Yufeng0918 Spring Cloud Learning Save

spring-cloud, spring-cloud-alibaba, nacos, feign, ribbon, eurka, cloud-config, hystrix, seata

Project README

Spring Cloud

Spring Cloud Netflix

Spring Cloud Alibaba

全链路测试

本地自测

服务本地跑起来自己测试各个功能,直接通过dubbo服务调用,浏览器的http请求,直接请求你的接口,测试一下自己的各个功能,你自己一个人维护一个java web系统,不依赖别人的接口。 也可能跑不起来,那就单元测试,单元测试其实是一个很专业的领域,跑本地单元测试的时候,需要把你的spring容器跑起来,然后对各种bean的注入可能需要打桩,接着再测试各个接口。

有的时候跑起来需要有一些其他人负责的服务的配合,这个时候有一些方案可以做到,比如说本地可以跑起来一个服务注册中心,然后其他人的服务你手头没有,那团队可以做一个统一的本地服务模拟工程,工程跑起来就自动往本地注册中心去注册,接口的返回结果都是mock的。然后你的服务跑起来,就可以跑通了,包括数据库,缓存,MQ这些东西,都可以在本地部署,有一个本地的maven profile,放这些配置。

或者是在公司内网环境里,提供几台机器,作为dev环境,部署了数据库、缓存、MQ等中间件,服务可以部署,一台机器可以多部署几个服务,然后当你笔记本电脑在公司内网的时候,就可以访问到那几台机器,那么你本地启动,就可以直接访问到测试环境里的其他服务了

持续集成

有的公司会做持续集成,意思是你每天开发好的代码和功能,都必须有对应的单元测试可以进行自动化的测试,然后你本地单元测试跑通了,就可以提交代码到git仓库以及进行代码合并,直接触发jenkins这种集成工具,他会拉取你的最新代码,然后跑你所有的单元测试,确保你的代码在所有测试下可以正常运行

甚至可能是多个人维护一个系统,每个人每天/隔天,都要提交自己的代码+单测到git仓库进行代码合并,集成的概念,单人维护一个独立的工程/服务,每天不停的提交最新代码到你的git仓库里,你在不停的把自己最新写好的代码集成到已有的完整的代码里去

持续集成,提代码。多人维护一个系统,你本地写好的代码,本地跑单测是ok的,但是你提到git仓库合并进去,此时可能别人也会提代码合并进去,此时你们俩的代码集成在一起了,此时到底集成好的代码能不能正常工作呢?jenkins持续集成的工具,如果发现你有提交代码以及合并的操作,此时jenkins会触发一个任务,拉取你的代码到本地,自动运行所有的单元测试,用你的单元测试自动运行和检查,来确保你现在最新的集成后的代码都是可以运行的

有的时候还会专门写特定的自动化集成测试代码,就是说你代码提交之后,然后可能会把你完整代码跑起来,此时所有代码是一个集成在一起的状态,接着就是运行集成测试的代码,把你集成在一起的代码都跑一下,检查是否正常。但是这个比较麻烦,搞持续集成,在工具上要求git、jenkins之类的整合,你要做很多配置,同时要求你每天的代码都有对应的自动化测试代码,所以真的把持续集成做好,做的很标准的公司,其实不多

联调测试/功能测试

一个人维护一个java web系统,对其他人没有依赖,太low了。比较正常的,就是你写好了代码,自己简单自测完毕了,然后部署到一个联调测试/功能测试的环境里去,这个环境,是可能团队内部各个服务之间联调,或者甚至和其他团队的系统进行联调的地方

这个环境最好是独立的一套环境,部署好之后,QA会进行大量的手工测试,各种点击系统,也可能会有自动化测试,不过说实话,能玩儿自动化测试的公司不多,最后在这个环境,会有一个PM功能验收测试的过程

这个环境重在联调,把各个系统和服务跑通,确保功能运行没问题,一般机器都是低配置的,而且一个服务就一台机器,甚至是一台机器几个服务都有可能

预发布测试

接着一般是预发布环境的测试,这个环境一般是模拟线上环境,可能会在这里做压力测试、全链路压测、性能测试、可用性测试、稳定性测试,都可能会在这里做,一般机器配置跟线上一样,每个服务都是独立机器,甚至每个服务给多台机器。

比如说线上高峰QPS是1w+,线上机器是4核8G的,部署20台,那么预发布环境可能就是模拟每秒QPS是1000+,每个服务部署2台机器,做一个低压力测试,把全链路都压一下,测试性能,QPS,机器负载

有时候也可能会跑一些可用性测试,比如设计一些特殊故障之类的。在这个环境,通常流量是从线上获取回放过来的,有一个线上流量回放的过程,很多公司其实没这个环节,此时可能也就是走个过场,但是正经来说,是要做流量回放的,不是靠人力来测试,而是回放线上流量,看系统的功能是否正常,压力是否ok,性能是否还行,机器负载如何,全链路表现如何

有时候也会在这个环境让QA做一个全量功能回归测试,这可能是大版本变动的时候要做的。如果一切正常,那么就可以上线了

公司部署方案

设计、开发、测试、部署,流程都讲过了,微服务技术栈,服务注册中心,nacos,RPC框架,dubbo,设计就要把各个服务拆分完毕,包括你的业务逻辑,需求,接口,数据库,类,功能的时序图

每个人就负责开发自己的服务就可以了,nacos+dubbo。用dubbo开发一些接口,只要定义一些接口和dubbo注解,更多的还是写java代码

不同的环境之下,你的服务注册的namespace必须是不同的

滚动发布

中小型公司现在稍微好点的话,都会做自动化部署,自动化部署用的比较多的是jenkins,因为jenkins是支持持续集成和持续交付的,之前说过持续集成,那么持续交付就是比持续集成更进一步,简单来说,就是你每天都提交代码,他每天都自动跑测试确保代码集成没问题,然后可能每隔几天,就把一个生产可用的小版本交付到线上。这个时候,就需要一个自动化部署,jenkins可以自动在多台机器上部署你的服务/系统,过程其实也是类似的,只不过把手动改成自动罢了,你自己部署tomcat/基于spring boot内嵌容器,其实都行

中大型公司,一般发布系统都是自己研发的,你在上面指定对一个服务,指定一个git仓库的代码分支,然后指定一个环境,指定一批机器,发布系统自动到git仓库拉取代码到本地,编译打包,然后在你指定环境的机器上,依次停止当前运行的进程,然后依次重启你新代码的服务进程

这都是典型的滚动发布

但凡发布,都要考虑两个问题,一个是验证,一个是回滚

验证就是说,你怎么确定你这次部署成功了?一般来说,要观察每台机器启动后处理请求时的日志,日志是否正常,是否有报错,一般日志正常、没有报错,那么就算是启动成功了,有时候也会让QA/PM做一个线上验证。那么万一发布失败了呢?此时就得回滚,因为不同的上线是不一样的,有时候你仅仅是对代码做一些微调,大多数时候是针对新需求有上线,加了新的代码/接口,有时候是架构重构,实现机制和技术架构都变了

所以回滚的话,也不太一样,比如你如果是加了一些新的接口,结果上线失败了,此时心接口没人访问,直接代码回滚到旧版本重新部署就行了;如果你是做技术架构升级,此时失败了,可能很多请求已经处理失败,数据丢失,严重的时候会导致公司丢失订单,或者是数据写入了但是都错了

此时可能会采用回滚代码,或者清洗错乱数据的方式来回滚,总之,针对你的发布,你要考虑到失败之后的回滚方案,回滚代码,就得用旧版本的代码,然后重新在各个机器依次部署,就算是一次回滚了,至于丢失了数据没有,要不要清洗数据,这个看情况来定。滚动发布的话,风险还是比较大的,因为一旦你用了自动化的滚动发布,那么发布系统会自动把你的所有机器都部署新版本的代码,这个时候中间很有可能会出现问题,导致大规模的异常和损失

所以现在一般中大型公司,都不会贸然用滚动发布模式了

灰度发布

灰度发布,指的就是说,不要上线就滚动全部发布到所有机器,一般就是会部署在比如1台机器上,采用新版本,然后切比如10%的流量过去,观察那10%的流量在1台机器上运行一段时间,比如运行个几天时间,观察日志、异常、数据,是否一切正常,如果验证发现全部正常,那么此时就可以全量发布了。全量发布的时候,就是采用滚动发布那种模式

这个好处就是说,你先用10%以内的小流量放到灰度新版本的那台机器上验证一段时间,感觉没问题了,才会全量部署,这么操作,即使有问题,也就10%以内的请求出现问题,损失不会太大的,如果你公司体量特别大,灰度也可以是1%,甚至0.1%的流量。因为体量太大的公司,1%的流量就很大了

如果灰度的时候有问题,那么立刻把10%以内的小流量切去请求老版本代码部署的机器,灰度版本的机器立马就没流量请求了,这个回滚速度是极快的。通常灰度验证过后,全量发布,都不会有太大的问题,基本上再出问题概率就很小了,所以现在中大型互联网公司,一般都是灰度发布模式的

蓝绿部署

蓝绿部署的意思是说,你得同时准备两个集群,一个集群放新版本代码,一个集群放老版本代码,然后新版本代码的集群准备好了过后,直接线上流量切到新版本集群上去,跑一段时间来验证,如果发现有问题,回滚就是立马把流量切回老版本集群,回滚是很快速的。如果新版本集群运行一段时间感觉没问题了,此时就可以把老版本集群给下线了

那么为什么有灰度发布了还要用蓝绿部署呢?灰度发布过后,还是要全量部署的,但是有时候,如果涉及到一些新的架构方案,或者是新的接口,10%以内的小流量可能没办法暴露出线上的高并发问题,所以灰度验证没问题,结果全量部署还是有一个小概率会失败。此时全量发布用滚动发布的方式,逐步部署过去,很快会引发大规模的失败,此时回滚,是很慢的,因为要一台一台逐步回滚。

所以说,一般针对那种改动不太大的小版本,比如加个接口,修改一些代码,修复几个bug,类似这种整体变动不大的情况,建议用灰度发布,因为这种一般灰度验证没问题,全量部署也不会有问题

但是如果涉及到那种很大规模的架构重构或者架构升级,比如数据存储架构升级,或者是技术架构整体改造,或者是代码大规模重构,类似这种场景,最好是用蓝绿部署,也就是说,完全部署一个新的集群,然后把较大的流量切过去,比如先切10%,再切50%,最后切到100%,让新集群承载100%的流量跑一段时间。过程中一旦有问题,立马流量全部切回老集群,这个回滚速度就比灰度发布的全量部署回滚要快多了,因为仅仅是切流量而已,不需要重新部署

分布式事务TCC解决方案

技术方案

通用性TCC技术方案

组件
  • 主业务服务:相当于流量充值中心的服务,他就是TCC事务的主控服务,主要控制的服务,负责整个分布式事务的编排和管理,执行,回滚,都是他来控制
  • 从业务服务:相当于我们的资金服务、订单服务、积分服务、抽奖服务、流量券服务,主要就是提供了3个接口,try-confirm-cancel,try接口里是锁定资源,confirm是业务逻辑,cancel是回滚逻辑
  • 业务活动管理器:管理具体的分布式事务的状态,分布式事务中各个服务对应的子事务的状态,包括就是他会负责去触发各个从业务服务的confirm和cancel接口的执行和调用。。。
流程
  • try阶段,资源的锁定,先冻结掉用户的账户资金,将一部分资金转出到冻结资金字段里去;可以创建一个充值订单,但是状态是“交易中”
  • confirm阶段,就是将用户的冻结资金口减掉,转移到商户的账户里去;同时将充值订单的状态修改为“交易成功”;完成抽奖机会、积分、流量券的新增
  • cancel阶段,try阶段任何一个服务有问题的话,那么就cancel掉,相当于是将冻结的资金还回去,将订单状态修改为“交易失败”;如果confirm阶段任何一个服务有问题的话,也是cancel掉,相当于是将商户账户里的资金还到用户账户里去,同时将订单的状态修改为“交易失败”

有一张比较经典的图,就是主业务服务->数据库,然后几个从业务服务->数据库,接着主业务服务会访问业务活动管理器(有活动日志),主业务服务发起执行try,然后主业务服务通知业务活动管理器,业务活动管理器再通知各个从业务发起confirm或者是cancel操作,可以把这张图给体现一下

这里主业务服务其实就是总控整套逻辑的,然后从业务服务就是干活儿的,业务活动管理器主要是记录整个分布式事务活动状态的,这个还是挺有必要的吧,这样保存分布式事务进行过程中的各种状态。所以他会记录整个分布式事务的状态,分布式事务里各个服务代表的子事务的状态,而且他是负责在提交分布式事务的时候,调用各个从业务服务的confirm接口的,如果出问题的话也是他调用各个从业务服务的cancel接口的

一个执行流程和步骤大概是这样子的:

  • 主业务服务会先在本地开启一个本地事务(这个本地事务说白了,就是你的主业务服务是不是也可能会干点儿什么事儿)
  • 主业务服务向业务活动管理器申请启动一个分布式事务活动,主业务服务向业务活动管理器注册各个从业务活动
  • 接着主业务服务负责调用各个从业务服务的try接口
  • 如果所有从业务服务的try接口都调用成功的话,那么主业务服务就提交本地事务,然后通知业务活动管理器调用各个从业务服务的confirm接口
  • 如果有某个服务的try接口调用失败的话,那么主业务服务回滚本地事务,然后通知业务活动管理器调用各个从业务服务的cancel接口
  • 如果主业务服务触发了confirm操作,但是如果confirm过程中有失败,那么也会让业务活动管理器通知各个从业务服务cancel
  • 最后分布式事务结束

异步确保型TCC技术方案

如果要接入到一个TCC分布式事务中来,从业务服务必须改造自己的接口,本来就是一个接口,现在要新增两个接口,try接口,cancel接口。改造起来比较麻烦。这个大概来说就是把之前的通用型TCC方案给改造了一下,就是**在主业务服务和从业务服务之间加了一个可靠消息服务,**但是这个可靠消息服务可不是在请求什么MQ之类的东西,而是将消息放在数据库里的

主业务服务的try、confirm和canel操作都调用可靠消息服务,然后可靠消息服务在try阶段插入一条消息到本地数据库;接着主业务服务执行confirm操作,可靠消息服务就是根据之前的消息,调用从业务服务实际的业务接口;如果要是这个调用失败的话,那么主业务服务发起cancel,可靠消息服务删除自己本地的消息即可

这种方案大家可以看到,其实说白了最大的优点,就是不需要从业务服务配合改造,提供try、confirm和cancel三个接口了。那要是用了这种方案,就可以用可靠消息服务替代各个从业务服务提供TCC三个接口了

补偿性TCC解决方案

这个其实是跟通用型的TCC方案类似的,只不过从业务服务就提供俩接口就ok了,Do和Compensate,就是执行接口和补偿接口,这种方案的好处就是折中一下了,不需要从业务服务改造出来一个T接口,就是锁定资源的接口,只需要加一个补偿接口,如果业务逻辑执行失败之后,进行补偿

这样就可以少做一个接口了,但是因为没有做资源的一个锁定,那么大家需要自己注意类似资金转账的余额检查之类的事儿了,还有就是补偿的时候,因为你没做资源锁定,所以要注意一下补偿机制是否一定会成功

其实说实话,这个补偿性的TCC方案还是蛮不错挺有吸引力的。Do接口,Compensate接口,不要try接口,不要锁定资源,直接执行业务逻辑,如果有失败就调用Compensate接口,补偿接口,回滚刚才的操作

实现细节

接口拆分问题

首先就是,从业务服务的每个接口都要拆分为三个接口,一个是try接口,一个是confirm接口,一个是cancel接口,也就是说要提供分布式事务实现的业务接口,自己就要考虑好这个,要提供3个接口

try接口里,一般就是预留资源,比如说经典的资金转账,卡掉一些锁定资金,你要是不这么干,万一别的分布式事务给你干掉了一些资金,那你实际执行confirm的时候一旦检查资金余额就会发现转账失败,余额不足了

有些接口,没有资源锁定的操作,try接口就留空, confirm就是原来的业务方法,cancel接口,要提供回滚的方法,就是把try或者confirm里的操作给他回滚了

就比如说,如果是try阶段,资金服务的try成功了,资金被冻结了24块钱,结果订单服务的try失败了,主业务服务就会通知回滚,调用资金服务的cancel接口,就要检查一下lock_amount字段里的值,将里面的24块钱转回到原来的amount字段里面去

confirm阶段,资金服务,都把24块钱从id=1的账号里转移到id=2的账号里去了,lock_amount也扣减掉了24块钱。结果积分服务的confirm失败了,整个分布式事务回滚,调用各个接口的cancel接口

资金服务,就变成了需要将id=2的账号的amount字段扣减掉24块钱,给id=1的账户增加24块钱

接口的特殊情况
  • 空回滚:那要是try阶段,比如网络问题,人家压根儿没调通你的try接口,结果就认定失败,直接调用你的cancel接口,咋办?所以你这个时候啥都不能干
  • try回滚以及confirm回滚:try阶段如果执行了,但是其他服务try失败了,那么会调用cancel来回滚,你要可以回滚掉try阶段的操作;confirm阶段要是你执行了,但是有别的服务失败了,此时你就要回滚掉confirm阶段的操作
  • 倒置请求:比如说人家调用try接口,中间网络超时了,结果认定失败,直接调用cancel空回滚了;结果过了几秒钟try接口请求到来,此时咋整呢?尴尬了吧,你要在这个时候不允许执行try接口操作;同理啊,confirm请求超时了,结果都cancel掉了,但是过了几秒请求来了,让你confirm,你能干这事儿吗?
接口的幂等性保证

你有没有考虑过一个问题,就是try、confirm和cancel都可能被多次调用,所以无论怎么样,你都得保证这几个接口的幂等性,分布式接口幂等性那必须依赖第三方的中间件来实现,可以考虑使用经典的zk,zk非常适用于分布式系统的协调类操作。所以一个接口对同一个参数调用,只能调用一次,保证幂等操作

TCC分布式事务框架

我们的主业务服务那块,那必须得用tcc事务框架,不然各种接口调用,还有就是业务活动管理器,难不成都大家自己来写代码搞????那就废掉了啊!所以必须要选用一种tcc分布式事务框架,来实现主业务服务的各种try confirm concel控制逻辑,同时实现业务活动的管理

ByteTCC方案

全部要写三套,每个接口都要拆分为3个接口,Try、Commit、Cancel,一个接口,chooseCandidate接口,tryChooseCandidate,commitChooseCandidate,cancelChooseCandidate,每个接口要写3套逻辑

try里面,可以在数据库添加一批评审员,状态都设置为INVALID,无效的;commit,就可以把他们的状态设置为VALID;cancel,就可以把插入的那批评审员给删除了

分布式事务这个技术以及思想,过去在国内一直没有受到重视的,大厂还是小公司,只要你不是金融级跟钱直接相关的系统,普通的互联网系统,哪怕是订单系统,都不会上分布式事务。

Seata方案

Seata,支持多种分布式事务方案:TCC、XA、AT、Saga。

相对TCC而言,你不需要写TCC三个接口的,你的业务代码,就跟以前是一样的,就是一个接口,你的接口以前是什么样子的,现在也可以是什么样的,接口都没有任何的变化的话,分布式事务到底是怎么做的。

Seata 包含TC、TM、RM三个组件

  • TC,Seata自己独立部署的一个server,他用于全面的管理每个分布式事务
  • TM,用于对单个分布式事务进行管理和注册
  • RM,是对一个分布式事务内的每个服务本地分支事务进行管理的

分布式事务框架的角度来思考,TM这个东西他是怎么运作起来的

TM是可以基于一个注解来进行驱动的,SpringAOP的切面机制

Spring技术体系里,还有一些其他的拦截机制,监听机制,回调机制,完全可以针对TM设计一个注解,@ShishanTransaction,加在你的举报服务的Service组件的方法上,依托Spring的机制去对方法调用做一个拦截,如果你发现这个方法加了你指定的分布式事务的注解

**提取一下本次请求里带的一些请求头或者是请求附加的内置参数,**有没有一个全局事务id,xid,这个带着@ShishanTransaction注解的方法,他其实是一个分布式事务的起始方法,TM这样的一个组件的业务逻辑就可以开始运作起来了。引入依赖,还会在spring boot配置文件里配置一下分布式事务的一些配置,分布式事务server端的地址,暴露出来的都是RESTful API接口,基于HTTP请求就可以了。

**TM如果要找TC注册一个全局事务,**此时就可以通过HTTP通信组件,发送HTTP请求到指定地址的TC server的接口就可以了,TC server可以注册一个全局事务,生成一个唯一的txid,返回给你的TM

RM拦截你的本地数据库的操作

代理你的数据源,操作数据库,必须要有一个数据库连接,JDBC接口规范里就是一个Connection对象,数据库连接池,Druid、C3P0、DBCP,维护一个数据库连接池,一定会从数据库连接池里获取一个数据库连接。

依托这个数据库连接去对数据库执行增删改的操作, 你可以针对你的数据库连接去做一个代理,也就是说,业务系统拿到的数据库连接是被你代理过的,他基于你的代理数据库连接执行增删改操作,代码会先执行到你手上,此时你就可以做一些操作了。

增删改的语句,执行一些查询,DELETE语句,UPDATE,生成一个逆向的UPDATE语句,想要把一个字段改成1100,900,生成一个INSERT语句。**在一个本地事务里,让他执行增删改,把你生成的undo log插入到数据库的undo_log表里去,发送HTTP请求到TC去注册一个分支事务,提交本地事务,把增删改操作和undo log插入都放一个本地事务里,**他们会一起成功或者失败

Open Source Agenda is not affiliated with "Yufeng0918 Spring Cloud Learning" Project. README Source: Yufeng0918/spring-cloud-learning
Stars
37
Open Issues
0
Last Commit
3 years ago

Open Source Agenda Badge

Open Source Agenda Rating