# 基本介绍

# 监控的意义

  • 监控服务状态是否宕机
  • 监控服务运行指标(内存、虚拟机、线程、请求等)
  • 监控日志
  • 管理服务(服务下线)

# 可视化监控平台

Spring Boot Admin 开源社区项目,是一个针对spring-boot的actuator接口进行UI美化封装的监控工具,客户端注册到服务端后,通过Http请求方式,服务端定期从客户端获取对应的信息,Health信息、内存信息、JVM信息、垃圾回收信息、各种配置信息(比如数据源、缓存列表和命中率)等,还可以直接修改logger的level。

# 如何使用

  • 导入依赖坐标
<properties>
	<spring-boot-admin.version>2.7.10</spring-boot-admin.version>
</properties>

<!-- Admin服务端 -->
<dependency>
	<groupId>de.codecentric</groupId>
	<artifactId>spring-boot-admin-starter-server</artifactId>
	<version>${spring-boot-admin.version}</version>
</dependency>

<!--Admin客户端-->
<dependency>
	<groupId>de.codecentric</groupId>
	<artifactId>spring-boot-admin-starter-client</artifactId>
	<version>${spring-boot-admin.version}</version>
</dependency>

注意

这里的spring-boot-admin.version要和springboot的版本相同

  • 配置里面配置一下端口
spring:
  boot:
    admin:
      client:
	    # 当前客户端将信息上传到哪个服务器上
        url: http://localhost:8080
management:
  endpoints:
    web:
      exposure:
        include: '*'   # 开放哪些监测信息
  endpoint:
    health:
      show-details: ALWAYS  #  开放健康信息
    logfile:
      external-file: ./logs/sys-console.log
  • springboot启动类
@SpringBootApplication
@EnableAdminServer //设置启用Admin服务端
public class SpringbootActuatorServerApplication{
	public static void main(String[] args){
		SpringApplication.run(SpringbootActuatorServerApplication.class,args);
	}
} 

# 监控原理

监控中显示的信息实际上是通过发送请求后得到json数据,然后展示出来。按照上述操作,可以发送更多的以/actuator开头的链接地址,获取更多的数据,这些数据汇总到一起组成了监控平台显示的所有数据。

Actuator,可以称为端点,描述了一组监控信息,SpringBootAdmin提供了多个内置端点,通过访问端点就可以获取对应的监控信息,也可以根据需要自定义端点信息。通过发送请求路劲/actuator可以访问应用所有端点信息,如果端点中还有明细信息可以发送请求/actuator/端点名称来获取详细信息。

地址 描述
/beans 显示所有的Spring bean列表
/caches 显示所有的缓存相关信息
/scheduledtasks 显示所有的定时任务相关信息
/loggers 显示所有的日志相关信息
/configprops 显示所有的配置信息
/env 显示所有的环境变量信息
/mappings 显示所有控制器相关信息
/info 显示自定义用户信息配置
/metrics 显示应用指标相关信息
/health 显示健康检查状态信息,up表示成功 down表示失败
/threaddump 显示程序线程的信息
  • springboot admin设置了13个较为常用的端点作为默认开放的端点,如果需要控制默认开放的端点的开放状态,可以通过配置设置,如下:
management:
  endpoints:
    enabled-by-default: true	# 是否开启默认端点,默认值true
  • 整体上来说,对于端点的配置有两组信息,一组是endpoints开头的,对所有端点进行配置,一组是endpoint开头的,对具体端点进行配置
management:
  endpoint:		# 具体端点的配置
    health:
      show-details: always
    info:
      enabled: true
  endpoints:	# 全部端点的配置
    web:
      exposure:
        include: "*"
    enabled-by-default: true

# 自定义监控指标

端点描述了被监控的信息,除了系统默认的指标,还可以自行添加显示的指标

# INFO端点

info端点描述了当前应用的基本信息,可以通过两种形式快速配置info端点的信息

  • 在yml文件中通过设置info节点的信息就可以快速配置端点信息
management:
  endpoints:
    web:
      exposure: 
        include: '*' 
  endpoint:
    info: 
      enabled: true
  info:  #配置info信息
    env:
      enabled: true

info:
  enabled: true
  company: 千舜网络
  author: sylone
  • 编程形式,通过配置的形式只能添加固定的数据,此信息与配置信息共存
@Component
public class InfoConfig implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("runTime",System.currentTimeMillis());//添加单个信息
        Map infoMap = new HashMap();		
        infoMap.put("buildTime","2006");
        builder.withDetails(infoMap);//添加一组信息
    }
}

# Health端点

health端点描述当前应用的运行健康指标,即应用的运行是否成功

@Component
public class HealthConfig extends AbstractHealthIndicator {
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        boolean condition = true;
        if(condition) {
            builder.status(Status.UP);//设置运行状态为启动状态
            builder.withDetail("runTime", System.currentTimeMillis());
            Map infoMap = new HashMap();
            infoMap.put("buildTime", "2006");
            builder.withDetails(infoMap);
        }else{
            builder.status(Status.OUT_OF_SERVICE);//设置运行状态为不在服务状态
            builder.withDetail("上线了吗?","你做梦");
        }
    }
}

# Metrics端点

metrics端点描述了性能指标,除了系统自带的监控性能指标

@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
    @Autowired
    private BookDao bookDao;

    private Counter counter;

    public BookServiceImpl(MeterRegistry meterRegistry){
        counter = meterRegistry.counter("用户付费操作次数:");
    }

    @Override
    public boolean delete(Integer id) {
        //每次执行删除业务等同于执行了付费业务
        counter.increment();
        return bookDao.deleteById(id) > 0;
    }
}

# 自定义端点

可以根据业务需要自定义端点,方便业务监控

@Component
@Endpoint(id="pay",enableByDefault = true)
public class PayEndpoint {
    @ReadOperation
    public Object getPay(){
        Map payMap = new HashMap();
        payMap.put("level 1","300");
        payMap.put("level 2","291");
        payMap.put("level 3","666");
        return payMap;
    }
}

由于此端点数据spirng boot admin无法预知该如何展示,所以通过界面无法看到此数据
可以通过HTTP请求获取当前端点的信息,但是需要先开启当前端点对外功能,或者设置当前端点为默认开发的端点

# 集成Nacos

在使用Admin时,如果没有注册中心,需要各个客户端填写Admin服务端地址,而Admin是支持Nacos、Eureka、ZooKeeper等组件,可以直接从注册中心拉取服务实例

  • 添加依赖
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  • 项目yml配置添加nacos地址,包含客户端和服务端
spring: 
  application:
    # 应用名称
    name: ruoyi-xxxx 
  cloud:
    nacos:
      discovery:
        # 服务注册地址
        server-addr: 127.0.0.1:8848

# 登录认证

  • 添加依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
  • 配置spring security权限
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter
{
    private final String adminContextPath;

    public WebSecurityConfigurer(AdminServerProperties adminServerProperties)
    {
        this.adminContextPath = adminServerProperties.getContextPath();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        SavedRequestAwareAuthenticationSuccessHandler successHandler = 
		                                              new SavedRequestAwareAuthenticationSuccessHandler();
        successHandler.setTargetUrlParameter("redirectTo");
        successHandler.setDefaultTargetUrl(adminContextPath + "/");

        http
            .headers().frameOptions().disable()
            .and().authorizeRequests()
            .antMatchers(adminContextPath + "/assets/**"
                , adminContextPath + "/login"
                , adminContextPath + "/actuator/**"
                , adminContextPath + "/instances/**"
            ).permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin().loginPage(adminContextPath + "/login")
            .successHandler(successHandler).and()
            .logout().logoutUrl(adminContextPath + "/logout")
            .and()
            .httpBasic().and()
            .csrf()
            .disable();
    }
}
  • 在ruoyi-monitor-dev.yml配置用户,默认账户ruoyi/123456
spring: 
  security:
    user:
      name: ruoyi
      password: 123456
  boot:
    admin:
      ui:
        title: 若依服务状态监控

# 日志相关

  • Spring Boot Admin提供了基于Web页面的方式实时查看日志,前提是服务中配置了logging.file.name
logging:
  file:
    name: logs/${spring.application.name}/info.log
  • 进入日志->日志文件查看实时日志
  • 进入日志->日志配置动态修改日志级别

# 自定义通知

可以通过添加实现Notifier接口的Spring Beans来添加您自己的通知程序

import org.springframework.stereotype.Component;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
import de.codecentric.boot.admin.server.notify.AbstractStatusChangeNotifier;
import reactor.core.publisher.Mono;

/**
 * 通知发送配置
 * 
 * @author ruoyi
 */
@Component
public class RuoYiStatusChangeNotifier extends AbstractStatusChangeNotifier
{
    public RuoYiStatusChangeNotifier(InstanceRepository repository)
    {
        super(repository);
    }

    @Override
    protected Mono<Void> doNotify(InstanceEvent event,
            de.codecentric.boot.admin.server.domain.entities.Instance instance)
    {
        return Mono.fromRunnable(() -> {
            if (event instanceof InstanceStatusChangedEvent)
            {
                String status = ((InstanceStatusChangedEvent) event)
				                                   .getStatusInfo().getStatus();
                switch (status)
                {
                    // 健康检查没通过
                    case "DOWN":
                        System.out.println("发送 健康检查没通过 的通知!");
                        break;
                    // 服务离线
                    case "OFFLINE":
                        System.out.println("发送 服务离线 的通知!");
                        break;
                    // 服务上线
                    case "UP":
                        System.out.println("发送 服务上线 的通知!");
                        break;
                    // 服务未知异常
                    case "UNKNOWN":
                        System.out.println("发送 服务未知异常 的通知!");
                        break;
                    default:
                        break;
                }
            }
        });
    }
}