redis出现过多command 慢查询slowlog出现command命令
大家好,我是烤鴨:
今天分享一個(gè)問(wèn)題,一個(gè)關(guān)于redis slowlog,執(zhí)行過(guò)多 command命令的問(wèn)題。
?
問(wèn)題來(lái)源
所有走redis 的接口tp99和平均耗時(shí)是原來(lái)的兩倍不止,運(yùn)維說(shuō)redis 的qps也翻倍了。查了下slowlog,發(fā)現(xiàn)command 命令占大多數(shù),除了兩條業(yè)務(wù)請(qǐng)求,其他都是command。
?
command命令返回的是當(dāng)前redis所能執(zhí)行的命令和對(duì)應(yīng)的賬號(hào)權(quán)限,也就不超過(guò)200條數(shù)據(jù)吧。(但頻繁執(zhí)行會(huì)增加qps,影響集群性能)
這個(gè)command命令是確實(shí)由業(yè)務(wù)服務(wù)發(fā)起的(ip符合),但是代碼里也沒(méi)有顯示的調(diào)用(第一時(shí)間感覺(jué)是sdk有問(wèn)題)。由于問(wèn)題最開始出現(xiàn)在5天前,讓運(yùn)維查了一周前的記錄,發(fā)現(xiàn)command命令也存在,只是沒(méi)那么明顯,比例大概1/20(調(diào)20次get命令,會(huì)出現(xiàn)一次command)。
?
源碼查看,找蛛絲馬跡
項(xiàng)目框架用的是 springboot的版本2.0.4.RELEASE,對(duì)應(yīng) spring-boot-starter-data-redis的版本2.0.4.RELEASE
對(duì)應(yīng) lettuce-core 版本 5.0.4.RELEASE。
分別從這幾個(gè)包搜 command 命令,搜不到。
代碼里用的比較多的有 redisTemplate.executePipelined 方法,跟這個(gè)代碼進(jìn)去看一下。
以get方法為例,由于默認(rèn)調(diào)用的是 lettuce,RedisStringCommands.get ——> LettuceStringCommands.getConnection(getAsyncConnection) —>
LettuceConnection.getConnection()—>
而 LettuceConnection中的變量 asyncSharedConn 會(huì)是null,所以每次都得嘗試重新連接
?
LettuceConnection.getDedicatedConnection()—> ClusterConnectionProvider.getConnection (配置連接池的話走的是 LettucePoolingConnectionProvider.getConnection,所以每次不需要再次創(chuàng)建連接 )
?
而LettucePoolingConnectionProvider.getConnection會(huì)先判斷連接池是否存在,是否不存在,會(huì)先去創(chuàng)建。
ClusterConnectionProvider.getConnection 和 LettucePoolingConnectionProvider.getConnection 創(chuàng)建連接池時(shí)調(diào)用的都是 LettuceConnectionProvider.getConnection
?
LettuceConnectionProvider.getConnection(每次創(chuàng)建連接都會(huì)執(zhí)行 command命令)
?
RedisClusterClient.connect—> RedisClusterClient.connectClusterImpl
這個(gè)方法里的調(diào)用了 connection.inspectRedisState();
command() 方法進(jìn)入到了 ClusterFutureSyncInvocationHandler.handleInvocation
?
這里能看到每一次調(diào)用的命令,打開debug日志也能看到。
?
原因清楚了,就是每次執(zhí)行命令都會(huì)檢查 LettuceConnection asyncDedicatedConn是否為空, 如果為空,就會(huì)再次連接,再次連接就會(huì)執(zhí)行command命令。為啥呢?連接沒(méi)有池化。看下配置類。 ?
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 以上版本默認(rèn) lettuce ,會(huì)自動(dòng)創(chuàng)建連接池)
升級(jí)springboot 版本(2.1.0.RELEASE 及以上)
總結(jié)
個(gè)人覺(jué)得這應(yīng)該是算是 spring-data-redis 的一個(gè)bug吧。 升級(jí)版本確實(shí)可以解決這個(gè)問(wèn)題。看下新版本怎么解決的。
2.1.0.RELEASE 以前
LettuceConnectionFactory,構(gòu)造參數(shù)第一個(gè)是 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 這個(gè)值是傳入的,getOrCreateSharedConnection(),沒(méi)有就創(chuàng)建,所以不會(huì)有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());}?
?
總結(jié)
以上是生活随笔為你收集整理的redis出现过多command 慢查询slowlog出现command命令的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 错题集03
- 下一篇: 总结openstack nuetron网