{"id":67722,"date":"2025-05-10T08:13:30","date_gmt":"2025-05-10T00:13:30","guid":{"rendered":"https:\/\/fwq.ai\/blog\/67722\/"},"modified":"2025-05-10T08:13:30","modified_gmt":"2025-05-10T00:13:30","slug":"%e5%85%b3%e4%ba%8e%e4%bd%bf%e7%94%a8redisson%e8%ae%a2%e9%98%85%e6%95%b0%e9%97%ae%e9%a2%98-2","status":"publish","type":"post","link":"https:\/\/fwq.ai\/blog\/67722\/","title":{"rendered":"\u5173\u4e8e\u4f7f\u7528Redisson\u8ba2\u9605\u6570\u95ee\u9898"},"content":{"rendered":"<p><b><\/b> <\/p>\n<h1>\u5173\u4e8e\u4f7f\u7528Redisson\u8ba2\u9605\u6570\u95ee\u9898<\/h1>\n<p><span style=\"cursor: pointer\"><i><\/i>\u6536\u85cf<\/span> <\/p>\n<p>\u672c\u7bc7\u6587\u7ae0\u7ed9\u5927\u5bb6\u5206\u4eab\u300a\u5173\u4e8e\u4f7f\u7528Redisson\u8ba2\u9605\u6570\u95ee\u9898\u300b\uff0c\u8986\u76d6\u4e86\u6570\u636e\u5e93\u7684\u5e38\u89c1\u57fa\u7840\u77e5\u8bc6\uff0c\u5176\u5b9e\u4e00\u4e2a\u8bed\u8a00\u7684\u5168\u90e8\u77e5\u8bc6\u70b9\u4e00\u7bc7\u6587\u7ae0\u662f\u4e0d\u53ef\u80fd\u8bf4\u5b8c\u7684\uff0c\u4f46\u5e0c\u671b\u901a\u8fc7\u8fd9\u4e9b\u95ee\u9898\uff0c\u8ba9\u8bfb\u8005\u5bf9\u81ea\u5df1\u7684\u638c\u63e1\u7a0b\u5ea6\u6709\u4e00\u5b9a\u7684\u8ba4\u8bc6(B \u6570)\uff0c\u4ece\u800c\u5f25\u8865\u81ea\u5df1\u7684\u4e0d\u8db3\uff0c\u66f4\u597d\u7684\u638c\u63e1\u5b83\u3002<\/p>\n<h2>\u4e00\u3001\u524d\u63d0<\/h2>\n<p>\u6700\u8fd1\u5728\u4f7f\u7528\u5206\u5e03\u5f0f\u9501redisson\u65f6\u9047\u5230\u4e00\u4e2a\u7ebf\u4e0a\u95ee\u9898\uff1a\u53d1\u73b0\u662f<code>subscriptionsPerConnection or subscriptionConnectionPoolSize <\/code>\u7684\u5927\u5c0f\u4e0d\u591f\uff0c\u9700\u8981\u63d0\u9ad8\u914d\u7f6e\u624d\u80fd\u89e3\u51b3\u3002<\/p>\n<h2>\u4e8c\u3001\u6e90\u7801\u5206\u6790<\/h2>\n<p>\u4e0b\u9762\u5bf9\u5176\u6e90\u7801\u8fdb\u884c\u5206\u6790\uff0c\u624d\u80fd\u627e\u5230\u5230\u5e95\u662f\u4ec0\u4e48\u903b\u8f91\u5bfc\u81f4\u95ee\u9898\u6240\u5728\uff1a<\/p>\n<h3>1\u3001RedissonLock#lock() \u65b9\u6cd5<\/h3>\n<pre>private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {\n&nbsp; &nbsp; &nbsp; &nbsp; long threadId = Thread.currentThread().getId();\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u5c1d\u8bd5\u83b7\u53d6\uff0c\u5982\u679cttl == null\uff0c\u5219\u8868\u793a\u83b7\u53d6\u9501\u6210\u529f\n&nbsp; &nbsp; &nbsp; &nbsp; Long ttl = tryAcquire(leaseTime, unit, threadId);\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ lock acquired\n&nbsp; &nbsp; &nbsp; &nbsp; if (ttl == null) {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return;\n&nbsp; &nbsp; &nbsp; &nbsp; }\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u8ba2\u9605\u9501\u91ca\u653e\u4e8b\u4ef6\uff0c\u5e76\u901a\u8fc7await\u65b9\u6cd5\u963b\u585e\u7b49\u5f85\u9501\u91ca\u653e\uff0c\u6709\u6548\u7684\u89e3\u51b3\u4e86\u65e0\u6548\u7684\u9501\u7533\u8bf7\u6d6a\u8d39\u8d44\u6e90\u7684\u95ee\u9898\n&nbsp; &nbsp; &nbsp; &nbsp; RFuture\n   \n     future = subscribe(threadId);\n&nbsp; &nbsp; &nbsp; &nbsp; if (interruptibly) {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; commandExecutor.syncSubscriptionInterrupted(future);\n&nbsp; &nbsp; &nbsp; &nbsp; } else {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; commandExecutor.syncSubscription(future);\n&nbsp; &nbsp; &nbsp; &nbsp; }\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u540e\u9762\u4ee3\u7801\u5ffd\u7565\n&nbsp; &nbsp; &nbsp; &nbsp; try {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u65e0\u9650\u5faa\u73af\u83b7\u53d6\u9501\uff0c\u76f4\u5230\u83b7\u53d6\u9501\u6210\u529f\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ ...\n&nbsp; &nbsp; &nbsp; &nbsp; } finally {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u53d6\u6d88\u8ba2\u9605\u9501\u91ca\u653e\u4e8b\u4ef6\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; unsubscribe(future, threadId);\n&nbsp; &nbsp; &nbsp; &nbsp; }\n}\n   <\/pre>\n<p>\u603b\u7ed3\u4e0b\u4e3b\u8981\u903b\u8f91\uff1a<\/p>\n<ul>\n<li>\u83b7\u53d6\u5f53\u524d\u7ebf\u7a0b\u7684\u7ebf\u7a0bid\uff1b<\/li>\n<li>tryAquire\u5c1d\u8bd5\u83b7\u53d6\u9501\uff0c\u5e76\u8fd4\u56dettl<\/li>\n<li>\u5982\u679cttl\u4e3a\u7a7a\uff0c\u5219\u7ed3\u675f\u6d41\u7a0b\uff1b\u5426\u5219\u8fdb\u5165\u540e\u7eed\u903b\u8f91\uff1b<\/li>\n<li>this.subscribe(threadId)\u8ba2\u9605\u5f53\u524d\u7ebf\u7a0b\uff0c\u8fd4\u56de\u4e00\u4e2aRFuture\uff1b<\/li>\n<li>\u5982\u679c\u5728\u6307\u5b9a\u65f6\u95f4\u6ca1\u6709\u76d1\u542c\u5230\uff0c\u5219\u4f1a\u4ea7\u751f\u5982\u4e0a\u5f02\u5e38\u3002<\/li>\n<li>\u8ba2\u9605\u6210\u529f\u540e\uff0c \u901a\u8fc7while(true)\u5faa\u73af\uff0c\u4e00\u76f4\u5c1d\u8bd5\u83b7\u53d6\u9501<\/li>\n<li>fially\u4ee3\u7801\u5757\uff0c\u4f1a\u89e3\u9664\u8ba2\u9605<\/li>\n<\/ul>\n<p>\u6240\u4ee5\u4e0a\u8ff0\u8fd9\u60c5\u51b5\u95ee\u9898\u5e94\u8be5\u51fa\u73b0\u5728subscribe()\u65b9\u6cd5\u4e2d<\/p>\n<h3>2\u3001\u8be6\u7ec6\u770b\u4e0bsubscribe()\u65b9\u6cd5<\/h3>\n<pre>protected RFuture\n   \n     subscribe(long threadId) {\n&nbsp; &nbsp; \/\/ entryName \u683c\u5f0f\uff1a\u201cid:name\u201d\uff1b\n&nbsp; &nbsp; \/\/ channelName \u683c\u5f0f\uff1a\u201credisson_lock__channel:name\u201d\uff1b\n&nbsp; &nbsp; return pubSub.subscribe(getEntryName(), getChannelName());\n}\n   <\/pre>\n<p>RedissonLock#pubSub \u662f\u5728RedissonLock\u6784\u9020\u51fd\u6570\u4e2d\u521d\u59cb\u5316\u7684\uff1a<\/p>\n<pre>public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {\n&nbsp; &nbsp; \/\/ ....\n&nbsp; &nbsp; this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();\n}<\/pre>\n<p>\u800csubscribeService\u5728MasterSlaveConnectionManager\u7684\u5b9e\u73b0\u4e2d\u53c8\u662f\u901a\u8fc7\u5982\u4e0b\u65b9\u5f0f\u6784\u9020\u7684<\/p>\n<pre>public MasterSlaveConnectionManager(MasterSlaveServersConfig cfg, Config config, UUID id) {\n&nbsp; &nbsp; this(config, id);\n&nbsp; &nbsp; this.config = cfg;\n\n&nbsp; &nbsp; \/\/ \u521d\u59cb\u5316\n&nbsp; &nbsp; initTimer(cfg);\n&nbsp; &nbsp; initSingleEntry();\n}\n\nprotected void initTimer(MasterSlaveServersConfig config) {\n&nbsp; &nbsp; int[] timeouts = new int[]{config.getRetryInterval(), config.getTimeout()};\n&nbsp; &nbsp; Arrays.sort(timeouts);\n&nbsp; &nbsp; int minTimeout = timeouts[0];\n&nbsp; &nbsp; if (minTimeout % 100 != 0) {\n&nbsp; &nbsp; &nbsp; &nbsp; minTimeout = (minTimeout % 100) \/ 2;\n&nbsp; &nbsp; } else if (minTimeout == 100) {\n&nbsp; &nbsp; &nbsp; &nbsp; minTimeout = 50;\n&nbsp; &nbsp; } else {\n&nbsp; &nbsp; &nbsp; &nbsp; minTimeout = 100;\n&nbsp; &nbsp; }\n\n&nbsp; &nbsp; timer = new HashedWheelTimer(new DefaultThreadFactory(\"redisson-timer\"), minTimeout, TimeUnit.MILLISECONDS, 1024, false);\n\n&nbsp; &nbsp; connectionWatcher = new IdleConnectionWatcher(this, config);\n\n&nbsp; &nbsp; \/\/ \u521d\u59cb\u5316:\u5176\u4e2dthis\u5c31\u662fMasterSlaveConnectionManager\u5b9e\u4f8b\uff0cconfig\u5219\u4e3aMasterSlaveServersConfig\u5b9e\u4f8b\uff1a\n&nbsp; &nbsp; subscribeService = new PublishSubscribeService(this, config);\n}<\/pre>\n<p>PublishSubscribeService\u6784\u9020\u51fd\u6570<\/p>\n<pre>private final SemaphorePubSub semaphorePubSub = new SemaphorePubSub(this);\npublic PublishSubscribeService(ConnectionManager connectionManager, MasterSlaveServersConfig config) {\n&nbsp; &nbsp; super();\n&nbsp; &nbsp; this.connectionManager = connectionManager;\n&nbsp; &nbsp; this.config = config;\n&nbsp; &nbsp; for (int i = 0; i \n<h3>3\u3001\u56de\u5230subscribe()\u65b9\u6cd5\u4e3b\u8981\u903b\u8f91\u8fd8\u662f\u4ea4\u7ed9\u4e86 LockPubSub#subscribe()\u91cc\u9762<\/h3>\n<pre>private final ConcurrentMap\n    \n      entries = new ConcurrentHashMap();\n\npublic RFuture\n     \n       subscribe(String entryName, String channelName) { &nbsp; &nbsp; &nbsp; \/\/ \u4ecePublishSubscribeService\u83b7\u53d6\u5bf9\u5e94\u7684\u4fe1\u53f7\u91cf\u3002 \u76f8\u540c\u7684channelName\u83b7\u53d6\u7684\u662f\u540c\u4e00\u4e2a\u4fe1\u53f7\u91cf &nbsp; &nbsp; &nbsp;\/\/ public AsyncSemaphore getSemaphore(ChannelName channelName) { &nbsp; &nbsp; \/\/ &nbsp; &nbsp;return locks[Math.abs(channelName.hashCode() % locks.length)]; &nbsp; &nbsp; \/\/ } &nbsp; &nbsp; AsyncSemaphore semaphore = service.getSemaphore(new ChannelName(channelName)); &nbsp; &nbsp; AtomicReference\n      \n        listenerHolder = new AtomicReference\n       \n        (); &nbsp; &nbsp; &nbsp; &nbsp; RPromise\n        \n          newPromise = new RedissonPromise\n         \n          () { &nbsp; &nbsp; &nbsp; &nbsp; @Override &nbsp; &nbsp; &nbsp; &nbsp; public boolean cancel(boolean mayInterruptIfRunning) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return semaphore.remove(listenerHolder.get()); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; Runnable listener = new Runnable() { &nbsp; &nbsp; &nbsp; &nbsp; @Override &nbsp; &nbsp; &nbsp; &nbsp; public void run() { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ &nbsp;\u5982\u679c\u5b58\u5728RedissonLockEntry\uff0c \u5219\u76f4\u63a5\u5229\u7528\u5df2\u6709\u7684\u76d1\u542c &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; E entry = entries.get(entryName); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (entry != null) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; entry.acquire(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; semaphore.release(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; entry.getPromise().onComplete(new TransferListener\n          \n           (newPromise)); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; E value = createEntry(newPromise); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value.acquire(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; E oldValue = entries.putIfAbsent(entryName, value); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (oldValue != null) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; oldValue.acquire(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; semaphore.release(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; oldValue.getPromise().onComplete(new TransferListener\n           \n            (newPromise)); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u521b\u5efa\u76d1\u542c\uff0c &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; RedisPubSubListener\n             listener = createListener(channelName, value); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u8ba2\u9605\u76d1\u542c &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; service.subscribe(LongCodec.INSTANCE, channelName, semaphore, listener); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; \/\/ \u6700\u7ec8\u4f1a\u6267\u884clistener.run\u65b9\u6cd5 &nbsp; &nbsp; semaphore.acquire(listener); &nbsp; &nbsp; listenerHolder.set(listener); &nbsp; &nbsp; return newPromise; }\n           \n          \n         \n        \n       \n      \n     \n    <\/pre>\n<p>AsyncSemaphore#acquire()\u65b9\u6cd5<\/p>\n<pre>public void acquire(Runnable listener) {\n&nbsp; &nbsp; acquire(listener, 1);\n}\n\npublic void acquire(Runnable listener, int permits) {\n&nbsp; &nbsp; boolean run = false;\n\n&nbsp; &nbsp; synchronized (this) {\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ counter\u521d\u59cb\u5316\u503c\u4e3a1\n&nbsp; &nbsp; &nbsp; &nbsp; if (counter \n<p>\u68b3\u7406\u4e0a\u8ff0\u903b\u8f91\uff1a<\/p>\n<p style=\"margin-left:40px\">1\u3001\u4ecePublishSubscribeService\u83b7\u53d6\u5bf9\u5e94\u7684\u4fe1\u53f7\u91cf\uff0c \u76f8\u540c\u7684channelName\u83b7\u53d6\u7684\u662f\u540c\u4e00\u4e2a\u4fe1\u53f7\u91cf<br>2\u3001\u5982\u679c\u662f\u7b2c\u4e00\u6b21\u8bf7\u6c42\uff0c\u5219\u4f1a\u7acb\u9a6c\u6267\u884clistener.run()\u65b9\u6cd5\uff0c \u5426\u5219\u9700\u8981\u7b49\u4e0a\u4e2a\u7ebf\u7a0b\u83b7\u53d6\u5230\u8be5\u4fe1\u53f7\u91cf\u6267\u884c\u5b8c\u65b9\u80fd\u6267\u884c\uff1b<br>3\u3001\u5982\u679c\u5df2\u7ecf\u5b58\u5728RedissonLockEntry\uff0c \u5219\u5229\u7528\u5df2\u7ecf\u8ba2\u9605\u5c31\u884c<br>4\u3001\u5982\u679c\u4e0d\u5b58\u5728RedissonLockEntry\uff0c \u5219\u4f1a\u521b\u5efa\u65b0\u7684RedissonLockEntry\uff0c\u7136\u540e\u8fdb\u884c\u3002<\/p>\n<p>\u4ece\u4e0a\u9762\u4ee3\u7801\u770b\uff0c\u4e3b\u8981\u903b\u8f91\u662f\u4ea4\u7ed9\u4e86PublishSubscribeService#subscribe\u65b9\u6cd5<\/p>\n<h3>4\u3001PublishSubscribeService#subscribe\u903b\u8f91\u5982\u4e0b\uff1a<\/h3>\n<pre>private final ConcurrentMap\n     \n       name2PubSubConnection = new ConcurrentHashMap();\nprivate final Queue\n      \n        freePubSubConnections = new ConcurrentLinkedQueue(); public RFuture\n       \n         subscribe(Codec codec, String channelName, AsyncSemaphore semaphore, RedisPubSubListener&gt;... listeners) { &nbsp; &nbsp; RPromise\n        \n          promise = new RedissonPromise\n         \n          (); &nbsp; &nbsp; \/\/ \u4e3b\u8981\u903b\u8f91\u5165\u53e3\uff0c \u8fd9\u91cc\u8981\u4e3b\u8981channelName\u6bcf\u6b21\u90fd\u662f\u65b0\u5bf9\u8c61, \u4f46\u5185\u90e8\u8986\u5199hashCode+equals\u3002 &nbsp; &nbsp; subscribe(codec, new ChannelName(channelName), promise, PubSubType.SUBSCRIBE, semaphore, listeners); &nbsp; &nbsp; return promise; } private void subscribe(Codec codec, ChannelName channelName, &nbsp;RPromise\n          \n            promise, PubSubType type, AsyncSemaphore lock, RedisPubSubListener&gt;... listeners) { &nbsp; &nbsp; PubSubConnectionEntry connEntry = name2PubSubConnection.get(channelName); &nbsp; &nbsp; if (connEntry != null) { &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u4ece\u5df2\u6709Connection\u4e2d\u53d6\uff0c\u5982\u679c\u5b58\u5728\u76f4\u63a5\u628alisteners\u52a0\u5165\u5230PubSubConnectionEntry\u4e2d &nbsp; &nbsp; &nbsp; &nbsp; addListeners(channelName, promise, type, lock, connEntry, listeners); &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; } &nbsp; &nbsp; \/\/ \u6ca1\u6709\u65f6\uff0c\u624d\u662f\u6700\u91cd\u8981\u7684\u903b\u8f91 &nbsp; &nbsp; freePubSubLock.acquire(new Runnable() { &nbsp; &nbsp; &nbsp; &nbsp; @Override &nbsp; &nbsp; &nbsp; &nbsp; public void run() { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (promise.isDone()) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; lock.release(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; freePubSubLock.release(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u4ece\u961f\u5217\u4e2d\u53d6\u5934\u90e8\u5143\u7d20 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PubSubConnectionEntry freeEntry = freePubSubConnections.peek(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (freeEntry == null) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u7b2c\u4e00\u6b21\u80af\u5b9a\u662f\u6ca1\u6709\u7684\u9700\u8981\u5efa\u7acb &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; connect(codec, channelName, promise, type, lock, listeners); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u5982\u679c\u5b58\u5728\u5219\u5c1d\u8bd5\u83b7\u53d6\uff0c\u5982\u679cremainFreeAmount\u5c0f\u4e8e0\u5219\u629b\u51fa\u5f02\u5e38\u7ec8\u6b62\u4e86\u3002 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; int remainFreeAmount = freeEntry.tryAcquire(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (remainFreeAmount == -1) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new IllegalStateException(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PubSubConnectionEntry oldEntry = name2PubSubConnection.putIfAbsent(channelName, freeEntry); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (oldEntry != null) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; freeEntry.release(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; freePubSubLock.release(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addListeners(channelName, promise, type, lock, oldEntry, listeners); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u5982\u679cremainFreeAmount=0\uff0c \u5219\u4ece\u961f\u5217\u4e2d\u79fb\u9664 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (remainFreeAmount == 0) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; freePubSubConnections.poll(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; freePubSubLock.release(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u589e\u52a0\u76d1\u542c &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; RFuture\n           \n             subscribeFuture = addListeners(channelName, promise, type, lock, freeEntry, listeners); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ChannelFuture future; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (PubSubType.PSUBSCRIBE == type) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; future = freeEntry.psubscribe(codec, channelName); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; future = freeEntry.subscribe(codec, channelName); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; future.addListener(new ChannelFutureListener() { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Override &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; public void operationComplete(ChannelFuture future) throws Exception { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!future.isSuccess()) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!promise.isDone()) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; subscribeFuture.cancel(false); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; connectionManager.newTimeout(new TimerTask() { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Override &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; public void run(Timeout timeout) throws Exception { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; subscribeFuture.cancel(false); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, config.getTimeout(), TimeUnit.MILLISECONDS); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); } private void connect(Codec codec, ChannelName channelName, RPromise\n            \n              promise, PubSubType type, AsyncSemaphore lock, RedisPubSubListener&gt;... listeners) { &nbsp; &nbsp; \/\/ \u6839\u636echannelName\u8ba1\u7b97\u51faslot\u83b7\u53d6PubSubConnection &nbsp; &nbsp; int slot = connectionManager.calcSlot(channelName.getName()); &nbsp; &nbsp; RFuture\n             \n               connFuture = nextPubSubConnection(slot); &nbsp; &nbsp; promise.onComplete((res, e) -&gt; { &nbsp; &nbsp; &nbsp; &nbsp; if (e != null) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ((RPromise\n              \n               ) connFuture).tryFailure(e); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); &nbsp; &nbsp; connFuture.onComplete((conn, e) -&gt; { &nbsp; &nbsp; &nbsp; &nbsp; if (e != null) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; freePubSubLock.release(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; lock.release(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; promise.tryFailure(e); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u8fd9\u91cc\u4f1a\u4ece\u914d\u7f6e\u4e2d\u8bfb\u53d6subscriptionsPerConnection &nbsp; &nbsp; &nbsp; &nbsp; PubSubConnectionEntry entry = new PubSubConnectionEntry(conn, config.getSubscriptionsPerConnection()); &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u6bcf\u83b7\u53d6\u4e00\u6b21\uff0csubscriptionsPerConnection\u5c31\u4f1a\u51cf\u76f4\u5230\u4e3a0 &nbsp; &nbsp; &nbsp; &nbsp; int remainFreeAmount = entry.tryAcquire(); &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u5982\u679c\u65e7\u7684\u5b58\u5728\uff0c\u5219\u5c06\u73b0\u6709\u7684entry\u91ca\u653e\uff0c\u7136\u540e\u5c06listeners\u52a0\u5165\u5230oldEntry\u4e2d &nbsp; &nbsp; &nbsp; &nbsp; PubSubConnectionEntry oldEntry = name2PubSubConnection.putIfAbsent(channelName, entry); &nbsp; &nbsp; &nbsp; &nbsp; if (oldEntry != null) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; releaseSubscribeConnection(slot, entry); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; freePubSubLock.release(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addListeners(channelName, promise, type, lock, oldEntry, listeners); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; if (remainFreeAmount &gt; 0) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u52a0\u5165\u5230\u961f\u5217\u4e2d &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; freePubSubConnections.add(entry); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; freePubSubLock.release(); &nbsp; &nbsp; &nbsp; &nbsp; RFuture\n               \n                 subscribeFuture = addListeners(channelName, promise, type, lock, entry, listeners); &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u8fd9\u91cc\u771f\u6b63\u7684\u8fdb\u884c\u8ba2\u9605\uff08\u5e95\u5c42\u4e0eredis\u4ea4\u4e92\uff09 &nbsp; &nbsp; &nbsp; &nbsp; ChannelFuture future; &nbsp; &nbsp; &nbsp; &nbsp; if (PubSubType.PSUBSCRIBE == type) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; future = entry.psubscribe(codec, channelName); &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; future = entry.subscribe(codec, channelName); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; future.addListener(new ChannelFutureListener() { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Override &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; public void operationComplete(ChannelFuture future) throws Exception { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!future.isSuccess()) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!promise.isDone()) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; subscribeFuture.cancel(false); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; connectionManager.newTimeout(new TimerTask() { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Override &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; public void run(Timeout timeout) throws Exception { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; subscribeFuture.cancel(false); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, config.getTimeout(), TimeUnit.MILLISECONDS); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; }); }\n               \n              \n             \n            \n           \n          \n         \n        \n       \n      \n     <\/pre>\n<p>PubSubConnectionEntry#tryAcquire\u65b9\u6cd5\uff0c subscriptionsPerConnection\u4ee3\u8868\u4e86\u6bcf\u4e2a\u8fde\u63a5\u7684\u6700\u5927\u8ba2\u9605\u6570\u3002\u5f53tryAcqcurie\u7684\u65f6\u5019\u4f1a\u51cf\u5c11\u8fd9\u4e2a\u6570\u91cf\uff1a<\/p>\n<pre>&nbsp;public int tryAcquire() {\n&nbsp;&nbsp;&nbsp;&nbsp;while (true) {\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int value = subscribedChannelsAmount.get();\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (value == 0) {\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return -1;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (subscribedChannelsAmount.compareAndSet(value, value - 1)) {\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return value - 1;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}\n&nbsp;&nbsp;&nbsp;&nbsp;}\n}<\/pre>\n<p>\u68b3\u7406\u4e0a\u8ff0\u903b\u8f91\uff1a<\/p>\n<p style=\"margin-left:40px\">1\u3001\u8fd8\u662f\u8fdb\u884c\u91cd\u590d\u5224\u65ad\uff0c \u6839\u636echannelName\u4ecename2PubSubConnection\u4e2d\u83b7\u53d6\uff0c\u770b\u662f\u5426\u5b58\u5728\u5df2\u7ecf\u8ba2\u9605\uff1aPubSubConnectionEntry\uff1b \u5982\u679c\u5b58\u5728\u76f4\u63a5\u628a\u65b0\u7684listener\u52a0\u5165\u5230PubSubConnectionEntry\u3002<br \/>2\u3001\u4ece\u961f\u5217freePubSubConnections\u4e2d\u53d6\u516c\u7528\u7684PubSubConnectionEntry\uff0c \u5982\u679c\u6ca1\u6709\u5c31\u8fdb\u5165connect()\u65b9\u6cd5<\/p>\n<p style=\"margin-left:80px\">2.1 \u4f1a\u6839\u636esubscriptionsPerConnection\u521b\u5efaPubSubConnectionEntry\uff0c \u7136\u540e\u8c03\u7528\u5176tryAcquire()\u65b9\u6cd5 - \u6bcf\u8c03\u7528\u4e00\u6b21\u5c31\u4f1a\u51cf1<br \/>2.2 \u5c06\u65b0\u7684PubSubConnectionEntry\u653e\u5165\u5168\u5c40\u7684name2PubSubConnection\uff0c \u65b9\u4fbf\u540e\u7eed\u91cd\u590d\u4f7f\u7528\uff1b<br \/>2.3 \u540c\u65f6\u4e5f\u5c06PubSubConnectionEntry\u653e\u5165\u961f\u5217freePubSubConnections\u4e2d\u3002- remainFreeAmount &gt; 0<br \/>2.4 \u540e\u9762\u5c31\u662f\u8fdb\u884c\u5e95\u5c42\u7684subscribe\u548caddListener<\/p>\n<p style=\"margin-left:40px\">3\u3001\u5982\u679c\u5df2\u7ecf\u5b58\u5728PubSubConnectionEntry\uff0c\u5219\u5229\u7528\u5df2\u6709\u7684PubSubConnectionEntry\u8fdb\u884ctryAcquire\uff1b<br \/>4\u3001\u5982\u679cremainFreeAmount 5\u3001\u6700\u540e\u4e5f\u662f\u8fdb\u884c\u5e95\u5c42\u7684subscribe\u548caddListener\uff1b<\/p>\n<h2>\u4e09 \u603b\u7ed3<\/h2>\n<p>\u6839\u56e0\uff1a \u4ece\u4e0a\u9762\u4ee3\u7801\u5206\u6790\uff0c \u5bfc\u81f4\u95ee\u9898\u7684\u6839\u56e0\u662f\u56e0\u4e3aPublishSubscribeService \u4f1a\u4f7f\u7528\u516c\u5171\u961f\u5217\u4e2d\u7684freePubSubConnections\uff0c \u5982\u679c\u540c\u4e00\u4e2akey\u4e00\u6b21\u6027\u8bf7\u6c42\u8d85\u8fc7subscriptionsPerConnection\u5b83\u7684\u9ed8\u8ba4\u503c5\u65f6\uff0cremainFreeAmount\u5c31\u53ef\u80fd\u51fa\u73b0-1\u7684\u60c5\u51b5\uff0c \u90a3\u4e48\u5c31\u4f1a\u5bfc\u81f4commandExecutor.syncSubscription(future)\u4e2d\u7b49\u5f85\u8d85\u65f6\uff0c\u4e5f\u5c31\u629b\u51fa\u5982\u4e0a\u5f02\u5e38Subscribe timeout: (7500ms). Increase 'subscriptionsPerConnection' and\/or 'subscriptionConnectionPoolSize' parameters.<\/p>\n<p>\u89e3\u51b3\u65b9\u6cd5\uff1a \u5728\u521d\u59cb\u5316Redisson\u53ef\u4ee5\u53ef\u6307\u5b9a\u8fd9\u4e2a\u914d\u7f6e\u9879\u7684\u503c\u3002<\/p>\n<p>\u76f8\u5173\u53c2\u6570\u7684\u89e3\u91ca\u4ee5\u53ca\u9ed8\u8ba4\u503c\u8bf7\u53c2\u8003\u5b98\u7f51\uff1a<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u5173\u4e8e\u4f7f\u7528Redisson\u8ba2\u9605\u6570\u95ee\u9898 \u6536\u85cf \u672c\u7bc7\u6587\u7ae0\u7ed9\u5927\u5bb6\u5206\u4eab\u300a\u5173\u4e8e\u4f7f\u7528Redisson\u8ba2\u9605\u6570\u95ee\u9898\u300b\uff0c\u8986\u76d6\u4e86\u6570\u636e\u5e93\u7684\u5e38\u89c1\u57fa\u7840\u77e5\u8bc6\uff0c\u5176\u5b9e\u4e00\u4e2a\u8bed\u8a00\u7684\u5168\u90e8\u77e5\u8bc6\u70b9\u4e00\u7bc7\u6587\u7ae0\u662f\u4e0d\u53ef\u80fd\u8bf4\u5b8c\u7684\uff0c\u4f46\u5e0c\u671b\u901a\u8fc7\u8fd9\u4e9b\u95ee\u9898\uff0c\u8ba9\u8bfb\u8005\u5bf9\u81ea\u5df1\u7684\u638c\u63e1\u7a0b\u5ea6\u6709\u4e00\u5b9a\u7684\u8ba4\u8bc6(B \u6570)\uff0c\u4ece\u800c\u5f25\u8865\u81ea\u5df1\u7684\u4e0d\u8db3\uff0c\u66f4\u597d\u7684\u638c\u63e1\u5b83\u3002 \u4e00\u3001\u524d\u63d0 \u6700\u8fd1\u5728\u4f7f\u7528\u5206\u5e03\u5f0f\u9501redisson\u65f6\u9047\u5230\u4e00\u4e2a\u7ebf\u4e0a\u95ee\u9898\uff1a\u53d1\u73b0\u662fsubscriptionsPerConnection or subscriptionConnectionPoolSize \u7684\u5927\u5c0f\u4e0d\u591f\uff0c\u9700\u8981\u63d0\u9ad8\u914d\u7f6e\u624d\u80fd\u89e3\u51b3\u3002 \u4e8c\u3001\u6e90\u7801\u5206\u6790 \u4e0b\u9762\u5bf9\u5176\u6e90\u7801\u8fdb\u884c\u5206\u6790\uff0c\u624d\u80fd\u627e\u5230\u5230\u5e95\u662f\u4ec0\u4e48\u903b\u8f91\u5bfc\u81f4\u95ee\u9898\u6240\u5728\uff1a 1\u3001RedissonLock#lock() \u65b9\u6cd5 private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException { &nbsp; &nbsp; &nbsp; &nbsp; long threadId = Thread.currentThread().getId(); &nbsp; &nbsp; &nbsp; &nbsp; \/\/ \u5c1d\u8bd5\u83b7\u53d6\uff0c\u5982\u679cttl == null\uff0c\u5219\u8868\u793a\u83b7\u53d6\u9501\u6210\u529f &nbsp; &nbsp; &nbsp; &nbsp; Long ttl = tryAcquire(leaseTime, unit, threadId); &nbsp; &nbsp; &nbsp; &nbsp; \/\/ [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[],"class_list":["post-67722","post","type-post","status-publish","format-standard","hentry","category-os"],"_links":{"self":[{"href":"https:\/\/fwq.ai\/blog\/wp-json\/wp\/v2\/posts\/67722","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fwq.ai\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fwq.ai\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fwq.ai\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fwq.ai\/blog\/wp-json\/wp\/v2\/comments?post=67722"}],"version-history":[{"count":0,"href":"https:\/\/fwq.ai\/blog\/wp-json\/wp\/v2\/posts\/67722\/revisions"}],"wp:attachment":[{"href":"https:\/\/fwq.ai\/blog\/wp-json\/wp\/v2\/media?parent=67722"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fwq.ai\/blog\/wp-json\/wp\/v2\/categories?post=67722"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fwq.ai\/blog\/wp-json\/wp\/v2\/tags?post=67722"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}