性能优化-数据读写分离
5.17 数据读写分离
大部分业务系统的数据都是写少读多的场景,为了保证并发下的读写数据安全,通常会对这些数据加锁。常见的方式是使用读写锁。另外一种提供更高效的读取数据方式采用无锁方式,即当数据更新的时候,复制一份完整数据,在复制数据上更新,最后替换为原来的数据。比如Java的CopyOnWriteArrayList 就是这种实现,如下是add操作
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}代码3行getArray返回当前数据的引用,并在代码5行,复制一份新的数据,其容量比原来数据增加1,代码6行设置新的数据,代码7行,将原有数据的引用替换成现在的数据es。
注意到尽管代码2行加锁,但这是同步并发写操作。此处加锁不影响读操作。其get方法如下:
public E get(int index) {
return elementAt(getArray(), index);
}
static <E> E elementAt(Object[] a, int index) {
return (E) a[index];
}读写分离使得高性能数据同步维护比较简单,假设你的业务系统有多种数据需要缓存,比如角色,权限,和字典表。假定设定每1小时刷新,你如何更新这些数据呢?
需要考虑,如果缓存更新过程中,用户访问这些数据,会造成数据不一致,比如后台管理系统新增角色后,角色缓存更新完毕,但权限数据还未来得及更新,那么用户访问时候读取缓存,可能出现角色信息无对应的权限信息。
比较简单的方法采用读写分离,使用新的缓存代替旧的缓存,如下,定义一个CacheManager,维护最新的缓存
public class CacheManager{
Cache cache;
public Cache getCache(){return cache;}
public void setNewCache(Cache cache){this.cache=cache}
}
public class Cache{
HashMap roleInfo =...;
HashMap permissionInfo =...;
HashMap dictInfo = ......
public void init(){}
public HashMap getRoles(){return roleInfo;}
}当触发缓存更新时候,可以重新创建一个Cache,并替换原有Cache
//定时调度
Cache cache = new Cache();
cache.init();
cacheManager.setNewCache(cache);在分布式系统架构中,读写分离以并结合数据分片,是实现大流量的分布式系统高可用,高性能的战术。比如电商系统,通常把商品,库存信息存放在一个固定64分片的Redis里。如果单分片的读访问量大,负载高,则可以配置Redis从节点,读操作能路由到Redis从节点

物联网中,查询设备的状态信息,也不在查询设备的连接的网关,这是因为查询设备信息的流量非常大,设备网关应该专注于设备连接。因此把设备状态维护到Redis里,其他系统访问Redis获取设备信息

