我所理解的Redis系列·基于 Redis 优化订单统计接口的解决方案
mhr18 2024-11-24 18:46 19 浏览 0 评论
1. 场景描述
在客户性能压测现场支持时,遇到一个问题:用户订单状态统计接口响应时间(RT, Response Time)较长,成为项目整体性能的瓶颈,亟需优化。
用户订单状态统计接口是标品提供的能力,由于其业务复杂性及数据存储结构的特殊性(具体内容下文描述),进行了多次 ES 的查询,导致 RT 较长,影响整体性能。
1.1 业务逻辑描述
标品原有的存储及查询逻辑是这样:
- 用户下单,RDS 进行持久化
- DTS 监听 bin log 变动,经由 Kafka 发送订单变更消息
- 交易中心监听并消费 Kafka 消息,将订单写入 ES
- 当触点端进行查询时,交易中心直接查询 ES 的订单数据返回
整体链路时序图如下:
Touch SideTrade CenterRDSDTSKafkaES变更订单状态1持久化订单信息2持久化完成3订单状态变更完成4订单异步写入 ES 逻辑订单变动信息5投递订单变动消息6订阅订单变动消息7同步订单信息8触点端查询订单统计信息逻辑查询订单统计信息9查询 ES10返回 ES 订单信息11返回订单统计信息12Touch SideTrade CenterRDSDTSKafkaES
1.2 核心问题
在标品原有的业务逻辑中,有这样两个问题是比较麻烦的:
1.2.1 订单状态映射关系
第一个是订单状态映射关系的复杂性,触点端需要展示的订单状态有:待支付、待发货、待收货、已完成。这些触点端订单状态是由多个持久化字段的状态共同决定的,其映射关系如下表:
触点端订单状态 | 持久化字段需满足的状态 | ||
支付状态 | 发货状态 | 收货状态 | |
待支付 | 待支付 | ||
待发货 | 已支付 | 待发货 | |
待收货 | 已支付 | 已发货 | 未收货 |
已完成 | 已收货 |
Ps:私以为已完成状态的判断条件有待商榷,比如已完成,只要收货状态是已收货就可以吗?严格来说,已完成的状态应该同时满足:支付状态为已支付,发货状态为已发货,收货状态为已收货。不应该有“收货状态为已收货时必定意味着已支付、已发货”这样的约定,当约定太多时,可能会发生很多意料之外的情况。
1.2.2 ES 数据存储结构
交易中心在向 ES 写入数据时,其结构与 RDS 持久化的数据结构是一致的,也就是说,查询 ES 无法直接获得触点端需要的订单状态,这也是在查询订单统计结果需要多次查询 ES 的原因。
由于 ES 中存储的数据与 RDS 持久化的数据结构,我们可以把 ES 看成是一个更快的 MySQL,当我们查询用户 id=1 的待收货订单数量时,SQL 语句如下:
select count(*) from order where user_id = 1 and `支付状态`='已支付' and `发货状态`='已发货' and `收货状态`='未收货';
复制代码
而如果我们需要同时查询到四种订单状态的数量,那么只能通过四次查询获得:
-- 待支付
select count(*) from order where user_id = 1 and `支付状态`='待支付';
-- 待发货
select count(*) from order where user_id = 1 and `支付状态`='已支付' and `发货状态`='待发货';
-- 待收货
select count(*) from order where user_id = 1 and `支付状态`='已支付' and `发货状态`='已发货' and `收货状态`='未收货';
-- 已完成
select count(*) from order where user_id = 1 and `支付状态`='已支付' and `发货状态`='已发货' and `收货状态`='已收货';
复制代码
这就是整个用户订单统计结果接口 RT 较长的原因。
2. 解决方案
基于上述的业务及核心问题分析,我们提出了以下几种解法:
2.1 修改 ES 存储数据结构
每条数据新增一个支付状态字段,然后再写入 ES。
这样就将原本要N次的查询减少到了1次,并且仅需要将订单状态作为查询条件即可。但是,基于种种无法描述的原因,这个方案被放弃了(主要的原因还是尽量不对标品做大改动)。
2.2 新增缓存机制
在摒弃了第一种方案之后,我们又提出第二种解决方案:缓存,对用户订单统计结果做缓存。
这样就不需要每次都对 ES 进行N次查询然后再组装结果集进行返回了。但是,缓存的准确性和健壮性是一个需要考虑的问题:
- 如何在异步场景下保证数据的准确性,不被例如重复消费的场景影响缓存数据?
- 如何让缓存数据具备健壮性?即假设缓存有误,如何在没有后台干预的情况下让缓存数据能够自我修正?
- 如何在特定场景下使得缓存能够持续命中(如性能压测)?
- ……
这些都是在设计缓存机制时需要考虑的问题。
3. 详细设计
3.1 缓存流程设计
在确定了基于缓存对本场景进行优化的方向之后,首先需要确定的是优化后整体的业务流程,前面几个步骤不变,还是:
- 用户下单,RDS 进行持久化
- DTS 监听 bin log 变动,经由 Kafka 发送订单变更消息
- 交易中心监听并消费 Kafka 消息,将订单写入 ES
新增了同步缓存的步骤:
- 交易中心将订单信息同步至缓存
当触点端进行订单统计查询时,首先查询缓存,需要分情况讨论:
- 第一次查询(缓存未命中)
- 交易中心直接查询 ES 的订单数据
- 将查询结果缓存,缓存分两类
- 用户订单统计结果缓存:缓存了各状态订单数量
- 用户订单ID缓存:缓存了各状态订单ID值
- 返回查询结果至触点端
- 第N次查询(缓存命中),这里也需要分情况讨论
- 两类缓存一致
- 返回查询结果至触点端
- 两类缓存不一致
- 交易中心直接查询 ES 的订单数据
- 修正缓存,将查询结果更新至缓存
- 返回查询结果至触点端
除此之外,我们还在用户查询指定状态所有订单时,对缓存进行准确性校验及补偿:
- 触点端查询指定状态所有订单
- 交易中心查询 ES 中指定状态所有订单
- 判断与缓存中数据是否一致
- 若不一致,对缓存中的数据进行补偿更新
- 返回指定状态所有订单至触点端
Touch SideTrade CenterRDSDTSKafkaESRedis变更订单状态1持久化订单信息2订单变动信息3投递订单变动消息4订阅订单变动消息5同步订单信息6同步用户订单信息缓存7触点端发起订单统计查询发起订单统计查询请求8查询用户订单统计信息缓存及用户订单ID缓存9缓存命中10缓存数据一致性校验11校验通过,返回订单统计结果12缓存未命中或缓存数据不一致的场景查询准确的订单信息13数据补偿及修正14返回订单统计结果15触点端发起查询指定状态所有订单请求发起查询指定状态所有订单请求16查询指定状态所有订单17查询缓存信息18缓存数据准确性校验19缓存准确性校验通过,返回指定状态所有订单20缓存准确性校验未通过的场景缓存数据补偿修正21返回指定状态所有订单22Touch SideTrade CenterRDSDTSKafkaESRedis
3.2 Redis 数据结构设计
在客户的项目中使用的缓存中间件是 Redis。
在缓存设计中提到,我们会将缓存分为两类,其含义及数据结构如下:
- 用户订单统计结果缓存:缓存了各状态订单数量
- Redis 数据结构(Hash)
- 键:UserOrderCountCache:userId
- 值:(订单状态:统计数量结果)
- 用户订单ID缓存:缓存了各状态订单ID值
- Redis 数据结构(Zset)
- 键:UserOrderDetailCache:userId:订单状态
- 值:(订单ID)
3.3 补偿机制设计
基于对缓存的不信任,我们设计了两种补偿机制。
3.3.1 主动补偿
- 查询订单统计信息(Redis 缓存内部修正)Touch SideTrade CenterESRedis发送订单统计查询请求1第1次查询2返回订单统计查询结果3第N次查询4订单ID缓存与订单统计结果缓存校验5两类缓存一致,返回查询结果6两类缓存不一致的操作查询用户所有订单7数据补偿,更新缓存8更新订单ID缓存及订单统计结果缓存补偿更新缓存结束,返回订单统计查询结果9Touch SideTrade CenterESRedis
- 查询指定状态所有订单(懒加载主动补偿修正)Touch SideTrade CenterESRedis发送查询指定状态所有订单请求1查询指定订单状态的订单信息2查询用户订单ID缓存与统计结果缓存3缓存准确性校验4缓存准确,返回查询结果5缓存不准确的操作数据补偿,更新缓存6更新订单ID缓存及订单统计结果缓存补偿更新缓存结束,返回查询指定状态所有订单结果7Touch SideTrade CenterESRedis
3.3.2 定时补偿
基于定时任务,进行两类缓存的一致性判断,若不一致查询 ES 进行缓存补偿更新。
ScheduleTrade CenterRedisES定时任务启动1查询缓存2用户订单ID缓存与统计结果缓存校验3两类缓存一致,定时任务结束4两类缓存不一致的操作查询用户所有订单5数据补偿,更新缓存6更新订单ID缓存及订单统计结果缓存补偿更新缓存结束,定时任务结束7ScheduleTrade CenterRedisES
3.4 论证及说明
3.4.1 两类缓存是否过度设计
设计两类缓存的目的是为了防止重复消费。
假设有一条用户下单的消息被重复消费了,如果只设计了一种缓存(订单统计结果),那么在消费 Kafka 消息更新缓存数据时,订单统计结果将会+2,导致缓存数据异常。
而如果按照现在的设计有两种缓存时,就算用户订单统计结果缓存中数据异常,但是由于用户订单ID缓存存储的是订单 ID,所以不会产生重复数据,能够再用户进行下一次查询时识别到本次命中的缓存数据是错误的,从而走 ES 的查询。
3.4.2 对缓存进行计算是否合理
可能有人会说,对缓存结果进行计算是不安全的,假设不是重复消费而是在消费消息时在某种情况下导致 ES 中同步了订单信息,但缓存中数据丢失的情况怎么办?此时两类缓存的数据是一致的,并不会走 ES 查询,这就导致了向触点端返回了错误的数据。
确实,是存在这种可能的,所以当触点端查询指定状态所有订单时(如下图的查询所有代付款订单),我们追加了一种主动补偿机制。
这种主动补偿机制是基于查询指定状态所有订单的结果一定是准确的,其工作原理是在得到查询结果后并不直接返回,而是对缓存数据进行一次查询并校验准确性,如查询结果与缓存不一致,说明缓存数据是错误的,需要对缓存进行补偿修正。
4. 主要场景时序图
本部分时序图主要是为了枚举出所有业务场景,将交易中心基于各种场景需要对缓存进行的操作一一列举。
4.1 用户下单(创建订单)
省略 DTS/Kafka 等非核心节点
Touch SideTrade CenterRDSESRedis创建订单1持久化订单信息2同步订单信息3更新缓存信息4统计结果缓存(待支付)+1订单明细缓存(待支付)增加OrderId返回下单结果5Touch SideTrade CenterRDSESRedis
4.2 用户支付(订单状态变更)
省略 DTS/Kafka 等非核心节点
Touch SideTrade CenterRDSESRedis用户支付1更新订单状态2同步订单信息3更新缓存信息4统计结果缓存(待支付)-1,(待发货)+1订单明细缓存(待支付)删除OrderId,(待发货)增加OrderId返回支付结果5Touch SideTrade CenterRDSESRedis
4.3 商家发货(订单状态变更)
省略 DTS/Kafka 等非核心节点
Management PlatformTrade CenterRDSESRedis商家发货1更新订单状态2同步订单信息3更新缓存信息4统计结果缓存(待发货)-1,(待收货)+1订单明细缓存(待发货)删除OrderId,(待收货)增加OrderId返回发货结果5Management PlatformTrade CenterRDSESRedis
4.4 用户确认收货(订单状态变更)
省略 DTS/Kafka 等非核心节点
Touch SideTrade CenterRDSESRedis用户确认收货1更新订单状态2同步订单信息3更新缓存信息4统计结果缓存(待收货)-1,(已完成)+1订单明细缓存(待收货)删除OrderId,(已完成)增加OrderId返回确认收货结果5Touch SideTrade CenterRDSESRedis
4.5 用户退货(取消订单)
省略 DTS/Kafka 等非核心节点
Touch SideTrade CenterRDSESRedis用户取消订单1更新订单状态2同步订单信息3更新缓存信息4统计结果缓存(原订单状态)-1订单明细缓存(原订单状态)删除OrderId返回取消订单结果5Touch SideTrade CenterRDSESRedis
5. 兜底方案
- 定时任务:定时补偿修正所有缓存
- 开放补偿接口:主动调用补偿接口修正(指定/所有)缓存
- 以批任务的形式防止出现慢 SQL 打爆数据库导致服务不可用
原文链接:https://juejin.cn/post/7055522400225460261
相关推荐
- 【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...
- Pure Storage推出统一数据管理云平台及新闪存阵列
-
PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...
- 对Java学习的10条建议(对java课程的建议)
-
不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...
- SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!
-
官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...
- JDK21有没有什么稳定、简单又强势的特性?
-
佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...
- 「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了
-
在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...
- Java面试题及答案最全总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 数据库日常运维工作内容(数据库日常运维 工作内容)
-
#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...
- 分布式之系统底层原理(上)(底层分布式技术)
-
作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...
- oracle 死锁了怎么办?kill 进程 直接上干货
-
1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)