public static bool Dequeue <T>(string queueName, MemcachedQueueItemHandler <T> handler, uint maxRetryTimes = 2) { if (maxRetryTimes < 0) { maxRetryTimes = 0; } if (string.IsNullOrWhiteSpace(queueName)) { throw new ArgumentNullException("queueName"); } if (CheckSerializable(typeof(T)) == false) { throw new NotSupportedException("不支持handler消息处理返回结果的类型\"" + typeof(T).FullName + "\",因为此类型不支持二进制序列化,如果要使用请在此类型的定义处添加[Serializable]的Attribute"); } queueName = queueName.Trim(); string queueId = GetQueueId(queueName); if (string.IsNullOrWhiteSpace(queueId)) // 队列列表里未找到指定名称的队列 { //handler(-1, null); return(false); } ulong en_index = GetIndexById(queueId, MemcachedQueueIndexType.Enqueue); if (en_index <= 0) // 还从未写入过,那么肯定读取不到数据了 { //handler(-1, null); return(false); } var client = MemcachedCacheHelper.GetClientInstance(); MemcachedClient.CasResult casRst; ulong unique; string k1 = BuildIndexKey(queueId, MemcachedQueueIndexType.Dequeue); ulong de_index; MemcachedQueueItem item; do { string de_index_string = client.Gets(k1, out unique) as string; // 读取上次出队列的index if (string.IsNullOrWhiteSpace(de_index_string) || de_index_string == "0" || ulong.TryParse(de_index_string, out de_index) == false || de_index <= 0) { // 说明之前还从未出过队列 de_index = 0; } de_index++; // 计算出本次应该出队列的item的index if (de_index > en_index) // 如果已经超过队列最近入的index了,说明队列已经读空了(所有item都读取过了) { //handler(-1, null); return(false); } string itemKey = BuildItemIndexKey(queueId, de_index); item = client.Get(itemKey) as MemcachedQueueItem; // 这里可能遇到一种极端的读写并发情况,即在Enqueue方法的地方先将EnqueueIndex增长上去了,但此index处的元素数据还未来得及Set写 // 然后本Dequeue方法就先拿到此index,然后Get数据,结果因为Set还未来得及写入所以获取数据未空 // 所以需要等待一会儿再试(期望这段等待时间内写入线程能完成此index处的数据的写入) int reTryTimes = 0; while (item == null) { Thread.Sleep(10); // 等待10ms再试着获取此index处的数据 item = client.Get(itemKey) as MemcachedQueueItem; reTryTimes++; if (reTryTimes >= maxRetryTimes) // 如果重试达到maxRetryTimes次(累计maxRetryTimes * 10ms了),那么基本上证明不是并发还未写入了,因为并发写入问题不可能等这么久还未写入进去,基本上确定是遇到网络问题或脏数据,或者memcached服务挂了 { break; } // 基本上Dequeue操作都是单线程慢慢处理,所以对执行时间要求不是那么高,500ms的延迟等待可以接受(并且是要遇到item==null的很极端的读写并发,或者是遇到了脏数据,发生的几率都很小) } if (unique == 0) // 说明还不存在,需要新增 { casRst = client.Add(k1, de_index.ToString(CultureInfo.InvariantCulture)) ? MemcachedClient.CasResult.Stored : MemcachedClient.CasResult.NotStored; } else { casRst = client.CheckAndSet(k1, de_index.ToString(CultureInfo.InvariantCulture), unique); // 乐观锁,满足条件(没有人优先改动过)则更新缓存,如果更新成功则锁定了de_index这个索引值,其它线程无法再获取到这个index的item了 } } while ( casRst != MemcachedClient.CasResult.Stored || // 乐观锁,这种写法防止并发冲突 item == null // 发生item == null这种情况可能是脏数据了,则继续找下一个索引处的元素 ); if (item == null) // 说明没有找到有效数据 { //handler(-1, null); return(false); } string itemProcessdResultKey = BuildItemIndexKeyForProcessResult(queueId, de_index); T rst = default(T); Exception ex = null; try { rst = handler((long)de_index, item); ex = null; } catch (Exception ex1) { rst = default(T); ex = ex1; throw; } finally { MemcachedQueueItemProcessResult pr = new MemcachedQueueItemProcessResult { Exception = ex, Result = rst }; client.Set(itemProcessdResultKey, pr, DateTime.Now.AddMinutes(120)); } return(true); }
public static MemcachedQueueItem GetItemWithProcessResult(string queueName, long index, out MemcachedQueueItemProcessResult processResult) { if (string.IsNullOrWhiteSpace(queueName)) { throw new ArgumentNullException("queueName"); } if (index <= 0) { processResult = null; return(null); } queueName = queueName.Trim(); var queueId = GetQueueId(queueName); if (string.IsNullOrWhiteSpace(queueId)) { processResult = null; return(null); } string itemKey = BuildItemIndexKey(queueId, (ulong)index); string itemProcessdResultKey = BuildItemIndexKeyForProcessResult(queueId, (ulong)index); var client = MemcachedCacheHelper.GetClientInstance(); var objs = client.Get(new string[] { itemKey, itemProcessdResultKey }); MemcachedQueueItem item = null; processResult = null; if (objs != null) { if (objs.Length > 0) { item = objs[0] as MemcachedQueueItem; } if (objs.Length > 1) { processResult = objs[1] as MemcachedQueueItemProcessResult; } } return(item); }