欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

缓存架构中的服务详解!SpringBoot中二级缓存服务的实现

程序员文章站 2022-03-03 09:47:35
创建缓存服务 创建缓存服务接口项目 创建myshop-service-redis-api项目,该项目只负责定义接口 创建项目的pom.xml:

创建缓存服务

创建缓存服务接口项目

  • 创建myshop-service-redis-api项目,该项目只负责定义接口
  • 创建项目的pom.xml:
<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
         xsi:schemalocation="http://maven.apache.org/pom/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelversion>4.0.0</modelversion>
    <parent>
        <groupid>com.oxford</groupid>
        <artifactid>myshop-dependencies</artifactid>
        <version>1.0.0-snapshot</version>
        <relativepath>../myshop-dependencies/pom.xml</relativepath>
    </parent>

    <artifactid>myshop-service-redis-api</artifactid>
    <packaging>jar</packaging>
</project>
  • 定义数据redis接口redisservice:
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);
}

创建缓存服务提供者项目

  • 创建myshop-service-redis-provider项目,该项目用作缓存服务提供者
  • 创建项目的pom.xml:
<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
         xsi:schemalocation="http://maven.apache.org/pom/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelversion>4.0.0</modelversion>
    <parent>
        <groupid>com.oxford</groupid>
        <artifactid>myshop-dependencies</artifactid>
        <version>1.0.0-snapshot</version>
        <relativepath>../myshop-dependencies/pom.xml</relativepath>
    </parent>

    <artifactid>myshop-service-redis-api</artifactid>
    <packaging>jar</packaging>

	<dependencies>
		<!-- spring boot starter settings-->
		<dependency>
			<groupid>org.springframework.boot</groupid>
			<artifactid>spring-boot-starter-data-redis</artifactid>
		</dependency>

		<dependency>
			<groupid>org.springframework.boot</groupid>
			<artifactid>spring-boot-starter-test</artifactid>
			<scope>test</scope>
		</dependency>


		<!--common setting-->
		<dependency>
			<groupid>org.apache.commons</groupid>
			<artifactid>commons-pool2</artifactid>
		</dependency>
		<dependency>
			<groupid>de.javakaffee</groupid>
			<artifactid>kryo-serializers</artifactid>
		</dependency>

		<!--project settings-->
		<dependency>
			<groupid>com.oxford</groupid>
			<artifactid>my-shop-commons-dubbo</artifactid>
			<version>${project.parent.version}</version>
		</dependency>
		<dependency>
			<groupid>com.oxford</groupid>
			<artifactid>my-shop-service-redis-api</artifactid>
			<version>${project.parent.version}</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupid>org.springframework.boot</groupid>
				<artifactid>spring-boot-maven-plugin</artifactid>
				<configuration>
					<mainclass>com.oxford.myshop.service.redis.provider.myshopserviceredisproviderapplication</mainclass>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

redis底层实现的java的lettuce客户端

  • 创建缓存服务接口实现类redisserviceimpl
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);
	}
}
  • 创建启动类springbootapplication
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);
    }
}
  • 创建配置文件application.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

创建缓存服务消费者项目

  • 在pom文件中引入redis接口依赖
  • 在缓存服务消费者项目的serviceimpl中调用redisservice
@reference(version="services.versions.redis.v1")
private redisservice redisservice;

mybatis redis二级缓存

mybatis缓存

  • 一级缓存:
    • mybatis会在表示会话的sqlsession对象中建立一个简单的缓存: 将每次查询到的结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询
    • 一级缓存是sqlsession级别的缓存:
      • 在操作数据库时需要构造sqlsession对象
      • 对象中有一个(内存区域)数据结构(hashmap)用于存储缓存数据
      • 不同的sqlsession之间的缓存数据区域(hashmap)互不影响,
      • 一级缓存的作用域是同一个sqlsession
      • 在同一个sqlsession中两次执行相同的sql语句: 第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据,将不再从数据库查询,从而提高查询效率
      • 当一个sqlsession结束后该sqlsession中的一级缓存就不存在了
      • mybatis默认开启一级缓存
  • 二级缓存:
    • 二级缓存是mapper级别的缓存: 多个sqlsession去操作同一个mapper的sql语句,多个sqlsession去操作数据库得到数据会存在二级缓存区域,多个sqlsession可以共用二级缓存,二级缓存是跨sqlsession的
    • 二级缓存的作用域是mapper的同一个namespace
    • 不同的sqlsession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句: 第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率
    • mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存

配置mybatis二级缓存

springboot中开启mybatis二级缓存
  • 在myshop-service-user-provider的配置文件中开启mybatis二级缓存
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
  • 在myshop-commons-mapper的pom.xml中增加redis依赖:
<dependency>
	<groupid>org.springframework.boot</groupid>
	<artifactid>spring-boot-starter-data-redis</artifacted>
</dependency>

<dependency>
	<groupid>org.apache.commons</groupid>
	<artifactid>commons-pool2</artifacted>
</dependency>
实体类实现序列化接口并声明序列号
private static final long serialversionuid = 82897704415244673535l
idea生成序列号方法:
- 使用generateserialversionuid插件生成,安装完插件后在实现了序列化接口的类中
- 使用快捷键alt+insert即可呼出生成菜单,即可自动生成序列号
实现mybatis cache接口,自定义缓存为redis
  • 在myshop-commons项目中创建applicationcontextholder类
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;

	/**
	 * 获取存储在静态变量中的applicationcontext
	 */
	 public static applicationcontext getapplicationcontext(){
	 	assertcontextinjected();
	 	return applicationcontext;
	 }

	/**
	 * 从静态变量applicationcontext中获取bean,自动转型成所赋值对象的类型
	 */
	 public static <t> t getbean(string name){
	 	assertcontextinjected();
	 	return (t) applicationcontext.getbean(name);
	 }

	/**
	 * 从静态变量applicationcontext中获取bean,自动转型成所赋值对象的类型
	 */
	public static <t> t getbean(class<t> clazz){
	 	assertcontextinjected();
	 	return (t) applicationcontext.getbean(clazz);
	 }

	/**
	 * 实现disposablebean接口,在context关闭时清理静态变量
	 */
	 public void destroy() throws exception{
	 	logger.debug("清除 springcontext 中的 applicationcontext: {}",applicationcontext);
	 	applicationcontext=null;
	 }

	/**
	 * 实现applicationcontextaware接口,注入context到静态变量中
	 */
	 public void setapplicationcontext(applicationcontext applicationcontext) throws beanexception{
	 	applicationcontext.applicationcontext=applicationcontext;
	 }

	/**
	 * 断言context已经注入
	 */
	 private static void assertcontextinjected(){
		validate.validstate(applicationcontext !=null,"applicationcontext 属性未注入,请在配置文件中配置定义applicationcontextcontext");
	}
}
  • 在myshop-commons-mapper项目中创建一个rediscache的工具类
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过期时间

	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;
	}
}
mapper接口类中标注注解
  • 在mapper接口类上标注注解,声明使用二级缓存
@cachenamespace(implementation=rediscache.class)