作者:微信小助手
<section style="max-width: 100%;box-sizing: border-box;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);word-wrap: break-word !important;margin-bottom: 0px;"> <section powered-by="xiumi.us" style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section style="padding-right: 10px;padding-left: 10px;max-width: 100%;box-sizing: border-box;line-height: 1.6;word-wrap: break-word !important;"> <section style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section powered-by="xiumi.us" style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section style="padding-right: 30px;padding-left: 30px;max-width: 100%;box-sizing: border-box;line-height: 1.6;word-wrap: break-word !important;"> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;word-wrap: break-word !important;"><br></p> </section> </section> </section> </section> </section> </section> </section> </section> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">实时数据同步是现代数据处理中非常重要的一环。在常见的数据同步工具中,Flume、Flink CDC和DataX都是非常受欢迎的选择。它们各自有自己的工作原理、优势和劣势。</span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">Flume主要用于日志传输,简单易用但对特定数据源可能有限;Flink CDC基于CDC技术实现了实时的数据同步,性能高但在复杂场景下可能需要额外的工作;DataX CDC则是基于CDC技术的实时数据同步工具,具备实时同步、高性能和精确同步的优势,但需要对不同的数据源进行适配,并对数据库性能产生一定影响。选择适合自己场景的工具,能够更好地满足数据同步的需求。</span></p> <p style="text-align: center;margin-bottom: 0px;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="16" data-backw="578" data-imgfileid="100002698" data-ratio="0.027777777777777776" data-s="300,640" src="/upload/b2aa937958f79501a6500376685c9596.png" data-type="png" data-w="1080" style="width: 100%;height: auto;"></p> <p style="text-align: center;margin-bottom: 0px;"><br></p> <section style="max-width: 100%;box-sizing: border-box;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);word-wrap: break-word !important;margin-bottom: 0px;"> <section powered-by="xiumi.us" style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section style="padding-right: 20px;padding-left: 20px;max-width: 100%;box-sizing: border-box;line-height: 0.8;word-wrap: break-word !important;"> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;text-align: center;word-wrap: break-word !important;"><span style="max-width: 100%;box-sizing: border-box;font-size: 24px;color: rgb(0, 82, 255);word-wrap: break-word !important;">01</span></p> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;text-align: center;word-wrap: break-word !important;"><span style="max-width: 100%;box-sizing: border-box;letter-spacing: 0px;font-size: 20px;color: rgb(0, 82, 255);word-wrap: break-word !important;">—</span></p> </section> </section> </section> </section> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);white-space: normal;background-color: rgb(255, 255, 255);text-align: center;margin-bottom: 0px;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="color: rgb(62, 62, 62);font-size: 20px;letter-spacing: 0.578000009059906px;text-decoration: none;"><span style="text-decoration: none;letter-spacing: 0.578000009059906px;font-size: 20px;color: rgb(62, 62, 62);caret-color: rgb(62, 62, 62);text-align: left;">Flume</span></span><span style="font-size: 20px;"></span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;word-wrap: break-word !important;margin-bottom: 0px;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);"><br></span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;word-wrap: break-word !important;margin-bottom: 0px;"><span style="color: rgb(0, 82, 255);"><strong><span style="caret-color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">一、Flume的工作原理和优势</span></strong></span><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);"><span style="display: none;line-height: 0px;"></span></span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;word-wrap: break-word !important;margin-bottom: 0px;"><br></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">Flume的原理是将数据从源头(source)采集,经过一个或多个中间节点(channel)进行传输,最终发送到目的地(sink)。下面是Flume的工作原理的详细解释:</span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">1、Source(数据源):Source负责从数据源(如日志文件、网络流、消息队列等)采集数据,并将数据发送到Channel。</span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">2、Channel(通道):Channel是数据传输的中间节点,它负责暂存Source采集到的数据。Channel可以是内存、磁盘或者其他存储介质,可以按照事务或批量的方式传输数据。</span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">3、Sink(数据目的地):Sink从Channel中获取数据,并将数据发送到指定的目的地(如HDFS、关系型数据库、消息队列等)进行存储和分析。</span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">4、Agent(代理):Agent是Flume的一个独立运行实例,它由Source、Channel和Sink组成,负责管理整个数据流的采集、传输和存储。</span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);"><br></span></p> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">Flume的工作流程如下:</span> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);"><br></span> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">1、Source采集数据,将数据发送到Channel。</span> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">2、Channel将数据暂存,并等待Sink的消费。</span> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">3、Sink从Channel中获取数据,发送到目的地进行存储和分析。</span> </section> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100002702" data-ratio="0.37962962962962965" data-s="300,640" src="/upload/ddbe901894ad888911e4bc497acc1c31.png" data-type="png" data-w="1080" style=""></p> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);"><br></span> </section> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">Flume的可靠性和容错性体现在以下几个方面:</span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">1、消息确认:Channel会对消息进行确认,确保数据在传输过程中不会丢失。</span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">2、事务机制:Flume使用事务机制来确保数据的可靠传输,如果发送失败,会进行回滚和重试操作。</span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">3、失败处理:Flume提供了失败处理机制,可以配置重试策略、错误日志记录等来处理发送失败的情况。</span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);"><br></span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">总的来说,Flume通过源头采集数据,经过中间节点传输,最后发送到目的地实现数据的收集和传输。通过可靠的机制和丰富的配置选项,可以保证数据的安全和可靠传输。</span></p> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);"><br></span> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <strong><span style="caret-color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);color: rgb(0, 82, 255);">优势:</span></strong> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">1、可靠性高:Flume 采用了可靠性机制,包括数据重传、事件推送确认机制等,确保数据不会丢失。</span> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">2、扩展性强:Flume 可以通过添加多个代理节点实现横向扩展,使其可以处理大规模的数据流。</span> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">3、灵活性:Flume 可以根据不同的需求进行配置,支持多种收集器、聚合器和传输器,可以满足不同场景的需求。</span> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">4、实时数据传输:Flume 支持实时数据传输,可以以流式方式处理和传输数据,适用于需要实时数据处理和分析的场景。</span> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">5、分布式流处理:Flume 支持分布式架构,可以将数据流分发到多个节点上进行并行处理和传输。</span> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <strong><span style="caret-color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);color: rgb(0, 82, 255);">劣势:</span></strong> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">1、复杂性:Flume 的配置和部署相对复杂,需要一定的技术功底和经验。</span> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">2、依赖性:Flume 依赖于其他组件,比如 Hadoop、HBase 等,如果没有这些组件的支持,应用可能会受到限制。</span> </section> <section style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">3、部署和维护成本:由于 Flume 的复杂性和依赖性,部署和维护的成本可能较高。</span> </section> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);"><br></span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="text-indent: 32px;caret-color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);color: rgb(0, 82, 255);">Flume支持的实时采集数据源类型:</span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;text-indent: 32px;background-color: rgb(255, 255, 255);"><br></span></span></p> <table style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <tbody style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <tr style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <td width="139" valign="top" style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">数据源类型</span></td> <td width="426" valign="top" style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">说明</span></td> </tr> <tr style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <td valign="top" colspan="1" rowspan="1" width="159" style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">AvroSource</span></p></td> <td valign="top" colspan="1" rowspan="1" width="426" style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">Flume中使用最多的一种数据源类型,它能够将消息数据在不同节点之间进行安全可靠地传输,可以从另一个AvroSource或AvroSink中接收消息</span></p></td> </tr> <tr style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <td valign="top" colspan="1" rowspan="1" width="159" style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;" height="85"><p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">SpoolingDirectorySource</span></p></td> <td valign="top" colspan="1" rowspan="1" width="406" style="word-break: break-all;" height="85"><p style="font-family: Calibri;font-size: 11pt;caret-color: rgb(0, 0, 0);color: rgb(0, 0, 0);letter-spacing: normal;white-space: normal;"><span style="caret-color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);color: rgb(0, 82, 255);">它会监控特定目录下的新文件,它会将这些新文件中的信息读取出来,然后将其数据转换成消息体,传输到指定的sink。</span><span style="font-size: 11pt;"> <o:p></o:p></span></p></td> </tr> <tr style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <td valign="top" colspan="1" rowspan="1" width="159" style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">SyslogTcpSource</span></p></td> <td valign="top" colspan="1" rowspan="1" width="426" style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;word-break: break-all;"><p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);color: rgb(0, 82, 255);">专门用于接收syslog方式协议的日志流量,并将其内容转换为消息体进行采集。</span></p></td> </tr> <tr style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"> <td valign="top" colspan="1" rowspan="1" style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">JMS source</span></p></td> <td valign="top" colspan="1" rowspan="1" width="34" style="word-break: break-all;"><p style="font-family: Calibri;font-size: 11pt;caret-color: rgb(0, 0, 0);color: rgb(0, 0, 0);letter-spacing: normal;white-space: normal;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">专门用于接收JMS(Java Messaging Service) 方式协议的消息流量,并将其内容转换为消息体进行采集,用于数据中心之间的数据传输。</span><span style="font-size: 11pt;"> <o:p></o:p></span></p></td> </tr> <tr style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;word-break: break-all;box-sizing: border-box !important;word-wrap: break-word !important;"> <td valign="top" colspan="1" rowspan="1" style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;word-break: break-all;box-sizing: border-box !important;word-wrap: break-word !important;"><p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;word-break: break-all;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">Kafka Source</span></p></td> <td valign="top" colspan="1" rowspan="1" width="34" style="font-family: Calibri;font-size: 11pt;caret-color: rgb(0, 0, 0);color: rgb(0, 0, 0);letter-spacing: normal;white-space: normal;word-break: break-all;max-width: 100%;min-height: 1em;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><p style="font-family: Calibri;font-size: 11pt;caret-color: rgb(0, 0, 0);color: rgb(0, 0, 0);letter-spacing: normal;white-space: normal;word-break: break-all;max-width: 100%;min-height: 1em;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">专门用于接收Kafka 协议的消息,并将其内容转换为消息体进行采集,Kafka是一种分布式发布-订阅消息系统,它能够很好地解决大规模消息传输的需求。</span></p></td> </tr> </tbody> </table> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;text-indent: 32px;background-color: rgb(255, 255, 255);"></span></span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><br></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;word-wrap: break-word !important;margin-bottom: 0px;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);"><br></span></p> <p style="margin-bottom: 0px;white-space: normal;text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="16" data-backw="578" data-imgfileid="100002699" data-ratio="0.027777777777777776" data-s="300,640" src="/upload/b2aa937958f79501a6500376685c9596.png" data-type="png" data-w="1080" style="width: 578px;"></p> <p style="margin-bottom: 0px;white-space: normal;text-align: center;"><br></p> <section style="margin-bottom: 0px;white-space: normal;color: rgb(62, 62, 62);font-size: 16px;background-color: rgb(255, 255, 255);"> <section powered-by="xiumi.us"> <section> <section style="padding-right: 20px;padding-left: 20px;line-height: 0.8;"> <p style="text-align: center;"><span style="font-size: 24px;color: rgb(0, 82, 255);">02</span></p> <p style="text-align: center;"><span style="letter-spacing: 0px;font-size: 20px;color: rgb(0, 82, 255);">—</span></p> </section> </section> </section> </section> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);white-space: normal;background-color: rgb(255, 255, 255);text-align: center;margin-bottom: 0px;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="text-decoration: none;letter-spacing: 0.578000009059906px;font-size: 20px;color: rgb(62, 62, 62);caret-color: rgb(62, 62, 62);text-align: left;">Flink CDC</span><br></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);white-space: normal;background-color: rgb(255, 255, 255);text-align: center;margin-bottom: 0px;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="text-decoration: none;letter-spacing: 0.578000009059906px;font-size: 20px;color: rgb(62, 62, 62);caret-color: rgb(62, 62, 62);text-align: left;"><br></span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><strong style="caret-color: rgb(0, 82, 255);color: rgb(0, 82, 255);font-size: 16px;white-space: normal;"><span style="caret-color: rgb(62, 62, 62);text-align: left;background-color: rgb(255, 255, 255);">一、Flink CDC的工作原理和优势</span></strong></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);"></span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">Flink CDC通过与数据库进行交互,实时捕获数据库中的变更操作。它的工作原理可以分为以下几个步骤:</span> <o:p></o:p></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">1. 数据库连接和监控:首先,Flink CDC需要与目标数据库建立连接,并监控数据库的变更操作。它可以通过监听数据库的事务日志或者使用数据库引擎的内部机制来实现。</span> <o:p></o:p></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">2. 变更事件解析:一旦数据库发生变更操作,Flink CDC会解析这些变更事件。它会将变更事件转化为对应的数据结构,例如INSERT、UPDATE或DELETE操作。</span> <o:p></o:p></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">3. 数据转换和格式化:解析后的变更事件需要经过数据转换和格式化,以便能够被Flink进行处理。Flink CDC会将变更事件转化为Flink支持的数据格式,例如JSON、Avro等。</span> <o:p></o:p></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">4. 事件流生成:经过转换和格式化后,Flink CDC会将变更事件转化为数据流。这个数据流可以被Flink的流处理任务进行消费和处理。</span> <o:p></o:p></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);margin-bottom: 0px;text-indent: 2em;line-height: 2em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);">5. 数据同步和传输:生成的数据流可以被传输到不同的目的地,例如Flink的流处理任务、消息队列或者其他外部系统。这样,我们就可以对变更事件进行实时分析和处理。</span> <o:p></o:p></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;backgro
作者:微信小助手
<section data-role="outer" label="edit by 135editor" data-mpa-powered-by="yiban.io"> <section data-role="paragraph"> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzkyNTI5NTQ1NQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/wxcY9TH8dPsYAnrjaZktBe0iahF8ic9QkF26cAw8pK6HPR1bfFEImdyJspvkQvQwmnYxP4eEVW60ewVVickcWXnrQ/0?wx_fmt=png" data-nickname="架构文摘" data-alias="ArchDigest" data-signature="每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性能、高稳定)、大数据、机器学习、Java架构等各个热门领域。" data-from="1" data-is_biz_ban="0"></mp-common-profile> </section> <p style="margin: 16px 8px 24px;"><span style="color: rgb(136, 136, 136);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 13px;letter-spacing: 0.034em;text-align: left;">来源:</span><span style="color: rgb(136, 136, 136);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 13px;letter-spacing: 0.034em;text-align: left;">网络</span><span style="color: rgb(136, 136, 136);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 13px;letter-spacing: 0.034em;text-align: left;"> </span></p> <section style="margin-right: 8px;margin-bottom: 24px;margin-left: 8px;"> <span style="color: rgb(74, 74, 74);font-size: 16px;letter-spacing: 0.034em;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">最快的速度把10亿条数据导入到</span> <span style="color: rgb(74, 74, 74);font-size: 16px;letter-spacing: 0.034em;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">数据库,首先需要和面试官明确一下,10亿条数据什么形式存在哪里,每条数据多大,是否有序导入,是否不能重复,数据库是否是MySQL?</span> </section> <section style="margin-right: 8px;margin-bottom: 24px;margin-left: 8px;"> <span style="color: rgb(74, 74, 74);font-size: 16px;letter-spacing: 0.034em;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">假设</span> <span style="color: rgb(74, 74, 74);font-size: 16px;letter-spacing: 0.034em;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">和面试官明确后,有如下约束</span> </section> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-bottom: 24px;padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0.034em;color: rgb(63, 63, 63);font-size: 16px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;"> <ol data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;width: 557.438px;color: black;list-style-position: outside;"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 10亿条数据,每条数据 1 Kb </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 数据内容是非结构化的用户访问日志,需要解析后写入到数据库 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 数据存放在 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">Hdfs</code> 或 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">S3</code> 分布式文件存储里 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 10亿条数据并不是1个大文件,而是被近似切分为100个文件,后缀标记顺序 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 要求有序导入,尽量不重复 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 数据库是 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">MySQL</code> </section></li> </ol> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">首先考虑10亿数据写到MySQL单表可行吗?</p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><strong style="color: #f79646;font-size: 18px;letter-spacing: 0.034em;caret-color: red;">数据库单表能支持10亿吗?</strong></p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">答案是不能,单表推荐的值是2000W以下。这个值怎么计算出来的呢?</p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">MySQL索引数据结构是B+树,全量数据存储在主键索引,也就是聚簇索引的叶子结点上。B+树插入和查询的性能和B+树层数直接相关,2000W以下是3层索引,而2000w以上则可能为四层索引。</p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">Mysql b+</code>索引的叶子节点每页大小16K。当前每条数据正好1K,所以简单理解为每个叶子节点存储16条数据。b+索引每个非叶子节点大小也是16K,但是其只需要存储主键和指向叶子节点的指针,我们假设主键的类型是 BigInt,长度为 8 字节,而指针大小在 InnoDB 中设置为 6 字节,这样一共 14 字节,这样一个非叶子节点可以存储 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">16 * 1024/14=1170</code>。</p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">也就是每个非叶子节点可关联1170个叶子节点,每个叶子节点存储16条数据。由此可得到B+树索引层数和存储数量的表格。2KW 以上 索引层数为 4 层,性能更差。</p> <section data-tool="mdnice编辑器" style="overflow-x: auto;"> <table> <thead> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <th style="border-top-width: 1px;border-color: rgb(204, 204, 204);background-color: rgb(240, 240, 240);font-size: 14px;min-width: 85px;text-align: justify;">层数</th> <th style="border-top-width: 1px;border-color: rgb(204, 204, 204);background-color: rgb(240, 240, 240);font-size: 14px;min-width: 85px;text-align: justify;" width="154">最大数据量</th> </tr> </thead> <tbody style="border-width: 0px;border-style: initial;border-color: initial;"> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;">2</td> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="454">1170 * 16 = 18720</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;">3</td> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="454">1170 * 1170 * 16= 21902400 = 2000w</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;">4</td> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="474">1170 * 1170 * 1170 * 16 = 25625808000 = 256亿</td> </tr> </tbody> </table> </section> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">为了便于计算,我们可以设计单表容量在1KW,10亿条数据共100个表。</p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><strong style="color: #f79646;font-size: 18px;letter-spacing: 0.034em;caret-color: red;">如何高效的写入数据库</strong></p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">单条写入数据库性能比较差,可以考虑批量写入数据库,批量数值动态可调整。每条1K,默认可先调整为100条批量写入。</p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">批量数据如何保证数据同时写成功?MySQL Innodb存储引擎保证批量写入事务同时成功或失败。</p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">写库时要支持重试,写库失败重试写入,如果重试N次后依然失败,可考虑单条写入100条到数据库,失败数据打印记录,丢弃即可。</p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">此外写入时按照主键id顺序顺序写入可以达到最快的性能,而非主键索引的插入则不一定是顺序的,频繁地索引结构调整会导致插入性能下降。最好不创建非主键索引,或者在表创建完成后再创建索引,以保证最快的插入性能。</p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><strong style="color: #f79646;font-size: 18px;letter-spacing: 0.034em;caret-color: red;">是否需要并发写同一个表</strong></p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">不能</p> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;" data-role="list"> <ol class="list-paddingleft-1" style="padding-left: 30px;list-style-position: outside;"> <li><p>并发写同一个表无法保证数据写入时是有序的。</p></li> <li><p>提高批量插入的阈值,在一定程度上增加了插入并发度。无需再并发写入单表</p></li> </ol> </section> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);height: 0px;overflow: hidden;"> <br> </section> <p style="text-align:justify;margin-top: 20px;margin-bottom: 25px;line-height: 26px;color: rgb(1, 1, 1);"><strong style="color: #f79646;font-size: 18px;letter-spacing: 0.034em;caret-color: red;">MySQL存储引擎的选择</strong></p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">Myisam </code>比<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">innodb</code>有更好的插入性能,但失去了事务支持,批量插入时无法保证同时成功或失败,所以当批量插入超时或失败时,如果重试,势必对导致一些重复数据的发生。但是为了保证更快的导入速度,可以把myisam存储引擎列为计划之一。</p> <p data-tool="mdnice编辑器" style="text-align:justify;padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">现阶段我引用一下别人的性能测试结果:MyISAM与InnoDB对比分析</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" src="/upload/ea85c1f224cd0b41032e9a7e46d1a5a8.jpg" data-imgfileid="100032369" style="margin-right: auto;margin-bottom: 25px;margin-left: auto;display: block;border-radius: 4px;" data-ratio="0.458" data-w="1000"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;font-family: PingFangSC-Light;"> 图片 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">从数据可以看到批量写入明显优于单条写入。并且在innodb关闭即时刷新磁盘策略后,innodb插入性能没有比myisam差太多。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">innodb_flush_log_at_trx_commit</code>: 控制MySQL刷新数据到磁盘的策略。</p> <ol data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;width: 557.438px;color: black;list-style-position: outside;"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 默认=1,即每次事务提交都会刷新数据到磁盘,安全性最高不会丢失数据。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 当配置为0、2 会每隔1s刷新数据到磁盘, 在系统宕机、 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">mysql crash</code>时可能丢失1s的数据。 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">考虑到Innodb在关闭即时刷新磁盘策略时,批量性能也不错,所以暂定先使用<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">innodb</code>(如果公司MySQL集群不允许改变这个策略值,可能要使用MyIsam了。)。线上环境测试时可以重点对比两者的插入性能。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><strong style="color: #f79646;font-size: 18px;text-align: center;letter-spacing: 0.034em;caret-color: red;">要不要进行分库</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">mysql 单库的并发写入是有性能瓶颈的,一般情况5K TPS写入就很高了。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">当前数据都采用SSD 存储,性能应该更好一些。但如果是HDD的话,虽然顺序读写会有非常高的表现,但HDD无法应对并发写入,例如每个库10张表,假设10张表在并发写入,每张表虽然是顺序写入,由于多个表的存储位置不同,HDD只有1个磁头,不支持并发写,只能重新寻道,耗时将大大增加,失去顺序读写的高性能。所以对于HDD而言,单库并发写多个表并不是好的方案。回到SSD的场景,不同SSD厂商的写入能力不同,对于并发写入的能力也不同,有的支持500M/s,有的支持1G/s读写,有的支持8个并发,有的支持4个并发。在线上实验之前,我们并不知道实际的性能表现如何。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">所以在设计上要更加灵活,需要支持以下能力</p> <ol data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;width: 557.438px;color: black;list-style-position: outside;"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 支持配置数据库的数量 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 支持配置并发写表的数量,(如果MySQL是HDD磁盘,只让一张表顺序写入,其他任务等待) </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">通过以上配置,灵活调整线上数据库的数量,以及写表并发度,无论是HDD还是SSD,我们系统都能支持。不论是什么厂商型号的SSD,性能表现如何,都可调整配置,不断获得更高的性能。这也是后面设计的思路,不固定某一个阈值数量,都要动态可调整。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">接下来聊一下文件读取,10亿条数据,每条1K,一共是931G。近1T大文件,一般不会生成如此大的文件。所以我们默认文件已经被大致切分为100个文件。每个文件数量大致相同即可。为什么切割为100个呢?切分为1000个,增大读取并发,不是可以更快导入数据库吗?刚才提到数据库的读写性能受限于磁盘,但任何磁盘相比写操作,读操作都要更快。尤其是读取时只需要从文件读取,但写入时MySQL要执行建立索引,解析SQL、事务等等复杂的流程。所以写的并发度最大是100,读文件的并发度无需超过100。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">更重要的是读文件并发度等于分表数量,有利于简化模型设计。即100个读取任务,100个写入任务,对应100张表。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><strong style="color: #f79646;font-size: 18px;text-align: center;letter-spacing: 0.034em;caret-color: red;">如何保证写入数据库有序</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">既然文件被切分为100个10G的小文件,可以按照文件后缀+ 在文件行号 作为记录的唯一键,同时保证同一个文件的内容被写入同一个表。例如</p> <ol data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;width: 557.438px;color: black;list-style-position: outside;"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> index_90.txt 被写入 数据库database_9,table_0 , </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> index_67.txt被写入数据库 database_6,table_7。 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">这样每个表都是有序的。整体有序通过数据库后缀+表名后缀实现。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><strong style="color: #f79646;font-size: 18px;text-align: center;letter-spacing: 0.034em;caret-color: red;">如何更快地读取文件</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">10G的文件显然不能一次性读取到内存中,场景的文件读取包括</p> <ol data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;width: 557.438px;color: black;list-style-position: outside;"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">Files.readAllBytes</code>一次性加载内内存 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> FileReader+ BufferedReader 逐行读取 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> File+ BufferedReader </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Scanner逐行读取 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Java NIO FileChannel缓冲区方式读取 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">在MAC上,使用这几种方式的读取3.4G大小文件的性能对比</p> <section data-tool="mdnice编辑器" style="overflow-x: auto;"> <table> <thead> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <th style="border-top-width: 1px;border-color: rgb(204, 204, 204);background-color: rgb(240, 240, 240);font-size: 14px;min-width: 85px;text-align: left;" width="257">读取方式</th> <th style="border-top-width: 1px;border-color: rgb(204, 204, 204);background-color: rgb(240, 240, 240);font-size: 14px;min-width: 85px;text-align: left;" width="175"><br></th> </tr> </thead> <tbody style="border-width: 0px;border-style: initial;border-color: initial;"> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="257"><code>Files.readAllBytes</code></td> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="175">内存爆了 OOM</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="322"><code>FileReader+ BufferedReader</code> 逐行读取</td> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="175">11秒</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="302"><code>File+ BufferedReader</code></td> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="155">10 秒</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="257"><code>Scanner</code></td> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="155">57秒</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="257"><code>Java NIO FileChannel</code>缓冲区方式读取</td> <td style="border-color: rgb(204, 204, 204);font-size: 14px;min-width: 85px;" width="155">3秒</td> </tr> </tbody> </table> </section> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">详细的评测内容请参考:读取文件性能比较 :https://zhuanlan.zhihu.com/p/142029812</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">由此可见 使用<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">JavaNIO FileChannnel</code>明显更优,但是<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">FileChannel</code>的方式是先读取固定大小缓冲区,不支持按行读取。也无法保证缓冲区正好包括整数行数据。如果缓冲区最后一个字节正好卡在一行数据中间,还需要额外配合读取下一批数据。如何把缓冲区变为一行行数据,比较困难。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="margin-bottom: -7px;display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/HhRfwkaJkMsm5vgibiceXdd9hn8XexWRaSBORZpS7YiaQyYLD5zC7zHQ4EUFOicDcuZbLa2pcicRbj9s7luZ7xpv6H8XHEXpDRBBR/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 558px;border-radius: 5px;"></span><code style="padding: 15px 16px 16px;overflow-x: auto;color: rgb(171, 178, 191);display: -webkit-box;font-size: 12px;background: rgb(40, 44, 52);border-radius: 5px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">File file = new File(<span style="color: rgb(152, 195, 121);line-height: 26px;">"/xxx.zip"</span>);<br>FileInputStream fileInputStream = null;<br>long now = System.currentTimeMillis();<br>try {<br> fileInputStream = new FileInputStream(file);<br> FileChannel fileChannel = fileInputStream.getChannel();<br><br> int capacity = 1 * 1024 * 1024;//1M<br> ByteBuffer byteBuffer = ByteBuffer.allocate(capacity);<br> StringBuffer buffer = new StringBuffer();<br> int size = 0;<br> <span style="color: rgb(198, 120, 221);line-height: 26px;">while</span> (fileChannel.read(byteBuffer) != -1) {<br> //读取后,将位置置为0,将<span style="color: rgb(230, 192, 123);line-height: 26px;">limit</span>置为容量, 以备下次读入到字节缓冲中,从0开始存储<br> byteBuffer.clear();<br> byte[] bytes = byteBuffer.array();<br> size += bytes.length;<br> }<br> System.out.println(<span style="color: rgb(152, 195, 121);line-height: 26px;">"file size:"</span> + size);<br>} catch (FileNotFoundException e) {<br> e.printStackTrace();<br>} catch (IOException e) {<br> e.printStackTrace();<br>} finally {<br> //TODO close资源.<br>}<br>System.out.println(<span style="color: rgb(152, 195, 121);line-height: 26px;">"Time:"</span> + (System.currentTimeMillis() - now));<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">JavaNIO </code>是基于缓冲区的,<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">ByteBuffer</code>可转为byte数组,需要转为字符串,并且要处理按行截断。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">但是<strong style="line-height: 1.75em;">BufferedReader JavaIO方式读取可以天然支持按行截断,况且性能还不错</strong>,10G文件,大致只需要读取30s,由于导入的整体瓶颈在写入部分,即便30s读取完,也不会影响整体性能。所以文件读取使用<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">BufferedReader</code> 逐行读取。即方案3</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><strong style="color: #f79646;font-size: 18px;text-align: center;letter-spacing: 0.034em;caret-color: red;">如果协调读文件任务和写数据库任务</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">这块比较混乱,请耐心看完。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">100个读取任务,每个任务读取一批数据,立即写入数据库是否可以呢?前面提到了由于数据库并发写入的瓶颈,无法满足1个库同时并发大批量写入10个表,所以100个任务同时写入数据库,势必导致每个库同时有10个表同时在顺序写,这加剧了磁盘的并发写压力。为尽可能提高速度,减少磁盘并发写入带来的性能下降, 需要一部分写入任务被暂停的。那么读取任务需要限制并发度吗?不需要。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">假设写入任务和读取任务合并,会影响读取任务并发度。初步计划读取任务和写入任务各自处理,谁也不耽误谁。但实际设计时发现这个方案较为困难。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">最初的设想是引入Kafka,即100个读取任务把数据投递到Kafka,由写入任务消费kafka写入DB。100个读取任务把消息投递到Kafka,此时顺序就被打乱了,如何保证有序写入数据库呢?我想到可以使用Kafka partition路由,即读取任务id把同一任务的消息都路由到同一个partition,保证每个partition内有序消费。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">要准备多少个分片呢?100个很明显太多,如果partition小于100个,例如10个。那么势必存在多个任务的消息混合在一起。如果同一个库的多个表在一个Kafka partition,且这个数据库只支持单表批量写入,不支持并发写多个表。这个库多个表的消息混在一个分片中,由于并发度的限制,不支持写入的表对应的消息只能被丢弃。所以这个方案既复杂,又难以实现。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">所以最终放弃了Kafka方案,也暂时放弃了将读取和写入任务分离的方案。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">最终方案简化为 读取任务读一批数据,写入一批。即任务既负责读文件、又负责插入数据库。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><strong style="color: #f79646;font-size: 18px;text-align: center;letter-spacing: 0.034em;caret-color: red;">如何保证任务的可靠性</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">如果读取任务进行到一半,宕机或者服务发布如何处理呢?或者数据库故障,一直写入失败,任务被暂时终止,如何保证任务再次拉起时,再断点处继续处理,不会存在重复写入呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">刚才我们提到可以 为每一个记录设置一个主键Id,即 文件后缀index+文件所在行号。可以通过主键id的方式保证写入的幂等。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">文件所在的行号,最大值 大致为 10G/1k = 10M,即10000000。拼接最大的后缀99。最大的id为990000000。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">所以也无需数据库自增主键ID,可以在批量插入时指定主键ID。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">如果另一个任务也需要导入数据库呢?如何实现主键ID隔离,所以主键ID还是需要拼接<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">taskId</code>。例如<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">{taskId}{fileIndex}{fileRowNumber} </code>转化为Long类型。如果taskId较大,拼接后的数值过大,转化为Long类型可能出错。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">最重要的是,如果有的任务写入1kw,有的其他任务写入100W,使用Long类型无法获知每个占位符的长度,存在冲突的可能性。而如果拼接字符串<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">{taskId}_{fileIndex}_{fileRowNumber}</code> ,新增唯一索引,会导致插入性能更差,无法满足最快导入数据的诉求。所以需要想另一个方案。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">可以考虑使用Redis记录当前任务的进度。例如Redis记录task的进度,批量写入数据库成功后,更新 task进度。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="margin-bottom: -7px;display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/HhRfwkaJkMsm5vgibiceXdd9hn8XexWRaSBORZpS7YiaQyYLD5zC7zHQ4EUFOicDcuZbLa2pcicRbj9s7luZ7xpv6H8XHEXpDRBBR/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 558px;border-radius: 5px;"></span><code style="padding: 15px 16px 16px;overflow-x: auto;color: rgb(171, 178, 191);display: -webkit-box;font-size: 12px;background: rgb(40, 44, 52);border-radius: 5px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">INCRBY KEY_NAME INCR_AMOUNT<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">指定当前进度增加100,例如 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">incrby task_offset_{taskId}</code> 100。如果出现批量插入失败的,则重试插入。多次失败,则单个插入,单个更新redis。要确保Redis更新成功,可以在Redis更新时 也加上重试。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">如果还不放心Redis进度和数据库更新的一致性,可以考虑 消费 数据库binlog,每一条记录新增则redis +1 。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">如果任务出现中断,则首先查询任务的offset。然后读取文件到指定的offset继续 处理。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><strong style="color: #f79646;font-size: 18px;text-align: center;letter-spacing: 0.034em;caret-color: red;">如何协调读取任务的并发度</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">前面提到了为了避免单个库插入表的并发度过高,影响数据库性能。可以考虑限制并发度。如何做到呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">既然读取任务和写入任务合并一起。那么就需要同时限制读取任务。即每次只挑选一批读取写入任务执行。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">在此之前需要设计一下任务表的存储模型。</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="100032368" data-ratio="0.7280701754385965" src="/upload/b110f27c55dd7b38577619e196165ff8.png" data-w="456" style="margin-right: auto;margin-bottom: 25px;margin-left: auto;display: block;border-radius: 4px;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;font-family: PingFangSC-Light;"> 图片 </figcaption> </figure> <ol data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;width: 557.438px;color: black;list-style-position: outside;"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> bizId为了以后支持别的产品线,预设字段。默认为1,代表当前业务线。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> datbaseIndex 代表被分配的数据库后缀 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> tableIndex 代表被分配的表名后缀 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">parentTaskId</code>,即总的任务id </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> offset可以用来记录当前任务的进度 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 10亿条数据导入数据库,切分为100个任务后,会新增100个taskId,分别处理一部分数据,即一个10G文件。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> status 状态用来区分当前任务是否在执行,执行完成。 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">如何把任务分配给每一个节点,可以考虑抢占方式。每个任务节点都需要抢占任务,每个节点同时只能抢占1个任务。具体如何实现呢?可以考虑 每个节点都启动一个定时任务,定期扫表,扫到待执行子任务,尝试执行该任务。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">如何控制并发呢?可以使用redission的信号量。key为数据库id、</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="margin-bottom: -7px;display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/HhRfwkaJkMsm5vgibiceXdd9hn8XexWRaSBORZpS7YiaQyYLD5zC7zHQ4EUFOicDcuZbLa2pcicRbj9s7luZ7xpv6H8XHEXpDRBBR/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 558px;border-radius: 5px;"></span><code style="padding: 15px 16px 16px;overflow-x: auto;color: rgb(171, 178, 191);display: -webkit-box;font-size: 12px;background: rgb(40, 44, 52);border-radius: 5px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">RedissonClient redissonClient = Redisson.create(config);<br> RSemaphore rSemaphore = redissonClient.getSemaphore(<span style="color: rgb(152, 195, 121);line-height: 26px;">"semaphore"</span>);<br> // 设置1个并发度<br> rSemaphore.trySetPermits(1);<br> rSemaphore.tryAcquire();//申请加锁,非阻塞。<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">由任务负责定期轮训,抢到名额后,就开始执行任务。将该任务状态置为Process,任务完成后或失败后,释放信号量。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">TaskTassk任务表Redisalt争抢信号量成功定时轮训任务开始查询待执行的任务循环争抢信号量修改任务状态执行中,设置开始时间时间查询当前进度读取文件到从当前进度读取文件,批量导入数据库更新进度执行完成,释放信号量申请下一个任务的信号量TaskTassk任务表Redis</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">但是使用信号量限流有个问题,如果任务忘记释放信号量,或者进程Crash无法释放信号量,如何处理呢?可以考虑给信号量增加一个超时时间。那么如果任务执行过长,导致提前释放信号量,另一个客户单争抢到信号量,导致 两个客户端同时写一个任务如何处理呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">what,明明是将10亿数据导入数据库,怎么变成分布式锁超时的类似问题?</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">实际上 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">Redisson</code>的信号量并没有很好的办法解决信号量超时问题,正常思维:如果任务执行过长,导致信号量被释放,解决这个问题只需要续约就可以了,任务在执行中,只要发现快信号量过期了,就续约一段时间,始终保持信号量不过期。但是 Redission并没有提供信号量续约的能力,怎么办?</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">不妨换个思路,我们一直在尝试让多个节点争抢信号量,进而限制并发度。可以试试选取一个主节点,通过主节点轮训任务表。分三种情况,</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">情况1 当前执行中数量小于并发度。</p> <ol data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;width: 557.438px;color: black;list-style-position: outside;"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 则选取id最小的待执行任务,状态置为进行中,通知发布消息。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 消费到消息的进程,申请分布式锁,开始处理任务。处理完成释放锁。借助于Redission分布式锁续约,保证任务完成前,锁不会超时。 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">情况2 当前执行中数量等于并发度。</p> <ol data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;width: 557.438px;color: black;list-style-position: outside;"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 主节点尝试 get 进行中任务是否有锁。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 如果没有锁,说明有任务执行失败,此时应该重新发布任务。如果有锁,说明有任务正在执行中。 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">情况3 当前执行中数量大于并发度</p> <ol data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;width: 557.438px;color: black;list-style-position: outside;"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 上报异常情况,报警,人工介入 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">使用主节点轮训任务,可以减少任务的争抢,通过kafka发布消息,接收到消息的进程处理任务。为了保证更多的节点参与消费,可以考虑增加Kafka分片数。虽然每个节点可能同时处理多个任务,但是不会影响性能,因为性能瓶颈在数据库。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">那么主节点应该如何选取呢?可以通过<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">Zookeeper+curator</code> 选取主节点。可靠性比较高。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">10亿条数据插入数据库的时间影响因素非常多。包括数据库磁盘类型、性能。数据库分库数量如果能切分1000个库当然性能更快,要根据线上实际情况决策分库和分表数量,这极大程度决定了写入的速率。最后数据库批量插入的阈值也不是一成不变的,需要不断测试调整,以求得最佳的性能。可以按照100,1000,10000等不断尝试批量插入的最佳阈值。</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;">最后总结一下几点重要的</p> <p data-tool="mdnice编辑器" style="padding-top: 1em;padding-bottom: 8px;color: rgb(74, 74, 74);line-height: 1.75em;"><strong style="color: #f79646;font-size: 18px;text-align: center;letter-spacing: 0.034em;caret-color: red;">总结</strong></p> <ol data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;width: 557.438px;color: black;list-style-position: outside;"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 要首先确认约束条件,才能设计方案。确定面试官主要想问的方向,例如1T文件如何切割为小文件,虽是难点,然而可能不是面试官想考察的问题。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 从数据规模看,需要分库分表,大致确定分表的规模。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 从单库的写入瓶颈分析,判断需要进行分库。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 考虑到磁盘对并发写的支持力度不同,同一个库多个表写入的并发需要限制。并且支持动态调整,方便在线上环境调试出最优值。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">MySQL innodb、myisam</code> 存储引擎对写入性能支持不同,也要在线上对比验证 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 数据库批量插入的最佳阈值需要反复测试得出。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 由于存在并发度限制,所以基于Kafka分离读取任务和写入任务比较困难。所以合并读取任务和写入任务。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 需要Redis记录任务执行的进度。任务失败后,重新导入时,记录进度,可避免数据重复问题。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 分布式任务的协调工作是难点,使用Redission信号量无法解决超时续约问题。可以由主节点分配任务+分布式锁保证任务排他写入。主节点使用 <code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);word-break: break-all;color: rgb(255, 192, 0);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;">Zookeeper+Curator</code>选取。 </section></li> </ol> </section> </section>
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;margin-bottom: 24px;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;"><span style="color: rgb(0, 150, 136);font-size: 1.8em;font-weight: bold;text-align: center;letter-spacing: 0px;">概览</span></p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;color: rgb(0, 150, 136);padding-left: 10px;margin: 1em auto;border-left: 3px solid rgb(0, 150, 136);">1.什么是微服务?</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">微服务(Microservices)是一种软件架构风格,将一个大型应用程序划分为一组小型、自治且松耦合的服务。每个微服务负责执行特定的业务功能,并通过轻量级通信机制(如HTTP)相互协作。每个微服务可以独立开发、部署和扩展,使得应用程序更加灵活、可伸缩和可维护。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">在微服务的架构演进中,一般可能会存在这样的演进方向:单体式-->服务化-->微服务。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">单体服务一般是所有项目最开始的样子:</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;line-height: 26px;color: rgb(1, 1, 1);"> 单体服务(Monolithic Service)是一种传统的软件架构方式,将整个应用程序作为一个单一的、紧耦合的单元进行开发和部署。单体服务通常由多个模块组成,这些模块共享同一个数据库和代码库。然而,随着应用程序规模的增长,单体服务可能变得庞大且难以维护,且部署和扩展困难。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">后来,单体服务过大,维护困难,渐渐演变到了分布式的SOA:</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;line-height: 26px;color: rgb(1, 1, 1);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;text-align: justify;">SOA(Service-Oriented Architecture,面向服务的架构)是一种软件架构设计原则,强调将应用程序拆分为相互独立的服务,通过标准化的接口进行通信。SOA关注于服务的重用性和组合性,但并没有具体规定服务的大小。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;text-align: justify;">微服务是在SOA的基础上进一步发展而来,是一种特定规模下的服务拆分和部署方式。微服务架构强调将应用程序拆分为小型、自治且松耦合的服务,每个服务都专注于特定的业务功能。这种架构使得应用程序更加灵活、可伸缩和可维护。</p> </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">需要注意的是,微服务是一种特定的架构风格,而SOA是一种设计原则。微服务可以看作是对SOA思想的一种具体实践方式,但并不等同于SOA。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.7342592592592593" src="/upload/ac84270d25aa98f87940599f2484111e.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;" data-imgfileid="100004546"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> 架构演进简图 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">微服务与单体服务的区别在于规模和部署方式。微服务将应用程序拆分为更小的、自治的服务单元,每个服务都有自己的数据库和代码库,可以独立开发、测试、部署和扩展,带来了更大的灵活性、可维护性、可扩展性和容错性。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;color: rgb(0, 150, 136);padding-left: 10px;margin: 1em auto;border-left: 3px solid rgb(0, 150, 136);">2.微服务带来了哪些挑战?</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">微服务架构不是万金油,尽它有很多优点,但是对于是否采用微服务架构,是否将原来的单体服务进行拆分,还是要考虑到服务拆分后可能带来的一些挑战和问题:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.3718647764449291" src="/upload/7e200d81a785c6f6018ecd7cee33b51d.png" data-type="png" data-w="917" style="display: block;margin-right: auto;margin-left: auto;" data-imgfileid="100004547"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> 微服务带来的挑战 </figcaption> </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;line-height: 26px;color: rgb(1, 1, 1);"> 系统复杂性增加:一个服务拆成了多个服务,整体系统的复杂性增加,需要处理服务之间的通信、部署、监控和维护等方面的复杂性。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 服务间通信开销:微服务之间通过网络进行通信,传递数据需要额外的网络开销和序列化开销,可能导致性能瓶颈和增加系统延迟。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 数据一致性和事务管理:每个微服务都有自己的数据存储,数据一致性和跨服务的事务管理变得更加复杂,需要额外解决分布式事务和数据同步的问题。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 部署和运维复杂性:微服务架构涉及多个独立部署的服务,对于部署、监控和容错机制的要求更高,需要建立适当的部署管道和自动化工具,以简化部署和运维过程。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 团队沟通和协作成本:每个微服务都由专门的团队负责,可能增加团队之间的沟通和协作成本。需要有效的沟通渠道和协作机制,确保服务之间的协调和一致性。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 服务治理和版本管理:随着微服务数量的增加,服务的治理和版本管理变得更加复杂。需要考虑服务的注册发现、负载均衡、监控和故障处理等方面,以确保整个系统的可靠性和稳定性。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 分布式系统的复杂性:微服务架构涉及构建和管理分布式系统,而分布式系统本身具有一些固有的挑战,如网络延迟、分布式一致性和容错性。 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">简单说,采用微服务需要权衡这些问题和挑战,根据实际的需求来选择对应的技术方案,很多时候单体能搞定的也可以用单体,不能为了微服务而微服务。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;color: rgb(0, 150, 136);padding-left: 10px;margin: 1em auto;border-left: 3px solid rgb(0, 150, 136);">3.现在有哪些流行的微服务解决方案?</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">目前最主流的微服务开源解决方案有三种:</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;line-height: 26px;color: rgb(1, 1, 1);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;text-align: justify;">Dubbo:</p> <figure style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5796296296296296" src="/upload/6d622a72d299924fad87adcebeb7c1af.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;" data-imgfileid="100004550"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> Dubbo工作原理图-来源官网 </figcaption> </figure> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Dubbo 是一个高性能、轻量级的 Java 微服务框架,最初由阿里巴巴(Alibaba)开发并于2011年开源。它提供了服务注册与发现、负载均衡、容错、分布式调用等功能,后来一度停止维护,在近两年,又重新开始迭代,并推出了Dubbo3。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Dubbo 使用基于 RPC(Remote Procedure Call)的通信模型,具有较高的性能和可扩展性。它支持多种传输协议(如TCP、HTTP、Redis)和序列化方式(如JSON、Hessian、Protobuf),可根据需求进行配置。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Dubbo更多地被认为是一个高性能的RPC(远程过程调用)框架,一些服务治理功能依赖于第三方组件实现,比如使用ZooKeeper、Apollo等等。 </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;text-align: justify;">Spring Cloud Netflix:</p> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Netflix 是 Spring Cloud 的一个子项目,结合了 Netflix 开源的多个组件,但是Netflix自2018年停止维护和更新Netflix OSS项目,包括Eureka、Hystrix等组件,所以Spring Cloud Netflix也逐渐进入了维护模式。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 该项目包含了许多流行的 Netflix 组件,如Eureka(服务注册与发现)、Ribbon(客户端负载均衡)、Hystrix(断路器)、Zuul(API 网关)等。它们都是高度可扩展的、经过大规模实践验证的微服务组件。 </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;text-align: justify;">Spring Cloud Alibaba:</p> <h3 style="font-weight: bold;color: black;font-size: 20px;margin: 0.6em auto;padding-left: 10px;border-left: 2px solid rgb(0, 150, 136);">这三种方案有什么区别吗?</h3> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;text-align: justify;">三种方案的区别:</p> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;overflow-x: auto;"> <table> <thead> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <th style="text-align: left;border-top-width: 1px;border-top-color: rgb(0, 150, 136);border-right-color: rgb(0, 150, 136);border-left-color: rgb(0, 150, 136);background-color: rgb(0, 150, 136);color: rgb(248, 248, 248);border-bottom: 0px;min-width: 85px;">特点</th> <th style="text-align: left;border-top-width: 1px;border-top-color: rgb(0, 150, 136);border-right-color: rgb(0, 150, 136);border-left-color: rgb(0, 150, 136);background-color: rgb(0, 150, 136);color: rgb(248, 248, 248);border-bottom: 0px;min-width: 85px;">Dubbo</th> <th style="text-align: left;border-top-width: 1px;border-top-color: rgb(0, 150, 136);border-right-color: rgb(0, 150, 136);border-left-color: rgb(0, 150, 136);background-color: rgb(0, 150, 136);color: rgb(248, 248, 248);border-bottom: 0px;min-width: 85px;">Spring Cloud Netflix</th> <th style="text-align: left;border-top-width: 1px;border-top-color: rgb(0, 150, 136);border-right-color: rgb(0, 150, 136);border-left-color: rgb(0, 150, 136);background-color: rgb(0, 150, 136);color: rgb(248, 248, 248);border-bottom: 0px;min-width: 85px;">Spring Cloud Alibaba</th> </tr> </thead> <tbody style="border-width: 0px;border-style: initial;border-color: initial;"> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">开发语言</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Java</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Java</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Java</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">服务治理</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">提供完整的服务治理功能</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">提供部分服务治理功能</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">提供完整的服务治理功能</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">服务注册与发现</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">ZooKeeper/Nacos</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Eureka/Consul</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Nacos</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">负载均衡</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">自带负载均衡策略</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Ribbon</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Ribbon\Dubbo负载均衡策略</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">服务调用</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">RPC方式</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">RestTemplate/Feign</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Feign/RestTemplate/Dubbo</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">熔断器</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Sentinel</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Hystrix</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Sentinel/Resilience4j</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">配置中心</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Apollo</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Spring Cloud Config</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Nacos Config</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">API网关</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Higress/APISIX</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Zuul/Gateway</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Spring Cloud Gateway</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">分布式事务</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Seata</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">不支持分布式事务</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Seata</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">限流和降级</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Sentinel</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Hystrix</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Sentinel</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">分布式追踪和监控</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Skywalking</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Spring Cloud Sleuth + Zipkin</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">SkyWalking或Sentinel Dashboard</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">微服务网格</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Dubbo Mesh</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">不支持微服务网格</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Service Mesh(Nacos+Dubbo Mesh)</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">社区活跃度</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">相对较高</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">目前较低</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">相对较高</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">孵化和成熟度</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">孵化较早,成熟度较高</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">成熟度较高</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">孵化较新,但迅速发展</td> </tr> </tbody> </table> </section> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Alibaba 是 Spring Cloud 的另一个子项目,与阿里巴巴的分布式应用开发框架相关。它提供了一整套与 Alibaba 生态系统集成的解决方案。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 该项目包括 Nacos(服务注册与发现、配置管理)、Sentinel(流量控制、熔断降级)、RocketMQ(消息队列)等组件,以及与 Alibaba Cloud(阿里云)的集成。它为构建基于 Spring Cloud 的微服务架构提供了丰富的选项。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 据说SpringCloud Alibaba项目的发起人已经跑路去了腾讯,并发起了SpringCloud Tecent项目,社区发展存在隐忧。 </section></li> </ul> </ol> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgba(0, 0, 0, 0.05);padding: 10px 10px 10px 1em;margin-bottom: 20px;margin-top: 20px;border-left-width: 2px;border-left-color: rgb(136, 136, 136);border-right: 2px solid rgb(136, 136, 136);color: rgb(119, 119, 119);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;text-align: justify;color: black;line-height: 26px;">在面试中,微服务一般主要讨论的是Spring Cloud Netflix,其次是Spring Cloud Alibaba,Dubbo更多的是作为一个RPC框架来问。</p> </blockquote> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;color: rgb(0, 150, 136);padding-left: 10px;margin: 1em auto;border-left: 3px solid rgb(0, 150, 136);">4.说下微服务有哪些组件?</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">微服务给系统开发带来了一些问题和挑战,如服务调用的复杂性、分布式事务的处理、服务的动态管理等。为了更好地解决这些问题和挑战,各种微服务治理的组件应运而生,充当微服务架构的基石和支撑。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.7685185185185185" src="/upload/99350e76ce2f763ed5ce5ba6594941f8.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;" data-imgfileid="100004548"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> 微服务组件示意图 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">微服务的各个组件和常见实现:</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;line-height: 26px;color: rgb(1, 1, 1);"> 注册中心:用于服务的注册与发现,管理微服务的地址信息。常见的实现包括: </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Netflix:Eureka、Consul </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Alibaba:Nacos </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 配置中心:用于集中管理微服务的配置信息,可以动态修改配置而不需要重启服务。常见的实现包括: </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Netflix:Spring Cloud Config </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Alibaba:Nacos Config </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 远程调用:用于在不同的微服务之间进行通信和协作。常见的实现保包括: </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> RESTful API:如RestTemplate、Feign </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> RPC(远程过程调用):如Dubbo、gRPC </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> API网关:作为微服务架构的入口,统一暴露服务,并提供路由、负载均衡、安全认证等功能。常见的实现包括: </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Netflix:Zuul、Gateway </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Alibaba:Gateway、Apisix等 </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 分布式事务:保证跨多个微服务的一致性和原子性操作。常见的实现包括: </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Alibaba:Seata </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 熔断器:用于防止微服务之间的故障扩散,提高系统的容错能力。常见的实现包括: </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Netflix:Hystrix </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Alibaba:Sentinel、Resilience4j </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 限流和降级:用于防止微服务过载,对请求进行限制和降级处理。常见的实现包括: </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Netflix:Hystrix </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Alibaba:Sentinel </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 分布式追踪和监控:用于跟踪和监控微服务的请求流程和性能指标。常见的实现包括: </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: disc;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Netflix:Spring Cloud Sleuth + Zipkin </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Spring Cloud Alibaba:SkyWalking、Sentinel Dashboard </section></li> </ul> </ol> <h1 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 1.8em;color: rgb(0, 150, 136);margin: 1.2em auto;text-align: center;border-bottom: 1px solid rgb(0, 150, 136);">注册中心</h1> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;color: rgb(0, 150, 136);padding-left: 10px;margin: 1em auto;border-left: 3px solid rgb(0, 150, 136);">5.注册中心是用来干什么的?</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">注册中心是用来管理和维护分布式系统中各个服务的地址和元数据的组件。它主要用于实现<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(0, 150, 136);">服务发现</code>和<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(0, 150, 136);">服务注册</code>功能。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5287037037037037" src="/upload/cf3896a6c789eb0dd9c4c414f003ed7c.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;" data-imgfileid="100004549"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> 注册中心示意图 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">总结一下注册中心的作用:</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;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">服务注册</strong>:各个服务在启动时向注册中心注册自己的网络地址、服务实例信息和其他相关元数据。这样,其他服务就可以通过注册中心获取到当前可用的服务列表。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">服务发现</strong>:客户端通过向注册中心查询特定服务的注册信息,获得可用的服务实例列表。这样客户端就可以根据需要选择合适的服务进行调用,实现了服务间的解耦。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">负载均衡</strong>:注册中心可以对同一服务的多个实例进行负载均衡,将请求分发到不同的实例上,提高整体的系统性能和可用性。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">故障恢复</strong>:注册中心能够监测和检测服务的状态,当服务实例发生故障或下线时,可以及时更新注册信息,从而保证服务能够正常工作。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">服务治理</strong>:通过注册中心可以进行服务的配置管理、动态扩缩容、服务路由、灰度发布等操作,实现对服务的动态管理和控制。 </section></li> </ol> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;color: rgb(0, 150, 136);padding-left: 10px;margin: 1em auto;border-left: 3px solid rgb(0, 150, 136);">6.SpringCloud可以选择哪些注册中心?</h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">SpringCloud可以与多种注册中心进行集成,常见的注册中心包括:</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;line-height: 26px;color: rgb(1, 1, 1);"> Eureka:Eureka 是 Netflix 开源的服务发现框架,具有高可用、弹性、可扩展等特点,并与 Spring Cloud 集成良好。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Consul:Consul 是一种分布式服务发现和配置管理系统,由 HashiCorp 开发。它提供了服务注册、服务发现、健康检查、键值存储等功能,并支持多数据中心部署。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> ZooKeeper:ZooKeeper 是 Apache 基金会开源的分布式协调服务,可以用作服务注册中心。它具有高可用、一致性、可靠性等特点。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Nacos:Nacos 是阿里巴巴开源的一个动态服务发现、配置管理和服务管理平台。它提供了服务注册和发现、配置管理、动态 DNS 服务等功能。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> etcd:etcd 是 CoreOS 开源的一种分布式键值存储系统,可以被用作服务注册中心。它具有高可用、强一致性、分布式复制等特性。 </section></li> </ol> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;color: rgb(0, 150, 136);padding-left: 10px;margin: 1em auto;border-left: 3px solid rgb(0, 150, 136);">7.说下Eureka、ZooKeeper、Nacos的区别?</h2> <section data-tool="mdnice编辑器" style="overflow-x: auto;"> <table> <thead> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <th style="text-align: left;border-top-width: 1px;border-top-color: rgb(0, 150, 136);border-right-color: rgb(0, 150, 136);border-left-color: rgb(0, 150, 136);background-color: rgb(0, 150, 136);color: rgb(248, 248, 248);border-bottom: 0px;min-width: 85px;">特性</th> <th style="text-align: left;border-top-width: 1px;border-top-color: rgb(0, 150, 136);border-right-color: rgb(0, 150, 136);border-left-color: rgb(0, 150, 136);background-color: rgb(0, 150, 136);color: rgb(248, 248, 248);border-bottom: 0px;min-width: 85px;">Eureka</th> <th style="text-align: left;border-top-width: 1px;border-top-color: rgb(0, 150, 136);border-right-color: rgb(0, 150, 136);border-left-color: rgb(0, 150, 136);background-color: rgb(0, 150, 136);color: rgb(248, 248, 248);border-bottom: 0px;min-width: 85px;">ZooKeeper</th> <th style="text-align: left;border-top-width: 1px;border-top-color: rgb(0, 150, 136);border-right-color: rgb(0, 150, 136);border-left-color: rgb(0, 150, 136);background-color: rgb(0, 150, 136);color: rgb(248, 248, 248);border-bottom: 0px;min-width: 85px;">Nacos</th> </tr> </thead> <tbody style="border-width: 0px;border-style: initial;border-color: initial;"> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">开发公司</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Netflix</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Apache 基金会</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">阿里巴巴</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">CAP</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">AP(可用性和分区容忍性)</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">CP(一致性和分区容忍性)</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">既支持AP,也支持CP</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">功能</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">服务注册与发现</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">分布式协调、配置管理、分布式锁</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">服务注册与发现、配置管理、服务管理</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">定位</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">适用于构建基于 HTTP 的微服务架构</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">通用的分布式协调服务框架</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">适用于微服务和云原生应用</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">访问协议</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">HTTP</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">TCP</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">HTTP/DNS</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">自我保护</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">支持</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">-</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">支持</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">数据存储</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">内嵌数据库、多个实例形成集群</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">ACID 特性的分布式文件系统 ZAB 协议</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">内嵌数据库、MySQL 等</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">健康检查</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Client Beat</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">Keep Alive</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">TCP/HTTP/MYSQL/Client Beat</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">特点</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">简单易用、自我保护机制</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">高性能、强一致性</td> <td style="border-color: rgb(0, 150, 136);min-width: 85px;">动态配置管理、流量管理、灰度发布等</td> </tr> </tbody> </table> </section> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">可以看到Eureka和ZooKeeper的最大区别是一个支持<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(0, 150, 136);">AP</code>,一个支持<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(0, 150, 136);">CP</code>,Nacos既支持既支持<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(0, 150, 136);">AP</code>,也支持<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(0, 150, 136);">CP</code>。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;color: rgb(0, 150, 136);padding-left: 10px;margin: 1em auto;border-left: 3px solid rgb(0, 150, 136);">8.Eureka实现原理了解吗?</h2> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6814814814814815" src="/upload/477cde58e9553323d74ebac6d5a63079.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;" data-imgfileid="100004553"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> Eureka原理示意图 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">Eureka的实现原理,大概可以从这几个方面来看:</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;line-height: 26px;color: rgb(1, 1, 1);"> 服务注册与发现: 当一个服务实例启动时,它会向Eureka Server发送注册请求,将自己的信息注册到注册中心。Eureka Server会将这些信息保存在内存中,并提供REST接口供其他服务查询。服务消费者可以通过查询服务实例列表来获取可用的服务提供者实例,从而实现服务的发现。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 服务健康检查: Eureka通过心跳机制来检测服务实例的健康状态。服务实例会定期向Eureka Server发送心跳,也就是续约,以表明自己的存活状态。如果Eureka Server在一定时间内没有收到某个服务实例的心跳,则会将其标记为不可用,并从服务列表中移除,下线实例。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 服务负载均衡: Eureka客户端在调用其他服务时,会从本地缓存中获取服务的注册信息。如果缓存中没有对应的信息,则会向Eureka Server发送查询请求。Eureka Server会返回一个可用的服务实例列表给客户端,客户端可以使用负载均衡算法选择其中一个进行调用。 </section></li> </ol> <blockquote
作者:じ☆ve宝贝
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding-right: 10px;padding-left: 10px;line-height: 1.6;word-break: break-word;text-align: left;font-size: 15px;letter-spacing: 0.05em;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;" data-mpa-powered-by="yiban.io"> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding-right: 10px;padding-left: 10px;line-height: 1.6;word-break: break-word;text-align: left;font-size: 15px;letter-spacing: 0.05em;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;"> <blockquote data-tool="mdnice编辑器" style="margin: 10px 5px;padding: 10px 10px 10px 20px;border-left-color: rgb(53, 179, 120);color: rgb(97, 97, 97);font-size: 0.9em;border-top: none;border-bottom: none;overflow: auto;border-right: 0px solid rgb(53, 179, 120);quotes: none;background: rgb(251, 249, 253);"> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 16px;color: black;line-height: 26px;">DDD & 微服务系列第19篇</p> </blockquote> </section> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">在构建微服务架构时,Spring Cloud Gateway作为一个重要的微服务网关,经常需要在过滤器(Filter)中对POST请求的Body内容进行操作,如日志记录、签名验证和权限验证等。然而,由于Request的Body只能读取一次,如果直接在过滤器中读取而不进行封装,可能导致后续服务无法获取数据。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">网上搜这个问题的解决方案,大多数文章都是告诉你写一个Filter将Request的Body缓存起来。这种方法确实可以,只不过需要对代码经过充分压力测试,否则很有可能出现如下所示的堆外内存溢出问题。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">reactor.netty.ReactorNetty$InternalNettyException: io.netty.util.internal.OutOfDirectMemoryError:failed to allocate<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">实际上,Spring Cloud Gateway已经内置了<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 179, 120);">AdaptCachedBodyGlobalFilter</code>过滤器,它在Exchange中巧妙地缓存了Request的Body,避免了直接读取导致的一系列问题。这种方式更为稳妥,避免了潜在的内存溢出风险。</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="100034649" data-ratio="0.49166666666666664" src="/upload/7a0bc174d36f3cc68e677b08de799f13.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> image-20231125210759846 </figcaption> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">在需要获取Body的地方,我们只需要通过以下方法即可:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">DataBuffer body = exchange.getAttributeOrDefault(<span style="color: #50a14f;line-height: 26px;">"cachedRequestBody"</span>, <span style="color: #a626a4;line-height: 26px;">null</span>);<br>String bodyStr = body.toString(StandardCharsets.UTF_8);<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">只不过通过源码可以看出,缓存RequestBody需要路由被标记为需要缓存,也就是<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 179, 120);">this.routesToCache.containsKey(rouceId)</code>方法必须返回true。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 179, 120);">AdaptCachedBodyGlobalFilter</code>会监听<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 179, 120);">EnableBodyCachingEvent</code>事件,当发布该事件时就将RouteId放入<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 179, 120);">routesToCache</code>中。为了方便使用,我们可以编写一个配置类,在初始化时发布<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 179, 120);">EnableBodyCachingEvent</code>事件,将所有路由都启用缓存功能。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #4078f2;line-height: 26px;">@Configuration</span>(proxyBeanMethods = <span style="color: #a626a4;line-height: 26px;">false</span>)<br><span style="color: #4078f2;line-height: 26px;">@Slf</span>4j<br><span style="color: #a626a4;line-height: 26px;">public</span> <span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span> <span style="color: #c18401;line-height: 26px;">EnableCachedBodyConfiguration</span> </span>{<br> <br> <span style="color: #4078f2;line-height: 26px;">@Resource</span><br> <span style="color: #a626a4;line-height: 26px;">private</span> ApplicationEventPublisher publisher;<br> <br> <span style="color: #4078f2;line-height: 26px;">@Resource</span><br> <span style="color: #a626a4;line-height: 26px;">private</span> GatewayProperties gatewayProperties;<br> <br> <span style="color: #4078f2;line-height: 26px;">@PostConstruct</span><br> <span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span> <span style="color: #a626a4;line-height: 26px;">void</span> <span style="color: #4078f2;line-height: 26px;">init</span><span style="line-height: 26px;">()</span> </span>{<br> gatewayProperties.getRoutes().forEach(routeDefinition -> {<br> <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 对 spring cloud gateway 路由配置中的每个路由都启用 AdaptCachedBodyGlobalFilter</span><br> EnableBodyCachingEvent enableBodyCachingEvent = <span style="color: #a626a4;line-height: 26px;">new</span> EnableBodyCachingEvent(<span style="color: #a626a4;line-height: 26px;">new</span> Object(), routeDefinition.getId());<br> publisher.publishEvent(enableBodyCachingEvent);<br> });<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">通过这种方式,我们可以更加方便地处理POST请求的Body内容,避免了一些常见的问题。在实际应用中,考虑到稳定性和性能,这种解决方案提供了一种更为可靠的选择。</p> </section>
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">前面两篇文章介绍了Nacos的基础、注册中心的原理,如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;list-style-type: disc;color: #f83929;font-size: 16px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&mid=2247493854&idx=1&sn=4b3fb7f7e17a76000733899f511ef915&chksm=fcf73713cb80be05fe4473390f946dfbaf77848d7041c30f069bcb5a3629be782f4b1121bd6a&scene=21&cur_album_id=2042874937312346114#wechat_redirect" style="text-decoration: none;word-wrap: break-word;color: rgb(248, 57, 41);font-weight: 400;border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">五十五张图告诉你微服务的灵魂摆渡者Nacos究竟有多强?</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <a href="https://mp.weixin.qq.com/s?__biz=MzU3MDAzNDg1MA==&mid=2247514933&idx=1&sn=374da0ea32321baf6938ff2e611d8fce&chksm=fcf764f8cb80edee2a0c493f58570b1502fb093ccd38fd498de1f6c1213e24e0355d8bcd713f&token=913464299&lang=zh_CN&sc
作者:微信小助手
<section class="mp_profile_iframe_wrp"> <mp-common-profile class="custom_select_card mp_profile_iframe mp_common_widget" data-pluginname="mp-common-profile" data-from="0" data-id="MzIwNTczMDkyMg==" data-alias="itbigboy" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/EhMglquf8XNYZo3ew5c6Oe8IjlhVxWBlcCEYpXkNgzbqKalV5tgkMjjpm7cuJnXyFYWP5BRRLOuKoKHJaOugicw/0?wx_fmt=png" data-nickname="技术老男孩" data-signature="分享技术路上的点滴,专注于后端技术,助力开发者成长,欢迎关注。"></mp-common-profile> </section> <p><br></p> <p style="text-align: right;" data-mpa-powered-by="yiban.io"><span style="color: rgb(136, 136, 136);font-size: 13px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.034em;"></span></p> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding-right: 10px;padding-left: 10px;text-align: left;line-height: 1.6;letter-spacing: 0.034em;font-size: 16px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;margin-bottom: 0px;"> <ul data-tool="mdnice编辑器" style="color: black;margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 一. 索引介绍 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 二. 索引分类 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 三. 性能分析 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 四. 查询优化 </section></li> </ul> <hr data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;height: 1px;border-width: initial;border-style: none;border-color: initial;text-align: center;background-image: linear-gradient(to right, rgba(93, 186, 133, 0), rgba(93, 186, 133, 0.75), rgba(93, 186, 133, 0));"> <h2 data-tool="mdnice编辑器" style="color: black;font-weight: bold;font-size: 22px;text-align: center;background-image: url("https://mmbiz.qpic.cn/sz_mmbiz_png/knmrNHnmCLFRkO3swqicuPN2fpdiaeyicVjC76SvWBYiaPvSc49Z6u50EAl64nMbRWWnNpd4icFZJJOJe1cQ4iaZFNrw/640?wx_fmt=png&amp;from=appmsg&random=0.7088920958047964");background-size: 50px;margin-top: 1em;margin-bottom: 10px;background-position: center center;background-repeat: no-repeat no-repeat;" data-lazy-bgimg="https://mmbiz.qpic.cn/sz_mmbiz_png/knmrNHnmCLFRkO3swqicuPN2fpdiaeyicVjC76SvWBYiaPvSc49Z6u50EAl64nMbRWWnNpd4icFZJJOJe1cQ4iaZFNrw/640?wx_fmt=png&amp;from=appmsg&random=0.7088920958047964" class="" data-fail="0"><span style="display: none;"></span><span style="display: inline-block;height: 38px;line-height: 42px;color: rgb(72, 179, 120);background-position: left center;background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;background-size: 63px;margin-top: 38px;font-size: 18px;margin-bottom: 10px;"><strong style="line-height: 1.75em;color: rgb(74, 74, 74);">一. 索引介绍</strong></span></h2> <h4 data-tool="mdnice编辑器" style="color: black;margin-bottom: 15px;font-weight: bold;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>1.1 什么是Mysql索引<span style="display: none;"></span></h4> <ul data-tool="mdnice编辑器" style="color: black;margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> MySQL官方对于索引的定义:索引是帮助MySQL高效获取数据的数据结构。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> MySQL在存储数据之外,数据库系统中还维护着满足特定查找算法的数据结构,这些数据结构以某种引用(指向)表中的数据,这样我们就可以通过数据结构上实现的高级查找算法来快速找到我们想要的数据。而这种数据结构就是索引。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 简单理解为“排好序的可以快速查找数据的数据结构”。 </section></li> </ul> <h4 data-tool="mdnice编辑器" style="color: black;margin-bottom: 15px;font-weight: bold;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>1.2 索引数据结构<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="color: rgb(74, 74, 74);padding-bottom: 8px;padding-top: 1em;line-height: 1.75em;">下图是二叉树的索引方式:</p> <p><img class="rich_pages wxw-img" data-imgfileid="100039954" data-ratio="0.7888888888888889" src="/upload/706c907600840c4ea0e1eb09ccb78dbd.png" data-type="png" data-w="1080" style="-webkit-touch-callout: none;-webkit-user-select: none;width: 335px !important;height: auto !important;visibility: visible !important;" width="335px"></p> <figure data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <figcaption style="margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 12px;font-family: PingFangSC-Light;"> 图片 </figcaption> </figure> <ul data-tool="mdnice编辑器" style="color: black;margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 二叉树数据结构的弊端:当极端情况下,数据递增插入时,会一直向右插入,形成链表,查询效率会降低。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> MySQL中常用的的索引数据结构有BTree索引(Myisam普通索引),B+Tree索引(Innodb普通索引),Hash索引(memory存储引擎)等等。 </section></li> </ul> <h4 data-tool="mdnice编辑器" style="color: black;margin-bottom: 15px;font-weight: bold;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>1.3 索引优势<span style="display: none;"></span></h4> <ul data-tool="mdnice编辑器" style="color: black;margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 提高数据检索的效率,降低数据库的IO成本。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 通过索引对数据进行排序,降低数据排序的成本,降低了CPU的消耗。 </section></li> </ul> <h4 data-tool="mdnice编辑器" style="color: black;margin-bottom: 15px;font-weight: bold;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>1.4 索引劣势<span style="display: none;"></span></h4> <ul data-tool="mdnice编辑器" style="color: black;margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 索引实际上也是一张表,保存了主键和索引的字段,并且指向实体表的记录,所以索引也是需要占用空间的。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 在索引大大提高查询速度的同时,却会降低表的更新速度,在对表进行数据增删改的同时,MySQL不仅要更新数据,还需要保存一下索引文件。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 每次更新添加了的索引列的字段,都会去调整因为更新带来的减值变化后的索引的信息。 </section></li> </ul> <h4 data-tool="mdnice编辑器" style="color: black;margin-bottom: 15px;font-weight: bold;font-size: 18px;margin-top: 30px;"><span style="display: none;"></span>1.5 索引使用场景<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="color: rgb(74, 74, 74);padding-bottom: 8px;padding-top: 1em;line-height: 1.75em;"><strong style="line-height: 1.75em;">哪些情况需要创建索引:</strong></p> <ul data-tool="mdnice编辑器" style="color: black;margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 主键自动建立唯一索引 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 频繁作为查询条件的字段应该创建索引(where 后面的语句) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 查询中与其它表关联的字段,外键关系建立索引 </section></li> <li> <section st
作者:微信小助手
<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;" powered-by="xiumi.us"> <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;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">作者:vivo 互联网服务器团队 - Zhou Shaobin</p> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <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;" powered-by="xiumi.us"> <section style="font-size: 14px;text-align: justify;line-height: 1.8;padding-right: 5px;padding-left: 5px;color: rgb(160, 160, 160);"> <p style="text-wrap: wrap;">本文主要介绍了Spring事务传播性的相关知识。<br><br></p> <p style="text-wrap: wrap;">Spring中定义了7种事务传播性:</p> <p style="text-wrap: wrap;"><br></p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="text-wrap: wrap;"><span style="color: rgb(160, 160, 160);font-size: 14px;letter-spacing: 0.578px;text-wrap: wrap;">PROPAGATION_REQUIRED </span></p></li> <li><p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;">PROPAGATION_SUPPORTS</span></p></li> <li><p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;">PROPAGATION_MANDATORY</span></p></li> <li><p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;">PROPAGATION_REQUIRES_NEW</span></p></li> <li><p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;">PROPAGATION_NOT_SUPPORTED</span></p></li> <li><p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;">PROPAGATION_NEVER</span></p></li> <li><p style="text-wrap: wrap;"><span style="letter-spacing: 0.034em;">PROPAGATION_NESTED</span></p></li> </ul> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">在Spring环境中,含有事务的方法嵌套调用,事务是如何传递的规则,以及每种规则是如何开展工作的。文章还提到每种事务传播性是如何使用的,方便读者依据实际的场景,使用不同的事务规则。</p> </section> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>一、什么是Spring事务的传播性</p> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">Spring 事务传播性是指, 在Spring的环境中,当多个含有事务的方法嵌套调用时,每个事务方法都处于自己事务的上下文中,其提交或者回滚行为应该如何处理。</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">通俗讲,就是当一个事务方法调用另外一个事务方法时,事务如何跨上下文传播。</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014182" data-ratio="0.4357142857142857" data-s="300,640" src="/upload/3e9586a170d3100885f6ecb29f5d86ba.jpg" data-type="jpeg" data-w="700" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section powered-by="xiumi.us"> <p style="text-wrap: wrap;">1)当事务方法A调用事务方法B时,事务方法B是合并到事务方法A中,还是开启新事务?</p> <p style="text-wrap: wrap;">2)当事务方法B抛出异常时 ,在合并事务或者开启新的事务的场景中,事务的回滚是如何处理的 ?</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">以上事务的处理规则,都取决于事务传播级别的设置。</p> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>二、事务的传播性都有哪些行为</p> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014183" data-ratio="0.6296829971181557" data-s="300,640" src="/upload/7b745db69c6ac11c807435a10e4cc7c0.jpg" data-type="jpeg" data-w="694" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">事务的传播行为,主要分为三种类型,分别是:<strong>支持当前事务</strong>、<strong>不支持当前事务</strong>、<strong>嵌套事务</strong>。</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><span style="font-size: 16px;color: rgb(65, 95, 255);">2.1 支持当前事务</span></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section powered-by="xiumi.us"> <p style="text-wrap: wrap;"><strong>REQUIRED</strong>:默认的事务传播级别,表示如果当前方法已在事务内,该方法就在当前事务中执行,否则,开启一个新的事务并在其上下文中执行。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>SUPPORTED</strong>:当前方法在事务内,则在其上下文中执行该方法,否则,开启一个新的事务。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>MANDATORY</strong>:必须在事务中执行,否则,将抛出异常。</p> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><span style="font-size: 16px;color: rgb(65, 95, 255);">2.2 不支持当前事务</span></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section powered-by="xiumi.us"> <p style="text-wrap: wrap;"><strong>REQUIRES_NEW</strong>:无论当前是否有事务上下文,都会开启一个事务 。如果已经有一个事务在执行 ,则正在执行的事务将被挂起 ,新开启的事务会被执行。<br></p> <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;"><strong>NOT_SUPPORTED</strong>:不支持事务,如果当前存在事务上下文,则挂起当前事务,然后以非事务的方式执行。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>NEVER</strong>:不能在事务中执行,如果当前存在事务上下文,则抛出异常。</p> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><span style="font-size: 16px;color: rgb(65, 95, 255);">2.3 嵌套事务</span></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><strong>NESTED</strong>:嵌套事务,如果当前已存在一个事务的上下文中,则在嵌套事务中执行,如果抛异常,则回滚嵌套事务,而不影响其他事务的操作。</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>三、每种事务的传播性如何工作</p> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><span style="font-size: 16px;color: rgb(65, 95, 255);">3.1 REQUIRED </span></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <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: justify;line-height: 1.8;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">默认的事务传播行为,保证多个嵌套的事务方法在同一个事务内执行,并且同时提交,或者出现异常时,同时回滚。</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;"></span><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;">这个机制可以满足大多数业务场景。</span></p> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014184" data-ratio="0.577490774907749" data-s="300,640" src="/upload/d6ca43f8d96b789d9446ed853978a7a5.jpg" data-type="jpeg" data-w="542" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;" powered-by="xiumi.us"> <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);text-align: justify;" powered-by="xiumi.us"> <p style="text-wrap: wrap;"> 例子 :</p> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014185" data-ratio="0.4429447852760736" data-s="300,640" src="/upload/bbe3010a6fafaf26ed858f924e90e4e3.jpg" data-type="jpeg" data-w="815" style=""></p> <section powered-by="xiumi.us"> <p style="text-wrap: wrap;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014186" data-ratio="0.3899755501222494" data-s="300,640" src="/upload/6091e4de1fb0973b6eaf407625234ff2.jpg" data-type="jpeg" data-w="818" style=""></p> <p style="text-wrap: wrap;">1)类TestAService的方法通过声明式事务的方式,加上了事务注解@Transactional ,并设置事务的传播性为REQUIRED。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">2)调用者调用TestAService的A方法时,如果调用者没有开启事务,那么A方法会开启一个事务。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">A方法的具体执行过程如下 :</p> <p style="text-wrap: wrap;">a. 执行insert,但没有提交;</p> <p style="text-wrap: wrap;">b.调用TestBServcie的B方法,由于B方法也声明了事务,并且传播性是REQUIRED,所以方法B的事务,合并到方法A开启的事务中。</p> <p style="text-wrap: wrap;">c.方法B执行insert操作,此时也没有提交。<br><br></p> <p style="text-wrap: wrap;">3)由于这两个方法的操作都在同一个事务中执行,当这两个方法所有操作执行成功之后,提交事务。</p> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">嵌套调用链路:</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014187" data-ratio="0.13892078071182548" data-s="300,640" src="/upload/23ba60784115f47a6afe98d43189d083.jpg" data-type="jpeg" data-w="871" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;" powered-by="xiumi.us"> <section style="display: inline-block;width: 100%;vertical-align: top;border-left: 3px solid rgb(70, 97, 246);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;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">当方法B 执行时抛出了 Exception 异常后,事务是如何处理的 ?</p> </section> </section> </section> <p>1)方法B声明了事务,insert操作会回滚</p> <p>2)由于方法A和方法B 同属一个事务,方法A也会执行回滚,由此说明该规则保证了事务的原子性。</p> <p><br></p> <p>嵌套调用,异常后的链路:</p> <p><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014188" data-ratio="0.15219842164599776" data-s="300,640" src="/upload/02a0eba3cd92bbebf11dfca37bb9f046.jpg" data-type="jpeg" data-w="887" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section powered-by="xiumi.us" style="margin-top: 10px;margin-bottom: 8px;font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="padding-left: 8px;display: inline-block;width: 578px;vertical-align: top;border-left: 3px solid rgb(70, 97, 246);border-bottom-left-radius: 0px;align-self: flex-start;flex: 0 0 auto;"> <section powered-by="xiumi.us" style="color: rgb(62, 62, 62);text-align: justify;"> <p>如果 方法B 抛出异常后,方法A 使用 try-catch 处理了方法B的异常(如下代码),并没有向外抛出,此时事务又如何处理的 ?</p> </section> </section> </section> <p powered-by="xiumi.us" style="margin-bottom: 0px;font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014189" data-ratio="0.33291457286432163" data-s="300,640" src="/upload/af534193f99c009189f720bcacb7334a.jpg" data-type="jpeg" data-w="796" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section powered-by="xiumi.us"> <p style="text-wrap: wrap;">方法A也会回滚。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">从事务的特性我们可知,事务具有原子性。方法A和方法B同属一个事务,当方法B抛出异常,触发回滚操作后,整个事务的操作都会回滚。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">因此,Spring 在处理事务过程中,当事务的传播性设置为REQUIRED,在整个事务的调用链上,任何一个环节抛出的异常都会导致全局回滚。</p> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><span style="font-size: 16px;color: rgb(65, 95, 255);">3.2 REQUIRES_ NEW</span></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <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: justify;line-height: 1.8;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">每次都开启一 个新的事务。</p> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014190" data-ratio="0.4325153374233129" data-s="300,640" src="/upload/bf5a211187ef63f728346eb9294f7424.jpg" data-type="jpeg" data-w="652" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;" powered-by="xiumi.us"> <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);text-align: justify;" powered-by="xiumi.us"> <p style="text-wrap: wrap;"> 例子:</p> </section> </section> </section> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014191" data-ratio="0.20330368487928843" data-s="300,640" src="/upload/78316d20531643f5e8c21287e262ca52.jpg" data-type="jpeg" data-w="787" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">上面例子中,方法B的传播性设置为 REQUIRES_NEW,方法A仍然是REQUIRED,当A调用B时,具体调用链路如下:</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014193" data-ratio="0.2803203661327231" data-s="300,640" src="/upload/37615fb61050c6ed32db910bb021f8b6.jpg" data-type="jpeg" data-w="874" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">具体执行过程:</p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: right;" powered-by="xiumi.us"> <section style="display: inline-block;width: 100%;border-width: 1px;border-style: solid;border-color: transparent;padding: 10px;background-color: rgb(239, 239, 239);height: auto;"> <section style="text-align: justify;" powered-by="xiumi.us"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;background-color: rgb(239, 239, 239);">方法A被执行前,如果调用者没有开启事务,方法A开启一个事务1,然后执行insert ,此时没有提交;</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;background-color: rgb(239, 239, 239);">方法B的事务传播性设置为REQUIRES_NEW,当被方法A调用时,此时方法A的事务1会被挂起,方法B开启自己的事务2,然后执行insert,此时并没有提交;</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;background-color: rgb(239, 239, 239);">当方法B执行完毕后,提交事务2;</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;background-color: rgb(239, 239, 239);">恢复事务1,最终提交。</span><span style="letter-spacing: 0.034em;"></span></p></li> </ul> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;" powered-by="xiumi.us"> <section style="display: inline-block;width: 100%;vertical-align: top;border-left: 3px solid rgb(70, 97, 246);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;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">当 方法B 执行时抛出了异常,会发生什么?</p> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us">方法B的insert操作会被回滚掉,方法A不受影响。但这里有个前提,方法A需要try-catch方法B的异常,使其异常不会往上传递,从而导致方法A接收到异常,导致回滚。</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014194" data-ratio="0.2877939529675252" data-s="300,640" src="/upload/19cd4bb166923f09fbf9d47607cd324d.jpg" data-type="jpeg" data-w="893" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><span style="font-size: 16px;color: rgb(65, 95, 255);">3.3 SUPPORTED</span></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <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: justify;line-height: 1.8;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">当外层方法A存在事务,方法B加入到当前事务中,以事务的方式执行。</p> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014195" data-ratio="0.559748427672956" data-s="300,640" src="/upload/ae591b3678d0071241bbdca321917cd7.jpg" data-type="jpeg" data-w="636" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <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: justify;line-height: 1.8;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">当外层方法A不存在事务,方法B不会创建新的事务,以非事务的方式执行。</p> </section> </section> </section> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014196" data-ratio="0.6865671641791045" data-s="300,640" src="/upload/54336b00886c9a626ed326bc6f21df6f.jpg" data-type="jpeg" data-w="536" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;" powered-by="xiumi.us"> <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);text-align: justify;" powered-by="xiumi.us"> <p style="text-wrap: wrap;"> 例子1:</p> </section> </section> </section> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014197" data-ratio="0.23020527859237536" data-s="300,640" src="/upload/2b8ff4ae820163e0a4d07645e69d5d3a.jpg" data-type="jpeg" data-w="682" style=""></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014198" data-ratio="0.1793103448275862" data-s="300,640" src="/upload/355ae68496c60c907b9c6ca4449f172d.jpg" data-type="jpeg" data-w="725" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">以上例子,方法A没有加事务注解,方法B的加了事务注解,并且传播为SUPPORTS。</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">具体执行过程:</p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: right;" powered-by="xiumi.us"> <section style="display: inline-block;width: 100%;border-width: 1px;border-style: solid;border-color: transparent;padding: 10px;background-color: rgb(239, 239, 239);height: auto;"> <section style="text-align: justify;" powered-by="xiumi.us"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;background-color: rgb(239, 239, 239);">方法A以非事务的方式执行insert操作。</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;background-color: rgb(239, 239, 239);">方法B被调用,由于其外层事务A没有开启事务,方法B也是以非事务方法执行insert操作。</span></p></li> </ul> </section> </section> </section> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014199" data-ratio="0.21016949152542372" data-s="300,640" src="/upload/740e6889781b8a7392788096c7696cb6.jpg" data-type="jpeg" data-w="590" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;" powered-by="xiumi.us"> <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);text-align: justify;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">例子2:</p> </section> </section> </section> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014200" data-ratio="0.12777777777777777" data-s="300,640" src="/upload/6ea68a828e9f159c4bca21e98c15b7e9.jpg" data-type="jpeg" data-w="1080" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">以上例子,方法A和B都加上了事务注解,其中方法A的传播性为REQUIRED,方法B的传播性为SUPPORTS。</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">具体执行过程:</p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: right;" powered-by="xiumi.us"> <section style="display: inline-block;width: 100%;border-width: 1px;border-style: solid;border-color: transparent;padding: 10px;background-color: rgb(239, 239, 239);height: auto;"> <section style="text-align: justify;" powered-by="xiumi.us"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;background-color: rgb(239, 239, 239);">如果方法A的调用方没有开启事务,则方法A开启事务,并执行insert操作,但没有提交;</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;">方法B被调用,由于其外层方法A开启了事务,因此方法B加入到方法A开启的事务中,并执行insert,但没有提交;</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;">当事务中的所有操作执行成功后,事务提交。</span></p></li> </ul> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014201" data-ratio="0.13157894736842105" data-s="300,640" src="/upload/86a975bae4ac13dbaa03efb7a0e9f3ef.jpg" data-type="jpeg" data-w="798" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><span style="font-size: 16px;color: rgb(65, 95, 255);">3.4 NOT_SUPPORTED</span></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <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: justify;line-height: 1.8;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">不支持事务。</p> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">如果外层方法存在事务,则挂起外层事务,以非事务方式执行,执行完毕后,恢复外层事务。</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014202" data-ratio="0.4672131147540984" data-s="300,640" src="/upload/5acdaf1693a03561ec7af33828292169.jpg" data-type="jpeg" data-w="610" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;" powered-by="xiumi.us"> <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);text-align: justify;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">例子:</p> </section> </section> </section> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014203" data-ratio="0.12407407407407407" data-s="300,640" src="/upload/8c875d4d94a76eb816a77201970d4c13.jpg" data-type="jpeg" data-w="1080" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">以上例子:方法A和B都加上了事务注解,方法A的传播性为REQUIRED,方法B为NOT_SUPPORTED。</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">具体执行过程:</p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: right;" powered-by="xiumi.us"> <section style="display: inline-block;width: 100%;border-width: 1px;border-style: solid;border-color: transparent;padding: 10px;background-color: rgb(239, 239, 239);height: auto;"> <section style="text-align: justify;" powered-by="xiumi.us"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;background-color: rgb(239, 239, 239);">如A的调用方没有开启事务,方法A开启事务,并执行insert,但没有提交。</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;">方法A调用方法B时,方法B的传播性为NOT_SUPPORTED,不支持事务,然后挂起外层方法A的事务,方法B以非事务的方式执行insert。</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;">方法B执行完毕后,恢复方法A的事务,最终提交事务。</span></p></li> </ul> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">调用链路过程:</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014204" data-ratio="0.1400208986415883" data-s="300,640" src="/upload/71a248b089347ad75ff1274213dbc6ca.jpg" data-type="jpeg" data-w="957" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><span style="font-size: 16px;color: rgb(65, 95, 255);">3.5 NEVER</span></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <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: justify;line-height: 1.8;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">不支持事务</p> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">当外层方法A开启了事务,方法B抛出异常</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014205" data-ratio="0.578" data-s="300,640" src="/upload/822d87525ed5497d63b5d01c68c1f07c.jpg" data-type="jpeg" data-w="500" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;" powered-by="xiumi.us"> <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);text-align: justify;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">例子:</p> </section> </section> </section> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014206" data-ratio="0.12777777777777777" data-s="300,640" src="/upload/e347fd8dc02077141209fe9724e4151a.jpg" data-type="jpeg" data-w="1080" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">以上代码,两个方法都打上了事务注解,方法A的传播性是REQUIRED,方法B的传播性是NEVER。</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">具体执行过程:</p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: right;" powered-by="xiumi.us"> <section style="display: inline-block;width: 100%;border-width: 1px;border-style: solid;border-color: transparent;padding: 10px;background-color: rgb(239, 239, 239);height: auto;"> <section style="text-align: justify;" powered-by="xiumi.us"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;background-color: rgb(239, 239, 239);">方法A开启事务,执行insert,没有提交。</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;">含有事务的方法A调用方法B,方法B的传播性是NEVER,表示不支持事务,因此方法B抛出异常。</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;">方法A的事务执行回滚。</span></p></li> </ul> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014207" data-ratio="0.26361867704280156" data-s="300,640" src="/upload/99cc2982b33caab17142914262fbdf46.jpg" data-type="jpeg" data-w="1028" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><span style="font-size: 16px;color: rgb(65, 95, 255);">3.6 MANDATORY</span></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <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: justify;line-height: 1.8;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">必须在事务中执行。</p> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">如果外层方法A没有开启事务,方法B抛出异常。</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014208" data-ratio="0.5640535372848948" data-s="300,640" src="/upload/91a611495ef907e90461126a8af5bd2f.jpg" data-type="jpeg" data-w="523" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">如果外层方法A开启了事务,方法B加入事务,方法A&B在同一事务中执行。</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014209" data-ratio="0.6473029045643154" data-s="300,640" src="/upload/f6b9c6d8031b53cb878e95b98ebe879a.jpg" data-type="jpeg" data-w="482" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;" powered-by="xiumi.us"> <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);text-align: justify;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">例子:</p> </section> </section> </section> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014210" data-ratio="0.15" data-s="300,640" src="/upload/97eb53c46e4ddef9881d1844b3919628.jpg" data-type="jpeg" data-w="1080" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">以上例子,方法A没有加事务注解,方法B 的传播性为 MANDATORY。</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">具体执行过程:</p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: right;" powered-by="xiumi.us"> <section style="display: inline-block;width: 100%;border-width: 1px;border-style: solid;border-color: transparent;padding: 10px;background-color: rgb(239, 239, 239);height: auto;"> <section style="text-align: justify;" powered-by="xiumi.us"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;background-color: rgb(239, 239, 239);">方法A的调用方如果本身没有开启事务,方法A执行前不会开启事务。</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;">当非事务方法A调用方法B时,由于方法B的传播性为MANDATORY,必须在事务中执行,条件不满足,抛出异常。</span></p></li> </ul> </section> </section> </section> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014211" data-ratio="0.30708661417322836" data-s="300,640" src="/upload/5390aceebb4a7fa004e4647881e124c9.jpg" data-type="jpeg" data-w="889" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><span style="font-size: 16px;color: rgb(65, 95, 255);">3.7 NESTED</span></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <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: justify;line-height: 1.8;" powered-by="xiumi.us"> <p style="margin-bottom: 10px;text-wrap: wrap;">嵌套事务</p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="margin-bottom: 10px;text-wrap: wrap;">如果外层方法A不存在事务,内层方法B的规则与REQUIRED 一致。</p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;">如果外层方法A存在事务,内层方法B做为外层方法A</span><span style="letter-spacing: 0.034em;">事务的子事务执行,两个方法是一起提交,但子事务是独立回滚。</span></p><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;"></span><span style="letter-spacing: 0.034em;">内层方法B抛出异常,则会回滚方法B的所有操作,但不影响外层事务方法A。</span><span style="letter-spacing: 0.034em;">(方法A需要try-catch子事务,避免异常传递到父层事务)</span></p><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;"></span><span style="letter-spacing: 0.034em;">外层方法A回滚,则内层方法B也会回滚。</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;">该传播性的特点是可以保存状态点,当回滚时,只会回滚到某一个状态点,保证了子事务之间的独立性,避免嵌套事务的全局回滚。</span></p></li> </ul> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014212" data-ratio="0.5464949928469242" data-s="300,640" src="/upload/3be3a101d208744575af34773b95e008.jpg" data-type="jpeg" data-w="699" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin: 10px 0% 8px;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;" powered-by="xiumi.us"> <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);text-align: justify;" powered-by="xiumi.us"> <p style="text-wrap: wrap;">例子:</p> </section> </section> </section> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014213" data-ratio="0.22870370370370371" data-s="300,640" src="/upload/f750021ee696e70aee471b1f1e0ad495.jpg" data-type="jpeg" data-w="1080" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">以上例子,方法A的传播性为REQUIRED,方法B为NESTED。</p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">具体执行过程:</p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: right;" powered-by="xiumi.us"> <section style="display: inline-block;width: 100%;border-width: 1px;border-style: solid;border-color: transparent;padding: 10px;background-color: rgb(239, 239, 239);height: auto;"> <section style="text-align: justify;" powered-by="xiumi.us"> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;background-color: rgb(239, 239, 239);">方法A执行时,如调用方没有开启事务,则开启一个事务。</span></p></li> <li><p style="margin-bottom: 10px;">方法B被外层方法A调用时,因为方法B的传播性为NESTED,方法B在此处建立savepoint,标记insert行为。</p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;">当方法B抛出异常,其insert操作会回滚,但只会回滚到savepoint,(前提是方法A要try-catch方法B,使方法B的异常不会往外传递)。</span></p></li> <li><p style="margin-bottom: 10px;text-wrap: wrap;"><span style="letter-spacing: 0.034em;">方法B回滚后,方法A的事务提交。</span></p></li> </ul> </section> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <p style="text-wrap: wrap;" powered-by="xiumi.us">调用链路:</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100014214" data-ratio="0.2857142857142857" data-s="300,640" src="/upload/96f2976503a618e5ed2e12c9307e1068.jpg" data-type="jpeg" data-w="910" style=""></p> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);"> <p>四、总结</p> </section> </section> <p style="text-wrap: wrap;" powered-by="xiumi.us"><br></p> <section powered-by="xiumi.us"> <p style="text-wrap: wrap;">本文解释了Spring框架中的事务传播性,即多个业务方法之间调用时事务如何处理的规则。Spring提供了七种传播级别,如</p> <p style="text-wrap: wrap;">PROPAGATION_REQUIRED、</p> <p style="text-wrap: wrap;">PROPAGATION_REQUIRES_NEW等。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">每种级别都有适用场景和限制,本文提供了一些示例,介绍了声明式事务如何使用,每种事务的规则,产生哪种行为,当方法抛出异常时,事务的提交和回滚是如何被处理的。正确处理事务对于任何企业级应用程序都是必要的,了解Spring事务传播性是构建高效、可靠和可扩展应用程序的关键。</p> </section> </section>
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding-right: 10px;padding-left: 10px;text-align: left;line-height: 1.6;letter-spacing: 0.034em;color: rgb(63, 63, 63);font-size: 16px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;" data-mpa-powered-by="yiban.io"> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;text-align: center;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/ibJZVicC7nz5j01Rq8dRiaeibA4Dv83icOJfgF6fsnf1zcicW2S2CbjLmotySdwmOQ5m0rDw5jdGicpH8gbYNCrjXvlow/640?wx_fmt=png");background-position: center center;background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;background-size: 50px;margin-top: 1em;margin-bottom: 10px;"><span style="display: none;"></span></h2> <section data-mpa-template="t" mpa-from-tpl="t"> <section mpa-from-tpl="t"> <section style="width:100%;text-align:center;" data-width="100%" mpa-from-tpl="t"> <section style="display:inline-block;width:auto;" mpa-from-tpl="t"> <section style="width: 45px;margin-right: auto;margin-bottom: -12px;margin-left: auto;background-color: #fefefe;transform: translateZ(10px);-webkit-transform: translateZ(10px);-moz-transform: translateZ(10px);-ms-transform: translateZ(10px);-o-transform: translateZ(10px);" mpa-from-tpl="t"> <img data-imgfileid="100125173" data-ratio="1" src="/upload/2f587a759a9336e658edf1970c0a8632.png" data-w="100" data-width="100%" style="display: block;width: 100%;"> </section> <section style="display:inline-block;width:auto;height:auto;" mpa-from-tpl="t"> <section style="border-width: 1px;border-style: solid;border-color: rgb(102, 102, 102);height: 40px;" mpa-from-tpl="t"> <br> </section> <section style="margin-top:-37px;margin-right:-3px;margin-bottom:-3px;margin-left:3px;" mpa-from-tpl="t"> <section style="display: inline-block;height: 40px;line-height: 40px;padding-right: 15px;padding-left: 15px;border-width: 1px;border-style: solid;border-color: rgb(255, 116, 128);width: 100%;" data-width="100%" mpa-from-tpl="t"> <p mpa-is-content="t">前言</p> </section> </section> </section> </section> </section> </section> </section> <p><br mpa-from-tpl="t"></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">在java的庞大体系中,其实有很多不错的小工具,也就是我们平常说的:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">轮子</code>。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">如果在我们的日常工作当中,能够将这些轮子用户,再配合一下idea的快捷键,可以极大得提升我们的开发效率。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">今天我决定把一些压箱底的小工具,分享给大家,希望对你有所帮助。</p> <h2 data-tool="mdnice编辑器"><span style="display: none;"></span><br mpa-from-tpl="t"></h2> <section data-mpa-template="t" mpa-from-tpl="t"> <blockquote style="margin: 5px auto;padding: 0px;max-width: 100%;border-width: 0px;border-style: none;border-color: rgb(68, 153, 231);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);line-height: 25.6px;box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;color: rgb(68, 153, 231);" mpa-from-tpl="t"> <section style="margin: 0px auto;padding: 0px;max-width: 100%;border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto;padding: 0px;max-width: 100%;border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <blockquote style="margin: 5px auto;padding: 0px;max-width: 100%;border-width: 0px;border-style: none;border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto;padding: 0px;max-width: 100%;text-align: center;border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto -38px;padding: 0px 15px;max-width: 100%;display: inline-block;background-color: rgb(68, 153, 231);border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <p style="margin-top: 5px;margin-bottom: 5px;max-width: 100%;clear: both;min-height: 1em;color: rgb(68, 153, 231);border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;"><span style="font-family: Arial, Helvetica, sans-serif;"><strong mpa-from-tpl="t"><span style="border-color: rgb(68, 153, 231);color: rgb(255, 255, 255);font-size: 16px;" mpa-is-content="t">1. Collections</span></strong></span></p> </section> <section style="margin: -40px 25px 50px;padding: 0px;max-width: 100%;border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 3px 0px 0px;padding: 10px 0px;max-width: 100%;box-sizing: border-box;display: inline-block;width: 495px;color: rgb(68, 153, 231);float: left;border-color: rgb(68, 153, 231);word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 1em 0px 0px;padding: 0px;max-width: 100%;box-sizing: border-box;border-width: 0px;border-style: none;border-color: rgb(68, 153, 231);clear: both;color: rgb(68, 153, 231);word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px 0px -3px;padding: 0px;max-width: 100%;float: right;border-color: rgb(68, 153, 231);width: 6px;border-radius: 50%;background-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;height: 6px !important;" mpa-from-tpl="t"> <br> </section> <section style="margin: 0px 0px -2px;padding: 0px;max-width: 100%;text-align: left;border-color: rgb(68, 153, 231);width: 6px;border-radius: 50%;background-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;height: 6px !important;" mpa-from-tpl="t"> <br> </section> <section style="margin: -20px 0px 0px;padding: 0px;max-width: 100%;box-sizing: border-box;text-decoration: inherit;color: rgb(68, 153, 231);border-color: rgb(68, 153, 231);word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px;padding: 0px;max-width: 100%;box-sizing: border-box;border-top: 1px solid rgb(68, 153, 231);width: 495px;float: left;border-right-color: rgb(68, 153, 231);border-bottom-color: rgb(68, 153, 231);border-left-color: rgb(68, 153, 231);color: rgb(68, 153, 231);word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <br> </section> </section> </section> </section> </section> </section> </blockquote> </section> </section> </blockquote> </section> <p><br mpa-from-tpl="t"></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">首先出场的是<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">java.util</code>包下的<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">Collections</code>类,该类主要用于操作集合或者返回集合,我个人非常喜欢用它。</p> <h3 data-tool="mdnice编辑器"><br mpa-from-tpl="t"></h3> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-color="rgb(252, 180, 43)" mpa-from-tpl="t"> <section style="border-width: 0px;border-style: none;border-color: initial;" mpa-from-tpl="t"> <section mpa-from-tpl="t"> <section style="display: inline-block;" mpa-from-tpl="t"> <section style="font-size: 16px;padding-right: 10px;padding-left: 10px;color: rgb(73, 73, 73);" mpa-from-tpl="t"> <p mpa-is-content="t"><span style="font-size: 18px;" mpa-is-content="t">1.1 排序</span></p> </section> <section style="background-color: rgb(252, 180, 43);height: 8px;margin-top: -10px;color: rgb(255, 255, 255);" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> </section> </section> </section> </section> </section> </section> <p><span style="color: rgb(74, 74, 74);letter-spacing: 0.034em;">在工作中经常有对集合排序的需求。</span><br></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">看看使用<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">Collections</code>工具是如何实现升序和降序的:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wzJhLVPsrd2AhSLhUIEicrMUzVG13cVd3q6ibe8eX9Ydic9RhbhISLhJY8C9zr6LJTkUXictRoWRiaW6DF3Gic0es9qicrMCLuibTacj/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"> List<Integer> list = <span style="color: #c678dd;line-height: 26px;">new</span> ArrayList<>();<br> list.add(<span style="color: #d19a66;line-height: 26px;">2</span>);<br> list.add(<span style="color: #d19a66;line-height: 26px;">1</span>);<br> list.add(<span style="color: #d19a66;line-height: 26px;">3</span>);<br> Collections.sort(list);<span style="color: #5c6370;font-style: italic;line-height: 26px;">//升序</span><br> System.out.println(list);<br> Collections.reverse(list);<span style="color: #5c6370;font-style: italic;line-height: 26px;">//降序</span><br> System.out.println(list);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">执行结果:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wzJhLVPsrd2AhSLhUIEicrMUzVG13cVd3q6ibe8eX9Ydic9RhbhISLhJY8C9zr6LJTkUXictRoWRiaW6DF3Gic0es9qicrMCLuibTacj/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">[<span style="color: #d19a66;line-height: 26px;">1</span>, <span style="color: #d19a66;line-height: 26px;">2</span>, <span style="color: #d19a66;line-height: 26px;">3</span>]<br>[<span style="color: #d19a66;line-height: 26px;">3</span>, <span style="color: #d19a66;line-height: 26px;">2</span>, <span style="color: #d19a66;line-height: 26px;">1</span>]<br></code></pre> <h3 data-tool="mdnice编辑器"><br mpa-from-tpl="t"></h3> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-color="rgb(252, 180, 43)" mpa-from-tpl="t"> <section style="border-width: 0px;border-style: none;border-color: initial;" mpa-from-tpl="t"> <section mpa-from-tpl="t"> <section style="display: inline-block;" mpa-from-tpl="t"> <section style="font-size: 16px;padding-right: 10px;padding-left: 10px;color: rgb(73, 73, 73);" mpa-from-tpl="t"> <p mpa-is-content="t"><span style="font-size: 18px;" mpa-is-content="t">1.2 获取最大或最小值</span></p> </section> <section style="background-color: rgb(252, 180, 43);height: 8px;margin-top: -10px;color: rgb(255, 255, 255);" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> </section> </section> </section> </section> </section> </section> <p><span style="color: rgb(74, 74, 74);letter-spacing: 0.034em;">有时候需要找出集合中的</span><code style="letter-spacing: 0.034em;font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">最大值</code><span style="color: rgb(74, 74, 74);letter-spacing: 0.034em;">或者</span><code style="letter-spacing: 0.034em;font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">最小值</code><span style="color: rgb(74, 74, 74);letter-spacing: 0.034em;">,这时可以使用Collections的</span><code style="letter-spacing: 0.034em;font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">max</code><span style="color: rgb(74, 74, 74);letter-spacing: 0.034em;">和</span><code style="letter-spacing: 0.034em;font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">min</code><span style="color: rgb(74, 74, 74);letter-spacing: 0.034em;">方法。例如:</span><br></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wzJhLVPsrd2AhSLhUIEicrMUzVG13cVd3q6ibe8eX9Ydic9RhbhISLhJY8C9zr6LJTkUXictRoWRiaW6DF3Gic0es9qicrMCLuibTacj/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">List<Integer> list = <span style="color: #c678dd;line-height: 26px;">new</span> ArrayList<>();<br>list.add(<span style="color: #d19a66;line-height: 26px;">2</span>);<br>list.add(<span style="color: #d19a66;line-height: 26px;">1</span>);<br>list.add(<span style="color: #d19a66;line-height: 26px;">3</span>);<br>Integer max = Collections.max(list);<span style="color: #5c6370;font-style: italic;line-height: 26px;">//获取最大值</span><br>Integer min = Collections.min(list);<span style="color: #5c6370;font-style: italic;line-height: 26px;">//获取最小值</span><br>System.out.println(max);<br>System.out.println(min);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">执行结果:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wzJhLVPsrd2AhSLhUIEicrMUzVG13cVd3q6ibe8eX9Ydic9RhbhISLhJY8C9zr6LJTkUXictRoWRiaW6DF3Gic0es9qicrMCLuibTacj/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #d19a66;line-height: 26px;">3</span><br><span style="color: #d19a66;line-height: 26px;">1</span><br></code></pre> <h3 data-tool="mdnice编辑器"><br mpa-from-tpl="t"></h3> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-color="rgb(252, 180, 43)" mpa-from-tpl="t"> <section style="border-width: 0px;border-style: none;border-color: initial;" mpa-from-tpl="t"> <section mpa-from-tpl="t"> <section style="display: inline-block;" mpa-from-tpl="t"> <section style="font-size: 16px;padding-right: 10px;padding-left: 10px;color: rgb(73, 73, 73);" mpa-from-tpl="t"> <p mpa-is-content="t"><span style="font-size: 18px;" mpa-is-content="t">1.3 转换线程安全集合</span></p> </section> <section style="background-color: rgb(252, 180, 43);height: 8px;margin-top: -10px;color: rgb(255, 255, 255);" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> </section> </section> </section> </section> </section> </section> <p><span style="color: rgb(74, 74, 74);letter-spacing: 0.034em;">我们都知道,java中的很多集合,比如:ArrayList、LinkedList、HashMap、HashSet等,都是线程不安全的。</span><br></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">换句话说,这些集合在多线程的环境中,添加数据会出现异常。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">这时,可以用Collections的<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">synchronizedxxx</code>方法,将这些线程不安全的集合,直接转换成线程安全集合。例如:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wzJhLVPsrd2AhSLhUIEicrMUzVG13cVd3q6ibe8eX9Ydic9RhbhISLhJY8C9zr6LJTkUXictRoWRiaW6DF3Gic0es9qicrMCLuibTacj/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"> List<Integer> list = <span style="color: #c678dd;line-height: 26px;">new</span> ArrayList<>();<br> list.add(<span style="color: #d19a66;line-height: 26px;">2</span>);<br> list.add(<span style="color: #d19a66;line-height: 26px;">1</span>);<br> list.add(<span style="color: #d19a66;line-height: 26px;">3</span>);<br><br> List<Integer> integers = Collections.synchronizedList(list);<span style="color: #5c6370;font-style: italic;line-height: 26px;">//将ArrayList转换成线程安全集合</span><br> System.out.println(integers);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">它的底层会创建<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">SynchronizedRandomAccessList</code>或者<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">SynchronizedList</code>类,这两个类的很多方法都会用<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">synchronized</code>加锁。</p> <h3 data-tool="mdnice编辑器"><br mpa-from-tpl="t"></h3> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-color="rgb(252, 180, 43)" mpa-from-tpl="t"> <section style="border-width: 0px;border-style: none;border-color: initial;" mpa-from-tpl="t"> <section mpa-from-tpl="t"> <section style="display: inline-block;" mpa-from-tpl="t"> <section style="font-size: 16px;padding-right: 10px;padding-left: 10px;color: rgb(73, 73, 73);" mpa-from-tpl="t"> <p mpa-is-content="t"><span style="font-size: 18px;" mpa-is-content="t">1.4 返回空集合</span></p> </section> <section style="background-color: rgb(252, 180, 43);height: 8px;margin-top: -10px;color: rgb(255, 255, 255);" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> </section> </section> </section> </section> </section> </section> <p><span style="color: rgb(74, 74, 74);letter-spacing: 0.034em;">有时,我们在判空之后,需要返回空集合,就可以使用</span><code style="letter-spacing: 0.034em;font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">emptyList</code><span style="color: rgb(74, 74, 74);letter-spacing: 0.034em;">方法,例如:</span><br></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wzJhLVPsrd2AhSLhUIEicrMUzVG13cVd3q6ibe8eX9Ydic9RhbhISLhJY8C9zr6LJTkUXictRoWRiaW6DF3Gic0es9qicrMCLuibTacj/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">private</span> List<Integer> <span style="color: #61aeee;line-height: 26px;">fun</span><span style="line-height: 26px;">(List<Integer> list)</span> </span>{<br> <span style="color: #c678dd;line-height: 26px;">if</span> (list == <span style="color: #c678dd;line-height: 26px;">null</span> || list.size() == <span style="color: #d19a66;line-height: 26px;">0</span>) {<br> <span style="color: #c678dd;line-height: 26px;">return</span> Collections.emptyList();<br> }<br> <span style="color: #5c6370;font-style: italic;line-height: 26px;">//业务处理</span><br> <span style="color: #c678dd;line-height: 26px;">return</span> list;<br>}<br></code></pre> <h3 data-tool="mdnice编辑器"><br mpa-from-tpl="t"></h3> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-color="rgb(252, 180, 43)" mpa-from-tpl="t"> <section style="border-width: 0px;border-style: none;border-color: initial;" mpa-from-tpl="t"> <section mpa-from-tpl="t"> <section style="display: inline-block;" mpa-from-tpl="t"> <section style="font-size: 16px;padding-right: 10px;padding-left: 10px;color: rgb(73, 73, 73);" mpa-from-tpl="t"> <p mpa-is-content="t"><span style="font-size: 18px;" mpa-is-content="t">1.5 二分查找</span></p> </section> <section style="background-color: rgb(252, 180, 43);height: 8px;margin-top: -10px;color: rgb(255, 255, 255);" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> </section> </section> </section> </section> </section> </section> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">binarySearch</code>方法提供了一个非常好用的<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">二分查找</code>功能,只用传入指定集合和需要找到的key即可。例如:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wzJhLVPsrd2AhSLhUIEicrMUzVG13cVd3q6ibe8eX9Ydic9RhbhISLhJY8C9zr6LJTkUXictRoWRiaW6DF3Gic0es9qicrMCLuibTacj/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">List<Integer> list = <span style="color: #c678dd;line-height: 26px;">new</span> ArrayList<>();<br>list.add(<span style="color: #d19a66;line-height: 26px;">2</span>);<br>list.add(<span style="color: #d19a66;line-height: 26px;">1</span>);<br>list.add(<span style="color: #d19a66;line-height: 26px;">3</span>);<br><br><span style="color: #c678dd;line-height: 26px;">int</span> i = Collections.binarySearch(list, <span style="color: #d19a66;line-height: 26px;">3</span>);<span style="color: #5c6370;font-style: italic;line-height: 26px;">//二分查找</span><br>System.out.println(i );<br></code></pre> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">执行结果:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wzJhLVPsrd2AhSLhUIEicrMUzVG13cVd3q6ibe8eX9Ydic9RhbhISLhJY8C9zr6LJTkUXictRoWRiaW6DF3Gic0es9qicrMCLuibTacj/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #d19a66;line-height: 26px;">2</span><br></code></pre> <h3 data-tool="mdnice编辑器"><br mpa-from-tpl="t"></h3> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-color="rgb(252, 180, 43)" mpa-from-tpl="t"> <section style="border-width: 0px;border-style: none;border-color: initial;" mpa-from-tpl="t"> <section mpa-from-tpl="t"> <section style="display: inline-block;" mpa-from-tpl="t"> <section style="font-size: 16px;padding-right: 10px;padding-left: 10px;color: rgb(73, 73, 73);" mpa-from-tpl="t"> <p mpa-is-content="t"><span style="font-size: 18px;" mpa-is-content="t">1.6 转换成不可修改集合</span></p> </section> <section style="background-color: rgb(252, 180, 43);height: 8px;margin-top: -10px;color: rgb(255, 255, 255);" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> </section> </section> </section> </section> </section> </section> <p><span style="color: rgb(74, 74, 74);letter-spacing: 0.034em;">为了防止后续的程序把某个集合的结果修改了,有时候我们需要把某个集合定义成不可修改的,使用Collections的</span><code style="letter-spacing: 0.034em;font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">unmodifiablexxx</code><span style="color: rgb(74, 74, 74);letter-spacing: 0.034em;">方法就能轻松实现:</span><br></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wzJhLVPsrd2AhSLhUIEicrMUzVG13cVd3q6ibe8eX9Ydic9RhbhISLhJY8C9zr6LJTkUXictRoWRiaW6DF3Gic0es9qicrMCLuibTacj/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">List<Integer> list = <span style="color: #c678dd;line-height: 26px;">new</span> ArrayList<>();<br>list.add(<span style="color: #d19a66;line-height: 26px;">2</span>);<br>list.add(<span style="color: #d19a66;line-height: 26px;">1</span>);<br>list.add(<span style="color: #d19a66;line-height: 26px;">3</span>);<br><br>List<Integer> integers = Collections.unmodifiableList(list);<br>integers.add(<span style="color: #d19a66;line-height: 26px;">4</span>);<br>System.out.println(integers);<br></code></pre> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">执行结果:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wzJhLVPsrd2AhSLhUIEicrMUzVG13cVd3q6ibe8eX9Ydic9RhbhISLhJY8C9zr6LJTkUXictRoWRiaW6DF3Gic0es9qicrMCLuibTacj/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">Exception in thread <span style="color: #98c379;line-height: 26px;">"main"</span> java.lang.UnsupportedOperationException<br> at java.util.Collections$UnmodifiableCollection.add(Collections.java:<span style="color: #d19a66;line-height: 26px;">1055</span>)<br> at com.sue.jump.service.test1.UtilTest.main(UtilTest.java:<span style="color: #d19a66;line-height: 26px;">19</span>)<br></code></pre> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">当然Collections工具类中还有很多常用的方法,在这里就不一一介绍了,需要你自己去探索。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="1.0555555555555556" src="/upload/62fb961b3bf51fa212e273a2d13ef745.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;" data-imgfileid="100125176"> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.9537037037037037" src="/upload/a9cba4362c8fb38a65ddac31811519c3.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;" data-imgfileid="100125174"> </figure> <h2 data-tool="mdnice编辑器"><span style="display: none;"></span><br mpa-from-tpl="t"></h2> <section data-mpa-template="t" mpa-from-tpl="t"> <blockquote style="margin: 5px auto;padding: 0px;max-width: 100%;border-width: 0px;border-style: none;border-color: rgb(68, 153, 231);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);line-height: 25.6px;box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;color: rgb(68, 153, 231);" mpa-from-tpl="t"> <section style="margin: 0px auto;padding: 0px;max-width: 100%;border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto;padding: 0px;max-width: 100%;border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <blockquote style="margin: 5px auto;padding: 0px;max-width: 100%;border-width: 0px;border-style: none;border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto;padding: 0px;max-width: 100%;text-align: center;border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto -38px;padding: 0px 15px;max-width: 100%;display: inline-block;background-color: rgb(68, 153, 231);border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <p style="margin-top: 5px;margin-bottom: 5px;max-width: 100%;clear: both;min-height: 1em;color: rgb(68, 153, 231);border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;"><span style="font-family: Arial, Helvetica, sans-serif;"><strong mpa-from-tpl="t"><span style="border-color: rgb(68, 153, 231);color: rgb(255, 255, 255);font-size: 16px;" mpa-is-content="t">2. CollectionUtils</span></strong></span></p> </section> <section style="margin: -40px 25px 50px;padding: 0px;max-width: 100%;border-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 3px 0px 0px;padding: 10px 0px;max-width: 100%;box-sizing: border-box;display: inline-block;width: 495px;color: rgb(68, 153, 231);float: left;border-color: rgb(68, 153, 231);word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 1em 0px 0px;padding: 0px;max-width: 100%;box-sizing: border-box;border-width: 0px;border-style: none;border-color: rgb(68, 153, 231);clear: both;color: rgb(68, 153, 231);word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px 0px -3px;padding: 0px;max-width: 100%;float: right;border-color: rgb(68, 153, 231);width: 6px;border-radius: 50%;background-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;height: 6px !important;" mpa-from-tpl="t"> <br> </section> <section style="margin: 0px 0px -2px;padding: 0px;max-width: 100%;text-align: left;border-color: rgb(68, 153, 231);width: 6px;border-radius: 50%;background-color: rgb(68, 153, 231);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;height: 6px !important;" mpa-from-tpl="t"> <br> </section> <section style="margin: -20px 0px 0px;padding: 0px;max-width: 100%;box-sizing: border-box;text-decoration: inherit;color: rgb(68, 153, 231);border-color: rgb(68, 153, 231);word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px;padding: 0px;max-width: 100%;box-sizing: border-box;border-top: 1px solid rgb(68, 153, 231);width: 495px;float: left;border-right-color: rgb(68, 153, 231);border-bottom-color: rgb(68, 153, 231);border-left-color: rgb(68, 153, 231);color: rgb(68, 153, 231);word-wrap: break-word !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <br> </section> </section> </section> </section> </section> </section> </blockquote> </section> </section> </blockquote> </section> <p><br mpa-from-tpl="t"></p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">对集合操作,除了前面说的<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">Collections</code>工具类之后,<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">CollectionUtils</code>工具类也非常常用。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">目前比较主流的�
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0px;word-break: break-word;text-align: left;counter-reset: counterh1 0 counterh2 0 counterh3 0;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;margin-bottom: 24px;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;"><em style="color: rgb(37, 132, 181);">作者:</em><em style="color: rgb(37, 132, 181);">张海文,中国移动云能力中心高级软件研发工程师,移动云服务网格负责人,QCon、KubeCon等大会分享者,专注于云原生、微服务、算力网络等。</em></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;text-align: justify;"><span style="display: inline-block;"><span style="counter-increment: counterh2;color: rgb(159,205,208);border-bottom: 4px solid rgb(159,205,208);font-size: 18px;padding: 2px 4px;">1</span></span><span style="font-size: 18px;border-bottom: 4px solid rgb(37,132,181);padding: 2px 4px;color: rgb(37,132,181);">概述</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">在当今快节奏的软件开发环境中,随着用户需求的不断变化和竞争日益激烈,软件更新和发布的频率已成为常态。然而,与此同时,保证用户体验的稳定性和可靠性也是至关重要的。传统的大规模软件发布往往会面临着线上故障风险,可能导致用户体验下降,甚至影响业务正常运行。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">为了解决这一矛盾,在软件开发领域催生出了灰度发布的概念。灰度发布是一种渐进式的软件发布方式,它允许将新功能或更新逐步推送给一部分用户,而不是一次性全部发布。这样的方式能够有效降低线上故障的风险,保障用户体验,同时也为开发团队提供了更多时间和机会在全面发布前进行验证和修复。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">然而,随着软件架构的演进,尤其是微服务架构的普及,软件系统往往由多个微服务组成,不同服务的版本升级需要协调和同步。在这种背景下,单一服务的灰度发布已经不能完全适应需求,全链路灰度发布应运而生。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">全链路灰度发布考虑到整个软件系统的多个微服务,允许多个微服务同时进行版本控制和升级,以确保整个系统的平稳过渡和稳定性,是一种更为全面和细致的灰度发布方式。通过全链路灰度发布,开发团队能够更加精确地控制不同服务版本的发布比例,降低系统风险,保障线上稳定性,最大程度地满足用户需求。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100003348" data-ratio="0.2388888888888889" data-s="300,640" src="/upload/ee1623d91953b5c70c065183cadcbaf4.png" data-type="png" data-w="1080" style=""></p> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">以图 1 为例,软件系统包含网关和 4 个微服务,通过全链路灰度发布,可以使ServiceB 和 ServiceD 进行灰度发布,通过灰线所示的流量进行灰度功能验证,同时不影响蓝线所示的正常访问流量。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;text-align: justify;"><span style="display: inline-block;"><span style="counter-increment: counterh2;color: rgb(159,205,208);border-bottom: 4px solid rgb(159,205,208);font-size: 18px;padding: 2px 4px;">2</span></span><span style="font-size: 18px;border-bottom: 4px solid rgb(37,132,181);padding: 2px 4px;color: rgb(37,132,181);">全链路灰度发布核心问题</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">我们通过图1 可以清晰地看到,在实施全链路灰度发布时,需要部署相关服务的灰度版本,并确保在整个请求的调用链上,网关和微服务组件能够准确识别正式流量和特定版本灰度流量,并根据流量类型动态地将请求路由到正确版本的上游微服务上。所以,我们需要解决以下几个问题:</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;line-height: 26px;color: rgb(1, 1, 1);text-align: justify;"> 微服务实例具有版本信息,针对不同流量对应版本实例提供服务; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);text-align: justify;"> 请求流量具有流量特征,可以区分出是请求不同版本微服务的流量; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);text-align: justify;"> 调用链上各组件可以根据流量特征将请求动态路由到正确版本的微服务上。当前的微服务架构主要分为两类模式:一类是建立在传统微服务框架(例如Spring Cloud等)之上的微服务体系,另一类是云原生时代采用Kubernetes和服务网格(如Istio)构建的微服务架构,为了方便起见,我们姑且简称为传统模式和云原生模式。下面我们将介绍在两类模式下,如何解决以上问题,进行全链路灰度发布。 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;text-align: justify;"><span style="display: inline-block;background-image: linear-gradient(45deg, transparent 48%, rgb(37, 132, 181) 48%, rgb(37, 132, 181) 52%, transparent 52%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;width: 24px;height: 24px;margin-bottom: -7px;"></span><span style="font-size: 16px;border-bottom: 1px solid rgb(37,132,181);padding: 2px 10px;color: rgb(37,132,181);">微服务标识</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">我们通过给微服务实例添加标识的方式,使微服务实例具有版本信息,针对不同流量对应版本实例提供服务。</p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 18px;text-align: justify;"><span style="display: none;"></span>传统模式<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">在传统模式下,一般需要产品根据使用的微服务框架决定添加标识的方式。我们以基于 Spring Cloud 框架 + Nacos注册中心 的微服务为例,一般通过在微服务元数据配置(spring.cloud.nacos.discovery.metadata)中添加标识,配置示例如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/BfRL3E0G1pfZCKt5MHCTYHvoicYu8CJBiat8ic6v3NlOh6MeBpS1HF5UVmVZqmWtxgAXjvzoQIibNU4rqbOSNL6FKO3B4SDx1oOO/640?wx_fmt=svg&from=appmsg") 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: 15px 16px 16px;color: rgb(171, 178, 191);display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(40, 44, 52);border-radius: 5px;text-align: justify;">spring:<br> cloud:<br> nacos:<br> discovery:<br> metadata:<br> version: <span style="color: #d19a66;line-height: 26px;">${APP-VERSION:v1}</span><br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">微服务在 Nacos 注册中心中服务信息如下:</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100003350" data-ratio="0.08888888888888889" data-s="300,640" src="/upload/e72b56dda240d5a81d7b9dfa75c16bb9.png" data-type="png" data-w="1080" style=""></p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 18px;text-align: justify;"><span style="display: none;"></span>云原生模式<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">在云原生模式下,为微服务实例添加标识更加方便,不需要修改微服务代码配置,一般在微服务 Deployment 的 Pod 模版中添加Labal标识即可,实例如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/BfRL3E0G1pfZCKt5MHCTYHvoicYu8CJBiat8ic6v3NlOh6MeBpS1HF5UVmVZqmWtxgAXjvzoQIibNU4rqbOSNL6FKO3B4SDx1oOO/640?wx_fmt=svg&from=appmsg") 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: 15px 16px 16px;color: rgb(171, 178, 191);display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(40, 44, 52);border-radius: 5px;text-align: justify;">apiVersion: apps/v1<br>kind: Deployment<br>metadata:<br> name: demo<br>spec:<br> selector:<br> matchLabels:<br> app: demo<br> version: v1<br> template:<br> metadata:<br> labels:<br> app: demo<br> version: v1<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;text-align: justify;"><span style="display: inline-block;background-image: linear-gradient(45deg, transparent 48%, rgb(37, 132, 181) 48%, rgb(37, 132, 181) 52%, transparent 52%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;width: 24px;height: 24px;margin-bottom: -7px;"></span><span style="font-size: 16px;border-bottom: 1px solid rgb(37,132,181);padding: 2px 10px;color: rgb(37,132,181);">流量标识</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">和微服务标识相同,我们也是通过为流量添加不同标识的方式区分流量,流量标识在业界还有一个更专业更形象的叫法叫流量染色。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;"><span style="letter-spacing: 0px;">流量染色分两步:</span><span style="letter-spacing: 0px;">第一步在流量源头染色,第二步在调用链内染色。</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;"><span style="letter-spacing: 0px;"></span><span style="letter-spacing: 0px;">第一步比较简单,通过前端或者网关根据流量特点(比如浏览器类型,用户标识,地域标识等)为流量添加标识(比如在HTTP Header中添加version标签)。</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;"><span style="letter-spacing: 0px;"></span><span style="letter-spacing: 0px;">第二步是全链路灰度中最核心也是最复杂的部分,需要流量标识在调用链中透传下去,保证调用链上的每个微服务组件都能根据标识识别流量并动态路由。</span><span style="letter-spacing: 0px;"></span><span style="letter-spacing: 0px;"></span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;"><span style="letter-spacing: 0px;">传统的微服务框架注重基础功能的实现,例如服务注册与发现、负载均衡等,而未将流量标识透传作为核心特性之一,导致在实际应用中难以实现流量标识的无缝传递。</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;"><span style="letter-spacing: 0px;">云原生时代的 Kubernetes + Istio 同样无法帮助微服务实现流量标识透传。</span><span style="letter-spacing: 0px;">不少人存在误解,认为 Istio 能对微服务实例的出入流量进行拦截,应该原生支持流量标识透传,但实际上 Istio 本身没有流量标识透传的能力。</span></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100003349" data-ratio="0.4336734693877551" data-s="300,640" src="/upload/f7a7ae4959129832c3171cb3fa4f92e0.png" data-type="png" data-w="784" style=""></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">Istio Sidecar 对微服务入口和出口流量拦截如上图所示,Sidecar 虽然能将入口流量1 拦截后转给微服务容器(入口流量2),也能将微服务容器出口流量 3 拦截并转发到 Pod 外(出口流量4),但 Sidecar 不知道出口流量 3 和入口流量 2 的对应关系,在实际情况中,Sidecar 会拦截很多的出口流量,也会拦截很多的入口流量,但 Sidecar 并不知道某一个出口流量对应哪个入口流量。只有微服务应用知道对应关系,因为微服务应用亲自做了流量处理(微服务应用收到入口流量请求2后,进行业务逻辑处理,然后再发出出口流量3,请求下一级微服务),所以 Istio 虽然能对微服务出入流量拦截,但不知道出入流量的对应关系,无法将入口流量的标识自动添加到出口流量上,无法做流量标识透传。</p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 18px;text-align: justify;"><span style="display: none;"></span>流量标识透传方式<span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">那么如何进行流量标识透传呢,通常有以下3种方式:</p> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;text-align: justify;"><span style="display: none;"></span>微服务修改源码方式<span style="display: none;"></span></h5> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">微服务侧进行业务代码改造,从入口流量请求中获取流量标识,并在出口流量中添加流量标识,代码示例如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/BfRL3E0G1pfZCKt5MHCTYHvoicYu8CJBiat8ic6v3NlOh6MeBpS1HF5UVmVZqmWtxgAXjvzoQIibNU4rqbOSNL6FKO3B4SDx1oOO/640?wx_fmt=svg&from=appmsg") 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: 15px 16px 16px;color: rgb(171, 178, 191);display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(40, 44, 52);border-radius: 5px;text-align: justify;">// 从请求中获取流量标识 version<br>String versionValue = request.getHeader(<span style="color: #98c379;line-height: 26px;">"version"</span>);<br>// 构造新请求需要的 Header,获取到的流量标识添加到新请求的 Header 中<br>HttpHeaders headers = new HttpHeaders();<br>headers.set(<span style="color: #98c379;line-height: 26px;">"version"</span>, versionValue);<br>// 发起出口流量请求<br></code></pre> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;text-align: justify;"><span style="display: none;"></span>使用基础SDK方式<span style="display: none;"></span></h5> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">将从入口流量请求中获取流量标识,并在出口流量中添加流量标识这种共性逻辑封装到基础 SDK 中,其原理通常涉及 SDK 对请求和响应的拦截处理。这种方法的核心在于SDK能够拦截到微服务内部的请求,从请求中获取流量标识,并在微服务发起外部请求时,将这个标识加入请求中,实现流量标识的透传。基础 SDK 的工作机制一般包括以下几个关键步骤:</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;line-height: 26px;color: rgb(1, 1, 1);text-align: justify;"> <strong style="color: rgb(37, 132, 181);">拦截请求和响应:</strong> SDK会通过某种方式(例如AOP、拦截器等)拦截微服务的请求和响应,这使得SDK能够在请求进入微服务之前或响应返回之后对其进行处理。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);text-align: justify;"> <strong style="color: rgb(37, 132, 181);">获取流量标识:</strong> 在请求被微服务处理之前,SDK会从请求中获取流量标识。这可能包括从HTTP头部、Cookie、请求参数等位置获取特定的标识符。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);text-align: justify;"> <strong style="color: rgb(37, 132, 181);">向外发送请求:</strong> 当微服务需要向外部服务发起请求时,SDK会在请求中添加之前获取到的流量标识。这意味着,SDK会将其添加到新的请求头、请求体或其他适当的位置,以确保这个标识被透传到外部服务。 </section> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);text-align: justify;"> 通过以上步骤,基础 SDK 能够在微服务内部对流量进行拦截、获取流量标识,并在微服务发起外部请求时,将这个标识透传到外部服务中去。 </section> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);text-align: justify;"> 在一些大型企业内部,基础设施团队会提供基础 SDK 供产品团队使用,也有一些相关的开源方案可以参考,例如阿里开源的KtEnv,提供了一个Java语言的SDK示例,采用Spring框架的切面机制来自动化"环境标签"的传递,其中环境标签即为一种流量标识。 </section></li> </ul> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;text-align: justify;"><span style="display: none;"></span>使用基础Agent方式<span style="display: none;"></span></h5> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">使用 Agent 技术实现流量标识透传是一种相对隐式且高度可配置的方式。Agent 是一种可以介入到 JVM 运行时的程序,它可以对 Java 应用程序进行动态的字节码操作和增强。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">在实现流量标识透传时,Agent 可以通过动态字节码增强技术,通过字节码操作工具(如ASM、ByteBuddy等)对特定类或方法进行字节码增强,动态地修改微服务应用的字节码,使得在请求处理链路中自动获取到流量标识,并在请求发起时将这些标识添加到外部请求中。这些标识可能包括从HTTP头部、上下文信息、或者其他标识性的数据。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">有一些基础Agent开源方案可以选择参考,如:Homer,这是专门为javaweb应用提供了无感知的header透传的开源方案,华为的Sermant,Sermant 是利用JavaAgent技术为Java应用程序提供服务网格功能的开源方案,提供了流量透传插件tag-transmission,可以帮助微服务实现流量透传功能。</p> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;text-align: justify;"><span style="display: none;"></span>三种方式总结<span style="display: none;"></span></h5> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">这三种实现流量标识透传的方式各自具有独特的优势和适用场景。总结如下:</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;line-height: 26px;color: rgb(1, 1, 1);text-align: justify;"> <strong style="color: rgb(37, 132, 181);">业务代码修改:</strong> 这种方式简单直接,但会增加业务代码的复杂性和维护成本,尤其在大型微服务体系中,需要在多个服务间添加相似的逻辑,不够灵活和智能,实际应用较少。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);text-align: justify;"> <strong style="color: rgb(37, 132, 181);">基础 SDK:</strong> 使用基础 SDK 相对于业务代码修改更为智能化和自动化,减少了对业务代码的侵入,同时也能够提供一定程度的可配置性和扩展性,但对业务代码仍然有侵入,并且和语言相关,在大型多语言微服务体系中需要提供多语言SDK,另外 SDK 版本升级困难。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);text-align: justify;"> <strong style="color: rgb(37, 132, 181);">Agent 方式:</strong> 基于 Agent 技术,可以在不修改业务代码的情况下,实现流量标识的拦截和传递。这种方式尤其适用于不想或不能直接修改业务代码的场景,其灵活性和智能化程度较高,但也存在和语言绑定,版本升级相对困难的问题。 </section> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);text-align: justify;"> 总体而言,三种方式各有利弊,在实际场景中,需要根据具体需求和现有架构,选择适合的方式。 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;text-align: justify;"><span style="display: inline-block;background-image: linear-gradient(45deg, transparent 48%, rgb(37, 132, 181) 48%, rgb(37, 132, 181) 52%, transparent 52%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;width: 24px;height: 24px;margin-bottom: -7px;"></span><span style="font-size: 16px;border-bottom: 1px solid rgb(37,132,181);padding: 2px 10px;color: rgb(37,132,181);">流量路由</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">流量路由和微服务标识类似,由于传统模式和云原生模式都支持,较流量标识简单很多。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">在传统微服务框架(如 Spring Cloud)中,实现动态路由通常通过 API 网关(如Spring Cloud Gateway)或负载均衡器(如Netflix Ribbon)等组件,根据特定的策略或规则,对流量进行分发和路由。例如,可以基于请求头中的流量标识信息,利用负载均衡策略,将请求分发到不同版本的微服务实例上,实现动态路由。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">而在云原生架构下(例如 Kubernetes + Istio),动态路由更加简单。通过 Istio 中的流量管理功能,定义Gateway、VirtualService、DestinationRule等规则和配置来实现流量的精细化控制和路由。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;text-align: justify;"><span style="display: inline-block;"><span style="counter-increment: counterh2;color: rgb(159,205,208);border-bottom: 4px solid rgb(159,205,208);font-size: 18px;padding: 2px 4px;">3</span></span><span style="font-size: 18px;border-bottom: 4px solid rgb(37,132,181);padding: 2px 4px;color: rgb(37,132,181);">全链路灰度发布实践</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">我们在云原生模式下,对概述部分图1 所示的微服务进行全链路灰度发布实践。微服务版本情况及调用链路和图 1 一致,微服务实例列表如下:</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100003351" data-ratio="0.1712962962962963" data-s="300,640" src="/upload/f65595a829d7e38309e2e6904f89af05.png" data-type="png" data-w="1080" style=""></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">网关采用 Istio Ingress Gateway,流量标识透传采用基础 Agent 方式,可以在微服务调用链路中透传 key 为 et-mark 的 HTTP Request Header ,流量路由通过 Istio 流量管理功能实现,关键Gateway、VirtualService、DestinationRule 规则部分如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/BfRL3E0G1pfZCKt5MHCTYHvoicYu8CJBiat8ic6v3NlOh6MeBpS1HF5UVmVZqmWtxgAXjvzoQIibNU4rqbOSNL6FKO3B4SDx1oOO/640?wx_fmt=svg&from=appmsg") 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: 15px 16px 16px;color: rgb(171, 178, 191);display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(40, 44, 52);border-radius: 5px;text-align: justify;">---<br>apiVersion: networking.istio.io/v1beta1<br>kind: Gateway<br>metadata:<br> name: ingress-gateway<br> namespace: e2e-canary-release<br>spec:<br> selector:<br> istio: ingressgateway<br> servers:<br> - hosts:<br> - <span style="color: #98c379;line-height: 26px;">'www.e2e-canary-release.com'</span><br> port:<br> name: http<br> number: 80<br> protocol: HTTP<br>---<br>apiVersion: networking.istio.io/v1beta1<br>kind: VirtualService<br>metadata:<br> name: service<span style="line-height: 26px;">-a</span><br> namespace: e2e-canary-release<br>spec:<br> gateways:<br> - e2e-canary-release/ingress-gateway<br> hosts:<br> - <span style="color: #98c379;line-height: 26px;">'www.e2e-canary-release.com'</span><br> http:<br> - route:<br> - destination:<br> host: service<span style="line-height: 26px;">-a</span><br>---<br>apiVersion: networking.istio.io/v1beta1<br>kind: VirtualService<br>metadata:<br> name: service-b<br> namespace: e2e-canary-release<br>spec:<br> hosts:<br> - service-b<br> http:<br> - match:<br> - headers:<br> et-mark:<br> exact: v2<br> route:<br> - destination:<br> host: service-b<br> subset: v2<br> - route:<br> - destination:<br> host: service-b<br> subset: v1<br>---<br>apiVersion: networking.istio.io/v1beta1<br>kind: DestinationRule<br>metadata:<br> name: service-b<br> namespace: e2e-canary-release<br>spec:<br> host: service-b<br> subsets:<br> - labels:<br> version: v1<br> name: v1<br> - labels:<br> version: v2<br> name: v2<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">实际效果如下,可以看到默认情况下请求流量流经的微服务版本均为正式版本v1,当请求header中包含流量标识时,即流量为灰度流量时,会按照图1 中的灰色路径流转,实现了全链路灰度发布。</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100003352" data-ratio="0.11018518518518519" data-s="300,640" src="/upload/586c90a9286c7013022d3bd80d682f35.png" data-type="png" data-w="1080" style=""></p> <p style="text-align: center;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100003353" data-ratio="0.09444444444444444" data-s="300,640" src="/upload/c63118cd31827f5be02d7728e4643b17.png" data-type="png" data-w="1080" style=""></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;text-align: justify;"><span style="display: inline-block;"><span style="counter-increment: counterh2;color: rgb(159,205,208);border-bottom: 4px solid rgb(159,205,208);font-size: 18px;padding: 2px 4px;">4</span></span><span style="font-size: 18px;border-bottom: 4px solid rgb(37,132,181);padding: 2px 4px;color: rgb(37,132,181);">总结</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;text-align: justify;">本文首先介绍了全链路灰度发布的概念、作用以及实现全链路灰度发布时需要解决的关键问题,针对每个问题分别从传统模式和云原生模式介绍了对应的解决方案,其中对流量标识透传做了详细的介绍,然后在云原生模式下,对微服务 Demo 进行了全链路灰度发布实践,展示了实践效果。由于能力和时间有限,一些内容仅进行了粗浅介绍,希望后续可以继续深入研究分享,文中存在错误的地方,也望大家指正。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;text-align: justify;"><span style="display: inline-block;"><span style="counter-increment: counterh2;color: rgb(159,205,208);border-bottom: 4px solid rgb(159,205,208);font-size: 18px;padding: 2px 4px;">5</span></span><span style="font-size: 18px;border-bottom: 4px solid rgb(37,132,181);padding: 2px 4px;color: rgb(37,132,181);">参考文章及相关链接</span></h2> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li><p style="text-align: left;">深入剖析全链路灰度技术内幕</p><p style="text-align: left;">https://developer.aliyun.com/article/834510#slide-1</p></li> <li><p style="text-align: left;">基于 Istio 的全链路灰度方案探索和实践https://xie.infoq.cn/article/f6a1db8756e8bfa831947ee05</p></li> <li><p style="text-align: left;">聊聊 Spring Cloud 全链路灰度发布方案~ https://z.itpub.net/article/detail/5D9F94265D666C4607B92CBC32667692</p></li> <li><p style="text-align: left;">Spring Cloud Alibaba-全链路灰度设计https://www.nowcoder.com/discuss/517248839594541056</p></li> <li><p style="text-align: left;">标记透传:微服务系统如何做标记透传方案选型?https://leeshengis.com/archives/444794</p></li> <li><p style="text-align: left;">流量治理的基石——基于字节码增强的全链路流量标签透传https://juejin.cn/post/7282957826510667816</p></li> <li><p style="text-align: left;">KtEnv </p><p style="text-align: left;">https://alibaba.github.io/virtual-environment/#/zh-cn/doc/use-sdk?id=%E4%BD%BF%E7%94%A8sdk</p></li> <li><p style="text-align: left;">Homer</p><p style="text-align: left;">https://github.com/kaikeba/homer</p></li> <li><p style="text-align: left;">Sermant</p><p style="text-align: left;">https://sermant.io/zh/</p></li> </ul> </section> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzU4MjQ0MTU4Ng==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/z9BgVMEm7YtTw2oONBkwaiaM9hBxUj6yRLDEw8rSSxR8wWZFLjjXWpmGq5LNDlEAn4v9lSALDiaGfC4MyPZwL95g/0?wx_fmt=png" data-nickname="k8s技术圈" data-alias="kube100" data-signature="专注容器、专注 kubernetes 技术......" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> <p style="display: none;margin-bottom: 24px;"> <mp-style-type data-value="3"></mp-style-type></p>
作者:じ☆ve宝贝
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Guava Cache是一款非常优秀的本地缓存框架。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这篇文章,我们聊聊如何使用 Guava Cache <span style="font-weight: 700;color: rgb(248, 57, 41);">异步刷新技巧</span>带飞系统性能 。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-imgfileid="100047398" data-ratio="0.6861111111111111" src="/upload/90139c2ec011330ad2867fa8d635dfc6.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p style="text-align: right;"><strong style="outline: 0px;color: rgb(255, 41, 65);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;letter-spacing: 0.8px;text-align: right;word-spacing: 0.8px;text-wrap: wrap;background-color: rgb(255, 255, 255);visibility: visible;">Java进阶指南:https://java-family.cn</strong></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;"> <br> <span style="letter-spacing: 0.8px;word-spacing: 0.8px;"></span> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);letter-spacing: 0.5444px;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);visibility: visible;">1. 经典配置</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Guava Cache 的数据结构跟 JDK1.7 的 ConcurrentHashMap 类似,提供了基于时间、容量、引用三种回收策略,以及自动加载、访问统计等功能。</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="100047394" data-ratio="0.5657407407407408" src="/upload/cb2c228892d37c49d2f364f9a5d14e5d.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">首先,我们温习下 Gauva Cache 的经典配置 。</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="100047395" data-ratio="0.41203703703703703" src="/upload/7696fb9e883236a46a4bcc7d72bb448b.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">例子中,缓存最大容量设置为 100 (<span style="font-weight: 700;color: rgb(248, 57, 41);">基于容量进行回收</span>),配置了<span style="font-weight: 700;color: rgb(248, 57, 41);">失效策略</span>和<span style="font-weight: 700;color: rgb(248, 57, 41);">刷新策略</span>。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">1、失效策略</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">配置 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">expireAfterWrite</code> 后,缓存项在被创建或最后一次更新后的指定时间内会过期。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">2、刷新策略</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">配置 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">refreshAfterWrite</code> 设置刷新时间,当缓存项过期的同时可以重新加载新值 。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这个例子里,有的同学可能会有疑问:<span style="font-weight: 700;color: rgb(248, 57, 41);">为什么需要配置刷新策略,只配置失效策略不就可以吗</span>?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当然是可以的,但在高并发场景下,配置刷新策略会有奇效,接下来,我们会写一个测试用例,方便大家理解 Gauva Cache 的线程模型。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);letter-spacing: 0.5444px;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);visibility: visible;">2. 理解线程模型</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">我们模拟在多线程场景下,「缓存过期执行 load 方法」和「刷新执行 reload 方法」两者的运行情况。</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="100047396" data-ratio="1.1898148148148149" src="/upload/36a5b1b8df7077e51ad8bace7df60ca8.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">执行结果见下图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-imgfileid="100047397" data-ratio="0.967948717948718" src="/upload/4e8ec5697e04998fd66a504679c3d8dd.png" data-w="624" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">执行结果表明:<span style="font-weight: 700;color: rgb(248, 57, 41);">Guava Cache 并没有后台任务线程异步的执行 load 或者 reload 方法。</span></p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">失效策略</span>:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">expireAfterWrite</code> 允许一个线程执行 load 方法,其他线程阻塞等待 。</p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当大量线程用相同的 key 获取缓存值时,只会有一个线程进入 load 方法,而其他线程则等待,直到缓存值被生成。这样也就避免了缓存击穿的危险。高并发场景下 ,这样还是会阻塞大量线程。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">刷新策略</span>:<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">refreshAfterWrite</code> 允许一个线程执行 load 方法,其他线程返回旧的值。</p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">单个 key 并发下,使用 refreshAfterWrite ,虽然不会阻塞了,但是如果恰巧同时多个 key 同时过期,还是会给数据库造成压力。</p> </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">为了提升系统性能,我们可以从如下两个方面来优化 :</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 配置 refresh < expire ,减少大量线程阻塞的概率; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 采用 <span style="font-weight: 700;color: rgb(248, 57, 41);">异步刷新</span>的策略,也就是 <span style="font-weight: 700;color: rgb(248, 57, 41);">线程异步加载数据,期间所有请求返回旧的缓存值</span>,防止缓存雪崩。 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">下图展示优化方案的时间轴 :</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="100047400" data-ratio="0.6306306306306306" src="/upload/67a9f33f612e5c2b556926047bfc0db0.png" data-w="555" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);letter-spacing: 0.5444px;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);visibility: visible;">3. 两种方式实现异步刷新</span></h2> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">3.1 重写 reload 方法</span><span style="display: none;"></span></h3> <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="100047401" data-ratio="0.5694444444444444" src="/upload/12d4ca9294d6195ca5f88f5b355fdad5.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">3.2 实现 asyncReloading 方法</span><span style="display: none;"></span></h3> <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="100047402" data-ratio="0.35833333333333334" src="/upload/922d5f1bfce6a5cd7015a0d1a8f05ed3.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">不管使用哪种方案, 都需要定义单独的线程池来执行刷新任务 。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);letter-spacing: 0.5444px;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);visibility: visible;">4. 异步刷新 + 多级缓存</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">2018 年,笔者服务的一家电商公司需要进行 app 首页接口的性能优化。笔者花了大概两天的时间完成了整个方案,采取的是两级缓存模式,同时采用了 Guava 的异步刷新机制。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">整体架构如下图所示:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-imgfileid="100047399" data-ratio="0.3146779303062302" src="/upload/ca54a712a59cc0d9687ee2ede3eb664c.png" data-w="947" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">缓存读取流程如下 :</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">1、业务网关刚启动时,本地缓存没有数据,读取 Redis 缓存,如果 Redis 缓存也没数据,则通过 RPC 调用导购服务读取数据,然后再将数据写入本地缓存和 Redis 中;若 Redis 缓存不为空,则将缓存数据写入本地缓存中。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">2、由于步骤1已经对本地缓存预热,后续请求直接读取本地缓存,返回给用户端。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">3、Guava 配置了 refresh 机制,每隔一段时间会调用自定义 LoadingCache 线程池(5个最大线程,5个核心线程)去导购服务同步数据到本地缓存和 Redis 中。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">优化后,性能表现很好,平均耗时在 5ms 左右,同时大幅度的减少应用 GC 的频率。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">该方案依然有瑕疵,一天晚上我们发现 app 端首页显示的数据时而相同,时而不同。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">也就是说:虽然 LoadingCache 线程一直在调用接口更新缓存信息,但是各个服务器本地缓存中的数据并非完成一致。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这说明了两个很重要的点:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">1、惰性加载仍然可能造成多台机器的数据不一致;</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">2、LoadingCache 线程池数量配置的不太合理, 导致了任务堆积。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">最终,我们的解决方案是:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">1、异步刷新结合消息机制来更新缓存数据,也就是:当导购服务的配置发生变化时,通知业务网关重新拉取数据,更新缓存。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">2、适当调大 LoadingCache 的线程池参数,并在线程池埋点,监控线程池的使用情况,当线程繁忙时能发出告警,然后动态修改线程池参数。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);letter-spacing: 0.5444px;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);visibility: visible;">5. 总结</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Guava Cache 非常强大,它并没有后台任务线程异步的执行 load 或者 reload 方法,而是通过请求线程来执行相关操作。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">为了提升系统性能,我们可以从如下两个方面来处理 :</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 配置 refresh < expire,减少大量线程阻塞的概率。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 采用 <span style="font-weight: 700;color: rgb(248, 57, 41);">异步刷新</span>的策略,也就是 <span style="font-weight: 700;color: rgb(248, 57, 41);">线程异步加载数据,期间所有请求返回旧的缓存值</span>。 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">笔者曾经优化过某电商网站的首页接口,使用的方案是:Guava 的异步刷新机制 + 多级缓存 ,取得了非常好的优化效果。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">尽管如此,我们在使用这种方式时,依然需要考虑的缓存和数据库一致性问题。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);border-left-width: 2px;padding: 8px 10px 8px 15px;background: rgb(255, 249, 249);border-left-color: rgb(239, 112, 96);margin-top: 0px;margin-bottom: 20px;letter-spacing: 0.5444px;"></blockquote> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"></figure> </section>