标签:Redis.Mybatis.Lock;

一、简介

缓存在项目开发中,基本上是必选组件之一,Redis作为一个key-value存储系统,具备极高的数据读写效率,并且支持的数据类型比较丰富,在业务场景中的应用非常广泛;

Redis典型的应用场景就是数据缓存能力,用来解决业务中最容易出现的查询性能问题,提升系统的响应效率;其次就是分布式锁机制,用来解决分布式系统中多线程并发处理资源的安全问题;

二、工程搭建

1、工程结构

2、依赖管理

Redis的客户端使用的是lettuce组件的6.2.4.RELEASE版本,该组件具备可扩展和线程安全的特性,支持Redis各种高级功能,如哨兵,集群,流水线,自动重新连接等;

<!-- Redis组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring集成Redis组件 -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
<version>${spring-integration.version}</version>
</dependency>

3、Redis配置

配置文件,在lettuce组件中默认使用的是common-pool2连接池组件,在配置中定义几个常用的核心参数;

spring:
# Redis配置
data:
redis:
host: 127.0.0.1
port: 6379
database: 1
# 读超时
timeout: 3000
# 连接超时
connectTimeout: 5000
# Lettuce连接池
lettuce:
pool:
# 最小空闲连接
min-idle: 5
# 最大空闲连接
max-idle: 10
# 最大连接数
max-active: 100
# 连接分配应该阻塞的最大时间
max-wait: 2000

配置类

@Configuration
public class RedisConfig { /**
* RedisTemplate模板
*/
@Bean("redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
} /**
* StringRedisTemplate模板
*/
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(factory);
stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
return stringRedisTemplate;
} /**
* Redis分布式锁
*/
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory factory){
// 1、锁的密钥前缀:REDIS-LOCK
// 2、锁的过期时间:20秒
return new RedisLockRegistry(factory, "REDIS-LOCK",20000L);
}
}

三、Redis用法

1、环境搭建

Redis文档中推荐使用brew工具安装的方式,本地环境中是4.1版本,可以搜索到redis@6.2,即安装该版本,并且可以使用brew命令管理Redis的启动和停止;

brew --version
Homebrew 4.1.0 brew search redis
==> Formulae
redis redis-leveldb redis@3.2 redis@4.0 redis@6.2 brew install redis@6.2 brew services start redis@6.2
==> Successfully started `redis@6.2` (label: homebrew.mxcl.redis@6.2) brew services restart redis@6.2
==> Successfully stopped `redis@6.2` (label: homebrew.mxcl.redis@6.2)
==> Successfully started `redis@6.2` (label: homebrew.mxcl.redis@6.2) brew services stop redis@6.2
==> Successfully stopped `redis@6.2` (label: homebrew.mxcl.redis@6.2)

配置Redis环境变量,这样可以在命令行使用Redis原生的指令进行启动和客户端操作,在测试完成之后,Mac系统本地的redis@6.2环境就配置好了;

open -e ~/.bash_profile

export REDIS_HOME=/usr/local/opt/redis@6.2
export PATH=$REDIS_HOME/bin:$PATH source ~/.bash_profile redis-server
Redis 6.2.13 ,Running in standalone mode , Port: 6379 , PID: 7966 redis-cli
127.0.0.1:6379> select 1
OK
127.0.0.1:6379> set redis-key redis-value
OK
127.0.0.1:6379> get redis-key
"redis-value"
127.0.0.1:6379> del redis-key
(integer) 1

2、数据类型

Strings:字符串是最基本的数据类型,表示一个字节序列,也是最常使用的类型,单个最大能够存储512MB

public void dataString () throws Exception {
// 1、添加K-V缓存
redisTemplate.opsForValue().set("key-01","value-01",300, TimeUnit.SECONDS);
// 2、添加JSON格式
JsonMapper jsonMapper = new JsonMapper() ;
String jsonVar = jsonMapper.writeValueAsString(new IdValue(1,"Json格式")) ;
redisTemplate.opsForValue().set("key-02",jsonVar,500,TimeUnit.SECONDS);
Object key01 = redisTemplate.opsForValue().get("key-01");
Object key02 = redisTemplate.opsForValue().get("key-02");
log.info("key01:{},key02:{}",key01,key02);
}

Lists:链表结构,可以有序的存储多个字符串值,基于数据左写入右读取的方式可以模拟消息队列,单个最大长度为2^32-1

public void dataList (){
// 1、左侧写数据
redisTemplate.opsForList().leftPushAll("data-list","value-01","value-02","value-03");
// 2、右侧读数据
Object rightPop01 = redisTemplate.opsForList().rightPop("data-list") ;
Object rightPop02 = redisTemplate.opsForList().rightPop("data-list") ;
log.info("rightPop01:{},rightPop02:{}",rightPop01,rightPop02);
}

Sets:用来存储无序的、唯一的字符串集合,通常用来跟踪分析唯一项的业务场景,比如唯一标签下的用户群,独立IP等;

public void dataSet (){
// 1、写数据
redisTemplate.opsForSet().add("data-set","Java","C++","Python","C++");
// 2、读数据
Object pop01 = redisTemplate.opsForSet().pop("data-set");
Object pop02 = redisTemplate.opsForSet().pop("data-set");
log.info("pop01:{},pop02:{}",pop01,pop02);
}

Hashes:Redis本身的K-V结构上,在value中可以再次使用K-V数据类型,在涉及大规模的关系型数据存储时,会优先使用该类型;

public void dataHash (){
// 1、写数据
HashMap<String,String> hashMap = new HashMap<>() ;
hashMap.put("key1","value1") ;
hashMap.put("key2","value2") ;
redisTemplate.opsForHash().putAll("data-hash",hashMap);
// 2、读数据
Object kv1 = redisTemplate.opsForHash().get("data-hash","key1");
Object kv2 = redisTemplate.opsForHash().get("data-hash","key2");
log.info("kv1:{},kv2:{}",kv1,kv2);
}

Sorted-sets:唯一的字符串集合,但是可以通过每个字符串的相关分数来维持顺序,这里的分数可以表示排行顺序,也可以标识为对象的权重或优先级;

public void dataSortedSet (){
// 1、写数据
redisTemplate.opsForZSet().add("sorted-set","Java",1.0);
redisTemplate.opsForZSet().add("sorted-set","Python",3.0);
redisTemplate.opsForZSet().add("sorted-set","C++",2.0);
// 2、读数据
Object popMax = redisTemplate.opsForZSet().popMax("sorted-set");
Object popMin = redisTemplate.opsForZSet().popMin("sorted-set");
log.info("popMax:{},popMin:{}",popMax,popMin);
}

3、加锁机制

使用SETNX方法即代码中的setIfAbsent,如果key不存在,则设置key保存字符串的值,也就相当于SET,当key已经存在时,不执行任何操作;

@Service
public class RedisLockService {
@Resource
private RedisTemplate<String,Object> redisTemplate ; public Boolean lock (String lock,long timeout){
return redisTemplate.opsForValue().setIfAbsent(lock,lock,timeout, TimeUnit.SECONDS);
}
public void unLock (String lock){
Object lockVal = redisTemplate.opsForValue().get(lock);
if (!Objects.isNull(lockVal) && Objects.equals(lockVal.toString(),lock)){
redisTemplate.delete(lock) ;
}
}
}

RedisLockRegistry是由spring-integration组件提供的封装类,基于Redis实现分布式锁的能力;有关于两种锁机制的测试,可以查看代码工程中RedisLockTest单元测试类,

@Service
public class LockRegistryService { @Resource
protected RedisLockRegistry redisLockRegistry; /**
* 尝试一次加锁
*/
@SneakyThrows
public <T> Boolean tryLock(T lockKey, Long time) {
return redisLockRegistry.obtain(lockKey).tryLock(time, TimeUnit.SECONDS);
} /**
* 重试机制多次加锁
*/
@SneakyThrows
public <T> Boolean reTryLock(T lockKey, Long time,int retryNum) {
Boolean lockFlag = tryLock(lockKey, time);
if (Boolean.TRUE.equals(lockFlag)) {
return Boolean.TRUE;
}
for (int i = 0; i < retryNum; i++) {
if (Boolean.TRUE.equals(tryLock(lockKey, time))){
return Boolean.TRUE;
}
}
return Boolean.FALSE;
} /**
* 释放锁
*/
public <T> void unlock(T lockKey) {
redisLockRegistry.obtain(lockKey).unlock();
}
}

四、Mybatis缓存

1、基础配置

在Mybatis框架中,一级缓存是指SqlSession级别内;二级缓存是指Mapper级别内,需要在配置文件中开启,二级缓存并不适用于频繁修改数据的业务场景;

配置文件

mybatis-plus:
configuration:
cache-enabled: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

Mapper注解,这里使用自定义的MybatisCache类,并且使用Redis组件来缓存数据;

@CacheNamespace(implementation = MybatisCache.class)
public interface ArticleMapper extends BaseMapper<Article> { }

2、自定义实现

通过实现Cache接口,来定义二级缓存的处理策略,可以运行MybatisTest测试类中的方法,分析其执行的原理,符合Cache-Aside模式,即先查询缓存,如果未命中就查询数据库,并将结果写入缓存,数据更新时会删除相关缓存;

public class MybatisCache implements Cache {

    private static final Logger log = LoggerFactory.getLogger(MybatisCache.class);

    private RedisTemplate<String,Object> redisTemplate ;

    private String id ;

    public MybatisCache (String id){
log.info("mybatis...cache...id:{}",id);
this.id = id ;
} public RedisTemplate<String,Object> getRedisTemplate(){
if(redisTemplate == null) {
synchronized (MybatisCache.class) {
if(redisTemplate == null) {
redisTemplate = SpringContextUtil.getBean("redisTemplate",RedisTemplate.class);
return redisTemplate;
}
return redisTemplate;
}
}
return redisTemplate ;
}
@Override
public String getId() {
return this.id ;
} @Override
public void putObject(Object key, Object value) {
log.info("mybatis...cache...put...key:{},value:{}",key,value);
this.getRedisTemplate().opsForValue().set(String.valueOf(key),value,5, TimeUnit.MINUTES);
} @Override
public Object getObject(Object key) {
log.info("mybatis...cache...get...key:{}",key);
return this.getRedisTemplate().opsForValue().get(String.valueOf(key)) ;
} @Override
public Object removeObject(Object key) {
log.info("mybatis...cache...remove...key:{}",key);
return this.getRedisTemplate().delete(String.valueOf(key));
} @Override
public void clear() {
Set<String> keys = this.getRedisTemplate().keys("*" + id + "*");
if (keys != null && keys.size()>0){
log.info("mybatis...cache...clear...keys:{}",keys);
this.getRedisTemplate().delete(keys) ;
}
} @Override
public int getSize() {
Set<String> keys = this.getRedisTemplate().keys("*" + id + "*");
if (keys != null){
log.info("mybatis...cache...size...keys:{}",keys.size());
return keys.size() ;
}
return 0;
}
}

五、参考源码

文档仓库:
https://gitee.com/cicadasmile/butte-java-note 源码仓库:
https://gitee.com/cicadasmile/butte-spring-parent

SpringBoot3集成Redis的更多相关文章

  1. 【springBoot】springBoot集成redis的key,value序列化的相关问题

    使用的是maven工程 springBoot集成redis默认使用的是注解,在官方文档中只需要2步; 1.在pom文件中引入即可 <dependency> <groupId>o ...

  2. SpringBoot集成redis的key,value序列化的相关问题

    使用的是maven工程 springBoot集成redis默认使用的是注解,在官方文档中只需要2步; 1.在pom文件中引入即可 <dependency> <groupId>o ...

  3. spring集成redis

    redis是一种非关系型数据库,与mongoDB不同的是redis是内存数据库,所以访问速度很快.常用作缓存和发布-订阅式的消息队列.redis官方没有提供windows版本的软件.windows版本 ...

  4. springboot集成redis(mybatis、分布式session)

    安装Redis请参考:<CentOS快速安装Redis> 一.springboot集成redis并实现DB与缓存同步 1.添加redis及数据库相关依赖(pom.xml) <depe ...

  5. Windows环境下springboot集成redis的安装与使用

    一,redis安装 首先我们需要下载Windows版本的redis压缩包地址如下: https://github.com/MicrosoftArchive/redis/releases 连接打开后如下 ...

  6. Springboot集成Redis步骤

    Spring boot 集成Redis的步骤如下: 1.在pom.xml中配置相关的jar依赖: <!--加载spring boot redis包 --> <dependency&g ...

  7. spring boot集成redis的血泪史

    首先说明环境不是我搭建的,然后因项目需要添加redis的时候,麻烦来了.springboot 用的是1.5.9因为以前弄过redis,所以直接拿过来,麻烦了首先是莫名的错误,连项目都启动不了.但是最后 ...

  8. Spring Boot 项目实战(四)集成 Redis

    一.前言 上篇介绍了接口文档工具 Swagger 及项目监控工具 JavaMelody 的集成过程,使项目更加健壮.在 JAVA Web 项目某些场景中,我们需要用缓存解决如热点数据访问的性能问题,业 ...

  9. SpringBoot集成Redis

    1.引入 spring-boot-starter-redis <dependency> <groupId>redis.clients</groupId> <a ...

  10. Springboot 2.0 - 集成redis

    序 最近在入门SpringBoot,然后在感慨 SpringBoot较于Spring真的方便多时,顺便记录下自己在集成redis时的一些想法. 1.从springboot官网查看redis的依赖包 & ...

随机推荐

  1. 2021-01-08:cpu和gpu有什么区别?

    福哥答案2021-01-08:[答案来自此链接:](https://www.cnblogs.com/biglucky/p/4223565.html)Cache, local memory: CPU & ...

  2. 2021-06-20:已知一个消息流会不断地吐出整数 1~N,但不一定按照顺序依次吐出。如果上次打印的序号为i, 那么当i+1出现时,请打印 i+1 及其之后接收过的并且连续的所有数,直到1~N全部接

    2021-06-20:已知一个消息流会不断地吐出整数 1~N,但不一定按照顺序依次吐出.如果上次打印的序号为i, 那么当i+1出现时,请打印 i+1 及其之后接收过的并且连续的所有数,直到1~N全部接 ...

  3. Maven安装、阿里云镜像配置、Idea中创建Maven项目、Tomcat的部署

    我为什么要学这个技术? 为了更快创建web开发项目和jar包 在javaweb开发中,需要使用大量的jar包,我们手动去导入: 如何能够让一个东西自动帮我导入和配置这个jar包 因此,Maven诞生了 ...

  4. ABP - 模块加载机制

    Abp是一个基于模块化开发的应用程序框架,提供了模块化基础的架构和模块化加载的引擎. 理解模块 一个模块是对一个功能点的封装,可以独立成为一个包,实现了松耦合的代码组织方式.Abp框架的基本思想就是模 ...

  5. Abp Vnext 动态(静态)API客户端源码解析

    根据以往的经验,通过接口远程调用服务的原理大致如下: 服务端:根据接口定义方法的签名生成路由,并暴露Api. 客户端:根据接口定义方法的签名生成请求,通过HTTPClient调用. 这种经验可以用来理 ...

  6. linux 条件语句和逻辑判断

    目录 一.条件判断 二.逻辑判断 三.if和case 四.七个实验 一.条件判断 1.test测试 test [ 条件表达式 ] -e:测试目录是否存在 -d:测试是否为目录    -f:是否为文件 ...

  7. 逍遥自在学C语言 | break-循环的中断与跳转

    前言 在C语言中,break语句是一种控制流语句,它用于终止当前所在的循环结构(for.while.do-while)或者switch语句,从而跳出循环或者结束switch语句的执行. 一.人物简介 ...

  8. 在linux上启动arthas报“Can not find java process”

    发生背景 完整报错信息: [***@localhost ~]$ java -jar arthas-boot.jar [INFO] JAVA_HOME: /usr/lib/jvm/java-1.8.0- ...

  9. 逍遥自在学C语言 | 函数初级到高级解析

    前言 函数是C语言中的基本构建块之一,它允许我们将代码组织成可重用.模块化的单元. 本文将逐步介绍C语言函数的基础概念.参数传递.返回值.递归以及内联函数和匿名函数. 一.人物简介 第一位闪亮登场,有 ...

  10. Kafka中的消费者Offset

    消费者位移 每个 consumer 实例都会为它消费的分区维护属于自己的位置信息来记录当前消费了多少条消息.这在 Kafka 中有一个特有的术语:位移(offset). 相比较将offset保存在服务 ...