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 的方法名称
-
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>
-
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 # 最小空闲数
-
配置类
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 连接成功:{} \t {}", host, port); return jedisPool; } }
-
测试类,测试整合 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); } }
-
业务类,使用 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; } }
-
工具类,改写业务类
@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; } }
-
使用 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 中的内容乱码。
-
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>
-
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 # 关闭超时时间
-
配置类
@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; } }
-
业务类,操作 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; } }
-
优化业务类
@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; } }