Jedis使用教程完整版

  • 摘要:概述Jedis是Redis官方推荐的Java连接开发工具。要在Java开发中使用好Redis中间件,必须对Jedis熟悉才能写成漂亮的代码。这篇文章不描述怎么安装Redis和Reids的命令,只对Jedis的使用进行对介绍。1.基本使用Jedis的基本使用非常简单,只需要创建Jedis对象的时候指定host,port,password即可。当然,Jedis对象又很多构造方法,都大同小异,只是对应和Redis连接的socket的参数不一样而已。Jedisjedis=newJed
  • 概述 

    Jedis是Redis官方推荐的Java连接开发工具。要在Java开发中使用好Redis中间件,必须对Jedis熟悉才能写成漂亮的代码。这篇文章不描述怎么安装Redis和Reids的命令,只对Jedis的使用进行对介绍。

     

    Jedis使用教程完整版_Java


    1. 基本使用 

    Jedis的基本使用非常简单,只需要创建Jedis对象的时候指定host,port, password即可。当然,Jedis对象又很多构造方法,都大同小异,只是对应和Redis连接的socket的参数不一样而已。


    Jedis jedis = new Jedis("localhost", 6379);//指定Redis服务Host和port 
    jedis.auth("xxxx"); //如果Redis服务连接需要密码,制定密码 
    String value = jedis.get("key"); //访问Redis服务 
    jedis.close(); //使用完关闭连接 

    Jedis基本使用十分简单,在每次使用时,构建Jedis对象即可。在Jedis对象构建好之后,Jedis底层会打开一条Socket通道和Redis服务进行连接。所以在使用完Jedis对象之后,需要调用Jedis.close()方法把连接关闭,不如会占用系统资源。当然,如果应用非常平凡的创建和销毁Jedis对象,对应用的性能是很大影响的,因为构建Socket的通道是很耗时的(类似数据库连接)。我们应该使用连接池来减少Socket对象的创建和销毁过程。


    2. 连接池使用 

    Jedis连接池是基于apache-commons pool2实现的。在构建连接池对象的时候,需要提供池对象的配置对象,及JedisPoolConfig(继承自GenericObjectPoolConfig)。我们可以通过这个配置对象对连接池进行相关参数的配置(如最大连接数,最大空数等)。


    JedisPoolConfig config = new JedisPoolConfig(); 
    config.setMaxIdle(8); 
    config.setMaxTotal(18); 
    JedisPool pool = new JedisPool(config, "127.0.0.1", 6379, 2000, "password"); 
    Jedis jedis = pool.getResource(); 
    String value = jedis.get("key"); 
    ...... 
    jedis.close(); 
    pool.close(); 

    使用Jedis连接池之后,在每次用完连接对象后一定要记得把连接归还给连接池。Jedis对close方法进行了改造,如果是连接池中的连接对象,调用Close方法将会是把连接对象返回到对象池,若不是则关闭连接。可以查看如下代码


    @Override 
    public void close() { //Jedis的close方法 
    if (dataSource != null) { 
    if (client.isBroken()) { 
    this.dataSource.returnBrokenResource(this); 
    } else { 
    this.dataSource.returnResource(this); 

    } else { 
    client.close(); 


    //另外从对象池中获取Jedis链接时,将会对dataSource进行设置 
    // JedisPool.getResource()方法 
    public Jedis getResource() { 
    Jedis jedis = super.getResource(); 
    jedis.setDataSource(this); 
    return jedis; 

    3. 高可用连接 

    我们知道,连接池可以大大提高应用访问Reids服务的性能,减去大量的Socket的创建和销毁过程。但是Redis为了保障高可用,服务一般都是Sentinel部署方式(可以查看我的文章详细了解)。当Redis服务中的主服务挂掉之后,会仲裁出另外一台Slaves服务充当Master。这个时候,我们的应用即使使用了Jedis连接池,Master服务挂了,我们的应用奖还是无法连接新的Master服务。为了解决这个问题,Jedis也提供了相应的Sentinel实现,能够在Redis Sentinel主从切换时候,通知我们的应用,把我们的应用连接到新的 Master服务。先看下怎么使用。

     
     

    注意:Jedis版本必须2.4.2或更新版本


    Set sentinels = new HashSet<>(); 
    sentinels.add("172.18.18.207:26379"); 
    sentinels.add("172.18.18.208:26379"); 
    JedisPoolConfig config = new JedisPoolConfig(); 
    config.setMaxIdle(5); 
    config.setMaxTotal(20); 
    JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels, config); 
    Jedis jedis = pool.getResource(); 
    jedis.set("jedis", "jedis"); 
    ...... 
    jedis.close(); 
    pool.close(); 

    Jedis Sentinel的使用也是十分简单的,只是在JedisPool中添加了Sentinel和MasterName参数。Jedis Sentinel底层基于Redis订阅实现Redis主从服务的切换通知。当Reids发生主从切换时,Sentinel会发送通知主动通知Jedis进行连接的切换。JedisSentinelPool在每次从连接池中获取链接对象的时候,都要对连接对象进行检测,如果此链接和Sentinel的Master服务连接参数不一致,则会关闭此连接,重新获取新的Jedis连接对象。


    public Jedis getResource() { 
    while (true) { 
    Jedis jedis = super.getResource(); 
    jedis.setDataSource(this); 
    // get a reference because it can change concurrently 
    final HostAndPort master = currentHostMaster; 
    final HostAndPort connection = new HostAndPort(jedis.getClient().getHost(), jedis.getClient().getPort()); 
    if (master.equals(connection)) { 
    // connected to the correct master 
    return jedis; 
    } else { 
    returnBrokenResource(jedis); 


    当然,JedisSentinelPool对象要时时监控RedisSentinel的主从切换。在其内部通过Reids的订阅实现。具体的实现看JedisSentinelPool的两个方法就很清晰


    private HostAndPort initSentinels(Set sentinels, final String masterName) { 
    HostAndPort master = null; 
    boolean sentinelAvailable = false; 
    log.info("Trying to find master from available Sentinels..."); 
    for (String sentinel : sentinels) { 
    final HostAndPort hap = HostAndPort.parseString(sentinel); 
    log.fine("Connecting to Sentinel " + hap); 
    Jedis jedis = null; 
    try { 
    jedis = new Jedis(hap.getHost(), hap.getPort()); 
    //从RedisSentinel中获取Master信息 
    List masterAddr = jedis.sentinelGetMasterAddrByName(masterName); 
    sentinelAvailable = true; // connected to sentinel... 
    if (masterAddr == null || masterAddr.size() != 2) { 
    log.warning("Can not get master addr, master name: " + masterName + ". Sentinel: " + hap + "."); 
    continue; 

    master = toHostAndPort(masterAddr); 
    log.fine("Found Redis master at " + master); 
    break; 
    } catch (JedisException e) { 
    // it should handle JedisException there's another chance of raising JedisDataException 
    log.warning("Cannot get master address from sentinel running @ " + hap + ". Reason: " + e + ". Trying next one."); 
    } finally { 
    if (jedis != null) { 
    jedis.close(); 



    if (master == null) { 
    if (sentinelAvailable) { 
    // can connect to sentinel, but master name seems to not monitored 
    throw new JedisException("Can connect to sentinel, but " + masterName + " seems to be not monitored..."); 
    } else { 
    throw new JedisConnectionException("All sentinels down, cannot determine where is " + masterName + " master is running..."); 


    log.info("Redis master running at " + master + ", starting Sentinel listeners..."); 
    //启动后台线程监控RedisSentinal的主从切换通知 
    for (String sentinel : sentinels) { 
    final HostAndPort hap = HostAndPort.parseString(sentinel); 
    MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort()); 
    // whether MasterListener threads are alive or not, process can be stopped 
    masterListener.setDaemon(true); 
    masterListeners.add(masterListener); 
    masterListener.start(); 

    return master; 
    }private void initPool(HostAndPort master) { 
    if (!master.equals(currentHostMaster)) { 
    currentHostMaster = master; 
    if (factory == null) { 
    factory = new JedisFactory(master.getHost(), master.getPort(), connectionTimeout, soTimeout, password, database, clientName, false, null, null, null); 
    initPool(poolConfig, factory); 
    } else { 
    factory.setHostAndPort(currentHostMaster); 
    // although we clear the pool, we still have to check the returned object 
    // in getResource, this call only clears idle instances, not 
    // borrowed instances 
    internalPool.clear(); 

    log.info("Created JedisPool to master at " + master); 

    可以看到,JedisSentinel的监控时使用MasterListener这个对象来实现的。看对应源码可以发现是基于Redis的订阅实现的,其订阅频道为"+switch-master"。当MasterListener接收到switch-master消息时候,会使用新的Host和port进行initPool。这样对连接池中的连接对象清除,重新创建新的连接指向新的Master服务。


    4. 客户端分片 

    对于大应用来说,单台Redis服务器肯定满足不了应用的需求。在Redis3.0之前,是不支持集群的。如果要使用多台Reids服务器,必须采用其他方式。很多公司使用了代理方式来解决Redis集群。对于Jedis,也提供了客户端分片的模式来连接“Redis集群”。其内部是采用Key的一致性hash算法来区分key存储在哪个Redis实例上的。


    JedisPoolConfig config = new JedisPoolConfig(); 
    config.setMaxTotal(500); 
    config.setTestOnBorrow(true); 
    List jdsInfoList = new ArrayList<>(2); 
    jdsInfoList.add(new JedisShardInfo("192.168.2.128", 6379)); 
    jdsInfoList.add(new JedisShardInfo("192.168.2.108", 6379)); 
    pool = new ShardedJedisPool(config, jdsInfoList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN); 
    jds.set(key, value); 
    ...... 
    jds.close(); 
    pool.close(); 

    当然,采用这种方式也存在两个问题

    扩容问题: 因为使用了一致性哈稀进行分片,那么不同的key分布到不同的Redis-Server上,当我们需要扩容时,需要增加机器到分片列表中,这时候会使得同样的key算出来落到跟原来不同的机器上,这样如果要取某一个值,会出现取不到的情况。 
    单点故障问题: 当集群中的某一台服务挂掉之后,客户端在根据一致性hash无法从这台服务器取数据。

    对于扩容问题,Redis的作者提出了一种名为Pre-Sharding的方式。即事先部署足够多的Redis服务。 对于单点故障问题,我们可以使用Redis的HA高可用来实现。利用Redis-Sentinal来通知主从服务的切换。当然,Jedis没有实现这块。我将会在下一篇文章进行介绍。


    5. 小结 
     

    对于Jedis的基本使用还是很简单的。要根据不用的应用场景选择对于的使用方式。 另外,Spring也提供了Spring-data-redis包来整合Jedis的操作,另外Spring也单独分装了Jedis(我将会在另外一篇文章介绍)。

     

    作者:曹金桂 链接:http://www.jianshu.com/p/a1038eed6d44 來源:简书

  • 以上是Jedis使用教程完整版的内容,更多 整版 使用 教程 jedis 的内容,请您使用右上方搜索功能获取相关信息。

转载于:https://my.oschina.net/airship/blog/2875170

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Jedis使用总结 前段时间细节的了解了Jedis的使用,Jedis是redis的java版本的客户端实现。 本文做个总结,主要分享如下内容: 【pipeline】【分布式的id生成器】【分布式锁【watch】【multi】】【redis分布式】 好了,一个一个来。 一、 Pipeline 官方的说明是:starts a pipeline,which is a very efficient way to send lots of command and read all the responses when you finish sending them。简单点说pipeline适用于批处理。当有大量的操作需要一次性执行的时候,可以用管道。 示例: Jedis jedis = new Jedis(String, int); Pipeline p = jedis.pipelined(); p.set(key,value);//每个操作都发送请求给redis-server p.get(key,value); p.sync();//这段代码获取所有的response 这里我进行了20w次连续操作(10w读,10w写),不用pipeline耗时:187242ms,用pipeline耗时:1188ms,可见使用管道后的性能上了一个台阶。看了代码了解到,管道通过一次性写入请求,然后一次性读取响应。也就是说jedis是:request response,request response,...;pipeline则是:request request... response response的方式。这样无需每次请求都等待server端的响应。 二、 跨jvm的id生成器 谈到这个话题,首先要知道redis-server端是单线程来处理client端的请求的。 这样来实现一个id生成器就非常简单了,只要简单的调用jdeis.incr(key);就搞定了。 你或许会问,incr是原子操作吗,能保证不会出现并发问题吗,不是说了吗,server端是单线程处理请求的。 三、 【跨jvm的锁实现【watch】【multi】】 首先说下这个问题的使用场景,有些时候我们业务逻辑是在不同的jvm进程甚至是不同的物理机上的jvm处理的。这样如何来实现不同jvm上的同步问题呢,其实我们可以基于redis来实现一个锁。 具体事务和监听请参考文章:redis学习笔记之事务 暂时找到三种实现方式: 1. 通过jedis.setnx(key,value)实现 import java.util.Random; import org.apache.commons.pool.impl.GenericObjectPool.Config; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; /** * @author Teaey */ public class RedisLock { //加锁标志 public static final String LOCKED = "TRUE"; public static final long ONE_MILLI_NANOS = 1000000L; //默认超时时间(毫秒) public static final long DEFAULT_TIME_OUT = 3000; public static JedisPool pool; public static final Random r = new Random(); //锁的超时时间(秒),过期删除 public static final int EXPIRE = 5 * 60; static { pool = new JedisPool(new Config(), "host", 6379); } private Jedis jedis; private String key; //锁状态标志 private boolean locked = false; public RedisLock(String key) { this.key = key; this.jedis = pool.getResource(); } public boolean lock(long timeout) { long nano = System.nanoTime(); timeout *= ONE_MILLI_NANOS; try { while ((System.nanoTime() - nano) < timeout) { if (jedis.setnx(key, LOCKED) == 1) { jedis.expire(key, EXPIRE); locked = true; return locked; } // 短暂休眠,nano避免出现活锁 Thread.sleep(3, r.nextInt(500)); } } catch (Exception e) { } return false; } public boolean lock() { return lock(DEFAULT_TIME_OUT); } // 无论是否加锁成功,必须调用 public void unlock() { try { if (locked) jedis.del(key); } finally { pool.returnResource(jedis); } } } 2. 通过事务(multi)实现 由于采纳第一张方法,第二种跟第三种实现只贴了关键代码,望谅解。^_^ public boolean lock_2(long timeout) { long nano = System.nanoTime(); timeout *= ONE_MILLI_NANOS; try { while ((System.nanoTime() - nano) < timeout) { Transaction t = jedis.multi(); // 开启事务,当server端收到multi指令 // 会将该client的命令放入一个队列,然后依次执行,知道收到exec指令 t.getSet(key, LOCKED); t.expire(key, EXPIRE); String ret = (String) t.exec().get(0); if (ret == null || ret.equals("UNLOCK")) { return true; } // 短暂休眠,nano避免出现活锁 Thread.sleep(3, r.nextInt(500)); } } catch (Exception e) { } return false; } 3. 通过事务+监听实现 public boolean lock_3(long timeout) { long nano = System.nanoTime(); timeout *= ONE_MILLI_NANOS; try { while ((System.nanoTime() - nano) < timeout) { jedis.watch(key); // 开启watch之后,如果key的值被修改,则事务失败,exec方法返回null String value = jedis.get(key); if (value == null || value.equals("UNLOCK")) { Transaction t = jedis.multi(); t.setex(key, EXPIRE, LOCKED); if (t.exec() != null) { return true; } } jedis.unwatch(); // 短暂休眠,nano避免出现活锁 Thread.sleep(3, r.nextInt(500)); } } catch (Exception e) { } return false; } 最终采用第一种实现,因为加锁只需发送一个请求,效率最高。 四、 【redis分布式】 最后一个话题,jedis的分布式。在jedis的源码里发现了两种hash算法(MD5,MURMUR Hash(默认)),也可以自己实现redis.clients.util.Hashing接口扩展。 List<JedisShardInfo> hosts = new ArrayList<JedisShardInfo>(); //server1 JedisShardInfo host1 = new JedisShardInfo("", 6380, 2000); //server2 JedisShardInfo host2 = new JedisShardInfo("", 6381, 2000); hosts.add(host1); hosts.add(host2); ShardedJedis jedis = new ShardedJedis(hosts); jedis.set("key", "");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值