- 前言
- 第一部分 基础应用开发
- 第 1 章 Spring Boot 入门
- 第 2 章 在 Spring Boot 中使用数据库
- 第 3 章 Spring Boot 界面设计
- 第 4 章 提高数据库访问性能
- 第 5 章 Spring Boot 安全设计
- 第二部分 分布式应用开发
- 第 6 章 Spring Boot SSO
- 第 7 章 使用分布式文件系统
- 第 8 章 云应用开发
- 第 9 章 构建高性能的服务平台
- 第三部分 核心技术源代码分析
- 第 10 章 Spring Boot 自动配置实现原理
- 第 11 章 Spring Boot 数据访问实现原理
- 第 12 章 微服务核心技术实现原理
- 附录 A 安装 Neo4j
- 附录 B 安装 MongoDB
- 附录 C 安装 Redis
- 附录 D 安装 RabbitMQ
- 结束语
4.3 使用 Redis 做缓存
在数据库使用中,数据查询是最大的性能开销。如果能借助 Redis 作为辅助缓存,将可以极大地提高数据库的访问性能。使用 Redis 做缓存,一方面可以像第 2 章介绍的使用 Redis 那样调用,另一方面,可以使用注解的方式来调用,这种方式更加简单,代码也更加简洁。
需要注意的是,Redis 是一个具有持久化功能的数据库系统,若使用默认配置,存取的数据就会永久地保存在磁盘中。如果只是使用 Redis 来做缓存,并不需要 Redis 永久保存数据,可以设定在 Redis 保存数据的期限来实现,这样,过期的数据将自动从 Redis 数据库中清除。这不但能很好地利用 Redis 的快速存取功能,而且能彻底减轻 Redis 的负担。作为缓存使用的数据,最初就是从数据库中查询出来的,所以完全没有必要再做一次永久保存。始终让 Redis 保持轻装上阵,才能最好地发挥它的性能优势。
4.3.1 使用 Spring Cache 注解
结构简单的对象,即没有包含其他对象的实体,可以使用 Spring Cache 注解的方式来使用 Redis 缓存。要使用注解的方式调用缓存,必须在配置类中启用 Spring Cache,如代码清单 4-11 所示。其中 setDefaultExpiration 指定了数据在 Redis 数据库中的有效期限。
代码清单 4-11 Spring Cache 配置
@Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Bean public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) { RedisCacheManager manager = new RedisCacheManager(redisTemplate); manager.setDefaultExpiration(43200);// 12 小时 return manager; } }
这样,就可以在对数据接口的调用中,对增删查改加入如代码清单 4-12 所示的注解,自动增加缓存的创建、修改和删除等功能。其中注解 @Cacheable 为存取缓存,注解 @CachePut 为修改缓存,如果不存在则创建,注解 @CacheEvict 为删除缓存,当删除数据时,如果缓存还存在,就必须删除,各个注解中的 value 参数是一个 key 的前缀,并由 keyGenerator 按一定的规则生成一个唯一标识。
代码清单 4-12 用注解方式使用 Redis 做缓存
@Service public class RoleService { @Autowired private RoleRepository roleRepository; @Autowired private RoleRedis roleRedis; @Cacheable(value = "mysql:findById:role", keyGenerator = "simpleKey") public Role findById(Long id) { return roleRepository.findOne(id); } @CachePut(value = "mysql:findById:role", keyGenerator = "objectId") public Role create(Role role) { return roleRepository.save(role); } @CachePut(value = "mysql:findById:role", keyGenerator = "objectId") public Role update(Role role) { return roleRepository.save(role); } @CacheEvict(value = "mysql:findById:role", keyGenerator = "simpleKey") public void delete(Long id) { roleRepository.delete(id); } ...... }
对于 key 的生成规则,使用如代码清单 4-13 所示的方法来实现,这里主要使用了调用者本身对象的 ID 属性来保证它的唯一性,其中 simpleKey 和 objectId 都是提取调用者本身的类名字和参数 id 作为唯一标识。
代码清单 4-13 生成 cache 的 key
@Bean public KeyGenerator simpleKey(){ return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()+":"); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } @Bean public KeyGenerator objectId(){ return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params){ StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()+":"); try { sb.append(params[0].getClass().getMethod("getId", null). invoke(params[0], null).toString()); }catch (NoSuchMethodException no){ no.printStackTrace(); }catch(IllegalAccessException il){ il.printStackTrace(); }catch(InvocationTargetException iv){ iv.printStackTrace(); } return sb.toString(); } }; }
4.3.2 使用 RedisTemplate
由于使用 Spring Cache 注解的方法使用 Redis 缓存,只能对简单对象进行系列化操作,所以对于像实体 User 这样的包含了一定关系的复杂对象,或其他集合、列表对象等,就不能使用简单注解的方法来实现了,还要像第 2 章中介绍的方法那样使用 RedisTemplate 来调用 Redis,其使用的效果和上面使用 Cache 注解的效果相同,只不过实现方法完全不同。
代码清单 4-14 使用 RedisTemplate 实现了对 Redis 的调用。这种方式可以很方便地对列表对象进行系列化,在数据存取时使用 Json 进行格式转换。这里使用分钟作为时间单位来设定数据在 Redis 中保存的有效期限。
代码清单 4-14 使用 RedisTemplate
@Repository public class UserRedis { @Autowired private RedisTemplate<String, String> redisTemplate; public void add(String key, Long time, User user) { Gson gson = new Gson(); redisTemplate.opsForValue().set(key, gson.toJson(user), time, TimeUnit. MINUTES); } public void add(String key, Long time, List<User> users) { Gson gson = new Gson(); redisTemplate.opsForValue().set(key, gson.toJson(users), time, TimeUnit. MINUTES); } public User get(String key) { Gson gson = new Gson(); User user = null; String json = redisTemplate.opsForValue().get(key); if(!StringUtils.isEmpty(json)) user = gson.fromJson(json, User.class); return user; } public List<User> getList(String key) { Gson gson = new Gson(); List<User> ts = null; String listJson = redisTemplate.opsForValue().get(key); if(!StringUtils.isEmpty(listJson)) ts = gson.fromJson(listJson, new TypeToken<List<User>>(){}.getType()); return ts; } public void delete(String key){ redisTemplate.opsForValue().getOperations().delete(key); } }
然后编写如代码清单 4-15 所示的代码来使用 Redis 缓存。即在使用原来数据库的增删查改过程中,同时使用 Redis 进行辅助存取,以达到提升访问速度的目的,从而缓解对原来数据库的访问压力。这样,访问一条数据时,首先从 Redis 读取,如果存在则不再到 MySQL 中读取,如果不存在再到 MySQL 读取,并将读取的结果暂时保存在 Redis 中。
代码清单 4-15 在数据服务中使用 Redis 作为辅助缓存
@Service public class UserService { @Autowired private UserRepository userRepository; @Autowired private UserRedis userRedis; private static final String keyHead = "mysql:get:user:"; public User findById(Long id) { User user = userRedis.get(keyHead + id); if(user == null){ user = userRepository.findOne(id); if(user != null) userRedis.add(keyHead + id, 30L, user); } return user; } public User create(User user) { User newUser = userRepository.save(user); if(newUser != null) userRedis.add(keyHead + newUser.getId(), 30L, newUser); return newUser; } public User update(User user) { if(user != null) { userRedis.delete(keyHead + user.getId()); userRedis.add(keyHead + user.getId(), 30L, user); } return userRepository.save(user); } public void delete(Long id) { userRedis.delete(keyHead + id); userRepository.delete(id); }
上面使用 Redis 缓存的两种方法,可以在一个应用中混合使用。但不管怎么使用,对于控制器来说都是完全透明的,控制器对数据接口的调用还是像以前一样,它并不清楚数据接口后端是否启用了缓存,如代码清单 4-16 所示。
代码清单 4-16 控制器使用数据接口
@Autowired private UserService userService; @RequestMapping(value="/{id}") public String show(ModelMap model,@PathVariable Long id) { User user = userService.findById(id); model.addAttribute("user",user); return "user/show"; }
使用缓存之后,大量的查询语句就从原来的数据库服务器中,转移到了高效的 Redis 服务器中执行,这将在很大程度上减轻原来数据库服务器的压力,并且提升查询的反应速度和效率。所以在很大的程度上,系统性能就得到了很好的改善。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论