|
| 1 | +## 如何设计一个注册中心? |
| 2 | + |
| 3 | +今天,给大家分享如何设计一个**注册中心**。 |
| 4 | + |
| 5 | +不管是出于面试,还是深入学习注册中心,关于如何设计一个注册中心都是一个很好的话题。 |
| 6 | + |
| 7 | +假设现在我们系统有两个小系统: |
| 8 | + |
| 9 | +- 订单系统 |
| 10 | +- 商品系统 |
| 11 | + |
| 12 | +单个系统分别部署在不同服务器上,如果我们订单系统需要调用商品系统的某个服务: |
| 13 | + |
| 14 | + |
| 15 | + |
| 16 | + |
| 17 | + |
| 18 | +#### 怎么调用? |
| 19 | + |
| 20 | +方法1:商品系统开发的朋友告诉你对应的地址。 |
| 21 | + |
| 22 | + |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +方法2:商品系统开发的朋友把对应`API`地址存放到某个地方。 |
| 27 | + |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | + |
| 32 | +方法3:直接通过Nginx,使用域名进行转发到某个实例上。 |
| 33 | + |
| 34 | + |
| 35 | + |
| 36 | +这时候,订单系统就可以通过上述方法调用商品系统的`API`了。 |
| 37 | + |
| 38 | +#### 问题来了 |
| 39 | + |
| 40 | +实际线上环境中,很少是单体机构的,很多都是做了集群的,也就是说每个服务会有N个实例,少则几个几十个,多则几百上千上万。如果此时我们还用上面三种方法,当我们的商品系统某个服务下线(宕机了),或者新增实例,此时是非常的头疼。 |
| 41 | + |
| 42 | +> 所以,注册中心就来了。 |
| 43 | +
|
| 44 | +#### 注册中心来了 |
| 45 | + |
| 46 | +我们能不能搞一个第三方的节点,这个节点就用来存放我们商品系统的服务信息,这样一来,其他系统需要服务信息,直接去第三方节点上去获取即可。此时,其他系统只要知道这个第三方的节点地址就可以了。这个第三方的节点,我们也称之为`注册中心`。 |
| 47 | + |
| 48 | +> 下面我们用服务提供方(商品系统)称之为provider,服务调用方(订单系统)我们称之为consumer。 |
| 49 | +
|
| 50 | +#### 如何设计一个注册中心 |
| 51 | + |
| 52 | +我们需要解决如下几个问题: |
| 53 | + |
| 54 | +- 服务如何注册 |
| 55 | +- consumer如何知道provider |
| 56 | +- 服务注册中心如何高可用 |
| 57 | +- 服务上下线,消费端如何动态感知 |
| 58 | + |
| 59 | +##### 服务注册 |
| 60 | + |
| 61 | + |
| 62 | + |
| 63 | +当我们把服务信息注册上去后,就应该是: |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | +> 服务列表保存通常有三种方式:本地内存、数据库、第三方缓存系统 |
| 68 | +
|
| 69 | +注册上去后,consumer需要服务地址的时候,就可以用相应key去注册中心获取对应的服务列表。 |
| 70 | + |
| 71 | + |
| 72 | + |
| 73 | +> 同一个服务注册中心,我们可以注册多个服务,比如用户服务、商品服务、订单服务... |
| 74 | +
|
| 75 | +##### 服务消费 |
| 76 | + |
| 77 | + |
| 78 | + |
| 79 | +consumer端通过key获取指定的服务地址列表。 |
| 80 | + |
| 81 | +以上的还是蛮简单的吧,简单来说,我们就是引用了一个第三方的服务来存放我们的服务提供者列表。并且以key-value的形式存储,key我们可以理解为服务名称,value就是服务实例列表。 |
| 82 | + |
| 83 | + |
| 84 | + |
| 85 | + |
| 86 | + |
| 87 | +##### 注册中心高可用 |
| 88 | + |
| 89 | +高可用无非就是做集群,我们可以对注册中心部署多个节点。在消费端consumer只需要知道一个服务注册中心集群地址`cluster-url`即可。 |
| 90 | + |
| 91 | + |
| 92 | + |
| 93 | + |
| 94 | + |
| 95 | +##### 动态感知服务上下线 |
| 96 | + |
| 97 | +consumer拿到服务列表后,会把服务列表保存起来,保存到本地缓存里。 |
| 98 | + |
| 99 | + |
| 100 | + |
| 101 | + |
| 102 | + |
| 103 | +consumer通过一定的负载均衡算法,选择出一个地址,最后发起远程的调用。 |
| 104 | + |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | + |
| 109 | +如果我们的服务节点挂掉一个了,怎么办? |
| 110 | + |
| 111 | + |
| 112 | + |
| 113 | + |
| 114 | + |
| 115 | +此时,服务注册中心的服务列表还是之前的列表,如果consumer调用到过掉的节点上,那岂不是会出问题呀。 |
| 116 | + |
| 117 | +所以,我们的服务注册中心需要知道哪个服务节点挂了,然后从对应服务列表里删除。 |
| 118 | + |
| 119 | +有种办法叫做心跳检测`heartBeat`,即就是服务注册中心,每隔一定时间去监测一下provider,如果监测到某个服务挂了,那就把对应服务地址从服务列表中删除。 |
| 120 | + |
| 121 | +> 根据心跳检测,来提出无效服务。 |
| 122 | +
|
| 123 | + |
| 124 | + |
| 125 | + |
| 126 | + |
| 127 | +可是不对呀,此时consumer端本地列表里还有过掉的服务地址,怎么办呢? |
| 128 | + |
| 129 | +或者是,在增加一个新的服务节点 |
| 130 | + |
| 131 | + |
| 132 | + |
| 133 | + |
| 134 | + |
| 135 | +对于服务注册中心来说,就是服务列表里增加一个服务地址。 |
| 136 | + |
| 137 | +但是在消费端存在同样的问题,就是服务注册中心的服务列表和consumer端的服务列表不一样了。 |
| 138 | + |
| 139 | +如何让consumer端也动态感知呢? |
| 140 | + |
| 141 | +其实很简单,此时,我们得思维换一下,因为consumer的服务列表是来自于服务注册中心,我们就可以把consumer理解为消费端,服务注册中心理解为服务端。此时,consumer端就可以去服务端(服务注册中心)拉取provider服务列表。 |
| 142 | + |
| 143 | +通常有两种方案:push和pull |
| 144 | + |
| 145 | +- push:服务注册中心主动推送服务列表给consumer。 |
| 146 | +- pull:consumer主动从注册中心拉取服务列表。 |
| 147 | + |
| 148 | + |
| 149 | + |
| 150 | + |
| 151 | + |
| 152 | +不管是push还是pull,都会存在consumer和服务注册中心的通信管道。如果他们之间断开了,那就无法获取服务列表了。 |
| 153 | + |
| 154 | +还有就是服务注册中心知道consumer的地址,比如 |
| 155 | + |
| 156 | +> 我得知道你的微信好友,不然我怎么把我手里的资源发给你 |
| 157 | +
|
| 158 | +我们的网络通信,必然会存在监听的动作。 |
| 159 | + |
| 160 | +如果服务注册中心要push到consumer,此时他们之间需要建立一个会话,所以,在服务注册中心会维护一个会话管理的模块。还有一种方式就是consumer提供一个`API`,这个`API`给服务注册中心进行回调。 |
| 161 | + |
| 162 | +> 本质是我们是使用HTTP协议还是使用Socket监听 |
| 163 | +
|
| 164 | +push有个不好点,那就是服务注册中心需要维护大量的会话,而且还需要对每个会话维持一个心跳,一遍知晓这些会话状态,得确保这些consumer能收到数据, |
| 165 | + |
| 166 | +另外就是pull,pull其实就相对push就简单多了。pull和我们前面说的心跳机制是类似的,consumer端启动定时任务,每个多久拉取服务注册中心的服务列表。pull也不需要去维护大量的会话,我只需要每隔多久调用接口拉取服务列表即可。但是这里还是会存在一个问题,因为是定时去拉取,所以会存在一定的数据延迟,比如consumer刚刚拉取服务列表,但就在拉取结束的后,某个服务provider挂了,consumer就要等下次拉取才知道对应服务provider挂了。 |
| 167 | + |
| 168 | +> 如果定时任务是每隔30秒拉去一次,那就是说,延迟最长时间是30秒。 |
| 169 | +
|
| 170 | +还有一种方式long-pull,也叫长轮询,是上面两种方案的优化方案,consumer发起拉取请求时,先把这个请求hold住,当服务注册中心有发生变化后,consumer端能立马感知。 |
| 171 | + |
| 172 | +关于长轮询: |
| 173 | + |
| 174 | +> 与简单轮询相似,只是在服务端在没有新的返回数据情况下不会立即响应,而会挂起,直到有数据或即将超时 |
| 175 | +> |
| 176 | +> `优点`:实现也不复杂,同时相对轮询,节约带宽 |
| 177 | +> |
| 178 | +> `缺点`:还是存在占用服务端资源的问题,虽然及时性比轮询要高,但是会在没有数据的时候在服务端挂起,所以会一直占用服务端资源,处理能力变少 |
| 179 | +> |
| 180 | +> `应用`:一些早期的对及时性有一些要求的应用:web IM 聊天 |
| 181 | +
|
| 182 | +这样,我们就搞定了所谓的服务上下线动态感知。 |
| 183 | + |
| 184 | +通过上面的服务注册、服务消费、注册中心高可用以及动态感知服务的上下线,这就是我们去实现一个服务注册中心的通用模型。 |
| 185 | + |
| 186 | +##### 小总结 |
| 187 | + |
| 188 | +关于如何设计一个注册中心,无非重点关以下几点: |
| 189 | + |
| 190 | +- 服务是如何注册 |
| 191 | +- 消费端如何获取服务 |
| 192 | +- 如何保证注册中心的高可用 |
| 193 | +- 动态感知服务的上下线 |
0 commit comments