一, 简介
在 [补习系列(A3)-springboot Redis 与发布订阅]() 一文中, 我们介绍了使用 Redis 实现消息订阅发布的机制, 并且给出了一个真实用例.
然而, 绝大多数场景下 Redis 是作为缓存被使用的(这是其主要优势). 除此之外, 由于 Redis 提供了 AOF 以及 RDB 两种持久化机制, 某些情况下也可以作为临时数据库使用.
本次将介绍 SpringBoot 中如何使用 Redis 进行缓存读写.
Redis 的基本命令
在学习之前, 需要先了解一些 Redis 的基本命令, 可以参考这里 http://www.redis.cn/
http://www.redis.cn/
二, SpringBoot Redis 读写
A. 引入 spring-data-Redis
添加依赖
- <!-- redis -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-Redis</artifactId>
- <version>${spring-boot.version}</version>
- </dependency>
spring-boot-starter-Redis 在 1.4 版本已经废弃
配置 Redis 连接
- application.properties
- # Redis 连接配置
- spring.Redis.database=0
- spring.Redis.host=127.0.0.1
- spring.Redis.password=
- spring.Redis.port=6379
- spring.Redis.ssl=false
- # 连接池最大数
- spring.Redis.pool.max-active=10
- # 空闲连接最大数
- spring.Redis.pool.max-idle=10
- # 获取连接最大等待时间(s)
- spring.Redis.pool.max-wait=600000
B. 序列化
同样, 我们需要指定 JSON 作为 Key/HashKey/Value 的主要方式:
- /**
- * 序列化定制
- *
- * @return
- */
- @Bean
- public Jackson2JsonRedisSerializer<Object> jackson2JsonSerializer() {
- Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
- Object.class);
- // 初始化 objectmapper
- ObjectMapper mapper = new ObjectMapper();
- mapper.setSerializationInclusion(Include.NON_NULL);
- mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
- jackson2JsonRedisSerializer.setObjectMapper(mapper);
- return jackson2JsonRedisSerializer;
- }
- /**
- * 操作模板
- *
- * @param connectionFactory
- * @param jackson2JsonRedisSerializer
- * @return
- */
- @Bean
- public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory connectionFactory,
- Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer) {
- RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
- template.setConnectionFactory(connectionFactory);
- // 设置 key/hashkey 序列化
- RedisSerializer<String> stringSerializer = new StringRedisSerializer();
- template.setKeySerializer(stringSerializer);
- template.setHashKeySerializer(stringSerializer);
- // 设置值序列化
- template.setValueSerializer(jackson2JsonRedisSerializer);
- template.setHashValueSerializer(jackson2JsonRedisSerializer);
- template.afterPropertiesSet();
- // template.setValueSerializer(new
- // GenericToStringSerializer<Object>(Object.class));
- return template;
- }
Jackson2JsonRedisSerializer 是 Jackson 转换的桥接器;
RedisTemplate 是用于读写的主要操作类;
C. 读写样例
首先定义一个 Pet 实体类
- public class RedisPet {
- private String name;
- private String type;
- ... ignore get set
利用 RedisTemplate 封装一层 Repository, 如下:
- @Repository
- public static class PetRepository {
- private static final String KEY = "Pets";
- @Autowired
- private RedisTemplate<String, Object> redisTemplate;
- private HashOperations<String, String, Object> hashOperations;
- @PostConstruct
- private void init() {
- hashOperations = redisTemplate.opsForHash();
- }
- public void add(RedisPet pet) {
- hashOperations.put(KEY, pet.getName(), pet);
- }
- public RedisPet find(String name) {
- return (RedisPet) hashOperations.get(KEY, name);
- }
- public Map<String, Object> findAll() {
- return hashOperations.entries(KEY);
- }
- public void clear() {
- hashOperations.getOperations().delete(KEY);
- }
- }
在 PetRepository 的实现中, 我们利用 Hash 结构来存储 Pet 信息(Pet.name 是 key)
分别实现了添加 (add)/ 查找(get)/ 清除(clear) 等方法.
最后, 实现读写调用:
- @Service
- public class RedisDataOperation {
- private static final Logger logger = LoggerFactory.getLogger(RedisDataOperation.class);
- @Autowired
- private PetRepository petRepo;
- @PostConstruct
- public void start() {
- RedisPet pet1 = new RedisPet("Polly", "Bird");
- RedisPet pet2 = new RedisPet("Tom", "Cat");
- // 写入宠物信息
- petRepo.add(pet1);
- petRepo.add(pet2);
- // 打印宠物信息
- logger.info("polly {}", JsonUtil.toJson(petRepo.find("Polly")));
- logger.info("pets {}", JsonUtil.toJson(petRepo.findAll()));
- // 清空
- petRepo.clear();
- }
上面的代码在应用启动时, 会写入两个 Pet 信息, 之后完成清理, 控制台输出如下:
- RedisDataOperation : polly {
- "name":"Polly","type":"Bird"
- }
- RedisDataOperation : pets {
- "Tom":{
- "name":"Tom","type":"Cat"
- },"Polly":{
- "name":"Polly","type":"Bird"
- }
- }
三, 方法级缓存
除了上面的 RedisTemplate,spring-data-Redis 还提供了方法级缓存,
就是将业务方法的执行结果缓存起来, 后面再次调用直接从缓存中取得结果返回.
这种方式可以简化缓存逻辑的代码, 比如配置类数据的读取, 通过方法注解就可以实现,
下面是一个样例:
- /**
- * 方法级缓存样例
- *
- * @author atp
- *
- */
- @Service
- public class RedisCacheOperation {
- private static final Logger logger = LoggerFactory.getLogger(RedisCacheOperation.class);
- public static final String PREFIX = "pets:";
- public static final String WRAP_PREFIX = "'pets:'";
- /**
- * 当结果不为空时缓存
- *
- * @param name
- * @return
- */
- @Cacheable(value = "petCache", key = WRAP_PREFIX + "+#name", unless = "#result==null")
- public RedisPet getPet(String name) {
- logger.info("get pet {}", name);
- return new RedisPet(name, "Bird");
- }
- /**
- * 当结果不为空时淘汰缓存
- *
- * @param pet
- * @return
- */
- @CacheEvict(value = "petCache", key = WRAP_PREFIX + "+#pet.name", condition = "#result!=null")
- public RedisPet updatePet(RedisPet pet) {
- logger.info("update pet {}", pet.getName());
- return new RedisPet(pet.getName(), "Bird1");
- }
- /**
- * 当结果为 true 时淘汰缓存
- *
- * @param name
- * @return
- */
- @CacheEvict(value = "petCache", key = WRAP_PREFIX + "+#name", condition = "#result==true")
- public boolean deletePet(String name) {
- logger.info("delete pet {}", name);
- return true;
- }
- }
涉及到几个注解:
注解 | 说明 |
---|---|
@Cachable | 方法执行结果缓存 |
@CachePut | 方法执行结果缓存 (强制) |
@CacheEvict | 方法执行时触发删除 |
其中 @CachePut 与 @Cachable 的区别在于, 前者一定会执行方法, 并尝试刷新缓存(条件满足),
而后者则是当缓存中不存在时才会执行方法并更新.
注解中的属性 key/condition 都支持通过 Spring EL 表达式来引用参数对象.
启用注解
除了上面的代码, 我们还需要使用 @EnableCaching 启用注解:
- @EnableCaching
- @Configuration
- public class RedisConfig {
- private static final Logger logger = LoggerFactory.getLogger(RedisConfig.class);
- /**
- * 缓存管理, 支持方法级注解
- *
- * @param template
- * @return
- */
- @Bean
- public RedisCacheManager cacheManager(RedisTemplate<String, Object> template) {
- RedisCacheManager redisCacheManager = new RedisCacheManager(template);
- // 默认过期时间
- redisCacheManager.setDefaultExpiration(30 * 60 * 1000);
- return redisCacheManager;
- }
当 @Cacheable 的 key 属性为空时, 框架会自动生成, 格式类似:
param1,param2,param3...
如果希望修改默认的行为, 可以使用自定义的 KeyGenerator:
- /**
- * 定制方法缓存的 key 生成策略
- *
- * @return
- */
- @Bean
- public KeyGenerator keyGenerator() {
- return new KeyGenerator() {
- @Override
- public Object generate(Object target, Method method, Object... args) {
- StringBuilder sb = new StringBuilder();
- sb.append(target.getClass().getName());
- sb.append(method.getName());
- for (Object arg : args) {
- sb.append(arg.toString());
- }
- return sb.toString();
- }
- };
- }
单元测试
使用一小段单元测试代码来测试方法级缓存功能
- @RunWith(SpringRunner.class)
- @SpringBootTest(classes =BootSampleRedis.class)
- public class RedisCacheOperationTest {
- private static final Logger logger = LoggerFactory.getLogger(RedisCacheOperationTest.class);
- @Autowired
- private RedisTemplate<String, Object> redisTemplate;
- @Autowired
- private RedisCacheOperation operation;
- private RedisPet pet1 = new RedisPet("Polly", "Bird");
- @Test
- public void testGet() {
- operation.getPet(pet1.getName());
- Object object = redisTemplate.opsForValue().get(RedisCacheOperation.PREFIX + pet1.getName());
- logger.info(String.valueOf(object));
- assertNotNull(object);
- }
- @Test
- public void testUpdate() {
- operation.updatePet(pet1);
- Object object = redisTemplate.opsForValue().get(RedisCacheOperation.PREFIX + pet1.getName());
- logger.info(String.valueOf(object));
- assertNull(object);
- }
- @Test
- public void testDelete() {
- operation.getPet(pet1.getName());
- // delete cache
- operation.deletePet(pet1.getName());
- Object object = redisTemplate.opsForValue().get(RedisCacheOperation.PREFIX + pet1.getName());
- logger.info(String.valueOf(object));
- assertNull(object);
- }
- }
四, 连接池
如果希望通过代码来配置 Jedis 的连接池(熟悉的方式), 可以声明 JedisConnectionFactory 实现:
- /**
- * 连接池配置
- *
- * @return
- */
- @Bean
- public JedisConnectionFactory jedisConnectionFactory() {
- JedisPoolConfig config = new JedisPoolConfig();
- // 最大连接
- config.setMaxTotal(10);
- // 最大空闲, 与最大连接保持一致, 可减少频繁键链的开销
- config.setMaxIdle(10);
- // 连接最大空闲时间
- config.setMinEvictableIdleTimeMillis(10 * 60 * 1000);
- // 获取连接等待的最大时长
- config.setMaxWaitMillis(30000);
- // 进行空闲连接检测的时间间隔
- config.setTimeBetweenEvictionRunsMillis(30 * 1000);
- // 取消不必要的 test, 有利于性能提升
- config.setTestOnBorrow(false);![](https://img2018.cnblogs.com/blog/242916/201812/242916-20181206231048870-1133770725.png)
- config.setTestOnReturn(false);
- JedisConnectionFactory factory = new JedisConnectionFactory(config);
- factory.setHostName("127.0.0.1");
- factory.setPort(6379);
- logger.info("redis config init first");
- return factory;
- }
更多配置可参考这里
示例代码可从 码云 gitee 下载.
https://gitee.com/littleatp/springboot-samples/
小结
Redis 在大多数项目中的核心用途是缓存, spring-data-Redis 为 SpringBoot 中集成 Redis 读写的封装.
除了 RedisTemplate 之外, 还实现了方法级的缓存注解, 一定程度上简化了业务的使用.
Redis 在分布式系统中的应用场景有很多, 后续有机会将进行更多的探讨.
来源: https://yq.aliyun.com/articles/675397