Spring boot admin可视化服务监控

本文详细介绍了如何使用Spring Boot Admin进行可视化服务监控,包括admin-service和admin-client的集成,以及实时日志查看和admin-server的通知扩展功能。通过启用@EnableAdminServer,配置安全框架,以及设置客户端的日志路径,可以实现对Spring Boot应用的全面监控。此外,文章还揭示了实时日志查看的实现原理,并提供了admin-server自定义通知功能的实现思路。

2.3.0版本的文档

作用

监控spring boot应用的可视化组件。已经内置了漂亮的ui界面。

集成

admin分为两个组件:service和client;版本选择需要与spring boot版本对应,我选择的版本是2.3.0

admin-service 服务

1、创建一个项目作为监控服务端:service-monitor,maven依赖如下

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- spring boot admin:包含了ui依赖和server依赖 -->
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-server</artifactId>
        <version>2.3.0</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
</dependencies>

既然是监控服务,那么就需要保障安全,不能“裸奔”让所有人都能访问,所以需要引入security安全框架保障安全。

2、使用@EnableAdminServer注解激活服务

@SpringBootApplication
// 启动admin
@EnableAdminServer
public class ServiceMonitorApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceMonitorApplication.class, args);
    }
}

3、security安全配置

官方文档有详细的讲解和代码(我的这个配置就是照搬的)

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AdminServerProperties adminServer;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 配置认证成功的处理类,绑定 adminServer 的目标地址
        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successHandler.setTargetUrlParameter("redirectTo");
        successHandler.setDefaultTargetUrl(this.adminServer.path("/"));

        http.authorizeRequests(authorizeRequests ->
                // admin 的静态资源不做校验
                authorizeRequests.antMatchers(adminServer.path("/assets/**")).permitAll()
                        .antMatchers(adminServer.path("/login")).permitAll()
                        // 其他资源都需要认证
                        .anyRequest().authenticated()
        ).formLogin(formLogin ->
                // 指向 admin 的登录页面,而不是 security 默认的登录页面
                formLogin.loginPage(adminServer.path("/login"))
                        .successHandler(successHandler)
        ).logout(logout ->
                // 指向 admin 的退出接口
                logout.logoutUrl(adminServer.path("/logout"))
        ).httpBasic(
                Customizer.withDefaults()
        ).csrf(csrf ->
                // csrf的防护对下面三个接口忽略
                csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                        .ignoringRequestMatchers(
                                new AntPathRequestMatcher(this.adminServer.path("/instances"),
                                        HttpMethod.POST.toString()),
                                new AntPathRequestMatcher(this.adminServer.path("/instances/*"),
                                        HttpMethod.DELETE.toString()),
                                new AntPathRequestMatcher(this.adminServer.path("/actuator/**"))
                        )
        ).rememberMe(rememberMe ->
                rememberMe.key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600)
        );
    }

}

4、这里为了简单,直接配置用户名密码,正式环境中可以通过security实现数据库用户的认证、或者sso的用户认证等复杂认证功能。

spring:
  application:
    name: service-monitor
  security:
    user:
      name: user
      password: f83e7a83-5a92-4221-8198-f0066557f0b5
      roles: USER

5、启动项目并访问:有security的存在,访问http://localhost:8080会跳转到登录页面

输入配置的用户名密码后,进入系统:

admin-Client 服务

1、创建一个普通的spring boot项目,例如:service-gateway,maven依赖如下:

<dependencies>
    <!-- 网关依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

    <!-- admin-client -->
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
        <version>2.3.0</version>
    </dependency>
</dependencies>

2、甚至都不需要其他代码,直接配置一下就可以了

server:
  port: 8081

spring:
  application:
    name: gateway
  boot.admin.client:
    # admin-service的地址
    url: "http://localhost:8080"
    # admin-service中开启了登录认证,所以需要配置用户名密码
    username: user
    password: f83e7a83-5a92-4221-8198-f0066557f0b5
# 开启节点信息
management:
  endpoints:
    # 默认情况下,只有info和health节点通过http协议公布出去,这里配置*,表示所有的节点都公布
    web:
      exposure:
        include: "*"
  server:
    # 节点使用单独的端口,区分业务端口
    port: 8181
# 日志文件配置:路径和名称,要与logback.xml中配置的一致,admin-service中才能成功获取应用日志
logging:
  file:
    name: logs/gateway/info.log

上面的配置中,配置了日志的路径和名称,一定要与logback.xml中配置的输出日志路径和名称一致;这样客户端就能通过actuator将env注册到admin-server中,admin-server拿到logging.file.name就能来实时获取日志,方便在admin-server中查看。

(注意:客户端开启了 jmx和actuator 等对外暴露信息的功能时,客户端服务不要直接对外暴露)

3、启动项目,就发现gateway客户端注册到admin中了。

再次查看admin的页面:

 监控页面

如上图,进入监控页面就能看到内存占用、线程数、cpu利用率、垃圾回收情况等基本监控信息。在性能页面,通过添加指标可以监控服务性能。

实时日志

客户端配置了logging.file.name后,日志菜单下就会多一个“日志文件”的菜单,用于浏览实时日志;当时我就想,他是怎么实现的呢?难道是每次都全量拉取日志过来么?打开F12,发现是每秒钟发送一次请求拉取最新的日志,研究了请求,如果没有新的日志,响应体是空的,如果有新的日志,只会响应最新的日志,前面显示出来了的日志是没有在响应中的。

那是怎么实现的呢?通过仔细研究请求头和响应头:

发现每次请求都携带了Range属性,这个属性就是用于指定从目标文件的什么位置加载数据;而响应头中响应当前已经加载到的数据位置;Range属性多用于大文件的多请求批量下载。

看了之后恍然大悟。

admin-server的通知扩展

当监控的服务实例状态发送变化,admin-server中可以有通知功能,内置了例如email、消息等通知,可以查看官方文档的介绍,这里就不详细讲解。在某些场景下,是需要我们自定义通知功能的,自定义的通知可以实现 AbstractStatusChangeNotifier 抽象类,或者 Notifier 接口。

  • 实现 AbstractStatusChangeNotifier 抽象类
@Component
public class StatusNotifier extends AbstractStatusChangeNotifier {

    private final Logger log = LoggerFactory.getLogger(getClass());

    public StatusNotifier(InstanceRepository repository) {
        super(repository);
    }

    /**
     * 执行通知
     * 通过查看源码发现,只有在 admin 中注册过了的实例,才会触发调用这个方法,所以新注册的服务进不了这里
     *
     * @param event    事件
     * @param instance 实例
     * @return
     */
    @Override
    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
        return Mono.fromRunnable(() -> {
            // 查看 AbstractStatusChangeNotifier 源码,只有 InstanceStatusChangedEvent 事件才会调用到这里
            InstanceStatusChangedEvent sce = (InstanceStatusChangedEvent) event;
            // 实例状态变更事件
            StatusInfo statusInfo = sce.getStatusInfo();
            if (statusInfo.isDown()) {
                // 服务掉线,心跳丢失
                log.info(">>>>>>>>>> {} 服务的实例 {} 掉线(心跳丢失),服务地址:{}", instance.getRegistration().getName(),
                        instance.getId(), instance.getRegistration().getServiceUrl());
            } else if (statusInfo.isOffline()) {
                // 服务离线了
                log.info(">>>>>>>>>> {} 服务的实例 {} 离线(服务宕机或者停止),服务地址:{}", instance.getRegistration().getName(),
                        instance.getId(), instance.getRegistration().getServiceUrl());
            } else if (statusInfo.isUnknown()) {
                // 未知异常
                log.info(">>>>>>>>>> {} 服务的实例 {} 出现未知异常,服务地址:{}", instance.getRegistration().getName(),
                        instance.getId(), instance.getRegistration().getServiceUrl());
            } else if (statusInfo.isUp()) {
                // 服务重新上线
                log.info(">>>>>>>>>> {} 服务的实例 {} 重新上线了,服务地址:{}", instance.getRegistration().getName(),
                        instance.getId(), instance.getRegistration().getServiceUrl());
            } else {
                // 其他情况
                log.info(">>>>>>>>>> {} 服务的实例 {} 出现未知状况,服务地址:{}", instance.getRegistration().getName(),
                        instance.getId(), instance.getRegistration().getServiceUrl());
            }
        });
    }
}
  •  实现 Notifier 接口
@Component
public class AppRegistryNotifier implements Notifier {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Override
    public Mono<Void> notify(InstanceEvent event) {
        return Mono.fromRunnable(() -> {
            if (event instanceof InstanceRegisteredEvent) { // 新的 app 实例注册事件
                InstanceRegisteredEvent re = (InstanceRegisteredEvent) event;
                log.info(">>>>>>>>>> {} 服务启动了新的实例,实例id= {},服务地址:{}", re.getRegistration().getName(),
                        re.getInstance(), re.getRegistration().getServiceUrl());
            }
        });
    }
}

 两种方式完成的功能不一样,依据需求自行选择即可。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值