Redis 读写优化
2020-01-08 本文已影响0人
香沙小熊
1.慢查询
redis生命周期两点说明:
(1) 慢查询发生在第3阶段
(2) 客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素。
1.1简介
慢查询顾名思义是将redis执行命令较慢的命令记录下来,redis处理慢查询时是将慢查询记录到慢查询队列中
慢查询有两个参数需要配置:
- slowlog-log-slower-than
表示慢查询预设的超时阀值,单位是微妙(μs)
1s = 1000ms = 1_000_000μs
默认10000微秒,即10毫秒
执行超过这个时间的命令将被记录到慢查询日志
slowlog-log-slower-than = 0:表示记录所有命令。
slowlog-log-slower-than < 0:表示不记录
- slowlog-max-len
表示慢查询日志的条数
默认为 128
Redis使用列表存储慢查询日志
当已经记录了128条慢查询,现在又来一条,最早记录的那条将被踢出,最新一条入列
- config get slowlog*
查看当前配置
123.57.251.156:6379> config get slowlog*
slowlog-log-slower-than
0
slowlog-max-len
10
修改配置
直接修改配置文件
config set 命令动态修改
# 设置记录超过1ms命令
config set slowlog-log-slower-than 1000
# 最多记录100条
config set slowlog-max-len 100
# 持久化到本地配置文件
config rewrite
慢查询日志操作
查询
# 获取慢查询日志,n表示获取的条数,不写则为全部
slowlog get [n]
运维经验
- slowlog-max-len不要设置过大,默认10ms,通常设置1ms
- slowlog-log-slower-than不要设置过小,通常设置1000左右
- 理解命令生命周期
- 定期持久化慢查询
2.pipeline
2.1什么是流水线
Pipeline指的是管道技术,指的是客户端允许将多个请求依次发给服务器,过程中而不需要等待请求的回复,在最后再一并读取结果即可。
Redis 是 CS 模式,Redis客户端与Redis之间使用TCP协议进行连接,一个客户端可以通过一个socket连接发起多个请求命令,每个请求命令发出后client通常会阻塞并等待Redis服务处理,Redis处理完后请求命令后会将结果通过响应报文返回给client,因此当执行多条命令的时候都需要等待上一条命令执行完毕才能执行。如果一次性批量数据单次操作,会有网络延迟。而redis也是单线程的。
而Pipelining可以满足批量的操作,把多个命令连续的发送给Redis Server,然后一一解析响应结果。Pipelining可以提高批量处理性能,提升的原因主要是TCP连接中减少了“交互往返”的时间。
2.2与原生操作对比
普通请求模型Pipelining
命令 | 时间 | 每次网络请求命令数据量 | 特性 |
---|---|---|---|
n个命令 | n次网络 + n次命令 | 1条命令 | 原子性 |
1次pipeline(n个命令) | 1次网络 + n次执行 | n条命令 | 非原子性 |
注意:
1.Redis的命令时间是微妙级别
2.pipeline每次条数要控制(网络)。
2.3实现
public class PiplineTest {
private static int count = 10000;
public static void main(String[] args){
useNormal();
usePipeline();
}
public static void usePipeline(){
ShardedJedis jedis = getShardedJedis();
ShardedJedisPipeline pipeline = jedis.pipelined();
long begin = System.currentTimeMillis();
for(int i = 0;i<count;i++){
pipeline.set("key_"+i,"value_"+i);
}
pipeline.sync();
jedis.close();
System.out.println("usePipeline total time:" + (System.currentTimeMillis() - begin));
}
public static void useNormal(){
ShardedJedis jedis = getShardedJedis();
long begin = System.currentTimeMillis();
for(int i = 0;i<count;i++){
jedis.set("key_"+i,"value_"+i);
}
jedis.close();
System.out.println("useNormal total time:" + (System.currentTimeMillis() - begin));
}
public static ShardedJedis getShardedJedis(){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(2);
poolConfig.setMaxIdle(2);
poolConfig.setMaxWaitMillis(2000);
poolConfig.setTestOnBorrow(false);
poolConfig.setTestOnReturn(false);
JedisShardInfo info1 = new JedisShardInfo("127.0.0.1",6379);
JedisShardInfo info2 = new JedisShardInfo("127.0.0.1",6379);
ShardedJedisPool pool = new ShardedJedisPool(poolConfig, Arrays.asList(info1,info2));
return pool.getResource();
}
}
useNormal total time:411
usePipeline total time:42
2.4使用建议
- pipeline只适用于那些不需要获取同步结果的场景,比如hincr,hset等更新操作。而对于读取hget操作则不能适用。
- pipeline组装命令也不能是没有节制的,如果pipeline组装命令数据过多,则会导致一次pipeline同步等待时间过长,影响客户端体验甚至导致网络阻塞。
- pipeline不能保证命令执行的原子性。如多个命令在执行的中间发生了异常,那么将会丢失未执行的命令。所以我们一般使用pipeline时,需要自己保证执行命令的数据安全性。
使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存。所以,如果你需要发送大量的命令,
最好是把他们按照合理数量分批次的处理,例如10K的命令,读回复,然后再发送另一个10k的命令,等等。这样速度几
乎是相同的,但是在回复这10k命令队列需要非常大量的内存用来组织返回数据内容。