Redis最佳实践——购物车优化详解

news2025/6/5 3:42:58

在这里插入图片描述

Redis在电商购物车高并发读写场景下的优化实践


一、购物车业务场景分析
  1. 典型操作特征

    • 读/写比例 ≈ 8:2
    • 高峰QPS可达10万+
    • 单用户最大商品数500+
    • 操作类型:增删改查、全选/反选、数量修改
  2. 技术挑战

    • 高并发下的数据一致性
    • 海量数据存储与快速访问
    • 实时价格计算与库存校验
    • 分布式环境下的会话管理

二、核心数据结构设计优化

1. 存储结构方案对比

方案优点缺点
String+JSON简单直观修改需反序列化整个数据
Hash结构支持字段级操作嵌套结构处理略复杂
Sorted Set天然支持排序存储成本较高
混合结构平衡性能与灵活性实现复杂度略高

2. 最终数据结构设计

// Key设计:cart:{userType}:{userId}
String cartKey = "cart:user:10001"; 

// Value结构:
// Hash结构存储商品基础信息
Map<String, String> itemData = new HashMap<>();
itemData.put("sku:1001", 
    "{\"quantity\":2,\"selected\":1,\"price\":5999,\"timestamp\":1717025661}");

// Sorted Set维护操作顺序
jedis.zadd(cartKey + ":zset", System.currentTimeMillis(), "sku:1001");

三、读写分离架构设计

1. 多级缓存架构

首次访问
缓存穿透
缓存未命中
缓存命中
缓存命中
回写
回写
客户端
读请求
本地缓存
Redis集群
数据库
返回数据

2. 各层缓存配置

缓存层级技术选型容量过期策略
本地缓存Caffeine10万用户基于大小+访问时间(1分钟)
Redis缓存Hash+Zset1TB内存动态TTL+LRU淘汰
持久化存储MySQL+TiDB无限扩展事务保障

四、高并发写入优化

1. 批量操作管道化

public void batchAddItems(String userId, List<CartItem> items) {
    try (Jedis jedis = jedisPool.getResource()) {
        Pipeline pipeline = jedis.pipelined();
        String cartKey = buildCartKey(userId);
        
        items.forEach(item -> {
            String field = "sku:" + item.getSkuId();
            // 更新Hash
            pipeline.hset(cartKey, field, serialize(item));
            // 更新ZSET
            pipeline.zadd(cartKey + ":zset", System.currentTimeMillis(), field);
        });
        
        pipeline.sync();
    }
}

2. 异步队列削峰

@KafkaListener(topics = "cart_updates")
public void processCartUpdate(CartUpdateEvent event) {
    redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
        event.getUpdates().forEach(update -> {
            connection.hSet(
                update.getCartKey().getBytes(),
                update.getField().getBytes(),
                serialize(update.getValue())
            );
        });
        return null;
    });
}

五、高并发读取优化

1. 热点数据预加载

@Scheduled(fixedRate = 600000) // 每10分钟执行
public void preloadActiveCarts() {
    List<String> activeUsers = userService.getRecentActiveUsers(10000);
    activeUsers.parallelStream().forEach(userId -> {
        String cartKey = buildCartKey(userId);
        Map<String, String> cartData = jedis.hgetAll(cartKey);
        localCache.put(userId, cartData);
    });
}

2. 分片读取优化

public Map<String, CartItem> getCartSharded(String userId) {
    String cartKey = buildCartKey(userId);
    List<String> fields = new ArrayList<>();
    
    // 分片读取Hash
    Map<String, CartItem> result = new ConcurrentHashMap<>();
    IntStream.range(0, 4).parallel().forEach(shard -> {
        ScanParams params = new ScanParams().count(100).match("sku*");
        String cursor = "0";
        do {
            ScanResult<Map.Entry<String, String>> scanResult = 
                jedis.hscan(cartKey, cursor, params);
            scanResult.getResult().forEach(entry -> {
                if (entry.getKey().hashCode() % 4 == shard) {
                    result.put(entry.getKey(), deserialize(entry.getValue()));
                }
            });
            cursor = scanResult.getCursor();
        } while (!"0".equals(cursor));
    });
    
    return result;
}

六、实时库存校验方案

1. 库存缓存设计

// 库存Key结构
String stockKey = "stock:" + skuId + ":" + warehouseId;

// 原子扣减库存
Long remain = jedis.eval(
    "local current = redis.call('get', KEYS[1])\n" +
    "if not current then return -1 end\n" +
    "if tonumber(current) < tonumber(ARGV[1]) then return -1 end\n" +
    "return redis.call('decrby', KEYS[1], ARGV[1])", 
    Collections.singletonList(stockKey), 
    Collections.singletonList("1")
);

2. 库存预占机制

public boolean reserveStock(String userId, String skuId, int quantity) {
    String lockKey = "stock_lock:" + skuId;
    RLock lock = redissonClient.getLock(lockKey);
    
    try {
        if (lock.tryLock(100, 1000, TimeUnit.MILLISECONDS)) {
            // 检查实际库存
            int realStock = getRealStock(skuId);
            if (realStock < quantity) return false;
            
            // 写入预占记录
            String reserveKey = "reserve:" + userId + ":" + skuId;
            jedis.setex(reserveKey, 300, String.valueOf(quantity));
            
            // 更新显示库存
            jedis.decrBy("display_stock:" + skuId, quantity);
            return true;
        }
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
    return false;
}

七、数据一致性保障

1. 双写一致性方案

App Redis MQ DB 1. 写入购物车数据 2. 发送变更事件 3. 异步持久化 4. 定时全量同步 5. 返回操作结果 App Redis MQ DB

2. 补偿对账机制

@Scheduled(cron = "0 0 2 * * ?")
public void cartReconciliation() {
    // 扫描所有购物车Key
    ScanParams params = new ScanParams().match("cart:*").count(100);
    String cursor = "0";
    
    do {
        ScanResult<String> scanResult = jedis.scan(cursor, params);
        scanResult.getResult().parallelStream().forEach(cartKey -> {
            // 对比Redis与数据库
            Map<String, String> redisData = jedis.hgetAll(cartKey);
            Map<String, CartItem> dbData = cartDAO.getFromDB(extractUserId(cartKey));
            
            if (!dataEquals(redisData, dbData)) {
                log.warn("数据不一致:{}", cartKey);
                repairData(cartKey, redisData, dbData);
            }
        });
        cursor = scanResult.getCursor();
    } while (!"0".equals(cursor));
}

八、性能压测数据

测试环境

  • Redis Cluster(6节点,32核/128GB)
  • 1000并发线程
  • 单用户购物车50件商品

性能指标

操作类型优化前性能优化后性能提升倍数
添加商品1200 TPS8500 TPS7.1x
批量删除800 TPS6800 TPS8.5x
全量获取300 QPS4500 QPS15x
库存校验1500 TPS12000 TPS8x

九、生产环境最佳实践
  1. 容量规划

    • 按每个用户购物车平均50个商品计算
    • 单个Hash存储约需5KB内存
    • 百万用户需预留:1,000,000 * 5KB = 5GB
  2. 故障应急方案

    • 熔断降级:启用本地缓存应急模式
    • 快速扩容:Redis Cluster在线扩容
    • 数据恢复:AOF+RDB双重保障
  3. 监控关键指标

    # 实时监控命令
    redis-cli info stats | grep -E "instantaneous_ops_per_sec|keyspace_hits"
    redis-cli info memory | grep used_memory_human
    redis-cli latency doctor
    

十、总结与扩展

通过本方案可实现:

  • 毫秒级响应:核心操作<10ms
  • 99.99%可用性:双机房容灾保障
  • 线性扩展:支持千万级用户购物车
  • 精准库存:实时库存校验误差<0.1%

扩展优化方向

  1. 结合CDN缓存静态化购物车页面
  2. 使用Redis Stream实现实时价格推送
  3. 引入机器学习预测用户购物行为

更多资源:

https://www.kdocs.cn/l/cvk0eoGYucWA

本文发表于【纪元A梦】

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

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

相关文章

【计算机网络】传输层UDP协议

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;计算机网络 &#x1f339;往期回顾&#x1f339;&#xff1a; 【计算机网络】应用层协议Http——构建Http服务服务器 &#x1f516;流水不争&#xff0c;争的是滔滔不…

安全漏洞修复导致SpringBoot2.7与Springfox不兼容

项目基于 springboot2.5.2 实现的&#xff0c;用 springfox-swagger2 生成与前端对接的 API 文档&#xff1b;pom.xml 中依赖如下 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId>&l…

从法律层面剖析危化品证书:两证一证背后的安全逻辑

《安全生产法》第 24 条明确规定&#xff0c;危化品单位主要负责人和安全管理人员 “必须考核合格方可上岗”。这并非仅仅是行政要求&#xff0c;而是通过法律来筑牢安全防线。在某危化品仓库爆炸事故中&#xff0c;由于负责人未持证&#xff0c;导致事故责任升级&#xff0c;企…

深入理解复数加法与乘法:MATLAB演示

在学习复数的过程中&#xff0c;复数加法与乘法是两个非常基础且重要的概念。复数的加法和乘法操作与我们常见的实数运算有所不同&#xff0c;它们不仅涉及到数值的大小&#xff0c;还有方向和相位的变化。在这篇博客中&#xff0c;我们将通过MATLAB演示来帮助大家更好地理解复…

【设计模式-3.6】结构型——桥接模式

说明&#xff1a;本文介绍结构型设计模式之一的桥接模式 定义 桥接模式&#xff08;Bridge Pattern&#xff09;又叫作桥梁模式、接口&#xff08;Interface&#xff09;模式或柄体&#xff08;Handle and Body&#xff09;模式&#xff0c;指将抽象部分与具体实现部分分离&a…

力扣题解654:最大二叉树

一、题目内容 题目要求根据一个不重复的整数数组 nums 构建最大二叉树。最大二叉树的构建规则如下&#xff1a; 创建一个根节点&#xff0c;其值为 nums 中的最大值。递归地在最大值左边的子数组前缀上构建左子树。递归地在最大值右边的子数组后缀上构建右子树。返回由 nums 构…

95套HTML高端大数据可视化大屏源码分享

概述​​ 在大数据时代&#xff0c;数据可视化已成为各行各业的重要需求。这里精心整理了95套高端HTML大数据可视化大屏源码&#xff0c;这些资源采用现代化设计风格&#xff0c;可帮助开发者快速构建专业的数据展示界面。 ​​主要内容​​ ​​1. 设计风格与特点​​ 采用…

scale up 不能优化 TCP 聚合性能

scale up 作为一种系统扩展优化的方法&#xff0c;旨在提高系统组件的执行效率&#xff0c;比如替换更高性能的硬件或算法。是否可以此为依据优化 TCP 呢&#xff0c;例如通过多条路径聚合带宽实现吞吐优化(对&#xff0c;还是那个 MPTCP)&#xff0c;答案是否定的。 因为 TCP…

Python-matplotlib库之核心对象

matplotlib库之核心对象 FigureFigure作用Figure常用属性Figure常用方法Figure对象的创建隐式创建&#xff08;通过 pyplot&#xff09;显式创建使用subplots()一次性创建 Figure 和 Axes Axes&#xff08;绘图区&#xff09;Axes创建方式Axes基本绘图功能Axes绘图的常用参数Ax…

Linux 脚本文件编辑(vim)

1. 用户级配置文件&#xff08;~/.bashrc&#xff09; vim ~/.bashrc # 编辑 source ~/.bashrc # 让编辑生效 ~/.bashrc 文件是 Bash Shell 的配置文件&#xff0c;用于定义用户登录时的环境变量、别名、函数等设置。当你修改了 ~/.bashrc 文件后&#xff0c;通常需要重新…

学习BI---基本操作---数据集操作

什么是数据集&#xff0c; 数据集&#xff08;Dataset&#xff09;​​ 是指从原始数据源&#xff08;如数据库、Excel、API等&#xff09;提取并经过标准化处理后的数据集合&#xff0c;通常以二维表形式存储&#xff0c;用于支撑报表、仪表盘等可视化分析。 数据集在QuickB…

初学大模型部署以及案例应用(windows+wsl+dify+mysql+Ollama+Xinference)

大模型部署以及案例应用&#xff08;windowswsldifymysqlOllamaXinference&#xff09; 1.wsl 安装①安装wsl②测试以及更新③安装Ubuntu系统查看系统以及版本安装Ubuntu系统进入Ubuntu系统 2、docker安装①下载安装包②安装③docker配置 3、安装dify①下载dify②安装③生成.en…

Redis部署架构详解:原理、场景与最佳实践

Redis部署架构详解&#xff1a;原理、场景与最佳实践 Redis作为一种高性能的内存数据库&#xff0c;在现代应用架构中扮演着至关重要的角色。随着业务规模的扩大和系统复杂度的提升&#xff0c;选择合适的Redis部署架构变得尤为重要。本文将详细介绍Redis的各种部署架构模式&a…

C++哈希表:unordered系列容器详解

本节目标 1.unordered系列关联式容器 2.底层结构 3.模拟实现 4.哈希的应用 5.海量数据处理面试题 unordered系列关联式容器 在c98中&#xff0c;STL提供了底层为红黑树结构的一系列关联式容器&#xff0c;在查询时效率可以达到logN&#xff0c;即最差的情况下需要比较红…

WordPress通过简码插入bilibili视频

发布于&#xff1a;Eucalyptus-Blog 一、前言 B站是国内非常受欢迎的视频分享平台&#xff0c;上面不仅内容丰富&#xff0c;而且很多视频制作精良、趣味十足。很多人&#xff0c;比如我&#xff0c;就喜欢将B站的视频通过 iframe 嵌入到自己的网页中&#xff0c;但这段代码又…

ZLG ZCANPro,ECU刷新,bug分享

文章目录 摘要 📋问题的起因bug分享 ✨思考&反思 🤔摘要 📋 ZCANPro想必大家都不陌生,买ZLG的CAN卡,必须要用的上位机软件。在汽车行业中,有ECU软件升级的需求,通常都通过UDS协议实现程序的更新,满足UDS升级的上位机要么自己开发,要么用CANoe或者VFlash,最近…

黑马k8s(十七)

一&#xff1a;高级存储 1.高级存储-pv和pvc介绍 2.高级存储-pv 3.高级存储-pvc 最后一个改成5gi pvc3是没有来绑定成功的 pv3没有绑定 删除pod、和pvc&#xff0c;观察状态&#xff1a; 4.高级存储-pc和pvc的生命周期 二&#xff1a;配置存储 1.配置存储-ConfigMap 2.配…

掌握HttpClient技术:从基础到实战(Apache)

目录 前言 一、Apache HttpClient简介 二、HttpClient基础使用 1. 添加依赖 2. 创建HttpClient实例 3. 发送GET请求 4. 发送POST请求 三、HttpClient高级配置与实战案例 1. 连接池优化 2. 超时与重试配置 3. 文件上传&#xff08;Multipart&#xff09; 总结 前言 …

sql知识梳理(超全,超详细,自用)

目录 通识 查询的基本语法 数据库&#xff08;database&#xff09;操作 表&#xff08;table&#xff09;的操作 表中列的操作 索引操作 表中行的操作 insert into语句 update语句 删除语句 select语句 表与表之间的关系 连接查询 子查询 视图 数据备份与还原 …

[ Qt ] | QPushButton常见用法

目录 绑定键盘快捷键 前面已经说了很多用法了&#xff0c;下面主要说说绑定键盘&#xff0c;设置Icon图片。 绑定键盘快捷键 实现四个按钮&#xff0c;可以使用wsad来控制另一个按钮的上下左右的移动。 #include "widget.h" #include "ui_widget.h"Wid…