为什么要有注册中心
项目开发中采用微服务架构时,由于将原来的单体架构拆分成了多个微服务,每当要实现一个业务功能时,需要多个微服务协同工作,而多个微服务之间的相互调用,需要知道对方的 URL,至少需要知道服务提供方的IP和端口。当服务的数量到达一定规模时,比如一些大型的项目可能会有成千上万个服务,每个服务都有各自的URL,这么多的URL如果以硬编码的方式写到代码中,不利于后期项目的维护。这个时候就需要注册中心。
注册中心的设计思想
注册中心的设计思想有些类似手机通讯录,所以,在讲解注册中心的设计思想之前,我们先来引入这样一个实际生活中的场景,张三给李四打电话。
- 没有通讯录的情况:
- 张三需要自己记忆李四的电话号码
- 当需要给李四打电话时,回忆李四的电话号码,然后拨号
- 当李四的号码变更时,需要重新记忆
- 有通讯录的情况:
- 张三将李四的号码记录到通讯录中
- 当需要给李四打电话时,从通讯录中查找,然后拨号
- 当李四的号码变更时,只需在通讯录中更正
不难看出,引入通讯录比在自己的头脑中维护别人的手机号码要轻松的多。下面我们就从注册中心的存储结构和提供的操作两个方面来讲解注册中心的设计思想。
注册中心的存储结构
类比通讯录,生活通讯录存储的信息如下表:
姓名 | 电话 |
---|---|
张三 | 158XXXXXXXX |
同理,注册中心也是类似的存储结构:
服务名 | 服务信息 |
---|---|
user-service(用户服务) | 服务的IP、端口、URL |
通过上面的表格很容易让我们联想到Java中的Map数据结构。而实际也正是如此,Eureka的源码中的AbstractInstanceRegistry.java中有一个属性,源码如下:
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap();
该属性的存储结构采用的是ConcurrentHashMap,它的键值是服务名,值是一个Map。而值的Map中键是服务实例ID,值是 Lease(Lease里面包括服务信息)。
注册表数据的存储结构如下图:
服务注册表数据的存储结构使用两层Map的原因
因为在微服务系统中,为了避免服务的单点故障,通常使用的都是服务集群,一个服务名会对应多个服务实例。所以在每个服务实例的存储结构外面,又包了一层Map,这样一个服务名就可以对应多个服务实例了。
注册中心提供的操作
我们先来梳理一下注册中心需要为服务提供哪些功能,首先,服务启动后需要向注册中心注册,当服务下线的时候也需要通知注册中心,在服务工作的过程中,注册中心还需要监测服务的工作状态,注册表中存储的信息还需要被其他服务查询到,此外,为避免注册中心单点故障,注册中心也需要做集群,所以各个注册中心之间的数据需要同步。总结一下,注册中心应该提供以下六个操作:
- 接收注册服务:注册中心提供一个接口,让服务调用该接口来进行服务的登记注册
- 接收服务下线:注册中心提供一个接口,让服务调用该接口来进行服务的下线
- 接收服务心跳:注册中心提供一个接口,让服务定期地告诉注册中心自己的工作状态(是否可用)
- 服务剔除:如果服务出现故障没有及时通知注册中心,此时注册中心也发现服务最近没有发送心跳,注册中心要主动剔除出现故障的服务
- 注册表中服务信息的查询
- 注册中心集群间注册表服务信息的同步
其实注册中心的本质就是一个Web服务,通过提供上面分析的6个接口供服务调用。对应的调用注册中心的服务称为注册中心客户端。
使用注册中心分为如下两步操作:
8. 搭建注册中心服务端。
9. 启动注册中心客户端,让服务和注册中心连通。