DotNet Core 使用 StackExchange.Redis 简单封装和实现分布式锁
前言
公司的项目以前一直使用 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 简单封装和实现分布式锁的更多相关文章
- Redis中是如何实现分布式锁的?
分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 本地面试考点是,你对Redis使用熟悉吗?Redis中是如何实现分布式锁的. 要点 Red ...
- 【spring boot】【redis】spring boot基于redis的LUA脚本 实现分布式锁
spring boot基于redis的LUA脚本 实现分布式锁[都是基于redis单点下] 一.spring boot 1.5.X 基于redis 的 lua脚本实现分布式锁 1.pom.xml &l ...
- python使用redis实现协同控制的分布式锁
python使用redis实现协同控制的分布式锁 上午的时候,有个腾讯的朋友问我,关于用zookeeper分布式锁的设计,他的需求其实很简单,就是节点之间的协同合作. 我以前用redis写过一个网络锁 ...
- Redis的“假事务”与分布式锁
关注公众号:CoderBuff,回复"redis"获取<Redis5.x入门教程>完整版PDF. <Redis5.x入门教程>目录 第一章 · 准备工作 第 ...
- 使用数据库、Redis、ZK分别实现分布式锁!
分布式锁三种实现方式: 基于数据库实现分布式锁: 基于缓存(Redis等)实现分布式锁: 基于Zookeeper实现分布式锁: 基于数据库实现分布式锁 悲观锁 利用select - where - f ...
- 基于redis集群实现的分布式锁,可用于秒杀商品的库存数量管理,有測试代码(何志雄)
转载请标明出处. 在分布式系统中,常常会出现须要竞争同一资源的情况,本代码基于redis3.0.1+jedis2.7.1实现了分布式锁. redis集群的搭建,请见我的另外一篇文章:<>& ...
- 分布式缓存技术redis学习系列(五)——redis实战(redis与spring整合,分布式锁实现)
本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...
- 分布式缓存技术redis系列(五)——redis实战(redis与spring整合,分布式锁实现)
本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...
- redis系列之5----redis实战(redis与spring整合,分布式锁实现)
本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...
随机推荐
- python学习——tuple
tuple 上次谈到了列表,而这次所谈的元组其实和列表有许多相似的地方,故元组又叫"戴上了枷锁的列表".这是因为元组不能改动内部的元素,所以就不能使用上次谈到的append.ext ...
- VB6的安装过程视频讲解
开发Windows桌面应用程序.开发Office的COM加载项,都离不开VB.本视频从VB安装包的下载和解压缩,一直讲到安装结束后创建项目为止.截图如下: 视频地址: https://pan.baid ...
- linux下nfs共享目录
1. 关掉防火墙 systemctl disable firewalld.service 2. 关掉selinux vim /etc/selinux/config 修改第七行: ...
- nginx应用geoip模块,实现不同地区访问不同页面的需求(实践版)
https://www.52os.net/articles/configure-nginx-using-geoip-allow-whitelist.html 搞了几天没有搞定,这篇文章一下 ...
- Linux centos 下安装redis
一.安装编译工具及库文件 yum -y install make zlib zlib-devel gcc-c++ libtool openssl openssl-devel 二.选择安装文件 ...
- 2018-10-09-Pser
title date tags layout Pser 2018-10-09 杂谈 post ### 踏雪无痕 2018-10-12 post 产生原因: 由于在编写代码时避免麻烦就需要把公用的一段代码写到一个单独的文件里面 ...
- iOS仿写有妖气漫画、视频捕获框架、启动页广告页demo、多种动画效果等源码
iOS精选源码 以tableview的section为整体添加阴影效果/ta'b'le'vi'e'w顶部悬浮.... 一个可以轻松应用自定义过滤器的视频捕获框架. 基于UITableView的组件,旨 ...
- 规范化开发和time相关模块
1. 规范化开发 如果在开发的过程中将所有的程序放在一个py文件中,加载时会很慢,同时降低了代码的可读性,查询起来也麻烦 所以要将一个oy文件合理的分成多个py文件,在blog大目录下分为以下几个部分 ...