社区精选|SpringBoot3 集成 Redis
今天小编为大家带来的是社区作者 知了一笑 的文章,让我们一起来学习 SpringBoot3 集成 Redis 的方法。
一、简介
缓存在项目开发中,基本上是必选组件之一,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);
}
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
点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,“公众号后台“回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~
- END -
往期推荐
社区精选|【动画进阶】有意思的 Emoji 3D 表情切换效果
社区精选|借助 input range 实现图片对比功能
社区精选|Spring AOP 中的代理对象是怎么创建出来的?
关注公众号:拾黑(shiheibook)了解更多
赞助链接:
关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
随时掌握互联网精彩
- 韩元汇率对人民币2024年10月21日
- 向“新”而行,乘“数”而上,加快培育新质生产力——访宁波高新区管委会主任徐云
- 澳元兑换人民币汇率2023年11月16日
- GDC 2023报告:Quest平台主导XR市场,但PS VR2关注度升高
- 我在美国问了大家对拼多多美国版Temu的感受
- 本周大新闻|Niantic公布AR社交应用Truffel,DecaGear办公室疑似关闭
- 社区精选 |多行文本下的文字渐隐消失术
- 赛灵思Xilinx Adapt - 中国站12月14日邀您“解锁”5G网络新价值
- 东芝2020财年盈利1139亿日元;微信将不再提供小程序打开App技术服务|Do早报
- 紧急提醒!这个电话一定要接!
- 划重点:2021政府工作报告中的信息通信
- 英超引入 AI 球探,寻找下一个足球巨星