Redis之三Redis的Java客户端Jedis

10. Redis的Java客户端

10.1 启用远程连接

  1. 注释掉bind 127.0.0.1可以使所有的ip访问redis

  2. 修改:protected-mode no

  3. 修改daemonize为yes,以守护进程的方式运行

    daemonize yes # 根据版本不同可能默认值不同我的默认是yes不需要修改
    
  4. 启用redis密码登陆:

    config set requirepass 30807 #由于在redis在虚拟机密码没有设置很长
    
  5. 设置防火墙规则

    iptables -I INPUT -p tcp --dport 6379 -j ACCEPT
    

10.2 Jedis测试连接

  1. 创建maven项目名为redis

  2. pom.xml中引入依赖

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.0.1</version>
        <type>jar</type>
        <scope>compile</scope>
    </dependency>
    
  3. 创建测试类TestPing

    @Test
    public void testPing(){
        Jedis jedis = new Jedis("192.168.93.131", 6379);
        jedis.auth("30807");//验证密码
        System.out.println(jedis.ping());
        jedis.close();
    }
    
    

10.3 Jedis的基本操作

public class TestAPI {
    private static String url = "192.168.93.131";
    private static int port = 6379;
    
    public Jedis getJedis(){
        Jedis jedis = new Jedis(url, port);
        jedis.auth("30807");//验证密码
        return jedis;
    }
    
    //测试set操作
    @Test
    public void testSet(){
        Jedis jedis = getJedis();
        
        jedis.set("k1", "v1");
        jedis.set("k2", "v2");
        
        jedis.close();
    }
    
    //测试get操作
    @Test
    public void testGet(){
        Jedis jedis = getJedis();
        
        System.out.println(jedis.get("k1"));
        
        jedis.close();
    }
    
    //测试keys
    @Test
    public void testKeys(){
        Jedis jedis = getJedis();
        
        Set<String> set = jedis.keys("*");
        System.out.println(set);
        
        jedis.close();
    }
}

jedis的事务:

public class TestTransaction {
    public Jedis getJedis(){
        Jedis jedis = new Jedis("192.168.93.131", 6379);
        jedis.auth("30807");//验证密码
        return jedis;
    }
    @Test
    public void testTx(){
        Jedis jedis = getJedis();
        //开启事务
        Transaction transaction = jedis.multi();
        transaction.set("k3", "v3");
        transaction.set("k4", "v4");
        transaction.get("k4");
        
        //提交,并返回执行结果
        List<Object> list = transaction.exec();
        System.out.println(list);
        
        jedis.close();
    }
}

通过上面的代码会发现方法名基本上和redis命令是一样的,但是每次都创建连接使用完关闭连接就很烦。

所以下面演示redis线程池

10.4 JedisPool线程池

Jedis实例不是线程安全的,所以不可以多个线程共用一个Jedis实例,但是创建太多的实现也不好因为这意味着会建立很多sokcet连接。
JedisPool是一个线程安全的网络连接池。可以用JedisPool创建一些可靠Jedis实例,可以从池中获取Jedis实例,使用完后再把Jedis实例还回JedisPool。这种方式可以避免创建大量socket连接并且会实现高效的性能。

JedisPoll有切片非切片之分:

  • JedisPool类是非切片连接池适用于单机版,即单节点非集群
  • ShardedJedisPool是切片连接池,适用于Redis集群

这里创建JedisPool单机版连接池工具类,一般集群会使用spring-data所以不写ShardedJedisPool工具类了

public class RedisManager  {
    private JedisPool jedisPool;//非切片连接池
    
    private String connectHost;
    private int connectPort;
    private String requirePass;
    
    private int maxTotal = 20;//最大连接数
    private int maxIdle = 5;//最小空闲数
    private long maxWaitMillis = 10000;
    //是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
    private boolean testOnBorrow = true;
    //在空闲时检查有效性,默认false
    private boolean testWhileIdle = false;
    private Properties prop = new Properties();
    
    private volatile static RedisManager redisManager = null;
    
    //创建redisClient时初始化连接池创建jedis并返回
    private RedisManager() { 
        try {
            prop.load(RedisManager.class.getClassLoader().getResourceAsStream("redis.properties"));
        } catch (IOException e) {
            new RuntimeException("File not find:redis.properties is not find in classpth");
        }
        
        //读取配置文件
        this.connectHost = prop.getProperty("redis.connect.host");
        
        String port = prop.getProperty("redis.connect.port");
        if(isNotBlank(port)){
            this.connectPort = Integer.parseInt(port);
        }
        
        String maxIdle = prop.getProperty("redis.connect.maxIdle");
        if(isNotBlank(maxIdle)){
            this.maxIdle = Integer.parseInt(maxIdle);
        }
        String maxTotal = prop.getProperty("redis.connect.maxTotal");
        if(isNotBlank(maxTotal)){
            this.maxTotal = Integer.parseInt(maxTotal);
        }
        String maxWaitMillis = prop.getProperty("redis.connect.maxWaitMillis");
        if(isNotBlank(maxWaitMillis)){
            this.maxWaitMillis = Long.parseLong(maxWaitMillis);
        }
        String testOnBorrow = prop.getProperty("redis.connect.testOnBorrow");
        if(isNotBlank(testOnBorrow)){
            this.testOnBorrow = Boolean.parseBoolean(testOnBorrow);
        }
        String testWhileIdle = prop.getProperty("redis.connect.testWhileIdle");
        if(isNotBlank(testWhileIdle)){
            this.testOnBorrow = Boolean.parseBoolean(testWhileIdle);
        }
        
        this.requirePass = prop.getProperty("redis.connect.requirePass");
        
        initialPool(connectHost, connectPort,requirePass);
    } 
   
    /**
     * @Description: 单类模式获取instance的方法   
     * @param: @return      
     * @return: RedisManager      
     * @throws
     */
    public static RedisManager getRedisManagerInstance(){
        while(redisManager==null){
            synchronized(RedisManager.class){
                redisManager = new RedisManager();
            }
        }
        return redisManager;
    }
   
    /**
     * @Description: 判断字符串是否为空   
     * @param: @param str
     * @param: @return      
     * @return: boolean      
     * @throws
     */
    private boolean isNotBlank(String str){
        if(str != null && str.length() > 0 && str.trim().length() > 0){
            return true;
        }
        return false;
    }
    
    /**
     * 初始化非切片池
     */
    private void initialPool(String connectionUrl, int port, String requirePass) { 
        // 池基本配置 
        JedisPoolConfig config = new JedisPoolConfig(); 
        config.setMaxTotal(maxTotal);//总连接数
        config.setMaxIdle(maxIdle); 
        config.setMaxWaitMillis(maxWaitMillis);//10s
        config.setTestOnBorrow(testOnBorrow); 
        config.setTestWhileIdle(testWhileIdle);
        
        this.jedisPool = new JedisPool(config, connectionUrl, port);
    }
    
    /**
     * @Description: 获取Jedis实例
     * @param: @return      
     * @return: Jedis      
     * @throws
     */
    public Jedis getJedis() {
        Jedis jedis = null;
        try {
            if (jedisPool != null) {
                jedis = jedisPool.getResource();
                //设置连接密码
                jedis.auth(this.requirePass);
            }
        } catch (Exception e) {
            throw new RuntimeException("get jedis failed");
        }  
        return jedis;
    }
}

测试:

@Test
public void testRedisManager(){
    RedisManager redisManager1 = RedisManager.getRedisManagerInstance();

    Jedis jedis = redisManager1.getJedis();

    System.out.println(jedis.ping());

    //使用连接池后底层会自动归还连接
    jedis.close();
}

用完后直接使用jedis.close();方法即可向池归还连接,贴出close实现源码:

@Override
  public void close() {
    if (dataSource != null) {
      JedisPoolAbstract pool = this.dataSource;
      this.dataSource = null;
      if (client.isBroken()) {
        pool.returnBrokenResource(this);
      } else {
        pool.returnResource(this);
      }
    } else {
      super.close();
    }
  }

11.关于锁补充

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

悲观锁:每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别人修改,直接将整张表上锁,使用完成后进行数据解锁。由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待。

乐观锁(OptimisticLock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量乐观锁策略:

提交版本必须大于记录当前版本才能执行更新。

InnoDB的行锁模式及加锁方法

InnoDB实现了以下两种类型的行锁。

  • 共享锁(s):又称读锁。允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
  • 排他锁(X):又称写锁。允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。
  • 对于共享锁大家可能很好理解,就是多个事务只能读数据不能改数据。
    对于排他锁大家的理解可能就有些差别,我当初就犯了一个错误,以为排他锁锁住一行数据后,其他事务就不能读取和修改该行数据,其实不是这样的。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句:update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。

另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

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

推荐阅读更多精彩内容