目录
背景
环境
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识别并录入数据