京东抢购服务高并发实践

2023-11-11

声明:本位来自京东张开涛的微信公众号(kaitao-1234567),授权CSDN转载,如需转载请联系作者。
作者:张子良,京东高级开发工程师,在京东负责抢购后端服务系统架构和开发工作。
责编:钱曙光,关注架构和算法领域,寻求报道或者投稿请发邮件qianshg@csdn.net,另有「CSDN 高级架构师群」,内有诸多知名互联网公司的大牛架构师,欢迎架构师加微信qshuguang2008申请入群,备注姓名+公司+职位。

服务介绍

限时抢购又称闪购,英文Flash sale,起源于法国网站Vente Privée。闪购模式即是以互联网为媒介的B2C电子零售交易活动,以限时特卖的形式,定期定时推出国际知名品牌的商品,一般以原价1-5折的价格供专属会员限时抢购,每次特卖时间持续5-10天不等,先到先买,限时限量,售完即止。顾客在指定时间内(一般为20分钟)必须付款,否则商品会重新放到待销售商品的行列里。

模式特征:

品牌丰富 —— 推出国内外一二线名牌商品,供消费者购买选择;

时间短暂 —— 每个品牌推出时间短暂,一般为5—10天,先到先买,限量售卖,售完即止;

折扣超低 —— 以商品原价1—5折的价格销售,折扣力度大。

摘自【百度百科】,通过这段简介相信对限时抢购有了一定的了解,我们内部称之为抢购系统。

对于抢购系统来说,首先要有可抢购的活动,而且这些活动具有促销性质,比如直降500元。其次要求可抢购的活动类目丰富,用户才有充分的选择性。618(6.1-6.20)期间增量促销活动量非常多,可能某个活动力度特别大,大多用户都在抢,必然对系统是一个考验。这样抢购系统具有秒杀特性,并发访问量高,同时用户也可选购多个限时抢商品,与普通商品一起进购物车结算。这种大型活动的负载可能是平时的几十倍,所以通过增加硬件、优化瓶颈代码等手段是很难达到目标的,所以抢购系统得专门设计。

服务主要功能

创建促销服务:采销创建促销后,促销管理系统审核通过后,会调用抢购系统创建促销;

抢服务:为符合条件的订单操作剩余数,主要是扣减剩余数;

针对哪些SKU

目前主要为单品促销,直降或者一口价,比如:

图片描述

主要渠道

移动APP、微信、手Q和主站

限购类型

限数量、限ip、限pin和限制ip与pin

系统设计要点

如何实现实时库存?

这里说的库存不是真正意义上的库存,其实是该促销可以抢购的数量,真正的库存在基础库存服务。用户点击『提交订单』按钮后,在抢购系统中获取了资格后才去基础库存服务中扣减真正的库存;而抢购系统控制的就是资格/剩余数。传统方案利用数据库行锁,但是在促销高峰数据库压力过大导致服务不可用,目前采用redis集群(16分片)缓存促销信息,例如促销id、促销剩余数、抢次数等,抢的过程中按照促销id散列到对应分片,实时扣减剩余数。当剩余数为0或促销删除,价格恢复原价。

如何设计抢购redis数据结构?

采销人员发布促销后,在抢购redis中生成一笔记录,给抢服务提供基本信息。每一个促销对应一个促销id,促销信息是Hashes结构。

例如促销A,对应的类型为单品促销,我们暂且认为类型值为1,对应redis中的key为 C_A_1,数据结构内容类似于如下:

o:  100 // 原始数量

b:  99  // 可抢购数量,假如抢购了一个剩下了99

c:  1   // 抢购次数记录,用来限流,后面会介绍到

如何保证不超卖?

因为扣减资格是一组操作,我们利用EVAL操作redis剩余数实现原子化操作,伪代码如下:

local key = KEYS[1]

local tag  = "b"

local num   = tonumber(ARGV[1]); 

local lastNum = redis.call('HINCRBY',key,tag,-num);

if业务性判断ortonumber(lastNum) == 0then

   return lastNum

end

如上代码会返回剩余数,如果小于等于0了,则没有库存了。

如何提高吞吐量?

减少网络交互(一次抢数据通过 EVALSHA 一次性提交给redis集群);数据库操作异步化(使用JMQ异步记录日志)。

如何保证可用性?

采用JSF(京东内部SOA框架)对外开放服务(抢服务和发布促销服务),可降级为系统自身webservice服务;

抢购系统主要依赖于redis集群,redis采用一主三从集群方案,部署在两个机房,每个集群16个分片,每两分片共用一台物理机,可通过配置中心切换主从;

如果Redis挂掉了,如何恢复呢?通过汇总MySQL中的抢购和取消流水日志,并恢复Redis的抢购数量。

系统架构

这里主要涉及抢服务架构剖析,因为它具有典型的高并发特性,下面是基本架构概图:

图片描述

注:此处的库存是可抢购数量设置,或者叫做资格/剩余数,并非真正的实际库存。

抢服务流程

Redis使用单个Lua解释器去运行所有脚本,并且Redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或Redis命令被执行。这种特性很好的解决了抢服务流程中并发带来的问题。

图片描述

REDIS+LUA抢购子流程:

此流程通过lua Script脚本实现,我们暂时命名为q.lua(主要功能限流和扣减促销活动剩余数)。这样把抢购流程与Script脚本结合,一次性提交给Redis减少网络交互,使得性能大大提升。

q.lua伪代码:

--[[

--!@brief 促销Id下限流:可以防止某个促销过热导致服务不可以用

--]]

local function limited()

    -- todo: 实现

end

--[[

--!@brief 限制逻辑(ip和pin):比如有的促销是限制ip,这里校验ip是否存在,如果为限ip类型抢购活动,存在抛出异常告知ip已经存在不能抢购

--]]

local function check_ip_pin()

    -- todo: 实现

end

--[[

--!@brief 记录订单号:主要目的实现抢方法幂等性,调用方网络超时可以重复调用,存在订单号直接返回抢购成功,不至于超卖

--]]

local function record_order_id()

    -- todo: 实现

end

--[[

--!@brief 扣减剩余数

--]]

local function scalebuy()

    --

    local lastNum = redis.call('HINCRBY',key,tag,-num);

    --

end



-- 调用顺序不可调整

-- 1 限流

local status,msg = limited()

if status == 0then

    return msg

end

-- 2 校验

status,msg = check_ip_pin()

if status == 0 then

    return msg

end

-- 3 记录订单

status,msg = record_order_id()

if status == 0 then

    return msg

end

-- 4 扣减剩余数

status,msg = scalebuy()

if status == 0 then

    return msg

end

-- 5 返回成功标示

return 1

子流程具体如下:

1、解析请求参数,根据促销Id按照Jedis中MurmurHash算法获取分片,然后按照分片包装Pipeline批量发送请求参数argList;

2、获取系统初始化时SCRIPT LOAD加载q.lua返回的串shaValue;

3、执行EVALSHA,伪代码如下:

// 其他操作

Pipeline p;

// 初始化p

p.evalsha(shaValue,keyList, argList);

// 其他操作

4、处理返回结果,只要有一个分片失败,本次抢购就失败。

补充:详细Script操作可以参考Jedis中 ScriptingCommandsTest。

JMQ发送子流程:

执行REDIS+LUA抢购子流程成功仅仅代表着操作redis成功,发送jmq(京东mq基础服务)成功(后端异步将实时库存更新到MySQL)才算一笔抢购成功,否则算抢购失败。这么设计的原因主要是保证抢购redis和mysql记录最终一致,发送失败需要回滚REDIS+LUA抢购子流程(恢复Redis的库存和抢购资格)。当然要考虑降级,jmq不可用时,直接切到jsf服务模拟jmq,也就是直接写MySQL库,前提是限流次数调小,否则数据库有压力过大的风险。这样虽然用户体验下降了,但是服务依然可用。开关都在配置中心操作,一分钟内生效。

资格回滚子流程:

发送JMQ失败必须回滚,否则就出现了超卖现象,具体流程同REDIS+LUA抢购子流程类似,是它的逆向流程,只不过运行脚本不同罢了。

限流处理

方法级限流,限流阈值通过配置中心配置,一分钟生效,伪代码如下:

private static AtomicInteger atomic = new AtomicInteger(0);

public void test() {

    try {

         // 限流

        int limitNum = XXX.getLimitNum(); 

        int nowConcurrent = atomic.incrementAndGet();

        if(nowConcurrent > limitNum) {

            // 异常处理

        }   

        // 正常业务逻辑

    } catch(Exception e) {

        // 异常处理

    } finally {

        atomic.decrementAndGet();

    }

}

q.lua中促销级别的限流,主要利用C_A_1中c的抢次数和阈值比对。比如促销A,60秒内只能抢60000次,超过阈值60000该促销就会抢购失败。

到此抢购系统的核心逻辑就介绍完了,这里边还有一些细节问题需要大家在设计时思考,如限购(如每个人限购2个)、真实库存不足取消、用户取消订单归还资格、Redis挂了恢复数据、停促销(时间过期停、库存不足停)等。

相关阅读:


FROM: http://geek.csdn.net/news/detail/93986



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

京东抢购服务高并发实践 的相关文章

  • 软件系统设计-15-架构设计

    1 设计架构 Design Architecture 1 1 设计策略 Design Strategies Abstraction Generate Test Decomposition Reusable Elements Iteratio
  • python(数据分析)第5天:图例

    图例 plt legend import matplotlib pyplot as plt import random import matplotlib from matplotlib import cycler from matplot
  • Kafka练习

    需求 写一个生产者 不断的去生产用户行为数据 写入到kafka的一个topic中 生产的数据格式 造数据 guid 1 eventId pageview timestamp 1637868346789 isNew 1 guid 1 even
  • fastjson自定义字段命名规则

    文章首发于个人博客 欢迎访问关注 https www lin2j tech 前置知识 fastjson 在将对象转变为 JSON 字符串时 字段默认使用 CamelCase 规则命名 在1 2 15版本之后 fastjson 支持配置 Pr

随机推荐

  • vue2 ajax异步请求,数据嵌套层数过多,导致页面无法正常通过数据驱动渲染

    数据层数过多的小坑 初入门vue2 在开发项目过程中因为用到了vue echarts v3 涉及图表的数据 难免数据就有过多的层数 导致出现了这么一个坑 其实归根结底是自己没有按照vue2官方的方法进行对象数据修改 首先 数据结构大致是这样
  • 精美简历生成器(Nice_Resume_Builder)

    文章目录 前言 功能演示 后记 前言 写简历有时候是个比较麻烦的事情 不管是用Word还是用别的设计工具 如果内容经常需要修改的话 那么修改后通常有需要花时间去调整格式排版 这个过程令我烦躁 毫无意义的浪费时间 所以稍微花点时间弄了这个东西
  • 超过最大更新深度。当组件在 componentWillUpdate 或 componentDidUpdate 中重复调用 setState 时,可能会发生这种情况。React 限制了嵌套更新的数量以防

    超过最大更新深度 当组件在 componentWillUpdate 或 componentDidUpdate 中重复调用 setState 时 可能会发生这种情况 React 限制了嵌套更新的数量以防止无限循环 有没用像我这样报错的 这个报
  • 每天Leetcode 刷题 初级算法篇-设计问题-最小栈

    题目要求 力扣题解 代码 program mydemo description 设计问题 最小栈 author Mr zeng create 2021 02 19 09 49 public class MinStack private St
  • linux获取主板温度电压_穷人省钱技巧揭秘!200元技嘉主板竟可满血英特尔I9处理器?...

    其实 这是一个意外 有一个粉丝 手里置闲了一块技嘉Z270芯片组的主板 本来他以为的型号是GA Z270X UD3 结果 发过来的却是GA Z370 HD3 本来UD3的供电就够乞丐的了 HD3则更加低端 更要命的是 粉丝想搭配的处理器 竟
  • java--基础--26--模块化

    java 基础 26 模块化 代码 https gitee com DanShenGuiZu learnDemo tree mysql mybaties DB java model learn 1 模块化概述 无论是运行一个大型的软件系统
  • MOS管和三极管区别-对比很显然

    在电路设计当中假设我们想要对电流中止控制 那就少不了三极管的帮助 我们俗称的三极管其全称为半导体三极管 它的主要作用就是将微小的信号中止放大 MOS管与三极管有着许多相近的地方 这就使得一些新手不断无法明白两者之间的区别 这里就将为大家引见
  • uniapp 各种兼容,优化等问题记录

    对于ios自带的上下拉 进行禁用 橡皮筋回弹 1 pages json中加入如下配置 path pages my my style navigationBarTitleText 个人中心 disableScroll true 禁止滑动 en
  • xss-labs/level5

    输入 查看回显 如下所示 能够发现script被恶意替换为scr ipt 查看源代码 第一个输出点被转义了 所以没有利用价值了 第二个输出点如同刚才所言被进行了关键字的恶意替换操作 那没办法 我们只能继续尝试一下在标签内部构造一个新属性然后
  • opencv CvSolve函数深度解析

    Opencv CvSolve函数主要是用来求解线性系统Ax b的方程 X的解 solve函数跟它的算法是一样的 也是用来求解线性系统 设方程Ax b 根据有效的方程个数和未知数的个数 可以分为以下3种情况 1 rank A lt n 也就是
  • mysql故障切换 java_java.sql.SQLException: Value'0000-00-00'异常解决办法及mysql的url设置...

    一 0000 00 00错误及解决方案 在使用MySql 时 数据库中的字段类型是timestamp的 默认为0000 00 00 会发生异常 java sql SQLException Value 0000 00 00 can not b
  • 音频服务器运行失败怎么办,音频服务错误1068怎么解决 音频服务未运行win10解决方法...

    win10音频服务无法启动 提示错误1068 依赖服务或组无法启动是什么情况 怎么解决 在这里就跟着小编一起来了解一下吧 让大家解决电脑不能够出声的问题 音频服务未运行win10问题排除 一 电脑没了声音 小喇叭上有个红叉 鼠标放上去显 音
  • 【Python学习笔记2】Python网络爬虫的异常处理

    本文信息主要来源于韦玮老师的 精通python网络爬虫 仅作为个人学习笔记 通过python库函数urllib request去爬取网页时 遇到的异常主要时URL类和网页类的错误 python已提供URLError类和HTTPError类来
  • Ado.Net总结

    ADO NET总结 ADO NET 是在 NET 平台上访问数据库的组件 它是以 ODBC Open Database Connectivity 技术的方式来访问数据库的一种技术 ADO NET常用命名空间 命名空间 数据提供程序 Syst
  • 多输入多输出

    多输入多输出 MATLAB实现GRU门控循环单元多输入多输出 目录 多输入多输出 MATLAB实现GRU门控循环单元多输入多输出 预测效果 基本介绍 程序设计 往期精彩 参考资料 预测效果 基本介绍 MATLAB实现GRU门控循环单元多输入
  • spark SQL配置连接Hive Metastore 3.1.2

    Hive Metastore作为元数据管理中心 支持多种计算引擎的读取操作 例如Flink Presto Spark等 本文讲述通过spark SQL配置连接Hive Metastore 并以3 1 2版本为例 通过Spark连接Hive
  • HTML <progress> 标签

    实例 正在进行的下载
  • kali 破解压缩包密码

    一 提取加密压缩包的哈希值 zip2john 文件名 gt hash txt zip2john 文件名 能提取出文件的哈希值 然后我们通过重定向 将它写入一个文档中 二 利用工具对提取到的哈希值进行破解 john hash txt 然后就能
  • windows11安装微软商店里的ubuntu报错,已解决

    1 问题情况 安装微软商店的Ubuntu系统 安装完成后打开开始菜单的Ubuntu图标 初始化过程中卡住 点击界面下方的展开按钮有文字提示报错 报错提示内容为 Installing this may take a few minutes W
  • 京东抢购服务高并发实践

    声明 本位来自京东张开涛的微信公众号 kaitao 1234567 授权CSDN转载 如需转载请联系作者 作者 张子良 京东高级开发工程师 在京东负责抢购后端服务系统架构和开发工作 责编 钱曙光 关注架构和算法领域 寻求报道或者投稿请发邮件