Redis常用数据结构以及多并发场景下的使用分析:Hash类型

文章目录

  • 前言
  • hash 对比 String
    • 简单存储对象
    • 【秒杀系统】- 商品库存管理
    • 【用户会话管理】- 分布式Session存储
    • 【信息预热】- 首页信息预热
    • 降级策略
  • 总结

前言

上文我们分析了String类型 在多并发下的应用 本文该轮到 Hash了,期不期待 兄弟们 hhh

Redis常用数据结构以及多并发场景下的使用分析:String类型

okok 那么hash 相对于String类型有哪些优势呢?

hash 对比 String

举一个简单的例子:
你可以看到 hash 在面对存在结构化的数据 会更有优势 方便统一管理

适合存储对象

// 单独String
redisTemplate.opsForValue().set("user:123:name", "张三");
redisTemplate.opsForValue().set("user:123:age", "25");
redisTemplate.opsForValue().set("user:123:city", "北京");
// 问题:3次网络往返 + 3个key占用更多内存// 一个HashMap
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("name", "张三");
userInfo.put("age", "25");
userInfo.put("city", "北京");redisTemplate.opsForHash().putAll("user:123", userInfo);
// 优势:1次网络往返 + 内存更紧凑

简单存储对象

@Service
@RequiredArgsConstructor
public class HashRedisService {private final RedisTemplate<String, Object> redisTemplate;// 商品属性存储public void saveProduct(String productId, Product product) {String key = "product:" + productId;Map<String, Object> productMap = new HashMap<>();productMap.put("name", product.getName());productMap.put("price", product.getPrice().toString());productMap.put("category", product.getCategory());productMap.put("stock", product.getStock().toString());redisTemplate.opsForHash().putAll(key, productMap);}}

在这里插入图片描述

【秒杀系统】- 商品库存管理

设计思路:

使用Hash存储商品库存信息,避免热点key问题
Key: seckill:stock:date
Field: productId
Value: stock

Lua脚本 原子化扣减库存Value
·1. 获取当前库存(HGET key field)
·2. 判断商品是否存在
·3. 判断库存是否足够
·4. 使用 HINCRBY 扣减库存(负数)
使用 HMGET 命令批量获取库存 例如
·HMGET seckill:stock:2025-07-08 1001 1002 1003

// 1. 【秒杀系统】- 商品库存管理
@Service
@RequiredArgsConstructor
@Slf4j
public class SeckillStockService {private final RedisTemplate<String, Object> redisTemplate;/*** 使用Hash存储商品库存信息,避免热点key问题* Key: seckill:stock:date* Field: productId* Value: stock*/public void initSeckillStock(String date, Map<String, Integer> productStocks) {String stockKey = "seckill:stock:" + date;// 批量初始化库存,比逐个set效率高很多Map<String, Object> stockMap = new HashMap<>();productStocks.forEach((productId, stock) -> {stockMap.put(productId, stock);});redisTemplate.opsForHash().putAll(stockKey, stockMap);redisTemplate.expire(stockKey, Duration.ofDays(1));log.info("初始化秒杀库存完成,商品数量: {}", productStocks.size());}/*** 高并发扣减库存 - 使用Lua脚本保证原子性*/public boolean decrementStock(String date, String productId, int quantity) {String stockKey = "seckill:stock:" + date;String luaScript = """local stockKey = KEYS[1]local productId = KEYS[2] local quantity = tonumber(ARGV[1])local currentStock = redis.call('HGET', stockKey, productId)if currentStock == false thenreturn -1  -- 商品不存在endcurrentStock = tonumber(currentStock)if currentStock < quantity thenreturn 0   -- 库存不足endredis.call('HINCRBY', stockKey, productId, -quantity)return 1      -- 扣减成功""";DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);Long result = redisTemplate.execute(script, Arrays.asList(stockKey, productId),quantity);return result != null && result == 1;}/*** 批量查询库存状态 - 单次查询多个商品*/public Map<String, Integer> batchGetStock(String date, List<String> productIds) {String stockKey = "seckill:stock:" + date;// 使用HMGet批量获取,比多次HGet效率高List<Object> stocks = redisTemplate.opsForHash().multiGet(stockKey,new ArrayList<>(productIds));Map<String, Integer> result = new HashMap<>();for (int i = 0; i < productIds.size(); i++) {Object stock = stocks.get(i);result.put(productIds.get(i), stock != null ? (Integer) stock : 0);}return result;}
}

写一个测试类 去测试 这个扣减库存的逻辑

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class SeckillStockServiceTest {@Autowiredprivate SeckillStockService seckillStockService;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;private static final String TEST_DATE = "2025-07-07";@Test@Order(1)void testInitSeckillStock() {Map<String, Integer> stockMap = new HashMap<>();stockMap.put("p1001", 10);stockMap.put("p1002", 5);stockMap.put("p1003", 0);seckillStockService.initSeckillStock(TEST_DATE, stockMap);Map<String, Integer> result = seckillStockService.batchGetStock(TEST_DATE, List.of("p1001", "p1002", "p1003"));assertEquals(10, result.get("p1001"));assertEquals(5, result.get("p1002"));assertEquals(0, result.get("p1003"));}@Test@Order(2)void testDecrementStockSuccess() {boolean success = seckillStockService.decrementStock(TEST_DATE, "p1001", 2);assertTrue(success);Map<String, Integer> result = seckillStockService.batchGetStock(TEST_DATE, List.of("p1001"));assertEquals(8, result.get("p1001"));}@Test@Order(3)void testDecrementStockFailDueToNotEnough() {boolean success = seckillStockService.decrementStock(TEST_DATE, "p1003", 1);assertFalse(success);  // 原库存是 0,无法扣减}@Test@Order(4)void testDecrementStockFailDueToNonExistProduct() {boolean success = seckillStockService.decrementStock(TEST_DATE, "p9999", 1);assertFalse(success);  // 商品不存在,Lua 返回 -1,也处理为 false}}

在这里插入图片描述

【用户会话管理】- 分布式Session存储

设计思路:

用户登录 - 创建分布式Session
Key: session:userId
Fields: token, loginTime, lastActiveTime, deviceInfo, permissions…
Value: token_value,loginTime_value…
Lua脚本 权限检查 - 快速获取用户权限
·1. HGET session:{userId} permissions
·2. 判断权限信息是否存在
Lua脚本 更新用户活跃时间 - 只更新单个字段
·1. HSET session:123 lastActiveTime 1720492341255

// 2. 【用户会话管理】- 分布式Session存储
@Service
@RequiredArgsConstructor
public class UserSessionService {private final RedisTemplate<String, Object> redisTemplate;/*** 用户登录 - 创建分布式Session* Key: session:userId* Fields: token, loginTime, lastActiveTime, deviceInfo, permissions...*/public String createUserSession(String userId, String deviceInfo, Set<String> permissions) {String sessionKey = "session:" + userId;String token = generateToken();long currentTime = System.currentTimeMillis();Map<String, Object> sessionData = new HashMap<>();sessionData.put("token", token);sessionData.put("loginTime", currentTime);sessionData.put("lastActiveTime", currentTime);sessionData.put("deviceInfo", deviceInfo);sessionData.put("permissions", String.join(",", permissions));sessionData.put("status", "active");// 一次性存储所有session数据redisTemplate.opsForHash().putAll(sessionKey, sessionData);redisTemplate.expire(sessionKey, Duration.ofHours(24));return token;}/*** 更新用户活跃时间 - 只更新单个字段*/public void updateLastActiveTime(String userId) {String sessionKey = "session:" + userId;redisTemplate.opsForHash().put(sessionKey, "lastActiveTime", System.currentTimeMillis());// 延长session过期时间redisTemplate.expire(sessionKey, Duration.ofHours(24));}/*** 权限检查 - 快速获取用户权限*/public boolean hasPermission(String userId, String permission) {String sessionKey = "session:" + userId;Object permissions = redisTemplate.opsForHash().get(sessionKey, "permissions");if (permissions == null) return false;String permissionStr = (String) permissions;return Arrays.asList(permissionStr.split(",")).contains(permission);}/*** 批量获取在线用户信息*/public Map<String, Map<Object, Object>> batchGetUserSessions(List<String> userIds) {Map<String, Map<Object, Object>> result = new HashMap<>();// 使用Pipeline批量获取,避免多次网络往返List<Object> sessionData = redisTemplate.executePipelined(new SessionCallback<Object>() {@Override@SuppressWarnings("unchecked")public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {for (String userId : userIds) {operations.opsForHash().entries((K) ("session:" + userId));}return null;}});for (int i = 0; i < userIds.size(); i++) {@SuppressWarnings("unchecked")Map<Object, Object> session = (Map<Object, Object>) sessionData.get(i);if (session != null && !session.isEmpty()) {result.put(userIds.get(i), session);}}return result;}private String generateToken() {return UUID.randomUUID().toString().replace("-", "");}
}

写一个测试类去测试获取权限信息

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class UserSessionServiceTest {@Autowiredprivate UserSessionService userSessionService;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Test@Order(1)void testCreateUserSession() {String userId = "user123";String deviceInfo = "iPhone 15 Pro";Set<String> permissions = Set.of("read", "write", "admin");String token = userSessionService.createUserSession(userId, deviceInfo, permissions);assertNotNull(token);assertFalse(token.isEmpty());// 验证session数据是否正确存储String sessionKey = "session:" + userId;Map<Object, Object> sessionData = redisTemplate.opsForHash().entries(sessionKey);assertEquals(token, sessionData.get("token"));assertEquals(deviceInfo, sessionData.get("deviceInfo"));
//        assertEquals("read,write,admin", sessionData.get("permissions"));assertEquals("active", sessionData.get("status"));System.out.println("用户会话创建成功,Token: " + token);}@Test@Order(2)void testUpdateLastActiveTime() throws InterruptedException {String userId = "user123";// 获取初始时间Object initialTime = redisTemplate.opsForHash().get("session:" + userId, "lastActiveTime");Thread.sleep(10); // 等待一小段时间// 更新活跃时间userSessionService.updateLastActiveTime(userId);// 验证时间是否更新Object updatedTime = redisTemplate.opsForHash().get("session:" + userId, "lastActiveTime");assertNotEquals(initialTime, updatedTime);System.out.println("用户活跃时间更新成功");}@Test@Order(3)void testHasPermission() {String userId = "user123";assertTrue(userSessionService.hasPermission(userId, "read"));assertTrue(userSessionService.hasPermission(userId, "write"));assertTrue(userSessionService.hasPermission(userId, "admin"));assertFalse(userSessionService.hasPermission(userId, "delete"));System.out.println("权限检查功能正常");}@Test@Order(4)void testBatchGetUserSessions() {// 创建多个用户会话String userId2 = "user456";String userId3 = "user789";userSessionService.createUserSession(userId2, "Android Phone", Set.of("read"));userSessionService.createUserSession(userId3, "MacBook Pro", Set.of("read", "write"));// 批量获取用户会话List<String> userIds = List.of("user123", userId2, userId3, "nonexistent");Map<String, Map<Object, Object>> sessions = userSessionService.batchGetUserSessions(userIds);assertEquals(3, sessions.size()); // 应该返回3个存在的用户会话assertTrue(sessions.containsKey("user123"));assertTrue(sessions.containsKey(userId2));assertTrue(sessions.containsKey(userId3));assertFalse(sessions.containsKey("nonexistent"));// 验证批量获取的数据正确性Map<Object, Object> user123Session = sessions.get("user123");assertEquals("iPhone 15 Pro", user123Session.get("deviceInfo"));assertEquals("active", user123Session.get("status"));System.out.println("批量获取用户会话功能正常");System.out.println("获取到 " + sessions.size() + " 个用户会话");}//    @AfterAll
//    static void cleanup(@Autowired RedisTemplate<String, Object> redisTemplate) {
//        // 清理测试数据
//        redisTemplate.delete("session:user123");
//        redisTemplate.delete("session:user456");
//        redisTemplate.delete("session:user789");
//        System.out.println("测试数据清理完成");
//    }
} 

在这里插入图片描述

【信息预热】- 首页信息预热

设计思路:

缓存预热就是在系统启动时就“主动把常用数据装进 Redis”,让用户访问时“直接命中缓存” 一下是一个简单的使用场景

// 【缓存预热】- 提升系统启动速度
@Service
@RequiredArgsConstructor
public class CacheWarmupService {private final RedisTemplate<String, Object> redisTemplate;private final ProductService productService;private final UserService userService;/*** 商品信息缓存预热*/@EventListener(ApplicationReadyEvent.class)public void warmupProductCache() {log.info("开始商品缓存预热...");// 首先从数据库 获取热门商品列表List<Product> hotProducts = productService.getHotProducts(1000);// 批量缓存商品信息Map<String, Map<String, Object>> productBatch = new HashMap<>();for (Product product : hotProducts) {Map<String, Object> productInfo = new HashMap<>();productInfo.put("name", product.getName());productInfo.put("price", product.getPrice().toString());productInfo.put("category", product.getCategory());productInfo.put("brand", product.getBrand());productInfo.put("stock", product.getStock());productInfo.put("sales", product.getSales());productInfo.put("rating", product.getRating());productBatch.put("product:" + product.getId(), productInfo);}// 使用Pipeline批量预热redisTemplate.executePipelined(new SessionCallback<Object>() {@Overridepublic <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {productBatch.forEach((key, productInfo) -> {operations.opsForHash().putAll((K) key, productInfo);operations.expire((K) key, Duration.ofHours(6));});return null;}});log.info("商品缓存预热完成,缓存商品数量: {}", hotProducts.size());}}

降级策略

那么如果当redis不可用了 该怎么处理呢?那么就应该使用多级缓存的思想 楼主后续也会专门写一个文章去讲解多级缓存 请期待 这里首先给出一个简单的 降级策略 代码片段

public Map<String, Object> getProductInfoWithFallback(String productId) {try {// 优先从 Redis 缓存中读取商品详情return redisTemplate.opsForHash().entries("product:" + productId);} catch (Exception e) {// Redis 报错时(如连接失败、超时等),降级处理log.warn("Redis查询失败,降级到数据库", e);// 从数据库中查询return productService.getFromDatabase(productId);}
}

总结

使用hash结构去存储结构化的数据 例如 本质都是一种缓存的思想

网页的首页展示 (你想想不可能去数据库查询吧 响应太慢了)
电商系统 库存管理
用户系统 用户信息权限管理
排行榜 管理点赞数量
配置中心 …

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/90247.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/90247.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

双因子认证(2FA)是什么?从零设计一个安全的双因子登录接口

前言在信息系统逐渐走向数字化、云端化的今天&#xff0c;账号密码登录已不再是足够安全的手段。数据泄露、撞库攻击、社工手段频发&#xff0c;仅靠「你知道的密码」已不足以保证账户安全。因此&#xff0c;双因子认证&#xff08;2FA, Two-Factor Authentication&#xff09;…

stack栈练习

为了你&#xff0c;我变成狼人模样&#xff1b; 为了你&#xff0c;染上了疯狂~ 目录stack栈练习栈括号的分数单调栈模板框架小结下一个更大元素 I&#xff08;单调栈哈希&#xff09;接雨水stack栈练习 栈 一种先进后出的线性数据结构 具体用法可参考往期文章或者维基介绍i…

详细页智能解析算法:洞悉海量页面数据的核心技术

详细页智能解析算法&#xff1a;突破网页数据提取瓶颈的核心技术剖析引言&#xff1a;数字时代的数据采集革命在当今数据驱动的商业环境中&#xff0c;详细页数据已成为企业决策的黄金资源。无论是电商商品详情、金融公告还是新闻资讯&#xff0c;​​有效提取结构化信息​​直…

ubuntu环境如何安装matlab2016

一、下载安装文件&#xff08;里面包含激活包CRACK&#xff09;可从度盘下载&#xff1a;链接:https://pan.baidu.com/s/1wxmVMzXiSY4RIT0dyKkjZg?pwd26h6 复制这段内容打开「百度网盘APP 即可获取」注&#xff1a;这里面包含三个文件&#xff0c;其中ISO包含安装文件&#x…

Mybits-plus 表关联查询,嵌套查询,子查询示例演示

在 MyBatis-Plus 中实现表关联查询、嵌套查询和子查询&#xff0c;通常需要结合 XML 映射文件或 Select 注解编写自定义 SQL。以下是具体示例演示&#xff1a;示例场景 假设有两张表&#xff1a; 用户表 userCREATE TABLE user (id BIGINT PRIMARY KEY,name VARCHAR(50),age IN…

Stable Diffusion Web 环境搭建

默认你的系统Ubuntu、CUDA、Conda等都存在&#xff0c;即具备运行深度学习模型的基础环境 本人&#xff1a;Ubuntu22.04、CUDA11.8环境搭建 克隆项目并且创建环境 https://github.com/AUTOMATIC1111/stable-diffusion-webui conda create -n sd python3.10运行过程自动安装依赖…

嵌入式系统中实现串口重定向

在嵌入式系统中实现串口重定向&#xff08;将标准输出如 printf 函数输出重定向到串口&#xff09;通常有以下几种常用方法&#xff0c;下面结合具体代码示例和适用场景进行说明&#xff1a; 1. 重写 fputc 函数&#xff08;最常见、最基础的方法&#xff09; 通过重写标准库中…

static补充知识点-代码

public class Student {private static int age;//静态的变量private double score;//非静态的方法public void run(){}public static void go(){}public static void main(String[] args) {new Student().run();Student.go();} } public class Person {//2 &#xff1a; 赋初始…

使用泛型<T>,模块化,反射思想进行多表数据推送

需求&#xff1a;有13个表&#xff0c;其中一个主表和12细表&#xff0c;主表用来记录推送状态&#xff0c;细表记录12种病例的详细信息&#xff0c;现在需要把这12张病例表数据进行数据推送&#xff1b;普通方法需要写12个方法分别去推送数据然后修改状态&#xff1b;现在可以…

光流 | RAFT光流算法如何改进提升

RAFT(Recurrent All-Pairs Field Transforms)作为ECCV 2020最佳论文,已成为光流估计领域的标杆模型。其通过构建4D相关体金字塔和GRU迭代优化机制,在精度与泛化性上实现了突破。但针对其计算效率、大位移处理、跨场景泛化等问题,研究者提出了多维度改进方案,核心方向可系…

linux/ubuntu日志管理--/dev/log 的本质与作用

文章目录 **一、基本概念****二、技术细节:UNIX域套接字****三、在不同日志系统中的角色****四、应用程序如何使用 `dev/log`****五、查看和验证 `/dev/log`****六、总结 `/dev/log` 的核心作用**一、基本概念 /dev/log 是一个 UNIX域套接字(Unix Domain Socket),是Linux系…

EMC整改案例之(1):汽车NFC进入模块BCI整改

EMC整改案例(1):汽车NFC进入模块BCI整改 在汽车电子系统中,NFC(Near Field Communication)进入模块用于实现无钥匙进入功能,但它在电磁兼容(EMC)测试中常面临挑战。本案例聚焦于BCI(Bulk Current Injection)测试整改,该测试模拟大电流注入对设备的影响。以下是基于…

2025年INS SCI2区,灵活交叉变异灰狼算法GWO_C/M+集群任务调度,深度解析+性能实测

目录1.摘要2.灰狼算法GWO原理3.灵活交叉变异灰狼算法GWO_C/M4.结果展示5.参考文献6.代码获取7.算法辅导应用定制读者交流1.摘要 随着云计算的快速发展&#xff0c;受自然现象启发的任务调度算法逐渐成为研究的热点。灰狼算法&#xff08;GWO&#xff09;因其强大的收敛性和易于…

Java常用加密算法详解与实战代码 - 附可直接运行的测试示例

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Micro麦可乐的博客 &#x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程&#xff0c;入门到实战 &#x1f33a;《RabbitMQ》…

2025开发者工具链革命:AI赋能的效率跃迁

目录引言&#xff1a;效率焦虑下的开发者生存现状一、智能代码编辑器&#xff1a;从辅助到主导的进化1.1 GitHub Copilot&#xff1a;全能型AI助手1.2 Cursor Pro&#xff1a;极致编码体验1.3 飞算JavaAI&#xff1a;垂直领域颠覆者二、版本控制革命&#xff1a;Git的AI进化论2…

“虚空”的物理、哲学悖论

一、虚空并非“完全真空”&#xff1a;量子场论揭示的“真空不空” 物理真空的本质 现代物理学中的“真空”并非绝对的空无一物&#xff0c;而是量子场的基态&#xff08;能量最低状态&#xff09;。根据量子场论&#xff1a; 虚粒子涨落&#xff1a;真空中持续发生量子涨落&am…

CSP-S模拟赛二总结(实际难度大于CSP-S)

T1 很简短&#xff0c;也很好做&#xff0c;第一题直接场切。 我的方法 首先要明确一件事&#xff1a;就是如果选了 ax,ya_{x,y}ax,y​&#xff0c;那么就必然要选 ay,xa_{y,x}ay,x​&#xff0c;所以第一步就在 ax,ya_{x,y}ax,y​ 的基础上加上 ay,xa_{y,x}ay,x​。 然后我…

旋转屏幕优化

1.问题背景 从google原生算法&#xff0c;可以知道其有2个比较大的缺陷&#xff1a; 1) 通过重力传感器传来的x&#xff0c;y&#xff0c;z轴的加速度合成之后只有一个垂直往下的加速度&#xff0c;如果此时用户在别的方向上有加速度&#xff0c;那么通过反余弦、反正切等计算…

Java---day2

七、IDEA开发工具 &#x1f4e6; 一、下载 IntelliJ IDEA 官网地址&#xff1a; &#x1f517; IntelliJ IDEA – the IDE for Pro Java and Kotlin Development 版本选择&#xff1a; 版本说明Community Edition (CE)免费开源版本&#xff0c;适合 Java、Kotlin、Android…

RAL-2025 | 清华大学数字孪生驱动的机器人视觉导航!VR-Robo:面向视觉机器人导航与运动的现实-模拟-现实框架

作者&#xff1a; Shaoting Zhu, Linzhan Mou, Derun Li, Baijun Ye, Runhan Huang, Hang Zhao单位&#xff1a;清华大学交叉信息研究院&#xff0c;上海期智研究院&#xff0c;Galaxea AI&#xff0c;上海交通大学电子信息与电气工程学院论文标题&#xff1a;VR-Robo: A Real-…