添加一个微服务实例的时候,微服务就会将自己的 ip 与 port 发送到注册中心,在注册中心里面记录起来。当 API gateway 需要访问某些微服务的时候,就会去注册中心取到相应的 ip 与 port,从而实现自动化操作。
技术选型
名称 | 优点 | 缺点 | 接口 | 一致性算法 |
---|---|---|---|---|
zookeeper | 1.功能强大,不仅仅只是服务发现 2.提供 watcher 机制能实时获取服务提供者的状态 3.dubbo 等框架支持 | 1.没有健康检查 2.需在服务中集成 sdk,复杂度高 3.不支持多数据中心 | sdk | Paxos |
consul | 1.简单易用,不需要集成 sdk 2.自带健康检查 3.支持多数据中心 4.提供 web 管理界面 | 1.不能实时获取服务信息的变化通知 | http/dns | Raft |
etcd | 1.简单易用,不需要集成 sdk 2.可配置性强 | 1.没有健康检查 2.需配合第三方工具一起完成服务发现 3.不支持多数据中心 | http | Raft |
consul安装
docker run -d -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8600:8600/udp consul consul agent -dev -client=0.0.0.0
api文档
实现
user-service注册
配置文件增加consul配置
consul:
host: xx.xx.xx.xx
port: xxxx
定义consul配置结构体
type ConsulConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
}
添加全局变量和初始化
ConsulConfig *models.ConsulConfig
ConsulConfig = &models.ConsulConfig{}
添加解析配置文件代码
err = vp.UnmarshalKey("consul", &global.ConsulConfig)
if err != nil {
panic(any(fmt.Sprintf("Read consul config failed:%v", err)))
}
实现注册函数
func RegisterService(address, name, id string, port int, tags []string) error {
cfg := api.DefaultConfig()
cfg.Address = fmt.Sprintf("%s:%d", global.Config.Consul.Host, global.Config.Consul.Port)
client, err := api.NewClient(cfg)
if err != nil {
panic(any(err))
}
check := &api.AgentServiceCheck{
GRPC: fmt.Sprintf("%s:%d", global.Config.Service.Host, global.Config.Service.Port),
GRPCUseTLS: false,
Timeout: "5s",
Interval: "5s",
DeregisterCriticalServiceAfter: "10s",
}
registration := api.AgentServiceRegistration{}
registration.ID = id
registration.Name = name
registration.Port = port
registration.Address = address
registration.Tags = tags
registration.Check = check
err = client.Agent().ServiceRegister(®istration)
if err != nil {
panic(any(err))
}
return nil
}
主函数调用注册函数
err = initializer.RegisterService(
global.Config.Service.Host, global.Config.Service.Name, global.Config.Service.Id,
global.Config.Service.Port, strings.Split(global.Config.Service.Tags, ","),
)
if err != nil {
zap.S().Fatal("RegisterService failed")
}
可以看到此时,健康检查是失败的,我们需要注册健康检查到grpc中。首先实现健康检查的接口
type HealthImpl struct{}
func (h *HealthImpl) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
return &grpc_health_v1.HealthCheckResponse{
Status: grpc_health_v1.HealthCheckResponse_SERVING,
}, nil
}
func (h *HealthImpl) Watch(*grpc_health_v1.HealthCheckRequest, grpc_health_v1.Health_WatchServer) error {
return nil
}
再在grpc中进行注册
grpc_health_v1.RegisterHealthServer(grpcServer, &initializer.HealthImpl{})
可以看到,此时已经好了。
user-api服务发现
在配置文件增加consul的配置块
consul:
host: xx.xx.xx.xx
port: xxxx
添加consul配置结构体
type ConsulConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
}
添加全局变量
ConsulConfig *models.ConsulConfig
ConsulConfig = &models.ConsulConfig{}
增加解析配置文件
err = vp.UnmarshalKey("consul", &global.ConsulConfig)
if err != nil {
panic(any(fmt.Sprintf("Read consul config faild: %v", err)))
}
实现根据服务名获取地址信息的方法
func FilterServiceByName(name string) (host string, port int, err error) {
cfg := api.DefaultConfig()
cfg.Address = fmt.Sprintf("%s:%d", global.ConsulConfig.Host, global.ConsulConfig.Port)
client, err := api.NewClient(cfg)
if err != nil {
panic(any(err))
}
data, err := client.Agent().ServicesWithFilter(fmt.Sprintf(`Service == "%s"`, name))
if err != nil {
panic(any(err))
}
for _, v := range data {
host = v.Address
port = v.Port
return
}
return
}
替换掉注册user-service的地址信息
host, port, err := utils.FilterServiceByName(global.UserService.Name)
if err != nil {
zap.S().Fatalw("FilterServiceByName", "name", global.UserService.Name, "err", err)
return
}
userConn := initialize.InitUserConnection(host, port)
为了方便测试,我把密码登陆中的验证码注释掉了。可以看到成功的连接到了用户服务并获取到了数据