本节主要解决以下几个问题:
(1)contact point是如何工作的?
(2)如何配置contact point?
例如假设在DCAwareRoundRobinPolicy开启“可使用远程DC”,是否需要配置远程DC的结点作为contact point.
1 contact point 如何工作?
contact的作用是获取cassandra的cluster的结点、表等所有信息,相当于“通信兵”的角色。通过代码阅读可知,cluster在初始化中使用contact point做了以下几件事情:
a acquire contact points. Then try one-by-one until success.(before success one, the failed one will retry forever, after success, it won’t retry others)
b use contact point to Registering for events:
List<ProtocolEvent.Type> evs = Arrays.asList( ProtocolEvent.Type. TOPOLOGY_CHANGE, ProtocolEvent.Type. STATUS_CHANGE, ProtocolEvent.Type. SCHEMA_CHANGE );
c use contact point to Refreshing schema and Refreshing node list and token map
d use contact point to get all hosts.
因此其作用相当于开启cassandra大门的”钥匙“,考虑以下几种情况:
(1)仅配置1个结点,则启动时,假设这个结点不能正常工作,则启动不了:
com.datastax.driver.core.Cluster.Manager.init() catch (NoHostAvailableException e) { //no control point connected close(); throw e; } Exception in thread "main" com.datastax.driver.core.exceptions.NoHostAvailableException: All host(s) tried for query failed (tried: /10.224.57.163:9042 (com.datastax.driver.core.TransportException: [/10.224.57.163:9042] Cannot connect)) at com.datastax.driver.core.ControlConnection.reconnectInternal(ControlConnection.java:223) at com.datastax.driver.core.ControlConnection.connect(ControlConnection.java:78) at com.datastax.driver.core.Cluster$Manager.init(Cluster.java:1230) at com.datastax.driver.core.Cluster.init(Cluster.java:157) at com.datastax.driver.core.Cluster.connect(Cluster.java:245) at com.datastax.driver.core.Cluster.connect(Cluster.java:278)
(2)假设配置2+以上结点,当第一个可以工作时,会继续连接后面配置的么?
答案是不会,代码如下:
com.datastax.driver.core.ControlConnection.reconnectInternal(Iterator<Host>, boolean) while (iter.hasNext()) { host = iter.next(); try { return tryConnect(host, isInitialConnection); //try one be one } catch (ConnectionException e) { errors = logError(host, e, errors, iter); if (isInitialConnection) { // Mark the host down right away so that we don't try it again during the initialization process. // We don't call cluster.triggerOnDown because it does a bunch of other things we don't want to do here (notify LBP, etc.) host.setDown(); cluster.startPeriodicReconnectionAttempt(host, true); //重试 } }
(3)假设配置了2+结点,其中第二个可以工作,第一个不能正常工作,第一个会重试么?
答案是会的。参考(2)代码,当连不上时,会重试,重试策略是根据配置的,默认如下:
private static final ReconnectionPolicy DEFAULT_RECONNECTION_POLICY = new ExponentialReconnectionPolicy(1000, 10 * 60 * 1000); //从1S开始重试,然后2,4,8等等,最终重试到10分钟,然后保持10分钟重试一次的节奏。
重试后,假设第一个恢复了,会启用它么?不会:
protected void onReconnection(Connection connection) { // We don't use that first connection so close it. // TODO: this is a bit wasteful, we should consider passing it to onAdd/onUp so // we use it for the first HostConnectionPool created connection.closeAsync(); // Make sure we have up-to-date infos on that host before adding it (so we typically // catch that an upgraded node uses a new cassandra version). if (controlConnection.refreshNodeInfo(host)) { logger.debug("Successful reconnection to {}, setting host UP", host); try { if (isHostAddition) onAdd(host); else onUp(host); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { logger.error("Unexpected error while setting node up", e); } } else { logger.debug("Not enough info for {}, ignoring host", host); } }
所以通过以上三个问题的分析,可知通信结点,不管配置多少,最终只会有一个通信连接Control connection。那么引出问题4,:
(4)假设通信连接挂了,会如何?
按照常理,如果通信结点挂了,那么会使用配置的其他的通信结点,实际上从代码中可以看出,完全不是如此,而是使用了query plan中的结点。query plan是什么?是和loadbalance绑定的一批结点。例如假设使用DCAwareRoundRobinPolicy,且指定DC1是本地DC,那么query plan中的结点是DC1的所有节点,而如果这个DCAwareRoundRobinPolicy开启了远程DC允许,且允许数目是1,则query plan还包括其他所有DC中的:每个DC中连接1个。之所以称为query plan是其含有多个结点以便于重试。
所以当通信结点挂了时,通信结点的任务会转交给load balance策略中的结点,即负责处理请求的”协调者“。
com.datastax.driver.core.Connection.Dispatcher.channelClosed(ChannelHandlerContext, ChannelStateEvent) //trigger it when connection down. com.datastax.driver.core.ControlConnection.backgroundReconnect(long) protected Connection tryReconnect() throws ConnectionException { try { return reconnectInternal(queryPlan(), false); //will use nodes in query plan } catch (NoHostAvailableException e) { throw new ConnectionException(null, e.getMessage()); } catch (UnsupportedProtocolVersionException e) { // reconnectInternal only propagate those if we've not decided on the protocol version yet, // which should only happen on the initial connection and thus in connect() but never here. throw new AssertionError(); } }
(5)通信连接会复用处理请求的连接么?
不会,例如下图:10.224.57.168存在2个连接,其中1个是通信连接,一个是数据连接。
(6)配置的一批通信结点中,最前面的会先尝试么?
不一定,这个问题比较让人无语,但不管是从代码还是实际测试都是如此。
因为在具体连接时,使用的通信结点的信息是转化过后的concurrent hash map的values(),而不是一个有序的list.。反过来看,cassandra driver真的没有对contact point进行优选的过程。
结论:
通过以上概要分析,可以得出以下几点关键信息:
(1)不管contact point配置多少个,最终只有一个control connection;
(2)DCAwareRoundRobinPolicy中开启远程DC读(allowRemoteDCsForLocalConsistencyLevel),不需要让contact point增加一个远程DC的结点配置:因为假设通信结点所在的DC全部down,则query plan会适应这种变化(将以后的所有的请求转往其他DC),而通信结点正好使用的也是query plan来重建连接,所以自然OK.
(需要说明的是:如果需要在本DC结点全部都挂掉的情况下,仍然可以启动起来,那么真的需要配置一个远程DC的结点作为通信结点,否则压根启动不了,但是这种情况下确定需要启动起来么?如果启动起来或许掩盖了后续问题?)
可知理论上contact point配置的越多越好,但是从另外一个角度说太多(真的太多)也无意义,因为即使配置的少点,只要有一个能工作,让cassandra启动起来,则以后的各种情景都不会存在问题,因为随着各种down/up的切换,最终cassandra可以完全不使用配置的任何contact point.
综上:从代码阅读和一些测试结果来看,contact point配不配远程DC node从现在的driver实现来看已成悖论:配置的话,有可能让其成为通信连接(因为不会因为配置在后面,就最后一个尝试),node change,schema change可能稍慢处理(好在这些数据对适时性要求不是特别高)。不配,在本地DC全部node down时,应用启动不起来。
所以结论是:如果希望本DC结点全部都不work时,还能启动起来,那么需要至少配置一个远程DC Node作为通信结点。否则全部配置本DC结点吧,越多越好。
PS: 一直觉得不优选contact point是一个可以优化的地方,翻阅了github上的代码历史,在以后的版本中,不仅没有优化这个,而且会故意完全打乱contact point,从driver设计者的角度来看是为了避免多个connect()启动时都会用某一个从而产生hotspot.
参考:https://github.com/datastax/java-driver/commit/e6b28bcca1882b198064594d82696fc247ffac1f