SideCar架构问题排查流程

SideCar架构问题排查流程 #

中台微服务名词解释 #

appid #

  • 中台创建的微服务会分配一个唯一的32key,16进制小写 a8defa95f68246edb5389d8f5d7c8157
  • 当一个微服务被部署在多份时,appid相同,会存储在部署容器的环境变量中 IDG_APPID

appkey #

  • 微服务部署时分配的一个32key小写 hzal0012xr8fsidchp5j9q12l3csdecs
  • appkey + channel可以确定唯一的部署实例,对应到一个unique_id
  • appid => appkey 一对多
  • 环境变量 IDG_APPKEY(只有开发环境的容器会注入此环境变量)

channel #

  • 部署时分配的业务通道,数值类型,例:2399….
  • 0,1号通道保留,默认从2开始递增,由部署侧分配
  • appkey => channel 一对多
  • 环境变量 IDG_CHANNEL(只有开发环境的容器会注入此环境变量)

uniuqe_id #

  • 部署时分配的实例唯一标识,类似于k8s中的service,是MSP服务治理体系的最小单元
  • 同一个unique_id 可以对应多个pod,多个pod环境变量中unique_id 相同
  • unique_id => appkey + channel 一对多
  • appid => unique_id 一对多
  • msp平台叫做服务识别号
  • 环境变量 IDG_UNIQUEID(所有容器都会有)

channel_alias #

  • 别名alias,是调用关系中开发者自己设置的一个上下级调用关系别名,如果不设置则默认是default

account_id #

  • 用户id,类似user_id,32位字符串

sub_org_key #

  • 机构的唯一标识,32位字符串

site_id #

  • 一个区域的标识,长度为4位的字符串
  • 类似阿里云的,华北二、华北三
  • 环境变量IDG_SITEUID(所有容器都有)

cluster_id #

  • k8s集群标识,一个site下面可以有多个k8s集群
  • site_id => cluster_id 一对多的关系
  • 环境变量IDG_CLUSTERUID(所有容器都有)

环境变量示例 #

### 中台业务环境变量
IDG_RUNTIME=release       # 中台定义的服务实例运行环境
IDG_SERVICE_NAME=注册服务  # 服务名称
IDG_IMAGEURL=harbor.oneitfarm.com/itfarm-test/3014544c41844d2b9b6794785fc23c0c:2.0.75
IDG_VERSION=2.0.75 # 当前服务版本
IDG_SITEUID=sal2 # site_id
IDG_CLUSTERUID=aliyun-sh-prod # cluster_id 
IDG_UNIQUEID=2986f95118a23fb20625c579a6e68111 # unique_id,服务治理识别号
IDG_WEIGHT=10 # 负载均衡权重,unique_id 对应多个实例时根据weight负载
IDG_APPID=46e3b264a11c40f58b07198490ed471b # 应用appid

MSP_LOG_REDIS_HOST=redis-cluster-proxy-log.msp:6380 # 日志输出redis代理地址
### 基础环境变量示例,pod中必须保证这几个值相同才能互相调用
IDG_SITEUID=sal2 # site_id 
IDG_CLUSTERUID=aliyun-sh-prod # cluster_id
MSP_SE_NGINX_ADDRESS=192.168.60.48:80 # 动态注册中心地址,服务互相调用的基础依赖
MSP_SE_ENV=production # 集群运行环境定义,需要保证一个集群下值相同,和业务无关

服务调用方式说明 #

中台的标准微服务分为,应用、服务两大类

应用 #

  • 通常是一个拓扑的最顶层,是一个业务产品的后端调用入口
  • 应用自身可以调用自身体系下所有服务,跨层级调用callByChain

服务 #

  • 应用拓扑下引用的依赖服务,一个拓扑中一般只有一个应用,其余全是服务
  • 服务自身只可以调用自身节点引用的服务,不可以跨层级调用

示例 #

image-20201204140150510

调用方法 #

中台sdk #

  • 中台调用有标准的sdk,一共定义了三种调用方法,静态调用call动态调用callServiceInstance跨链调用callByChain
  • 应用往下层发起调用时可以使用所有方法,调用自身引用的服务(有直接引用关系)三种方法都支持,当调用非自身引用的服务时(跨层级调用)只能用callByChain方法
  • 服务往下层发起调用时只能使用静态调用call动态调用callServiceInstance, 不支持跨层级调用
  • 调用时把上下层级调用信息会编译为token,存储到请求的header Authorization里面

image-20201204140709638

token内容示例 #

下面是一个 A => 静态调用B, B从header中拿到的token示例,token在线解析:https://jwt.io

{
  "from_appid": "a8defa95f68246edb5389d8f5d7c8157", // 调用方appid
  "from_appkey": "7723929acb9647af9b6c828195691b7b",// 调用方appkey
  "from_channel": 2, // 调用方channel
  "account_id": "7ff04df4a3bd4fa9897bec935848dfcf", // 操作用户的id
  "sub_org_key": "1dd04df4a3bd4fa9897bec935848ddab", // 应用所属机构
  "user_info": [],
  "call_stack": [ // 调用链路信息,经过多少层服务会把各层服务信息压入栈中
    { // 首层栈,请求发起的起点,一般是顶层应用信息,不一定等于from_xxx
      "appid": "a8defa95f68246edb5389d8f5d7c8157", // A
      "appkey": "7723929acb9647af9b6c828195691b7b", // A
      "channel": 2, // A
      "alias": "",
      "version": "1.0.23" // 顶层应用版本
    },
    { // 尾层 目标栈,上层静态调用自身
      "appid": "46e3b264a11c40f58b07198490ed471b", // B
      "alias": "default" // A => B 由开发者确定的 channel_alias
    }
  ]
}

call静态调用方法 #

  • 调用静态部署的目标服务,调用方式为sdk中规定的 call方法
  • 静态调用只需要知道目标服务的appid,和开发时协商好的channel_alias
  • 调用经过sidecar时,sidecar会根据调用方身份和目标方appidchannel_alias验证调用关系,当验证失败时直接返回错误 -102状态码
{
    "state": -102,
    "msg": "The relation is invalid"
}
  • sidecar验证调用链路正常,会把目标服务的 appid、appkey、channel写入请求的header里面,并转发请求image-20201204144606938

  • 静态调用,只能调用有直接引用关系的服务 image-20201204143937113

CallServiceInstance动态调用方法 #

  • 调用动态部署的目标服务,调用方法为sdk中的CallServiceInstance方法

  • 动态调用需要知道目标服务的appid、appkey、channel

  • 调用经过sidecar时,sidecar会根据调用方身份和目标方appidappkeychannel验证调用关系,当验证失败时直接返回错误 -102状态码,sidecar验证调用链路正常,会把目标服务的 appid、appkey、channel写入请求的header里面,并转发请求

  • 动态调用,只能调用有直接引用关系的服务

  • 静态调用和动态调用的唯一区别是,请求发起时需要知道的目标服务信息不同

目标服务静态调用动态调用
appid
appkey
channel
alias

CallByChain跨层级调用 #

  • 只有顶层应用可以使用此方法
  • 此方法支持调用有直接引用关系的下级服务和跨层级调用
  • 调用此方法时,一般由前端程序传入调用链路信息,经过应用后端生成token后转发请求

image-20201204150553340

  • requestStack解析
[
    {	// 顶层应用自身信息
        "appid": "fnue42okwlghuw5tohvmbsxqiz1amgcz", 
        "appkey": "2532ebecd93d470b9d9d8275fb941188",
        "channel": "2"
    },
    {// 中间层服务信息,此服务在部署时为动态部署,请求并不会真正经过此层服务
        "appid": "uh49y8vwmxp5rsiqthfjm62ynxbajofc",
        "appkey": "3b960c7ddfda45a3b1a815e0335ae08c",
        "channel": "233"
    },
    {// 目标服务信息,此服务在部署时是静态部署,表示callByChain真正要调用的服务
        "appid": "nudzko5dwqbi3sbvay0tchxap48j6clp",
        "channelAlias": "default"
    }
]
  • 请求流程解析,假设有以下拓扑

image-20201204155643206

调用问题排查方法 #

sidecar错误码说明 #

由sidecar抛出的错误码全部小于0

-100 解析请求route错误 #

{"state":-100, "msg":"Request format error"}
  • 此错误只会在调用下层服务时触发,当没有通过标准的sdk发起调用时会抛出此状态码
  • 排查流程:
    • 检查业务代码发起调用的地方,是否逻辑错误
    • 检查sdk版本是否错误

-101 解析token错误 #

{"state":-101, "msg":"Request token invalid"}
  • 此错误只会在调用下层服务时触发,当没有通过标准的sdk发起调用时会抛出此状态码
  • 排查流程:
    • 检查业务代码发起调用的地方,是否逻辑错误
    • 检查sdk版本是否错误
    • 如果通过postman发起的调用,排查token是否正确

-102 调用链不成立 #

{"state":-102, "msg":"The relation is invalid"}
  • 此错误只会在调用下层服务时触发,当调用时构造的请求链路信息验证错误时会抛出此码
  • 排查流程:
    • 检查业务代码发起调用的地方
      • 发起调用者自身是服务:检查自身是否接收到上层传递的token,验证token是否正确,token解析后尾栈必须是自身
      • 发起调用者自身是应用:自身appid、appkey、channel是否正确获取到(1、通过前端传递,2、开发容器可以通过环境变量获取),检查setAppInfo方法是否设置正确的自身信息,前端是否传递错误等
    • 确认自身往下层调用时是静态调用还是动态调用,调用方法是否使用错误,要确认部署时是动态部署还是静态部署,必须使用对应的调用方法
    • 可以通过msp平台查看日志及调用链路分析,根据调用的api + 服务识别号(unique_id),找到token后解析查看上下级调用链路是否正确
    • 验证发起调用时传递的参数是否正确
    • 找相关人员查看静态注册中心确认调用链路是否正确

-103 callbychain方式调用链不成立 #

{"state":-103, "msg":"The chain is invalid"}
  • 此错误只会在应用调用下层服务时触发,当调用时构造的请求链路信息验证错误时会抛出此码
  • 排查流程:
    • 检查业务代码发起调用的地方逻辑是否错误,检查setAppInfo方法是否设置正确的自身信息,前端是否传递错误等,代码解析前端传递的requestStack时是否解析错误
    • 检查sdk版本是否错误
    • 确认自身往下层调用时方法是否使用正确,跨层级链路调用必须用callBychain
    • 验证发起调用时传递的参数是否正确
    • 可以通过msp平台查看日志及调用链路分析,链路是否正确传递到sidecar
    • 查看静态注册中心确认调用链路是否正确

-104 目标服务的信息不存在 #

{"state":-104, "msg":"The required parameter is null"}
  • 此错误只会在调用下层服务时触发

  • 排查流程:

    • 检查业务代码发起调用的地方
      • 发起调用者是服务:检查自身是否接收到上层传递的token,验证token是否正确,token解析后尾栈必须是自身
      • 发起调用者是应用:检查setAppInfo方法是否设置正确的自身信息,前端是否传递错误等
    • 查看静态注册中心确认调用链路是否正确
    • 确认目标服务是否存在,是否已经销毁调用关系

-105 请求错误 #

{"state":-105, "msg":"Request error"}
  • 此错误只会在调用下层服务时触发,调用目标服务时出现异常时抛出此错误码

  • 排查流程:

    • 通过msp查看日志、trace等

    • 排查调用的目标服务接口是否正确

    • 是否超时错误,当目标服务响应时间过长时,会报超时错误,解决方法到msp平台配置目标服务的超时时间复制目标服务的unique_id, 到熔断管理配置目标服务的超时时间,sidecar默认请求超时时间为5秒

      {"state":-105,"msg":"Request error: 请求超时"}
      
    image-20201204165756995

-106 响应错误 #

{"state":-106, "msg":"Response error"}
  • 此错误只会在调用下层服务时触发,调用目标服务时,目标服务响应错误时触发

  • 排查流程:

    • 通过msp查看日志、trace,查看具体报错信息
  • 错误示例

    • 目标服务异常,搜索目标服务的unique_id 检查目标服务是否正常部署,是否健康检查失败

      {"state":-106,"msg":"Response error: discovery: 目标服务没有注册到动态注册中心!"}
      

      image-20201204170758093

      image-20201204171752068

      • 如果直接搜索不到,代表目标服务没有部署或者调用链路错误根据调用链查看目标服务的unique_id是否正确

      image-20201221154800902

    • 其他目标服务抛出的异常状态码,msp查看日志后具体排查

-429 分布式限流 #

{"state":-429,"msg":"Rate limit exceeded"}
  • 此错误只会在调用下层服务时触发,调用目标服务时,速率超出目标服务的限流配置后会直接返回此错误码

  • 此错可在msp平台查询到相关日志

  • 限流:A => X,B => X, C => X,当X被调用频繁,达到配置的最大值时(例如:60s内最大调用500次)会触发分布式限流,分布式限流的含义是:不管X部署多少份副本,都共享配置参数,可以把X的多个副本看成是一个整体,超过周期内最大调用次数后任何副本在接收到新请求后都会触发此错误码

  • sidecar默认不开启分布式限流规则,可在msp平台配置

  • 排查流程:

    • 通过msp查看日志、trace,查看具体报错信息
    • 调整目标服务限流参数
    • 调整请求速率

    image-20201204174045107

-430 BBR限流 #

{"state":-430,"msg":"BBR Rate limit exceeded"}
  • 此错误只会在调用下层服务时触发,调用目标服务时,目标服务监测自身负载当负载过高后拒绝响应直接返回限流错误码

  • BBR限流:A => X,B => X, C => X,当调用频繁,X服务自身cpu、内存等资源达到限制后触发,当请求进来后直接快速返回异常,抛出-430 状态码

  • sidecar始终会开启BBR限流规则,保障服务自身可用性

  • 排查流程:

    • 通过msp查看日志、trace,查看具体报错信息
    • 通过msp查看目标服务负载状态
    • 可通过部署多个目标服务或者调整目标服务资源限制解决问题

    image-20201204173935158

-499 熔断错误 #

{"state":-499,"msg":"The fuse error: circuit breaker is open"}
  • 此错误只会在调用下层服务时触发,调用目标服务时,目标服务连续响应错误时触发

  • 熔断:A => B,当B服务持续不可用时,A熔断B,一个时间周期内A再次访问B会直接响应熔断错误码,一个时间周期后放开一次请求,当请求响应错误时再次开启熔断,响应正常则关闭熔断,后续可正常请求

  • sidecar默认开启熔断规则:

    • 连续错误5次触发熔断
    • 熔断周期30s,30s后半开,可重试一次请求是否正常
  • 排查流程:

    • 通过msp查看日志、trace,查看具体报错信息
    • 通过日志查看目标服务是否异常
    • 查看目标服务的接口响应是否正常
    • 可在msp平台调整目标服务熔断规则
    image-20201204165756995

分布式限流、BBR限流、熔断等概念可以详细阅读服务治理PPT(PPT可以找基础架构组获取)

错误日志示例及说明 #

  • 服务当前资源获取失败,服务没有基础探针,需要重新在开发空间编译新镜像后重新部署
    • 基础探针:读取容器内存、cpu等资源的api接口,默认:http://127.0.0.1:6001

image-20201221162039759

  • 健康检查失败,sidecar治理的服务状态异常,有两种错误可能
      1. 服务没有加健康检查接口/healthcheck
      1. healthcheck接口返回的http status 状态码非200

image-20201221162615386

  • 服务调用异常:可以查看res_body后排查具体问题

image-20201221165819596

  • 资源预警: 此类问题首先应该优化代码,尽量减少大量消耗cpu、内存的操作;或者在开发空间配置服务部署时的资源限制

image-20201221171302329

service mesh架构默认配置说明 #

  • 默认开启熔断
    • 调用目标连续5次触发熔断
    • 熔断周期30s后可以再次调用,如果返回错误,再次开启熔断等待下一个周期,如果返回正确则关闭熔断器,后续可以正常调用
  • 默认开启BBR限流
  • 默认关闭分布式限流
  • sidecar请求入口请求body限制 4M
  • sidecar转发请求默认超时时间5秒,可以通过msp平台治理中心配置调控
  • sidecar默认每2秒发起一次健康检查,一次健康检查的超时时间3s,当连续3次健康检查失败时会从动态注册中心下线服务,会导致下游服务在调用时出现{"state":-106,"msg":"Response error: discovery: 目标服务没有注册到动态注册中心!"},当连续3次健康检查成功时会重新上线到动态注册中心;

其他说明 #

  • 由于健康检查接口每2s会发起一次,所以接口的内部实现不要出现耗时较长或消耗资源的业务代码