文章列表

用了BigDecimal就不会资损?了解下BigDecimal这五个坑

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com"> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="letter-spacing: 0px;font-size: 14px;">上周看到一篇因为在金额计算中没有使用</span> <code style="letter-spacing: 0px;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.8 !important;"><span style="font-size: 14px;">BigDecimal</span></code> <span style="letter-spacing: 0px;font-size: 14px;">而导致故障的文章,但是除非在一些非常简单的场景,结算汇金类的业务也不会直接用</span> <code style="letter-spacing: 0px;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.8 !important;"><span style="font-size: 14px;">BigDecimal</span></code> <span style="letter-spacing: 0px;font-size: 14px;">来计算金额,原因有两点:<br></span> </section> <ol data-tool="mdnice编辑器" style="color: black;margin: 8px 0px;padding-left: 25px;list-style-type: decimal;line-height: 1.8 !important;" class="list-paddingleft-1"> <li style="line-height: 1.8 !important;"> <section style="text-align: left;color: rgb(1, 1, 1);margin-top: 0.3em;margin-bottom: 0.3em;font-weight: normal;line-height: 1.8 !important;"> <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;line-height: 1.8 !important;"><span style="font-size: 14px;">BigDecimal</span></code> <span style="font-size: 14px;">里面还是有很多隐蔽的坑的</span> </section></li> <li style="line-height: 1.8 !important;"> <section style="text-align: left;color: rgb(1, 1, 1);margin-top: 0.3em;margin-bottom: 0.3em;font-weight: normal;line-height: 1.8 !important;"> <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;line-height: 1.8 !important;"><span style="font-size: 14px;">BigDecimal</span></code> <span style="font-size: 14px;">没有提供金额的单位</span> </section></li> </ol> <h1 data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin: 0.3em 0px 15px;padding: 0px 0px 0.2em;font-weight: bold;font-size: 1.3em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.8 !important;"><span style="font-size: 14px;line-height: 1.8 !important;">1. <code style="line-height: 1.8 !important;">BigDecimal</code>中的五个容易踩的坑</span></h1> <h2 data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin: 0em 0px 15px;padding: 0px 0px 0.2em;font-weight: bold;font-size: 1.2em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.8 !important;"><span style="font-size: 14px;line-height: 1.8 !important;">1.1 <code style="line-height: 1.8 !important;">new BigDecimal()</code>还是<code style="line-height: 1.8 !important;">BigDecimal#valueOf()</code>?</span></h2> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">先看下面这段代码</span> </section> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">BigDecimal&nbsp;bd1&nbsp;=&nbsp;<span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">new</span>&nbsp;BigDecimal(<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">0.01</span>);<br style="line-height: 1.8 !important;">BigDecimal&nbsp;bd2&nbsp;=&nbsp;<span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">BigDecimal.valueOf</span>(<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">0.01</span>);<br style="line-height: 1.8 !important;">System.out.println(<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"bd1&nbsp;=&nbsp;"</span>&nbsp;+&nbsp;bd1);<br style="line-height: 1.8 !important;">System.out.println(<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"bd2&nbsp;=&nbsp;"</span>&nbsp;+&nbsp;bd2);<br style="line-height: 1.8 !important;"></span> </section></pre> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">输出到控制台的结果是:</span> </section> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">bd1&nbsp;=&nbsp;<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">0.01000000000000000020816681711721685132943093776702880859375</span><br style="line-height: 1.8 !important;">bd2&nbsp;=&nbsp;<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">0.01</span><br style="line-height: 1.8 !important;"></span> </section></pre> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">造成这种差异的原因是0.1这个数字计算机是无法精确表示的,送给</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">BigDecimal</span></code> <span style="font-size: 14px;">的时候就已经丢精度了,而</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">BigDecimal#valueOf</span></code> <span style="font-size: 14px;">的实现却完全不同</span> </section> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;"><span style="line-height: 1.8 !important;"><span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">public</span>&nbsp;<span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">static</span>&nbsp;BigDecimal&nbsp;<span style="color: rgb(153, 0, 0);font-weight: bold;line-height: 1.8 !important;">valueOf</span><span style="line-height: 1.8 !important;">(<span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">double</span>&nbsp;val)</span>&nbsp;</span>{<br style="line-height: 1.8 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;Reminder:&nbsp;a&nbsp;zero&nbsp;double&nbsp;returns&nbsp;'0.0',&nbsp;so&nbsp;we&nbsp;cannot&nbsp;fastpath</span><br style="line-height: 1.8 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;to&nbsp;use&nbsp;the&nbsp;constant&nbsp;ZERO.&nbsp;&nbsp;This&nbsp;might&nbsp;be&nbsp;important&nbsp;enough&nbsp;to</span><br style="line-height: 1.8 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;justify&nbsp;a&nbsp;factory&nbsp;approach,&nbsp;a&nbsp;cache,&nbsp;or&nbsp;a&nbsp;few&nbsp;private</span><br style="line-height: 1.8 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;constants,&nbsp;later.</span><br style="line-height: 1.8 !important;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">return</span>&nbsp;<span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">new</span>&nbsp;BigDecimal(Double.toString(val));<br style="line-height: 1.8 !important;">}<br style="line-height: 1.8 !important;"></span> </section></pre> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">它使用了浮点数相应的字符串来构造</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">BigDecimal</span></code> <span style="font-size: 14px;">对象,因此避免了精度问题。所以大家要尽量要使用字符串而不是浮点数去构造</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">BigDecimal</span></code> <span style="font-size: 14px;">对象,如果实在不行,就使用</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">BigDecimal#valueOf()</span></code> <span style="font-size: 14px;">方法吧。</span> </section> <h2 data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin: 0em 0px 15px;padding: 0px 0px 0.2em;font-weight: bold;font-size: 1.2em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.8 !important;"><span style="font-size: 14px;line-height: 1.8 !important;">1.2 等值比较</span></h2> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">BigDecimal&nbsp;bd1&nbsp;=&nbsp;<span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">new</span>&nbsp;BigDecimal(<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"1.0"</span>);<br style="line-height: 1.8 !important;">BigDecimal&nbsp;bd2&nbsp;=&nbsp;<span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">new</span>&nbsp;BigDecimal(<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"1.00"</span>);<br style="line-height: 1.8 !important;">System.out.println(bd1.equals(bd2));<br style="line-height: 1.8 !important;">System.out.println(bd1.compareTo(bd2));<br style="line-height: 1.8 !important;"></span> </section></pre> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">控制台的输出将会是:</span> </section> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;"><span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">false</span><br style="line-height: 1.8 !important;"><span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">0</span><br style="line-height: 1.8 !important;"></span> </section></pre> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">究其原因是,</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">BigDecimal</span></code> <span style="font-size: 14px;">中</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">equals</span></code> <span style="font-size: 14px;">方法的实现会比较两个数字的精度,而</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">compareTo</span></code> <span style="font-size: 14px;">方法则只会比较数值的大小。</span> </section> <h2 data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin: 0em 0px 15px;padding: 0px 0px 0.2em;font-weight: bold;font-size: 1.2em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.8 !important;"><span style="font-size: 14px;line-height: 1.8 !important;">1.3 <code style="line-height: 1.8 !important;">BigDecimal</code>并不代表无限精度</span></h2> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">先看这段代码</span> </section> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">BigDecimal&nbsp;a&nbsp;=&nbsp;<span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">new</span>&nbsp;BigDecimal(<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"1.0"</span>);<br style="line-height: 1.8 !important;">BigDecimal&nbsp;b&nbsp;=&nbsp;<span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">new</span>&nbsp;BigDecimal(<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"3.0"</span>);<br style="line-height: 1.8 !important;">a.divide(b)&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;results&nbsp;in&nbsp;the&nbsp;following&nbsp;exception.</span><br style="line-height: 1.8 !important;"></span> </section></pre> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">结果会抛出异常:</span> </section> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">java.lang.ArithmeticException:&nbsp;Non-terminating&nbsp;decimal&nbsp;expansion;&nbsp;no&nbsp;exact&nbsp;representable&nbsp;decimal&nbsp;result.<br style="line-height: 1.8 !important;"></span> </section></pre> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">关于这个异常,Oracle的官方文档有具体说明</span> </section> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations.</span> </section> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">大意是,如果除法的商的结果是一个无限小数但是我们期望返回精确的结果,那程序就会抛出异常。回到我们的这个例子,我们需要告诉</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">JVM</span></code> <span style="font-size: 14px;">我们不需要返回精确的结果就好了</span> </section> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">BigDecimal&nbsp;a&nbsp;=&nbsp;<span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">new</span>&nbsp;BigDecimal(<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"1.0"</span>);<br style="line-height: 1.8 !important;">BigDecimal&nbsp;b&nbsp;=&nbsp;<span style="color: rgb(51, 51, 51);font-weight: bold;line-height: 1.8 !important;">new</span>&nbsp;BigDecimal(<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"3.0"</span>);<br style="line-height: 1.8 !important;">a.divide(b,&nbsp;<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">2</span>,&nbsp;RoundingMode.HALF_UP)<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;0.33</span><br style="line-height: 1.8 !important;"></span> </section></pre> <h2 data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin: 0em 0px 15px;padding: 0px 0px 0.2em;font-weight: bold;font-size: 1.2em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.8 !important;"><span style="font-size: 14px;line-height: 1.8 !important;">1.4 <code style="line-height: 1.8 !important;">BigDecimal</code>转回<code style="line-height: 1.8 !important;">String</code>要小心</span></h2> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">BigDecimal&nbsp;d&nbsp;=&nbsp;BigDecimal.valueOf(<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">12334535345456700.12345634534534578901</span>);<br style="line-height: 1.8 !important;">String&nbsp;out&nbsp;=&nbsp;d.toString();&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;Or&nbsp;perform&nbsp;any&nbsp;formatting&nbsp;that&nbsp;needs&nbsp;to&nbsp;be&nbsp;done</span><br style="line-height: 1.8 !important;">System.out.println(out);&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;1.23345353454567E+16</span><br style="line-height: 1.8 !important;"></span> </section></pre> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">可以看到结果已经被转换成了科学计数法,可能这个并不是预期的结果</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">BigDecimal</span></code> <span style="font-size: 14px;">有三个方法可以转为相应的字符串类型,切记不要用错:</span> </section> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;"><span style="line-height: 1.8 !important;">String&nbsp;<span style="color: rgb(153, 0, 0);font-weight: bold;line-height: 1.8 !important;">toString</span><span style="line-height: 1.8 !important;">()</span></span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;有必要时使用科学计数法</span><br style="line-height: 1.8 !important;"><span style="line-height: 1.8 !important;">String&nbsp;<span style="color: rgb(153, 0, 0);font-weight: bold;line-height: 1.8 !important;">toPlainString</span><span style="line-height: 1.8 !important;">()</span></span>;&nbsp;&nbsp;&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;不使用科学计数法</span><br style="line-height: 1.8 !important;"><span style="line-height: 1.8 !important;">String&nbsp;<span style="color: rgb(153, 0, 0);font-weight: bold;line-height: 1.8 !important;">toEngineeringString</span><span style="line-height: 1.8 !important;">()</span></span>;&nbsp;&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;工程计算中经常使用的记录数字的方法,与科学计数法类似,但要求10的幂必须是3的倍数</span><br style="line-height: 1.8 !important;"></span> </section></pre> <h2 data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin: 0em 0px 15px;padding: 0px 0px 0.2em;font-weight: bold;font-size: 1.2em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.8 !important;"><span style="font-size: 14px;line-height: 1.8 !important;">1.5 执行顺序不能调换(乘法交换律失效)</span></h2> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">乘法满足交换律是一个常识,但是在计算机的世界里,会出现不满足乘法交换律的情况</span> </section> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">BigDecimal&nbsp;a&nbsp;=&nbsp;BigDecimal.valueOf(<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">1.0</span>);<br style="line-height: 1.8 !important;">BigDecimal&nbsp;b&nbsp;=&nbsp;BigDecimal.valueOf(<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">3.0</span>);<br style="line-height: 1.8 !important;">BigDecimal&nbsp;c&nbsp;=&nbsp;BigDecimal.valueOf(<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">3.0</span>);<br style="line-height: 1.8 !important;">System.out.println(a.divide(b,&nbsp;<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">2</span>,&nbsp;RoundingMode.HALF_UP).multiply(c));&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;0.990</span><br style="line-height: 1.8 !important;">System.out.println(a.multiply(c).divide(b,&nbsp;<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">2</span>,&nbsp;RoundingMode.HALF_UP));&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;1.00</span><br style="line-height: 1.8 !important;"></span> </section></pre> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">别小看这这0.01的差别,在汇金领域,会产生非常大的金额差异。</span> </section> <h1 data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin: 0.3em 0px 15px;padding: 0px 0px 0.2em;font-weight: bold;font-size: 1.3em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.8 !important;"><span style="font-size: 14px;line-height: 1.8 !important;">2. 最佳实践</span></h1> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">关于金额计算,很多业务团队会基于</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">BigDecimal</span></code> <span style="font-size: 14px;">再封装一个</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">Money</span></code> <span style="font-size: 14px;">类,其实我们直接可以用一个半官方的</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">Money</span></code> <span style="font-size: 14px;">类:JSR 354 ,虽然没能在</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">Java 9</span></code> <span style="font-size: 14px;">中成为</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">Java</span></code> <span style="font-size: 14px;">标准,很有可能集成到后续的</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">Java</span></code> <span style="font-size: 14px;">版本中成为官方库。</span> </section> <h2 data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin: 0em 0px 15px;padding: 0px 0px 0.2em;font-weight: bold;font-size: 1.2em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.8 !important;"><span style="font-size: 14px;line-height: 1.8 !important;">2.1 <code style="line-height: 1.8 !important;">maven</code>坐标</span></h2> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">&lt;dependency&gt;<br style="line-height: 1.8 !important;">&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;org.javamoney&lt;/groupId&gt;<br style="line-height: 1.8 !important;">&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&gt;moneta&lt;/artifactId&gt;<br style="line-height: 1.8 !important;">&nbsp;&nbsp;&nbsp;&nbsp;&lt;version&gt;<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">1.1</span>&lt;/version&gt;<br style="line-height: 1.8 !important;">&lt;/dependency&gt;<br style="line-height: 1.8 !important;"></span> </section></pre> <h2 data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin: 0em 0px 15px;padding: 0px 0px 0.2em;font-weight: bold;font-size: 1.2em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.8 !important;"><span style="font-size: 14px;line-height: 1.8 !important;">2.2 新建<code style="line-height: 1.8 !important;">Money</code>类</span></h2> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">CurrencyUnit&nbsp;cny&nbsp;=&nbsp;Monetary.getCurrency(<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"CNY"</span>);<br style="line-height: 1.8 !important;">Money&nbsp;money&nbsp;=&nbsp;Money.of(<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">1.0</span>,&nbsp;cny);&nbsp;<br style="line-height: 1.8 !important;"><span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;或者&nbsp;Money&nbsp;money&nbsp;=&nbsp;Money.of(1.0,&nbsp;"CNY");</span><br style="line-height: 1.8 !important;"><span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//System.out.println(money);</span><br style="line-height: 1.8 !important;"></span> </section></pre> <h2 data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin: 0em 0px 15px;padding: 0px 0px 0.2em;font-weight: bold;font-size: 1.2em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.8 !important;"><span style="font-size: 14px;line-height: 1.8 !important;">2.3 金额运算</span></h2> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">CurrencyUnit&nbsp;cny&nbsp;=&nbsp;Monetary.getCurrency(<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"CNY"</span>);<br style="line-height: 1.8 !important;">Money&nbsp;oneYuan&nbsp;=&nbsp;Money.of(<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">1.0</span>,&nbsp;cny);<br style="line-height: 1.8 !important;">Money&nbsp;threeYuan&nbsp;=&nbsp;oneYuan.add(Money.of(<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">2.0</span>,&nbsp;<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"CNY"</span>));&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//CNY&nbsp;3</span><br style="line-height: 1.8 !important;">Money&nbsp;tenYuan&nbsp;=&nbsp;oneYuan.multiply(<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">10</span>);&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;CNY&nbsp;10</span><br style="line-height: 1.8 !important;">Money&nbsp;fiveFen&nbsp;=&nbsp;oneYuan.divide(<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">2</span>);&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//CNY&nbsp;0.5</span><br style="line-height: 1.8 !important;"></span> </section></pre> <h2 data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin: 0em 0px 15px;padding: 0px 0px 0.2em;font-weight: bold;font-size: 1.2em;border-bottom: 1px solid rgb(223, 226, 229);line-height: 1.8 !important;"><span style="font-size: 14px;line-height: 1.8 !important;">2.4 比较相等</span></h2> <pre data-tool="mdnice编辑器" style="color: rgb(51, 51, 51);margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;line-height: 1.8 !important;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 0.875em;background: rgb(248, 248, 248);border-radius: 5px;margin-left: 0px;margin-right: 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">Money&nbsp;fiveFen&nbsp;=&nbsp;Money.of(<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">0.5</span>,&nbsp;<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"CNY"</span>);&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//CNY&nbsp;0.5</span><br style="line-height: 1.8 !important;">Money&nbsp;anotherFiveFen&nbsp;=&nbsp;Money.of(<span style="color: rgb(0, 128, 128);line-height: 1.8 !important;">0.50</span>,&nbsp;<span style="color: rgb(221, 17, 68);line-height: 1.8 !important;">"CNY"</span>);&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;CNY&nbsp;0.50</span><br style="line-height: 1.8 !important;">System.out.println(fiveFen.equals(anotherFiveFen));&nbsp;<span style="color: rgb(153, 153, 136);font-style: italic;line-height: 1.8 !important;">//&nbsp;true</span><br style="line-height: 1.8 !important;"></span> </section></pre> <section style="color: rgb(51, 51, 51);padding-top: 8px;padding-bottom: 8px;font-size: inherit;margin: 0.1em 0px;line-height: 1.8 !important;"> <span style="font-size: 14px;">可以看到,这个类对金额做了显性的抽象,增加了金额的单位,也避免了直接使用</span> <code style="word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #e46918;background-color: #efefef;font-size: .875em;line-height: 1.8 !important;"><span style="font-size: 14px;">BigDecimal</span></code> <span style="font-size: 14px;">的一些坑。</span> </section> </section>

字节最爱问的智力题,你会几道?

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0px;word-break: break-word;overflow-wrap: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin-bottom: 0px;" 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;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">在面试过程中,智力题的考察也经常出现,这种题的特点是如果你看过,那么很容易就能做出来,如果没加过那可能在面试过程中不太容易做出来,所以在面试过程中恰好问到看过的题也不要马上答出来,还是要假装思考一下,因为你脱口而出会让面试官发现你做过,给你换一道题的。</p> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">1. 只有两个无刻度的水桶,一个可以装6L水,一个可以装5L水,如何在桶里装入3L的水</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">提示:这个问题的本质就是利用两个水桶的已知容量倒来倒去,问题的解法并不唯一。</p> </blockquote> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 先将5L的桶装满,将5L的桶的水倒入6L的桶中。这时5L的桶是空的,6L的桶中有5L的水 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 再将5L的桶装满,倒入6L的桶中。这时5L的桶有4L的水,6L的桶是满的 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 将6L的桶中的水倒掉,5L的桶的水倒入6L的桶中。这时5L的桶是空的,6L的桶中有4L的水 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 将5L的桶装满,倒入6L的桶中。这时 <strong style="color: black;">5L的桶还有3L的水</strong>,6L的桶是满的。 </section></li> </ol> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">2. 25匹马,5个赛道,每次只能同时有5匹马跑,最少比赛几次选出最快的马?</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">这个题目字节经常问,大概思想就是先分5组跑,跑出每组第一名,将每组第一名放到一起跑,找出25匹马的第一名,然后找出2、3名,一共需要7次</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">前五次:将25匹马放到5个赛道比赛,找出每个赛道的第一名。假设A1、B1、C1、D1、E1分别为每组的第一名,如下图。</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.5547619047619048" src="/upload/904267f71c725c3bc67e947b1f8913f9.png" data-type="png" data-w="840" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">第六次:将A1、B1、C1、D1、E1放到一个赛道上找出第一名,假设为A1,其他四名分别为B1、C1、D1、E1。这时第一名已经找到了,还需找到二、三名。因为C1的速度比D1和E1的速度快,所以赛道D和赛道E的所有马都被淘汰了。有机会成为二、三名的马为A2、A3、B1、B2、C1这五匹马,即前五名在这个区域,并且第一名为A1。如下图</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.5020080321285141" src="/upload/8a06b14729683f454b0bf891140263ba.png" data-type="png" data-w="747" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">第七次:将A2、A3、B1、B2、C1放到一个赛道找出前两名,再加上A1,这就找到前三名了。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">延申:如果要找到前五名呢?</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>3.1000瓶药水里面只有1瓶是有毒的,毒发时间为24个小时,问需要多少只老鼠才能在24小时后试出那瓶有毒。<span style="display: none;"></span></h3> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">这个问题不太容易想到可以先记住答案,需要老鼠的数量为<span style="cursor:pointer;"><span role="presentation" data-formula="log_2 1000" data-formula-type="inline-equation" style=""> <svg xmlns="http://www.w3.org/2000/svg" role="img" focusable="false" viewbox="0 -694 3663.6 899" aria-hidden="true" style="vertical-align: -0.464ex;width: 8.289ex;height: 2.034ex;"> <g stroke="currentColor" fill="currentColor" stroke-width="0" transform="matrix(1 0 0 -1 0 0)"> <g data-mml-node="math"> <g data-mml-node="mi"> <path data-c="6C" d="M117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59Z"></path> </g> <g data-mml-node="mi" transform="translate(298, 0)"> <path data-c="6F" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path> </g> <g data-mml-node="msub" transform="translate(783, 0)"> <g data-mml-node="mi"> <path data-c="67" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path> </g> <g data-mml-node="mn" transform="translate(477, -150) scale(0.707)"> <path data-c="32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path> </g> </g> <g data-mml-node="mn" transform="translate(1663.6, 0)"> <path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path> <path data-c="30" d="M96 585Q152 666 249 666Q297 666 345 640T423 548Q460 465 460 320Q460 165 417 83Q397 41 362 16T301 -15T250 -22Q224 -22 198 -16T137 16T82 83Q39 165 39 320Q39 494 96 585ZM321 597Q291 629 250 629Q208 629 178 597Q153 571 145 525T137 333Q137 175 145 125T181 46Q209 16 250 16Q290 16 318 46Q347 76 354 130T362 333Q362 478 354 524T321 597Z" transform="translate(500, 0)"></path> <path data-c="30" d="M96 585Q152 666 249 666Q297 666 345 640T423 548Q460 465 460 320Q460 165 417 83Q397 41 362 16T301 -15T250 -22Q224 -22 198 -16T137 16T82 83Q39 165 39 320Q39 494 96 585ZM321 597Q291 629 250 629Q208 629 178 597Q153 571 145 525T137 333Q137 175 145 125T181 46Q209 16 250 16Q290 16 318 46Q347 76 354 130T362 333Q362 478 354 524T321 597Z" transform="translate(1000, 0)"></path> <path data-c="30" d="M96 585Q152 666 249 666Q297 666 345 640T423 548Q460 465 460 320Q460 165 417 83Q397 41 362 16T301 -15T250 -22Q224 -22 198 -16T137 16T82 83Q39 165 39 320Q39 494 96 585ZM321 597Q291 629 250 629Q208 629 178 597Q153 571 145 525T137 333Q137 175 145 125T181 46Q209 16 250 16Q290 16 318 46Q347 76 354 130T362 333Q362 478 354 524T321 597Z" transform="translate(1500, 0)"></path> </g> </g> </g> </svg></span></span></p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">为了简化问题,可以先假设有只有8瓶药水,其中有一瓶有毒,根据公式需要 <span style="cursor:pointer;"><span role="presentation" data-formula="log_2 8=3" data-formula-type="inline-equation" style=""> <svg xmlns="http://www.w3.org/2000/svg" role="img" focusable="false" viewbox="0 -694 3997.1 899" aria-hidden="true" style="vertical-align: -0.464ex;width: 9.043ex;height: 2.034ex;"> <g stroke="currentColor" fill="currentColor" stroke-width="0" transform="matrix(1 0 0 -1 0 0)"> <g data-mml-node="math"> <g data-mml-node="mi"> <path data-c="6C" d="M117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59Z"></path> </g> <g data-mml-node="mi" transform="translate(298, 0)"> <path data-c="6F" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path> </g> <g data-mml-node="msub" transform="translate(783, 0)"> <g data-mml-node="mi"> <path data-c="67" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path> </g> <g data-mml-node="mn" transform="translate(477, -150) scale(0.707)"> <path data-c="32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path> </g> </g> <g data-mml-node="mn" transform="translate(1663.6, 0)"> <path data-c="38" d="M70 417T70 494T124 618T248 666Q319 666 374 624T429 515Q429 485 418 459T392 417T361 389T335 371T324 363L338 354Q352 344 366 334T382 323Q457 264 457 174Q457 95 399 37T249 -22Q159 -22 101 29T43 155Q43 263 172 335L154 348Q133 361 127 368Q70 417 70 494ZM286 386L292 390Q298 394 301 396T311 403T323 413T334 425T345 438T355 454T364 471T369 491T371 513Q371 556 342 586T275 624Q268 625 242 625Q201 625 165 599T128 534Q128 511 141 492T167 463T217 431Q224 426 228 424L286 386ZM250 21Q308 21 350 55T392 137Q392 154 387 169T375 194T353 216T330 234T301 253T274 270Q260 279 244 289T218 306L210 311Q204 311 181 294T133 239T107 157Q107 98 150 60T250 21Z"></path> </g> <g data-mml-node="mo" transform="translate(2441.3, 0)"> <path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path> </g> <g data-mml-node="mn" transform="translate(3497.1, 0)"> <path data-c="33" d="M127 463Q100 463 85 480T69 524Q69 579 117 622T233 665Q268 665 277 664Q351 652 390 611T430 522Q430 470 396 421T302 350L299 348Q299 347 308 345T337 336T375 315Q457 262 457 175Q457 96 395 37T238 -22Q158 -22 100 21T42 130Q42 158 60 175T105 193Q133 193 151 175T169 130Q169 119 166 110T159 94T148 82T136 74T126 70T118 67L114 66Q165 21 238 21Q293 21 321 74Q338 107 338 175V195Q338 290 274 322Q259 328 213 329L171 330L168 332Q166 335 166 348Q166 366 174 366Q202 366 232 371Q266 376 294 413T322 525V533Q322 590 287 612Q265 626 240 626Q208 626 181 615T143 592T132 580H135Q138 579 143 578T153 573T165 566T175 555T183 540T186 520Q186 498 172 481T127 463Z"></path> </g> </g> </g> </svg></span></span>个老鼠</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">先将瓶子进行编号为0-7号,用位数表示老鼠,如下图,</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.9474835886214442" src="/upload/bf040ae7995b6e14e00d12141bb7c46b.png" data-type="png" data-w="457" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">将4、5、6、7号药水混合到一起喂给老鼠1,将2,3,6,7号药水混合喂给老鼠2,将1、3、5、7药水混合喂给老鼠3,观察老鼠是否中毒。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">中毒的老鼠标号为1,未中毒的老鼠标号为0,将三只老鼠标号组合到一起即为有毒药水的标号。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">例如,老鼠1中毒,老鼠2未中毒,老鼠3中毒。**那么三只老鼠的二进制表示为101,即5号药水有毒。**因为老鼠1中毒,说明4、5、6、7号药水中含有毒的药水。老鼠2未中毒,说明2、3、6、7无毒。老鼠3中毒,说明1、3、5、7中有一瓶有毒。所以有毒的为5号药水,其实和直接将二进制转化为十进制的结果是一样的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">回到正题,如果有1000瓶药水,则需要10只老鼠,因为10位二进制足以表示0-999。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">4.家里有两个孩子,一个是女孩,另一个也是女孩的概率是多少?</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">这是一个概率问题,答案是二分之一,看到这里脑瓜子嗡嗡的吧</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这个问题我也是看了B站的视频分析才搞明白咋回事,题目多少有些歧义,面试时说清楚就行了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">已知家里有两个孩子A和B,<strong>其中一个是女孩</strong>,关键问题就在其中一个是女陔这句话上。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">如果你理解为这个是指定了一个孩子为女孩,例如A为女孩,那么B也是女孩的概率显然为二分之一。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">如果你理解为A或B有一个孩子是女孩,问另一个孩子也是女陔的概率,这就是三分之一了。因为两个孩子的性别只有男男、男女、女男、女女四种组合,男男被排除了,剩下三种组合均符合题意,所以是三分之一。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">其实,题目本身应该是第二种理解的意思,告诉你了有一个是女孩并未明确说哪个是。但很多人看到题目就会先入为主,先指定了一个孩子为女孩,那另一个孩子为女孩的概率肯定是二分之一了,这是不正确的。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">5.烧一根不均匀的绳,从头烧到尾总共需要1个小时。现在有若干条材质相同的绳子,问如何用烧绳的方法来计时一个小时十五分钟呢?</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">这个问题的关键就是要知道绳子可以从两头烧</p> </blockquote> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 将绳子1从一段开始烧,同时将绳子2从两端烧,绳子2在半小时后烧完。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 这时开始计时,将绳子1的另一端点燃,从计时开始绳子1烧完后是15分钟,然后点燃绳子3的一端,绳子3烧完需要一个小时。加上刚才的15分钟正好是1小时15分钟 </section></li> </ol> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">6.一共12个一样的小球, 其中只有一个重量与其它不一样(未知轻重),给你一个天平,找出那个不同重量的球?</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">这个问题的思想是采用分治的思想。</p> </blockquote> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 将12个小球分为三组(因为分成两组不能找到重量不一样的球在哪组),为A组、B组、C组 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 将三组球分别两两称重,找到重量和另外两组不同的那一组(只要有两组可以使天平平衡,重量不一致的球必然在第三组)。假设坏的球在C组 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 将C组的球分成两组C1和C2,每组两个球,这时从A组和B组里找到两个正常的球,分别和C1和C2去称,天平不能平衡说明重量不一致的球就在哪组。假设在C1 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 将C1组的球分别和正常的球去称,天平不平衡时就能找到重量与其他不一致的球。 </section></li> </ol> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">7.有10瓶药,每瓶有10粒药,其中有一瓶是变质的。好药每颗重1克,变质的药每颗比好药重0.1克。问怎样用天秤称一次找出变质的那瓶药?</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 将这10瓶药标好号1-10。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 然后按照瓶子的标号取药,1号药瓶取1粒药,2号药瓶取2粒药,3号药瓶取3粒药,以此类推,取完10瓶药一起放到天平上去称。如果没有变质的药,重量应该是55克,这时多出几克,几号药瓶就是变质的。例如55.3克,那么变质的药就是3号药瓶的。 </section></li> </ol> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">8.你有两个罐子,50个红色弹球,50个蓝色弹球,如何将这100个球放入到两个罐子,随机选出一个罐子取出的球为红球的概率最大?</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这个问题应该是这几道题中最简单的了,将一个红球放到一个罐子中,另一个罐子放49个红球和50个蓝球,这样随便选出一个罐子取出红球的概率是1/2 * 1 + 1/2 * 49 /(49+50),接近0.75。</p> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzA4NjU1MzA2MQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/cBnxLn7axrz3ibWvTibUL3uUuTPVKC7u560T1No4czC6BD0ZWeod6fJiaUCicfRGdvquzibveQ9L9gFuAYAibx6ibgB6w/0?wx_fmt=png" data-nickname="路人zhang" data-alias="lu_ren_zhang" data-signature="西安电子科技大学硕士,拖延癌晚期患者,致力于分享计算机、通信行业的面试求职技巧及资料,闲聊程序员的未来发展出路及日常生活。" data-from="0"></mpprofile> </section> </section> <p style="margin-bottom: 0px;"><br></p>

MySQL的varchar水真的太深了,你真的会用吗?

作者:微信小助手

<section data-mpa-powered-by="yiban.io" style="max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;margin-bottom: 0px;"> <section powered-by="xiumi.us" style="max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;"> <section style="max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;"> <section style="max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;"> <section powered-by="xiumi.us" style="max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;"> <section> <p style="white-space: normal;text-align: center;"><span style="font-size: 13px;letter-spacing: 0.12px;color: rgb(255, 76, 65);"></span><br></p> <p style="margin: 0px 0px 0em;padding: 0px;outline: 0px;max-width: 100%;clear: both;min-height: 1em;color: rgb(34, 34, 34);font-family: system-ui, -apple-system, &quot;system-ui&quot;, &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;font-size: 17px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: 0.544px;orphans: 2;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;text-align: center;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.5513905683192262" data-s="300,640" src="/upload/25e77a0ae4a12f9550868c85b635746b.png" data-type="png" data-w="1654" style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;vertical-align: bottom;height: auto !important;visibility: visible !important;width: 677px !important;"></p> <h3 data-tool="mdnice编辑器" style="margin: 0px;padding: 0px;outline: 0px;font-weight: 400;font-size: 16px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;letter-spacing: 0.544px;orphans: 2;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;color: rgb(0, 0, 0);background-color: rgb(255, 255, 255);text-align: right;visibility: visible;"><em style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;font-style: italic;color: rgb(136, 136, 136);font-size: 12px;letter-spacing: 0.5px;visibility: visible;">来源:</em><em style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;font-style: italic;color: rgb(136, 136, 136);font-size: 12px;letter-spacing: 0.5px;visibility: visible;">liuchenyang0515.blog.csdn.net/article/</em><br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible;"></h3> <h3 data-tool="mdnice编辑器" style="margin: 0px;padding: 0px;outline: 0px;font-weight: 400;font-size: 16px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;letter-spacing: 0.544px;orphans: 2;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;color: rgb(0, 0, 0);background-color: rgb(255, 255, 255);text-align: right;visibility: visible;"><em style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;font-style: italic;color: rgb(136, 136, 136);font-size: 12px;letter-spacing: 0.5px;visibility: visible;">details/117524328</em></h3> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin: 0px;padding: 0px 10px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;orphans: 2;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;font-size: 16px;color: black;line-height: 1.6;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;visibility: visible;"> <h3 data-tool="mdnice编辑器" style="margin: 10px 0px 5px;padding: 10px 0px 0px;outline: 0px;font-weight: bold;font-size: 20px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;line-height: 1.4;visibility: visible;"><span style="margin: 0px;padding: 0px 0px 0px 20px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(81, 81, 81);font-size: 1em;border-left: 3px solid rgb(249, 191, 69);visibility: visible;">1. InnoDB是干嘛的?</span></h3> <p data-tool="mdnice编辑器" style="margin: 0px 0px 20px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;line-height: 1.8em;color: rgb(58, 58, 58);visibility: visible;">InnoDB是一个将表中的数据存储到磁盘上的存储引擎。</p> <h3 data-tool="mdnice编辑器" style="margin: 10px 0px 5px;padding: 10px 0px 0px;outline: 0px;font-weight: bold;font-size: 20px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;line-height: 1.4;visibility: visible;"><span style="margin: 0px;padding: 0px 0px 0px 20px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(81, 81, 81);font-size: 1em;border-left: 3px solid rgb(249, 191, 69);visibility: visible;">2. InnoDB是如何读写数据的?</span></h3> <p data-tool="mdnice编辑器" style="margin: 0px 0px 20px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;line-height: 1.8em;color: rgb(58, 58, 58);visibility: visible;">InnoDB处理数据的过程是发生在内存中的,需要把磁盘中的数据加载到内存中,如果是处理写入或修改请求的话,还需要把内存中的内容刷新到磁盘上。</p> <p data-tool="mdnice编辑器" style="margin: 0px 0px 20px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;line-height: 1.8em;color: rgb(58, 58, 58);visibility: visible;">读写磁盘的速度非常慢,和内存读写差了几个数量级,所以当我们想从表中获取某些记录时,InnoDB存储引擎将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小默认为 16 KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,或者一次最少把内存中的16KB内容刷新到磁盘中。</p> <p data-tool="mdnice编辑器" style="margin: 0px 0px 20px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: bor

如何保证MySQL和Redis的数据一致性?

作者:微信小助手

<blockquote data-tool="mdnice编辑器" style="box-sizing: border-box !important;margin: 20px 0px;padding: 15px 10px;outline: 0px;border-width: initial;border-style: none;border-color: initial;color: rgb(53, 53, 53);font-size: 0.9em;max-width: 100%;overflow-wrap: break-word !important;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;orphans: 2;text-align: left;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;-webkit-text-stroke-width: 0px;text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;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.8px;word-spacing: 0.8px;display: block;overflow: auto;line-height: 1.75;border-radius: 13px;background: rgb(245, 245, 245);visibility: visible;" data-mpa-powered-by="yiban.io"> <p style="box-sizing: border-box !important;margin: 0px 10px;padding: 8px 0px;outline: 0px;max-width: 100%;overflow-wrap: break-word !important;clear: both;min-height: 1em;line-height: 26px;color: rgb(53, 53, 53);font-size: 16px;display: block;visibility: visible;">我的<a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzAwMTk4NjM1MA==&amp;mid=2247506585&amp;idx=1&amp;sn=7f1e9eaea04030a256c511f4bafb85fe&amp;chksm=9ad3c0d8ada449ce61b9870feacc4df15f59ef4ca7ff2b1c7a74a58d45efab4eae666f97565d&amp;scene=21#wechat_redirect" textvalue="知识星球正式上线了" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="box-sizing: border-box !important;margin: 0px;padding: 0px;outline: 0px;color: rgb(87, 107, 149);text-decoration: none;background-color: transparent;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;overflow-wrap: break-word !important;visibility: visible;">知识星球正式上线了</a>(戳链接),期待你的加入,我们一起冲冲冲!</p> </blockquote> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding: 0 10px;line-height: 1.6;word-spacing: 0px;word-break: break-word;word-wrap: break-word;text-align: left;font-size: 15px;letter-spacing: 0.05em;color: #595959;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, 'PingFang SC', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;"> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">这个问题在面试的时候经常会遇到,刚好前几天也有粉丝问了我这个问题,所以感觉有必要单独出一篇。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;"><strong style="font-weight: bold;color: #35b378;">之前也看了很多相关的文章,但是感觉讲的都不好</strong>,很多文章都会去讲各种策略,比如(旁路缓存)策略、(读穿 / 写穿)策略和(写回)策略等,感觉意义真的不大,然后有的文章也只讲了部分情况,也没有告诉最优解。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">我直接先抛一下结论:<strong style="font-weight: bold;color: #35b378;">在满足实时性的条件下,不存在两者完全保存一致的方案,只有最终一致性方案。</strong> 根据网上的众多解决方案,总结出 6 种,直接看目录:</p> <figure data-tool="mdnice编辑器" style="margin: 0;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.5665665665665666" src="/upload/5795b21acc06b9c7c51c4785a05112ee.png" data-type="png" data-w="999" style="display: block;margin: 0 auto;max-width: 100%;"> </figure> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-size: 24px;margin: 1.2em 0 1em;padding: 0;font-weight: bold;color: #35b378;"><span style="display: none;"></span>不好的方案</h1> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;bmin-height: 32px;line-height: 32px;border-bottom: solid 1px #000000;color: #35b378;display: inline-block;border-bottom-width: 0px;border-bottom-style: solid;border-color: #35b378;padding-top: 5px;padding-right: 0.5em;padding-left: 0.5em;font-size: 23px;margin: 1em 0 0rem 0;padding: 0.5em 0;text-align: leftt;font-weight: bold;"><span style="display: none;"></span>1. 先写 MySQL,再写 Redis</h2> <figure data-tool="mdnice编辑器" style="margin: 0;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.4182547642928786" src="/upload/9f6661108ae42a992ecf97e0b717e49.png" data-type="png" data-w="997" style="display: block;margin: 0 auto;max-width: 100%;"> </figure> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-bottom: none;display: block;font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin: 10px 5px;border-left: 3px solid rgb(53, 179, 120);border-right: 0px solid rgb(53, 179, 120);color: rgb(97, 97, 97);quotes: none;background: rgb(251, 249, 253);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;color: black;line-height: 26px;">图解说明:</p> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 这是一副时序图,描述请求的先后调用顺序; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 橘黄色的线是请求 A,黑色的线是请求 B; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 橘黄色的文字,是 MySQL 和 Redis 最终不一致的数据; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 数据是从 10 更新为 11; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 后面所有的图,都是这个含义,不再赘述。 </section></li> </ul> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">请求 A、B 都是先写 MySQL,然后再写 Redis,在高并发情况下,如果请求 A 在写 Redis 时卡了一会,请求 B 已经依次完成数据的更新,就会出现图中的问题。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">这个图已经画的很清晰了,我就不用再去啰嗦了吧,<strong style="font-weight: bold;color: #35b378;">不过这里有个前提,就是对于读请求,先去读 Redis,如果没有,再去读 DB,但是读请求不会再回写 Redis。</strong> 大白话说一下,就是读请求不会更新 Redis。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;bmin-height: 32px;line-height: 32px;border-bottom: solid 1px #000000;color: #35b378;display: inline-block;border-bottom-width: 0px;border-bottom-style: solid;border-color: #35b378;padding-top: 5px;padding-right: 0.5em;padding-left: 0.5em;font-size: 23px;margin: 1em 0 0rem 0;padding: 0.5em 0;text-align: leftt;font-weight: bold;"><span style="display: none;"></span>2. 先写 Redis,再写 MySQL</h2> <figure data-tool="mdnice编辑器" style="margin: 0;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.4069529652351738" src="/upload/8c93898d5f0bdef386f7544378da4b61.png" data-type="png" data-w="978" style="display: block;margin: 0 auto;max-width: 100%;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">同“先写 MySQL,再写 Redis”,看图可秒懂。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;bmin-height: 32px;line-height: 32px;border-bottom: solid 1px #000000;color: #35b378;display: inline-block;border-bottom-width: 0px;border-bottom-style: solid;border-color: #35b378;padding-top: 5px;padding-right: 0.5em;padding-left: 0.5em;font-size: 23px;margin: 1em 0 0rem 0;padding: 0.5em 0;text-align: leftt;font-weight: bold;"><span style="display: none;"></span>3. 先删除 Redis,再写 MySQL</h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">这幅图和上面有些不一样,前面的请求 A 和 B 都是更新请求,这里的请求 A 是更新请求,<strong style="font-weight: bold;color: #35b378;">但是请求 B 是读请求,且请求 B 的读请求会回写 Redis。</strong></p> <figure data-tool="mdnice编辑器" style="margin: 0;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.42681678607983625" src="/upload/ba34b469e027a386422aeca9c5ab2960.png" data-type="png" data-w="977" style="display: block;margin: 0 auto;max-width: 100%;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">请求 A 先删除缓存,可能因为卡顿,数据一直没有更新到 MySQL,导致两者数据不一致。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;"><strong style="font-weight: bold;color: #35b378;">这种情况出现的概率比较大,因为请求 A 更新 MySQL 可能耗时会比较长,而请求 B 的前两步都是查询,会非常快。</strong></p> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-size: 24px;margin: 1.2em 0 1em;padding: 0;font-weight: bold;color: #35b378;"><span style="display: none;"></span>好的方案</h1> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;bmin-height: 32px;line-height: 32px;border-bottom: solid 1px #000000;color: #35b378;display: inline-block;border-bottom-width: 0px;border-bottom-style: solid;border-color: #35b378;padding-top: 5px;padding-right: 0.5em;padding-left: 0.5em;font-size: 23px;margin: 1em 0 0rem 0;padding: 0.5em 0;text-align: leftt;font-weight: bold;"><span style="display: none;"></span>4. 先删除 Redis,再写 MySQL,再删除 Redis</h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">对于“先删除 Redis,再写 MySQL”,如果要解决最后的不一致问题,其实再对 Redis 重新删除即可,<strong style="font-weight: bold;color: #35b378;">这个也是大家常说的“缓存双删”。</strong></p> <figure data-tool="mdnice编辑器" style="margin: 0;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.4656964656964657" src="/upload/1354d0ed48ea722f6cd238bcc6eca391.png" data-type="png" data-w="962" style="display: block;margin: 0 auto;max-width: 100%;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">为了便于大家看图,对于蓝色的文字,“删除缓存 10”必须在“回写缓存10”后面,那如何才能保证一定是在后面呢?<strong style="font-weight: bold;color: #35b378;">网上给出的第一个方案是,让请求 A 的最后一次删除,等待 500ms。</strong></p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">对于这种方案,看看就行,反正我是不会用,太 Low 了,风险也不可控。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;"><strong style="font-weight: bold;color: #35b378;">那有没有更好的方案呢,我建议异步串行化删除,即删除请求入队列</strong></p> <figure data-tool="mdnice编辑器" style="margin: 0;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.41379310344827586" src="/upload/d36beec329acc5528e475db1934900d7.png" data-type="png" data-w="1102" style="display: block;margin: 0 auto;max-width: 100%;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">异步删除对线上业务无影响,串行化处理保障并发情况下正确删除。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">如果双删失败怎么办,网上有给 Redis 加一个缓存过期时间的方案,这个不敢苟同。<strong style="font-weight: bold;color: #35b378;">个人建议整个重试机制,可以借助消息队列的重试机制,也可以自己整个表,记录重试次数</strong>,方法很多。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-bottom: none;display: block;font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin: 10px 5px;border-left: 3px solid rgb(53, 179, 120);border-right: 0px solid rgb(53, 179, 120);color: rgb(97, 97, 97);quotes: none;background: rgb(251, 249, 253);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;color: black;line-height: 26px;">简单小结一下:</p> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> “缓存双删”不要用无脑的 sleep 500 ms; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 通过消息队列的异步&amp;串行,实现最后一次缓存删除; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 缓存删除失败,增加重试机制。 </section></li> </ul> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;bmin-height: 32px;line-height: 32px;border-bottom: solid 1px #000000;color: #35b378;display: inline-block;border-bottom-width: 0px;border-bottom-style: solid;border-color: #35b378;padding-top: 5px;padding-right: 0.5em;padding-left: 0.5em;font-size: 23px;margin: 1em 0 0rem 0;padding: 0.5em 0;text-align: leftt;font-weight: bold;"><span style="display: none;"></span>5. 先写 MySQL,再删除 Redis</h2> <figure data-tool="mdnice编辑器" style="margin: 0;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.487378640776699" src="/upload/5ed045e42789d97fd9ef32256fa56af6.png" data-type="png" data-w="1030" style="display: block;margin: 0 auto;max-width: 100%;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">对于上面这种情况,对于第一次查询,请求 B 查询的数据是 10,但是 MySQL 的数据是 11,<strong style="font-weight: bold;color: #35b378;">只存在这一次不一致的情况,对于不是强一致性要求的业务,可以容忍。</strong>(那什么情况下不能容忍呢,比如秒杀业务、库存服务等。)</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">当请求 B 进行第二次查询时,因为没有命中 Redis,会重新查一次 DB,然后再回写到 Reids。</p> <figure data-tool="mdnice编辑器" style="margin: 0;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.47804878048780486" src="/upload/d8fecb2e603300b493f87b0526a7b59f.png" data-type="png" data-w="1025" style="display: block;margin: 0 auto;max-width: 100%;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">这里需要满足 2 个条件:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 缓存刚好自动失效; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 请求 B 从数据库查出 10,回写缓存的耗时,比请求 A 写数据库,并且删除缓存的还长。 </section></li> </ul> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">对于第二个条件,我们都知道更新 DB 肯定比查询耗时要长,所以出现这个情况的概率很小,同时满足上述条件的情况更小。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;bmin-height: 32px;line-height: 32px;border-bottom: solid 1px #000000;color: #35b378;display: inline-block;border-bottom-width: 0px;border-bottom-style: solid;border-color: #35b378;padding-top: 5px;padding-right: 0.5em;padding-left: 0.5em;font-size: 23px;margin: 1em 0 0rem 0;padding: 0.5em 0;text-align: leftt;font-weight: bold;"><span style="display: none;"></span>6. 先写 MySQL,通过 Binlog,异步更新 Redis</h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">这种方案,主要是监听 MySQL 的 Binlog,然后通过异步的方式,将数据更新到 Redis,这种方案有个前提,查询的请求,不会回写 Redis。</p> <figure data-tool="mdnice编辑器" style="margin: 0;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.35511363636363635" src="/upload/c334189895ba723bc9382113ecee3550.png" data-type="png" data-w="1056" style="display: block;margin: 0 auto;max-width: 100%;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">这个方案,会保证 MySQL 和 Redis 的最终一致性,但是如果中途请求 B 需要查询数据,如果缓存无数据,就直接查 DB;如果缓存有数据,查询的数据也会存在不一致的情况。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;"><strong style="font-weight: bold;color: #35b378;">所以这个方案,是实现最终一致性的终极解决方案,但是不能保证实时性。</strong></p> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-size: 24px;margin: 1.2em 0 1em;padding: 0;font-weight: bold;color: #35b378;"><span style="display: none;"></span>几种方案比较</h1> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">我们对比上面讨论的 6 种方案:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 先写 Redis,再写 MySQL </section></li> </ol> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> <strong style="font-weight: bold;color: #35b378;">这种方案,我肯定不会用</strong>,万一 DB 挂了,你把数据写到缓存,DB 无数据,这个是灾难性的; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 我之前也见同学这么用过,如果写 DB 失败,对 Redis 进行逆操作,那如果逆操作失败呢,是不是还要搞个重试? </section></li> </ul> <ol start="2" data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 先写 MySQL,再写 Redis </section></li> </ol> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> <strong style="font-weight: bold;color: #35b378;">对于并发量、一致性要求不高的项目,很多就是这么用的</strong>,我之前也经常这么搞,但是不建议这么做; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 当 Redis 瞬间不可用的情况,需要报警出来,然后线下处理。 </section></li> </ul> <ol start="3" data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 先删除 Redis,再写 MySQL </section></li> </ol> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 这种方式,我还真没用过, <strong style="font-weight: bold;color: #35b378;">直接忽略吧。</strong> </section></li> </ul> <ol start="4" data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 先删除 Redis,再写 MySQL,再删除 Redis </section></li> </ol> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 这种方式虽然可行,但是 <strong style="font-weight: bold;color: #35b378;">感觉好复杂</strong>,还要搞个消息队列去异步删除 Redis。 </section></li> </ul> <ol start="5" data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 先写 MySQL,再删除 Redis </section></li> </ol> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> <strong style="font-weight: bold;color: #35b378;">比较推荐这种方式</strong>,删除 Redis 如果失败,可以再多重试几次,否则报警出来; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 这个方案,是实时性中最好的方案,在一些高并发场景中,推荐这种。 </section></li> </ul> <ol start="6" data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 先写 MySQL,通过 Binlog,异步更新 Redis </section></li> </ol> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> <strong style="font-weight: bold;color: #35b378;">对于异地容灾、数据汇总等,建议会用这种方式</strong>,比如 binlog + kafka,数据的一致性也可以达到秒级; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> 纯粹的高并发场景,不建议用这种方案,比如抢购、秒杀等。 </section></li> </ul> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;"><strong style="font-weight: bold;color: #35b378;">个人结论:</strong></p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> <strong style="font-weight: bold;color: #35b378;">实时一致性方案</strong>:采用“先写 MySQL,再删除 Redis”的策略,这种情况虽然也会存在两者不一致,但是需要满足的条件有点苛刻, <strong style="font-weight: bold;color: #35b378;">所以是满足实时性条件下,能尽量满足一致性的最优解。</strong> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;margin: 10px 0;"> <strong style="font-weight: bold;color: #35b378;">最终一致性方案</strong>:采用“先写 MySQL,通过 Binlog,异步更新 Redis”,可以通过 Binlog,结合消息队列异步更新 Redis, <strong style="font-weight: bold;color: #35b378;">是最终一致性的最优解。</strong> </section></li> </ul> </section> <section> <mp-common-profile class="js_uneditable custom_select_card" data-pluginname="mpprofile" data-id="MzAwMTk4NjM1MA==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/PxMzT0Oibf4gcBzLSUNh2cgXUsuLIsvQYJE1lzZd74qpC3iciaM6gcYIfOVV0KjDDkeN4CTLTn4ETPtaHOAuTWSWA/0?wx_fmt=png" data-nickname="JAVA日知录" data-alias="javadaily" data-signature="写代码的架构师,做架构的程序员! 实战、源码、数据库、架构...只要你来,你想了解的这里都有!" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> <p><br></p>

100 条"未读消息" 怎么实现的?7 种技术方案安排

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;line-height: 1.6;word-spacing: 0px;letter-spacing: 0px;word-break: break-word;overflow-wrap: break-word;text-align: left;padding: 0.6px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin-bottom: 0px;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;"><strong><span style="color: rgb(255, 104, 39);">大家好,我是悟空~</span></strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">现在要实现一个站内信web消息推送的功能,对,就是下图这个小红点,一个很常用的功能。</p> <figure data-tool="mdnice编辑器" style="margin: 0;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.08426270136307311" src="/upload/db9252b5656016e5fb65125e9792c238.png" data-type="png" data-w="807" style="max-width: 100%;border-radius: 2px;display: block;margin: 20px auto;width: 85%;height: 100%;object-fit: contain;box-shadow: #84A1A8 0px 2px 5px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">这里整理了一下几种方案,并简单做了实现。</p> <figure data-tool="mdnice编辑器" style="margin: 0;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.6847946725860156" src="/upload/4ca9741b96ca46d4001e832cca856f14.png" data-type="png" data-w="901" style="max-width: 100%;border-radius: 2px;display: block;margin: 20px auto;width: 85%;height: 100%;object-fit: contain;box-shadow: #84A1A8 0px 2px 5px;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;font-size: 18px;color: #0e88eb;"><span style="display: none;"></span><span style="font-size: 18px;color: #0e88eb;">什么是消息推送(push)</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">消息推送(<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">push</code>)通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备APP进行的主动消息推送。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">消息推送一般又分为<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">web端消息推送</code>和<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">移动端消息推送</code>。</p> <figure data-tool="mdnice编辑器" style="margin: 0;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.3170940170940171" src="/upload/b8d5deebbbc4a634b79b343ea29615ec.png" data-type="png" data-w="1170" style="max-width: 100%;border-radius: 2px;display: block;margin: 20px auto;width: 85%;height: 100%;object-fit: contain;box-shadow: #84A1A8 0px 2px 5px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">上边的这种属于移动端消息推送,web端消息推送常见的诸如站内信、未读邮件数量、监控报警数量等,应用的也非常广泛。</p> <figure data-tool="mdnice编辑器" style="margin: 0;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.2620320855614973" src="/upload/4d623c9f95c2aa1d9331288f3eddf9ba.png" data-type="png" data-w="1122" style="max-width: 100%;border-radius: 2px;display: block;margin: 20px auto;width: 85%;height: 100%;object-fit: contain;box-shadow: #84A1A8 0px 2px 5px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),web页面的通知小红点就会实时的<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">+1</code>就可以了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">通常在服务端会有若干张消息推送表,用来记录用户触发不同事件所推送不同类型的消息,前端主动查询(拉)或者被动接收(推)用户所有未读的消息数。</p> <figure data-tool="mdnice编辑器" style="margin: 0;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.42444444444444446" src="/upload/590ceaebf0c79ba8db4993de5e836110.png" data-type="png" data-w="1800" style="max-width: 100%;border-radius: 2px;display: block;margin: 20px auto;width: 85%;height: 100%;object-fit: contain;box-shadow: #84A1A8 0px 2px 5px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">消息推送无非是推(<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">push</code>)和拉(<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">pull</code>)两种形式,下边我们逐个了解下。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;font-size: 18px;color: #0e88eb;"><span style="display: none;"></span><span style="font-size: 18px;color: #0e88eb;">短轮询</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">轮询(<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">polling</code>)应该是实现消息推送方案中最简单的一种,这里我们暂且将轮询分为<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">短轮询</code>和<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">长轮询</code>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">短轮询很好理解,指定的时间间隔,由浏览器向服务器发出<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">HTTP</code>请求,服务器实时返回未读消息数据给客户端,浏览器再做渲染显示。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">一个简单的JS定时器就可以搞定,每秒钟请求一次未读消息数接口,返回的数据展示即可。</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/CJ35Z2cnZA3Dv8nvz8jxcibdLaD21YDUb5gJ8iaO0oBF9LI6rJ32Z56Vt5NibpXtgeAoBom9icpXdvBd9Jia74czxKXDusI0YXhcA/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;">setInterval(()&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;//&nbsp;方法请求<br>&nbsp;&nbsp;messageCount().<span style="color: #c678dd;line-height: 26px;">then</span>((res)&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">if</span>&nbsp;(res.code&nbsp;===&nbsp;200)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.messageCount&nbsp;=&nbsp;res.data<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;})<br>},&nbsp;1000);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">效果还是可以的,短轮询实现固然简单,缺点也是显而易见,由于推送数据并不会频繁变更,无论后端此时是否有新的消息产生,客户端都会进行请求,势必会对服务端造成很大压力,浪费带宽和服务器资源。</p> <figure data-tool="mdnice编辑器" style="margin: 0;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.7565011820330969" src="/upload/b38f04a6daa322c6c4c947689374a091.png" data-type="gif" data-w="846" style="max-width: 100%;border-radius: 2px;display: block;margin: 20px auto;width: 85%;height: 100%;object-fit: contain;box-shadow: #84A1A8 0px 2px 5px;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;font-size: 18px;color: #0e88eb;"><span style="display: none;"></span><span style="font-size: 18px;color: #0e88eb;">长轮询</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">长轮询是对上边短轮询的一种改进版本,在尽可能减少对服务器资源浪费的同时,保证消息的相对实时性。长轮询在中间件中应用的很广泛,比如<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">Nacos</code>和<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">apollo</code>配置中心,消息队列<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">kafka</code>、<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">RocketMQ</code>中都有用到长轮询。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;"><a href="https://mp.weixin.qq.com/s?__biz=MzAxNTM4NzAyNg==&amp;mid=2247494748&amp;idx=1&amp;sn=2cccdbc6269ea01e75012340af1496ef&amp;scene=21#wechat_redirect" style="text-decoration: none;word-wrap: break-word;font-weight: bold;color: #0e88eb;border-bottom: 0px solid #ff3502;font-family: STHeitiSC-Light;" data-linktype="2">Nacos配置中心交互模型是push还是pull?</a>一文中我详细介绍过<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">Nacos</code>长轮询的实现原理,感兴趣的小伙伴可以瞅瞅。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">这次我使用<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">apollo</code>配置中心实现长轮询的方式,应用了一个类<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">DeferredResult</code>,它是在<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">servelet3.0</code>后经过Spring封装提供的一种异步请求机制,直意就是延迟结果。</p> <figure data-tool="mdnice编辑器" style="margin: 0;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.5174193548387097" src="/upload/a9700f07d4379bbc95dd451252ee0835.png" data-type="png" data-w="1550" style="max-width: 100%;border-radius: 2px;display: block;margin: 20px auto;width: 85%;height: 100%;object-fit: contain;box-shadow: #84A1A8 0px 2px 5px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;"><code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">DeferredResult</code>可以允许容器线程快速释放占用的资源,不阻塞请求线程,以此接受更多的请求提升系统的吞吐量,然后启动异步工作线程处理真正的业务逻辑,处理完成调用<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">DeferredResult.setResult(200)</code>提交响应结果。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">下边我们用长轮询来实现消息推送。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">因为一个ID可能会被多个长轮询请求监听,所以我采用了<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">guava</code>包提供的<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">Multimap</code>结构存放长轮询,一个key可以对应多个value。一旦监听到key发生变化,对应的所有长轮询都会响应。前端得到非请求超时的状态码,知晓数据变更,主动查询未读消息数接口,更新页面数据。</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/CJ35Z2cnZA3Dv8nvz8jxcibdLaD21YDUb5gJ8iaO0oBF9LI6rJ32Z56Vt5NibpXtgeAoBom9icpXdvBd9Jia74czxKXDusI0YXhcA/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: #61aeee;line-height: 26px;">@Controller</span><br><span style="color: #61aeee;line-height: 26px;">@RequestMapping</span>(<span style="color: #98c379;line-height: 26px;">"/polling"</span>)<br><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">class</span>&nbsp;<span style="color: #e6c07b;line-height: 26px;">PollingController</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;存放监听某个Id的长轮询集合</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;线程同步结构</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;Multimap&lt;String,&nbsp;DeferredResult&lt;String&gt;&gt;&nbsp;watchRequests&nbsp;=&nbsp;Multimaps.synchronizedMultimap(HashMultimap.create());<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;公众号:程序员小富<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;设置监听<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #61aeee;line-height: 26px;">@GetMapping</span>(path&nbsp;=&nbsp;<span style="color: #98c379;line-height: 26px;">"watch/{id}"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #61aeee;line-height: 26px;">@ResponseBody</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;DeferredResult&lt;String&gt;&nbsp;<span style="color: #61aeee;line-height: 26px;">watch</span><span style="line-height: 26px;">(@PathVariable&nbsp;String&nbsp;id)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;延迟对象设置超时时间</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DeferredResult&lt;String&gt;&nbsp;deferredResult&nbsp;=&nbsp;<span style="color: #c678dd;line-height: 26px;">new</span>&nbsp;DeferredResult&lt;&gt;(TIME_OUT);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;异步请求完成时移除&nbsp;key,防止内存溢出</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;deferredResult.onCompletion(()&nbsp;-&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;watchRequests.remove(id,&nbsp;deferredResult);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;注册长轮询请求</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;watchRequests.put(id,&nbsp;deferredResult);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;deferredResult;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;公众号:程序员小富<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;变更数据<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #61aeee;line-height: 26px;">@GetMapping</span>(path&nbsp;=&nbsp;<span style="color: #98c379;line-height: 26px;">"publish/{id}"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #61aeee;line-height: 26px;">@ResponseBody</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;String&nbsp;<span style="color: #61aeee;line-height: 26px;">publish</span><span style="line-height: 26px;">(@PathVariable&nbsp;String&nbsp;id)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;数据变更&nbsp;取出监听ID的所有长轮询请求,并一一响应处理</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">if</span>&nbsp;(watchRequests.containsKey(id))&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Collection&lt;DeferredResult&lt;String&gt;&gt;&nbsp;deferredResults&nbsp;=&nbsp;watchRequests.get(id);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">for</span>&nbsp;(DeferredResult&lt;String&gt;&nbsp;deferredResult&nbsp;:&nbsp;deferredResults)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;deferredResult.setResult(<span style="color: #98c379;line-height: 26px;">"我更新了"</span>&nbsp;+&nbsp;<span style="color: #c678dd;line-height: 26px;">new</span>&nbsp;Date());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;<span style="color: #98c379;line-height: 26px;">"success"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">当请求超过设置的超时时间,会抛出<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">AsyncRequestTimeoutException</code>异常,这里直接用<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">@ControllerAdvice</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/CJ35Z2cnZA3Dv8nvz8jxcibdLaD21YDUb5gJ8iaO0oBF9LI6rJ32Z56Vt5NibpXtgeAoBom9icpXdvBd9Jia74czxKXDusI0YXhcA/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;">@ControllerAdvice<br>public&nbsp;class&nbsp;AsyncRequestTimeoutHandler&nbsp;{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;@ResponseStatus(HttpStatus.NOT_MODIFIED)<br>&nbsp;&nbsp;&nbsp;&nbsp;@ResponseBody<br>&nbsp;&nbsp;&nbsp;&nbsp;@ExceptionHandler(AsyncRequestTimeoutException.class)<br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;String&nbsp;asyncRequestTimeoutHandler(AsyncRequestTimeoutException&nbsp;e)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="color: #98c379;line-height: 26px;">"异步请求超时"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #e6c07b;line-height: 26px;">return</span>&nbsp;<span style="color: #98c379;line-height: 26px;">"304"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">我们来测试一下,首先页面发起长轮询请求<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">/polling/watch/10086</code>监听消息更变,请求被挂起,不变更数据直至超时,再次发起了长轮询请求;紧接着手动变更数据<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">/polling/publish/10086</code>,长轮询得到响应,前端处理业务逻辑完成后再次发起请求,如此循环往复。</p> <figure data-tool="mdnice编辑器" style="margin: 0;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.5844255975327679" src="/upload/84b2a55563a5cd3aee77212095443993.png" data-type="gif" data-w="1297" style="max-width: 100%;border-radius: 2px;display: block;margin: 20px auto;width: 85%;height: 100%;object-fit: contain;box-shadow: #84A1A8 0px 2px 5px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">长轮询相比于短轮询在性能上提升了很多,但依然会产生较多的请求,这是它的一点不完美的地方。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;font-size: 18px;color: #0e88eb;"><span style="display: none;"></span><span style="font-size: 18px;color: #0e88eb;">iframe流</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;color: black;margin: 10px 1px;line-height: 1.75;letter-spacing: 0.10em;font-size: 16px;word-spacing: 0.2em;">iframe流就是在页面中插入一个隐藏的<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">&lt;iframe&gt;</code>标签,通过在<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">src</code>中请求消息数量API接口,由此在服务端和客户端之间创建一条长连接,服务端持续向<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">iframe</code>传输数据。</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;display: block;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;font-style: normal;padding: 10px;line-height: 1.8;border-radius: 0px 0px 10px 10px;color: rgb(14, 136, 235);background: rgb(255, 255, 255);box-shadow: rgb(132, 161, 168) 0px 10px 15px;"> <span style="display: inline;color: #0e88eb;font-size: 4em;f

大文件上传时如何做到 秒传?

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用普通的方式进行上传,这可不是一个好的办法,毕竟很少有人会忍受,当文件上传到一半中断后,继续上传却只能重头开始上传,这种让人不爽的体验。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">那有没有比较好的上传体验呢,答案有的,就是下边要介绍的几种上传方式</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">秒传</span></h2> <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);">MD5</span>校验,如果服务器上有一样的东西,它就直接给你个新地址,其实你下载的都是服务器上的同一个文件,想要不秒传,其实只要让<span style="font-weight: 700;color: rgb(248, 57, 41);">MD5</span>改变,就是对文件本身做一下修改(改名字不行),例如一个文本文件,你多加几个字,MD5就变了,就不会秒传了.</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;"><span style="font-weight: 700;color: rgb(248, 57, 41);">a</span>、利用redis的set方法存放文件上传状态,其中key为文件上传的md5,value为是否上传完成的标志位,</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);">b</span>、当标志位true为上传已经完成,此时如果有相同文件上传,则进入秒传逻辑。如果标志位为false,则说明还没上传完成,此时需要在调用set的方法,保存块号文件记录的路径,其中key为上传文件md5加一个固定前缀,value为块号文件记录路径</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">分片上传</span></h2> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">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;">分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。</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;">1.大文件上传</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">2.网络环境环境不好,存在需要重传风险的场景</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">断点续传</span></h2> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1、什么是断点续传</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传或者下载未完成的部分,而没有必要从头开始上传或者下载。本文的断点续传主要是针对断点上传场景。</p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;"><span style="display: none;"></span>2、应用场景<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">断点续传可以看成是分片上传的一个衍生,因此可以使用分片上传的场景,都可以使用断点续传。</p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;"><span style="display: none;"></span>3、实现断点续传的核心逻辑<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在分片上传的过程中,如果因为系统崩溃或者网络中断等异常因素导致上传中断,这时候客户端需要记录上传的进度。在之后支持再次上传时,可以继续从上次上传中断的地方进行继续上传。</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> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;"><span style="display: none;"></span>4、实现流程步骤<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">a、方案一,常规步骤</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 初始化一个分片上传任务,返回本次分片上传唯一标识; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 按照一定的策略(串行或并行)发送各个分片数据块; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">b、方案二、本文实现的步骤</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 前端(客户端)需要根据固定大小对文件进行分片,请求后端(服务端)时要带上分片序号和大小 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 服务端创建conf文件用来记录分块位置,conf文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE 127(这步是实现断点续传和秒传的核心步骤) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 服务器按照请求数据中给的分片序号和每片分块大小(分片大小是固定且一样的)算出开始位置,与读取到的文件片段数据,写入文件。 </section></li> </ul> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;"><span style="display: none;"></span>5、分片上传/断点上传代码实现<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">a、前端采用百度提供的webuploader的插件,进行分片。因本文主要介绍服务端代码实现,webuploader如何进行分片,具体实现可以查看如下链接:</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">http://fex.baidu.com/webuploader/getting-started.html</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">b、后端用两种方式实现文件写入,一种是用<span style="font-weight: 700;color: rgb(248, 57, 41);">RandomAccessFile</span>,如果对<span style="font-weight: 700;color: rgb(248, 57, 41);">RandomAccessFile</span>不熟悉的朋友,可以查看如下链接:</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">https://blog.csdn.net/dimudan2015/article/details/81910690</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">另一种是使用<span style="font-weight: 700;color: rgb(248, 57, 41);">MappedByteBuffer</span>,对<span style="font-weight: 700;color: rgb(248, 57, 41);">MappedByteBuffer</span>不熟悉的朋友,可以查看如下链接进行了解:</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">https://www.jianshu.com/p/f90866dcbffc</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> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">后端进行写入操作的核心代码</span></h2> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1、RandomAccessFile实现方式</span><span style="display: none;"></span></h3> <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/A7sq8BD8oexqmcDw0weKQQChC3Y8nhI2wxzyicsmW8pFnUw4430HuO2I41zib8qTSqv9DnqKJx6zunET4f0VoGm0aXNaka2RmJ/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #4078f2;line-height: 26px;">@UploadMode</span>(mode&nbsp;=&nbsp;UploadModeEnum.RANDOM_ACCESS)&nbsp;&nbsp;<br><span style="color: #4078f2;line-height: 26px;">@Slf</span>4j&nbsp;&nbsp;<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;">RandomAccessUploadStrategy</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">extends</span>&nbsp;<span style="color: #c18401;line-height: 26px;">SliceUploadTemplate</span>&nbsp;</span>{&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Autowired</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;FilePathUtil&nbsp;filePathUtil;&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Value</span>(<span style="color: #50a14f;line-height: 26px;">"${upload.chunkSize}"</span>)&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">long</span>&nbsp;defaultChunkSize;&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">boolean</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">upload</span><span style="line-height: 26px;">(FileUploadRequestDTO&nbsp;param)</span>&nbsp;</span>{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;RandomAccessFile&nbsp;accessTmpFile&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">try</span>&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;uploadDirPath&nbsp;=&nbsp;filePathUtil.getPath(param);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;File&nbsp;tmpFile&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">super</span>.createTmpFile(param);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;accessTmpFile&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;RandomAccessFile(tmpFile,&nbsp;<span style="color: #50a14f;line-height: 26px;">"rw"</span>);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//这个必须与前端设定的值一致&nbsp;&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">long</span>&nbsp;chunkSize&nbsp;=&nbsp;Objects.isNull(param.getChunkSize())&nbsp;?&nbsp;defaultChunkSize&nbsp;*&nbsp;<span style="color: #986801;line-height: 26px;">1024</span>&nbsp;*&nbsp;<span style="color: #986801;line-height: 26px;">1024</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;param.getChunkSize();&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">long</span>&nbsp;offset&nbsp;=&nbsp;chunkSize&nbsp;*&nbsp;param.getChunk();&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//定位到该分片的偏移量&nbsp;&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;accessTmpFile.seek(offset);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//写入该分片数据&nbsp;&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;accessTmpFile.write(param.getFile().getBytes());&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">boolean</span>&nbsp;isOk&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">super</span>.checkAndSetUploadProgress(param,&nbsp;uploadDirPath);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;isOk;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">catch</span>&nbsp;(IOException&nbsp;e)&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.error(e.getMessage(),&nbsp;e);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">finally</span>&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FileUtil.close(accessTmpFile);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">false</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>}&nbsp;&nbsp;<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">2、MappedByteBuffer实现方式</span><span style="display: none;"></span></h3> <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/A7sq8BD8oexqmcDw0weKQQChC3Y8nhI2wxzyicsmW8pFnUw4430HuO2I41zib8qTSqv9DnqKJx6zunET4f0VoGm0aXNaka2RmJ/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #4078f2;line-height: 26px;">@UploadMode</span>(mode&nbsp;=&nbsp;UploadModeEnum.MAPPED_BYTEBUFFER)&nbsp;&nbsp;<br><span style="color: #4078f2;line-height: 26px;">@Slf</span>4j&nbsp;&nbsp;<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;">MappedByteBufferUploadStrategy</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">extends</span>&nbsp;<span style="color: #c18401;line-height: 26px;">SliceUploadTemplate</span>&nbsp;</span>{&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Autowired</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;FilePathUtil&nbsp;filePathUtil;&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Value</span>(<span style="color: #50a14f;line-height: 26px;">"${upload.chunkSize}"</span>)&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">long</span>&nbsp;defaultChunkSize;&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">boolean</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">upload</span><span style="line-height: 26px;">(FileUploadRequestDTO&nbsp;param)</span>&nbsp;</span>{&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;RandomAccessFile&nbsp;tempRaf&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;FileChannel&nbsp;fileChannel&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;MappedByteBuffer&nbsp;mappedByteBuffer&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">try</span>&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;uploadDirPath&nbsp;=&nbsp;filePathUtil.getPath(param);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;File&nbsp;tmpFile&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">super</span>.createTmpFile(param);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tempRaf&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;RandomAccessFile(tmpFile,&nbsp;<span style="color: #50a14f;line-height: 26px;">"rw"</span>);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fileChannel&nbsp;=&nbsp;tempRaf.getChannel();&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">long</span>&nbsp;chunkSize&nbsp;=&nbsp;Objects.isNull(param.getChunkSize())&nbsp;?&nbsp;defaultChunkSize&nbsp;*&nbsp;<span style="color: #986801;line-height: 26px;">1024</span>&nbsp;*&nbsp;<span style="color: #986801;line-height: 26px;">1024</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;param.getChunkSize();&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//写入该分片数据&nbsp;&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">long</span>&nbsp;offset&nbsp;=&nbsp;chunkSize&nbsp;*&nbsp;param.getChunk();&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">byte</span>[]&nbsp;fileData&nbsp;=&nbsp;param.getFile().getBytes();&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mappedByteBuffer&nbsp;=&nbsp;fileChannel&nbsp;&nbsp;<br>.map(FileChannel.MapMode.READ_WRITE,&nbsp;offset,&nbsp;fileData.length);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mappedByteBuffer.put(fileData);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">boolean</span>&nbsp;isOk&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">super</span>.checkAndSetUploadProgress(param,&nbsp;uploadDirPath);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;isOk;&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">catch</span>&nbsp;(IOException&nbsp;e)&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.error(e.getMessage(),&nbsp;e);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">finally</span>&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FileUtil.freedMappedByteBuffer(mappedByteBuffer);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FileUtil.close(fileChannel);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FileUtil.close(tempRaf);&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">false</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>}&nbsp;&nbsp;<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> <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/A7sq8BD8oexqmcDw0weKQQChC3Y8nhI2wxzyicsmW8pFnUw4430HuO2I41zib8qTSqv9DnqKJx6zunET4f0VoGm0aXNaka2RmJ/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #4078f2;line-height: 26px;">@Slf</span>4j&nbsp;&nbsp;<br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">abstract</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">SliceUploadTemplate</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">implements</span>&nbsp;<span style="color: #c18401;line-height: 26px;">SliceUploadStrategy</span>&nbsp;</span>{&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">abstract</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">boolean</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">upload</span><span style="line-height: 26px;">(FileUploadRequestDTO&nbsp;param)</span></span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">protected</span>&nbsp;File&nbsp;<span style="color: #4078f2;line-height: 26px;">createTmpFile</span><span style="line-height: 26px;">(FileUploadRequestDTO&nbsp;param)</span>&nbsp;</span>{&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;FilePathUtil&nbsp;filePathUtil&nbsp;=&nbsp;SpringContextHolder.getBean(FilePathUtil<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>)</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;param.setPath(FileUtil.withoutHeadAndTailDiagonal(param.getPath()));&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;fileName&nbsp;=&nbsp;param.getFile().getOriginalFilename();&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;uploadDirPath&nbsp;=&nbsp;filePathUtil.getPath(param);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;tempFileName&nbsp;=&nbsp;fileName&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">"_tmp"</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;File&nbsp;tmpDir&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;File(uploadDirPath);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;File&nbsp;tmpFile&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;File(uploadDirPath,&nbsp;tempFileName);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(!tmpDir.exists())&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tmpDir.mkdirs();&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;tmpFile;&nbsp;&nbsp;<br>&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;FileUploadDTO&nbsp;<span style="color: #4078f2;line-height: 26px;">sliceUpload</span><span style="line-height: 26px;">(FileUploadRequestDTO&nbsp;param)</span>&nbsp;</span>{&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">boolean</span>&nbsp;isOk&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>.upload(param);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(isOk)&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;File&nbsp;tmpFile&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>.createTmpFile(param);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FileUploadDTO&nbsp;fileUploadDTO&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>.saveAndFileUploadDTO(param.getFile().getOriginalFilename(),&nbsp;tmpFile);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;fileUploadDTO;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;md5&nbsp;=&nbsp;FileMD5Util.getFileMD5(param.getFile());&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;Map&lt;Integer,&nbsp;String&gt;&nbsp;map&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;HashMap&lt;&gt;();&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;map.put(param.getChunk(),&nbsp;md5);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;FileUploadDTO.builder().chunkMd5Info(map).build();&nbsp;&nbsp;<br>&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;*&nbsp;检查并修改文件上传进度&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;*/</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">boolean</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">checkAndSetUploadProgress</span><span style="line-height: 26px;">(FileUploadRequestDTO&nbsp;param,&nbsp;String&nbsp;uploadDirPath)</span>&nbsp;</span>{&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;fileName&nbsp;=&nbsp;param.getFile().getOriginalFilename();&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;File&nbsp;confFile&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;File(uploadDirPath,&nbsp;fileName&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">".conf"</span>);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">byte</span>&nbsp;isComplete&nbsp;=&nbsp;<span style="color: #986801;line-height: 26px;">0</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;RandomAccessFile&nbsp;accessConfFile&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">try</span>&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;accessConfFile&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;RandomAccessFile(confFile,&nbsp;<span style="color: #50a14f;line-height: 26px;">"rw"</span>);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//把该分段标记为&nbsp;true&nbsp;表示完成&nbsp;&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="color: #50a14f;line-height: 26px;">"set&nbsp;part&nbsp;"</span>&nbsp;+&nbsp;param.getChunk()&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">"&nbsp;complete"</span>);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//创建conf文件文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认0,已上传的就是Byte.MAX_VALUE&nbsp;127&nbsp;&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;accessConfFile.setLength(param.getChunks());&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;accessConfFile.seek(param.getChunk());&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;accessConfFile.write(Byte.MAX_VALUE);&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//completeList&nbsp;检查是否全部完成,如果数组里是否全部都是127(全部分片都成功上传)&nbsp;&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">byte</span>[]&nbsp;completeList&nbsp;=&nbsp;FileUtils.readFileToByteArray(confFile);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;isComplete&nbsp;=&nbsp;Byte.MAX_VALUE;&nbsp;&nbsp;<br>&nbsp;&nbsp;&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;completeList.length&nbsp;&amp;&amp;&nbsp;isComplete&nbsp;==&nbsp;Byte.MAX_VALUE;&nbsp;i++)&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//与运算,&nbsp;如果有部分没有完成则&nbsp;isComplete&nbsp;不是&nbsp;Byte.MAX_VALUE&nbsp;&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;isComplete&nbsp;=&nbsp;(<span style="color: #a626a4;line-height: 26px;">byte</span>)&nbsp;(isComplete&nbsp;&amp;&nbsp;completeList[i]);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="color: #50a14f;line-height: 26px;">"check&nbsp;part&nbsp;"</span>&nbsp;+&nbsp;i&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">"&nbsp;complete?:"</span>&nbsp;+&nbsp;completeList[i]);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">catch</span>&nbsp;(IOException&nbsp;e)&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.error(e.getMessage(),&nbsp;e);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">finally</span>&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FileUtil.close(accessConfFile);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;<span style="color: #a626a4;line-height: 26px;">boolean</span>&nbsp;isOk&nbsp;=&nbsp;setUploadProgress2Redis(param,&nbsp;uploadDirPath,&nbsp;fileName,&nbsp;confFile,&nbsp;isComplete);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;isOk;&nbsp;&nbsp;<br>&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;*&nbsp;把上传进度信息存进redis&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;*/</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">boolean</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">setUploadProgress2Redis</span><span style="line-height: 26px;">(FileUploadRequestDTO&nbsp;param,&nbsp;String&nbsp;uploadDirPath,&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;fileName,&nbsp;File&nbsp;confFile,&nbsp;<span style="color: #a626a4;line-height: 26px;">byte</span>&nbsp;isComplete)</span>&nbsp;</span>{&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;RedisUtil&nbsp;redisUtil&nbsp;=&nbsp;SpringContextHolder.getBean(RedisUtil<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>)</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(isComplete&nbsp;==&nbsp;Byte.MAX_VALUE)&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS,&nbsp;param.getMd5(),&nbsp;<span style="color: #50a14f;line-height: 26px;">"true"</span>);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;redisUtil.del(FileConstant.FILE_MD5_KEY&nbsp;+&nbsp;param.getMd5());&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;confFile.delete();&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">true</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">else</span>&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(!redisUtil.hHasKey(FileConstant.FILE_UPLOAD_STATUS,&nbsp;param.getMd5()))&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS,&nbsp;param.getMd5(),&nbsp;<span style="color: #50a14f;line-height: 26px;">"false"</span>);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;redisUtil.set(FileConstant.FILE_MD5_KEY&nbsp;+&nbsp;param.getMd5(),&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uploadDirPath&nbsp;+&nbsp;FileConstant.FILE_SEPARATORCHAR&nbsp;+&nbsp;fileName&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">".conf"</span>);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">false</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;}&nbsp;&nbsp;<br><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;*&nbsp;保存文件操作&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;*/</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;FileUploadDTO&nbsp;<span style="color: #4078f2;line-height: 26px;">saveAndFileUploadDTO</span><span style="line-height: 26px;">(String&nbsp;fileName,&nbsp;File&nbsp;tmpFile)</span>&nbsp;</span>{&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;FileUploadDTO&nbsp;fileUploadDTO&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>;&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">try</span>&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fileUploadDTO&nbsp;=&nbsp;renameFile(tmpFile,&nbsp;fileName);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(fileUploadDTO.isUploadComplete())&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.println(<span style="color: #50a14f;line-height: 26px;">"upload&nbsp;complete&nbsp;!!"</span>&nbsp;+&nbsp;fileUploadDTO.isUploadComplete()&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">"&nbsp;name="</span>&nbsp;+&nbsp;fileName);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//TODO&nbsp;保存文件信息到数据库&nbsp;&nbsp;</span><br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">catch</span>&nbsp;(Exception&nbsp;e)&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.error(e.getMessage(),&nbsp;e);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">finally</span>&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;fileUploadDTO;&nbsp;&nbsp;<br>&nbsp;&nbsp;}&nbsp;&nbsp;<br><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;*&nbsp;文件重命名&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">@param</span>&nbsp;toBeRenamed&nbsp;将要修改名字的文件&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">@param</span>&nbsp;toFileNewName&nbsp;新的名字&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;*/</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;FileUploadDTO&nbsp;<span style="color: #4078f2;line-height: 26px;">renameFile</span><span style="line-height: 26px;">(File&nbsp;toBeRenamed,&nbsp;String&nbsp;toFileNewName)</span>&nbsp;</span>{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//检查要重命名的文件是否存在,是否是文件&nbsp;&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;FileUploadDTO&nbsp;fileUploadDTO&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;FileUploadDTO();&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(!toBeRenamed.exists()&nbsp;||&nbsp;toBeRenamed.isDirectory())&nbsp;{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info(<span style="color: #50a14f;line-height: 26px;">"File&nbsp;does&nbsp;not&nbsp;exist:&nbsp;{}"</span>,&nbsp;toBeRenamed.getName());&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fileUploadDTO.setUploadComplete(<span style="color: #a626a4;line-height: 26px;">false</span>);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;fileUploadDTO;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;ext&nbsp;=&nbsp;FileUtil.getExtension(toFileNewName);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;p&nbsp;=&nbsp;toBeRenamed.getParent();&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;filePath&nbsp;=&nbsp;p&nbsp;+&nbsp;FileConstant.FILE_SEPARATORCHAR&nbsp;+&nbsp;toFileNewName;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;File&nbsp;newFile&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;File(filePath);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//修改文件名&nbsp;&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">boolean</span>&nbsp;uploadFlag&nbsp;=&nbsp;toBeRenamed.renameTo(newFile);&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fileUploadDTO.setMtime(DateUtil.getCurrentTimeStamp());&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fileUploadDTO.setUploadComplete(uploadFlag);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fileUploadDTO.setPath(filePath);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fileUploadDTO.setSize(newFile.length());&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fileUploadDTO.setFileExt(ext);&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fileUploadDTO.setFileId(toFileNewName);&nbsp;&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;fileUploadDTO;&nbsp;&nbsp;<br>&nbsp;&nbsp;}&nbsp;&nbsp;<br>}&nbsp;&nbsp;<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);">总结</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在实现分片上传的过程,需要前端和后端配合,比如前后端的上传块号的文件大小,前后端必须得要一致,否则上传就会有问题。其次文件相关操作正常都是要搭建一个文件服务器的,比如使用<span style="font-weight: 700;color: rgb(248, 57, 41);">fastdfs</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">hdfs</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;">本示例代码在电脑配置为4核内存8G情况下,上传24G大小的文件,上传时间需要30多分钟,主要时间耗费在前端的<span style="font-weight: 700;color: rgb(248, 57, 41);">md5</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;">如果项目组觉得自建文件服务器太花费时间,且项目的需求仅仅只是上传下载,那么推荐使用阿里的oss服务器,其介绍可以查看官网:</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">https://help.aliyun.com/product/31815.html</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">阿里的oss它本质是一个对象存储服务器,而非文件服务器,因此如果有涉及到大量删除或者修改文件的需求,oss可能就不是一个好的选择。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">文末提供一个oss表单上传的链接demo,通过oss表单上传,可以直接从前端把文件上传到oss服务器,把上传的压力都推给oss服务器:</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">https://www.cnblogs.com/ossteam/p/4942227.html</p> </blockquote> </section>

面试官:要不你给我说说什么是长轮询吧?

作者:微信小助手

<p style="white-space: normal;margin-bottom: 0px;"><br></p> <h2 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;padding: 0px;outline: 0px;font-weight: bold;font-size: 1.3em;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;letter-spacing: normal;orphans: 2;text-align: left;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-style: initial;text-decoration-color: initial;border-bottom: 2px solid rgb(239, 112, 96);visibility: visible;"><span style="margin: 0px 3px 0px 0px;padding: 3px 10px 1px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: inline-block;font-weight: bold;background: rgb(239, 112, 96);color: rgb(255, 255, 255);border-top-right-radius: 3px;border-top-left-radius: 3px;visibility: visible;">前言</span><span style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: inline-block;vertical-align: bottom;border-bottom: 36px solid rgb(239, 235, 233);border-right: 20px solid transparent;visibility: visible;"></span></h2> <p data-tool="mdnice编辑器" style="margin: 0px;padding: 8px 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 16px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-align: left;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-style: initial;text-decoration-color: initial;line-height: 26px;visibility: visible;">传统的静态配置方式想要修改某个配置时,必须重新启动一次应用,如果是数据库连接串的变更,那可能还容易接受一些,但如果变更的是一些运行时实时感知的配置,如某个功能项的开关,重启应用就显得有点大动干戈了。</p> <p data-tool="mdnice编辑器" style="margin: 0px;padding: 8px 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 16px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-align: left;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-style: initial;text-decoration-color: initial;line-height: 26px;visibility: visible;">配置中心正是为了解决此类问题应运而生的,特别是在微服务架构体系中,更倾向于使用配置中心来统一管理配置。</p> <p data-tool="mdnice编辑器" style="margin: 0px;padding: 8px 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 16px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-align: left;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-style: initial;text-decoration-color: initial;line-height: 26px;">配置中心最核心的能力就是配置的动态推送,常见的配置中心如 Nacos、Apollo 等都实现了这样的能力。</p> <p data-tool="mdnice编辑器" style="margin: 0px;padding: 8px 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 16px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-align: left;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-style: initial;text-decoration-color: initial;line-height: 26px;">在早期接触配置中心时,我就很好奇,配置中心是如何做到服务端感知配置变化实时推送给客户端的?</p> <p data-tool="mdnice编辑器" style="margin: 0px;padding: 8px 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 16px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-align: left;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-style: initial;text-decoration-color: initial;line-height: 26px;">在没有研究过配置中心的实现原理之前,我一度认为配置中心是通过长连接来做到配置推送的。</p> <p data-tool="mdnice编辑器" style="margin: 0px;padding: 8px 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !imp

可视化全链路日志追踪

作者:微信小助手

<p style="margin: 0px 0px 0em;padding: 0px;clear: both;min-height: 1em;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;text-align: center;" data-mpa-powered-by="yiban.io"><br></p> <section data-role="outer" label="Powered by 135editor.com" style="margin: 0px 0.5em;padding: 0px 0.5em;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;white-space: normal;"> <section data-role="outer" label="Powered by 135editor.com" style="margin: 0px;padding: 0px;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;"> <section data-tools="135编辑器" data-id="127" style="margin: 0px;padding: 0px;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;border-width: 0px;border-style: none;border-color: initial;"> <section data-tools="135编辑器" data-id="127" style="margin: 0px;padding: 0px;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;border-width: 0px;border-style: none;border-color: initial;"> <section style="margin: 60px 16px 16px;padding: 0px;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;border-width: 1px;border-style: solid;border-color: rgb(235, 234, 225);text-align: center;border-radius: 8px;"> <section style="margin: -3.3em 5px 0px;padding: 0px;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;font-weight: inherit;text-decoration: inherit;font-size: 18px;color: inherit;"> <p style="margin: 0px auto 15px;padding: 0px;clear: both;min-height: 1em;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;border-width: 2px;border-style: solid;border-color: rgb(235, 234, 225);width: 108px;height: 108px;border-radius: 50%;box-shadow: rgb(201, 201, 201) 0px 2px 2px 2px;background-color: rgb(254, 254, 254);"><img border="0" class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="103" data-cropsely1="0" data-cropsely2="103" data-ratio="1" src="/upload/2dd4db69f17ee81dacb110fb00f0a42a.png" data-type="png" data-w="750" data-width="100%" opacity="" style="margin: 0px;padding: 0px;max-width: 100%;height: 103px;overflow-wrap: break-word !important;vertical-align: bottom;border-radius: 50%;color: inherit;display: inline-block;width: 103px;visibility: visible !important;" title="undefined"></p> </section> <section data-brushtype="text" data-style="text-align: left; font-size: 14px; color: inherit;" style="margin: 8px 15px;padding: 0px;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;line-height: 1.4;"> <p style="margin: 0px 0px 8px;padding: 0px;max-width: 100%;text-align: justify;overflow-wrap: break-word !important;box-sizing: border-box !important;"><span style="margin: 0px;padding: 0px;max-width: 100%;color: rgb(136, 136, 136);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;overflow-wrap: break-word !important;box-sizing: border-box !important;">可观测性作为系统高可用的重要保障,已经成为系统建设中不可或缺的一环。然而随着业务逻辑的日益复杂,传统的ELK方案在日志搜集、筛选和分析等方面愈加耗时耗力,而分布式会话跟踪方案虽然基于追踪能力完善了日志的串联,但更聚焦于调用链路,也难以直接应用于高效的业务追踪。</span></p> <section style="margin: 0px;padding: 0px;max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;text-align: justify;"> <span style="margin: 0px;padding: 0px;max-width: 100%;color: rgb(136, 136, 136);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;overflow-wrap: break-word !important;box-sizing: border-box !important;">本文介绍了可视化全链路日志追踪的新方案,它以业务链路为载体,通过有效组织业务每次执行的日志,实现了执行现场的可视化还原,支持问题的高效定位。</span> </section> </section> </section> </section> </section> </section> </section> <ul style="margin: 8px 32px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li style="font-size: 13px;color: rgb(136, 136, 136);"><p><span style="font-size: 13px;color: rgb(136, 136, 136);">1. 背景</span></p></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: square;" class="list-paddingleft-1"> <li style="font-size: 13px;color: rgb(136, 136, 136);"><p><span style="font-size: 13px;color: rgb(136, 136, 136);">1.1 业务系统日益复杂</span></p></li> <li style="font-size: 13px;color: rgb(136, 136, 136);"><p><span style="font-size: 13px;color: rgb(136, 136, 136);">1.2 业务追踪面临挑战</span></p></li> </ul> <li style="font-size: 13px;color: rgb(136, 136, 136);"><p><span style="font-size: 13px;color: rgb(136, 136, 136);">2. 可视化全链路日志追踪</span></p></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: square;" class="list-paddingleft-1"> <li style="font-size: 13px;color: rgb(136, 136, 136);"><p><span style="font-size: 13px;color: rgb(136, 136, 136);">2.1 设计思路</span></p></li> <li style="font-size: 13px;color: rgb(136, 136, 136);"><p><span style="font-size: 13px;color: rgb(136, 136, 136);">2.2 通用方案</span></p></li> </ul> <li style="font-size: 13px;color: rgb(136, 136, 136);"><p><span style="font-size: 13px;color: rgb(136, 136, 136);">3. 大众点评内容平台实践</span></p></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: square;" class="list-paddingleft-1"> <li style="font-size: 13px;color: rgb(136, 136, 136);"><p><span style="font-size: 13px;color: rgb(136, 136, 136);">3.1 业务特点与挑战</span></p></li> <li style="font-size: 13px;color: rgb(136, 136, 136);"><p><span style="font-size: 13px;color: rgb(136, 136, 136);">3.2 实践与成果</span></p></li> </ul> <li style="font-size: 13px;color: rgb(136, 136, 136);"><p><span style="font-size: 13px;color: rgb(136, 136, 136);">4. 总结与展望</span></p></li> </ul> <h2 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;padding: 0px;font-weight: bold;color: black;font-size: 22px;text-align: justify;"><span style="font-size: 20px;color: rgb(255, 209, 0);">1. 背景</span></h2> <h3 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;text-align: justify;"><span style="font-size: 18px;">1.1 业务系统日益复杂</span></h3> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">随着互联网产品的快速发展,不断变化的商业环境和用户诉求带来了纷繁复杂的业务需求。业务系统需要支撑的业务场景越来越广、涵盖的业务逻辑越来越多,系统的复杂度也跟着快速提升。与此同时,由于微服务架构的演进,业务逻辑的实现往往需要依赖多个服务间的共同协作。总而言之,业务系统的日益复杂已经成为一种常态。</span> </section> <h3 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;text-align: justify;"><span style="font-size: 18px;">1.2 业务追踪面临挑战</span></h3> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">业务系统往往面临着多样的日常客诉和突发问题,“业务追踪”就成为了关键的应对手段。业务追踪可以看做一次业务执行的现场还原过程,通过执行中的各种记录还原出原始现场,可用于业务逻辑执行情况的分析和问题的定位,是整个系统建设中重要的一环。</span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">目前在分布式场景下,业务追踪的主流实现方式包括两类,一类是基于日志的ELK方案,一类是基于单次请求调用的会话跟踪方案。然而随着业务逻辑的日益复杂,上述方案越来越不适用于当下的业务系统。</span> </section> <h4 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;padding: 0px;font-weight: bold;color: black;font-size: 18px;text-align: justify;"><span style="font-size: 16px;">1.2.1 传统的ELK方案</span></h4> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">日志作为业务系统的必备能力,职责就是记录程序运行期间发生的离散事件,并且在事后阶段用于程序的行为分析,比如曾经调用过什么方法、操作过哪些数据等等。在分布式系统中,ELK技术栈已经成为日志收集和分析的通用解决方案。如下图1所示,伴随着业务逻辑的执行,业务日志会被打印,统一收集并存储至Elasticsearch(</span> <span style="font-size: 15px;color: rgb(136, 136, 136);">下称ES</span> <span style="font-size: 15px;">)<sup style="line-height: 0;">[2]</sup>。</span> </section> <section style="font-size: 16px;color: black;padding: 0px 10px;line-height: 1.6;word-spacing: 0px;letter-spacing: 0px;word-break: break-word;overflow-wrap: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin-left: 0px;margin-right: 0px;margin-bottom: 0px;"> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="558" data-cropsely1="0" data-cropsely2="303" data-ratio="0.5423728813559322" src="/upload/b4c2f34af58b24ce34fa5b5a350372a3.jpg" data-w="1062" style="display: block;margin: 0px auto;max-width: 100%;width: 559px;height: 303px;"> <figcaption style="margin-top: 5px;text-align: justify;color: rgb(136, 136, 136);font-size: 14px;"> <span style="font-size: 12px;">图1 业务系统ELK案例</span> </figcaption> </figure> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">传统的ELK方案需要开发者在编写代码时尽可能全地打印日志,再通过关键字段从ES中搜集筛选出与业务逻辑相关的日志数据,进而拼凑出业务执行的现场信息。然而该方案存在如下的痛点:</span> </section> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li style="font-size: 14px;"><p style="margin-bottom: 8px;"><strong><span style="font-size: 14px;">日志搜集繁琐</span></strong><span style="font-size: 14px;">:虽然ES提供了日志检索的能力,但是日志数据往往是缺乏结构性的文本段,很难快速完整地搜集到全部相关的日志。</span></p></li> <li style="font-size: 14px;"><p style="margin-bottom: 8px;"><strong><span style="font-size: 14px;">日志筛选困难</span></strong><span style="font-size: 14px;">:不同业务场景、业务逻辑之间存在重叠,重叠逻辑打印的业务日志可能相互干扰,难以从中筛选出正确的关联日志。</span></p></li> <li style="font-size: 14px;"> <section> <strong><span style="font-size: 14px;">日志分析耗时</span></strong> <span style="font-size: 14px;">:搜集到的日志只是一条条离散的数据,只能阅读代码,再结合逻辑,由人工对日志进行串联分析,尽可能地还原出现场。</span> </section></li> </ul> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">综上所述,随着业务逻辑和系统复杂度的攀升,传统的ELK方案在日志搜集、日志筛选和日志分析方面愈加的耗时耗力,很难快速实现对业务的追踪。</span> </section> <h4 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;padding: 0px;font-weight: bold;color: black;font-size: 18px;text-align: justify;"><span style="font-size: 16px;">1.2.2 分布式会话跟踪方案</span></h4> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">在分布式系统,尤其是微服务系统中,业务场景的某次请求往往需要经过多个服务、多个中间件、多台机器的复杂链路处理才能完成。为了解决复杂链路排查困难的问题,“分布式会话跟踪方案”诞生。该方案的理论知识由Google在2010年《Dapper》论文<sup style="line-height: 0;">[3]</sup>中发表,随后Twitter开发出了一个开源版本Zipkin<sup style="line-height: 0;">[4]</sup>。</span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">市面上的同类型框架几乎都是以Google Dapper论文为基础进行实现,整体大同小异,都是通过一个分布式全局唯一的id(</span> <span style="font-size: 15px;color: rgb(136, 136, 136);">即traceId</span> <span style="font-size: 15px;">),将分布在各个服务节点上的同一次请求串联起来,还原调用关系、追踪系统问题、分析调用数据、统计系统指标。分布式会话跟踪,是一种<strong style="font-weight: bold;color: black;">会话级别</strong>的追踪能力,如下图2所示,单个分布式请求被还原成一条调用链路,从客户端发起请求抵达系统的边界开始,记录请求流经的每一个服务,直到向客户端返回响应为止。</span> </section> <section style="margin: 0px;padding: 0px 10px;max-width: 100%;line-height: 1.6;letter-spacing: 0px;word-break: break-word;overflow-wrap: break-word !important;box-sizing: border-box !important;"> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="558" data-cropsely1="0" data-cropsely2="395" data-ratio="0.7078488372093024" src="/upload/8d5969f5353ab38ff2072ded44d1589f.jpg" data-w="688" style="display: block;margin: 0px auto;max-width: 100%;width: 447px;height: 316px;"> <figcaption style="margin-top: 5px;text-align: justify;color: rgb(136, 136, 136);font-size: 14px;"> <span style="font-size: 12px;">图2 一次典型的请求全过程(摘自《Dapper》)</span> </figcaption> </figure> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">分布式会话跟踪的主要作用是<strong style="font-weight: bold;color: black;">分析分布式系统的调用行为</strong>,并不能很好地应用于业务逻辑的追踪。下图3是一个审核业务场景的追踪案例,业务系统对外提供审核能力,待审对象的审核需要经过“初审”和“复审”两个环节(</span> <span style="font-size: 15px;color: rgb(136, 136, 136);">两个环节关联相同的taskId</span> <span style="font-size: 15px;">),因此整个审核环节的执行调用了两次审核接口。如图左侧所示,完整的审核场景涉及众多“业务逻辑”的执行,而分布式会话跟踪只是根据两次RPC调用生成了右侧的两条调用链路,并没有办法准确地描述审核场景业务逻辑的执行,问题主要体现在以下几个方面:</span> </section> <section style="margin: 0px;padding: 0px 10px;max-width: 100%;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;overflow-wrap: break-word !important;box-sizing: border-box !important;"> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="558" data-cropsely1="0" data-cropsely2="263" data-ratio="0.471875" src="/upload/13ba49d437e80efdbaf38c24765eeba9.jpg" data-w="1280" style="display: block;margin: 0px auto;max-width: 100%;width: 558px;height: 263px;"> <figcaption style="margin-top: 5px;text-align: justify;color: rgb(136, 136, 136);font-size: 14px;"> <span style="font-size: 12px;">图3 分布式会话跟踪案例</span> </figcaption> </figure> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;"><strong style="font-weight: bold;color: black;">(1) 无法同时追踪多条调用链路</strong></span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">分布式会话跟踪仅支持单个请求的调用追踪,当业务场景包含了多个调用时,将生成多条调用链路;由于调用链路通过traceId串联,不同链路之间相互独立,因此给完整的业务追踪增加了难度。例如当排查审核场景的业务问题时,由于初审和复审是不同的RPC请求,所以无法直接同时获取到2条调用链路,通常需要额外存储2个traceId的映射关系。</span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;"><strong style="font-weight: bold;color: black;">(2) 无法准确描述业务逻辑的全景</strong></span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">分布式会话跟踪生成的调用链路,只包含单次请求的实际调用情况,部分未执行的调用以及本地逻辑无法体现在链路中,导致无法准确描述业务逻辑的全景。例如同样是审核接口,初审链路1包含了服务b的调用,而复审链路2却并没有包含,这是因为审核场景中存在“判断逻辑”,而该逻辑无法体现在调用链路中,还是需要人工结合代码进行分析。</span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;"><strong style="font-weight: bold;color: black;">(3) 无法聚焦于当前业务系统的逻辑执行</strong></span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">分布式会话跟踪覆盖了单个请求流经的所有服务、组件、机器等等,不仅包含当前业务系统,还涉及了众多的下游服务,当接口内部逻辑复杂时,调用链路的深度和复杂度都会明显增加,而业务追踪其实仅需要聚焦于当前业务系统的逻辑执行情况。例如审核场景生成的调用链路,就涉及了众多下游服务的内部调用情况,反而给当前业务系统的问题排查增加了复杂度。</span> </section> <h4 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;padding: 0px;font-weight: bold;color: black;font-size: 18px;text-align: justify;"><span style="font-size: 16px;">1.2.3 总结</span></h4> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">传统的ELK方案是一种滞后的业务追踪,需要事后从大量离散的日志中搜集和筛选出需要的日志,并人工进行日志的串联分析,其过程必然耗时耗力。而分布式会话跟踪方案则是在调用执行的同时,实时地完成了链路的动态串联,但由于是会话级别且仅关注于调用关系等问题,导致其无法很好地应用于业务追踪。</span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">因此,无论是传统的ELK方案还是分布式会话跟踪方案,都难以满足日益复杂的业务追踪需求。本文希望能够实现聚焦于业务逻辑追踪的高效解决方案,将业务执行的日志以业务链路为载体进行高效组织和串联,并支持业务执行现场的还原和可视化查看,从而提升定位问题的效率,即<strong style="font-weight: bold;color: black;">可视化全链路日志追踪</strong>。</span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">下文将介绍<strong style="font-weight: bold;color: black;">可视化全链路日志追踪</strong>的设计思路和通用方案,同时介绍新方案在大众点评内容平台的落地情况,旨在帮助有类似需求的业务系统开发需求的同学提供一些思路。</span> </section> <h2 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;padding: 0px;font-weight: bold;color: black;font-size: 22px;text-align: justify;"><span style="font-size: 20px;color: rgb(255, 209, 0);">2. 可视化全链路日志追踪</span></h2> <h3 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;text-align: justify;"><span style="font-size: 18px;">2.1 设计思路</span></h3> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;"><strong style="font-weight: bold;color: black;">可视化全链路日志追踪</strong>考虑在前置阶段,即业务执行的同时实现业务日志的高效组织和动态串联,如下图4所示,此时离散的日志数据将会根据业务逻辑进行组织,绘制出执行现场,从而可以实现高效的业务追踪。</span> </section> <section style="margin: 0px;padding: 0px;max-width: 100%;font-size: 16px;line-height: 1.6;letter-spacing: 0px;overflow-wrap: break-word !important;box-sizing: border-box !important;"> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="578" data-cropsely1="0" data-cropsely2="316" data-ratio="0.5459715639810426" src="/upload/c8987867b58c105843c65c70b9a9c05.jpg" data-type="jpeg" data-w="1055" style="display: block;margin: 0px auto;max-width: 100%;width: 578px;height: 315px;"> <figcaption style="margin-top: 5px;text-align: justify;color: rgb(136, 136, 136);font-size: 14px;"> <span style="font-size: 12px;">图4 业务系统日志追踪案例</span> </figcaption> </figure> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">新方案需要回答两个关键问题:如何高效组织业务日志,以及如何动态串联业务日志。下文将逐一进行回答。</span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;"><strong style="font-weight: bold;color: black;">问题1:如何高效组织业务日志?</strong></span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">为了实现高效的业务追踪,首先需要准确完整地描述出业务逻辑,形成业务逻辑的全景图,而业务追踪其实就是通过执行时的日志数据,在全景图中还原出业务执行的现场。</span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">新方案对业务逻辑进行了抽象,定义出业务逻辑链路,下面还是以“审核业务场景”为例,来说明业务逻辑链路的抽象过程:</span> </section> <ul data-tool="mdnice编辑器" style="margin: 8px 0px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li style="font-size: 14px;"><p style="margin-bottom: 8px;"><strong><span style="font-size: 14px;">逻辑节点</span></strong><span style="font-size: 14px;">:业务系统的众多逻辑可以按照业务功能进行拆分,形成一个个相互独立的业务逻辑单元,即<strong>逻辑节点</strong>,可以是本地方法(</span><span style="font-size: 14px;color: rgb(136, 136, 136);">如下图5的“判断逻辑”节点</span><span style="font-size: 14px;">)也可以是RPC等远程调用方法(</span><span style="font-size: 14px;color: rgb(136, 136, 136);">如下图5的“逻辑A”节点</span><span style="font-size: 14px;">)。</span></p></li> <li style="font-size: 14px;"> <section> <strong><span style="font-size: 14px;">逻辑链路</span></strong> <span style="font-size: 14px;">:业务系统对外支撑着众多的业务场景,每个业务场景对应一个完整的业务流程,可以抽象为由逻辑节点组合而成的<strong>逻辑链路</strong>,如下图5中的逻辑链路就准确完整地描述了“审核业务场景”。</span> </section></li> </ul> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">一次业务追踪就是<strong style="font-weight: bold;color: black;">逻辑链路</strong>的某一次执行情况的还原,<strong style="font-weight: bold;color: black;">逻辑链路</strong>完整准确地描述了业务逻辑全景,同时作为载体可以实现业务日志的高效组织。</span> </section> <section style="margin: 0px;padding: 0px 0em;max-width: 100%;line-height: 1.6;letter-spacing: 0px;overflow-wrap: break-word !important;box-sizing: border-box !important;"> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="578" data-cropsely1="0" data-cropsely2="611" data-ratio="1.0575221238938053" src="/upload/757f9be79854252baead8142f372e298.jpg" data-type="jpeg" data-w="678" style="display: block;margin: 0px auto;max-width: 100%;width: 491px;height: 519px;"> <figcaption style="margin-top: 5px;text-align: justify;color: rgb(136, 136, 136);font-size: 14px;"> <span style="font-size: 12px;">图5 业务逻辑链路案例</span> </figcaption> </figure> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;"><strong style="font-weight: bold;color: black;">问题2:如何动态串联业务日志?</strong></span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">业务逻辑执行时的日志数据原本是离散存储的,而此时需要实现的是,随着业务逻辑的执行动态串联各个逻辑节点的日志,进而还原出完整的业务逻辑执行现场。</span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">由于逻辑节点之间、逻辑节点内部往往通过MQ或者RPC等进行交互,新方案可以采用分布式会话跟踪提供的<strong style="font-weight: bold;color: black;">分布式参数透传能力</strong><sup style="line-height: 0;">[5]</sup>实现业务日志的动态串联:</span> </section> <ul data-tool="mdnice编辑器" style="margin: 8px 0px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li style="font-size: 14px;"><p style="margin-bottom: 8px;"><span style="font-size: 14px;">通过在执行线程和网络通信中持续地透传参数,实现在业务逻辑执行的同时,不中断地传递链路和节点的标识,实现离散日志的染色。</span></p></li> <li style="font-size: 14px;"> <section> <span style="font-size: 14px;">基于标识,染色的离散日志会被动态串联至正在执行的节点,逐渐汇聚出完整的逻辑链路,最终实现业务执行现场的高效组织和可视化展示。</span> </section></li> </ul> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">与分布式会话跟踪方案不同的是,当同时串联多次分布式调用时,新方案需要结合业务逻辑选取一个公共id作为标识,例如图5的审核场景涉及2次RPC调用,为了保证2次执行被串联至同一条逻辑链路,此时结合审核业务场景,选择初审和复审相同的“任务id”作为标识,完整地实现审核场景的逻辑链路串联和执行现场还原。</span> </section> <h3 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;text-align: justify;"><span style="font-size: 18px;">2.2 通用方案</span></h3> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">明确日志的高效组织和动态串联这两个基本问题后,本文选取图4业务系统中的“逻辑链路1”进行通用方案的详细说明,方案可以拆解为以下步骤:</span> </section> <section style="margin: 0px;padding: 0px;max-width: 100%;line-height: 1.6;letter-spacing: 0px;overflow-wrap: break-word !important;box-sizing: border-box !important;"> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="578" data-cropsely1="0" data-cropsely2="128" data-ratio="0.2220744680851064" src="/upload/2d229ea6f465a622d501034f34c33be9.jpg" data-w="752" style="display: block;margin: 0px auto;max-width: 100%;width: 459px;height: 102px;"> <figcaption style="margin-top: 5px;text-align: justify;color: rgb(136, 136, 136);font-size: 14px;"> <span style="font-size: 12px;">图6 通用方案拆解</span> </figcaption> </figure> </section> <h4 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;padding: 0px;font-weight: bold;color: black;font-size: 18px;text-align: justify;"><span style="font-size: 16px;">2.2.1 链路定义</span></h4> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">“链路定义”的含义为:使用特定语言,静态描述完整的<strong style="font-weight: bold;color: black;">逻辑链路</strong>,链路通常由多个<strong style="font-weight: bold;color: black;">逻辑节点</strong>,按照一定的<strong style="font-weight: bold;color: black;">业务规则</strong>组合而成,<strong style="font-weight: bold;color: black;">业务规则</strong>即各个逻辑节点之间存在的执行关系,包括<strong style="font-weight: bold;color: black;">串行</strong>、<strong style="font-weight: bold;color: black;">并行</strong>、<strong style="font-weight: bold;color: black;">条件分支</strong>。</span> </section> <section style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0px;line-height: 26px;color: black;text-align: justify;"> <span style="font-size: 15px;">DSL(D</span> <span style="font-size: 15px;color: rgb(136, 136, 136);">omain Specific Language</span> <span style="font-size: 15px;">)是为了解决某一类任务而专门设计的计算机语言,可以通过JSON或XML定义出一系列节点(</span> <span style="font-size: 15px;color: rgb(136, 136, 136);">逻辑节点</span> <span style="font-size: 15px;">)的组合关系(</span> <span style="font-size: 15px;color: rgb(136, 136, 136);">业务规则</span> <span style="font-size: 15px;">)。因此,本方案选择使用DSL描述逻辑链路,实现逻辑链路从<strong style="font-weight: bold;color: black;">抽象定义</strong>到<strong style="font-weight: bold;color: black;">具体实现</strong>。</span> </section> <section style="margin: 0px;padding: 0px;max-width: 100%;line-height: 1.6;letter-spacing: 0px;overflow-wrap: break-word !important;box-sizing: border-box !important;"> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="578" data-cropsely1="0" data-cropsely2="267" data-ratio="0.4625984251968504" src="/upload/d56bc42d1daee28045aaa6e95b64a354.jpg" data-type="jpeg" data-w="1016" style="display: block;margin: 0px auto;max-width: 100%;width: 578px;height: 267px;"> <figcaption style="margin-top: 5px;text-align: justify;color: rgb(136, 136, 136);font-size: 14px;"> <span style="font-size: 12px;">图7 链路的抽象定义和具体实现</span> </figcaption> <figcaption style="margin-top: 5px;text-align: left;color: rgb(136, 136, 136);font-size: 14px;"> <span style="background-color: rgb(250, 250, 250);color: rgb(56, 58, 66);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 12px;white-space: pre-wrap;">逻辑链路1-DSL</span> </figcaption> </figure> </section> <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;"> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(56, 58, 66);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(250, 250, 250);border-radius: 5px;text-align: justify;margin-left: 0px;margin-right: 0px;"> <span style="font-size: 12px;">&nbsp;&nbsp;[<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"nodeName"</span>:&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"A"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"nodeType"</span>:&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"rpc"</span><br>&nbsp;&nbsp;&nbsp;&nbsp;},<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"nodeName"</span>:&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"Fork"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"nodeType"</span>:&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"fork"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"forkNodes"</span>:&nbsp;[<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"nodeName"</span>:&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"B"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"nodeType"</span>:&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"rpc"</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;],<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"nodeName"</span>:&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"C"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"nodeType"</span>:&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"local"</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<br>&nbsp;&nbsp;&nbsp;&nbsp;},<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"nodeName"</span>:&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"Join"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"nodeType"</span>:&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"join"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"joinOnList"</span>:&nbsp;[<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"B"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"C"</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<br>&nbsp;&nbsp;&nbsp;&nbsp;},<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"nodeName"</span>:&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"D"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);line-height: 26px;">"nodeType"</span>:&nbsp;<span style="font-size: 12px;color: rgb(80, 161, 79);lin

公司新来了一个同事,把权限系统设计的炉火纯青!

作者:微信小助手

<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;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><span style="color: rgb(136, 136, 136);font-size: 14px;">作者:小小____<br>来源:segmentfault.com/a/1190000023052493</span></p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">思维导图如下</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.3989071038251366" src="/upload/356b1af9e00cb7c9d01f8f19df494f36.jpg" data-type="jpeg" data-w="732" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;font-weight: bold;font-size: 2.1em;line-height: 1.1em;padding-top: 16px;padding-bottom: 10px;margin-bottom: 4px;border-bottom: 1px solid rgb(201, 152, 51);"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);">RBAC权限分析</span></h1> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">RBAC 全称为基于角色的权限控制,本段将会从什么是RBAC,模型分类,什么是权限,用户组的使用,实例分析等几个方面阐述RBAC</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">思维导图</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">绘制思维导图如下</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.5448613376835236" src="/upload/9b21b8c200d83643613f1623d3046fea.jpg" data-type="jpeg" data-w="613" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">什么是RBAC</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">RBAC 全称为用户角色权限控制,通过角色关联用户,角色关联权限,这种方式,间阶的赋予用户的权限,如下图所示</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.1448087431693989" src="/upload/8dd1cbbdda2524ff96da579a807e5529.jpg" data-type="jpeg" data-w="732" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">对于通常的系统而言,存在多个用户具有相同的权限,在分配的时候,要为指定的用户分配相关的权限,修改的时候也要依次的对这几个用户的权限进行修改,有了角色这个权限,在修改权限的时候,只需要对角色进行修改,就可以实现相关的权限的修改。这样做增加了效率,减少了权限漏洞的发生。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">模型分类</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">对于RBAC模型来说,分为以下几个模型 分别是RBAC0,RBAC1,RBAC2,RBAC3,这四个模型,这段将会依次介绍这四个模型,其中最常用的模型有RBAC0.</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">RBAC0</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">RBAC0是最简单的RBAC模型,这里面包含了两种。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">用户和角色是多对一的关系,即一个用户只充当一种角色,一个角色可以有多个角色的担当。用户和角色是多对多的关系,即,一个用户可以同时充当多个角色,一个角色可以有多个用户。&nbsp;</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">此系统功能单一,人员较少,这里举个栗子,张三既是行政,也负责财务,此时张三就有俩个权限,分别是行政权限,和财务权限两个部分。</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">RBAC1</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">相对于RBAC0模型来说,增加了子角色,引入了继承的概念。</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.21584699453551912" src="/upload/7d61dace9a579cad88e0d2fccdde997c.jpg" data-type="jpeg" data-w="732" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">RBAC2 模型</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">这里RBAC2模型,在RBAC0模型的基础上,增加了一些功能,以及限制</p> <h4 data-tool="mdnice编辑器" style="line-height: 1.5em;margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 18px;"><span style="display: none;"></span>角色互斥<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">即,同一个用户不能拥有两个互斥的角色,举个例子,在财务系统中,一个用户不能拥有会计员和审计这两种角色。</p> <h4 data-tool="mdnice编辑器" style="line-height: 1.5em;margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 18px;"><span style="display: none;"></span>基数约束<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">即,用一个角色,所拥有的成员是固定的,例如对于CEO这种角色,同一个角色,也只能有一个用户。</p> <h4 data-tool="mdnice编辑器" style="line-height: 1.5em;margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 18px;"><span style="display: none;"></span>先决条件<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">即,对于该角色来说,如果想要获得更高的角色,需要先获取低一级别的角色。举个栗子,对于副总经理和经理这两个权限来说,需要先有副总经理权限,才能拥有经理权限,其中副总经理权限是经理权限的先决条件。</p> <h4 data-tool="mdnice编辑器" style="line-height: 1.5em;margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 18px;"><span style="display: none;"></span>运行时互斥<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">即,一个用户可以拥有两个角色,但是这俩个角色不能同时使用,需要切换角色才能进入另外一个角色。举个栗子,对于总经理和专员这两个角色,系统只能在一段时间,拥有其一个角色,不能同时对这两种角色进行操作。</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">RBAC3模型</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">即,RBAC1,RBAC2,两者模型全部累计,称为统一模型。</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.5748502994011976" src="/upload/61a7a18cd5762ac2bdc6a5e1680a0163.jpg" data-type="jpeg" data-w="501" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">什么是权限</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">权限是资源的集合,这里的资源指的是软件中的所有的内容,即,对页面的操作权限,对页面的访问权限,对数据的增删查改的权限。举个栗子。对于下图中的系统而言,</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.3551912568306011" src="/upload/a9f4cf89873b182d6ef18d5304a54f0c.jpg" data-type="jpeg" data-w="732" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">拥有,计划管理,客户管理,合同管理,出入库通知单管理,粮食安全追溯,粮食统计查询,设备管理这几个页面,对这几个页面的访问,以及是否能够访问到菜单,都属于权限。</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">用户组的使用</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">对于用户组来说,是把众多的用户划分为一组,进行批量授予角色,即,批量授予权限。举个栗子,对于部门来说,一个部门拥有一万多个员工,这些员工都拥有相同的角色,如果没有用户组,可能需要一个个的授予相关的角色,在拥有了用户组以后,只需要,把这些用户全部划分为一组,然后对该组设置授予角色,就等同于对这些用户授予角色。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">优点:减少工作量,便于理解,增加多级管理,等。<span style="letter-spacing: 0px;color: rgb(89, 89, 89);">最新面试题整理好了,点击<a class="weapp_text_link js_weapp_entry" style="font-size:16px;" data-miniprogram-appid="wxe57fd7ba3fb24ae8" data-miniprogram-path="pages/index/list" data-miniprogram-nickname="Java面试库" href="" data-miniprogram-type="text" data-miniprogram-servicetype="">Java面试库</a></span><span style="letter-spacing: 0px;color: rgb(89, 89, 89);">小程序在线刷题。</span><span style="color: rgb(89, 89, 89);letter-spacing: 0px;"></span></p> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;font-weight: bold;font-size: 2.1em;line-height: 1.1em;padding-top: 16px;padding-bottom: 10px;margin-bottom: 4px;border-bottom: 1px solid rgb(201, 152, 51);"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);">SpringSecurity 简单使用</span><br></h1> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">首先添加依赖</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><span style="color: black;letter-spacing: 0px;">Spring Boot 基础就不介绍了,推荐下这个实战教程:</span><span style="color: black;letter-spacing: 0px;">https://github.com/javastacks/spring-boot-best-practice</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/7N2JRaWooRCg2OO2q6iaCs9x8tDYViahqcu3T2GDVC3LPbxdYjE1MhtbhAUo69vfpia9f3tRhTZ3BuQ7L05Hb5w61swseLWPhHh/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="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">dependency</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">groupId</span>&gt;</span>org.springframework.boot<span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">groupId</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">artifactId</span>&gt;</span>spring-boot-starter-security<span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">artifactId</span>&gt;</span><br><span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">dependency</span>&gt;</span><br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">然后添加相关的访问接口</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/7N2JRaWooRCg2OO2q6iaCs9x8tDYViahqcu3T2GDVC3LPbxdYjE1MhtbhAUo69vfpia9f3tRhTZ3BuQ7L05Hb5w61swseLWPhHh/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;">package</span>&nbsp;com.example.demo.web;<br><br><span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;org.springframework.web.bind.<span style="color: #c678dd;line-height: 26px;">annotation</span>.RequestMapping;<br><span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;org.springframework.web.bind.<span style="color: #c678dd;line-height: 26px;">annotation</span>.RestController;<br><br><span style="color: #61aeee;line-height: 26px;">@RestController</span><br><span style="color: #61aeee;line-height: 26px;">@RequestMapping(<span style="color: #98c379;line-height: 26px;">"/test"</span>)</span><br><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">class</span>&nbsp;<span style="color: #e6c07b;line-height: 26px;">Test</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #61aeee;line-height: 26px;">@RequestMapping(<span style="color: #98c379;line-height: 26px;">"/test"</span>)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;String&nbsp;test(){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;<span style="color: #98c379;line-height: 26px;">"test"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">最后启动项目,在日志中查看相关的密码</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.1871584699453552" src="/upload/8f7b2dc21d49aa17423014ce17f7bf4e.jpg" data-type="jpeg" data-w="732" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">访问接口,可以看到相关的登录界面</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.36885245901639346" src="/upload/6ab5ee65bc39e561b1974ec01347df6a.jpg" data-type="jpeg" data-w="732" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">输入用户名和相关的密码</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/7N2JRaWooRCg2OO2q6iaCs9x8tDYViahqcu3T2GDVC3LPbxdYjE1MhtbhAUo69vfpia9f3tRhTZ3BuQ7L05Hb5w61swseLWPhHh/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;">用户名:user<br>密码 984cccf2-ba82-468e-a404-7d32123d0f9c<br></code></pre> <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.34972677595628415" src="/upload/6ee1454554c4a5ee8c24f182a4babc0f.jpg" data-type="jpeg" data-w="732" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">登录成功</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">增加用户名和密码</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">在配置文件中,书写相关的登录和密码</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/7N2JRaWooRCg2OO2q6iaCs9x8tDYViahqcu3T2GDVC3LPbxdYjE1MhtbhAUo69vfpia9f3tRhTZ3BuQ7L05Hb5w61swseLWPhHh/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;">spring:<br> security:<br> user:<br> name: ming<br> password: 123456<br> roles: admin<br><br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">在登录页面,输入用户名和密码,即可正常登录。另外,Spring&nbsp;系列面试题和答案全部整理好了,微信搜索Java技术栈,在后台发送:面试,可以在线阅读。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">基于内存的认证</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&amp;mid=2247549468&amp;idx=2&amp;sn=50dca55eacad0848fee04222b2064841&amp;chksm=eb50872adc270e3c12d8227c1f4ee4b8981a8a847602053fe44c6edd769600b1dbd52759ffec&amp;scene=21#wechat_redirect" textvalue="需要自定义类继承 WebSecurityConfigurerAdapter 代码如下package&nbsp;com.example.demo.config;import&nbsp;org.springframework.context.annotation.Bean;import&nbsp;org.springframework.context.annotation.Configuration;import&nbsp;org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import&nbsp;org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import&nbsp;org.springframework.security.crypto.password.NoOpPasswordEncoder;import&nbsp;org.springframework.security.crypto.password.PasswordEncoder;@Configurationpublic&nbsp;class&nbsp;MyWebSecurityConfig&nbsp;extends&nbsp;WebSecurityConfigurerAdapter&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;@Bean&nbsp;&nbsp;&nbsp;&nbsp;PasswordEncoder&nbsp;passwordEncoder(){&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;NoOpPasswordEncoder.getInstance();&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;&nbsp;&nbsp;protected&nbsp;void&nbsp;configure(AuthenticationManagerBuilder&nbsp;auth)&nbsp;throws&nbsp;Exception&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;auth.inMemoryAuthentication()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.withUser(&quot;admin&quot;).password(&quot;123&quot;).roles(&quot;admin&quot;);&nbsp;&nbsp;&nbsp;&nbsp;}}即,配置的用户名为admin,密码为123,角色为admin" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2"><span style="color: rgb(58, 58, 58);">需要自定义类继承 WebSecurityConfigurerAdapter 代码如下</span></a></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&amp;mid=2247549468&amp;idx=2&amp;sn=50dca55eacad0848fee04222b2064841&amp;chksm=eb50872adc270e3c12d8227c1f4ee4b8981a8a847602053fe44c6edd769600b1dbd52759ffec&amp;scene=21#wechat_redirect" textvalue="需要自定义类继承 WebSecurityConfigurerAdapter 代码如下package&nbsp;com.example.demo.config;import&nbsp;org.springframework.context.annotation.Bean;import&nbsp;org.springframework.context.annotation.Configuration;import&nbsp;org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import&nbsp;org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import&nbsp;org.springframework.security.crypto.password.NoOpPasswordEncoder;import&nbsp;org.springframework.security.crypto.password.PasswordEncoder;@Configurationpublic&nbsp;class&nbsp;MyWebSecurityConfig&nbsp;extends&nbsp;WebSecurityConfigurerAdapter&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;@Bean&nbsp;&nbsp;&nbsp;&nbsp;PasswordEncoder&nbsp;passwordEncoder(){&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;NoOpPasswordEncoder.getInstance();&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;&nbsp;&nbsp;protected&nbsp;void&nbsp;configure(AuthenticationManagerBuilder&nbsp;auth)&nbsp;throws&nbsp;Exception&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;auth.inMemoryAuthentication()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.withUser(&quot;admin&quot;).password(&quot;123&quot;).roles(&quot;admin&quot;);&nbsp;&nbsp;&nbsp;&nbsp;}}即,配置的用户名为admin,密码为123,角色为admin" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7N2JRaWooRCg2OO2q6iaCs9x8tDYViahqcu3T2GDVC3LPbxdYjE1MhtbhAUo69vfpia9f3tRhTZ3BuQ7L05Hb5w61swseLWPhHh/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;">package</span>&nbsp;com.example.demo.config;<br><br><span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;org.springframework.context.annotation.<span style="color: #d19a66;line-height: 26px;">Bean</span>;<br><span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;org.springframework.context.annotation.<span style="color: #d19a66;line-height: 26px;">Configuration</span>;<br><span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;org.springframework.security.config.annotation.authentication.builders.<span style="color: #d19a66;line-height: 26px;">AuthenticationManagerBuilder</span>;<br><span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;org.springframework.security.config.annotation.web.configuration.<span style="color: #d19a66;line-height: 26px;">WebSecurityConfigurerAdapter</span>;<br><span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;org.springframework.security.crypto.password.<span style="color: #d19a66;line-height: 26px;">NoOpPasswordEncoder</span>;<br><span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;org.springframework.security.crypto.password.<span style="color: #d19a66;line-height: 26px;">PasswordEncoder</span>;<br><br><span style="color: #61aeee;line-height: 26px;">@Configuration</span><br>public&nbsp;<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">class</span>&nbsp;<span style="color: #e6c07b;line-height: 26px;">MyWebSecurityConfig</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">extends</span>&nbsp;<span style="color: #e6c07b;line-height: 26px;">WebSecurityConfigurerAdapter</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #61aeee;line-height: 26px;">@Bean</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #d19a66;line-height: 26px;">PasswordEncoder</span>&nbsp;passwordEncoder(){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;<span style="color: #d19a66;line-height: 26px;">NoOpPasswordEncoder</span>.getInstance();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #61aeee;line-height: 26px;">@Override</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">protected</span>&nbsp;void&nbsp;configure(<span style="color: #d19a66;line-height: 26px;">AuthenticationManagerBuilder</span>&nbsp;auth)&nbsp;<span style="color: #c678dd;line-height: 26px;">throws</span>&nbsp;<span style="color: #d19a66;line-height: 26px;">Exception</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;auth.inMemoryAuthentication()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.withUser(<span style="color: #98c379;line-height: 26px;">"admin"</span>).password(<span style="color: #98c379;line-height: 26px;">"123"</span>).roles(<span style="color: #98c379;line-height: 26px;">"admin"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></a></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&amp;mid=2247549468&amp;idx=2&amp;sn=50dca55eacad0848fee04222b2064841&amp;chksm=eb50872adc270e3c12d8227c1f4ee4b8981a8a847602053fe44c6edd769600b1dbd52759ffec&amp;scene=21#wechat_redirect" textvalue="需要自定义类继承 WebSecurityConfigurerAdapter 代码如下package&nbsp;com.example.demo.config;import&nbsp;org.springframework.context.annotation.Bean;import&nbsp;org.springframework.context.annotation.Configuration;import&nbsp;org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import&nbsp;org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import&nbsp;org.springframework.security.crypto.password.NoOpPasswordEncoder;import&nbsp;org.springframework.security.crypto.password.PasswordEncoder;@Configurationpublic&nbsp;class&nbsp;MyWebSecurityConfig&nbsp;extends&nbsp;WebSecurityConfigurerAdapter&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;@Bean&nbsp;&nbsp;&nbsp;&nbsp;PasswordEncoder&nbsp;passwordEncoder(){&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;NoOpPasswordEncoder.getInstance();&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;&nbsp;&nbsp;protected&nbsp;void&nbsp;configure(AuthenticationManagerBuilder&nbsp;auth)&nbsp;throws&nbsp;Exception&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;auth.inMemoryAuthentication()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.withUser(&quot;admin&quot;).password(&quot;123&quot;).roles(&quot;admin&quot;);&nbsp;&nbsp;&nbsp;&nbsp;}}即,配置的用户名为admin,密码为123,角色为admin" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" style="color: rgb(58, 58, 58);" data-linktype="2"><span style="color: rgb(58, 58, 58);">即,配置的用户名为admin,密码为123,角色为admin</span></a></p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">HttpSecurity</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&amp;mid=2247549468&amp;idx=2&amp;sn=50dca55eacad0848fee04222b2064841&amp;chksm=eb50872adc270e3c12d8227c1f4ee4b8981a8a847602053fe44c6edd769600b1dbd52759ffec&amp;scene=21#wechat_redirect" textvalue="这里对一些方法进行拦截package com.ming.demo.interceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { //基于内存的用户存储 @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser(&quot;itguang&quot;).password(&quot;123456&quot;).roles(&quot;USER&quot;).and() .withUser(&quot;admin&quot;).password(&quot;{noop}&quot; + &quot;123456&quot;).roles(&quot;ADMIN&quot;); } //请求拦截 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().permitAll() .and() .formLogin() .permitAll() .and() .logout() .permitAll(); }}即,这里完成了对所有的方法访问的拦截。" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2"><span style="color: rgb(58, 58, 58);">这里对一些方法进行拦截</span></a></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&amp;mid=2247549468&amp;idx=2&amp;sn=50dca55eacad0848fee04222b2064841&amp;chksm=eb50872adc270e3c12d8227c1f4ee4b8981a8a847602053fe44c6edd769600b1dbd52759ffec&amp;scene=21#wechat_redirect" textvalue="这里对一些方法进行拦截package com.ming.demo.interceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { //基于内存的用户存储 @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser(&quot;itguang&quot;).password(&quot;123456&quot;).roles(&quot;USER&quot;).and() .withUser(&quot;admin&quot;).password(&quot;{noop}&quot; + &quot;123456&quot;).roles(&quot;ADMIN&quot;); } //请求拦截 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().permitAll() .and() .formLogin() .permitAll() .and() .logout() .permitAll(); }}即,这里完成了对所有的方法访问的拦截。" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7N2JRaWooRCg2OO2q6iaCs9x8tDYViahqcu3T2GDVC3LPbxdYjE1MhtbhAUo69vfpia9f3tRhTZ3BuQ7L05Hb5w61swseLWPhHh/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;">package com.ming.demo.interceptor;<br><br>import org.springframework.beans.factory.annotation.Autowired;<br>import org.springframework.context.annotation.Bean;<br>import org.springframework.context.annotation.Configuration;<br>import org.springframework.http.HttpMethod;<br>import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;<br>import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;<br>import org.springframework.security.config.annotation.web.builders.HttpSecurity;<br>import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;<br>import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;<br>import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;<br>import org.springframework.security.crypto.password.PasswordEncoder;<br>import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;<br><br>@Configuration<br>@EnableWebSecurity<br>public class SecurityConfig extends WebSecurityConfigurerAdapter {<br> //基于内存的用户存储<br> @Override<br> public void configure(AuthenticationManagerBuilder auth) throws Exception {<br> auth.inMemoryAuthentication()<br> .withUser("itguang").password("123456").roles("USER").and()<br> .withUser("admin").password("{noop}" + "123456").roles("ADMIN");<br> }<br><br> //请求拦截<br> @Override<br> protected void configure(HttpSecurity http) throws Exception {<br> http.authorizeRequests()<br> .anyRequest().permitAll()<br> .and()<br> .formLogin()<br> .permitAll()<br> .and()<br> .logout()<br> .permitAll();<br> }<br><br>}<br></code></a></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&amp;mid=2247549468&amp;idx=2&amp;sn=50dca55eacad0848fee04222b2064841&amp;chksm=eb50872adc270e3c12d8227c1f4ee4b8981a8a847602053fe44c6edd769600b1dbd52759ffec&amp;scene=21#wechat_redirect" textvalue="这里对一些方法进行拦截package com.ming.demo.interceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { //基于内存的用户存储 @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser(&quot;itguang&quot;).password(&quot;123456&quot;).roles(&quot;USER&quot;).and() .withUser(&quot;admin&quot;).password(&quot;{noop}&quot; + &quot;123456&quot;).roles(&quot;ADMIN&quot;); } //请求拦截 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().permitAll() .and() .formLogin() .permitAll() .and() .logout() .permitAll(); }}即,这里完成了对所有的方法访问的拦截。" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" style="color: rgb(58, 58, 58);" data-linktype="2"><span style="color: rgb(58, 58, 58);">即,这里完成了对所有的方法访问的拦截。</span></a></p> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;font-weight: bold;font-size: 2.1em;line-height: 1.1em;padding-top: 16px;padding-bottom: 10px;margin-bottom: 4px;border-bottom: 1px solid rgb(201, 152, 51);"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);">SpringSecurity 集成JWT</span></h1> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">这是一个小demo,目的,登录以后返回jwt生成的token</p> <p>推荐一个 Spring Boot 基础教程及实战示例:</p> <p>https://github.com/javastacks/spring-boot-best-practice</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">导入依赖</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">添加web依赖</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.6612021857923497" src="/upload/735d972b33261c15389cb1694f168aa9.jpg" data-type="jpeg" data-w="732" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">导入JWT和Security依赖</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/7N2JRaWooRCg2OO2q6iaCs9x8tDYViahqcu3T2GDVC3LPbxdYjE1MhtbhAUo69vfpia9f3tRhTZ3BuQ7L05Hb5w61swseLWPhHh/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;">&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">&lt;!--&nbsp;https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt&nbsp;--&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height:

构建 Java 镜像的 10 个最佳实践

作者:微信小助手

<p style="margin-bottom: 0px;"><span style="font-size: 15px;text-align: left;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;">你想构建一个 Java 应用程序并在 Docker 中运行它吗?</span><span style="font-size: 15px;text-align: left;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;">你知道在使用 Docker 构建 Java 容器有哪些最佳实践?</span></p> <p data-track="1" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="2" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">在下面的速查表中,我将为你提供构建生产级 Java 容器的最佳实践,旨在优化和保护要投入生产环境中的 Docker 镜像。</span><span style="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></p> <p data-track="14" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="14" style="margin-bottom: 0px;"><span style="color: rgb(171, 25, 66);"><strong><span style="color: rgb(171, 25, 66);font-size: 15px;text-align: left;">构建一个简单的 Java 容器镜像</span></strong></span></p> <p data-track="14" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="29" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">让我们从简单的 Dockerfile 开始,在构建 Java 容器时,我们经常会有如下类似的内容:</span></p> <p data-track="29" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer">FROM&nbsp;maven</span></code><code><span class="code-snippet_outer">RUN&nbsp;mkdir&nbsp;/app</span></code><code><span class="code-snippet_outer">WORKDIR&nbsp;/app</span></code><code><span class="code-snippet_outer">COPY&nbsp;.&nbsp;/app</span></code><code><span class="code-snippet_outer">RUN&nbsp;mvn&nbsp;clean&nbsp;<span class="code-snippet__keyword">install</span></span></code><code><span class="code-snippet_outer">CMD&nbsp;<span class="code-snippet__string">"mvn"</span>&nbsp;<span class="code-snippet__string">"exec:java"</span></span></code></pre> </section> <p data-track="29" style="text-align: left;margin-bottom: 0px;"><br></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="shell"><code><span class="code-snippet_outer"><span class="code-snippet__meta">$</span> docker build . -t java-application</span></code><code><span class="code-snippet_outer"><span class="code-snippet__meta">$</span> docker run -p 8080:8080 java-application</span></code></pre> </section> <p data-track="29" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="39" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">这很简单,而且有效。但是,此镜像充满错误。</span></p> <p data-track="39" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="40" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">我们不仅应该了解如何正确使用 Maven,而且还应避免像上述示例那样构建 Java 容器。</span></p> <p data-track="40" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="41" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">下面,让我们开始逐步改进这个Dockerfile,使你的Java应用程序生成高效,安全的Docker镜像。</span></p> <p data-track="41" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="42" style="margin-bottom: 0px;"><span style="color: rgb(171, 25, 66);"><strong><span style="color: rgb(171, 25, 66);font-size: 15px;text-align: left;">1. Docker 镜像使用确定性的标签</span></strong></span></p> <p data-track="42" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="54" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">当使用 Maven 构建 Java 容器镜像时,我们首先需要基于 Maven 镜像。但是,你知道使用 Maven 基本镜像时实际上引入了哪些内容吗?</span></p> <p data-track="54" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="55" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">当你使用下面的代码行构建镜像时,你将获得该 Maven 镜像的最新版本:</span></p> <p data-track="55" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="nginx"><code><span class="code-snippet_outer"><span class="code-snippet__attribute">FROM</span> maven</span></code></pre> </section> <p data-track="56" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="57" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">这似乎是一个有趣的功能,但是这种采用 Maven 默认镜像的策略可能存在一些潜在问题:</span></p> <p data-track="57" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li style="font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">你的 Docker 构建不是幂等的。这意味着每次构建的结果可能会完全不同,今天的最新镜像可能不同于明天或下周的最新镜像,导致你的应用程序的字节码也是不同的,并且可能发生意外。因此,构建镜像时,我们希望具有可复制的确定性行为;</span></p></li> <li style="font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">Maven Docker 镜像是基于完整的操作系统镜像。这样会导致许多其他二进制文件出现在最终的生产镜像中,但是运行你的 Java 应用程序不需要很多这些二进制文件。因此,将它们作为 Java 容器镜像的一部分存在一些缺点:</span><span style="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;">1) 镜像体积变大,导致更长的下载和构建时间。2) 额外的二进制文件可能会引入安全漏洞。</span></p></li> </ul> <p data-track="62" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="62" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">如何解决?</span></p> <p data-track="62" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <ul class="list-paddingleft-1" style="text-align: left;margin-bottom: 0px;"> <li style="text-align: left;margin-bottom: 0px;font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">使用适合你需求的最小基础镜像。考虑一下——你是否需要一个完整的操作系统(包括所有额外的二进制文件)来运行你的程序?如果没有,也许基于 alpine 镜像或 Debian 的镜像会更好;</span></p></li> <li style="text-align: left;margin-bottom: 0px;font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">使用特定的镜像 如果使用特定的镜像,则已经可以控制和预测某些行为。如果我使用 maven:3.6.3-jdk-11-slim 镜像,则已经确定我正在使用 JDK 11 和 Maven 3.6.3。JDK 和 Maven 的更新,将不再影响 Java 容器的行为。为了更加精确,你也可以使用镜像的 SHA256 哈希值。使用哈希将确保你每次构建镜像时都使用完全相同的基础镜像。</span></p></li> </ul> <p data-track="71" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="71" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">让我们用这些知识更新我们的 Dockerfile:</span></p> <p data-track="71" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;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;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="properties"><code><span class="code-snippet_outer"><span class="code-snippet__attr">FROM</span> <span class="code-snippet__string">maven:3.6.3-jdk-11-slim@sha256:68ce1cd457891f48d1e137c7d6a4493f60843e84c9e2634e3df1d3d5b381d36c</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN</span> <span class="code-snippet__string">mkdir /app</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">WORKDIR</span> <span class="code-snippet__string">/app</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">COPY</span> <span class="code-snippet__string">. /app</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN</span> <span class="code-snippet__string">mvn clean package -DskipTests</span></span></code></pre> </section> <p data-track="71" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="80" style="margin-bottom: 0px;"><span style="color: rgb(171, 25, 66);"><strong><span style="color: rgb(171, 25, 66);font-size: 15px;text-align: left;">2. 在 Java 镜像中仅安装需要的内容</span></strong></span></p> <p data-track="80" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="81" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">以下命令会在容器中构建 Java 程序,包括其所有依赖项。这意味着源代码和构建系统都将会是 Java 容器的一部分。</span></p> <p data-track="81" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="java"><code><span class="code-snippet_outer">RUN mvn clean <span class="code-snippet__keyword">package</span> -DskipTests</span></code></pre> </section> <p data-track="82" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="83" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">我们都知道 Java 是一种编译语言。这意味着我们只需要由你的构建环境创建的工件,而不需要代码本身。这也意味着构建环境不应成为 Java 镜像的一部分。</span></p> <p data-track="83" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="84" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">要运行 Java 镜像,我们也不需要完整的 JDK。一个 Java 运行时环境(JRE)就足够了。因此,从本质上讲,如果它是可运行的 JAR,那么只需要使用 JRE 和已编译的 Java 构件来构建镜像。</span></p> <p data-track="84" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="85" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">使用 Maven 在 CI 流水线中都构建编译程序,然后将JAR复制到镜像中,如下面的更新的 Dockerfile 中所示:</span></p> <p data-track="85" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="nginx"><code><span class="code-snippet_outer"><span class="code-snippet__attribute">FROM</span>&nbsp;openjdk:<span class="code-snippet__number">11</span>-jre-slim<span class="code-snippet__variable">@sha256</span>:31a5d3fa2942eea891cf954f7d07359e09cf1b1f3d35fb32fedebb1e3399fc9e</span></code><code><span class="code-snippet_outer">RUN&nbsp;mkdir&nbsp;/app</span></code><code><span class="code-snippet_outer">COPY&nbsp;./target/java-application.jar&nbsp;/app/java-application.jar</span></code><code><span class="code-snippet_outer">WORKDIR&nbsp;/app</span></code><code><span class="code-snippet_outer">CMD <span class="code-snippet__string">"java"</span> <span class="code-snippet__string">"-jar"</span> <span class="code-snippet__string">"java-application.jar"</span></span></code></pre> </section> <p data-track="98" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="140" style="margin-bottom: 0px;"><span style="color: rgb(171, 25, 66);"><strong><span style="color: rgb(171, 25, 66);font-size: 15px;text-align: left;">3. 使用多阶段构建 Java 镜像</span></strong></span></p> <p data-track="140" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="141" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">在本文的前面,我们谈到了我们不需要在容器中构建 Java 应用程序。但是,在某些情况下,将我们的应用程序构建为 Docker 镜像的一部分很方便。</span></p> <p data-track="141" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="142" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">我们可以将 Docker 镜像的构建分为多个阶段。我们可以使用构建应用程序所需的所有工具来构建镜像,并在最后阶段创建实际的生产镜像。</span></p> <p data-track="142" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="properties"><code><span class="code-snippet_outer"><span class="code-snippet__attr">FROM</span> <span class="code-snippet__string">maven:3.6.3-jdk-11-slim@sha256:68ce1cd457891f48d1e137c7d6a4493f60843e84c9e2634e3df1d3d5b381d36c AS build</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN&nbsp;mkdir&nbsp;/project</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">COPY&nbsp;.&nbsp;/project</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">WORKDIR&nbsp;/project</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN&nbsp;mvn&nbsp;clean&nbsp;package&nbsp;-DskipTests</span></span></code></pre> </section> <p data-track="142" style="text-align: left;margin-bottom: 0px;"><br></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="typescript"><code><span class="code-snippet_outer">FROM adoptopenjdk/openjdk11:jre<span class="code-snippet__number">-11.0</span><span class="code-snippet__number">.9</span><span class="code-snippet__number">.1</span>_1-alpine<span class="code-snippet__meta">@sha256</span>:b6ab039066382d39cfc843914ef1fc624aa60e2a16ede433509ccadd6d995b1f</span></code><code><span class="code-snippet_outer">RUN&nbsp;mkdir&nbsp;/app</span></code><code><span class="code-snippet_outer">COPY&nbsp;--<span class="code-snippet__keyword">from</span>=build&nbsp;/project/target/java-application.jar&nbsp;/app/java-application.jar</span></code><code><span class="code-snippet_outer">WORKDIR&nbsp;/app</span></code><code><span class="code-snippet_outer">CMD&nbsp;<span class="code-snippet__string">"java"</span>&nbsp;<span class="code-snippet__string">"-jar"</span>&nbsp;<span class="code-snippet__string">"java-application.jar"</span></span></code></pre> </section> <p data-track="142" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="170" style="margin-bottom: 0px;"><span style="color: rgb(171, 25, 66);"><strong><span style="color: rgb(171, 25, 66);font-size: 15px;text-align: left;">4. 防止敏感信息泄漏</span></strong></span></p> <p data-track="170" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="171" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">在创建 Java 应用程序和 Docker 镜像时,很有可能需要连接到私有仓库,类似 settings.xml 的配置文件经常会泄露敏感信息。但在使用多阶段构建时,你可以安全地将 settings.xml 复制到你的构建容器中。带有凭据的设置将不会出现在你的最终镜像中。</span><span style="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;font-size: 15px;">此外,如果将凭据用作命令行参数,则可以在构建镜像中安全地执行此操作。</span></p> <p data-track="171" style="text-align: left;margin-bottom: 0px;"><span style="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;font-size: 15px;"><br></span></p> <p data-track="172" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">使用多阶段构建,你可以创建多个阶段,仅将结果复制到最终的生产镜像中。这种分离是确保在生产环境中不泄漏数据的一种方法。</span></p> <p data-track="172" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="173" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">哦,顺便说一句,使用 docker history 命令查看 Java 镜像的输出:</span></p> <p data-track="173" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="shell"><code><span class="code-snippet_outer">$ docker history java-application</span></code></pre> </section> <p data-track="174" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="175" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">输出仅显示来自容器镜像的信息,而不显示构建镜像的过程。</span></p> <p data-track="175" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="176" style="margin-bottom: 0px;"><span style="color: rgb(171, 25, 66);"><strong><span style="color: rgb(171, 25, 66);font-size: 15px;text-align: left;">5.不要以 root 用户运行容器</span></strong></span></p> <p data-track="179" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="179" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">创建 Docker 容器时,你需要应用最小特权原则,防止由于某种原因攻击者能够入侵你的应用程序,则你不希望他们能够访问所有内容。</span></p> <p data-track="179" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="180" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">拥有多层安全性,可以帮助你减少系统威胁。因此,必须确保你不以 root 用户身份运行应用程序。</span></p> <p data-track="180" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="181" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">但默认情况下,创建 Docker 容器时,你将以 root 身份运行它。尽管这对于开发很方便,但是你不希望在生产镜像中使用它。假设由于某种原因,攻击者可以访问终端或可以执行代码。在那种情况下,它对正在运行的容器具有显著的特权,并且访问主机文件系统。</span></p> <p data-track="181" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="182" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">解决方案非常简单。创建一个有限特权的特定用户来运行你的应用程序,并确保该用户可以运行该应用程序。最后,在运行应用程序之前,不要忘记使用新创建的用户。</span></p> <p data-track="183" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">让我们相应地更新我们的 Dockerfile。</span></p> <p data-track="183" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="properties"><code><span class="code-snippet_outer"><span class="code-snippet__attr">FROM</span> <span class="code-snippet__string">maven:3.6.3-jdk-11-slim@sha256:68ce1cd457891f48d1e137c7d6a4493f60843e84c9e2634e3df1d3d5b381d36c AS build</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN&nbsp;mkdir&nbsp;/project</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">COPY&nbsp;.&nbsp;/project</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">WORKDIR&nbsp;/project</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN&nbsp;mvn&nbsp;clean&nbsp;package&nbsp;-DskipTests</span></span></code></pre> </section> <p data-track="183" style="text-align: left;margin-bottom: 0px;"><br></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="properties"><code><span class="code-snippet_outer"><span class="code-snippet__attr">FROM</span> <span class="code-snippet__string">adoptopenjdk/openjdk11:jre-11.0.9.1_1-alpine@sha256:b6ab039066382d39cfc843914ef1fc624aa60e2a16ede433509ccadd6d995b1f</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN&nbsp;mkdir&nbsp;/app</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN&nbsp;addgroup&nbsp;--system&nbsp;javauser&nbsp;&amp;&amp;&nbsp;adduser&nbsp;-S&nbsp;-s&nbsp;/bin/false&nbsp;-G&nbsp;javauser&nbsp;javauser</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__meta">COPY&nbsp;--from</span>=<span class="code-snippet__string">build&nbsp;/project/target/java-application.jar&nbsp;/app/java-application.jar</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">WORKDIR&nbsp;/app</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__meta">RUN&nbsp;chown&nbsp;-R&nbsp;javauser</span>:<span class="code-snippet__string">javauser&nbsp;/app</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">USER&nbsp;javauser</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">CMD&nbsp;"java"&nbsp;"-jar"&nbsp;"java-application.jar"</span></span></code></pre> </section> <p data-track="183" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="204" style="margin-bottom: 0px;"><span style="color: rgb(171, 25, 66);"><strong><span style="color: rgb(171, 25, 66);font-size: 15px;text-align: left;">6. Java 应用程序不要使用 PID 为 1 的进程</span></strong></span></p> <p data-track="204" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="215" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">在许多示例中,我看到了使用构建环境来启动容器化 Java 应用程序的常见错误。</span></p> <p data-track="216" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">上面,我们了解了要在&nbsp; Java 容器中使用 Maven 或 Gradle 的重要性,但是使用如下命令,会有不同的效果:</span></p> <p data-track="216" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <ul class="list-paddingleft-1" style="text-align: left;margin-bottom: 0px;"> <li style="text-align: left;margin-bottom: 0px;font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">CMD “mvn” “exec:java”</span></p></li> </ul> <ul class="list-paddingleft-1" style="text-align: left;margin-bottom: 0px;"> <li style="text-align: left;margin-bottom: 0px;font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">CMD [“mvn”, “spring-boot run”]</span></p></li> </ul> <ul class="list-paddingleft-1" style="text-align: left;margin-bottom: 0px;"> <li style="text-align: left;margin-bottom: 0px;font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">CMD “gradle” “bootRun”</span></p></li> </ul> <ul class="list-paddingleft-1" style="text-align: left;margin-bottom: 0px;"> <li style="text-align: left;margin-bottom: 0px;font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">CMD “run-app.sh”</span></p></li> </ul> <p data-track="221" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="221" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">在 Docker 中运行应用程序时,第一个应用程序将以进程 ID 为 1(PID=1)运行。Linux内核会以特殊方式处理 PID 为 1 的进程。通常,进程号为 1 的 PID 上的过程是初始化过程。如果我们使用 Maven 运行 Java 应用程序,那么如何确定 Maven 将类似 SIGTERM 信号转发给 Java 进程呢?</span></p> <p data-track="221" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="222" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">如果像下面的示例,那样运行 Docker 容器,则Java应用程序将具有 PID 为 1 的进程。</span></p> <p data-track="222" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="css"><code><span class="code-snippet_outer"><span class="code-snippet__selector-tag">CMD</span>&nbsp;"<span class="code-snippet__selector-tag">java</span>"&nbsp;"<span class="code-snippet__selector-tag">-jar</span>"&nbsp;"<span class="code-snippet__selector-tag">application</span><span class="code-snippet__selector-class">.jar</span>"</span></code></pre> </section> <p data-track="222" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="224" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">请注意,docker kill 和 docker stop 命令仅向 PID 为 1 的容器进程发送信号。例如,如果你正在运行 Java 应用的 shell 脚本,/bin/sh 不会将信号转发给子进程。</span></p> <p data-track="224" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="232" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">更为重要的是,在 Linux 中,PID 为 1 的容器进程还有一些其他职责。因此,在某些情况下,你不希望应用程序成为 PID 为 1 的进程,因为你不知道如何处理这些问题。一个很好的解决方案是使用 dumb-init。</span></p> <p data-track="232" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer">RUN&nbsp;apk&nbsp;<span class="code-snippet__keyword">add</span>&nbsp;dumb-init</span></code><code><span class="code-snippet_outer">CMD <span class="code-snippet__string">"dumb-init"</span> <span class="code-snippet__string">"java"</span> <span class="code-snippet__string">"-jar"</span> <span class="code-snippet__string">"java-application.jar"</span></span></code></pre> </section> <p data-track="232" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="243" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">当你像这样运行 Docker 容器时,dumb-init 会占用 PID 为1的容器进程并承担所有责任。你的 Java 流程不再需要考虑这一点。</span></p> <p data-track="243" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="244" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">我们更新后的 Dockerfile 现在看起来像这样:</span></p> <p data-track="244" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="properties"><code><span class="code-snippet_outer"><span class="code-snippet__attr">FROM</span> <span class="code-snippet__string">maven:3.6.3-jdk-11-slim@sha256:68ce1cd457891f48d1e137c7d6a4493f60843e84c9e2634e3df1d3d5b381d36c AS build</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN&nbsp;mkdir&nbsp;/project</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">COPY&nbsp;.&nbsp;/project</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">WORKDIR&nbsp;/project</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN&nbsp;mvn&nbsp;clean&nbsp;package&nbsp;-DskipTests</span></span></code></pre> </section> <p data-track="305" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="properties"><code><span class="code-snippet_outer"><span class="code-snippet__attr">FROM</span> <span class="code-snippet__string">adoptopenjdk/openjdk11:jre-11.0.9.1_1-alpine@sha256:b6ab039066382d39cfc843914ef1fc624aa60e2a16ede433509ccadd6d995b1f</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN&nbsp;apk&nbsp;add&nbsp;dumb-init</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN&nbsp;mkdir&nbsp;/app</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">RUN&nbsp;addgroup&nbsp;--system&nbsp;javauser&nbsp;&amp;&amp;&nbsp;adduser&nbsp;-S&nbsp;-s&nbsp;/bin/false&nbsp;-G&nbsp;javauser&nbsp;javauser</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__meta">COPY&nbsp;--from</span>=<span class="code-snippet__string">build&nbsp;/project/target/java-code-workshop-0.0.1-SNAPSHOT.jar&nbsp;/app/java-application.jar</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">WORKDIR&nbsp;/app</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__meta">RUN&nbsp;chown&nbsp;-R&nbsp;javauser</span>:<span class="code-snippet__string">javauser&nbsp;/app</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">USER&nbsp;javauser</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">CMD&nbsp;"dumb-init"&nbsp;"java"&nbsp;"-jar"&nbsp;"java-application.jar"</span></span></code></pre> </section> <p data-track="305" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="305" style="margin-bottom: 0px;"><span style="color: rgb(171, 25, 66);"><strong><span style="color: rgb(171, 25, 66);font-size: 15px;text-align: left;">7. 优雅下线 Java 应用程序</span></strong></span></p> <p data-track="289" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="289" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">当你的应用程序收到关闭信号时,理想情况下,我们希望所有内容都能正常关闭。根据你开发应用程序的方式,中断信号(SIGINT)或 CTRL + C 可能导致立即终止进程。</span></p> <p data-track="290" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">这可能不是你想要的东西,因为诸如此类的事情可能会导致意外行为,甚至导致数据丢失。</span></p> <p data-track="290" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="291" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">当你将应用程序作为 Payara 或 Apache Tomcat 之类的 Web 服务器的一部分运行时,该 Web 服务器很可能会正常关闭。对于某些支持可运行应用程序的框架也是如此。例如,Spring Boot 具有嵌入式 Tomcat 版本,可以有效地处理关机问题。</span></p> <p data-track="291" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="292" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">当你创建一个独立的 Java 应用程序或手动创建一个可运行的 JAR 时,你必须自己处理这些中断信号。</span></p> <p data-track="292" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="293" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">解决方案非常简单。添加一个退出钩子(hook),如下面的示例所示。收到类似 SIGINT 信号后,优雅下线应用程序的进程将会被启动。</span></p> <p data-track="293" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer">Runtime.getRuntime().addShutdownHook(<span class="code-snippet__keyword">new</span> Thread() {</span></code><code><span class="code-snippet_outer"> @<span class="code-snippet__function">Override</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet_outer"> <span class="code-snippet__keyword">public</span> <span class="code-snippet__keyword">void</span> <span class="code-snippet__title">run</span>(<span class="code-snippet__params"></span>)</span> {</span></code><code><span class="code-snippet_outer"> System.<span class="code-snippet__keyword">out</span>.println(<span class="code-snippet__string">"Inside Add Shutdown Hook"</span>);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">});</span></code></pre> </section> <pre style="text-align: left;margin-bottom: 0px;"></pre> <p data-track="300" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="300" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">诚然,与 Dockerfile 相关的问题相比,这是一个通用的 Web 应用程序问题,但在容器环境中更重要。</span></p> <p data-track="300" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="301" style="margin-bottom: 0px;"><span style="color: rgb(171, 25, 66);"><strong><span style="color: rgb(171, 25, 66);font-size: 15px;text-align: left;">8. 使用 .dockerignore 文件</span></strong></span></p> <p data-track="306" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="306" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">为了防止不必要的文件污染 git 存储库,你可以使用 .gitignore 文件。</span></p> <p data-track="306" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="307" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">对于 Docker 镜像,我们有类似的东西—— .dockerignore 文件。类似于 git 的忽略文件,它是为了防止 Docker 镜像中出现不需要的文件或目录。同时,我们也不希望敏感信息泄漏到我们的 Docker 镜像中。</span></p> <p data-track="307" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="308" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">请参阅以下示例的 .dockerignore:</span></p> <p data-track="308" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="css"><code><span class="code-snippet_outer"><span class="code-snippet__selector-class">.dockerignore</span></span></code><code><span class="code-snippet_outer">**<span class="code-snippet__comment">/*.logDockerfile</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">.git</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">.gitignore</span></span></code></pre> </section> <pre style="text-align: left;margin-bottom: 0px;"></pre> <p data-track="314" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="314" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">使用 .dockerignore 文件的要点是:</span></p> <p data-track="314" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <ul class="list-paddingleft-1" style="text-align: left;margin-bottom: 0px;"> <li style="text-align: left;margin-bottom: 0px;font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">跳过仅用于测试目的的依赖项;</span></p></li> <li style="text-align: left;margin-bottom: 0px;font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">使你免于泄露密钥或凭据信息进入 Java Docker 镜像的文件;</span></p></li> <li style="text-align: left;margin-bottom: 0px;font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">另外,日志文件也可能包含你不想公开的敏感信息;</span></p></li> <li style="text-align: left;margin-bottom: 0px;font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">保持 Docker 镜像的美观和整洁,本质上是使镜像变小。除此之外,它还有助于防止意外行为。</span></p></li> </ul> <p data-track="319" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="319" style="margin-bottom: 0px;"><span style="color: rgb(171, 25, 66);"><strong><span style="color: rgb(171, 25, 66);font-size: 15px;text-align: left;">9. 确保 Java 版本支持容器</span></strong></span></p> <p data-track="322" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="322" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">Java 虚拟机(JVM)是一件了不起的事情。它会根据其运行的系统进行自我调整。有基于行为的调整,可以动态优化堆的大小。但是,在 Java 8 和 Java 9 等较旧的版本中,JVM 无法识别容器设置的CPU限制或内存限制。这些较旧的 Java 版本的 JVM 看到了主机系统上的全部内存和所有 CPU 容量。Docker 设置的限制将被忽略。</span></p> <p data-track="322" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="323" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">随着 Java 10 的发布,JVM 现在可以感知容器,并且可以识别容器设置的约束。该功能 UseContainerSupport 是 JVM 标志,默认情况下设置为活动状态。Java 10 中发布的容器感知功能也已移植到 Java-8u191。</span></p> <p data-track="323" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="324" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">对于 Java 8 之前的版本,你可以手动尝试使用该 -Xmx 标志来限制堆大小,但这是一个痛苦的练习。紧接着,堆大小不等于 Java 使用的内存。对于 Java-8u131 和 Java 9,容器感知功能是实验性的,你必须主动激活。</span></p> <p data-track="324" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="diff"><code><span class="code-snippet_outer"><span class="code-snippet__deletion">-XX:+ UnlockExperimentalVMOptions -XX:+ UseCGroupMemoryLimitForHeap</span></span></code></pre> </section> <p data-track="324" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"></span></p> <p data-track="326" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">最好的选择是将 Java 更新到 10 以上的版本,以便默认情况下支持容器。不幸的是,许多公司仍然严重依赖 Java 8。这意味着你应该在 Docker 镜像中更新到 Java 的最新版本,或者确保至少使用 Java 8 update 191 或更高版本。</span></p> <p data-track="326" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="357" style="margin-bottom: 0px;"><span style="color: rgb(171, 25, 66);"><strong><span style="color: rgb(171, 25, 66);font-size: 15px;text-align: left;">10. 谨慎使用容器自动化生成工具</span></strong></span></p> <p data-track="331" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="331" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">你可能会偶然发现适用于构建系统的出色工具和插件。除了这些插件,还有一些很棒的工具可以帮助你创建 Java 容器,甚至可以根据需要自动发布应用。</span></p> <p data-track="331" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="332" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">从开发人员的角度来看,这看起来很棒,因为你不必在创建实际应用程序时,还要花费精力维护 Dockerfile。</span></p> <p data-track="332" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="333" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">这样的插件的一个例子是 JIB。如下所示,我只需要调用 mvn jib:dockerBuild 命令可以构建镜像:</span></p> <p data-track="333" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="xml"><code><span class="code-snippet_outer"><span class="code-snippet__tag">&lt;<span class="code-snippet__name">plugin</span>&gt;</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag">&lt;<span class="code-snippet__name">groupId</span>&gt;</span>com.google.cloud.tools<span class="code-snippet__tag">&lt;/<span class="code-snippet__name">groupId</span>&gt;</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag">&lt;<span class="code-snippet__name">artifactId</span>&gt;</span>jib-maven-plugin<span class="code-snippet__tag">&lt;/<span class="code-snippet__name">artifactId</span>&gt;</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag">&lt;<span class="code-snippet__name">version</span>&gt;</span>2.7.1<span class="code-snippet__tag">&lt;/<span class="code-snippet__name">version</span>&gt;</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag">&lt;<span class="code-snippet__name">configuration</span>&gt;</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag">&lt;<span class="code-snippet__name">to</span>&gt;</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag">&lt;<span class="code-snippet__name">image</span>&gt;</span>myimage<span class="code-snippet__tag">&lt;/<span class="code-snippet__name">image</span>&gt;</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag">&lt;/<span class="code-snippet__name">to</span>&gt;</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag">&lt;/<span class="code-snippet__name">configuration</span>&gt;</span><span class="code-snippet__tag">&lt;/<span class="code-snippet__name">plugin</span>&gt;</span></span></code></pre> </section> <pre style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"></span></pre> <p data-track="344" style="text-align: left;margin-bottom: 0px;"><br></p> <p data-track="344" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">它将为我构建一个具有指定名称的 Docker 镜像,而没有任何麻烦。</span></p> <p data-track="345" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">使用 2.3 及更高版本时,可以通过调用 mvn 命令进行操作:</span></p> <p data-track="345" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="nginx"><code><span class="code-snippet_outer"><span class="code-snippet__attribute">mvn</span> spring-boot:build-image</span></code></pre> </section> <p data-track="345" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"></span></p> <p data-track="347" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">在这种情况下,系统都会自动为我创建一个 Java 镜像。这些镜像还比较小,那是因为他们正在使用非发行版镜像或 buildpack 作为镜像的基础。但是,无论镜像大小如何,你如何知道这些容器是安全的?你需要进行更深入的调查,即使这样,你也不确定将来是否会保持这种状态。</span></p> <p data-track="347" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p data-track="351" style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">我并不是说你在创建 Java Docker 时不应使用这些工具。但是,如果你打算发布这些镜像,则应研究 Java 镜像所有方面的安全。镜像扫描将是一个好的开始。从安全性的角度来看,我的观点是,以完全控制和正确的方式创建 Dockerfile,是创建镜像更好,更安全的方式。</span></p> <p style="text-align: left;margin-bottom: 0px;"><br></p> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="53" data-source-title=""> <section class="js_blockquote_digest"> <section> <p style="color: rgb(154, 154, 154);font-size: 15px;white-space: normal;"><span style="font-size: 14px;">转自:程序员朱朱,</span></p> <p style="color: rgb(154, 154, 154);font-size: 15px;white-space: normal;">outiao.com/article/6959742944421200387/</p> </section> </section> </blockquote>