背景
我所在的公司是一家做直播和视频通话APP的公司,我主要负责服务端。最开始我们的架构很简单,就是单体PHP,全部用lumen写成。Redis缓存都用的少,后端人员也只有两人。
此时最大的问题主要是慢,单机扛不住多少并发。
大概2年前想要做直播,开始引进声望、腾讯IM,但是直播房间需要大量的缓存处理,swoole开始进入我的视野。将架构拆分成了API和服务,服务使用的swoft1.0。将原来的lumen还是作为API层保留了。
此时的问题主要是curl请求依然是一条通道,导致注册IM的时候卡顿,一旦推广人数多了,会卡在注册接口。虽然swoole在后面的版本添加了curl的请求。
其次是swoft1.0还是雏形,很多高级点的功能并不成熟。大多数的业务都用的Redis在抗。
随着业务场景的不断扩大,加上GO的火热,我开始考虑将架构中需要集中运算和追求并发的模块用GO重写,于是有了对微服务的研究。准确的说还称不上微服务,只是业务有针对性地选择语言,进行了一些业务模块的拆分。
那么怎样从PHP到GO,又不影响既有业务?让我们慢慢聊。
微服务是什么
对于微服务的讨论网上已经足够多了。其实微服务是一种思想,和分层一样。
像以前,网络架构、操作系统架构,大多数采用的是分层的思想,因为大家的视野都集中在一台机器上。我们一直在考虑怎样将事情分散给不同的人,比如用户态和管态,比如网络协议的7层架构,包括MVC,都是为了将事情分散。也都是基于大家在一起。
到了现在,用户数飞速增长,单机再厉害也不可能抗住业务请求了,于是产生了一个新的观念,那就分而治之。
从横向上来看,可以将全国乃至全球的用户分片,湖北就湖北的机房,杭州就杭州的机房,这样分到每个机房的请求数大大减少。这也就是大家所说的“多活”。
从纵向来看,可以将不同的业务采用不同的技术,不同数量的集群,不同的处理方式,分而治之,比如账号服务,可以用最优的机器,最好的管理,保证服务永不中断,而采集服务,可以放到每个CND节点,将其分散到全国各地,进行打包汇总了再上传到数据中心进行分析,而数据中心采用CPU密集型的机器,进行分析和存储。这样分工明确,好钢使在刀刃上。
听上去很美好,是真的很美好么?
单体应用的不足
每个公司都是从单体应用过来的,如果有人上来就是集群和微服务,分库分表都已经做好了,那一定是土豪级公司。
业务都不是一蹴而就的,而是慢慢演化的。
我也经历过单体应用,其实作为一个人开发是很爽的,没有那么多问题,只用关心MySQL优化一下,该套缓存套一下,然后上线的时候注意下兼容,写起来好爽好快。运维起来也爽,直接上服务器看日志,PHP脚本语言,改了就生效,贼爽。
其实PHP确实是一个试错最好的语言,对于一个新项目,就是要开发快、上线快,好维护、好运维。
别上来就微服务,几个开发人员的小公司,去追求微服务、去追求中台架构,这是追求完美吗?不是,是找死。
但是渐渐的,业务量上来了,开发人员也变多了,问题就显现了:
- PHP首先就是效率问题,单机能抗的业务量太少了。对于Java首先就是编译开始变慢,IDE开始变卡。
- 开发人员的变多,导致代码风格也变多,而且代码冲突也变多了,如果是Java还涉及到编译不过的问题。
- 如果涉及到业务重构,很难,代码量的变多导致耦合性难以保持,很容易牵一发而动全身。
- 框架升级困难。无法有针对性的选择语言和技术。
- 难以扩展,可能久了就谁都不敢动了。新同事一来需要阅读大量代码,学习成本极高。如果相关负责人离职,那将是雪崩效应。
- 对于数据库和Redis是个考验,如果有一个人掌握的不好,则直接崩盘。
开始拆分
架构的演进一定是源于痛点,所以在小公司说把集群、微服务玩到很溜是不可能的,那都是实验室的玩具。如果想要当架构师,一定要在业务量足够大的公司。但是在小公司并不妨碍我们对技术的学习。
最开始由于技术不足和为了方便,我们采用了纵向拆分,就是将业务分块,按路由分组,采用NGINX反向代理,重构一个模块就上一个模块,最后实现了重构。我称为纵向拆分。
好处是:
- 模块分离了,模块A有问题不影响B,而且上线可以分开上。
- 可以用多种语言。
- 开发人员分离,减少了代码量和代码冲突。
问题:
- 公共模块多套,我们采用的一个公共库,比如用户数据、鉴权等,go直接包含这个应用。但是一旦改了这个公共库,所有项目都要打包。
- 数据库还是没有分离,一个bug拖垮全局的问题依然存在。
- 最后还是以单体的形式上线的,只是负载均衡更加灵活,运维起来其实很麻烦。
- 对于单个业务场景,无法隔离,无法降级,无法熔断,挂就是一个业务一起挂。
后来我开始考虑横向拆分。也就是业界采用比较多的方式。
就是用户中心、通话中心、直播中心这样分,一个服务只做好一件事。
其实最主要的因素是人员的增加,现在后端有5个人了,虽然还是小团队,但是对于现在的业务量可以开始探索这种重构方式了。
所带来的问题
凡事有利就有弊,没有银弹,也没有不变的架构。往往分久必合,合久必分。
天然的复杂性:
- 模块之间调用会不会有问题?账号中心挂了就全挂了。
- 日志好难查啊, 原来最多就4台机器,现在拆分后一个服务一个机器,虽然加上了docker,但是道理是一样的,日志分散了。
- 链路追踪很难,一个接口ABCD4个服务,出了问题到底在哪一层挂掉的,不知道。
- 测试困难,A同事重启了一个服务,调用它的服务全挂。好难。
- 文档变多了, 原来写个接口文档就好了,现在服务和服务之间也要写文档。
- 兼容问题,必须要统一标准,不然每个服务暴露的接口都不一样,那真的千奇百怪。
- 分布式事务问题。如果账号中心有A和B两个机器,我在A机器关注了一个人,从B机器请求到的关注数据没更新,或者在B机器取消关注,怎么保证数据一致性?其实和MySQL的ACID一样。
面对问题:
- 首先添加日志采集,现在用的是阿里的日志中心,后面可以用ELK建立自己的日志中心。
- 其次采用链路ID,根据ID可以直接检索到所有服务的请求状态和耗时。
- gPRC,直接代码即文档,还挺好。
- 对于测试,可以加入染色标签,将主分支和开发分支分离,引导流量到不用的机器。
- 分布式事务是个大话题,后面聊。
问题肯定是层不不穷的,发现了慢慢优化即可,保持热情,不畏艰险吧。
为什么要这么费力
其实我们公司的业务量,基于现在的PHP架构完全扛得住,我完全可以悠哉喝茶写写业务代码。
首先源于我本来就是一个闲不住的人,其次对于新技术有些热情。
换句话说,我就是在作死。
没有把握,千万别这样,真的是吃力可能还不讨好。架构演进必然会带来线上系统的不稳当,如果不是扛不住业务量了,不要轻易尝试。搞不好到最后头发少了,工资还降了。
我也是小步试错,逐步修改,慢慢演进的。
总结
- 根据自己公司的实际情况选择架构。不要盲目和脑袋一热就开始换架构。单挑真的死无全尸。
- 好的架构都是逐步演进过来的,不要急,适合当前业务场景的架构才是最好的。
- 不要放弃梦想,不要丢掉程序员的尊严,不要向bug低头,不要向屎一样的代码妥协,不要每天加班都是重复着增删改查完全不考虑全局。
后面我会继续慢慢分享演进中的学习和经历,欢迎大家指点和讨论。