Redis主从服务读写分离安装部署与.NET实现Redis读写分离
前言
说在最前面,本章主要讲解如下:
一、在Windows环境下Redis主从安装部署,Redis版本以Win-x64-3.2.100为例,此版本是Windows环境下官方最新版本,注意:Windows版本官方已停更,如需更高版本请使用Linux版;
二、本人针对常用的Redis操作进行了接口封装,支持在.NET(Core3.1+版本)框架下的Redis读写分离多从机(只负责读操作)动态配置,及Redis常规配置项多方式配置;详细请移步下文⇩⇩⇩
简介
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redis属于非关系型数据库中的一种解决方案,目前也是业界主流的缓存解决方案组件。
1、Redis 优点
- 高性能:基于内存操作,读写速度非常快,可以达到10万次/秒以上(读110000次/s,写81000次/s)。这使得Redis非常适合用于高并发的场景,如电商网站、社交网络等。
- 丰富的数据结构:支持多种数据结构(String、List、Set、SortedSet、Hash等),可以满足不同场景的需求。例如,使用哈希表可以实现快速查找;使用集合可以实现交集、并集等操作;使用有序集合可以实现排行榜等功能。
- 持久化:支持两种持久化方式,RDB和AOF。RDB是将当前内存中的数据生成一个快照文件,而AOF则是记录每个写操作,重启时通过重新执行这些操作来恢复数据。这两种方式可以根据实际需求选择使用。
- 主从复制:支持主从复制,可以将一台服务器的数据同步到另一台服务器,实现数据的备份和负载均衡,也可用作读写分离。
- 发布订阅:支持发布订阅模式,可以实现消息的实时推送和广播。
- 成本低:单例部署简单,大部分开源免费,社区活跃。
- 扩展性强:支持集群分布式部署。
2、Redis 缺点
- 内存消耗:Redis 是基于内存的数据库,存储容量受物理内存限制,数据量较大时会占用大量内存,不能用作海量数据的高性能读写。这可能导致需要投入更多的成本来扩展内存容量或者进行性能优化。内存管理也不够灵活,不支持自动内存碎片整理或按需分配内存。
- 持久化和数据安全:虽然 Redis 提供了持久化选项,但在某些情况下可能会存在数据丢失的风险。比如在使用异步持久化时,如果在持久化过程中发生了故障,可能会导致部分数据丢失。主从模式下主机宕机前如有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
- 单线程模型:Redis 的单线程模型意味着它在处理大量并发请求时可能存在性能瓶颈。虽然 Redis 在实践中通常能够处理大量请求,但在极端情况下处理大量客户端连接时可能会受到限制。
- 定时快照(snapshot):每隔一段时间将整个数据库写到磁盘上,每次均是写全部数据,代价非常高。
- 基于语句追加(AOF):只追踪变化的数据,但是追加的log可能过大,同时所有的操作均重新执行一遍,回复速度慢。
- 数据结构局限性:虽然 Redis 支持丰富的数据结构,但在某些特定的复杂查询或者关联查询方面,如join联接、子查询、批量更新等,且事务处理能力弱,使用传统的关系型数据库可能更为方便。
- 高可用性配置复杂:在构建高可用的 Redis 集群时,需要进行复杂的配置和监控,确保集群能够正确地工作并且数据不会丢失。
- 开发复杂性:使用 Redis 可能需要对数据的存储和访问模式进行重新思考,需要更多的开发和设计成本来保证数据一致性和正确性,缺乏数据完整性约束,不提供SQL支持会造成开发人员额外的学习成本。
- 在线扩容较难:在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费,否则需要对系统停服升级。
3、Redis Key的命名建议
- 简洁性:尽可能简短,但要足够表达键的意思,尽量不要超过1024字节。
- 唯一性:确保键名在应用中是唯一的,且区分大小写,以避免冲突。
- 无空格:键名中不要包含空格,可以使用下划线_作为单词之间的分隔符。
- 清晰性:键名应该是清晰的,可以通过名字直接理解其含义。
- 前缀规范:可以使用项目名或模块名作为前缀,以区分不同应用或模块的键。
- 类型明确:如果值的类型不同,可以通过前缀或后缀来区分。
一、Redis主从安装部署
1、Redis程序安装包下载
综合包下载地址:https://download.csdn.net/download/u013899802/89302836 (内含可视化管理工具resp)
官方下载地址:https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100
第三方版本下载地址:https://github.com/tporadowski/redis/releases(最新版v5.0.14.1,实测可用,但未详细测试细节功能)
2、安装单例服务
①安装包方式
注意:此方式只适合单机单例Redis服务安装,操作系统重启服务也会自动启动。
运行安装包操作
直接管理员方式运行Windows安装程序包(Win-最新-Redis-x64-3.2.100.msi)
安装完成后验证是否安装成功
- 打开安装目录检查是否已有Redis相关文件
- win+r 运行,输入命令services.msc打开系统服务,查看是否有“Redis”服务正在运行
- 运行,打开cmd,输入命令“redis-cli”,连接本地Redis服务客户端(未设置密码可直接进入),进入后输入命令“info”,查看当前安装的Redis版本相关信息,至此您已成功安装Redis服务。
注:如需要修改数据存放位置等其他配置,同下一种方式,直接修改配置文件,重启服务即可。
②命令行方式
进入配置文件配置相关参数
- 解压“Win-最新-Redis-x64-3.2.100.zip”压缩包,得到以下文件包
- 找到配置文件,需要部署为系统服务则打开“redis.windows-service.conf”配置文件进行配置;非服务模式选“redis.windows.conf”;二者区别并不大,需要的配置都可以自行更改。
- 打开配置文件,找到连接地址配置项“bind”,“127.0.0.1”代表本地连接,需要开启远程连接则改为“0.0.0.0”。
# bind 127.0.0.1
bind 0.0.0.0
- 找到端口配置项“port”,默认6379,可根据需要自行修改其他端口,注意需要远程连接的需要打开对应端口。
port 6379
- 找到最大占用内存限制配置项“maxmemory”,默认不限制,需要限制则启用并设置大小,数值不带单位表“字节数”,带单位以单位为准(B、KB、MB、GB…),注意数值与单位之间不能有空格。
# maxmemory <bytes>
maxmemory 2048MB
- 找到在达到内存限制时的淘汰策略配置项“maxmemory-policy”,配置为根据LRU算法生成的过期时间来删除“volatile-lru”。
Redis默认有六种过期策略:
volatile-lru -> 根据LRU算法生成的过期时间来删除。
-allkeys-lru -> 根据LRU算法删除任何key。
volatile-random -> 根据过期设置来随机删除key。
allkeys->random -> 无差别随机删。
volatile-ttl -> 根据最近过期时间来删除(辅以TTL)。
noeviction -> 谁也不删,直接在写操作时返回错误。
- 找到数据存储位置配置项“dir”,默认“dir ./”即存储在安装根目录下,可更改为指定位置,注意需保证文件夹路径存在。
dir C:/Redis/Data
- 找到密码配置项“requirepass”,不需要密码可跳过此步(没开远程连接的不用密码效率更高,开了远程连接的建议加上密码更安全),设置如下。
# requirepass foobared
requirepass 123456
- 配置环境变量,系统属性→环境变量→Path编辑→新增Redis安装目录→保存(部分系统需要重启生效),如下图。
配置完成后验证是否配置成功
- 运行cmd,进入安装目录输入命令“redis-server.exe redis.windows.conf”启动Redis,查看Redis是否已启动,关闭窗体即停止。
- 测试Redis是否可连接,运行cmd,进入安装目录输入命令“redis-cli -h 127.0.0.1 -p 6379 -a 123456”(无密码则不输入“ -a 123456”),连接后输入命令“info”,查看是否输出版本号等信息,如下图即为成功。
安装到Windows系统服务
- 安装服务,运行cmd,进入安装目录输入以下命令:
redis-server --service-install redis.windows-service.conf --service-name "Redis" --loglevel verbose
- 启动服务:
redis-server --service-start --service-name "Redis"
操作成功显示如下:
- 服务其他操作
--停止服务
redis-server --service-stop --service-name "Redis"
--卸载服务
redis-server --service-uninstall --service-name "Redis"
3、部署主从配置
①主服务部署(写读)
同单例服务部署安装一样, 主 服务参照即可,无需特殊处理。
注意:主服务设置了密码,从服务必须设置密码,且必须相同。
②从服务部署(只读)
- 参照单例服务命令行方式,重新解压一组Redis文件到文件夹“RedisSlave”,重复操作命令行方式步骤1.2.3,第4步由于是同一台服务器所以需修改端口为6380或其他端口,注意远程连接要开防火墙对应端口(同一台服务器端口需不同,不同服务器可保持默认6379端口)
port 6380
继续重复操作步骤5.6.7,第8步设置密码,同主服务密码保持一致(如主服务未设置密码,从服务也不能设置密码)
# requirepass foobared
requirepass 123456
- 找到主服务的IP及端口配置项“slaveof”,“10.32.128.54”为主服务的IP,修改配置如下:
# slaveof <masterip> <masterport>
slaveof 10.32.128.54 6379
- 找到主服务的访问密码配置项“masterauth”,配置为你主服务设置的密码
# masterauth <master-password>
masterauth 123456
- 找到配置从服务为只读模式的配置项“slave-read-only”, 2.6 版本开始默认为 yes 开启的,如有特殊需求,可关闭从服务只读模式,关闭改为 no
slave-read-only yes
- 安装到Windows系统服务并启动,操作同上面的单例服务一样,注意服务名称不能重复。
redis-server --service-install redis.windows-service.conf --service-name "Redis Slave" --loglevel verbose
redis-server --service-start --service-name "Redis Slave"
- 验证主从是否配置成功,使用cmd连接客户端,或使用上面下载的RESP可视化管理工具操作查看;配置成功时,主服务写入数据,从服务会自动同步此数据,如下图:
cmdRESP
如需部署多个Redis从服务,则重复上述步骤,注意名称端口等问题即可。
4、常用Redis命令
使用给定的密码验证服务器:AUTH 123456(Redis密码)
查看当前服务详细信息:INFO
查看当前服务内存使用情况:INFO Memory
实时监控执行命令:MONITOR
清空当前库所有数据:FLUSHDB
清空当前服务器所有库所有数据:FLUSHALL
切换当前数据库:SELECT 0(数据库索引值)
退出当前连接:QUIT
二、.NET实现Redis读写分离
1、Redis配置类
新建配置类:RedisConfig.cs
需引用NuGet包:Microsoft.Extensions.Options.ConfigurationExtensions
using System.Reflection;
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Infrastructure.Cache
{
/// <summary>
/// Redis配置类
/// </summary>
public class RedisConfig
{
#region 扩展属性
/// <summary>
/// 自定义实例名称
/// (用于区分不同项目或服务的键名,规则:英文大小写字母加下划线,Name1_Server1;不需要区分则留空)
/// </summary>
public string? InstanceName { get; set; }
/// <summary>
/// 默认缓存时间(秒)
/// </summary>
public double? DefaultCacheTime { get; set; }
#endregion
#region 基本配置字符串
/// <summary>
/// Redis连接配置字符串(独立使用,如:redis0:6380,redis1:6380,allowAdmin=true)
/// </summary>
public string? ConfigStr { get; set; }
#endregion
#region 配置选项
/// <summary>
/// Redis连接地址(多个英文分号;隔开)
/// </summary>
public string? HostUrl { get; set; }
/// <summary>
/// 用户名
/// </summary>
public string? User { get; set; }
/// <summary>
/// 密码
/// </summary>
public string? Password { get; set; }
/// <summary>
/// 默认使用的数据库下标(默认0)
/// </summary>
public int? DefaultDatabase { get; set; }
/// <summary>
/// 允许管理员(启用一系列被认为有风险的命令)
/// </summary>
public bool? AllowAdmin { get; set; }
/// <summary>
/// 发送消息以帮助保持套接字处于活动状态的时间(秒)(默认60)
/// </summary>
public int? KeepAlive { get; set; }
/// <summary>
/// 初始期间重复连接尝试的次数(默认3)
/// </summary>
public int? ConnectRetry { get; set; }
/// <summary>
/// 连接操作超时(毫秒)(默认5000)
/// </summary>
public int? ConnectTimeout { get; set; }
/// <summary>
/// 允许同步操作的时间(毫秒)(默认5000)
/// </summary>
public int? SyncTimeout { get; set; }
/// <summary>
/// 允许异步操作的时间(毫秒)(默认5000)
/// </summary>
public int? AsyncTimeout { get; set; }
/// <summary>
/// Redis版本级别
/// </summary>
public string? DefaultVersion { get; set; }
/// <summary>
/// 用于连接到哨兵主要服务名称
/// </summary>
public string? ServiceName { get; set; }
/// <summary>
/// 所有发布/订阅操作的可选通道前缀
/// </summary>
public string? ChannelPrefix { get; set; }
/// <summary>
/// 中止连接(默认true。如果为true,Connect则在没有可用服务器时不会创建连接)
/// </summary>
public bool? AbortOnConnectFail { get; set; }
#endregion
}
/// <summary>
/// 任意对象操作转换扩展类(Redis专用)
/// </summary>
public static partial class RedisObjectsExtensions
{
#region 不同类型对象同名属性赋值
/// <summary>
/// 不同类型对象同名属性赋值(Redis专用)
/// </summary>
/// <typeparam name="S">源类型</typeparam>
/// <typeparam name="T">目标类型</typeparam>
/// <param name="s">源对象</param>
public static T RedisAutoMapping<S, T>(this S s) where T : new()
{
T newT = new T();
PropertyInfo[] pps = s.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);//获取源类型的属性(仅公有和实例)
Type target = newT.GetType();
foreach (var pp in pps)
{
try
{
PropertyInfo targetPP = target.GetProperty(pp.Name);//匹配目标类型属性名
object value = pp.GetValue(s, null);
if (targetPP != null && value != null)
{
targetPP.SetValue(newT, value, null);
}
}
catch (Exception ex)
{
throw ex;
}
}
return newT;
}
#endregion
}
#region 添加Redis缓存扩展类
/// <summary>
/// 添加Redis缓存扩展类
/// </summary>
/// <param name="services">service集合句柄</param>
/// <param name="configuration">配置信息(section根节点名为“RedisConfig”)</param>
public static IServiceCollection AddRedisCache(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<RedisConfig>(configuration.GetSection("RedisConfig"));//注入Redis配置项 //需要引用包:Microsoft.Extensions.Options.ConfigurationExtensions
services.AddSingleton<IRedisCacheService, RedisCacheService>();//注入Redis服务
return services;
}
#endregion
}
2、Redis接口类
新建接口类:IRedisCacheService.cs
需引用NuGet包:StackExchange.Redis
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
namespace Infrastructure.Cache
{
/// <summary>
/// Redis缓存服务接口
/// </summary>
public interface IRedisCacheService
{
#region 获取当前Redis连接对象
/// <summary>
/// 获取当前Redis连接对象
/// </summary>
ConnectionMultiplexer GetCurrConnectionMultiplexer();
#endregion
#region 获取Redis服务的版本号
/// <summary>
/// 获取Redis服务的版本号
/// </summary>
string GetRedisVersion();
#endregion
#region 获取当前操作数据库对象
/// <summary>
/// 获取当前操作数据库对象
/// </summary>
IDatabase GetCurrDatabase();
#endregion
#region 切换数据库
/// <summary>
/// 切换数据库
/// 注意:切换后将保持此库连接,需手动切换回默认库:ChangeDatabase()
/// </summary>
/// <param name="db">库索引(不传为默认库)</param>
void ChangeDatabase(int db = -1);
#endregion
#region 获取Redis的完整Key名称
/// <summary>
/// 获取Redis的完整Key名称(前缀_键名)
/// </summary>
/// <param name="key">键名</param>
/// <returns>返回带前缀的键名</returns>
RedisKey GetKeyForRedis(RedisKey key);
#endregion
#region 判断指定键名是否存在
/// <summary>
/// 判断指定键名是否存在
/// </summary>
/// <param name="key">键名</param>
/// <returns></returns>
Task<bool> Exists(RedisKey key);
#endregion
#region 缓存String
#region 设置
#region 设置缓存String(绝对过期时间)
/// <summary>
/// 设置缓存String(绝对过期时间)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">对象值</param>
/// <param name="expiressAbsoulte">绝对过期时间</param>
/// <param name="isSerialize">是否序列化值(默认false不序列化,true序列化)</param>
Task<bool> SetString(RedisKey key, RedisValue? value, DateTimeOffset expiressAbsoulte, bool isSerialize = false);
#endregion
#region 设置缓存String(相对过期时间)
/// <summary>
/// 设置缓存String(相对过期时间)
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expirationSeconds">相对过期时间(秒)</param>
/// <param name="isSerialize">是否序列化值(默认false不序列化,true序列化)</param>
Task<bool> SetString(RedisKey key, RedisValue? value, double expirationSeconds, bool isSerialize = false);
#endregion
#region 设置缓存String(自定义过期时间)
/// <summary>
/// 设置缓存String(自定义过期时间)
/// (未设置自定义过期时间使用默认缓存时间,未配置默认时永不过期)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">对象值</param>
/// <param name="ts">【可选】自定义过期时间</param>
/// <param name="isSerialize">是否序列化值(默认false不序列化,true序列化)</param>
Task<bool> SetString(RedisKey key, RedisValue? value, TimeSpan? ts = null, bool isSerialize = false);
#endregion
#region 设置实体对象序列化缓存String(自定义过期时间)
/// <summary>
/// 设置实体对象序列化缓存String(自定义过期时间)
/// (未设置自定义过期时间使用默认缓存时间,未配置默认时永不过期)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">实体对象值</param>
/// <param name="ts">【可选】自定义过期时间</param>
Task<bool> SetStringJson(RedisKey key, object value, TimeSpan? ts = null);
#endregion
#region 设置缓存String(指定键名追加值到末尾)
/// <summary>
/// 设置缓存String(指定键名追加值到末尾)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">追加的值</param>
/// <param name="isSerialize">是否序列化值(默认false不序列化,true序列化)</param>
/// <returns></returns>
Task<long> SetStringAppend(RedisKey key, RedisValue? value, bool isSerialize = false);
#endregion
#region 设置缓存String(重设新值并返回旧值)
/// <summary>
/// 设置缓存String(重设新值并返回旧值)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">追加的值</param>
/// <param name="isSerialize">是否序列化值(默认false不序列化,true序列化)</param>
/// <returns></returns>
Task<RedisValue> SetStringGet(RedisKey key, RedisValue? value, bool isSerialize = false);
#endregion
#region 设置递增递减缓存
/// <summary>
/// 设置递增缓存(Redis专用)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">递增量(默认1)</param>
/// <param name="expTimeSpan">【可选】过期时间</param>
/// <returns></returns>
Task<long> SetIncrement(RedisKey key, long value = 1L, TimeSpan? expTimeSpan = null);
/// <summary>
/// 设置递减缓存(Redis专用)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">递减量(默认1)</param>
/// <param name="expTimeSpan">【可选】过期时间</param>
/// <returns></returns>
Task<long> SetDecrement(RedisKey key, long value = 1L, TimeSpan? expTimeSpan = null);
#endregion
#endregion
#region 获取
/// <summary>
/// 获取String缓存原始数据
/// </summary>
/// <param name="key">键名</param>
Task<string> GetString(string key);
/// <summary>
/// 获取String缓存数据转为指定对象
/// </summary>
/// <typeparam name="T">实体类</typeparam>
/// <param name="key">键名</param>
/// <returns></returns>
Task<T> GetString<T>(string key);
#endregion
#endregion
#region 缓存List
// 注意:
// 1、小数数字偶现不精确,RedisValue尽量使用字符串类型值;
// 2、列表允许有重复值。
#region 设置
#region 顶底插入
/// <summary>
/// 从列表顶部插入数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">插入项的值</param>
/// <returns></returns>
Task<long> SetListLeftPush(RedisKey key, RedisValue value);
/// <summary>
/// 从列表底部插入数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">插入项的值</param>
/// <returns></returns>
Task<long> SetListRightPush(RedisKey key, RedisValue value);
#endregion
#region 中间的前后插入
/// <summary>
/// 从列表中间指定值位置之前插入数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="pivot">作为中间点的指定值</param>
/// <param name="value">插入项的值</param>
/// <returns></returns>
Task<long> SetListInsertBefore(RedisKey key, RedisValue pivot, RedisValue value);
/// <summary>
/// 从列表中间指定值位置之后插入数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="pivot">作为中间点的指定值</param>
/// <param name="value">插入项的值</param>
/// <returns></returns>
Task<long> SetListInsertAfter(RedisKey key, RedisValue pivot, RedisValue value);
#endregion
#region 修改列表指定索引位置的值
/// <summary>
/// 修改列表指定索引位置的值(无返回值)
/// </summary>
/// <param name="key">键名</param>
/// <param name="index">指定索引位置</param>
/// <param name="value">修改的值</param>
Task SetListByIndex(RedisKey key, long index, RedisValue value);
#endregion
#endregion
#region 删除
#region 从列表两边取出数据
/// <summary>
/// 从列表顶部拿出一条数据
/// </summary>
/// <param name="key">键名</param>
/// <returns>删除的值</returns>
Task<RedisValue> RemoveListLeftPop(string key);
/// <summary>
/// 从列表顶部拿出一组数据(从 Redis 版本 6.2.0 开始支持)
/// </summary>
/// <param name="key">键名</param>
/// <param name="count">拿出的数量</param>
/// <returns>删除的一组值</returns>
Task<RedisValue[]> RemoveListLeftPop(string key, long count);
/// <summary>
/// 从列表底部拿出一条数据
/// </summary>
/// <param name="key">键名</param>
/// <returns>删除的值</returns>
Task<RedisValue> RemoveListRightPop(string key);
/// <summary>
/// 从列表底部拿出一组数据(从 Redis 版本 6.2.0 开始支持)
/// </summary>
/// <param name="key">键名</param>
/// <param name="count">拿出的数量</param>
/// <returns>删除的一组值</returns>
Task<RedisValue[]> RemoveListRightPop(string key, long count);
#endregion
#region 从列表删除所有指定值的数据项
/// <summary>
/// 从列表删除所有指定值的数据项
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">需要删除的指定值</param>
/// <returns>已删除元素的数量</returns>
Task<long> RemoveListByValue(string key, RedisValue value);
#endregion
#region 从列表删除指定起止索引位置范围外的数据项
/// <summary>
/// 从列表删除指定起止索引位置范围外的数据项(无返回值)
/// </summary>
/// <param name="key">键名</param>
/// <param name="start">起点索引</param>
/// <param name="stop">终点索引</param>
Task RemoveListByIndex(string key, long start, long stop);
#endregion
#endregion
#region 获取
/// <summary>
/// 获取列表一组数据(不会删除数据)
/// </summary>
/// <param name="key">键名</param>
/// <param name="start">起点索引(不传取所有)</param>
/// <param name="stop">终点索引(不传取所有)</param>
Task<RedisValue[]> GetListRange(string key, long start = 0L, long stop = -1L);
/// <summary>
/// 获取列表指定索引的一条数据(不会删除数据)
/// </summary>
/// <param name="key">键名</param>
/// <param name="index">指定索引</param>
Task<RedisValue> GetListByIndex(string key, long index);
/// <summary>
/// 获取列表项总数
/// </summary>
/// <param name="key">键名</param>
Task<long> GetListCount(string key);
#endregion
#endregion
#region 缓存Set
// 注意:
// 1、小数数字偶现不精确,RedisValue尽量使用字符串类型值;
// 2、列表自动排重;
// 3、列表无序。
#region 设置
/// <summary>
/// 列表插入数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">插入的值</param>
Task<bool> SetAdd(RedisKey key, RedisValue value);
#endregion
#region 删除
/// <summary>
/// 随机删除列表一条数据
/// </summary>
/// <param name="key">键名</param>
/// <returns>删除的值</returns>
Task<RedisValue> RemoveSetPop(RedisKey key);
/// <summary>
/// 随机删除列表一组数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="count">删除项数量</param>
/// <returns>删除的一组值</returns>
Task<RedisValue[]> RemoveSetPop(RedisKey key, long count);
/// <summary>
/// 删除列表中指定值
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">需要删除的指定值</param>
Task<bool> RemoveSetByValue(RedisKey key, RedisValue value);
#endregion
#region 获取
/// <summary>
/// 获取列表是否包含指定值
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">指定值</param>
Task<bool> GetSetContains(RedisKey key, RedisValue value);
/// <summary>
/// 获取列表项总数
/// </summary>
/// <param name="key">键名</param>
Task<long> GetSetCount(RedisKey key);
/// <summary>
/// 获取所有列表项
/// </summary>
/// <param name="key">键名</param>
Task<RedisValue[]> GetSetMembers(RedisKey key);
/// <summary>
/// 随机获取列表中一条数据(不会删除数据)
/// </summary>
/// <param name="key">键名</param>
Task<RedisValue> GetSetRandomMember(RedisKey key);
/// <summary>
/// 随机获取列表中一条数据(不会删除数据)
/// </summary>
/// <param name="key">键名</param>
/// <param name="count">随机项数量</param>
Task<RedisValue[]> GetSetRandomMembers(RedisKey key, long count);
/// <summary>
/// 查询列表中匹配指定关键字的一组数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="pattern">匹配规则</param>
/// <param name="pageSize">分页大小</param>
/// <param name="cursor">游标</param>
/// <param name="pageOffset">页面偏移量</param>
RedisValue[] GetSetScan(RedisKey key, RedisValue pattern = default, int pageSize = 250, long cursor = 0L, int pageOffset = 0);
#endregion
#region 移动
/// <summary>
/// 将指定值从一个列表移动到另一个列表中
/// </summary>
/// <param name="key">源列表键名</param>
/// <param name="toKey">目标列表键名</param>
/// <param name="value">指定值</param>
Task<bool> GetSetMove(RedisKey key, RedisKey toKey, RedisValue value);
#endregion
#endregion
#region 缓存SortedSet
// 注意:
// 1、小数数字偶现不精确,RedisValue尽量使用字符串类型值;
// 2、列表自动排重;
// 3、列表有序(默认根据score分数升序);
// 4、列表多一列double类型的score分数列。
#region 设置
/// <summary>
/// 列表插入数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">插入的值</param>
/// <param name="score">分值</param>
Task<bool> SetSortedSetAdd(RedisKey key, RedisValue value, double score);
/// <summary>
/// 列表插入递增数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">匹配对应的值</param>
/// <param name="score">递增的分值</param>
Task<double> SetSortedSetIncrement(RedisKey key, RedisValue value, double score);
/// <summary>
/// 列表插入递减数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">匹配对应的值</param>
/// <param name="score">递减的分值</param>
Task<double> SetSortedSetDecrement(RedisKey key, RedisValue value, double score);
#endregion
#region 删除
/// <summary>
/// 删除列表中指定一个值
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">需要删除的指定值</param>
/// <returns>删除是否成功</returns>
Task<bool> RemoveSortedSetByValue(RedisKey key, RedisValue value);
/// <summary>
/// 删除列表中指定一组值
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">需要删除的一组值</param>
/// <returns>删除的成员数</returns>
Task<long> RemoveSortedSetByValue(RedisKey key, RedisValue[] value);
#endregion
#region 获取
/// <summary>
/// 获取指定value值所在排名
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">指定value值</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
/// <returns>值对应的排名(值不存在则为空)</returns>
Task<long?> GetSortedSetRank(RedisKey key, RedisValue value, Order order = Order.Ascending);
/// <summary>
/// 获取指定score分数范围内列表项总数
/// </summary>
/// <param name="key">键名</param>
/// <param name="min">范围起点</param>
/// <param name="max">范围终点</param>
/// <param name="exclude">起点终点排除设置(Exclude.None都不排除,Exclude.Start排除起点,Exclude.Stop排除终点,Exclude.Both都排除)</param>
Task<long> GetSortedSetCount(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None);
/// <summary>
/// 获取指定排名范围内列表数据value
/// </summary>
/// <param name="key">键名</param>
/// <param name="start">范围起点</param>
/// <param name="stop">范围终点</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
Task<RedisValue[]> GetSortedSetRangeByRank(RedisKey key, long start = 0L, long stop = -1L, Order order = Order.Ascending);
/// <summary>
/// 获取指定排名范围内列表数据value+score
/// </summary>
/// <param name="key">键名</param>
/// <param name="start">排名起点</param>
/// <param name="stop">排名终点</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
Task<SortedSetEntry[]> GetSortedSetRangeByRankWithScores(RedisKey key, long start = 0L, long stop = -1L, Order order = Order.Ascending);
/// <summary>
/// 获取指定score分数范围内列表数据value
/// </summary>
/// <param name="key">键名</param>
/// <param name="start">分数起点</param>
/// <param name="stop">分数终点</param>
/// <param name="exclude">起点终点排除设置(Exclude.None都不排除,Exclude.Start排除起点,Exclude.Stop排除终点,Exclude.Both都排除)</param>
/// <param name="skip">要跳过的项数量</param>
/// <param name="take">要取的项数量</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
Task<RedisValue[]> GetSortedSetRangeByScore(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0L, long take = -1L);
/// <summary>
/// 获取指定score分数范围内列表数据value+score
/// </summary>
/// <param name="key">键名</param>
/// <param name="start">分数起点</param>
/// <param name="stop">分数终点</param>
/// <param name="exclude">起点终点排除设置(Exclude.None都不排除,Exclude.Start排除起点,Exclude.Stop排除终点,Exclude.Both都排除)</param>
/// <param name="skip">要跳过的项数量</param>
/// <param name="take">要取的项数量</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
Task<SortedSetEntry[]> GetSortedSetRangeByWithScores(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0L, long take = -1L);
/// <summary>
/// 获取指定value值范围内列表数据value
/// </summary>
/// <param name="key">键名</param>
/// <param name="min">值起点</param>
/// <param name="max">值终点</param>
/// <param name="exclude">起点终点排除设置(Exclude.None都不排除,Exclude.Start排除起点,Exclude.Stop排除终点,Exclude.Both都排除)</param>
/// <param name="skip">要跳过的项数量</param>
/// <param name="take">要取的项数量</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
Task<RedisValue[]> GetSortedSetRangeByValue(RedisKey key, RedisValue min = default, RedisValue max = default, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0L, long take = -1L);
#endregion
#endregion
#region 缓存Hash
// 注意:
// 1、小数数字偶现不精确,RedisValue尽量使用字符串类型值;
// 2、key自动排重;
// 3、无序;
// 4、列表以key+value存储。
#region 设置
#region 基础插入
/// <summary>
/// 列表插入一组键值数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="fields">字段集合(字段名不能重复,同字段名的值会覆盖)</param>
Task SetHash(RedisKey key, HashEntry[] fields);
/// <summary>
/// 列表插入一条键值数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
/// <param name="value">字段值</param>
/// <returns></returns>
Task<bool> SetHash(RedisKey key, RedisValue hashField, RedisValue value);
#endregion
#region 设置递增递减缓存
/// <summary>
/// 列表插入一条递增键值数据(long数字)
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
/// <param name="value">递增量(默认1)</param>
/// <returns></returns>
Task<long> SetHashIncrement(RedisKey key, RedisValue hashField, long value = 1L);
/// <summary>
/// 列表插入一条递减键值数据(long数字)
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
/// <param name="value">递减量(默认1)</param>
/// <returns></returns>
Task<long> SetHashDecrement(RedisKey key, RedisValue hashField, long value = 1L);
/// <summary>
/// 列表插入一条递增键值数据(double浮点)
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
/// <param name="value">递增量(默认1)</param>
/// <returns></returns>
Task<double> SetHashIncrement(RedisKey key, RedisValue hashField, double value = 1L);
/// <summary>
/// 列表插入一条递减键值数据(double浮点)
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
/// <param name="value">递减量(默认1)</param>
/// <returns></returns>
Task<double> SetHashDecrement(RedisKey key, RedisValue hashField, double value = 1L);
#endregion
#endregion
#region 删除
/// <summary>
/// 删除指定字段
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">要删除的字段名</param>
Task<bool> RemoveHash(RedisKey key, RedisValue hashField);
/// <summary>
/// 删除指定字段集合
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashFields">要删除的字段集合</param>
Task<long> RemoveHash(RedisKey key, RedisValue[] hashFields);
#endregion
#region 获取
/// <summary>
/// 判断指定键名中指定字段名是否存在
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
Task<bool> ExistsHash(RedisKey key, RedisValue hashField);
/// <summary>
/// 获取指定键名中指定字段名的值
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
Task<RedisValue> GetHash(RedisKey key, RedisValue hashField);
/// <summary>
/// 获取指定键名中指定字段名集合的一组值
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashFields">字段名集合</param>
Task<RedisValue[]> GetHash(RedisKey key, RedisValue[] hashFields);
/// <summary>
/// 获取指定键名中所有字段名及对应值集合
/// </summary>
/// <param name="key">键名</param>
Task<HashEntry[]> GetHashAll(RedisKey key);
/// <summary>
/// 获取指定键名中所有字段名
/// </summary>
/// <param name="key">键名</param>
Task<RedisValue[]> GetHashKeys(RedisKey key);
/// <summary>
/// 获取指定键名中所有字段值
/// </summary>
/// <param name="key">键名</param>
Task<RedisValue[]> GetHashValues(RedisKey key);
/// <summary>
/// 获取指定键名中所有字段总数
/// </summary>
/// <param name="key">键名</param>
Task<long> GetHashCount(RedisKey key);
#endregion
#endregion
#region 删除缓存
/// <summary>
/// 删除指定缓存
/// </summary>
/// <param name="key">键名</param>
Task<bool> Remove(RedisKey key);
#endregion
#region 设置指定键名过期时间
/// <summary>
/// 设置指定键名过期时间
/// </summary>
/// <param name="key">键名</param>
/// <param name="expiry">自定义过期时间</param>
/// <param name="when">过期条件(Always,GreaterThanCurrentExpiry,HasExpiry,HasNoExpiry,LessThanCurrentExpiry)</param>
/// <returns></returns>
Task<bool> Expire(RedisKey key, TimeSpan? expiry, ExpireWhen when = ExpireWhen.Always);
#endregion
#region 重命名指定键名
/// <summary>
/// 重命名指定键名
/// </summary>
/// <param name="key">原键名</param>
/// <param name="newKey">新键名</param>
Task<bool> Rename(RedisKey key, RedisKey newKey);
#endregion
#region 获取匹配键名的数据
#region 获取匹配键名的总数
/// <summary>
/// 获取模糊匹配的键名总数
/// </summary>
/// <param name="key">键名关键字(为空查所有键)</param>
Task<int> GetKeysCount(string key = "");
/// <summary>
/// 获取指定匹配规则的键名总数
/// </summary>
/// <param name="pattern">匹配规则</param>
Task<int> GetKeysPatternCount(string pattern);
#endregion
#region 获取匹配的键名集合
/// <summary>
/// 获取模糊匹配的键名集合
/// </summary>
/// <param name="key">键名关键字(为空查所有键)</param>
Task<IEnumerable<string>> GetKeys(string key = "");
/// <summary>
/// 获取指定匹配规则的键名集合
/// </summary>
/// <param name="pattern">匹配规则</param>
Task<IEnumerable<string>> GetKeysPattern(string pattern);
#endregion
#region 获取匹配的键名对应值集合
/// <summary>
/// 获取模糊匹配的键名对应值集合(只适用String类型数据)
/// </summary>
/// <param name="key">键名关键字(为空查所有键)</param>
Task<RedisValue[]> GetKeysValues(string key = "");
/// <summary>
/// 获取指定匹配规则的键名对应值集合(只适用String类型数据)
/// </summary>
/// <param name="pattern">匹配规则</param>
Task<RedisValue[]> GetKeysValuesPattern(string pattern);
#endregion
#endregion
#region 列表重新排序(List、Set、SortedSet)
/// <summary>
/// 列表重新排序(List、Set、SortedSet)
/// </summary>
/// <param name="key">键名</param>
/// <param name="skip">要跳过的项数量</param>
/// <param name="take">要取的项数量</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
/// <param name="sortType">排序类型(默认SortType.Numeric数字,SortType.Alphabetic字母)</param>
/// <param name="by">要排序的关键字模式</param>
/// <param name="get">要排序的键模式</param>
/// <returns></returns>
Task<RedisValue[]> GetSort(RedisKey key, long skip = 0L, long take = -1L, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null);
#endregion
#region 迁移指定键值到指定服务器库
/// <summary>
/// 迁移指定键值到指定服务器库
/// </summary>
/// <param name="key">键名</param>
/// <param name="toServer">服务器地址</param>
/// <param name="toDatabase">数据库索引</param>
/// <param name="timeountMSeconds">超时毫秒</param>
Task KeyMigrate(string key, EndPoint toServer, int toDatabase, int timeountMSeconds);
#endregion
#region 销毁连接
/// <summary>
/// 销毁连接
/// </summary>
void Dispose();
#endregion
}
}
3、Redis实现类
新建实现类:RedisCacheService.cs
需引用NuGet包:
StackExchange.Redis
Newtonsoft.Json
Microsoft.Extensions.Options.ConfigurationExtensions
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace Infrastructure.Cache
{
/// <summary>
/// Redis缓存服务类
/// </summary>
public class RedisCacheService : IRedisCacheService
{
#region 初始构造
/// <summary>
/// Redis缓存配置
/// </summary>
private RedisConfig _redisConfig;
/// <summary>
/// 当前数据库
/// </summary>
private IDatabase _database;
/// <summary>
/// Redis链接对象
/// </summary>
private readonly ConnectionMultiplexer _connectionMultiplexer;
/// <summary>
/// Redis默认缓存时间(秒)
/// </summary>
private double _defaultCacheTime;
/// <summary>
/// 初始构造
/// </summary>
/// <param name="optionsRedisConfig">配置项(支持类型1 ConfigStr字符串;支持类型2 ConfigurationOptions项)</param>
/// <exception cref="ArgumentNullException">配置项参数为空异常</exception>
public RedisCacheService(IOptions<RedisConfig> optionsRedisConfig)
{
_redisConfig = optionsRedisConfig.Value ?? throw new ArgumentNullException(nameof(optionsRedisConfig));//配置绑值
_defaultCacheTime = _redisConfig.DefaultCacheTime ?? 0;//获取缓存时间
var configString = _redisConfig.ConfigStr;//获取配置字符串
if (string.IsNullOrWhiteSpace(configString))
{//配置项
#region 绑定配置项
var configurationOptions = _redisConfig.RedisAutoMapping<RedisConfig, ConfigurationOptions>();//自动赋值
#region 绑定主机地址
if (!string.IsNullOrWhiteSpace(_redisConfig.HostUrl))
{
if (_redisConfig.HostUrl.Contains(";"))
{//多个Redis服务主机地址
var hostUrls = _redisConfig.HostUrl.Split(';');
foreach (var hostUrl in hostUrls)
{
configurationOptions.EndPoints.Add(hostUrl);
}
}
else
{//单个
configurationOptions.EndPoints.Add(_redisConfig.HostUrl);
}
}
#endregion
#endregion
_connectionMultiplexer = ConnectionMultiplexer.ConnectAsync(configurationOptions).Result;
}
else
{//配置字符串
_connectionMultiplexer = ConnectionMultiplexer.ConnectAsync(configString).Result;
}
_database = _connectionMultiplexer.GetDatabase();//获取当前操作数据库对象
}
/// <summary>
/// 初始构造(传入配置项)
/// </summary>
/// <param name="redisConfig">传入配置项(支持类型1 ConfigStr字符串;支持类型2 ConfigurationOptions项)</param>
/// <exception cref="ArgumentNullException">配置项参数为空异常</exception>
public RedisCacheService(RedisConfig redisConfig)
{
_redisConfig = redisConfig ?? throw new ArgumentNullException(nameof(redisConfig));//配置绑值
_defaultCacheTime = _redisConfig.DefaultCacheTime ?? 0;//获取缓存时间
var configString = _redisConfig.ConfigStr;//获取配置字符串
if (string.IsNullOrWhiteSpace(configString))
{//配置项
#region 绑定配置项
var configurationOptions = _redisConfig.RedisAutoMapping<RedisConfig, ConfigurationOptions>();//自动赋值
#region 绑定主机地址
if (!string.IsNullOrWhiteSpace(_redisConfig.HostUrl))
{
if (_redisConfig.HostUrl.Contains(";"))
{//多个Redis服务主机地址
var hostUrls = _redisConfig.HostUrl.Split(';');
foreach (var hostUrl in hostUrls)
{
configurationOptions.EndPoints.Add(hostUrl);
}
}
else
{//单个
configurationOptions.EndPoints.Add(_redisConfig.HostUrl);
}
}
#endregion
#endregion
_connectionMultiplexer = ConnectionMultiplexer.ConnectAsync(configurationOptions).Result;
}
else
{//配置字符串
_connectionMultiplexer = ConnectionMultiplexer.ConnectAsync(configString).Result;
}
_database = _connectionMultiplexer.GetDatabase();//获取当前操作数据库对象
}
#endregion
#region 获取当前Redis连接对象
/// <summary>
/// 获取当前Redis连接对象
/// </summary>
public ConnectionMultiplexer GetCurrConnectionMultiplexer()
{
return _connectionMultiplexer;
}
#endregion
#region 获取Redis服务的版本号
/// <summary>
/// 获取Redis服务的版本号
/// </summary>
public string GetRedisVersion()
{
IServer[] servers = _connectionMultiplexer.GetServers();
if (servers != null && servers.Length > 0)
{
string[] strings = new string[servers.Length];
for (int i = 0; i < servers.Length; i++)
{
strings[i] = servers[i].EndPoint.ToString() + "v" + servers[i].Version;
}
return string.Join(";", strings);
}
else
{
return string.Empty;
}
}
#endregion
#region 获取当前操作数据库对象
/// <summary>
/// 获取当前操作数据库对象
/// </summary>
public IDatabase GetCurrDatabase()
{
return _database;
}
#endregion
#region 切换数据库
/// <summary>
/// 切换数据库
/// 注意:切换后将保持此库连接,需手动切换回默认库:ChangeDatabase()
/// </summary>
/// <param name="db">库索引(不传为默认库)</param>
public void ChangeDatabase(int db = -1)
{
if (db > -1)
{//需要操作指定库
_database = _connectionMultiplexer.GetDatabase(db);//切换当前操作数据库为指定库
}
else
{
_database = _connectionMultiplexer.GetDatabase();//获取默认库
}
}
#endregion
#region 获取Redis的完整Key名称
/// <summary>
/// 获取Redis的完整Key名称(前缀_键名)
/// </summary>
/// <param name="key">键名</param>
/// <returns>返回带前缀的键名</returns>
public RedisKey GetKeyForRedis(RedisKey key)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(nameof(key));
if (!string.IsNullOrWhiteSpace(_redisConfig.InstanceName))
{
if (!key.ToString().StartsWith(_redisConfig.InstanceName))
{//开头没有前缀
return _redisConfig.InstanceName + "_" + key.ToString();
}
}
return key;
}
#endregion
#region 判断指定键名是否存在
/// <summary>
/// 判断指定键名是否存在
/// </summary>
/// <param name="key">键名</param>
/// <returns></returns>
public async Task<bool> Exists(RedisKey key)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(nameof(key));
return await _database.KeyExistsAsync(GetKeyForRedis(key), CommandFlags.PreferMaster);//优先主库
}
#endregion
#region 缓存String
#region 设置
#region 设置缓存String(绝对过期时间)
/// <summary>
/// 设置缓存String(绝对过期时间)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">对象值</param>
/// <param name="expiressAbsoulte">绝对过期时间</param>
/// <param name="isSerialize">是否序列化值(默认false不序列化,true序列化)</param>
public async Task<bool> SetString(RedisKey key, RedisValue? value, DateTimeOffset expiressAbsoulte, bool isSerialize = false)
{
TimeSpan t = expiressAbsoulte - DateTimeOffset.Now;
return await SetString(key, value, t, isSerialize);
}
#endregion
#region 设置缓存String(相对过期时间)
/// <summary>
/// 设置缓存String(相对过期时间)
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expirationSeconds">相对过期时间(秒)</param>
/// <param name="isSerialize">是否序列化值(默认false不序列化,true序列化)</param>
public async Task<bool> SetString(RedisKey key, RedisValue? value, double expirationSeconds, bool isSerialize = false)
{
return await SetString(key, value, TimeSpan.FromSeconds(expirationSeconds), isSerialize);
}
#endregion
#region 设置缓存String(自定义过期时间)
/// <summary>
/// 设置缓存String(自定义过期时间)
/// (未设置自定义过期时间使用默认缓存时间,未配置默认时永不过期)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">对象值</param>
/// <param name="ts">【可选】自定义过期时间</param>
/// <param name="isSerialize">是否序列化值(默认false不序列化,true序列化)</param>
public async Task<bool> SetString(RedisKey key, RedisValue? value, TimeSpan? ts = null, bool isSerialize = false)
{
if (value == null || !value.HasValue)
throw new ArgumentNullException(nameof(value));
RedisValue val = value.Value;
if (isSerialize)//需要序列化
val = JsonConvert.SerializeObject(value);
if (await Exists(key))
await Remove(key);
var res = await _database.StringSetAsync(GetKeyForRedis(key), val, null, When.Always, CommandFlags.PreferMaster);//初始永不过期,优先主服务器
if (ts != null)
{
await Expire(key, ts);//设置自定义过期时间
}
else
{
if (_defaultCacheTime > 0)
await Expire(key, TimeSpan.FromSeconds(_defaultCacheTime));//设置默认过期时间
}
return res;
}
#endregion
#region 设置实体对象序列化缓存String(自定义过期时间)
/// <summary>
/// 设置实体对象序列化缓存String(自定义过期时间)
/// (未设置自定义过期时间使用默认缓存时间,未配置默认时永不过期)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">实体对象值</param>
/// <param name="ts">【可选】自定义过期时间</param>
public async Task<bool> SetStringJson(RedisKey key, object value, TimeSpan? ts = null)
{
return await SetString(key, JsonConvert.SerializeObject(value), ts);
}
#endregion
#region 设置缓存String(指定键名追加值到末尾)
/// <summary>
/// 设置缓存String(指定键名追加值到末尾)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">追加的值</param>
/// <param name="isSerialize">是否序列化值(默认false不序列化,true序列化)</param>
/// <returns></returns>
public async Task<long> SetStringAppend(RedisKey key, RedisValue? value, bool isSerialize = false)
{
if (value == null || !value.HasValue)
throw new ArgumentNullException(nameof(value));
RedisValue val = value.Value;
if (isSerialize)//需要序列化
val = JsonConvert.SerializeObject(value);
return await _database.StringAppendAsync(GetKeyForRedis(key), val, CommandFlags.PreferMaster);//优先主服务器
}
#endregion
#region 设置缓存String(重设新值并返回旧值)
/// <summary>
/// 设置缓存String(重设新值并返回旧值)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">追加的值</param>
/// <param name="isSerialize">是否序列化值(默认false不序列化,true序列化)</param>
/// <returns></returns>
public async Task<RedisValue> SetStringGet(RedisKey key, RedisValue? value, bool isSerialize = false)
{
if (value == null || !value.HasValue)
throw new ArgumentNullException(nameof(value));
RedisValue val = value.Value;
if (isSerialize)//需要序列化
val = JsonConvert.SerializeObject(value);
return await _database.StringGetSetAsync(GetKeyForRedis(key), val, CommandFlags.PreferMaster);//优先主服务器
}
#endregion
#region 设置递增递减缓存
/// <summary>
/// 设置递增缓存(Redis专用)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">递增量(默认1)</param>
/// <param name="expTimeSpan">【可选】过期时间</param>
/// <returns></returns>
public async Task<long> SetIncrement(RedisKey key, long value = 1L, TimeSpan? expTimeSpan = null)
{
var res = await _database.StringIncrementAsync(GetKeyForRedis(key), value, CommandFlags.PreferMaster);//优先主服务器
if (expTimeSpan != null)
{
await Expire(key, expTimeSpan);//设置过期时间
}
else
{//设置默认过期时间
if (_defaultCacheTime > 0)
await Expire(key, TimeSpan.FromSeconds(_defaultCacheTime));//设置过期时间
}
return res;
}
/// <summary>
/// 设置递减缓存(Redis专用)
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">递减量(默认1)</param>
/// <param name="expTimeSpan">【可选】过期时间</param>
/// <returns></returns>
public async Task<long> SetDecrement(RedisKey key, long value = 1L, TimeSpan? expTimeSpan = null)
{
var res = await _database.StringDecrementAsync(GetKeyForRedis(key), value, CommandFlags.PreferMaster);//优先主服务器
if (expTimeSpan != null)
{
await Expire(key, expTimeSpan);//设置过期时间
}
else
{//设置默认过期时间
if (_defaultCacheTime > 0)
await Expire(key, TimeSpan.FromSeconds(_defaultCacheTime));//设置过期时间
}
return res;
}
#endregion
#endregion
#region 获取
/// <summary>
/// 获取String缓存原始数据
/// </summary>
/// <param name="key">键名</param>
public async Task<string> GetString(string key)
{
var value = await _database.StringGetAsync(GetKeyForRedis(key), CommandFlags.PreferReplica);//CommandFlags.PreferReplica:如果从服务器可用,优先在从服务器上执行
if (value != RedisValue.Null && !value.HasValue)
return default;
return value;
}
/// <summary>
/// 获取String缓存数据转为指定对象
/// </summary>
/// <typeparam name="T">实体类</typeparam>
/// <param name="key">键名</param>
/// <returns></returns>
public async Task<T> GetString<T>(string key)
{
var value = await _database.StringGetAsync(GetKeyForRedis(key), CommandFlags.PreferReplica);//CommandFlags.PreferReplica:如果从服务器可用,优先在从服务器上执行
if (value == RedisValue.Null || !value.HasValue)
return default(T);
return JsonConvert.DeserializeObject<T>(value);
}
#endregion
#endregion
#region 缓存List
// 注意:
// 1、小数数字偶现不精确,RedisValue尽量使用字符串类型值;
// 2、列表允许有重复值。
#region 设置
#region 顶底插入
/// <summary>
/// 从列表顶部插入数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">插入项的值</param>
/// <returns></returns>
public async Task<long> SetListLeftPush(RedisKey key, RedisValue value)
{
return await _database.ListLeftPushAsync(GetKeyForRedis(key), value, When.Always, CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 从列表底部插入数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">插入项的值</param>
/// <returns></returns>
public async Task<long> SetListRightPush(RedisKey key, RedisValue value)
{
return await _database.ListRightPushAsync(GetKeyForRedis(key), value, When.Always, CommandFlags.PreferMaster);//优先主库
}
#endregion
#region 中间的前后插入
/// <summary>
/// 从列表中间指定值位置之前插入数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="pivot">作为中间点的指定值</param>
/// <param name="value">插入项的值</param>
/// <returns></returns>
public async Task<long> SetListInsertBefore(RedisKey key, RedisValue pivot, RedisValue value)
{
return await _database.ListInsertBeforeAsync(GetKeyForRedis(key), pivot, value, CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 从列表中间指定值位置之后插入数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="pivot">作为中间点的指定值</param>
/// <param name="value">插入项的值</param>
/// <returns></returns>
public async Task<long> SetListInsertAfter(RedisKey key, RedisValue pivot, RedisValue value)
{
return await _database.ListInsertAfterAsync(GetKeyForRedis(key), pivot, value, CommandFlags.PreferMaster);//优先主库
}
#endregion
#region 修改列表指定索引位置的值
/// <summary>
/// 修改列表指定索引位置的值(无返回值)
/// </summary>
/// <param name="key">键名</param>
/// <param name="index">指定索引位置</param>
/// <param name="value">修改的值</param>
public async Task SetListByIndex(RedisKey key, long index, RedisValue value)
{
await _database.ListSetByIndexAsync(GetKeyForRedis(key), index, value, CommandFlags.PreferMaster);//优先主库
}
#endregion
#endregion
#region 删除
#region 从列表两边取出数据
/// <summary>
/// 从列表顶部拿出一条数据
/// </summary>
/// <param name="key">键名</param>
/// <returns>删除的值</returns>
public async Task<RedisValue> RemoveListLeftPop(string key)
{
return await _database.ListLeftPopAsync(GetKeyForRedis(key), CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 从列表顶部拿出一组数据(从 Redis 版本 6.2.0 开始支持)
/// </summary>
/// <param name="key">键名</param>
/// <param name="count">拿出的数量</param>
/// <returns>删除的一组值</returns>
public async Task<RedisValue[]> RemoveListLeftPop(string key, long count)
{
return await _database.ListLeftPopAsync(GetKeyForRedis(key), count, CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 从列表底部拿出一条数据
/// </summary>
/// <param name="key">键名</param>
/// <returns>删除的值</returns>
public async Task<RedisValue> RemoveListRightPop(string key)
{
return await _database.ListRightPopAsync(GetKeyForRedis(key), CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 从列表底部拿出一组数据(从 Redis 版本 6.2.0 开始支持)
/// </summary>
/// <param name="key">键名</param>
/// <param name="count">拿出的数量</param>
/// <returns>删除的一组值</returns>
public async Task<RedisValue[]> RemoveListRightPop(string key, long count)
{
return await _database.ListRightPopAsync(GetKeyForRedis(key), count, CommandFlags.PreferMaster);//优先主库
}
#endregion
#region 从列表删除所有指定值的数据项
/// <summary>
/// 从列表删除所有指定值的数据项
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">需要删除的指定值</param>
/// <returns>已删除元素的数量</returns>
public async Task<long> RemoveListByValue(string key, RedisValue value)
{
return await _database.ListRemoveAsync(GetKeyForRedis(key), value, 0, CommandFlags.PreferMaster);//优先主库
}
#endregion
#region 从列表删除指定起止索引位置范围外的数据项
/// <summary>
/// 从列表删除指定起止索引位置范围外的数据项(无返回值)
/// </summary>
/// <param name="key">键名</param>
/// <param name="start">起点索引</param>
/// <param name="stop">终点索引</param>
public async Task RemoveListByIndex(string key, long start, long stop)
{
await _database.ListTrimAsync(GetKeyForRedis(key), start, stop, CommandFlags.PreferMaster);//优先主库
}
#endregion
#endregion
#region 获取
/// <summary>
/// 获取列表一组数据(不会删除数据)
/// </summary>
/// <param name="key">键名</param>
/// <param name="start">起点索引(不传取所有)</param>
/// <param name="stop">终点索引(不传取所有)</param>
public async Task<RedisValue[]> GetListRange(string key, long start = 0L, long stop = -1L)
{
return await _database.ListRangeAsync(GetKeyForRedis(key), start, stop, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取列表指定索引的一条数据(不会删除数据)
/// </summary>
/// <param name="key">键名</param>
/// <param name="index">指定索引</param>
public async Task<RedisValue> GetListByIndex(string key, long index)
{
return await _database.ListGetByIndexAsync(GetKeyForRedis(key), index, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取列表项总数
/// </summary>
/// <param name="key">键名</param>
public async Task<long> GetListCount(string key)
{
return await _database.ListLengthAsync(GetKeyForRedis(key), CommandFlags.PreferReplica);//优先在从服务器上执行
}
#endregion
#endregion
#region 缓存Set
// 注意:
// 1、小数数字偶现不精确,RedisValue尽量使用字符串类型值;
// 2、列表自动排重;
// 3、列表无序。
#region 设置
/// <summary>
/// 列表插入数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">插入的值</param>
public async Task<bool> SetAdd(RedisKey key, RedisValue value)
{
return await _database.SetAddAsync(GetKeyForRedis(key), value, CommandFlags.PreferMaster);//优先主库
}
#endregion
#region 删除
/// <summary>
/// 随机删除列表一条数据
/// </summary>
/// <param name="key">键名</param>
/// <returns>删除的值</returns>
public async Task<RedisValue> RemoveSetPop(RedisKey key)
{
return await _database.SetPopAsync(GetKeyForRedis(key), CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 随机删除列表一组数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="count">删除项数量</param>
/// <returns>删除的一组值</returns>
public async Task<RedisValue[]> RemoveSetPop(RedisKey key, long count)
{
return await _database.SetPopAsync(GetKeyForRedis(key), count, CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 删除列表中指定值
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">需要删除的指定值</param>
public async Task<bool> RemoveSetByValue(RedisKey key, RedisValue value)
{
return await _database.SetRemoveAsync(GetKeyForRedis(key), value, CommandFlags.PreferMaster);//优先主库
}
#endregion
#region 获取
/// <summary>
/// 获取列表是否包含指定值
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">指定值</param>
public async Task<bool> GetSetContains(RedisKey key, RedisValue value)
{
return await _database.SetContainsAsync(GetKeyForRedis(key), value, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取列表项总数
/// </summary>
/// <param name="key">键名</param>
public async Task<long> GetSetCount(RedisKey key)
{
return await _database.SetLengthAsync(GetKeyForRedis(key), CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取所有列表项
/// </summary>
/// <param name="key">键名</param>
public async Task<RedisValue[]> GetSetMembers(RedisKey key)
{
return await _database.SetMembersAsync(GetKeyForRedis(key), CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 随机获取列表中一条数据(不会删除数据)
/// </summary>
/// <param name="key">键名</param>
public async Task<RedisValue> GetSetRandomMember(RedisKey key)
{
return await _database.SetRandomMemberAsync(GetKeyForRedis(key), CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 随机获取列表中一条数据(不会删除数据)
/// </summary>
/// <param name="key">键名</param>
/// <param name="count">随机项数量</param>
public async Task<RedisValue[]> GetSetRandomMembers(RedisKey key, long count)
{
return await _database.SetRandomMembersAsync(GetKeyForRedis(key), count, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 查询列表中匹配指定关键字的一组数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="pattern">匹配规则</param>
/// <param name="pageSize">分页大小</param>
/// <param name="cursor">游标</param>
/// <param name="pageOffset">页面偏移量</param>
public RedisValue[] GetSetScan(RedisKey key, RedisValue pattern = default, int pageSize = 250, long cursor = 0L, int pageOffset = 0)
{
return _database.SetScan(GetKeyForRedis(key), pattern, pageSize, cursor, pageOffset, CommandFlags.PreferReplica).ToArray();//优先在从服务器上执行
}
#endregion
#region 移动
/// <summary>
/// 将指定值从一个列表移动到另一个列表中
/// </summary>
/// <param name="key">源列表键名</param>
/// <param name="toKey">目标列表键名</param>
/// <param name="value">指定值</param>
public async Task<bool> GetSetMove(RedisKey key, RedisKey toKey, RedisValue value)
{
return await _database.SetMoveAsync(GetKeyForRedis(key), GetKeyForRedis(toKey), value, CommandFlags.PreferMaster);//优先主库
}
#endregion
#endregion
#region 缓存SortedSet
// 注意:
// 1、小数数字偶现不精确,RedisValue尽量使用字符串类型值;
// 2、列表自动排重;
// 3、列表有序(默认根据score分数升序);
// 4、列表多一列double类型的score分数列。
#region 设置
/// <summary>
/// 列表插入数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">插入的值</param>
/// <param name="score">分值</param>
public async Task<bool> SetSortedSetAdd(RedisKey key, RedisValue value, double score)
{
return await _database.SortedSetAddAsync(GetKeyForRedis(key), value, score, CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 列表插入递增数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">匹配对应的值</param>
/// <param name="score">递增的分值</param>
public async Task<double> SetSortedSetIncrement(RedisKey key, RedisValue value, double score)
{
return await _database.SortedSetIncrementAsync(GetKeyForRedis(key), value, score, CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 列表插入递减数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">匹配对应的值</param>
/// <param name="score">递减的分值</param>
public async Task<double> SetSortedSetDecrement(RedisKey key, RedisValue value, double score)
{
return await _database.SortedSetDecrementAsync(GetKeyForRedis(key), value, score, CommandFlags.PreferMaster);//优先主库
}
#endregion
#region 删除
/// <summary>
/// 删除列表中指定一个值
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">需要删除的指定值</param>
/// <returns>删除是否成功</returns>
public async Task<bool> RemoveSortedSetByValue(RedisKey key, RedisValue value)
{
return await _database.SortedSetRemoveAsync(GetKeyForRedis(key), value, CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 删除列表中指定一组值
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">需要删除的一组值</param>
/// <returns>删除的成员数</returns>
public async Task<long> RemoveSortedSetByValue(RedisKey key, RedisValue[] value)
{
return await _database.SortedSetRemoveAsync(GetKeyForRedis(key), value, CommandFlags.PreferMaster);//优先主库
}
#endregion
#region 获取
/// <summary>
/// 获取指定value值所在排名
/// </summary>
/// <param name="key">键名</param>
/// <param name="value">指定value值</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
/// <returns>值对应的排名(值不存在则为空)</returns>
public async Task<long?> GetSortedSetRank(RedisKey key, RedisValue value, Order order = Order.Ascending)
{
return await _database.SortedSetRankAsync(GetKeyForRedis(key), value, order, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取指定score分数范围内列表项总数
/// </summary>
/// <param name="key">键名</param>
/// <param name="min">范围起点</param>
/// <param name="max">范围终点</param>
/// <param name="exclude">起点终点排除设置(Exclude.None都不排除,Exclude.Start排除起点,Exclude.Stop排除终点,Exclude.Both都排除)</param>
public async Task<long> GetSortedSetCount(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None)
{
return await _database.SortedSetLengthAsync(GetKeyForRedis(key), min, max, exclude, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取指定排名范围内列表数据value
/// </summary>
/// <param name="key">键名</param>
/// <param name="start">范围起点</param>
/// <param name="stop">范围终点</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
public async Task<RedisValue[]> GetSortedSetRangeByRank(RedisKey key, long start = 0L, long stop = -1L, Order order = Order.Ascending)
{
return await _database.SortedSetRangeByRankAsync(GetKeyForRedis(key), start, stop, order, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取指定排名范围内列表数据value+score
/// </summary>
/// <param name="key">键名</param>
/// <param name="start">排名起点</param>
/// <param name="stop">排名终点</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
public async Task<SortedSetEntry[]> GetSortedSetRangeByRankWithScores(RedisKey key, long start = 0L, long stop = -1L, Order order = Order.Ascending)
{
return await _database.SortedSetRangeByRankWithScoresAsync(GetKeyForRedis(key), start, stop, order, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取指定score分数范围内列表数据value
/// </summary>
/// <param name="key">键名</param>
/// <param name="start">分数起点</param>
/// <param name="stop">分数终点</param>
/// <param name="exclude">起点终点排除设置(Exclude.None都不排除,Exclude.Start排除起点,Exclude.Stop排除终点,Exclude.Both都排除)</param>
/// <param name="skip">要跳过的项数量</param>
/// <param name="take">要取的项数量</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
public async Task<RedisValue[]> GetSortedSetRangeByScore(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0L, long take = -1L)
{
return await _database.SortedSetRangeByScoreAsync(GetKeyForRedis(key), start, stop, exclude, order, skip, take, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取指定score分数范围内列表数据value+score
/// </summary>
/// <param name="key">键名</param>
/// <param name="start">分数起点</param>
/// <param name="stop">分数终点</param>
/// <param name="exclude">起点终点排除设置(Exclude.None都不排除,Exclude.Start排除起点,Exclude.Stop排除终点,Exclude.Both都排除)</param>
/// <param name="skip">要跳过的项数量</param>
/// <param name="take">要取的项数量</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
public async Task<SortedSetEntry[]> GetSortedSetRangeByWithScores(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0L, long take = -1L)
{
return await _database.SortedSetRangeByScoreWithScoresAsync(GetKeyForRedis(key), start, stop, exclude, order, skip, take, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取指定value值范围内列表数据value
/// </summary>
/// <param name="key">键名</param>
/// <param name="min">值起点</param>
/// <param name="max">值终点</param>
/// <param name="exclude">起点终点排除设置(Exclude.None都不排除,Exclude.Start排除起点,Exclude.Stop排除终点,Exclude.Both都排除)</param>
/// <param name="skip">要跳过的项数量</param>
/// <param name="take">要取的项数量</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
public async Task<RedisValue[]> GetSortedSetRangeByValue(RedisKey key, RedisValue min = default, RedisValue max = default, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0L, long take = -1L)
{
return await _database.SortedSetRangeByValueAsync(GetKeyForRedis(key), min, max, exclude, order, skip, take, CommandFlags.PreferReplica);//优先在从服务器上执行
}
#endregion
#endregion
#region 缓存Hash
// 注意:
// 1、小数数字偶现不精确,RedisValue尽量使用字符串类型值;
// 2、key自动排重;
// 3、无序;
// 4、列表以key+value存储。
#region 设置
#region 基础插入
/// <summary>
/// 列表插入一组键值数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="fields">字段集合(字段名不能重复,同字段名的值会覆盖)</param>
public async Task SetHash(RedisKey key, HashEntry[] fields)
{
await _database.HashSetAsync(GetKeyForRedis(key), fields, CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 列表插入一条键值数据
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
/// <param name="value">字段值</param>
/// <returns></returns>
public async Task<bool> SetHash(RedisKey key, RedisValue hashField, RedisValue value)
{
return await _database.HashSetAsync(GetKeyForRedis(key), hashField, value, When.Always, CommandFlags.PreferMaster);//优先主库
}
#endregion
#region 设置递增递减缓存
/// <summary>
/// 列表插入一条递增键值数据(long数字)
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
/// <param name="value">递增量(默认1)</param>
/// <returns></returns>
public async Task<long> SetHashIncrement(RedisKey key, RedisValue hashField, long value = 1L)
{
return await _database.HashIncrementAsync(GetKeyForRedis(key), hashField, value, CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 列表插入一条递减键值数据(long数字)
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
/// <param name="value">递减量(默认1)</param>
/// <returns></returns>
public async Task<long> SetHashDecrement(RedisKey key, RedisValue hashField, long value = 1L)
{
return await _database.HashDecrementAsync(GetKeyForRedis(key), hashField, value, CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 列表插入一条递增键值数据(double浮点)
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
/// <param name="value">递增量(默认1)</param>
/// <returns></returns>
public async Task<double> SetHashIncrement(RedisKey key, RedisValue hashField, double value = 1L)
{
return await _database.HashIncrementAsync(GetKeyForRedis(key), hashField, value, CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 列表插入一条递减键值数据(double浮点)
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
/// <param name="value">递减量(默认1)</param>
/// <returns></returns>
public async Task<double> SetHashDecrement(RedisKey key, RedisValue hashField, double value = 1L)
{
return await _database.HashDecrementAsync(GetKeyForRedis(key), hashField, value, CommandFlags.PreferMaster);//优先主库
}
#endregion
#endregion
#region 删除
/// <summary>
/// 删除指定字段
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">要删除的字段名</param>
public async Task<bool> RemoveHash(RedisKey key, RedisValue hashField)
{
return await _database.HashDeleteAsync(GetKeyForRedis(key), hashField, CommandFlags.PreferMaster);//优先主库
}
/// <summary>
/// 删除指定字段集合
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashFields">要删除的字段集合</param>
public async Task<long> RemoveHash(RedisKey key, RedisValue[] hashFields)
{
return await _database.HashDeleteAsync(GetKeyForRedis(key), hashFields, CommandFlags.PreferMaster);//优先主库
}
#endregion
#region 获取
/// <summary>
/// 判断指定键名中指定字段名是否存在
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
public async Task<bool> ExistsHash(RedisKey key, RedisValue hashField)
{
return await _database.HashExistsAsync(GetKeyForRedis(key), hashField, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取指定键名中指定字段名的值
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashField">字段名</param>
public async Task<RedisValue> GetHash(RedisKey key, RedisValue hashField)
{
return await _database.HashGetAsync(GetKeyForRedis(key), hashField, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取指定键名中指定字段名集合的一组值
/// </summary>
/// <param name="key">键名</param>
/// <param name="hashFields">字段名集合</param>
public async Task<RedisValue[]> GetHash(RedisKey key, RedisValue[] hashFields)
{
return await _database.HashGetAsync(GetKeyForRedis(key), hashFields, CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取指定键名中所有字段名及对应值集合
/// </summary>
/// <param name="key">键名</param>
public async Task<HashEntry[]> GetHashAll(RedisKey key)
{
return await _database.HashGetAllAsync(GetKeyForRedis(key), CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取指定键名中所有字段名
/// </summary>
/// <param name="key">键名</param>
public async Task<RedisValue[]> GetHashKeys(RedisKey key)
{
return await _database.HashKeysAsync(GetKeyForRedis(key), CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取指定键名中所有字段值
/// </summary>
/// <param name="key">键名</param>
public async Task<RedisValue[]> GetHashValues(RedisKey key)
{
return await _database.HashValuesAsync(GetKeyForRedis(key), CommandFlags.PreferReplica);//优先在从服务器上执行
}
/// <summary>
/// 获取指定键名中所有字段总数
/// </summary>
/// <param name="key">键名</param>
public async Task<long> GetHashCount(RedisKey key)
{
return await _database.HashLengthAsync(GetKeyForRedis(key), CommandFlags.PreferReplica);//优先在从服务器上执行
}
#endregion
#endregion
#region 删除缓存
/// <summary>
/// 删除指定缓存
/// </summary>
/// <param name="key">键名</param>
public async Task<bool> Remove(RedisKey key)
{
return await _database.KeyDeleteAsync(GetKeyForRedis(key), CommandFlags.PreferMaster);//优先主服务器执行
}
#endregion
#region 设置指定键名过期时间
/// <summary>
/// 设置指定键名过期时间
/// </summary>
/// <param name="key">键名</param>
/// <param name="expiry">自定义过期时间</param>
/// <param name="when">过期条件(Always,GreaterThanCurrentExpiry,HasExpiry,HasNoExpiry,LessThanCurrentExpiry)</param>
/// <returns></returns>
public async Task<bool> Expire(RedisKey key, TimeSpan? expiry, ExpireWhen when = ExpireWhen.Always)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(nameof(key));
if (!await Exists(key))//不存在
return false;
return await _database.KeyExpireAsync(GetKeyForRedis(key), expiry, when, CommandFlags.PreferMaster);//优先主服务器执行
}
#endregion
#region 重命名指定键名
/// <summary>
/// 重命名指定键名
/// </summary>
/// <param name="key">原键名</param>
/// <param name="newKey">新键名</param>
public async Task<bool> Rename(RedisKey key, RedisKey newKey)
{
return await _database.KeyRenameAsync(GetKeyForRedis(key), GetKeyForRedis(newKey), When.Always, CommandFlags.PreferMaster);//优先主库
}
#endregion
#region 获取匹配键名的数据
#region 获取匹配键名的总数
/// <summary>
/// 获取模糊匹配的键名总数
/// </summary>
/// <param name="key">键名关键字(为空查所有键)</param>
public async Task<int> GetKeysCount(string key = "")
{
var pattern = $"*{key}*";
return await GetKeysPatternCount(pattern);
}
/// <summary>
/// 获取指定匹配规则的键名总数
/// </summary>
/// <param name="pattern">匹配规则</param>
public async Task<int> GetKeysPatternCount(string pattern)
{
if (string.IsNullOrWhiteSpace(pattern))
throw new ArgumentNullException(nameof(pattern));
var res = await _database.ScriptEvaluateAsync(LuaScript.Prepare(
" local res = redis.call('KEYS', @keypattern) " +
" return res "), new { @keypattern = pattern }, CommandFlags.PreferReplica);//优先从库
return res.Length;
}
#endregion
#region 获取匹配的键名集合
/// <summary>
/// 获取模糊匹配的键名集合
/// </summary>
/// <param name="key">键名关键字(为空查所有键)</param>
public async Task<IEnumerable<string>> GetKeys(string key = "")
{
var pattern = $"*{key}*";
return await GetKeysPattern(pattern);
}
/// <summary>
/// 获取指定匹配规则的键名集合
/// </summary>
/// <param name="pattern">匹配规则</param>
public async Task<IEnumerable<string>> GetKeysPattern(string pattern)
{
if (string.IsNullOrWhiteSpace(pattern))
throw new ArgumentNullException(nameof(pattern));
var res = await _database.ScriptEvaluateAsync(LuaScript.Prepare(
" local res = redis.call('KEYS', @keypattern) " +
" return res "), new { @keypattern = pattern }, CommandFlags.PreferReplica);//优先从库
if (res == null)
return default;
return (string[])res;
}
#endregion
#region 获取匹配的键名对应值集合
/// <summary>
/// 获取模糊匹配的键名对应值集合(只适用String类型数据)
/// </summary>
/// <param name="key">键名关键字(为空查所有键)</param>
public async Task<RedisValue[]> GetKeysValues(string key = "")
{
var pattern = $"*{key}*";
return await GetKeysValuesPattern(pattern);
}
/// <summary>
/// 获取指定匹配规则的键名对应值集合(只适用String类型数据)
/// </summary>
/// <param name="pattern">匹配规则</param>
public async Task<RedisValue[]> GetKeysValuesPattern(string pattern)
{
if (string.IsNullOrWhiteSpace(pattern))
throw new ArgumentNullException(nameof(pattern));
try
{
RedisResult res = await _database.ScriptEvaluateAsync(LuaScript.Prepare(
" local keys = redis.call('KEYS', @keypattern) " +
" return redis.call('MGET', unpack(keys))"), new { @keypattern = pattern }, CommandFlags.PreferReplica);//优先从库
return (RedisValue[])res;
}
catch (Exception)
{//查询键名为空时取值会报错
return Array.Empty<RedisValue>();
}
}
#endregion
#endregion
#region 列表重新排序(List、Set、SortedSet)
/// <summary>
/// 列表重新排序(List、Set、SortedSet)
/// </summary>
/// <param name="key">键名</param>
/// <param name="skip">要跳过的项数量</param>
/// <param name="take">要取的项数量</param>
/// <param name="order">排序方式(默认Order.Ascending升序,Order.Descending降序)</param>
/// <param name="sortType">排序类型(默认SortType.Numeric数字,SortType.Alphabetic字母)</param>
/// <param name="by">要排序的关键字模式</param>
/// <param name="get">要排序的键模式</param>
/// <returns></returns>
public async Task<RedisValue[]> GetSort(RedisKey key, long skip = 0L, long take = -1L, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default, RedisValue[]? get = null)
{
return await _database.SortAsync(GetKeyForRedis(key), skip, take, order, sortType, by, get, CommandFlags.PreferMaster);//优先主库,Sort命令需要写权限
}
#endregion
#region 迁移指定键值到指定服务器库
/// <summary>
/// 迁移指定键值到指定服务器库
/// </summary>
/// <param name="key">键名</param>
/// <param name="toServer">服务器地址</param>
/// <param name="toDatabase">数据库索引</param>
/// <param name="timeountMSeconds">超时毫秒</param>
public async Task KeyMigrate(string key, EndPoint toServer, int toDatabase, int timeountMSeconds)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(nameof(key));
await _database.KeyMigrateAsync(GetKeyForRedis(key), toServer, toDatabase, timeountMSeconds);
}
#endregion
#region 销毁连接
/// <summary>
/// 销毁连接
/// </summary>
public void Dispose()
{
if (_connectionMultiplexer != null)
_connectionMultiplexer.Dispose();
GC.SuppressFinalize(this);
}
#endregion
}
}
注:以上三个类可以直接封装到NuGet包中使用。
4、Redis配置json
appsettings.json中常用两种配置方式(二选一即可)
如下:(“RedisConfig”为固定配置名称,如需更改可在下面的实现类更改即可)
//配置方式 1 (连接配置项)
"RedisConfig": {
"InstanceName": "MyRedisServer", //实例名(前缀)
"HostUrl": "127.0.0.1:6379;127.0.0.1:6380", //redis服务地址(支持多个,英文逗号隔开)
"Password": "123456",//有密码则配置,无密码删除此项
"KeepAlive": 180,//发送消息以帮助保持套接字活动的时间(秒)(默认为 60 秒)
"AllowAdmin": true,//启用一系列被认为有风险的命令
"AbortOnConnectFail": false, //连接失败不中止连接
"DefaultDatabase": 0, //默认库
"DefaultCacheTime": 7200 //默认缓存时间(秒)
}
//配置方式 2 (连接字符串)
"RedisConfig": {
"InstanceName": "MyRedisServer", //实例名(前缀)
"ConfigStr": "127.0.0.1:6379,127.0.0.1:6380,password=123456,keepAlive=180,allowAdmin=true,abortConnect=false,defaultDatabase=0", //redis配置字符串
"DefaultCacheTime": 7200 //默认缓存时间(秒)
}
如需配置其他属性,参考以下官方文档,所有属性均支持,但需注意您使用的Redis服务版本,部分属性需要Redis高版本支持。
官方配置:https://stackexchange.github.io/StackExchange.Redis/Configuration
5、Redis服务注册
以.NET6框架为例,在Program.cs类中添加以下代码:
//方式 1 通过扩展类方式
builder.Services.AddRedisCache(builder.Configuration);//注册Redis缓存服务
//方式 2 直接注册配置类及服务接口
builder.Services.Configure<RedisConfig>(builder.Configuration.GetSection("RedisConfig"));//注入Redis配置项
builder.Services.AddSingleton<IRedisCacheService, RedisCacheService>();//注入Redis服务接口
6、业务层使用
业务控制器注入接口类:
/// <summary>
/// redis缓存操作
/// </summary>
private readonly IRedisCacheService _redisCache;
/// <summary>
/// 控制器构造
/// </summary>
/// <param name="redisCache">redis缓存操作类</param>
public MyController(IRedisCacheService redisCache)
{
_redisCache = redisCache;
}
/// <summary>
/// 测试
/// </summary>
[HttpGet("Test")]
public async Task<IActionResult> Test()
{
var version = _redisCache.GetRedisVersion();
var a = await _redisCache.SetString("aaa1", "asfsdfsdgfsdfwe124sfse");//设置键值(字符串类型)
var b = await _redisCache.GetString("aaa1");//获取键值
var k = await _redisCache.GetKeys();//获取所有键名
var k2 = await _redisCache.GetKeysPattern("*");
var k3 = await _redisCache.GetKeys("123123");
var v1 = await _redisCache.GetKeysValues();
var v2 = await _redisCache.GetKeysValuesPattern("*");
var v3 = await _redisCache.GetKeysValues("1123234");
//其他……
return Ok(version);
}
以上均为本人实操总结,如有不对的请指教,其他疑问可评论留言。