服务注册发现之服务注册中心设计原理与Golang实现.docx
?
?
服务注册发现之服务注册中心设计原理与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