文章列表

阿里面试:canal+MQ,会有乱序的问题吗?

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com"> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin: 10px auto 5px;border-top: 1px solid rgb(242, 242, 242);background-color: rgb(242, 242, 242);"><span style="margin-top: -1px;padding-top: 14px;padding-bottom: 14px;padding-right: 5px;padding-left: 5px;font-size: 17px;border-top: 4px solid rgb(33, 33, 34);display: inline-block;line-height: 1.5;font-weight: normal;background-color: rgb(30, 30, 30);border-bottom-right-radius: 100px;color: rgb(255, 255, 255);padding-right: 20px;padding-left: 10px;">本文目录</span></h2> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;Helvetica Neue&quot;;"><strong style="font-family: &quot;Helvetica Neue&quot;;font-size: 13px;letter-spacing: 0.578px;white-space: normal;"><strong><span style="font-size: 14px;">-&nbsp;</span></strong></strong><strong>尼恩说在前面</strong></p> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;Helvetica Neue&quot;;"><strong><span style="font-size: 14px;">-&nbsp;</span>1&nbsp;如何保证消息顺序?</strong></p> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;Helvetica Neue&quot;;"><strong><span style="font-size: 14px;">-&nbsp;</span>2 消息有序的两大类型</strong></p> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;Helvetica Neue&quot;;"><span style="font-family: &quot;Helvetica Neue&quot;;font-size: 14px;letter-spacing: 0.578px;">&nbsp;-&nbsp;</span>2.1 全局顺序</p> <h5 data-sourcepos="299:1-299:178" style="letter-spacing: 0.578px;white-space: normal;"><span style="font-size: 14px;letter-spacing: 0.578px;">&nbsp; &nbsp; -</span><span style="font-family: &quot;Helvetica Neue&quot;;font-size: 13px;letter-spacing: 0.034em;">适用场景</span></h5> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;Helvetica Neue&quot;;"><span style="font-family: &quot;Helvetica Neue&quot;;font-size: 14px;letter-spacing: 0.578px;">&nbsp;-&nbsp;</span>2.2 分区顺序</p> <h5 data-sourcepos="299:1-299:178" style="letter-spacing: 0.578px;white-space: normal;"><span style="font-size: 14px;letter-spacing: 0.578px;">&nbsp; &nbsp; -</span><span style="font-family: &quot;Helvetica Neue&quot;;font-size: 13px;letter-spacing: 0.034em;">适用场景</span></h5> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;Helvetica Neue&quot;;"><span style="font-family: &quot;Helvetica Neue&quot;;font-size: 14px;letter-spacing: 0.578px;">&nbsp;-&nbsp;</span>2.3 对比</p> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;Helvetica Neue&quot;;"><strong><span style="font-size: 14px;">-&nbsp;</span>3 应用开发维度的实现消息有序需要做的工作:</strong></p> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;Helvetica Neue&quot;;"><strong><span style="font-size: 14px;">-&nbsp;</span>4:canal+MQ,如何实现有序?</strong></p> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;Helvetica Neue&quot;;"><span style="font-family: &quot;Helvetica Neue&quot;;font-size: 14px;letter-spacing: 0.578px;">&nbsp;-&nbsp;</span>4.1 Cannal 的有序发送</p> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;Helvetica Neue&quot;;"><span style="font-family: &quot;Helvetica Neue&quot;;font-size: 14px;letter-spacing: 0.578px;">&nbsp;-&nbsp;</span>4.2 Cannal 的有序发送示例</p> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;Helvetica Neue&quot;;"><span style="font-family: &quot;Helvetica Neue&quot;;font-size: 14px;letter-spacing: 0.578px;">&nbsp;-&nbsp;</span>4.3 Cannal 的使用场景</p> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;Helvetica Neue&quot;;"><strong><span style="font-size: 14px;">-&nbsp;</span>尼恩《技术自由圈》多个核心MQ面试题</strong></p> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;PingFang SC&quot;;"><strong><span style="font-stretch: normal;font-family: &quot;Helvetica Neue&quot;;font-size: 14px;">-&nbsp;</span>说在最后</strong></p> <p style="font-stretch: normal;font-size: 13px;font-family: &quot;PingFang SC&quot;;"><strong><br></strong></p> <h2 data-sourcepos="25:1-25:34" style="font-weight: bold;font-size: 22px;margin: 10px auto 5px;border-top-width: 1px;border-top-style: solid;border-top-color: rgb(242, 242, 242);background-color: rgb(242, 242, 242);"><span style="margin-top: -1px;padding-top: 14px;padding-bottom: 14px;padding-right: 5px;padding-left: 5px;font-size: 17px;border-top: 4px solid rgb(33, 33, 34);display: inline-block;line-height: 1.5;font-weight: normal;background-color: rgb(30, 30, 30);border-bottom-right-radius: 100px;color: rgb(255, 255, 255);padding-right: 20px;padding-left: 10px;">1&nbsp;如何保证消息顺序?</span></h2> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">如何实现消息有序?实现顺序消息所必要的条件:顺序发送、顺序存储、顺序消费。</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">在MQ模型中,顺序需由3个阶段去保障</span> </section> <ol data-sourcepos="33:1-36:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">顺序发送: 发送时保持顺序一致</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">顺序存储: broker 存储时保持 顺序一致</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">顺序消费: 消费时 保持 顺序一致</span> </section></li> </ol> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <img class="rich_pages wxw-img" data-imgfileid="100019171" data-ratio="0.2953703703703704" src="/upload/7c33c6b6b11835c94f5566c5d7721023.png" data-type="png" data-w="1080" style="box-sizing: content-box;border-style: none;background-color: rgb(255, 255, 255);"> </section> <h2 data-sourcepos="39:1-39:32" style="font-weight: bold;font-size: 22px;margin: 10px auto 5px;border-top-width: 1px;border-top-style: solid;border-top-color: rgb(242, 242, 242);background-color: rgb(242, 242, 242);"><span style="margin-top: -1px;padding-top: 14px;padding-bottom: 14px;padding-right: 5px;padding-left: 5px;font-size: 17px;border-top: 4px solid rgb(33, 33, 34);display: inline-block;line-height: 1.5;font-weight: normal;background-color: rgb(30, 30, 30);border-bottom-right-radius: 100px;color: rgb(255, 255, 255);padding-right: 20px;padding-left: 10px;">2 消息有序的两大类型</span></h2> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">有序消息,又叫顺序消息(FIFO消息),指消息的消费顺序和产生顺序相同。</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">如订单的生成、付款、发货,这串消息必须按序处理。</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">顺序消息又可分为全局有序和局部有序:</span> </section> <ul data-sourcepos="49:1-57:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>全局有序</strong>:整个MQ系统的所有消息严格按照队列先入先出顺序进行消费</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>局部有序</strong>:只保证一部分关键信息的消费顺序</span> </section></li> </ul> <h3 data-sourcepos="58:1-58:20" style="margin: 10px auto 5px;font-weight: bold;font-size: 20px;letter-spacing: 0.578px;white-space: normal;background-color: rgb(252, 252, 252);"><span style="margin-top: -1px;padding: 6px 20px 6px 10px;font-size: 17px;font-weight: normal;display: inline-block;line-height: 1.3;background-color: rgb(212, 224, 250);border-bottom-right-radius: 100px;color: rgb(30, 30, 30);">2.1 全局顺序</span></h3> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">一个Topic内所有的消息都发布到同一Q,按FIFO顺序进行发布和消费:</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <img class="rich_pages wxw-img" data-imgfileid="100019169" data-ratio="0.1259259259259259" src="/upload/d3b3b5c40c19bb1d141dd9d52ddbdd6b.png" data-type="png" data-w="1080" style="box-sizing: content-box;border-style: none;background-color: rgb(255, 255, 255);"> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">落地到RocketMQ,如何保证全局有序?</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">为了 保证Topic全局消息有序的方式,就是将Topic配置成只有一个唯一的MessageQueue队列, 默认是4个MessageQueue。</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">RocketMQ中,可以在发送者发送消息时指定一个MessageSelector对象,让这个对象来决定消息发入哪一个MessageQueue。这样就可以保证一组有序的消息能够发到同一个MessageQueue里。</span> </section> <h4 data-sourcepos="74:1-74:17" style="margin: 10px auto -1px;padding-left: 3px;font-weight: bold;font-size: 18px;letter-spacing: 0.578px;white-space: normal;border-left-width: 10px;border-left-style: solid;border-left-color: rgb(222, 235, 255);"><span style="margin-top: -1px;padding-top: 6px;padding-right: 5px;padding-left: 5px;font-size: 16px;display: inline-block;line-height: 1.1;">适用场景</span></h4> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">性能要求不高,所有消息严格按照FIFO进行消息发布和消费的场景。</span> </section> <h3 data-sourcepos="80:1-80:20" style="margin: 10px auto 5px;font-weight: bold;font-size: 20px;letter-spacing: 0.578px;white-space: normal;background-color: rgb(252, 252, 252);"><span style="margin-top: -1px;padding: 6px 20px 6px 10px;font-size: 17px;font-weight: normal;display: inline-block;line-height: 1.3;background-color: rgb(212, 224, 250);border-bottom-right-radius: 100px;color: rgb(30, 30, 30);">2.2 分区顺序</span></h3> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">对于指定的一个Topic,所有消息按</span> <code style="font-family: ui-monospace, SFMono-Regular, Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;font-size: 13.6px;padding: 0.14em 0.3em;vertical-align: 5%;background-color: rgba(27, 31, 35, 0.05);border-radius: 3px;border: 1px solid rgb(223, 226, 229);"><span style="font-size: 15px;">sharding key</span></code> <span style="font-size: 15px;">进行区块(queue)分区,同一Queue内的消息严格按FIFO发布和消费。</span> </section> <ul data-sourcepos="84:1-85:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">Sharding key是顺序消息中用来区分不同分区的关键字段,和普通消息的Key完全不同。</span> </section></li> </ul> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <img class="rich_pages wxw-img" data-imgfileid="100019170" data-ratio="0.32037037037037036" src="/upload/08e5a81e25c02f16ba46f7e4af2f630e.png" data-type="png" data-w="1080" style="box-sizing: content-box;border-style: none;background-color: rgb(255, 255, 255);"> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">落地到RocketMQ。而MessageQueue是RocketMQ存储消息的最小单元,他们之间的消息都是互相隔离的,在这种情况下,是无法保证消息全局有序的,但是可以保证局部有序。</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">默认的做法是,发送消息时,会通过MessageQueue轮询的方式保证消息尽量均匀分布到所有的MessageQueue上,而消费者也就同样需要从多个MessageQueue上消费消息。这就做不到局部有序。</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">对于局部有序的要求,只需要将有序的一组消息都存入同一个MessageQueue里,这样MessageQueue的FIFO设计天生就可以保证这一组消息的有序。</span> </section> <h4 data-sourcepos="98:1-98:17" style="margin: 10px auto -1px;padding-left: 3px;font-weight: bold;font-size: 18px;letter-spacing: 0.578px;white-space: normal;border-left-width: 10px;border-left-style: solid;border-left-color: rgb(222, 235, 255);"><span style="margin-top: -1px;padding-top: 6px;padding-right: 5px;padding-left: 5px;font-size: 16px;display: inline-block;line-height: 1.1;">适用场景</span></h4> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">性能要求高,根据消息中的sharding key去决定消息发送到哪个queue。</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">其实大部分的MQ业务场景,我们只需要保证局部有序就可以了。</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">例如</span> </section> <ul data-sourcepos="108:1-115:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="margin-top: 16px;margin-bottom: 16px;line-height: 1.75em;"> <span style="font-size: 15px;">我们用QQ聊天,只需要保证一个聊天窗口里的消息有序就可以了。</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="margin-top: 16px;margin-bottom: 16px;line-height: 1.75em;"> <span style="font-size: 15px;">而对于电商订单场景,也只要保证一个订单的所有消息是有序的就可以了。</span> </section></li> </ul> <h3 data-sourcepos="116:1-116:14" style="margin: 10px auto 5px;font-weight: bold;font-size: 20px;letter-spacing: 0.578px;white-space: normal;background-color: rgb(252, 252, 252);"><span style="margin-top: -1px;padding: 6px 20px 6px 10px;font-size: 17px;font-weight: normal;display: inline-block;line-height: 1.3;background-color: rgb(212, 224, 250);border-bottom-right-radius: 100px;color: rgb(30, 30, 30);">2.3 对比</span></h3> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;"><strong>发送方式对比</strong></span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <img class="rich_pages wxw-img" data-imgfileid="100019168" data-ratio="0.22685185185185186" src="/upload/78e55d580bc179434463b35372b2ffe8.png" data-type="png" data-w="1080" style="box-sizing: content-box;border-style: none;background-color: rgb(255, 255, 255);"> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;"><strong>存储方式对比</strong></span> </section> <ul data-sourcepos="124:1-125:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">无</span> </section></li> </ul> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;"><strong>消费方式对比</strong></span> </section> <ul data-sourcepos="128:1-133:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="margin-top: 16px;margin-bottom: 16px;line-height: 1.75em;"> <span style="font-size: 15px;">有序消费的消费者类型:ConsumeMessageConcurrentlyService 并发消费服务</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="margin-top: 16px;margin-bottom: 16px;line-height: 1.75em;"> <span style="font-size: 15px;">无序消费的消费者类型:ConsumeMessageOrderlyService</span> </section></li> </ul> <h2 data-sourcepos="134:1-134:65" style="font-weight: bold;font-size: 22px;margin: 10px auto 5px;border-top-width: 1px;border-top-style: solid;border-top-color: rgb(242, 242, 242);background-color: rgb(242, 242, 242);"><span style="margin-top: -1px;padding-top: 14px;padding-bottom: 14px;padding-right: 5px;padding-left: 5px;font-size: 17px;border-top: 4px solid rgb(33, 33, 34);display: inline-block;line-height: 1.5;font-weight: normal;background-color: rgb(30, 30, 30);border-bottom-right-radius: 100px;color: rgb(255, 255, 255);padding-right: 20px;padding-left: 10px;">3 应用开发维度的实现消息有序需要做的工作:</span></h2> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">实现顺序消息所必要的条件:顺序发送、顺序存储、顺序消费。 顺序存储环节,RocketMQ 里的分区队列 MessageQueue 本身是能保证 FIFO 的。</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">所以,在应用开发过程中,不能顺序消费消息主要有两个原因:</span> </section> <ul data-sourcepos="140:1-142:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>顺序发送环节,消息发生没有序</strong>:Producer 发送消息到 MessageQueue 时是轮询发送的,消息被发送到不同的分区队列,就不能保证 FIFO 了。</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>顺序消费环节,消息消费无序</strong>:Consumer 默认是多线程并发消费同一个 MessageQueue 的,即使消息是顺序到达的,也不能保证消息顺序消费。</span> </section></li> </ul> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">我们知道了实现顺序消息所必要的条件:顺序发送、顺序存储、顺序消费。</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">顺序存储 由 Rocketmq 完成,所以,在应用开发层, 消息的顺序需要由两个阶段保证:</span> </section> <ul data-sourcepos="147:1-149:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">消息发送有序</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">消息消费有序</span> </section></li> </ul> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100019172" data-ratio="0.6680455015511892" data-s="300,640" src="/upload/9a5f20285c39517e08be63397cd88fc3.png" data-type="png" data-w="967" style=""></p> <h2 data-sourcepos="154:1-154:39" style="font-weight: bold;font-size: 22px;margin: 10px auto 5px;border-top-width: 1px;border-top-style: solid;border-top-color: rgb(242, 242, 242);background-color: rgb(242, 242, 242);"><span style="margin-top: -1px;padding-top: 14px;padding-bottom: 14px;padding-right: 5px;padding-left: 5px;font-size: 17px;border-top: 4px solid rgb(33, 33, 34);display: inline-block;line-height: 1.5;font-weight: normal;background-color: rgb(30, 30, 30);border-bottom-right-radius: 100px;color: rgb(255, 255, 255);padding-right: 20px;padding-left: 10px;">4:canal+MQ,如何实现有序?</span></h2> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">Canal 是阿里巴巴开源的一个增量订阅和消费的中间件,用于基于 MySQL 的数据库增量日志解析(Binlog)。通过 Canal,可以实现对数据库的实时监控和数据同步。</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">我们在通过Canal把MySQL的Binlog数据发送到MQ(kafak/rocketmq)时,需要关注好几个环节:</span> </section> <ul data-sourcepos="160:1-166:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">Cannal 的有序监听。 Binlog本身是有序的,写入到mq之后如何保障顺序</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">Cannal 的有序发送。</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">顺序存储: broker 存储时保持 顺序一致</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">顺序消费: 消费时 保持 顺序一致</span> </section></li> </ul> <h3 data-sourcepos="167:1-167:30" style="margin: 10px auto 5px;font-weight: bold;font-size: 20px;letter-spacing: 0.578px;white-space: normal;background-color: rgb(252, 252, 252);"><span style="margin-top: -1px;padding: 6px 20px 6px 10px;font-size: 17px;font-weight: normal;display: inline-block;line-height: 1.3;background-color: rgb(212, 224, 250);border-bottom-right-radius: 100px;color: rgb(30, 30, 30);">4.1 Cannal 的有序发送</span></h3> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">canal目前支持kafka和rocketmq,在使用 Canal 进行数据同步时,保证数据的有序性是一个重要的问题,尤其是在分布式环境中。在 Kafka 或 RocketMQ 等消息队列中,消息的顺序性和分区策略至关重要。</span> </section> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">canal 本质上都是基于本地文件的方式来支持分区级别的顺序消息,也就是binlog写入mq是可以有一定的顺序性保障,这个保障级别取决于用户的两个配置项:</span> </section> <ul data-sourcepos="173:1-177:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <code style="font-family: ui-monospace, SFMono-Regular, Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;font-size: 13.6px;padding: 0.14em 0.3em;vertical-align: 5%;background-color: rgba(27, 31, 35, 0.05);border-radius: 3px;border: 1px solid rgb(223, 226, 229);"><span style="font-size: 15px;">canal.mq.partitionsNum</span></code> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <code style="font-family: ui-monospace, SFMono-Regular, Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;font-size: 13.6px;padding: 0.14em 0.3em;vertical-align: 5%;background-color: rgba(27, 31, 35, 0.05);border-radius: 3px;border: 1px solid rgb(223, 226, 229);"><span style="font-size: 15px;">canal.mq.partitionHash</span></code> </section></li> </ul> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">两个配置项 用于控制消息的分区和顺序。两个配置项介绍如下:</span> </section> <ol data-sourcepos="180:1-186:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>canal.mq.partitionsNum</strong>:</span> </section></li> <ul data-sourcepos="181:4-182:165" style="padding-left: 2em;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>描述</strong>:设置消息队列的分区数量。</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>作用</strong>:决定了消息在消息队列中被分区的数量。不同的分区可以并行处理,但需要注意同一个分区内的消息是有序的。</span> </section></li> </ul> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>canal.mq.partitionHash</strong>:</span> </section></li> <ul data-sourcepos="184:4-186:0" style="padding-left: 2em;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>描述</strong>:设置消息分区的哈希规则。</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>作用</strong>:用于指定分区的哈希策略,可以根据特定的字段进行分区。例如,可以根据表名、主键等字段进行分区,以保证某些关键数据的有序性。</span> </section></li> </ul> </ol> <h3 data-sourcepos="187:1-187:36" style="margin: 10px auto 5px;font-weight: bold;font-size: 20px;letter-spacing: 0.578px;white-space: normal;background-color: rgb(252, 252, 252);"><span style="margin-top: -1px;padding: 6px 20px 6px 10px;font-size: 17px;font-weight: normal;display: inline-block;line-height: 1.3;background-color: rgb(212, 224, 250);border-bottom-right-radius: 100px;color: rgb(30, 30, 30);">4.2 Cannal 的有序发送示例</span></h3> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">假设我们希望将数据同步到 Kafka,并且需要保证某张表的数据是有序的,可以使用以下配置:</span> </section> <pre data-sourcepos="191:1-194:3" style="letter-spacing: normal;text-align: start;font-family: ui-monospace, SFMono-Regular, Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;font-size: 13.6px;margin-bottom: 16px;overflow-wrap: normal;overflow: auto;line-height: 1.45;border-radius: 4px;border: thin solid rgb(224, 224, 224);caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);"> <section style="display: block;overflow-x: auto;padding: 16px;color: rgb(47, 51, 55);background-color: rgb(246, 246, 246);font-family: Menlo, ui-monospace, SFMono-Regular, Consolas, &quot;Liberation Mono&quot;, Courier, &quot;Courier New&quot;, Monaco, monospace, system-ui, ui-serif, ui-rounded;font-size: 13.6px;word-break: normal;border-radius: 3px;overflow-wrap: normal;line-height: 1.75em;"> <span style="font-size: 15px;">canal.mq.partitionsNum=10<br>canal.mq.partitionHash=my_database.my_table:id<br></span> </section></pre> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">配置解释</span> </section> <ol data-sourcepos="198:1-205:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>canal.mq.partitionsNum=10</strong>:</span> </section></li> <ul data-sourcepos="199:4-199:105" style="padding-left: 2em;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">表示消息会被分成 10 个分区。每个分区可以并行处理,从而提高处理效率。</span> </section></li> </ul> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>canal.mq.partitionHash=my_database.my_table</strong></span> </section></li> <ul data-sourcepos="201:4-205:0" style="padding-left: 2em;" class="list-paddingleft-1"> <li> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">针对&nbsp;</span> <code style="font-family: ui-monospace, SFMono-Regular, Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;font-size: 13.6px;padding: 0.14em 0.3em;vertical-align: 5%;background-color: rgba(27, 31, 35, 0.05);border-radius: 3px;border: 1px solid rgb(223, 226, 229);"><span style="font-size: 15px;">my_database.my_table</span></code> <span style="font-size: 15px;">&nbsp;表的数据,根据&nbsp;</span> <code style="font-family: ui-monospace, SFMono-Regular, Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;font-size: 13.6px;padding: 0.14em 0.3em;vertical-align: 5%;background-color: rgba(27, 31, 35, 0.05);border-radius: 3px;border: 1px solid rgb(223, 226, 229);"><span style="font-size: 15px;">id</span></code> <span style="font-size: 15px;">&nbsp;字段进行哈希分区。</span> </section></li> <li style="margin-top: 0.25em;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;">这样可以确保同一个&nbsp;</span> <code style="font-family: ui-monospace, SFMono-Regular, Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;font-size: 13.6px;padding: 0.14em 0.3em;vertical-align: 5%;background-color: rgba(27, 31, 35, 0.05);border-radius: 3px;border: 1px solid rgb(223, 226, 229);"><span style="font-size: 15px;">id</span></code> <span style="font-size: 15px;">&nbsp;的所有变更都进入同一个分区,从而保证该&nbsp;</span> <code style="font-family: ui-monospace, SFMono-Regular, Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;font-size: 13.6px;padding: 0.14em 0.3em;vertical-align: 5%;background-color: rgba(27, 31, 35, 0.05);border-radius: 3px;border: 1px solid rgb(223, 226, 229);"><span style="font-size: 15px;">id</span></code> <span style="font-size: 15px;">&nbsp;的变更顺序不变。</span> </section></li> </ul> </ol> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">通过配置&nbsp;</span> <code style="font-family: ui-monospace, SFMono-Regular, Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;font-size: 13.6px;padding: 0.14em 0.3em;vertical-align: 5%;background-color: rgba(27, 31, 35, 0.05);border-radius: 3px;border: 1px solid rgb(223, 226, 229);"><span style="font-size: 15px;">canal.mq.partitionHash</span></code> <span style="font-size: 15px;">,分区内的消息是有序的,因此只要保证同一实体(如同一行数据)的变更进入同一个分区,就能保证其有序性。</span> </section> <h3 data-sourcepos="210:1-210:30" style="margin: 10px auto 5px;font-weight: bold;font-size: 20px;letter-spacing: 0.578px;white-space: normal;background-color: rgb(252, 252, 252);"><span style="margin-top: -1px;padding: 6px 20px 6px 10px;font-size: 17px;font-weight: normal;display: inline-block;line-height: 1.3;background-color: rgb(212, 224, 250);border-bottom-right-radius: 100px;color: rgb(30, 30, 30);">4.3 Cannal 的使用场景</span></h3> <ul data-sourcepos="212:1-215:0" style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;padding-left: 2em;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>实时数据同步</strong>:将数据库的变更实时同步到其他系统,如搜索引擎、缓存等。</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>数据备份</strong>:实现数据库的实时备份,确保数据的一致性和完整性。</span> </section></li> <li style="margin-top: 0.25em;font-size: 15px;"> <section style="line-height: 1.75em;"> <span style="font-size: 15px;"><strong>事件驱动架构</strong>:在事件驱动架构中,利用 Canal 将数据库变更作为事件发布到消息队列,供其他系统消费。</span> </section></li> </ul> <section style="font-size: 16px;letter-spacing: normal;text-align: start;white-space: normal;margin-bottom: 16px;caret-color: rgb(36, 41, 46);color: rgb(36, 41, 46);font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;;line-height: 1.75em;"> <span style="font-size: 15px;">通过合理配置&nbsp;</span> <code style="font-family: ui-monospace, SFMono-Regular, Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;font-size: 13.6px;padding: 0.14em 0.3em;vertical-align: 5%;background-color: rgba(27, 31, 35, 0.05);border-radius: 3px;border: 1px solid rgb(223, 226, 229);"><span style="font-size: 15px;">canal.mq.partitionsNum</span></code> <span style="font-size: 15px;">&nbsp;和&nbsp;</span> <code style="font-family: ui-monospace, SFMono-Regular, Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;font-size: 13.6px;padding: 0.14em 0.3em;vertical-align: 5%;background-color: rgba(27, 31, 35, 0.05);border-radius: 3px;border: 1px solid rgb(223, 226, 229);"><span style="font-size: 15px;">canal.mq.partitionHash</span></code> <span style="font-size: 15px;">,可以在数据同步中既保证有序性,又提高处理效率。</span> </section> </section>

MySQL 5.7 DDL 与 GH-OST 对比分析

作者:微信小助手

<section style="font-size: 15px;line-height: 1.6;"> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;width: 100%;vertical-align: top;border-left: 3px solid rgb(219, 219, 219);border-bottom-left-radius: 0px;padding-left: 8px;align-self: flex-start;flex: 0 0 auto;"> <section style="color: rgba(0, 0, 0, 0.5);font-size: 14px;text-align: justify;"> <p style="text-wrap: wrap;">作者:来自 vivo 互联网存储研发团队- Xia Qianyong</p> </section> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;"> <section style="display: inline-block;width: 100%;border-width: 1px;border-style: solid;border-color: rgb(160, 160, 160);padding: 10px;"> <section style="text-align: left;"> <section style="text-align: justify;line-height: 1.8;padding-right: 5px;padding-left: 5px;color: rgb(160, 160, 160);"> <p style="text-wrap: wrap;">本文首先介绍MySQL 5.7 DDL以及GH-OST的原理,然后从效率、空间占用、锁阻塞、binlog日志产生量、主备延时等方面,对比GH-OST和MySQL5.7 DDL的差异。</p> </section> </section> <section style="margin-right: 0%;margin-bottom: -5px;margin-left: 0%;text-align: right;line-height: 1;font-size: 5px;transform: translate3d(5px, 0px, 0px);"> <section style="width: 0px;display: inline-block;vertical-align: top;border-bottom: 0.6em solid rgb(160, 160, 160);border-right: 0.6em solid rgb(160, 160, 160);border-top: 0.6em solid transparent !important;border-left: 0.6em solid transparent !important;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>一、背景介绍</p> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">在 MySQL 数据库中,DDL(数据定义语言)操作包括对表结构、索引、触发器等进行修改、创建和删除等操作。由于 MySQL 自带的 DDL 操作可能会阻塞 DML(数据操作语言)写语句的执行,大表变更容易产生主备延时,DDL 变更的速度也不能控制,因此在进行表结构变更时需要非常谨慎。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">为了解决这个问题,可以使用 GitHub 开源的工具 GH-OST。GH-OST 是一个可靠的在线表结构变更工具,可以实现零宕机、低延迟、自动化、可撤销的表结构变更。相比于 MySQL 自带的 DDL 操作,GH-OST 可以在不影响正常业务运行的情况下进行表结构变更,避免了 DDL 操作可能带来的风险和影响。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">通过使用 GH-OST工具,可以对 MySQL 数据库中的表进行在线结构变更,而不会对业务造成太大的影响。同时,GH-OST 工具还提供了多种高级特性,如安全性检测、自动化流程等,可以帮助用户更加高效地进行表结构变更。</p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>二、MySQL5.7几种DDL介绍</p> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">2.1 copy</span></p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p>server层触发创建临时表<br></p></li> <li><p>server层对源表加MDL锁,阻塞DML写、不阻塞DML读</p></li> <li><p>server层从源表中逐行读取数据,写入到临时表</p></li> <li><p>数据拷贝完成后,升级字典锁,禁止读写</p></li> <li><p>删除源表,把临时表重命名为源表</p></li> </ul> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">MySQL copy方式的DDL变更,数据表的重建(主键、二级索引重建),server层作为中转把从innodb读取数据表,在把数据写到innodb层临时表。简单示意图如下:</p> <p style="text-wrap: wrap;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100015338" data-ratio="0.5751479289940828" data-s="300,640" src="/upload/7ddcf23e189ddcb755851aca47c132db.png" data-type="png" data-w="845" style=""></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">2.2 inplace</span></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>(1)rebuild table</strong></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">需要根据DDL语句创建新的表结构,根据源表的数据和变更期间增量日志,重建新表的主键索引和所有的二级索引。</p> <p style="text-wrap: wrap;"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;width: 100%;vertical-align: top;border-left: 3px solid rgb(65, 95, 255);border-bottom-left-radius: 0px;padding-left: 8px;align-self: flex-start;flex: 0 0 auto;"> <section style="color: rgb(62, 62, 62);text-align: justify;"> <p style="text-wrap: wrap;">Prepare阶段:</p> </section> </section> </section> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p>创建新的临时frm文件</p></li> <li><p>持有EXCLUSIVE-MDL锁,禁止读写</p></li> <li><p>根据alter类型,确定执行方式(copy,online-rebuild,online-norebuild)<br>假如是Add Index,则选择online-norebuild</p></li> <li><p>更新数据字典的内存对象</p></li> <li><p>分配row_log对象记录增量</p></li> <li><p>生成新的临时ibd文件</p></li> </ul> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;width: 100%;vertical-align: top;border-left: 3px solid rgb(65, 95, 255);border-bottom-left-radius: 0px;padding-left: 8px;align-self: flex-start;flex: 0 0 auto;"> <section style="color: rgb(62, 62, 62);text-align: justify;"> <p style="text-wrap: wrap;">ddl执行阶段 :</p> </section> </section> </section> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p>降级EXCLUSIVE-MDL锁,允许读写</p></li> <li><p>扫描old_table的聚集索引每一条记录rec</p></li> <li><p>遍历新表的聚集索引和二级索引,逐一处理各个索引</p></li> <li><p>根据rec构造对应的索引项</p></li> <li><p>将构造索引项插入sort_buffer块排序</p></li> <li><p>将sort_buffer块更新到新表的索引上</p></li> <li><p>记录ddl执行过程中产生的增量(记录主键和索引字段)</p></li> <li><p>重放row_log中的操作到新表索引商</p></li> <li><p>重放row_log间产生dml操作append到row_log最后一个Block</p></li> </ul> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;width: 100%;vertical-align: top;border-left: 3px solid rgb(65, 95, 255);border-bottom-left-radius: 0px;padding-left: 8px;align-self: flex-start;flex: 0 0 auto;"> <section style="color: rgb(62, 62, 62);text-align: justify;"> <p style="text-wrap: wrap;">commit阶段 :</p> </section> </section> </section> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p>当前Block为row_log最后一个时,禁止读写,升级到EXCLUSIVE-MDL锁</p></li> <li><p>重做row_log中最后一部分增量</p></li> <li><p>更新innodb的数据字典表</p></li> <li><p>rename临时idb文件,frm文件</p></li> <li><p>增量完成</p></li> </ul> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">MySQL rebuild table方式的DDL,数据不需要通过sever层中转,innodb层自己完成数据表的重建。简单示意图如下:</p> <p style="text-wrap: wrap;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100015339" data-ratio="0.4993939393939394" data-s="300,640" src="/upload/9c84f39c750ed0a011299c10fcfdd8bc.png" data-type="png" data-w="825" style=""></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>(2)build-index</strong></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">需要根据DDL语句创建新的表结构,根据源表的数据和变更期间增量日志,创建新的索引。</p> <p style="text-wrap: wrap;"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;width: 100%;vertical-align: top;border-left: 3px solid rgb(65, 95, 255);border-bottom-left-radius: 0px;padding-left: 8px;align-self: flex-start;flex: 0 0 auto;"> <section style="color: rgb(62, 62, 62);text-align: justify;"> <p style="text-wrap: wrap;">Prepare阶段&nbsp;:</p> </section> </section> </section> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p>持有EXCLUSIVE-MDL锁,禁止读写</p></li> <li><p>根据alter类型,确定执行方式(copy,online-rebuild,online-norebuild)</p></li> <li><p>假如是Add Index,则选择online-norebuild</p></li> <li><p>更新数据字典的内存对象</p></li> <li><p>分配row_log对象记录增量</p></li> </ul> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;width: 100%;vertical-align: top;border-left: 3px solid rgb(65, 95, 255);border-bottom-left-radius: 0px;padding-left: 8px;align-self: flex-start;flex: 0 0 auto;"> <section style="color: rgb(62, 62, 62);text-align: justify;"> <p style="text-wrap: wrap;">ddl执行阶段 :</p> </section> </section> </section> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p>降级EXCLUSIVE-MDL锁,允许读写</p></li> <li><p>扫描old_table的聚集索引每一条记录rec</p></li> <li><p>遍历新表的聚集索引,根据rec构造新的索引数据</p></li> <li><p>将构造索引项插入sort_buffer块排序</p></li> <li><p>将sort_buffer块更新到新表的索引上</p></li> <li><p>记录ddl执行过程中产生的增量(仅记录主键和新索引字段)</p></li> <li><p>重放row_log中的操作到新表索引上</p></li> <li><p>重放row_log间产生dml操作append到row_log最后一个Block</p></li> </ul> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;width: 100%;vertical-align: top;border-left: 3px solid rgb(65, 95, 255);border-bottom-left-radius: 0px;padding-left: 8px;align-self: flex-start;flex: 0 0 auto;"> <section style="color: rgb(62, 62, 62);text-align: justify;"> <p style="text-wrap: wrap;">commit阶段 :</p> </section> </section> </section> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p>当前Block为row_log最后一个时,禁止读写,升级到EXCLUSIVE-MDL锁</p></li> <li><p>重做row_log中最后一部分增量</p></li> <li><p>更新innodb的数据字典表</p></li> <li><p>增量完成</p></li> </ul> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">MySQL rebuild index方式的DDL,数据不需要通过sever层中转,innodb层只需要完成变更二级索引的创建。简单示意图如下:</p> <p style="text-wrap: wrap;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100015342" data-ratio="0.7167182662538699" data-s="300,640" src="/upload/83ace03f6b27f0abfb2c8b2106926a8f.png" data-type="png" data-w="646" style=""><span style="letter-spacing: 0.034em;"></span></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>(3)only modify metadata</strong></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">只修改元数据(.frm文件和数据字典),不需要拷贝表的数据。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100015343" data-ratio="0.6695059625212947" data-s="300,640" src="/upload/65890b663fdef1f0d3dea324df9680f4.png" data-type="png" data-w="587" style=""></p> <p style="text-align: center;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>三、GH-OST</p> </section> </section> <p style="text-wrap: wrap;"><br></p> <section> <p style="text-wrap: wrap;">在GH-OST端,根据DDL语句创建新的表结构,根据源表的数据和增量期间增量日志,重建新表的主键索引和所有的二级索引,最终完成DDL增量。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">主要流程如下:</p> </section> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p>根据DDL语句和源表创建新的表结构<br></p></li> </ul> <p><br></p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p>根据唯一索引(主键索引或者其它唯一索引)</p><p>- 优先应用新增量的binlog到新的表中,需要经过GH-OST把binlog日志转换为sql,然后回放到影子表</p><p>- 其次拷贝源表中的数据到新的表中,表数据拷贝通过sql语句 insert ignore into (select .. from)直接在MySQL实例上执行,无需经过GH-OST中转</p><p><br></p></li> <li><p>数据拷贝完成并应用完binlog后,通过lock table write 锁住源表</p><p><br></p></li> <li><p>应用数据完成-获取到锁期间产生的增量binlog</p><p><br></p></li> <li><p>delete源表,rename影子表为源表,完成数据增量</p></li> </ul> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">GH-OST 进行DDL变更,GH-OST服务通知server层,server层作为中转把从innodb读取数据表,在把数据写到innodb层影子表。并且GH-OST作为中转读取DDL变更期间增量binlog解析成SQL写语句回放到影子表。简单示意图如下:</p> <p style="text-wrap: wrap;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100015344" data-ratio="0.7891373801916933" data-s="300,640" src="/upload/ab4e97b897398cf918c17d0fbde429f2.png" data-type="png" data-w="939" style=""></p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>四、对比分析</p> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">DDL变更执行时长、对磁盘的额外占用(临时数据表+binlog)、锁阻塞时长、主备延时都是执行DDL变更人员比较关心的问题,本章将从从执行效率、占用表空间、锁阻塞、产生binlog日志量、主备延时等方面对MySQL原生的DDL和GH-OST进行对比分析。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.1 执行效率</span></p> <p style="text-wrap: wrap;"><br></p> <section> <p style="text-wrap: wrap;">(1)only modify metadata(正常小于1S)</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">(2)build-index: 数据条目越多、新索引字段越大耗时越多</p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="text-wrap: wrap;">&nbsp;增量日志超过innodb_online_<span style="letter-spacing: 0.034em;">alter_log_</span><span style="letter-spacing: 0.034em;">max_</span></p><p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;">size造成DDL失败</span></p><p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;"></span></p></li> </ul> <p style="text-wrap: wrap;">(3)rebuild table: 数据条目越多、所有索引字段之和越大耗时越多</p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="text-wrap: wrap;">增量日志超过innodb_online<span style="letter-spacing: 0.034em;">_alter_log_max_</span></p><p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;">size造成DDL失败</span></p><p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;"></span></p></li> </ul> <p style="text-wrap: wrap;">(4)copy:数据条目越多,所有索引字段之和越大耗时越多,相对于rebuild table,数据需要从server层中转,所以比rebuild table耗时多</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">(5)GH-OST :数据条目越多,所有索引字段之和越大耗时越多,</p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="text-wrap: wrap;">相对于copy,增量日志数据需要从GH-OST中转,所以比copy耗时多</p></li> <li><p style="text-wrap: wrap;">有各种限流,(主备延时,threads超限延时…),增加耗时</p></li> <li><p style="text-wrap: wrap;">增量期间应用binlog速度如果跟不上业务产生binlog日志的速度,将无法完成增量</p></li> <li> <section> <p style="text-wrap: wrap;">critical 参数还会导致主动退出,例如thread_running</p> </section></li> </ul> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <p>耗时:only modify metadata &lt; build-index &lt; build &lt; copy &lt; GH-OST</p> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.2 占用表空间</span></p> <p style="text-wrap: wrap;"><br></p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li> <section> <p style="text-wrap: wrap;">【<span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;">o</span><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;">nly modify&nbsp;</span><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;">metadata</span>】:忽略</p> </section></li> <li> <section> <p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;">【<span style="font-size: 15px;letter-spacing: 0.51px;text-wrap: wrap;">build-index</span>】:</span><span style="letter-spacing: 0.034em;">额外需要,新增索引字段占用的空间</span></p> </section></li> <li> <section> <p style="text-wrap: wrap;">【<span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;">rebuild-table</span>】:额外需要约两倍的表空间</p> </section></li> <li> <section> <p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;">【<span style="font-size: 15px;letter-spacing: 0.51px;text-wrap: wrap;">copy</span>】:</span><span style="letter-spacing: 0.034em;">额外需要约两倍的表空间</span></p> </section></li> <li> <section> <p style="text-wrap: wrap;">【<span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;">GH-OS</span><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;">T</span>】&nbsp;:临时表占用约两倍的表空间,另外生成影子表会产生大量的binlog日志会占用表空间</p> </section></li> </ul> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <p>占用表空间:&nbsp;only modify metadata &lt;&nbsp;build-index&nbsp;&lt; build = copy &lt; GH-OST</p> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.3 锁阻塞</span></p> <p style="text-wrap: wrap;"><br></p> <section> <p style="text-wrap: wrap;">(1)only modify metadata</p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;">DDL prepare阶段短暂的MDL排他锁,阻塞读写</span></p></li> </ul> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">(2)build-index table</p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="text-wrap: wrap;">DDL prepare阶段短暂的MDL排他锁,阻塞读写</p></li> <li><p style="text-wrap: wrap;">执行阶段(主要耗时阶段),MDL SHARED_UPGRADABLE锁,不阻塞读写</p></li> <li><p style="text-wrap: wrap;">执行阶段的最后会回放增量日志row_log,两个block间隙和最后block,持有源表索引的数据结构锁,会阻塞写</p></li> <li><p style="text-wrap: wrap;">提交阶段,MDL锁升级为排他锁</p></li> <li><p style="text-wrap: wrap;">回放剩余的row_log(执行完成致MDL锁升级期间新增的row_log,持有源表索引的数据结构锁,阻塞读写)</p></li> </ul> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">(3)rebuild-table: 和build-index table一致</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">(4)copy</p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="text-wrap: wrap;">DDL prepare阶段短暂的MDL排他锁,阻塞读写</p></li> <li><p style="text-wrap: wrap;">执行阶段(主要耗时阶段),阻塞写,不阻塞读</p></li> </ul> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">(5)GH-OST</p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="text-wrap: wrap;">等待锁的时间也会阻塞业务</p></li> <li><p style="text-wrap: wrap;">进入rename到拿表写锁的间隙有少量的新增binlog,后续需要持锁回放这部分日志</p></li> <li><p style="text-wrap: wrap;">rename表本身的耗时通常1s以内左右</p></li> </ul> <p style="text-wrap: wrap;"><br></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <p><strong>锁阻塞时间:</strong></p> <p>only modify metadata=GH-OST &lt; build-index table = rebuild-table&nbsp; &lt; copy(整个DDL期间都会阻塞业务的写)</p> <p><br></p> <p><strong>锁阻塞分析:</strong></p> <p>MySQL DDL在获取MDL排它锁和GH-OST获取表的的写锁,在获取锁的等待期间都会阻塞业务的读写</p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p>MySQL等待锁的超时时间为MySQL参数innodb_lock_wait_timeout。等待超时则失败</p></li> <li><p>GH-OST等待锁的时间,等待超时时间可配(默认6秒),等待超时次数可配</p></li> </ul> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.4 产生binlog日志量</span></p> <p style="text-wrap: wrap;"><br></p> <section> <p style="text-wrap: wrap;">【<span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;">MySQL5.7 DDL</span>】:&nbsp;在DDL执行结束时仅向binlog中写入一条DDL语句,日志量较小。<br></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">【<span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;">GH-OST</span>】:&nbsp;影子表在全量数据拷贝和增量数据应用过程中产生大量的binlog日志(row模式),对于大表日志量非常大。</p> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <p>产生binlog日志量:MySQL5.7 DDL &lt; GH-OST</p> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.5 主备延时分析</span></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>(1)MySQL5.7 DDL:MySQL集群主备环境</strong></p> <p style="text-wrap: wrap;"><br></p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li> <section> <p style="text-wrap: wrap;">Master上DDL执行完成,binlog提交后,slave才开始进行DDL。</p> </section></li> <li> <section> <p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;">slave串行复制、group复制模式,需要等前面的DDL回放完成后才会进行后续binlog回放,主备延时至少是DDL回放的时间。</span></p> </section></li> </ul> <p style="text-wrap: wrap;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100015345" data-ratio="0.12459546925566344" data-s="300,640" src="/upload/5d0d2ec187792e3cb5bea1d184a668d0.png" data-type="png" data-w="618" style=""></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>(2)GH-OST:主备复制延时基本可以忽略</strong></p> <section> <p style="text-wrap: wrap;"><br></p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="text-wrap: wrap;">GH-OST在master上创建一个影子表,在执行数据拷贝和binlog应用阶段,GHO表的binlog会实时同步到备。</p></li> <li> <section> <p style="text-wrap: wrap;">影子表(_GHO表)应用完成后,通过rename实现新表切换,这个rename动作也会通过binlog传到salve执行完成DDL。</p> </section></li> </ul> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100015346" data-ratio="0.11216429699842022" data-s="300,640" src="/upload/514916c6918e77d95a0720ec819ce827.png" data-type="png" data-w="633" style=""></p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <p><strong>延时时间:GH-OST &lt; MySQL DDL</strong></p> <p><br></p> <p>备库执行DDL期间主库异常,主备切换。备库升级为主过程中,要回放完relaylog中的DDL和dml,才能对外服务,否则会出现数据丢失,这将造成业务较长时间的阻塞。</p> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.6 总结</span></p> <p style="text-wrap: wrap;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100015347" data-ratio="0.47657295850066933" data-s="300,640" src="/upload/ef407bdb4d76423f8d4335c592c57012.jpg" data-type="jpeg" data-w="747" style=""></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">GH-OST 工具和 MySQL 原生 DDL 工具的适用场景不同,具体使用哪种工具需要根据实际需求进行选择。</p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p>变更人员无法判断本次DDL是否会造成DML阻塞、锁阻塞等,建议使用GH-OST工具。</p></li> <li><p>如果需要进行在线表结构变更,并且需要减少锁阻塞时间、减少主备延时等问题,建议使用 GH-OST 工具。</p></li> <li><p>变更只涉及到元数据的修改,建议使用mysql原生DDL。</p></li> <li><p>如果表结构变更较小,对锁阻塞时间和主备延时要求不高,建议使用 MySQL 原生 DDL 工具。</p></li> </ul> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="color: rgb(65, 94, 255);"> <p style="text-wrap: wrap;">参考资料:</p> </section> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li> <section> <p style="text-wrap: wrap;"><a target="_blank" href="https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html#online-ddl-table-operations" textvalue="online DDL Operations" linktype="text" imgurl="" tab="outerlink" data-linktype="2">online DDL Operations</a></p> </section></li> <li> <section> <p style="text-wrap: wrap;"><a target="_blank" href="https://www.bookstack.cn/read/aliyun-rds-core/4bc7183c056a978a.md" textvalue="MySQL · 源码阅读 · 白话Online DDL" linktype="text" imgurl="" tab="outerlink" data-linktype="2"><span style="letter-spacing: 0.034em;">MySQL · 源码阅读 · 白话Online DDL</span></a><span style="letter-spacing: 0.034em;"></span></p> </section></li> <li> <section> <p style="text-wrap: wrap;"><a target="_blank" href="https://cloud.tencent.com/developer/article/1006513" textvalue="【腾讯云CDB】源码分析·MySQL online ddl日志回放解析&nbsp;" linktype="text" imgurl="" tab="outerlink" data-linktype="2">【腾讯云CDB】源码分析·MySQL online ddl日志回放解析&nbsp;</a></p> </section></li> <li> <section> <p style="text-wrap: wrap;"><a target="_blank" href="https://github.com/github/gh-ost/blob/master/doc/requirements-and-limitations.md" textvalue="GH-OST一些使用限制" linktype="text" imgurl="" tab="outerlink" data-linktype="2"><span style="letter-spacing: 0.034em;">GH-OST一些使用限制</span></a><span style="letter-spacing: 0.034em;"></span></p> </section></li> <li> <section> <p style="text-wrap: wrap;"><a target="_blank" href="https://github.com/mysql/mysql-server/blob/trunk/sql/mdl.h" textvalue="mysql mdl锁类型" linktype="text" imgurl="" tab="outerlink" data-linktype="2">mysql mdl锁类型</a></p> <p style="text-wrap: wrap;"><br></p> </section></li> </ul> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><br></p> <section style="margin-right: 0%;margin-bottom: 20px;margin-left: 0%;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;vertical-align: middle;width: 40%;align-self: center;flex: 0 0 auto;"> <section style="margin-top: 0.5em;margin-bottom: 0.5em;"> <section style="border-top: 1px dotted rgb(90, 98, 114);"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> </section> <section style="display: inline-block;vertical-align: middle;width: 20%;align-self: center;flex: 0 0 auto;"> <section style="text-align: center;color: rgb(45, 66, 87);font-size: 11px;"> <p>END</p> </section> </section> <section style="display: inline-block;vertical-align: middle;width: 40%;align-self: center;flex: 0 0 auto;"> <section style="margin-top: 0.5em;margin-bottom: 0.5em;"> <section style="border-top: 1px dotted rgb(90, 98, 114);"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> </section> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: left;"> <section style="padding-left: 1em;padding-right: 1em;display: inline-block;text-align: center;"> <span style="display: inline-block;padding: 0.3em 0.5em;border-radius: 0.5em;background-color: rgb(65, 94, 255);color: rgb(255, 255, 255);" title="" opera-tn-ra-cell="_$.pages:0.layers:0.comps:122.title1"><p>猜你喜欢</p></span> </section> <section style="border-width: 1px;border-style: solid;border-color: transparent;margin-top: -1em;padding: 20px 10px 10px;background-color: rgb(239, 239, 239);text-align: center;"> <section style="font-size: 14px;text-align: left;"> <p><br></p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4NjY4MTU5Nw==&amp;mid=2247498950&amp;idx=1&amp;sn=3cebd18bb2014db6d64e1a63e78bdae4&amp;chksm=ebdb8c54dcac0542affae41abdd7aae3e7fb7df37444ee8fe877a606ae125781de59a9e1c2fb&amp;scene=21#wechat_redirect" textvalue="数据特征采样在 MySQL 同步一致性校验中的实践" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">数据特征采样在 MySQL 同步一致性校验中的实践</a></p></li> <li><p><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4NjY4MTU5Nw==&amp;mid=2247498865&amp;idx=1&amp;sn=b7ae1c85ad5a244b5993b8021edb3f20&amp;chksm=ebdb8ce3dcac05f510a30311151c0bc48caf78c323db37cb1461611b034ff38cfe6dec08cb72&amp;scene=21#wechat_redirect" textvalue="前端生成海报图技术选型与问题解决" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">前端生成海报图技术选型与问题解决</a></p></li> <li><p><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4NjY4MTU5Nw==&amp;mid=2247498813&amp;idx=1&amp;sn=edda3b3d2ddea8e1bd6a98f5365144c3&amp;chksm=ebdb8cafdcac05b9a0d2c3744b8fd8b2b579f0b33e6d8c22fb808f8f86c91704968b3203d9eb&amp;scene=21#wechat_redirect" textvalue="分布式任务调度内的 MySQL 分页查询优化" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">分布式任务调度内的 MySQL 分页查询优化</a></p></li> </ul> </section> </section> </section> <p style="text-wrap: wrap;"><br></p> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzI4NjY4MTU5Nw==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/4g5IMGibSxt45QXJZicZ9gaNU2mRSlvqhQd94MJ7oQh4QFj1ibPV66xnUiaKoicSatwaGXepL5sBDSDLEckicX1ttibHg/0?wx_fmt=png" data-nickname="vivo互联网技术" data-alias="vivoVMIC" data-signature="分享 vivo 互联网技术干货与沙龙活动,推荐最新行业动态与热门会议。" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> <p style="text-wrap: wrap;"><br></p> </section> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>

千万级数据的全表update的正确姿势!

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-bottom: 0px;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 16px;color: rgb(53, 53, 53);line-height: 1.5em;word-spacing: 0.8px;letter-spacing: 0.8px;word-break: break-word;text-align: left;padding: 5px;border-radius: 16px;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">有些时候在进行一些业务迭代时需要我们对Mysql表中数据进行全表update,如果是在数据量比较小的情况下(万级别),可以直接执行sql语句,但是如果数据量达到一个量级后,就会出现一些问题,比如主从架构部署的Mysql,主从同步需要需要binlog来完成,而binlog格式如下,其中使用statement和row格式的主从同步之间binlog在update情况下的展示:</p> <section data-tool="mdnice编辑器" style="overflow-x: auto;"> <table> <thead> <tr> <th style="color: rgb(0, 0, 0);line-height: 1.5em;letter-spacing: 0em;text-align: left;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(240, 240, 240);width: auto;height: auto;border-top-width: 1px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;min-width: 85px;">格式</th> <th style="color: rgb(0, 0, 0);line-height: 1.5em;letter-spacing: 0em;text-align: left;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(240, 240, 240);width: auto;height: auto;border-top-width: 1px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;min-width: 85px;">内容</th> </tr> </thead> <tbody style="line-height: 1.5em;letter-spacing: 0em;border-width: 0px;border-style: initial;border-color: initial;"> <tr style="color: rgb(0, 0, 0);background-attachment: scroll;background-clip: border-box;background-color: rgb(255, 255, 255);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;"> <td style="color: rgb(100, 86, 71);min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">statement</td> <td style="color: rgb(100, 86, 71);min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">记录同步在主库上执行的每一条sql,日志量较少,减少io,但是部分函数sql会出现问题比如random</td> </tr> <tr style="color: rgb(0, 0, 0);background-attachment: scroll;background-clip: border-box;background-color: rgb(248, 248, 248);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;"> <td style="color: rgb(100, 86, 71);min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">row</td> <td style="color: rgb(100, 86, 71);min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">记录每一条数据被修改或者删除的详情,日志量在特定条件下很大,如批量delete、update</td> </tr> <tr style="color: rgb(0, 0, 0);background-attachment: scroll;background-clip: border-box;background-color: rgb(255, 255, 255);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;"> <td style="color: rgb(100, 86, 71);min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">mixed</td> <td style="color: rgb(100, 86, 71);min-width: 85px;border-color: rgba(204, 204, 204, 0.4);border-radius: 0px;">以上两种方式混用,一般的语句修改使用statement记录,其他函数式使用row</td> </tr> </tbody> </table> </section> <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-imgfileid="100051064" data-ratio="0.44814814814814813" src="/upload/418e29a3a8e02829c355d11def39c71b.png" data-type="other" data-w="1080" style="display: block;margin-top: 20px;margin-right: auto;margin-bottom: 20px;margin-left: auto;max-width: 95%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">我们当前线上mysql是使用row格式binlog来进行的主从同步,因此如果在亿级数据的表中执行全表update,必然会在主库中产生大量的binlog,接着会在进行主从同步时,从库也需要阻塞执行大量sql,风险极高,因此直接update是不行的。本文就从我最开始的一个全表update sql开始,到最后上线的分批更新策略,如何优化和思考来展开说明。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);line-height: 1.5em;letter-spacing: 0.5444px;font-weight: bold;display: block;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);visibility: visible;">直接update的问题</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">我们前段时间需要将用户的一些基本信息存储从http转换为https,库中数据大概在几千w的级别,需要对一些大表进行全表update,最开始我试探性的跟dba同事抛出了一个简单的update语句,想着流量低的时候执行,如下:</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewDZfMcOnV7PR7pcM5CzWOZYgGMVwaZlVTI3JXUia3ibTm7V0woQAia9vmEicEUiaiaBbboBvo7YAl6icQYJkZSOobmqQ6/640?wx_fmt=svg&amp;from=appmsg&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;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;">update tb_user_info set user_img=replace(user_img,'http://','https://')<br></code></pre> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);line-height: 1.5em;letter-spacing: 0.5444px;font-weight: bold;display: block;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);visibility: visible;">深度分页问题</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">上面肯定是不合理的会给主库生成binlog、从库接收binlog写数据带来很大的压力,于是就想使用脚本分批处理如下所示:写一个这样的脚本,依次分批替换,limit的游标不断增加。大概一看是没有问题的,但是仔细一想mysql的limit游标进行的范围查找原理,是下沉到B+数的叶子节点进行的向后遍历查找,在limit数据比较小的情况下还好,limit数据量比较大的情况下,效率很低接近于全表扫描,这也就是我们常说的“深度分页问题”。</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewDZfMcOnV7PR7pcM5CzWOZYgGMVwaZlVTI3JXUia3ibTm7V0woQAia9vmEicEUiaiaBbboBvo7YAl6icQYJkZSOobmqQ6/640?wx_fmt=svg&amp;from=appmsg&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;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;">update tb_user_info set user_img=replace(user_img,'http://','https://') limit 1,1000;<br></code></pre> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);line-height: 1.5em;letter-spacing: 0.5444px;font-weight: bold;display: block;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);visibility: visible;">in的效率</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">既然mysql的深分页有问题,那么我就把这批id全部查出来,然后更新的id in这些列表,进行批量更新可以吗?于是我又写了类似下面sql的脚本。结果是还不行,虽然mysql对于in这些查找有一些键值预测,但是仍然是很低效。</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewDZfMcOnV7PR7pcM5CzWOZYgGMVwaZlVTI3JXUia3ibTm7V0woQAia9vmEicEUiaiaBbboBvo7YAl6icQYJkZSOobmqQ6/640?wx_fmt=svg&amp;from=appmsg&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;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;">select&nbsp;*&nbsp;from&nbsp;tb_user_info&nbsp;<span style="color: #e6c07b;line-height: 26px;">where</span>&nbsp;id&gt;&nbsp;{index}&nbsp;<span style="color: #e6c07b;line-height: 26px;">limit</span>&nbsp;100;<br><br>update&nbsp;tb_user_info&nbsp;<span style="color: #e6c07b;line-height: 26px;">set</span>&nbsp;user_img=replace(user_img,<span style="color: #98c379;line-height: 26px;">'http'</span>,<span style="color: #98c379;line-height: 26px;">'https'</span>)<span style="color: #e6c07b;line-height: 26px;">where</span>&nbsp;id&nbsp;<span style="color: #c678dd;line-height: 26px;">in</span>&nbsp;{id1,id3,id2};<br></code></pre> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);line-height: 1.5em;letter-spacing: 0.5444px;font-weight: bold;display: block;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);visibility: visible;">最终版本</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">最终在与dba的多次沟通下,我们写了如下的sql及脚本,这里有几个问题需要注意,我们在select sql中使用了这个语法<code style="font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">/*!40001 SQL_NO_CACHE */</code>,这个语法的意思就是本次查询不使用innodb的buffer pool,也不会将本次查询的数据页放到buffer pool中作为热点数据的缓存。接着对于查询强制使用主键索引<code style="font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);"> FORCE INDEX(</code>PRIMARY<code style="font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">) </code>,并且根据主键索引排序,排序后的数据进行id游标的筛选。最后执行update更新时,由于我们在前面的sql中查询到的就是已经排序后的主键,因此可以对id执行范围查找。</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewDZfMcOnV7PR7pcM5CzWOZYgGMVwaZlVTI3JXUia3ibTm7V0woQAia9vmEicEUiaiaBbboBvo7YAl6icQYJkZSOobmqQ6/640?wx_fmt=svg&amp;from=appmsg&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;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;">select&nbsp;/*!40001&nbsp;SQL_NO_CACHE&nbsp;*/&nbsp;id&nbsp;from&nbsp;tb_user_info&nbsp;FORCE&nbsp;INDEX(`PRIMARY`)&nbsp;<span style="color: #e6c07b;line-height: 26px;">where</span>&nbsp;id&gt;&nbsp;<span style="color: #98c379;line-height: 26px;">"1"</span>&nbsp;ORDER&nbsp;BY&nbsp;id&nbsp;<span style="color: #e6c07b;line-height: 26px;">limit</span>&nbsp;1000,1;<br><br>update&nbsp;tb_user_info&nbsp;<span style="color: #e6c07b;line-height: 26px;">set</span>&nbsp;user_img=replace(user_img,<span style="color: #98c379;line-height: 26px;">'http'</span>,<span style="color: #98c379;line-height: 26px;">'https'</span>)&nbsp;<span style="color: #e6c07b;line-height: 26px;">where</span>&nbsp;id&nbsp;&gt;<span style="color: #98c379;line-height: 26px;">"{1}"</span>&nbsp;and&nbsp;id&nbsp;&lt;<span style="color: #98c379;line-height: 26px;">"{2}"</span>;<br></code></pre> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">我们可以仅关注第一个sql,如下图所示,是buffer pool大概内容,我们可以通过这个no cache的关键字,对批量处理的数据进行强制指定不走buffer pool,不把这些冷数据影响到正常使用的缓存内容,防止效率的降低,其实mysql在一些备份的动作中。使用的数据扫描sql也会带上这个关键字,防止影响到正常的业务缓存;接着需要强制对当前查询指定的主键索引,然后进行排序,否则mysql有可能在计算io成本进行索引选择时,选择其他的索引。</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-imgfileid="100051063" data-ratio="0.2914798206278027" src="/upload/d822b1db045a067e5d4c8e41359a4267.png" data-type="other" data-w="669" style="display: block;margin-top: 20px;margin-right: auto;margin-bottom: 20px;margin-left: auto;max-width: 95%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">使用这样的方式对数据库进行批量更新可以通过一个接口来控制速率,对于数据库主从同步、iops、内存使用率等关键属性进行观察,手动调整刷库速率。这样看是单线程阻塞的操作,其实接口也可以定义线程个数等属性,接口中根据赋予的线程个数,通过线程池并行刷数据,从而提高全表更新速率的上限,同时对速率进行控制控制。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);line-height: 1.5em;letter-spacing: 0.5444px;font-weight: bold;display: block;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);visibility: visible;">其他问题</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">如果我们使用snowflake雪花算法或者自增主键来生成主键id的话,插入的记录都是根据主键id顺序插入的,如果使用uuid这种我们怎么处理?当然是业务中就预先处理了,先把入库的数据提前进行替换,进行代码上线后再进行的全量数据更新了。</p> </section>

MySQL亿级数据平滑迁移实战

作者:微信小助手

<section style="font-size: 15px;"> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;width: 100%;vertical-align: top;border-left: 3px solid rgb(219, 219, 219);border-bottom-left-radius: 0px;padding-left: 8px;align-self: flex-start;flex: 0 0 auto;"> <section style="color: rgba(0, 0, 0, 0.5);font-size: 14px;text-align: justify;"> <p style="text-wrap: wrap;">作者:来自 vivo 互联网服务器团队- Li Gang</p> </section> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;"> <section style="display: inline-block;width: 100%;border-width: 1px;border-style: solid;border-color: rgb(160, 160, 160);padding: 10px;"> <section style="text-align: left;"> <section style="text-align: justify;line-height: 1.8;padding-right: 5px;padding-left: 5px;color: rgb(160, 160, 160);"> <p style="text-wrap: wrap;">本文介绍了一次 MySQL 数据迁移的流程,通过方案选型、业务改造、双写迁移最终实现了亿级数据的迁移。</p> </section> </section> <section style="margin-right: 0%;margin-bottom: -5px;margin-left: 0%;text-align: right;line-height: 1;font-size: 5px;transform: translate3d(5px, 0px, 0px);"> <section style="width: 0px;display: inline-block;vertical-align: top;border-bottom: 0.6em solid rgb(160, 160, 160);border-right: 0.6em solid rgb(160, 160, 160);border-top: 0.6em solid transparent !important;border-left: 0.6em solid transparent !important;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>一、背景</p> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">预约业务是 vivo 游戏中心的重要业务之一。由于历史原因,预约业务数据表与其他业务数据表存储在同一个数据库中。当其他业务出现慢 SQL 等异常情况时,可能会直接影响到预约业务,从而降低系统整体的可靠性和稳定性。为了尽可能提高系统的稳定性和数据隔离性,我们迫切需要将预约相关数据表从原来的数据库中迁移出来,单独建立一个预约业务的数据库。</p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>二、方案选型</p> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">常见的迁移方案大致可以分为以下几类:</p> <section style="line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100015515" data-ratio="0.524074074074074" data-s="300,640" src="/upload/ec8e5886e8cacc3164d6b770e267bf4e.png" data-type="png" data-w="1080" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">而预约业务有以下特点:</p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ul class="list-paddingleft-1" style="padding-left: 40px;list-style-position: outside;"> <li><p>读写场景多,频率高,在用户预约/取消预约/福利发放等场景均涉及到大量的读写。</p></li> <li><p>不可接受停机,停机不可避免的会造成经济损失,在有其他方案的情况下不适合选择此方案。</p></li> <li><p>大部分的场景能接受秒级的数据不一致,少部分不能。</p></li> </ul> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">结合这些特点,我们再评估下上面的方案:</p> <section style="line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100015513" data-ratio="0.2616387337057728" data-s="300,640" src="/upload/bbe082583eec7a72da58a68366f3d3b6.png" data-type="png" data-w="1074" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <section> <p style="text-wrap: wrap;">停机迁移方案需要停机,不适用于预约场景。预约场景存在不活跃的用户数据,如果用渐进式迁移方案的话很难迁移干净,可能还需要再写一个迁移任务来辅助完成迁移。而双写方案最大的优势在于每一步操作都可向上回滚,能尽可能的保证业务不出问题。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">因此,最终选择的是双写方案。预约业务涉及到的读写场景多,每一个场景单独进行改造的成本大,采用 Mybatis 插件来实现迁移所需的双写等功能,可以有效降低改造成本。</p> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>三、前期准备</p> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">3.1 全量同步&amp;增量同步&amp;一致性校验</span></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">这几步使用了公司提供的数据同步工具。全量同步基于 MySQLDump 实现;增量同步基于 binlog 实现;一致性校验通过在新老库各选一个分块,然后聚合列数据计算并对比其特征值实现。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">3.2 代码改造</span></p> <p style="text-wrap: wrap;"><br></p> <section> <p style="text-wrap: wrap;">引入了新库,那自然就需要在项目里新建数据源,并创建表对应的 Mybatis Mapper 类。这里有一个小细节需要注意,Mybatis 默认的 BeanNameGenerator 是</p> <p style="text-wrap: wrap;">&nbsp;AnnotationBeanNameGenerator,它会使用类名作为 BeanName 注册到 Spring 的 ioc 容器中,Spring 启动时如果发现有了两个重名 Bean 就会启动失败,笔者这里给 Mybatis 设置了一个新的 BeanNameGenerator ,使用类的全路径名作为 BeanName 解决了问题。</p> </section> <p style="text-wrap: wrap;"><br></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="c"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> <span class="code-snippet__class"><span class="code-snippet__keyword">class</span> <span class="code-snippet__title">FullPathBeanNameGenerator</span> <span class="code-snippet__title">implements</span> <span class="code-snippet__title">BeanNameGenerator</span> {</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;@<span class="code-snippet__function">Override</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__keyword">public</span> String <span class="code-snippet__title">generateBeanName</span><span class="code-snippet__params">(BeanDefinition definition, BeanDefinitionRegistry registry)</span> </span>{</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__keyword">return</span> definition.getBeanClassName();</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <section style="color: rgb(182, 182, 182);"> <p style="text-align: center;text-wrap: wrap;"><span style="font-size: 13px;color: rgb(62, 62, 62);">(左右滑动查看更多)</span></p> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">还有一点是主键 id,本次预约迁移需要保证新老库主键 id 一致,预约业务没做分库分表,id 都是直接用 MySQL 的自增 id,没有用 id 生成器之类的中间件。因此插入新表时只需要使用插入老表后 Mybatis 自动设置好的 id 即可,这次迁移前先检查了一遍业务代码,确保插入语句都用了 Mybatis 的 useGeneratedKeys 功能来自动设置 id。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">3.3 插件实现</span></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">Mybatis 插件可以拦截 SQL 语句执行过程中的某一点进行干预和处理,而 Executor 是 Mybatis 中负责执行 SQL 语句的核心组件。我们可以对 Executor 的 update 和 query 方法进行代理以实现迁移所需的功能。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">插件需要为读写场景分别实现以下功能:<br></p> <section style="line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100015514" data-ratio="0.5376344086021505" data-s="300,640" src="/upload/5ae385d8ec274b60cbaf9e49b1d4c3a2.png" data-type="png" data-w="930" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <section> <p style="text-wrap: wrap;">考虑到开关切换部分的代码逻辑较为简单,因此在下文中,笔者将不再过多介绍该部分的具体实现,而是着重介绍如何在插件中使用老库的执行语句来访问新的数据库。此外,代码里会涉及到 Mybatis 相关的一些概念,由于网上已经有较多详尽的资料,这里就不再赘述。</p> <p style="text-wrap: wrap;"><br></p> </section> <p style="text-wrap: wrap;">迁移插件代理了 Executor 的 query 和 update 方法,首先在插件里获取到当前执行的 SQL 语句所在的 Mapper 路径。<br></p> <p style="text-wrap: wrap;"><br></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="c"><code><span class="code-snippet_outer">@Intercepts(</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;{</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;@Signature(type = Executor.class, method = <span class="code-snippet__string">"update"</span>, args = {MappedStatement.class, Object.class}),</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;@Signature(type = Executor.class, method = <span class="code-snippet__string">"query"</span>, args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;@Signature(type = Executor.class, method = <span class="code-snippet__string">"query"</span>, args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer">)</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> <span class="code-snippet__class"><span class="code-snippet__keyword">class</span> <span class="code-snippet__title">AppointMigrateInterceptor</span> <span class="code-snippet__title">implements</span> <span class="code-snippet__title">Interceptor</span> {</span></span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;@<span class="code-snippet__function">Override</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__keyword">public</span> Object <span class="code-snippet__title">intercept</span><span class="code-snippet__params">(Invocation invocation)</span> throws Throwable </span>{</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;Object[] args = invocation.getArgs();</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__comment">// Mybatis插件代理的Executor的update或者query方法,第一个参数就是MappedStatement</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;MappedStatement ms = (MappedStatement) args[<span class="code-snippet__number">0</span>];</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;SqlCommandType sqlCommandType = ms.getSqlCommandType();</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;String id = ms.getId();</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__comment">// 从MappedStatement id中获取对应的Mapper接口文件全路径</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;String sourceMapper = id.substring(<span class="code-snippet__number">0</span>, id.lastIndexOf(<span class="code-snippet__string">"."</span>));</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__comment">// ...</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; </span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__comment">// ...</span></span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <section style="text-align: center;font-size: 13px;color: rgb(62, 62, 62);"> <p>(左右滑动查看更多)</p> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">得到老库 Mapper 路径后,将其转换为新库 Mapper 路径,再使用 Class.forName 获取到新库 Mapper 类,然后用新库的 sqlSessionFactory 开启 sqlSession,再获取反射调用所需的方法、对象、参数,在新库上执行语句。</p> <p style="text-wrap: wrap;"><br></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="c"><code><span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">protected</span> Object <span class="code-snippet__title">invoke</span><span class="code-snippet__params">(Invocation invocation, TableConfiguration tableConfiguration)</span> throws NoSuchMethodException, InvocationTargetException, IllegalAccessException </span>{</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__comment">// 获取 MappedStatement</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;MappedStatement ms = (MappedStatement) invocation.getArgs()[<span class="code-snippet__number">0</span>];</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__comment">// 获取 Mybatis 封装好的入参,封装函数 MapperMethod.convertArgsToSqlCommandParam(Object[] args)</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;Object parameter = invocation.getArgs()[<span class="code-snippet__number">1</span>];</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__comment">// 使用 Class.forName 获取到的新库 Mapper</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;Class&lt;?&gt; targetMapperClass = tableConfiguration.getTargetMapperClazz();</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__comment">// 使用新库的 sqlSessionFactory 创建 sqlSession</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;SqlSession sqlSession = sqlSessionFactory.openSession();</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;Object result = null;</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__keyword">try</span>{</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__comment">// 使用新库的 Mapper 路径获取对应的 MapperProxy 对象</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;Object mapper = sqlSession.getMapper(targetMapperClass);</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__comment">// 将 Mybatis 封装好的参数转换为原始参数</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;Object[] paramValues = getParamValue(parameter);</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__comment">// 使用 mappedStatement Id 从新库对应的 Mapper 里获取对应的方法</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;Method method = getMethod(ms.getId(), targetMapperClass, paramValues);</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;paramValues = fixNullParam(method, paramValues);</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__comment">// 反射调用新库 Mapper 的方法,本质上执行的是 MapperProxy.invoke</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;result = method.invoke(mapper, paramValues);</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;} finally {</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;sqlSession.close();</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__keyword">return</span> result;</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__keyword">private</span> Object[] fixNullParam(Method method, Object[] paramValues) {</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__keyword">if</span> (method.getParameterTypes().length &gt; <span class="code-snippet__number">0</span> &amp;&amp; paramValues.length == <span class="code-snippet__number">0</span>) {</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__keyword">return</span> <span class="code-snippet__keyword">new</span> Object[]{null};</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__keyword">return</span> paramValues;</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <section style="color: rgb(62, 62, 62);text-align: center;font-size: 13px;"> <p>(左右滑动查看更多)</p> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">上述代码里,getMethod 方法负责从新库 Mapper 类里找到对应的方法,以用于后续的反射调用。</p> <p style="text-wrap: wrap;"><br></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="c"><code><span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">private</span> Method <span class="code-snippet__title">getMethod</span><span class="code-snippet__params">(String id, Class mapperClass)</span> throws NoSuchMethodException </span>{</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__comment">//获取参数对应的 class</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;String methodName = id.substring(id.lastIndexOf(<span class="code-snippet__string">"."</span>) + <span class="code-snippet__number">1</span>);</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;String key = id;</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__comment">// methodCache 用来缓存 MappedStatement 和对应的 Method,避免每次都从 Mapper 里查找</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;Method method = methodCache.get(key);</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__keyword">if</span> (method == null){</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;method = findMethodByMethodSignature(mapperClass, methodName);</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__keyword">if</span> (method == null){</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__keyword">throw</span> <span class="code-snippet__keyword">new</span> NoSuchMethodException(<span class="code-snippet__string">"No such method "</span> + methodName + <span class="code-snippet__string">" in class "</span> + mapperClass.getName());</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;methodCache.put(key,method);</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__keyword">return</span> method;</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> Method <span class="code-snippet__title">findMethodByMethodSignature</span><span class="code-snippet__params">(Class mapperClass,String methodName)</span> throws NoSuchMethodException </span>{</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__comment">// mybatis 的 Mapper 内的方法不支持重载,所以这里只要方法名匹配到了就行,不用进行参数的匹配</span></span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;Method method = null;</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__keyword">for</span> (Method m : mapperClass.getMethods()) {</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__keyword">if</span> (m.getName().equals(methodName)) {</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;method = m;</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__keyword">break</span>;</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__keyword">return</span> method;</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <section style="text-align: center;font-size: 13px;color: rgb(62, 62, 62);"> <p>(左右滑动查看更多)</p> </section> <p style="text-wrap: wrap;"><br></p> <section style="text-align: left;"> <p>得到方法后,还需要得到反射调用所需的参数。Mybatis 执行到 Executor.update/query 方法时,参数已经经过&nbsp; &nbsp;MapperMethod.convertArgsToSqlCommandParam(Object[] args) 方法封装,不能直接用来执行&nbsp; MapperProxy.invoke ,需要转换后才可用。下图是<span style="letter-spacing: 0.034em;">MapperMethod.convertArgsToSqlCommandParam(Object[] args) 的封装过程,而下面的 getParamValue 是这个函数的逆过程。</span></p> </section> <p style="text-wrap: wrap;"><br></p> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100015517" data-ratio="0.5777777777777777" data-s="300,640" src="/upload/0ec345a51996da533f738a27e48eb066.png" data-type="png" data-w="1080" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="c"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">private</span> Object[] getParamValue(Object parameter) {</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;List&lt;Object&gt; paramValues = <span class="code-snippet__keyword">new</span> ArrayList&lt;&gt;();</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__keyword">if</span> (parameter instanceof Map) {</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;Map&lt;String, Object&gt; paramMap = (Map&lt;String, Object&gt;) parameter;</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__keyword">if</span> (paramMap.containsKey(<span class="code-snippet__string">"collection"</span>)) {</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;paramValues.add(paramMap.get(<span class="code-snippet__string">"collection"</span>));</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;} <span class="code-snippet__keyword">else</span> <span class="code-snippet__keyword">if</span> (paramMap.containsKey(<span class="code-snippet__string">"array"</span>)) {</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;paramValues.add(paramMap.get(<span class="code-snippet__string">"array"</span>));</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;} <span class="code-snippet__keyword">else</span> {</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__keyword">int</span> count = <span class="code-snippet__number">1</span>;</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__keyword">while</span> (count &lt;= paramMap.size() / <span class="code-snippet__number">2</span>){</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__keyword">try</span> {</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;paramValues.add(paramMap.get(<span class="code-snippet__string">"param"</span>+(count++)));</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}<span class="code-snippet__keyword">catch</span> (BindingException e){</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="code-snippet__keyword">break</span>;</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;} <span class="code-snippet__keyword">else</span> <span class="code-snippet__keyword">if</span> (parameter != null){</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp; &nbsp; &nbsp;paramValues.add(parameter);</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;}</span></code><code><span class="code-snippet_outer"> &nbsp; &nbsp;<span class="code-snippet__keyword">return</span> paramValues.toArray();</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p style="text-align: center;text-wrap: wrap;"><span style="font-size: 13px;color: rgb(62, 62, 62);">(左右滑动查看更多)</span></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">通过上述流程,我们就能使用 Mybatis 插件拦截老库的执行过程,实现迁移所需的读写数据源切换/新老库查询结果对比/先写老库再异步写新库等功能。</p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>四、双写流程</p> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.1 上线双写改造后的业务代码,上线时只读写老库</span></p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ol class="list-paddingleft-1" style="padding-left: 40px;list-style-position: outside;"> <li><p>读开关:只读老库<br></p></li> <li><p>写开关:只写老库<br></p></li> <li><p>新老库查询结果对比开关:关</p></li> </ol> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;">此时业务仍只读写老库。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.2 使用公司中间件平台提供的数据工具同步老库数据到新库</span></p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ol class="list-paddingleft-1" style="padding-left: 40px;list-style-position: outside;"> <li><p>读开关:只读老库</p></li> <li><p>写开关:只写老库</p></li> <li><p>新老库查询结果对比开关:关</p></li> </ol> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <section> <p style="text-wrap: wrap;">第1步和第2步并没有严格的顺序要求,只要在切换为双写前做完第1步和第2步就好。</p> <p style="text-wrap: wrap;font-size: 15px;"><br></p> <p style="text-wrap: wrap;">条件允许的情况下,全量+增量同步时应选择不对外提供服务的离线从库作为数据源,避免主从延迟等问题对线上业务造成影响。</p> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.3 停止同步程序,然后开启双写</span></p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ol class="list-paddingleft-1" style="padding-left: 40px;list-style-position: outside;"> <li><p>读开关:只读老库<span style="letter-spacing: 0.034em;">(开启查询结果对比开关)</span></p></li> <li><p>写开关:双写</p></li> <li><p>新老库查询结果对比开关:开</p></li> </ol> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <section> <p style="text-wrap: wrap;">老库追上新库后,对数据做一次全量校验,避免出现数据不一致的情况。此外还需要开启新老库查询结果对比开关,通过日志监控观察新老库的查询结果是否一致。</p> <p style="text-wrap: wrap;"><br></p> </section> <section> <p style="text-wrap: wrap;">停止数据同步和切换双写之间必然有时间差,如果先开启双写再停止数据同步,则可能出现插入重复数据或数据被覆盖的情况。因此需要对数据同步工具和迁移插件进行改造,以处理数据异常的情况,但是这样改造需要处理的情况较多,改造成本较高。所以这里选择先停止同步,再切换到双写,中间丢失的数据使用对比&amp;补偿任务恢复,由于此时仍然全量读老库,所以对业务不会有影响。需要注意的是,双写阶段的时间不应太长,只要确保新老库数据一致就应该前进到下一步。</p> <p style="text-wrap: wrap;"><br></p> </section> <p style="text-wrap: wrap;">这一步在实际操作过程中需要注意以下情况:</p> <p style="text-wrap: wrap;"><br></p> <section style="color: rgb(65, 94, 255);"> <p style="text-wrap: wrap;">4.3.1 自增主键</p> </section> <p style="text-wrap: wrap;"><br></p> <section> <p style="text-wrap: wrap;">预约业务新库的主键 id 需要和老库保持一致,因此在迁移前检查了一遍业务代码,确保插入语句都用了 Mybatis 的 useGeneratedKeys 功能来返回 id ,这样插入新库时可以直接用设置好 id 的对象。但是这里有一个问题,批量插入时 Mybatis 自动设置的 id 和数据库生成的自增主键不一定完全一致,比如批量 insert ignore 和 on duplicate key update 语句。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">这个问题和 useGeneratedKeys 的实现有关,代码可参考</p> <p style="text-wrap: wrap;">com.mysql.jdbc.StatementImpl#getGeneratedKeysInternal(long) 函数,以下是其执行逻辑:</p> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ol class="list-paddingleft-1" style="padding-left: 40px;list-style-position: outside;"> <li><p>Mybatis 执行完插入语句后,MySQL 会返回这次插入影响的数据行数,注意,使用 insert ignore 插入时,忽略的那部分数据不会加到影响的行数上。</p></li> <li><p>Mybatis 使用 SELECT LAST_INSERT_ID() 查询这次插入的最小 id 。</p></li> <li><p>Mybatis 循环遍历插入时用的对象列表,循环的最大次数为第1步里获取的这次插入影响的行数,使用 n 代表当前的循环次数,列表中的每个对象的 id 被赋值为 LAST_INSERT_ID() + n*AUTO_INCREMENT 。</p></li> </ol> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">举例来说,假设老库的某张表里有数据 b ,其 id=1,此时往该表使用 insert ignore 批量插入三条数据 a,b,c,其在表内的 id 为 a:2、b:1、c:3,返回的影响行数为2,SELECT LAST_INSERT_ID() 返回的是2,因此 Mybatis 往对象里设置的主键分别为 a:2、b:3、c:null,再使用这个设置好 id 的对象列表插入新库时会导致新老库 id 不一致。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">解决方案:由于直接删除 ignore 会改变这条 SQL 的语义,无法通过修改语句来解决问题。所以我们只能在迁移插件里跳过这条语句,使其固定写入老库。然后在业务层单独对其进行迁移改造,将插入新库的流程修改为先使用 id 以外的唯一键查询一次老库的数据,获取到 id 以后设置到对象列表里,再插入新库。</p> </section> <p style="text-wrap: wrap;"><br></p> <section style="color: rgb(65, 94, 255);"> <p style="text-wrap: wrap;">4.3.2 事务</p> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">预约业务有部分逻辑用到了事务,但这部分逻辑在双写期间均可以暂停功能,因此迁移插件没有实现事务的支持。如果需要支持业务的话可以不依赖插件,在业务层单独对那部分代码进行改造。</p> <p style="text-wrap: wrap;"><br></p> <section style="color: rgb(65, 95, 255);"> <p style="text-wrap: wrap;">4.3.3 异步写入新库引起的问题</p> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">双写过程中是异步写新库,需要重点关注是否会有线程安全问题。举例来说,假设有个业务需要往表里插入一个列表,插入完列表后又对列表进行了修改,比如执行了 List.clear() 函数或者其中的对象发生了变更,由于是异步写新库,所以实际的执行流程可能如下:</p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ol class="list-paddingleft-1" style="padding-left: 40px;list-style-position: outside;"> <li><p>老库 insert(list)</p></li> <li><p>list.clear()</p></li> <li><p>新库 insert(list)</p></li> </ol> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">这会导致新库执行操作时,传入的对象和老库执行操作时不一样,导致新老库数据不一致。建议在迁移前人为的确认业务逻辑,避免异步写入导致新老库数据不一致。</p> <p style="text-wrap: wrap;"><br></p> <section style="color: rgb(0, 0, 0);direction: ltr;"> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.4 开启对比和补偿程序,补偿切换开关的过程中遗失的数据</span></p> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ol class="list-paddingleft-1" style="padding-left: 40px;list-style-position: outside;"> <li><p>读开关:只读老库(对比开关开启)</p></li> <li><p>写开关:双写</p></li> <li><p>新老库查询结果对比开关:开</p></li> <li><p>对比&amp;补偿任务:开启</p></li> </ol> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100015516" data-ratio="0.24166666666666667" data-s="300,640" src="/upload/045391035ce92f19320d4f370bc1890b.png" data-type="png" data-w="1080" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">该对比&amp;补偿任务有一个缺陷,其不能处理数据被删除的情况,如果老库里的数据被删除但是新库的数据删除失败,那使用更新时间区间就无法从老库查出这条数据,自然也无法进行对比&amp;补偿。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">双写期间,如果出现删老库成功但是删新库失败的情况会有日志告警,所以不会有问题。但是停止数据同步工具 → 开启双写开关这一过程中删除的数据无法补偿。不过大部分业务用的都是逻辑删除,只有一处用了物理删除,笔者在这一处添加了日志,如果切换过程中出现删除数据的日志,就需要手动进行补偿操作。实际操作过程中,开关的切换的耗时较短,只花了30秒左右,在这过程中没有打印删除数据的日志。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.5 逐步切量请求到新库上</span></p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ol class="list-paddingleft-1" style="padding-left: 40px;list-style-position: outside;"> <li><p>读开关:部分读新库 → 只读新库</p></li> <li><p>写开关:双写</p></li> <li><p>新老库查询结果对比开关:开</p></li> <li><p>对比&amp;补偿任务:开启</p></li> </ol> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">双写时,由于数据先写入老库再异步写入新库,因此新库的数据肯定会滞后于老库。如果将一部分读流量切换到新库上,就可能会在一些对延迟要求较高的业务场景中出现问题。对于这种场景,我们不能采用逐步切量的策略,只能同时切换读写开关,将其修改为只写老库+只读新库。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.6 停止对比补偿程序,关闭双写,读写都切换到新库,开启反向补偿任务</span></p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ol class="list-paddingleft-1" style="padding-left: 40px;list-style-position: outside;"> <li><p>读开关:只读新库</p></li> <li><p>写开关:只写新库</p></li> <li><p>新老库查询结果对比开关:关</p></li> <li><p>对比&amp;补偿任务:开启反向补偿</p></li> </ol> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">反向补偿是从新库补偿数据到老库,由于该任务是定时执行,开启后,新库和老库的数据会有 1~2 分钟的延迟,万一写新库的逻辑有问题,可以切回老库。至于为什么用反向补偿任务而不是使用先写新库再异步写老库的策略,是因为双写是用 MyBatis 插件实现的,插件代理的是 excutor 的 update 和 query 方法,如果异步写入老库,有可能会发生以下情况:</p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="margin-bottom: -2.25em;margin-right: 5px;background-color: rgb(247, 247, 247);"> <section style="padding: 10px;margin-bottom: 5px;"> <section style="text-align: left;"> <ol class="list-paddingleft-1" style="padding-left: 40px;list-style-position: outside;"> <li><p>假设有两个线程,业务线程 A 需要写入一条数据,迁移插件拦截后,先同步写入新库,写完新库后提交任务给线程 B 中异步写入老库,提交完任务后插件立刻返回。</p></li> <li><p>由于插件已返回结果,executor 上层的 sqlsession 调用 close() 方法关闭 executor (见 org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke ),此时线程 B 可能还没执行完写老库的操作。</p></li> <li><p>线程 B 执行过程中,由于 executor 已经关闭,导致其写老库失败。</p></li> </ol> </section> </section> </section> <section style="margin-left: auto;width: 2.25em;height: 2.25em;border-right: 5px solid transparent;border-bottom: 5px solid transparent;"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> <p style="text-wrap: wrap;">因此无法使用 Mybatis 插件来实现异步写老库。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><span style="font-size: 16px;color: rgb(65, 95, 255);">4.7 停止反向补偿任务,删除表迁移相关代码</span></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">停止反向补偿前,需要关注是否还有业务在读老库。观察一段时间,确认老库没有补偿任务以外的读写流量后,可以关闭补偿任务,清理迁移过程中产生的代码,清理老库数据。</p> <p style="text-wrap: wrap;"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>五、总结</p> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">在进行数据表迁移的过程中,虽然遇到了一些问题,但是制定的方案中每一步都有回退措施,即使出现问题也不会影响业务的正常运行。此外,笔者在迁移过程中对各种异常情况进行了监控,能及时发现并解决问题。如果其他业务需要进行类似的迁移,需要关注以下几个方面:</p> <p style="text-wrap: wrap;"><br></p> <ol class="list-paddingleft-1" style="padding-left: 40px;list-style-position: outside;"> <li><p>迁移插件实现:在对迁移过程进行反思后,笔者人为通过代理或重写 MapperProxy 的方式来实现迁移插件可能是更加合理的方案。这种方案有两个优点:<span style="letter-spacing: 0.034em;">一方面,可以避免处理 Mybatis 复杂的参数转换流程,从而减少潜在的错误和异常;</span><span style="letter-spacing: 0.034em;">另一方面,可以实现先写新库再异步写老库的操作。</span><span style="letter-spacing: 0.034em;">但是这个方案没有经过实践,还不能确定是否有可行性。</span></p></li> <li><p>自增主键:需要确定业务是否需要保证新老库的 id 一致。</p></li> <li><p>事务:双写过程中应该结合业务考虑是否需要实现事务支持。本次迁移过程中,我们暂停了部分需要事务支持的业务。</p></li> <li><p>异步写入:先写老库再异步写入新库的方式可能导致新老库数据不一致,迁移插件自身无法解决这个问题,只能人工提前排查可能存在的隐患。</p></li> </ol> <p style="text-wrap: wrap;"><br></p> <section style="margin-right: 0%;margin-bottom: 20px;margin-left: 0%;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;vertical-align: middle;width: 40%;align-self: center;flex: 0 0 auto;"> <section style="margin-top: 0.5em;margin-bottom: 0.5em;"> <section style="border-top: 1px dotted rgb(90, 98, 114);"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> </section> <section style="display: inline-block;vertical-align: middle;width: 20%;align-self: center;flex: 0 0 auto;"> <section style="text-align: center;color: rgb(45, 66, 87);font-size: 11px;"> <p>END</p> </section> </section> <section style="display: inline-block;vertical-align: middle;width: 40%;align-self: center;flex: 0 0 auto;"> <section style="margin-top: 0.5em;margin-bottom: 0.5em;"> <section style="border-top: 1px dotted rgb(90, 98, 114);"> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> </section> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: left;"> <section style="padding-left: 1em;padding-right: 1em;display: inline-block;text-align: center;"> <span style="display: inline-block;padding: 0.3em 0.5em;border-radius: 0.5em;background-color: rgb(65, 94, 255);color: rgb(255, 255, 255);" title="" opera-tn-ra-cell="_$.pages:0.layers:0.comps:140.title1"><p>猜你喜欢</p></span> </section> <section style="border-width: 1px;border-style: solid;border-color: transparent;margin-top: -1em;padding: 20px 10px 10px;background-color: rgb(239, 239, 239);text-align: center;"> <section style="font-size: 14px;text-align: left;"> <p><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4NjY4MTU5Nw==&amp;mid=2247499108&amp;idx=1&amp;sn=6904feec20d690a2f102795f22c7bc65&amp;chksm=ebdb8df6dcac04e039af9a47569217b2bbc87bb5b68c8f668baed8a8cd1e12e5fb7e03c395fa&amp;scene=21#wechat_redirect" textvalue="基于 Three.js 的 3D 模型加载优化" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">MySQL 5.7 DDL 与 GH-OST 对比分析</a></p> <p><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4NjY4MTU5Nw==&amp;mid=2247498950&amp;idx=1&amp;sn=3cebd18bb2014db6d64e1a63e78bdae4&amp;chksm=ebdb8c54dcac0542affae41abdd7aae3e7fb7df37444ee8fe877a606ae125781de59a9e1c2fb&amp;scene=21#wechat_redirect" textvalue="数据特征采样在 MySQL 同步一致性校验中的实践" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">数据特征采样在 MySQL 同步一致性校验中的实践</a></p> <p><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4NjY4MTU5Nw==&amp;mid=2247498320&amp;idx=1&amp;sn=c0180b1567a8b362c3c23c51528dfb7d&amp;chksm=ebdb8ac2dcac03d4a3113cf75191bbf134746649184986b161fc521f4e340e1753ad6afbf744&amp;scene=21#wechat_redirect" textvalue="vivo 在离线混部探索与实践" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">vivo 在离线混部探索与实践</a></p> </section> </section> </section> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzI4NjY4MTU5Nw==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/4g5IMGibSxt45QXJZicZ9gaNU2mRSlvqhQd94MJ7oQh4QFj1ibPV66xnUiaKoicSatwaGXepL5sBDSDLEckicX1ttibHg/0?wx_fmt=png" data-nickname="vivo互联网技术" data-alias="vivoVMIC" data-signature="分享 vivo 互联网技术干货与沙龙活动,推荐最新行业动态与热门会议。" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> <p style="text-wrap: wrap;"><br></p> </section> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>

39个MySQL性能优化策略,赶紧收藏!

作者:微信小助手

<section class="mp_profile_iframe_wrp" data-mpa-powered-by="yiban.io"> <mp-common-profile class="custom_select_card mp_profile_iframe mp_common_widget" 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"></mp-common-profile> </section> <section> <span style="color: rgb(62, 62, 62);font-family: Helvetica, Arial, sans-serif;font-size: 15px;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);">给大家汇总了一份MySQL优化的清单清单,是目前我能想到一些优化点以及这么多年的踩坑总结。虽然大家对此并不陌生,但肯定有你平常想不到的,我尽可能的给大家整理出了一份较全的总结并给大家一一举例详解,希望做到温故而知新。</span> </section> <section data-role="outer" label="edit by 135editor"> <section data-role="paragraph" style="outline: 0px;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);visibility: visible;font-family: sans-serif;"> <p style="outline: 0px;"><br></p> </section> <section data-tools="135编辑器" data-id="86122" data-color="#138bde" data-custom="#138bde" style="outline: 0px;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);font-family: sans-serif;"> <section style="margin-top: 0.5em;margin-bottom: 0.5em;outline: 0px;"> <section style="padding-bottom: 2px;outline: 0px;text-align: center;color: rgb(255, 255, 255);background-color: rgb(19, 139, 222);"> <section style="padding: 0.5em 1.5em;outline: 0px;border-bottom: 1px solid rgb(254, 254, 254);border-top-color: rgb(19, 139, 222);border-right-color: rgb(19, 139, 222);border-left-color: rgb(19, 139, 222);"> <p data-brushtype="text" style="outline: 0px;line-height: 20px;"><span style="outline: 0px;font-family: Helvetica, Arial, sans-serif;"><strong style="outline: 0px;">一般语句优化</strong></span></p> </section> </section> </section> </section> <section data-role="paragraph"> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">先从一般的语句优化开始,其实对于很多规范大家并不陌生,可就是在用的时候,无法遵从,希望今天大家再过一遍,可以养成一种良好的数据库编码习惯。</span></p> <p><br></p> <p><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">选择合适的数据类型及字符集</span></strong></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">使用合适的数据类型可以减少存储空间和提高查询速度。这个可不能小看,数据量到达一个量级,这个就能看出明显差异。</span></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">例子:对于布尔值使用 TINYINT(1) 而不是 CHAR(1) 比如你有一个字段是表示业务状态或者是类型。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">CREATE</span> <span class="code-snippet__keyword">TABLE</span> <span class="code-snippet__keyword">users</span> (</span></code><code><span class="code-snippet_outer"> is_active <span class="code-snippet__built_in">TINYINT</span>(<span class="code-snippet__number">1</span>)</span></code><code><span class="code-snippet_outer">);</span></code></pre> </section> <p style="line-height:1.75em;"><span style="color: rgb(62, 62, 62);font-family: Helvetica, Arial, sans-serif;font-size: 15px;letter-spacing: 0.544px;"><br></span></p> <p style="line-height:1.75em;"><span style="color: rgb(62, 62, 62);font-family: Helvetica, Arial, sans-serif;font-size: 15px;letter-spacing: 0.544px;">对于仅存储英文的表,使用 latin1 而不是 utf8mb4。</span><br></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">CREATE</span> <span class="code-snippet__keyword">TABLE</span> messages (</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">content</span> <span class="code-snippet__built_in">VARCHAR</span>(<span class="code-snippet__number">255</span>) <span class="code-snippet__built_in">CHARACTER</span> <span class="code-snippet__keyword">SET</span> latin1</span></code><code><span class="code-snippet_outer">);</span></code></pre> </section> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">避免使用SELECT *</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">仅选择必要的列,减少数据传输量。</span></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">例子:避免 SELECT *,改用具体列名。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SELECT</span> <span class="code-snippet__keyword">id</span>, <span class="code-snippet__keyword">name</span>, email <span class="code-snippet__keyword">FROM</span> <span class="code-snippet__keyword">users</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">合理使用JOIN、避免子查询</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">避免过多的 JOIN 操作,尽量减少数据集的大小。</span></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">例子:优化连接条件,确保连接列上有索引。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SELECT</span> * <span class="code-snippet__keyword">FROM</span> <span class="code-snippet__keyword">users</span> u</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">JOIN</span> orders o <span class="code-snippet__keyword">ON</span> u.id = o.user_id</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">WHERE</span> u.status = <span class="code-snippet__string">'active'</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">尽量使用 JOIN 或者 EXISTS 代替子查询。</span></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">例子:避免使用子查询,改用 JOIN。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SELECT</span> u.name, o.amount</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">FROM</span> <span class="code-snippet__keyword">users</span> u</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">JOIN</span> orders o <span class="code-snippet__keyword">ON</span> u.id = o.user_id;</span></code></pre> </section> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">使用UNION代替OR、优化ORDER BY和GROUP BY</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">确保 ORDER BY 和 GROUP BY 的列上有索引。</span></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">例子:在排序和分组列上添加索引。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">CREATE</span> <span class="code-snippet__keyword">INDEX</span> idx_order_date <span class="code-snippet__keyword">ON</span> orders (order_date);</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SELECT</span> * <span class="code-snippet__keyword">FROM</span> orders <span class="code-snippet__keyword">ORDER</span> <span class="code-snippet__keyword">BY</span> order_date;</span></code></pre> </section> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">在业务允许的情况下,使用 UNION 代替 OR 条件。</span></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">例子:用两个查询的 UNION 代替一个带 OR 的查询。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SELECT</span> <span class="code-snippet__keyword">id</span>, <span class="code-snippet__keyword">name</span> <span class="code-snippet__keyword">FROM</span> <span class="code-snippet__keyword">users</span> <span class="code-snippet__keyword">WHERE</span> <span class="code-snippet__keyword">status</span> = <span class="code-snippet__string">'active'</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">UNION</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SELECT</span> <span class="code-snippet__keyword">id</span>, <span class="code-snippet__keyword">name</span> <span class="code-snippet__keyword">FROM</span> <span class="code-snippet__keyword">users</span> <span class="code-snippet__keyword">WHERE</span> <span class="code-snippet__keyword">status</span> = <span class="code-snippet__string">'pending'</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">避免使用%开头的LIKE查询</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">避免使用 % 开头的 LIKE 查询,因为不能使用索引。</span></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">例子:使用全文本搜索代替 LIKE '%keyword%'。也就是让%在最后面</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SELECT</span> * <span class="code-snippet__keyword">FROM</span> products <span class="code-snippet__keyword">WHERE</span> description <span class="code-snippet__keyword">LIKE</span> <span class="code-snippet__string">'keyword%'</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">这个尤其重要,相信各位在各大平台网站上。很多搜索只有输入前面的字才能有结果,你输入中间的字,会查询不到,其实就是这个原理。</span></p> <p><br></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">使用批量插入、优化INSERT操作</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">使用批量插入减少插入操作的开销。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">INSERT</span> <span class="code-snippet__keyword">INTO</span> <span class="code-snippet__keyword">users</span> (<span class="code-snippet__keyword">name</span>, email) <span class="code-snippet__keyword">VALUES</span> (<span class="code-snippet__string">'Alice'</span>, <span class="code-snippet__string">'alice@example.com'</span>), </span></code><code><span class="code-snippet_outer">(<span class="code-snippet__string">'Bob'</span>, <span class="code-snippet__string">'bob@example.com'</span>);</span></code></pre> </section> <p style="line-height:1.75em;"><span style="color: rgb(62, 62, 62);font-family: Helvetica, Arial, sans-serif;font-size: 15px;letter-spacing: 0.544px;"><br></span></p> <p style="line-height:1.75em;"><span style="color: rgb(62, 62, 62);font-family: Helvetica, Arial, sans-serif;font-size: 15px;letter-spacing: 0.544px;">在批量插入时,关闭唯一性检查和索引更新,插入完成后再开启(此种情况大家可根据业务来,比如当查询很频繁的时候,这样操作会影响查询效率)。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> autocommit=<span class="code-snippet__number">0</span>;</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> unique_checks=<span class="code-snippet__number">0</span>;</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> foreign_key_checks=<span class="code-snippet__number">0</span>;</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">-- 批量插入操作</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> unique_checks=<span class="code-snippet__number">1</span>;</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> foreign_key_checks=<span class="code-snippet__number">1</span>;</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">COMMIT</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><strong style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><strong style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">避免使用HAVING代替WHERE</span></strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"></span></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">在可能的情况下,使用 WHERE 代替 HAVING 进行过滤。</span></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">例子:避免使用 HAVING 过滤。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SELECT</span> user_id, <span class="code-snippet__keyword">COUNT</span>(*) <span class="code-snippet__keyword">FROM</span> orders</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">WHERE</span> order_date &gt; <span class="code-snippet__string">'2020-01-01'</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">GROUP</span> <span class="code-snippet__keyword">BY</span> user_id</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">HAVING</span> <span class="code-snippet__keyword">COUNT</span>(*) &gt; <span class="code-snippet__number">1</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"></span></p> </section> <section data-role="paragraph"> <section data-tools="135编辑器" data-id="86122" data-color="#138bde" data-custom="#138bde" style="outline: 0px;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);font-family: sans-serif;"> <section style="margin-top: 0.5em;margin-bottom: 0.5em;outline: 0px;"> <section style="padding-bottom: 2px;outline: 0px;text-align: center;color: rgb(255, 255, 255);background-color: rgb(19, 139, 222);"> <section style="padding: 0.5em 1.5em;outline: 0px;border-bottom: 1px solid rgb(254, 254, 254);border-top-color: rgb(19, 139, 222);border-right-color: rgb(19, 139, 222);border-left-color: rgb(19, 139, 222);"> <p data-brushtype="text" style="outline: 0px;line-height: 20px;"><span style="outline: 0px;font-family: Helvetica, Arial, sans-serif;"><strong style="outline: 0px;">配置参数调优</strong></span></p> </section> </section> </section> </section> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">该部分主要针对MySQL的配置做一些操作,这块还是相当重要的,虽然是运维领域,但熟悉MySQL的配置是我们研发的不可不会的领域。</span></p> <p><br></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">调整innodb_buffer_pool_size</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">innodb_buffer_pool_size 是 InnoDB 存储引擎最重要的配置参数之一,用于指定 InnoDB 缓冲池的大小。缓冲池用于缓存数据页、索引页和 InnoDB 表的其它信息。合理设置这个参数对数据库性能有很大影响。</span></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">增大 InnoDB 缓冲池大小,提高缓存命中率。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> <span class="code-snippet__keyword">GLOBAL</span> innodb_buffer_pool_size = <span class="code-snippet__number">2</span>G;</span></code></pre> </section> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">但是这里要注意 该值并不是越大越好。innodb_buffer_pool_size 应该设置要尽可能大,但要确保为操作系统和其他应用程序留出足够的内存。</span></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">一般建议在数据库专用服务器上设置为物理内存的 60% 到 80%。通过监控数据库性能和内存使用情况,可以进一步调整这个参数以优化数据库性能。</span></p> <p><br></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">调整query_cache_size</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">query_cache_size 是用于指定查询缓存的大小。查询缓存可以缓存 SELECT 查询的结果,避免重复执行相同的查询,从而提高性能。</span></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">然而,在 MySQL 8.0 及更高版本中,查询缓存已经被完全移除。如果你使用的是 MySQL 8.0 及以上版本,可以忽略 query_cache_size 参数。</span></p> <p><br></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">调整thread_cache_size</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">增大线程缓存大小,减少线程创建开销。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> <span class="code-snippet__keyword">GLOBAL</span> thread_cache_size = <span class="code-snippet__number">100</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">调整table_open_cache</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">增大表缓存大小,减少表打开的开销。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> <span class="code-snippet__keyword">GLOBAL</span> table_open_cache = <span class="code-snippet__number">4000</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">调整tmp_table_size和max_heap_table_size</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">增大临时表和堆表的最大大小,减少磁盘 I/O。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> <span class="code-snippet__keyword">GLOBAL</span> tmp_table_size = <span class="code-snippet__number">64</span>M;</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> <span class="code-snippet__keyword">GLOBAL</span> max_heap_table_size = <span class="code-snippet__number">64</span>M;</span></code></pre> </section> <p style="line-height:1.75em;"><strong style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">调整innodb_flush_log_at_trx_commit</span></strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"></span></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">根据需求调整日志刷新策略,权衡性能和数据安全性。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> <span class="code-snippet__keyword">GLOBAL</span> innodb_flush_log_at_trx_commit = <span class="code-snippet__number">2</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">调整innodb_log_file_size</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">增大日志文件大小,减少日志文件切换的开销。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> <span class="code-snippet__keyword">GLOBAL</span> innodb_log_file_size = <span class="code-snippet__number">256</span>M;</span></code></pre> </section> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">调整innodb_log_buffer_size</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">增大日志缓冲区大小,提高写入性能。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> <span class="code-snippet__keyword">GLOBAL</span> innodb_log_buffer_size = <span class="code-snippet__number">16</span>M;</span></code></pre> </section> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">调整innodb_io_capacity</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">根据磁盘 I/O 性能调整 InnoDB I/O 容量。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> <span class="code-snippet__keyword">GLOBAL</span> innodb_io_capacity = <span class="code-snippet__number">2000</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">调整max_connections</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">增大最大连接数,支持更多并发连接。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> <span class="code-snippet__keyword">GLOBAL</span> max_connections = <span class="code-snippet__number">500</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">调整sort_buffer_size</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">增大排序缓冲区大小,提高排序操作的性能。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> <span class="code-snippet__keyword">GLOBAL</span> sort_buffer_size = <span class="code-snippet__number">4</span>M;</span></code></pre> </section> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">调整read_buffer_size</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">增大读缓冲区大小,提高顺序扫描性能。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SET</span> <span class="code-snippet__keyword">GLOBAL</span> read_buffer_size = <span class="code-snippet__number">2</span>M;</span></code></pre> </section> <p style="line-height:1.75em;"><br></p> <section> <section> <section> <section data-tools="135编辑器" data-id="86122" data-color="#138bde" data-custom="#138bde" style="outline: 0px;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);font-family: sans-serif;"> <section style="margin-top: 0.5em;margin-bottom: 0.5em;outline: 0px;"> <section style="padding-bottom: 2px;outline: 0px;text-align: center;color: rgb(255, 255, 255);background-color: rgb(19, 139, 222);"> <section style="padding: 0.5em 1.5em;outline: 0px;border-bottom: 1px solid rgb(254, 254, 254);border-top-color: rgb(19, 139, 222);border-right-color: rgb(19, 139, 222);border-left-color: rgb(19, 139, 222);"> <p data-brushtype="text" style="outline: 0px;line-height: 20px;"><span style="outline: 0px;font-family: Helvetica, Arial, sans-serif;"><strong style="outline: 0px;">正确使用索引</strong></span></p> </section> </section> </section> </section> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">这块是最重要的,因为假如使用不当,那么创建索引不但没有效果,反而还会成为负担。</span></p> <p><br></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">在常用查询条件和连接条件的列上建立索引</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">这块很清楚,反正只要发现查询较慢,优先检查where条件后面,有没有被创建索引。</span></p> <p><br></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">遵循最左前缀原则</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">这个是针对复合索引时的要求,遵循最左前缀原则。</span></p> <p><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">例子:对于索引 (a, b, c),可以用于 (a),(a, b),(a, b, c) 的查询。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">CREATE</span> <span class="code-snippet__keyword">INDEX</span> idx_abc <span class="code-snippet__keyword">ON</span> table_name (a, b, c);</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SELECT</span> * <span class="code-snippet__keyword">FROM</span> table_name <span class="code-snippet__keyword">WHERE</span> a = <span class="code-snippet__number">1</span> <span class="code-snippet__keyword">AND</span> b = <span class="code-snippet__number">2</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><strong style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">避免在索引列上进行计算</span></strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"></span></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">例子:避免 WHERE YEAR(date) = 2020,改用范围查询。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">SELECT</span> * <span class="code-snippet__keyword">FROM</span> orders <span class="code-snippet__keyword">WHERE</span> <span class="code-snippet__built_in">date</span> <span class="code-snippet__keyword">BETWEEN</span> <span class="code-snippet__string">'2024-06-01'</span> <span class="code-snippet__keyword">AND</span> <span class="code-snippet__string">'2024-06-30'</span>;</span></code></pre> </section> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">避免重复索引</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">检查并删除重复的索引,减少维护开销。了解mysql底层的都知道,创建索引,就会增加一个页,重复索引无疑是给增加负担。</span></p> <p><br></p> <p style="line-height:1.75em;"><strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">更新频繁的列慎用索引</span></strong></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">对于更新频繁的列,索引会增加写操作的开销,需要慎重使用。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">CREATE</span> <span class="code-snippet__keyword">INDEX</span> idx_update_col <span class="code-snippet__keyword">ON</span> table_name (update_col); </span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">-- 如果 update_col 更新频繁,需慎用</span></span></code></pre> </section> <p style="line-height:1.75em;"><strong style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">避免过多的列使用复合索引</span></strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"></span></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">复合索引的列数不要太多,列数过多会增加索引的维护开销,并且可能导致索引文件过大。对此可以拆分为较少复合索引和单个索引</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">CREATE</span> <span class="code-snippet__keyword">INDEX</span> idx_columns <span class="code-snippet__keyword">ON</span> table_name (col1, col2, col3, col4, col5); </span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">-- 列数太多</span></span></code></pre> </section> <p style="line-height:1.75em;"><strong style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"><br></span></strong></p> <p style="line-height:1.75em;"><strong style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;">使用覆盖索引</span></strong><span style="outline: 0px;font-size: 15px;color: rgb(62, 62, 62);line-height: 1.6em;visibility: visible;letter-spacing: 0.544px;font-family: Helvetica, Arial, sans-serif;"></span></p> <p style="line-height:1.75em;"><br></p> <p style="line-height:1.75em;"><span style="outline: 0px;f

less 命令

作者:じ☆ve宝贝

## 一、简介 >` less `是 Linux 和 Unix 系统中常用的一个分页工具,它允许用户查看文本文件的内容,而不是一次显示整个文件。与 more 命令相比,less 提供了更多的功能和灵活性,例如向前和向后浏览文件、搜索文本等。 ## 二、基本用法 使用` less `查看文件内容的基本语法如下: ` less [选项] [文件名] ` 例如,要查看名为 file.txt 的文件,只需输入: ` less file.txt ` 例如,要查看名为 file.txt 的文件,并且要显示行号只需输入: ` less -N file.txt ` ## 三、常用操作 ### 1.导航: * **空格键**:向下滚动一页。 * **b**:向上滚动一页。 * **g**:跳到文件的开头。 * **G**:跳到文件的末尾。 * **箭头键**:上下左右移动。 ### 2.搜索: * **/**:向前搜索。例如,输入 `/pattern` 会从当前位置开始向前搜索 `pattern`。 * **?**:向后搜索。例如,输入 `?pattern` 会从当前位置开始向后搜索 `pattern`。 * **n**:重复上一次的搜索。 * **N**:反方向重复上一次的搜索。 ### 3.退出: * **q**:退出 `less`。 ### 4.其他操作: * **h**:显示帮助屏幕。 * **=**:显示当前行的行号。 * **-**:减少水平滚动的宽度。 * **+**:增加水平滚动的宽度。 * **f**:向前滚动一屏,类似于空格键。 * **r**:刷新屏幕。 * **R**:重新绘制屏幕,丢弃任何缓冲的输入。 ## 四、高级特性 - **多文件查看:** 可以在` less `中查看多个文件。例如,less file1.txt file2.txt 允许您在两个文件之间切换。使用 :n 和 :p 分别切换到下一个和上一个文件。 - **行编辑:** 虽然` less `主要是一个查看器,但它也提供了一些基本的行编辑功能,如剪切、复制和粘贴。这些功能通常与 vim 的编辑命令相似。 - **管道命令的输出:** 您可以将其他命令的输出通过管道传递给` less `进行查看。例如,ls -l |` less `将允许您分页查看目录列表。 - **定制显示:** 可以使用环境变量` less `或在命令行中使用 -O 选项来自定义` less `的显示行为,例如设置颜色、行号等。 - **与 vim 集成:** 如果您是 vim 用户,可能会发现` less `的某些快捷键与 vim 相似。实际上,您可以在` less `中设置 vim 风格的键绑定,使导航更加直观。 - **书签功能:** 虽然` less `没有直接的书签功能,但您可以使用 &pattern 在文件中快速定位到匹配的行,这在某种程度上起到了书签的作用。 - **安全性:** 与某些文本查看器不同,less 在处理大型或二进制文件时通常更加稳定和安全,不会意外地修改或损坏文件内容。 - **兼容性:** 由于其简单性和普遍性,less 在几乎所有的 Unix 和 Linux 系统上都可用,使得它成为一个高度可移植的工具。 - **自定义和脚本化:** 尽管` less `本身非常强大,但您还可以通过创建自己的脚本或使用其他工具(如 awk, sed 等)与` less `结合使用来进一步扩展其功能。例如,您可以编写一个脚本,该脚本先对文件进行某种处理,然后再将结果传递给` less `进行查看。 - **与其他命令的集成:** 许多命令行工具(如 man, info 等)实际上在后端使用` less `或类似的分页程序来显示它们的内容。这意味着一旦您熟悉了 less,您就会发现在使用这些工具时也会感到更加舒适。此外,许多环境变量(如 MANPAGER, PAGER 等)都可以设置为 less,使其成为系统默认的分页程序。这意味着无论何时需要分页显示内容(例如在查看手册页或管道命令的输出时),都可以使用您熟悉的` less `界面和命令。这大大提高了在命令行环境中工作和学习的效率。通过使用这些高级特性和与其他命令的集成,您可以充分发挥` less `命令的潜力,并将其变成一个强大而灵活的工具,以满足您在 Linux 系统中的各种文本查看和处理需求。 ## 五、总结 尽管现在有许多图形化的文本编辑器和查看器可供选择,但` less `命令在 Linux 系统中仍然占有重要地位。它不仅简单易用,而且功能强大,能够满足大多数用户的需求。通过熟悉` less `的基本操作和高级特性,您将能够更高效地浏览和处理文本文件,从而提升您在 Linux 系统中的工作效率。无论您是初学者还是经验丰富的用户,less 命令都是您值得掌握的强大工具。 ## 六、实践练习 为了更深入地理解` less `命令的使用,以下是一些实践练习的建议: - **创建一个大文件并使用` less `查看:** 创建一个包含大量文本的文本文件,例如一个长篇小说或一篇长篇文章。然后使用` less `命令查看该文件,并尝试使用各种导航和搜索功能来浏览文件内容。 - **使用管道和` less `查看命令输出:** 尝试将其他命令的输出通过管道传递给` less `命令进行查看。例如,使用 ls -l |` less `命令查看目录列表,或者使用 cat largefile.txt |` less `命令查看大文件的内容。 - **定制` less `的显示:** 尝试设置环境变量` less `或使用 -O 选项来自定义` less `的显示行为。例如,设置颜色方案、显示行号等。 - **练习使用快捷键:** 尝试使用各种快捷键来导航和搜索文件内容。例如,使用 /pattern 搜索特定的文本模式,或使用箭头键上下左右移动。 - **使用其他编辑功能:** 尝试使用` less `的其他编辑功能,如剪切、复制和粘贴等。这些功能可以帮助您在查看文件时进行简单的编辑操作。 - **与其他命令集成:** 尝试将其他命令与` less `集成,例如使用` less `查看手册页或使用其他工具对文件进行处理后再用` less `查看结果。 通过这些实践练习,您将更加熟悉` less `命令的使用,并能够更好地发挥其潜力。

证书3月一换很麻烦?一行命令让你解放双手

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-bottom: 0px;padding-left: 10px;padding-right: 10px;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;font-family: Optima, &quot;Microsoft YaHei&quot;, PingFangSC-regular, serif;font-size: 16px;color: rgb(0, 0, 0);line-height: 1.5em;word-spacing: 0em;letter-spacing: 0em;word-break: break-word;text-align: left;"> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;background-attachment: scroll;background-clip: border-box;background-image: url(&quot;https://mmbiz.qpic.cn/sz_mmbiz_png/eVGE8ib7OCj0Ob4Icala8ogOXYib2THLicdFAicDDnus09K9ydlHqZeokftY537DHdYbFww3RZgagL79OjAQvXnODw/640?wx_fmt=png&amp;from=appmsg&quot;);background-origin: padding-box;background-position: 50% 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow: unset; text-shadow: none;transform: none;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style=" font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;overflow: unset; text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">问题</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">目前证书的大部分有效期都是3个月(免费),对于我们就需要每次在各大运营商平台进行重新申请,替换秘钥,虽然快到期的时候会提醒,但是还是很麻烦。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;background-attachment: scroll;background-clip: border-box;background-image: url(&quot;https://mmbiz.qpic.cn/sz_mmbiz_png/eVGE8ib7OCj0Ob4Icala8ogOXYib2THLicdFAicDDnus09K9ydlHqZeokftY537DHdYbFww3RZgagL79OjAQvXnODw/640?wx_fmt=png&amp;from=appmsg&quot;);background-origin: padding-box;background-position: 50% 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow: unset; text-shadow: none;transform: none;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style=" font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;overflow: unset; text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">建议</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><span style="color:red;font-size:18px;"> 如果你的nginx配置文件有多个,比如一个二级域名一个配置文件,我建议使用Certbot的手动模式,这样更好自己去管理</span></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;background-attachment: scroll;background-clip: border-box;background-image: url(&quot;https://mmbiz.qpic.cn/sz_mmbiz_png/eVGE8ib7OCj0Ob4Icala8ogOXYib2THLicdFAicDDnus09K9ydlHqZeokftY537DHdYbFww3RZgagL79OjAQvXnODw/640?wx_fmt=png&amp;from=appmsg&quot;);background-origin: padding-box;background-position: 50% 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow: unset; text-shadow: none;transform: none;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style=" font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;overflow: unset; text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">Certbot简介</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">CertbotCertbot 是一个由 Electronic Frontier Foundation(电子前线基金会,简称 EFF)开发的开源工具,主要用于自动获取和更新 Let’s Encrypt 颁发的 SSL/TLS 证书。它能够帮助网站管理员轻松地为其网站配置 HTTPS,提供更安全的数据传输。</p> <h3 data-tool="mdnice编辑器" style=" margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset; text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; "><span style="background-attachment: scroll;background-clip: border-box;background-image: url(&quot;https://mmbiz.qpic.cn/sz_mmbiz_png/eVGE8ib7OCj0Ob4Icala8ogOXYib2THLicdjzaIQy01EPCXU8hicic6rUzcdVJzDfdR86icyAhZhJEicXTAYdahYthV3Q/640?wx_fmt=png&amp;from=appmsg&quot;);background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: 15px 15px;width: 15px;height: 15px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-size: 22px;font-weight: bold;flex-direction: unset;float: unset;justify-content: unset;letter-spacing: 0px;line-height: 1.5em;margin-bottom: -2px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;-webkit-box-reflect: unset;"></span><span style="display: none;"></span><span style=" color: rgb(72, 179, 120);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 8px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">Certbot 的主要功能</span><span style="display: none;"></span></h3> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">自动化获取证书</strong>:Certbot 能够自动与 Let’s Encrypt 交互,验证域名所有权并获取 SSL/TLS 证书。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">自动配置 HTTPS</strong>:Certbot 可以自动配置 Web 服务器(如 Apache、Nginx),使其使用新获得的证书提供 HTTPS 服务。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">自动续期</strong>:Let’s Encrypt 证书的有效期为 90 天,Certbot 会自动在证书到期前更新它们,确保 HTTPS 服务的持续性。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">支持多种操作系统和 Web 服务器</strong>:Certbot 支持多种操作系统(如 Ubuntu、Debian、CentOS 等)和多种 Web 服务器(如 Apache、Nginx)。 </section></li> </ol> <h3 data-tool="mdnice编辑器" style=" margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset; text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; "><span style="background-attachment: scroll;background-clip: border-box;background-image: url(&quot;https://mmbiz.qpic.cn/sz_mmbiz_png/eVGE8ib7OCj0Ob4Icala8ogOXYib2THLicdjzaIQy01EPCXU8hicic6rUzcdVJzDfdR86icyAhZhJEicXTAYdahYthV3Q/640?wx_fmt=png&amp;from=appmsg&quot;);background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: 15px 15px;width: 15px;height: 15px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-size: 22px;font-weight: bold;flex-direction: unset;float: unset;justify-content: unset;letter-spacing: 0px;line-height: 1.5em;margin-bottom: -2px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;-webkit-box-reflect: unset;"></span><span style="display: none;"></span><span style=" color: rgb(72, 179, 120);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 8px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">使用 Certbot 的基本步骤</span><span style="display: none;"></span></h3> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">安装 Certbot</strong>:根据操作系统和 Web 服务器的类型,使用包管理器或直接下载方式安装 Certbot。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">获取证书</strong>:通过 Certbot 命令行工具,指定域名并获取 SSL/TLS 证书。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">配置 Web 服务器</strong>:Certbot 可以自动配置 Web 服务器,也可以手动配置。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">测试和验证</strong>:确保 Web 服务器正确启用了 HTTPS,并验证证书的有效性。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">自动续期</strong>:Certbot 会定期运行并检查证书的有效期,自动续期证书。 </section></li> </ol> <h3 data-tool="mdnice编辑器" style=" margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset; text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; "><span style="background-attachment: scroll;background-clip: border-box;background-image: url(&quot;https://mmbiz.qpic.cn/sz_mmbiz_png/eVGE8ib7OCj0Ob4Icala8ogOXYib2THLicdjzaIQy01EPCXU8hicic6rUzcdVJzDfdR86icyAhZhJEicXTAYdahYthV3Q/640?wx_fmt=png&amp;from=appmsg&quot;);background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: 15px 15px;width: 15px;height: 15px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-size: 22px;font-weight: bold;flex-direction: unset;float: unset;justify-content: unset;letter-spacing: 0px;line-height: 1.5em;margin-bottom: -2px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;-webkit-box-reflect: unset;"></span><span style="display: none;"></span><span style=" color: rgb(72, 179, 120);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 8px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">适用场景</span><span style="display: none;"></span></h3> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">个人或小型网站</strong>:不需要购买商业证书,使用 Let’s Encrypt 提供的免费证书即可。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">希望自动管理证书的用户</strong>:Certbot 自动化的流程可以节省手动管理证书的时间和精力。 </section></li> </ul> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">总的来说,Certbot 是一个强大且易于使用的工具,能够帮助网站管理员轻松地为其网站配置和管理 SSL/TLS 证书,提高网站的安全性。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;background-attachment: scroll;background-clip: border-box;background-image: url(&quot;https://mmbiz.qpic.cn/sz_mmbiz_png/eVGE8ib7OCj0Ob4Icala8ogOXYib2THLicdFAicDDnus09K9ydlHqZeokftY537DHdYbFww3RZgagL79OjAQvXnODw/640?wx_fmt=png&amp;from=appmsg&quot;);background-origin: padding-box;background-position: 50% 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow: unset; text-shadow: none;transform: none;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style=" font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;overflow: unset; text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">自动配置(可忽略)</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">在CentOS上使用Certbot为Nginx配置SSL证书并设置自动续签的步骤如下:</p> <h3 data-tool="mdnice编辑器" style=" margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset; text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; "><span style="background-attachment: scroll;background-clip: border-box;background-image: url(&quot;https://mmbiz.qpic.cn/sz_mmbiz_png/eVGE8ib7OCj0Ob4Icala8ogOXYib2THLicdjzaIQy01EPCXU8hicic6rUzcdVJzDfdR86icyAhZhJEicXTAYdahYthV3Q/640?wx_fmt=png&amp;from=appmsg&quot;);background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: 15px 15px;width: 15px;height: 15px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-size: 22px;font-weight: bold;flex-direction: unset;float: unset;justify-content: unset;letter-spacing: 0px;line-height: 1.5em;margin-bottom: -2px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;-webkit-box-reflect: unset;"></span><span style="display: none;"></span><span style=" color: rgb(72, 179, 120);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 8px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">1. 安装Certbot</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">首先,确保系统已更新:</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/00GYaClAoOo7hauXibZMFA2TY1XmxxPia6rZiby2BdDdg7nJrz02xenwRYdK8zMbicC2avyGmllqBibkDiamKm0aeQPydywtG8DicK3/640?wx_fmt=svg&amp;from=appmsg&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;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;">sudo&nbsp;yum&nbsp;update&nbsp;-y<br></code></pre> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">然后,安装EPEL(Extra Packages for Enterprise Linux)存储库:</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/00GYaClAoOo7hauXibZMFA2TY1XmxxPia6rZiby2BdDdg7nJrz02xenwRYdK8zMbicC2avyGmllqBibkDiamKm0aeQPydywtG8DicK3/640?wx_fmt=svg&amp;from=appmsg&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;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;">sudo&nbsp;yum&nbsp;install&nbsp;epel-release&nbsp;-y<br></code></pre> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">接下来,安装Certbot和Nginx插件:</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/00GYaClAoOo7hauXibZMFA2TY1XmxxPia6rZiby2BdDdg7nJrz02xenwRYdK8zMbicC2avyGmllqBibkDiamKm0aeQPydywtG8DicK3/640?wx_fmt=svg&amp;from=appmsg&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;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;">sudo&nbsp;yum&nbsp;install&nbsp;certbot&nbsp;python2-certbot-nginx&nbsp;-y<br></code></pre> <h3 data-tool="mdnice编辑器" style=" margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset; text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; "><span style="background-attachment: scroll;background-clip: border-box;background-image: url(&quot;https://mmbiz.qpic.cn/sz_mmbiz_png/eVGE8ib7OCj0Ob4Icala8ogOXYib2THLicdjzaIQy01EPCXU8hicic6rUzcdVJzDfdR86icyAhZhJEicXTAYdahYthV3Q/640?wx_fmt=png&amp;from=appmsg&quot;);background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: 15px 15px;width: 15px;height: 15px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-size: 22px;font-weight: bold;flex-direction: unset;float: unset;justify-content: unset;letter-spacing: 0px;line-height: 1.5em;margin-bottom: -2px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;-webkit-box-reflect: unset;"></span><span style="display: none;"></span><span style=" color: rgb(72, 179, 120);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 8px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">2. 获取SSL证书</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">使用Certbot获取SSL证书。这里假设你的域名是<code style="color: rgb(40, 202, 113);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;">example.com</code>,请替换成你的实际域名:</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/00GYaClAoOo7hauXibZMFA2TY1XmxxPia6rZiby2BdDdg7nJrz02xenwRYdK8zMbicC2avyGmllqBibkDiamKm0aeQPydywtG8DicK3/640?wx_fmt=svg&amp;from=appmsg&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;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;">sudo&nbsp;certbot&nbsp;--nginx&nbsp;-d&nbsp;example.com&nbsp;-d&nbsp;www.example.com<br></code></pre> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">在运行此命令时,Certbot会自动配置Nginx以使用生成的SSL证书。按照提示完成域名验证。</p> <h3 data-tool="mdnice编辑器" style=" margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset; text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; "><span style="background-attachment: scroll;background-clip: border-box;background-image: url(&quot;https://mmbiz.qpic.cn/sz_mmbiz_png/eVGE8ib7OCj0Ob4Icala8ogOXYib2THLicdjzaIQy01EPCXU8hicic6rUzcdVJzDfdR86icyAhZhJEicXTAYdahYthV3Q/640?wx_fmt=png&amp;from=appmsg&quot;);background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: 15px 15px;width: 15px;height: 15px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-size: 22px;font-weight: bold;flex-direction: unset;float: unset;justify-content: unset;letter-spacing: 0px;line-height: 1.5em;margin-bottom: -2px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;-webkit-box-reflect: unset;"></span><span style="display: none;"></span><span style=" color: rgb(72, 179, 120);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 8px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">3. 验证Nginx配置</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">Certbot成功生成证书后,它会自动更新Nginx的配置文件。你可以通过以下命令测试Nginx配置是否正确:</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/00GYaClAoOo7hauXibZMFA2TY1XmxxPia6rZiby2BdDdg7nJrz02xenwRYdK8zMbicC2avyGmllqBibkDiamKm0aeQPydywtG8DicK3/640?wx_fmt=svg&amp;from=appmsg&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;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;">sudo&nbsp;nginx&nbsp;-t<br></code></pre> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">如果配置正确,重启Nginx以应用更改:</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/00GYaClAoOo7hauXibZMFA2TY1XmxxPia6rZiby2BdDdg7nJrz02xenwRYdK8zMbicC2avyGmllqBibkDiamKm0aeQPydywtG8DicK3/640?wx_fmt=svg&amp;from=appmsg&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;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;">sudo&nbsp;systemctl&nbsp;restart&nbsp;nginx<br></code></pre> <h3 data-tool="mdnice编辑器" style=" margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset; text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; "><span style="background-attachment: scroll;background-clip: border-box;background-image: url(&quot;https://mmbiz.qpic.cn/sz_mmbiz_png/eVGE8ib7OCj0Ob4Icala8ogOXYib2THLicdjzaIQy01EPCXU8hicic6rUzcdVJzDfdR86icyAhZhJEicXTAYdahYthV3Q/640?wx_fmt=png&amp;from=appmsg&quot;);background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: 15px 15px;width: 15px;height: 15px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-size: 22px;font-weight: bold;flex-direction: unset;float: unset;justify-content: unset;letter-spacing: 0px;line-height: 1.5em;margin-bottom: -2px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;-webkit-box-reflect: unset;"></span><span style="display: none;"></span><span style=" color: rgb(72, 179, 120);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 8px;overflow: unset; text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">4. 设置自动续签</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">Certbot安装时通常会创建一个定时任务(cron job)来自动续签证书。你可以通过以下命令查看已存在的续签任务:</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background:

单独对 websocket 抽象封装,支撑了公司不同业务的消息即时通讯!

作者:微信小助手

<h1 style="letter-spacing: normal;text-wrap: wrap;text-align: center;line-height: 1.75;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: 17.1px;font-weight: bold;display: table;margin: 2em auto 1em;padding-right: 1em;padding-left: 1em;border-bottom: 2px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">一、背景</h1> <p style="font-family: &quot;Microsoft YaHei&quot;;text-wrap: wrap;line-height: 1.75;font-size: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">公司之前很多涉及到后端需要主动与前端web交互的业务,代码耦合严重,新的业务场景需要即时通信的得重新接入websocket,花费很多时间和精力,因此需要将websocket(缩写为:ws)抽象为公司内部的通讯服务,可以解决业务不同需求,比如:</p> <ol style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;padding-left: 1em;color: rgb(63, 63, 63);" class="list-paddingleft-1"> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>1. 业务采用了轮询方式来获取服务器异步请求的结果(支付回调订单、业务订单)。</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>2. 系统中有部分业务使用了即时推送功能(反扫二维码定时刷新、充电端口加载刷新)。</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>3. 提高系统的响应速率,同步调用重构为异步调用方式,调用结果以websocket方式推送给前端,降低接口延迟性。</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>4. 考虑未来有新的业务需要使用websocket即时通讯支撑。</p></li> </ol> <h1 style="letter-spacing: normal;text-wrap: wrap;text-align: center;line-height: 1.75;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: 17.1px;font-weight: bold;display: table;margin: 2em auto 1em;padding-right: 1em;padding-left: 1em;border-bottom: 2px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">二、目标</h1> <ul style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;padding-left: 1em;list-style: circle;color: rgb(63, 63, 63);" class="list-paddingleft-1"> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>•&nbsp;规范ws通讯工程项目结构和写法。</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>•&nbsp;剔除业务代码,提高接入效率。</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>•&nbsp;使用推送代替不合理的接口轮询。</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>•&nbsp;支撑原有同步调用优化为异步调用,接口响应结果通过ws推送给前端,提高系统的整体响应效率。</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>•&nbsp;使用MQ代替Redis发布订阅和微服务调用</p></li> </ul> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;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: 15px;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">核心设计</h3> <figure style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;margin: 1.5em 8px;color: rgb(63, 63, 63);"> <img class="rich_pages wxw-img" data-imgfileid="100045802" data-ratio="1.4037037037037037" data-type="other" data-w="1080" style="height: auto !important;" src="/upload/6d5899b1610249cd2470c88e8ce41edf.jpg"> </figure> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;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: 15px;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">项目结构</h3> <figure style="letter-spacing: normal;text-wrap: wrap;text-align: center;line-height: 1.75;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: 15px;margin: 1.5em 8px;color: rgb(63, 63, 63);"> <img class="rich_pages wxw-img" data-imgfileid="100045799" data-ratio="1.6261980830670926" data-type="other" data-w="313" style="height: auto !important;" src="/upload/0058c8686366676568e1186fcce4d424.jpg"> </figure> <h1 style="letter-spacing: normal;text-wrap: wrap;text-align: center;line-height: 1.75;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: 17.1px;font-weight: bold;display: table;margin: 2em auto 1em;padding-right: 1em;padding-left: 1em;border-bottom: 2px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">三、业务流程</h1> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;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: 15px;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">3.1 应用关系图</h3> <h4 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;font-weight: bold;margin: 2em 8px 0.5em;color: rgb(250, 81, 81);"><span style="color: rgb(255, 104, 39);">消息推送(Fanout)</span></h4> <figure style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;margin: 1.5em 8px;color: rgb(63, 63, 63);"> <img class="rich_pages wxw-img" data-imgfileid="100045801" data-ratio="0.5899772209567198" data-type="other" data-w="878" style="height: auto !important;" src="/upload/39dba00bb0ce694c539bcdaaab8dd62d.jpg"> </figure> <h4 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;font-weight: bold;margin: 2em 8px 0.5em;color: rgb(250, 81, 81);"><span style="color: rgb(255, 104, 39);">消息接收处理(Topic)</span></h4> <figure style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;margin: 1.5em 8px;color: rgb(63, 63, 63);"> <img class="rich_pages wxw-img" data-imgfileid="100045800" data-ratio="0.58675799086758" data-type="other" data-w="876" style="height: auto !important;" src="/upload/737bcfc217d08abc2b4b7e77bf09ca5f.jpg"> </figure> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;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: 15px;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">3.2 业务时序图</h3> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">从上到下依次为:</p> <ol style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;padding-left: 1em;color: rgb(63, 63, 63);" class="list-paddingleft-1"> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>1.&nbsp;websocket客户端注册,连接流程</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>2.&nbsp;推送消息到服务端流程</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>3.&nbsp;推送消息到客户端流程</p></li> </ol> <figure style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;margin: 1.5em 8px;color: rgb(63, 63, 63);"> <img class="rich_pages wxw-img" data-imgfileid="100045803" data-ratio="1.048" data-type="other" data-w="750" style="height: auto !important;" src="/upload/4e10321b5ddb606db87075e456b281c6.jpg"> </figure> <h1 style="letter-spacing: normal;text-wrap: wrap;text-align: center;line-height: 1.75;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: 17.1px;font-weight: bold;display: table;margin: 2em auto 1em;padding-right: 1em;padding-left: 1em;border-bottom: 2px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">四、如何保证消息的可靠性传输</h1> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">在这个架构的设计过程中,如何保证消息不丢失也是项目的一个重点需要解决的技术问题,对应RabbitMq来说,实现上消息丢失的具体情况主要会分为三种:</p> <ol style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;padding-left: 1em;color: rgb(63, 63, 63);" class="list-paddingleft-1"> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>1. 生成者把消息发送到RabbitMQ Server过程丢失;</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>2. RabbitMQ Server接收到消息后在持久化之前宕机导致消息丢失;</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>3. 消费者接收到消息,在即将消费的时候,业务还未做处理,结果进程挂掉了,这时候RabbitMQ会认为已经消费了,导致消息丢失。</p></li> </ol> <h4 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;font-weight: bold;margin: 2em 8px 0.5em;color: rgb(250, 81, 81);"><span style="color: rgb(255, 104, 39);">✔确保生产者端的可靠性传输方式(两种方式):</span></h4> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">方式一:开启事务机制,生产者在发送消息之前开启RabbitMQ事务,然后在发送消息,如果消息没有成功被RabbitMQ接收到,那边生产者会收到异常报错,此时可以执行事务回滚操作<code style="text-align: left;line-height: 1.75;font-size: 13.5px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;"><span style="color: rgb(255, 104, 39);">channel.txRollback()</span></code>,然后重试发送。</p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">方式二:开启确认机制,RabbitMQ提供了发送方确认机制(<code style="text-align: left;line-height: 1.75;font-size: 13.5px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;"><span style="color: rgb(255, 104, 39);">publisher confirm</span></code>)来确保消息发送成功,关注公众号:取阿里内部Java性能调优手册!如果成功发送到RabbitMQ Server,MQ会给你回传一个ack消息,确保这个消息已经发送成功,如果MQ没有接收处理到这条消息,会回调你的一个nack()接口,告诉你这个消息接收失败,这时候你可以重试。</p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);"><span style="font-style: italic;">两种方式的优缺点分析:</span></p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">事务机制是同步的,你提交一个事务之后会阻塞在那里,但是comfirm机制是异步的,你发送一个消息之后不需要等待上一个消息的回调即可以接着下一个消息的发送,所以整体的性能、效率会更高,因此一般在生产者这快保证消息的可靠性传输,都是采用confirm机制实现。</p> <h4 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;font-weight: bold;margin: 2em 8px 0.5em;color: rgb(250, 81, 81);"><span style="color: rgb(255, 104, 39);">✔MQ Server如何保证消息丢失</span></h4> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">方式:开始MQ的持久化,就是将消息写入持久到磁盘,哪怕是MQ自己挂了,重启之后会激动读取之前储存的数据,保证数据不丢失。</p> <h4 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;font-weight: bold;margin: 2em 8px 0.5em;color: rgb(250, 81, 81);"><span style="color: rgb(255, 104, 39);">✔确保消息者端的可靠性传输</span></h4> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">方式:关闭自动ack,开启手动确认机制,RabbitMQ提供接收方响应机制(<code style="text-align: left;line-height: 1.75;font-size: 13.5px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;"><span style="color: rgb(255, 104, 39);">consumer ack</span></code>)来确保消息成功接收,简单来说就是每次自己在代码里确保业务逻辑处理完之后,在程序中自己ack一把,可以通过调用一个api来实现,如果你还没处理完,就不触发ack,那么RabbitMQ就会认为你还没处理完,这个时候MQ会把这个消息分配给别的consumer做处理,消息是不会丢失的。</p> <figure style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;margin: 1.5em 8px;color: rgb(63, 63, 63);"> <img class="rich_pages wxw-img" data-imgfileid="100045804" data-ratio="0.3450479233226837" data-type="other" data-w="626" style="height: auto !important;" src="/upload/262527404cbe6f232e25e06a4b654f1a.jpg"> </figure> <h1 style="letter-spacing: normal;text-wrap: wrap;text-align: center;line-height: 1.75;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: 17.1px;font-weight: bold;display: table;margin: 2em auto 1em;padding-right: 1em;padding-left: 1em;border-bottom: 2px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">五、消息分类</h1> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;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: 15px;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">5.1 客户端→服务端</h3> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);"><span style="color: rgb(255, 104, 39);"><strong>描述</strong></span></p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">应用场景为客户端主动向服务端推送消息,在服务端执行相应的业务流程。</p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">现有系统中有此应用场景的业务是:反扫二维码请求开始刷新,用户要请求开始刷新启动二维码</p> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;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: 15px;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">5.2 服务端→客户端</h3> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);"><span style="color: rgb(255, 104, 39);"><strong>描述</strong></span></p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">应用场景为服务端触发了某个事件,需要推送相应结果到某个客户端。</p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">现有系统中有此应用场景的业务是:支付完成后,等待第三方服务器回调,回调成功结果推送</p> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;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: 15px;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">5.3 客户端 →客户端</h3> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);"><span style="color: rgb(255, 104, 39);"><strong>描述</strong></span></p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">应用场景为客户端需要向另一客户端推送消息。</p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">现有系统中有此应用场景的业务是:C端用户发送接口请求,推送响应结果到用户H5页面中</p> <h1 style="letter-spacing: normal;text-wrap: wrap;text-align: center;line-height: 1.75;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: 17.1px;font-weight: bold;display: table;margin: 2em auto 1em;padding-right: 1em;padding-left: 1em;border-bottom: 2px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">六、Websocket API设计</h1> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;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: 15px;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">6.1 请求websocket连接token</h3> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">请求方式:GET</p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">统一请求接口url:</p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);"><code style="text-align: left;line-height: 1.75;font-size: 13.5px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;"><span style="color: rgb(255, 104, 39);">域名/xxx/websocket/token</span></code></p> <section> <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="json"><code><span class="code-snippet_outer">{</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__attr">"result"</span>: <span class="code-snippet__number">0</span>,</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__attr">"description"</span>: <span class="code-snippet__string">"无"</span>,</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__attr">"data"</span>:<span class="code-snippet__string">"wss://dws.test.com:8086/socket/asrwqgvsd"</span> //连接url</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> </section> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;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: 15px;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">6.2 使用返回的url连接websocket</h3> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">连接方式: wss</p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">连接url:</p> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);"><code style="text-align: left;line-height: 1.75;font-size: 13.5px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;"><span style="color: rgb(255, 104, 39);">wss://dws.test.com:8086/socket/{token}</span></code></p> <h1 style="letter-spacing: normal;text-wrap: wrap;text-align: center;line-height: 1.75;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: 17.1px;font-weight: bold;display: table;margin: 2em auto 1em;padding-right: 1em;padding-left: 1em;border-bottom: 2px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">七、统一消息体</h1> <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="json"><code><span class="code-snippet_outer">{</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__attr">"sendType"</span>:<span class="code-snippet__string">""</span>,</span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">"messageType"</span>:<span class="code-snippet__string">"消息类型"</span>,</span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">"businessType"</span>:<span class="code-snippet__string">""</span>,</span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">"fromUniqueId"</span>:<span class="code-snippet__string">"发送端唯一id"</span>,</span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">"toUniqueId"</span>:<span class="code-snippet__string">"接收端唯一id"</span>,</span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">"fromClientType"</span>:<span class="code-snippet__string">"发送端类型"</span>,</span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">"toClientType"</span>:<span class="code-snippet__string">"接收端类型"</span>,</span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">"timestamp"</span>:<span class="code-snippet__number">0</span>,</span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">"content"</span>:<span class="code-snippet__string">"业务数据"</span>,</span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">"tags"</span>:[</span></code><code><span class="code-snippet_outer"><span class="code-snippet__string">"标签集"</span></span></code><code><span class="code-snippet_outer">],</span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">"businesses"</span>:[</span></code><code><span class="code-snippet_outer"><span class="code-snippet__string">"业务集"</span></span></code><code><span class="code-snippet_outer">]</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <h1 style="letter-spacing: normal;text-wrap: wrap;text-align: center;line-height: 1.75;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: 17.1px;font-weight: bold;display: table;margin: 2em auto 1em;padding-right: 1em;padding-left: 1em;border-bottom: 2px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">八、统一调用方式</h1> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;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: 15px;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">8.1 Websocket API聚合封装</h3> <figure style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;margin: 1.5em 8px;color: rgb(63, 63, 63);"> <img class="rich_pages wxw-img" data-imgfileid="100045808" data-ratio="1.2824074074074074" data-type="other" data-w="1080" style="height: auto !important;" src="/upload/78f07f76dda58abef4ee56a9194db15d.jpg"> </figure> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;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: 15px;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">8.2 业务统一调用</h3> <figure style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;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: 15px;margin: 1.5em 8px;color: rgb(63, 63, 63);"> <img class="rich_pages wxw-img" data-imgfileid="100045806" data-ratio="0.47685185185185186" data-type="other" data-w="1080" style="height: auto !important;" src="/upload/0f22dd4c4bafa7111fdd344da1451298.jpg"> </figure> <h1 style="letter-spacing: normal;text-wrap: wrap;text-align: center;line-height: 1.75;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: 17.1px;font-weight: bold;display: table;margin: 2em auto 1em;padding-right: 1em;padding-left: 1em;border-bottom: 2px solid rgb(250, 81, 81);color: rgb(63, 63, 63);">总结</h1> <p style="text-wrap: wrap;line-height: 1.75;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: 15px;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">本文主要记录我基于对WebSocket做的抽象统一封装实现消息即时通讯功能的整体设计思想,从项目代码设计上采用了DDD的思想建模,降低了代码的耦合程度,不同业务在需要使用ws即时通讯可以做到“即引即用”的效果,不再需要考虑WebSocket接入底层的配置和逻辑。</p>

解决node: /lib64/libm.so.6: version `GLIBC_2.27‘ not found问题

作者:じ☆ve宝贝

> 由于jenkins服务器是centos7.5的系统,在使用nodejs 20版本打包时,提示node: /lib64/libm.so.6: version 'GLIBC_2.27' not found, 这是因为Node v18开始,需要GLIBC_2.27支持。 ### 查看当前系统支持的GLIBC版本 ``` strings /lib64/libc.so.6 |grep GLIBC_ # 输出结果 GLIBC_2.2.5 …… GLIBC_2.16 GLIBC_2.17 ``` ## 解决方案 ### 替换yum镜像源 - 众所周知 centos7的yum官方源已经失效,因此更换为阿里云的镜像源 ``` # 备份旧的源 cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo sudo yum clean all sudo yum makecache ``` ### 升级GCC 模块 ``` # 升级GCC(默认为4 升级为8) yum install -y centos-release-scl # 此处需要注意,可能centos-release-scl会安装失败导致提示无法找到devtoolset-8-gcc yum install -y devtoolset-8-gcc* mv /usr/bin/gcc /usr/bin/gcc-4.8.5 ln -s /opt/rh/devtoolset-8/root/bin/gcc /usr/bin/gcc mv /usr/bin/g++ /usr/bin/g++-4.8.5 ln -s /opt/rh/devtoolset-8/root/bin/g++ /usr/bin/g++ ``` ### 如果提示无法找到devtoolset-8-gcc的解决方法 ``` cd /etc/yum.repos.d vi CentOS-SCLo-rh.repo # 添加一下内容 [centos-sclo-rh] name=CentOS-7 - SCLo rh baseurl=https://mirrors.aliyun.com/centos/7/sclo/x86_64/rh/ gpgcheck=1 enabled=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo # 添加内容结束 # 执行yum clean 和 重建缓存 sudo yum clean all sudo yum makecache # 执行完成以上步骤 再次执行 yum install -y devtoolset-8-gcc* 即可 ``` ### make 升级版本3升级4 ``` # 执行一下make -v 判断一下当前make版本,如果不是4版本则升级 make -v # 升级make cd /data/soft wget http://ftp.gnu.org/gnu/make/make-4.3.tar.gz tar -xzvf make-4.3.tar.gz && cd make-4.3/ ./configure --prefix=/usr/local/make make && make install cd /usr/bin/ && mv make make.bak ln -sv /usr/local/make/bin/make /usr/bin/make ``` ### 更新glibc - 按照错误提示 下载glibc2.28的包 ``` # 下载 glibc2.28 cd /data/soft wget http://ftp.gnu.org/gnu/glibc/glibc-2.28.tar.gz tar -zxvf glibc-2.28.tar.gz cd glibc-2.28/ && mkdir build && cd build # 如果已经编译过了,但是报错了,要重新执行的话,需要先清空build目录,这个目录自己创建的清空没事,编译目录要保持为空 ../configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin make && make install ## # 结果 LD_SO=ld-linux-x86-64.so.2 CC="gcc -B/usr/bin/" /usr/bin/perl scripts/test-installation.pl /opt/module/mysql-lib/glibc-2.28/build/ /usr/bin/ld: cannot find -lnss_test2 collect2: error: ld returned 1 exit status Execution of gcc -B/usr/bin/ failed! The script has found some problems with your installation! Please read the FAQ and the README file and check the following: - Did you change the gcc specs file (necessary after upgrading from Linux libc5)? - Are there any symbolic links of the form libXXX.so to old libraries? Links like libm.so -> libm.so.5 (where libm.so.5 is an old library) are wrong, libm.so should point to the newly installed glibc file - and there should be only one such link (check e.g. /lib and /usr/lib) You should restart this script from your build directory after you've fixed all problems! Btw. the script doesn't work if you're installing GNU libc not as your primary library! make[1]: *** [Makefile:111: install] Error 1 make[1]: Leaving directory '/opt/module/mysql-lib/glibc-2.28' make: *** [Makefile:12: install] Error 2 这上面的问题可以忽略 ``` ### 查看当前系统支持的GLIBC版本 包含2.28即可 ``` strings /lib64/libc.so.6 |grep GLIBC_ ``` ### glibc更新成功后又提示缺少libstdc++.so.6 ``` cd /data/soft # 更新lib libstdc++.so.6.0.26 wget https://cdn.frostbelt.cn/software/libstdc%2B%2B.so.6.0.26 # 替换系统中的/usr/lib64 cp libstdc++.so.6.0.26 /usr/lib64/ cd /usr/lib64/ ln -snf ./libstdc++.so.6.0.26 libstdc++.so.6 ```

京东二面:Java中一共有 N 种实现锁的方式,你知道都有哪些吗?

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-bottom: 0px;padding-left: 10px;padding-right: 10px;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;font-family: PingFangSC-regular, sans-serif;font-size: 16px;color: rgb(0, 0, 0);line-height: 1.5em;word-spacing: 0em;letter-spacing: 0em;word-break: break-word;text-align: left;"> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">首先,我们先来看下线程安全性的定义,为什么需要锁?</p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">线程安全,即在多线程编程中,一个程序或者代码段在并发访问时,能够正确地保持其预期的行为和状态,而不会出现意外的错误或者不一致的结果。</p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">而解决线程安全问题,主要分为两大类:1、无锁;2、有锁。</p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">无锁的方式有:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 局部变量; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 对象加 final 为不可变对象; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 使用 ThreadLocal 作为线程副本对象; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> CAS,Compare-And-Swap 即比较并交换,是 Java 十分常见的无锁实现方式。 </section></li> </ol> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgb(168, 196, 240);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box, none rgb(245, 245, 245);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(92, 87, 87);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;">小白:那有锁的方式呢,怎么通过加锁保证线程安全呢?</p> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">别急哈,下面听我给你一一道来。</p> <h1 data-tool="mdnice编辑器" style="border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(92, 157, 255);margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 2px;border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 22px;color: rgb(255, 255, 255);background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(92, 157, 255);line-height: 1.5em;letter-spacing: 0em;padding: 3px 10px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 4px 4px 0px 0px;box-shadow: none;display: inline;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">Java 有哪些锁?</span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">从加锁的策略看,分为隐式锁和显示锁。隐式锁通过 Synchronized 实现,显示锁通过 Lock 实现。</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 乐观锁:顾名思义,它是一种基于乐观的思想,认为读取的数据一般不会冲突,不会对其加锁,而是在最后提交数据更新时判断数据是否被更新,如果冲突,则更新不成功。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 悲观锁:它总是假设最坏的情况,每次读取数据都认为别人会更新,所以每次读取数据的时候都会加锁,这样别人就得阻塞等待它处理完释放锁后才能去读取。 </section></li> </ul> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">乐观锁实现:CAS,比较并交换,通常指的是这样一种原子操作:针对一个变量,首先比较它的内存值与某个期望值是否相同,如果相同,就给它赋一个新值。</p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">但是,这一篇我们主要来看下悲观锁的一些常用实现。</p> <h1 data-tool="mdnice编辑器" style="border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(92, 157, 255);margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 2px;border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 22px;color: rgb(255, 255, 255);background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(92, 157, 255);line-height: 1.5em;letter-spacing: 0em;padding: 3px 10px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 4px 4px 0px 0px;box-shadow: none;display: inline;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">syncroized 是什么?</span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">syncronized 是 Java 中的一个关键字,用于控制对共享资源的并发访问,从而防止多个线程同时访问某个特定资源,这被称为同步。这个关键字可以用来修饰方法或代码块。</p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">syncronized 使用对象锁保证临界区内代码的原子性</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-imgfileid="100020463" data-ratio="0.6388888888888888" src="/upload/4c41bd2981ed37d726d4b3b5c1c455b8.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgb(204, 204, 204) 0px 0px 10px 0px;"> </figure> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgb(168, 196, 240);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box, none rgb(245, 245, 245);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(92, 87, 87);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;">小白:synchronized 的底层原理是什么呀,怎么自己就完成加锁释放锁操作了?</p> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">其实 synchronized 的原理也不难,主要有以下两个关键点。</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> synchronized 又被称为监视器锁,基于 Monitor 机制实现的,主要依赖底层操作系统的互斥原语 Mutex(互斥量)。Monitor 类比加了锁的房间,一次只能有一个线程进入,进入房间即持有 Monitor,退出后就释放 Monitor。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 另一个关键点是 Java 对象头,在 JVM 虚拟机中,对象在内存中的存储结构有三部分:对象头;实例数据;对齐填充。 </section></li> </ul> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">对象头主要包括标记字段 Mark World,元数据指针,如果是数组对象的话,对象头还必须存储数组长度。</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-imgfileid="100020464" data-ratio="0.5916666666666667" src="/upload/0f118d494c0c3eb32f5f71721c319e13.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgb(204, 204, 204) 0px 0px 10px 0px;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">synchronized 也是基于此,通过锁对象的 monitor 获取和 monitor 释放来实现,对象头标记为存储具体锁状态,ThreadId 记录持有偏向锁的线程 ID。</p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">这里,又引申另外出一个问题:你知道什么是偏向锁呢?</p> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgb(168, 196, 240);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box, none rgb(245, 245, 245);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(92, 87, 87);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;">小白:不知道,啥玩意?</p> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;"><strong style="color: rgb(171, 146, 62);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">synchronized 锁升级过程</strong></p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">说到这里,那就不得不提及 synchronized 的锁升级机制了,因为 synchronized 的加锁释放锁操作会使得 CPU 在内核态和户态之间发生切换,有一定性能开销。在 JDK1.5 版本以后,对 synchronized 做了锁升级的优化,主要利用轻量级锁、偏向锁、自适应锁等减少锁操作带来的开销,对其性能做了很大提升。</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-imgfileid="100020461" data-ratio="1.97979797979798" src="/upload/7e261bb53422cb44ae36a1ae6fa7ac8a.png" data-type="png" data-w="396" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgb(204, 204, 204) 0px 0px 10px 0px;"> </figure> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 无锁:没有对资源进行加锁 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 偏向锁:在大部分情况下,只有一个线程访问修改资源,该线程自动获取锁,降低了锁操作的代价,这里就通过对象头的 ThreadId 记录线程 ID。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 轻量级锁:当前持有偏向锁,当有另外的线程来访问后,偏向锁会升级为轻量级锁,别的线程通过自旋形式尝试获取锁,不会阻塞,以提高性能。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 重量级锁:在自旋次数或时间超过一定阈值时,最后会升级为重量级锁。 </section></li> </ol> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgb(168, 196, 240);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box, none rgb(245, 245, 245);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(92, 87, 87);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;">小白:哦哦原来如此,那刚刚你说了 Java 除了隐式锁之外,还有显示锁呢?</p> </blockquote> <h1 data-tool="mdnice编辑器" style="border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(92, 157, 255);margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 2px;border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 22px;color: rgb(255, 255, 255);background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(92, 157, 255);line-height: 1.5em;letter-spacing: 0em;padding: 3px 10px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 4px 4px 0px 0px;box-shadow: none;display: inline;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">ReentrantLock 简介</span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">在 Java 中,除了对象锁,还有显示的加锁的方式,比如 Lock 接口,用得比较多的就是 ReentrantLock。它的特性如下:</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-imgfileid="100020462" data-ratio="1.1393939393939394" src="/upload/26c1a85d5bde0ae066b8a477173841fc.png" data-type="png" data-w="660" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgb(204, 204, 204) 0px 0px 10px 0px;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">下面我们再来对比看下 ReentrantLock 和 synchronized 的区别</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-imgfileid="100020460" data-ratio="0.20320855614973263" src="/upload/d21ec505826ec6fae53e152c43595ab8.png" data-type="png" data-w="748" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgb(204, 204, 204) 0px 0px 10px 0px;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">从这些对比就能看出 ReentrantLock 使用更加的灵活,特性更加丰富。</p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">ReentrantLock 是一个悲观锁,即是同一个时刻,只允许一个线程访问代码块,这一点 synchronized 其实也一样。</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-imgfileid="100020469" data-ratio="0.3731481481481482" src="/upload/4d0f2fb30aedf801ba498f8de4c78005.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgb(204, 204, 204) 0px 0px 10px 0px;"> </figure> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgb(168, 196, 240);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box, none rgb(245, 245, 245);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(92, 87, 87);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;">小白:这个是挺好用的,但是我们有一些读多写少的场景中比如缓存,大部分时间都是读操作,这里每个操作都要加锁,读性能不是很差吗,有没有更好的方案实现这种场景呀?</p> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">当然有的,比如 ReentrantReadWriteLock,读写锁。</p> <h1 data-tool="mdnice编辑器" style="border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(92, 157, 255);margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 2px;border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 22px;color: rgb(255, 255, 255);background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(92, 157, 255);line-height: 1.5em;letter-spacing: 0em;padding: 3px 10px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 4px 4px 0px 0px;box-shadow: none;display: inline;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">ReentrantReadWriteLock 介绍</span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">针对上述场景,Java 提供了读写锁 ReentrantReadWriteLock,它的内部维护了一对相关的锁,一个用于只读操作,称为读锁;一个用于写入操作,称为写锁。</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7N2JRaWooRBM0e4oNCqKqFKYPibApbzyzf6jrGbIuvRFo1z0J8HftYOaicuX4MIPcjLSZRYUHaBpdHsGOgV92icZFEgrYdEhfUU/640?wx_fmt=svg&amp;from=appmsg&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;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">/**&nbsp;Inner&nbsp;class&nbsp;providing&nbsp;readlock&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">private</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">final</span>&nbsp;ReentrantReadWriteLock.ReadLock&nbsp;readerLock;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">/**&nbsp;Inner&nbsp;class&nbsp;providing&nbsp;writelock&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">private</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">final</span>&nbsp;ReentrantReadWriteLock.WriteLock&nbsp;writerLock;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">/**&nbsp;Performs&nbsp;all&nbsp;synchronization&nbsp;mechanics&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">final</span>&nbsp;Sync&nbsp;sync;<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">使用核心代码如下</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/7N2JRaWooRBM0e4oNCqKqFKYPibApbzyzf6jrGbIuvRFo1z0J8HftYOaicuX4MIPcjLSZRYUHaBpdHsGOgV92icZFEgrYdEhfUU/640?wx_fmt=svg&amp;from=appmsg&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;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">class</span>&nbsp;<span style="color: #e6c07b;line-height: 26px;">LocalCacheService</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;Map&lt;String,&nbsp;Object&gt;&nbsp;localCache&nbsp;=&nbsp;<span style="color: #c678dd;line-height: 26px;">new</span>&nbsp;HashMap&lt;&gt;();<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;ReentrantReadWriteLock&nbsp;lock&nbsp;=&nbsp;<span style="color: #c678dd;line-height: 26px;">new</span>&nbsp;ReentrantReadWriteLock();<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;Lock&nbsp;readL&nbsp;=&nbsp;lock.readLock();<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;Lock&nbsp;writeL&nbsp;=&nbsp;lock.writeLock();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;Object&nbsp;<span style="color: #61aeee;line-height: 26px;">read</span><span style="line-height: 26px;">(String&nbsp;key)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;readL.lock();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">try</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;localCache.get(key);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #c678dd;line-height: 26px;">finally</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;readL.unlock();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;Object&nbsp;<span style="color: #61aeee;line-height: 26px;">save</span><span style="line-height: 26px;">(String&nbsp;key,&nbsp;String&nbsp;value)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writeL.lock();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">try</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;localCache.put(key,&nbsp;value);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #c678dd;line-height: 26px;">finally</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writeL.unlock();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">在 ReentrantReadWriteLock 中,多个线程可以同时读取一个共享资源。</p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">当有其他线程的写锁时,读线程会被阻塞,反之一样。</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-imgfileid="100020468" data-ratio="0.3111111111111111" src="/upload/84a32994a2e671acb3e3d09ca772f57f.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgb(204, 204, 204) 0px 0px 10px 0px;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">读写锁设计思路</p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">这里有一个关键点,就是在 ReentrantLock 中,使用 AQS 的 state 表示同步状态,表示锁被一个线程重复获取的次数。但是在读写锁 ReentrantReadWriteLock 中,如何用一个变量维护这两个状态呢?</p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">实际 ReentrantReadWriteLock 采用“高低位切割”的方式来维护,将 state 切分为两部分:高 16 位表示读;低 16 位表示写。</p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">分割之后,通过位运算,假设当前状态为 S,那么:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 写状态=S&amp;0x0000FFFF(将高 16 位全部移除),当写状态需要加 1,S+1 再运算即可。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 读状态=S&gt;&gt;&gt;16(无符号补 0 右移 16 位),当读状态需要加 1,计算 S+(1&lt;&lt;16)。 </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-imgfileid="100020467" data-ratio="0.14074074074074075" src="/upload/a36c12cab5a1dd465b6663fad06b4ffa.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgb(204, 204, 204) 0px 0px 10px 0px;"> </figure> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgb(168, 196, 240);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box, none rgb(245, 245, 245);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(92, 87, 87);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;">这时,我们再来思考下,如果有线程正在读,写线程需要等待读线程释放锁才能获取锁,也就是读的时候不允许写,那么有没有更好的方式改进呢?</p> </blockquote> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgb(168, 196, 240);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box, none rgb(245, 245, 245);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(92, 87, 87);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;">小白:emm,这个真的难倒我了。。。。。。</p> </blockquote> <h1 data-tool="mdnice编辑器" style="border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(92, 157, 255);margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 2px;border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 22px;color: rgb(255, 255, 255);background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(92, 157, 255);line-height: 1.5em;letter-spacing: 0em;padding: 3px 10px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 4px 4px 0px 0px;box-shadow: none;display: inline;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">什么是 StampedLock?</span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">哈哈莫慌,Java8 已经引入了新的读写锁,StampedLock。它和 ReentrantReadWriteLock 相比,区别在于读过程允许获取写锁写入,在原来读写锁的基础上加了一种乐观锁机制,该模式不会阻塞写锁,只是最后会对比原来的值,有着更高的并发性能。</p> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">StampedLock 三种模式如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 独占锁:和 ReentrantReadWriteLock 一样,同一时刻只能有一个写线程获取资源 </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-imgfileid="100020465" data-ratio="0.9322033898305084" src="/upload/c071b8cdc79cd72f5e7f4472124246b1.png" data-type="png" data-w="590" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgb(204, 204, 204) 0px 0px 10px 0px;"> </figure> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 悲观读锁:允许多个线程获取读锁,但是读写互斥。 </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-imgfileid="100020466" data-ratio="0.9131832797427653" src="/upload/c9a90e9db38a9001b9ae9ce055ca7017.png" data-type="png" data-w="622" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgb(204, 204, 204) 0px 0px 10px 0px;"> </figure> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 乐观读:没有加锁,允许多个线程获取乐观读和读锁,同时允许一个写线程获取写锁。 </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-imgfileid="100020472" data-ratio="0.9063545150501672" src="/upload/cba7d8e85ca657853f1818f3ff2d81d2.png" data-type="png" data-w="598" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgb(204, 204, 204) 0px 0px 10px 0px;"> </figure> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgb(168, 196, 240);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box, none rgb(245, 245, 245);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(92, 87, 87);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;">小白:那这里可以允许多个读操作和也给写线程同时进入共享资源操作,那读取的数据被改了怎么办啊??</p> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 15px;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">别担心,乐观读不能保证读到的数据是最新的,所以当把数据读取到局部变量的时候需要通过 lock.validate 方法来校验是否被修改过,如果是改过了那么就加上悲观读锁,再重新读取数据到局部变量。</p> </section>