文章列表

Redis 分布式锁如何自动续期

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding-right: 10px;padding-left: 10px;outline: 0px;white-space: normal;background-color: rgb(255, 255, 255);font-size: 16px;color: black;line-height: 1.6;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;"> <h3 data-tool="mdnice编辑器" style="margin-top: 40px;margin-bottom: 20px;outline: 0px;font-weight: bold;font-size: 19.2px;line-height: 1.5;color: rgb(63, 63, 63);">Redis 实现分布式锁</h3> <ul data-tool="mdnice编辑器" class="list-paddingleft-2" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;outline: 0px;"> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">指定一个 key 作为锁标记,存入 Redis 中,指定一个 唯一的用户标识作为 value。</p> </section></li> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">当 key 不存在时才能设置值,确保同一时间只有一个客户端进程获得锁,满足互斥性特性。</p> </section></li> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">设置一个过期时间,防止因系统异常导致没能删除这个 key,满足防死锁特性。</p> </section></li> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">当处理完业务之后需要清除这个 key 来释放锁,清除 key 时需要校验 value 值,需要满足只有加锁的人才能释放锁 。</p> </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 40px;margin-bottom: 20px;outline: 0px;font-weight: bold;font-size: 19.2px;line-height: 1.5;color: rgb(63, 63, 63);">问题</h3> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">如果这个锁的过期时间是30秒,但是业务运行超过了30秒,比如40秒,当业务运行到30秒的时候,锁过期了,其他客户端拿到了这个锁,怎么办</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">我们可以设置一个合理的过期时间,让业务能够在这个时间内完成业务逻辑,但LockTime的设置原本就很不容易。</p> <ul data-tool="mdnice编辑器" class="list-paddingleft-2" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;outline: 0px;"> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">LockTime设置过小,锁自动超时的概率就会增加,锁异常失效的概率也就会增加;</p> </section></li> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">LockTime设置过大,万一服务出现异常无法正常释放锁,那么出现这种异常锁的时间也就越长。</p> </section></li> </ul> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">我们只能通过经验去配置,一个可以接受的值,基本上是这个服务历史上的平均耗时再增加一定的buff。总体来说,设置一个合理的过期时间并不容易</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">我们也可以不设置过期时间,让业务运行结束后解锁,但是如果客户端出现了异常结束了或宕机了,那么这个锁就无法解锁,变成死锁;</p> <h3 data-tool="mdnice编辑器" style="margin-top: 40px;margin-bottom: 20px;outline: 0px;font-weight: bold;font-size: 19.2px;line-height: 1.5;color: rgb(63, 63, 63);">自动续期</h3> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">我们可以先给锁设置一个LockTime,然后启动一个守护线程,让守护线程在一段时间后,重新去设置这个锁的LockTime。</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">看起来很简单,但实现起来并不容易</p> <ul data-tool="mdnice编辑器" class="list-paddingleft-2" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;outline: 0px;"> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">和释放锁的情况一样,我们需要先判断持有锁客户端是否有变化。否则会造成无论谁持有锁,守护线程都会去重新设置锁的LockTime。</p> </section></li> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">守护线程要在合理的时间再去重新设置锁的LockTime,否则会造成资源的浪费。不能动不动就去续。</p> </section></li> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">如果持有锁的线程已经处理完业务了,那么守护线程也应该被销毁。不能业务运行结束了,守护者还在那里继续运行,浪费资源。</p> </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 40px;margin-bottom: 20px;outline: 0px;font-weight: bold;font-size: 19.2px;line-height: 1.5;color: rgb(63, 63, 63);">看门狗</h3> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">Redisson的看门狗机制就是这种机制实现自动续期的</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.3617407071622847" src="/upload/16458d6eb4710c8f724382e2707be38a.png" data-type="png" data-w="1103" style="margin-right: auto;margin-left: auto;outline: 0px;display: block;box-sizing: border-box !important;width: 677px !important;visibility: visible !important;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 40px;margin-bottom: 20px;outline: 0px;font-weight: bold;font-size: 19.2px;line-height: 1.5;color: rgb(63, 63, 63);">Redissson tryLock</h3> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;"><code style="padding: 16px;outline: 0px;overflow-x: auto;color: rgb(171, 178, 191);background: rgb(40, 44, 52);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;"><span style="outline: 0px;line-height: 26px;"><span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">public</span>&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">boolean</span>&nbsp;<span style="outline: 0px;color: rgb(97, 174, 238);line-height: 26px;">tryLock</span><span style="outline: 0px;line-height: 26px;">(<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">long</span>&nbsp;waitTime,&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">long</span>&nbsp;leaseTime,&nbsp;TimeUnit&nbsp;unit)</span>&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">throws</span>&nbsp;InterruptedException&nbsp;</span>{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">long</span>&nbsp;time&nbsp;=&nbsp;unit.toMillis(waitTime);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">long</span>&nbsp;current&nbsp;=&nbsp;System.currentTimeMillis();<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">long</span>&nbsp;threadId&nbsp;=&nbsp;Thread.currentThread().getId();<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//&nbsp;1.尝试获取锁</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Long&nbsp;ttl&nbsp;=&nbsp;tryAcquire(leaseTime,&nbsp;unit,&nbsp;threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//&nbsp;lock&nbsp;acquired</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(ttl&nbsp;==&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">null</span>)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">return</span>&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">true</span>;<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;"><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//&nbsp;申请锁的耗时如果大于等于最大等待时间,则申请锁失败.</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;time&nbsp;-=&nbsp;System.currentTimeMillis()&nbsp;-&nbsp;current;<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(time&nbsp;&lt;=&nbsp;<span style="outline: 0px;color: rgb(209, 154, 102);line-height: 26px;">0</span>)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;acquireFailed(threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">return</span>&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">false</span>;<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;"><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;current&nbsp;=&nbsp;System.currentTimeMillis();<br style="outline: 0px;"><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/**<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;* 2.订阅锁释放事件,并通过 await 方法阻塞等待锁释放,有效的解决了无效的锁申请浪费资源的问题:<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;基于信息量,当锁被其它资源占用时,当前线程通过&nbsp;Redis&nbsp;的&nbsp;channel&nbsp;订阅锁的释放事件,一旦锁释放会发消息通知待等待的线程进行竞争.<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;当&nbsp;this.await&nbsp;返回&nbsp;false,说明等待时间已经超出获取锁最大等待时间,取消订阅并返回获取锁失败.<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;当&nbsp;this.await&nbsp;返回&nbsp;true,进入循环尝试获取锁.<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RFuture&lt;RedissonLockEntry&gt;&nbsp;subscribeFuture&nbsp;=&nbsp;subscribe(threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//&nbsp;await&nbsp;方法内部是用&nbsp;CountDownLatch&nbsp;来实现阻塞,获取&nbsp;subscribe&nbsp;异步执行的结果(应用了&nbsp;Netty&nbsp;的&nbsp;Future)</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(!subscribeFuture.await(time,&nbsp;TimeUnit.MILLISECONDS))&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(!subscribeFuture.cancel(<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">false</span>))&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;subscribeFuture.onComplete((res,&nbsp;e)&nbsp;-&gt;&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(e&nbsp;==&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">null</span>)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsubscribe(subscribeFuture,&nbsp;threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;acquireFailed(threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">return</span>&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">false</span>;<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;"><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">try</span>&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//&nbsp;计算获取锁的总耗时,如果大于等于最大等待时间,则获取锁失败.</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;time&nbsp;-=&nbsp;System.currentTimeMillis()&nbsp;-&nbsp;current;<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(time&nbsp;&lt;=&nbsp;<span style="outline: 0px;color: rgb(209, 154, 102);line-height: 26px;">0</span>)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;acquireFailed(threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">return</span>&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">false</span>;<br style="outline: 0px;"><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;"><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/**<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;3.收到锁释放的信号后,在最大等待时间之内,循环一次接着一次的尝试获取锁<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;获取锁成功,则立马返回&nbsp;true,<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;若在最大等待时间之内还没获取到锁,则认为获取锁失败,返回&nbsp;false&nbsp;结束循环<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">while</span>&nbsp;(<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">true</span>)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">long</span>&nbsp;currentTime&nbsp;=&nbsp;System.currentTimeMillis();<br style="outline: 0px;"><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//&nbsp;再次尝试获取锁</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ttl&nbsp;=&nbsp;tryAcquire(leaseTime,&nbsp;unit,&nbsp;threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//&nbsp;lock&nbsp;acquired</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(ttl&nbsp;==&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">null</span>)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">return</span>&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">true</span>;<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//&nbsp;超过最大等待时间则返回&nbsp;false&nbsp;结束循环,获取锁失败</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;time&nbsp;-=&nbsp;System.currentTimeMillis()&nbsp;-&nbsp;currentTime;<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(time&nbsp;&lt;=&nbsp;<span style="outline: 0px;color: rgb(209, 154, 102);line-height: 26px;">0</span>)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;acquireFailed(threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">return</span>&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">false</span>;<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;"><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/**<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;* 6.阻塞等待锁(通过信号量(共享锁)阻塞,等待解锁消息):<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;currentTime&nbsp;=&nbsp;System.currentTimeMillis();<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(ttl&nbsp;&gt;=&nbsp;<span style="outline: 0px;color: rgb(209, 154, 102);line-height: 26px;">0</span>&nbsp;&amp;&amp;&nbsp;ttl&nbsp;&lt;&nbsp;time)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//如果剩余时间(ttl)小于wait time ,就在 ttl 时间内,从Entry的信号量获取一个许可(除非被中断或者一直没有可用的许可)。</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getEntry(threadId).getLatch().tryAcquire(ttl,&nbsp;TimeUnit.MILLISECONDS);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">else</span>&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//则就在wait&nbsp;time&nbsp;时间范围内等待可以通过信号量</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getEntry(threadId).getLatch().tryAcquire(time,&nbsp;TimeUnit.MILLISECONDS);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;"><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//&nbsp;更新剩余的等待时间(最大等待时间-已经消耗的阻塞时间)</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;time&nbsp;-=&nbsp;System.currentTimeMillis()&nbsp;-&nbsp;currentTime;<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(time&nbsp;&lt;=&nbsp;<span style="outline: 0px;color: rgb(209, 154, 102);line-height: 26px;">0</span>)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;acquireFailed(threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">return</span>&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">false</span>;<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">finally</span>&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//&nbsp;7.无论是否获得锁,都要取消订阅解锁消息</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsubscribe(subscribeFuture,&nbsp;threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">return</span>&nbsp;get(tryLockAsync(waitTime,&nbsp;leaseTime,&nbsp;unit));<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;"></code></pre> <ul data-tool="mdnice编辑器" class="list-paddingleft-2" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;outline: 0px;"> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">尝试获取锁,返回 null 则说明加锁成功,返回一个数值,则说明已经存在该锁,ttl 为锁的剩余存活时间。</p> </section></li> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">如果此时客户端 2 进程获取锁失败,那么使用客户端 2 的线程 id(其实本质上就是进程 id)通过 Redis 的 channel 订阅锁释放的事件。如果等待的过程中一直未等到锁的释放事件通知,当超过最大等待时间则获取锁失败,返回 false,也就是第 39 行代码。如果等到了锁的释放事件的通知,则开始进入一个不断重试获取锁的循环。</p> </section></li> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">循环中每次都先试着获取锁,并得到已存在的锁的剩余存活时间。如果在重试中拿到了锁,则直接返回。如果锁当前还是被占用的,那么等待释放锁的消息,具体实现使用了信号量 Semaphore 来阻塞线程,当锁释放并发布释放锁的消息后,信号量的 release() 方法会被调用,此时被信号量阻塞的等待队列中的一个线程就可以继续尝试获取锁了。</p> </section></li> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">当锁正在被占用时,等待获取锁的进程并不是通过一个 while(true) 死循环去获取锁,而是利用了 Redis 的发布订阅机制,通过 await 方法阻塞等待锁的进程,有效的解决了无效的锁申请浪费资源的问题。</p> </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 40px;margin-bottom: 20px;outline: 0px;font-weight: bold;font-size: 19.2px;line-height: 1.5;color: rgb(63, 63, 63);">看门狗如何自动续期</h3> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">Redisson看门狗机制, 只要客户端加锁成功,就会启动一个 Watch Dog。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;"><code style="padding: 16px;outline: 0px;overflow-x: auto;color: rgb(171, 178, 191);background: rgb(40, 44, 52);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;"><span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">private</span>&nbsp;&lt;T&gt;&nbsp;<span style="outline: 0px;line-height: 26px;">RFuture&lt;Long&gt;&nbsp;<span style="outline: 0px;color: rgb(97, 174, 238);line-height: 26px;">tryAcquireAsync</span><span style="outline: 0px;line-height: 26px;">(<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">long</span>&nbsp;leaseTime,&nbsp;TimeUnit&nbsp;unit,&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">long</span>&nbsp;threadId)</span>&nbsp;</span>{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(leaseTime&nbsp;!=&nbsp;-<span style="outline: 0px;color: rgb(209, 154, 102);line-height: 26px;">1</span>)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">return</span>&nbsp;tryLockInnerAsync(leaseTime,&nbsp;unit,&nbsp;threadId,&nbsp;RedisCommands.EVAL_LONG);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;RFuture&lt;Long&gt;&nbsp;ttlRemainingFuture&nbsp;=&nbsp;tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),&nbsp;TimeUnit.MILLISECONDS,&nbsp;threadId,&nbsp;RedisCommands.EVAL_LONG);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;ttlRemainingFuture.onComplete((ttlRemaining,&nbsp;e)&nbsp;-&gt;&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(e&nbsp;!=&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">null</span>)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">return</span>;<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;"><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">//&nbsp;lock&nbsp;acquired</span><br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(ttlRemaining&nbsp;==&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">null</span>)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scheduleExpirationRenewal(threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;});<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">return</span>&nbsp;ttlRemainingFuture;<br style="outline: 0px;">}<br style="outline: 0px;"></code></pre> <ul data-tool="mdnice编辑器" class="list-paddingleft-2" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;outline: 0px;"> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> leaseTime 必须是 -1 才会开启 Watch Dog 机制,如果需要开启 Watch Dog 机制就必须使用默认的加锁时间为 30s。 </section></li> <li style="outline: 0px;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;line-height: 26px;color: rgb(1, 1, 1);"> 如果你自己自定义时间,超过这个时间,锁就会自定释放,并不会自动续期。 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 40px;margin-bottom: 20px;outline: 0px;font-weight: bold;font-size: 19.2px;line-height: 1.5;color: rgb(63, 63, 63);">续期原理</h3> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">续期原理其实就是用lua脚本,将锁的时间重置为30s</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;"><code style="padding: 16px;outline: 0px;overflow-x: auto;color: rgb(171, 178, 191);background: rgb(40, 44, 52);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;"><span style="outline: 0px;line-height: 26px;"><span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">private</span>&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">void</span>&nbsp;<span style="outline: 0px;color: rgb(97, 174, 238);line-height: 26px;">scheduleExpirationRenewal</span><span style="outline: 0px;line-height: 26px;">(<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">long</span>&nbsp;threadId)</span>&nbsp;</span>{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;ExpirationEntry&nbsp;entry&nbsp;=&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">new</span>&nbsp;ExpirationEntry();<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;ExpirationEntry&nbsp;oldEntry&nbsp;=&nbsp;EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(),&nbsp;entry);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">if</span>&nbsp;(oldEntry&nbsp;!=&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">null</span>)&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oldEntry.addThreadId(threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">else</span>&nbsp;{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;entry.addThreadId(threadId);<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;renewExpiration();<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;">}<br style="outline: 0px;"><br style="outline: 0px;"><span style="outline: 0px;line-height: 26px;"><span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">protected</span>&nbsp;RFuture&lt;Boolean&gt;&nbsp;<span style="outline: 0px;color: rgb(97, 174, 238);line-height: 26px;">renewExpirationAsync</span><span style="outline: 0px;line-height: 26px;">(<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">long</span>&nbsp;threadId)</span>&nbsp;</span>{<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(198, 120, 221);line-height: 26px;">return</span>&nbsp;commandExecutor.evalWriteAsync(getName(),&nbsp;LongCodec.INSTANCE,&nbsp;RedisCommands.EVAL_BOOLEAN,<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(152, 195, 121);line-height: 26px;">"if&nbsp;(redis.call('hexists',&nbsp;KEYS[1],&nbsp;ARGV[2])&nbsp;==&nbsp;1)&nbsp;then&nbsp;"</span>&nbsp;+<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(152, 195, 121);line-height: 26px;">"redis.call('pexpire',&nbsp;KEYS[1],&nbsp;ARGV[1]);&nbsp;"</span>&nbsp;+<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(152, 195, 121);line-height: 26px;">"return&nbsp;1;&nbsp;"</span>&nbsp;+<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(152, 195, 121);line-height: 26px;">"end;&nbsp;"</span>&nbsp;+<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;color: rgb(152, 195, 121);line-height: 26px;">"return&nbsp;0;"</span>,<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Collections.&lt;Object&gt;singletonList(getName()),<br style="outline: 0px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;internalLockLeaseTime,&nbsp;getLockName(threadId));<br style="outline: 0px;">}<br style="outline: 0px;"></code></pre> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">Watch Dog 机制其实就是一个后台定时任务线程,获取锁成功之后,会将持有锁的线程放入到一个&nbsp;<code style="margin-right: 2px;margin-left: 2px;padding: 3px 5px;outline: 0px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 14.4px;border-radius: 2px;">RedissonLock.EXPIRATION_RENEWAL_MAP</code>里面,然后每隔 10 秒 (<code style="margin-right: 2px;margin-left: 2px;padding: 3px 5px;outline: 0px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 14.4px;border-radius: 2px;">internalLockLeaseTime / 3</code>) 检查一下,如果客户端 还持有锁 key(判断客户端是否还持有 key,其实就是遍历&nbsp;<code style="margin-right: 2px;margin-left: 2px;padding: 3px 5px;outline: 0px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 14.4px;border-radius: 2px;">EXPIRATION_RENEWAL_MAP</code>&nbsp;里面线程 id 然后根据线程 id 去 Redis 中查,如果存在就会延长 key 的时间),那么就会不断的延长锁 key 的生存时间。</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);">如果服务宕机了,Watch Dog 机制线程也就没有了,此时就不会延长 key 的过期时间,到了 30s 之后就会自动过期了,其他线程就可以获取到锁。</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;line-height: 1.6;color: rgb(63, 63, 63);"><br></p> </section>

如何自己实现 Spring AOP?

作者:微信小助手

<section class="mp_profile_iframe_wrp" data-mpa-powered-by="yiban.io"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzA5MTU0OTY0Ng==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/zc3KLDBfJlmPt0J5PXYOoiaG8wsQPZrLevbxMZSfgQ0YypNYaicnbS0P9UicluuOySLSP4CjTcRUVHCZzYeXQ9WlA/0?wx_fmt=png" data-nickname="Java派" data-alias="javapai" data-signature="专注Java相关技术栈:Spring全家筒、Docker、k8s、Mysql、集群、微服务、中间件等知识。" data-from="0"></mpprofile> </section> <section style="margin: 5px 8px 1em;padding-top: 8px;padding-bottom: 8px;outline: 0px;white-space: normal;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.75px;background-color: rgb(255, 255, 255);font-size: 16px;color: black;line-height: 2em;"> <span style="font-size: 14px;"></span> <br> </section> <section style="margin: 5px 8px 1em;padding-top: 8px;padding-bottom: 8px;outline: 0px;white-space: normal;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.75px;background-color: rgb(255, 255, 255);font-size: 16px;color: black;line-height: 2em;"> <span style="font-size: 14px;">正好遇到了一道这样的题:抛开</span> <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 179, 120);"><span style="font-size: 14px;">Spring</span></code> <span style="font-size: 14px;">来说,如何自己实现</span> <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 179, 120);"><span style="font-size: 14px;">Spring AOP</span></code> <span style="font-size: 14px;">?</span> <span style="font-size: 14px;">就喜欢这样的题,能把那些天天写增删改查从来不思考的人给</span> <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 179, 120);">PK</code> <span style="font-size: 14px;">下去,今天就和大家一切学习代理模式与</span> <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 179, 120);">Spring AOP</code> <span style="font-size: 14px;">。</span> </section> <h1 data-tool="mdnice编辑器" style="margin-bottom: 20px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);margin-left: 8px;margin-right: 8px;line-height: 2em;"><span style="outline: 0px;color: rgb(123, 12, 0);font-size: 14px;"><strong style="outline: 0px;">| 代理与装饰器</strong></span></h1> <h2 data-tool="mdnice编辑器" style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);margin-left: 8px;margin-right: 8px;line-height: 2em;"><span style="font-size: 14px;"><strong style="outline: 0px;"><span style="outline: 0px;">场景描述</span></strong></span></h2> <p style="margin: 1em 8px;padding-top: 8px;padding-bottom: 8px;outline: 0px;white-space: normal;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.75px;background-color: rgb(255, 255, 255);font-size: 16px;color: black;line-height: 2em;"><span style="font-size: 14px;">代理,即替代之意,可替代所有功能,即和原类实现相同的<strong style="outline: 0px;color: rgb(53, 179, 120);">规范</strong>。</span></p> <p style="margin: 1em 8px;padding-top: 8px;padding-bottom: 8px;outline: 0px;white-space: normal;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.75px;background-color: rgb(255, 255, 255);font-size: 16px;color: black;line-height: 2em;"><span style="font-size: 14px;">代理模式和装饰器模式很像,之前的装饰器讲的不是很好,这里换个例子再讲一遍。</span></p> <blockquote data-tool="mdnice编辑器" style="outline: 0px;color: rgba(0, 0, 0, 0.5);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);"> <p style="outline: 0px;margin-left: 8px;margin-right: 8px;line-height: 2em;"><span style="outline: 0px;font-size: 14px;"><strong style="outline: 0px;">宁静的午后,来到咖啡馆,想喝一杯咖啡。</strong></span></p> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);margin-left: 8px;margin-right: 8px;line-height: 2em;"><span style="font-size: 14px;"><strong style="outline: 0px;"><span style="outline: 0px;">基础实现</span></strong></span></h2> <p style="margin: 1em 8px;padding-top: 8px;padding-bottom: 8px;outline: 0px;white-space: normal;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);font-size: 16px;color: black;line-height: 2em;"><span style="outline: 0px;color: rgb(58, 58, 58);font-size: 14px;">给你一个咖啡接口:</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span>&nbsp;<span class="code-snippet__keyword">interface</span>&nbsp;<span class="code-snippet__title">Coffee</span>&nbsp;{</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 打印当前咖啡的原材料,即咖啡里有什么</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__function"><span class="code-snippet__keyword">void</span> <span class="code-snippet__title">printMaterial</span>(<span class="code-snippet__params"></span>)</span>;</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p style="margin: 1em 8px;padding-top: 8px;padding-bottom: 8px;outline: 0px;white-space: normal;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);font-size: 16px;color: black;line-height: 2em;"><span style="outline: 0px;color: rgb(58, 58, 58);font-size: 14px;">一个默认的苦咖啡的实现:</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="java"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> <span class="code-snippet__class"><span class="code-snippet__keyword">class</span> <span class="code-snippet__title">BitterCoffee</span> <span class="code-snippet__keyword">implements</span> <span class="code-snippet__title">Coffee</span> </span>{</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__meta">@Override</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__function"><span class="code-snippet__keyword">public</span> <span class="code-snippet__keyword">void</span> <span class="code-snippet__title">printMaterial</span><span class="code-snippet__params">()</span> </span>{</span></code><code><span class="code-snippet_outer"> System.out.println(<span class="code-snippet__string">"咖啡"</span>);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p style="margin: 1em 8px;padding-top: 8px;padding-bottom: 8px;outline: 0px;white-space: normal;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);font-size: 16px;color: black;line-height: 2em;"><span style="outline: 0px;color: rgb(58, 58, 58);font-size: 14px;">默认的点餐逻辑:</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> <span class="code-snippet__keyword">class</span> <span class="code-snippet__title">Main</span> {</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__function"><span class="code-snippet__keyword">public</span> <span class="code-snippet__keyword">static</span> <span class="code-snippet__keyword">void</span> <span class="code-snippet__title">main</span>(<span class="code-snippet__params">String[] args</span>)</span> {</span></code><code><span class="code-snippet_outer"> Coffee coffee = <span class="code-snippet__keyword">new</span> BitterCoffee();</span></code><code><span class="code-snippet_outer"> coffee.printMaterial();</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p style="margin: 1em 8px;padding-top: 8px;padding-bottom: 8px;outline: 0px;white-space: normal;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);font-size: 16px;color: black;line-height: 2em;"><span style="font-size: 14px;">点一杯咖啡。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 2em;"><br></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;white-space: normal;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="281" data-cropsely1="0" data-cropsely2="143" data-fileid="100063486" data-ratio="0.5088967971530249" src="/upload/fd6dd3343bde9977970cb9b159d45c3d.png" data-type="png" data-w="281" style="margin-right: auto;margin-left: auto;outline: 0px;display: block;box-sizing: border-box !important;visibility: visible !important;width: 227px !important;height: auto !important;height: auto !important;"> </figure> <p><br></p> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);margin-left: 8px;margin-right: 8px;line-height: 2em;"><span style="font-size: 14px;"><strong style="outline: 0px;"><span style="outline: 0px;">装饰器模式</span></strong></span></h2> <p style="margin: 1em 8px;padding-top: 8px;padding-bottom: 8px;outline: 0px;white-space: normal;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);font-size: 16px;color: black;line-height: 2em;"><span style="font-size: 14px;">优雅的服务生把咖啡端了上来,抿了一口,有些苦。</span><span style="color: rgb(58, 58, 58);font-size: 14px;">想加点糖,对服务生说:“您好,请为我的咖啡加些糖”。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="java"><code><span class="code-snippet_outer"><span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 糖装饰器,用来给咖啡加糖</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> <span class="code-snippet__class"><span class="code-snippet__keyword">class</span> <span class="code-snippet__title">SugarDecorator</span> <span class="code-snippet__keyword">implements</span> <span class="code-snippet__title">Coffee</span> </span>{</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 持有的咖啡对象</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">private</span> <span class="code-snippet__keyword">final</span> Coffee coffee;</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__function"><span class="code-snippet__keyword">public</span> <span class="code-snippet__title">SugarDecorator</span><span class="code-snippet__params">(Coffee coffee)</span> </span>{</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">this</span>.coffee = coffee;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__meta">@Override</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__function"><span class="code-snippet__keyword">public</span> <span class="code-snippet__keyword">void</span> <span class="code-snippet__title">printMaterial</span><span class="code-snippet__params">()</span> </span>{</span></code><code><span class="code-snippet_outer"> System.out.println(<span class="code-snippet__string">"糖"</span>);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">this</span>.coffee.printMaterial();</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p style="margin: 1em 8px;padding-top: 8px;padding-bottom: 8px;outline: 0px;white-space: normal;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.75px;background-color: rgb(255, 255, 255);font-size: 16px;color: black;line-height: 2em;"><span style="outline: 0px;color: rgb(58, 58, 58);font-size: 14px;">然后服务生就拿走了我的咖啡,去使用</span><code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 179, 120);"><span style="font-size: 14px;">SugarDecorator</span></code><span style="outline: 0px;color: rgb(58, 58, 58);font-size: 14px;">为咖啡加糖,最后把加好糖的咖啡给我。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> <span class="code-snippet__keyword">class</span> <span class="code-snippet__title">Main</span> {</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__function"><span class="code-snippet__keyword">public</span> <span class="code-snippet__keyword">static</span> <span class="code-snippet__keyword">void</span> <span class="code-snippet__title">main</span>(<span class="code-snippet__params">String[] args</span>)</span> {</span></code><code><span class="code-snippet_outer"> Coffee coffee = <span class="code-snippet__keyword">new</span> BitterCoffee();</span></code><code><span class="code-snippet_outer"> coffee = <span class="code-snippet__keyword">new</span> SugarDecorator(coffee);</span></code><code><span class="code-snippet_outer"> coffee.printMaterial();</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p style="margin: 1em 8px;padding-top: 8px;padding-bottom: 8px;outline: 0px;white-space: normal;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);font-size: 16px;color: black;line-height: 2em;"><span style="font-size: 14px;">看一看咖啡的成分,对的,确实加上了糖!</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 2em;"><br></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;white-space: normal;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="291" data-cropsely1="0" data-cropsely2="165" data-fileid="100063488" data-ratio="0.5670103092783505" src="/upload/364a73fdec421575f360eabf053f3ee6.png" data-type="png" data-w="291" style="margin-right: auto;margin-left: auto;outline: 0px;border-width: 1px;border-style: solid;border-color: rgb(238, 237, 235);display: block;box-sizing: border-box !important;background-color: rgb(238, 237, 235) !important;background-size: 22px !important;background-position: center center !important;background-repeat: no-repeat !important;visibility: visible !important;width: 231px !im

3万字 | 34 图 | Netty | 内核角度看IO模型

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0px;word-break: break-word;overflow-wrap: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;" data-mpa-powered-by="yiban.io"> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.3850574712643678" data-s="300,640" src="/upload/5e5eb8f6045e7ea851c094885a85782e.png" data-type="png" data-w="1740" style=""></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><span style="color: rgb(255, 104, 39);">大家好,我是悟空呀。</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><span style="color: rgb(255, 104, 39);letter-spacing: 0px;">今天我们从内核角度来聊下 Netty 的 IO 模型。这篇很长,建议收藏起来慢慢看~</span><br></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">我们都知道Netty是一个高性能异步事件驱动的网络框架。<span style="letter-spacing: 0px;">它的设计异常优雅简洁,扩展性高,稳定性强。拥有非常详细完整的用户文档。</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">同时内置了很多非常有用的模块基本上做到了开箱即用,用户只需要编写短短几行代码,就可以快速构建出一个具有<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">高吞吐</code>,<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">低延时</code>,<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">更少的资源消耗</code>,<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">高性能(非必要的内存拷贝最小化)</code>等特征的高并发网络应用程序。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">本文我们来探讨下支持Netty具有<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">高吞吐</code>,<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">低延时</code>特征的基石----netty的<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">网络IO模型</code>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">由Netty的<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">网络IO模型</code>开始,我们来正式揭开本系列Netty源码解析的序幕。</p> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzAwMjI0ODk0NA==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/SfAHMuUxqJ2JicQpKzblLHz64qoibVa3ATNA4rH8mIYXAF3OErAzxFKHzf5qiaiblb4rAMuAXXMJHEcKcvaHv4ia9rA/0?wx_fmt=png" data-nickname="悟空聊架构" data-alias="PassJava666" data-signature="用故事讲解分布式、架构。 《 JVM 性能调优实战》专栏作者, 《Spring Cloud 实战 PassJava》开源作者, 自主开发了 PMP 刷题小程序。" data-from="0"></mpprofile> </section> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">网络包接收流程</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.8943548387096775" src="/upload/9f8018def8b32bfb69c9ecc23022742f.png" data-type="png" data-w="1240" style="display: block;margin-right: auto;margin-left: auto;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> 网络包收发过程.png </figcaption> </figure> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 当 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">网络数据帧</code>通过网络传输到达网卡时,网卡会将网络数据帧通过 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">DMA的方式</code>放到 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">环形缓冲区RingBuffer</code>中。 </section></li> </ul> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">RingBuffer</code>是网卡在启动的时候<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">分配和初始化</code>的<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">环形缓冲队列</code>。当<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">RingBuffer满</code>的时候,新来的数据包就会被<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">丢弃</code>。我们可以通过<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">ifconfig</code>命令查看网卡收发数据包的情况。其中<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">overruns</code>数据项表示当<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">RingBuffer满</code>时,被<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">丢弃的数据包</code>。如果发现出现丢包情况,可以通过<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">ethtool命令</code>来增大RingBuffer长度。</p> </blockquote> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 当 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">DMA操作完成</code>时,网卡会向CPU发起一个 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">硬中断</code>,告诉 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">CPU</code>有网络数据到达。CPU调用网卡驱动注册的 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">硬中断响应程序</code>。网卡硬中断响应程序会为网络数据帧创建内核数据结构 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">sk_buffer</code>,并将网络数据帧 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">拷贝</code>到 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">sk_buffer</code>中。然后发起 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">软中断请求</code>,通知 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">内核</code>有新的网络数据帧到达。 </section></li> </ul> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">sk_buff</code>缓冲区,是一个维护网络帧结构的<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">双向链表</code>,链表中的每一个元素都是一个<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">网络帧</code>。虽然 TCP/IP 协议栈分了好几层,但上下不同层之间的传递,实际上只需要操作这个数据结构中的指针,而<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">无需进行数据复制</code>。</p> </blockquote> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 内核线程 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">ksoftirqd</code>发现有软中断请求到来,随后调用网卡驱动注册的 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">poll函数</code>, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">poll函数</code>将 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">sk_buffer</code>中的 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">网络数据包</code>送到内核协议栈中注册的 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">ip_rcv函数</c

实战!阿里神器 Seata 实现 TCC模式 解决分布式事务,真香!

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">大家好,我是不才陈某~</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这是<a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU3MDAzNDg1MA==&amp;action=getalbum&amp;album_id=2042874937312346114&amp;scene=126#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">《Spring Cloud 进阶》</a>第<span style="font-weight: 700;color: rgb(248, 57, 41);">19</span>篇文章,往期文章如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247493854&amp;idx=1&amp;sn=4b3fb7f7e17a76000733899f511ef915&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">五十五张图告诉你微服务的灵魂摆渡者Nacos究竟有多强?</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247496653&amp;idx=1&amp;sn=7185077b3bdc1d094aef645d677ec472&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">openFeign夺命连环9问,这谁受得了?</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247496772&amp;idx=1&amp;sn=8a88b998920bb9b665f52320cf94d9c7&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">阿里面试这样问:Nacos、Apollo、Config配置中心如何选型?这10个维度告诉你!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247497371&amp;idx=1&amp;sn=df5aa872452970f5f46efff5fc777b34&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">阿里面试败北:5种微服务注册中心如何选型?这几个维度告诉你!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247498039&amp;idx=1&amp;sn=3a3caee655ff015b46249bd51aa4dc79&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">阿里限流神器Sentinel夺命连环 17 问?</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247499421&amp;idx=1&amp;sn=a55797652284bafd9216ea981f4125e0&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">对比7种分布式事务方案,还是偏爱阿里开源的Seata,真香!(原理+实战)</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247499894&amp;idx=1&amp;sn=f1606e4c00fd15292269afe052f5bca2&amp;chksm=fcf71fbbcb8096ad349e6da50b0b9141964c2084d0a38eba977fe8baa3fbe8af3b20c7591110&amp;token=1887105114&amp;lang=zh_CN&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">Spring Cloud Gateway夺命连环10问?</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247500540&amp;idx=1&amp;sn=2967bf1f9fa2c4d5b94b7fe291b7869b&amp;chksm=fcf71d31cb8094271b5bbeca85cc03cf7d7c8bbf7c4d5b83a539e39c956e3f4015f91e7742b6&amp;token=2077958771&amp;lang=zh_CN&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">Spring Cloud Gateway 整合阿里 Sentinel网关限流实战!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247500757&amp;idx=1&amp;sn=ef71f10d5736029c92287f7894c842ed&amp;chksm=fcf71c18cb80950ec3144d66957a9d2914b51b8af22cf59a1ca3f9e2b561a11aca359d50d5a1&amp;scene=21&amp;cur_album_id=2042874937312346114#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">分布式链路追踪之Spring Cloud Sleuth夺命连环9问?</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247501079&amp;idx=1&amp;sn=438ef3a3d65fb4919b61cf6972827bec&amp;chksm=fcf71adacb8093ccc4c3d6dfb8860ca07aefbc761c1ed5126eb4a4e87548441841198db1f8e3&amp;scene=21&amp;cur_album_id=2042874937312346114#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">链路追踪自从用了SkyWalking,睡的真香!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247501590&amp;idx=1&amp;sn=149d1498504cda7cc391413f14cda9c2&amp;chksm=fcf718dbcb8091cd6907e86e148216bae0a2d5b1664b1e8cc2d3e2c96c38181cc778de5711b6&amp;scene=21&amp;cur_album_id=2042874937312346114#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">3本书了,7万+字,10篇文章,《Spring Cloud 进阶》基础版 PDF</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247502682&amp;idx=1&amp;sn=52a15b623ab6135c134b8262bd605946&amp;chksm=fcf71497cb809d81f1d2dbce76b3e00170f085306b2a2a67a807a6d9e2cf03bf1de3b8f203a2&amp;scene=21&amp;cur_album_id=2042874937312346114#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">妹子始终没搞懂OAuth2.0,今天整合Spring Cloud Security 一次说明白!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247502801&amp;idx=1&amp;sn=56b1af09bfa25d5e44193a7d75dfa623&amp;chksm=fcf7141ccb809d0a1b0b2d7f6d9893c7d3e560dd8996296276f0274d2578236ee87e9124810d&amp;scene=21&amp;cur_album_id=2042874937312346114#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">OAuth2.0实战!使用JWT令牌认证!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247502905&amp;idx=1&amp;sn=32ba3ae4e0a4097d238f64719c88b7f7&amp;chksm=fcf713f4cb809ae2ccb706b8e9f8184739d3c7b97d388467bfe9ff31a8c15768b6de05054f08&amp;scene=21&amp;cur_album_id=2042874937312346114#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">OAuth2.0实战!玩转认证、资源服务异常自定义这些骚操作!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247503249&amp;idx=1&amp;sn=b33ae3ff70a08b17ee0779d6ccb30b53&amp;chksm=fcf7125ccb809b4aa4985da09e620e06c606754e6a72681c93dcc88bdc9aa7ba0cb64f52dbc3&amp;token=1286998820&amp;lang=zh_CN&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">实战干货!Spring Cloud Gateway 整合 OAuth2.0 实现分布式统一认证授权!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247503263&amp;idx=1&amp;sn=edc475e36127792e35112c4746e53d37&amp;chksm=fcf71252cb809b44d7ae246e9503e1d6b91b29965c0cd7ebfc7da9c1a098fe186cd977736c40&amp;token=1144087588&amp;lang=zh_CN&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">字节面试这样问:跨库多表存在大量数据依赖问题有哪些解决方案?</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247504322&amp;idx=1&amp;sn=4b0a2488a4edcb025d0694604e86f840&amp;chksm=fcf70e0fcb808719b98a65891bc08e9490db09f07debd4521052978a319ab052f8a72b93c1a7&amp;token=284256295&amp;lang=zh_CN&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">实战!退出登录时如何借助外力使JWT令牌失效?</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247504442&amp;idx=1&amp;sn=48c1dd73c038e3d936db4e5134f7bbc2&amp;chksm=fcf70df7cb8084e1556cac092fdb68ffd6503cbb287485ef76e9941611d134258c38d03e890a&amp;token=780863812&amp;lang=zh_CN&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">实战!Spring Cloud Gateway集成 RBAC 权限模型实现动态权限控制!</a> </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">今天这篇文章介绍一下<span style="font-weight: 700;color: rgb(248, 57, 41);">Seata</span>如何实现<span style="font-weight: 700;color: rgb(248, 57, 41);">TCC</span>事务模式,文章目录如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.46111111111111114" src="/upload/241ec1b58de9c9aefb437cf4b66d9649.png" data-type="png" data-w="1080" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> 目录 </figcaption> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">什么是TCC模式?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">TCC(Try Confirm Cancel)方案是一种应用层面侵入业务的两阶段提交。是目前最火的一种柔性事务方案,其核心思想是:<span style="font-weight: 700;color: rgb(248, 57, 41);">针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作</span>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">TCC分为两个阶段,分别如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">第一阶段</span>:Try(尝试),主要是对业务系统做检测及资源预留 <span style="font-weight: 700;color: rgb(248, 57, 41);">(加锁,锁住资源)</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">第二阶段</span>:本阶段根据第一阶段的结果,决定是执行 <span style="font-weight: 700;color: rgb(248, 57, 41);">confirm</span>还是 <span style="font-weight: 700;color: rgb(248, 57, 41);">cancel</span> </section></li> <ol style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">Confirm</span>(确认):执行真正的业务(执行业务,释放锁) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">Cancle</span>(取消):是预留资源的取消(出问题,释放锁) </section></li> </ol> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.4546296296296296" src="/upload/a04e38632d3a82f6d3697b1cbe5e155c.png" data-type="png" data-w="1080" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> TCC </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">为了方便理解,下面以电商下单为例进行方案解析,这里把整个过程简单分为扣减库存,订单创建 2 个步骤,库存服务和订单服务分别在不同的服务器节点上。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">假设商品库存为 100,购买数量为 2,这里检查和更新库存的同时,冻结用户购买数量的库存,同时创建订单,订单状态为待确认。</p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;"><span style="display: none;"></span>①Try 阶段<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">TCC 机制中的 Try 仅是一个初步操作,它和后续的确认一起才能真正构成一个完整的业务逻辑,这个阶段主要完成:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 完成所有业务检查( 一致性 ) 。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 预留必须业务资源( 准隔离性 ) 。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> Try 尝试执行业务。 </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.685251798561151" src="/upload/905177e0146ebd06c3d546aef96a9870.png" data-type="png" data-w="556" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> Try阶段 </figcaption> </figure> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;"><span style="display: none;"></span>②Confirm / Cancel 阶段<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">根据 <span style="font-weight: 700;color: rgb(248, 57, 41);">Try</span> 阶段服务是否全部正常执行,继续执行确认操作(Confirm)或取消操作(Cancel)。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Confirm 和 Cancel 操作满足幂等性,如果 Confirm 或 Cancel 操作执行失败,将会不断重试直到执行完成。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Confirm:当 Try 阶段服务全部正常执行, 执行确认业务逻辑操作,业务如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.35555555555555557" src="/upload/4707fe8bfc30204b641dfd6980f43095.png" data-type="png" data-w="1080" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> Try-&gt;Confirm </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这里使用的资源一定是 Try 阶段预留的业务资源。在 TCC 事务机制中认为,如果在 Try 阶段能正常的预留资源,那 Confirm 一定能完整正确的提交。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Confirm 阶段也可以看成是对 Try 阶段的一个补充,Try+Confirm 一起组成了一个完整的业务逻辑。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Cancel:当 Try 阶段存在服务执行失败, 进入 Cancel 阶段,业务如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.35555555555555557" src="/upload/9948a43b9361e2578332ec40f9a235b1.png" data-type="png" data-w="1080" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> Try-Cancel </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Cancel 取消执行,释放 Try 阶段预留的业务资源,上面的例子中,Cancel 操作会把冻结的库存释放,并更新订单状态为取消。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">以上便是TCC模式的全部概念,这部分内容在陈某之前的文章也是详细的介绍过:<a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247499421&amp;idx=1&amp;sn=a55797652284bafd9216ea981f4125e0&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">对比7种分布式事务方案,还是偏爱阿里开源的Seata,真香!(原理+实战)</a></p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">TCC模式的三种类型?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">业内实际生产中对TCC模式进行了扩展,总结出了如下三种类型,其实从官方的定义中无此说法,不过是企业生产中根据实际的需求衍生出来的三种方案。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1、通用型 TCC 解决方案</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">通用型TCC解决方案是最经典的TCC事务模型的实现,正如第一节介绍的模型,所有的从业务都参与到主业务的决策中。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.8470948012232415" src="/upload/df2d38e16f192749e736ff0b39d44251.png" data-type="png" data-w="654" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> 通用型TCC </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">适用场景</span>:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">由于从业务服务是同步调用,其结果会影响到主业务服务的决策,因此通用型 TCC 分布式事务解决方案<span style="font-weight: 700;color: rgb(248, 57, 41);">适用于执行时间确定且较短的业务</span>,比如电商系统的三个核心服务:订单服务、账户服务、库存服务。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这个三个服务要么同时成功,要么同时失败。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.37524557956778" src="/upload/56f87396b68e213a7a05aa48aff6f646.png" data-type="png" data-w="1018" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当库存服务、账户服务的第二阶段调用完成后,整个分布式事务完成。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span styl

我被骗好久了!count(*) 性能最差?

作者:微信小助手

<section data-mpa-powered-by="yiban.io" style="white-space: normal;"> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 当我们对一张数据表中的记录进行统计的时候,习惯都会使用 count 函数来统计,但是 count 函数传入的参数有很多种,比如 count(1)、count( <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">*</code>)、count(字段) 等。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 到底哪种效率是最好的呢?是不是 count( <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">*</code>) 效率最差? </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 我曾经以为 count( <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">*</code>) 是效率最差的,因为认知上&nbsp; <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">selete * from t</code>&nbsp;会读取所有表中的字段,所以凡事带有&nbsp; <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">*</code>&nbsp;字符的就觉得会读取表中所有的字段,当时网上有很多博客也这么说。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 但是,当我深入 count 函数的原理后,被啪啪啪的打脸了! </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 不多说, 发车! </section> <section style="margin-right: 8px;margin-left: 8px;text-align: center;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.5764430577223089" data-s="300,640" data-type="png" data-w="2564" src="/upload/dd7240a8818d468875877c781a29574a.png"> </section> <h3 style="margin: 1.6em 8px;font-weight: bold;font-size: 1.3em;color: inherit;line-height: inherit;border-bottom: 2px solid rgb(65, 105, 225);"><span style="margin-right: 3px;padding: 3px 10px 1px;font-size: inherit;line-height: inherit;display: inline-block;font-weight: normal;background: rgb(65, 105, 225);color: rgb(255, 255, 255);border-top-right-radius: 3px;border-top-left-radius: 3px;">哪种 count 性能最好?</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid rgb(239, 235, 233);border-right: 20px solid transparent;"></span></h3> <blockquote style="padding: 15px 15px 15px 1rem;border-left-width: 5px;border-left-color: rgb(100, 149, 237);color: rgb(0, 0, 0);line-height: inherit;font-size: 0.9em;background: rgb(227, 242, 253);overflow: auto;overflow-wrap: normal;word-break: normal;"> <section style="margin-right: 8px;margin-left: 8px;font-size: inherit;color: inherit;line-height: inherit;"> 哪种 count 性能最好? </section> </blockquote> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 我先直接说结论: </section> <section style="margin-right: 8px;margin-left: 8px;text-align: center;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.28449744463373083" data-s="300,640" data-type="png" data-w="1174" src="/upload/e6b02d3476a130d7e9a4b9c7744c3574.png"> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 要弄明白这个,我们得要深入 count 的原理,以下内容基于常用的 innodb 存储引擎来说明。 </section> <blockquote style="padding: 15px 15px 15px 1rem;border-left-width: 5px;border-left-color: rgb(100, 149, 237);color: rgb(0, 0, 0);line-height: inherit;font-size: 0.9em;background: rgb(227, 242, 253);overflow: auto;overflow-wrap: normal;word-break: normal;"> <section style="margin-right: 8px;margin-left: 8px;font-size: inherit;color: inherit;line-height: inherit;"> count() 是什么? </section> </blockquote> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> count() 是一个聚合函数,函数的参数不仅可以是字段名,也可以是其他任意表达式,该函数作用是 <strong style="font-size: inherit;line-height: inherit;color: rgb(48, 79, 254);">统计符合查询条件的记录中,函数指定的参数不为 NULL 的记录有多少个</strong>。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 假设 count() 函数的参数是字段名,如下: </section> <pre style="font-size: inherit;color: inherit;line-height: inherit;"> <section style="margin-right: 8px;margin-left: 8px;padding: 0.5em;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;background: rgb(25, 23, 28);color: rgb(139, 135, 146);overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"> <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp; <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">count</span>( <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">name</span>)&nbsp; <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;t_order; <br> </section></pre> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 这条语句是统计「 t_order 表中,name 字段不为 NULL 的记录」有多少个。也就是说,如果某一条记录中的 name 字段的值为 NULL,则就不会被统计进去。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 再来假设 count() 函数的参数是数字 1 这个表达式,如下: </section> <pre style="font-size: inherit;color: inherit;line-height: inherit;"> <section style="margin-right: 8px;margin-left: 8px;padding: 0.5em;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;background: rgb(25, 23, 28);color: rgb(139, 135, 146);overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"> <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp; <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">count</span>( <span style="font-size: inherit;line-height: inherit;color: rgb(170, 87, 60);overflow-wrap: inherit !important;word-break: inherit !important;">1</span>)&nbsp; <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;t_order; <br> </section></pre> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 这条语句是统计「 t_order 表中,1 这个表达式不为 NULL 的记录」有多少个。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 1 这个表达式就是单纯数字,它永远都不是 NULL,所以上面这条语句,其实是在统计 t_order 表中有多少个记录。 </section> <blockquote style="padding: 15px 15px 15px 1rem;border-left-width: 5px;border-left-color: rgb(100, 149, 237);color: rgb(0, 0, 0);line-height: inherit;font-size: 0.9em;background: rgb(227, 242, 253);overflow: auto;overflow-wrap: normal;word-break: normal;"> <section style="margin-right: 8px;margin-left: 8px;font-size: inherit;color: inherit;line-height: inherit;"> count(主键字段) 执行过程是怎样的? </section> </blockquote> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 在通过 count 函数统计有多少个记录时,MySQL 的 server 层会维护一个名叫 count 的变量。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> server 层会循环向 InnoDB 读取一条记录,如果 count 函数指定的参数不为 NULL,那么就会将变量 count 加 1,直到符合查询的全部记录被读完,就退出循环。最后将 count 变量的值发送给客户端。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> InnoDB 是通过 B+ 树来保持记录的,根据索引的类型又分为聚簇索引和二级索引,它们区别在于,聚簇索引的叶子节点存放的是实际数据,而二级索引的叶子节点存放的是主键值,而不是实际数据。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 用下面这条语句作为例子: </section> <pre style="font-size: inherit;color: inherit;line-height: inherit;"> <section style="margin-right: 8px;margin-left: 8px;padding: 0.5em;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;background: rgb(25, 23, 28);color: rgb(139, 135, 146);overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"> //id&nbsp;为主键值 <br> <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp; <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">count</span>( <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">id</span>)&nbsp; <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;t_order; <br> </section></pre> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 如果表里只有主键索引,没有二级索引时,那么,InnoDB 循环遍历聚簇索引,将读取到的记录返回给 server 层,然后读取记录中的 id 值,就会 id 值判断是否为 NULL,如果不为 NULL,就将 count 变量加 1。 </section> <section style="margin-right: 8px;margin-left: 8px;text-align: center;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.3130434782608696" data-s="300,640" src="/upload/c96c3ba0360ec1c142340561ac836f9e.png" data-type="png" data-w="1150"> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 但是,如果表里有二级索引时,InnoDB 循环遍历的对象就不是聚簇索引,而是二级索引。 </section> <section style="margin-right: 8px;margin-left: 8px;text-align: center;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.2649434571890145" data-s="300,640" src="/upload/52a73034282c232acc2587b691bdd6d1.png" data-type="png" data-w="1238"> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 这是因为相同数量的二级索引记录可以比聚簇索引记录占用更少的存储空间,所以二级索引树比聚簇索引树小,这样遍历二级索引的 I/O 成本比遍历聚簇索引的 I/O 成本小,因此「优化器」优先选择的是二级索引。 </section> <blockquote style="padding: 15px 15px 15px 1rem;border-left-width: 5px;border-left-color: rgb(100, 149, 237);color: rgb(0, 0, 0);line-height: inherit;font-size: 0.9em;background: rgb(227, 242, 253);overflow: auto;overflow-wrap: normal;word-break: normal;"> <section style="margin-right: 8px;margin-left: 8px;font-size: inherit;color: inherit;line-height: inherit;"> count(1) 执行过程是怎样的? </section> </blockquote> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 用下面这条语句作为例子: </section> <pre style="font-size: inherit;color: inherit;line-height: inherit;"> <section style="margin-right: 8px;margin-left: 8px;padding: 0.5em;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;background: rgb(25, 23, 28);color: rgb(139, 135, 146);overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"> <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp; <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">count</span>( <span style="font-size: inherit;line-height: inherit;color: rgb(170, 87, 60);overflow-wrap: inherit !important;word-break: inherit !important;">1</span>)&nbsp; <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;t_order; <br> </section></pre> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 如果表里只有主键索引,没有二级索引时。 </section> <section style="margin-right: 8px;margin-left: 8px;text-align: center;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.2786624203821656" data-s="300,640" src="/upload/2da83baf4a106bae71f600b56cd92d87.png" data-type="png" data-w="1256"> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 那么,InnoDB 循环遍历聚簇索引(主键索引),将读取到的记录返回给 server 层, <strong style="font-size: inherit;line-height: inherit;color: rgb(48, 79, 254);">但是不会读取记录中的任何字段的值</strong>,因为 count 函数的参数是 1,不是字段,所以不需要读取记录中的字段值。参数 1 很明显并不是 NULL,因此 server 层每从 InnoDB 读取到一条记录,就将 count 变量加 1。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 可以看到,count(1) 相比 count(主键字段) 少一个步骤,就是不需要读取记录中的字段值,所以通常会说 count(1) 执行效率会比 count(主键字段) 高一点。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 但是,如果表里有二级索引时,InnoDB 循环遍历的对象就二级索引了。 </section> <section style="margin-right: 8px;margin-left: 8px;text-align: center;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.2879746835443038" data-s="300,640" src="/upload/f0a0ff01424dcc589d4b91496c6a0a16.png" data-type="png" data-w="1264"> </section> <blockquote style="padding: 15px 15px 15px 1rem;border-left-width: 5px;border-left-color: rgb(100, 149, 237);color: rgb(0, 0, 0);line-height: inherit;font-size: 0.9em;background: rgb(227, 242, 253);overflow: auto;overflow-wrap: normal;word-break: normal;"> <section style="margin-right: 8px;margin-left: 8px;font-size: inherit;color: inherit;line-height: inherit;"> count(*) 执行过程是怎样的? </section> </blockquote> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 看到&nbsp; <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">*</code>&nbsp;这个字符的时候,是不是大家觉得是读取记录中的所有字段值? </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 对于&nbsp; <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">selete *</code>&nbsp;这条语句来说是这个意思,但是在 count(*) &nbsp;中并不是这个意思。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> <strong style="font-size: inherit;line-height: inherit;color: rgb(48, 79, 254);">count(<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">*</code>) 其实等于 count(<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">0</code>)</strong>,也就是说,当你使用 count( <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">*</code>) &nbsp;时,MySQL 会将&nbsp; <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">*</code>&nbsp;参数转化为参数 0 来处理。 </section> <section style="margin-right: 8px;margin-left: 8px;text-align: center;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.300187617260788" data-s="300,640" src="/upload/c95e5a26cb999acd37a20745e4cf1f14.png" data-type="png" data-w="1066"> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 所以, <strong style="font-size: inherit;line-height: inherit;color: rgb(48, 79, 254);">count(*) 执行过程跟 count(1) 执行过程基本一样的</strong>,性能没有什么差异。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 在 MySQL 5.7 的官方手册中有这么一句话: </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> <em style="font-size: inherit;line-height: inherit;color: rgb(98, 0, 234);">InnoDB handles SELECT COUNT(<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">*</code>) and SELECT COUNT(<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">1</code>) operations in the same way. There is no performance difference.</em> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> <em style="font-size: inherit;line-height: inherit;color: rgb(98, 0, 234);">翻译:InnoDB以相同的方式处理SELECT COUNT(<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">*</code>)和SELECT COUNT(<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">1</code>)操作,没有性能差异。</em> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 而且 MySQL 会对 count(*) 和 count(1) 有个优化,如果有多个二级索引的时候,优化器会使用key_len 最小的二级索引进行扫描。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 只有当没有二级索引的时候,才会采用主键索引来进行统计。 </section> <blockquote style="padding: 15px 15px 15px 1rem;border-left-width: 5px;border-left-color: rgb(100, 149, 237);color: rgb(0, 0, 0);line-height: inherit;font-size: 0.9em;background: rgb(227, 242, 253);overflow: auto;overflow-wrap: normal;word-break: normal;"> <section style="margin-right: 8px;margin-left: 8px;font-size: inherit;color: inherit;line-height: inherit;"> count(字段) 执行过程是怎样的? </section> </blockquote> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> count(字段) 的执行效率相比前面的 count(1)、 count(*)、 count(主键字段) 执行效率是最差的。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 用下面这条语句作为例子: </section> <pre style="font-size: inherit;color: inherit;line-height: inherit;"> <section style="margin-right: 8px;margin-left: 8px;padding: 0.5em;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;background: rgb(25, 23, 28);color: rgb(139, 135, 146);overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"> //name不是索引,普通字段 <br> <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp; <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">count</span>( <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">name</span>)&nbsp; <span style="font-size: inherit;line-height: inherit;color: rgb(149, 90, 231);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;t_order; <br> </section></pre> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 对于这个查询来说,会采用全表扫描的方式来计数,所以它的执行效率是比较差的。 </section> <section style="margin-right: 8px;margin-left: 8px;text-align: center;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.2893481717011129" data-s="300,640" data-type="png" data-w="1258" src="/upload/f502ba81887fc9c50249ebc6863a1d59.png"> </section> <blockquote style="padding: 15px 15px 15px 1rem;border-left-width: 5px;border-left-color: rgb(100, 149, 237);color: rgb(0, 0, 0);line-height: inherit;font-size: 0.9em;background: rgb(227, 242, 253);overflow: auto;overflow-wrap: normal;word-break: normal;"> <section style="margin-right: 8px;margin-left: 8px;font-size: inherit;color: inherit;line-height: inherit;"> 小结 </section> </blockquote> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> count(1)、 count(*)、 count(主键字段)在执行的时候,如果表里存在二级索引,优化器就会选择二级索引进行扫描。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 所以,如果要执行 count(1)、 count(*)、 count(主键字段) 时,尽量在数据表上建立二级索引,这样优化器会自动采用 key_len 最小的二级索引进行扫描,相比于扫描主键索引效率会高一些。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 再来,就是不要使用 count(字段) &nbsp;来统计记录个数,因为它的效率是最差的,会采用全表扫描的方式来统计。如果你非要统计表中该字段不为 NULL 的记录个数,建议给这个字段建立一个二级索引。 </section> <h3 style="margin: 1.6em 8px;font-weight: bold;font-size: 1.3em;color: inherit;line-height: inherit;border-bottom: 2px solid rgb(65, 105, 225);"><span style="margin-right: 3px;padding: 3px 10px 1px;font-size: inherit;line-height: inherit;display: inline-block;font-weight: normal;background: rgb(65, 105, 225);color: rgb(255, 255, 255);border-top-right-radius: 3px;border-top-left-radius: 3px;">为什么要通过遍历的方式来计数?</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid rgb(239, 235, 233);border-right: 20px solid transparent;"></span></h3> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 你可以会好奇,为什么 count 函数需要通过遍历的方式来统计记录个数? </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 我前面将的案例都是基于 Innodb 存储引擎来说明的,但是在 MyISAM 存储引擎里,执行 count 函数的方式是不一样的,通常在没有任何查询条件下的 count(*),MyISAM 的查询速度要明显快于 InnoDB。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 使用 MyISAM 引擎时,执行 count 函数只需要 O(1 )复杂度,这是因为每张 MyISAM 的数据表都有一个 meta 信息有存储了row_count值,由表级锁保证一致性,所以直接读取 row_count &nbsp;值就是 count 函数的执行结果。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 而 InnoDB 存储引擎是支持事务的,同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的,所以无法像 MyISAM一样,只维护一个 row_count 变量。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 举个例子,假设表 t_order 有 100 条记录,现在有两个会话并行以下语句: </section> <section style="margin-right: 8px;margin-left: 8px;text-align: center;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.6058732612055642" data-s="300,640" src="/upload/6ca3fd5f9d213fee991666b7b2683e12.png" data-type="png" data-w="1294"> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 在会话 A 和会话 B的最后一个时刻,同时查表 t_order 的记录总个数,可以发现,显示的结果是不一样的。所以,在使用 InnoDB 存储引擎时,就需要扫描表来统计具体的记录。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 而当带上 where 条件语句之后,MyISAM 跟 InnoDB 就没有区别了,它们都需要扫描表来进行记录个数的统计。 </section> <h3 style="margin: 1.6em 8px;font-weight: bold;font-size: 1.3em;color: inherit;line-height: inherit;border-bottom: 2px solid rgb(65, 105, 225);"><span style="margin-right: 3px;padding: 3px 10px 1px;font-size: inherit;line-height: inherit;display: inline-block;font-weight: normal;background: rgb(65, 105, 225);color: rgb(255, 255, 255);border-top-right-radius: 3px;border-top-left-radius: 3px;">如何优化 &nbsp;count(*)?</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid rgb(239, 235, 233);border-right: 20px solid transparent;"></span></h3> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 如果对一张大表经常用 count(*) 来做统计,其实是很不好的。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 比如下面我这个案例,表 t_order 共有 1200+ 万条记录,我也创建了二级索引,但是执行一次&nbsp; <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(248, 35, 117);background: rgb(248, 248, 248);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">select count(*) from t_order</code>&nbsp;要花费差不多 5 秒! </section> <section style="margin-right: 8px;margin-left: 8px;text-align: center;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.3964285714285714" data-s="300,640" src="/upload/cbbfbf8a65b09bf012dc3d28e1a8ef06.png" data-type="png" data-w="1120"> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 面对大表的记录统计,我们有没有什么其他更好的办法呢? </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> <strong style="font-size: inherit;line-height: inherit;color: rgb(48, 79, 254);"><em style="font-size: inherit;line-height: inherit;color: rgb(197, 17, 98);">第一种,近似值</em></strong> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 如果你的业务对于统计个数不需要很精确,比如搜索引擎在搜索关键词的时候,给出的搜索结果条数是一个大概值。 </section> <section style="margin-right: 8px;margin-left: 8px;text-align: center;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.36774193548387096" data-s="300,640" src="/upload/edcc8556581bdee94adf10d64e1a8b52.png" data-type="png" data-w="1550"> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 这时,我们就可以使用 show table status 或者 explain 命令来表进行估算。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 执行 explain 命令效率是很高的,因为它并不会真正的去查询,下图中的 rows 字段值就是 &nbsp;explain 命令对表 t_order 记录的估算值。 </section> <section style="margin-right: 8px;margin-left: 8px;text-align: center;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.5594059405940595" data-s="300,640" src="/upload/e2fb02d4e23212b31f3270ab977d5384.png" data-type="png" data-w="1212"> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> <strong style="font-size: inherit;line-height: inherit;color: rgb(48, 79, 254);"><em style="font-size: inherit;line-height: inherit;color: rgb(197, 17, 98);">第二种,额外表保存计数值</em></strong> </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 如果是想精确的获取表的记录总数,我们可以将这个计数值保存到单独的一张计数表中。 </section> <section style="margin: 1.7em 8px;font-size: inherit;color: inherit;line-height: inherit;"> 当我们在数据表插入一条记录的同时,将计数表中的计数字段 + 1。也就是说,在新增和删除操作时,我们需要额外维护这个计数表。 </section> </section>

SkyWalking 实现链路追踪,再也不怕问题排查无从下手了

作者:微信小助手

<section data-mpa-template="t" mpa-from-tpl="t" data-mpa-powered-by="yiban.io"> <section data-mpa-template="t" mpa-from-tpl="t"> <p style="clear: both;min-height: 1em;color: rgb(51, 51, 51);font-size: 17px;letter-spacing: 0.544px;text-align: center;"><span style="letter-spacing: 0.544px;word-spacing: 2px;caret-color: rgb(60, 60, 60);"></span><span style="color: rgb(248, 57, 41);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 16px;font-weight: 700;letter-spacing: 0.8px;text-align: left;word-spacing: 0.8px;">大家好,我是码哥字节~</span></p> </section> </section> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="letter-spacing: 0.8px;word-spacing: 0.8px;">本篇文章介绍链路追踪的另外一种解决方案 Skywalking,文章目录如下:</span></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.7301587301587301" src="/upload/682bd0149cd158ea0aa9fe63ef710bc4.png" data-type="png" data-w="882" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">什么是Skywalking?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上一篇文章介绍了分布式链路追踪的一种方式:<span style="font-weight: 700;color: rgb(248, 57, 41);">Spring Cloud Sleuth+ZipKin</span>,这种方案目前也是有很多企业在用,但是作为程序员要的追逐一些新奇的技术,Skywalking作为后起之秀也是值得大家去学习的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">skywalking是一个优秀的<span style="font-weight: 700;color: rgb(248, 57, 41);">国产开源框架</span>,<span style="font-weight: 700;color: rgb(248, 57, 41);">2015</span>年由个人吴晟(华为开发者)开源 , <span style="font-weight: 700;color: rgb(248, 57, 41);">2017</span>年加入Apache孵化器。短短两年就被Apache收入麾下,实力可见一斑。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">skywalking支持dubbo,SpringCloud,SpringBoot集成,<span style="font-weight: 700;color: rgb(248, 57, 41);">代码无侵入,通信方式采用GRPC,性能较好,实现方式是java探针,支持告警,支持JVM监控,支持全局调用统计</span>等等,功能较完善。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">Skywalking和Spring Cloud Sleuth+ZipKin如何选型?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Skywalking相比于zipkin还是有很大的优势的,如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> skywalking采用字节码增强的技术实现代码无侵入,zipKin代码侵入性比较高 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> skywalking功能比较丰富,报表统计,UI界面更加人性化 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">个人建议</span>:如果是新的架构,建议优先选择skywalking。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">Skywalking架构是怎样的?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">skywalking和zipkin一样,也分为服务端和客户端,服务端负责收集日志数据并且展示,架构如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.4649243466299862" src="/upload/e6bb170c31adc5a36c566b7cc78af0db.png" data-type="png" data-w="727" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上述架构图中主要分为四个部分,如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">上面的Agent</span>:负责收集日志数据,并且传递给中间的OAP服务器 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">中间的OAP</span>:负责接收 Agent 发送的 Tracing 和Metric的数据信息,然后进行分析(Analysis Core) ,存储到外部存储器( Storage ),最终提供查询( Query )功能。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">左面的UI</span>:负责提供web控制台,查看链路,查看各种指标,性能等等。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">右面Storage</span>:负责数据的存储,支持多种存储类型。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">看了架构图之后,思路很清晰了,Agent负责收集日志传输数据,通过GRPC的方式传递给OAP进行分析并且存储到数据库中,最终通过UI界面将分析的统计报表、服务依赖、拓扑关系图展示出来。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">服务端如何搭建?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">skywalking同样是通过jar包方式启动,需要下载jar包,地址:https://skywalking.apache.org/downloads/</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1、下载安装包</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">选择<span style="font-weight: 700;color: rgb(248, 57, 41);">V8.7.0</span>这个版本,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.34414893617021275" src="/upload/f48b0d08fc7e573ebfc754980e414ed.png" data-type="png" data-w="1880" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">以上只是陈某的选择,可以按照自己的需要选择其他版本</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">解压之后完整目录如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.6309111880046137" src="/upload/6bc0ff2c7983ef099a3dcbb8467750b8.png" data-type="png" data-w="867" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">重要的目录结构分析如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">agent</span>:客户端需要指定的目录,其中有一个jar,就是负责和客户端整合收集日志 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">bin</span>:服务端启动的脚本 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">config</span>:一些配置文件的目录 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">logs</span>:oap服务的日志目录 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">oap-libs</span>:oap所需的依赖目录 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">webapp</span>:UI服务的目录 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">2、配置修改</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">启动之前需要对配置文件做一些修改,修改如下:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">1、/config/application.yml</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这个是oap服务的配置文件,需要修改注册中心为nacos,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.636698599852616" src="/upload/19435058fae378bc263756df6af81185.png" data-type="png" data-w="1357" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">配置①</span>:修改默认注册中心选择nacos,这样就不用在启动参数中指定了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">配置②</span>:修改nacos的相关配置,由于陈某是本地的,则不用改动,根据自己情况修改。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">2、webapp/webapp.yml</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这个是UI服务的配置文件,其中有一个<span style="font-weight: 700;color: rgb(248, 57, 41);">server.port</span>配置,是UI服务的端口,默认<span style="font-weight: 700;color: rgb(248, 57, 41);">8080</span>,陈某将其改成<span style="font-weight: 700;color: rgb(248, 57, 41);">8888</span>,避免端口冲突,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.18575851393188855" src="/upload/350745914762a8bcd8eff5332d64632e.png" data-type="png" data-w="323" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">3、启动服务</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">启动命令在/bin目录下,这里需要启动两个服务,如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">oap服务</span>:对应的启动脚本oapService.bat,Linux下对应的后缀是sh </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">UI服务</span>:对应的启动脚本webappService.bat,Linux下对应的后缀是sh </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当然还有一个<span style="font-weight: 700;color: rgb(248, 57, 41);">startup.bat</span>启动文件,可以直接启动上述两个服务,我们可以直接使用这个脚本,直接双击,将会弹出两个窗口则表示启动成功,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5008250825082509" src="/upload/9d039671d35346f920ba0d7be9e1759a.png" data-type="png" data-w="1212" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">此时直接访问:http://localhost:8888/,直接进入UI端,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.4665271966527197" src="/upload/714cc85089ccf2b052532c662f89e2da.png" data-type="png" data-w="1912" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">客户端如何搭建?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">客户端也就是单个微服务,由于Skywalking采用字节码增强技术,因此对于微服务无代码侵入,只要是普通的微服务即可,不需要引入什么依赖。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">还是上一篇Spring Cloud Sleuth的三个服务,如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">skywalking-product1001</span>:商品微服务 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">skywalking-order1002</span>:订单微服务 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">skywalking-gateway1003</span>:网关微服务 </section></li> </ul> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">案例源码已经上传,关注公众号:码猿技术专栏,回复关键词 <span style="font-weight: 700;color: rgb(248, 57, 41);">9528</span>获取!</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">案例源码结构目录如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="1.4043126684636118" src="/upload/7deae39afcd46e4fc794837d3c8525dd.png" data-type="png" data-w="371" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">想要传输数据必须借助skywalking提供的agent,只需要在启动参数指定即可,命令如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ7VoREH0wriahkhY3Mzx9HK15gdE7H9VIYFKYz6FqB19lcPL2BwSO89EXLSLHMBkhYbicf5mCyOTxF/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">-javaagent:E:\springcloud\apache-skywalking-apm-es7-8.7.0\apache-skywalking-apm-bin-es7\agent\skywalking-agent.jar<br>-Dskywalking.agent.service_name=skywalking-product-service<br>-Dskywalking.collector.backend_service=127.0.0.1:11800<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上述命令解析如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">-javaagent</span>:指定skywalking中的agent中的 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">skywalking-agent.jar</code>的路径 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">-Dskywalking.agent.service_name</span>:指定在skywalking中的服务名称,一般是微服务的 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">spring.application.name</code> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">-Dskywalking.collector.backend_service</span>:指定oap服务绑定的地址,由于陈某这里是本地,并且oap服务默认的端口是11800,因此只需要配置为 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">127.0.0.1:11800</code> </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上述参数可以在命令行通过<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">java -jar xxx</code>指定,在IDEA中操作如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5687575392038601" src="/upload/f9ce1c815146bbca77bd04916016d25e.png" data-type="png" data-w="1658" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上述三个微服务都需要配置skywalking的启动配置,配置成功后正常启动即可。</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">注意:agent的jar包路径不能包含中文,不能有空格,否则运行不成功。</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">成功启动后,直接通过网关访问:http://localhost:1003/order/get/12,返回如下信息:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5789085545722714" src="/upload/c64fb300cb330d87599a0ebaed5f985a.png" data-type="png" data-w="1356" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">此时查看skywalking的UI端,可以看到三个服务已经监控成功了,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.23384130320546506" src="/upload/b9f1b9f9e245cd41a18f6fab8678fa85.png" data-type="png" data-w="1903" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">服务之前的依赖关系也是可以很清楚的看到,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.4531658817373103" src="/upload/1b7e2763a53ee4aa6851b16f91ad438d.png" data-type="png" data-w="1911" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">请求链路的信息也是能够很清楚的看到,比如请求的url,执行时间、调用的服务,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.4666666666666667" src="/upload/cbdfbaffb6f85c4cceda8c052e3946e6.png" data-type="png" data-w="1920" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">感觉怎样?是不是很高端,比zipkin的功能更加丰富。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">数据如何持久化?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">你会发现只要服务端重启之后,这些链路追踪数据将会丢失了,因为skywalking默认持久化的方式是存储在内存中。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当然这里也是可以通过插拔方式的替换掉存储中间件,企业中往往是使用<span style="font-weight: 700;color: rgb(248, 57, 41);">ES</span>存储,陈某这章介绍一下<span style="font-weight: 700;color: rgb(248, 57, 41);">MySQL</span>的方式存储,ES方式后面单独开一篇文章介绍。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1、修改配置文件</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">修改config/application.yml文件中的存储方式,总共需要修改两处地方。</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 修改默认的存储方式为mysql,如下图: </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.1401098901098901" src="/upload/c53aa4b33446818692f66c8f8fdc9070.png" data-type="png" data-w="364" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 修改Mysql相关的信息,比如用户名、密码等,如下图: </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.13965744400527008" src="/upload/70c438d394b5ea2f5c44bc862812133e.png" data-type="png" data-w="1518" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">2、添加MySQL的jdbc依赖</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">默认的oap中是没有jdbc驱动依赖,因此需要我们手动添加一下,只需要将驱动的jar放在<span style="font-weight: 700;color: rgb(248, 57, 41);">oap-libs</span>文件夹中,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.06455862977602109" src="/upload/85043bc87346e40ba0efbc90178592fe.png" data-type="png" data-w="759" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">好了,已经配置完成,启动服务端,在skywalking这个数据库中将会自动创建表,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5795339412360689" src="/upload/43274ec0b526e05fbdc3f1f7b6536882.png" data-type="png" data-w="987" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">陈某这里就不再测试了,自己耍耍吧.......</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">日志监控如何做?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在skywalking的UI端有一个日志的模块,用于收集客户端的日志,默认是没有数据的,那么需要如何将日志数据传输到skywalking中呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">日志框架的种类很多,比较出名的有log4j,logback,log4j2,陈某就以<span style="font-weight: 700;color: rgb(248, 57, 41);">logback</span>为例子介绍一下如何配置,官方文档如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> log4j:https://skywalking.apache.org/docs/skywalking-java/v8.8.0/en/setup/service-agent/java-agent/application-toolkit-log4j-1.x/ </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> log4j2:https://skywalking.apache.org/docs/skywalking-java/v8.8.0/en/setup/service-agent/java-agent/application-toolkit-log4j-2.x/ </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> logback:https://skywalking.apache.org/docs/skywalking-java/v8.8.0/en/setup/service-agent/java-agent/application-toolkit-logback-1.x/ </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1、添加依赖</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">根据官方文档,需要先添加依赖,如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ7VoREH0wriahkhY3Mzx9HK15gdE7H9VIYFKYz6FqB19lcPL2BwSO89EXLSLHMBkhYbicf5mCyOTxF/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span>org.apache.skywalking<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span>apm-toolkit-logback-1.x<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">version</span>&gt;</span>${project.release.version}<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">version</span>&gt;</span><br><span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">2、添加配置文件</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">新建一个<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">logback-spring.xml</code>放在resource目录下,配置如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.6448948948948949" src="/upload/32d6f573715ca2d8050eee425582ab08.png" data-type="png" data-w="1332" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">以上配置全部都是拷贝官方文档的,对于日志配置有不理解的地方,可以看陈某之前的文章:<a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&amp;mid=2247484716&amp;idx=1&amp;sn=04db24b54f73e7eaf72e393cc1f215a3&amp;chksm=fcf4dae1cb8353f738b3b61d9a935e256f2f885d13dff5d6e8cff03b85a5153d4aee8fc8328e&amp;token=1889058377&amp;lang=zh_CN&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">Spring Boot第三弹,一文带你搞懂日志如何配置?</a></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">配置完成之后,启动服务,访问:http://localhost:1003/order/get/12。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">控制台打印的日志如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.20481927710843373" src="/upload/b54e128ee2bd829101d97a617dab9040.png" data-type="png" data-w="1826" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">可以看到已经打印出了traceId。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">skywalking中的日志模块输出的日志如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.4667365112624411" src="/upload/c77eac2bfa9f73f6982aa3e27098ddfa.png" data-type="png" data-w="1909" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">日志已经传输到了skywalking中..............</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">注意</span>:如果<span style="font-weight: 700;color: rgb(248, 57, 41);">agent</span>和<span style="font-weight: 700;color: rgb(248, 57, 41);">oap</span>服务不在同一台服务器上,需要在<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">/agent/config/agent.config</code>配置文件末尾添加如下配置:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ7VoREH0wriahkhY3Mzx9HK15gdE7H9VIYFKYz6FqB19lcPL2BwSO89EXLSLHMBkhYbicf5mCyOTxF/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">plugin.toolkit.log.grpc.reporter.server_host=${SW_GRPC_LOG_SERVER_HOST:10.10.10.1}<br>plugin.toolkit.log.grpc.reporter.server_port=${SW_GRPC_LOG_SERVER_PORT:11800}<br>plugin.toolkit.log.grpc.reporter.max_message_size=${SW_GRPC_LOG_MAX_MESSAGE_SIZE:10485760}<br>plugin.toolkit.log.grpc.reporter.upstream_timeout=${SW_GRPC_LOG_GRPC_UPSTREAM_TIMEOUT:30}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">配置分析如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.12571428571428572" src="/upload/16f75f2bdc45467312e7b70269c641ae.png" data-type="png" data-w="875" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">性能剖析如何做?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">skywalking在性能剖析方面真的是非常强大,提供到基于堆栈的分析结果,能够让运维人员一眼定位到问题。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">新建一个<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">/order/list</code>接口,如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.17151162790697674" src="/upload/9221781da5ff6f7b02b9336414474eb6.png" data-type="png" data-w="1376" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上述代码中休眠了2秒,看看如何在skywalking中定位这个问题。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在性能剖析模块-&gt;新建任务-&gt;选择服务、填写端点、监控时间,操作如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.47239583333333335" src="/upload/fba4be17efb13ca72849e540e2ff51b0.png" data-type="png" data-w="1920" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上图中选择了最大采样数为5,则直接访问5次:http://localhost:1003/order/list,然后选择这个任务将会出现监控到的数据,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.4421875" src="/upload/7eaa18ea903e48d711fb11f1f4648c32.png" data-type="png" data-w="1920" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上图中可以看到<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">{GET}/order/list</code>这个接口上耗费了2秒以上,因此选择这个接口点击分析,可以看到详细的堆栈信息,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.48165618448637315" src="/upload/6968dc932363cf8b66d8c46b51a13f4a.png" data-type="png" data-w="1908" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如何定位到睡眠2秒钟的那一行代码呢?直接往下翻,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.624804992199688" src="/upload/b24cc4cb7c11ba040518aba5bdb5e700.png" data-type="png" data-w="1282" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">是不是很清楚了,在OrderController这个接口线程睡眠了两秒........</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">监控告警如何做?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">对于服务的异常信息,比如接口有较长延迟,skywalking也做出了告警功能,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.2684073107049608" src="/upload/1c5dd4e23e6f2c3fe30a2c2c972e79b8.png" data-type="png" data-w="1915" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">skywalking中有一些默认的告警规则,如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 最近3分钟内服务的平均响应时间超过1秒 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 最近2分钟服务成功率低于80% </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 最近3分钟90%服务响应时间超过1秒 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 最近2分钟内服务实例的平均响应时间超过1秒 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当然除了以上四种,随着Skywalking不断迭代也会新增其他规则,这些规则的配置在<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">config/alarm-settings.yml</code>配置文件中,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.6355555555555555" src="/upload/e32a0d2597697761160884e7371aa4b0.png" data-type="png" data-w="1350" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">每个规则都由相同的属性组成,这些属性的含义如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.6060935799782372" src="/upload/c94a1ac39dead8b81153c1d124565f38.png" data-type="png" data-w="919" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如果想要调整默认的规则,比如监控返回的信息,监控的参数等等,只需要改动上述配置文件中的参数即可。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当然除了以上默认的几种规则,skywalking 还适配了一些钩子(<span style="font-weight: 700;color: rgb(248, 57, 41);">webhooks</span>)。其实就是相当于一个回调,一旦触发了上述规则告警,skywalking则会调用配置的webhook,这样开发者就可以定制一些处理方法,比如发送<span style="font-weight: 700;color: rgb(248, 57, 41);">邮件</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">微信</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">钉钉</span>通知运维人员处理。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当然这个钩子也是有些规则的,如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> POST请求 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">application/json</span> 接收数据 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 接收的参数必须是AlarmMessage中指定的参数。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">注意</span>:AlarmMessage这个类随着skywalking版本的迭代可能出现不同,一定要到对应版本源码中去找到这个类,拷贝其中的属性。这个类在源码的路径:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">org.apache.skywalking.oap.server.core.alarm</code>,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5008777062609713" src="/upload/a888923524451e70dd86c8581061d391.png" data-type="png" data-w="1709" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">新建一个告警模块:<span style="font-weight: 700;color: rgb(248, 57, 41);">skywalking-alarm1004</span>,其中利用webhook定义一个接口,如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.6577992744860943" src="/upload/cf036fc21503bb95a2e8c26a0f0eda7.png" data-type="png" data-w="827" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">接口定制完成后,只需要在<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">config/alarm-settings.yml</code>配置文件中添加这个钩子,如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.18762475049900199" src="/upload/45dd097b04e00e1457c0edac3eb6e411.png" data-type="png" data-w="501" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">好了,这就已经配置完成了,测试也很简单,还是调用上面案例中的睡眠两秒的接口:http://localhost:1003/order/list,多调用几次,则会触发告警,控制台打印日志如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5510752688172043" src="/upload/31b94a6e52145e91b1a7f9f1b6a82f8e.png" data-type="png" data-w="1116" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">总结</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">本篇文章介绍了链路追踪解决方案 Skywalking,主要讲解了服务端搭建、客户端搭建、数据持久化、日志监控、性能剖析这几个知识点,每个知识点都非常重要。<span style="background-color: rgb(245, 245, 245);letter-spacing: 0.8px;word-spacing: 0.8px;"></span></p> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzU3MDAzNDg1MA==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/19cc2hfD2rA07Je2pY1o0ic2KcPRn44icO8GVcKRdwiaYvrE6bNeTbWPicyV7c7jWmSyzsiaWASjjckzBcsJMJw06pA/0?wx_fmt=png" data-nickname="码猿技术专栏" data-alias="oneswholife" data-signature="前蚂蚁P8,纯粹的技术人,专注于Java后端技术分享,只写外面看不到的干货,你想要的都在这里……" data-from="0"></mpprofile> </section> </section>

18张图,详解SpringBoot解析yml全流程

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="color: black;letter-spacing: 0px;white-space: normal;font-size: 16px;padding-right: 10px;padding-left: 10px;line-height: 1.6;word-break: break-word;overflow-wrap: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;" data-mpa-powered-by="yiban.io"> <h2 data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);white-space: normal;margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 1.3em;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;text-align: left;border-bottom: 2px solid rgb(239, 112, 96);"><span style="margin-right: 3px;padding: 3px 10px 1px;display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);border-top-right-radius: 3px;border-top-left-radius: 3px;">背景</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid rgb(239, 235, 233);border-right: 20px solid transparent;"></span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">前几天的时候,项目里有一个需求,需要一个开关控制代码中是否执行一段逻辑,于是理所当然的在<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">yml</code>文件中配置了一个属性作为开关,再配合<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">nacos</code>就可以随时改变这个值达到我们的目的,yml文件中是这样写的:<br></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/CkBYF6IYNs34076jqaBLz5pwicGl0ka3y9K6AYIgDKpPHhqCnt7oM7G7Wdpy1Khibo7F6QP5xreQag5ERTksH3qqzRAibic3OSHa/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 747px;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(40, 44, 52);border-radius: 5px;"><span style="color: rgb(209, 154, 102);line-height: 26px;">switch:</span><br>&nbsp;&nbsp;<span style="color: rgb(209, 154, 102);line-height: 26px;">turnOn:</span>&nbsp;<span style="color: rgb(152, 195, 121);line-height: 26px;">on</span><br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">程序中的代码也很简单,大致的逻辑就是下面这样,如果取到的开关字段是<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">on</code>的话,那么就执行<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">if</code>判断中的代码,否则就不执行:</p>

10000字 | 深入理解 OpenFeign 的架构原理

作者:じ☆ve宝贝

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 15px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">大家好,我是悟空呀。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">上次我们深入讲解了 <a href="https://mp.weixin.qq.com/s?__biz=MzAwMjI0ODk0NA==&amp;mid=2451961128&amp;idx=1&amp;sn=3e2c399aa8ac70d9bdd2df04ccca61db&amp;scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">Ribbon 的架构原理</a>,这次我们再来看下 Feign 远程调用的架构原理。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="1.0436893203883495" src="/upload/ed6b444122549520bf6a9ce8c97e7154.png" data-type="png" data-w="1236" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> <span style="color: rgb(34, 34, 34);font-size: 18px;font-weight: bold;letter-spacing: 0.8px;word-spacing: 0.8px;"><br></span> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzAwMjI0ODk0NA==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/SfAHMuUxqJ2JicQpKzblLHz64qoibVa3ATNA4rH8mIYXAF3OErAzxFKHzf5qiaiblb4rAMuAXXMJHEcKcvaHv4ia9rA/0?wx_fmt=png" data-nickname="悟空聊架构" data-alias="PassJava666" data-signature="用故事讲解分布式、架构。 《 JVM 性能调优实战》专栏作者, 《Spring Cloud 实战 PassJava》开源作者, 自主开发了 PMP 刷题小程序。" data-from="0"></mpprofile> </section> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">一、理解远程调用</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">远程调用怎么理解呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">远程调用</code>和<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">本地调用</code>是相对的,那我们先说本地调用更好理解些,本地调用就是同一个 Service 里面的方法 A 调用方法 B。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">那远程调用就是不同 Service 之间的方法调用。Service 级的方法调用,就是我们自己构造请求 URL和请求参数,就可以发起远程调用了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">在服务之间调用的话,我们都是基于 HTTP 协议,一般用到的远程服务框架有 OKHttp3,Netty, HttpURLConnection 等。其调用流程如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.9367311072056239" src="/upload/6fca39d697c35d3dadc90a80ccd07322.png" data-type="png" data-w="1138" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">但是这种虚线方框中的构造请求的过程是很<span style="font-weight: 700;color: rgb(248, 57, 41);">繁琐</span>的,有没有更<span style="font-weight: 700;color: rgb(248, 57, 41);">简便</span>的方式呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;"><span style="color: rgb(255, 0, 0);"><strong>Feign</strong></span> 就是来简化我们发起远程调用的代码的,那简化到什么程度呢?<strong>简化成就像调用本地方法那样简单。</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">比如我的开源项目 PassJava 中的使用 Feign 执行远程调用的代码:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/ibKHP1TZZeXLuuibODqFs5MmtTHUwWklKz5eAwuc1KibW3vPj3FrwAJ8KXpNvNibKSFLicAn9kFhQ0D7Eibs7doltcK1QQAKMzBPYJ/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//远程调用拿到该用户的学习时长</span><br>R&nbsp;memberStudyTimeList&nbsp;=&nbsp;studyTimeFeignService.getMemberStudyTimeListTest(id);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">而 Feign 又是 Spring Cloud 微服务技术栈中非常重要的一个组件,如果让你来设计这个微服务组件,你会怎么来设计呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">我们需要考虑这几个因素</span>:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);font-size: 16px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 如何使远程调用像本地方法调用简单? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> Feign 如何找到远程服务的地址的? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> Feign 是如何进行负载均衡的? </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">接下来我们围绕这些核心问题来一起看下 Feign 的设计原理。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">二、Feign 和 OpenFeign</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">OpenFeign 组件的前身是 Netflix Feign 项目,它最早是作为 Netflix OSS 项目的一部分,由 Netflix 公司开发。后来 Feign 项目被贡献给了开源组织,于是才有了我们今天使用的 Spring Cloud OpenFeign 组件。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">Feign 和 OpenFeign 有很多大同小异之处,不同的是 OpenFeign 支持 MVC 注解。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">可以认为 OpenFeign 为 Feign 的增强版</span>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">简单总结下 OpenFeign 能用来做什么:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);font-size: 16px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> OpenFeign 是声明式的 HTTP 客户端,让远程调用更简单。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 提供了HTTP请求的模板,编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 整合了Ribbon(负载均衡组件)和 Hystix(服务熔断组件),不需要显示使用这两个组件 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> Spring Cloud Feign 在 Netflix Feign的基础上扩展了对SpringMVC注解的支持 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">三、OpenFeign 如何用?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">OpenFeign 的使用也很简单,这里还是用我的开源 SpringCloud 项目 PassJava 作为示例。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(255, 177, 27);background: rgb(255, 245, 227);"> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: black;line-height: 26px;">开源地址: https://github.com/Jackson0714/PassJava-Platform</p> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: black;line-height: 26px;">喜欢的小伙伴来点个 Star 吧,冲 2K Star。</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">Member 服务远程调用 Study 服务的方法 memberStudyTime(),如下图所示。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.528158295281583" src="/upload/833b7e4f47fb4ebb8a3495fc05c8c536.png" data-type="png" data-w="1314" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">第一步</span>:Member 服务需要定义一个 OpenFeign 接口:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/ibKHP1TZZeXLuuibODqFs5MmtTHUwWklKz5eAwuc1KibW3vPj3FrwAJ8KXpNvNibKSFLicAn9kFhQ0D7Eibs7doltcK1QQAKMzBPYJ/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #4078f2;line-height: 26px;">@FeignClient</span>(<span style="color: #50a14f;line-height: 26px;">"passjava-study"</span>)<br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">interface</span>&nbsp;<span style="color: #c18401;line-height: 26px;">StudyTimeFeignService</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@RequestMapping</span>(<span style="color: #50a14f;line-height: 26px;">"study/studytime/member/list/test/{id}"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;R&nbsp;<span style="color: #4078f2;line-height: 26px;">getMemberStudyTimeListTest</span><span style="line-height: 26px;">(@PathVariable(<span style="color: #50a14f;line-height: 26px;">"id"</span>)</span>&nbsp;Long&nbsp;id)</span>;<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">我们可以看到这个 interface 上添加了注解<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">@FeignClient</code>,而且括号里面指定了服务名:passjava-study。<span style="font-weight: 700;color: rgb(248, 57, 41);">显示声明</span>这个接口用来远程调用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">passjava-study</code>服务。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">第二步</span>:Member 启动类上添加 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">@EnableFeignClients</code>注解开启远程调用服务,且需要开启服务发现。如下所示:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/ibKHP1TZZeXLuuibODqFs5MmtTHUwWklKz5eAwuc1KibW3vPj3FrwAJ8KXpNvNibKSFLicAn9kFhQ0D7Eibs7doltcK1QQAKMzBPYJ/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #4078f2;line-height: 26px;">@EnableFeignClients</span>(basePackages&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"com.jackson0714.passjava.member.feign"</span>)<br><span style="color: #4078f2;line-height: 26px;">@EnableDiscoveryClient</span><br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">第三步</span>:Study 服务定义一个方法,其方法路径和 Member 服务中的接口 URL 地址一致即可。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">URL 地址:"study/studytime/member/list/test/{id}"</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/ibKHP1TZZeXLuuibODqFs5MmtTHUwWklKz5eAwuc1KibW3vPj3FrwAJ8KXpNvNibKSFLicAn9kFhQ0D7Eibs7doltcK1QQAKMzBPYJ/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #4078f2;line-height: 26px;">@RestController</span><br><span style="color: #4078f2;line-height: 26px;">@RequestMapping</span>(<span style="color: #50a14f;line-height: 26px;">"study/studytime"</span>)<br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">StudyTimeController</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@RequestMapping</span>(<span style="color: #50a14f;line-height: 26px;">"/member/list/test/{id}"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;R&nbsp;<span style="color: #4078f2;line-height: 26px;">memberStudyTimeTest</span><span style="line-height: 26px;">(@PathVariable(<span style="color: #50a14f;line-height: 26px;">"id"</span>)</span>&nbsp;Long&nbsp;id)&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;"><span style="font-weight: 700;col

Kafka内外网访问的设置

作者:じ☆ve不哭

## kafka的两个配置listeners和advertised.listeners > 本文作者: 民宿 原文链接: https://www.cnblogs.com/gentlescholar/p/15179258.html #### listeners - kafka监听的网卡的ip,假设你机器上有两张网卡,内网192.168.0.213和外网101.89.163.1 如下配置 ``` listeners=PLAINTEXT://192.168.0.213:9092 ``` 那么kafka只监听内网网卡,即只接收内网网卡的数据,如果你不能把外网网卡流量转发到内网网卡(为什么要强调这一点,下面说),那么kafka就接收不到外网网卡数据。如果配置成外网ip同理。当然你可以配置成0.0.0.0,监听所有网卡。 #### advertised.listeners 我们观察kafka的配置文件server.properties,会发现里面记录了zookeeper集群的各个节点的访问地址,但是并没有记录kafka兄弟节点的地址。kafka节点启动后,会向zookeeper注册自己,同时从zookeeper中获取兄弟节点的地址,以便与兄弟节点通信。 同样,我们使用客户端连接kafka后,kafka返回给客户端的是集群各节点的访问地址,这个地址也是上面说的从zookeeper中获得的地址。 这个地址哪里来,就是kafka节点向zookeeper注册时提供的advertised.listeners。如果没有,就会使用listeners。 **三种情景,搭配使用这两个配置** - 只需要内网访问kafka ``` listeners=PLAINTEXT://192.168.0.213:9092 ``` - 只需要内网访问kafka 你肯定想到了最简单的一个方法,listeners使用外网ip ``` listeners=PLAINTEXT://101.89.163.1:9092 ``` - 需要外网访问 如果宿主机有外网网卡,这么配当然没问题。如果没有(ifconfig看不到外网ip的网卡,基本上就不存在这个外网网卡),很可能和我使用的的宿主机一样是通过NAT映射或者啥办法搞出来的外网ip,此时kafka无法监听这个外网ip(因为不存在,启动就会报错)。 这时候就是advertised.listeners真正发挥作用的时候了。使用如下配置: ``` listeners=PLAINTEXT://192.168.0.213:9092 advertised.listeners=PLAINTEXT://101.89.163.1:9092 ``` **此时一个完整的kafka客户端访问服务端的流程:** - 客户端访问101.89.163.1:9092,被kafka宿主机所在环境映射到内网192.168.0.213:9092,访问到了kafka节点,请求获得kafka服务端的访问地址 - kafka从zookeeper拿到自己和其他兄弟节点通过advertised.listeners注册到zookeeper的101.89.163.1:9092等外网地址,作为kafka的服务端访问地址返回给客户端 - 客户端拿这些地址访问kafka集群,被kafka宿主机所在环境映射到各kafka节点的内网ip,访问到了kafka服务端......完美循环 你可能会问已经配置了访问地址,为什么还要在第一次访问的时候请求获得kafka的访问地址。因为如果是kafka集群,你可以选择只给客户端配置一个kafka节点的地址(这样是不推荐的),但是客户端必须要访问集群中的每一个节点,所以必须通过这个节点获得集群中每一个节点的访问地址。 ``` 如果不配置advertised.listeners=PLAINTEXT://101.89.163.1:9092,你会发现虽然你给kafka客户端配置的访问地址是101.89.163.1:9092,但是kafka客户端访问时报错,报错原因是Connection to node -1[192.168.0.213:9092] could not be established. Broker may not be available.。这就是因为不配置advertised.listeners则advertised.listeners默认使用listeners配置的地址,客户端拿到的就是listeners配置的内网地址 ``` #### 内外网分流 上面说的有外网ip的情况,直接配置外网ip有没有问题呢? 如果既要内网访问,又要外网访问,本来可以走内网的流量都走外网网卡,显然不合适;而且有的环境可能被配置成这些kafka宿主机是没有外网访问权限的,即虽然他可以访问自己的外网ip,但是访问不了兄弟节点的外网ip。这时候就要配置内外网。 **配置1:** ``` listener.security.protocol.map=INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT listeners=INTERNAL://192.168.0.213:9092,EXTERNAL://192.168.0.213:19092 advertised.listeners=INTERNAL://192.168.0.213:9092,EXTERNAL://101.89.163.9:19092 inter.broker.listener.name=INTERNAL ``` **配置2:** ``` listener.security.protocol.map=INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT listeners=INTERNAL://192.168.0.213:9092,EXTERNAL://101.89.163.9:19092 advertised.listeners=INTERNAL://192.168.0.213:9092,EXTERNAL://101.89.163.9:19092 inter.broker.listener.name=INTERNAL ``` *注意这两的区别是**listeners**的**EXTERNAL**使用的ip不一样,一个使用内网ip,一个使用外网ip。* - 如果你的kafka宿主机有外网网卡,只能用外网ip,若使用配置1,kafka通过listeners监听的两个端口都是内网网卡的数据,无法接收到外网网卡数据; - 如果你的kafka宿主机外网ip是映射来的,只能使用内网ip,原因也是上面说过的,不存在外网网卡,kafka启动监听就会报错,而使用内网ip有环境配置好的转发,可以接收到外网ip的数据。

字节一面:MQ 幂等、去重 有哪些通用的解决方案?

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">消息中间件是分布式系统常用的组件,无论是<span style="font-weight: 700;color: rgb(248, 57, 41);">异步化</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">解耦</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">削峰</span>等都有广泛的应用价值。我们通常会认为,消息中间件是一个可靠的组件——这里所谓的可靠是指,只要我把消息成功投递到了消息中间件,消息就不会丢失,即消息肯定会至少保证消息能被消费者成功消费一次,这是消息中间件最基本的特性之一,也就是我们常说的“<span style="font-weight: 700;color: rgb(248, 57, 41);">AT LEAST ONCE</span>”,即消息至少会被“成功消费一遍”。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">举个例子,一个消息M发送到了消息中间件,消息投递到了消费程序<span style="font-weight: 700;color: rgb(248, 57, 41);">A</span>,<span style="font-weight: 700;color: rgb(248, 57, 41);">A</span>接受到了消息,然后进行消费,但在消费到一半的时候程序重启了,这时候这个消息并没有标记为消费成功,这个消息还会继续投递给这个消费者,直到其消费成功了,消息中间件才会停止投递。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">然而这种可靠的特性导致,消息可能被<span style="font-weight: 700;color: rgb(248, 57, 41);">多次</span>地投递。举个例子,还是刚刚这个例子,程序<span style="font-weight: 700;color: rgb(248, 57, 41);">A</span>接受到这个消息M并完成消费逻辑之后,正想通知消息中间件“<span style="font-weight: 700;color: rgb(248, 57, 41);">我已经消费成功了</span>”的时候,程序就重启了,那么对于消息中间件来说,这个消息并没有成功消费过,所以他还会继续投递。这时候对于应用程序A来说,看起来就是这个消息明明消费成功了,但是消息中间件还在重复投递。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这在<span style="font-weight: 700;color: rgb(248, 57, 41);">RockectMQ</span>的场景来看,就是同一个<span style="font-weight: 700;color: rgb(248, 57, 41);">messageId</span>的消息重复投递下来了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">基于消息的投递可靠(消息不丢)是优先级更高的,所以消息不重的任务就会转移到应用程序自我实现,这也是为什么RocketMQ的文档里强调的,消费逻辑需要自我实现幂等。背后的逻辑其实就是:不丢和不重是矛盾的(在分布式场景下),但消息重复是有解决方案的,而消息丢失是很麻烦的。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">简单的消息去重解决方案</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">例如:假设我们业务的消息消费逻辑是:插入某张订单表的数据,然后更新库存:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oexqmcDw0weKQdDJMXEUAvnjqIzOsC2bUXWso6DP9rGs6bcj4OPz4W4zllX77NRU22PysBoo6o7jYl7iatvvib9BFN/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #a626a4;line-height: 26px;">insert</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">into</span>&nbsp;t_order&nbsp;<span style="color: #a626a4;line-height: 26px;">values</span>&nbsp;.....<br><span style="color: #a626a4;line-height: 26px;">update</span>&nbsp;t_inv&nbsp;<span style="color: #a626a4;line-height: 26px;">set</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">count</span>&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">count</span><span style="color: #986801;line-height: 26px;">-1</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">where</span>&nbsp;good_id&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">'good123'</span>;<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">要实现消息的幂等,我们可能会采取这样的方案:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oexqmcDw0weKQdDJMXEUAvnjqIzOsC2bUXWso6DP9rGs6bcj4OPz4W4zllX77NRU22PysBoo6o7jYl7iatvvib9BFN/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #a626a4;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">from</span>&nbsp;t_order&nbsp;<span style="color: #a626a4;line-height: 26px;">where</span>&nbsp;order_no&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">'order123'</span><br><br><span style="color: #a626a4;line-height: 26px;">if</span>(<span style="color: #a626a4;line-height: 26px;">order</span>&nbsp;&nbsp;!=&nbsp;<span style="color: #0184bb;line-height: 26px;">null</span>)&nbsp;{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;;//消息重复,直接返回<br><br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这对于很多情况下,的确能起到不错的效果,但是在并发场景下,还是会有问题。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">并发重复消息</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">假设这个消费的所有代码加起来需要1秒,有重复的消息在这1秒内(假设100毫秒)内到达(例如生产者快速重发,Broker重启等),那么很可能,上面去重代码里面会发现,数据依然是空的(因为上一条消息还没消费完,还没成功更新订单状态),</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">那么就会穿透掉检查的挡板,最后导致重复的消息消费逻辑进入到非幂等安全的业务代码中,从而引发重复消费的问题(如主键冲突抛出异常、库存被重复扣减而没释放等)</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">并发去重的解决方案之一</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">要解决上面并发场景下的消息幂等问题,一个可取的方案是开启事务把select 改成 <span style="font-weight: 700;color: rgb(248, 57, 41);">select for update</span>语句,把记录进行锁定。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oexqmcDw0weKQdDJMXEUAvnjqIzOsC2bUXWso6DP9rGs6bcj4OPz4W4zllX77NRU22PysBoo6o7jYl7iatvvib9BFN/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #a626a4;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">from</span>&nbsp;t_order&nbsp;<span style="color: #a626a4;line-height: 26px;">where</span>&nbsp;order_no&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">'THIS_ORDER_NO'</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">for</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">update</span>&nbsp;&nbsp;//开启事务<br><span style="color: #a626a4;line-height: 26px;">if</span>(order.status&nbsp;!=&nbsp;<span style="color: #0184bb;line-height: 26px;">null</span>)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;;//消息重复,直接返回<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">但这样消费的逻辑会因为引入了<span style="font-weight: 700;color: rgb(248, 57, 41);">事务</span>包裹而导致整个消息消费可能变长,<span style="font-weight: 700;color: rgb(248, 57, 41);">并发度下降</span>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当然还有其他更高级的解决方案,例如更新订单状态采取<span style="font-weight: 700;color: rgb(248, 57, 41);">乐观锁</span>,更新失败则消息重新消费之类的。但这需要针对具体业务场景做更复杂和细致的代码开发、库表设计,不在本文讨论的范围。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">但无论是<span style="font-weight: 700;color: rgb(248, 57, 41);">select for update</span>, 还是<span style="font-weight: 700;color: rgb(248, 57, 41);">乐观锁</span>这种解决方案,实际上都是基于业务表本身做去重,这无疑增加了业务开发的复杂度, 一个业务系统里面很大部分的请求处理都是依赖MQ的,如果每个消费逻辑本身都需要基于业务本身而做去重/幂等的开发的话,这是繁琐的工作量。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">本文希望探索出一个通用的消息幂等处理的方法,从而抽象出一定的工具类用以适用各个业务场景。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">Exactly Once</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在消息中间件里,有一个投递语义的概念,而这个语义里有一个叫”<span style="font-weight: 700;color: rgb(248, 57, 41);">Exactly Once</span>”,即消息肯定会被成功消费,并且只会被消费一次。以下是阿里云里对Exactly Once的解释:</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">Exactly-Once 是指发送到消息系统的消息只能被消费端处理且仅处理一次,即使生产端重试消息发送导致某消息重复投递,该消息在消费端也只被消费一次。</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在我们业务消息幂等处理的领域内,可以认为业务消息的代码肯定会被执行,并且只被执行一次,那么我们可以认为是Exactly Once。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">但这在分布式的场景下想找一个通用的方案几乎是不可能的。不过如果是针对基于数据库事务的消费逻辑,实际上是可行的。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">基于关系数据库事务插入消息表</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">假设我们业务的消息消费逻辑是:更新MySQL数据库的某张订单表的状态:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oexqmcDw0weKQdDJMXEUAvnjqIzOsC2bUXWso6DP9rGs6bcj4OPz4W4zllX77NRU22PysBoo6o7jYl7iatvvib9BFN/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #a626a4;line-height: 26px;">update</span>&nbsp;t_order&nbsp;<span style="color: #a626a4;line-height: 26px;">set</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">status</span>&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">'SUCCESS'</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">where</span>&nbsp;order_no=&nbsp;<span style="color: #50a14f;line-height: 26px;">'order123'</span>;<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">要实现Exaclty Once即这个消息只被消费一次(并且肯定要保证能消费一次),我们可以这样做:在这个数据库中增加一个<span style="font-weight: 700;color: rgb(248, 57, 41);">消息消费记录表</span>,把消息插入到这个表,并且把原来的订单更新和这个插入的动作放到同一个事务中一起提交,就能保证消息只会被消费一遍了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">流程如下:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 开启事务 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 插入消息表(处理好主键冲突的问题) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 更新订单表(原消费逻辑) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 提交事务 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">说明:</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">1、这时候如果消息消费成功并且事务提交了,那么消息表就插入成功了,这时候就算RocketMQ还没有收到消费位点的更新再次投递,也会插入消息失败而视为已经消费过,后续就直接更新消费位点了。这保证我们消费代码只会执行一次。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">2、如果事务提交之前服务挂了(例如重启),对于本地事务并没有执行所以订单没有更新,消息表也没插入成功;而对于RocketMQ服务端来说,消费位点也没更新,所以消息还会继续投递下来,投递下来发现这个消息插入消息表也是成功的,所以可以继续消费。这保证了消息不丢失。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">事实上,阿里云ONS的EXACTLY-ONCE语义的实现上,就是类似这个方案基于数据库的事务特性实现的。更多详情可参考:https://help.aliyun.com/document_detail/102777.html</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">基于这种方式,的确这是有能力拓展到不同的应用场景,因为他的实现方案与具体业务本身无关——而是依赖一个消息表。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">但是这里有它的局限性</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">1、消息的消费逻辑必须是依赖于关系型数据库事务。如果消费的消费过程中还涉及其他数据的修改,例如<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis</span>这种不支持事务特性的数据源,则这些数据是不可回滚的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">2、数据库的数据必须是在一个库,跨库无法解决</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">注:业务上,消息表的设计不应该以消息ID作为标识,而应该以业务的<span style="font-weight: 700;color: rgb(248, 57, 41);">业务主键</span>作为标识更为合理,以应对<span style="font-weight: 700;color: rgb(248, 57, 41);">生产者的重发</span>。阿里云上的消息去重只是<span style="font-weight: 700;color: rgb(248, 57, 41);">RocketMQ</span>的<span style="font-weight: 700;color: rgb(248, 57, 41);">messageId</span>,在生产者因为某些原因手动重发(例如上游针对一个交易重复请求了)的场景下起不到去重/幂等的效果(因消息id不同)。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">更复杂的业务场景</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如上所述,这种方式Exactly Once语义的实现,实际上有很多局限性,这种局限性使得这个方案基本不具备广泛应用的价值。并且由于基于事务,可能导致锁表时间过长等性能问题。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">例如我们以一个比较常见的一个订单申请的消息来举例,可能有以下几步(以下统称为步骤X):</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 检查库存(RPC) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 锁库存(RPC) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 开启事务,插入订单表(MySQL) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 调用某些其他下游服务(RPC) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 更新订单状态 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> commit 事务(MySQL) </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这种情况下,我们如果采取<span style="font-weight: 700;color: rgb(248, 57, 41);">消息表+本地事务</span>的实现方式,消息消费过程中很多子过程是不支持回滚的,也就是说就算我们加了事务,实际上这背后的操作并不是原子性的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">怎么说呢,就是说有可能第一条小在经历了第二步锁库存的时候,服务重启了,这时候实际上库存是已经在另外的服务里被锁定了,这并不能被回滚。当然消息还会再次投递下来,要保证消息能至少消费一遍,换句话说,锁库存的这个RPC接口本身依旧要支持“幂等”。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">再者,如果在这个比较耗时的长链条场景下加入事务的包裹,将大大的降低系统的并发。所以通常情况下,我们处理这种场景的消息去重的方法还是会使用一开始说的业务自己实现去重逻辑的方式,如前面加select for update,或者使用乐观锁。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">那我们有没有方法抽取出一个公共的解决方案,能兼顾去重、通用、高性能呢?</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">拆解消息执行过程</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">其中一个思路是把上面的几步,拆解成几个不同的子消息,例如:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">1、库存系统消费A:检查库存并做锁库存,发送消息B给订单服务 2、订单系统消费消息B:插入订单表(MySQL),发送消息C给自己(下游系统)消费 3、下游系统消费消息C:处理部分逻辑,发送消息D给订单系统 4、订单系统消费消息D:更新订单状态</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;"><span style="font-weight: 700;color: rgb(248, 57, 41);">注</span>:上述步骤需要保证本地事务和消息是一个事务的(至少是最终一致性的),这其中涉及到分布式事务消息相关的话题,不在本文论述。</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">可以看到这样的处理方法会使得每一步的操作都比较原子,而原子则意味着是小事务,小事务则意味着使用消息表+事务的方案显得可行。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">然而,这太复杂了!这把一个本来连续的代码逻辑割裂成多个系统多次消息交互!那还不如业务代码层面上加锁实现呢。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">更通用的解决方案</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上面<span style="font-weight: 700;color: rgb(248, 57, 41);">消息表+本地事务</span>的方案之所以有其局限性和并发的短板,究其根本是因为它依赖于关系型数据库的事务,且必须要把事务包裹于整个消息消费的环节。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如果我们能不依赖事务而实现消息的去重,那么方案就能推广到更复杂的场景例如:RPC、跨库等。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">例如,我们依旧使用消息表,但是不依赖事务,而是针对消息表增加消费状态,是否可以解决问题呢?</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">基于消息幂等表的非事务方案</span><span style="display: none;"></span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="1.135185185185185" src="/upload/75775c62939b01882a25fc6e27d0b6fe.png" data-type="png" data-w="1080" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">以上是去事务化后的消息幂等方案的流程,可以看到,此方案是无事务的,而是针对消息表本身做了状态的区分:消费中、消费完成。只有消费完成的消息才会被幂等处理掉。而对于已有消费中的消息,后面重复的消息会触发延迟消费(在RocketMQ的场景下即发送到RETRY TOPIC),之所以触发延迟消费是为了控制并发场景下,第二条消息在第一条消息没完成的过程中,去控制消息不丢(如果直接幂等,那么会丢失消息(同一个消息id的话),因为上一条消息如果没有消费完成的时候,第二条消息你已经告诉broker成功了,那么第一条消息这时候失败broker也不会重新投递了)</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上面的流程不再细说,后文有github源码的地址,读者可以参考源码的实现,这里我们回头看看我们一开始想解决的问题是否解决了:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">1、 消息已经消费成功了,第二条消息将被直接幂等处理掉(消费成功)。2、 并发场景下的消息,依旧能满足不会出现消息重复,即穿透幂等挡板的问题。3、 支持上游业务生产者重发的业务重复的消息幂等问题。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">关于第一个问题已经很明显已经解决了,在此就不讨论了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">关于第二个问题是如何解决的?主要是依靠插入消息表的这个动作做控制的,假设我们用MySQL作为消息表的存储媒介(设置消息的唯一ID为主键),那么插入的动作只有一条消息会成功,后面的消息插入会由于主键冲突而失败,走向延迟消费的分支,然后后面延迟消费的时候就会变成上面第一个场景的问题。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">关于第三个问题,只要我们设计去重的消息键让其支持业务的主键(例如订单号、请求流水号等),而不仅仅是messageId即可。所以也不是问题。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">此方案是否有消息丢失的风险?</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如果细心的读者可能会发现这里实际上是有逻辑漏洞的,问题出在上面聊到的个三问题中的第2个问题(并发场景),在并发场景下我们依赖于消息状态是做并发控制使得第2条消息重复的消息会不断延迟消费(重试)。但如果这时候第1条消息也由于一些异常原因(例如机器重启了、外部异常导致消费失败)没有成功消费成功呢?也就是说这时候延迟消费实际上每次下来看到的都是<em style="color: rgb(248, 57, 41);">消费中</em>的状态,最后消费就会被视为消费失败而被投递到死信Topic中(RocketMQ默认可以重复消费16次)。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">有这种顾虑是正确的!对于此,我们解决的方法是,插入的消息表必须要带一个最长消费过期时间,例如10分钟,意思是如果一个消息处于<em style="color: rgb(248, 57, 41);">消费中</em>超过10分钟,就需要从消息表中删除(需要程序自行实现)。所以最后这个消息的流程会是这样的:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="1.1435185185185186" src="/upload/768558cc4949991258543b3cc86551a8.png" data-type="png" data-w="1080" style="border-radius: 12px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;width: 100%;height: auto !important;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">更灵活的消息表存储媒介</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">我们这个方案实际上没有事务的,只需要一个存储的中心媒介,那么自然我们可以选择更灵活的存储媒介,例如Redis。使用Redis有两个好处:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">1、性能上损耗更低 2、上面我们讲到的超时时间可以直接利用Redis本身的ttl实现</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当然Redis存储的数据可靠性、一致性等方面是不如MySQL的,需要用户自己取舍。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">源码:RocketMQDedupListener</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">以上方案针对RocketMQ的Java实现已经开源放到Github中,具体的使用文档可以参考https://github.com/Jaskey/RocketMQDedupListener ,</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">以下仅贴一个Readme中利用Redis去重的使用样例,用以意业务中如果使用此工具加入消息去重幂等的是多么简单:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oexqmcDw0weKQdDJMXEUAvnjqIzOsC2bUXWso6DP9rGs6bcj4OPz4W4zllX77NRU22PysBoo6o7jYl7iatvvib9BFN/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//利用Redis做幂等表</span><br>DefaultMQPushConsumer&nbsp;consumer&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;DefaultMQPushConsumer(<span style="color: #50a14f;line-height: 26px;">"TEST-APP1"</span>);<br>consumer.subscribe(<span style="color: #50a14f;line-height: 26px;">"TEST-TOPIC"</span>,&nbsp;<span style="color: #50a14f;line-height: 26px;">"*"</span>);<br><br>String&nbsp;appName&nbsp;=&nbsp;consumer.getConsumerGroup();<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;大部分情况下可直接使用consumer&nbsp;group名</span><br>StringRedisTemplate&nbsp;stringRedisTemplate&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;这里省略获取StringRedisTemplate的过程</span><br>DedupConfig&nbsp;dedupConfig&nbsp;=&nbsp;DedupConfig.enableDedupConsumeConfig(appName,&nbsp;stringRedisTemplate);<br>DedupConcurrentListener&nbsp;messageListener&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;SampleListener(dedupConfig);<br><br>consumer.registerMessageListener(messageListener);<br>consumer.start();<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">以上代码大部分是原始RocketMQ的必须代码,唯一需要修改的仅仅是创建一个<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">DedupConcurrentListener</code>示例,在这个示例中指明你的消费逻辑和去重的业务键(默认是messageId)。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">更多使用详情请参考Github上的说明。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">这种实现是否一劳永逸?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">实现到这里,似乎方案挺完美的,所有的消息都能快速的接入去重,且与具体业务实现也完全解耦。那么这样是否就完美的完成去重的所有任务呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">很可惜,其实不是的。原因很简单:因为要保证消息至少被成功消费一遍,那么消息就有机会消费到一半的时候失败触发消息重试的可能。还是以上面的订单流程X:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">1、 检查库存(RPC) 2、 锁库存(RPC) 3、 开启事务,插入订单表(MySQL) 4、 调用某些其他下游服务(RPC) 5、 更新订单状态 6、 commit 事务(MySQL)</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当消息消费到步骤3的时候,我们假设MySQL异常导致失败了,触发消息重试。因为在重试前我们会删除幂等表的记录,所以消息重试的时候就会重新进入消费代码,那么步骤1和步骤2就会重新再执行一遍。如果步骤2本身不是幂等的,那么这个业务消息消费依旧没有做好完整的幂等处理。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">本实现方式的价值?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">那么既然这个并不能完整的完成消息幂等,还有什么价值呢?价值可就大了!虽然这不是解决消息幂等的银弹(事实上,软件工程领域里基本没有银弹),但是他能以便捷的手段解决:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">1、各种由于Broker、负载均衡等原因导致的消息重投递的重复问题</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">2、各种上游生产者导致的业务级别消息重复问题</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">3、重复消息并发消费的控制窗口问题,就算重复,重复也不可能同一时间进入消费逻辑</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">一些其他的消息去重的建议</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">也就是说,使用这个方法能保证正常的消费逻辑场景下(无异常,无异常退出),消息的幂等工作全部都能解决,无论是业务重复,还是rocketmq特性带来的重复。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">事实上,这已经能解决99%的消息重复问题了,毕竟异常的场景肯定是少数的。那么如果希望异常场景下也能处理好幂等的问题,可以做以下工作降低问题率:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">1、消息消费失败做好回滚处理。如果消息消费失败本身是带回滚机制的,那么消息重试自然就没有副作用了。2、消费者做好优雅退出处理。这是为了尽可能避免消息消费到一半程序退出导致的消息重试。3、一些无法做到幂等的操作,至少要做到终止消费并告警。例如锁库存的操作,如果统一的业务流水锁成功了一次库存,再触发锁库存,如果做不到幂等的处理,至少要做到消息消费触发异常(例如主键冲突导致消费异常等) 4、在#3做好的前提下,做好消息的消费监控,发现消息重试不断失败的时候,手动做好#1的回滚,使得下次重试消费成功。</p> </section>