今天在和同事查找菜单数据清CACHE时为什么出问题,其中有谈到MEMCACHED在集群环境下是否会保持数据的一致性,我的印象中好像memcached是有通过版本控制来实现的,不过只是有点模糊的印象,为了查了究竟也为了解决这个问题,我查了一下资料,不过顺便在这里也把菜单系统的CACHE实现在这里YY几句,知道的就当我废话了。
菜单系统有以下几个特点:
1、从其本身上来说,是属于不经常性变化的数据,读取的频率远大于写的频率;
2、从数据量上来看,也不大,也就是数据几K到十K左右的数据;
3、访问量比较大,每天都会有上百万的访问;
4、每个用户都会有菜单数据;
5、菜单系统是属于多个系统共用。
从以上的数据变化性比较小、数据量比较小、访问量比较频繁以、高命中率、属于分布式应用,我们是将菜单数据放到了Memcached中,当然,通常使用Memcached也最好符合这几个条件,否则会起不到相应的效果。
菜单系统本身有权限限制,用户与用户之间看到的菜单是不同的,不过这个权限没有定义到人头的千人千面,是通过用户分组来实现的,用户组的菜单CACHE键是通过当前用户组所具有的权限组合出来的,因为每个用户组可以查看的菜单是确定的,目前的方式是将每个用户组的菜单分别存放到MemCached中,也避免了频繁的读取数据库以及再次验权的问题。不过这个时候产生了一个新的问题,需要将CACHE中用户组的菜单给删除,这样才能够立即展示后台菜单管理系统对菜单操作的变化效果,但是用户组的CACHE键的数量及名称是根据用户拥用的权限组合起来的,也就是CACHE KEYS的值以及数量是不确定的,并且MEMCACHED本身不支持对键的遍历,也就是我们要清除一个菜单CACHE项,首先要知道这个CACHE项的键,菜单后台管理系统是一套单独的系统,为了清理CACHE中所有的菜单数据,后台系统必须要拿到所有用户组的CACHE KEYS,因而就需要有有一个地方存放这些键,现在采取的方式,将所有存放用户组权限菜单的CACHE KEYS,单独存放到一个CACHE项中,在后台菜单和管理系统中清理菜单之前,先获取这个存放所有用户组权限菜单的CACHE KEYS,再根据这个CACHE项中存放的所有CACHE KEYS,循环删除所有的用户组权限菜单CACHE项。
这里插一句memcached的命令执行方式,所有的被发送到memcached的单个命令是完全原子的。如针对同一份数据同时发送了一个set命令和一个get命令,它们不会影响对方。它们将被串行化、先后执行。即使在多线程模式,所有的命令都是原子的,除非程序有bug:)。但是命令序列不是原子的。如果通过get命令获取了一个item,修改了它,然后想把它set回memcached,我们不保证这个item没有被其他进程 (process,未必是操作系统中的进程)操作过。在并发的情况下,也可能覆写了一个被其他进程set的item。
也就是说大家都是操作的同一份存放在MEMCACHED中的CACHE KEYS,在并发的情况也会出现问题的,如原来MEMCACHED中的KES的内容为A,客户端C1和客户端C2都把A取了出来,C1往准备往其中加B,C2准备往其中加C,这就会造成C1和C2执行后的CACHE KEYS要么是AB要么是AC,而不会出现我们期望的ABC。这种情况,如果不是在集群环境中,而只是单机服务器,可以通过在写CACHE KEYS时增加同步锁,就可以解决问题,可是在集群环境中,同步锁是显然解决不了问题的。
不过,也不要悲观,出现问题时,总会有解决办法的出现,memcached 1.2.5以及更高版本,提供了gets和cas命令,它们可以解决上面的问题。如果使用gets命令查询某个key的item,memcached会返回该item当前值的唯一标识。如果覆写了这个item并想把它写回到memcached中,可以通过cas命令把那个唯一标识一起发送给 memcached。如果该item存放在memcached中的唯一标识与提供的一致,写操作将会成功。如果另一个进程在这期间也修改了这个 item,那么该item存放在memcached中的唯一标识将会改变,写操作就会失败。
本文出自:冯立彬的博客