20200610 千锋教育 Redis 3. Redis 客户端,与 SpringBoot 整合

Redis 客户端,与 SpringBoot 整合

常用的 Redis 客户端介绍以及对比

常用的 Redis 客户端:

  • Jedis

    • 是老牌的 Redis 的 Java 实现客户端,提供了比较全面的 Redis 命令的支持

    • 比较全面的提供了 Redis 的操作特性

    • 使用阻塞的 I/O ,且其方法调用都是同步的,程序流需要等到 Sockets 处理完 I/O オ能执行,不支持异步

    • 客户端实例不是线程安全的,所以需要通过连接池来使用 Jedis

  • Redisson

    • 实现了分布式和可扩展的 Java 数据结构。
    • 促使使用者对 Redis 的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过 Redis 支持延迟队列
    • 基于 Netty 框架的事件驱动的通信层,其方法调用是异步的。Redisson 的 API 是线程安全的,所以可以操年单个 Redisson 连接来完成各种操作
  • Lettuce

    • 高級 Redis 客户端,用于线程安全同步,异步和响应使用,支持集群, Sentinel,管道和编码器。
    • 基于 Netty 框架的事件驱动的通信层,其方法调用是异步的。 Lettuce 的 API 是线程安全的,所以可以操作单个 Lettuce 连接来完成各种操作
    • 基于 Netty 框架的事件驱动的通信层,其方法调用是异步的。 Lettuce 的 API 是线程安全的,所以可以操作单个 Lettuce 连接来完成各种操作
    • Lettuce 能够支持 Redis 4,需要 Java8 及以上 Lettuce是基于 Netty 实现的可以与 Redis 进行同步和异步的通信

总结:

首先,在 Spring Boot2 之后,对 Redis 连接的支持,默认就采用了 Lettuce。这就一定程度说明了 Lettuce 和 Jedis 的优劣。

优先使用 Lettuce,如果需要分布式锁,分布式集合等分布式的高级特性,添加 Redisson 结合使用,因为 Redisson 本身对字符串的操作支持很差。

在一些高并发的场景中,比如秒杀,抢票,抢购这些场景,都存在对核心资源,商品库存的争夺,控制不好,库存数量可能被减少到负数,出现超卖的情况,或者产生唯一的一个递増 ID,由于 web 应用部署在多个机器上,简单的同步加锁是无法实现的,给数据库加锁的话,对于高并发,1000/s 的并发,数据库可能由行锁变成表锁,性能下降会历害。那相对而言,Redis 的分布式锁,相对而言,是个很好的选择,Redis 官方推荐使用的 Redisson 就提供了分布式锁和相关服务。

SpringBoot 整合 Jedis

Redis 的命令名称对应 Jedis 的方法名称

  1. POM

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>studyJedis</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
    
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>${jedis.version}</version>
            </dependency>
    
        </dependencies>
    
    </project>
    
  2. YML

    server:
      port: 8090
    
    spring:
      redis:
        host: 192.168.181.128
        port: 6379
        password: 123456
        timeout: 2000       # 连接超时
        jedis:
          pool:
            max-idle: 6     # 最大空闲数
            max-active: 10  # 最大连接数
            min-idle: 2     # 最小空闲数
    
  3. 配置类

    package study.hwj.redis.config;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    
    @Slf4j
    @Configuration
    public class JedisConfig {
        @Value("${spring.redis.host}")
        private String host;
        @Value("${spring.redis.port}")
        private Integer port;
        @Value("${spring.redis.password}")
        private String password;
        @Value("${spring.redis.timeout}")
        private Integer timeout;
        @Value("${spring.redis.jedis.pool.max-idle}")
        private Integer maxIdle;
        @Value("${spring.redis.jedis.pool.max-active}")
        private Integer maxActive;
        @Value("${spring.redis.jedis.pool.min-idle}")
        private Integer minIdle;
    
        @Bean
        public JedisPool jedisPool() {
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            jedisPoolConfig.setMaxIdle(maxIdle);
            jedisPoolConfig.setMinIdle(minIdle);
            jedisPoolConfig.setMaxTotal(maxActive);
    
            JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password);
            log.info("JedisPool 连接成功:{} 	 {}", host, port);
            return jedisPool;
        }
    }
    
  4. 测试类,测试整合 Jedis 成功

    package study.hwj.redis;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    import redis.clients.jedis.JedisPool;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class JedisTest {
        @Autowired
        private JedisPool jedisPool;
    
        @Test
        public void contextLoads() {
            System.out.println(jedisPool);
        }
    }
    
  5. 业务类,使用 Jedis 操作 Redis 的 string 数据类型

    @Service
    @Slf4j
    public class UserService {
        
        @Autowired
        private JedisPool jedisPool;
        
        public String getValue(String key) {
            String retVal;
    
            // 得到 Jedis 对象
            Jedis jedis = jedisPool.getResource();
    
            // 判断在 Redis 中 key 是否存在
            if (jedis.exists(key)) {
                log.info("获取 【Redis】 中的数据");
                // 从 Redis 中获取数据
                retVal = jedis.get(key);
            } else {
                log.info("获取 【数据库】 中的数据");
                // 模拟从 数据库 中获取数据
                retVal = "2020-6-6";
    
                // 将数据存入 Redis
                jedis.set(key, retVal);
            }
    
            // 关闭连接
            jedis.close();
    
            return retVal;
        }
    }
    
  6. 工具类,改写业务类

    @Component
    public class JedisUtil {
        @Autowired
        private JedisPool jedisPool;
    
        public Jedis getJedis() {
            return jedisPool.getResource();
        }
    
        public void close(Jedis jedis) {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
    

    改写后的业务类:

    @Service
    @Slf4j
    public class UserService {
    
        @Autowired
        private JedisUtil jedisUtil;
    
        public String getValue(String key) {
            String retVal;
    
            // 得到 Jedis 对象
            Jedis jedis = jedisUtil.getJedis();
    
            // 判断在 Redis 中 key 是否存在
            if (jedis.exists(key)) {
                log.info("获取 【Redis】 中的数据");
                // 从 Redis 中获取数据
                retVal = jedis.get(key);
            } else {
                log.info("获取 【数据库】 中的数据");
                // 模拟从 数据库 中获取数据
                retVal = "2020-6-6";
    
                // 将数据存入 Redis
                jedis.set(key, retVal);
            }
    
            // 关闭连接
            jedisUtil.close(jedis);
    
            return retVal;
        }
    }
    
  7. 使用 Jedis 操作 Redis 的 hash 数据类型

    public User selectUserById(String id) {
        String key = "user:" + id;
    
        User user = new User();
    
        // 得到 Jedis 对象
        Jedis jedis = jedisUtil.getJedis();
    
        // 判断在 Redis 中 key 是否存在
        if (jedis.exists(key)) {
            log.info("获取 【Redis】 中的数据");
            // 从 Redis 中获取数据
            Map<String, String> map = jedis.hgetAll(key);
            user.setId(map.get("id"));
            user.setName(map.get("name"));
            user.setAge(Integer.valueOf(map.get("age")));
    
        } else {
            log.info("获取 【数据库】 中的数据");
            // 模拟从 数据库 中获取数据
            user.setId(key);
            user.setName("testName");
            user.setAge(18);
    
            Map<String,String> map = new HashMap();
            map.put("id", user.getId());
            map.put("name", user.getName());
            map.put("age", user.getAge().toString());
    
    
            // 将数据存入 Redis
            jedis.hset(key, map);
        }
    
        // 关闭连接
        jedisUtil.close(jedis);
    
        return user;
    }
    

SpringBoot 整合 Lettuce

SpringBoot2 默认采用了 Lettuce

默认的 RedisTemplate 没有配置序列化器,会使用 JDK 的序列化器,导致存入 Redis 中的内容乱码。

  1. POM

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>studyLettuce</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencies>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
                <version>2.2.2.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
    
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
                <version>${commons-pool2.version}</version>
            </dependency>
    
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
            </dependency>
    
        </dependencies>
    </project>
    
  2. YML

    server:
      port: 8090
    
    spring:
      redis:
        host: 192.168.181.128
        port: 6379
        password: 123456
        timeout: 2000       # 连接超时
        lettuce:
          pool:
            max-idle: 6     # 最大空闲数
            max-active: 10  # 最大连接数
            min-idle: 2     # 最小空闲数
          shutdown-timeout: 100   # 关闭超时时间
    
  3. 配置类

    @Configuration
    public class RedisConfig  {
    
        @Bean
        public RedisTemplate redisTemplate(LettuceConnectionFactory factory){
            RedisTemplate<Object, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(factory);
    
            Jackson2JsonRedisSerializer<Object> 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);
    
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    
            template.setKeySerializer(stringRedisSerializer);
            template.setValueSerializer(jackson2JsonRedisSerializer);
    
            template.setHashKeySerializer(stringRedisSerializer);
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
            return template;
        }
    
    }
    
    
  4. 业务类,操作 string 和 hash 类型

    @Service
    @Slf4j
    public class UserService {
        @Autowired
        private RedisTemplate redisTemplate;
    
        public void getRedisTemplate() {
            System.out.println(redisTemplate);
        }
    
        public String getString(String key) {
            String retVal;
    
            // 判断在 Redis 中 key 是否存在
            if (redisTemplate.hasKey(key)) {
                log.info("获取 【Redis】 中的数据");
                // 从 Redis 中获取数据
                retVal = (String) redisTemplate.opsForValue().get(key);
            } else {
                log.info("获取 【数据库】 中的数据");
                // 模拟从 数据库 中获取数据
                retVal = "2020-6-6";
    
                // 将数据存入 Redis
                redisTemplate.opsForValue().set(key, retVal);
            }
    
            return retVal;
        }
    
        public User selectUserById(String id) {
            String key = "user";
    
            User user = new User();
    
            // 判断在 Redis 中 key 是否存在
            if (redisTemplate.opsForHash().hasKey(key, id)) {
                log.info("获取 【Redis】 中的数据");
                // 从 Redis 中获取数据
                user = (User) redisTemplate.opsForHash().get(key, id);
            } else {
                log.info("获取 【数据库】 中的数据");
                // 模拟从 数据库 中获取数据
                user.setId(key);
                user.setName("testName");
                user.setAge(18);
    
                // 将数据存入 Redis
                redisTemplate.opsForHash().put(key, id, user);
            }
    
            return user;
        }
    }
    
  5. 优化业务类

    @Service
    @Slf4j
    public class UserService {
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Resource(name = "redisTemplate")
        private ValueOperations<String, String> valueOperations;
    
        @Resource(name = "redisTemplate")
        private HashOperations<String, String, User> hashOperations;
    
        public void getRedisTemplate() {
            System.out.println(redisTemplate);
        }
    
        public String getString(String key) {
            String retVal;
    
            // 判断在 Redis 中 key 是否存在
            if (redisTemplate.hasKey(key)) {
                log.info("获取 【Redis】 中的数据");
                // 从 Redis 中获取数据
                retVal = valueOperations.get(key);
            } else {
                log.info("获取 【数据库】 中的数据");
                // 模拟从 数据库 中获取数据
                retVal = "2020-6-6";
    
                // 将数据存入 Redis
                valueOperations.set(key, retVal);
            }
    
            return retVal;
        }
    
        public User selectUserById(String id) {
            String key = "user";
    
            User user = new User();
    
            // 判断在 Redis 中 key 是否存在
            if (redisTemplate.opsForHash().hasKey(key, id)) {
                log.info("获取 【Redis】 中的数据");
                // 从 Redis 中获取数据
                user = hashOperations.get(key, id);
            } else {
                log.info("获取 【数据库】 中的数据");
                // 模拟从 数据库 中获取数据
                user.setId(key);
                user.setName("testName");
                user.setAge(18);
    
                // 将数据存入 Redis
                hashOperations.put(key, id, user);
            }
    
            return user;
        }
    }
    
    
原文地址:https://www.cnblogs.com/huangwenjie/p/13088703.html