当前位置:   article > 正文

第三篇:Sentinel控制台使用与客户端自动连接原理分析_csp.sentinel.dashboard.server

csp.sentinel.dashboard.server

Sentinel开发了一个控制台给我们可以动态的去操作规则,方便了我们在项目运行过程中临时需要操作的一些情况,使用文档可以参考里面的第5点

Sentinel控制台使用

控制台使用

1. 下载对应的jar包或者从源码级别编译sentinel-dashboard模块都可以

2. 启动即可,就是一个spring-boot项目

3. 控制台启动可以配置的一些参数

Sentinel控制台配置参数

客户端连接

1. 引入以下模块即可

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>1.8.4</version>
</dependency>

2.添加客户端启动参数

-Dcsp.sentinel.dashboard.server=127.0.0.1:7001 -Dproject.name=zxc

sp.sentinel.dashboard.server 为控制台地址

project.name 为客户端项目名称

以上参数都可以在 Sentinel控制台配置参数 找到

我本地的客户端程序还是比较简单,因为只是要进行测试,如下图

 配置的参数,其中127.0.0.1:7001是我控制台的地址

 以这种方式启动以后在控制台就可以看到如下,前提是引入了对应的依赖,上来说了,引入那个依赖sentinel就会把你当前的客户端注册到你指定的控制台上面去

总结:使用控制台还是比较简单的

1. 下载对应的jar包

2. 启动jar包,就是一个spring-boot项目的jar包

3. 引入对应的maven依赖

4. 添加对应的参数

5. 在控制台进行操作

控制台操作,直接点击簇点链路的流控等就会看到对应的操作页面,如流控规则

其他规则可以参考官网文档,这里就不细说了,后面有讲到这些规则的时候再进行细说

 使用其实还是比较简单的,只要根据对应的文档进行下载,操作即可,不过你会不会好奇引入一个maven依赖,然后配置下参数,它就自动会帮我们注册到Sentinel上,这个是怎么做的,这个点会不会有点好奇呢?下面就来说下这个,看它是怎么做的

注:以下只说明是怎么做的逻辑,具体的实现可能不会说太多太细

客户端自动注册原理(源码)

需要下载源码才可以,下载过程在第一篇就说了,这里就不重复说了,可以参考

第一篇:Sentinel基础知识与源码入门_zxc_user的博客-CSDN博客

注册入口

  1. Sentinel-core 模块下有个类叫 com.alibaba.csp.sentinel.Env
  2. public class Env {
  3. public static final Sph sph = new CtSph();
  4. static {
  5. // If init fails, the process will exit.
  6. //执行初始化逻辑,失败的话程序会直接退出
  7. InitExecutor.doInit();
  8. }
  9. }
  10. 具体的逻辑在这个doInit()方法里面
  11. public static void doInit() {
  12. //只执行一遍,使用原子量控制并发问题
  13. if (!initialized.compareAndSet(false, true)) {
  14. return;
  15. }
  16. try {
  17. //这里使用了SPI机制去加载InitFunc接口的实现类
  18. List<InitFunc> initFuncs = SpiLoader.of(InitFunc.class).loadInstanceListSorted();
  19. //进行排序处理,InitFunc可以通过标记@InitOrder注解来指定先后顺序
  20. List<OrderWrapper> initList = new ArrayList<OrderWrapper>();
  21. for (InitFunc initFunc : initFuncs) {
  22. RecordLog.info("[InitExecutor] Found init func: {}", initFunc.getClass().getCanonicalName());
  23. insertSorted(initList, initFunc);
  24. }
  25. //循环调用InitFunc方法的init()方法
  26. for (OrderWrapper w : initList) {
  27. w.func.init();
  28. RecordLog.info("[InitExecutor] Executing {} with order {}",
  29. w.func.getClass().getCanonicalName(), w.order);
  30. }
  31. } catch (Exception ex) {
  32. RecordLog.warn("[InitExecutor] WARN: Initialization failed", ex);
  33. ex.printStackTrace();
  34. } catch (Error error) {
  35. RecordLog.warn("[InitExecutor] ERROR: Initialization failed with fatal error", error);
  36. error.printStackTrace();
  37. }
  38. }

这里面使用了SPI机制加载InitFunc的接口这个是在最重要的一个点,SPI机制前面已经说了,不清楚的可以看下第二篇,因为这里加载了SPI,再看看我们依赖的那个模块下面有什么东西,以下是结构图

 那么由于spi机制的存在,上面两个实现类在应用启动的时候就会被调用对应的init方法,我们来看下这两个实现类做了啥

CommandCenterInitFunc

  1. @Override
  2. public void init() throws Exception {
  3. //获取CommandCenter对象
  4. CommandCenter commandCenter = CommandCenterProvider.getCommandCenter();
  5. //为空则不进行任何处理
  6. if (commandCenter == null) {
  7. RecordLog.warn("[CommandCenterInitFunc] Cannot resolve CommandCenter");
  8. return;
  9. }
  10. //调用CommandCenter对象两个方法
  11. commandCenter.beforeStart();
  12. commandCenter.start();
  13. RecordLog.info("[CommandCenterInit] Starting command center: "
  14. + commandCenter.getClass().getCanonicalName());
  15. }
  16. 代码量很简单,接着我们就需要看下CommandCenterProvider这个提供者做了啥了
  17. public final class CommandCenterProvider {
  18. private static CommandCenter commandCenter = null;
  19. static {
  20. //解析实例
  21. resolveInstance();
  22. }
  23. private static void resolveInstance() {
  24. //又是利用spi机制获取CommandCenter然后取优先值最高的对象
  25. CommandCenter resolveCommandCenter = SpiLoader.of(CommandCenter.class).loadHighestPriorityInstance();
  26. if (resolveCommandCenter == null) {
  27. RecordLog.warn("[CommandCenterProvider] WARN: No existing CommandCenter found");
  28. } else {
  29. commandCenter = resolveCommandCenter;
  30. RecordLog.info("[CommandCenterProvider] CommandCenter resolved: {}", resolveCommandCenter.getClass()
  31. .getCanonicalName());
  32. }
  33. }
  34. /**
  35. * Get resolved {@link CommandCenter} instance.
  36. *
  37. * @return resolved {@code CommandCenter} instance
  38. */
  39. public static CommandCenter getCommandCenter() {
  40. return commandCenter;
  41. }
  42. private CommandCenterProvider() {}
  43. }
  44. 可以看到CommandCenterProvider一开始就去加载了,再次使用到spi机制,CommandCenter 有几个实现类,其中一个就是sentinel-transport-simple-http maven依赖里面提供的SimpleHttpCommandCenter实现类

通过上面的分析我们找到了SimpleHttpCommandCenter,那么接下来就是看这个类里面的beforeStart()和start()方法了

SimpleHttpCommandCenter

  1. com.alibaba.csp.sentinel.transport.command.SimpleHttpCommandCenter#beforeStart
  2. @Override
  3. @SuppressWarnings("rawtypes")
  4. public void beforeStart() throws Exception {
  5. // Register handlers
  6. //通过spi机制获取所有CommandHandler对象
  7. Map<String, CommandHandler> handlers = CommandHandlerProvider.getInstance().namedHandlers();
  8. //注册命令
  9. registerCommands(handlers);
  10. //注:这里使用了命令设计模式
  11. }
  12. public void start() throws Exception {
  13. //获取当前运行环境的线程核心数
  14. int nThreads = Runtime.getRuntime().availableProcessors();
  15. //声明线程池
  16. this.bizExecutor = new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
  17. new ArrayBlockingQueue<Runnable>(10),
  18. new NamedThreadFactory("sentinel-command-center-service-executor", true),
  19. new RejectedExecutionHandler() {
  20. @Override
  21. public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
  22. CommandCenterLog.info("EventTask rejected");
  23. throw new RejectedExecutionException();
  24. }
  25. });
  26. Runnable serverInitTask = new Runnable() {
  27. int port;
  28. {
  29. try {
  30. port = Integer.parseInt(TransportConfig.getPort());
  31. } catch (Exception e) {
  32. port = DEFAULT_PORT;
  33. }
  34. }
  35. @Override
  36. public void run() {
  37. boolean success = false;
  38. //创建ServerSocket客户端,用来接收控制台放松的消息
  39. ServerSocket serverSocket = getServerSocketFromBasePort(port);
  40. if (serverSocket != null) {
  41. CommandCenterLog.info("[CommandCenter] Begin listening at port " + serverSocket.getLocalPort());
  42. socketReference = serverSocket;
  43. //往线程池提交任务,类型为ServerThread
  44. executor.submit(new ServerThread(serverSocket));
  45. success = true;
  46. port = serverSocket.getLocalPort();
  47. } else {
  48. CommandCenterLog.info("[CommandCenter] chooses port fail, http command center will not work");
  49. }
  50. if (!success) {
  51. port = PORT_UNINITIALIZED;
  52. }
  53. TransportConfig.setRuntimePort(port);
  54. //关闭线程池
  55. executor.shutdown();
  56. }
  57. };
  58. //启动上面定义的线程
  59. new Thread(serverInitTask).start();
  60. }
  61. ServerThread的代码如下
  62. class ServerThread extends Thread {
  63. private ServerSocket serverSocket;
  64. ServerThread(ServerSocket s) {
  65. this.serverSocket = s;
  66. setName("sentinel-courier-server-accept-thread");
  67. }
  68. @Override
  69. public void run() {
  70. //死循环
  71. while (true) {
  72. Socket socket = null;
  73. try {
  74. //接收控制台发来的消息
  75. socket = this.serverSocket.accept();
  76. setSocketSoTimeout(socket);
  77. //事件任务,默认情况下控制台会一直发送 metric 事件
  78. HttpEventTask eventTask = new HttpEventTask(socket);
  79. //提交
  80. bizExecutor.submit(eventTask);
  81. } catch (Exception e) {
  82. CommandCenterLog.info("Server error", e);
  83. if (socket != null) {
  84. try {
  85. socket.close();
  86. } catch (Exception e1) {
  87. CommandCenterLog.info("Error when closing an opened socket", e1);
  88. }
  89. }
  90. try {
  91. // In case of infinite log.
  92. //10ms就接收一次
  93. Thread.sleep(10);
  94. } catch (InterruptedException e1) {
  95. // Indicates the task should stop.
  96. break;
  97. }
  98. }
  99. }
  100. }
  101. }
  102. 这里面最核心的是HttpEventTask事件,用来接收处理控制器发过来的事件,看一下它的run方法,比较长,不过主要的逻辑就那几句
  103. @Override
  104. public void run() {
  105. if (socket == null) {
  106. return;
  107. }
  108. PrintWriter printWriter = null;
  109. InputStream inputStream = null;
  110. try {
  111. //整理获取数据...这一块就不解析了
  112. long start = System.currentTimeMillis();
  113. inputStream = new BufferedInputStream(socket.getInputStream());
  114. OutputStream outputStream = socket.getOutputStream();
  115. printWriter = new PrintWriter(
  116. new OutputStreamWriter(outputStream, Charset.forName(SentinelConfig.charset())));
  117. String firstLine = readLine(inputStream);
  118. CommandCenterLog.info("[SimpleHttpCommandCenter] Socket income: " + firstLine
  119. + ", addr: " + socket.getInetAddress());
  120. CommandRequest request = processQueryString(firstLine);
  121. if (firstLine.length() > 4 && StringUtil.equalsIgnoreCase("POST", firstLine.substring(0, 4))) {
  122. // Deal with post method
  123. processPostRequest(inputStream, request);
  124. }
  125. // Validate the target command.
  126. // 从请求中获取要执行的命令
  127. String commandName = HttpCommandUtils.getTarget(request);
  128. if (StringUtil.isBlank(commandName)) {
  129. writeResponse(printWriter, StatusCode.BAD_REQUEST, INVALID_COMMAND_MESSAGE);
  130. return;
  131. }
  132. System.out.println("commandName = " + commandName);
  133. // Find the matching command handler.
  134. //再从前面加载的命令中获取对应的命令处理器
  135. CommandHandler<?> commandHandler = SimpleHttpCommandCenter.getHandler(commandName);
  136. if (commandHandler != null) {
  137. //处理命令并响应给控制台
  138. CommandResponse<?> response = commandHandler.handle(request);
  139. handleResponse(response, printWriter);
  140. } else {
  141. // No matching command handler.
  142. //没有找到匹配的,直接报错
  143. writeResponse(printWriter, StatusCode.BAD_REQUEST, "Unknown command `" + commandName + '`');
  144. }
  145. long cost = System.currentTimeMillis() - start;
  146. CommandCenterLog.info("[SimpleHttpCommandCenter] Deal a socket task: " + firstLine
  147. + ", address: " + socket.getInetAddress() + ", time cost: " + cost + " ms");
  148. } catch (RequestException e) {
  149. writeResponse(printWriter, e.getStatusCode(), e.getMessage());
  150. } catch (Throwable e) {
  151. CommandCenterLog.warn("[SimpleHttpCommandCenter] CommandCenter error", e);
  152. try {
  153. if (printWriter != null) {
  154. String errorMessage = SERVER_ERROR_MESSAGE;
  155. e.printStackTrace();
  156. if (!writtenHead) {
  157. writeResponse(printWriter, StatusCode.INTERNAL_SERVER_ERROR, errorMessage);
  158. } else {
  159. printWriter.println(errorMessage);
  160. }
  161. printWriter.flush();
  162. }
  163. } catch (Exception e1) {
  164. CommandCenterLog.warn("Failed to write error response", e1);
  165. }
  166. } finally {
  167. closeResource(inputStream);
  168. closeResource(printWriter);
  169. closeResource(socket);
  170. }
  171. }
  172. 这里面最核心的就是命令行设计模式设计的CommandHandler对象了,这个对象的实现类在前面已经通过spi机制进行加载了,可以稍微看下有哪些
  173. 在 sentinel-transport-common模块中的com.alibaba.csp.sentinel.command.CommandHandler文件
  174. com.alibaba.csp.sentinel.command.handler.BasicInfoCommandHandler
  175. com.alibaba.csp.sentinel.command.handler.FetchActiveRuleCommandHandler
  176. com.alibaba.csp.sentinel.command.handler.FetchClusterNodeByIdCommandHandler
  177. com.alibaba.csp.sentinel.command.handler.FetchClusterNodeHumanCommandHandler
  178. com.alibaba.csp.sentinel.command.handler.FetchJsonTreeCommandHandler
  179. com.alibaba.csp.sentinel.command.handler.FetchOriginCommandHandler
  180. com.alibaba.csp.sentinel.command.handler.FetchSimpleClusterNodeCommandHandler
  181. com.alibaba.csp.sentinel.command.handler.FetchSystemStatusCommandHandler
  182. com.alibaba.csp.sentinel.command.handler.FetchTreeCommandHandler
  183. com.alibaba.csp.sentinel.command.handler.ModifyRulesCommandHandler
  184. com.alibaba.csp.sentinel.command.handler.OnOffGetCommandHandler
  185. com.alibaba.csp.sentinel.command.handler.OnOffSetCommandHandler
  186. com.alibaba.csp.sentinel.command.handler.SendMetricCommandHandler
  187. com.alibaba.csp.sentinel.command.handler.VersionCommandHandler
  188. com.alibaba.csp.sentinel.command.handler.cluster.FetchClusterModeCommandHandler
  189. com.alibaba.csp.sentinel.command.handler.cluster.ModifyClusterModeCommandHandler
  190. com.alibaba.csp.sentinel.command.handler.ApiCommandHandler
  191. 有以上这些实现,我们可以稍微看下ModifyRulesCommandHandler这个类,这是最重要的一个
  192. @Override
  193. public CommandResponse<String> handle(CommandRequest request) {
  194. //前面还是在整理获取数据
  195. // XXX from 1.7.2, force to fail when fastjson is older than 1.2.12
  196. // We may need a better solution on this.
  197. if (VersionUtil.fromVersionString(JSON.VERSION) < FASTJSON_MINIMAL_VER) {
  198. // fastjson too old
  199. return CommandResponse.ofFailure(new RuntimeException("The \"fastjson-" + JSON.VERSION
  200. + "\" introduced in application is too old, you need fastjson-1.2.12 at least."));
  201. }
  202. String type = request.getParam("type");
  203. // rule data in get parameter
  204. String data = request.getParam("data");
  205. if (StringUtil.isNotEmpty(data)) {
  206. try {
  207. data = URLDecoder.decode(data, "utf-8");
  208. } catch (Exception e) {
  209. RecordLog.info("Decode rule data error", e);
  210. return CommandResponse.ofFailure(e, "decode rule data error");
  211. }
  212. }
  213. RecordLog.info("Receiving rule change (type: {}): {}", type, data);
  214. String result = "success";
  215. //流控规则
  216. if (FLOW_RULE_TYPE.equalsIgnoreCase(type)) {
  217. //解析数据已经更新数据
  218. List<FlowRule> flowRules = JSONArray.parseArray(data, FlowRule.class);
  219. FlowRuleManager.loadRules(flowRules);
  220. //持久化数据
  221. if (!writeToDataSource(getFlowDataSource(), flowRules)) {
  222. result = WRITE_DS_FAILURE_MSG;
  223. }
  224. return CommandResponse.ofSuccess(result);
  225. } else if (AUTHORITY_RULE_TYPE.equalsIgnoreCase(type)) { //黑白名单
  226. //解析数据已经更新数据
  227. List<AuthorityRule> rules = JSONArray.parseArray(data, AuthorityRule.class);
  228. AuthorityRuleManager.loadRules(rules);
  229. //持久化数据
  230. if (!writeToDataSource(getAuthorityDataSource(), rules)) {
  231. result = WRITE_DS_FAILURE_MSG;
  232. }
  233. return CommandResponse.ofSuccess(result);
  234. } else if (DEGRADE_RULE_TYPE.equalsIgnoreCase(type)) {
  235. //降级数据
  236. List<DegradeRule> rules = JSONArray.parseArray(data, DegradeRule.class);
  237. DegradeRuleManager.loadRules(rules);
  238. //持久化数据
  239. if (!writeToDataSource(getDegradeDataSource(), rules)) {
  240. result = WRITE_DS_FAILURE_MSG;
  241. }
  242. return CommandResponse.ofSuccess(result);
  243. } else if (SYSTEM_RULE_TYPE.equalsIgnoreCase(type)) {
  244. //系统规则
  245. List<SystemRule> rules = JSONArray.parseArray(data, SystemRule.class);
  246. SystemRuleManager.loadRules(rules);
  247. //持久化数据
  248. if (!writeToDataSource(getSystemSource(), rules)) {
  249. result = WRITE_DS_FAILURE_MSG;
  250. }
  251. return CommandResponse.ofSuccess(result);
  252. }
  253. return CommandResponse.ofFailure(new IllegalArgumentException("invalid type"));
  254. }
  255. writeToDataSource 写到数据源的逻辑原来也在这里,不过这个后面再看就行了

总结:以上就是 CommandCenterInitFunc以及SimpleHttpCommandCenter的原理,客户端接收服务端的流程和逻辑处理,下面再讲另一个东西

HeartbeatSenderInitFunc

看名字就知道是用来处理心跳机制的,也就是客户端发送信息给控制台的,下面就来看下具体的情况

  1. 先看下 HeartbeatSenderProvider 这个类做了啥
  2. public final class HeartbeatSenderProvider {
  3. private static HeartbeatSender heartbeatSender = null;
  4. static {
  5. //解析实例
  6. resolveInstance();
  7. }
  8. private static void resolveInstance() {
  9. //又是利用spi机制获取优先级最高的
  10. //我们引入sentinel-transport-simple-http会加载一个SimpleHttpHeartbeatSender,所以这里拿到的就是SimpleHttpHeartbeatSender
  11. //除非有其他实现类
  12. HeartbeatSender resolved = SpiLoader.of(HeartbeatSender.class).loadHighestPriorityInstance();
  13. if (resolved == null) {
  14. RecordLog.warn("[HeartbeatSenderProvider] WARN: No existing HeartbeatSender found");
  15. } else {
  16. heartbeatSender = resolved;
  17. RecordLog.info("[HeartbeatSenderProvider] HeartbeatSender activated: {}", resolved.getClass()
  18. .getCanonicalName());
  19. }
  20. }
  21. /**
  22. * Get resolved {@link HeartbeatSender} instance.
  23. *
  24. * @return resolved {@code HeartbeatSender} instance
  25. */
  26. public static HeartbeatSender getHeartbeatSender() {
  27. return heartbeatSender;
  28. }
  29. private HeartbeatSenderProvider() {}
  30. }
  31. HeartbeatSenderProvider这个类就是生成一个HeartbeatSender 对象,而我们这里实际上就是SimpleHttpHeartbeatSender对象
  32. com.alibaba.csp.sentinel.transport.init.HeartbeatSenderInitFunc#init
  33. @Override
  34. public void init() {
  35. //获取HeartbeatSender对象
  36. HeartbeatSender sender = HeartbeatSenderProvider.getHeartbeatSender();
  37. if (sender == null) {
  38. RecordLog.warn("[HeartbeatSenderInitFunc] WARN: No HeartbeatSender loaded");
  39. return;
  40. }
  41. //如果需要初始话定时线程池
  42. initSchedulerIfNeeded();
  43. //获取发送间隔
  44. long interval = retrieveInterval(sender);
  45. //把间隔设置回去
  46. setIntervalIfNotExists(interval);
  47. //启动任务定时发送心跳
  48. scheduleHeartbeatTask(sender, interval);
  49. }
  50. retrieveInterval 这个方法里面就可以看到具体的间隔时间是怎么拿取的,可以通过
  51. csp.sentinel.heartbeat.interval.ms参数进行设置,原理也就是在这个位置
  52. long retrieveInterval(/*@NonNull*/ HeartbeatSender sender) {
  53. //从配置中获取,如果是有效的直接返回
  54. Long intervalInConfig = TransportConfig.getHeartbeatIntervalMs();
  55. if (isValidHeartbeatInterval(intervalInConfig)) {
  56. RecordLog.info("[HeartbeatSenderInitFunc] Using heartbeat interval "
  57. + "in Sentinel config property: " + intervalInConfig);
  58. return intervalInConfig;
  59. } else {
  60. //如果无效则从HeartbeatSender获取这个间隔, SimpleHttpHeartbeatSender默认为10s执行一次
  61. long senderInterval = sender.intervalMs();
  62. RecordLog.info("[HeartbeatSenderInit] Heartbeat interval not configured in "
  63. + "config property or invalid, using sender default: " + senderInterval);
  64. return senderInterval;
  65. }
  66. }
  67. 最后再看下 scheduleHeartbeatTask方法,具体发送心跳的逻辑
  68. private void scheduleHeartbeatTask(/*@NonNull*/ final HeartbeatSender sender, /*@Valid*/ long interval) {
  69. pool.scheduleAtFixedRate(new Runnable() {
  70. @Override
  71. public void run() {
  72. try {
  73. //发送心跳
  74. sender.sendHeartbeat();
  75. } catch (Throwable e) {
  76. //失败了也只是当时那个失败,不会停止整个线程
  77. RecordLog.warn("[HeartbeatSender] Send heartbeat error", e);
  78. }
  79. }
  80. //5秒后启动,每interval执行一次
  81. }, 5000, interval, TimeUnit.MILLISECONDS);
  82. RecordLog.info("[HeartbeatSenderInit] HeartbeatSender started: "
  83. + sender.getClass().getCanonicalName());
  84. }
  85. 那么逻辑很明显就在具体的HeartbeatSender 进行实现了,下面再具体看一下
  86. com.alibaba.csp.sentinel.transport.heartbeat.SimpleHttpHeartbeatSender#sendHeartbeat
  87. @Override
  88. public boolean sendHeartbeat() throws Exception {
  89. //前置判断
  90. if (TransportConfig.getRuntimePort() <= 0) {
  91. RecordLog.info("[SimpleHttpHeartbeatSender] Command server port not initialized, won't send heartbeat");
  92. return false;
  93. }
  94. //获取控制台的地址,就是csp.sentinel.dashboard.server参数配置的,可以配置多个,多个时会进行负载均衡请求
  95. Endpoint addrInfo = getAvailableAddress();
  96. if (addrInfo == null) {
  97. return false;
  98. }
  99. //封装请求对象以及设置参数
  100. SimpleHttpRequest request = new SimpleHttpRequest(addrInfo, TransportConfig.getHeartbeatApiPath());
  101. request.setParams(heartBeat.generateCurrentMessage());
  102. try {
  103. //发送http请求
  104. SimpleHttpResponse response = httpClient.post(request);
  105. if (response.getStatusCode() == OK_STATUS) {
  106. return true;
  107. } else if (clientErrorCode(response.getStatusCode()) || serverErrorCode(response.getStatusCode())) {
  108. RecordLog.warn("[SimpleHttpHeartbeatSender] Failed to send heartbeat to " + addrInfo
  109. + ", http status code: " + response.getStatusCode());
  110. }
  111. } catch (Exception e) {
  112. RecordLog.warn("[SimpleHttpHeartbeatSender] Failed to send heartbeat to " + addrInfo, e);
  113. }
  114. return false;
  115. }
  116. 逻辑还是挺清晰的,这里唯一的问题就是请求地址是怎么确定的,可以再稍微看下
  117. SimpleHttpHeartbeatSender构造方法里面获取的,TransportConfig.getConsoleServerList()
  118. 这个就不进去看了,就是解析csp.sentinel.dashboard.server参数,多个以,分割,然后把它组成一个个
  119. Endpoint对象放到列表去
  120. public SimpleHttpHeartbeatSender() {
  121. // Retrieve the list of default addresses.
  122. //获取地址列表
  123. List<Endpoint> newAddrs = TransportConfig.getConsoleServerList();
  124. if (newAddrs.isEmpty()) {
  125. RecordLog.warn("[SimpleHttpHeartbeatSender] Dashboard server address not configured or not available");
  126. } else {
  127. RecordLog.info("[SimpleHttpHeartbeatSender] Default console address list retrieved: {}", newAddrs);
  128. }
  129. //赋值
  130. this.addressList = newAddrs;
  131. }
  132. 至此心跳逻辑就分析完了,发下心跳的时候自然是会往控制台注册自己的,至于控制台规则是怎么来的,那个逻辑就是控制台那边通过注册的ip,调用对应的借口获取然后展示到上面的,后面如果有时间,再写篇文章去看这个问题,其实原理都是一样的

明显心跳的逻辑比接收的逻辑要简单很多,这里依然使用的设计模式有策略模式以及spi机制

到这里,自动注册的原理就分析完了,再稍微总结一下原理

1, 引入sentinel-transport-simple-http的maven依赖

2. Env初始快调用InitFunc的init方法

3. 引入sentinel-transport-simple-http时会同时依赖sentinel-transport-common,而该模块会有InitFunc的实现,主要有以下两个

CommandCenterInitFunc:用于接收控制台发送消息给客户段的

HeartbeatSenderInitFunc:用于发送心跳给控制台的

4. 在执行上面这两个的时候会有对应的实现类,而这两个实现类是在sentinel-transport-simple-http模块中的,主要是

SimpleHttpCommandCenter:用于实现接收控制台发送消息给客户段

SimpleHttpHeartbeatSender:用于实现发送心跳也就是注册信息给控制台的

到此就结束了,在这个过程中可以看到作者是如何巧妙的使用spi机制进行解耦的,Env中执行调用了InitFunc的init方法,而sentinel-transport-simple-htt模块中自己根据spi机制定义了对应的规范,有以上的实现类,自然而然通过spi机制就可以直接添加被所承载的应用进行加载了,最后实现了引入依赖就自动被注册的动态效果!!

多参考一下大佬们的源码书写确实有一定帮助

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号