官方推荐使用zookeeoer注册中心,注册中心负责服务地址的注册和查找,相当于目录服务,提供提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力小。
- Zookeeper的服务注册与发现,主要应用的是Zookeeper的Znode数据模型和Watcher机制,主要分为如下几个步骤:
服务注册:服务提供者(Provider)启动时,会向Zookeeper服务端注册服务信息,即会在Zookeeper服务器上创建一个服务节点,并在节点上存储服务的相关数据(如服务提供者的ip地址、端口等),比如注册一个用户注册服务(user/register):
服务发现:服务消费者(Consumer)启动时,会根据本身依赖的服务信息,向Zookeeper服务端获取注册的服务信息并设置Watch,获取到注册的服务信息之后将服务提供者信息缓存在本地,调用服务时直接根据从Zookeeper注册中心获取到的服务注册信息调用服务,比如发现用户注册服务(user/register)并调用。
服务通知:当服务提供者因为某种原因宕机或不提供服务之后,Zookeeper服务注册中心的对应服务节点会被删除,因为服务消费者在获取服务信息的时候在对应节点上设置了Watch,因此节点删除之后会触发对应的Watcher,Zookeeper注册中心会异步向服务所关联的所有服务消费者发出节点删除的通知,服务消费者根据收到的通知更新缓存的服务列表。
Consul Etcd Zookeeper
zookeeper
用来实现分布式协调服务,提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。分布式应用程序可以基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
特点
除了基于 Docker 或者 Vagrant 虚拟化技术在一台机器上虚拟化出多个节点外,Zookeeper 本身还支持以伪集群的方式在一台机器搭建多个节点。-
集成到 Go Micro 作为注册中心
在服务端和客户端代码中引入 zookeeper 时,会通过初始化函数 init 将其注册到默认的注册中心数组:func init() {
cmd.DefaultRegistries["zookeeper"] = NewRegistry
} 注册实现 src/github.com/micro/go-plugins/registry/zookeeper/zookeeper.go
func (z *zookeeperRegistry) Register(s *registry.Service, opts ...registry.RegisterOption) error {
if len(s.Nodes) == 0 {
return errors.New("Require at least one node")
}
var options registry.RegisterOptions
for _, o := range opts {
o(&options)
}
// create hash of service; uint64
h, err := hash.Hash(s, nil)
if err != nil {
return err
}
// get existing hash
z.Lock()
v, ok := z.register[s.Name]
z.Unlock()
// the service is unchanged, skip registering
if ok && v == h {
return nil
}
service := ®istry.Service{
Name: s.Name,
Version: s.Version,
Metadata: s.Metadata,
Endpoints: s.Endpoints,
}
for _, node := range s.Nodes {
service.Nodes = []*registry.Node{node}
exists, _, err := z.client.Exists(nodePath(service.Name, node.Id))
if err != nil {
return err
}
srv, err := encode(service)
if err != nil {
return err
}
if exists {
_, err := z.client.Set(nodePath(service.Name, node.Id), srv, -1)
if err != nil {
return err
}
} else {
err := createPath(nodePath(service.Name, node.Id), srv, z.client)
if err != nil {
return err
}
}
}
// save our hash of the service
z.Lock()
z.register[s.Name] = h
z.Unlock()
return nil
}
- 服务发现
当我们在客户端请求微服务接口时,首先会通过封装了 Registry 组件的 Selector 层查询服务节点,第一次查询的时候会通过 Registry 组件查询服务节点,这里会走到 zookeeper 插件的 GetService 方法.
func (z *zookeeperRegistry) GetService(name string) ([]*registry.Service, error) {
l, _, err := z.client.Children(servicePath(name))
if err != nil {
return nil, err
}
serviceMap := make(map[string]*registry.Service)
for _, n := range l {
_, stat, err := z.client.Children(nodePath(name, n))
if err != nil {
return nil, err
}
if stat.NumChildren > 0 {
continue
}
b, _, err := z.client.Get(nodePath(name, n))
if err != nil {
return nil, err
}
sn, err := decode(b)
if err != nil {
return nil, err
}
s, ok := serviceMap[sn.Version]
if !ok {
s = ®istry.Service{
Name: sn.Name,
Version: sn.Version,
Metadata: sn.Metadata,
Endpoints: sn.Endpoints,
}
serviceMap[s.Version] = s
}
for _, node := range sn.Nodes {
s.Nodes = append(s.Nodes, node)
}
}
var services []*registry.Service
for _, service := range serviceMap {
services = append(services, service)
}
return services, nil
}