最新发布《数智时代的AI人才粮仓模型解读白皮书(2024版)》,立即领取! 了解详情
写点什么

罗辑思维首席架构师:Go 微服务改造实践

  • 2018-05-14
  • 本文字数:7199 字

    阅读完需:约 24 分钟

7 月 6-9 日, ArchSummit 全球架构师峰会将在深圳举行,此次大会邀请了罗辑思维首席架构师方圆前来分享《罗辑思维Go 语言微服务改造实践》。在此之前,方圆曾在 GopherChina有过不一样的分享,本文依据分享简单整理而成。

方圆

曾先后在 Cisco,新浪微博从事基础架构研发工作。十多年一直专注于后端技术的研发,在消息通信,分布式存储等方向有着丰富的经验。个人技术兴趣广泛,主要专注 Go/Java/Python 等编程语言的发展,尤其是在云计算等前沿领域的应用。

一、改造的背景

得到最早的 APP 就是一个单体的 PHP 的应用,就是图中最大的黄色块,中间蓝色块代表不同模块。下面的黄色部分代表 passport 和支付系统,这个是在做得到之前就存在的系统,因为公司早期有微信里的电商业务。

后来发现有一些业务逻辑并不需要从得到走,还有一些数据格式转换的工作也不需要跟业务完全耦合,所以加了一层 PHP 的网关就是下图看到的 V3 那部分。但是这样做也有一些问题,PHP 后端是 FPM,一旦后端的接口响应较慢,就需要启动大量 FPM 保证并发访问,从而导致操作系统负载较高,从这一点上来说,使用 PHP 做这部分工作并不合适。

屋漏偏逢连夜雨

案例一:8/31 大故障:2017 年 8 月 31 日的时候,老板做活动,导致流量超过预期很多,系统挂了两个小时。

案例二:罗老师要跨年

每年罗老师都要跨年演讲,第一年是在优酷,有 200 多万人的在线观看,第二年是同时和优酷等视频网站再加上深圳卫视一起合作直播,2016 年深圳卫视的收视率是地方第一。2017 年的老板当时想要送东西,送东西的这个场景比较恐怖,二维码一放出来,就会有大量用户同时请求。

最恐怖的事情是,老板要送的东西 8 月 31 日的时候还没有,要在后面 2 个月期间把东西开发出来。一方面业务迭代不能停,一方面需要扛过跨年,所以就需要我们对业务系统进行改造。

改造目标

  • 高性能:首先是性能要高,如果你单台机器跑几十 QPS,那么堆机器也很难满足要求。

  • 服务化:服务化实际上在故障之前就已经开始了,并且由于我们不同的业务团队已经在负责不同的业务,实际上也是需要服务化继续做下去。

  • 资源拆分隔离:随着服务化过程,就需要对资源进行拆分,需要每个服务提供相应的接口,服务之间不能直接访问其他服务的数据库或者缓存。

  • 高可用:当时定的目标是 99.9 的可用性。

Go 的好处很多,最重要的还是对 PHP 程序员来说,上手更容易,而且性能好很多

二、改造的过程

首先有一个系统架构图

对于系统改造来说,首先需要知道,系统需要改成什么样子。因此我们需要一个架构的蓝图。上面就是我们的架构蓝图。首先需要的是一个统一对外的 API GATEWAY,图中最上层的黄色部分。 中间淡紫色的部分是对外的业务服务。浅绿色部分是基础资源服务,比如音频文稿信息,加密服务。下面红色部分是支付和 passport 等公用服务,最右侧是一些通用的框架和中间件。最下层是一些基础设施。

我们的框架跟基础设施的完善和系统重构是交织进行的,不是说一开始就有一个完全没问题的设计,随着业务的改造,会有很多新的功能加进来。

框架和基础设施完善

我不讲应用系统怎么拆分,因为每个公司业务系统都不一样,我讲一下我们在框架和中间件这部分事情。

API gateway

API gateway 是我们和陈皓(著名的左耳朵耗子)团队合作研发的。他们团队对于我们成功跨年帮助很大,在此先感谢一下。

目的

  • 限流

    API gateway 主要的目的就是限流,改造过程当中,我们线上有 400 多个接口,经常加新功能。我们可以保证新接口的性能,但是总有在改造过程中疏忽的老接口,通过 API gateway 限流可以保证在流量大的时候,老接口也有部分用户可用。

  • 升级 API

    大部分的 API 升级都是跟客户端解决的,但是我们不太强制用户升级,导致线上老接口存在很长时间,我们需要在 API gateway 这一层做一些把新接口数据格式转成老接口数据格式的工作。

  • 鉴权

    在拆分服务之后,需要统一对接口进行鉴权和访问控制,业界的做法通常都是在网关这一层来做,我们也不例外。

接下来看一下 API gateway 的架构

API gateway 由一个 write 节点和多个 read 节点,节点之间通过 gossip 协议通信。每个节点最上层有一个 CLI 的命令行,可以用来调用 Gateway 的 API。下层的 HTTPServer 等都是一个 plugin,由多个 plugin 组成不同的 pipeline 来处理不同的请求。在后面我会介绍这个的设计。每个节点都有一个统计模块来做一些统计信息,这个统计信息主要是接口平均响应时间,QPS 等。修改配置之后,write 节点会把配置信息同步到 read 节点上,并且通过 model 模块持久化到本地磁盘上。

请求经过了两段 pipeline,第一段 pipeline 基于请求的 url。可以在不同的 pipeline 上面组合不同的 plugin。假设一个接口不需要限流,只需要在接口的配置里头不加 limiter plugin 就可以了。第二段 pipeline 基于后端的 Server 配置,做一些负载均衡的工作。

接下来看整个 API gateway 启动的流程和调度方面

启动是比较简单的,去加载 plugin,然后再去加载相应的配置文件,根据配置文件把 plugin 和 pipeline 做对应。右上角的这个调度器分为静态调度和动态调度。静态调度是假设分配 5 个 go routine 来做处理,始终都有 5 个 go routine 来处理对应的请求。动态调度器是根据请求繁忙程度,在一个 go routine 最大值和最小值之间变化。

API gateway 鉴权方面比较简单,客户端调用登录接口,passport 会把 token 和 userid,传到 API gateway,API gateway 再把相应的 token 传到这个 APP 端。客户端下次请求就拿 token 请求,如果 token 验证不过,就返回客户端。如果验证通过再调用后端不同的服务获取结果,最后返回结果给客户端。

最后再强调一下 API gateway 如何进行

我们在 API gateway 里面引入两种限流的策略

  • 滑动窗口限流

    为什么会根据滑动窗口限流呢?因为线上接口太多,我们也不知道到底是限 100 好 200 好还是限 10000 好,除非每一个都进行压测。用滑动窗口来统计一个时间窗口之内,响应时间,成功和失败的数量,根绝这个统计数据对下一个时间窗口是否要进行限流做判断。

  • QPS 的限流

    为什么还会留一个 QPS 的限流呢?因为要做活动,滑动窗口是一个时间窗口,做活动的时候,客户拿起手机扫二维码,流量瞬间就进来了,滑动窗口在这种情况下很难起到作用。

服务框架

目的

  • 简化应用开发
  • 服务注册发现
  • 方便配置管理

服务框架的常用架构

第一种方式是做成一个库,把相关功能编译进服务本身。这里有两个问题,第一个是我们兼容好几种语言,开发量比较大。还有一个是一旦客户端跟随服务调用方发布到生产环境中,后续如果要对客户库进行升级,势必要求服务调用方修改代码并重新发布,所以该方案的升级推广有不小的阻力。在业界来说,spring cloud,dubbo,motan 都是用这样的机制。

还有一种方案是把 Lord Balancing 的功能拿出来做成一个 agent,跟 consumer 单独跑,每次 consumer 请求的时候是通过 agent 拿到 Service Provder 的地址,然后再调用 Service Provder。

  • 好处是简化了服务调用方,不需要为不同语言开发客户库,LB 的升级不需要服务调用方改代码。
  • 缺点也很明显,部署比较复杂;还有可用性检测会更麻烦一点,这个 agent 也可能会挂。如果 agent 挂掉,整个服务也要摘下来。

百度内部的 BNS 和 Airbnb 的 SmartStack 服务发现框架也是这种做法。由于我们内部语言较多,因此选择了第二种做法。

在 Consul 集群中,每个提供服务的节点上都要部署和运行 Consul 的 agent,所有运行 Consul agent 节点的集合构成 Consul Cluster。Consul agent 有两种运行模式:

  • Server
  • Client

这里的 Server 和 Client 只是 Consul 集群层面的区分,与搭建在 Cluster 之上 的应用服务无关。以 Server 模式运行的 Consul agent 节点用于维护 Consul 集群的状态,官方建议每个 Consul Cluster 至少有 3 个或以上的运行在 Server mode 的 Agent,Client 节点不限。

Client 和 Server 的角色在 DDNS 是没有严格区分的,请求服务时该服务就是 Client,提供服务时候就是 Server。

NNDS 提供出来的是一个 SDK 可以很容易的集成和扩展为一个独立的服务并且集成更多的功能。采用 agent 方式,将在每一个服务器部署安装得到的 agent,支持使用 HTTP 和 grpc 进行请求。

服务完成启动并可以可以对外提供服务之后,请求 agent 的接口 v1/service/register 将其注册的进入 DDNS;

  • 注册成功则其他客户端可以通过 DDNS 发现接口获取到该 APP 节点信息;
  • 如果注册失败,APP 会重复尝试重新注册,重试三次失败则报警;

假设服务 A 需要请求服务 B,服务名称为 bbb,直接请求本机的 agent 接口 v1/service/getservice,获取到 bbb 的服务节点信息。

对于 agent 而言,如果服务 bbb 是第一次被请求,则会请求 Consul 集群,获取到服务 bbb 的数据之后进行本地从 cache 并对服务 bbb 的节点进行 watch 监控,并定时更新本地的 service 信息;

如果获取失败,给出原因,如果是系统错误则报警;

这是服务框架基本的接口

这个就是客户端调用的封装,可以同时支持 HTTP 和 JRTC,在这个之后我们还做了 RBAC 的权限控制,我们希望能调哪些服务都是可以做权限控制的。

多级缓存

client 请求到 server,server 先在缓存里找,找到就返回,没有就数据库找,如果找到就回设到缓存然后返回客户端。这里是一个比较简单的模型。只有一级 cache,但是一级 cache 有可能不够用,比如说压测的时候我们发现,一个 redis 在我们的业务情况下支撑到接口的 QPS 就是一万左右,QPS 高一点怎么办呢?我们引入多级缓存。

越靠近上面的缓存就越小,一级就是服务 local cache,如果命中就返回数据,如果没有就去 L1 查,如果查到就更新 local cache, 并且返回数据。如果 L1 级也没有就去

L2 级查,如果查到数据就更新 L1 cache/local cache,并返回数据

我们上面看到的是针对单条内容本身的缓存,在整个栈上来看,gateway 也可以缓存一部分数据,不用请求透穿。这个 5 的虚线是什么意思呢?因为数据修改后需要更新,在应用层做有时候会有失败,所以读取数据库 binlog 来补漏,减少数据不一致的情况。

我一直觉得如果有泛型代码好写很多,没有泛型框架里面就要大量的反射来代替泛型。

多级缓存开始加了之后整个性能的对比,最早 PHP 是一两百,改成 Go 之后,也不强多少,后面 Go 和 big cache 的大概到两千左右的,但是有一些问题,后面会讲当问题。后面基于对象的 cache,把对象缓存起来,我们跑测试的机器是在八核,达到这样的结果还可以接受。

熔断降级

接口同时请求内部服务,service7、8、9 不一样,service5 是挂掉的状态,但是对外的服务还在每次调用,我们需要减少调用,让 service5 恢复过来。

打开的状态下,失败达到一定的阈值就关起来,等熔断的窗口结束,达到一个半开的状态接受一部分的请求。如果失败的阈值很高就回到关闭的状态。这个统计的做法就是我们之前提到的滑动窗口算法。

这里是移植了 JAVA hystrix 的库,JAVA 里面有很多做得很不错的框架和库,值得我们借鉴。

经验总结

通用基础库非常重要

刚才讲的性能提升部分,QPS 从 600 提升到 12000,我们只用了一天,主要原因就在于我们通过基础库做了大量优化,而且基础库做的提升,所有服务都会受益。

善用工具

• generate + framework 提升开发效率

• pprof+trace+go-torch 确定性能问题

比如说我们大量的用 generate + framework,通过 generate 和模板生成很多代码。查性能的时候,pprof+trace+go-torch 可以帮你节省很多工作。Go-torch 是做火焰图的,Go 新版本已经内置了火焰图的功能。

这是根据我们的表结构生成相应的数据库访问代码,多级缓存是把所有的访问都要抽象成 K-V,K-LIST 等访问模式,每次这么做的时候手动去写太繁琐,我们就做了一个工具,你用哪一个表,工具就生成好,你只需要把它组装一下。

定位性能问题的时候,火焰图一定要用

比如说定位性能问题就要看最长的地方在哪里,着力优化这个热点的 code,压测的时候发现,大家 600、900 的火火焰图这里有问题,优化完成后如下图

其他经验总结

例如:

  • 针对热点代码做优化
  • 合理复用对象
  • 尽量避免反射
  • 合理的序列化和反序列化方式

接下来重点讲几个操作:

GC 开销

举例来说我们之前有一个服务会从缓存里面拿到很多 ID 的 list,数据是存成 json 格式 [1,2,3] 这样,发现 json 的序列化和反序列化性能开销非常大,基本上会占到 50% 以上的开销。

滴滴讲他们的 json 库,可以提升 10 倍性能,实际上在我们的场景下提升不了那么多,大概只能提升一倍,当然提升一倍也是很大的提升(因为你只用改一行代码就能提升这么多)。

其次 json 饭序列化导致的 GC 的问题也很厉害,最猛的时候能够达到 20%CPU,即使是在 Go 的算法也做得很不错的情况下。

最终解决的办法就是在这里引入 PB 替代 json。PB 反序列化性能(在我们的情况下)确实比 json 好 10 倍,并且分配的临时对象少多了,从而也降低了 GC 开销。

为什么要避免反射呢?我们在本地建了 local cache,缓存整个对象就要求你不能在缓存之外修改这个对象,但是实际业务上有这个需求。我们出现过这样的情况后就用反射来做 deep copy。JAVA 反射还可以用,原因是 jvm 会将反射代码生成 JAVA 代码,实际上调用的是生成的代码。

但是在 Go 里面不是,本来 Go 的性能是和 C 接近的,大量用了反射之后,性能就跟 python 接近额。后来我们就定义一个 cloneable 的接口,让程序员手动来做这个 clone 工作。

压力测试

我们主要用的就是 ab 和 Siege,这两个通常是针对单个系统的压力测试。实际上用户在使用的过程当中,调用链上每一个地方都可能出现问题。所以在微服务的情况下,单个系统的压力测试,虽然很重要,但是不足以完全消除我们系统的所有问题。

举一个例子,跨年的时候罗老板要送东西,首先要领东西,领东西是一个接口,接下来通常用户会再刷一下已购列表看看在不在,最后再确认一下他领到的东西对不对。因此你需要对整个链路进行压测,不能只压测一下领取接口,这样可能是有问题的。假设你已购列表接口比较慢,用户领了以后就再刷一下看一看有没有,没有的情况下,一般用户会持续的刷,导致越慢的接口越容易成为瓶颈。因此需要合理的规划访问路径,对链路上的所有服务进行压测,不能只关注一个服务。

我们直接买了阿里云 PTS 的服务,他们做法就是在 CDN 节点上模拟请求,可以对整个访问路径进行模拟。

正在做什么

分库分表和分布式事务

选择一个数据库跟你公司相关的运维是相关的。分布式事务在我这里比较重要,我们有很多购买的环节,一旦拆了微服务之后,只要有一个地方错,就需要对整个进行回滚。我们现在的做法是手动控制,但是随着你后面的业务越来越多,不可能所有的都手动控制,这时就需要有一个分布式事务框架,所以我们现在基于 TCC 的方式正在做自己的分布式事务框架。

分库分表也是一个硬性的需求,我们在这里暂时没有上 tidb 的原因主要是 DBA 团队对 tidb 不熟悉。我们之前的分库分表也是程序员自己来处理,现在正在做一个框架能同时支持分库和分表,同时支持 hash 和 range 两种方式。

API gateway

API gateway 上面有很多事情可以做,我们在熔断和降级做了一些事情。现在一些 Service mesh 做的很多事情是把很多工作放在内部 API gateway 上,是做控制的事情,实际上不应该是业务逻辑关心的事情。我们也在考虑怎么把 API gateway 和 SM 做结合。

APM

拆了微服务之后,最大的问题是不方便定位具体问题在哪里。我们有时候出问题,我叫好几个人看看各自负责的系统对不对,大家人肉看出问题的地方在哪,这是个比较蛋疼的做法。因入 APM+tracing 之后,就方便我们来追踪问题在哪里。

容器化

我们现在的线上环境,还是在用虚拟机。仿真环境和测试环境已经是容器,使用容器有很多好处,我就不一一列举了。这也是我们下半年要做的重点工作。

缓存服务化

我们现在有多级缓存的实现,但是多级缓存还是一个库的形式来实现的。如果把缓存抽出来,使用 memcached 或者 redis 的协议,抽出来成为一个独立的服务。后面的业务系统迭代的时候不用关心缓存本身的扩容缩容策略。

我今天分享的内容就到这儿,谢谢大家!

提问环节

Q:通过你的描述,我知道你以前有 JAVA 方面的经验,我们 Go 其实没有一个像 JAVA spring cloud 这样比较成熟的微服务的开箱即用的解决方案,现在让你重新做服务化转型这个事情,你会怎么选择?

方圆:让一群 php 程序员学 JAVA 比较麻烦,学习 Go 就比较简单。其次现在搞一个微服务框架,其实并没有那么难以接受。因为很多开源软件已经提供了对应的功能,所以要造的轮子其实没有那么多。

Q:你的分库分表框架里面有支持水平分库的情况吗?

方圆:支持。

Q:众所周知 PHP 是世界上最好的语言,你们转成 Go,Go 的开发效率比 PHP(04:49:50)这两种情况。

方圆:我 PHP 学得不是太好,我自己写 Go 肯定比 PHP 快很多。我们团队来说,Go 的开发效率比 PHP 略低,但是运行性能却好太多。

Q:重新选择一次技术选型,你是上来就选 Go,还是从 PHP 再演化到 Go?

方圆:我肯定是上来就选 Go,我还是认为 PHP 做 rest API 没有啥优势。


ArchSummit 演讲摘要

本次方圆在 ArchSummit 的分享主要基于罗辑思维后端平台从其他语言转型至 Go 的整个实践过程,和大家分享转型的经历。涉及现有 Go 语言实现的服务框架技术栈选型,其中包括 API Gateway,配置中心 / 服务注册发现,客户端负载均衡,分库分表,任务调度,多级缓存,熔断等多个方面。此外也会涉及到服务监控和容器化相关的工作,以及一些常见的痛点。最后对公司未来技术方向进行阐述。大纲如下:

为什么选择 Go 语言?

  • 选择 Go 语言的原因:并发,性能
  • 微服务架构
  • 服务框架
  • API Gateway
  • 配置中心 / 服务注册发现
  • client side lb
  • 自动分库分表
  • 分布式任务调度
  • 多级缓存
  • 熔断器
  • 服务监控
  • 容器化

碰见的问题:

  • Error 处理和缺乏泛型
  • CPU 使用分析
  • 内存使用分析
  • 公用代码库

未来需要的方向:

  • 分布式事务
  • 分布式追踪
  • Service Mesh

目前 ArchSummit 限时 9 折报名中,点击此处可获取大会日程。

PS:联系小助手豆包可获取更多优惠(微信:aschina666,或致电 010-84780850)。

公众号推荐:

跳进 AI 的奇妙世界,一起探索未来工作的新风貌!想要深入了解 AI 如何成为产业创新的新引擎?好奇哪些城市正成为 AI 人才的新磁场?《中国生成式 AI 开发者洞察 2024》由 InfoQ 研究中心精心打造,为你深度解锁生成式 AI 领域的最新开发者动态。无论你是资深研发者,还是对生成式 AI 充满好奇的新手,这份报告都是你不可错过的知识宝典。欢迎大家扫码关注「AI前线」公众号,回复「开发者洞察」领取。

2018-05-14 10:526996

评论 1 条评论

发布
用户头像
写了个寂寞,图都看不清
2023-03-23 19:31 · 北京
回复
没有更多了
发现更多内容

架构实战营4期-模块3作业

木几丶

「架构实战营」

【架构实战营】模块三:命题作业

wgl

「架构实战营」

Golang中文件的基本操作

liuzhen007

Go 28天写作 Go 语言 12月日更

ALC北京发起人 姜宁:通过开放与协作,我们可以实现一个人想都不敢想的事情 I OpenTEKr 大话开源 Vol.6

OpenTEKr

大话开源

Java 数据持久化系列之池化技术

程序员历小冰

MySQL 持久化 28天写作 池化技术 12月日更

Apache 海豚调度 PMC 郭炜:开源,不是天才的甜点,而是执着者的盛宴 I OpenTEKr 大话开源 Vol.7

OpenTEKr

大话开源

一个cpp协程库的前世今生(一)缘起

SkyFire

协程 cpp cocpp

架构实战 模块三作业

mj4ever

架构实战

费用节省 50%,函数计算 FC 助力分众传媒降本增效

阿里巴巴云原生

阿里云 云原生 合作 函数计算FC 分众传媒

百度智能云发布零碳园区解决方案,助力实现双碳目标

百度大脑

人工智能

外包学生管理系统详细设计文档

糖糖学编程

架构实战营

学习总结 2021.12.30

mj4ever

学习笔记

引领人工智能技术自立自强 百度吴甜获评“首都最美巾帼奋斗者”

百度大脑

人工智能「

第三模块学习总结

Anlumina

#架构实战营

【架构实战营】模块三:知识点总结

wgl

「架构实战营」

「架构实战营」模块三《如何保证设计出合理的架构》作业

DaiChen

作业 模块三 「架构实战营」

百度飞桨EasyDL桌面版正式上线,没网也能训练AI!

百度大脑

人工智能

瞰见 | 开源,会不会变成开源创业的焦油坑?

OpenTEKr

狄安瞰源

瞰见 | 初创1个月就融到3亿美金,ClickHouse 你凭什么?

OpenTEKr

狄安瞰源

盘点 2021|自己一个人扛起了公司的半边天

liuzhen007

技术人生 盘点2021 盘点 2021

第三周学习总结

糖糖学编程

架构实战营

深入理解一下Python中的面向对象编程

宇宙之一粟

Python 面向对象 12月日更

元宇宙很好,但VR开发者不准备停留在这里

白洞计划

VS Code 如何设置大小写转换快捷键

AlwaysBeta

vscode

从人工到智能!百度AI开发者大会分论坛,探寻国球乒乓背后的AI之路

百度大脑

人工智能

阿里巴巴超大规模 Kubernetes 基础设施运维体系揭秘

阿里巴巴云原生

阿里云 Serverless Kubernetes 云原生 ASI

架构实战营模块三作业

lchx08

「架构实战营」

架构实战营:模块三作业

Geek_93ffb0

「架构实战营」

Git基础 |打tag

xcbeyond

git 28天写作 tag 12月日更

一口气搞懂【Linux内存管理】,就靠这60张图、59个问题了

奔着腾讯去

内存泄露 内存管理 Linux Kenel 内存映射 内存池

详细架构设计文档

Anlumina

#架构实战营

罗辑思维首席架构师:Go微服务改造实践_架构_方圆_InfoQ精选文章