SpringBoot 集成 layering-cache 实现两级缓存调研与实践

前言

对于系统查多改少的数据,可以通过缓存来提升系统的访问性能。一般情况下我们会采用 Redis ,但是如果仅仅依赖 Redis 很容易出现缓存雪崩的情况。为了防止缓存雪崩可以通过 Redis 高可用,主从+哨兵解决方案、本地 ehcache 缓存 + hystrix 限流&降级、Redis 持久化 等手段有效的防止缓存雪崩。其中同时使用本地缓存和Redis 缓存就是两级缓存。本文主要介绍的内容就是:在SpringBoot 项目下实现两级缓存,具体的介绍内容如下:

  • 什么是缓存雪崩?
  • 什么是两级缓存?
  • SpringBoot 实现两级缓存解决方案。
  • SpringBoot 集成 layering-cache 实现两级缓存

什么是缓存雪崩?

对于不了解什么是缓存雪崩小伙伴,这里在简单的介绍一下。如果您了解什么是缓存雪崩可以绕过该小节。

对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。

这就是缓存雪崩。

redis-caching-avalanche

为了防止缓存雪崩,一般会采用方式如下:

事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。
事中:本地 Ehcache 缓存 + Hystrix 限流&降级,避免 MySQL 被打死。
事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

缓存雪崩介绍引用 https://github.com/shishan100/Java-Interview-Advanced 中华石杉–互联网Java进阶面试训练营

什么是两级缓存?

一级缓存就是:本地缓存,二级缓存就是:Redis。当访问请求过来的时候,先从一级缓存获取数据,如果一级缓存不存在则从二级缓存中获取,从二级缓存获取到数据后在将其设置到一级缓存。

为什么两级缓存可以防止缓存雪崩?

当Redis 宕机后请求会获取本地缓存而不是直接走MySql 来防止缓存雪崩。还有一点是:
Redis缓存操作会有一大部分的网络开销,使用本地缓存可以避免网络开销,提升系统的性能。

SpringBoot 实现两级缓存解决方案

Spring Cache

Spring Cache 是 Spring 默认提供的缓存解决方案,如果是单独使用 一级或二级缓存,比较容易实现。但是如果结合使用,需要自己处理缓存同步、二级缓存等功能。

Alibaba JetCache 框架

JetCache是一个基于Java的缓存系统封装,提供统一的API和注解来简化缓存的使用。 JetCache提供了比SpringCache更加强大的注解,可以原生的支持TTL、两级缓存、分布式自动刷新,还提供了Cache接口用于手工缓存操作。 当前有四个实现,RedisCache、TairCache(此部分未在github开源)、CaffeineCache(in memory)和一个简易的LinkedHashMapCache(in memory),要添加新的实现也是非常简单的。

缺点是:JetCache 没有提供默认 一级缓存和二级缓存同步,需要自己手动实现。

Layering Cache 框架

layering-cache是一个支持分布式环境的多级缓存框架,使用方式和spring-cache类似,主要目的是在使用注解的时候支持配置过期时间。layering-cache其实是一个两级缓存,一级缓存使用Caffeine作为本地缓存,二级缓存使用redis作为集中式缓存。并且基于redis的Pub/Sub做缓存的删除,所以它是一个适用于分布式环境下的一个缓存系统。

支持
支持缓存监控统计
支持缓存过期时间在注解上直接配置
支持二级缓存的自动刷新(当缓存命中并发现缓存将要过期时会开启一个异步线程刷新缓存)
刷新缓存分为强刷新和软刷新,强刷新直接调用缓存方法,软刷新直接改缓存的时间
缓存Key支持SpEL表达式
新增FastJsonRedisSerializer,KryoRedisSerializer序列化,重写String序列化。
支持同一个缓存名称设置不同的过期时间
输出INFO级别的监控统计日志
二级缓存是否允许缓存NULL值支持配置
二级缓存空值允许配置时间倍率

综合 Spring Cache、Alibaba JetCache、Layering Cache 个人推荐使用功能更全的 Layering Cache,接下来正式开启 SpringBoot 集成 layering-cache 实战 !

SpringBoot 集成 layering-cache 实战

application.properties 配置

spring.layering-cache.stats=true
spring.layering-cache.namespace=layering-cache
spring.layering-cache.layeringCacheServletEnabled=true
spring.layering-cache.urlPattern=/layering-cache/*
spring.layering-cache.loginUsername=admin
spring.layering-cache.loginPassword=123456
spring.layering-cache.enableUpdate=true
spring.redis.host=localhost
spring.redis.port=6379
  • spring.layering-cache.stats = 是否开启缓存统计 true 是 false 否
  • spring.layering-cache.namespace = 命名空间,必须唯一般使用服务名
  • spring.layering-cache.layeringCacheServletEnabled = 是否开启内置的监控页面
  • spring.layering-cache.urlPattern = 内置的监控页面访问路径
  • spring.layering-cache.loginUsername = 内置的监控登录用户账号
  • spring.layering-cache.loginPassword = 内置的监控登录用户密码
  • spring.layering-cache.enableUpdate = 内置的监控是否启用更新权限

引入依赖:

<dependency>
	<groupId>com.github.xiaolyuh</groupId>
	<artifactId>layering-cache-starter</artifactId>
	<version>2.0.8</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>1.2.22</version>
</dependency>

配置RedisTemplate:

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return createRedisTemplate(redisConnectionFactory);
    }

    public RedisTemplate createRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        //Map
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

使用 layering 包中的 @Cacheable @CachePut @CatchEvict 声明一级和一级缓存配置

具体缓存操作 UserService

@Service
public class UserService {

    private Logger log = LoggerFactory.getLogger(UserService.class);

    /**
     *  根据用户Id 获取用户信息
     *  key:[user:info+userId值]  value: [User]
     * @param userId
     * @return
     */
    @Cacheable(value = "user:info", depict = "用户信息缓存", key = "#userId",
            firstCache = @FirstCache(expireTime = 2, timeUnit = TimeUnit.SECONDS),
            secondaryCache = @SecondaryCache(expireTime = 30, preloadTime = 3, forceRefresh = true, timeUnit = TimeUnit.SECONDS))
    public User getUserById(String userId) {
        log.info("userId:{}未走缓存",userId);
          return  new User(userId,"zhuoqianmingyue"+userId, 1);
    }

    /**
     *  key:[user:info+userId值]  value: [User]
     * 根据用户Id 设置缓存
     * @param userId
     * @return
     */
    @CachePut(value = "user:info", key = "#userId", depict = "用户信息缓存",
            firstCache = @FirstCache(expireTime = 4, timeUnit = TimeUnit.SECONDS),
            secondaryCache = @SecondaryCache(expireTime = 10, preloadTime = 3, forceRefresh = true, timeUnit = TimeUnit.SECONDS))
    public User saveUser(String userId) {
        log.info("userId:{}设置缓存",userId);
        return  new User(userId,"zhuoqianmingyue"+userId, 1);
    }

    /**
     *  key:[user:info+userId值]  value: [User]
     * 根据用户Id 删除缓存
     * @param userId
     */
    @CacheEvict(value = "user:info", key = "#userId")
    public void evictUser(String userId) {
        log.info("删除userId:{}缓存",userId);
    }

    /**
     * 删除所有用户
     */
    @CacheEvict(value = "user:info", allEntries = true)
    public void evictAllUser() {
    }

}

前台调用Service 的Controller

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User getUserById(@PathVariable String id){
       return userService.getUserById(id);
    }

    @GetMapping("/save/{id}")
    public User saveUser(@PathVariable String id){
        return userService.saveUser(id);
    }

    @GetMapping("/delete/{id}")
    public void deleteUser(@PathVariable String id){
         userService.evictUser(id);
    }

    @GetMapping("/deleteAll")
    public void deleteAllUser(@PathVariable String id){
        userService.evictAllUser();
    }
}

访问内置监控

在这里插入图片描述

具体参数说明请查看:文档:https://github.com/xiaolyuh/layering-cache/wiki/文档

小结

本文介绍了什么是缓存雪崩、2级缓存概念与操作流程、 SpringBoot 实现两级缓存解决方案以及 SpringBoot 集成 layering-cache 实战操作。关于 layering-cache 使用注意事项、原理等其他操作请参看:https://github.com/xiaolyuh/layering-cache。

参考文献

  • https://github.com/xiaolyuh/layering-cache
  • https://github.com/alibaba/jetcache
  • https://github.com/zhuoqianmingyue/Java-Interview-Advanced/blob/master/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值