深圳幻海软件技术有限公司 欢迎您!

技术Leader教你看源码的本质

2023-02-27

 前面我说过技术 leader 的几个特质, 今天还想跟大家分享下,作为技术 leader ,还要懂得研究和引入技术,引入的 前提一定是要 Hold 住。怎么才叫 hold 住呢?就是能精通

 

前面我说过技术 leader 的几个特质, 今天还想跟大家分享下,作为技术 leader ,还要懂得研究和引入技术,引入的 前提一定是要 Hold 住。怎么才叫 hold 住呢?就是能精通使用它,能够深入了解它的架构、原理,能够剖析它的核心源代码。

以研究 Nacos 为例,这次我分享下研究技术的方法,授之以渔,希望大家有所收获,当然也欢迎留言共同讨论更好的技巧。

01  官方文档,搭建demo使用

很多人喜欢买书看,看别人的博客,其实都是吃剩饭,别人也是看了官方文档写的。一名合格的技术人员, 尽量从源头看,看官方的文档,原汁原味的,耐心点一点点看。

Nacos 的官方文档,怎么看这个过程我就不讲了,基本上就是按目录过一遍,然后根据官方例子搭建起来,知道它的基本功能使用。

重点看看里面的架构设计、模型概念。

02  了解功能设计主线,确定研究主线,高维度抽象功能模型

看完官方文档,基本会用后,要确定深入研究的主线。 Nacos 不仅仅包含了服务管理功能,还包含了配置管理,元数据管理。看到这里其实也能明白为什么 Nacos 未来会成为注册中心的趋势,因为它同时包含了微服务的两个套件:注册中心、配置中心,用了它能少部署一个配置中心。下图来自官方文档:

图片来源: nac os 官方文档

这篇文章我们研究的主线是注册中心,所以只研究它如何实现注册中心的。 这个时候,我们要高维度看,注册中心需要哪些功能?这些功能,是任何注册中心都需要实现的功能,要把这些掌握清楚。显然,注册中心通用的功能模型包含:

1. 服务注册

2. 服务心跳保活

3. 服务下线(正常下线、异常下线)

4. 服务发现

基本上实现上面四点,一个单体的注册中心就实现了。然后如果考虑分布式,还要设计它如何实现 CP/AP 模式。

03  下载源代码,提取精华

很多人看源码,学源码,往往都是看了一个寂寞,为了寂寞而寂寞。 到底要看什么?

1. 源码看什么?

看源码,要看作者怎么架构、怎么设计、怎么实现,并思考为什么要这么实现,通过源码看到了它里面的精髓,才算真看了源码。不然就是看了个西瓜,吃了就没了,就是个吃瓜群众。相反,看源代码 ,提炼模型、原理、机制、设计模式、并发经验、网络经验、 OS 存储机制等 ,那你才算真看了源码,吸收了它的营养。

2.源代码怎么看呢?

抛开技术积累和经验因素外,方法也是很重要的一个部分。很多看源代码都没有经验,看到源代码复杂,代码又多,一看就懵逼,也不知道从哪里看起。

我先分享 3 个经验:

( 1 )找源头,就是启动的地方, 这个一般从脚本里看可以到,大部分中间件都是封装了启动脚本的,你就从这个启动脚本里找启动类,让源码能跑起来,后续可以 debug 验证。

( 2 )只看主线代码。 就是我们上面提炼的功能模型。那些日志、统计分析、异常分支、非主线分支第一次都不要看。

( 3 )先静态看源码, 不要动态 debug ,因为 debug ,很容易陷入细节,陷入各种分支,几绕几绕就懵逼了,然后就放弃了。静态看源代码,就是不断锻炼自己,让自己只看主线代码,那种明显是分支的直接跳过不看,这样快速的过主线。   实在有疑惑了,然后 debug 验证下。

我们来看看 Nacos 的源码,版本是 1.4.2 ,分析下我是怎么看的。

( 1 )服务注册如何实现的?如何确保高并发?

客户端启动的时候,会通过 http 请求发送注册请求,请求链接采用 restful 模式。 服务端接受到注册请求后,会把请求参数封装放到一个阻塞队列里,然后基于一个线程不断的获取这个阻塞队列的信息,放入到注册表中。

可以看到高并发设计的一个关键点:异步。 这里还可以对比延伸, zookeeper 如何实现的? Eureka 如何实现的?这些实现之间有什么优劣?它们能否做到高并发?是否也是异步?   这些就留给读者探索了。

(2)服务注册表是如何设计的?为什么这么设计?以及怎么防止多节点的读写并发冲突?

Nacos 支持 CP 和 AP 模式,如果不懂 CP 和 AP 的自己百度了,这种简单的概念我就不科普了。

①AP 模式下,是基于内存存储的,底层其实是一个双重的 map 结构。 CP 模式下,数据是存储到文件的。这里我们主要还是研究 AP 模式。因为大多数场景下,我们注册中心更适合 AP 模式。

看到这个 map 结构,有没有思考过为什么这么设计? namespace 的目的是? group 的目的是什么? 如果有一定 Devops 经验的同学知道,我们一个项目环境往往可能有多套,比如开发环境、测试环境、预发布环境、线上环境等。如果每一套环境都部署一个注册中心,是不是很麻烦。所以这里 namespace 的目的,就是可以用同一套注册中心,基于 namespace 来隔离这些不同的环境。

那么 group 的目的是什么呢?如果我们用过 dubbo 就知道这个概念了,对服务进行分组。有时候我们一个服务刚开始是一个大服务,但随着业务扩展,有时候需要拆成几个小服务,这样就可以设置为一个 group。

这些都是基于 可扩展性 来考虑设计的。我们看看官方文档的数据模型:

图片来源: nac os 官方文档

② 怎么防止读写冲突呢?

核心点:读写分离,采用了写时复制模式,提升了高并发。 就是写的时候,拷贝一份旧的实例,对这份拷贝数据修改,修改完后,再复制过去,读直接读旧实例。

读写分离这种模式,避免了加锁冲突,提升了高并发能力。读过 Eureka 源码的了解,它的实现是基于多级缓存来实现的,然后缓存之间同步数据。时效性显然没有 Nacos 的好。

这里还要思考一个点,这里复制,复制的是什么?如果写时复制,把所有的数据都复制,显然内存吃不消的。这里研究下官网的服务模型,服务下面封装的是一个个集群,集群下面是实例。

为什么有集群这个概念呢? 如果公司规模大一点的同学会知道,为了容灾高可用,一个服务,可能是多机房部署的。比如一个服务可能在亦庄机房部署一个集群,兆维机房下也有一个集群。这里可以看到 nacos 模型设计的是非常巧妙的,基本上很多点都考虑到。

图片来源: nac os 官方文档

我们看源代码也可以验证,可以看到 Service 下面,封装了一个 clusterMap。

而 cluster 下面又封装了具体的实例集合,画横线的部分。

所以,这里的写时复制,它复制的是这个实例所属的集群结构,我把核心代码截图出来,

先复制旧的实例,放到一个 oldmap 里面。

对旧的 map 做一系列运算操作,比如下线一个实例 , 然后把结果放到 ips 。

最后把新的服务实例集合赋值回去。

可以看到这里面有很多技巧,这些都可以学习,以后自己设计中间件或者写代码的时候,都是可以直接用的。

(3)服务心跳是如何保活的?

客户端每 5s 发送心跳给服务端,通过 http 请求调用发送给服务端。 服务端开启健康检查任务,每隔 5s 检查一次,如果发现超过 15s 没有收到心跳,设置健康状态为 false. 如果超过 30s 没有收到心跳,直接剔除实例。

这里我们想一个问题,服务端开启健康检查任务,如果集群模式下,每个服务端都要判断吗?这个会不会很耗性能?

我们看到健康检查任务里有这样一段代码 , 它会根据服务名称通过 hash 运算后对机器结点数取模,判断是否要执行健康检查代码。也就是说,集群模式下,不管启动了多个服务实例,任何一个服务,正常情况下只有一个结点来执行健康检查代码。但可能以为时效性,如果其他节点多执行一次,也没什么大影响对吧。当然这里面还有一些细节,都可以深扣,服务发现,时效性是多大?

(4)服务是如何下线的?

超过 30s 未收到心跳,就会剔除,这个上面我们知道了,剔除调用的其实是自己的 deregister 方法:

 

 跟进去看一下,我们发现删除方法对 service 也是加了锁的,也就是说对同一个服务的修改,是做了防并发的。

最后删除,本质也是基于异步的,这个和注册逻辑类似。

(5)客户端如何发现服务的,服务修改是如何感知的?

①  客户端先从本地缓存获取服务实例,如果为空,则从服务端拉取。

并启动一个定时任务,定期更新服务端最新实例信息。

② 服务端修改后,通过 udp 协议推送

一方面基于 udp 推送提升了实时性,另一方面, udp 虽然可能丢包,但客户端定时拉取可以作为兜底。这个设计真的很巧妙。

然后 Nacos 的 CP 模式,基于 raft 协议实现的一致性。还有它的配置中心架构是如何设计的,限于篇幅,就不再展开了。大家按照我的思路,去研究就好。记住看源码,根据主线看,然后学习它的机制、原理。不要紧紧只是看个代码。

  3.提取源码精华

看完源码后,需要提取总结里面的精华,这里提取了部分用于举例,大家可以根据自己的逻辑提取精华,不断提取精华,不断内化成自己的经验,技术才能得到质的飞跃。

维度

核心点

描述

总结

接口设计

版本设计

/nacos/v1/ns/instance

设计接口的时候考虑版本设计

设计模式

代理模式

DelegateConsistencyServiceImpl

NamingClientProxyDelegate

基于是否临时节点选择一致性协议具体实现,临时节点是 Distro, 持久节点是 raft

客户端代理

工厂模式

NacosFactory

该类统一提供了创建 ConfigService (配置中心服务)、 NamingService (注册中心服务)和 NamingMaintainService (注册中心实例操作服务)的实例化方法,并且里面使用了反射机制

架构设计

可扩展设计

数据模型

命名空间支持环境隔离

服务分组

服务实例支持集群

高并发设计

异步、读写分离、写时复制、缓存机制

熟悉基本套路,在考虑高并发时都可以套用

高可用设计

从客户端、心跳机制、服务端多个角度确保了高可用机制

客户端重试机制、客户端本地缓存文件及故障转移机制、服务端集群、一致性协议 (ap)

分层架构设计

架构层次非常清晰

整体架构也好,服务注册发现也好,架构分层很清晰。比如服务注册发现: Controller ->ServiceManager->ConsistencyService

中间件底层源码机制

高并发容器

ArrayBlockingQueue 、 ConcurrentHashMap 、

大部分中间底层本质就是高并发容器、线程池、定时任务、网络,剩下的就是具体业务。

线程池

ThreadPoolManager 线程池生命周期管理、

ThreadPoolExecutor

定时任务

ScheduledThreadPoolExecutor

4.学以致用

学习完源码,吸取精华不是目的,目的还是要学以致用。常见的路径有:参加开源社区,自研中间件投入到生产实践,内部分享经验,外部演讲分享。

学以致用才是本质!

本文以研究Nacos为例,以实践步骤分享研究技术的方法;对于微服务架构,后续将有其他同学分享Service Mesh,敬请关注!