redisson-2.10.4源代碼分析

    

redis 學習問題總結

http://aperise.iteye.com/blog/2310639

ehcache memcached redis 緩存技術總結

http://aperise.iteye.com/blog/2296219

redis-stat 離線安裝

http://aperise.iteye.com/blog/2310254

redis  cluster 非ruby方式啓動

http://aperise.iteye.com/blog/2310254

redis-sentinel安裝部署

http://aperise.iteye.com/blog/2342693

spring-data-redis使用

 http://aperise.iteye.com/blog/2342615

redis客戶端redisson實戰

http://aperise.iteye.com/blog/2396196

redisson-2.10.4源代碼分析

http://aperise.iteye.com/blog/2400528

tcmalloc jemalloc libc選擇

http://blog.csdn.net/u010994304/article/details/49906819

 

1.RedissonClient一主兩從部署時連接池組成

       對如下圖的主從部署(1主2從): 
       redisson純java操作代碼如下: 

Java代碼  收藏代碼
  1. Config config = new Config();// 創建配置  
  2.   config.useMasterSlaveServers() // 指定使用主從部署方式  
  3.   //.setReadMode(ReadMode.SLAVE) 默認值SLAVE,讀操作只在從節點進行  
  4.   //.setSubscriptionMode(SubscriptionMode.SLAVE) 默認值SLAVE,訂閱操作只在從節點進行  
  5.   //.setMasterConnectionMinimumIdleSize(10) 默認值10,針對每個master節點初始化10個連接  
  6.   //.setMasterConnectionPoolSize(64) 默認值64,針對每個master節點初始化10個連接,最大可以擴展至64個連接  
  7.   //.setSlaveConnectionMinimumIdleSize(10) 默認值10,針對每個slave節點初始化10個連接  
  8.   //.setSlaveConnectionPoolSize(64) 默認值,針對每個slave節點初始化10個連接,最大可以擴展至64個連接  
  9.   //.setSubscriptionConnectionMinimumIdleSize(1) 默認值1,在SubscriptionMode=SLAVE時候,針對每個slave節點初始化1個連接  
  10.   //.setSubscriptionConnectionPoolSize(50) 默認值50,在SubscriptionMode=SLAVE時候,針對每個slave節點初始化1個連接,最大可以擴展至50個連接  
  11.   .setMasterAddress("redis://192.168.29.24:6379")  // 設置redis主節點  
  12.   .addSlaveAddress("redis://192.168.29.24:7000") // 設置redis從節點  
  13.   .addSlaveAddress("redis://192.168.29.24:7001"); // 設置redis從節點  
  14. RedissonClient redisson = Redisson.create(config);// 創建客戶端(發現這一操作非常耗時,基本在2秒-4秒左右)  

       上面代碼執行完畢後,如果在redis服務端所在服務器執行以下linux命令:

Java代碼  收藏代碼
  1. #6379上建立了10個連接  
  2. netstat -ant |grep 6379|grep  ESTABLISHED  
  3. #7000上建立了11個連接  
  4. netstat -ant |grep 7000|grep  ESTABLISHED  
  5. #7001上建立了11個連接  
  6. netstat -ant |grep 7001|grep  ESTABLISHED  

       你會發現redisson連接到redis服務端總計建立了32個連接,其中masterpool佔據10個連接,slavepool佔據20個連接,另外pubSubConnectionPool佔據2個連接,連接池中池化對象分佈如下圖:

  • MasterConnectionPool:默認針對每個不同的IP+port組合,初始化10個對象,最大可擴展至64個,因爲只有一個master,所以上圖創建了10個連接;
  • MasterPubSubConnectionPool:默認針對每個不同的IP+port組合,初始化1個對象,最大可擴展至50個,因爲默認SubscriptionMode=SubscriptionMode.SLAVE,所以master上不會創建連接池所以上圖MasterPubSubConnectionPool裏沒有創建任何連接;
  • SlaveConnectionPool:默認針對每個不同的IP+port組合,初始化10個對象,最大可擴展至64個,因爲有兩個slave,每個slave上圖創建了10個連接,總計創建了20個連接;
  • PubSubConnectionPool:默認針對每個不同的IP+port組合,初始化1個對象,最大可擴展至50個,因爲有兩個slave,每個slave上圖創建了1個連接,總計創建了2個連接。

       從上圖可以看出,連接池是針對每個IP端口都有一個獨立的池,連接池也按照主從進行劃分,那麼這些連接池是在哪裏初始化的?如何初始化的?讀操作和寫操作如何進行的?這就是今天要解答的問題,要解答這些問題最好還是查看redisson的源碼。

 

2.Redisson初始化連接池源碼分析

    2.1 Redisson.java 

       RedissonClient.java是一個接口類,它的實現類是Redisson.java,對於Redisson.java的介紹先以一張Redisson的4大組件關係圖開始,如下圖:

       對Redisson.java的代碼註釋如下:

Java代碼  收藏代碼
  1. /**   
  2. * 根據配置Config創建redisson操作類RedissonClient   
  3. * @param config for Redisson   
  4. * @return Redisson instance   
  5. */    
  6. public static RedissonClient create(Config config) {    
  7.     //調用構造方法    
  8.     Redisson redisson = new Redisson(config);    
  9.     if (config.isRedissonReferenceEnabled()) {    
  10.         redisson.enableRedissonReferenceSupport();    
  11.     }    
  12.     return redisson;    
  13. }    
  14.   
  15. /**   
  16. * Redisson構造方法   
  17. * @param config for Redisson   
  18. * @return Redisson instance   
  19. */    
  20. protected Redisson(Config config) {    
  21.     //賦值變量config    
  22.     this.config = config;    
  23.     //產生一份對於傳入config的備份    
  24.     Config configCopy = new Config(config);    
  25.   
  26.     //根據配置config的類型(主從模式、單機模式、哨兵模式、集羣模式、亞馬遜雲模式、微軟雲模式)而進行不同的初始化    
  27.     connectionManager = ConfigSupport.createConnectionManager(configCopy);    
  28.     //連接池對象回收調度器    
  29.     evictionScheduler = new EvictionScheduler(connectionManager.getCommandExecutor());    
  30.     //Redisson的對象編碼類    
  31.     codecProvider = configCopy.getCodecProvider();    
  32.     //Redisson的ResolverProvider,默認爲org.redisson.liveobject.provider.DefaultResolverProvider    
  33.     resolverProvider = configCopy.getResolverProvider();    
  34. }  

       其中與連接池相關的就是ConnectionManager,ConnectionManager的初始化轉交工具類ConfigSupport.java進行,ConfigSupport.java會根據部署方式(主從模式、單機模式、哨兵模式、集羣模式、亞馬遜雲模式、微軟雲模式)的不同而分別進行。

 

    2.2 ConfigSupport.java

       這裏現將ConfigSupport.java創建ConnectionManager的核心代碼註釋如下: 

Java代碼  收藏代碼
  1. /**   
  2. * 據配置config的類型(主從模式、單機模式、哨兵模式、集羣模式、亞馬遜雲模式、微軟雲模式)而進行不同的初始化   
  3. * @param configCopy for Redisson   
  4. * @return ConnectionManager instance   
  5. */    
  6. public static ConnectionManager createConnectionManager(Config configCopy) {    
  7.     if (configCopy.getMasterSlaveServersConfig() != null) {//配置configCopy類型爲主從模式    
  8.         validate(configCopy.getMasterSlaveServersConfig());    
  9.         return new MasterSlaveConnectionManager(configCopy.getMasterSlaveServersConfig(), configCopy);    
  10.     } else if (configCopy.getSingleServerConfig() != null) {//配置configCopy類型爲單機模式    
  11.         validate(configCopy.getSingleServerConfig());    
  12.         return new SingleConnectionManager(configCopy.getSingleServerConfig(), configCopy);    
  13.     } else if (configCopy.getSentinelServersConfig() != null) {//配置configCopy類型爲哨兵模式    
  14.         validate(configCopy.getSentinelServersConfig());    
  15.         return new SentinelConnectionManager(configCopy.getSentinelServersConfig(), configCopy);    
  16.     } else if (configCopy.getClusterServersConfig() != null) {//配置configCopy類型爲集羣模式    
  17.         validate(configCopy.getClusterServersConfig());    
  18.         return new ClusterConnectionManager(configCopy.getClusterServersConfig(), configCopy);    
  19.     } else if (configCopy.getElasticacheServersConfig() != null) {//配置configCopy類型爲亞馬遜雲模式    
  20.         validate(configCopy.getElasticacheServersConfig());    
  21.         return new ElasticacheConnectionManager(configCopy.getElasticacheServersConfig(), configCopy);    
  22.     } else if (configCopy.getReplicatedServersConfig() != null) {//配置configCopy類型爲微軟雲模式    
  23.         validate(configCopy.getReplicatedServersConfig());    
  24.         return new ReplicatedConnectionManager(configCopy.getReplicatedServersConfig(), configCopy);    
  25.     } else if (configCopy.getConnectionManager() != null) {//直接返回configCopy自帶的默認ConnectionManager    
  26.         return configCopy.getConnectionManager();    
  27.     }else {    
  28.         throw new IllegalArgumentException("server(s) address(es) not defined!");    
  29.     }    
  30. }  

       上面可以看到根據傳入的配置Config.java的不同,會分別創建不同的ConnectionManager的實現類。

 

    2.3 MasterSlaveConnectionManager.java

       這裏開始介紹ConnectionManager,ConnectionManager.java是一個接口類,它有6個實現類,分別對應着不同的部署模式(主從模式、單機模式、哨兵模式、集羣模式、亞馬遜雲模式、微軟雲模式),如下如所示:
       這裏以主從部署方式進行講解,先通過一張圖瞭解MasterSlaveConnectionManager的組成:


       上圖中最終要的組件要數MasterSlaveEntry,在後面即將進行介紹,這裏註釋MasterSlaveConnectionManager.java的核心代碼如下:  

Java代碼  收藏代碼
  1. /**    
  2. * MasterSlaveConnectionManager的構造方法  
  3. * @param cfg for MasterSlaveServersConfig  
  4. * @param config for Config    
  5. */      
  6. public MasterSlaveConnectionManager(MasterSlaveServersConfig cfg, Config config) {  
  7.     //調用構造方法  
  8.     this(config);  
  9.     //  
  10.     initTimer(cfg);  
  11.     this.config = cfg;  
  12.     //初始化MasterSlaveEntry  
  13.     initSingleEntry();  
  14. }  
  15. /**    
  16. * MasterSlaveConnectionManager的構造方法  
  17. * @param cfg for Config  
  18. */      
  19. public MasterSlaveConnectionManager(Config cfg) {  
  20.     //讀取redisson的jar中的文件META-INF/MANIFEST.MF,打印出Bundle-Version對應的Redisson版本信息  
  21.     Version.logVersion();  
  22.     //EPOLL是linux的多路複用IO模型的增強版本,這裏如果啓用EPOLL,就讓redisson底層netty使用EPOLL的方式,否則配置netty裏的NIO非阻塞方式  
  23.     if (cfg.isUseLinuxNativeEpoll()) {  
  24.         if (cfg.getEventLoopGroup() == null) {  
  25.             //使用linux IO非阻塞模型EPOLL  
  26.             this.group = new EpollEventLoopGroup(cfg.getNettyThreads(), new DefaultThreadFactory("redisson-netty"));  
  27.         } else {  
  28.             this.group = cfg.getEventLoopGroup();  
  29.         }  
  30.         this.socketChannelClass = EpollSocketChannel.class;  
  31.     } else {  
  32.         if (cfg.getEventLoopGroup() == null) {  
  33.             //使用linux IO非阻塞模型NIO  
  34.             this.group = new NioEventLoopGroup(cfg.getNettyThreads(), new DefaultThreadFactory("redisson-netty"));  
  35.         } else {  
  36.             this.group = cfg.getEventLoopGroup();  
  37.         }  
  38.         this.socketChannelClass = NioSocketChannel.class;  
  39.     }  
  40.     if (cfg.getExecutor() == null) {  
  41.         //線程池大小,對於2U 2CPU 8cores/cpu,意思是有2塊板子,每個板子上8個物理CPU,那麼總計物理CPU個數爲16  
  42.         //對於linux有個超線程概念,意思是每個物理CPU可以虛擬出2個邏輯CPU,那麼總計邏輯CPU個數爲32  
  43.         //這裏Runtime.getRuntime().availableProcessors()取的是邏輯CPU的個數,所以這裏線程池大小會是64  
  44.         int threads = Runtime.getRuntime().availableProcessors() * 2;  
  45.         if (cfg.getThreads() != 0) {  
  46.             threads = cfg.getThreads();  
  47.         }  
  48.         executor = Executors.newFixedThreadPool(threads, new DefaultThreadFactory("redisson"));  
  49.     } else {  
  50.         executor = cfg.getExecutor();  
  51.     }  
  52.   
  53.     this.cfg = cfg;  
  54.     this.codec = cfg.getCodec();  
  55.     //一個可以獲取異步執行任務返回值的回調對象,本質是對於java的Future的實現,監控MasterSlaveConnectionManager的shutdown進行一些必要的處理  
  56.     this.shutdownPromise = newPromise();  
  57.     //一個持有MasterSlaveConnectionManager的異步執行服務  
  58.     this.commandExecutor = new CommandSyncService(this);  
  59. }  
  60. /**    
  61. * 初始化定時調度器 
  62. * @param config for MasterSlaveServersConfig  
  63. */     
  64. protected void initTimer(MasterSlaveServersConfig config) {  
  65.     //讀取超時時間配置信息  
  66.     int[] timeouts = new int[]{config.getRetryInterval(), config.getTimeout(), config.getReconnectionTimeout()};  
  67.     Arrays.sort(timeouts);  
  68.     int minTimeout = timeouts[0];  
  69.     //設置默認超時時間  
  70.     if (minTimeout % 100 != 0) {  
  71.         minTimeout = (minTimeout % 100) / 2;  
  72.     } else if (minTimeout == 100) {  
  73.         minTimeout = 50;  
  74.     } else {  
  75.         minTimeout = 100;  
  76.     }  
  77.     //創建定時調度器  
  78.     timer = new HashedWheelTimer(Executors.defaultThreadFactory(), minTimeout, TimeUnit.MILLISECONDS, 1024);  
  79.       
  80.     // to avoid assertion error during timer.stop invocation  
  81.     try {  
  82.         Field leakField = HashedWheelTimer.class.getDeclaredField("leak");  
  83.         leakField.setAccessible(true);  
  84.         leakField.set(timer, null);  
  85.     } catch (Exception e) {  
  86.         throw new IllegalStateException(e);  
  87.     }  
  88.     //檢測MasterSlaveConnectionManager的空閒連接的監視器IdleConnectionWatcher,會清理不用的空閒的池中連接對象  
  89.     connectionWatcher = new IdleConnectionWatcher(this, config);  
  90. }  
  91.   
  92. /**    
  93. * 創建MasterSlaveConnectionManager的MasterSlaveEntry   
  94. */      
  95. protected void initSingleEntry() {  
  96.     try {  
  97.         //主從模式下0~16383加入到集合slots    
  98.         HashSet<ClusterSlotRange> slots = new HashSet<ClusterSlotRange>();  
  99.         slots.add(singleSlotRange);  
  100.   
  101.         MasterSlaveEntry entry;  
  102.         if (config.checkSkipSlavesInit()) {//ReadMode不爲MASTER並且SubscriptionMode不爲MASTER才執行  
  103.             entry = new SingleEntry(slots, this, config);  
  104.             RFuture<Void> f = entry.setupMasterEntry(config.getMasterAddress());  
  105.             f.syncUninterruptibly();  
  106.         } else {//默認主從部署ReadMode=SLAVE,SubscriptionMode=SLAVE,這裏會執行  
  107.             entry = createMasterSlaveEntry(config, slots);  
  108.         }  
  109.         //將每個分片0~16383都指向創建的MasterSlaveEntry  
  110.         for (int slot = singleSlotRange.getStartSlot(); slot < singleSlotRange.getEndSlot() + 1; slot++) {  
  111.             addEntry(slot, entry);  
  112.         }  
  113.         //DNS相關  
  114.         if (config.getDnsMonitoringInterval() != -1) {  
  115.             dnsMonitor = new DNSMonitor(this, Collections.singleton(config.getMasterAddress()),   
  116.                     config.getSlaveAddresses(), config.getDnsMonitoringInterval());  
  117.             dnsMonitor.start();  
  118.         }  
  119.     } catch (RuntimeException e) {  
  120.         stopThreads();  
  121.         throw e;  
  122.     }  
  123. }  
  124. /**    
  125. * MasterSlaveEntry的構造方法  
  126. * @param config for MasterSlaveServersConfig   
  127. * @param slots for HashSet<ClusterSlotRange>  
  128. * @return MasterSlaveEntry 
  129. */      
  130. protected MasterSlaveEntry createMasterSlaveEntry(MasterSlaveServersConfig config, HashSet<ClusterSlotRange> slots) {  
  131.     //創建MasterSlaveEntry  
  132.     MasterSlaveEntry entry = new MasterSlaveEntry(slots, this, config);  
  133.     //從節點連接池SlaveConnectionPool和PubSubConnectionPool的默認的最小連接數初始化  
  134.     List<RFuture<Void>> fs = entry.initSlaveBalancer(java.util.Collections.<URI>emptySet());  
  135.     for (RFuture<Void> future : fs) {  
  136.         future.syncUninterruptibly();  
  137.     }  
  138.     主節點連接池MasterConnectionPool和MasterPubSubConnectionPool的默認的最小連接數初始化  
  139.     RFuture<Void> f = entry.setupMasterEntry(config.getMasterAddress());  
  140.     f.syncUninterruptibly();  
  141.     return entry;  
  142. }  

       上面個人覺得有兩處代碼值得我們特別關注,特別說明如下:

  • entry.initSlaveBalancer:從節點連接池SlaveConnectionPoolPubSubConnectionPool的默認的最小連接數初始化。
  • entry.setupMasterEntry:主節點連接池MasterConnectionPoolMasterPubSubConnectionPool的默認的最小連接數初始化。

 

    2.4 MasterSlaveEntry.java

       用一張圖來解釋MasterSlaveEntry的組件如下:

       MasterSlaveEntry.java里正是我們一直在尋找着的四個連接池MasterConnectionPool、MasterPubSubConnectionPool、SlaveConnectionPool和PubSubConnectionPool,這裏註釋MasterSlaveEntry.java的核心代碼如下:

Java代碼  收藏代碼
  1. /**    
  2. * MasterSlaveEntry的構造方法  
  3. * @param slotRanges for Set<ClusterSlotRange>    
  4. * @param connectionManager for ConnectionManager    
  5. * @param config for MasterSlaveServersConfig  
  6. */      
  7. public MasterSlaveEntry(Set<ClusterSlotRange> slotRanges, ConnectionManager connectionManager, MasterSlaveServersConfig config) {    
  8.     //主從模式下0~16383加入到集合slots    
  9.     for (ClusterSlotRange clusterSlotRange : slotRanges) {    
  10.         for (int i = clusterSlotRange.getStartSlot(); i < clusterSlotRange.getEndSlot() + 1; i++) {    
  11.             slots.add(i);    
  12.         }    
  13.     }    
  14.     //賦值MasterSlaveConnectionManager給connectionManager    
  15.     this.connectionManager = connectionManager;    
  16.     //賦值config    
  17.     this.config = config;    
  18.     
  19.     //創建LoadBalancerManager    
  20.     //其實LoadBalancerManager裏持有者從節點的SlaveConnectionPool和PubSubConnectionPool    
  21.     //並且此時連接池裏還沒有初始化默認的最小連接數    
  22.     slaveBalancer = new LoadBalancerManager(config, connectionManager, this);    
  23.     //創建主節點連接池MasterConnectionPool,此時連接池裏還沒有初始化默認的最小連接數    
  24.     writeConnectionHolder = new MasterConnectionPool(config, connectionManager, this);    
  25.     //創建主節點連接池MasterPubSubConnectionPool,此時連接池裏還沒有初始化默認的最小連接數    
  26.     pubSubConnectionHolder = new MasterPubSubConnectionPool(config, connectionManager, this);    
  27. }    
  28.     
  29. /**    
  30. * 從節點連接池SlaveConnectionPool和PubSubConnectionPool的默認的最小連接數初始化  
  31. * @param disconnectedNodes for Collection<URI>  
  32. * @return List<RFuture<Void>>    
  33. */     
  34. public List<RFuture<Void>> initSlaveBalancer(Collection<URI> disconnectedNodes) {    
  35.     //這裏freezeMasterAsSlave=true    
  36.     boolean freezeMasterAsSlave = !config.getSlaveAddresses().isEmpty() && !config.checkSkipSlavesInit() && disconnectedNodes.size() < config.getSlaveAddresses().size();    
  37.     
  38.     List<RFuture<Void>> result = new LinkedList<RFuture<Void>>();    
  39.     //把主節點當作從節點處理,因爲默認ReadMode=ReadMode.SLAVE,所以這裏不會添加針對該節點的連接池    
  40.     RFuture<Void> f = addSlave(config.getMasterAddress(), freezeMasterAsSlave, NodeType.MASTER);    
  41.     result.add(f);    
  42.     //讀取從節點的地址信息,然後針對每個從節點地址創建SlaveConnectionPool和PubSubConnectionPool    
  43.     //SlaveConnectionPool【初始化10個RedisConnection,最大可以擴展至64個】    
  44.     //PubSubConnectionPool【初始化1個RedisPubSubConnection,最大可以擴展至50個】    
  45.     for (URI address : config.getSlaveAddresses()) {    
  46.         f = addSlave(address, disconnectedNodes.contains(address), NodeType.SLAVE);    
  47.         result.add(f);    
  48.     }    
  49.     return result;    
  50. }    
  51.     
  52. /**    
  53. * 從節點連接池SlaveConnectionPool和PubSubConnectionPool的默認的最小連接數初始化  
  54. * @param address for URI  
  55. * @param freezed for boolean  
  56. * @param nodeType for NodeType  
  57. * @return RFuture<Void>  
  58. */     
  59. private RFuture<Void> addSlave(URI address, boolean freezed, NodeType nodeType) {    
  60.     //創建到從節點的連接RedisClient    
  61.     RedisClient client = connectionManager.createClient(NodeType.SLAVE, address);    
  62.     ClientConnectionsEntry entry = new ClientConnectionsEntry(client,    
  63.             this.config.getSlaveConnectionMinimumIdleSize(),    
  64.             this.config.getSlaveConnectionPoolSize(),    
  65.             this.config.getSubscriptionConnectionMinimumIdleSize(),    
  66.             this.config.getSubscriptionConnectionPoolSize(), connectionManager, nodeType);    
  67.     //默認只有主節點當作從節點是會設置freezed=true    
  68.     if (freezed) {    
  69.         synchronized (entry) {    
  70.             entry.setFreezed(freezed);    
  71.             entry.setFreezeReason(FreezeReason.SYSTEM);    
  72.         }    
  73.     }    
  74.     //調用slaveBalancer來對從節點連接池SlaveConnectionPool和PubSubConnectionPool的默認的最小連接數初始化    
  75.     return slaveBalancer.add(entry);    
  76. }    
  77.     
  78. /**    
  79. * 主節點連接池MasterConnectionPool和MasterPubSubConnectionPool的默認的最小連接數初始化  
  80. * @param address for URI  
  81. * @return RFuture<Void>  
  82. */     
  83. public RFuture<Void> setupMasterEntry(URI address) {    
  84.     //創建到主節點的連接RedisClient    
  85.     RedisClient client = connectionManager.createClient(NodeType.MASTER, address);    
  86.     masterEntry = new ClientConnectionsEntry(    
  87.             client,     
  88.             config.getMasterConnectionMinimumIdleSize(),     
  89.             config.getMasterConnectionPoolSize(),    
  90.             config.getSubscriptionConnectionMinimumIdleSize(),    
  91.             config.getSubscriptionConnectionPoolSize(),     
  92.             connectionManager,     
  93.             NodeType.MASTER);    
  94.     //如果配置的SubscriptionMode=SubscriptionMode.MASTER就初始化MasterPubSubConnectionPool    
  95.     //默認SubscriptionMode=SubscriptionMode.SLAVE,MasterPubSubConnectionPool這裏不會初始化最小連接數    
  96.     if (config.getSubscriptionMode() == SubscriptionMode.MASTER) {    
  97.         //MasterPubSubConnectionPool【初始化1個RedisPubSubConnection,最大可以擴展至50個】    
  98.         RFuture<Void> f = writeConnectionHolder.add(masterEntry);    
  99.         RFuture<Void> s = pubSubConnectionHolder.add(masterEntry);    
  100.         return CountListener.create(s, f);    
  101.     }    
  102.     //調用MasterConnectionPool使得連接池MasterConnectionPool裏的對象最小個數爲10個    
  103.     //MasterConnectionPool【初始化10個RedisConnection,最大可以擴展至64個】    
  104.     return writeConnectionHolder.add(masterEntry);    
  105. }  
       上面代碼個人覺得有四個地方值得我們特別關注,它們是一個連接池創建對象的入口,列表如下:
  • writeConnectionHolder.add(masterEntry):其實writeConnectionHolder的類型就是MasterConnectionPool,這裏是連接池MasterConnectionPool裏添加對象 
  • pubSubConnectionHolder.add(masterEntry):其實pubSubConnectionHolder的類型是MasterPubSubConnectionPool,這裏是連接池MasterPubSubConnectionPool添加對象
  • slaveConnectionPool.add(entry):這裏是連接池SlaveConnectionPool裏添加對象
  • pubSubConnectionPool.add(entry):這裏是連接池PubSubConnectionPool裏添加對象

 

 

    2.5 LoadBalancerManager.java

       圖解LoadBalancerManager.java的內部組成如下:

       LoadBalancerManager.java裏面有着從節點相關的兩個重要的連接池SlaveConnectionPoolPubSubConnectionPool,這裏註釋LoadBalancerManager.java的核心代碼如下:

Java代碼  收藏代碼
  1. /**    
  2. * LoadBalancerManager的構造方法  
  3. * @param config for MasterSlaveServersConfig   
  4. * @param connectionManager for ConnectionManager    
  5. * @param entry for MasterSlaveEntry  
  6. */      
  7. public LoadBalancerManager(MasterSlaveServersConfig config, ConnectionManager connectionManager, MasterSlaveEntry entry) {    
  8.     //賦值connectionManager    
  9.     this.connectionManager = connectionManager;    
  10.     //創建連接池SlaveConnectionPool    
  11.     slaveConnectionPool = new SlaveConnectionPool(config, connectionManager, entry);    
  12.     //創建連接池PubSubConnectionPool    
  13.     pubSubConnectionPool = new PubSubConnectionPool(config, connectionManager, entry);    
  14. }    
  15. /**    
  16. * LoadBalancerManager的連接池SlaveConnectionPool和PubSubConnectionPool裏池化對象添加方法,也即池中需要對象時,調用此方法添加  
  17. * @param entry for ClientConnectionsEntry  
  18. * @return RFuture<Void>  
  19. */      
  20. public RFuture<Void> add(final ClientConnectionsEntry entry) {    
  21.     final RPromise<Void> result = connectionManager.newPromise();    
  22.     //創建一個回調監聽器,在池中對象創建失敗時進行2次莫仍嘗試    
  23.     FutureListener<Void> listener = new FutureListener<Void>() {    
  24.         AtomicInteger counter = new AtomicInteger(2);    
  25.         @Override    
  26.         public void operationComplete(Future<Void> future) throws Exception {    
  27.             if (!future.isSuccess()) {    
  28.                 result.tryFailure(future.cause());    
  29.                 return;    
  30.             }    
  31.             if (counter.decrementAndGet() == 0) {    
  32.                 String addr = entry.getClient().getIpAddr();    
  33.                 ip2Entry.put(addr, entry);    
  34.                 result.trySuccess(null);    
  35.             }    
  36.         }    
  37.     };    
  38.     //調用slaveConnectionPool添加RedisConnection對象到池中    
  39.     RFuture<Void> slaveFuture = slaveConnectionPool.add(entry);    
  40.     slaveFuture.addListener(listener);    
  41.     //調用pubSubConnectionPool添加RedisPubSubConnection對象到池中    
  42.     RFuture<Void> pubSubFuture = pubSubConnectionPool.add(entry);    
  43.     pubSubFuture.addListener(listener);    
  44.     return result;    
  45. }  

       至此,我們已經瞭解了開篇提到的四個連接池是在哪裏創建的。

 

3. Redisson的4類連接池

       這裏我們來詳細介紹下Redisson的連接池實現類,Redisson裏有4種連接池,它們是MasterConnectionPool、MasterPubSubConnectionPool、SlaveConnectionPool和PubSubConnectionPool,它們的父類都是ConnectionPool,其類繼承關係圖如下:  

       通過上圖我們瞭解了ConnectionPool類的繼承關係圖,再來一張圖來了解下ConnectionPool.java類的組成,如下:

       好了,再來圖就有點囉嗦了,註釋ConnectionPool.java代碼如下:

Java代碼  收藏代碼
  1. abstract class ConnectionPool<T extends RedisConnection> {    
  2.     private final Logger log = LoggerFactory.getLogger(getClass());    
  3.     //維持着連接池對應的redis節點信息    
  4.     //比如1主2從部署MasterConnectionPool裏的entries只有一個主節點(192.168.29.24 6379)    
  5.     //比如1主2從部署MasterPubSubConnectionPool裏的entries爲空,因爲SubscriptionMode=SubscriptionMode.SLAVE    
  6.     //比如1主2從部署SlaveConnectionPool裏的entries有3個節點(192.168.29.24 6379,192.168.29.24 7000,192.168.29.24 7001,但是注意192.168.29.24 6379凍結屬性freezed=true不會參與讀操作除非2個從節點全部宕機才參與讀操作)    
  7.     //比如1主2從部署PubSubConnectionPool裏的entries有2個節點(192.168.29.24 7000,192.168.29.24 7001),因爲SubscriptionMode=SubscriptionMode.SLAVE,主節點不會加入    
  8.     protected final List<ClientConnectionsEntry> entries = new CopyOnWriteArrayList<ClientConnectionsEntry>();    
  9.     //持有者RedissonClient的組件ConnectionManager    
  10.     final ConnectionManager connectionManager;    
  11.     //持有者RedissonClient的組件ConnectionManager裏的MasterSlaveServersConfig    
  12.     final MasterSlaveServersConfig config;    
  13.     //持有者RedissonClient的組件ConnectionManager裏的MasterSlaveEntry    
  14.     final MasterSlaveEntry masterSlaveEntry;    
  15.     
  16.     //構造函數    
  17.     public ConnectionPool(MasterSlaveServersConfig config, ConnectionManager connectionManager, MasterSlaveEntry masterSlaveEntry) {    
  18.         this.config = config;    
  19.         this.masterSlaveEntry = masterSlaveEntry;    
  20.         this.connectionManager = connectionManager;    
  21.     }    
  22.     
  23.     //連接池中需要增加對象時候調用此方法    
  24.     public RFuture<Void> add(final ClientConnectionsEntry entry) {    
  25.         final RPromise<Void> promise = connectionManager.newPromise();    
  26.         promise.addListener(new FutureListener<Void>() {    
  27.             @Override    
  28.             public void operationComplete(Future<Void> future) throws Exception {    
  29.                 entries.add(entry);    
  30.             }    
  31.         });    
  32.         initConnections(entry, promise, true);    
  33.         return promise;    
  34.     }    
  35.     
  36.     //初始化連接池中最小連接數    
  37.     private void initConnections(final ClientConnectionsEntry entry, final RPromise<Void> initPromise, boolean checkFreezed) {    
  38.         final int minimumIdleSize = getMinimumIdleSize(entry);    
  39.     
  40.         if (minimumIdleSize == 0 || (checkFreezed && entry.isFreezed())) {    
  41.             initPromise.trySuccess(null);    
  42.             return;    
  43.         }    
  44.     
  45.         final AtomicInteger initializedConnections = new AtomicInteger(minimumIdleSize);    
  46.         int startAmount = Math.min(50, minimumIdleSize);    
  47.         final AtomicInteger requests = new AtomicInteger(startAmount);    
  48.         for (int i = 0; i < startAmount; i++) {    
  49.             createConnection(checkFreezed, requests, entry, initPromise, minimumIdleSize, initializedConnections);    
  50.         }    
  51.     }    
  52.     
  53.     //創建連接對象到連接池中    
  54.     private void createConnection(final boolean checkFreezed, final AtomicInteger requests, final ClientConnectionsEntry entry, final RPromise<Void> initPromise,    
  55.             final int minimumIdleSize, final AtomicInteger initializedConnections) {    
  56.     
  57.         if ((checkFreezed && entry.isFreezed()) || !tryAcquireConnection(entry)) {    
  58.             int totalInitializedConnections = minimumIdleSize - initializedConnections.get();    
  59.             Throwable cause = new RedisConnectionException(    
  60.                     "Unable to init enough connections amount! Only " + totalInitializedConnections + " from " + minimumIdleSize + " were initialized. Server: "    
  61.                                         + entry.getClient().getAddr());    
  62.             initPromise.tryFailure(cause);    
  63.             return;    
  64.         }    
  65.             
  66.         acquireConnection(entry, new Runnable() {    
  67.                 
  68.             @Override    
  69.             public void run() {    
  70.                 RPromise<T> promise = connectionManager.newPromise();    
  71.                 createConnection(entry, promise);    
  72.                 promise.addListener(new FutureListener<T>() {    
  73.                     @Override    
  74.                     public void operationComplete(Future<T> future) throws Exception {    
  75.                         if (future.isSuccess()) {    
  76.                             T conn = future.getNow();    
  77.     
  78.                             releaseConnection(entry, conn);    
  79.                         }    
  80.     
  81.                         releaseConnection(entry);    
  82.     
  83.                         if (!future.isSuccess()) {    
  84.                             int totalInitializedConnections = minimumIdleSize - initializedConnections.get();    
  85.                             String errorMsg;    
  86.                             if (totalInitializedConnections == 0) {    
  87.                                 errorMsg = "Unable to connect to Redis server: " + entry.getClient().getAddr();    
  88.                             } else {    
  89.                                 errorMsg = "Unable to init enough connections amount! Only " + totalInitializedConnections     
  90.                                         + " from " + minimumIdleSize + " were initialized. Redis server: " + entry.getClient().getAddr();    
  91.                             }    
  92.                             Throwable cause = new RedisConnectionException(errorMsg, future.cause());    
  93.                             initPromise.tryFailure(cause);    
  94.                             return;    
  95.                         }    
  96.     
  97.                         int value = initializedConnections.decrementAndGet();    
  98.                         if (value == 0) {    
  99.                             log.info("{} connections initialized for {}", minimumIdleSize, entry.getClient().getAddr());    
  100.                             if (!initPromise.trySuccess(null)) {    
  101.                                 throw new IllegalStateException();    
  102.                             }    
  103.                         } else if (value > 0 && !initPromise.isDone()) {    
  104.                             if (requests.incrementAndGet() <= minimumIdleSize) {    
  105.                                 createConnection(checkFreezed, requests, entry, initPromise, minimumIdleSize, initializedConnections);    
  106.                             }    
  107.                         }    
  108.                     }    
  109.                 });    
  110.             }    
  111.         });    
  112.     
  113.     }    
  114.     
  115.     //連接池中租借出連接對象    
  116.     public RFuture<T> get(RedisCommand<?> command) {    
  117.         for (int j = entries.size() - 1; j >= 0; j--) {    
  118.             final ClientConnectionsEntry entry = getEntry();    
  119.             if (!entry.isFreezed()     
  120.                     && tryAcquireConnection(entry)) {    
  121.                 return acquireConnection(command, entry);    
  122.             }    
  123.         }    
  124.             
  125.         List<InetSocketAddress> failedAttempts = new LinkedList<InetSocketAddress>();    
  126.         List<InetSocketAddress> freezed = new LinkedList<InetSocketAddress>();    
  127.         for (ClientConnectionsEntry entry : entries) {    
  128.             if (entry.isFreezed()) {    
  129.                 freezed.add(entry.getClient().getAddr());    
  130.             } else {    
  131.                 failedAttempts.add(entry.getClient().getAddr());    
  132.             }    
  133.         }    
  134.     
  135.         StringBuilder errorMsg = new StringBuilder(getClass().getSimpleName() + " no available Redis entries. ");    
  136.         if (!freezed.isEmpty()) {    
  137.             errorMsg.append(" Disconnected hosts: " + freezed);    
  138.         }    
  139.         if (!failedAttempts.isEmpty()) {    
  140.             errorMsg.append(" Hosts disconnected due to `failedAttempts` limit reached: " + failedAttempts);    
  141.         }    
  142.     
  143.         RedisConnectionException exception = new RedisConnectionException(errorMsg.toString());    
  144.         return connectionManager.newFailedFuture(exception);    
  145.     }    
  146.     
  147.     //連接池中租借出連接對象執行操作RedisCommand    
  148.     public RFuture<T> get(RedisCommand<?> command, ClientConnectionsEntry entry) {    
  149.         if ((!entry.isFreezed() || entry.getFreezeReason() == FreezeReason.SYSTEM) &&     
  150.                 tryAcquireConnection(entry)) {    
  151.             return acquireConnection(command, entry);    
  152.         }    
  153.     
  154.         RedisConnectionException exception = new RedisConnectionException(    
  155.                 "Can't aquire connection to " + entry);    
  156.         return connectionManager.newFailedFuture(exception);    
  157.     }    
  158.       
  159.     //通過向redis服務端發送PING看是否返回PONG來檢測連接  
  160.     private void ping(RedisConnection c, final FutureListener<String> pingListener) {    
  161.         RFuture<String> f = c.async(RedisCommands.PING);    
  162.         f.addListener(pingListener);    
  163.     }    
  164.     
  165.     //歸還連接對象到連接池    
  166.     public void returnConnection(ClientConnectionsEntry entry, T connection) {    
  167.         if (entry.isFreezed()) {    
  168.             connection.closeAsync();    
  169.         } else {    
  170.             releaseConnection(entry, connection);    
  171.         }    
  172.         releaseConnection(entry);    
  173.     }    
  174.     
  175.     //釋放連接池中連接對象    
  176.     protected void releaseConnection(ClientConnectionsEntry entry) {    
  177.         entry.releaseConnection();    
  178.     }    
  179.     
  180.     //釋放連接池中連接對象    
  181.     protected void releaseConnection(ClientConnectionsEntry entry, T conn) {    
  182.         entry.releaseConnection(conn);    
  183.     }    
  184. }  

       用一張圖來解釋ConnectionPool幹了些啥,如下圖:

       都到這裏了,不介意再送一張圖瞭解各種部署方式下的連接池分佈了,如下圖:


4.Redisson的讀寫操作句柄類RedissonObject

       對於Redisson的任何操作,都需要獲取到操作句柄類RedissonObject,RedissonObject根據不同的數據類型有不同的RedissonObject實現類,RedissonObject的類繼承關係圖如下:

       例如想設置redis服務端的key=key的值value=123,你需要查詢Redis命令和Redisson對象匹配列表,找到如下對應關係:

       然後我們就知道調用代碼這麼寫:

Java代碼  收藏代碼
  1. Config config = new Config();// 創建配置    
  2.   config.useMasterSlaveServers() // 指定使用主從部署方式    
  3.   .setMasterAddress("redis://192.168.29.24:6379")  // 設置redis主節點    
  4.   .addSlaveAddress("redis://192.168.29.24:7000") // 設置redis從節點    
  5.   .addSlaveAddress("redis://192.168.29.24:7001"); // 設置redis從節點    
  6. RedissonClient redisson = Redisson.create(config);// 創建客戶端(發現這一操作非常耗時,基本在2秒-4秒左右)   
  7.   
  8. //任何Redisson操作首先需要獲取對應的操作句柄  
  9. //RBucket是操作句柄之一,實現類是RedissonBucket  
  10. RBucket<String> rBucket = redissonClient.getBucket("key");  
  11.   
  12. //通過操作句柄rBucket進行讀操作  
  13. rBucket.get();  
  14.   
  15. //通過操作句柄rBucket進行寫操作  
  16. rBucket.set("123");  

       至於其它的redis命令對應的redisson操作對象,都可以官網的Redis命令和Redisson對象匹配列表 查到。

 

6.Redisson的讀寫操作源碼分析

       從一個讀操作的代碼作爲入口分析代碼,如下:

Java代碼  收藏代碼
  1. //任何Redisson操作首先需要獲取對應的操作句柄,RBucket是操作句柄之一,實現類是RedissonBucket    
  2. RBucket<String> rBucket = redissonClient.getBucket("key");    
  3.     
  4. //通過操作句柄rBucket進行讀操作    
  5. rBucket.get();    
       繼續追蹤上面RBucket的get方法,如下:

       上面我們看到不管是讀操作還是寫操作都轉交CommandAsyncExecutor進行處理,那麼這裏我們需要看一下CommandAsyncExecutor.java裏關於讀寫操作處理的核心代碼,註釋代碼如下:
Java代碼  收藏代碼
  1. private NodeSource getNodeSource(String key) {  
  2.     //通過公式CRC16.crc16(key.getBytes()) % MAX_SLOT  
  3.     //計算出一個字符串key對應的分片在0~16383中哪個分片  
  4.     int slot = connectionManager.calcSlot(key);  
  5.     //之前已經將0~16383每個分片對應到唯一的一個MasterSlaveEntry,這裏取出來  
  6.     MasterSlaveEntry entry = connectionManager.getEntry(slot);  
  7.     //這裏將MasterSlaveEntry包裝成NodeSource【slot=null,addr=null,redirect=null,entry=MasterSlaveEntry】  
  8.     return new NodeSource(entry);  
  9. }  
  10. @Override  
  11. public <T, R> RFuture<R> readAsync(String key, Codec codec, RedisCommand<T> command, Object... params) {  
  12.     RPromise<R> mainPromise = connectionManager.newPromise();  
  13.     //獲取NodeSource【slot=null,addr=null,redirect=null,entry=MasterSlaveEntry】  
  14.     NodeSource source = getNodeSource(key);  
  15.     調用異步執行方法async  
  16.     async(true, source, codec, command, params, mainPromise, 0);  
  17.     return mainPromise;  
  18. }  
  19. protected <V, R> void async(final boolean readOnlyMode, final NodeSource source, final Codec codec,  
  20.         final RedisCommand<V> command, final Object[] params, final RPromise<R> mainPromise, final int attempt) {  
  21.     //操作被取消,那麼直接返回  
  22.     if (mainPromise.isCancelled()) {  
  23.         free(params);  
  24.         return;  
  25.     }  
  26.     //連接管理器無法連接,釋放參數所佔資源,然後返回  
  27.     if (!connectionManager.getShutdownLatch().acquire()) {  
  28.         free(params);  
  29.         mainPromise.tryFailure(new RedissonShutdownException("Redisson is shutdown"));  
  30.         return;  
  31.     }  
  32.       
  33.     final AsyncDetails<V, R> details = AsyncDetails.acquire();  
  34.     if (isRedissonReferenceSupportEnabled()) {  
  35.         try {  
  36.             for (int i = 0; i < params.length; i++) {  
  37.                 RedissonReference reference = RedissonObjectFactory.toReference(getConnectionManager().getCfg(), params[i]);  
  38.                 if (reference != null) {  
  39.                     params[i] = reference;  
  40.                 }  
  41.             }  
  42.         } catch (Exception e) {  
  43.             connectionManager.getShutdownLatch().release();  
  44.             free(params);  
  45.             mainPromise.tryFailure(e);  
  46.             return;  
  47.         }  
  48.     }  
  49.       
  50.     //開始從connectionManager獲取池中的連接  
  51.     //這裏採用異步方式,創建一個RFuture對象,等待池中連接,一旦獲得連接,然後進行讀和寫操作  
  52.     final RFuture<RedisConnection> connectionFuture;  
  53.     if (readOnlyMode) {//對於讀操作默認readOnlyMode=true,這裏會執行  
  54.         connectionFuture = connectionManager.connectionReadOp(source, command);  
  55.     } else {//對於寫操作默認readOnlyMode=false,這裏會執行  
  56.         connectionFuture = connectionManager.connectionWriteOp(source, command);  
  57.     }  
  58.       
  59.     //創建RPromise,用於操作失敗時候重試  
  60.     final RPromise<R> attemptPromise = connectionManager.newPromise();  
  61.     details.init(connectionFuture, attemptPromise, readOnlyMode, source, codec, command, params, mainPromise, attempt);  
  62.     //創建FutureListener,監測外部請求是否已經取消了之前提交的讀寫操作,如果取消了,那麼就讓正在執行的讀寫操作停止  
  63.     FutureListener<R> mainPromiseListener = new FutureListener<R>() {  
  64.         @Override  
  65.         public void operationComplete(Future<R> future) throws Exception {  
  66.             if (future.isCancelled() && connectionFuture.cancel(false)) {  
  67.                 log.debug("Connection obtaining canceled for {}", command);  
  68.                 details.getTimeout().cancel();  
  69.                 if (details.getAttemptPromise().cancel(false)) {  
  70.                     free(params);  
  71.                 }  
  72.             }  
  73.         }  
  74.     };  
  75.   
  76.     //創建TimerTask,用於操作失敗後通過定時器進行操作重試  
  77.     final TimerTask retryTimerTask = new TimerTask() {  
  78.         @Override  
  79.         public void run(Timeout t) throws Exception {  
  80.             if (details.getAttemptPromise().isDone()) {  
  81.                 return;  
  82.             }  
  83.             if (details.getConnectionFuture().cancel(false)) {  
  84.                 connectionManager.getShutdownLatch().release();  
  85.             } else {  
  86.                 if (details.getConnectionFuture().isSuccess()) {  
  87.                     if (details.getWriteFuture() == null || !details.getWriteFuture().isDone()) {  
  88.                         if (details.getAttempt() == connectionManager.getConfig().getRetryAttempts()) {  
  89.                             if (details.getWriteFuture().cancel(false)) {  
  90.                                 if (details.getException() == null) {  
  91.                                     details.setException(new RedisTimeoutException("Unable to send command: " + command + " with params: " + LogHelper.toString(details.getParams()) + " after " + connectionManager.getConfig().getRetryAttempts() + " retry attempts"));  
  92.                                 }  
  93.                                 details.getAttemptPromise().tryFailure(details.getException());  
  94.                             }  
  95.                             return;  
  96.                         }  
  97.                         details.incAttempt();  
  98.                         Timeout timeout = connectionManager.newTimeout(this, connectionManager.getConfig().getRetryInterval(), TimeUnit.MILLISECONDS);  
  99.                         details.setTimeout(timeout);  
  100.                         return;  
  101.                     }  
  102.   
  103.                     if (details.getWriteFuture().isDone() && details.getWriteFuture().isSuccess()) {  
  104.                         return;  
  105.                     }  
  106.                 }  
  107.             }  
  108.             if (details.getMainPromise().isCancelled()) {  
  109.                 if (details.getAttemptPromise().cancel(false)) {  
  110.                     free(details);  
  111.                     AsyncDetails.release(details);  
  112.                 }  
  113.                 return;  
  114.             }  
  115.             if (details.getAttempt() == connectionManager.getConfig().getRetryAttempts()) {  
  116.                 if (details.getException() == null) {  
  117.                     details.setException(new RedisTimeoutException("Unable to send command: " + command + " with params: " + LogHelper.toString(details.getParams() + " after " + connectionManager.getConfig().getRetryAttempts() + " retry attempts")));  
  118.                 }  
  119.                 details.getAttemptPromise().tryFailure(details.getException());  
  120.                 return;  
  121.             }  
  122.             if (!details.getAttemptPromise().cancel(false)) {  
  123.                 return;  
  124.             }  
  125.             int count = details.getAttempt() + 1;  
  126.             if (log.isDebugEnabled()) {  
  127.                 log.debug("attempt {} for command {} and params {}",  
  128.                         count, details.getCommand(), Arrays.toString(details.getParams()));  
  129.             }  
  130.             details.removeMainPromiseListener();  
  131.             async(details.isReadOnlyMode(), details.getSource(), details.getCodec(), details.getCommand(), details.getParams(), details.getMainPromise(), count);  
  132.             AsyncDetails.release(details);  
  133.         }  
  134.     };  
  135.   
  136.     //配置對於讀寫操作的超時時間  
  137.     Timeout timeout = connectionManager.newTimeout(retryTimerTask, connectionManager.getConfig().getRetryInterval(), TimeUnit.MILLISECONDS);  
  138.     details.setTimeout(timeout);  
  139.     details.setupMainPromiseListener(mainPromiseListener);  
  140.   
  141.     //給connectionFuture增加監聽事件,當從連接池中獲取連接成功,成功的事件會被觸發,通知這裏執行後續讀寫動作  
  142.     connectionFuture.addListener(new FutureListener<RedisConnection>() {  
  143.         @Override  
  144.         public void operationComplete(Future<RedisConnection> connFuture) throws Exception {  
  145.             if (connFuture.isCancelled()) {//從池中獲取連接被取消,直接返回  
  146.                 return;  
  147.             }  
  148.   
  149.             if (!connFuture.isSuccess()) {//從池中獲取連接失敗  
  150.                 connectionManager.getShutdownLatch().release();  
  151.                 details.setException(convertException(connectionFuture));  
  152.                 return;  
  153.             }  
  154.   
  155.             if (details.getAttemptPromise().isDone() || details.getMainPromise().isDone()) {//從池中獲取連接失敗,並且嘗試了一定次數仍然失敗,默認嘗試次數爲0  
  156.                 releaseConnection(source, connectionFuture, details.isReadOnlyMode(), details.getAttemptPromise(), details);  
  157.                 return;  
  158.             }  
  159.   
  160.             //從池中獲取連接成功,這裏取出連接對象RedisConnection  
  161.             final RedisConnection connection = connFuture.getNow();  
  162.             //如果需要重定向,這裏進行重定向  
  163.             //重定向的情況有:集羣模式對應的slot分佈在其他節點,就需要進行重定向  
  164.             if (details.getSource().getRedirect() == Redirect.ASK) {  
  165.                 List<CommandData<?, ?>> list = new ArrayList<CommandData<?, ?>>(2);  
  166.                 RPromise<Void> promise = connectionManager.newPromise();  
  167.                 list.add(new CommandData<Void, Void>(promise, details.getCodec(), RedisCommands.ASKING, new Object[]{}));  
  168.                 list.add(new CommandData<V, R>(details.getAttemptPromise(), details.getCodec(), details.getCommand(), details.getParams()));  
  169.                 RPromise<Void> main = connectionManager.newPromise();  
  170.                 ChannelFuture future = connection.send(new CommandsData(main, list));  
  171.                 details.setWriteFuture(future);  
  172.             } else {  
  173.                 if (log.isDebugEnabled()) {  
  174.                     log.debug("acquired connection for command {} and params {} from slot {} using node {}... {}",  
  175.                             details.getCommand(), Arrays.toString(details.getParams()), details.getSource(), connection.getRedisClient().getAddr(), connection);  
  176.                 }  
  177.                 //發送讀寫操作到RedisConnection,進行執行  
  178.                 ChannelFuture future = connection.send(new CommandData<V, R>(details.getAttemptPromise(), details.getCodec(), details.getCommand(), details.getParams()));  
  179.                 details.setWriteFuture(future);  
  180.             }  
  181.             //對於寫操作增加監聽事件回調,對寫操作是否成功,失敗原因進行日誌打印  
  182.             details.getWriteFuture().addListener(new ChannelFutureListener() {  
  183.                 @Override  
  184.                 public void operationComplete(ChannelFuture future) throws Exception {  
  185.                     checkWriteFuture(details, connection);  
  186.                 }  
  187.             });  
  188.             //返回RedisConnection連接到連接池  
  189.             releaseConnection(source, connectionFuture, details.isReadOnlyMode(), details.getAttemptPromise(), details);  
  190.         }  
  191.     });  
  192.   
  193.     attemptPromise.addListener(new FutureListener<R>() {  
  194.         @Override  
  195.         public void operationComplete(Future<R> future) throws Exception {  
  196.             checkAttemptFuture(source, details, future);  
  197.         }  
  198.     });  
  199. }  
       上面的代碼我用一張讀寫操作處理流程圖總結如下:

 

 

       至此,關於讀寫操作的源碼講解完畢。在上面的代碼註釋中,列出如下重點。

    6.1 分片SLOT的計算公式

       SLOT=CRC16.crc16(key.getBytes()) % MAX_SLOT

 

    6.2 每個ConnectionPool持有的ClientConnectionsEntry對象凍結判斷條件

       一個節點被判斷爲凍結,必須同時滿足以下條件:

  • 該節點有slave節點,並且從節點個數大於0;
  • 設置的配置ReadMode不爲並且SubscriptionMode不爲MASTER;
  • 該節點的從節點至少有一個存活着,也即如果有從節點宕機,宕機的從節點的個數小於該節點總的從節點個數

    6.3 讀寫負載圖

 

 


 

7.Redisson的讀寫操作從連接池獲取連接對象源碼分析和Redisson裏RedisClient使用netty源碼分析

       讀寫操作首先都需要獲取到一個連接對象,在上面的分析中我們知道讀寫操作都是通過CommandAsyncExecutor.java裏的如下代碼獲取連接對象:

Java代碼  收藏代碼
  1. //開始從connectionManager獲取池中的連接    
  2. //這裏採用異步方式,創建一個RFuture對象,等待池中連接,一旦獲得連接,然後進行讀和寫操作    
  3. final RFuture<RedisConnection> connectionFuture;    
  4. if (readOnlyMode) {//對於讀操作默認readOnlyMode=true,這裏會執行    
  5.     connectionFuture = connectionManager.connectionReadOp(source, command);    
  6. else {//對於寫操作默認readOnlyMode=false,這裏會執行    
  7.     connectionFuture = connectionManager.connectionWriteOp(source, command);    
  8. }    
       上面讀操作調用了connectionManager.connectionReadOp從連接池獲取連接對象,寫操作調用了connectionManager.connectionWriteOp從連接池獲取連接對象,我們繼續跟進connectionManager關於connectionReadOp和connectionWriteOp的源代碼,註釋如下:
Java代碼  收藏代碼
  1. /**    
  2. * 讀操作通過ConnectionManager從連接池獲取連接對象 
  3. * @param source for NodeSource  
  4. * @param command for RedisCommand<?>  
  5. * @return RFuture<RedisConnection> 
  6. */   
  7. public RFuture<RedisConnection> connectionReadOp(NodeSource source, RedisCommand<?> command) {  
  8.     //這裏之前分析過source=NodeSource【slot=null,addr=null,redirect=null,entry=MasterSlaveEntry】  
  9.     MasterSlaveEntry entry = source.getEntry();  
  10.     if (entry == null && source.getSlot() != null) {//這裏不會執行source裏slot=null  
  11.         entry = getEntry(source.getSlot());  
  12.     }  
  13.     if (source.getAddr() != null) {//這裏不會執行source裏addr=null  
  14.         entry = getEntry(source.getAddr());  
  15.         if (entry == null) {  
  16.             for (MasterSlaveEntry e : getEntrySet()) {  
  17.                 if (e.hasSlave(source.getAddr())) {  
  18.                     entry = e;  
  19.                     break;  
  20.                 }  
  21.             }  
  22.         }  
  23.         if (entry == null) {  
  24.             RedisNodeNotFoundException ex = new RedisNodeNotFoundException("Node: " + source.getAddr() + " for slot: " + source.getSlot() + " hasn't been discovered yet");  
  25.             return RedissonPromise.newFailedFuture(ex);  
  26.         }  
  27.           
  28.         return entry.connectionReadOp(command, source.getAddr());  
  29.     }  
  30.       
  31.     if (entry == null) {//這裏不會執行source裏entry不等於null  
  32.         RedisNodeNotFoundException ex = new RedisNodeNotFoundException("Node: " + source.getAddr() + " for slot: " + source.getSlot() + " hasn't been discovered yet");  
  33.         return RedissonPromise.newFailedFuture(ex);  
  34.     }  
  35.     //MasterSlaveEntry裏從連接池獲取連接對象  
  36.     return entry.connectionReadOp(command);  
  37. }  
  38. /**    
  39. * 寫操作通過ConnectionManager從連接池獲取連接對象 
  40. * @param source for NodeSource  
  41. * @param command for RedisCommand<?>  
  42. * @return RFuture<RedisConnection> 
  43. */   
  44. public RFuture<RedisConnection> connectionWriteOp(NodeSource source, RedisCommand<?> command) {  
  45.     //這裏之前分析過source=NodeSource【slot=null,addr=null,redirect=null,entry=MasterSlaveEntry】  
  46.     MasterSlaveEntry entry = source.getEntry();  
  47.     if (entry == null) {  
  48.         entry = getEntry(source);  
  49.     }  
  50.     if (entry == null) {//這裏不會執行source裏entry不等於null  
  51.         RedisNodeNotFoundException ex = new RedisNodeNotFoundException("Node: " + source.getAddr() + " for slot: " + source.getSlot() + " hasn't been discovered yet");  
  52.         return RedissonPromise.newFailedFuture(ex);  
  53.     }  
  54.     //MasterSlaveEntry裏從連接池獲取連接對象  
  55.     return entry.connectionWriteOp(command);  
  56. }  
       我們看到上面調用ConnectionManager從連接池獲取連接對象,但是ConnectionManager卻將獲取連接操作轉交MasterSlaveEntry處理,我們再一次回顧一下MasterSlaveEntry的組成: 

       MasterSlaveEntry裏持有中我們開篇所提到的四個連接池,那麼這裏我們繼續關注MasterSlaveEntry.java的源代碼:
Java代碼  收藏代碼
  1. /**    
  2. * 寫操作從MasterConnectionPool連接池裏獲取連接對象 
  3. * @param command for RedisCommand<?>  
  4. * @return RFuture<RedisConnection> 
  5. */   
  6. public RFuture<RedisConnection> connectionWriteOp(RedisCommand<?> command) {  
  7.     //我們知道writeConnectionHolder的類型爲MasterConnectionPool  
  8.     //這裏就是從MasterConnectionPool裏獲取連接對象  
  9.     return writeConnectionHolder.get(command);  
  10. }  
  11.   
  12. /**    
  13. * 寫操作從LoadBalancerManager裏獲取連接對象 
  14. * @param command for RedisCommand<?>  
  15. * @return RFuture<RedisConnection> 
  16. */   
  17. public RFuture<RedisConnection> connectionReadOp(RedisCommand<?> command) {  
  18.     if (config.getReadMode() == ReadMode.MASTER) {  
  19.         //我們知道默認ReadMode=ReadMode.SLAVE,所以對於讀操作這裏不會執行  
  20.         return connectionWriteOp(command);  
  21.     }  
  22.     //我們知道slaveBalancer裏持有者SlaveConnectionPool和PubSubConnectionPool  
  23.     //這裏就是從SlaveConnectionPool裏獲取連接對象  
  24.     return slaveBalancer.nextConnection(command);  
  25. }  
       似乎又繞回來了,最終的獲取連接對象都轉交到了從連接池ConnectionPool裏獲取連接對象,註釋ConnectionPool裏的獲取連接對象代碼如下: 
Java代碼  收藏代碼
  1. /**    
  2. * 讀寫操作從ConnectionPool.java連接池裏獲取連接對象 
  3. * @param command for RedisCommand<?>  
  4. * @return RFuture<T> 
  5. */   
  6. public RFuture<T> get(RedisCommand<?> command) {  
  7.     for (int j = entries.size() - 1; j >= 0; j--) {  
  8.         final ClientConnectionsEntry entry = getEntry();  
  9.         if (!entry.isFreezed() && tryAcquireConnection(entry)) {  
  10.             //遍歷ConnectionPool裏維持的ClientConnectionsEntry列表  
  11.             //遍歷的算法默認爲RoundRobinLoadBalancer  
  12.             //ClientConnectionsEntry裏對應的redis節點爲非凍結節點,也即freezed=false  
  13.             return acquireConnection(command, entry);  
  14.         }  
  15.     }  
  16.       
  17.     //記錄失敗重試信息  
  18.     List<InetSocketAddress> failedAttempts = new LinkedList<InetSocketAddress>();  
  19.     List<InetSocketAddress> freezed = new LinkedList<InetSocketAddress>();  
  20.     for (ClientConnectionsEntry entry : entries) {  
  21.         if (entry.isFreezed()) {  
  22.             freezed.add(entry.getClient().getAddr());  
  23.         } else {  
  24.             failedAttempts.add(entry.getClient().getAddr());  
  25.         }  
  26.     }  
  27.   
  28.     StringBuilder errorMsg = new StringBuilder(getClass().getSimpleName() + " no available Redis entries. ");  
  29.     if (!freezed.isEmpty()) {  
  30.         errorMsg.append(" Disconnected hosts: " + freezed);  
  31.     }  
  32.     if (!failedAttempts.isEmpty()) {  
  33.         errorMsg.append(" Hosts disconnected due to `failedAttempts` limit reached: " + failedAttempts);  
  34.     }  
  35.     //獲取連接失敗拋出異常  
  36.     RedisConnectionException exception = new RedisConnectionException(errorMsg.toString());  
  37.     return connectionManager.newFailedFuture(exception);  
  38. }  
  39.   
  40. /**    
  41. * 讀寫操作從ConnectionPool.java連接池裏獲取連接對象 
  42. * @param command for RedisCommand<?>  
  43. * @param entry for ClientConnectionsEntry 
  44. * @return RFuture<T> 
  45. */   
  46. private RFuture<T> acquireConnection(RedisCommand<?> command, final ClientConnectionsEntry entry) {  
  47.     //創建一個異步結果獲取RPromise  
  48.     final RPromise<T> result = connectionManager.newPromise();  
  49.     //獲取連接前首先將ClientConnectionsEntry裏的空閒連接信號freeConnectionsCounter值減1  
  50.     //該操作成功後將調用這裏的回調函數AcquireCallback<T>  
  51.     AcquireCallback<T> callback = new AcquireCallback<T>() {  
  52.         @Override  
  53.         public void run() {  
  54.             result.removeListener(this);  
  55.             //freeConnectionsCounter值減1成功,說明獲取可以獲取到連接  
  56.             //這裏纔是真正獲取連接的操作  
  57.             connectTo(entry, result);  
  58.         }  
  59.           
  60.         @Override  
  61.         public void operationComplete(Future<T> future) throws Exception {  
  62.             entry.removeConnection(this);  
  63.         }  
  64.     };  
  65.     //異步結果獲取RPromise綁定到上面的回調函數callback  
  66.     result.addListener(callback);  
  67.     //嘗試將ClientConnectionsEntry裏的空閒連接信號freeConnectionsCounter值減1,如果成功就調用callback從連接池獲取連接  
  68.     acquireConnection(entry, callback);  
  69.     //返回異步結果獲取RPromise  
  70.     return result;  
  71. }  
  72.   
  73. /**    
  74. * 真正從連接池中獲取連接 
  75. * @param entry for ClientConnectionsEntry 
  76. * @param promise for RPromise<T> 
  77. */   
  78. private void connectTo(ClientConnectionsEntry entry, RPromise<T> promise) {  
  79.     if (promise.isDone()) {  
  80.         releaseConnection(entry);  
  81.         return;  
  82.     }  
  83.     //從連接池中取出一個連接  
  84.     T conn = poll(entry);  
  85.     if (conn != null) {  
  86.         if (!conn.isActive()) {  
  87.             promiseFailure(entry, promise, conn);  
  88.             return;  
  89.         }  
  90.   
  91.         connectedSuccessful(entry, promise, conn);  
  92.         return;  
  93.     }  
  94.     //如果仍然獲取不到連接,可能連接池中連接對象都被租借了,這裏開始創建一個新的連接對象放到連接池中  
  95.     createConnection(entry, promise);  
  96. }  
  97.   
  98. /**    
  99. * 從連接池中獲取連接 
  100. * @param entry for ClientConnectionsEntry 
  101. * @return T 
  102. */   
  103. protected T poll(ClientConnectionsEntry entry) {  
  104.     return (T) entry.pollConnection();  
  105. }  
  106.   
  107. /**    
  108. * 調用ClientConnectionsEntry創建一個連接放置到連接池中並返回此連接 
  109. * @param entry for ClientConnectionsEntry 
  110. * @param promise for RPromise<T> 
  111. */   
  112. private void createConnection(final ClientConnectionsEntry entry, final RPromise<T> promise) {  
  113.     //調用ClientConnectionsEntry創建一個連接放置到連接池中並返回此連接  
  114.     RFuture<T> connFuture = connect(entry);  
  115.     connFuture.addListener(new FutureListener<T>() {  
  116.         @Override  
  117.         public void operationComplete(Future<T> future) throws Exception {  
  118.             if (!future.isSuccess()) {  
  119.                 promiseFailure(entry, promise, future.cause());  
  120.                 return;  
  121.             }  
  122.   
  123.             T conn = future.getNow();  
  124.             if (!conn.isActive()) {  
  125.                 promiseFailure(entry, promise, conn);  
  126.                 return;  
  127.             }  
  128.   
  129.             connectedSuccessful(entry, promise, conn);  
  130.         }  
  131.     });  
  132. }  
       ConnectionPool.java裏獲取讀寫操作的連接,是遍歷ConnectionPool裏維持的ClientConnectionsEntry列表,找到一非凍結的ClientConnectionsEntry,然後調用ClientConnectionsEntry裏的freeConnectionsCounter嘗試將值減1,如果成功,說明連接池中可以獲取到連接,那麼就從ClientConnectionsEntry裏獲取一個連接出來,如果拿不到連接,會調用ClientConnectionsEntry創建一個新連接放置到連接池中,並返回此連接,這裏回顧一下ClientConnectionsEntry的組成圖
       我們繼續跟進ClientConnectionsEntry.java的源代碼,註釋如下:
Java代碼  收藏代碼
  1. /**    
  2. *  ClientConnectionsEntry裏從freeConnections裏獲取一個連接並返回給讀寫操作使用 
  3. */   
  4. public RedisConnection pollConnection() {  
  5.     return freeConnections.poll();  
  6. }  
  7.   
  8. /**    
  9. *  ClientConnectionsEntry裏新創建一個連接對象返回給讀寫操作使用 
  10. */   
  11. public RFuture<RedisConnection> connect() {  
  12.     //調用RedisClient利用netty連接redis服務端,將返回的netty的outboundchannel包裝成RedisConnection並返回  
  13.     RFuture<RedisConnection> future = client.connectAsync();  
  14.     future.addListener(new FutureListener<RedisConnection>() {  
  15.         @Override  
  16.         public void operationComplete(Future<RedisConnection> future) throws Exception {  
  17.             if (!future.isSuccess()) {  
  18.                 return;  
  19.             }  
  20.               
  21.             RedisConnection conn = future.getNow();  
  22.             onConnect(conn);  
  23.             log.debug("new connection created: {}", conn);  
  24.         }  
  25.     });  
  26.     return future;  
  27. }  
       上面的代碼說明如果ClientConnectionsEntry裏的freeConnections有空閒連接,那麼直接返回該連接,如果沒有那麼調用RedisClient.connectAsync創建一個新的連接,這裏我繼續註釋一下RedisClient.java的源代碼如下:
Java代碼  收藏代碼
  1. package org.redisson.client;  
  2.   
  3. import java.net.InetSocketAddress;  
  4. import java.net.URI;  
  5. import java.util.concurrent.ExecutorService;  
  6. import java.util.concurrent.Executors;  
  7. import java.util.concurrent.TimeUnit;  
  8. import org.redisson.api.RFuture;  
  9. import org.redisson.client.handler.RedisChannelInitializer;  
  10. import org.redisson.client.handler.RedisChannelInitializer.Type;  
  11. import org.redisson.misc.RPromise;  
  12. import org.redisson.misc.RedissonPromise;  
  13. import io.netty.bootstrap.Bootstrap;  
  14. import io.netty.channel.Channel;  
  15. import io.netty.channel.ChannelFuture;  
  16. import io.netty.channel.ChannelFutureListener;  
  17. import io.netty.channel.ChannelOption;  
  18. import io.netty.channel.EventLoopGroup;  
  19. import io.netty.channel.group.ChannelGroup;  
  20. import io.netty.channel.group.ChannelGroupFuture;  
  21. import io.netty.channel.group.DefaultChannelGroup;  
  22. import io.netty.channel.nio.NioEventLoopGroup;  
  23. import io.netty.channel.socket.SocketChannel;  
  24. import io.netty.channel.socket.nio.NioSocketChannel;  
  25. import io.netty.util.HashedWheelTimer;  
  26. import io.netty.util.Timer;  
  27. import io.netty.util.concurrent.Future;  
  28. import io.netty.util.concurrent.FutureListener;  
  29. import io.netty.util.concurrent.GlobalEventExecutor;  
  30. import org.redisson.misc.URIBuilder;  
  31.   
  32. /** 
  33.  * 使用java裏的網絡編程框架Netty連接redis服務端 
  34.  * 作者: Nikita Koksharov 
  35.  */  
  36. public class RedisClient {  
  37.     private final Bootstrap bootstrap;//Netty的工具類Bootstrap,用於連接建立等作用  
  38.     private final Bootstrap pubSubBootstrap;//Netty的工具類Bootstrap,用於連接建立等作用  
  39.     private final InetSocketAddress addr;//socket連接的地址  
  40.     //channels是netty提供的一個全局對象,裏面記錄着當前socket連接上的所有處於可用狀態的連接channel  
  41.     //channels會自動監測裏面的channel,當channel斷開時,會主動踢出該channel,永遠保留當前可用的channel列表  
  42.     private final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);  
  43.   
  44.     private ExecutorService executor;//REACOTR模型的java異步執行線程池  
  45.     private final long commandTimeout;//超時時間  
  46.     private Timer timer;//定時器  
  47.     private boolean hasOwnGroup;  
  48.     private RedisClientConfig config;//redis連接配置信息  
  49.   
  50.     //構造方法  
  51.     public static RedisClient create(RedisClientConfig config) {  
  52.         if (config.getTimer() == null) {  
  53.             config.setTimer(new HashedWheelTimer());  
  54.         }  
  55.         return new RedisClient(config);  
  56.     }  
  57.     //構造方法  
  58.     private RedisClient(RedisClientConfig config) {  
  59.         this.config = config;  
  60.         this.executor = config.getExecutor();  
  61.         this.timer = config.getTimer();  
  62.           
  63.         addr = new InetSocketAddress(config.getAddress().getHost(), config.getAddress().getPort());  
  64.           
  65.         bootstrap = createBootstrap(config, Type.PLAIN);  
  66.         pubSubBootstrap = createBootstrap(config, Type.PUBSUB);  
  67.           
  68.         this.commandTimeout = config.getCommandTimeout();  
  69.     }  
  70.   
  71.     //java的網路編程框架Netty工具類Bootstrap初始化  
  72.     private Bootstrap createBootstrap(RedisClientConfig config, Type type) {  
  73.         Bootstrap bootstrap = new Bootstrap()  
  74.                         .channel(config.getSocketChannelClass())  
  75.                         .group(config.getGroup())  
  76.                         .remoteAddress(addr);  
  77.         //註冊netty相關socket數據處理RedisChannelInitializer  
  78.         bootstrap.handler(new RedisChannelInitializer(bootstrap, config, this, channels, type));  
  79.         //設置超時時間  
  80.         bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectTimeout());  
  81.         return bootstrap;  
  82.     }  
  83.       
  84.     //構造方法  
  85.     @Deprecated  
  86.     public RedisClient(String address) {  
  87.         this(URIBuilder.create(address));  
  88.     }  
  89.       
  90.     //構造方法  
  91.     @Deprecated  
  92.     public RedisClient(URI address) {  
  93.         this(new HashedWheelTimer(), Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2), new NioEventLoopGroup(), address);  
  94.         hasOwnGroup = true;  
  95.     }  
  96.   
  97.     //構造方法  
  98.     @Deprecated  
  99.     public RedisClient(Timer timer, ExecutorService executor, EventLoopGroup group, URI address) {  
  100.         this(timer, executor, group, address.getHost(), address.getPort());  
  101.     }  
  102.       
  103.     //構造方法  
  104.     @Deprecated  
  105.     public RedisClient(String host, int port) {  
  106.         this(new HashedWheelTimer(), Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2), new NioEventLoopGroup(), NioSocketChannel.class, host, port, 1000010000);  
  107.         hasOwnGroup = true;  
  108.     }  
  109.   
  110.     //構造方法  
  111.     @Deprecated  
  112.     public RedisClient(Timer timer, ExecutorService executor, EventLoopGroup group, String host, int port) {  
  113.         this(timer, executor, group, NioSocketChannel.class, host, port, 1000010000);  
  114.     }  
  115.       
  116.     //構造方法  
  117.     @Deprecated  
  118.     public RedisClient(String host, int port, int connectTimeout, int commandTimeout) {  
  119.         this(new HashedWheelTimer(), Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2), new NioEventLoopGroup(), NioSocketChannel.class, host, port, connectTimeout, commandTimeout);  
  120.     }  
  121.   
  122.     //構造方法  
  123.     @Deprecated  
  124.     public RedisClient(final Timer timer, ExecutorService executor, EventLoopGroup group, Class<? extends SocketChannel> socketChannelClass, String host, int port,   
  125.                         int connectTimeout, int commandTimeout) {  
  126.         RedisClientConfig config = new RedisClientConfig();  
  127.         config.setTimer(timer).setExecutor(executor).setGroup(group).setSocketChannelClass(socketChannelClass)  
  128.         .setAddress(host, port).setConnectTimeout(connectTimeout).setCommandTimeout(commandTimeout);  
  129.           
  130.         this.config = config;  
  131.         this.executor = config.getExecutor();  
  132.         this.timer = config.getTimer();  
  133.           
  134.         addr = new InetSocketAddress(config.getAddress().getHost(), config.getAddress().getPort());  
  135.           
  136.         //java的網路編程框架Netty工具類Bootstrap初始化  
  137.         bootstrap = createBootstrap(config, Type.PLAIN);  
  138.         pubSubBootstrap = createBootstrap(config, Type.PUBSUB);  
  139.           
  140.         this.commandTimeout = config.getCommandTimeout();  
  141.     }  
  142.   
  143.     //獲取連接的IP地址  
  144.     public String getIpAddr() {  
  145.         return addr.getAddress().getHostAddress() + ":" + addr.getPort();  
  146.     }  
  147.     //獲取socket連接的地址  
  148.     public InetSocketAddress getAddr() {  
  149.         return addr;  
  150.     }  
  151.     //獲取超時時間  
  152.     public long getCommandTimeout() {  
  153.         return commandTimeout;  
  154.     }  
  155.     //獲取netty的線程池  
  156.     public EventLoopGroup getEventLoopGroup() {  
  157.         return bootstrap.config().group();  
  158.     }  
  159.     //獲取redis連接配置  
  160.     public RedisClientConfig getConfig() {  
  161.         return config;  
  162.     }  
  163.     //獲取連接RedisConnection  
  164.     public RedisConnection connect() {  
  165.         try {  
  166.             return connectAsync().syncUninterruptibly().getNow();  
  167.         } catch (Exception e) {  
  168.             throw new RedisConnectionException("Unable to connect to: " + addr, e);  
  169.         }  
  170.     }  
  171.     //啓動netty去連接redis服務端,設置java的Future嘗試將netty連接上的OutBoundChannel包裝成RedisConnection並返回RedisConnection  
  172.     public RFuture<RedisConnection> connectAsync() {  
  173.         final RPromise<RedisConnection> f = new RedissonPromise<RedisConnection>();  
  174.         //netty連接redis服務端  
  175.         ChannelFuture channelFuture = bootstrap.connect();  
  176.         channelFuture.addListener(new ChannelFutureListener() {  
  177.             @Override  
  178.             public void operationComplete(final ChannelFuture future) throws Exception {  
  179.                 if (future.isSuccess()) {  
  180.                     //將netty連接上的OutBoundChannel包裝成RedisConnection並返回RedisConnection  
  181.                     final RedisConnection c = RedisConnection.getFrom(future.channel());  
  182.                     c.getConnectionPromise().addListener(new FutureListener<RedisConnection>() {  
  183.                         @Override  
  184.                         public void operationComplete(final Future<RedisConnection> future) throws Exception {  
  185.                             bootstrap.config().group().execute(new Runnable() {  
  186.                                 @Override  
  187.                                 public void run() {  
  188.                                     if (future.isSuccess()) {  
  189.                                         if (!f.trySuccess(c)) {  
  190.                                             c.closeAsync();  
  191.                                         }  
  192.                                     } else {  
  193.                                         f.tryFailure(future.cause());  
  194.                                         c.closeAsync();  
  195.                                     }  
  196.                                 }  
  197.                             });  
  198.                         }  
  199.                     });  
  200.                 } else {  
  201.                     bootstrap.config().group().execute(new Runnable() {  
  202.                         public void run() {  
  203.                             f.tryFailure(future.cause());  
  204.                         }  
  205.                     });  
  206.                 }  
  207.             }  
  208.         });  
  209.         return f;  
  210.     }  
  211.     //獲取訂閱相關連接RedisPubSubConnection  
  212.     public RedisPubSubConnection connectPubSub() {  
  213.         try {  
  214.             return connectPubSubAsync().syncUninterruptibly().getNow();  
  215.         } catch (Exception e) {  
  216.             throw new RedisConnectionException("Unable to connect to: " + addr, e);  
  217.         }  
  218.     }  
  219.   
  220.     //啓動netty去連接redis服務端,設置java的Future嘗試將netty連接上的OutBoundChannel包裝成RedisPubSubConnection並返回RedisPubSubConnection  
  221.     public RFuture<RedisPubSubConnection> connectPubSubAsync() {  
  222.         final RPromise<RedisPubSubConnection> f = new RedissonPromise<RedisPubSubConnection>();  
  223.         //netty連接redis服務端  
  224.         ChannelFuture channelFuture = pubSubBootstrap.connect();  
  225.         channelFuture.addListener(new ChannelFutureListener() {  
  226.             @Override  
  227.             public void operationComplete(final ChannelFuture future) throws Exception {  
  228.                 if (future.isSuccess()) {  
  229.                     //將netty連接上的OutBoundChannel包裝成RedisPubSubConnection並返回RedisPubSubConnection  
  230.                     final RedisPubSubConnection c = RedisPubSubConnection.getFrom(future.channel());  
  231.                     c.<RedisPubSubConnection>getConnectionPromise().addListener(new FutureListener<RedisPubSubConnection>() {  
  232.                         @Override  
  233.                         public void operationComplete(final Future<RedisPubSubConnection> future) throws Exception {  
  234.                             bootstrap.config().group().execute(new Runnable() {  
  235.                                 @Override  
  236.                                 public void run() {  
  237.                                     if (future.isSuccess()) {  
  238.                                         if (!f.trySuccess(c)) {  
  239.                                             c.closeAsync();  
  240.                                         }  
  241.                                     } else {  
  242.                                         f.tryFailure(future.cause());  
  243.                                         c.closeAsync();  
  244.                                     }  
  245.                                 }  
  246.                             });  
  247.                         }  
  248.                     });  
  249.                 } else {  
  250.                     bootstrap.config().group().execute(new Runnable() {  
  251.                         public void run() {  
  252.                             f.tryFailure(future.cause());  
  253.                         }  
  254.                     });  
  255.                 }  
  256.             }  
  257.         });  
  258.         return f;  
  259.     }  
  260.   
  261.     //關閉netty網絡連接  
  262.     public void shutdown() {  
  263.         shutdownAsync().syncUninterruptibly();  
  264.         if (hasOwnGroup) {  
  265.             timer.stop();  
  266.             executor.shutdown();  
  267.             try {  
  268.                 executor.awaitTermination(15, TimeUnit.SECONDS);  
  269.             } catch (InterruptedException e) {  
  270.                 Thread.currentThread().interrupt();  
  271.             }  
  272.             bootstrap.config().group().shutdownGracefully();  
  273.               
  274.         }  
  275.     }  
  276.   
  277.     //異步關閉netty網絡連接  
  278.     public ChannelGroupFuture shutdownAsync() {  
  279.         for (Channel channel : channels) {  
  280.             RedisConnection connection = RedisConnection.getFrom(channel);  
  281.             if (connection != null) {  
  282.                 connection.setClosed(true);  
  283.             }  
  284.         }  
  285.         return channels.close();  
  286.     }  
  287.   
  288.     @Override  
  289.     public String toString() {  
  290.         return "[addr=" + addr + "]";  
  291.     }  
  292. }  
       上面就是Redisson利用java網絡編程框架netty連接redis的全過程,如果你對netty比較熟悉,閱讀上面的代碼應該不是問題。

轉載地址:http://aperise.iteye.com/blog/2400528

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章