前言

公司的项目以前一直使用 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 以上的版本才支持。

DotNet Core 使用 StackExchange.Redis 简单封装和实现分布式锁的更多相关文章

  1. Redis中是如何实现分布式锁的?

    分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 本地面试考点是,你对Redis使用熟悉吗?Redis中是如何实现分布式锁的. 要点 Red ...

  2. 【spring boot】【redis】spring boot基于redis的LUA脚本 实现分布式锁

    spring boot基于redis的LUA脚本 实现分布式锁[都是基于redis单点下] 一.spring boot 1.5.X 基于redis 的 lua脚本实现分布式锁 1.pom.xml &l ...

  3. python使用redis实现协同控制的分布式锁

    python使用redis实现协同控制的分布式锁 上午的时候,有个腾讯的朋友问我,关于用zookeeper分布式锁的设计,他的需求其实很简单,就是节点之间的协同合作. 我以前用redis写过一个网络锁 ...

  4. Redis的“假事务”与分布式锁

    关注公众号:CoderBuff,回复"redis"获取<Redis5.x入门教程>完整版PDF. <Redis5.x入门教程>目录 第一章 · 准备工作 第 ...

  5. 使用数据库、Redis、ZK分别实现分布式锁!

    分布式锁三种实现方式: 基于数据库实现分布式锁: 基于缓存(Redis等)实现分布式锁: 基于Zookeeper实现分布式锁: 基于数据库实现分布式锁 悲观锁 利用select - where - f ...

  6. 基于redis集群实现的分布式锁,可用于秒杀商品的库存数量管理,有測试代码(何志雄)

    转载请标明出处. 在分布式系统中,常常会出现须要竞争同一资源的情况,本代码基于redis3.0.1+jedis2.7.1实现了分布式锁. redis集群的搭建,请见我的另外一篇文章:<>& ...

  7. 分布式缓存技术redis学习系列(五)——redis实战(redis与spring整合,分布式锁实现)

    本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...

  8. 分布式缓存技术redis系列(五)——redis实战(redis与spring整合,分布式锁实现)

    本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...

  9. redis系列之5----redis实战(redis与spring整合,分布式锁实现)

    本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...

随机推荐

  1. HTTP Status 500 - Request processing failed; nested exception is java.lang.IllegalArgumentException: Control character in cookie value or attribute.

    HTTP Status 500 - Request processing failed; nested exception is java.lang.IllegalArgumentException: ...

  2. the least-squares criterion|Sxx|Sxy|Syy|Regression Equation|Outliers|Influential Observations|curvilinear regression|linear regression

    4.2 The Regression Equation Because we could draw many different lines through the cluster of data p ...

  3. linux配置和查看环境变量

    环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数,比如临时文件夹位置和系统文件夹位置等等. 一.Linux的变量种类 按变量的生存周期来划分,Linux变量可分为两类: 1.永久的:需要 ...

  4. Yii框架的学习指南(策码秀才篇)1-2 一步步学习yii framework

    我也是新手,不过之前学习了tp和ci框架,所以尝鲜想看看yii到底有多强大. 如何新建一个webapp(网站应用)呢,下面是2个步骤: 1. cmd 切换到htdocs下面的yii目录下的framew ...

  5. discussion|局限性|解释|猜测|前作与同行

    讨论是整篇论文的精华和灵魂,考查作者的文献积累量和对所研究内容的理解深度,作者需要阐述为什么结果是重要的,内容包括理论.应用.在其他其他领域的作用及应用,阐述时要求直接明确. 具体而言,首先概述最重要 ...

  6. smarty应用1 之 模板进行数学运算,保留小数位数

    例子1 乘法除法运算: 1格式:{$number|number_format} 保留小数点后2位小数. {$v/$total*100|string_format:"%0.2f"|c ...

  7. labview线程相关

    两个结论.     1. 在 LabVIEW 上编写多线程程序非常方便,我们应该充分利用这个优势.一般情况下,编写程序时应当遵循这样的原则:可以同时运行的模块就并排摆放,千万不要用连线,顺序框等方式强 ...

  8. [LC] 392. Is Subsequence

    Given a string s and a string t, check if s is subsequence of t. You may assume that there is only l ...

  9. 对《java程序员上班那点事》笔者对数组占用内存质疑

    1.<java程序员上班那点事>笔者对数组占用内存的描述 2.实际测试情况: /** * 测试一维数组占用内存 */ public static void testOneArray() { ...

  10. linux基本指令梳理