Redis的Java客户端—Jedis和Lettuce

Jedis连接Redis

1.添加Jedis依赖

<dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
   <version>3.1.0</version>
</dependency>

2.在虚拟机端配置:将bind注释掉,然后改protected-mode为no

改了之后保存并重启Redis。

3.使用Jedis提供的Jedis这个工具类来连接Jedis,首先在虚拟机使用ifconfig命令查看虚拟机的ip,然后向Redis发送一个ping命令,测试一下是否可以连接上远程的Redis:

public class RedisTest {

    private Jedis jedis = null;

    @Before
    public void getConnection() {
        jedis = new Jedis("192.168.92.128", 6379);
    }

    /**
     * 测试和虚拟机上的Redis的连通性
     */
    @Test
    public void pingTest() {
        System.out.println(jedis.ping());
    }
}

可以连接上Redis的标志是程序运行后打印”PONG“

如果程序运行出现了JedisConnectionException,这种情况要么是你代码中把ip或端口写错了,要不就是由于Linux的防火墙导致的,在你确定你没有写错ip或端口的前提下,你可以直接关闭防火墙或者为了安全你可以开放6379这个端口给远程,Centos 7上开放端口有关的命令操作如下:

# 开放6379端口
firewall-cmd --permanent --add-port=6379/tcp
# 查询端口是否开放,yes就是开放的
firewall-cmd --query-port=6379/tcp
# 重新加载配置
firewall-cmd --reload
# 移除指定开放的端口
firewall-cmd --permanent --remove-port=6379/tcp
Jedis常用API

Jedis操作Redis的常用API几乎和Redis的命令是一样的,比如操作String:

   /**
     * String类型的数据接的测试
     */
    @Test
    public void testString() {

        //set  添加key和value   如果已经有了就覆盖
        jedis.set("k1", "111");
        jedis.set("k2", "222");
        jedis.set("k3", "333");

        //get  获取值
        System.out.println(jedis.get("k1") + " " + jedis.get("k2") + " " + jedis.get("k3"));

        //set可以设置参数:EX(秒)/PX(毫秒)-->过期时间    NX 不存在时操作    XX存才时操作
        jedis.set("k4", "444", new SetParams().nx());   //key不存在的时候进行操作
        jedis.set("k3", "123", new SetParams().xx());    //key存在的时候操作
        jedis.set("k5", "345", new SetParams().ex(60));   //设置key的生命时间,单位s
        jedis.set("k6", "666", new SetParams().px(10000));  //设置key的生命时间,单位ms

        //append  追加内容
        jedis.append("k1", "hello");    //追加

        //STRLEN
        System.out.println(jedis.get("k1") + ",v1的长度:" + jedis.strlen("k1"));

        //INCR INCRBY  DECR DECRBY
        jedis.incr("k6");     //加1
        jedis.incrBy("k5", 100);   //加任意增量
        jedis.decr("k4");   //减1
        jedis.decrBy("k3", 100);   //减去任意减量

        System.out.println(jedis.get("k3") + " " + jedis.get("k4") + " " + jedis.get("k5") + " " + jedis.get("k6") + " ");

        //GETRANGE   获得字符串的一部分
        System.out.println(jedis.getrange("k1", 0, -1));
        //SETRANGE  设置字符串的一部分
        jedis.setrange("k1", 0, "redis");

        System.out.println(jedis.get("k1"));

        jedis.mset("msg1", "hello", "msg2", "error", "msg3", "success");
        System.out.println(jedis.mget("msg1", "msg2", "msg3"));
}

可以看到通过Jedis操作Redis所调用的API和Redis的命令是一样的,所以只要熟悉Redis的关于5大常用数据类型的命令,那么使用Jedis操作Redis就没有大的问题。如果你还不是熟悉Redis的关于5大常用数据类型的命令,可以参考我的这篇笔记:Redis五大常用数据类型

Jedis事务

Redis中和事务有关的命令:mulitexecdiscardwatchunwatch,然而Jedis中操作Redis事务的API也和这几个命令是一样的,比如我们实现一个简单的事务:

   @Test
    public void tx(){
        Jedis jedis=new Jedis("192.168.92.128", 6379);
        Transaction transaction = jedis.multi();//开启事务
        //命令入队
        transaction.set("k1","v1");
        transaction.set("k2","v2");
        transaction.get("k1");
        //执行事务
        List<Object> exec =transaction.exec();
        //transaction.discard();   //取消事物
        //打印执行事务后的返回结果
        exec.forEach(ele->System.out.println(ele));
    }

有两个关键字balance表示信用卡的余额(初始值为1000),debt表示信用卡的欠额(初始值为0),使用redis提供的乐观锁watch来实现对消费的记录

package com.xust.iot;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.util.List;

public class TxTest {


    private Integer balance;   //账户余额
    private Integer debt;     //账户欠额
    private static Jedis jedis = new Jedis("192.168.92.128", 6379);
    private static final int autoDECR = 10;   //模拟每次消费的金额

    public boolean coustmer() {

        try {
            String balance = jedis.watch("balance");//开启监控
            //jedis.mset("balance","100","debt","400");    //模拟另一个线程在本线程开启了对balance监控以后改变了balance的值
            this.balance = Integer.parseInt(jedis.get("balance"));
            List<Object> exec =null;
            if (this.balance > autoDECR) {
                Transaction transaction = jedis.multi();    //开启事务
                transaction.decrBy("balance", autoDECR);
                transaction.incrBy("debt", autoDECR);
                exec = transaction.exec();  //提交事务,执行事务后会自动unwatch
            } else {
                jedis.unwatch();
            }
            if(null!=exec){
                System.out.println("操作成功:");
                System.out.println("余额:"+jedis.get("balance") + " 欠额:" + jedis.get("debt"));
                return true;
            }else{
                System.out.println("操作失败,监控到balance发生改动");
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
          jedis.close();    //关闭连接
        }

        return false;
    }


    public static void main(String[] args) {
        TxTest txTest = new TxTest();
        while (!txTest.coustmer());   //自旋,直到执行成功为止
    }
}

正常情况下(就是没有别的线程干扰):

异常情况下:

解释一下这两种不同的结果:
当开启监控后,如果期间别的线程把监控的关键字的值改变了,那么Redis就会在本次事务期间不执行任何操作,即使使用exec提交事务了,也不会执行(这时返回exec的返回值是null),这种基于CAS的监控,不仅保证了共享数据的安全,而且还提高了响应速速。这也正是程序所体现的,当jedis.mset(“balance”,“100”,“debt”,“400”); 这条语句被注释掉以后,程序可以正常执行,执行后返回true,程序结束;当jedis.mset(“balance”,“100”,“debt”,“400”);语句没有注释以后,在开启watch以后,相当于别的进程改变了监控关键字的值,那么这时Redis就不会在执行事务了,exec就会返回false,然后 while (!txTest.coustmer()); 就又再次调用方法,直到执行成功,然后结束程序。

JedisPool连接池

类似于mysql的数据库连接池c3p0、Durid等,JedisPool是java连接Redis的连接池,基本的使用方式如下:

public class RedisTest2 {
    public static void main(String[] args) {
        // 比较特殊的是,redis连接池的配置首先要创建一个连接池配置对象
        JedisPoolConfig config = new JedisPoolConfig();
        // 当然这里还有设置属性的代码

        // 创建Jedis连接池对象
        JedisPool jedisPool = new JedisPool(config,"localhost",6379);

        // 获取连接
        Jedis jedis = jedisPool.getResource();

        // 使用

        // 关闭,归还连接到连接池
        jedis.close();
    }
}

一般我们可以各种配置的代码抽取出来写一个工具类,下面是一个基于单例模式的JedisPoolUtils:
首先我们需要一个redis.properties的配置文件,用于配置JedisPool的一些属性:

redis.host=192.168.92.128
redis.port=6379
redis.maxTotal=50
redis.maxIdle=10

JedisPoolUtils.java

public class JedisPoolUtils {

    private static  volatile JedisPool jedisPool=null;

    public JedisPoolUtils(){}

    public static JedisPool getJedisPool(){
        if(null==jedisPool){
            synchronized (JedisPoolUtils.class){
                if(null==jedisPool){
                    try {
                        //设置各种属性
                        JedisPoolConfig poolConfig = new JedisPoolConfig();
                        ResourceBundle rs = ResourceBundle.getBundle("redis");
                        poolConfig.setMaxIdle(Integer.parseInt(rs.getString("redis.maxIdle")));
                        poolConfig.setMaxTotal(Integer.parseInt(rs.getString("redis.maxTotal")));
                        jedisPool = new JedisPool(poolConfig, rs.getString("redis.host"),Integer.parseInt(rs.getString("redis.port")));
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
        return jedisPool;
    }
}

然后测试一下我们的JedisPoolUtils工具类:

public class JedisPoolTest {

    public static void main(String[] args){
        JedisPool jedisPool = JedisPoolUtils.getJedisPool();
        Jedis jedis = jedisPool.getResource();
        jedis.set("msg","jedisPopl test success!");
        System.out.println(jedis.get("msg"));
        jedis.close();  //把jedis放回池子
    }
}

测试结果:

SpringBoot连接Redis

导入redis的相关依赖
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> 
在application.yml中配置redis的有关连接信息
spring:
  #Redis的有关配置
  redis:
    host: 192.168.92.128
    port: 6379
    password:
	#在SpirngBoot1.x中配置Jedis作为连接Redis的客户端
    jedis:
      pool:
        max-wait: 200
        max-idle: 10
        max-active: 10
    timeout: 2000
	

---
spring:
  redis:
    host: 192.168.92.145
    port: 6379
    #password: CySynWsw5NLzbzHne3XcXaRGPWKpJxSKsHikLYvQ6VI0dNKjScuyYPjt
    database: 0
    timeout: 10000
    #springBoot 2.x应该配置lettuce作为Redis客户端  Spring1.x配置jedis
    lettuce:
      pool:
        max-active: 8
        min-idle: 0
        max-idle: 8
        max-wait: 5000ms
      shutdown-timeout: 100ms
	
自定义RedisTemplate

       SpringBoot自动帮我们在容器中生成了一个RedisTemplate和一个StringRedisTemplate。我们可以使用RedisTemplate来像Jedis一样操作Redis。但是,这个RedisTemplate的泛型是<Object,Object>,写代码不方便,需要写好多类型转换的代码;我们需要一个泛型为<String,Object>形式的RedisTemplate。并且,这个RedisTemplate没有设置数据存在Redis时,key及value的序列化方式。

        看到这个@ConditionalOnMissingBean注解后,就知道如果Spring容器中有了RedisTemplate对象了,这个自动配置的RedisTemplate不会实例化。因此我们可以直接自己写个配置类,配置RedisTemplate。

RedisConfig.java

@Configuration
public class RedisConfig {


    /***
     * 自定义的redisTemplate
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<Object, String> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<Object, String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //默认的序列化方式:jackson
        redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setKeySerializer(RedisSerializer.string());
        //设置了默认的序列化就不需要在设置下面的了,因为afterPropertiesSet()中会设置为默认的序列化方式
        //redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        //redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
        //redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

留言区

还能输入500个字符