小鹏汽车的技术中台(Logan)已经快两岁了,今天我们不讨论该不该做技术中台,只说说中台给我们带来了什么。
不管黑猫白猫,捉到老鼠就是好猫。
技术中台正好契合了公司上述所有需求。在公司的引领和推动下,2018年4月召开了小鹏汽车技术中台的启动会,同年5月底团队成立。技术中台团队始终坚持“兵不在多而在精”的原则,近两年高峰时期团队也未超过10人。
也许有人会怀疑,一支足球队规模的团队,到底能做出个什么样的中台来?
简单来说,小鹏汽车的技术中台主要由以下及部分组成:
接入中台的应用不需要写一行业务代码,可以立即具备以下能力:
对外保证:
如上图,小鹏汽车的技术中台分为微服务中台和云平台两大部分:
历史背景:
鉴于以上原因,开源软件及产品被列入首选,同时结合现实情况及需求在开源的基础上进行功能的扩展开发。只有实在没有选择的情况下,我们才会考虑自造轮子。
部分正在使用的组件:
此外,中台通过自定义的SDK提供对上述功能开箱即用,包含组件的配置调优、Metrics输出、统一日志、框架Bug的紧急修复以及功能扩展。
参考官方的文档,跑个DEMO是很容易的,但是真正在生产级环境中使用又会踩不少坑。
可用性对于车企来说尤为重要,甚至说高于一切也毫不夸张,公司对这一方面也格外重视。
a.服务实例上下线的被发现延迟
使用默认配置的情况下,实例正常上、下线的被发现延迟最大为90s,比如,服务B(提供方)的一个实例上/下线,服务A(调用方)在最长90s之后才会发现。这与Netflix的设计有关:
可通过修改配置缩短上下线的被发现延迟:
b.非正常下线的被发现延迟
上面提到默认配置下被发现的延迟最大是90s。运行过程中不可避免的会出现非正常下线的情况,比如进程被强杀(kill-9),实例来不及通知注册中心进行注销操作就退出了。这种情况下,此实例的信息会存在服务调用方的Ribbon、实例列表中最长达240s。如果是2个实例的话,会有50%的请求受到影响。这同样源于Netflix的设计:
c.自我保护模式到底开不开?
Eureka是基于CAP理论的AP模型,用于保证分区容错性,但这也导致注册信息在网络分区期间可能出现不一致,自我保护功能是为了尽可能减少这种不一致。
自我保护(selfpreservation)是Eureka的一项功能,Eureka注册表在未收到实例的心跳情况超过一定阈值(默认:85%)时停止驱逐过期的实例。
解决了b中的延迟问题,必然会导致自我保护模式不准确。
d.一个深坑
解决了b中的延迟问题,还会带来一个坑更深的坑:极低的概率下,实例启动后本地处于UP状态,而远端(注册中心)的状态持续处于STARTING状态,且无法更新。这会导致实例实际正常运行,而且对服务消费者不可见。
这种情况在Kubernetes环境下尤为恐怖:实例本地状态为UP,健康状态也为UP。在Kubernetes的滚动升级模式下,旧的实例为删除,新的实例正常运行,却对消费端不可见。
注:在技术中台运行一年多,生产环境累计出现的次数不到10次,不是2个实例运行的情况下没有对请求出现影响。
Ribbon大家常听到的就是负载均衡,但是除了负载均衡之外,它还提供容错的能力:重试。
这里有个隐藏的坑,就是加入开启了对所有操作重试的情况下,且出现SocketTimeoutException时,可能会导致一致性的问题:
因为SocketTimeoutException不只是连接超时,还有读取超时。假如一个POST请求会更新数据库,出现客户端的读取超时,但是服务端可能在客户端断开后完成的更新的操作。如果客户端进行重试,则会再次进行更新。
SpringBootTomcat的优雅退出,不知为何官方没有实现,实在匪夷所思。
为什么需要优雅退出?当Spring上下文关闭时,假如有未处理完的请求,不等请求处理完毕就直接退出。从健壮性或者一致性方面考虑,并不是一个好的解决方案。
这也是我们生产中遇到的一个问题,会在凌晨的低峰时段偶发。异常如下:
SpringBoot中通过server.connectionTimeout来设置Tomcat的keepAliveTimeout,如果未设置,则使用Tomcat的默认配置。
大写的但是:Tomcat并没有在响应头部带上Keep-Alive:timeout=60。
可通过增加过滤器–在响应头部增加Keep-Alive的timeout配置的方式来解决。
微服务中台运行于云平台之上,应用的升级模式为滚动升级:对服务进行升级时会先创建新版本的实例,待相应的检查(借助Actuator提供的/health接口)通过后,再将旧版本的实例下线。
小鹏汽车的业务比较复杂,除了主要的Java技术栈之外,还有基于CPU/GPU的Python应用,以及Node.js应用和前端应用。
而中台的出发点是功能的复用,如何让非Java语言的应用使用到中台的能力?
我们的做法是借助Sidecar模式实现基础功能下沉,与应用解耦,将SDK中的功能,下沉到独立进程中。
对于我们运行在Kubernetes上的应用来说,实现这一点就更容易了:在Pod中增加一个Sidecar的容器。
同时Sidecar还给我们带来了意想不到的效果:
当然目前的实现也不是很完美,sidecar的实现,我们用的是spring-cloud-netflix-sidecar。是的,Java的实现很重,对资源还存在一定的浪费。但是战略有了,战术的选择是多样的,比如C++实现的Envoy。
因此当前的方案不只是一次尝试,也是一个布局。
服务的容器化,允许我们在统一了日志格式后,将日志直接输出到标准输出/错误。通过Docker的json-filedriver统一落盘到Node(Kubernetes的工作节点)上。再经由以DaemonSet方式运行采集器挂在日志目录对日志进行采集。
详细的工作方式及调优,请期待云平台实践篇(云平台的能力不仅限于此)。
技术中台同样提供了基于Jenkins流水线(Pipeline)的CICD平台,经历过两个阶段:项目级的流水线和平台级的流水线。
因此我们在后续的演进中进一步将流水线提升到平台级,即平台上的系统使用统一的流水线脚本,脚本保存在GitLab仓库中,并提供版本控制。
借助JobDSL插件,我们将抽象的项目元数据转化为Jenkins作业。在项目注册到平台时,会自动为其创建作业。
每个作业的结构基本一致,包含:
外部对接云平台、Sonar、Nexus以及自动化测试平台。
微服务中台不仅仅是技术上的开发工作,还包括中台的落地,以及打造各种配套的功能设施,比如流程的简化、DevOps还有表面上无法体现的各种优化和修复工作。
作者介绍:
张晓辉:资深码农,12年软件开发经验。曾在汇丰软件、唯品会、数人云等公司任职。目前就职小鹏汽车,在基础架构团队从事技术中台的研发。