Lettuce与Jedis

Redis和Mysql一样是数据库,Mysql有对应的JDBC连接,Redis也有对应的Java客户端开发包,集成了Redis的一些命令操作,封装了Redis的java客户端,类似与redis-cli

前面:SpringBoot - 整合Redis:使用Jedis客户端通过Jedis、JedisPool使SpringBoot连接Redis

SpringBoot1.x的版本时默认使用的Jedis客户端,在SpringBoot2.x后,默认使用Lettuce

Lettuce与Jedis有什么区别?

  • Jedis在实现上是直接连接Redis Server,如果在多线程环境下是非线程安全的。每个线程都去拿自己的Jedis 实例,当连接数量增多时,资源消耗阶梯式增大,连接成本就较高(使用JedisPool会有一定的改善)
  • Lettuce的连接是基于Netty的,异步、多线程、事件驱动,连接实例可以在多个线程间共享,当多线程使用同一连接实例时,是线程安全的

Lettuce虽然更复杂,但更强(能被Spring生态承认)

redistemplate blpop 会影响redis吗_spring


RedisTemplate封装Lettuce

就如同JDBC可以连接Mysql数据库一样,通过Lettuce也可以连接操作Redis,通过调用lettuce-core核心jar包可以进行操作

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
</dependency>

关于Lettuce操作Redis可以看这位大佬的文章:Redis高级客户端Lettuce详解

但可以实现是一回事,方便又是一回事,用Lettuce操作Redis实际上是够呛的
Spring框架提供了对Lettuce的封装RedisTemplate,它让Spring框架体系可以更方便的操作Redis

RedisTemplate位于spring-data-redis:Spring官网相关,是一个高度封装的对象

相关配置:

  • 一个SpringBoot项目及Maven依赖

redistemplate blpop 会影响redis吗_Redis_02

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--redis依赖commons-pool-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  • application.yml配置:Redis与Lettuce的相关属性
spring:
  redis:
    host: IP地址
    password: Redis密码
    port: 6379
    timeout: 10000
    lettuce:
      pool:
      	# 连接池最大连接数
        max-active: 8
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池中的最小空闲连接
        min-idle: 2
        # 连接池最大阻塞等待时间
        max-wait: 1000
  • 配置类RedisConfig(可以不设置,这个的作用是设置序列化策略,后面解释)
    默认情况下的模板只能支持StringRedisTemplate<String,String>,只能存字符串,如果改成RedisTemplate<String,Object>就不行了

没有配置的话存的数据是:1111:RedisTemplate => lettuce,实际上Redis中键值对是这样的

redistemplate blpop 会影响redis吗_redis_03

package com.lettuce.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Author : zfk
 * Data : 16:27
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
        RedisTemplate<String,Object> template = new RedisTemplate<>();

        template.setConnectionFactory(factory);

        Jackson2JsonRedisSerializer jacksonSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jacksonSerializer.setObjectMapper(objectMapper);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //使用注解@Bean返回RedisTemplate时,同时配置hashKey与HashValue的序列化方式
        //key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //value序列化方式采用Jackson
        template.setValueSerializer(jacksonSerializer);

        //hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //hash的vlaue序列化采用Jackson
        template.setHashValueSerializer(jacksonSerializer);

        template.afterPropertiesSet();

        return template;
    }

}

一个简单的案例:通过key获得value

使用服务层即可:

UserService接口:

package com.lettuce.service;

import com.lettuce.po.User;

/**
 * Author : zfk
 * Data : 17:01
 */
public interface UserService {
    public String getString(String key);
}

实现类:UserServiceImpl

package com.lettuce.service;

import com.lettuce.po.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * Author : zfk
 * Data : 16:56
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService{

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    /**
     * Redis命令 ==》 Lettuce => RedisTemplate进一步的封装
     * redis String类型
     * 用户输入一个key,先判断Redis是否存在数据,存在就在Redis中查询,
     * 不存在就在Mysql数据库查询(模拟)。将结果返回给Redis
     */
    @Override
    public String getString(String key){

        log.info("RedisTemplate ==> 测试");

        String value = null;
        //hasKey 相当于 exist
        if (redisTemplate.hasKey(key)){
       		log.info("=== Redis查询到数据 ===");
            return (String) redisTemplate.opsForValue().get(key);
        }else {
            value = "RedisTemplate => lettuce";
            log.info("Redis没有查询到,存入:"+value);
            redisTemplate.opsForValue().set(key,value);
        }
        return value;

    }
}

测试类:LettuceApplicationTests

package com.lettuce;

import com.lettuce.po.User;
import com.lettuce.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class LettuceApplicationTests {

    @Autowired
    private UserService userService;
    @Test
    void contextLoads() {

        String string = userService.getString("1111");

        System.out.println(string);

    }
}

第一次没有在Redis查找到数据,将值存入Redis:

redistemplate blpop 会影响redis吗_spring Lettuc redis_04

再测试一次:Redis查找的了数据

redistemplate blpop 会影响redis吗_redis_05


序列化策略RedisSerializer<T>

前面我们自定义了配置类,当value为Object对象时的处理方法

Spring提供了以下序列化策略:

redistemplate blpop 会影响redis吗_redis_06

JdkSerializationRedisSerializer:POJO对象的存取场景,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。是目前最常用的序列化策略。


StringRedisSerializer:Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封装。是最轻量级和高效的策略。


Jackson2JsonRedisSerializer:jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。【需要jackson-mapper-asl工具支持】


OxmSerializer:提供了将javabean与xml之间的转换能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存储的数据将是xml工具。不过使用此策略,编程将会有些难度,而且效率最低;不建议使用。【需要spring-oxm模块的支持】

我们来解读一下编写的RedisConfig:

redistemplate blpop 会影响redis吗_Redis_07

在RedisTemplate类中可以看到:默认StringRedisSerializer序列化策略
内置了keySerializer、hashKeySerializer4个属性

redistemplate blpop 会影响redis吗_spring Lettuc redis_08

redistemplate blpop 会影响redis吗_spring Lettuc redis_09


操作类:operation

Spring针对Lettuce/Jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口

在RedisTemplate类中可以看到这些属性:

redistemplate blpop 会影响redis吗_redis_10

这些Operation对象对应Redis的数据类型:

  • ValueOperations:简单K-V操作
  • ListOperations:针对list类型的数据操作
  • SetOperations:set类型数据操作
  • StreamOperations:Stream是Redis 5.0引入的一种新数据类型,它以更抽象的方式模拟日志数据结构,但日志的本质仍然完好无损
  • ZSetOperations:zset类型数据操作
  • GeoOperations:地理位置信息
  • HyperLogLogOperations:HyperLogLog是用来做基数统计的算法
  • ClusterOperations:cluster集群

我们使用RedisTemplate操作:

redisTemplate.opsForValue().get(key);

本质上是操作Operation对象:

redistemplate blpop 会影响redis吗_Redis_11

所以如果不喜欢这种方式,可以直接用Operation操作:改造上面的getString方法

public class UserServiceImpl implements UserService{

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Resource(name = "redisTemplate")
    private ValueOperations<String,Object> valueOps;
    /**
     * Redis命令 ==》 Lettuce => RedisTemplate进一步的封装
     * redis String类型
     * 用户输入一个key,先判断Redis是否存在数据,存在就在Redis中查询,
     * 不存在就在Mysql数据库查询(模拟)。将结果返回给Redis
     */
    @Override
    public String getString(String key){

        log.info("RedisTemplate ==> 测试");

        String value = null;
        //hasKey 相当于 exist
        if (redisTemplate.hasKey(key)){
            log.info("=== Redis查询到数据 ===");
            return (String) valueOps.get(key);
        }else {
            value = "RedisTemplate => lettuce";
            log.info("Redis没有查询到,存入:"+value);
            valueOps.set(key,value);
        }
        return value;

    }
}

redistemplate blpop 会影响redis吗_redis_12


一个报错JsonParseException

如果Redis的键值对为:

redistemplate blpop 会影响redis吗_spring_13

取值时会报错:Json解析错误

Caused by: com.fasterxml.jackson.core.JsonParseException:
 Unrecognized token 'RedisTemplate': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
 at [Source: (byte[])"RedisTemplate => lettuce"; line: 1, column: 15]

redistemplate blpop 会影响redis吗_spring Lettuc redis_14

因为我们的配置template.setValueSerializer(jacksonSerializer);,value序列化方式采用Jackson

而Jackson将字符串转为json会带上双引号,我们取值时没有双引号就会报错

把数据修改一下:带上双引号就ok了

redistemplate blpop 会影响redis吗_redis_15


代码

码云