大家好,我是烤鸭:

今天分享一个问题,一个关于redis slowlog,执行过多 command命令的问题。

问题来源

所有走redis 的接口tp99和平均耗时是原来的两倍不止,运维说redis 的qps也翻倍了。查了下slowlog,发现command 命令占大多数,除了两条业务请求,其他都是command。

command命令返回的是当前redis所能执行的命令和对应的账号权限,也就不超过200条数据吧。(但频繁执行会增加qps,影响集群性能)

这个command命令是确实由业务服务发起的(ip符合),但是代码里也没有显示的调用(第一时间感觉是sdk有问题)。由于问题最开始出现在5天前,让运维查了一周前的记录,发现command命令也存在,只是没那么明显,比例大概1/20(调20次get命令,会出现一次command)。

源码查看,找蛛丝马迹

项目框架用的是 springboot的版本2.0.4.RELEASE,对应 spring-boot-starter-data-redis的版本2.0.4.RELEASE

对应 lettuce-core 版本 5.0.4.RELEASE。

分别从这几个包搜 command 命令,搜不到。

代码里用的比较多的有 redisTemplate.executePipelined 方法,跟这个代码进去看一下。

以get方法为例,由于默认调用的是 lettuce,RedisStringCommands.get ——> LettuceStringCommands.getConnection(getAsyncConnection) —>

LettuceConnection.getConnection()—>

而 LettuceConnection中的变量 asyncSharedConn 会是null,所以每次都得尝试重新连接

LettuceConnection.getDedicatedConnection()—> ClusterConnectionProvider.getConnection (配置连接池的话走的是 LettucePoolingConnectionProvider.getConnection,所以每次不需要再次创建连接 )

而LettucePoolingConnectionProvider.getConnection会先判断连接池是否存在,是否不存在,会先去创建。

ClusterConnectionProvider.getConnection 和 LettucePoolingConnectionProvider.getConnection 创建连接池时调用的都是 LettuceConnectionProvider.getConnection

LettuceConnectionProvider.getConnection(每次创建连接都会执行 command命令)

RedisClusterClient.connect—> RedisClusterClient.connectClusterImpl

这个方法里的调用了 connection.inspectRedisState();

command() 方法进入到了 ClusterFutureSyncInvocationHandler.handleInvocation

这里能看到每一次调用的命令,打开debug日志也能看到。

原因清楚了,就是每次执行命令都会检查 LettuceConnection asyncDedicatedConn是否为空, 如果为空,就会再次连接,再次连接就会执行command命令。为啥呢?连接没有池化。看下配置类。 ​

package com.my.maggie.demo.config;
​
​
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
​
import java.util.HashSet;
import java.util.List;
import java.util.Set;
​
​
@Configuration
public class RedisConfigration {
​@Bean@ConfigurationProperties(prefix="spring.redis-one")@Primarypublic RedisProperties redisPropertiesCache() {return new RedisProperties();}
​
​@Bean@Primarypublic LettuceConnectionFactory redisConnectionFactoryOne() {List<String> clusterNodes = redisPropertiesCache().getCluster().getNodes();Set<RedisNode> nodes = new HashSet<RedisNode>();clusterNodes.forEach(address -> nodes.add(new RedisNode(address.split(":")[0].trim(), Integer.parseInt(address.split(":")[1]))));RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();clusterConfiguration.setClusterNodes(nodes);if (!StringUtils.isEmpty(RedisPassword.of(redisPropertiesCache().getPassword()))) {clusterConfiguration.setPassword(RedisPassword.of(redisPropertiesCache().getPassword()));}return new LettuceConnectionFactory(clusterConfiguration);}
​@Bean(name="redisTemplateOne")public StringRedisTemplate redisTemplateCache(@Qualifier("redisConnectionFactoryOne") RedisConnectionFactory redisConnectionFactory){return new StringRedisTemplate(redisConnectionFactory);}
​
}

解决方案

  1. 配置类增加池化配置

    @Bean@Primarypublic LettuceConnectionFactory redisConnectionFactoryOne() {// 连接池配置GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();genericObjectPoolConfig.setMaxIdle(100);genericObjectPoolConfig.setMinIdle(10);genericObjectPoolConfig.setMaxTotal(5);genericObjectPoolConfig.setMaxWaitMillis(-1);genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(100);LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(1000)).shutdownTimeout(Duration.ofMillis(1000)).poolConfig(genericObjectPoolConfig).build();// redis 配置List<String> clusterNodes = redisPropertiesCache().getCluster().getNodes();Set<RedisNode> nodes = new HashSet<RedisNode>();clusterNodes.forEach(address -> nodes.add(new RedisNode(address.split(":")[0].trim(), Integer.parseInt(address.split(":")[1]))));RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();clusterConfiguration.setClusterNodes(nodes);if (!StringUtils.isEmpty(RedisPassword.of(redisPropertiesCache().getPassword()))) {clusterConfiguration.setPassword(RedisPassword.of(redisPropertiesCache().getPassword()));}return new LettuceConnectionFactory(clusterConfiguration,clientConfig);}
  2. 使用springboot自带的 StringRedisTemplate (2.0 以上版本默认 lettuce ,会自动创建连接池)

  3. 升级springboot 版本(2.1.0.RELEASE 及以上)

总结

个人觉得这应该是算是 spring-data-redis 的一个bug吧。 升级版本确实可以解决这个问题。看下新版本怎么解决的。

2.1.0.RELEASE 以前

LettuceConnectionFactory,构造参数第一个是 asyncSharedConn,直接传的是 null

@Overridepublic RedisClusterConnection getClusterConnection() {
​if (!isClusterAware()) {throw new InvalidDataAccessApiUsageException("Cluster is not configured!");}
​return new LettuceClusterConnection(connectionProvider, clusterCommandExecutor,clientConfiguration.getCommandTimeout());}

LettuceClusterConnection

public LettuceClusterConnection(LettuceConnectionProvider connectionProvider, ClusterCommandExecutor executor,Duration timeout) {
​super(null, connectionProvider, timeout.toMillis(), 0);
​Assert.notNull(executor, "ClusterCommandExecutor must not be null.");
​this.topologyProvider = new LettuceClusterTopologyProvider(getClient());this.clusterCommandExecutor = executor;this.disposeClusterCommandExecutorOnClose = false;}

2.1.0.RELEASE 以后

LettuceConnectionFactory,asyncSharedConn 这个值是传入的,getOrCreateSharedConnection(),没有就创建,所以不会有null的情况

public RedisClusterConnection getClusterConnection() {
​if (!isClusterAware()) {throw new InvalidDataAccessApiUsageException("Cluster is not configured!");}
​RedisClusterClient clusterClient = (RedisClusterClient) client;
​return getShareNativeConnection()? new LettuceClusterConnection((StatefulRedisClusterConnection<byte[], byte[]>) getOrCreateSharedConnection().getConnection(),connectionProvider, clusterClient, clusterCommandExecutor, clientConfiguration.getCommandTimeout()): new LettuceClusterConnection(null, connectionProvider, clusterClient, clusterCommandExecutor,clientConfiguration.getCommandTimeout());}

redis出现过多command 慢查询slowlog出现command命令相关推荐

  1. redis高级特性学习(慢查询、Pipeline、事务、Lua)(上)

    Redis高级特性和应用(慢查询.Pipeline.事务.Lua) Redis的慢查询 许多存储系统(例如 MySQL)提供慢查询日志帮助开发和运维人员定位系统存在的慢操作.所谓慢查询日志就是系统在命 ...

  2. redis的游标和模糊查询key的不适用

    项目场景: 提示:这里简述项目相关背景: 例如:项目场景:查平台的支付方式 问题描述 用来redis的scan模糊匹配的方式,查询key,在redis的key大于百万级的key的时候,会导致查询非常缓 ...

  3. Node.js bull Redis连接数过多

    Node.js bull 连接数过多问题 使用 bull https://www.npmjs.com/package/bull 包做 node.js 的消息队列. 遇到一个很大的坑, 每过一段时间, ...

  4. 如何通过多个id查询多条数据

    如何通过多个id查询多条数据 如何通过多个id查询多条数据 List<Integer> list = Arrays.asList(1,2,3); List<ProductCatego ...

  5. Redis入门【安装,常用类型,常用命令行命令】

    目录 1.安装 1.1 windows版 1.2 Linux版 2.Redis命令行客户端 3.Redis常见命令 3.1 通用命令 3.2 String类型 3.3 Hash类型 3.4 List类 ...

  6. macos -bash: yarn: command not found/-bash: cnpm: command not found

    博客主要更新地址:?https://www.cnblogs.com/niceyoo -bash: cnpm: command not found -bash: yarn: command not fo ...

  7. Python os.system(command),这样执行的command命令,和主程序是异步的吗?

    Python os.system(command),这样执行的command命令,和主程序是异步的吗? 是同步执行的. 尚未执行完成的情况下,下面的程序不会继续操作.看下面的例子即可: >> ...

  8. redis 中setex、setnx、set、getset 命令的区别与使用

    转载:redis 中setex.setnx.set.getset 命令的区别与使用 介绍几个常用的redis命令: SET 命令 set key value 设置指定 key 的值为 value. 如 ...

  9. linux vi命令的查询,关于Linux vi命令 vi命令一览表

    vi的基本概念 基本上vi可分为三种操作状态,分别是命令模式(Command mode).插入模式(Insert mode)和底线命令模式(Last line mode),各模式的功能区分如下: 1. ...

最新文章

  1. 不想 CRUD 干到老,就来看看这篇 OOM 排查的实战案例!
  2. NAPI技术--在Linux 网络驱动上的应用和完善(二)
  3. java 基本类型 引用类型_Java中的基本类型和引用类型变量的区别
  4. brackets作为html编辑器,为前端而生丨编辑器 Brackets 及配置推荐
  5. IS-IS和OSPFv2对比
  6. c++整理程序 dev_【C++】用Dev-C++写的C++程序老是报错,请问为什么?如何解决?...
  7. MapGuide open source开发心得一:简介
  8. uygurqa输入法android,uygurqa维语输入法2021
  9. 完美数简介及算法分析
  10. C# MES系统结构梳理
  11. 米勒-拉宾素数检测法(判断一个极大的数是否为质数)——算法解析
  12. 【论文笔记】Deep Survival: A Deep Cox Proportional Hazards Network
  13. 简单实现antd的表单设计
  14. Stitcher: Feedback-driven Data Provider for Object Detection 论文学习
  15. 搭建区块链浏览器——基于hyperledger fabric 1.0,MySQL容器
  16. 项目管理必备,教你如何制作甘特图
  17. [Mysql]查看mysql默认密码
  18. 三种css垂直居中方案
  19. 阿里最强 Python 自动化工具开源了!
  20. 如何快速高效学习技术

热门文章

  1. 前端学习(3146):react-hello-react之getBeforeUpdate
  2. 前端学习(3113):react-hello-类式组件
  3. 前端学习(2988):vue+element今日头条管理--使用技术栈
  4. [html] HTML5的Server-Sent和WebSocket如何选择哪一个?
  5. [css] 说说visibility属性的collapse属性值有什么作用?在不同浏览器下有什么区别?
  6. [vue] 你有使用过vue开发多语言项目吗?说说你的做法?
  7. 前端学习(2657):vue3实现计算器
  8. 工作76::一直报400
  9. 前端学习(2479):接口文档使用
  10. 前端学习(2241):react打卡倒计时十五天之react出现背景