redisson用的是netty的io框架,逻辑在channel的handler中
先看配置,以常用的哨兵模式为例,config.useSentinelServers()+Redisson.create(config)。Redisson的构造方法中,最重要的是给connectionManager属性初始化,ConfigSupport.createConnectionManager(configCopy)---new SentinelConnectionManager,其中initTimer中有subscribeService的初始化,这个是订阅用的。再往下,是先去哨兵地址的第一个,发请求获取master的地址+其他哨兵的地址+salve的地址。再往下有initSingleEntry方法,这里实例化了一个MasterSlaveEntry,并为它实例化了一个RedisClient,这个client顾名思义是和master通信的。之后把这个entry放到connectionManager的client2entry和slotEntry中,到这里配置逻辑基本完成。
下面以加锁的lock方法为例,看看请求是怎么发出去的。进入到tryAcquire方法,tryAcquireAsync返回一个future,这个future在get的时候会await,其实它是tryLockInnerAsync方法的返回值:ttlRemainingFuture,而一路点下去,又是evalAsync方法中的mainPromise,这个mainPromise会在响应到来之后执行mainPromise.trySuccess(res)方法,并在DefaultPromise的checkNotifyWaiters方法中notifyAll,唤醒刚才被await的线程(至于响应到来怎么让mainPromise执行这个回调方法,以后再说)。redisson的源码中频繁的用到了异步的调用,看习惯了也就适应了,script用eval一次执行多个语句这个不说了,看evalAsync方法的async ---> executor.execute(),先用getConnection获取连接 --- connectionManager.connectionWriteOp,先是获取entry,从上面的slotEntry中取出上面说的MasterSlaveEntry---一直到ConnectionPool.acquireConnection,获取freeConnectionsCounter的许可,获取之后在回调里执行connectTo方法,从entry的freeConnections中试图取出空闲连接,如果没有,用client连接生成一个(至于为什么返回值是RedisConnection类型而不是channel类型,以后再说)。连接成功之后,执行回调,RedisExecutor的connectionFuture.onComplete方法,其实这时候返回的future已经是isDone的,所以刚添加到listener中的观察者会被立即执行,执行到sendCommand方法,把attemptPromise封装成CommandData,channel.writeAndFlush(data),到这里请求基本结束了。
至于响应的处理,和RocketMQ不同,前者发出请求前用请求ID作为key把回调放入map中,而redisson是用信号量来控制总访问数,在到达最大值之前新建或者获取空闲连接,响应处理完再还回去,这种处理方式个人感觉比较占资源。一个请求发出之后,所经过的handler都在RedisChannelInitializer的initChannel方法中,在其中的CommandsQueue的write---sendData中, ch.attr(CURRENT_COMMAND).set(data)一句把command放到ch的属性中,而在响应到来的处理是在CommandDecoder中,decode方法中有QueueCommand data = ctx.channel().attr(CommandsQueue.CURRENT_COMMAND).get(),取出刚才的command,执行decodeXXX一直到handleResult,result = data.getCommand().getConvertor().convert(result);先取出convertor转化结果,completeResponse方法再调用data.getPromise().trySuccess(result)唤醒线程并返回结果。这里的promise就是之前说的ttlRemainingFuture。
刚才说的client.connect返回一个RedisConnection类型,这个的逻辑还是去handler里面找,不过这次不是read和write了,而是channelRegistered,在RedisConnectionHandler的父类BaseConnectionHandler的channelRegistered方法中,createConnection新建一个RedisConnection,channel.attr(CONNECTION).set(this),把RedisConnection,放到channel的属性中,而redisClient在connect的时候会取出这个RedisConnection,调用返回的future的trySuccess方法,把值设置进去。
再看subscribe,先判断有没有entryName(connectionManagerId + lockName)对应的RedissonLockEntry,如果有,说明已经订阅直接放行进入下面的while。如果没有生成一个新的,封装一下,然后createListener生成一个listener并传到PublishSubscribeService.subscribe方法中,其实这个方法才是关键,因为subscribe并不仅仅在lock方法中,redissonClient是随时可以订阅频道的,比如getTopic.addListener就可以把自己的回调方法封装成listener,传到PublishSubscribeService.subscribe中。进入service.subscribe,先检查PubSubConnectionEntry,同样的,如果有了connEntry,那么说明已经订阅了,直接把listener添加到entry的conn属性中。如果没有connEntry,那么需要在获取freePubSubLock(限额1)的同步下,建立新链接,和tryaquire方法一样,用的是connect方法----nextPubSubConnection----pubSubConnectionPool.get()----acquireConnection----connectTo---存在空闲的、已经和master建立起来的连接,就取出来,如果没有,就createConnection----connectPubSub----connectPubSubAsync---- pubSubBootstrap.connect,这里和tryquire用的bootstrap是两个strap,其他的逻辑都是一样的。订阅的话,会收到两种消息,一个是订阅成功的消息,一个是释放锁之后发布的消息。和tryaquire一样,都要走encoder,不过此时的encoder是CommandPubSubDecoder了,在decodeCommand---decode,之前tryAquire的code是+,现在是*,所以进入decodeList----decodeResult----根据message的类型,放入队列然后取出执行,如果是PubSubStatusMessage(subscribe/unsubscibe成功的响应),取出conn中的所有listener,执行onStatus方法,进入上面说的封装的listener,发现onStatus是取出RedissonLockEntry中的promise的trySuccess方法,这个正好对应lock方法中的commandExecutor.syncSubscription(future)这一句同步方法的释放;而PubSubMessage调用的是封装的listener的onMessage方法---LockPubSub.onMessage---value.getLatch().release(),这里又正好对应lock方法中的getEntry(threadId).getLatch().tryAcquire的放行。
所以整个lock的逻辑就是:先去获取锁,如果获取不到,就会获取到当前锁的过期时长,那么开始订阅相应的channel,订阅成功后进入while循环,进入之后,先再尝试去获取锁,获取不到就会返回当前锁的过期时长,以此做为最大时长挂起当前线程,等待锁释放消息的发布,收到之后,再去获取锁……如此反复直到获取到为止。