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操作代碼如下:
- Config config = new Config();// 創建配置
- config.useMasterSlaveServers() // 指定使用主從部署方式
- //.setReadMode(ReadMode.SLAVE) 默認值SLAVE,讀操作只在從節點進行
- //.setSubscriptionMode(SubscriptionMode.SLAVE) 默認值SLAVE,訂閱操作只在從節點進行
- //.setMasterConnectionMinimumIdleSize(10) 默認值10,針對每個master節點初始化10個連接
- //.setMasterConnectionPoolSize(64) 默認值64,針對每個master節點初始化10個連接,最大可以擴展至64個連接
- //.setSlaveConnectionMinimumIdleSize(10) 默認值10,針對每個slave節點初始化10個連接
- //.setSlaveConnectionPoolSize(64) 默認值,針對每個slave節點初始化10個連接,最大可以擴展至64個連接
- //.setSubscriptionConnectionMinimumIdleSize(1) 默認值1,在SubscriptionMode=SLAVE時候,針對每個slave節點初始化1個連接
- //.setSubscriptionConnectionPoolSize(50) 默認值50,在SubscriptionMode=SLAVE時候,針對每個slave節點初始化1個連接,最大可以擴展至50個連接
- .setMasterAddress("redis://192.168.29.24:6379") // 設置redis主節點
- .addSlaveAddress("redis://192.168.29.24:7000") // 設置redis從節點
- .addSlaveAddress("redis://192.168.29.24:7001"); // 設置redis從節點
- RedissonClient redisson = Redisson.create(config);// 創建客戶端(發現這一操作非常耗時,基本在2秒-4秒左右)
上面代碼執行完畢後,如果在redis服務端所在服務器執行以下linux命令:
- #6379上建立了10個連接
- netstat -ant |grep 6379|grep ESTABLISHED
- #7000上建立了11個連接
- netstat -ant |grep 7000|grep ESTABLISHED
- #7001上建立了11個連接
- 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的代碼註釋如下:
- /**
- * 根據配置Config創建redisson操作類RedissonClient
- * @param config for Redisson
- * @return Redisson instance
- */
- public static RedissonClient create(Config config) {
- //調用構造方法
- Redisson redisson = new Redisson(config);
- if (config.isRedissonReferenceEnabled()) {
- redisson.enableRedissonReferenceSupport();
- }
- return redisson;
- }
- /**
- * Redisson構造方法
- * @param config for Redisson
- * @return Redisson instance
- */
- protected Redisson(Config config) {
- //賦值變量config
- this.config = config;
- //產生一份對於傳入config的備份
- Config configCopy = new Config(config);
- //根據配置config的類型(主從模式、單機模式、哨兵模式、集羣模式、亞馬遜雲模式、微軟雲模式)而進行不同的初始化
- connectionManager = ConfigSupport.createConnectionManager(configCopy);
- //連接池對象回收調度器
- evictionScheduler = new EvictionScheduler(connectionManager.getCommandExecutor());
- //Redisson的對象編碼類
- codecProvider = configCopy.getCodecProvider();
- //Redisson的ResolverProvider,默認爲org.redisson.liveobject.provider.DefaultResolverProvider
- resolverProvider = configCopy.getResolverProvider();
- }
其中與連接池相關的就是ConnectionManager,ConnectionManager的初始化轉交工具類ConfigSupport.java進行,ConfigSupport.java會根據部署方式(主從模式、單機模式、哨兵模式、集羣模式、亞馬遜雲模式、微軟雲模式)的不同而分別進行。
2.2 ConfigSupport.java
這裏現將ConfigSupport.java創建ConnectionManager的核心代碼註釋如下:
- /**
- * 據配置config的類型(主從模式、單機模式、哨兵模式、集羣模式、亞馬遜雲模式、微軟雲模式)而進行不同的初始化
- * @param configCopy for Redisson
- * @return ConnectionManager instance
- */
- public static ConnectionManager createConnectionManager(Config configCopy) {
- if (configCopy.getMasterSlaveServersConfig() != null) {//配置configCopy類型爲主從模式
- validate(configCopy.getMasterSlaveServersConfig());
- return new MasterSlaveConnectionManager(configCopy.getMasterSlaveServersConfig(), configCopy);
- } else if (configCopy.getSingleServerConfig() != null) {//配置configCopy類型爲單機模式
- validate(configCopy.getSingleServerConfig());
- return new SingleConnectionManager(configCopy.getSingleServerConfig(), configCopy);
- } else if (configCopy.getSentinelServersConfig() != null) {//配置configCopy類型爲哨兵模式
- validate(configCopy.getSentinelServersConfig());
- return new SentinelConnectionManager(configCopy.getSentinelServersConfig(), configCopy);
- } else if (configCopy.getClusterServersConfig() != null) {//配置configCopy類型爲集羣模式
- validate(configCopy.getClusterServersConfig());
- return new ClusterConnectionManager(configCopy.getClusterServersConfig(), configCopy);
- } else if (configCopy.getElasticacheServersConfig() != null) {//配置configCopy類型爲亞馬遜雲模式
- validate(configCopy.getElasticacheServersConfig());
- return new ElasticacheConnectionManager(configCopy.getElasticacheServersConfig(), configCopy);
- } else if (configCopy.getReplicatedServersConfig() != null) {//配置configCopy類型爲微軟雲模式
- validate(configCopy.getReplicatedServersConfig());
- return new ReplicatedConnectionManager(configCopy.getReplicatedServersConfig(), configCopy);
- } else if (configCopy.getConnectionManager() != null) {//直接返回configCopy自帶的默認ConnectionManager
- return configCopy.getConnectionManager();
- }else {
- throw new IllegalArgumentException("server(s) address(es) not defined!");
- }
- }
上面可以看到根據傳入的配置Config.java的不同,會分別創建不同的ConnectionManager的實現類。
2.3 MasterSlaveConnectionManager.java
這裏開始介紹ConnectionManager,ConnectionManager.java是一個接口類,它有6個實現類,分別對應着不同的部署模式(主從模式、單機模式、哨兵模式、集羣模式、亞馬遜雲模式、微軟雲模式),如下如所示:
這裏以主從部署方式進行講解,先通過一張圖瞭解MasterSlaveConnectionManager的組成:
上圖中最終要的組件要數MasterSlaveEntry,在後面即將進行介紹,這裏註釋MasterSlaveConnectionManager.java的核心代碼如下:
- /**
- * MasterSlaveConnectionManager的構造方法
- * @param cfg for MasterSlaveServersConfig
- * @param config for Config
- */
- public MasterSlaveConnectionManager(MasterSlaveServersConfig cfg, Config config) {
- //調用構造方法
- this(config);
- //
- initTimer(cfg);
- this.config = cfg;
- //初始化MasterSlaveEntry
- initSingleEntry();
- }
- /**
- * MasterSlaveConnectionManager的構造方法
- * @param cfg for Config
- */
- public MasterSlaveConnectionManager(Config cfg) {
- //讀取redisson的jar中的文件META-INF/MANIFEST.MF,打印出Bundle-Version對應的Redisson版本信息
- Version.logVersion();
- //EPOLL是linux的多路複用IO模型的增強版本,這裏如果啓用EPOLL,就讓redisson底層netty使用EPOLL的方式,否則配置netty裏的NIO非阻塞方式
- if (cfg.isUseLinuxNativeEpoll()) {
- if (cfg.getEventLoopGroup() == null) {
- //使用linux IO非阻塞模型EPOLL
- this.group = new EpollEventLoopGroup(cfg.getNettyThreads(), new DefaultThreadFactory("redisson-netty"));
- } else {
- this.group = cfg.getEventLoopGroup();
- }
- this.socketChannelClass = EpollSocketChannel.class;
- } else {
- if (cfg.getEventLoopGroup() == null) {
- //使用linux IO非阻塞模型NIO
- this.group = new NioEventLoopGroup(cfg.getNettyThreads(), new DefaultThreadFactory("redisson-netty"));
- } else {
- this.group = cfg.getEventLoopGroup();
- }
- this.socketChannelClass = NioSocketChannel.class;
- }
- if (cfg.getExecutor() == null) {
- //線程池大小,對於2U 2CPU 8cores/cpu,意思是有2塊板子,每個板子上8個物理CPU,那麼總計物理CPU個數爲16
- //對於linux有個超線程概念,意思是每個物理CPU可以虛擬出2個邏輯CPU,那麼總計邏輯CPU個數爲32
- //這裏Runtime.getRuntime().availableProcessors()取的是邏輯CPU的個數,所以這裏線程池大小會是64
- int threads = Runtime.getRuntime().availableProcessors() * 2;
- if (cfg.getThreads() != 0) {
- threads = cfg.getThreads();
- }
- executor = Executors.newFixedThreadPool(threads, new DefaultThreadFactory("redisson"));
- } else {
- executor = cfg.getExecutor();
- }
- this.cfg = cfg;
- this.codec = cfg.getCodec();
- //一個可以獲取異步執行任務返回值的回調對象,本質是對於java的Future的實現,監控MasterSlaveConnectionManager的shutdown進行一些必要的處理
- this.shutdownPromise = newPromise();
- //一個持有MasterSlaveConnectionManager的異步執行服務
- this.commandExecutor = new CommandSyncService(this);
- }
- /**
- * 初始化定時調度器
- * @param config for MasterSlaveServersConfig
- */
- protected void initTimer(MasterSlaveServersConfig config) {
- //讀取超時時間配置信息
- int[] timeouts = new int[]{config.getRetryInterval(), config.getTimeout(), config.getReconnectionTimeout()};
- Arrays.sort(timeouts);
- int minTimeout = timeouts[0];
- //設置默認超時時間
- if (minTimeout % 100 != 0) {
- minTimeout = (minTimeout % 100) / 2;
- } else if (minTimeout == 100) {
- minTimeout = 50;
- } else {
- minTimeout = 100;
- }
- //創建定時調度器
- timer = new HashedWheelTimer(Executors.defaultThreadFactory(), minTimeout, TimeUnit.MILLISECONDS, 1024);
- // to avoid assertion error during timer.stop invocation
- try {
- Field leakField = HashedWheelTimer.class.getDeclaredField("leak");
- leakField.setAccessible(true);
- leakField.set(timer, null);
- } catch (Exception e) {
- throw new IllegalStateException(e);
- }
- //檢測MasterSlaveConnectionManager的空閒連接的監視器IdleConnectionWatcher,會清理不用的空閒的池中連接對象
- connectionWatcher = new IdleConnectionWatcher(this, config);
- }
- /**
- * 創建MasterSlaveConnectionManager的MasterSlaveEntry
- */
- protected void initSingleEntry() {
- try {
- //主從模式下0~16383加入到集合slots
- HashSet<ClusterSlotRange> slots = new HashSet<ClusterSlotRange>();
- slots.add(singleSlotRange);
- MasterSlaveEntry entry;
- if (config.checkSkipSlavesInit()) {//ReadMode不爲MASTER並且SubscriptionMode不爲MASTER才執行
- entry = new SingleEntry(slots, this, config);
- RFuture<Void> f = entry.setupMasterEntry(config.getMasterAddress());
- f.syncUninterruptibly();
- } else {//默認主從部署ReadMode=SLAVE,SubscriptionMode=SLAVE,這裏會執行
- entry = createMasterSlaveEntry(config, slots);
- }
- //將每個分片0~16383都指向創建的MasterSlaveEntry
- for (int slot = singleSlotRange.getStartSlot(); slot < singleSlotRange.getEndSlot() + 1; slot++) {
- addEntry(slot, entry);
- }
- //DNS相關
- if (config.getDnsMonitoringInterval() != -1) {
- dnsMonitor = new DNSMonitor(this, Collections.singleton(config.getMasterAddress()),
- config.getSlaveAddresses(), config.getDnsMonitoringInterval());
- dnsMonitor.start();
- }
- } catch (RuntimeException e) {
- stopThreads();
- throw e;
- }
- }
- /**
- * MasterSlaveEntry的構造方法
- * @param config for MasterSlaveServersConfig
- * @param slots for HashSet<ClusterSlotRange>
- * @return MasterSlaveEntry
- */
- protected MasterSlaveEntry createMasterSlaveEntry(MasterSlaveServersConfig config, HashSet<ClusterSlotRange> slots) {
- //創建MasterSlaveEntry
- MasterSlaveEntry entry = new MasterSlaveEntry(slots, this, config);
- //從節點連接池SlaveConnectionPool和PubSubConnectionPool的默認的最小連接數初始化
- List<RFuture<Void>> fs = entry.initSlaveBalancer(java.util.Collections.<URI>emptySet());
- for (RFuture<Void> future : fs) {
- future.syncUninterruptibly();
- }
- 主節點連接池MasterConnectionPool和MasterPubSubConnectionPool的默認的最小連接數初始化
- RFuture<Void> f = entry.setupMasterEntry(config.getMasterAddress());
- f.syncUninterruptibly();
- return entry;
- }
上面個人覺得有兩處代碼值得我們特別關注,特別說明如下:
- entry.initSlaveBalancer:從節點連接池SlaveConnectionPool和PubSubConnectionPool的默認的最小連接數初始化。
- entry.setupMasterEntry:主節點連接池MasterConnectionPool和MasterPubSubConnectionPool的默認的最小連接數初始化。
2.4 MasterSlaveEntry.java
用一張圖來解釋MasterSlaveEntry的組件如下:
MasterSlaveEntry.java里正是我們一直在尋找着的四個連接池MasterConnectionPool、MasterPubSubConnectionPool、SlaveConnectionPool和PubSubConnectionPool,這裏註釋MasterSlaveEntry.java的核心代碼如下:
- /**
- * MasterSlaveEntry的構造方法
- * @param slotRanges for Set<ClusterSlotRange>
- * @param connectionManager for ConnectionManager
- * @param config for MasterSlaveServersConfig
- */
- public MasterSlaveEntry(Set<ClusterSlotRange> slotRanges, ConnectionManager connectionManager, MasterSlaveServersConfig config) {
- //主從模式下0~16383加入到集合slots
- for (ClusterSlotRange clusterSlotRange : slotRanges) {
- for (int i = clusterSlotRange.getStartSlot(); i < clusterSlotRange.getEndSlot() + 1; i++) {
- slots.add(i);
- }
- }
- //賦值MasterSlaveConnectionManager給connectionManager
- this.connectionManager = connectionManager;
- //賦值config
- this.config = config;
- //創建LoadBalancerManager
- //其實LoadBalancerManager裏持有者從節點的SlaveConnectionPool和PubSubConnectionPool
- //並且此時連接池裏還沒有初始化默認的最小連接數
- slaveBalancer = new LoadBalancerManager(config, connectionManager, this);
- //創建主節點連接池MasterConnectionPool,此時連接池裏還沒有初始化默認的最小連接數
- writeConnectionHolder = new MasterConnectionPool(config, connectionManager, this);
- //創建主節點連接池MasterPubSubConnectionPool,此時連接池裏還沒有初始化默認的最小連接數
- pubSubConnectionHolder = new MasterPubSubConnectionPool(config, connectionManager, this);
- }
- /**
- * 從節點連接池SlaveConnectionPool和PubSubConnectionPool的默認的最小連接數初始化
- * @param disconnectedNodes for Collection<URI>
- * @return List<RFuture<Void>>
- */
- public List<RFuture<Void>> initSlaveBalancer(Collection<URI> disconnectedNodes) {
- //這裏freezeMasterAsSlave=true
- boolean freezeMasterAsSlave = !config.getSlaveAddresses().isEmpty() && !config.checkSkipSlavesInit() && disconnectedNodes.size() < config.getSlaveAddresses().size();
- List<RFuture<Void>> result = new LinkedList<RFuture<Void>>();
- //把主節點當作從節點處理,因爲默認ReadMode=ReadMode.SLAVE,所以這裏不會添加針對該節點的連接池
- RFuture<Void> f = addSlave(config.getMasterAddress(), freezeMasterAsSlave, NodeType.MASTER);
- result.add(f);
- //讀取從節點的地址信息,然後針對每個從節點地址創建SlaveConnectionPool和PubSubConnectionPool
- //SlaveConnectionPool【初始化10個RedisConnection,最大可以擴展至64個】
- //PubSubConnectionPool【初始化1個RedisPubSubConnection,最大可以擴展至50個】
- for (URI address : config.getSlaveAddresses()) {
- f = addSlave(address, disconnectedNodes.contains(address), NodeType.SLAVE);
- result.add(f);
- }
- return result;
- }
- /**
- * 從節點連接池SlaveConnectionPool和PubSubConnectionPool的默認的最小連接數初始化
- * @param address for URI
- * @param freezed for boolean
- * @param nodeType for NodeType
- * @return RFuture<Void>
- */
- private RFuture<Void> addSlave(URI address, boolean freezed, NodeType nodeType) {
- //創建到從節點的連接RedisClient
- RedisClient client = connectionManager.createClient(NodeType.SLAVE, address);
- ClientConnectionsEntry entry = new ClientConnectionsEntry(client,
- this.config.getSlaveConnectionMinimumIdleSize(),
- this.config.getSlaveConnectionPoolSize(),
- this.config.getSubscriptionConnectionMinimumIdleSize(),
- this.config.getSubscriptionConnectionPoolSize(), connectionManager, nodeType);
- //默認只有主節點當作從節點是會設置freezed=true
- if (freezed) {
- synchronized (entry) {
- entry.setFreezed(freezed);
- entry.setFreezeReason(FreezeReason.SYSTEM);
- }
- }
- //調用slaveBalancer來對從節點連接池SlaveConnectionPool和PubSubConnectionPool的默認的最小連接數初始化
- return slaveBalancer.add(entry);
- }
- /**
- * 主節點連接池MasterConnectionPool和MasterPubSubConnectionPool的默認的最小連接數初始化
- * @param address for URI
- * @return RFuture<Void>
- */
- public RFuture<Void> setupMasterEntry(URI address) {
- //創建到主節點的連接RedisClient
- RedisClient client = connectionManager.createClient(NodeType.MASTER, address);
- masterEntry = new ClientConnectionsEntry(
- client,
- config.getMasterConnectionMinimumIdleSize(),
- config.getMasterConnectionPoolSize(),
- config.getSubscriptionConnectionMinimumIdleSize(),
- config.getSubscriptionConnectionPoolSize(),
- connectionManager,
- NodeType.MASTER);
- //如果配置的SubscriptionMode=SubscriptionMode.MASTER就初始化MasterPubSubConnectionPool
- //默認SubscriptionMode=SubscriptionMode.SLAVE,MasterPubSubConnectionPool這裏不會初始化最小連接數
- if (config.getSubscriptionMode() == SubscriptionMode.MASTER) {
- //MasterPubSubConnectionPool【初始化1個RedisPubSubConnection,最大可以擴展至50個】
- RFuture<Void> f = writeConnectionHolder.add(masterEntry);
- RFuture<Void> s = pubSubConnectionHolder.add(masterEntry);
- return CountListener.create(s, f);
- }
- //調用MasterConnectionPool使得連接池MasterConnectionPool裏的對象最小個數爲10個
- //MasterConnectionPool【初始化10個RedisConnection,最大可以擴展至64個】
- return writeConnectionHolder.add(masterEntry);
- }
- 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裏面有着從節點相關的兩個重要的連接池SlaveConnectionPool和PubSubConnectionPool,這裏註釋LoadBalancerManager.java的核心代碼如下:
- /**
- * LoadBalancerManager的構造方法
- * @param config for MasterSlaveServersConfig
- * @param connectionManager for ConnectionManager
- * @param entry for MasterSlaveEntry
- */
- public LoadBalancerManager(MasterSlaveServersConfig config, ConnectionManager connectionManager, MasterSlaveEntry entry) {
- //賦值connectionManager
- this.connectionManager = connectionManager;
- //創建連接池SlaveConnectionPool
- slaveConnectionPool = new SlaveConnectionPool(config, connectionManager, entry);
- //創建連接池PubSubConnectionPool
- pubSubConnectionPool = new PubSubConnectionPool(config, connectionManager, entry);
- }
- /**
- * LoadBalancerManager的連接池SlaveConnectionPool和PubSubConnectionPool裏池化對象添加方法,也即池中需要對象時,調用此方法添加
- * @param entry for ClientConnectionsEntry
- * @return RFuture<Void>
- */
- public RFuture<Void> add(final ClientConnectionsEntry entry) {
- final RPromise<Void> result = connectionManager.newPromise();
- //創建一個回調監聽器,在池中對象創建失敗時進行2次莫仍嘗試
- FutureListener<Void> listener = new FutureListener<Void>() {
- AtomicInteger counter = new AtomicInteger(2);
- @Override
- public void operationComplete(Future<Void> future) throws Exception {
- if (!future.isSuccess()) {
- result.tryFailure(future.cause());
- return;
- }
- if (counter.decrementAndGet() == 0) {
- String addr = entry.getClient().getIpAddr();
- ip2Entry.put(addr, entry);
- result.trySuccess(null);
- }
- }
- };
- //調用slaveConnectionPool添加RedisConnection對象到池中
- RFuture<Void> slaveFuture = slaveConnectionPool.add(entry);
- slaveFuture.addListener(listener);
- //調用pubSubConnectionPool添加RedisPubSubConnection對象到池中
- RFuture<Void> pubSubFuture = pubSubConnectionPool.add(entry);
- pubSubFuture.addListener(listener);
- return result;
- }
至此,我們已經瞭解了開篇提到的四個連接池是在哪裏創建的。
3. Redisson的4類連接池
這裏我們來詳細介紹下Redisson的連接池實現類,Redisson裏有4種連接池,它們是MasterConnectionPool、MasterPubSubConnectionPool、SlaveConnectionPool和PubSubConnectionPool,它們的父類都是ConnectionPool,其類繼承關係圖如下:
通過上圖我們瞭解了ConnectionPool類的繼承關係圖,再來一張圖來了解下ConnectionPool.java類的組成,如下:
好了,再來圖就有點囉嗦了,註釋ConnectionPool.java代碼如下:
- abstract class ConnectionPool<T extends RedisConnection> {
- private final Logger log = LoggerFactory.getLogger(getClass());
- //維持着連接池對應的redis節點信息
- //比如1主2從部署MasterConnectionPool裏的entries只有一個主節點(192.168.29.24 6379)
- //比如1主2從部署MasterPubSubConnectionPool裏的entries爲空,因爲SubscriptionMode=SubscriptionMode.SLAVE
- //比如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個從節點全部宕機才參與讀操作)
- //比如1主2從部署PubSubConnectionPool裏的entries有2個節點(192.168.29.24 7000,192.168.29.24 7001),因爲SubscriptionMode=SubscriptionMode.SLAVE,主節點不會加入
- protected final List<ClientConnectionsEntry> entries = new CopyOnWriteArrayList<ClientConnectionsEntry>();
- //持有者RedissonClient的組件ConnectionManager
- final ConnectionManager connectionManager;
- //持有者RedissonClient的組件ConnectionManager裏的MasterSlaveServersConfig
- final MasterSlaveServersConfig config;
- //持有者RedissonClient的組件ConnectionManager裏的MasterSlaveEntry
- final MasterSlaveEntry masterSlaveEntry;
- //構造函數
- public ConnectionPool(MasterSlaveServersConfig config, ConnectionManager connectionManager, MasterSlaveEntry masterSlaveEntry) {
- this.config = config;
- this.masterSlaveEntry = masterSlaveEntry;
- this.connectionManager = connectionManager;
- }
- //連接池中需要增加對象時候調用此方法
- public RFuture<Void> add(final ClientConnectionsEntry entry) {
- final RPromise<Void> promise = connectionManager.newPromise();
- promise.addListener(new FutureListener<Void>() {
- @Override
- public void operationComplete(Future<Void> future) throws Exception {
- entries.add(entry);
- }
- });
- initConnections(entry, promise, true);
- return promise;
- }
- //初始化連接池中最小連接數
- private void initConnections(final ClientConnectionsEntry entry, final RPromise<Void> initPromise, boolean checkFreezed) {
- final int minimumIdleSize = getMinimumIdleSize(entry);
- if (minimumIdleSize == 0 || (checkFreezed && entry.isFreezed())) {
- initPromise.trySuccess(null);
- return;
- }
- final AtomicInteger initializedConnections = new AtomicInteger(minimumIdleSize);
- int startAmount = Math.min(50, minimumIdleSize);
- final AtomicInteger requests = new AtomicInteger(startAmount);
- for (int i = 0; i < startAmount; i++) {
- createConnection(checkFreezed, requests, entry, initPromise, minimumIdleSize, initializedConnections);
- }
- }
- //創建連接對象到連接池中
- private void createConnection(final boolean checkFreezed, final AtomicInteger requests, final ClientConnectionsEntry entry, final RPromise<Void> initPromise,
- final int minimumIdleSize, final AtomicInteger initializedConnections) {
- if ((checkFreezed && entry.isFreezed()) || !tryAcquireConnection(entry)) {
- int totalInitializedConnections = minimumIdleSize - initializedConnections.get();
- Throwable cause = new RedisConnectionException(
- "Unable to init enough connections amount! Only " + totalInitializedConnections + " from " + minimumIdleSize + " were initialized. Server: "
- + entry.getClient().getAddr());
- initPromise.tryFailure(cause);
- return;
- }
- acquireConnection(entry, new Runnable() {
- @Override
- public void run() {
- RPromise<T> promise = connectionManager.newPromise();
- createConnection(entry, promise);
- promise.addListener(new FutureListener<T>() {
- @Override
- public void operationComplete(Future<T> future) throws Exception {
- if (future.isSuccess()) {
- T conn = future.getNow();
- releaseConnection(entry, conn);
- }
- releaseConnection(entry);
- if (!future.isSuccess()) {
- int totalInitializedConnections = minimumIdleSize - initializedConnections.get();
- String errorMsg;
- if (totalInitializedConnections == 0) {
- errorMsg = "Unable to connect to Redis server: " + entry.getClient().getAddr();
- } else {
- errorMsg = "Unable to init enough connections amount! Only " + totalInitializedConnections
- + " from " + minimumIdleSize + " were initialized. Redis server: " + entry.getClient().getAddr();
- }
- Throwable cause = new RedisConnectionException(errorMsg, future.cause());
- initPromise.tryFailure(cause);
- return;
- }
- int value = initializedConnections.decrementAndGet();
- if (value == 0) {
- log.info("{} connections initialized for {}", minimumIdleSize, entry.getClient().getAddr());
- if (!initPromise.trySuccess(null)) {
- throw new IllegalStateException();
- }
- } else if (value > 0 && !initPromise.isDone()) {
- if (requests.incrementAndGet() <= minimumIdleSize) {
- createConnection(checkFreezed, requests, entry, initPromise, minimumIdleSize, initializedConnections);
- }
- }
- }
- });
- }
- });
- }
- //連接池中租借出連接對象
- public RFuture<T> get(RedisCommand<?> command) {
- for (int j = entries.size() - 1; j >= 0; j--) {
- final ClientConnectionsEntry entry = getEntry();
- if (!entry.isFreezed()
- && tryAcquireConnection(entry)) {
- return acquireConnection(command, entry);
- }
- }
- List<InetSocketAddress> failedAttempts = new LinkedList<InetSocketAddress>();
- List<InetSocketAddress> freezed = new LinkedList<InetSocketAddress>();
- for (ClientConnectionsEntry entry : entries) {
- if (entry.isFreezed()) {
- freezed.add(entry.getClient().getAddr());
- } else {
- failedAttempts.add(entry.getClient().getAddr());
- }
- }
- StringBuilder errorMsg = new StringBuilder(getClass().getSimpleName() + " no available Redis entries. ");
- if (!freezed.isEmpty()) {
- errorMsg.append(" Disconnected hosts: " + freezed);
- }
- if (!failedAttempts.isEmpty()) {
- errorMsg.append(" Hosts disconnected due to `failedAttempts` limit reached: " + failedAttempts);
- }
- RedisConnectionException exception = new RedisConnectionException(errorMsg.toString());
- return connectionManager.newFailedFuture(exception);
- }
- //連接池中租借出連接對象執行操作RedisCommand
- public RFuture<T> get(RedisCommand<?> command, ClientConnectionsEntry entry) {
- if ((!entry.isFreezed() || entry.getFreezeReason() == FreezeReason.SYSTEM) &&
- tryAcquireConnection(entry)) {
- return acquireConnection(command, entry);
- }
- RedisConnectionException exception = new RedisConnectionException(
- "Can't aquire connection to " + entry);
- return connectionManager.newFailedFuture(exception);
- }
- //通過向redis服務端發送PING看是否返回PONG來檢測連接
- private void ping(RedisConnection c, final FutureListener<String> pingListener) {
- RFuture<String> f = c.async(RedisCommands.PING);
- f.addListener(pingListener);
- }
- //歸還連接對象到連接池
- public void returnConnection(ClientConnectionsEntry entry, T connection) {
- if (entry.isFreezed()) {
- connection.closeAsync();
- } else {
- releaseConnection(entry, connection);
- }
- releaseConnection(entry);
- }
- //釋放連接池中連接對象
- protected void releaseConnection(ClientConnectionsEntry entry) {
- entry.releaseConnection();
- }
- //釋放連接池中連接對象
- protected void releaseConnection(ClientConnectionsEntry entry, T conn) {
- entry.releaseConnection(conn);
- }
- }
用一張圖來解釋ConnectionPool幹了些啥,如下圖:
都到這裏了,不介意再送一張圖瞭解各種部署方式下的連接池分佈了,如下圖:
4.Redisson的讀寫操作句柄類RedissonObject
對於Redisson的任何操作,都需要獲取到操作句柄類RedissonObject,RedissonObject根據不同的數據類型有不同的RedissonObject實現類,RedissonObject的類繼承關係圖如下:
例如想設置redis服務端的key=key的值value=123,你需要查詢Redis命令和Redisson對象匹配列表,找到如下對應關係:
然後我們就知道調用代碼這麼寫:
- Config config = new Config();// 創建配置
- config.useMasterSlaveServers() // 指定使用主從部署方式
- .setMasterAddress("redis://192.168.29.24:6379") // 設置redis主節點
- .addSlaveAddress("redis://192.168.29.24:7000") // 設置redis從節點
- .addSlaveAddress("redis://192.168.29.24:7001"); // 設置redis從節點
- RedissonClient redisson = Redisson.create(config);// 創建客戶端(發現這一操作非常耗時,基本在2秒-4秒左右)
- //任何Redisson操作首先需要獲取對應的操作句柄
- //RBucket是操作句柄之一,實現類是RedissonBucket
- RBucket<String> rBucket = redissonClient.getBucket("key");
- //通過操作句柄rBucket進行讀操作
- rBucket.get();
- //通過操作句柄rBucket進行寫操作
- rBucket.set("123");
至於其它的redis命令對應的redisson操作對象,都可以官網的Redis命令和Redisson對象匹配列表 查到。
6.Redisson的讀寫操作源碼分析
從一個讀操作的代碼作爲入口分析代碼,如下:
- //任何Redisson操作首先需要獲取對應的操作句柄,RBucket是操作句柄之一,實現類是RedissonBucket
- RBucket<String> rBucket = redissonClient.getBucket("key");
- //通過操作句柄rBucket進行讀操作
- rBucket.get();
上面我們看到不管是讀操作還是寫操作都轉交CommandAsyncExecutor進行處理,那麼這裏我們需要看一下CommandAsyncExecutor.java裏關於讀寫操作處理的核心代碼,註釋代碼如下:
- private NodeSource getNodeSource(String key) {
- //通過公式CRC16.crc16(key.getBytes()) % MAX_SLOT
- //計算出一個字符串key對應的分片在0~16383中哪個分片
- int slot = connectionManager.calcSlot(key);
- //之前已經將0~16383每個分片對應到唯一的一個MasterSlaveEntry,這裏取出來
- MasterSlaveEntry entry = connectionManager.getEntry(slot);
- //這裏將MasterSlaveEntry包裝成NodeSource【slot=null,addr=null,redirect=null,entry=MasterSlaveEntry】
- return new NodeSource(entry);
- }
- @Override
- public <T, R> RFuture<R> readAsync(String key, Codec codec, RedisCommand<T> command, Object... params) {
- RPromise<R> mainPromise = connectionManager.newPromise();
- //獲取NodeSource【slot=null,addr=null,redirect=null,entry=MasterSlaveEntry】
- NodeSource source = getNodeSource(key);
- 調用異步執行方法async
- async(true, source, codec, command, params, mainPromise, 0);
- return mainPromise;
- }
- protected <V, R> void async(final boolean readOnlyMode, final NodeSource source, final Codec codec,
- final RedisCommand<V> command, final Object[] params, final RPromise<R> mainPromise, final int attempt) {
- //操作被取消,那麼直接返回
- if (mainPromise.isCancelled()) {
- free(params);
- return;
- }
- //連接管理器無法連接,釋放參數所佔資源,然後返回
- if (!connectionManager.getShutdownLatch().acquire()) {
- free(params);
- mainPromise.tryFailure(new RedissonShutdownException("Redisson is shutdown"));
- return;
- }
- final AsyncDetails<V, R> details = AsyncDetails.acquire();
- if (isRedissonReferenceSupportEnabled()) {
- try {
- for (int i = 0; i < params.length; i++) {
- RedissonReference reference = RedissonObjectFactory.toReference(getConnectionManager().getCfg(), params[i]);
- if (reference != null) {
- params[i] = reference;
- }
- }
- } catch (Exception e) {
- connectionManager.getShutdownLatch().release();
- free(params);
- mainPromise.tryFailure(e);
- return;
- }
- }
- //開始從connectionManager獲取池中的連接
- //這裏採用異步方式,創建一個RFuture對象,等待池中連接,一旦獲得連接,然後進行讀和寫操作
- final RFuture<RedisConnection> connectionFuture;
- if (readOnlyMode) {//對於讀操作默認readOnlyMode=true,這裏會執行
- connectionFuture = connectionManager.connectionReadOp(source, command);
- } else {//對於寫操作默認readOnlyMode=false,這裏會執行
- connectionFuture = connectionManager.connectionWriteOp(source, command);
- }
- //創建RPromise,用於操作失敗時候重試
- final RPromise<R> attemptPromise = connectionManager.newPromise();
- details.init(connectionFuture, attemptPromise, readOnlyMode, source, codec, command, params, mainPromise, attempt);
- //創建FutureListener,監測外部請求是否已經取消了之前提交的讀寫操作,如果取消了,那麼就讓正在執行的讀寫操作停止
- FutureListener<R> mainPromiseListener = new FutureListener<R>() {
- @Override
- public void operationComplete(Future<R> future) throws Exception {
- if (future.isCancelled() && connectionFuture.cancel(false)) {
- log.debug("Connection obtaining canceled for {}", command);
- details.getTimeout().cancel();
- if (details.getAttemptPromise().cancel(false)) {
- free(params);
- }
- }
- }
- };
- //創建TimerTask,用於操作失敗後通過定時器進行操作重試
- final TimerTask retryTimerTask = new TimerTask() {
- @Override
- public void run(Timeout t) throws Exception {
- if (details.getAttemptPromise().isDone()) {
- return;
- }
- if (details.getConnectionFuture().cancel(false)) {
- connectionManager.getShutdownLatch().release();
- } else {
- if (details.getConnectionFuture().isSuccess()) {
- if (details.getWriteFuture() == null || !details.getWriteFuture().isDone()) {
- if (details.getAttempt() == connectionManager.getConfig().getRetryAttempts()) {
- if (details.getWriteFuture().cancel(false)) {
- if (details.getException() == null) {
- details.setException(new RedisTimeoutException("Unable to send command: " + command + " with params: " + LogHelper.toString(details.getParams()) + " after " + connectionManager.getConfig().getRetryAttempts() + " retry attempts"));
- }
- details.getAttemptPromise().tryFailure(details.getException());
- }
- return;
- }
- details.incAttempt();
- Timeout timeout = connectionManager.newTimeout(this, connectionManager.getConfig().getRetryInterval(), TimeUnit.MILLISECONDS);
- details.setTimeout(timeout);
- return;
- }
- if (details.getWriteFuture().isDone() && details.getWriteFuture().isSuccess()) {
- return;
- }
- }
- }
- if (details.getMainPromise().isCancelled()) {
- if (details.getAttemptPromise().cancel(false)) {
- free(details);
- AsyncDetails.release(details);
- }
- return;
- }
- if (details.getAttempt() == connectionManager.getConfig().getRetryAttempts()) {
- if (details.getException() == null) {
- details.setException(new RedisTimeoutException("Unable to send command: " + command + " with params: " + LogHelper.toString(details.getParams() + " after " + connectionManager.getConfig().getRetryAttempts() + " retry attempts")));
- }
- details.getAttemptPromise().tryFailure(details.getException());
- return;
- }
- if (!details.getAttemptPromise().cancel(false)) {
- return;
- }
- int count = details.getAttempt() + 1;
- if (log.isDebugEnabled()) {
- log.debug("attempt {} for command {} and params {}",
- count, details.getCommand(), Arrays.toString(details.getParams()));
- }
- details.removeMainPromiseListener();
- async(details.isReadOnlyMode(), details.getSource(), details.getCodec(), details.getCommand(), details.getParams(), details.getMainPromise(), count);
- AsyncDetails.release(details);
- }
- };
- //配置對於讀寫操作的超時時間
- Timeout timeout = connectionManager.newTimeout(retryTimerTask, connectionManager.getConfig().getRetryInterval(), TimeUnit.MILLISECONDS);
- details.setTimeout(timeout);
- details.setupMainPromiseListener(mainPromiseListener);
- //給connectionFuture增加監聽事件,當從連接池中獲取連接成功,成功的事件會被觸發,通知這裏執行後續讀寫動作
- connectionFuture.addListener(new FutureListener<RedisConnection>() {
- @Override
- public void operationComplete(Future<RedisConnection> connFuture) throws Exception {
- if (connFuture.isCancelled()) {//從池中獲取連接被取消,直接返回
- return;
- }
- if (!connFuture.isSuccess()) {//從池中獲取連接失敗
- connectionManager.getShutdownLatch().release();
- details.setException(convertException(connectionFuture));
- return;
- }
- if (details.getAttemptPromise().isDone() || details.getMainPromise().isDone()) {//從池中獲取連接失敗,並且嘗試了一定次數仍然失敗,默認嘗試次數爲0
- releaseConnection(source, connectionFuture, details.isReadOnlyMode(), details.getAttemptPromise(), details);
- return;
- }
- //從池中獲取連接成功,這裏取出連接對象RedisConnection
- final RedisConnection connection = connFuture.getNow();
- //如果需要重定向,這裏進行重定向
- //重定向的情況有:集羣模式對應的slot分佈在其他節點,就需要進行重定向
- if (details.getSource().getRedirect() == Redirect.ASK) {
- List<CommandData<?, ?>> list = new ArrayList<CommandData<?, ?>>(2);
- RPromise<Void> promise = connectionManager.newPromise();
- list.add(new CommandData<Void, Void>(promise, details.getCodec(), RedisCommands.ASKING, new Object[]{}));
- list.add(new CommandData<V, R>(details.getAttemptPromise(), details.getCodec(), details.getCommand(), details.getParams()));
- RPromise<Void> main = connectionManager.newPromise();
- ChannelFuture future = connection.send(new CommandsData(main, list));
- details.setWriteFuture(future);
- } else {
- if (log.isDebugEnabled()) {
- log.debug("acquired connection for command {} and params {} from slot {} using node {}... {}",
- details.getCommand(), Arrays.toString(details.getParams()), details.getSource(), connection.getRedisClient().getAddr(), connection);
- }
- //發送讀寫操作到RedisConnection,進行執行
- ChannelFuture future = connection.send(new CommandData<V, R>(details.getAttemptPromise(), details.getCodec(), details.getCommand(), details.getParams()));
- details.setWriteFuture(future);
- }
- //對於寫操作增加監聽事件回調,對寫操作是否成功,失敗原因進行日誌打印
- details.getWriteFuture().addListener(new ChannelFutureListener() {
- @Override
- public void operationComplete(ChannelFuture future) throws Exception {
- checkWriteFuture(details, connection);
- }
- });
- //返回RedisConnection連接到連接池
- releaseConnection(source, connectionFuture, details.isReadOnlyMode(), details.getAttemptPromise(), details);
- }
- });
- attemptPromise.addListener(new FutureListener<R>() {
- @Override
- public void operationComplete(Future<R> future) throws Exception {
- checkAttemptFuture(source, details, future);
- }
- });
- }
至此,關於讀寫操作的源碼講解完畢。在上面的代碼註釋中,列出如下重點。
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裏的如下代碼獲取連接對象:
- //開始從connectionManager獲取池中的連接
- //這裏採用異步方式,創建一個RFuture對象,等待池中連接,一旦獲得連接,然後進行讀和寫操作
- final RFuture<RedisConnection> connectionFuture;
- if (readOnlyMode) {//對於讀操作默認readOnlyMode=true,這裏會執行
- connectionFuture = connectionManager.connectionReadOp(source, command);
- } else {//對於寫操作默認readOnlyMode=false,這裏會執行
- connectionFuture = connectionManager.connectionWriteOp(source, command);
- }
- /**
- * 讀操作通過ConnectionManager從連接池獲取連接對象
- * @param source for NodeSource
- * @param command for RedisCommand<?>
- * @return RFuture<RedisConnection>
- */
- public RFuture<RedisConnection> connectionReadOp(NodeSource source, RedisCommand<?> command) {
- //這裏之前分析過source=NodeSource【slot=null,addr=null,redirect=null,entry=MasterSlaveEntry】
- MasterSlaveEntry entry = source.getEntry();
- if (entry == null && source.getSlot() != null) {//這裏不會執行source裏slot=null
- entry = getEntry(source.getSlot());
- }
- if (source.getAddr() != null) {//這裏不會執行source裏addr=null
- entry = getEntry(source.getAddr());
- if (entry == null) {
- for (MasterSlaveEntry e : getEntrySet()) {
- if (e.hasSlave(source.getAddr())) {
- entry = e;
- break;
- }
- }
- }
- if (entry == null) {
- RedisNodeNotFoundException ex = new RedisNodeNotFoundException("Node: " + source.getAddr() + " for slot: " + source.getSlot() + " hasn't been discovered yet");
- return RedissonPromise.newFailedFuture(ex);
- }
- return entry.connectionReadOp(command, source.getAddr());
- }
- if (entry == null) {//這裏不會執行source裏entry不等於null
- RedisNodeNotFoundException ex = new RedisNodeNotFoundException("Node: " + source.getAddr() + " for slot: " + source.getSlot() + " hasn't been discovered yet");
- return RedissonPromise.newFailedFuture(ex);
- }
- //MasterSlaveEntry裏從連接池獲取連接對象
- return entry.connectionReadOp(command);
- }
- /**
- * 寫操作通過ConnectionManager從連接池獲取連接對象
- * @param source for NodeSource
- * @param command for RedisCommand<?>
- * @return RFuture<RedisConnection>
- */
- public RFuture<RedisConnection> connectionWriteOp(NodeSource source, RedisCommand<?> command) {
- //這裏之前分析過source=NodeSource【slot=null,addr=null,redirect=null,entry=MasterSlaveEntry】
- MasterSlaveEntry entry = source.getEntry();
- if (entry == null) {
- entry = getEntry(source);
- }
- if (entry == null) {//這裏不會執行source裏entry不等於null
- RedisNodeNotFoundException ex = new RedisNodeNotFoundException("Node: " + source.getAddr() + " for slot: " + source.getSlot() + " hasn't been discovered yet");
- return RedissonPromise.newFailedFuture(ex);
- }
- //MasterSlaveEntry裏從連接池獲取連接對象
- return entry.connectionWriteOp(command);
- }
MasterSlaveEntry裏持有中我們開篇所提到的四個連接池,那麼這裏我們繼續關注MasterSlaveEntry.java的源代碼:
- /**
- * 寫操作從MasterConnectionPool連接池裏獲取連接對象
- * @param command for RedisCommand<?>
- * @return RFuture<RedisConnection>
- */
- public RFuture<RedisConnection> connectionWriteOp(RedisCommand<?> command) {
- //我們知道writeConnectionHolder的類型爲MasterConnectionPool
- //這裏就是從MasterConnectionPool裏獲取連接對象
- return writeConnectionHolder.get(command);
- }
- /**
- * 寫操作從LoadBalancerManager裏獲取連接對象
- * @param command for RedisCommand<?>
- * @return RFuture<RedisConnection>
- */
- public RFuture<RedisConnection> connectionReadOp(RedisCommand<?> command) {
- if (config.getReadMode() == ReadMode.MASTER) {
- //我們知道默認ReadMode=ReadMode.SLAVE,所以對於讀操作這裏不會執行
- return connectionWriteOp(command);
- }
- //我們知道slaveBalancer裏持有者SlaveConnectionPool和PubSubConnectionPool
- //這裏就是從SlaveConnectionPool裏獲取連接對象
- return slaveBalancer.nextConnection(command);
- }
- /**
- * 讀寫操作從ConnectionPool.java連接池裏獲取連接對象
- * @param command for RedisCommand<?>
- * @return RFuture<T>
- */
- public RFuture<T> get(RedisCommand<?> command) {
- for (int j = entries.size() - 1; j >= 0; j--) {
- final ClientConnectionsEntry entry = getEntry();
- if (!entry.isFreezed() && tryAcquireConnection(entry)) {
- //遍歷ConnectionPool裏維持的ClientConnectionsEntry列表
- //遍歷的算法默認爲RoundRobinLoadBalancer
- //ClientConnectionsEntry裏對應的redis節點爲非凍結節點,也即freezed=false
- return acquireConnection(command, entry);
- }
- }
- //記錄失敗重試信息
- List<InetSocketAddress> failedAttempts = new LinkedList<InetSocketAddress>();
- List<InetSocketAddress> freezed = new LinkedList<InetSocketAddress>();
- for (ClientConnectionsEntry entry : entries) {
- if (entry.isFreezed()) {
- freezed.add(entry.getClient().getAddr());
- } else {
- failedAttempts.add(entry.getClient().getAddr());
- }
- }
- StringBuilder errorMsg = new StringBuilder(getClass().getSimpleName() + " no available Redis entries. ");
- if (!freezed.isEmpty()) {
- errorMsg.append(" Disconnected hosts: " + freezed);
- }
- if (!failedAttempts.isEmpty()) {
- errorMsg.append(" Hosts disconnected due to `failedAttempts` limit reached: " + failedAttempts);
- }
- //獲取連接失敗拋出異常
- RedisConnectionException exception = new RedisConnectionException(errorMsg.toString());
- return connectionManager.newFailedFuture(exception);
- }
- /**
- * 讀寫操作從ConnectionPool.java連接池裏獲取連接對象
- * @param command for RedisCommand<?>
- * @param entry for ClientConnectionsEntry
- * @return RFuture<T>
- */
- private RFuture<T> acquireConnection(RedisCommand<?> command, final ClientConnectionsEntry entry) {
- //創建一個異步結果獲取RPromise
- final RPromise<T> result = connectionManager.newPromise();
- //獲取連接前首先將ClientConnectionsEntry裏的空閒連接信號freeConnectionsCounter值減1
- //該操作成功後將調用這裏的回調函數AcquireCallback<T>
- AcquireCallback<T> callback = new AcquireCallback<T>() {
- @Override
- public void run() {
- result.removeListener(this);
- //freeConnectionsCounter值減1成功,說明獲取可以獲取到連接
- //這裏纔是真正獲取連接的操作
- connectTo(entry, result);
- }
- @Override
- public void operationComplete(Future<T> future) throws Exception {
- entry.removeConnection(this);
- }
- };
- //異步結果獲取RPromise綁定到上面的回調函數callback
- result.addListener(callback);
- //嘗試將ClientConnectionsEntry裏的空閒連接信號freeConnectionsCounter值減1,如果成功就調用callback從連接池獲取連接
- acquireConnection(entry, callback);
- //返回異步結果獲取RPromise
- return result;
- }
- /**
- * 真正從連接池中獲取連接
- * @param entry for ClientConnectionsEntry
- * @param promise for RPromise<T>
- */
- private void connectTo(ClientConnectionsEntry entry, RPromise<T> promise) {
- if (promise.isDone()) {
- releaseConnection(entry);
- return;
- }
- //從連接池中取出一個連接
- T conn = poll(entry);
- if (conn != null) {
- if (!conn.isActive()) {
- promiseFailure(entry, promise, conn);
- return;
- }
- connectedSuccessful(entry, promise, conn);
- return;
- }
- //如果仍然獲取不到連接,可能連接池中連接對象都被租借了,這裏開始創建一個新的連接對象放到連接池中
- createConnection(entry, promise);
- }
- /**
- * 從連接池中獲取連接
- * @param entry for ClientConnectionsEntry
- * @return T
- */
- protected T poll(ClientConnectionsEntry entry) {
- return (T) entry.pollConnection();
- }
- /**
- * 調用ClientConnectionsEntry創建一個連接放置到連接池中並返回此連接
- * @param entry for ClientConnectionsEntry
- * @param promise for RPromise<T>
- */
- private void createConnection(final ClientConnectionsEntry entry, final RPromise<T> promise) {
- //調用ClientConnectionsEntry創建一個連接放置到連接池中並返回此連接
- RFuture<T> connFuture = connect(entry);
- connFuture.addListener(new FutureListener<T>() {
- @Override
- public void operationComplete(Future<T> future) throws Exception {
- if (!future.isSuccess()) {
- promiseFailure(entry, promise, future.cause());
- return;
- }
- T conn = future.getNow();
- if (!conn.isActive()) {
- promiseFailure(entry, promise, conn);
- return;
- }
- connectedSuccessful(entry, promise, conn);
- }
- });
- }
我們繼續跟進ClientConnectionsEntry.java的源代碼,註釋如下:
- /**
- * ClientConnectionsEntry裏從freeConnections裏獲取一個連接並返回給讀寫操作使用
- */
- public RedisConnection pollConnection() {
- return freeConnections.poll();
- }
- /**
- * ClientConnectionsEntry裏新創建一個連接對象返回給讀寫操作使用
- */
- public RFuture<RedisConnection> connect() {
- //調用RedisClient利用netty連接redis服務端,將返回的netty的outboundchannel包裝成RedisConnection並返回
- RFuture<RedisConnection> future = client.connectAsync();
- future.addListener(new FutureListener<RedisConnection>() {
- @Override
- public void operationComplete(Future<RedisConnection> future) throws Exception {
- if (!future.isSuccess()) {
- return;
- }
- RedisConnection conn = future.getNow();
- onConnect(conn);
- log.debug("new connection created: {}", conn);
- }
- });
- return future;
- }
- package org.redisson.client;
- import java.net.InetSocketAddress;
- import java.net.URI;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.TimeUnit;
- import org.redisson.api.RFuture;
- import org.redisson.client.handler.RedisChannelInitializer;
- import org.redisson.client.handler.RedisChannelInitializer.Type;
- import org.redisson.misc.RPromise;
- import org.redisson.misc.RedissonPromise;
- import io.netty.bootstrap.Bootstrap;
- import io.netty.channel.Channel;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelFutureListener;
- import io.netty.channel.ChannelOption;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.group.ChannelGroup;
- import io.netty.channel.group.ChannelGroupFuture;
- import io.netty.channel.group.DefaultChannelGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.SocketChannel;
- import io.netty.channel.socket.nio.NioSocketChannel;
- import io.netty.util.HashedWheelTimer;
- import io.netty.util.Timer;
- import io.netty.util.concurrent.Future;
- import io.netty.util.concurrent.FutureListener;
- import io.netty.util.concurrent.GlobalEventExecutor;
- import org.redisson.misc.URIBuilder;
- /**
- * 使用java裏的網絡編程框架Netty連接redis服務端
- * 作者: Nikita Koksharov
- */
- public class RedisClient {
- private final Bootstrap bootstrap;//Netty的工具類Bootstrap,用於連接建立等作用
- private final Bootstrap pubSubBootstrap;//Netty的工具類Bootstrap,用於連接建立等作用
- private final InetSocketAddress addr;//socket連接的地址
- //channels是netty提供的一個全局對象,裏面記錄着當前socket連接上的所有處於可用狀態的連接channel
- //channels會自動監測裏面的channel,當channel斷開時,會主動踢出該channel,永遠保留當前可用的channel列表
- private final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
- private ExecutorService executor;//REACOTR模型的java異步執行線程池
- private final long commandTimeout;//超時時間
- private Timer timer;//定時器
- private boolean hasOwnGroup;
- private RedisClientConfig config;//redis連接配置信息
- //構造方法
- public static RedisClient create(RedisClientConfig config) {
- if (config.getTimer() == null) {
- config.setTimer(new HashedWheelTimer());
- }
- return new RedisClient(config);
- }
- //構造方法
- private RedisClient(RedisClientConfig config) {
- this.config = config;
- this.executor = config.getExecutor();
- this.timer = config.getTimer();
- addr = new InetSocketAddress(config.getAddress().getHost(), config.getAddress().getPort());
- bootstrap = createBootstrap(config, Type.PLAIN);
- pubSubBootstrap = createBootstrap(config, Type.PUBSUB);
- this.commandTimeout = config.getCommandTimeout();
- }
- //java的網路編程框架Netty工具類Bootstrap初始化
- private Bootstrap createBootstrap(RedisClientConfig config, Type type) {
- Bootstrap bootstrap = new Bootstrap()
- .channel(config.getSocketChannelClass())
- .group(config.getGroup())
- .remoteAddress(addr);
- //註冊netty相關socket數據處理RedisChannelInitializer
- bootstrap.handler(new RedisChannelInitializer(bootstrap, config, this, channels, type));
- //設置超時時間
- bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectTimeout());
- return bootstrap;
- }
- //構造方法
- @Deprecated
- public RedisClient(String address) {
- this(URIBuilder.create(address));
- }
- //構造方法
- @Deprecated
- public RedisClient(URI address) {
- this(new HashedWheelTimer(), Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2), new NioEventLoopGroup(), address);
- hasOwnGroup = true;
- }
- //構造方法
- @Deprecated
- public RedisClient(Timer timer, ExecutorService executor, EventLoopGroup group, URI address) {
- this(timer, executor, group, address.getHost(), address.getPort());
- }
- //構造方法
- @Deprecated
- public RedisClient(String host, int port) {
- this(new HashedWheelTimer(), Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2), new NioEventLoopGroup(), NioSocketChannel.class, host, port, 10000, 10000);
- hasOwnGroup = true;
- }
- //構造方法
- @Deprecated
- public RedisClient(Timer timer, ExecutorService executor, EventLoopGroup group, String host, int port) {
- this(timer, executor, group, NioSocketChannel.class, host, port, 10000, 10000);
- }
- //構造方法
- @Deprecated
- public RedisClient(String host, int port, int connectTimeout, int commandTimeout) {
- this(new HashedWheelTimer(), Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2), new NioEventLoopGroup(), NioSocketChannel.class, host, port, connectTimeout, commandTimeout);
- }
- //構造方法
- @Deprecated
- public RedisClient(final Timer timer, ExecutorService executor, EventLoopGroup group, Class<? extends SocketChannel> socketChannelClass, String host, int port,
- int connectTimeout, int commandTimeout) {
- RedisClientConfig config = new RedisClientConfig();
- config.setTimer(timer).setExecutor(executor).setGroup(group).setSocketChannelClass(socketChannelClass)
- .setAddress(host, port).setConnectTimeout(connectTimeout).setCommandTimeout(commandTimeout);
- this.config = config;
- this.executor = config.getExecutor();
- this.timer = config.getTimer();
- addr = new InetSocketAddress(config.getAddress().getHost(), config.getAddress().getPort());
- //java的網路編程框架Netty工具類Bootstrap初始化
- bootstrap = createBootstrap(config, Type.PLAIN);
- pubSubBootstrap = createBootstrap(config, Type.PUBSUB);
- this.commandTimeout = config.getCommandTimeout();
- }
- //獲取連接的IP地址
- public String getIpAddr() {
- return addr.getAddress().getHostAddress() + ":" + addr.getPort();
- }
- //獲取socket連接的地址
- public InetSocketAddress getAddr() {
- return addr;
- }
- //獲取超時時間
- public long getCommandTimeout() {
- return commandTimeout;
- }
- //獲取netty的線程池
- public EventLoopGroup getEventLoopGroup() {
- return bootstrap.config().group();
- }
- //獲取redis連接配置
- public RedisClientConfig getConfig() {
- return config;
- }
- //獲取連接RedisConnection
- public RedisConnection connect() {
- try {
- return connectAsync().syncUninterruptibly().getNow();
- } catch (Exception e) {
- throw new RedisConnectionException("Unable to connect to: " + addr, e);
- }
- }
- //啓動netty去連接redis服務端,設置java的Future嘗試將netty連接上的OutBoundChannel包裝成RedisConnection並返回RedisConnection
- public RFuture<RedisConnection> connectAsync() {
- final RPromise<RedisConnection> f = new RedissonPromise<RedisConnection>();
- //netty連接redis服務端
- ChannelFuture channelFuture = bootstrap.connect();
- channelFuture.addListener(new ChannelFutureListener() {
- @Override
- public void operationComplete(final ChannelFuture future) throws Exception {
- if (future.isSuccess()) {
- //將netty連接上的OutBoundChannel包裝成RedisConnection並返回RedisConnection
- final RedisConnection c = RedisConnection.getFrom(future.channel());
- c.getConnectionPromise().addListener(new FutureListener<RedisConnection>() {
- @Override
- public void operationComplete(final Future<RedisConnection> future) throws Exception {
- bootstrap.config().group().execute(new Runnable() {
- @Override
- public void run() {
- if (future.isSuccess()) {
- if (!f.trySuccess(c)) {
- c.closeAsync();
- }
- } else {
- f.tryFailure(future.cause());
- c.closeAsync();
- }
- }
- });
- }
- });
- } else {
- bootstrap.config().group().execute(new Runnable() {
- public void run() {
- f.tryFailure(future.cause());
- }
- });
- }
- }
- });
- return f;
- }
- //獲取訂閱相關連接RedisPubSubConnection
- public RedisPubSubConnection connectPubSub() {
- try {
- return connectPubSubAsync().syncUninterruptibly().getNow();
- } catch (Exception e) {
- throw new RedisConnectionException("Unable to connect to: " + addr, e);
- }
- }
- //啓動netty去連接redis服務端,設置java的Future嘗試將netty連接上的OutBoundChannel包裝成RedisPubSubConnection並返回RedisPubSubConnection
- public RFuture<RedisPubSubConnection> connectPubSubAsync() {
- final RPromise<RedisPubSubConnection> f = new RedissonPromise<RedisPubSubConnection>();
- //netty連接redis服務端
- ChannelFuture channelFuture = pubSubBootstrap.connect();
- channelFuture.addListener(new ChannelFutureListener() {
- @Override
- public void operationComplete(final ChannelFuture future) throws Exception {
- if (future.isSuccess()) {
- //將netty連接上的OutBoundChannel包裝成RedisPubSubConnection並返回RedisPubSubConnection
- final RedisPubSubConnection c = RedisPubSubConnection.getFrom(future.channel());
- c.<RedisPubSubConnection>getConnectionPromise().addListener(new FutureListener<RedisPubSubConnection>() {
- @Override
- public void operationComplete(final Future<RedisPubSubConnection> future) throws Exception {
- bootstrap.config().group().execute(new Runnable() {
- @Override
- public void run() {
- if (future.isSuccess()) {
- if (!f.trySuccess(c)) {
- c.closeAsync();
- }
- } else {
- f.tryFailure(future.cause());
- c.closeAsync();
- }
- }
- });
- }
- });
- } else {
- bootstrap.config().group().execute(new Runnable() {
- public void run() {
- f.tryFailure(future.cause());
- }
- });
- }
- }
- });
- return f;
- }
- //關閉netty網絡連接
- public void shutdown() {
- shutdownAsync().syncUninterruptibly();
- if (hasOwnGroup) {
- timer.stop();
- executor.shutdown();
- try {
- executor.awaitTermination(15, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- bootstrap.config().group().shutdownGracefully();
- }
- }
- //異步關閉netty網絡連接
- public ChannelGroupFuture shutdownAsync() {
- for (Channel channel : channels) {
- RedisConnection connection = RedisConnection.getFrom(channel);
- if (connection != null) {
- connection.setClosed(true);
- }
- }
- return channels.close();
- }
- @Override
- public String toString() {
- return "[addr=" + addr + "]";
- }
- }
轉載地址:http://aperise.iteye.com/blog/2400528