redis性能测试
一 测试目的
开发需要为了寻求系统最优的解决方案,但是网上关于性能方面的资料并不是很多,没有很明显的数据说明,只是一般的结论性判断不好说什么,所以这里自己重新测试整理了一番.
二 测试环境
主机主机ip为...86,系统为64位win7,4g内存,双核2.93GHz,2.94GHz,从机ip为...59,系统为64位win7,6g内存,双核2.93GHz,2.94GHz.
双机在同一个局域网,开启了密码验证,主从配置通过配置文件直接配置后,从机配置为不可写入.
开发工具为Eclipse Indigo Service Release 1,java环境为32位java1.6 .Redis为Redis-x64-3.0.504版本,redis架包为jedis-2.9.0.jar.
*注意*
每次读写时候,由于电脑性能对比有差异性,所以可以先行通过3.2.4的快速测试,对比一下自己的电脑性能之后再进行测试,因为楼主昨天做测试的时候电脑有些卡,导致今天的数据重新测试时候都快了很多!很多!为了是测试数据明显,使用的键与值都是具有一定长度的,实际做开发时候一般都会使用简写代替.
三 开始测试
3.1 一般测试
3.1.1 测试方法:
直接手动操作,针对redis进行各种状况的模拟.主要涉及到的工具有redis,management tool for redis.cmd窗口.
3.1.2 测试过程:
主从均关闭,开启主redis导入少量数据到主redis,开启从redis,从redis有一样的数据.
主从均关闭,开启从redis,删除少量数据到从redis(management tool for redis),开启主redis,主redis数据不变化,刷新从,从redis恢复原来的数据.
主从均开启,操作部分数据到主redis,从redis有同样的数据,
主从均开启,删除部分数据(management tool for redis)到从redis,主redis原有数据不会变动,再次刷新从redis发现更改未生效,cmd操作从机发现无法进行写入,
主从均开启,主导入部分数据之后从有同样数据,关闭主之后再次开启,主的数据恢复.
主从均开启,主导入部分数据之后从有同样数据,关闭从之后再次开启,从的数据恢复.
主从均开启,主导入部分数据之后从有同样数据,关闭主之后,删除本地文件dump.rdb,然后主再次开启,主的数据没有,从的数据直接同步为主的没有数据状态,cmd操作从机发现无法进行写入.
主从均开启,主导入部分数据之后从有同样数据,关闭从之后,删除本地文件dump.rdb,再次开启从,从的数据恢复.
3.1.3 测试结论:
主机开启之后,首先从dump文件恢复数据,之后会不定期同步数据到磁盘的dump.rbd文件,关闭之后再起开启会再次直接从dump恢复数据,如果没有这个文件会造成数据清空的状况.
从开启之后,会从dump文件回复数据,之后查找网络的主机,找到之后进行一个备份编码的对比,如果相同就进行局部同步,不同就进行全同步--即发送全同步请求之后,直接获取到从机的dump文件,之后直接清空从机的数据之后从文件恢复.
3.2代码测试
3.2.1 写入数据测试
3.2.1.1 操作方法
通过写程序对数据的作进行写入操作并进行分析.
3.2.1.2 操作代码
直接通过jedis代码一个个写入数据:
private static void thingDataCreat() { Jedis jedis = new Jedis(rip, rport); if (rpassword != null && rpassword != "") jedis.auth(rpassword); System.out.println("Server is running: " + jedis.ping() + " Start to write!"); //jedis.flushAll(); MyTimeUtil mt = new MyTimeUtil(); for (int j = 0; j < 5; j++) { // 写入的次数,多次以求取平均值 jedis.flushAll(); mt.start(); for (int i = 0; i < 500; i++) { for (PlanProprity e : PlanProprity.values()) { jedis.set("China_beijing_plane:" + i + ":" + e, " " + mt.getTime()); } } // mt.endAndDisp(); // // mt.start(); for (int i = 0; i < 500; i++) { for (CarProprity e : CarProprity.values()) { jedis.set("China_hubei_car:" + i + ":" + e, " " + mt.getTime()); } } mt.endAndDisp(); } System.out.println("Server is running: " + jedis.ping() + "Write finished!"); }
直接通过jedis管道进行数据的批量写入:
private static void EngineDataCreatByPipe() { Jedis jedis = new Jedis(rip, rport); if(rpassword!=null&&rpassword!="")jedis.auth(rpassword); System.out.println("Server is running: " + jedis.ping() + " Start to write!"); jedis.flushAll(); MyTimeUtil mt = new MyTimeUtil(); for (int j = 0; j < 1; j++) { mt.start(); Pipeline pipeline = jedis.pipelined(); for (int i = 0; i < 1000; i++) { for (PlanProprity e : PlanProprity.values()) { pipeline.set("China_beijing_plane:" + i + ":" + e, " " + mt.getTime()); } } for (int i = 0; i < 1000; i++) { for (CarProprity e : CarProprity.values()) { pipeline.set("China_hubei_car:" + i + ":" + e, " " + mt.getTime()); } } pipeline.syncAndReturnAll(); mt.endAndDisp(); } System.out.println("Server is running: " + jedis.ping() + "Write finished!"); }
上面的那个计时的类是自己写的一个工具,可以参考下:
http://www.cnblogs.com/wangkun1993/p/7199146.html 自写时间小工具类
rip就是主机和从机的ip号,rport是端口好,rpassword是验证的密码.
遍历的两个是两个大小为十的枚举类型:
enum PlanProprity { PSpeed, PPrice, PLength, PHeight, PWidth, PCapacity, PMore1, PMore2, PMore3, PMore4 }; enum CarProprity { CSpeed, CPrice, CMOre1, CMOre2, CMOre3, CMOre4, CMOre5, CMOre6, CMOre7, CMOre }
3.2.1.3 操作结果
主单个写入10000条数据耗时: 24454ms 28491ms 45811ms 38017ms 22528ms 17845ms 18502ms 18079ms
*重启之后重新测试代码耗时10000数据 1276ms 1050ms 935ms 1218ms 935ms
*重启之后重新测试代码耗时20000数据 2096ms 1906ms 1920ms 1819ms 2072ms
主通过管道一次写入10000数据耗时: 388ms 503ms 1288ms 922ms 419ms 341ms 342ms 325ms
*重启之后重新测试管道代码耗时10000数据 122ms 64ms 48ms 53ms 68ms
*重启之后重新测试管道代码耗时20000数据(880KB) 208ms 124ms 98ms 126ms 119ms
写入20w数据4356ms
*重启之后20w数据(8.79MB)耗时 1728ms 1369ms 1391ms 1635ms 1477ms
写入200w数据(89.8MB)34121ms
从机无法写入数据.
从更改为主后测试远程写入数据:
从单个写入10000条数据耗时 6528ms 26953ms 32541ms 24982ms 22493ms 29789ms 31062ms
从通过管道一次写入10000数据耗时 227ms 753ms 599ms 387ms 508ms 531ms 507ms
3.2.1.4 结果分析
通过对比发现数据的写入速度通过管道实现效率得到了极大的提升,电脑的处理速度和网络的连通率对于远程操作的影响还是比较大的.
3.2.2 读取数据测试
3.2.2.1 操作方法
通过代码对redis进行数据的获取操作.
3.2.2.2 操作代码
直接通过get函数单个获取数据操作:
private static void getDate() { Jedis jedis = new Jedis(rip, rport); if(rpassword!=null&&rpassword!="")jedis.auth(rpassword); System.out.println("Server is running: " + jedis.ping()); // jedis.flushAll(); MyTimeUtil mt = new MyTimeUtil(); String strSaveDir = "d:\\edata\\"; String fileName = "BAIHEYUAN_HW_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".DJ"; // String fileName="BAIHEYUAN_HW_20161122_0030.DJ"; MyFileUtil mf = new MyFileUtil(); String fileContent = null, tcont = null; String dateType = "China_beijing_plane"; System.out.println("Start to get data"); for (int j = 0; j < 1; j++) { fileContent = null; mt.start(); for (int i = 0; i < 1000; i++) { for (PlanProprity e : PlanProprity.values()) { tcont = jedis.get(dateType + ":" + i + ":" + e); // fileContent += dateType+":" + i + ":\t" + e + tcont // + "\r\n"; } } mt.end(); mt.disp(); // if(mf.existsDictionary(strSaveDir + fileName)){ // mf.createFile(strSaveDir + fileName, fileContent); // } } System.out.println("Get data end!"); System.out.println("Server is running: " + jedis.ping()); }
通过管道进行批量操作:
private static void getDateByPipe() { Jedis jedis = new Jedis(rip, rport); if(rpassword!=null&&rpassword!="")jedis.auth(rpassword); System.out.println("Server is running: " + jedis.ping()); // jedis.flushAll(); MyTimeUtil mt = new MyTimeUtil(); String strSaveDir = "d:\\edata\\"; String fileName = "BAIHEYUAN_HW_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".DJ"; // String fileName="BAIHEYUAN_HW_20161122_0030.DJ"; MyFileUtil mf = new MyFileUtil(); String fileContent = null; Response<String> tcont = null; String dateType = "China_beijing_plane"; System.out.println("Start to get data"); for (int j = 0; j < 1; j++) { fileContent = null; mt.start(); Pipeline pipeline = jedis.pipelined(); for (int i = 0; i < 100; i++) { for (PlanProprity e : PlanProprity.values()) { tcont = pipeline.get("China_beijing_plane:" + i + ":" + e); fileContent += dateType + ":" + i + ":\t" + e + tcont + "\r\n"; } } pipeline.syncAndReturnAll(); mt.end(); mt.disp(); if (mf.existsDictionary(strSaveDir + fileName)) { mf.createFile(strSaveDir + fileName, fileContent); } } System.out.println("Get data end!"); System.out.println("Server is running: " + jedis.ping()); }
上面的代码中除了获取数据之外,另外还有一个导出数据到字符串和一个导出到文本的操作,后面发现这两个操作大大影响了时间效率,因此作为对比,进行了注释之后的二次测试.
补充:(20170720)
针对上面的管道操作,存在遗漏之处,主要是java在才做比较长的字符串的时候操作的时间是比较的,因此下面的这段管道操作是对前面的改进,也是前面一个接收对象出现数据遗漏bug的更正.
private static void getDateByPipeNew() { Jedis jedis = new Jedis(rip, rport); if(rpassword!=null&&rpassword!="")jedis.auth(rpassword); System.out.println("Server is running: " + jedis.ping()); // jedis.flushAll(); MyTimeUtil mt = new MyTimeUtil(); String strSaveDir = "d:\\edata\\"; String fileName = "BAIHEYUAN_HW_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".DJ"; File file = new File(strSaveDir+fileName); List<Response<String>> resultList = new ArrayList<Response<String>>(); String dateType ="China_beijing_plane"; System.out.println("Start to get data"); for (int j = 0; j < 1; j++) { mt.start(); Pipeline pipeline = jedis.pipelined(); for (int i = 0; i < 1000; i++) { dateType = "China_beijing_plane"; for (PlanProprity e : PlanProprity.values()) { resultList.add( pipeline.get(dateType+":" + i + ":" + e)); } } pipeline.syncAndReturnAll(); FileWriter fw = null; BufferedWriter bw = null; Iterator<Response<String>> iter = resultList.iterator(); try { fw = new FileWriter(file); bw = new BufferedWriter(fw); for(int i=0;i<resultList.size();i++){ bw.write(dateType+":"+(i/10)+":"+getPlanProprity(i%10)+"\t"+iter.next().get()); bw.newLine(); } bw.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { bw.close(); fw.close(); } catch (Exception e) { e.printStackTrace(); } } mt.end(); mt.disp(); } System.out.println("Get data end!"); System.out.println("Server is running: " + jedis.ping()); } static PlanProprity getPlanProprity(int i){ if(i==0) return PlanProprity.PSpeed; if(i==1) return PlanProprity.PPrice; if(i==2) return PlanProprity.PLength; if(i==3) return PlanProprity.PHeight; if(i==4) return PlanProprity.PWidth; if(i==5) return PlanProprity.PCapacity; if(i==6) return PlanProprity.PMore1; if(i==7) return PlanProprity.PMore2; if(i==8) return PlanProprity.PMore3; if(i==9) return PlanProprity.PMore4; return PlanProprity.PSpeed; };
查询的速度没有多大的变化,但是直接将字符串输出到文本,时间是大大减少了.
如果对于文件类比较感兴趣可以参考下我写的一个工具类: http://www.cnblogs.com/wangkun1993/p/7199326.html 自写文件小工具类
3.2.2.3 操作结果
主获取数据测试:
单个获取10000条数据时间:64704ms
去掉写数据操作 19356ms 20747ms 17678ms 20170ms
单个获取1000条数据时间:3202ms 2432ms 1791ms 2857ms 2218ms 2107ms
去掉写数据操作 1255ms 1267ms 1570ms 1408ms 1137ms
单个获取100条数据时间:186ms
去掉写数据操作 169ms 180ms 236ms 218ms
批量获取10000条数据时间:31367ms 31749ms 23582ms
去掉写数据操作 367ms 308ms 375ms
批量获取1000条数据时间:171ms 215ms 208ms 226ms 248ms 199ms 185ms 177ms
去掉写数据操作 86ms 62ms 45ms
*优化操作方法之后批量获取1w条数据并写入到文本时间: 167ms 147ms 171ms 157ms
*优化操作方法之后批量获取10w条数据时间: 943ms 1104ms 1180ms 992ms 1267ms
从获取数据测试:
单个获取1000条数据时间:1675ms 826ms 691ms 667ms 1112ms 1930ms 614ms
去掉写数据操作 955ms 818ms 959ms 1007ms 1187ms 338ms 494ms
单个获取100条数据时间:66ms 44ms 46ms
去掉写数据操作 121ms 137ms 101ms
批量个获取10000条数据时间:29965ms 30986ms 22533ms
去掉写数据操作 363ms 323ms 360ms
批量获取1000条数据时间:174ms 145ms 135ms
去掉写数据操作 55ms 27ms 12ms
3.2.2.4 结果分析
从上面的数据可以看到,通过管道获取数据想效率比一般获取数据要高的多,因此数据量比较多的时候显然管道操作是比较合适的,如果只是几个数据的操作,一般两种方式都行了.
对于操作的时候如果进行一些文件的导出操作和长字符创的生成操作,可以看到完成的时间是大大增加了,因此如果需要在获取数据之后进行一些其他的操作,最好认真斟酌一下,可以在数据获取完毕之后直接从内存中读取数据,进行一系列的操作.比方说按照改进的那个方法进行字符串直接写入到文件,而不是生成长串之后再写入到文件.
3.2.3 恢复数据测试
3.2.3.1 操作方法
通过代码,针对操作之后恢复数据的时间进行一个判断,
3.2.3.2 操作代码
private static void testRecovery() { Jedis jedis = new Jedis(rip, rport); long t = 0; boolean tag=true; for (int i = 0; i < 1;) { try { String ttt = null; if ("OK" .equals(jedis.auth(rpassword))&&tag==true) { t = System.currentTimeMillis(); System.out.println(System.currentTimeMillis() - t+" OK");//break; tag=false; } if ("PONG".equals(jedis.ping())) { System.out.println(System.currentTimeMillis() - t+" PONG");//break; } if (jedis.dbSize() >= 20000) { System.out.println((System.currentTimeMillis() - t)+" all data"); break; } } catch (Exception e) { // TODO: handle exception System.out.println("test"); } } System.out.println(System.currentTimeMillis() - t+" all out"); }
代码没有什么特殊的地方,直接运行就行了,运行之后才打开redis进行恢复操作,从文件恢复的时间太短,不好统计,直接取的它自己显示的数字,主要测试的是远程服务器传输数据需要话费的时间.
3.2.3.3 操作结果
主恢复数据测试(2w数据880kb)
直接dump文件恢复(s) 0.044 0.049 0.047
测试20w(2.89m)恢复时间) 0.15s
删除主机备份文件无法恢复.
从恢复数据测试(2w数据880kb)
直接dump文件恢复(s) 0.40 0.039 0.040 0.039
删除从机备份文件之后网络获取文件回复
2w数据3.35s 3.40s
3.2.3.4 结果分析
从结果来看,影响恢复速度的因素主要是网络的问题,从文件恢复几乎可以在秒级完成,可以忽略不计,当然需要看情况啦,至少我边项目不需要考虑从文件恢复的时间.恢复的速度还是比较满意的,测试数据时候的因为是内部网络,所以这个值不好做评判参考类.
3.2.4快速测试
3.2.4.1操作方法
直接通过代码快速进行读写测试,并输出结果,每个电脑的性能不一样时候,输出的时间会存在差异.
3.2.4.2操作代码
private static void simpleDataCreat() { Jedis jedis = new Jedis("127.0.0.1"); try { MyTimeUtil mt = new MyTimeUtil(); for (int z = 0; z < 5; z++) { jedis.flushAll(); mt.start(); for (int i = 0; i < 10000; i++) { jedis.set(i + "", i + ""); } mt.endAndDisp(); mt.start(); for (int i = 0; i < 10000; i++) { jedis.set(i + "", i + ""); } mt.endAndDisp(); } } catch (Exception e) { e.printStackTrace(); } jedis.disconnect(); }
这段代码是模仿一个朋友进行测试的,他测试时候和我的昨天测试时候输出比较大,因此我用同样的代码进行了下测试,对比发现差距并不怎大,但是和昨天的数据对比确实差距明显,因此今天重新测试了下一下数据.
3.2.4.3操作结果
操作的结果还是比较明显的:
生成1w数据(68.1KB)时间:1128ms 947ms 952ms 941ms 1209ms
读取1w数据时间:1159ms 1078ms 1001ms 988ms 1283ms
3.2.4.4结果分析
通过结果对比分析可以看到,快速的读写操作的速度还是比较高效的,从这里也可以看到电脑性能对于程序的影响,这个是正常编辑代码时候输出的效率,读者可以对比自己的电脑输出的时间,和楼主的数据做一个误差对比.一般的读写时间相差不会很大.
3.2.5主机生成键值为数组测试
3.2.5.1测试方法
因为redis的键值足够大,所以我们的存储方式是多样的,但是哪种方式最佳不是很好确定,没有一个完善的定论,因此还是在这里测试了下另外的一种存储方式------直接存储数组的形式.
3.2.5.2测试代码
考虑到生成代码时候比较长,redis又可以追加字串的长度,这里的话根据两种方式分别进行了测试.
private static void thingDataAppendArrayCreatByPipe() { Jedis jedis = new Jedis(rip, rport); if (rpassword != null && rpassword != "") jedis.auth(rpassword); System.out.println("Server is running: " + jedis.ping() + " Start to write!"); // jedis.flushAll(); MyTimeUtil mt = new MyTimeUtil(); for (int j = 0; j < 1; j++) { // jedis.flushAll(); mt.start(); Pipeline pipeline = jedis.pipelined(); for (PlanProprity e : PlanProprity.values()) { for (int i = 0; i < 10000; i++) { pipeline.append("China_beijing_plane2:" + e, "" + mt.getTime() + " "); } } for (CarProprity e : CarProprity.values()) { for (int i = 0; i < 10000; i++) { pipeline.append("China_hubei_car2:" + e, "" + mt.getTime() + " "); } } pipeline.syncAndReturnAll(); mt.endAndDisp(); } System.out.println("Server is running: " + jedis.ping() + "Write finished!"); } private static void thingDataSetArrayCreatByPipe() { Jedis jedis = new Jedis(rip, rport); if (rpassword != null && rpassword != "") jedis.auth(rpassword); System.out.println("Server is running: " + jedis.ping() + " Start to write!"); // jedis.flushAll(); MyTimeUtil mt = new MyTimeUtil(); for (int j = 0; j < 1; j++) { // jedis.flushAll(); mt.start(); Pipeline pipeline = jedis.pipelined(); for (PlanProprity e : PlanProprity.values()) { String t = ""; for (int i = 0; i < 10000; i++) { t += "" + mt.getTime() + " "; } pipeline.set("China_beijing_plane2:" + e, t); } for (CarProprity e : CarProprity.values()) { String t = ""; for (int i = 0; i < 10000; i++) { t += "" + mt.getTime() + " "; } pipeline.set("China_hubei_car2:" + e, t); } pipeline.syncAndReturnAll(); mt.endAndDisp(); } System.out.println("Server is running: " + jedis.ping() + "Write finished!"); }
3.2.5.3测试结果
主append生成20条1000长度数组(4KB) 890ms 896ms 766ms 808ms 801ms
主append生成20条10000长度数组(32KB) 10035ms 9557ms 9700ms 10119ms 10056ms
主append生成20条100000长度数组 119072ms
主set生成20条1000长度数组(4KB) 437ms 449ms 447ms 550ms 570ms
主set生成20条10000长度数组(32KB) 38995ms 40071ms 40633ms 47055ms
3.2.5.4结果分析
从上面的测试数据可以看到,这样的一个生成方式,内存占用是小了很多的,但是有一个问题是生成这么长的一个字符串需要的时间是比较大的,当然1k的字符串长度还是算正常,1w的字符串似乎处理时间就翻了一百倍了.对比了一下直接的set和append追加方式完成数据,append方式似乎是更加的节省时间一些.这里的话就根据项目的需要斟酌去对比了.数据的生成时间对比一下之后可以看到,单个的键值话费的时间还是比较少的.这里数组形式多出的时间主要是在字符串的处理,如果有好的优化建议欢迎评论.
3.2.6主机获取键值为数组测试
3.2.6.1测试方法
前面已经生成了需要的数据,这里直接通过代码将所有的数据直接生成并导出文件,进行和前面单个键值一样的一个操作.
3.2.6.2测试代码
通过前面的测试,我们已经明白主从机的效率相比,主机省去了网络传输的时间,所以效率更好.管道和单个获取数据,管道有着自身的优势,因此代码只是以主机的管道获取数据进行测试.
private static void getDateArrayByPipe() { Jedis jedis = new Jedis(rip, rport); if (rpassword != null && rpassword != "") jedis.auth(rpassword); System.out.println("Server is running: " + jedis.ping()); // jedis.flushAll(); MyTimeUtil mt = new MyTimeUtil(); String strSaveDir = "d:\\edata\\"; String fileName = "BAIHEYUAN_HW_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".DJ"; File file = new File(strSaveDir + fileName); List<Response<String>> resultList = new ArrayList<Response<String>>(); String dateType = "China_beijing_plane"; System.out.println("Start to get data"); for (int j = 0; j < 1; j++) {
if(mf.existsFile(strSaveDir + fileName)){
mf.deleteFile(strSaveDir + fileName);
}
resultList.removeAll(resultList);
mt.start(); Pipeline pipeline = jedis.pipelined(); for (int i = 0; i < 1; i++) { dateType = "China_beijing_plane2"; for (PlanProprity e : PlanProprity.values()) { resultList.add(pipeline.get(dateType + ":" + e)); } } pipeline.syncAndReturnAll(); // System.out.println(getPlanProprity(1)); FileWriter fw = null; BufferedWriter bw = null; Iterator<Response<String>> iter = resultList.iterator(); try { fw = new FileWriter(file); bw = new BufferedWriter(fw); for (int i = 0; i < resultList.size(); i++) { String ts = iter.next().get(); String[] tsarr = ts.split(" "); for (int k = 0; k < tsarr.length; k++) { bw.write(dateType + ":" + k + ":" + getPlanProprity(i) + "\t" + tsarr[k]); bw.newLine(); } } bw.flush(); System.out.println("write over!"); } catch (Exception e) { e.printStackTrace(); } finally { try { bw.close(); fw.close(); } catch (Exception e) { e.printStackTrace(); } } mt.end(); mt.disp(); } System.out.println("Get data end!"); System.out.println("Server is running: " + jedis.ping()); }
3.2.6.3测试结果
主获取10条10000长度数组 227ms 217ms 254ms 260ms 220ms
主获取10条100000长度数组 2168ms 2190ms 2357ms 1803ms 2033ms 1838ms
3.2.6.4结果分析
从这里可以看到,目前数据的处理速度是与数据量成正比的,数据越多,时间消耗越大,相对于前面的一个个的通过管道处理,这样的方式节省了读取的时间与次数,综合的时间还是比较省的.可以根据自己的实际需要进行分析判断选取.
目前测试就这些,后续可能继续进行其他测试,会继续修改这个博客.
个人原创,转载请注明出处.
欢迎指出不足之处以作改进.