Redis之三Redis的Java客户端Jedis
in with 2 comment

Redis之三Redis的Java客户端Jedis

in with 2 comment

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单机版连接池工具类,一般集群会使用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实现了以下两种类型的行锁。

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