Detailed explanation of services in cache architecture! Implementation of L2 cache service in springboot

Time:2021-11-26

Create cache service

Create cache service interface project

  • Create a myshop service redis API project, which is only responsible for defining interfaces
  • To create a projectpom.xml:
4.0.0
    
        com.oxford
        myshop-dependencies
        1.0.0-SNAPSHOT
        ../myshop-dependencies/pom.xml
    

    myshop-service-redis-api
    jar
  • Define data redis interfaceRedisService:
package com.oxford.myshop.service.redis.api

public interface RedisService{
	void set(String key,Object value);
	
	void set(String key,Object value,int seconds);

	void del(String key);

	Object get(String key);
}

Create cache service provider project

  • Create the myshop service redis provider project, which is used as a caching service provider
  • To create a projectpom.xml:
4.0.0
    
        com.oxford
        myshop-dependencies
        1.0.0-SNAPSHOT
        ../myshop-dependencies/pom.xml
    

    myshop-service-redis-api
    jar

	
		
		
			org.springframework.boot
			spring-boot-starter-data-redis
		

		
			org.springframework.boot
			spring-boot-starter-test
			test
		


		
		
			org.apache.commons
			commons-pool2
		
		
			de.javakaffee
			kryo-serializers
		

		
		
			com.oxford
			my-shop-commons-dubbo
			${Project.parent.version}
		
		
			com.oxford
			my-shop-service-redis-api
			${Project.parent.version}
		
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
				
					com.oxford.myshop.service.redis.provider.MyshopServiceRedisProviderApplication

Java lettuce client implemented at the bottom of redis

  • Create cache service interface implementation classRedisServiceImpl
package com.oxford.myshop.service.redis.provider.api.impl;

@Service(version="${service.versions.redis.v1}")
public class RedisServiceImpl implements RedisService{
	
	@Override
	public void set(String key,Object value){
		redisTemplate.opsForValue().set(key,value);
	}

	@Override
	public void set(String key,Object value,int seconds){
		redisTemplate.opsForValue().set(key,value,seconds,TimeUnit.SECONDS);
	}

	@Override
	public void del(String key){
		redisTemplate.delete(key);
	}

	@Override
	public Object get(String key){
		return redisTemplate.opsForValue().get(key);
	}
}
  • Create startup classSpringBootApplication
package com.oxford.myshop.service.redis.provider;

import com.alibaba.dubbo.container.Main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;



@EnableHystrix
@EnableHystrixDashboard
public class MyShopServiceRedisrProviderApplication {
    public static void main(String[]args) {
        SpringApplication.run(MyShopServiceRedisProviderApplication.class,args);
        Main.main(args);
    }
}
  • create profileapplication.yml
spring:
  application:
    name: myshop-service-redis-provider
  redis:
  	lettuce:
      pool:
      	max-active: 8
      	max-idle: 8
      	max-wait: -1ms
      	min-idle: 0
    sentinel:
      master: mymaster
      nodes: 192.168.32.255:26379,192.168.32.255:26380,192.168.32.255:26381
server:
  port: 8503

services:
  version:
    redis:
  	  v1: 1.0.0
    user:
      v1: 1.0.0

dubbo:
  scan:
    basePackages: com.oxford.myshop.service.redis.provider.api.impl
  application:
    id: com.oxford.myshop.service.redis.provider.api
    name: com.oxford.myshop.service.redis.provider.api
    qos-port: 22224
    qos-enable: true
  protocal:
    id: dubbo
    name: dubbo
    port: 20883
    status: server
    serialization: kryo

  regitry:
    id: zookeeper
    address: zookeeper://localhost:2181?backup=192.168.32.255:2182,192.168.32.255:2183

management:
  endpoint:
    dubbo:
      enabled: true
    dubbo-shutdown:
      enabled: true
    dubbo-configs:
      enabled: true
    dubbo-sevicies:
      enabled: true
    dubbo-reference:
      enabled: true
    dubbo-properties:
      enabled: true
  health:
    dubbo:
      status:
        defaults: memory
        extras: load,threadpool

Create cache service consumer item

  • Introduce redis interface dependency in POM file
  • Invoke RedisService in the ServiceImpl of the caching consumer item
@Reference(version="services.versions.redis.v1")
private RedisService redisService;

Mybatis redis L2 cache

Mybatis cache

  • L1 cache:
    • Mybatis will establish a simple cache in the sqlsession object representing the session:Cache the results of each query. If it is judged that there is an identical query before the next query, the results will be taken directly from the cache and returned to the user without another database query
    • The L1 cache is a sqlsession level cache:
      • The sqlsession object needs to be constructed when operating the database
      • There is a (memory area) data structure (HashMap) in the object to store cached data
      • The cached data areas (hashmaps) between different sqlsessions do not affect each other,
      • The scope of the L1 cache is the same sqlsession
      • Execute the same SQL statement twice in the same sqlsession:After the first execution, the data queried in the database will be written to the cache (memory), and the second time, the data will be obtained from the cache. No query will be made from the database, so as to improve the query efficiency
      • When a sqlsession ends, the L1 cache in the sqlsession does not exist
      • Mybatis enables L1 cache by default
  • L2 cache:
    • L2 cache is mapper level cache:Multiple sqlsessions operate the SQL statement of the same mapper, and the data obtained by multiple sqlsessions operate the database will exist in the L2 cache area. Multiple sqlsessions can share the L2 cache, which is cross sqlsessions
    • The scope of L2 cache is the same namespace of mapper
    • Different sqlsessions execute SQL statements in the same namespace twice, and pass the same parameters to SQL, that is, finally execute the same SQL statement:After the first execution, the data queried in the database will be written to the cache (memory), and the second time, the data will be obtained from the cache. The query will no longer be queried from the database, so as to improve the query efficiency
    • Mybatis does not enable L2 cache by default. You need to configure enabling L2 cache in the setting global parameter

Configure mybatis L2 cache

Enable mybatis L2 cache in springboot
spring:
  application:
    name: myshop-service-user-provider
  datasource:
    druid:
      url: jdbc:mysql://localhost:3306/myshop?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: 123456
      initial-size: 1
      min-idle: 1
      main-active: 20
      test-on-borrow: true
      driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
  	lettuce:
      pool:
      	max-active: 8
      	max-idle: 8
      	max-wait: -1ms
      	min-idle: 0
    sentinel:
      master: mymaster
      nodes: 192.168.32.255:26379,192.168.32.255:26380,192.168.32.255:26381

server:
  port: 8501

# MyBatis Config properties
mybatis:
  configuration:
  	cache-enabled: true
  type-aliases-package: com.oxford.myshop.commons.domain
  mapper-location: classpath:mapper/*.xml

services:
  version:
  	redis:
  	  v1: 1.0.0
    user:
      v1: 1.0.0

dubbo:
  scan:
    basePackages: com.oxford.myshop.service.user.provider.api.impl
  application:
    id: com.oxford.myshop.service.user.provider.api
    name: com.oxford.myshop.service.user.provider.api
    qos-port: 22222
    qos-enable: true
  protocal:
    id: dubbo
    name: dubbo
    port: 20001
    status: server
    serialization: kryo

  regitry:
    id: zookeeper
    address: zookeeper://localhost:2181?backup=192.168.32.255:2182,192.168.32.255:2183

management:
  endpoint:
    dubbo:
      enabled: true
    dubbo-shutdown:
      enabled: true
    dubbo-configs:
      enabled: true
    dubbo-sevicies:
      enabled: true
    dubbo-reference:
      enabled: true
    dubbo-properties:
      enabled: true
  health:
    dubbo:
      status:
        defaults: memory
        extras: load,threadpool
  • Add redis dependency in pom.xml of myshop commons mapper:
org.springframework.boot
	spring-boot-starter-data-redis



	org.apache.commons
	commons-pool2
The entity class implements the serialization interface and declares the serial number
private static final long serialVersionUID = 82897704415244673535L
Method of generating serial number from idea:
-Generated by using the generateserialversionuid plug-in. After installing the plug-in, it is in the class that implements the serialization interface
-Use the shortcut key Alt + insert to call out the generation menu and automatically generate the serial number
Implement the mybatis cache interface, and customize the cache as redis
  • Create the applicationcontextholder class in the myshop commons project
package com.oxford.myshop.commons.context;

@Component
public class ApplicationContextHolder implements ApplicationContextAware,DisposableBean{

	private static final Logger logger=LoggerFactory.getLogger(ApplicationContext.class);

	private static ApplicationContext applicationContext;

	/**
	 *Gets the ApplicationContext stored in the static variable
	 */
	 public static ApplicationContext getApplicationContext(){
	 	assertContextInjected();
	 	return applicationContext;
	 }

	/**
	 *Obtain the bean from the static variable ApplicationContext and automatically transform it into the type of the assigned object
	 */
	 public static  T getBean(String name){
	 	assertContextInjected();
	 	return (T) applicationContext.getBean(name);
	 }

	/**
	 *Obtain the bean from the static variable ApplicationContext and automatically transform it into the type of the assigned object
	 */
	public static  T getBean(Class clazz){
	 	assertContextInjected();
	 	return (T) applicationContext.getBean(clazz);
	 }

	/**
	 *Implement the disposablebean interface to clean up static variables when the context is closed
	 */
	 public void destroy() throws Exception{
	 	Logger. Debug ("clear ApplicationContext: {}" in springcontext ", ApplicationContext);
	 	applicationContext=null;
	 }

	/**
	 *Implement applicationcontextaware interface and inject context into static variables
	 */
	 public void setApplicationContext(ApplicationContext applicationContext) throws BeanException{
	 	ApplicationContext.applicationContext=applicationContext;
	 }

	/**
	 *Assert that context has been injected
	 */
	 private static void assertContextInjected(){
		Validate. Validstate (ApplicationContext! = null, "ApplicationContext property is not injected, please configure and define applicationcontextcontext in the configuration file");
	}
}
  • Create one in the myshop commons mapper projectRedisCacheTool class for
package com.oxford.myshop.commons.utils;

public class RedisCache implements Cache{
	private static final Logger logger=LoggerFactory.getLogger(RedisCache.class);

	private final ReadWriteLock readWriteLock=new ReentranReadWriteLock();
	private final String id;
	private RedisTemplate redisTemplate;

	private static final long EXPIRE_ TIME_ IN_ MINUTES=30 		//  Redis expiration time

	public RedisCache(String id){
		if(id==null){
			throw new IllegalArgumentException("Cache instances require an ID");
		}
		this.id=id;
	}

	@Override
	public String getId(){
		return id;
	}

	/**
	 * Put query result to redis 
	 */
	@Override
	public void putObject(Object key,Object value){
		try{
			RedisTemplate redisTemplate=getRedisTemplate();
			ValueOperations opsForValue=redisTemplate.opsForValue();
			opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
			logger.debug("Put query result to redis");
		}catch(Throwable t){
			logger.error("Redis put failed",t);
		}
	}

	/**
	 * Get cached query result from redis 
	 */
	 @Override
	 public Object getObject(Object key){
		try{
			RedisTemplate redisTemplate=getRedisTemplate();
			ValueOperations opsForValue=redisTemplate.opsForValue();
			opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
			logger.debug("Get cache query result from redis");
			return opsForValue.get(key);
		}catch(Throwable t){
			logger.error("Redis get failed, fail over to db");
			return null;
		}
	}

	/**
	 * Get cached query result from redis 
	 */
	 @Override
	 public Object getObject(Object key){
		try{
			RedisTemplate redisTemplate=getRedisTemplate();
			ValueOperations opsForValue=redisTemplate.opsForValue();
			opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
			logger.debug("Get cache query result from redis");
			return opsForValue.get(key);
		}catch(Throwable t){
			logger.error("Redis get failed, fail over to db");
			return null;
		}
	}

	/**
	 * Remove cached query result from redis 
	 */
	 @Override
	 @SuppressWarnings("unchecked")
	 public Object removeObject(Object key){
		try{
			RedisTemplate redisTemplate=getRedisTemplate();
			redisTemplate.delete(key);
			logger.debug("Remove cached query result from redis");
		}catch(Throwable t){
			logger.error("Redis remove failed");
		}
			return null;
	}

	/**
	 * Clear this cache instance
	 */
	 @Override
	 public void clear(){
	 	RedisTemplate redisTemplate=getRedisTemplate();
	 	redisTemplate.execute((RedisCallback)->{
	 		connection.flushDb();
	 		return null;
	 	});
	 	logger.debug("Clear all the cached query result from redis");
	 }

	@Override
	public int getSize(){
		return 0;
	}
	
	@Override
	public ReadWriteLock getReadWriteLock(){
		return readWriteLock;
	}

	private RedisTemplate getRedisTemplate(){
		if(redisTemplate==null){
			redisTemplate=ApplicationContextHolder.getBean("redisTemplate");
		}
		return redisTemplate;
	}
}
Annotation in mapper interface class
  • Annotate mapper interface class and declare to use L2 cache
@CacheNamespace(implementation=RedisCache.class)