sentinel心跳
sentinel分为客户端client和监控面板dashboard。client端启动后,需要向dashboard上报心跳。基础概念就不做过多介绍,不清楚的,可以网上参考其他资料。整理主要介绍心跳是如何上报的,如何扩展心跳报文内容?
心跳过程
心跳入口
心跳的入口,在HeartbeatSenderInitFunc.java中,这个类会被初始化方法加载,并执行init方法。过程可参照 sentinel框架基于SPI机制的二次开发 这篇文章。
// SPI机制中的加载顺序
@InitOrder(-1)
public class HeartbeatSenderInitFunc implements InitFunc {
// 心跳任务线程池
private ScheduledExecutorService pool = null;
// 初始化线程池
private void initSchedulerIfNeeded() {
if (pool == null) {
pool = new ScheduledThreadPoolExecutor(2,
new NamedThreadFactory("sentinel-heartbeat-send-task", true),
new DiscardOldestPolicy());
}
}
// 入口方法
@Override
public void init() {
// 创建心跳发送实例。这是一个扩展点,可以扩展出自己的心跳发送器。具体往下看
HeartbeatSender sender = HeartbeatSenderProvider.getHeartbeatSender();
// 未获取到发送器,报错
if (sender == null) {
RecordLog.warn("[HeartbeatSenderInitFunc] WARN: No HeartbeatSender loaded");
return;
}
// 初始化心跳任务线程池
initSchedulerIfNeeded();
// 获取心跳间隔
long interval = retrieveInterval(sender);
// 将心跳间隔存储到系统变量中
setIntervalIfNotExists(interval);
// 创建心跳任务
scheduleHeartbeatTask(sender, interval);
}
private boolean isValidHeartbeatInterval(Long interval) {
return interval != null && interval > 0;
}
/**
* 将心跳间隔存储到系统变量中
*/
private void setIntervalIfNotExists(long interval) {
SentinelConfig.setConfig(TransportConfig.HEARTBEAT_INTERVAL_MS, String.valueOf(interval));
}
/**
* 获取心跳间隔
*/
long retrieveInterval(/*@NonNull*/ HeartbeatSender sender) {
// 从系统变量中获取
// 此处是扩展点,可在配置文件中配置心跳间隔时间,则直接使用配置的时间
// csp.sentinel.heartbeat.interval.ms
Long intervalInConfig = TransportConfig.getHeartbeatIntervalMs();
// 如果配置了心跳间隔,并且是符合条件的心跳间隔,则使用配置信息
if (isValidHeartbeatInterval(intervalInConfig)) {
RecordLog.info("[HeartbeatSenderInitFunc] Using heartbeat interval "
+ "in Sentinel config property: " + intervalInConfig);
return intervalInConfig;
} else {
// 否则,从发送器中获取默认心跳间隔
long senderInterval = sender.intervalMs();
RecordLog.info("[HeartbeatSenderInit] Heartbeat interval not configured in "
+ "config property or invalid, using sender default: " + senderInterval);
return senderInterval;
}
}
/**
* 创建心跳任务
*/
private void scheduleHeartbeatTask(/*@NonNull*/ final HeartbeatSender sender, /*@Valid*/ long interval) {
pool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
sender.sendHeartbeat();
} catch (Throwable e) {
RecordLog.warn("[HeartbeatSender] Send heartbeat error", e);
}
}
}, 5000, interval, TimeUnit.MILLISECONDS);
RecordLog.info("[HeartbeatSenderInit] HeartbeatSender started: "
+ sender.getClass().getCanonicalName());
}
}
心跳消息
sentinel原生支持http形式的心跳发送方式。实现类SimpleHttpHeartbeatSender。核心代码如下:
该实现类没有设置顺便注解,默认优先级最低。
public class SimpleHttpHeartbeatSender implements HeartbeatSender {
private static final int OK_STATUS = 200;
private static final long DEFAULT_INTERVAL = 1000 * 10;
private final HeartbeatMessage heartBeat = new HeartbeatMessage();
private final SimpleHttpClient httpClient = new SimpleHttpClient();
@Override
public boolean sendHeartbeat() throws Exception {
... ...
// 获取心跳发送地址
InetSocketAddress addr = new InetSocketAddress(addrInfo.r1, addrInfo.r2);
// 创建心跳请求
SimpleHttpRequest request = new SimpleHttpRequest(addr, TransportConfig.getHeartbeatApiPath());
// 设置心跳参数
request.setParams(heartBeat.generateCurrentMessage());
try {
// 发送心跳
SimpleHttpResponse response = httpClient.post(request);
if (response.getStatusCode() == OK_STATUS) {
return true;
} else if (clientErrorCode(response.getStatusCode()) || serverErrorCode(response.getStatusCode())) {
RecordLog.warn("[SimpleHttpHeartbeatSender] Failed to send heartbeat to " + addr
+ ", http status code: " + response.getStatusCode());
}
} catch (Exception e) {
RecordLog.warn("[SimpleHttpHeartbeatSender] Failed to send heartbeat to " + addr, e);
}
return false;
}
/**
* 获取心跳间隔
*/
@Override
public long intervalMs() {
return DEFAULT_INTERVAL;
}
... ...
}
关心的问题就在于心跳消息。
heartBeat.generateCurrentMessage()
看这个方法:
public class HeartbeatMessage {
private final Map<String, String> message = new HashMap<String, String>();
/**
* 默认心跳参数
*/
public HeartbeatMessage() {
message.put("hostname", HostNameUtil.getHostName());
message.put("ip", TransportConfig.getHeartbeatClientIp());
message.put("app", AppNameUtil.getAppName());
// Put application type (since 1.6.0).
message.put("app_type", String.valueOf(SentinelConfig.getAppType()));
message.put("port", String.valueOf(TransportConfig.getPort()));
}
/**
* 添加自定义参数,可通过此方法扩展心跳消息参数
*/
public HeartbeatMessage registerInformation(String key, String value) {
message.put(key, value);
return this;
}
/**
* 附加参数
*/
public Map<String, String> generateCurrentMessage() {
// Version of Sentinel.
message.put("v", Constants.SENTINEL_VERSION);
// Actually timestamp.
message.put("version", String.valueOf(TimeUtil.currentTimeMillis()));
message.put("port", String.valueOf(TransportConfig.getPort()));
return message;
}
}
心跳扩展
发送器扩展
上面说到的发送器扩展,可以具体看心跳包的provider
public final class HeartbeatSenderProvider {
private static HeartbeatSender heartbeatSender = null;
static {
// 静态方法,类加载时,即执行
resolveInstance();
}
/**
* 解析发送器
*/
private static void resolveInstance() {
// 通过SPI获取级别最高的实现类。即@InitOrder标签设置的顺序
HeartbeatSender resolved = SpiLoader.loadHighestPriorityInstance(HeartbeatSender.class);
if (resolved == null) {
RecordLog.warn("[HeartbeatSenderProvider] WARN: No existing HeartbeatSender found");
} else {
heartbeatSender = resolved;
RecordLog.info("[HeartbeatSenderProvider] HeartbeatSender activated: " + resolved.getClass()
.getCanonicalName());
}
}
/**
* Get resolved {@link HeartbeatSender} instance.
*
* @return resolved {@code HeartbeatSender} instance
*/
public static HeartbeatSender getHeartbeatSender() {
return heartbeatSender;
}
private HeartbeatSenderProvider() {}
}
如果需要扩展,则可以自行实现HeartbeatSender接口,并设置@InitOrder(Integer.MIN_VALUE),这边加载最新的发送器时,就会将顺序最小的加载出来。比如,如果项目支持rpc调用,则可以不适用http方式来发送心跳,而是直接使用rpc接口调用来注册心跳。示例代码如下:
@InitOrder(Integer.MIN_VALUE)
public class MyDemoHeartbeatSender implements HeartbeatSender {
private static final int OK_STATUS = 200;
// 默认的心跳间隔
private static final long DEFAULT_INTERVAL = 1000 * 10;
public MyDemoHeartbeatSender() {
}
@Override
public boolean sendHeartbeat() throws Exception {
// todo your heartbeat send code
return false;
}
@Override
public long intervalMs() {
return DEFAULT_INTERVAL;
}
}
心跳包扩展
通过心跳消息的自定义参数方法扩展心跳包。
private final HeartbeatMessage heartBeat = new HeartbeatMessage();
heartBeat.registerInformation("key","value");