? ? 服务注册发现之服务注册中心设计原理与Golang实现 ? ? 内容提要 通过本文您将 get 如下知识: 微服务为什么引入服务注册发现 服务注册中心设计原理 Golang 代码实现服务注册中心 为什么引入服务注册发现 从单体架构转向微服务架构过程中,当服务调用其他服务时,如何找到正确的服务地址是最基础问题。服务拆分的早期,将服务调用域名写死到代码或配置文件中,然后通过 Host 配置或 DNS 域名解析进行路由寻址,服务有多个实例,还会加入负载均衡 (Nginx、F5)。 (服务域名配置模式) 但人工维护慢慢会出现瓶颈和问题:新增服务或服务扩容,所有依赖需要新增修改配置;某台服务器挂了还要手动摘流量;服务上下线变更时效慢;人工配置错误或漏配;RPC 类型服务不能满足 ... 这时你会想如果能让服务自动化完成配置(注册)和查找(发现)就好了,于是乎服务注册发现就应运而生。 (服务注册发现模式) 可以看出,所有服务提供者在上下线时都会告知服务注册中心,服务消费者要查找服务直接从注册中心拉取。一切都变得更加美好,那么服务注册中心该如何实现呢?简单!优秀的开源项目已有一大把,大名鼎鼎的?Zookeeper、Eureka,还有后期之秀?Consul、Nacos、Etcd,当然有些算是分布式 KV 存储,要实现服务注册发现仍需些额外工作。如何技术选型,是 AP 模式更好还是 CP 模式更好?今天先抛开这些开源项目,我们亲自动手来实现一个服务注册中心,深入理解其设计原理,逐行代码分析与实践。PS:本文项目代码参考?bilibili discover?开源项目进行改造。 注册中心实现原理 设计思想 首先进行功能需求分析,作为服务注册中心,要实现如下基本功能: 服务注册:接受来自服务提交的注册信息,并保存起来 服务下线:接受服务的主动下线请求,并将服务从注册信息表中删除 服务获取:调用方从注册中心拉取服务信息 服务续约:服务健康检查,服务通过心跳保持(主动续约)告知注册中心服务可用 服务剔除:注册中心将长时间不续约的服务实例从注册信息表中删除 构造注册表 服务中心首先要维护一个服务地址注册信息列表(简称注册表)。通俗理解注册表就像手机通讯录,记录了所有联系人(服务)的电话(服务地址),通过联系人姓名(服务名称)即可找到。 那么如何存储注册表呢?最普遍认知想到存数据库(Redis 这种内存数据库),Zookeeper、Etcd 本身作为分布式 KV 存储天然具有成为注册中心的优势,但这些都会引入新组件,要考虑其稳定性及性能。那么我们可以直接将注册信息存到内存中,这时候你会想如果服务挂了内存数据丢了怎么办?这个问题后面我们会想办法解决。 首先构建一个注册表 Registry 数据结构,定义如下: type?Registry?struct?{ ????apps?map[string]*Application ????lock?sync.RWMutex } apps? 记录应用服务 Application 的信息,使用 map 结构,key 为应用服务的唯一标识,值为应用服务结构类型 lock? 读写锁,保障并发读写安全 应用服务 Application结构如下: type?Application?struct?{ ????appid???????????string ????instances???????map[string]*Instance ????latestTimestamp?int64 ????lock????????????sync.RWMutex } appid? 记录应用服务唯一标识 lock? 读写锁,保障并发读写安全 latestTimestamp 记录更新时间 instances 记录服务实例 Instance 的信息,使用 map 结构,key 为实例的 hostname (唯一标识),值为实例结构类型 服务实例 Instance?的结构如下: type?Instance?struct?{ ????Env??????string???`json:"env"` ????AppId????string???`json:"appid"` ????Hostname?string???`json:"hostname"` ????Addrs????[]string?`json:"addrs"` ????Version??string???`json:"version"` ????Status???uint32???`json:"status"` ????RegTimestamp????int64?`json:"reg_timestamp"` ????UpTimestamp?????int64?`json:"up_time