文章列表

面试官:抛开Spring来说,如何自己实现Spring AOP?

作者:微信小助手

<h1 data-tool="mdnice编辑器"><span style="color: rgb(0, 82, 255);"><strong><span style="color: rgb(0, 82, 255);font-size: 20px;">| 引言</span></strong></span></h1> <p data-tool="mdnice编辑器" style="margin: 1em 4px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.75px;text-align: left;white-space: normal;background-color: rgb(255, 255, 255);font-size: 16px;line-height: 26px;color: black;box-sizing: border-box !important;overflow-wrap: break-word !important;">翻开<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;max-width: 100%;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;

十九招,让你写出好代码!

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;" data-mpa-powered-by="yiban.io"> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在JAVA中好的代码可以带来性能的提升,本文将讲解一些常用的代码优化招式,从而让我们在编码中保持好的编程习惯,让代码保持最优状态,当然也可以将这些招式引入到你们的代码评审中,让整个团队都写出更好的代码。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1.使用局部变量可避免在堆上分配</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">由于堆资源是多线程共享的,是垃圾回收器工作的主要区域,过多的对象会造成 GC 压力。可以通过局部变量的方式,将变量在栈上分配。这种方式变量会随着方法执行的完毕而销毁,能够减轻 GC 的压力。</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;">2.减少变量的作用范围</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">注意变量的作用范围,尽量减少对象的创建。如下面的代码,变量 a 每次进入方法都会创建,可以将它移动到 if 语句内部。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">test1</span><span style="line-height: 26px;">(String&nbsp;str)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">final</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;a&nbsp;=&nbsp;<span style="color: #986801;line-height: 26px;">100</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(!StringUtils.isEmpty(str))&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;b&nbsp;=&nbsp;a&nbsp;*&nbsp;a;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">3.访问静态变量直接使用类名</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">有的同学习惯使用对象访问静态变量,这种方式多了一步寻址操作,需要先找到变量对应的类,再找到类对应的变量,如下面的代码:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">StaticCall</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">final</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;A&nbsp;=&nbsp;<span style="color: #986801;line-height: 26px;">1</span>;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">test</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="color: #a626a4;line-height: 26px;">this</span>.A);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(StaticCall.A);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">对应的字节码为:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">test</span><span style="line-height: 26px;">()</span></span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;descriptor:&nbsp;()V<br>&nbsp;&nbsp;&nbsp;&nbsp;flags:<br>&nbsp;&nbsp;&nbsp;&nbsp;Code:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stack=<span style="color: #986801;line-height: 26px;">2</span>,&nbsp;locals=<span style="color: #986801;line-height: 26px;">1</span>,&nbsp;args_size=<span style="color: #986801;line-height: 26px;">1</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">0</span>:&nbsp;getstatic&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#<span style="color: #986801;line-height: 26px;">2</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;Field&nbsp;java/lang/System.out:Ljava/io/PrintStream;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">3</span>:&nbsp;aload_0<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">4</span>:&nbsp;pop<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">5</span>:&nbsp;iconst_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">6</span>:&nbsp;invokevirtual&nbsp;#<span style="color: #986801;line-height: 26px;">3</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;Method&nbsp;java/io/PrintStream.println:(I)V</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">9</span>:&nbsp;getstatic&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#<span style="color: #986801;line-height: 26px;">2</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;Field&nbsp;java/lang/System.out:Ljava/io/PrintStream;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">12</span>:&nbsp;iconst_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">13</span>:&nbsp;invokevirtual&nbsp;#<span style="color: #986801;line-height: 26px;">3</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;Method&nbsp;java/io/PrintStream.println:(I)V</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">16</span>:&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LineNumberTable:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;line&nbsp;<span style="color: #986801;line-height: 26px;">5</span>:&nbsp;<span style="color: #986801;line-height: 26px;">0</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;line&nbsp;<span style="color: #986801;line-height: 26px;">6</span>:&nbsp;<span style="color: #986801;line-height: 26px;">9</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;line&nbsp;<span style="color: #986801;line-height: 26px;">7</span>:&nbsp;<span style="color: #986801;line-height: 26px;">16</span><br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">可以看到使用 this 的方式多了一个步骤。</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;">4.字符串拼接使用 StringBuilder</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">字符串拼接,使用 StringBuilder 或者 StringBuffer,不要使用 + 号。比如下面这段代码,在循环中拼接了字符串。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;String&nbsp;<span style="color: #4078f2;line-height: 26px;">test</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;str&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"-1"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">for</span>&nbsp;(<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;i&nbsp;=&nbsp;<span style="color: #986801;line-height: 26px;">0</span>;&nbsp;i&nbsp;&lt;&nbsp;<span style="color: #986801;line-height: 26px;">10</span>;&nbsp;i++)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;str&nbsp;+=&nbsp;i;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;str;<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">从下面对应的字节码内容可以看出,它在每个循环里都创建了一个 StringBuilder 对象。所以,我们在平常的编码中,显式地创建一次即可。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">&nbsp;<span style="color: #986801;line-height: 26px;">5</span>:&nbsp;iload_2<br>&nbsp;<span style="color: #986801;line-height: 26px;">6</span>:&nbsp;bipush&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">10</span><br>&nbsp;<span style="color: #986801;line-height: 26px;">8</span>:&nbsp;if_icmpge&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">36</span><br><span style="color: #986801;line-height: 26px;">11</span>:&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#<span style="color: #986801;line-height: 26px;">3</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;class&nbsp;java/lang/StringBuilder</span><br><span style="color: #986801;line-height: 26px;">14</span>:&nbsp;dup<br><span style="color: #986801;line-height: 26px;">15</span>:&nbsp;invokespecial&nbsp;#<span style="color: #986801;line-height: 26px;">4</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;Method&nbsp;java/lang/StringBuilder."&lt;init&gt;":()V</span><br><span style="color: #986801;line-height: 26px;">18</span>:&nbsp;aload_1<br><span style="color: #986801;line-height: 26px;">19</span>:&nbsp;invokevirtual&nbsp;#<span style="color: #986801;line-height: 26px;">5</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;Method&nbsp;java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;</span><br><span style="color: #986801;line-height: 26px;">22</span>:&nbsp;iload_2<br><span style="color: #986801;line-height: 26px;">23</span>:&nbsp;invokevirtual&nbsp;#<span style="color: #986801;line-height: 26px;">6</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;Method&nbsp;java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;</span><br><span style="color: #986801;line-height: 26px;">26</span>:&nbsp;invokevirtual&nbsp;#<span style="color: #986801;line-height: 26px;">7</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;Method&nbsp;java/lang/StringBuilder.toString:()Ljava/lang/String;</span><br><span style="color: #986801;line-height: 26px;">29</span>:&nbsp;astore_1<br><span style="color: #986801;line-height: 26px;">30</span>:&nbsp;iinc&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">2</span>,&nbsp;<span style="color: #986801;line-height: 26px;">1</span><br><span style="color: #986801;line-height: 26px;">33</span>:&nbsp;goto&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">5</span><br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">5.重写对象的 HashCode,不要简单地返回固定值</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在代码 review 的时候,我发现有开发重写 HashCode 和 Equals 方法时,会把 HashCode 的值返回固定的 0,而这样做是不恰当的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当这些对象存入 HashMap 时,性能就会非常低,因为 HashMap 是通过 HashCode 定位到 Hash 槽,有冲突的时候,才会使用链表或者红黑树组织节点。固定地返回 0,相当于把 Hash 寻址功能给废除了。</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;">6.HashMap 等集合初始化的时候,指定初始值大小</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这样的对象有很多,比如 ArrayList,StringBuilder 等,通过指定初始值大小可减少扩容造成的性能损耗。</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;">7.遍历 Map 的时候,使用 EntrySet 方法</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">使用 EntrySet 方法,可以直接返回 set 对象,直接拿来用即可;而使用 KeySet 方法,获得的是key 的集合,需要再进行一次 get 操作,多了一个操作步骤。所以更推荐使用 EntrySet 方式遍历 Map。</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;">8.不要在多线程下使用同一个 Random</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Random 类的 seed 会在并发访问的情况下发生竞争,造成性能降低,建议在多线程环境下使用 ThreadLocalRandom 类。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在 Linux 上,通过加入 JVM 配置 <span style="font-weight: 700;color: rgb(248, 57, 41);">-Djava.security.egd=file:/dev/./urandom</span>,使用 urandom 随机生成器,在进行随机数获取时,速度会更快。</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;">9.自增推荐使用 LongAddr</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">自增运算可以通过 synchronized 和 volatile 的组合,或者也可以使用原子类(比如 AtomicLong)。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">后者的速度比前者要高一些,AtomicLong 使用 CAS 进行比较替换,在线程多的情况下会造成过多无效自旋,所以可以使用 LongAdder 替换 AtomicLong 进行进一步的性能提升。</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;">10.不要使用异常控制程序流程</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">异常,是用来了解并解决程序中遇到的各种不正常的情况,它的实现方式比较昂贵,比平常的条件判断语句效率要低很多。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这是因为异常在字节码层面,需要生成一个如下所示的异常表(Exception table),多了很多判断步骤。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">Exception&nbsp;table:<br>&nbsp;&nbsp;&nbsp;&nbsp;from&nbsp;&nbsp;&nbsp;&nbsp;to&nbsp;&nbsp;target&nbsp;type<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">7</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">17</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">20</span>&nbsp;&nbsp;&nbsp;any<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">20</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">23</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">20</span>&nbsp;&nbsp;&nbsp;any<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">所以,尽量不要使用异常控制程序流程。</p> <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;">11.不要在循环中使用 try catch</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">道理与上面类似,很多文章介绍,不要把异常处理放在循环里,而应该把它放在最外层,但实际测试情况表明这两种方式性能相差并不大。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">既然性能没什么差别,那么就推荐根据业务的需求进行编码。比如,循环遇到异常时,不允许中断,也就是允许在发生异常的时候能够继续运行下去,那么异常就只能在 for 循环里进行处理。</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;">12.不要捕捉 RuntimeException</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Java 异常分为两种,一种是可以通过预检查机制避免的 RuntimeException;另外一种就是普通异常。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">其中,RuntimeException 不应该通过 catch 语句去捕捉,而应该使用编码手段进行规避。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如下面的代码,list 可能会出现数组越界异常。是否越界是可以通过代码提前判断的,而不是等到发生异常时去捕捉。提前判断这种方式,代码会更优雅,效率也更高。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//BAD</span><br><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;String&nbsp;<span style="color: #4078f2;line-height: 26px;">test1</span><span style="line-height: 26px;">(List&lt;String&gt;&nbsp;list,&nbsp;<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;index)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">try</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;list.get(index);<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">catch</span>&nbsp;(IndexOutOfBoundsException&nbsp;ex)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br><br><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//GOOD</span><br><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;String&nbsp;<span style="color: #4078f2;line-height: 26px;">test2</span><span style="line-height: 26px;">(List&lt;String&gt;&nbsp;list,&nbsp;<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;index)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(index&nbsp;&gt;=&nbsp;list.size()&nbsp;||&nbsp;index&nbsp;&lt;&nbsp;<span style="color: #986801;line-height: 26px;">0</span>)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;list.get(index);<br>}<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">13.合理使用 PreparedStatement</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">PreparedStatement 使用预编译对 SQL 的执行进行提速,大多数数据库都会努力对这些能够复用的查询语句进行预编译优化,并能够将这些编译结果缓存起来。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这样等到下次用到的时候,就可以很快进行执行,也就少了一步对 SQL 的解析动作。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">PreparedStatement 还能提高程序的安全性,能够有效防止 SQL 注入。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">但如果你的程序每次 SQL 都会变化,不得不手工拼接一些数据,那么 PreparedStatement 就失去了它的作用,反而使用普通的 Statement 速度会更快一些。</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;">14.日志打印的注意事项</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">我们平常会使用 debug 输出一些调试信息,然后在线上关掉它。如下代码:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">logger.debug(<span style="color: #50a14f;line-height: 26px;">"xjjdog:"</span>+&nbsp;topic&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">"&nbsp;&nbsp;is&nbsp;&nbsp;awesome"</span>&nbsp;&nbsp;);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">程序每次运行到这里,都会构造一个字符串,不管你是否把日志级别调试到 INFO 还是 WARN,这样效率就会很低。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">可以在每次打印之前都使用 isDebugEnabled 方法判断一下日志级别,代码如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">if</span>(logger.isDebugEnabled())&nbsp;{&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;logger.debug(<span style="color: #50a14f;line-height: 26px;">"xjjdog:"</span>+&nbsp;topic&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">"&nbsp;&nbsp;is&nbsp;&nbsp;awesome"</span>&nbsp;&nbsp;);<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">使用占位符的方式,也可以达到相同的效果,就不用手动添加 isDebugEnabled 方法了,代码也优雅得多。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">logger.debug(<span style="color: #50a14f;line-height: 26px;">"xjjdog:{}&nbsp;&nbsp;is&nbsp;&nbsp;awesome"</span>&nbsp;&nbsp;,topic);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">对于业务系统来说,日志对系统的性能影响非常大,不需要的日志,尽量不要打印,避免占用 I/O 资源。</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;">15.减少事务的作用范围</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如果的程序使用了事务,那一定要注意事务的作用范围,尽量以最快的速度完成事务操作。这是因为,事务的隔离性是使用锁实现的。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #4078f2;line-height: 26px;">@Transactional</span>&nbsp;<br><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">test</span><span style="line-height: 26px;">(String&nbsp;id)</span></span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;value&nbsp;=&nbsp;rpc.getValue(id);&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//高耗时</span><br>&nbsp;&nbsp;&nbsp;&nbsp;testDao.update(sql,value);<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如上面的代码,由于 rpc 服务耗时高且不稳定,就应该把它移出到事务之外,改造如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">test</span><span style="line-height: 26px;">(String&nbsp;id)</span></span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;value&nbsp;=&nbsp;rpc.getValue(id);&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//高耗时</span><br>&nbsp;&nbsp;&nbsp;&nbsp;testDao(value);<br>}<br><span style="color: #4078f2;line-height: 26px;">@Transactional</span>&nbsp;<br><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">testDao</span><span style="line-height: 26px;">(String&nbsp;value)</span></span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;testDao.update(value);<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">这里有一点需要注意的地方,由于 SpringAOP 的原因,@Transactional 注解只能用到 public 方法上,如果用到 private 方法上,将会被忽略。</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;">16.使用位移操作替代乘除法</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">计算机是使用二进制表示的,位移操作会极大地提高性能。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">&lt;&lt;&nbsp;左移相当于乘以&nbsp;<span style="color: #986801;line-height: 26px;">2</span>;<br>&gt;&gt;&nbsp;右移相当于除以&nbsp;<span style="color: #986801;line-height: 26px;">2</span>;<br>&gt;&gt;&gt;&nbsp;无符号右移相当于除以&nbsp;<span style="color: #986801;line-height: 26px;">2</span>,但它会忽略符号位,空位都以&nbsp;<span style="color: #986801;line-height: 26px;">0</span>&nbsp;补齐。<br></code></pre> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;a&nbsp;=&nbsp;<span style="color: #986801;line-height: 26px;">2</span>;<br><span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;b&nbsp;=&nbsp;(a++)&nbsp;&lt;&lt;&nbsp;(++a)&nbsp;+&nbsp;(++a);<br>System.out.println(b);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">注意:位移操作的优先级非常低,所以上面的代码,输出是 1024。</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;">17.不要打印大集合或者使用大集合的 toString 方法</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">有的开发喜欢将集合作为字符串输出到日志文件中,这个习惯是非常不好的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">拿 ArrayList 来说,它需要遍历所有的元素来迭代生成字符串。在集合中元素非常多的情况下,这不仅会占用大量的内存空间,执行效率也非常慢。我曾经就遇到过这种批量打印方式造成系统性能直线下降的实际案例。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">下面这段代码,就是 ArrayList 的 toString 方法。它需要生成一个迭代器,然后把所有的元素内容拼接成一个字符串,非常浪费空间。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;String&nbsp;<span style="color: #4078f2;line-height: 26px;">toString</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;Iterator&lt;E&gt;&nbsp;it&nbsp;=&nbsp;iterator();<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(!&nbsp;it.hasNext())<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #50a14f;line-height: 26px;">"[]"</span>;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;StringBuilder&nbsp;sb&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;StringBuilder();<br>&nbsp;&nbsp;&nbsp;&nbsp;sb.append(<span style="color: #50a14f;line-height: 26px;">'['</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">for</span>&nbsp;(;;)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;E&nbsp;e&nbsp;=&nbsp;it.next();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sb.append(e&nbsp;==&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>&nbsp;?&nbsp;<span style="color: #50a14f;line-height: 26px;">"(this&nbsp;Collection)"</span>&nbsp;:&nbsp;e);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(!&nbsp;it.hasNext())<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;sb.append(<span style="color: #50a14f;line-height: 26px;">']'</span>).toString();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sb.append(<span style="color: #50a14f;line-height: 26px;">','</span>).append(<span style="color: #50a14f;line-height: 26px;">'&nbsp;'</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">18.程序中少用反射</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">反射的功能很强大,但它是通过解析字节码实现的,性能就不是很理想。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">现实中有很多对反射的优化方法,比如把反射执行的过程(比如 Method)缓存起来,使用复用来加快反射速度。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Java 7 之后,加入了新的包 java.lang.invoke,同时加入了新的 JVM 字节码指令 invokedynamic,用来支持从 JVM 层面,直接通过字符串对目标方法进行调用。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如果你对性能有非常苛刻的要求,则使用 invoke 包下的 MethodHandle 对代码进行着重优化,但它的编程不如反射方便,在平常的编码中,反射依然是首选。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">下面是一个使用 MethodHandle 编写的代码实现类。它可以完成一些动态语言的特性,通过方法名称和传入的对象主体,进行不同的调用,而 Bike 和 Man 类,可以是没有任何关系的。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;java.lang.invoke.MethodHandle;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;java.lang.invoke.MethodHandles;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;java.lang.invoke.MethodType;<br><br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">MethodHandleDemo</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">Bike</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">String&nbsp;<span style="color: #4078f2;line-height: 26px;">sound</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #50a14f;line-height: 26px;">"ding&nbsp;ding"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">Animal</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">String&nbsp;<span style="color: #4078f2;line-height: 26px;">sound</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #50a14f;line-height: 26px;">"wow&nbsp;wow"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">Man</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">extends</span>&nbsp;<span style="color: #c18401;line-height: 26px;">Animal</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">String&nbsp;<span style="color: #4078f2;line-height: 26px;">sound</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #50a14f;line-height: 26px;">"hou&nbsp;hou"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">String&nbsp;<span style="color: #4078f2;line-height: 26px;">sound</span><span style="line-height: 26px;">(Object&nbsp;o)</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">throws</span>&nbsp;Throwable&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MethodHandles.Lookup&nbsp;lookup&nbsp;=&nbsp;MethodHandles.lookup();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MethodType&nbsp;methodType&nbsp;=&nbsp;MethodType.methodType(String<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>)</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MethodHandle&nbsp;methodHandle&nbsp;=&nbsp;lookup.findVirtual(o.getClass(),&nbsp;<span style="color: #50a14f;line-height: 26px;">"sound"</span>,&nbsp;methodType);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;obj&nbsp;=&nbsp;(String)&nbsp;methodHandle.invoke(o);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;obj;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">main</span><span style="line-height: 26px;">(String[]&nbsp;args)</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">throws</span>&nbsp;Throwable&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;str&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;MethodHandleDemo().sound(<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;Bike());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(str);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;str&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;MethodHandleDemo().sound(<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;Animal());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(str);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;str&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;MethodHandleDemo().sound(<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;Man());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(str);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">19.正则表达式可以预先编译,加快速度</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Java 的正则表达式需要先编译再使用,典型代码如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">Pattern&nbsp;pattern&nbsp;=&nbsp;Pattern.compile({pattern});<br>Matcher&nbsp;pattern&nbsp;=&nbsp;pattern.matcher({content});<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Pattern 编译非常耗时,它的 Matcher 方法是线程安全的,每次调用方法这个方法都会生成一个新的 Matcher 对象。所以,一般 Pattern 初始化一次即可,可以作为类的静态成员变量。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><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, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;">= </section> </section> </section>

阿里二面:怎么解决MySQL死锁问题的?

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding-right: 10px;padding-left: 10px;overflow-wrap: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;line-height: 1.6;letter-spacing: 0.034em;color: rgb(63, 63, 63);font-size: 16px;" data-mpa-powered-by="yiban.io"> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(251, 249, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(53, 179, 120);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">咱们使用 MySQL 大概率上都会遇到死锁问题,这实在是个令人非常头痛的问题。本文将会对死锁进行相应介绍,对常见的死锁案例进行相关分析与探讨,以及如何去尽可能避免死锁给出一些建议。</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">话不多说,开整!</code></p> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/7VkkuTzAZPp08uy3ZghjeKeEiae1ooyibiczmsxyIeJTTHHibthKSlb9JBAwfZmGH8zRI63HogiazWAicZZr2Fy3wm8Q/640?wx_fmt=png&quot;);background-size: 100% 100%;background-repeat: no-repeat;display: inline-block;width: 16px;height: 15px;line-height: 15px;margin-bottom: -1px;"></span><span style="display: none;"></span><span style="font-size: 17px;display: inline-block;margin-left: 8px;color: rgb(72, 179, 120);">什么是死锁</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">死锁是并发系统中常见的问题,同样也会出现在数据库MySQL的并发读写请求场景中。当两个及以上的事务,双方都在等待对方释放已经持有的锁或因为加锁顺序不一致造成循环等待锁资源,就会出现“死锁”。常见的报错信息为 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">Deadlock found when trying to get lock...</code>。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">举例来说 A 事务持有 X1 锁 ,申请 X2 锁,B事务持有 X2 锁,申请 X1 锁。A 和 B 事务持有锁并且申请对方持有的锁进入循环等待,就造成了死锁。</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.446875" src="/upload/dc580981da320e46235e41b304353685.png" data-type="png" data-w="640" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">如上图,是右侧的四辆汽车资源请求产生了回路现象,即死循环,导致了死锁。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">从死锁的定义来看,MySQL 出现死锁的几个要素为:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 两个或者两个以上事务 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 每个事务都已经持有锁并且申请新的锁 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 锁资源同时只能被同一个事务持有或者不兼容 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 事务之间因为持有锁和申请锁导致彼此循环等待 </section></li> </ol> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/7VkkuTzAZPp08uy3ZghjeKeEiae1ooyibiczmsxyIeJTTHHibthKSlb9JBAwfZmGH8zRI63HogiazWAicZZr2Fy3wm8Q/640?wx_fmt=png&quot;);background-size: 100% 100%;background-repeat: no-repeat;display: inline-block;width: 16px;height: 15px;line-height: 15px;margin-bottom: -1px;"></span><span style="display: none;"></span><span style="font-size: 17px;display: inline-block;margin-left: 8px;color: rgb(72, 179, 120);">InnoDB 锁类型</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">为了分析死锁,我们有必要对 InnoDB 的锁类型有一个了解。<img class="rich_pages wxw-img" data-ratio="0.46875" src="/upload/5f135025d77c73b0f9b4e72392d31819.png" data-type="png" data-w="640" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">MySQL InnoDB 引擎实现了标准的<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">行级别锁:共享锁( S lock ) 和排他锁 ( X lock )</code></p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(251, 249, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(53, 179, 120);"> <ol style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 不同事务可以同时对同一行记录加 S 锁。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 如果一个事务对某一行记录加 X 锁,其他事务就不能加 S 锁或者 X 锁,从而导致锁等待。 </section></li> </ol> </blockquote> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">如果事务 T1 持有行 r 的 S 锁,那么另一个事务 T2 请求 r 的锁时,会做如下处理:</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(251, 249, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(53, 179, 120);"> <ol style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> T2 请求 S 锁立即被允许,结果 T1 T2 都持有 r 行的 S 锁 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> T2 请求 X 锁不能被立即允许 </section></li> </ol> </blockquote> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">如果 T1 持有 r 的 X 锁,那么 T2 请求 r 的 X、S 锁都不能被立即允许,T2 必须等待 T1 释放 X 锁才可以,因为 X 锁与任何的锁都不兼容。共享锁和排他锁的兼容性如下所示:<img class="rich_pages wxw-img" data-ratio="0.1782608695652174" src="/upload/30a2c55533646fd2576d416107b4ae1a.png" data-type="png" data-w="690" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"></p> <h4 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>间隙锁( gap lock )<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">间隙锁锁住一个间隙以防止插入。假设索引列有2, 4, 8 三个值,如果对 4 加锁,那么也会同时对(2,4)和(4,8)这两个间隙加锁。其他事务无法插入索引值在这两个间隙之间的记录。但是,间隙锁有个例外:</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(251, 249, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(53, 179, 120);"> <ol style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 如果索引列是唯一索引,那么只会锁住这条记录(只加行锁),而不会锁住间隙。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 对于联合索引且是唯一索引,如果 where 条件只包括联合索引的一部分,那么依然会加间隙锁。 </section></li> </ol> </blockquote> <h4 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>next-key lock<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">next-key lock 实际上就是 行锁+这条记录前面的 gap lock 的组合。假设有索引值10,11,13和 20,那么可能的 next-key lock 包括:</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(251, 249, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(53, 179, 120);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">(负无穷,10],(10,11],(11,13],(13,20],(20,正无穷)</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">在 RR 隔离级别下,InnoDB 使用 next-key lock 主要是防止<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">幻读</code>问题产生。</p> <h4 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>意向锁( Intention lock )<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">InnoDB 为了支持多粒度的加锁,允许行锁和表锁同时存在。为了支持在不同粒度上的加锁操作,InnoDB 支持了额外的一种锁方式,称之为意向锁( Intention Lock )。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁。意向锁分为两种:</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(251, 249, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(53, 179, 120);"> <ol style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 意向共享锁( IS ):事务有意向对表中的某些行加共享锁 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 意向排他锁( IX ):事务有意向对表中的某些行加排他锁 </section></li> </ol> </blockquote> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">由于 InnoDB 存储引擎支持的是行级别的锁,因此意向锁其实不会阻塞除全表扫描以外的任何请求。表级意向锁与行级锁的兼容性如下所示:<img class="rich_pages wxw-img" data-ratio="0.2753623188405797" src="/upload/e9fdcf52a2c800aeae801de1640e1dfa.png" data-type="png" data-w="690" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"></p> <h4 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>插入意向锁( Insert Intention lock )<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,即多个事务在相同的索引间隙插入时如果不是插入间隙中相同的位置就不需要互相等待。假设某列有索引值2,6,只要两个事务插入位置不同(如事务 A 插入3,事务 B 插入4),那么就可以同时插入。</p> <h4 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>锁模式兼容矩阵<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">横向是已持有锁,纵向是正在请求的锁:<img class="rich_pages wxw-img" data-ratio="0.28695652173913044" src="/upload/3edbbd33829dc2f60324fac61f611718.png" data-type="png" data-w="690" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"></p> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/7VkkuTzAZPp08uy3ZghjeKeEiae1ooyibiczmsxyIeJTTHHibthKSlb9JBAwfZmGH8zRI63HogiazWAicZZr2Fy3wm8Q/640?wx_fmt=png&quot;);background-size: 100% 100%;background-repeat: no-repeat;display: inline-block;width: 16px;height: 15px;line-height: 15px;margin-bottom: -1px;"></span><span style="display: none;"></span><span style="font-size: 17px;display: inline-block;margin-left: 8px;color: rgb(72, 179, 120);">阅读死锁日志</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">在进行具体案例分析之前,咱们先了解下如何去读懂死锁日志,尽可能地使用死锁日志里面的信息来帮助我们来解决死锁问题。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">后面测试用例的数据库场景如下:<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">MySQL 5.7 事务隔离级别为 RR</code></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">表结构和数据如下:<img class="rich_pages wxw-img" data-ratio="0.26956521739130435" src="/upload/3445dda96e2ab2ab43e79c58fc51b4e9.png" data-type="png" data-w="690" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">测试用例如下:<img class="rich_pages wxw-img" data-ratio="0.4109375" src="/upload/3da07295c8cbb1d8b92c29137578cd02.png" data-type="png" data-w="640" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">通过执行show engine innodb status 可以查看到最近一次死锁的日志。</p> <h4 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>日志分析如下:<span style="display: none;"></span></h4> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> ***** (1) TRANSACTION: TRANSACTION 2322, ACTIVE 6 sec starting index read </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">事务号为2322,活跃 6秒,starting index read 表示事务状态为根据索引读取数据。常见的其他状态有:<img class="rich_pages wxw-img" data-ratio="0.18985507246376812" src="/upload/2f643edbc3fe98cc4fceb6d7026363be.png" data-type="png" data-w="690" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">mysql tables in use 1</code> 说明当前的事务使用一个表。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">locked 1</code> 表示表上有一个表锁,对于 DML 语句为 LOCK_IX</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)</code></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">LOCK WAIT</code> 表示正在等待锁,<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">2 lock struct(s)</code> 表示 trx-&gt;trx_locks 锁链表的长度为2,每个链表节点代表该事务持有的一个锁结构,包括表锁,记录锁以及自增锁等。本用例中 2locks 表示 IX 锁和lock_mode X (Next-key lock)</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">1 row lock(s)</code> 表示当前事务持有的行记录锁/ gap 锁的个数。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">MySQL thread id 37, OS thread handle 140445500716800, query id 1234 127.0.0.1 root updating</code></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">MySQL thread id 37</code> 表示执行该事务的线程 ID 为 37 (即 show processlist; 展示的 ID )</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">delete from student where stuno=5</code> 表示事务1正在执行的 sql,比较难受的事情是 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">show engine innodb status</code> 是查看不到完整的 sql 的,通常显示当前正在等待锁的 sql。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">***** (1) WAITING FOR THIS LOCK TO BE GRANTED:</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2322 lock_mode X waiting</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">RECORD LOCKS 表示记录锁, 此条内容表示事务 1 正在等待表 student 上的 idx_stuno 的 X 锁,本案例中其实是 Next-Key Lock 。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">事务2的 log 和上面分析类似:</p> <ol start="2" data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> ***** (2) HOLDS THE LOCK(S): </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2321 lock_mode X</code></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">显示事务 2 的 insert into student(stuno,score) values(2,10) 持有了 a=5 的 Lock mode X</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">| LOCK_gap,不过我们从日志里面看不到事务2执行的 delete from student where stuno=5;</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">这点也是造成 DBA 仅仅根据日志难以分析死锁的问题的根本原因。</p> <ol start="2" data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> ***** (2) WAITING FOR THIS LOCK TO BE GRANTED: </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2321 lock_mode X locks gap before rec insert intention waiting</code></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">表示事务 2 的 insert 语句正在等待插入意向锁 lock_mode X locks gap before rec insert intention waiting ( LOCK_X + LOCK_REC_gap )</p> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/7VkkuTzAZPp08uy3ZghjeKeEiae1ooyibiczmsxyIeJTTHHibthKSlb9JBAwfZmGH8zRI63HogiazWAicZZr2Fy3wm8Q/640?wx_fmt=png&quot;);background-size: 100% 100%;background-repeat: no-repeat;display: inline-block;width: 16px;height: 15px;line-height: 15px;margin-bottom: -1px;"></span><span style="display: none;"></span><span style="font-size: 17px;display: inline-block;margin-left: 8px;color: rgb(72, 179, 120);">经典案例分析</span><span style="display: none;"></span></h3> <h4 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>案例一:事务并发 insert 唯一键冲突<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">表结构和数据如下所示:<img class="rich_pages wxw-img" data-ratio="0.21594202898550724" src="/upload/68851ce91666b02033a5ed36ce1b3b18.png" data-type="png" data-w="690" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"><img class="rich_pages wxw-img" data-ratio="0.7342342342342343" src="/upload/371e6df729463fdea386d18c13bc5616.png" data-type="png" data-w="444" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;">测试用例如下:<img class="rich_pages wxw-img" data-ratio="0.27246376811594203" src="/upload/8f6bc497c0f9c66ff4a7da0c50dcd1e7.png" data-type="png" data-w="690" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;">日志分析如下:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 事务 T2 insert into t7(id,a) values (26,10) 语句 insert 成功,持有 a=10 的 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">排他行锁( Xlocks rec but no gap )</code> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 事务 T1 insert into t7(id,a) values (30,10), 因为T2的第一条 insert 已经插入 a=10 的记录,事务 T1 insert a=10 则发生唯一键冲突,需要申请对冲突的唯一索引加上S Next-key Lock( 即 lock mode S waiting ) 这是一个 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">间隙锁</code>会申请锁住(,10],(10,20]之间的 gap 区域。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 事务 T2 insert into t7(id,a) values (40,9)该语句插入的 a=9 的值在事务 T1 申请的 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">gap 锁4-10之间</code>, 故需事务 T2 的第二条 insert 语句要等待事务 T1 的 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">S-Next-key Lock 锁</code>释放,在日志中显示 lock_mode X locks gap before rec insert intention waiting 。 </section></li> </ol> <h4 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>案例一:先 update 再 insert 的并发死锁问题<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">表结构如下,无数据:<img class="rich_pages wxw-img" data-ratio="0.24202898550724639" src="/upload/c3fe6a7f19664a860a4a15885c167e20.png" data-type="png" data-w="690" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;">测试用例如下:<img class="rich_pages wxw-img" data-ratio="0.5" src="/upload/af6aae427acd20aaaf8fcb4e500b2192.png" data-type="png" data-w="640" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;">死锁分析:<br>可以看到两个事务 update 不存在的记录,先后获得<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">间隙锁( gap 锁)</code>,gap 锁之间是兼容的所以在update环节不会阻塞。两者都持有 gap 锁,然后去竞争插入<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">意向锁</code>。当存在其他会话持有 gap 锁的时候,当前会话申请不了插入意向锁,导致死锁。</p> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/7VkkuTzAZPp08uy3ZghjeKeEiae1ooyibiczmsxyIeJTTHHibthKSlb9JBAwfZmGH8zRI63HogiazWAicZZr2Fy3wm8Q/640?wx_fmt=png&quot;);background-size: 100% 100%;background-repeat: no-repeat;display: inline-block;width: 16px;height: 15px;line-height: 15px;margin-bottom: -1px;"></span><span style="display: none;"></span><span style="font-size: 17px;display: inline-block;margin-left: 8px;color: rgb(72, 179, 120);">如何尽可能避免死锁</span><span style="display: none;"></span></h3> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 合理的设计索引,区分度高的列放到组合索引前面,使业务 SQL 尽可能通过索引 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">定位更少的行,减少锁竞争</code>。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 调整业务逻辑 SQL 执行顺序, 避免 update/delete 长时间持有锁的 SQL 在事务前面。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 避免 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">大事务</code>,尽量将大事务拆成多个小事务来处理,小事务发生锁冲突的几率也更小。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 以 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">固定的顺序</code>访问表和行。比如两个更新数据的事务,事务 A 更新数据的顺序为 1,2;事务 B 更新数据的顺序为 2,1。这样更可能会造成死锁。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 在并发比较高的系统中,不要显式加锁,特别是是在事务里显式加锁。如 select … for update 语句,如果是在事务里 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">(运行了 start transaction 或设置了autocommit 等于0)</code>,那么就会锁定所查找到的记录。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 尽量按 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">主键/索引</code>去查找记录,范围查找增加了锁冲突的可能性,也不要利用数据库做一些额外额度计算工作。比如有的程序会用到 “select … where … order by rand();”这样的语句,由于类似这样的语句用不到索引,因此将导致整个表的数据都被锁住。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 优化 SQL 和表设计,减少同时占用太多资源的情况。比如说, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">减少连接的表</code>,将复杂 SQL <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">分解</code>为多个简单的 SQL。 </section></li> </ol> <hr data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;height: 1px;border-width: initial;border-style: none;border-color: initial;text-align: center;background-image: linear-gradient(to right, rgba(93, 186, 133, 0), rgba(93, 186, 133, 0.75), rgba(93, 186, 133, 0));"> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">好了。今天就说到这了,我还会不断分享自己的所学所想,希望我们一起走在成功的道路上!</p> </section> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzkyNjI0MTYwNQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/7VkkuTzAZPpHc0kB2AWPsLvdd3oC9u1QZvXmHniaUnicetGssmNiaydZvKibR2zZWgEA1zJzPGQ61doIl2MtBolkHQ/0?wx_fmt=png" data-nickname="狼王编程" data-alias="" data-signature="狼王专注Java和架构领域,在coding的道路,一边学习一边分享,你可以和我聊篮球,也可以和我交流技术,只要你愿意,我们就有故事!" data-from="0"></mpprofile> <span style="background-color: rgb(253, 245, 13);color: rgb(73, 73, 72);font-weight: bold;letter-spacing: 1.5px;font-size: 15px;word-spacing: 2px;"></span> </section>

石墨文档Websocket百万长连接技术实践

作者:微信小助手

<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="MzA3ODIxNjYxNQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/Baq5lYpIw7WIvzVfHZ61VvJoaTzb7HDxtsJoicXicBJJ5uc9FrPG3eVztG6eBfUfdwIYmoqu8ibM5APCTDBUBPLrg/0?wx_fmt=png" data-nickname="架构文摘" data-alias="ArchDigest" data-signature="每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性能、高稳定)、大数据、机器学习、Java架构等各个热门领域。" data-from="0"></mpprofile> </section> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding: 8px 10px;line-height: 26px;font-size: 16px;letter-spacing: 0px;white-space: normal;color: black;word-break: break-word;word-wrap: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;"> <h1 data-tool="mdnice编辑器" style="padding: 8px 10px;line-height: 26px;font-size: 16px;letter-spacing: 0px;white-space: normal;color: black;word-break: break-word;word-wrap: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;"><span style="color: rgb(136, 136, 136);">内容简介:Web 服务端推送技术经过了长轮询、短轮询的发展,最终到 HTML5 标准带来的 WebSocket 规范逐步成为了目前业内主流技术方案。它使得消息推送、消息通知等功能的实现变得异常简单,那么百万级别连接下的 Websocket 网关该如何实践呢?本文整理自石墨文档资深工程师杜旻翔根据石墨websocket重构的实践经验。</span></h1> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;">1 引言</h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在石墨文档的业务中,如文档分享、评论、幻灯片演示和文档表格跟随等场景,涉及多客户端数据同步和服务端批量数据推送的需求,采用短轮询或长轮询的方式无法很好的满足服务端消息推送、消息通知的业务场景,因此选择业内的主流方案:基于 HTML5 标准定义的 WebSocket 规范。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">随着石墨文档的发展,现在日连接峰值达到了百万量级,日益增长的用户连接数和停止更新的架构设计导致了内存和 CPU 使用量急剧增长,因此我们考虑对网关进行重构,以适应发展需求。</p> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;">2 网关 1.0</h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">网关 1.0 是使用 Node.js 基于 Socket.IO 进行修改开发的版本,很好的满足了当时用户量级下的业务场景需求。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">2.1 架构</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">网关 1.0 版本架构设计图:<img class="rich_pages wxw-img" data-ratio="1.0759878419452888" src="/upload/bb009e07125d428574ca17122e621f23.png" data-type="png" data-w="329" style="display: block;margin-right: auto;margin-left: auto;"></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">网关 1.0 客户端连接流程:</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);"> 用户通过 NGINX 连接网关,该操作被业务服务感知; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 业务服务感知到用户连接后,会进行相关用户数据查询,再将消息 Pub 到 Redis; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 网关服务通过 Redis Sub 收到消息; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 查询网关集群中的用户会话数据,向客户端进行消息推送。 </section></li> </ol> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">2.2 痛点</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">虽然 1.0 版本的网关在线上运行良好,但是不能很好的支持后续业务的扩展,并且有以下几个问题需要解决:</p> <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);"> 资源消耗:Nginx 仅使用证书,大部分请求被透传,产生了一定的资源浪费,同时之前的 Node 网关性能不好,消耗大量的 CPU、内存。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 维护与观测:未接入石墨的监控体系,无法和现有监控告警联通,维护上存在一定的困难; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 业务耦合问题:业务服务与网关功能被集成到了同一个服务中,无法针对业务部分性能损耗进行针对性水平扩容,为了解决性能问题,以及后续的模块扩展能力,都需要进行服务解耦。 </section></li> </ul> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;">3 网关 2.0</h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">网关 2.0 需要解决很多问题:石墨文档内部有很多组件:文档、表格、幻灯片和表单等等。在 1.0 版本中组件对网关的业务调用可以通过:Redis、Kafka 和 HTTP 接口,来源不可查,管控困难。此外,从性能优化的角度考虑也需要对原有服务进行解耦合,将 1.0 版本网关拆分为网关功能部分和业务处理部分,网关功能部分为 WS-Gateway:集成用户鉴权、TLS 证书验证和 WebSocket 连接管理等;业务处理部分为 WS-API:组件服务直接与该服务进行 gRPC 通信。可针对具体的模块进行针对性扩容;服务重构加上 Nginx 移除,整体硬件消耗显著降低;服务整合到石墨监控体系。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">3.1 整体架构</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">网关 2.0 版本架构设计图:<img class="rich_pages wxw-img" data-ratio="1.0903954802259888" src="/upload/ce52234ff6fa1927102d48d7e2f57a0.png" data-type="png" data-w="354" style="display: block;margin-right: auto;margin-left: auto;"></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">网关 2.0 客户端连接流程:</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);"> 客户端与 WS-Gateway 服务通过握手流程建立 WebSocket 连接; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 连接建立成功后,WS-Gateway 服务将会话进行节点存储,将连接信息映射关系缓存到 Redis 中,并通过 Kafka 向 WS-API 推送客户端上线消息; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> WS-API 通过 Kafka 接收客户端上线消息及客户端上行消息; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> WS-API 服务预处理及组装消息,包括从 Redis 获取消息推送的必要数据,并进行完成消息推送的过滤逻辑,然后 Pub 消息到 Kafka; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> WS-Gateway 通过 Sub Kafka 来获取服务端需要返回的消息,逐个推送消息至客户端。 </section></li> </ol> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">3.2 握手流程</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">网络状态良好的情况下,完成如下图所示步骤 1 到步骤 6 之后,直接进入 WebSocket 流程;网络环境较差的情况下,WebSocket 的通信模式会退化成 HTTP 方式,客户端通过 POST 方式推送消息到服务端,再通过 GET 长轮询的方式从读取服务端返回数据。客户端初次请求服务端连接建立的握手流程:<img data-ratio="1.353846153846154" src="/upload/2bd4d456e62e17c4f57a83402c1e9a12.png" data-type="png" data-w="390" style="display: block;margin-right: auto;margin-left: auto;"></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);"> Client 发送 GET 请求尝试建立连接; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Server 返回相关连接数据,sid 为本次连接产生的唯一 Socket ID,后续交互作为凭证; </section></li> </ol> <blockquote data-tool="mdnice编辑器" style="border-top-style: none;border-right-style: none;border-bottom-style: none;font-size: 0.9em;overflow: auto;border-left-color: rgba(0, 0, 0, 0.4);background-color: rgba(0, 0, 0, 0.05);color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">{"sid":"xxx","upgrades":["websocket"],"pingInterval":xxx,"pingTimeout":xxx}</p> </blockquote> <ol start="3" 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);"> Client 携带步骤 2 中的 sid 参数再次请求; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Server 返回 40,表示请求接收成功; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Client 发送 POST 请求确认后期降级通路情况; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Server 返回 ok,此时第一阶段握手流程完成; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5p

Spring Cloud Security 整合 OAuth 2.0,从原理到实战一次说明白

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">本篇文章介绍一下<span style="font-weight: 700;color: rgb(248, 57, 41);">OAuth2.0</span>相关的知识点,并且手把手带大家搭建一个<span style="font-weight: 700;color: rgb(248, 57, 41);">认证授权中心</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">资源服务</span>进行<span style="font-weight: 700;color: rgb(248, 57, 41);">OAuth2.0</span>四种授权模式的验证,案例源码详细,一梭子带大家了解清楚。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">本篇文章的案例源码项目架构为:<span style="font-weight: 700;color: rgb(248, 57, 41);">Spring Boot + Spring Cloud Alibaba + Spring Security</span>&nbsp;。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="letter-spacing: 0.8px;word-spacing: 0.8px;">文章目录如下:</span></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="1.6497109826589595" src="/upload/2d942455dba22b890beadf1f7ceb9939.png" data-type="png" data-w="865" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">为什么需要OAuth2.0?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">编码永远都是为了解决生产中的问题,想要理解为什么需要OAuth2,当然要从实际生活出发。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">举个例子:小区的业主点了一份外卖,但是小区的门禁系统不给外卖人员进入,此时想要外卖员进入只能业主下来开门或者告知门禁的密码。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">密码告知外卖员岂不是每次都能凭密码进入小区了,这明显造成了<span style="font-weight: 700;color: rgb(248, 57, 41);">安全隐患</span>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">那么有没有一种方案:既能不泄露密码,也能让外卖小哥进入呢?</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">于是此时就想到了一个授权机制,分为以下几个步骤:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 门禁系统中新增一个 <span style="font-weight: 700;color: rgb(248, 57, 41);">授权按钮</span>,外卖小哥只需要点击授权按钮呼叫对应业主 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 业主收到小哥的呼叫,知道小哥正在要求授权,于是做出了 <span style="font-weight: 700;color: rgb(248, 57, 41);">应答授权</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 此时门禁系统弹出一个 <span style="font-weight: 700;color: rgb(248, 57, 41);">密码</span>(类似于 <span style="font-weight: 700;color: rgb(248, 57, 41);">access_token</span>), <span style="font-weight: 700;color: rgb(248, 57, 41);">有效期30分钟</span>,在30分钟内,小哥可以凭借这个密码进入小区。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 小哥输入密码进入小区 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">另外这个授权的密码不仅可以通过门禁,还可以通过楼下的门禁,这就非常类似于<span style="font-weight: 700;color: rgb(248, 57, 41);">网关</span>和<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);">令牌和密码的区别?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上述例子中令牌和密码的作用是一样的,都可以进入小区,但是存在以下几点差异:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">时效不同</span>:令牌一般都是存在过期时间的,比如30分钟后失效,这个是无法修改的,除非重新申请授权;而密码一般都是永久的,除非主人去修改 </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>:令牌的权限是有限的,比如上述例子中,小哥获取了令牌,能够打开小区的门禁、业主所在的楼下门禁,但是可能无法打开其它幢的门禁; </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>:业主可以撤销这个令牌的授权,一旦撤销了,这个令牌也就失效了,无法使用;但是密码一般不允许撤销。 </section></li> </ol> <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);">什么是OAuth2?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密<span style="font-weight: 700;color: rgb(248, 57, 41);">资源</span>(如头像、照片、视频等),而在这个过程中无需将<span style="font-weight: 700;color: rgb(248, 57, 41);">用户名</span>和<span style="font-weight: 700;color: rgb(248, 57, 41);">密码</span>提供给<span style="font-weight: 700;color: rgb(248, 57, 41);">第三方应用</span>。实现这一功能是通过提供一个令牌(<span style="font-weight: 700;color: rgb(248, 57, 41);">token</span>),而不是用户名和密码来访问他们存放在特定<span style="font-weight: 700;color: rgb(248, 57, 41);">服务提供者</span>的数据。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">采用令牌(token)的方式可以让用户灵活的对第三方应用授权或者收回权限。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">OAuth2 是 OAuth 协议的下一版本,但不向下兼容 <span style="font-weight: 700;color: rgb(248, 57, 41);">OAuth 1.0</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;">传统的 Web 开发登录认证一般都是基于 <span style="font-weight: 700;color: rgb(248, 57, 41);">session</span> 的,但是在<span style="font-weight: 700;color: rgb(248, 57, 41);">前后端分离</span>的架构中继续使用 session 就会有许多不便,因为移动端(Android、iOS、微信小程序等)要么不支持 <span style="font-weight: 700;color: rgb(248, 57, 41);">cookie</span>(微信小程序),要么使用非常不便,对于这些问题,使用 <span style="font-weight: 700;color: rgb(248, 57, 41);">OAuth2</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;">对于大家而言,我们在互联网应用中最常见的 OAuth2 应该就是各种第三方登录了,例如 <span style="font-weight: 700;color: rgb(248, 57, 41);">QQ 授权登录</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">微信授权登录</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">微博授权登录</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">GitHub 授权登录</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);">OAuth2.0的四种模式?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">OAuth2.0协议一共支持 4 种不同的授权模式:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">授权码模式</span>:常见的第三方平台登录功能基本都是使用这种模式。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">简化模式</span>:简化模式是不需要客户端服务器参与,直接在浏览器中向授权服务器申请令牌(token),一般如果网站是纯静态页面则可以采用这种方式。 </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>:密码模式是用户把用户名密码直接告诉客户端,客户端使用说这些信息向授权服务器申请令牌(token)。这需要用户对客户端高度信任,例如客户端应用和服务提供商就是同一家公司,自己做前后端分离登录就可以采用这种模式。 </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>:客户端模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请授权,严格来说,客户端模式并不能算作 OAuth 协议要解决的问题的一种解决方案,但是,对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用这种模式还是非常方便的。 </section></li> </ol> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1、授权码模式</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这种方式是最常用的流程,<span style="font-weight: 700;color: rgb(248, 57, 41);">安全性也最高</span>,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">令牌获取的流程如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5674846625766872" src="/upload/62ba765a6bc77907fdf82835292232e4.png" data-type="png" data-w="652" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> 授权码模式 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上图中涉及到两个角色,分别是客户端、认证中心,客户端负责拿令牌,认证中心负责发放令牌。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">但是不是所有客户端都有权限请求令牌的,需要事先在认证中心申请,比如微信并不是所有网站都能直接接入,而是要去微信后台开通这个权限。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">至少要提前向认证中心申请的几个参数如下:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">client_id</span>:客户端唯一id,认证中心颁发的唯一标识 </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);">client_secret</span>:客户端的秘钥,相当于密码 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">scope</span>:客户端的权限 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">redirect_uri</span>:授权码模式使用的跳转uri,需要事先告知认证中心。 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">1、请求授权码</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">客户端需要向认证中心拿到授权码,比如第三方登录使用微信,扫一扫登录那一步就是向微信的认证中心获取授权码。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">请求的url如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ42nP33ibvO1HOTebwA8JP5XzH7NB6Cia6VJxswE3iaLTeLWeXYenzbwhpPpibyyH4otcLrjrueTNXqk/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">/oauth/authorize?client_id=&amp;response_type=code&amp;scope=&amp;redirect_uri=<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上述这个url中携带的几个参数如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">client_id</span>:客户端的id,这个由认证中心分配,并不是所有的客户端都能随意接入认证中心 </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);">response_type</span>:固定值为 <span style="font-weight: 700;color: rgb(248, 57, 41);">code</span>,表示要求返回授权码。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">scope</span>:表示要求的授权范围,客户端的权限 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">redirect_uri</span>:跳转的uri,认证中心同意或者拒绝授权跳转的地址,如果同意会在uri后面携带一个 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">code=xxx</code>,这就是授权码 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">2、返回授权码</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">第1步请求之后,认证中心会要求登录、是否同意授权,用户同意授权之后直接跳转到<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">redirect_uri</code>(这个需要事先在认证中心申请配置),授权码会携带在这个地址后面,如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ42nP33ibvO1HOTebwA8JP5XzH7NB6Cia6VJxswE3iaLTeLWeXYenzbwhpPpibyyH4otcLrjrueTNXqk/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">http:<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//xxxx?code=NMoj5y</span><br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上述链接中的<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">NMoj5y</code>就是授权码了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">3、请求令牌</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;">客户端拿到授权码之后,直接携带授权码发送请求给认证中心获取令牌,请求的url如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ42nP33ibvO1HOTebwA8JP5XzH7NB6Cia6VJxswE3iaLTeLWeXYenzbwhpPpibyyH4otcLrjrueTNXqk/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">/oauth/token?<br>&nbsp;client_id=&amp;<br>&nbsp;client_secret=&amp;<br>&nbsp;grant_type=authorization_code&amp;<br>&nbsp;code=NMoj5y&amp;<br>&nbsp;redirect_uri=<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">相同的参数同上,不同参数解析如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">grant_type</span>:授权类型,授权码固定的值为 <span style="font-weight: 700;color: rgb(248, 57, 41);">authorization_code</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">code</span>:这个就是上一步获取的授权码 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">4、返回令牌</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;">认证中心收到令牌请求之后,通过之后,会返回一段JSON数据,其中包含了令牌<span style="font-weight: 700;color: rgb(248, 57, 41);">access_token</span>,如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ42nP33ibvO1HOTebwA8JP5XzH7NB6Cia6VJxswE3iaLTeLWeXYenzbwhpPpibyyH4otcLrjrueTNXqk/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">{&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"access_token"</span>:<span style="color: #50a14f;line-height: 26px;">"ACCESS_TOKEN"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"token_type"</span>:<span style="color: #50a14f;line-height: 26px;">"bearer"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"expires_in"</span>:<span style="color: #986801;line-height: 26px;">2592000</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"refresh_token"</span>:<span style="color: #50a14f;line-height: 26px;">"REFRESH_TOKEN"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"scope"</span>:<span style="color: #50a14f;line-height: 26px;">"read"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"uid"</span>:<span style="color: #986801;line-height: 26px;">100101</span><br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">access_token</span>则是颁发的令牌,refresh_token是刷新令牌,一旦令牌失效则携带这个令牌进行刷新。</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;">2、简化模式</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这种模式不常用,主要针对那些无后台的系统,直接通过web跳转授权,流程如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5674846625766872" src="/upload/fcb65e804b4000d93423bb68701a31a3.png" data-type="png" data-w="652" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> 简化模式 </figcaption> </figure> <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);">session</span>)有效,浏览器关掉,令牌就失效了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">1、请求令牌</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;">客户端直接请求令牌,请求的url如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ42nP33ibvO1HOTebwA8JP5XzH7NB6Cia6VJxswE3iaLTeLWeXYenzbwhpPpibyyH4otcLrjrueTNXqk/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">/oauth/authorize?<br>&nbsp;&nbsp;response_type=token&amp;<br>&nbsp;&nbsp;client_id=CLIENT_ID&amp;<br>&nbsp;&nbsp;redirect_uri=CALLBACK_URL&amp;<br>&nbsp;&nbsp;scope=<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这个url正是授权码模式中获取授权码的url,各个参数解析如下:</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);"> client_id:客户端的唯一Id </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> response_type:简化模式的固定值为 <span style="font-weight: 700;color: rgb(248, 57, 41);">token</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> scope:客户端的权限 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> redirect_uri:跳转的uri,这里后面携带的直接是 <span style="font-weight: 700;color: rgb(248, 57, 41);">令牌</span>,不是授权码了。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">2、返回令牌</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">认证中心认证通过后,会跳转到<span style="font-weight: 700;color: rgb(248, 57, 41);">redirect_uri</span>,并且后面携带着令牌,链接如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ42nP33ibvO1HOTebwA8JP5XzH7NB6Cia6VJxswE3iaLTeLWeXYenzbwhpPpibyyH4otcLrjrueTNXqk/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">https:<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//xxxx#token=NPmdj5</span><br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">#token=NPmdj5</code>这一段后面携带的就是认证中心携带的,令牌为<span style="font-weight: 700;color: rgb(248, 57, 41);">NPmdj5</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;">3、密码模式</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">密码模式也很简单,直接通过<span style="font-weight: 700;color: rgb(248, 57, 41);">用户名</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">密码</span>获取令牌,流程如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5674846625766872" src="/upload/fcb65e804b4000d93423bb68701a31a3.png" data-type="png" data-w="652" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> 密码模式 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">1、请求令牌</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;">认证中心要求客户端输入用户名、密码,认证成功则颁发令牌,请求的url如下:</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: #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;">/oauth/token?<br>&nbsp;&nbsp;grant_type=password&amp;<br>&nbsp;&nbsp;username=&amp;<br>&nbsp;&nbsp;password=&amp;<br>&nbsp;&nbsp;client_id=&amp;<br>&nbsp;&nbsp;client_secret=<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">参数解析如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">grant_type</span>:授权类型,密码模式固定值为password </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);">username</span>:用户名 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">password</span>:密码 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">client_id</span>:客户端id </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);">client_secret</span>:客户端的秘钥 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">2、返回令牌</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;">上述认证通过,直接返回JSON数据,不需要跳转,如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ42nP33ibvO1HOTebwA8JP5XzH7NB6Cia6VJxswE3iaLTeLWeXYenzbwhpPpibyyH4otcLrjrueTNXqk/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">{&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"access_token"</span>:<span style="color: #50a14f;line-height: 26px;">"ACCESS_TOKEN"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"token_type"</span>:<span style="color: #50a14f;line-height: 26px;">"bearer"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"expires_in"</span>:<span style="color: #986801;line-height: 26px;">2592000</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"refresh_token"</span>:<span style="color: #50a14f;line-height: 26px;">"REFRESH_TOKEN"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"scope"</span>:<span style="color: #50a14f;line-height: 26px;">"read"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"uid"</span>:<span style="color: #986801;line-height: 26px;">100101</span><br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">access_token</span>则是颁发的令牌,refresh_token是刷新令牌,一旦令牌失效则携带这个令牌进行刷新。</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;">4、客户端模式</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">适用于没有前端的命令行应用,即在命令行下请求令牌。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">流程如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5674846625766872" src="/upload/fcb65e804b4000d93423bb68701a31a3.png" data-type="png" data-w="652" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> 客户端模式 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">1、请求令牌</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;">请求的url为如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ42nP33ibvO1HOTebwA8JP5XzH7NB6Cia6VJxswE3iaLTeLWeXYenzbwhpPpibyyH4otcLrjrueTNXqk/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">/oauth/token?<br>grant_type=client_credentials&amp;<br>client_id=&amp;<br>client_secret=<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">参数解析如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">grant_type</span>:授权类型,客户端模式固定值为 <span style="font-weight: 700;color: rgb(248, 57, 41);">client_credentials</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">client_id</span>:客户端id </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);">client_secret</span>:客户端秘钥 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">2、返回令牌</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;">认证成功后直接返回令牌,格式为JSON数据,如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ42nP33ibvO1HOTebwA8JP5XzH7NB6Cia6VJxswE3iaLTeLWeXYenzbwhpPpibyyH4otcLrjrueTNXqk/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"access_token"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"ACCESS_TOKEN"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"token_type"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"bearer"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"expires_in"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">7200</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"scope"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"all"</span><br>}<br></code></pre> <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);">OAuth2.0的认证中心搭建</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">为了方便测试OAuth2的四种授权模式,这里为了方便测试,简单搭建一个认证中心,后续会逐渐完善。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1、案例架构</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">陈某使用的是<span style="font-weight: 700;color: rgb(248, 57, 41);">Spring Boot + Spring Cloud Alibaba</span> 作为基础搭建,新建一个<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">oauth2-auth-server-in-memory</code>模块作为认证中心,目录如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5417789757412399" src="/upload/6bbb0947c5309a6468b278cb783a6d0f.png" data-type="png" data-w="742" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">案例源码已经上传GitHub,关注公众号:<span style="font-weight: 700;color: rgb(248, 57, 41);">码猿技术专栏</span>,回复关键词 <span style="font-weight: 700;color: rgb(248, 57, 41);">9529</span> 获取。</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">2、添加依赖</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Spring Boot 和 Spring Cloud 的相关依赖这里陈某就不再说了,直接上Spring Security和OAuth2的依赖,如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ42nP33ibvO1HOTebwA8JP5XzH7NB6Cia6VJxswE3iaLTeLWeXYenzbwhpPpibyyH4otcLrjrueTNXqk/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">&lt;!--spring&nbsp;security的依赖--&gt;</span><br><span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span>org.springframework.boot<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span><br>&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span>spring-boot-starter-security<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span><br><span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br><br><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">&lt;!--OAuth2的依赖--&gt;</span><br><span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br>&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span>org.springframework.cloud<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span><br>&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span>spring-cloud-starter-oauth2<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span><br><span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">3、Spring Security安全配置</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这里主要涉及到Spring Security的配置。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">SecurityConfig</code>这个配置类中主要设置有4块内容,如下:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">1、加密方式</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">采用<span style="font-weight: 700;color: rgb(248, 57, 41);">BCryptPasswordEncoder</span>加密,如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.4510739856801909" src="/upload/343a74581a398c827c31809ed64ea2a0.png" data-type="png" data-w="419" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">2、配置用户</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这里为了方便测试,直接将用户信息存储在内存中,<span style="font-weight: 700;color: rgb(248, 57, 41);">后续完善</span>,代码如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.3926014319809069" src="/upload/fd7839b24b1eb27155dd4303e995e36b.png" data-type="png" data-w="838" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上述代码配置了两个用户,如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 用户名admin,密码123,角色admin </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 用户名user,密码123,角色user </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">3、注入认证�

实战!用Redisson来实现分布式锁,真香!

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" data-mpa-powered-by="yiban.io"> <section data-mpa-template="t" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;justify-content: flex-start;padding-left: 34px;" data-mid="" mpa-from-tpl="t"> <section style="display: flex;justify-content: flex-start;align-items: center;flex-direction: column;" data-mid="" mpa-from-tpl="t"> <section style="width: 8px;height: 3px;align-self: flex-end;transform: translateY(-28px);margin-right: -4px;" data-mid="" mpa-from-tpl="t"> <img class="rich_pages wxw-img" data-ratio="0.875" src="/upload/ca621f85a55151157be89ea6853d30e3.png" data-type="png" data-w="16" style="display: block;"> </section> </section> </section> </section> <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><span style="letter-spacing: 0.1em;word-spacing: 0.1em;color: rgb(255, 41, 65);">大家好,我是不才陈某~</span></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);"><span style="letter-spacing: 0.1em;word-spacing: 0.1em;">R</span><span style="letter-spacing: 0.1em;word-spacing: 0.1em;">edis 分布式锁使用 </span><code style="letter-spacing: 0.1em;word-spacing: 0.1em;font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">SET</code><span style="letter-spacing: 0.1em;word-spacing: 0.1em;"> 指令就可以实现了么?在分布式领域 </span><code style="letter-spacing: 0.1em;word-spacing: 0.1em;font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">CAP</code><span style="letter-spacing: 0.1em;word-spacing: 0.1em;"> 理论一直存在。</span><br></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> <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);"> 什么时候需要分布式锁? </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);"> 超时时间设置多少合适呢? </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);"> 主从架构会带来什么安全问题? </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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">Redlock</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);"> Redisson 分布式锁最佳实战 </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> </ul> <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);"> <br> </section> <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);text-align: center;"> <span style="color: rgb(244, 138, 0);font-size: 22px;font-weight: bold;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">什么时候用分布式锁?</span> <br> </section> <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);">码哥,说个通俗的例子讲解下什么时候需要分布式锁呢?</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);">诊所只有一个医生,很多患者前来就诊。</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> <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);"><strong>分布式锁就是用来控制同一时刻,只有一个 JVM 进程中的一个线程可以访问被保护的资源。</strong></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;">分布式锁入门</span></h1> <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);">65 哥:分布式锁应该满足哪些特性?</p> </blockquote> <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);"> 互斥:在任何给定时刻,只有一个客户端可以持有锁; </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);"> 容错:只要大多数 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">Redis</code>的节点都已经启动,客户端就可以获取和释放锁。 </section></li> </ol> <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);">码哥,我可以使用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">SETNX key value</code> 命令是实现「互斥」特性。</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);">这个命令来自于<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">SET if Not eXists</code>的缩写,意思是:如果 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">key</code> 不存在,则设置 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">value</code> 给这个<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">key</code>,否则啥都不做。Redis 官方地址说的:</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);"> 1:设置成功; </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:key 没有设置成功。 </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);">敲代码一天累了,想去放松按摩下肩颈。</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);">168 号技师最抢手,大家喜欢点,所以并发量大,需要分布式锁控制。</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);">同一时刻只允许一个「客户」预约 168 技师。</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);">肖菜鸡申请 168 技师成功:</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: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">&gt;&nbsp;SETNX&nbsp;lock:168&nbsp;1<br>(<span style="color: #a6e22e;line-height: 26px;">integer</span>)&nbsp;1&nbsp;<span style="color: #75715e;line-height: 26px;">#&nbsp;获取 168 技师成功</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: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">&gt;&nbsp;SETNX&nbsp;lock&nbsp;2<br>(<span style="color: #a6e22e;line-height: 26px;">integer</span>)&nbsp;0&nbsp;<span style="color: #75715e;line-height: 26px;">#&nbsp;客户谢霸哥&nbsp;2&nbsp;获取失败</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);">此刻,申请成功的客户就可以享受 168 技师的肩颈放松服务「共享资源」。</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);">享受结束后,要及时释放锁,给后来者享受 168 技师的服务机会。</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);">肖菜鸡,码哥考考你如何释放锁呢?</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);">很简单,使用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">DEL</code> 删除这个 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">key</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: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">&gt;&nbsp;DEL&nbsp;lock:168<br>(<span style="color: #a6e22e;line-height: 26px;">integer</span>)&nbsp;1<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

MySQL 大批量插入,如何过滤掉重复数据?

作者:微信小助手

<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: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 14px;padding: 10px;"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">加班原因是上线,解决线上数据库存在重复数据的问题,发现了程序的bug,很好解决,有点问题的是,修正线上的重复数据。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">线上库有6个表存在重复数据,其中2个表比较大,一个96万+、一个30万+,因为之前处理过相同的问题,就直接拿来了上次的Python去重脚本,脚本很简单,就是连接数据库,查出来重复数据,循环删除。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">emmmm,但是这个效率嘛,实在是太低了,1秒一条,重复数据大约2万+,预估时间大约在8个小时左右。。。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">盲目依靠前人的东西,而不去自己思考是有问题的!总去想之前怎么可以,现在怎么不行了,这也是有问题的!我发现,最近确实状态不太对,失去了探索和求知的欲望,今天算是一个警醒,颇有迷途知返的感觉。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">言归正传,下面详细介绍去重步骤。</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh109iaa9YjHVCkvxRyclb9C09whTSzUvRjgfX4t0mLdAwLmqYwBlrWATQwSnv6Hmx7uAgC6CBjpvq5a/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;"><span style="color: #569CD6;line-height: 26px;">CREATE</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">TABLE</span>&nbsp;<span style="color: #D69D85;line-height: 26px;">`animal`</span>&nbsp;(<br>&nbsp;&nbsp;<span style="color: #D69D85;line-height: 26px;">`id`</span>&nbsp;<span style="color: #4EC9B0;line-height: 26px;">int</span>(<span style="color: #B8D7A3;line-height: 26px;">11</span>)&nbsp;<span style="color: #569CD6;line-height: 26px;">NOT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">NULL</span>&nbsp;AUTO_INCREMENT,<br>&nbsp;&nbsp;<span style="color: #D69D85;line-height: 26px;">`name`</span>&nbsp;<span style="color: #4EC9B0;line-height: 26px;">varchar</span>(<span style="color: #B8D7A3;line-height: 26px;">20</span>)&nbsp;<span style="color: #569CD6;line-height: 26px;">DEFAULT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">NULL</span>,<br>&nbsp;&nbsp;<span style="color: #D69D85;line-height: 26px;">`age`</span>&nbsp;<span style="color: #4EC9B0;line-height: 26px;">int</span>(<span style="color: #B8D7A3;line-height: 26px;">11</span>)&nbsp;<span style="color: #569CD6;line-height: 26px;">DEFAULT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">NULL</span>,<br>&nbsp;&nbsp;PRIMARY&nbsp;<span style="color: #569CD6;line-height: 26px;">KEY</span>&nbsp;(<span style="color: #D69D85;line-height: 26px;">`id`</span>)<br>)&nbsp;<span style="color: #569CD6;line-height: 26px;">ENGINE</span>=<span style="color: #569CD6;line-height: 26px;">InnoDB</span>&nbsp;AUTO_INCREMENT=<span style="color: #B8D7A3;line-height: 26px;">1</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">DEFAULT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">CHARSET</span>=utf8&nbsp;<span style="color: #569CD6;line-height: 26px;">COLLATE</span>=utf8_bin;<br><br><br><span style="color: #569CD6;line-height: 26px;">INSERT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">INTO</span>&nbsp;<span style="color: #D69D85;line-height: 26px;">`pilipa_dds`</span>.<span style="color: #D69D85;line-height: 26px;">`student`</span>&nbsp;(<span style="color: #D69D85;line-height: 26px;">`id`</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">`name`</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">`age`</span>)&nbsp;<span style="color: #569CD6;line-height: 26px;">VALUES</span>&nbsp;(<span style="color: #D69D85;line-height: 26px;">'1'</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">'cat'</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">'12'</span>);<br><span style="color: #569CD6;line-height: 26px;">INSERT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">INTO</span>&nbsp;<span style="color: #D69D85;line-height: 26px;">`pilipa_dds`</span>.<span style="color: #D69D85;line-height: 26px;">`student`</span>&nbsp;(<span style="color: #D69D85;line-height: 26px;">`id`</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">`name`</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">`age`</span>)&nbsp;<span style="color: #569CD6;line-height: 26px;">VALUES</span>&nbsp;(<span style="color: #D69D85;line-height: 26px;">'2'</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">'dog'</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">'13'</span>);<br><span style="color: #569CD6;line-height: 26px;">INSERT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">INTO</span>&nbsp;<span style="color: #D69D85;line-height: 26px;">`pilipa_dds`</span>.<span style="color: #D69D85;line-height: 26px;">`student`</span>&nbsp;(<span style="color: #D69D85;line-height: 26px;">`id`</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">`name`</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">`age`</span>)&nbsp;<span style="color: #569CD6;line-height: 26px;">VALUES</span>&nbsp;(<span style="color: #D69D85;line-height: 26px;">'3'</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">'camel'</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">'25'</span>);<br><span style="color: #569CD6;line-height: 26px;">INSERT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">INTO</span>&nbsp;<span style="color: #D69D85;line-height: 26px;">`pilipa_dds`</span>.<span style="color: #D69D85;line-height: 26px;">`student`</span>&nbsp;(<span style="color: #D69D85;line-height: 26px;">`id`</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">`name`</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">`age`</span>)&nbsp;<span style="color: #569CD6;line-height: 26px;">VALUES</span>&nbsp;(<span style="color: #D69D85;line-height: 26px;">'4'</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">'cat'</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">'32'</span>);<br><span style="color: #569CD6;line-height: 26px;">INSERT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">INTO</span>&nbsp;<span style="color: #D69D85;line-height: 26px;">`pilipa_dds`</span>.<span style="color: #D69D85;line-height: 26px;">`student`</span>&nbsp;(<span style="color: #D69D85;line-height: 26px;">`id`</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">`name`</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">`age`</span>)&nbsp;<span style="color: #569CD6;line-height: 26px;">VALUES</span>&nbsp;(<span style="color: #D69D85;line-height: 26px;">'5'</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">'dog'</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">'42'</span>);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">目标:我们要去掉name相同的数据。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">先看看哪些数据重复了</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh109iaa9YjHVCkvxRyclb9C09whTSzUvRjgfX4t0mLdAwLmqYwBlrWATQwSnv6Hmx7uAgC6CBjpvq5a/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;"><span style="color: #569CD6;line-height: 26px;">SELECT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">name</span>,<span style="color: #569CD6;line-height: 26px;">count</span>(&nbsp;<span style="color: #B8D7A3;line-height: 26px;">1</span>&nbsp;)&nbsp;<br><span style="color: #569CD6;line-height: 26px;">FROM</span><br>&nbsp;student&nbsp;<br><span style="color: #569CD6;line-height: 26px;">GROUP</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">BY</span><br><span style="color: #569CD6;line-height: 26px;">NAME</span>&nbsp;<br><span style="color: #569CD6;line-height: 26px;">HAVING</span><br>&nbsp;<span style="color: #569CD6;line-height: 26px;">count</span>(&nbsp;<span style="color: #B8D7A3;line-height: 26px;">1</span>&nbsp;)&nbsp;&gt;&nbsp;<span style="color: #B8D7A3;line-height: 26px;">1</span>;<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">输出:</p> <blockquote data-tool="mdnice编辑器" style="border-top: 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: rgba(0, 0, 0, 0.65);border-right: 1px solid rgba(0, 0, 0, 0.65);background: rgb(249, 249, 249);"> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;color: black;line-height: 26px;">name count(1) cat 2 dog 2</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">name为cat和dog的数据重复了,每个重复的数据有两条;</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh109iaa9YjHVCkvxRyclb9C09whTSzUvRjgfX4t0mLdAwLmqYwBlrWATQwSnv6Hmx7uAgC6CBjpvq5a/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;"><span style="color: #569CD6;line-height: 26px;">Select</span>&nbsp;*&nbsp;<span style="color: #569CD6;line-height: 26px;">From</span>&nbsp;表&nbsp;<span style="color: #569CD6;line-height: 26px;">Where</span>&nbsp;重复字段&nbsp;<span style="color: #569CD6;line-height: 26px;">In</span>&nbsp;(<span style="color: #569CD6;line-height: 26px;">Select</span>&nbsp;重复字段&nbsp;<span style="color: #569CD6;line-height: 26px;">From</span>&nbsp;表&nbsp;<span style="color: #569CD6;line-height: 26px;">Group</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">By</span>&nbsp;重复字段&nbsp;<span style="color: #569CD6;line-height: 26px;">Having</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">Count</span>(<span style="color: #B8D7A3;line-height: 26px;">1</span>)&gt;<span style="color: #B8D7A3;line-height: 26px;">1</span>)<br></code></pre> <h2 data-tool="mdnice编辑器" style="font-size: 22px;text-align: center;font-weight: bold;line-height: 1.1em;padding-top: 12px;padding-bottom: 12px;margin: 70px 30px 30px;border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);"><span style="float: left;display: block;width: 90%;border-top: 1px solid #000;height: 1px;line-height: 1px;margin-left: -5px;margin-top: -17px;"> </span><span style="display: block;width: 3px;margin-left: 5%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="display: block;-webkit-box-reflect: below 0em -webkit-gradient(linear,left top,left bottom, from(rgba(0,0,0,0)),to(rgba(255,255,255,0.1)));"><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&amp;mid=2247487551&amp;idx=1&amp;sn=18f64ba49f3f0f9d8be9d1fdef8857d9&amp;scene=21#wechat_redirect" style="color: rgb(30, 107, 184);border-bottom: 1px solid rgb(30, 107, 184);" data-linktype="2">删除全部重复数据,一条不留</a></span><span style="display: block;width: 3px;margin-left: 95%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="float: right;display: block;width: 90%;border-bottom: 1px solid #000;height: 1px;line-height: 1px;margin-right: -5px;margin-top: 16px;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">直接删除会报错</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh109iaa9YjHVCkvxRyclb9C09whTSzUvRjgfX4t0mLdAwLmqYwBlrWATQwSnv6Hmx7uAgC6CBjpvq5a/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;"><span style="color: #569CD6;line-height: 26px;">DELETE</span>&nbsp;<br><span style="color: #569CD6;line-height: 26px;">FROM</span><br>&nbsp;student&nbsp;<br><span style="color: #569CD6;line-height: 26px;">WHERE</span><br>&nbsp;<span style="color: #569CD6;line-height: 26px;">NAME</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">IN</span>&nbsp;(<br>&nbsp;<span style="color: #569CD6;line-height: 26px;">SELECT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">NAME</span>&nbsp;<br>&nbsp;<span style="color: #569CD6;line-height: 26px;">FROM</span><br>&nbsp;&nbsp;student&nbsp;<br>&nbsp;<span style="color: #569CD6;line-height: 26px;">GROUP</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">BY</span><br>&nbsp;<span style="color: #569CD6;line-height: 26px;">NAME</span>&nbsp;<br><span style="color: #569CD6;line-height: 26px;">HAVING</span><br>&nbsp;<span style="color: #569CD6;line-height: 26px;">count</span>(&nbsp;<span style="color: #B8D7A3;line-height: 26px;">1</span>&nbsp;)&nbsp;&gt;&nbsp;<span style="color: #B8D7A3;line-height: 26px;">1</span>)<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">报错:</p> <blockquote data-tool="mdnice编辑器" style="border-top: 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: rgba(0, 0, 0, 0.65);border-right: 1px solid rgba(0, 0, 0, 0.65);background: rgb(249, 249, 249);"> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;color: black;line-height: 26px;">1093 - You can't specify target table 'student' for update in FROM clause, Time: 0.016000s</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">原因是:更新这个表的同时又查询了这个表,查询这个表的同时又去更新了这个表,可以理解为死锁。mysql不支持这种更新查询同一张表的操作</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">解决办法:把要更新的几列数据查询出来做为一个第三方表,然后筛选更新。</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh109iaa9YjHVCkvxRyclb9C09whTSzUvRjgfX4t0mLdAwLmqYwBlrWATQwSnv6Hmx7uAgC6CBjpvq5a/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;"><span style="color: #569CD6;line-height: 26px;">DELETE</span>&nbsp;<br><span style="color: #569CD6;line-height: 26px;">FROM</span><br>&nbsp;student&nbsp;<br><span style="color: #569CD6;line-height: 26px;">WHERE</span><br>&nbsp;<span style="color: #569CD6;line-height: 26px;">NAME</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">IN</span>&nbsp;(<br>&nbsp;<span style="color: #569CD6;line-height: 26px;">SELECT</span><br>&nbsp;&nbsp;t.NAME&nbsp;<br><span style="color: #569CD6;line-height: 26px;">FROM</span><br>&nbsp;(&nbsp;<span style="color: #569CD6;line-height: 26px;">SELECT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">NAME</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">FROM</span>&nbsp;student&nbsp;<span style="color: #569CD6;line-height: 26px;">GROUP</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">BY</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">NAME</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">HAVING</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">count</span>(&nbsp;<span style="color: #B8D7A3;line-height: 26px;">1</span>&nbsp;)&nbsp;&gt;&nbsp;<span style="color: #B8D7A3;line-height: 26px;">1</span>&nbsp;)&nbsp;t)<br></code></pre> <blockquote data-tool="mdnice编辑器" style="border-top: 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: rgba(0, 0, 0, 0.65);border-right: 1px solid rgba(0, 0, 0, 0.65);background: rgb(249, 249, 249);"> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;color: black;line-height: 26px;">推荐下自己做的 Spring Boot 的实战项目:</p> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;color: black;line-height: 26px;">https://github.com/YunaiV/ruoyi-vue-pro</p> </blockquote> <h2 data-tool="mdnice编辑器" style="font-size: 22px;text-align: center;font-weight: bold;line-height: 1.1em;padding-top: 12px;padding-bottom: 12px;margin: 70px 30px 30px;border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);"><span style="float: left;display: block;width: 90%;border-top: 1px solid #000;height: 1px;line-height: 1px;margin-left: -5px;margin-top: -17px;"> </span><span style="display: block;width: 3px;margin-left: 5%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="display: block;-webkit-box-reflect: below 0em -webkit-gradient(linear,left top,left bottom, from(rgba(0,0,0,0)),to(rgba(255,255,255,0.1)));"><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&amp;mid=2247487551&amp;idx=1&amp;sn=18f64ba49f3f0f9d8be9d1fdef8857d9&amp;scene=21#wechat_redirect" style="color: rgb(30, 107, 184);border-bottom: 1px solid rgb(30, 107, 184);" data-linktype="2">删除表中删除重复数据,仅保留一条</a></span><span style="display: block;width: 3px;margin-left: 95%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="float: right;display: block;width: 90%;border-bottom: 1px solid #000;height: 1px;line-height: 1px;margin-right: -5px;margin-top: 16px;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在删除之前,我们可以先查一下,我们要删除的重复数据是啥样的</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh109iaa9YjHVCkvxRyclb9C09whTSzUvRjgfX4t0mLdAwLmqYwBlrWATQwSnv6Hmx7uAgC6CBjpvq5a/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;"><span style="color: #569CD6;line-height: 26px;">SELECT</span><br>&nbsp;*&nbsp;<br><span style="color: #569CD6;line-height: 26px;">FROM</span><br>&nbsp;student&nbsp;<br><span style="color: #569CD6;line-height: 26px;">WHERE</span><br>&nbsp;<span style="color: #569CD6;line-height: 26px;">id</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">NOT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">IN</span>&nbsp;(<br>&nbsp;<span style="color: #569CD6;line-height: 26px;">SELECT</span><br>&nbsp;&nbsp;t.id&nbsp;<br>&nbsp;<span style="color: #569CD6;line-height: 26px;">FROM</span><br>&nbsp;(&nbsp;<span style="color: #569CD6;line-height: 26px;">SELECT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">MIN</span>(&nbsp;<span style="color: #569CD6;line-height: 26px;">id</span>&nbsp;)&nbsp;<span style="color: #569CD6;line-height: 26px;">AS</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">id</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">FROM</span>&nbsp;student&nbsp;<span style="color: #569CD6;line-height: 26px;">GROUP</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">BY</span>&nbsp;<span style="color: #D69D85;line-height: 26px;">`name`</span>&nbsp;)&nbsp;t&nbsp;<br>&nbsp;)<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">啥意思呢,就是先通过name分组,查出id最小的数据,这些数据就是我们要留下的火种,那么再查询出id不在这里面的,就是我们要删除的重复数据。</p> <blockquote data-tool="mdnice编辑器" style="border-top: 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: rgba(0, 0, 0, 0.65);border-right: 1px solid rgba(0, 0, 0, 0.65);background: rgb(249, 249, 249);"> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;color: black;line-height: 26px;">推荐下自己做的 Spring Cloud 的实战项目:</p> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;color: black;line-height: 26px;">https://github.com/YunaiV/onemall</p> </blockquote> <h2 data-tool="mdnice编辑器" style="font-size: 22px;text-align: center;font-weight: bold;line-height: 1.1em;padding-top: 12px;padding-bottom: 12px;margin: 70px 30px 30px;border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);"><span style="float: left;display: block;width: 90%;border-top: 1px solid #000;height: 1px;line-height: 1px;margin-left: -5px;margin-top: -17px;"> </span><span style="display: block;width: 3px;margin-left: 5%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="display: block;-webkit-box-reflect: below 0em -webkit-gradient(linear,left top,left bottom, from(rgba(0,0,0,0)),to(rgba(255,255,255,0.1)));"><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&amp;mid=2247487551&amp;idx=1&amp;sn=18f64ba49f3f0f9d8be9d1fdef8857d9&amp;scene=21#wechat_redirect" style="color: rgb(30, 107, 184);border-bottom: 1px solid rgb(30, 107, 184);" data-linktype="2">开始删除重复数据,仅留一条</a></span><span style="display: block;width: 3px;margin-left: 95%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="float: right;display: block;width: 90%;border-bottom: 1px solid #000;height: 1px;line-height: 1px;margin-right: -5px;margin-top: 16px;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">很简单,刚才的select换成delete即可</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh109iaa9YjHVCkvxRyclb9C09whTSzUvRjgfX4t0mLdAwLmqYwBlrWATQwSnv6Hmx7uAgC6CBjpvq5a/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;"><span style="color: #569CD6;line-height: 26px;">DELETE</span>&nbsp;<br><span style="color: #569CD6;line-height: 26px;">FROM</span><br>&nbsp;student&nbsp;<br><span style="color: #569CD6;line-height: 26px;">WHERE</span><br>&nbsp;<span style="color: #569CD6;line-height: 26px;">id</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">NOT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">IN</span>&nbsp;(<br>&nbsp;<span style="color: #569CD6;line-height: 26px;">SELECT</span><br>&nbsp;&nbsp;t.id&nbsp;<br>&nbsp;<span style="color: #569CD6;line-height: 26px;">FROM</span><br>&nbsp;(&nbsp;<span style="color: #569CD6;line-height: 26px;">SELECT</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">MIN</span>(&nbsp;<span style="color: #569CD6;line-height: 26px;">id</span>&nbsp;)&nbsp;<span style="color: #569CD6;line-height: 26px;">AS</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">id</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">FROM</span>&nbsp;student&nbsp;<span style="color: #569CD6;line-height: 26px;">GROUP</span>&nbsp;<span style="color: #569CD6;line-height: 26px;">BY</span>&nbsp;<span style="color: #D69D85;line-height: 26px;">`name`</span>&nbsp;)&nbsp;t&nbsp;<br>&nbsp;)<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">90万+的表执行起来超级快。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">All done 👏👏👏👏~</p> </section>

如何使用注解优雅的记录操作日志

作者:微信小助手

<p style="line-height: 26px;font-size: 16px;color: black;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 10px;">大家好,我是3y</p> <p style="line-height: 26px;font-size: 16px;color: black;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 10px;"><span style="letter-spacing: 0px;">今天先给大家看看</span><span style="letter-spacing: 0px;">austin准备要接入的功能</span><span style="letter-spacing: 0px;">:</span><strong><span style="letter-spacing: 0px;">优雅</span><span style="letter-spacing: 0px;">记录操作日志</span></strong><span style="letter-spacing: 0px;"></span><br></p> <p style="line-height: 26px;font-size: 16px;color: black;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 10px;"><span style="letter-spacing: 0px;">这个在之前的austin系列文章</span><span style="letter-spacing: 0px;">已经提到过了,不知道大家还有没有印象。</span><span style="letter-spacing: 0px;">我的好兄弟</span><span style="letter-spacing: 0px;">在逐渐把该功能完善。</span><br></p> <p style="line-height: 26px;font-size: 16px;color: black;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 10px;"><span style="letter-spacing: 0px;">今天先来看看</span><span style="letter-spacing: 0px;">它的实现原理和</span><span style="letter-spacing: 0px;">用途吧,后边我</span><span style="letter-spacing: 0px;">在接入的时候就只介绍大概了,不会针对它再做详细的介绍啦。</span><br></p> <p style="line-height: 26px;font-size: 16px;color: black;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 10px;"><span style="letter-spacing: 0px;">这种轮子还是蛮有意思的</span><span style="letter-spacing: 0px;">,</span><span style="letter-spacing: 0px;">我</span><span style="letter-spacing: 0px;">就特爱看这种</span><br></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: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 14px;padding: 10px;" data-mpa-powered-by="yiban.io"> <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-fileid="100006241" data-ratio="0.41875" src="/upload/4def45e56cb3624554ec1d35525dd028.png" data-type="png" data-w="1600" style="display: block;margin-right: auto;margin-left: auto;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> </figure> <h2 data-tool="mdnice编辑器" style="font-size: 22px;text-align: center;font-weight: bold;line-height: 1.1em;padding-top: 12px;padding-bottom: 12px;margin: 70px 30px 30px;border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);"><span style="float: left;display: block;width: 90%;border-top: 1px solid #000;height: 1px;line-height: 1px;margin-left: -5px;margin-top: -17px;"> </span><span style="display: block;width: 3px;margin-left: 5%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="display: block;-webkit-box-reflect: below 0em -webkit-gradient(linear,left top,left bottom, from(rgba(0,0,0,0)),to(rgba(255,255,255,0.1)));">写在开头</span><span style="display: block;width: 3px;margin-left: 95%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="float: right;display: block;width: 90%;border-bottom: 1px solid #000;height: 1px;line-height: 1px;margin-right: -5px;margin-top: 16px;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">本文讨论如何优雅的记录操作日志,并且<strong>实现了一个SpringBoot Starter(取名log-record-starter),方便的使用注解记录操作日志,并将日志数据推送到指定数据管道(消息队列等)</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">本文灵感来源于美团技术团队的文章:如何优雅地记录操作日志?。文中使用的部分定义描述和示例来源于美团原文,请知悉。</p> <blockquote data-tool="mdnice编辑器" style="border-top: 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: rgba(0, 0, 0, 0.65);border-right: 1px solid rgba(0, 0, 0, 0.65);background: rgb(249, 249, 249);"> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 16px;color: black;line-height: 26px;">本文作为《萌新写开源》的开篇,先把项目成品介绍给大家,之后的文章会详细介绍,如何一步步将个人项目做成一个大家都能参与的开源项目(如何写SpringBoot Starter,如何上传到Maven仓库,如何设计和使用注解和切面等),麻烦大家多多点赞支持,这是我更新的动力。请大家放心,公众号还会持续更新,我没有忘掉密码。:)——蛮三刀酱</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><strong>本文目录:</strong></p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;list-style-type: square;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 什么是操作日志? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Java中常见的操作日志实现方式 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 实战:通过注解实现操作日志的记录 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="font-size: 22px;text-align: center;font-weight: bold;line-height: 1.1em;padding-top: 12px;padding-bottom: 12px;margin: 70px 30px 30px;border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);"><span style="float: left;display: block;width: 90%;border-top: 1px solid #000;height: 1px;line-height: 1px;margin-left: -5px;margin-top: -17px;"> </span><span style="display: block;width: 3px;margin-left: 5%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="display: block;-webkit-box-reflect: below 0em -webkit-gradient(linear,left top,left bottom, from(rgba(0,0,0,0)),to(rgba(255,255,255,0.1)));">什么是操作日志?</span><span style="display: block;width: 3px;margin-left: 95%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="float: right;display: block;width: 90%;border-bottom: 1px solid #000;height: 1px;line-height: 1px;margin-right: -5px;margin-top: 16px;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">定义:<strong>操作日志</strong>主要是指对某个对象进行新增操作或者修改操作后记录下这个新增或者修改,操作日志要求可读性比较强,因为它主要是给用户看的,比如订单的物流信息,用户需要知道在什么时间发生了什么事情。再比如,客服对工单的处理记录信息。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">以我们系统内部使用的一个CRM系统举例,里面每个联系人的资料都会有操作历史:</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-fileid="100006243" data-ratio="1.0358490566037737" src="/upload/50c042c52943d508d359995722a75477.jpg" data-type="jpeg" data-w="1060" style="display: block;margin-right: auto;margin-left: auto;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">这些数据就是操作系统日志,这些数据通常会以结构化数据的形式存储在数据库中,对于开发来说,这种日志的代码逻辑通常是非常规律,比如读取变化前和变化后的数据,获取当前操作人和操作时间等等。</p> <h2 data-tool="mdnice编辑器" style="font-size: 22px;text-align: center;font-weight: bold;line-height: 1.1em;padding-top: 12px;padding-bottom: 12px;margin: 70px 30px 30px;border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);"><span style="float: left;display: block;width: 90%;border-top: 1px solid #000;height: 1px;line-height: 1px;margin-left: -5px;margin-top: -17px;"> </span><span style="display: block;width: 3px;margin-left: 5%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="display: block;-webkit-box-reflect: below 0em -webkit-gradient(linear,left top,left bottom, from(rgba(0,0,0,0)),to(rgba(255,255,255,0.1)));">常见的操作日志实现方式</span><span style="display: block;width: 3px;margin-left: 95%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="float: right;display: block;width: 90%;border-bottom: 1px solid #000;height: 1px;line-height: 1px;margin-right: -5px;margin-top: 16px;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">在小型项目中,这种日志记录的操作通常会以提供一个接口或整个日志记录Service来实现。那么放到多人共同开发的项目中,除了封装一个方法,还有什么更好的办法来统一实现操作日志的记录?下面就要讨论下在Java中,常见的操作日志实现方式。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">当你需要给一个大型系统从头到尾加上操作日志,那么除了上述的手动处理方式,也有很多种整体设计方案:</p> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;background-color: #000;color: #fff;padding: 2px 10px;width: fit-content;font-size: 17px;margin: 60px auto 10px;margin-top: 25px;"><span style="display: none;"></span>1. 使用Canal监听数据库记录操作日志<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">Canal应运而生,它通过伪装成数据库的从库,读取主库发来的binlog,用来实现<strong>数据库增量订阅和消费业务需求</strong>。可以看我的这篇文章:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><a href="https://mp.weixin.qq.com/s?__biz=MzU1NTA0NTEwMg==&amp;mid=2247484273&amp;idx=1&amp;sn=7fec41a40e763df094c0dd675330808a&amp;chksm=fbdb1af0ccac93e676c2a0c6aeb1ff3edfe43b30969a7c1bbe19ccf7270acd6e41e6812caf0d&amp;token=541499407&amp;lang=zh_CN&amp;scene=21#wechat_redirect" style="color: rgb(30, 107, 184);font-weight: bold;border-bottom: 1px solid rgb(30, 107, 184);" data-linktype="2">阿里开源MySQL中间件Canal快速入门</a></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">这个方式有点是和业务逻辑完全分离,缺点也很大,需要使用到MySQL的Binlog,向DBA申请就有点困难。如果涉及到修改第三方接口,那么就无法监听别人的数据库了。所以调用RPC接口时,就需要额外的在业务代码中增加记录代码,破坏了“和业务逻辑完全分离”这个基本原则,局限性大。</p> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;background-color: #000;color: #fff;padding: 2px 10px;width: fit-content;font-size: 17px;margin: 60px auto 10px;margin-top: 25px;"><span style="display: none;"></span>2. 通过日志文件的方式记录<span style="display: none;"></span></h3> <p><br></p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7j1UQofaR9eRUFTcVBXBeXpxMo1ekwm4g3wOXgt6OiaPaCINMqnKpico7AZOpCwnicRic6LfmzugVFL1qK8nicrSzPXvHv3IUDI86/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;">log.info(<span style="color: #D69D85;line-height: 26px;">"订单已经创建,订单编号:{}"</span>,&nbsp;orderNo)<br>log.info(<span style="color: #D69D85;line-height: 26px;">"修改了订单的配送地址:从“{}”修改到“{}”,&nbsp;"</span>金灿灿小区<span style="color: #D69D85;line-height: 26px;">",&nbsp;"</span>银盏盏小区<span style="color: #D69D85;line-height: 26px;">")<br></span></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><br></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">这种方式,需要手动的设定好操作日志和其他日志的区别,<strong>比如给操作日志单独的Logger</strong>。并且,对于操作人的记录,需要在函数中额外的写入请求的上下文中。<strong>后期这种日志还需要在SLS等日志系统中做额外的抽取。</strong></p> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;background-color: #000;color: #fff;padding: 2px 10px;width: fit-content;font-size: 17px;margin: 60px auto 10px;margin-top: 25px;"><span style="display: none;"></span>3. 通过 LogUtil 的方式记录日志<span style="display: none;"></span></h3> <p><br></p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7j1UQofaR9eRUFTcVBXBeXpxMo1ekwm4g3wOXgt6OiaPaCINMqnKpico7AZOpCwnicRic6LfmzugVFL1qK8nicrSzPXvHv3IUDI86/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;">LogUtil.log(orderNo,&nbsp;<span style="color: #D69D85;line-height: 26px;">"订单创建"</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">"小明"</span>)<br>LogUtil.log(orderNo,&nbsp;<span style="color: #D69D85;line-height: 26px;">"订单创建,订单号"</span>+<span style="color: #D69D85;line-height: 26px;">"NO.11089999"</span>,&nbsp;&nbsp;<span style="color: #D69D85;line-height: 26px;">"小明"</span>)<br>String&nbsp;template&nbsp;=&nbsp;<span style="color: #D69D85;line-height: 26px;">"用户%s修改了订单的配送地址:从“%s”修改到“%s”"</span><br>LogUtil.log(orderNo,&nbsp;String.format(tempalte,&nbsp;<span style="color: #D69D85;line-height: 26px;">"小明"</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">"金灿灿小区"</span>,&nbsp;<span style="color: #D69D85;line-height: 26px;">"银盏盏小区"</span>),&nbsp;&nbsp;<span style="color: #D69D85;line-height: 26px;">"小明"</span>)<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><br></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">这种方式会导致业务的逻辑比较繁杂,最后导致 LogUtils.logRecord() 方法的调用存在于很多业务的代码中,而且类似 getLogContent() 这样的方法也散落在各个业务类中,对于代码的可读性和可维护性来说是一个灾难。</p> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;background-color: #000;color: #fff;padding: 2px 10px;width: fit-content;font-size: 17px;margin: 60px auto 10px;margin-top: 25px;"><span style="display: none;"></span>4. 方法注解实现操作日志<span style="display: none;"></span></h3> <p><br></p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7j1UQofaR9eRUFTcVBXBeXpxMo1ekwm4g3wOXgt6OiaPaCINMqnKpico7AZOpCwnicRic6LfmzugVFL1qK8nicrSzPXvHv3IUDI86/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;">@OperationLog(bizType&nbsp;=&nbsp;<span style="color: #D69D85;line-height: 26px;">"bizType"</span>,&nbsp;bizId&nbsp;=&nbsp;<span style="color: #D69D85;line-height: 26px;">"#request.orderId"</span>,&nbsp;pipeline&nbsp;=&nbsp;DataPipelineEnum.QUEUE)<br>public&nbsp;Response&lt;BaseResult&gt;&nbsp;<span style="color: #569CD6;line-height: 26px;">function</span>(Request&nbsp;request)&nbsp;{<br>&nbsp;&nbsp;//&nbsp;方法执行逻辑<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><br></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">我们可以在注解的操作日志上记录固定文案,这样业务逻辑和业务代码可以做到解耦,让我们的业务代码变得纯净起来。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">美团的原文给出了注解记录日志的详细架构设计方案,并且贴出了部分源码。但是文中并没有完整的开源项目,由于自己也很感兴趣,并且公司的业务正好也有类似需求,所以我花了点时间,实现了一版最简易的版本,支持将操作日志传递到消息队列中。</p> <h2 data-tool="mdnice编辑器" style="font-size: 22px;text-align: center;font-weight: bold;line-height: 1.1em;padding-top: 12px;padding-bottom: 12px;margin: 70px 30px 30px;border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);"><span style="float: left;display: block;width: 90%;border-top: 1px solid #000;height: 1px;line-height: 1px;margin-left: -5px;margin-top: -17px;"> </span><span style="display: block;width: 3px;margin-left: 5%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="display: block;-webkit-box-reflect: below 0em -webkit-gradient(linear,left top,left bottom, from(rgba(0,0,0,0)),to(rgba(255,255,255,0.1)));">实战:通过注解实现操作日志的记录</span><span style="display: block;width: 3px;margin-left: 95%;height: 3px;line-height: 3px;overflow: hidden;background-color: rgb(0, 0, 0);box-shadow: rgb(0, 0, 0) 3px 0px, rgb(0, 0, 0) 0px 3px, rgb(0, 0, 0) -3px 0px, rgb(0, 0, 0) 0px -3px;"></span><span style="float: right;display: block;width: 90%;border-bottom: 1px solid #000;height: 1px;line-height: 1px;margin-right: -5px;margin-top: 16px;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">大楼不是一天建成的,美团博客中描述的方案应该在公司内部已经非常成熟了,我也没有那么多精力一口气吃成一个胖子,我们从最基础的版本写起。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">我给自己的这个项目,或者说依赖包起名为log-record-starter,一方面遵循springboot-starter命名规范,一方面也表明项目的用处,记录日志。</p> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;background-color: #000;color: #fff;padding: 2px 10px;width: fit-content;font-size: 17px;margin: 60px auto 10px;margin-top: 25px;"><span style="display: none;"></span>开启项目之前,先问问自己<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">Q:你这个依赖包,又是一个冗余的造轮子吧?市面上这种东西是不是已经够多了?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">A:本着有现成轮子绝不造轮子的原则,我在Github和其他网站进行了一系列的相关搜索,Github有几个类似的实现项目,不过都以个人实现为主,没有一个具有一定影响力的成熟项目。<strong>基于我在自己的业务项目中拥有实际的场景需求,并且目前还没有满足我需求的现成可接入依赖,我才开始这个依赖包的代码编写。</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">Q:我用了你这个依赖包,是不是很复杂?之后你不维护了的话,是不是坑我们这些吃螃蟹的?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">A:依赖包的维护问题一直是一个大问题,本着最小依赖,尽量可扩展的原则。<strong>本库特点如下:</strong></p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;list-style-type: square;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">使用SpringBoot Starter,接入只需要简单引入一个依赖。</strong> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">通过Spring Spel表达式拿到参数,对你的业务逻辑没有侵入性。</strong> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 默认使用RabbitMq传递日志消息,日志操作解耦。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 之后会引入其他数据源,例如Kafka等(毕竟还要给三歪的项目用,我没有被三歪绑架,嗯,绝对没有)。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">好了,这就是我想说在前面的话。下面就是该项目的使用介绍和应用场景介绍。</p> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;background-color: #000;color: #fff;padding: 2px 10px;width: fit-content;font-size: 17px;margin: 60px auto 10px;margin-top: 25px;"><span style="display: none;"></span>Log-record-starter 一句话介绍<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">本项目支持用户使用注解的方式从方法中获取操作日志,并推送到指定数据源</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><strong>只需要简单的加上一个@OperationLog便可以将方法的参数,返回结果甚至是异常堆栈通过消息队列发送出去,统一处理。</strong></p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7j1UQofaR9eRUFTcVBXBeXpxMo1ekwm4g3wOXgt6OiaPaCINMqnKpico7AZOpCwnicRic6LfmzugVFL1qK8nicrSzPXvHv3IUDI86/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;">@OperationLog(bizType&nbsp;=&nbsp;<span style="color: #D69D85;line-height: 26px;">"bizType"</span>,&nbsp;bizId&nbsp;=&nbsp;<span style="color: #D69D85;line-height: 26px;">"#request.orderId"</span>,&nbsp;pipeline&nbsp;=&nbsp;DataPipelineEnum.QUEUE)<br>public&nbsp;Response&lt;BaseResult&gt;&nbsp;<span style="color: #569CD6;line-height: 26px;">function</span>(Request&nbsp;request)&nbsp;{<br>&nbsp;&nbsp;//&nbsp;方法执行逻辑<br>}<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;background-color: #000;color: #fff;padding: 2px 10px;width: fit-content;font-size: 17px;margin: 60px auto 10px;margin-top: 25px;"><span style="display: none;"></span>使用方法<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><strong>只需要简单的三步:</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">第一步:SpringBoot项目中引入依赖</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7j1UQofaR9eRUFTcVBXBeXpxMo1ekwm4g3wOXgt6OiaPaCINMqnKpico7AZOpCwnicRic6LfmzugVFL1qK8nicrSzPXvHv3IUDI86/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;">&lt;dependency&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;cn.monitor4all&lt;/groupId&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&gt;<span style="color: #4EC9B0;line-height: 26px;">log</span>-record-starter&lt;/artifactId&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;version&gt;1.0.0&lt;/version&gt;<br>&lt;/dependency&gt;<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">这里先打断一下,由于Maven公共仓库,是全球唯一托管的,个人开发的项目要提交上去,需要复杂的审核流程,我搞了一会没搞定,就先将包传到了Github Package上(实际就是Github的私有Maven库),所以大家引入依赖后,是不会直接拉到包的,需要配置下你的Maven settings.xml文件。(<strong>之后我肯定想办法发到公共仓库,呜呜呜~</strong>)</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">配置很简单,两步,一步是去Github登录,到自己的Settings中,申请一个token,拿到一串字符串。</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-fileid="100006242" data-ratio="0.728744939271255" src="/upload/e7a1b5d1b34d11d22d3f495c04eeae08.jpg" data-type="jpeg" data-w="1482" style="display: block;margin-right: auto;margin-left: auto;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 12px;"> image-20211106162359065 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">第二步,找到你的settings.xml文件,添加上:</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7j1UQofaR9eRUFTcVBXBeXpxMo1ekwm4g3wOXgt6OiaPaCINMqnKpico7AZOpCwnicRic6LfmzugVFL1qK8nicrSzPXvHv3IUDI86/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;">activeProfiles&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;activeProfile&gt;github&lt;/activeProfile&gt;<br>&nbsp;&nbsp;&lt;/activeProfiles&gt;<br><br>&nbsp;&nbsp;&lt;profiles&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;profile&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;id&gt;github&lt;/id&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;repositories&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;repository&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;id&gt;central&lt;/id&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;url&gt;https://repo1.maven.org/maven2&lt;/url&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/repository&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;repository&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;id&gt;github&lt;/id&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;url&gt;https://maven.pkg.github.com/OWNER/REPOSITORY&lt;/url&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;snapshots&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;enabled&gt;<span style="color: #569CD6;line-height: 26px;">true</span>&lt;/enabled&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/snapshots&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/repository&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/repositories&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/profile&gt;<br>&nbsp;&nbsp;&lt;/profiles&gt;<br><br>&nbsp;&nbsp;&lt;servers&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;server&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;id&gt;github&lt;/id&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;username&gt;这里填写你的Github用户名&lt;/username&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;password&gt;这里填写你刚才申请的token&lt;/password&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/server&gt;<br>&nbsp;&nbsp;&lt;/servers&gt;<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">还搞不定的同学,这里是Github官方中文教程:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><span style="color: rgb(2, 30, 170);">https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-apache-maven-registry</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">重启下你的IDEA,能看到下面这个,应该你的settings.xml生效了。</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-fileid="100006239" data-ratio="1.0502392344497609" src="/upload/e2ddd87bf8d397c1b3c84d135c597dca.jpg" data-type="jpeg" data-w="836" style="display: block;margin-right: auto;margin-left: auto;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">目前我的版本号是1.0.0,之后会更新,未来最新版本号在我仓库查询:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">https://github.com/qqxx6661/logRecord</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">第二步:在Spring配置文件中添加RabbitMq数据源配置</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">在自己公司里由于阿里封装了自己的MQ叫做MetaQ,并没有对外开源,所以这里先接入了RabbitMQ,也算是比较通用,图个方便。未来会接其他数据源。RabbitMq的安装在这里不展开了,实在是不想把篇幅拉得太大,大家可以自行谷歌下,比如“Docker安装RabbitMq”类似的文章,几分钟就可以设置安装好。</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7j1UQofaR9eRUFTcVBXBeXpxMo1ekwm4g3wOXgt6OiaPaCINMqnKpico7AZOpCwnicRic6LfmzugVFL1qK8nicrSzPXvHv3IUDI86/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;"><span style="color: #4EC9B0;line-height: 26px;">log</span>-record.rabbitmq.host=localhost<br><span style="color: #4EC9B0;line-height: 26px;">log</span>-record.rabbitmq.port=5672<br><span style="color: #4EC9B0;line-height: 26px;">log</span>-record.rabbitmq.username=admin<br><span style="color: #4EC9B0;line-height: 26px;">log</span>-record.rabbitmq.password=xxxxxxxx<br><span style="color: #4EC9B0;line-height: 26px;">log</span>-record.rabbitmq.queue-name=logrecord<br><span style="color: #4EC9B0;line-height: 26px;">log</span>-record.rabbitmq.routing-key=<br><span style="color: #4EC9B0;line-height: 26px;">log</span>-record.rabbitmq.exchange-name=logrecord<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><br></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">第三步:在你自己的项目中,在需要记录日志的方法上,添加注解。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><br></p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7j1UQofaR9eRUFTcVBXBeXpxMo1ekwm4g3wOXgt6OiaPaCINMqnKpico7AZOpCwnicRic6LfmzugVFL1qK8nicrSzPXvHv3IUDI86/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;">@OperationLog(bizType&nbsp;=&nbsp;<span style="color: #D69D85;line-height: 26px;">"bizType"</span>,&nbsp;bizId&nbsp;=&nbsp;<span style="color: #D69D85;line-height: 26px;">"#request.orderId"</span>,&nbsp;pipeline&nbsp;=&nbsp;DataPipelineEnum.QUEUE)<br>public&nbsp;Response&lt;BaseResult&gt;&nbsp;<span style="color: #569CD6;line-height: 26px;">function</span>(Request&nbsp;request)&nbsp;{<br>&nbsp;//&nbsp;方法执行逻辑<br>}</code></pre> <p><br></p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;list-style-type: square;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> (必填)bizType:业务类型 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> (必填)bizId:唯一业务ID(支持SpEL表达式) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> (必填)pipeline:数据管道,目前只有QUEUE一个数据管道,后续可考虑接入更多数据源 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> (非必填)msg:需要传递的其他数据(支持SpEL表达式) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> (非必填)tag:自定义标签 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;background-color: #000;color: #fff;padding: 2px 10px;width: fit-content;font-size: 17px;margin: 60px auto 10px;margin-top: 25px;"><span style="display: none;"></span>代码工作原理<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">由于采用的是SpringBoot Starter方式,所以只要你是用的是SpringBoot,会自动扫描到依赖包中的类,并自动通过Spring进行配置和管理。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">该注解通过在切面中解析SpEL参数(啥事SpEL?快去谷歌下,之后要讲),将数据发往数据源。目前仅支持RabbitMq,发送的消息体如下:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">方法处理正常发送消息体:</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7j1UQofaR9eRUFTcVBXBeXpxMo1ekwm4g3wOXgt6OiaPaCINMqnKpico7AZOpCwnicRic6LfmzugVFL1qK8nicrSzPXvHv3IUDI86/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;">[LogDTO(logId=3771ff1e-e5ff-4251-a534-31dab5b666b3,&nbsp;bizId=str,&nbsp;bizType=testType1,&nbsp;exception=null,&nbsp;operateDate=Sat&nbsp;Nov&nbsp;06&nbsp;20:08:54&nbsp;CST&nbsp;2021,&nbsp;success=<span style="color: #569CD6;line-height: 26px;">true</span>,&nbsp;msg={<span style="color: #D69D85;line-height: 26px;">"testList"</span>:[<span style="color: #D69D85;line-height: 26px;">"1"</span>,<span style="color: #D69D85;line-height: 26px;">"2"</span>,<span style="color: #D69D85;line-height: 26px;">"3"</span>],<span style="color: #D69D85;line-height: 26px;">"testStr"</span>:<span style="color: #D69D85;line-height: 26px;">"str"</span>},&nbsp;tag=operation)]<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><br></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">方法处理异常发送消息体:</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7j1UQofaR9eRUFTcVBXBeXpxMo1ekwm4g3wOXgt6OiaPaCINMqnKpico7AZOpCwnicRic6LfmzugVFL1qK8nicrSzPXvHv3IUDI86/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;">[LogDTO(logId=d162b2db-2346-4144-8cd4-aea900e4682b,&nbsp;bizId=str,&nbsp;bizType=testType1,&nbsp;exception=testError,&nbsp;operateDate=Sat&nbsp;Nov&nbsp;06&nbsp;20:09:24&nbsp;CST&nbsp;2021,&nbsp;success=<span style="color: #569CD6;line-height: 26px;">false</span>,&nbsp;msg={<span style="color: #D69D85;line-height: 26px;">"testList"</span>:[<span style="color: #D69D85;line-height: 26px;">"1"</span>,<span style="color: #D69D85;line-height: 26px;">"2"</span>,<span style="color: #D69D85;line-height: 26px;">"3"</span>],<span style="color: #D69D85;line-height: 26px;">"testStr"</span>:<span style="color: #D69D85;line-height: 26px;">"str"</span>},&nbsp;tag=operation)]<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><br></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">LogDTO是定义的消息结构:</p> <pre data-tool="mdnice编辑器" style="box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;margin-right: auto;margin-left: auto;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7j1UQofaR9eRUFTcVBXBeXpxMo1ekwm4g3wOXgt6OiaPaCINMqnKpico7AZOpCwnicRic6LfmzugVFL1qK8nicrSzPXvHv3IUDI86/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;">logId:生成的UUID<br>bizId:注解中传递的bizId<br>bizType:注解中传递的bizType<br>exception:若方法执行失败,写入执行的异常信息<br>operateDate:操作执行的当前时间<br>success:方式是否执行成功<br>msg:注解中传递的tag<br>tag:注解中传递的tag<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><br></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">我还加上了重复注解的支持,可以在一个方法上同时加多个@OperationLog,下图是最终使用效果,可以看到,有几个@OperationLog,就能同时发送多条日志:</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-fileid="100006240" data-ratio="0.5378486055776892" src="/upload/7f156c3f2a5e400a9a205351d64b8b7b.jpg" data-type="jpeg" data-w="2008" style="display: block;margin-right: auto;margin-left: auto;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><strong>项目具体的实现原理和细节,放在下一篇文章详细讲。</strong>(肯定会填坑)</p> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;background-color: #000;color: #fff;padding: 2px 10px;width: fit-content;font-size: 17px;margin: 60px auto 10px;margin-top: 25px;"><span style="display: none;"></span>应用场景<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">以下罗列了一些实际的应用场景,包括我业务中实际使用,并且已经上线使用的场景。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">一、特定操作记录日志:如文章最上面一张CRM系统的图描述的那样,在用户进行了编辑操作后,拿到用户操作的数据,执行日志写入。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">二、特定操作触发通知:由于我的业务是接手了好几个仓库,并且这几个仓库的操作串成了一条完成链路,我需要在链路的某个节点触发给用户的提醒,如果写硬编码也可以实现,但是远不如在方法上使用注解发送消息来得方便。例如下方在下单方法调用后发送消息。</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-fileid="100006244" data-ratio="0.08775654635527247" src="/upload/f5baf8c70d2b60111c423ed9913b1731.jpg" data-type="jpeg" data-w="2826" style="display: block;margin-right: auto;margin-left: auto;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">三、特定操作更新数据表:我的业务中,几个系统互相吞吐数据,订单的一部分数据存留在外部系统里,我们最终目标想要将其中一个系统替代掉,所以需要拦截他们的数据,恰好几个系统是使用LINK作为网关的,我们将数据请求拦截一层,并将拦截的方法使用该二方库进行全部参数的发送,将数据同步写入我们自己的数据库中,实现”双写“。</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-fileid="100006245" data-ratio="0.07226107226107226" src="/upload/48a4b5c5c487362424b01bb5caee61b9.jpg" data-type="jpeg" data-w="2574" style="display: block;margin-right: auto;margin-left: auto;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">四、跨多应用数据聚合操作:和”三“类似,在多个应用中,如果需要做行为相同的业务逻辑,完全可以在各个系统中将数据发送到同一个消息队列中,再进行统一处理。</p> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;background-color: #000;color: #fff;padding: 2px 10px;width: fit-content;font-size: 17px;margin: 60px auto 10px;margin-top: 25px;"><span style="display: none;"></span>附录:Demo<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;">最后,肯定有小伙伴希望有一个完整的使用Demo,这就奉上!</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;"><span style="color: rgb(2, 30, 170);">https://github.com/qqxx6661/systemLog</span></p> </section>

聊聊 sql 优化的 15 个小技巧

作者:微信小助手

<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;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;"> <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;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">sql 优化是一个大家都比较关注的热门话题,无论你在面试,还是工作中,都很有可能会遇到。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">如果某天你负责的某个线上接口,出现了性能问题,需要做优化。那么你首先想到的很有可能是优化 sql 语句,因为它的改造成本相对于代码来说也要小得多。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">那么,如何优化 sql 语句呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这篇文章从 15 个方面,分享了 sql 优化的一些小技巧,希望对你有所帮助。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&amp;mid=2247487551&amp;idx=1&amp;sn=18f64ba49f3f0f9d8be9d1fdef8857d9&amp;scene=21#wechat_redirect" textvalue="你已选中了添加链接的内容" linktype="text" imgurl="" imgdata="null" tab="innerlink" data-linktype="1"><span class="js_jump_icon h5_image_link" data-positionback="static" style="top: auto;left: auto;margin: 0px;right: auto;bottom: auto;"><img class="rich_pages wxw-img" data-ratio="1.4396782841823057" src="/upload/4bba152a865162a37faf7603c93b8232.png" data-type="png" data-w="746" style="display: block;margin: 0px;"></span></a> </figure> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;border-left-color: rgba(0, 0, 0, 0.4);background: rgba(0, 0, 0, 0.05);color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">推荐下自己做的 Spring Boot 的实战项目:</p> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">https://github.com/YunaiV/ruoyi-vue-pro</p> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><span style="display: none;"></span><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&amp;mid=2247487551&amp;idx=1&amp;sn=18f64ba49f3f0f9d8be9d1fdef8857d9&amp;scene=21#wechat_redirect" style="color: rgb(30, 107, 184);border-bottom: 1px solid rgb(30, 107, 184);" data-linktype="2">1 避免使用 select * </a></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">很多时候,我们写 sql 语句时,为了方便,喜欢直接使用<code style="font-size: 14px;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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">select *</code>,一次性查出表中所有列的数据。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>反例:</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(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh103SUhrw9QoyS24VwzKER1QcTtTUmcibOjKueLDk9pbm9yXUKgxpXiaRJXc1l1KsjlOovF1MXqQ0OpD/640?wx_fmt=svg&quot;) 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: #c678dd;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #c678dd;line-height: 26px;">from</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">user</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">where</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">id</span>=<span style="color: #d19a66;line-height: 26px;">1</span>;<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在实际业务场景中,可能我们真正需要使用的只有其中一两列。查了很多数据,但是不用,白白浪费了数据库资源,比如:内存或者 cpu。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">此外,多查出来的数据,通过网络 IO 传输的过程中,也会增加数据传输的时间。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">还有一个最重要的问题是:<code style="font-size: 14px;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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">select *</code>不会走<code style="font-size: 14px;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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">覆盖索引</code>,会出现大量的<code style="font-size: 14px;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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">回表</code>操作,而从导致查询 sql 的性能很低。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">那么,如何优化呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>正例:</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(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh103SUhrw9QoyS24VwzKER1QcTtTUmcibOjKueLDk9pbm9yXUKgxpXiaRJXc1l1KsjlOovF1MXqQ0OpD/640?wx_fmt=svg&quot;) 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: #c678dd;line-height: 26px;">select</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">name</span>,age&nbsp;<span style="color: #c678dd;line-height: 26px;">from</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">user</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">where</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">id</span>=<span style="color: #d19a66;line-height: 26px;">1</span>;<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">sql 语句查询时,只查需要用到的列,多余的列根本无需查出来。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;border-left-color: rgba(0, 0, 0, 0.4);background: rgba(0, 0, 0, 0.05);color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">推荐下自己做的 Spring Cloud 的实战项目:</p> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">https://github.com/YunaiV/onemall</p> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><span style="display: none;"></span><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&amp;mid=2247487551&amp;idx=1&amp;sn=18f64ba49f3f0f9d8be9d1fdef8857d9&amp;scene=21#wechat_redirect" style="color: rgb(30, 107, 184);border-bottom: 1px solid rgb(30, 107, 184);" data-linktype="2">2 用 union all 代替 union</a></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">我们都知道 sql 语句使用<code style="font-size: 14px;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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">union</code>关键字后,可以获取排重后的数据。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">而如果使用<code style="font-size: 14px;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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">union all</code>关键字,可以获取所有数据,包含重复的数据。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>反例:</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(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh103SUhrw9QoyS24VwzKER1QcTtTUmcibOjKueLDk9pbm9yXUKgxpXiaRJXc1l1KsjlOovF1MXqQ0OpD/640?wx_fmt=svg&quot;) 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: #c678dd;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #c678dd;line-height: 26px;">from</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">user</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">where</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">id</span>=<span style="color: #d19a66;line-height: 26px;">1</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">union</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(<span style="color: #c678dd;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #c678dd;line-height: 26px;">from</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">user</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">where</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">id</span>=<span style="color: #d19a66;line-height: 26px;">2</span>);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">排重的过程需要遍历、排序和比较,它更耗时,更消耗 cpu 资源。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">所以如果能用 union all 的时候,尽量不用 union。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>正例:</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(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh103SUhrw9QoyS24VwzKER1QcTtTUmcibOjKueLDk9pbm9yXUKgxpXiaRJXc1l1KsjlOovF1MXqQ0OpD/640?wx_fmt=svg&quot;) 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: #c678dd;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #c678dd;line-height: 26px;">from</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">user</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">where</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">id</span>=<span style="color: #d19a66;line-height: 26px;">1</span>)&nbsp;<br><span style="color: #c678dd;line-height: 26px;">union</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">all</span><br>(<span style="color: #c678dd;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #c678dd;line-height: 26px;">from</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">user</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">where</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">id</span>=<span style="color: #d19a66;line-height: 26px;">2</span>);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">除非是有些特殊的场景,比如 union all 之后,结果集中出现了重复数据,而业务场景中是不允许产生重复数据的,这时可以使用 union。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><span style="display: none;"></span><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&amp;mid=2247487551&amp;idx=1&amp;sn=18f64ba49f3f0f9d8be9d1fdef8857d9&amp;scene=21#wechat_redirect" style="color: rgb(30, 107, 184);border-bottom: 1px solid rgb(30, 107, 184);" data-linktype="2">3 小表驱动大表</a></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">小表驱动大表,也就是说用小表的数据集驱动大表的数据集。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">假如有 order 和 user 两张表,其中 order 表有 10000 条数据,而 user 表有 100 条数据。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这时如果想查一下,所有有效的用户下过的订单列表。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">可以使用<code style="font-size: 14px;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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">in</code>关键字实现:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh103SUhrw9QoyS24VwzKER1QcTtTUmcibOjKueLDk9pbm9yXUKgxpXiaRJXc1l1KsjlOovF1MXqQ0OpD/640?wx_fmt=svg&quot;) 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: #c678dd;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #c678dd;line-height: 26px;">from</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">order</span><br><span style="color: #c678dd;line-height: 26px;">where</span>&nbsp;user_id&nbsp;<span style="color: #c678dd;line-height: 26px;">in</span>&nbsp;(<span style="color: #c678dd;line-height: 26px;">select</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">id</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">from</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">user</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">where</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">status</span>=<span style="color: #d19a66;line-height: 26px;">1</span>)<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">也可以使用<code style="font-size: 14px;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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">exists</code>关键字实现:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh103SUhrw9QoyS24VwzKER1QcTtTUmcibOjKueLDk9pbm9yXUKgxpXiaRJXc1l1KsjlOovF1MXqQ0OpD/640?wx_fmt=svg&quot;) 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: #c678dd;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #c678dd;line-height: 26px;">from</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">order</span><br><span style="color: #c678dd;line-height: 26px;">where</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">exists</span>&nbsp;(<span style="color: #c678dd;line-height: 26px;">select</span>&nbsp;<span style="color: #d19a66;line-height: 26px;">1</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">from</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">user</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">where</span>&nbsp;order.user_id&nbsp;=&nbsp;user.id&nbsp;<span style="color: #c678dd;line-height: 26px;">and</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">status</span>=<span style="color: #d19a66;line-height: 26px;">1</span>)<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">前面提到的这种业务场景,使用 in 关键字去实现业务需求,更加合适。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">为什么呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">因为如果 sql 语句中包含了 in 关键字,则它会优先执行 in 里面的<code style="font-size: 14px;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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">子查询语句</code>,然后再执行 in 外面的语句。如果 in 里面的数据量很少,作为条件查询速度更快。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">而如果 sql 语句中包含了 exists 关键字,它优先执行 exists 左边的语句(即主查询语句)。然后把它作为条件,去跟右边的语句匹配。如果匹配上,则可以查询出数据。如果匹配不上,数据就被过滤掉了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这个需求中,order 表有 10000 条数据,而 user 表有 100 条数据。order 表是大表,user 表是小表。如果 order 表在左边,则用 in 关键字性能更好。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">总结一下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="font-size: 14px;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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">in</code> 适用于左边大表,右边小表。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="font-size: 14px;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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">exists</code> 适用于左边小表,右边大表。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">不管是用 in,还是 exists 关键字,其核心思想都是用小表驱动大表。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><span style="display: none;"></span><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&amp;mid=2247487551&amp;idx=1&amp;sn=18f64ba49f3f0f9d8be9d1fdef8857d9&amp;scene=21#wechat_redirect" style="color: rgb(30, 107, 184);border-bottom: 1px solid rgb(30, 107, 184);" data-linktype="2">4 批量操作</a></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">如果你有一批数据经过业务处理之后,需要插入数据,该怎么办?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>反例:</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(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh103SUhrw9QoyS24VwzKER1QcTtTUmcibOjKueLDk9pbm9yXUKgxpXiaRJXc1l1KsjlOovF1MXqQ0OpD/640?wx_fmt=svg&quot;) 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;">for(Order&nbsp;order:&nbsp;list){<br>&nbsp;&nbsp;&nbsp;orderMapper.insert(<span style="color: #c678dd;line-height: 26px;">order</span>):<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在循环中逐条插入数据。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh103SUhrw9QoyS24VwzKER1QcTtTUmcibOjKueLDk9pbm9yXUKgxpXiaRJXc1l1KsjlOovF1MXqQ0OpD/640?wx_fmt=svg&quot;) 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: #c678dd;line-height: 26px;">insert</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">into</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">order</span>(<span style="color: #c678dd;line-height: 26px;">id</span>,code,user_id)&nbsp;<br><span style="color: #c678dd;line-height: 26px;">values</span>(<span style="color: #d19a66;line-height: 26px;">123</span>,<span style="color: #98c379;line-height: 26px;">'001'</span>,<span style="color: #d19a66;line-height: 26px;">100</span>);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">该操作需要多次请求数据库,才能完成这批数据的插入。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">但众所周知,我们在代码中,每次远程请求数据库,是会消耗一定性能的。而如果我们的代码需要请求多次数据库,才能完成本次业务功能,势必会消耗更多的性能。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">那么如何优化呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>正例:</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(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh103SUhrw9QoyS24VwzKER1QcTtTUmcibOjKueLDk9pbm9yXUKgxpXiaRJXc1l1KsjlOovF1MXqQ0OpD/640?wx_fmt=svg&quot;) 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;">orderMapper.insertBatch(list):<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">提供一个批量插入数据的方法。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh103SUhrw9QoyS24VwzKER1QcTtTUmcibOjKueLDk9pbm9yXUKgxpXiaRJXc1l1KsjlOovF1MXqQ0OpD/640?wx_fmt=svg&quot;) 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: #c678dd;line-height: 26px;">insert</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">into</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">order</span>(<span style="color: #c678dd;line-height: 26px;">id</span>,code,user_id)&nbsp;<br><span style="color: #c678dd;line-height: 26px;">values</span>(<span style="color: #d19a66;line-height: 26px;">123</span>,<span style="color: #98c379;line-height: 26px;">'001'</span>,<span style="color: #d19a66;line-height: 26px;">100</span>),(<span style="color: #d19a66;line-height: 26px;">124</span>,<span style="color: #98c379;line-height: 26px;">'002'</span>,<span style="color: #d19a66;line-height: 26px;">100</span>),(<span style="color: #d19a66;line-height: 26px;">125</span>,<span style="color: #98c379;line-height: 26px;">'003'</span>,<span style="color: #d19a66;line-height: 26px;">101</span>);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这样只需要远程请求一次数据库,sql 性能会得到提升,数据量越多,提升越大。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">但需要注意的是,不建议一次批量操作太多的数据,如果数据太多数据库响应也会很慢。批量操作需要把握一个度,建议每批数据尽量控制在 500 以内。如果数据多于 500,则分多批次处理。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><span style="display: none;"></span><a href="https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&amp;mid=2247487551&amp;idx=1&amp;sn=18f64ba49f3f0f9d8be9d1fdef8857d9&amp;scene=21#wechat_redirect" style="color: rgb(30, 107, 184);border-bottom: 1px solid rgb(30, 107, 184);" data-linktype="2">5 多用 limit</a></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">有时候,我们需要查询某些数据中的第一条,比如:查询某个用户下的第一个订单,想看看他第一次的首单时间。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>反例:</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(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hzVGicX27IG31py8tnzh103SUhrw9QoyS24VwzKER1QcTtTUmcibOjKueLDk9pbm9yXUKgxpXiaRJXc1l1KsjlOovF1MXqQ0OpD/640?wx_fmt=svg&quot;) 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: #c678dd;line-height: 26px;">select</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">id</span>,&nbsp;create_date&nbsp;<br>&nbsp;<span style="color: #c678dd;line-height: 26px;">from</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">order</span>&nbsp;<br><span style="color: #c678dd;line-height: 26px;">where</span>&nbsp;user

HBase数据同步|HBase表数据导出至Hive

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="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-size: 16px;margin-top: -1em;font-family: -apple-system, system-ui, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;"> <h1 data-tool="mdnice编辑器" style="font-weight: bold;margin-top: 1em;margin-bottom: 1em;font-size: 1.4em;padding-bottom: 0.3em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.6 !important;"><span style="display: none;line-height: 1.6 !important;"></span><span style="line-height: 1.6 !important;">目录导读</span><span style="line-height: 1.6 !important;"></span></h1> <p data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;font-size: inherit;line-height: 1.6 !important;"><br></p> <ul style="padding-left: 25px;margin-top: 1em;margin-bottom: 1em;line-height: 1.6 !important;" class="list-paddingleft-2"> <li style="line-height: 1.6 !important;"><p>1. 前言</p></li> <li style="line-height: 1.6 !important;"><p>2. 环境介绍</p></li> <li style="line-height: 1.6 !important;"><p>3. 核心代码</p></li> <ul style="padding-left: 25px;list-style-type: square;line-height: 1.6 !important;" class="list-paddingleft-2"> <li style="line-height: 1.6 !important;"><p>3.1 执行命令及注意事项</p></li> <li style="line-height: 1.6 !important;"><p>3.2 核心代码</p></li> </ul> <li style="line-height: 1.6 !important;"><p>4. 优化建议</p></li> <li style="line-height: 1.6 !important;"><p>5. 参考文章</p></li> </ul> <p data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;font-size: inherit;line-height: 1.6 !important;"><br></p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;margin-top: 1em;margin-bottom: 1em;font-size: 1.3em;padding-bottom: 0.3em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.6 !important;"><span style="display: none;line-height: 1.6 !important;"></span><span style="line-height: 1.6 !important;">1. 前言</span><span style="line-height: 1.6 !important;"></span></h2> <p data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;font-size: inherit;line-height: 1.6 !important;">本篇文章和大家分享使用 Spark 读取 HBase 表快照数据的实现细节,此方案适用于对 HBase 表中的数据进行离线 OLAP 处理或同步至 Hive 中。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;margin-top: 1em;margin-bottom: 1em;font-size: 1.3em;padding-bottom: 0.3em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.6 !important;"><span style="display: none;line-height: 1.6 !important;"></span><span style="line-height: 1.6 !important;">2. 环境介绍</span><span style="line-height: 1.6 !important;"></span></h2> <ul data-tool="mdnice编辑器" style="padding-left: 25px;margin-top: 1em;margin-bottom: 1em;line-height: 1.6 !important;" class="list-paddingleft-2"> <li style="line-height: 1.6 !important;"> <section style="color: rgb(1, 1, 1);margin-top: 0.3em;margin-bottom: 0.3em;line-height: 1.6 !important;"> <p style="margin-top: 1em;margin-bottom: 1em;font-size: inherit;color: rgb(51, 51, 51);line-height: 1.6 !important;"><code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(228, 105, 24);background-color: rgb(239, 239, 239);font-size: 0.875em;line-height: 1.6 !important;">spark2.4</code>跑在 kerberos 认证下的<code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(228, 105, 24);background-color: rgb(239, 239, 239);font-size: 0.875em;line-height: 1.6 !important;">hadoop2.6.0-cdh5.13.1</code>的 yarn 上,即大集群 A。</p> </section></li> <li style="line-height: 1.6 !important;"> <section style="color: rgb(1, 1, 1);margin-top: 0.3em;margin-bottom: 0.3em;line-height: 1.6 !important;"> <p style="margin-top: 1em;margin-bottom: 1em;font-size: inherit;color: rgb(51, 51, 51);line-height: 1.6 !important;">HBase 在单独的集群上,即 HBase 集群 B,集群版本是<code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(228, 105, 24);background-color: rgb(239, 239, 239);font-size: 0.875em;line-height: 1.6 !important;">hbase2.1.0-cdh6.3.2</code>。</p> </section></li> </ul> <h2 data-tool="mdnice编辑器" style="font-weight: bold;margin-top: 1em;margin-bottom: 1em;font-size: 1.3em;padding-bottom: 0.3em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.6 !important;"><span style="display: none;line-height: 1.6 !important;"></span><span style="line-height: 1.6 !important;">3. 核心代码</span><span style="line-height: 1.6 !important;"></span></h2> <h3 data-tool="mdnice编辑器" style="font-weight: bold;margin-top: 1em;margin-bottom: 1em;font-size: 1.2em;line-height: 1.6 !important;"><span style="display: none;line-height: 1.6 !important;"></span><span style="line-height: 1.6 !important;">3.1 执行命令及注意事项</span><span style="display: none;line-height: 1.6 !important;"></span></h3> <pre data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.6 !important;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/AYfn0IKjIIMJJmeFribJcYP9qvyN6icQzFaqzE6Q2HSbZnia4zAic0fzxLgHSJ8P389H779Ymo1EDxsII6kUDQTXrOQwTCbJyibHR/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;-webkit-overflow-scrolling: touch;font-size: .875em;padding-top: 15px;background: #1E1E1E;border-radius: 5px;line-height: 1.6 !important;">&nbsp;sh&nbsp;/opt/spark-2.4.0-bin-hadoop2.6/bin/spark-submit&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--master&nbsp;yarn&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--queue&nbsp;hbase_to_hive_queue&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--name&nbsp;hbase_to_hive&nbsp;\<br style="line-height: 1.6 !important;"><span style="color: #9B9B9B;line-height: 1.6 !important;">&nbsp;#</span><span style="line-height: 1.6 !important;">&nbsp;driver-memory&nbsp;可以调大点,否则可能会出现ApplicationMaster&nbsp;UI&nbsp;打开卡死的情况</span><br style="line-height: 1.6 !important;">&nbsp;--driver-memory&nbsp;16g&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--deploy-mode&nbsp;cluster&nbsp;\<br style="line-height: 1.6 !important;"><span style="color: #9B9B9B;line-height: 1.6 !important;">&nbsp;#</span><span style="line-height: 1.6 !important;">&nbsp;非kerberos集群无需</span><br style="line-height: 1.6 !important;">&nbsp;--principal&nbsp;{your&nbsp;principal}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--keytab&nbsp;/home/{current_user}/{current_user}.keytab&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--executor-memory&nbsp;12G&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--executor-cores&nbsp;4&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--num-executors&nbsp;20&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--conf&nbsp;spark.kryoserializer.buffer.max=512m&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--conf&nbsp;spark.shuffle.service.enabled=true&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--conf&nbsp;spark.speculation=true&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--conf&nbsp;spark.network.timeout=100000000&nbsp;\<br style="line-height: 1.6 !important;"><span style="color: #9B9B9B;line-height: 1.6 !important;">&nbsp;#</span><span style="line-height: 1.6 !important;">&nbsp;解决读取压缩表,发生sanppy等相关的异常</span><br style="line-height: 1.6 !important;">&nbsp;--conf&nbsp;spark.driver.extraClassPath=/opt/cloudera/parcels/CDH/lib/hadoop/lib/*&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--conf&nbsp;spark.driver.extraLibraryPath=/opt/cloudera/parcels/CDH/lib/hadoop/lib/native&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--conf&nbsp;spark.executor.extraClassPath=/opt/cloudera/parcels/CDH/lib/hadoop/lib/*&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--conf&nbsp;spark.executor.extraLibraryPath=/opt/cloudera/parcels/CDH/lib/hadoop/lib/native&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--files&nbsp;/etc/hive/conf/hive-site.xml&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;--class&nbsp;org.bigdata.leo.hbase.bukload.HBaseExportSnapshotToHive&nbsp;/data/hbase1/bigdata-hbase-bukload.jar&nbsp;\<br style="line-height: 1.6 !important;"><span style="color: #9B9B9B;line-height: 1.6 !important;">&nbsp;#</span><span style="line-height: 1.6 !important;">&nbsp;HBaseToHive需要的main函数传参,具体作用后文</span><br style="line-height: 1.6 !important;">&nbsp;-sourceHBaseZkAddress={sourceHBaseZkAddress}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-sourceHBaseZkPort={sourceHBaseZkPort}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-sourceHBaseTableName={sourceHBaseTableName}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-sourceHBaseTableFamilyName={sourceHBaseTableFamilyName}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-sourceHBaseFieldNameToLower={sourceHBaseFieldNameToLower}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-sourceHBaseTablePerRegionSplitNum={sourceHBaseTablePerRegionSplitNum}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-sourceHBaseTableSnapshotName={sourceHBaseTableSnapshotName}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-sourceHBaseDefaultFS={sourceHBaseDefaultFS}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-sourceHBaseRootDir={sourceHBaseRootDir}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-tmpStoreSnapshotPath={tmpStoreSnapshotPath}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-targetHiveTableName={targetHiveTableName}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-targetHiveTableNamePrimaryKeyName={targetHiveTableNamePrimaryKeyName}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-targetHiveTablePartitionOrNot={targetHiveTablePartitionOrNot}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-targetHiveTablePartitionFieldName={targetHiveTablePartitionFieldName}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-scanHBaseTableStartTimestamp={scanHBaseTableStartTimestamp}&nbsp;\<br style="line-height: 1.6 !important;">&nbsp;-scanHBaseTableEndTimestamp={scanHBaseTableEndTimestamp}<br style="line-height: 1.6 !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;font-size: inherit;line-height: 1.6 !important;">如上命令需要在大集群上提交并运行,然后读取 HBase 独立集群 B 上的数据。</p> <p data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;font-size: inherit;line-height: 1.6 !important;">大集群在 Kerberos 环境下,HBase 集群无 Kerberos,所以,Spark 命令提交时需要指定 kerberos 相关的参数,同时,Kerberos 集群访问非 Kerberos 集群的文件时,需要为设置 Hadoop 的参数:<code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(228, 105, 24);background-color: rgb(239, 239, 239);font-size: 0.875em;line-height: 1.6 !important;">ipc.client.fallback-to-simple-auth-allowed=true</code>。</p> <p data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;font-size: inherit;line-height: 1.6 !important;">但是,这个参数无论指定在 spark-conf 中随命令一块提交,亦或是显式声明在代码里,貌似都不起作用,原因猜测,newAPIHadoopRDD 这个 API 强制加载了本地 hdfs-site.xml 文件,源码如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.6 !important;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/AYfn0IKjIIMJJmeFribJcYP9qvyN6icQzFaqzE6Q2HSbZnia4zAic0fzxLgHSJ8P389H779Ymo1EDxsII6kUDQTXrOQwTCbJyibHR/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;-webkit-overflow-scrolling: touch;font-size: .875em;padding-top: 15px;background: #1E1E1E;border-radius: 5px;line-height: 1.6 !important;">&nbsp;&nbsp;<span style="line-height: 1.6 !important;"><span style="color: #569CD6;line-height: 1.6 !important;">def</span>&nbsp;<span style="line-height: 1.6 !important;">newAPIHadoopRDD</span></span>[<span style="color: #4EC9B0;line-height: 1.6 !important;">K</span>,&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">V</span>,&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">F</span>&nbsp;&lt;:&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">NewInputFormat</span>[<span style="color: #4EC9B0;line-height: 1.6 !important;">K</span>,&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">V</span>]](<br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;conf:&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">Configuration</span>&nbsp;=&nbsp;hadoopConfiguration,<br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fClass:&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">Class</span>[<span style="color: #4EC9B0;line-height: 1.6 !important;">F</span>],<br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;kClass:&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">Class</span>[<span style="color: #4EC9B0;line-height: 1.6 !important;">K</span>],<br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vClass:&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">Class</span>[<span style="color: #4EC9B0;line-height: 1.6 !important;">V</span>]):&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">RDD</span>[(<span style="color: #4EC9B0;line-height: 1.6 !important;">K</span>,&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">V</span>)]&nbsp;=&nbsp;withScope&nbsp;{<br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;assertNotStopped()<br style="line-height: 1.6 !important;"><br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #57A64A;font-style: italic;line-height: 1.6 !important;">//&nbsp;This&nbsp;is&nbsp;a&nbsp;hack&nbsp;to&nbsp;enforce&nbsp;loading&nbsp;hdfs-site.xml.</span><br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #57A64A;font-style: italic;line-height: 1.6 !important;">//&nbsp;See&nbsp;SPARK-11227&nbsp;for&nbsp;details.</span><br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #57A64A;font-style: italic;line-height: 1.6 !important;">//&nbsp;newAPIHadoopRDD&nbsp;这个API强制加载了本地hdfs-site.xml文件</span><br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">FileSystem</span>.getLocal(conf)<br style="line-height: 1.6 !important;"><br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #57A64A;font-style: italic;line-height: 1.6 !important;">//&nbsp;Add&nbsp;necessary&nbsp;security&nbsp;credentials&nbsp;to&nbsp;the&nbsp;JobConf.&nbsp;Required&nbsp;to&nbsp;access&nbsp;secure&nbsp;HDFS.</span><br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #569CD6;line-height: 1.6 !important;">val</span>&nbsp;jconf&nbsp;=&nbsp;<span style="color: #569CD6;line-height: 1.6 !important;">new</span>&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">JobConf</span>(conf)<br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">SparkHadoopUtil</span>.get.addCredentials(jconf)<br style="line-height: 1.6 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #569CD6;line-height: 1.6 !important;">new</span>&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">NewHadoopRDD</span>(<span style="color: #569CD6;line-height: 1.6 !important;">this</span>,&nbsp;fClass,&nbsp;kClass,&nbsp;vClass,&nbsp;jconf)<br style="line-height: 1.6 !important;">&nbsp;&nbsp;}<br style="line-height: 1.6 !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;font-size: inherit;line-height: 1.6 !important;">最终有效的解决办法是,在大集群的客户端<code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(228, 105, 24);background-color: rgb(239, 239, 239);font-size: 0.875em;line-height: 1.6 !important;">hdfs-site.xml</code>高级配置中设置此参数,然后在 yarn 上分发给所有的 node-manager 节点客户端,(此步骤不用重启 hadoop 相关的服务)。</p> <p data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;font-size: inherit;line-height: 1.6 !important;">操作 HBase Snappy 压缩的表时,遇到如下报错:</p> <pre data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.6 !important;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/AYfn0IKjIIMJJmeFribJcYP9qvyN6icQzFaqzE6Q2HSbZnia4zAic0fzxLgHSJ8P389H779Ymo1EDxsII6kUDQTXrOQwTCbJyibHR/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;-webkit-overflow-scrolling: touch;font-size: .875em;padding-top: 15px;background: #1E1E1E;border-radius: 5px;line-height: 1.6 !important;">·21/11/25&nbsp;17:47:16&nbsp;WARN&nbsp;scheduler.TaskSetManager:&nbsp;Lost&nbsp;task&nbsp;0.0&nbsp;<span style="color: #569CD6;line-height: 1.6 !important;">in</span>&nbsp;stage<br style="line-height: 1.6 !important;">0.0&nbsp;(TID&nbsp;0,&nbsp;centos-bigdata-datanode,&nbsp;executor&nbsp;1):&nbsp;org.apache.hadoop.hbas<br style="line-height: 1.6 !important;">e.DoNotRetryIOException:&nbsp;Compression&nbsp;algorithm&nbsp;<span style="color: #D69D85;line-height: 1.6 !important;">'snappy'</span>&nbsp;previously&nbsp;faile<br style="line-height: 1.6 !important;">d&nbsp;<span style="color: #4EC9B0;line-height: 1.6 !important;">test</span>.<br style="line-height: 1.6 !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;font-size: inherit;line-height: 1.6 !important;">一方面解决 snappy 相关 so 文件的安装部署问题(网上有很多资料可供参考,CDH 集群中默认会部署好),一方面在 Spark 提交命令(或环境变量)中指定 snappy(native)目录:</p> <pre data-tool="mdnice编辑器" style="margin-top: 1em;margin-bottom: 1em;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.6 !important;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/AYfn0IKjIIMJJmeFribJcYP9qvyN6icQzFaqzE6Q2HSbZnia4zAic0fzxLgHSJ8P389H779Ymo1EDxsII6kUDQTXrOQwTCbJyibHR/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;-webkit-overflow-scrolling: touch;font-size: .875em;padding-top: 15px;bac