天天看点

Springboot Admin(SBA) + Nacos + Arthas 搭建你的在线性能分析和问题定位工具-客户端改造篇核心改造说明arthas-springboot-starter改造SpringCloud服务使用实战演示以上服务端&客户端的整合完成,整体代码待后续开放…

接上一篇服务端改造

核心改造说明

  1. 基于arthas-springboot-starter进行改造,自动启动更改为通过jmx mbean的方式手动启动和停止
  2. 我们的服务都是springcloud基于K8S的,在docker上做了微调适配
  3. 基于nacos,arthas的服务端配置使用了公共文件

arthas-springboot-starter改造

此步骤主要参考博文进行整合改造,核心改造点:

Springboot Admin(SBA) + Nacos + Arthas 搭建你的在线性能分析和问题定位工具-客户端改造篇核心改造说明arthas-springboot-starter改造SpringCloud服务使用实战演示以上服务端&客户端的整合完成,整体代码待后续开放…

pom引用

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <arthas.version>3.4.5</arthas.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>com.taobao.arthas</groupId>
      <artifactId>arthas-agent-attach</artifactId>
      <version>${arthas.version}</version>
    </dependency>
    <dependency>
      <groupId>com.taobao.arthas</groupId>
      <artifactId>arthas-packaging</artifactId>
      <version>${arthas.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-context</artifactId>
    </dependency>
    <dependency>
      <groupId>org.jolokia</groupId>
      <artifactId>jolokia-core</artifactId>
    </dependency>
  </dependencies>
           

ArthasMbeanImpl

/**
 * jmx实现 <br>
 *
 * @date: 2021/8/22 <br>
 * @author: llxiao <br>
 * @since: 1.0 <br>
 * @version: 1.0 <br>
 */
@Component
@ManagedResource(objectName = "ArthasMbean:name=ArthasMbean", description = "Arthas远程管理Mbean")
@Slf4j
public class ArthasMbeanImpl {

    private static final String ARTHAS_AGENT_BEAN_NAME = "arthasAgent";

    private static final String ARTHAS_PREFIX = "arthas.";

    @Autowired
    private Map<String, String> arthasConfigMap;

    @Autowired
    private ArthasProperties arthasProperties;

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 初始化&启动arthas
     *
     * @return
     */
    private ArthasAgent arthasAgentInit() {
        arthasConfigMap = StringUtils.removeDashKey(arthasConfigMap);
        // 给配置全加上前缀
        Map<String, String> mapWithPrefix = new HashMap<>(arthasConfigMap.size());
        for (Map.Entry<String, String> entry : arthasConfigMap.entrySet()) {
            mapWithPrefix.put(ARTHAS_PREFIX + entry.getKey(), entry.getValue());
        }
        final ArthasAgent arthasAgent = new ArthasAgent(mapWithPrefix, arthasProperties.getHome(),
            arthasProperties.isSlientInit(), null);
        log.info("开始连接Arthas tunnel server,配置信息:{}", JSON.toJSONString(arthasProperties));
        arthasAgent.init();
        String errorMessage = arthasAgent.getErrorMessage();
        log.info("连接Arthas tunnel server,返回结果:{}", errorMessage);
        return arthasAgent;
    }

    @ManagedOperation(description = "获取Tunnel Server地址")
    public String getArthasTunnelServerUrl() {
        return arthasProperties.getTunnelServer();
    }

    @ManagedOperation(description = "重置Tunnel Server地址(重新attach后生效)")
    @ManagedOperationParameter(name = "tunnelServer", description = "example:ws://127.0.0.1:7777/ws")
    public Boolean setArthasTunnelServerUrl(String tunnelServer) {
        if (tunnelServer == null || tunnelServer.trim().equals("") || tunnelServer.indexOf("ws://") < 0) {
            return false;
        }
        arthasProperties.setTunnelServer(tunnelServer);
        return true;
    }

    @ManagedOperation(description = "获取AgentID")
    public String getAgentId() {
        return arthasProperties.getAgentId();
    }

    @ManagedOperation(description = "获取应用名称")
    public String getAppName() {
        return arthasProperties.getAppName();
    }

    @ManagedOperation(description = "获取Arthas配置")
    public HashMap<String, String> getArthasConfigMap() {
        return (HashMap) arthasConfigMap;
    }

    @ManagedOperation(description = "是否启动Arthas")
    public Boolean isArthasAttched() {
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext
            .getAutowireCapableBeanFactory();
        if (defaultListableBeanFactory.containsBean(ARTHAS_AGENT_BEAN_NAME)) {
            return true;
        }
        return false;
    }

    @ManagedOperation(description = "启动Arthas")
    public Boolean startArthasAgent() {
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext
            .getAutowireCapableBeanFactory();
        if (defaultListableBeanFactory.containsBean(ARTHAS_AGENT_BEAN_NAME)) {
            ((ArthasAgent) defaultListableBeanFactory.getBean(ARTHAS_AGENT_BEAN_NAME)).init();
            return true;
        }
        defaultListableBeanFactory.registerSingleton(ARTHAS_AGENT_BEAN_NAME, arthasAgentInit());
        return true;
    }

    @ManagedOperation(description = "关闭Arthas(暂未实现)")
    public Boolean stopArthasAgent() {
        // TODO 无法获取自定义tmp文件夹加载的classLoader,因此无法获取到com.taobao.arthas.core.server.ArthasBootstrap类并调用destroy方法
        // TODO 现有官方提供的com.taobao.arthas.agent.attach.ArthasAgent 中启动arthas agent的客户端使用的arthasClassLoader和bootstrapClass均为方法内的临时变量,外部无法获取相关句柄实现通过bootstrapClass关闭arthas agent的功能;
        // TODO 临时解决方案为通过JMX启动后,在web console连接使用后,使用stop命令实现目标进程中 arthas agent的关闭
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext
            .getAutowireCapableBeanFactory();
        if (defaultListableBeanFactory.containsBean(ARTHAS_AGENT_BEAN_NAME)) {
            defaultListableBeanFactory.destroySingleton(ARTHAS_AGENT_BEAN_NAME);
            return true;
        } else {
            return false;
        }
    }
}
           

ArthasConfiguration

import java.util.HashMap;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;

/**
 * @ClassName: arthas agent加载,需要扫描包,加载jmx的信息,通过jmx的方式启动arthas agent
 * @Description: 线程池初始化
 * @author: xiaolinlin
 * @date: 2020/9/1 13:45
 **/
@EnableConfigurationProperties({ArthasProperties.class})
@ComponentScan("com.xiaogj.arthas.spring")
public class ArthasConfiguration {


    @ConfigurationProperties(prefix = "arthas")
    @ConditionalOnMissingBean
    @Bean
    public HashMap<String, String> arthasConfigMap() {
        return new HashMap<>();
    }
}
           

SpringCloud服务使用

  1. 客户端改造后的引入
<dependency>
      <groupId>com.xx.xxx</groupId>
      <artifactId>xx-arthas-springboot-starter</artifactId>
      <version>1.0.0-SNAPSHOT</version>
   </dependency>
           
  1. 基于spring-actutor,需要开放对应的监控目录

    application.yml

# 监控监控
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: ALWAYS
  metrics:
    tags:
      application: ${spring.application.name}
           
  1. nacos配置
spring:
  main:
    ## 解决 xxx.FeignClientSpecification异常
    allow-bean-definition-overriding: true
  application:
    name: arthas-service-name
  profiles:
    active: ${SPRING_PROFILES:dev}
  cloud:
    nacos:
      config:
        file-extension: yml
        # 读取公共配置文件
        refreshable-dataids: arthas-commom.yml
        shared-dataids: arthas-commom.yml
        namespace: ${NACOS_NAMESPACE:you group}
      username: ${NACOS_USERNAME:nacos}
      password: ${NACOS_PASSWORD:nacos}
      server-addr: ${NACOS_SERVER_ADDR:you naco url}
      discovery:
        namespace: ${NACOS_NAMESPACE:you group}
           
  1. arthas-commom.yml
arthas:
  tunnel-server: ws://10.0.30.205:31817/ws
  app-name: paymonitor
  telnet-port: 3658
  ip: 0.0.0.0
  #agent-id: ${spring.application.name}@${random.value}
  # 保证agentid 唯一,由于容器部署,我这里用应用名词+ip,类似取注册中心的服务和地址
  agent-id: ${spring.application.name}@${spring.cloud.client.ip-address}
           
  1. 容器部署调整

    我们是用K8S+容器部署,打包的时候调整了dockerfile文件,主要是:

    -Dspring.jmx.enabled=true

    开放jmx
## 开放端口
EXPOSE 3658

ENTRYPOINT ["java","-Dspring.jmx.enabled=true","-jar","/app.jar"]
           

实战演示

  1. SBA控制台,找到刚刚集成的服务,进入到服务信息,要绿色的才能使用,红色的标识没有开放actutor相关权限
    Springboot Admin(SBA) + Nacos + Arthas 搭建你的在线性能分析和问题定位工具-客户端改造篇核心改造说明arthas-springboot-starter改造SpringCloud服务使用实战演示以上服务端&amp;客户端的整合完成,整体代码待后续开放…
  2. 找到你集成的服务,点击进入详情,找到JVM,JAVA管理扩展
    Springboot Admin(SBA) + Nacos + Arthas 搭建你的在线性能分析和问题定位工具-客户端改造篇核心改造说明arthas-springboot-starter改造SpringCloud服务使用实战演示以上服务端&amp;客户端的整合完成,整体代码待后续开放…
  3. 找到arthas客户端定义的mbean名称
    Springboot Admin(SBA) + Nacos + Arthas 搭建你的在线性能分析和问题定位工具-客户端改造篇核心改造说明arthas-springboot-starter改造SpringCloud服务使用实战演示以上服务端&amp;客户端的整合完成,整体代码待后续开放…
  4. 点击进入,启动客户端的arthas
    Springboot Admin(SBA) + Nacos + Arthas 搭建你的在线性能分析和问题定位工具-客户端改造篇核心改造说明arthas-springboot-starter改造SpringCloud服务使用实战演示以上服务端&amp;客户端的整合完成,整体代码待后续开放…
  5. 启动成功后,正常到sba- arthas console控制台刷新后会看到对应的arthas agentid
    Springboot Admin(SBA) + Nacos + Arthas 搭建你的在线性能分析和问题定位工具-客户端改造篇核心改造说明arthas-springboot-starter改造SpringCloud服务使用实战演示以上服务端&amp;客户端的整合完成,整体代码待后续开放…
  6. 进入arthas控制台
    Springboot Admin(SBA) + Nacos + Arthas 搭建你的在线性能分析和问题定位工具-客户端改造篇核心改造说明arthas-springboot-starter改造SpringCloud服务使用实战演示以上服务端&amp;客户端的整合完成,整体代码待后续开放…
    这里一定要注意这个ip和port的配置,是arthas tunnel server的端口配置,不是sba服务应用的端口,否则会连接不上
  7. 选中刚刚注册上来的arthas 点击连接,即可进入arthas控制台,尽情享用
    Springboot Admin(SBA) + Nacos + Arthas 搭建你的在线性能分析和问题定位工具-客户端改造篇核心改造说明arthas-springboot-starter改造SpringCloud服务使用实战演示以上服务端&amp;客户端的整合完成,整体代码待后续开放…
    补充:
  1. 刷新按钮可以动态刷新已注册上来的arthas agent.
  2. 想要停止服务,暂时只能在arthas控制台使用

    stop

    命令停止

以上服务端&客户端的整合完成,整体代码待后续开放…