美团团购订单系统优化记

2023-11-06

美团团购订单系统优化记


团购订单系统简介

美团团购订单系统主要作用是支撑美团的团购业务,为上亿美团用户购买、消费提供服务保障。2015年初时,日订单量约400万~500万,同年七夕订单量达到800万。

目标

作为线上S级服务,稳定性的提升是我们不断的追求。尤其像七夕这类节日,高流量,高并发请求不断挑战着我们的系统。发现系统瓶颈,并有效地解决,使其能够稳定高效运行,为业务增长提供可靠保障是我们的目标。

优化思路

2015年初的订单系统,和团购其它系统如商品信息、促销活动、商家结算等强耦合在一起,约50多个研发同时在同一个代码库上开发,按不同业务结点全量部署,代码可以互相修改,有冲突在所难免。同时,对于订单系统而言,有很多问题,架构方面不够合理清晰,存储方面问题不少,单点较多,数据库连接使用不合理,连接打满频发等等。

针对这些问题,我们按紧迫性,由易到难,分步骤地从存储、传输、架构方面对订单系统进行了优化。

具体步骤

1. 存储优化

订单存储系统之前的同事已进行了部分优化,但不够彻底,且缺乏长远规划。具体表现在有分库分表行为,但没有解决单点问题,分库后数据存储不均匀。
此次优化主要从水平、垂直两个方面进行了拆分。垂直方面,按业务进行了分库,将非订单相关表迁出订单库;水平方面,解决了单点问题后进行了均匀拆库。

这里主要介绍一下ID分配单点问题:

系统使用一张表的自增来得到订单号,所有的订单生成必须先在这里insert一条数据,得到订单号。分库后,库的数量变多,相应的故障次数变多,但由于单点的存在,故障影响范围并未相应的减少,使得全年downtime上升,可用性下降。

针对ID分配单点问题,考虑到数据库表分配性能的不足,调研了Tair、Redis、Snowflake等ID分配器,同时也考虑过将ID区间分段,多点分配。

但最后没有使用这些方案,主要原因是ID分配对系统而言是强依赖服务,在分布式系统中,增加这样一个服务,整体可用性必然下降。 我们还是从数据库入手,进行改良,方案如下。

如下图,由原来一个表分配改为100张表同时分配,业务逻辑上根据不同的表名执行一个简单的运算得到最终的订单号。
ID生成方案

ID与用户绑定:对订单系统而言,每个用户有一个唯一的userid,我们可以根据这个userid的末2位去对应的id_x表取订单号,例如userid为10086的用户去id_86表取到值为42,那订单号就42*100+86=4286。

将订单内容根据userid模100分表后如下图:
订单拆分方案

通过看上面的技巧,我们发现订单根据“userid取模”分表和根据“订单号取模”来分表结果是一样的,因为后两位数一样。到此,分库操作就相当简单容易了,极限情况下分成100个库,每个库两个表。同一个用户的请求一定在同一个库完成操作,达到了完全拆分。

:一般情况下,订单数据分表都是按userid进行的,因为我们希望同一个用户的数据存储在一张表中,便于查询。当给定一个订单号的时候,我们无法判别这个订单在哪个分表,所以大多数订单系统同时维护了一个订单号和userid的关联关系,先根据订单号查到userid,再根据userid确定分表进而查询得到内容。在这里,我们通过前面的技巧发现,订单号末二位和userid一样,给定订单号后,我们就直接知道了分表位置,不需要维护关联表了。给定订单号的情况下,单次查询由原来2条SQL变为1条,查询量减少50%,极大提升了系统高并发下性能。

2. 传输优化

当时订单业务主要用PHP编码,直连数据库。随着前端机器的增多,高流量下数据库的连接数频繁报警,大量连接被闲置占用,因此也发生过数次故障。另一方面,数据库IP地址硬编码,数据库故障后上下线操作需要研发人员改代码上线配合,平均故障处理时间(MTTR)达小时级。
如下图:
DB连接持有说明
在整个业务流程中,只有执行SQL的t1和t2时间需要数据库连接,其余时间连接资源应该释放出来供其它请求使用。现有情况是连接持有时间为t,很不合理。如果在代码中显式为每次操作分别建立并释放资源,无疑增大了业务代码的复杂度,并且建立和释放连接的开销变得不可忽略。最好的解决办法是引入连接池,由连接池管理所有的数据库连接资源。

经过调研,我们引入了DBA团队的Atlas中间件,解决了上述问题。
Atlas方案

有了中间件后,数据库的连接资源不再如以前频繁地创建、销毁,而是和中间件保持动态稳定的数量,供业务请求复用。下图是某个库上线中间件后,数据库每秒新增连接数的监控。
上线中间件后DB连接数变化

同时,Atlas所提供的自动读写分离也减轻了业务自主择库的复杂度。数据库机器的上下线通过Atlas层热切换,对业务透明。

3. 架构优化

经过前面两步的处理,此时的订单系统已比较稳定,但仍然有一些问题需要解决。如前面所述,50多个共享同一个代码仓库,开发过程互相影响,部署时需要全量发布所有机器,耗时高且成功率偏低。

在此基础上,结合业界主流实践,我们开始对订单系统进行微服务化改造。服务化其实早已是很热门的话题,最早有Amazon的服务化改造,并且收益颇丰,近年有更多公司结合自身业务所进行的一些案例。当然也有一些反思的声音,如Martin Fowler所说,要搞微服务,你得“Tall enough”。

我们搞微服务,是否tall enough呢,或者要进行微服务化的话,有什么先决条件呢?结合业内大牛分享以及我自己的理解,我认为主要有以下三方面:

  • DevOps:开发即要考虑运维。架构设计、开发过程中必须考虑好如何运维,一个大服务被拆成若干小服务,服务注册、发现、监控等配套工具必不可少,服务治理能力得达标。
  • 服务自演进:大服务被拆成小服务后,如何划清边界成为一个难题。拆的太细,增加系统复杂度;太粗,又达不到预期的效果。所以整个子服务的边界也应该不断梳理完善、细化,服务需要不断演进。
  • 团队与架构对齐:服务的拆分应该和团队人员配置保持一致,团队人员如何沟通,设计出的服务架构也应一样,这就是所谓康威定律。

公司层面,美团点评平台主要基于Java生态,在服务治理方面已有较完善的解决方案。统一的日志收集、报警监控,服务注册、服务发现、负载均衡等等。如果继续使用PHP语言做服务化,困难重重且与公司技术发展方向不符,所以我们果断地换语言,使用Java对现有的订单系统进行升级改造。使用公司基础设施后,业务开发人员需要考虑的,就只剩下服务的拆分与人员配置了,在这个过程中还需考虑开发后的部署运维。

结合业务实际情况,订单核心部分主要分为三块:下单、查询和发券。

下单部分

由易到难,大体经过如下两次迭代过程:

第一步:新造下单系统,分为二层结构,foundation这层主要处理数据相关,不做业务逻辑。通过这一层严格控制与数据库的连接,SQL的执行。在foundation的上层,作为下单逻辑处理层,在这里我们部署了物理隔离的两套系统,分别作为普通订单请求和促销订单(节日大促等不稳定流量)请求服务。

通过从原系统www不断切流量,完成下单服务全量走新系统,www演变为一个导流量的接入层。
下单服务1

第二步:在上述基础上,分别为正常下单和促销下单开发了front层服务,完成基本的请求接入和数据校验,为这两个新服务启用新的域名URI。在这个过程中,我们推动客户端升级开发,根据订单发起时是否有促销活动或优惠券,访问不同的URI地址,从源头上对促销和非促流量进行了隔离。
下单服务2

查询部分:

和下单部分类似,分为两层结构,上层根据不同业务请求和重要性进行了物理隔离。
查询服务

发券部分:

发券服务

纵观发券业务历史上的一些故障原因,主要集中在两点:
一是消息队列本身出问题,连不上,数据不能投递,消费者取不到消息。
二是个别脏数据问题,消费者不断重试、失败,造成队列堵塞。

针对上述问题,我们设计了如图所示架构,搭建两组消息队列,互相独立。支付通知分别向L队列和W队列的一个10秒延时队列投递消息,只要有一个投递成功即可。

  • 消息到达L队列后,迅速被发券L服务消费。发券L服务拿到消息后,先ack消息,再尝试进行发券,不论成功或失败,仅一次。
  • 与此同时,相同的消息到达W的10秒延时队列后,经过10秒时间,被投递到MQ_W队列,被发券W服务拿到。发券W服务先检查此消息关联的订单是否已成功发券,若非,尝试进行发券,并完成一系列兜底策略,如超过30分钟自动退款等。

去掉一些细节部分,全景如下:
订单架构

稳定性保障

目前,订单系统服务化已完成,从上述模块部署图中可以看出,架构设计中充分考虑了隔离、降级等容灾措施。具体从以下几个方面说明:

  1. 开发、测试。相比于原来大一统的系统,彼此代码耦合、无法进行测试,服务化后,各个模块单独开发部署,依赖便于mock,单元测试很容易进行。同时我们搭建了稳定的线下环境,便于回归功能。
  2. 蓝绿发布。这是无停机发布常见的一种方法,指的是系统的两个版本,蓝色的表示已经在生产上运行的版本,绿色表示即将发布的新版本。首先将两套版本的系统都启动起来,现有的用户请求连接的还是旧的蓝色版本,而新的绿色版本启动起来后,观察有没有异常,如果没有问题的话,再将现有的用户请求连接到新的绿色版本。目前线上服务发布均采用蓝绿发布流程,对用户无感知。
  3. 多机房部署。按照整体规划,订单系统主要以一个机房为主,另一个机房作为辅助,按照2:1比例进行部署,提升机房故障容灾能力。
  4. 促销与非促购买隔离。如上述下单部署架构图,我们推动App方,对于促销和非促流量,从源头上区别访问地址,达到物理隔离,做到互不影响。
  5. 全流程去单点。除去数据库主库外,全流量无单点,弱化对消息队列的依赖,使用Databus进行数据异步复制;对发券服务搭建两套独立集群,只要同时有一个集群正常运行即可。
  6. 自动化降级。使用开源组件Hystrix,处理外部依赖。当某个服务失败率超过阈值,自动进行熔断,不再访问,经过一定时间后再访问探测该依赖是否恢复。
  7. 完善的监控。利用统一日志中心收集订单流转消息,使用Elasticsearch检索来定位、发现并解决问题。使用Falcon对异常进行报警。

小结

至此订单服务化完成,架构部署比较清晰,业务日常迭代只影响相关的小服务,便于开发。
新的架构有效支撑了今年七夕等节日高流量,运行稳定。
在整个服务优化过程中,也走过一些弯路,结合一些成功的实践,总结如下:

  1. 要有一个Roadmap,设计上要有整体的、长远的、合理的规划,分步骤朝这个方向靠拢。
  2. 不要掉入过度追求性能的陷阱。服务做到多少QPS,多长的耗时,应结合手头有限的资源、业务需求来制定。例如在做订单号改造的时候,Tair和Redis等均能提供上万QPS能力,但业务上并不需要这么多,应该更关注稳定性,所以最终没有采纳,还避免了增加一个依赖。
  3. 对于整体系统来说,要找到瓶颈点整体优化。局部服务能力的提升并不代表整个系统能力的提升。
  4. 每一步操作应该有个评判的标准。优化前和优化后系统的可用性是提升了还是下降了,理论上是可以分析的。

  1. 整个服务已进行了拆分,变成一个个微服务,那就应该允许服务之间的差异化、个性化。避免大而统一且繁琐的配置,个人觉得极少变化的值可以硬编码,配置数量不应过多。



http://blog.csdn.net/lang_niu/article/details/54578292

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

美团团购订单系统优化记 的相关文章

  • 期货开户交易保持独立客观

    一旦相信了交易就是概率游戏 那么对错 赢亏等概念就不再重要 有了合适的期望 就不会把市场定义解读为痛苦的或威胁的 也会有效地中和交易的情绪风险 保持策略的持续一致性客观地确认优势 这是长期经验总结的结果 但优势不是完全正确 仅仅代表概率较高
  • 鸿蒙笔记2

    常用基础组件 1 组件介绍 组件 Component 是界面搭建与显示的最小单位 HarmonyOS ArkUI声明式开发范式为开发者提供了丰富多样的UI组件 我们可以使用这些组件轻松的编写出更加丰富 漂亮的界面 组件根据功能可以分为以下五
  • MongoDB数据库常用SQL命令

    1 db collection updateMany 修改集合中的多个文档 db getCollection user find pId 3332a512df604a74a72f267cf246 updateMany pId c8018dd
  • ES6 数组的扩展方法

    1 数组的方法 from of from 将伪数组转换成真正的数组 function add console log arguments es5中 将参数转换成数组 let arr slice call arguments console
  • 时间序列--残差分析

    残差 y yhat 一般我们就停止在这里了 但是如果残差表现的有某种形式 代表我们的模型需要进一步改进 如果残差表现的杂乱无章 代表确实没什么别的信息好提取了 现在用最naive的model 上一个时间的值 yhat看看残差表现吧 关于残差
  • 初识QT(二十)——Qt元对象和属性系统详解

    Qt 是一个用标准 C 编写的跨平台开发类库 它对标准 C 进行了扩展 引入了元对象系统 信号与槽 属性等特性 使应用程序的开发变得更高效 本节将介绍 Qt 的这些核心特点 对于理解和编写高效的 Qt C 程序是大有帮助的 Qt 的元对象系
  • http协议 git服务器,利用Nginx搭建HTTP访问的Git服务器

    利用Nginx搭建HTTP访问的Git服务器过程记录 搭建 Git 仓库 实现 SSH 协议 配合 Nginx 实现 HTTP 协议拉取 推送代码 利用 Nginx 实现 Gitweb 在线浏览代码 使用 Gitweb theme 更新默认
  • Super Egg Drop

    题目 出处 参考的解法 class Solution public int superEggDrop int K int N int memo new int K 1 N 1 return helper K N memo private i
  • 在javascript中,slice与splice的区别

    介绍 众所周知 Javascript中的数组是能够保存多个值的变量 我们有多种方法来处理数组 其中最常用的是 slice 和 splice 有时人们会混淆这两者 因此 在本博客中 我们将了解这两种方法以及它们之间的区别 Slice slic
  • Linux网络编程-客户端与服务器端通信

    Linux网络编程 客户端连接服务器端让我们已经看到了client与server之间是如何建立连接的 接下来介绍它们之间如何建立tcp协议交互通信 先看看服务器端代码 tcpserver c include
  • 15个经典面试问题及回答思路,知乎上转疯了!

    一 简介 Handler机制是一套Android消息传递机制 在Android开发多线程的应用场景中 将工作线程中需更新UI的操作信息 传递到 UI主线程 从而实现 工作线程对UI的更新处理 最终实现异步消息的处理 在Android开发中
  • Linux 初始组(主组)和附加组详解

    Linux 初始组 主组 和附加组详解 介绍 在Linux系统中 每个用户都有一个主组和多个附加组 初始组 主组 是用户创建后默认分配的组 而附加组则可以根据需要进行添加或删除 本文将介绍Linux系统中初始组 主组 和附加组的方法 并探讨
  • tar.xz文件如何解压

    创建或解压tar xz文件的方法 习惯了 tar czvf 或 tar xzvf 的人可能碰到 tar xz也会想用单一命令搞定解压或压缩 其实不行 tar里面没有征对xz格式的参数比如 z是针对 gzip j是针对 bzip2 创建tar

随机推荐

  • vue el-table 树形数据懒加载每次点击子集父级只展示一行

    功能需求 每次只打开一个同级数据节点展开 之前展开的自动收起 ps 找了好久都没有找到完全符合需求的组件 展示效果 vue el table 数据懒加载实现每次子集只展示一行 实现代码 模板 使用load配合expand change使用
  • IDEA创建SpringBoot无法连接https://start.spring.io

    解决办法 将网址改成 http start spring io
  • 小白学股票基金_4_ETF

    ETF Exchange Traded Funds 中文名称为交易型开放式指数基金 是一种在交易所上市交易的开放式指数基金 兼具股票 开放式指数基金及封闭式指数基金的优势 属于高效的指数化投资工具 也就是说 如果我们买一手科技ETF 就可以
  • Intellij IDEA使用技巧,去掉拼写检查和unused提示

    在setting里面搜索spell将其中的拼写检查的 号去掉 搜索never used 关键字将其中的unused的检查去掉
  • 模块化软件设计

    模块化的基本原理 模块化 Modularity 是在软件系统设计时保持系统内各部分相对独立 以便每一个部分可以被独立地进行设计和开发 这个做法背后的基本原理是关注点的分离 SoC Separation of Concerns 关注点的分离在
  • 第5天-[21天学Python]-Python中自定义函数及调用的方法

    本章内容主要包括 声明函数 调用自定义函数 变量作用域 各种类型的函数参数应用 使用lambda建立匿名函数 Python其他常用内建函数 1 使用函数 1 1 声明函数 在python中 函数必须先声明 然后才能调用它 使用函数时 只要按
  • Multisim14.0仿真(八)LM555制作流水灯

    一 仿真原理图 二 仿真运行效果
  • mysql-数据页结构

    数据页结构 数据删除后记录并没有马上被删除 而是被打上了删除标记 并被记录到一个垃圾链表中 之后若有新纪录来 它们则可能覆盖被删除的记录占用的存储空间 页内数据组成单向链表 且再次进行了分组 每组最后一条数据顺序存储在靠近页尾部的地方 这种
  • react-jsx语法上使用switch匹配不同渲染组件

    这里主要讲的是jsx语法使用switch 的js语句 一般jsx语法执行的是简单的运算和三元表达式 如果想要执行条件判断如if语句和switch语句以及函数等 直接使用是会报错的 这里应该使用函数立即执行的语法写法 如果需要根据不同判断渲染
  • 关于javascript中的函数作用域

    var scope global function f alert log scope 输出 undefined 而不是 global var scope local 变量在这里赋初值 单变量本身在函数体内任何地方军事有定义的 consol
  • vue配置页面预渲染(将页面静态化,便于seo读取)

    在项目中安装prerender spa plugin npm install save prerender spa plugin 找到bulid目录下的webpack prod conf js文件 在其中写入以下内容 在文件的上方写入 co
  • tensorflow学习笔记(四)

    代码学习有点吃力 学习了YOLOv1的代码 主要是训练部分的代码 对yolo的又有了进一步的理解 其文件夹下主要包含py文件为 train py yolo net py pascal voc 下面是比较详细的代码解读 但是还是有一些内容理解
  • 《曾国藩家书》读书手记(修身篇二)

    致诸弟 劝弟谨记进德修业 吾人只有进德 修业两事靠得住 进德 则孝弟仁义是也 修业 则诗文作字是也 这个是说进德修业是很重要的东西 这两个东西是越积累越多的 只增不减的 致诸弟 劝弟切勿恃才傲物 故吾人用功 力除傲气 力戒自满 毋为人所冷笑
  • 基于PaddleOCR开发Auto.js Pro文字识别插件

    目录 目的 准备工作 插件开发 1 项目结构对比 2 插件SDK集成 3 调整assets资源 4 删除无用的Activity文件 5 修改AndroidManifest xml 6 修改Predictor文件 7 修改包名 8 新建OCR
  • 从零开始的Python机器学习指南(二)——监督学习之OLS回归

    介绍 本博客将结合样例介绍监督学习 Supervised Learning SL下的第一大分支 回归 Regression 开始前的准备 开始前 请先确保你的python环境中有以下包 pandas numpy sklearn 本文的所有代
  • 矩阵论(五)——矩阵分析

    矩阵论 五 矩阵分析 1 向量范数 2 矩阵范数 3 向量序列与矩阵序列的极限 3 1 向量序列的极限 3 2 矩阵序列的极限 4 矩阵幂级数 1 向量范数 向量范数 x V forall x in V x V
  • 万字长文,梳理清楚Python多线程与多进程!

    作者丨钱魏Way 来源 https www biaodianfu com python multi thread and multi process html 在学习Python的过程中 有接触到多线程编程相关的知识点 先前一直都没有彻底的
  • 细说MVC、桌面客户端应用软件和WPF

    MVC开发框架相比较于类似ASP这种翻译脚本语言来讲 已经让广大的Web开发者有了足够的兴奋点 它使得Web开发更加简单和规范 那么接下来的桌面应用软件呢 Kevin Hoffman在Cocoa下的MVC框架和WPF的嵌入式开发有很好的经验
  • 多线程(六)-sleep和wait方法的区别

    目录 一 sleep和wait方法的区别 二 wait方法 wait方法的使用 wait 结束等待的条件 三 notify和notifyAll方法 notify 方法只是唤醒某一个等待的线程 notifyAll方法可以一次唤醒所有的等待线程
  • 美团团购订单系统优化记

    美团团购订单系统优化记 团购订单系统简介 美团团购订单系统主要作用是支撑美团的团购业务 为上亿美团用户购买 消费提供服务保障 2015年初时 日订单量约400万 500万 同年七夕订单量达到800万 目标 作为线上S级服务 稳定性的提升是我