Go | Go 使用 consul 做服务发现
<h1 id="go-使用-consul-做服务发现">Go 使用 consul 做服务发现</h1><hr>
<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>Go 使用 consul 做服务发现</li><li>前言</li><li>一、目标</li><li>二、使用步骤<ul><li>1. 安装 consul</li><li>2. 服务注册<ul><li>定义接口</li><li>具体实现</li><li>测试用例</li></ul></li><li>3. 服务发现<ul><li>接口定义</li><li>具体实现</li><li>测试用例</li></ul></li></ul></li><li>总结</li><li>参考</li></ul></div><p></p>
<hr>
<h1 id="前言">前言</h1>
<p>前面一章讲了微服务的一些优点和缺点,那如何做到</p>
<h1 id="一目标">一、目标</h1>
<h1 id="二使用步骤">二、使用步骤</h1>
<h2 id="1-安装-consul">1. 安装 consul</h2>
<p>我们可以直接使用官方提供的二进制文件来进行安装部署,其官网地址为 https://www.consul.io/downloads<br>
<img src="https://img-blog.csdnimg.cn/20201012225904133.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p5bmRldg==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述" loading="lazy"><br>
下载后为可执行文件,在我们开发试验过程中,可以直接使用 <code>consul agent -dev</code> 命令来启动一个单节点的 consul</p>
<p>在启动的打印日志中可以看到 <code>agent: Started HTTP server on 127.0.0.1:8500 (tcp)</code>, 我们可以在浏览器直接访问 <code>127.0.0.1:8500</code> 即可看到如下<br>
<img src="https://img-blog.csdnimg.cn/20201012231011560.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p5bmRldg==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述" loading="lazy"><br>
这里我们的 consul 就启动成功了</p>
<h2 id="2-服务注册">2. 服务注册</h2>
<p>在网络编程中,一般会提供项目的 IP、PORT、PROTOCOL,在服务治理中,我们还需要知道对应的服务名、实例名以及一些自定义的扩展信息</p>
<p>在这里使用 <code>ServiceInstance</code> 接口来规定注册服务时必须的一些信息,同时用 <code>DefaultServiceInstance</code> 实现</p>
<pre><code class="language-go">type ServiceInstance interface {
// return The unique instance ID as registered.
GetInstanceId() string
// return The service ID as registered.
GetServiceId() string
// return The hostname of the registered service instance.
GetHost() string
// return The port of the registered service instance.
GetPort() int
// return Whether the port of the registered service instance uses HTTPS.
IsSecure() bool
// return The key / value pair metadata associated with the service instance.
GetMetadata() mapstring
}
type DefaultServiceInstance struct {
InstanceId string
ServiceIdstring
Host string
Port int
Secure bool
Metadata mapstring
}
func NewDefaultServiceInstance(serviceId string, host string, port int, secure bool,
metadata mapstring, instanceId string) (*DefaultServiceInstance, error) {
// 如果没有传入 IP 则获取一下,这个方法在多网卡的情况下,并不好用
if len(host) == 0 {
localIP, err := util.GetLocalIP()
if err != nil {
return nil, err
}
host = localIP
}
if len(instanceId) == 0 {
instanceId = serviceId + "-" + strconv.FormatInt(time.Now().Unix(), 10) + "-" + strconv.Itoa(rand.Intn(9000)+1000)
}
return &DefaultServiceInstance{InstanceId: instanceId, ServiceId: serviceId, Host: host, Port: port, Secure: secure, Metadata: metadata}, nil
}
func (serviceInstance DefaultServiceInstance) GetInstanceId() string {
return serviceInstance.InstanceId
}
func (serviceInstance DefaultServiceInstance) GetServiceId() string {
return serviceInstance.ServiceId
}
func (serviceInstance DefaultServiceInstance) GetHost() string {
return serviceInstance.Host
}
func (serviceInstance DefaultServiceInstance) GetPort() int {
return serviceInstance.Port
}
func (serviceInstance DefaultServiceInstance) IsSecure() bool {
return serviceInstance.Secure
}
func (serviceInstance DefaultServiceInstance) GetMetadata() mapstring {
return serviceInstance.Metadata
}
</code></pre>
<h3 id="定义接口">定义接口</h3>
<p>在上面规定了需要注册的服务的必要信息,下面定义下服务注册和剔除的方法</p>
<pre><code class="language-go">type ServiceRegistry interface {
Register(serviceInstance cloud.ServiceInstance) bool
Deregister()
}
</code></pre>
<h3 id="具体实现">具体实现</h3>
<p>因为 consul 提供了 http 接口来对consul 进行操作,我们也可以使用 http 请求方式进行注册和剔除操作,具体 http 接口文档见 <code>https://www.consul.io/api-docs</code>,consul 默认提供了go 语言的实现,这里直接使用<code>github.com/hashicorp/consul/api</code></p>
<pre><code class="language-go">import (
"errors"
"fmt"
"github.com/hashicorp/consul/api"
"strconv"
"unsafe"
)
type consulServiceRegistry struct {
serviceInstances mapmapcloud.ServiceInstance
client api.Client
localServiceInstance cloud.ServiceInstance
}
func (c consulServiceRegistry) Register(serviceInstance cloud.ServiceInstance) bool {
// 创建注册到consul的服务到
registration := new(api.AgentServiceRegistration)
registration.ID = serviceInstance.GetInstanceId()
registration.Name = serviceInstance.GetServiceId()
registration.Port = serviceInstance.GetPort()
var tags []string
if serviceInstance.IsSecure() {
tags = append(tags, "secure=true")
} else {
tags = append(tags, "secure=false")
}
if serviceInstance.GetMetadata() != nil {
var tags []string
for key, value := range serviceInstance.GetMetadata() {
tags = append(tags, key+"="+value)
}
registration.Tags = tags
}
registration.Tags = tags
registration.Address = serviceInstance.GetHost()
// 增加consul健康检查回调函数
check := new(api.AgentServiceCheck)
schema := "http"
if serviceInstance.IsSecure() {
schema = "https"
}
check.HTTP = fmt.Sprintf("%s://%s:%d/actuator/health", schema, registration.Address, registration.Port)
check.Timeout = "5s"
check.Interval = "5s"
check.DeregisterCriticalServiceAfter = "20s" // 故障检查失败30s后 consul自动将注册服务删除
registration.Check = check
// 注册服务到consul
err := c.client.Agent().ServiceRegister(registration)
if err != nil {
fmt.Println(err)
return false
}
if c.serviceInstances == nil {
c.serviceInstances = mapmapcloud.ServiceInstance{}
}
services := c.serviceInstances
if services == nil {
services = mapcloud.ServiceInstance{}
}
services = serviceInstance
c.serviceInstances = services
c.localServiceInstance = serviceInstance
return true
}
// deregister a service
func (c consulServiceRegistry) Deregister() {
if c.serviceInstances == nil {
return
}
services := c.serviceInstances
if services == nil {
return
}
delete(services, c.localServiceInstance.GetInstanceId())
if len(services) == 0 {
delete(c.serviceInstances, c.localServiceInstance.GetServiceId())
}
_ = c.client.Agent().ServiceDeregister(c.localServiceInstance.GetInstanceId())
c.localServiceInstance = nil
}
// new a consulServiceRegistry instance
// token is optional
func NewConsulServiceRegistry(host string, port int, token string) (*consulServiceRegistry, error) {
if len(host) < 3 {
return nil, errors.New("check host")
}
if port <= 0 || port > 65535 {
return nil, errors.New("check port, port should between 1 and 65535")
}
config := api.DefaultConfig()
config.Address = host + ":" + strconv.Itoa(port)
config.Token = token
client, err := api.NewClient(config)
if err != nil {
return nil, err
}
return &consulServiceRegistry{client: *client}, nil
}
</code></pre>
<h3 id="测试用例">测试用例</h3>
<p>注册服务的代码基本完成,来测试一下</p>
<pre><code class="language-go">func TestConsulServiceRegistry(t *testing.T) {
host := "127.0.0.1"
port := 8500
registryDiscoveryClient, _ := extension.NewConsulServiceRegistry(host, port, "")
ip, err := util.GetLocalIP()
if err != nil {
t.Error(err)
}
serviceInstanceInfo, _ := cloud.NewDefaultServiceInstance("go-user-server", "", 8090,
false, mapstring{"user":"zyn"}, "")
registryDiscoveryClient.Register(serviceInstanceInfo)
r := gin.Default()
// 健康检测接口,其实只要是 200 就认为成功了
r.GET("/actuator/health", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
err = r.Run(":8090")
if err != nil{
registryDiscoveryClient.Deregister()
}
}
</code></pre>
<p>如果成功,则会在 consul 看到 go-user-server 这个服务</p>
<h2 id="3-服务发现">3. 服务发现</h2>
<p>在服务发现中,一般会需要两个方法</p>
<ol>
<li>获取所有的服务列表</li>
<li>获取指定的服务的所有实例信息</li>
</ol>
<h3 id="接口定义">接口定义</h3>
<pre><code class="language-go">type DiscoveryClient interface {
/**
* Gets all ServiceInstances associated with a particular serviceId.
* @param serviceId The serviceId to query.
* @return A List of ServiceInstance.
*/
GetInstances(serviceId string) ([]cloud.ServiceInstance, error)
/**
* @return All known service IDs.
*/
GetServices() ([]string, error)
}
</code></pre>
<h3 id="具体实现-1">具体实现</h3>
<p>来实现一下</p>
<pre><code>type consulServiceRegistry struct {
serviceInstances mapmapcloud.ServiceInstance
client api.Client
localServiceInstance cloud.ServiceInstance
}
func (c consulServiceRegistry) GetInstances(serviceId string) ([]cloud.ServiceInstance, error) {
catalogService, _, _ := c.client.Catalog().Service(serviceId, "", nil)
if len(catalogService) > 0 {
result := make([]cloud.ServiceInstance, len(catalogService))
for index, sever := range catalogService {
s := cloud.DefaultServiceInstance{
InstanceId: sever.ServiceID,
ServiceId:sever.ServiceName,
Host: sever.Address,
Port: sever.ServicePort,
Metadata: sever.ServiceMeta,
}
result = s
}
return result, nil
}
return nil, nil
}
func (c consulServiceRegistry) GetServices() ([]string, error) {
services, _, _ := c.client.Catalog().Services(nil)
result := make([]string, unsafe.Sizeof(services))
index := 0
for serviceName, _ := range services {
result = serviceName
index++
}
return result, nil
}
// new a consulServiceRegistry instance
// token is optional
func NewConsulServiceRegistry(host string, port int, token string) (*consulServiceRegistry, error) {
if len(host) < 3 {
return nil, errors.New("check host")
}
if port <= 0 || port > 65535 {
return nil, errors.New("check port, port should between 1 and 65535")
}
config := api.DefaultConfig()
config.Address = host + ":" + strconv.Itoa(port)
config.Token = token
client, err := api.NewClient(config)
if err != nil {
return nil, err
}
return &consulServiceRegistry{client: *client}, nil
}
</code></pre>
<h3 id="测试用例-1">测试用例</h3>
<pre><code class="language-go">func TestConsulServiceDiscovery(t *testing.T) {
host := "127.0.0.1"
port := 8500
token := ""
registryDiscoveryClient, err := extension.NewConsulServiceRegistry(host, port, token)
if err != nil {
panic(err)
}
t.Log(registryDiscoveryClient.GetServices())
t.Log(registryDiscoveryClient.GetInstances("go-user-server"))
}
</code></pre>
<p>结果</p>
<pre><code>consul_service_registry_test.go:57: <nil>
consul_service_registry_test.go:59: [{go-user-server-1602590661-56179 go-user-server 127.0.0.1 8090 false map}] <nil>
</code></pre>
<h1 id="总结">总结</h1>
<p>通过使用 consul api 我们可以简单的实现基于 consul 的服务发现,在通过结合 http rpc 就可简单的实现服务的调用,下面一章来简单讲下 go 如何发起 http 请求,为我们做 rpc 做个铺垫</p>
<blockquote>
<p>具体代码见 https://github.com/zhangyunan1994/lemon</p>
</blockquote>
<h1 id="参考">参考</h1>
<ul>
<li>https://www.consul.io/api-docs</li>
<li>https://github.com/hashicorp/consul/tree/master/api</li>
</ul>
<p><img src="https://img2020.cnblogs.com/blog/1246875/202008/1246875-20200822203040972-1191312426.jpg" alt="白色兔子公众号图片" loading="lazy"></p><br><br>
来源:https://www.cnblogs.com/zyndev/p/13811589.html 感谢楼主分享微服务相关的经验!consul做服务发现确实是个不错的选择,部署简单而且功能强大。
不过我有个小建议,楼主可以考虑在服务注册的时候加上健康检查的设置,这样consul能自动剔除不健康的服务节点。另外 consul 的 raft 协议需要至少3个节点才能保证高可用,生产环境部署的时候需要注意这点。
期待楼主后续能分享更多微服务相关的内容,比如服务间的通信、负载均衡这些~
頁:
[1]