go-micro学习笔记

特性

服务发现

自动服务注册和发现。默认使用consul作为服务发现和注册中心,可通过插件化修改为其他的注册中心,比如etcd,zookeeper等。  

负载均衡

基于服务发现的客户端负载平衡。

消息编码

基于内容类型的动态消息编码。 客户端和服务器将根据内容类型使用对应的编解码器。

同步流

基于RPC的请求/响应,支持双向流。提供了同步通讯的抽象。向服务发出的请求将被自动解析、负载平衡、拨号和流处理。 默认值启用tls时,transport为http / 1.1或http2。

异步消息

PubSub是作为异步通信和事件驱动架构的一等公民而构建的。事件通知是微服务开发的核心模式。启用tls时,默认消息传递是点对点http1.1或http2。

可插拔接口

Go Micro为每个分布式系统抽象出Go接口。这些接口是可插拔的,并且允许Go Micro不依赖于运行时。您可以插入任何底层技术。

micro toolkit

micro是一个工具包。提供了一些工具方便在go-micro微服务架构中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
~ micro -h
NAME:
micro - A cloud-native toolkit
USAGE:
micro [global options] command [command options] [arguments...]
VERSION:
0.13.0
COMMANDS:
api Run the micro API
bot Run the micro bot
cli Start the interactive cli
registry Query registry
call Call a service or function
query Deprecated: Use call instead
stream Create a service or function stream
publish Publish a message to a topic
health Query the health of a service
stats Query the stats of a service
list List items in registry
register Register an item in the registry
deregister Deregister an item in the registry
get Get item from registry
proxy Run the micro proxy
new Create a new micro service by specifying a directory path relative to your $GOPATH
web Run the micro web app
GLOBAL OPTIONS:
--client Client for go-micro; rpc [$MICRO_CLIENT]
--client_request_timeout Sets the client request timeout. e.g 500ms, 5s, 1m. Default: 5s [$MICRO_CLIENT_REQUEST_TIMEOUT]
--client_retries "0" Sets the client retries. Default: 1 [$MICRO_CLIENT_RETRIES]
--client_pool_size "0" Sets the client connection pool size. Default: 1 [$MICRO_CLIENT_POOL_SIZE]
--client_pool_ttl Sets the client connection pool ttl. e.g 500ms, 5s, 1m. Default: 1m [$MICRO_CLIENT_POOL_TTL]
--register_ttl "0" Register TTL in seconds [$MICRO_REGISTER_TTL]
--register_interval "0" Register interval in seconds [$MICRO_REGISTER_INTERVAL]
--server Server for go-micro; rpc [$MICRO_SERVER]
--server_name Name of the server. go.micro.srv.example [$MICRO_SERVER_NAME]
--server_version Version of the server. 1.1.0 [$MICRO_SERVER_VERSION]
--server_id Id of the server. Auto-generated if not specified [$MICRO_SERVER_ID]
--server_address Bind address for the server. 127.0.0.1:8080 [$MICRO_SERVER_ADDRESS]
--server_advertise Used instead of the server_address when registering with discovery. 127.0.0.1:8080 [$MICRO_SERVER_ADVERTISE]
--server_metadata [--server_metadata option --server_metadata option] A list of key-value pairs defining metadata. version=1.0.0 [$MICRO_SERVER_METADATA]
--broker Broker for pub/sub. http, nats, rabbitmq [$MICRO_BROKER]
--broker_address Comma-separated list of broker addresses [$MICRO_BROKER_ADDRESS]
--registry Registry for discovery. consul, mdns [$MICRO_REGISTRY]
--registry_address Comma-separated list of registry addresses [$MICRO_REGISTRY_ADDRESS]
--selector "cache" Selector used to pick nodes for querying [$MICRO_SELECTOR]
--transport Transport mechanism used; http [$MICRO_TRANSPORT]
--transport_address Comma-separated list of transport addresses [$MICRO_TRANSPORT_ADDRESS]
--enable_acme Enables ACME support via Let's Encrypt. ACME hosts should also be specified. [$MICRO_ENABLE_ACME]
--acme_hosts Comma separated list of hostnames to manage ACME certs for [$MICRO_ACME_HOSTS]
--enable_tls Enable TLS support. Expects cert and key file to be specified [$MICRO_ENABLE_TLS]
--tls_cert_file Path to the TLS Certificate file [$MICRO_TLS_CERT_FILE]
--tls_key_file Path to the TLS Key file [$MICRO_TLS_KEY_FILE]
--tls_client_ca_file Path to the TLS CA file to verify clients against [$MICRO_TLS_CLIENT_CA_FILE]
--api_address Set the api address e.g 0.0.0.0:8080 [$MICRO_API_ADDRESS]
--proxy_address Proxy requests via the HTTP address specified [$MICRO_PROXY_ADDRESS]
--web_address Set the web UI address e.g 0.0.0.0:8082 [$MICRO_WEB_ADDRESS]
--api_handler Specify the request handler to be used for mapping HTTP requests to services; {api, proxy, rpc} [$MICRO_API_HANDLER]
--api_namespace Set the namespace used by the API e.g. com.example.api [$MICRO_API_NAMESPACE]
--web_namespace Set the namespace used by the Web proxy e.g. com.example.web [$MICRO_WEB_NAMESPACE]
--api_cors Comma separated whitelist of allowed origins for CORS [$MICRO_API_CORS]
--web_cors Comma separated whitelist of allowed origins for CORS [$MICRO_WEB_CORS]
--proxy_cors Comma separated whitelist of allowed origins for CORS [$MICRO_PROXY_CORS]
--enable_stats Enable stats [$MICRO_ENABLE_STATS]
--help, -h show help
--version print the version

问题

使用micro toolkit,安装其他插件,第一次build缺少相关包,go get 下来,build成功之后运行命令,报如下错误:

1
2
3
4
5
panic: /debug/requests is already registered. You may have two independent copies of golang.org/x/net/trace in your binary, trying to maintain separate state. This may involve a vendored copy of golang.org/x/net/trace.
goroutine 1 [running]:
go.etcd.io/etcd/vendor/golang.org/x/net/trace.init.0()
/Users/doublesouth/Documents/mycode/golang/lib/src/go.etcd.io/etcd/vendor/golang.org/x/net/trace/trace.go:116 +0x16c

解决方法参考该链接:https://github.com/etcd-io/etcd/issues/9357#issuecomment-377560659

使用

  • 手动取消注册

    1
    micro deregister service '{"name":"<service.name>","version":"<service.version>","nodes":[{"id":"<node.id>"}]}'

如何使用

安装服务注册发现中心

本地使用docker安装consul:

  1. 本地安装docker
  2. 运行 docker pull consul 命令从docker hub上拉取最新官方consul镜像
  3. 运行 docker run -d --name=local-consul -e CONSUL_BIND_INTERFACE=eth0 -p 8500:8500 consul 命令启动docker容器,需要暴露8500端口,否则本地服务无法注册到consul

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type HelloWorld struct {
}
// 实现hello_world service中的Hello方法
func (g *HelloWorld) Hello(ctx context.Context, req *hello_world.HelloRequest, rsp *hello_world.HelloResponse) error {
rsp.Greeting = "Hello World: " + req.Name
return nil
}
func main() {
service := micro.NewService(
micro.Name("hello_world"),
micro.Version("latest"),
micro.Metadata(map[string]string{
"type": "helloworld",
}),
)
service.Init()
hello_world.RegisterHelloWorldHandler(service.Server(), new(HelloWorld))
if err := service.Run(); err != nil {
fmt.Println(err) }
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
service := micro.NewService(
micro.Name("hello_world"),
micro.Version("latest"),
micro.Metadata(map[string]string{
"type": "helloworld",
}),
)
service.Init()
greeter := hello_world.NewHelloWorldClient("hello_world", service.Client())
rsp, err := greeter.Hello(context.TODO(), &hello_world.HelloRequest{Name: "Alice"})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(rsp.Greeting)
}

踩过的坑

  1. go-micro框架客户端连接池大小配置不是只建这么多的连接,源码中的处理机制是每次发起客户端请求首先检查池中有没有连接,如果没有连接会直接新建一个连接发起请求,连接用完释放连接的时候会检查池中的连接数是达到配置的连接数,如果达到配置的连接数直接关闭该连接,如果未达到才将该连接放到池中。这就会导致一个问题,如果请求量过大会导致连接一直在创建,最终会报 too many open files 错误。

    下面是go-micro框架中client端获取连接和释放连接的源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    // 获取连接
    func (p *pool) getConn(addr string, tr transport.Transport, opts ...transport.DialOption) (*poolConn, error) {
    p.Lock()
    conns := p.conns[addr]
    now := time.Now().Unix()
    // while we have conns check age and then return one
    // otherwise we'll create a new conn
    // 1. 首先检查池中可用连接数是否大于1
    for len(conns) > 0 {
    conn := conns[len(conns)-1]
    conns = conns[:len(conns)-1]
    p.conns[addr] = conns
    // if conn is old kill it and move on
    if d := now - conn.created; d > p.ttl {
    conn.Client.Close()
    continue
    }
    // we got a good conn, lets unlock and return it
    p.Unlock()
    // 2. 直接返回池中连接
    return conn, nil
    }
    p.Unlock()
    // create new conn
    // 3. 如果池中没有连接了,这时新建一个连接并返回
    c, err := tr.Dial(addr, opts...)
    if err != nil {
    return nil, err
    }
    return &poolConn{c, time.Now().Unix()}, nil
    }
    // 释放连接
    func (p *pool) release(addr string, conn *poolConn, err error) {
    // don't store the conn if it has errored
    if err != nil {
    conn.Client.Close()
    return
    }
    // otherwise put it back for reuse
    p.Lock()
    conns := p.conns[addr]
    // 1. 检查池中连接数是否已达到最大,如果达到最大直接关闭连接
    if len(conns) >= p.size {
    p.Unlock()
    conn.Client.Close()
    return
    }
    // 2. 未达到池中最大连接数将该连接放入池中
    p.conns[addr] = append(conns, conn)
    p.Unlock()
    }
  2. 客户端配置register直接放到main方法获取测试用例中都可以调用到服务端,但是本地同样启个服务端然后在服务里面配置客户端register发现未生效,一直使用默认的consul。

    本地测试代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 这种方式在测试用例中是可以调用成功的
    // 但是如果和server一起启动,就会初始化失败,使用的register始终都是默认的consul
    func TestClient(t *testing.T) {
    var c client.Client
    r := etcdv3.NewRegistry(registry.Addrs("127.0.0.1:2379"))
    c = client.NewClient()
    c.Init(client.Selector(
    selector.NewSelector(selector.Registry(r))),
    client.PoolSize(100), //连接池大小
    client.PoolTTL(5*time.Minute), //长连接缓存时间
    client.Retries(3), //重试次数
    client.RequestTimeout(30*time.Second), //请求超时
    client.DialTimeout(2*time.Second), //客户端连接超时
    client.Transport(tcp.NewTransport()),
    )
    Client = service.NewDemoService("demo.service", c)
    rsp, err := Client.Do(context.TODO(), &Request{
    Say: "hello",
    })
    if err != nil {
    t.Errorf("err %v\n", err)
    }
    }

这篇博客只是关于go-micro的一个简单介绍以及入门使用,笔者也在学习阶段,随着研究的深入也会分享更多关于go-micro的只是给大家。

参考文档

Micro Documentation[go-micro]
Micro Documentation[function]
Micro examples repository
go-micro 框架介绍
Micro-Api文档

小伙伴,如果您觉得文章还不错,欢迎您的支持,我会继续努力创作!