前言
稍微复杂一点的互联网项目, 技术选型都可能会涉及 Redis,.NetCore 的生态越发完善, 支持. NetCore 的 Redis 客户端越来越多,
下面三款常见的 Redis 客户端, 相信大家平时或多或少用到一些, 结合平时对三款客户端的使用, 有些心得体会.
先比较宏观的背景:
包名称 | 背景 | github star | .NetStandard2.0 目标框架上 依赖 |
Stackexchange.redis | 老牌. Net Redis 客户端,免费无限制,Stackoverflow 背书 | 3700 | |
Microsoft.Extensions.Caching.StackExchangeRedis | .Netcore 2.2 针对 < a class="xref" href="https://docs.microsoft.com/dotnet/api/microsoft.extensions.caching.distributed.idistributedcache" data-linktype="external" ztid="67" ow="110" oh="19">IDistributedCache 接口实现的 Redis 分布式缓存 | ||
CSRedisCore | 国人实现的著名第三方客户端, redis.io 官方网站推荐 | 894 |
使用心得
三款客户端 Redis 支持的连接字符串配置基本相同
- "connectionstrings": {
- "redis": "localhost:6379,password=abcdef,connectTimeout=5000,writeBuffer=40960"
- }
- StackExchange.Redis
定位是高性能, 通用的 Redis .Net 客户端; 方便地应用 Redis 全功能; 支持 Redis Cluster
高性能的核心在于 多路复用器 (支持在多个调用线程高效共享 Redis 连接)
- 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 密集的操作, 能使用异步尽量使用异步
- _redisDB0.StringDecrementAsync("ProfileUsageCap", (double)1) // 对应 Redis 自增 API:DECR mykey
- _redisDB0.HashGetAsync(profileUsage, eqidPair.ProfileId)) // 对应 Redis API: hget key field1
- _redisDB0.HashDecrementAsync(profileUsage, eqidPair.ProfileId, 1) // 对应 Redis 哈希自增 API: HINCRBY myhash field -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);
- }
1 很明显, 该 Cache 组件并不能做到自由切换 Redis DB, 目前可在 Redis 连接字符串一次性配置项目要使用哪个 Redis DB
1 会在指定 DB(默认为 0) 生成 key = SampleInstancecachedTimeUTC 的 Redis 缓存项
2 Redis 并不支持 bytes[] 形式的存储值, 以上 byte[] 实际是以 Hash 的形式存储
CSRedisCore
该组件的功能更为强大, 针对实际 Redis 应用场景有更多玩法.
- 普通模式
- 官方集群模式 Redis cluster
- 分区模式 (作者实现)
普通模式使用方法极其简单, 这里要提示的是: 该客户端也不支持 随意切换 Redis DB, 但是原作者给出一种缓解的方式: 构造多客户端.
- var redisDB = new CSRedisClient[16]; // 多客户端
- ......
- 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 更为有效.
来源: https://www.cnblogs.com/JulianHuang/p/11418881.html