作者:微信小助手
<section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;letter-spacing: 0.544px;text-wrap: wrap;font-family: "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", Helvetica, sans-serif;font-size: 16px;text-align: start;background-color: rgb(255, 255, 255);" data-mpa-powered-by="yiban.io"> <section mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;letter-spacing: 0.544px;text-wrap: wrap;font-family: "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", Helvetica, sans-serif;font-size: 16px;text-align: start;background-color: rgb(255, 255, 255);"> <section mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;letter-spacing: 0.544px;text-wrap: wrap;font-family: "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", Helvetica, sans-serif;font-size: 16px;text-align: start;background-color: rgb(255, 255, 255);"> <section mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;letter-spacing: 0.544px;text-wrap: wrap;font-family: "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", Helvetica, sans-serif;font-size: 16px;text-align: start;background-color: rgb(255, 255, 255);"> 前面的文章中,我们已经聊过如何设计一个订单的系统,今天我们分别从日万级订单系统和日千万级的订单系统的设计差异化作分析入口。 <br> </section> </section> </section> </section> <p style="margin-bottom: 0px;outline: 0px;letter-spacing: 0.544px;text-wrap: wrap;font-family: "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", Helvetica, sans-serif;font-size: 16px;text-align: start;background-color: rgb(255, 255, 255);"><br style="outline: 0px;"></p> <p style="margin-bottom: 0px;outline: 0px;letter-spacing: 0.544px;text-wrap: wrap;font-family: "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", Helvetica, sans-serif;font-size: 16px;text-align: start;background-color: rgb(255, 255, 255);"><span style="font-size: 15px;">作为一个后端程序员,在网络下单后,后台的逻辑是怎么处理的。订单系统是怎么保持系统低延迟,高可用,还有不丢单情况的。<br style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.49537037037037035" data-s="300,640" src="/upload/36d57dee2e3bfa61622eac88e9f2744b.png" data-type="png" data-w="1080" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;letter-spacing: 0.544px;text-wrap: wrap;font-family: "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", Helvetica, sans-serif;font-size: 16px;text-align: start;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">我们先来看一个简单的订单系统。</span></p> <p style="margin-bottom: 0px;outline: 0px;letter-spacing: 0.544px;text-wrap: wrap;font-family: "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", Helvetica, sans-serif;font-size: 16px;text-align: start;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">这是一个比较简单的系统,前台有结算页提供用户去结算,当后台收到前台用户点击结算操作的时候,就开始处理下单服务。<br style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;letter-spacing: 0.544px;text-wrap: wrap;font-family: "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", Helvetica, sans-serif;font-size: 16px;text-align: start;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">订单写入到后台的数据库,异构数据到缓存,用户在"我的订单"中进行订单查询,当用户支付完成后,收银台发送消息给下单服务。如下图所示:<br style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.48055555555555557" data-s="300,640" src="/upload/99cdc114f76d1133a09480f1217fba2.png" data-type="png" data-w="1080" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">我们看下这个系统哪些环节会出现丢单情况,我觉得关键点应该聚焦在写数据库、接受消息、发送订单消息这些环节上。</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;"><br></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">这边总结几个可能出现丢单的情况:</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">一.<span style="outline: 0px;color: rgb(0, 82, 255);">关键逻辑不要使用读写分离的查询方式</span>,比如创建订单后要创建支付单。但是在反查订单的时候由于主从延迟,没查询到订单信息,就可能造成创建支付单失败。<br style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.6518518518518519" data-s="300,640" src="/upload/3923e31aebec96ff3fbc53cadd873e53.png" data-type="png" data-w="1080" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">另外,<span style="outline: 0px;color: rgb(0, 82, 255);">关键逻辑也不要使用缓存来进行订单查询</span>,同样是为了避免因为缓存延迟造成订单反查的失败。</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.5728643216080402" data-s="300,640" src="/upload/4a87a3d933a42ddb6ec840b6355ce68.png" data-type="png" data-w="796" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><br></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">还有一点是,<span style="outline: 0px;color: rgb(0, 82, 255);">订单补偿不要粗暴地使用消息队列</span>,可能会造成丢单。比如在进行订单状态的修改时,如果处理失败,则将这个订单信息插入到消息队列中,重新消费,以此来完成订单的补偿,这样会造成消息的丢失。</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;"><br></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.5570719602977667" data-s="300,640" src="/upload/cd0a8a1666135472ef77743958f59ca8.png" data-type="png" data-w="806" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><br></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">最后一点,<span style="outline: 0px;color: rgb(0, 82, 255);">接收消息失败时要让消息进行重试</span>,避免丢失。比如一次消费多条订单记录,一条一条的处理,如果成功,就继续处理,如果修改失败,可能会因为return或者continue关键字将其余消息都丢失掉了。</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;"><br></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.370048309178744" data-s="300,640" src="/upload/d0ff238b861349a33577e193254854fb.png" data-type="png" data-w="1035" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="color: rgb(0, 82, 255);outline: 0px;font-size: 15px;">那么如何设计一个支持日万级别订单系统呢?</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="color: rgb(0, 82, 255);outline: 0px;font-size: 15px;"><br></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">其实前面的设计已经很容易能支撑日万级的订单系统了,但是出于从稳定性和可用性和不丢单的方面去考虑,我们需要进行<span style="outline: 0px;color: rgb(0, 82, 255);">重构优化</span>。<br style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">一个是:<span style="outline: 0px;color: rgb(0, 82, 255);">写数据库时,数据库事务粒度不要太大,避免死锁,关注慢sql</span>。比如,不要再数据库事务里同时去更新其他数据源,或者发送MQ消息等,这不仅不能保证数据的一致性,还会把数据库的连接耗尽。<br style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.3074074074074074" data-s="300,640" src="/upload/15f0c3c999e215b9f72e4b04675f69c0.png" data-type="png" data-w="1080" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><br></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">另外需要注意的是,<span style="outline: 0px;color: rgb(0, 82, 255);">关注异构数据库的稳定性和性能</span>,在网络抖动的情况下,要关注订单系统的幂等性,避免出现计费等错误,影响后续操作流程。</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">做好这几个点,前面的架构方案,基本就可以满足并支撑一个日万级的订单量的订单系统了。</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><br style="outline: 0px;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">那么我们如何设计一个支撑日千万级别的订单系统。万级和千万级主要的区别在一个量,由于量的增大,造成系统负载,导致服务宕机。<br style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;"><br></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);visibility: visible;"><span style="outline: 0px;visibility: visible;font-size: 15px;">首先前面的设计过分依赖数据库,而且这个数据库还是订单库,比如修改订单的状态就要反查数据库,这些高并发的写数据库下,会造成数据资源的抢占,从而影响系统的稳定性。<br style="outline: 0px;visibility: visible;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);visibility: visible;"><br style="outline: 0px;visibility: visible;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);visibility: visible;"><span style="outline: 0px;visibility: visible;font-size: 15px;">其次,为了避免数据的不一致性,请求访问主要集中在主库,这样主库压力就很大。因此就要设计分库分表的部署结构,下单服务因此就要设计成支持分库分表的架构,但由于热点数据的存在,可能导致数据库数据倾斜的问题,会提早进行扩容。</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;visibility: visible;"><img class="rich_pages wxw-img" data-ratio="0.6915422885572139" data-s="300,640" src="/upload/7f464adad080a20cb797d089c8805ac4.png" data-type="png" data-w="1005" style="outline: 0px;visibility: visible !important;width: 677px !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">还有,下单服务耦合业务过重,使得即使是多集群的部署架构,也很难实现快速的处理响应,更何况不同业务的订单处理流程还不一样,使得系统维护性也会越来愈差。比如,创建订单时由于业务不同,数码、3C、图书等订单包含的信息是不一样的,这就需要特殊处理。这种特殊处理逻辑与创建订单耦合在一起,系统自然就会变得越来越慢。</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><br style="outline: 0px;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">总体来说,上面的架构有如下几个问题:下单服务处理接单慢,数据库压力大,数据异构速度慢,缓存数据质量差等,那我们怎么解决呢?</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><br style="outline: 0px;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">为了应对日千万级的订单量,我们将下单服务进行了服务拆分,使用单独的接单服务处理接单,使用订单引擎和订单管道处理订单业务逻辑,改用双写和数据补偿的方式处理缓存,使用缓存过期的方式控制数据量。具体的实现方案如下:<br mpa-from-tpl="t" style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><br style="outline: 0px;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">当用户在结算页点击提交订单之后,接单服务会在同一个事务里,将订单插入接单库,将首任任务插入任务库,再由订单引擎进行任务调度。什么是任务呢,就是订单的操作步骤,比如说订单缓存,减库存,发送订单通知等,我们通过将整个订单处理流程分解成一个个的任务,逐个单独处理,来应对日千万级的订单处理压力。</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><br style="outline: 0px;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">当然,接单库为集群服务器,通过随机的方式写入,原因是可以扩展的灵活性,当遇到流量洪峰的时候,新增服务器,对写入逻辑是无感的,接单库是一主多从,当一台故障,可以快速切换主从或者摘除故障机等手段进行修复。<br style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.6723952738990333" data-s="300,640" src="/upload/e9550ae19616840b6ac82fda48b8c090.png" data-type="png" data-w="931" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">而其中的任务库是订单引擎驱动执行,任务通过订单引擎的服务编排能力生成任务队列,首任务执行成功之后,会插入第二个任务或者同时插入第二个和第三个任务,如果插入任务失败,订单引擎会重新执行当前任务,这里需要每个任务的业务处理都需要保证幂等性。</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.5731481481481482" data-s="300,640" src="/upload/edc276eedcc3d6be4bfc4be31dea29a0.png" data-type="png" data-w="1080" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">刚才是任务的创建,下面说说任务的线程调度方式,任务使用多线程的异步方式进行调度,并根据配置选择是串行还是并发执行。<br style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.5583333333333333" data-s="300,640" src="/upload/8b783e1c7d5fedd449ce9ad8ced20fff.png" data-type="png" data-w="1080" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">前面的任务执行失败,订单引擎是如何重新执行失败的任务的呢?这是通过任务状态机来实现的,状态机通过识别任务的状态,来判断每个任务是执行完成还是执行失败,并根据状态来进行任务调度,并且会多次执行失败的任务,重试调度的频次会逐渐递减,当超过一定的重试次数后,就会告警通知人工干预。<br style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.6268518518518519" data-s="300,640" src="/upload/8de086bfe0bac7bbbae78e9f4ddfdc8f.png" data-type="png" data-w="1080" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="font-size: 15px;"><span style="outline: 0px;color: rgb(0, 82, 255);">订单引擎</span><span style="outline: 0px;">正在执行调度远程服务的并非是订单引擎来调度的,而是由订单引擎调度</span><span style="outline: 0px;color: rgb(0, 82, 255);">订单管道</span><span style="outline: 0px;">,订单管道去调度远程真实的服务来执行的,其原因在于任务引擎本身就是多线程设计架构,对线程占用就比较高。<br style="outline: 0px;"></span></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.4351851851851852" data-s="300,640" src="/upload/d75590ce6178e87915dfbfc475dbd838.png" data-type="png" data-w="1080" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">接下来我们再说下订单缓存的策略。为了保证订单的及时性,在插入订单和任务之后,接单任务会将订单通过接口写入到订单中心的缓存中,以支持用户在线支付之后,在我的订单列表里立即查询到我的订单。</span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><br style="outline: 0px;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;"><span style="outline: 0px;font-size: 15px;"><span style="background-color: rgb(255, 255, 255);">总的来说就是,订单中心接到下单任务之后,会将订单落库。</span><span style="background-color: rgb(255, 255, 255);">再同步更新缓存,在后续订单中心接</span><span style="background-color: rgba(9, 187, 7, 0.31);">收</span><span style="background-color: rgb(255, 255, 255);">到台账的消息后,也会同时更新数据库和缓存,将订单状态更新为订单完成。</span><br style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.7037037037037037" data-s="300,640" src="/upload/8093524fa3cc2718231a3d6e6150d3bf.png" data-type="png" data-w="1080" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><br></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);"><span style="outline: 0px;font-size: 15px;">最后我着重说下订单引擎:根据任务编号依次进程任务调度,更新任务状态,并有任务状态机进行任务校验补偿处理。订单引擎通过调度订单管道,实现真实服务的远程调度,订单管道请求服务之后,将处理结果返回任务引擎,最后,订单中心会在接单服务创建订单时,异步写一份订单缓存在订单中心,然后再通过数据异构的方式,再次写一份数据在订单缓存中。<br style="outline: 0px;"></span></p> <p style="margin-bottom: 0px;outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-wrap: wrap;background-color: rgb(255, 255, 255);text-align: center;"><img class="rich_pages wxw-img" data-ratio="0.5222222222222223" data-s="300,640" src="/upload/167a6d5cd741f3aa4694a858a0643cb7.png" data-type="png" data-w="1080" style="outline: 0px;width: 677px !important;visibility: visible !important;"></p> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>
作者:微信小助手
<section class="mp_profile_iframe_wrp" data-mpa-powered-by="yiban.io"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="Mzg4NjYyODc4OA==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/J4jTHmo8Xh6qM32ASOtVbXNoiaegrI26qLRw6r6FTI7dZw6TMT7vecvnjd1O8xSsM5MiajIuQZicxSC6KFK8TMpbg/0?wx_fmt=png" data-nickname="java突击队" data-alias="" data-signature="技术经验分享" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> <p style="min-height: 24px;margin: 8px 1em;" data-diff-id="ct-diff-id-a8152e1d-f82c-471c-ac35-d146c3239243" data-node-id="167a330af7a04872b4f44faecc9cc56a" data-pm-slice="1 1 []"><span style="font-size: 15px;letter-spacing: 0.5px;">之前,我们已经分享了一些关于实战方面的内容,有一些读者有给我留言,说这些内容对日常工作有帮助,这让我感到非常开心。</span></p> <p style="min-height: 24px;margin: 8px 1em;" data-diff-id="ct-diff-id-a8152e1d-f82c-471c-ac35-d146c3239243" data-node-id="167a330af7a04872b4f44faecc9cc56a" data-pm-slice="1 1 []"><span style="font-size: 15px;letter-spacing: 0.5px;">所以,今天我们继续来讲讲实战方面的内容,那就是:<strong><span style="color: rgb(0, 82, 255);">如何定位java程序中的性能问题?</span></strong></span><br></p> <section style="margin: 8px 1em 24px;min-height: 24px;letter-spacing: 0.578px;white-space: normal;"> <span style="font-size: 15px;letter-spacing: 0.5px;">这个话题来自于读者的留言,但无独有偶的是,我的日常工作也经常会碰到这个问题。我来介绍下我自己常用的一个方式,也算是一个实用小技巧,就是标题中的:<span style="color: rgb(0, 82, 255);"><strong>使用Arthas来定位性能问题</strong></span>。</span> </section> <section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 24px;"> <section data-mpa-template="t" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;" data-mid="" mpa-from-tpl="t"> <section style="display: flex;justify-content: flex-start;align-items: center;flex-direction: column;" data-mid="" mpa-from-tpl="t"> <section style="border-width: 1px;border-style: dotted;border-color: rgb(0, 0, 0);padding: 3px;" data-mid="" mpa-from-tpl="t"> <section style="border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);text-align: center;padding: 4px 15px 5px;" data-mid="" mpa-from-tpl="t"> <p style="font-size: 16px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #000000;line-height: 22px;letter-spacing: 2px;" data-mid="">你也许是这样来定位问题的</p> </section> </section> <section style="width: 6px;height: 6px;border-left: 1px solid #000000;border-bottom: 1px solid #000000;background: #ffffff;align-self: center;margin-top: -7px;z-index: 2;transform: rotate(-45deg);" data-mid="" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> <section style="align-self: center;width: 10px;height: 3px;margin-top: -2px;background: #ffffff;z-index: 1;" data-mid="" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> </section> </section> </section> </section> <section style="margin: 8px 1em 16px;min-height: 24px;letter-spacing: 0.578px;white-space: normal;"> <span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">我相信大家或多或少都碰到过类似如下这样的问题:</span> </section> <section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 24px;"> <section data-mpa-template="t" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;padding: 0px 10px;" data-mid="" mpa-from-tpl="t"> <section style="width: 100%;border-width: 1px;border-style: solid;border-color: #e6e9ed;box-shadow: rgb(204 204 204) 3.53553px 3.53553px 4px;" data-mid="" mpa-from-tpl="t"> <section style="padding: 10px;width: 100%;" data-mid="" mpa-from-tpl="t"> <p data-mid=""><span style="color: rgb(136, 136, 136);"><em><span style="font-size: 14px;">有一个接口提供服务,但这个服务现在耗时很长,无法满足我们的要求,我们需要找到其中的性能问题并解决。</span></em></span></p> </section> </section> </section> </section> </section> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;"><span style="font-size: 15px;letter-spacing: 0.5px;">说白了,就是程序比较慢,我们要找到慢的地方。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-08a3f8b8-b39d-483e-aa26-c379b3219767" data-node-id="a13756e1841440b79c4198ad3bb4d500" data-pm-slice="1 1 []"><span style="font-size: 15px;letter-spacing: 0.5px;">最常见的办法无非是在代码各处添加打点逻辑,统计每一块代码的运行时间,类似下图这样:</span></p> <p style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 24px;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.35185185185185186" data-s="300,640" src="/upload/46e6dc2e00e65a8d0d070b2f96518358.jpg" data-type="jpeg" data-w="1080" style="height: auto !important;"></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-00bac1d9-4413-484d-9d91-bd2ffb17a560" data-node-id="89ff45a875cb4398ab7b9af28558dec0" data-pm-slice="1 1 []"><span style="font-size: 15px;letter-spacing: 0.5px;">这种办法有几个问题:</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-a494aabe-57de-40a2-9751-e4d28c009913" data-node-id="1227d348ee79459c8cbac46b36ad80e1"><span style="font-size: 15px;letter-spacing: 0.5px;font-weight: bold;color: rgb(47, 118, 195);">【1】打点的地方太多了,导致代码特别乱。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-da6d7193-33c9-4ece-8ecb-e6db0a42e4c0" data-node-id="717aec3254924af39c0efa610857c59a"><span style="font-size: 15px;letter-spacing: 0.5px;font-weight: bold;color: rgb(47, 118, 195);">【2】容易漏了打点,导致关键位置没有分析到。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-e17c2578-cc2f-4f02-9a51-323ca07a169c" data-node-id="a853b08d388c4e35b93f74807ff48bef"><span style="font-size: 15px;letter-spacing: 0.5px;font-weight: bold;color: rgb(47, 118, 195);">【3】很多时候会在线下环境打点分析,但是线下又没有足够的请求来复现性能问题。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-f005f379-ed8e-4696-8de1-7b174cae1c1e" data-node-id="8a06ad80e5224df8ba4272a3e6305062"><span style="font-size: 15px;letter-spacing: 0.5px;font-weight: bold;color: rgb(47, 118, 195);">【4】不是所有请求都会有性能问题,但打点针对所有请求,导致打点数据太多无法分析。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-3297e9fe-129c-4c7f-ac14-485bacf2a7fd" data-node-id="7d01a65048e448888dd53ab857c22e4c"><span style="font-size: 15px;letter-spacing: 0.5px;">相信这些点很多同学都感同身受。一个性能分析花上几周都不见得能够定位到问题,更别谈解决了。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-34d6e58d-c58d-43ec-951f-7a674fba75a3" data-node-id="fde38de5cb7c4c4094f9cb2b236e161f" data-pm-slice="1 1 []"><span style="font-size: 15px;letter-spacing: 0.5px;">今天我给大家来介绍下如何使用Arthas来定位代码中的性能问题。也许无法解决所有问题,但“病急乱投医”的你用一下,搞不好就“瞎猫碰到死耗子了呗”<img data-ratio="1" data-w="128" style="display: inline-block;width: 20px;vertical-align: middle;background-size: cover;height: auto !important;" src="/upload/32da9fbfdded8edde3b1eed89cde9cd7.null"></span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-a35d828c-75f6-4476-b280-d0d88690bee3" data-node-id="41b5093a9c384dff9a069d209b1c3c59"><span style="font-size: 15px;letter-spacing: 0.5px;">我这种说法好像是碰碰运气,但事实上在绝大部分场景下,Arthas可以帮助到我们的。我们这就开始吧。</span></p> <section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 24px;"> <section data-mpa-template="t" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;" data-mid="" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;" data-mid="" mpa-from-tpl="t"> <section style="display: flex;justify-content: flex-start;align-items: center;flex-direction: column;align-self: flex-end;margin-right: -4.1px;" data-mid="" mpa-from-tpl="t"> <section style="text-align: center;transform: translateY(25px);" data-mid="" mpa-from-tpl="t"> <p style="font-size: 18px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #FFFFFF;line-height: 25px;letter-spacing: 1px;" data-mid="">01</p> </section> <section style="width: 28px;height: 26px;background: #6F90F0;border-radius: 4px;" data-mid="" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> </section> <section style="text-align: left;padding: 0px 0px 1px 10px;border-bottom: 1px solid #6f90f0;align-self: flex-end;" data-mid="" mpa-from-tpl="t"> <p style="font-size: 16px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #6F90F0;line-height: 22px;letter-spacing: 1px;" data-mid="">Arthas是什么</p> </section> </section> </section> </section> </section> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-bdc68081-de58-49be-8fad-4eaa3b54df10" data-node-id="9de1fc9d1d9b49ef9d2dcf4d43a05eca" data-pm-slice="1 3 []"><span style="font-size: 15px;letter-spacing: 0.5px;">用Arthas之前,我们还是要先介绍一下它。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-c3962d92-ee12-4081-b422-2c4d3b9d8cc6" data-node-id="b3f9f780929b4921ab4b0a130c40913c"><span style="font-size: 15px;letter-spacing: 0.5px;">Arthas是阿里巴巴开源的Java诊断工具,能够在线诊断Java应用程序的性能问题。它可以实时查看应用的线程、类、对象、方法等信息,并且支持热更新类和方法,还可以执行JVM命令和监控系统指标。此外,它还支持Spring Cloud应用、Dubbo服务等场景的诊断与治理,非常适合Java开发及运维人员进行Java应用程序的线上问题排查和性能优化。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-80efeecb-7c97-45c6-bc25-a1876ad52b6f" data-node-id="067176c6abf945048559ea63d011944e"><span style="font-size: 15px;letter-spacing: 0.5px;">我们看下其中的关键字:</span><span style="font-size: 15px;letter-spacing: 0.5px;text-decoration: underline;"><strong>开源、Java、性能问题、热更新、JVM监控、线上问题排查</strong></span><span style="font-size: 15px;letter-spacing: 0.5px;">。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-46b4e915-c522-4b7f-a7e5-5c3dcaf5305b" data-node-id="9133e7ca105a4ae1af46b8c0a8fb46ed"><span style="font-size: 15px;letter-spacing: 0.5px;">看到这些关键字,相信我已经不用做过多的介绍,你已经可以对Arthas的功能知道个八九不离十了。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-9e32e749-3fb2-4ce4-90a6-01607e934ca5" data-node-id="6a351b8d856f4accb3270776bce83d20"><span style="font-size: 15px;letter-spacing: 0.5px;">我还是来再枚举下Arthas的功能:</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-9e32e749-3fb2-4ce4-90a6-01607e934ca5" data-node-id="6a351b8d856f4accb3270776bce83d20"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">【1】实时查看应用的线程、类、对象、方法等信息,支持热更新类和方法。同类工具参考:JConsole、VisualVM、YourKit。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-9e32e749-3fb2-4ce4-90a6-01607e934ca5" data-node-id="6a351b8d856f4accb3270776bce83d20"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">【2】执行JVM命令,监控系统指标,如堆内存、GC、线程等。同类工具参考:jcmd、jstack、jmap等。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-9e32e749-3fb2-4ce4-90a6-01607e934ca5" data-node-id="6a351b8d856f4accb3270776bce83d20"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">【3】支持Spring Cloud应用、Dubbo服务等场景的诊断和治理。同类工具参考:SkyWalking、Pinpoint、Zipkin等。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-9e32e749-3fb2-4ce4-90a6-01607e934ca5" data-node-id="6a351b8d856f4accb3270776bce83d20"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">【4】实时监控请求的响应时间,打印出耗时最长的方法调用链。同类工具参考:Spring Boot Actuator、Micrometer等。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-9e32e749-3fb2-4ce4-90a6-01607e934ca5" data-node-id="6a351b8d856f4accb3270776bce83d20"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">【5】追踪和分析方法的入参和返回值,支持 MVEL 等方式的表达式。同类工具参考:AspectJ、logback。</span></p> <section style="margin: 8px 1em 0px;min-height: 24px;letter-spacing: 0.578px;white-space: normal;"> <span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">【6】支持 Java 8 lambda 表达式的调试和诊断。同类工具参考:Java Debugger、IntelliJ IDEA等。</span> </section> <section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 24px;"> <section data-mpa-template="t" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;" data-mid="" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;" data-mid="" mpa-from-tpl="t"> <section style="display: flex;justify-content: flex-start;align-items: center;flex-direction: column;align-self: flex-end;margin-right: -4.1px;" data-mid="" mpa-from-tpl="t"> <section style="text-align: center;transform: translateY(25px);" data-mid="" mpa-from-tpl="t"> <p style="font-size: 18px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #FFFFFF;line-height: 25px;letter-spacing: 1px;" data-mid="">02</p> </section> <section style="width: 28px;height: 26px;background: #6F90F0;border-radius: 4px;" data-mid="" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> </section> <section style="text-align: left;padding: 0px 0px 1px 10px;border-bottom: 1px solid #6f90f0;align-self: flex-end;" data-mid="" mpa-from-tpl="t"> <p style="font-size: 16px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #6F90F0;line-height: 22px;letter-spacing: 1px;" data-mid="">如何使用Arthas诊断代码性能问题</p> </section> </section> </section> </section> </section> <section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 24px;"> <section data-mpa-template="t" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;padding-left: 18px;" data-mid="" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;justify-content: flex-start;width: 100%;" data-mid="" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t" style="background: url("https://mmbiz.qpic.cn/mmbiz_png/ia1x7XVKSklmDYH0QCVAAiaVPrBjS5qEv4Ht6oQia7qTicWI0vgd6mKqulB5q7fqa02qSnTxZlOhkQQtxQg0XtX3pQ/640?wx_fmt=png") 0% 0% / 100% 100% no-repeat;width: 21px;height: 21px;text-align: center;margin-right: -28px;overflow: hidden;white-space: nowrap;"> <p style="font-size: 12px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #5E8ECB;line-height: 21px;width: 13px;margin: auto;" data-mid="">01</p> </section> <section style="display: flex;justify-content: flex-start;align-items: center;flex-direction: column;margin-left: 8px;" data-mid="" mpa-from-tpl="t"> <section style="text-align: center;z-index: 1;padding: 0px 10px 0px 28px;" data-mid="" mpa-from-tpl="t"> <p style="font-size: 16px;font-family: PingFangSC-Regular, PingFang SC;color: #487BBD;line-height: 22px;letter-spacing: 1px;" data-mid="">基础诊断</p> </section> <section data-mid="" mpa-from-tpl="t" style="background: url("https://mmbiz.qpic.cn/mmbiz_png/3pMjttY77FDibhDsHOTRb7MRgplJ3a1knEib1SJzB7wWRE6KOqUez5eb3xkNa1lMP2tS1Aybt2Zkk8js2nibJbtlA/640?wx_fmt=png") 0% 0% / 100% 100% no-repeat;width: 100%;height: 3px;"> <br mpa-from-tpl="t"> </section> </section> </section> </section> </section> </section> <section style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;"> <span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">我们还是用一个例子来说明。</span> </section> <section style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;"> <span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">下面这个controller提供了一个接口,从日志来看,这个接口耗时是174ms,我们需要确定其中耗时较长的部分。</span> </section> <section style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.26481481481481484" data-s="300,640" src="/upload/827c90f484c7346a05733e02e5246799.png" data-type="png" data-w="1080" style="height: auto !important;"> </section> <section style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.2074688796680498" data-s="300,640" data-type="png" data-w="482" style="width: 542px;height: auto !important;" src="/upload/d5a5c7758b02175f2d7c5e8a25eb5b2a.png"> </section> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-e6735ea9-df3c-4e7e-8cd1-b4d0eb97fe6e" data-node-id="b624d4a4d24e42fbb1c61388f908325a" data-pm-slice="1 1 []"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">最常规的办法就是上面提到的加上各种打点,我们再图示一下这种方式,如下:</span></p> <section style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.41388888888888886" data-s="300,640" src="/upload/dd5a9ed4a36f958ed99aae268538b55b.png" data-type="png" data-w="1080" style="height: auto !important;"> </section> <p style="text-align: center;margin: 8px 1em;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.36177474402730375" data-s="300,640" data-type="png" data-w="586" style="height: auto !important;" src="/upload/539dde705a8244a349ccb4df6f368df9.png"></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-ac6f48f5-f436-4027-aece-a63688ea3b23" data-node-id="88e99cadeefd43708bc198845994390a" data-pm-slice="1 1 []"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">可想而知,如果代码很多,其中的方法又层层嵌套的话,不知道要加多少打点代码了。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-1184f3ac-fc89-4a59-91b9-3f215b027093" data-node-id="1d2e3e12a32844ffafcb2f6ff2ffea8a"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">如果用Arthas的话,我们可以用它的<span style="font-weight: bold;color: rgb(47, 118, 195);">trace</span>命令来直接查看各个方法的耗时,我们一步步来做一下(这里我们省略了arthas的安装过程,非常容易):</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-cd5a60f1-91cb-40ee-8666-05514ab7bb95" data-node-id="02fbe141d1f94cd4bdbb56825e30f02d" data-pm-slice="1 1 []"><span style="font-size: 16px;"><strong><span style="letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">【step1】启动我们的应用</span></strong></span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-cd5a60f1-91cb-40ee-8666-05514ab7bb95" data-node-id="02fbe141d1f94cd4bdbb56825e30f02d" data-pm-slice="1 1 []"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">第一步当然是先启动应用。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-fa9b2353-3003-42f0-900f-37c6efd0e40e" data-node-id="4e8043fdf5b94e4f8f6be4d2748c4a0f"><span style="font-size: 16px;"><strong><span style="letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">【step2】使用Arthas连接到我们的JVM进程</span></strong></span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-fa9b2353-3003-42f0-900f-37c6efd0e40e" data-node-id="4e8043fdf5b94e4f8f6be4d2748c4a0f"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">我们到arthas所在目录下,执行如下命令:<br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="nginx"><code><span class="code-snippet_outer">java -jar arthas-boot.jar</span></code></pre> </section> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-fa9b2353-3003-42f0-900f-37c6efd0e40e" data-node-id="4e8043fdf5b94e4f8f6be4d2748c4a0f"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">我们可以看到如下结果:</span></p> <section style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.11944444444444445" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/d42f1cdfae3bdfabb9ff873f5920d116.png"> </section> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">我们可以从中看到本地运行的所有jvm进程,从其中选择我们的程序【2】,输入2回车即可。</span><br></p> <section style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.6363636363636364" data-s="300,640" src="/upload/f6f64a2023538eb81700687fa34f2dc4.png" data-type="png" data-w="968" style="height: auto !important;"> </section> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">我们就进入了还蛮有腔调的Arthas工作台。</span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-c5afff7c-f88d-42ae-88bc-4125667752bd" data-node-id="3e9a1bb6978b425381ebf10604c41c29"><span style="font-size: 16px;"><strong><span style="letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">【step3】使用trace命令指定追踪的类和方法</span></strong></span></p> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-c5afff7c-f88d-42ae-88bc-4125667752bd" data-node-id="3e9a1bb6978b425381ebf10604c41c29"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">接着我们输入要追踪的类和方法。<span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">输入格式为:</span></span><span style="color: rgb(136, 136, 136);font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-decoration: underline;"><em><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;text-decoration: underline;color: rgb(136, 136, 136);letter-spacing: 0.578px;">trace {全限定类名} {方法名}</span></em></span><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">:<br></span></p> <section style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.07777777777777778" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/49775600f5a82be1c4dae43c962c9630.png"> </section> <section style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">这里需要给Arthas点赞的是,整个类名和方法名的输入过程是可以使用tab来补全的,非常方便。</span> </section> <p style="margin: 8px 1em;min-height: 24px;letter-spacing: 0.578px;white-space: normal;" data-diff-id="ct-diff-id-9fd8f848-4402-4ff7-b4a4-b5b5838745f8" data-node-id="b83af2cd6a2c4e08a7082ec7546ec91c"><span style="font-size: 16px;"><strong><span style="letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">【step4】定位耗时部分</span></strong></span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;" data-diff-id="ct-diff-id-9fd8f848-4402-4ff7-b4a4-b5b5838745f8" data-node-id="b83af2cd6a2c4e08a7082ec7546ec91c"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">我们来访问下controller中的callServices这个方法。这个时候arthas控制台有如下输出:</span></p> <section style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.2222222222222222" data-s="300,640" src="/upload/6116d27ec3a664f44626a9186f1a9e89.png" data-type="png" data-w="1080" style="height: auto !important;"> </section> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">我们可以看到,arthas给我们分析了整个过程中最耗时的部分。这不就是我们想要的功能吗?我们可以选择其中耗时的部分继续往下排查。</span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">怎么往下?重新trace耗时的类名+方法名就好了。</span></p> <section style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.16111111111111112" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/5b6747a7bc51bcfbff12f4fcac42f884.png"> </section> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">就这样,我们找到了这个demo中耗时的部分,就是RpcC这个远程调用耗时过长。</span></p> <section style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 24px;"> <span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">要知道,这可以直接在不侵入代码逻辑的情况下,追踪性能的“凶手”。</span> </section> <section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 24px;"> <section data-mpa-template="t" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;padding-left: 18px;" data-mid="" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;justify-content: flex-start;width: 100%;" data-mid="" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t" style="background: url("https://mmbiz.qpic.cn/mmbiz_png/ia1x7XVKSklmDYH0QCVAAiaVPrBjS5qEv4Ht6oQia7qTicWI0vgd6mKqulB5q7fqa02qSnTxZlOhkQQtxQg0XtX3pQ/640?wx_fmt=png") 0% 0% / 100% 100% no-repeat;width: 21px;height: 21px;text-align: center;margin-right: -28px;overflow: hidden;white-space: nowrap;"> <p style="font-size: 12px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #5E8ECB;line-height: 21px;width: 13px;margin: auto;" data-mid="">02</p> </section> <section style="display: flex;justify-content: flex-start;align-items: center;flex-direction: column;margin-left: 8px;" data-mid="" mpa-from-tpl="t"> <section style="text-align: center;z-index: 1;padding: 0px 10px 0px 28px;" data-mid="" mpa-from-tpl="t"> <p style="font-size: 16px;font-family: PingFangSC-Regular, PingFang SC;color: #487BBD;line-height: 22px;letter-spacing: 1px;" data-mid="">进阶诊断</p> </section> <section data-mid="" mpa-from-tpl="t" style="background: url("https://mmbiz.qpic.cn/mmbiz_png/3pMjttY77FDibhDsHOTRb7MRgplJ3a1knEib1SJzB7wWRE6KOqUez5eb3xkNa1lMP2tS1Aybt2Zkk8js2nibJbtlA/640?wx_fmt=png") 0% 0% / 100% 100% no-repeat;width: 100%;height: 3px;"> <br mpa-from-tpl="t"> </section> </section> </section> </section> </section> </section> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">但在真实世界中,诊断可能没有那么简单,最常见的现象就是:<strong>并不是所有的请求都有性能问题,而往往是其中的一部分请求表现出了性能问题</strong>。</span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;">在Arthas中,可以对需要观测的请求做筛选,以使得我们观测到我们关心的部分。</span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><strong><span style="font-size: 15px;letter-spacing: 0.5px;">【根据入参筛选】</span></strong></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;">最常见的筛选条件就是根据入参进行筛选:</span></p> <section style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.1398148148148148" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/71d0a57b7189d8c47bd7d04280758221.png"> </section> <section style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <span style="font-size: 15px;letter-spacing: 0.5px;">上图中,我们就要求:入参的第一个参数等于字符串“abc”才查看耗时情况。</span> </section> <section style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <span style="font-size: 15px;letter-spacing: 0.5px;">上图红色框部分使用的是OGNL表达式,支持各种复杂的嵌套结构及比较逻辑,你可以自己去官网上了解下。<br></span> </section> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><strong><span style="font-size: 15px;letter-spacing: 0.5px;">【根据耗时筛选】</span></strong><span style="font-size: 15px;letter-spacing: 0.5px;"><br></span><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">还有一种较为常见的,就是我们只关心达到一定运行时间的情况,比如某个方法超过180ms我才关心其具体情况。那我们就可以使用#cost关键字。例如:</span></p> <p style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.3509259259259259" data-s="300,640" src="/upload/2fa267e29e22f735da55a496064196ff.png" data-type="png" data-w="1080" style="height: auto !important;"></p> <section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 24px;"> <section data-mpa-template="t" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;" data-mid="" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;" data-mid="" mpa-from-tpl="t"> <section style="display: flex;justify-content: flex-start;align-items: center;flex-direction: column;align-self: flex-end;margin-right: -4.1px;" data-mid="" mpa-from-tpl="t"> <section style="text-align: center;transform: translateY(25px);" data-mid="" mpa-from-tpl="t"> <p style="font-size: 18px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #FFFFFF;line-height: 25px;letter-spacing: 1px;" data-mid="">03</p> </section> <section style="width: 28px;height: 26px;background: #6F90F0;border-radius: 4px;" data-mid="" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> </section> <section style="text-align: left;padding: 0px 0px 1px 10px;border-bottom: 1px solid #6f90f0;align-self: flex-end;" data-mid="" mpa-from-tpl="t"> <p style="font-size: 16px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #6F90F0;line-height: 22px;letter-spacing: 1px;" data-mid="">火焰图<br></p> </section> </section> </section> </section> </section> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">最后,我们再来聊聊在碰到性能问题时我们常用到的一个工具,那就是“火焰图”。<br></span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">火焰图并不是Arthas发明出来的东西,而Arthas中可以方便的采样并生成火焰图。我们来看一个例子(<span style="text-align: left;">这个例子是上面我们讲述Arthas定位性能问题时用到的例子,这里再重新看一下 ):</span></span></p> <section style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.24722222222222223" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/8e403b8dd64bc26c922aa10cfee3d357.png"> </section> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">从例子中可以看到,callServices接口调用了三个服务(ServiceA、ServiceB、ServiceC)。</span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">然后,我们在Arthas中依次开启profile采集、观察采集样本数、终止采集(这里需要注意的是,profiler start默认对cpu采样,你也可以选择采样事件):</span></p> <section style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.14814814814814814" data-s="300,640" src="/upload/d4b0dd66366091add79ed702816121b8.png" data-type="png" data-w="1080" style="height: auto !important;"> </section> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">然后,我们打开采样后的火焰图(火焰图地址会在profiler stop后显示在终端)</span></p> <section style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.42962962962962964" data-s="300,640" src="/upload/feb67230f9e61efd999e687c9e8e8fdd.png" data-type="png" data-w="1080" style="height: auto !important;"> </section> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">简单说下火焰图怎么看:</span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;"><span style="font-weight: bold;">【纵轴(Y轴)】</span>表示的是方法调用的调用栈信息,即从最外层方法到最内层方法的调用信息。</span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-size: 15px;letter-spacing: 0.5px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;"><span style="font-weight: bold;">【横轴(X轴)】</span>表示的是方法调用的持续时间,即方法在执行过程中所占用的时间长度。</span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 15px;letter-spacing: 0.5px;color: rgb(47, 118, 195);font-weight: bold;">在一个火焰图中,越靠近下面的函数在x轴上越长是正常的,而越往上的函数就应该越短。所以,火焰图也像是一个个山峰。那么,如果火焰图出现较宽的峰顶,那就往往是性能瓶颈。</span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 15px;letter-spacing: 0.5px;">如上图所示,出现了明显的“平顶”。<span style="text-align: left;">“平顶”</span>对应的函数是ServiceC.process函数,我们看一下他的实现。</span></p> <section style="text-align: center;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.7648148148148148" data-s="300,640" src="/upload/5f459e0f4f54f54edcdd5cd9c80c8bc5.png" data-type="png" data-w="1080" style="height: auto !important;"> </section> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;"><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 15px;letter-spacing: 0.5px;">没错,这个ServiceC正是我故意实现的一个较为耗时的函数(计算1000w内的所有质数)。</span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 24px;"><span style="font-size: 15px;letter-spacing: 0.5px;">所以,通过火焰图,我们找到了性能问题。</span></p> <section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 24px;"> <section data-mpa-template="t" mpa-from-tpl="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;" data-mid="" mpa-from-tpl="t"> <section style="display: flex;justify-content: flex-start;align-items: center;flex-direction: column;" data-mid="" mpa-from-tpl="t"> <section style="border-width: 1px;border-style: dotted;border-color: rgb(0, 0, 0);padding: 3px;" data-mid="" mpa-from-tpl="t"> <section style="border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);text-align: center;padding: 4px 15px 5px;" data-mid="" mpa-from-tpl="t"> <p style="font-size: 16px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #000000;line-height: 22px;letter-spacing: 2px;" data-mid="">结语</p> </section> </section> <section style="width: 6px;height: 6px;border-left: 1px solid #000000;border-bottom: 1px solid #000000;background: #ffffff;align-self: center;margin-top: -7px;z-index: 2;transform: rotate(-45deg);" data-mid="" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> <section style="align-self: center;width: 10px;height: 3px;margin-top: -2px;background: #ffffff;z-index: 1;" data-mid="" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> </section> </section> </section> </section> <p style="text-align: left;margin: 8px 1em;"><span style="font-size: 15px;letter-spacing: 0.5px;">到这里,我们把<span style="color: rgb(47, 118, 195);font-weight: bold;">【如何使用Arthas定位java性能问题】</span>讲完了。</span></p> <p style="text-align: left;margin: 8px 1em;"><span style="font-size: 15px;letter-spacing: 0.5px;">事实上,Arthas远比今天介绍的要强大和丰富。这篇文章也算是Arthas的敲门砖,如果你感兴趣,非常推荐你自己去研究研究(Arthas的阿里云官网)。</span></p> <p style="text-align: left;margin: 8px 1em;"><span style="font-size: 15px;letter-spacing: 0.5px;">回头说,性能问题是一个范围很广的问题。语言层面、算法层面、通信层面、部署层面等等都会有性能问题及对应的解决方式。我们今天仅聚焦在其中的一个小点,也就是</span><span style="font-size: 15px;letter-spacing: 0.5px;text-decoration: underline;"><strong>定位java程序内的性能瓶颈</strong></span><span style="font-size: 15px;letter-spacing: 0.5px;">。但我相信,这个点往往是我们工作中最常碰到和死磕的点。</span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;" data-diff-id="ct-diff-id-43974825-866f-4adc-ac36-e6f2c3276851" data-node-id="b72308f9926c4ed0819e7e76a1fa5e63" data-pm-slice="1 1 []"><span style="font-size: 15px;letter-spacing: 0.5px;">希望今天的内容能够帮助到你,也推荐你收藏,如果以后碰到可以拿出来用用。<br></span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;" data-diff-id="ct-diff-id-43974825-866f-4adc-ac36-e6f2c3276851" data-node-id="b72308f9926c4ed0819e7e76a1fa5e63" data-pm-slice="1 1 []"><span style="font-size: 15px;letter-spacing: 0.5px;">最后想要再叨叨两句我的【实战板块】。我对【实战板块】的要求是“小而美”。</span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;" data-diff-id="ct-diff-id-43974825-866f-4adc-ac36-e6f2c3276851" data-node-id="b72308f9926c4ed0819e7e76a1fa5e63" data-pm-slice="1 1 []"><span style="font-size: 15px;letter-spacing: 0.5px;">所谓的【小】是指具备<span style="font-weight: bold;color: rgb(47, 118, 195);">可操作性</span>。你有某方面的问题,这篇文章足够帮助你动起手来就可以,并不追求多么全面。<br></span></p> <p style="text-align: left;margin-left: 1em;margin-right: 1em;margin-bottom: 8px;" data-diff-id="ct-diff-id-43974825-866f-4adc-ac36-e6f2c3276851" data-node-id="b72308f9926c4ed0819e7e76a1fa5e63" data-pm-slice="1 1 []"><span style="font-size: 15px;letter-spacing: 0.5px;">所谓的【美】是指具备<span style="color: rgb(47, 118, 195);font-weight: bold;">实用性</span>。实战板块的内容就是针对日常工作的,不追求高大上的各种概念,接地气。</span></p> <pre style="outline: 0px;font-size: 16px;color: rgb(0, 0, 0);font-weight: 700;letter-spacing: 0.544px;orphans: 4;widows: 1;word-spacing: 1px;caret-color: rgb(51, 51, 51);background-color: rgb(255, 255, 255);text-align: center;"><p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;outline: 0px;letter-spacing: 0.544px;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;color: rgb(63, 63, 63);word-spacing: 0.8px;line-height: 26px;"><span style="outline: 0px;">最后欢迎大家加入苏三的知识星球<span style="outline: 0px;color: rgb(74, 74, 74);letter-spacing: 0.544px;">【</span><strong style="outline: 0px;color: rgb(74, 74, 74);letter-spacing: 0.544px;line-height: 1.75em;visibility: visible;">Java突击队</strong><span style="outline: 0px;color: rgb(74, 74, 74);letter-spacing: 0.544px;">】</span>,一起学习。</span></p><p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;outline: 0px;letter-spacing: 0.544px;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;color: rgb(63, 63, 63);word-spacing: 0.8px;line-height: 26px;"><span style="outline: 0px;color: rgb(74, 74, 74);letter-spacing: 0.544px;">星球中有很多独家的干货内容,比如:Java后端学习路线,分享实战项目,源码分析,百万级系统设计,系统上线的一些坑,MQ专题,真实面试题,每天都会回答大家提出的问题,免费修改简历,免费回答工作中的问题。</span></p><p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;outline: 0px;letter-spacing: 0.544px;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;color: rgb(63, 63, 63);word-spacing: 0.8px;line-height: 26px;"><span style="outline: 0px;">星球目前开通了9个优质专栏:技术选型、系统设计、踩坑分享、工作实战、底层原理、Spring源码解读、痛点问题、高频面试题 和 性能优化。</span></p><p style="outline: 0px;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;"><br></p><p style="outline: 0px;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;"><br></p><p style="outline: 0px;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="1.7103658536585367" data-s="300,640" src="/upload/3053c4e84da01825ceaa70993340f1aa.png" data-type="png" data-w="656" style="outline: 0px;visibility: visible !important;width: 656px !important;"></p><p style="outline: 0px;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;font-size: 15px;"><span style="outline: 0px;letter-spacing: 0.544px;caret-color: rgb(255, 0, 0);"></span></p><p style="outline: 0px;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;"><span style="outline: 0px;letter-spacing: 0.544px;caret-color: rgb(255, 0, 0);color: rgb(63, 63, 63);font-size: 15px;text-align: left;"></span></p><p style="outline: 0px;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="1.215625" data-s="300,640" src="/upload/dab88f6200602fee24aec03b754b2fbe.png" data-type="png" data-w="640" style="outline: 0px;visibility: visible !important;width: 640px !important;"></p><p style="outline: 0px;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;"><br style="outline: 0px;"></p><p style="outline: 0px;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;word-spacing: 1.5px;color: rgb(63, 63, 63);font-size: 15px;"><span style="outline: 0px;letter-spacing: 0.544px;word-spacing: 1px;caret-color: rgb(255, 0, 0);"></span></p><p style="outline: 0px;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;word-spacing: 1.5px;color: rgb(63, 63, 63);font-size: 15px;"><span style="outline: 0px;letter-spacing: 0.544px;word-spacing: 1px;caret-color: rgb(255, 0, 0);">加入星球如果不满意,3天内包退。</span></p></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;outline: 0px;line-height: 30px;font-family: "Avenir, -apple-system-font, 微软雅黑, sans-serif";letter-spacing: 0.5444px;"><br style="outline: 0px;color: rgb(74, 74, 74);font-size: 15px;letter-spacing: 0.5444px;text-wrap: wrap;background-color: rgb(255, 255, 255);"></p> <p style="display: none;margin-bottom: 24px;"> <mp-style-type data-value="3"></mp-style-type></p>
作者:微信小助手
<h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;outline: 0px;font-weight: bold;font-size: 1.3em;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;background-color: rgb(255, 255, 255);border-bottom: 2px solid rgb(9, 142, 252);visibility: visible;color: rgb(9, 142, 252) !important;"><span style="margin-right: 3px;padding: 3px 10px 1px;outline: 0px;display: inline-block;background: rgb(9, 142, 252);color: rgb(255, 255, 255);border-top-right-radius: 3px;border-top-left-radius: 3px;visibility: visible;"><span style="outline: 0px;letter-spacing: 0.578px;visibility: visible;">常见方法</span></span></h2> <p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消息中间件通常采用一些策略来避免消息的重复消费。这在分布式系统中非常重要,以确保消息被处理一次且仅一次,避免产生错误或重复的结果。以下是一些常见的方法:</p> <ol style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);" class="list-paddingleft-1"> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消息确认机制</span>:消费者在处理完一条消息后,向消息中间件发送确认消息。如果消息中间件收到确认,就会将该消息标记为已消费,如果没有收到确认,就会将消息重新发送给其他消费者。这确保了消息只有在确认后才会被标记为已处理。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消息去重</span>:消息中间件可以在存储消息之前对消息内容进行去重操作,以确保相同内容的消息只被投递一次。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消费者端去重</span>:消费者可以在自己的业务逻辑中实现去重操作。比如,记录已处理的消息 ID 或唯一标识符,以避免处理相同的消息。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">幂等性处理</span>:消费者可以设计其处理逻辑,使得多次处理相同的消息不会产生不一致的结果。这需要确保相同的操作可以多次执行,而不会引起问题。例如,数据库插入操作可以使用主键冲突处理,确保不会重复插入相同记录。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消息超时机制</span>:如果消息在一定时间内没有得到确认,消息中间件可以将其重新发送给其他消费者,以确保消息不会永远挂起在未确认状态。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">分布式事务</span>:在一些情况下,消息消费可能需要和其他操作一起构成一个分布式事务。消息中间件可以与其他数据存储或操作协同工作,以保证消息和其他操作的一致性。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消息顺序保证</span>:有些消息中间件支持保证消息按照特定的顺序传递给消费者,这有助于避免由于消息乱序而导致的重复消费问题。</p></li> </ol> <p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">不同的消息中间件提供不同的机制来处理消息的重复消费问题,开发者在选择和使用消息中间件时需要考虑这些因素,并根据实际需求来实现避免重复消费的策略。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 1.3em;text-wrap: wrap;outline: 0px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;background-color: rgb(255, 255, 255);border-bottom: 2px solid rgb(9, 142, 252);visibility: visible;color: rgb(9, 142, 252) !important;"><span style="margin-right: 3px;padding: 3px 10px 1px;outline: 0px;display: inline-block;background: rgb(9, 142, 252);color: rgb(255, 255, 255);border-top-right-radius: 3px;border-top-left-radius: 3px;visibility: visible;"><span style="outline: 0px;letter-spacing: 0.578px;visibility: visible;">RocketMQ</span></span></h2> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.6555977229601518" data-s="300,640" src="/upload/8ef5b59e339d2982d0d5125c875f1af.png" data-type="png" data-w="1054" style=""></p> <p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">Apache RocketMQ 是一个开源的分布式消息中间件,它提供了一些机制来避免重复消费消息。以下是 RocketMQ 如何避免重复消费消息的一些方法:</p> <ol style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);" class="list-paddingleft-1"> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消息消费状态追踪</span>:RocketMQ 提供了消费者的消息消费状态追踪功能。消费者可以定期向消息中间件发送消费进度信息,包括已成功消费的消息的偏移量。当消费者重启或者发生故障时,RocketMQ 可以根据消费者提交的消费进度信息,将尚未消费的消息重新传递给消费者。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消费者组</span>:RocketMQ 允许多个消费者以相同的消费者组名字订阅同一个主题。这些消费者会形成一个消费者组,消息会被分发给组内的每个消费者。当组内某个消费者成功消费了一条消息后,消息将被标记为已消费,其他组内的消费者将不会再收到该消息。这样可以确保在同一个消费者组内不会出现重复消费。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消费者幂等性设计</span>:开发者可以设计消费者的业务逻辑,使得即使接收到相同的消息多次,也不会产生重复的影响。这需要在业务逻辑中考虑幂等性,确保多次处理相同消息不会产生错误或重复的结果。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消费者端去重</span>:类似于其他消息中间件,RocketMQ 的消费者也可以在消费者端实现去重操作,比如记录已处理的消息 ID 或唯一标识符,以避免处理相同的消息。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消息的唯一标识符</span>:为每条消息生成一个唯一的标识符,并在消费者端使用这个标识符来判断是否重复消费。这要求生产者在发送消息时,附加一个唯一标识符。</p></li> </ol> <p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">需要注意的是,尽管 RocketMQ 提供了这些机制来避免重复消费,但开发者在设计和实现消费者时,仍然需要注意保证幂等性和正确处理可能的重复消息情况。</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.5328125" data-s="300,640" src="/upload/c0ba174c6ac53071bafd8b94f40f1801.png" data-type="png" data-w="1280" style=""></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 1.3em;text-wrap: wrap;outline: 0px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;background-color: rgb(255, 255, 255);border-bottom: 2px solid rgb(9, 142, 252);visibility: visible;color: rgb(9, 142, 252) !important;"><span style="margin-right: 3px;padding: 3px 10px 1px;outline: 0px;display: inline-block;background: rgb(9, 142, 252);color: rgb(255, 255, 255);border-top-right-radius: 3px;border-top-left-radius: 3px;visibility: visible;"><span style="outline: 0px;letter-spacing: 0.578px;visibility: visible;">RabbitMQ</span></span></h2> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.43125" data-s="300,640" src="/upload/1ee24658fa150ed95af4a2f96eb0bf5e.png" data-type="png" data-w="1280" style=""></p> <p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">RabbitMQ 是另一个常用的开源消息中间件,它也提供了一些方法来避免重复消费消息。以下是 RabbitMQ 如何处理避免重复消费消息的一些方式:</p> <ol style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);" class="list-paddingleft-1"> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消息确认机制</span>:RabbitMQ 支持消息确认机制,消费者在处理完一条消息后,向 RabbitMQ 发送确认消息。如果消息处理成功,RabbitMQ 将会将该消息标记为已消费,如果没有收到确认,RabbitMQ 可能会将消息重新发送给其他消费者。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消息去重</span>:在消息的发布者端,可以设置消息的唯一标识符,并在消费者端维护已处理的消息标识符。这样消费者在处理消息前,先检查该消息的标识符是否已经处理过,避免重复消费。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消费者幂等性设计</span>:类似于其他消息中间件,RabbitMQ 的消费者也可以设计业务逻辑,使得多次处理相同的消息不会引起错误或重复的结果。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消息的唯一标识符</span>:为每条消息生成一个唯一的标识符,消费者在处理消息时,可以使用这个标识符来判断是否已经处理过该消息。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消费者端的定时确认</span>:消费者可以在处理完消息后,通过一段时间内定时确认的方式,来确保消息已经被正确处理。如果在确认之前消费者发生了故障,消息会被重新发送给其他消费者。</p></li> <li style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><span style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">消息过期机制</span>:RabbitMQ 支持设置消息的过期时间,如果一条消息在一定时间内没有被消费者处理,就会被标记为过期,不会再被发送给消费者。</p></li> </ol> <p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);">无论选择哪种方法,都需要开发者在设计消费者时考虑到可能的重复消息问题,并实现相应的逻辑来确保消息被处理一次且仅一次。</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.459375" data-s="300,640" src="/upload/627195056572ae0233429c189b15eeff.png" data-type="png" data-w="1280" style=""></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 1.3em;text-wrap: wrap;outline: 0px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;background-color: rgb(255, 255, 255);border-bottom: 2px solid rgb(9, 142, 252);visibility: visible;color: rgb(9, 142, 252) !important;"><span style="margin-right: 3px;padding: 3px 10px 1px;outline: 0px;display: inline-block;background: rgb(9, 142, 252);color: rgb(255, 255, 255);border-top-right-radius: 3px;border-top-left-radius: 3px;visibility: visible;"><span style="outline: 0px;letter-spacing: 0.578px;visibility: visible;">Kafka</span></span></h2> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.50234375" data-s="300,640" src="/upload/896bb29a5380dd3f6126a33a3e0bc90b.png" data-type="png" data-w="1280" style=""></p> <p style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ">Apache Kafka 是另一种流行的分布式消息中间件,它也提供了一些方法来避免重复消费消息。以下是 Kafka 如何处理避免重复消费消息的一些策略:</p> <ol style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; " class="list-paddingleft-1"> <li style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; "><p style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; "><span style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ">消费者偏移量(Consumer Offset)管理</span>:Kafka 使用偏移量来标识每个消费者所消费的消息位置。消费者可以将已处理的消息的偏移量保存在外部存储中(如数据库或文件),以便在重启后能够从正确的位置开始消费。这确保了消费者能够继续从上次处理的位置继续消费消息,避免了重复消费。</p></li> <li style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; "><p style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; "><span style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ">消费者组管理</span>:Kafka 允许多个消费者以相同的消费者组名字订阅同一个主题。同一个消费者组内的消费者共同消费消息,并且每条消息只会被组内的一个消费者处理。这样可以避免同一消息被多次消费。</p></li> <li style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; "><p style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; "><span style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ">幂等性处理</span>:消费者可以设计其处理逻辑,使得多次处理相同的消息不会产生不一致的结果。这需要确保相同的操作可以多次执行,而不会引起问题。例如,数据库插入操作可以使用主键冲突处理,确保不会重复插入相同记录。</p></li> <li style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; "><p style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; "><span style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ">消息超时机制</span>:Kafka 提供了消息超时的机制,如果一个消费者在一定时间内没有确认消费消息,Kafka 将会将该消息重新发送给其他消费者。</p></li> <li style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; "><p style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; "><span style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ">消费者心跳和会话超时</span>:消费者定期发送心跳给 Kafka 服务器,以表明自己还在活动状态。如果消费者崩溃或无法发送心跳,Kafka 服务器会认为该消费者不再活动,并将其所持有的分区重新分配给其他消费者。这有助于避免消费者长时间无响应而导致重复消费。</p></li> <li style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; "><p style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; "><span style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ">幂等生产者</span>:在消息的生产端,可以使用幂等生产者来确保消息不会重复发送。Kafka 的幂等生产者会在发送消息时为消息分配一个唯一的序列号,并在发送失败后自动重试,确保消息只会被发送一次。</p></li> </ol> <p style=" border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227); ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ">使用这些方法,开发者可以在 Kafka 中实现避免重复消费消息的策略,确保消息被处理一次且仅一次。</p> <p style="border-width: 0px;border-style: solid;border-color: rgb(217, 217, 227);"><br></p> <p><br></p> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>
作者:微信小助手
<section data-role="outer" label="edit by 135editor"> <section style="font-size: 16px;"> <section style="margin-top: 30px;margin-bottom: 10px;text-align: center;"> <section style="padding: 10px;display: inline-block;border-width: 1px;border-style: solid;border-color: rgb(192, 200, 209);background-color: rgb(255, 255, 255);width: 100%;box-shadow: rgb(220, 221, 221) 3.5px 3.5px 0px;height: auto;"> <section style="margin-top: -28px;"> <section style="padding: 0.1em 0.3em;display: inline-block;border-width: 2px;border-style: solid;border-color: rgb(255, 255, 255);background-color: rgb(254, 255, 255);color: rgb(13, 0, 21);font-size: 18px;"> <p><span style="color: rgb(122, 68, 66);"><strong>STRAT</strong></span></p> </section> </section> <section style="font-size: 24px;"> <p><span style="color: rgb(61, 170, 214);font-size: 22px;">乘风破浪 | 直挂云帆</span></p> </section> </section> </section> </section> <p style="text-align:justify;margin: 24px 8px;line-height: 1.75em;"><span style="color: rgb(96, 96, 96);font-size: 15px;letter-spacing: 1px;caret-color: red;">Redisson是</span><span style="font-size: 15px;letter-spacing: 1px;caret-color: red;color: rgb(255, 76, 0);">基于NIO的Netty框架</span><span style="color: rgb(96, 96, 96);font-size: 15px;letter-spacing: 1px;caret-color: red;">上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。能够为我们提供多服务版并发解决的能力,大大降低分布式系统的难度。</span></p> <p style="text-align:justify;margin: 24px 8px;line-height: 1.75em;"><span style="color:#4d4d4d;"><span style="font-size: 16px;"><img class="rich_pages wxw-img" data-ratio="0.5203007518796993" data-type="png" data-w="665" style="vertical-align: inherit;color: rgba(0, 0, 0, 0.9);font-size: 17px;letter-spacing: 0.578px;width: 578px;height: auto !important;" width="532" src="/upload/ab7230f1c592417f2bf8e9bf2b281cc6.png"></span></span></p> <section data-class="_mbEditor"> <section data-class="_mbEditor"> <section style="margin: 10px auto;text-align: left;"> <section style="margin-bottom: -15px;margin-left: 16px;letter-spacing: 1.5px;"> <span style="color: rgb(122, 68, 66);">使用与底层结构</span> </section> <section style="display: flex;justify-content: center;align-items: flex-end;"> <section style="width: 0.886525%;height: 22px;background-color: rgb(66, 163, 225);"> <br> </section> <section style="margin-left: 8px;width: 100%;height: 3px;background-color: rgb(255, 238, 194);flex: 1 1 0%;"> <br> </section> </section> </section> </section> </section> <section data-class="_mbEditor" style="letter-spacing: 0.578px;text-wrap: wrap;"> <section data-class="_mbEditor"> <section style="margin: 10px auto;text-align: left;"> <section> <section data-class="_mbEditor" style="letter-spacing: 0.578px;"> <section data-class="_mbEditor"> <section> <p style="margin: 24px 8px;line-height: 1.75em;text-indent: 0em;"><span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">主要的使用时集成到Springboot中,首先引入pom依赖,然后引入配置。</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="xml"><code><span class="code-snippet_outer"><dependency></span></code><code><span class="code-snippet_outer"> <groupId>org.redisson</groupId></span></code><code><span class="code-snippet_outer"> <artifactId>redisson</artifactId></span></code><code><span class="code-snippet_outer"> <version>3.11.1</version></span></code><code><span class="code-snippet_outer"></dependency></span></code></pre> </section> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="kotlin"><code><span class="code-snippet_outer" style="color: rgb(175, 175, 175);">@Configuration</span></code><code><span class="code-snippet_outer"><span style="color: rgb(202, 125, 55);">public</span> <span style="color: rgb(202, 125, 55);">class</span> <span style="color: rgb(14, 156, 229);">RedissonConfig</span> {</span></code><code><span class="code-snippet_outer"> <span style="color: rgb(175, 175, 175);">@Value("<span style="color: rgb(202, 125, 55);">${spring.redis.host}</span>")</span></span></code><code><span class="code-snippet_outer"> <span style="color: rgb(202, 125, 55);">private</span> String host;</span></code><code><span class="code-snippet_outer"> <span style="color: rgb(175, 175, 175);">@Value("<span style="color: rgb(202, 125, 55);">${spring.redis.port}</span>")</span></span></code><code><span class="code-snippet_outer"> <span style="color: rgb(202, 125, 55);">private</span> String port;</span></code><code><span class="code-snippet_outer"> <span style="color: rgb(175, 175, 175);">@Value("<span style="color: rgb(202, 125, 55);">${spring.redis.password}</span>")</span></span></code><code><span class="code-snippet_outer"> <span style="color: rgb(202, 125, 55);">private</span> String password;</span></code><code><span class="code-snippet_outer"> <span style="color: rgb(175, 175, 175);">@Value("<span style="color: rgb(202, 125, 55);">${spring.redis.database}</span>")</span></span></code><code><span class="code-snippet_outer"> <span style="color: rgb(202, 125, 55);">private</span> int database;</span></code><code><span class="code-snippet_outer"> <span style="color: rgb(175, 175, 175);">@Bean</span></span></code><code><span class="code-snippet_outer"> <span style="color: rgb(202, 125, 55);">public</span> RedissonClient getRedisson() {</span></code><code><span class="code-snippet_outer"> Config config = new Config();</span></code><code><span class="code-snippet_outer"> <span style="color: rgb(175, 175, 175);font-style: italic;">//线程定时间隔时间10s</span></span></code><code><span class="code-snippet_outer"> config.setLockWatchdogTimeout(<span style="color: rgb(14, 156, 229);">10000</span>);</span></code><code><span class="code-snippet_outer"> <span style="color: rgb(175, 175, 175);font-style: italic;">//设置单机版本redis</span></span></code><code><span class="code-snippet_outer"> config.useSingleServer().setAddress(<span style="color: rgb(221, 17, 68);">"redis://"</span> + host + <span style="color: rgb(221, 17, 68);">":"</span> + port).setPassword(password).setDatabase(database);</span></code><code><span class="code-snippet_outer"> <span style="color: rgb(175, 175, 175);font-style: italic;">//设置集群的方式</span></span></code><code><span class="code-snippet_outer"> config.useClusterServers().addNodeAddress(<span style="color: rgb(221, 17, 68);">"redis://"</span> + host + <span style="color: rgb(221, 17, 68);">":"</span> + port);</span></code><code><span class="code-snippet_outer"> <span style="color: rgb(175, 175, 175);font-style: italic;">//添加主从配置</span></span></code><code><span class="code-snippet_outer"> config.useMasterSlaveServers().setMasterAddress(<span style="color: rgb(221, 17, 68);">""</span>).setPassword(<span style="color: rgb(221, 17, 68);">""</span>).addSlaveAddress(new String[]{<span style="color: rgb(221, 17, 68);">""</span>,<span style="color: rgb(221, 17, 68);">""</span>});</span></code><code><span class="code-snippet_outer"> <span style="color: rgb(202, 125, 55);">return</span> Redisson.create(config);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p style="margin: 24px 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;color: rgb(61, 170, 214);">底层结构:</span></p> <section data-tools="135编辑器" data-id="119830"> <section style="margin: 10px auto;"> <section> <section style="display: flex;"> <section style="flex-shrink:0;"> <section style="background-color: #7f7f7f;height: 100%;"> <section style="padding-top: 20px;padding-right: 2px;padding-left: 2px;"> <section style="width: 15px;"> <img class="rich_pages wxw-img" data-ratio="2.0987124463519313" data-type="gif" data-w="466" data-width="100%" style="vertical-align: inherit;width: 100%;display: block;height: auto !important;" src="/upload/edf29291b98f0aee1f8d6e3fd69b2f30.png"> </section> </section> </section> </section> <section style="background-color: rgb(242, 249, 255);width: 100%;" data-width="100%"> <section style="padding-top: 15px;padding-right: 15px;padding-left: 15px;"> <section data-autoskip="1" style="text-align: justify;line-height:1.75em;letter-spacing: 1.5px;font-size:14px;color:#333333;background: transparent;"> <p style="margin: 8px;line-height: 1.75em;"><span style="max-inline-size: 100%;cursor: text;text-align: left;caret-color: rgb(255, 0, 0);font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);outline: none 0px !important;">一个分布式锁对应一个hash</span><span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"><span style="max-inline-size: 100%;cursor: text;text-align: left;caret-color: rgb(255, 0, 0);outline: none 0px !important;">,</span><span style="max-inline-size: 100%;cursor: text;text-align: left;caret-color: red;font-family: 微软雅黑, "Microsoft YaHei", sans-serif;outline: none 0px !important;">hash 对应的 key
作者:微信小助手
<section class="mp_profile_iframe_wrp" data-mpa-powered-by="yiban.io"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-weui-theme="light" data-id="MzU2MTIyNDUwMA==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/EO58xpw5UMOQ7SLUFBoTAic22Pd63GqfXZibppZSGia2DsCllsnZrhZZqFN0ucxmztqP0icicOEiaQKAIAvnF71lqT4w/0?wx_fmt=png" data-nickname="前端充电宝" data-alias="FE-Charge" data-signature="掘金LV7作者,坚持原创。分享前端技术文章、学习资料、面试经验、热点资讯,开启前端进阶之旅!" data-from="0" data-is_biz_ban="0"></mp-common-profile> </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;"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在 Web 开发中,Canvas 是一个强大的绘图技术,可以实现各种有趣的交互效果和动态图形。本文将盘点 10 个基于 Canvas 的开源项目,旨在为大家提供开发灵感和思路,以便更好地探索并应用 Canvas 技术!</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">canvas-editor</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">canvas-editor 是一个基于 canvas/svg 的富文本编辑器,类似 word。其具有以下特点:</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);"> <strong style="color: black;">所见即所得</strong>:类word可分页,所见即所得 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">轻量的数据结构</strong>:一段JSON即可呈现复杂样式 </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>:官方发布核心npm包,菜单栏、工具栏可自行维护 </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;">完全类型化的API</strong>:灵活的 API 和完整的 TypeScript 类型 </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5472222222222223" src="/upload/c4dd48242d0cc5b625871d576231f88e.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>Github:</strong>https://github.com/Hufe921/canvas-editor</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">lucky-canvas</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">lucky-canvas 是一套基于 TS + Canvas 开发的【大转盘 / 九宫格 / 老虎机】抽奖插件,一套源码适配多端框架 JS / Vue / React / Taro / UniApp / 微信小程序等,奖品 / 文字 / 图片 / 颜色 / 按钮均可配置,支持同步 / 异步抽奖,概率前 / 后端可控,自动根据 dpr 调整清晰度适配移动端。</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.4787037037037037" src="/upload/619823282299c24cbd4192b156f6625c.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>Github:</strong>https://github.com/buuing/lucky-canvas</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">Excalidraw</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Excalidraw 是一个开源的在线白板工具,主要用于创建简单直观的图形和草图,支持共享和协作。以下是 Excalidraw 的主要特点:</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);"> 简单易用:具有直观简单的界面和操作方式,用户可以轻松创建和编辑图形,并实现各种设计需求。 </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);"> 高度灵活性:支持导出为SVG、PNG、Clipboard等多种格式,方便用户进行分享和保存。 </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6546296296296297" src="/upload/9d29f5e52a0deea50ca52788a2f4b78e.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>Github:</strong>https://github.com/excalidraw/excalidraw</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">fireworks-js</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">fireworks-js 是一个基于 Canvas 的动画库,用于在网页上制作烟花特效。该库的特点如下:</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);"> 轻量级:fireworks-js 体积小,不依赖其他库或框架,易于集成。 </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);"> 动画效果逼真:fireworks-js 采用粒子系统实现烟花特效,能够模拟出逼真的爆炸、飞溅和星光等效果。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 浏览器兼容性良好:可以在主流的现代浏览器上运行,支持多种设备和分辨率,包括移动端。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">该项目提供了多种框架的实现:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.7546391752577319" src="/upload/21a839e8f09cc12fac30bf8b73222569.png" data-type="png" data-w="970" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5416666666666666" src="/upload/4f727285cdeae272ee61f4d33321e6f7.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>Github:</strong>https://github.com/crashmax-dev/fireworks-js</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">Luckysheet</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Luckysheet 是一个纯前端基于 Canvas 的类似 excel 的在线表格,功能强大、配置简单、完全开源。其具有以下功能:</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);"> <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> <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>:数据透视表、图表、评论、协同编辑、插入图片、矩阵计算、截图、复制为其他格式、EXCEL导入导出等。 </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.458758109360519" src="/upload/2e97116fe1067ec8f09fa96bbf1c25cf.png" data-type="gif" data-w="1079" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>Github:</strong>https://github.com/dream-num/Luckysheet</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">rough</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Rough.js 是一个轻量级的(大约 8k),基于 Canvas 的可以绘制出粗略的手绘风格的图形库。该库提供绘制线条、曲线、弧线、多边形、圆形和椭圆的基础能力,同时支持绘制 SVG 路径。除此之外,Rough.js 还提供了大量的可自定义选项,可以调整线宽、线条颜色、填充颜色、字体样式、背景颜色等,以使图形更加个性化。</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.32205683355886333" src="/upload/596ff8dee7de89533fd93648e8ef46a4.png" data-type="png" data-w="739" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>Github:</strong>https://github.com/rough-stuff/rough</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">Signature Pad</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Signature Pad 是一个基于 Canvas 实现的签名库,用于绘制签名。它可以在所有现代桌面和移动浏览器中使用,不依赖于任何外部库。Signature Pad提供了许多可自定义的选项,如笔画颜色、粗细、背景色、画布大小、签名格式等,可以轻松实现不同的签名风格和功能。</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.6" src="/upload/6b39cf19bd60b94e2e30414a1054afcc.png" data-type="png" data-w="500" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>Github:</strong>https://github.com/szimek/signature_pad</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">canvas-confetti</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">canvas-confetti 是一个基于 Canvas 的库,用于在 Web 页面中实现炫酷的彩色纸屑动画效果。它实现了高性能、流畅的纸屑动画效果,同时兼容各种现代浏览器。提供了许多可自定义的选项,如纸屑颜色、形状、数量、速度、角度、发射器位置等,可以轻松实现不同的纸屑效果。并支持多种触发方式,如点击按钮、滚动页面、定时触发等,可根据需求进行定制。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5" src="/upload/ac03fcde4d0e1501172b8f4c858c72cd.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>Github:</strong>https://github.com/catdad/canvas-confetti</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">x-spreadsheet</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">x-spreadsheet 是一个基于 Web(es6) canvas 构建的轻量级 Excel 开发库。其具有以下特点:</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);"> 轻量级:完整功能,包含所有插件。代码打包后只不到 200kb </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> <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.7564814814814815" src="/upload/36d608795af201ec5415c3167c4919c7.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;"> sheet-demo.png </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>Github:</strong>https://github.com/myliang/x-spreadsheet</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">QRCanvas</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">QRCanvas 是一个基于 canvas 的 JavaScript 二维码生成工具。其具有以下特点:</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);"> 仅依赖 canvas,兼容性好 </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);"> 支持 Node.js </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 方便在 React 和 Vue 中使用 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 支持所有主流的浏览器 </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.7379629629629629" src="/upload/db6c27c176ba830de19743479dbb6f15.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>Github:</strong>https://github.com/gera2ld/qrcanvas</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>往期推荐<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzU2MTIyNDUwMA==&mid=2247514447&idx=1&sn=05f03d252c70fc06236f0be66414cee1&chksm=fc7ef714cb097e024db505c347e7c665f04290ef700e6b14d7fab4b1f53c70b7c85eb6c88ccb&scene=21#wechat_redirect" textvalue="React 发布十周年!" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">React 发布十周年!</a></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzU2MTIyNDUwMA==&mid=2247514403&idx=1&sn=d691fc8e171c34cb35d6ac73fb0f9cf6&chksm=fc7ef778cb097e6efa555cdb6149821043940b8aadcb5dd20f7708f70c1c328cfb0ddf432d12&scene=21#wechat_redirect" textvalue="React API 和代码重用的演变!" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">React API 和代码重用的演变!</a></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzU2MTIyNDUwMA==&mid=2247514363&idx=1&sn=074d292cfff83cf8e4ec3a09d277ec35&chksm=fc7ef6a0cb097fb6d1a6a35731ff5155269b56b95327b7efba1c3ab9b94a565c1a124845b1fe&scene=21#wechat_redirect" textvalue="好看又好用的开源博客系统!" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">好看又好用的开源博客系统!</a></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzU2MTIyNDUwMA==&mid=2247514282&idx=1&sn=1d2d1b40f22655e3e33b8ed297abf431&chksm=fc7ef6f1cb097fe7ae5d0f4cc402f93ea423eb817546177e9cc6f9b51505975290a81b0e1e45&scene=21#wechat_redirect" textvalue="Nuxt 3.5 正式发布,支持 Vue 3.3!" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">Nuxt 3.5 正式发布,支持 Vue 3.3!</a></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzU2MTIyNDUwMA==&mid=2247514270&idx=1&sn=45376f220e8774a0d01ce44f4b06a643&chksm=fc7ef6c5cb097fd3e234ebc0e191635179541dc9dec7b17ae6fa9a44ad3a259391f88751ec92&scene=21#wechat_redirect" textvalue="WebGPU:在浏览器中解锁现代 GPU 访问" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">WebGPU:在浏览器中解锁现代 GPU 访问</a></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzU2MTIyNDUwMA==&mid=2247514231&idx=1&sn=d5cb9aed9ef80969e40ef1b6637b73bd&chksm=fc7ef62ccb097f3a1c5709a1304636b3d300465487dad581541286f848d8e945f64699f88d51&scene=21#wechat_redirect" textvalue="Google I/O 2023:最新 CSS 特性解读!" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">Google I/O 2023:最新 CSS 特性解读!</a></p> </section> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>
作者:じ☆ve宝贝
<p style="text-wrap: wrap;color: rgb(53, 53, 53);font-family: "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei", 黑体, Arial, sans-serif;letter-spacing: normal;text-align: start;caret-color: rgba(0, 0, 0, 0);outline: 0px;background: rgb(255, 255, 255);"><strong><span style="font-size: 20px;">一、简介</span></strong></p> <p style="text-wrap: wrap;color: rgb(53, 53, 53);font-family: "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei", 黑体, Arial, sans-serif;letter-spacing: normal;text-align: start;caret-color: rgba(0, 0, 0, 0);outline: 0px;background: rgb(255, 255, 255);"><span style="font-size: 17px;">基于Redis的BitMap相关命令,实现用户签到、连续签到统计等功能。</span></p> <p style="text-wrap: wrap;color: rgb(53, 53, 53);font-family: "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei", 黑体, Arial, sans-serif;letter-spacing: normal;text-align: start;caret-color: rgba(0, 0, 0, 0);outline: 0px;background: rgb(255, 255, 255);"><span style="font-size: 18px;"><strong>1.1、背景</strong></span></p> <p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.4824074074074074" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/3b5fd5c099c4befe04921f3480ffb54.png"><strong><span style="color: rgb(53, 53, 53);font-family: "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei", 黑体, Arial, sans-serif;letter-spacing: normal;text-align: start;caret-color: rgba(0, 0, 0, 0);font-size: var(--articleFontsize);">分析:</span></strong><span style="font-size: 17px;"><span style="font-size: 17px;color: rgb(53, 53, 53);font-family: "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei", 黑体, Arial, sans-serif;letter-spacing: normal;text-align: start;caret-color: rgba(0, 0, 0, 0);">使用<span style="color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;text-align: start;text-wrap: wrap;background-color: rgb(255, 255, 255);">用一张表来存储用户签到信息,</span></span><span style="background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;">假如用户数量庞大,平均每人每年签到次数为 10 次,则这张表一年的数据量为 1 亿条,每签到一次需要使用(8 + 8 + 1 + 1 + 3 + 1)共 22 字节的内存,一个月则最多需要 600 多字节。</span></span></p> <p style="text-wrap: wrap;color: rgb(53, 53, 53);font-family: "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei", 黑体, Arial, sans-serif;letter-spacing: normal;text-align: start;caret-color: rgba(0, 0, 0, 0);outline: 0px;background: rgb(255, 255, 255);"><span style="font-size: 18px;"><strong>1.2、BitMap</strong></span><br></p> <p> bitmap 不是一个独立的数据类型,而是一种特殊的 string 类型,它可以将一个 string 类型的值看作是一个由二进制位组成的数组,并提供了一系列操作二进制位的命令。一个 bitmap 类型的键最多可以存储 2^32 - 1 个二进制位。</p> <p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"> bitmap 类型的底层实现是 SDS(simple dynamic string),它和 string 类型相同,只是在操作时会将每个字节拆分成 8 个二进制位。</span></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.45" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/427ebd6259dce94ed07f1bc9485c66e6.png"></p> <p style="text-align: left;"><strong>常见用法:</strong></p> <p style="text-align: left;"> <span style="font-size: 17px;background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;"> Redis中的Bitmap是一种数据结构,用于存储和操作位数组(bit array)。它可以有效地表示指定范围内的位状态,每个位的值可以是0或1。使用Bitmap可以进行高效的位级别操作,例如对某个位进行设置、获取、翻转等操作,以及位的逻辑运算,如AND、OR、XOR等。</span></p> <p style="text-align: left;"><strong><span style="background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;font-size: var(--articleFontsize);">在Redis中,Bitmap的应用场景:</span></strong></p> <p style="text-align: left;"><strong><span style="font-size: 17px;background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;">1、统计在线用户(签到)</span></strong><span style="font-size: 17px;background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;">:可以使用一个Bitmap,每个位代表一个用户ID,如果某个用户在线,则将对应位设置为1,否则设置为0。可以通过位操作来统计在线用户的数量。</span></p> <p style="text-align: left;"><span style="font-size: 17px;background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;">2、</span><strong><span style="font-size: 17px;background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;">频率统计</span></strong><span style="font-size: 17px;background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;">:可以使用一个Bitmap,每个位代表一个事件,如果事件发生,则将对应位设置为1。可以通过位操作来统计某段时间内事件发生的频率。</span></p> <p style="text-align: left;"><strong><span style="font-size: 17px;background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;">3、实现布隆过滤器</span></strong><span style="font-size: 17px;background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;"><span style="color: rgba(0, 0, 0, 0.75);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;text-align: left;text-wrap: wrap;background-color: rgb(255, 255, 255);">,利用 setbit 和 getbit 命令实现快速判断一个元素是否存在于一个集合中。</span></span></p> <p style="text-align: left;"><strong><span style="font-size: 17px;"><span style="background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;">4、实现位图索引</span></span></strong><span style="font-size: 17px;"><span style="background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;"></span><span style="letter-spacing: 0.034em;text-align: justify;">,利用 bitop 和 bitpos 命令实现对多个条件进行位运算和定位。</span></span></p> <p style="text-align: left;"><strong><span style="font-size: 17px;"><span style="background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;">5、统计用户活跃度</span></span></strong><span style="font-size: 17px;"><span style="background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;"></span><span style="color: rgba(0, 0, 0, 0.75);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;text-align: left;text-wrap: wrap;background-color: rgb(255, 255, 255);">,利用 setbit 和 bitcount 命令实现每天或每月用户登录次数的统计</span></span></p> <p style="text-align: left;"><strong><span style="font-size: 17px;background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;">常用的命令:</span></strong><span style="font-size: 17px;background-color: rgb(255, 255, 255);color: rgb(77, 77, 77);font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;font-variant-ligatures: no-common-ligatures;letter-spacing: normal;"><br></span></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.44722222222222224" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/1a570c6470a3a2a6cb3f75eef2c6c8d9.png"></p> <p style="text-align: left;"> <strong>需要注意的是</strong>,由于Bitmap是以字节为单位存储的,因此对于较大的位图,可能会占用较多的内存。在使用Bitmap时,需要根据实际情况评估内存消耗。</p> <p style="text-wrap: wrap;color: rgb(53, 53, 53);font-family: "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei", 黑体, Arial, sans-serif;letter-spacing: normal;text-align: start;caret-color: rgba(0, 0, 0, 0);outline: 0px;background: rgb(255, 255, 255);"><span style="font-size: 18px;"><strong>1.3、BITFIELD使用说明</strong></span><br></p> <p style="text-align: left;"><strong> </strong> Redis中的BITFIELD命令是用于对位域(bit field)进行操作的,位域是由多个位组成的数据结构。它允许你对位域进行读取、设置和计算等操作。下面是BITFIELD命令的用法示例:</p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer">bitfield bitfield_test get u4 0 #从第一个位开始取4个位(0110),结果为无符号数(u)</span></code><code><span class="code-snippet_outer">结果:6</span></code><code><span class="code-snippet_outer">bitfield bitfield_testget u3 2 #从第三个位开始取3个位(101),结果为无符号数(u)</span></code><code><span class="code-snippet_outer">结果:5</span></code><code><span class="code-snippet_outer">bitfield bitfield_testget get i4 0 #从第一个位开始取4个位(0110),结果为有符号数(i)</span></code><code><span class="code-snippet_outer">结果:6</span></code><code><span class="code-snippet_outer">因为结果为有符号数所以,第一位符号位为0代表是正数。110为6,结果直接为6</span></code><code><span class="code-snippet_outer">bitfield bitfield_testget get i3 2 #从第三个位开始取3个位(101),结果为有符号数(i)</span></code><code><span class="code-snippet_outer">结果:-3</span></code><code><span class="code-snippet_outer">取到的结果首位为1代表是负数,01需要取补码运算。01取反为10,10+1为11。11十进制为3,因为符号位为1所以最终结果为-3</span></code></pre> </section> <p style="text-align: left;"><strong style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">命令操作案例:</strong><br></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="css"><code><span class="code-snippet_outer">redis6.3:0>setbit qd_key 0 1</span></code><code><span class="code-snippet_outer">"0"</span></code><code><span class="code-snippet_outer">redis6.3:0>setbit qd_key 1 1</span></code><code><span class="code-snippet_outer">"0"</span></code><code><span class="code-snippet_outer">redis6.3:0>setbit qd_key 2 1</span></code><code><span class="code-snippet_outer">"0"</span></code><code><span class="code-snippet_outer">redis6.3:0>getbit qd_key 10</span></code><code><span class="code-snippet_outer">"1"</span></code><code><span class="code-snippet_outer">redis6.3:0>bitcount qd_key</span></code><code><span class="code-snippet_outer">"6"</span></code><code><span class="code-snippet_outer">redis6.3:0>bitfield qd_key get u2 0</span></code><code><span class="code-snippet_outer">1) "3"</span></code></pre> </section> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.5925925925925926" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/ba786619e8c39c6e16ea14c3784890e2.png"></p> <p style="text-align: left;"><strong><span style="font-size: 20px;">二、签到功能实现</span></strong></p> <p style="text-align: left;"><span style="font-size: 18px;"><strong>2.1、需求分析</strong></span></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.4962962962962963" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/7b1b1a7cad60133c76349ec6d4e51719.png"></p> <p style="text-align: left;"><span style="font-size: 18px;"><strong>2.2、代码实现</strong></span><br></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="typescript"><code><span class="code-snippet_outer"> public Result sign() {</span></code><code><span class="code-snippet_outer"> // 1.获取当前登录用户</span></code><code><span class="code-snippet_outer">// Long userId = UserHolder.getUser().getId();</span></code><code><span class="code-snippet_outer"> Long userId =999L;</span></code><code><span class="code-snippet_outer"> // 2.获取日期 使用hutool的日期时间工具-DateUtil</span></code><code><span class="code-snippet_outer"> Date date = DateUtil.date();</span></code><code><span class="code-snippet_outer"> // 3.拼接key</span></code><code><span class="code-snippet_outer"> String keySuffix = DateUtil.format(date, ":yyyyMM");</span></code><code><span class="code-snippet_outer"> String key = USER_SIGN_KEY + userId + keySuffix;</span></code><code><span class="code-snippet_outer"> // 4.获取今天是本月的第几天</span></code><code><span class="code-snippet_outer"> int dayOfMonth = DateUtil.dayOfMonth(date);</span></code><code><span class="code-snippet_outer"> // 5.写入Redis SETBIT key offset 1</span></code><code><span class="code-snippet_outer"> stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);</span></code><code><span class="code-snippet_outer"> return Result.ok();</span></code><code><span class="code-snippet_outer"> }</span></code></pre> </section> <p style="text-align: left;"><span style="font-size: 18px;"><strong>结果展示:</strong></span><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.2777777777777778" data-s="300,640" data-type="png" data-w="1080" style="letter-spacing: 0.578px;text-align: center;height: auto !important;" src="/upload/6ccc1ebf415789b11de818cc045152f6.png"></p> <p style="text-align: left;"><strong><span style="font-size: 20px;">三、连续签到统计功能实现</span></strong></p> <p style="text-align: left;"><strong><span style="font-size: 20px;"><strong style="font-size: 18px;letter-spacing: 0.578px;text-align: left;text-wrap: wrap;">3.1、需求分析</strong></span></strong></p> <p><strong>问题1:什么叫做连续签到天数?</strong></p> <p> 从最后一次(当前时间)签到开始向前统计,直到遇到第一次未签到为止,计算总的签到次数,就是连续签到天数。</p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.13689700130378096" data-s="300,640" data-type="png" data-w="767" style="height: auto !important;" src="/upload/aa98f29b421aaa5dfed750cf6ffe7a38.png"></p> <p><strong>问题2:如何得到本月到今天为止的所有签到数据?</strong></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="css"><code><span class="code-snippet_outer"> BITFIELD key GET u[dayOfMonth]0</span></code></pre> </section> <p><strong>问题3:如何从后向前遍历每个bit位?</strong></p> <p> 与1做与运算,就能得到最后一个bt位。随后右移一位,下一个Bit位就成了最后一个Bit位。</p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.45185185185185184" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/5e3aad0e7bd8fe41f7f8ecf3d31d74ae.png"></p> <p style="text-align: left;"><strong><span style="font-size: 20px;"><strong style="font-size: 18px;letter-spacing: 0.578px;text-align: left;text-wrap: wrap;">3.2、代码实现</strong></span></strong><br></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="kotlin"><code><span class="code-snippet_outer">public Result signCount() {</span></code><code><span class="code-snippet_outer"> // Long userId = UserHolder.getUser().getId();</span></code><code><span class="code-snippet_outer"> Long userId =999L; //暂时写死</span></code><code><span class="code-snippet_outer"> // 2.获取日期 使用hutool的日期时间工具-DateUtil</span></code><code><span class="code-snippet_outer"> Date date = DateUtil.date();</span></code><code><span class="code-snippet_outer"> // 3.拼接key</span></code><code><span class="code-snippet_outer"> String keySuffix = DateUtil.format(date, ":yyyyMM");</span></code><code><span class="code-snippet_outer"> String key = USER_SIGN_KEY + userId + keySuffix;</span></code><code><span class="code-snippet_outer"> // 4.获取今天是本月的第几天</span></code><code><span class="code-snippet_outer"> int dayOfMonth = DateUtil.dayOfMonth(date);</span></code><code><span class="code-snippet_outer"> // 5.获取本月截止今天为止的所有的签到记录,返回的是一个十进制的数字 BITFIELD sign:999:202308 GET u18 0</span></code><code><span class="code-snippet_outer"> List<Long> result = stringRedisTemplate.opsForValue().bitField(</span></code><code><span class="code-snippet_outer"> key,</span></code><code><span class="code-snippet_outer"> BitFieldSubCommands.create()</span></code><code><span class="code-snippet_outer"> .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)</span></code><code><span class="code-snippet_outer"> );</span></code><code><span class="code-snippet_outer"> if (result == null || result.isEmpty()) {</span></code><code><span class="code-snippet_outer"> // 没有任何签到结果</span></code><code><span class="code-snippet_outer"> return Result.ok(0);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> //num为0,直接返回0</span></code><code><span class="code-snippet_outer"> Long num = result.get(0);</span></code><code><span class="code-snippet_outer"> if (num == null || num == 0) {</span></code><code><span class="code-snippet_outer"> return Result.ok(0);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> // 6.循环遍历</span></code><code><span class="code-snippet_outer"> int count = 0;</span></code><code><span class="code-snippet_outer"> while (true) {</span></code><code><span class="code-snippet_outer"> // 6.1.让这个数字与1做与运算,得到数字的最后一个bit位 // 判断这个bit位是否为0</span></code><code><span class="code-snippet_outer"> if ((num & 1) == 0) {</span></code><code><span class="code-snippet_outer"> // 如果为0,说明未签到,结束</span></code><code><span class="code-snippet_outer"> break;</span></code><code><span class="code-snippet_outer"> }else {</span></code><code><span class="code-snippet_outer"> // 如果不为0,说明已签到,计数器+1</span></code><code><span class="code-snippet_outer"> count++;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> // 把数字右移一位,抛弃最后一个bit位,继续下一个bit位</span></code><code><span class="code-snippet_outer"> num >>>= 1;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> return Result.ok(count);</span></code><code><span class="code-snippet_outer"> }</span></code></pre> </section> <p style="text-align: center;"><br></p> <p style="text-align: left;"><strong><span style="font-size: 20px;"><strong style="font-size: 18px;letter-spacing: 0.578px;text-align: left;text-wrap: wrap;">3.3、结果展示:</strong></span></strong><br></p> <p style="text-align: left;"><span style="font-size: 17px;"><strong><strong style="font-size: 18px;letter-spacing: 0.578px;text-align: left;text-wrap: wrap;">当天还没有签到统计:</strong></strong></span></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.5407407407407407" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/52939d9ed4bf680074c1456a95594aa9.png"></p> <p style="text-align: left;"><span style="font-size: 17px;"><strong>当天已经签到统计:</strong></span></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.6212962962962963" data-s="300,640" data-type="png" data-w="1080" style="height: auto !important;" src="/upload/234e13065a1e6386c6629fc306dc7a6f.png"></p>
作者:じ☆ve宝贝
<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;" data-mpa-powered-by="yiban.io"> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="custom_select_card mp_profile_iframe mp_common_widget" data-pluginname="mpprofile" data-id="MzAwMTk4NjM1MA==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/PxMzT0Oibf4gcBzLSUNh2cgXUsuLIsvQYJE1lzZd74qpC3iciaM6gcYIfOVV0KjDDkeN4CTLTn4ETPtaHOAuTWSWA/0?wx_fmt=png" data-nickname="JAVA日知录" data-alias="javadaily" data-signature="写代码的架构师,做架构的程序员! 实战、源码、数据库、架构...只要你来,你想了解的这里都有!" data-from="0"></mp-common-profile> </section> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><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;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationContext</code> 中的事件处理是通过 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEvent</code> 类和 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationListener</code> 接口提供的。如果将实现了 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationListener</code> 接口的 bean 部署到容器中,则每次将 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEvent</code> 发布到<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationContext</code> 时,都会通知到该 bean,这简直是典型的观察者模式。设计的初衷就是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Spring 中提供了以下的事件</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.37590861889927313" data-type="png" data-w="963" style="display: block;margin-right: auto;margin-left: auto;box-shadow: rgb(30, 30, 30) 0em 0em 0.5em 0px;font-size: 17px;height: auto !important;" src="/upload/22325b2f05e58172fc8c329cb6984a39.png"> </figure> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><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);">ApplicationEvent 与 ApplicationListener 应用</span></h2> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><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;">1、自定义事件类,基于 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEvent</code> 实现扩展</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">class</span> <span style="font-weight: bold;color: white;line-height: 26px;">DemoEvent</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">extends</span> <span style="font-weight: bold;color: white;line-height: 26px;">ApplicationEvent</span> </span>{<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">private</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">static</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">final</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">long</span> serialVersionUID = -<span style="line-height: 26px;">2753705718295396328L</span>;<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">private</span> String msg;<br><br> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">DemoEvent</span><span style="line-height: 26px;">(Object source, String msg)</span> </span>{<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">super</span>(source);<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">this</span>.msg = msg;<br> }<br><br> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> String <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">getMsg</span><span style="line-height: 26px;">()</span> </span>{<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">return</span> msg;<br> }<br><br> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">void</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">setMsg</span><span style="line-height: 26px;">(String msg)</span> </span>{<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">this</span>.msg = msg;<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">2、定义 Listener 类,实现 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationListener</code>接口,并且注入到 IOC 中。等发布者发布事件时,都会通知到这个bean,从而达到监听的效果。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: none 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;" data-remoteid="c1696945823207" data-cacheurl="https://files.mdnice.com/user/3441/876cad08-0422-409d-bb5a-08afec5da8ee.svg"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="color: #75715e;line-height: 26px;">@Component</span><br><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">class</span> <span style="font-weight: bold;color: white;line-height: 26px;">DemoListener</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">implements</span> <span style="font-weight: bold;color: white;line-height: 26px;">ApplicationListener</span><<span style="font-weight: bold;color: white;line-height: 26px;">DemoEvent</span>> </span>{<br> <span style="color: #75715e;line-height: 26px;">@Override</span><br> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">void</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">onApplicationEvent</span><span style="line-height: 26px;">(DemoEvent demoEvent)</span> </span>{<br> String msg = demoEvent.getMsg();<br> System.out.println(<span style="color: #a6e22e;line-height: 26px;">"bean-listener 收到了 publisher 发布的消息: "</span> + msg);<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">3、要发布上述自定义的 event,需要调用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEventPublisher</code> 的 publishEvent 方法,我们可以定义一个实现 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEventPublisherAware</code> 的类,并注入 IOC来进行调用。</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: none 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;" data-remoteid="c1696945823208" data-cacheurl="https://files.mdnice.com/user/3441/876cad08-0422-409d-bb5a-08afec5da8ee.svg"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="color: #75715e;line-height: 26px;">@Component</span><br><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">class</span> <span style="font-weight: bold;color: white;line-height: 26px;">DemoPublisher</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">implements</span> <span style="font-weight: bold;color: white;line-height: 26px;">ApplicationEventPublisherAware</span> </span>{<br><br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">private</span> ApplicationEventPublisher applicationEventPublisher;<br><br> <span style="color: #75715e;line-height: 26px;">@Override</span><br> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">void</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">setApplicationEventPublisher</span><span style="line-height: 26px;">(ApplicationEventPublisher applicationEventPublisher)</span> </span>{<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">this</span>.applicationEventPublisher = applicationEventPublisher;<br> }<br><br> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">void</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">sendMsg</span><span style="line-height: 26px;">(String msg)</span> </span>{<br> applicationEventPublisher.publishEvent(<span style="color: #f92672;font-weight: bold;line-height: 26px;">new</span> DemoEvent(<span style="color: #f92672;font-weight: bold;line-height: 26px;">this</span>, msg));<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">4、客户端调用 publisher</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: none 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;" data-remoteid="c1696945823209" data-cacheurl="https://files.mdnice.com/user/3441/876cad08-0422-409d-bb5a-08afec5da8ee.svg"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="color: #75715e;line-height: 26px;">@RestController</span><br><span style="color: #75715e;line-height: 26px;">@RequestMapping</span>(<span style="color: #a6e22e;line-height: 26px;">"/event"</span>)<br><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">class</span> <span style="font-weight: bold;color: white;line-height: 26px;">DemoClient</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">implements</span> <span style="font-weight: bold;color: white;line-height: 26px;">ApplicationContextAware</span> </span>{<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">private</span> ApplicationContext applicationContext;<br><br> <span style="color: #75715e;line-height: 26px;">@Override</span><br> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">void</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">setApplicationContext</span><span style="line-height: 26px;">(ApplicationContext applicationContext)</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">throws</span> BeansException </span>{<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">this</span>.applicationContext = applicationContext;<br> }<br><br> <span style="color: #75715e;line-height: 26px;">@GetMapping</span>(<span style="color: #a6e22e;line-height: 26px;">"/publish"</span>)<br> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">void</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">publish</span><span style="line-height: 26px;">()</span></span>{<br> DemoPublisher bean = applicationContext.getBean(DemoPublisher<span style="line-height: 26px;">.<span style="color: #f92672;font-weight: bold;line-height: 26px;">class</span>)</span>;<br> bean.sendMsg(<span style="color: #a6e22e;line-height: 26px;">"发布者发送消息......"</span>);<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">输出结果:</p> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-width: 1px;border-style: dashed;border-color: rgb(37, 132, 181);background-image: initial;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">bean-listener 收到了 publisher 发布的消息: 发布者发送消息......</p> </blockquote> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><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;">我们可以不用实现 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">AppplicationListener</code> 接口 ,在方法上使用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">@EventListener</code> 注册事件。如果你的方法应该侦听多个事件,并不使用任何参数来定义,可以在 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">@EventListener</code> 注解上指定多个事件。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">重写 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">DemoListener</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: none 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;" data-remoteid="c1696945823210" data-cacheurl="https://files.mdnice.com/user/3441/876cad08-0422-409d-bb5a-08afec5da8ee.svg"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">class</span> <span style="font-weight: bold;color: white;line-height: 26px;">DemoListener</span> </span>{<br> <span style="color: #75715e;line-height: 26px;">@EventListener</span>(value = {DemoEvent<span style="line-height: 26px;">.<span style="color: #f92672;font-weight: bold;line-height: 26px;">class</span>, <span style="font-weight: bold;color: white;line-height: 26px;">TestEvent</span>.<span style="font-weight: bold;color: white;line-height: 26px;">class</span>})<br> <span style="font-weight: bold;color: white;line-height: 26px;">public</span> <span style="font-weight: bold;color: white;line-height: 26px;">void</span> <span style="font-weight: bold;color: white;line-height: 26px;">processApplicationEvent</span>(<span style="font-weight: bold;color: white;line-height: 26px;">DemoEvent</span> <span style="font-weight: bold;color: white;line-height: 26px;">event</span>) </span>{<br> String msg = event.getMsg();<br> System.out.println(<span style="color: #a6e22e;line-height: 26px;">"bean-listener 收到了 publisher 发布的消息: "</span> + msg);<br> }<br>}<br></code></pre> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><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;">如果希望通过一定的条件对事件进行过滤,可以使用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">@EventListener</code> 的 condition 属性。以下实例中只有 event 的 msg 属性是 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">my-event</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: none 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;" data-remoteid="c1696945823211" data-cacheurl="https://files.mdnice.com/user/3441/876cad08-0422-409d-bb5a-08afec5da8ee.svg"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="color: #75715e;line-height: 26px;">@EventListener</span>(value = {DemoEvent<span style="line-height: 26px;">.<span style="color: #f92672;font-weight: bold;line-height: 26px;">class</span>, <span style="font-weight: bold;color: white;line-height: 26px;">TestEvent</span>.<span style="font-weight: bold;color: white;line-height: 26px;">class</span>}, <span style="font-weight: bold;color: white;line-height: 26px;">condition</span> </span>= <span style="color: #a6e22e;line-height: 26px;">"#event.msg == 'my-event'"</span>)<br><span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">void</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">processApplicationEvent</span><span style="line-height: 26px;">(DemoEvent event)</span> </span>{<br> String msg = event.getMsg();<br> System.out.println(<span style="color: #a6e22e;line-height: 26px;">"bean-listener 收到了 publisher 发布的消息: "</span> + msg);<br> }<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">此时,发送符合条件的消息,listener 才会侦听到 publisher 发布的消息。</p> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-width: 1px;border-style: dashed;border-color: rgb(37, 132, 181);background-image: initial;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">bean-listener 收到了 publisher 发布的消息: my-event</p> </blockquote> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><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;">前面提到的都是同步处理事件,那如果我们希望某个特定的侦听器异步去处理事件,如何做?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">使用 @Async 注解可以实现类内方法的异步调用,这样方法在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。</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: none 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;" data-remoteid="c1696945823212" data-cacheurl="https://files.mdnice.com/user/3441/876cad08-0422-409d-bb5a-08afec5da8ee.svg"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="color: #75715e;line-height: 26px;">@EventListener</span><br><span style="color: #75715e;line-height: 26px;">@Async</span><br><span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">void</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">processApplicationEvent</span><span style="line-height: 26px;">(DemoEvent event)</span> </span>{<br> String msg = event.getMsg();<br> System.out.println(<span style="color: #a6e22e;line-height: 26px;">"bean-listener 收到了 publisher 发布的消息: "</span> + msg);<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">使用异步监听时,有两点需要注意:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-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;">如果异步事件抛出异常,则不会将其传播到调用方。</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;">异步事件监听方法无法通过返回值来发布后续事件,如果需要作为处理结果发布另一个事件,请插入<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"> ApplicationEventPublisher</code> 以手动发布事件</p> </section></li> </ul> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><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;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationContext</code> 在运行期会自动检测到所有实现了 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationListener</code> 的 bean,并将其作为事件接收对象。当我们与 spring 上下文交互触发 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">publishEvent</code> 方法时,每个实现了 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationListener</code> 的 bean 都会收到 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEvent</code> 对象,每个 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationListener</code> 可以根据需要只接收自己感兴趣的事件。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong style="color: rgb(37, 132, 181);">这样做有什么好处呢?</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在传统的项目中,各个业务逻辑之间耦合性比较强,controller 和 service 间都是关联关系,然而,使用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEvent</code> 监听 publisher 这种方式,类间关系是什么样的?我们不如画张图来看看。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">DemoPublisher</code> 和 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">DemoListener</code> 两个类间并没有直接关联,解除了传统业务逻辑两个类间的关联关系,将耦合降到最小。这样在后期更新、维护时难度大大降低了。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.6281156530408774" data-type="png" data-w="1003" style="display: block;margin-right: auto;margin-left: auto;font-size: 17px;box-shadow: rgb(30, 30, 30) 0em 0em 0.5em 0px;height: auto !important;" src="/upload/938fd15effb37f1ea9e53e0a3393e3b8.png"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEvent</code> 使用观察者模式实现,那什么时候适合使用观察者模式呢?观察者模式也叫 发布-订阅模式,例如,微博的订阅,我们订阅了某些微博账号,当这些账号发布消息时,我们都会收到通知。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">总结来说,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,从而实现广播的效果。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><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> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.6083333333333333" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;font-size: 17px;box-shadow: rgb(30, 30, 30) 0em 0em 0.5em 0px;height: auto !important;" src="/upload/28360319c1c05bc0f218214447d22e40.png"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong style="color: rgb(37, 132, 181);">Spring中的事件机制流程</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">1、<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEventPublisher</code>是Spring的事件发布接口,事件源通过该接口的pulishEvent方法发布事件</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">2、<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEventMulticaster</code>就是Spring事件机制中的事件广播器,它默认提供一个<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">SimpleApplicationEventMulticaster</code>实现,如果用户没有自定义广播器,则使用默认的。它通过父类<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">AbstractApplicationEventMulticaster</code>的<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">getApplicationListeners</code>方法从事件注册表(事件-监听器关系保存)中获取事件监听器,并且通过<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">invokeListener</code>方法执行监听器的具体逻辑</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">3、<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationListener</code>就是Spring的事件监听器接口,所有的监听器都实现该接口,本图中列出了典型的几个子类。其中<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">RestartApplicationListnener</code>在SpringBoot的启动框架中就有使用</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">4、在Spring中通常是<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationContext</code>本身担任监听器注册表的角色,在其子类<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">AbstractApplicationContext</code>中就聚合了事件广播器<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEventMulticaster</code>和事件监听器<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationListnener</code>,并且提供注册监听器的<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">addApplicationListnener</code>方法</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">通过上图就能较清晰的知道当一个事件源产生事件时,它通过事件发布器<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEventPublisher</code>发布事件,然后事件广播器<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEventMulticaster</code>会去事件注册表<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationContext</code>中找到事件监听器<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationListnener</code>,并且逐个执行监听器的<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">onApplicationEvent</code>方法,从而完成事件监听器的逻辑。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">来到<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEventPublisher</code> 的 publishEvent 方法内部</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: none 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;" data-remoteid="c1696945823213" data-cacheurl="https://files.mdnice.com/user/3441/876cad08-0422-409d-bb5a-08afec5da8ee.svg"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">protected</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">void</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">publishEvent</span><span style="line-height: 26px;">(Object event, @Nullable ResolvableType eventType)</span> </span>{<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">if</span> (<span style="color: #f92672;font-weight: bold;line-height: 26px;">this</span>.earlyApplicationEvents != <span style="color: #f92672;font-weight: bold;line-height: 26px;">null</span>) {<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">this</span>.earlyApplicationEvents.add(applicationEvent);<br> }<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">else</span> {<br> <span style="color: #75715e;line-height: 26px;">// </span><br> getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">多播事件方法</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: none 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;" data-remoteid="c1696945823214" data-cacheurl="https://files.mdnice.com/user/3441/876cad08-0422-409d-bb5a-08afec5da8ee.svg"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="color: #75715e;line-height: 26px;">@Override</span><br><span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">public</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">void</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">multicastEvent</span><span style="line-height: 26px;">(<span style="color: #f92672;font-weight: bold;line-height: 26px;">final</span> ApplicationEvent event, @Nullable ResolvableType eventType)</span> </span>{<br> ResolvableType type = (eventType != <span style="color: #f92672;font-weight: bold;line-height: 26px;">null</span> ? eventType : resolveDefaultEventType(event));<br> Executor executor = getTaskExecutor();<br> <span style="color: #75715e;line-height: 26px;">// 遍历所有的监听者</span><br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">for</span> (ApplicationListener<?> listener : getApplicationListeners(event, type)) {<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">if</span> (executor != <span style="color: #f92672;font-weight: bold;line-height: 26px;">null</span>) {<br> <span style="color: #75715e;line-height: 26px;">// 异步调用监听器</span><br> executor.execute(() -> invokeListener(listener, event));<br> }<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">else</span> {<br> <span style="color: #75715e;line-height: 26px;">// 同步调用监听器</span><br> invokeListener(listener, event);<br> }<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">invokeListener</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: none 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;" data-remoteid="c1696945823215" data-cacheurl="https://files.mdnice.com/user/3441/876cad08-0422-409d-bb5a-08afec5da8ee.svg"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">protected</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">void</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">invokeListener</span><span style="line-height: 26px;">(ApplicationListener<?> listener, ApplicationEvent event)</span> </span>{<br> ErrorHandler errorHandler = getErrorHandler();<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">if</span> (errorHandler != <span style="color: #f92672;font-weight: bold;line-height: 26px;">null</span>) {<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">try</span> {<br> doInvokeListener(listener, event);<br> }<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">catch</span> (Throwable err) {<br> errorHandler.handleError(err);<br> }<br> }<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">else</span> {<br> doInvokeListener(listener, event);<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">doInvokeListener</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: none 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;" data-remoteid="c1696945823216" data-cacheurl="https://files.mdnice.com/user/3441/876cad08-0422-409d-bb5a-08afec5da8ee.svg"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">private</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">void</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">doInvokeListener</span><span style="line-height: 26px;">(ApplicationListener listener, ApplicationEvent event)</span> </span>{<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">try</span> {<br> <span style="color: #75715e;line-height: 26px;">// 这里是事件发生的地方</span><br> listener.onApplicationEvent(event);<br> }<br> <span style="color: #f92672;font-weight: bold;line-height: 26px;">catch</span> (ClassCastException ex) {<br> ......<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">点击 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationListener</code> 接口 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">onApplicationEvent</code> 方法的实现,可以看到我们重写的方法。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5328083989501312" data-type="png" data-w="762" style="display: block;margin-right: auto;margin-left: auto;font-size: 17px;box-shadow: rgb(30, 30, 30) 0em 0em 0.5em 0px;height: auto !important;" src="/upload/b1ca0e3d605fa075b9b4826e7af1ee43.png"> </figure> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><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> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Spring 使用反射机制,获取了所有继承 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationListener</code> 接口的监听器,在 Spring 初始化时,会把监听器都自动注册到注册表中。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Spring 的事件发布非常简单,我们来总结一下:</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;">定义一个继承 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEvent</code> 的事件</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;">定义一个实现 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationListener</code> 的监听器或者使用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">@EventListener</code> 监听事件</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;">定义一个发送者,调用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationContext</code> 直接发布或者使用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">ApplicationEventPublisher</code> 来发布自定义事件</p> </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">最后,发布-订阅模式可以很好的将业务逻辑进行解耦(上图验证过),大大提高了可维护性、可扩展性。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><br></p>
作者:じ☆ve不哭
> 在项目中使用SSE向前台推送数据,发现本地没问题,但是服务器连接SSE接口出现超时不能请求问题。排查后发现是Nginx的问题。 ### 配置 ``` # 反向代理配置 server { listen 80; server_name xx.xx.xx.xx; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header HTTP_AUTHORIZATION $http_authorization; # SSE 连接时的超时时间 proxy_read_timeout 1800s; # 取消缓冲 proxy_buffering off; # 关闭代理缓存 proxy_cache off; # 禁用分块传输编码 #chunked_transfer_encoding off location ^~ /api/ { proxy_pass http://localhost:82/; } location ^~ /dev/file/download/ { proxy_pass http://localhost:82; } location / { root /home/xxxxx/xxxx-admin-web; index index.html index.htm; try_files $uri $uri/ /index.html; } } ``` ### 参数说明 - proxy_set_header设置一些必要的头部信息,如连接方式、真实客户端IP等。 - proxy_read_timeout 指令来设置 SSE 连接的超时时间。默认情况下,Nginx 会在 60 秒后关闭空闲的连接,这对于 SSE 来说是不合适的,所以我们将超时时间设置为一天(86400 秒)。这样,客户端和服务器之间的连接可以持续保持打开状态。 - proxy_buffering off 指令来确保数据可以实时传输,而不需要等待缓冲区满。在SSE请求中禁用缓冲,以便正确处理SSE流式数据。 - proxy_cache 对于 SSE(Server-Sent Events)连接,通常不建议启用 Nginx 的代理缓存(proxy_cache)。因为 SSE 是一种长连接技术,它通过保持持久连接来实时推送数据给客户端,而代理缓存会将响应数据缓存起来并在后续请求中返回缓存的响应,这与 SSE 的工作方式相违背。如果启用代理缓存,Nginx 可能会缓存 SSE 的数据,并在后续的连接中返回相同的缓存数据,这样会导致客户端收到重复的消息,破坏了 SSE 的实时性和准确性。 - chunked_transfer_encoding 参数可以根据你的需求决定是否关闭。在 SSE 中,通常不需要禁用分块传输编码,因为它允许将数据以数据块的形式逐步传输,与 SSE 的流式数据特性相符合。 - proxy_pass 指令正确反向代理到你的 SSE 应用程序的地址和端口,以使连接正确工作。 ### 验证 ``` cd /usr/local/nginx # 验证nginx.conf配置是否正确 ./sbin/nginx -t # 重启nginx ./sbin/nginx -s reload ```
作者:微信小助手
<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"> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">大家好,我是飘渺。今天,我们将继续探讨DDD与微服务架构的系列主题。</p> <h2 data-tool="mdnice编辑器" style="line-height: 32px;color: rgb(53, 179, 120);display: inline-block;border-bottom: 0px solid rgb(53, 179, 120);border-top-color: rgb(53, 179, 120);border-right-color: rgb(53, 179, 120);border-left-color: rgb(53, 179, 120);font-size: 23px;margin-top: 1em;margin-bottom: 0rem;padding-top: 0.5em;padding-bottom: 0.5em;font-weight: bold;"><span style="display: none;"></span>概述</h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">在软件开发过程中,我们经常会遇到需要生成全局唯一流水号的场景,例如各种流水号和分库分表的分布式主键ID。特别是在使用MySQL数据库时,除了要求流水号具有“<strong><span style="color: rgb(0, 209, 0);">全局唯一</span></strong>”性外,还需要具备“<span style="color: rgb(0, 209, 0);"><strong>递增趋势</strong></span>”,以减少MySQL的数据页分裂,从而降低数据库IO压力并提升服务器性能。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">因此,在项目中通常需要引入一种算法,能够生成满足“<span style="color: rgb(0, 209, 0);"><strong>全局唯一</strong></span>”、“<span style="color: rgb(0, 209, 0);"><strong>递增趋势</strong></span>”和“<span style="color: rgb(0, 209, 0);"><strong>高性能</strong></span>”要求的数据。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">关于全局分布式ID的生成,网上有很多相关文章。其中最常见的方法是借助第三方开源组件实现,如百度开源的Uidgenerator、滴滴开源的TinyID、美团开源的Leaf以及雪花算法SnowFlake等。然而,大部分开源组件都需要依赖数据库或Redis中间件来实现,对于非特大型项目来说可能过于繁重。因此,我更倾向于在项目中使用雪花算法SnowFlake来生成全局唯一ID。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-bottom: none;font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin: 10px 5px;border-left-color: rgb(53, 179, 120);border-right: 0px solid rgb(53, 179, 120);color: rgb(97, 97, 97);quotes: none;background: rgb(251, 249, 253);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">标准版雪花算法网上已经有很多解读文章了,此处就不再赘述了。</p> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">然而,标准版的雪花算法存在 <strong style="color: rgb(53, 179, 120);">时钟敏感</strong> 问题。由于ID生成与当前操作系统时间戳绑定(利用了时间的单调递增性),当操作系统的时钟出现回拨时,生成的ID可能会重复(尽管通常不会人为地回拨时钟,但服务器可能会出现偶发的“时钟漂移”现象)。</p> <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="border-top: none;border-bottom: none;font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin: 10px 5px;border-left-color: rgb(53, 179, 120);border-right: 0px solid rgb(53, 179, 120);color: rgb(97, 97, 97);quotes: none;background: rgb(251, 249, 253);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">如果要要解决这个问题,我们可以在获取 ID 时记录当前的时间戳。然后在下一次获取 ID 时,比较当前时间戳和上次记录的时间戳。如果发现当前时间戳小于上次记录的时间戳,说明出现了时钟回拨现象,此时可以拒绝服务并等待时间戳追上记录值。</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;">因此,在项目中我们不能直接使用标准版的雪花算法,而需要寻找一个改良后的方案。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">这里我推荐大家使用开源分布式事务处理组件Seata的改良方案,它完美的解决了雪花算法时钟敏感的问题,并且代码简洁,可以非常方便集成在你项目中。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">下面让我们来分析一下Seata改进后的方案。</p> <h2 data-tool="mdnice编辑器" style="line-height: 32px;color: rgb(53, 179, 120);display: inline-block;border-bottom: 0px solid rgb(53, 179, 120);border-top-color: rgb(53, 179, 120);border-right-color: rgb(53, 179, 120);border-left-color: rgb(53, 179, 120);font-size: 23px;margin-top: 1em;margin-bottom: 0rem;padding-top: 0.5em;padding-bottom: 0.5em;font-weight: bold;"><span style="display: none;"></span>Seata的优化方案</h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">在原版雪花算法中,分布式ID的格式是这样的。</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.1675925925925926" src="/upload/a1e439848f83d3f091976fe2f2091753.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;"> <br> </figcaption> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">雪花算法主要是利用时间的单调递增特性,并且与操作系统的时间戳时刻绑定,一旦出现时间“回退”,则打破了时间 “单调递增”这个前提,所以可能会出现重复。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">而在改良后的Seata方案中,其ID格式是这样的。</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.1685185185185185" src="/upload/719981af5ef788d15b97aee4c14ad5a2.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;"> <br> </figcaption> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">通过观察Seata代码,我们可以发现它只是简单地调整了节点ID和时间戳的位置。那么这样做的目的是什么呢?</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">答案是通过这种方式<span style="color: rgb(53, 179, 120);"><strong>解除了算法与操作系统时间戳的强绑定关系。</strong></span>生成器仅在初始化时获取系统时间戳作为初始时间戳,之后不再与系统时间戳同步。生成器的递增仅由序列号的递增驱动。例如,当序列号的当前值达到4095时,下一个请求到来时,序列号将溢出12位空间并重新归零,同时溢出的进位将加到时间戳上,使时间戳+1。因此,时间戳和序列号实际上可以视为一个整体。</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);">AtomicLong</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: #a0a1a7;font-style: italic;line-height: 26px;">/**<br> * timestamp and sequence mix in one Long<br> * highest 11 bit: not used<br> * middle 41 bit: timestamp<br> * lowest 12 bit: sequence<br> */</span><br><span style="color: #a626a4;line-height: 26px;">private</span> AtomicLong timestampAndSequence;<br><br><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br> * The number of bits occupied by sequence<br> */</span><br><span style="color: #a626a4;line-height: 26px;">private</span> <span style="color: #a626a4;line-height: 26px;">final</span> <span style="color: #a626a4;line-height: 26px;">int</span> sequenceBits = <span style="color: #986801;line-height: 26px;">12</span>;<br><br><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br> * init first timestamp and sequence immediately<br> */</span><br><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">private</span> <span style="color: #a626a4;line-height: 26px;">void</span> <span style="color: #4078f2;line-height: 26px;">initTimestampAndSequence</span><span style="line-height: 26px;">()</span> </span>{<br> <span style="color: #a626a4;line-height: 26px;">long</span> timestamp = getNewestTimestamp();<br> <span style="color: #a626a4;line-height: 26px;">long</span> timestampWithSequence = timestamp << sequenceBits;<br> <span style="color: #a626a4;line-height: 26px;">this</span>.timestampAndSequence = <span style="color: #a626a4;line-height: 26px;">new</span> AtomicLong(timestampWithSequence);<br>}<br></code></pre> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-bottom: none;font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin: 10px 5px;border-left-color: rgb(53, 179, 120);border-right: 0px solid rgb(53, 179, 120);color: rgb(97, 97, 97);quotes: none;background: rgb(251, 249, 253);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">代码解释:</p> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">1. 在初始化方法中,获取当前时间戳<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 179, 120);">getNewestTimestamp()</code>以后将其左移12位,留出了序列号的位置。</p> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">2. 而Long类型转化成二进制以后是64位,前11位不使用,中间的41位代表时间戳,后面的12位代表序列号。</p> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">最高11位在初始化时就直接确定好,之后不再变化,核心代码如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br> * init workerId<br> * <span style="color: #a626a4;line-height: 26px;">@param</span> workerId if null, then auto generate one<br> */</span><br><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">private</span> <span style="color: #a626a4;line-height: 26px;">void</span> <span style="color: #4078f2;line-height: 26px;">initWorkerId</span><span style="line-height: 26px;">(Long workerId)</span> </span>{<br> <span style="color: #a626a4;line-height: 26px;">if</span> (workerId == <span style="color: #a626a4;line-height: 26px;">null</span>) {<br> workerId = generateWorkerId();<br> }<br> <span style="color: #a626a4;line-height: 26px;">if</span> (workerId > maxWorkerId || workerId < <span style="color: #986801;line-height: 26px;">0</span>) {<br> String message = String.format(<span style="color: #50a14f;line-height: 26px;">"worker Id can't be greater than %d or less than 0"</span>, maxWorkerId);<br> <span style="color: #a626a4;line-height: 26px;">throw</span> <span style="color: #a626a4;line-height: 26px;">new</span> IllegalArgumentException(message);<br> }<br> <span style="color: #a626a4;line-height: 26px;">this</span>.workerId = workerId << (timestampBits + sequenceBits);<br>}<br><br><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br> * auto generate workerId, try using mac first, if failed, then randomly generate one<br> * <span style="color: #a626a4;line-height: 26px;">@return</span> workerId<br> */</span><br><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">private</span> <span style="color: #a626a4;line-height: 26px;">long</span> <span style="color: #4078f2;line-height: 26px;">generateWorkerId</span><span style="line-height: 26px;">()</span> </span>{<br> <span style="color: #a626a4;line-height: 26px;">try</span> {<br> <span style="color: #a626a4;line-height: 26px;">return</span> generateWorkerIdBaseOnMac();<br> } <span style="color: #a626a4;line-height: 26px;">catch</span> (Exception e) {<br> <span style="color: #a626a4;line-height: 26px;">return</span> generateRandomWorkerId();<br> }<br>}<br><br><br><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br> * use lowest 10 bit of available MAC as workerId<br> * <span style="color: #a626a4;line-height: 26px;">@return</span> workerId<br> * <span style="color: #a626a4;line-height: 26px;">@throws</span> Exception when there is no available mac found<br> */</span><br><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">private</span> <span style="color: #a626a4;line-height: 26px;">long</span> <span style="color: #4078f2;line-height: 26px;">generateWorkerIdBaseOnMac</span><span style="line-height: 26px;">()</span> <span style="color: #a626a4;line-height: 26px;">throws</span> Exception </span>{<br> Enumeration<NetworkInterface> all = NetworkInterface.getNetworkInterfaces();<br> <span style="color: #a626a4;line-height: 26px;">while</span> (all.hasMoreElements()) {<br> NetworkInterface networkInterface = all.nextElement();<br> <span style="color: #a626a4;line-height: 26px;">boolean</span> loopBack = networkInterface.isLoopback();<br> <span style="color: #a626a4;line-height: 26px;">boolean</span> isVirtual = networkInterface.isVirtual();<br> <span style="color: #a626a4;line-height: 26px;">if</span> (loopBack || isVirtual) {<br> <span style="color: #a626a4;line-height: 26px;">continue</span>;<br> }<br> <span style="color: #a626a4;line-height: 26px;">byte</span>[] mac = networkInterface.getHardwareAddress();<br> <span style="color: #a626a4;line-height: 26px;">return</span> ((mac[<span style="color: #986801;line-height: 26px;">4</span>] & <span style="color: #986801;line-height: 26px;">0B11</span>) << <span style="color: #986801;line-height: 26px;">8</span>) | (mac[<span style="color: #986801;line-height: 26px;">5</span>] & <span style="color: #986801;line-height: 26px;">0xFF</span>);<br> }<br> <span style="color: #a626a4;line-height: 26px;">throw</span> <span style="color: #a626a4;line-height: 26px;">new</span> RuntimeException(<span style="color: #50a14f;line-height: 26px;">"no available mac found"</span>);<br>}<br></code></pre> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-bottom: none;font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin: 10px 5px;border-left-color: rgb(53, 179, 120);border-right: 0px solid rgb(53, 179, 120);color: rgb(97, 97, 97);quotes: none;background: rgb(251, 249, 253);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">代码解读:</p> <ol style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-1"> <li> <section style="line-height: 26px;color: rgb(1, 1, 1);margin-top: 10px;margin-bottom: 10px;"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">算法规定了节点ID最长为10位,2的10次方是1024,所以可以服务1024台机器,体现在数字上的取值范围是为[0,1023);</p> </section></li> <li> <section style="line-height: 26px;color: rgb(1, 1, 1);margin-top: 10px;margin-bottom: 10px;"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">在原版雪花算法中,如果未指定节点ID,会截取本地IPv4地址的低10位作为节点ID,这样在生成实践中如果出现IP的第4个字节和第3个字节的低2位一样就会重复。如:192.168.4.10 和 192.168.8.10</p> </section></li> <li> <section style="line-height: 26px;color: rgb(1, 1, 1);margin-top: 10px;margin-bottom: 10px;"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">新版算法generateWorkerIdBaseOnMac()是从从本机网卡的MAC地址截取低10位,最后通过(mac[4] & 0B11) << 8) | (mac[5] & 0xFF)保证其取值范围最大值为1023,算法有点难懂,分步解释:</p> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;"><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);">mac[4]</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);">mac[5]</code> 是无符号8位整数的变量,其取值范围是[0,255)</p> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;"><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);">(mac[4] & 0B11)</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);">mac[4]</code> 的最后两位00,01,10,11,也就是取值范围为 0 到 3。</p> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;"><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);">(mac[4] & 0B11) << 8</code>。左移 8 位相当于乘以 256,所以结果的取值范围是 0 到 3 * 256 = 0 到 768。</p> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;"><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);">(mac[5] & 0xFF)</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);">0xFF</code>, 也就是取值范围是 0 到 255</p> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">所以最后结果的取值范围是从 0 到 768 | 255 = 1023。</p> </section></li> <li> <section style="line-height: 26px;color: rgb(1, 1, 1);margin-top: 10px;margin-bottom: 10px;"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">计算出节点ID以后,将其左移,this.workerId = workerId << (timestampBits + sequenceBits),这样就完成了算法ID的组装。</p> </section></li> </ol> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">最后看看生成ID的算法</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">private</span> <span style="color: #a626a4;line-height: 26px;">final</span> <span style="color: #a626a4;line-height: 26px;">int</span> timestampBits = <span style="color: #986801;line-height: 26px;">41</span>;<br><span style="color: #a626a4;line-height: 26px;">private</span> <span style="color: #a626a4;line-height: 26px;">final</span> <span style="color: #a626a4;line-height: 26px;">int</span> sequenceBits = <span style="color: #986801;line-height: 26px;">12</span>;<br><span style="color: #a626a4;line-height: 26px;">private</span> <span style="color: #a626a4;line-height: 26px;">final</span> <span style="color: #a626a4;line-height: 26px;">long</span> timestampAndSequenceMask = ~(-<span style="color: #986801;line-height: 26px;">1L</span> << (timestampBits + sequenceBits));<br><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span> <span style="color: #a626a4;line-height: 26px;">long</span> <span style="color: #4078f2;line-height: 26px;">nextId</span><span style="line-height: 26px;">()</span> </span>{<br> <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 获得递增后的时间戳和序列号</span><br> <span style="color: #a626a4;line-height: 26px;">long</span> next = timestampAndSequence.incrementAndGet();<br> <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 截取低53位</span><br> <span style="color: #a626a4;line-height: 26px;">long</span> timestampWithSequence = next & timestampAndSequenceMask;<br> <span style="color: #a0a1a7;font-style: italic;line-height: 26px;">// 跟先前保存好的高11位进行一个或的位运算</span><br> <span style="color: #a626a4;line-height: 26px;">return</span> workerId | timestampWithSequence;<br>}<br></code></pre> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-bottom: none;font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin: 10px 5px;border-left-color: rgb(53, 179, 120);border-right: 0px solid rgb(53, 179, 120);color: rgb(97, 97, 97);quotes: none;background: rgb(251, 249, 253);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">看完Seata雪花算法的实现逻辑,你觉得怎么样呢?反正我只会直呼 ”卧槽,牛皮“~</p> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">通过对Seata改良算法代码的解读,可以知道,算法生成器仅在启动时获取了一次系统时钟,可以说是弱依赖于操作系统时钟,这样在运行期间,生成器不再受时钟回拨的影响。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">同时由于序列号有12位,最大取值范围是[0,4095]。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">如果在当前毫秒下序列号生成到了 4096 ,这个时候序列号回重新归0,同时让时间戳+1,也就是 "借用"下一个时间戳的序列号空间,这种<span style="color: rgb(53, 179, 120);"><strong>超前消费</strong></span>会不会导致生成器内的时间戳大大超前于系统的时间戳,从而导致重启时ID重复呢?</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;"><span style="color: rgb(53, 179, 120);"><strong>理论上有,实际上并不会。</strong></span>因为要达到这个效果,也就意味着生成器的QPS得持续稳定在4096/ms,约400W/s之上,这得什么场景才能有这样的流量呢?(12306在2020年春运期间高峰QPS约为170W/s,2020年双11淘宝TPS为58.3W/s) 就算有了,瓶颈一定不在生成器这里。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">通过对Seata改良算法代码的解读,我们可以了解到算法生成器仅在启动时获取一次系统时钟,因此它在运行期间对操作系统时钟的依赖相对较弱。<strong style="color: rgb(53, 179, 120);">这意味着生成器不会受到时钟回拨的影响。</strong></p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">此外,根据序列号的位数为12位,其取值范围为[0, 4095]。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">如果在当前毫秒内序列号生成到了4096,这时序列号会重新归0,并且时间戳会增加1,即"借用"下一个时间戳的序列号空间。这种超前消费是否会导致生成器内部的时间戳大大超前于系统的时间戳,从而导致在重启时出现重复的ID呢?</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">理论上来说,这种情况是有可能发生的。然而,在实际情况下并不会出现这种问题。因为要达到这种效果,也就意味着生成器的每秒请求数(QPS)需要持续稳定在4096次以上,相当于每秒处理约400万个请求。这样高的流量场景是非常罕见的,而且即使存在这样的流量,天塌下来有高个子顶着,一定会是其他组件先出问题。</p> <h2 data-tool="mdnice编辑器" style="line-height: 32px;color: rgb(53, 179, 120);display: inline-block;border-bottom: 0px solid rgb(53, 179, 120);border-top-color: rgb(53, 179, 120);border-right-color: rgb(53, 179, 120);border-left-color: rgb(53, 179, 120);font-size: 23px;margin-top: 1em;margin-bottom: 0rem;padding-top: 0.5em;padding-bottom: 0.5em;font-weight: bold;"><span style="display: none;"></span>Seata雪花算法的 “缺陷”</h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">经过观察,我们可以发现一个问题:Seata改良版的算法在<strong style="color: rgb(53, 179, 120);">单节点内部确实是单调递增</strong>的,但是在多实例部署时,它<strong style="color: rgb(53, 179, 120);">不再保证全局单调递增</strong>。这是因为节点ID在生成的ID中占据了高位,因此节点ID较大的生成的ID一定大于节点ID较小的生成的ID,与它们的生成时间先后顺序无关。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">相比之下,原版雪花算法将时间戳放在高位,并且始终追随系统时钟,可以确保早期生成的ID小于后期生成的ID。只有当两个节点恰好在同一时间戳生成ID时,两个ID的大小才由节点ID决定。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">从这个角度来看,新版算法是否存在问题呢?</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">关于这个问题,官方已经给出了结论:</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;"><strong style="color: rgb(53, 179, 120);">新版算法的确不具备全局的单调递增性,但这不影响我们的初衷(减少数据库的页分裂)。这个结论看起来有点违反直觉,但可以被证明。</strong></p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">现在让我们来进一步优化和解释这个结论。</p> <h2 data-tool="mdnice编辑器" style="line-height: 32px;color: rgb(53, 179, 120);display: inline-block;border-bottom: 0px solid rgb(53, 179, 120);border-top-color: rgb(53, 179, 120);border-right-color: rgb(53, 179, 120);border-left-color: rgb(53, 179, 120);font-size: 23px;margin-top: 1em;margin-bottom: 0rem;padding-top: 0.5em;padding-bottom: 0.5em;font-weight: bold;"><span style="display: none;"></span>B+树原理</h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">在证明之前我们需要先回顾一下数据库页分裂的相关知识(基于B+数索引的MySQL InnoDB引擎)。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">在B+树索引中,主键索引的叶子节点除了保存键的值之外,还保存了数据行的完整记录。叶子节点之间以双向链表的形式连接在一起。叶子节点在物理存储上被组织为数据页,每个数据页最多可以存储N条行记录。</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.2712962962962963" src="/upload/bbfdd72c0758f522b066bc9b21ed721c.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;"> <br> </figcaption> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">B+树的特性要求左边的节点的键值小于右边节点的键值。如果现在要插入一条ID为25的记录,会发生什么呢?(假设每个数据页只能容纳4条记录)答案是会导致页分裂,如下图所示:</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.20833333333333334" src="/upload/f22d3827df536277929df6cf550630d1.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;"> <br> </figcaption> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">页分裂对IO操作不友好,需要创建新的数据页,并复制和转移旧数据页中的部分记录。因此,我们应该尽量避免页分裂的发生。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-bottom: none;font-size: 0.9em;overflow: auto;padding: 10px 10px 10px 20px;margin: 10px 5px;border-left-color: rgb(53, 179, 120);border-right: 0px solid rgb(53, 179, 120);color: rgb(97, 97, 97);quotes: none;background: rgb(251, 249, 253);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">如果你想直观地了解B+树节点分裂的过程,建议访问以下网站:</p> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">B+ Tree Visualization -> https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html</p> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;"><strong style="color: rgb(53, 179, 120);">理想的情况下</strong>,主键ID最好是顺序递增的(例如把主键设置为<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);">auto_increment</code>),这样就只会在当前数据页放满了的时候,才需要新建下一页,双向链表永远是顺序尾部增长的,不会有中间的节点发生分裂的情况。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;"><strong style="color: rgb(53, 179, 120);">最糟糕的情况下</strong>,主键ID是随机无序生成的(例如java中一个UUID字符串),这种情况下,新插入的记录会随机分配到任何一个数据页,如果该页已满,就会触发页分裂。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">如果主键ID由标准版雪花算法生成,最好的情况下,是每个时间戳内只有一个节点在生成ID,这时候算法的效果等同于理想情况的顺序递增,即跟<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);">auto_increment</code>无差。最坏的情况下,是每个时间戳内所有节点都在生成ID,这时候算法的效果接近于无序(但仍比UUID的完全无序要好得多,因为workerId只有10位决定了最多只有1024个节点)。实际生产中,算法的效果取决于业务流量,并发度越低,算法越接近理想情况。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">在理想情况下,主键ID最好是按顺序递增的(例如使用<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);">auto_increment</code>设置主键),这样只有在当前数据页已满时才需要创建下一页,双向链表的增长总是在尾部进行的,不会导致中间节点的分裂。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">在最糟糕的情况下,主键ID是随机无序生成的(例如在Java中使用UUID字符串),这种情况下,新插入的记录会被随机分配到任意一个数据页,如果该页已满,则触发页分裂。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">这也是为什么不推荐使用UUID作为主键ID的原因,UUID会导致频繁出现页裂变,影响数据库性能。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">如果主键ID由标准版雪花算法生成,最理想的情况是每个时间戳内只有一个节点生成ID,这种情况下算法的效果与理想情况的顺序递增相同,即与<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);">auto_increment</code>没有区别。最糟糕的情况是每个时间戳内的所有节点都在生成ID,这种情况下算法的效果接近于无序(但仍比完全无序的UUID要好得多,因为workerId只有10位,限制了节点数量最多为1024个)。在实际生产环境中,算法的效果取决于业务流量,较低的并发度会使算法接近理想情况。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">那么,Seata改良版的雪花算法又是如何呢?</p> <h2 data-tool="mdnice编辑器" style="line-height: 32px;color: rgb(53, 179, 120);display: inline-block;border-bottom: 0px solid rgb(53, 179, 120);border-top-color: rgb(53, 179, 120);border-right-color: rgb(53, 179, 120);border-left-color: rgb(53, 179, 120);font-size: 23px;margin-top: 1em;margin-bottom: 0rem;padding-top: 0.5em;padding-bottom: 0.5em;font-weight: bold;"><span style="display: none;"></span>Seata 改良算法会导致频繁页裂变吗?</h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">新版算法从全局角度来看,生成的ID是无序的。然而,对于每个节点而言,它所生成的ID序列是严格单调递增的。由于节点ID是有限的,因此最多可以划分出1024个子序列,每个子序列都是单调递增的。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">对于数据库而言,在初始阶段接收到的ID可能是无序的,来自各个子序列的ID会混合在一起。假设节点ID的值是递增的,初始阶段的效果如下图所示:</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.2675925925925926" src="/upload/fddfa2a6fa4ec0ecca06a5d096cc92cb.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;"> <br> </figcaption> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">假设此时出现了一个worker1-seq2的ID,由于数据页已经存满,会触发一次页分裂,如下图所示:</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.20555555555555555" src="/upload/641646550c7e075fcdbaee9d3942f4ba.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;"> <br> </figcaption> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">然而,分裂之后发生了一件有趣的事情。对于worker1而言,后续的seq3、seq4由于可以直接放入数据页,不会再触发页分裂。而seq5只需要像顺序递增一样,在新建的页中进行链接。<strong style="color: rgb(53, 179, 120);">值得注意的是,由于worker1的后续ID都比worker2的ID小,它们不会被分配到worker2及其之后的节点,因此不会导致后续节点的页分裂。同样地,由于是单调递增,它们也不会被分配到worker1当前节点的前面,因此不会导致前面节点的页分裂。</strong></p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">在这里,我们称具有这种性质的子序列达到了稳态,意味着该子序列已经"稳定"下来,其后续增长只会发生在子序列的尾部,而不会引起其他节点的页分裂。同样的情况也可以推广到其他子序列上。无论初始阶段数据库接收到的ID有多么混乱,在有限次页分裂之后,双向链表总能达到这样一个稳定的终态:</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.37407407407407406" src="/upload/30940728e250fc00f78fc69553014d0d.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;"> <br> </figcaption> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">到达终态后,后续的ID只会在该ID所属的子序列上进行顺序增长,而不会造成页分裂。该状态下的顺序增长与<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);">auto_increment</code>的顺序增长的区别是,前者有1024个增长位点(各个子序列的尾部),后者只有尾部一个。</p> <h2 data-tool="mdnice编辑器" style="line-height: 32px;color: rgb(53, 179, 120);display: inline-block;border-bottom: 0px solid rgb(53, 179, 120);border-top-color: rgb(53, 179, 120);border-right-color: rgb(53, 179, 120);border-left-color: rgb(53, 179, 120);font-size: 23px;margin-top: 1em;margin-bottom: 0rem;padding-top: 0.5em;padding-bottom: 0.5em;font-weight: bold;"><span style="display: none;"></span>小结</h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">综上所述,改进版的雪花算法虽然不具备全局单调递增的特性,但在同一节点下能够保持单调递增。此外,经过几次数据页分裂后,它会达到一个稳定状态,不会频繁触发数据库的页分裂。同时,该算法仍然满足高性能和全局唯一的要求。因此,完全可以将改进版的雪花算法引入到项目中使用。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">然而,需要注意的是,在实际业务系统中,最好将此算法应用于那些需要长期保存数据的场景,而对于需要频繁删除的表则不太适用。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">这是因为该算法利用前期的页分裂,逐渐将不同子序列分离,从而实现算法的收敛到稳定状态。如果频繁删除数据,会触发数据库的页合并操作,这会阻碍数据的收敛。在极端情况下,刚刚分离的数据可能会立即发生页合并,导致数据无法保持稳定状态。因此,在使用改进版的雪花算法时需要谨慎考虑业务需求和数据操作的频率。</p> <h2 data-tool="mdnice编辑器" style="line-height: 32px;color: rgb(53, 179, 120);display: inline-block;border-bottom: 0px solid rgb(53, 179, 120);border-top-color: rgb(53, 179, 120);border-right-color: rgb(53, 179, 120);border-left-color: rgb(53, 179, 120);font-size: 23px;margin-top: 1em;margin-bottom: 0rem;padding-top: 0.5em;padding-bottom: 0.5em;font-weight: bold;"><span style="display: none;"></span>DailyMart集成全局ID算法</h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">DailyMart项目中涉及到多个场景需要使用全局唯一ID,因此我已经将Seata改进版的雪花算法通过自定义Starter的方式集成到了项目中。使用时只需要调用<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);">IdUtils.nextId()</code>方法即可获取全局唯一ID,你可以参考源代码进行具体实现。</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/e0f68327ad883513bd767a9948f4ce43.png" data-cropx1="0" data-cropx2="1326" data-cropy1="0" data-cropy2="798.4516129032259" data-ratio="0.6018518518518519" src="https://mmbiz.qpic.cn/mmbiz_jpg/PxMzT0Oibf4hDYJrur4PJx0z0y8WUvm8bsUystTAdLJt0TCyjqianWYdFDwicpKm0yBru7aiaztibYQhBLFzh7SzDmA/640?wx_fmt=jpeg" data-type="jpeg" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;width: 558px;height: 336px;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">同时,之前的文章中提到了在使用Mybatis-Plus时,由于没有正确配置<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);">worker-id</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);">datacenter-id</code>参数,导致生成的ID可能会出现重复。基于此我还在<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);">datasources</code>公共模块中替换了Mybatis-Plus的ID生成算法,直接使用Seata改进后的雪花算法。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;margin: 1em 4px;">以下为代码具体实现:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">public</span> <span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span> <span style="color: #c18401;line-height: 26px;">CustomIdGenerator</span> <span style="color: #a626a4;line-height: 26px;">implements</span> <span style="color: #c18401;line-height: 26px;">IdentifierGenerator</span> </span>{<br> <br> <span style="color: #4078f2;line-height: 26px;">@Override</span><br> <span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span> Number <span style="color: #4078f2;line-height: 26px;">nextId</span><span style="line-height: 26px;">(Object entity)</span> </span>{<br> <span style="color: #a626a4;line-height: 26px;">return</span> IdUtils.nextId();<br> }<br> <br>}<br><br><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br> * 替换Mybatis-plus的算法生成器<br> */</span><br><span style="color: #4078f2;line-height: 26px;">@Bean</span><br><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span> IdentifierGenerator <span style="color: #4078f2;line-height: 26px;">identifierGenerator</span><span style="line-height: 26px;">()</span> </span>{<br> <span style="color: #a626a4;line-height: 26px;">return</span> <span style="color: #a626a4;line-height: 26px;">new</span> CustomIdGenerator();<br>}</code></pre> </section> <p style="margin-top: 10px;margin-bottom: 10px;outline: 0px;letter-spacing: 0.544px;white-space: normal;color: rgb(62, 62, 62);font-size: 16px;text-align: center;widows: 1;word-spacing: 2px;background-color: rgb(255, 255, 255);font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;line-height: 2em;"><span style="outline: 0px;font-weight: bolder;letter-spacing: 0.544px;font-family: 微软雅黑;font-size: 16.3636px;">·END·</span><br style="outline: 0px;"></p> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding-right: 10px;padding-left: 10px;outline: 0px;white-space: normal;background-color: rgb(255, 255, 255);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;visibility: visible;"> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding-right: 10px;padding-left: 10px;outline: 0px;line-height: 1.6;word-break: break-word;letter-spacing: 0.05em;visibility: visible;"> <blockquote data-tool="mdnice编辑器" style="margin: 10px 5px;padding: 10px 10px 10px 20px;outline: 0px;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;outline: 0px;font-size: 16px;color: black;line-height: 26px;">DDD&微服务系列源码已经上传至GitHub,如果需要获取源码地址,请关注公号并回复关键字 DDD 即可!</p> </blockquote> </section> <section style="outline: 0px;"> <span style="outline: 0px;letter-spacing: 0.05em;"></span> </section> <section style="outline: 0px;"> <span style="outline: 0px;letter-spacing: 0.05em;"></span> </section> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzAwMTk4NjM1MA==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/PxMzT0Oibf4gcBzLSUNh2cgXUsuLIsvQYJE1lzZd74qpC3iciaM6gcYIfOVV0KjDDkeN4CTLTn4ETPtaHOAuTWSWA/0?wx_fmt=png" data-nickname="JAVA日知录" data-alias="javadaily" data-signature="写代码的架构师,做架构的程序员! 实战、源码、数据库、架构...只要你来,你想了解的这里都有!" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> </section> <p style="margin-top: 10px;margin-bottom: 10px;outline: 0px;letter-spacing: 0.544px;white-space: normal;color: rgb(62, 62, 62);font-size: 16px;text-align: center;widows: 1;word-spacing: 2px;background-color: rgb(255, 255, 255);font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;line-height: 2em;"><span style="outline: 0px;font-weight: bolder;letter-spacing: 0.544px;font-family: 微软雅黑;font-size: 16.3636px;"></span></p> <pre style="margin-bottom: 1rem;outline: 0px;font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;font-size: 15px;overflow: auto;color: rgb(89, 89, 89);letter-spacing: 0.544px;background-color: rgb(255, 255, 255);text-align: left;"><p style="outline: 0px;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;white-space: normal;"><span style="outline: 0px;font-weight: bolder;">👉🏻👉🏻👉🏻 <a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzAwMTk4NjM1MA==&mid=2247517420&idx=1&sn=20fb442c2b4c2b3a6db2f2924af2e5c9&chksm=9ad39eadada417bbdb96ab21a7284060dfb51d912d92d0038ae26722f0b2f1dd029f44cf34f6&scene=21#wechat_redirect" textvalue="点击加入高质量技术交流群" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" style="outline: 0px;color: var(--weui-LINK);background-color: transparent;cursor: pointer;">加入高质量技术交流群</a></span></p></pre> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>
作者:微信小助手
<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;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">本文章实现最简单全面的 Jenkins + Docker + Spring Boot 一键自动部署项目。步骤齐全,少走坑路。</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;">环境:CentOS7 + Git (Gitee)</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;">实现步骤:在 Docker 安装 Jenkins,配置 Jenkins 基本信息,利用 Dockerfile 和 Shell 脚本实现项目自动拉取打包并运行。</p> </section></li> </ul> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><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);">安装 Docker</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">安装社区版本 Docker CE</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);"> 确保 yum 包更新到最新 </section></li> </ol> <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/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">yum update<br></code></pre> <ol start="2" 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> </ol> <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/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">yum remove docker docker-common docker-selinux docker-engine<br></code></pre> <ol start="3" 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> </ol> <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/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">yum install -y yum-utils device-mapper-persistent-data lvm2<br></code></pre> <ol start="4" 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);"> 设置 yum 源 </section></li> </ol> <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/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo<br></code></pre> <ol start="5" 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);"> 安装 Docker </section></li> </ol> <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/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">yum install docker-ce <span style="color: #75715e;line-height: 26px;">#由于repo中默认只开启stable仓库,故这里安装的是最新稳定版17.12.0</span><br>yum install <自己的版本> <span style="color: #75715e;line-height: 26px;"># 例如:sudo yum install docker-ce-17.12.0.ce</span><br></code></pre> <ol start="6" 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> </ol> <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/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">systemctl start docker<br>systemctl <span style="color: #a6e22e;line-height: 26px;">enable</span> docker<br></code></pre> <ol start="7" 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> </ol> <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/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">docker version<br></code></pre> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><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);">安装 Jenkins</span></h2> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-width: 1px;border-style: dashed;border-color: rgb(37, 132, 181);background-image: initial;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">Jenkins 中文官网:https://www.jenkins.io/zh/</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">1. 安装 Jenkins</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Docker 安装一切都是那么简单。注意检查 8080 是否已经占用,如果占用请修改端口。</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/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">docker run --name jenkins -u root --rm -d -p 8080:8080 -p 50000:50000 -v /var/jenkins_home:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock jenkinsci/blueocean<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">如果没改端口号的话,安装完成后访问地址:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">http://{部署Jenkins所在服务IP}:8080<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">此处会有几分钟的等待时间。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">2. 初始化 Jenkins</span><span style="display: none;"></span></h3> <blockquote data-tool="mdnice编辑器" style="font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-width: 1px;border-style: dashed;border-color: rgb(37, 132, 181);background-image: initial;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">详情见官网教程:https://www.jenkins.io/zh/doc</p> </blockquote> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><span style="display: none;"></span>2.1 解锁 Jenkins<span style="display: none;"></span></h5> <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/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="color: #75715e;line-height: 26px;"># 进入Jenkins容器</span><br>docker <span style="color: #a6e22e;line-height: 26px;">exec</span> -it {Jenkins容器名} bash <br><span style="color: #75715e;line-height: 26px;"># 例如 docker exec -it jenkins bash</span><br><br><span style="color: #75715e;line-height: 26px;"># 查看密码</span><br>cat /var/lib/jenkins/secrets/initialAdminPassword<br><br><span style="color: #75715e;line-height: 26px;"># 复制密码到输入框里面</span><br></code></pre> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.4394394394394394" src="/upload/9c9a29cb3334061ff478d67f135e0d5a.png" data-type="png" data-w="999" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><span style="display: none;"></span>2.2 安装插件<span style="display: none;"></span></h5> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">选择第一项:安装推荐的插件。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5387596899224806" src="/upload/4cb69ce4067e813264ddf57667222307.png" data-type="png" data-w="774" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><span style="display: none;"></span>2.3 创建管理员用户<span style="display: none;"></span></h5> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">此账户信息一定要记住哦。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><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> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">1. 安装需要插件</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">进入【首页】–【系统管理】–【插件管理】–【可选插件】。搜索以下需要安装的插件,点击安装即可。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.8373493975903614" src="/upload/aed33a4677daad91a97e0135c83b4fcc.png" data-type="png" data-w="498" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">安装 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">Maven Integration</code></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">安装 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">Publish Over SSH</code>(如果不需要远程推送,不用安装)</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">如果使用 Gitee 码云,安装插件Gitee(自带 Git 不用单独安装)</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">2. 配置 Maven</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">进入【首页】–【系统管理】–【全局配置】,拉到页面最下方 maven–maven 安装。</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.42962962962962964" src="/upload/5a20619a1582ada6e659623dea8b9d71.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><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> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">1. 新建任务</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">点击【新建任务】,输入任务名称,点击构建一个自由风格的软件项目。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.28762135922330095" src="/upload/d182916e35b6d6b5565944e49c0418da.png" data-type="png" data-w="824" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">2. 源码管理</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">点击【源码管理】–【Git】,输入仓库地址,添加凭证,选择好凭证即可。</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.7423664122137404" src="/upload/2bce17059806032c917601271c63e8b7.png" data-type="png" data-w="524" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="1.1605504587155964" src="/upload/afee466470b25ce9c6ee844fd4b1645a.png" data-type="png" data-w="436" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">3. 构建触发器</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">点击【构建触发器】–【构建】–【增加构建步骤】–【调用顶层 Maven 目标】–【「填写配置」】–【保存】。</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.969047619047619" src="/upload/6b4b74843cfe81fdf62d79dece78965c.png" data-type="png" data-w="840" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">此处命令只是 install,看是否能生成 jar 包。</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/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">clean install -Dmaven.test.skip=<span style="color: #f92672;font-weight: bold;line-height: 26px;">true</span><br></code></pre> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.3881401617250674" src="/upload/7a5d833eef70b4ea78065ec3e79a1457.png" data-type="png" data-w="742" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">4. 保存</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">点击【保存】按钮即可。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><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> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">该功能测试是否能正常打包。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">1. 构建</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">点击构建按钮。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.10740740740740741" src="/upload/6fe58c059238dfcbbe483d853d122732.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">2. 查看日志</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">点击正在构建的任务,或者点击任务名称进入详情页面,查看控制台输出。看是否能成功打成 jar 包。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">该处日志第一次可能下载依赖 jar 包失败,再次点击构建即可成功。</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.10740740740740741" src="/upload/6fe58c059238dfcbbe483d853d122732.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="1.083086053412463" src="/upload/84f01de273152814912fe702d153d41a.png" data-type="png" data-w="674" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5918803418803419" src="/upload/37e865ec71500f1ca05d96d9e28a53df.png" data-type="png" data-w="936" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">3. 查看项目位置</span><span style="display: none;"></span></h3> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="color: #a6e22e;line-height: 26px;">cd</span> /var/jenkins_home/workspace<br>ll <span style="color: #75715e;line-height: 26px;"># 即可查看是否存在</span><br></code></pre> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;"><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;">6</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;">因为项目和 Jenkins 在同一台服务器,所以我们用 Shell 脚本运行项目,原理既是通过 Dockerfile 打包镜像,然后 docker 运行即可。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">1. Dockerfile</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在 Spring Boot 项目根目录新建一个名为 Dockerfile 的文件,注意没有后缀名。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">其内容如下:(大致就是使用 JDK 8,把 jar 包添加到 docker 然后运行 prd 配置文件。详细可以查看其他教程)</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/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">FROM jdk:8<br>VOLUME /tmp<br>ADD target/zx-order-0.0.1-SNAPSHOT.jar app.jar<br>EXPOSE 8888ENTRYPOINT [<span style="color: #a6e22e;line-height: 26px;">"java"</span>,<span style="color: #a6e22e;line-height: 26px;">"-Djava.security.egd=file:/dev/./urandom"</span>,<span style="color: #a6e22e;line-height: 26px;">"-jar"</span>,<span style="color: #a6e22e;line-height: 26px;">"/app.jar"</span>,<span style="color: #a6e22e;line-height: 26px;">"--spring.profiles.active=prd"</span>]<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">2. 修改 Jenkins 任务配置</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;"> <img class="rich_pages wxw-img" data-ratio="1.0557029177718833" src="/upload/1a15a26dc68a7617bcfe361b06ce6bc1.png" data-type="png" data-w="754" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">配置如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.575" src="/upload/794867189b4552b94d95ba962cdd71c9.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">"-t" 指定新镜像名,"." 表示 Dockfile 在当前路径。</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/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;"><span style="color: #a6e22e;line-height: 26px;">cd</span> /var/jenkins_home/workspace/zx-order-api<br>docker stop zx-order || <span style="color: #f92672;font-weight: bold;line-height: 26px;">true</span><br>docker rm zx-order || <span style="color: #f92672;font-weight: bold;line-height: 26px;">true</span><br>docker rmi zx-order || <span style="color: #f92672;font-weight: bold;line-height: 26px;">true</span><br>docker build -t zx-order .<br>docker run -d -p 8888:8888 --name zx-order zx-order:latest<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">备注:</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;">上图用了 docker logs -f 是为了方便看日志,真实环境不要用,因为会一直等待日志,构建任务会失败;</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;">加 "|| true" 是如果命令执行失败也会继续实行,为了防止第一次没有该镜像报错;</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;">保存:点击保存即可;</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;">构建:查看 Jenkins 控制台输出,输出如下,证明成功;</p> </section></li> </ol> <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.6337148803329865" src="/upload/95eae505deb12253b0baa05e2bca5523.png" data-type="png" data-w="961" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><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);">5. 验证</span><span style="display: none;"></span></h3> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/wJibWkqN1bUPFlsrzna5YtxkbLP6TFJZlibShb6v8l8PDibbPVkibrVicy0DsKBVmP317nic8zERIK3ZPHg2eeodfl4cBeDkGdsSgT/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(39, 40, 34);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #272822;border-radius: 5px;">docker ps <span style="color: #75715e;line-height: 26px;"># 查看是否有自己的容器</span><br>docker logs <span style="color: #75715e;line-height: 26px;"># 自己的容器名,查看日志是否正确</span><br><span style="color: #75715e;line-height: 26px;"># 打开浏览器访问项目</span><br></code></pre> </section> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="54" data-source-title="" style="outline: 0px;color: var(--weui-FG-1);text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;background-color: rgb(255, 255, 255);"> <section class="js_blockquote_digest" style="outline: 0px;"> <section style="outline: 0px;"> <span style="outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;color: rgb(136, 136, 136);cursor: pointer;font-size: 11px;">来源:blog.csdn.net/zqqiang0307/</span> </section> <section style="outline: 0px;"> <span style="outline: 0px;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;color: rgb(136, 136, 136);cursor: pointer;font-size: 11px;">article/details/120458586</span> </section> </section> </blockquote> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 1em;outline: 0px;border-left-width: 2px;border-left-color: rgb(136, 136, 136);color: rgb(119, 119, 119);font-size: 0.9em;text-wrap: wrap;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: normal;text-align: left;caret-color: rgb(60, 60, 60);widows: 1;border-top: none;border-bottom: none;overflow: auto;background: rgba(0, 0, 0, 0.05);border-right: 2px solid rgb(136, 136, 136);visibility: visible;"></blockquote> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;text-wrap: wrap;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;letter-spacing: normal;text-align: left;caret-color: rgb(60, 60, 60);widows: 1;background-color: rgb(255, 255, 255);display: flex;flex-direction: column;justify-content: center;align-items: center;visibility: visible;"> <figcaption style="margin-top: 5px;outline: 0px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;visibility: visible;"> 加我好友,拉你进群 </figcaption> </figure>