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

服务注册与发现

相关参考
中文文档

什么是服务注册与发现

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

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

项目的演进过程

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

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

服务发现框架

  • 我们先来一张图看看服务发现是怎么提供服务的,我们根据服务名去服务发现拉取服务的ip和端口号。然后拿着ip和端口号就可以获取到服务。
    明明白白的聊一下什么是服务发现_第2张图片
通常我们用到的服务发现框架有三种,zookeeper,consul,etcd
  • 我们来对比一下三者的区别,其中zookeeper是用java编写的,通过sdk去调用。consul和etcd都是用go编写的,但是consul功能更丰富一点,而且有web管理界面,也是go项目中最常用到的。
    明明白白的聊一下什么是服务发现_第3张图片
consul

明明白白的聊一下什么是服务发现_第4张图片

  • 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 端口,用于服务发现。
    明明白白的聊一下什么是服务发现_第5张图片
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

明明白白的聊一下什么是服务发现_第6张图片

  • 查询服务列表
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())
	}
}

  • 运行完毕之后,在服务列表就出现了
    明明白白的聊一下什么是服务发现_第7张图片
  • 但是这个服务是不存在的,我们通过下面的代码实现服务监控
//注册
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
}
  • 执行完毕之后,我们发现健康检查返回结果
    明明白白的聊一下什么是服务发现_第8张图片
    明明白白的聊一下什么是服务发现_第9张图片
  • 那么我来注册一个可用的服务
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")
		}

	}
}

明明白白的聊一下什么是服务发现_第10张图片

  • 我们也可以注册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")
	}
}

修改一下服务地址
明明白白的聊一下什么是服务发现_第11张图片
明明白白的聊一下什么是服务发现_第12张图片
明明白白的聊一下什么是服务发现_第13张图片

  • 批量注册服务
//注册
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)

}


明明白白的聊一下什么是服务发现_第14张图片

  • 更多用法参考

你可能感兴趣的:(微服务,数据库,golang,服务发现)