专栏名称: 春天的旁边
SpringSide,江南白衣的Java后端开发,唯品会基础架构,微服务实践的日常。
目录
相关文章推荐
NaturePortfolio  ·  中国下一代计算芯片研究数量已达美国两倍 ·  5 天前  
51好读  ›  专栏  ›  春天的旁边

唯品会的Service Mesh三年进化史

春天的旁边  · 公众号  ·  · 2018-08-23 21:08

正文

唯品会的Service Mesh三年进化史

| Filed under 工作 技术

前言

今年在Goolge的推动,和K8S社区的惯性力量下,Service Mesh的风很大。蓦然回首,唯品会的服务化体系OSP(Open Service Platform) 在三年前就走上了ServiceMesh的路,一股淡淡的自豪,和对定下基调的前老板的佩服。

每种架构风格,由于各司不同的历史因果,以及对采纳新架构的诉求和期望,都会有不同的实现路线。我们的SM在实战中一点点演化而来,与 Istio由谷歌IBM的超级架构师们画出来的架构也有不同之处。希望分享我们的演进过程,给各司里同样往SM演进中的架构师们一个参考。

SM演进史

一、标准服务化体系阶段

如果用两根手指将Local Proxy 和 Remote Proxy的框框按住,就是个标准的服务化框架。

传输与序列化层

当时还没有GRPC,自己拿Netty撸了一个TCP长连接,多路复用,异步IO的传输层,基于Thrift协议的序列化。

当然,这个组合从性能上现在看也不过时,自定义TCP不比HTTP2弱, Thrift 也比ProtocolBuffer略快,我们还支持基于Java类来定义Thrift接口,而不是原生Thrift,PB的有额外学习成本的中立语言,完美。

缺点嘛,就是跨语言时会略尴尬,虽然原生的Thrift也在某种程度上跨语言,但我们除了保留Thrift序列化协议外,其他从头到脚都重新实现了,也就不再能从它那借力。

服务注册中心

当时的大环境下,ZooKeeper也是比较流行的注册中心选择,不过最近在做去ZK化,改为自研的Http Api Server,数据用Redis或 Etcd存储。

API网关

当然要有入口网关,将外网的HTTP请求,经过认证,安全防刷, 再转换为OSP调用后端服务。

服务配置

动态修改服务的各种配置,比如路由,熔断,超时,重试等等,动态下发。

服务路由

服务的注册发现,负载均衡,路由定制,机房策略等路由策略,超时,重试,熔断等可用性策略,我们统称为路由逻辑。


我司的主力语言,前端是PHP 和 Java,后端是Java,在调用端有着硬性的跨语言需求。

SM第一步

上述的路由逻辑,如果在每种语言实现一次客户端,显然是不合算的。为了PHP,将这些逻辑抽取成独立的Proxy。

SM第二步

这个独立的Proxy,是部署成传统的集中式的Proxy集群,还是当时还不很著名的本地SideCar模式呢?

在公司的体量下,有着强烈的去中心化的愿望,所以我们选了SideCar模式。

SM第三步

PHP使用了Proxy,那Java呢,继续像dubbo一样在客户端里可以吗?

这个当时有比较大的争议,因为毕竟多经过了一层节点,虽然本地连接不经过网络,也很难说性能毫无影响。 为此我们还专门做了个基于Unix Domain Socket的版本,但后来觉得性能足够,一直没上线。

最后,老板力排众议,一是为了架构统一性,二是因为我们的Proxy功能还不成熟,而使用它的应用又特别多,如果不独立出来,很难推动快速升级。所以统一使用了Proxy模式。

SM第四步

SideCar模式虽好,但是存在单点的问题,如果SideCar在升级,或者挂掉了怎么办?

SideCar升级,可以联动同一台机上的应用先摘除流量。
SideCar挂掉,可以搞个脚本把它自动重新拉起。但重新拉起的间隔里还是会丢失的请求,如果重新拉起还是失败呢?

作为一个架构师,高可用的观念是深入到骨子里的,所以我们又在每个机房搭建了一个Remote Proxy集群,并在客户端里的SDK加入了如下的逻辑:

“如果本地Proxy不可用或宣称自己准备关闭,就将请求无损转发到Remote Proxy,再启动一个监视器观察本地Proxy什么时候重新可用。”

至此,一个封闭的服务化框架完备了,我们有了标准的服务化体系。能力上,也比当时久不更新的Dubbo,还有后来的Netflix,SpringCloud要强不少。

二,按不住的多语言的客户端接入

时间匆匆过, 一个公司里,总是按不住会有更多的语言出现,比如Node.js, C++, Go,像前面说的,再撸一次TCP/Thrift 感觉有点累了,这些也不是主流语言,花太多时间在上面不值得。 所以,我们在Proxy上额外支持了HTTP/JSON的传输层,再根据注册中心里的元数据,重新序列化成Thrift来调用后端服务就好了。

我们还发现,在PHP里用Thrift,还不如它内置的C写的HTTP/JSON库快。

不过,我们也不想在这些客户端再实现一次本地SideCar与Remote Proxy的切换逻辑了,反正这些非主流语言的客户端的调用量不会很大,就通通去调用Remote Proxy集群好了。哪天撑不住时,可能会改用前面SM第四步说的不那么完美的方案。

至此,多语言的客户端完全咩有接入限制了。

三, 普通Web应用也想零成本变身服务化

时间又匆匆过,又有大量原来的Web应用,不想改造成OSP应用,又想作为一个服务,或者接入API网关,或者加入服务化大家庭,享受各种服务治理的能力。

为此,我们开发了一个很轻量的注册器,也是以sidecar的形式运行,不断的对Web应用的做健康检查,与注册中心进行注册,心跳,和反注册。

当然,免费的午餐肯定没那么好吃,比如没有了基于IDL生成SDK的与客户端的契约化编程,没有了高效的传输层序列化层,没有了限流,自动隔离线程池,ClassLoader预热,闲时主动GC等等细微的Runtime加强。

至此,多语言的服务端也完全咩有问题了。

四、容器化了,容器化了

为了容器化,sidecar跑在哪里,又成了问题。 如果跑在每一个Pod里,一来呢,身为Java应用,堆内堆外内存吃得有点多;二来呢,升级Proxy时,又要重新发布每一个应用;

所以,我们选择了DaemonSet的形式,每台宿主机上只运行一个Proxy,Proxy启动时把自己的IP写在一个共享文件里,这个文件也Mount进各个容器里面,各个客户端会监听这个文件。

为了隔离性,Proxy加了个来源IP的限流,效果就是单个容器的调用高于2万QPS时,第二万零一个请求开始就把它临时重定向到Remote Proxy集群。 十秒钟后再重试本地Proxy,如果还是高,又继续打发过去。

另一个改动,我们之前基于IP来定义路由规则( 比如快慢分离,有三台隔离的机器,专门服务一些消耗较大的批量接口)。容器里IP不固定了怎么办?

我们引入了一个部署池的概念,比如一个应用有两个部署池,一个3台机器,另一个20台机器,这个池的名字,会注册为服务实例的元信息。在服务路由里就用池的名字来定义规则。


与Istio 的区别

1. Server端无入口Agent

Server端前面也摆一个Agent,全部流量都流经它,有点重。







请到「今天看啥」查看全文