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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 本文分析了在微服务架构项目中SpringBoot框架里的缓存的集成和使用。通过真实的案例说明了如何创建缓存服务,包括创建缓存服务的接口,创建缓存服务的提供者和缓存服务的消费者。重点分析了MyBatis中的二级缓存,主要说明了MyBatis中二级缓存的配置和使用。通过对本文的学习,可以清楚地了解到SpringBoot中的缓存功能以及缓存在MyBatis框架中的应用。

创建缓存服务

创建缓存服务接口项目

  • 创建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)
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
缓存 NoSQL Java
什么是缓存?如何在 Spring Boot 中使用缓存框架
什么是缓存?如何在 Spring Boot 中使用缓存框架
55 0
|
2月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
9天前
|
决策智能 数据库 开发者
使用Qwen2.5+SpringBoot+SpringAI+SpringWebFlux的基于意图识别的多智能体架构方案
本项目旨在解决智能体的“超级入口”问题,通过开发基于意图识别的多智能体框架,实现用户通过单一交互入口使用所有智能体。项目依托阿里开源的Qwen2.5大模型,利用其强大的FunctionCall能力,精准识别用户意图并调用相应智能体。 核心功能包括: - 意图识别:基于Qwen2.5的大模型方法调用能力,准确识别用户意图。 - 业务调用中心:解耦框架与业务逻辑,集中处理业务方法调用,提升系统灵活性。 - 会话管理:支持连续对话,保存用户会话历史,确保上下文连贯性。 - 流式返回:支持打字机效果的流式返回,增强用户体验。 感谢Qwen2.5系列大模型的支持,使项目得以顺利实施。
170 7
使用Qwen2.5+SpringBoot+SpringAI+SpringWebFlux的基于意图识别的多智能体架构方案
|
2月前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
1月前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
122 5
|
1月前
|
缓存 NoSQL Java
Spring Boot中的分布式缓存方案
Spring Boot提供了简便的方式来集成和使用分布式缓存。通过Redis和Memcached等缓存方案,可以显著提升应用的性能和扩展性。合理配置和优化缓存策略,可以有效避免常见的缓存问题,保证系统的稳定性和高效运行。
47 3
|
3月前
|
JSON Java 网络架构
elasticsearch学习四:使用springboot整合 rest 进行搭建elasticsearch服务
这篇文章介绍了如何使用Spring Boot整合REST方式来搭建和操作Elasticsearch服务。
157 4
elasticsearch学习四:使用springboot整合 rest 进行搭建elasticsearch服务
|
3月前
|
监控 Dubbo Java
dubbo学习三:springboot整合dubbo+zookeeper,并使用dubbo管理界面监控服务是否注册到zookeeper上。
这篇文章详细介绍了如何将Spring Boot与Dubbo和Zookeeper整合,并通过Dubbo管理界面监控服务注册情况。
194 0
dubbo学习三:springboot整合dubbo+zookeeper,并使用dubbo管理界面监控服务是否注册到zookeeper上。
|
3月前
|
自然语言处理 Java Maven
elasticsearch学习二:使用springboot整合TransportClient 进行搭建elasticsearch服务
这篇博客介绍了如何使用Spring Boot整合TransportClient搭建Elasticsearch服务,包括项目创建、Maven依赖、业务代码和测试示例。
146 0
elasticsearch学习二:使用springboot整合TransportClient 进行搭建elasticsearch服务
|
3月前
|
JSON 前端开发 Java
Spring Boot框架中的响应与分层解耦架构
在Spring Boot框架中,响应与分层解耦架构是两个核心概念,它们共同促进了应用程序的高效性、可维护性和可扩展性。
74 3