作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;overflow-wrap: break-word;text-align: left;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">今天分享一个非常不错且开源的分布式存储组件MinIO,有多人朋友在用,文末留言评价一下~</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 22px;text-align: left;margin: 20px 10px 0px 0px;"><span style="display: none;"></span><span style="font-size: 18px;font-weight: 700;color: #222;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">什么是MinIO?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">Minio 是个基于 Golang 编写的开源对象存储套件,基于Apache License v2.0开源协议,虽然轻量,却拥有着不错的性能。它兼容亚马逊S3云存储服务接口。可以很简单的和其他应用结合使用,例如 NodeJS、Redis、MySQL等。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1. 应用场景</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">MinIO 的应用场景除了可以作为私有云的对象存储服务来使用,也可以作为云对象存储的网关层,无缝对接 <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(271, 93, 108);">Amazon S3</code> 或者 <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(271, 93, 108);">MicroSoft Azure</code> 。</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.6339869281045751" src="/upload/83b1762aeb92da2828be89b56c3146ae.jpg" data-w="612" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">2. 特点</span><span style="display: none;"></span></h3> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;list-style-type: decimal;color: #f83929;font-size: 16px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;"><strong style="font-weight: 700;color: rgb(248, 57, 41);">高性能</strong>:作为一款高性能存储,在标准硬件条件下,其读写速率分别可以达到 <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(271, 93, 108);">55Gb/s</code> 和 <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(271, 93, 108);">35Gb/s</code>。并且MinIO 支持一个对象文件可以是任意大小,从几kb到最大5T不等。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;"><strong style="font-weight: 700;color: rgb(248, 57, 41);">可扩展</strong>:不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并且支持跨越多个数据中心。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;"><strong style="font-weight: 700;color: rgb(248, 57, 41);">云原生</strong>:容器化、基于K8S的编排、多租户支持。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;"><strong style="font-weight: 700;color: rgb(248, 57, 41);">Amazon S3兼容</strong>:使用 Amazon S3 v2 / v4 API。可以使用Minio SDK,Minio Client,AWS SDK 和 AWS CLI 访问Minio服务器。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;"><strong style="font-weight: 700;color: rgb(248, 57, 41);">SDK支持</strong>:</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <br> </section></li> <ol style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;list-style-type: lower-alpha;color: rgb(248, 57, 41);font-size: 16px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> GO SDK:https://github.com/minio/minio-go </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> JavaSDK:https://github.com/minio/minio-java </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> PythonSDK:https://github.com/minio/minio-py </section></li> </ol> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;"><strong style="font-weight: 700;color: rgb(248, 57, 41);">图形化界面</strong>:有操作页面</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;"><strong style="font-weight: 700;color: rgb(248, 57, 41);">支持纠删码</strong>:MinIO使用纠删码、Checksum来防止硬件错误和静默数据污染。在最高冗余度配置下,即使丢失1/2的磁盘也能恢复数据。</p> </section></li> </ol> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;display: block;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;font-style: normal;padding: 15px 10px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: #353535;font-size: 16px;margin: 0 10px;display: block;">功能很强大,本文只是抛砖引玉,有兴趣的朋友自己去探索吧~</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 22px;text-align: left;margin: 20px 10px 0px 0px;"><span style="display: none;"></span><span style="font-size: 18px;font-weight: 700;color: #222;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">安装MinIO</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">安装非常简单,笔者这里使用docker安装,步骤如下:</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1. 获取镜像</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">执行命令如下:</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/A7sq8BD8oexqmcDw0weKQRrBQeTR8XD4xmTVRFn6mRqVzNXDpm84iaLUA0va32ZEicbRU6aXHXqur1icTibxFNTpZicz1G9ShY2h4/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">docker pull minio/minio<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">2. 启动镜像</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">执行命令如下:</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/A7sq8BD8oexqmcDw0weKQRrBQeTR8XD4xmTVRFn6mRqVzNXDpm84iaLUA0va32ZEicbRU6aXHXqur1icTibxFNTpZicz1G9ShY2h4/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"> docker run -p 9000:9000 -p 9001:9001 --name minio -d --restart=always -e <span style="color: #50a14f;line-height: 26px;">"MINIO_ACCESS_KEY=admin"</span> -e <span style="color: #50a14f;line-height: 26px;">"MINIO_SECRET_KEY=admin"</span> -v /home/data:/data -v /home/config:/root/.minio minio/minio server --console-address <span style="color: #50a14f;line-height: 26px;">":9000"</span> --address <span style="color: #50a14f;line-height: 26px;">":9001"</span> /data<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">命令解释如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;list-style-type: disc;color: #f83929;font-size: 16px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <strong style="font-weight: 700;color: rgb(248, 57, 41);">-p</strong>: <strong style="font-weight: 700;color: rgb(248, 57, 41);">9000</strong>是图形界面的端口, <strong style="font-weight: 700;color: rgb(248, 57, 41);">9001</strong>是API的端口,在使用SDK连接需要用到 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <strong style="font-weight: 700;color: rgb(248, 57, 41);">MINIO_ACCESS_KEY</strong>:指定图形界面的用户名 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <strong style="font-weight: 700;color: rgb(248, 57, 41);">MINIO_SECRET_KEY</strong>:指定图形界面的密码 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">按照上述两个步骤启动成功即可。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">3. 图形界面操作</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">安装成功后直接访问地址:<strong style="font-weight: 700;color: rgb(248, 57, 41);">http:/ip:9000/login</strong>,如下:</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.5231481481481481" src="/upload/8b7ceb87651306020f270d96949dd3f0.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">输入用户名和密码登录成功后,如下:</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img src="/upload/5dc47ea5fe1c0941bb3bacc8fc8ee696.png" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;" class="rich_pages wxw-img" data-ratio="0.48055555555555557" data-w="1080"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">菜单很多,这里就不再详细介绍了,笔者这里直接在<strong style="font-weight: 700;color: rgb(248, 57, 41);">Buckets</strong>菜单中创建一个桶为<strong style="font-weight: 700;color: rgb(248, 57, 41);">test</strong>,如下图:</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.3768518518518518" src="/upload/ceb2f2ac36505f642c626f9a2d5ab293.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">并且设置这个桶的隐私规则为<strong style="font-weight: 700;color: rgb(248, 57, 41);">public</strong>,如下:</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.3925925925925926" src="/upload/7a5059f1f1af48f63cbd1b3667895644.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;display: block;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;font-style: normal;padding: 15px 10px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: #353535;font-size: 16px;margin: 0 10px;display: block;">MinIO到此已经安装设置成功了</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 22px;text-align: left;margin: 20px 10px 0px 0px;"><span style="display: none;"></span><span style="font-size: 18px;font-weight: 700;color: #222;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">Spring Boot 整合MinIO 上传文件</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">虽然MinIO在图形界面提供了手动上传的操作,但是也可以通过SDK的方式去上传,下面介绍一下Spring Boot 整合MinIO上传文件。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1. 获取accessKey和secretKey</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">这里的<strong style="font-weight: 700;color: rgb(248, 57, 41);">accessKey</strong>和<strong style="font-weight: 700;color: rgb(248, 57, 41);">secretKey</strong>并不是图形界面登录名和密码,获取很简单,直接在图形界面中操作,如下图:</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.412962962962963" src="/upload/2a946de9b8b507b22b5dd954fd5d831c.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.42685185185185187" src="/upload/441c0ef8048a1c856e022b9ced7b0ab.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">2. 添加依赖</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">添加MinIO的依赖,如下:</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/A7sq8BD8oexqmcDw0weKQRrBQeTR8XD4xmTVRFn6mRqVzNXDpm84iaLUA0va32ZEicbRU6aXHXqur1icTibxFNTpZicz1G9ShY2h4/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="line-height: 26px;"><<span style="color: #e45649;line-height: 26px;">dependency</span>></span><br> <span style="line-height: 26px;"><<span style="color: #e45649;line-height: 26px;">groupId</span>></span>io.minio<span style="line-height: 26px;"></<span style="color: #e45649;line-height: 26px;">groupId</span>></span><br> <span style="line-height: 26px;"><<span style="color: #e45649;line-height: 26px;">artifactId</span>></span>minio<span style="line-height: 26px;"></<span style="color: #e45649;line-height: 26px;">artifactId</span>></span><br> <span style="line-height: 26px;"><<span style="color: #e45649;line-height: 26px;">version</span>></span>8.2.1<span style="line-height: 26px;"></<span style="color: #e45649;line-height: 26px;">version</span>></span><br><span style="line-height: 26px;"></<span style="color: #e45649;line-height: 26px;">dependency</span>></span><br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">3. 添加配置</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">这里笔者对SDK做了简单的封装,案例源码都会提供,下面只列出部分代码。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">在<strong style="font-weight: 700;color: rgb(248, 57, 41);">aplication.yml</strong>配置中添加MInIO相关的配置,如下:</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/A7sq8BD8oexqmcDw0weKQRrBQeTR8XD4xmTVRFn6mRqVzNXDpm84iaLUA0va32ZEicbRU6aXHXqur1icTibxFNTpZicz1G9ShY2h4/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #986801;line-height: 26px;">minio:</span><br> <span style="color: #a0a1a7;font-style: italic;line-height: 26px;"># 访问的url</span><br> <span style="color: #986801;line-height: 26px;">endpoint:</span> <span style="color: #50a14f;line-height: 26px;">http://192.168.47.148</span><br> <span style="color: #a0a1a7;font-style: italic;line-height: 26px;"># API的端口</span><br> <span style="color: #986801;line-height: 26px;">port:</span> <span style="color: #986801;line-height: 26px;">9001</span><br> <span style="color: #a0a1a7;font-style: italic;line-height: 26px;"># 秘钥</span><br> <span style="color: #986801;line-height: 26px;">accessKey:</span> <span style="color: #50a14f;line-height: 26px;">HQGWFYLWGC6FVJ0CQFOG</span><br> <span style="color: #986801;line-height: 26px;">secretKey:</span> <span style="color: #50a14f;line-height: 26px;">pUGhAgQhZDxJaLmN3uz65YX7Bb3FyLdLglBvcCr1</span><br> <span style="color: #986801;line-height: 26px;">secure:</span> <span style="color: #0184bb;line-height: 26px;">false</span><br> <span style="color: #986801;line-height: 26px;">bucket-name:</span> <span style="color: #50a14f;line-height: 26px;">test</span> <span style="color: #a0a1a7;font-style: italic;line-height: 26px;"># 桶名 我这是给出了一个默认桶名</span><br> <span style="color: #986801;line-height: 26px;">image-size:</span> <span style="color: #986801;line-height: 26px;">10485760</span> <span style="color: #a0a1a7;font-style: italic;line-height: 26px;"># 我在这里设定了 图片文件的最大大小</span><br> <span style="color: #986801;line-height: 26px;">file-size:</span> <span style="color: #986801;line-height: 26px;">1073741824</span> <span style="color: #a0a1a7;font-style: italic;line-height: 26px;"># 此处是设定了文件的最大大小</span><br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">4. 新建上传文件接口</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">笔者这里定义了一个上传文件接口,如下:</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/A7sq8BD8oexqmcDw0weKQRrBQeTR8XD4xmTVRFn6mRqVzNXDpm84iaLUA0va32ZEicbRU6aXHXqur1icTibxFNTpZicz1G9ShY2h4/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br> * <span style="color: #a626a4;line-height: 26px;">@author</span> 公众号:码猿技术专栏<br> */</span><br><span style="color: #4078f2;line-height: 26px;">@RequestMapping</span>(<span style="color: #50a14f;line-height: 26px;">"/minio"</span>)<br><span style="color: #4078f2;line-height: 26px;">@RestController</span><br><span style="color: #a626a4;line-height: 26px;">public</span> <span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span> <span style="color: #c18401;line-height: 26px;">MinioController</span> </span>{<br><br> <span style="color: #4078f2;line-height: 26px;">@Autowired</span><br> <span style="color: #a626a4;line-height: 26px;">private</span> MinioService minioService;<br><br> <span style="color: #4078f2;line-height: 26px;">@PostMapping</span>(<span style="color: #50a14f;line-height: 26px;">"/upload"</span>)<br> <span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span> String <span style="color: #4078f2;line-height: 26px;">uploadFile</span><span style="line-height: 26px;">(MultipartFile file, String bucketName)</span> </span>{<br> String fileType = FileTypeUtils.getFileType(file);<br> <span style="color: #a626a4;line-height: 26px;">if</span> (fileType != <span style="color: #a626a4;line-height: 26px;">null</span>) {<br> <span style="color: #a626a4;line-height: 26px;">return</span> minioService.putObject(file, bucketName, fileType);<br> }<br> <span style="color: #a626a4;line-height: 26px;">return</span> <span style="color: #50a14f;line-height: 26px;">"不支持的文件格式。请确认格式,重新上传!!!"</span>;<br> }<br>}<br></code></pre> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;display: block;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;font-style: normal;padding: 15px 10px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: #353535;font-size: 16px;margin: 0 10px;display: block;">源码已经上传GitHub,关注公众号:码猿技术专栏,回复关键词:<strong style="font-weight: 700;color: rgb(248, 57, 41);">9535</strong> 获取!</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">5. 测试</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">上述4个步骤已经整合完成了,下面直接调用接口上传一张图片试一下,如下:</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.4425925925925926" src="/upload/759d0ff08e44168a979ed5a3b78181d6.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">接口返回的<strong style="font-weight: 700;color: rgb(248, 57, 41);">URL</strong>就是文件的访问地址,直接输入浏览器访问即可。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">在MInIO中也可以看到存储的文件,如下图:</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.412962962962963" src="/upload/2bbe6b981aaa76375d9108ea1dda2474.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">如果你需要分享给别人,也可以手动分享,有效期是7天,一旦过了这个有效期将会失效,如下:</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.41203703703703703" src="/upload/b7e4a0c71e566f25c85472bfd2d5d70b.png" data-w="1080" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;display: block;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;font-style: normal;padding: 15px 10px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: #353535;font-size: 16px;margin: 0 10px;display: block;">源码已经上传GitHub,关注公众号:码猿技术专栏,回复关键词:<strong style="font-weight: 700;color: rgb(248, 57, 41);">9535</strong> 获取!</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 22px;text-align: left;margin: 20px 10px 0px 0px;"><span style="display: none;"></span><span style="font-size: 18px;font-weight: 700;color: #222;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">总结</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">MInIO虽然是个开源项目,但是功能非常强大,小型项目中完全可以用它实现对象存储,也可以使用MinIO搭建一个免费的图床。</p> </section>
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;padding: 0px 10px;line-height: 1.6;word-spacing: 0px;letter-spacing: 0px;word-break: break-word;overflow-wrap: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;margin-bottom: 24px;" data-mpa-powered-by="yiban.io"> <p style="text-align: center;margin-bottom: 0em;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.5513608428446005" data-s="300,640" src="/upload/17a2f58c7d48388c7e5819b4f75e2c0f.png" data-type="png" data-w="1139" style=""></p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;"><strong style="font-weight: bold;color: black;"></strong></p> <section class="mp_profile_iframe_wrp"> <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 data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;"><strong style="font-weight: bold;color: black;">大家好,我是苏三。</strong></p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">Spring 事务是复杂一致性业务必备的知识点,掌握好 Spring 事务可以让我们写出更好地代码。这篇文章我们将介绍 Spring 事务的诞生背景,从而让我们可以更清晰地了解 Spring 事务存在的意义。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">接着,我们会介绍如何快速使用 Spring 事务。接着,我们会介绍 Spring 事务的一些特性,从而帮助我们更好地使用 Spring 事务。最后,我们会总结一些 Spring 事务常见的问题,避免大家踩坑。</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6181474480151229" src="/upload/7332334cacf6e14dce036a3819f98675.jpg" data-type="jpeg" data-w="529" style="display: block;margin: 0 auto;max-width: 100%;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> Spring 事务 - 思维导图 </figcaption> </figure> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;letter-spacing: 5px;padding: 20px;color: rgb(0, 13, 131);text-align: center;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/AVWicyZuuClG0n6AKMHDnEu4ibvrJfSFCyHWVQCnf7IcibmKaw8A5ibVjXIoqpiarABUuicfsibzciaK0OevCzpsGicXqAQ/640?wx_fmt=png");background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;background-position: center -10px;background-size: 150px;"><span style="padding-bottom: 12px;border-bottom: 2px solid #000d83;">诞生背景</span></h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">当我们聊起事务的时候,我们需要明白「事务」这个词代表着什么。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">事务其实是一个并发控制单位,是用户定义的一个操作序列,这些操作要么全部完成,要不全部不完成,是一个不可分割的工作单位。事务有 ACID 四个特性,即:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> Atomicity(原子性):事务中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 事务隔离(Isolation):多个事务之间是独立的,不相互影响的。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 </section></li> </ol> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">而我们说的 Spring 事务,其实是事务在 Spring 中的实现。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">明白了什么是事务之后,我们来聊聊:<strong style="font-weight: bold;color: black;">为什么要有 Spring 事务?</strong></p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">为了解释清楚这个问题,我们举个简单的例子:银行里树哥要给小黑转 1000 块钱,这时候会有两个必要的操作:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 将树哥的账户余额减少 1000 元。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 将小黑的账户余额增加 1000 元。 </section></li> </ol> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">这两个操作,要么一起都完成,要么都不完成。如果其中某个成功,另外一个失败,那么就会出现严重的问题。<strong style="font-weight: bold;color: black;">而我们要保证这个操作的原子性,就必须通过 Spring 事务来完成,这就是 Spring 事务存在的原因。</strong></p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">如果你深入了解过 MySQL 事务,那么你应该知道:MySQL 默认情况下,对于所有的单条语句都作为一个单独的事务来执行。我们要使用 MySQL 事务的时候,可以通过手动提交事务来控制事务范围。<strong style="font-weight: bold;color: black;">Spring 事务的本质,其实就是通过 Spring AOP 切面技术,在合适的地方开启事务,接着在合适的地方提交事务或回滚事务,从而实现了业务编程层面的事务操作。</strong></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;letter-spacing: 5px;padding: 20px;color: rgb(0, 13, 131);text-align: center;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/AVWicyZuuClG0n6AKMHDnEu4ibvrJfSFCyHWVQCnf7IcibmKaw8A5ibVjXIoqpiarABUuicfsibzciaK0OevCzpsGicXqAQ/640?wx_fmt=png");background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;background-position: center -10px;background-size: 150px;"><span style="padding-bottom: 12px;border-bottom: 2px solid #000d83;">使用指南</span></h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">Spring 事务支持两种使用方式,分别是:声明式事务(注解方式)、编程式事务(代码方式)。一般来说,我们使用声明式事务比较多,这里我们就演示声明式事务的使用方法。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;">项目准备</h3> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">为了较好地进行讲解,我们需要搭建一个具备数据库 CURD 功能的项目,并创建 tablea 和 tableb 两张表。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">首先,创建 tablea 和 tableb 两张表,两张表都只有 id 和 name 两列,建表语句如下图所示。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">CREATE TABLE `tablea` (<br> `id` int NOT NULL AUTO_INCREMENT,<br> `name` varchar(45) DEFAULT NULL,<br> PRIMARY KEY (`id`)<br>) ENGINE=InnoDB AUTO_INCREMENT=1;<br>CREATE TABLE `tableb` (<br> `id` int NOT NULL AUTO_INCREMENT,<br> `name` varchar(45) DEFAULT NULL,<br> PRIMARY KEY (`id`)<br>) ENGINE=InnoDB AUTO_INCREMENT=1;<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">接着,创建一个 SpringBoot 项目,随后加入 MyBatis 及 MySQL 的 POM 依赖。</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><dependency><br> <groupId>mysql</groupId><br> <artifactId>mysql-connector-java</artifactId><br></dependency><br><dependency><br> <groupId>org.mybatis.spring.boot</groupId><br> <artifactId>mybatis-spring-boot-starter</artifactId><br> <version>2.1.0</version><br></dependency><br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">最后,我们创建对应的 controller 接口、service 接口、mapper 接口,代码如下所示。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">创建 controller 接口:</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@SpringBootApplication<br>@RestController<br>@RequestMapping(<span style="color: #98c379;line-height: 26px;">"/api"</span>)<br>public class SpringTransactionController {<br><br> @Autowired<br> private TransactionServiceA transactionServiceA;<br><br> @RequestMapping(<span style="color: #98c379;line-height: 26px;">"/spring-transaction"</span>)<br> public String <span style="color: rgb(97, 174, 238);line-height: 26px;">testTransaction</span>() {<br> transactionServiceA.methodA();<br> <span style="color: #e6c07b;line-height: 26px;">return</span> <span style="color: #98c379;line-height: 26px;">"SUCCESS"</span>;<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">创建 TableService 接口。</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">public interface TableService {<br> void insertTableA(TableEntity tableEntity);<br> void insertTableB(TableEntity tableEntity);<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">创建 Service 接口实现类 TransactionServiceA 类,在 methodA () 方法中先往 tablea 表格插入一条数据,随后会调用 TransactionServiceB 服务的 methodB () 方法。</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Service<br>public class TransactionServiceA {<br><br> @Autowired<br> private TableService tableService;<br><br> @Autowired<br> private TransactionServiceB transactionServiceB;<br><br> public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodA</span>(){<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"methodA"</span>);<br> tableService.insertTableA(new TableEntity());<br> transactionServiceB.methodB();<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">创建 TransactionServiceB 类实现,在 methodB () 方法中往 tableb 表格插入一条数据。</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Service<br>public class TransactionServiceB {<br><br> @Autowired<br> private TableService tableService;<br><br> public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodB</span>(){<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"methodB"</span>);<br> tableService.insertTableB(new TableEntity());<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">创建 Mapper 接口方法:</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Mapper<br>public interface TableMapper {<br> @Insert(<span style="color: #98c379;line-height: 26px;">"INSERT INTO tablea(id, name) "</span> +<br> <span style="color: #98c379;line-height: 26px;">"VALUES(#{id}, #{name})"</span>)<br> @Options(useGeneratedKeys = <span style="color: #56b6c2;line-height: 26px;">true</span>, keyProperty = <span style="color: #98c379;line-height: 26px;">"id"</span>)<br> void insertTableA(TableEntity tableEntity);<br><br> @Insert(<span style="color: #98c379;line-height: 26px;">"INSERT INTO tableb(id, name) "</span> +<br> <span style="color: #98c379;line-height: 26px;">"VALUES(#{id}, #{name})"</span>)<br> @Options(useGeneratedKeys = <span style="color: #56b6c2;line-height: 26px;">true</span>, keyProperty = <span style="color: #98c379;line-height: 26px;">"id"</span>)<br> void insertTableB(TableEntity tableEntity);<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">数据库表对应的 TableEntity:</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Data<br>public class TableEntity {<br> private static final long serialVersionUID = 1L;<br><br> private Long id;<br><br> private String name;<br><br> public <span style="color: rgb(97, 174, 238);line-height: 26px;">TableEntity</span>() {<br> }<br><br> public TableEntity(String name) {<br> this.name = name;<br> }<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">最后,我们在配置文件中配置好数据库地址:</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">spring:<br> datasource:<br> url: jdbc:mysql://localhost:3306/<span style="color: #e6c07b;line-height: 26px;">test</span>?serverTimezone=UTC&useUnicode=<span style="color: #56b6c2;line-height: 26px;">true</span>&characterEncoding=utf-8&useSSL=<span style="color: #56b6c2;line-height: 26px;">true</span><br> username: root<br> password: root<br> driver-class-name: com.mysql.cj.jdbc.Driver<br><span style="color: #5c6370;font-style: italic;line-height: 26px;"># MyBatis 配置</span><br>mybatis:<br> <span style="color: #e6c07b;line-height: 26px;">type</span>-aliases-package: tech.shuyi.javacodechip.spring_transaction.model<br> configuration:<br> map-underscore-to-camel-case: <span style="color: #56b6c2;line-height: 26px;">true</span><br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">最后,我们运行 SpringBoot 项目。通过浏览器访问地址:localhost:8080/api/spring-transaction,正常的话应该是接口请求成功。</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.30148619957537154" src="/upload/67ff45cd33f2d59a82a03a17c403a711.jpg" data-type="jpeg" data-w="471" style="display: block;margin: 0 auto;max-width: 100%;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">查看数据库表,会看到 tablea 和 tableb 都插入了一条数据。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;"><strong style="font-weight: bold;color: black;">到这里,我们用于测试 Spring 事务的 Demo 就准备完毕了!</strong></p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;">快速入门</h3> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">使用声明式事务的方法很简单,其实就是在 Service 层对应方法上配置 <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">@Transaction</code> 注解即可。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">假设我们的业务需求是:往 tablea 和 tableb 插入的数据,要么都完成,要么都不完成。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">这时候,我们应该怎么操作呢?</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">首先,我们需要在 TransactionServiceA 类的 methodA () 方法上配置 <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">@Transaction</code> 注解,同时也在 TransactionServiceB 类的 methodB () 方法上配置 <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;color: #1e6bb8;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;">@Transaction</code> 注解。修改之后的 TransactionServiceA 和 TransactionServiceB 代码如下所示。</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">// TransactionServiceA<br>@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodA</span>(){<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"methodA"</span>);<br> tableService.insertTableA(new TableEntity());<br> transactionServiceB.methodB();<br>}<br>// TransactionServiceB<br>@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodB</span>(){<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"methodB"</span>);<br> tableService.insertTableB(new TableEntity());<br> throw new RuntimeException();<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">可以看到,我们在 methodB () 中模拟了业务异常,我们看看是否 tablea 和 tableb 都没有插入数据。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">修改之后重新启动项目,此时我们继续访问地址:localhost:8080/api/spring-transaction,我们会发现执行错误,并且控制台也报错了。</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.2859237536656892" src="/upload/69f33b71efecee56b48f3f66a56351af.jpg" data-type="jpeg" data-w="682" style="display: block;margin: 0 auto;max-width: 100%;"> </figure> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.2906091370558376" src="/upload/ca1f5628910a37f9435954c6bf104f78.jpg" data-type="jpeg" data-w="788" style="display: block;margin: 0 auto;max-width: 100%;"> </figure> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">这时候我们查看数据库,会发现 tablea 和 tableb 都没有插入数据。这说明事务起作用了。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;letter-spacing: 5px;padding: 20px;color: rgb(0, 13, 131);text-align: center;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/AVWicyZuuClG0n6AKMHDnEu4ibvrJfSFCyHWVQCnf7IcibmKaw8A5ibVjXIoqpiarABUuicfsibzciaK0OevCzpsGicXqAQ/640?wx_fmt=png");background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;background-position: center -10px;background-size: 150px;"><span style="padding-bottom: 12px;border-bottom: 2px solid #000d83;">事务传播类型</span></h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">事务传播类型,指的是事务与事务之间的交互策略。例如:在事务方法 A 中调用事务方法 B,当事务方法 B 失败回滚时,事务方法 A 应该如何操作?这就是事务传播类型。Spring 事务中定义了 7 种事务传播类型,分别是:REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。其中最常用的只有 3 种,即:REQUIRED、REQUIRES_NEW、NESTED。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">针对事务传播类型,我们要弄明白的是 4 个点:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 子事务与父事务的关系,是否会启动一个新的事务? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 子事务异常时,父事务是否会回滚? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 父事务异常时,子事务是否会回滚? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 父事务捕捉异常后,父事务是否还会回滚? </section></li> </ol> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;">REQUIRED</h3> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">REQUIRED 是 Spring 默认的事务传播类型,该传播类型的特点是:<strong style="font-weight: bold;color: black;">当前方法存在事务时,子方法加入该事务。此时父子方法共用一个事务,无论父子方法哪个发生异常回滚,整个事务都回滚。即使父方法捕捉了异常,也是会回滚。而当前方法不存在事务时,子方法新建一个事务。</strong> 为了验证 REQUIRED 事务传播类型的特点,我们来做几个测试。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">还是上面 methodA 和 methodB 的例子。当 methodA 不开启事务,methodB 开启事务,这时候 methodB 就是独立的事务,而 methodA 并不在事务之中。因此当 methodB 发生异常回滚时,methodA 中的内容就不会被回滚。用如下的代码就可以验证我们所说的。</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodA</span>(){<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"methodA"</span>);<br> tableService.insertTableA(new TableEntity());<br> transactionServiceB.methodB();<br>}<br><br>@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodB</span>(){<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"methodB"</span>);<br> tableService.insertTableB(new TableEntity());<br> throw new RuntimeException();<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">最终的结果是:tablea 插入了数据,tableb 没有插入数据,符合了我们的猜想。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">当 methodA 开启事务,methodB 也开启事务。按照我们的结论,此时 methodB 会加入 methodA 的事务。此时,我们验证当父子事务分别回滚时,另外一个事务是否会回滚。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">我们先验证第一个:当父方法事务回滚时,子方法事务是否会回滚?</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodA</span>(){<br> tableService.insertTableA(new TableEntity());<br> transactionServiceB.methodB();<br> throw new RuntimeException();<br>}<br><br>@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodB</span>(){<br> tableService.insertTableB(new TableEntity());<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">结果是:talbea 和 tableb 都没有插入数据,即:父事务回滚时,子事务也回滚了。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">我们继续验证第二个:当子方法事务回滚时,父方法事务是否会回滚?</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodA</span>(){<br> tableService.insertTableA(new TableEntity());<br> transactionServiceB.methodB();<br>}<br><br>@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodB</span>(){<br> tableService.insertTableB(new TableEntity());<br> throw new RuntimeException();<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">结果是:talbea 和 tableb 都没有插入数据,即:子事务回滚时,父事务也回滚了。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">我们继续验证第三个:当字方法事务回滚时,父方法捕捉了异常,父方法事务是否会回滚?</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodA</span>() {<br> tableService.insertTableA(new TableEntity());<br> try {<br> transactionServiceB.methodB();<br> } catch (Exception e) {<br> System.out.println(<span style="color: #98c379;line-height: 26px;">"methodb occur exp."</span>);<br> }<br>}<br> <br>@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodB</span>() {<br> tableService.insertTableB(new TableEntity());<br> throw new RuntimeException();<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">结果是:talbea 和 tableb 都没有插入数据,即:子事务回滚时,父事务也回滚了。所以说,这也进一步验证了我们之前所说的:REQUIRED 传播类型,它是父子方法共用同一个事务的。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;">REQUIRES_NEW</h3> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">REQUIRES_NEW 也是常用的一个传播类型,该传播类型的特点是:<strong style="font-weight: bold;color: black;">无论当前方法是否存在事务,子方法都新建一个事务。此时父子方法的事务时独立的,它们都不会相互影响。但父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。</strong> 为了验证 REQUIRES_NEW 事务传播类型的特点,我们来做几个测试。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">首先,我们来验证一下:当父方法事务发生异常时,子方法事务是否会回滚?</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodA</span>(){<br> tableService.insertTableA(new TableEntity());<br> transactionServiceB.methodB();<br> throw new RuntimeException();<br>}<br> @Transactional(propagation = Propagation.REQUIRES_NEW)<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodB</span>(){<br> tableService.insertTableB(new TableEntity());<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">结果是:tablea 没有插入数据,tableb 插入了数据,即:父方法事务回滚了,但子方法事务没回滚。这可以证明父子方法的事务是独立的,不相互影响。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">下面,我们来看看:当子方法事务发生异常时,父方法事务是否会回滚?</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodA</span>(){<br> tableService.insertTableA(new TableEntity());<br> transactionServiceB.methodB();<br>}<br> @Transactional(propagation = Propagation.REQUIRES_NEW)<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodB</span>(){<br> tableService.insertTableB(new TableEntity());<br> throw new RuntimeException();<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">结果是:tablea 没有插入了数据,tableb 没有插入数据。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">从这个结果来看,貌似是子方法事务回滚,导致父方法事务也回滚了。但我们不是说父子事务都是独立的,不会相互影响么?怎么结果与此相反呢?</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">其实是因为子方法抛出了异常,而父方法并没有做异常捕捉,此时父方法同时也抛出异常了,于是 Spring 就会将父方法事务也回滚了。如果我们在父方法中捕捉异常,那么父方法的事务就不会回滚了,修改之后的代码如下所示。</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodA</span>(){<br> tableService.insertTableA(new TableEntity());<br> // 捕捉异常<br> try {<br> transactionServiceB.methodB();<br> } catch (Exception e) {<br> e.printStackTrace();<br> }<br>}<br> @Transactional(propagation = Propagation.REQUIRES_NEW)<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodB</span>(){<br> tableService.insertTableB(new TableEntity());<br> throw new RuntimeException();<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">结果是:tablea 插入了数据,tableb 没有插入数据。这正符合我们刚刚所说的:父子事务是独立的,并不会相互影响。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">这其实就是我们上面所说的:父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。因为如果执行过程中发生 RuntimeException 异常和 Error 的话,那么 Spring 事务是会自动回滚的。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;">NESTED</h3> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">NESTED 也是常用的一个传播类型,该方法的特性与 REQUIRED 非常相似,其特性是:<strong style="font-weight: bold;color: black;">当前方法存在事务时,子方法加入在嵌套事务执行。当父方法事务回滚时,子方法事务也跟着回滚。当子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。如果捕捉了异常,那么就不回滚,否则回滚。</strong></p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">可以看到 NESTED 与 REQUIRED 的区别在于:父方法与子方法对于共用事务的描述是不一样的,REQUIRED 说的是共用同一个事务,而 NESTED 说的是在嵌套事务执行。这一个区别的具体体现是:<strong style="font-weight: bold;color: black;">在子方法事务发生异常回滚时,父方法有着不同的反应动作。</strong></p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">对于 REQUIRED 来说,无论父子方法哪个发生异常,全都会回滚。而 NESTED 则是:父方法发生异常回滚时,子方法事务会回滚。而子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">为了验证 NESTED 事务传播类型的特点,我们来做几个测试。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">首先,我们来验证一下:当父方法事务发生异常时,子方法事务是否会回滚?</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodA</span>() {<br> tableService.insertTableA(new TableEntity());<br> transactionServiceB.methodB();<br> throw new RuntimeException();<br>}<br>@Transactional(propagation = Propagation.NESTED)<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodB</span>() {<br> tableService.insertTableB(new TableEntity());<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">结果是:tablea 和 tableb 都没有插入数据,即:父子方法事务都回滚了。这说明父方法发送异常时,子方法事务会回滚。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">接着,我们继续验证一下:当子方法事务发生异常时,如果父方法没有捕捉异常,父方法事务是否会回滚?</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodA</span>() {<br> tableService.insertTableA(new TableEntity());<br> transactionServiceB.methodB();<br>}<br>@Transactional(propagation = Propagation.NESTED)<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodB</span>() {<br> tableService.insertTableB(new TableEntity());<br> throw new RuntimeException();<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">结果是:tablea 和 tableb 都没有插入数据,即:父子方法事务都回滚了。这说明子方法发送异常回滚时,如果父方法没有捕捉异常,那么父方法事务也会回滚。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">最后,我们验证一下:当子方法事务发生异常时,如果父方法捕捉了异常,父方法事务是否会回滚?</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Transactional<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodA</span>() {<br> tableService.insertTableA(new TableEntity());<br> try {<br> transactionServiceB.methodB();<br> } catch (Exception e) {<br> <br> }<br>}<br>@Transactional(propagation = Propagation.NESTED)<br>public void <span style="color: rgb(97, 174, 238);line-height: 26px;">methodB</span>() {<br> tableService.insertTableB(new TableEntity());<br> throw new RuntimeException();<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">结果是:tablea 插入了数据,tableb 没有插入数据,即:父方法事务没有回滚,子方法事务回滚了。这说明子方法发送异常回滚时,如果父方法捕捉了异常,那么父方法事务就不会回滚。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">看到这里,相信大家已经对 REQUIRED、REQUIRES_NEW 和 NESTED 这三个传播类型有了深入的理解了。最后,让我们来总结一下:</p> <section data-tool="mdnice编辑器" style="overflow-x: auto;"> <table> <thead> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <th style="font-size: 16px;border-width: 1px;border-style: solid;border-color: rgb(204, 204, 204);padding: 5px 10px;font-weight: bold;background-color: rgb(240, 240, 240);min-width: 85px;text-align: center;">事务传播类型</th> <th style="font-size: 16px;border-width: 1px;border-style: solid;border-color: rgb(204, 204, 204);padding: 5px 10px;font-weight: bold;background-color: rgb(240, 240, 240);min-width: 85px;text-align: center;">特性</th> </tr> </thead> <tbody style="border-width: 0px;border-style: initial;border-color: initial;"> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="font-size: 16px;border-width: 1px;border-style: solid;border-color: rgb(204, 204, 204);padding: 5px 10px;min-width: 85px;text-align: center;">REQUIRED</td> <td style="font-size: 16px;border-width: 1px;border-style: solid;border-color: rgb(204, 204, 204);padding: 5px 10px;min-width: 85px;text-align: center;">当前方法存在事务时,子方法加入该事务。此时父子方法共用一个事务,无论父子方法哪个发生异常回滚,整个事务都回滚。即使父方法捕捉了异常,也是会回滚。而当前方法不存在事务时,子方法新建一个事务。</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"> <td style="font-size: 16px;border-width: 1px;border-style: solid;border-color: rgb(204, 204, 204);padding: 5px 10px;min-width: 85px;text-align: center;">REQUIRES_NEW</td> <td style="font-size: 16px;border-width: 1px;border-style: solid;border-color: rgb(204, 204, 204);padding: 5px 10px;min-width: 85px;text-align: center;">无论当前方法是否存在事务,子方法都新建一个事务。此时父子方法的事务时独立的,它们都不会相互影响。但父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。</td> </tr> <tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"> <td style="font-size: 16px;border-width: 1px;border-style: solid;border-color: rgb(204, 204, 204);padding: 5px 10px;min-width: 85px;text-align: center;">NESTED</td> <td style="font-size: 16px;border-width: 1px;border-style: solid;border-color: rgb(204, 204, 204);padding: 5px 10px;min-width: 85px;text-align: center;">当前方法存在事务时,子方法加入在嵌套事务执行。当父方法事务回滚时,子方法事务也跟着回滚。当子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。如果捕捉了异常,那么就不回滚,否则回滚。</td> </tr> </tbody> </table> </section> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;letter-spacing: 5px;padding: 20px;color: rgb(0, 13, 131);text-align: center;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/AVWicyZuuClG0n6AKMHDnEu4ibvrJfSFCyHWVQCnf7IcibmKaw8A5ibVjXIoqpiarABUuicfsibzciaK0OevCzpsGicXqAQ/640?wx_fmt=png");background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;background-position: center -10px;background-size: 150px;"><span style="padding-bottom: 12px;border-bottom: 2px solid #000d83;"> 应该怎么用?</span></h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">看完了事务的传播类型,我们对 Spring 事务又有了深刻的理解。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">看到这里,你应该也明白:使用事务,不再是简单地使用 @Transaction 注解就可以,还需要根据业务场景,选择合适的传播类型。那么我们再升华一下使用 Spring 事务的方法论。一般来说,使用 Spring 事务的步骤为:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 根据业务场景,分析要达成的事务效果,确定使用的事务传播类型。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 在 Service 层使用 @Transaction 注解,配置对应的 propogation 属性。 </section></li> </ol> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">下次遇到要使用事务的情况,记得按照这样的步骤去做哦~</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;letter-spacing: 5px;padding: 20px;color: rgb(0, 13, 131);text-align: center;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/AVWicyZuuClG0n6AKMHDnEu4ibvrJfSFCyHWVQCnf7IcibmKaw8A5ibVjXIoqpiarABUuicfsibzciaK0OevCzpsGicXqAQ/640?wx_fmt=png");background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;background-position: center -10px;background-size: 150px;"><span style="padding-bottom: 12px;border-bottom: 2px solid #000d83;">Spring 事务失效</span></h2> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 什么时候 Spring 事务会失效? </section></li> </ol> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有 @Transactional 注解的方法的事务会失效。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">这是由于 Spring AOP 代理的原因造成的,因为只有当 @Transactional 注解的方法在类以外被调用的时候,Spring 事务管理才生效。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">另外,如果直接调用,不通过对象调用,也是会失效的。因为 Spring 事务是通过 AOP 实现的。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">@Transactional 注解只有作用到 public 方法上事务才生效。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">被 @Transactional 注解的方法所在的类必须被 Spring 管理。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">底层使用的数据库必须支持事务机制,否则不生效。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;letter-spacing: 5px;padding: 20px;color: rgb(0, 13, 131);text-align: center;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/AVWicyZuuClG0n6AKMHDnEu4ibvrJfSFCyHWVQCnf7IcibmKaw8A5ibVjXIoqpiarABUuicfsibzciaK0OevCzpsGicXqAQ/640?wx_fmt=png");background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;background-position: center -10px;background-size: 150px;"><span style="padding-bottom: 12px;border-bottom: 2px solid #000d83;">彩蛋</span></h2> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;color: black;line-height: 2em;">Spring 事务执行过程中,如果抛出非 RuntimeException 和非 Error 错误的其他异常,那么是不会回滚的哦。例如下面的代码执行后,tablea 和 tableb 两个表格,都会插入一条数据。</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: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">@Transactional<br>public void methodA() throws Exception {<br> tableService.insertTableA(new TableEntity());<br> transactionServiceB.methodB();<br>}<br>@Transactional<br>public void methodB() throws Exception {<br> tableService.insertTableB(new TableEntity());<br> // 非 RuntimeException<br> throw new Exception();<br>}<br></code></pre> <h2 data-tool="mdnice编辑器" style="line-height: 2em;"><span style="color: rgb(214, 214, 214);">参考资料</span></h2> <ul data-tool="mdnice编辑器" class="list-paddingleft-1" style="list-style-type: disc;"> <li style="color: rgb(214, 214, 214);"><p style="line-height: 2em;"><span style="color: rgb(214, 214, 214);">咱们从头到尾说一次 Spring 事务管理(器) - SegmentFault 思否</span></p></li> <li style="color: rgb(214, 214, 214);"><p style="line-height: 2em;"><span style="color: rgb(214, 214, 214);">【技术干货】Spring 事务原理一探 - 知乎</span></p></li> <li style="color: rgb(214, 214, 214);"><p style="line-height: 2em;"><span style="color: rgb(214, 214, 214);">Spring 事务详解 | JavaGuide</span></p></li> <li style="color: rgb(214, 214, 214);"><p style="line-height: 2em;"><span style="color: rgb(214, 214, 214);">事务之六:spring 嵌套事务 - duanxz - 博客园</span></p></li> <li style="color: rgb(214, 214, 214);"><p style="line-height: 2em;"><span style="color: rgb(214, 214, 214);">例子很详细,不错!VIP!NESTED 区别!spring 事务传播行为详解 - 双间 - 博客园</span></p></li> <li style="color: rgb(214, 214, 214);"><p style="line-height: 2em;"><span style="color: rgb(214, 214, 214);">Spring Boot 实战 —— MyBatis(注解版)使用方法 | Michael 翔</span></p></li> <li style="color: rgb(214, 214, 214);"><p style="line-height: 2em;"><span style="color: rgb(214, 214, 214);">记一次事务的坑 Transaction rolled back because it has been marked as rollback-only - 云扬四海</span></p></li> </ul> </section> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="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>
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;padding: 0px 10px;line-height: 1.6;word-spacing: 0px;letter-spacing: 0px;word-break: break-word;overflow-wrap: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;" data-mpa-powered-by="yiban.io"> <section class="channels_iframe_wrp"> <mpvideosnap class="js_uneditable custom_select_card channels_iframe videosnap_video_iframe" data-pluginname="videosnap" data-id="export/UzFfAgtgekIEAQAAAAAAEiMtlniHyQAAAAstQy6ubaLX4KHWvLEZgBPE26I8XBIQQtSCzNPgMItRdpPf3krfaBHhXnd5AafD" data-url="https://findermp.video.qq.com/251/20304/stodownload?encfilekey=S7s6ianIic0ia4PicKJSfB8EjyjpQibPUAXolTzdq9fpcia0mOy0OeVWJnwscfKzTfgzkT7tPjMOAIT8bBUY4nuzuGljyKnpgkny4V8GZCP4X4PljZEK8uRJlYmA&adaptivelytrans=0&bizid=1023&dotrans=0&hy=SH&idx=1&m=&scene=0&token=AxricY7RBHdVRZBb1ZdAn2wgp5l78b57vXkensNUSZkBU5DzaLz7BdgibS0ibq3ZCL2YUW8XicmOeGs" data-headimgurl="http://wx.qlogo.cn/finderhead/Q3auHgzwzM5nv7YHhmhvPsGGX04JCIgibK2x2Ru0TOY9HeZTGSIL1KQ/0" data-username="v2_060000231003b20faec8c5e08a1fc3d5c807ec30b07756771265bc6b6234fb9e05062ae69ab4@finder" data-nickname="儒猿IT" data-desc="CAS机制不仅是Java面试中会高频出现的面试题,而且也是高并发实践中必须掌握的知识点。如果你对CAS还不甚了解,这个视频一定值得你花时间学习一下#并发#面试@微信时刻 " data-nonceid="8132951973286499732" data-type="video" data-width="1920" data-height="1080"></mpvideosnap> </section> <p style="text-align: right;"><span style="margin: 0px;padding: 0px;outline: none;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;letter-spacing: 0.544px;orphans: 2;text-align: right;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);background: padding-box padding-box rgb(255, 255, 255);transition: color 0.3s ease 0s;cursor: pointer;touch-action: manipulation;white-space: pre-wrap;color: rgb(136, 136, 136);font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 13px;font-weight: 700;visibility: visible;"></span><span style="color: rgb(136, 136, 136);font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 13px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 700;letter-spacing: 0.544px;orphans: 2;text-align: right;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;display: inline !important;float: none;">文章来源:【公众号:</span><span style="color: rgb(136, 136, 136);font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 13px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 700;letter-spacing: 0.544px;orphans: 2;text-align: right;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;display: inline !important;float: none;">架构染色</span><span style="color: rgb(136, 136, 136);font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 13px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 700;letter-spacing: 0.544px;orphans: 2;text-align: right;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;display: inline !important;float: none;">】</span></p> <p><br></p> <p><strong><br></strong></p> <p><strong>目录</strong></p> <p>1、开篇</p> <h3 data-tool="mdnice编辑器">2、为什么需要全链路监控</h3> <h3 data-tool="mdnice编辑器">3、为什么选择Skywalking</h3> <h3 data-tool="mdnice编辑器">4、预研阶段</h3> <h3 data-tool="mdnice编辑器">5、POC阶段</h3> <h3 data-tool="mdnice编辑器">6、推广和优化阶段</h3> <p><br></p> <p><br></p> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-mpa-template="t" mpa-from-tpl="t"> <blockquote style="margin: 5px auto;padding: 0px;max-width: 100%;border-width: 0px;border-style: none;border-color: rgb(131, 187, 239);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);line-height: 25.6px;color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto;padding: 0px;max-width: 100%;border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto;padding: 0px;max-width: 100%;border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <blockquote style="margin: 5px auto;padding: 0px;max-width: 100%;border-width: 0px;border-style: none;border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto;padding: 0px;max-width: 100%;text-align: center;border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto -38px;padding: 0px 15px;max-width: 100%;display: inline-block;background-color: rgb(131, 187, 239);border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <p style="margin-top: 5px;margin-bottom: 5px;max-width: 100%;clear: both;min-height: 1em;color: rgb(131, 187, 239);border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="font-family: Arial, Helvetica, sans-serif;"><strong mpa-from-tpl="t"><span style="border-color: rgb(131, 187, 239);color: rgb(255, 255, 255);font-size: 16px;" mpa-is-content="t">1、开篇</span></strong></span></p> </section> <section style="margin: -40px 25px 50px;padding: 0px;max-width: 100%;border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 3px 0px 0px;padding: 10px 0px;max-width: 100%;box-sizing: border-box;display: inline-block;width: 495px;color: rgb(131, 187, 239);float: left;border-color: rgb(131, 187, 239);overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 1em 0px 0px;padding: 0px;max-width: 100%;box-sizing: border-box;border-width: 0px;border-style: none;border-color: rgb(131, 187, 239);clear: both;color: rgb(131, 187, 239);overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px 0px -3px;padding: 0px;max-width: 100%;float: right;border-color: rgb(131, 187, 239);width: 6px;border-radius: 50%;background-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;height: 6px !important;" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> <section style="margin: 0px 0px -2px;padding: 0px;max-width: 100%;text-align: left;border-color: rgb(131, 187, 239);width: 6px;border-radius: 50%;background-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;height: 6px !important;" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> <section style="margin: -20px 0px 0px;padding: 0px;max-width: 100%;box-sizing: border-box;text-decoration: inherit;color: rgb(131, 187, 239);border-color: rgb(131, 187, 239);overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px;padding: 0px;max-width: 100%;box-sizing: border-box;border-top: 1px solid rgb(131, 187, 239);width: 495px;float: left;border-right-color: rgb(131, 187, 239);border-bottom-color: rgb(131, 187, 239);border-left-color: rgb(131, 187, 239);color: rgb(131, 187, 239);overflow-wrap: break-word !important;" mpa-from-tpl="t"> <br mpa-from-tpl="t"> </section> </section> </section> </section> </section> </section> </blockquote> </section> </section> </blockquote> </section> </section> <p><span style="letter-spacing: 0px;">自</span><span style="letter-spacing: 0px;">从Skywaling开始在公司推广,时不时会在排查问题的人群中听到这样的话:</span><span style="letter-spacing: 0px;">“你咋还没接Skywalking?</span><span style="letter-spacing: 0px;">接入后,一眼就看出是哪儿的问题了...",正如同事所说的,在许多情况下,Skywalking就是这么秀。</span><span style="letter-spacing: 0px;">作为实践者,我非常感谢Skywalking,因为这款国产全链路监控产品给公司的伙伴们带来了实实在在的帮助;</span><span style="letter-spacing: 0px;">也特别感谢公司的领导和同事们,正因为他们的支持和帮助,才让这套Skywalking系统从起初的有用进化到现在的好用;</span><strong style="letter-spacing: 0px;">从几十亿的Segment储能上限、<strong>几十秒的查询耗时</strong>,优化到千亿级的Segment储能、毫秒级的查询耗时</strong><span style="letter-spacing: 0px;">。</span></p> <p><span style="letter-spacing: 0px;"><br></span></p> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-mpa-category="模板" style="width: 100%;padding: 0 15px;" data-mid="" mpa-from-tpl="t"> <section style="width: 100%;padding: 15px 17px 20px;background: #ebf4ff;font-size: 14px;font-weight: 400;color: #6273aa;line-height: 20px;" data-mid="" mpa-from-tpl="t"> <p data-mid="" mpa-is-content="t">小提示:Segment是Skywalking中提出的概念,表示一次请求在某个服务内的执行链路片段的合集,一个请求在多个服务中先后产生的Segment串起来构成一个完整的Trace,如下图所示:</p> </section> <section style="width: 100%;display: flex;justify-content: flex-end;align-items: flex-start;padding-right: 5px;" data-mid="" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t" style="width: 23px;height: 27px;background: url("https://mmbiz.qpic.cn/mmbiz_png/dLHvdVqeWINyJQzMVmQzxIcFqDsaiaK6ATCCANPe11FbZKCApfz0TbDtfc5gfSQNgJpic86YejM6qcNU5gubeetQ/640?wx_fmt=png") center center / contain no-repeat;margin-top: -27px;z-index: 10;"> <br> </section> </section> <section style="width: 100%;display: flex;justify-content: flex-start;align-items: flex-start;" data-mid="" mpa-from-tpl="t"> <section style="width: 52.3%;height: 8px;background: #6273AA;margin-top: -6px;" data-mid="" mpa-from-tpl="t"> <br> </section> <section style="width: 10px;height: 8px;background: #6273AA;transform: skew(35deg);margin-left: -5px;margin-top: -6px;" data-mid="" mpa-from-tpl="t"> <br> </section> </section> <section style="width: 100%;display: flex;justify-content: flex-end;align-items: flex-start;" data-mid="" mpa-from-tpl="t"> <section style="width: 10px;height: 3px;background: #6273AA;transform: skew(35deg);margin-right: -5px;margin-top: -3px;" data-mid="" mpa-from-tpl="t"> <br> </section> <section style="width: 42.2%;height: 3px;background: #6273AA;margin-top: -3px;" data-mid="" mpa-from-tpl="t"> <br> </section> </section> </section> </section> <p><br mpa-from-tpl="t"></p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;"><img class="rich_pages wxw-img" data-ratio="0.3870967741935484" src="/upload/fa034afadda2709a582ec6f02a70686c.png" data-type="png" data-w="1240" style="letter-spacing: 0px;height: auto !important;"></p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;">Skywalking的这次实践,截止到现在有一年多的时间,回顾总结一下这段历程中的些许积累和收获,愿能反哺社区,给有需求的道友提供个案例借鉴;也希望能收获到专家们的指导建议,把项目做得更好。因为某某约束吧,把有些内容先和谐掉,但也努力把这段历程中那些<strong style="font-weight: bold;color: black;">靓丽的风景,</strong>尽可能完整的呈现给大家。</p> <p data-tool="mdnice编辑器" style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;"><br></p> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-mpa-template="t" mpa-from-tpl="t"> <blockquote style="margin: 5px auto;padding: 0px;max-width: 100%;border-width: 0px;border-style: none;border-color: rgb(131, 187, 239);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);line-height: 25.6px;color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto;padding: 0px;max-width: 100%;border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto;padding: 0px;max-width: 100%;border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <blockquote style="margin: 5px auto;padding: 0px;max-width: 100%;border-width: 0px;border-style: none;border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto;padding: 0px;max-width: 100%;text-align: center;border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <section style="margin: 0px auto -38px;padding: 0px 15px;max-width: 100%;display: inline-block;background-color: rgb(131, 187, 239);border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;" mpa-from-tpl="t"> <p style="margin-top: 5px;margin-bottom: 5px;max-width: 100%;clear: both;min-height: 1em;color: rgb(131, 187, 239);border-color: rgb(131, 187, 239);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="font-family: Arial, Helvetica, sans-serif;"><strong mpa-from-tpl="t"><span style="border-
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="color: black;line-height: 1.6;word-spacing: 0px;letter-spacing: 0px;word-break: break-word;word-wrap: break-word;text-align: left;font-size: 14px;padding: 10px;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;margin: 0;line-height: 26px;color: black;font-size: 14px;">大家好,我是老三,之前在 <a href="https://mp.weixin.qq.com/s?__biz=MzkwODE5ODM0Ng==&mid=2247491980&idx=1&sn=22c357da998773d57115d71c3f5708c3&scene=21#wechat_redirect" style="text-decoration: none;color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid #1e6bb8;" data-linktype="2">如何防止订单重复支付</a> 里和大家聊过掉单导致的重复支付,这篇文章,我们来聊聊,如何防止掉单。</p> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzkwODE5ODM0Ng==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/PMZOEonJxWdFLJg0sAOqwHB1mb24icMADUgxm1qZQft5aN3H37NAmQnOvpGB7J9JVHxC6NSiacxbBP1DYdhIAeyA/0?wx_fmt=png" data-nickname="三分恶" data-alias="Fighter3FullStack" data-signature="CSDN博客专家、优质创作者,华为云云享专家;肝过外包、混过国企,目前在一家跨境电商搬砖;写过诗,打过拳,佛系小码农。认真讲技术,随性侃人生,关注我,我们一起走的更远。" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-bottom: none;display: block;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left: 3px solid rgba(0, 0, 0, 0.65);border-right: 1px solid rgba(0, 0, 0, 0.65);background: rgb(249, 249, 249);"> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;margin: 0px;color: black;line-height: 26px;">关注公众号「<strong style="font-weight: bold;color: black;">三分恶</strong>」,回复「<strong style="font-weight: bold;color: black;">666</strong>」,领取七百多页独家原创的面试手册!</p> <figure style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5145098039215686" src="/upload/df52b8305cb9bac55ff97513708dede8.png" data-type="png" data-w="2550" style="display: block;margin: 0 auto;max-width: 100%;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 12px;"> 面渣逆袭手册 </figcaption> </figure> </blockquote> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 24px;"><span style="display: none;"></span>好好的支付,怎么就掉单了?</h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">我听说过下单、买单、脱单……掉单是什么东西?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">所谓的掉单,就是用户下单支付,在钱包里完成了支付,结果回到电商APP一看,订单还是未支付……</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">毫无疑问,用户肯定会炸,结果不是客诉,就是差评。</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.9722222222222222" src="/upload/732da10b7fbaafe74fc7af9264ea6fd7.png" data-type="png" data-w="432" style="display: block;margin: 0 auto;max-width: 100%;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 12px;"> 用户感觉受到了欺诈 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">那么掉单是怎么来的呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">我们先来看看订单支付的完整流程:</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6880984952120383" src="/upload/af5c3c269a757e87d3caafe5af4754d3.png" data-type="png" data-w="1462" style="display: block;margin: 0 auto;max-width: 100%;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 12px;"> 钱包支付的完整流程 </figcaption> </figure> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 用户从电商应用点击支付,客户端向服务端发起支付请求 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 支付服务会向第三方的支付渠道发起支付,支付渠道会响应对应的url </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 以APP为例,客户端通常是会拉起对应的钱包,用户跳到对应的钱包 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 用户在钱包里完成支付 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 用户完成支付后,跳转回对应的电商APP </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 客户端轮询订单服务,获取订单状态 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 支付渠道回调支付服务,通知支付结果 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 支付服务通知订单服务,更新订单状态 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">对于支付订单而言,大概可以分为这么几个状态:</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5656028368794326" src="/upload/458657ffa1c7b4ff667f158a36c7bb89.png" data-type="png" data-w="1128" style="display: block;margin: 0 auto;max-width: 100%;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 12px;"> 支付状态 </figcaption> </figure> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: square;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 未支付:用户在点击支付之后,支付服务请求支付渠道之前,处于未支付状态 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 支付中:用户发起支付后,到跳转到支付钱包,再到完成支付,支付服务获取到最终支付结果之间,属于支付中状态,这个状态下,可以说是一个迷雾状态,电商系统对于用户的支付是不确定 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 支付成功/失败/取消/关闭:电商系统最终确定了用户在第三方钱包的支付最终结果 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">看起来没什么问题啊,怎么就掉单了?简单说,就是支付的状态没有同步到,或者没有及时同步到。</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6898016997167139" src="/upload/bc144b258ad8bf9291c691f5b01569e2.png" data-type="png" data-w="1412" style="display: block;margin: 0 auto;max-width: 100%;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 12px;"> 掉单发生 </figcaption> </figure> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">支付渠道的支付回调</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">发生了一些异常,导致支付服务没有收到支付渠道的回调通知</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">支付服务通知订单服务</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">服务内部出现异常,导致支付状态没有同步到订单服务</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">客户端获取订单状态</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">客户端通常是轮询获取状态,可能会在轮询时间内没有获取到订单状态,结果用户看到未支付</p> </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">其中1可以称之为外部掉单,2和3可以称之为内部掉单。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">接下来我们看看,怎么预防掉单问题。</p> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 24px;"><span style="display: none;"></span>怎么防止内部掉单</h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">我们先从系统内部的掉单说起,当然在系统内部,稳定性更容易保证,发生掉单的概率还是比较小的。</p> <h2 data-tool="mdnice编辑器" style="padding: 12px 0px;font-size: 22px;text-align: center;font-weight: bold;color: black;line-height: 1.1em;margin: 70px 30px 30px;border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);"><span style="float: left;display: block;width: 90%;border-top: 1px solid #000;height: 1px;line-height: 1px;margin-left: -5px;margin-top: -17px;"> </span><span style="display: block;width: 3px;margin: 0 0 0 5%;height: 3px;line-height: 3px;overflow: hidden;background-color: #000;box-shadow: 3px 0 #000, 0 3px #000, -3px 0 #000, 0 -3px #000;"></span><span style="display: block;-webkit-box-reflect: below 0em -webkit-gradient(linear,left top,left bottom, from(rgba(0,0,0,0)),to(rgba(255,255,255,0.1)));">服务端防止掉单</span><span style="display: block;width: 3px;margin: 0 0 0 95%;height: 3px;line-height: 3px;overflow: hidden;background-color: #000;box-shadow: 3px 0 #000, 0 3px #000, -3px 0 #000, 0 -3px #000;"></span><span style="float: right;display: block;width: 90%;border-bottom: 1px solid #000;height: 1px;line-height: 1px;margin-right: -5px;margin-top: 16px;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">支付服务和订单服务之间防止掉单,关键就在于尽可能保证支付通知订单支付结果成功,我们一般通过这两种方式。</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6321525885558583" src="/upload/632daf7137cc80b6241c397748d2f747.png" data-type="png" data-w="734" style="display: block;margin: 0 auto;max-width: 100%;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 12px;"> 服务端防止掉单 </figcaption> </figure> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">同步调用重试机制</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">支付服务调用订单服务的时候,要进行失败重试,防止网络抖动情况下的调用失败。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">异步消息可靠性投递</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">同步不稳妥,那就再加一个异步。支付服务投递一个支付成功消息,订单服务消费支付成功消息,整个过程要尽可能保证可靠性,例如订单服务要在完成订单状态更新后再确认完成消息消费。</p> </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">同步+异步两手策略,基本上可以防范服务端的内部掉单。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">至于引入分布式事务(事务消息、Seata)来保证状态一致,我觉得也没有必要。</p> <h2 data-tool="mdnice编辑器" style="padding: 12px 0px;font-size: 22px;text-align: center;font-weight: bold;color: black;line-height: 1.1em;margin: 70px 30px 30px;border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);"><span style="float: left;display: block;width: 90%;border-top: 1px solid #000;height: 1px;line-height: 1px;margin-left: -5px;margin-top: -17px;"> </span><span style="display: block;width: 3px;margin: 0 0 0 5%;height: 3px;line-height: 3px;overflow: hidden;background-color: #000;box-shadow: 3px 0 #000, 0 3px #000, -3px 0 #000, 0 -3px #000;"></span><span style="display: block;-webkit-box-reflect: below 0em -webkit-gradient(linear,left top,left bottom, from(rgba(0,0,0,0)),to(rgba(255,255,255,0.1)));">客户端如何防止掉单</span><span style="display: block;width: 3px;margin: 0 0 0 95%;height: 3px;line-height: 3px;overflow: hidden;background-color: #000;box-shadow: 3px 0 #000, 0 3px #000, -3px 0 #000, 0 -3px #000;"></span><span style="float: right;display: block;width: 90%;border-bottom: 1px solid #000;height: 1px;line-height: 1px;margin-right: -5px;margin-top: 16px;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">用户支付完成后,跳回电商系统,客户端会轮询一下订单的状态,通常两三秒内,就会得到订单完成支付的结果,这个过程出现问题的概率相比是非常低的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">但是也不排除,很小概率下,客户端轮询一段时间,还没得到结果,那么只能结束轮询,给用户展示未支付。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">这种情况,通常问题也是出在服务端,没有及时更新订单的状态,最主要的还是要处理服务端的掉单,保证服务端能及时同步支付订单的状态。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">但是一旦服务端的订单状态变更了,也要尽可能同步到客户端,不能让用户一直看到未支付。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">客户端和服务端之间,同步状态,无非就是推和拉:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">客户端轮询</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">客户端判断用户未支付之后,通常会进行订单倒计时。</p> <figure style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="1.5829596412556053" src="/upload/778b1a2578bd878ab292747a7efbecfd.png" data-type="png" data-w="446" style="display: block;margin: 0 auto;max-width: 100%;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 12px;"> 倒计时 </figcaption> </figure> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">这里再提一下?大家觉得这种倒计时是怎么实现的呢?纯客户端组组件倒计时吗?</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">——肯定不行,通常是客户端组件倒计时,定期向服务端请求,检查倒计时时间。同样的,这种情况下,客户端也可以检查支付状态。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">服务端推送</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">说真的,服务端推送,看上去是一种很美好的方案,Web端可以使用Websocket,APP端可以用自定义Push,大家可以看看<a href="https://mp.weixin.qq.com/s?__biz=MzkwODE5ODM0Ng==&mid=2247492091&idx=1&sn=0a9d537d7e04035d4496e51b7e2fe7ed&scene=21#wechat_redirect" style="text-decoration: none;color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid #1e6bb8;" data-linktype="2">我有 7种 实现web实时消息推送的方案,7种!</a>。但实际上,推送的成功率经常不那么理想。</p> </section></li> </ol> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 24px;"><span style="display: none;"></span>怎么防止外部掉单</h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">相比较内部掉单,外部掉单发生的概率就大很多,毕竟和外部渠道的对接,不可控的因素更多。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">要防止外部掉单,核心就是四个字:“<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #ff6441;">主动查询</code>”,如果只是等待第三方的回调通知,风险还是比较大的,支付服务要主动向第三方查询支付状态,即使有什么异常,也能及时感知到。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">主动查询,主要就是两种形式:</p> <h2 data-tool="mdnice编辑器" style="padding: 12px 0px;font-size: 22px;text-align: center;font-weight: bold;color: black;line-height: 1.1em;margin: 70px 30px 30px;border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);"><span style="float: left;display: block;width: 90%;border-top: 1px solid #000;height: 1px;line-height: 1px;margin-left: -5px;margin-top: -17px;"> </span><span style="display: block;width: 3px;margin: 0 0 0 5%;height: 3px;line-height: 3px;overflow: hidden;background-color: #000;box-shadow: 3px 0 #000, 0 3px #000, -3px 0 #000, 0 -3px #000;"></span><span style="display: block;-webkit-box-reflect: below 0em -webkit-gradient(linear,left top,left bottom, from(rgba(0,0,0,0)),to(rgba(255,255,255,0.1)));">定时任务查询</span><span style="display: block;width: 3px;margin: 0 0 0 95%;height: 3px;line-height: 3px;overflow: hidden;background-color: #000;box-shadow: 3px 0 #000, 0 3px #000, -3px 0 #000, 0 -3px #000;"></span><span style="float: right;display: block;width: 90%;border-bottom: 1px solid #000;height: 1px;line-height: 1px;margin-right: -5px;margin-top: 16px;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">毫无疑问,最简单的肯定就是定时任务了,支付服务,定时查询一段时间内<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #ff6441;">支付中</code>的支付订单,向第三方渠道查询支付结果,查询到终态之后,就去更新支付订单状态、通知订单服务:</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.7951127819548872" src="/upload/b76429482f09e924a8291e06c63c8fa5.png" data-type="png" data-w="1064" style="display: block;margin: 0 auto;max-width: 100%;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 12px;"> 定时查询支付状态 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">实现也很简单,用xxl-job之类的定时任务框架,定时扫表,向第三方查询就行了,大概代码如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;max-width: 100%;border-radius: 4px;margin: 10px auto 0 auto;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/iahdQicCC5VBSsGkibhVicLUiaHIHibjHJuMoLlfCUpmKSt9huXaaDvkVxItyz1e8jMMj81fjFDTrJQv7OCpeMZe0LAkkyumB5JaCw/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #DCDCDC;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #1E1E1E;border-radius: 5px;"> <span style="color: #9B9B9B;line-height: 26px;">@XxlJob</span>(<span style="color: #D69D85;line-height: 26px;">"syncPaymentResult"</span>)<br> <span style="color: #DCDCDC;line-height: 26px;"><span style="color: #569CD6;line-height: 26px;">public</span> ReturnT<String> <span style="color: #DCDCDC;line-height: 26px;">syncPaymentResult</span><span style="color: #DCDCDC;line-height: 26px;">(<span style="color: #569CD6;line-height: 26px;">int</span> hour)</span> </span>{<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//……</span><br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//查询一段之间支付中的流水</span><br> List<PayDO> pendingList = payMapper.getPending(now.minusHours(hour));<br> <span style="color: #569CD6;line-height: 26px;">for</span> (PayDO payDO : pendingList) {<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//……</span><br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">// 主动去第三方查</span><br> PaymentStatusResult paymentStatusResult = paymentService.getPaymentStatus(paymentId);<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">// 第三方支付中</span><br> <span style="color: #569CD6;line-height: 26px;">if</span> (PaymentStatusEnum.PENDING.equals(paymentStatusResult.getPayStatus())) {<br> <span style="color: #569CD6;line-height: 26px;">continue</span>;<br> }<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//支付完成,获取到终态</span><br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//……</span><br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">// 1.更新流水</span><br> payMapper.updatePayDO(payDO);<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">// 2.通知订单服务</span><br> orderService.notifyOrder(notifyLocalRequestVO);<br> }<br> <span style="color: #569CD6;line-height: 26px;">return</span> ReturnT.SUCCESS;<br> }<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">定时任务的最大好处肯定是简单了,但是它也有一些问题:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: decimal;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">查询的结果不实时</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">定时任务频率的设置永远是个不好确定的事情,间隔短对数据库压力大,间隔长了不实时,很容易出现,上面提到的用户回到APP,结果轮询不到支付成功状态的情况。</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">实际上,用户跳转钱包之后,通常会很快完成支付,如果短时间内没有完成支付,那么一般也不会再付了。所以其实,发起支付开始,从第三方查询支付结果的频率应该是递减的。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">对数据库有压力</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">定时任务扫表,对数据库肯定是会有压力的,扫表的时候,经常会看到数据库的监控出现一个小突刺,如果数据量大的话,可能影响更大。</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">可以单独创建一个支付中流水表,定时任务扫描这张表,获取到支付最终态之后,就删除掉对应的记录。</p> </section></li> </ol> <h2 data-tool="mdnice编辑器" style="padding: 12px 0px;font-size: 22px;text-align: center;font-weight: bold;color: black;line-height: 1.1em;margin: 70px 30px 30px;border-width: 1px;border-style: solid;border-color: rgb(0, 0, 0);"><span style="float: left;display: block;width: 90%;border-top: 1px solid #000;height: 1px;line-height: 1px;margin-left: -5px;margin-top: -17px;"> </span><span style="display: block;width: 3px;margin: 0 0 0 5%;height: 3px;line-height: 3px;overflow: hidden;background-color: #000;box-shadow: 3px 0 #000, 0 3px #000, -3px 0 #000, 0 -3px #000;"></span><span style="display: block;-webkit-box-reflect: below 0em -webkit-gradient(linear,left top,left bottom, from(rgba(0,0,0,0)),to(rgba(255,255,255,0.1)));">延时消息查询</span><span style="display: block;width: 3px;margin: 0 0 0 95%;height: 3px;line-height: 3px;overflow: hidden;background-color: #000;box-shadow: 3px 0 #000, 0 3px #000, -3px 0 #000, 0 -3px #000;"></span><span style="float: right;display: block;width: 90%;border-bottom: 1px solid #000;height: 1px;line-height: 1px;margin-right: -5px;margin-top: 16px;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">定时任务存在一些问题,那么有没有什么其它办法呢?答案是延时消息。</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.7338709677419355" src="/upload/b5bf286e2ba9f53040afe665df8e1ef6.png" data-type="png" data-w="1240" style="display: block;margin: 0 auto;max-width: 100%;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 12px;"> 延时消息查询支付状态 </figcaption> </figure> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: square;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">在发起支付之后,发送一个延时消息,前面讲到,用户跳转到钱包,通常很快会支付,所以我们希望查询支付状态这个步骤,符合这个规律,所以希望在10s、30s、1min、1min30s、2min、5min、7min……这种频率去查询支付订单的状态,这里我们可以用一个队列结构实现,队列里存放下一次查询的时间间隔。</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">大概代码如下:</p> <pre style="margin-top: 10px;margin-bottom: 10px;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;max-width: 100%;border-radius: 4px;margin: 10px auto 0 auto;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/iahdQicCC5VBSsGkibhVicLUiaHIHibjHJuMoLlfCUpmKSt9huXaaDvkVxItyz1e8jMMj81fjFDTrJQv7OCpeMZe0LAkkyumB5JaCw/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;display: -webkit-box;-webkit-overflow-scrolling: touch;font-size: 14px;word-wrap: break-word;padding: 2px 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #ff6441;padding-top: 15px;background: #1E1E1E;border-radius: 5px;"> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//……</span><br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//控制查询频率的队列,时间单位为s</span><br> Deque<Integer> queue = <span style="color: #569CD6;line-height: 26px;">new</span> LinkedList<>();<br> queue.offer(<span style="color: #B8D7A3;line-height: 26px;">10</span>);<br> queue.offer(<span style="color: #B8D7A3;line-height: 26px;">30</span>);<br> queue.offer(<span style="color: #B8D7A3;line-height: 26px;">60</span>);<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//……</span><br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//支付订单号</span><br> PaymentConsultDTO paymentConsultDTO = <span style="color: #569CD6;line-height: 26px;">new</span> PaymentConsultDTO();<br> paymentConsultDTO.setPaymentId(paymentId);<br> paymentConsultDTO.setIntervalQueue(queue);<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//发送延时消息</span><br> Message message = <span style="color: #569CD6;line-height: 26px;">new</span> Message();<br> message.setTopic(<span style="color: #D69D85;line-height: 26px;">"PAYMENT"</span>);<br> message.setKey(paymentId);<br> message.setTag(<span style="color: #D69D85;line-height: 26px;">"CONSULT"</span>);<br> message.setBody(toJSONString(paymentConsultDTO).getBytes(StandardCharsets.UTF_8));<br> <span style="color: #569CD6;line-height: 26px;">try</span> {<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//第一个延时消息,延时10s</span><br> <span style="color: #569CD6;line-height: 26px;">long</span> delayTime = System.currentTimeMillis() + <span style="color: #B8D7A3;line-height: 26px;">10</span> * <span style="color: #B8D7A3;line-height: 26px;">1000</span>;<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">// 设置消息需要被投递的时间。</span><br> message.setStartDeliverTime(delayTime);<br> SendResult sendResult = producer.send(message);<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//……</span><br> } <span style="color: #569CD6;line-height: 26px;">catch</span> (Throwable th) {<br> log.error(<span style="color: #D69D85;line-height: 26px;">"[sendMessage] error:"</span>, th);<br> }<br></code></pre> <blockquote style="border-top: none;border-bottom: none;display: block;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left: 3px solid rgba(0, 0, 0, 0.65);border-right: 1px solid rgba(0, 0, 0, 0.65);background-image: initial;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;"> <p style="padding-top: 8px;padding-bottom: 8px;font-size: 14px;margin: 0px;color: black;line-height: 26px;"><span style="background-color: rgb(249, 249, 249);">PS:</span><span style="background-color: rgb(249, 249, 249);">这里用的是RocketMQ云服务器版,支持任意级别的延时消息,开源版的RocketMQ只支持固定级别的延时消息,不得不</span>感慨<span style="background-color: rgb(249, 249, 249);">充钱才能变强。</span><span style="background-color: rgb(249, 249, 249);">有实力的开发团队,可以在开源基础上,进行二次开发。</span></p> </blockquote> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">在消费到延时消息之后,向第三方查询支付订单的状态,如果还在支付中,就继续发送下一个延时消息,延时间隔从队列结构中取。如果获取到最终态,就去更新支付订单状态、通知订单服务。</p> <pre style="margin-top: 10px;margin-bottom: 10px;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;max-width: 100%;border-radius: 4px;margin: 10px auto 0 auto;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/iahdQicCC5VBSsGkibhVicLUiaHIHibjHJuMoLlfCUpmKSt9huXaaDvkVxItyz1e8jMMj81fjFDTrJQv7OCpeMZe0LAkkyumB5JaCw/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(30, 30, 30);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;display: -webkit-box;-webkit-overflow-scrolling: touch;font-size: 14px;word-wrap: break-word;padding: 2px 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #ff6441;padding-top: 15px;background: #1E1E1E;border-radius: 5px;"><span style="color: #9B9B9B;line-height: 26px;">@Component</span><br><span style="color: #9B9B9B;line-height: 26px;">@Slf</span>4j<br><span style="color: #569CD6;line-height: 26px;">public</span> <span style="color: #B8D7A3;line-height: 26px;"><span style="color: #569CD6;line-height: 26px;">class</span> <span style="color: #DCDCDC;line-height: 26px;">ConsultListener</span> <span style="color: #569CD6;line-height: 26px;">implements</span> <span style="color: #DCDCDC;line-height: 26px;">MessageListener</span> </span>{<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//消费者注册,监听器注册</span><br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//……</span><br> <br> <span style="color: #9B9B9B;line-height: 26px;">@Override</span><br> <span style="color: #DCDCDC;line-height: 26px;"><span style="color: #569CD6;line-height: 26px;">public</span> Action <span style="color: #DCDCDC;line-height: 26px;">consume</span><span style="color: #DCDCDC;line-height: 26px;">(Message message, ConsumeContext context)</span> </span>{<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">// UTF-8解析</span><br> String body = <span style="color: #569CD6;line-height: 26px;">new</span> String(message.getBody(), StandardCharsets.UTF_8);<br> PaymentConsultDTO paymentConsultDTO= JsonUtil.parseObject(body, <span style="color: #569CD6;line-height: 26px;">new</span> TypeReference<PaymentConsultDTO>() {<br> });<br> <span style="color: #569CD6;line-height: 26px;">if</span> (paymentConsultDTO == <span style="color: #569CD6;line-height: 26px;">null</span>) {<br> <span style="color: #569CD6;line-height: 26px;">return</span> Action.ReconsumeLater;<br> }<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//获取支付流水</span><br> PayDO payDO=payMapper.selectById(paymentConsultDTO.getPaymentId());<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//……</span><br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//查询支付状态</span><br> PaymentStatusResult paymentStatusResult=payService.getPaymentStatus(paymentStatusContext);<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//还在支付中,继续投递一个延时消息</span><br> <span style="color: #569CD6;line-height: 26px;">if</span> (PaymentStatusEnum.PENDING.equals(paymentStatusResult.getPayStatus())){<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//发送延时消息</span><br> Message msg = <span style="color: #569CD6;line-height: 26px;">new</span> Message();<br> message.setTopic(<span style="color: #D69D85;line-height: 26px;">"PAYMENT"</span>);<br> message.setKey(paymentConsultDTO.getPaymentId());<br> message.setTag(<span style="color: #D69D85;line-height: 26px;">"CONSULT"</span>);<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//下一个延时消息的频率</span><br> Long delaySeconds=paymentConsultDTO.getIntervalQueue().poll(); message.setBody(toJSONString(paymentConsultDTO).getBytes(StandardCharsets.UTF_8));<br> <span style="color: #569CD6;line-height: 26px;">try</span> {<br> Long delayTime = System.currentTimeMillis() + delaySeconds * <span style="color: #B8D7A3;line-height: 26px;">1000</span>;<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">// 设置消息需要被投递的时间。</span><br> message.setStartDeliverTime(delayTime);<br> SendResult sendResult = producer.send(message);<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//……</span><br> } <span style="color: #569CD6;line-height: 26px;">catch</span> (Throwable th) {<br> log.error(<span style="color: #D69D85;line-height: 26px;">"[sendMessage] error:"</span>, th);<br> }<br> <span style="color: #569CD6;line-height: 26px;">return</span> Action.CommitMessage;<br> }<br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//获取到最终态</span><br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//更新支付订单状态</span><br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//…… </span><br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//通知订单服务</span><br> <span style="color: #57A64A;font-style: italic;line-height: 26px;">//……</span><br> <span style="color: #569CD6;line-height: 26px;">return</span> Action.CommitMessage;<br> }<br>}<br></code></pre> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">延时消息的方案相对于定时轮询方案来讲:</p> <p style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">不过大家也看到,我这里的实现是利用的是充钱版的RocketMQ,所以看起来不太复杂,但是如果用开源方案,那就没那么简单。</p> <figure style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6659090909090909" src="/upload/d6080716449af4ad694c28dffa6b4e07.png" data-type="png" data-w="440" style="display: block;margin: 0 auto;max-width: 100%;box-shadow: rgba(170, 170, 170, 0.48) 0px 0px 6px 0px;border-radius: 4px;margin-top: 10px;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 12px;"> 充钱就能解决 </figcaption> </figure> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: circle;" class="list-paddingleft-1"> <li style="list-style-type: circle;"> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 时效性更好 </section></li> <li style="list-style-type: circle;"> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> 无需扫表,对数据库压力较小 </section></li> </ul> </ul> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 24px;"><span style="display: none;"></span>结语</h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">这篇文章介绍了一个让用户炸毛,让客服恼火,让开发挠头的问题——掉单,包括为什么会掉单,怎么防止掉单。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">其中内部掉单,发生的概率相对较少,掉单最主要的原因还是所谓的外部掉单。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;">外部掉单解决的关键点是<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #ff6441;">主动查询</code>,有两种常用的方案:<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #ff6441;">定时任务查询</code>和<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #ff6441;">延时消息查询</code>,前者简单一些,后者功能上更加出色。</p> <hr data-tool="mdnice编辑器" style="height: 1px;margin: 10px 0px;border-right: none;border-bottom: none;border-left: none;border-top: 1px solid black;"> <br data-tool="mdnice编辑器"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;"><big><strong>参考:</strong></big></p> <ul class="list-paddingleft-1" style="list-style-type: circle;"> <li> <section style="padding-top: 8px;padding-bottom: 8px;margin: 0px;color: black;font-size: 14px;line-height: normal;"> [1]. 支付掉单异常最全解决方案 </section></li> <li> <section style="padding-top: 8px;padding-bottom: 8px;margin: 0px;color: black;font-size: 14px;line-height: normal;"> [2]. 解决支付掉单问题 </section></li> </ul> <hr data-tool="mdnice编辑器" style="height: 1px;margin: 10px 0px;border-right: none;border-bottom: none;border-left: none;border-top: 1px solid black;"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;"><br></p> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzkwODE5ODM0Ng==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/PMZOEonJxWdFLJg0sAOqwHB1mb24icMADUgxm1qZQft5aN3H37NAmQnOvpGB7J9JVHxC6NSiacxbBP1DYdhIAeyA/0?wx_fmt=png" data-nickname="三分恶" data-alias="Fighter3FullStack" data-signature="CSDN博客专家、优质创作者,华为云云享专家;肝过外包、混过国企,目前在一家跨境电商搬砖;写过诗,打过拳,佛系小码农。认真讲技术,随性侃人生,关注我,我们一起走的更远。" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;"><br></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin: 0;line-height: 26px;color: black;font-size: 14px;"><big><strong>⭐面渣逆袭系列:</strong></big></p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: square;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <a href="https://mp.weixin.qq.com/s?__biz=MzkwODE5ODM0Ng==&mid=2247489569&idx=1&sn=6d0ff8b376e35d68f272248b3e3927b2&scene=21#wechat_redirect" style="text-decoration: none;color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid #1e6bb8;" data-linktype="2">面渣逆袭:Java基础五十三问</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <a href="https://mp.weixin.qq.com/s?__biz=MzkwODE5ODM0Ng==&mid=2247488788&idx=1&sn=01875e3e45515c2d57593cb7a01d0b6b&scene=21#wechat_redirect" style="text-decoration: none;color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid #1e6bb8;" data-linktype="2">面渣逆袭:Java集合连环三十问</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <a href="https://mp.weixin.qq.com/s?__biz=MzkwODE5ODM0Ng==&mid=2247489004&idx=1&sn=8cba55cb769e271f031ce866de2be249&scene=21#wechat_redirect" style="text-decoration: none;color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid #1e6bb8;" data-linktype="2">面渣逆袭:JVM经典五十问,这下面试稳了!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <a href="https://mp.weixin.qq.com/s?__biz=MzkwODE5ODM0Ng==&mid=2247489245&idx=1&sn=bc52281ebc85372e19513d663beb2d2d&chksm=c0ccfe78f7bb776e2c6396fe26aca84d0cd96f407e6fe0bf6eb068aed638ba9491bce8fc5b4c&scene=21&cur_album_id=2041709347461709827#wechat_redirect" style="text-decoration: none;color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid #1e6bb8;" data-linktype="2">面渣逆袭:Java并发六十问</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <a href="https://mp.weixin.qq.com/s?__biz=MzkwODE5ODM0Ng==&mid=2247491359&idx=1&sn=7a0c3f5fc04b2e45a3cfba638941f663&chksm=c0ccf7baf7bb7eaccba3e29d2a768710af8a16c87574c3a8f4b24c8dee814b296ff56e3bd6e3&scene=21&cur_album_id=2041709347461709827#wechat_redirect" style="text-decoration: none;color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid #1e6bb8;" data-linktype="2">面渣逆袭:Spring三十五问,四万字+五十图详解!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <a href="https://mp.weixin.qq.com/s?__biz=MzkwODE5ODM0Ng==&mid=2247490612&idx=1&sn=e37c69a7875ce54a28c9918ea6a24a73&chksm=c0ccf491f7bb7d87bcc6f49a04a3e3a175f382cfdba3305151861988caa86b0feff1e5578e54&scene=21&cur_album_id=2041709347461709827#wechat_redirect" style="text-decoration: none;color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid #1e6bb8;" data-linktype="2">面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <a href="https://mp.weixin.qq.com/s?__biz=MzkwODE5ODM0Ng==&mid=2247489885&idx=1&sn=1a4cb15c40c07e18f180df6fda8f472f&chksm=c0ccf1f8f7bb78eef66f067d63e2abdf1092847eba6372b6e4c15185a6d6ce7d407278c83e6f&scene=21&cur_album_id=2041709347461709827#wechat_redirect" style="text-decoration: none;color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid #1e6bb8;" data-linktype="2">面渣逆袭:计算机网络六十二问,三万字图文详解!速收藏!</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <a href="https://mp.weixin.qq.com/s?__biz=MzkwODE5ODM0Ng==&mid=2247488406&idx=1&sn=93e2435b319c42497a4efa966ddc9237&scene=21#wechat_redirect" style="text-decoration: none;color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid #1e6bb8;" data-linktype="2">面试字节,被操作系统问挂了</a> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;color: rgb(1,1,1);font-weight: 500;"> <a href="https://mp.weixin.qq.com/s?__biz=MzkwODE5ODM0Ng==&mid=2247490996&idx=1&sn=ba9558574d71979aa689a710c28c7e0e&scene=21#wechat_redirect" style="text-decoration: none;color: #1e6bb8;word-wrap: break-word;font-weight: bold;border-bottom: 1px solid #1e6bb8;" data-linktype="2">面渣逆袭:RocketMQ二十三问</a> </section></li> </ul> <span style="font-size: 15px;display: block;text-align: center;margin-top: 50px;color: #999;border-bottom: 1px solid #eee;">- END -</span> </section> <p><br></p>
作者:微信小助手
<p style="margin-bottom: 0px;text-align: left;"><strong style="color: rgb(171, 25, 66);"><span style="font-size: 15px;">背景</span></strong></p> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">某天收到一封读者的邮件,询问我一个关于 InnoDB 死锁的问题。他在 MySQL 5.7 可以复现这个问题,MySQL 8.0.22 却无法复现,他询问其死锁的原因。经过一系列的排查,我后来发现是 InnoDB 内部实现的一个 Bug,目前这个 Bug 已经在 8.0.18 版本进行了修复,所以也可以通过 8.0.17 vs 8.0.18 来验证这个问题。</span></p> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">整个 SQL 流程如下:</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 1. 表结构 */</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">CREATE</span> <span class="code-snippet__keyword">TABLE</span> t (</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">id</span> <span class="code-snippet__built_in">BIGINT</span> <span class="code-snippet__keyword">UNSIGNED</span> <span class="code-snippet__keyword">NOT</span> <span class="code-snippet__literal">NULL</span> PRIMARY <span class="code-snippet__keyword">KEY</span> <span class="code-snippet__keyword">COMMENT</span> <span class="code-snippet__string">'id, 无实际意义'</span>,</span></code><code><span class="code-snippet_outer"> account_id <span class="code-snippet__built_in">VARCHAR</span> (<span class="code-snippet__number">64</span>) <span class="code-snippet__keyword">NOT</span> <span class="code-snippet__literal">NULL</span> <span class="code-snippet__keyword">COMMENT</span> <span class="code-snippet__string">'用户id,不同app下的account_id可能重复'</span>,</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">type</span> <span class="code-snippet__built_in">TINYINT</span> <span class="code-snippet__keyword">UNSIGNED</span> <span class="code-snippet__keyword">NOT</span> <span class="code-snippet__literal">NULL</span> <span class="code-snippet__keyword">COMMENT</span> <span class="code-snippet__string">'余额类型 1:可用余额'</span>,</span></code><code><span class="code-snippet_outer"> balance <span class="code-snippet__built_in">BIGINT</span> <span class="code-snippet__keyword">UNSIGNED</span> <span class="code-snippet__keyword">NOT</span> <span class="code-snippet__literal">NULL</span> <span class="code-snippet__keyword">DEFAULT</span> <span class="code-snippet__number">0</span> <span class="code-snippet__keyword">COMMENT</span> <span class="code-snippet__string">'余额'</span>,</span></code><code><span class="code-snippet_outer"> state <span class="code-snippet__built_in">INT</span> <span class="code-snippet__keyword">UNSIGNED</span> <span class="code-snippet__keyword">NOT</span> <span class="code-snippet__literal">NULL</span> <span class="code-snippet__keyword">DEFAULT</span> <span class="code-snippet__number">1</span> <span class="code-snippet__keyword">COMMENT</span> <span class="code-snippet__string">'账户状态 1:NORMAL; 2:FROZE'</span>,</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">UNIQUE</span> <span class="code-snippet__keyword">KEY</span> uk_account (account_id, <span class="code-snippet__keyword">type</span>)</span></code><code><span class="code-snippet_outer">)<span class="code-snippet__keyword">ENGINE</span> = <span class="code-snippet__keyword">INNODB</span> <span class="code-snippet__keyword">DEFAULT</span> <span class="code-snippet__keyword">CHARSET</span> utf8mb4 <span class="code-snippet__keyword">COMMENT</span> <span class="code-snippet__string">'测试'</span>;</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 2. 其中 UNIQUE INDEX 为 uk_account(account_id, type) */</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 3. 插入数据 */</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">insert</span> <span class="code-snippet__keyword">into</span> t <span class="code-snippet__keyword">values</span>(<span class="code-snippet__number">1</span>,<span class="code-snippet__string">'1'</span>,<span class="code-snippet__number">1</span>,<span class="code-snippet__number">100</span>,<span class="code-snippet__number">1</span>);</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">insert</span> <span class="code-snippet__keyword">into</span> t <span class="code-snippet__keyword">values</span>(<span class="code-snippet__number">2</span>,<span class="code-snippet__string">'2'</span>,<span class="code-snippet__number">1</span>,<span class="code-snippet__number">100</span>,<span class="code-snippet__number">1</span>);</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">insert</span> <span class="code-snippet__keyword">into</span> t <span class="code-snippet__keyword">values</span>(<span class="code-snippet__number">3</span>,<span class="code-snippet__string">'3'</span>,<span class="code-snippet__number">1</span>,<span class="code-snippet__number">100</span>,<span class="code-snippet__number">1</span>);</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">insert</span> <span class="code-snippet__keyword">into</span> t <span class="code-snippet__keyword">values</span>(<span class="code-snippet__number">4</span>,<span class="code-snippet__string">'4'</span>,<span class="code-snippet__number">1</span>,<span class="code-snippet__number">100</span>,<span class="code-snippet__number">1</span>);</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">insert</span> <span class="code-snippet__keyword">into</span> t <span class="code-snippet__keyword">values</span>(<span class="code-snippet__number">5</span>,<span class="code-snippet__string">'5'</span>,<span class="code-snippet__number">1</span>,<span class="code-snippet__number">100</span>,<span class="code-snippet__number">1</span>);</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 4. 查询所有数据. */</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">select</span> * <span class="code-snippet__keyword">from</span> t;</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 5. 执行以下 SQL, 注意事务隔离级别为 (RR) */</span></span></code><code><span class="code-snippet_outer">t1-1:(session1)</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">begin</span>; <span class="code-snippet__comment">/* 显式开启事务, 排除 autocommit 的影响. */</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">select</span> * <span class="code-snippet__keyword">from</span> t <span class="code-snippet__keyword">where</span> account_id = <span class="code-snippet__string">'1'</span> <span class="code-snippet__keyword">and</span> <span class="code-snippet__keyword">type</span> =<span class="code-snippet__number">1</span> <span class="code-snippet__keyword">for</span> <span class="code-snippet__keyword">update</span>;</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">t2: (session2)</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">begin</span>;</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">select</span> * <span class="code-snippet__keyword">from</span> t <span class="code-snippet__keyword">where</span> account_id = <span class="code-snippet__string">'1'</span> <span class="code-snippet__keyword">and</span> <span class="code-snippet__keyword">type</span> =<span class="code-snippet__number">1</span> <span class="code-snippet__keyword">for</span> <span class="code-snippet__keyword">update</span>;</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">t1-2: (session1)</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">update</span> t <span class="code-snippet__keyword">set</span> state = <span class="code-snippet__number">2</span> <span class="code-snippet__keyword">where</span> account_id = <span class="code-snippet__string">'1'</span>;</span></code></pre> </section> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">我们按照顺序执行分别在 MySQL 8.0.17 和 MySQL 8.0.18 执行。可以看到在 8.0.17 版本事务 t2 因为死锁检测而被视为 victim_trx 进行了回滚,而 8.0.18 却不会回滚事务 t2。</span></p> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">基于 MySQL 8.0.17:</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer">MySQL [sbtest]> <span class="code-snippet__keyword">select</span> * <span class="code-snippet__keyword">from</span> t <span class="code-snippet__keyword">where</span> account_id = <span class="code-snippet__string">'1'</span> and type =<span class="code-snippet__number">1</span> <span class="code-snippet__keyword">for</span> update;</span></code><code><span class="code-snippet_outer">ERROR <span class="code-snippet__number">1213</span> (<span class="code-snippet__number">40001</span>): Deadlock found when trying to <span class="code-snippet__keyword">get</span> <span class="code-snippet__keyword">lock</span>; <span class="code-snippet__keyword">try</span> restarting transaction</span></code></pre> </section> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><strong><span style="color: rgb(171, 25, 66);font-size: 15px;">分析流程</span></strong></p> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">我们基于问题版本 8.0.17 来分析 Bug 的真正原因。</span></p> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><strong><span style="font-size: 15px;">SQL 分析</span></strong></p> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">通过表结构我们可以看到整个表有两个索引,PRIMARY INDEX 和 UNIQUE INDEX uk_account。因为是死锁问题,所以我们要逐条分析 SQL 语句加的 record lock 分别是什么:</span></p> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">t1-1 是一条 SELECT FROM UPDATE 的语句,而account_id 和 type 是一组唯一索引字段,所以只需要加一个主键索引的 X record lock 和唯一索引 uk_account 的 X record lock。</span></p> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">t2 语句与 t1-1 相同,加锁一致,也是一个主键索引的 X record lock 和 唯一索引 uk_account 的 X record lock。</span></p> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">t1-2 注意 t1-2 的查询条件只有 where account_id = ‘1’,这与 t1-1 的查询条件是不同的。所以,在 RR 隔离级别下,为了避免出现可能的幻读,这需要加一个 Next-key lock。另外,需要对 record (2,’2’,1,100,1) 加一个 GAP lock,防止在此之前的插入造成幻读。</span></p> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><strong><span style="font-size: 15px;">锁信息验证</span></strong></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">为了验证我们对于 SQL 的分析,我们可以通过 set global innodb_status_output_locks = on; 打开锁状态输出,然后 show engine innodb status\G 来查看锁信息。这里我们为了验证分析正确,跳过执行 t2 语句,因为 t2 的加锁类型一定是与 t1-1 一致的:</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="go"><code><span class="code-snippet_outer">> begin;</span></code><code><span class="code-snippet_outer">> <span class="code-snippet__keyword">select</span> * from t where account_id = <span class="code-snippet__string">'1'</span> and <span class="code-snippet__keyword">type</span> =<span class="code-snippet__number">1</span> <span class="code-snippet__keyword">for</span> update;</span></code><code><span class="code-snippet_outer">> update t set state = <span class="code-snippet__number">2</span> where account_id = <span class="code-snippet__string">'1'</span>;</span></code><code><span class="code-snippet_outer">> set global innodb_status_output_locks = on; <span class="code-snippet__comment">/* 暂不提交,以查看事务锁的持有信息. */</span></span></code><code><span class="code-snippet_outer">> show engine innodb status\G</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 事务 t1 持有 5 个 lock, 4 个 row lock, 1 个 table lock (暂时忽略). */</span></span></code><code><span class="code-snippet_outer">---TRANSACTION <span class="code-snippet__number">2068</span>, ACTIVE <span class="code-snippet__number">16</span> sec</span></code><code><span class="code-snippet_outer"><span class="code-snippet__number">5</span> lock <span class="code-snippet__keyword">struct</span>(s), heap size <span class="code-snippet__number">1200</span>, <span class="code-snippet__number">4</span> row lock(s)</span></code><code><span class="code-snippet_outer">MySQL thread id <span class="code-snippet__number">9</span>, OS thread handle <span class="code-snippet__number">140737025267456</span>, query id <span class="code-snippet__number">74</span> <span class="code-snippet__number">127.0</span><span class="code-snippet__number">.0</span><span class="code-snippet__number">.1</span> myadmin starting</span></code><code><span class="code-snippet_outer">show engine innodb status</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 下列为 table lock (笔者注). */</span></span></code><code><span class="code-snippet_outer">TABLE LOCK table <span class="code-snippet__string">`sbtest`</span>.<span class="code-snippet__string">`t`</span> trx id <span class="code-snippet__number">2068</span> lock mode IX</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 下列为 Record: 1,'1',1,100,1 的 X record lock 基于索引 uk_account (笔者注). */</span></span></code><code><span class="code-snippet_outer">RECORD LOCKS space id <span class="code-snippet__number">2</span> page no <span class="code-snippet__number">5</span> n bits <span class="code-snippet__number">72</span> index uk_account of table <span class="code-snippet__string">`sbtest`</span>.<span class="code-snippet__string">`t`</span> trx id <span class="code-snippet__number">2068</span> lock_mode X locks rec but not gap</span></code><code><span class="code-snippet_outer">Record lock, heap no <span class="code-snippet__number">2</span> PHYSICAL RECORD: n_fields <span class="code-snippet__number">3</span>; compact format; info bits <span class="code-snippet__number">0</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">0</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">1</span>; hex <span class="code-snippet__number">31</span>; asc <span class="code-snippet__number">1</span>;;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">1</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">1</span>; hex <span class="code-snippet__number">01</span>; asc ;;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">2</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">8</span>; hex <span class="code-snippet__number">0000000000000001</span>; asc ;;</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 下列为 Record: 1,'1',1,100,1 的 X record lock 基于主键索引 (笔者注). */</span></span></code><code><span class="code-snippet_outer">RECORD LOCKS space id <span class="code-snippet__number">2</span> page no <span class="code-snippet__number">4</span> n bits <span class="code-snippet__number">72</span> index PRIMARY of table <span class="code-snippet__string">`sbtest`</span>.<span class="code-snippet__string">`t`</span> trx id <span class="code-snippet__number">2068</span> lock_mode X locks rec but not gap</span></code><code><span class="code-snippet_outer">Record lock, heap no <span class="code-snippet__number">2</span> PHYSICAL RECORD: n_fields <span class="code-snippet__number">7</span>; compact format; info bits <span class="code-snippet__number">0</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">0</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">8</span>; hex <span class="code-snippet__number">0000000000000001</span>; asc ;;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">1</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">6</span>; hex <span class="code-snippet__number">000000000809</span>; asc ;;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">2</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">7</span>; hex <span class="code-snippet__number">01000001160151</span>; asc Q;;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">3</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">1</span>; hex <span class="code-snippet__number">31</span>; asc <span class="code-snippet__number">1</span>;;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">4</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">1</span>; hex <span class="code-snippet__number">01</span>; asc ;;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">5</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">8</span>; hex <span class="code-snippet__number">0000000000000064</span>; asc d;;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">6</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">4</span>; hex <span class="code-snippet__number">00000002</span>; asc ;;</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 下列为 Record: 1,'1',1,100,1 的 Next-key record lock 基于索引 uk_account. (笔者注). */</span></span></code><code><span class="code-snippet_outer">RECORD LOCKS space id <span class="code-snippet__number">2</span> page no <span class="code-snippet__number">5</span> n bits <span class="code-snippet__number">72</span> index uk_account of table <span class="code-snippet__string">`sbtest`</span>.<span class="code-snippet__string">`t`</span> trx id <span class="code-snippet__number">2068</span> lock_mode X</span></code><code><span class="code-snippet_outer">Record lock, heap no <span class="code-snippet__number">2</span> PHYSICAL RECORD: n_fields <span class="code-snippet__number">3</span>; compact format; info bits <span class="code-snippet__number">0</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">0</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">1</span>; hex <span class="code-snippet__number">31</span>; asc <span class="code-snippet__number">1</span>;;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">1</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">1</span>; hex <span class="code-snippet__number">01</span>; asc ;;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">2</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">8</span>; hex <span class="code-snippet__number">0000000000000001</span>; asc ;;</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 下列为 Record: 2,'2',1,100,1 的 GAP lock 基于索引 uk_account. (笔者注). */</span></span></code><code><span class="code-snippet_outer">RECORD LOCKS space id <span class="code-snippet__number">2</span> page no <span class="code-snippet__number">5</span> n bits <span class="code-snippet__number">72</span> index uk_account of table <span class="code-snippet__string">`sbtest`</span>.<span class="code-snippet__string">`t`</span> trx id <span class="code-snippet__number">2068</span> lock_mode X locks gap before rec</span></code><code><span class="code-snippet_outer">Record lock, heap no <span class="code-snippet__number">3</span> PHYSICAL RECORD: n_fields <span class="code-snippet__number">3</span>; compact format; info bits <span class="code-snippet__number">0</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">0</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">1</span>; hex <span class="code-snippet__number">32</span>; asc <span class="code-snippet__number">2</span>;;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">1</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">1</span>; hex <span class="code-snippet__number">01</span>; asc ;;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">2</span>: <span class="code-snippet__built_in">len</span> <span class="code-snippet__number">8</span>; hex <span class="code-snippet__number">0000000000000002</span>; asc ;;</span></code></pre> </section> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">通过 show engine innodb status\G 我们可以看到当前的事务的锁持有信息。事务 t1 分别执行 t1-1 和 t1-2 语句后持有的锁分别有:</span></p> <p style="text-align: left;margin-bottom: 0px;"><br></p> <ol class="list-paddingleft-1" style="list-style-type: decimal;"> <li style="font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">一个主键索引的 X record lock</span></p></li> <li style="font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">一个 UNIQUE INDEX 的 X record lock</span></p></li> <li style="font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">一个 Next-key record lock, InnoDB 为了明确 Next-key lock 和普通的 record lock 的区别,分别用不同的 mode 来区分:</span></p></li> <ol class="list-paddingleft-1" style="list-style-type: lower-alpha;"> <li style="font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="color: rgb(51, 51, 51);font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-align: left;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;float: none;font-size: 15px;display: inline !important;">普通的 X record lock:</span></p><p style="text-align: left;margin-bottom: 0px;"><span style="color: rgb(51, 51, 51);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;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-align: left;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;float: none;display: inline !important;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.07637906647807638" data-s="300,640" src="/upload/a887c12414c91fa890a016ed98609875.png" data-type="png" data-w="1414" style="margin: 0px;padding: 0px;max-width: 100%;height: auto !important;vertical-align: bottom;color: rgb(51, 51, 51);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: 17px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-align: center;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;"></span></p></li> <li style="font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="color: rgb(48, 48, 48);font-family: Avenir, "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans-serif;font-size: 15px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-align: left;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;float: none;display: inline !important;">Next-key lock:</span></p><p style="text-align: left;margin-bottom: 0px;"><span style="color: rgb(48, 48, 48);font-family: Avenir, "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans-serif;font-size: 15px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-align: left;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;float: none;display: inline !important;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.0800561797752809" data-s="300,640" src="/upload/f76465bfcb59e8e125a5083c91a957cc.png" data-type="png" data-w="1424" style="margin: 0px;padding: 0px;max-width: 100%;height: auto !important;vertical-align: bottom;color: rgb(51, 51, 51);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: 17px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-align: center;text-indent: 0px;text-transform: none;white-space: normal;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;"></span></p></li> </ol> <li style="text-align: left;margin-bottom: 0px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">一个 GAP record lock</span></p></li> </ol> <p style="text-align: left;margin-bottom: 0px;"><br></p> <h3 style="text-align: left;margin-bottom: 0px;"><strong><span style="font-size: 15px;color: rgb(171, 25, 66);">死锁原因排查</span></strong></h3> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">既然 t2 被死锁检测回滚,我们就需要检查当时是什么锁关系导致了死锁。</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">官方在 8.0.18 版本对死锁检测进行了优化,将原先的死锁检测机制 MySQL 死锁检测源码分析[1] 交给了 background thread 来处理,具体的 Patch 链接:MySQL-8.0.18 死锁检测优化[2]。具体的思路是将当前事务系统的 lock 信息打一份快照,由这份快照判断是否存在回环,假如存在死锁即唤醒等待事务。</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">而在 8.0.17 版本依然采用旧的死锁检测方法,具体细节可以参考这篇文章:MySQL 死锁检测源码分析[1]:每次申请 lock 失败进入 wait 状态后触发一下死锁检测,所以我们通过 gdb 调试的方法来梳理当时的锁依赖关系。当我们执行完成 t1-1,继而执行 t2 后,事务 t2 进入了 wait 状态。当执行 t1-2 后 t2 回滚,说明触发 t2 回滚的死锁检测是由 t1-2 发起的。我们 break 在死锁检测的路径上,然后 print 整个锁信息(代码基于 8.0.17):</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">我们设置断点在死锁检测的路径上,因为可以明确是 t1-2 的死锁检测触发了 t2 的回滚,所以我们可以明确哪次 break 是我们想要的断点位置。</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer">(gdb) b storage/innobase/<span class="code-snippet__keyword">lock</span>/lock0lock.cc:<span class="code-snippet__number">7125</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 1. 按顺序执行 t1-1, t2, t1-2 直到 t1-2 触发死锁检测. */</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 2. DeadlockChecker 会设置一个 m_start 即发起死锁检测的 trx 和 m_wait_lock 即 m_start 等待的 lock. */</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">(gdb) p m_start <span class="code-snippet__comment">/* 事务t1 */</span></span></code><code><span class="code-snippet_outer">$<span class="code-snippet__number">9</span> = (const trx_t *) <span class="code-snippet__number">0x7fffe506cc78</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">(gdb) p *m_wait_lock <span class="code-snippet__comment">/* 事务 t1 尝试申请的 lock, 目前在等待状态. */</span></span></code><code><span class="code-snippet_outer">$<span class="code-snippet__number">53</span> = {trx = <span class="code-snippet__number">0x7fffe506cc78</span>, trx_locks = {prev = <span class="code-snippet__number">0x7fffe00101f8</span>, <span class="code-snippet__keyword">next</span> = <span class="code-snippet__number">0x0</span>}, <span class="code-snippet__keyword">index</span> = <span class="code-snippet__number">0x7ff8740971f8</span>, <span class="code-snippet__keyword">hash</span> = <span class="code-snippet__number">0x0</span>, {tab_lock = {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">table</span> = <span class="code-snippet__number">0x500000002</span>, locks = {prev = <span class="code-snippet__number">0x48</span>, <span class="code-snippet__keyword">next</span> = <span class="code-snippet__number">0x0</span>}}, rec_lock = {<span class="code-snippet__keyword">space</span> = <span class="code-snippet__number">2</span>, page_no = <span class="code-snippet__number">5</span>, n_bits = <span class="code-snippet__number">72</span>}},</span></code><code><span class="code-snippet_outer"> m_psi_internal_thread_id = <span class="code-snippet__number">43</span>, m_psi_event_id = <span class="code-snippet__number">66</span>, type_mode = <span class="code-snippet__number">291</span>, m_seq = <span class="code-snippet__number">72</span>}</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 注意 m_wait_lock 的 type_mode */</span></span></code><code><span class="code-snippet_outer">(gdb) p /t <span class="code-snippet__number">291</span></span></code><code><span class="code-snippet_outer">$<span class="code-snippet__number">13</span> = <span class="code-snippet__number">100100011</span> <span class="code-snippet__comment">/* 即 LOCK_X | LOCK_REC | LOCK_WAIT</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">(gdb) p m_wait_lock->index->name</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">$22 = {m_name = 0x7ff874097538 "uk_account"} /* 可以确认是等待在唯一索引 uk_account 的 record lock. */</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 3. s 进入函数 DeadlockChecker::search(), 会首先 get_first_lock(&heap_no) 即从 m_wait_lock 对应的 record 上的 heap_no 从 lock_sys->rec_hash 找到第一个 lock. */</span></span></code><code><span class="code-snippet_outer">const lock_t *<span class="code-snippet__keyword">lock</span> = get_first_lock(&heap_no);</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">(gdb) p <span class="code-snippet__keyword">lock</span></span></code><code><span class="code-snippet_outer">$<span class="code-snippet__number">15</span> = (const ib_lock_t *) <span class="code-snippet__number">0x7fffe0010098</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">(gdb) p *<span class="code-snippet__keyword">lock</span></span></code><code><span class="code-snippet_outer">$<span class="code-snippet__number">14</span> = {trx = <span class="code-snippet__number">0x7fffe506cc78</span>, trx_locks = {prev = <span class="code-snippet__number">0x7fffe0010cb8</span>, <span class="code-snippet__keyword">next</span> = <span class="code-snippet__number">0x7fffe00101f8</span>}, <span class="code-snippet__keyword">index</span> = <span class="code-snippet__number">0x7ff8740971f8</span>, <span class="code-snippet__keyword">hash</span> = <span class="code-snippet__number">0x7fffe0010358</span>, {</span></code><code><span class="code-snippet_outer"> tab_lock = {<span class="code-snippet__keyword">table</span> = <span class="code-snippet__number">0x500000002</span>, locks = {prev = <span class="code-snippet__number">0x48</span>, <span class="code-snippet__keyword">next</span> = <span class="code-snippet__number">0x0</span>}}, rec_lock = {<span class="code-snippet__keyword">space</span> = <span class="code-snippet__number">2</span>, page_no = <span class="code-snippet__number">5</span>, n_bits = <span class="code-snippet__number">72</span>}},</span></code><code><span class="code-snippet_outer"> m_psi_internal_thread_id = <span class="code-snippet__number">43</span>, m_psi_event_id = <span class="code-snippet__number">50</span>, type_mode = <span class="code-snippet__number">1059</span>, m_seq = <span class="code-snippet__number">54</span>}</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* record 上的第一个 record 锁是 0x7fffe0010098, 它属于我们目前发起的事务 m_start 即 0x7fffe506cc78.</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> type_mode 是 1059 即 LOCK_X | LOCK_REC | LOCK_REC_NOT_GAP. */</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 我们通过 lock->hash 迭代来查看整个 record 上的锁排列信息. */</span></span></code><code><span class="code-snippet_outer">(gdb) p *(ib_lock_t*)<span class="code-snippet__number">0x7fffe0010098</span> <span class="code-snippet__comment">/* 第一个 record lock */</span></span></code><code><span class="code-snippet_outer">$<span class="code-snippet__number">48</span> = {trx = <span class="code-snippet__number">0x7fffe506cc78</span>, trx_locks = {prev = <span class="code-snippet__number">0x7fffe0010cb8</span>, <span class="code-snippet__keyword">next</span> = <span class="code-snippet__number">0x7fffe00101f8</span>}, <span class="code-snippet__keyword">index</span> = <span class="code-snippet__number">0x7ff8740971f8</span>, <span class="code-snippet__keyword">hash</span> = <span class="code-snippet__number">0x7fffe0011918</span>, {</span></code><code><span class="code-snippet_outer"> tab_lock = {<span class="code-snippet__keyword">table</span> = <span class="code-snippet__number">0x500000002</span>, locks = {prev = <span class="code-snippet__number">0x48</span>, <span class="code-snippet__keyword">next</span> = <span class="code-snippet__number">0x0</span>}}, rec_lock = {<span class="code-snippet__keyword">space</span> = <span class="code-snippet__number">2</span>, page_no = <span class="code-snippet__number">5</span>, n_bits = <span class="code-snippet__number">72</span>}},</span></code><code><span class="code-snippet_outer"> m_psi_internal_thread_id = <span class="code-snippet__number">43</span>, m_psi_event_id = <span class="code-snippet__number">65</span>, type_mode = <span class="code-snippet__number">1059</span>, m_seq = <span class="code-snippet__number">69</span>}</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">(gdb) p *(ib_lock_t*)<span class="code-snippet__number">0x7fffe0011918</span> <span class="code-snippet__comment">/* 第二个 record lock. */</span></span></code><code><span class="code-snippet_outer">$<span class="code-snippet__number">49</span> = {trx = <span class="code-snippet__number">0x7fffe506d090</span>, trx_locks = {prev = <span class="code-snippet__number">0x7fffe00124c8</span>, <span class="code-snippet__keyword">next</span> = <span class="code-snippet__number">0x0</span>}, <span class="code-snippet__keyword">index</span> = <span class="code-snippet__number">0x7ff8740971f8</span>, <span class="code-snippet__keyword">hash</span> = <span class="code-snippet__number">0x7fffe0010358</span>, {tab_lock = {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">table</span> = <span class="code-snippet__number">0x500000002</span>, locks = {prev = <span class="code-snippet__number">0x48</span>, <span class="code-snippet__keyword">next</span> = <span class="code-snippet__number">0x0</span>}}, rec_lock = {<span class="code-snippet__keyword">space</span> = <span class="code-snippet__number">2</span>, page_no = <span class="code-snippet__number">5</span>, n_bits = <span class="code-snippet__number">72</span>}},</span></code><code><span class="code-snippet_outer"> m_psi_internal_thread_id = <span class="code-snippet__number">44</span>, m_psi_event_id = <span class="code-snippet__number">36</span>, type_mode = <span class="code-snippet__number">1315</span>, m_seq = <span class="code-snippet__number">71</span>}</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">(gdb) p *(ib_lock_t*)<span class="code-snippet__number">0x7fffe0010358</span> <span class="code-snippet__comment">/* 第三个 record lock. */</span></span></code><code><span class="code-snippet_outer">$<span class="code-snippet__number">50</span> = {trx = <span class="code-snippet__number">0x7fffe506cc78</span>, trx_locks = {prev = <span class="code-snippet__number">0x7fffe00101f8</span>, <span class="code-snippet__keyword">next</span> = <span class="code-snippet__number">0x0</span>}, <span class="code-snippet__keyword">index</span> = <span class="code-snippet__number">0x7ff8740971f8</span>, <span class="code-snippet__keyword">hash</span> = <span class="code-snippet__number">0x0</span>, {tab_lock = {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">table</span> = <span class="code-snippet__number">0x500000002</span>, locks = {prev = <span class="code-snippet__number">0x48</span>, <span class="code-snippet__keyword">next</span> = <span class="code-snippet__number">0x0</span>}}, rec_lock = {<span class="code-snippet__keyword">space</span> = <span class="code-snippet__number">2</span>, page_no = <span class="code-snippet__number">5</span>, n_bits = <span class="code-snippet__number">72</span>}},</span></code><code><span class="code-snippet_outer"> m_psi_internal_thread_id = <span class="code-snippet__number">43</span>, m_psi_event_id = <span class="code-snippet__number">66</span>, type_mode = <span class="code-snippet__number">291</span>, m_seq = <span class="code-snippet__number">72</span>}</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 当前事务与 record 的关系如下:</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">发起死锁检测是 trx 是: 0x7fffe506cc78, trx 0x7fffe506cc78 等待的 lock 是 0x7fffe0010358.</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">lock: 0x7fffe0010098 所属的 trx: 0x7fffe506cc78 type_mode: 1059 LOCK_X | LOCK_REC | LOCK_REC_NOT_GAP 即持有 X record lock.</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">lock: 0x7fffe0011918 所属的 trx: 0x7fffe506d090 type_mode: 1315 LOCK_X | LOCK_WAIT | LOCK_REC | LOCK_REC_NOT_GAP 即等待 X record lock.</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">lock: 0x7fffe0010358 所属的 trx: 0x7fffe506cc78 type_mode: 291 LOCK_X | LOCK_REC | LOCK_WAIT 即等待 X record lock. */</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 死锁检测流程结束后选择的 victim_trx 是: */</span></span></code><code><span class="code-snippet_outer">(gdb) p victim_trx</span></code><code><span class="code-snippet_outer">$<span class="code-snippet__number">58</span> = (const trx_t *) <span class="code-snippet__number">0x7fffe506d090</span></span></code></pre> </section> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">通过上述分析我们可以得出结论 t1 事务的 t1-2 语句触发了死锁检测,选择的 victim_trx 是事务 t2。我们需要明确以下几个问题:</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li style="text-align: left;margin-bottom: 0px;font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">发起死锁检测的原因是因为事务 t1 无法立即获得 X record lock;</span></p></li> <li style="text-align: left;margin-bottom: 0px;font-size: 15px;"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">事务 t1 认为可能会发生的死锁原因是因为在整个 lock 的等待关系中存在一个环,即 t1 不 commit 提交事务,t2 事务也无法获取 X record lock,从而导致 t1-2 的 UPDATE 语句也无法获得 X record lock 组成 Next-key record lock,即使 t1 已经持有了 X record lock。</span></p></li> </ul> <h3 style="text-align: left;margin-bottom: 0px;"></h3> <h3 style="text-align: left;margin-bottom: 0px;"><br></h3> <h3 style="text-align: left;margin-bottom: 0px;"><strong><span style="font-size: 15px;color: rgb(171, 25, 66);">解决方案</span></strong></h3> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">根据最近的 Release Note,我二分验证 8.0.16 - 8.0.22 的版本。发现在 8.0.17 存在问题,8.0.18 不存在。所以,根据现象我仔细查看了 8.0.18 的 Release Note[3],发现了疑似这个现象的 Bugfix:</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <ul style="text-align: left;margin-bottom: 0px;" class="list-paddingleft-1"> <li style="text-align: left;margin-bottom: 0px;font-size: 14px;color: rgb(136, 136, 136);"><p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 14px;color: rgb(136, 136, 136);">InnoDB: A deadlock was possible when a transaction tries to upgrade a record lock to a next key lock. (Bug #23755664, Bug #82127)</span></p></li> </ul> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">根据 Bug ID,可以通过 Github 的 MySQL 提交记录来查找这个 Patch[4]:</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 14px;color: rgb(136, 136, 136);">Bug #23755664 DEADLOCK WITH 3 CONCURRENT DELETES BY UNIQUE KEY</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 14px;color: rgb(136, 136, 136);"><br></span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 14px;color: rgb(136, 136, 136);">PROBLEM: A deadlock was possible when a transaction tried to “upgrade” an already held Record Lock to Next Key Lock.</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 14px;color: rgb(136, 136, 136);"><br></span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 14px;color: rgb(136, 136, 136);">SOLUTION: This patch is based on observations that: (1) a Next Key Lock is equivalent to Record Lock combined with Gap Lock (2) a GAP Lock never has to wait for any other lock In case we request a Next Key Lock, we check if we already own a Record Lock of equal or stronger mode, and if so, then we either upgrade it to Next Key Lock, or if it is not possible (because the single lock_t struct is shared by more than one row) we change the requested lock type to GAP Lock, which we either already have, or can be granted immediately. (I don’t consider Insert Intention Locks a Gap Lock in above statements).</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 14px;color: rgb(136, 136, 136);"><br></span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 14px;color: rgb(136, 136, 136);">Reviewed-by: Debarun Banerjee debarun.banerjee@oracle.com RB:19879</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <section style="text-align: left;margin-bottom: 0px;"> <span style="font-size: 15px;">经过验证确实是这个 Patch 修复了这个死锁的问题。</span> </section> <section style="text-align: left;margin-bottom: 0px;"> <span style="font-size: 15px;"><br></span> </section> <h2 style="text-align: left;margin-bottom: 0px;"><strong><span style="font-size: 15px;color: rgb(171, 25, 66);">Patch 分析</span></strong></h2> <section style="margin-bottom: 0px;"> <span style="font-size: 15px;"><br></span> </section> <section style="text-align: left;margin-bottom: 0px;"> <span style="font-size: 15px;">这个 Patch 具体的原理是当尝试获取 Next-key record lock 时,不再与旧的逻辑一样。旧的逻辑是先直接尝试申请 Next-key lock,现在改为先判断当前 trx 是否持有 X record lock,假如持有就复用这个 X record lock,从而直接申请 GAP lock 以达到 Next-key Lock 的效果。</span> </section> <section style="text-align: left;margin-bottom: 0px;"> <span style="font-size: 15px;"><br></span> </section> <section style="text-align: left;margin-bottom: 0px;"> <span style="font-size: 15px;">所以在我们上面的例子中,申请 Next-key record lock 时跳过申请 X record lock,就不会进入等待队列,也不会产生死锁的回环。</span> </section> <section style="text-align: left;margin-bottom: 0px;"> <span style="font-size: 15px;"><br></span> </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="sql"><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 使用 8.0.26 最新版分析 lock 持有情况.</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 执行 t1:</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> select * from t where account_id = '1' and type =1 for update;</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> update t set state = 2 where account_id = '1';</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> show engine innodb status\G; */</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">show</span> <span class="code-snippet__keyword">engine</span> <span class="code-snippet__keyword">innodb</span> <span class="code-snippet__keyword">status</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">TABLE</span> <span class="code-snippet__keyword">LOCK</span> <span class="code-snippet__keyword">table</span> <span class="code-snippet__string">`sbtest`</span>.<span class="code-snippet__string">`t`</span> trx <span class="code-snippet__keyword">id</span> <span class="code-snippet__number">1323</span> <span class="code-snippet__keyword">lock</span> <span class="code-snippet__keyword">mode</span> IX</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 下列为 Record: 1,'1',1,100,1 的 X record lock 基于索引 uk_account. (笔者注). */</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__built_in">RECORD</span> LOCKS <span class="code-snippet__keyword">space</span> <span class="code-snippet__keyword">id</span> <span class="code-snippet__number">2</span> page <span class="code-snippet__keyword">no</span> <span class="code-snippet__number">5</span> n bits <span class="code-snippet__number">72</span> <span class="code-snippet__keyword">index</span> uk_account <span class="code-snippet__keyword">of</span> <span class="code-snippet__keyword">table</span> <span class="code-snippet__string">`sbtest`</span>.<span class="code-snippet__string">`t`</span> trx <span class="code-snippet__keyword">id</span> <span class="code-snippet__number">1323</span> lock_mode X locks rec but <span class="code-snippet__keyword">not</span> gap</span></code><code><span class="code-snippet_outer"><span class="code-snippet__built_in">Record</span> <span class="code-snippet__keyword">lock</span>, <span class="code-snippet__keyword">heap</span> <span class="code-snippet__keyword">no</span> <span class="code-snippet__number">2</span> <span class="code-snippet__keyword">PHYSICAL</span> <span class="code-snippet__built_in">RECORD</span>: n_fields <span class="code-snippet__number">3</span>; compact format; info bits 0</span></code><code><span class="code-snippet_outer"> 0: len 1; hex 31; asc 1;;</span></code><code><span class="code-snippet_outer"> 1: len 1; hex 01; asc ;;</span></code><code><span class="code-snippet_outer"> 2: len 8; hex 0000000000000001; asc ;;</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 下列为 Record: 1,'1',1,100,1 的 X record lock 基于主键索引. (笔者注). */</span></span></code><code><span class="code-snippet_outer">RECORD LOCKS space id 2 page no 4 n bits 72 index PRIMARY of table `sbtest`.`t` trx id 1323 lock_mode X locks rec but not gap</span></code><code><span class="code-snippet_outer">Record <span class="code-snippet__keyword">lock</span>, <span class="code-snippet__keyword">heap</span> <span class="code-snippet__keyword">no</span> <span class="code-snippet__number">2</span> <span class="code-snippet__keyword">PHYSICAL</span> <span class="code-snippet__built_in">RECORD</span>: n_fields <span class="code-snippet__number">7</span>; compact format; info bits 0</span></code><code><span class="code-snippet_outer"> 0: len 8; hex 0000000000000001; asc ;;</span></code><code><span class="code-snippet_outer"> 1: len 6; hex 00000000052b; asc +;;</span></code><code><span class="code-snippet_outer"> 2: len 7; hex 020000011b0110; asc ;;</span></code><code><span class="code-snippet_outer"> 3: len 1; hex 31; asc 1;;</span></code><code><span class="code-snippet_outer"> 4: len 1; hex 01; asc ;;</span></code><code><span class="code-snippet_outer"> 5: len 8; hex 0000000000000064; asc d;;</span></code><code><span class="code-snippet_outer"> 6: len 4; hex 00000002; asc ;;</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 下列为 Record: 1,'1',1,100,1 的 GAP record lock 基于索引 uk_account, 与上面的 uk_account 的 X lock 组成 Next-key Lock. (笔者注). */</span></span></code><code><span class="code-snippet_outer">RECORD LOCKS space id 2 page no 5 n bits 72 index uk_account of table `sbtest`.`t` trx id 1323 lock_mode X locks gap before rec</span></code><code><span class="code-snippet_outer">Record <span class="code-snippet__keyword">lock</span>, <span class="code-snippet__keyword">heap</span> <span class="code-snippet__keyword">no</span> <span class="code-snippet__number">2</span> <span class="code-snippet__keyword">PHYSICAL</span> <span class="code-snippet__built_in">RECORD</span>: n_fields <span class="code-snippet__number">3</span>; compact format; info bits 0</span></code><code><span class="code-snippet_outer"> 0: len 1; hex 31; asc 1;;</span></code><code><span class="code-snippet_outer"> 1: len 1; hex 01; asc ;;</span></code><code><span class="code-snippet_outer"> 2: len 8; hex 0000000000000001; asc ;;</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/* 下列为 Record: 2,'2',1,100,1 的 GAP record lock 基于索引 uk_account. (笔者注: lock 的类型与上列一致, InnoDB 在这里对相同类型的 lock 做了省略). */</span></span></code><code><span class="code-snippet_outer">Record <span class="code-snippet__keyword">lock</span>, <span class="code-snippet__keyword">heap</span> <span class="code-snippet__keyword">no</span> <span class="code-snippet__number">3</span> <span class="code-snippet__keyword">PHYSICAL</span> <span class="code-snippet__built_in">RECORD</span>: n_fields <span class="code-snippet__number">3</span>; compact format; info bits 0</span></code><code><span class="code-snippet_outer"> 0: len 1; hex 32; asc 2;;</span></code><code><span class="code-snippet_outer"> 1: len 1; hex 01; asc ;;</span></code><code><span class="code-snippet_outer"> 2: len 8; hex 0000000000000002; asc ;</span></code></pre> </section> <section style="text-align: left;margin-bottom: 0px;"> <br> </section> <h2 style="text-align: left;margin-bottom: 0px;"><span style="color: rgb(171, 25, 66);"><strong><span style="font-size: 15px;">总结</span></strong></span></h2> <p style="text-align: left;margin-bottom: 0px;"><br></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;">根据例子我们分析了一个 InnoDB 的死锁场景,以及 Bug 产生的原因。通过 gdb 调试的方式分析 InnoDB 的死锁原因,最主要任务就是梳理整个锁的等待依赖关系,这能帮助我们更直观的分析真正的原因。这是一个 X record lock “升级” 至 Next-key record lock 的 Bug,官方在 8.0.18 已经修复了这个存在了几年的问题。</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 15px;"><br></span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 12px;"><strong>参考阅读</strong></span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 12px;color: rgb(136, 136, 136);">[1]:https://leviathan.vip/2020/02/02/mysql-deadlock-check/</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 12px;color: rgb(136, 136, 136);">[2]:https://github.com/mysql/mysql-server/commit/3859219875b62154b921e8c6078c751198071b9c</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 12px;color: rgb(136, 136, 136);">[3]:https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-18.html</span></p> <p style="text-align: left;margin-bottom: 0px;"><span style="font-size: 12px;color: rgb(136, 136, 136);">[4]:https://github.com/mysql/mysql-server/commit/85927b60bc658ddfffcc3aeab15d7553d163b0be</span></p> <p style="color: rgb(154, 154, 154);font-size: 15px;white-space: normal;margin-bottom: 0px;text-align: left;"><br></p> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="60" data-source-title=""> <section class="js_blockquote_digest"> <section> <p style="color: rgb(154, 154, 154);font-size: 15px;white-space: normal;text-align: left;"><span style="font-size: 14px;">转自:阿里云PolarDB-数据库内核组,</span></p> <p style="color: rgb(154, 154, 154);font-size: 15px;white-space: normal;text-align: left;"><span style="font-size: 14px;">链接:</span>mysql.taobao.org/monthly/2022/02/01/</p> </section> </section> </blockquote>
作者:微信小助手
<section style="line-height: 1.75em;" data-mpa-powered-by="yiban.io"> <span style="font-size: 15px;"><img src="/upload/a83f0db3ab48535b968a42f5fe078c47.png" class="rich_pages wxw-img" data-ratio="0.14106583072100312" data-w="638" data-backw="578" data-backh="82" style="width: 100%;he
作者:微信小助手
<p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">身为一个程序员,遇到线上问题那都是家常便饭的事儿。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">如果你在深夜看到一群同事围在一起,他们是在共同探讨什么哲学问题么?非也,他们一定是遇到了线上BUG。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">线上问题只要影响到了核心业务流程那便是事故,所以一旦事故发生,无论你在约会,还是周末打游戏,甚至是在睡觉,只要接到了来自公司的电话,那只能赶紧连上公司网络加班了。</p> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <img class="rich_pages wxw-img" data-ratio="0.5036261079774376" src="/upload/954b5a61eda43cfa619c685e2f0f4a6c.png" data-type="png" data-w="1241" style="box-sizing: border-box;vertical-align: middle;border-style: none;display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;"> <br> </figcaption> </figure> <h1 style="box-sizing: border-box;margin-top: 1.2em;margin-bottom: 1em;color: rgb(119, 48, 152);font-weight: bold;font-size: 24px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">BUG分类</span><span style="box-sizing: border-box;"></span></h1> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">线上问题是复杂多变的,我们一般将bug分为系统级别和业务级别bug。</p> <h2 style="box-sizing: border-box;margin: 1em auto;color: rgb(119, 48, 152);font-weight: bold;padding-top: 0.5em;padding-bottom: 0.5em;font-size: 22px;min-height: 32px;line-height: 28px;border-bottom: 1px solid rgb(119, 48, 152);border-top-color: rgb(119, 48, 152);border-right-color: rgb(119, 48, 152);border-left-color: rgb(119, 48, 152);text-align: center;width: 262.987px;display: flex;flex-direction: column;justify-content: center;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">系统级别bug</span><span style="box-sizing: border-box;"></span></h2> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">业务部署在整套系统上运行,一旦出现系统级别bug则业务会被严重拖垮。如CPU爆满、服务不可用、甚至服务器宕机等都属于系统级别的bug。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">如果是CPU100%,那是由哪个线程,哪个类,甚至是哪个方法导致的?</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">若是业务流程正常但是部分服务性能拉跨,那么如何快速定位到问题在哪儿?</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">因为是线上发生的事儿,所以重点在于如何<span style="color: rgb(255, 79, 121);"><strong>迅速解决</strong></span>。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">以下分享我最常用的一些问题排查工具。</p> <h3 style="box-sizing: border-box;margin-top: 1.2em;margin-bottom: 1em;color: rgb(119, 48, 152);font-weight: bold;font-size: 20px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">linux定位工具</span></h3> <h4 style="box-sizing: border-box;margin-top: 30px;margin-bottom: 15px;color: black;font-weight: bold;font-size: 18px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">1.CPU高负载,甚至100%?</span></h4> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><strong style="box-sizing: border-box;color: rgb(119, 48, 152);">perf工具</strong></p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">perf是linux的性能分析工具,核心作用之一就是用来查看热点函数的分布情况。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">用它可以生成<strong style="box-sizing: border-box;color: rgb(119, 48, 152);">火焰图</strong>查看到函数的资源占用情况,函数的调用栈越深火焰就越高。所以对于异常的函数一眼就能看出。</p> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <img class="rich_pages wxw-img" data-ratio="0.1338212232096184" src="/upload/dbc5157867997c9ad9dd4cf627d40ab7.png" data-type="png" data-w="3826" style="box-sizing: border-box;vertical-align: middle;border-style: none;display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;"> <br> </figcaption> </figure> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">如上图通过调用栈你可以看出Monitor管程在反复调用enter和wait,这种情况下就可以判断出该程序已经发生死锁且存在性能问题。假设有大量线程请求这段代码,那么CPU资源将被迅速打满!</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">在著名的“713B站事故”里技术团队在事故发生时就用到了当前工具生成了火焰图,快速地分析出了事故的根因也就是导致CPU100%的lua热点函数。</p> <h4 style="box-sizing: border-box;margin-top: 30px;margin-bottom: 15px;color: black;font-weight: bold;font-size: 18px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">2.某一进程存在异常嫌疑,想快速知道它的状态?</span></h4> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><strong style="box-sizing: border-box;color: rgb(119, 48, 152);">ps命令</strong></p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">我们项目部署的服务器里在跑的进程老多了,java进程、nginx进程、redis、消息队列进程等等。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">举个例子,假设在某一流量高峰期系统监控到整个服务性能下降5倍,业务被严重拖垮,在确定没有业务层面bug的情况下大概率就是因为服务性能达到瓶颈了。如何确定瓶颈在哪儿?</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">大部分情况下通过系统告警就可以知道大概问题所在。如发生消息堆积我们就该怀疑消息生产者和消费者的状态,这个时候就要具体去查看消息队列这一进程。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">可以使用一些轻量级的linux命令,如<span style="color: rgb(255, 79, 121);"><strong>ps</strong></span>:</p> <pre style="box-sizing: border-box;font-size: 15px;font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;color: rgb(89, 89, 89);letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><code style="box-sizing: border-box;font-size: 12px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);border-radius: 5px;">[root@linuxfancy ~]<span style="box-sizing: border-box;color: rgb(97, 174, 238);line-height: 26px;"># ps -ef | grep queuejob</span><br style="box-sizing: border-box;"> root <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">1303</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">1</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0</span> Apr17 ? <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">00</span>:<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">00</span>:<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">00</span> /usr/sbin/queuejob<br style="box-sizing: border-box;"> root <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">3260</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">3087</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0</span> Apr17 ? <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">00</span>:<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">00</span>:<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">00</span> /usr/bin/queuejob /bin/sh -c exec -l /bin/bash -c <span style="box-sizing: border-box;color: rgb(152, 195, 121);line-height: 26px;">"env GNOME_SHELL_SESSION_MODE=classic gnome-session --session gnome-classic"</span><br style="box-sizing: border-box;"> root <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">24174</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">19508</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">11</span>:<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">39</span> pts/<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">00</span>:<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">00</span>:<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">00</span> grep --color=<span style="box-sizing: border-box;color: rgb(198, 120, 221);line-height: 26px;">auto</span> ssh<br style="box-sizing: border-box;"> [root@linux265 ~]# ps -aux | grep queueA<br style="box-sizing: border-box;"> root <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">1303</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0.0</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0.0</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">82468</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">1204</span> ? Ss Apr17 <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0</span>:<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">00</span> /usr/sbin/queueA<br style="box-sizing: border-box;"> root <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">3260</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0.0</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0.0</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">52864</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">572</span> ? Ss Apr17 <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0</span>:<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">00</span> /usr/bin/queueA /bin/sh -c exec -l <br style="box-sizing: border-box;"> root <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">24188</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0.0</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0.0</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">112652</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">956</span> pts/<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0</span> S+ <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">11</span>:<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">39</span> <span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">0</span>:<span style="box-sizing: border-box;color: rgb(209, 154, 102);line-height: 26px;">00</span> grep --color=<span style="box-sizing: border-box;color: rgb(198, 120, 221);line-height: 26px;">auto</span> ssh<br style="box-sizing: border-box;"></code></pre> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">该命令还可以用于对进程的资源使用情况进行排序:</p> <pre style="box-sizing: border-box;font-size: 15px;font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;color: rgb(89, 89, 89);letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><code style="box-sizing: border-box;font-size: 12px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);border-radius: 5px;">[root@linuxfancy ~]# ps aux | sort -nk 3</code></pre> <pre style="box-sizing: border-box;font-size: 15px;font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;color: rgb(89, 89, 89);letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><code style="box-sizing: border-box;font-size: 12px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);border-radius: 5px;">[root@linuxfancy ~]<span style="box-sizing: border-box;color: rgb(97, 174, 238);line-height: 26px;"># ps aux | sort -rnk 4 </span><br style="box-sizing: border-box;"></code></pre> <h4 style="box-sizing: border-box;margin-top: 30px;margin-bottom: 15px;color: black;font-weight: bold;font-size: 18px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">3.我想知道内存&磁盘的使用情况?</span></h4> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><strong style="box-sizing: border-box;color: rgb(119, 48, 152);">vmstat命令</strong></p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">vmstat是Virtual Meomory Statistics(虚拟内存统计)的缩写。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">它是一个用于监控内存和磁盘使用情况的工具,但是也可以用来查看CPU的一些指标,如中断次数等。使用它可以查看内存使用的详细信息和磁盘的读/写情况。</p> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <img class="rich_pages wxw-img" data-ratio="0.36810344827586206" src="/upload/c278def166d6ab3ec4bf1a53dcab06c1.png" data-type="png" data-w="1160" style="box-sizing: border-box;vertical-align: middle;border-style: none;display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;"> <br> </figcaption> </figure> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">以上表头字段的说明如下:</p> <pre style="box-sizing: border-box;font-size: 15px;font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;color: rgb(89, 89, 89);letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><code style="box-sizing: border-box;font-size: 12px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);border-radius: 5px;">Procs(进程):<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">r: 运行队列中进程数量<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">b: 等待IO的进程数量<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">Memory(内存):<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">swpd: 使用虚拟内存大小<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">free: 可用内存大小<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">buff: 用作缓冲的内存大小<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">cache: 用作缓存的内存大小<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">Swap(交换):<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">si: 每秒从交换区写到内存的大小<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">so: 每秒写入交换区的内存大小IO:(现在的Linux版本块的大小为1024bytes)bi: 每秒读取的块数bo: 每秒写入的块数<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">System(系统):<br style="box-sizing: border-box;"><br style="box-sizing: border-box;"><span style="box-sizing: border-box;color: rgb(198, 120, 221);line-height: 26px;">in</span>: 每秒中断数,包括时钟中断<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">cs: 每秒上下文切换数<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">CPU(以百分比表示)<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">us: 用户进程执行时间(user time)<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">sy: 系统进程执行时间(system time)<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">id: 空闲时间(包括IO等待时间),中央处理器的空闲时间<br style="box-sizing: border-box;"><br style="box-sizing: border-box;">wa: IO等待时间<br style="box-sizing: border-box;"></code></pre> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">从以上命令就可以很清晰地看出服务器的各方面性能情况。除此之外还有以下命令也可以在排查或者调优中使用:</p> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <img class="rich_pages wxw-img" data-ratio="0.7892644135188867" src="/upload/228e56da10cdfd5120db0199c1b2153c.png" data-type="png" data-w="1006" style="box-sizing: border-box;vertical-align: middle;border-style: none;display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;"> <br> </figcaption> </figure> <h2 style="box-sizing: border-box;margin: 1em auto;color: rgb(119, 48, 152);font-weight: bold;padding-top: 0.5em;padding-bottom: 0.5em;font-size: 22px;min-height: 32px;line-height: 28px;border-bottom: 1px solid rgb(119, 48, 152);border-top-color: rgb(119, 48, 152);border-right-color: rgb(119, 48, 152);border-left-color: rgb(119, 48, 152);text-align: center;width: 262.987px;display: flex;flex-direction: column;justify-content: center;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">业务级别bug</span><span style="box-sizing: border-box;"></span></h2> <h3 style="box-sizing: border-box;margin-top: 1.2em;margin-bottom: 1em;color: rgb(119, 48, 152);font-weight: bold;font-size: 20px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">如何定位到业务bug?</span></h3> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">出现了业务bug那就纯纯的是开发或测试的锅了。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">bug确定后第一步一定是<span style="color: rgb(255, 79, 121);"><strong>先看日志</strong></span>,只要你写需求的时候日志打的全,一般出现了问题日志或者告警都会第一时间推送。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">通过日志我们可以定位到bug对应代码的位置,但这仅仅是第一步,因为你只知道哪里出了问题,并不知道代码出了什么问题(除非一眼就能看出)。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">所以下一步,<span style="color: rgb(255, 79, 121);"><strong>看数据</strong></span>,数据是业务应用的核心。若通过日志和页面表现查看到你的主流程是没有问题的,那么下一步就是要确定表的数据是否有问题,数据存在bug的表现会是各方面的,可能是用户反馈,也可能是流程错误,这要取决于你表的设计。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">切记!!线上数据是重中之重,当你决定要修复数据,在处理之前一定要做好备份,这样起码可以保证事情不会变的更糟。一般情况下修改线上数据这种活都需要你写好SQL,然后经过leader审批再交给DBA来操作,一定不要干出删库跑路这种事哟。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">假设验证了你数据是OK的,那么问题就极大可能出现在了<span style="color: rgb(255, 79, 121);"><strong>代码层面</strong></span>。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">当代程序员最难过的瞬间无非就是有一个非常紧急的线上bug需要你来解决,但是摆在你面前的却是一堆屎山代码!!</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">修改业务bug最重要的是要将bug点修改掉并且保证其它业务还能正常运行,这是牵一发而动全身的事情,否则bug只会越改越多。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">所以平时应该预知到这些风险,做好代码设计。总结一下定位业务bug的正确步骤:</p> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <img class="rich_pages wxw-img" data-ratio="0.27586206896551724" src="/upload/e83332aab7d4c3a784d1b45115238831.png" data-type="png" data-w="696" style="box-sizing: border-box;vertical-align: middle;border-style: none;display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;"> <br> </figcaption> </figure> <h1 style="box-sizing: border-box;margin-top: 1.2em;margin-bottom: 1em;color: rgb(119, 48, 152);font-weight: bold;font-size: 24px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">方案设计</span><span style="box-sizing: border-box;"></span></h1> <h2 style="box-sizing: border-box;margin: 1em auto;color: rgb(119, 48, 152);font-weight: bold;padding-top: 0.5em;padding-bottom: 0.5em;font-size: 22px;min-height: 32px;line-height: 28px;border-bottom: 1px solid rgb(119, 48, 152);border-top-color: rgb(119, 48, 152);border-right-color: rgb(119, 48, 152);border-left-color: rgb(119, 48, 152);text-align: center;width: 262.987px;display: flex;flex-direction: column;justify-content: center;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">代码设计</span><span style="box-sizing: border-box;"></span></h2> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">一般公司都有自己的代码设计规范。比如由外到里包装代码,每一个方法都要有对应的职责,并且一个方法不要超过100行,一个类不要超过1000行代码等。清晰的结构可以让你和他人更好地review代码,避免看起来一头雾水。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">写业务逻辑有两种方式,一种就是简洁明了的线性逻辑,另一种就是通过封装代码来减少代码耦合提高内聚性,也就是我们说的设计模式的使用。两种方式各有优缺点,但是工作多年了咱写的代码也不能直里直气的,多少得带点”艺术“对吧?推荐一下我经常使用但是也不会特别复杂的设计模式。</p> <h3 style="box-sizing: border-box;margin-top: 1.2em;margin-bottom: 1em;color: rgb(119, 48, 152);font-weight: bold;font-size: 20px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">设计模式</span></h3> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><strong style="box-sizing: border-box;color: rgb(119, 48, 152);">工厂模式</strong></p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">这是最常使用的设计模式之一。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">工厂模式分为简单工厂模式、工厂方法模式和抽象工厂模式。我们这里讲解简单工厂模式,因为后两个都是以其为基础做改进的。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">其结构如下:</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">通过定义一个用以创建对象的接口, 让子类决定实例化哪个类。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">所以其实质就是由一个<span style="color: rgb(255, 79, 121);"><strong>工厂类</strong></span>根据传入的参数,动态决定应该创建哪一个<span style="color: rgb(255, 79, 121);"><strong>产品类</strong></span>(这些产品类继承自一个父类或接口)的实例。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">其包含以下角色:</p> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);" class="list-paddingleft-1"> <li style="box-sizing: border-box;"> <section style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;">工厂(Creator)角色:工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。</p> </section></li> <li style="box-sizing: border-box;"> <section style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;">抽象产品(Product)角色:它负责描述所有实例所共有的公共接口。</p> </section></li> <li style="box-sizing: border-box;"> <section style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;">具体产品(Concrete Product)角色:创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。</p> </section></li> </ul> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <img class="rich_pages wxw-img" data-ratio="0.7542662116040956" src="/upload/c685113ecd97d30d89d3cb194eb3bd7a.png" data-type="png" data-w="879" style="box-sizing: border-box;vertical-align: middle;border-style: none;display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;"> <br> </figcaption> </figure> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">当遇到<span style="color: rgb(255, 79, 121);"><strong>需要根据某个前提条件创建不同的类实现</strong></span>时, 可以使用工厂模式。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><strong style="box-sizing: border-box;color: rgb(119, 48, 152);">装饰者模式</strong></p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">它是在不必改变原类结构和继承体系的情况下,<span style="color: rgb(255, 79, 121);"><strong>动态地扩展一个对象的功能</strong></span>。通过创建一个包装对象来实现对功能的扩展,动态的给一个对象添加一些额外的职责。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">所以装饰者模式分为主体和装饰者。</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">其包含角色如下:</p> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);" class="list-paddingleft-1"> <li style="box-sizing: border-box;"> <section style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;line-height: 26px;color: rgb(1, 1, 1);"> 主体(Main):业务主体逻辑、字段等。 </section></li> <li style="box-sizing: border-box;"> <section style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;line-height: 26px;color: rgb(1, 1, 1);"> 主体具体实现类(MainComponent):主体具体的实现类。 </section></li> <li style="box-sizing: border-box;"> <section style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;line-height: 26px;color: rgb(1, 1, 1);"> 装饰者(Decorator):要做的装饰扩展逻辑接口。 </section></li> <li style="box-sizing: border-box;"> <section style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;line-height: 26px;color: rgb(1, 1, 1);"> 装饰者具体实现类(DecoratorComponent):扩展逻辑的具体实现类。 </section></li> </ul> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <img class="rich_pages wxw-img" data-ratio="0.8079306071871127" src="/upload/d0192af89982b86cfc192d129a1b1c53.png" data-type="png" data-w="807" style="box-sizing: border-box;vertical-align: middle;border-style: none;display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;"> <br> </figcaption> </figure> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">以上两种设计模式都有着”高扩展性“的特点,我们应该根据业务灵活设计接口,避免需求迭代导致的一坨坨又臭又长的代码。但是设计模式切勿用来炫技,一些较为冷门或者复杂的设计模式不推荐使用,否则当一套代码只有你能维护时,那将会是非常痛苦的。。</p> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <img class="rich_pages wxw-img" data-ratio="1.048936170212766" src="/upload/2cd3a854f7961935138675ddd0b801a7.png" data-type="png" data-w="470" style="box-sizing: border-box;vertical-align: middle;border-style: none;display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;"> <br> </figcaption> </figure> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">当然了这也能够体现出你在公司的不可替代性!</p> <h2 style="box-sizing: border-box;margin: 1em auto;color: rgb(119, 48, 152);font-weight: bold;padding-top: 0.5em;padding-bottom: 0.5em;font-size: 22px;min-height: 32px;line-height: 28px;border-bottom: 1px solid rgb(119, 48, 152);border-top-color: rgb(119, 48, 152);border-right-color: rgb(119, 48, 152);border-left-color: rgb(119, 48, 152);text-align: center;width: 262.987px;display: flex;flex-direction: column;justify-content: center;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">架构设计</span><span style="box-sizing: border-box;"></span></h2> <h3 style="box-sizing: border-box;margin-top: 1.2em;margin-bottom: 1em;color: rgb(119, 48, 152);font-weight: bold;font-size: 20px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">系统高性能 & 高可用</span></h3> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);" class="list-paddingleft-1"> <li style="box-sizing: border-box;"> <section style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;line-height: 26px;color: rgb(1, 1, 1);"> 使用缓存:缓存的作用是为了系统的读能力。将用户经常访问的数据扔到缓存里面可以有效地提高访问速度并且减少数据库的压力。 </section></li> </ul> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <img class="rich_pages wxw-img" data-ratio="0.44224924012158057" src="/upload/8e54725a6202003a307cbe07924c3896.png" data-type="png" data-w="658" style="box-sizing: border-box;vertical-align: middle;border-style: none;display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;"> <br> </figcaption> </figure> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);" class="list-paddingleft-1"> <li style="box-sizing: border-box;"> <section style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;line-height: 26px;color: rgb(1, 1, 1);"> 服务降级 & 限流:若短时间内流量激增影响到服务器性能,可考虑降级边缘业务以保证核心业务的可用性和性能。 </section></li> </ul> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <img class="rich_pages wxw-img" data-ratio="0.4322766570605187" src="/upload/44a9f5ba93ac8197328f82df4dd13fae.png" data-type="png" data-w="1041" style="box-sizing: border-box;vertical-align: middle;border-style: none;display: block;margin-right: auto;margin-left: auto;height: auto !important;"> </figure> <p style="margin-bottom: 0px;"><br></p> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;"> <span style="color: rgb(1, 1, 1);text-align: left;font-size: 15px;"></span> </figcaption> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: left;color: rgb(136, 136, 136);font-size: 14px;"> <span style="color: rgb(1, 1, 1);text-align: left;font-size: 15px;">分布式系统 & 服务拆分:</span> <span style="color: rgb(1, 1, 1);text-align: left;font-size: 15px;">将整个系统拆分成不同的业务模块再部署到对应的服务器中,服务之间通过中间件通信,可以有效地避免</span> </figcaption> </figure> <p style="margin-bottom: 0px;"><br></p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">和减少单一服务故障对整体系统的影响。</p> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <img class="rich_pages wxw-img" data-ratio="0.8859934853420195" src="/upload/e3e5234d8a05a03f0fc62f8bd252c742.png" data-type="png" data-w="614" style="box-sizing: border-box;vertical-align: middle;border-style: none;display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;"> <br> </figcaption> </figure> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);" class="list-paddingleft-1"> <li style="box-sizing: border-box;"> <section style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;line-height: 26px;color: rgb(1, 1, 1);"> 高可用架构:重要性不言而喻。同城多活、异地多活的架构部署可以保证单机房挂掉的情况下流量可以迅速切换到其他机房让核心业务不受影响。可谓是防止系统宕机必备良药啊! </section></li> </ul> <figure style="box-sizing: border-box;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"> <img class="rich_pages wxw-img" data-ratio="0.3461909353905497" src="/upload/3147861b5e9048399f0959023a698a7.png" data-type="png" data-w="1037" style="box-sizing: border-box;vertical-align: middle;border-style: none;display: block;margin-right: auto;margin-left: auto;height: auto !important;"> <figcaption style="box-sizing: border-box;margin-top: 5px;text-align: center;color: rgb(136, 136, 136);font-size: 14px;"> <br> </figcaption> </figure> <h1 style="box-sizing: border-box;margin-top: 1.2em;margin-bottom: 1em;color: rgb(119, 48, 152);font-weight: bold;font-size: 24px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);"><span style="box-sizing: border-box;">做好事故复盘</span><span style="box-sizing: border-box;"></span></h1> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">都说小事故伤身,大事故提桶。。一般发生事故后写一张事故单是不可避免的。除了详细描述好事故发生的经过,背锅人,解决方案,后续的事故跟进也是一系列流程的事,多则需要数周去跟进。事故的发生对于团队的技术发展和成型往往起着积极推进作用,所以对于每一个团队来说事故一定是不可避免的。每次事故发生我们都要思考如何完善系统,打破技术壁垒。并且遇到事儿也不要慌,如果是大问题,那么首先背锅的一定是leader!</p> <p style="box-sizing: border-box;margin: 1em 4px;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;letter-spacing: 0.75px;text-align: left;background-color: rgb(255, 255, 255);">其实呢一般公司最喜欢的是能快速解决问题的员工,即便这些问题可能是由你创造的。</p>
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">过去,我们运维着“能做一切”的大型单体应用程序。这是一种将产品推向市场的很好的方式,因为刚开始我们也只需要让我们的第一个应用上线。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">而且我们总是可以回头再来改进它的。部署一个大应用总是比构建和部署多个小块要容易。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">集中式:</span></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.423963133640553" src="/upload/28ee645244038197b28887d3e06526db.png" data-type="png" data-w="434" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">集群:</span></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.3970276008492569" src="/upload/2b5644630a796cea895c17d3d26812d0.png" data-type="png" data-w="471" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">分布式:</span></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.3866943866943867" src="/upload/5cd082ab2b3a0ab339ad06d647939205.png" data-type="png" data-w="481" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">分布式和集中式会配合使用。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">我们在搭建网站的时候,为了及时响应用户的请求,尤其是高并发请求的时候,我们需要搭建分布式集群来处理请求。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">我们一个服务器的处理能力是有限的。如果用我们一台设备当作服务器,那么当并发量比较大的时候,同一时间达到上百的访问量。那服务器就宕机了。然后只能重启服务器,当出现高并发访问的时候,就又会宕机。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">所以我们需要更多的服务器来并行工作,处理用户的请求。那么问题来了,我们服务器运行的时候,怎么分发大量的请求给不同的服务器呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">一般会采用(1apache+nTomcat)或者服务器模式来分发并处理请求。或者采用nginx分发请求。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">微服务是运行在自己的进程中的可独立部署的服务套件。他们通常使用 HTTP 资源进行通信,每个服务通常负责整个应用中的某一个单一的领域。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在流行的电子商务目录例子中,你可以有一个商品条目服务,一个审核服务和一个评价服务,每个都只专注一个领域。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">用这种方法让多语言服务(使用不同语言编写的服务)也成为可能,这样我们就可以让 Java/C++ 服务执行更多的计算密集型工作,让 Rails / Node.js 服务更多来支持前端应用等等。推荐公号:码猿技术专栏,回复关键词:1111 获取阿里内部java调优手册</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">微服务会成为大规模分布式应用的主流架构。任何复杂的工程问题都会归结为devide and conquer(分而治之),意思就是就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。微服务本质是对服务的拆分,与工程领域惯用的“分而治之”的思路是一致的。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);"><span style="color: rgb(248, 57, 41);">Spring Cloud 与 K8S 对比</span></span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">两个平台 Spring Cloud 和 Kubernetes 非常不同并且它们之间没有直接的相同特征。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.3847352024922118" src="/upload/9055dca6d07f45e3031f9c32772fbd0.png" data-type="png" data-w="642" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">两种架构处理了不同范围的MSA障碍,并且它们从根本上用了不同的方法。Spring Cloud方法是试图解决在JVM中每个MSA挑战,然而Kubernetes方法是试图让问题消失,为开发者在平台层解决。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Spring Cloud在JVM中非常强大,Kubernetes管理那些JVM很强大。同样的,它就像一个自然发展,结合两种工具并且从两个项目中最好的部分受益。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">可以看到,里面差不多一半关注点是和运维相关的。这么看来,似乎拿spring cloud和kubernetes比较有点不公平,spring cloud只是一个开发框架,对于应用如何部署和调度是无能为力的,而kubernetes是一个运维平台。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">也许用spring cloud+cloud foundry去和kubernetes比较才更加合理,但需要注意的是,即使加入了cloud foundry的paas能力,spring cloud仍然是“侵入式”的且语言相关,而kubernetes是“非侵入式”的且语言无关。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);"><span style="color: rgb(248, 57, 41);">Spring Cloud vs Istio</span></span></h2> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.48314606741573035" src="/upload/17410c27132f0be4a47cac775ffa2990.png" data-type="png" data-w="979" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这里面哪些内容是我们可以拿掉或者说基于 Service Mesh(以 Istio 为例)能力去做的?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">分析下来,可以替换的组件包括网关(gateway 或者 Zuul,由Ingress gateway 或者 egress 替换),熔断器(hystrix,由SideCar替换),注册中心(Eureka及Eureka client,由Polit,SideCar 替换),负责均衡(Ribbon,由SideCar 替换),链路跟踪及其客户端(Pinpoint 及 Pinpoint client,由 SideCar 及Mixer替换)。推荐公号:码猿技术专栏,回复关键词:1111 获取阿里内部java调优手册</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这是我们在 Spring Cloud 解析中需要完成的目标:即确定需要删除或者替换的支撑模块。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="1.5075" src="/upload/3e7f605c6545d0f2a789d6f2bde32091.png" data-type="png" data-w="400" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">可以说,springcloud关注的功能是kubernetes的一个子集。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">可以看出,两边的解决方案都是比较完整的。kubernetes这边,在Istio还没出来以前,其实只能提供最基础的服务注册、服务发现能力(service只是一个4层的转发代理),istio出来以后,具有了相对完整的微服务能力。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">而spring cloud这边,除了发布、调度、自愈这些运维平台的功能,其他的功能也支持的比较全面。相对而言,云厂商会更喜欢kubernetes的方案,原因就是三个字:非侵入。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">平台能力与应用层的解耦,使得云厂商可以非常方便的升级、维护基础设施而不需要去关心应用的情况,这也是我比较看好service mesh这类技术前景的原因。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">Spring Boot + K8S</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如果不用 Spring Cloud,那就是使用 Spring Boot + K8S。推荐:<a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU3MDAzNDg1MA==&action=getalbum&album_id=1532834475389288449#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">SpringBoot实战教程</a></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.75" src="/upload/479f0fb3d9107bcd99347bf10bfb6986.png" data-type="png" data-w="960" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这里就需要介绍一个项目,Spring Cloud Kubernetes,作用是把kubernetes中的服务模型映射到Spring Cloud的服务模型中,以使用Spring Cloud的那些原生sdk在kubernetes中实现服务治理。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">具体来说,就是把k8s中的services对应到Spring Cloud中的services,k8s中的endpoints对应到Spring Cloud的instances。这样通过标准的Spring Cloud api就可以对接k8的服务治理体系。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">老实说,个人认为这个项目的意义并不是很大,毕竟都上k8了,k8本身已经有了比较完善的微服务能力(有注册中心、配置中心、负载均衡能力),应用之间直接可以互相调用,应用完全无感知,你再通过sdk去调用,有点多此一举的感觉。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">而且现在强调的是语言非侵入,Spring Cloud一个很大的限制是只支持java语言(甚至比较老的j2ee应用都不支持,只支持Spring Boot应用)。所以我个人感觉,这个项目,在具体业务服务层面,使用的范围非常有限。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">借助于Spring Cloud Kubernetes项目,zuul可以以一种无侵入的方式提供api网关的能力,应用完全不需要做任何改造,并且网关是可插拔的,将来可以用其他网关产品灵活替换,整体耦合程度非常低。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">得益于k8的service能力,zuul甚至支持异构应用的接入,这是Spring Cloud体系所不具备的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">而本身基于java开发,使得java程序员可以方便的基于zuul开发各种功能复杂的filter,而不需要去学习go或者openresty这样不太熟悉的语言。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);"><span style="color: rgb(248, 57, 41);">Service Mesh的价值</span></span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">无论是单体应用,还是分布式应用,都可以建立在Service Mesh上,mesh上的sidecar支撑了所有的上层应用,业务开发者无须关心底层构成,可以用Java,也可以用Go等语言完成自己的业务开发。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当微服务架构体系越来越复杂的时候,需要将“业务服务”和“基础设施”解耦,将一个微服务进程一分为二:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.2998204667863555" src="/upload/fdc9f9aa5fda546e374ea43d062a313c.png" data-type="png" data-w="557" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">为什么代理会叫sidecar proxy?</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-ratio="0.7157360406091371" src="/upload/c9dfa989f9e22668e736618e70301d53.png" data-type="png" data-w="394" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">看了上图就容易懂了,biz和proxy相生相伴,就像摩托车(motor)与旁边的车厢(sidecar)。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">未来,sidecar和proxy就指微服务进程解耦成两个进程之后,提供基础能力的那个代理进程。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Istio的理论概念是Service Mesh(服务网络),我们不必纠结于概念实际也是微服务的一种落地形式有点类似上面的SideCar模式。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">它的主要思想是关注点分离,即不像SpringCloud一样交给研发来做,也不集成到k8s中产生职责混乱,Istio是通过为服务配 Agent代理来提供服务发现、负截均衡、限流、链路跟踪、鉴权等微服务治理手段。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">Istio开始就是与k8s结合设计的,Istio结合k8s可以牛逼的落地微服务架构。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">istio 超越 spring cloud和dubbo 等传统开发框架之处, 就在于不仅仅带来了远超这些框架所能提供的功能, 而且也不需要应用程序为此做大量的改动,开发人员也不必为上面的功能实现进行大量的知识储备。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">但结论是不是 spring cloud 能做到的,k8s + istio 也能做到?甚至更好?</p> </section>
作者:微信小助手
<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="MzkyNTI5NTQ1NQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/wxcY9TH8dPsYAnrjaZktBe0iahF8ic9QkF26cAw8pK6HPR1bfFEImdyJspvkQvQwmnYxP4eEVW60ewVVickcWXnrQ/0?wx_fmt=png" data-nickname="架构文摘" data-alias="ArchDigest" data-signature="每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性能、高稳定)、大数据、机器学习、Java架构等各个热门领域。" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> <p><br></p> <section data-mpa-template="t" mpa-from-tpl="t" data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgb(163, 163, 163)" data-darkmode-original-color-16624447131116="#fff|rgb(34, 34, 34)" data-style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; color: rgb(34, 34, 34); font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; font-size: 17px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); visibility: visible; box-sizing: border-box !important; overflow-wrap: break-word !important;" class="js_darkmode__2" mp-original-font-size="17" mp-original-line-height="27.200000762939453" style="outline: 0px;max-width: 100%;caret-color: rgb(34, 34, 34);color: rgb(34, 34, 34);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;white-space: normal;text-size-adjust: auto;background-color: rgb(255, 255, 255);visibility: visible;line-height: 27.2px;box-sizing: border-box !important;overflow-wrap: break-word !important;margin-bottom: 24px;"> <section data-mid="" mpa-from-tpl="t" data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgb(163, 163, 163)" data-darkmode-original-color-16624447131116="#fff|rgb(34, 34, 34)" mp-original-font-size="17" mp-original-line-height="27.200000762939453" style="outline: 0px;max-width: 100%;display: flex;justify-content: center;align-items: center;width: 596px;visibility: visible;line-height: 27.2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section data-mid="" mpa-from-tpl="t" data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgb(163, 163, 163)" data-darkmode-original-color-16624447131116="#fff|rgb(34, 34, 34)" mp-original-font-size="17" mp-original-line-height="27.200000762939453" style="outline: 0px;max-width: 100%;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;line-height: 27.2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section data-mid="" mpa-from-tpl="t" data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgb(163, 163, 163)" data-darkmode-original-color-16624447131116="#fff|rgb(34, 34, 34)" data-style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; z-index: 1; display: flex; justify-content: center; align-items: center; height: 36px; border-width: 1px 0px; border-top-style: dotted; border-bottom-style: dotted; border-top-color: rgb(193, 205, 249); border-bottom-color: rgb(193, 205, 249); visibility: visible; box-sizing: border-box !important; overflow-wrap: break-word !important;" class="js_darkmode__3" mp-original-font-size="17" mp-original-line-height="27.200000762939453" style="outline: 0px;max-width: 100%;z-index: 1;display: flex;justify-content: center;align-items: center;height: 36px;border-width: 1px 0px;border-top-style: dotted;border-bottom-style: dotted;border-top-color: rgb(193, 205, 249);border-bottom-color: rgb(193, 205, 249);visibility: visible;line-height: 27.2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section data-mid="" mpa-from-tpl="t" data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgb(163, 163, 163)" data-darkmode-original-color-16624447131116="#fff|rgb(34, 34, 34)" mp-original-font-size="17" mp-original-line-height="27.200000762939453" style="padding-right: 12px;padding-left: 12px;outline: 0px;max-width: 100%;text-align: center;visibility: visible;line-height: 27.2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <p data-mid="" mpa-is-content="t" data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgb(68, 104, 248)" data-darkmode-original-color-16624447131116="#fff|rgb(34, 34, 34)|rgb(68, 104, 248)" mp-original-font-size="16" mp-original-line-height="22" style="outline: 0px;max-width: 100%;font-size: 16px;font-family: PingFangSC-Semibold, "PingFang SC";font-weight: bold;color: rgb(68, 104, 248);line-height: 22px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">背景</p> </section> <section data-mid="" mpa-from-tpl="t" data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgb(163, 163, 163)" data-darkmode-original-color-16624447131116="#fff|rgb(34, 34, 34)" mp-original-font-size="17" mp-original-line-height="27.200000762939453" style="outline: 0px;max-width: 100%;width: 7px;height: 9px;transform: rotate(180deg);visibility: visible;line-height: 27.2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <img class="rich_pages wxw-img" data-ratio="1.2857142857142858" src="/upload/528d6047c1f8fc974a14ee4f59de28c0.png" data-w="14" style="outline: 0px;display: block;line-height: 27.2px;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 14px !important;"> </section> </section> </section> </section> </section> <p data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgb(163, 163, 163)" data-darkmode-original-color-16624447131116="#fff|rgb(34, 34, 34)" data-style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; clear: both; min-height: 1em; color: rgb(34, 34, 34); font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; font-size: 17px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); visibility: visible; box-sizing: border-box !important; overflow-wrap: break-word !important;" class="js_darkmode__4" mp-original-font-size="17" mp-original-line-height="27.200000762939453" style="outline: 0px;max-width: 100%;caret-color: rgb(34, 34, 34);color: rgb(34, 34, 34);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;white-space: normal;text-size-adjust: auto;background-color: rgb(255, 255, 255);visibility: visible;line-height: 27.2px;box-sizing: border-box !important;overflow-wrap: break-word !important;margin-bottom: 24px;"><br mpa-from-tpl="t" data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgb(163, 163, 163)" data-darkmode-original-color-16624447131116="#fff|rgb(34, 34, 34)" mp-original-font-size="17" mp-original-line-height="27.200000762939453" style="outline: 0px;max-width: 100%;visibility: visible;line-height: 27.2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <blockquote data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgba(163, 163, 163, 0.5)" data-darkmode-original-color-16624447131116="#fff|rgba(0, 0, 0, 0.5)" data-style="margin: 1em 0px; padding: 4px 0px 0px 10px; outline: 0px; border-left-width: 3px; border-left-style: solid; border-left-color: rgb(219, 219, 219); color: rgba(0, 0, 0, 0.5); font-size: 15px; max-width: 100%; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); visibility: visible; box-sizing: border-box !important; overflow-wrap: break-word !important;" class="js_darkmode__5" mp-original-font-size="15" mp-original-line-height="24" style="outline: 0px;color: rgba(0, 0, 0, 0.5);max-width: 100%;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;white-space: normal;text-size-adjust: auto;background-color: rgb(255, 255, 255);visibility: visible;line-height: 24px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <p data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgba(163, 163, 163, 0.5)" data-darkmode-original-color-16624447131116="#fff|rgba(0, 0, 0, 0.5)" mp-original-font-size="15" mp-original-line-height="24" style="outline: 0px;max-width: 100%;visibility: visible;line-height: 24px;box-sizing: border-box !important;overflow-wrap: break-word !important;">业务飞速发展导致数据规模急速膨胀,单机的数据库已经无法满足互联网业务的发展。</p> <p data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgba(163, 163, 163, 0.5)" data-darkmode-original-color-16624447131116="#fff|rgba(0, 0, 0, 0.5)" mp-original-font-size="15" mp-original-line-height="24" style="outline: 0px;max-width: 100%;visibility: visible;line-height: 24px;box-sizing: border-box !important;overflow-wrap: break-word !important;">传统的将数据集中存储单一数据结节的方案,在容量、性能、可用性和可维护性方面已经难以满足互联网海量数据的场景。</p> <p data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgba(163, 163, 163, 0.5)" data-darkmode-original-color-16624447131116="#fff|rgba(0, 0, 0, 0.5)" mp-original-font-size="15" mp-original-line-height="24" style="outline: 0px;max-width: 100%;visibility: visible;line-height: 24px;box-sizing: border-box !important;overflow-wrap: break-word !important;">从容量方面考虑,单机数据库容量有限,难以扩容。</p> <p data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgba(163, 163, 163, 0.5)" data-darkmode-original-color-16624447131116="#fff|rgba(0, 0, 0, 0.5)" mp-original-font-size="15" mp-original-line-height="24" style="outline: 0px;max-width: 100%;visibility: visible;line-height: 24px;box-sizing: border-box !important;overflow-wrap: break-word !important;">从性能方面来说,由于关系型数据库大多数采用B+树类型索引,在数据量超过一定的阈值后,索引的深度增加导致对磁盘的随机IO次数增加,进而导致性能问题。</p> <p data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgba(163, 163, 163, 0.5)" data-darkmode-original-color-16624447131116="#fff|rgba(0, 0, 0, 0.5)" mp-original-font-size="15" mp-original-line-height="24" style="outline: 0px;max-width: 100%;visibility: visible;line-height: 24px;box-sizing: border-box !important;overflow-wrap: break-word !important;">从可用性方面来说,服务通常设计成无状态的,这必然导致系统的存储压力都集中在数据库层面,而单一的数据节点,或者简单的主从架构,已经越来越难以承担。</p> <p data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgba(163, 163, 163, 0.5)" data-darkmode-original-color-16624447131116="#fff|rgba(0, 0, 0, 0.5)" mp-original-font-size="15" mp-original-line-height="24" style="outline: 0px;max-width: 100%;visibility: visible;line-height: 24px;box-sizing: border-box !important;overflow-wrap: break-word !important;">从运维角度来看,当数据都集中在一个节点上时,数据备份和恢复的时间成本也随之数据量上升变得不可控。同时数据丢失导致影响的范围也会被放大。</p> </blockquote> <p data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgb(163, 163, 163)" data-darkmode-original-color-16624447131116="#fff|rgb(34, 34, 34)" data-style="margin: 0px 0px 0em; padding: 0px; outline: 0px; max-width: 100%; clear: both; min-height: 1em; color: rgb(34, 34, 34); font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; font-size: 17px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); box-sizing: border-box !important; overflow-wrap: break-word !important;" class="js_darkmode__6" mp-original-font-size="17" mp-original-line-height="27.200000762939453" style="margin-bottom: 0em;outline: 0px;max-width: 100%;caret-color: rgb(34, 34, 34);color: rgb(34, 34, 34);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;white-space: normal;text-size-adjust: auto;background-color: rgb(255, 255, 255);visibility: visible;line-height: 27.2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-ratio="0.3164933135215453" src="/upload/a2a837f0e4cd4a5e98e57169e59db64.jpg" data-type="other" data-w="1346" style="outline: 0px;line-height: 27.2px;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 596px !important;"></p> <h3 data-id="heading-1" data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-darkmode-color-16624447131116="rgb(163, 163, 163)" data-darkmode-original-color-16624447131116="#fff|rgb(34, 34, 34)" data-style="margin: 0px; padding: 0px; outline: 0px; font-weight: 400; font-size: 16px; max-width: 100%; color: rgb(34, 34, 34); font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); box-sizing: border-box !important; overflow-wrap: break-word !important;" class="js_darkmode__7" mp-original-font-size="16" mp-original-line-height="25.600000381469727" style="outline: 0px;max-width: 100%;caret-color: rgb(34, 34, 34);color: rgb(34, 34, 34);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;white-space: normal;text-size-adjust: auto;background-color: rgb(255, 255, 255);visibility: visible;line-height: 25.6px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br mpa-from-tpl="t" data-darkmode-bgcolor-16624447131116="rgb(25, 25, 25)" data-darkmode-original-bgcolor-16624447131116="#fff|rgb(255, 255, 255)" data-d
作者:微信小助手
<fieldset style="margin: 0.8em 33.5px 0.3em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;max-width: 100%;box-sizing: border-box;min-width: 0px;line-height: 25.6px;border-width: initial;border-style: initial;border-color: currentcolor;text-align: center;background-color: rgb(255, 255, 255);overflow-wrap: break-word !important;" data-mpa-powered-by="yiban.io"> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzkyNTI5NTQ1NQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/wxcY9TH8dPsYAnrjaZktBe0iahF8ic9QkF26cAw8pK6HPR1bfFEImdyJspvkQvQwmnYxP4eEVW60ewVVickcWXnrQ/0?wx_fmt=png" data-nickname="架构文摘" data-alias="ArchDigest" data-signature="每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性能、高稳定)、大数据、机器学习、Java架构等各个热门领域。" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> </fieldset> <section> <br>