目标
既然是学习使用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是更加容易使用一些。
至于其他的点,完全不懂,以后再补充吧,哈哈。