Redis管道技术Pipeline使用
Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:
- 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应(所以如果超过了redis的处理能力,会导致其他查询超时???)。服务端处理命令,并将结果返回给客户端。
- Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。
- pipeline就是把一组命令进行打包,然后一次性通过网络发送到Redis。同时将执行的结果批量的返回回来. 使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存。所以,如果你需要发送大量的命令,最好是把他们按照合理数量分批次的处理,例如10K的命令,读回复,然后再发送另一个10k的命令,等等。
Pipeline(管道) 的优点:
pipeline
通过打包命令,一次性执行,可以节省连接->发送命令->返回结果
所产生的往返时间,- 减少的I/O的调用次数。
Pipeline(管道) 的缺点:
pipeline
每批打包的命令不能过多,因为pipeline
方式打包命令再发送,Redis服务器是以队列来存储准备执行的命令,而队列是存放在有限的内存中的. 同时redis
必须在处理完所有命令前先缓存起所有命令的处理结果。都有一个内存的消耗,如果需要大量的命令,可分批进行(官方的推荐是命令10k
每批),效率不会相差太远滴,太大会影响网络性能;。 ( 如果批量读key的返回的数据量太大会有异常 org.springframework.data.redis.RedisConnectionFailureException: java.net.SocketException: Broken pipe; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketException: Broken pipe )- Pipeline 实现的原理是队列,而队列的原理是时先进先出,这样就保证数据的顺序性, pipeline的原理是收集需执行的命令,到最后才一次性执行(
pipeline里面的命令不能保证命令的顺序执行,因此命令之间不要有数据依赖, 有说能保证顺序的),所以无法在中途立即查得数据的结果(需待pipelining完毕后才能查得结果),这样会使得无法立即查得数据进行条件判断(比如判断是非继续插入记录)。 pipeline
是责任链模式,这个模式的缺点是,每次它对于一个输入都必须从链头开始遍历(参考Http Server处理请求就能明白),这确实存在一定的性能损耗。pipeline
不保证原子性,如果要求原子性的,不推荐使用pipeline
Pipeline
批量执行的时候Redis 采用多路I/O复用模型,非阻塞IO,所以Pipeline
批量写入的时候,Redis不会进行锁定导致其他应用无法再进行读写, 一定范围内不影响其他的读操作。
下面的话感觉有误导, 命令是先到先执行 区分开也没用吧
为什么Pipelining这么快?
先看看原来的多条命令,是如何执行的:
1 2 3 4 5 6 7 |
|
Pipeling机制是怎样的呢:
1 2 3 4 5 6 |
|
实际应用场景: 查询用户下礼券列表
关于Pipeline 同步数据的问题
A、Pipeline 有与redis形同的操作,但是在数据落盘的时候需要在执行的方法后添加sync()方法,如果insert时有多条数据,在数据拼接完之后,在执行sync()方法,这样可以提高效率。
B、如果在hget()时没有sync()时会报Please close pipeline or multi block before calling this method,没有在hget()同步数据
C、如果在hset(),hdel(),hget()获取数据时都没有执行sync()方法,但是在最后执行了pl.close()方法,Pipeline 同样会执行sync()方法
1.pipelined.sync()表示我一次性的异步发送到redis,不关注执行结果。
2.pipelined.syncAndReturnAll()程序会阻塞,等到所有命令执行完之后返回一个List集合。
3.pipeline也不适合组装特别多的命令,因此如果是成千上万的这种命令,我们还是要进行命令的拆分。
//最原始的写法,需要注意连接泄漏
public void testPipeline(){
Jedis jedis = new Jedis("172.23.88.107", 6379);
try {
Pipeline pipelined = jedis.pipelined();
pipelined.set("addr", "chongqing");
pipelined.del("addr");
//pipelined.sync();//没有返回值
List<Object> list= pipelined.syncAndReturnAll();
for (Object object : list) {
System.out.println(object);
}
} finally {
redis.dispose(jedis);
}
}
package com.opec.shop.core.cache.jedis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @date 2019/10/29 15:28
*/
public class Test11 {
public static void main(String[] args) {
//最原始的写法,需要注意连接泄漏
Jedis jedis = new Jedis("localhost",6379);
jedis.auth("root");
noPipelineHset(jedis);
pipelineHget(jedis);
jedis.close();
}
//jedis.hset数据
public static void noPipelineHset(Jedis jedis){
jedis.select(4);
jedis.flushDB();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
jedis.hset("k_" + i,"k_" + i,"v_" + i);
}
long end = System.currentTimeMillis();
System.out.println("datasize=" + jedis.dbSize());
System.out.println("hset without pipeline used=" + (end - start) / 1000 + "seconds!");
}
//pipeline.hset命令
public static void pipelineHset(Jedis jedis){
//用pipeline存储数据
jedis.select(4);
jedis.flushDB();
Pipeline pipeline = jedis.pipelined();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
pipeline.hset("k_" + i,"k_" + i,"v_" + i); //通过pipeline set数据
}
pipeline.sync(); //Pipeline 有与redis形同的操作,但是在数据落盘的时候需要在执行的方法后添加sync()方法
long end = System.currentTimeMillis();
System.out.println("datasize=" + jedis.dbSize());
System.out.println("hset with pipeline used=" + (end - start) / 1000 + "seconds!");
}
//直接使用jedis.hgetAll 拿到map
public static void noPipelineHget(Jedis jedis){
jedis.select(4);
Set<String> keys = jedis.keys("*");//keys模糊查询拿到keys
long start = System.currentTimeMillis();
Map<String, Map<String, String>> resultMap = new HashMap<>();
for (String key : keys) {
resultMap.put(key, jedis.hgetAll(key));//将数据填充到resultMap中
}
long end = System.currentTimeMillis();
System.out.println("result size:[" + resultMap.size() + "] ..");
System.out.println("hgetAll without pipeline used [" + (end - start) / 1000 + "] seconds ..");
}
public static void pipelineHget(Jedis jedis){
jedis.select(4);
Map<String, Map<String, String>> result = new HashMap<>();
Set<String> keys = jedis.keys("*");
Pipeline pipeline = jedis.pipelined();
// 使用pipeline hgetall
Map<String, Response<Map<String, String>>> responseMap = new HashMap<>(keys.size());
long start = System.currentTimeMillis();
for (String key : keys) {
responseMap.put(key, pipeline.hgetAll(key));
}
pipeline.sync();
//此处需要将结果集 Response<Map<String, String>>> 转成 Map<String, String>
for (String k : responseMap.keySet()) {
result.put(k, responseMap.get(k).get());
}
long end = System.currentTimeMillis();
System.out.println("result size:[" + responseMap.size() + "] ..");
System.out.println("hgetAll with pipeline used [" + (end - start) / 1000 + "] seconds ..");
}
}
//只能返回Object??
这个list是放在匿名类内部,对于数据处理不太友好,代码会看起来相当难受,想取出来使用还是不可变的。如果要获取返回值,我们可以调用如下代码executePipelined,这样就可以返回我们需要的结果,下面我们可以对得到list进行操作。
Object execute = redisTemplate.execute(new RedisCallback<Long>() {
@Nullable
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
connection.openPipeline();
for (int i = 0; i < 1000000; i++) {
String key = "123" + i;
connection.zCount(key.getBytes(), 0,Integer.MAX_VALUE);
}
List<Object> result=connection.closePipeline();
return null;
}
});