After learning about process cache king Caffeine last time, if you’ve been dying to know how it works, Caffeine has arrived.

preface

Take a look back at the mystery behind Caffeine, the future of cache

Once we introduced how to use Redis or Caffeine for cache, some people will ask why I need to use other caches in combination with Redis. The biggest effect of cache is to improve efficiency, but with the development of business needs and the increase of business volume, The role of multi-level caching is highlighted, let’s keep an eye on oh!

Why use multi-level cache?

  • If only redis is used for caching, we will have a large number of requests to Redis, but the data of each request is the same. If this part of the data is stored locally in the application server, then the network overhead of requesting Redis is saved, and the request speed will be much faster. But scaling horizontally with Redis is convenient.
  • If Caffeine is used only for local cache, our application server has limited memory and it is not cost-effective to scale the application server solely for cache. So using only local caching is also very limited.

Do we have an idea at this point? Use both. Hot data is placed in the local cache (level 1 cache) and non-hot data is placed in the Redis cache (level 2 cache).

Cache selection

  • Level 1 Cache: Caffeine is a high-performance Java cache library; The Window TinyLfu recycle strategy provides a near-optimal hit ratio.
  • Level 2 cache: Redis is a high-performance, highly available key-value database that supports multiple data types, supports clustering, and is deployed separately from application servers for horizontal scaling.

The data flow

Data reading process

Data Deletion Process

solution

Spring provides Cache support, the core of which is to implement the Cache and CacheManager interfaces.

The usage of actual multilevel cache

The following demo project code in the public number background reply [multi-level cache] can be taken by yourself!

Project description

1. We used two levels of caching in our project

2. Local cache time is 60 seconds, after the expiration of redis data,

3. If redis does not exist, fetch the data from the database.

4. After getting data from the database, write it to Redis

The project structure

Configuration File Description

application.properties

#redis1
spring.redis1.host=127.0.0.1
spring.redis1.port=6379
spring.redis1.password=lhddemo
spring.redis1.database=0


spring.redis1.lettuce.pool.max-active=32
spring.redis1.lettuce.pool.max-wait=300
spring.redis1.lettuce.pool.max-idle=16
spring.redis1.lettuce.pool.min-idle=8


spring.redis1.enabled=1


#profile
spring.profiles.active=cacheenable
Copy the code

Description:

Spring. Redis1. enabled=1: controls whether redis takes effect

Spring.profiles. Active = cacheEnable: controls whether Caffeine takes effect,

In a test environment we sometimes need to turn off the cache to debug the database,

There is also a need to turn off the cache if there is a problem with it in a production environment,

So you have to have control

Table structure in mysql

CREATE TABLE `goods` (
 `goodsId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
 `goodsName` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT ' ' COMMENT 'name',
 `subject` varchar(200) NOT NULL DEFAULT ' ' COMMENT 'title',
 `price` decimal(15.2) NOT NULL DEFAULT '0.00' COMMENT 'price',
 `stock` int(11) NOT NULL DEFAULT '0' COMMENT 'stock'.PRIMARY KEY (`goodsId`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='List of Goods'
Copy the code

Java Code Description

CacheConfig.java

@Profile("cacheenable")   // cache only takes effect when prod this profile
@Configuration
@EnableCaching // Enable caching
public class CacheConfig {
    public static final int DEFAULT_MAXSIZE = 10000;
    public static final int DEFAULT_TTL = 600;


    private SimpleCacheManager cacheManager = new SimpleCacheManager();


     // Define the cache name, timeout duration (s), and maximum capacity
    public enum CacheEnum{
        goods(60.1000),          // Valid for 60 seconds, maximum capacity 1000
        homePage(7200.1000),  // Valid for 2 hours, maximum capacity 1000
        ;
        CacheEnum(int ttl, int maxSize) {
            this.ttl = ttl;
            this.maxSize = maxSize;
        }
        private int maxSize=DEFAULT_MAXSIZE;    // Maximum quantity
        private int ttl=DEFAULT_TTL;        // Expiration time (seconds)
        public int getMaxSize(a) {
            return maxSize;
        }
        public int getTtl(a) {
            returnttl; }}// Create Caffeine based Cache Manager
    @Bean
    @Primary
    public CacheManager caffeineCacheManager(a) {
        ArrayList<CaffeineCache> caches = new ArrayList<CaffeineCache>();
        for(CacheEnum c : CacheEnum.values()){
            caches.add(new CaffeineCache(c.name(),
                    Caffeine.newBuilder().recordStats()
                            .expireAfterWrite(c.getTtl(), TimeUnit.SECONDS)
                            .maximumSize(c.getMaxSize()).build())
            );
        }
        cacheManager.setCaches(caches);
        return cacheManager;
    }


    @Bean
    public CacheManager getCacheManager(a) {
        returncacheManager; }}Copy the code

Effect: Adds a defined cache to Caffeine

RedisConfig.java

@Configuration
public class RedisConfig {


    @Bean
    @Primary
    public LettuceConnectionFactory redis1LettuceConnectionFactory(RedisStandaloneConfiguration redis1RedisConfig,
                                                                    GenericObjectPoolConfig redis1PoolConfig) {
        LettuceClientConfiguration clientConfig =
                LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(100))
                        .poolConfig(redis1PoolConfig).build();
        return new LettuceConnectionFactory(redis1RedisConfig, clientConfig);
    }


    @Bean
    public RedisTemplate<String, String> redis1Template(
            @Qualifier("redis1LettuceConnectionFactory") LettuceConnectionFactory redis1LettuceConnectionFactory) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        / / use Jackson2JsonRedisSerializer to serialization and deserialization redis value value
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        // Use StringRedisSerializer to serialize and deserialize the redis key
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        // Start the transaction
        redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.setConnectionFactory(redis1LettuceConnectionFactory);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }


    @Configuration
    public static class Redis1Config {
        @Value("${spring.redis1.host}")
        private String host;
        @Value("${spring.redis1.port}")
        private Integer port;
        @Value("${spring.redis1.password}")
        private String password;
        @Value("${spring.redis1.database}")
        private Integer database;


        @Value("${spring.redis1.lettuce.pool.max-active}")
        private Integer maxActive;
        @Value("${spring.redis1.lettuce.pool.max-idle}")
        private Integer maxIdle;
        @Value("${spring.redis1.lettuce.pool.max-wait}")
        private Long maxWait;
        @Value("${spring.redis1.lettuce.pool.min-idle}")
        private Integer minIdle;


        @Bean
        public GenericObjectPoolConfig redis1PoolConfig() {
            GenericObjectPoolConfig config = new GenericObjectPoolConfig();
            config.setMaxTotal(maxActive);
            config.setMaxIdle(maxIdle);
            config.setMinIdle(minIdle);
            config.setMaxWaitMillis(maxWait);
            return config;
        }


        @Bean
        public RedisStandaloneConfiguration redis1RedisConfig() {
            RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
            config.setHostName(host);
            config.setPassword(RedisPassword.of(password));
            config.setPort(port);
            config.setDatabase(database);
            returnconfig; }}}Copy the code

Purpose: To generate a connection to redis

HomeController.java

// Commodity details parameter: commodity ID
    @Cacheable(value = "goods", key="#goodsId",sync = true)
    @GetMapping("/goodsget")
    @ResponseBody
    public Goods goodsInfo(@RequestParam(value="goodsid",required = true,defaultValue = "0") Long goodsId) {
        Goods goods = goodsService.getOneGoodsById(goodsId);
        return goods;
    }
Copy the code

Note the use of the Cacheable annotation to enable local caching

GoodsServiceImpl.java

@Override
    public Goods getOneGoodsById(Long goodsId) {
        Goods goodsOne;
        if (redis1enabled == 1) {
            System.out.println("get data from redis");
            Object goodsr = redis1Template.opsForValue().get("goods_"+String.valueOf(goodsId));
            if (goodsr == null) {
                System.out.println("get data from mysql");
                goodsOne = goodsMapper.selectOneGoods(goodsId);
                if (goodsOne == null) {
                    redis1Template.opsForValue().set("goods_"+String.valueOf(goodsId),"1".600, TimeUnit.SECONDS);
                } else {
                    redis1Template.opsForValue().set("goods_"+String.valueOf(goodsId),goodsOne,600, TimeUnit.SECONDS); }}else {
                if (goodsr.equals("1")) {
                    goodsOne = null;
                } else{ goodsOne = (Goods)goodsr; }}}else {
            goodsOne = goodsMapper.selectOneGoods(goodsId);
        }
        return goodsOne;
    }
Copy the code

Get data from Redis. If not, access data from the database.

Check whether redis1Enabled ==1, that is, when redis takes effect globally,

Only use redis, otherwise directly access mysql

The test results

Visit address:

http:/ / 127.0.0.1:8080 / home/goodsget? goodsid=3
Copy the code

View the console output:

get data from redis
get data fromMysql Costtime AOP method Doafterreturning:395
Copy the code

Since caffeine/ Redis has no data, you can see that the program queries data from mysql

Costtime AOP method Doafterreturning:0
Copy the code

No data was read from Redis /mysql when caffeine was refreshed again. The usage time was less than 1ms

get data fromRedis Costtime AOP method DoafterRETURNING:8
Copy the code

After the local cache expires, you can see the data being fetched from Redis in 8 milliseconds

The specific cache time can be determined according to the update frequency of their own business data. In principle, the duration of local cache is shorter than that of REDis, because the data in Redis is usually updated by a synchronous mechanism, and the time of local cache is not too long because it is in each Web service.

conclusion

This article introduces the principles and usage of multi-level caching, through the introduction of these knowledge is sure to gain a lot. I hope this article introduces you to multi-level caching and what situations it can be used in. Garnett will continue to share the technology dry, and I hope you are my best audience!