什么是服务注册中心
服务注册中心是服务实现服务化管理的核心组件,类似于目录服务的作用,主要用来存储服务信息,譬如提供者url 串、路由信息等。 服务注册中心是微服务架构中最基础的设施之一。 注册中心可以说是微服务架构中的“通讯录”,它记录了服务和服务地址的映射关系。
注册中心选型
序号 | 比较项 | Eureka | zookeeper | Nacos | Consul |
---|---|---|---|---|---|
1 | 集群结构 | 平级 | 主从 | 支持平级和主从 | 主从 |
2 | 集群角色 | 主人 | Leader、follower observer | leader、follower、candidate | server-leader、server以及client |
3 | 是否可以及时知道服务状态变化 | 不能及时知道 | 会及时知道 | 不能及时知道 | 不能及时知道 |
4 | 一致性协议(CAP****) | 注重可用性(AP) | 注重一致性(CP) | 支持CP\AP 默认AP | 注重一致性(CP) |
5 | 雪崩保护 | 有 | 没有 | 有 | 没有 |
6 | 社区是否活跃 | Eureka2.0不再维护了 | 持续维护 | 持续维护 | 持续维护 |
7 | 管理端 | 有现成的eureka管理端 | 没有现成的管理端 | 有现成的管理端 | 有现成的管理端 |
8 | 负载均衡策略 | 使用ribbon实现 | 一般可以直接采用RPC的负载均衡 | 权重/metadata/Selector | Fabio |
9 | 权限控制 | 无 | 使用ACL实现节点权限控制 | RBAC-用户、角色、权限 | ACL |
10 | Spring Cloud集成 | 支持 | 支持 | 支持 | 支持 |
11 | 健康检查 | Client Beat | Keep Alive | TCP/HTTP/MYSQL/Client Beat | TCP/HTTP/gRPC/Cmd |
12 | 自动注销实例 | 支持 | 支持 | 支持 | 不支持 |
13 | 访问协议 | HTTP | TCP | HTTP/DNS | HTTP/DNS |
14 | 是否可用作配置中心 | 否 | 是 | 是 | 是 |
15 | 多数据中心 | 不支持 | 不支持 | 不支持 | 支持 |
16 | 跨注册中心同步 | 不支持 | 不支持 | 支持 | 支持 |
17 | Dubbo集成 | 不支持 | 支持 | 支持 | 不支持 |
18 | K8S集成 | 支持 | 支持 | 支持 | 支持 |
架构图
原理
主从集群
优雅停机主动从Nacos下线实例
问题场景
服务注册到 Nacos 上,部署采用的是 k8s + docker 容器部署。
当pod发布升级时,新的 pod 被创建——Nacos 上服务实例加 1,旧的 pod 被关闭——Nacos 上服务实例减 1。
Nacos 通过心跳检测机制,将旧的 pod 实例下线。
在关闭旧的 pod 到下线 pod 之间,存在一定的时间差,导致服务消费方调用接口会发生报错的情况,对业务操作造成一定影响。
解决方案
在 Pod 关闭前设置一个 preStop 钩子,在 preStop 脚本中执行主动从 Nacos 下线本机实例, sleep 25 秒后再执行 Pod 的销毁,从而实现优雅停机。
# 调用从nacos下线接口
curl http://localhost:8080/api/nacos/deregister
# 延迟发送关闭信号到容器进程
sleep 25
代码示例
// 创建clientConfig
clientConnfig := constant.ClientConfig{
TimeoutMs: 5000, // 超时时间
NamespaceId: "95d1f5d2-cc16-4b65-86e0-9ac0357d623d",
NotLoadCacheAtStart: true,
CacheDir: "/tmp/nacos/cache",
LogDir: "/tmp/nacos/log",
RotateTime: "1h", // 日志轮转周期,比如:30m, 1h, 24h, 默认是24h
MaxAge: 3, // 日志最大文件数,默认3
LogLevel: "debug", // 日志默认级别,值必须是:debug,info,warn,error,默认值是info
}
// 创建ServerConfig 至少一个ServerConfig
serverConfigs := []constant.ServerConfig{
{
IpAddr: "172.16.213.134",
ContextPath: "/nacos",
Port: 8848,
Scheme: "http",
},
}
// 创建服务发现客户端
namingClient, err := clients.CreateNamingClient(map[string]interface{}{
"serverConfigs": serverConfigs,
"clientConfig": clientConnfig,
})
// 注册实例
success, err := namingClient.RegisterInstance(vo.RegisterInstanceParam{
Ip: "172.16.231.1",
Port: 8080,
ServiceName: "user-service",
Weight: 1,
Enable: true,
Healthy: true,
Ephemeral: true,
Metadata: map[string]string{"idc": "shanghai"},
ClusterName: "cluster-a",
GroupName: "group-a",
})
// 注销实例
success, err := namingClient.DeregisterInstance(vo.DeregisterInstanceParam{
Ip: "172.16.231.1",
Port: 8080,
ServiceName: "user-service",
Ephemeral: true,
Cluster: "cluster-a",
GroupName: "group-a",
})
// 获取服务信息
services, err := namingClient.GetService(vo.GetServiceParam{
Clusters: []string{"cluster-a"},
ServiceName: "user-service",
GroupName: "group-a",
})
// 获取所有的实例列表
instances, err := namingClient.SelectAllInstances(vo.SelectAllInstancesParam{
Clusters: []string{"cluster-a"},
ServiceName: "user-service",
GroupName: "group_a",
})
// 获取实例列表
instances, err := namingClient.SelectInstances(vo.SelectInstancesParam{
Clusters: []string{"cluster-a"},
ServiceName: "user-service",
GroupName: "group-a",
HealthyOnly: true,
})
// 获取一个健康的实例(加权随机轮询)
instance, err := namingClient.SelectOneHealthyInstance(vo.SelectOneHealthInstanceParam{
Clusters: []string{"cluster-a"},
ServiceName: "user-service",
GroupName: "group-a",
})
// 监听服务变化
err := namingClient.Subscribe(&vo.SubscribeParam{
ServiceName: "service-user",
GroupName: "group-a",
Clusters: []string{"cluster-a"},
SubscribeCallback: func(services []model.SubscribeService, err error) {
log.Printf("\n\n callback return services:%s \n\n", util.ToJsonString(services))
},
})
// 取消服务监听
err := namingClient.Unsubscribe(&vo.SubscribeParam{
ServiceName: "user-service",
Clusters: []string{"cluster-a"},
GroupName: "group-a",
SubscribeCallback: func(services []model.SubscribeService, err error) {
log.Printf("\n\n callback return services:%s \n\n", util.ToJsonString(services))
},
})
// 获取服务名列表
serviceInfos, err := namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{
NameSpace: "95d1f5d2-cc16-4b65-86e0-9ac0357d623d",
GroupName: "group-a",
PageNo: 1,
PageSize: 10,
})