文章列表

一口气说出 4 种 API 版本控制方案!

作者:微信小助手

<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 style="text-align: left;"><strong style="color: rgb(255, 41, 65);letter-spacing: 0.8px;text-align: left;word-spacing: 0.8px;">大家好,我是不才陈某~</strong><br></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在实际项目开发中我们经常需要对接口进行版本管理。那今天我们就来聊聊为什么需要版本控制,以及如何对REST API进行版本控制。我们将讨论4种版本控制的方法,并比较不同的方法。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">通过此文您将学到</span></p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 为什么我们需要对RESTful API 进行版本控制? </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);"> 如何实现基于 Restful 的版本控制? </section></li> </ul> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">为什么我们需要对RESTful API进行版本化</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">最好的版本控制方法是不进行版本控制。只要不需要版本控制,就不要版本控制。</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">构建向后兼容的服务,以便尽可能避免版本控制!</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">然而,在许多情况下我们都需要进行版本控制,然我们看看下面具体的例子:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">最初,你有个这个版本的Student服务,返回数据如下:</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/hNWCQ9bibbzEGR101C4cWRWpQnrSk0Po0x5onwkQOzibpw0icBRxS1alMPvEACCc9sXpJoGhJvs3p1icU02RKMSmN1My4TEPLKrV/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">{<br>&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"name"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"Bob&nbsp;Charlie"</span><br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">后来,您希望将学生的名字拆分,因此创建了这个版本的服务。</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/hNWCQ9bibbzEGR101C4cWRWpQnrSk0Po0x5onwkQOzibpw0icBRxS1alMPvEACCc9sXpJoGhJvs3p1icU02RKMSmN1My4TEPLKrV/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">{<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"name"</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"firstName"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"Bob"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"lastName"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"Charlie"</span><br>&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">您可以从同一个服务支持这两个请求,但是随着每个版本的需求多样化,它会变得越来越复杂。</p> <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;">接下来让我们创建一个简单的SpringBoot的maven项目,并理解对 RESTful 服务进行版本控制的4种不同方法。</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/hNWCQ9bibbzEGR101C4cWRWpQnrSk0Po0x5onwkQOzibpw0icBRxS1alMPvEACCc9sXpJoGhJvs3p1icU02RKMSmN1My4TEPLKrV/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">dependencies</span>&gt;</span><br>&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br>&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span>org.springframework.boot<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span><br>&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span>spring-boot-starter<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span><br>&nbsp;<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br><br>&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br>&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span>org.springframework.boot<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span><br>&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span>spring-boot-starter-web<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span><br>&nbsp;<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br><br>&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br>&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span>org.projectlombok<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span><br>&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span>lombok<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span><br>&nbsp;<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br><br>&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br>&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span>org.springframework.boot<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span><br>&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span>spring-boot-starter-test<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span><br>&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">scope</span>&gt;</span>test<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">scope</span>&gt;</span><br>&nbsp;<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br><span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">dependencies</span>&gt;</span><br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">几个用于实现版本控制的Bean</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;">第一个版本的 Bean</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/hNWCQ9bibbzEGR101C4cWRWpQnrSk0Po0x5onwkQOzibpw0icBRxS1alMPvEACCc9sXpJoGhJvs3p1icU02RKMSmN1My4TEPLKrV/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;">@Data</span><br><span style="color: #4078f2;line-height: 26px;">@AllArgsConstructor</span><br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">StudentV1</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;String&nbsp;name;<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">第二个版本的 Bean</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/hNWCQ9bibbzEGR101C4cWRWpQnrSk0Po0x5onwkQOzibpw0icBRxS1alMPvEACCc9sXpJoGhJvs3p1icU02RKMSmN1My4TEPLKrV/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;">@Data</span><br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">StudentV2</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;Name&nbsp;name;<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">StudentV2使用的Name实体</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/hNWCQ9bibbzEGR101C4cWRWpQnrSk0Po0x5onwkQOzibpw0icBRxS1alMPvEACCc9sXpJoGhJvs3p1icU02RKMSmN1My4TEPLKrV/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;">@Data</span><br><span style="color: #4078f2;line-height: 26px;">@AllArgsConstructor</span><br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">Name</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;String&nbsp;firstName;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;String&nbsp;lastName;<br>}<br></code></pre> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">Restful 版本控制的方法</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;">我们希望创建两个版本的服务,一个返回 StudentV1,另一个返回 StudentV2。</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种不同方法。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="font-size: 16px;color: rgb(255, 41, 65);">通过 URI 进行版本控制</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/hNWCQ9bibbzEGR101C4cWRWpQnrSk0Po0x5onwkQOzibpw0icBRxS1alMPvEACCc9sXpJoGhJvs3p1icU02RKMSmN1My4TEPLKrV/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #4078f2;line-height: 26px;">@RestController</span><br><span style="color: #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;">StudentUriController</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@GetMapping</span>(<span style="color: #50a14f;line-height: 26px;">"v1/student"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;StudentV1&nbsp;<span style="color: #4078f2;line-height: 26px;">studentV1</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;StudentV1(<span style="color: #50a14f;line-height: 26px;">"javadaily"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@GetMapping</span>(<span style="color: #50a14f;line-height: 26px;">"v2/student"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;StudentV2&nbsp;<span style="color: #4078f2;line-height: 26px;">studentV2</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;StudentV2(<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;Name(<span style="color: #50a14f;line-height: 26px;">"javadaily"</span>,&nbsp;<span style="color: #50a14f;line-height: 26px;">"JAVA日知录"</span>));<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">请求:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/v1/student</code></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">响应:{"name":"javadaily"}</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">请求:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/v2/student</code></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">响应:{"name":{"firstName":"javadaily","lastName":"JAVA日知录"}}</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="font-size: 16px;color: rgb(255, 41, 65);">通过请求参数进行版本控制</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> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/student/param?version=1</code> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/student/param?version=2</code> </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">实现方式如下:</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/hNWCQ9bibbzEGR101C4cWRWpQnrSk0Po0x5onwkQOzibpw0icBRxS1alMPvEACCc9sXpJoGhJvs3p1icU02RKMSmN1My4TEPLKrV/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #4078f2;line-height: 26px;">@RestController</span><br><span style="color: #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;">StudentParmController</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@GetMapping</span>(value=<span style="color: #50a14f;line-height: 26px;">"/student/param"</span>,params&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"version=1"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;StudentV1&nbsp;<span style="color: #4078f2;line-height: 26px;">studentV1</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;StudentV1(<span style="color: #50a14f;line-height: 26px;">"javadaily"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@GetMapping</span>(value=<span style="color: #50a14f;line-height: 26px;">"/student/param"</span>,params&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"version=2"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;StudentV2&nbsp;<span style="color: #4078f2;line-height: 26px;">studentV2</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;StudentV2(<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;Name(<span style="color: #50a14f;line-height: 26px;">"javadaily"</span>,&nbsp;<span style="color: #50a14f;line-height: 26px;">"JAVA日知录"</span>));<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">请求:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/student/param?version=1</code></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">响应:{"name":"javadaily"}</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">请求:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/student/param?version=2</code></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">响应:{"name":{"firstName":"javadaily","lastName":"JAVA日知录"}}</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="font-size: 16px;color: rgb(255, 41, 65);">通过自定义Header进行版本控制</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> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/student/header</code></p> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);list-style-type: square;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> headers=[X-API-VERSION=1] </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/student/header</code></p> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);list-style-type: square;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> headers=[X-API-VERSION=2] </section></li> </ul> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">实现方式如下所示:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hNWCQ9bibbzEGR101C4cWRWpQnrSk0Po0x5onwkQOzibpw0icBRxS1alMPvEACCc9sXpJoGhJvs3p1icU02RKMSmN1My4TEPLKrV/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #4078f2;line-height: 26px;">@RestController</span><br><span style="color: #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;">StudentHeaderController</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@GetMapping</span>(value=<span style="color: #50a14f;line-height: 26px;">"/student/header"</span>,headers&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"X-API-VERSION=1"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;StudentV1&nbsp;<span style="color: #4078f2;line-height: 26px;">studentV1</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;StudentV1(<span style="color: #50a14f;line-height: 26px;">"javadaily"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@GetMapping</span>(value=<span style="color: #50a14f;line-height: 26px;">"/student/header"</span>,headers&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"X-API-VERSION=2"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;StudentV2&nbsp;<span style="color: #4078f2;line-height: 26px;">studentV2</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;StudentV2(<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;Name(<span style="color: #50a14f;line-height: 26px;">"javadaily"</span>,&nbsp;<span style="color: #50a14f;line-height: 26px;">"JAVA日知录"</span>));<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">下图展示了我们如何使用Postman执行带有请求头的Get请求方法。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">请求:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/student/header</code> <br>header:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">X-API-VERSION = 1</code></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.3175" src="/upload/af6482154eaa9ee4d395f45158fa9980.png" data-type="png" data-w="800" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> <br> </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">请求:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/student/header</code> <br>header:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">X-API-VERSION = 2</code></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.36625" src="/upload/158a842ee473c0908c924d3fb8cd1fa8.png" data-type="png" data-w="800" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> <br> </figcaption> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="font-size: 16px;color: rgb(255, 41, 65);">通过媒体类型进行版本控制</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;">最后一种版本控制方法是在请求中使用Accept Header,请求示例如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/student/produce</code></p> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);list-style-type: square;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">headers=[Accept=application/api-v1+json]</code> </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/student/produce</code></p> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);list-style-type: square;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">headers=[Accept=application/api-v2+json]</code> </section></li> </ul> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">实现方式如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/hNWCQ9bibbzEGR101C4cWRWpQnrSk0Po0x5onwkQOzibpw0icBRxS1alMPvEACCc9sXpJoGhJvs3p1icU02RKMSmN1My4TEPLKrV/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #4078f2;line-height: 26px;">@RestController</span><br><span style="color: #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;">StudentProduceController</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@GetMapping</span>(value=<span style="color: #50a14f;line-height: 26px;">"/student/produce"</span>,produces&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"application/api-v1+json"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;StudentV1&nbsp;<span style="color: #4078f2;line-height: 26px;">studentV1</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;StudentV1(<span style="color: #50a14f;line-height: 26px;">"javadaily"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@GetMapping</span>(value=<span style="color: #50a14f;line-height: 26px;">"/student/produce"</span>,produces&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"application/api-v2+json"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;StudentV2&nbsp;<span style="color: #4078f2;line-height: 26px;">studentV2</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;StudentV2(<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;Name(<span style="color: #50a14f;line-height: 26px;">"javadaily"</span>,&nbsp;<span style="color: #50a14f;line-height: 26px;">"JAVA日知录"</span>));<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">下图展示了我们如何使用Postman执行带有请求Accept的Get方法。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">请求:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/student/produce</code> <br>header:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">Accept = application/api-v1+json</code></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.335" src="/upload/ff5903f914387d88c647a2c94344f372.png" data-type="png" data-w="800" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> <br> </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">请求:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">http://localhost:8080/student/produce</code> <br>header:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">Accept = application/api-v2+json</code></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.355" src="/upload/2956973e529b0257a3dfdc361872da4c.png" data-type="png" data-w="800" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;"> <br> </figcaption> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">影响版本选择的因素</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">以下因素影响版本控制的选择</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> URI 污染 - URL版本和请求参数版本控制会污染URI空间。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 滥用请求头 - Accept 请求头并不是为版本控制而设计的。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 缓存 - 如果你使用基于头的版本控制,我们不能仅仅基于URL缓存,你需要考虑特定的请求头。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 是否能在浏览器直接执行 ? - 如果您有非技术消费者,那么基于URL的版本将更容易使用,因为它们可以直接在浏览器上执行。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> API文档 - 如何让文档生成理解两个不同的url是同一服务的版本? </section></li> </ul> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;"><span style="font-weight: 700;color: rgb(248, 57, 41);">事实上,并没有完美的版本控制解决方案,你需要根据项目实际情况进行选择。</span></p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">下面列表展示了主要API提供商使用的不同版本控制方法:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">媒体类型的版本控制</p> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);list-style-type: square;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> Github </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">自定义Header</p> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);list-style-type: square;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> Microsoft </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">URI路径</p> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);list-style-type: square;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> Twitter,百度,知乎 </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">请求参数控制</p> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);list-style-type: square;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> Amazon </section></li> </ul> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">好了,今天的文章就到这里了,希望能对你有所帮助。</p> </section> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" 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"></mpprofile> </section> <section> <br> </section> <p style="text-align: right;"><span style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-align: right;color: rgb(53, 53, 53);font-size: 16px;word-spacing: 0.8px;background-color: rgb(255, 255, 255);">求点赞、在看、分享三连</span><img class="rich_pages wxw-img" data-fileid="100011967" data-ratio="1" data-type="png" data-w="20" src="/upload/1d550a991385b842a21e2b301725407e.png" style="outline: 0px;vertical-align: text-bottom;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-align: right;white-space: normal;color: rgb(53, 53, 53);font-size: 16px;word-spacing: 0.8px;background-color: rgb(255, 255, 255);display: inline-block;box-sizing: border-box !important;visibility: visible !important;width: 20px !important;"></p>

12306抢票算法被曝光了!!居然这么简单!

作者:微信小助手

<blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="11" data-source-title="" data-mpa-powered-by="yiban.io"> <section class="js_blockquote_digest"> <section style="text-align: left;"> <span style="color: rgb(0, 82, 255);"><strong><span style="color: rgb(0, 82, 255);font-size: 14px;">来自公众号:<span style="color: rgb(0, 82, 255);font-size: 14px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-align: start;">程序员小饭</span></span></strong></span> </section> </section> </blockquote> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;padding-right: 10px;padding-left: 10px;word-break: break-word;text-align: left;line-height: 1.25;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangTC-Light, PingFangSC-light, PingFangTC-light;letter-spacing: 2px;background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%), linear-gradient(360deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%);background-size: 20px 20px;background-position: center center;"> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 25px;"><span style="display: inline-block;color: rgb(64, 184, 250);">导读</span><span style="display: inline-block;color: rgb(64, 184, 250);"></span></h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">相信大家应该都有抢火车票的经验,每年年底,这都是一场盛宴。然而你有没有想过抢火车票这个算法是怎么实现的呢?应该没有吧,咱们今天就来一一探讨。其实并没有你想的那么难</p> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 25px;"><span style="color: rgb(64, 184, 250);display: none;"></span><span style="display: inline-block;color: rgb(64, 184, 250);">bitmap与位运算</span><span style="display: inline-block;color: rgb(64, 184, 250);"></span></h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">redis的bitmap基本使用咱们之前已经介绍过了,如果不是很熟悉的朋友可以看看这里</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzA4MjcyNzIzNQ==&amp;mid=2451871578&amp;idx=1&amp;sn=fa26e25430b0978f86b3b03408a699d3&amp;chksm=88516f76bf26e660117c4e05896479259b5daeb78c9050c851a8c5239e61b8c854736e56364c&amp;scene=21#wechat_redirect" data-itemshowtype="0" tab="innerlink" data-linktype="2">redis中setbit(位操作)的实际应用</a></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">今天在这里咱们主要是先回顾一下位运算<img class="rich_pages wxw-img" data-ratio="0.3940972222222222" src="/upload/274a9d9f8a6277643ad3d8e84855b943.png" data-type="png" data-w="1152" style="border-radius: 6px;display: block;margin: 20px auto;object-fit: contain;box-shadow: rgb(153, 153, 153) 2px 4px 7px;"></p> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 25px;"><span style="color: rgb(64, 184, 250);display: none;"></span><span style="display: inline-block;color: rgb(64, 184, 250);">12306抢票算法详解</span><span style="display: inline-block;color: rgb(64, 184, 250);"></span></h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;"><img class="rich_pages wxw-img" data-ratio="0.7598736176935229" src="/upload/b879cf2aba6cb725973baae503a6063a.png" data-type="png" data-w="633" style="border-radius: 6px;display: block;margin: 20px auto;object-fit: contain;box-shadow: rgb(153, 153, 153) 2px 4px 7px;">我们以北京到西安这趟高铁为例,比如我的路线就是从北京到西安,车上如果只剩最后一张票了,那么如果有其他人,在北京到西安这条路线之间买任何一站,那么我都是买不了票的,换句话说,对于单个座位来说,必须是起点到目的地之间的所有站,都没有人买的话,那么才能被算是有票状态。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">所以我们可以尝试用bitmap结合上位操作来实现这种场景,以上述北京到西安为例,我们把问题简化</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;font-size: 15px;color: #595959;list-style-type: circle;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 比如一个火车上只有4个座位 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 北京到西安,一共是4站,其实是三个区间的,分别为北京-&gt;石家庄,石家庄-&gt;郑州,郑州-&gt;西安 </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><span style="height: 16px;line-height: 16px;font-size: 16px;"><span style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/KemByjgBE3Dw77auPLbUA7fAfdIx16KIMDH66KicFicP8U1YhRhapNJLpxCGwzEBzINWSxpwQWfaHohdbQnSUtiaw/640?wx_fmt=png&quot;);display: inline-block;background-size: 100%;background-position: left bottom;background-repeat: no-repeat;width: 16px;height: 15px;line-height: 15px;margin-right: 6px;margin-bottom: -2px;"></span>首先我们给每个区间构建一个空位图(0为有票,1为无票)</span><span style="display: none;"></span></h4> <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.3378995433789954" src="/upload/b89685bcebd2431372dba20415757eba.png" data-type="png" data-w="876" style="border-radius: 6px;display: block;margin: 20px auto;object-fit: contain;box-shadow: rgb(153, 153, 153) 2px 4px 7px;"> </figure> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;"><span style="display: none;"></span><span style="height: 16px;line-height: 16px;font-size: 16px;"><span style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/KemByjgBE3Dw77auPLbUA7fAfdIx16KIMDH66KicFicP8U1YhRhapNJLpxCGwzEBzINWSxpwQWfaHohdbQnSUtiaw/640?wx_fmt=png&quot;);display: inline-block;background-size: 100%;background-position: left bottom;background-repeat: no-repeat;width: 16px;height: 15px;line-height: 15px;margin-right: 6px;margin-bottom: -2px;"></span>接下来,比如有人买了一张从北京到西安的票</span><span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">买票这个动作,比如被分配到的座位是编号为1的座位,那么我们直接把北京到西安的所有站,1号座位全部设置为1,如下图</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img data-ratio="0.32860520094562645" src="/upload/a0f7b619c8aea0bf995e9f52b1195e72.png" data-type="png" data-w="846" style="border-radius: 6px;display: block;margin: 20px auto;object-fit: contain;box-shadow: rgb(153, 153, 153) 2px 4px 7px;"> </figure> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 18px;"><span style="display: none;"></span><span style="height: 16px;line-height: 16px;font-size: 16px;"><span style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/KemByjgBE3Dw77auPLbUA7fAfdIx16KIMDH66KicFicP8U1YhRhapNJLpxCGwzEBzINWSxpwQWfaHohdbQnSUtiaw/640?wx_fmt=png&quot;);display: inline-block;background-size: 100%;background-position: left bottom;background-repeat: no-repeat;width: 16px;height: 15px;line-height: 15px;margin-right: 6px;margin-bottom: -2px;"></span>接下来又有人买了一张从石家庄到西安的票</span><span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">比如这次分配的是座位2,那么我们把石家庄到西安的所有票全部设置为1就行了,如下图<img class="rich_pages wxw-img" data-ratio="0.35079726651480636" src="/upload/834f8812d766fd26ba8eb9f24363fbe5.png" data-type="png" data-w="878" style="border-radius: 6px;display: block;margin: 20px auto;object-fit: contain;box-shadow: rgb(153, 153, 153) 2px 4px 7px;"></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><span style="height: 16px;line-height: 16px;font-size: 16px;"><span style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/KemByjgBE3Dw77auPLbUA7fAfdIx16KIMDH66KicFicP8U1YhRhapNJLpxCGwzEBzINWSxpwQWfaHohdbQnSUtiaw/640?wx_fmt=png&quot;);display: inline-block;background-size: 100%;background-position: left bottom;background-repeat: no-repeat;width: 16px;height: 15px;line-height: 15px;margin-right: 6px;margin-bottom: -2px;"></span>如何知道还剩几张票?</span><span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">其实解决这个问题很简单,我们直接把上述位图做一个或操作就可以了,因为或操作是必须全部都为0,才为0</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.39086294416243655" src="/upload/b09ba5cfc4179c2f5bb044b541f5f81a.png" data-type="png" data-w="788" style="border-radius: 6px;display: block;margin: 20px auto;object-fit: contain;box-shadow: rgb(153, 153, 153) 2px 4px 7px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">或操作结果有几个0,则说明还剩几张票。</p> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 25px;"><span style="color: rgb(64, 184, 250);display: none;"></span><span style="display: inline-block;color: rgb(64, 184, 250);">总结</span><span style="display: inline-block;color: rgb(64, 184, 250);"></span></h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">其实解决这个问题主要在于位图的构建,因为火车票对于某一个座位来说,只要起点到终点中间某一个区间被占用了(置为1),那么整个座位都是无效的这个特点,很容易想到用或操作的结果来判断买票结果,我们这里只用了4位是为了方便说明问题,实际中应该是火车上有多少座位,位图的长度就应该是多少。好了,关于抢票算法我们就介绍到这里,你有没有Get到呢?或者你有没有更好的实现方法呢?</p> <figure style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);text-align: center;"> <span style="outline: 0px;text-align: justify;">--- EOF ---</span> <br style="outline: 0px;"> </figure> <p style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);"><br style="outline: 0px;"></p> <section style="outline: 0px;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);color: rgb(53, 53, 53);text-align: start;font-family: -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: 16px;"> <section powered-by="xiumi.us" style="outline: 0px;font-size: 15px;"> <section style="outline: 0px;color: rgb(0, 0, 0);font-size: 16px;font-variant-numeric: normal;letter-spacing: 0.544px;text-align: center;"> <section style="outline: 0px;letter-spacing: 0.544px;color: rgb(89, 89, 89);font-size: 15px;text-align: left;"> <section style="outline: 0px;"> <section style="outline: 0px;"> <section style="outline: 0px;"> <section style="outline: 0px;"> <section style="outline: 0px;"> <section style="outline: 0px;"> <section style="outline: 0px;"> <section style="outline: 0px;"> <section style="outline: 0px;"> <section style="outline: 0px;"> <section style="outline: 0px;"> <section style="outline: 0px;font-variant-numeric: normal;letter-spacing: 0.544px;color: rgb(62, 62, 62);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 16px;word-spacing: 2px;widows: 1;line-height: 27.2px;"> <section style="outline: 0px;"> <section style="margin-top: 10px;margin-bottom: 10px;outline: 0px;text-align: center;"> <section style="padding-right: 1em;padding-left: 1em;outline: 0px;display: inline-block;"> <span style="padding: 0.3em 0.5em;outline: 0px;display: inline-block;border-radius: 0.5em;font-size: 14.08px;color: rgb(255, 255, 255);background-color: rgb(249, 110, 87);"> <section style="outline: 0px;"> <span style="outline: 0px;font-size: 16px;"><strong style="outline: 0px;">推荐↓↓↓</strong></span> </section></span> </section> <section style="margin-top: -1em;padding: 20px 10px 10px;outline: 0px;border-width: 1px;border-style: solid;border-color: rgb(192, 200, 209);background-color: rgb(239, 239, 239);"> <section style="outline: 0px;"> <section style="outline: 0px;"> <section style="outline: 0px;text-align: left;"> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzA5MTgwOTMzMQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/iawSlOAmgP0QN1GotokJSFQk5NtNIRDl4IqVZf4h6MOTYYUlVUtTIEg9CU85ficczjFThpGNhEia7rmrGA387EstA/0?wx_fmt=png" data-nickname="计算机工作原理" data-alias="CoderNews" data-signature="计算机组成原理、计算机系统架构、操作系统原理、编译原理等计算机原理的内容分享。" data-from="0"></mpprofile> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section>

我司Spring Boot 项目打包 + Shell 脚本部署详细总结,太有用了!

作者:微信小助手

<section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 16px;padding-right: 10px;padding-left: 10px;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;"> <p style="margin: 5px 0px;color: rgb(1, 1, 1);font-size: 16px;padding-right: 10px;padding-left: 10px;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;line-height: 1.75em;"><span style="font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 15px;">本篇和大家分享的是 Spring Boot 打包并结合 Shell 脚本命令部署,重点在分享一个shell 程序启动工具,希望能便利工作;<br><br></span></p> </section> <ul data-tool="mdnice编辑器" style="margin: 8px 0px;padding-left: 25px;" class="list-paddingleft-2"> <li style="font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 15px;"><p style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.75em;"><span style="font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 15px;">profiles指定不同环境的配置</span></p></li> <li style="font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 15px;"><p style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.75em;"><span style="font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 15px;">maven-assembly-plugin打发布压缩包</span></p></li> <li style="font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 15px;"><p style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.75em;"><span style="font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 15px;">分享shenniu_publish.sh程序启动工具</span></p></li> <li style="font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 15px;"><p style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.75em;"><span style="font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 15px;">linux上使用shenniu_publish.sh启动程序</span></p></li> </ul> <p style="padding-top: 8px;padding-bottom: 8px;color: rgb(89, 89, 89);margin-left: 0px;margin-right: 0px;line-height: 1.75em;"><br></p> <p style="padding-top: 8px;padding-bottom: 8px;color: rgb(89, 89, 89);margin-left: 0px;margin-right: 0px;line-height: 1.75em;"><span style="font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 15px;"><strong style="color: rgb(71, 193, 168);">profiles指定不同环境的配置</strong></span></p> <p style="padding-top: 8px;padding-bottom: 8px;color: rgb(89, 89, 89);margin-left: 0px;margin-right: 0px;line-height: 1.75em;"><span style="font-family: &quot;Helvetica Neue&quot;, Helvetica

分库分表的 9种分布式主键ID 生成方案,挺全乎的

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" data-mpa-powered-by="yiban.io"> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;padding-right: 10px;padding-left: 10px;line-height: 1.6;word-break: break-word;text-align: left;font-family: -apple-system, system-ui, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;"> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;"><a href="https://mp.weixin.qq.com/s?__biz=MzAxNTM4NzAyNg==&amp;mid=2247488693&amp;idx=1&amp;sn=285fc1e4f675289284ab7623a7891e98&amp;scene=21#wechat_redirect" style="color: rgb(3, 106, 202);border-bottom: 0px;" data-linktype="1"><span class="js_jump_icon h5_image_link" data-positionback="static" style="top: auto;left: auto;margin: 0px;right: auto;bottom: auto;"><img class="rich_pages wxw-img" data-ratio="0.5625" src="/upload/ac47083fecef05023ad8fa2fad9b09d2.jpg" data-type="jpeg" data-w="1920" style="margin: 0px;"></span></a></p> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-mpa-template="t" mpa-from-tpl="t"> <p style="clear: both;min-height: 1em;color: rgb(51, 51, 51);letter-spacing: 0.544px;text-align: center;font-size: 16px;"><span style="color: rgb(255, 0, 0);font-size: 14px;line-height: 1.75em;"><span style="vertical-align: inherit;"><span style="vertical-align: inherit;">点击</span></span></span><span style="color: rgb(127, 127, 127);font-size: 14px;line-height: 1.75em;"><span style="vertical-align: inherit;"><span style="vertical-align: inherit;">“&nbsp;</span></span></span><span style="font-size: 14px;line-height: 1.75em;"><span style="color: rgb(0, 176, 240);"><span style="vertical-align: inherit;"><span style="vertical-align: inherit;">程序员内点事</span></span></span></span><span style="color: rgb(127, 127, 127);font-size: 14px;line-height: 1.75em;"><span style="vertical-align: inherit;"><span style="vertical-align: inherit;">&nbsp;”关注,选择“&nbsp;</span></span><a href="http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&amp;mid=2247486188&amp;idx=3&amp;sn=f160d91ea23e5113e6077c500a2e30c4&amp;chksm=fa49755dcd3efc4bf4f566fbbbf74c191d0b79f2d3222fd211bc52d80b5ef127f52b1158ed71&amp;scene=21#wechat_redirect" target="_blank" data-linktype="2" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;"><span style="vertical-align: inherit;"><span style="vertical-align: inherit;">设置星标</span></span></a><span style="vertical-align: inherit;"><span style="vertical-align: inherit;">&nbsp;”</span></span></span></p> <p style="clear: both;min-height: 1em;letter-spacing: 0.544px;text-align: center;font-size: 16px;color: rgb(62, 62, 62);"><span style="color: rgb(127, 127, 127);font-size: 14px;"><span style="vertical-align: inherit;"><span style="vertical-align: inherit;">坚持学习,好文每日送达!</span></span></span></p> </section> </section> <p><br mpa-from-tpl="t"></p> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;"><a href="https://mp.weixin.qq.com/s?__biz=MzAxNTM4NzAyNg==&amp;mid=2247488693&amp;idx=1&amp;sn=285fc1e4f675289284ab7623a7891e98&amp;scene=21#wechat_redirect" style="color: rgb(3, 106, 202);border-bottom: 0px;" data-linktype="2">《sharding-jdbc 分库分表的 4种分片策略》</a> 中我们介绍了 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">sharding-jdbc</code> 4种分片策略的使用场景,可以满足基础的分片功能开发,这篇我们来看看分库分表后,应该如何为分片表生成全局唯一的主键 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">ID</code>。<br></p> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;margin-top: 16px;">引入任何一种技术都是存在风险的,分库分表当然也不例外,除非库、表数据量持续增加,大到一定程度,以至于现有高可用架构已无法支撑,否则不建议大家做分库分表,因为做了数据分片后,你会发现自己踏上了一段踩坑之路,而分布式主键 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">ID</code> 就是遇到的第一个坑。</p> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;margin-top: 16px;">不同数据节点间生成全局唯一主键是个棘手的问题,一张逻辑表 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">t_order</code> 拆分成多个真实表 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">t_order_n</code>,然后被分散到不同分片库 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">db_0</code>、<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">db_1</code>... ,各真实表的自增键由于无法互相感知从而会产生重复主键,此时数据库本身的自增主键,就无法满足分库分表对主键全局唯一的要求。</p> <pre data-tool="mdnice编辑器" style="letter-spacing: 0px;margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">&nbsp;db_0--<br>&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;t_order_0<br>&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;t_order_1<br>&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;t_order_2<br>&nbsp;db_1--<br>&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;t_order_0<br>&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;t_order_1<br>&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;t_order_2<br></code></pre> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;">尽管我们可以通过严格约束,各个分片表自增主键的 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">初始值</code> 和 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">步长</code> 的方式来解决 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">ID</code> 重复的问题,但这样会让运维成本陡增,而且可扩展性极差,一旦要扩容分片表数量,原表数据变动比较大,所以这种方式不太可取。</p> <pre data-tool="mdnice编辑器" style="letter-spacing: 0px;margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">&nbsp;步长&nbsp;step&nbsp;=&nbsp;分表张数<br><br>&nbsp;db_0--<br>&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;t_order_0&nbsp;&nbsp;ID:&nbsp;<span style="color: #d19a66;line-height: 26px;">0</span>、<span style="color: #d19a66;line-height: 26px;">6</span>、<span style="color: #d19a66;line-height: 26px;">12</span>、<span style="color: #d19a66;line-height: 26px;">18</span>...<br>&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;t_order_1&nbsp;&nbsp;ID:&nbsp;<span style="color: #d19a66;line-height: 26px;">1</span>、<span style="color: #d19a66;line-height: 26px;">7</span>、<span style="color: #d19a66;line-height: 26px;">13</span>、<span style="color: #d19a66;line-height: 26px;">19</span>...<br>&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;t_order_2&nbsp;&nbsp;ID:&nbsp;<span style="color: #d19a66;line-height: 26px;">2</span>、<span style="color: #d19a66;line-height: 26px;">8</span>、<span style="color: #d19a66;line-height: 26px;">14</span>、<span style="color: #d19a66;line-height: 26px;">20</span>...<br>&nbsp;db_1--<br>&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;t_order_0&nbsp;&nbsp;ID:&nbsp;<span style="color: #d19a66;line-height: 26px;">3</span>、<span style="color: #d19a66;line-height: 26px;">9</span>、<span style="color: #d19a66;line-height: 26px;">15</span>、<span style="color: #d19a66;line-height: 26px;">21</span>...<br>&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;t_order_1&nbsp;&nbsp;ID:&nbsp;<span style="color: #d19a66;line-height: 26px;">4</span>、<span style="color: #d19a66;line-height: 26px;">10</span>、<span style="color: #d19a66;line-height: 26px;">16</span>、<span style="color: #d19a66;line-height: 26px;">22</span>...<br>&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;t_order_2&nbsp;&nbsp;ID:&nbsp;<span style="color: #d19a66;line-height: 26px;">5</span>、<span style="color: #d19a66;line-height: 26px;">11</span>、<span style="color: #d19a66;line-height: 26px;">17</span>、<span style="color: #d19a66;line-height: 26px;">23</span>...<br></code></pre> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;">目前已经有了许多第三放解决方案可以完美解决这个问题,比如基于 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">UUID</code>、<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">SNOWFLAKE</code>算法 、<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">segment</code>号段,使用特定算法生成不重复键,或者直接引用主键生成服务,像美团(<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">Leaf</code>)和 滴滴(<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">TinyId</code>)等。</p> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;margin-top: 16px;">而<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">sharding-jdbc</code> 内置了两种分布式主键生成方案,<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">UUID</code>、<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">SNOWFLAKE</code>,不仅如此它还抽离出分布式主键生成器的接口,以便于开发者实现自定义的主键生成器,后续我们会在自定义的生成器中接入 滴滴(<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">TinyId</code>)的主键生成服务。</p> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;margin-top: 16px;">前边介绍过在 sharding-jdbc 中要想为某个字段自动生成主键 ID,只需要在 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">application.properties</code> 文件中做如下配置:</p> <pre data-tool="mdnice编辑器" style="letter-spacing: 0px;margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">#&nbsp;主键字段<br>spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id<br>#&nbsp;主键ID&nbsp;生成方案<br>spring.shardingsphere.sharding.tables.t_order.key-generator.type=UUID<br>#&nbsp;工作机器&nbsp;id<br>spring.shardingsphere.sharding.tables.t_order.key-generator.props.worker.id=<span style="color: #d19a66;line-height: 26px;">123</span><br></code></pre> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;"><code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">key-generator.column</code> 表示主键字段,<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">key-generator.type</code> 为主键 ID 生成方案(内置或自定义的),<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">key-generator.props.worker.id</code> 为机器ID,在主键生成方案设为 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">SNOWFLAKE</code> 时机器ID 会参与位运算。</p> <blockquote data-tool="mdnice编辑器" style="letter-spacing: 0px;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(178, 174, 197);background: rgb(255, 249, 249);"> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 16px;color: rgb(102, 102, 102);line-height: 2;">在使用 sharding-jdbc 分布式主键时需要注意两点:</p> </blockquote> <ul data-tool="mdnice编辑器" style="letter-spacing: 0px;margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 2;"> 一旦 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">insert</code> 插入操作的实体对象中主键字段已经赋值,那么即使配置了主键生成方案也会失效,最后SQL 执行的数据会以赋的值为准。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 2;"> 不要给主键字段设置自增属性,否则主键ID 会以默认的 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">SNOWFLAKE</code> 方式生成。比如:用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">mybatis plus</code> 的 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">@TableId</code> 注解给字段 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">order_id</code> 设置了自增主键,那么此时配置哪种方案,总是按雪花算法生成。 </section></li> </ul> <hr data-tool="mdnice编辑器" style="letter-spacing: 0px;height: 1px;margin-top: 10px;margin-bottom: 10px;border-right: none;border-bottom: none;border-left: none;border-top-style: solid;border-top-color: rgb(19, 92, 224);"> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;">下面我们从源码上分析下 sharding-jdbc 内置主键生成方案 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">UUID</code>、<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">SNOWFLAKE</code> 是怎么实现的。</p> <h2 data-tool="mdnice编辑器" style="letter-spacing: 0px;font-weight: bold;padding-top: 30px;padding-bottom: 30px;color: rgb(19, 92, 224);font-size: 20px;"><span style="border-left: 4px solid;padding-left: 10px;">UUID</span></h2> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;">打开 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">UUID</code> 类型的主键生成实现类 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">UUIDShardingKeyGenerator</code> 的源码发现,它的生成规则只有 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">UUID.randomUUID()</code> 这么一行代码,额~ 心中默默来了一句<span style="background-image: linear-gradient(to right, rgb(50, 153, 210), rgb(239, 189, 181));background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(255, 255, 255);padding-right: 4px;padding-left: 4px;display: inline-block;border-radius: 4px;margin-right: 2px;margin-left: 2px;letter-spacing: 1px;">卧槽</span>。</p> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;margin-top: 16px;">UUID 虽然可以做到全局唯一性,但还是不推荐使用它作为主键,因为我们的实际业务中不管是 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">user_id</code> 还是 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">order_id</code> 主键多为整型,而 UUID 生成的是个 32 位的字符串。</p> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;margin-top: 16px;">它的存储以及查询对 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">MySQL</code> 的性能消耗较大,而且 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">MySQL</code> 官方也明确建议,主键要尽量越短越好,作为数据库主键 UUID 的无序性还会导致数据位置频繁变动,严重影响性能。</p> <pre data-tool="mdnice编辑器" style="letter-spacing: 0px;margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">public&nbsp;final&nbsp;<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">class</span>&nbsp;<span style="color: #e6c07b;line-height: 26px;">UUIDShardingKeyGenerator</span>&nbsp;<span style="color: #e6c07b;line-height: 26px;">implements</span>&nbsp;<span style="color: #e6c07b;line-height: 26px;">ShardingKeyGenerator</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;Properties&nbsp;properties&nbsp;=&nbsp;<span style="color: #c678dd;line-height: 26px;">new</span>&nbsp;Properties();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;UUIDShardingKeyGenerator()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;<span style="color: #e6c07b;line-height: 26px;">String</span>&nbsp;getType()&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;">"UUID"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;synchronized&nbsp;Comparable&lt;?&gt;&nbsp;generateKey()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;UUID.randomUUID().toString().replaceAll(<span style="color: #98c379;line-height: 26px;">"-"</span>,&nbsp;<span style="color: #98c379;line-height: 26px;">""</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;Properties&nbsp;getProperties()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">this</span>.properties;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;<span style="color: #c678dd;line-height: 26px;">void</span>&nbsp;setProperties(Properties&nbsp;properties)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">this</span>.properties&nbsp;=&nbsp;properties;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br><br></code></pre> <h2 data-tool="mdnice编辑器" style="letter-spacing: 0px;font-weight: bold;padding-top: 30px;padding-bottom: 30px;color: rgb(19, 92, 224);font-size: 20px;"><span style="border-left: 4px solid;padding-left: 10px;">SNOWFLAKE</span></h2> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;"><code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">SNOWFLAKE</code>(雪花算法)是默认使用的主键生成方案,生成一个 64bit的长整型(<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">Long</code>)数据。</p> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;margin-top: 16px;"><code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">sharding-jdbc</code> 中雪花算法生成的主键主要由 4部分组成,<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">1bit</code>符号位、<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">41bit</code>时间戳位、<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">10bit</code>工作进程位以及 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">12bit</code> 序列号位。</p> <figure data-tool="mdnice编辑器" style="letter-spacing: 0px;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.41884057971014493" src="/upload/952fd4085524f97d9149ee63813ecd39.png" data-type="png" data-w="1380" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h3 data-tool="mdnice编辑器" style="letter-spacing: 0px;font-weight: bold;padding-top: 30px;padding-bottom: 30px;color: rgb(19, 92, 224);">符号位(1bit位)</h3> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;">Java 中 Long 型的最高位是符号位,正数是0,负数是1,一般生成ID都为正数,所以默认为0</p> <h3 data-tool="mdnice编辑器" style="letter-spacing: 0px;font-weight: bold;padding-top: 30px;padding-bottom: 30px;color: rgb(19, 92, 224);">时间戳位(41bit)</h3> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;">41位的时间戳可以容纳的毫秒数是 2 的 41次幂,而一年的总毫秒数为 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">1000L * 60 * 60 * 24 * 365</code>,计算使用时间大概是69年,额~,我有生之间算是够用了。</p> <pre data-tool="mdnice编辑器" style="letter-spacing: 0px;margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">Math.pow(2,&nbsp;41)&nbsp;/&nbsp;(365&nbsp;*&nbsp;24&nbsp;*&nbsp;60&nbsp;*&nbsp;60&nbsp;*&nbsp;1000L)&nbsp;=&nbsp;=&nbsp;69年&nbsp;<br></code></pre> <h3 data-tool="mdnice编辑器" style="letter-spacing: 0px;font-weight: bold;padding-top: 30px;padding-bottom: 30px;color: rgb(19, 92, 224);">工作进程位(10bit)</h3> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;">表示一个唯一的工作进程id,默认值为 0,可通过 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">key-generator.props.worker.id</code> 属性设置。</p> <pre data-tool="mdnice编辑器" style="letter-spacing: 0px;margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">spring.shardingsphere.sharding.tables.t_order.key-generator.props.worker.id=0000<br></code></pre> <h3 data-tool="mdnice编辑器" style="letter-spacing: 0px;font-weight: bold;padding-top: 30px;padding-bottom: 30px;color: rgb(19, 92, 224);">序列号位(12bit)</h3> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;">同一毫秒内生成不同的ID。</p> <h3 data-tool="mdnice编辑器" style="letter-spacing: 0px;font-weight: bold;padding-top: 30px;padding-bottom: 30px;color: rgb(19, 92, 224);">时钟回拨</h3> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;">了解了雪花算法的主键 ID 组成后不难发现,这是一种严重依赖于服务器时间的算法,而依赖服务器时间的就会遇到一个棘手的问题:<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">时钟回拨</code>。</p> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;margin-top: 16px;"><span style="background-image: linear-gradient(to right, rgb(50, 153, 210), rgb(239, 189, 181));background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(255, 255, 255);padding-right: 4px;padding-left: 4px;display: inline-block;border-radius: 4px;margin-right: 2px;margin-left: 2px;letter-spacing: 1px;">为什么会出现时钟回拨呢?</span></p> <p data-tool="mdnice编辑器" style="letter-spacing: 0px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 2;margin-top: 16px;">互联网中有一种网络时间协议 <code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;color: rgb(19, 148, 216);padding: 2px 6px;word-break: normal;">ntp</code> 全称 (<code style="font-size: 14px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consola

实战!玩转Redis必备的几款运维工具!

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">大家好,我是不才陈某~</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;">我们在应用 Redis 时,经常会面临的运维工作,包括 Redis 的运行<span style="font-weight: 700;color: rgb(248, 57, 41);">状态监控</span>,<span style="font-weight: 700;color: rgb(248, 57, 41);">数据迁移</span>,<span style="font-weight: 700;color: rgb(248, 57, 41);">主从集群</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">切片集群</span>的部署和运维。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">接下来,我就从这三个方面,给你介绍一些工具。我们先来学习下监控 Redis 实时运行状态的工具,这些工具都用到了 Redis 提供的一个监控命令:<span style="font-weight: 700;color: rgb(248, 57, 41);">INFO</span>。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">最基本的监控命令:INFO 命令</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);">Redis 本身提供的 INFO 命令会返回丰富的实例运行监控信息,这个命令是 Redis 监控工具的基础。</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">INFO 命令在使用时,可以带一个参数 section,这个参数的取值有好几种,相应的,INFO 命令也会返回不同类型的监控信息。我把 INFO 命令的返回信息分成 5 大类,其中,有的类别当中又包含了不同的监控内容,如下表所示:</p> <p style="text-align: center;padding: 0px 0.5em;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.5722222222222222" data-s="300,640" src="/upload/f9fd0f1b3646238bf0480836229c60f0.jpg" data-type="jpeg" data-w="1080" style="border-radius: 3px;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"></figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在监控 Redis 运行状态时,INFO 命令返回的结果非常有用。如果你想了解 INFO 命令的所有参数返回结果的详细含义,可以查看 Redis官网的介绍。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这里,我给你提几个运维时需要重点关注的参数以及它们的重要返回结果。</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);">stat</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">commandstat</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">cpu</span> 和 <span style="font-weight: 700;color: rgb(248, 57, 41);">memory</span> 这四个参数的返回结果,这里面包含了命令的执行情况(比如命令的执行次数和执行时间、命令使用的 CPU 资源),内存资源的使用情况(比如内存已使用量、内存碎片率),CPU 资源使用情况等,这可以帮助我们判断实例的运行状态和资源消耗情况。</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);">RDB</span> 或 <span style="font-weight: 700;color: rgb(248, 57, 41);">AOF</span> 功能时,你就需要重点关注下 <span style="font-weight: 700;color: rgb(248, 57, 41);">persistence</span> 参数的返回结果,你可以通过它查看到 <span style="font-weight: 700;color: rgb(248, 57, 41);">RDB</span> 或者 <span style="font-weight: 700;color: rgb(248, 57, 41);">AOF</span> 的执行情况。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如果你在使用主从集群,就要重点关注下 <span style="font-weight: 700;color: rgb(248, 57, 41);">replication</span> 参数的返回结果,这里面包含了主从同步的实时状态。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">不过,<span style="font-weight: 700;color: rgb(248, 57, 41);">INFO</span> 命令只是提供了文本形式的监控结果,并没有可视化,所以,在实际应用中,我们还可以使用一些第三方开源工具,将 INFO 命令的返回结果可视化。接下来,我要讲的 <span style="font-weight: 700;color: rgb(248, 57, 41);">Prometheus</span>,就可以通过插件将 Redis 的统计结果可视化。</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);">面向 Prometheus 的 Redis-exporter 监控</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);">Prometheus</span>是一套开源的系统监控报警框架。它的核心功能是从被监控系统中拉取监控数据,结合<span style="font-weight: 700;color: rgb(248, 57, 41);">Grafana</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;">而且,监控数据可以保存到时序数据库中,以便运维人员进行历史查询。同时,Prometheus 会检测系统的监控指标是否超过了预设的阈值,一旦超过阈值,Prometheus 就会触发报警。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">对于系统的日常运维管理来说,这些功能是非常重要的。而 Prometheus 已经实现了使用这些功能的工具框架。我们只要能从被监控系统中获取到监控数据,就可以用 Prometheus 来实现运维监控。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Prometheus 正好提供了插件功能来实现对一个系统的监控,我们把插件称为 <span style="font-weight: 700;color: rgb(248, 57, 41);">exporter</span>,每一个 <span style="font-weight: 700;color: rgb(248, 57, 41);">exporter</span> 实际是一个采集监控数据的组件。<span style="font-weight: 700;color: rgb(248, 57, 41);">exporter</span> 采集的数据格式符合 Prometheus 的要求,Prometheus 获取这些数据后,就可以进行展示和保存了。</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);">Redis-exporter</span>就是用来监控 Redis 的,它将 <span style="font-weight: 700;color: rgb(248, 57, 41);">INFO</span> 命令监控到的运行状态和各种统计信息提供给 Prometheus,从而进行可视化展示和报警设置。目前,<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-exporter</span> 可以支持 <span style="font-weight: 700;color: rgb(248, 57, 41);">Redis 2.0</span> 至 <span style="font-weight: 700;color: rgb(248, 57, 41);">6.0</span> 版本,适用范围比较广。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">除了获取 Redis 实例的运行状态,Redis-<span style="font-weight: 700;color: rgb(248, 57, 41);">exporter</span> 还可以监控键值对的大小和集合类型数据的元素个数,这个可以在运行 Redis-exporter 时,使用 check-keys 的命令行选项来实现。</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);">Lua</span> 脚本,定制化采集所需监控的数据。然后,我们使用 <span style="font-weight: 700;color: rgb(248, 57, 41);">scripts</span> 命令行选项,让 <span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-exporter</span> 运行这个特定的脚本,从而可以满足业务层的多样化监控需求。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">最后,我还想再给你分享两个小工具:<span style="font-weight: 700;color: rgb(248, 57, 41);">redis-stat</span>和<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis Live</span>。跟 <span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-exporter</span> 相比,这两个都是轻量级的监控工具。它们分别是用 <span style="font-weight: 700;color: rgb(248, 57, 41);">Ruby</span> 和 <span style="font-weight: 700;color: rgb(248, 57, 41);">Python</span> 开发的,也是将 INFO 命令提供的实例运行状态信息可视化展示。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">虽然这两个工具目前已经很少更新了,不过,如果你想自行开发 Redis 监控工具,它们都是不错的参考。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">除了监控 Redis 的运行状态,还有一个常见的运维任务就是<span style="font-weight: 700;color: rgb(248, 57, 41);">数据迁移</span>。接下来,我们再来学习下数据迁移的工具。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">数据迁移工具 <span style="color: rgb(248, 57, 41);">Redis-shake</span></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);">Redis-shake</span>,这是阿里云 Redis 和 MongoDB 团队开发的一个用于 Redis 数据同步的工具。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</span> 的基本运行原理,是先启动 <span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</span> 进程,这个进程模拟了一个 Redis 实例。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">然后,<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</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;">这个过程和 Redis 主从实例的全量同步是类似的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">源实例相当于主库,<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</span> 相当于从库,源实例先把 <span style="font-weight: 700;color: rgb(248, 57, 41);">RDB</span> 文件传输给 <span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</span>,<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</span> 会把 RDB 文件发送给目的实例。接着,源实例会再把增量命令发送给 <span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</span>,<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</span> 负责把这些增量命令再同步给目的实例。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">下面这张图展示了 <span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</span> 进行数据迁移的过程:</p> <p style="text-align: center;padding: 0px 0.5em;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.26481481481481484" data-s="300,640" src="/upload/1e75d12fd86e813d77dac927b5a39c1e.jpg" data-type="jpeg" data-w="1080" style="border-radius: 3px;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"></figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</span> 的一大优势,就是支持多种类型的迁移。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">首先,它既支持单个实例间的数据迁移,也支持集群到集群间的数据迁移。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">其次,有的 Redis 切片集群(例如 Codis)会使用 proxy 接收请求操作,<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</span> 也同样支持和 proxy 进行数据迁移。</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);">Redis-shake</span> 是阿里云团队开发的,所以,除了支持开源的 Redis 版本以外,<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</span> 还支持云下的 Redis 实例和云上的 Redis 实例进行迁移,可以帮助我们实现 Redis 服务上云的目标。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在数据迁移后,我们通常需要对比源实例和目的实例中的数据是否一致。如果有不一致的数据,我们需要把它们找出来,从目的实例中剔除,或者是再次迁移这些不一致的数据。</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);">Redis-full-check</span>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-full-check</span> 的工作原理很简单,就是对源实例和目的实例中的数据进行全量比对,从而完成数据校验。不过,为了降低数据校验的比对开销,<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-full-check</span> 采用了多轮比较的方法。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在第一轮校验时,<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-full-check</span> 会找出在源实例上的所有 key,然后从源实例和目的实例中把相应的值也都查找出来,进行比对。第一次比对后,<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-full-check</span> 会把目的实例中和源实例不一致的数据,记录到 sqlite 数据库中。</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);">Redis-full-check</span> 只比较上一轮结束后记录在数据库中的不一致的数据。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">为了避免对实例的正常请求处理造成影响,<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-full-check</span> 在每一轮比对结束后,会暂停一段时间。随着 <span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-shake</span> 增量同步的进行,源实例和目的实例中的不一致数据也会逐步减少,所以,我们校验比对的轮数不用很多。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">我们可以自己设置比对的轮数。具体的方法是,在运行 <span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-full-check</span> 命令时,把参数 comparetimes 的值设置为我们想要比对的轮数。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">等到所有轮数都比对完成后,数据库中记录的数据就是源实例和目的实例最终的差异结果了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这里有个地方需要注意下,<span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-full-check</span> 提供了三种比对模式,我们可以通过 <span style="font-weight: 700;color: rgb(248, 57, 41);">comparemode</span> 参数进行设置。<span style="font-weight: 700;color: rgb(248, 57, 41);">comparemode</span> 参数有三种取值,含义如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">KeyOutline</span>,只对比 key 值是否相等; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">ValueOutline</span>,只对比 value 值的长度是否相等; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">FullValue</span>,对比 key 值、value 长度、value 值是否相等。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">我们在应用 <span style="font-weight: 700;color: rgb(248, 57, 41);">Redis-full-check</span> 时,可以根据业务对数据一致性程度的要求,选择相应的比对模式。如果一致性要求高,就把 <span style="font-weight: 700;color: rgb(248, 57, 41);">comparemode</span> 参数设置为 <span style="font-weight: 700;color: rgb(248, 57, 41);">FullValue</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;">好了,最后,我再向你介绍一个用于 Redis 集群运维管理的工具 <span style="font-weight: 700;color: rgb(248, 57, 41);">CacheCloud</span>。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">集群管理工具 <span style="color: rgb(248, 57, 41);">CacheCloud</span></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);">CacheCloud</span>是搜狐开发的一个面向 Redis 运维管理的云平台,它实现了主从集群、哨兵集群和 Redis Cluster 的自动部署和管理,用户可以直接在平台的管理界面上进行操作。</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);">CacheCloud</span> 提供了 5 个运维操作。</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">下线实例</span>:关闭实例以及实例相关的监控任务。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">上线实例</span>:重新启动已下线的实例,并进行监控。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">添加从节点</span>:在主从集群中给主节点添加一个从节点。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">故障切换</span>:手动完成 Redis Cluster 主从节点的故障转移。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">配置管理</span>:用户提交配置修改的工单后,管理员进行审核,并完成配置修改。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当然,作为运维管理平台,<span style="font-weight: 700;color: rgb(248, 57, 41);">CacheCloud</span> 除了提供运维操作以外,还提供了丰富的监控信息。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">CacheCloud</span> 不仅会收集 INFO 命令提供的实例实时运行状态信息,进行可视化展示,而且还会把实例运行状态信息保存下来,例如内存使用情况、客户端连接数、键值对数据量。这样一来,当 Redis 运行发生问题时,运维人员可以查询保存的历史记录,并结合当时的运行状态信息进行分析。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如果你希望有一个统一平台,把 Redis 实例管理相关的任务集中托管起来, 是一个不错的工具。</p> </section>

实战!Spring Boot 整合 阿里开源中间件 Canal 实现数据增量同步!

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">大家好,我是不才陈某~</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">数据同步一直是一个令人头疼的问题。在业务量小,场景不多,数据量不大的情况下我们可能会选择在项目中直接写一些定时任务手动处理数据,例如从多个表将数据查出来,再汇总处理,再插入到相应的地方。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">但是随着业务量增大,数据量变多以及各种复杂场景下的分库分表的实现,使数据同步变得越来越困难。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">今天这篇文章使用<span style="font-weight: 700;color: rgb(248, 57, 41);">阿里</span>开源的中间件<span style="font-weight: 700;color: rgb(248, 57, 41);">Canal</span>解决数据增量同步的痛点。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">文章目录如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.6675925925925926" src="/upload/4f92e2d2c4e541a48d7ab612394f8f6e.png" data-type="png" data-w="1080" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <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);">Canal是什么?</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);">canal</span>译意为水道/管道/沟渠,主要用途是基于 <span style="font-weight: 700;color: rgb(248, 57, 41);">MySQL</span> 数据库<span style="font-weight: 700;color: rgb(248, 57, 41);">增量日志</span>解析,提供增量数据订阅和消费。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">从这句话理解到了什么?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">基于MySQL,并且通过MySQL日志进行的增量解析,这也就意味着对原有的业务代码完全是无侵入性的。</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;"><span style="color: rgb(255, 41, 65);"><strong>工作原理</strong></span>:解析MySQL的binlog日志,提供增量数据。</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">基于日志增量订阅和消费的业务包括</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 数据库镜像 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 数据库实时备份 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 索引构建和实时维护(拆分异构索引、倒排索引等) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 业务 cache 刷新 </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;">当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x。</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://github.com/alibaba/canal</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);">Canal数据如何传输?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">先来一张官方图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.526818515797208" src="/upload/62b3d37dbef68080060e4a47bec76d19.png" data-type="png" data-w="1361" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Canal分为服务端和客户端,这也是阿里常用的套路,比如前面讲到的注册中心<span style="font-weight: 700;color: rgb(248, 57, 41);">Nacos</span>:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">服务端</span>:负责解析MySQL的binlog日志,传递增量数据给客户端或者消息中间件 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">客户端</span>:负责解析服务端传过来的数据,然后定制自己的业务处理。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">目前为止支持的消息中间件很全面了,比如<span style="font-weight: 700;color: rgb(248, 57, 41);">Kafka</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">RocketMQ</span>,<span style="font-weight: 700;color: rgb(248, 57, 41);">RabbitMQ</span>。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">数据同步还有其他中间件吗?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">有,当然有,还有一些开源的中间件也是相当不错的,比如<span style="font-weight: 700;color: rgb(248, 57, 41);">Bifrost</span>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">常见的几款中间件的区别如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.4148148148148148" src="/upload/6c6113d1615872f832b12152eeeee3f8.png" data-type="other" data-w="1080" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当然要我选择的话,首选阿里的中间件Canal。</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);">Canal服务端安装</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;">服务端需要下载压缩包,下载地址:https://github.com/alibaba/canal/releases</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);">v1.1.5</span>,点击下载:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.2833698030634573" src="/upload/a3eac37fa40b70b42ec282f74d2c216.png" data-type="png" data-w="914" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">下载完成解压,目录如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.2263959390862944" src="/upload/f0a3b798e550b62f2f03701fb511ba17.png" data-type="png" data-w="985" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">本文使用<span style="font-weight: 700;color: rgb(248, 57, 41);">Canal+RabbitMQ</span>进行数据的同步,因此下面步骤完全按照这个base进行。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;"><span style="color: rgb(248, 57, 41);">1、打开MySQL的binlog日志</span></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;">修改MySQL的日志文件,my.cnf 配置如下:</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/A7sq8BD8oewJibQ3rHv2SQ22vVQCHQnqHMdOU3sqyHGAKo6YLdcfEEdG4oyeAsf8UDak0l6JXnW2m6gEDwibGoalqOnAtHbYnh/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;">[mysqld]<br>log-bin=mysql-bin # 开启 binlog<br>binlog-format=ROW # 选择 ROW 模式<br>server_id=1 # 配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复<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;"><span style="color: rgb(248, 57, 41);">2、设置MySQL的配置</span></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;">需要设置服务端配置文件中的MySQL配置,这样Canal才能知道需要监听哪个库、哪个表的日志文件。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">一个 Server 可以配置多个实例监听 ,Canal 功能默认自带的有个 example 实例,本篇就用 example 实例 。如果增加实例,复制 example 文件夹内容到同级目录下,然后在 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">canal.properties</code> 指定添加实例的名称。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">修改canal.deployer-1.1.5\conf\example\instance.properties配置文件</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/A7sq8BD8oewJibQ3rHv2SQ22vVQCHQnqHMdOU3sqyHGAKo6YLdcfEEdG4oyeAsf8UDak0l6JXnW2m6gEDwibGoalqOnAtHbYnh/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;"># url<br>canal.instance.master.address=127.0.0.1:3306<br># username/password<br>canal.instance.dbUsername=root<br>canal.instance.dbPassword=root<br># 监听的数据库<br>canal.instance.defaultDatabaseName=test<br><br># 监听的表,可以指定,多个用逗号分割,这里正则是监听所有<br>canal.instance.filter.regex=.*\\..*<br><br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;"><span style="color: rgb(248, 57, 41);">3、设置RabbitMQ的配置</span></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);">tcp</span>,需要在配置文件中设置<span style="font-weight: 700;color: rgb(248, 57, 41);">MQ</span>的相关信息。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这里需要修改两处配置文件,如下;</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">1、canal.deployer-1.1.5\conf\canal.properties</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;">这个配置文件主要是设置MQ相关的配置,比如URL,用户名、密码...</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ22vVQCHQnqHMdOU3sqyHGAKo6YLdcfEEdG4oyeAsf8UDak0l6JXnW2m6gEDwibGoalqOnAtHbYnh/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;"># 传输方式:tcp, kafka, rocketMQ, rabbitMQ<br>canal.serverMode = rabbitMQ<br>##################################################<br>######### RabbitMQ #############<br>##################################################<br>rabbitmq.host = 127.0.0.1<br>rabbitmq.virtual.host =/<br># exchange<br>rabbitmq.exchange =canal.exchange<br># 用户名、密码<br>rabbitmq.username =guest<br>rabbitmq.password =guest<br>## 是否持久化<br>rabbitmq.deliveryMode = 2<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">2、canal.deployer-1.1.5\conf\example\instance.properties</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;">这个文件设置MQ的路由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/A7sq8BD8oewJibQ3rHv2SQ22vVQCHQnqHMdOU3sqyHGAKo6YLdcfEEdG4oyeAsf8UDak0l6JXnW2m6gEDwibGoalqOnAtHbYnh/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;">canal.mq.topic=canal.routing.key<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;"><span style="color: rgb(248, 57, 41);">4、RabbitMQ新建exchange和Queue</span></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;">在RabbitMQ中需要新建一个<span style="font-weight: 700;color: rgb(248, 57, 41);">canal.exchange</span>(必须和配置中的相同)的exchange和一个名称为 <span style="font-weight: 700;color: rgb(248, 57, 41);">canal.queue</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;">其中绑定的路由KEY为:<span style="font-weight: 700;color: rgb(248, 57, 41);">canal.routing.key</span>(必须和配置中的相同),如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.3457142857142857" src="/upload/e2518762de559381714b573bf5f092fc.png" data-type="png" data-w="700" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;"><span style="color: rgb(248, 57, 41);">5、启动服务端</span></span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">点击bin目录下的脚本,windows直接双击<span style="font-weight: 700;color: rgb(248, 57, 41);">startup.bat</span>,启动成功如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.2914572864321608" src="/upload/1b0b623b83d962a04b511375aceb996b.png" data-type="png" data-w="1194" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;"><span style="color: rgb(248, 57, 41);">6、测试</span></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);">test</span>中的<span style="font-weight: 700;color: rgb(248, 57, 41);">oauth_client_details</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/A7sq8BD8oewJibQ3rHv2SQ22vVQCHQnqHMdOU3sqyHGAKo6YLdcfEEdG4oyeAsf8UDak0l6JXnW2m6gEDwibGoalqOnAtHbYnh/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;">INSERT INTO `oauth_client_details` VALUES ('myjszl', 'res1', '$2a$10$F1tQdeb0SEMdtjlO8X/0wO6Gqybu6vPC/Xg8OmP9/TL1i4beXdK9W', 'all', 'password,refresh_token,authorization_code,client_credentials,implicit', 'http://www.baidu.com', NULL, 1000, 1000, NULL, 'false');<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">此时查看MQ中的<span style="font-weight: 700;color: rgb(248, 57, 41);">canal.queue</span>已经有了数据,如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.15148247978436657" src="/upload/9cffa00c3dc6b278f0450fbbc14ec2cc.png" data-type="png" data-w="1855" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">其实就是一串JSON数据,这个JSON如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ22vVQCHQnqHMdOU3sqyHGAKo6YLdcfEEdG4oyeAsf8UDak0l6JXnW2m6gEDwibGoalqOnAtHbYnh/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">{<br>&nbsp;<span style="color: #986801;line-height: 26px;">"data"</span>:&nbsp;[{<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"client_id"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"myjszl"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"resource_ids"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"res1"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"client_secret"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"$2a$10$F1tQdeb0SEMdtjlO8X/0wO6Gqybu6vPC/Xg8OmP9/TL1i4beXdK9W"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"scope"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"all"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"authorized_grant_types"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"password,refresh_token,authorization_code,client_credentials,implicit"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"web_server_redirect_uri"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"http://www.baidu.com"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"authorities"</span>:&nbsp;<span style="color: #0184bb;line-height: 26px;">null</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"access_token_validity"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"1000"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"refresh_token_validity"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"1000"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"additional_information"</span>:&nbsp;<span style="color: #0184bb;line-height: 26px;">null</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"autoapprove"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"false"</span><br>&nbsp;}],<br>&nbsp;<span style="color: #986801;line-height: 26px;">"database"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"test"</span>,<br>&nbsp;<span style="color: #986801;line-height: 26px;">"es"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">1640337532000</span>,<br>&nbsp;<span style="color: #986801;line-height: 26px;">"id"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">7</span>,<br>&nbsp;<span style="color: #986801;line-height: 26px;">"isDdl"</span>:&nbsp;<span style="color: #0184bb;line-height: 26px;">false</span>,<br>&nbsp;<span style="color: #986801;line-height: 26px;">"mysqlType"</span>:&nbsp;{<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"client_id"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"varchar(48)"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"resource_ids"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"varchar(256)"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"client_secret"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"varchar(256)"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"scope"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"varchar(256)"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"authorized_grant_types"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"varchar(256)"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"web_server_redirect_uri"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"varchar(256)"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"authorities"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"varchar(256)"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"access_token_validity"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"int(11)"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"refresh_token_validity"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"int(11)"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"additional_information"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"varchar(4096)"</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"autoapprove"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"varchar(256)"</span><br>&nbsp;},<br>&nbsp;<span style="color: #986801;line-height: 26px;">"old"</span>:&nbsp;<span style="color: #0184bb;line-height: 26px;">null</span>,<br>&nbsp;<span style="color: #986801;line-height: 26px;">"pkNames"</span>:&nbsp;[<span style="color: #50a14f;line-height: 26px;">"client_id"</span>],<br>&nbsp;<span style="color: #986801;line-height: 26px;">"sql"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">""</span>,<br>&nbsp;<span style="color: #986801;line-height: 26px;">"sqlType"</span>:&nbsp;{<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"client_id"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">12</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"resource_ids"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">12</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"client_secret"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">12</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"scope"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">12</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"authorized_grant_types"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">12</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"web_server_redirect_uri"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">12</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"authorities"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">12</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"access_token_validity"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">4</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"refresh_token_validity"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">4</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"additional_information"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">12</span>,<br>&nbsp;&nbsp;<span style="color: #986801;line-height: 26px;">"autoapprove"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">12</span><br>&nbsp;},<br>&nbsp;<span style="color: #986801;line-height: 26px;">"table"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"oauth_client_details"</span>,<br>&nbsp;<span style="color: #986801;line-height: 26px;">"ts"</span>:&nbsp;<span style="color: #986801;line-height: 26px;">1640337532520</span>,<br>&nbsp;<span style="color: #986801;line-height: 26px;">"type"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"INSERT"</span><br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">每个字段的意思已经很清楚了,有表名称、方法、参数、参数类型、参数值.....</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);">客户端要做的就是监听MQ获取JSON数据,然后将其解析出来,处理自己的业务逻辑。</span></p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">Canal客户端搭建</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;">客户端很简单实现,要做的就是消费Canal服务端传递过来的消息,监听<span style="font-weight: 700;color: rgb(248, 57, 41);">canal.queue</span>这个队列。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;"><span style="color: rgb(248, 57, 41);">1、创建消息实体类</span></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;">MQ传递过来的是JSON数据,当然要创建个实体类接收数据,如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewJibQ3rHv2SQ22vVQCHQnqHMdOU3sqyHGAKo6YLdcfEEdG4oyeAsf8UDak0l6JXnW2m6gEDwibGoalqOnAtHbYnh/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">@author</span>&nbsp;公众号&nbsp;码猿技术专栏<br>&nbsp;*&nbsp;Canal消息接收实体类<br>&nbsp;*/</span><br><span style="color: #4078f2;line-height: 26px;">@NoArgsConstructor</span><br><span style="color: #4078f2;line-height: 26px;">@Data</span><br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">CanalMessage</span>&lt;<span style="color: #c18401;line-height: 26px;">T</span>&gt;&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@JsonProperty</span>(<span style="color: #50a14f;line-height: 26px;">"type"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;String&nbsp;type;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@JsonProperty</span>(<span style="color: #50a14f;line-height: 26px;">"table"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;String&nbsp;table;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@JsonProperty</span>(<span style="color: #50a14f;line-height: 26px;">"data"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;List&lt;T&gt;&nbsp;data;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@JsonProperty</span>(<span style="color: #50a14f;line-height: 26px;">"database"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;String&nbsp;database;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@JsonProperty</span>(<span style="color: #50a14f;line-height: 26px;">"es"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;Long&nbsp;es;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@JsonProperty</span>(<span style="color: #50a14f;line-height: 26px;">"id"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;Integer&nbsp;id;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@JsonProperty</span>(<span style="color: #50a14f;line-height: 26px;">"isDdl"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;Boolean&nbsp;isDdl;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@JsonProperty</span>(<span style="color: #50a14f;line-height: 26px;">"old"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;List&lt;T&gt;&nbsp;old;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@JsonProperty</span>(<span style="color: #50a14f;line-height: 26px;">"pkNames"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;List&lt;String&gt;&nbsp;pkNames;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@JsonProperty</span>(<span style="color: #50a14f;line-height: 26px;">"sql"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;String&nbsp;sql;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@JsonProperty</span>(<span style="color: #50a14f;line-height: 26px;">"ts"</span>)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;Long&nbsp;ts;<br>}<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;"><span style="color: rgb(248, 57, 41);">2、MQ消息监听业务</span></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;">接下来就是监听队列,一旦有Canal服务端有数据推送能够及时的消费。</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> <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/A7sq8BD8oewJibQ3rHv2SQ22vVQCHQnqHMdOU3sqyHGAKo6YLdcfEEdG4oyeAsf8UDak0l6JXnW2m6gEDwibGoalqOnAtHbYnh/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;cn.hutool.json.JSONUtil;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;cn.myjszl.middle.ware.canal.mq.rabbit.model.CanalMessage;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;lombok.RequiredArgsConstructor;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;lombok.extern.slf4j.Slf4j;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;org.springframework.amqp.rabbit.annotation.Exchange;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;org.springframework.amqp.rabbit.annotation.Queue;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;org.springframework.amqp.rabbit.annotation.QueueBinding;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;org.springframework.amqp.rabbit.annotation.RabbitListener;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;org.springframework.stereotype.Component;<br><br><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;*&nbsp;监听MQ获取Canal增量的数据消息<br>&nbsp;*/</span><br><span style="color: #4078f2;line-height: 26px;">@Component</span><br><span style="color: #4078f2;line-height: 26px;">@Slf</span>4j<br><span style="color: #4078f2;line-height: 26px;">@RequiredArgsConstructor</span><br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">CanalRabbitMQListener</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@RabbitListener</span>(bindings&nbsp;=&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@QueueBinding</span>(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value&nbsp;=&nbsp;<span style="color: #4078f2;line-height: 26px;">@Queue</span>(value&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"canal.queue"</span>,&nbsp;durable&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"true"</span>),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exchange&nbsp;=&nbsp;<span style="color: #4078f2;line-height: 26px;">@Exchange</span>(value&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"canal.exchange"</span>),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;key&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"canal.routing.key"</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)<br>&nbsp;&nbsp;&nbsp;&nbsp;})<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">handleDataChange</span><span style="line-height: 26px;">(String&nbsp;message)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//将message转换为CanalMessage</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CanalMessage&nbsp;canalMessage&nbsp;=&nbsp;JSONUtil.toBean(message,&nbsp;CanalMessage<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>)</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;tableName&nbsp;=&nbsp;canalMessage.getTable();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info(<span style="color: #50a14f;line-height: 26px;">"Canal 监听&nbsp;{}&nbsp;发生变化;明细:{}"</span>,&nbsp;tableName,&nbsp;message);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//TODO&nbsp;业务逻辑自己完善...............</span><br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br><br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;"><span style="color: rgb(248, 57, 41);">3、测试</span></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;">下面向表中插入数据,看下接收的消息是什么样的,SQL如下:</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/A7sq8BD8oewJibQ3rHv2SQ22vVQCHQnqHMdOU3sqyHGAKo6YLdcfEEdG4oyeAsf8UDak0l6JXnW2m6gEDwibGoalqOnAtHbYnh/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #a626a4;line-height: 26px;">INSERT</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">INTO</span>&nbsp;<span style="color: #50a14f;line-height: 26px;">`oauth_client_details`</span><br><span style="color: #a626a4;line-height: 26px;">VALUES</span><br>&nbsp;(&nbsp;<span style="color: #50a14f;line-height: 26px;">'myjszl'</span>,&nbsp;<span style="color: #50a14f;line-height: 26px;">'res1'</span>,&nbsp;<span style="color: #50a14f;line-height: 26px;">'$2a$10$F1tQdeb0SEMdtjlO8X/0wO6Gqybu6vPC/Xg8OmP9/TL1i4beXdK9W'</span>,&nbsp;<span style="color: #50a14f;line-height: 26px;">'all'</span>,&nbsp;<span style="color: #50a14f;line-height: 26px;">'password,refresh_token,authorization_code,client_credentials,implicit'</span>,&nbsp;<span style="color: #50a14f;line-height: 26px;">'http://www.baidu.com'</span>,&nbsp;<span style="color: #0184bb;line-height: 26px;">NULL</span>,&nbsp;<span style="color: #986801;line-height: 26px;">1000</span>,&nbsp;<span style="color: #986801;line-height: 26px;">1000</span>,&nbsp;<span style="color: #0184bb;line-height: 26px;">NULL</span>,&nbsp;<span style="color: #50a14f;line-height: 26px;">'false'</span>&nbsp;);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">客户端转换后的消息如下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5620578778135048" src="/upload/db6e09c8f5ce87c434f6f99b34a1df99.png" data-type="png" data-w="1555" style="border-radius: 9px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上图可以看出所有的数据都已经成功接收到,只需要根据数据完善自己的业务逻辑即可。</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">客户端案例源码已经上传GitHub,关注公众号:<span style="font-weight: 700;color: rgb(248, 57, 41);">码猿技术专栏</span>,回复关键词:<span style="font-weight: 700;color: rgb(248, 57, 41);">9530</span> 获取!</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <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;">数据增量同步的开源工具并不只有Canal一种,根据自己的业务需要选择合适的组件。</p> </section>

丢,我讲的是监控,不是QPS

作者:微信小助手

<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="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">大家好,我是3y</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img" data-ratio="0.7764198418404026" src="/upload/184bc8366f215995c4ccd237dd339a91.jpg" data-type="jpeg" data-w="1391" style="display: block;margin-right: auto;margin-left: auto;">今天austin项目来给大家整点不一样的:花点时间跟着文章做完,屏幕壁纸就可以有了,我来上个图,大家就懂了。</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.5128205128205128" src="/upload/afec9e0d0548ebfb60f9900203269e3d.jpg" data-type="jpeg" data-w="2106" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <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.5490594814438231" src="/upload/ef552ab03d0ebfecb8878c0f3147ba6b.jpg" data-type="jpeg" data-w="1967" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">每当同事一瞄你的电脑,发现都是图形化的、黑色的看起来就比较高端的界面:“<strong>嗯,这逼又在找Bug了吧</strong>”</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">没错,要聊的话题就是<strong>监控</strong></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><span style="display: none;"></span>01、为什么监控</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">过去在面试的时候,我记得曾经被问过:“<strong>线上出了问题,你们是怎么排查的?排查的思路是怎么样的?</strong>”</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">我以前的部门老大很看重<strong>稳定性</strong>,经常让我们梳理系统的上下链路和接口信息。我想:想要提高系统的稳定性就需要有<strong>完备的监控和及时告警</strong>。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.14251781472684086" src="/upload/785b4ec47163270de647f043fb5471e8.jpg" data-type="jpeg" data-w="1684" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">有了监控,出了问题可以<strong>快速定位</strong>(而不是出了问题还在那里打印日志查找,很多问题都可以通过监控的数据就直接看出来了)。有了监控,我们可以把<strong>指标都配置在监控</strong>内,无论是技术上的还是业务上的(只不过业务的数据叫做看板,而系统的数据叫做监控)。有了监控我们看待系统的角度都会不一样(<strong>全方位理解系统</strong>的性能指标和业务指标)</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">如果你线上的系统还没有监控,<strong>那着实是不太行的了</strong></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><span style="display: none;"></span>02、监控开源组件</h2> <p data-tool="mdnice编辑�

深入理解Netty-从偶现宕机看Netty流量控制

作者:微信小助手

<section style="font-size: 15px;line-height: 1.9;letter-spacing: 0.75px;box-sizing: border-box;"> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="26" data-source-title=""> <section class="js_blockquote_digest"> <section> 作者:vivo互联网服务器团队-Zhang Lin </section> </section> </blockquote> <p><br></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">一、业务背景</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">目前移动端的使用场景中会用到大量的消息推送,push消息可以帮助运营人员更高效地实现运营目标(比如给用户推送营销活动或者提醒APP新功能)。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">对于推送系统来说需要具备以下两个特性:</p> <ul class=" list-paddingleft-2" style="list-style-type: disc;"> <li><p style="white-space: normal;box-sizing: border-box;">消息秒级送到用户,无延时,支持每秒百万推送,单机百万长连接。</p></li> <li> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">支持通知、文本、自定义消息透传等展现形式。正是由于以上原因,对于系统的开发和维护带来了挑战。下图是推送系统的简单描述(API-&gt;推送模块-&gt;手机)。</p> </section></li> </ul> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="198" data-backw="538" data-ratio="0.3687150837988827" data-s="300,640" src="/upload/117dd15057362c0201d7016f490af8cb.png" data-type="png" data-w="537" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">二、问题背景</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">推送系统中长连接集群在稳定性测试、压力测试阶运行一段时间后随机会出现一个进程挂掉的情况,概率较小(频率为一个月左右发生一次),这会影响部分客户端消息送到的时效。</p> <p style="white-space: normal;box-sizing: border-box;"><br></p> <p style="white-space: normal;box-sizing: border-box;">推送系统中的长<span style="letter-spacing: 0.75px;">连接节点(Broker系统)是基于Netty开发,此节点维护了服务端和手机终端的长连接,线上问题出现后,添加</span><span style="letter-spacing: 0.75px;">N</span><span style="letter-spacing: 0.75px;">etty</span><span style="letter-spacing: 0.75px;">内存泄露监控参数进行问题排查,观察多天但并未排查出问题。</span></p> <p style="white-space: normal;box-sizing: border-box;"><span style="letter-spacing: 0.75px;"><br></span></p> <p style="white-space: normal;box-sizing: border-box;"><span style="letter-spacing: 0.75px;"></span><span style="letter-spacing: 0.75px;">由</span><span style="letter-spacing: 0.75px;">于长连接节点是</span><span style="letter-spacing: 0.75px;">N</span><span style="letter-spacing: 0.75px;">etty</span><span style="letter-spacing: 0.75px;">开发,为便于读者理解,下面简单介绍一下</span><span style="letter-spacing: 0.75px;">N</span><span style="letter-spacing: 0.75px;">etty</span><span style="letter-spacing: 0.75px;">。</span></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">三、&nbsp;Netty介绍</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">Netty是一个高性能、异步事件驱动的NIO框架,基于Java NIO提供的API实现。它提供了对TCP、UDP和文件传输的支持,作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,HBase,Hadoop,Bees,Dubbo等开源组件也基于Netty的NIO框架构建。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">四、问题分析</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="box-sizing: border-box;"><span style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;">4.1 猜想</span></p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;">最初猜想是长连接数导致的,但经过排查日志、分析代码,发现并不是此原因造成。</p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;">长连接数:39万,如下图:</p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="40" data-backw="578" data-ratio="0.06974248927038626" data-s="300,640" src="/upload/fc26e2c74862a8d8652ae0b6f9a81415.png" data-type="png" data-w="932" style="width: 100%;height: auto;"></p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;">每个channel字节大小1456, 按40万长连接计算,不致于产生内存过大现象。</p> <p style="box-sizing: border-box;"><br></p> <p style="box-sizing: border-box;"><span style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;">4.2 查看GC日志</span></p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;">查看GC日志,发现进程挂掉之前频繁full GC(频率5分钟一次),但内存并未降低,怀疑堆外内存泄露。</p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;"><span style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;">4.3 分析heap内存情况</span></p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;"><span style="color: rgb(136, 136, 136);text-decoration: underline;">ChannelOutboundBuffer</span>对象占将近5G内存,泄露原因基本可以确定:ChannelOutboundBuffer的entry数过多导致,查看<span style="color: rgb(136, 136, 136);text-decoration: underline;">ChannelOutboundBuffer</span>的源码可以分析出,是<span style="color: rgb(136, 136, 136);text-decoration: underline;">ChannelOutboundBuffer</span>中的数据。</p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;">没有写出去,导致一直积压;</p> <p style="box-sizing: border-box;">ChannelOutboundBuffer内部是一个链表结构。</p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="121" data-backw="578" data-ratio="0.20869565217391303" data-s="300,640" src="/upload/88ecab754f3b108f08c82263b4acb641.png" data-type="png" data-w="690" style="width: 100%;height: auto;"></p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;"><span style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;">4.4 从上图分析数据未写出去,为什么会出现这种情况?</span></p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;">代码中实际有判断连接是否可用的情况(Channel.isActive),并且会对超时的连接进行关闭。从历史经验来看,这种情况发生在连接半打开(客户端异常关闭)的情况比较多---双方不进行数据通信无问题。</p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;">按上述猜想,测试环境进行重现和测试。</p> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="156" data-source-title=""> <section class="js_blockquote_digest"> <section> <p style="box-sizing: border-box;">1)模拟客户端集群,并与长连接服务器建立连接,设置客户端节点的防火墙,模拟服务器与客户端网络异常的场景(即要模拟Channel.isActive调用成功,但数据实际发送不出去的情况)。</p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;">2)调小堆外内存,持续发送测试消息给之前的客户端。消息大小(1K左右)。</p> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="box-sizing: border-box;">3)按照128M内存来计算,实际上调用9W多次就会出现。</p> </section> </section> </blockquote> <p><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="39" data-backw="578" data-ratio="0.06726907630522089" data-s="300,640" src="/upload/4d35ebb8b4134364e106dcb03b7766f4.png" data-type="png" data-w="996" style="width: 100%;height: auto;"></p> <p style="box-sizing: border-box;"><br></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">五、问题解决</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">5.1 启用autoRead机制</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">当channel不可写时,关闭autoRead;</p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">public</span> <span class="code-snippet__keyword">void</span> <span class="code-snippet__title">channelReadComplete</span>(<span class="code-snippet__params">ChannelHandlerContext ctx</span>) throws Exception</span> {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (!ctx.channel().isWritable()) {</span></code><code><span class="code-snippet_outer"> Channel channel = ctx.channel();</span></code><code><span class="code-snippet_outer"> ChannelInfo channelInfo = ChannelManager.CHANNEL_CHANNELINFO.<span class="code-snippet__keyword">get</span>(channel);</span></code><code><span class="code-snippet_outer"> String clientId = <span class="code-snippet__string">""</span>;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (channelInfo != <span class="code-snippet__literal">null</span>) {</span></code><code><span class="code-snippet_outer"> clientId = channelInfo.getClientId();</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> LOGGER.info(<span class="code-snippet__string">"channel is unwritable, turn off autoread, clientId:{}"</span>, clientId);</span></code><code><span class="code-snippet_outer"> channel.config().setAutoRead(<span class="code-snippet__literal">false</span>);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">当数据可写时开启autoRead;</p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="java"><code><span class="code-snippet_outer"><span class="code-snippet__meta">@Override</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">public</span> <span class="code-snippet__keyword">void</span> <span class="code-snippet__title">channelWritabilityChanged</span><span class="code-snippet__params">(ChannelHandlerContext ctx)</span> <span class="code-snippet__keyword">throws</span> Exception</span></span></code><code><span class="code-snippet_outer">{</span></code><code><span class="code-snippet_outer"> Channel channel = ctx.channel();</span></code><code><span class="code-snippet_outer"> ChannelInfo channelInfo = ChannelManager.CHANNEL_CHANNELINFO.get(channel);</span></code><code><span class="code-snippet_outer"> String clientId = <span class="code-snippet__string">""</span>;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (channelInfo != <span class="code-snippet__keyword">null</span>) {</span></code><code><span class="code-snippet_outer"> clientId = channelInfo.getClientId();</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (channel.isWritable()) {</span></code><code><span class="code-snippet_outer"> LOGGER.info(<span class="code-snippet__string">"channel is writable again, turn on autoread, clientId:{}"</span>, clientId);</span></code><code><span class="code-snippet_outer"> channel.config().setAutoRead(<span class="code-snippet__keyword">true</span>);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;"><strong>说明:</strong></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.6395939086294417" data-s="300,640" src="/upload/2c5429fdaad85846d7121405cfd0ec96.png" data-type="png" data-w="394" style="width: 393px;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">autoRead的作用是更精确的速率控制,如果打开的时候Netty就会帮我们注册读事件。当注册了读事件后,如果网络可读,则Netty就会从channel读取数据。那如果autoread关掉后,则Netty会不注册读事件。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">这样即使是对端发送数据过来了也不会触发读事件,从而也不会从channel读取到数据。当recv_buffer满时,也就不会再接收数据。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">5.2 设置高低水位</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <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">serverBootstrap</span><span class="code-snippet__selector-class">.option</span>(<span class="code-snippet__selector-tag">ChannelOption</span><span class="code-snippet__selector-class">.WRITE_BUFFER_WATER_MARK</span>, <span class="code-snippet__selector-tag">new</span> <span class="code-snippet__selector-tag">WriteBufferWaterMark</span>(1024 * 1024, 8 * 1024 * 1024));</span></code></pre> </section> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="23" data-source-title=""> <section class="js_blockquote_digest"> <section> 注:高低水位配合后面的isWritable使用 </section> </section> </blockquote> <p><br></p> </section> <section style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">5.3 增加channel.isWritable()的判断</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">channel是否可用除了校验channel.isActive()还需要加上channel.isWrite()的判断,isActive只是保证连接是否激活,而是否可写由isWrite来决定。</p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">private</span> <span class="code-snippet__keyword">void</span> <span class="code-snippet__title">writeBackMessage</span>(<span class="code-snippet__params">ChannelHandlerContext ctx, MqttMessage message</span>)</span> {</span></code><code><span class="code-snippet_outer"> Channel channel = ctx.channel();</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">//增加channel.isWritable()的判断</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (channel.isActive() &amp;&amp; channel.isWritable()) {</span></code><code><span class="code-snippet_outer"> ChannelFuture cf = channel.writeAndFlush(message);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (cf.isDone() &amp;&amp; cf.cause() != <span class="code-snippet__literal">null</span>) {</span></code><code><span class="code-snippet_outer"> LOGGER.error(<span class="code-snippet__string">"channelWrite error!"</span>, cf.cause());</span></code><code><span class="code-snippet_outer"> ctx.close();</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="76" data-source-title=""> <section class="js_blockquote_digest"> <section style="text-align: left;"> 注: <span style="letter-spacing: 0.75px;">isWritable可以来控制ChannelOutboundBuffer,不让其无限制膨胀。</span> <span style="letter-spacing: 0.75px;">其机制就是利用设置好的channel高低水位来进行判断。</span> </section> </section> </blockquote> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">5.4 问题验证</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">修改后再进行测试,发送到27W次也并不报错;</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="43" data-backw="578" data-ratio="0.07525870178739416" data-s="300,640" src="/upload/93b007077ec2fdbef55dab52ab88d6df.png" data-type="png" data-w="1063" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">六、解决思路分析</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">一般<span style="font-size: 15px;letter-spacing: 0.75px;">N</span><span style="font-size: 15px;letter-spacing: 0.75px;">etty</span>数据处理流程如下:将读取的数据交由业务线程处理,处理完成再发送出去(整个过程是异步的),<span style="font-size: 15px;letter-spacing: 0.75px;">N</span><span style="font-size: 15px;letter-spacing: 0.75px;">etty</span>为了提高网络的吞吐量,在业务层与socket之间增加了一个ChannelOutboundBuffer。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">在调用channel.write的时候,所有写出的数据其实并没有写到socket,而是先写到ChannelOutboundBuffer。当调用channel.flush的时候才真正的向socket写出。因为这中间有一个buffer,就存在速率匹配了,而且这个buffer还是无界的(链表),也就是你如果没有控制channel.write的速度,会有大量的数据在这个buffer里堆积,如果又碰到socket写不出数据的时候(isActive此时判断无效)或者写得慢的情况。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;text-align: justify;">很有可能的结果就是资源耗尽,而且如果ChannelOutboundBuffer存放的是</p> <p style="white-space: normal;box-sizing: border-box;text-align: justify;">DirectByteBuffer,这会让问题更加难排查。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">流程可抽象如下:</p> <p style="white-space: normal;box-sizing: border-box;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.2696296296296296" data-s="300,640" src="/upload/8413825327fe8f1a3727e57d36da1524.png" data-type="png" data-w="675" style=""></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">从上面的分析可以看出,步骤一写太快(快到处理不过来)或者下游发送不出数据都会造成问题,这实际是一个速率匹配问题。</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">七、Netty源码说明</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">超过高水位</strong></p> <p style="white-space: normal;box-sizing: border-box;text-align: left;">当ChannelOutboundBuffer的容量超过高水位设定阈值后,isWritable()返回false,设置channel不可写(setUnwritable),并且触发fireChannelWritabilityChanged()。</p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="java"><code><span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">private</span> <span class="code-snippet__keyword">void</span> <span class="code-snippet__title">incrementPendingOutboundBytes</span><span class="code-snippet__params">(<span class="code-snippet__keyword">long</span> size, <span class="code-snippet__keyword">boolean</span> invokeLater)</span> </span>{</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (size == <span class="code-snippet__number">0</span>) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span>;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">long</span> newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(<span class="code-snippet__keyword">this</span>, size);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (newWriteBufferSize &gt; channel.config().getWriteBufferHighWaterMark()) {</span></code><code><span class="code-snippet_outer"> setUnwritable(invokeLater);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code><code><span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">private</span> <span class="code-snippet__keyword">void</span> <span class="code-snippet__title">setUnwritable</span><span class="code-snippet__params">(<span class="code-snippet__keyword">boolean</span> invokeLater)</span> </span>{</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">for</span> (;;) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">final</span> <span class="code-snippet__keyword">int</span> oldValue = unwritable;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">final</span> <span class="code-snippet__keyword">int</span> newValue = oldValue | <span class="code-snippet__number">1</span>;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (UNWRITABLE_UPDATER.compareAndSet(<span class="code-snippet__keyword">this</span>, oldValue, newValue)) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (oldValue == <span class="code-snippet__number">0</span> &amp;&amp; newValue != <span class="code-snippet__number">0</span>) {</span></code><code><span class="code-snippet_outer"> fireChannelWritabilityChanged(invokeLater);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">break</span>;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><strong>低于低水位</strong><strong style="box-sizing: border-box;"></strong></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">当ChannelOutboundBuffer的容量低于低水位设定阈值后,isWritable()返回true,设置channel可写,并且触发fireChannelWritabilityChanged()。</p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="java"><code><span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">private</span> <span class="code-snippet__keyword">void</span> <span class="code-snippet__title">decrementPendingOutboundBytes</span><span class="code-snippet__params">(<span class="code-snippet__keyword">long</span> size, <span class="code-snippet__keyword">boolean</span> invokeLater, <span class="code-snippet__keyword">boolean</span> notifyWritability)</span> </span>{</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (size == <span class="code-snippet__number">0</span>) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span>;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">long</span> newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(<span class="code-snippet__keyword">this</span>, -size);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (notifyWritability &amp;&amp; newWriteBufferSize &lt; channel.config().getWriteBufferLowWaterMark()) {</span></code><code><span class="code-snippet_outer"> setWritable(invokeLater);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code><code><span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">private</span> <span class="code-snippet__keyword">void</span> <span class="code-snippet__title">setWritable</span><span class="code-snippet__params">(<span class="code-snippet__keyword">boolean</span> invokeLater)</span> </span>{</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">for</span> (;;) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">final</span> <span class="code-snippet__keyword">int</span> oldValue = unwritable;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">final</span> <span class="code-snippet__keyword">int</span> newValue = oldValue &amp; ~<span class="code-snippet__number">1</span>;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (UNWRITABLE_UPDATER.compareAndSet(<span class="code-snippet__keyword">this</span>, oldValue, newValue)) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (oldValue != <span class="code-snippet__number">0</span> &amp;&amp; newValue == <span class="code-snippet__number">0</span>) {</span></code><code><span class="code-snippet_outer"> fireChannelWritabilityChanged(invokeLater);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">break</span>;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">八、总结</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;text-align: justify;">当ChannelOutboundBuffer的容量超过高水位设定阈值后,isWritable()返回false,表明消息产生堆积,需要降低写入速度。</p> <p style="white-space: normal;box-sizing: border-box;text-align: justify;"><span style="letter-spacing: 0.75px;"><br></span></p> <p style="white-space: normal;box-sizing: border-box;text-align: justify;"><span style="letter-spacing: 0.75px;">当ChannelOutbound</span><span style="letter-spacing: 0.75px;">Buffer</span><span style="letter-spacing: 0.75px;">的容量低于低水位设定阈值后,isWritable()返回true,表明消息过少,需要提高写入速度。</span><span style="letter-spacing: 0.75px;">通过以上三个步骤修改后,部署线上观察半年未发生问题出现。</span></p> </section> </section>

敢说你没遇到过,主从数据库不一致?

作者:微信小助手

<section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;"><strong>问:常见的数据库集群架构如何?</strong></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;"><span style="font-size: 15px;color: rgb(255, 76, 0);">一主多从,主从同步,读写分离</span>。</span> </section> <section style="line-height: 1.75em;"> <img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.2709497206703911" data-s="300,640" src="/upload/d78833254a56ad8def3592e77cf52467.png" data-type="png" data-w="358" style=""> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">如上图:</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(1)一个主库提供写服务;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(2)多个从库提供读服务,可以增加从库提升读性能;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(3)主从之间同步数据;</span> </section> <section style="line-height: 1.75em;"> <span style="color: rgb(0, 82, 255);font-size: 15px;letter-spacing: 1px;"><em>画外音:任何方案不要忘了本心,加从库的本心,是提升读性能。</em></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;"><br></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;"><strong>问:为什么会出现不一致?</strong></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;"><span style="font-size: 15px;color: rgb(255, 76, 0);">主从同步有时延</span>,这个时延期间读从库,可能读到不一致的数据。</span> </section> <section style="line-height: 1.75em;"> <img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.45871559633027525" data-s="300,640" src="/upload/8c84f4fef81921e5cfa3b5c0f9db612a.png" data-type="png" data-w="327" style=""> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">如上图:</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(1)服务发起了一个写请求;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(2)服务又发起了一个读请求,此时同步未完成,读到一个不一致的脏数据;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(3)数据库主从同步最后才完成;</span> </section> <section style="line-height: 1.75em;"> <span style="color: rgb(0, 82, 255);font-size: 15px;letter-spacing: 1px;"><em>画外音:任何数据冗余,必将引发一致性问题。</em></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">&nbsp;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;"><strong>问:如何避免这种主从延时导致的不一致?</strong></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">常见的方法有这么几种。</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">&nbsp;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;"><strong>方案一:忽略。</strong></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">任何脱离业务的架构设计都是耍流氓,绝大部分业务,例如:百度搜索,淘宝订单,QQ消息,58帖子都允许短时间不一致。</span> </section> <section style="line-height: 1.75em;"> <span style="color: rgb(0, 82, 255);font-size: 15px;letter-spacing: 1px;"><em>画外音:如果业务能接受,最推崇此法。</em></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;"><br></span> </section> <section style="line-height: 1.75em;"> <span style="color: rgb(255, 76, 0);font-size: 15px;letter-spacing: 1px;">如果业务能够接受,别把系统架构搞得太复杂。</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">&nbsp;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;"><strong>方案二:强制读主。</strong></span> </section> <section style="line-height: 1.75em;"> <img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.4754601226993865" data-s="300,640" src="/upload/685f0e1acb41d61fa8beb6fbb03185b1.png" data-type="png" data-w="326" style=""> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">如上图:</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(1)使用一个<span style="font-size: 15px;color: rgb(255, 76, 0);">高可用主库</span>提供数据库服务;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(2)读和写都落到主库上;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(3)采<span style="font-size: 15px;color: rgb(255, 76, 0);">用缓存来提升系统读性能;</span></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">这是很常见的微服务架构,可以避免数据库主从一致性问题。</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">&nbsp;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;"><strong>方案三:选择性读主。</strong></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">强制读主过于粗暴,毕竟只有少量写请求,很短时间,可能读取到脏数据。</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;"><br></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">有没有可能实现,<span style="font-size: 15px;color: rgb(255, 76, 0);">只有这一段时间,可能读到从库脏数据的读请求读主</span>,平时读从呢?</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">&nbsp;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">可以利用一个缓存记录必须读主的数据。</span> </section> <section style="line-height: 1.75em;"> <img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.4567901234567901" data-s="300,640" src="/upload/a519bbe09c98ad4b2f4dda5f883d8520.png" data-type="png" data-w="405" style=""> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">如上图,当写请求发生时:</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(1)写主库;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(2)将哪个库,哪个表,哪个主键三个信息拼装一个key设置到cache里,这条记录的超时时间,设置为“主从同步时延”;</span> </section> <section style="line-height: 1.75em;"> <span style="color: rgb(0, 82, 255);font-size: 15px;letter-spacing: 1px;"><em>画外音:key的格式为“</em></span> <span style="color: rgb(0, 82, 255);letter-spacing: 1px;font-size: 12px;"><em>db:table:PK</em></span> <span style="color: rgb(0, 82, 255);font-size: 15px;letter-spacing: 1px;"><em>”,假设主从延时为1s,这个key的cache超时时间也为1s。</em></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">&nbsp;</span> </section> <section style="line-height: 1.75em;"> <img class="rich_pages wxw-img" data-copyright="0" data-ratio="0.44525547445255476" data-s="300,640" src="/upload/7c300ae17b568b18b4bc7cf6329f6db9.png" data-type="png" data-w="411" style=""> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">如上图,当读请求发生时:</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">这是要读哪个库,哪个表,哪个主键的数据呢,也将这三个信息拼装一个key,到cache里去查询,如果,</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(1)<strong>cache里有这个key</strong>,说明1s内刚发生过写请求,数据库主从同步可能还没有完成,此时就应该<span style="font-size: 15px;color: rgb(255, 76, 0);">去主库查询;</span></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(2)<strong>cache里没有这个key</strong>,说明最近没有发生过写请求,此时就可以<span style="font-size: 15px;color: rgb(255, 76, 0);">去从库查询;</span></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">以此,保证读到的一定不是不一致的脏数据。</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">&nbsp;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;"><strong>总结</strong></span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">数据库主库和从库不一致,常见有这么几种优化方案:</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(1)业务可以接受,系统不优化;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(2)强制读主,高可用主库,用缓存提高读性能;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">(3)在cache里记录哪些记录发生过写请求,来路由读主还是读从;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">&nbsp;</span> </section> <section style="line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;">文字很短,希望能给大家一些启示。</span> </section> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MjM5ODYxMDA5OQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/YrezxckhYOxbibeY4UQvLjjG76dIsbXYGaaKCJpqU0kzRuu3r2CXosccgtc57I15CePibfpQMd5dBibXZDNNZYtkg/0?wx_fmt=png" data-nickname="架构师之路" data-alias="road5858" data-signature="架构师之路,坚持撰写接地气的架构文章" data-from="0"></mpprofile> </section> <section style="background-color: transparent;box-sizing: border-box;clear: both;color: rgb(51, 51, 51);line-height: 1.75em;"> <span style="box-sizing: border-box;margin: 0px;max-width: 100%;padding: 0px;overflow-wrap: break-word;font-size: 15px;letter-spacing: 1px;"></span> </section> <section style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);min-height: 1em;line-height: 1.75em;text-align: center;"> <span style="outline: 0px;font-size: 15px;letter-spacing: 1px;"><strong style="outline: 0px;letter-spacing: 0.544px;font-family: -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 style="outline: 0px;font-size: 12px;letter-spacing: 1px;">架构师之路</span></strong><span style="outline: 0px;font-family: -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: 12px;">-分享</span><span style="outline: 0px;font-family: -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;color: rgb(255, 76, 0);font-size: 12px;">可落地</span><span style="outline: 0px;font-family: -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: 12px;">的技术文章</span></span> </section> <section style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);line-height: 1.75em;"> <strong style="outline: 0px;font-size: 15px;letter-spacing: 1px;"><span style="outline: 0px;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></strong> <span style="outline: 0px;font-size: 15px;letter-spacing: 1px;">:</span> </section> <section style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);line-height: 1.75em;"> <span style="outline: 0px;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;letter-spacing: 1px;">《<a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&amp;mid=2651967131&amp;idx=1&amp;sn=9d56b8358911c2a36445758cfdf53efd&amp;chksm=bd2d7b478a5af2516d3c25f2a10e635512376b3365b6082d566b788971fc18c96a56e5733102&amp;scene=21#wechat_redirect" textvalue="架构师之路,20年干货精选" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" wah-hotarea="click" hasload="1" style="outline: 0px;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;">架构师之路,20年干货精选</a>》</span> </section> <section style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);line-height: 1.75em;"> <br> </section> <section style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);line-height: 1.75em;"> <span style="outline: 0px;font-size: 15px;letter-spacing: 1px;"><span style="outline: 0px;"><span style="font-size: 15px;letter-spacing: 1px;">有更好的方案,欢迎</span><span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">交流</span>,谢</span><span style="outline: 0px;color: rgb(255, 76, 0);">转</span><span style="outline: 0px;">。</span></span> </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;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="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">我是3y,一年<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">CRUD</code>经验用十年的<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">markdown</code>程序员👨🏻‍💻常年被誉为优质八股文选手</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">今天来聊些大家都用得上的东西:<strong>数据链路追踪</strong>。之前引入了系统的监控来快速定位应用操作系统上的问题,而业务问题呢?在这篇文章中你可以看到用注解的方式打印日志,也能看到<strong>简易版</strong>的全链路追踪是怎么实现的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">不多BB,开始吧</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.8780487804878049" data-type="jpeg" data-w="1230" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/4df7f2b6b365d37cc825ddeaf6551cc0.jpg"> </figure> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><span style="display: none;"></span>01、注解日志打印</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">日志的搭建我在austin最开始的前几篇已经有提及了,之前一直在等我的基友<strong>@蛮三刀酱</strong>他的日志组件库上传到Maven库,好让我使用使用下。在最近,他已经更新了两个版本,然后传到了Maven库了,所以我就来接入了</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这个组件库做的事情就是使用<strong>注解</strong>的方式来打印日志信息,并支持<strong>SpEL解析</strong>、<strong>自定义上下文</strong>以及<strong>自定义函数</strong>。它支持的东西听起来很牛逼,但说白了就是<strong>让记录日志的方式做得更装逼</strong>。我们写个破代码还能装逼,这谁受得了!这谁顶得住!</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6646153846153846" data-type="jpeg" data-w="1625" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/ad69398bf04a34c05d47f6bf4f981515.jpg"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">现在我已经把注解在方法上定义了,当该方法被调用时,它打印了以下的日志:</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.23547880690737832" data-type="jpeg" data-w="2548" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/f13ff59174475c18f921acfcd5d5fd82.jpg"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">看起来很好用,对不对?通过一个注解,我就能把<strong>方法的入参</strong>信息打印出来,有<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">bizType</code>和<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">bizId</code>给我们自定义,那就可以很方便地<strong>定位</strong>出打印日志的地方了,并且他还<strong>贴心</strong>把<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">response</code>返回值也输出到日志上。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">至少在这个接口上,这非常符合我这个场景的需求,我们再通过一张图稍微重温下这个<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">send</code>接口到底做了什么事:</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.18086816720257234" data-type="jpeg" data-w="2488" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/6ac72f53025dd2eedab6089621a2a3d3.jpg"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在<strong>接口层面</strong>打印入参信息以及返回值就能定位到很多问题(<strong>懂的都懂</strong>),使用注解还<strong>不用干扰</strong>到我们正常的业务代码就能打印出这么好的日志信息了(<strong>这个逼是装上了</strong>)</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">它的实现原理并不复杂,感兴趣的小伙伴可以拉代码自己看看,先看<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">readmd</code>再看代码!!</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">GitHub:<span style="color: rgb(2, 30, 170);"><strong>https://github.com/qqxx6661/logRecord</strong></span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">总的来说,他通过<strong>SpEL表达式</strong>来读取到<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">#sendRequest</code>入参对象的信息,而注解解析则用的是<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">Spring AOP</code>。至于<strong>自定义上下文</strong>以及<strong>自定义函数</strong>我在这是没用到的,至少在austin项目场景下,我感觉都没什么用。哦,对了,它还能将日志输出到别的管道(MQ)。可惜的是,我这场景也用不到。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在目前的实现下,我<strong>就只有这个接口</strong>能用到该组件,我承认他在某些场景是很好用。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">但它是<strong>有局限性</strong>的:打印的日志信息跟<strong>方法参数强相关</strong>:如果要打印<strong>方法参数以外的变量</strong>那需要用到<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">上下文Context</code> 或者<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">自定义函数</code> 。自定义函数的使用姿势是有局限性的,我们并不能把日志所涉及的变量都抽取到某函数上。如果用上下文Context的话,还是得<strong>嵌入业务代码</strong>里,那为啥不直接拼装好日志打呢?</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.12968299711815562" data-type="jpeg" data-w="1388" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/62d64ab4138b5920720440d5367b2e5e.jpg"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">我一度怀疑是不是我的使用姿势不对,跟基友探讨了下,我的应用场景下还得自己抽取<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">LogUtils</code>进行日志打印。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><span style="display: none;"></span>02、数据链路追踪</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">从上面的接口打印的日志以及能很快地排查出<strong>接入层</strong>的问题了,其实重头戏其实是在<strong>处理层</strong>上,回顾下处理层目前做的事情:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.3648334624322231" data-type="jpeg" data-w="2582" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/11095f2901122c61f8a79463cd4677b6.jpg"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在处理层上会有不少的<strong>平台过滤规则</strong>,这些过滤规则大多都不是针对于消息模板的,而是针对于userId(接收者)的。在这个处理过程中,记录下<strong>每个消息模板中的每个用户的执行情况</strong>就尤其重要了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>1</strong>、定位和排查问题。如果客户反馈用户收不到短信,一般情况下都在这个处理的过程中导致的(可能是被去重,可能是调用接口出问题)</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>2</strong>、对模板执行的整体链路数据分析。一个消息模板一天发送的量级,中途被<strong>每个规则</strong>过滤的量级,成功下发的量级以及消息最后被点击的量级。<strong>除了点击数据,其他的数据都来源处理层</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">基于上面的背景,我设计了一套埋点的规则,在处理<strong>关键链路上</strong>打上对应的点位📝</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="1.0509259259259258" data-type="jpeg" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/6940692cc4b61fa1b394c95a433ba50c.jpg"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">目前点位的信息是不全的,随着系统的完善和接入各个渠道,这里的点位信息还会继续增加,只要我们认为有哪些地方是需要记录下来的,就可以增加。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">可能看到这里你会觉得有些抽象,我请求一次接口打印下日志就容易懂啦:</p> <pre data-tool="mdnice编辑器" style="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/LIND77SSexibEBibTiaxaLNZ75HfN9YRNNlcUibxy1NcSibSLymU8MVtSBFeCP8ibM2bx1p3CzNuKbbniapCba1CEZMWibZLTpiaq04m7/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: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;1、接入层打印日志(returnStr打印处理结果,而msg打印出入参信息)</span><br><span style="color: #d19a66;line-height: 26px;">2022</span>-<span style="color: #d19a66;line-height: 26px;">01</span>-<span style="color: #d19a66;line-height: 26px;">08</span>&nbsp;<span style="color: #d19a66;line-height: 26px;">15</span>:<span style="color: #d19a66;line-height: 26px;">44</span>:<span style="color: #d19a66;line-height: 26px;">53.512</span>&nbsp;[http-nio-<span style="color: #d19a66;line-height: 26px;">8080</span>-exec-<span style="color: #d19a66;line-height: 26px;">7</span>]&nbsp;INFO&nbsp;&nbsp;com.java3y.austin.utils.LogUtils&nbsp;-&nbsp;<br>{<span style="color: #98c379;line-height: 26px;">"bizId"</span>:<span style="color: #98c379;line-height: 26px;">"1"</span>,<span style="color: #98c379;line-height: 26px;">"bizType"</span>:<span style="color: #98c379;line-height: 26px;">"SendService#send"</span>,<span style="color: #98c379;line-height: 26px;">"logId"</span>:<span style="color: #98c379;line-height: 26px;">"34df87fc-0489-46c1-b39f-cafd7652f55b"</span>,<br><span style="color: #98c379;line-height: 26px;">"msg"</span>:<span style="color: #98c379;line-height: 26px;">"{\"code\":\"send\",\"messageParam\":{\"extra\":null,\"receiver\":\"13288888888\",\"variables\":{\"title\":\"yyyyyy\",\"contentValue\":\"66661641627893157\"}},\"messageTemplateId\":1}"</span>,<span style="color: #98c379;line-height: 26px;">"operateDate"</span>:<span style="color: #d19a66;line-height: 26px;">1641627893512</span>,<span style="color: #98c379;line-height: 26px;">"returnStr"</span>:<span style="color: #98c379;line-height: 26px;">"{\"code\":\"00000\",\"msg\":\"操作成功\"}"</span>,<span style="color: #98c379;line-height: 26px;">"success"</span>:<span style="color: #c678dd;line-height: 26px;">true</span>,<span style="color: #98c379;line-height: 26px;">"tag"</span>:<span style="color: #98c379;line-height: 26px;">"operation"</span>}<br><br><span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;2、处理层打印入口日志(表示成功消费到Kafka的消息&nbsp;state=10)</span><br><span style="color: #d19a66;line-height: 26px;">2022</span>-<span style="color: #d19a66;line-height: 26px;">01</span>-<span style="color: #d19a66;line-height: 26px;">08</span>&nbsp;<span style="color: #d19a66;line-height: 26px;">15</span>:<span style="color: #d19a66;line-height: 26px;">44</span>:<span style="color: #d19a66;line-height: 26px;">53.622</span>&nbsp;[org.springframework.kafka.KafkaListenerEndpointContainer#<span style="color: #d19a66;line-height: 26px;">6</span>-<span style="color: #d19a66;line-height: 26px;">0</span>-C-<span style="color: #d19a66;line-height: 26px;">1</span>]&nbsp;INFO&nbsp;&nbsp;com.java3y.austin.utils.LogUtils&nbsp;-&nbsp;<br>{<span style="color: #98c379;line-height: 26px;">"businessId"</span>:<span style="color: #d19a66;line-height: 26px;">1000000120220108</span>,<span style="color: #98c379;line-height: 26px;">"ids"</span>:[<span style="color: #98c379;line-height: 26px;">"13288888888"</span>],<span style="color: #98c379;line-height: 26px;">"state"</span>:<span style="color: #d19a66;line-height: 26px;">10</span>,<span style="color: #98c379;line-height: 26px;">"timestamp"</span>:<span style="color: #d19a66;line-height: 26px;">1641627893622</span>}<br><br><span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;3、处理层打印入口日志(表示成功消费到Kafka的原始日志)</span><br><span style="color: #d19a66;line-height: 26px;">2022</span>-<span style="color: #d19a66;line-height: 26px;">01</span>-<span style="color: #d19a66;line-height: 26px;">08</span>&nbsp;<span style="color: #d19a66;line-height: 26px;">15</span>:<span style="color: #d19a66;line-height: 26px;">44</span>:<span style="color: #d19a66;line-height: 26px;">53.622</span>&nbsp;[org.springframework.kafka.KafkaListenerEndpointContainer#<span style="color: #d19a66;line-height: 26px;">6</span>-<span style="color: #d19a66;line-height: 26px;">0</span>-C-<span style="color: #d19a66;line-height: 26px;">1</span>]&nbsp;INFO&nbsp;&nbsp;com.java3y.austin.utils.LogUtils&nbsp;-&nbsp;<br>{<span style="color: #98c379;line-height: 26px;">"bizType"</span>:<span style="color: #98c379;line-height: 26px;">"Receiver#consumer"</span>,<span style="color: #98c379;line-height: 26px;">"object"</span>:{<span style="color: #98c379;line-height: 26px;">"businessId"</span>:<span style="color: #d19a66;line-height: 26px;">1000000120220108</span>,<span style="color: #98c379;line-height: 26px;">"contentModel"</span>:{<span style="color: #98c379;line-height: 26px;">"content"</span>:<span style="color: #98c379;line-height: 26px;">"66661641627893157"</span>},<span style="color: #98c379;line-height: 26px;">"deduplicationTime"</span>:<span style="color: #d19a66;line-height: 26px;">1</span>,<span style="color: #98c379;line-height: 26px;">"idType"</span>:<span style="color: #d19a66;line-height: 26px;">30</span>,<span style="color: #98c379;line-height: 26px;">"isNightShield"</span>:<span style="color: #d19a66;line-height: 26px;">0</span>,<span style="color: #98c379;line-height: 26px;">"messageTemplateId"</span>:<span style="color: #d19a66;line-height: 26px;">1</span>,<span style="color: #98c379;line-height: 26px;">"msgType"</span>:<span style="color: #d19a66;line-height: 26px;">10</span>,<span style="color: #98c379;line-height: 26px;">"receiver"</span>:[<span style="color: #98c379;line-height: 26px;">"13288888888"</span>],<span style="color: #98c379;line-height: 26px;">"sendAccount"</span>:<span style="color: #d19a66;line-height: 26px;">66</span>,<span style="color: #98c379;line-height: 26px;">"sendChannel"</span>:<span style="color: #d19a66;line-height: 26px;">30</span>,<span style="color: #98c379;line-height: 26px;">"templateType"</span>:<span style="color: #d19a66;line-height: 26px;">10</span>},<span style="color: #98c379;line-height: 26px;">"timestamp"</span>:<span style="color: #d19a66;line-height: 26px;">1641627893622</span>}<br><br><span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;4、处理层打印逻辑过滤日志(state=20,表示这条消息由于配置了丢弃,已经丢弃掉)</span><br><span style="color: #d19a66;line-height: 26px;">2022</span>-<span style="color: #d19a66;line-height: 26px;">01</span>-<span style="color: #d19a66;line-height: 26px;">08</span>&nbsp;<span style="color: #d19a66;line-height: 26px;">15</span>:<span style="color: #d19a66;line-height: 26px;">44</span>:<span style="color: #d19a66;line-height: 26px;">53.623</span>&nbsp;[pool-<span style="color: #d19a66;line-height: 26px;">8</span>-thread-<span style="color: #d19a66;line-height: 26px;">3</span>]&nbsp;INFO&nbsp;&nbsp;com.java3y.austin.utils.LogUtils&nbsp;-&nbsp;<br>{<span style="color: #98c379;line-height: 26px;">"businessId"</span>:<span style="color: #d19a66;line-height: 26px;">1000000120220108</span>,<span style="color: #98c379;line-height: 26px;">"ids"</span>:[<span style="color: #98c379;line-height: 26px;">"13288888888"</span>],<span style="color: #98c379;line-height: 26px;">"state"</span>:<span style="color: #d19a66;line-height: 26px;">20</span>,<span style="color: #98c379;line-height: 26px;">"timestamp"</span>:<span style="color: #d19a66;line-height: 26px;">1641627893622</span>}<br><br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">我打印日志的核心逻辑是:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 在 <strong style="color: black;">入口侧</strong>(这里包括接口的入口以及刚消费Kafka的入口)需要打印出原始的信息。原始信息有了,才好对问题进行定位和排查,至少帮助我们复现 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 在处理过程中使用 <strong style="color: black;">某个标识</strong>来标明处理的过程(10代表成功消费Kafka,20代表该消息已经被丢弃...),并且 <strong style="color: black;">日志的格式是统一</strong>的这样后续我们可以统一清洗该日志信息 </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.140534262485482" data-type="jpeg" data-w="1722" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/802de1a02277eef96158de423d2bb44f.jpg"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">至于打日志的过程就很简单了,只要抽取一个<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">LogUtils</code>类就好咯:</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="1.1018518518518519" data-type="jpeg" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/9d0373f11cdd1e4700d972c3f39f2f3e.jpg"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">那对于点击是怎么追踪的呢?其实也好办,在<strong>下发的链接</strong>上拼接<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">businessId</code>就好了。只要我们能拿到点击的数据,在链接上就可以判断是否存在<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">track_code_bid</code>字符,进而找到是哪个用户点击了哪个模板消息。</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.6954282034771411" data-type="jpeg" data-w="1553" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/4ed4b3e1b2fda2a1bb1b751f7786e44b.jpg"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">无论是打点日志还是原始日志,<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">businessId</code>会跟随着消息的生命周期始终。而businessId的构成只是通过<strong>消息模板内容+时间</strong>而成</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.12060889929742388" data-type="jpeg" data-w="1708" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/adc2b1594be203d67b302fe070eada11.jpg"> </figure> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><span style="display: none;"></span>03、后续</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">现在已经打印出对应的数据链路信息了,但这是不够的,这只是将数据链路信息写到了服务器的本地上,还需要考虑以下的情况:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>1</strong>、运行应用的服务器一般是集群,日志数据会记录到不同的机器上,排查和定位问题只能登录各个服务器查看</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>2</strong>、链路的数据需要<strong>实时</strong>,通过提供Web后台的界面功能快速让<strong>业务方自助</strong>查看整个流程</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>3</strong>、链路的数据需要离线保存用于对数据的分析以及留备份(本地日志往往存放不超过30天)</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.0990606319385141" data-type="jpeg" data-w="2342" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/83313b295d88f51322df34cb7be919d5.jpg"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">后面这些功能都会一一实现,优先会接入ELK来有统一查询日志信息的入口以及配置相关的监控或告警,敬请期待。</p> </section>