Bistoury介绍

Bistoury 是去哪儿网开源的一个对应用透明,无侵入的java应用诊断工具,用于提升开发人员的诊断效率和能力,可以让开发人员无需登录机器或修改系统,就可以从日志、内存、线程、类信息、调试、机器和系统属性等各个方面对应用进行诊断,提升开发人员诊断问题的效率和能力。

Bistoury 集成了Alibaba开源的arthas和唯品会开源的vjtools,因此arthas和vjtools相关功能都可以在Bistoury中使用。

Arthas和vjtools通过命令行或类似的方式使用,Bistoury在保留命令行界面的基础上,还对很多命令提供了图形化界面,方面用户使用。

Bistoury 英文解释是外科手术刀,含义也就不言而喻了。

Screenshots

通过命令行界面查看日志,使用arthas和vjtools的各项功能

在线debug,在线应用调试神器

线程级cpu监控,帮助你掌握线程级cpu使用率

在web界面查看JVM运行信息,以及各种其它信息

动态给方法添加监控

线程dump

Bistoury架构分析

Bistoury核心组件包含agent,proxy,ui:

  • agent : 与需要诊断的应用部署到一起,负责具体的诊断命令执行,通过域名连接proxy
  • proxy:agent的代理,agent启动时会通过ws和proxy连接注册,proxy可以部署多个,推荐使用域名负载
  • ui:ui提供图形化和命令行界面,接收从用户传来的命令,传递命令给proxy,接收从proxy传来的结果并展示给用户。

一次命令执行的数据流向为 ui -> proxy -> agent -> proxy -> ui

具体分析一下:

  • proxy 先启动,将自己地址注册到zk
  • agent通过域名访问proxy,随机分配到一个proxy,在proxy注册自己
  • UI 访问一个具体的应用时,通过zk拿到所有的proxy,然后依次检查app对应的agent是否在该proxy,如果在,web网页连接这个proxy
  • web上输入一个命令:web->proxy->agent->proxy->ui

具体参见 https://github.com/qunarcorp/bistoury/blob/master/docs/cn/design/design.md

bistoury原理分析: https://www.jianshu.com/p/f7202e490156

总结下就是使用类似skywalking那样的agent技术,来监测和协助运行在JVM上的程序。

Bistoury快速开始

官方有一个快速开始文档: https://github.com/qunarcorp/bistoury/blob/master/docs/cn/quick_start.md

可以下载release包快速启动,就可以体验了。

首先我们将快速启动包 bistoury-quick-start.tar.gz 拷贝到想要安装的位置。

然后解压启动包:

  1. tar -zxvf bistoury-quick-start.tar.gz
  2. cd bistoury

最后是启动 Bistoury,因为 Bistoury 会用到 jstack 等操作,为了保证所有功能可用,需要使用和待诊断 JAVA 应用相同的用户启动。

假设应用进程 id 为 1024

  • 如果应用以本人用户启动,可以直接运行
  1. ./quick_start.sh -p 1024 start
  • 如果应用以其它帐号启动,比如 tomcat,需要指定一下用户然后运行
  1. sudo -u tomcat ./quick_start.sh -p 1024 start
  • 停止运行
  1. ./quick_start.sh stop

Bistoury 在docker运行

官方的git仓库里,有一个docker分支,翻阅后找到相关文档。

官方的快速启动命令:

  1. #!/bin/bash
  2. #创建网络
  3. echo "start create network"
  4. docker network create --subnet=172.19.0.0/16 bistoury
  5. #mysql 镜像
  6. echo "start run mysql image"
  7. docker run --name mysql -p 3307:3306 -e MYSQL_ROOT_PASSWORD=root -d -i --net bistoury --ip 172.19.0.7 registry.cn-hangzhou.aliyuncs.com/bistoury/bistoury-db
  8. #zk 镜像
  9. echo "start run zk image"
  10. docker run -d -p 2181:2181 -it --net bistoury --ip 172.19.0.2 registry.cn-hangzhou.aliyuncs.com/bistoury/zk:latest
  11. sleep 30
  12. #proxy 镜像
  13. echo "start run proxy module"
  14. docker run -d -p 9880:9880 -p 9881:9881 -p 9090:9090 -i --net bistoury --ip 172.19.0.3 registry.cn-hangzhou.aliyuncs.com/bistoury/bistoury-proxy --real-ip $1 --zk-address 172.19.0.2:2181 --proxy-jdbc-url jdbc:mysql://172.19.0.7:3306/bistoury
  15. #ui 镜像
  16. echo "start run ui module"
  17. docker run -p 9091:9091 -it -d --net bistoury --ip 172.19.0.4 registry.cn-hangzhou.aliyuncs.com/bistoury/bistoury-ui --zk-address 172.19.0.2:2181 --ui-jdbc-url jdbc:mysql://172.19.0.7:3306/bistoury
  18. #boot 镜像
  19. echo "start run demo application"
  20. docker run -it -d -p 8686:8686 -i --net bistoury --ip 172.19.0.5 registry.cn-hangzhou.aliyuncs.com/bistoury/bistoury-demo --proxy-host $1:9090
  21. docker run -it -d -p 8687:8686 -i --net bistoury --ip 172.19.0.6 registry.cn-hangzhou.aliyuncs.com/bistoury/bistoury-demo --proxy-host $1:9090

上面的命令不能直接运行,$1是需要替换成当前服务器IP,然后再运行就OK了。

Bistoury 在生产环境运行

官方推荐部署方式:

  • ui 独立部署,推荐部署在多台机器,并提供独立的域名

  • proxy 独立部署,推荐部署在多台机器,并提供独立的域名

  • agent 需要和应用部署在同一台机器上。推荐在测试环境全环境自动部署,线上环境提供单机一键部署,以及应用下所有机器一键部署

  • 独立的应用中心,管理所有功能内部应用和机器信息,这是一个和 Bistoury 相独立的系统,Bistoury 从中拿到不断更新的应用和机器信息

这里有个关键的点,应用中心,Bistoury内置了一个简单的应用中心,Bistoury里代码对应bistoury-application,ui和proxy都通过这个工程获取应用信息,官方默认实现了一个mysql版本的:

使用mysql的缺点是,你需要ui界面里手动维护应用以及应用的服务器,做个demo还OK,生产环境肯定不行。更优雅的方式是,用户系统应该在启动时自动注册到注册中心上,汇报自己的应用、机器信息(ip、域名等)、端口等信息。当然这个对大部分微服务架构来说,注册中心是标配的,因此实现一套bistoury-application-api接口即可。

bistoury-application-k8s(Bistoury on K8S)

我们项目组所有的应用都部署在K8S环境,因此要实现一个bistoury-application-k8s

拷贝bistoury-application-mysql项目,建立bistoury-application-k8s

简单对应下:

  • 一个应用对应一个deployment,对应一个application
  • 一个deployment里有n个pod,对应applicationServer

所以,我们只需要调用调用K8S API 获取deployment和pod即可。

首先引入相关jar包:

  1. <dependency>
  2. <groupId>io.kubernetes</groupId>
  3. <artifactId>client-java</artifactId>
  4. <version>8.0.0</version>
  5. <scope>compile</scope>
  6. </dependency>

初始化ApiClient

  1. ApiClient defaultClient = Configuration.getDefaultApiClient();
  2. defaultClient.setBasePath(k8sApiServer);
  3. ApiKeyAuth BearerToken = (ApiKeyAuth) defaultClient.getAuthentication("BearerToken");
  4. BearerToken.setApiKey(k8sToken);
  5. BearerToken.setApiKeyPrefix("Bearer");
  6. defaultClient.setVerifyingSsl(false);

获取deployment

区分下是获取所有namespace,还是获取指定的namespace

  1. private List<V1Deployment> getDeployments() throws ApiException {
  2. AppsV1Api appsV1Api = new AppsV1Api(k8SConfiguration.getApiClient());
  3. return k8SConfiguration.isAllNamespace()
  4. ? appsV1Api.listDeploymentForAllNamespaces(false, null, null, null, 0, null, null, 120, false).getItems()
  5. : getNamespacesDeployments(k8SConfiguration.getAllowedNamespace());
  6. }
  7. List<V1Deployment> getNamespacesDeployments(List<String> namespaces) {
  8. AppsV1Api appsV1Api = new AppsV1Api(k8SConfiguration.getApiClient());
  9. List<V1Deployment> deploymentList = new ArrayList<>();
  10. for (String nameSpace : namespaces) {
  11. try {
  12. deploymentList.addAll(appsV1Api.listNamespacedDeployment(nameSpace, null, null, null, null, null, 0, null, 120, false).getItems());
  13. } catch (ApiException e) {
  14. logger.error("get " + nameSpace + "'s deployment error", e);
  15. }
  16. }
  17. return deploymentList;
  18. }

转换为application:

  1. private List<Application> getApplications(List<V1Deployment> applist) {
  2. return applist.stream().map(this::getApplication).collect(Collectors.toList());
  3. }
  4. private Application getApplication(V1Deployment deployment) {
  5. Application application = new Application();
  6. application.setCreateTime(deployment.getMetadata().getCreationTimestamp().toDate());
  7. application.setCreator(deployment.getMetadata().getName());
  8. application.setGroupCode(deployment.getMetadata().getNamespace());
  9. application.setName(deployment.getMetadata().getName());
  10. application.setStatus(1);
  11. application.setCode(getAppCode(deployment.getMetadata().getNamespace(), deployment.getMetadata().getName()));
  12. return application;
  13. }

获取pod

获取pod相对麻烦点,需要先获取到V1Deployment,拿到部署的lableSelector,然后根据lableSelector选择pod:

  1. public List<AppServer> getAppServerByAppCode(final String appCode) {
  2. Preconditions.checkArgument(!Strings.isNullOrEmpty(appCode), "app code cannot be null or empty");
  3. try {
  4. V1Deployment deployment = getDeployMent(appCode);
  5. String nameSpace = appCode.split(APPCODE_SPLITTER)[0];
  6. Map<String, String> labelMap = Objects.requireNonNull(deployment.getSpec()).getSelector().getMatchLabels();
  7. StringBuilder lableSelector = new StringBuilder();
  8. labelMap.entrySet().stream().forEach(e -> {
  9. if (lableSelector.length() > 0) {
  10. lableSelector.append(",");
  11. }
  12. lableSelector.append(e.getKey()).append("=").append(e.getValue());
  13. });
  14. CoreV1Api coreV1Api = new CoreV1Api(k8SConfiguration.getApiClient());
  15. V1PodList podList = coreV1Api.listNamespacedPod(nameSpace, null, false, null,
  16. null, lableSelector.toString(), 200, null, 600, false);
  17. return podList.getItems().stream().map(pod -> {
  18. AppServer server = new AppServer();
  19. server.setAppCode(appCode);
  20. server.setHost(pod.getMetadata().getName());
  21. server.setIp(pod.getStatus().getPodIP());
  22. server.setLogDir(k8SConfiguration.getAppLogPath());
  23. server.setAutoJMapHistoEnable(true);
  24. server.setAutoJStackEnable(true);
  25. server.setPort(8080);
  26. return server;
  27. }).collect(Collectors.toList());
  28. } catch (ApiException e) {
  29. logger.error("get deployment's pod error", e);
  30. }
  31. return null;
  32. }

最后,修改ui和proxy工程,将原来的mysql替换为k8s:

应用引入bistoury agent

这块相对比较容易:

在需要调试的应用的Dockerfile里增加:

  1. COPY --from=hub.xfyun.cn/abkdev/bistoury-agent:2.0.11 /home/q/bistoury /opt/bistoury

然后修改应用的启动脚本,在最前面增加:

  1. BISTOURY_APP_LIB_CLASS="org.springframework.web.servlet.DispatcherServlet"
  2. # default proxy
  3. PROXY="bistoury-bistoury-proxy.incubation:9090"
  4. AGENT_JAVA_HOME="/usr/local/openjdk-8/"
  5. # env
  6. if [[ -n $PROXY_HOST ]]; then
  7. PROXY=$PROXY_HOST
  8. fi
  9. TEMP=`getopt -o : --long proxy-host:,app-class:,agent-java-home: -- "$@"`
  10. eval set -- "$TEMP"
  11. while true; do
  12. case "$1" in
  13. --proxy-host )
  14. PROXY="$2"; shift 2 ;;
  15. --app-class )
  16. BISTOURY_APP_LIB_CLASS="$2"; shift 2 ;;
  17. --agent-java-home )
  18. AGENT_JAVA_HOME="$2"; shift 2 ;;
  19. * ) break ;;
  20. esac
  21. done
  22. echo "proxy host: "$PROXY_HOST
  23. echo "app class: "$BISTOURY_APP_LIB_CLASS
  24. echo "agent java home: "$AGENT_JAVA_HOME

在最后面增加:

  1. APP_PID=`$AGENT_JAVA_HOME/bin/jps -l|awk '{if($2!="sun.tools.jps.Jps"){print $1 ;{exit}} }'`
  2. echo "app pid: "$APP_PID
  3. /opt/bistoury/agent/bin/bistoury-agent.sh -j $AGENT_JAVA_HOME -p $APP_PID -c $BISTOURY_APP_LIB_CLASS -s $PROXY -f start

集成测试

部署一个测试应用 agent-debug-demo,部署到jx namespace:

  1. {
  2. "kind": "Deployment",
  3. "apiVersion": "extensions/v1beta1",
  4. "metadata": {
  5. "name": "agent-debug-demo",
  6. "namespace": "jx",
  7. "annotations": {
  8. "deployment.kubernetes.io/revision": "2"
  9. }
  10. },
  11. "spec": {
  12. "replicas": 1,
  13. "selector": {
  14. "matchLabels": {
  15. "app": "agent-debug-demo",
  16. "draft": "draft-app"
  17. }
  18. },
  19. "template": {
  20. "metadata": {
  21. "creationTimestamp": null,
  22. "labels": {
  23. "app": "agent-debug-demo",
  24. "draft": "draft-app"
  25. }
  26. },
  27. "spec": {
  28. "containers": [
  29. {
  30. "name": "springboot-rest-demo",
  31. "image": "hub.xxx.cn/abkdev/springboot-rest-demo:dev-113",
  32. "ports": [
  33. {
  34. "containerPort": 8080,
  35. "protocol": "TCP"
  36. }
  37. ],
  38. "env": [
  39. {
  40. "name": "SPRING_PROFILES_ACTIVE",
  41. "value": "dev"
  42. },
  43. {
  44. "name": "PROXY_HOST",
  45. "value": "$PROXY_HOST:9090"
  46. }
  47. ],
  48. "resources": {},
  49. "terminationMessagePath": "/dev/termination-log",
  50. "terminationMessagePolicy": "File",
  51. "imagePullPolicy": "IfNotPresent"
  52. }
  53. ],
  54. "restartPolicy": "Always",
  55. "terminationGracePeriodSeconds": 10,
  56. "dnsPolicy": "ClusterFirst",
  57. "securityContext": {},
  58. "schedulerName": "default-scheduler"
  59. }
  60. },
  61. "strategy": {
  62. "type": "RollingUpdate",
  63. "rollingUpdate": {
  64. "maxUnavailable": 1,
  65. "maxSurge": 1
  66. }
  67. },
  68. "revisionHistoryLimit": 2147483647,
  69. "progressDeadlineSeconds": 2147483647
  70. },
  71. "status": {
  72. "observedGeneration": 2,
  73. "replicas": 1,
  74. "updatedReplicas": 1,
  75. "unavailableReplicas": 1,
  76. "conditions": [
  77. {
  78. "type": "Available",
  79. "status": "True",
  80. "lastUpdateTime": "2020-04-09T01:32:42Z",
  81. "lastTransitionTime": "2020-04-09T01:32:42Z",
  82. "reason": "MinimumReplicasAvailable",
  83. "message": "Deployment has minimum availability."
  84. }
  85. ]
  86. }
  87. }

部署后:

打开ui,查看:

应用名称显示为: namespace名称-部署名称

在线调试:

先选择应用:

点击Debug,然后选择需要调试的类,

测试工程源代码为:

  1. @SpringBootApplication
  2. @Controller
  3. public class RestPrometheusApplication {
  4. @Autowired
  5. private MeterRegistry registry;
  6. @Autowired
  7. private Environment env;
  8. @GetMapping(path = "/", produces = "application/json")
  9. @ResponseBody
  10. public Map<String, Object> landingPage() {
  11. Counter.builder("mymetric").tag("foo", "bar").register(registry).increment();
  12. String profile = "default";
  13. if(env.getActiveProfiles().length > 0){
  14. profile = env.getActiveProfiles()[0];
  15. }
  16. return singletonMap("hello", ""+ profile);
  17. }
  18. public static void main(String[] args) {
  19. SpringApplication.run(RestPrometheusApplication.class, args);
  20. }
  21. }

因此,我们输入RestPrometheusApplication筛选:

然后点击调试,可以看到,反编译出来了源代码:

在landingPage最后一行加一个端点,然后点击添加端点,最后访问该POD对应的服务,该pod对应的ip是170.22.149.37,因此我们访问:

  1. curl http://170.22.149.37:8080
  2. {"hello":"dev"}

再回到UI,可以看到成员变量,局部变量和调用堆栈等信息。

well down!


作者:Jadepeng

出处:jqpeng的技术记事本--http://www.cnblogs.com/xiaoqi

您的支持是对博主最大的鼓励,感谢您的认真阅读。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

java应用诊断和在线debug利器bistoury介绍与在K8S环境使用的更多相关文章

  1. Java虚拟机诊断利器

    Java虚拟机诊断利器  

  2. Thread Dump 和Java应用诊断(转)

    Thread Dump 和Java应用诊断 Thread Dump是非常有用的诊断Java应用问题的工具,每一个Java虚拟机都有及时生成显示所有线程在某一点状态的thread-dump的能力.虽然各 ...

  3. java Socket实现简单在线聊天(二)

    接<java Socket实现简单在线聊天(一)>,在单客户端连接的基础上,这里第二步需要实现多客户端的连接,也就需要使用到线程.每当有一个新的客户端连接上来,服务端便需要新启动一个线程进 ...

  4. [解决]java.lang.IllegalArgumentException: Bad level "DEBUG"

    Tomcat启动报错,搞得烦的一比.常规思维就会迷瞪,谁让tomcat的日志级别特殊ne.... http://tomcat.apache.org/tomcat-7.0-doc/ 错误现象: Hand ...

  5. Debug 利器:pstack & strace

    工作中难免会遇到各种各样的 bug,对于开发环境 or 测试环境的问题还好解决,可以使用 gdb 打断点或者在代码中埋点来定位异常; 但是遇到线上的 bug 就很难受了,由于生产环境不能随意替换.中断 ...

  6. web前端自动化测试/爬虫利器puppeteer介绍

    web前端自动化测试/爬虫利器puppeteer介绍 Intro Chrome59(linux.macos). Chrome60(windows)之后,Chrome自带headless(无界面)模式很 ...

  7. Java基础-考察JVM内部结构的常用工具介绍

    Java基础-考察JVM内部结构的常用工具介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 我们可以通过jvisualvm.exe考察jvm内部结构.而jvisualvm.exe ...

  8. Eclipse的Debug各种视图介绍(二)

    本文链接:https://blog.csdn.net/u011781521/article/details/55000066    http://blog.csdn.net/u010075335/ar ...

  9. java中几种常见字符集与乱码介绍

    1.  ASCII和Ansi编码 字符内码(charcter code)指的是用来代表字符的内码 .读者在输入和存储文档时都要使用内码,内码分为  单字节内码 -- Single-Byte chara ...

随机推荐

  1. 谈谈集合.Queue

    之前说到,Java中集合的主要作用就是装盛其他数据和实现常见的数据结构.所以当我们要用到"栈"."队列"."链表"和"数组&quo ...

  2. Python入门的三大问题和三大谎言

    Python广告,铺天盖地,小白们雾里看花,Python无限美好.作为会20几种语言(BASIC Foxbase/pro VB VC C C++ c# js typescript HTML Ardui ...

  3. 10个机器学习人工智能开发框架和AI库(优缺点对比表)/贪心学院

    概述 通过本文我们来一起看一些用于人工智能的高质量AI库,它们的优点和缺点,以及它们的一些特点. 人工智能(AI)已经存在很长时间了.然而,由于这一领域的巨大进步,近年来它已成为一个流行语.人工智能曾 ...

  4. ARC中__bridge, __bridge__transfer, __bridge_retained 关系

    总结于 IOS Tuturial 中 ARC两章,详细在dropbox pdf 文档. Toll-Free Bridging 当你在 Objective-C 和 Core Foundation 对象之 ...

  5. TP5使用Redis处理电商秒杀

    本篇文章介绍了ThinkPHP使用Redis实现电商秒杀的处理方法,具有一定的参考价值,希望对学习ThinkPHP的朋友有帮助! TP5使用Redis处理电商秒杀 1.首先在TP5中创建抢购活动所需要 ...

  6. 结题报告--P2441角色属性树

    题目:点此 题目描述 绪萌同人社是一个有趣的组织,该组织结构是一个树形结构.有一个社长,直接下属一些副社长.每个副社长又直接下属一些部长……. 每个成员都有一个萌点的属性,萌点属性是由一些质数的萌元素 ...

  7. TLS/SSL 梳理

    数据加密通篇都是为了防止第三方的劫持伪造,保证连接安全, 毫无遮掩的明文传输只有民风淳朴的时候才是安全的. 先是一些基础的内容: 对称加密 最开始为了对数据进行加密,使用的是对称加密算法,即双方协商好 ...

  8. go中处理各种请求方式以及处理接口请求参数

    话不多说直接上代码,解读内容全部在代码中 1.处理请求方式 package main import ( "fmt" "io/ioutil" "net/ ...

  9. CodeMixerPro工具,完美替代ChaosTool,iOS添加垃圾代码工具,代码混淆工具,代码生成器,史上最好用的垃圾代码添加工具,自己开发的小工具

    新工具 ProjectTool 已上线 这是一款快速写白包工具,秒级别写H5游戏壳包,可视化操作,极易使用,支持Swift.Objecive-C双语言 扣扣交流群:811715780 进入 Proje ...

  10. Python-时间戳、元组时间的格式、自定义时间格式之间的转换

    一.时间戳.元组时间的格式.自定义时间格式之间的转换 1.下面是三者之间的转换关系: 2.代码如下: import time import datetime print(time.time()) #获 ...