Windows环境下Redis主从服务读写分离安装部署与.NET实现Redis读写分离

5 篇文章 0 订阅
3 篇文章 0 订阅

前言

说在最前面,本章主要讲解如下:
一、在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 优点

  1. 高性能:基于内存操作,读写速度非常快,可以达到10万次/秒以上(读110000次/s,写81000次/s)。这使得Redis非常适合用于高并发的场景,如电商网站、社交网络等。
  2. 丰富的数据结构:支持多种数据结构(String、List、Set、SortedSet、Hash等),可以满足不同场景的需求。例如,使用哈希表可以实现快速查找;使用集合可以实现交集、并集等操作;使用有序集合可以实现排行榜等功能。
  3. 持久化:支持两种持久化方式,RDB和AOF。RDB是将当前内存中的数据生成一个快照文件,而AOF则是记录每个写操作,重启时通过重新执行这些操作来恢复数据。这两种方式可以根据实际需求选择使用。
  4. 主从复制:支持主从复制,可以将一台服务器的数据同步到另一台服务器,实现数据的备份和负载均衡,也可用作读写分离。
  5. 发布订阅:支持发布订阅模式,可以实现消息的实时推送和广播。
  6. 成本低:单例部署简单,大部分开源免费,社区活跃。
  7. 扩展性强:支持集群分布式部署。

2、Redis 缺点

  1. 内存消耗:Redis 是基于内存的数据库,存储容量受物理内存限制,数据量较大时会占用大量内存,不能用作海量数据的高性能读写。这可能导致需要投入更多的成本来扩展内存容量或者进行性能优化。内存管理也不够灵活,不支持自动内存碎片整理或按需分配内存。
  2. 持久化和数据安全:虽然 Redis 提供了持久化选项,但在某些情况下可能会存在数据丢失的风险。比如在使用异步持久化时,如果在持久化过程中发生了故障,可能会导致部分数据丢失。主从模式下主机宕机前如有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
  3. 单线程模型:Redis 的单线程模型意味着它在处理大量并发请求时可能存在性能瓶颈。虽然 Redis 在实践中通常能够处理大量请求,但在极端情况下处理大量客户端连接时可能会受到限制。
  4. 定时快照(snapshot):每隔一段时间将整个数据库写到磁盘上,每次均是写全部数据,代价非常高。
  5. 基于语句追加(AOF):只追踪变化的数据,但是追加的log可能过大,同时所有的操作均重新执行一遍,回复速度慢。
  6. 数据结构局限性:虽然 Redis 支持丰富的数据结构,但在某些特定的复杂查询或者关联查询方面,如join联接、子查询、批量更新等,且事务处理能力弱,使用传统的关系型数据库可能更为方便。
  7. 高可用性配置复杂:在构建高可用的 Redis 集群时,需要进行复杂的配置和监控,确保集群能够正确地工作并且数据不会丢失。
  8. 开发复杂性:使用 Redis 可能需要对数据的存储和访问模式进行重新思考,需要更多的开发和设计成本来保证数据一致性和正确性,缺乏数据完整性约束,不提供SQL支持会造成开发人员额外的学习成本。
  9. 在线扩容较难:在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费,否则需要对系统停服升级。

3、Redis Key的命名建议

  1. 简洁性:尽可能简短,但要足够表达键的意思,尽量不要超过1024字节。
  2. 唯一性:确保键名在应用中是唯一的,且区分大小写,以避免冲突。
  3. 无空格:键名中不要包含空格,可以使用下划线_作为单词之间的分隔符。
  4. 清晰性:键名应该是清晰的,可以通过名字直接理解其含义。
  5. 前缀规范:可以使用项目名或模块名作为前缀,以区分不同应用或模块的键。
  6. 类型明确:如果值的类型不同,可以通过前缀或后缀来区分。

一、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
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

安装完成后验证是否安装成功
  1. 打开安装目录检查是否已有Redis相关文件
    在这里插入图片描述
  2. win+r 运行,输入命令services.msc打开系统服务,查看是否有“Redis”服务正在运行
    在这里插入图片描述
  3. 运行,打开cmd,输入命令“redis-cli”,连接本地Redis服务客户端(未设置密码可直接进入),进入后输入命令“info”,查看当前安装的Redis版本相关信息,至此您已成功安装Redis服务。
    在这里插入图片描述

:如需要修改数据存放位置等其他配置,同下一种方式,直接修改配置文件,重启服务即可。

②命令行方式

进入配置文件配置相关参数
  1. 解压“Win-最新-Redis-x64-3.2.100.zip”压缩包,得到以下文件包
    在这里插入图片描述
  2. 找到配置文件,需要部署为系统服务则打开“redis.windows-service.conf”配置文件进行配置;非服务模式选“redis.windows.conf”;二者区别并不大,需要的配置都可以自行更改。
    在这里插入图片描述
  3. 打开配置文件,找到连接地址配置项“bind”,“127.0.0.1”代表本地连接,需要开启远程连接则改为“0.0.0.0”。
# bind 127.0.0.1
bind 0.0.0.0

在这里插入图片描述

  1. 找到端口配置项“port”,默认6379,可根据需要自行修改其他端口,注意需要远程连接的需要打开对应端口
port 6379

在这里插入图片描述

  1. 找到最大占用内存限制配置项“maxmemory”,默认不限制,需要限制则启用并设置大小,数值不带单位表“字节数”,带单位以单位为准(B、KB、MB、GB…),注意数值与单位之间不能有空格。
# maxmemory <bytes>
maxmemory 2048MB

在这里插入图片描述

  1. 找到在达到内存限制时的淘汰策略配置项“maxmemory-policy”,配置为根据LRU算法生成的过期时间来删除“volatile-lru”。
    在这里插入图片描述

Redis默认有六种过期策略:
volatile-lru -> 根据LRU算法生成的过期时间来删除。
-allkeys-lru -> 根据LRU算法删除任何key。
volatile-random -> 根据过期设置来随机删除key。
allkeys->random -> 无差别随机删。
volatile-ttl -> 根据最近过期时间来删除(辅以TTL)。
noeviction -> 谁也不删,直接在写操作时返回错误。

  1. 找到数据存储位置配置项“dir”,默认“dir ./”即存储在安装根目录下,可更改为指定位置,注意需保证文件夹路径存在
dir C:/Redis/Data

在这里插入图片描述

  1. 找到密码配置项“requirepass”,不需要密码可跳过此步(没开远程连接的不用密码效率更高,开了远程连接的建议加上密码更安全),设置如下。
# requirepass foobared
requirepass 123456

在这里插入图片描述

  1. 配置环境变量,系统属性→环境变量→Path编辑→新增Redis安装目录→保存(部分系统需要重启生效),如下图。
    在这里插入图片描述
配置完成后验证是否配置成功
  1. 运行cmd,进入安装目录输入命令“redis-server.exe redis.windows.conf”启动Redis,查看Redis是否已启动,关闭窗体即停止。
    在这里插入图片描述
  2. 测试Redis是否可连接,运行cmd,进入安装目录输入命令“redis-cli -h 127.0.0.1 -p 6379 -a 123456”(无密码则不输入“ -a 123456”),连接后输入命令“info”,查看是否输出版本号等信息,如下图即为成功。
    在这里插入图片描述
安装到Windows系统服务
  1. 安装服务,运行cmd,进入安装目录输入以下命令:
redis-server --service-install redis.windows-service.conf --service-name "Redis"  --loglevel verbose
  1. 启动服务:
redis-server --service-start --service-name "Redis"

操作成功显示如下:
在这里插入图片描述

  1. 服务其他操作
--停止服务
redis-server --service-stop --service-name "Redis"
--卸载服务
redis-server --service-uninstall --service-name "Redis"

3、部署主从配置

①主服务部署(写读)

同单例服务部署安装一样, 服务参照即可,无需特殊处理。
注意:主服务设置了密码,从服务必须设置密码,且必须相同。

②从服务部署(只读)

  1. 参照单例服务命令行方式,重新解压一组Redis文件到文件夹“RedisSlave”,重复操作命令行方式步骤1.2.3,第4步由于是同一台服务器所以需修改端口为6380或其他端口,注意远程连接要开防火墙对应端口(同一台服务器端口需不同,不同服务器可保持默认6379端口
port 6380

继续重复操作步骤5.6.7,第8步设置密码,同主服务密码保持一致(如主服务未设置密码,从服务也不能设置密码)

# requirepass foobared
requirepass 123456
  1. 找到主服务的IP及端口配置项“slaveof”,“10.32.128.54”为主服务的IP,修改配置如下:
# slaveof <masterip> <masterport>
slaveof 10.32.128.54 6379

在这里插入图片描述

  1. 找到主服务的访问密码配置项“masterauth”,配置为你主服务设置的密码
# masterauth <master-password>
masterauth 123456

在这里插入图片描述

  1. 找到配置从服务为只读模式的配置项“slave-read-only”, 2.6 版本开始默认为 yes 开启的,如有特殊需求,可关闭从服务只读模式,关闭改为 no
slave-read-only yes

在这里插入图片描述

  1. 安装到Windows系统服务并启动,操作同上面的单例服务一样,注意服务名称不能重复。
redis-server --service-install redis.windows-service.conf --service-name "Redis Slave"  --loglevel verbose
redis-server --service-start --service-name "Redis Slave"

在这里插入图片描述

  1. 验证主从是否配置成功,使用cmd连接客户端,或使用上面下载的RESP可视化管理工具操作查看;配置成功时,主服务写入数据,从服务会自动同步此数据,如下图:
    cmd在这里插入图片描述RESP在这里插入图片描述
    在这里插入图片描述

如需部署多个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);
        }

以上均为本人实操总结,如有不对的请指教,其他疑问可评论留言。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值