Exemple #1
0
        /// <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));
        }
Exemple #2
0
        /// <summary>
        /// Retrieve an item from the cache
        /// </summary>
        /// <typeparam name="T">Object type to return</typeparam>
        /// <param name="key">Key</param>
        /// <returns>Object of type T associated with the key</returns>
        public T Get <T>(string key)
        {
            // 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;

            int keyHashSlot = -1; // -1 is used as a sentinel value used to determine if the key's hash slot has already been computed

            // attempt to retreive value from the in-process cache

            var inProcessCacheEntry = _inProcessCache.Get(key) as LocalCacheEntry <T>;

            if (inProcessCacheEntry != null)
            {
                // found the entry in the in-process cache, now
                // need to check if the entry may be stale and we
                // need to re-read from Redis

                keyHashSlot = inProcessCacheEntry.KeyHashSlot;

                // Check the timestamp of the cache entry versus the _lastUpdated array
                // If the timestamp of the cache entry is greater than what's in _lastUpdated,
                // then we can just return the value from the in-process cache

                // Could use and Interlocked operation here, but lock is a more obvious in intent

                lock (_lastUpdated)
                {
                    if (_lastUpdated[keyHashSlot] < inProcessCacheEntry.Timestamp)
                    {
                        return((T)inProcessCacheEntry.Data);
                    }
                }
            }

            // if we've made it to here, the key is either not in the in-process cache or
            // the in-process cache entry is stale.  In either case we need to hit Redis to
            // get the correct value, and the key's remaining TTL in Redis

            T value = default(T);

            string luaScript = @"
        local result={}
        result[1] = redis.call('GET', KEYS[1])
        result[2] = redis.call('TTL', KEYS[1])
        return result;
      ";

            RedisValue[] results = (RedisValue[])_redisDb.ScriptEvaluate(luaScript, new RedisKey[] { key });

            if (!results[0].IsNull)
            {
                var serializedData = (byte[])results[0];

                if (serializedData.Length > 0)
                {
                    // Deserialize the bytes returned from Redis

                    using (MemoryStream ms = new MemoryStream(serializedData))
                    {
                        value = _serializationProvider.Deserialize <T>(ms);
                    }

                    // Don't want to have to recalculate the hashslot twice, so test if it's already
                    // been computed

                    if (keyHashSlot == -1)
                    {
                        keyHashSlot = HashSlotCalculator.CalculateHashSlot(key);
                    }

                    // Update the in-proces cache with the value retrieved from Redis

                    _inProcessCache.Set(key, new LocalCacheEntry <T>((ushort)keyHashSlot, timestamp, value), DateTimeOffset.UtcNow.AddSeconds((double)results[1]));
                }
            }

            return((T)inProcessCacheEntry.Data);
        }