声明
声明
声明
Jaeger官方并没有明确说明其服务注册和服务发现的具体使用和介绍,这部分功能是在分析源码的时候,发现其原理与服务注册和服务发现类似,所以结合自己对服务注册和服务发现的认识,做一次总结,有错还请指点。
TChannel服务注册和服务发现
Jaeger不借助第三方工具也能实现服务注册和服务发现,这部分功能由其依赖的RPC框架提供。
第三方注册——手动注册
1go run cmd/agent/main.go --collector.host-port=192.168.0.10:14267,192.168.0.11:14267
在启动agent的时候,可配置多个collector静态地址,这部分地址会形成一张注册表。
注册表
- 注册表结构
1github.com/uber/tchannel-go/peer.go #59
2type PeerList struct {
3 sync.RWMutex
4 parent *RootPeerList
5 //以hostPort为下标组成注册表
6 peersByHostPort map[string]*peerScore
7 //负载均衡实现
8 peerHeap *peerHeap
9 scoreCalculator ScoreCalculator
10 lastSelected uint64
11}
- 健康检查
1github.com/jaegertracing/jaeger/pkg/discovery/peerlistmgr/peer_list_mgr.go #150
2func (m *PeerListManager) ensureConnections() {
3 peers := m.peers.Copy()
4 minPeers := m.getMinPeers(peers)
5 numConnected, notConnected := m.findConnected(peers)
6 //有一定量的链接,就不进行健康检查
7 if numConnected >= minPeers {
8 return
9 }
10 ......
11 for i := range notConnected {
12 // swap current peer with random from the remaining positions
13 r := i + m.rnd.Intn(len(notConnected)-i)
14 notConnected[i], notConnected[r] = notConnected[r], notConnected[i]
15 // try to connect to current peer (swapped)
16 peer := notConnected[i]
17 m.logger.Info("Trying to connect to peer", zap.String("host:port", peer.HostPort()))
18 //用于控制超时
19 ctx, cancel := context.WithTimeout(context.Background(), m.connCheckTimeout)
20 conn, err := peer.GetConnection(ctx)
21 cancel()
22 if err != nil {
23 m.logger.Error("Unable to connect", zap.String("host:port", peer.HostPort()), zap.Duration("connCheckTimeout", m.connCheckTimeout), zap.Error(err))
24 continue
25 }
26 ......
27 }
28}
29
在注册表上的地址,TChannel都会进行健康检查,每秒进行一次,如果0.25秒没有连接上,视为服务不可用。如果连接成功则保留当前服务实例,供agent提交数据使用。
1github.com/uber/tchannel-go/connection.go #228
2func (ch *Channel) newOutboundConnection(timeout time.Duration, hostPort string, events connectionEvents) (*Connection, error) {
3 conn, err := net.DialTimeout("tcp", hostPort, timeout)
4 if err != nil {
5 if ne, ok := err.(net.Error); ok && ne.Timeout() {
6 ch.log.WithFields(LogField{"hostPort", hostPort}, LogField{"timeout", timeout}).Infof("Outbound net.Dial timed out")
7 err = ErrTimeout
8 }
9 return nil, err
10 }
11
12 return ch.newConnection(conn, hostPort, connectionWaitingToSendInitReq, events), nil
13}
客户端服务发现
- 软负载均衡
1github.com/uber/tchannel-go/peer.go #149
2func (l *PeerList) choosePeer(prevSelected map[string]struct{}, avoidHost bool) *Peer {
3 var psPopList []*peerScore
4 var ps *peerScore
5 ......
6 size := l.peerHeap.Len()
7 for i := 0; i < size; i++ {
8 //把peer从Heap头部弹出来
9 popped := l.peerHeap.popPeer()
10 if canChoosePeer(popped.HostPort()) {
11 ps = popped
12 break
13 }
14 psPopList = append(psPopList, popped)
15 }
16 //不符合的放入Heap尾部
17 for _, p := range psPopList {
18 heap.Push(l.peerHeap, p)
19 }
20
21 if ps == nil {
22 return nil
23 }
24 //符合条件的打分,再放入Heap尾部
25 l.peerHeap.pushPeer(ps)
26 ps.chosenCount.Inc()
27 return ps.Peer
28}
当Agent需要提交数据的时候,会从TChannel的负载均衡获取peer(服务信息),当有多个的时候,TChannel通过轮询方式,查询peer。实现方式:注册表把所有peer放入peerHeap,先把peer从头部弹出,再把peer放回尾部,从而实现轮询策略的负载均衡。
- 重试
1github.com/uber/tchannel-go/retry.go #212
2func (ch *Channel) RunWithRetry(runCtx context.Context, f RetriableFunc) error {
3 var err error
4
5 opts := getRetryOptions(runCtx)
6 rs := ch.getRequestState(opts)
7 defer requestStatePool.Put(rs)
8 //默认重试5次
9 for i := 0; i < opts.MaxAttempts; i++ {
10 rs.Attempt++
11
12 if opts.TimeoutPerAttempt == 0 {
13 err = f(runCtx, rs)
14 } else {
15 attemptCtx, cancel := context.WithTimeout(runCtx, opts.TimeoutPerAttempt)
16 err = f(attemptCtx, rs)
17 cancel()
18 }
19
20 if err == nil {
21 return nil
22 }
23 if !opts.RetryOn.CanRetry(err) {
24 if ch.log.Enabled(LogLevelInfo) {
25 ch.log.WithFields(ErrField(err)).Info("Failed after non-retriable error.")
26 }
27 return err
28 }
29 ......
30 }
31
32 // Too many retries, return the last error
33 return err
34}
网络之间的通讯避免不了网络异常,所以为了提高可用性,重试是其中一种方式。当从负载均衡获取peer提交数据到Collector,如果提交失败,会再从负载均衡获取peer,最多5次,如果5次都不成功就会放弃这次提交。
Consul+docker 服务注册和服务发现
使用consul实现服务注册和服务发现是一件很简单的事情。很多功能都是开箱即用。
准备工作
- 启动Consul——ip:172.18.0.2
1docker run -itd --network=backend \
2-p 8400:8400 -p 8500:8500 -p 8600:53/udp \
3-h node1 progrium/consul -server -bootstrap -ui-dir /ui
- 启动Agent
1docker run \
2-itd --network=backend \
3--name=jaeger-agent \
4-p5775:5775/udp \
5-p6831:6831/udp \
6-p6832:6832/udp \
7-p5778:5778/tcp \
8--dns-search="service.consul" --dns=172.18.0.2 \
9jaegertracing/jaeger-agent \
10/go/bin/agent-linux --collector.host-port=jaeger-collector:14267
- 启动Collector
1#node1
2docker run -itd --network=backend \
3--name=jaeger-collector-node1 \
4-p :14267 \
5--dns-search="service.consul" --dns=172.18.0.2 \
6jaegertracing/jaeger-collector \
7/go/bin/collector-linux \
8--span-storage.type=cassandra \
9--cassandra.keyspace=jaeger_v1_dc \
10--cassandra.servers=cassandra:9042
11
12#node2
13docker run -itd --network=backend \
14--name=jaeger-collector-node2 \
15-p :14267 \
16--dns-search="service.consul" --dns=172.18.0.2 \
17jaegertracing/jaeger-collector \
18/go/bin/collector-linux \
19--span-storage.type=cassandra \
20--cassandra.keyspace=jaeger_v1_dc \
21--cassandra.servers=cassandra:9042
服务注册——自动注册
1docker run -itd --net=backend --name=registrator \
2--volume=/var/run/docker.sock:/tmp/docker.sock \
3gliderlabs/registrator:latest \
4consul://172.18.0.2:8500
使用consul+docker的形式,只要部署好服务,就会被自动注册到consul,十分简单。
注册表
- 查看注册表信息
查看注册表信息http://localhost:8500/ui/#/dc1/nodes/node1
可以看到启动的2个Collector服务ip分别为:172.18.0.5和172.18.0.8
consul提供了很多种健康检查方式:HTTP、TCP、Docker、Shell和TTL。详情可以查看官网。
服务端服务发现
Consul相对于Agent和Collector是远程服务,所以提供了2种服务发现方式:HTTP和DNS,在这里主要使用是DNS,因为简单,轻量。
- DNS和软负载均衡
当Agent通过DNS解析出多个IP的时候,Consul会随机选择一个IP给Agent实现负载均衡。
由于DNS存在缓存,所以有可能出现,服务不健康,一样会被正常解析,所以在默认情况下Consul是没有设置缓存时间,TTL为0,但是也考虑到了不缓存对Consul的压力,所以开放配置,让我们去决定缓存时间点DNS Caching。
总结
TChannel与Consul+docker实现的服务发现和服务注册中都有他们的优缺点:
服务注册
- TChannel
TChannel的服务注册适用于一些基础服务,例如Jaeger就属于一种基础服务,这种服务一旦部署很少会变动。
- Consul + docker
在现在docker流行的大环境下使用Consul实现的服务注册会简单很多,docker有一个特点就是ip地址是动态,所以它很适合业务场景,因为业务经常变动,服务也随着变化。
健康检查
TChannel和Consul都提供了健康检查,但是都只是检测服务是否正在运行,无法了解是否能够正常处理请求。
服务发现
- TChannel
TChannel使用的是客户端服务发现,这种方式相对于Consul的服务端服务发现的优点就是没有了远程网络开销,单点问题。同时缺点就是各个语言都需要自己实现注册表,负载均衡等功能。
- Consul
Consul使用服务端服务发现,它可以很好的和其他服务结合使用,不需要关心注册表,负载均衡等。而且关于网络开销和单点问题都提供了方案。