go 使用consul实现服务注册发现

目标

既然是学习使用consul实现服务注册发现,目标自然是实现一个程序,可以通过consul,发现另一个程序。

为此我准备了3个机器:

1.云服务器A(ubuntu):运行consul agent

2.云服务器B(ubuntu):运行一个服务,并在服务启动时注册到consul上

3.本机(mac):运行一个程序,可以通过云服务器A上的consul,发现云服务器B上的服务

启动consul agent

首先,要在云服务器A上启动consul agent。

如果你的服务器上还没安装consul,可以参考这篇ubuntu安装consul

使用一下命令启动consul agent。

consul agent -dev -client 0.0.0.0

注意,一定要加-client 0.0.0.0,否则其他机器是无法访问consul的。

服务注册

consul agent跑起来之后,我们就可以写个简单的服务程序,然后让它注册到consul上了。

注意,下面这段程序使用了consul官方提供的api包,如果你机器还没有这个包,那就需要先装一下,可以参考这篇go 安装consul包("github.com/hashicorp/consul/api")

//本段程序参考了https://www.cnblogs.com/hcy-fly/p/10826607.html博客中的内容
package main

import (
	"fmt"
	consulapi "github.com/hashicorp/consul/api"
	"net/http"
)

const (
	consulAddress = "consul agent ip:8500"
	localIp       = "your server ip"
	localPort     = 81
)

func consulRegister()  {
	// 创建连接consul服务配置
	config := consulapi.DefaultConfig()
	config.Address = consulAddress
	client, err := consulapi.NewClient(config)
	if err != nil {
		fmt.Println("consul client error : ", err)
	}

	// 创建注册到consul的服务到
	registration := new(consulapi.AgentServiceRegistration)
	registration.ID = "337"
	registration.Name = "service337"
	registration.Port = localPort
	registration.Tags = []string{"testService"}
	registration.Address = localIp

	// 增加consul健康检查回调函数
	check := new(consulapi.AgentServiceCheck)
	check.HTTP = fmt.Sprintf("http://%s:%d", registration.Address, registration.Port)
	check.Timeout = "5s"
	check.Interval = "5s"
	check.DeregisterCriticalServiceAfter = "30s" // 故障检查失败30s后 consul自动将注册服务删除
	registration.Check = check

	// 注册服务到consul
	err = client.Agent().ServiceRegister(registration)
}

func Handler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("you are visiting health check api"))
}

func main()  {
	consulRegister()
	//定义一个http接口
	http.HandleFunc("/", Handler)
	err := http.ListenAndServe("0.0.0.0:81", nil)
	if err != nil {
		fmt.Println("error: ", err.Error())
	}
}

将上面这段程序,拷贝到云服务器B上任意路径下运行即可,注意把里面的两个服务器ip改一下,这里就不透露本人的服务器ip了哈哈。

测试服务跑起来之后,在本机的浏览器上输入

http://ip:8500/ui/

打开consul ui,即可看到,刚刚跑起来的服务,已经注册到consul上啦~

点击service337,就可以看到服务的地址了:

到这里,我们已经将云服务器B上的服务信息,成功注册到consul了。 

不要着急往下进行,注意到程序里还有一段关于consul健康检查的。健康检查是服务注册发现组件必不可少的功能,有了健康检查,服务注册上之后,如果挂了,consul是可以知道的。

在consul ui界面,点击337,可以看到如下信息:

看到output信息,正是我们接口函数里设置的输出内容,由此可以看出,consul的健康检查,就是定期执行:

curl ip:port/ 

如果结果是200ok,健康检查即为成功。

到这里,我们一定要注意的是,你的服务,一定要处理ip:port/这个请求,不然即使你的服务运行的好好的,健康检查也是无法通过的。

健康检查有三个重要的参数,在程序里就是这三个:

check.Timeout = "5s"
check.Interval = "5s"
check.DeregisterCriticalServiceAfter = "30s"

其中:

Interval是执行健康检查的时间间隔,程序里设置为了5s,也就是consul agent每隔5s会执行一次健康检查;

Timeout是健康检查超时时间,程序里设置为了5s,也就是如果consul超过5s没有收到健康检查的回复,就会认为服务出现问题,并将该服务的状态置为critical;

DeregisterCriticalServiceAfter是注册信息删除时间,程序里设置为了30s,也就是服务如果保持critical状态超过30s,consul就会将该服务的注册信息彻底删除。

下面来验证一下,首先,control+c手动停掉我们的服务,然后过5s观察consul界面,发现变成了这样:

可以看到,健康检查失败了。

再过30s,再刷新界面,发现变成了404,说明这个服务的信息已经被删除了。

服务发现

我们再把上面云服务器B上的服务跑起来,让它重新注册到consul上,然后,就要开始做服务发现啦~

相对于服务注册,服务发现要简单一些,其实就是一次简单的查询,直接上代码:

//本段程序参考了https://www.cnblogs.com/hcy-fly/p/10826607.html博客中的内容
package main

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

const (
	consulAgentAddress = "your consul agent ip:8500"
)

// 从consul中发现服务
func ConsulFindServer()  {
	// 创建连接consul服务配置
	config := consulapi.DefaultConfig()
	config.Address = consulAgentAddress
	client, err := consulapi.NewClient(config)
	if err != nil {
		fmt.Println("consul client error : ", err)
	}

	// 获取指定service
	service, _, err := client.Agent().Service("337", nil)
	if err == nil{
		fmt.Println(service.Address)
		fmt.Println(service.Port)
	}

    	//只获取健康的service
	//serviceHealthy, _, err := client.Health().Service("service337", "", true, nil)
	//if err == nil{
	//	fmt.Println(serviceHealthy[0].Service.Address)
	//}

}

func main()  {
	ConsulFindServer()
}

这段程序的运行结果如下:

可以看到,程序成功的获取到了云服务器B上那个服务的ip和端口信息。注意上面程序中注释的一段,这是另一种服务发现的接口,与上面不同的是,这个接口可以只获取状态为健康的服务。

服务挂了,调用者怎么知道?

最后补充一点,如果服务挂了,那么这个服务的调用者怎么才能知道呢?

说到这里就不得不提另一种服务治理组件zookeeper了,zookeeper可以通过watcher机制,将服务状态改变直接通知订阅者。

那么consul有没有这种机制呢?答案是:并没有。。。

consul提供的是http接口,而http嘛,就是只能你请求,它不会主动给你发东西。。。所以,也就无法支持通知功能啦~

consul为这个问题提供的解决办法是:长轮询。

所谓长轮询,就是指服务器收到请求之后,发现请求的内容没有变化,就先不回复你,直到请求的内容变化了,或者超时了,再返回结果,这样就也实现了,服务状态改变,调用者可以及时获取。

比起普通轮训,长轮询的优点是,不用在服务没有任何变化的情况下还一直返回东西了,节约了网络资源。

长轮询的缺点是,如果收到请求时,请求内容没有变化,连接会挂起,也就是占用了一个连接资源。

但是zookeeper的watcher机制,实际上也要在zk客户端和zk服务器之间保持tcp连接,也要一直占用一个连接,所以从这一点来看,长轮询跟watcher相比,并不吃亏。

对于服务调用者而言,不管是使用consul的长轮询,还是zookeeper的watcher机制,获取服务的状态变化都是会有延迟的,因为zookeeper也有类似于consul的健康机制,同样是通过服务器和客户端之间的定期通信检查的服务状态,也就是说,延迟的产生,是因为consul或者zookeeper需要在服务健康检查失败后才知道服务挂了。这个延迟嘛,就很难避免了,因为总不能指望服务挂了的时候,自己通知consul或者zookeeper一声吧,服务要是能知道自己什么时候会挂,也就不会挂了。。。

最后,既然都提到zookeeper了,就简单对比一下:

1.通知机制:上面说过了,我的结论是,差不多

2.一致性协议:consul使用raft,zookeeper使用给予paxos的zab,这两个我暂时没有研究过,无法评论孰好孰坏。

3.使用:consul支持http接口,这篇博客使用了consul的api包,其实就是把http调用封装成了函数,更好用了一些,而zookeeper就只能使用客户端,把客户端集成到程序里。从这一点来看,consul是更加容易使用一些。

至于其他的点,完全不懂,以后再补充吧,哈哈。

发布了39 篇原创文章 · 获赞 25 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/u013536232/article/details/104235282