目录

 

背景

分析

原因

总结


背景

  1. 早先的都采用基于grpc+etcd做服务注册和服务发现,都是正常的
  2. 后来有部分服务采用k8s部署,为了新旧兼容,服务会按照老方式把宿主机的ip注册到etcd上,k8s体系类使用体系类的服务发现,k8s体系外的依然使用原来的方式,互不影响
  3. 但是有服务基于k8s部署之后,发现client调用接口超时,而直接通过ip调用却是正常
  4. 且多数是发生在k8s的出现pod弹性伸缩之后

分析

  1. 服务端注册地址,从etcd来看,是正常的
  2. 服务端直接通过ip调用也正常的,所以排除服务端本身的问题
  3. etcd里面显示的地址的确会出现没有的情况,但是通过日志发现服务端lease维持的心跳却是正常的,一度觉得怀疑人生~~
  4. 后来发现是下面的原因A导致的,
  5. 解决后,发现依然有问题,O(∩_∩)O,很奇怪~
  6. 通过打开grpc日志,发现grpc的client端会收到地址列表为空的问题:balancerWrapper: got update addr from Notify:[]
  7. 但是etcd里面显示的注册地址却都是存在的,一度觉得怀疑人生~~
  8. 后来通过阅读grcp源码分析,增加日志排查 :google.golang.org/grpc/balancer.go  文件中 函数 (rr *roundRobin) watchAddrUpdates()  里面,增加 etcd watch的数据变动行为,发现了原因
  9. 通过下面的原因B的处理方式解决

原因

  1. 问题存在两个
  2. 原因A
    1. 早先服务注册:类似于

      key:service/go_server_xx/10.1.1.1:9999
      val:{"Op":0,"Addr":"10.1.1.1:9999","Metadata":null}

    2. 并且通过etcd的lease机制维持心跳

    3. 假设一个宿主机起了两个pod,也就两个服务,ser1和ser2
    4. ser1启动的时候会注册 service/go_server_xx/10.1.1.1:9999 并且绑定 lease1,并且维持lease1心跳
    5. 之后,ser2启动的时候也会注册 service/go_server_xx/10.1.1.1:9999 并且绑定 lease2,并且维持lease2心跳,住以此时这个key和lease1解绑了
    6. 当发生弹性伸缩的时候,比如ser2关闭了,此时 lease2过期,同时与其绑定的key也被删除,所以client会发生找不到地址列表的问题
    7. 但是从服务端ser1来看,依然提供服务,并且与etcd的lease1心跳也正常~~,哈哈
  3. 解决方法A
    1. 通过在key后边增加后缀解决,类似于:service/go_server_xx/lease1/10.1.1.1:9999, 这样即使一个宿主机上有多个服务,各自也会注册各自的
  4. 原因B
    1. 上述问题解决之后,还有个问题
    2. 比如同一个宿主机注册的两个服务地址

      key1:service/go_server_xx/lease1/10.1.1.1:9999
      val1:{"Op":0,"Addr":"10.1.1.1:9999","Metadata":null}
      和
      key2:service/go_server_xx/lease2/10.1.1.1:9999
      val2:{"Op":0,"Addr":"10.1.1.1:9999","Metadata":null}
      

       

    3. 在client端watch etcd变化的时候会发现两个add行为,但是 由于val中的地址是一样的,可用addrs列表里面只会保存一个 10.1.1.1:9999,另一个会跳过:
      grpclog.Infoln("grpc: The name resolver wanted to add an existing address: ", addr)
    4. 当发生弹性伸缩的时候,比如ser2关闭了
      1. watch etcd变化的时候会收到一个del行为,这个时候会删除10.1.1.1:9999
      2. 这样client可用地址列表里面就是空的了
      3. grpc的client对象收到 balancerWrapper: got update addr from Notify:[]
  5. 解决方法B:
    1. 注册的时候增加metadata做区分就可以了,类似

      key1:service/go_server_xx/lease1/10.1.1.1:9999
      val1:{"Op":0,"Addr":"10.1.1.1:9999","Metadata":lease1}
      和
      key2:service/go_server_xx/lease2/10.1.1.1:9999
      val2:{"Op":0,"Addr":"10.1.1.1:9999","Metadata":lease2}
    2. 这样增加的时候,可用地址列表里面就会增加两个元素

总结

etcd+grpc的一整套机制(服务注册&服务发现等)是好的

k8s的那一套机制也是好的

但是两者结合使用,就得注意很多地方,要不然会出现很多蛋疼的问题提

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐