Closed pengjunlong closed 5 years ago
告警的本质 没有多少系统的告警是设计得当的。良好的告警设计是一项非常困难的工作。如何知道你收到的告警是糟糕的?多少次你收到了告警之后,立即就关掉了的?是不是成天被这些然而并没有什么卵用的东西给淹没?最常见的告警设置:cpu使用率超过90%,然后告警。这种设置在大部分场合下是没有办法提供高质量的告警的。
高质量的告警应该是这样的:每次收到之后你可以立即评估影响的范围,并且每一个告警需要你做出分级响应。所谓每个告警都应该是,actionable的。
告警的实质可以用下图表明:
服务器的设计应该是以这样的无人值守为目的的。假设所有的运维全部放假了,服务也能7*24自动运转。
告警的实质就是“把人当服务用”。在一些事情还没有办法做到程序化执行的时候,用告警通知人的方式去干预系统达到修正的目的。一次告警就像一次服务调用一样。如果告警了,但是收到告警的人并不需要做任何处理,那么这就是一种DDoS攻击,攻击的是运维的幸福生活。
很多时候,告警通知人去干的事情是真的可以被自动化掉的。比如服务器挂了,换一台上来。在小一点的系统里,可能就是停机一会,人工来处理换一台冷备的机器上去。大一点的系统,因为服务器多了,天天都挂可不行,必须是热备的,系统自动切换到备机。再大一点的系统,因为切换实在太频繁了,故障机的退库,备机的保有都变成了一种管理负担,那么可以和其他的运维流程打通变成完全自动化的系统。只是因为业务处理不同阶段,选择不同的实现策略而已。业务量小,拿血肉当机器用,有的时候更经济而已。当然对于那个被当成机器人来用的哥们来说,生活确实有点不公平。
告警对象 告警对象可以分为两种:
业务规则监控 系统可靠性监控 对于业务规则监控可以举一个游戏的例子。比如DNF的游戏角色在一定装备的情况下,单次打击的伤害输出应该是有一个上限,如果超过了就说明有作弊的情况。又比如斗地主游戏里一个人的连胜场次是有一定上限的,每天的胜率是有一定上限,如果超出平均值太多就可能是作弊。业务规则监控的不是硬件,也不是软件是否工作正常。而是软件是否按照业务规则实现的,是否有漏洞。也可以理解为对“正确性”的监控。
系统可靠性监控是最常见的监控形式,比如发现是不是服务器挂掉了,服务是不是过载了等等。对于大部分后台服务,系统可以抽象建模成这个样子:
对于这样的系统可以采集什么指标?
请求数,请求到达速率 正常响应数,正常响应占比 错误响应数,错误响应占比 响应延时 队列长度,排队时间 实际的情况是,几乎任何系统都不是孤立运行的。而是这样的:
一个DB会依赖于底层的cpu,内存,磁盘等资源。一个Http服务会依赖于底层的DB服务。一个应用会依赖于数个底层的RPC服务。于是又多了几个指标
资源A的调用量(比如CPU使用率) 资源B的调用量(比如内存分配和释放) 资源C的调用量(比如网络发送包量) ... 这种层次结构,一般来说简单来说可以分为四层:
产品策略和营销:它们决定了根本的请求到达的速率 应用层(更粗俗一点可以叫web层):最上层的胶水 服务层:db,各种RPC服务,以及层层嵌套的服务 硬件层:cpu,内存,磁盘,网络
因为这样的一个依赖层次。上一层对下一层的资源消耗量变成了下一层的请求数。比如Http服务消耗了多少DB的资源,就对应了DB服务需要处理多少请求数。DB繁忙与否取决于Http服务请求,Http服务请求繁忙与否取决于多少人打开客户端,多少人打开客户端又取决于产品策略和营销活动。这种层次结构决定了单纯跟踪一个指标,比如绝对请求数,很难说明这一层的服务是否出现了故障。
有这么多层次,每层又有很多指标可以采集。那么应该采集什么指标,用什么告警策略去告警呢?最前面已经提到了告警必须是actionable的,但是实际情况下只有这种纲领性要求仍然是不好操作的。至少可以提几点不应该做的事情:
不应该用采集的难度决定你使用什么指标去告警。很多情况下cpu使用率可能是最好采集的,但是未必是最值得告警的。 不要给运维他们想要的告警,而是要做“真正”想要的告警。大部分情况下,人们告诉你的是一个解决方案。运维告诉你它需要对db进程的cpu使用率超过x%的时候告警,它给你的是一个他认为最优的解决方案。但是他真正想要的是知道db服务是否有异常,cpu使用率超过x%未必是最好的告诉你服务是否出现异常的指标。 盲目地采集那些容易获取的指标,并随意地设定阈值告警是大部分糟糕的告警质量的根源。
监控的指标和策略 那到底应该采集什么指标呢?我认为大部分的系统可靠性监控不外乎三个目标:
is the work getting done?系统是否在持续完成其设定的工作。 is the user having good experience?用户体验是否好。 where is the problem/bottleneck?问题或者瓶颈在哪里。 其中最核心最关键的是第一个问题,is the work getting done。对于数据库来说,我们可以采集:
cpu 使用率 网络带宽大小 db请求数 db响应数 db错误响应数 db请求延迟 显然要回答一个db是否完成了其指定的工作,更应该关注的指标是这两个:
db请求数的绝对量 db正确响应相对请求数的占比 这两个指标相对于采集什么cpu使用率更能说明问题。不仅仅是db,各个层次的服务都可以用请求量和正确响应占比来反映其工作状况。比如http请求数(对比http正确响应数),比如app打开次数(对比服务端记录的在线人数)等等。
为什么cpu使用率不能说明问题?大部分时候,我们并不关心cpu本身,而关心使用cpu为资源的服务。所以cpu使用率只是一种资源的请求数而已。与请求数相关的一个概念是saturation(上限),当上限达到的时候,处理开始排队,延迟开始变长,错误率开始升高。那么cpu使用率是不是能够说明上限呢?cpu使用率的上限以100%记,那么90%开始告警不是很合理吗?毕竟cpu 100%了几乎可以等同于db无法正常处理请求了。
这种利用底层资源调用量,评估其是否达到上限的做法有两个根本缺陷:
你无法知道上层服务可以把底层资源利用到什么程度 底层资源的 saturation 未必可以容易度量 具体来说,db是不是可以真的100%利用cpu是位置的。假如请求里锁,或者sleep,那么也许cpu永远也无法达到100%。90%可能就是极限了。而且现代的cpu是多核的,如果请求处理只能利用单核,处理在多个核之间跳跃,对于一个核来说永远也不会一直保持100%。
对于cpu可能其上限真的有一个100%的值。但是对于很多非硬件的服务,比如你是一个登陆服务,依赖于一个db。那么这个db每秒可以处理的不同sql组合数是很难度量的,绝非和磁盘一样有一个mb/s的极限绝对值可以做为对比。
而且度量底层资源的使用还有一个缺陷是你无法枚举出所有依赖的资源的。所以与其这么绕弯子地通过底层资源来间接监控上层服务是否正常,还不如直接测量work是不是getting done呢。
对于第二个问题,is the user having good experience?可以采集的指标为
平均排队时间,平均总响应延迟 99/95/90 percentile的排队时间,99/95/90 percentile的响应延迟 这里的用户不一定是指人或者玩家,可能是上一层的服务调用方,另外一个系统。
第三个问题就是所谓的故障定位。要是人工来做的话,最常见的做法是收到了告警,然后登陆CRT,开始敲各种命令查找原因。对于系统来说,最合适的做法不是出了问题再去执行一堆命令,而是:
每个层次都对自己做告警 顶层服务出了告警触发自动定位程序 按照服务的依赖关系和大致的时间范围,定位到告警之间的关联,从而找到出问题或者瓶颈的地方 当然实际情况是很复杂的。很多原因和结果是互为因果的。两个告警是两个现象,还是一个原因一个现象实际上很难说得清楚。
从告警算法的角度来讲,对成功请求率,或者平均响应延迟做告警是非常容易的。静态阈值大家看不起,觉得简单。但是大部分告警用静态阈值就可以解决问题。
理论与现实 那告警要不要高难度的算法?我的观点是采集到了正确的指标,是不需要复杂算法的,就是静态阈值都可以搞得定。但是至少有三种场合需要算法:
无法直接采集到错误数:需要对错误日志的自动分类 无法直接采集到请求成功率:需要对请求数或响应数的绝对值做异常检测 只有总数,无法采集到其中的每个细分构成项的占比:需要对参与的factor进行算法拟合 其实这三项都是一个主题的,当你无法直接获取到告警所需的指标的时候,事情会变得复杂很多。有一个比喻是:最近NASA宣布的地球孪生兄弟Kepler 452b。如果我们的探测器可以跑到1400光年之外,发现他将是非常容易的事情。正式因为直接获得数据非常困难,所以科学家才需要根据行星阻挡恒星时引起的亮度变化(所谓掩星法)来发现这些遥远的星球。
采集所需的指标的困难可能是几方面的因素。一种原因是采集本身是非常消耗资源的事情。比如获取每个mysql查询所消耗的cpu。跟踪每个请求处理过程是不可能的。这个时候就需要算法的帮助了,可以仔细看一下vividcortex的视频:http://www.youtube.com/watch?v=szAfGjwLO8k
更多情况是采集指标困难是D/O分离造成的沟通问题,运维需要的指标需要开发去埋点,而开发埋点的地方又需要运维去做告警。很多时候退而求其次就会造成,有什么指标就用什么指标的状况。比如虽然没有请求响应的错误数,但是错误基本上都会有错误日志记录,根据错误日志滚动的快慢可以大致知道是不是出了问题。这就引入了一个非常困难的日志分类问题,什么日志代表了正常,什么日志代表了异常,异常又非了哪些类型?这个方面算法做得好的是summo logic公司:Log Management & Analytics, Continuous Intelligence: Sumo Logic 。为什么这种opsdev(嘲讽devops那)公司如此热衷于算法?对于他们来说好处是显而易见的,客户需要做的改动越少,接入成本越低,客户面就越广。但是拿机器算法去挖掘海量日志真的是回答:is the work getting done?的最佳手段?显然不是。这就是大炮打蚊子。日志的存在是用于解决问题,而不是有了海量日志了,如何用好“它们”变成了问题本身。
第三类情况是没有办法采集到请求成功率,只能对绝对的处理成功的量。只有这类数据要告警,就无法做简单的静态阈值了。对于延迟,一般可以定一个业务上可以接受的延迟上限。对于成功率,也可以定一个可接受的成功率上限。但是对于绝对的处理量,是没有办法简单地比较一个静态阈值就可以判断是正常还是异常的。
在讨论如何实现之前,再强调两点:
处理成功的量不是度量is work getting done的最佳指标。费事费力去搞算法,不如直接把成功率指标给采集了。 处理成功的量,还取决于请求数。而请求数根本上是取决于上层服务了。你是一个dba,发现db的每秒处理的请求数陡降了。这说明是db故障了?还是app故障了?都有可能……最最上层是产品和营销。你发现一个业务的注册量相对前几天变少了,这个是不是说明注册服务出问题了?也需是产品太烂了,游戏根本没有人来玩。也可能是营销手段的营销,不送金币了,玩家没积极性了。 异常检测 只有请求数,没有参考的上限值(saturation),也没有成功率,没有失败率,怎么检测异常?
上图的黄线是昨天的值,绿线是今天的值,大部分服务监控的曲线图都长这样。可以得出四个思路:
曲线平滑:故障一般是对近期趋势的一个破坏,视觉上来说就是不平滑 绝对值的时间周期性:两条曲线几乎重合 波动的时间周期性:假设两个曲线不重合,在相同时间点的波动趋势和振幅也是类似的 有一个长度可观的坑:当曲线开始回升到历史范围的时候,一般可以确认这个时间段是真的故障了 从这四种直觉展开,可以得出各种或复杂或简单的算法。下面要讲的算法都是非常简单的,无需很高深的数学知识。
基于曲线的平滑性的检测 这种检测的根据是在一个最近的时间窗口,比如1个小时。曲线会遵循某种趋势,而新的数据点打破了这种趋势,使得曲线不光滑了。也就是说,这种检测利用的是时间序列的temporal dependency,T对于T-1有很强的趋势依赖性。业务逻辑上来说,8:00 有很多人登陆,8:01 也有很多人来登陆的概率是很高的,因为吸引人来登陆的因素是有很强的惯性的。但是7.1很多人来登陆,8.1也有很多人来登陆的惯性就要差很多。
基于近期趋势做告警,就需要对曲线的趋势进行拟合。拟合有两种方式,moving average 或者 regression。这两种拟合方式有不同的bias(倾向)。
这就是一种moving average的算法图,叫做exponentially weighted moving average。它的计算非常简单
x是实际值,s是ewma计算出来的平均值。也就是下一点的平均值是由上一点的平均值,加上当前点的实际值修正而来。这个修正的比例,就取决月这个alpha的decay factor的大小。视觉上来说就是ewma曲线是否紧跟实际曲线,也就是平滑程度。
有了平均值之后可以计算方差,方差乘以一定的倍数可以得出对于振幅的容忍范围。比较实际的值是否超出了这个范围就可以知道是否可以告警了。超出了上界,可能是突然用户量突然激增了。超出了下届,可能是营销活动结束了,用户快速离开,也可能是光纤断了,玩家掉线了。想要了解更多关于ewma的算法细节:关注Baron Schwartz(http://www.slideshare.net/vividcortex/statistical-anomaly-detection-fo...)
moving average认为曲线是趋向于历史的,如果曲线的势头是上升,那么它认为下一个点应该是开始下降的。regression认为曲线是趋向于未来的,如果曲线的势头是上升,那么它认为下一个点应该是保持这个上升势头。还有更复杂的模型是综合了moving average和regression的。无论是哪种算法,用过去10分钟预测下10分钟是不可能精确的。如果这种预测可以精确,那么股神早就诞生了。使用moving average,可能会掩盖故障产生的下降(因为其bias是下降)。如果使用regression,那么又有可能把没有上升得那么快当成故障了(因为其bias是上升)。
这种基于近期趋势计算方差的算法还有一个缺陷是当前面几个点振动很大的时候,方差值会被搞大。后面的故障就被掩盖了,使得连续的故障点无法被检测到。其实也就是算法对于什么是正常是没有概念的,它认为过去的历史就是正常。如果过去几分钟处于故障中,那么故障的曲线就是正常。
实际使用中发现这种基于曲线平滑度的算法的优点有
依赖的数据少,只需要近期的历史,不依赖于周期性 非常敏感,历史如果波动很小,方差就很小,容忍的波动范围也会非常小 缺点也是显著的
过于敏感,容易误报。因为方差会随着异常点的引入而变大,所以很难使用连续三点才告警这样的策略 业务曲线可能自身有规律性的陡增和陡降 最佳的使用方式是不用一根曲线做告警。结合几条相关的曲线,如果同时出现平滑度破坏的情况,而且与业务规律的趋势相背离(比如在线人数降低,登陆请求数增高)则可以认定为业务出现故障。
基于绝对值的时间周期性
上图中不同的颜色代表了不同日期的曲线。很多监控曲线都有这样以一天为周期的周期性(早上4点最低,晚上11点最高之类的)。一种利用时间周期性的最简单的算法
min(14 days history) * 0.6
对历史14天的曲线取最小值。怎么个取最小值的方法?对于12:05分,有14天对应的点,取最小值。对于12:06分,有14天对应的点,取最小值。这样可以得出一条一天的曲线。然后对这个曲线整体乘以0.6。如果几天的曲线低于这条参考线则告警。
这其实是一种静态阈值告警的升级版,动态阈值告警。过去静态阈值是一个根据历史经验拍脑袋的产物。用这个算法,其实是把同时间点的历史值做为依据,计算出一个最不可能的下界。同时阈值不是唯一的一个,而是每个时间点有一个。如果1分钟一个点,一天中就有1440个下界阈值。
实际使用中0.6当然还是要酌情调整的。而且一个严重的问题是如果14天历史中有停机发布或者故障,那么最小值会受到影响。也就是说不能把历史当成正常,而是要把历史剔除掉异常值之后再进行计算。一个务实的近似的做法是取第二小的值。
为了让告警更加精确,可以累积计算实际曲线和参考曲线的差值之和。也就是相对于参考曲线下跌的面积。这个面积超过一定的值则告警。对于深度下跌,则累积几个点就可以告警。对于浅度下跌,那么多累几个点也可以告警出来。翻译成人话就是,一下在跌了很多,则很有可能是故障了。或者连续好久都偏离正常值,那么也很有可能是出问题了。
优点:
计算简单 可以确保发现大的故障,出了告警一定是大问题,可以直接打电话 缺点:
依赖周期性的历史数据,计算量大,而且无法对新接入的曲线告警 非常不敏感,小波动无法发现 基于振幅的时间周期性
有些时候曲线是有周期性,但是两个周期的曲线相叠加是不重合的。比如上图这样的,曲线整体的趋势是网上的。两个周期的曲线一叠加,一个会比另外一个高出一头。对于这种情况,利用绝对值告警就会有问题。
比如今天是10.1日,放假第一天。过去14天的历史曲线必然会比今天的曲线低很多。那么今天出了一个小故障,曲线下跌了,相对于过去14天的曲线仍然是高很多的。这样的故障如何能够检测得出来?一个直觉的说法是,两个曲线虽然不一样高,但是“长得差不多”。那么怎么利用这种“长得差不多”呢?那就是振幅了。
与其用x(t)的值,不如用x(t) - x(t-1)的值,也就是把绝对值变成变化速度。可以直接利用这个速度值,也可以是 x(t) - x(t-1) 再除以 x(t-1),也就是一个速度相对于绝对值的比率。比如t时刻的在线900人,t-1时刻的在线是1000人,那么可以计算出掉线人数是10%。这个掉线比率在历史同时刻是高还是低?那么就和前面一样处理了。
实际使用中有两个技巧:可以是x(t) - x(t-1),也可以是x(t) - x(t-5)等值。跨度越大,越可以检测出一些缓慢下降的情况。
另外一个技巧是可以计算x(t) -x(t-2),以及x(t+1) - x(t-1),如果两个值都异常则认为是真的异常,可以避免一个点的数据缺陷问题。
优点:
比绝对值要敏感 利用了时间周期性,规避了业务曲线自身的周期性陡降 缺点:
要求原曲线是光滑的 周期性陡降的时间点必须重合,否则误警 按百分比计算容易在低峰时期误警 陡降不一定代表故障,由上层服务波动引起的冲高再回落的情况时有发生 这种异常告警算法是比较优秀的。缺点也很多。所以可以进行一些修补凑合用。为了避免低峰时期,基于振幅百分比容易误警,可以加入绝对振幅的下限。业务上来说,就是小波动如果相对比率大,但是绝对影响范围小也是没关系的。对于冲高回落的问题,可以判断一下冲高的情况,对于冲高之后屏蔽一段时间。
基于曲线回升的异常判断
当我们看见图2的时候比图1更确认是故障了。为什么?因为图2中有一个明显的回升。算法其实和人眼一样。如果多等几个时间点,发现曲线回升了可以更很准确地判断“曾经”有一个故障。但是这种基于回升的异常检测是没有多少“告警”意义上的机制的。告警的作用就是让人参与干预,去帮助曲线回升。如果曲线已经开始回升,再告警不是事后诸葛了吗?
这种检测的意义在于机器复制告警的确认。当我们需要统计误警率,漏警率的时候。用另外一种视角的算法重新跑一遍可以统计出很多原算法的问题。同时也可以用半自动化的方式建立一个历史故障的样本库。这个样本库可以变成更复杂的机器学习算法的训练集。
总结 Key take away
高质量的告警是actionable的 不应该用采集的难度决定你使用什么指标去告警 不要别人做什么告警,你就做什么,要做“真正”有用的告警:特别是cpu使用率告警 is work getting done:请求数 + 成功率 is the user having good experience:响应延迟 只要采集对了指标,大部分时候告警不需要复杂算法 基于算法的异常检测:算法不难,实在必要也是可以做到的
100天以前,我是一个屯书狂,信奉“买书是最经济的消费方式”,不错过京东和当当任何一次满XX减XX的优惠活动。但买书如山倒,读书如抽丝,漫无目的买了一堆,成了家里的装饰。
100天之后,我初步建立了自己的知识体系,我明白自己要读哪些,为什么要读,怎么读;我开始在简书上写自己的专题,在线上做主题分享,在线下和小伙伴一起拆书。
这篇文章的目的,是想为我自己总结一下一个31岁的老学渣如何通过100天(每天清晨2个番茄钟)的阅读来进行自我提升的过程,也希望能给小伙伴们提供一些参考。
一、蛮荒时代:漫读书 蛮荒时代就是漫无目的地无事乱翻书,朋友圈有人晒的,图书馆偶然翻到的;看到书单就收集,然后kindle里面下了一堆电子书,十几个G的电子书,反正不要钱,反正不占空间··· ···这个时期的读书就像原始人的生活,看到顺眼的就放到嘴里尝尝;味道好就多吃,味道不好就扔掉;看到有促销就大量囤积,也不管看不看的完,合不合适。
蛮荒时代是见到什么读什么
这个时代的杂食方式很适合培养阅读爱好,知道自己的口味。但是在成人学习的角度看,我在这个时期的阅读并没有获得多少养分,阅读也没有给我带来致用的价值。
二、农业时代:一周读一本书 经过漫长的蛮荒时代,唯一锻炼的能力是对阅读没有恐惧,好吃的也吃过,难吃的也吃过,所以好坏都能尝尝。
在漫长的蛮荒时代之后,我参加十点读书会的100天阅读计划。这个活动被限定每天要对看过的内容进行124个字的小总结,并在微博中进行打卡,并坚持100天。
100天阅读计划让我从开始从蛮荒时代进化
这个过程每天都和天南海北的小伙伴们打卡互相勉励,“我一定能存活下来”是那时候经常和小伙伴们拼的一句话。开始是很折磨人,因为之前是原始人,哪有这样的规矩;现在好啦,每天要写小总结——于是慢慢养成了习惯,看完书就立马落笔记录。
这个过程很辛苦,但是慢慢让我对读过的内容有了越来越清晰的记忆和理解。
因为每天都记录读过的书,读过了多少页,用了几个番茄钟。我开始了解自己的阅读速度。于是就想从蛮荒时代进入农业时代,我开始稳定下来,一周完成一本书的阅读,并产出书评或者读书PPT。
这个过程对于书本的选择也开始有了一些方法:
开始通过豆瓣来找书,看书评,通过快速阅读豆瓣评价来判断这本书是不是适合我。
同类型的书还有哪些,这本书的延伸还有哪些?
勤恳读书的农业时代漫长但是却非常重要
三、工业时代:一天读一本书 农业时代的一周一本书计划让我每周都固定有了知识的输入,而且这样每周的阅读得到了量化,一周就1本,每天记录进度和小回顾,读完写篇整书评,这个过程很好记录也很好执行。
从农业到工业时代的交替,肯定需要重大的技术革命。我的第二次阅读革命就是了解了什么是“快速阅读”。
在职场中,阅读商业致用类书籍是绝大多数,我的一周一本书计划也绝大部分是属于这里。
开始尝试使用“快速阅读”之后,我原来一本书逐字逐页,从头看到尾,每天一小时,一周看一本的阅读方式,变成了30分钟速读模式。我尝试练习用30分钟中时间读懂一本书;这个过程当然没有那么容易,我通过限时的方式来训练自己,30分钟完成不了,我设定1个小时完成。
限时阅读,读后立马产出,讲究效率的的工业时代
快速阅读让我用20%的时间去找到书本80%的重点内容,剩下的20%重点,直接舍弃。这样的遗漏并不会影响我的书本理解,而且相对原来一周一本书的时间成本,快速阅读让我的效率提高了5倍,我可以做到一天读一本书。这简直帅呆了,我开始像一台机器一样对蛮荒时代和农业时代囤积下来的书进行快速尝了一遍——合适的留下,不合适的送人。
快速阅读让我步入工业时代快车道
四、信息时代:主题阅读 1天1本书的进度确实很吓人了,想想一年能看300本书,是多么牛叉的一件事情呀。
但是我的阅读训练并没有就此停步,因为在阅读量上来之后,我又开始接触到新的技术:主题阅读。战隼老师和秋叶老师强烈推荐过,《如何阅读一本书中》也将其作为阅读的高阶过程。
主题阅读的方法是带着问题,设定一个主题然后寻找答案;通过主题,罗列出主题书单,然后从中筛选出10-15本书,然后在这些书中寻找答案,再产出主题报告。因为有了快速阅读的经验,所以我的主题阅读筛选过程非常快,一天一本的速度,我可以在一周之内完成一个主题阅读。
参加死磕小组让主题阅读变得非常有乐趣
这时候我已经不再执着于阅读量了,从原来的1天1本书变成了一周15本书,说出去人家会骂我是骗纸啊!ORZ。我利用主题阅读进入了知识大爆炸的信息时代,我的个人知识体系也是在这个过程中初步建立雏形。
主题阅读让我成为职业生涯的主人
五、互联网时代:按需阅读 从漫无目的胡乱读书的野蛮人到了大阅读量的信息时代,虽然我也很反感阅读这件事情去讲什么数量,但是阅读量的增加确实让量变产生了质变:
这本书是否对我有用,如果要读,我还可以找哪些书来读?
进入一个新的领域后,我应该去如何快速补充这个领域的专业知识?
一本书我能从中应用几个知识点到我的工作中去?
以上这些我之前所没有思考过,在100天后,我越来越能清晰的回答这些问题。
随着主题阅读的深入,和阅读量的增加,我对于读书方法有了更多的包容性:
面对基础类的知识,我会选择蛮荒时代的办法,遇到喜欢的就吃,东看西翻,精益试错;
面对基础专业类的书,我会选择信息时代的办法,进行主题阅读,全方位去了解;
面对专业类的书籍,我会用农业时代的办法,精耕细作,慢慢研究。
于是我的阅读方式进入互联网时代,这就像互联网时代的思维一样,精益迭代,用变化去面对变化,几种阅读方式交替进行,根据自己的工作和生活环境来改变。就像一只自带系统随处插拔的U盘,随时更新自己。
保留每个时代的阅读基因,按需阅读
六、总结:改变的选择权在我们自己 以上就是我的100天阅读变革过程了:
蛮荒时代:漫无目的随便乱看
农业时代:每天阅读做记录
工业时代:一周一本书,注重输出;尝试快速阅读
信息时代:主题阅读,阅读量大爆炸
互联网时代:根据变化来选择阅读方式
100天的时间,占据了一年将近1/3的时间;当我在这100天里完成了自己的阅读方式的重要革命。我很感谢十点读书会和战隼老师这次的100天阅读计划,他们给我打开了一扇门,让我这个学渣有机会、有动力去做一次尝试。
我在这100天里的收获:
1、学会了放弃,没有人能看完所有的书,即使是你的书架上的书;这100天让我学会了取舍,每本书没有好坏只有当下对你适不适用。所以果断扔掉不合适的,死磕适合你的。(单身狗们你们听到了嘛!!!)
2、认识了很多的共同爱好的朋友,阅读并不是孤芳自赏的事儿,他是一种交流,交喜欢的读书的朋友,总是没有坏处的。
3、养成了好奇心,好奇心是驱使人们探索和发现的原动力,我很高兴我能养成这样的好奇心,让我去不断发现自己的的无知。
4、学会了分享和表达,这种表达有手绘,有写作,有线上训练营分享,有线下的拆书活动;这种分享让我更加珍惜我每一次的阅读时光。分享是最好的学习,是我最大的感悟。
我之所以今天写这篇分享,是因为我觉得它改变了我,而我也希望我的经历能让你有所启发,哪怕只有一点点,也就够啦,么么哒。
我说的不一定对,也不一定适合你,但是不试试,怎么知道呢?
随着业务的复杂性增大、系统吞吐量增长,所有功能统一部署难度加大,各个功能模块相互影响,使系统变的笨重且脆弱;因此需要对业务进行拆分、对系统进行解耦、对系统内部架构升级,来提升系统容量及健壮性。
接下来主要分两部分介绍:系统拆分与结构演变;
系统拆分
系统拆分从资源角度分为:应用拆分和数据库拆分;
从采用的先后顺序可分为:水平扩展、垂直拆分、业务拆分、水平拆分;
图1 系统分解原则
1、水平扩展 水平扩展是最初始的解决的手段,也是系统遇到瓶颈的首选方案,主要从以下两个方面扩展:
应用加实例,搞集群,把系统吞吐量扩上去。
数据库利用主从进行读写分离,数据库其实是系统最应该保护的资源。
2、垂直拆分 垂直拆分才是真正开始拆分系统,主要是从业务功能角度拆分。如拆出用户系统、商品系统、交易系统等。为了解决拆分后各个子系统之间相互依赖调用的问题,这时会引入服务调用治理。系统复杂度有所加大,但系统基本解耦,稳定性相对提高,做好降级就能避免因其它系统功能异常导致系统崩溃。
业务对应的库也会按照对应的业务进行拆分出用户库、商品库、交易库等。
3、业务拆分 业务拆分主要是针对应用层面按功能特点拆分,如交易拆分出:购物车、结算页、订单、秒杀等系统。然后根据业务的特点,针对性做处理,如秒杀系统,由于同时参加秒杀的商品有限,可以提前把商品信息加载到JVM缓存中,自身减少外部调用提高性能,同时商品系统也减轻压力。
数据库拆分也可以分为几步:垂直分表、垂直分库、水平分表、水平分库分表;
垂直分表是指大表拆多张小表,可以根据字段更新或查询频次拆分;
图2 商品表拆分
垂直分库是指按业务拆库,如拆出订单库、商品库、用户库等
水平分表是解决数据量大,把一张表拆成多张表;
水平分库分表是更进一步拆分表;
图3 分库分表
4、水平拆分 服务分层,系统服务积木化,拆分功能与非功能系统,以及业务组合的系统,如最近比较火的大中台或前台拆分;中台为积木组件,承担服务功能输出。前台更多的是组合积木服务,及时响应业务发展,如在电商网站单品页能看见主图、价格、库存、优惠券或推荐等信息,都是组合各积木组件呈现。
数据库也可以进行冷热数据分离;过期或过季商品可以归档,比如诺基亚3210手机,早已经停产且没有销售;用户查看订单时,更多的只是查看最近1、2年信息,2年前数据查看量少,在存储设计时可以区别处理。
结构演变
结构演变主要是随着系统复杂度增加及对性能要求提高而不得不做的系统内部架构升级;
早期系统基本是应用直联数据库,但在系统进行拆分后,功能本系统不能单独完成,需要依赖其它系统,就出现远程调用;
图4 早期应用结构
随着自身系统的业务发展,对性能要求高,而数据库一定程度上成为瓶颈,就会引入缓存及索引,分别解决key-value及复杂检索;索引加缓存现在已经成为解决高并发的基本方案,但在实施过程会有所区别;
14年对3亿热数据的系统升级时,技术选型为solr+redis,考虑到数据量过大,数据在solr中只存index,而结果只存并返回主键id,再通过id从redis中读取数据,redis也不存放全部数据,数据设置过期时间,若未命中redis,回源数据库查询并反写redis;主要考虑资源与性能的平衡,solr的存储减少及IO性能提高,结果数据只在redis存放一份,redis的数据经过运行大部分是热数据;当然现在也流行ES+Hbase组合。
图5 增加缓存及索引
对于频繁使用的数据,从集中缓存读取,不一定达到性能要求,可以考虑把数据入JVM缓存,如类目信息,类目是电商系统基本数据,数据量不多,调用量大;
个别情况下,使用ThreadLocal做线程内缓存也是种有效手段,但需要考虑数据清除及有效性;
在修改商品信息时,业务对商品信息的校验有名称长度、状态、库存及各业务模式等,而为了参数的统一校验方法参数为商品编号,导致各校验方法都需要读取一次商品,使用线程缓存可以解决该问题,性能提高了尽20ms,读取商品每分钟减少近万次;
图6 增加本地缓存
有时所依赖系统性能不太稳定,避免出现因第三方系统影响系统,把依赖的服务进行数据闭环,与Dao一样当成系统的数据源;如商品系统强依赖商家系统的商家信息服务,若商家服务不稳定,商品系统一半服务都不稳定,采取对商家信息缓存一份,降低外部风险,把风险控制在自己手上;
图7 远程服务进化成数据源
用户体验最近越来越重视,系统响应时间性能要求也越来越高,异步化是很好的一种选择:消息中间件;电商下单就是个很好的案例,在用户点击下单时,服务端不直接保存数据,给订单系统发送消息,就直接返回支付页面,在用户支付过程中,订单系统异步进行数据保存;
业务层、数据层的范围越来越宽泛,业务层可以分为基础服务与组合服务;数据层分为数据源与索引缓存;依赖的技术或中间件需要有效的结合,用于解决系统所遇到各种问题。
图8 复杂的结构
最后
系统结构慢慢变复杂,稳定性、健壮性逐渐提高;技术选择都需要结合业务痛点、技术储备以及资源情况,否则就有些不切实际,泛泛而谈;
以上是近几年自己经历的技术变革及升级的总结,后续可以针对个别点进行详细分享。
系统拆分的最后是微服务,结构的演变是技术的升级。
前情提要
平台化架构由于缺乏对于前端业务一以贯之的端到端的支撑能力,平台与平台之间存在gap。平台化架构按照康威定律,必然是几个平台,几个团队,涉及到巨大的沟通成本而导致协作困难。平台化架构在数据化运营上存在短板,往往需要把多个平台的数据集成到一起并加工分析而产生新的支持到业务的价值。
中台思想的提出 马老师参观一个著名的游戏公司Supercell之后提出了中台思想,简言之就是“小前台、大中台”。 Supercell从表象看有200多名员工,一个游戏通常4-5个人研发,截止到2016年3月,Supercell面向全球市场推出了《部落冲突》、《卡通农场》、《海岛奇兵》和《皇室战争》四款游戏。大致可以分析Supercell采用的策略:
必须容忍很多失败:比如一个新项目在测试之前,团队就要设定一个指标,比如玩家留存、参与度,我们把这个目标告诉全公司的人,游戏进入测试之后,如果达不到指标,它就会被取消。
快速尝试:曾经在两年内,他们只发布了一款游戏《皇室战争》,但期间取消了9个项目,和若干很多优秀的创意原型。
招聘足够优秀的人:采用倒三角的模式组织团队,一个游戏公司等于若干创业小团队,小团队可以决定做什么,但没达成目标就必须中止。这决定了团队的每一个人是足够super的cell。
Supercell由于其游戏业务的特点,或与其它业务的研发模式不同。但有一个共同性思考,就是一个良好的中台首要的支持前台业务的快速创新。几个人干1-2个月,业务可以close,不用心疼。但如果百人月的产品,试错成本太高,时间方向也不满足高速变化的市场需要。
类比美军作战模式,就可进一步感受中台的作用。美军在二战时期,以军为单位作战;越战时变成以营为单位作战;中东战争时期进化为7人或11人的极小班排作战。之所以美军的“小前端”如此灵活,因为有强大的中台能力,给前端军队提供各种资源支持以及中台炮火群支持打击。
From:陈华编著《企业IT架构转型之道:阿里巴巴中台战略思想与架构实战》图2-4 战场中的中台阵型
中台思想的解法 我理解,中台不是凭空而来,亦不是平台化架构换个名字。中台化架构是平台化架构的自然演进。一定规模的互联网IT公司都可能有一个叫共享平台或者平台技术这样的部门,就是把业务基础设施和技术基础设施下沉,然后由对应的研发和产品部门去负责。但久而久之,共享平台就成了资源中心,前端业务找你就是要人干活,平台做的也是接客干活。如前文所述,各平台接客模式协同负责度高,周期长。一个商业系统不仅仅是组织几个component,而是需要解决方案。中台提供的能力可以是service、可以是由service组合的组合能力、亦可以是解决方案(solution)的直接输出。
中台是平台的自然演进 前面提个平台化目标是高内聚、低耦合;职责边界清晰;易于集成等。那么中台化架构进一步可总结为:高内聚、低耦合;数据完整性原则;业务可运营原则。当然,从架构方法来讲,宜采用渐进式架构的演进原则。如果一个中台把若干平台聚拢起来,对业务支持的SLA没有变化、也没有在业务运营上有所改变,一定是失败的。
以上图为例,业务在发展过程中,会有若干业务系统。平台化的架构是按项目模式,把公共平台和业务系统的架构师,开发,测试,产品搞在一起协同、排期、研发、上线。中台化架构可以在进一步把平台能力按能力、服务、实体进行管理。把平台划分为系统运行、业务运营2部分。实现80% 甚至更多的业务需求由业务团队自助进入。这反映到前端业务上支持效能提升了,中台的代码基本不用研发,沟通成本也急剧下降。其2,中台的架构师和研发队伍可以把精力放到中台能力提升,从运营视角发掘类似业务全息查询、数据产品这样的创新。
阿里巴巴电商中台的思想 电商中台的负责人玄难提到:我们讲整个淘宝最早是一个系统搞定,后来不行,必须分拆,用分布式架构,后来每个系统又很复杂了。阿里的生态太大了之后,其实每个人进来已经不知道阿里有什么了,所以必须通过中台把我们有什么能力要呈现出来,让业务方根据自己的需要去选择去使用。同时,我们在架构上能让业务方在这些能力上可以自己去定制,组装成自己的业务。当前的问题通过中台的思路去解决,慢慢这个矛盾就会变低,但必然会产生新的矛盾,就需要用新的思想去解决。
同时,电商业务中台,有四件事情肯定要去做:
第一,保证阿里的业务跑得更快,更稳定。比如保障双十一稳定,同时不断提升前台业务的开发效率。
第二,产生创新。有三种形式,一种是我们看到了某个业务模式比较好,我们会把它变成一种基础能力,提供给更多业务方用;第二种是打通业务之间的连接,例如把阿里生态中A业务和B业务连接,提供给客户新的价值;第三种是通过自己的思考形成新的产品能力,就像前面提到的「地动仪」。
第三,根据集团的需要进行新业务孵化。孵化到一定阶段,觉得可以独立发展的,我们分拆出去,就像我们现在重点投入的海外市场。
第四,人才的培养。在中台,我们能看到集团所有的业务,同时也支撑所有的前台业务,相对来说系统性思考,全局性思考会更好一些。所以,我们也会根据需要给前台业务培养和输出人才。
中台不只一种解法 实现中台有不同的方法和实施路径,但可以总结出类似的目标和价值。
赋予业务快速创新和试错能力
打造数字化运营能力
改变组织阵型带来组织效能提升
有人讲,是否是足够庞大的组织才需要中台思想呢?我觉得不尽然。先说庞大的,曾经淘宝使用近百人、几个月的浴血奋战搞了一个统一的CRM平台。但是大部分商家并不买账,最后这个系统下线了。因为淘宝的几百万商家划分不同行业,规模也有很大差异,不可能靠一套系统解决。后面淘宝通过开放生态建设,把消费者服务、商家IT服务、商家运营服务做了分类,依托15万家ISV来搞定几百万家商家不同需求。按我的理解,这就是中台化实践,通过开放赋予不同业务,不同商家业务创新的机会。同时通过一系列运营服务比如数据分析工具促进了商家业务量的变化,是运营赋能业务的体现。
第2个case,来自转瞬之夏《一次交易失败引发的血案和思考》。故事大概是这样的:
任何一个合格的产品负责人,都会具有一个特点,就是喜欢使用、捣腾自己的产品以及同行的产品。就拿今天来说,抱着思考产品特性和探究异常处理逻辑的想法,我故意在收银台签约并支付时,输错3次短信验证码,果不其然,在点击支付时,系统报交易存在风险,交易失败。
一切都和预期一致,于是我想,简单测试完了,估计是触发了风控规则,,找客服帮我解锁吧。但想了想,正好可以验证下这种输错验证码被拦截的常见场景,客服处理起来效率怎么样。
于是我找了在线客服,然后角色扮演一位不小心输错验证码而被锁的可怜顾客,并只提供少量信息。我本以为是很简单就能定位的事,所以也估计客服也能很快就能解决。但实际情况是,客服给了我一个完全错误的解决方案,而且把出现异常的原因认定为我输错了银行预留手机号导致的。
......
作者提出的关注客户,关注客服,关注体验的观点,我很认同。但这个问题具备普遍性,想站在中台的角度多说几句。站在平台化思考问题,对于你的服务对象,可能是割裂的。
平台化的协作可能是这样的,YY一张图:
从上图可以看到,用户可能在做某项业务比如购买的时候到收银台;他没有正确完成签约;触发了风控规则。那么一般而言,如果客服是人肉服务模式的话,通过知识库查询对应问题的答案告知客户,或者找产品,产品找开发,开发找安全团队的开发或产品,进行一系列讨论,最终给出答案。
回顾一下,我前面提出的平台化思路的不足,平台职责明晰,但如果站在用户(商户)服务和支持的视角,就全然不是那么回事了,用户对了解内部职责的划分没有兴趣。他们只想快速,准确的给我解释,告诉我怎么做。
那么,我们把接触客户的部分当作客户服务中台,把共享服务当作共享业务中台,前端Bu业务作为xx业务中台。这些中台之间需要按照服务对象来梳理能力,并互相协同。
如上图所示,业务共享中台和业务中台的业务产品上下线、对客可见的规则变更类需要联动到服务中台的知识库、help页面。而服务中台运营对产品分析,产品改进提出反馈。业务共享中台拥有全部的基础数据和系统链路资产,可以提供用户画像数据、业务全息查询等工具给服务中台。有此可见,多个中台互为消费关系,虽然各中台职责分明,但如何更好的服务业务,服务用户,提升企业运营能力甚至支持产品创新都是最基础的原则。
总结:平台化相比于烟囱型架构基于提升效能,消除重复,职责聚焦的视角而来;而中台化是平台化的自然演进,以进一步通过改变组织阵型提升效能、数据化运营、更好支持业务发展和创新。任何应用架构、组织架构都没有终点,只有“合适”。当非关键冲突变成关键冲突的时候,又是新一代架构要持续升级的时候了!
QA:
Q:银行的组织架构也划分了前台、中台、后台。 前台通常是以客户为中心,和客户直接打交道的岗位;中台一般不直接接触客户,但要负责制定业务策略、风险控制等;后台主要是业务的处理和支持。 前中后台分离后,通过流程银行来支持业务运营,建立呼叫中心、单证中心、集中作业中心等,提高效率降低成本、降低人员要求,让专业的人做专业的事情。 IT架构为适应业务架构,通常也是划分了前台、中台、后台系统。 而基础的技术支撑平台则是共享的,如开发工具、开发框架、运行平台、基础设施(云服务、中间件)、基础组件(认证、安全、通讯、加密、审计等)。 等)。那么这里的中台化是不是就是银行组织结构的中台?
A:
我觉得不尽然。不同平台无论在业务的组织形态还是上下游关系上有”前台”、“中台”、“后台”的分野,但如果是分离提供服务支持业务的模式,那么就没进化到中台模式。中台模式可以构建解决方案能力,快速支持业务,消除平台之间的gap、通过组织变革配套促进创新。传统的前台业务比如to C的客户接触、受理也可以升级中台思维。能不能做全渠道打通?能不能提升用户体验、精准服务用户?等等,跳出柜台受理、网银受理的界限和框框,全渠道全业务,或有更好的用户黏性也未可知。
附录:面向全球化的业务中台
From:http://blog.csdn.net/mini_monster/article/details/51175903
参考材料:
简书:转瞬之夏的日记本(http://www.jianshu.com/p/5207680ea08c) 阿里研究员玄难:如何做电商中台
淘宝和天猫背后,阿里系还有一个不为人知的神秘组织
陈华编著《企业IT架构转型之道:阿里巴巴中台战略思想与架构实战》
云栖社区:平台化三部曲之一微核心可扩展架构 - 从Eclipse平台看交易平台化
背景 软件的核心是其为用户解决领域相关问题的能力。 — Eric Evans 《领域驱动设计—软件核心复杂性应对之道》
交易系统作为电商平台架构的核心系统之一,它为解决什么问题呢?我认为它应最大化满足买卖双方的价值交换,在交易前、后提供完备的服务。
交易前:客户购买下单,支付金额,电商网站确认支付后,进行配送发货。 交易后:在退换周期内,客户申请退换,电商进行退换处理,并返还客户金额;同时对交易金额进行结算。
完整交易需要图中多个核心流程来支撑,其本身的业务链条比较长;它会引入订单、支付单、配送单、售后服务单等多个领域概念;产生营销活动抵扣、支付方式、退换货策略等不同的业务规则;使系统具有较高的复杂性。对于交易系统构建者来说,如何理清关联、避免僵化设计、如何进行科学的构建呢?
交易系统领域设计 现实中的很多软件/应用程序最主要的复杂性并不在技术上,而是来自领域本身、用户的活动或业务。当这种领域复杂性在设计中没有得到解决时,基础技术的构思再好也无济于事。而这种聚焦于领域和领域逻辑复杂性问题,大师 Eric Evans 在《领域驱动设计—软件核心复杂性应对之道》一书中,已经给出了完整的设计范式和原则。
跟随大师的脚步,从领域驱动设计的角度,重新思考整个交易系统的设计。
领域划分 在一个大型系统中,如果因为缺少一种全局性的原则而使人们无法根据元素在模式(这些模式被应用于整个设计)中的角色来解释这些元素,那么开发人员就会陷入“只见树木,不见森林”的境地。 — Eric Evans 《领域驱动设计—软件核心复杂性应对之道》
交易系统包含的内容太过繁杂,很难对系统的全貌直接描述,所以需要对其进行分解,将交易过程中涉及的业务问题划分到不同的业务子域中。如:用户的登录/注册(用户域),网站上展示商品的管理(商品域),商品购买后的支付(支付域)等等。让复杂的交易问题简逐步分解,让子域聚焦本域内的问题解决。
以交易为核心,理清业务子域:
用户域:用户账号登录/注册、用户信息管理等
商户域:企业账户,商户分级,合同及协议管理等
商品域:分类、商品信息、标签、价格管理等
营销域:打折降价活动、卡码券、预售等
订单域:订单生成、订单查询、订单生命周期管理、订单业务对账等
支付域:与第三方资金支付、个人资金账户、资金交易方式的管理等
仓储/配送域:商品的库存管理、配送管理等
售后域:退货、退款、评价管理等
结算域:交易金额,与商户、平台、第三方的结算
业务子域划分过程中,通过领域统一视图来呈现系统的全貌,有利于我们理解各个子域在整体系统中所处的位置以及之间的关系;同时在不需要知道细节的情况下,在大脑中建立系统的概况模型。基于对电商交易侧领域知识的理解,我整理出的领域统一视图。
交易系统领域统一视图:
从交易系统来看:
核心域:订单域、支付域、营销域
支撑域:仓储配送域、售后域、结算域
通用域:用户域、商品域、商户域
教育电商中的商品,是以服务、虚拟商品为主,实物商品为辅,因此弱化仓储/配送(或物流),将它放在支撑域位置上;同时提升售后服务在整个系统中的重要性,将售后服务从订单域中分化出来,独立成业务子域。
子域细化 领域统一视图,明确子域角色与关系之后还是不够的,凭这些还无法进行后续的领域模型设计;我们需要进一步明确子域的核心关注点。在明确核心关注点的同时,我们会通过核心流程的走查,来反向验证子域细化模型是否与核心业务保持一致。
子域细化会从多个维度上进行(以订单子域为例):
领域概念:订单、预售定金单、预售尾款单等
业务规则:商品结算金额与订单支付金额不能小于零,多种订单抵扣使用的顺序,金额在购买商品的分摊比例等
交互场景:结算使用卡券抵扣,提交订单,我的订单查询等
业务流程:用户下单流程,订单取消流程,对账等
订单域细化后的形态:
(其它子域不在逐一叙述)
领域统一视图,描述了系统领域的大型结构;它会随着系统的演进而改变,也会随着子域丰富与深层模型精炼而变动。同时在全局的视野,来指导子域的细化。子域在其细化的过程,是对模型进一步设计提供系统性决策:让强关联的业务模型内聚在同一个子域,让弱关联的模型分离出去。
随着业务的发展,基于交易数据的财务需求逐步增多,交易系统中将会诞生财务子域,内聚多个财务模型;而确认收入计算模型,也会将从订单域中分离出去,汇聚到财务子域中。
订单域核心设计 领域驱动模型设计提供了大量模式,让设计更加自然的实现模型,从而避免或减少反映领域模型而出现的僵化设计。我将通过订单域的核心服务之一:下单服务,来讨论这些模式的使用。
以前台下单场景为例,它是指用户在前台结算页上提交订单信息,并生成订单的过程。主要流程如下:
用户在结算页上提交选购的商品
订单服务接收到用户提交的数据,进行订单模型的填充(用户、商品、订单跟踪信息、结转信息等)
商品促销折扣、卡码券抵扣及营销规则的计算
订单金额计算(商品结算价、卡券抵扣、现金收入、金额分摊等)
订单持久化
订单创建完成后,卡码券使用通知
下单的时序图(已经被简化):
领域模式 在明确下单业务逻辑后,在开发服务前需要明确领域分层与模型(此时可能无法一次性把所需的都细化清楚):
服务的领域分层
承载用户提交订单信息的对象模型,承载持久化订单信息的对象模型,与商品、营销域之间的通信模型
订单域中的有界上下文信息(用于填充用户提交的订单信息,商品信息,金额计算规则,基于订单信息的显示约束等)
领域分层:
明确领域分层,有利于系统在开发阶段时,团队在对领域层次模型保持一致,对模块、服务、仓储给予预先的划分。避免产生违反系统中的分层约定或原则。
对象模型:
有界上下文:
这一阶段是为系统建立完整自封闭的领域模型。领域驱动架构相对比较容易理解,但建立一个完整自封闭的领域模型却很困难。“领域模型”是一个针对业务逻辑抽象的分析模型,它反映出对领域问题的整体描述。领域模型不是编程的实现模型,而是一组抽象概念的集合。
领域需求的最初细节,在功能层面通过领域专家的讨论得出。领域专家并不一定需要熟知软件开发领域的知识,相反强调的是具有领域中的相关知识。领域需求在相互讨论中不断得到细化,还有可能在开发过程出现需求的反复或变更,这都要求领域模型的建立完善是一个反复重构的过程。
设计模式应用 为了保证软件实现的简洁(避免僵化设计),不管实际情况如何复杂,设计与模型都需要保持一致;在面对这种局面时,可以运用建模和设计的最佳实践来解决。如:面向对象设计的23种常用模式,通用职责分配软件模式 GRASP 的 9 种设计原则,还有 Martin Fowler 在《分析模式》中论述的最佳实践 等等。
随着下单服务的细化,会遇到更多复杂的问题,如:
问题一:订单对象的创建,在不同渠道中订单构建的信息也是不一样的;如:用户购买下单、预售订单、后台下单等。
在领域驱动设计中,对复杂对象构建给出了领域工厂模式来解决。领域工厂模式包含:简单工厂、抽象工厂、建造者等设计模式。
订单对象的创建,采用 BUILDER (建造者)模式:
问题二:订单金额的计算;在一个订单中可以购买多个商品,每个商品可以拥有不同的商品折扣,而卡券抵扣可以作用不同商品上,会在 3 个维度上独立变化。
金额计算核心在于商品金额分摊计算,它可以在 3 个维度独自变化,需要分离:
商品折扣、卡券抵扣的基价计算规则
金额计算因子(被分摊的商品集合、单个分摊商品基价、分摊商品总基价、分摊金额)
使用策略模式,让基价计算规则与计算因子分离
使用职责链模式,让不同商品享有不同折扣、抵扣金额计算器形成独自的链条,进行计算商品金额。
模式的应用,让复杂模型的设计,变得更优雅。设计元素非常自然的组合到一起,其结果可以被清晰地刻画出来。让业务逻辑被合理的分散到不同的领域对象中,代码结构更加清晰。
系统突破 持续重构让事物逐步变得有序。代码和模型的每一次精化都让开发人员有了更加清晰的认识。一系列微小的重构会逐渐汇聚深层次模型,最终会给系统带来巨大价值的突破。
交易初期的系统模型:
随着其它业务系统开始关心订单的状态(订单创建、支付、配送),需要将订单状态告知业务系统;但交易系统又不想与它们产生直接依赖,在下单、修改订单、配送订单等节点接入 MQ,交易系统通过MQ把订单状态通知到业务系统。引入 MQ 不久之后,发现交易系统已经具备领域事件的特征,引发了事件驱动的重构。
交易重构后的系统模型:
事件模型的引入,让交易链上系统之间的耦合性进一步降低,为系统各自独立进化带来了更大的空间。
总结 本文是基于我个人对交易系统的理解,讲述了有关领域驱动设计的中大型结构、精炼、常用模型(实体、值对象、服务、分层结构)、设计模式应用、突破等。分享了在领域驱动设计方面的经验及思考,希望能给大家在实际项目中运用领域驱动设计提供一些启发和帮助。
背景介绍 去Windows化:从使用“Windows 系统部署+Net 开发技术+SqlServer 数据库”的技术栈迁移到“Linux 系统部署+Java 开发技术+MySql 数据库”的技术栈。
首先,公司在规模化的发展中,对技术的要求更加偏向于开源平台的技术栈;其次,在业务不断的发展演进中,也需要对现有系统进行一次规模化的重构,来偿还不断前进中忽略的技术债务。
任何一个傻瓜都能写出计算机能理解的程序, 而优秀的程序员却能写出别人能读得懂的程序。 —— Martin Fowler <<重构>>
在此背景下,公共业务部门开展了去Windows化的技术栈迁移改造。公共业务主要涉及业务有订单、商品、促销、清结算,以及一些老的遗留业务等。其中,商品前期已经完成了去Windows化。本次迁移的重点是交易核心部分,共涉及应用系统有13个,影响业务方有5个(如:享互、沪江网校、财务、BI、仓库等)。
由于时间紧,改变了之前先迁移 Java 应用再迁移 MySql 数据库的方案,采取了应用+数据库的同步切换、订单正向交易和订单逆向交易分步部署的方案,难度和复杂度还是相当可观的。
系统梳理 总体业务划分 由于业务不断发展和演进,早期的系统设计不完善之处也逐渐显现出来。如:在之前的设计中订单部分耦合了很多营销的业务,像:卡码券的金额计算、促销规则的应用、卡券列表的查询等,导致订单业务过重,修改频繁;借着此次系统迁移重构,对现有的业务做了域的划分,共分为四大块,如下图所示:
订单域:主要负责订单查询、下单、改单、配送、金额分摊、对账管理等
营销域:主要负责商品管理、卡码券管理、促销管理、规则引擎计算等
结算域:主要负责商户管理、合同管理、计费规则、清分、分销、结算管理等
售后域:主要负责逆向退款退货等
订单域业务系统 下图是梳理出的订单域正逆向的服务及数据库。
红色模块:第一批同步迁移的订单正向服务模块及订单数据库
绿色模块:第二批同步迁移的订单逆向服务模块
蓝色模块:正逆向依赖的服务模块及数据库
其中,订单域最重要的一个服务就是下单服务。分析下前台结算页的下单服务流程。
关键步骤:
校验创建订单参数(金额、学币、卡码券等等)
获取商品、用户、订单跟踪、结转信息等等
加载促销、卡码券信息、计算营销规则
填充订单上下文、金额计算并校验
并发检测(卡码券是否使用)
持久化订单信息
支付通知(0元直接支付)
MQ 发布订单创建完成消息
这里,第3步是比较复杂的一步,在订单域重构改造前,这一步的营销规则计算是放在订单系统里。订单先从营销拿到促销、卡码券、规则信息,再进行各种规则及优先级组合计算。从设计角度来看,订单系统的职责过重、和营销系统耦合较深,系统边界不够清晰。在这次迁移重构中,也将这一块规则计算全部移到营销系统去处理。
还有,第6步持久化信息,创建订单需要持久化多张关联表的信息;这里将以前的长事务处理重构成分步持久化,通过设置订单的最终有效状态,来保证事务的最终一致性。
如下是下单关键流程的时序图:
经过重构后的下单服务通过压测,TPS 达到870左右,这个压测环境比生产环境配置差了很多,还有优化的空间,如下图所示:
系统梳理小结 根据业务域的划分,进行业务的重新整理和规划,梳理出需要迁移的服务接口共计185个,后台任务7个,需要改动的相关前后台站点5个。
系统梳理的目的:
理清系统边界(优化重构、职责分离)
梳理依赖方(数据隔离、老服务切换)
划分重点项目、关键应用、关键模块(重点模块单元测试全覆盖)
合理安排开发、测试资源(重点项目优先)
为什么要分批部署?
首先,还是资源紧张,大量的开发任务集中在订单正向。 其次,订单逆向的业务流程相对滞后,可以通过数据同步,在之前的老的系统上继续运行订单逆向业务,待逆向部分开发完成,再次部署上线。
如何数据同步? 本次数据迁移及上线回滚方案主要用到阿里巴巴的跨数据源异构数据同步的开源中间件(DataX、yugong)。 DataX:我们主要用做数据的全量同步。 yugong:我们主要用做数据的增量同步。 在使用中,DataX 的配置比较简单,可以自定义读写插件,但不支持数据的增量同步;yugong 的配置稍复杂点,它对源数据、目标数据的字段名、类型字段个数都有强校验,但好处是支持全量增量数据同步、数据校验等。 具体的数据同步方案,后面的迁移实战会有介绍。
迁移实战 1、数据隔离、服务化 去Windows化,首先要进行的就是数据隔离,杜绝外围业务系统对订单数据库的直接访问,统一改成服务化调用。之前的很多业务,如订单对账就是通过Kettle直接从订单库和支付库进行数据抽取来做对账的,这样如果要做数据库迁移时,这些逻辑都要重做。所以,在应用迁移改造前,就需要在现有的 .NET 应用里提供代替外部直接访问数据库的 API 接口,并请业务方配合完成新的 API 接口切换。
2、通用代码的快速迁移 a、Python 脚本批量生成代码实体类 首先明确的是,我们的服务接口契约是和 .NET 服务接口契约完全一致的,那么接口的传入、传出参数肯定是不会变的。 所以,我们做了一个 Python 的脚本,来对 .NET 的代码实体类批量转换到 Java 的代码实体类。
代码片段:
def processConvertJavaByDir ( paths , namespace ):
list
= os . listdir ( paths )
for i , file in enumerate ( list ):
subpath
= os . path . join ( paths , file )
if os . path . isdir ( subpath )== True :
processConvertJavaByDir
( subpath , namespace )
elif os . path . isfile ( subpath )
and subpath . endswith ( ".cs" ):
targetContent
= genTargetContent ( subpath , namespace )
targetFile
= os . path . join ( paths , file . replace ( ".cs" ,
".java" ). replace ( "Entity" , "Bo" ))
if os . path . isfile ( targetFile ):
os
. remove ( targetFile )
open
'utf-8' ). write ( targetContent )
def main ():
try :
sourceStr
= input ( "输入源文件目录: \n" ). encode ();
namespaceStr
= input ( "输入命名空间: \n" ). encode ();
while
( not os . path . isdir ( sourceStr . decode ())):
sourceStr
= input ( "请输入有效的源文件目录: \n" ). encode ();
namespace
= namespaceStr . decode ()
sourceDir
= sourceStr . decode ()
targetDir
= sourceDir
processConvertJavaByDir
( targetDir , namespace )
print ( "成功执行转换,生成目录:"
+ targetDir )
except
Exception
as err :
print ( err )
finally :
print ( "结束..." )
b、表结构变动批量替换PO实体、DAO方法、DAO映射XML 订单库在从 SqlServer 重构迁移到 MySQL 中,由于设计调整,经常会有字段名变更、添加字段、变更字段类型等等。虽然有 MybatisGenerator 自动生成代码,但去找到对应代码的位置,一个个文件去替换也是比较繁杂的,如果自定义的代码和生成代码混合在一起就更不好替换了。这里主要通过改造 MybatisGenerator ,在生成代码时,前后加上特定标记,然后通过 Python 脚本去批量替换标记内的代码。
@Data
true )
public
class
OrderConfigPo
extends
BasePo
{
/
/**
* 主键
*/
private
Long id ;
/**
* 配置的key
*/
private
String key ;
/
public
ShopConfigBaseBo toShopConfigBase (){
return
this . toShopConfigBase ( this );
}
}
PO实体、DAO方法通过/
DAO映射XML是通过来标记生成的代码。 按照约定自定义的代码是放在这个标记之外。
3、保证服务的完整性 本次去Windows化的一个前提就是,无论我们怎么迁移改造、业务方不需要做任何改动,做到无感知的过渡。那么,我们迁移改造的服务就要向前完全兼容。
做到兼容的原则:
接口传入、传出参数一致性(名称相同、类型相同、默认值相同)
写入数据时,表字段类型、值、默认值相同
这里默认值是比较大的一个坑,在之前 .NET 应用代码里,我们使用的基本类型都是值类型;而在 Java 应用里,我们约定了基本类型都定义为引用类型,如下所示:
//c#代码
public
int
ProductType
{
get ;
set ;
}
public
bool
IsBill
{
get ;
set ;
}
//java代码
private
Integer productType ;
private
Boolean isBill ;
那像 C# 代码IsBill字段,默认值是false(这种设计就分不清到底是业务上的false还是默认值),而 Java 代码isBill字段,默认值是null。
Java 应用会将isBill序列化为null,业务方 .NET 应用在反序列化时会报错,这个问题在商品去Windows化时遇到过。还有,业务方如果用了引用类型的基本类型,对反序列化后的isBill的判断也会有问题,因为之前IsBill始终会有值的。 所以,我们在序列化时,如果是null,就不序列化该字段;如果之前 .NET 应用有默认值, Java 应用里也设置默认值。
只保证接口的传入、传出参数的一致性还不够;还要对关联表字段进行检查,确保和之前的表字段值一致。在商品去Windows化上线一个月了,业务方发现有个业务属性不正确,后来排查发现,在迁移改造中,这个属性字段在保存时遗漏了。
上述,只是在开发中需要遵循的规则,要做到完全兼容,还需要测试人员进行自动化的测试。
测试要点:
同时在 .NET 应用、Java 应用调用服务,通过 Python 脚本,来比对接口的传入、传出参数是否一致
通过 Python 脚本,抓取 SqlServer 表、MySql 表,来比对关联字段值是否一致
4、迁移过程中遇到的问题 4.1、基础知识问题 a、典型的字符串“==”问题,由于 .NET 开发人员的习惯,这个也是问题的高发点,所以建议使用org.apache.commons.lang3包下的 StringUtils 类去做字符串的判断操作。(其他如日期、数值都建议使用该包下的类去操作)
b、BigDecimal 的比较大小,使用 compareTo,而不是 equals
c、BigDecimal 构造数值型,使用new BigDecimal(“0.9”),而不是 new BigDecimal(0.9)
d、自动拆箱的空引用问题。 自动拆箱赋值时,编译期无错误,运行时也不一定有错误(如果为 null 就会抛错);一定要注意判空,同时也建议基础类型都用引用类型。
//如:将一个 Integer tempOrderVersion 属性拆箱赋值给 int tempOrderVersion 的属性
response . setTempOrderVersion ( po . getTempOrderVersion ());
e、不建议使用 BeanUtils.copyProperties 进行属性的赋值。 主要是属性个数、属性类型、属性名称的不匹配,而没有成功赋值;但编译期又发现不了,很难排查,很容易带到线上。像之前有个属性就因为名称大小写不一致而导致始终查不到值。如下这个就一个字母 N 大小写不一致,排查只能靠眼力了。
//源字段
private
String originalPaynumber ;
//目标字段
private
String originalPayNumber ;
f、对一个公用数据进行临时修改时,没有克隆副本,导致多线程读取数据错误。这个问题虽然很简单,但当时做优惠券压测时确实查了很久,而且由于业务数据的原因不是每次都必现。
g、并发执行对 MySql 同一个数据表的批量修改、删除操作时;如果条件为非主键,非唯一索引,容易引起死锁问题。
4.2、RabbitMQ、Codis 反序列化问题 这里主要是框架使用 RabbitMQ、Codis 传值时,使用了 FastJson 的数据格式。
框架为了嵌套反序列化时,类型不丢失,使用了 type 来描述对象类型。如下图所示:
{ "@type" : "com.company.ecm.model.Person" , "age" : 18 , "name" : "李明" }
但我们的应用为了兼容 .NET 的应用,并没有使用一致的对象类型来接收这种 json 串,使用这种格式反序列化就会报错。
故在项目里重新定义了 json 的序列化参数,移除了 SerializerFeature.WriteClassName 类型的配置:
//SerializerFeature[] serializerFeautres = new SerializerFeature[]{SerializerFeature.WriteClassName,SerializerFeature.DisableCircularReferenceDetect}
new
SerializerFeature []{ SerializerFeature . DisableCircularReferenceDetect }
4.3、依赖服务 a、返回数据格式
先看下调用的依赖服务接口的返回数据格式,大部分的都是遵循标准规范的返回格式,但还是有些老的服务使用了非标准的返回格式,如下所示:
//返回数据
//标准规范格式
{
//data为基本对象类型、不能是泛型、数组、基础类型等
"data" :
null ,
"message" : null ,
"status" : 0
}
//非标准规范格式
{
"data" :
null ,
"faults" :
[],
"type" :
1
}
//非标准规范格式
{
"data" :
null ,
"status" :
false ,
"error" :
null
}
b、请求响应数据类型
接口的请求响应数据类型一般都为 json 格式的,但也有一些其他的请求响应类型,如:
//标准格式
@POST
@Path ( "/test/apply" )
@Produces ( MediaType . APPLICATION_JSON )
@Consumes ( MediaType . APPLICATION_JSON )
CustomerJsonReaderPlugin . class )
Response getResponse ( TestRequest request );
//非标准格式
@POST
@Path ( "/test/{userId}" )
@Produces ( MediaType . TEXT_HTML )
@Consumes ( MediaType . APPLICATION_FORM_URLENCODED )
HtmlJsonReaderPlugin . class )
Response getResponse ( @PathParam ( "userId" )
Long userId ,
@RequestBody
String body );
解决方法:
通过自定义实现MessageBodyReader、ReaderPlugin 来处理非标准的数据格式。
c、接口请求数据的验签规则
这一块,依赖服务的验签规则都不太统一,针对各种验签规则,都需要分别处理下。
4.4、MySql、SqlServer 日期时间存储问题 SqlServer 的 datetime 字段类型,是可以存储毫秒的;而 MySql 的 datetime 字段类型,默认只能精确到秒。 我们业务中没有用到毫秒,所以精确到秒是没有问题的,但是在存储时,如果带了毫秒的数据会发生进位。 如:
'2017-09-17 16:41:10.500'
100006014 ;
如果存储的毫秒大于等于500毫秒,MySql 会自动进位到秒,上述实际存储值是:2017-09-17 16:41:11这样在创建订单时,服务返回的时间和数据库存储的时间会有 1s 的误差。
解决方法: 实现 MyBatis 的 Interceptor 接口,拦截需要存储的时间类型,将毫秒置为 0。
4.5、服务 URL 大小写问题 在之前 .NET 应用里,服务 URL 使用上,是不区分大小写的。现在使用的 RESTEasy 默认是区分大小写的,这样就导致服务被调用时,URL 会找不到的情况发生(这个也是在测试时遇到的比较多的问题)。
解决方法: 应用通过实现 ContainerRequestFilter ContainerResponseFilter,构造自定义的 filter 将请求的 URL 统一转为小写的 URL 来处理。
4.6、yugong 数据同步的问题 场景是需要从 MySql 增量、全量同步数据到 SqlServer;SqlServer 表存在复合主键、而 MySql 表都有主键 ID,SqlServer 表字段不允许为null,MySql 表字段允许为 null 等等。
官方的 yugong 是不支持 SqlServer 的同步以及复合主键同步的,这里架构的同事们帮忙实现了这部分功能、并添加了一些自定义的转换,而且支持 YAML 的配置方式。
在使用过程中,为了业务需要还添加了几个功能点:
a、配置节添加defaultColumns的配置项,主要是当 SqlServer 不允许为 null 时,如果 MySql 为 null 则设置一个默认值;如果 MySql 为某个特定值,则设置 SqlServer 的替换值。
ColumnTranslator 类,添加对defaultColumns配置项的处理。
//如果设置了defaultColumns,则根据配置的默认值来替换原来的null值,如果设置了原值,则根据匹配的原值替换
for
( Map . Entry < String ,
Map < String ,
Object
entry : defaultColumns . entrySet ())
{
record . getColumnByName ( entry . getKey ());
entry . getValue (). get ( "beforeValue" );
if ( beforeValue != null ){
if ( column . getValue (). equals ( beforeValue )){
column
. setValue ( entry . getValue (). get ( "value" ));
}
} else
if ( column . getValue ()== null ){
column
. setValue ( entry . getValue (). get ( "value" ));
}
}
b、如果使用联合主键同步到 SqlServer 时,目前更新的 position_data 的 id 值为第一个联合主键的值,而不是 MySql 的主键 id,会导致增量同步数据出问题(原因:增量数据是从 position_data 的 id 值起始同步) 解决方法:
Record 类添加 sourcePkeys 来存储原始的 MySql 主键信息
//当目标库复合主键时,原库的主键信息
private
List < ColumnValue
sourcePkeys ;
AbstractFullRecordExtractor 类的 ack 方法修改存储 position_data 的 id 值为 sourcePkeys 的主键 ID。
c、更新 position_data 的 id 值时,记录当前的更新时间(为了方便计算和修改增量同步 SQL 语句的更新时间)
这些更新点,也推给了架构组维护的 gitlab 项目;这个修改版本,架构部也在筹备推给开源社区 github。
具体的使用如下,这个是表字段配置的一个代码段:
appiler :
{ a : todo , b : todo }
databases :
source :
{ schema :
null }
target :
{ schema :
null }
extractor :
{ a : todo , b : todo }
table :
{ a : todo , b : todo }
translators :
record :
order_deal_memo
:
-
class : com . taobao . yugong . translator . NameStyleDataTranslator
properties
:
{ table_to :
ShopOrderDealMemo }
-
class : com . taobao . yugong . translator . ColumnFixDataTranslator
properties
:
column_alias
:
Id :
[ ID ]
OrderId :
[ OrderID ]
exclude_columns
:
[ DealUserCompanyId ,
Timestamp ]
defaultColumns
:
DealUser :
{ value :
0 }
IsChild :
{ beforeValue :
0 , value :
null }
我们要迁移的有 24 张表,这里每张表做了一个 YAML 配置文件便于维护,然后通过 Python 脚本合并成最终的配置文件。并将数据初始化、数据同步、增量配置、数据验证等写到 Shell 脚本里,方便执行。
最终的运行目录如下:
定时或手动运行 run_yugong_import.sh
修改 positioner_data 目录对应表的配置文件里的 id 值(如果是需要同步修改数据,这个 id 值是固定的)
根据上次同步完成写入 position_data 的更新时间,修改 target_order_mysql-mssql-check.properties、target_order_mysql-mssql-full-sync.properties文件里 SQL 语句的更新时间
执行 yugong-shaded.jar,先做数据的同步,产生结果到 yg_app_import;再次执行 yugong-shaded.jar,做数据的验证,产生结果到 yg_app_check
将执行后的 yg_app_import 目录下的 positioner_data 文件夹覆盖到主目录下的 positioner_data 文件夹
将同步、验证后的日志按照时间戳备份到 logs 文件夹
输出数据验证的结果,是否有错误产生等
run_only_check.sh:主要用作数据验证,不需要数据同步。像通过 DataX 全量数据同步后,可运行这个脚本来对比验证数据的一致性,并有格式良好的对比日志。
checkResult.py:主要用作数据验证后的日志分析,查看是否有验证错误产生。
5、验证环境测试方案 5.1、API GateWay 切换 验证环境需要做好两份 API GateWay 的转发配置,一套指向 .NET 应用的配置,一套指向 Java 应用的配置。方便在验证环境测试时,可以做到一键切换。
还有订单逆向的 .NET 应用,因为要配合订单正向的迁移,是做了一些改动的。这部分服务只能通过重新发布来切换了。
5.2、验证、产线库数据同步 我们验证环境和产线环境共用一套数据库,当验证环境运行 MySql 数据库做测试的话,产线 SqlServer 数据库也在产生数据。同时产生数据的话,就要做数据的同步而且同时产生的订单ID 不能重复,否则就会产生后续的订单对账、清结算、分销等流程就无法做了。这里说的是测试,其实数据都是真实的,是需要真实支付和后续的报表输出的。
这里以订单表的数据同步方案为例,其他表也是一样: 假设当前 SqlServer 订单表的订单 ID 最大自增值为:10000,也即数据范围:1-10000 a、首先将 SqlServer 表的订单 ID 的起始自增值修改为 12001(这里具体修改多大的自增值取决于预计会在验证环境产生多少测试数据)。
b、通过 DataX,将 SqlServer 订单表里小于 12001 的数据记录全量同步到 MySql 数据库。
c、MySql 订单表新产生的订单 ID 范围也即:10001-12000,SqlServer 订单表新产生的订单 ID 起始值:12001
d、每天定时通过 yugong 将 MySql 新产生的订单数据增量同步到 SqlServer 的订单表,预留的订单 ID 范围:10001-12000(验证环境的数据也是真实数据,也要同步到产线的 SqlServer 库)
这里 b 步骤全量同步后,其实产线订单数据会有取消订单存在,也即会有之前的数据发生变更;这里的做法是在上线前一个时间点将这些变更数据同步更新到 MySql 数据表里,像订单取消最大值是三天,也即全量同步后三天,就可以将订单取消的数据同步更新到 MySql 的数据表里。
具体方案示意图如下:
6、上线部署方案 在上线部署时,我们有一个小时数据库停机来完成数据同步的操作。 沿用上述验证环境测试方案的例子,在 MySql 和 SqlServer 都停止服务后,操作如下:
a、将 SqlServer 订单表订单 ID 值大于等于12001的数据通过 DataX 全量同步到 MySql 订单表。
b、假设同步到 MySql 最大的订单 ID 值为12800,则修改MySql起始自增值 13000。(因为在数据库停服的过程中,可能会有一些阻塞的订单,这里自增值调的大点,方便后续的数据补偿。)
c、启动 MySql 服务,API GateWay 切换到 Java 应用,启动 SqlServer 服务,然后开启 yugong 程序,定时增量同步从 MySql 到 SqlServer 的订单数据。
这里 c 步骤还要继续定时增量同步从 MySql 到 SqlServer 的订单数据,主要是因为这一阶段订单逆向部分还没有上线,需要订单正向 Java 应用和订单逆向 .NET 应用同时并行,等到逆向部分上线后,就可以彻底关停 SqlServer 数 据库了。
上线部署示意图如下:
7、上线回滚方案 一旦上线后,验证出现重大问题,就必须考虑回滚方案。具体操作如下:
a、停止 MySql 数据库的访问,禁止写入。 b、手动运行 yugong,增量同步数据到 SqlServer 库。 c、修改 SqlServer 订单表的起始自增值,需要大于当前最大值。(可以调的稍大一点,防止停库过程中,阻塞的数据需要后续补偿) d、启用 SqlServer 数据库写入权限,API GateWay 切换到 .NET 应用。
迁移总结 以上主要是对这次 去Windows化 过程中,遇到的一些问题进行分析和总结。 这次公共业务 去Windows化 的技术栈迁移改造还是非常成功的,各业务条线共同通宵验证,协同作战,原计划 6.5 个小时的停机部署上线方案,最终 2.5 个小时就完全成功部署上线,无重大 bug,无回滚操作。
上线成功的那一刻,是非常激动人心的,让我们觉得共耗时近6个月,在不中断业务项目的基础上,加班加点完成的工作终于收到了回报。
非常感谢小伙伴们辛苦的付出,也感谢各业务方的配合联调
前言
京麦是服务于京东数十万商家的开放式工作平台,由京东官方和ISV(第三方服务商)通过京麦为商家提供多样的应用服务。
京麦交易平台简介
京麦交易平台是整个京麦的核心系统之一,通过整合京东内部的各个系统的能力来打造一个功能丰富、稳定可靠的通用虚拟服务交易系统,我们首先来看一下整个京麦交易平台的生存环境:
京麦交易平台是一个通用的交易系统,具备对外开放的能力,所以很多京东内部的系统都是通过京麦交易平台来实现他们的交易能力,而京麦交易平台则通过整合京东内部各个子业务系统的服务能力来提供这样一个丰富的交易能力。
可以看到,整个京麦交易平台的生存环境是非常复杂的,并且业务增长飞快、变动频繁,下面将通过几个例子来介绍整个京麦交易平台是如何在一个如此复杂的环境中稳定的运行,同时要具备良好的业务扩展能力。
交易流程的高性能设计
对于一个交易系统来说最重要的当然就是交易流程,上面我们看到整个京麦交易平台依赖了非常多的外部系统,这对于交易流程的性能是一个非常严峻的考研,频繁与外部系统进行交互必然会产生大量的网络耗时导致性能的降低,并且我们也无法保证我们依赖的每一个系统都能为我们提供高性能、高可用的服务,下图是我们整个交易流程设计的核心思想:
整个设计的核心思想就是缓存和异步化,我们会对商品、用户等热点数据进行多级缓存,减少RPC接口的调用和数据库的访问,同时我们会梳理出整个交易流程最核心的一条线,除了这条线以外的分支逻辑通通异步化,这也是系统异步化设计的一个理念,在图中有一个异步设计的例子,在生成订单的时候其实仅仅是生成一个订单id,而真正订单写库落库的操作是异步完成的,这样可以减少访问数据库的耗时。缓存+异步化的设计可以保证我们核心交易流程的高性能。
履约流程的高扩展性设计
用户在京麦交易平台完成一次交易之后,我们要对用户的本次交易进行履约,例如我们要记录本次用户订购的服务让用户能够使用他购买的服务、要发消息给用户和ISV、要推送本次交易的结算信息到结算系统等等,整个履约流程的步骤非常多,并且会频繁的增加,所以我们要保证每个步骤之间不能相互影响,要保证整个流程良好的健壮性,并且需要高扩展性来应对业务的增加。
这样图阐述了我们整个履约流程的设计理念 — 观察者模式,履约流程的每个步骤都抽象成一个观察者,它们同时监听同一个订单消息主题,各自完全没有影响,并且具有非常好的扩展性,后续针对业务的扩展我们只需要增加观察者即可,不会对原履约流程有任何的入侵,这样也可以保证整个流程的高容错性。
相关内容已经整理到博客中,详见 观察者模式在交易系统中的应用
复杂业务的智慧处理 — 引导路由
用户在京麦交易进行一次交易的时候,我们需要为用户的这次交易选出合适的支付方式,这个流程在业界有一个通用的概念,就是引导路由,而影响一次支付方式选择的因素有很多,例如每个接入系统、订单类型、用户账号类型、交易限额、商品信息等等,这些因素都会有不同的支付方式的支持,这些因素相互作用在一起选择出本次交易到底支持哪些支付方式,这个逻辑非常复杂,而且未来会有更多的因素影响支付方式的选择。如何简化引导路由逻辑复杂度的同时又要保证它的高扩展性,成为我们的一个难题。
这张图就是我们解决这个难题的答案,首先我们会对影响因素中灵活异变的因素做灵活的配置,采用zookeeper订阅、通知的能力来完成配置下发的功能,其次我们会把每一个影响因素抽象成支付方式这一主题的装饰者,用装饰者模式来将这些因素相互作用在一起,这样的设计可以应对业务逻辑频繁的变动,并且可以保证良好的扩展性,整个引导路由的逻辑复杂度也会降低。
发票模块的高扩展性设计
发票模块是京麦交易平台的核心模块,依托于京东发票系统实现,京东发票的出口有很多,例如有通用发票、融聚发票,针对发票类型还会有增票、普票等不同发票类型的出口,未来我们还会扩展泰国和印尼的业务,也同样是不同的发票出口,当用户开具发票时,我们要在众多发票出口中选择出唯一的一个出口来完成发票申请单的提交,同样有很多的影响因素,例如接入系统、账号类型、订单类型、商品、发票类型等等,并且出口还在不断的增加,针对这种场景,我们决定采用责任链模式来解决,如下图:
首先构建发票申请的责任链,每个出口都会成为这条链的一个节点,并定义好它们的顺序,当一条有发票申请的订单消息过来之后,会顺序的通过整条链的每个节点,当链中的某个节点可以匹配本次发票申请的条件时,就会被选出做发票申请单的提交,否则会交给下一个节点来做处理,这样以后扩展发票出口时,我们只需在链上增加节点即可,扩展性非常好。
相关内容已经整理到博客中,详见 责任链模式在交易系统中的应用
对数据的妥善处理
随着业务量的增长,系统的数据也在不断的增加,达到一定量级之后,就需要对单表进行水平拆分来保证数据库操作的性能,也就是我们常说的分库分表,拿我们的订单表来举例,通常这样的表我们会用订单id来作为水平拆分依据的key,这样可以保证数据的均匀分布,但拆分之后会带来另外一个问题,有些查询操作会跨库,例如我们查询某个用户的订单就需要查询所有的分库,无论你设计的跨库查询算法有多么牛,都无法避免跨库查询需要与多个库进行交互的天然劣势,所以这个时候我们一般会以用户id作为分区的key异构出用户维度的库,但我们如何保证多个维度的库的数据一致性呢,有人可能想到我们可以双写或者多写各个维度的库,这个方案有几个缺点,首先,增加一个维度之后对原流程入侵很大,我们需要在原流程上进行修改增加对新增维度库的写操作,导致原流程性能降低并且容易出错,其次,如果你的系统做的不够原子不够内聚,对订单的写操作可能分布在各个系统各个模块中,需要梳理出对订单的所有写操作,也很容易遗漏,一旦出现遗漏就会造成数据的不一致,针对以上问题我们决定在数据库底层来完成数据异构,如下图:
基于MySQL主从复制原理,对binlog进行增量订阅,这里我们采用京东自研的中间件BinLake,它会伪装成一个从库,与目标库完成主从同步这个过程,拉取到增量的binlog之后会加工处理并通过MQ发送给我们的应用程序,我们会在应用程序中将消息写入到其他维度的库中,这样可以保证数据一致性的同时还不会对原来的流程有任何的入侵。
对系统“无微不至”的监控
我们依托于京东内部的ump监控系统来完成对系统性能、可用率、健康状况等等多维度的监控,并采用AOP原理将监控切入到我们系统每个方法中,保证任何环节出问题我们都能及时的收到报警,并且会自动保留案发现场完整的线索,这样“无微不至”的监控可以让我们及时的发现系统任何的问题从而做出及时妥善的处理。
结语
整个京麦交易平台发展演进过程中遇到很多难点和痛点,它的生存环境很复杂,我们在解决难点和痛点的同时要保证系统稳定性,并且要对业务的频繁变动和扩展做很好的支持,这里用“化繁为简”这个词来为本篇文章做一个总结并画上一个句号。
前言
信息化时代,搜索引擎辅助搜索已成为生活中不可分割的一个组成部分。有了搜索引擎,就不必劳心费力的考虑在哪里找到某个信息,自己想要的资料,只要一搜索,马上就会出现。我们可能会觉得使用搜索引擎是再简单不过的事情。不过,搜索实际上是一种技能,掌握越多的技巧,在除去次要信息、抓住关键问题方面做的就越好。有助于最快的速度找到自己需要的信息。
Google 搜索和大多数搜索引擎使用的是一种称为 “爬虫” 的技术,爬虫程序会频繁地访问各个网络站点,读取并捕获网页上的大部分文本信息,同时也会跟踪进入该网站的其他页面。爬虫程序在一个网站上搜集到的信息会添加到搜索引擎的网页索引中。当我们搜索的时候,搜索引擎会快速分析网页索引中的相关信息,以便找出匹配内容。搜索结果按照相关性排序,相关程度最高的网页排在最前面(主要指搜索的自然结果,而非付费结果)。搜索结果的相关性和排序有着极其复杂的算法,搜索引擎使用很多不同的相关因子来决定查询要求相关性最高的结果是什么。Google 使用的相关因子有近 300 个。复杂的算法不在本文讨论范围内,本文以 Google 搜索引擎为主,介绍一些实用技巧,帮助大家快速找到期望的资料,提升效率。
举个例子
首先举一个例子,我们在搜索引擎中检索关键字 “苹果”,检索结果 7900 万条。无论是在 Google 还是 Baidu,绝大部分结果都是和苹果公司有关的。但我们可能想要的结果只是 “水果”。
事实上,我们可以给搜索的关键字限制很多条件或分类,将检索结果控制在我们期望的范围内。例如我们将搜索关键字由 “苹果” 换为“苹果 ~ 水果”
可以看到,检索结果 200 万条,比起 7900 万条,少了很多。而且排在前面的检索结果完全是我们期望的。
波浪号是一个通配符,意思是告诉搜索引擎去检索与水果有关的页面。利用 “~” 给检索关键字限定分类或形容词的方法同样也可以用在 baidu 或 taobao 中。
下面会针对多种技巧举例说明,大家可以根据实际情况举一反三。特别注意,本文出现在检索中用的标点符号,特殊字符,均为英文格式符号,中文格式的符号无效。
1
排除不想要的结果
例如搜索 “运动相机”,但只想看 GoPro 品牌以外的产品 运动相机 -GoPro 减号 “-“可以排除我们不想要的结果 排除特定条件的格式是,关键字 - 排查条件
2
搜索特定网站的内容
例如我们想搜索清华大学发表的和关键字 “load balance“有关的论文 load balance site:lib.tsinghua.edu.cn site: 网址,可以搜索特定网站的内容。 搜索特定网站的内容格式是,关键字 site: 网址
3
搜索特定类型的文件
例如搜索 “人类简史”pdf 格式的电子书 人类简史 filetype:pdf filetype 可以搜索特定类型的文件,指定文件的扩展名,有助于我们快速找到想要的结果 采用这种方法可以轻松找到 word,ppt,excel 文件,甚至指定 mkv,mp4,用来快速找到想要的视频 搜索特定类型文件的格式是,关键字 filetype: 扩展名
4
限制搜索的关键字出现在网页标题中
例如搜索 “反向代理 “,期望结果是网页标题中包含关键字 intitle: 反向代理 类似的方法,可以用 intext: 关键字 搜索关键字出现在网页内容中的结果,还可以使用 allintitle 或 allintext 指定多个关键字。 例如检索网页标题中包含华为和小米 2 个关键字的页面 allintitle: 小米 华为
5
双引号的用处
搜索引擎为了提升检索速度,会将包括 “and”、“the,”、“where”、“how”、“what”、“or”,或一些单独的字母,数字忽略掉。如果想让搜索结果包含这些被忽略的部分,需要将关键字字符串放到双引号内 例如:"how to write a code" 如果没有引号,搜索的大部分结果是以 write code 为关键字。包含引号后,会确保将完整的字符串做为期望的检索结果提交给搜索引擎。
总结
类似的技巧还有很多,大家有兴趣可以自行扩展,本文介绍的是几个非常常用且核心的技巧,熟练掌握可以帮助我们除去次要信息,快速找到期望的信息,节省时间和精力。
2016 ATF阿里技术论坛于4月15日在清华大学举办,主旨是阐述阿里对世界创新做出的贡献。会上阿里业务平台事业部&淘宝基础平台技术部负责人玄难阐释了淘宝经历13年的发展中,业务平台从零到有,同时又逐步演进为业务中台。而中台的概念是阿里巴巴在2015年12月提出,并且按照“大中台、小前台”理念进行组织升级,旨在建设“敏捷的前端+强大的中台”,降低整个集团的创新成本,适应新时期的发展。
下面是玄难演讲内容
b2402032161a36ef32a6fe72db98002c58bd28ec
玄难:大家好,我叫玄难。接下来主要讲一下淘宝13年发展中,每个阶段的业务状况、技术挑战和技术体系的应对策略。
065bf2057c82ed2118059884207e45682511848c
1a5da4fb188999e6b3e54271c914091ec2e43f8c
在复杂的电商生态中,我们有4亿多的消费者,这是我们最大的资产。我们要服务好这几亿消费者,只是阿里巴巴是不可能的,现在有1700多万商家,在全国有200多万快递人员。有上万家软件服务商为我们的商家提供企业支撑软件。随着电商生态的发展,有更多的生态角色不断出现,例如大家都知道的网红。这些是外部生态。内部也从淘宝,一步步的发展壮大,现在有几十个事业部,相关的技术人员有一万多人。支撑了7000多个应用,上千种现存的业务,每天几百个业务需求,500多个独立的变更。面对这样一个复杂的体系,我们如何应对它呢?这就是我重点要讲的业务平台的发展历史。
ae31cd3064072d944b3c44a7404cdb53346a0ed2
在电商技术体系里面,我们会看到中间有一层非常关键的业务平台。淘宝网、天猫、聚划算,以及1688等耳闻目详的零售市场,都是基于业务平台建立的。很多业务也是通过业务平台进行串联的。在业务平台中包括了会员、商品、交易、营销、资金结算、店铺、评价等等电商的基础平台。4亿多会员、10多亿的商品,双11一天900多亿的成交都是在业务平台中进行管理完成的。业务平台是整个电商业务生态的基础。这是非常复杂的业务抽象和业务逻辑控制层。这一层的能力,直接决定了业务发展的速度和业务创新的成本。
e7534d87d48b5db2cc13eb87ca400eff261d1428
电商系统的发展经历了四个阶段。第一个就是淘宝生长期,单一业务系统阶段。第二个是淘宝集市+淘宝商城时期,分布式业务系统阶段。第三个是三淘(淘宝、天猫、一淘)时期、业务平台化阶段。第四个就是目前的垂直化事业部、业务中台化阶段。
9e1f1ad882f885c73cc63c33f72323a38f4c3a18
第一个阶段是淘宝早期的建立,业务简单,一个业务系统、几台机器就支撑了。刚开始系统不稳定,每天晚上都需要把系统重新启动一下。这是2003年的界面,我们还是保留了历史的痕迹。 这个阶段也分两个部分,最早是花50万买的别人的一个PHP的系统。随着业务的发展,逐步改造成了Java技术体系。系统名字叫Denali。随着电商的迅猛发展,业务品类的快速增长以及淘宝商城起来之后,技术人员的增加,这个系统搞不定了,效率和稳定性都变差了。
f9a3011f6f0000c519d5e32bf21398312f336935
到2007年团队已经有了上千人,这个时候我们的项目支撑面临巨大的挑战,系统架构必须升级进化。这就到了第二个分布式业务系统阶段。我们开始做分布式战略,把原来的单一系统拆分成多个高内聚,低耦合的中心化系统。现在耳闻目详的用户中心,商品中心,交易中心,店铺中心,就是这个阶段出现的。同时也意味着把上千人的团队拆分成了业务相对比较集中的小团队。每个独立的系统可以独立设计、独立接需求、独立发布,整个研发效率和系统稳定性都上了一个台阶。后面小邪要讲的中间件体系也都是在这个阶段发展起来的。
be435367b8fce221664bb703411caed4a4f68a48
电商发展的速度实在太快了,到2011年随着各种B2C网站,各种导购网站的出现,公司也在战略上进行了调整,把淘宝拆分成立淘宝、天猫和一淘三个独立的事业部。三个事业部的业务决策链路更短,业务发展更快、技术人员也快速增长。而且三个事业部的定位不一样、业务发展方向不一样、业务的管控规则不一样。而且在一些业务规则上可能还相互冲突。我们都知道在做业务系统的时候,为了快速应对每天的业务需求变更,很多时候都是通过代码来写业务逻辑的。在业务抽象建模,系统架构的开放性方面都是不足的。这就会导致业务逻辑之间的耦合和相互影响,研发效率大幅下降。
系统架构必须升级,这就进入了第三个业务中心平台化阶段。业务中心平台化,什么是平台?就是要把基础能力跟每个业务方的特性业务拆分,要把业务和业务之间的逻辑进行隔离。比如说天猫的业务跟淘宝网的业务有可能是冲突的,但他们需要在一个平台上执行,这时候我们必须把业务的逻辑分开。这时候就开始升级会员平台、商品平台、交易平台等等。平台化最核心的是业务抽象建模和系统架构的开放性。业务抽象解决共性的80%问题,系统架构开放性解决20%的个性化问题。
16b0f6df9f48308a0c8d0d4d1e91b178103625ff
比如说我们看到这个商品的平台化,整个电商体系有10几亿的商品,涉及商品本身的物理属性、不同渠道的销售价格、库存等等。还有很多的不同类目的管控规则,比例医疗器械,必须要有企业资质证明,要有检验证明等等。而且还有些类目,因为市场定位不一样,管控的力度也不一样,比例淘宝和天猫在手机的管控上也都有差异。我们怎么去解决差异性的问题,这些东西就是在整个商品平台里面去做的。我们要通过建立元数据中心、规则中心、商品发布界面自动生成、图片扫描等等来实现商品管理的平台化。
6e1f3cfc32b6ce59403ca75829a0680f708ed681
我们知道每一个人去逛淘宝买东西,每一笔交易都在交易平台上发生,但是在不同的交易的流程和规则是不一样的。比如我们买实物商品、买虚拟商品,还有线下洗头服务等等,它的交易流程不同,有些是先支付在发货,有些是先发货后支付。比如说汽车可能需要在购买的时候同时申请贷款。支付宝给我们提供了很多支付方式,但不同业务需要的支付方式是不一样的,这些东西都是需要通过交易平台来实现。平台化要把不同业务的逻辑隔离开,避免相互影响。
f80d6825d9f74f168c9d902231d052a799c31289
淘宝今年业务方向是内容化,社区化、本地服务化。我们需要为淘宝网未来5年的业务升级搭建新的基础设施,新的业务平台。像这个内容平台就是其中的一个,需要能同时支持所有业务的内容生产、内容管控、内容的检索、内容的组织等等。
a9ead78ec527905f3b35b1992a41ab36f5dbb3d0
全球化是整个集团得战略方向。要能把我们中国的货卖到国外,国外货卖到中国,国外不同国家之间的货物买卖。这些东西都涉及我们整个电商基础设施的升级,包括多语言、多币种、多时区的支持。但他不仅仅这些,还有更复杂的电商基础模型的升级。比如中国有一个很好的制造商,商品品质好。但是他们自己本身不具备英语的能力,俄语的能力,这时候在我们的电商体系里面,如何帮助他把货物卖到全球去。国外的品牌如何简单方便的把货物在淘宝、天猫、聚划算、1688、农村淘宝上去卖等等。比如说华为生产的手机,在天猫上卖的价格、AliExpress上的价格不一样,而且整个的营销模式不一样。“双十一”搞活动的时候,你会发现因为时差的问题,我们整个交易营销的控制逻辑都极大的提升了复杂度。会员安全控制的复杂度等等,都会发生天大的变化。当我们面对全球化的时候,所有基础的模型都会发生变化,涉及到我们对电商商业领域的理解升级,基础模型升级,平台能力升级。
e0ed89154f44d6a7e1fc8da2a9d93f00a64ee669
到现在为止,平台化的升级改造还在不断的继续。而且只要这个业务存在,这个公司存在一天我们就要不断的进行平台的升级。
但随着生态的复杂度、业务的复杂度、系统复杂度的升级,我们又遇到了新的问题。我们领域的平台化解决了领域内部的问题。但是我们每一个业务的执行都是夸领域的,涉及会员、商品、交易、营销、店铺、评价、支付、物流、售后等等,业务逻辑横跨几十个系统。比如说这个衣服,我们的商品发布规则是什么样的,交易规则什么样的,营销规则什么样的,这些规则分散在不同的系统中,而且还是相互有关联的。逐渐的,没有一个人能说清楚全局情况了。最后发现最厉害的是谁?就是我们的程序员,因为他们能翻代码,能把所有的逻辑还原出来,但代价极大。最后发现做一个需求,我们评估所有系统的需求要1个月时间,开发只需要10天,但测试又花了10天。真正有效的工作占比很少。整个研发效率和业务响应速度都比较差。这个问题已经不是一个技术问题了,而是一个复杂生态的协作问题。我们就到了第4个业务中台化阶段。主要解决4个问题:1、信息获取成本高。2、互联互通成本高。3、服务具有不确定性。4、低水平重复建设。
如何来解决这些问题呢,我们可去看一下传统的建筑行业和互联网本身的基础设施建设。基本上靠三个东西来共同解决一个复杂生态的协作问题。1、协议标准、运行机制。2、满足标准的分布式执行单元。3、中心化的控制单元。比如说我们的移动互联网,我们现在之所以上网能用手机,它的根本是什么呢?网络的协议,就是我们整个对网络的理解,它是最基本的思考。然后我们有很多设备,不管是什么品牌的手机,只要满足3G协议、4G协议,就可以插上卡上网。也就是通过这张sim卡确定了我们的身份,从运营商控制网络上获取了控制信息。知道我们是谁,能不能上网,网络速率等等信息。回头来看我们的电商生态,就是要根据我们对商业的理解,把一些基础协议梳理出来。例如什么是业务?什么是业务身份?各个业务领域的边界是什么?每个领域提供的基础服务是什么?领域服务和领域服务之间的流程链接标准是什么?再在这些思想的指导下去建立业务平台化的实施标准和业务管控标准。电商业务中台由一系列业务能力标准、运行机制、业务分析方法论,配置管理和执行系统以及运营服务团队构成的体系,提供各业务方能够快速,低成本创新的能力。
dcc47bc61ef8d45c51394a7a97d710c5b5fdb7bd
业务中台需要一个中心化控制单元,就是我们的运营平台。它主要由协议标准,能力地图、业务需求结构分解、全局业务身份、业务全景图、业务度量等构成。能让我们有一个地方纵观全局,把控细节。其中能力地图是一个最基础的设施,要能把电商生态里面的能力都呈现出来,并在过程中不断的优化完善。就象我们现在出行离不开高德地图一样,今后所有的业务方需要做业务规划,业务创新,都可以到这儿来寻找需要的基础能力。
7a88afa02a7c36415e4a44e87f85a300730080c2
为了能将业务逻辑本身与实现逻辑分离,可以将业务逻辑下发给不同实现的执行系统,引入竞争,方便业务平台的改造升级,我们要将控制信息从业务平台中抽离到业务中台,以业务身份为主线来进行组织管理和呈现。并以生态角色的视角来重构信息架构。这样的变革对我们原来的系统架构提出了更高的要求。
f0a722b65dc4093fba07d5f4eeabcdc260c5e198
通过业务中台化,我们把所有业务的数据汇集沉淀。每个业务它是怎么出来的,出来之后做了哪些业务需求,业务活动,每个业务活动的效果是怎么样的,都可以沉淀下来。当一个新业务来了之后,我们就可以让他看到前人成功和失败的经验。逐步可以做一些系统建议,建议他怎么去做营销活动,怎么做效果分析。这样我们能通过数据最终反过来支撑我们的业务创新。
最后,我想说的是业务平台是在最基础技术和前端大家能感受到交互技术中间的一层。这一层不是外部消费者和入门级技术人员能直接感知到的。但是这一层才是真正制约我们整个商业发展速度的核心地带。业务平台如何能支撑几千种业务,几万人在同一个时间里面进行变更,这个对业务业务的抽象能力和系统架构能力的要求都是极高的。
大家学一个技术,学一个网络的协议,今天学完了以后我一辈子都不会忘记,都可以用。但是我们在做业务抽象建模的时候,今天你的想法跟明天的想法都会不一样。为什么?桌子上的一杯水,今天你看到一个杯子,材质是塑料的,瓶口是小的,肚子是大的,你可能就做了抽象建模,但明天发现另外一个杯子的时候,它是大口小肚子的,你对杯子的理解变化了。所以说它是随着你生活阅历的增长,视野越来越开阔,你对同样一件事情的理解越来越深刻,你的建模能力都在不断的提升,而且基本是无止境的。在业务平台你会对商业的本质有更深入的学习、理解和成长。
本次分享的主题为:蘑菇街下单平台演进。主要介绍的内容是蘑菇街如何一步一步从PHP往Java服务化平台转化的过程。
63e31ae7208b46f3aa2710f6cb926a380b1672cd
这次介绍的主要内容包括5个点: 1、首先,我们需要看一下就是去年9月我刚到蘑菇街时,我所看到的下单系统。当时这个系统所面临的一个问题是什么?面对这些问题我们应该做如何的应对方案。 2、其次,我们为什么要做系统拆分,整个服务化过程是怎么做的。我们抽取的数据模型、服务梳理、系统架构是如何做的。 3、下单系统独立过程中,对蘑菇街快速的业务发展,如何进行对应的支撑。如何实现下单真正的平台化,快速支持业务和需求。 4、独立化前后,我们在日常的问题排查、性能优化中所做的变化。 5、总结,新阶段下单平台的能力。
a16307f03ae748d83e5a64240470c10809dc17be
在做下单服务化之前,蘑菇街整体还是以PHP为主的。随着蘑菇街每年业务成倍数的发展,系统面临的问题越来越严重,挑战越来越大。主要体现在三个方面: 系统上:这个时候的蘑菇街应用称为主站。主站是一个单体的应用。蘑菇街所有的业务场景代码基本都在这个PHP应用中,大小2G+的应用。这个时候,几百号人同时修改着这个应用,每周周一到周四下午是发布时间点。我记得,每次发布的时候,各个应用模块的人,都把代码提交到主干,然后上百人修改的发布就这么浩浩荡荡的往上面发布了。
这个时候,最常出现的问题就是:A:某个同学发布把另外⼀一个同学的代码带上线了,因为他们在同时修改同一个文件。B:发布过程中,在测试机时(线上beta环境)发现了问题,这个时候,所有人的代码都回滚回来,重新发布。在这个时候,各个应用模块之间的耦合严重,稳定性也相当差。很容易出现线上故障。由于这样复杂的相互依赖关系,导致整体性能很低,无法满足大促需求。同时,出现线上问题时,排查起来相当低效。
业务上:没有一个抽象的业务模型,各个业务都是对应的业务PD提需求过来,然后开发评估工作量和可行性,然后对应这个业务流程就开始开发。没有把所有业务流程好好的梳理出来(其实也努力过,梳理过程中,也没有对应的下单能力支撑,最终以失败告终)。另外,业务的边界很不清楚,比如,资金的同学需要对某种业务做红包或者支付减免,这个时候,就需要我们对这个业务做对应的判断,然后新增加一个字段传递给资金,告诉他们这个业务类型。扩展上,也是一样的问题。
总结种种因数,我们得到一个结论:拆。把下单系统从主站中拆出来,做为一个独立的java服务化应用。08bf6f29361f72acd59306635bab20fe3571e0ca
刚刚分析了,拆分是势在必行。那么我们要做应用独立,要服务化,遇到的第一个问题就是:现有服务依赖问题。这么多的依赖,这么多的入口:PC、H5、App、快抢、秒杀等,不同入口不同的业务,他们对各个服务的依赖也不一样,处理逻辑也不一样。另外,第二个问题就是,下单过程中涉及到的一个分布式事务问题。
ef2bb3c6879f2c6ce6855d814ed9bec4579ffece
这里是我在入职之初,删老代码过程中,整理出来的一个原有下单系统内部流程图。其中每个框(无论大小,大框代表核心节点,小框代表各种⼩小业务节点)代表一个功能或者业务节点。基本一个节点就是对应的一个服务依赖。
整个下单过程中的依赖有几十个,分属于各种业务,也包含了强弱依赖。 那么,对于这样一个流程,问题来了:我们怎么知道哪个业务走了哪个节点。对于每个节点对外的服务依赖是强依赖还是弱依赖,大促时我们应该如何降级。另外还有个很严重的问题,就是由于经历了多代码农的辛勤耕耘,相互之间代码理解不一致,加上没有通用的数据模型规范,有一些业务节点,分散在四处。
举个例子:校验这个节点,在入口的地方,在商品读取,拆单,价格计算,运费计算都有。并且,在新来业务时,如做预售,又要新增一个全局的util方法,供给各个接口使用。这样,校验节点不断累积重复。
b572a444f46986ecc4a171eeefe18d87cbcc3911
总结原因,在服务化之初,做的第一项重要的任务就是把所有的服务进行梳理,下单过程中所有的功能节点进行抽取。合并相同的功能节点,拆分负责的综合节点。然后将下单过程中所有可能涉及的功能节点进行固化(对应提炼了功能节点新增标准和扩展标准)。对于同一类型的服务依赖,定义为一个功能节点。比如,计价节点:包括了促销优惠计价,普通优惠计价,多阶段计价等不同的实现。
如上图所示,下单的功能节点和对应的依赖抽象出来之后。就可以很轻松的总结出,下单过程中其实主要包括了三个要素:下单的静态数据(下单流程中所需要的所有数据),下单功能节点(抽象出来的对应节点)以及对应的订单数据模型(落入对应的DB数据,持久化)。
另外,抽象出来了这些功能节之后,我们对于强弱服务的依赖治理也变得相对简单很多。每个节点对应一个对应的功能,每个节点的具体实现,有对应实现该功能的服务。
比如,对于校验模块,我们可能有黑名单校验,有限购检验两个实现;我们在大促时,可以直接对黑名单校验进行对应开关推送,降级该服务以保障整个下单流程的稳定。
对于其他的服务同理,如果对应依赖的服务出现故障或者系统不稳,我们可以切换对应的节点开关,⾛走其他的备用链路。
14709024953c16ce4aeb50cfb7635773659405c2
刚刚在总结功能节点时,我们提炼出来了三大要素。针对这三大要素之后,我们在实现过程中发现,其实这样的功能节点实现时,就是一个模板+一个策略模式的选择器。
就 实现了整个下单流程。整体流程如上图1所示:用户请求进入时,首先根据下单的入参,进行对应的外部信息查询,拼装好对应的下单数据模型(下单流程上下文, 见图2中:orderSDTO);拼装好数据之后,开始执⾏行整个模板对应的功能节点,获取到最终的订单模型;最后将订单模型写入DB生成订单。
我 们可以看到,每个功能节点实现方式,以及执行过程完全一致,如图3中的举例计价功能节点的选择实现过程。这些功能节点都是:通过一定的 condition,然后选择对应的一个或者多个provider,然后顺序执行(invoke)。同时,在退款,在购物车等其他应用中,都有类似的一种 场景需求;所以,对应这样的功能节点实现以及选择过程,抽象出来了一个基础框架:SPI框架。这个框架做了以下几件事情:
1:抽象出了对应的SpiBase类银SpiProvider类,所以具体的功能节点实现继承者两个基础的类。框架将所有的实现类实现集中管理到ProviderFactory中。 2:各个节点针对不同的业务场景实现,通过xml配置对应的condition。不同的业务场景,执行不同的功能节点实现。 3:对功能节点提供互斥(只能选择一个实现类执行)、组合(选用对个实现类执行)的选择器功能(SpiSelector)。
1b69cec32acfaa55397ae20a4a9a1d690d947c65
通过下单的功能节点管理过程抽象获取到的SPI框架,同时也建立了对应的SPI标准,也是对应的功能节点管理的一个标准: SPI粒度:对应功能节点的抽取,与依赖管理的功能节点一一映射的关系。
SPI互斥和组合:每个功能点执行的时候,不是说简单的只能执行一个,也不是说顺序执行等。而是可以建立对应的互斥组合关系。比如:校验,可以多个实现都执行;计算价格,只能选择⼀一种计算价格方式(优惠计价/多阶段价格/其他系统自定义价格/等) SPI与共建:由于有了SPI,对于某些业务方需求,可以开权限让业务方自己实现对应的某个SPI节点的特殊业务实现。举例:快抢业务,下单过程对于超时功能节点的需求,不是通用节点的30分钟或者24小时;于是让快抢业务方对于这个SPI新写了一个provider(15分钟),执行的condition为:当快抢业务时,该SPI选择新写的provider。这样业务方自己写代码,我们负责review,然后发布上线,实现平台共建。
SPI隔离:不同的SPI实现隔离标准,目前是通过condition条件隔离。
1d64c2b9954a9a12b9cfb1c1fa987aa8815d8ecf
在服务化过程中,遇到的第二个问题是之前的分布式事务实现方式。
PHP时代,采用的是手动catch,然后人工回滚的方式。比如:减库存时,失败了。这个时候手动catch住,然后调用回库存和解锁券的接口。这种手动处理的方式会带来比较多的问题:首先,如果回库存接口或者回券接口异常,会导致券和库存不准问题。其次,如果回券接口卡了,会影响这个下单rt。
在服务化过程中,不再使用这样的分布式事务处理方式。通过消息来结构整个回滚过程,流程如上图所示。 如果在锁券或者减库存时,出现了异常,这个时候异步发出废单消息,库存和促销应用接对应的废单消息,即可以回滚对应的优惠券和库存。(库存和优惠券内部做幂等)
d12b59e8c15d9d6aa29ae07d4bdbe26918c7952b
在服务化过程中,除了系统方面的问题外,还有很多业务支撑相关的问题。
首先,没有业务模型:所有的对各个垂直业务进行详细的梳理,每个垂直业务的特点、流程、下单过程的要求、依赖等。对于部分业务,如快抢、秒杀等都是单开接口,单独处理对应的业务流程。导致同时维护多个下单流程接口。
扩展性不好:下单内部,没有抽取一个可扩展的业务模型和数据模型,遇到新的业务流程时,⽐比如:新来一个预售,可能需要从下单入口开始,对应的PC、H5、App、秒杀、快抢等多个接口同时维护对应的校验和相关的处理。订单表也没有合适的字段来标记这个类型的订单,导致后续的订单列表,退款,物流等各个系统处理复杂。
业务边界:如试用有抽奖、魔豆商城也有抽奖,还有很多地方都有抽奖流程面对临时需求:只能通过if else,然后在整体的大的php数组中添加一个额外的字段,来标记这个临时需求,下单内部的上下文也不断增大。开发人员之间,也相互不是很 理解这么多的字段具体的含义。
f32cfa26ab7c0285b4c94e62a3ef86f0509e3609
为了支撑各类业务的发展,在服务化过程中,对所有的业务流程进行了抽象和整理。总结出来了20+种下单业务流程类型,如上图所示。每种业务流程,在下单内部映射为对应的流程标记,SPI以这些标记作为condition条件,执行不同的功能节点以及走对应的后续下单流程。
c7c2a852ffa71a91763227378aeb1618e094d8b3
如上所述,我们需要根据不同的业务,把不同的外部流程转换为内部的标记,然后通过标记执行不同的功能节点实现。对于整个流程,对于外部入参,整理出来了六个维度的识别入口。 1:channel:渠道信息,不同的渠道,如快抢(channel_fastbuy)、秒杀(channel_seckkill),传不同的channel,通过标记转化规则转化为内部的entranceTag。 2:flowType:不同的业务流程类型,传不同的流程,如0元订单(flowType=ZERO)。通过标记转化规则转化为内部的itemProcessTag。 3:店铺:根据店铺信息获取对应的卖家信息,映射为对应的sellerTag。 4:用户、会员:通过用户接口,获取对应的买家信息,通过标记转化规则转化为内部的buyerTag。 5:商品:通过商品接口,获取对应的商品类型信息,通过标记转化规则转化为内部的itemTypeTag。 内部,根据6个Tag,决定对应的流程类型(processType),这也决定了下单内部的功能节点执行condition。
b921f690f5857fd06af760962c4b7e9bbcb89810
整合上面对业务流程的总结,我们整理出来了真个交易业务流程的一个规则(如上图所示): 根据业务的描述—-》对应的交易流程——》交易的内部环节——》下单内部选择哪些功能节点——》写⼊入不同的订单数据27c1c72ef478abb4324fb57c3a8ef0ebbc758534
结合上面对交易流程的总结,我们确定了新的下单流程的一个内部分层结构。 外部请求过来时,下单根据对应的外部数据源,经过标记隐射层,转换获取到对应的下单内部的六个标记和流程。然后进入到下单内部服务层,根据Tag执行SPI,写入对应的订单信息。
85be296eb81d14e2704aaefaab2b9a3776aa3af1
在这样的一个业务流程抽象标准之下,我们对实际的业务也进行了对应的PHP到新java应用的迁移。具体情况如上图所示。 图2中,绿色的部分为对原有通用节点有改动,蓝色部分为标示该业务对此节点新增的provider实现。
举例:在快抢这个业务接入的时候,我们对订单校验(添加只能一次购买一个商品校验),extra数据(添加快抢的活动Id,计入订单表的extra字段)两个节点的通用实现进行了扩充,对订单超时(超时时间修改为半小时)和减库存节点(新增减活动库存实现)进行了新增实现。
3d1ad830d27c7117924e464aed122e0ed062f392
我们对于整个下单系统的服务治理、业务支撑、系统耦合等等问题都进行了对应分析和处理。改造完成之后对于我们具体有什么效果,或者说好处。那么可以从线上问题分析这一点直接看出来
1:线上问题排查: php时代,耦合比较严重、职责不清:问题排查效率低(一般通过打日志跟踪)
服务化之后:入口收拢、逻辑内聚,只有一个入口接口,规范错误码,所有的服务节点和实现都有对应的错误码,大部分错误通过错误码一眼就能看到问题。例如:上右图 中所示,测试妹子在测试环境测试时,看到确认下单接口返回的错误码FAIL_BIZ_CALC_PROMOTION_INVOKE_PROMOTION_INIT_EXCEPTION 直接就去找促销的同学了,一眼就定位出来是促销异常。
b8e723065fd113ffa8f65c141a5cbae0b5148e40
另外,在服务化之火,还对下单系统增加了业务系统监控、服务器系统监控、所有日志采用统一的规范便于查找。 68ff85f267c58006da4ce472a48e5fdec8f8cc91
同样,在对系统优化时,相对于服务化之前,也有了很多改观。 1:首先,服务化过程去除了很多冗余的依赖,这样使得下单的整体链路短了很多。
2:通过流程标记+SPI的方式,让不同业务能跳过不必要的功能节点,减少了对其他服务的压力。如:非海淘业务,跳过对关税税率节点的依赖。
3:新的系统下,做服务优化时,也变得简单易行:每个服务都是一个功能节点内部。整个下单系能优化的过程,其实主要是对下单各节点依赖的服务的优化过程。通过压测,压出各个节点的拐点,然后配合相应的服务提供方,优化接口。如缓存问题,多线程问题等。
4:对于下单系统内部的优化,也有很多成熟可行的方案。如:上图中,是最近,通过Tprofiler对下单应用的踩点分析结果。该结果中显示,在下单内部的一个util方法JSON转化过程中,平均耗时18ms,对于这样的实现,就可以进行对应的优化。721124fc783a0a007ca6a6da4466ca8c23258980
经过对服务依赖的治理,对业务流程的抽象,对整个下单流程的进行的独立和服务化之后,形成了现有的下单平台。 整个下单系统也从最初的综合主站应用(左图)—-》现有的下单分层架构(右图)
Q&A
提问一:蘑菇街这套下单系统发展了几年,为什么有这么严重的技术债务。或者说虽然历经过淘宝洗礼的黄裳等也必须经历再一次的洗礼?(黄裳->皇上,即蘑菇街创始人之一岳旭强。)
回答:其实不仅蘑菇街,很多初创公司,为了满足业务的发展,都会选择快速支持业务的PHP技术架构。这样在早期的业务快速扩展中,能够实现快速的迭代。这个也是为什么会出现到15年,蘑菇街业务高速发展时,技术上面会面临的一些挑战。
问题二:对于新搭建电商的创业团队,业务也是拓荒。老师在设计原则上能不能总结一些建议?
回答二:
首先,其实蘑菇街的业务发展,经历了6年的时间。在做服务化时,如我上面的介绍也说到,其实已经有一个大致的问题参考点在了。我们觉得主站比较冗杂,所以做拆分。我们觉得业务没有梳理好,所以,我们梳理了业务模型和流程等。
其次,对于设计过程中,除了技术架构的简单、分层的明确以外,业务参照上面,其实还是会考虑到未来至少2年的业务发展情况;同时还会参照其他电商公司的一些业务情况,进行分析。
问题三:新下单平台架构,DSL这一层 是用什么实现的,和普通的web服务有什么区别
问题三:其实就是我们内部的业务规则。就是我们目前对于业务接入时,有个业务规则映射过程,这个是抽象出来的一个规则引擎。目前,是通过配置Groovy脚本来实现的。与普通Web服务的区别在于,普通的Web服务只是针对具体的业务请求和提供对于的数据服务接口,而DSL这一层只是一个业务规则的层,不直接对外暴露服务的。
分享者简介
朱伟,来自蘑菇街电商基础平台,花名:逆天。本、硕均就读于于华中科技⼤大 学(俗称:关山⼝口男子职业技术学院)。毕业后进入阿里(花名:猪尾),主要做运费、物流相关的工作,15年9月进入蘑菇街后,主要负责下单、去支付相关 ⼯工作。
作者:谌文涛(俞月)
每年电商双11大促对阿里技术人都是一次大考,对阿里数据库团队更是如此。经过9年的发展,双11单日交易额从2009年的0.5亿一路攀升到2017年的1682亿,秒级交易创建峰值达到了32.5万笔/秒。支撑这一切业务指标的背后,是底层技术体系的一次次迭代升级。
阿里巴巴数据库系统经历了10多年的发展,今年正式确定从 第三代大规模分库分表 向 第四代X-DB分布式数据库系统 演进的目标。X-DB分布式数据库的落地已经在2017年双11大促中获得了可行性验证,同时底层开始引入存储计算分离架构。分布式在系统稳定性、容灾能力、容量扩展性、技术体系内聚性上有了质的提升,今年双11开启了阿里数据库技术架构新的篇章。
本文以阿里电商交易链路中的核心系统库存中心为例,一窥阿里集团数据数据库的发展历程。库存中心数据库集群(简称库存DB集群),从2012年独立拆分后,其发展可以概括为以下3个阶段:
2012~2013年:分库分表水平拆分,构建大规模数据库集群 2014~2016年:单元化异地多活架构,数据多单元间同步 2017年:X-DB 1.0分布式集群部署上线,新的起点 作为阿里数据库体系中的核心系统,库存DB集群的发展历程可以作为缩影,代表了阿里巴巴数据库体系的演进。
诞生 库存DB集群诞生于2012年,是业务垂直拆分的产物。库存最早是商品中心数据库的一个字段,随着淘宝业务的复杂化,单一字段已经满足不了基于后端仓储的库存管理体系,所以便有了垂直拆分出来的库存DB集群。
水平拆分 2012~2017年,双11交易额一步步的刷新纪录,库存DB集群的QPS/TPS也实现了几十倍的增长。水平拆分的基本思路是把数据库扩展到多个物理节点上,让每个节点处理不同的读写请求,从而缓解单一数据库的性能问题。
借助于数据库团队的DTS(Data Transmission Service)产品,库存中心进行了大规模的水平拆分,分库和分表数量扩展到最初的几百倍,平稳的支撑了这一个阶段业务的快速发展。与此同时,在热点商品扣减、防超卖数据强一致需求、跨城异地容灾数据质量问题、业务数据量急剧膨胀、超大规模数据库集群运维等问题点上,迫切需要新一代架构来解决。
异地多活单元化 2014~2015年,为了进一步提升用户的购物体验,库存DB集群与主站交易链路一同做了单元化部署。单元化很大程度上解决了买家的使用体验问题,在本单元内封闭完成读写操作。但是对于卖家维度的数据,比如编辑商品、扣减库存,就会涉及到跨单元中心去写。关于单元化架构,之前已经有了很多介绍。
有了多个单元,对于底层数据库来说,面临的最大挑战就是数据同步,因为对于单元封闭的买家维度的数据,需要把单元的数据全部同步到中心;对于读写分离类型的业务,我们要把中心的数据同步到单元。这条数据通道就是依靠DRC(Data Replication Center)来完成。
如今,DRC不仅成为集团单元化链路的基础设施,对应的云产品DTS已经从2016年开始让阿里云用户、聚石塔商家低成本的搭建异地容灾。单元化架构给库存DB集群带来的最大挑战是多单元间的数据强一致问题,我们也为此做了很大的努力。
X-DB分布式集群 2017年双11,库存DB集群第一次使用X-DB 1.0分布式集群部署,平稳的支持了32.5万笔/秒的交易创建峰值。X-DB是阿里巴巴自研高性能分布式可全球化部署数据库,其核心技术目标概括为以下6点:
100%兼容MySQL生态,应用无缝迁移 跨AZ、Region的全球化部署能力,5个9以上的可用率 自动化的数据Sharding,计算、存储均可水平扩展 高性能的事务处理,相同硬件下达到MySQL 10倍的事务处理能力,百万TPS 自动化的数据冷热分离,存储成本为MySQL的1/10 计算存储分离,存储按需扩展 库存DB集群双十一部署架构:
正是由于X-DB提供了全面兼容MySQL、高性能、低成本、跨城容灾、数据强一致的能力。在2017年年初,数据库团队和业务研发团队确定了在库存中心部署X-DB 1.0的目标,解决业务目前面临的痛点:
全面兼容MySQL,实现业务系统平滑迁入 极致性能,双11单实例热点扣减峰值TPS是去年的3倍 低成本,相比于传统的单元化主备架构部署,减少2个数据副本以及单元间数据同步资源成本 跨城容灾,借助Batching和Pipelining技术实现跨城强同步场景吞吐量几乎无衰减 数据强一致,借助Paxos协议提供多单元间数据强一致能力;批量关闭中心集群全部实例,集群30秒内完成单元选主切换,数据零丢失 计算存储分离,彻底解决传统机型计算资源和存储资源固定配比问题,搭配容器化技术,大促峰值期间将数据库弹性部署运行在离线任务主机,落地零扩容成本支持双十一大促 X-DB首次亮相在2017年双11的舞台,平稳支撑零点峰值32.5万笔/秒,开启了阿里数据库体系从分库分表时代向分布式集群时代的大门。技术之路永无止境,我们今天的技术现状离业务对我们的要求还有很大的差距。但是千里之行,始于足下,借用《魔戒》里的经典台词:“There’s some good in this world, Mr. Frodo. And it’s worth fighting for.”
本文根据白辉在 2016ArchSummit 全球架构师(深圳)峰会上的演讲整理而成。ArchSummit 北京站即将在 12 月 2 日开幕,更多专题讲师信息请到北京站官网查询。
非常荣幸在这里跟大家一起来探讨“海量服务架构探索”相关专题的内容。
我叫白辉,花名是七公。2014 年之前主要在阿里 B2B 负责资金中心、评价、任务中心等系统。2015 年加入蘑菇街,随着蘑菇街的飞速成长,经历了网站技术架构的大
变革。今天分享的内容来自于去年我们做的事情,题目用了一个关键词是“篱笆”,篱笆的英文是 Barrier,是指 2015 年蘑菇街面临的问题和艰巨的困难。我们越过了这些篱笆,取得了很好的成果。
引言
今天分享的内容主要分为五部分。第一部分,概述电商系统发展中期面临的一般性问题。第二部分,如何解决面临的问题,主要的策略是做拆分、做服务化。第三、四部分,服务化之后业务的大增长、网站流量飞速的增加、“双 11”大促等的挑战很大,我们做了服务的专项系统优化以及稳定性治理。第五部分,进行了总结和展望。
电商系统发展中期面临的一般性问题 我们先看第一部分的内容。
我总结了一下,一般电商系统发展到中期都会面临三个方面的问题(如图)。第一方面是业务问题。比如,一开始做业务的时候可能很随意,一是并不考虑业务模型、系统架构,二是业务之间的耦合比较严重,比如交易和资金业务,有可能资金和外部第三方支付公司的交互状态耦合在交易系统里,这些非常不利于业务发展。第二方面是系统问题。2014 年我们面临单体应用,400 人开发一个大应用,扩展性很差,业务比较难做。第三方面是支撑问题,比如关于环境、开发框架和质量工具等。这些是电商系统发展到中期都会面临的问题,中期的概念是用户过了千万,PV 过了 1 亿。
我们来看一下蘑菇街 2015 年初面临的问题。蘑菇街 2015 年用户过亿,PV 过 10 亿,业务在超高速发展,每年保持 3 倍以上的增长。电商促销、交易、支付等业务形态都在快速膨胀,我们需要快速支持业务发展,而不是成为业务的瓶颈。那么就是要去做系统的拆分和服务化。
系统拆分与服务化过程 第二部分的内容,是关于蘑菇街系统拆分与服务化的历程。
按照如下几条思路(见图),我们进行系统拆分以及服务化。最开始,大家在同一个应用里开发一些业务功能,都是选择速度最快的方式,所有的 DB 和业务代码都是在一起的。首先我们将 DB 做垂直拆分。第二步是做业务系统垂直拆分,包括交易、资金等。第三步是在系统拆完了之后要考虑提供什么样的 API 来满足业务的需求?这里我们要做数据建模 + 业务建模,数据建模方面包括数据表的设计和扩展支持,数据模型应该非常稳定;业务建模方面,使用标准和灵活的 API,而且尽量不用修改代码或者改少量代码就能支持业务需求。第四步是需要将业务逻辑下沉到服务,Web 层专注于展示逻辑和编排,不要涉及过多业务的事情。然后用 SOA 中间件建设服务化系统。最后会做一些服务的治理。
来看一个 API 服务化的例子,在做服务化之前和做服务化之后,交易创建下单业务有什么不一样。服务化之前我们面临的问题有:入口分散,如果要在底层做任何一个微小的改动,十几个入口需要几十个人配合修改,这是非常不合理的一种方式;多端维护多套接口,成本非常高;还有稳定性的问题,依赖非常复杂,维护很难。我刚到蘑菇街的时候,一次大促活动就导致数据库崩溃,暴露了系统架构很大的问题和总量上的瓶颈。按照上面提到几条思路去做服务化,看看有了哪些改善?首先是 API 统一,多个端、多个业务都用统一的 API 提供;其次是依赖有效管理起来,大事务拆分成多个本地小事务;最后降低了链路风险,逻辑更加清晰,稳定性更好。
2015 年 3 月我来到蘑菇街之后,先制订了服务化的规范,探讨了到底什么是标准的服务化。在做服务化的过程中,发现大家代码风格完全不一样,所以制定编码规范非常重要。2015 年 8 月,我们完成了各个模块的改造,包括用户、商品、交易、订单、促销、退款等,然后有了服务化架构 1.0 的体系。在此基础之上,我们进一步做了提升流量和稳定性等更深度的建设。2015 年 9 月,我们实施了分库分表和链路性能提升优化,2015 年 10 月做了服务治理和服务保障。
接下来,以服务架构和服务体系建设为主线,讲一下去年整个网站架构升级的过程。
在服务化 1.0 体系完成之后,我们得到了一个简单的体系,包含下单服务、营销服务、店铺服务、商品服务和用户服务,还有简单的 RPC 框架 Tesla。当时,我们并没有做很多性能优化的事情,但是通过业务流程化简和逻辑优化,每秒最大订单数从 400 提升到 1K,基础服务也都搭建了起来。
有了 1.0 初步的服务化体系之后,更进一步,我们一是要继续深入网站如资金等的服务化,二是要做服务内部的建设,比如容量、性能,这也是接下来要讲的内容。
购买链路的性能提升
这个链路(见图)是比较典型的电商链路,有商品页、下单、支付、营销和库存等内容。一开始每个点都有瓶颈,每个瓶颈都是一个篱笆,我们要正视它,然后翻越它。
我们先来看第一个篱笆墙:下单的瓶颈。
2015 年“3.21”大促的时候,DB 崩溃了,这个瓶颈很难突破。下一个订单要插入很多条数据记录到单 DB 的 DB 表。我们已经用了最好的硬件,但是瓶颈依然存在,最主要的问题就是 DB 单点,需要去掉单点,做成可水平扩展的。流量上来了,到 DB 的行写入数是 2 万 / 秒,对 DB 的压力很大。写应该控制在一个合理的量,DB 负载维持在较低水平,主从延时也才会在可控范围内。所以 DB 单点的问题非常凸显,这座大山必须迈过去,我们做了一个分库分表组件 TSharding 来实施分库分表。
将我们写的分库分表工具与业界方案对比,业界有淘宝 TDDL Smart Client 的方式,还有 Google 的 Vitess 等的 Proxy 方式,这两种成熟方案研发和运维的成本都太高,短期内我们接受不了,所以借鉴了 Mybatis Plugin 的方式,但 Mybatis Plugin 不支持数据源管理,也不支持事务。我大概花了一周时间写了一个组件——自研分库分表组件 TSharding( https://github.com/baihui212/tsharding ),然后快速做出方案,把这个组件应用到交易的数据库,在服务层和 DAO 层,订单容量扩展到千亿量级,并且可以继续水平扩展。TSharding 上线一年之后,我们将其开放出来。
第二个篱笆墙就是营销服务 RT 的问题。促销方式非常多,包括各种红包、满减、打折、优惠券等。实际上促销的接口逻辑非常复杂,在“双 11”备战的时候,面对这个复杂的接口,每轮链路压测促销服务都会发现问题,之后优化再压测,又发现新的问题。我们来一起看看遇到的各种问题以及是如何解决的。首先是压测出现接口严重不可用,这里可以看到 DB 查询频次高,响应很慢,流量一上来,这个接口就崩溃了。那怎么去排查原因和解决呢?
首先是 SQL 优化,用工具识别慢 SQL,即全链路跟踪系统 Lurker。
这张图我简单介绍一下。遇到 SQL 执行效率问题的时候,就看是不是执行到最高效的索引,扫表行数是不是很大,是不是有 filesort。有 ORDER BY 的时候,如果要排序的数据量不大或者已经有索引可以走到,在数据库的内存排序缓存区一次就可以排序完。如果一次不能排序完,那就先拿到 1000 个做排序,然后输出到文件,然后再对下 1000 个做排序,最后再归并起来,这就是 filesort 的大致过程,效率比较低。所以尽量要走上索引,一般类的查询降低到 2 毫秒左右可以返回。
其次是要读取很多优惠规则和很多优惠券,数据量大的时候 DB 是很难扛的,这时候我们要做缓存和一些预处理。特别是查询 DB 的效率不是很高的时候,尽量缓存可以缓存的数据、尽量缓存多一些数据。但如果做缓存,DB 和缓存数据的一致性是一个问题。在做数据查询时,首先要看本地缓存有没有开启,如果本地缓存没有打开,就去查分布式缓存,如果分布式缓存中没有就去查 DB,然后从 DB 获取数据过来。需要尽量保持 DB、缓存数据的一致性,如果 DB 有变化,可以异步地做缓存数据失效处理,数据百毫秒内就失效掉,减少不一致的问题。
另外,如果读到本地缓存,这个内存访问比走网络请求性能直接提升了一个量级,但是带来的弊端也很大,因为本地缓存没有办法及时更新,平时也不能打开,因为会带来不一致问题。但大促高峰期间我们会关闭关键业务数据变更入口,开启本地缓存,把本地缓存设置成一分钟失效,一分钟之内是可以缓存的,也能容忍短暂的数据不一致,所以这也是一个很好的做法。同样的思路,我们也会把可能会用到的数据提前放到缓存里面,做预处理。在客户端进行数据预处理,要么直接取本地数据,或者在本地直接做计算,这样更高效,避免了远程的 RPC。大促期间我们就把活动价格信息预先放到商品表中,这样部分场景可以做本地计价,有效解决了计价接口性能的问题。
再就是读容量问题,虽然缓存可以缓解压力,但是 DB 还是会有几十 K 的读压力,单点去扛也是不现实的,所以要把读写分离,如果从库过多也有延时的风险,我们会把数据库的并行复制打开。
我们来看一下数据。这是去年“双 11”的情况(如图)。促销服务的 RT 得到了有效控制,所以去年“双 11”平稳度过。
接下来讲一个更基础、更全局的优化,就是异步化。比如说下单的流程,有很多业务是非实时性要求的,比如下单送优惠券,如果在下单的时候同步做,时间非常长,风险也更大,其实业务上是非实时性或者准实时性的要求,可以做异步化处理,这样可以减少下单对机器数量的要求。另外是流量高峰期的一些热点数据。大家可以想象一下,下单的时候,一万个人竞争同一条库存数据,一万个节点锁在这个请求上,这是多么恐怖的事情。所以我们会有异步队列去削峰,先直接修改缓存中的库存数目,改完之后能读到最新的结果,但是不会直接竞争 DB,这是异步队列削峰很重要的作用。还有,数据库的竞争非常厉害,我们需要把大事务做拆分,尽量让本地事务足够小,同时也要让多个本地事务之间达到一致。
异步是最终达到一致的关键,异步的处理是非常复杂的。可以看一下这个场景(见图),这是一个 1-6 步的处理过程,如果拆分成步骤 1、2、3、4、end,然后到 5,可以异步地做;6 也一样,并且 5 和 6 可以并行执行。同时,这个步骤走下来链路更短,保障也更容易;步骤 5 和 6 也可以单独保障。所以异步化在蘑菇街被广泛使用。
异步化之后面临的困难也是很大的,会有分布式和一致性的问题。交易创建过程中,订单、券和库存要把状态做到绝对一致。但下单的时候如果先锁券,锁券成功了再去减库存,如果减库存失败了就是很麻烦的事情,因为优化券服务在另外一个系统里,如果要同步调用做券的回滚,有可能这个回滚也会失败,这个时候处理就会非常复杂。我们的做法是,调用服务超时或者失败的时候,我们就认为失败了,就会异步发消息通知回滚。优惠券服务和库存服务被通知要做回滚时,会根据自身的状态来判断是否要回滚,如果锁券成功了券就回滚,减库存也成功了库存做回滚;如果库存没有减就不用回滚。所以我们是通过异步发消息的方式保持多个系统之间的一致性;如果不做异步就非常复杂,有的场景是前面所有的服务都调用成功,第 N 个服务调用失败。另外的一致性保障策略包括 Corgi MQ 生产端发送失败会自动重试保证发成功,消费端接收 ACK 机制保证最终的一致。另外,与分布式事务框架比起来,异步化方案消除了二阶段提交等分布式事务框架的侵入性影响,降低了开发的成本和门槛。
另一个场景是,服务调用上会有一些异步的处理。以购物车业务为例,购物车列表要调用 10 个 Web 服务,每一个服务返回的时间都不一样,比如第 1 个服务 20 毫秒返回,第 10 个服务 40 毫秒返回,串行执行的效率很低。而电商类的大多数业务都是 IO 密集型的,而且数据量大时还要分批查询。所以我们要做服务的异步调用。比如下图中这个场景,步骤 3 处理完了之后 callback 马上会处理,步骤 4 处理完了 callback 也会马上处理,步骤 3 和 4 并不相互依赖,且处理可以同时进行了,提高了业务逻辑执行的并行度。目前我们是通过 JDK7 的 Future 和 Callback 实现的,在逐步往 JDK8 的 Completable Future 迁移。这是异步化在网站整体的应用场景,异步化已经深入到我们网站的各个环节。
刚才我们讲了链路容量的提升、促销 RT 的优化,又做了异步化的一些处理。那么优化之后怎么验证来优化的效果呢?到底有没有达到预期?我们有几个压测手段,如线下单机压测识别应用单机性能瓶颈,单链路压测验证集群水位及各层核? 系统容量配比,还有全链路压测等。
这是去年“双 11”之前做的压测(见图),达到了 5K 容量的要求。今年对每个点进一步深入优化,2016 年最大订单提升到了 10K,比之前提升了 25 倍。实际上这些优化可以不断深入,不仅可以不断提高单机的性能和单机的 QPS,还可以通过对服务整体上的优化达到性能的极致,并且可以引入一些廉价的机器(如云主机)来支撑更大的量。
我们为什么要做这些优化?业务的发展会对业务系统、服务框架提出很多很高的要求。因此,我们对 Tesla 做了这些改善(见图),服务的配置推送要更快、更可靠地到达客户端,所以有了新的配置中心 Metabase,也有了 Lurker 全链路监控,服务和服务框架的不断发展推动了网站其他基础中间件产品的诞生和发展。2015 年的下半年我们进行了一系列中间件的自研和全站落地。
我们得到了服务架构 1.5 的体系(见图),首先是用户服务在最底层,用户服务 1200K 的 QPS,库存 250K,商品服务 400K,营销 200K,等等。
接下来我们看一下这一阶段,Tesla 开始做服务管控,真正成为了一个服务框架。我们最开始做发布的时候,客户端、服务端由于做的只是初级的 RPC 调用,如果服务端有变更,客户端可能是几秒甚至数十秒才能拉到新配置,导致经常有客户投诉。有了对服务变更推送更高的要求后,我们就有了 Matabase 配置中心,服务端如果有发布或者某一刻崩溃了,客户端马上可以感知到,这样就完成了整个服务框架连接优化的改进,真正变成服务管控、服务治理框架的开端。
购买链路的稳定性提升
有了上面讲到的服务化改进和性能提升之后,是不是大促的时候看一看监控就行了?其实不是。大流量来的时候,万一导致整个网站崩溃了,一分钟、两分钟的损失是非常大的,所以还要保证服务是稳的和高可用的。只有系统和服务是稳定的,才能更好地完成业务指标和整体的经营目标。
下面会讲一下服务 SLA 保证的内容。
首先 SLA 体现在对容量、性能、程度的约束,包括程度是多少的比例。那么要保证这个 SLA 约束和目标达成,首先要把关键指标监控起来;第二是依赖治理、逻辑优化;第三是负载均衡、服务分组和限流;第四是降级预案、容灾、压测、在线演练等。这是我们服务的关键指标的监控图(见上图)。支付回调服务要满足 8K QPS,99% 的 RT 在 30ms 内,但是图中监控说明 SLA 未达到,RT 程度指标方面要优化。
服务的 SLA 保证上,服务端超时和限流非常重要。如果没有超时,很容易引起雪崩。我们来讲一个案例,有次商品服务响应变慢,就导致上层的其他服务都慢,而且商品服务积压了很多请求在线程池中,很多请求响应过慢导致客户端等待超时,客户端早就放弃调用结果结束掉了,但是在商品服务线程池线程做处理时拿到这个请求还会处理,客户都跑了,再去处理,客户也拿不到这个结果,最后还会造成上层服务请求的堵塞,堵塞原因缓解时产生洪流。
限流是服务稳定的最后一道保障。一个是 HTTP 服务的限流,一个是 RPC 服务的限流。我们服务的处理线程是 Tesla 框架分配的,所以服务限流可以做到非常精确,可以控制在服务级别和服务方法级别,也可以针对来源做限流。
我们做了这样一系列改造之后,服务框架变成了有完善的监控、有负载均衡、有服务分组和限流等完整管控能力的服务治理框架。服务分组之后,如果通用的服务崩溃了,购买链路的服务可以不受影响,这就做到了隔离。这样的一整套服务体系(如图)就构成了我们的服务架构 2.0,最终网站的可用性做到了 99.979%,这是今年 6 月份的统计数据。我们还会逐步把服务的稳定性和服务质量做到更好。
总结及下一步展望
最后总结一下,服务框架的体系完善是一个漫长的发展过程,不需要一开始就很强、什么都有的服务框架,最早可能就是一个 RPC 框架。服务治理慢慢随着业务量增长也会发展起来,服务治理是服务框架的重要组成部分。另外,Tesla 是为蘑菇街业务体系量身打造的服务框架。可以说服务框架是互联网网站架构的核心和持续发展的动力。选择开源还是自建,要看团队能力、看时机。我们要深度定制服务框架,所以选择了自研,以后可能会开源出来。
服务框架是随着业务发展不断演变的,我们有 1.0、1.5 和 2.0 架构的迭代。要前瞻性地谋划和实施,要考虑未来三年、五年的容量。有一些系统瓶颈可能是要提前解决的,每一个场景不一样,根据特定的场景选择最合适的方案。容量和性能关键字是一切可扩展、Cache、IO、异步化。目前我们正在做的是服务治理和 SLA 保障系统化,未来会做同城异地的双活。
谢谢大家!
https://www.infoq.cn/article/qq-red-envelopes-technology-program/ https://www.infoq.cn/article/2017hongbao-weixin/ https://www.infoq.cn/article/2016-hongbao-weibo/ https://www.infoq.cn/article/weixin-yaoyiyao-technology-details/ https://www.infoq.cn/article/1-billion-bonus-from-the-clouds/ https://www.infoq.cn/article/qq-red-envelopes-technology-program/?utm_source=articles_about_Hongbao&utm_medium=link&utm_campaign=Hongbao https://yq.aliyun.com/articles/71053?spm=a2c4e.11153940.blogcont70747.9.2fea506fDWPGzZ http://www.10tiao.com/html/674/201702/2656594086/1.html https://www.infoq.cn/article/1-billion-bonus-from-the-clouds/?utm_source=articles_about_Hongbao&utm_medium=link&utm_campaign=Hongbao
使用ADB不root删除小米MIUI系统自带应用
将 MIUI 系统 root 并且使用管理工具屏蔽广告推送,或者 root 后直接刷入干净的第三方 rom,都是一劳永逸地解决垃圾广告问题的方法,使用本文介绍的 ADB 工具屏蔽广告的效果没有前面两个好,但好在不是很麻烦,而且不用 root 系统。
方法及步骤说明如下:
1、下载 ADB 工具 ADB 全称 Android Debug Bridge(Android 调试桥),是一个通用命令行工具,可以与模拟器实例或连接的Android 设备进行通信。查看介绍及下载可以直接前面的超链接。下载好之后把下面三个文件放到如下路径: C:\Windows
2、安装小米刷机工具(安装手机驱动) 小米通用刷机工具自带手机驱动,安装好驱动才能正常使用 ADB 工具。
3、开启 MIUI 开发者模式 依次打开设置-我的设备-全部参数,在「MIUI 版本」一栏连续点击多次,开启 MIUI 开发者模式。
4、开启 USB 调试 依次打开设置-更多设置,进入开发者选项,再打开「USB 调试」和「USB 调试(安全设置)」两项,如下图所示:
5、用 USB 数据线连接电脑和手机
6、使用 ADB 命令删除 MIUI 系统自带应用 删除应用的 ADB 命令是: adb shell pm uninstall --user 0 应用包名
例如,在 MIUI 系统中「搜狗输入法」的包名为:com.sohu.inputmethod.sogou.xiaomi,那么卸载搜狗输入法的完整 ADB 命令为: adb shell pm uninstall --user 0 com.sohu.inputmethod.sogou.xiaomi
打开 Windows 系统的「命令提示符」工具(快捷键是 win + R,再输入 cmd 回车),直接输入上面的删除命令,成功之后会返回 success 提示,这种删除是实时生效的。
注意:
1)不同的 MIUI 版本可能系统应用包名会有变化,可以使用如下命令查看系统所有包名: adb shell pm list packages 2)并不是所有的系统自带应用都可以删除,有些删除会导致手机无法开机,请谨慎操作; 3)如果你不想删除应用,可以使用如下命令冻结,效果差不多,冻结的应用可以再解冻: adb shell pm disable --user 0 应用包名 4)删除的应用(通过 uninstall 命令)无法使用命令恢复,但是升级系统可以再次回来。
下面是我将自己和家人的小米手机 MIUI 系统删除的一些系统自带应用集合,删除后重启手机没有问题,其它可删除应用请自行测试。
(MIUI 9、MIUI 10 测试删除后能正常开机使用) adb shell pm uninstall --user 0 com.miui.systemAdSolution (小米系统广告解决方案,必删) adb shell pm uninstall --user 0 com.miui.analytics (小米广告分析,必删) adb shell pm uninstall --user 0 com.xiaomi.gamecenter.sdk.service (小米游戏中心服务) adb shell pm uninstall --user 0 com.xiaomi.gamecenter (小米游戏中心) adb shell pm uninstall --user 0 com.sohu.inputmethod.sogou.xiaomi (搜狗输入法) adb shell pm uninstall --user 0 com.miui.player (小米音乐) adb shell pm uninstall --user 0 com.miui.video (小米视频) adb shell pm uninstall --user 0 com.miui.notes (小米便签) adb shell pm uninstall --user 0 com.miui.translation.youdao (有道翻译) adb shell pm uninstall --user 0 com.miui.translation.kingsoft (金山翻译) adb shell pm uninstall --user 0 com.android.email (邮件) adb shell pm uninstall --user 0 com.xiaomi.scanner (小米扫描) adb shell pm uninstall --user 0 com.miui.hybrid (混合器) adb shell pm uninstall --user 0 com.miui.bugreport (bug 反馈) adb shell pm uninstall --user 0 com.milink.service (米连服务) adb shell pm uninstall --user 0 com.android.browser (浏览器) adb shell pm uninstall --user 0 com.miui.gallery (相册) adb shell pm uninstall --user 0 com.miui.yellowpage (黄页) adb shell pm uninstall --user 0 com.xiaomi.midrop (小米快传) adb shell pm uninstall --user 0 com.miui.virtualsim (小米虚拟器) adb shell pm uninstall --user 0 com.xiaomi.payment (小米支付) adb shell pm uninstall --user 0 com.mipay.wallet (小米钱包) adb shell pm uninstall --user 0 com.android.soundrecorder (录音机) adb shell pm uninstall --user 0 com.miui.screenrecorder (屏幕录制) adb shell pm uninstall --user 0 com.android.wallpaper (壁纸) adb shell pm uninstall --user 0 com.miui.voiceassist (语音助手) adb shell pm uninstall --user 0 com.miui.fm (收音机) adb shell pm uninstall --user 0 com.miui.touchassistant (悬浮球) adb shell pm uninstall --user 0 com.android.cellbroadcastreceiver (小米广播) adb shell pm uninstall --user 0 com.xiaomi.mitunes (小米助手) adb shell pm uninstall --user 0 com.xiaomi.pass (小米卡包) adb shell pm uninstall --user 0 com.android.thememanager (个性主题管理) adb shell pm uninstall --user 0 com.android.wallpaper (动态壁纸) adb shell pm uninstall --user 0 com.android.wallpaper.livepicker (动态壁纸获取) adb shell pm uninstall --user 0 com.miui.klo.bugreport (KLO bug 反馈)
前面两个是 MIUI 系统支撑广告及精准化推送的应用,应第一时间删除,删除后不会出现无法开机的情况。这样 MIUI 系统的广告就会少很多——没有验证是否完全屏蔽。
【警告】以下系统自带应用删除后必定无法正常开机(来自网络),请避免误删:
com.miui.cloudservice (小米云服务) com.xiaomi.account (小米账户) com.miui.cloudbackup (云备份) com.xiaomi.market (应用市场)