为了解决在分布式系统中需要对某个资源进行全局的一个非重复ID生成,所以有了分布式ID这么一个概念

 

在分布式应用下,像分库分表的这种场景是很常见的, 这个时候如果还是用数据库本身的自增的话,那多个数据库ID肯定会重复。

比如订单表由于数据过多,分到了多个数据库中存储的话,那么这个ID要如何生成呢?

还是以原来的逻辑进行自增的话,那就会出现这种情况:   数据库A里边有订单1、2、3 , 数据库B里边也有个订单1、2、3, 这在业务逻辑上就冲突了。

 

 

那么这就需要有一个分布式ID生成系统

而一个分布式ID系统需要满足的有这么几点:

1、高可用, 所有的业务系统都需要调用这么一种id生成器来生成id,这玩意千万不能挂

2、趋势有序, 无论是啥关系型数据库,在一个字段上加索引肯定是有序的更快

 

 

解决方案:

 

1、UUID

这个建议不用管,虽然好处多(无脑)。但是代价太大了。

 

主要就是在DB里边用UUID太长了,空间又占得大,你建的索引越多, 影响越严重。

UUID还是非自增,无论是增删改查相对于自增id的比较大小来说都要慢很多,尤其是在那种取数据段的场景。

 

 

2、数据库ID自增

那为了保证高可用,一个DB肯定是不行的,那必须得集群才行。

就用mysql举例:

主从:  那肯定是不行的。 slave同步master的数据会出现延时,在master挂掉时可能出现获取ID重复的情况

主主:  如果只是普通的使用自增策略,那这个也会有问题。假设有A/B两个数据库,都是1、2、3自增。那么每个ID都会是重复的。

 

这个时候就要引入一个步长的概念。假设设置MySQL自增策略步长为2。然后其中一个从1开始,另一个从2开始。那么A/B两个数据库自增的ID就会是1、3、5/2、4、6  ; 这样子就很美好了。

可以用这个语句设置, 多个库都用不用的起始值和相同的步长就行了。重要的是有多少个DB就要有多长的步长

 

 

可是这样也存在一个问题,那就是无法扩容

用多主数据库来进行ID自增的话,只要设定好了,就再也无法变更了。以我上边的来说, 想要原基础上添加一个C数据库来进行ID自增,那是做不到的。

这样子就丧失掉了拓展性。虽说在一般的企业应用中这样子一个架构也确实够了 。

 

当然,每次生成ID都去走数据库也怪麻烦的,所以一般也有个ID生成服务,各个应用只需要请求那一个服务就行了。由那个服务去获取ID,达到一个解耦的目的。

这个服务为了高可用也要集群才行,不过这个集群只是为了防特殊情况导致服务挂掉 ,  他们请求的数据源都是一样的。

 

既然可以用MySQL这种关系型数据库,那肯定也可以用Redis这种基于内存的NoSQL数据库来实现这个。

那这个效率不是比关系型数据库高不少?

使用Redis的INCRBY命令就可以从容的在值上增加指定的数字,设置起始值、自增步长。Redis也可以很轻松做到嘛。

但是用Redis的话有一个点要注意:

分布式ID首先得考虑高可用,所以得考虑持久化的问题。Redis支持RDB和AOF两种持久化的方式。

而我们只能用AOF,对每一条写命令进行持久化,因为RDB持久化相当于定时打快照来持久化,如果打完快照后,还生成了多次ID,没来得及做下一次快照的时候Redis挂了,重启Redis后就会出现ID重复。

 

3、号段

其实号段也算是数据库ID自增的一种形式。这里只是单独拿出来说

不过就是将原本的一个个获取改成了批量获取,比如直接获取这个数据库里边1 – 10000的ID (只要查出一个数据段落和步长就够了, 自增在自己服务里边自增)。存在分布式ID服务的缓存里边。

这样就免得一次次去和数据库交互。

而且这种方案不再强依赖数据库,就算数据库不可用,那么这个分布式ID服务也能继续支撑一段时间。缺点就是他挂掉重启的话,会丢失一段ID。

之前说过,分布式ID服务的数据源都是一样的。而正是因为数据源一样, 在号段模式里边可能出现那种两个ID服务同时请求一个数据库的同一段数据的情况。那这就很麻烦。

所以这个号段模式需要上一个乐观锁才可以,关于乐观锁可以看我这篇文章:  [浅谈CAS与乐观锁]  http://skypyb.com/2019/08/jishu/961/

 

当然,这种轮子就别自己实现了,人家早就给你安排的明明白白的,只管拿过来用就OK

比如滴滴开源的TinyId:   https://github.com/didi/tinyid

 

4、SnowFlake

这个就是分布式ID界的头头, 著名的雪花算法

对,他是一种算法,是twitter开源的分布式ID生成算法,他和我上边说的数据库生成分布式ID机制完全不一样,最核心的就是它不依赖数据库。

不依赖数据库,这也太美妙了。要知道在软件中每引入一个额外变量都会使架构的复杂度几何上升,SnowFlake无疑就是那分布式ID的最佳实现者。

核心思想是:

分布式ID固定是一个long型的数字,一个long型占8个字节,也就是64个bit,那这64个bit的每一位分配如下: 

 

第一个bit位是标识,在java中由于long的最高位是符号位,正数是0,负数是1,由于一般生成的ID为正数,所以固定为0。

时间戳部分占41bit,这个是毫秒级的时间,这个会储存时间戳的差值(当前时间-固定的开始时间),因为这样可以使产生的ID从更小值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年

工作机器id占10bit,这里可以很灵活,比如,可以使用前5位作为数据中心机房标识,后5位作为单机房机器标识,可以部署1024个节点。

序列号部分占12bit,支持同一毫秒内同一个节点可以生成4096个ID

 

根据这个算法的逻辑,只需要将这个算法实现出来,封装为一个工具方法,那么各个业务应用可以直接使用他来获取分布式ID,只需保证每个业务应用有自己的工作机器id即可。直接降低大量复杂度。

关于这个算法的具体实现,搜索引擎搜索SnowFlake,一堆实现。直接复制到自己的项目里边用就完事了。

 

关于雪花算法,其实也有一个小缺点。那就是要为每个机器指定一个机器id, 几十台几百台机子还好,更多那就很操蛋了。

所以有大厂对SnowFlake进行了改造,让其可以自动分配。比如百度的 uid-generator:  https://github.com/baidu/uid-generator

 

结语: 

分布式ID这玩意,其实是个非常简单的东西。

我相信任何一个有点分布式经验的开发者都可以在短时间内理解并在生产中实践。

我就想说一句: 其实用哪个都无所谓。

你要是真的业务有什么东西每天几亿个ID自增,估摸着公司也是流水多多了,轮不到普通程序员操心这玩意。 要是就一普通软件,用户几万几十万的。搞两台Redis来incr也是一样的…

阅读全文

说完了分布式事务最核心的思想(2PC) –> [探秘分布式解决方案: 分布式事务——从核心思想之2PC(两阶段提交)开始]    http://skypyb.com/2019/11/jishu/1149/

 

那么现在进入到更加复杂的场景。像这种跨库调用之类的,一线互联网公司早就不玩这一套东西了。这都9102年了,上来就是微服务架构。

我这么多服务,你整个啥跨库调用呢?一个服务可能同时调用多个其他的服务。这多个其他的服务中都要执行SQL语句,修改落实到服务所对应的数据库之中。

 

 

 

微服务下的服务调用

还是以一个订单支付的流程为例,只不过由之前讲解2PC的单机多库示例改为了微服务示例。

微服务架构下,用户下单的流程将会变成这个样子:

微服务下的订单服务

 

这里边每一个 “服务” 都是一个系统,都是会单独部署的。

可以见得这个复杂度上升的非常大。比如说我用户在下单购买,调用了订单服务的接口,此时订单接口的方法内逻辑不单单只是订单服务自己操作自己的数据库。

订单还得去调用其他的服务的接口。比如库存、积分、仓储 等。

 

那么,在微服务架构下,服务之间互相通过接口进行通信,如何才能在一个 “事务” 中保证数据一致性?

如果出现这种情况   比如订单支付状态已经修改完毕、库存扣除完毕,但是加分却失败了, 这样子咋回滚?

 

那么就轮到新的解决方案出场了。那就是 TCC (Try-Confirm-Cancel)

TCC全名为 两阶段补偿型方案。是现在的互联网大厂主流的分布式事务解决方案之一。而TCC,就是脱胎于2PC,是作为2PC思想在微服务领域的子集被使用着。

所以说为啥2PC是核心思想,不懂2PC,那TCC是肯定明白不了的。 要是懂了TCC的原理,那么前置条件必然是已经懂了2PC。

 

 

 

TCC原理思想

而TCC的原理相比于2PC,有这样一种行为上的变更:

2PC的行为是:  1、预提交 2、实际提交

TCC将其改为了: 1、预留资源 2、确认资源

 

什么叫做预留资源?什么叫做确认资源?

还是以上面我画的的订单活动图为例子,预留的资源,那就是 订单状态、库存数、积分数、出库单

那我们在TCC的方案下进行事物的话,第一阶段就是将这几个资源给他锁定住。然后在第二阶段进行一个资源的确认,即提交/回滚

你如果知道2PC的思想,我想这完全不难以理解。下面是TCC方案的状态图:

TCC-STATE

 

在TCC方案下,在第一阶段中的修改,修改的并不是实际期望的结果,而是期望结果之前的一个中间值。

所以,每个服务都要提供两个接口,一个用于第一阶段的资源预留行为,另一个用于确认/回滚。

(其实你也可以就搞一个接口,然后参数传个对象,由对象中的属性来决定做什么操作也不是不行)

 

比如更改订单,我不可能一下就将这个订单给改成已支付。毕竟现在这个方法还跑到一半,不能完全确定之后的业务是否正常进行。

所以,在此取了个中间态,将订单改为 “更新中” 。

库存、积分等服务也和订单相同。 当所有的接口均调用完成后,即所有的资源预留操作均完成。则在这时进行确认行为。

如果说所有的操作都返回了成功,那么就将订单状态改为已支付,然后将冻结的库存真正减掉、积分真正加上、草稿出库单改为生效出库单。 (confirm)

而反之,若是其中有任意一个接口失败了,那么就对应执行 calcel 操作,将所有改变的状态通通回滚。

 

 

 

如何预留资源

而有的人可能会好奇,什么冻结库存之类的玩意如何实现?

这个其实也很简单,比如你库存表本来只有一个库存数字段。但你现在要用这种TCC方案了,那么就给他再加上一个冻结库存数字段

本来来说,你某个商品库存数是10 , 那么用户再下单完成支付的这个流程中,假设买了1个商品,那么原来的逻辑就得改一下。

正常你买1个商品,那库存就是10-1=9 咯,所以将库存数改为9。而在TCC下,你就不将他改为9,而是在冻结库存数上+1,即数据库状态变为 库存数10,冻结库存数1

那么在其他的用户购买时,你返回给前端的库存数应该就是 库存数10 减去 冻结库存数1 等于现在只有9个库存能够被使用。

用户他不知道冻结不冻结,但是你的程序里边是知道的。用这种逻辑那是一点没有问题。

在你支付接口里边所有的预留操作成功之后,那么就如上所述,将库存字段真正减掉,也就是将10库存真正减1,将冻结库存还原,这是第二阶段该做的事情。

 

说到这里,总没人问第二阶段要是某环节错了怎么办吧? 要是有这疑问的那肯定是没看我上一篇分布式事务文章。

这里也简单的讲一句:本身失败的概率就极小,要是操作失败了肯定是会重试、并且记录事务日志的。状态一个不对劲就由人肉团队、定时任务来进行补偿。

 

 

TCC讲解完毕,那么到底TCC和2PC有着哪些区别呢?

TCC其实说白了,其实也和两阶段提交差不多。就是要开发新的接口。

毕竟是在微服务架构下,复杂度上升很多,要在这种架构下来进行分布式事务本身就要比单机应用考虑的方面更广。

而TCC和2PC之间最主要还有这么一个区别:

在很复杂的、跨了很多数据库的业务场景下,2PC的锁粒度非常大。因为所有的数据库锁均被应用所持有,直到事务完成。性能会很低,根本顶不住大并发。

而TCC的话,从整个系统上来看,吞吐量会好非常多,因为在TCC方案下的的复杂调用链之中,实际上每个数据库操作,都是commit了的,换而言之就是锁释放了并不是一个加在全局的大锁,而是将锁粒度控制在每一个系统的每一个接口里。嘿,有没有想到ConcurrentHashMap。

 

 

 

说在最后

如果将TCC这种解决方案称之为 “术” 。那么2PC就是所谓的”道”

只要你学会了”道”   那么一切基于此道的术相信都难不倒你了。

当然,像这种TCC的实现肯定是有现成的给你用。毕竟自己来搞这么一个框架也是蛮复杂的,不推荐自个造轮子。

业界开源实现也挺多比如ByteTCC、seata、tcc-transaction等,使用方面就不贴了,基本都是配置。

而除了TCC,其实还有像是最大努力通知型方案可靠消息最终一致性方案这些基于最终一致性的分布式事务解决方案。也可以网上搜搜了解一下。

 

阅读全文

像我这博客也一直更新近两年了,其实在IT这个圈子里可以说是比较少见的。

虽说在各种程序员论坛中,总是隔三岔五就有人加博客友链啊、问用什么博客建站工具啊。

但是只要真的点进去看一看就会发现。90% 的博客都是只有10、20篇左右甚至更少的。

不由得令人深思,到底是为什么,一个博客那么难以坚持下来呢?

 

其实原因我思考了一下,也很简单很直接。主要就是写博客太累了

写博客本质就是将自己的知识提炼出来、以尽可能通俗易懂的方法、以文字的形式,将其叙述出来。

写作的时间就是个问题,一篇博客的完成少说1、2个小时,多则10小时+,这还是单论写作的时间。

除开写作的困难以外,写博客这又有一个前置条件,既然是将自己的知识以文字的形式表述,那么首先你得有知识才行。

那么知识从哪里来呢? 想要获得知识,那只能学啊!

偏偏,主动学习是很多人做不到的。毕竟每天上班都这么累了,回到家还要学习?那不是折腾人么。

稍微苛责一些、带着恶意的话来说那就是: 大部分人都是懒狗,学是不可能学的,下班只有打打游戏看看剧日子才能过下去的样子。

 

诚然,不可能每个程序员都是自己想做这行才选择这份职业。

更多的可能是随波逐流,自己也不清楚为什么就去当了程序员。最常见的就是 “大家都说程序员好,所以我报了这个专业”、”程序员钱多,搞起来”、”我也不知道为什么,反正这专业是xx(某亲戚)推荐的”、”我喜欢打游戏,所以选了个能天天玩电脑的专业”

但是既然当都当上了,那么多少还是要有一些职业素养。

虽然职业素养这个词说起来给人很装的感觉, 有人可能会觉得: 不就是个码农?CRUD boy而已,每天搬砖,有什么职业素养之说。

不否认有许多人是CRUD boy 。 因为码代码对于很多人而言真的只是一份工作而已,没有其他的意味。

但是,我相信着,也确信着。是有着相当数量的、对编程真正怀抱热爱的人在做着程序员这份工作的。

 

那么所谓的”职业素养” 又从何而体现?

以我主观的眼光来看,程序员的职业素养就体现在你是否能够实现业务; 代码码的是否优雅;以及 是否能为这个圈子、这个社会、这个世界创造价值。

呵呵,一下就上升到什么圈子、社会、世界的层面了。看起来很像中二少年的自大话语。为世界创造价值?普通人光是为自己创造价值就已经竭尽全力了。

而我想说的是,程序员这个职业,与其他的职业有着本质上的不同。

其不同在于几个关键点:

1、接受信息的渠道比普通职业要更多。接受的信息多,意味着眼界广,更加理性。每个人接触的信息都不同,意味着大家不同质化、性格各异。这是一个群体是否容易被奴化的核心要素。也会使得群体成为”乌合之众”的概率下降。

2、 一定程度上不受资本遏制。在程序员的世界里人就是最重要的生产力。与其余的工科不同,很多工科需要依赖于生产器械。没那个几千万几亿的设备,你这个什么高级工程师、专家就是个屎。资本家可以肆意玩弄你,因为你离开了他你将一无所有。而程序员有着反抗的资本,电脑就是生产工具,只要脑子还在,惹不起还是躲得起的。

3、每一行代码,都可以产生价值。这个是及其关键的一点。 为何说信息革命是第四次工业革命?看看现在的世界吧。互联网已经渗透到各处了。你会发现,其实在不知不觉中,不少程序员已经将这个世界改变了。

 

但是很遗憾,到底有多少程序员是对行业有着热情的,又有多少只是单纯用来吃饭的。这个比例我还不得而知。

因为网络中看,有激情的实在是太多了,而在现实中却发现好像也不是这么回事。毕竟网络会将人的情绪放大、也会将你最想看的展示出来。所以我是不大相信网络中的所见所闻的。

就算如此,有几点我还是可以很确定的。

那就是光 实现业务代码优雅 就是部分程序员难以跨过的沟壑了。

实现业务,就是你吃饭的能力。起码需求下来能够实现。而作为需求来说,[点击用户可以查看用户的名字] 是需求。[在百万级QPS下能够保证系统不挂] 也是需求。而就算是普通CRUD的需求也是会有一些人难以做到的,所幸,一般都可以靠时间解决。混个三五年,怎么着最次也是个CRUD熟练工了。

在程序中,除了业务的实现以外,又有一个至关重要的点就是应用可维护性和可拓展性。其直接导致了后续业务的开发是否顺利。这里就涉及到了代码的优雅。

说实话,在我短暂的程序员生涯中,总是能够看到那种7、8层循环/判断嵌套的代码。或是一个方法好几百行还没注释的代码。更过分的是代码格式扭曲成麻花的,格式化快捷键都不愿意用一下。对于这种屎山真的没什么好说的,跟主动写出这种代码的程序员讲什么高内聚低耦合、设计模式、开闭原则、迪米特法则都是放屁。

避免屎山诞生的唯一方法就是一开始就将代码实现的优雅。在其他人的屎山上你就算在努力的铲屎,也改变不了屎山是由屎构成的事实,除非将屎山推倒(重写)。所以遇到需要你自己在屎山里畅游的需求,就好好享受就对了,佛系一点,别去重写,因为你不知道你重写了会发生什么,只能尽量理解屎山的构造,然后在其之上添上新的东西,大部分情况下,你也只能在屎山上面拉屎。

我坚信优雅代码的诞生是一定要编写代码的程序员主动注意,主动去让其变得优雅的。可惜这点相比于CRUD来说难度又更上了一个层级。

一个拥有多年经验的软件开发工程师写出的代码如果像一坨屎,哪怕实现了业务需求,我也只能说一句:菜比。

 

那么为这个圈子、这个社会、这个世界创造价值的方法,在何处?

假设你是滴滴/美团的开发工程师,你写的代码在百万人、千万人的手机上发光发热。这个时候你说一句你为这个社会的改变做出了一份贡献,我想应该不过分吧?

假设你是Google、Apple、Microsoft等公司的职员,你参与创造的东西帮助了世界上数亿、数十亿的人,你能不能说自己为这个世界做出了贡献?

当然,我上边都是在放屁。毕竟不是每个人都能进这种水平的公司。

并且也不是所有的程序员都是普通打工仔,从程序员变成企业家的也不少,他们可以用更高效率的手段来为社会创造价值创造财富。

我想说的是,以一个普通的程序员而言,写出的每一行代码都是可以被高效的复用的。那么在你写出的产品被他人使用,被他人需要,解决了他人的痛点时,你就为他人创造了价值,而且你能帮助了多少人完全取决于用户量。

当怀抱着这样的心情,这样的思维去敲代码的话。所谓的”自我实现” 的需求也被完成了。

 

而程序员还有一条绝佳的价值创造渠道。那就是开源。

自己写出的代码被同样的程序员拿来使用,以此影响到更多的用户。没有什么比这更令人激动了,

一个没有自己github的程序员,肯定是会让人怀疑其水平的。因为基本可以认定: “此人不会在休息时间写代码,缺少对编程的热情。”

而除了开源外,那就是写作了。并不是每个人都能写书,但是我觉得起码每个人都可以写博客,因为这几乎没有门槛,只取决于你想与不想。

写博客只需要做到两件事情: 1、持续学习  2、坚持写

持续学习是为了持续提升自己,坚持写是为了将自己提升的知识给展现出来。

 

 

并且,博客不只是能够为他人创造价值。而是可以实实在在为自己创造价值的。

最基本的就是可以作为求职筹码,很多公司对你的个人造物是很看重的,这个造物可以是开源应用,也可以是博客。

并且,由于博客是你的作品,所以可以作为你的个人标识物。在你的博客越来越多访问,帮助了越来越多人之后。它就会成为你一个互联网世界中的身份。至于这个身份的作用嘛……

而且博客本身作为一个帮助他人获取知识的渠道。这可以实现你的自我价值。就拿我的这个网站来说,现在2019年11月。平均PV是1000-3000 ,UV100-300 已经持续了很久了。你要说我博客这两年来没帮上一些人吧,那我是不信的。

在这所有之上,最重要的是,写博客本身就是一种学习

来看图提问:

学习金字塔

问题来了,写博客作为一种知识获取行为是在第几层?

答 :  所有的博客都是在教授给他人;并且大部分在教授之前,都会进行实践 ;  而在实践之前会以阅读、视听的方式进行学习,为了获取实践所需要的知识。

你觉得,以写博客的形式来学习,效率高么?

 

不管大众意义上的程序员群体们写不写博客,反正我是在写。今后也会一直写下去。

注: 此篇博客出炉耗时2天,总共3小时以上,所以我在最后还是不得不吐槽一句 写博客真tm累 !

 

阅读全文

为什么要有分布式事务?

本地单机事务,有点工作经验的肯定不陌生,主要用于处理操作量大,复杂度高的数据,一般都由数据库自己实现。

开启一个事务,进行了多个对数据库进行更新(增删改)的语句后,可以自由的选择 commit rollback 来结束事务。

可以从根源上保证多个数据集合的同步。

 

比如一个订单系统,用户支付订单后,订单表的订单状态被改为已支付,同时,库存表中商品剩余数量减一。

若是没有事务机制,在订单库状态修改成功后,由于库存不够减、网络波动、程序bug、数据库挂掉等原因导致数据库中库存减一的SQL语句执行失败; 那这样到最后就会出现实际库存早就没有了,但用户却支付成功的重大事件,即超卖了。

所幸。只要你开了事物,那么事务的原子性确保让你多个语句作为一个原子操作来执行,要么同时成功要么同时失败,在库存-1 失败的同时你订单修改也失败了。所以在单机简单应用中一般不存在这种问题。

 

可是,在稍微复杂一点的场景中,数据库自带的事务机制也有着无能为力的时候。

仍然是一个订单系统,这回场景为由于系统用户激增,入驻商家也越来越多。在日渐庞大的数据量下出于性能考虑决定对数据库进行拆分,由原来的一个库,拆分为一个订单库、一个库存库

你的程序里边,一个下单方法中会执行两个库的SQL语句,在这样的逻辑下,普通的事务还有用么?

就像这样:

订单服务-1

 

那肯定是没有用的。

这种场景中必须要实现跨库事务才能够满足企业级生产需求。

 

要如何实现这种跨库事务? 这里引出分布式事务中的重点思想:阶段提交

 

两阶段提交协议(2PC)

2PC 引入一个事务管理器来作为事务的协调者。

在程序执行复杂数据操作时,开启事务、SQL语句执行完后,先不进行commit。先将执行的结果告知给事务管理器

这个时候,再执行其他数据库的SQL语句。当这个事务中,所有的SQL语句全部执行完毕后,即事务管理器已经得到所有此次事务中SQL语句的执行结果后。

再集体通知所有的数据库进行commit 或 rollback。

 

以下单流程来模拟的话。

前置条件:已引入事务管理器

此时,用户一个支付请求过来。先执行订单库的修改语句,将用户的订单改为已支付。

执行成功后,不提交事务。再开启库存库的事务、执行库存库的库存修改语句,将库存减一。注意,这是两个不同数据库的不同事务。

这个时候数据库的事务隔离机制就将数据锁住了,包括订单库的订单、库存的数量。但是还没有提交。

在每个SQL执行完成之后,都会将执行完成的信息提交给事务管理器。

那么,当订单、库存都修改成功,事务管理器之中都收到了执行成功信息,确认好了本次事务中的所有语句都执行完成后、立即通知所有数据库进行 commit 操作。

任何一个语句执行失败,事务管理器就会通知所有数据库进行rollback。

 

活动图如下所示:

2PC-01

 

2PC中的底层实现思想,就是将原先的单次提交操作分为两个阶段,即 1、预提交 2、实际提交

看起来是不是很简单。大道至简,实际上只要理解了两阶段提交的思想内核,那么任何分布式事务的解决方案都难不倒你了。

因为TCC、消息事务等事务解决方案,都是从2PC衍生的。

 

 

那么问题来了,2PC凭什么可以解决分布式事务问题?

有人可能也有疑问。2PC好像不是那么严谨?

比如: 事务管理器的通知失败了咋搞,假设事务管理器通知订单库的commit成功了,但是通知库存库去commit的时候失败了。那不还是有问题。

这里就必须说明一下分布式事务解决方案的重点。 那就是: 分布式事务不可能100%解决

不信你去问阿里巴巴的员工、Google的员工,去问他们:”你们公司的分布式事务能不能百分百成功呀?” 肯定不可能,起码现在9102年在这个地球上是没有哪家公司敢说自己的分布式事务是百分百成功的。

虽说分布式事务不可能百分百解决,但是可以尽量提高成功概率,比如让你的成功概率接近99.9999%,即让你的系统尽可能的高可用。

 

 

而2PC就是及其有效的提高成功率的方法

这里要先说明,这个事务管理器并不是一个单独的服务,而是内嵌在你程序里的组件。这玩意挂了你程序也挂了,所以不要操心事务管理器挂掉的问题。

以订单为例,其实最容易出现的问题主要是 库存不够减、数据库挂了、网络波动

而在使用2PC的情况下,你的所有SQL都执行成功了,但是事务管理器commit失败了,是个什么概念啊?

要知道在程序的世界里,你别说分两个阶段,你分再多也就几毫秒的时间。

在下单这里预提交成功后,可以显而易见的得出几个结论:

1、所有SQL都执行成功了,所以数据库在执行SQL语句时状态是正常的、服务器和数据库之间的网络也是正常的

2、由于库存库的SQL执行成功,所以库存够减,不可能出现库存不够的问题

 

你跟我说你预提交完,这个时候事务管理器刚通知完订单库commit成功,在提交库存库减库存commit时数据库挂掉了? 或者网络波动导致通知失败了?明明前1ms还好好的?

wdnmd

咳咳,不否认有这种概率啊。可是对于2PC来说,你通知commit失败后,是会重试的。基本上都不会有问题。

那么,要是一直重试都不成功呢?比如挖掘机在你提交库存库commit时,将网线挖断了。emmmmmm,有这种疑问也实属正常,毕竟啥情况都可能发生是不。

这种情况出现确实没有别的办法。一般来说都会记日志打log,之后要么后台通过定时任务补偿,要么人工介入

像一线互联网公司啊,都有大量的一些人工团队专门处理这种极端情况下出现的数据不一致问题,还会让研发工程师写一些什么脚本之类的去回滚、回复数据。

 

 

综上所述。回过头再品一品两阶段提交的思想。为什么说2PC可以提高分布式事务的成功概率,相信已经了然于心了。

而像是这种2PC的具体实现,那肯定也是不用你自己操心的。在java的世界中这种框架已经有不少了。比如开源框架 atomikos,就可以帮你解决这种跨库事务问题,当然,底层就是用的2PC思想做的。

至于如何具体在代码里实现,这里就不贴了。搜索引擎搜一下atomikos可以搜到一大堆。基本上在Spring里边也就是引入依赖后配置一下,然后编程式事务/声明式事务轻松搞定。还可以找一下、了解一下我这篇博客没说明(因为感觉没什么必要)的XA/JTA 接口规范等。

阅读全文

既然是一个网关。那么全局过滤器肯定是少不了的一个存在。像是鉴权、认证啥的不可能每个服务都做一次,一般都是在网关处就搞定了。

Zuul他就有很强大的过滤器体系来给人使用。

Gateway当然也不会差这么点东西。

对于SpringCloud体系来说,一切的实现都是那么的简单。那么废话不多说,直接开始写起来。

 

Gateway内部有一个接口 名为GlobalFilter,这个就是Gateway的全局过滤器接口,只要在应用中实现此接口后注册为Spring的Bean,背后就会帮你将这个实现注册到全局过滤器链条里边去。

我这里就简单的写了个模拟鉴权的过滤器实现:

虽说是鉴权,但实际上我这就是个简单的demo而已。想知道真正的Spring Security鉴权/认证怎么写?

我以前写的这个: http://skypyb.com/2019/09/jishu/kj/1050/ 应该可以帮助你。

 

看我写的这个过滤器内部实现哈,其实就是拿出Request Header中的 Authorization字段(token) 然后判断是否存在。不存在就返回错误,存在就交给链条中的下一个过滤器。

 

过滤器其实也没啥好说的,那么说说限流。

关于限流这个东西,常见的算法就是漏桶和令牌桶了,对于一个应用单机限流来说也复杂不到哪儿去。

靠着google guava包里的RateLimiter工具都能搞定大多数场景了。

不过既然人家Gateway好心好意给你搞了个限流的实现。那么还是尊重他用一下。

由于Gateway是用的Redis和lua脚本实现了令牌桶的算法,那么先导入几个需要的依赖:

 

既然是Redis,那还是先配一下Redis序列化先:

 

万事俱备,开始进行限流的具体实现了。

 

既然是限流,那么也得有个限流策略

是根据用户来限流呢?还是说根据请求路径限流?或者是IP限流?

不过这个都是由需求来决定了,我这就简单的写个根据IP来限流的。

人家也给你封装完毕了,只需要你自己实现KeyResolver这个接口就可以。

由于实现这个一般来说也就一行代码,所以我就不写个单独的类去实现了,而是直接写在配置类里边。

看,我其实只需要返回我需要限制的东西就可以了。我这里提取到用户的IP将其返回,即可实现通过ip来进行限流的策略。

不过限流相关的配置写了,那也得用起来。

这个怎么用起来? 其实直接在配置文件里配置就OK了

 

这里可以看到,除了Redis配置 ( spring.redis.** )以外。

主要就是对于Getway的配置。

在Gateway路由配置中,设置了一个filters参数。

这个是为了指定路由的各种过滤器的。这个参数也有很多种,可以参考官方讲解: https://cloud.spring.io/spring-cloud-gateway/2.0.x/single/spring-cloud-gateway.html#gateway-route-filters

我这就是指定了一个RequestRateLimiter,请求限流。

redis-rate-limiter.burstCapacity: 20     这个参数表示突发容量,即每秒可以最大通过多少次请求

redis-rate-limiter.replenishRate: 5       这个是令牌桶的补充速度,每秒往桶里边放几个令牌

key-resolver: ‘#{@ipKeyResolver}’             这个就是用上KeyResolver的具体实现了,这里用spel表达式指定我写的那个ip限制类

 

 

准备好之后将应用启动就完事了,想测的话可以用jmeter测测看,或者将请求限制写的更小一点,在网页上狂按f5也行。

 

阅读全文
EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00