Consul+Prometheus+SpringBoot框架下不使用Spring Cloud组件完成Consul服务注册(by consul-api)

背景

环境

SpringBoot:2.1.9.RELEASE
SpringCloud:Greenwich.RELEASE(在原始环境中使用这个版本)

遇到问题

Consul+Prometheus+SpringBoot框架下的服务监控搭建网上有一大波文章,这边不再赘述。
而我在使用时却碰到了一个问题:
在使用公司的微服务组件搭建一个新的站点时,微服务组件与SpringCloud的spring-cloud-dependencies组件存在冲突:当引入SpringCloud组件时,会导致微服务组件报错。为了保证站点能如期上线,则屏蔽了spring-cloud-dependencies组件

解决思路

由于我们在这个框架下并不是真正使用了SpringCloud的功能,只是借用SpringCloud中的consul组件向Consul Server进行注册,进而实现服务发现的功能
那么:如果脱离SpringCloud组件,那我们需要通过其它方式实现consul的服务发现功能。

目标:另辟蹊径,完成consul服务发现

先整理一下相关的技术文章:
GitHub中的项目ReadMe
官方文档中的Http API

技术实现1-直接使用HTTP API

其实下面用SDK实现方式,也是使用了这个API。关键点在2个地方

  • Http Method
    • /v1/agent/service/register
    • PUT——注意一下,这边不是用Post,是用PUT
  • 报文
{
    "ID": "{你的负载ID}",
    "Name": "{服务名称}",
    "Address": "{负载IP地址}",
    "Port": {服务端口},
    "Check": {
        "HTTP": "{健康检查地址}",
        "Interval": "{健康检查间隔}"
}

样例:

{
    "ID": "demo.service-test1",
    "Name": "demo.service",
    "Address": "10.0.1.15",
    "Port": 9158,
    "Check": {
        "HTTP": "http://10.0.1.15:9158/actuator/health",
        "Interval": "10s"
}

正常上报后,ResponseBody是空字符串,可以通过HttpCode=2xx来判断

技术实现2-使用consul-api实现

GitHub中的项目ReadMe中已经给出了Demo,但是有个小问题:这一段示例实际会导致报错

NewService.Check serviceCheck = new NewService.Check();
//报错的原因是这里通过setScript写入了健康检查路径,但是不知道为什么实际在服务端不接受这样的方式
//会报错:Invalid check: TTL must be > 0 for TTL checks
serviceCheck.setScript("/usr/bin/some-check-script");
serviceCheck.setInterval("10s");
newService.setCheck(serviceCheck);

代码

Maven引用

<dependency>
  <groupId>com.ecwid.consul</groupId>
  <artifactId>consul-api</artifactId>
  <version>1.4.4</version>
</dependency>

RegisterManager

先给出最底层的Register类

package demo.service;

import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.agent.model.NewService;

import javax.validation.constraints.NotNull;
import java.net.InetAddress;
import java.util.Collections;

/**
 * ConsulRegisterManager
 * 用于consul的服务发现,将服务注册到consul上
 *
 * @author John Chen
 * @since 2020/3/11
 */
public class ConsulRegisterManager {
    /**
     * 用于Prometheus的consul服务注册
     *
     * @param agentHost        服务端Host;对应cloud配置:spring.cloud.consul.host 必填
     * @param agentPort        服务端Port:为null则默认8500
     * @param ipAddress        IP地址,如为空则默认使用当前IP地址;对应cloud配置:spring.cloud.consul.discovery.ip-address
     * @param serviceName      服务组名,对应cloud配置:spring.cloud.consul.discovery.service-name 必填
     * @param instanceId       实例ID,对应cloud配置:spring.cloud.consul.discovery.instance-id 必填
     * @param healthyCheckPort 健康检查端口,对应cloud配置:spring.cloud.consul.discovery.port 必填
     * @param healthyCheckPath 健康检查路径,对应cloud配置:spring.cloud.consul.discovery.health-check-path 必填
     * @param interval         检查间隔,为null则默认:10s.对应cloud配置:spring.cloud.consul.discovery.health-check-interval
     */
    public static void registerForPrometheus(@NotNull String agentHost, Integer agentPort, String ipAddress
            , @NotNull String serviceName, @NotNull String instanceId
            , int healthyCheckPort, @NotNull String healthyCheckPath, String interval) throws Exception {
        //构建客户端实例
        ConsulClient client;
        if (agentPort != null) {
            client = new ConsulClient(agentHost, agentPort);
        } else {
            client = new ConsulClient(agentHost);
        }
        NewService newService = new NewService();
        newService.setId(instanceId);
        newService.setName(serviceName);
        //注意,负载必须有且存在Prometheus配置文件consul的Job配置部分中指定的tag,才会被Prometheus采集数据
        newService.setTags(Collections.singletonList("management"));
        ipAddress = ipAddress == null ? InetAddress.getLocalHost().getHostAddress() : ipAddress;
        newService.setAddress(ipAddress);
        newService.setPort(healthyCheckPort);

        //健康检查部分
        NewService.Check serviceCheck = new NewService.Check();
        serviceCheck.setHttp("http://" + ipAddress + ":" + healthyCheckPort + healthyCheckPath);
        serviceCheck.setInterval(interval == null ? "10s" : interval);
        newService.setCheck(serviceCheck);

        //进行服务注册
        client.agentServiceRegister(newService);
    }
}

这里需要注意的是,只有负载必须有且存在Prometheus配置文件consul的Job配置部分中指定的tag时,Prometheus才会对这台负载进行采集。
举例:Prometheus配置scrape_configs部分如下

scrape_configs:
  - job_name: 'consul-prometheus'
    metrics_path: /actuator/prometheus
    consul_sd_configs:
    #consul 地址
      - server: 'xx.xx.xx.xx:8500'
        services: []
        #注意这里的tag
        tags: ['management']

这段配置中,“tags”中配置了management,则负载也必须给到management才能正确被Prometheus拿去进行埋点收集

顶层构建

package demo.service;

import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;

import java.io.IOException;

/**
 * ConsulRegister
 * 用于代替Spring cloud,向consul进行注册,用于Prometheus的服务发现
 * 通过Spring的事件机制,在项目启动成功后向Consul进行注册
 * 项目如需要使用,则需要构建一个Bean
 * 实例内的配置项是按照spring cloud consul的标准配置获取配置值的,配置方面可以与cloud通用
 *
 * @author John Chen
 * @since 2020/3/11
 */
 @Slf4j
public class ConsulRegister {
    private final static String MODULE = EnumSkynetLogModule.SYSTEM_BUILD.getName();
    private final String CATEGORY = "ConsulRegister";
    @Value("${spring.cloud.consul.host}")
    private String agentHost;
    @Value("${spring.cloud.consul.discovery.ip-address}")
    private String ipAddress;
    @Value("${spring.cloud.consul.discovery.service-name}")
    private String serviceName;
    @Value("${spring.cloud.consul.discovery.instance-id}")
    private String instanceId;
    @Value("${spring.cloud.consul.discovery.port}")
    private int healthyCheckPort;
    @Value("${spring.cloud.consul.discovery.health-check-path}")
    private String healthyCheckPath;
    @Value("${spring.cloud.consul.discovery.health-check-interval}")
    private String interval;

    @EventListener
    public void registerForPrometheus(@NotNull ApplicationReadyEvent event) {
        log.info("开始注册Consul服务");
        try {
            ConsulRegisterManager.registerForPrometheus(
                    agentHost, null, ipAddress, serviceName, instanceId, healthyCheckPort, healthyCheckPath, interval
            );
            log.info("Consul服务注册成功");
        } catch (Exception e) {
            log.error("Prometheus服务注册失败;该错误不影响项目启动,但可能会导致consul上无该服务,进而可能导致Prometheus无法收集该负载数据", e);
        }
    }

}

这里要注意的是,我们希望在整个项目启动完成后才会去向云端注册服务。所以这里利用了spring事件机制,使用了 @EventListener 注解去监听ApplicationReadyEvent事件

BeanConfig

最后一步,将ConsulRegister 构建Bean(如果使用注解,可以省略这一步)

    @Bean
    public ConsulRegister consulRegister() {
        return new ConsulRegister();
    }

配置

为了保证项目的通用性,这里使用了spring cloud consul的标准配置获取配置值的,配置方面可以与cloud通用
application.yml:

spring:
  profiles:
    active: @spring.profiles.active@
  freemarker:
    checkTemplateLocation: false
  application:
    name: neo.service.${DAOKEAPPUK:demo}
  cloud:
    consul:
      host: 10.100.217.20
      discovery:
        #健康检查地址(这里使用了Spring的actuator组件的健康检查地址)
        health-check-path: /actuator/health
        #健康检查间隔(默认值)
        health-check-interval: 10s
        port: ${management.server.port}
        enabled: true
        #prefer-ip-address→在注册时,使用ip地址
        prefer-ip-address: true
        instance-id: @spring.application.name@-${DAOKEID:${random.value}}
        service-name: @spring.application.name@-${DAOKEENV:${spring.profiles.active:unknownEvn}}
      ip-address: ${DAOKEIP:${HOST:10.101.8.15}}

注意一下:这里部分引用了公司内部开发环境下的环境变量,实际使用中需根据实际开发环境进行调整

总结

  • 当无法使用或不想使用Spring Cloud的时候,可以使用Consul官方提供的其他方式
  • HTTP API-直接调用即可,同时参数和代码质量难以控制
  • consul-api-官方直接给到的封装好的HTTP API工具
  • 负载注册的时候记得要录入tag:management,以便Prometheus识别并录入数据
发布了7 篇原创文章 · 获赞 12 · 访问量 2288

猜你喜欢

转载自blog.csdn.net/weixin_42182797/article/details/104800526