前言
公司的項目之前一直使用 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