redis出现过多command 慢查询slowlog出现command命令
大家好,我是烤鴨:
今天分享一個問題,一個關于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);} ? }解決方案
配置類增加池化配置
?
?
使用springboot自帶的 StringRedisTemplate (2.0 以上版本默認 lettuce ,會自動創建連接池)
升級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命令的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 错题集03
- 下一篇: 总结openstack nuetron网