Java生鲜电商平台-微服务生鲜电商系统设计(小程序/APP)
说明:本文章主要是讲解-微服务生鲜电商系统设计与架构,希望对大家有所帮助
商品模块(库存)
订单模块
支付模块
DDD 领域驱动设计
刚刚确实是梳理了一下模块,既然是微服务,就得进行服务的拆分,服务怎么进行拆分呢?
-
Infrastructure(基础实施层)
-
Domain(领域层)
-
Application(应用层)
-
Interfaces(表示层,也叫用户界面层或是接口层)
微服务结合 DDD
不过对于领域设计而言,代码层其实不是最重要,最重要的是如何去划分领域,划分好边界。
而对于微服务而言,非常适合从业务上去划分各个 Modules,划分好各个业务板块,微服务 + DDD,个人觉得首先从微服务的角度考虑去划分大的业务模块,每个微服务都应该是一个可以独立部署,各司其职的模块。
简单的说,在微服务实际的开发中,结合 DDD 的思想去划分所有属于自己的领域。
实施 DDD 的关键
第一点是使用通过的语言建立所有的聚合,实体,值对象。
第二点也就是最关键的“建模”:
-
划分“战略建模”,从一种宏观的角度去审核整个项目,划分出“界限上下文”,形成具有上帝视角的“上下文映射图”。
-
还有一个建模是“战术建模”,在我们的“战略建模”划分出来的“界限上下文”中进行“聚合”,“实体”,“值对象”,并按照模块分组。
构建电商系统的上下文映射图
先来确定我们的战略核心的领域是什么?我们的目的是什么?
作为一个电商系统,我们的核心肯定是卖出更多的商品,获取更多订单更多的利润,那么销售可以作为我们的一个核心的领域。
然后我们对支撑着核心域的子域也做了划分,支撑着核心域的有商品域,用户域,通用域有订单域,物流域,支付域。
而且我们花费的精力可能相比以后的数据驱动开发模式更多,但在整体对项目的把控性能上说,领域比数据驱动更加抽象,更加的顶层设计,在对应互联网的多变情况看得更远。
我们将微服务拆分为 5 个领域,分别是:
-
销售域
-
商品域
-
用户域
-
订单域
-
支付域
完美,接下来就可以开始开发了。^?_?^
等等,兵马未动,粮草先行;代码未动,图先行,先把时序图画出来。
时序图
等等,微服务的技术栈还未选型。
微服务技术栈选型
服务拆分完了,时序图也画完了,可以开始我们的微服务之旅了,目前主流的微服务有阿里大名鼎鼎的 Dubbo 和 Spring Cloud 全家桶,还有新浪的 Motan。
比较熟悉的还是 Dubbo 和 Spring Cloud,也都使用过,究竟应该选用哪一个呢?
因为之前都使用过,做点简单,粗暴的总结。Dubbo 在很早之前就开始使用,当时的微服务还没有现在这么火,很多理论体系也未完善,Dubbo 更像是一套 RPC 整合框架,Spring Cloud 则更倾向微服务架构的生态。
相比 Dubbo,Spring Cloud 可以说是微服务一整套的解决方案,在功能上是 Dubbo 的一个超级。
Dubbo 和 Spring Cloud 比喻,Dubbo 架构的微服务就像组装电脑,各个环节自由度很高。Spring Cloud 更像品牌机。
基于不折腾,简单快捷,更倾向选择 Spring Cloud。OK,就定下来技术栈使用 Spring Cloud,愉快的决定
等等,就这么草率就决定用 Spring Cloud 做为微服务,难道不需要把微服务的利弊先弄清楚吗?
微服务的利和弊
①强模块化边界
我们知道做软件架构,软件设计,模块化是非常重要的一点,一开始我们写程序做软件,我们采用类的方式来做模块化,后面开始采用组件或类库的方式做模块化,可以做到工程上的重用和分享给其他团队来使用。
微服务在组件的层次上面又高了一层,以服务的方式来做模块化,每个团队独立开始和维护自己的服务,有明显的一个边界。
开发完一个服务,其他团队可以直接调用这个服务,不需要像组件通过 Jar 或源码的方式去进行分享,所以微服务的边界是比较清晰的。
②可独立部署
③技术多样性
弊(或者说挑战)
①分布式复杂性
在原来单块应用就是一个应用,一个对单块应用的架构比较熟悉的人可以对整个单块应用有一个很好的把控。
但是到了分布式系统,微服务化了以后可能涉及到的服务有好几十个,一些大公司可能涉及到的服务上百个,服务与服务之间是通过相互沟通来实现业务。
那么这个时候整个系统就变成非常复杂,一般的开发人员或一个团队都无法理解整个系统是如何工作的,这个就是分布式带来的复杂性。
②最终一致性
微服务的数据是分散式治理的,每个团队都有自己的数据源和数据拷贝,比方说团队 A 有订单数据,B 团队也有订单数据,团队 A 修改了订单数据是否应该同步给团队 B 的数据呢?
这里就涉及到数据一致性问题,如果没有很好的解决一致性问题,就可能造成数据的不一致,这个在业务上是不可以接受的。
③运维复杂性
以往的运维需要管理的是机器+单块的应用,分布式系统和单块应用不一样的是,分布式系统需要很多的服务,服务与服务之间相互协同。
那么对分布式系统的资源,容量规划,监控,对整个系统的可靠性稳定性都非常具备挑战的。
只有在清楚了解微服务带来的挑战,明知道山有虎偏向虎山行,才能够真正的胜任挑战,最重要的是,要清楚明了里面有什么坑,怎么避免踩坑。
完美,已经了解微服务带来的好处和挑战,接下来就可以开始开发了。^?_?^
等等,微服务还没有做逻辑分层。
微服务怎么做逻辑分层
目前我们的微服务里面有几个服务,分别是订单,商品,用户。
如果客户端向查看 “我的订单” 这么一个接口;如果客户端假定是 PC 端,就需要请求三次接口,分别对接订单,商品,用户三个服务,分别拿完三次调用数据,再将三次调用数据进行整合输出展示。
要知道 PC 调用后端服务是走外网,这无疑大大增加了网络的开销,而且让 PC 端变成更为复杂。
这些都属于比较基础和原子性,下沉一个公司的基础设施的低层,向下承接存储,向上提供业务能力,有些公司叫基础服务,中间层服务,公共服务,Netflix 成为中间层服务。我们暂且统称为基础服务。
②微服务聚合服务层
已经有了基础服务能提供业务能力,为什么还需要聚合服务,因为我们有不同的接入端,如 App 和 H5,PC 等等,它们看似调用大致相同的数据,但其实存在很多差异。
例如 PC 需要展示更多信息,App 需要做信息裁剪等等。一般低层服务都是比较通用的,基础服务应该对外输出相对统一的服务,在抽象上做得比较好。
但是对不同的外界 App 和 PC 的接入,我们需要作出不同的适配,这个时候需要有一个层去做出聚合裁剪的工作。
例如一个商品详情在 PC 端展示和 App 端的展示,PC 可能会展示更多的信息,而 App 则需要对信息作出一些裁剪。
如果基础服务直接开放接口给到 PC 和 App,那么基础服务也需要去做成各种设配,这个很不利于基础服务的抽象。
所以我们在基础层之上加入聚合服务层,这个层可以针对 PC 和 App 做成适当的设配进行相应的裁剪。
分布式事务
那么这个时候单个数据库的 ACID 已经不能适应这种情况,而在这种集群中想去保证集群的 ACID 几乎很难达到,或者即使能达到那么效率和性能会大幅下降,最为关键的是再很难扩展新的分区了。
这个时候如果再追求集群的 ACID 会导致我们的系统变得很差,这时我们就需要引入一个新的理论原则来适应这种集群的情况,就是 CAP。
CAP 定理
CAP 必须满足以下的 3 个属性:
-
一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
-
可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
-
分区容错性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 C 和 A 之间做出选择。
简单的来说,在一个分布式系统中,最多能支持上面的两种属性。但显然既然是分布式注定我们是必然要进行分区,既然分区,我们就无法百分百避免分区的错误。因此,我们只能在一致性和可用性去作出选择。
在分布式系统中,我们往往追求的是可用性,它的重要性比一致性要高,那么如何实现高可用,这里又有一个理论,就是 base 理论,它给 CAP 理论做了进一步的扩充。
base 理论
base 理论指出:
-
Basically Available(基本可用)
-
Soft state(软状态)
-
Eventually consistent(最终一致性)
base 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
好了,说了一大顿理论,程序员们都等急了,赶快来看看分布式事务的解决方案有哪些,可以进行接下去的 Coding...
先排除掉我们应该不会选择的方案,一个是 XA 两阶段提交,这个在很多传统型公司会被使用,但不适合互联网微服务的分布式系统,锁定资源时间长,性能影响大,排除。
另一个是阿里的 GTS,并没有开源,目前已经开源了 Fescar,不过目前尚缺少调研,可能在下个阶段研究后会使用,目前先排除。
剩下的是 TCC 和 MQ 消息事务两种。
MQ 消息事务:RocketMQ
先说说 MQ 的分布式事务,RocketMQ 在 4.3 版本已经正式宣布支持分布式事务,在选择 RokcetMQ 做分布式事务请务必选择 4.3 以上的版本。
这里涉及到一个异步化的改造,我们理一下,如果是同步流程中的各个步骤:
-
查看商品详情(或购物车)
-
计算商品价格和目前商品存在库存(生成订单详情)
-
商品扣库存(调用商品库存服务)
-
订单确认(生成有效订单)
无论成功或失败,商品服务都将发送一个扣减库存结果的消息“stroeReduce”到消息队列中,订单服务会订阅扣减库存的结果。
订单服务收到消息后有两种可能:
-
如果扣减库存成功,将订单状态改为 “确认订单” ,下单成功。
-
上面使用 MQ 的方式确实是可以完成 A 和 B 操作,但是 A 和 B 并不是严格一致性,而是最终一致性。
我们牺牲掉严格一致性,换来性能的提升,这种很适合在大促高并发场景使用。
但是如果 B 一直执行不成功,那么一致性也会被破坏,后续应该考虑到更多的兜底方案,方案越细系统就将越复杂。
TCC 方案
TCC 是服务化的二阶段变成模型,每个业务服务都必须实现 Try,/confirm/i,Calcel 三个方法。
这三个方式可以对应到 SQL 事务中 Lock,Commit,Rollback:
-
Try 阶段:Try 只是一个初步的操作,进行初步的确认,它的主要职责是完成所有业务的检查,预留业务资源。
-
Confirm 阶段:Confirm 是在 Try 阶段检查执行完毕后,继续执行的确认操作,必须满足幂等性操作,如果 Confirm 中执行失败,会有事务协调器触发不断的执行,直到满足为止。
-
Cancel:是取消执行,在 Try 没通过并释放掉 Try 阶段预留的资源,也必须满足幂等性,跟 Confirm 一样有可能被不断执行。
完美,可以把我们的系统引入 TCC。^?_?^
等等,有同学提问:
-
有同学可能会问了,如果在 Confirm 或 Cancel 中,有一方的操作失败了,可能出现异常等情况该怎么解决。
这个就涉及 TCC 的事务协调器了,事务协调器就 Confirm 或 Cancel 没有得到返回的时候,会启用定时器不断的进行 Confirm 或 Cancel 的重试。
这个也就是我们强调,/confirm/i,Cancel 接口必须是幂等性的一个原因了。
-
还有同学会问了,为什么事务协调器知道 /confirm/i,或 Cancel 没有完成。
这个就涉及到了 TCC 也做了一张本地消息表,会记录一次事务,包括主事务,子事务,事务的完成情况都会记录在这种表中(当然未必是表,可能是 ZK,Redis 等等介质),然后启用一个定时器去检查这种表。
-
还有同学会问,事务怎么传递,这个就涉及使用的 TCC 的框架了,一般来说用的都是隐式传参的方式。
在主事务创建的时候用隐式传参调用子事务,子事务包含 Try,/confirm/i,Cancel 都会记录到事务表里面。
这里推荐 TCC 的开源框架使用 mengyun 的 TCC,然后也可以其他的,无所谓。
完美,下单的流程开发完毕了,可以让 QA 接入。^?_?^
等等,微服务的保护措施做了吗?
熔断限流隔离降级
微服务分布式依赖关系错综复杂,比方说前端的一个请求,这来到后端会被转为为很多个请求。
这个时候后台的服务出现不稳定或者延迟,如果没有好的限流熔断措施,可能会造成用户体验的下降,严重的时候会出现雪崩效应,把整个网站给搞垮。
如果像阿里巴巴在双 11 等活动中,如果没有一套好的限流熔断措施,这是不可想象的,可能是根本无法支撑那么大的并发容量。
Netflix 在 2012 年前也没有设计好的限流容错,当时也是饱受着系统稳定性的困扰,好几次网站因为没有好的熔断措施把网站搞垮。
在 2012 年 Netflix 启动了弹性工程项目,其中有一个产品叫 Hystrix,这个产品主要用来解决微服务的可靠性。
有了这个系统之后,Netflix 在系统稳定性上上了一个大的台阶,在此之后就没有出现过大规模的雪崩事故。
下面使用 Hystrix 例子来讲解一下限流熔断。
几个概念:熔断,隔离,限流,降级,这几个概念是分布式容错最重要的概念和模式。
①熔断:如果说房子里面安装了电路熔断器,当你使用超大功率的电路时,有熔断设备帮你保护不至于出问题的时候把问题扩大化。
②隔离:我们知道计算资源都是有限的,CPU,内存,队列,线程池都是资源。
他们都是限定的资源数,如果不进行隔离,一个服务的调用可能要消耗很多的线程资源,把其他服务的资源都给占用了,那么可能出现因为一个服务的问题连带效应造成其他服务不能进行访问。
③限流:让大流量的访问冲进去我们的服务时,我们需要一定的限流措施,比方说我们规则一定时间内只允许一定的访问数从我们的资源过,如果再大的话系统会出现问题,那么就需要限流保护。
④降级:如果说系统后台无法提供足够的支撑能力,那么需要一个降级能力,保护系统不会被进一步恶化,而且可以对用户提供比较友好的柔性方案,例如告知用户暂时无法访问,请在一段时候后重试等等。
Hystrix
-
构建一个 HystrixCommand 对象,用于封装请求,并在构造方法配置请求被执行需要的参数。
-
执行命令,Hystrix 提供了几种执行命令的方法,比较常用到的是 Synchrous 和 Asynchrous。
-
判断电路是否被打开,如果被打开,直接进入 Fallback 方法。
-
判断线程池/队列/信号量是否已经满,如果满了,直接进入 Fallback 方法。
-
执行 Run 方法,一般是 HystrixCommand.run(),进入实际的业务调用,执行超时或者执行失败抛出未提前预计的异常时,直接进入 Fallback 方法。
-
无论中间走到哪一步都会进行上报 Metrics,统计出熔断器的监控指标。
-
Fallback 方法也分实现和备用的环节。
-
最后是返回请求响应。
完美,把 Hystrix 加入我们系统吧,这样突然有洪峰流量也不至于我们的系统一下就冲垮。^?_?^
等等,Hystrix 的限流数值,错误数熔断,超时熔断,尝试恢复比率这些需要我们配置的数值应该怎么定呢?
这个就取决你的系统压测的指标和你部署的规模了,这里还涉及到一个容量设计的问题,一会我们将系统部署上线的时候再来详细说道。
刚刚提到一个问题,就是这些限流数值,错误数熔断这些数字,我们现在都写在配置文件里面。
例如说写在 Properties,YML 里面,当有一天突然需要把限流数下调(可能是系统遭受到什么压力打击),那我们只能把代码拉下来,巴拉巴拉改了。
然后重新上传打包,发布重启,一个流程下来,不说个把小时吧,十来分钟总少不了吧。
想办法我们把这些配置项放到一个集中式配置中心。
集中式配置中心
自己写配置中心还挺麻烦的,去菜市场逛逛吧,菜市场里面有,Spring Cloud Config,百度的 Disconf,阿里的 Diamond,还有携程的 Apollo。
基本上他们的原理都差不多,配置中心可以简单的理解为一个服务模块,开发人员或运维人员可以通过界面对配置中心进行配置。
下面相关的微服务连接到配置中心上面就可以实时连接获取到配置中心上面修改的参数。
更新的方式一般有两种:
-
Pull 模式,服务定时去拉取配置中心的数据。
-
-
Pull 一般使用定时器拉取,就算某一个网络抖动没有 Pull 成功,在下一次定时器的时候,终将能保证获取最新的配置。
-
Push 可以避免 Pull 定时器存在的延时,基本可以做到实时获取数据,但也有问题就是网络抖动的时候可能会丢失更新。
但考虑到可能由于某些网络抖动没有推送成功,客户端还具备了定时向 Apollo 服务端拉取 Pull 数据的功能。
就算推送没成功,但是只要一定时间周期,客户端还是会主动去拉取同步数据,保证能把最终配置同步到服务中。这个也是 Apollo 在高可用方面上非常有特色的设计。
Apollp 在高可用上也做了保证,客户端获取到数据会把数据缓存在内存,还会 Sync 到本地磁盘。
就算 Apollo 服务器挂掉了,就算客户端服务重启了,也可以从本地磁盘中拉取回来数据,继续提供对外服务,从这点来看 Apollo 的配置中心在高可用上考虑还是比较周到的。
把配置中心配置上去后,我们就可以把 Hystrix 还有 MySQL 的用户密码,还有一些业务开关等等的配置参数放上去了。
完美,开发基本完工了,其实就几个模块,一个简单的下单购物流程,当我们把系统交付给运维,运维喊道,日志呢,做微服务怎么可以没有调用链日志呢?
调用链监控&日志
确实,微服务是一个分布式非常复杂的系统,如果没有一套调用链监控,如果服务之间依赖出现问题就很难进行定位。
调用链监控其实最早是 Google 提出来的,2010 年 Google 发表了一篇调用链的论文,论文以它内部的调用链系统 Dapper 命名。
值得一说的是 Skywalking 是国人出品的一款新的调用链工具,采用开源的基于字节码注入的调用链分析,接入段无代码入侵。
而且开源支持多种插件,UI 在几款工具来说比较功能比较强大,而且 UI 也比较赏心悦目,目前已经加入了 Apache 孵化器。
采用 Skywalking 作为调用链工具
为何会采用 Skywaling,在低层原理的实现,这几款产品都差不多,但在实现和使用的细节相别还是很大:
-
首先在实现方式上,Skywalking 基本对于代码做到了无入侵,采用 Java 探针和字节码增强的方式,而在 Cat 还采用了代码埋点,而 Zipkin 采用了拦截请求,Pinpoint 也是使用 Java 探针和字节码增强。
-
其次在分析的颗粒度上,Skywaling 是方法级,而 Zipkin 是接口级,其他两款也是方法级。
-
在数据存储上,Skywalking 可以采用日志体系中比较出名的 ES,其他几款,Zipkin 也可以使用 ES,Pinpoint 使用 Hbase,Cat 使用 MySQL 或 HDFS,相对复杂。
由于目前公司对 ES 熟悉的人才比较有保证,选择熟悉存储方案也是考虑技术选型的重点。
-
还有就是性能影响,根据网上的一些性能报告,虽然未必百分百准备,但也具备参考价值,Skywalking 的探针对吞吐量的影响在 4 者中间是最效的,经过对 Skywalking 的一些压测也大致证明。
完美,把微服务的包打好,上传到服务器就可以运行了。^?_?^
等等,微服务包都打好了,剩下就是 Jar 包或 War 包一个一个上传到服务器上,然后用个脚本 Start,在以前单块应用还好,现在微服务几十几百个应用,请问,运营人员怕不怕?
听说,Docker + Kubernetes 和微服务更配喔。
Docker + Kubernetes
就几个服务,先不用容器化部署了...乍一看,没完没了,还有 CICD,灰度发布...容易编排...
下次再讲吧,先把服务部署上去吧。
部署到生产,预估容量
该把服务部署上线了,一个服务上线肯定得评估下或者预估下访问量有多少用户,有多少访问,这个涉及到该配置多少的机器资源,这应该怎么去估算呢,反正程序员在家里怎么算都算不出来。
评估访问量
①问运营,如果是一个已经上线的产品,肯定存在已有的用户数和访问数据,就算存在偏差,也是可控的范围。
②问产品,确定一个什么样形态的产品,例如是拼团,例如是秒杀,各种处理方式都不同。
评估平均访问量 QPS
一天 86400 秒,一般认为请求大部分发生在白天,就按照 40000 计算,日平均访问量=日总访问量/40000。
评估高峰 QPS
可以把之前每日的访问曲线图拉出来看看,峰值是根据业务不同而定的,例如,有些业务是白天早上 10 点的流量偏多,有些业务是晚上人家休闲类的流量偏多。
总之,根据业务去估算出日均的峰值,类似于电商类的服务,一般峰值是日均流量的 5 倍左右。
还有例如一些大促活动可能会更高,这个都要跟运营人员提前沟通好的,还有一些活动例如,秒杀,这个就不是靠预估出来,秒杀是另一种的考虑情况,采取的应对策略跟普通订单是完全不同。
评估系统,单机极限 QPS
在上线之前需要跟测试人员一起做压力测试,针对每个服务每台机器去做,一般来说,会把一个服务一台机器压到极限,在逐步的进行优化。
思考一个问题,假定单台机器最大的 QPS 是 1000,我们峰值是 5000,那需要用多少台机器去抗?答案是大于等于 6 台,最少的容错不得少于 1 台。
貌似一个非常简单的微服务就差不多了,不过貌似还是差了很多,数一下:
-
监控系统哪去了(基础设施监控,系统监控,应用监控,业务监控)
-
网关哪里去了
-
统一的异常处理哪里去了
-
API 文档哪里去了
-
容器化哪里去了
-
服务编排哪里去了
-
...
QQ:137071249
-
-