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!