大型网站的缓存设计Guava+redis实现多级缓存

文章内容输出来源:拉勾教育Java高薪训练营;

应用场景

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统带来很大压力。突然间大量的key失效了或redis重启,大量访问数据库,数据库崩溃,这时候就需要设置一个本地缓存作为二级缓存来解决这个问题。

本地缓存的应用场景:

  • 对性能有非常高的要求
  • 不经常变化
  • 占用内存不大
  • 有访问整个集合的需求
  • 数据允许不时时一致

实现原理

数据从服务层读取,然后放到本地缓存中(Guava),如果出现超时或读取为空,则返回原来本地缓存的数据。这样如果出现分布式缓存redis挂掉等情况,依然可以从本地缓存中获取到数据,不会出现项目的瘫痪。

大型网站的缓存设计Guava+redis实现多级缓存_第1张图片

代码逻辑

引入maven坐标

	<dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.1.1version>
        dependency>

        <dependency>
            <groupId>tk.mybatisgroupId>
            <artifactId>mapper-spring-boot-starterartifactId>
            <version>2.0.2version>
        dependency>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
            <version>1.4.7.RELEASEversion>
        dependency>

        <dependency>
            <groupId>com.google.guavagroupId>
            <artifactId>guavaartifactId>
            <version>28.2-jreversion>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintagegroupId>
                    <artifactId>junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>
    dependencies>

具体逻辑实现部分,先从本地缓存Guava Cache中获取要查询的数据,如果没有则从redis中查询,如果redis 中也没有,则去mysql中查询,最后回写到redis,再回写到本地缓存Guava Cache中返回查询结果

@Service
public class PositionServiceImpl implements PositionService {

    @Autowired
    PositionMapper positionMapper;

    @Autowired
    RedisTemplate redisTemplate;

    private static Cache<Object, Object> cache = CacheBuilder.newBuilder().expireAfterWrite(5,TimeUnit.SECONDS).build();

    @Override
    public List<Position> getHotPosition() throws ExecutionException {
		//从guava本地缓存中获取数据,如果没有则从redis中回源
        Object value = cache.get("position", new Callable() {
            @Override
            public Object call() throws Exception {

                return getHotPositionListFromRedis();
            }
        });

        if(value != null){
            return (List<Position>)value;
        }

        return null;
    }

    @Override
    public List<Position> getHotPositionListFromRedis()  {

        Object position = redisTemplate.opsForValue().get("position");
        System.out.println("从redis中获取数据");

        if(position == null) {
            //从mysql中获取数据
            List<Position> positionList = positionMapper.select(null);
            System.out.println("从mysql中获取数据");

            //同步至redis
            redisTemplate.opsForValue().set("position",positionList);
            System.out.println("同步至redis");

            redisTemplate.expire("position",5, TimeUnit.SECONDS);

            return positionList;
        }

        return (List<Position>)position;
    }

}

调用查询接口

@RestController
@RequestMapping("position")
public class PositionController {

    @Autowired
    PositionService positionService;

    @GetMapping
    public List<Position> getHotPosition() throws ExecutionException {

        List<Position> result = positionService.getHotPosition();

        return result;
    }

}

实体类

@Table(name = "t_position")
public class Position implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Mapper

@org.apache.ibatis.annotations.Mapper
public interface PositionMapper extends Mapper<Position> {
}

配置文件信息

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/lg?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=docker

logging.level.com.cache.cachework.dao.mapper=debug

spring.redis.cluster.nodes=127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005,127.0.0.1:7006
spring.redis.database=0
spring.redis.timeout=5000

效果演示

首次请求,回源操作从mysql->redis->cache
大型网站的缓存设计Guava+redis实现多级缓存_第2张图片

大型网站的缓存设计Guava+redis实现多级缓存_第3张图片
然后把redis停掉,模拟宕机,继续请求
从本地缓存Guava Cache中获取到查询结果
大型网站的缓存设计Guava+redis实现多级缓存_第4张图片

写在最后

工作几年,一直都没有去体系化的学习,很多东西没有复杂的工作场景经验,去年综合几家机构,最后还是决定报了拉勾的高薪训练营,在这里也是实实在在的学习到了很多,学完掌握程度也比之前深了很多,而且还有定期的内推,多了更多的机会,真的对我有了很大的帮助提升。

你可能感兴趣的:(缓存设计,缓存,GuavaCache)