前言
公司的項目以前一直使用 CSRedis 這個類庫來操作 Redis,最近增加了一些新功能,會存儲一些比較大的數據,內測的時候發現其中有兩台服務器會莫名的報錯 Unexpected response type: Status (expecting Bulk) 和 Connection was not opened,最后定位到問題是 Redis 寫入和讀取數據的時候發生的錯誤,弄了兩台新服務器重新部署還是沒有解決,在 GitHub 上向作者發了 issues,作者說升級類庫可以解決,嘗試了一下也沒有解決,無奈之下只好寫了個小程序用 StackExchange.Redis 在服務器上做讀寫測試,發現沒有任何問題,防止耽誤上線只好換成了 StackExchange.Redis,經過兩天內部測試,一切操作均未發現異常。
StackExchange.Redis 封裝
RedisClient 類:
/// <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 類:
/// <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.AddSingleton<IRedisClient, RedisClient>();
services.AddSingleton<IConnectionMultiplexer, ConnectionMultiplexer>();
services.AddSingleton<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;
});
使用
直接在使用的類里構造注入就可以使用了。
[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 以上的版本才支持。
