[SpringBoot integrated cache] ----- jetcache and j2cache articles

This column will start from the basics, step by step, take actual combat as a clue, and gradually deepen the knowledge of SpringBoot related knowledge, create a complete SpringBoot learning process, improve engineering coding ability and thinking ability, and write high-quality code. I hope everyone can gain something from it, and please support me a lot.
Column address: SpringBoot Column
The code involved in this article has been placed on gitee: gitee address
If there are mistakes in the knowledge points of the article, please correct me! Everyone learns and progresses together.
Column summary: Column summary

SpringBoot integrates jetcache caching

​ At present, the caches we use are either A or B. Can A and B be used together? This section solves this problem. The integration of springboot for cache only stays on the use of cache. If the cache itself does not support the use of AB at the same time, springboot can't do it. Therefore, in order to solve the problem of using AB cache together, you must find a cache that can support both AB and AB. Cache is used together, is there such a cache? There really is, Ali produced, jetcache.

Strictly speaking, jetcache is not a caching solution. It can only be said that it is a caching framework, and then other caches are managed in jetcache, so that AB caches can be used together. And jetcache refers to springboot's idea of ​​integrating caching, and the overall technology usage is very similar to springboot's caching solution idea. Let's use jetcache first, and then talk about some small functions in it.

​ Before doing it, make it clear that jetcache can’t just take two caches together. Currently, jetcache supports two types of caching schemes: local caching and remote caching, which are as follows:

  • Local cache (Local)
    • LinkedHashMap
    • Caffeine
  • Remote cache (Remote)
    • Redis
    • Tair

​ Why does jetcache only support 4 caches like 2+2? In fact, Ali developed this technology mainly to meet its own needs. There must be only 1+1 species at first, and gradually changed to 2+2 species. The following will use the LinkedHashMap+Redis scheme to realize the simultaneous use of local and remote caching schemes.

Pure remote solution

Step 1: Import springboot to integrate the coordinate starter corresponding to jetcache. The default remote scheme used by the current coordinate is redis

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>2.6.2</version>
</dependency>

Step 2: Basic configuration of remote solution

jetcache:
  remote:
    default:
      type: redis
      host: localhost
      port: 6379
#  Since the redis cache does not support saving objects, it is necessary to set how to perform type conversion when the Object type data enters redis in redis.
      keyCovertor: fastjson
      valueEncode: java
      valueDecode: java
      poolconfig:
        maxTotal: 50

​ Where poolConfig is a required item, otherwise an error will be reported

Step 3: Enable caching, mark the annotation @EnableCreateCacheAnnotation above the boot class to configure the springboot program to create a cache in the form of annotations

@SpringBootApplication
//jetcache main switch to enable caching
@EnableCreateCacheAnnotation
public class Springboot20JetCacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot20JetCacheApplication.class, args);
    }
}

Step 4: Create the cache object Cache, and use the annotation @CreateCache to mark the information of the current cache, and then use the API of the Cache object to operate the cache, put the write cache, and get the read cache.

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    @Autowired
    private CodeUtils codeUtils;
    
    @CreateCache(name="jetCache_",expire = 10,timeUnit = TimeUnit.SECONDS)
    private Cache<String ,String> jetCache;

    public String sendCodeToSMS(String tele) {
        String code = codeUtils.generator(tele);
        jetCache.put(tele,code);
        return code;
    }

    public boolean checkCode(SMSCode smsCode) {
        String code = jetCache.get(smsCode.getTele());
        return smsCode.getCode().equals(code);
    }
}

​ Through the above jetcache using the remote scheme to connect to redis, it can be seen that the interface operation of jetcache when operating the cache is more in line with the developer's habits. When using the cache, the cache object Cache is obtained first, the data is put in, and the data is retrieved. get, which is simpler and easier Understand. And when jetcache operates the cache, you can set the expiration time for a cache object, and put the same type of data into the cache to facilitate the management of the effective period.

​ The above scheme uses the default cache defined in the configuration. In fact, this default is a name, which can be written or added at will. For example, to add another caching solution, refer to the following configuration:

jetcache:
  remote:
    default:
      type: redis
      host: localhost
      port: 6379
#  Since the redis cache does not support saving objects, it is necessary to set how to perform type conversion when the Object type data enters redis in redis.
      keyCovertor: fastjson
      valueEncode: java
      valueDecode: java
      poolconfig:
        maxTotal: 50
    sms:
      type: redis
      host: localhost
      port: 6379
#  Since the redis cache does not support saving objects, it is necessary to set how to perform type conversion when the Object type data enters redis in redis.
      keyCovertor: fastjson
      valueEncode: java
      valueDecode: java
      poolconfig:
        maxTotal: 50

​ If you want to use the cache with the name sms, you need to specify the parameter area when creating the cache, and declare the corresponding cache.

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    @Autowired
    private CodeUtils codeUtils;
    
    @CreateCache(area="sms",name="jetCache_",expire = 10,timeUnit = TimeUnit.SECONDS)
    private Cache<String ,String> jetCache;

    public String sendCodeToSMS(String tele) {
        String code = codeUtils.generator(tele);
        jetCache.put(tele,code);
        return code;
    }

    public boolean checkCode(SMSCode smsCode) {
        String code = jetCache.get(smsCode.getTele());
        return smsCode.getCode().equals(code);
    }
}

pure local solution

​ In the remote solution, remote is used in the configuration to indicate remote, and local is local, but the type is different.

Step 1: Import springboot to integrate the coordinate starter corresponding to jetcache

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>2.6.2</version>
</dependency>

Step 2: Local cache basic configuration

jetcache:
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson

​ In order to speed up the key matching speed during data acquisition, jetcache requires the type converter to specify the key. Simply put, if you give an Object as a key, I first convert it to a string using the key's type converter, and then save it. When the data is obtained, the given Object is still converted into a string first, and then matched according to the string. Since jetcache is Ali's technology, it is recommended to use Ali's fastjson as the key type converter here.

Step ③: Enable caching

@SpringBootApplication
//jetcache main switch to enable caching
@EnableCreateCacheAnnotation
public class Springboot20JetCacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot20JetCacheApplication.class, args);
    }
}

Step 4: When creating the cache object Cache, mark the current use of the local cache

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    @CreateCache(name="jetCache_",expire = 1000,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.LOCAL)
    private Cache<String ,String> jetCache;

    public String sendCodeToSMS(String tele) {
        String code = codeUtils.generator(tele);
        jetCache.put(tele,code);
        return code;
    }

    public boolean checkCode(SMSCode smsCode) {
        String code = jetCache.get(smsCode.getTele());
        return smsCode.getCode().equals(code);
    }
}

​ cacheType controls whether the current cache uses the local cache or the remote cache. Configure cacheType=CacheType.LOCAL to use the local cache.

Local + Remote Scenario

​ Both local and remote methods are available. How to configure the two schemes together? In fact, it is enough to combine the two configurations.

jetcache:
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson
  remote:
    default:
      type: redis
      host: localhost
      port: 6379
#  Since the redis cache does not support saving objects, it is necessary to set how to perform type conversion when the Object type data enters redis in redis.
      keyCovertor: fastjson
      valueEncode: java
      valueDecode: java
      poolconfig:
        maxTotal: 50
    sms:
      type: redis
      host: localhost
      port: 6379
#  Since the redis cache does not support saving objects, it is necessary to set how to perform type conversion when the Object type data enters redis in redis.
      keyCovertor: fastjson
      valueEncode: java
      valueDecode: java
      poolconfig:
        maxTotal: 50

When creating a cache, configure the cacheType as BOTH, that is, the local cache and the remote cache are used at the same time.

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    @CreateCache(name="jetCache_",expire = 1000,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.BOTH)
    private Cache<String ,String> jetCache;
}

​ If cacheType is not configured, the default value is REMOTE, that is, only the remote cache scheme is used. For jetcache configuration, refer to the following information

AttributesDefaultsillustrate
jetcache.statIntervalMinutes0Statistics interval, 0 means no statistics
jetcache.hiddenPackagesnoneWhen the name is automatically generated, hide the specified package name prefix
jetcache.[local|remote].${area}.typenoneCache type, local support linkedhashmap, caffeine, remote support redis, tair
jetcache.[local|remote].${area}.keyConvertornonekey converter, currently only supports fastjson
jetcache.[local|remote].${area}.valueEncoderjavaOnly remote type cache needs to be specified, optional java and kryo
jetcache.[local|remote].${area}.valueDecoderjavaOnly remote type cache needs to be specified, optional java and kryo
jetcache.[local|remote].${area}.limit100Only the local type of cache needs to be specified, the maximum number of elements of the cache instance
jetcache.[local|remote].${area}.expireAfterWriteInMillisgiganticDefault expiration time, in milliseconds
jetcache.local.${area}.expireAfterAccessInMillis0Only local type caches are valid, milliseconds, maximum inactivity interval

The above solutions only support manual control of the cache, but the method cache in the springcache solution is particularly useful. Add an annotation to a method and the method will automatically use the cache. jetcache also provides a corresponding function, namely method caching.

Jetcache method cache

​ jetcache provides a method caching solution, but the name has changed. Just use the annotation @Cached above the corresponding operation interface

Step 1: Import springboot to integrate the coordinate starter corresponding to jetcache

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>2.6.2</version>
</dependency>

Step 2: Configure the cache

jetcache:
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson
  remote:
    default:
      type: redis
      host: localhost
      port: 6379
      keyConvertor: fastjson
      valueEncode: java
      valueDecode: java
#  Since the redis cache does not support saving objects, it is necessary to set how to perform type conversion when the Object type data enters redis in redis.
      keyCovertor: fastjson
      valueEncode: java
      valueDecode: java
      poolconfig:
        maxTotal: 50
    sms:
      type: redis
      host: localhost
      port: 6379
#  Since the redis cache does not support saving objects, it is necessary to set how to perform type conversion when the Object type data enters redis in redis.
      keyCovertor: fastjson
      valueEncode: java
      valueDecode: java
      poolconfig:
        maxTotal: 50

Since the redis cache does not support saving objects, it is necessary to set how to perform type conversion for redis when the Object type data enters into redis. The keyConvertor needs to be configured to indicate the type conversion method of the key, and at the same time, the conversion type method of the value should be marked. When the value enters redis, it is of java type, and the valueEncode is marked as java.

​ Note that in order to implement the value of Object type in and out of redis, it is necessary to ensure that the data of type Object in and out of redis must implement the serialization interface.

@Data
public class Book implements Serializable {
    private Integer id;
    private String type;
    private String name;
    private String description;
}

Step 3: Enable the method cache function when the cache is enabled, and configure the basePackages to indicate in which packages the method cache is enabled

@SpringBootApplication
//jetcache main switch to enable caching
@EnableCreateCacheAnnotation
//Enable method annotation caching
@EnableMethodCache(basePackages = "com.hashnode")
public class Springboot20JetCacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot20JetCacheApplication.class, args);
    }
}

Step 4: Use the annotation @Cached to mark the current method to use the cache

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;
    
    @Override
    @Cached(name="book_",key="#id",expire = 3600,cacheType = CacheType.REMOTE)
    public Book getById(Integer id) {
        return bookDao.selectById(id);
    }
}

Data synchronization for remote scenarios

Since the data saved by redis in the remote scheme can be shared by multiple clients, there is a data synchronization problem. jetcache provides 3 annotations to solve this problem, synchronizing the cached data during update and delete operations, and periodically refreshing the data when reading the cache

refresh cache

@CacheUpdate(name="book_",key="#book.id",value="#book")
public boolean update(Book book) {
    return bookDao.updateById(book) > 0;
}

delete cache

@CacheInvalidate(name="book_",key = "#id")
public boolean delete(Integer id) {
    return bookDao.deleteById(id) > 0;
}

Periodically refresh the cache

@Cached(name="book_",key="#id",expire = 3600,cacheType = CacheType.REMOTE)
//    5s refresh once
@CacheRefresh(refresh = 5)
public Book getById(Integer id) {
    return bookDao.selectById(id);
}

data report

​ jetcache also provides a simple data report function to help developers quickly view cache hit information, just add a configuration

jetcache:
  statIntervalMinutes: 1

​ After setting, output cache data hit information on the console every 1 minute

[DefaultExecutor] c.alicp.jetcache.support.StatInfoLogger  : jetcache stat from 2022-02-28 09:32:15,892 to 2022-02-28 09:33:00,003
cache    |    qps|   rate|   get|    hit|   fail|   expire|   avgLoadTime|   maxLoadTime
---------+-------+-------+------+-------+-------+---------+--------------+--------------
book_    |   0.66| 75.86%|    29|     22|      0|        0|          28.0|           188
---------+-------+-------+------+-------+-------+---------+--------------+--------------

Summarize

  1. jetcache is a caching solution similar to springcache. It does not have a caching function. It provides a multi-level caching solution that uses local caching and remote caching.
  2. The caching solutions provided by jetcache are limited by the currently supported solutions. Local caching supports two and remote caching supports two.
  3. Pay attention to the type conversion problem when the data enters the remote cache
  4. jetcache provides method caching, and provides the corresponding cache update and refresh functions
  5. jetcache provides a simple cache information hit report to facilitate developers to monitor cache data hits in real time

think

​ jetcache solves the problem of using a single cache solution in the early stage, but it still cannot flexibly choose the cache for use. Is there a technology that can be used flexibly with various caches? Yes, let's talk about it in the next section.

SpringBoot integrates j2cache caching

​ jetcache can build a multi-level cache within a limited range, but it is not flexible enough to match the cache at will. This section introduces a cache integration framework, j2cache, that can be freely matched with a caching solution. Let's explain how to use this caching framework, taking the integration of Ehcache and redis as an example:

Step 1: Import j2cache, redis, ehcache coordinates

<dependency>
    <groupId>net.oschina.j2cache</groupId>
    <artifactId>j2cache-core</artifactId>
    <version>2.8.4-release</version>
</dependency>
<dependency>
    <groupId>net.oschina.j2cache</groupId>
    <artifactId>j2cache-spring-boot2-starter</artifactId>
    <version>2.8.0-release</version>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>

​ The starter of j2cache contains redis coordinates by default. It is officially recommended to use redis as the secondary cache, so there is no need to import redis coordinates here.

Step 2: Configure the first-level and second-level caches, and configure the data transfer method between the first-level and second-level caches. The configuration is written in a file named j2cache.properties. If you use ehcache, you also need to add the ehcache configuration file separately

# Level 1 cache
j2cache.L1.provider_class = ehcache
ehcache.configXml = ehcache.xml

# Level 2 cache
j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
j2cache.L2.config_section = redis
redis.hosts = localhost:6379

# How data in level 1 cache arrives at level 2 cache
j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy

​ The configuration here cannot be arbitrarily configured, and you need to refer to the official configuration instructions. For example, if the tier 1 supplier chooses ehcache, the supplier name is just an ehcache, but when the tier 2 supplier chooses redis, they need to write a dedicated Spring-integrated Redis supplier class name SpringRedisProvider, and this name is not available in all redis packages. , also not provided in the spring package. Therefore, to configure j2cache, you must refer to the official document configuration, and you must also find a dedicated integration package and import the corresponding coordinates before it can be used.

​ The most important configuration of the first-level and second-level caches is the data communication method between the two. This type of configuration is not arbitrarily configured, and the data communication methods provided by different caching solutions are very different. You need to query the official website. document to set.

Step ③: Use the cache

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    @Autowired
    private CodeUtils codeUtils;

    @Autowired
    private CacheChannel cacheChannel;

    public String sendCodeToSMS(String tele) {
        String code = codeUtils.generator(tele);
        cacheChannel.set("sms",tele,code);
        return code;
    }

    public boolean checkCode(SMSCode smsCode) {
        String code = cacheChannel.get("sms",smsCode.getTele()).asString();
        return smsCode.getCode().equals(code);
    }
}

​ The use of j2cache is similar to jetcache, but you don’t need to enable the switch to use, you can directly define the cache object and use it. The cache object name is CacheChannel.

The use of j2cache is not complicated, and the configuration is the core of j2cache. After all, it is an integrated caching framework. There are too many cache-related configurations. You can refer to the description in the j2cache.properties file in the j2cache-core core package. as follows:

#J2Cache configuration
#########################################
# Cache Broadcast Method
# values:
# jgroups -> use jgroups's multicast
# redis -> use redis publish/subscribe mechanism (using jedis)
# lettuce -> use redis publish/subscribe mechanism (using lettuce, Recommend)
# rabbitmq -> use RabbitMQ publisher/consumer mechanism
# rocketmq -> use RocketMQ publisher/consumer mechanism
# none -> don't notify the other nodes in cluster
# xx.xxxx.xxxx.Xxxxx your own cache broadcast policy classname that implement net.oschina.j2cache.cluster.ClusterPolicy
#########################################
j2cache.broadcast = redis

# jgroups properties
jgroups.channel.name = j2cache
jgroups.configXml = /network.xml

# RabbitMQ properties
rabbitmq.exchange = j2cache
rabbitmq.host = localhost
rabbitmq.port = 5672
rabbitmq.username = guest
rabbitmq.password = guest

# RocketMQ properties
rocketmq.name = j2cache
rocketmq.topic = j2cache
# use ; to split multi hosts
rocketmq.hosts = 127.0.0.1:9876

#########################################
# Level 1&2 provider
# values:
# none -> disable this level cache
# ehcache -> use ehcache2 as level 1 cache
# ehcache3 -> use ehcache3 as level 1 cache
# caffeine -> use caffeine as level 1 cache(only in memory)
# redis -> use redis as level 2 cache (using jedis)
# lettuce -> use redis as level 2 cache (using lettuce)
# readonly-redis -> use redis as level 2 cache ,but never write data to it. if use this provider, you must uncomment `j2cache.L2.config_section` to make the redis configurations available.
# memcached -> use memcached as level 2 cache (xmemcached),
# [classname] -> use custom provider
#########################################

j2cache.L1.provider_class = caffeine
j2cache.L2.provider_class = redis

# When L2 provider isn't `redis`, using `L2.config_section = redis` to read redis configurations
# j2cache.L2.config_section = redis

# Enable/Disable ttl in redis cache data (if disabled, the object in redis will never expire, default:true)
# NOTICE: redis hash mode (redis.storage = hash) do not support this feature)
j2cache.sync_ttl_to_redis = true

# Whether to cache null objects by default (default false)
j2cache.default_cache_null_object = true

#########################################
# Cache Serialization Provider
# values:
# fst -> using fast-serialization (recommend)
# kryo -> using kryo serialization
# json -> using fst's json serialization (testing)
# fastjson -> using fastjson serialization (embed non-static class not support)
# java -> java standard
# fse -> using fse serialization
# [classname implements Serializer]
#########################################

j2cache.serialization = json
#json.map.person = net.oschina.j2cache.demo.Person

#########################################
# Ehcache configuration
#########################################

# ehcache.configXml = /ehcache.xml

# ehcache3.configXml = /ehcache3.xml
# ehcache3.defaultHeapSize = 1000

#########################################
# Caffeine configuration
# caffeine.region.[name] = size, xxxx[s|m|h|d]
#
#########################################
caffeine.properties = /caffeine.properties

#########################################
# Redis connection configuration
#########################################

#########################################
# Redis Cluster Mode
#
# single -> single redis server
# sentinel -> master-slaves servers
# Cluster - > cluster servers (invalid database configuration, use database = 0)
# Sharded - > sharded servers (password, database must be specified in hosts, and connection pool configuration is invalid; redis://user:password@127.0.0.1:6379/0)
#
#########################################

redis.mode = single

#redis storage mode (generic|hash)
redis.storage = generic

## redis pub/sub channel name
redis.channel = j2cache
## redis pub/sub server (using redis.hosts when empty)
redis.channel.host =

#cluster name just for sharded
redis.cluster_name = j2cache

## redis cache namespace optional, default[empty]
redis.namespace =

## redis command scan parameter count, default[1000]
#redis.scanCount = 1000

## connection
# Separate multiple redis nodes with commas, such as 192.168.0.10:6379,192.168.0.11:6379,192.168.0.12:6379

redis.hosts = 127.0.0.1:6379
redis.timeout = 2000
redis.password =
redis.database = 0
redis.ssl = false

## redis pool properties
redis.maxTotal = 100
redis.maxIdle = 10
redis.maxWaitMillis = 5000
redis.minEvictableIdleTimeMillis = 60000
redis.minIdle = 1
redis.numTestsPerEvictionRun = 10
redis.lifo = false
redis.softMinEvictableIdleTimeMillis = 10
redis.testOnBorrow = true
redis.testOnReturn = false
redis.testWhileIdle = true
redis.timeBetweenEvictionRunsMillis = 300000
redis.blockWhenExhausted = false
redis.jmxEnabled = false

#########################################
# Lettuce scheme
#
# redis -> single redis server
# rediss -> single redis server with ssl
# redis-sentinel -> redis sentinel
# redis-cluster -> cluster servers
#
#########################################

#########################################
# Lettuce Mode
#
# single -> single redis server
# sentinel -> master-slaves servers
# Cluster - > cluster servers (invalid database configuration, use database = 0)
# Sharded - > sharded servers (password, database must be specified in hosts, and connection pool configuration is invalid; redis://user:password@127.0.0.1:6379/0)
#
#########################################

## redis command scan parameter count, default[1000]
#lettuce.scanCount = 1000
lettuce.mode = single
lettuce.namespace =
lettuce.storage = hash
lettuce.channel = j2cache
lettuce.scheme = redis
lettuce.hosts = 127.0.0.1:6379
lettuce.password =
lettuce.database = 0
lettuce.sentinelMasterId =
lettuce.maxTotal = 100
lettuce.maxIdle = 10
lettuce.minIdle = 10
# timeout in milliseconds
lettuce.timeout = 10000
# redis cluster topology refresh interval in milliseconds
lettuce.clusterTopologyRefresh = 3000

#########################################
# memcached server configurations
# refer to https://gitee.com/mirrors/XMemcached
#########################################

memcached.servers = 127.0.0.1:11211
memcached.username =
memcached.password =
memcached.connectionPoolSize = 10
memcached.connectTimeout = 1000
memcached.failureMode = false
memcached.healSessionInterval = 1000
memcached.maxQueuedNoReplyOperations = 100
memcached.opTimeout = 100
memcached.sanitizeKeys = false

Summarize

  1. j2cache is a caching framework that does not have a caching function by itself. It provides solutions for integrating multiple caches.
  2. j2cache needs to set up all levels of cache through complex configuration, as well as the way of data exchange between caches
  3. The j2cache operation interface is implemented through CacheChannel

Tags: Redis Spring Boot Cache

Posted by |Adam| on Sun, 24 Jul 2022 03:30:16 +0930