当前位置: 首页 > 科技观察

老码农说说后端的情况

时间:2023-03-18 17:46:26 科技观察

折腾了一个月,终于分开了。原有的订单模块、库存模块、积分模块、支付模块……已经改造成独立的系统。主人给这些独立的系统起了一个时髦的名字:微服务!有些微服务是主人的心脏,“霸占”了一台或多台机器,像我的积分模块,哦不,是一个积分系统,不受欢迎,只能委屈地和其他几个家伙合用一台机器。师傅说,我们现在是一个分布式系统,大家要共同完成原来的任务。原来大家生活在同一个JVM中,模块之间有直接的函数调用。现在大家都提供了基于HTTP的API:如果要访问别人,需要准备JSON数据,通过HTTP发送过去。处理后,发送JSON响应。好麻烦,连最简单的通信都要跨网。当我反复提到这个网络时,我感到很生气。想着大家都在同一个进程,调用速度爽。现在好了,一个慢如蜗牛,一个不靠谱,时不时就会出错。30毫秒前,点单小哥调用我的接口给一个叫U0002的用户加200分,我开心的执行了。POST/xxx/BonusPoint/U0002{"value:200"}然而,当我想告诉订单系统积分的调用结果时,却发现网络断开,发送失败。我应该怎么办?反正我觉得已经执行了,算了!但是点单的人对我这边的情况一无所知。他认为可能是我这边搞错了,于是又打了同样的电话。.对我来说,这个新电话与上一个电话无关。(别忘了,HTTP是无状态的)老老实实的再执行一遍。结果可想而知,用户“U0002”的积分无缘无故增加了两倍!点餐员说:“这不行,你要记住我已经发起调用了,这样就没必要再执行第二次了!”“开玩笑!HTTP是无状态的,我怎么记录你之前的电话?”“我们可以加一点state,每次调用,我都会发给你一个TransactionID,简称TxID。你处理完之后,你需要发这个TxID,UserID,积分等信息保存到数据库中。"POST/xxx/BonusPoint/U0002{"txid":"T0001","value":"200"}我说:"这个有什么用?database啊,如果看到已经存在相同的TxID,说明之前已经执行过了,就不用重复了。如果不存在,则实际执行。”这是个好主意,虽然我增加了一点工作量并且需要一点额外的存储空间(正好借此机会要求更好的服务器!),但它有一个很好的特点:对于相同的TxID,无论如何多次调用,执行效果就像执行过一次一样,不会有错误。后来我们才知道,人类把这个特性叫做幂等性。一般来说,在后端数据不变的情况下,读取操作是幂等的,无论读取多少次,结果都是一样的。但是写操作不同,每次操作都会导致数据发生变化。要让一个操作可以多次执行而没有副作用,那一定是我得想办法记录这个操作是否被执行过。如果我错过了执行,我会告诉你新的API:一定要传给我一个TxID,否则别怪我没处理!这天,接到了两个HTTP调用,**第一次是这样的:POST/xxx/BonusPoint/U0002{"txid":"T0010","value":"200"}于是我愉快的执行了,保存了交易代码T0010。然后第一次第二次又来了,和第一次一模一样:POST/xxx/BonusPoint/U0002{"txid":"T0010","value":"200"}我查了T0010,数据库已经存在,我只是知道,我不需要再处理了,直接告诉对方:处理完成即可。没想到很快就有用户抱怨:为什么我加了两次积分(每次200),实际上只加了一次?这一定不是我的错,我这边没有任何问题,一切都按设计执行。我说:“刚才是谁打的,查一下通话记录吧!”查了调用者的日志,发现这两个调用是两个系统发出的!巧合的是,这两个系统生成了相同的TxID:T0010,这导致我认为是对同一个调用的两次尝试,而实际上这是两个完全不同的调用。事实上,TxID是罪魁祸首。可见,这个TxID在整个分布式系统中不能重复,必须是唯一的。如何在分布式系统中生成分布式ID作为唯一ID?订单小哥说:“很简单,我们可以用UUID,里面包含了网卡的MAC地址,时间戳,随机数等信息,在时间和空间上保证了信息的唯一性,一定会不可重复。”无需任何远程调用,即可在本地轻松生成UUID,效率极高。844A6D2B-CF7B-47C9-9B2B-2AC5C1B1C56B我说:“只是128位的数字和字母比较乱,不能排序,也不能保证递增的顺序(尤其是数据库中,有序的ID是更容易确定位置)。”众人点头,UUID被拒绝,MySQL提议:“你们忘了我吧!我可以支持auto_increment列,自然ID,战友们,顺序绝对可以保证。”“啊?使用数据库?如果你罢工怎么办?我们应该做什么?我们没有身份证可以用,我们也无能为力!”想到那个慢条斯理的老头,让所有人都依赖他,将生死大权托付给他,众人都有些不高兴。Ngnix说:“如果怕他罢工,可以多弄个MySQL,比如2个。第一个的初始值为1,每增加2,它生成的ID就是1、3、5,7......第二个初始值为2,每次加2,生成的ID为2,4,6,8,10...再做一个ID生成服务,如果一个MySQL罢工则访问另一个。”“如果这个ID生成服务也死了怎么办?”有人问。“那你可以部署更多的ID生成服务,这不就是你们微服务的优势吗?”Nginx问道。Ngnix有利于负载均衡。这个方法相当巧妙。它不仅提高了可用性,而且保持了ID增加的趋势。“可是,我每次需要一个TxID,都要访问一次数据库,这多慢啊!”点单的人说。负责缓存的Redis说:“不要每次都去访问数据库,学我的,在内存中缓存一些数据。”“缓存?怎么缓存?”Redis说:“每次访问数据库,都可以得到一批ID,比如10个,然后保存在内存中,这样其他人就可以直接使用,而不需要访问数据库。当然,数据库需要记录当前的***ID。”假设初始***ID为1,得到10个ID,即1,2,3...10,存入内存,此时***ID变为10。下次再取10个,也就是11、12、13……20,***ID变成20。我说。Ngnix说:“这种事情很简单,再增加一个MySQL,建立一个一主一从的结构。嗯,如果数据没有及时从Master复制到Slave,Master就会罢工.这时候Slave中的MaxID就不是唯一的了,那接下来可能会有问题,说不定可以创建一个双主结构……”唉,创建一个分布式唯一ID好复杂啊!Ngnix在那里嘀咕道,大家没注意到有新服务上线了,一上来就说:“大家好,我是雪花……”作者微信公众号正在打码获取授权】点此查看阅读该作者的更多好文章