/// <summary> /// Add/update an entry in the cache /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key">Key used to locate value in the cache</param> /// <param name="value"></param> /// <param name="ttl"></param> public void Set <T>(string key, T value, TimeSpan ttl) { // parameter validation if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException(nameof(key)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } if (ttl.TotalSeconds < 1) { throw new ArgumentNullException(nameof(ttl)); } // Get the current timestamp before we do anything else. Decrement it by one so any sync messages processed during // this method call will force an immediate expiration of the key's hash slot var timestamp = Stopwatch.GetTimestamp() - 1; var keyHashSlot = HashSlotCalculator.CalculateHashSlot(key); byte[] serializedData = null; // Serialize the data and write to Redis using (MemoryStream ms = new MemoryStream()) { _serializationProvider.Serialize <T>(ms, value); serializedData = ms.ToArray(); } // Execute the Redis SET and PUBLISH operations in one round trip using Lua // (Could use StackExchange.Redis batch here, instead) string luaScript = @" redis.call('SET', KEYS[1], ARGV[1], 'EX', ARGV[2]) redis.call('PUBLISH', ARGV[3], ARGV[4]) "; var scriptArgs = new RedisValue[4]; scriptArgs[0] = serializedData; scriptArgs[1] = ttl.TotalSeconds; scriptArgs[2] = SYNC_CHANNEL_NAME; scriptArgs[3] = DataSyncMessage.Create(_instanceId, keyHashSlot).Serialize(); _redisDb.ScriptEvaluate(luaScript, new RedisKey[] { key }, scriptArgs); // Update the in-process cache _inProcessCache.Set(key, new LocalCacheEntry <T>(keyHashSlot, timestamp, value), DateTimeOffset.UtcNow.Add(ttl)); }
/// <summary> /// Message handler for hash key invalidation messages /// </summary> /// <param name="channel">Redis pub/sub channel name</param> /// <param name="message">Pub/sub message</param> private void DataSynchronizationMessageHandler(RedisChannel channel, RedisValue message) { // Early out, if channel name doesn't match our sync channel if (string.Compare(channel, SYNC_CHANNEL_NAME, StringComparison.InvariantCultureIgnoreCase) != 0) { return; } // otherwise, deserialize the message var dataSyncMessage = DataSyncMessage.Deserialize(message); // and update the appropriate _lastUpdated element with the current timestamp // Invalidate.Exchange() would make more sense here, but the lock statement // makes the purpose more evident for an example lock (_lastUpdated) { _lastUpdated[dataSyncMessage.KeyHashSlot] = Stopwatch.GetTimestamp(); } }