以下的分析环境基于内存消息队列和无注册中心配置以及按照默认配置

1.Clustering mode

官网对集群模式有一部分介绍这可以帮助我们理解代码为什么会这么做:

ThingsBoard adopts consistent hashing to ensure scalability and availability. Message from Device A that is received on a particular node may be forwarded to the other node based on the hash of the device ID. Although this introduces certain networking overhead, it allows to process all messages from a particular device using corresponding device actor on a determined server, which introduces the following advantages: improve cache hit rate. Device attributes and other device related data are fetched by device actor on a specific server. avoid race conditions. All messages for a particular device are processed on a determined server. allows targeting server-side api calls based on the device id.

2. TbServiceInfoProvider

DefaultTbServiceInfoProvider init()方法由@PostConstruct标记,spring 启动的时候会自动调用该方法:

  1. public void init() {
  2. if (StringUtils.isEmpty(serviceId)) {
  3. try {
  4. //获取本机的HostName作为serviceId
  5. serviceId = InetAddress.getLocalHost().getHostName();
  6. } catch (UnknownHostException e) {
  7. serviceId = org.apache.commons.lang3.RandomStringUtils.randomAlphabetic(10);
  8. }
  9. }
  10. log.info("Current Service ID: {}", serviceId);
  11. //serviceType是配置文件下service.type的值,默认为monolith
  12. //serviceTypes将会是一个List包含TB_CORE, TB_RULE_ENGINE, TB_TRANSPORT, JS_EXECUTOR ①
  13. if (serviceType.equalsIgnoreCase("monolith")) {
  14. serviceTypes = Collections.unmodifiableList(Arrays.asList(ServiceType.values()));
  15. } else {
  16. serviceTypes = Collections.singletonList(ServiceType.of(serviceType));
  17. }
  18. ServiceInfo.Builder builder = ServiceInfo.newBuilder()
  19. .setServiceId(serviceId)
  20. .addAllServiceTypes(serviceTypes.stream().map(ServiceType::name).collect(Collectors.toList()));
  21. UUID tenantId;
  22. //tenantIdStr是配置文件中service.tenant_id的值,默认情况下为空,isolatedTenant也就为空了
  23. if (!StringUtils.isEmpty(tenantIdStr)) {
  24. tenantId = UUID.fromString(tenantIdStr);
  25. isolatedTenant = new TenantId(tenantId);
  26. } else {
  27. tenantId = TenantId.NULL_UUID;
  28. }
  29. //返回此 uuid 的 128 位值中的最高有效 64 位和最低64位
  30. builder.setTenantIdMSB(tenantId.getMostSignificantBits());
  31. builder.setTenantIdLSB(tenantId.getLeastSignificantBits());
  32. //ruleEngineSettings是一个TbQueueRuleEngineSettings的一个实例,读取queue.rule-engine下的值
  33. //ruleEngineSettings包含topic是tb_rule_engine,queue队列有三个分别是②:
  34. // 1. name: Main topic: tb_rule_engine.main partition: 10
  35. // 2. name: HighPriority topic: tb_rule_engine.hp partition: 10
  36. // 3. name: SequentialByOriginator topic: tb_rule_engine.sq partition: 10
  37. if (serviceTypes.contains(ServiceType.TB_RULE_ENGINE) && ruleEngineSettings != null) {
  38. for (TbRuleEngineQueueConfiguration queue : ruleEngineSettings.getQueues()) {
  39. TransportProtos.QueueInfo queueInfo = TransportProtos.QueueInfo.newBuilder()
  40. .setName(queue.getName())
  41. .setTopic(queue.getTopic())
  42. .setPartitions(queue.getPartitions()).build();
  43. builder.addRuleEngineQueues(queueInfo);
  44. }
  45. }
  46. serviceInfo = builder.build();
  47. }

3. PartitionService

PartitionService的默认实现是HashPartitionService

  1. @PostConstruct
  2. public void init() {
  3. //根据queue.partitions.hash_function_name的配置选择以后做partition的hash方法,默认值是murmur3_128
  4. this.hashFunction = forName(hashFunctionName);
  5. //ConcurrentMap<ServiceQueue, Integer> partitionSizes
  6. //ServiceQueue 类成员ServiceType和字符串类型的queue name,构造函数如果不提供queue name或者queue name是null的话,ServiceQueue对象的的queue name是"Main"
  7. //corePartitions 是queue.core.partitions默认值10
  8. partitionSizes.put(new ServiceQueue(ServiceType.TB_CORE), corePartitions);
  9. //coreTopic对应的配置文件键是queue.core.topic,默认值tb_core
  10. partitionTopics.put(new ServiceQueue(ServiceType.TB_CORE), coreTopic);
  11. //根据DefaultTbServiceInfoProvider②的分析可以得出partitionTopics,partitionSizes的具体内容
  12. tbQueueRuleEngineSettings.getQueues().forEach(queueConfiguration -> {
  13. partitionTopics.put(new ServiceQueue(ServiceType.TB_RULE_ENGINE, queueConfiguration.getName()), queueConfiguration.getTopic());
  14. partitionSizes.put(new ServiceQueue(ServiceType.TB_RULE_ENGINE, queueConfiguration.getName()), queueConfiguration.getPartitions());
  15. });
  16. }

4. DiscoveryService

因为没有使用 Zookeeper 做注册中心,DiscoveryService的实现由DummyDiscoveryService实现,在收到 Spring 发送的ApplicationReadyEvent事件后,调用partitionService.recalculatePartitions方法:

  1. public void recalculatePartitions(ServiceInfo currentService, List<ServiceInfo> otherServices) {
  2. //日志记录
  3. logServiceInfo(currentService);
  4. //dummy Discovery将不包含otherService,Zookeeper注册中心的实现将会有otherService
  5. otherServices.forEach(this::logServiceInfo);
  6. Map<ServiceQueueKey, List<ServiceInfo>> queueServicesMap = new HashMap<>();
  7. //展开ServiceInfo的serviceTypes和RuleEngineQueue,并添加到queueServicesMap
  8. addNode(queueServicesMap, currentService);
  9. for (ServiceInfo other : otherServices) {
  10. addNode(queueServicesMap, other);
  11. }
  12. queueServicesMap.values().forEach(list -> list.sort((a, b) -> a.getServiceId().compareTo(b.getServiceId())));
  13. ConcurrentMap<ServiceQueueKey, List<Integer>> oldPartitions = myPartitions;
  14. TenantId myIsolatedOrSystemTenantId = getSystemOrIsolatedTenantId(currentService);
  15. myPartitions = new ConcurrentHashMap<>();
  16. //创建了ServiceQueueKey和partitionIndex的list组合
  17. partitionSizes.forEach((serviceQueue, size) -> {
  18. ServiceQueueKey myServiceQueueKey = new ServiceQueueKey(serviceQueue, myIsolatedOrSystemTenantId);
  19. for (int i = 0; i < size; i++) {
  20. ServiceInfo serviceInfo = resolveByPartitionIdx(queueServicesMap.get(myServiceQueueKey), i);
  21. if (currentService.equals(serviceInfo)) {
  22. ServiceQueueKey serviceQueueKey = new ServiceQueueKey(serviceQueue, getSystemOrIsolatedTenantId(serviceInfo));
  23. myPartitions.computeIfAbsent(serviceQueueKey, key -> new ArrayList<>()).add(i);
  24. }
  25. }
  26. });
  27. oldPartitions.forEach((serviceQueueKey, partitions) -> {
  28. if (!myPartitions.containsKey(serviceQueueKey)) {
  29. log.info("[{}] NO MORE PARTITIONS FOR CURRENT KEY", serviceQueueKey);
  30. applicationEventPublisher.publishEvent(new PartitionChangeEvent(this, serviceQueueKey, Collections.emptySet()));
  31. }
  32. });
  33. //发送PartitionChangeEvent,创建的TopicPartitionInfo的topic是partitionTopics的topic,fullTopicName是topic+Index, DummyDiscovery每次tpiList包含了所有的partitionIndex, 此例0-9.例:tpiList其中的fullTopicName是tb_core.9,tb_rule_engine.main.3
  34. myPartitions.forEach((serviceQueueKey, partitions) -> {
  35. if (!partitions.equals(oldPartitions.get(serviceQueueKey))) {
  36. log.info("[{}] NEW PARTITIONS: {}", serviceQueueKey, partitions);
  37. Set<TopicPartitionInfo> tpiList = partitions.stream()
  38. .map(partition -> buildTopicPartitionInfo(serviceQueueKey, partition))
  39. .collect(Collectors.toSet());
  40. applicationEventPublisher.publishEvent(new PartitionChangeEvent(this, serviceQueueKey, tpiList));
  41. }
  42. });
  43. tpiCache.clear();
  44. if (currentOtherServices == null) {
  45. currentOtherServices = new ArrayList<>(otherServices);
  46. } else {
  47. Set<ServiceQueueKey> changes = new HashSet<>();
  48. Map<ServiceQueueKey, List<ServiceInfo>> currentMap = getServiceKeyListMap(currentOtherServices);
  49. Map<ServiceQueueKey, List<ServiceInfo>> newMap = getServiceKeyListMap(otherServices);
  50. currentOtherServices = otherServices;
  51. currentMap.forEach((key, list) -> {
  52. if (!list.equals(newMap.get(key))) {
  53. changes.add(key);
  54. }
  55. });
  56. currentMap.keySet().forEach(newMap::remove);
  57. changes.addAll(newMap.keySet());
  58. if (!changes.isEmpty()) {
  59. applicationEventPublisher.publishEvent(new ClusterTopologyChangeEvent(this, changes));
  60. }
  61. }
  62. }

5. DefaultTbCoreConsumerService

ThingsBoard源码分析之启动分析 1 - 图1

  • DefaultTbCoreConsumerService的父类是AbstractConsumerService, 而父类接收到ApplicationReadyEvent时,会调用子类的launchMainConsumers()应用设计模式模板方法模式,启动了消费者线;其作用是:消费者线程按照固定的延时,无限循环去消息队列提取消息,由于此时并未 subscribed,也即未订阅,将暂时不从消息队列里取消息;
  • 前面讨论到DiscoveryService发布了PartitionChangeEventDefaultTbCoreConsumerService实现了ApplicationListener<PartitionChangeEvent>接口,在接收到PartitionChangeEvent时,会做出相应的反应,调用onApplicationEvent,订阅了的 fullTopicName 是tb_core.0-9 每次从消息队列进行 poll 的时候,都会检查这些 fullTopicName 里面是否有消息准备就绪;

    6. DefaultTbRuleEngineConsumerService

    ThingsBoard源码分析之启动分析 1 - 图2

  • @PostConstruct注解初始化了 ruleEngine 的消费者,按照TbServiceInfoProviderbean 的初始化②,可以知道,初始化了三个 consumer, 放到了consumers的 map 变量里,key 是 queue 的 name;

  • DefaultTbRuleEngineConsumerService的父类也是AbstractConsumerService, 跟DefaultTbCoreConsumerService一样,父类接收到ApplicationReadyEvent时,调用该类的launchMainConsumers()方法,启动了三个消费者线程,由于还未订阅,所以暂时不从消息队列里面获取消息;
  • DefaultTbCoreConsumerService一样,收到PartitionChangeEvent时,启动订阅主题tb_rule_engine.main.0-9,tb_rule_engine.sq.0-9,tb_rule_engine.hp.0-9
    1. public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
    2. //此时serviceType是ServiceType.TB_RULE_ENGINE,过滤了其他的type
    3. if (partitionChangeEvent.getServiceType().equals(getServiceType())) {
    4. ServiceQueue serviceQueue = partitionChangeEvent.getServiceQueueKey().getServiceQueue();
    5. log.info("[{}] Subscribing to partitions: {}", serviceQueue.getQueue(), partitionChangeEvent.getPartitions());
    6. //根绝queueName做区分,让三个consumer分别订阅了三个主题的列表tb_rule_engine.main.0-9,tb_rule_engine.sq.0-9,tb_rule_engine.hp.0-9
    7. consumers.get(serviceQueue.getQueue()).subscribe(partitionChangeEvent.getPartitions());
    8. }
    9. }

    总结

    通过上面类的初始化一系列过程,我们有了大概的印象:
  1. 创建了ServiceInfo对象,并根据配置文件我们得知了 tb_core,和 tb_rule_engine 的一些配置信息;
  2. DiscoveryService根据注册中心,和其他的服务提供者,会计算相应的 partition index,并发布PartitionChangeEvent;
  3. AbstractConsumerService的两个实现类在接收到PartitionChangeEvent之前,都启动了一个或多个线程,在接收到此消息的时候, 都会使自己的 consumer 订阅相应的主题等待消息的到来。