Memcached的CAS机制的实现
来源:互联网 发布:淘宝虚拟物品怎么收货 编辑:程序博客网 时间:2024/06/06 14:02
Memcached的CAS机制解决的问题及其原理:
1. 实现了Check-and-Set原子操作功能;
2. 其使用方式为:首先使用gets指令一个key-value及key对应value的版本号;其次操作产生新的value值;最后使用cas指令重新提交key-value,并附带刚刚获得到的版本号;
3. 当服务端判断cas操作中的版本号不是最新的时,则认为改key的值已经被修改,本次cas操作失败。程序设计人员通过CAS机制可实现自增和自减的原子操作;
Memcached的CAS机制的实现:
1. Memcached CAS的核心是一个64-bit唯一的版本。服务端会为每个key生成一个64-bit唯一的整数值作为版本号,并保存在item结构体中,具体的存储结构见结构体item的定义:
typedef struct _stritem { struct _stritem *next; struct _stritem *prev; struct _stritem *h_next; /* hash chain next */ rel_time_t time; /* least recent access */ rel_time_t exptime; /* expire time */ int nbytes; /* size of data */ unsigned short refcount; uint8_t nsuffix; /* length of flags-and-length string */ uint8_t it_flags; /* ITEM_* above */ uint8_t slabs_clsid;/* which slab class we're in */ uint8_t nkey; /* key length, w/terminating null and padding */ /* this odd type prevents type-punning issues when we do * the little shuffle to save space when not using CAS. */ union { uint64_t cas; char end; } data[]; /* if it_flags & ITEM_CAS we have 8 bytes CAS */ /* then null-terminated key */ /* then " flags length\r\n" (no terminating null) */ /* then data with terminating \r\n (no terminating null; it's binary!) */} item;
cas的版本号的值存储在item中key之前的位置处。
2. Memcached CAS的开启和关闭由settings.use_cas选项来控制。结构体struct settings存储了Memcached服务器的各种选项存储,其定义的代码如下:
struct settings { size_t maxbytes; int maxconns; int port; int udpport; char *inter; int verbose; rel_time_t oldest_live; /* ignore existing items older than this */ int evict_to_free; char *socketpath; /* path to unix socket if using local socket */ int access; /* access mask (a la chmod) for unix domain socket */ double factor; /* chunk size growth factor */ int chunk_size; int num_threads; /* number of worker (without dispatcher) libevent threads to run */ int num_threads_per_udp; /* number of worker threads serving each udp socket */ char prefix_delimiter; /* character that marks a key prefix (for stats) */ int detail_enabled; /* nonzero if we're collecting detailed stats */ int reqs_per_event; /* Maximum number of io to process on each io-event. */ bool use_cas; enum protocol binding_protocol; int backlog; int item_size_max; /* Maximum item size, and upper end for slabs */ bool sasl; /* SASL on/off */ bool maxconns_fast; /* Whether or not to early close connections */ bool slab_reassign; /* Whether or not slab reassignment is allowed */ bool slab_automove; /* Whether or not to automatically move slabs */ int hashpower_init; /* Starting hash power level */};
默认情况下settings.use_cas的值为true,在settings_init调用的时候进行初始化设置:
static void settings_init(void) { settings.use_cas = true; ... ...}
当启动Memcached服务时,如果指定了-C选项,则关闭cas机制,改部分代码在main函数中:
int main (int argc, char **argv) { ... ... case 'C' : settings.use_cas = false; break; ... ...}
3. Memcached在分配item的内存时会根据是否开启settings.use_cas选项来分配内存和设置item->flags:
item *do_item_alloc(char *key, const size_t nkey, const int flags, const rel_time_t exptime, const int nbytes) { ... ... if (settings.use_cas) { ntotal += sizeof(uint64_t); } ... ... it->it_flags = settings.use_cas ? ITEM_CAS : 0; ... ...}
4. Memcached CAS机制的相关实现:
在Memcached中,所有的命令处理逻辑在process_command中实现,该函数的实现根据不同的命令又细分为不同的处理函数。
gets指令调用process_get_command,该函数当最后的参数为true时,代表处理带cas的get操作,会在response中包含cas的值。
cas指令调用process_update_command,该函数当最后的参数true代表,处理带cas的update操作,最终会调用do_store_item函数来处理update操作。相关的cas的逻辑代码有:
/* validate cas operation */ if(old_it == NULL) { // LRU expired stored = NOT_FOUND; pthread_mutex_lock(&c->thread->stats.mutex); c->thread->stats.cas_misses++; pthread_mutex_unlock(&c->thread->stats.mutex); } else if (ITEM_get_cas(it) == ITEM_get_cas(old_it)) { // cas validates // it and old_it may belong to different classes. // I'm updating the stats for the one that's getting pushed out pthread_mutex_lock(&c->thread->stats.mutex); c->thread->stats.slab_stats[old_it->slabs_clsid].cas_hits++; pthread_mutex_unlock(&c->thread->stats.mutex); item_replace(old_it, it, hv); stored = STORED; } else { pthread_mutex_lock(&c->thread->stats.mutex); c->thread->stats.slab_stats[old_it->slabs_clsid].cas_badval++; pthread_mutex_unlock(&c->thread->stats.mutex); if(settings.verbose > 1) { fprintf(stderr, "CAS: failure: expected %llu, got %llu\n", (unsigned long long)ITEM_get_cas(old_it), (unsigned long long)ITEM_get_cas(it)); } stored = EXISTS; }
cas指令的cas版本值的递增在do_item_link函数中完成:
int do_item_link(item *it, const uint32_t hv) { ... ... /* Allocate a new CAS ID on link. */ ITEM_set_cas(it, (settings.use_cas) ? get_cas_id() : 0); ... ...}
5. 其它:
除了显式的gets和cas操作为,incr/decr操作也会使用cas机制;
append/prepend操作也会涉及到cas的相关操作;
最近笔者自己的项目中,遇到了乐观锁的需求。memcache天然的支持这种并发原语,即:GETS和CAS操作。
我们为什么要使用这种并发原语呢?如果是单机版的,我们可以通过通过加锁同步就可以解决执行时序的问题。但是我们的应用是分布式的,无状态的应用服务器通过负载均衡,部署到了多台。加锁也解决不了多台服务器的时序执行。
如果不采用CAS,则有如下的情景:
第一步,A取出数据对象X;
第二步,B取出数据对象X;
第三步,B修改数据对象X,并将其放入缓存;
第四步,A修改数据对象X,并将其放入缓存。
我们可以发现,第四步中会产生数据写入冲突。
如果采用CAS协议,则是如下的情景。
第一步,A取出数据对象X,并获取到CAS-ID1;
第二步,B取出数据对象X,并获取到CAS-ID2;
第三步,B修改数据对象X,在写入缓存前,检查CAS-ID与缓存空间中该数据的CAS-ID是否一致。结果是“一致”,就将修改后的带有CAS-ID2的X写入到缓存。
第四步,A修改数据对象Y,在写入缓存前,检查CAS-ID与缓存空间中该数据的CAS-ID是否一致。结果是“不一致”,则拒绝写入,返回存储失败。
我们可以通过重试,或者其他业务逻辑解决第四步设置失败的问题。
没有CAS的方案
if (memcache.get(key) == null) { // 3 min timeout to avoid mutex holder crash if (memcache.add(key_mutex, 3 * 60 * 1000) == true) { value = db.get(key); memcache.set(key, value); memcache.delete(key_mutex); } else { sleep(50); retry(); }}
最初的解决方案:
利用memcached的add操作的原子性来控制并发,具体方式如下:
1.申请锁:在校验是否创建过活动前,执行add操作key为key,如果add操作失败,则表示有另外的进程在并发的为该key创建活动,返回创建失败。否则表示无并发
2.执行创建活动
3.释放锁:创建活动完成后,执行delete操作,删除该key。
问题:
1.memcached中存放的值有有效期,即过期后自动失效,如add过M1后,M1失效,可以在此add成功
2.即使通过配置,可以使memcached永久有效,即不设有效期,memcached有容量限制,当容量不够后会进行自动替换,即有可能add过M1后,M1被其他key值置换掉,则再次add可以成功。
3.此外,memcached是基于内存的,掉电后数据会全部丢失,导致重启后所有memberId均可重新add。
解决方案
针对上述的几个问题,根本原因是add操作有时效性,过期,被替换,重启,都会是原来的add操作失效。解决该问题有方法
1.减轻时效性的影响,使用memcached CAS(check and set)方式。
使用CAS的方案
CAS的基本原理
基本原理非常简单,一言以蔽之,就是“版本号”。每个存储的数据对象,多有一个版本号。我们可以从下面的例子来理解:
package com.home.phl;import net.rubyeye.xmemcached.CASOperation;import net.rubyeye.xmemcached.GetsResponse;import net.rubyeye.xmemcached.MemcachedClient;import net.rubyeye.xmemcached.MemcachedClientBuilder;import net.rubyeye.xmemcached.XMemcachedClientBuilder;import net.rubyeye.xmemcached.command.BinaryCommandFactory;import net.rubyeye.xmemcached.utils.AddrUtil;/** * 参考文章 * @author piaohailin * @date 2014-6-28*/public class TestCAS { public static void main(String[] args) throws Exception { MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses("192.168.56.3:11211")); builder.setCommandFactory(new BinaryCommandFactory()); MemcachedClient memcachedClient = builder.build(); memcachedClient.set("key", 0, value); final GetsResponse<Object> response = memcachedClient.gets("key"); System.out.println(response.getValue()); System.out.println(response.getCas()); boolean flag = memcachedClient.cas("key", new CASOperation<Object>() { @Override public int getMaxTries() { return 3; } @Override public Object getNewValue(long currentCAS, Object currentValue) { if (currentCAS != response.getCas()) { throw new RuntimeException("CAS不对"); } value = db.get(key); return value; } }); System.out.println(flag); memcachedClient.shutdown(); }}
- Memcached的CAS机制的实现
- Memcached的CAS机制的实现
- Memcached的CAS机制的实现
- Memcached的CAS机制的实现
- Memcached的CAS协议
- Memcached的CAS协议
- tomcat8 + nginx + memcached + cas 实现负载均衡的配置
- Java的CAS机制
- 基于memcached的php锁机制实现
- 基于memcached的CAS单点登录集群
- Memcached的使用与CAS命令
- 实战Memcached缓存系统(4)Memcached的CAS协议
- 实战Memcached缓存系统(4)Memcached的CAS协议
- 实战Memcached缓存系统(4)Memcached的CAS协议
- 实战Memcached缓存系统(4)Memcached的CAS协议
- 实战Memcached缓存系统(4)Memcached的CAS协议
- 基于CAS实现单点登录(SSO):配置CAS服务端的数据库查询认证机制
- [Memcached]Memcached 的删除机制和发展方向
- 【HTML 初学】3、HTML标题
- 【Java编程思想】三、操作符
- 【Java编程思想 - 练习】吸血鬼数字
- 算法笔记
- 【Android 初学】5、控件--ImageView的使用方法
- Memcached的CAS机制的实现
- 【Android 初学】6、线性布局--深入LinearLayout
- XShell连不上本机虚拟机报:Connection closed by foreign host.
- 【Android 初学】7、相对布局--初步入门
- Exception details are logged in Window > Show View > Error Log
- 【Android 初学】8、进度条(ProgressBar、SeekBar、RatingBar)
- 【Android 初学】9、Activity生命周期
- Selenium官方文档:Selenium RC 安装步骤
- 【Android 初学】10、Intent对象的使用