/// <summary> /// Must call this periodically to move work items from priority queue to pending queue /// </summary> public bool PrepareNextWorkItem() { //harvest zombies every 5 minutes var now = DateTime.UtcNow; var ts = now - harvestTime; if (ts.TotalMinutes > 5) { HarvestZombies(); harvestTime = now; } using (var disposableClient = clientManager.GetDisposableClient <SerializingRedisClient>()) { var client = disposableClient.Client; //1. get next workItemId, or return if there isn't one var smallest = client.ZRangeWithScores(workItemIdPriorityQueue, 0, 0); if (smallest == null || smallest.Length <= 1 || RedisNativeClient.ParseDouble(smallest[1]) == CONVENIENTLY_SIZED_FLOAT) { return(false); } var workItemId = client.Deserialize(smallest[0]) as string; // lock work item id var lockKey = queueNamespace.GlobalLockKey(workItemId); using (var disposableLock = new DisposableDistributedLock(client, lockKey, lockAcquisitionTimeout, lockTimeout)) { // if another client has queued this work item id, // then the work item id score will be set to CONVENIENTLY_SIZED_FLOAT // so we return false in this case var score = client.ZScore(workItemIdPriorityQueue, smallest[0]); if (score == CONVENIENTLY_SIZED_FLOAT) { return(false); } using (var pipe = client.CreatePipeline()) { var rawWorkItemId = client.Serialize(workItemId); // lock work item id in priority queue pipe.QueueCommand( r => ((RedisNativeClient)r).ZAdd(workItemIdPriorityQueue, CONVENIENTLY_SIZED_FLOAT, smallest[0])); // track dequeue lock id pipe.QueueCommand(r => ((RedisNativeClient)r).SAdd(dequeueIdSet, rawWorkItemId)); // push into pending set pipe.QueueCommand(r => ((RedisNativeClient)r).LPush(pendingWorkItemIdQueue, rawWorkItemId)); pipe.Flush(); } } } return(true); }
/// <summary> /// Queue incoming messages /// </summary> /// <param name="workItem"></param> /// <param name="workItemId"></param> public void Enqueue(string workItemId, T workItem) { using (var disposableClient = clientManager.GetDisposableClient <SerializingRedisClient>()) { var client = disposableClient.Client; var lockKey = queueNamespace.GlobalLockKey(workItemId); using (var disposableLock = new DisposableDistributedLock(client, lockKey, lockAcquisitionTimeout, lockTimeout)) { using (var pipe = client.CreatePipeline()) { pipe.QueueCommand(r => ((RedisNativeClient)r).RPush(queueNamespace.GlobalCacheKey(workItemId), client.Serialize(workItem))); pipe.QueueCommand(r => ((RedisNativeClient)r).ZIncrBy(workItemIdPriorityQueue, -1, client.Serialize(workItemId))); pipe.Flush(); } } } }
/// <summary> /// Unlock work item id, so other servers can process items for this id /// </summary> /// <param name="workItemId"></param> private void Unlock(string workItemId) { if (workItemId == null) { return; } var key = queueNamespace.GlobalCacheKey(workItemId); var lockKey = queueNamespace.GlobalLockKey(workItemId); using (var disposableClient = clientManager.GetDisposableClient <SerializingRedisClient>()) { var client = disposableClient.Client; using (var disposableLock = new DisposableDistributedLock(client, lockKey, lockAcquisitionTimeout, lockTimeout)) { var len = client.LLen(key); using (var pipe = client.CreatePipeline()) { //untrack dequeue lock pipe.QueueCommand(r => ((RedisNativeClient)r).SRem(dequeueIdSet, client.Serialize(workItemId))); // update priority queue if (len == 0) { pipe.QueueCommand(r => ((RedisNativeClient)r).ZRem(workItemIdPriorityQueue, client.Serialize(workItemId))); } else { pipe.QueueCommand(r => ((RedisNativeClient)r).ZAdd(workItemIdPriorityQueue, len, client.Serialize(workItemId))); } pipe.Flush(); } } } }