错误使用.Net Redis客户端CSRedisCore,自己挖坑自己填

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 在使用CSRedisCore客户端时,要深入理解① Stackexchange.Redis 使用的多路复用连接机制(使用时很容易想到注册为单例),CSRedisCore开源库采用连接池机制,在高并发场景下强烈建议注册为单例, 否则在生产使用中可能会误用在瞬态请求中实例化,导致redis连接数几天之后消耗完。② CSRedisCore会默认建立连接池,预热50个连接,开发者要心中有数。额外的方法论: 尽量不要从某度找答案,要学会问问题,并尝试从官方、stackoverflow、github社区寻求解答,你挖过的坑也许别人早就挖过并踏平过。

背景


上次Redis MQ分布式改造之后,编排的容器稳定运行一个多月,昨天突然收到ETL端同事通知,没有采集到解析日志。


759943d63f9463a7f27581110f5eb7b7.png


赶紧进服务器 docker ps查看容器:


用于数据接收的ReceiverApp容器挂掉了;


尝试docker container start [containerid]几分钟后该容器再次崩溃。


Redis连接超限

docker log [containerid] 查看容器日志: 显示连接Redis服务的客户端数量超限。


CSRedis.RedisException: ERR max number of clients reached.


    Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]      Executed action EqidManager.Controllers.EqidController.BatchPutEqidAndProfileIds (EqidReceiver) in 7.1767msfail: Microsoft.AspNetCore.Server.Kestrel[13]      Connection id "0HLPR3AP8ODKH", Request id "0HLPR3AP8ODKH:00000001": An unhandled exception was thrown by the application.CSRedis.RedisException: ERR max number of clients reached   at CSRedis.CSRedisClient.GetAndExecute[T](RedisClientPool pool, Func`2 handler, Int32 jump, Int32 errtimes)   at CSRedis.CSRedisClient.ExecuteScalar[T](String key, Func`3 hander)   at CSRedis.CSRedisClient.LPush[T](String key, T[] value)   at RedisHelper.LPush[T](String key, T[] value)   at EqidManager.Controllers.EqidController.BatchPutEqidAndProfileIds(List`1 eqidPairs) in /home/gitlab-runner/builds/haD2h5xC/0/webdissector/datasource/eqid-manager/src/EqidReceiver/Controllers/EqidController.cs:line 31   at lambda_method(Closure , Object )   at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()   at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)   at System.Threading.Tasks.ValueTask`1.get_Result()   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]      Request finished in 8.9549ms 500 【dockerhost:6379/0】仍然不可用,下一次恢复检查时间:09/17/2019 03:11:25,错误:(ERR max number of clients reached)【dockerhost:6379/0】仍然不可用,下一次恢复检查时间:09/17/2019 03:11:25,错误:(ERR max number of clients reached)【dockerhost:6379/0】仍然不可用,下一次恢复检查时间:09/17/2019 03:11:25,错误:(ERR max number of clients reached)【dockerhost:6379/0】仍然不可用,下一次恢复检查时间:09/17/2019 03:11:25,错误:(ERR max number of clients reached)【dockerhost:6379/0】仍然不可用,下一次恢复检查时间:09/17/2019 03:11:25,错误:(ERR max number of clients reached)
    【dockerhost:6379/0】仍然不可用,下一次恢复检查时间:09/17/2019 03:11:25,错误:(ERR max number of clients reached)【dockerhost:6379/0】仍然不可用,下一次恢复检查时间:09/17/2019 03:11:25,错误:(ERR max number of clients reached)


    快速思考:目前编排的某容器使用CSRedisCore对16个Redis DB实例化了16个客户端,但Redis服务也不至于这么不经折腾吧。


    赶紧进redis.io官网搜集资料。


    After the client is initialized, Redis checks if we are already at the limit of the number of clients that it is possible to handle simultaneously (this is configured using the maxclients configuration directive, see the next section of this document for further information).

    In case it can't accept the current client because the maximum number of clients was already accepted, Redis tries to send an error to the client in order to make it aware of this condition, and closes the connection immediately. The error message will be able to reach the client even if the connection is closed immediately by Redis because the new socket output buffer is usually big enough to contain the error, so the kernel will handle the transmission of the error.


    大致意思是:maxclients配置了Redis服务允许的客户端最大连接数, 如果当前连接的客户端数超限,Redis服务会回发一个错误消息给客户端,并迅速关闭客户端连接


    立刻进入Redis宿主机查看默认配置,确认当前Redis服务的maxclients=10000(这是一个动态值,由maxclients和最大进程文件句柄决定)。


    # Set the max number of connected clients at the same time. By default

    # this limit is set to 10000 clients, however if the Redis server is not

    # able to configure the process file limit to allow for the specified limit

    # the max number of allowed clients is set to the current file limit

    # minus 32 (as Redis reserves a few file descriptors for internal uses).

    #

    # Once the limit is reached Redis will close all the new connections sending

    # an error 'max number of clients reached'.

    # maxclients 10000


    通过Redis-Cli登录Redis服务器, 立刻被踢下线。


    744117974d74ee32de73f4c882963223.png


    基本可认定Redis客户端使用方式有问题。


    CSRedisCore使用方式


    查看Redis官方资料,可利用redis-cli命令info clients、client list分析客户端连接。

    info clients 命令显示现场确实有10000的连接数;


    636fe4e5948b79ce8fbb5e00126035fd.png


    client list命令输出字段的官方解释:


    • addr: The client address, that is, the client IP and the remote port number it used to connect with the Redis server.


    • fd: The client socket file descriptor number.


    • name: The client name as set by CLIENT SETNAME.


    • age: The number of seconds the connection existed for.


    • idle: The number of seconds the connection is idle

    • flags: The kind of client (N means normal client, check the full list of flags).


    • omem: The amount of memory used by the client for the output buffer


    • cmd: The last executed command


    以上解释表明Redis服务器收到很多ip=172.16.1.3(故障容器在网桥内的Ip 地址)的客户端连接,这些连接最后发出的是ping命令(这是一个测试命令)


    故障容器使用的Redis客户端是CSRedisCore,该客户端只是单纯将msg写入Redis list数据结构,CSRedisCore上相关github issue给了一些启发。


    发现自己将CSRedisClient实例化代码写在 .NETCore api Controller构造函数,这样每次请求构造Controller时都实例化一次Redis客户端,最终Redis客户端连接数达到最大允许连接值。

    依赖注入三种模式: 单例(系统内单一实例,一次性注入);瞬态(每次请求产生实例并注入);自定义范围。

    有关dotnet apiController瞬态模式注入,请查阅文末链接


    还有一个疑问?


    为什么Redis服务器没有释放空闲的客户端连接,如果空闲连接被释放了,即使我写了low代码也不至于如此?

    查询官方:

    By default recent versions of Redis don't close the connection with the client if the client is idle for many seconds: the connection will remain open forever.

    However if you don't like this behavior, you can configure a timeout, so that if the client is idle for more than the specified number of seconds, the client connection will be closed.

    You can configure this limit via redis.conf or simply using CONFIG SET timeout .


    大致意思是最新的Redis服务默认不会释放空闲的客户端连接。


    # Close the connection after a client is idle for N seconds (0 to disable)timeout 0


    修改以上Redis服务配置可释放空闲客户端连接。


    我们最佳实践当然不是修改Redis idle timeout 配置,问题本质还是因为我实例化了多客户端,赶紧将CSRedisCore实例化代码移到startup.cs并注册为单例。


    大胆求证

    info clients命令显示稳定在53个Redis连接。


    5377985daf98d3b3f3859630692059dc.png


    client list命令显示:172.16.1.3(故障容器)建立了50个客户端连接,编排的另一个容器webapp建立了2个连接,redis-cli命令登录到服务器建立了1个连接。


    那么问题来了,修改之后,ReceiverApp容器为什么还稳定建立了50个redis连接?


    进一步与CSRedisCore原作者沟通,确认CSRedisCore有预热机制,默认在连接池中预热了 50 个连接。


    bingo,故障和困惑全部排查清楚。


    总结


    经此一役,在使用CSRedisCore客户端时,要深入理解


    ①  Stackexchange.Redis 使用的多路复用连接机制(使用时很容易想到注册为单例),CSRedisCore开源库采用连接池机制,在高并发场景下强烈建议注册为单例, 否则在生产使用中可能会误用在瞬态请求中实例化,导致redis连接数几天之后消耗完。


    ②   CSRedisCore会默认建立连接池,预热50个连接,开发者要心中有数。

    额外的方法论: 尽量不要从某度找答案,要学会问问题,并尝试从官方、stackoverflow、github社区寻求解答,你挖过的坑也许别人早就挖过并踏平过。


    相关实践学习
    基于Redis实现在线游戏积分排行榜
    本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
    云数据库 Redis 版使用教程
    云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
    相关文章
    |
    3月前
    |
    NoSQL Redis 数据安全/隐私保护
    Redis 最流行的图形化界面下载及使用超详细教程(带安装包)! redis windows客户端下载
    文章提供了Redis最流行的图形化界面工具Another Redis Desktop Manager的下载及使用教程,包括如何下载、解压、连接Redis服务器以及使用控制台和查看数据类型详细信息。
    254 6
    Redis 最流行的图形化界面下载及使用超详细教程(带安装包)! redis windows客户端下载
    |
    3月前
    |
    NoSQL Redis 数据库
    Redis 图形化界面下载及使用超详细教程(带安装包)! redis windows下客户端下载
    文章提供了Redis图形化界面工具的下载及使用教程,包括如何连接本地Redis服务器、操作键值对、查看日志和使用命令行等功能。
    231 0
    Redis 图形化界面下载及使用超详细教程(带安装包)! redis windows下客户端下载
    |
    2月前
    |
    开发框架 .NET C#
    在 ASP.NET Core 中创建 gRPC 客户端和服务器
    本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
    49 5
    在 ASP.NET Core 中创建 gRPC 客户端和服务器
    |
    3月前
    |
    NoSQL 网络协议 算法
    Redis 客户端连接
    10月更文挑战第21天
    50 1
    |
    4月前
    |
    JSON NoSQL Java
    redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
    这篇文章介绍了在Java中使用Redis客户端的几种方法,包括Jedis、SpringDataRedis和SpringBoot整合Redis的操作。文章详细解释了Jedis的基本使用步骤,Jedis连接池的创建和使用,以及在SpringBoot项目中如何配置和使用RedisTemplate和StringRedisTemplate。此外,还探讨了RedisTemplate序列化的两种实践方案,包括默认的JDK序列化和自定义的JSON序列化,以及StringRedisTemplate的使用,它要求键和值都必须是String类型。
    redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
    |
    3月前
    |
    网络协议 Unix Linux
    一个.NET开源、快速、低延迟的异步套接字服务器和客户端库
    一个.NET开源、快速、低延迟的异步套接字服务器和客户端库
    107 4
    |
    3月前
    |
    存储 消息中间件 NoSQL
    Redis 入门 - C#.NET Core客户端库六种选择
    Redis 入门 - C#.NET Core客户端库六种选择
    80 8
    |
    5月前
    |
    存储 NoSQL Redis
    【Azure Developer】一个复制Redis Key到另一个Redis服务的工具(redis_copy_net8)
    【Azure Developer】一个复制Redis Key到另一个Redis服务的工具(redis_copy_net8)
    |
    5月前
    |
    NoSQL 网络协议 Linux
    【AKS+Redis】AKS中客户端(ioredis)遇见Azure Redis服务Failover后链接中断的可能性
    【AKS+Redis】AKS中客户端(ioredis)遇见Azure Redis服务Failover后链接中断的可能性
    |
    5月前
    |
    NoSQL 网络协议 Linux
    【Azure Redis】Lettuce客户端遇见连接Azure Redis长达15分钟的超时
    【Azure Redis】Lettuce客户端遇见连接Azure Redis长达15分钟的超时