执行大量的Redis命令,担心效率问题?用Pipelining试试吧~
参考的优秀文章
来源
原来,系统中一个树结构的数据来源是Redis,由于数据增多、业务复杂,查询速度并不快。究其原因,是单次查询的数量太多了,一个树结构,大概要几万次Redis的交互。于是,尝试用Redis的Pipelining特性。
测试Pipelining使用与否的差别
不使用pipelining
首先,不使用pipelining,插入10w条记录,再删除10w条记录,看看需要多久。
首先来个小程序,用于计算程序消耗的时间:
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class TimeLag {
private Date start;
private Date end;
public TimeLag() {
start = new Date();
}
public String cost() {
end = new Date();
long c = end.getTime() - start.getTime();
String s = new StringBuffer().append("cost ").append(c).append(" milliseconds (").append(c / 1000).append(" seconds).").toString();
return s;
}
public static void main(String[] args) throws InterruptedException {
TimeLag t = new TimeLag();
TimeUnit.SECONDS.sleep(2);
System.out.println(t.cost());
}
}
package com.nicchagil.study.jedis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class HowToTest {
public static void main(String[] args) {
// 连接池
JedisPool jedisPool = new JedisPool("192.168.1.9", 6379);
/* 操作Redis */
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
TimeLag t = new TimeLag();
System.out.println("操作前,全部Key值:" + jedis.keys("*"));
/* 插入多条数据 */
for(Integer i = 0; i < 100000; i++) {
jedis.set(i.toString(), i.toString());
}
/* 删除多条数据 */
for(Integer i = 0; i < 100000; i++) {
jedis.del(i.toString());
}
System.out.println("操作前,全部Key值:" + jedis.keys("*"));
System.out.println(t.cost());
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
日志,Key值“user_001”是我的Redis存量的值,忽略即可:
操作前,全部Key值:[user_001]
操作前,全部Key值:[user_001]
cost 35997 milliseconds (35 seconds).
使用pipelining
package com.nicchagil.study.jedis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
public class HowToTest {
public static void main(String[] args) {
// 连接池
JedisPool jedisPool = new JedisPool("192.168.1.9", 6379);
/* 操作Redis */
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
TimeLag t = new TimeLag();
System.out.println("操作前,全部Key值:" + jedis.keys("*"));
Pipeline p = jedis.pipelined();
/* 插入多条数据 */
for(Integer i = 0; i < 100000; i++) {
p.set(i.toString(), i.toString());
}
/* 删除多条数据 */
for(Integer i = 0; i < 100000; i++) {
p.del(i.toString());
}
p.sync();
System.out.println("操作前,全部Key值:" + jedis.keys("*"));
System.out.println(t.cost());
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
日志:
操作前,全部Key值:[user_001]
操作前,全部Key值:[user_001]
cost 629 milliseconds (0 seconds).
为什么Pipelining这么快?
先看看原来的多条命令,是如何执行的:
sequenceDiagram
Redis Client->>Redis Server: 发送第1个命令
Redis Server->>Redis Client: 响应第1个命令
Redis Client->>Redis Server: 发送第2个命令
Redis Server->>Redis Client: 响应第2个命令
Redis Client->>Redis Server: 发送第n个命令
Redis Server->>Redis Client: 响应第n个命令
Pipeling机制是怎样的呢:
sequenceDiagram
Redis Client->>Redis Server: 发送第1个命令(缓存在Redis Client,未即时发送)
Redis Client->>Redis Server: 发送第2个命令(缓存在Redis Client,未即时发送)
Redis Client->>Redis Server: 发送第n个命令(缓存在Redis Client,未即时发送)
Redis Client->>Redis Server: 发送累积的命令
Redis Server->>Redis Client: 响应第1、2、n个命令
Pipelining的局限性(重要!)
基于其特性,它有两个明显的局限性:
- 鉴于Pipepining发送命令的特性,Redis服务器是以队列来存储准备执行的命令,而队列是存放在有限的内存中的,所以不宜一次性发送过多的命令。如果需要大量的命令,可分批进行,效率不会相差太远滴,总好过内存溢出嘛~~
- 由于pipeline的原理是收集需执行的命令,到最后才一次性执行。所以无法在中途立即查得数据的结果(需待pipelining完毕后才能查得结果),这样会使得无法立即查得数据进行条件判断(比如判断是非继续插入记录)。
比如,以下代码中,response.get()在p.sync();完毕前无法执行,否则,会报异常redis.clients.jedis.exceptions.JedisDataException: Please close pipeline or multi block before calling this method.。
package com.nicchagil.study.jedis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
public class HowToTest {
public static void main(String[] args) {
// 连接池
JedisPool jedisPool = new JedisPool("192.168.1.9", 6379);
/* 操作Redis */
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
TimeLag t = new TimeLag();
System.out.println("操作前,全部Key值:" + jedis.keys("*"));
Pipeline p = jedis.pipelined();
/* 插入多条数据 */
for(Integer i = 0; i < 100000; i++) {
p.set(i.toString(), i.toString());
}
Response<String> response = p.get("999");
// System.out.println(response.get()); // 执行报异常:redis.clients.jedis.exceptions.JedisDataException: Please close pipeline or multi block before calling this method.
/* 删除多条数据 */
for(Integer i = 0; i < 100000; i++) {
p.del(i.toString());
}
p.sync();
System.out.println(response.get());
System.out.println("操作前,全部Key值:" + jedis.keys("*"));
System.out.println(t.cost());
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
如何使用Pipelining查询大量数据
用Map<String, Response<String>>先将Response缓存起来再使用就OK了:
package com.nicchagil.study.jedis;
import java.util.HashMap;
import java.util.Map;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
public class GetMultiRecordWithPipelining {
public static void main(String[] args) {
// 连接池
JedisPool jedisPool = new JedisPool("192.168.1.9", 6379);
/* 操作Redis */
Jedis jedis = null;
Map<String, Response<String>> map = new HashMap<String, Response<String>>();
try {
jedis = jedisPool.getResource();
TimeLag t = new TimeLag(); // 开始计算时间
Pipeline p = jedis.pipelined();
/* 插入多条数据 */
for(Integer i = 0; i < 100000; i++) {
if (i % 2 == 1) {
map.put(i.toString(), p.get(i.toString()));
}
}
p.sync();
/* 由Response对象获取对应的值 */
Map<String, String> resultMap = new HashMap<String, String>();
String result = null;
for (String key : map.keySet()) {
result = map.get(key).get();
if (result != null && result.length() > 0) {
resultMap.put(key, result);
}
}
System.out.println("get record num : " + resultMap.size());
System.out.println(t.cost()); // 计时结束
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
执行大量的Redis命令,担心效率问题?用Pipelining试试吧~的更多相关文章
- redis如何执行redis命令
Redis 命令 Redis 命令用于在 redis 服务上执行操作.所以我们必须要启动Redis服务程序,也就是redis安装目录下的redis-server.exe,你可以双击执行,也可以打开cm ...
- 使用控制台对Redis执行增删改查命令
使用控制台对Redis执行增删改查命令 在上一篇里,我们已经安装了redis.这一篇我们将一起来学习如何使用"控制台"管理Redis 首先肯定是打开一个控制台,在windows系统 ...
- Redis 命令执行过程(上)
今天我们来了解一下 Redis 命令执行的过程.在之前的文章中<当 Redis 发生高延迟时,到底发生了什么>我们曾简单的描述了一条命令的执行过程,本篇文章展示深入说明一下,加深读者对 R ...
- Redis 命令执行过程(下)
在上一篇文章中<Redis 命令执行过程(上)>中,我们首先了解 Redis 命令执行的整体流程,然后细致分析了从 Redis 启动到建立 socket 连接,再到读取 socket 数据 ...
- Redis 命令执行全过程分析
今天我们来了解一下 Redis 命令执行的过程.我们曾简单的描述了一条命令的执行过程,本篇文章展示深入说明一下,加深大家对 Redis 的了解. 如下图所示,一条命令执行完成并且返回数据一共涉及三部分 ...
- 深入Redis命令的执行过程
深入Redis命令的执行过程 Redis 服务器: Redis 服务器实现与多个客户端的连接,并处理这些客户端发送过来的请求,同时保存客户端执行命令所产生的数据到数据库中.Redis 服务器依靠资源管 ...
- 用Lua定制Redis命令
* { color: #3e3e3e } body { font-family: "Helvetica Neue", Helvetica, "Hiragino Sans ...
- Redis 命令学习
每天不学习点新的东西,感觉就有点会被社会淘汰掉了.也许现在学习的知识会很快忘记,下次学习用到这个知识点的时候,再回来翻记录的笔记,我想这样会比从头再学,效率会高点吧. 闲话不多聊,回归正题.今天学习r ...
- Redis命令大全(超详细)
一:序 其实本文的命令大家都可以去官网学习,但是我出这篇文章只是以更直观的方式来解读官网上的命令,让大家一眼可以看得懂,看的明白: 注意:我全文使用的Redis版本为 6.2.x 版本,低版本可能有些 ...
随机推荐
- 字符串格式化命令 sprintf
原型 int sprintf( char *buffer, const char *format, [ argument] … ); 参数列表 buffer:char型指针,指向将要写入的字符串的缓冲 ...
- BizTalk开发系列(七) Hello World2
之前根据BizTalk的订阅原理,使用BizTalk管理控制台创建了第一个应用程序 Hello World.但是由于控制台的开发功能有限,绝大多数的BizTalk程序都是在集成开发环境Visual S ...
- 使用花生壳6.5客户端FTP设置
1.打开FTP客户端—选项—参数选择 2.设置为主动模式(PORT) 3.连接FTP服务器 4.FTP连接成功
- 杭电ACM 1201
#include <stdio.h> int func(int year){ if ( year % 400 == 0 || (year % 4 == 0 &&year % ...
- 【iCore3 双核心板】例程二十三:LAN_HTTP实验——网页服务器
实验指导书及代码包下载: http://pan.baidu.com/s/1getgyKr iCore3 购买链接: https://item.taobao.com/item.htm?id=524229 ...
- RESTful使用
测试工具: JMeter 可以使用JMeter对HTTP REST接口进行测试 Java RESTful框架: Jersey
- markdown语法测试
斜体 粗体 百度 标题一 -------- 标题二 ======== 标题三 标题四 有序列表一 有序列表二 无序列表一 无序列表二 这是引用的文字 这是一句行内代码var=1 public clas ...
- Cell的一些坑: UITableViewCell宽度,在iphone5的时候是320,在iphone6的时候为啥也是320?
在自定制cell'的.m文件里重写setframe方法就可以了- (void)setFrame:(CGRect)frame{ frame.size.width = [UIScreen mainS ...
- 白话学习MVC(九)View的呈现一
一.概述 本节来看一下ASP.NET MVC[View的呈现]的内容,View的呈现是在Action执行之后进行,Action的执行生成一个ActionResult,[View的呈现]的功能就是:通过 ...
- Android (二维码)关于java.lang.UnsatisfiedLinkError的小案例
在许多项目中我们都会用到第三方动态库.so文件,但是往往会引来很多烦恼,比如:Java.lang.UnsatisfiedLinkError - ::-/com.ishow.scan E/Android ...