DotNet Core 使用 StackExchange.Redis 簡單封裝和實現分佈式鎖

前言

公司的項目之前一直使用 CSRedis 這個類庫來操做 Redis,最近增長了一些新功能,會存儲一些比較大的數據,內測的時候發現其中有兩臺服務器會莫名的報錯 Unexpected response type: Status (expecting Bulk)Connection was not opened,最後定位到問題是 Redis 寫入和讀取數據的時候發生的錯誤,弄了兩臺新服務器從新部署仍是沒有解決,在 GitHub 上向做者發了 issues,做者說升級類庫能夠解決,嘗試了一下也沒有解決,無奈之下只好寫了個小程序用 StackExchange.Redis 在服務器上作讀寫測試,發現沒有任何問題,防止耽誤上線只好換成了 StackExchange.Redis,通過兩天內部測試,一切操做均未發現異常。html

StackExchange.Redis 封裝

RedisClient 類:git

/// <summary>
    /// 封裝 Redis 相關操做的方法類。
    /// </summary>
    public class RedisClient : IRedisClient
    {
        private readonly IConnectionMultiplexer _connectionMultiplexer;
        private readonly IDatabase _database;

        /// <summary>
        /// 初始化 <see cref="RedisClient"/> 類的新實例。
        /// </summary>
        /// <param name="connectionMultiplexer">鏈接多路複用器。</param>
        public RedisClient(IConnectionMultiplexer connectionMultiplexer)
        {
            _connectionMultiplexer = connectionMultiplexer;
            if (_connectionMultiplexer != null && _connectionMultiplexer.IsConnected)
            {
                _database = _connectionMultiplexer.GetDatabase();
            }
            else
            {
                throw new Exception("Redis is not Connected");
            }

        }

        #region 同步方法...

        /// <summary>
        /// 添加一個字符串對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="value">值。</param>
        /// <param name="expiry">過時時間(時間間隔)。</param>
        /// <returns>返回是否執行成功。</returns>
        public bool Set(string key, string value, TimeSpan? expiry = null)
        {
            return _database.StringSet(key, value, expiry);
        }

        /// <summary>
        /// 添加一個字符串對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <typeparam name="T">對象的類型。</typeparam>
        /// <param name="value">值。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>返回是否執行成功。</returns>
        public bool Set(string key, string value, int seconds)
        {
            TimeSpan expiry = TimeSpan.FromSeconds(seconds);
            return _database.StringSet(key, value, expiry);
        }

        /// <summary>
        /// 添加一個對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <typeparam name="T">對象的類型。</typeparam>
        /// <param name="value">值。</param>
        /// <param name="expiry">過時時間(時間間隔)。</param>
        /// <returns>返回是否執行成功。</returns>
        public bool Set<T>(string key, T value, TimeSpan? expiry = null)
        {
            var data = JsonConvert.SerializeObject(value);
            return _database.StringSet(key, data, expiry);
        }

        /// <summary>
        /// 添加一個對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <typeparam name="T">對象的類型。</typeparam>
        /// <param name="value">值。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>返回是否執行成功。</returns>
        public bool Set<T>(string key, T value, int seconds)
        {
            TimeSpan expiry = TimeSpan.FromSeconds(seconds);
            var data = JsonConvert.SerializeObject(value);
            return _database.StringSet(key, data, expiry);
        }

        /// <summary>
        /// 獲取一個對象。
        /// </summary>
        /// <param name="key">值。</param>
        /// <returns>返回對象的值。</returns>
        public T Get<T>(string key)
        {
            string json = _database.StringGet(key);
            if (string.IsNullOrWhiteSpace(json))
            {
                return default(T);
            }
            T entity = JsonConvert.DeserializeObject<T>(json);
            return entity;
        }

        /// <summary>
        /// 獲取一個字符串對象。
        /// </summary>
        /// <param name="key">值。</param>
        /// <returns>返回對象的值。</returns>
        public string Get(string key)
        {
            return _database.StringGet(key);
        }

        /// <summary>
        /// 刪除一個對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <returns>返回是否執行成功。</returns>
        public bool Delete(string key)
        {
            return _database.KeyDelete(key);
        }

        /// <summary>
        /// 返回鍵是否存在。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <returns>返回鍵是否存在。</returns>
        public bool Exists(string key)
        {
            return _database.KeyExists(key);
        }

        /// <summary>
        /// 設置一個鍵的過時時間。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="expiry">過時時間(時間間隔)。</param>
        /// <returns>返回是否執行成功。</returns>
        public bool SetExpire(string key, TimeSpan? expiry)
        {
            return _database.KeyExpire(key, expiry);
        }

        /// <summary>
        /// 設置一個鍵的過時時間。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>返回是否執行成功。</returns>
        public bool SetExpire(string key, int seconds)
        {
            TimeSpan expiry = TimeSpan.FromSeconds(seconds);
            return _database.KeyExpire(key, expiry);
        }

        #endregion

        #region 異步方法...

        /// <summary>
        /// 異步添加一個字符串對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="value">值。</param>
        /// <param name="expiry">過時時間(時間間隔)。</param>
        /// <returns>返回是否執行成功。</returns>
        public async Task<bool> SetAsync(string key, string value, TimeSpan? expiry = null)
        {
            return await _database.StringSetAsync(key, value, expiry);
        }

        /// <summary>
        /// 異步添加一個字符串對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="value">值。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>返回是否執行成功。</returns>
        public async Task<bool> SetAsync(string key, string value, int seconds)
        {
            TimeSpan expiry = TimeSpan.FromSeconds(seconds);
            return await _database.StringSetAsync(key, value, expiry);
        }

        /// <summary>
        /// 異步添加一個對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <typeparam name="T">對象的類型。</typeparam>
        /// <param name="value">值。</param>
        /// <returns>返回是否執行成功。</returns>
        public async Task<bool> SetAsync<T>(string key, T value)
        {
            var data = JsonConvert.SerializeObject(value);
            return await _database.StringSetAsync(key, data);
        }

        /// <summary>
        /// 異步獲取一個對象。
        /// </summary>
        /// <typeparam name="T">對象的類型。</typeparam>
        /// <param name="key">值。</param>
        /// <returns>返回對象的值。</returns>
        public async Task<T> GetAsync<T>(string key)
        {
            string json = await _database.StringGetAsync(key);
            if (string.IsNullOrWhiteSpace(json))
            {
                return default(T);
            }
            T entity = JsonConvert.DeserializeObject<T>(json);
            return entity;
        }

        /// <summary>
        /// 異步獲取一個字符串對象。
        /// </summary>
        /// <param name="key">值。</param>
        /// <returns>返回對象的值。</returns>
        public async Task<string> GetAsync(string key)
        {
            return await _database.StringGetAsync(key);
        }

        /// <summary>
        /// 異步刪除一個對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <returns>返回是否執行成功。</returns>
        public async Task<bool> DeleteAsync(string key)
        {
            return await _database.KeyDeleteAsync(key);
        }

        /// <summary>
        /// 異步設置一個鍵的過時時間。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>返回是否執行成功。</returns>
        public async Task<bool> SetExpireAsync(string key, int seconds)
        {
            TimeSpan expiry = TimeSpan.FromSeconds(seconds);
            return await _database.KeyExpireAsync(key, expiry);
        }

        /// <summary>
        /// 異步設置一個鍵的過時時間。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="expiry">過時時間(時間間隔)。</param>
        /// <returns>返回是否執行成功。</returns>
        public async Task<bool> SetExpireAsync(string key, TimeSpan? expiry)
        {
            return await _database.KeyExpireAsync(key, expiry);
        }

        #endregion

        #region 分佈式鎖...

        /// <summary>
        /// 分佈式鎖 Token。
        /// </summary>
        private static readonly RedisValue LockToken = Environment.MachineName;

        /// <summary>
        /// 獲取鎖。
        /// </summary>
        /// <param name="key">鎖名稱。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>是否已鎖。</returns>
        public bool Lock(string key, int seconds)
        {
            return _database.LockTake(key, LockToken, TimeSpan.FromSeconds(seconds));
        }

        /// <summary>
        /// 釋放鎖。
        /// </summary>
        /// <param name="key">鎖名稱。</param>
        /// <returns>是否成功。</returns>
        public bool UnLock(string key)
        {
            return _database.LockRelease(key, LockToken);
        }

        /// <summary>
        /// 異步獲取鎖。
        /// </summary>
        /// <param name="key">鎖名稱。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>是否成功。</returns>
        public async Task<bool> LockAsync(string key, int seconds)
        {
            return await _database.LockTakeAsync(key, LockToken, TimeSpan.FromSeconds(seconds));
        }

        /// <summary>
        /// 異步釋放鎖。
        /// </summary>
        /// <param name="key">鎖名稱。</param>
        /// <returns>是否成功。</returns>
        public async Task<bool> UnLockAsync(string key)
        {
            return await _database.LockReleaseAsync(key, LockToken);
        }

        #endregion
    }

IRedisClient 類:github

/// <summary>
    /// 封裝 Redis 相關操做的方法。
    /// </summary>
    public interface IRedisClient
    {
        /// <summary>
        /// 添加一個字符串對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="value">值。</param>
        /// <param name="expiry">過時時間(時間間隔)。</param>
        /// <returns>返回是否執行成功。</returns>
        bool Set(string key, string value, TimeSpan? expiry = null);

        /// <summary>
        /// 添加一個字符串對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <typeparam name="T">對象的類型。</typeparam>
        /// <param name="value">值。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>返回是否執行成功。</returns>
        bool Set(string key, string value, int seconds);

        /// <summary>
        /// 添加一個對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <typeparam name="T">對象的類型。</typeparam>
        /// <param name="value">值。</param>
        /// <param name="expiry">過時時間(時間間隔)。</param>
        /// <returns>返回是否執行成功。</returns>
        bool Set<T>(string key, T value, TimeSpan? expiry = null);

        /// <summary>
        /// 添加一個對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <typeparam name="T">對象的類型。</typeparam>
        /// <param name="value">值。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>返回是否執行成功。</returns>
        bool Set<T>(string key, T value, int seconds);

        /// <summary>
        /// 獲取一個對象。
        /// </summary>
        /// <param name="key">值。</param>
        /// <returns>返回對象的值。</returns>
        T Get<T>(string key);

        /// <summary>
        /// 獲取一個字符串對象。
        /// </summary>
        /// <param name="key">值。</param>
        /// <returns>返回對象的值。</returns>
        string Get(string key);

        /// <summary>
        /// 刪除一個對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <returns>返回是否執行成功。</returns>
        bool Delete(string key);

        /// <summary>
        /// 返回鍵是否存在。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <returns>返回鍵是否存在。</returns>
        bool Exists(string key);

        /// <summary>
        /// 設置一個鍵的過時時間。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="expiry">過時時間(時間間隔)。</param>
        /// <returns>返回是否執行成功。</returns>
        bool SetExpire(string key, TimeSpan? expiry);

        /// <summary>
        /// 設置一個鍵的過時時間。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>返回是否執行成功。</returns>
        bool SetExpire(string key, int seconds);

        /// <summary>
        /// 異步添加一個字符串對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="value">值。</param>
        /// <param name="expiry">過時時間(時間間隔)。</param>
        /// <returns>返回是否執行成功。</returns>
        Task<bool> SetAsync(string key, string value, TimeSpan? expiry = null);

        /// <summary>
        /// 異步添加一個字符串對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="value">值。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>返回是否執行成功。</returns>
        Task<bool> SetAsync(string key, string value, int seconds);

        /// <summary>
        /// 異步添加一個對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <typeparam name="T">對象的類型。</typeparam>
        /// <param name="value">值。</param>
        /// <returns>返回是否執行成功。</returns>
        Task<bool> SetAsync<T>(string key, T value);

        /// <summary>
        /// 異步獲取一個對象。
        /// </summary>
        /// <typeparam name="T">對象的類型。</typeparam>
        /// <param name="key">值。</param>
        /// <returns>返回對象的值。</returns>
        Task<T> GetAsync<T>(string key);

        /// <summary>
        /// 異步獲取一個字符串對象。
        /// </summary>
        /// <param name="key">值。</param>
        /// <returns>返回對象的值。</returns>
        Task<string> GetAsync(string key);

        /// <summary>
        /// 異步刪除一個對象。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <returns>返回是否執行成功。</returns>
        Task<bool> DeleteAsync(string key);

        /// <summary>
        /// 異步設置一個鍵的過時時間。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>返回是否執行成功。</returns>
        Task<bool> SetExpireAsync(string key, int seconds);

        /// <summary>
        /// 異步設置一個鍵的過時時間。
        /// </summary>
        /// <param name="key">鍵。</param>
        /// <param name="expiry">過時時間(時間間隔)。</param>
        /// <returns>返回是否執行成功。</returns>
        Task<bool> SetExpireAsync(string key, TimeSpan? expiry);

        #region 分佈式鎖...

        /// <summary>
        /// 獲取鎖。
        /// </summary>
        /// <param name="key">鎖名稱。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>是否已鎖。</returns>
        bool Lock(string key, int seconds);

        /// <summary>
        /// 釋放鎖。
        /// </summary>
        /// <param name="key">鎖名稱。</param>
        /// <returns>是否成功。</returns>
        bool UnLock(string key);

        /// <summary>
        /// 異步獲取鎖。
        /// </summary>
        /// <param name="key">鎖名稱。</param>
        /// <param name="seconds">過時時間(秒)。</param>
        /// <returns>是否成功。</returns>
        Task<bool> LockAsync(string key, int seconds);

        /// <summary>
        /// 異步釋放鎖。
        /// </summary>
        /// <param name="key">鎖名稱。</param>
        /// <returns>是否成功。</returns>
        Task<bool> UnLockAsync(string key);

        #endregion

    }

服務註冊和配置

services.AddTransient<IRedisClient, RedisClient>();
        services.AddTransient<IConnectionMultiplexer, ConnectionMultiplexer>();
        services.AddTransient<IConnectionMultiplexer>(a =>
        {
            ConfigurationOptions options = ConfigurationOptions.Parse(redisConfig.url);
            options.Password = redisConfig.pass;
            string configuration = "{0},$UNLINK=,abortConnect=false,defaultDatabase={1},ssl=false,ConnectTimeout={2},allowAdmin=true,connectRetry={3},password={4}";
            ConnectionMultiplexer connectionMultiplexer = ConnectionMultiplexer.Connect(string.Format(configuration, redisConfig.url, 0, 1800, 3, redisConfig.pass));
            return connectionMultiplexer;
        });

使用

直接在使用的類裏構造注入就能夠使用了。redis

[SwaggerTag("User", Description = "用戶管理")]
    [Authorize]
    public class UserController : ApiBaseController
    {
        readonly IUserService _userService;
        readonly IUserRoleService _userRoleService;
        private readonly ILogger _logger;
        private readonly IMapper _mapper;
        private readonly IRedisClient _redisClient;
        private const string keyPrefix = "token:";

        public UserController(IUserService userService, IUserRoleService userRoleService, ILogger<UserController> logger, IMapper mapper, IRedisClient redisClient)
        {
            _userService = userService;
            _logger = logger;
            _userRoleService = userRoleService;
            _mapper = mapper;
            _redisClient = redisClient;
        }
        /// <summary>
        /// 設置 Redis 數據。
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        [HttpGet]
        [Route("redis/set")]
        public ApiResult<Dictionary<string, bool>> SetRedisData(string key, string value)
        {
            TimeSpan timeSpan = TimeSpan.FromSeconds(7 * 24 * 60 * 60);
            var flag = _redisClient.Set(key, value, timeSpan);
            Dictionary<string, bool> res = new Dictionary<string, bool>
            {
                { "ok", flag }
            };
            return ApiResult<Dictionary<string, bool>>.Current.UpdateSuccess(res);
        }

    }

關於鎖的使用參考了 axel10 大神的文章,須要注意的是必定要禁用 UNLINK,否則會報 StackExchange.Redis.RedisServerException:「EXECABORT Transaction discarded because of previous errors.」 這個錯誤,UNLINK 須要 Redis 4.0 以上的版本才支持。json

相關文章
相關標籤/搜索