jedis之调用方式

jedis是一个著名的key-value存储系统,而作为其官方推荐的java版客户端jedis也非常强大和稳定,支持事务、管道及有jedis自身实现的分布式。

在这里对jedis关于事务、管道和分布式的调用方式做一个简单的介绍和对比:

一、普通同步方式

最简单和基础的调用方式,

@Test

public void test1Normal() {

Jedis jedis = new Jedis("localhost");

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

String result = jedis.set("n" + i, "n" + i);

}

long end = System.currentTimeMillis();

System.out.println("Simple SET: " + ((end - start)/1000.0) + " seconds");

jedis.disconnect();

}

很简单吧,每次set之后都可以返回结果,标记是否成功。

二、事务方式(Transactions)

redis的事务很简单,他主要目的是保障,一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令。

看下面例子:

@Test

public void test2Trans() {

Jedis jedis = new Jedis("localhost");

long start = System.currentTimeMillis();

Transaction tx = jedis.multi();

for (int i = 0; i < 100000; i++) {

tx.set("t" + i, "t" + i);

}

List results = tx.exec();

long end = System.currentTimeMillis();

System.out.println("Transaction SET: " + ((end - start)/1000.0) + " seconds");

jedis.disconnect();

}

我们调用jedis.watch(…)方法来监控key,如果调用后key值发生变化,则整个事务会执行失败。另外,事务中某个操作失败,并不会回滚其他操作。这一点需要注意。还有,我们可以使用discard()方法来取消事务。

三、管道(Pipelining)

有时,我们需要采用异步方式,一次发送多个指令,不同步等待其返回结果。这样可以取得非常好的执行效率。这就是管道,调用方法如下:

@Test

public void test3Pipelined() {

Jedis jedis = new Jedis("localhost");

Pipeline pipeline = jedis.pipelined();

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

pipeline.set("p" + i, "p" + i);

}

List results = pipeline.syncAndReturnAll();

long end = System.currentTimeMillis();

System.out.println("Pipelined SET: " + ((end - start)/1000.0) + " seconds");

jedis.disconnect();

}

四、管道中调用事务

就Jedis提供的方法而言,是可以做到在管道中使用事务,其代码如下:

@Test

public void test4combPipelineTrans() {

jedis = new Jedis("localhost");

long start = System.currentTimeMillis();

Pipeline pipeline = jedis.pipelined();

pipeline.multi();

for (int i = 0; i < 100000; i++) {

pipeline.set("" + i, "" + i);

}

pipeline.exec();

List results = pipeline.syncAndReturnAll();

long end = System.currentTimeMillis();

System.out.println("Pipelined transaction: " + ((end - start)/1000.0) + " seconds");

jedis.disconnect();

}

但是经测试(见本文后续部分),发现其效率和单独使用事务差不多,甚至还略微差点。

五、分布式直连同步调用

@Test

public void test5shardNormal() {

List shards = Arrays.asList(

new JedisShardInfo("localhost",6379),

new JedisShardInfo("localhost",6380));

ShardedJedis sharding = new ShardedJedis(shards);

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

String result = sharding.set("sn" + i, "n" + i);

}

long end = System.currentTimeMillis();

System.out.println("Simple@Sharing SET: " + ((end - start)/1000.0) + " seconds");

sharding.disconnect();

}

这个是分布式直接连接,并且是同步调用,每步执行都返回执行结果。类似地,还有异步管道调用。

六、分布式直连异步调用

@Test

public void test6shardpipelined() {

List shards = Arrays.asList(

new JedisShardInfo("localhost",6379),

new JedisShardInfo("localhost",6380));

ShardedJedis sharding = new ShardedJedis(shards);

ShardedJedisPipeline pipeline = sharding.pipelined();

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

pipeline.set("sp" + i, "p" + i);

}

List results = pipeline.syncAndReturnAll();

long end = System.currentTimeMillis();

System.out.println("Pipelined@Sharing SET: " + ((end - start)/1000.0) + " seconds");

sharding.disconnect();

}

七、分布式连接池同步调用

如果,你的分布式调用代码是运行在线程中,那么上面两个直连调用方式就不合适了,因为直连方式是非线程安全的,这个时候,你就必须选择连接池调用。ShardedJedis是通过一致性哈希来实现分布式缓存的,通过一定的策略把不同的key分配到不同的redis server上,达到横向扩展的目的。

ShardedJedis的使用方法除了配置时有点区别,其他和Jedis基本类似,有一点要注意的是ShardedJedis不支持多命令操作,像mget、mset、brpop等可以在redis命令后一次性操作多个key的命令,具体包括哪些,大家可以看Jedis下的MultiKeyCommands这个类,这里面就包含了所有的多命令操作。

在初始化ShardedJedisPool时,我们还可以传入ShardedJedis采用的hash算法,支持MURMUR_HASHmd5两种算法,默认是使用MURMUR_HASH(可以查看redis.clients.util.Hashing类查看相关的信息)


@Test

public void test7shardSimplePool() {

List shards = Arrays.asList(

new JedisShardInfo("localhost",6379),

new JedisShardInfo("localhost",6380));

ShardedJedisPool pool = new ShardedJedisPool(new JedisPoolConfig(), shards);

ShardedJedis one = pool.getResource();

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

String result = one.set("spn" + i, "n" + i);

}

long end = System.currentTimeMillis();

pool.returnResource(one);

System.out.println("Simple@Pool SET: " + ((end - start)/1000.0) + " seconds");

pool.destroy();

}

jedis获取后一定要关闭,这和我们使用数据库连接池是一样的,放在finally块中保证jedis的关闭.

ps:如果大家使用的jdk是1.7版本或者以上的话,可以使用1.7加入的try-with-resources语句。

另外还可以传入keyTagPattern来指定我们key的分布策略,所有能够匹配keyTagPattern的key(通过正则匹配)将放在同一个redis里,默认的是直接使用key来进行判定。Redis自带了一个Sharded.keyTagPattern,如下

Pattern DEFAULT_KEY_TAG_PATTERN = Pattern.compile("\\{(.+?)\\}");

我们可以用下面的代码来实际测试下

ShardedJedis jedis =jedisPool.getResource();

jedis.set("cnblog", "cnblog");

jedis.set("redis", "redis");

jedis.set("test", "test");

jedis.set("123456", "1234567");

Client client1= jedis.getShard("cnblog").getClient();

Client client2= jedis.getShard("redis").getClient();

Client client3= jedis.getShard("test").getClient();

Client client4= jedis.getShard("123456").getClient();////打印key在哪个server中System.out.println("cnblog in server:" + client1.getHost() + " and port is:" +client1.getPort());

System.out.println("redis  in server:" + client2.getHost() + " and port is:" +client2.getPort());

System.out.println("test  in server:" + client3.getHost() + " and port is:" +client3.getPort());

System.out.println("123456 in server:" + client4.getHost() + " and port is:" + client4.getPort());


八、分布式连接池异步调用

@Test

public void test8shardPipelinedPool() {

List shards = Arrays.asList(

new JedisShardInfo("localhost",6379),

new JedisShardInfo("localhost",6380));

ShardedJedisPool pool = new ShardedJedisPool(new JedisPoolConfig(), shards);

ShardedJedis one = pool.getResource();

ShardedJedisPipeline pipeline = one.pipelined();

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

pipeline.set("sppn" + i, "n" + i);

}

List results = pipeline.syncAndReturnAll();

long end = System.currentTimeMillis();

pool.returnResource(one);

System.out.println("Pipelined@Pool SET: " + ((end - start)/1000.0) + " seconds");

pool.destroy();

}

九、需要注意的地方

事务和管道都是异步模式。在事务和管道中不能同步查询结果。比如下面两个调用,都是不允许的:

Transaction tx = jedis.multi();

for (int i = 0; i < 100000; i++) {

tx.set("t" + i, "t" + i);

}

System.out.println(tx.get("t1000").get());  //不允许

List results = tx.exec();

Pipeline pipeline = jedis.pipelined();

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

pipeline.set("p" + i, "p" + i);

}

System.out.println(pipeline.get("p1000").get()); //不允许

List results = pipeline.syncAndReturnAll();

事务和管道都是异步的,个人感觉,在管道中再进行事务调用,没有必要,不如直接进行事务模式。

分布式中,连接池的性能比直连的性能略好(见后续测试部分)。

分布式调用中不支持事务。

因为事务是在服务器端实现,而在分布式中,每批次的调用对象都可能访问不同的机器,所以,没法进行事务。

十、测试

运行上面的代码,进行测试,其结果如下:

Simple SET: 5.227 seconds

Transaction SET: 0.5 seconds

Pipelined SET: 0.353 seconds

Pipelined transaction: 0.509 seconds

Simple@Sharing SET: 5.289 seconds

Pipelined@Sharing SET: 0.348 seconds

Simple@Pool SET: 5.039 seconds

Pipelined@Pool SET: 0.401 seconds

另外,经测试分布式中用到的机器越多,调用会越慢。上面是2片,下面是5片:

Simple@Sharing SET: 5.494 seconds

Pipelined@Sharing SET: 0.51 seconds

Simple@Pool SET: 5.223 seconds

Pipelined@Pool SET: 0.518 seconds

下面是10片:

Simple@Sharing SET: 5.9 seconds

Pipelined@Sharing SET: 0.794 seconds

Simple@Pool SET: 5.624 seconds

Pipelined@Pool SET: 0.762 seconds

下面是100片:

Simple@Sharing SET: 14.055 seconds

Pipelined@Sharing SET: 8.185 seconds

Simple@Pool SET: 13.29 seconds

Pipelined@Pool SET: 7.767 seconds

分布式中,连接池方式调用不但线程安全外,根据上面的测试数据,也可以看出连接池比直连的效率更好。

十一、完整的测试代码

package com.example.nosqlclient;

import java.util.Arrays;

import java.util.List;

import org.junit.AfterClass;

import org.junit.BeforeClass;

import org.junit.Test;

import redis.clients.jedis.Jedis;

import redis.clients.jedis.JedisPoolConfig;

import redis.clients.jedis.JedisShardInfo;

import redis.clients.jedis.Pipeline;

import redis.clients.jedis.ShardedJedis;

import redis.clients.jedis.ShardedJedisPipeline;

import redis.clients.jedis.ShardedJedisPool;

import redis.clients.jedis.Transaction;

import org.junit.FixMethodOrder;

import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)

public class TestJedis {

private static Jedis jedis;

private static ShardedJedis sharding;

private static ShardedJedisPool pool;

@BeforeClass

public static void setUpBeforeClass() throws Exception {

List shards = Arrays.asList(

new JedisShardInfo("localhost",6379),

new JedisShardInfo("localhost",6379)); //使用相同的ip:port,仅作测试

jedis = new Jedis("localhost");

sharding = new ShardedJedis(shards);

pool = new ShardedJedisPool(new JedisPoolConfig(), shards);

}

@AfterClass

public static void tearDownAfterClass() throws Exception {

jedis.disconnect();

sharding.disconnect();

pool.destroy();

}

@Test

public void test1Normal() {

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

String result = jedis.set("n" + i, "n" + i);

}

long end = System.currentTimeMillis();

System.out.println("Simple SET: " + ((end - start)/1000.0) + " seconds");

}

@Test

public void test2Trans() {

long start = System.currentTimeMillis();

Transaction tx = jedis.multi();

for (int i = 0; i < 100000; i++) {

tx.set("t" + i, "t" + i);

}

//System.out.println(tx.get("t1000").get());

List results = tx.exec();

long end = System.currentTimeMillis();

System.out.println("Transaction SET: " + ((end - start)/1000.0) + " seconds");

}

@Test

public void test3Pipelined() {

Pipeline pipeline = jedis.pipelined();

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

pipeline.set("p" + i, "p" + i);

}

//System.out.println(pipeline.get("p1000").get());

List results = pipeline.syncAndReturnAll();

long end = System.currentTimeMillis();

System.out.println("Pipelined SET: " + ((end - start)/1000.0) + " seconds");

}

@Test

public void test4combPipelineTrans() {

long start = System.currentTimeMillis();

Pipeline pipeline = jedis.pipelined();

pipeline.multi();

for (int i = 0; i < 100000; i++) {

pipeline.set("" + i, "" + i);

}

pipeline.exec();

List results = pipeline.syncAndReturnAll();

long end = System.currentTimeMillis();

System.out.println("Pipelined transaction: " + ((end - start)/1000.0) + " seconds");

}

@Test

public void test5shardNormal() {

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

String result = sharding.set("sn" + i, "n" + i);

}

long end = System.currentTimeMillis();

System.out.println("Simple@Sharing SET: " + ((end - start)/1000.0) + " seconds");

}

@Test

public void test6shardpipelined() {

ShardedJedisPipeline pipeline = sharding.pipelined();

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

pipeline.set("sp" + i, "p" + i);

}

List results = pipeline.syncAndReturnAll();

long end = System.currentTimeMillis();

System.out.println("Pipelined@Sharing SET: " + ((end - start)/1000.0) + " seconds");

}

@Test

public void test7shardSimplePool() {

ShardedJedis one = pool.getResource();

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

String result = one.set("spn" + i, "n" + i);

}

long end = System.currentTimeMillis();

pool.returnResource(one);

System.out.println("Simple@Pool SET: " + ((end - start)/1000.0) + " seconds");

}

@Test

public void test8shardPipelinedPool() {

ShardedJedis one = pool.getResource();

ShardedJedisPipeline pipeline = one.pipelined();

long start = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

pipeline.set("sppn" + i, "n" + i);

}

List results = pipeline.syncAndReturnAll();

long end = System.currentTimeMillis();

pool.returnResource(one);

System.out.println("Pipelined@Pool SET: " + ((end - start)/1000.0) + " seconds");

}

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,302评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,232评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,337评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,977评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,920评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,194评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,638评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,319评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,455评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,379评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,426评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,106评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,696评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,786评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,996评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,467评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,043评论 2 341

推荐阅读更多精彩内容