明明白白的聊一下什么是服务发现

2023-04-11 11:54:31 414 林溪

服务注册与发现

相关参考 中文文档

什么是服务注册与发现

  • 服务注册:服务进程在注册中心注册自己的元数据信息。通常包括主机和端口号,有时还有身份验证信息,协议,版本号,以及运行环境的信息。
  • 服务发现:客户端服务进程向注册中心发起查询,来获取服务的信息。服务发现的一个重要作用就是提供给客户端一个可用的服务列表。

服务注册与发现解决了什么问题

项目的演进过程

  • 一般来讲,一个项目的起初阶段,我们并不能判断出有多少用户量,并发量,每日大概有多少pv,uv,所以一开始不可能耗费大量的人力物力来搭建支持百万并发的平台,于是第一台服务器开始表演,集lnmp于一身,也就是就原始的单体架构。
  • 随着用户量增加,服务器的cpu和内存飙升,咋办?把MySQL服务单独拉一台机器吧,后来,一些用户的更新操作导致一些用户无法浏览内容,怎么解决?于是就有了数据库的读写分离,主从架构。
  • 突然有一天扫地阿姨不小心碰了电线,其中一台服务器掉电了,用户所有的请求都报错,随之而来的是一系列投诉电话。于是开始升级集群架构。将应用部署到多台机器上面,如果有一台机器出问题了,不影响其他机器继续提供服务。
  • 业务越来越大,一个项目的代码都超过5个G了。代码之间的耦合很严重,一个地方改动可能引发好几个线上问题,开发成本,测试成本都很大。于是我们开始拆解业务,订单系统,库存系统,积分系统,评价系统。。。。一个大的服务根据业务相关性拆分为很多小的业务系统,这就是微服务拆分。
  • 一个业务可能需要好几个微服务支撑,为了节省资源,最大化利用物理机,我们选择把微服务运行在容器之中,访问的时候,我们可以像我上面通过localhost:9988调用访问,但是一个微服务要动态的增加或者删减服务节点,重启之后ip也会发生变化,我们怎么合理地把请求均衡的分配给所有节点?ip变化之后怎么访问?这就需要用到服务动态注册与发现。
为什么redis不行?

我们刚才说过,需要有一个服务去存放我们的ip和端口,这个时候可能会有这样一个想法,我们根据key-value的存储方式,去存储我们服务的信息不行吗? 答案是否定的,因为作为一个服务发现服务,不仅要保存服务的访问方式(ip+port),而且还需要有服务监听的功能,隔一段时间去监听你的服务是否正常,即健康检查。 第二,可能有多个节点提供一个服务,特别是高并发的时候,我们可能会增加服务,那么我们来服务的实现负载均衡呢。 第三,如果几百个服务我们不可能一个一个去改服务的配置,而且本身作为微服务,每个服务之间应该都是独立的,一个服务的改动不应该影响到其他服务。 所以说一个服务发现的系统至少应该具备服务发现、健康检查、负载均衡和全局分布的键值存储的功能

服务发现框架

  • 我们先来一张图看看服务发现是怎么提供服务的,我们根据服务名去服务发现拉取服务的ip和端口号。然后拿着ip和端口号就可以获取到服务。
通常我们用到的服务发现框架有三种,zookeeper,consul,etcd
  • 我们来对比一下三者的区别,其中zookeeper是用java编写的,通过sdk去调用。consul和etcd都是用go编写的,但是consul功能更丰富一点,而且有web管理界面,也是go项目中最常用到的。
consul
在这里插入图片描述
  • consul是分布式的、高可用、横向扩展的。

  • service discovery:consul通过DNS或者HTTP接口使服务注册和服务发现变的很容易,一些外部服务,例如saas提供的也可以一样注册。

  • health checking:健康检测使consul可以快速的告警在集群中的操作。和服务发现的集成,可以防止服务转发到故障的服务上面。

  • key/value storage:一个用来存储动态配置的系统。提供简单的HTTP接口,可以在任何地方操作。

  • multi-datacenter:无需复杂的配置,即可支持任意数量的区域。

  • 安装 相关安装地址

  • 为了方便,我这里使用docker启动,相关命令如下 相关参考

zhangguofu@localhost ~ $ docker run -d -p 8500:8500 -p 8300:8309 -p 8301:8301 -p8302:8302 -p 8600:8600/udp consul consul agent -dev -client=0.0.0.0
Unable to find image 'consul:latest' locally
latest: Pulling from library/consul
895e193edb51: Pull complete
74b324295dd0: Pull complete
9ba21ab1eb9f: Pull complete
3c08861b7dc5: Pull complete
e2c643221233: Pull complete
fe56612bde98: Pull complete
Digest: sha256:28b50d5bfc7f95d515d19590d44f1cf566e550545e653452c2046c92b6a07e7e
Status: Downloaded newer image for consul:latest
530e4f9e489933e2145311fd97ad38ccf9b5ae778d17e3fd7feeeeed46cc5672
  • 其中8500端口是提供给外部的web页面,即web ui,默认有一个consul服务
  • 8300 server rpc 端口同一数据中心 consul server 之间通过该端口通信
  • 8301 serf lan 端口,同一数据中心 consul client 通过该端口通信
  • 8302 serf wan 端口:不同数据中心 consul server 通过该端口通信
  • 8600 dns 端口,用于服务发现。
API接口
  • 为了方便理解,我们先从最简单的api开始入手
  • 相关参考相关参考
  • consul给我们提供了api接口用于服务的注册,注销,健康检测等服务。
  • Register Service
zhangguofu@bogon ~ $ curl -X PUT -H "Content-Type: application/json" -d '{"Name": "my-service", "Address": "localhost", "Port": 5000, "Tags": ["tag1", "tag2"]}' http://localhost:8500/v1/agent/service/register
在这里插入图片描述
  • 查询服务列表
zhangguofu@bogon ~ $ curl http://localhost:8500/v1/catalog/services
{
    "consul": [],
    "my-service": [
        "tag1",
        "tag2"
    ]
}

  • 查询服务的详细信息
zhangguofu@bogon ~ $ curl http://localhost:8500/v1/catalog/service/my-service
[
    {
        "ID": "06788a51-1192-fed7-11cd-e088eea65330",
        "Node": "530e4f9e4899",
        
        "Address": "127.0.0.1",        
        "Datacenter": "dc1",
        "TaggedAddresses": {
            "lan": "127.0.0.1",
            "lan_ipv4": "127.0.0.1",
            "wan": "127.0.0.1",
            "wan_ipv4": "127.0.0.1"
        },
        "NodeMeta": {
            "consul-network-segment": ""
        },
        "ServiceKind": "",
         # 服务名称
        "ServiceID": "my-service",
         # 服务名称
        "ServiceName": "my-service",
         # 服务标签
        "ServiceTags": [
            "tag1",
            "tag2"
        ],
        # ip地址
        "ServiceAddress": "localhost",
        "ServiceWeights": {
            "Passing": 1,
            "Warning": 1
        },
        "ServiceMeta": {},
        # 服务的端口号
        "ServicePort": 5000,
        "ServiceSocketPath": "",
        "ServiceEnableTagOverride": false,
        "ServiceProxy": {
            "Mode": "",
            "MeshGateway": {},
            "Expose": {}
        },
        "ServiceConnect": {},
        "CreateIndex": 4318,
        "ModifyIndex": 4318
    }
]


  • 删除
curl -X PUT  http://localhost:8500/v1/agent/service/deregister/my-service

注意,在Consul中,如果要使用HTTP API进行服务注册,必须使用v1/agent API端点。这是因为v1/agent是Consul API中与代理进行交互的端点,可以让我们通过API与代理进行通信,从而注册、注销、查询服务等操作。使用v1/agent API端点可以帮助我们更好地管理服务实例,提高服务可用性和可靠性。

在go中使用consul

服务注册
  • 我们先通过一个简单例子看一下
package main
import "fmt"
import  consulapi "github.com/hashicorp/consul/api"

//定义服务注册的信息
type ServiceConfig struct {
 ID      string
 Name    string
 Tags    []string
 Port    int
 Address string
}
//consul的服务地址
var consulIp="127.0.0.1:8500"
//注册
func RegisterSevice(s ServiceConfig) error{
 config:=consulapi.DefaultConfig()
 config.Address = consulIp

 //获取到客户端
 client, err := consulapi.NewClient(config)
 if err != nil {
  fmt.Printf("create consul client : %v\n", err.Error())
  return err
 }
 registration := &consulapi.AgentServiceRegistration{
  ID:      s.ID,
  Name:    s.Name,
  Port:    s.Port,
  Tags:    s.Tags,
  Address: s.Address,
 }

 if err := client.Agent().ServiceRegister(registration); err != nil {
  fmt.Printf("register to consul error: %v\n", err.Error())
  return err
 }
 return nil
}

func main() {
 //先注册一下
 service := ServiceConfig{
  ID:      "9527",
  Name:    "demo_service",
  Tags:    []string{"a", "b"},
  Port:    10111,
  Address: "192.168.0.125",
 }
 err := RegisterSevice(service)
 if err != nil {
  fmt.Printf("register to consul error: %v\n", err.Error())
 }
}

  • 运行完毕之后,在服务列表就出现了
  • 但是这个服务是不存在的,我们通过下面的代码实现服务监控
//注册
func RegisterSevice(s ServiceConfig) error {
 config := consulapi.DefaultConfig()
 config.Address = consulIp

 //获取到客户端
 client, err := consulapi.NewClient(config)
 if err != nil {
  fmt.Printf("create consul client : %v\n", err.Error())
  return err
 }
 registration := &consulapi.AgentServiceRegistration{
  ID:      s.ID,
  Name:    s.Name,
  Port:    s.Port,
  Tags:    s.Tags,
  Address: s.Address,
 }
 //开启健康检测
 check := &consulapi.AgentServiceCheck{}
 //检测服务地址
 check.TCP = fmt.Sprintf("%s:%d",service.Address,service.Port )
 //设置过期时间
 check.Timeout="5s"
 //每5s执行一次
 check.Interval="5s"
 //检查失败超过20s将会被注销
 check.DeregisterCriticalServiceAfter = "20s"
 //将check属性添加上去
 registration.Check=check

 if err := client.Agent().ServiceRegister(registration); err != nil {
  fmt.Printf("register to consul error: %v\n", err.Error())
  return err
 }
 return nil
}
  • 执行完毕之后,我们发现健康检查返回结果
  • 那么我来注册一个可用的服务
func tcpService(){
 network:="tcp"
 address:=fmt.Sprintf("%s:%d",service.Address,service.Port )
 //绑定和监听tpc和端口
 listen, err := net.Listen(network, address)
 if err != nil {
  fmt.Println("listen err")
 }
 //关闭监听
 defer listen.Close()

 for{
  //等待连接
  _,err:=listen.Accept()
  if err != nil {
   fmt.Println("accept error")
  }

 }
}
在这里插入图片描述
  • 我们也可以注册http服务
func httpService() {
 url:=fmt.Sprintf("%s:%d",service.Address, service.Port)
 http.HandleFunc("/", func(w http.ResponseWriter,r *http.Request){
  fmt.Printf("run http service in %s",url)
 })
 //监听http请求
 err := http.ListenAndServe(url, nil)
 if err != nil {
  fmt.Printf("http service error")
 }
}

修改一下服务地址

  • 批量注册服务
//注册
func RegisterSevice(s ServiceConfig) error {
 config := consulapi.DefaultConfig()
 config.Address = consulIp

 //获取到客户端
 client, err := consulapi.NewClient(config)
 if err != nil {
  fmt.Printf("create consul client : %v\n", err.Error())
  return err
 }
 // 定义要注册的服务实例
 services := []*consulapi.AgentServiceRegistration{
  {
   ID:      "service-1",
   Name:    "test-service",
   Address: "192.168.1.10",
   Port:    8080,
   Tags:    []string{"v1"},
   Check: &consulapi.AgentServiceCheck{
    HTTP:     "http://192.168.1.10:8080/health",
    Interval: "10s",
   },
  },
  {
   ID:      "service-2",
   Name:    "test-service",
   Address: "192.168.1.11",
   Port:    8080,
   Tags:    []string{"v2"},
   Check: &consulapi.AgentServiceCheck{
    HTTP:     "http://192.168.1.11:8080/health",
    Interval: "10s",
   },
  },
 }

 for _, service := range services {
  err = client.Agent().ServiceRegister(service)
  if err != nil {
   fmt.Println("注册服务失败:", err)
   return nil
  }
  fmt.Println("注册服务成功:", service.ID)
 }
 return nil
}

服务发现
  • 我们通过向consul发送请求来获取服务列表
package main

import (
 "encoding/json"
 "fmt"
 consulapi "github.com/hashicorp/consul/api"
)

//consul的服务地址
var consulIp = "127.0.0.1:8500"
var serviceName = "demo_service"

func main() {
 //请求consul
 config := consulapi.DefaultConfig()
 config.Address = consulIp
 client, err := consulapi.NewClient(config)
 if err != nil {
  fmt.Printf("consul client error: %v", err)
 }

 service, _, err := client.Health().Service(serviceName, "", false, nil)
 if err != nil {
  fmt.Printf("get service error: %v", err)
 }

 addr := ""
 for _, v := range service {
  j, err := json.Marshal(v)
  if err != nil {
   return
  }
  fmt.Printf("%s \n\n", j)
  addr = fmt.Sprintf("http://%s:%d", v.Service.Address, v.Service.Port)
 }
 fmt.Printf("the service is %s",addr)

}


在这里插入图片描述