查看Curator框架 为实现对 连接状态ConnectionState的管理与监听是怎么构造的。后面我们也可以应用到业务的各种监听中。

Curator2.13实现

接口 Listener

Listener接口,给用户实现stateChange()传入新的状态,用户实现对这新的状态要做什么逻辑处理。

  1. public interface ConnectionStateListener
  2. {
  3. /**
  4. * Called when there is a state change in the connection
  5. * @param client the client
  6. * @param newState the new state
  7. */
  8. public void stateChanged(CuratorFramework client, ConnectionState newState);
  9. }

接口 Listenable

提供一个监听对象容器的接口

  1. // Abstracts a listenable object
  2. public interface Listenable<T>
  3. {
  4. /**
  5. * Add the given listener. The listener will be executed in the containing instance's thread.
  6. *
  7. * @param listener listener to add
  8. */
  9. public void addListener(T listener);
  10. public void addListener(T listener, Executor executor);
  11. public void removeListener(T listener);
  12. }

ListenerContainer<T> implements Listenable<T>

  1. /**
  2. * Abstracts an object that has listeners 装Listener的容器
  3. * <T> Listener类型
  4. */
  5. public class ListenerContainer<T> implements Listenable<T>
  6. {
  7. private final Map<T, ListenerEntry<T>> listeners = Maps.newConcurrentMap();
  8. @Override
  9. public void addListener(T listener)
  10. {
  11. addListener(listener, MoreExecutors.sameThreadExecutor());
  12. }
  13. @Override
  14. public void addListener(T listener, Executor executor)
  15. {
  16. listeners.put(listener, new ListenerEntry<T>(listener, executor));
  17. }
  18. /**
  19. * 对 Listener 列表的遍历进行封装
  20. * Utility - apply the given function to each listener.
  21. * @param function function to call for each listener
  22. */
  23. public void forEach(final Function<T, Void> function)
  24. {
  25. for ( final ListenerEntry<T> entry : listeners.values() )
  26. {
  27. entry.executor.execute
  28. (
  29. new Runnable()
  30. {
  31. @Override
  32. public void run()
  33. {
  34. try
  35. {
  36. function.apply(entry.listener);
  37. }
  38. catch ( Throwable e )
  39. {
  40. ThreadUtils.checkInterrupted(e);
  41. log.error(String.format("Listener (%s) threw an exception", entry.listener), e);
  42. }
  43. }
  44. }
  45. );
  46. }
  47. }
  48. public void clear()
  49. {
  50. listeners.clear();
  51. }
  52. public int size()
  53. {
  54. return listeners.size();
  55. }
  56. }

ConnectionStateManager

  1. // to manage connection state
  2. public class ConnectionStateManager {
  3. // 又是队列? 玩消息什么的都是用队列。现在是存放 ConnectionState
  4. BlockingQueue<ConnectionState> eventQueue = new ArrayBlockingQueue<ConnectionState>(QUEUE_SIZE);
  5. // 持有 ListenerContainer
  6. private final ListenerContainer<ConnectionStateListener> listeners = new ListenerContainer<ConnectionStateListener>();
  7. /**
  8. * Start the manager,起一个线程去执行 processEvents(),要是这线程挂了怎么办?异常怎么处理的?框架怎么处理的。。
  9. */
  10. public void start()
  11. {
  12. service.submit
  13. (
  14. new Callable<Object>()
  15. {
  16. @Override
  17. public Object call() throws Exception
  18. {
  19. processEvents();
  20. return null;
  21. }
  22. }
  23. );
  24. }
  25. @Override
  26. public void close()
  27. {
  28. if ( state.compareAndSet(State.STARTED, State.CLOSED) )
  29. {
  30. service.shutdownNow();
  31. listeners.clear();
  32. }
  33. }
  34. // 对不断产生的 ConnectionState 进行处理,生产者?
  35. private void processEvents(){
  36. // 当 ConnectionStateManager 启动完成
  37. while ( state.get() == State.STARTED )
  38. {
  39. // 不断从队列拿 Conection 状态
  40. final ConnectionState newState = eventQueue.take();
  41. // 对每个 状态监听接口 应用 Function, 状态监听接口作为 主语
  42. // forEach 是 listeners封装的 遍历所有 listener 的方法而已。。。
  43. listeners.forEach(
  44. new Function<ConnectionStateListener, Void>() {
  45. // ConnectionStateListener是我们自己要实现的接口,stateChanged是要实现的方法
  46. @Override
  47. public Void apply(ConnectionStateListener listener)
  48. {
  49. listener.stateChanged(client, newState);
  50. return null;
  51. }
  52. }
  53. );
  54. /**
  55. 上面这段
  56. 如果没有封装 Listener 到 ListenerContainer 的话, 所有 Listener 就是个 List列表,就直接调 Listener 的 stateChanged 方法了吧。
  57. for Listener {
  58. listener.stateChanged(client, newState);
  59. }
  60. 因为 封装 Listener 到 ListenerContainer了, 上面的 forEach 方法内部就可以有些内部实现,比如 对每个 Listener 都是用对应的 executor 来执行。
  61. **/
  62. }
  63. }
  64. // 上面的方法是处理 ConnectionState 的,那 ConnectionState 是怎么传进来的呢? 生产者?
  65. /**
  66. * Post a state change. If the manager is already in that state the change
  67. * is ignored. Otherwise the change is queued for listeners.
  68. *
  69. * @param newConnectionState new state
  70. * @return true if the state actually changed, false if it was already at that state
  71. */
  72. public synchronized boolean addStateChange(ConnectionState newConnectionState)
  73. {
  74. // 先判断 ConnectionStateManager 是否已经启动好, state 是内部 Enum
  75. if ( state.get() != State.STARTED )
  76. {
  77. return false;
  78. }
  79. ConnectionState previousState = currentConnectionState;
  80. if ( previousState == newConnectionState )
  81. {
  82. return false;
  83. }
  84. ConnectionState localState = newConnectionState;
  85. // !!!
  86. notifyAll();
  87. while ( !eventQueue.offer(state) )
  88. {
  89. eventQueue.poll();
  90. log.warn("ConnectionStateManager queue full - dropping events to make room");
  91. }
  92. return true;
  93. }
  94. }

调用

启动

  1. // 启动 connectionStateManager,不断检测 connectionState 变化
  2. connectionStateManager.start(); // must be called before client.start()
  3. // 来个匿名默认的 ConnectionStateListener
  4. final ConnectionStateListener listener = new ConnectionStateListener()
  5. {
  6. @Override
  7. public void stateChanged(CuratorFramework client, ConnectionState newState)
  8. {
  9. if ( ConnectionState.CONNECTED == newState || ConnectionState.RECONNECTED == newState )
  10. {
  11. logAsErrorConnectionErrors.set(true);
  12. }
  13. }
  14. };
  15. this.getConnectionStateListenable().addListener(listener);

生产 ConnectionState,把zk那里拿到的state转一下,然后addStateChange

  1. void validateConnection(Watcher.Event.KeeperState state)
  2. {
  3. if ( state == Watcher.Event.KeeperState.Disconnected )
  4. {
  5. suspendConnection();
  6. }
  7. else if ( state == Watcher.Event.KeeperState.Expired )
  8. {
  9. connectionStateManager.addStateChange(ConnectionState.LOST);
  10. }
  11. else if ( state == Watcher.Event.KeeperState.SyncConnected )
  12. {
  13. connectionStateManager.addStateChange(ConnectionState.RECONNECTED);
  14. }
  15. else if ( state == Watcher.Event.KeeperState.ConnectedReadOnly )
  16. {
  17. connectionStateManager.addStateChange(ConnectionState.READ_ONLY);
  18. }
  19. }

复用?

还有其他各种Listener,都可以放到 ListenerContainer

  1. private final ListenerContainer<CuratorListener> listeners;
  2. private final ListenerContainer<UnhandledErrorListener> unhandledErrorListeners;
  3. /**
  4. * Receives notifications about errors and background events
  5. */
  6. public interface CuratorListener {
  7. /**
  8. * Called when a background task has completed or a watch has triggered
  9. * @param event the event
  10. * @throws Exception any errors
  11. */
  12. public void eventReceived(CuratorFramework client, CuratorEvent event) throws Exception;
  13. }
  14. public interface UnhandledErrorListener
  15. {
  16. /**
  17. * Called when an exception is caught in a background thread, handler, etc. Before this
  18. * listener is called, the error will have been logged and a {@link ConnectionState#LOST}
  19. * event will have been queued for any {@link ConnectionStateListener}s.
  20. * @param message Source message
  21. * @param e exception
  22. */
  23. public void unhandledError(String message, Throwable e);
  24. }

总结一下源码技巧

  1. ConnectionState的监听和管理在类ConnectionStateManager中, 就是个 生产者消费者模式的代码,特点就是: public addStateChange() 暴露给外部用户生产 ConnectionState,通过队列eventQueue传递,private processEvents()在内部对ConnectionState进行消费。
  2. 直接new匿名类,对接口进行默认实现。
  3. Listener列表对象进行Container封装,然后 封装foreach方法,传入Function接口 就是foreach每个元素要执行的业务逻辑,方法体就可以加一些其他福利。

Curator4.2.0实现

curator4.2.0对ConnectionStateListener进行了一些改进,如下:

1. 装饰ConnectionStateListener,Ccurator-505

当网络断开时,zk会传送很多 connection/disconnection event,为防止 Curator因此而不断reseting state,该issue提供了 一种对监听回路进行切断或者连接的ConnectionStateListener, 它会对ConnectionStateListeners 进行装饰代理。当它接受到 ConnectionState.SUSPENDED,监听回路会切断变成open状态,这段时间会组织发送state给所有Listener,就忽略了对connection state的改变。时间到期后监听回路会变成close状态,并且发送当前的connection state。简而言之,当不断出现up/down/up/down/up/down的中间状态,使用者将只看到first down,然后N ms之后连接能够修复的话将只看到重连状态。

Create a circuit breaker style ConnectionStateListener. It would proxy any ConnectionStateListeners used by Curator recipe/classes such that when the connection is lost the circuit would open for a period of time and, while open, ignore any changes in state. After the time period expires the circuit would close and send whatever the current connection state is. This way, if the connection is going up/down/up/down/up/down, the application would only see the first down and then N ms later hopefully the connection is repaired and the application would only see the reconnection.

curator-505,结合源码中的testcase更易于理解。

接口ConnectionStateListenerDecorator

该接口用来统一返回是否装饰的ConnectionStateListener

  1. public interface ConnectionStateListenerDecorator
  2. {
  3. ConnectionStateListener decorateListener(CuratorFramework client, ConnectionStateListener actual);
  4. // 不对 ConnectionStateListener 进行装饰
  5. ConnectionStateListenerDecorator standard = (__, actual) -> actual;
  6. /**
  7. * Decorates the listener with circuit breaking behavior
  8. * @param retryPolicy the circuit breaking policy to use
  9. * @return new decorator
  10. */
  11. static ConnectionStateListenerDecorator circuitBreaking(RetryPolicy retryPolicy) {
  12. return (client, actual) -> new CircuitBreakingConnectionStateListener(client, actual, retryPolicy);
  13. }
  14. /**
  15. * Decorates the listener with circuit breaking behavior
  16. * @param retryPolicy the circuit breaking policy to use
  17. * @param service the scheduler to use
  18. * @return new decorator
  19. */
  20. static ConnectionStateListenerDecorator circuitBreaking(RetryPolicy retryPolicy, ScheduledExecutorService service)
  21. {
  22. return (client, actual) -> new CircuitBreakingConnectionStateListener(client, actual, retryPolicy, service);
  23. }
  24. }

CircuitBreakingConnectionStateListener

ConnectionStateListener进行装饰:正常情况下,decoratorclose为连接状态,会将state传给所有Listener。当decorator接收到第一个disconnected state,监听回路变为open即断开状态,不再将state传给各Listener(第一个disconnected state状态会传),而是decorator起线程来通过RetryPolicy进行重试,当重试次数达到一定程度会将close断路 。

  1. public class CircuitBreakingConnectionStateListener implements ConnectionStateListener
  2. {
  3. // 对该 Listener 进行装饰
  4. private final ConnectionStateListener listener;
  5. private final CircuitBreaker circuitBreaker;
  6. // guarded by sync
  7. private boolean circuitLostHasBeenSent;
  8. private ConnectionState circuitLastState;
  9. private ConnectionState circuitInitialState;
  10. // 同步方法?
  11. @Override
  12. public synchronized void stateChanged(CuratorFramework client, ConnectionState newState)
  13. {
  14. // 断路器被打开了,newState 将不传给 Listener 处理
  15. if ( circuitBreaker.isOpen() ) {
  16. handleOpenStateChange(newState);
  17. } else {
  18. handleClosedStateChange(newState);
  19. }
  20. }
  21. private synchronized void handleOpenStateChange(ConnectionState newState)
  22. {
  23. if ( circuitLostHasBeenSent || (newState != ConnectionState.LOST) )
  24. {
  25. // Circuit is open. Ignoring state change !!状态变化都自己收了。。
  26. circuitLastState = newState;
  27. }
  28. else
  29. {
  30. // Circuit is open. State changed to LOST. Sending to listener (第一次 Lost 信息还是会传给 Listener,也就是用个boolean来判断)
  31. circuitLostHasBeenSent = true;
  32. circuitLastState = circuitInitialState = ConnectionState.LOST;
  33. callListener(ConnectionState.LOST);
  34. }
  35. }
  36. // 正常状态下走这里,将state传给Listener
  37. private synchronized void handleClosedStateChange(ConnectionState newState)
  38. {
  39. // 接收到不是connection状态的就尝试打开断路器
  40. if ( !newState.isConnected() ) {
  41. // checkCloseCircuit 作为 调Runnable的匿名函数
  42. if ( circuitBreaker.tryToOpen(this::checkCloseCircuit) )
  43. {
  44. circuitLastState = circuitInitialState = newState;
  45. circuitLostHasBeenSent = (newState == ConnectionState.LOST);
  46. }
  47. else{
  48. log.debug("Could not open circuit breaker. State: {}", newState);
  49. }
  50. }
  51. callListener(newState);
  52. }
  53. // 通过线程池执行该方法,作为Runnable 匿名函数传入
  54. private synchronized void checkCloseCircuit() {
  55. // 连接上了就关闭断路
  56. if ( (circuitLastState == null) || circuitLastState.isConnected() ) {
  57. closeCircuit();
  58. }
  59. // 没连接上,还在 retry 的时候就 保持断路
  60. else if ( circuitBreaker.tryToRetry(this::checkCloseCircuit) ) {
  61. // Circuit open is continuing due to retry
  62. }
  63. // 经过 retryPolicy 的 不断 retry后还没连上,累了,就关闭断路
  64. else {
  65. // Circuit is closing due to retries exhausted
  66. closeCircuit();
  67. }
  68. }
  69. }

2. 重构 ListenerContainer

ListenerManager

这个ListenerManager就是从ListenerContainer演进而来的,PR上是说为了不再依赖Guava并且支持mappingmapping。和原来一样也封装了对Listener列表遍历的forEach(Consumer<V> function)

  1. /**
  2. * Upgraded version of {@link org.apache.curator.framework.listen.ListenerContainer} that
  3. * doesn't leak Guava's internals and also supports mapping/wrapping of listeners
  4. */
  5. public class MappingListenerManager<K, V> implements ListenerManager<K, V> {
  6. @Override
  7. public void forEach(Consumer<V> function)
  8. {
  9. for ( ListenerEntry<V> entry : listeners.values() )
  10. {
  11. entry.executor.execute(() -> {
  12. try {
  13. function.accept(entry.listener);
  14. }
  15. catch ( Throwable e ) {
  16. ThreadUtils.checkInterrupted(e);
  17. log.error(String.format("Listener (%s) threw an exception", entry.listener), e);
  18. }
  19. });
  20. }
  21. }
  22. }

源码技巧

  • 断路器?就是装饰器模式,增加了一些额外的if-else逻辑。
  • 将原来Guava的Function换成了原生的Consumer,实现传入function进行处理的逻辑。

Curator源码阅读 - ConnectionState的管理与监听的更多相关文章

  1. 简单读!tomcat源码(一)启动与监听

    tomcat 作为知名的web容器,很棒! 本文简单了从其应用命令开始拆解,让我们对他有清晰的了解,揭开神秘的面纱!(冗长的代码流水线,给你一目了然) 话分两头: 1. tomcat是如何启动的? 2 ...

  2. Linux 0.11源码阅读笔记-内存管理

    内存管理 Linux内核使用段页式内存管理方式. 内存池 物理页:物理空闲内存被划分为固定大小(4k)的页 内存池:所有空闲物理页组成内存池,以页为单位进行分配回收.并通过位图记录了每个物理页是否空闲 ...

  3. tomcat源码阅读之session管理器(Manager)

    一.UML图分析: (一) Session: Session保存了一个客户端访问服务器时,服务器专门为这个客户端建立一个session用来保存相关的会话信息,session有一个有效时间,这个时间默认 ...

  4. Linux 源码阅读 进程管理

    Linux 源码阅读 进程管理 版本:2.6.24 1.准备知识 1.1 Linux系统中,进程是最小的调度单位: 1.2 PCB数据结构:task_struct (Location:linux-2. ...

  5. 【面试】足够“忽悠”面试官的『Spring事务管理器』源码阅读梳理(建议珍藏)

    PS:文章内容涉及源码,请耐心阅读. 理论实践,相辅相成 伟大领袖毛主席告诉我们实践出真知.这是无比正确的.但是也会很辛苦. 就像淘金一样,从大量沙子中淘出金子一定是一个无比艰辛的过程.但如果真能淘出 ...

  6. LevelDB(v1.3) 源码阅读之 Arena(内存管理器)

    LevelDB(v1.3) 源码阅读系列使用 LevelDB v1.3 版本的代码,可以通过如下方式下载并切换到 v1.3 版本的代码: $ git clone https://github.com/ ...

  7. 【原】AFNetworking源码阅读(四)

    [原]AFNetworking源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDe ...

  8. 【原】AFNetworking源码阅读(三)

    [原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...

  9. 【原】SDWebImage源码阅读(三)

    [原]SDWebImage源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1.SDWebImageDownloader中的downloadImageWithURL 我们 ...

随机推荐

  1. js表格拖拽

    html部分 <div id="chenkbox"> <div id="tableSort"> <ol> <li> ...

  2. python之浮点型类型

    浮点型:float 如3.14,2.88 class float(object): """ float(x) -> floating point number Co ...

  3. SWF在线绘本批量制作高质量PDF的新方法(重点在批量制作)

    SWF在线绘本批量制作高质量PDF的新方法(重点在批量制作) 2012-12-21  未来决定...       http://www.ebama.net/thread-107643-1-1.html ...

  4. 利用IDEA构建springboot应用

    前提注意: 1.版本,java 1.8    maven  3.3.9 配置项目 项目版本 项目保存路径 在maven里面的conf里面的settings.xml里配置maven中央仓库  (阿里云) ...

  5. 小爬爬5:重点回顾&&移动端数据爬取1

    1. ()什么是selenium - 基于浏览器自动化的一个模块 ()在爬虫中为什么使用selenium及其和爬虫之间的关联 - 可以便捷的获取动态加载的数据 - 实现模拟登陆 ()列举常见的sele ...

  6. 2017校赛 问题 F: 懒人得多动脑

    题目描述 小D的家A和学校B都恰好在以点F为焦点的双曲线上,而小D每日所需的生活水源在一条平行该双曲线准线的直线上,设它的值为v.大家都知道,每天都是要喝水的,但是小D有点懒,他希望自己能在去上学或者 ...

  7. FZU 2234 牧场物语【多线程dp】

     Problem 2234 牧场物语  Problem Description 小茗同学正在玩牧场物语.该游戏的地图可看成一个边长为n的正方形. 小茗同学突然心血来潮要去砍树,然而,斧头在小茗的右下方 ...

  8. COGS 775 山海经

    COGS 775 山海经 思路: 求最大连续子段和(不能不选),只查询,无修改.要求输出该子段的起止位置. 线段树经典模型,每个节点记录权值和sum.左起最大前缀和lmax.右起最大后缀和rmax.最 ...

  9. 【vb.net机房收费系统】之sqlhelper 标签: 数据库 2015-05-17 10:47 819人阅读 评论(15)

    在敲机房收费重构版的时候,用到了sqlhelper,当时不知道怎么开始,各种听别人说,张晗说,一定要用sqlhelper,特别好用,我当时没有用balabala~当时一听,哎哎哎,这个高级,要搞一搞, ...

  10. 洛谷 3174 [HAOI2009]毛毛虫

    题目描述 对于一棵树,我们可以将某条链和与该链相连的边抽出来,看上去就象成一个毛毛虫,点数越多,毛毛虫就越大.例如下图左边的树(图 1 )抽出一部分就变成了右边的一个毛毛虫了(图 2 ). 输入输出格 ...