• dotnetcore三大Redis客户端对比和使用心得


    前言

      稍微复杂一点的互联网项目,技术选型都可能会涉及Redis,.NetCore的生态越发完善,支持.NetCore的Redis客户端越来越多,

    下面三款常见的Redis客户端,相信大家平时或多或少用到一些,结合平时对三款客户端的使用,有些心得体会。

    先比较宏观的背景: 

    包名称

    背景

    github star

    .NetStandard2.0目标框架上 依赖

    Stackexchange.redis

    老牌.Net Redis客户端,免费无限制,Stackoverflow背书

    3700+

    Microsoft.Extensions.Caching.StackExchangeRedis

    .Netcore 2.2针对IDistributedCache接口实现的Redis分布式缓存

     

    CSRedisCore

    国人实现的著名第三方客户端

    894+

     

    使用心得

    三款客户端Redis支持的连接字符串配置基本相同

      "connectionstrings": {
        "redis": "localhost:6379,password=abcdef,connectTimeout=5000,writeBuffer=40960"
      } 

    StackExchange.Redis

      定位是高性能、通用的Redis .Net客户端;方便地应用Redis全功能;支持Redis Cluster

    • 高性能的核心在于:多路复用连接允许有效使用来自多个调用线程的共享连接), 服务器端操作使用ConnectionMultiplexer 类
    ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("server1:6379,server2:6379");
    // 日常应用的核心类库是IDatabase
    IDatabase db = redis.GetDatabase();
    
    // 支持Pub/Sub
    ISubscriber sub = redis.GetSubscriber();
    sub.Subscribe("messages", (channel, message) => {
        Console.WriteLine((string)message);
    });
    ---
    sub.Publish("messages", "hello");

    也正是因为多路复用,StackExchange.Redis唯一不支持的Redis特性是 "blocking pops",这个特性是RedisMQ的关键理论。

    如果你需要blocking pops, StackExchange.Redis官方推荐使用pub/sub模型模拟实现。

    • 日常操作的API请关注IDatabase接口,支持异步方法,这里我对【客户端操作Redis尽量不要使用异步方法】的说法不敢苟同,对于异步方法我认为还是遵守微软最佳实践:对于IO密集的操作,能使用异步尽量使用异步

    // 对应redis自增api:DECR mykey
    _redisDB0.StringDecrementAsync("ProfileUsageCap", (double)1)
    // 对应redis api: HGET KEY field1
    _redisDB0.HashGetAsync(profileUsage, eqidPair.ProfileId))       
    // 对应redis哈希自增api:  HINCRBY myhash field -1
    _redisDB0.HashDecrementAsync(profileUsage, eqidPair.ProfileId, 1)
    • ConnectionMultiplexer 方式支持随时切换Redis DB,对于多个Redis DB的操作,我封装了一个常用的Redis DB 操作客户端。
     public class RedisStore
        {
            private static Lazy<ConnectionMultiplexer> LazyConnection;
            private static string connectionRedis = "localhost:6379";
    
            public RedisStore(string connectiontring)
            {
                connectionRedis = connectiontring ?? "localhost:6379";
                LazyConnection = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(connectionRedis));
            }
            public static ConnectionMultiplexer Connection => LazyConnection.Value;
            public RedisDatabase RedisCache => new RedisDatabase(Connection);
    
        }
    
        public class RedisDatabase
        {
            private Dictionary<int, IDatabase> DataBases = new Dictionary<int, IDatabase>();
            
            public ConnectionMultiplexer RedisConnection { get; }
    
            public RedisDatabase(ConnectionMultiplexer Connection)
            {
                DataBases = new Dictionary<int, IDatabase>{ };
                for(var i=0;i<16;i++)
                {
                    DataBases.Add(i, Connection.GetDatabase(i));
                }
                
                RedisConnection = Connection;
            }
    
            public IDatabase this[int index]
            {
                get
                {
                    if (DataBases.ContainsKey(index))
                        return DataBases[index];
                    else
                       return DataBases[0];
                }
            }
        }
    RedisCache

    Microsoft.Extensions.Caching.StackExchangeRedis

        从nuget doc可知,该组件库依赖于 StackExchange.Redis 客户端; 是.NetCore针对分布式缓存提供的客户端,侧重点在 Redis的缓存特性

    该库是基于 IDistributedCache 接口实现的,该接口为实现分布式缓存的通用性,缓存内容将以byte[] 形式读写
    另外能使用的函数签名也更倾向于【通用的 增、查操作】
    // add Redis cache service 
    services.AddStackExchangeRedisCache(options =>
    {
      options.Configuration = Configuration.GetConnectionString("redis");
      options.InstanceName = "SampleInstance";
    });
    
    // Set Cache Item (by byte[])
     lifetime.ApplicationStarted.Register(() =>
    {
          var currentTimeUTC = DateTime.UtcNow.ToString();
          byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
          var options = new DistributedCacheEntryOptions()
                .SetSlidingExpiration(TimeSpan.FromMinutes(20));
          cache.Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
    });  
    
    // Retrieve Cache Item
    [HttpGet]
    [Route("CacheRedis")]
    public async Task<string> GetAsync()
    {
      var ret = "";
      var bytes = await _cache.GetAsync("cachedTimeUTC");
       if (bytes != null)
       {
          ret = Encoding.UTF8.GetString(bytes);
          _logger.LogInformation(ret);
       }
       return  await Task.FromResult(ret);
    }

    ① 很明显,该Cache组件并不能做到自由切换 Redis DB, 目前可在redis连接字符串一次性配置项目要使用哪个Redis DB

    ② 会在指定DB(默认为0)生成key = SampleInstancecachedTimeUTC 的redis缓存项

    ③ Redis并不支持bytes[] 形式的存储值,以上byte[] 实际是以Hash的形式存储

    CSRedisCore

    该组件是基于连接池模型,默认配置会预热50个redis连接。 功能更好灵活,针对实际Redis应用场景有更多玩法

    - 普通模式

    - 官方集群模式 redis cluster

    - 分区模式(作者实现)

    普通模式使用方法极其简单,这里要提示的是: 该客户端也不支持 随意切换 Redis DB, 但是原作者给出一种缓解的方式:构造多客户端。
    var redisDB = new CSRedisClient[16];                        // 多客户端
    for (var a = 0; a < redisDB.Length; a++)
      redisDB[a] = new CSRedisClient(Configuration.GetConnectionString("redis") + ",defaultDatabase=" + a);
    services.AddSingleton(redisDB);
    // ----------------------------
    _redisDB[0].IncrByAsync("ProfileUsageCap", -1)
    _redisDB[0].HGetAsync(profileUsage, eqidPair.ProfileId.ToString())
    _redisDB[0].HIncrByAsync(profileUsage, eqidPair.ProfileId.ToString(), -1);

     内置的静态操作类RedisHelper, 与Redis-Cli 命令完全一致, 故他能原生支持”blocking pops”。

    // 实现后台服务,持续消费MQ消息
    public class BackgroundJob : BackgroundService
        {
            private readonly CSRedisClient[] _redisDB;
            private readonly IConfiguration _conf;
            private readonly ILogger _logger;
            public BackgroundJob(CSRedisClient[] csRedisClients,IConfiguration conf,ILoggerFactory loggerFactory)
            {
                _redisDB = csRedisClients;
                _conf = conf;
                _logger = loggerFactory.CreateLogger(nameof(BackgroundJob));
            }
            
            //  Background 需要实现的后台任务
            protected override async Task ExecuteAsync(CancellationToken stoppingToken)
            {
                _redisDB[0] = new CSRedisClient(_conf.GetConnectionString("redis") + ",defualtDatabase=" + 0);
                RedisHelper.Initialization(_redisDB[0]);
    
                while (!stoppingToken.IsCancellationRequested)
                {
                    var key = $"eqidpair:{DateTime.Now.ToString("yyyyMMdd")}";
                    // 阻塞式从右侧读取List首消息 
                    var eqidpair = RedisHelper.BRPop(5, key);
                    // TODO Handler Message
                    else
                        await Task.Delay(1000, stoppingToken);
                }
            }
        }
    
    -----RedisMQ 生产者---
    //  将一个或多个msg插入List头部
    RedisHelper.LPush(redisKey, eqidPairs.ToArray());
    简单有效RedisMQ

    Redis的一点小经验:

    • 对自己要使用的Redis API 的时间复杂度心里要有数,尽量不要使用长时间运行的命令如keys *,可通过redis.io SlowLog命令观测 哪些命令耗费较长时间

    • Redis Key可按照“:”分隔定义成有业务意义的字符串,如NewUsers:201909:666666(某些Redis UI可直观友好查看该业务)

    • 合适确定Key-Value的大小:Redis对于small value更友好, 如果值很大,考虑划分到多个key

    • 关于缓存穿透,面试的时候会问,自行搜索布隆过滤器。

    • redis虽然有持久化机制,但在实际中会将key-value 持久化到关系型数据库,因为对于某些结构化查询,SQL更为有效。

    ----- 20190829 多说两句-------

    以上三大客户端,Microsoft.Extensions.Caching.StackExchangeRedis 与其他两者的定位还是有很大差距的,单纯使用Redis 缓存特性, 有微软出品,必属精品情结的可使用此客户端;

    StackExchange.Redis、CSRedisCore 对于Redis全功能特性支持的比较全,但是我也始终没有解决StackExchange.Redis : RedisTimeoutException 超时的问题,换成CSRedisCore 确实没有出现Redis相关异常。

    ---- 2019-09-25 update--------

    CSRedisCore 挖坑填坑经历

    - 受到公号一些网友的启发,再次使用StackExchange.Redis , 并在redis配置中增加 abortConnect= false(缺省为true,表示不尝试重连),超时问题不再出现。

  • 相关阅读:
    VC 中 C2275问题解决
    MIPS指令学习
    《高效人士的116个IT秘诀》读书笔记
    Mercurial入门学习
    foobar 插件安装
    五笔输入法的学习记录
    AutoHotKey入门使用
    windows shell
    CSPS 2021霜降记
    ubunru下jdk安装
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/11418881.html
Copyright © 2020-2023  润新知