帖子
帖子
用户
博客
课程

业务单据显示异常解析:平台架构理解及排查思路

YonBuilder应用构建 2024-1-18 15:02 244人浏览 0人回复
摘要

本文深入解析业务单据【创建人】字段显示异常的排查过程,揭示YonBIP网络链路架构及背后机制, 文章最后强调了基础知识的重要性, 通过本文将获得一套完整的排查问题思路, 有助于类似问题的快速解决及未来预防。 .. ...

1.问题提出及排查过程

a. 问题描述
生产环境某节点单据列表页面创建人没有正常显示,显示为”-“,确认数据库中有值,不为空,单据近期没有做过更新,之前没有问题,一大早客户使用的时候就显示不正常。
问题现象截图如下:

b. 问题定位
接到工单后,快速定位对应后台服务, 查看后台服务日志截图如下:

查看日志可知这段日志描述的主要问题是与 Elasticsearch 集群的连接异常。以下是问题的主要方面:

  1. 连接异常: 日志中指出发生了连接异常,具体为..ConnectException: Connection refused,这表明应用程序无法建立与 Elasticsearch 集群的有效连接。
  2. 集群地址: 提供了连接失败的集群地址信息,其中包括三个节点的 IP 地址和端口号。这可能有助于排查问题,确定是哪个节点导致了连接问题。
  3. RestHighLevelClientHelper 错误: 与 RestHighLevelClientHelper 类相关的错误表明,可能是 Elasticsearch 客户端的使用或配置出现了问题。
  4. 时间戳和操作信息: 提供了操作发生的具体时间戳和与 Spring 框架相关的一些信息,有助于在日志中定位和理解问题发生的背景。

综合来看,主要问题在于应用程序无法成功连接到 Elasticsearch 集群,可能是由于配置错误、网络问题或 Elasticsearch 服务本身的问题。要解决这个问题,可能需要检查应用程序的配置、网络连接,以及确保 Elasticsearch 服务正常运行。
随即联系项目运维, 检查ES运行状态, 运维通过监控得知,ES集群服务内存不足,导致异常, 调整配置,重启ES服务后,服务恢复。
和项目沟通,后续加强监控措施,提前预警,避免生产环境出相同的问题。
以上为问题的诊断排查过程, 本身没有太复杂,问题也很快修复了, 但是项目组现场开发没有能快速定位问题及修复,所以借此机会也描述下怎么快速定位问题的方法,拆解下详细过程,给新手一点启发。
想要快速定位问题有几个关键问题得解决,如下2个:

¨    快速定位是哪个服务有异常
¨    MDD框架翻译机制

那我们下面就分别说下这2个关键问题。

2. 快速定位异常服务并梳理链路

前述问题链路相对较为简明,一般情况下,通过经验或猜测即可迅速确定涉及的服务。对于这类问题,仅需仔细查看日志文件,我们通常能够迅速而准确地锁定问题的所在。然而,当我们面对更为复杂的问题时,我们必须建立一套通用的方法,以更系统化的方式进行问题排查。在处理这类挑战性问题时,建立一个明晰的方法论可以事半功倍。
为了达到这一目的,我们需要深入了解并确定哪个具体的服务存在问题。考虑到BIP作为一个分布式服务平台,服务繁多,我们需要首先对BIP平台的整体调用链路进行深入梳理。这一步骤将提供更为清晰的全局视角,帮助我们更好地理解服务之间的依赖关系和交互,为处理复杂问题时提供更全面的认知基础。通过对调用链路的梳理,我们能够更有把握地把握问题的源头,并更高效地进行问题定位与解决。
a. 分析请求
首先,从请求地址开始, 请求信息如下:

  curl 'https://bip-v3r2-2302.yyuap.com/mdf-node/uniform/bill/list?busiObj=sds&designPreview=true&openType=iframe&terminalType=1' 
    -H 'Accept: */*' 
    -H 'Accept-Language: zh-CN,zh;q=0.9' 
    -H 'Cache-Control: no-cache' 
    -H 'Connection: keep-alive' 
    -H 'Content-Type: application/json;charset=UTF-8' 
    -H $'Cookie: _WorkbenchCross_=Ultraman; org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=zh-CN; yht_default_country=86; yht_default_login_type=normal; _yht_code_uuid=117695c7-b484-4d92-85ea-2f233a9f01a3; at=d5ed51c4-1d0c-4284-83b1-be62d314c90d; multilingualFlag=true; timezone=UTC+08:00; language=001; locale=zh_CN; orgId=; defaultOrg=; theme=; newArch=true; sysid=diwork; n_f_f=true; userRoleCode=CSBBS_role03; u_usercode=ec4326d0-d490-4d5a-9105-260b51fd16d0; __IF_SAMESITE_COOKIE_SUPPORT__=Y; process-center-locale=zh_CN; locale=zh_CN; process_center_token=7011eb27-3dfb-4f23-a87d-79e8a6956350; XSRF-TOKEN=MDF_61HU746OIAME9YS13L43DQ3WMu0021181418; JSESSIONID=DC57BB0756B103B75410DC116A2D1E54; yht_username_diwork=ST-245-4YagbdFMIiZTPaCIyVRo-dev__ec4326d0-d490-4d5a-9105-260b51fd16d0; yht_usertoken_diwork=vjicvio1J2Ax6TjkIZ31r4qmZkKHgWe0P7iUs8PfyQIC2sAaJs9NSK0RJWkkD2t9UaQneIAB9E3SJdL3OnXusA%3D%3D; yht_access_token=bttOTdWbERRWnI4Y213WG43aXF1aDFqSTNYeG9aUmVITGd2OUZFL1R2MkRBRjRKZkw0eElCN3l6ZzhzV2ZHQ1lkOF9fYmlwLXYzcjItMjMwMi55eXVhcC5jb20.__fe43487d18cefe63998c7249ad729f06_1701777696745dccore0iuap-apcom-workbench5a4dc28dYT; tenantid=lbqks70m; languages=001001002003; a00=6IYeUXJ-qvVwdGr1BkuZ0oxqJ9LjBDY18i6cDtMm1nJsYnFrczcwbWAzMjgzNjgzODI2NzMzMDg4YGxicWtzNzBtYGVjNDMyNmQwLWQ0OTAtNGQ1YS05MTA1LTI2MGI1MWZkMTZkMGAxYGA3OTY4NzQ2ZDYxNmU2MTY3NjU3MmBgYDE3MjE1OTYwMjI2MjM4MzAwMThgZmFsc2VgYDE3MDE3Nzc2OTY3NDdgeW1zc2VzOmQzNTM1ZDYxNzM0MGUwY2EyYTdjM2QxMjMxYTVmZDk4YGRpd29ya2A; a10=MDIzMTk0NDY4MzE5NTM2OTY3NDc; wb_at=LMjoqrjqRZfac8FBhSMIZ5BxOKnjcdu; jDiowrkTokenMock=bttOTdWbERRWnI4Y213WG43aXF1aDFqSTNYeG9aUmVITGd2OUZFL1R2MkRBRjRKZkw0eElCN3l6ZzhzV2ZHQ1lkOF9fYmlwLXYzcjItMjMwMi55eXVhcC5jb20.__fe43487d18cefe63998c7249ad729f06_1701777696745dccore0iuap-apcom-workbench5a4dc28dYT' 
    -H 'Domain-Key: developplatform' 
    -H 'Origin: https://bip-v3r2-2302.yyuap.com' 
    -H 'Pragma: no-cache' 
    -H 'Referer: https://bip-v3r2-2302.yyuap.com/mdf-node/meta/VoucherList/sdsList?domainKey=developplatform&busiObj=sds&designPreview=true&openType=iframe' 
    -H 'Sec-Fetch-Dest: empty' 
    -H 'Sec-Fetch-Mode: cors' 
    -H 'Sec-Fetch-Site: same-origin' 
    -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' 
    -H $'X-XSRF-TOKEN: MDF_61HU746OIAME9YS13L43DQ3WMu0021181418' 
    -H 'sec-ch-ua: "Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"' 
    -H 'sec-ch-ua-mobile: ?0' 
    -H 'sec-ch-ua-platform: "Windows"' 
    --data-raw '{"page":{"pageSize":20,"pageIndex":1,"totalCount":1},"billnum":"sdsList","condition":{"commonVOs":[{"itemName":"schemeName","value1":"默认方案"},{"itemName":"isDefault","value1":true}],"filtersId":"111005936","solutionId":201000491},"bClick":true,"bEmptyWithoutFilterTree":true,"busiObj":"sds","designPreview":"true","openType":"iframe","ownDomain":"developplatform","tplid":111011916,"queryId":1701777877208}' 
    --compressed

由上面的CUrl命令可知,
目标URL为:https://bip-v3r2-2302.yyuap.com/mdf-node/uniform/bill/list
包含一些参数,如 busiObj=sds、designPreview=true、openType=iframe 等。
请求头部信息
 H 'Accept: /': 声明客户端支持所有类型的响应。
 H 'Accept-Language: zh-CN,zh;q=0.9': 指定客户端接受的语言类型。
 H 'Cache-Control: no-cache': 禁用缓存。
 H 'Connection: keep-alive': 指定连接为持久连接。
 H 'Content-Type: application/json;charset=UTF-8': 指定请求主体的内容类型为 JSON。
 H 'Cookie: ...': 包含了一系列的 Cookie 信息,用于在请求中传递用户身份验证和状态信息。
 H 'Origin: ...': 指定请求的来源。
 H 'Referer: ...': 指定引用页面的 URL。
 H 'User-Agent: ...': 模拟用户代理,指定发送请求的客户端。
 H 'Domain-Key: developplatform': 注意, 这个参数后面很重要。
b. 查询业务中台nginx地址
先简单查询下对应域名的IP地址, 目前网络我示例的网络结构相对简单, 返回信息如下,通过我们的交维清单就能知道返回的这个地址就是我们nginx的IP地址;

更详细的分析方法可以咨询运维同学及搜索引擎, 不在赘述。
c. 查询/mdf-node地址
登录服务器, 输入指令如下
ps aux | grep nginx
确认nginx进程存在,尚在正常提供服务;还可以通过查看日志的方法确认我们的服务请求确实请求到我们正在查看的nginx上,命令示例如下:

 # 确认下nginx 配置文件路径
nginx -t 
 # yonbip 的路径一般是以下路径
 # nginx: the configuration file /data/iuap/middleware/nginx/nginx.conf syntax is ok
# nginx: configuration file /data/iuap/middleware/nginx/nginx.conf test is successful
 #查询配置文件
  find . -type f -name "*conf" ! -path "*bak*" | xargs grep -n "access_log"
  #确定日志路径为(例如):/data/iuap/logs/nginx/tomcat_access*.log
 #请求mdf-node/uniform/bill/list
grep "mdf-node/uniform/bill/list?busiObj=zxytztest" ./tomcat_access.2023-12-08.log

目前我们明确请求已经经过我们的nginx转发, 下面查看下/mdf-node的转发配置, 可以用以下命令

 cd /data/iuap/middleware/nginx/
#搜索下涉及到mdf-node的配置
 find . -type f -name "*conf" ! -path "*bak*" | xargs grep -n "mdf-node"

可以看到涉及到mdf-node的nginx配置有以下文件:

 ./sites-enabled/A_maps.conf:76:    'iuap-mdf-node' 'ingress';
./sites-enabled/B_maps.conf:70:    'iuap-mdf-node' 'dev-iuap-mdf-node.prod1.iuap-yks.local';
 ./locations/sPaaS/sPaaS.conf:221:location ~ ^/mdf-node/(?P<YYBIPURI>.*) {
 ./locations/sPaaS/sPaaS.conf:238:location ~ ^/iuap-mdf-node/(?P<YYBIPURI>.*) {

首先我们看第3行配置:

tips:在 Vi 编辑器中,要显示行号,请按以下步骤操作:

  1. 打开 Vi 编辑器。
  2. 按下 Esc 键进入命令模式。
  3. 输入 :set number 并按下回车键。这将启用行号显示功能。
  4. 若要关闭行号显示,可以输入 :set nonumber 并按下回车键。

我们看下 $target_backend的值是什么, 用以下命令
grep "iuap-mdf-node" -n ./sites-enabled/A_maps.conf
grep "iuap-mdf-node" -n ./sites-enabled/B_maps.conf
查询结果如下


到ingress中了,再看下ingress 这个upstream的定义,如下命令:
1 cat ./sites-enabled/upstreams.conf
结果如下

再使用以下命令
1 kubectl get pods -A -o wide |grep ingress

可以看到 172.20.47.252 这个ip正是咋们ingress这个pod的地址,
1 #查看ingresspod 定义
2 kubectl describe pod -n ingress-nginx nginx-ingress-controller-cmgbp
3 #可以把ingress的配置下载为文件详细查看
4 kubectl get ingress -A --output=yaml >> a.yaml
5
查看下a.yaml详细内容, 关键信息如下

正是2382这一行的转发规则起作用, 把yaml配置贴在如下:

 - apiVersion: networking.k8s.io/v1
   kind: Ingress 
   metadata:
      annotations:
        ingress.kubernetes.io/proxy-read-timeout: "7200"
        ingress.kubernetes.io/rewrite-target: /
        kubernetes.io/ingress.class: nginx
      creationTimestamp: "2023-03-16T07:50:52Z"
     generation: 1
      name: dev-iuap-mdf-node
      namespace: yonbip
      resourceVersion: "136686570"
      selfLink: /apis/networking.k8s.io/v1/namespaces/yonbip/ingresses/dev-iuap-mdf-node
      uid: 415eca36-8262-4c66-a29d-56f70aca3310
    spec:
      rules:  
      - host: dev-iuap-mdf-node.prod1.iuap-yks.local
        http:   
          paths:
          - backend:
              service:
                name: dev-iuap-mdf-node
                port:
                  number: 3003
            path: /
            pathType: Prefix
    status: 
      loadBalancer:
        ingress:
       - ip: 172.20.47.252

详细的配置接入可以查询相关文档, 以下是简要说明,这段配置是一个Kubernetes Ingress对象的YAML文件,用于将外部访问的URL映射到集群内部的服务。下面是对这段配置的解读:

1.apiVersion: networking.k8s.io/v1 - 指定了Ingress对象的API版本为networking.k8s.io/v1。
2.kind: Ingress - 指定了对象类型为Ingress。
3.metadata: - 包含了关于Ingress对象的元数据信息。

  • annotations: - 包含了一些注解信息,用于提供额外的元数据。

 ingress.kubernetes.io/proxy-read-timeout: "7200" - 设置代理读取超时时间为7200秒。
 ingress.kubernetes.io/rewrite-target: / - 设置重写目标为根路径。
 kubernetes.io/ingress.class: nginx - 指定使用nginx作为Ingress控制器。

  • creationTimestamp: "2023-03-16T07:50:52Z" - 记录了Ingress对象的创建时间。
  • generation: 1 - 表示Ingress对象的生成版本号。
  • name: dev-iuap-mdf-node - 指定了Ingress对象的名称为dev-iuap-mdf-node。
  • namespace: yonbip - 指定了Ingress对象所在的命名空间为yonbip。
  • resourceVersion: "136686570" - 记录了Ingress对象的资源版本号。
  • selfLink: /apis/networking.k8s.io/v1/namespaces/yonbip/ingresses/dev-iuap-mdf-node - 提供了Ingress对象的自链接。
  • uid: 415eca36-8262-4c66-a29d-56f70aca3310 - 记录了Ingress对象的全局唯一标识符。

4.spec: - 包含了Ingress对象的规范信息。

  • rules: - 定义了一组规则,用于匹配和路由请求。

 host: dev-iuap-mdf-node.prod1.iuap-yks.local - 指定了主机名,即要匹配的域名。 http: - 指定了HTTP协议的处理方式。
 paths: - 定义了一组路径,用于匹配请求的路径。

  • backend: - 指定了后端服务的相关信息。

 service: - 指定了后端服务的名称、端口等信息。
 name: dev-iuap-mdf-node - 指定了后端服务的名称为dev-iuap-mdf-node。
 port: - 指定了后端服务的端口号为3003。 path: / - 指定了请求路径为根路径。 pathType: Prefix - 指定了路径类型为前缀匹配。
5.status: - 包含了Ingress对象的当前状态信息。

  • loadBalancer: - 指定了负载均衡器的信息。

 ingress: - 指定了Ingress对象的负载均衡器信息。
 ip: 172.20.47.252 - 记录了Ingress对象的负载均衡器的IP地址。

下面验证下,查看ingress日志, 可以按照以下方法查看ingress日志:
1 kubectl describe pod -n ingress-nginx nginx-ingress-controller-cmgbp

到ingress宿主机上对应目录即可查看日志, ,命令如下:

tail -f /data/log/nginx_ingress/access.log
#或者以下命令
kubectl exec -it -n ingress-nginx   nginx-ingress-controller-cmgbp bash
tail -f /var/log/ingress_nginx/access.log

由上可知,这个请求转发到了“dev-iuap-mdf-node” 这个service, 我们查看下这个service;:

 kubectl describe service -n yonbip dev-iuap-mdf-node

1  kubectl get pods -l c87e2267-1001-4c70-bb2a-ab41f3b81aa3=dev-iuap-mdf-node -n yonbip

以上命令即可查看该service有多少个pod

kubectl exec -it  dev-iuap-mdf-node-6fcc77f75b-fhsdn -n yonbip bash

由此我们已经找到mdf-node服务, 下面介绍下怎么确定mdf-node转发规则。
d. 查询mdf-node指向服务
方法是一样的, 到mdf-node容器后, 可以观察下该服务的目录结构,大家有兴趣可以拆解下mdf-node的服务, 拆解完可以知道mdf-node会把请求转发到对应的Java后端服务中去, 具体配置可以看一下命令:
1 grep -n -A 40 '"developplatform":' ./*

到此, 我们已经通过手工方式查询到后端java服务的地址了, 只要跟以上方法一样, 把iuap-yonbuilder-runtime到nginx中查询可以知道, 这个也会指向ingress, 由ingress转发到java服务,
1 dev-iuap-yonbuilder-runtime.prod1.iuap-yks.local
对应服务如下:

e. 查看Java应用服务的一些常用方法
到了Java的服务,我们查看各种链路调用手段就很多了, 我画了一张图,如下:

我们都知道Spring Web Framework 的web服务基本有3层结构, controller, service , dao ; 还有中间件的使用, 还有和外部系统接口, 下面我就列一个表格, 告诉你怎么找到对应处理的逻辑, 有需要可以参考。

f. 框架日志开关说明
MDF框架本身也提供的方法,可以通过日志来看转发情况, 不过需要手工开启下, 开启方式为:在mdf-node服务的环境变量中加入2个变量
1 LOG_LEVEL = DEBUG
2 ENABLE_DEBUG = true

当指定LOG_LEVEL = DEBUG的时候, 到mdf-node服务后台日志就能看转发详细日志, 示例如下:

当指定ENABLE_DEBUG = true的时候, 在mdf 相关请求中加入参数 &debugNode=1; 例如之前查看详情的链接为:
1 https://bip-v3r2-2302.yyuap.com/mdf-node/uniform/bill/detail?terminalType=1&busiObj=kk01_purreq&fromMessage=1&adt=wf&billnum=kk01_purreq&tplid=111013623&id=1865636461086769157&pageDetail=true&spanTrace=undefined
修改为:
1 https://bip-v3r2-2302.yyuap.com/mdf-node/uniform/bill/detail?terminalType=1&busiObj=kk01_purreq&fromMessage=1&adt=wf&billnum=kk01_purreq&tplid=111013623&id=1865636461086769157&pageDetail=true&spanTrace=undefined&debugNode=1
输出的结果就会变成如下所示:

MDF 的转发逻辑即可看清楚。

MDD也有类似参数, 无需对应用程序做配置, 只需要参数调整, 加上&debug_trace=true, 例如:
1 https://bip-v3r2-2302.yyuap.com/mdf-node/uniform/bill/list?busiObj=ceshi&designPreview=true&terminalType=1&debug_trace=true
会发现详细的调用过程日志, 可以在里面找到一些有用的信息。
当然, 也可以用tcpdump来监听网络流量, 这个可以自行查阅相关文档,不再赘述。

3. MDD框架翻译机制

我们了解了常见的定位问题的方法以及相应的一些工具后, 要分析出mdd框架的翻译机制, 我相信就不是啥难事了, 下面就是我大概画了一个MDD翻译过程的图,如下:

当观察上述图表时,我们能够明显地发现返回给前端的数据分为两个主要来源。首先,有一部分数据是从数据库中检索出来的;其次,还有一部分数据是通过iuap-aip-search作为中介从Elasticsearch中检索而来的。这两个数据来源的内容被整合在一起,最终呈现在前端界面上。在前述章节中,我们已经初步了解了如何定位服务和排查问题。我相信通过仔细梳理整个数据传输链路,大家能够逐步揭示出翻译机制的运作原理。以下是一些必要的参考命令,如果你感兴趣的话,可以尝试运用它们进行更深入的探索。

  trace okhttp3.Call execute {returnObj} 'returnObj.request.url.url.toString().indexOf("intellis")>-1'  
  watch com.yonyou.iuap.intellis.sdk.search.api.SearchApiEndPoint customSqlRpc -x 3   
  watch com.yonyou.ucf.mdd.core.meta.RefDataTranslator fill -x 3 -b  
  watch com.yonyou.ucf.mdd.ext.dao.mybatis.TypedNativeSqlSession selectList -x 3

PS: 以上不是MDD框架翻译的全貌, 只是一部分, 算是个抛砖引玉, 大家可以尝试分析下其他情况。

4. 梳理链路

如下图所示,一个典型的mdf的数据请求链路如下

  1. 请求首先抵达 Nginx,通过 upstream 被转发至 Ingress。
  2. Ingress 根据域名进行进一步的转发。详细的信息可以通过查阅与 Ingress 相关的网络知识来了解。
  3. 一旦定位到 MDF-Node 应用,请求将通过 MDF-Node 的配置信息被进一步转发至相应的后端应用,例如 Java 应用。
  4. 这一步需要特别注意,MDF-Node 转发地址可能再次经过 Nginx 的处理。
  5. 重复执行第 2 步的流程。
  6. 寻找 Java 应用的域名,将请求发送至真实的服务。
  7. 如果涉及到远程调用,将进入这一步骤。在这一阶段,不再经过 Nginx,而是通过 Iris 微服务框架进行控制,通过 IP 直联方式进行调用。

以上为链路调用的说明, 验证拆解工具在上面的章节中已经详细的描述过了,大家可以尝试下。

5 扩展知识

下面我总结了一些常见的,需要在我们日常工作中常用的一些工具或技巧, 熟练掌握,相信会在日常工作中提升工作效率,更加得心应手。

 Linux 常用命令: ps, find, grep, tail, view, …
 K8S 常用命令: get pods; get ingress;
 Nginx, Ingress 配置及日志查看:
 Arthas 常用命令:sc, sm, vmtools, watch, stack…
 MDF 跳转配置
 MDD 的调试工具
 ElasticSearch
 YMC, Hubbo

本文暂无评论,快来抢沙发!