作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;padding-right: 10px;padding-left: 10px;word-break: break-word;text-align: left;line-height: 1.25;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangTC-Light, PingFangSC-light, PingFangTC-light;letter-spacing: 2px;background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%), linear-gradient(360deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%);background-size: 20px 20px;background-position: center center;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;"><span style="color: rgb(255, 41, 65);"><strong>大家好,我是不才陈某~</strong></span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">日常开发中,秒杀下单、抢红包等等业务场景,都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开,跟大家探讨Redis分布式锁的正确使用方式。如果有不正确的地方,欢迎大家指出哈,一起学习一起进步。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;"><strong style="color: rgb(53, 148, 247);"></strong></p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;font-size: 15px;color: #595959;list-style-type: circle;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 什么是分布式锁 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 方案一:SETNX + EXPIRE </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 方案二:SETNX + value值是(系统时间+过期时间) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 方案三:使用Lua脚本(包含SETNX + EXPIRE两条指令) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 方案四:SET的扩展命令(SET EX PX NX) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 方案五:SET EX PX NX + 校验唯一随机值,再释放锁 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 方案六: 开源框架~Redisson </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 方案七:多机实现的分布式锁Redlock </section></li> </ul> <h3 data-tool="mdnice编辑器" style="color: black;font-size: 17px;font-weight: bold;text-align: center;margin-top: 20px;margin-bottom: 20px;"><span style="display: none;"></span><span style="border-bottom: 2px solid RGBA(79, 177, 249, .65);color: #2b2b2b;padding-bottom: 2px;"><span style="width: 30px;height: 30px;display: block;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5buzb4EfyHdo91XEU07gRY1qBBc1siaianKhEVtTVYtRC8iczc0WSiavODfMQ/640?wx_fmt=png");background-position: center center;background-size: 30px;margin: auto auto -8px;opacity: 1;background-repeat: no-repeat;"></span>什么是分布式锁</span><span style="display: none;"></span></h3> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;text-size-adjust: 100%;line-height: 1.55em;border-radius: 6px;color: rgb(89, 89, 89);box-sizing: inherit;border-width: 1px;border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-color: rgba(64, 184, 250, 0.4);background: rgba(64, 184, 250, 0.1);"> <span style="color: RGBA(64, 184, 250, .5);font-size: 34px;line-height: 1;font-weight: 700;">❝</span> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;word-spacing: 2px;line-height: 26px;">分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。</p> <span style="float: right;color: RGBA(64, 184, 250, .5);">❞</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">我们先来看下,一把靠谱的分布式锁应该有哪些特征:</p> <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.4330508474576271" src="/upload/312d59e4492ddd5f8c3d9c7e82895da4.png" data-type="png" data-w="1180" style="border-radius: 6px;display: block;margin: 20px auto;object-fit: contain;box-shadow: rgb(153, 153, 153) 2px 4px 7px;"> </figure> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;font-size: 15px;color: #595959;list-style-type: circle;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <strong style="color: rgb(53, 148, 247);">「互斥性」</strong>: 任意时刻,只有一个客户端能持有锁。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <strong style="color: rgb(53, 148, 247);">「锁超时释放」</strong>:持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <strong style="color: rgb(53, 148, 247);">「可重入性」</strong>:一个线程如果获取了锁之后,可以再次对其请求加锁。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <strong style="color: rgb(53, 148, 247);">「高性能和高可用」</strong>:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <strong style="color: rgb(53, 148, 247);">「安全性」</strong>:锁只能被持有的客户端删除,不能被其他客户端删除 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="color: black;font-size: 17px;font-weight: bold;text-align: center;margin-top: 20px;margin-bottom: 20px;"><span style="display: none;"></span><span style="border-bottom: 2px solid RGBA(79, 177, 249, .65);color: #2b2b2b;padding-bottom: 2px;"><span style="width: 30px;height: 30px;display: block;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5buzb4EfyHdo91XEU07gRY1qBBc1siaianKhEVtTVYtRC8iczc0WSiavODfMQ/640?wx_fmt=png");background-position: center center;background-size: 30px;margin: auto auto -8px;opacity: 1;background-repeat: no-repeat;"></span>Redis分布式锁方案一:SETNX + EXPIRE</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">提到Redis的分布式锁,很多小伙伴马上就会想到<code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">setnx</code>+ <code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">expire</code>命令。即先用<code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">setnx</code>来抢锁,如果抢到之后,再用<code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">expire</code>给锁设置一个过期时间,防止锁忘记了释放。</p> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;text-size-adjust: 100%;line-height: 1.55em;border-radius: 6px;color: rgb(89, 89, 89);box-sizing: inherit;border-width: 1px;border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-color: rgba(64, 184, 250, 0.4);background: rgba(64, 184, 250, 0.1);"> <span style="color: RGBA(64, 184, 250, .5);font-size: 34px;line-height: 1;font-weight: 700;">❝</span> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;word-spacing: 2px;line-height: 26px;">SETNX 是SET IF NOT EXISTS的简写.日常命令格式是SETNX key value,如果 key不存在,则SETNX成功返回1,如果这个key已经存在了,则返回0。</p> <span style="float: right;color: RGBA(64, 184, 250, .5);">❞</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">假设某电商网站的某商品做秒杀活动,key可以设置为key_resource_id,value设置任意值,伪代码如下:</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("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5bugMGECNTq4hxb1SnmxU30UCuKUvJWo6wgLWibYBSMhqGAdWBFRc2MWXQ/640?wx_fmt=png") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;letter-spacing: 0px;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #c678dd;line-height: 26px;">if</span>(jedis.setnx(key_resource_id,lock_value) == 1){ //加锁<br> expire(key_resource_id,100); //设置过期时间<br> try {<br> <span style="color: #c678dd;line-height: 26px;">do</span> something //业务请求<br> }<span style="line-height: 26px;"><span style="color: #61aeee;line-height: 26px;">catch</span></span>(){<br> }<br> finally {<br> jedis.del(key_resource_id); //释放锁<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">但是这个方案中,<code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">setnx</code>和<code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">expire</code>两个命令分开了,<strong style="color: rgb(53, 148, 247);">「不是原子操作」</strong>。如果执行完<code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">setnx</code>加锁,正要执行<code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">expire</code>设置过期时间时,进程crash或者要重启维护了,那么这个锁就“长生不老”了,<strong style="color: rgb(53, 148, 247);">「别的线程永远获取不到锁啦」</strong>。</p> <h3 data-tool="mdnice编辑器" style="color: black;font-size: 17px;font-weight: bold;text-align: center;margin-top: 20px;margin-bottom: 20px;"><span style="display: none;"></span><span style="border-bottom: 2px solid RGBA(79, 177, 249, .65);color: #2b2b2b;padding-bottom: 2px;"><span style="width: 30px;height: 30px;display: block;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5buzb4EfyHdo91XEU07gRY1qBBc1siaianKhEVtTVYtRC8iczc0WSiavODfMQ/640?wx_fmt=png");background-position: center center;background-size: 30px;margin: auto auto -8px;opacity: 1;background-repeat: no-repeat;"></span>Redis分布式锁方案二:SETNX + value值是(系统时间+过期时间)</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">为了解决方案一,<strong style="color: rgb(53, 148, 247);">「发生异常锁得不到释放的场景」</strong>,有小伙伴认为,可以把过期时间放到<code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">setnx</code>的value值里面。如果加锁失败,再拿出value值校验一下即可。加锁代码如下:</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("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5bugMGECNTq4hxb1SnmxU30UCuKUvJWo6wgLWibYBSMhqGAdWBFRc2MWXQ/640?wx_fmt=png") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;letter-spacing: 0px;padding-top: 15px;background: #282c34;border-radius: 5px;">long expires = System.currentTimeMillis() + expireTime; //系统时间+设置的过期时间<br>String expiresStr = String.valueOf(expires);<br><br>// 如果当前锁不存在,返回加锁成功<br><span style="color: #c678dd;line-height: 26px;">if</span> (jedis.setnx(key_resource_id, expiresStr) == 1) {<br> <span style="color: #e6c07b;line-height: 26px;">return</span> <span style="color: #56b6c2;line-height: 26px;">true</span>;<br>} <br>// 如果锁已经存在,获取锁的过期时间<br>String currentValueStr = jedis.get(key_resource_id);<br><br>// 如果获取到的过期时间,小于系统当前时间,表示已经过期<br><span style="color: #c678dd;line-height: 26px;">if</span> (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {<br><br> // 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间(不了解redis的getSet命令的小伙伴,可以去官网看下哈)<br> String oldValueStr = jedis.getSet(key_resource_id, expiresStr);<br> <br> <span style="color: #c678dd;line-height: 26px;">if</span> (oldValueStr != null && oldValueStr.equals(currentValueStr)) {<br> // 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才可以加锁<br> <span style="color: #e6c07b;line-height: 26px;">return</span> <span style="color: #56b6c2;line-height: 26px;">true</span>;<br> }<br>}<br> <br>//其他情况,均返回加锁失败<br><span style="color: #e6c07b;line-height: 26px;">return</span> <span style="color: #56b6c2;line-height: 26px;">false</span>;<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">这个方案的优点是,巧妙移除<code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">expire</code>单独设置过期时间的操作,把<strong style="color: rgb(53, 148, 247);">「过期时间放到setnx的value值」</strong>里面来。解决了方案一发生异常,锁得不到释放的问题。但是这个方案还有别的缺点:</p> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;text-size-adjust: 100%;line-height: 1.55em;border-radius: 6px;color: rgb(89, 89, 89);box-sizing: inherit;border-width: 1px;border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-color: rgba(64, 184, 250, 0.4);background: rgba(64, 184, 250, 0.1);"> <span style="color: RGBA(64, 184, 250, .5);font-size: 34px;line-height: 1;font-weight: 700;">❝</span> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;font-size: 15px;list-style-type: circle;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 过期时间是客户端自己生成的(System.currentTimeMillis()是当前系统的时间),必须要求分布式环境下,每个客户端的时间必须同步。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 如果锁过期的时候,并发多个客户端同时请求过来,都执行jedis.getSet(),最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 该锁没有保存持有者的唯一标识,可能被别的客户端释放/解锁。 </section></li> </ul> <span style="float: right;color: RGBA(64, 184, 250, .5);">❞</span> </blockquote> <h3 data-tool="mdnice编辑器" style="color: black;font-size: 17px;font-weight: bold;text-align: center;margin-top: 20px;margin-bottom: 20px;"><span style="display: none;"></span><span style="border-bottom: 2px solid RGBA(79, 177, 249, .65);color: #2b2b2b;padding-bottom: 2px;"><span style="width: 30px;height: 30px;display: block;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5buzb4EfyHdo91XEU07gRY1qBBc1siaianKhEVtTVYtRC8iczc0WSiavODfMQ/640?wx_fmt=png");background-position: center center;background-size: 30px;margin: auto auto -8px;opacity: 1;background-repeat: no-repeat;"></span>Redis分布式锁方案三:使用Lua脚本(包含SETNX + EXPIRE两条指令)</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">实际上,我们还可以使用Lua脚本来保证原子性(包含setnx和expire两条指令),lua脚本如下:</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("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5bugMGECNTq4hxb1SnmxU30UCuKUvJWo6wgLWibYBSMhqGAdWBFRc2MWXQ/640?wx_fmt=png") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;letter-spacing: 0px;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #c678dd;line-height: 26px;">if</span> redis.call(<span style="color: #98c379;line-height: 26px;">'setnx'</span>,KEYS[1],ARGV[1]) == 1 <span style="color: #c678dd;line-height: 26px;">then</span><br> redis.call(<span style="color: #98c379;line-height: 26px;">'expire'</span>,KEYS[1],ARGV[2])<br><span style="color: #c678dd;line-height: 26px;">else</span><br> <span style="color: #e6c07b;line-height: 26px;">return</span> 0<br>end;<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">加锁代码如下:</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("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5bugMGECNTq4hxb1SnmxU30UCuKUvJWo6wgLWibYBSMhqGAdWBFRc2MWXQ/640?wx_fmt=png") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;letter-spacing: 0px;padding-top: 15px;background: #282c34;border-radius: 5px;"> String lua_scripts = <span style="color: #98c379;line-height: 26px;">"if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then"</span> +<br> <span style="color: #98c379;line-height: 26px;">" redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end"</span>; <br>Object result = jedis.eval(lua_scripts, Collections.singletonList(key_resource_id), Collections.singletonList(values));<br>//判断是否成功<br><span style="color: #e6c07b;line-height: 26px;">return</span> result.equals(1L);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">这个方案,跟方案二对比,你觉得哪个更好呢?</p> <h3 data-tool="mdnice编辑器" style="color: black;font-size: 17px;font-weight: bold;text-align: center;margin-top: 20px;margin-bottom: 20px;"><span style="display: none;"></span><span style="border-bottom: 2px solid RGBA(79, 177, 249, .65);color: #2b2b2b;padding-bottom: 2px;"><span style="width: 30px;height: 30px;display: block;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5buzb4EfyHdo91XEU07gRY1qBBc1siaianKhEVtTVYtRC8iczc0WSiavODfMQ/640?wx_fmt=png");background-position: center center;background-size: 30px;margin: auto auto -8px;opacity: 1;background-repeat: no-repeat;"></span>Redis分布式锁方案方案四:SET的扩展命令(SET EX PX NX)</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">除了使用,使用Lua脚本,保证<code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">SETNX + EXPIRE</code>两条指令的原子性,我们还可以巧用Redis的SET指令扩展参数!(<code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">SET key value[EX seconds][PX milliseconds][NX|XX]</code>),它也是原子性的!</p> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;text-size-adjust: 100%;line-height: 1.55em;border-radius: 6px;color: rgb(89, 89, 89);box-sizing: inherit;border-width: 1px;border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-color: rgba(64, 184, 250, 0.4);background: rgba(64, 184, 250, 0.1);"> <span style="color: RGBA(64, 184, 250, .5);font-size: 34px;line-height: 1;font-weight: 700;">❝</span> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;word-spacing: 2px;line-height: 26px;">SET key value[EX seconds][PX milliseconds][NX|XX]</p> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;font-size: 15px;list-style-type: circle;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> NX :表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> EX seconds :设定key的过期时间,时间单位是秒。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> PX milliseconds: 设定key的过期时间,单位为毫秒 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> XX: 仅当key存在时设置值 </section></li> </ul> <span style="float: right;color: RGBA(64, 184, 250, .5);">❞</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">伪代码demo如下:</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("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5bugMGECNTq4hxb1SnmxU30UCuKUvJWo6wgLWibYBSMhqGAdWBFRc2MWXQ/640?wx_fmt=png") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;letter-spacing: 0px;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #c678dd;line-height: 26px;">if</span>(jedis.set(key_resource_id, lock_value, <span style="color: #98c379;line-height: 26px;">"NX"</span>, <span style="color: #98c379;line-height: 26px;">"EX"</span>, 100s) == 1){ //加锁<br> try {<br> <span style="color: #c678dd;line-height: 26px;">do</span> something //业务处理<br> }<span style="line-height: 26px;"><span style="color: #61aeee;line-height: 26px;">catch</span></span>(){<br> }<br> finally {<br> jedis.del(key_resource_id); //释放锁<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">但是呢,这个方案还是可能存在问题:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;font-size: 15px;color: #595959;list-style-type: circle;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 问题一: <strong style="color: rgb(53, 148, 247);">「锁过期释放了,业务还没执行完」</strong>。假设线程a获取锁成功,一直在执行临界区的代码。但是100s过去后,它还没执行完。但是,这时候锁已经过期了,此时线程b又请求过来。显然线程b就可以获得锁成功,也开始执行临界区的代码。那么问题就来了,临界区的业务代码都不是严格串行执行的啦。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 问题二: <strong style="color: rgb(53, 148, 247);">「锁被别的线程误删」</strong>。假设线程a执行完后,去释放锁。但是它不知道当前的锁可能是线程b持有的(线程a去释放锁时,有可能过期时间已经到了,此时线程b进来占有了锁)。那线程a就把线程b的锁释放掉了,但是线程b临界区业务代码可能都还没执行完呢。 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="color: black;font-size: 17px;font-weight: bold;text-align: center;margin-top: 20px;margin-bottom: 20px;"><span style="display: none;"></span><span style="border-bottom: 2px solid RGBA(79, 177, 249, .65);color: #2b2b2b;padding-bottom: 2px;"><span style="width: 30px;height: 30px;display: block;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5buzb4EfyHdo91XEU07gRY1qBBc1siaianKhEVtTVYtRC8iczc0WSiavODfMQ/640?wx_fmt=png");background-position: center center;background-size: 30px;margin: auto auto -8px;opacity: 1;background-repeat: no-repeat;"></span>方案五:SET EX PX NX + 校验唯一随机值,再删除</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">既然锁可能被别的线程误删,那我们给value值设置一个标记当前线程唯一的随机数,在删除的时候,校验一下,不就OK了嘛。伪代码如下:</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("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5bugMGECNTq4hxb1SnmxU30UCuKUvJWo6wgLWibYBSMhqGAdWBFRc2MWXQ/640?wx_fmt=png") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;letter-spacing: 0px;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #c678dd;line-height: 26px;">if</span>(jedis.set(key_resource_id, uni_request_id, <span style="color: #98c379;line-height: 26px;">"NX"</span>, <span style="color: #98c379;line-height: 26px;">"EX"</span>, 100s) == 1){ //加锁<br> try {<br> <span style="color: #c678dd;line-height: 26px;">do</span> something //业务处理<br> }<span style="line-height: 26px;"><span style="color: #61aeee;line-height: 26px;">catch</span></span>(){<br> }<br> finally {<br> //判断是不是当前线程加的锁,是才释放<br> <span style="color: #c678dd;line-height: 26px;">if</span> (uni_request_id.equals(jedis.get(key_resource_id))) {<br> jedis.del(lockKey); //释放锁<br> }<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">在这里,<strong style="color: rgb(53, 148, 247);">「判断是不是当前线程加的锁」</strong>和<strong style="color: rgb(53, 148, 247);">「释放锁」</strong>不是一个原子操作。如果调用jedis.del()释放锁的时候,可能这把锁已经不属于当前客户端,会解除他人加的锁。</p> <p style="text-align: center;padding: 0px 0.5em;"><img class="rich_pages wxw-img" data-ratio="0.4436689930209372" data-s="300,640" src="/upload/6d80bf1e409a0036fce5efc5210e5058.png" data-type="png" data-w="1003" style="box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">为了更严谨,一般也是用lua脚本代替。lua脚本如下:<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("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5bugMGECNTq4hxb1SnmxU30UCuKUvJWo6wgLWibYBSMhqGAdWBFRc2MWXQ/640?wx_fmt=png") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;letter-spacing: 0px;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #c678dd;line-height: 26px;">if</span> redis.call(<span style="color: #98c379;line-height: 26px;">'get'</span>,KEYS[1]) == ARGV[1] <span style="color: #c678dd;line-height: 26px;">then</span> <br> <span style="color: #e6c07b;line-height: 26px;">return</span> redis.call(<span style="color: #98c379;line-height: 26px;">'del'</span>,KEYS[1]) <br><span style="color: #c678dd;line-height: 26px;">else</span><br> <span style="color: #e6c07b;line-height: 26px;">return</span> 0<br>end;<br></code></pre> <h3 data-tool="mdnice编辑器" style="color: black;font-size: 17px;font-weight: bold;text-align: center;margin-top: 20px;margin-bottom: 20px;"><span style="display: none;"></span><span style="border-bottom: 2px solid RGBA(79, 177, 249, .65);color: #2b2b2b;padding-bottom: 2px;"><span style="width: 30px;height: 30px;display: block;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5buzb4EfyHdo91XEU07gRY1qBBc1siaianKhEVtTVYtRC8iczc0WSiavODfMQ/640?wx_fmt=png");background-position: center center;background-size: 30px;margin: auto auto -8px;opacity: 1;background-repeat: no-repeat;"></span>Redis分布式锁方案六:Redisson框架</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">方案五还是可能存在<strong style="color: rgb(53, 148, 247);">「锁过期释放,业务没执行完」</strong>的问题。有些小伙伴认为,稍微把锁过期时间设置长一些就可以啦。其实我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">当前开源框架Redisson解决了这个问题。我们一起来看下Redisson底层原理图吧:</p> <p style="text-align: center;padding: 0px 0.5em;"><img class="rich_pages wxw-img" data-ratio="0.627633209417596" data-s="300,640" src="/upload/be162f45e5403efbf26f2486cd957c2e.png" data-type="png" data-w="1614" style="border-radius: 0px;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">只要线程一加锁成功,就会启动一个<code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">watch dog</code>看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。因此,Redisson就是使用watch dog解决了<strong style="color: rgb(53, 148, 247);">「锁过期释放,业务没执行完」</strong>问题。<br></p> <h3 data-tool="mdnice编辑器" style="color: black;font-size: 17px;font-weight: bold;text-align: center;margin-top: 20px;margin-bottom: 20px;"><span style="display: none;"></span><span style="border-bottom: 2px solid RGBA(79, 177, 249, .65);color: #2b2b2b;padding-bottom: 2px;"><span style="width: 30px;height: 30px;display: block;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5buzb4EfyHdo91XEU07gRY1qBBc1siaianKhEVtTVYtRC8iczc0WSiavODfMQ/640?wx_fmt=png");background-position: center center;background-size: 30px;margin: auto auto -8px;opacity: 1;background-repeat: no-repeat;"></span>Redis分布式锁方案七:多机实现的分布式锁Redlock+Redisson</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">前面六种方案都只是基于单机版的讨论,还不是很完美。其实Redis一般都是集群部署的:</p> <p style="text-align: center;padding: 0px 0.5em;"><img class="rich_pages wxw-img" data-ratio="0.3738229755178908" data-s="300,640" src="/upload/c76dc0bf46eeb7dfbdab2317be2b4b08.png" data-type="png" data-w="1062" style="box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">如果线程一在Redis的master节点上拿到了锁,但是加锁的key还没同步到slave节点。恰好这时,master节点发生故障,一个slave节点就会升级为master节点。线程二就可以获取同个key的锁啦,但线程一也已经拿到锁了,锁的安全性就没了。<br></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">为了解决这个问题,Redis作者 antirez提出一种高级的分布式锁算法:Redlock。Redlock核心思想是这样的:</p> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;text-size-adjust: 100%;line-height: 1.55em;border-radius: 6px;color: rgb(89, 89, 89);box-sizing: inherit;border-width: 1px;border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-color: rgba(64, 184, 250, 0.4);background: rgba(64, 184, 250, 0.1);"> <span style="color: RGBA(64, 184, 250, .5);font-size: 34px;line-height: 1;font-weight: 700;">❝</span> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;word-spacing: 2px;line-height: 26px;">搞多个Redis master部署,以保证它们不会同时宕掉。并且这些master节点是完全相互独立的,相互之间不存在数据同步。同时,需要确保在这多个master实例上,是与在Redis单实例,使用相同方法来获取和释放锁。</p> <span style="float: right;color: RGBA(64, 184, 250, .5);">❞</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">我们假设当前有5个Redis master节点,在5台服务器上面运行这些Redis实例。</p> <p style="text-align: center;padding: 0px 0.5em;"><img class="rich_pages wxw-img" data-ratio="0.6836483155299918" data-s="300,640" src="/upload/44d3ae3cbce8887fca45fe133c8d6332.png" data-type="png" data-w="1217" style="box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">RedLock的实现步骤:如下<br></p> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;text-size-adjust: 100%;line-height: 1.55em;border-radius: 6px;color: rgb(89, 89, 89);box-sizing: inherit;border-width: 1px;border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-color: rgba(64, 184, 250, 0.4);background: rgba(64, 184, 250, 0.1);"> <span style="color: RGBA(64, 184, 250, .5);font-size: 34px;line-height: 1;font-weight: 700;">❝</span> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;font-size: 15px;list-style-type: circle;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 1.获取当前时间,以毫秒为单位。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 2.按顺序向5个master节点请求加锁。客户端设置网络连接和响应超时时间,并且超时时间要小于锁的失效时间。(假设锁自动失效时间为10秒,则超时时间一般在5-50毫秒之间,我们就假设超时时间是50ms吧)。如果超时,跳过该master节点,尽快去尝试下一个master节点。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 3.客户端使用当前时间减去开始获取锁时间(即步骤1记录的时间),得到获取锁使用的时间。当且仅当超过一半(N/2+1,这里是5/2+1=3个节点)的Redis master节点都获得锁,并且使用的时间小于锁失效时间时,锁才算获取成功。(如上图,10s> 30ms+40ms+50ms+4m0s+50ms) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 如果取到了锁,key的真正有效时间就变啦,需要减去获取锁所使用的时间。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 如果获取锁失败(没有在至少N/2+1个master实例取到锁,有或者获取锁时间已经超过了有效时间),客户端要在所有的master节点上解锁(即便有些master节点根本就没有加锁成功,也需要解锁,以防止有些漏网之鱼)。 </section></li> </ul> <span style="float: right;color: RGBA(64, 184, 250, .5);">❞</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">简化下步骤就是:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;font-size: 15px;color: #595959;list-style-type: circle;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 按顺序向5个master节点请求加锁 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 根据设置的超时时间来判断,是不是要跳过该master节点。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 如果大于等于3个节点加锁成功,并且使用的时间小于锁的有效期,即可认定加锁成功啦。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 如果获取锁失败,解锁! </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">Redisson实现了redLock版本的锁,有兴趣的小伙伴,可以去了解一下哈~</p> <h3 data-tool="mdnice编辑器" style="color: black;font-size: 17px;font-weight: bold;text-align: center;margin-top: 20px;margin-bottom: 20px;"><span style="display: none;"></span><span style="border-bottom: 2px solid RGBA(79, 177, 249, .65);color: #2b2b2b;padding-bottom: 2px;"><span style="width: 30px;height: 30px;display: block;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/sMmr4XOCBzGxM0ZotibjMv7bw8KMNT5buzb4EfyHdo91XEU07gRY1qBBc1siaianKhEVtTVYtRC8iczc0WSiavODfMQ/640?wx_fmt=png");background-position: center center;background-size: 30px;margin: auto auto -8px;opacity: 1;background-repeat: no-repeat;"></span>参考与感谢</span><span style="display: none;"></span></h3> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;font-size: 15px;color: #595959;list-style-type: circle;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> redis系列:分布式锁 <sup style="line-height: 0;">[1]</sup> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 浅析 Redis 分布式锁解决方案 <sup style="line-height: 0;">[2]</sup> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 细说Redis分布式锁🔒 <sup style="line-height: 0;">[3]</sup> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> Redlock:Redis分布式锁最牛逼的实现 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="color: black;font-size: 17px;font-weight: bold;text-align: center;margin-top: 20px;margin-bottom: 20px;"><span style="color: rgb(89, 89, 89);letter-spacing: 1px;border-bottom: 2px solid rgb(64, 184, 250);background-image: linear-gradient(white 60%, rgba(64, 184, 250, 0.4) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;font-size: 20px;">Reference</span></h3> <section data-tool="mdnice编辑器" style="background: rgba(53, 148, 247, 0.4);padding: 20px;font-size: 14px;border-radius: 6px;border-width: 1px;border-style: solid;border-color: rgb(53, 148, 247);"> <span style="display: flex;"><span style="display: inline;width: 10%;background: none;font-size: 80%;opacity: 0.6;line-height: 26px;font-family: ptima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">[1]</span><p style="word-spacing: 2px;display: inline;width: 90%;line-height: 26px;word-break: break-all;color: rgb(89, 89, 89);font-weight: bold;">redis系列:分布式锁: <span style="display: block;font-weight: normal;">https://juejin.cn/post/6844903656911798285</span></p></span> <span style="display: flex;"><span style="display: inline;width: 10%;background: none;font-size: 80%;opacity: 0.6;line-height: 26px;font-family: ptima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">[2]</span><p style="word-spacing: 2px;display: inline;width: 90%;line-height: 26px;word-break: break-all;color: rgb(89, 89, 89);font-weight: bold;">浅析 Redis 分布式锁解决方案: <span style="display: block;font-weight: normal;">https://www.infoq.cn/article/dvaaj71f4fbqsxmgvdce</span></p></span> <span style="display: flex;"><span style="display: inline;width: 10%;background: none;font-size: 80%;opacity: 0.6;line-height: 26px;font-family: ptima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">[3]</span><p style="word-spacing: 2px;display: inline;width: 90%;line-height: 26px;word-break: break-all;color: rgb(89, 89, 89);font-weight: bold;">细说Redis分布式锁🔒: <span style="display: block;font-weight: normal;">https://juejin.cn/post/6844904082860146695#heading-3</span></p></span> </section> </section> <p><br></p> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="Mzg3NzU5NTIwNg==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxpY5pHeUUaicHSaqaylBb25Rbib0ocE4ZmXWdicS9tbGvFc9qvbiaNDibKKvuFYqlUdSW6VicXhoLNHfMQ/0?wx_fmt=png" data-nickname="捡田螺的小男孩" data-alias="" data-signature="专注后端技术栈,热爱分享,热爱交朋友,热爱工作总结。毕业于华南理工大学,软件工程专业~" data-from="0"></mpprofile> </section> <p style="text-align: right;"><span style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-align: right;color: rgb(53, 53, 53);font-size: 16px;word-spacing: 0.8px;background-color: rgb(255, 255, 255);"><br></span></p> <p style="text-align: right;"><span style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-align: right;color: rgb(53, 53, 53);font-size: 16px;word-spacing: 0.8px;background-color: rgb(255, 255, 255);">求点赞、在看、分享三连</span><img class="rich_pages wxw-img" data-fileid="100011967" data-ratio="1" src="/upload/1d550a991385b842a21e2b301725407e.png" data-type="png" data-w="20" style="outline: 0px;vertical-align: text-bottom;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-align: right;white-space: normal;color: rgb(53, 53, 53);font-size: 16px;word-spacing: 0.8px;background-color: rgb(255, 255, 255);display: inline-block;box-sizing: border-box !important;visibility: visible !important;width: 20px !important;"></p>
作者:微信小助手
<p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.623046875" data-s="300,640" data-type="jpeg" data-w="1024" style="height: auto !important;" src="/upload/880cc16cdf7d0ae580f55af67e841fb4.jpg"></p> <section style="line-height: 1.75em;"> <br> </section> <article data-clipboard-cangjie="["root",{},["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},"Redis 作为一种非常流行的内存数据"],["span",{"bold":false,"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},"库,通过将数据保存在内存中,Redis 得以拥有极高的读写性能。"],["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},"但是一旦进程退出,Redis 的数据就会全部丢失。"]]],["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},""]]],["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},"为了解决这个问题,Redis 提供了 RDB 和 AOF 两种持久化方案,将内存中的数据保存到磁盘中,避免数据丢失。本文将重点讨论AOF持久化方案,以及其存在的一些问题,并探讨在"],["span",{"color":"rgb(0, 0, 0)","fonts":{"ascii":"Optima-Regular","hAnsi":"Optima-Regular","cs":"Optima-Regular","eastAsia":"Optima-Regular"},"spacing":0.20400000000000001,"sz":12,"szUnit":"pt","data-type":"leaf"},"Redis 7.0 (已发布"]],["a",{"href":"https://raw.githubusercontent.com/redis/redis/7.0/00-RELEASENOTES"},["span",{"data-type":"text"},["span",{"color":"rgb(0, 0, 0)","fonts":{"ascii":"Optima-Regular","hAnsi":"Optima-Regular","cs":"Optima-Regular","eastAsia":"Optima-Regular"},"spacing":0.20400000000000001,"sz":12,"szUnit":"pt","data-type":"leaf"},"RC1"]]],["span",{"data-type":"text"},["span",{"color":"rgb(0, 0, 0)","fonts":{"ascii":"Optima-Regular","hAnsi":"Optima-Regular","cs":"Optima-Regular","eastAsia":"Optima-Regular"},"spacing":0.20400000000000001,"sz":12,"szUnit":"pt","data-type":"leaf"},") 中"],["span",{"bold":false,"color":"rgb(0, 0, 0)","fonts":{"ascii":"Optima-Regular","hAnsi":"Optima-Regular","cs":"Optima-Regular","eastAsia":"Optima-Regular"},"spacing":0.20400000000000001,"sz":12,"szUnit":"pt","data-type":"leaf"},"Multi Part AOF"],["span",{"color":"rgb(0, 0, 0)","fonts":{"ascii":"Optima-Regular","hAnsi":"Optima-Regular","cs":"Optima-Regular","eastAsia":"Optima-Regular"},"spacing":0.20400000000000001,"sz":12,"szUnit":"pt","data-type":"leaf"},"(下文简称为"],["span",{"color":"rgb(0, 0, 0)","fonts":{"ascii":"Optima-Regular","cs":"Optima-Regular","eastAsia":"Optima-Regular","hAnsi":"Optima-Regular"},"spacing":0.204,"sz":12,"szUnit":"pt","data-type":"leaf"},"MP-AOF)设计和实现细节。"]]],["h1",{"uuid":"kyo2dj0xn514joqncg","spacing":{"before":14.666666666666668,"after":14.666666666666668,"line":0.8529411764705882}},["span",{"data-type":"text"},["span",{"bold":true,"sz":20,"szUnit":"pt","data-type":"leaf"},"AOF"]]],["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},"AOF( append only file )持久化以独立日志文件的方式记录每条写命令,并在 Redis 启动时回放 AOF 文件中的命令以达到恢复数据的目的。"]]],["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},""]]],["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},"由于AOF会以追加的方式记录每一条redis的写命令,因此随着Redis处理的写命令增多,AOF文件也会变得越来越大,命令回放的时间也会增多,为了解决这个问题,Redis引入了AOF rewrite机制(下文称之为AOFRW)。AOFRW会移除AOF中冗余的写命令,以等效的方式重写、生成一个新的AOF文件,来达到减少AOF文件大小的目的。"]]],["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},""]]],["h1",{"uuid":"kytd9pe0fsmnb0u0bqp","spacing":{"before":14.666666666666668,"after":14.666666666666668,"line":0.8529411764705882}},["span",{"data-type":"text"},["span",{"bold":true,"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":20,"szUnit":"pt","data-type":"leaf"},"AOFRW"]]],["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},"图1展示的是AOFRW的实现原理。当AOFRW被触发执行时,Redis首先会fork一个子进程进行后台重写操作,该操作会将执行fork那一刻Redis的数据快照全部重写到一个名为"],["span",{"data-type":"leaf"},"temp-rewriteaof-bg-pid.aof"],["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},"的临时AOF文件中。 "]]],["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},""]]],["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},"由于重写操作为子进程后台执行,主进程在AOF重写期间依然可以正常响应用户命令。因此,为了让子进程最终也能获取重写期间主进程产生的增量变化,主进程除了会将执行的写命令写入aof_buf,还会写一份到aof_rewrite_buf中进行缓存。在子进程重写的后期阶段,主进程会将aof_rewrite_buf中累积的数据使用pipe发送给子进程,子进程会将这些数据追加到临时AOF文件中(详细原理可参考"]],["a",{"href":"http://mysql.taobao.org/monthly/2018/12/06/"},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},"这里"]]],["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},")。"]]],["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},""]]],["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":"pt","data-type":"leaf"},"当主进程承接了较大的写入流量时,aof_rewrite_buf中可能会堆积非常多的数据,导致在重写期间子进程无法将aof_rewrite_buf中的数据全部消费完。此时,aof_rewrite_buf剩余的数据将在重写结束时由主进程进行处理。"]]],["p",{},["span",{"data-type":"text"},["span",{"color":"rgb(51, 51, 51)","fonts":{"ascii":"-apple-system","hAnsi":"-apple-system","cs":"-apple-system","eastAsia":"-apple-system"},"sz":12,"szUnit":
作者:微信小助手
<p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;"><span style="color: rgb(255, 76, 0);"><strong>学习微服务和中台的必经之路就是 DDD</strong></span>,这次我们来卷一波~<br></p> <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, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;"> <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><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><br></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">DDD 是什么,DDD 的英文全称是 Domain-Driven Design,翻译过来就是领域驱动设计。</p> <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;">当我们聊微服务也必然会会聊到中台,中台又是什么呢?</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: 0em;margin-bottom: 0em;">中台从 2015 年提出,就已经被我们熟知,但是每个人对中台的认识可能都千差万别,有没有一个大家都比较认可的定义呢?</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;">将通用的<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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">可复用</code>的业务能力沉淀到中台业务模型,实现企业级能力复用。</p> </blockquote> <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;">而中台落地时,依然会面临微服务设计和拆分的问题。</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);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;font-size: 15px;"><span style="font-weight: 700;color: rgb(248, 57, 41);">微服务</span>:中台落地时需要用微服务进行支撑。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;font-size: 15px;"><span style="font-weight: 700;color: rgb(248, 57, 41);">中台</span>:复用业务,实现企业级能力复用。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;font-size: 15px;"><span style="font-weight: 700;color: rgb(248, 57, 41);">DDD</span>:对中台进行领域建模,实现适合企业发展的中台。</p> </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">DDD 可以说是微服务和中台的产品经理。我们去写业务功能时,是面向领域的,而不是面向数据库表来实现代码的。</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);">二、DDD 是什么?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">DDD 的核心思想:是通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">DDD 是一种处理高度复杂领域的设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。</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> <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> <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);">三、DDD 架构分层</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">首先我们来看下架构分层的原理图:</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.0696920583468394" src="/upload/52747bfa8f9fa9141a15f91a995784a9.png" data-type="png" data-w="1234" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </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;">用户接口层</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">用户接口层主要包含用户界面、Web 服务。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">用户接口层负责向用户显示信息和解释用户指令。这里的用户可能是:用户、程序、自动化测试和批处理脚本等等。</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: 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;">应用服务是在应用层的,它负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果的拼装,以粗粒度的服务通过 API 网关向前端发布。还有,应用服务还可以进行安全认证、权限校验、事务控制、发送或订阅领域事件等。</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: 0em;margin-bottom: 0em;">领域层主要实现企业的核心业务逻辑,和之前的三层架构的 Service 层很像。</p> <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;">领域模型的业务逻辑主要通过实体和领域服务来实现,采用充血模型来时先所有与之相关的业务功能。充血模型后面会解释。当单一实体(或值对象)不能实现时,领域服务就来进行聚合多个实体(或值对象),来实现复杂的业务逻辑。</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: 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;">它是封装了所有的基础服务,当切换基础组件时,只用稍微修改下基础服务就可以了。</p> <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;">这个就是采用了依赖倒置的原则,通过解耦来保持独立的核心业务逻辑。</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;">传统三层架构转 DDD 四层架构</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">传统的三层架构就是 controller->service->model 这种模型,我们的思维习惯就是基于数据库的表来开发业务功能。这种分层架构给开发人员带来了便利,但是如果有其他人过来看你的代码,他会很难从业务角度去理解,因为这些代码都是为操作数据库的表而写。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">有了 DDD 之后,代码是面向业务功能的,而不是面向数据库表的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">DDD 分层架构将业务逻辑层的服务拆分到了应用层和领域层。应用层快速响应前端的变化,领域层实现领域模型的能力。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">三层架构数据访问采用 DAO 方式;DDD 分层架构的数据库等基础资源访问,采用了仓储(Repository)设计模式,通过依赖倒置实现各层对基础资源的解耦。</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);">四、DDD 中各种 Object</span></h2> <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);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">数据持久化对象</span> (Persistent Object, PO),与数据库结构一一映射,它是数据持久化过程中的数据载体。 </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>( Domain Object, DO),微服务运行时核心业务对象的载体, DO 一般包括实体或值对象。 </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>( Data Transfer Object, DTO),用于前端应用与微服务应用层或者微服务之间的数据组装和传输,是应用之间数据传输的载体。 </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>(View Object, VO),用于封装展示层指定页面或组件的数据。 </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>的主要数据对象是PO。在设计时,我们需要先建立DO和PO的映射关系。大多数情况下DO和PO是一一对应的。但也有DO和PO多对多的情况。在DO和PO数据转换时,需要进行数据重组。对于DO对象较多复杂的数据转换操作,你可以在聚合用工厂模式来实现。当DO数据需要持久化时,先将DO转换为PO对象,由仓储实现服务完成数据库持久化操作。当DO需要构建和数据初始化时,仓储实现服务先从数据库获取PO对象,将PO转换为DO后,完成DO数据构建和初始化。 </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>主要是DO对象。DO是实体和值对象的数据和业务行为载体,承载着基础的核心业务逻辑,多个依赖紧密的DO对象构成聚合。领域层DO对象在持久化时需要转换为PO对象。 </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>主要对象有DO对象,但也可能会有DTO对象。应用层在进行不同聚合的领域服务编排时,一般建议采用聚合根ID的引用方式,应尽量避免不同聚合之间的DO对象直接引用,避免聚合之间产生依赖。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 在涉及跨微服务的应用服务调用时,在调用其他微服务的应用服务前,DO会被转换为DTO,完成跨微服务的DTO数据组装,因此会有DTO对象。在前端调用后端应用服务时,用户接口层先完成DTO到DO的转换,然后DO作为应用服务的参数,传导到领域层完成业务逻辑处理。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 用户接口层主要完成DO和DTO的互转,完成微服务与前端应用数据交互和转换。facade接口服务在完成后端应用服务封装后,会对多个DO对象进行组装,转换为DTO对象,向前端应用完成数据转换和传输。facade接口服务在接收到前端应用传入的DTO后,完成DTO向多个DO对象的转换,调用后端应用服务完成业务逻辑处理。前端应用主要是VO对象。展现层使用VO进行界面展示,通过用户接口层与应用层采用DTO对象进行数据交互。 </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);">五、领域分类</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">在研究和解决业务问题时,DDD 会按照一定的规则将业务领域进行细分,当领域细分到一定的程度后,DDD 会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。简言之,DDD 的领域就是这个边界内要解决的业务问题域。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">领域又可以分为多个子域,子域又包含核心域、通用域和支撑域。</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" src="/upload/a473e4481f36ae08689e6f20087ab99c.png" data-type="png" data-w="758" 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;"><strong>核心域</strong>:核心业务,决定产品和公司核心竞争力的子域。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;"><strong>通用域</strong>:同时被多个子域使用的通用功能子域。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;"><strong>支撑域</strong>:支持其他子域,非核心域和通用域。</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);">六、实现 DDD 流程</span></h2> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.7829581993569131" data-s="300,640" src="/upload/c35b49d4c39ab22083e9f554c826becd.png" data-type="png" data-w="1244" style=""><br></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);"> <strong>第一步</strong>:事件风暴,这里的风暴可以理解为头脑风暴,领域专家会和设计、开发人员一起建立领域模型。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <strong>第二步</strong>:对领域中涉及到的场景(用户故事)进行分析。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <strong>第三步</strong>:分析了场景之后,就要定义领域对象。设计实体、找出聚合根、设计值对象、设计领域事件、设计领域服务、设计仓储。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <strong>第四步</strong>:领域对象需要包含业务逻辑,所以会形成一个代码模型的映射。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <strong>第五步</strong>:根据代码模型进行代码落地。 </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);">六、限界上下文和通用语言</span></h2> <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: 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;">用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。</p> <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;">如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。</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);"> 物理边界:微服务之间的边界是物理边界。它强调微服务部署和运行的隔离,关注微服务的服务调用、容错和运行等。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 代码边界:不同层或者聚合之间代码目录的边界是代码边界。它强调的是代码之间的隔离,方便架构演进时代码的重组。 </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;">通用语言</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">DDD 分析和设计过程中的每一个环节都需要保证限界上下文内术语的统一,在代码模型设计的时侯就要建立领域对象和代码对象的一一映射,从而保证业务模型和代码模型的一致,实现业务语言与代码语言的统一。</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> <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: 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;">类包含了实体的属性和方法,通过这些方法实现实体自身的业务逻辑。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。字段的值可以变。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑。</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: 0em;margin-bottom: 0em;">有 ID 标识,通过 ID 判断相等性,ID 在聚合内唯一。依附于聚合根,生命周期由聚合根管理。实体一般会持久化,但是与数据库持久化对象不一定是一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">如下代码所示,Product 属于商品实体,有商品唯一 id。Location 属于值对象,后面会讲解值对象。</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("https://mmbiz.qpic.cn/mmbiz_svg/ibKHP1TZZeXLuuibODqFs5MuXSG47uzd6XYkImSicB8cJtF40cIgwPwTz8Qsia0KSWIMuEMgYnkOgFwzwmUewjQiapTWpjghYGoao/640?wx_fmt=svg") 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;">public</span> <span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span> <span style="color: #c18401;line-height: 26px;">Product</span> </span>{ <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 商品实体</span><br> <span style="color: #a626a4;line-height: 26px;">private</span> <span style="color: #a626a4;line-height: 26px;">long</span> id; <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 值对象,商品唯一 id</span><br> <span style="color: #a626a4;line-height: 26px;">private</span> String name; <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 单一属性值对象</span><br> <span style="color: #a626a4;line-height: 26px;">private</span> Location location; <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 属性值对象,被实体引用</span><br>}<br><br><span style="color: #a626a4;line-height: 26px;">public</span> <span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span> <span style="color: #c18401;line-height: 26px;">Location</span> </span>{ <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 值对象,无主键 id</span><br> <span style="color: #a626a4;line-height: 26px;">private</span> String country; <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 值对象</span><br> <span style="color: #a626a4;line-height: 26px;">private</span> String province; <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 值对象</span><br> <span style="color: #a626a4;line-height: 26px;">private</span> String city; <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 值对象</span><br> <span style="color: #a626a4;line-height: 26px;">private</span> String street; <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 值对象</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;">实体类通常采用充血模型。</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> <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);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;font-size: 15px;">贫血模型:数据和业务逻辑分开到不同的类中,比如 Model 类和 Service 类。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;font-size: 15px;">充血模型:数据和业务逻辑封装在同一个实体类中。</p> </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);">八、值对象</span></h2> <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: 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;">值对象是 DDD 领域模型中的一个基础对象,它跟实体一样都来源于事件风暴所构建的领域模型,都包含了若干个属性,它与实体一起构成聚合。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性集虽然在物理上独立出来了,但在逻辑上它仍然是实体属性的一部分,用于描述实体的特征。</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: 0em;margin-bottom: 0em;">无 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> <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: 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;">聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。</p> <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">聚合</code>属于 DDD <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">领域层</code>,领域层包含多个聚合,共同实现核心业务逻辑。</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: "Operator Mono", 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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">充血模型</code>实现个体业务能力,以及业务逻辑的高内聚。</p> <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;"><span style="font-weight: 700;color: rgb(248, 57, 41);">特点</span>:高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但是不建议对微服务过度拆分。一个聚合可以作为一个微服务,以满足版本的高频发布和极致的弹性伸缩能力。一个微服务也可以包含多个聚合,可以进行拆分和组合。</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: 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;">聚合可以比作组织,聚合根就是这个组织的负责人。</p> <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;"><span style="font-weight: 700;color: rgb(248, 57, 41);">特点</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: 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;">领域事件驱动设计可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,可以实现领域模型的解耦,维护领域模型的独立性和数据的一致性。微服务之间的数据不必要求强一致性,而是基于事件的最终一致性。</p> <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;"><strong>参考资料</strong>:</p> <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;">《领域驱动设计-软件核心复杂性应对之道》</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0em;margin-bottom: 0em;">https://time.geekbang.org/column/intro/100037301?tab=catalog</p> </section>
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;padding-right: 10px;padding-left: 10px;word-break: break-word;overflow-wrap: break-word;text-align: left;margin-top: -10px;line-height: 1.25;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangTC-Light, PingFangSC-light, PingFangTC-light;letter-spacing: 2px;background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%), linear-gradient(360deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%);background-size: 20px 20px;background-position: center center;"> <h3 data-tool="mdnice编辑器" style="color: black;font-size: 17px;font-weight: bold;text-align: center;margin-top: 20px;margin-bottom: 20px;"><span style="display: none;"></span><span style="border-bottom: 2px solid RGBA(79, 177, 249, .65);color: #2b2b2b;padding-bottom: 2px;"><span style="width: 30px;height: 30px;display: block;background-image: url("https://mmbiz.qpic.cn/sz_mmbiz_png/zTfAIs5rNXiajlShmOBdxgxXDUkof9TiaPTf73XrIrBQ95VNDQXwHEtibu48rQqkAO1Fu7dez7228P9jvicjnHcOWQ/640?wx_fmt=png");background-position: center center;background-size: 30px;margin: auto auto -8px;opacity: 1;background-repeat: no-repeat;"></span>持续坚持原创输出,点击蓝字关注我吧</span><span style="display: none;"></span></h3> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.4255555555555556" data-s="300,640" src="/upload/87687a9bf39146b409fe7624ee2c4319.jpg" data-type="jpeg" data-w="900" style=""></p> <p><span style="max-width: 100%;font-family: Optima-Regular, Optima, PingFangTC-Light, PingFangSC-light, PingFangTC-light;letter-spacing: 2px;text-align: left;color: rgb(136, 136, 136);font-size: 12px;word-spacing: 2px;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;">作者:小傅哥</span><br style="max-width: 100%;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangTC-Light, PingFangSC-light, PingFangTC-light;letter-spacing: 2px;text-align: left;white-space: normal;font-size: 14px;word-spacing: 2px;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-family: Optima-Regular, Optima, PingFangTC-Light, PingFangSC-light, PingFangTC-light;letter-spacing: 2px;text-align: left;color: rgb(136, 136, 136);font-size: 12px;word-spacing: 2px;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;">博客:https://bugstack.cn</span><span style="border-bottom: 2px solid RGBA(79, 177, 249, .65);color: #2b2b2b;padding-bottom: 2px;"></span><br></p> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;text-size-adjust: 100%;line-height: 1.55em;border-radius: 6px;color: rgb(89, 89, 89);box-sizing: inherit;border-width: 1px;border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-color: rgba(64, 184, 250, 0.4);background: rgba(64, 184, 250, 0.1);"> <span style="color: RGBA(64, 184, 250, .5);font-size: 34px;line-height: 1;font-weight: 700;">❝</span> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;word-spacing: 2px;line-height: 26px;">沉淀、分享、成长,让自己和他人都能有所收获!😜</p> <span style="float: right;color: RGBA(64, 184, 250, .5);">❞</span> </blockquote> </section> <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: PingFangSC-Light;"> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(14, 136, 235);font-size: 1.4em;text-align: center;"><span style="display: none;"></span><span style="font-size: 1.4em;display: inline-block;color: rgb(14, 136, 235);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">目录</span></h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;"><br></p> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li><p>一、前言</p></li> <li><p>二、延迟任务场景</p></li> <li><p>三、延迟任务设计</p></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;list-style-type: square;" class="list-paddingleft-2"> <li><p>1. 任务表方式</p></li> <li><p>2. 低延迟方式</p></li> </ul> <li><p>四、总结</p></li> <li><p>五、系列推荐</p></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;"><br></p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-family: STHeitiSC-Light;color: rgb(14, 136, 235);font-weight: bolder;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(14, 136, 235);">一、前言</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">不卷了,能用就行!</code></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;">哈哈哈,说好的不卷了,能凑活用就行了。但每次接到新需求时都手痒,想结合着上一次的架构设计和落地经验,在这一次需求上在迭代更新,或者找到完全颠覆之前的更优方案。<em style="color: rgb(14, 136, 235);letter-spacing: 0.3em;">卷完代码的那一刻总是神清气爽</em></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;">其实大部分喜欢写代码的一类纯粹码农,都是比较卷的,就比如一个需求在实现上是能用大概<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">是P5</code>、如果这个做出来的功能不只是能用还非常好用<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">是P6</code>、除了好用还凝练共性需求开发成通用的组件服务<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">是P7</code>。每一个成长过来的码农,都是在造轮子的路上一次次验证自己的想法和加以实践,绝对不是一篇篇的八股文就能累出来一个高级的技术大牛。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-family: STHeitiSC-Light;color: rgb(14, 136, 235);font-weight: bolder;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(14, 136, 235);">二、延迟任务场景</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">什么是延迟任务?</code></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;">当我们的实际业务需求场景中,有一些活动开始前的状态变更、订单结算后的T+1对账、贷款单息费的产生,都是需要使用到延迟任务来进行触达。实际的操作一般会有 Quartz、Schedule 来对你的库表数据进行定时扫描和处理,当条件满足后做数据状态的变更或者产生新的数据插入到表中。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;">这样一个简单的需求就是延迟任务最初需求,如果需求前期内容较少、使用方不多,可能在实际开发中就只是一个单台机器直接对着表一顿轮训就完事了。但随着业务需求的发展和功能的复杂度提升,往往反馈到研发设计和实现,就不那么简单了,比如:你需要保障尽可能低延迟完成较大规模的数据量扫描处理,否则就像贷款单息费的产生,已经到了第二天用户还没看到自己的息费信息或者是还款后的重新对账,可能就这个时候就要产生客诉了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;">那么,类似这样的场景该如何设计呢?</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-family: STHeitiSC-Light;color: rgb(14, 136, 235);font-weight: bolder;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(14, 136, 235);">三、延迟任务设计</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;">通常的任务中心处理流程主要,主要是由定时任务扫描任务库表,把即将达到超时时间的任务信息扫描到处理队列(<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">内存/MQ消息</code>),再由业务系统进行处理任务,处理完成后更新库表中的任务状态。</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.503793626707132" data-s="300,640" src="/upload/3a585d42cead85db28362f22d3ed767b.png" data-type="png" data-w="659" style=""></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> 高延时任务调度 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;"><strong style="font-weight: border;color: #0e88eb;">问题</strong>:</p> <ol 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);font-size: 15px;"> 海量数据规模较大的任务列表数据,在分库分表下该需要快速扫描。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> 任务扫描服务与业务逻辑处理,耦合在一起,不具有通用性和复用性。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> 细分任务体系有些是需要低延迟处理的,不能等待过长时间。 </section></li> </ol> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 18px;color: rgb(14, 136, 235);"><span style="display: none;"></span>1. 任务表方式<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;">除了一些较小的状态变更场景,例如在各自业务的库表中,就包含了一个状态字段,这个字段一方面有程序逻辑处理变更的状态,也有到达<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">指定到期</code>时间后由任务服务自动变更处理的操作,一般这类功能,直接设计到自己的库表中即可。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;">那么还有一些较大也较为频繁使用的场景,如果都是在每个系统的各自所需的N多个表中,都添加这样的字段进行维护,就显得非常冗余了,也不那么易于维护。所以针对这样的场景就很适合做一个通用的任务延时系统,各业务系统把需要被延时执行的动作提交到延时系统中,再有延时系统在指定时间下进行回调,回调的动作可以是接口或者MQ消息进行触达。例如可以设计这样一个任务调度表:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.531442663378545" data-s="300,640" src="/upload/9d8505b7d1c0ab4311ebac3015e05cd7.png" data-type="png" data-w="811" style=""></p> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> 任务调度库表设计 <br> </figcaption> </figure> <ol 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);font-size: 15px;"> 抽取的任务调度表,主要是拿到什么任务,在什么时间发起动作,具体的动作处理仍交给业务工程处理。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> 大批量的各自业务的任务进行集中处理,则需要设计一个分库分表,满足于后续业务体量的增长。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> 门牌号设计,针对一张表的扫描,如果数据量较大,又不希望只是一个任务扫描一个表,可以多个任务扫描一个表,加到扫描的体量。这个时候就需要一个门牌号来隔离不同任务扫描的范围,避免扫描出重复的任务数据。 </section></li> </ol> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 18px;color: rgb(14, 136, 235);"><span style="display: none;"></span>2. 低延迟方式<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;">低延迟处理方案,是在任务表方式的基础上,新增加的时间把控处理。它可以把即将到期的前一段时间的任务,放置到 Redis 集群队里中,在消费的时候再从队列中 pop 出来,这样可以更快的接近任务的处理时效,避免因为扫库间隔较大延迟任务执行。</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.3421828908554572" data-s="300,640" src="/upload/3bb2e7622713d9495aeacfef5f27b241.png" data-type="png" data-w="678" style=""></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> 任务处理流程 </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);font-size: 15px;"> 在接收业务系统提交进来的延迟任务时,按照执行时间的长短放置到任务库或者也同步到 Redis 集群中,一些执行时间较晚的任务则可以先放到任务库,再通过扫描的方式添加到超时任务执行队列中。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> 那么关于这块的设计核心在于 Redis 队列的使用,以及为了保证消费的可靠性需要引入二阶段消费、注册 ZK 注册中心至少保证一次消费的处理。 <em style="color: rgb(14, 136, 235);letter-spacing: 0.3em;">本文重点主要放在 Redis 队列的设计,其他更多的逻辑处理,可以按照业务需求进行扩展和完善</em> </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;"><strong style="font-weight: border;color: #0e88eb;">Redis 消费队列</strong></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.7349643221202854" data-s="300,640" src="/upload/4a0f59b94da0fb9164dfb11f2812c2a2.png" data-type="png" data-w="981" style=""></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> Redis 消费队列 </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);font-size: 15px;"> 按照消息体计算对应数据所属的槽位 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">index = CRC32 & 7</code> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> StoreQueue 采用 Slot 按照 SlotKey = <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">#{topic}_#{index}</code> 和 Sorted Set 的数据结构按执行任务分数排序,存放任务执行信息。 <em style="color: rgb(14, 136, 235);letter-spacing: 0.3em;">定时消息将时间戳作为分数,消费时每次弹出分数小于当前时间戳的一个消息</em> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> 为了保障每条消息至少可消费一次,消费者不是直接 pop 有序集合中的元素,而是将元素从 StoreQueue 移动到 PrepareQueue 并返回消息给消费者。消费成功后再从 PrepareQueue 从删除,如果消费失败则从PreapreQueue 重新移动到 StoreQueue,这样二阶段消费的方式进行处理。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> 参考文档:2021 阿里技术人的百宝黑皮书PDF文, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">低延迟的超时中心实现方式</code> </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;"><strong style="font-weight: border;color: #0e88eb;">简单案例</strong></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #333;background: #f8f8f8;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #999;font-weight: bold;line-height: 26px;">@Test</span><br><span style="line-height: 26px;"><span style="font-weight: bold;line-height: 26px;">public</span> <span style="font-weight: bold;line-height: 26px;">void</span> <span style="color: #900;font-weight: bold;line-height: 26px;">test_delay_queue</span><span style="line-height: 26px;">()</span> <span style="font-weight: bold;line-height: 26px;">throws</span> InterruptedException </span>{<br> RBlockingQueue<Object> blockingQueue = redissonClient.getBlockingQueue(<span style="color: #d14;line-height: 26px;">"TASK"</span>);<br> RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);<br> <span style="font-weight: bold;line-height: 26px;">new</span> Thread(() -> {<br> <span style="font-weight: bold;line-height: 26px;">try</span> {<br> <span style="font-weight: bold;line-height: 26px;">while</span> (<span style="font-weight: bold;line-height: 26px;">true</span>){<br> Object take = blockingQueue.take();<br> System.out.println(take);<br> Thread.sleep(<span style="color: #008080;line-height: 26px;">10</span>);<br> }<br> } <span style="font-weight: bold;line-height: 26px;">catch</span> (InterruptedException e) {<br> e.printStackTrace();<br> }<br> }).start();<br> <span style="font-weight: bold;line-height: 26px;">int</span> i = <span style="color: #008080;line-height: 26px;">0</span>;<br> <span style="font-weight: bold;line-height: 26px;">while</span> (<span style="font-weight: bold;line-height: 26px;">true</span>){<br> delayedQueue.offerAsync(<span style="color: #d14;line-height: 26px;">"测试"</span> + ++i, <span style="color: #008080;line-height: 26px;">100L</span>, TimeUnit.MILLISECONDS);<br> Thread.sleep(<span style="color: #008080;line-height: 26px;">1000L</span>);<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 10px;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;"><strong style="font-weight: border;color: #0e88eb;">测试数据</strong></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #333;background: #f8f8f8;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #008080;line-height: 26px;">2022</span>-<span style="color: #008080;line-height: 26px;">02</span>-<span style="color: #008080;line-height: 26px;">13</span> WARN <span style="color: #008080;line-height: 26px;">204760</span> --- [ Finalizer] i.l.c.resource.DefaultClientResources : io.lettuce.core.resource.DefaultClientResources was not shut down properly, shutdown() was not called before it<span style="color: #d14;line-height: 26px;">'s garbage-collected. Call shutdown() or shutdown(long,long,TimeUnit) <br>测试1<br>测试2<br>测试3<br>测试4<br>测试5<br><br>Process finished with exit code -1<br></span></code></pre> <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);font-size: 15px;"> 源码:https://github.com/fuzhengwei/TimeOutCenter </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> 描述:使用 redisson 中的 DelayedQueue 作为消息队列,写入后等待消费时间进行 POP 消费。 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-family: STHeitiSC-Light;color: rgb(14, 136, 235);font-weight: bolder;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(14, 136, 235);">四、总结</span></h2> <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);font-size: 15px;"> 调度任务的使用在实际的场景中非常频繁,例如我们经常使用 xxl-job,也有一些大厂自研的分布式任务调度组件,这些可能原本都是很小很简单的功能,但经过抽象、整合、提炼,变成了一个个核心通用的中间件服务。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> 当我们在考虑使用任务调度的时候,无论哪种方式的设计和实现,都需要考虑这个功能使用时候的以为迭代和维护性,如果仅仅是一个非常小的场景,又没多少人使用的话,那么在自己机器上折腾就可以。 <em style="color: rgb(14, 136, 235);letter-spacing: 0.3em;">过渡的设计和使用有时候也会把研发资源代入泥潭</em> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> 其实各项技术的知识点,都像是一个个工具,刀枪棍棒斧钺钩,那能怎么结合各自的特点,把这些兵器用起来,才是一个程序员不断成长的过程。如果你希望了解更多此类有深度的技术内容,可以加入 <a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzIxMDAwMDAxMw==&mid=2650731250&idx=1&sn=d3f650b05096be5f058126f11272e89c&chksm=8f611310b8169a069b62fa7edee8fe01ba5508c6423e6d278a9619addf57c9943ddbe70a501b&scene=21#wechat_redirect" textvalue="Lottery 分布式抽奖秒杀系统" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">Lottery 分布式抽奖秒杀系统</a> 学习更有价值的更抗用的实战手段。 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-family: STHeitiSC-Light;color: rgb(14, 136, 235);font-weight: bolder;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(14, 136, 235);">五、系列推荐</span></h2> <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);font-size: 15px;"> <a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzIxMDAwMDAxMw==&mid=2650731365&idx=1&sn=ad9beeb9f0ac06366fea29203cef6d14&chksm=8f611287b8169b914b44704d41be80f936927f6d9865a0cc4387097dab8d5ac37e55ac2a7547&scene=21#wechat_redirect" textvalue="金三银四面试前,把自己弄成卷王!" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">金三银四面试前,把自己弄成卷王!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> <a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzIxMDAwMDAxMw==&mid=2650730198&idx=1&sn=ee7a1dfe52e464a50af24ad35ac5ff6c&chksm=8f610f34b816862258b07068bd792a88c922656137310e8a7cf6a6ebe50e22b386ccbe5d834a&scene=21#wechat_redirect" textvalue="方案设计:基于IDEA插件开发和字节码插桩技术,实现研发交付质量自动分析" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">方案设计:基于IDEA插件开发和字节码插桩技术,实现研发交付质量自动分析</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> <a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzIxMDAwMDAxMw==&mid=2650730238&idx=1&sn=be97da23ef05e7533eeb0ae5e7ec89a0&chksm=8f610f1cb816860aed9f296f2c3969d285f34b6f938f458e005290844fdcab73394be82428c1&scene=21#wechat_redirect" textvalue="工作两三年了,整不明白架构图都画啥?" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">工作两三年了,整不明白架构图都画啥?</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> <a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzIxMDAwMDAxMw==&mid=2650725281&idx=1&sn=1b468438ad8361b317e6db72b5bf877f&chksm=8f613a43b816b355dfe17ff8dba3d3d1dd765e81aa0844374c37945e959ee90eb2c481456438&scene=21#wechat_redirect" textvalue="工作两年简历写成这样,谁要你呀!" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">工作两年简历写成这样,谁要你呀!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;"> <a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzIxMDAwMDAxMw==&mid=2650729359&idx=1&sn=7a1c0b7d4ebde4ba04b458cb9640148e&chksm=8f610a6db816837b61b38de9b5b867815a0df2f4d5fd24eb54607ee32e9e441102c4d2195e7c&scene=21#wechat_redirect" textvalue="BATJTMD,大厂招聘,都招什么样Java程序员?" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">BATJTMD,大厂招聘,都招什么样Java程序员?</a> </section></li> </ul> </section> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;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: -apple-system, system-ui, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;text-align: center;"><span style="caret-color: rgb(153, 153, 153);color: rgb(153, 153, 153);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;text-align: center;text-size-adjust: auto;background-color: rgb(255, 255, 255);">- END -<br></span></p> <hr style="border-style: solid;border-width: 1px 0 0;border-color: rgba(0,0,0,0.1);-webkit-transform-origin: 0 0;-webkit-transform: scale(1, 0.5);transform-origin: 0 0;transform: scale(1, 0.5);"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;"><span style="font-family: -apple-system, system-ui, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0px;">下方扫码关注 </span><span style="font-family: -apple-system, system-ui, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;background-image: linear-gradient(to right, rgb(50, 153, 210), rgb(239, 189, 181));background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(255, 255, 255);padding-right: 4px;padding-left: 4px;display: inline-block;border-radius: 4px;margin-right: 2px;margin-left: 2px;letter-spacing: 1px;">bugstack虫洞栈</span><span style="font-family: -apple-system, system-ui, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0px;">,与小傅哥一起学习成长、共同进步,做一个码场最贵Coder!</span></p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-2"> <li style="color: rgb(91, 91, 91);font-size: 14px;"> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 2;"> <span style="color: rgb(91, 91, 91);font-size: 14px;">回复【设计模式】,获取《重学Java设计模式》,这是一本互联网真实案例的实践书籍,从实际业务中抽离出,交易、营销、秒杀、中间件、源码等众多场景进行学习代码设计。</span> </section></li> <li style="color: rgb(91, 91, 91);font-size: 14px;"> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 2;"> <span style="color: rgb(91, 91, 91);">回复【Spring专栏】,</span> <span style="color: rgb(91, 91, 91);font-size: 14px;"><span style="color: rgb(91, 91, 91);font-family: -apple-system, system-ui, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;text-align: left;">获取《手撸Spring》,这是一本</span><span style="color: rgb(91, 91, 91);font-family: -apple-system, system-ui, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;text-align: left;">通过带着读者</span><span style="color: rgb(91, 91, 91);font-family: -apple-system, system-ui, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;text-align: left;">手写简化版 Spring 框架,了解 Spring IOC、AOP、循环依赖等</span><span style="color: rgb(91, 91, 91);font-family: -apple-system, system-ui, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;text-align: left;">核心原理和</span><span style="color: rgb(91, 91, 91);font-family: -apple-system, system-ui, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;text-align: left;">设计</span><span style="color: rgb(91, 91, 91);font-family: -apple-system, system-ui, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;text-align: left;">实现</span><span style="color: rgb(91, 91, 91);font-family: -apple-system, system-ui, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;text-align: left;">的技术</span><span style="color: rgb(91, 91, 91);font-family: -apple-system, system-ui, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;text-align: left;">资料。</span></span> </section></li> <li style="color: rgb(91, 91, 91);font-size: 14px;"> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 2;"> <span style="color: rgb(91, 91, 91);font-size: 14px;">回复【面经手册】,获取<span style="color: rgb(91, 91, 91);font-family: -apple-system, system-ui, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;text-align: left;">《面经手册 • 拿大厂Offer》</span>,这是一本有深度的Java核心内容,从数据结构、算法、并发编程以及JVM系8不断深入讲解,让懂了就是真的懂。</span> </section></li> </ul> <p style="text-align: center;"><img class="rich_pages js_insertlocalimg wxw-img" data-ratio="1" data-s="300,640" src="/upload/430388728ca76ed1351b393b6a1b9605.png" data-type="png" data-w="400" style="width: 322px;height: 322px;"></p> <section style="padding-top: 8px;padding-bottom: 8px;color: black;line-height: 1.5em;"> <span style="font-size: 12px;"><span style="color: rgb(91, 91, 91);">你好,我是</span><span style="background-image: linear-gradient(to right, rgb(50, 153, 210), rgb(239, 189, 181));background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(255, 255, 255);padding-right: 4px;padding-left: 4px;display: inline-block;border-radius: 4px;margin-right: 2px;margin-left: 2px;letter-spacing: 1px;">小傅哥</span><span style="color: rgb(91, 91, 91);">。一线互联网</span></span> <code style="font-size: 14px;overflow-wrap: break-word;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;"><span style="font-size: 12px;">java</span></code> <span style="font-size: 12px;color: rgb(91, 91, 91);">工程师、架构师,开发过交易&营销、写过运营&活动、设计过中间件也倒腾过中继器、IO板卡。不只是写Java语言,也搞过C#、PHP,是一个技术活跃的折腾者。</span> <span style="color: rgb(91, 91, 91);font-family: -apple-system, system-ui, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;font-size: 12px;letter-spacing: 0px;"></span> </section> <section style="padding-top: 8px;padding-bottom: 8px;color: black;line-height: 1.5em;"> <span style="color: rgb(91, 91, 91);font-family: -apple-system, system-ui, "system-ui", "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;font-size: 12px;text-align: left;">2022年在知识星球【码农会锁】开发完成基于 DDD 四层架构设计的,<a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzIxMDAwMDAxMw==&mid=2650730856&idx=1&sn=9293cc0afaaf2d27b81816914c9f1eba&chksm=8f61108ab816999c8bb6b433907e4ee9b514eff750d506f02b25fac4359cb7cd6f8bf5f95dd4&scene=21#wechat_redirect" textvalue="《分布式实战项目抽奖系统》" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">《分布式实战项目抽奖系统》</a>。此项目以互联网开发常用技术为主,包括:SpringBoot、Mybatis、Dubbo、MQ、Redis、分库分表、ELK、Docker等,以及大量的真实场景案例和对应的设计模式实战,解决每一个细节问题,非常适合学习实践。</span> </section> </section>
作者:微信小助手
<p data-mpa-powered-by="yiban.io"><span style="font-size: 15px;letter-spacing: 1.5px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;">大家好,我是DD。我</span><span style="font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 1.5px;text-align: left;">一直强调</span><span style="font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 1.5px;text-align: left;">基础很</span><span style="font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 1.5px;text-align: left;">重要!而现在我们通常直接学Spring Boot和Spring Cloud之后,有不少小伙伴对于Spring AOP的了解就不那么充分了!所以,今天转了一篇不错的博文,来给大家巩固下Spring AOP的知识。</span></p> <p><br></p> <p><span style="font-size: 15px;letter-spacing: 1.5px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;">Spring 一开始最强大的就是 IOC / AOP 两大核心功能,我们今天一起来学习一下 Spring AOP 常见注解和执行顺序。</span></p> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="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, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;"> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;font-weight: bold;font-size: 22px;border-bottom: 2px solid rgb(89, 89, 89);margin-bottom: 30px;color: rgb(89, 89, 89);"><span style="display: none;"></span><span style="font-size: 19px;display: inline-block;border-bottom: 2px solid rgb(89,89,89);">Spring Aop 的常用注解</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">首先我们一起来回顾一下 Spring Aop 中常用的几个注解:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;font-size: 15px;line-height: 30px;letter-spacing: 1.5px;color: rgb(51, 51, 51);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 30px;"> @Before 前置通知:目标方法之前执行 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 30px;"> @After 后置通知:目标方法之后执行(始终执行) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 30px;"> @AfterReturning 返回之后通知:执行方法结束之前执行(异常不执行) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 30px;"> @AfterThrowing 异常通知:出现异常后执行 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 30px;"> @Around 环绕通知:环绕目标方法执行 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;font-weight: bold;font-size: 22px;border-bottom: 2px solid rgb(89, 89, 89);margin-bottom: 30px;color: rgb(89, 89, 89);"><span style="display: none;"></span><span style="font-size: 19px;display: inline-block;border-bottom: 2px solid rgb(89,89,89);">常见问题</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">1、你肯定知道 Spring , 那说说 Aop 的去全部通知顺序, Spring Boot 或者 Spring Boot 2 对 aop 的执行顺序影响?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">2、说说你在 AOP 中遇到的那些坑?</p> <blockquote data-tool="mdnice编辑器" style="overflow: auto;background: rgba(0, 0, 0, 0.05);margin-bottom: 20px;margin-top: 20px;padding-top: 10px;padding-right: 10px;padding-bottom: 10px;line-height: 1.8;border-width: initial;border-style: none;border-color: initial;color: rgb(51, 51, 51);"> <p style="padding-top: 8px;padding-bottom: 8px;letter-spacing: 1.5px;line-height: 26px;display: inline;">如果您正在学习Spring Boot,那么推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/</p> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;font-weight: bold;font-size: 22px;border-bottom: 2px solid rgb(89, 89, 89);margin-bottom: 30px;color: rgb(89, 89, 89);"><span style="display: none;"></span><span style="font-size: 19px;display: inline-block;border-bottom: 2px solid rgb(89,89,89);">示例代码</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">下面我们先快速构建一个 spring aop 的 demo 程序来一起讨论 spring aop 中的一些细节。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;color: rgb(89, 89, 89);"><span style="display: none;"></span>配置文件<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">为了方便我直接使用 spring-boot 进行快速的项目搭建,大家可以使用 idea 的spring-boot 项目快速创建功能,或者去 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(71, 193, 168);">start.spring.io</code> 上面去快速创建spring-boot 应用。(因为本人经常手动去网上贴一些依赖导致,依赖冲突服务启动失败等一些问题)。</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("https://mmbiz.qpic.cn/mmbiz_svg/icFTnRoibgibpicVqKDsKh7eaqAZIldIJKNUGdaF1SrazFicVz1VibMiaxPtmojj3icukkJ6dcnibpvMKj27pib3xsLM6PIeO94aLcgia7h/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">plugins {<br> id <span style="color: #98c379;line-height: 26px;">'org.springframework.boot'</span> version <span style="color: #98c379;line-height: 26px;">'2.6.3'</span><br> id <span style="color: #98c379;line-height: 26px;">'io.spring.dependency-management'</span> version <span style="color: #98c379;line-height: 26px;">'1.0.11.RELEASE'</span><br> id <span style="color: #98c379;line-height: 26px;">'java'</span><br>}<br><br>group <span style="color: #98c379;line-height: 26px;">'io.zhengsh'</span><br>version <span style="color: #98c379;line-height: 26px;">'1.0-SNAPSHOT'</span><br><br>repositories {<br> mavenCentral()<br> maven { url <span style="color: #98c379;line-height: 26px;">'https://repo.spring.io/milestone'</span> }<br> maven { url <span style="color: #98c379;line-height: 26px;">'https://repo.spring.io/snapshot'</span> }<br>}<br><br>dependencies {<br> <span style="color: #5c6370;font-style: italic;line-height: 26px;"># 其实这里也可以不增加 web 配置,为了试验简单,大家请忽略 </span><br> implementation <span style="color: #98c379;line-height: 26px;">'org.springframework.boot:spring-boot-starter-web'</span><br> implementation <span style="color: #98c379;line-height: 26px;">'org.springframework.boot:spring-boot-starter-actuator'</span><br> implementation <span style="color: #98c379;line-height: 26px;">'org.springframework.boot:spring-boot-starter-aop'</span><br> <br> testImplementation <span style="color: #98c379;line-height: 26px;">'org.springframework.boot:spring-boot-starter-test'</span><br>}<br><br>tasks.named(<span style="color: #98c379;line-height: 26px;">'test'</span>) {<br> useJUnitPlatform()<br>}<br>复制代码<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;color: rgb(89, 89, 89);"><span style="display: none;"></span>接口类<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">首先我们需要定义一个接口。我们这里可以再来回顾一下 JDK 的默认代理实现的选择:</p> <blockquote data-tool="mdnice编辑器" style="overflow: auto;background: rgba(0, 0, 0, 0.05);margin-bottom: 20px;margin-top: 20px;padding-top: 10px;padding-right: 10px;padding-bottom: 10px;line-height: 1.8;border-width: initial;border-style: none;border-color: initial;color: rgb(51, 51, 51);"> <p style="padding-top: 8px;padding-bottom: 8px;letter-spacing: 1.5px;line-height: 26px;display: inline;">如果目标对象实现了接口,则默认采用JDK动态代理 如果目标对象没有实现接口,则采用进行动态代理 如果目标对象实现了接口,且强制Cglib,则使用cglib代理</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">这块的逻辑在 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(71, 193, 168);">DefaultAopProxyFactory</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("https://mmbiz.qpic.cn/mmbiz_svg/icFTnRoibgibpicVqKDsKh7eaqAZIldIJKNUGdaF1SrazFicVz1VibMiaxPtmojj3icukkJ6dcnibpvMKj27pib3xsLM6PIeO94aLcgia7h/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">public interface CalcService {<br><br> public int div(int x, int y);<br>}<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;color: rgb(89, 89, 89);"><span style="display: none;"></span> <mpcpc js_editor_cpcad="" class="js_cpc_area cpc_iframe" src="/cgi-bin/readtemplate?t=tmpl/cpc_tmpl#1644981147574" data-category_id_list="1|16|17|2|21|24|28|29|31|35|36|37|39|41|42|43|46|47|48|5|50|51|55|56|57|58|59|6|60|61|62|63|64|65|66|7|8" data-id="1644981147574"></mpcpc></h3> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;color: rgb(89, 89, 89);">实现类<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;"><a href="https://mp.weixin.qq.com/s?__biz=Mzg2MDYzODI5Nw==&mid=2247501614&idx=3&sn=2428a9c8ea52e93759aa5c195a815db0&scene=21#wechat_redirect" style="font-weight: bold;color: rgb(71, 193, 168);border-bottom: 1px solid rgb(71, 193, 168);" data-linktype="2">这里我们就简单一点做一个除法操作,可以模拟正常也可以很容易的模拟错误。最近整理了一份简历资料,里面收录了几位大佬的简历模板,打算跳槽的小伙伴可以参考下,点击免费领取!</a></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("https://mmbiz.qpic.cn/mmbiz_svg/icFTnRoibgibpicVqKDsKh7eaqAZIldIJKNUGdaF1SrazFicVz1VibMiaxPtmojj3icukkJ6dcnibpvMKj27pib3xsLM6PIeO94aLcgia7h/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Service<br>public class CalcServiceImpl implements CalcService {<br><br> @Override<br> public int div(int x, int y) {<br> int result = x / y;<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"====> CalcServiceImpl 被调用了,我们的计算结果是:"</span> + result);<br> <span style="color: #e6c07b;line-height: 26px;">return</span> result;<br> }<br>}<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;color: rgb(89, 89, 89);"><span style="display: none;"></span>aop 拦截器<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">申明一个拦截器我们要为当前对象增加 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(71, 193, 168);">@Aspect</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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(71, 193, 168);">@Component</code> ,笔者之前也是才踩过这样的坑,只加了一个。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">其实这块我刚开始也不是很理解,但是我看了 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(71, 193, 168);">Aspect</code>注解的定义我就清楚了<img class="rich_pages wxw-img" data-ratio="0.6071428571428571" src="/upload/a40c717f8206a1d818763ce0070fa049.jpg" data-type="jpeg" data-w="476" style="display: block;margin-right: auto;margin-left: auto;box-shadow: rgb(210, 210, 210) 3px 3px 10px;"></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">这里面根本就没有 Bean 的定义。所以我们还是乖乖的加上两个注解。还有就是如果当测试的时候需要开启Aop 的支持为配置类上增加 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(71, 193, 168);">@EnableAspectJAutoProxy</code> 注解。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">其实 Aop 使用就三个步骤:1、定义 Aspect 定义切面 2、定义 Pointcut 就是定义我们切入点 3、定义具体的通知,比如: @After, @Before 等。</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("https://mmbiz.qpic.cn/mmbiz_svg/icFTnRoibgibpicVqKDsKh7eaqAZIldIJKNUGdaF1SrazFicVz1VibMiaxPtmojj3icukkJ6dcnibpvMKj27pib3xsLM6PIeO94aLcgia7h/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #61aeee;line-height: 26px;">@Aspect</span><br><span style="color: #61aeee;line-height: 26px;">@Component</span><br><span style="color: #c678dd;line-height: 26px;">public</span> <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">class</span> <span style="color: #e6c07b;line-height: 26px;">MyAspect</span> </span>{<br><br> <span style="color: #61aeee;line-height: 26px;">@Pointcut</span>(<span style="color: #98c379;line-height: 26px;">"execution(* io.zhengsh.spring.service.impl..*.*(..))"</span>)<br> <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span> <span style="color: #c678dd;line-height: 26px;">void</span> <span style="color: #61aeee;line-height: 26px;">divPointCut</span><span style="line-height: 26px;">()</span> </span>{<br><br> }<br><br> <span style="color: #61aeee;line-height: 26px;">@Before</span>(<span style="color: #98c379;line-height: 26px;">"divPointCut()"</span>)<br> <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span> <span style="color: #c678dd;line-height: 26px;">void</span> <span style="color: #61aeee;line-height: 26px;">beforeNotify</span><span style="line-height: 26px;">()</span> </span>{<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"----===>> @Before 我是前置通知"</span>);<br> }<br><br> <span style="color: #61aeee;line-height: 26px;">@After</span>(<span style="color: #98c379;line-height: 26px;">"divPointCut"</span>)<br> <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span> <span style="color: #c678dd;line-height: 26px;">void</span> <span style="color: #61aeee;line-height: 26px;">afterNotify</span><span style="line-height: 26px;">()</span> </span>{<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"----===>> @After 我是后置通知"</span>);<br> }<br><br> <span style="color: #61aeee;line-height: 26px;">@AfterReturning</span>(<span style="color: #98c379;line-height: 26px;">"divPointCut"</span>)<br> <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span> <span style="color: #c678dd;line-height: 26px;">void</span> <span style="color: #61aeee;line-height: 26px;">afterReturningNotify</span><span style="line-height: 26px;">()</span> </span>{<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"----===>> @AfterReturning 我是前置通知"</span>);<br> }<br><br> <span style="color: #61aeee;line-height: 26px;">@AfterThrowing</span>(<span style="color: #98c379;line-height: 26px;">"divPointCut"</span>)<br> <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span> <span style="color: #c678dd;line-height: 26px;">void</span> <span style="color: #61aeee;line-height: 26px;">afterThrowingNotify</span><span style="line-height: 26px;">()</span> </span>{<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"----===>> @AfterThrowing 我是异常通知"</span>);<br> }<br><br> <span style="color: #61aeee;line-height: 26px;">@Around</span>(<span style="color: #98c379;line-height: 26px;">"divPointCut"</span>)<br> <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span> Object <span style="color: #61aeee;line-height: 26px;">around</span><span style="line-height: 26px;">(ProceedingJoinPoint proceedingJoinPoint)</span> <span style="color: #c678dd;line-height: 26px;">throws</span> Throwable </span>{<br> Object retVal;<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"----===>> @Around 环绕通知之前 AAA"</span>);<br> retVal = proceedingJoinPoint.proceed();<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"----===>> @Around 环绕通知之后 BBB"</span>);<br> <span style="color: #c678dd;line-height: 26px;">return</span> retVal;<br> }<br>}<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;color: rgb(89, 89, 89);"><span style="display: none;"></span>测试类<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">其实我这个测试类,虽然用了 @Test 注解,但是我这个类更加像一个 main 方法把:如下所示:</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;font-weight: bold;font-size: 22px;border-bottom: 2px solid rgb(89, 89, 89);margin-bottom: 30px;color: rgb(89, 89, 89);"><span style="display: none;"></span><span style="font-size: 19px;display: inline-block;border-bottom: 2px solid rgb(89,89,89);">执行结论</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">结果记录:spring 4.x, spring-boot 1.5.9</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;"><strong style="color: rgb(71, 193, 168);">无法现在依赖,所以无法试验</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">我直接说一下结论:Spring 4 中环绕通知是在最里面执行的</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;"><strong style="color: rgb(71, 193, 168);">结果记录:spring 版本5.3.15 springboot 版本2.6.3</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;"><img class="rich_pages wxw-img" data-ratio="0.34394904458598724" src="/upload/76cc0b004747b5cf5470645e50cdadad.jpg" data-type="jpeg" data-w="471" style="display: block;margin-right: auto;margin-left: auto;box-shadow: rgb(210, 210, 210) 3px 3px 10px;">image.png</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;color: rgb(89, 89, 89);"><span style="display: none;"></span>多切面的情况<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">多个切面的情况下,可以通过@Order指定先后顺序,数字越小,优先级越高。如下图所示:<img class="rich_pages wxw-img" data-ratio="0.4609375" src="/upload/843c7ff569caad6c4c8d16ae1f29aba7.jpg" data-type="jpeg" data-w="640" style="display: block;margin-right: auto;margin-left: auto;box-shadow: rgb(210, 210, 210) 3px 3px 10px;"></p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;color: rgb(89, 89, 89);"><span style="display: none;"></span>代理失效场景<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;font-size: 15px;color: rgb(51, 51, 51);margin-top: 8px;margin-bottom: 8px;line-height: 35px;letter-spacing: 1.5px;">下面一种场景会导致 aop 代理失效,因为我们在执行 a 方法的时候其实本质是执行 AServer#a 的方法拦截器(MethodInterceptor)链, 当我们在 a 方法内直接执行b(), 其实本质就相当于 this.b() , 这个时候由执行 a方法是调用到 a 的原始对象相当于是 this 调用,那么会导致 b() 方法的代理失效。<strong style="color: rgb(71, 193, 168);">这个问题也是我们开发者在开发过程中最常遇到的一个问题。</strong></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("https://mmbiz.qpic.cn/mmbiz_svg/icFTnRoibgibpicVqKDsKh7eaqAZIldIJKNUGdaF1SrazFicVz1VibMiaxPtmojj3icukkJ6dcnibpvMKj27pib3xsLM6PIeO94aLcgia7h/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Service<br>public class AService {<br> <br> public void <span style="line-height: 26px;"><span style="color: #61aeee;line-height: 26px;">a</span></span>() {<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"...... a"</span>);<br> b();<br> }<br> <br> public void <span style="line-height: 26px;"><span style="color: #61aeee;line-height: 26px;">b</span></span>() {<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"...... b"</span>);<br> }<br><br>}<br></code></pre> <p style="margin-top: 5px;margin-bottom: 5px;white-space: normal;text-align: left;font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);line-height: normal;"><br></p> <section style="margin-top: 5px;margin-bottom: 15px;white-space: normal;text-align: left;font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);line-height: normal;"> <span style="color: rgb(136, 136, 136);font-family: Optima-Regular, PingFangTC-light;font-size: 12px;letter-spacing: 1px;">本文来源:https://juejin.cn/post/7062506923194581029</span> </section> </section>
作者:微信小助手
<section style="margin: 5px 8px 15px;line-height: 2em;" data-mpa-powered-by="yiban.io"> <strong><span style="font-size: 14px;">1. 定义配置文件信息</span></strong> </section> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">有时候我们为了统一管理会把一些变量放到 yml 配置文件中</span><br></p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 5px 8px;line-height: 2em;"><span style="font-size: 14px;"><strong>例如</strong></span></p> <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.3208791208791209" src="/upload/916444ed75cf82b90781e19a55f8dbe.png" data-type="png" data-w="910" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <section style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px 5px;line-height: 2em;"> <span style="font-size: 14px;">用 @ConfigurationProperties 代替 @Value</span> </section> <p style="padding-top: 8px;padding-bottom: 8px;margin: 5px 8px;line-height: 2em;"><strong style="font-size: 14px;">使用方法</strong></p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 5px 8px 15px;line-height: 2em;"><span style="font-size: 14px;">定义对应字段的实体</span></p> <section data-mpa-preserve-tpl-color="t" data-mpa-template="t" mpa-preserve="t" mpa-from-tpl="t"> <pre style="background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"><p style="border-radius: 4px;font-size: 0.85em;margin-right: 0.15em;margin-bottom: 15px;margin-left: 0.15em;background: rgb(248, 248, 248);padding: 5.20625px;overflow-x: auto;white-space: nowrap;"><span style="font-size: 13px;"><span style="color: rgb(0, 128, 128);background: rgba(0, 0, 0, 0);width: 36px;text-decoration-style: solid;text-decoration-color: rgb(0, 128, 128);">@Data</span><br mpa-from-tpl="t"><span style="color: rgb(153, 153, 136);background: rgba(0, 0, 0, 0);width: 69px;text-decoration-style: solid;text-decoration-color: rgb(153, 153, 136);font-style: italic;">// 指定前缀</span><br mpa-from-tpl="t"><span style="color: rgb(0, 128, 128);background: rgba(0, 0, 0, 0);width: 173px;text-decoration-style: solid;text-decoration-color: rgb(0, 128, 128);">@ConfigurationProperties</span>(prefix = <span style="color: rgb(221, 17, 68);background: rgba(0, 0, 0, 0);width: 80px;text-decoration-style: solid;text-decoration-color: rgb(221, 17, 68);">"developer"</span>)<br mpa-from-tpl="t"><span style="color: rgb(0, 128, 128);background: rgba(0, 0, 0, 0);width: 72px;text-decoration-style: solid;text-decoration-color: rgb(0, 128, 128);">@Component</span><br mpa-from-tpl="t">public class DeveloperProperty {<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 50px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">private</span> <span style="background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">String</span> <span style="background: rgba(0, 0, 0, 0);width: 29px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">name</span>;<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 50px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">private</span> <span style="background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">String</span> <span style="background: rgba(0, 0, 0, 0);width: 51px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">website</span>;<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 50px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">private</span> <span style="background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">String</span> <span style="background: rgba(0, 0, 0, 0);width: 15px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">qq</span>;<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 50px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">private</span> <span style="background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">String</span> <span style="background: rgba(0, 0, 0, 0);width: 80px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">phoneNumber</span>;<br mpa-from-tpl="t">}</span></p></pre> </section> <section data-mpa-preserve-tpl-color="t" data-mpa-template="t" mpa-preserve="t" mpa-from-tpl="t"> <pre style="background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"><code style="border-radius: 4px;font-size: 0.85em;margin-right: 0.15em;margin-left: 0.15em;background: rgb(248, 248, 248);display: block;padding: 5.20625px;overflow-x: auto;white-space: nowrap;"><span style="font-size: 13px;"><span style="color: rgb(0, 128, 128);background: rgba(0, 0, 0, 0);width: 36px;text-decoration-style: solid;text-decoration-color: rgb(0, 128, 128);">@Data</span><br mpa-from-tpl="t"><span style="color: rgb(153, 153, 136);background: rgba(0, 0, 0, 0);width: 69px;text-decoration-style: solid;text-decoration-color: rgb(153, 153, 136);font-style: italic;">// 指定前缀</span><br mpa-from-tpl="t"><span style="color: rgb(0, 128, 128);background: rgba(0, 0, 0, 0);width: 173px;text-decoration-style: solid;text-decoration-color: rgb(0, 128, 128);">@ConfigurationProperties</span>(prefix = <span style="color: rgb(221, 17, 68);background: rgba(0, 0, 0, 0);width: 80px;text-decoration-style: solid;text-decoration-color: rgb(221, 17, 68);">"developer"</span>)<br mpa-from-tpl="t"><span style="color: rgb(0, 128, 128);background: rgba(0, 0, 0, 0);width: 72px;text-decoration-style: solid;text-decoration-color: rgb(0, 128, 128);">@Component</span><br mpa-from-tpl="t">public class DeveloperProperty {<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 50px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">private</span> <span style="background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">String</span> <span style="background: rgba(0, 0, 0, 0);width: 29px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">name</span>;<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 50px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">private</span> <span style="background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">String</span> <span style="background: rgba(0, 0, 0, 0);width: 51px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">website</span>;<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 50px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">private</span> <span style="background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">String</span> <span style="background: rgba(0, 0, 0, 0);width: 15px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">qq</span>;<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 50px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">private</span> <span style="background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">String</span> <span style="background: rgba(0, 0, 0, 0);width: 80px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">phoneNumber</span>;<br mpa-from-tpl="t">}</span></code></pre> </section> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">使用时注入这个bean</span></p> <section data-mpa-preserve-tpl-color="t" data-mpa-template="t" mpa-preserve="t" mpa-from-tpl="t"> <pre style="background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"><code style="border-radius: 4px;font-size: 0.85em;margin-right: 0.15em;margin-left: 0.15em;background: rgb(248, 248, 248);display: block;padding: 5.20625px;overflow-x: auto;white-space: nowrap;"><span style="color: rgb(0, 128, 128);background: rgba(0, 0, 0, 0);width: 108px;text-decoration-style: solid;text-decoration-color: rgb(0, 128, 128);">@</span><span style="font-size: 13px;"><span style="color: rgb(0, 128, 128);background: rgba(0, 0, 0, 0);width: 108px;text-decoration-style: solid;text-decoration-color: rgb(0, 128, 128);">RestController</span><br mpa-from-tpl="t"><span style="color: rgb(0, 128, 128);background: rgba(0, 0, 0, 0);width: 173px;text-decoration-style: solid;text-decoration-color: rgb(0, 128, 128);">@RequiredArgsConstructor</span><br mpa-from-tpl="t">public class PropertyController {<br mpa-from-tpl="t"> <br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 36px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">final</span> <span style="background: rgba(0, 0, 0, 0);width: 123px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">DeveloperProperty</span> <span style="background: rgba(0, 0, 0, 0);width: 123px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">developerProperty</span>;<br mpa-from-tpl="t"> <br mpa-from-tpl="t"> @<span style="background: rgba(0, 0, 0, 0);width: 72px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">GetMapping</span>(<span style="color: rgb(221, 17, 68);background: rgba(0, 0, 0, 0);width: 80px;text-decoration-style: solid;text-decoration-color: rgb(221, 17, 68);">"/property"</span>)<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 43px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">public</span> <span style="background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">Object</span> <span style="background: rgba(0, 0, 0, 0);width: 36px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">index</span>() {<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">return</span> <span style="background: rgba(0, 0, 0, 0);width: 123px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">developerProperty</span><span style="background: rgba(0, 0, 0, 0);width: 58px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);">.getName</span>();<br mpa-from-tpl="t"> }<br mpa-from-tpl="t">}</span></code></pre> </section> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">2. 用@RequiredArgsConstructor代替@Autowired</span></strong></p> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">我们都知道注入一个 bean 有三种方式哦(set 注入, 构造器注入, 注解注入),Spring 推荐我们使用构造器的方式注入 Bean</span></p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">我们来看看上段代码编译完之后的样子</span></p> <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.4108527131782946" src="/upload/927a87bce8a300779d8e0c077e0d682f.png" data-type="png" data-w="1290" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">RequiredArgsConstructor:lombok提供</span></p> <h2 data-tool="mdnice编辑器" style="margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">3.代码模块化</span></strong></h2> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">阿里巴巴 Java 开发手册中说到每个方法的代码不要超过 50 行(我没记错的话),</span><span style="font-size: 14px;">在实际的开发中我们要善于拆分自己的接口或方法, 做到一个方法只处理一种逻辑, 说不定以后某个功能就用到了, 拿来即用。</span></p> <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.6" src="/upload/707a0f290a322f8a7a09710c738d14d8.png" data-type="png" data-w="1280" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <h2 data-tool="mdnice编辑器" style="margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">4. 抛异常而不是返回</span></strong></h2> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">在写业务代码的时候,经常会根据不同的结果返回不同的信息,尽量减少返回,会显得代码比较乱</span></p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;"><strong>反例</strong></span></p> <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.4554183813443073" src="/upload/a4a63d127138204a44a2250a6bcc9fbf.png" data-type="png" data-w="1458" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;"><strong>正例</strong></span></p> <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.2386634844868735" src="/upload/9a0526d046a822e272556e484b37713c.png" data-type="png" data-w="1676" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <h2 data-tool="mdnice编辑器" style="margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">5. 减少不必要的db</span></strong></h2> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">尽可能的减少对数据库的查询</span></p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;"><strong>举例子</strong></span></p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">删除一个服务(已下架或未上架的才能删除),</span><span style="font-size: 14px;">之前有看别人写的代码,会先根据id查询该记录,然后做一些判断</span></p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;"><strong>反例</strong></span></p> <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.20039880358923232" data-type="png" data-w="2006" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/6749d4887e9e9416e9f0025ab6c38990.png"> </figure> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;"><strong>正例</strong></span></p> <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.21201413427561838" src="/upload/64e33147780a6e5da7d9713ba12a64ef.png" data-type="png" data-w="1698" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <h2 data-tool="mdnice编辑器" style="margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">6. 不要返回 null</span></strong></h2> <p style="margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">反例</span></strong></p> <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.23821339950372208" src="/upload/e97e5e0f0a0148e93c5edc70cc414f7e.png" data-type="png" data-w="1612" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;"><strong>正例</strong></span></p> <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.21216407355021216" src="/upload/6a68c3df7774fda2297fea786100d85d.png" data-type="png" data-w="1414" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">别处调用方法时,避免不必要的空指针</span></p> <h2 data-tool="mdnice编辑器" style="margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">7. if else</span></strong><span style="font-size: 14px;"></span></h2> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">不要太多了if else if,</span><span style="font-size: 14px;">可以试试策略模式代替</span></p> <h2 data-tool="mdnice编辑器" style="margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">8. 减少controller业务代码</span></strong><span style="font-size: 14px;"></span></h2> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">业务代码尽量放到service层进行处理,后期维护起来也好操作而且美观</span></p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;"><strong>反例</strong></span></p> <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.629500580720093" src="/upload/22508326b19fe88a1f88ba42b84e5fef.png" data-type="png" data-w="1722" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;"><strong>正例</strong></span></p> <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.7547169811320755" src="/upload/4514315709558c7eb4930a7865cbf686.png" data-type="png" data-w="1590" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <h2 data-tool="mdnice编辑器" style="margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">9. 利用好Idea</span></strong><span style="font-size: 14px;"></span></h2> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">目前为止市面上的企业基本都用idea作为开发工具了吧</span></p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;"><strong>举一个小例子</strong></span></p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">idea会对我们的代码进行判断,提出合理的建议</span></p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;"><strong>例如:</strong></span></p> <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.19953051643192488" src="/upload/c36f09768f5cb35888100500baed77cb.png" data-type="png" data-w="1704" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">它推荐我们用lanbda的形式代替,</span><span style="font-size: 14px;">点击replace</span></p> <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.13197278911564625" src="/upload/52b7ff12d9b243caaf10bb1388b01489.png" data-type="png" data-w="1470" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <h2 data-tool="mdnice编辑器" style="margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">10. 阅读源码</span></strong></h2> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">一定要养成阅读源码的好习惯包括优秀的开源项目GitHub上stars:>1000, 会从中学好好多知识包括其对代码的设计思想以及高级API,面试加分(好多面试官习惯问源码相关的知识)</span></p> <h2 data-tool="mdnice编辑器" style="margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">11. 设计模式</span></strong><span style="font-size: 14px;"></span></h2> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">23种设计模式,要尝试代码中运用设计模式思想,写出的代码即规范又美观还高大上哈哈。</span></p> <h2 data-tool="mdnice编辑器" style="margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">12. 拥抱新知识</span></strong><span style="font-size: 14px;"></span></h2> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">像我们这种工作年限少的程序员,我觉得要多学习自己认知之外的知识,不能每天crud,有机会就多用用有点难度的知识,没有机会(项目较传统),可以自己下班多些相关demo练习</span></p> <h2 data-tool="mdnice编辑器" style="line-height: 2em;margin-left: 8px;margin-right: 8px;margin-bottom: 15px;"><strong><span style="font-size: 14px;">13. 基础问题</span></strong><span style="font-size: 14px;"> </span></h2> <section style="line-height: 2em;margin-left: 8px;margin-right: 8px;margin-bottom: 15px;"> <span style="font-size: 14px;">map遍历</span> </section> <section data-mpa-preserve-tpl-color="t" data-mpa-template="t" mpa-preserve="t" mpa-from-tpl="t"> <pre style="background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"><code style="border-radius: 4px;font-size: 0.85em;margin-right: 0.15em;margin-left: 0.15em;background: rgb(248, 248, 248);display: block;padding: 5.20625px;overflow-x: auto;white-space: nowrap;"><span style="font-size: 13px;">HashMap<<span style="color: rgb(0, 134, 179);background: rgba(0, 0, 0, 0);width: 43px;text-decoration-style: solid;text-decoration-color: rgb(0, 134, 179);">String</span>, <span style="color: rgb(0, 134, 179);background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(0, 134, 179);">String</span>> map = <span style="background: rgba(0, 0, 0, 0);width: 21px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">new</span> HashMap<>();<br mpa-from-tpl="t"> map.put(<span style="color: rgb(221, 17, 68);background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(221, 17, 68);">"name"</span>, <span style="color: rgb(221, 17, 68);background: rgba(0, 0, 0, 0);width: 29px;text-decoration-style: solid;text-decoration-color: rgb(221, 17, 68);">"du"</span>);<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 21px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">for</span> (<span style="color: rgb(0, 134, 179);background: rgba(0, 0, 0, 0);width: 43px;text-decoration-style: solid;text-decoration-color: rgb(0, 134, 179);">String</span> key : map.keySet()) {<br mpa-from-tpl="t"> <span style="color: rgb(0, 134, 179);background: rgba(0, 0, 0, 0);width: 44px;text-decoration-style: solid;text-decoration-color: rgb(0, 134, 179);">String</span> value = map.get(key);<br mpa-from-tpl="t"> }<br mpa-from-tpl="t"> <br mpa-from-tpl="t"> map.forEach((k, v) -> {<br mpa-from-tpl="t"> <br mpa-from-tpl="t"> });<br mpa-from-tpl="t"> <br mpa-from-tpl="t"> <span style="color: rgb(153, 153, 136);background: rgba(0, 0, 0, 0);width: 45px;text-decoration-style: solid;text-decoration-color: rgb(153, 153, 136);font-style: italic;">// 推荐</span><br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 21px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">for</span> (<span style="color: rgb(0, 134, 179);background: rgba(0, 0, 0, 0);width: 21px;text-decoration-style: solid;text-decoration-color: rgb(0, 134, 179);">Map</span>.Entry<<span style="color: rgb(0, 134, 179);background: rgba(0, 0, 0, 0);width: 43px;text-decoration-style: solid;text-decoration-color: rgb(0, 134, 179);">String</span>, <span style="color: rgb(0, 134, 179);background: rgba(0, 0, 0, 0);width: 43px;text-decoration-style: solid;text-decoration-color: rgb(0, 134, 179);">String</span>> entry : map.entrySet()) {<br mpa-from-tpl="t"> <br mpa-from-tpl="t"> }</span></code></pre> </section> <p><br mpa-from-tpl="t"></p> <section style="line-height: 2em;margin-left: 8px;margin-right: 8px;margin-bottom: 15px;"> <strong style="font-size: 14px;">optional 判空</strong> </section> <section data-mpa-preserve-tpl-color="t" data-mpa-template="t" mpa-preserve="t" mpa-from-tpl="t"> <pre style="background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"><code style="border-radius: 4px;font-size: 0.85em;margin-right: 0.15em;margin-left: 0.15em;background: rgb(248, 248, 248);display: block;padding: 5.20625px;overflow-x: auto;white-space: nowrap;"><span style="font-size: 13px;"><span style="color: rgb(153, 153, 136);background: rgba(0, 0, 0, 0);width: 98px;text-decoration-style: solid;text-decoration-color: rgb(153, 153, 136);font-style: italic;">//获取子目录列表</span><br mpa-from-tpl="t"><span style="background: rgba(0, 0, 0, 0);width: 43px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">public</span> <span style="background: rgba(0, 0, 0, 0);width: 29px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">List</span><CatalogueTreeNode> getChild(String pid) {<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 15px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">if</span> (V.isEmpty(pid)) {<br mpa-from-tpl="t"> pid = BasicDic.TEMPORARY_DIRECTORY_ROOT;<br mpa-from-tpl="t"> }<br mpa-from-tpl="t"> CatalogueTreeNode node = treeNodeMap.get(pid);<br mpa-from-tpl="t"> <br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 43px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">return</span> Optional.ofNullable(node)<br mpa-from-tpl="t"> .map(CatalogueTreeNode::getChild)<br mpa-from-tpl="t"> .orElse(Collections.emptyList());<br mpa-from-tpl="t"> }</span></code></pre> </section> <p><br mpa-from-tpl="t"></p> <p style="line-height: 2em;margin-left: 8px;margin-right: 8px;"><strong style="font-size: 14px;">递归</strong><strong style="font-size: 14px;"><br></strong></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(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">大数据量的递归时,避免在递归方法里new对象,可以试试把对象当作方法参数进行传递使用</span></p> </blockquote> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;"><strong>注释</strong></span></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(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">类 接口方法 注解 较复杂的方法 注释都要写而且要写清楚, 有时候写注释不是给别人看的 而是给自己看的</span></p> </blockquote> <h2 data-tool="mdnice编辑器" style="margin: 15px 8px;line-height: 2em;"><strong><span style="font-size: 14px;">14. 判断元素是否存在</span></strong><span style="font-size: 14px;"></span></h2> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">hashSet 而不是 list,</span><span style="font-size: 14px;">list 判断一个元素是否存在的代码</span></p> <section data-mpa-preserve-tpl-color="t" data-mpa-template="t" mpa-preserve="t" mpa-from-tpl="t"> <pre style="background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"><code style="border-radius: 4px;font-size: 0.85em;margin-right: 0.15em;margin-left: 0.15em;background: rgb(248, 248, 248);display: block;padding: 5.20625px;overflow-x: auto;white-space: nowrap;"><span style="font-size: 13px;">ArrayList<String> <span style="color: rgb(0, 134, 179);background: rgba(0, 0, 0, 0);width: 29px;text-decoration-style: solid;text-decoration-color: rgb(0, 134, 179);">list</span> = <span style="background: rgba(0, 0, 0, 0);width: 22px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">new</span> ArrayList<>();<br mpa-from-tpl="t"> <br mpa-from-tpl="t"><span style="color: rgb(153, 153, 136);background: rgba(0, 0, 0, 0);width: 130px;text-decoration-style: solid;text-decoration-color: rgb(153, 153, 136);font-style: italic;">// 判断a是否在list中</span><br mpa-from-tpl="t"> <br mpa-from-tpl="t"><span style="background: rgba(0, 0, 0, 0);width: 21px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">for</span> (<span style="background: rgba(0, 0, 0, 0);width: 22px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">int</span> i = <span style="color: rgb(0, 128, 128);background: rgba(0, 0, 0, 0);width: 7px;text-decoration-style: solid;text-decoration-color: rgb(0, 128, 128);">0</span>; i < <span style="color: rgb(0, 134, 179);background: rgba(0, 0, 0, 0);width: 29px;text-decoration-style: solid;text-decoration-color: rgb(0, 134, 179);">list</span>.size(); i++)<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 15px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">if</span> (<span style="color: rgb(221, 17, 68);background: rgba(0, 0, 0, 0);width: 22px;text-decoration-style: solid;text-decoration-color: rgb(221, 17, 68);">"a"</span>.equals(elementData[i]))<br mpa-from-tpl="t"> <span style="background: rgba(0, 0, 0, 0);width: 43px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">return</span> i;</span></code></pre> </section> <p style="margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">由此可见其复杂度为On,</span><span style="font-size: 14px;">而hashSet底层采用hashMap作为数据结构进行存储,元素都放到map的key(即链表中)</span></p> <section data-mpa-preserve-tpl-color="t" data-mpa-template="t" mpa-preserve="t" mpa-from-tpl="t"> <pre style="background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"><code style="border-radius: 4px;font-size: 0.85em;margin-right: 0.15em;margin-left: 0.15em;background: rgb(248, 248, 248);display: block;padding: 5.20625px;overflow-x: auto;white-space: nowrap;"><span style="font-size: 13px;">HashSet<String> <span style="background: rgba(0, 0, 0, 0);width: 22px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">set</span> = <span style="background: rgba(0, 0, 0, 0);width: 21px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">new</span> HashSet<>();<br mpa-from-tpl="t"> <br mpa-from-tpl="t"><span style="color: rgb(153, 153, 136);background: rgba(0, 0, 0, 0);width: 122px;text-decoration-style: solid;text-decoration-color: rgb(153, 153, 136);font-style: italic;">// 判断a是否在set中</span><br mpa-from-tpl="t"> <br mpa-from-tpl="t"><span style="background: rgba(0, 0, 0, 0);width: 21px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">int</span> index = hash(a);<br mpa-from-tpl="t"> <br mpa-from-tpl="t"><span style="background: rgba(0, 0, 0, 0);width: 43px;text-decoration-style: solid;text-decoration-color: rgb(51, 51, 51);font-weight: 700;">return</span> getNode(index) != <span style="color: rgb(0, 128, 128);background: rgba(0, 0, 0, 0);width: 29px;text-decoration-style: solid;text-decoration-color: rgb(0, 128, 128);">null</span></span></code></pre> </section> <p style="padding-top: 8px;padding-bottom: 8px;margin: 15px 8px;line-height: 2em;"><span style="font-size: 14px;">由此可见其复杂度为O1。</span></p> <section data-mpa-preserve-tpl-color="t" data-mpa-template="t" mpa-preserve="t" mpa-from-tpl="t"> <pre style="background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"><code style="border-radius: 4px;font-size: 0.85em;margin-right: 0.15em;margin-left: 0.15em;background: rgb(248, 248, 248);display: block;padding: 5.20625px;overflow-x: auto;white-space: nowrap;"><span style="font-size: 13px;"><span style="color: rgba(0, 0, 0, 0.5);font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.544px;text-align: left;background-color: rgb(255, 255, 255);"></span></span></code> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="75" data-source-title=""> <section class="js_blockquote_digest"> <section> <span style="color: rgba(0, 0, 0, 0.5);font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.544px;text-align: left;background-color: rgb(255, 255, 255);">转自:国涛1998</span> <br style="outline: 0px;max-width: 100%;color: rgba(0, 0, 0, 0.5);font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.544px;text-align: left;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="color: rgba(0, 0, 0, 0.5);font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.544px;text-align: left;background-color: rgb(255, 255, 255);">来源:https://blog.csdn.net/weixin_44912855/article/details/120866194</span> </section> </section> </blockquote></pre> </section>
作者:微信小助手
<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="MzkyNTI5NTQ1NQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/wxcY9TH8dPsYAnrjaZktBe0iahF8ic9QkF26cAw8pK6HPR1bfFEImdyJspvkQvQwmnYxP4eEVW60ewVVickcWXnrQ/0?wx_fmt=png" data-nickname="架构文摘" data-alias="ArchDigest" data-signature="每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性能、高稳定)、大数据、机器学习、Java架构等各个热门领域。" data-from="0"></mpprofile> </section> <h2 style="margin-bottom: 15px;max-width: 100%;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(37, 183, 167);font-size: 20px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">背景</strong></span></h2> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">美团外卖已经发展了五年,即时物流探索也经历了3年多的时间,业务从零孵化到初具规模,在整个过程中积累了一些分布式高并发系统的建设经验。最主要的收获包括两点:</span></p> <ol class="list-paddingleft-2" style=""> <li><p style="margin-top: 1.5em;margin-bottom: 1.5em;max-width: 100%;min-height: 1em;font-size: inherit;color: inherit;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">即时物流业务对故障和高延迟的容忍度极低,在业务复杂度提升的同时也要求系统具备分布式、可扩展、可容灾的能力。即时物流系统阶段性的逐步实施分布式系统的架构升级,最终解决了系统宕机的风险。</span></p></li> <li><p style="margin-top: 1.5em;margin-bottom: 1.5em;max-width: 100%;min-height: 1em;font-size: inherit;color: inherit;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">围绕成本、效率、体验核心三要素,即时物流体系大量结合AI技术,从定价、ETA、调度、运力规划、运力干预、补贴、核算、语音交互、LBS挖掘、业务运维、指标监控等方面,业务突破结合架构升级,达到促规模、保体验、降成本的效果。</span></p></li> </ol> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.4806547619047619" data-s="300,640" src="/upload/1f22ded19a534f92ebabc8cd86a35516.png" data-type="png" data-w="1344" style="box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"></p> <p style="margin-top: 1.5em;margin-bottom: 1.5em;max-width: 100%;min-height: 1em;caret-color: rgb(51, 51, 51);color: inherit;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: inherit;text-align: justify;white-space: normal;text-size-adjust: auto;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">本文主要介绍在美团即时物流分布式系统架构逐层演变的进展中,遇到的技术障碍和挑战:</span></p> <ul class="list-paddingleft-2" style=""> <li><p style="max-width: 100%;min-height: 1em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: inherit;line-height: inherit;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">订单、骑手规模大,供需匹配过程的超大规模计算问题。</span></p></li> <li><p style="max-width: 100%;min-height: 1em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: inherit;line-height: inherit;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">遇到节假日或者恶劣天气,订单聚集效应,流量高峰是平常的十几倍。</span></p></li> <li><p style="max-width: 100%;min-height: 1em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: inherit;line-height: inherit;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">物流履约是线上连接线下的关键环节,故障容忍度极低,不能宕机,不能丢单,可用性要求极高。</span></p></li> <li><p style="max-width: 100%;min-height: 1em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: inherit;line-height: inherit;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">数据实时性、准确性要求高,对延迟、异常非常敏感。</span></p></li> </ul> <h2 style="max-width: 100%;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></h2> <h2 style="margin-bottom: 15px;max-width: 100%;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(37, 183, 167);font-size: 20px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">美团即时物流架构</strong></span></h2> <p style="margin-bottom: 15px;max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">美团即时物流配送平台主要围绕三件事展开:一是面向用户提供履约的SLA,包括计算送达时间ETA、配送费定价等;二是在多目标(</span><span style="max-width: 100%;font-size: 15px;color: rgb(136, 136, 136);box-sizing: border-box !important;overflow-wrap: break-word !important;">成本、效率、体验</span><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">)优化的背景下,匹配最合适的骑手;三是提供骑手完整履约过程中的辅助决策,包括智能语音、路径推荐、到店提醒等。</span></p> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.4947526236881559" data-s="300,640" src="/upload/4d89d4f4ffa9bf7b75d4f4c899f2518f.png" data-type="png" data-w="1334" style="box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"></p> <p style="margin-top: 1.5em;margin-bottom: 1.5em;max-width: 100%;min-height: 1em;caret-color: rgb(51, 51, 51);color: inherit;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: inherit;text-align: justify;white-space: normal;text-size-adjust: auto;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">在一系列服务背后,是美团强大的技术体系的支持,并由此沉淀出的配送业务架构体系,基于架构构建的平台、算法、系统和服务。庞大的物流系统背后离不开分布式系统架构的支撑,而且这个架构更要保证高可用和高并发。</span></p> <p style="margin-top: 1.5em;margin-bottom: 1.5em;max-width: 100%;min-height: 1em;caret-color: rgb(51, 51, 51);color: inherit;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: inherit;text-align: justify;white-space: normal;text-size-adjust: auto;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">分布式架构,是相对于集中式架构而言的一种架构体系。分布式架构适用CAP理论(</span><span style="max-width: 100%;font-size: 15px;color: rgb(136, 136, 136);box-sizing: border-box !important;overflow-wrap: break-word !important;">Consistency 一致性,Availability 可用性,Partition Tolerance 分区容忍性</span><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">)。在分布式架构中,一个服务部署在多个对等节点中,节点之间通过网络进行通信,多个节点共同组成服务集群来提供高可用、一致性的服务。</span></p> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">早期,美团按照业务领域划分成多个垂直服务架构;随着业务的发展,从可用性的角度考虑做了分层服务架构。后来,业务发展越发复杂,从运维、质量等多个角度考量后,逐步演进到微服务架构。这里主要遵循了两个原则:不宜过早的进入到微服务架构的设计中,好的架构是演进出来的不是提前设计出来的。</span></p> <h2 style="margin-top: 15px;max-width: 100%;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(37, 183, 167);font-size: 20px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">分布式系统实践</strong></span></h2> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.6743215031315241" data-s="300,640" src="/upload/d9ab8ad351e172f464cdf7e268c05826.png" data-type="png" data-w="958" style="box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"></p> <p style="margin-top: 1.5em;margin-bottom: 1.5em;max-width: 100%;min-height: 1em;caret-color: rgb(51, 51, 51);color: inherit;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: inherit;text-align: justify;white-space: normal;text-size-adjust: auto;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">上图是比较典型的美团技术体系下的分布式系统结构:依托了美团公共组件和服务,完成了分区扩容、容灾和监控的能力。前端流量会通过HLB来分发和负载均衡;在分区内,服务与服务会通过OCTO进行通信,提供服务注册、自动发现、负载均衡、容错、灰度发布等等服务。当然也可以通过消息队列进行通信,例如Kafka、RabbitMQ。在存储层使用Zebra来访问分布式数据库进行读写操作。利用<a href="http://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651749250&idx=2&sn=704a3c8b92e8221f0a0dfdbd947d9f85&chksm=bd12a2cf8a652bd9d1e1286c6dfb3ca85d46c1e83e958dc594bc21f74ea1a3181866a7e6edab&scene=21#wechat_redirect" target="_blank" style="color: rgb(87, 107, 149);max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" data-linktype="2">CAT</a>(</span><span style="max-width: 100%;font-size: 15px;color: rgb(136, 136, 136);box-sizing: border-box !important;overflow-wrap: break-word !important;">美团开源的分布式监控系统</span><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">)</span><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">进行分布式业务及系统日志的采集、上报和监控。分布式缓存使用Squirrel+Cellar的组合。分布式任务调度则是通过Crane。</span></p> <p style="margin-top: 1.5em;margin-bottom: 1.5em;max-width: 100%;min-height: 1em;caret-color: rgb(51, 51, 51);color: inherit;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: inherit;text-align: justify;white-space: normal;text-size-adjust: auto;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">在实践过程还要解决几个问题,比较典型的是集群的扩展性,有状态的集群可扩展性相对较差,无法快速扩容机器,无法缓解流量压力。同时,也会出现节点热点的问题,包括资源不均匀、CPU使用不均匀等等。</span></p> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.46131805157593125" data-s="300,640" src="/upload/611090921e5cac098babcd269d6a92b4.png" data-type="png" data-w="1396" style="box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"></p> <p style="margin-top: 1.5em;margin-bottom: 1.5em;max-width: 100%;min-height: 1em;caret-color: rgb(51, 51, 51);color: inherit;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: inherit;text-align: justify;white-space: normal;text-size-adjust: auto;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">首先,配送后台技术团队通过架构升级,将有状态节点变成无状态节点,通过并行计算的能力,让小的业务节点去分担计算压力,以此实现快速扩容。</span></p> <p style="margin-top: 1.5em;margin-bottom: 1.5em;max-width: 100%;min-height: 1em;caret-color: rgb(51, 51, 51);color: inherit;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: inherit;text-align: justify;white-space: normal;text-size-adjust: auto;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">第二是要解决一致性的问题,对于既要写DB也要写缓存的场景,业务写缓存无法保障数据一致性,美团内部主要通过Databus来解决,Databus是一个高可用、低延时、高并发、保证数据一致性的数据库变更实时传输系统。通过Databus上游可以监控业务Binlog变更,通过管道将变更信息传递给ES和其他DB,或者是其他KV系统,利用Databus的高可用特性来保证数据最终是可以同步到其他系统中。</span></p> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" src="/upload/133b3f15ce487c41c560c00cde0fcc65.png" data-cropx1="2.5412186379928317" data-cropx2="1418" data-cropy1="78.77777777777779" data-cropy2="564.1505376344086" data-ratio="0.34296875" data-s="300,640" src="https://mmbiz.qpic.cn/mmbiz_jpg/hEx03cFgUsXUJP3PB8yicK6pda3wIZ8UkC3FhNouAnIxTKcvR6vb4pV9PXaBS9c0l52g1HiaBcoP4CNSyxjEchew/640?wx_fmt=jpeg" data-type="jpeg" data-w="1280" style="box-sizing: border-box !important;overflow-wrap: break-word !important;width: 557px !important;visibility: visible !important;"></p> <p style="margin-top: 1.5em;margin-bottom: 1.5em;max-width: 100%;min-height: 1em;caret-color: rgb(51, 51, 51);color: inherit;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: inherit;text-align: justify;white-space: normal;text-size-adjust: auto;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">第三是我们一直在花精力解决的事情,就是保障集群高可用,主要从三个方面来入手,事前较多的是做全链路压测评,估峰值容量;周期性的集群健康性检查;随机故障演练(</span><span style="max-width: 100%;font-size: 15px;color: rgb(136, 136, 136);box-sizing: border-box !important;overflow-wrap: break-word !important;">服务、机器、组件</span><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">)。事中做异常报警(</span><span style="max-width: 100%;font-size: 15px;color: rgb(136, 136, 136);box-sizing: border-box !important;overflow-wrap: break-word !important;">性能、业务指标、可用性</span><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">);快速的故障定位(</span><span style="max-width: 100%;font-size: 15px;color: rgb(136, 136, 136);box-sizing: border-box !important;overflow-wrap: break-word !important;">单机故障、集群故障、IDC故障、组件异常、服务异常</span><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">);故障前后的系统变更收集。事后重点做系统回滚;扩容、限流、熔断、降级;核武器兜底。</span></p> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.47645429362880887" data-s="300,640" src="/upload/482ea87e25abc42f3208139c66330a8.png" data-type="png" data-w="1444" style="box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"></p> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.4226666666666667" data-s="300,640" src="/upload/58aaa8be6d0154c37d82ad00dc185e74.png" data-type="png" data-w="1500" style="box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"></p> <h3 style="margin-bottom: 15px;max-width: 100%;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(0, 0, 0);font-size: 18px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">单IDC的快速部署&容灾</strong></span></h3> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">单IDC故障之后,入口服务做到故障识别,自动流量切换;单IDC的快速扩容,数据提前同步,服务提前部署,Ready之后打开入口流量;要求所有做数据同步、流量分发的服务,都具备自动故障检测、故障服务自动摘除;按照IDC为单位扩缩容的能力。</span></p> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.6291161178509532" data-s="300,640" src="/upload/3e0bd0e848cbd9a8928223584df3f4e.png" data-type="png" data-w="1154" style="box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"></p> <h3 style="margin-bottom: 15px;max-width: 100%;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 18px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">多中心尝试</strong></span></h3> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">美团IDC以分区为单位,存在资源满排,分区无法扩容。美团的方案是多个IDC组成虚拟中心,以中心为分区的单位;服务无差别的部署在中心内;中心容量不够,直接增加新的IDC来扩容容量。</span></p> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.6212914485165794" data-s="300,640" src="/upload/a2e8cf15b01173d5063da26f1926d102.png" data-type="png" data-w="1146" style="box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"></p> <h3 style="margin-bottom: 15px;max-width: 100%;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 18px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">单元化尝试</strong></span></h3> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">相比多中心来说,单元化是进行分区容灾和扩容的更优方案。关于流量路由,美团主要是根据业务特点,采用区域或城市进行路由。数据同步上,异地会出现延迟状况。SET容灾上要保证同本地或异地SET出现问题时,可以快速把SET切换到其他SET上来承担流量。</span></p> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.5366269165247018" data-s="300,640" src="/upload/ce1f9f39d0210578db04f3c5684b165b.png" data-type="png" data-w="1174" style="box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"></p> <h2 style="margin-bottom: 15px;max-width: 100%;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(37, 183, 167);font-size: 20px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">智能物流的核心技术能力和平台沉淀</strong></span></h2> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">机器学习平台,是一站式线下到线上的模型训练和算法应用平台。之所以构建这个平台,目的是要解决算法应用场景多,重复造轮子的矛盾问题,以及线上、线下数据质量不一致。如果流程不明确不连贯,会出现迭代效率低,特征、模型的应用上线部署出现数据质量等障碍问题。</span></p> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.5595744680851064" data-s="300,640" src="/upload/8127769fc794833682ee8395b17b9472.png" data-type="png" data-w="940" style="box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"></p> <p style="margin-top: 1.5em;margin-bottom: 1.5em;max-width: 100%;min-height: 1em;caret-color: rgb(51, 51, 51);color: inherit;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: inherit;text-align: justify;white-space: normal;text-size-adjust: auto;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">JARVIS是一个以稳定性保障为目标的智能化业务运维AIOps平台。主要用于处理系统故障时报警源很多,会有大量的重复报警,有效信息很容易被淹没等各种问题。此外,过往小规模分布式集群的运维故障主要靠人和经验来分析和定位,效率低下,处理速度慢,每次故障处理得到的预期不稳定,在有效性和及时性方面无法保证。所以需要AIOps平台来解决这些问题。</span></p> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" src="/upload/78468c2c5f97a24fe6aca8489d7801df.png" data-cropx1="0" data-cropx2="1080" data-cropy1="48.38709677419354" data-cropy2="543.8709677419354" data-ratio="0.45925925925925926" data-s="300,640" src="https://mmbiz.qpic.cn/mmbiz_jpg/hEx03cFgUsXUJP3PB8yicK6pda3wIZ8UkTVnS2AGNsfa5m9H2Dk3ZRjiaJJl3ASjfnJOiawdVLqk0DJRqW8C9grug/640?wx_fmt=jpeg" data-type="jpeg" data-w="1080" style="box-sizing: border-box !important;overflow-wrap: break-word !important;width: 558px !important;visibility: visible !important;"></p> <h2 style="margin-bottom: 15px;max-width: 100%;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(37, 183, 167);font-size: 20px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">未来的挑战</strong></span></h2> <p style="max-width: 100%;min-height: 1em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 16px;text-align: justify;white-space: normal;text-size-adjust: auto;color: rgb(62, 62, 62);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">经过复盘和Review之后,我们发现未来的挑战很大,微服务不再“微”了,业务复杂度提升之后,服务就会变得膨胀。其次,网状结构的服务集群,任何轻微的延迟,都可能导致的网络放大效应。另外复杂的服务拓扑,如何做到故障的快速定位和处理,这也是AIOps需要重点解决的难题。最后,就是单元化之后,从集群为单位的运维到以单元为单位的运维,也给美团业务部署能力带来很大的挑战。</span></p>
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="color: black;line-height: 1.6;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: PingFangSC-Light;font-size: 16px;padding: 10px;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);"><span style="letter-spacing: 0.1em;word-spacing: 0.1em;">上回说到</span><a href="https://mp.weixin.qq.com/s?__biz=MzkzMDI1NjcyOQ==&mid=2247497323&idx=1&sn=387d84d644ce6351a05ebca8f8c46df5&scene=21#wechat_redirect" data-linktype="2" style="letter-spacing: 0.1em;word-spacing: 0.1em;color: rgb(244, 138, 0);border-bottom: 1px solid rgb(244, 138, 0);font-family: STHeitiSC-Light;">使用 Redis 的 List 实现消息队列</a><span style="letter-spacing: 0.1em;word-spacing: 0.1em;">有很多局限性,比如:</span></p> <ul data-tool="mdnice编辑器" style="font-size: 15px;margin-top: 8px;margin-bottom: 8px;padding-left: 20px;color: rgb(255, 191, 82);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> 没有良好的 ACK 机制; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> 没有 ConsumerGroup 消费组概念; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> 消息堆积。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> List 是线性结构,想要查询指定数据需要遍历整个列表; </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">Stream 是 Redis 5.0 引入的一种专门为消息队列设计的数据类型,Stream 是一个包含 0 个或者多个元素的有序队列,这些元素根据 ID 的大小进行有序排列。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">它实现了大部分消息队列的功能:</p> <ul data-tool="mdnice编辑器" style="font-size: 15px;margin-top: 8px;margin-bottom: 8px;padding-left: 20px;color: rgb(255, 191, 82);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> 消息 ID 系列化生成; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> 消息遍历; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> 消息的阻塞和非阻塞读; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> <strong style="color: rgba(0, 0, 0, 0.85);">Consumer Groups</strong> 消费组; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> ACK 确认机制。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> 支持多播。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">提供了很多消息队列操作命令,并且借鉴 Kafka 的 <strong>Consumer Groups</strong> 的概念,提供了消费组功能。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);"><strong>同时提供了消息的持久化和主从复制机制,客户端可以访问任何时刻的数据,并且能记住每一个客户端的访问位置,从而保证消息不丢失。</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">废话少说,先来看下如何使用,官网文档详见:https://redis.io/topics/streams-intro</p> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzkzMDI1NjcyOQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/EoJib2tNvVtf7icAmS0BQH6oDVG37Q8NzcfdguS5qAqOhfxvZyIKqmuX5BbnDjynrBbZzktp1EiaeFLzapp1nHysw/0?wx_fmt=png" data-nickname="码哥字节" data-alias="MageByte" data-signature="拥抱硬核技术和对象,面向人民币编程。" data-from="0"></mpprofile> </section> <h1 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 24px;text-align: center;line-height: 95px;margin-top: 10px;margin-bottom: 10px;"><span style="font-size: 22px;color: #f48a00;border-bottom: 2px solid #ffbf52;">XADD:插入消息</span></h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">「云岚宗众弟子听命,击杀萧炎!」</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">当云山最后一字落下,那弥漫的紧绷气氛,顿时宣告破碎,悬浮半空的众多云岚宗长老背后双翼一振,便是咻咻的划过天际,追杀萧炎。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">云山使用以下指令向队列中插入「追杀萧炎」命令,让长老带领子弟去执行。</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">XADD 云岚宗 * task kill name 萧炎<br>"1645936602161-0"<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">Stream 中的每个元素由键值对的形式组成,不<strong>同元素可以包含不同数量的键值对</strong>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">该命令的语法如下:</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">XADD streamName id field value [field value ...]<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">消息队列名称后面的 「*」 ,表示让 Redis 为插入的消息自动生成唯一 ID,当然也可以自己定义。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">消息 ID 由两部分组成:</p> <ul data-tool="mdnice编辑器" style="font-size: 15px;margin-top: 8px;margin-bottom: 8px;padding-left: 20px;color: rgb(255, 191, 82);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> 当前毫秒内的时间戳; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> 顺序编号。从 0 为起始值,用于区分同一时间内产生的多个命令。 </section></li> </ul> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;text-size-adjust: 100%;line-height: 1.75em;border-radius: 5px;box-sizing: inherit;border-width: 1px;border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-color: rgb(255, 191, 82);background: rgb(255, 248, 230);"> <span style="color: #f48a00;font-size: 32px;line-height: 0.6;margin-left: -15px;">❝</span> <p style="padding-top: 8px;padding-bottom: 8px;letter-spacing: 0.1em;font-size: 16px;word-spacing: 0.1em;text-align: justify;line-height: 26px;margin-top: -15px;color: rgba(0, 0, 0, 0.85);">通过将元素 ID 与时间进行关联,并强制要求新元素的 ID 必须大于旧元素的 ID, Redis 从逻辑上将流变成了一种只执行追加操作(append only)的数据结构。</p> <p style="padding-top: 8px;padding-bottom: 8px;letter-spacing: 0.1em;font-size: 16px;word-spacing: 0.1em;text-align: justify;line-height: 26px;margin-top: -15px;color: rgba(0, 0, 0, 0.85);">这种特性对于使用流实现消息队列和事件系统的用户来说是非常重要的:</p> <p style="padding-top: 8px;padding-bottom: 8px;letter-spacing: 0.1em;font-size: 16px;word-spacing: 0.1em;text-align: justify;line-height: 26px;margin-top: -15px;color: rgba(0, 0, 0, 0.85);">用户可以确信,新的消息和事件只会出现在已有消息和事件之后,就像现实世界里新事件总是发生在已有事件之后一样,一切都是有序进行的。</p> </blockquote> <h1 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 24px;text-align: center;line-height: 95px;margin-top: 10px;margin-bottom: 10px;"><span style="font-size: 22px;color: #f48a00;border-bottom: 2px solid #ffbf52;">XREAD:读取消息</span></h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">云凌老狗使用如下指令接收云山的命令:</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">XREAD COUNT 1 BLOCK 0 STREAMS 云岚宗 0-0<br>1) 1) "\xe4\xba\x91\xe5\xb2\x9a\xe5\xae\x97"<br> 2) 1) 1) "1645936602161-0"<br> 2) 1) "task"<br> 2) "kill"<br> 3) "name"<br> 4) "萧炎" # 萧炎<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);"><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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]</code></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">该指令可以同时对多个流进行读取,每个心法对应含义如下:</p> <ul data-tool="mdnice编辑器" style="font-size: 15px;margin-top: 8px;margin-bottom: 8px;padding-left: 20px;color: rgb(255, 191, 82);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> COUNT:表示每个流中最多读取的元素个数; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> BLOCK:阻塞读取,当消息队列没有消息的时候,则阻塞等待, 0 表示无限等待,单位是毫秒。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> ID:消息 ID, <strong style="color: rgba(0, 0, 0, 0.85);">在读取消息的时候可以指定 ID,并从这个 ID 的下一条消息开始读取,0-0 则表示从第一个元素开始读取</strong>。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);"><strong>如果想使用 XREAD 进行顺序消费,每次读取后要记住返回的消息 ID,下次调用 XREAD 就将上一次返回的消息 ID 作为参数传递到下一次调用就可以继续消费后续的消息了。</strong></p> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;text-size-adjust: 100%;line-height: 1.75em;border-radius: 5px;box-sizing: inherit;border-width: 1px;border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-color: rgb(255, 191, 82);background: rgb(255, 248, 230);"> <span style="color: #f48a00;font-size: 32px;line-height: 0.6;margin-left: -15px;">❝</span> <p style="padding-top: 8px;padding-bottom: 8px;letter-spacing: 0.1em;font-size: 16px;word-spacing: 0.1em;text-align: justify;line-height: 26px;margin-top: -15px;color: rgba(0, 0, 0, 0.85);">云韵宗主,我今天刚到云岚宗,历史的消息就不接了,只想接收我使用 XREAD 阻塞等待的那一刻开始通过 XADD 发布的消息要咋整?</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">运行「<span style="cursor: pointer;"> <svg xmlns="http://www.w3.org/2000/svg" role="img" focusable="false" viewbox="0 -750 10264.9 950" aria-hidden="true" style="vertical-align: -0.452ex;width: 23.224ex;height: 2.149ex;"> <g stroke="currentColor" fill="currentColor" stroke-width="0" transform="matrix(1 0 0 -1 0 0)"> <g data-mml-node="math"> <g data-mml-node="mo"> <text data-variant="normal" transform="matrix(1 0 0 -1 0 0)" font-size="855.5px" font-family="serif"> 」 </text> <text data-variant="normal" transform="translate(855.4, 0) matrix(1 0 0 -1 0 0)" font-size="855.5px" font-family="serif"> 心 </text> <text data-variant="normal" transform="translate(1710.8, 0) matrix(1 0 0 -1 0 0)" font-size="855.5px" font-family="serif"> 法 </text> <text data-variant="normal" transform="translate(2566.2, 0) matrix(1 0 0 -1 0 0)" font-size="855.5px" font-family="serif"> 即 </text> <text data-variant="normal" transform="translate(3421.6, 0) matrix(1 0 0 -1 0 0)" font-size="855.5px" font-family="serif"> 可 </text> <text data-variant="normal" transform="translate(4277, 0) matrix(1 0 0 -1 0 0)" font-size="855.5px" font-family="serif"> , </text> <text data-variant="normal" transform="translate(5132.4, 0) matrix(1 0 0 -1 0 0)" font-size="855.5px" font-family="serif"> 心 </text> <text data-variant="normal" transform="translate(5987.9, 0) matrix(1 0 0 -1 0 0)" font-size="855.5px" font-family="serif"> 法 </text> <text data-variant="normal" transform="translate(6843.3, 0) matrix(1 0 0 -1 0 0)" font-size="855.5px" font-family="serif"> 的 </text> <text data-variant="normal" transform="translate(7698.7, 0) matrix(1 0 0 -1 0 0)" font-size="855.5px" font-family="serif"> 最 </text> <text data-variant="normal" transform="translate(8554.1, 0) matrix(1 0 0 -1 0 0)" font-size="855.5px" font-family="serif"> 后 </text> <text data-variant="normal" transform="translate(9409.5, 0) matrix(1 0 0 -1 0 0)" font-size="855.5px" font-family="serif"> 「 </text> </g> </g> </g> </svg></span>」符号表示读取最新的阻塞消息,读取不到则一直死等。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">等待过程中,其他长老向队列追加消息,则会立即读取到。</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">XREAD COUNT 1 BLOCK 0 STREAMS 云岚宗 $<br></code></pre> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;text-size-adjust: 100%;line-height: 1.75em;border-radius: 5px;box-sizing: inherit;border-width: 1px;border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-color: rgb(255, 191, 82);background: rgb(255, 248, 230);"> <span style="color: #f48a00;font-size: 32px;line-height: 0.6;margin-left: -15px;">❝</span> <p style="padding-top: 8px;padding-bottom: 8px;letter-spacing: 0.1em;font-size: 16px;word-spacing: 0.1em;text-align: justify;line-height: 26px;margin-top: -15px;color: rgba(0, 0, 0, 0.85);">这么容易就实现消息队列了么?说好的 ACK 机制呢?</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">这里只是开胃菜,通过 XREAD 读取的数据其实并没有被删除,当重新执行 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">XREAD COUNT 2 BLOCK 0 STREAMS 云岚宗 0-0</code> 指令的时候又会重新读取到。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">所以我们还需要 ACK 机制,</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">接下来,我们来一个真正的消息队列。</p> <h1 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 24px;text-align: center;line-height: 95px;margin-top: 10px;margin-bottom: 10px;"><span style="font-size: 22px;color: #f48a00;border-bottom: 2px solid #ffbf52;">ConsumerGroup</span></h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">Redis Stream 的 ConsumerGroup(消费者组)允许用户将一个流从逻辑上划分为多个不同的流,并让 ConsumerGroup 的消费者去处理。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">它是一个强大的<strong>支持多播的可持久化的消息队列</strong>。Redis Stream 借鉴了 Kafka 的设计。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">Stream 的高可用是建立主从复制基础上的,它和其它数据结构的复制机制没有区别,也就是说在 Sentinel 和 Cluster 集群环境下 Stream 是可以支持高可用的。</p> <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.75" src="/upload/1a50460223e92ea03cf0a5ec66bd7e14.png" data-type="png" data-w="580" style="border-radius: 5px;display: block;margin-right: 10px;margin-bottom: auto;margin-left: 10px;width: 100%;height: 100%;object-fit: contain;"> <figcaption style="margin-top: 5px;font-size: 13px;color: rgba(0, 0, 0, 0.55);text-align: right;"> Redis-Stream </figcaption> </figure> <ul data-tool="mdnice编辑器" style="font-size: 15px;margin-top: 8px;margin-bottom: 8px;padding-left: 20px;color: rgb(255, 191, 82);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> Redis Stream 的结构如上图所示。有一个消息链表,每个消息都有一个唯一的 ID 和对应的内容; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> 消息持久化; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> <strong style="color: rgba(0, 0, 0, 0.85);">每个消费组的状态是独立的,不不影响,同一份的 Stream 消息会被所有的消费组消费;</strong> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> <strong style="color: rgba(0, 0, 0, 0.85);">一个消费组可以由多个消费者组成,消费者之间是竞争关系,任意一个消费者读取了消息都会使 last_deliverd_id 往前移动;</strong> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> <strong style="color: rgba(0, 0, 0, 0.85);">每个消费者有一个 pending_ids 变量,用于记录当前消费者读取了但是还没 ack 的消息。它用来保证消息至少被客户端消费了一次。</strong> </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">消费组实现的消息队列主要涉及以下三个指令:</p> <ul data-tool="mdnice编辑器" style="font-size: 15px;margin-top: 8px;margin-bottom: 8px;padding-left: 20px;color: rgb(255, 191, 82);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> XGROUP用于创建、销毁和管理消费者组。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> XREADGROUP通过消费组从流中读取数据。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> XACK是允许消费者将待处理消息标记为已正确处理的命令。 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin-top: 20px;"><span style="font-size: 20px;color: #f48a00;display: inline-block;padding-left: 10px;border-left: 8px solid #ffbf52;">创建消费组</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">Stream 通过 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">XGROUP CREATE</code> 指令创建消费组 (Consumer Group),需要传递起始消息 ID 参数用来初始化 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">last_delivered_id</code> 变量。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">我们使用 XADD 往 bossStream 队列插入一些消息:</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">XADD bossStream * name zhangsan age 26<br>XADD bossStream * name lisi age 2<br>XADD bossStream * name bigold age 40<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">如下指令,为消息队列名为 bossStream 创建「青龙门」和「六扇门」两个消费组。</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #5c6370;font-style: italic;line-height: 26px;"># 语法如下</span><br><span style="color: #5c6370;font-style: italic;line-height: 26px;"># XGROUP CREATE stream group start_id</span><br>XGROUP CREATE bossStream 青龙门 0-0 MKSTREAM<br>XGROUP CREATE bossStream 六扇门 0-0 MKSTREAM<br></code></pre> <ul data-tool="mdnice编辑器" style="font-size: 15px;margin-top: 8px;margin-bottom: 8px;padding-left: 20px;color: rgb(255, 191, 82);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> stream:指定队列的名字; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> group:指定消费组名字; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> start_id:指定消费组在 Stream 中的起始 ID,它决定了消费者组从哪个 ID 之后开始读取消息, <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">0-0</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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">$</code> 表示从最后一条向后开始读取,只接收新消息。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> MKSTREAM:默认情况下,XGROUP CREATE命令在目标流不存在时返回错误。可以使用可选 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">MKSTREAM</code>子命令作为 之后的最后一个参数来自动创建流。 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin-top: 20px;"><span style="font-size: 20px;color: #f48a00;display: inline-block;padding-left: 10px;border-left: 8px solid #ffbf52;">读取消息</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">让「青龙门」消费组的 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">consumer1</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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">bossStream</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">XREADGROUP GROUP 青龙门 consumer1 COUNT 1 BLOCK 0 STREAMS bossStream ><br>1) 1) <span style="color: #98c379;line-height: 26px;">"bossStream"</span><br> 2) 1) 1) <span style="color: #98c379;line-height: 26px;">"1645957821396-0"</span><br> 2) 1) <span style="color: #98c379;line-height: 26px;">"name"</span><br> 2) <span style="color: #98c379;line-height: 26px;">"zhangsan"</span><br> 3) <span style="color: #98c379;line-height: 26px;">"age"</span><br> 4) <span style="color: #98c379;line-height: 26px;">"26"</span><br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">语法如下:</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">XREADGROUP GROUP groupName consumerName [COUNT n] [BLOCK ms] STREAMS streamName [stream ...] id [id ...]<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">[] 内的表示可选参数,该命令与 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">XREAD</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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">GROUP groupName consumerName</code> 选项。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">该选项的两个参数分别用于指定被读取的消费者组以及负责处理消息的消费者。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">其中:</p> <ul data-tool="mdnice编辑器" style="font-size: 15px;margin-top: 8px;margin-bottom: 8px;padding-left: 20px;color: rgb(255, 191, 82);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">></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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">></code>,表示从尚未被消费的消息开始读取; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> BLOCK:阻塞读取; </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">敲黑板了</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);"><strong>如果消息队列中的消息被消费组的一个消费者消费了,这条消息就不会再被这个消费组的其他消费者读取到。</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">比如 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">consumer2</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">XREADGROUP GROUP 青龙门 consumer2 COUNT 1 BLOCK 0 STREAMS bossStream ><br>1) 1) <span style="color: #98c379;line-height: 26px;">"bossStream"</span><br> 2) 1) 1) <span style="color: #98c379;line-height: 26px;">"1645957838700-0"</span><br> 2) 1) <span style="color: #98c379;line-height: 26px;">"name"</span><br> 2) <span style="color: #98c379;line-height: 26px;">"lisi"</span><br> 3) <span style="color: #98c379;line-height: 26px;">"age"</span><br> 4) <span style="color: #98c379;line-height: 26px;">"2"</span><br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);"><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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">consumer2</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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">zhangsan</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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">lisi</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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">consumer1</code> 读取了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);"><strong>使用消费者的另一个目的可以让组内的多个消费者分担读取消息,也就是每个消费者读取部分消息,从而实现均衡负载。</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">比如一个消费组有三个消费者 C1、C2、C3 和一个包含消息 1、2、3、4、5、6、7 的流:</p> <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.8406862745098039" src="/upload/75491a4abd197ba4b7876ca59c523ede.png" data-type="png" data-w="408" style="border-radius: 5px;display: block;margin-right: 10px;margin-bottom: auto;margin-left: 10px;width: 100%;height: 100%;object-fit: contain;"> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin-top: 20px;"><span style="font-size: 20px;color: #f48a00;display: inline-block;padding-left: 10px;border-left: 8px solid #ffbf52;">XPENDING 查看已读未确认消息</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">为了保证消费者在消费的时候发生故障或者宕机重启后依然可以读取消息,<strong>Stream 内部有一个队列(pending List)保存每个消费者读取但是还没有执行 ACK 的消息</strong>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">如果消费者使用了 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">XREADGROUP GROUP groupName consumerName</code> 读取消息,但是没有给 Stream 发送 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">XACK</code> 命令,消息依然保留。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">比如查看 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">bossStream</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">XPENDING bossStream 青龙门<br>1) (<span style="color: #e6c07b;line-height: 26px;">integer</span>) 2<br>2) <span style="color: #98c379;line-height: 26px;">"1645957821396-0"</span><br>3) <span style="color: #98c379;line-height: 26px;">"1645957838700-0"</span><br>4) 1) 1) <span style="color: #98c379;line-height: 26px;">"consumer1"</span><br> 2) <span style="color: #98c379;line-height: 26px;">"1"</span><br> 2) 1) <span style="color: #98c379;line-height: 26px;">"consumer2"</span><br> 2) <span style="color: #98c379;line-height: 26px;">"1"</span><br></code></pre> <ol data-tool="mdnice编辑器" style="font-size: 15px;margin-top: 8px;margin-bottom: 8px;padding-left: 20px;color: rgb(255, 191, 82);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">1)</code>未确认消息条数; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;letter-spacing: 0.1em;word-spacing: 0.1em;color: rgba(0, 0, 0, 0.55);"> <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">2) ~ 3)</code>青龙门中所有消费者读取的消息最小和最大 ID; </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">查看 <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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">consumer1</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">XPENDING bossStream 青龙门 - + 10 consumer1<br>1) 1) <span style="color: #98c379;line-height: 26px;">"1645957821396-0"</span><br> 2) <span style="color: #98c379;line-height: 26px;">"consumer1"</span><br> 3) (<span style="color: #e6c07b;line-height: 26px;">integer</span>) 3758384<br> 4) (<span style="color: #e6c07b;line-height: 26px;">integer</span>) 1<br></code></pre> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin-top: 20px;"><span style="font-size: 20px;color: #f48a00;display: inline-block;padding-left: 10px;border-left: 8px solid #ffbf52;">ACK 确认</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">所以当接收到消息并且消费成功以后,我们需要手动 ACK 通知 Streams,这条消息就会被删除了。命令如下:</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">XACK bossStream 青龙门 1645957821396-0 1645957838700-0<br>(<span style="color: #e6c07b;line-height: 26px;">integer</span>) 2<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">语法如下:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);"><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: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">XACK key group-key ID [ID ...]</code></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">消费确认增加了消息的可靠性,一般在业务处理完成之后,需要执行 ack 确认消息已经被消费完成,整个流程的执行如下图所示:</p> <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.532258064516129" src="/upload/b60230ae1e00fe132c223a0afeed2939.png" data-type="png" data-w="806" style="border-radius: 5px;display: block;margin-right: 10px;margin-bottom: auto;margin-left: 10px;width: 100%;height: 100%;object-fit: contain;"> <figcaption style="margin-top: 5px;font-size: 13px;color: rgba(0, 0, 0, 0.55);text-align: right;"> Stream 整体流程 </figcaption> </figure> <h1 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 24px;text-align: center;line-height: 95px;margin-top: 10px;margin-bottom: 10px;"><span style="font-size: 22px;color: #f48a00;border-bottom: 2px solid #ffbf52;">使用 Redisson 实战</span></h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">使用 maven 添加依赖</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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="line-height: 26px;"><<span style="color: #e06c75;line-height: 26px;">dependency</span>></span><br> <span style="line-height: 26px;"><<span style="color: #e06c75;line-height: 26px;">groupId</span>></span>org.redisson<span style="line-height: 26px;"></<span style="color: #e06c75;line-height: 26px;">groupId</span>></span><br> <span style="line-height: 26px;"><<span style="color: #e06c75;line-height: 26px;">artifactId</span>></span>redisson-spring-boot-starter<span style="line-height: 26px;"></<span style="color: #e06c75;line-height: 26px;">artifactId</span>></span><br> <span style="line-height: 26px;"><<span style="color: #e06c75;line-height: 26px;">version</span>></span>3.16.7<span style="line-height: 26px;"></<span style="color: #e06c75;line-height: 26px;">version</span>></span><br><span style="line-height: 26px;"></<span style="color: #e06c75;line-height: 26px;">dependency</span>></span><br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-top: 5px;margin-bottom: 5px;line-height: 1.75;letter-spacing: 0.1em;word-spacing: 0.1em;text-align: justify;color: rgba(0, 0, 0, 0.85);">添加 Redis 配置,码哥的 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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #d19a66;line-height: 26px;">spring:</span><br> <span style="color: #d19a66;line-height: 26px;">application:</span><br> <span style="color: #d19a66;line-height: 26px;">name:</span> <span style="color: #98c379;line-height: 26px;">redission</span><br> <span style="color: #d19a66;line-height: 26px;">redis:</span><br> <span style="color: #d19a66;line-height: 26px;">host:</span> <span style="color: #d19a66;line-height: 26px;">127.0.0.1</span><br> <span style="color: #d19a66;line-height: 26px;">port:</span> <span style="color: #d19a66;line-height: 26px;">6379</span><br> <span style="color: #d19a66;line-height: 26px;">ssl:</span> <span style="color: #56b6c2;line-height: 26px;">false</span><br></code></pre> <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;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #61aeee;line-height: 26px;">@Slf</span>4j<br><span style="color: #61aeee;line-height: 26px;">@Service</span><br><span style="color: #c678dd;line-height: 26px;">public</span> <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">class</span> <span style="color: #e6c07b;line-height: 26px;">QueueService</span> </span>{<br><br> <span style="color: #61aeee;line-height: 26px;">@Autowired</span><br> <span style="color: #c678dd;line-height: 26px;">private</span> RedissonClient redissonClient;<br><br> <span style="color: #5c6370;font-style: italic;line-height: 26px;">/**<br> * 发送消息到队列<br> *<br> * <span style="color: #c678dd;line-height: 26px;">@param</span> message<br> */</span><br> <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span> <span style="color: #c678dd;line-height: 26px;">void</span> <span style="color: #61aeee;line-height: 26px;">sendMessage</span><span style="line-height: 26px;">(String message)</span> </span>{<br> RStream<String, String> stream = redissonClient.getStream(<span style="color: #98c379;line-height: 26px;">"sensor#4921"</span>);<br> stream.add(<span style="color: #98c379;line-height: 26px;">"speed"</span>, <span style="color: #98c379;line-height: 26px;">"19"</span>);<br> stream.add(<span style="color: #98c379;line-height: 26px;">"velocity"</span>, <span style="color: #98c379;line-height: 26px;">"39%"</span>);<br> stream.add(<span style="color: #98c379;line-height: 26px;">"temperature"</span>, <span style="color: #98c379;line-height: 26px;">"10C"</span>);<br> }<br><br> <span style="color: #5c6370;font-style: italic;line-height: 26px;">/**<br> * 消费者消费消息<br> *<br> * <span style="color: #c678dd;line-height: 26px;">@param</span> message<br> */</span><br> <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span> <span style="color: #c678dd;line-height: 26px;">void</span> <span style="color: #61aeee;line-height: 26px;">consumerMessage</span><span style="line-height: 26px;">(String message)</span> </span>{<br> RStream<String, String> stream = redissonClient.getStream(<span style="color: #98c379;line-height: 26px;">"sensor#4921"</span>);<br><br> stream.createGroup(<span style="color: #98c379;line-height: 26px;">"sensors_data"</span>, StreamMessageId.ALL);<br><br> Map<StreamMessageId, Map<String, String>> messages = stream.readGroup(<span style="color: #98c379;line-height: 26px;">"sensors_data"</span>, <span style="color: #98c379;line-height: 26px;">"consumer_1"</span>);<br> <span style="color: #c678dd;line-height: 26px;">for</span> (Map.Entry<StreamMessageId, Map<String, String>> entry : messages.entrySet()) {<br> Map<String, String> msg = entry.getValue();<br> System.out.println(msg);<br><br> stream.ack(<span style="color: #98c379;line-height: 26px;">"sensors_data"</span>, entry.getKey());<br> }<br><br> }<br><br>}<br><br></code></pre> </section>
作者:微信小助手
<p style="white-space: normal;"><span style="color: rgb(171, 25, 66);"><strong><span style="font-size: 15px;text-align: left;"></span></strong></span></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">在分布式系统中,由于 redis 分布式锁相对于更简单和高效,成为了分布式锁的首先,被我们用到了很多实际业务场景当中。</span></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">但不是说用了 redis 分布式锁,就可以高枕无忧了,如果没有用好或者用对,也会引来一些意想不到的问题。</span></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <section style="margin-bottom: 10px;outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;font-size: 15px;color: rgb(0, 122, 170);box-sizing: border-box !important;overflow-wrap: break-word !important;">今天我们就一起聊聊 redis 分布式锁的一些坑,给有需要的朋友一个参考:</span> </section> <figure data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"> <img class="rich_pages wxw-img" data-ratio="0.8096" src="/upload/64f3b31fac6395bb5cbf57e9748ea732.png" data-type="png" data-w="1250" style="margin-right: auto;margin-bottom: 25px;margin-left: auto;outline: 0px;display: block;border-radius: 4px;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"> </figure> <section style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section powered-by="xiumi.us" style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section powered-by="xiumi.us" style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;box-sizing: border-box;font-size: 16px;overflow-wrap: break-word !important;"> <section powered-by="xiumi.us" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;box-sizing: border-box;text-align: center;justify-content: center;overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;box-sizing: border-box;display: inline-block;width: auto;vertical-align: top;min-width: 10%;height: auto;border-bottom: 1px solid rgb(126, 169, 195);border-bottom-right-radius: 0px;line-height: 0;overflow-wrap: break-word !important;"> <section powered-by="xiumi.us" style="outline: 0px;max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section style="padding-right: 5px;padding-left: 5px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0px;line-height: 1.8;font-size: 15px;color: rgb(126, 169, 195);overflow-wrap: break-word !important;"> <p style="outline: 0px;max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;">非原子操作</strong></p> </section> </section> <section powered-by="xiumi.us" style="margin-bottom: -3px;outline: 0px;max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;box-sizing: border-box;display: inline-block;width: 5px;height: 5px;vertical-align: top;overflow: hidden;border-width: 0px;border-radius: 202px;border-style: none;border-color: rgb(62, 62, 62);background-color: rgb(126, 169, 195);overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> </section> </section> </section> </section> <p style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> </section> </section> </section> </section> </section> <h2 data-tool="mdnice编辑器" style="margin-bottom: 10px;outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">使用 redis 的分布式锁,我们首先想到的可能是 setNx 命令。</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></h2> <section style="outline: 0px;max-width: 100%;white-space: normal;background-color: rgb(255, 255, 255);font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;letter-spacing: 0px;font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <pre style="outline: 0px;max-width: 100%;font-size: inherit;color: inherit;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><code style="margin-right: 2px;margin-left: 2px;padding: 0.5em;outline: 0px;max-width: 100%;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);box-sizing: border-box !important;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"><span style="outline: 0px;max-width: 100%;font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">if</span> (jedis.setnx(lockKey, <span style="outline: 0px;max-width: 100%;font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">val</span>) == <span style="outline: 0px;max-width: 100%;font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">1</span>) {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> jedis.expire(lockKey, timeout);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> </section> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">容易,三下五除二,我们就可以把代码写好。</span><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">这段代码确实可以加锁成功,但你有没有发现什么问题?</span></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">加锁操作和后面的设置超时时间是分开的,并非原子操作。</span><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">假如加锁成功,但是设置超时时间失败了,该 lockKey 就变成永不失效。</span></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">假如在高并发场景中,有大量的 lockKey 加锁成功了,但不会失效,有可能直接导致 redis 内存空间不足。</span></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">那么,有没有保证原子性的加锁命令呢?</span><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">答案是:</span><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">有,请看下面。</span></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <section style="outline: 0px;max-width: 100%;box-sizing: border-box;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);font-size: 16px;overflow-wrap: break-word !important;"> <section powered-by="xiumi.us" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;box-sizing: border-box;text-align: center;justify-content: center;overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;box-sizing: border-box;display: inline-block;width: auto;vertical-align: top;min-width: 10%;height: auto;border-bottom: 1px solid rgb(126, 169, 195);border-bottom-right-radius: 0px;line-height: 0;overflow-wrap: break-word !important;"> <section powered-by="xiumi.us" style="outline: 0px;max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section style="padding-right: 5px;padding-left: 5px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0px;line-height: 1.8;font-size: 15px;color: rgb(126, 169, 195);overflow-wrap: break-word !important;"> <p style="outline: 0px;max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;">忘了释放锁</strong></p> </section> </section> <section powered-by="xiumi.us" style="margin-bottom: -3px;outline: 0px;max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;box-sizing: border-box;display: inline-block;width: 5px;height: 5px;vertical-align: top;overflow: hidden;border-width: 0px;border-radius: 202px;border-style: none;border-color: rgb(62, 62, 62);background-color: rgb(126, 169, 195);overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> </section> </section> </section> </section> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">上面说到使用 setNx 命令加锁操作和设置超时时间是分开的,并非原子操作。</span></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <section style="margin-bottom: 10px;outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">而在 redis 中还有 set 命令,该命令可以指定多个参数。</span> </section> <section style="outline: 0px;max-width: 100%;white-space: normal;background-color: rgb(255, 255, 255);font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;letter-spacing: 0px;font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <pre style="outline: 0px;max-width: 100%;font-size: inherit;color: inherit;line-height: inherit;box-sizing: border-box !important;overflow-wrap: break-word !important;"><code style="margin-right: 2px;margin-left: 2px;padding: 0.5em;outline: 0px;max-width: 100%;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);box-sizing: border-box !important;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;">String result = jedis.<span style="outline: 0px;max-width: 100%;font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">set</span>(lockKey, requestId, <span style="outline: 0px;max-width: 100%;font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">"NX"</span>, <span style="outline: 0px;max-width: 100%;font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">"PX"</span>, expireTime);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">if</span> (<span style="outline: 0px;max-width: 100%;font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">"OK"</span>.equals(result)) {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">return</span> <span style="outline: 0px;max-width: 100%;font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">true</span>;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">return</span> <span style="outline: 0px;max-width: 100%;font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">false</span>;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> </section> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;color: rgb(0, 122, 170);box-sizing: border-box !important;overflow-wrap: break-word !important;">其中:</span></p> <ul class="list-paddingleft-2" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);overflow-wrap: break-word !important;"> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><p style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">lockKey:锁的标识</span></p></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><p style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">requestId:请求 id</span></p></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><p style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">NX:只在键不存在时,才对键进行设置操作。</span></p></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><p style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">PX:设置键的过期时间为 millisecond 毫秒。</span></p></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><p style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">expireTime:过期时间</span></p></li> </ul> <section style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"> <br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> </section> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">set 命令是原子操作,加锁和设置超时时间,一个命令就能轻松搞定。</span><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">nice!</span></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">使用 set 命令加锁,表面上看起来没有问题。但如果仔细想想,加锁之后,每次都要达到了超时时间才释放锁,会不会有点不合理?加锁后,如果不及时释放锁,会有很多问题。</span></p> <p data-tool="mdnice编辑器" style="outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB",
作者:微信小助手
<section data-mpa-template="t" mpa-from-tpl="t" data-mpa-powered-by="yiban.io"> <br> </section> <p style="text-align: center;padding: 0px 0.5em;"><img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="578" data-cropsely1="0" data-cropsely2="325" data-galleryid="" data-ratio="0.562037037037037" data-s="300,640" src="/upload/e32603bbbf8c59922b85c27b2263dcb6.jpg" data-type="jpeg" data-w="1080" style="width: 578px;height: 325px;border-radius: 3px;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"></p> <p style="text-align: center;"><br></p> <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, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", 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="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==&action=getalbum&album_id=2042874937312346114&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);">第十篇</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==&mid=2247493854&idx=1&sn=4b3fb7f7e17a76000733899f511ef915&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==&mid=2247496653&idx=1&sn=7185077b3bdc1d094aef645d677ec472&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==&mid=2247496772&idx=1&sn=8a88b998920bb9b665f52320cf94d9c7&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==&mid=2247497371&idx=1&sn=df5aa872452970f5f46efff5fc777b34&scene=21#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">阿里面试败北:5种微服务注册中心如何选型?这几�