缓存架构中的服务详解!SpringBoot中二级缓存服务的实现
程序员文章站
2022-06-10 13:29: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)