turbine源码分析

1、turbine架构设计

一切从InstanceDiscovery模块开始,该模块提供所有的主机信息。它会定期的发送更新,ConnectionManager 负责创建连接到主机。一旦建立起连接,数据流将源源不断的发送给Aggregator既聚合器。聚合器将数据汇聚后的数据输出到客户端或者下游监听者。

汇聚示例:

  1. {type:'weather-data-temp', name:'New York', temp:74}
  2. {type:'weather-data-temp', name:'Los Angeles', temp:85}
  3. {type:'weather-data-temp', name:'New York', temp:76}
  4. {type:'weather-data-wind-velocity', name:'New York', temp:12}
  5. {type:'weather-data-wind-velocity', name:'Los Angeles', temp:10}

比如有一组这样的数据,经过汇聚后得到下面的结果

  1. {type:'weather-data-temp', name:'Los Angeles', temp:85}
  2. {type:'weather-data-temp', name:'New York', temp:75}
  3. {type:'weather-data-wind-velocity', name:'New York', temp:12}
  4. {type:'weather-data-wind-velocity', name:'Los Angeles', temp:10}

说明:它是使用type和name做为关键字,进行数据汇聚的

2、实例发现

  1. 我们先来看看的架构图

turbine通过配置文件,配置需要收集指标的应用。InstanceObservable定期的拉取实例信息,通知InstanceObserver进行主机信息的更新。turbine只有在主机是UP的状态的时候,才会去连接主机获取指标。这么做的原因是很多复杂的系统在他们将自己标记为可用之前,需要通过长时间的启动引导做一些预处理,比如查询缓存、建立主机连接、运行预计算逻辑等。我们通过分析代码的结构也可以看到

turbine启动的时候会调用TurbineInitinit()方法进行初始化

  1. public static void init() {
  2. ClusterMonitorFactory clusterMonitorFactory = PluginsFactory.getClusterMonitorFactory();
  3. if(clusterMonitorFactory == null) {
  4. PluginsFactory.setClusterMonitorFactory(new DefaultAggregatorFactory());
  5. }
  6. PluginsFactory.getClusterMonitorFactory().initClusterMonitors();
  7. InstanceDiscovery instanceDiscovery = PluginsFactory.getInstanceDiscovery();
  8. if (instanceDiscovery == null) {
  9. PluginsFactory.setInstanceDiscovery(getInstanceDiscoveryImpl());
  10. }
  11. //调用start()方式,初始化实例发现
  12. InstanceObservable.getInstance().start(PluginsFactory.getInstanceDiscovery());
  13. }

start()方法如下:

  1. public void start(InstanceDiscovery iDiscovery) {
  2. if (started.get()) {
  3. throw new RuntimeException("InstanceDiscovery already started");
  4. }
  5. if (iDiscovery == null) {
  6. throw new RuntimeException("InstanceDiscovery is null");
  7. }
  8. instanceDiscovery = iDiscovery;
  9. logger.info("Starting InstanceObservable at frequency: " + pollDelayMillis.get() + " millis");
  10. //每隔pollDelayMillis去拉取一次主机的信息
  11. //通过producer
  12. timer.schedule(producer, 0, pollDelayMillis.get());
  13. started.set(true);
  14. }

producer的定义如下:

  1. private final TimerTask producer = new TimerTask() {
  2. @Override
  3. public void run() {
  4. //主要代码...
  5. for(InstanceObserver watcher: observers.values()) {
  6. if(currentState.get().hostsUp.size() > 0) {
  7. try {
  8. //通知InstanceObserver主机是UP状态
  9. //watcher是个InstanceObserver接口的实例化对象
  10. watcher.hostsUp(currentState.get().hostsUp);
  11. } catch(Throwable t) {
  12. logger.error("Could not call hostUp on watcher: " + watcher.getName(), t);
  13. }
  14. }
  15. }
  16. }
  17. };

通过IDEA工具,找到InstanceObserver接口只有一个实现类ClusterMonitorInstanceManager,该类中实现了hostsUp()方法,而hostsUp()方法又循环去调用hostUp方法,hostUp()方法如下:

  1. public void hostUp(Instance host) {
  2. if (!(getObservationCriteria().observeHost(host))) {
  3. return;
  4. }
  5. TurbineDataMonitor<DataFromSingleInstance> monitor = getMonitor(host);
  6. try {
  7. if(hostDispatcher.findHandlerForHost(host, getEventHandler().getName()) == null) {
  8. // this handler is not already present for this host, then add it
  9. hostDispatcher.registerEventHandler(host, getEventHandler());
  10. }
  11. //启动实例的监听
  12. monitor.startMonitor();
  13. } catch(Throwable t) {
  14. logger.info("Failed to start monitor: " + monitor.getName() + ", ex message: ", t);
  15. monitor.stopMonitor();
  16. logger.info("Removing monitor from stats event console");
  17. TurbineDataMonitor<DataFromSingleInstance> oldMonitor = hostConsole.removeMonitor(monitor.getName());
  18. if (oldMonitor != null) {
  19. hostCount.decrementAndGet();
  20. }
  21. }
  22. }

monitorInstanceMonitor,既实例监控对象,如下图:

实例监听,主要工作就是从实例获取指标信息,下面会分析到

3、数据聚合

数据聚合的架构图如下

TurbineDataMonitor:数据监听,从实例处获取指标

TurbineDataDispatcher:派发器,将数据聚合后输出到客户端或者下游的数据监听

TurbineDataHandler:数据处理,其实就是客户端或者下游的数据监听

该架构有它的好处,可以实现数据的生产和消费的解耦,隔离客户端之间的处理。TurbineDataMonitor收到指标数据后,发送给TurbineDataDispatcher进行处理,它将指标信息聚合后写到一个队列中,TurbineDataHandler负责从队列取消息,如果TurbineDataHandler消费来不及,队列中的指标信息会增长,如果增长指定的大小的时候,只有消息被消费了,才会继续填充新的消息进去,否则消息将被丢弃。

4、TurbineDataMonitor

通过源码我们可以整理出TurbineDataMonitor类的上下结构图,如下:



TurbineDataMonitor是一个抽象类,里面的主要方法都是抽象的,其功能实现还是依赖它的子类。

在文中第二部分的最后,我们说到,InstanceObserver会启动实例的监听,我们继续看实例的监听到底做了什么。InstanceMonitor实例监听类中的startMonitor()方法如下

  1. public void startMonitor() throws Exception {
  2. // This is the only state that we allow startMonitor to proceed in
  3. if (monitorState.get() != State.NotStarted) {
  4. return;
  5. }
  6. taskFuture = ThreadPool.submit(new Callable<Void>() {
  7. @Override
  8. public Void call() throws Exception {
  9. try {
  10. //初始化
  11. init();
  12. monitorState.set(State.Running);
  13. while(monitorState.get() == State.Running) {
  14. //真正干活的地方
  15. doWork();
  16. }
  17. } catch(Throwable t) {
  18. logger.warn("Stopping InstanceMonitor for: " + getStatsInstance().getHostname() + " " + getStatsInstance().getCluster(), t);
  19. } finally {
  20. if (monitorState.get() == State.Running) {
  21. monitorState.set(State.StopRequested);
  22. }
  23. cleanup();
  24. monitorState.set(State.CleanedUp);
  25. }
  26. return null;
  27. }
  28. });
  29. }

先看看init()方法,初始化做了什么



通过调试我们发现,init()方法会根据实例的指标地址http://sheng:8088/manage/hystrix.stream去获取指标信息,这个正是指标真正的来源。

继续看doWork()方法

  1. private void doWork() throws Exception {
  2. DataFromSingleInstance instanceData = null;
  3. //获取实例的指标信息
  4. instanceData = getNextStatsData();
  5. if(instanceData == null) {
  6. return;
  7. } else {
  8. lastEventUpdateTime.set(System.currentTimeMillis());
  9. }
  10. List<DataFromSingleInstance> list = new ArrayList<DataFromSingleInstance>();
  11. list.add(instanceData);
  12. /* send to all handlers */
  13. //向派发器中发送数据
  14. boolean continueRunning = dispatcher.pushData(getStatsInstance(), list);
  15. if(!continueRunning) {
  16. logger.info("No more listeners to the host monitor, stopping monitor for: " + host.getHostname() + " " + host.getCluster());
  17. monitorState.set(State.StopRequested);
  18. return;
  19. }
  20. }

派发器的说明见第5部分

5、TurbineDataDispatcher

通过查看派发器TurbineDataDispatcher中的源码可以找到pushData()方法如下:

  1. public boolean pushData(final Instance host, final Collection<K> statsData) {
  2. if(stopped) {
  3. return false;
  4. }
  5. // get a copy of the list so we don't have ConcurrentModification errors when it changes while we're iterating
  6. Map<String, HandlerQueueTuple<K>> eventHandlers = eventHandlersForHosts.get(host);
  7. if (eventHandlers == null) {
  8. return false; // stop the monitor, this should generally not happen, since we generally manage a set of static listeners for all hosts in discovery
  9. }
  10. for (final HandlerQueueTuple<K> tuple : eventHandlers.values()) {
  11. //HandlerQueueTuple管道中添加数据
  12. tuple.pushData(statsData);
  13. }
  14. // keep track of listeners registered, and if there are none, then notify the publisher of the events
  15. AtomicInteger count = getIterationWithoutHandlerCount(host);
  16. if (eventHandlers.size() == 0) {
  17. count.incrementAndGet();
  18. if (count.get() > 5) {
  19. logger.info("We no longer have handlers to dispatch to");
  20. return false;
  21. }
  22. } else {
  23. count.set(0);
  24. }
  25. return true;
  26. }

HandlerQueueTuple管道中的方法pushData()方法如下

  1. public void pushData(K data) {
  2. if (stopped) {
  3. return;
  4. }
  5. //往队列中写数据
  6. boolean success = queue.writeEvent(data);
  7. if (isCritical()) {
  8. // track stats
  9. if (success) {
  10. counter.increment(Type.EVENT_PROCESSED);
  11. } else {
  12. counter.increment(Type.EVENT_DISCARDED);
  13. }
  14. }
  15. }

HandlerQueueTuple管道中除了writeEvent()写事件外,还有一个readEvent()读事件的操作。将在第6部分分析

6、TurbineDataHandler

我们在HandlerQueueTuple中找到doWork()方法如下

  1. public void doWork() throws Exception {
  2. List<K> statsData = new ArrayList<K>();
  3. int numMisses = 0;
  4. boolean stopPolling = false;
  5. do {
  6. //从队列中读取事件
  7. K data = queue.readEvent();
  8. if (data == null) {
  9. numMisses++;
  10. if (numMisses > 100) {
  11. Thread.sleep(100);
  12. numMisses = 0; // reset count so we can try polling again.
  13. }
  14. } else {
  15. statsData.add(data);
  16. numMisses = 0;
  17. stopPolling = true;
  18. }
  19. }
  20. while(!stopPolling);
  21. try {
  22. //通过事件处理器将数据输出到客户端
  23. eventHandler.handleData(statsData);
  24. } catch (Exception e) {
  25. if(eventHandler.getCriteria().isCritical()) {
  26. logger.warn("Could not publish event to event handler for " + eventHandler.getName(), e);
  27. }
  28. }
  29. }

通过事件处理器将数据输出到客户端,这个操作主要是由TurbineDataHandler完成的,我们通过源码的整理,可以得到如下的代码类的图。

AggregateClusterMonitor的内部类AggStatsEventHandler实现数据的汇聚,TurbineStreamingConnection类是浏览器客户端连接时,数据的输出。

AggStatsEventHandlerhandleData()方法如下:

  1. public void handleData(Collection<DataFromSingleInstance> statsData) {
  2. //整理出关键代码
  3. for (DataFromSingleInstance data : statsData) {
  4. TurbineData.Key dataKey = data.getKey();
  5. // 汇聚数据
  6. AggDataFromCluster clusterData = monitor.aggregateData.get(dataKey);
  7. if (clusterData == null) {
  8. monitor.aggregateData.putIfAbsent(dataKey, new AggDataFromCluster(monitor, data.getType(), data.getName()));
  9. }
  10. clusterData.addStatsDataFromSingleServer(data);
  11. AggDataFromCluster dataToSend = monitor.aggregateData.get(dataKey);
  12. if (dataToSend != null && (!throttleCheck.throttle(data))) {
  13. dataToSend.performPostProcessing();
  14. //将数据添加到集群派发器的队列中
  15. monitor.clusterDispatcher.pushData(monitor.getStatsInstance(), dataToSend);
  16. }
  17. }
  18. }

TurbineStreamingConnectionhandleData()方法很简单,就是将数据直接响应给浏览器。如下:

  1. public void handleData(Collection<T> data) {
  2. if (stopMonitoring) {
  3. // we have been stopped so don't try handling data
  4. return;
  5. }
  6. //写到stream中
  7. writeToStream(data);
  8. }

writeToStream()方法最终将指标数据响应给浏览器

turbine源码分析的更多相关文章

  1. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  2. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  3. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  4. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  5. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  6. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  7. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  8. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  9. ABP源码分析三:ABP Module

    Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...

随机推荐

  1. 初步实现GoQtTemplate

    #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> //新添加 #include <opencv2/co ...

  2. 20145127《java程序设计》第四周学习总结

    教材学习内容总结 第六章 继承与多态 6.1 何为继承 0.面向对象中,子类继承父类,避免城府的行为定义.正确判断使用继承的时机,以及继承之后如何活用多态,才是学习继承时的重点. 1.继承:避免多个类 ...

  3. CEF之CefSettings设置日志等级

    CefSettings结构体允许定义全局的CEF配置,经常用到的配置项如下: single_process 设置为true时,Browser和Renderer使用一个进程.此项也可以通过命令行参数“s ...

  4. C#中的异步编程模式

    异步编程模型(APM) 基于事件的异步编程模式 基于任务的异步模式 Async Await编程 关于C#,可以看看Learning Hard的博客

  5. 【基础知识】ActiveMQ基本原理

    “来,根据你的了解说下 ActiveMQ 是什么.” “这个简单,ActiveMQ 是一个 MOM,具体来说是一个实现了 JMS 规范的系统间远程通信的消息代理.它……” “等等,先解释下什么是 MO ...

  6. BZOJ5170: Fable 树状数组

    Description 有这么一则传闻,O(nlogn)的排序发明之前,滋滋国的排序都是采用的冒泡排序.即使是冒泡排序,对当时的国民 来说也太复杂太难以理解,于是滋滋国出现了这样一个职业——排序使,收 ...

  7. java中子类实例化过程中的内存分配

    知识点: 子类继承父类之后,实例化子类时,内存中子类是如何分配内存的呢? 下面,自己会结合一个例子,解释一下,一个子类实例化过程中,内存是如何分配的 参考博客:http://www.cnblogs.c ...

  8. 【Coursera】Security Introduction -Ninth Week(2)

    对于公钥系统,我们现在已经有了保证它 Confidentially 的一种方法:SSL.SSL利用了公钥的概念. 那么 who we are talking to? Integrity Certifi ...

  9. 【Coursera】Security Introduction -Eighth Week(2)

    Review -Terminology(术语): Confidentiallity & Integrity 泄密 & 欺骗 Confidentiallity: Prevent unau ...

  10. 前端验证用户登陆状态(vue.js)

    首先用户需要进行登陆(请求登陆接口),接口请求成功之后后台会返回对应的用户信息(可以把用户信息存放在浏览器缓存中),并且后台会设置浏览器的cookie值(可以在network->header-& ...