整合 Redis 分布式锁:从数据结构到缓存问题解决方案
引言
在现代分布式系统中,Redis 作为高性能的键值存储系统,广泛应用于缓存、消息队列、实时计数器等多种场景。然而,在高并发和分布式环境下,如何有效地管理和控制资源访问成为一个关键问题。Redis 分布式锁正是为了解决这一问题而诞生的技术。
本文将从 Redis 的数据结构应用入手,结合 Redisson 分布式锁的实现,深入探讨如何解决常见的缓存问题(如穿透、击穿、雪崩),并提供详尽的代码示例和注释。
一、Redis 数据结构应用
Redis 提供了多种数据结构,每种数据结构都有其特定的应用场景。以下是几种常见数据结构及其典型应用场景:
1. String(字符串)
- 应用场景:适用于简单的键值存储,如用户会话、计数器等。
- 示例代码:
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service; @Service
public class CounterService {@Autowired private StringRedisTemplate stringRedisTemplate;public void incrementCounter(String key) {stringRedisTemplate.opsForValue().increment(key, 1);}public Long getCounter(String key) {return stringRedisTemplate.opsForValue().get(key); }
}
increment(key, 1):原子递增计数器。get(key):获取计数器的值。
2. List(列表)
- 应用场景:适用于队列或栈结构,如消息队列、任务队列等。
- 示例代码:
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; @Service
public class QueueService {@Autowired private RedisTemplate<String, String> redisTemplate;public void addToQueue(String queueName, String message) {ListOperations<String, String> listOps = redisTemplate.opsForList(); listOps.rightPush(queueName, message);}public String removeFromQueue(String queueName) {ListOperations<String, String> listOps = redisTemplate.opsForList(); return listOps.leftPop(queueName); }
}
rightPush(queueName, message):将消息添加到队列尾部。leftPop(queueName):从队列头部取出消息。
3. Hash(哈希)
- 应用场景:适用于存储对象或映射表,如用户信息、商品详情等。
- 示例代码:
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; @Service
public class UserService {@Autowired private RedisTemplate<String, Object> redisTemplate;public void saveUser(String userId, Map<String, Object> userMap) {HashOperations<String, String, Object> hashOps = redisTemplate.opsForHash(); hashOps.putAll(userId, userMap);}public Map<String, Object> getUser(String userId) {HashOperations<String, String, Object> hashOps = redisTemplate.opsForHash(); return hashOps.entries(userId); }
}
putAll(userId, userMap):将用户信息存储到哈希中。entries(userId):获取用户的完整信息。
4. Set(集合)
- 应用场景:适用于存储唯一元素的集合,如用户关注列表、标签分类等。
- 示例代码:
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; @Service
public class TagService {@Autowired private RedisTemplate<String, String> redisTemplate;public void addTagToUser(String userId, String tag) {SetOperations<String, String> setOps = redisTemplate.opsForSet(); setOps.add(userId, tag);}public Set<String> getAllTags(String userId) {SetOperations<String, String> setOps = redisTemplate.opsForSet(); return setOps.members(userId); }
}
add(userId, tag):向用户的标签集合中添加一个标签。members(userId):获取用户的全部标签。
5. ZSet(有序集合)
- 应用场景:适用于需要排序的场景,如排行榜、优先级队列等。
- 示例代码:
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; @Service
public class RankingService {@Autowired private RedisTemplate<String, String> redisTemplate;public void addScore(String rankingKey, String user, double score) {ZSetOperations<String, String> zSetOps = redisTemplate.opsForZSet(); zSetOps.add(rankingKey, user, score);}public Set<String> getTopUsers(String rankingKey, int limit) {ZSetOperations<String, String> zSetOps = redisTemplate.opsForZSet(); return zSetOps.reverseRange(rankingKey, 0, limit);}
}
add(rankingKey, user, score):向排行榜中添加用户及其分数。reverseRange(rankingKey, 0, limit):获取排行榜前几名的用户。
二、Redisson 分布式锁
1. 什么是 Redisson?
Redisson 是一个 Redis 的 Java 客户端,提供了许多高级功能,包括分布式锁、分布式集合、分布式消息队列等。它简化了 Redis 的使用,并提供了丰富的功能。
2. 分布式锁的应用场景
在分布式系统中,多个服务实例可能同时访问共享资源(如数据库、文件等),这可能导致数据不一致或竞争条件。分布式锁可以确保在同一时间只有一个服务实例能够访问共享资源。
3. 使用 Redisson 实现分布式锁
步骤 1:添加依赖
在 pom.xml 中添加 Redisson 依赖:
<dependencies><dependency><groupId>org.redisson</groupId> <artifactId>redisson</artifactId><version>3.17.6</version></dependency>
</dependencies>
步骤 2:配置 Redisson
在配置类中配置 Redisson 客户端:
import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class RedissonConfig {@Bean public Redisson redisson() {Config config = new Config();config.useSingleServer() .setAddress("redis://localhost:6379");return Redisson.create(config); }
}
步骤 3:实现分布式锁
import org.redisson.api.RLock;
import org.redisson.api.Redisson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; @Service
public class DistributedLockService {@Autowired private Redisson redisson;public void executeWithLock(String lockName) {RLock lock = redisson.getLock(lockName); try {boolean isLocked = lock.tryLock(10, 1000, TimeUnit.MILLISECONDS);if (isLocked) {// 执行临界区代码 System.out.println("Lock acquired. Executing critical section...");Thread.sleep(2000); // 模拟耗时操作 } else {System.out.println("Failed to acquire lock.");}} catch (InterruptedException e) {Thread.currentThread().interrupt(); } finally {if (lock.isHeldByCurrentThread()) {lock.unlock(); }}}
}
tryLock(10, 1000, TimeUnit.MILLISECONDS):尝试获取锁,最长等待 10 秒,每次轮询间隔 1 秒。unlock():释放锁。
步骤 4:测试分布式锁
@RunWith(SpringRunner.class)
@SpringBootTest
public class DistributedLockServiceTest {@Autowired private DistributedLockService distributedLockService;@Test public void testDistributedLock() throws InterruptedException {// 同时启动多个线程尝试获取锁 Runnable task = () -> distributedLockService.executeWithLock("my_lock"); Thread thread1 = new Thread(task);Thread thread2 = new Thread(task);thread1.start(); thread2.start(); thread1.join(); thread2.join(); }
}
运行后,控制台将显示只有其中一个线程成功获取锁并执行临界区代码。
三、缓存问题解决方案
在实际应用中,缓存可能会遇到以下问题:
1. 缓存穿透
- 问题描述:查询一个不存在的数据,导致每次都去数据库查询。
- 解决方案:
- 缓存空值:将不存在的数据也缓存起来。
- 布隆过滤器:预先过滤不存在的数据。
示例代码(缓存空值):
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; @Service
public class UserService {@Cacheable(value = "users", key = "#id")public User getUserById(Long id) {User user = userRepository.findById(id).orElse(null); if (user == null) {// 缓存空值 return new User();}return user;}
}
2. 缓存击穿
- 问题描述:高并发下同一个热点数据过期,导致大量请求同时访问数据库。
- 解决方案:
- 互斥锁加延迟过期:在更新缓存时加锁,避免多个请求同时更新。
- 永不过期:通过版本号或其他方式实现逻辑过期。
示例代码(互斥锁加延迟过期):
import org.redisson.api.RLock;
import org.redisson.api.Redisson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; @Service
public class UserService {@Autowired private Redisson redisson;@Autowired private UserRepository userRepository;public User getUserById(Long id) {String key = "user:" + id;String value = redisTemplate.opsForValue().get(key); if (value != null) {return JSON.parseObject(value, User.class); }RLock lock = redisson.getLock("lock:" + id);try {boolean isLocked = lock.tryLock(10, 1000, TimeUnit.MILLISECONDS);if (isLocked) {value = redisTemplate.opsForValue().get(key); if (value != null) {return JSON.parseObject(value, User.class); }User user = userRepository.findById(id).orElse(null); if (user != null) {redisTemplate.opsForValue().set(key, JSON.toJSONString(user), 3600L, TimeUnit.SECONDS);} else {// 缓存空值 redisTemplate.opsForValue().set(key, "", 3600L, TimeUnit.SECONDS);}}} catch (InterruptedException e) {Thread.currentThread().interrupt(); } finally {if (lock.isHeldByCurrentThread()) {lock.unlock(); }}return user != null ? user : new User();}
}
3. 缓存雪崩
- 问题描述:大量缓存同时过期,导致数据库压力骤增。
- 解决方案:
- 随机过期时间:为每个缓存设置不同的过期时间。
- 永不过期:通过版本号或其他方式实现逻辑过期。
示例代码(随机过期时间):
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; @Service
public class CacheService {@Autowired private RedisTemplate<String, Object> redisTemplate;public void setValueWithRandomExpire(String key, Object value) {long randomExpireTime = 3600L + (long) (Math.random() * 3600); // 随机过期时间(1-2小时)redisTemplate.opsForValue().set(key, value, randomExpireTime, TimeUnit.SECONDS);}
}
相关文章:
整合 Redis 分布式锁:从数据结构到缓存问题解决方案
引言 在现代分布式系统中,Redis 作为高性能的键值存储系统,广泛应用于缓存、消息队列、实时计数器等多种场景。然而,在高并发和分布式环境下,如何有效地管理和控制资源访问成为一个关键问题。Redis 分布式锁正是为了解决这一问题…...
并查集题目
并查集题目 聚合一块(蓝桥)合根植物(蓝桥)等式方程的可满足性省份数量 并查集(Union-Find)算法是一个专门针对「动态连通性」的算法。双方向的连通。 模板: class UF {// 连通分量个数private …...
日志2025.2.9
日志2025.2.9 1.增加了敌人挥砍类型 2.增加了敌人的死亡状态 在敌人身上添加Ragdoll,死后激活布偶模式 public class EnemyRagdoll : MonoBehaviour { private Rigidbody[] rigidbodies; private Collider[] colliders; private void Awake() { rigidbodi…...
支持多种网络数据库格式的自动化转换工具——VisualXML
一、VisualXML软件介绍 对于DBC、ARXML……文件的编辑、修改等繁琐操作,WINDHILL风丘科技开发的总线设计工具——VisualXML,可轻松解决这一问题,提升工作效率。 VisualXML是一个强大且基于Excel表格生成多种网络数据库文件的转换工具&#…...
Java并发编程笔记
Java并发基础知识补全 启动 启动线程的方式只有: 1、X extends Thread;,然后X.start 2、X implements Runnable;然后交给Thread运行 线程的状态 Java中线程的状态分为6种: 1. 初始(NEW):新创建了一个线程对象&…...
大语言模型实践——基于现有API的二次开发
基于现有的API平台做一些实用的AI小应用。 API服务商:阿里云百炼 云服务器:阿里云(2核2GB) 部署框架:gradio 调用框架:openai 语言:Python (注:若搭建网站或API接口…...
获取程序运行目录 (jar运行目录)
FileSystems.getDefault().getPath("").toAbsolutePath().toString() 和 Path.get(MyClass.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent() 这两个代码片段在Java中用于获取不同的路径,尤其在打包为JAR文件运行时会有显…...
Elasticsearch:如何使用 Elastic 检测恶意浏览器扩展
作者:来着 Elastic Aaron Jewitt 当你的 CISO 询问你的任何工作站上是否安装过特定的浏览器扩展时,你多快能得到正确答案?恶意浏览器扩展是一个重大威胁,许多组织无法管理或检测。这篇博文探讨了 Elastic Infosec 团队如何使用 os…...
Oracle CDB自动处理表空间不足脚本
之前我曾经发过一个自动处理表空间的脚本,可以通过定时任务自动处理表空间不足的问题;但是之前那个脚本没有涵盖CDB模式下的PDB,这里将脚本做了一下更新,可以处理CDB模式下多PDB的表空间问题。 传统模式的脚本请参考这个链接 Or…...
java-list深入理解(流程图)
List源码学习: 此篇文章使用流程图和源码方式,理解List的源码,方便记忆 核心逻辑流程图: #mermaid-svg-BBrPrDuqUdLMtHvj {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-BBrPrDuqUdLMtHvj .error-icon{fill:#…...
Vue 中的 keep-alive 组件是什么?
Vue 中的 keep-alive 组件 keep-alive 是 Vue.js 提供的一个内置组件,用于在组件切换时缓存组件的状态。它可以有效提高用户体验,特别是在需要频繁切换视图的场景中,例如在 SPA(单页面应用)中。 目录 什么是 keep-alive如何使用 keep-alive属性介绍实际示例注意事项总结…...
单元测试的入门实践与应用
单元测试的目的是验证代码中最小的可测试单元(通常为函数或方法)是否按预期运行。它应当独立于系统的其他部分,并专注于特定的功能。 在软件开发中,单元测试是确保代码质量与可维护性的核心环节。优秀的单元测试不仅能帮助开发者…...
【大模型】硅基流动对接DeepSeek使用详解
目录 一、前言 二、硅基流动介绍 2.1 硅基流动平台介绍 2.1.1 平台是做什么的 2.2 主要特点与功能 2.2.1 适用场景 三、硅基流动快速使用 3.1 账户注册 3.2 token获取 3.2.1 获取token技巧 四、Cherry-Studio对接DeepSeek 4.1 获取 Cherry-Studio 4.2 Cherry-Stud…...
[Windows] PDF补丁丁v1.1.0.4627绿色版
[Windows] PDF补丁丁 链接:https://pan.xunlei.com/s/VOIdp50MV2BkOrFott_SCev1A1?pwdvbw4# PDFPatcher 是一款专门用于编辑 PDF 文件的软件,其主要功能包括添加、删除、修改、替换和提取 PDF 文件中的文本、图像、页面等内容,以及支持密码…...
Oracle 变更redo log文件位置
更改Oracle数据库的Redo log文件位置,可以按照以下步骤操作。 1.查询当前Redo log文件信息 select * from v$log; select * from v$logfile;通过查询结果可知Redo log文件放在/oradata/redofile 目录下。 2.拷贝redo log文件到新的位置/Data/redolog $cd /orada…...
使用Redis实现业务信息缓存(缓存详解,缓存更新策略,缓存三大问题)
一、什么是缓存? 缓存是一种高效的数据存储方式,它通过将数据保存在内存中来提供快速的读写访问。这种机制特别适用于需要高速数据访问的应用场景,如网站、应用程序和服务。在处理大量数据和高并发请求时, 缓存能显著提高性能和用户体验。 Redis就是一款常用的缓存中间件。…...
已验证正常,Java输入字符串生成PDF文件
Java输入字符串生成PDF文件过程: 在Java开发中,如何将字符串转换为 PDF 是一个常见的需求。网上找了很多例子都无法生成,经过多次尝试,终于实现了,特此记录一下。 1、引入pom.xml 添加所需的依赖 <dependency>&…...
android手机安装deepseek-r1:1.5b
序 本文主要展示一下如何在android手机上安装deepseek-r1:1.5b 步骤 安装termux 到https://termux.dev/cn/index.html去下载 然后执行termux-setup-storage以获取手机存储权限 安装构建依赖 pkg install git cmake golang下载ollama git clone --depth 1 https://gitee.…...
51单片机俄罗斯方块清屏函数
/************************************************************************************************************** * 名称:LED_Clr * 功能:清屏 * 参数:NULL * 返回:NULL * 备注:temp数组为动态显示数据ÿ…...
PLSQL: 存储过程,用户自定义函数[oracle]
注意: raise notice是高斯的输出语句; DBMS_OUT_PUT.PUT_LINE是oracle的输出语句 存储过程 Stored Procedure 存储过程可以封装数据访问逻辑,使得应用程序可以通过调用存储过程来执行这些逻辑,而不是直接执行SQL语句。这有助于提高代码的可重用性、可…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
python爬虫——气象数据爬取
一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...
怎么开发一个网络协议模块(C语言框架)之(六) ——通用对象池总结(核心)
+---------------------------+ | operEntryTbl[] | ← 操作对象池 (对象数组) +---------------------------+ | 0 | 1 | 2 | ... | N-1 | +---------------------------+↓ 初始化时全部加入 +------------------------+ +-------------------------+ | …...
python基础语法Ⅰ
python基础语法Ⅰ 常量和表达式变量是什么变量的语法1.定义变量使用变量 变量的类型1.整数2.浮点数(小数)3.字符串4.布尔5.其他 动态类型特征注释注释是什么注释的语法1.行注释2.文档字符串 注释的规范 常量和表达式 我们可以把python当作一个计算器,来进行一些算术…...
云原生时代的系统设计:架构转型的战略支点
📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、云原生的崛起:技术趋势与现实需求的交汇 随着企业业务的互联网化、全球化、智能化持续加深,传统的 I…...
