|
|
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}.code-container:hover .copy-btn,.highlight:hover .copy-btn{opacity:1}.code-container{position:relative}.copy-btn{color:#333;cursor:pointer;line-height:1.6;opacity:0;padding:2px 6px;position:absolute;transition:opacity .2s ease-in-out;background-color:#eee;background-image:linear-gradient(#fcfcfc,#eee);border:1px solid #d5d5d5;border-radius:3px;font-size:.8125em;right:4px;top:8px}code,figure.highlight,kbd,pre{background:var(--highlight-background);color:var(--highlight-foreground)}figure.highlight,pre{line-height:1.6;margin:0 auto 20px}figure.highlight figcaption,pre .caption,pre figcaption{background:var(--highlight-gutter-background);color:var(--highlight-foreground);display:flow-root;font-size:.875em;line-height:1.2;padding:.5em}figure.highlight figcaption a,pre .caption a,pre figcaption a{color:var(--highlight-foreground);float:right}figure.highlight figcaption a:hover,pre .caption a:hover,pre figcaption a:hover{border-bottom-color:var(--highlight-foreground)}code,pre{font-family:consolas,Menlo,monospace,'PingFang SC','Microsoft YaHei'}code{border-radius:3px;font-size:.875em;padding:2px 4px;overflow-wrap:break-word}kbd{border:2px solid #ccc;border-radius:.2em;box-shadow:.1em .1em .2em rgba(0,0,0,.1);font-family:inherit;padding:.1em .3em;white-space:nowrap}figure.highlight{overflow:auto;position:relative}figure.highlight pre{border:0;margin:0;padding:10px 0}figure.highlight table{border:0;margin:0;width:auto}figure.highlight td{border:0;padding:0}figure.highlight .gutter{-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}figure.highlight .gutter pre{background:var(--highlight-gutter-background);color:var(--highlight-gutter-foreground);padding-left:10px;padding-right:10px;text-align:right}figure.highlight .code pre{padding-left:10px;width:100%}figure.highlight .marked{background:rgba(0,0,0,.3)}pre .caption,pre figcaption{margin-bottom:10px}.gist table{width:auto}.gist table td{border:0}pre code{background:0 0;padding:0;text-shadow:none}.blockquote-center{border-left:0;margin:40px 0;padding:0;position:relative;text-align:center}.blockquote-center::after,.blockquote-center::before{left:0;line-height:1;opacity:.6;position:absolute;width:100%}.blockquote-center::before{border-top:1px solid #ccc;text-align:left;top:-20px;content:'\f10d';font-family:'Font Awesome 6 Free';font-weight:900}.blockquote-center::after{border-bottom:1px solid #ccc;bottom:-20px;text-align:right;content:'\f10e';font-family:'Font Awesome 6 Free';font-weight:900}.blockquote-center div,.blockquote-center p{text-align:center}.group-picture{margin-bottom:20px}.group-picture .group-picture-row{display:flex;gap:3px;margin-bottom:3px}.group-picture .group-picture-column{flex:1}.group-picture .group-picture-column img{height:100%;margin:0;object-fit:cover;width:100%}.post-body .label{color:#555;padding:0 2px}.post-body .label.default{background:#f0f0f0}.post-body .label.primary{background:#efe6f7}.post-body .label.info{background:#e5f2f8}.post-body .label.success{background:#e7f4e9}.post-body .label.warning{background:#fcf6e1}.post-body .label.danger{background:#fae8eb}.post-body .link-grid{display:grid;grid-gap:1.5rem;gap:1.5rem;grid-template-columns:1fr 1fr;margin-bottom:20px;padding:1rem}.post-body .link-grid .link-grid-container{border:solid #ddd;box-shadow:1rem 1rem .5rem rgba(0,0,0,.5);min-height:5rem;min-width:0;padding:.5rem;position:relative;transition:background .3s}.post-body .link-grid .link-grid-container:hover{animation:.5s next-shake;background:var(--card-bg-color)}.post-body .link-grid .link-grid-container:active{box-shadow:.5rem .5rem .25rem rgba(0,0,0,.5);transform:translate(.2rem,.2rem)}.post-body .link-grid .link-grid-container .link-grid-image{border:1px solid #ddd;border-radius:50%;box-sizing:border-box;height:5rem;padding:3px;position:absolute;width:5rem}.post-body .link-grid .link-grid-container p{margin:0 1rem 0 6rem}.post-body .link-grid .link-grid-container p:first-of-type{font-size:1.2em}.post-body .link-grid .link-grid-container p:last-of-type{font-size:.8em;line-height:1.3rem;opacity:.7}.post-body .link-grid .link-grid-container a{border:0;height:100%;left:0;position:absolute;top:0;width:100%}@keyframes next-shake{0%{transform:translate(1pt,1pt) rotate(0)}10%{transform:translate(-1pt,-2pt) rotate(-1deg)}20%{transform:translate(-3pt,0) rotate(1deg)}30%{transform:translate(3pt,2pt) rotate(0)}40%{transform:translate(1pt,-1pt) rotate(1deg)}50%{transform:translate(-1pt,2pt) rotate(-1deg)}60%{transform:translate(-3pt,1pt) rotate(0)}70%{transform:translate(3pt,1pt) rotate(-1deg)}80%{transform:translate(-1pt,-1pt) rotate(1deg)}90%{transform:translate(1pt,2pt) rotate(0)}100%{transform:translate(1pt,-2pt) rotate(-1deg)}}.post-body .note{border-radius:3px;margin-bottom:20px;padding:1em;position:relative;border:1px solid #eee;border-left-width:5px}.post-body .note summary{cursor:pointer;outline:0}.post-body .note summary p{display:inline}.post-body .note h2,.post-body .note h3,.post-body .note h4,.post-body .note h5,.post-body .note h6{border-bottom:initial;margin:0;padding-top:0}.post-body .note :first-child{margin-top:0}.post-body .note :last-child{margin-bottom:0}.post-body .note.default{border-left-color:#777}.post-body .note.default h2,.post-body .note.default h3,.post-body .note.default h4,.post-body .note.default h5,.post-body .note.default h6{color:#777}.post-body .note.primary{border-left-color:#6f42c1}.post-body .note.primary h2,.post-body .note.primary h3,.post-body .note.primary h4,.post-body .note.primary h5,.post-body .note.primary h6{color:#6f42c1}.post-body .note.info{border-left-color:#428bca}.post-body .note.info h2,.post-body .note.info h3,.post-body .note.info h4,.post-body .note.info h5,.post-body .note.info h6{color:#428bca}.post-body .note.success{border-left-color:#5cb85c}.post-body .note.success h2,.post-body .note.success h3,.post-body .note.success h4,.post-body .note.success h5,.post-body .note.success h6{color:#5cb85c}.post-body .note.warning{border-left-color:#f0ad4e}.post-body .note.warning h2,.post-body .note.warning h3,.post-body .note.warning h4,.post-body .note.warning h5,.post-body .note.warning h6{color:#f0ad4e}.post-body .note.danger{border-left-color:#d9534f}.post-body .note.danger h2,.post-body .note.danger h3,.post-body .note.danger h4,.post-body .note.danger h5,.post-body .note.danger h6{color:#d9534f}.post-body .tabs{margin-bottom:20px}.post-body .tabs,.tabs-comment{padding-top:10px}.post-body .tabs ul.nav-tabs,.tabs-comment ul.nav-tabs{background:var(--content-bg-color);display:flex;display:flex;flex-wrap:wrap;justify-content:center;margin:0;padding:0;position:-webkit-sticky;position:sticky;top:0;z-index:5}.post-body .tabs ul.nav-tabs li.tab,.tabs-comment ul.nav-tabs li.tab{border-bottom:1px solid #ddd;border-left:1px solid transparent;border-right:1px solid transparent;border-radius:0;border-top:3px solid transparent;flex-grow:1;list-style-type:none}@media (max-width:413px){.post-body .tabs ul.nav-tabs,.tabs-comment ul.nav-tabs{display:block;margin-bottom:5px}.post-body .tabs ul.nav-tabs li.tab,.tabs-comment ul.nav-tabs li.tab{border-bottom:1px solid transparent;border-left:3px solid transparent;border-right:1px solid transparent;border-top:1px solid transparent;border-radius:0}}.post-body .tabs ul.nav-tabs li.tab a,.tabs-comment ul.nav-tabs li.tab a{border-bottom:initial;display:block;line-height:1.8;padding:.25em .75em;text-align:center;transition:.2s ease-out}.post-body .tabs ul.nav-tabs li.tab a i[class^=fa],.tabs-comment ul.nav-tabs li.tab a i[class^=fa]{width:1.285714285714286em}.post-body .tabs ul.nav-tabs li.tab.active,.tabs-comment ul.nav-tabs li.tab.active{border-color:#fc6423 #ddd transparent}@media (max-width:413px){.post-body .tabs ul.nav-tabs li.tab.active,.tabs-comment ul.nav-tabs li.tab.active{border-color:#ddd #ddd #ddd #fc6423}}.post-body .tabs ul.nav-tabs li.tab.active a,.tabs-comment ul.nav-tabs li.tab.active a{cursor:default}.post-body .tabs .tab-content,.tabs-comment .tab-content{border:1px solid #ddd;border-radius:0;border-top-color:transparent}@media (max-width:413px){.post-body .tabs .tab-content,.tabs-comment .tab-content{border-radius:0;border-top-color:#ddd}}.post-body .tabs .tab-content .tab-pane,.tabs-comment .tab-content .tab-pane{padding:20px 20px 0}.post-body .tabs .tab-content .tab-pane:not(.active),.tabs-comment .tab-content .tab-pane:not(.active){display:none}.pagination .next,.pagination .page-number,.pagination .prev,.pagination .space{display:inline-block;margin:-1px 10px 0;padding:0 10px}.algolia-pagination .current .page-number,.pagination .page-number.current{background:#ccc;border-color:#ccc;color:var(--content-bg-color)}.pagination{border-top:1px solid #eee;margin:120px 0 0;text-align:center}.pagination .next,.pagination .page-number,.pagination .prev{border-bottom:0;border-top:1px solid #eee;transition:border-color .2s ease-in-out}.pagination .next:hover,.pagination .page-number:hover,.pagination .prev:hover{border-top-color:var(--link-hover-color)}@media (max-width:767px){.post-body .link-grid{grid-template-columns:1fr}.pagination .next,.pagination .page-number,.pagination .prev,.pagination .space{margin:0 5px}.pagination{border-top:0}.pagination .next,.pagination .page-number,.pagination .prev{border-bottom:1px solid #eee;border-top:0}.pagination .next:hover,.pagination .page-number:hover,.pagination .prev:hover{border-bottom-color:var(--link-hover-color)}.site-meta{text-align:center}}.pagination .space{margin:0;padding:0}.comments{margin-top:60px;overflow:hidden}.comment-button-group{display:flex;display:flex;flex-wrap:wrap;justify-content:center;justify-content:center;margin:1em 0}.comment-button-group .comment-button{margin:.1em .2em}.comment-button-group .comment-button.active{background:var(--btn-default-hover-bg);border-color:var(--btn-default-hover-border-color);color:var(--btn-default-hover-color)}.comment-position{display:none}.comment-position.active{display:block}.tabs-comment{margin-top:4em;padding-top:0}.tabs-comment .comments{margin-top:0;padding-top:0}.headband{background:var(--theme-color);height:3px}@media (max-width:991px){.headband{display:none}}.site-brand-container{display:flex;flex-shrink:0;padding:0 10px}.use-motion .column,.use-motion .site-brand-container .toggle{opacity:0}.site-meta{flex-grow:1;text-align:center}.custom-logo-image{margin-top:20px}@media (max-width:991px){.custom-logo-image{display:none}}.brand{border-bottom:0;color:var(--brand-color);display:inline-block;padding:0}.brand:hover{color:var(--brand-hover-color)}.site-title{font-family:Lato,'PingFang SC','Microsoft YaHei',sans-serif;font-size:1.375em;font-weight:400;line-height:1.5;margin:0}.site-subtitle{color:#ddd;font-size:.8125em;margin:10px 10px 0}.use-motion .custom-logo-image,.use-motion .site-subtitle,.use-motion .site-title{opacity:0;position:relative;top:-10px}.site-nav-right,.site-nav-toggle{display:none}.site-nav-right .toggle,.site-nav-toggle .toggle{color:var(--text-color);padding:10px;width:22px}.site-nav-right .toggle .toggle-line,.site-nav-toggle .toggle .toggle-line{background:var(--text-color);border-radius:1px}@media (max-width:767px){.site-nav-right,.site-nav-toggle{display:flex;flex-direction:column;justify-content:center}.site-nav{--scroll-height:0;height:0;overflow:hidden;transition:height .2s ease-in-out,visibility .2s ease-in-out;visibility:hidden}body:not(.site-nav-on) .site-nav .animated{animation:none}body.site-nav-on .site-nav{height:var(--scroll-height);visibility:unset}}.menu{margin:0;padding:1em 0;text-align:center}.menu-item{display:inline-block;list-style:none;margin:0 10px}@media (max-width:767px){.menu-item{display:block;margin-top:10px}.menu-item.menu-item-search{display:none}}.menu-item a{border-bottom:0;display:block;font-size:.8125em;transition:border-color .2s ease-in-out}.menu-item a.menu-item-active,.menu-item a:hover{background:var(--menu-item-bg-color)}.menu-item i[class^=fa]{margin-right:8px}.menu-item .badge{display:inline-block;font-weight:700;line-height:1;margin-left:.35em;margin-top:.35em;text-align:center;white-space:nowrap}.use-motion .menu-item{visibility:hidden}.github-corner :hover .octo-arm{animation:560ms ease-in-out octocat-wave}.github-corner svg{color:#fff;fill:var(--theme-color);position:absolute;right:0;top:0;z-index:5}@media (max-width:991px){.github-corner{display:none}.github-corner svg{color:var(--theme-color);fill:#fff}.github-corner .github-corner:hover .octo-arm{animation:none}.github-corner .github-corner .octo-arm{animation:560ms ease-in-out octocat-wave}}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}.sidebar-inner{color:#999;padding:18px 10px;text-align:center;display:flex;flex-direction:column;justify-content:center}.cc-license .cc-opacity{border-bottom:0;opacity:.7}.cc-license .cc-opacity:hover{opacity:.9}.cc-license img{display:inline-block}.site-author-image{border:1px solid #eee;max-width:120px;padding:2px}.site-author-name{color:var(--text-color);font-weight:600;margin:0}.site-description{color:#999;font-size:.8125em;margin-top:0}.links-of-author a{font-size:.8125em}.links-of-author i[class^=fa]{margin-right:2px}.sidebar .sidebar-button:not(:first-child){margin-top:15px}.sidebar .sidebar-button button{background:0 0;cursor:pointer;line-height:2;padding:0 15px;border-radius:4px}.sidebar .sidebar-button button i[class^=fa]{margin-right:5px}.links-of-blogroll{font-size:.8125em}.links-of-blogroll-title{font-size:.875em;font-weight:600}.links-of-blogroll-list{list-style:none;margin:0;padding:0}.sidebar-nav{font-size:.875em;height:0;margin:0;overflow:hidden;padding-left:0;pointer-events:none;transition:height .2s ease-in-out,visibility .2s ease-in-out;visibility:hidden}.sidebar-nav-active .sidebar-nav{height:calc(2em + 1px);pointer-events:unset;visibility:unset}.sidebar-nav li{border-bottom:1px solid transparent;color:var(--text-color);cursor:pointer;display:inline-block;transition:border-bottom-color .2s ease-in-out,color .2s ease-in-out}.sidebar-nav li.sidebar-nav-overview{margin-left:10px}.sidebar-nav li:hover{color:#fc6423}.sidebar-overview-active .sidebar-nav-overview,.sidebar-toc-active .sidebar-nav-toc{border-bottom-color:#fc6423;color:#fc6423;transition-delay:0.2s}.sidebar-overview-active .sidebar-nav-overview:hover,.sidebar-toc-active .sidebar-nav-toc:hover{color:#fc6423}.sidebar-panel-container{align-items:start;display:grid;flex:1;overflow-x:hidden;overflow-y:auto;padding-top:0;transition:padding-top .2s ease-in-out}.sidebar-nav-active .sidebar-panel-container{padding-top:20px}.sidebar-panel{animation:.2s ease-in-out deactivate-sidebar-panel;grid-area:1/1;height:0;opacity:0;overflow:hidden;pointer-events:none;transform:translateY(0);transition:.2s ease-in-out;transition-property:opacity,transform,visibility;visibility:hidden}.sidebar-nav-active .sidebar-panel,.sidebar-overview-active .sidebar-panel.post-toc-wrap{transform:translateY(-20px)}.sidebar-overview-active:not(.sidebar-nav-active) .sidebar-panel.post-toc-wrap{transition-delay:0s,0.2s,0s}.sidebar-overview-active .sidebar-panel.site-overview-wrap,.sidebar-toc-active .sidebar-panel.post-toc-wrap{animation-name:activate-sidebar-panel;height:auto;opacity:1;pointer-events:unset;transform:translateY(0);transition-delay:0.2s,0.2s,0s;visibility:unset}.sidebar-panel.site-overview-wrap{display:flex;flex-direction:column;justify-content:center;gap:10px;justify-content:flex-start}@keyframes deactivate-sidebar-panel{from{height:var(--inactive-panel-height,0)}to{height:var(--active-panel-height,0)}}@keyframes activate-sidebar-panel{from{height:var(--inactive-panel-height,auto)}to{height:var(--active-panel-height,auto)}}.sidebar-toggle{bottom:61px;height:16px;padding:5px;width:16px;background:#222;cursor:pointer;opacity:.6;position:fixed;z-index:30;right:30px}.sidebar-toggle:hover{opacity:.8}@media (max-width:991px){.sidebar-toggle{right:20px;opacity:.8}}.sidebar-toggle:hover .toggle-line{background:#fc6423}@media (any-hover:hover){body:not(.sidebar-active) .sidebar-toggle:hover :first-child{left:50%;top:2px;transform:rotate(45deg);width:50%}body:not(.sidebar-active) .sidebar-toggle:hover :last-child{left:50%;top:-2px;transform:rotate(-45deg);width:50%}}.sidebar-active .sidebar-toggle :nth-child(2){opacity:0}.sidebar-active .sidebar-toggle :first-child{top:6px;transform:rotate(45deg)}.sidebar-active .sidebar-toggle :last-child{top:-6px;transform:rotate(-45deg)}.post-toc{font-size:.875em}.post-toc ol{list-style:none;margin:0;padding:0 2px 0 10px;text-align:left}.post-toc ol>:last-child{margin-bottom:5px}.post-toc ol>ol{padding-left:0}.post-toc ol a{transition:.2s ease-in-out}.post-toc .nav-item{line-height:1.8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.post-toc .nav .nav-child{--height:0;height:0;opacity:0;overflow:hidden;transition:.2s ease-in-out;visibility:hidden}.post-toc .nav .active>.nav-child{height:var(--height,auto);opacity:1;visibility:unset}.post-toc .nav .active>a{border-bottom-color:#fc6423;color:#fc6423}.post-toc .nav .active-current>a,.post-toc .nav .active-current>a:hover{color:#fc6423}.site-state{display:flex;flex-wrap:wrap;justify-content:center;line-height:1.4}.site-state-item a{border-bottom:0;display:block}.site-state-item-count{display:block;font-size:1em;font-weight:600}.site-state-item-name{color:#999;font-size:.8125em}.sidebar-post-related{font-size:.8125em;padding:18px 0 0}.popular-posts{margin:0;padding:1em 0;text-align:left}.popular-posts .popular-posts-item{display:block}.popular-posts .popular-posts-item .popular-posts-link{border-bottom:0;display:block;padding:5px 20px;transition:background .2s ease-in-out}.popular-posts .popular-posts-item .popular-posts-link:hover{background:var(--menu-item-bg-color)}.popular-posts .popular-posts-item .popular-posts-time{color:#999}.footer{color:#999;font-size:.875em;padding:20px 0;transition:left .2s ease-in-out,right .2s ease-in-out}.footer.footer-fixed{bottom:0;left:0;position:absolute;right:0}.footer-inner{box-sizing:border-box;text-align:center;display:flex;flex-direction:column;justify-content:center;margin:0 auto;width:calc(100% - 20px)}@media (max-width:767px){.menu-item .badge{float:right;margin-left:0}.footer-inner{width:auto}}@media (min-width:1200px){.footer-inner{width:1160px}}@media (min-width:1600px){.footer-inner{width:73%}}.use-motion .footer{opacity:0}.languages{display:inline-block;font-size:1.125em;position:relative}.languages .lang-select-label span{margin:0 .5em}.languages .lang-select{height:100%;left:0;opacity:0;position:absolute;top:0;width:100%}.with-love{color:red;display:inline-block;margin:0 5px}.beian img{display:inline-block;margin:0 3px;vertical-align:middle}.busuanzi-count #busuanzi_container_site_pv,.busuanzi-count #busuanzi_container_site_uv{display:none}@keyframes icon-animate{0%,100%{transform:scale(1)}10%,30%{transform:scale(.9)}20%,40%,50%,60%,70%,80%{transform:scale(1.1)}}@media (max-width:567px){.main-inner{padding:initial!important}.posts-expand .post-header{margin-bottom:10px!important}.post-block{margin-top:initial!important;padding:8px 18px!important}.post-body h1,.post-body h2,.post-body h3,.post-body h4,.post-body h5,.post-body h6{margin:20px 0 8px}.post-body .note h1,.post-body .note h2,.post-body .note h3,.post-body .note h4,.post-body .note h5,.post-body .note h6,.post-body .tabs .tab-content .tab-pane h1,.post-body .tabs .tab-content .tab-pane h2,.post-body .tabs .tab-content .tab-pane h3,.post-body .tabs .tab-content .tab-pane h4,.post-body .tabs .tab-content .tab-pane h5,.post-body .tabs .tab-content .tab-pane h6{margin:0 5px}.post-body>p{margin:0 0 10px}.post-body .note>p,.post-body .tabs .tab-content .tab-pane>p{padding:0 5px}.post-body img,.post-body video{margin-bottom:10px!important}.post-body .fancybox+figcaption,.post-body img+figcaption{margin:-5px auto 15px!important}.post-body .note{margin-bottom:10px!important;padding:10px!important}.post-body .tabs .tab-content .tab-pane{padding:10px 10px 0!important}.post-eof{margin:40px auto 20px!important}.pagination{margin-top:40px}}.back-to-top{font-size:12px;align-items:center;bottom:-100px;color:#fff;display:flex;height:26px;transition:bottom .2s ease-in-out;background:#222;cursor:pointer;opacity:.6;position:fixed;z-index:30;right:30px}.back-to-top span{margin-right:8px;display:none}.back-to-top .fa{text-align:center;width:26px}.back-to-top:hover{opacity:.8;color:#fc6423}.back-to-top.back-to-top-on{bottom:30px}.rtl.post-body a,.rtl.post-body h1,.rtl.post-body h2,.rtl.post-body h3,.rtl.post-body h4,.rtl.post-body h5,.rtl.post-body h6,.rtl.post-body li,.rtl.post-body ol,.rtl.post-body p,.rtl.post-body ul{direction:rtl;font-family:UKIJ Ekran}.rtl.post-title{font-family:UKIJ Ekran}.post-button{margin-top:40px;text-align:center}.use-motion .collection-header,.use-motion .comments,.use-motion .pagination,.use-motion .post-block,.use-motion .post-body,.use-motion .post-header{visibility:hidden}.posts-collapse .post-content{margin-bottom:35px;margin-left:35px;position:relative}@media (max-width:767px){.posts-collapse .post-content{margin-left:0;margin-right:0}}.posts-collapse .post-content .collection-title{font-size:1.125em;position:relative}.posts-collapse .post-content .collection-title::before{background:#999;border:1px solid #fff;margin-left:-6px;margin-top:-4px;position:absolute;top:50%;border-radius:50%;content:' ';height:10px;width:10px}.posts-collapse .post-content .collection-year{font-size:1.5em;font-weight:700;margin:60px 0;position:relative}.posts-collapse .post-content .collection-year::before{background:#bbb;margin-left:-4px;margin-top:-4px;position:absolute;top:50%;border-radius:50%;content:' ';height:8px;width:8px}.posts-collapse .post-content .collection-header{display:block;margin-left:20px}.posts-collapse .post-content .collection-header small{color:#bbb;margin-left:5px}.posts-collapse .post-content .post-header{border-bottom:1px dashed #ccc;margin:30px 2px 0;padding-left:15px;position:relative;transition:border .2s ease-in-out}.posts-collapse .post-content .post-header::before{background:#bbb;border:1px solid #fff;left:-6px;position:absolute;top:.75em;transition:background .2s ease-in-out;border-radius:50%;content:' ';height:6px;width:6px}.posts-collapse .post-content .post-header:hover{border-bottom-color:#666}.posts-collapse .post-content .post-header:hover::before{background:#222}.posts-collapse .post-content .post-meta-container{display:inline;font-size:.75em;margin-right:10px}.posts-collapse .post-content .post-title{display:inline}.posts-collapse .post-content .post-title a{border-bottom:0;color:var(--link-color)}.posts-collapse .post-content::before{background:#f5f5f5;content:' ';height:100%;margin-left:-2px;position:absolute;top:1.25em;width:4px}.post-body{font-family:Lato,'PingFang SC','Microsoft YaHei',sans-serif;overflow-wrap:break-word}@media (min-width:1200px){.post-body{font-size:1.125em}}@media (min-width:992px){.post-body{text-align:justify}}.post-body h1 .header-anchor,.post-body h1 .headerlink,.post-body h2 .header-anchor,.post-body h2 .headerlink,.post-body h3 .header-anchor,.post-body h3 .headerlink,.post-body h4 .header-anchor,.post-body h4 .headerlink,.post-body h5 .header-anchor,.post-body h5 .headerlink,.post-body h6 .header-anchor,.post-body h6 .headerlink{border-bottom-style:none;color:inherit;float:right;font-size:.875em;margin-left:10px;opacity:0}.post-body h1 .header-anchor::before,.post-body h1 .headerlink::before,.post-body h2 .header-anchor::before,.post-body h2 .headerlink::before,.post-body h3 .header-anchor::before,.post-body h3 .headerlink::before,.post-body h4 .header-anchor::before,.post-body h4 .headerlink::before,.post-body h5 .header-anchor::before,.post-body h5 .headerlink::before,.post-body h6 .header-anchor::before,.post-body h6 .headerlink::before{content:'\f0c1';font-family:'Font Awesome 6 Free';font-weight:900}.post-body h1:hover .header-anchor,.post-body h1:hover .headerlink,.post-body h2:hover .header-anchor,.post-body h2:hover .headerlink,.post-body h3:hover .header-anchor,.post-body h3:hover .headerlink,.post-body h4:hover .header-anchor,.post-body h4:hover .headerlink,.post-body h5:hover .header-anchor,.post-body h5:hover .headerlink,.post-body h6:hover .header-anchor,.post-body h6:hover .headerlink{opacity:.5}.post-body h1:hover .header-anchor:hover,.post-body h1:hover .headerlink:hover,.post-body h2:hover .header-anchor:hover,.post-body h2:hover .headerlink:hover,.post-body h3:hover .header-anchor:hover,.post-body h3:hover .headerlink:hover,.post-body h4:hover .header-anchor:hover,.post-body h4:hover .headerlink:hover,.post-body h5:hover .header-anchor:hover,.post-body h5:hover .headerlink:hover,.post-body h6:hover .header-anchor:hover,.post-body h6:hover .headerlink:hover{opacity:1}.post-body .exturl .fa{font-size:.875em;margin-left:4px}.post-body .fancybox+figcaption,.post-body img+figcaption{color:#999;font-size:.875em;font-weight:700;line-height:1;margin:-15px auto 15px;text-align:center}.post-body embed,.post-body iframe,.post-body img,.post-body video{margin-bottom:20px}.post-body .video-container{height:0;margin-bottom:20px;overflow:hidden;padding-top:75%;position:relative;width:100%}.post-body .video-container embed,.post-body .video-container iframe,.post-body .video-container object{height:100%;left:0;margin:0;position:absolute;top:0;width:100%}.post-gallery{display:flex;min-height:200px}.post-gallery .post-gallery-image{flex:1}.post-gallery .post-gallery-image:not(:first-child){clip-path:polygon(40px 0,100% 0,100% 100%,0 100%);margin-left:-20px}.post-gallery .post-gallery-image:not(:last-child){margin-right:-20px}.post-gallery .post-gallery-image img{height:100%;object-fit:cover;opacity:1;width:100%}.posts-expand .post-gallery{margin-bottom:60px}.posts-collapse .post-gallery{margin:15px 0}.posts-expand .post-header{font-size:1.125em;margin-bottom:60px;text-align:center}.posts-expand .post-title{font-size:1.5em;font-weight:400;margin:initial;overflow-wrap:break-word}.posts-expand .post-title-link{border-bottom:0;color:var(--link-color);display:inline-block;position:relative}.posts-expand .post-title-link::before{background:var(--link-color);bottom:0;content:'';height:2px;left:0;position:absolute;transform:scaleX(0);transition:transform .2s ease-in-out;width:100%}.posts-expand .post-title-link:hover::before{transform:scaleX(1)}.posts-expand .post-title-link .fa{font-size:.875em;margin-left:5px}.post-sticky-flag{display:inline-block;margin-right:8px;transform:rotate(30deg)}.posts-expand .post-meta-container{color:#999;font-family:Lato,'PingFang SC','Microsoft YaHei',sans-serif;font-size:.75em;margin-top:3px}.posts-expand .post-meta-container .post-description{font-size:.875em;margin-top:2px}.posts-expand .post-meta-container time{border-bottom:1px dashed #999}.post-meta{display:flex;flex-wrap:wrap;justify-content:center}:not(.post-meta-break)+.post-meta-item::before{content:'|';margin:0 .5em}.post-meta-item-icon{margin-right:3px}@media (max-width:991px){.back-to-top{right:20px;opacity:.8}.post-body{text-align:justify}.post-meta-item-text{display:none}}.post-meta-break{flex-basis:100%;height:0}#busuanzi_container_page_pv{display:none}.post-nav{border-top:1px solid #eee;display:flex;gap:30px;justify-content:space-between;margin-top:1em;padding:10px 5px 0}.post-nav-item{flex:1}.post-nav-item a{border-bottom:0;display:block;font-size:.875em;line-height:1.6}.post-nav-item a:active{top:2px}.post-nav-item .fa{font-size:.75em}.post-nav-item:first-child .fa{margin-right:5px}.post-nav-item:last-child{text-align:right}.post-nav-item:last-child .fa{margin-left:5px}.post-footer{display:flex;flex-direction:column;justify-content:center}.post-eof{background:#ccc;height:1px;margin:80px auto 60px;width:8%}.post-block:last-of-type .post-eof{display:none}.post-copyright ul{list-style:none;overflow:hidden;padding:.5em 1em;position:relative;background:var(--card-bg-color);border-left:3px solid #ff2a2a;margin:1em 0 0}.post-copyright ul::after{content:'\f25e';font-family:'Font Awesome 6 Brands';font-size:200px;opacity:.1;position:absolute;right:-50px;top:-150px}.post-tags{margin-top:40px;text-align:center}.post-tags a{display:inline-block;font-size:.8125em}.post-tags a:not(:last-child){margin-right:10px}.social-like{border-top:1px solid #eee;font-size:.875em;margin-top:1em;padding-top:1em;display:flex;flex-wrap:wrap;justify-content:center}.social-like a{border-bottom:none}.reward-container{margin:1em 0 0;padding:1em 0;text-align:center}.reward-container button{background:0 0;color:#fc6423;cursor:pointer;line-height:2;padding:0 15px;border:2px solid #fc6423;border-radius:2px;outline:0;transition:.2s ease-in-out;vertical-align:text-top}.reward-container button:hover{background:#fc6423;color:#fff}.post-reward{display:none;padding-top:20px}.post-reward.active{display:block}.post-reward div{display:inline-block}.post-reward div span{display:block}.post-reward img{display:inline-block;margin:.8em 2em 0;max-width:100%;width:180px}@keyframes next-roll{from{transform:rotateZ(30deg)}to{transform:rotateZ(-30deg)}}.category-all-page .category-all-title{text-align:center}.category-all-page .category-all{margin-top:20px}.category-all-page .category-list{list-style:none;margin:0;padding:0}.category-all-page .category-list-item{margin:5px 10px}.category-all-page .category-list-count{color:#bbb}.category-all-page .category-list-count::before{content:' ('}.category-all-page .category-list-count::after{content:') '}.category-all-page .category-list-child{padding-left:10px}.event-list hr{background:#222;margin:20px 0 45px}.event-list hr::after{background:#222;color:#fff;content:'NOW';display:inline-block;font-weight:700;padding:0 5px}.event-list .event{--event-background:#222;--event-foreground:#bbb;--event-title:#fff;background:var(--event-background);padding:15px}.event-list .event .event-summary{border-bottom:0;color:var(--event-title);margin:0;padding:0 0 0 35px;position:relative}.event-list .event .event-summary::before{animation:1s ease-in-out infinite alternate dot-flash;background:var(--event-title);left:0;margin-top:-6px;position:absolute;top:50%;border-radius:50%;content:' ';height:12px;width:12px}.event-list .event:nth-of-type(odd) .event-summary::before{animation-delay:.5s}.event-list .event:not(:last-child){margin-bottom:20px}.event-list .event .event-relative-time{color:var(--event-foreground);display:inline-block;font-size:12px;font-weight:400;padding-left:12px}.event-list .event .event-details{color:var(--event-foreground);display:block;line-height:18px;padding:6px 0 6px 35px}.event-list .event .event-details::before{color:var(--event-foreground);display:inline-block;margin-right:9px;width:14px;font-family:'Font Awesome 6 Free';font-weight:900}.event-list .event .event-details.event-location::before{content:'\f041'}.event-list .event .event-details.event-duration::before{content:'\f017'}.event-list .event .event-details.event-description::before{content:'\f024'}.event-list .event-past{--event-background:#f5f5f5;--event-foreground:#999;--event-title:#222}@keyframes dot-flash{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.8)}}ul.breadcrumb{font-size:.75em;list-style:none;margin:1em 0;padding:0 2em;text-align:center}ul.breadcrumb li{display:inline}ul.breadcrumb li:not(:first-child)::before{content:'/\00a0';font-weight:400;padding:.5em}ul.breadcrumb li:last-child{font-weight:700}.tag-cloud{text-align:center}.tag-cloud a{display:inline-block;margin:10px}.tag-cloud-0{border-bottom-color:#aaa;color:#aaa}.tag-cloud-1{border-bottom-color:#9a9a9a;color:#9a9a9a}.tag-cloud-2{border-bottom-color:#8b8b8b;color:#8b8b8b}.tag-cloud-3{border-bottom-color:#7c7c7c;color:#7c7c7c}.tag-cloud-4{border-bottom-color:#6c6c6c;color:#6c6c6c}.tag-cloud-5{border-bottom-color:#5d5d5d;color:#5d5d5d}.tag-cloud-6{border-bottom-color:#4e4e4e;color:#4e4e4e}.tag-cloud-7{border-bottom-color:#3e3e3e;color:#3e3e3e}.tag-cloud-8{border-bottom-color:#2f2f2f;color:#2f2f2f}.tag-cloud-9{border-bottom-color:#202020;color:#202020}.tag-cloud-10{border-bottom-color:#111;color:#111}.search-active{overflow:hidden}.search-pop-overlay{background:rgba(0,0,0,0);display:flex;height:100%;left:0;position:fixed;top:0;transition:visibility .4s,background .4s;visibility:hidden;width:100%;z-index:40}.search-active .search-pop-overlay{background:rgba(0,0,0,.3);visibility:visible}.search-popup{background:var(--card-bg-color);border-radius:5px;height:80%;margin:auto;transform:scale(0);transition:transform .4s;width:700px}.search-active .search-popup{transform:scale(1)}@media (max-width:767px){.search-popup{border-radius:0;height:100%;width:100%}}.search-popup .popup-btn-close,.search-popup .search-icon{color:#999;font-size:18px;padding:0 10px}.search-popup .popup-btn-close{cursor:pointer}.search-popup .popup-btn-close:hover .fa{color:#222}.search-popup .search-header{background:#eee;border-top-left-radius:5px;border-top-right-radius:5px;display:flex;padding:5px}.search-popup input.search-input{background:0 0;border:0;outline:0;width:100%}.search-popup input.search-input::-webkit-search-cancel-button{display:none}.search-popup .search-result-container{height:calc(100% - 55px);overflow:auto;padding:5px 25px}.search-popup .search-result-container hr{margin:5px 0 10px}.search-popup .search-result-container hr:first-child{display:none}.search-popup .search-result-list{margin:0 5px;padding:0}.search-popup a.search-result-title{font-weight:700}.search-popup p.search-result{border-bottom:1px dashed #ccc;padding:5px 0}.search-input-container{flex-grow:1}.search-input-container form{padding:2px}.search-stats{align-items:center;display:flex;justify-content:space-between}.search-stats img{height:1em;margin:0}.algolia-pagination{margin:40px 0;opacity:1;padding:0}.algolia-pagination .pagination-item{display:inline-block}.algolia-pagination .current .page-number{cursor:default}.algolia-pagination .disabled-item{visibility:hidden}.use-motion .animated{animation-fill-mode:none;visibility:inherit}.use-motion .sidebar .animated{animation-fill-mode:both}header.header{background:var(--content-bg-color);border-radius:initial;box-shadow:initial}.main{align-items:stretch;display:flex;justify-content:space-between;margin:0 auto;width:calc(100% - 20px)}@media (max-width:767px){.main{width:auto}}@media (min-width:1200px){.main{width:1160px}}@media (min-width:1600px){.main{width:73%}}@media (max-width:991px){header.header{border-radius:initial}.main{display:block;width:auto}}.main-inner{border-radius:initial;box-sizing:border-box;width:calc(100% - 252px)}.footer-inner{padding-left:252px}@media (max-width:991px){.main-inner{border-radius:initial;width:100%}.footer-inner{padding-left:0;padding-right:0;width:auto}}.column{width:240px}.site-brand-container{background:var(--theme-color)}.site-meta{padding:20px 0}.site-nav-right .toggle,.site-nav-toggle .toggle{color:#fff}.site-nav-right .toggle .toggle-line,.site-nav-toggle .toggle .toggle-line{background:#fff}@media (min-width:768px) and (max-width:991px){.site-nav-right,.site-nav-toggle{display:flex;flex-direction:column;justify-content:center}.site-nav{--scroll-height:0;height:0;overflow:hidden;transition:height .2s ease-in-out,visibility .2s ease-in-out;visibility:hidden}body:not(.site-nav-on) .site-nav .animated{animation:none}body.site-nav-on .site-nav{height:var(--scroll-height);visibility:unset}}.menu .menu-item{display:block;margin:0}.menu .menu-item a{padding:5px 20px;position:relative;text-align:left;transition-property:background-color}.menu .menu-item .badge{background:#ccc;border-radius:10px;color:var(--content-bg-color);float:right;padding:2px 5px;text-shadow:1px 1px 0 rgba(0,0,0,.1)}.main-menu .menu-item-active::after{background:#bbb;border-radius:50%;content:' ';height:6px;margin-top:-3px;position:absolute;right:15px;top:50%;width:6px}.sub-menu{margin:0;padding:6px 0}.sub-menu .menu-item{display:inline-block}.sub-menu .menu-item a{background:0 0;margin:5px 10px;padding:initial}.sub-menu .menu-item a:hover{background:0 0;color:#fc6423}.sub-menu .menu-item-active{border-bottom-color:#fc6423;color:#fc6423}.sub-menu .menu-item-active:hover{border-bottom-color:#fc6423}.sidebar{position:-webkit-sticky;position:sticky;top:12px}@media (max-width:991px){.column{width:auto}.site-nav-on .site-brand-container{box-shadow:0 0 16px rgba(0,0,0,.5)}.menu .menu-item.menu-item-search,.sidebar{display:none}}.sidebar-inner{background:var(--content-bg-color);border-radius:initial;box-shadow:initial;box-sizing:border-box;color:var(--text-color);margin-top:12px;max-height:calc(100vh - 24px);visibility:hidden}.site-state-item{padding:0 10px}.sidebar .sidebar-button{border-bottom:1px dotted #ccc;border-top:1px dotted #ccc}.sidebar .sidebar-button button{border:0;color:#fc6423;display:block;width:100%}.sidebar .sidebar-button button:hover{background:0 0;border:0;color:#e34603}.links-of-author{display:flex;flex-wrap:wrap;justify-content:center}.links-of-author-item{margin:5px 0 0;width:50%}.links-of-author-item a{box-sizing:border-box;max-width:100%;overflow:hidden;padding:0 5px;text-overflow:ellipsis;white-space:nowrap;border-bottom:0;border-radius:4px;display:block}.links-of-author-item a:hover{background:var(--body-bg-color)}.main-inner{background:var(--content-bg-color);box-shadow:initial;padding:40px}@media (max-width:991px){.main-inner{padding:20px}}.sub-menu{border-bottom:1px solid #ddd}.post-block:first-of-type{padding-top:40px}@media (max-width:767px){.pagination{margin-bottom:10px}}</style><link as=style href=https://fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&display=swap&subset=latin,latin-ext onload=this.rel='stylesheet' rel=preload><link crossorigin=anonymous href=https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css integrity=sha256-yIDrPSXHZdOZhAqiBP7CKzIwMQmRCJ8UeB8Jo17YC4o= rel=stylesheet><link crossorigin=anonymous href=https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.1.1/animate.min.css integrity=sha256-PR7ttpcvz8qrF57fur/yAx1qXMFJeJFiA6pSzWi0OIE= rel=stylesheet><link crossorigin=anonymous href=https://cdnjs.cloudflare.com/ajax/libs/fancyapps-ui/5.0.28/fancybox/fancybox.css integrity=sha256-6cQIC71/iBIYXFK+0RHAvwmjwWzkWd+r7v/BX3/vZDc= rel=stylesheet><script class=next-config data-name=main type=application/json>{"hostname":"nicksxs.me","root":"/","images":"/images","scheme":"Pisces","darkmode":false,"version":"8.19.1","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12},"copycode":{"enable":true,"style":"default"},"fold":{"enable":false,"height":500},"bookmark":{"enable":false,"color":"#222","save":"auto"},"mediumzoom":false,"lazyload":true,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"stickytabs":false,"motion":{"enable":true,"async":false,"transition":{"menu_item":"fadeInDown","post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果:${query}","hits_time":"找到 ${hits} 个搜索结果(用时 ${time} 毫秒)","hits":"找到 ${hits} 个搜索结果"},"algolia":{"appID":"663Q99OQQD","apiKey":"e461a1371d12cec1491c1153b288a9ed","indexName":"nicksxs","hits":{"per_page":10}}}</script><script src=/js/config.js></script><meta content="learn from zero,技术博客,Nicksxs,史学森" name=description><meta content=website property=og:type><meta content="Nicksxs's Blog" property=og:title><meta content=https://nicksxs.me/page/40/index.html property=og:url><meta content="Nicksxs's Blog" property=og:site_name><meta content="learn from zero,技术博客,Nicksxs,史学森" property=og:description><meta content=zh_CN property=og:locale><meta content=Nicksxs property=article:author><meta content=Nicksxs,史学森,米方方,米方方的男朋友,森哥 property=article:tag><meta content=summary name=twitter:card><link href=https://nicksxs.me/page/40/ rel=canonical><script class=next-config data-name=page type=application/json>{"sidebar":"","isHome":true,"isPost":false,"lang":"zh-CN","comments":"","permalink":"","path":"page/40/index.html","title":""}</script><script class=next-config data-name=calendar type=application/json>""</script><title>Nicksxs's Blog - What hurts more, the pain of hard work or the pain of regret?</title><script async src=https://www.googletagmanager.com/gtag/js?id=UA-61358619-1></script><script class=next-config data-name=google_analytics type=application/json>{"tracking_id":"UA-61358619-1","only_pageview":false,"measure_protocol_api_secret":null}</script><script src=/js/third-party/analytics/google-analytics.js></script><script src=/js/third-party/analytics/baidu-analytics.js></script><script async src=https://hm.baidu.com/hm.js?20f33b3c0c0eff9b1522999c0015646d></script><noscript><link href=/css/noscript.css rel=stylesheet></noscript><link title="Nicksxs's Blog" href=/atom.xml rel=alternate type=application/atom+xml><body class=use-motion itemscope itemtype=http://schema.org/WebPage><div class=headband></div><main class=main><div class=column><header class=header itemscope itemtype=http://schema.org/WPHeader><div class=site-brand-container><div class=site-nav-toggle><div aria-label=切换导航栏 class=toggle role=button><span class=toggle-line></span><span class=toggle-line></span><span class=toggle-line></span></div></div><div class=site-meta><a class=brand href=/ rel=start><i class=logo-line></i><h1 class=site-title>Nicksxs's Blog</h1><i class=logo-line></i></a><p class=site-subtitle itemprop=description>What hurts more, the pain of hard work or the pain of regret?</div><div class=site-nav-right><div class="toggle popup-trigger" aria-label=搜索 role=button><i class="fa fa-search fa-fw fa-lg"></i></div></div></div><nav class=site-nav><ul class="main-menu menu"><li class="menu-item menu-item-home"><a href=/ rel=section><i class="fa fa-home fa-fw"></i>首页</a><li class="menu-item menu-item-about"><a href=/about/ rel=section><i class="fa fa-user fa-fw"></i>关于我</a><li class="menu-item menu-item-mirror"><a href=https://nicksxs.com/ rel=section target=_blank><i class="fa fa-user fa-fw"></i>国内镜像</a><li class="menu-item menu-item-tags"><a href=/tags/ rel=section><i class="fa fa-tags fa-fw"></i>标签</a><li class="menu-item menu-item-categories"><a href=/categories/ rel=section><i class="fa fa-th fa-fw"></i>分类</a><li class="menu-item menu-item-archives"><a href=/archives/ rel=section><i class="fa fa-archive fa-fw"></i>归档</a><li class="menu-item menu-item-top"><a href=/top/ rel=section><i class="fa fa-th fa-fw"></i>热度</a><li class="menu-item menu-item-sitemap"><a href=/sitemap.xml rel=section><i class="fa fa-sitemap fa-fw"></i>站点地图</a><li class="menu-item menu-item-commonweal"><a href=/404/ rel=section><i class="fa fa-heartbeat fa-fw"></i>公益 404</a><li class="menu-item menu-item-search"><a class=popup-trigger role=button><i class="fa fa-search fa-fw"></i>搜索</a></ul></nav><div class=search-pop-overlay><div class="popup search-popup"><div class=search-header><span class=search-icon><i class="fa fa-search"></i></span><div class=search-input-container></div><span class=popup-btn-close role=button><i class="fa fa-times-circle"></i></span></div><div class=search-result-container><div class=algolia-stats><hr></div><div class=algolia-hits></div><div class=algolia-pagination></div></div></div></div></header><aside class=sidebar><div class="sidebar-inner sidebar-overview-active"><ul class=sidebar-nav><li class=sidebar-nav-toc>文章目录<li class=sidebar-nav-overview>站点概览</ul><div class=sidebar-panel-container><div class="post-toc-wrap sidebar-panel"></div><div class="site-overview-wrap sidebar-panel"><div class="site-author animated" itemprop=author itemscope itemtype=http://schema.org/Person><img alt=Nicksxs class=site-author-image itemprop=image src=/uploads/avatar.jpg><p class=site-author-name itemprop=name>Nicksxs<div class=site-description itemprop=description>learn from zero,技术博客,Nicksxs,史学森</div></div><div class="site-state-wrap animated"><nav class=site-state><div class="site-state-item site-state-posts"><a href=/archives/><span class=site-state-item-count>317</span> <span class=site-state-item-name>日志</span></a></div><div class="site-state-item site-state-categories"><a href=/categories/><span class=site-state-item-count>172</span> <span class=site-state-item-name>分类</span></a></div><div class="site-state-item site-state-tags"><a href=/tags/><span class=site-state-item-count>306</span> <span class=site-state-item-name>标签</span></a></div></nav></div><div class="links-of-author animated"><span class=links-of-author-item><a rel="noopener me" title="GitHub → https://github.com/nicksxs" href=https://github.com/nicksxs target=_blank><i class="fab fa-github fa-fw"></i>GitHub</a> </span><span class=links-of-author-item><a rel="noopener me" title="E-Mail → mailto:nicksxs1202@gmail.com" href=mailto:nicksxs1202@gmail.com target=_blank><i class="fa fa-envelope fa-fw"></i>E-Mail</a></span></div><div class="cc-license animated" itemprop=license><a class=cc-opacity href=https://creativecommons.org/licenses/by-nc-sa/4.0/ rel=noopener target=_blank><img alt="Creative Commons" src=https://cdnjs.cloudflare.com/ajax/libs/creativecommons-vocabulary/2020.11.3/assets/license_badges/small/by_nc_sa.svg></a></div><div class=post-gallery itemscope itemtype=http://schema.org/ImageGallery style=height:120px;min-height:120px><div class=post-gallery-row><a class="post-gallery-img fancybox" href=https://url.cn/LLWrL7gx itemprop=url itemscope itemtype=http://schema.org/ImageObject rel=gallery_ target=_blank><img alt=腾讯云推广 itemprop=contentUrl src=https://img.nicksxs.com/blog/345X200.jpg style=padding:12px></a></div></div><script charset=utf-8 src=/js/tagcloud.js></script><script charset=utf-8 src=/js/tagcanvas.js></script><div class=widget-wrap><div class="widget tagcloud" id=myCanvasContainer><canvas height=250 id=resCanvas width=250><ul class=tag-list itemprop=keywords><li class=tag-list-item><a class=tag-list-link href=/tags/2019/ rel=tag>2019</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/2020/ rel=tag>2020</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/2021/ rel=tag>2021</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/2022/ rel=tag>2022</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/2023/ rel=tag>2023</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/2PC/ rel=tag>2PC</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/360-%E5%85%A8%E5%AE%B6%E6%A1%B6/ rel=tag>360 全家桶</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/3PC/ rel=tag>3PC</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/3Sum-Closest/ rel=tag>3Sum Closest</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/AOP/ rel=tag>AOP</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Adaptive/ rel=tag>Adaptive</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Apollo/ rel=tag>Apollo</a><span class=tag-list-count>4</span><li class=tag-list-item><a class=tag-list-link href=/tags/AutoConfiguration/ rel=tag>AutoConfiguration</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/Binary-Tree/ rel=tag>Binary Tree</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/Broker/ rel=tag>Broker</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/C/ rel=tag>C</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/C/ rel=tag>C++</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/CachedThreadPool/ rel=tag>CachedThreadPool</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Comparator/ rel=tag>Comparator</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/DFS/ rel=tag>DFS</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/DP/ rel=tag>DP</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/DefaultMQPushConsumer/ rel=tag>DefaultMQPushConsumer</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Design-Patterns/ rel=tag>Design Patterns</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Disruptor/ rel=tag>Disruptor</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/Distributed-Lock/ rel=tag>Distributed Lock</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Docker/ rel=tag>Docker</a><span class=tag-list-count>6</span><li class=tag-list-item><a class=tag-list-link href=/tags/Dockerfile/ rel=tag>Dockerfile</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Druid/ rel=tag>Druid</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Dubbo/ rel=tag>Dubbo</a><span class=tag-list-count>6</span><li class=tag-list-item><a class=tag-list-link href=/tags/EagerThreadPool/ rel=tag>EagerThreadPool</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Evict/ rel=tag>Evict</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Filter/ rel=tag>Filter</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/First-Bad-Version/ rel=tag>First Bad Version</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/FixedThreadPool/ rel=tag>FixedThreadPool</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/G1/ rel=tag>G1</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/GC/ rel=tag>GC</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Garbage-First-Collector/ rel=tag>Garbage-First Collector</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Gogs/ rel=tag>Gogs</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Homebrew/ rel=tag>Homebrew</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/Inorder-Traversal/ rel=tag>Inorder Traversal</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Interceptor/ rel=tag>Interceptor</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Intersection-of-Two-Arrays/ rel=tag>Intersection of Two Arrays</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/JMap/ rel=tag>JMap</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/JPS/ rel=tag>JPS</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/JStack/ rel=tag>JStack</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/JVM/ rel=tag>JVM</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/Java/ rel=tag>Java</a><span class=tag-list-count>75</span><li class=tag-list-item><a class=tag-list-link href=/tags/LLM/ rel=tag>LLM</a><span class=tag-list-count>7</span><li class=tag-list-item><a class=tag-list-link href=/tags/Leetcode-42/ rel=tag>Leetcode 42</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/LimitedThreadPool/ rel=tag>LimitedThreadPool</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Linked-List/ rel=tag>Linked List</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/Lowest-Common-Ancestor-of-a-Binary-Tree/ rel=tag>Lowest Common Ancestor of a Binary Tree</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/MQ/ rel=tag>MQ</a><span class=tag-list-count>9</span><li class=tag-list-item><a class=tag-list-link href=/tags/Mac/ rel=tag>Mac</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/Maven/ rel=tag>Maven</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Median-of-Two-Sorted-Arrays/ rel=tag>Median of Two Sorted Arrays</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Mybatis/ rel=tag>Mybatis</a><span class=tag-list-count>13</span><li class=tag-list-item><a class=tag-list-link href=/tags/Mysql/ rel=tag>Mysql</a><span class=tag-list-count>13</span><li class=tag-list-item><a class=tag-list-link href=/tags/NameServer/ rel=tag>NameServer</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/PHP/ rel=tag>PHP</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/Preorder-Traversal/ rel=tag>Preorder Traversal</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Print-FooBar-Alternately/ rel=tag>Print FooBar Alternately</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/RPC/ rel=tag>RPC</a><span class=tag-list-count>4</span><li class=tag-list-item><a class=tag-list-link href=/tags/Redis/ rel=tag>Redis</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/Remove-Duplicates-from-Sorted-List/ rel=tag>Remove Duplicates from Sorted List</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/RocketMQ/ rel=tag>RocketMQ</a><span class=tag-list-count>9</span><li class=tag-list-item><a class=tag-list-link href=/tags/Rotate-Image/ rel=tag>Rotate Image</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Rust/ rel=tag>Rust</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/SPI/ rel=tag>SPI</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/Servlet/ rel=tag>Servlet</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Sharding-Jdbc/ rel=tag>Sharding-Jdbc</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/Shift-2D-Grid/ rel=tag>Shift 2D Grid</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Singleton/ rel=tag>Singleton</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Spring/ rel=tag>Spring</a><span class=tag-list-count>7</span><li class=tag-list-item><a class=tag-list-link href=/tags/Spring-Event/ rel=tag>Spring Event</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/SpringBoot/ rel=tag>SpringBoot</a><span class=tag-list-count>22</span><li class=tag-list-item><a class=tag-list-link href=/tags/Sql%E6%B3%A8%E5%85%A5/ rel=tag>Sql注入</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Stream/ rel=tag>Stream</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Synchronized/ rel=tag>Synchronized</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/Thread-dump/ rel=tag>Thread dump</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/ThreadLocal/ rel=tag>ThreadLocal</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/ThreadPool/ rel=tag>ThreadPool</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Tomcat/ rel=tag>Tomcat</a><span class=tag-list-count>13</span><li class=tag-list-item><a class=tag-list-link href=/tags/Trapping-Rain-Water/ rel=tag>Trapping Rain Water</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/WeakReference/ rel=tag>WeakReference</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Web/ rel=tag>Web</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Webhook/ rel=tag>Webhook</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/Windows/ rel=tag>Windows</a><span class=tag-list-count>5</span><li class=tag-list-item><a class=tag-list-link href=/tags/WordPress/ rel=tag>WordPress</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/aqs/ rel=tag>aqs</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/await/ rel=tag>await</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/bloom-filter/ rel=tag>bloom filter</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/c/ rel=tag>c++</a><span class=tag-list-count>14</span><li class=tag-list-item><a class=tag-list-link href=/tags/cglib/ rel=tag>cglib</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/cgroup/ rel=tag>cgroup</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/cluster/ rel=tag>cluster</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/condition/ rel=tag>condition</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/dnsmasq/ rel=tag>dnsmasq</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/docker/ rel=tag>docker</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/dp/ rel=tag>dp</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/dubbo/ rel=tag>dubbo</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/echo/ rel=tag>echo</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/environment/ rel=tag>environment</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/gap-lock/ rel=tag>gap lock</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/gc/ rel=tag>gc</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/git/ rel=tag>git</a><span class=tag-list-count>4</span><li class=tag-list-item><a class=tag-list-link href=/tags/grep/ rel=tag>grep</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/hadoop/ rel=tag>hadoop</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/headscale/ rel=tag>headscale</a><span class=tag-list-count>6</span><li class=tag-list-item><a class=tag-list-link href=/tags/hexo/ rel=tag>hexo</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/http/ rel=tag>http</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/icu4c/ rel=tag>icu4c</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/im/ rel=tag>im</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/is-not-null/ rel=tag>is not null</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/is-null/ rel=tag>is null</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/j-u-c/ rel=tag>j.u.c</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/java/ rel=tag>java</a><span class=tag-list-count>52</span><li class=tag-list-item><a class=tag-list-link href=/tags/jvm/ rel=tag>jvm</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/leetcode/ rel=tag>leetcode</a><span class=tag-list-count>44</span><li class=tag-list-item><a class=tag-list-link href=/tags/leetcode-155/ rel=tag>leetcode 155</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/linked-list/ rel=tag>linked list</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/linux/ rel=tag>linux</a><span class=tag-list-count>7</span><li class=tag-list-item><a class=tag-list-link href=/tags/lock/ rel=tag>lock</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/mac/ rel=tag>mac</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/mfc/ rel=tag>mfc</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/min-stack/ rel=tag>min stack</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/mq/ rel=tag>mq</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/mvcc/ rel=tag>mvcc</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/mysql/ rel=tag>mysql</a><span class=tag-list-count>7</span><li class=tag-list-item><a class=tag-list-link href=/tags/namespace/ rel=tag>namespace</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/nas/ rel=tag>nas</a><span class=tag-list-count>6</span><li class=tag-list-item><a class=tag-list-link href=/tags/next-key-lock/ rel=tag>next-key lock</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/nginx/ rel=tag>nginx</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/nullsfirst/ rel=tag>nullsfirst</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/openresty/ rel=tag>openresty</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/openwrt/ rel=tag>openwrt</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/php/ rel=tag>php</a><span class=tag-list-count>5</span><li class=tag-list-item><a class=tag-list-link href=/tags/powershell/ rel=tag>powershell</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/procedure/ rel=tag>procedure</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/python/ rel=tag>python</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/read-view/ rel=tag>read view</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/redis/ rel=tag>redis</a><span class=tag-list-count>12</span><li class=tag-list-item><a class=tag-list-link href=/tags/scp/ rel=tag>scp</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/signal/ rel=tag>signal</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/sort/ rel=tag>sort</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/spark/ rel=tag>spark</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/ssh/ rel=tag>ssh</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/stack/ rel=tag>stack</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/stream/ rel=tag>stream</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/string/ rel=tag>string</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/swoole/ rel=tag>swoole</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/top/ rel=tag>top</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/trace/ rel=tag>trace</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/uname/ rel=tag>uname</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/unlock/ rel=tag>unlock</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/utf8/ rel=tag>utf8</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/utf8mb4/ rel=tag>utf8mb4</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/utf8mb4-0900-ai-ci/ rel=tag>utf8mb4_0900_ai_ci</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/utf8mb4-general-ci/ rel=tag>utf8mb4_general_ci</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/utf8mb4-unicode-ci/ rel=tag>utf8mb4_unicode_ci</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/value/ rel=tag>value</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/websocket/ rel=tag>websocket</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/windows/ rel=tag>windows</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/wsl/ rel=tag>wsl</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/zookeeper/ rel=tag>zookeeper</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/zsh/ rel=tag>zsh</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%B8%89%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4/ rel=tag>三阶段提交</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%B8%8D%E5%8F%AF%E5%8F%98%E5%BC%95%E7%94%A8/ rel=tag>不可变引用</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%B8%9C%E4%BA%AC%E5%A5%A5%E8%BF%90%E4%BC%9A/ rel=tag>东京奥运会</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4/ rel=tag>两阶段提交</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%B8%AD%E5%B1%B1%E8%B7%AF/ rel=tag>中山路</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%B8%AD%E5%BA%8F/ rel=tag>中序</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6/ rel=tag>中间件</a><span class=tag-list-count>4</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%B8%BE%E9%87%8D/ rel=tag>举重</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%B9%92%E4%B9%93%E7%90%83/ rel=tag>乒乓球</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%BA%8B%E5%8A%A1/ rel=tag>事务</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%BA%8C%E5%8F%89%E6%A0%91/ rel=tag>二叉树</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8/ rel=tag>云服务器</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%BA%92%E6%96%A5%E9%94%81/ rel=tag>互斥锁</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%BB%A3%E7%A0%81%E9%A2%98%E8%A7%A3/ rel=tag>代码题解</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%BF%AE%E7%94%B5%E8%84%91%E7%9A%84/ rel=tag>修电脑的</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%81%8F%E5%90%91%E9%94%81/ rel=tag>偏向锁</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%81%A5%E5%BA%B7%E7%A0%81/ rel=tag>健康码</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%85%AC%E4%BA%A4/ rel=tag>公交</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%85%AC%E4%BA%A4%E8%BD%A6/ rel=tag>公交车</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%86%85%E5%AD%98%E5%88%86%E5%B8%83/ rel=tag>内存分布</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/ rel=tag>内存泄漏</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%87%86%E5%A4%87/ rel=tag>准备</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%87%8F%E8%82%A5/ rel=tag>减肥</a><span class=tag-list-count>7</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/ rel=tag>分布式事务</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81/ rel=tag>分布式锁</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%88%87%E7%89%87/ rel=tag>切片</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%88%9D%E5%A7%8B%E5%8C%96/ rel=tag>初始化</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%89%8A%E5%B3%B0%E5%A1%AB%E8%B0%B7/ rel=tag>削峰填谷</a><span class=tag-list-count>4</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%89%8D%E5%BA%8F/ rel=tag>前序</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%8A%A0%E5%A1%9E/ rel=tag>加塞</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%8A%A0%E8%BD%BD/ rel=tag>加载</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%8D%95%E4%BE%8B/ rel=tag>单例</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%8D%9A%E5%AE%A2%EF%BC%8C%E6%96%87%E7%AB%A0/ rel=tag>博客,文章</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%8E%A6%E9%97%A8/ rel=tag>厦门</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE/ rel=tag>双亲委派</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%8F%91%E8%A1%8C%E7%89%88/ rel=tag>发行版</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%8F%A3%E7%BD%A9/ rel=tag>口罩</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%8F%AF%E5%8F%98%E5%BC%95%E7%94%A8/ rel=tag>可变引用</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%90%90%E6%A7%BD/ rel=tag>吐槽</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%9B%A4%E7%89%A9%E8%B5%84/ rel=tag>囤物资</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/ rel=tag>垃圾回收</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD/ rel=tag>基础设施</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%A4%A7%E6%89%AB%E9%99%A4/ rel=tag>大扫除</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%AD%97%E7%AC%A6%E9%9B%86/ rel=tag>字符集</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%AE%89%E5%85%A8/ rel=tag>安全</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%AE%B9%E9%94%99%E6%9C%BA%E5%88%B6/ rel=tag>容错机制</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%AF%84%E7%94%9F%E8%99%AB/ rel=tag>寄生虫</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B0%84%E5%87%BB/ rel=tag>射击</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B0%8F%E6%8A%80%E5%B7%A7/ rel=tag>小技巧</a><span class=tag-list-count>10</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B1%80%E5%8F%A3%E8%A1%97/ rel=tag>局口街</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B7%A5%E5%85%B7/ rel=tag>工具</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8/ rel=tag>布隆过滤器</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B9%B2%E6%B4%BB/ rel=tag>干活</a><span class=tag-list-count>6</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ rel=tag>年中总结</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ rel=tag>年终总结</a><span class=tag-list-count>4</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B9%B6%E5%8F%91/ rel=tag>并发</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B9%B8%E7%A6%8F%E4%BA%86%E5%90%97/ rel=tag>幸福了吗</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B9%BB%E8%AF%BB/ rel=tag>幻读</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%BA%94%E7%94%A8/ rel=tag>应用</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%BC%80%E8%BD%A6/ rel=tag>开车</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%BC%B1%E5%BC%95%E7%94%A8/ rel=tag>弱引用</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%BD%B1%E8%AF%84/ rel=tag>影评</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%81%B6%E6%84%8F%E7%9B%97%E5%88%B7/ rel=tag>恶意盗刷</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%89%80%E6%9C%89%E6%9D%83/ rel=tag>所有权</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%89%93%E5%8D%A1/ rel=tag>打卡</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%89%B6%E6%A2%AF/ rel=tag>扶梯</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%8A%80%E6%9C%AF/ rel=tag>技术</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%8A%98%E8%85%BE/ rel=tag>折腾</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%8B%96%E6%9B%B4/ rel=tag>拖更</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%8D%A2%E8%BD%A6%E7%89%8C/ rel=tag>换车牌</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%8E%92%E5%BA%8F/ rel=tag>排序</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%8E%A5%E9%9B%A8%E6%B0%B4/ rel=tag>接雨水</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%95%B0%E6%8D%AE%E6%BA%90%E5%8A%A8%E6%80%81%E5%88%87%E6%8D%A2/ rel=tag>数据源动态切换</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ rel=tag>数据结构</a><span class=tag-list-count>11</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%96%B0%E8%AF%AD%E8%A8%80/ rel=tag>新语言</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%97%85%E6%B8%B8/ rel=tag>旅游</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%97%A5%E5%BF%97/ rel=tag>日志</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%9B%BE%E5%8E%9D%E5%9E%B5/ rel=tag>曾厝垵</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%9C%80%E5%B0%8F%E6%A0%88/ rel=tag>最小栈</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%9D%80%E4%BA%BA%E8%AF%9B%E5%BF%83/ rel=tag>杀人诛心</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%9D%AD%E5%B7%9E/ rel=tag>杭州</a><span class=tag-list-count>4</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%A0%87%E8%AE%B0%E6%95%B4%E7%90%86/ rel=tag>标记整理</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%A4%8D%E7%89%A9%E5%9B%AD/ rel=tag>植物园</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%B2%99%E8%8C%B6%E9%9D%A2/ rel=tag>沙茶面</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%B3%A8%E8%A7%A3/ rel=tag>注解</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%B5%B7%E8%9B%8E%E7%85%8E/ rel=tag>海蛎煎</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/ rel=tag>消息队列</a><span class=tag-list-count>9</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5/ rel=tag>淘汰策略</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/ rel=tag>深度学习</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%BA%90%E7%A0%81/ rel=tag>源码</a><span class=tag-list-count>11</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/ rel=tag>源码解析</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%94%9F%E6%B4%BB/ rel=tag>生活</a><span class=tag-list-count>42</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%94%B5%E7%93%B6%E8%BD%A6/ rel=tag>电瓶车</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%96%AB%E6%83%85/ rel=tag>疫情</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%9C%8B%E4%B9%A6/ rel=tag>看书</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%9C%8B%E5%89%A7/ rel=tag>看剧</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%9F%A9%E9%98%B5/ rel=tag>矩阵</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91/ rel=tag>端口转发</a><span class=tag-list-count>3</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%AE%97%E6%B3%95/ rel=tag>算法</a><span class=tag-list-count>5</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%B1%BB%E5%8A%A0%E8%BD%BD/ rel=tag>类加载</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%B3%9F%E5%BF%83%E4%BA%8B/ rel=tag>糟心事</a><span class=tag-list-count>5</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%B4%A2%E5%BC%95/ rel=tag>索引</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%BA%BF%E7%A8%8B%E6%B1%A0/ rel=tag>线程池</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%BC%93%E5%AD%98/ rel=tag>缓存</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF/ rel=tag>缓存击穿</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F/ rel=tag>缓存穿透</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9/ rel=tag>缓存雪崩</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%BC%96%E7%A0%81/ rel=tag>编码</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%BE%8E%E5%9B%BD/ rel=tag>美国</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%80%81%E7%94%B5%E8%84%91/ rel=tag>老电脑</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%87%AA%E5%8A%A8%E8%A3%85%E9%85%8D/ rel=tag>自动装配</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%87%AA%E6%97%8B/ rel=tag>自旋</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%87%AA%E9%80%82%E5%BA%94%E6%8B%93%E5%B1%95/ rel=tag>自适应拓展</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%A3%85%E7%94%B5%E8%84%91/ rel=tag>装电脑</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%A7%84%E5%88%99/ rel=tag>规则</a><span class=tag-list-count>4</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%A7%A3%E6%9E%90/ rel=tag>解析</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/ rel=tag>设计模式</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%AF%BB%E4%B9%A6/ rel=tag>读书</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%AF%BB%E5%90%8E%E6%84%9F/ rel=tag>读后感</a><span class=tag-list-count>4</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/ rel=tag>负载均衡</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%B6%B3%E7%90%83/ rel=tag>足球</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%B7%91%E6%AD%A5/ rel=tag>跑步</a><span class=tag-list-count>7</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%B7%AF%E6%94%BF%E8%A7%84%E5%88%92/ rel=tag>路政规划</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%B7%AF%E7%94%B1%E5%99%A8/ rel=tag>路由器</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%B7%B3%E6%B0%B4/ rel=tag>跳水</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%B8%A9%E8%B8%8F/ rel=tag>踩踏</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%BD%AC%E4%B9%89/ rel=tag>转义</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81/ rel=tag>轻量级锁</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%BF%87%E6%9C%9F%E7%AD%96%E7%95%A5/ rel=tag>过期策略</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%BF%90%E5%8A%A8/ rel=tag>运动</a><span class=tag-list-count>9</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%BF%9C%E7%A8%8B%E5%8A%9E%E5%85%AC/ rel=tag>远程办公</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E9%80%92%E5%BD%92/ rel=tag>递归</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E9%87%8D%E9%87%8F%E7%BA%A7%E9%94%81/ rel=tag>重量级锁</a><span class=tag-list-count>2</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E9%93%BE%E6%8E%A5/ rel=tag>链接</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E9%A2%98%E8%A7%A3/ rel=tag>题解</a><span class=tag-list-count>28</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E9%A9%AC%E6%88%8F%E5%9B%A2/ rel=tag>马戏团</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E9%AA%8C%E8%AF%81/ rel=tag>验证</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E9%AA%91%E8%BD%A6/ rel=tag>骑车</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E9%AB%98%E9%80%9F/ rel=tag>高速</a><span class=tag-list-count>1</span><li class=tag-list-item><a class=tag-list-link href=/tags/%E9%BC%93%E6%B5%AA%E5%B1%BF/ rel=tag>鼓浪屿</a><span class=tag-list-count>1</span></ul></canvas></div></div></div></div></div><div class="sidebar-inner sidebar-blogroll"><div class="links-of-blogroll animated"><div class=links-of-blogroll-title><i class="fa fa-globe fa-fw"></i> 链接</div><ul class=links-of-blogroll-list><li class=links-of-blogroll-item><a href=https://covermusic.cn/ rel=noopener target=_blank title=https://covermusic.cn>69伙伴</a></ul></div></div></aside></div><div class="main-inner index posts-expand"><div class=post-block><article class=post-content itemscope itemtype=http://schema.org/Article><link href=https://nicksxs.me/2021/09/19/%E8%81%8A%E4%B8%80%E4%B8%8B-SpringBoot-%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%9A%84-cglib-%E4%BD%9C%E4%B8%BA%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E4%B8%AD%E7%9A%84%E4%B8%80%E4%B8%AA%E6%B3%A8%E6%84%8F%E7%82%B9/ itemprop=mainEntityOfPage><span hidden itemprop=author itemscope itemtype=http://schema.org/Person><meta content=/uploads/avatar.jpg itemprop=image><meta content=Nicksxs itemprop=name></span><span hidden itemprop=publisher itemscope itemtype=http://schema.org/Organization><meta content="Nicksxs's Blog" itemprop=name><meta content="learn from zero,技术博客,Nicksxs,史学森" itemprop=description></span><span hidden itemprop=post itemscope itemtype=http://schema.org/CreativeWork><meta content="undefined | Nicksxs's Blog" itemprop=name><meta itemprop=description></span><header class=post-header><h2 itemprop="name headline" class=post-title><a class=post-title-link href=/2021/09/19/%E8%81%8A%E4%B8%80%E4%B8%8B-SpringBoot-%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%9A%84-cglib-%E4%BD%9C%E4%B8%BA%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E4%B8%AD%E7%9A%84%E4%B8%80%E4%B8%AA%E6%B3%A8%E6%84%8F%E7%82%B9/ itemprop=url>聊一下 SpringBoot 中使用的 cglib 作为动态代理中的一个注意点</a></h2><div class=post-meta-container><div class=post-meta><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar"></i> </span><span class=post-meta-item-text>发表于</span> <time itemprop="dateCreated datePublished" title="创建时间:2021-09-19 22:56:31" datetime=2021-09-19T22:56:31+08:00>2021-09-19</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar-check"></i> </span><span class=post-meta-item-text>更新于</span> <time title="修改时间:2022-06-11 22:45:11" datetime=2022-06-11T22:45:11+08:00 itemprop=dateModified>2022-06-11</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-folder"></i> </span><span class=post-meta-item-text>分类于</span> <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/Java/ itemprop=url rel=index><span itemprop=name>Java</span></a> </span>, <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/Java/SpringBoot/ itemprop=url rel=index><span itemprop=name>SpringBoot</span></a> </span></span><span class="post-meta-item leancloud_visitors" data-flag-title="聊一下 SpringBoot 中使用的 cglib 作为动态代理中的一个注意点" id=/2021/09/19/%E8%81%8A%E4%B8%80%E4%B8%8B-SpringBoot-%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%9A%84-cglib-%E4%BD%9C%E4%B8%BA%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E4%B8%AD%E7%9A%84%E4%B8%80%E4%B8%AA%E6%B3%A8%E6%84%8F%E7%82%B9/ title=阅读次数><span class=post-meta-item-icon><i class="far fa-eye"></i> </span><span class=post-meta-item-text>阅读次数:</span> <span class=leancloud-visitors-count></span> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-comment"></i> </span><span class=post-meta-item-text>Disqus:</span> <a href=/2021/09/19/%E8%81%8A%E4%B8%80%E4%B8%8B-SpringBoot-%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%9A%84-cglib-%E4%BD%9C%E4%B8%BA%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E4%B8%AD%E7%9A%84%E4%B8%80%E4%B8%AA%E6%B3%A8%E6%84%8F%E7%82%B9/#disqus_thread itemprop=discussionUrl title=disqus><span class="post-comments-count disqus-comment-count" data-disqus-identifier=2021/09/19/聊一下-SpringBoot-中使用的-cglib-作为动态代理中的一个注意点/ itemprop=commentCount></span></a></span></div></div></header><div class=post-body itemprop=articleBody><p>这个话题是由一次组内同学分享引出来的,首先在 springboot 2.x 开始默认使用了 cglib 作为 aop 的实现,这里也稍微讲一下,在一个 1.x 的老项目里,可以看到AopAutoConfiguration 是这样的<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br><span class=line>17</span><br><span class=line>18</span><br></pre><td class=code><pre><span class=line><span class=meta>@Configuration</span></span><br><span class=line><span class=meta>@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })</span></span><br><span class=line><span class=meta>@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)</span></span><br><span class=line><span class=keyword>public</span> <span class=keyword>class</span> <span class="title class_">AopAutoConfiguration</span> {</span><br><span class=line></span><br><span class=line> <span class=meta>@Configuration</span></span><br><span class=line> <span class=meta>@EnableAspectJAutoProxy(proxyTargetClass = false)</span></span><br><span class=line> <span class=meta>@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = true)</span></span><br><span class=line> <span class=keyword>public</span> <span class=keyword>static</span> <span class=keyword>class</span> <span class="title class_">JdkDynamicAutoProxyConfiguration</span> {</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=meta>@Configuration</span></span><br><span class=line> <span class=meta>@EnableAspectJAutoProxy(proxyTargetClass = true)</span></span><br><span class=line> <span class=meta>@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = false)</span></span><br><span class=line> <span class=keyword>public</span> <span class=keyword>static</span> <span class=keyword>class</span> <span class="title class_">CglibAutoProxyConfiguration</span> {</span><br><span class=line> }</span><br><span class=line></span><br><span class=line>}</span><br></pre></table></figure><p>而在 2.x 中变成了这样<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br><span class=line>17</span><br><span class=line>18</span><br><span class=line>19</span><br><span class=line>20</span><br><span class=line>21</span><br><span class=line>22</span><br><span class=line>23</span><br><span class=line>24</span><br></pre><td class=code><pre><span class=line><span class=meta>@Configuration(proxyBeanMethods = false)</span></span><br><span class=line><span class=meta>@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)</span></span><br><span class=line><span class=keyword>public</span> <span class=keyword>class</span> <span class="title class_">AopAutoConfiguration</span> {</span><br><span class=line></span><br><span class=line> <span class=meta>@Configuration(proxyBeanMethods = false)</span></span><br><span class=line> <span class=meta>@ConditionalOnClass(Advice.class)</span></span><br><span class=line> <span class=keyword>static</span> <span class=keyword>class</span> <span class="title class_">AspectJAutoProxyingConfiguration</span> {</span><br><span class=line></span><br><span class=line> <span class=meta>@Configuration(proxyBeanMethods = false)</span></span><br><span class=line> <span class=meta>@EnableAspectJAutoProxy(proxyTargetClass = false)</span></span><br><span class=line> <span class=meta>@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")</span></span><br><span class=line> <span class=keyword>static</span> <span class=keyword>class</span> <span class="title class_">JdkDynamicAutoProxyConfiguration</span> {</span><br><span class=line></span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=meta>@Configuration(proxyBeanMethods = false)</span></span><br><span class=line> <span class=meta>@EnableAspectJAutoProxy(proxyTargetClass = true)</span></span><br><span class=line> <span class=meta>@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",</span></span><br><span class=line><span class=meta> matchIfMissing = true)</span></span><br><span class=line> <span class=keyword>static</span> <span class=keyword>class</span> <span class="title class_">CglibAutoProxyConfiguration</span> {</span><br><span class=line></span><br><span class=line> }</span><br><span class=line></span><br><span class=line> }</span><br></pre></table></figure><p>为何会加载 AopAutoConfiguration 在前面的文章<a href=https://nicksxs.me/2021/07/11/%E8%81%8A%E8%81%8ASpringBoot-%E8%87%AA%E5%8A%A8%E8%A3%85%E9%85%8D/>聊聊 SpringBoot 自动装配</a>里已经介绍过,有兴趣的可以看下,可以发现 springboot 在 2.x 版本开始使用 cglib 作为默认的动态代理实现。<p>然后就是出现的问题了,代码是这样的,一个简单的基于 springboot 的带有数据库的插入,对插入代码加了事务注解,<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br><span class=line>17</span><br><span class=line>18</span><br><span class=line>19</span><br><span class=line>20</span><br><span class=line>21</span><br><span class=line>22</span><br><span class=line>23</span><br><span class=line>24</span><br><span class=line>25</span><br><span class=line>26</span><br><span class=line>27</span><br><span class=line>28</span><br><span class=line>29</span><br><span class=line>30</span><br><span class=line>31</span><br><span class=line>32</span><br><span class=line>33</span><br><span class=line>34</span><br><span class=line>35</span><br><span class=line>36</span><br><span class=line>37</span><br><span class=line>38</span><br><span class=line>39</span><br><span class=line>40</span><br><span class=line>41</span><br><span class=line>42</span><br></pre><td class=code><pre><span class=line><span class=meta>@Mapper</span></span><br><span class=line><span class=keyword>public</span> <span class=keyword>interface</span> <span class="title class_">StudentMapper</span> {</span><br><span class=line> <span class=comment>// 就是插入一条数据</span></span><br><span class=line> <span class=meta>@Insert("insert into student(name, age)" + "values ('nick', '18')")</span></span><br><span class=line> <span class=keyword>public</span> Long <span class="title function_">insert</span><span class=params>()</span>;</span><br><span class=line>}</span><br><span class=line></span><br><span class=line><span class=meta>@Component</span></span><br><span class=line><span class=keyword>public</span> <span class=keyword>class</span> <span class="title class_">StudentManager</span> {</span><br><span class=line></span><br><span class=line> <span class=meta>@Resource</span></span><br><span class=line> <span class=keyword>private</span> StudentMapper studentMapper;</span><br><span class=line> </span><br><span class=line> <span class=keyword>public</span> Long <span class="title function_">createStudent</span><span class=params>()</span> {</span><br><span class=line> <span class=keyword>return</span> studentMapper.insert();</span><br><span class=line> }</span><br><span class=line>}</span><br><span class=line></span><br><span class=line><span class=meta>@Component</span></span><br><span class=line><span class=keyword>public</span> <span class=keyword>class</span> <span class="title class_">StudentServiceImpl</span> <span class=keyword>implements</span> <span class="title class_">StudentService</span> {</span><br><span class=line></span><br><span class=line> <span class=meta>@Resource</span></span><br><span class=line> <span class=keyword>private</span> StudentManager studentManager;</span><br><span class=line></span><br><span class=line> <span class=comment>// 自己引用</span></span><br><span class=line> <span class=meta>@Resource</span></span><br><span class=line> <span class=keyword>private</span> StudentServiceImpl studentService;</span><br><span class=line></span><br><span class=line> <span class=meta>@Override</span></span><br><span class=line> <span class=meta>@Transactional</span></span><br><span class=line> <span class=keyword>public</span> Long <span class="title function_">createStudent</span><span class=params>()</span> {</span><br><span class=line> <span class=type>Long</span> <span class=variable>id</span> <span class=operator>=</span> studentManager.createStudent();</span><br><span class=line> <span class=type>Long</span> <span class=variable>id2</span> <span class=operator>=</span> studentService.createStudent2();</span><br><span class=line> <span class=keyword>return</span> <span class=number>1L</span>;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=meta>@Transactional</span></span><br><span class=line> <span class=keyword>private</span> Long <span class="title function_">createStudent2</span><span class=params>()</span> {</span><br><span class=line><span class=comment>// Integer t = Integer.valueOf("aaa");</span></span><br><span class=line> <span class=keyword>return</span> studentManager.createStudent();</span><br><span class=line> }</span><br><span class=line>}</span><br></pre></table></figure><p>第一个公有方法 createStudent 首先调用了 manager 层的创建方法,然后再通过引入的 studentService 调用了createStudent2,我们先跑一下看看会出现啥情况,果不其然报错了,正是这个报错让我纠结了很久<p><img alt=EdR7oB data-src=https://img.nicksxs.com/uPic/EdR7oB.png><p>报了个空指针,而且是在 createStudent2 已经被调用到了,在它的内部,报的 studentManager 是 null,首先 cglib 作为动态代理它是通过继承的方式来实现的,相当于是会在调用目标对象的代理方法时调用 cglib 生成的子类,具体的代理切面逻辑在子类实现,然后在调用目标对象的目标方法,但是继承的方式对于 final 和私有方法其实是没法进行代理的,因为没法继承,所以我最开始的想法是应该通过 studentService 调用 createStudent2 的时候就报错了,也就是不会进入这个方法内部,后面才发现犯了个特别二的错误,继承的方式去调用父类的私有方法,对于 Java 来说是可以调用到的,父类的私有方法并不由子类的InstanceKlass维护,只能通过子类的InstanceKlass找到Java类对应的_super,这样间接地访问。也就是说子类其实是可以访问的,那为啥访问了会报空指针呢,这里报的是studentManager 是空的,可以往依赖注入方面去想,如果忽略依赖注入,我这个studentManager 的确是 null,那是不是就没有被依赖注入呢,但是为啥前面那个可以呢<p>这个问题着实查了很久,不废话来看代码<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br></pre><td class=code><pre><span class=line><span class=meta>@Override</span></span><br><span class=line> <span class=keyword>protected</span> Object <span class="title function_">invokeJoinpoint</span><span class=params>()</span> <span class=keyword>throws</span> Throwable {</span><br><span class=line> <span class=keyword>if</span> (<span class=built_in>this</span>.methodProxy != <span class=literal>null</span>) {</span><br><span class=line> <span class=comment>// 这里的 target 就是被代理的 bean</span></span><br><span class=line> <span class=keyword>return</span> <span class=built_in>this</span>.methodProxy.invoke(<span class=built_in>this</span>.target, <span class=built_in>this</span>.arguments);</span><br><span class=line> }</span><br><span class=line> <span class=keyword>else</span> {</span><br><span class=line> <span class=keyword>return</span> <span class=built_in>super</span>.invokeJoinpoint();</span><br><span class=line> }</span><br><span class=line> }</span><br></pre></table></figure><p>这个是<code>org.springframework.aop.framework.CglibAopProxy.CglibMethodInvocation</code>的代码,其实它在这里不是直接调用 super 也就是父类的方法,而是通过 methodProxy 调用 target 目标对象的方法,也就是原始的 studentService bean 的方法,这样子 spring 管理的已经做好依赖注入的 bean 就能正常起作用,否则就会出现上面的问题,因为 cglib 其实是通过继承来实现,通过将调用转移到子类上加入代理逻辑,我们在简单使用的时候会直接 invokeSuper() 调用父类的方法,但是在这里 spring 的场景里需要去支持 spring 的功能逻辑,所以上面的问题就可以开始来解释了,因为 createStudent 是公共方法,cglib 可以对其进行继承代理,但是在执行逻辑的时候其实是通过调用目标对象,也就是 spring 管理的被代理的目标对象的 bean 调用的 createStudent,而对于下面的 createStudent2 方法因为是私有方法,不会走代理逻辑,也就不会有调用回目标对象的逻辑,只是通过继承关系,在子类中没有这个方法,所以会通过子类的InstanceKlass找到这个类对应的_super,然后调用父类的这个私有方法,这里要搞清楚一个点,从这个代理类直接找到其父类然后调用这个私有方法,这个类是由 cglib 生成的,不是被 spring 管理起来经过依赖注入的 bean,所以是没有 studentManager 这个依赖的,也就出现了前面的问题<p>而在前面提到的cglib通过methodProxy调用到目标对象,目标对象是在什么时候设置的呢,其实是在bean的生命周期中,org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization这个接口的在bean的初始化过程中,会调用实现了这个接口的方法,<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br></pre><td class=code><pre><span class=line><span class=meta>@Override</span></span><br><span class=line><span class=keyword>public</span> Object <span class="title function_">postProcessAfterInitialization</span><span class=params>(<span class=meta>@Nullable</span> Object bean, String beanName)</span> {</span><br><span class=line> <span class=keyword>if</span> (bean != <span class=literal>null</span>) {</span><br><span class=line> <span class=type>Object</span> <span class=variable>cacheKey</span> <span class=operator>=</span> getCacheKey(bean.getClass(), beanName);</span><br><span class=line> <span class=keyword>if</span> (<span class=built_in>this</span>.earlyProxyReferences.remove(cacheKey) != bean) {</span><br><span class=line> <span class=keyword>return</span> wrapIfNecessary(bean, beanName, cacheKey);</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line> <span class=keyword>return</span> bean;</span><br><span class=line>}</span><br></pre></table></figure><p>具体的逻辑在 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary这个方法里<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br><span class=line>17</span><br><span class=line>18</span><br><span class=line>19</span><br><span class=line>20</span><br><span class=line>21</span><br><span class=line>22</span><br><span class=line>23</span><br><span class=line>24</span><br><span class=line>25</span><br><span class=line>26</span><br><span class=line>27</span><br><span class=line>28</span><br><span class=line>29</span><br><span class=line>30</span><br><span class=line>31</span><br><span class=line>32</span><br><span class=line>33</span><br><span class=line>34</span><br><span class=line>35</span><br><span class=line>36</span><br><span class=line>37</span><br><span class=line>38</span><br><span class=line>39</span><br><span class=line>40</span><br><span class=line>41</span><br><span class=line>42</span><br></pre><td class=code><pre><span class=line><span class=keyword>protected</span> Object <span class="title function_">getCacheKey</span><span class=params>(Class<?> beanClass, <span class=meta>@Nullable</span> String beanName)</span> {</span><br><span class=line> <span class=keyword>if</span> (StringUtils.hasLength(beanName)) {</span><br><span class=line> <span class=keyword>return</span> (FactoryBean.class.isAssignableFrom(beanClass) ?</span><br><span class=line> BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName);</span><br><span class=line> }</span><br><span class=line> <span class=keyword>else</span> {</span><br><span class=line> <span class=keyword>return</span> beanClass;</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=comment>/**</span></span><br><span class=line><span class=comment> * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.</span></span><br><span class=line><span class=comment> * <span class=doctag>@param</span> bean the raw bean instance</span></span><br><span class=line><span class=comment> * <span class=doctag>@param</span> beanName the name of the bean</span></span><br><span class=line><span class=comment> * <span class=doctag>@param</span> cacheKey the cache key for metadata access</span></span><br><span class=line><span class=comment> * <span class=doctag>@return</span> a proxy wrapping the bean, or the raw bean instance as-is</span></span><br><span class=line><span class=comment> */</span></span><br><span class=line> <span class=keyword>protected</span> Object <span class="title function_">wrapIfNecessary</span><span class=params>(Object bean, String beanName, Object cacheKey)</span> {</span><br><span class=line> <span class=keyword>if</span> (StringUtils.hasLength(beanName) && <span class=built_in>this</span>.targetSourcedBeans.contains(beanName)) {</span><br><span class=line> <span class=keyword>return</span> bean;</span><br><span class=line> }</span><br><span class=line> <span class=keyword>if</span> (Boolean.FALSE.equals(<span class=built_in>this</span>.advisedBeans.get(cacheKey))) {</span><br><span class=line> <span class=keyword>return</span> bean;</span><br><span class=line> }</span><br><span class=line> <span class=keyword>if</span> (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {</span><br><span class=line> <span class=built_in>this</span>.advisedBeans.put(cacheKey, Boolean.FALSE);</span><br><span class=line> <span class=keyword>return</span> bean;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=comment>// Create proxy if we have advice.</span></span><br><span class=line> Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, <span class=literal>null</span>);</span><br><span class=line> <span class=keyword>if</span> (specificInterceptors != DO_NOT_PROXY) {</span><br><span class=line> <span class=built_in>this</span>.advisedBeans.put(cacheKey, Boolean.TRUE);</span><br><span class=line> <span class=type>Object</span> <span class=variable>proxy</span> <span class=operator>=</span> createProxy(</span><br><span class=line> bean.getClass(), beanName, specificInterceptors, <span class=keyword>new</span> <span class="title class_">SingletonTargetSource</span>(bean));</span><br><span class=line> <span class=built_in>this</span>.proxyTypes.put(cacheKey, proxy.getClass());</span><br><span class=line> <span class=keyword>return</span> proxy;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=built_in>this</span>.advisedBeans.put(cacheKey, Boolean.FALSE);</span><br><span class=line> <span class=keyword>return</span> bean;</span><br><span class=line> }</span><br></pre></table></figure><p>然后在 <code>org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy</code> 中创建了代理类</div><footer class=post-footer><div class=post-eof></div></footer></article></div><div class=post-block><article class=post-content itemscope itemtype=http://schema.org/Article><link href=https://nicksxs.me/2021/09/12/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E4%BA%8C/ itemprop=mainEntityOfPage><span hidden itemprop=author itemscope itemtype=http://schema.org/Person><meta content=/uploads/avatar.jpg itemprop=image><meta content=Nicksxs itemprop=name></span><span hidden itemprop=publisher itemscope itemtype=http://schema.org/Organization><meta content="Nicksxs's Blog" itemprop=name><meta content="learn from zero,技术博客,Nicksxs,史学森" itemprop=description></span><span hidden itemprop=post itemscope itemtype=http://schema.org/CreativeWork><meta content="undefined | Nicksxs's Blog" itemprop=name><meta itemprop=description></span><header class=post-header><h2 itemprop="name headline" class=post-title><a class=post-title-link href=/2021/09/12/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E4%BA%8C/ itemprop=url>聊一下 RocketMQ 的消息存储二</a></h2><div class=post-meta-container><div class=post-meta><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar"></i> </span><span class=post-meta-item-text>发表于</span> <time itemprop="dateCreated datePublished" title="创建时间:2021-09-12 20:49:18" datetime=2021-09-12T20:49:18+08:00>2021-09-12</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar-check"></i> </span><span class=post-meta-item-text>更新于</span> <time title="修改时间:2022-06-11 22:45:11" datetime=2022-06-11T22:45:11+08:00 itemprop=dateModified>2022-06-11</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-folder"></i> </span><span class=post-meta-item-text>分类于</span> <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/MQ/ itemprop=url rel=index><span itemprop=name>MQ</span></a> </span>, <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/MQ/RocketMQ/ itemprop=url rel=index><span itemprop=name>RocketMQ</span></a> </span>, <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/MQ/RocketMQ/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/ itemprop=url rel=index><span itemprop=name>消息队列</span></a> </span></span><span class="post-meta-item leancloud_visitors" data-flag-title="聊一下 RocketMQ 的消息存储二" id=/2021/09/12/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E4%BA%8C/ title=阅读次数><span class=post-meta-item-icon><i class="far fa-eye"></i> </span><span class=post-meta-item-text>阅读次数:</span> <span class=leancloud-visitors-count></span> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-comment"></i> </span><span class=post-meta-item-text>Disqus:</span> <a href=/2021/09/12/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E4%BA%8C/#disqus_thread itemprop=discussionUrl title=disqus><span class="post-comments-count disqus-comment-count" data-disqus-identifier=2021/09/12/聊一下-RocketMQ-的消息存储二/ itemprop=commentCount></span></a></span></div></div></header><div class=post-body itemprop=articleBody><h3 id=CommitLog-结构><a title="CommitLog 结构" class=headerlink href=#CommitLog-结构></a>CommitLog 结构</h3><p>CommitLog 是 rocketmq 的服务端,也就是 broker 存储消息的的文件,跟 kafka 一样,也是顺序写入,当然消息是变长的,生成的规则是每个文件的默认1G =1024 * 1024 * 1024,commitlog的文件名fileName,名字长度为20位,左边补零,剩余为起始偏移量;比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1 073 741 824Byte;当这个文件满了,第二个文件名字为00000000001073741824,起始偏移量为1073741824, 消息存储的时候会顺序写入文件,当文件满了则写入下一个文件,代码中的定义<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line><span class=comment>// CommitLog file size,default is 1G</span></span><br><span class=line><span class=keyword>private</span> <span class=type>int</span> <span class=variable>mapedFileSizeCommitLog</span> <span class=operator>=</span> <span class=number>1024</span> * <span class=number>1024</span> * <span class=number>1024</span>;</span><br></pre></table></figure><p><img alt=kLahwW data-src=https://img.nicksxs.com/uPic/kLahwW.png><p>本地跑个 demo 验证下,也是这样,这里奇妙有几个比较巧妙的点(个人观点),首先文件就刚好是 1G,并且按照大小偏移量去生成下一个文件,这样获取消息的时候按大小算一下就知道在哪个文件里了,<p>代码中写入 CommitLog 的逻辑可以从这开始看<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br><span class=line>17</span><br><span class=line>18</span><br><span class=line>19</span><br><span class=line>20</span><br><span class=line>21</span><br><span class=line>22</span><br><span class=line>23</span><br><span class=line>24</span><br><span class=line>25</span><br><span class=line>26</span><br><span class=line>27</span><br><span class=line>28</span><br><span class=line>29</span><br><span class=line>30</span><br><span class=line>31</span><br><span class=line>32</span><br><span class=line>33</span><br><span class=line>34</span><br><span class=line>35</span><br><span class=line>36</span><br><span class=line>37</span><br><span class=line>38</span><br><span class=line>39</span><br><span class=line>40</span><br><span class=line>41</span><br><span class=line>42</span><br><span class=line>43</span><br><span class=line>44</span><br><span class=line>45</span><br><span class=line>46</span><br><span class=line>47</span><br><span class=line>48</span><br><span class=line>49</span><br><span class=line>50</span><br><span class=line>51</span><br><span class=line>52</span><br><span class=line>53</span><br><span class=line>54</span><br><span class=line>55</span><br><span class=line>56</span><br><span class=line>57</span><br><span class=line>58</span><br><span class=line>59</span><br><span class=line>60</span><br><span class=line>61</span><br><span class=line>62</span><br><span class=line>63</span><br><span class=line>64</span><br><span class=line>65</span><br><span class=line>66</span><br><span class=line>67</span><br><span class=line>68</span><br><span class=line>69</span><br><span class=line>70</span><br><span class=line>71</span><br><span class=line>72</span><br><span class=line>73</span><br><span class=line>74</span><br><span class=line>75</span><br><span class=line>76</span><br><span class=line>77</span><br><span class=line>78</span><br><span class=line>79</span><br><span class=line>80</span><br><span class=line>81</span><br><span class=line>82</span><br><span class=line>83</span><br><span class=line>84</span><br><span class=line>85</span><br><span class=line>86</span><br><span class=line>87</span><br><span class=line>88</span><br><span class=line>89</span><br><span class=line>90</span><br><span class=line>91</span><br><span class=line>92</span><br><span class=line>93</span><br><span class=line>94</span><br><span class=line>95</span><br><span class=line>96</span><br><span class=line>97</span><br><span class=line>98</span><br><span class=line>99</span><br><span class=line>100</span><br><span class=line>101</span><br><span class=line>102</span><br><span class=line>103</span><br><span class=line>104</span><br><span class=line>105</span><br><span class=line>106</span><br><span class=line>107</span><br><span class=line>108</span><br><span class=line>109</span><br><span class=line>110</span><br><span class=line>111</span><br></pre><td class=code><pre><span class=line><span class=keyword>public</span> PutMessageResult <span class="title function_">putMessage</span><span class=params>(<span class=keyword>final</span> MessageExtBrokerInner msg)</span> {</span><br><span class=line> <span class=comment>// Set the storage time</span></span><br><span class=line> msg.setStoreTimestamp(System.currentTimeMillis());</span><br><span class=line> <span class=comment>// Set the message body BODY CRC (consider the most appropriate setting</span></span><br><span class=line> <span class=comment>// on the client)</span></span><br><span class=line> msg.setBodyCRC(UtilAll.crc32(msg.getBody()));</span><br><span class=line> <span class=comment>// Back to Results</span></span><br><span class=line> <span class=type>AppendMessageResult</span> <span class=variable>result</span> <span class=operator>=</span> <span class=literal>null</span>;</span><br><span class=line></span><br><span class=line> <span class=type>StoreStatsService</span> <span class=variable>storeStatsService</span> <span class=operator>=</span> <span class=built_in>this</span>.defaultMessageStore.getStoreStatsService();</span><br><span class=line></span><br><span class=line> <span class=type>String</span> <span class=variable>topic</span> <span class=operator>=</span> msg.getTopic();</span><br><span class=line> <span class=type>int</span> <span class=variable>queueId</span> <span class=operator>=</span> msg.getQueueId();</span><br><span class=line></span><br><span class=line> <span class=keyword>final</span> <span class=type>int</span> <span class=variable>tranType</span> <span class=operator>=</span> MessageSysFlag.getTransactionValue(msg.getSysFlag());</span><br><span class=line> <span class=keyword>if</span> (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE</span><br><span class=line> || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {</span><br><span class=line> <span class=comment>// Delay Delivery</span></span><br><span class=line> <span class=keyword>if</span> (msg.getDelayTimeLevel() > <span class=number>0</span>) {</span><br><span class=line> <span class=keyword>if</span> (msg.getDelayTimeLevel() > <span class=built_in>this</span>.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) {</span><br><span class=line> msg.setDelayTimeLevel(<span class=built_in>this</span>.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel());</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> topic = ScheduleMessageService.SCHEDULE_TOPIC;</span><br><span class=line> queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());</span><br><span class=line></span><br><span class=line> <span class=comment>// Backup real topic, queueId</span></span><br><span class=line> MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());</span><br><span class=line> MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));</span><br><span class=line> msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));</span><br><span class=line></span><br><span class=line> msg.setTopic(topic);</span><br><span class=line> msg.setQueueId(queueId);</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=type>long</span> <span class=variable>eclipseTimeInLock</span> <span class=operator>=</span> <span class=number>0</span>;</span><br><span class=line> <span class=type>MappedFile</span> <span class=variable>unlockMappedFile</span> <span class=operator>=</span> <span class=literal>null</span>;</span><br><span class=line> <span class=type>MappedFile</span> <span class=variable>mappedFile</span> <span class=operator>=</span> <span class=built_in>this</span>.mappedFileQueue.getLastMappedFile();</span><br><span class=line></span><br><span class=line> putMessageLock.lock(); <span class=comment>//spin or ReentrantLock ,depending on store config</span></span><br><span class=line> <span class=keyword>try</span> {</span><br><span class=line> <span class=type>long</span> <span class=variable>beginLockTimestamp</span> <span class=operator>=</span> <span class=built_in>this</span>.defaultMessageStore.getSystemClock().now();</span><br><span class=line> <span class=built_in>this</span>.beginTimeInLock = beginLockTimestamp;</span><br><span class=line></span><br><span class=line> <span class=comment>// Here settings are stored timestamp, in order to ensure an orderly</span></span><br><span class=line> <span class=comment>// global</span></span><br><span class=line> msg.setStoreTimestamp(beginLockTimestamp);</span><br><span class=line></span><br><span class=line> <span class=keyword>if</span> (<span class=literal>null</span> == mappedFile || mappedFile.isFull()) {</span><br><span class=line> mappedFile = <span class=built_in>this</span>.mappedFileQueue.getLastMappedFile(<span class=number>0</span>); <span class=comment>// Mark: NewFile may be cause noise</span></span><br><span class=line> }</span><br><span class=line> <span class=keyword>if</span> (<span class=literal>null</span> == mappedFile) {</span><br><span class=line> log.error(<span class=string>"create mapped file1 error, topic: "</span> + msg.getTopic() + <span class=string>" clientAddr: "</span> + msg.getBornHostString());</span><br><span class=line> beginTimeInLock = <span class=number>0</span>;</span><br><span class=line> <span class=keyword>return</span> <span class=keyword>new</span> <span class="title class_">PutMessageResult</span>(PutMessageStatus.CREATE_MAPEDFILE_FAILED, <span class=literal>null</span>);</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> result = mappedFile.appendMessage(msg, <span class=built_in>this</span>.appendMessageCallback);</span><br><span class=line> <span class=keyword>switch</span> (result.getStatus()) {</span><br><span class=line> <span class=keyword>case</span> PUT_OK:</span><br><span class=line> <span class=keyword>break</span>;</span><br><span class=line> <span class=keyword>case</span> END_OF_FILE:</span><br><span class=line> unlockMappedFile = mappedFile;</span><br><span class=line> <span class=comment>// Create a new file, re-write the message</span></span><br><span class=line> mappedFile = <span class=built_in>this</span>.mappedFileQueue.getLastMappedFile(<span class=number>0</span>);</span><br><span class=line> <span class=keyword>if</span> (<span class=literal>null</span> == mappedFile) {</span><br><span class=line> <span class=comment>// <span class=doctag>XXX:</span> warn and notify me</span></span><br><span class=line> log.error(<span class=string>"create mapped file2 error, topic: "</span> + msg.getTopic() + <span class=string>" clientAddr: "</span> + msg.getBornHostString());</span><br><span class=line> beginTimeInLock = <span class=number>0</span>;</span><br><span class=line> <span class=keyword>return</span> <span class=keyword>new</span> <span class="title class_">PutMessageResult</span>(PutMessageStatus.CREATE_MAPEDFILE_FAILED, result);</span><br><span class=line> }</span><br><span class=line> result = mappedFile.appendMessage(msg, <span class=built_in>this</span>.appendMessageCallback);</span><br><span class=line> <span class=keyword>break</span>;</span><br><span class=line> <span class=keyword>case</span> MESSAGE_SIZE_EXCEEDED:</span><br><span class=line> <span class=keyword>case</span> PROPERTIES_SIZE_EXCEEDED:</span><br><span class=line> beginTimeInLock = <span class=number>0</span>;</span><br><span class=line> <span class=keyword>return</span> <span class=keyword>new</span> <span class="title class_">PutMessageResult</span>(PutMessageStatus.MESSAGE_ILLEGAL, result);</span><br><span class=line> <span class=keyword>case</span> UNKNOWN_ERROR:</span><br><span class=line> beginTimeInLock = <span class=number>0</span>;</span><br><span class=line> <span class=keyword>return</span> <span class=keyword>new</span> <span class="title class_">PutMessageResult</span>(PutMessageStatus.UNKNOWN_ERROR, result);</span><br><span class=line> <span class=keyword>default</span>:</span><br><span class=line> beginTimeInLock = <span class=number>0</span>;</span><br><span class=line> <span class=keyword>return</span> <span class=keyword>new</span> <span class="title class_">PutMessageResult</span>(PutMessageStatus.UNKNOWN_ERROR, result);</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> eclipseTimeInLock = <span class=built_in>this</span>.defaultMessageStore.getSystemClock().now() - beginLockTimestamp;</span><br><span class=line> beginTimeInLock = <span class=number>0</span>;</span><br><span class=line> } <span class=keyword>finally</span> {</span><br><span class=line> putMessageLock.unlock();</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>if</span> (eclipseTimeInLock > <span class=number>500</span>) {</span><br><span class=line> log.warn(<span class=string>"[NOTIFYME]putMessage in lock cost time(ms)={}, bodyLength={} AppendMessageResult={}"</span>, eclipseTimeInLock, msg.getBody().length, result);</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>if</span> (<span class=literal>null</span> != unlockMappedFile && <span class=built_in>this</span>.defaultMessageStore.getMessageStoreConfig().isWarmMapedFileEnable()) {</span><br><span class=line> <span class=built_in>this</span>.defaultMessageStore.unlockMappedFile(unlockMappedFile);</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=type>PutMessageResult</span> <span class=variable>putMessageResult</span> <span class=operator>=</span> <span class=keyword>new</span> <span class="title class_">PutMessageResult</span>(PutMessageStatus.PUT_OK, result);</span><br><span class=line></span><br><span class=line> <span class=comment>// Statistics</span></span><br><span class=line> storeStatsService.getSinglePutMessageTopicTimesTotal(msg.getTopic()).incrementAndGet();</span><br><span class=line> storeStatsService.getSinglePutMessageTopicSizeTotal(topic).addAndGet(result.getWroteBytes());</span><br><span class=line></span><br><span class=line> handleDiskFlush(result, putMessageResult, msg);</span><br><span class=line> handleHA(result, putMessageResult, msg);</span><br><span class=line></span><br><span class=line> <span class=keyword>return</span> putMessageResult;</span><br><span class=line> }</span><br></pre></table></figure><p>前面也看到在CommitLog 目录下是有大小为 1G 的文件组成,在实现逻辑中,其实是通过 <code>org.apache.rocketmq.store.MappedFileQueue</code> ,内部是存的一个<code>MappedFile</code>的队列,对于写入的场景每次都是通过<code>org.apache.rocketmq.store.MappedFileQueue#getLastMappedFile()</code> 获取最后一个文件,如果还没有创建,或者最后这个文件已经满了,那就调用 <code>org.apache.rocketmq.store.MappedFileQueue#getLastMappedFile(long)</code><figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br><span class=line>17</span><br><span class=line>18</span><br><span class=line>19</span><br><span class=line>20</span><br><span class=line>21</span><br><span class=line>22</span><br><span class=line>23</span><br><span class=line>24</span><br><span class=line>25</span><br><span class=line>26</span><br><span class=line>27</span><br><span class=line>28</span><br><span class=line>29</span><br><span class=line>30</span><br><span class=line>31</span><br><span class=line>32</span><br><span class=line>33</span><br><span class=line>34</span><br><span class=line>35</span><br><span class=line>36</span><br><span class=line>37</span><br><span class=line>38</span><br><span class=line>39</span><br><span class=line>40</span><br><span class=line>41</span><br><span class=line>42</span><br><span class=line>43</span><br><span class=line>44</span><br><span class=line>45</span><br><span class=line>46</span><br><span class=line>47</span><br><span class=line>48</span><br></pre><td class=code><pre><span class=line><span class=keyword>public</span> MappedFile <span class="title function_">getLastMappedFile</span><span class=params>(<span class=keyword>final</span> <span class=type>long</span> startOffset, <span class=type>boolean</span> needCreate)</span> {</span><br><span class=line> <span class=type>long</span> <span class=variable>createOffset</span> <span class=operator>=</span> -<span class=number>1</span>;</span><br><span class=line> <span class=comment>// 调用前面的方法,只是从 mappedFileQueue 获取最后一个</span></span><br><span class=line> <span class=type>MappedFile</span> <span class=variable>mappedFileLast</span> <span class=operator>=</span> getLastMappedFile();</span><br><span class=line></span><br><span class=line> <span class=comment>// 如果为空,计算下创建的偏移量</span></span><br><span class=line> <span class=keyword>if</span> (mappedFileLast == <span class=literal>null</span>) {</span><br><span class=line> createOffset = startOffset - (startOffset % <span class=built_in>this</span>.mappedFileSize);</span><br><span class=line> }</span><br><span class=line> </span><br><span class=line> <span class=comment>// 如果不为空,但是当前的文件写满了</span></span><br><span class=line> <span class=keyword>if</span> (mappedFileLast != <span class=literal>null</span> && mappedFileLast.isFull()) {</span><br><span class=line> <span class=comment>// 前一个的偏移量加上单个文件的偏移量,也就是 1G</span></span><br><span class=line> createOffset = mappedFileLast.getFileFromOffset() + <span class=built_in>this</span>.mappedFileSize;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>if</span> (createOffset != -<span class=number>1</span> && needCreate) {</span><br><span class=line> <span class=comment>// 根据 createOffset 转换成文件名进行创建</span></span><br><span class=line> <span class=type>String</span> <span class=variable>nextFilePath</span> <span class=operator>=</span> <span class=built_in>this</span>.storePath + File.separator + UtilAll.offset2FileName(createOffset);</span><br><span class=line> <span class=type>String</span> <span class=variable>nextNextFilePath</span> <span class=operator>=</span> <span class=built_in>this</span>.storePath + File.separator</span><br><span class=line> + UtilAll.offset2FileName(createOffset + <span class=built_in>this</span>.mappedFileSize);</span><br><span class=line> <span class=type>MappedFile</span> <span class=variable>mappedFile</span> <span class=operator>=</span> <span class=literal>null</span>;</span><br><span class=line></span><br><span class=line> <span class=comment>// 这里如果allocateMappedFileService 存在,就提交请求</span></span><br><span class=line> <span class=keyword>if</span> (<span class=built_in>this</span>.allocateMappedFileService != <span class=literal>null</span>) {</span><br><span class=line> mappedFile = <span class=built_in>this</span>.allocateMappedFileService.putRequestAndReturnMappedFile(nextFilePath,</span><br><span class=line> nextNextFilePath, <span class=built_in>this</span>.mappedFileSize);</span><br><span class=line> } <span class=keyword>else</span> {</span><br><span class=line> <span class=keyword>try</span> {</span><br><span class=line> <span class=comment>// 否则就直接创建</span></span><br><span class=line> mappedFile = <span class=keyword>new</span> <span class="title class_">MappedFile</span>(nextFilePath, <span class=built_in>this</span>.mappedFileSize);</span><br><span class=line> } <span class=keyword>catch</span> (IOException e) {</span><br><span class=line> log.error(<span class=string>"create mappedFile exception"</span>, e);</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>if</span> (mappedFile != <span class=literal>null</span>) {</span><br><span class=line> <span class=keyword>if</span> (<span class=built_in>this</span>.mappedFiles.isEmpty()) {</span><br><span class=line> mappedFile.setFirstCreateInQueue(<span class=literal>true</span>);</span><br><span class=line> }</span><br><span class=line> <span class=built_in>this</span>.mappedFiles.add(mappedFile);</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>return</span> mappedFile;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>return</span> mappedFileLast;</span><br><span class=line> }</span><br></pre></table></figure><p>首先看下直接创建的,<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br><span class=line>17</span><br><span class=line>18</span><br><span class=line>19</span><br><span class=line>20</span><br><span class=line>21</span><br><span class=line>22</span><br><span class=line>23</span><br><span class=line>24</span><br><span class=line>25</span><br><span class=line>26</span><br><span class=line>27</span><br><span class=line>28</span><br><span class=line>29</span><br><span class=line>30</span><br><span class=line>31</span><br><span class=line>32</span><br></pre><td class=code><pre><span class=line><span class=keyword>public</span> <span class="title function_">MappedFile</span><span class=params>(<span class=keyword>final</span> String fileName, <span class=keyword>final</span> <span class=type>int</span> fileSize)</span> <span class=keyword>throws</span> IOException {</span><br><span class=line> init(fileName, fileSize);</span><br><span class=line> }</span><br><span class=line><span class=keyword>private</span> <span class=keyword>void</span> <span class="title function_">init</span><span class=params>(<span class=keyword>final</span> String fileName, <span class=keyword>final</span> <span class=type>int</span> fileSize)</span> <span class=keyword>throws</span> IOException {</span><br><span class=line> <span class=built_in>this</span>.fileName = fileName;</span><br><span class=line> <span class=built_in>this</span>.fileSize = fileSize;</span><br><span class=line> <span class=built_in>this</span>.file = <span class=keyword>new</span> <span class="title class_">File</span>(fileName);</span><br><span class=line> <span class=built_in>this</span>.fileFromOffset = Long.parseLong(<span class=built_in>this</span>.file.getName());</span><br><span class=line> <span class=type>boolean</span> <span class=variable>ok</span> <span class=operator>=</span> <span class=literal>false</span>;</span><br><span class=line></span><br><span class=line> ensureDirOK(<span class=built_in>this</span>.file.getParent());</span><br><span class=line></span><br><span class=line> <span class=keyword>try</span> {</span><br><span class=line> <span class=comment>// 通过 RandomAccessFile 创建 fileChannel</span></span><br><span class=line> <span class=built_in>this</span>.fileChannel = <span class=keyword>new</span> <span class="title class_">RandomAccessFile</span>(<span class=built_in>this</span>.file, <span class=string>"rw"</span>).getChannel();</span><br><span class=line> <span class=comment>// 做 mmap 映射</span></span><br><span class=line> <span class=built_in>this</span>.mappedByteBuffer = <span class=built_in>this</span>.fileChannel.map(MapMode.READ_WRITE, <span class=number>0</span>, fileSize);</span><br><span class=line> TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(fileSize);</span><br><span class=line> TOTAL_MAPPED_FILES.incrementAndGet();</span><br><span class=line> ok = <span class=literal>true</span>;</span><br><span class=line> } <span class=keyword>catch</span> (FileNotFoundException e) {</span><br><span class=line> log.error(<span class=string>"create file channel "</span> + <span class=built_in>this</span>.fileName + <span class=string>" Failed. "</span>, e);</span><br><span class=line> <span class=keyword>throw</span> e;</span><br><span class=line> } <span class=keyword>catch</span> (IOException e) {</span><br><span class=line> log.error(<span class=string>"map file "</span> + <span class=built_in>this</span>.fileName + <span class=string>" Failed. "</span>, e);</span><br><span class=line> <span class=keyword>throw</span> e;</span><br><span class=line> } <span class=keyword>finally</span> {</span><br><span class=line> <span class=keyword>if</span> (!ok && <span class=built_in>this</span>.fileChannel != <span class=literal>null</span>) {</span><br><span class=line> <span class=built_in>this</span>.fileChannel.close();</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line> }</span><br></pre></table></figure><p>如果是提交给<code>AllocateMappedFileService</code>的话就用到了一些异步操作<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br><span class=line>17</span><br><span class=line>18</span><br><span class=line>19</span><br><span class=line>20</span><br><span class=line>21</span><br><span class=line>22</span><br><span class=line>23</span><br><span class=line>24</span><br><span class=line>25</span><br><span class=line>26</span><br><span class=line>27</span><br><span class=line>28</span><br><span class=line>29</span><br><span class=line>30</span><br><span class=line>31</span><br><span class=line>32</span><br><span class=line>33</span><br><span class=line>34</span><br><span class=line>35</span><br><span class=line>36</span><br><span class=line>37</span><br><span class=line>38</span><br><span class=line>39</span><br><span class=line>40</span><br><span class=line>41</span><br><span class=line>42</span><br><span class=line>43</span><br><span class=line>44</span><br><span class=line>45</span><br><span class=line>46</span><br><span class=line>47</span><br><span class=line>48</span><br><span class=line>49</span><br><span class=line>50</span><br><span class=line>51</span><br><span class=line>52</span><br><span class=line>53</span><br><span class=line>54</span><br><span class=line>55</span><br><span class=line>56</span><br><span class=line>57</span><br><span class=line>58</span><br><span class=line>59</span><br><span class=line>60</span><br><span class=line>61</span><br><span class=line>62</span><br><span class=line>63</span><br><span class=line>64</span><br><span class=line>65</span><br><span class=line>66</span><br><span class=line>67</span><br><span class=line>68</span><br><span class=line>69</span><br><span class=line>70</span><br></pre><td class=code><pre><span class=line><span class=keyword>public</span> MappedFile <span class="title function_">putRequestAndReturnMappedFile</span><span class=params>(String nextFilePath, String nextNextFilePath, <span class=type>int</span> fileSize)</span> {</span><br><span class=line> <span class=type>int</span> <span class=variable>canSubmitRequests</span> <span class=operator>=</span> <span class=number>2</span>;</span><br><span class=line> <span class=keyword>if</span> (<span class=built_in>this</span>.messageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {</span><br><span class=line> <span class=keyword>if</span> (<span class=built_in>this</span>.messageStore.getMessageStoreConfig().isFastFailIfNoBufferInStorePool()</span><br><span class=line> && BrokerRole.SLAVE != <span class=built_in>this</span>.messageStore.getMessageStoreConfig().getBrokerRole()) { <span class=comment>//if broker is slave, don't fast fail even no buffer in pool</span></span><br><span class=line> canSubmitRequests = <span class=built_in>this</span>.messageStore.getTransientStorePool().remainBufferNumbs() - <span class=built_in>this</span>.requestQueue.size();</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line> <span class=comment>// 将请求放在 requestTable 中</span></span><br><span class=line> <span class=type>AllocateRequest</span> <span class=variable>nextReq</span> <span class=operator>=</span> <span class=keyword>new</span> <span class="title class_">AllocateRequest</span>(nextFilePath, fileSize);</span><br><span class=line> <span class=type>boolean</span> <span class=variable>nextPutOK</span> <span class=operator>=</span> <span class=built_in>this</span>.requestTable.putIfAbsent(nextFilePath, nextReq) == <span class=literal>null</span>;</span><br><span class=line> <span class=comment>// requestTable 使用了 concurrentHashMap,用文件名作为 key,防止并发</span></span><br><span class=line> <span class=keyword>if</span> (nextPutOK) {</span><br><span class=line> <span class=comment>// 这里判断了是否可以提交到 TransientStorePool,涉及读写分离,后面再细聊</span></span><br><span class=line> <span class=keyword>if</span> (canSubmitRequests <= <span class=number>0</span>) {</span><br><span class=line> log.warn(<span class=string>"[NOTIFYME]TransientStorePool is not enough, so create mapped file error, "</span> +</span><br><span class=line> <span class=string>"RequestQueueSize : {}, StorePoolSize: {}"</span>, <span class=built_in>this</span>.requestQueue.size(), <span class=built_in>this</span>.messageStore.getTransientStorePool().remainBufferNumbs());</span><br><span class=line> <span class=built_in>this</span>.requestTable.remove(nextFilePath);</span><br><span class=line> <span class=keyword>return</span> <span class=literal>null</span>;</span><br><span class=line> }</span><br><span class=line> <span class=comment>// 塞到阻塞队列中</span></span><br><span class=line> <span class=type>boolean</span> <span class=variable>offerOK</span> <span class=operator>=</span> <span class=built_in>this</span>.requestQueue.offer(nextReq);</span><br><span class=line> <span class=keyword>if</span> (!offerOK) {</span><br><span class=line> log.warn(<span class=string>"never expected here, add a request to preallocate queue failed"</span>);</span><br><span class=line> }</span><br><span class=line> canSubmitRequests--;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=comment>// 这里的两个提交我猜测是为了多生成一个 CommitLog,</span></span><br><span class=line> <span class=type>AllocateRequest</span> <span class=variable>nextNextReq</span> <span class=operator>=</span> <span class=keyword>new</span> <span class="title class_">AllocateRequest</span>(nextNextFilePath, fileSize);</span><br><span class=line> <span class=type>boolean</span> <span class=variable>nextNextPutOK</span> <span class=operator>=</span> <span class=built_in>this</span>.requestTable.putIfAbsent(nextNextFilePath, nextNextReq) == <span class=literal>null</span>;</span><br><span class=line> <span class=keyword>if</span> (nextNextPutOK) {</span><br><span class=line> <span class=keyword>if</span> (canSubmitRequests <= <span class=number>0</span>) {</span><br><span class=line> log.warn(<span class=string>"[NOTIFYME]TransientStorePool is not enough, so skip preallocate mapped file, "</span> +</span><br><span class=line> <span class=string>"RequestQueueSize : {}, StorePoolSize: {}"</span>, <span class=built_in>this</span>.requestQueue.size(), <span class=built_in>this</span>.messageStore.getTransientStorePool().remainBufferNumbs());</span><br><span class=line> <span class=built_in>this</span>.requestTable.remove(nextNextFilePath);</span><br><span class=line> } <span class=keyword>else</span> {</span><br><span class=line> <span class=type>boolean</span> <span class=variable>offerOK</span> <span class=operator>=</span> <span class=built_in>this</span>.requestQueue.offer(nextNextReq);</span><br><span class=line> <span class=keyword>if</span> (!offerOK) {</span><br><span class=line> log.warn(<span class=string>"never expected here, add a request to preallocate queue failed"</span>);</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>if</span> (hasException) {</span><br><span class=line> log.warn(<span class=built_in>this</span>.getServiceName() + <span class=string>" service has exception. so return null"</span>);</span><br><span class=line> <span class=keyword>return</span> <span class=literal>null</span>;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=type>AllocateRequest</span> <span class=variable>result</span> <span class=operator>=</span> <span class=built_in>this</span>.requestTable.get(nextFilePath);</span><br><span class=line> <span class=keyword>try</span> {</span><br><span class=line> <span class=comment>// 这里就异步等着</span></span><br><span class=line> <span class=keyword>if</span> (result != <span class=literal>null</span>) {</span><br><span class=line> <span class=type>boolean</span> <span class=variable>waitOK</span> <span class=operator>=</span> result.getCountDownLatch().await(waitTimeOut, TimeUnit.MILLISECONDS);</span><br><span class=line> <span class=keyword>if</span> (!waitOK) {</span><br><span class=line> log.warn(<span class=string>"create mmap timeout "</span> + result.getFilePath() + <span class=string>" "</span> + result.getFileSize());</span><br><span class=line> <span class=keyword>return</span> <span class=literal>null</span>;</span><br><span class=line> } <span class=keyword>else</span> {</span><br><span class=line> <span class=built_in>this</span>.requestTable.remove(nextFilePath);</span><br><span class=line> <span class=keyword>return</span> result.getMappedFile();</span><br><span class=line> }</span><br><span class=line> } <span class=keyword>else</span> {</span><br><span class=line> log.error(<span class=string>"find preallocate mmap failed, this never happen"</span>);</span><br><span class=line> }</span><br><span class=line> } <span class=keyword>catch</span> (InterruptedException e) {</span><br><span class=line> log.warn(<span class=built_in>this</span>.getServiceName() + <span class=string>" service has exception. "</span>, e);</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>return</span> <span class=literal>null</span>;</span><br><span class=line> }</span><br></pre></table></figure><p>而真正去执行文件操作的就是 <code>AllocateMappedFileService</code>的 run 方法<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br><span class=line>17</span><br><span class=line>18</span><br><span class=line>19</span><br><span class=line>20</span><br><span class=line>21</span><br><span class=line>22</span><br><span class=line>23</span><br><span class=line>24</span><br><span class=line>25</span><br><span class=line>26</span><br><span class=line>27</span><br><span class=line>28</span><br><span class=line>29</span><br><span class=line>30</span><br><span class=line>31</span><br><span class=line>32</span><br><span class=line>33</span><br><span class=line>34</span><br><span class=line>35</span><br><span class=line>36</span><br><span class=line>37</span><br><span class=line>38</span><br><span class=line>39</span><br><span class=line>40</span><br><span class=line>41</span><br><span class=line>42</span><br><span class=line>43</span><br><span class=line>44</span><br><span class=line>45</span><br><span class=line>46</span><br><span class=line>47</span><br><span class=line>48</span><br><span class=line>49</span><br><span class=line>50</span><br><span class=line>51</span><br><span class=line>52</span><br><span class=line>53</span><br><span class=line>54</span><br><span class=line>55</span><br><span class=line>56</span><br><span class=line>57</span><br><span class=line>58</span><br><span class=line>59</span><br><span class=line>60</span><br><span class=line>61</span><br><span class=line>62</span><br><span class=line>63</span><br><span class=line>64</span><br><span class=line>65</span><br><span class=line>66</span><br><span class=line>67</span><br><span class=line>68</span><br><span class=line>69</span><br><span class=line>70</span><br><span class=line>71</span><br><span class=line>72</span><br><span class=line>73</span><br><span class=line>74</span><br><span class=line>75</span><br><span class=line>76</span><br><span class=line>77</span><br><span class=line>78</span><br><span class=line>79</span><br><span class=line>80</span><br><span class=line>81</span><br><span class=line>82</span><br><span class=line>83</span><br><span class=line>84</span><br><span class=line>85</span><br><span class=line>86</span><br></pre><td class=code><pre><span class=line><span class=keyword>public</span> <span class=keyword>void</span> <span class="title function_">run</span><span class=params>()</span> {</span><br><span class=line> log.info(<span class=built_in>this</span>.getServiceName() + <span class=string>" service started"</span>);</span><br><span class=line></span><br><span class=line> <span class=keyword>while</span> (!<span class=built_in>this</span>.isStopped() && <span class=built_in>this</span>.mmapOperation()) {</span><br><span class=line></span><br><span class=line> }</span><br><span class=line> log.info(<span class=built_in>this</span>.getServiceName() + <span class=string>" service end"</span>);</span><br><span class=line> }</span><br><span class=line><span class=keyword>private</span> <span class=type>boolean</span> <span class="title function_">mmapOperation</span><span class=params>()</span> {</span><br><span class=line> <span class=type>boolean</span> <span class=variable>isSuccess</span> <span class=operator>=</span> <span class=literal>false</span>;</span><br><span class=line> <span class=type>AllocateRequest</span> <span class=variable>req</span> <span class=operator>=</span> <span class=literal>null</span>;</span><br><span class=line> <span class=keyword>try</span> {</span><br><span class=line> <span class=comment>// 从阻塞队列里获取请求</span></span><br><span class=line> req = <span class=built_in>this</span>.requestQueue.take();</span><br><span class=line> <span class=type>AllocateRequest</span> <span class=variable>expectedRequest</span> <span class=operator>=</span> <span class=built_in>this</span>.requestTable.get(req.getFilePath());</span><br><span class=line> <span class=keyword>if</span> (<span class=literal>null</span> == expectedRequest) {</span><br><span class=line> log.warn(<span class=string>"this mmap request expired, maybe cause timeout "</span> + req.getFilePath() + <span class=string>" "</span></span><br><span class=line> + req.getFileSize());</span><br><span class=line> <span class=keyword>return</span> <span class=literal>true</span>;</span><br><span class=line> }</span><br><span class=line> <span class=keyword>if</span> (expectedRequest != req) {</span><br><span class=line> log.warn(<span class=string>"never expected here, maybe cause timeout "</span> + req.getFilePath() + <span class=string>" "</span></span><br><span class=line> + req.getFileSize() + <span class=string>", req:"</span> + req + <span class=string>", expectedRequest:"</span> + expectedRequest);</span><br><span class=line> <span class=keyword>return</span> <span class=literal>true</span>;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>if</span> (req.getMappedFile() == <span class=literal>null</span>) {</span><br><span class=line> <span class=type>long</span> <span class=variable>beginTime</span> <span class=operator>=</span> System.currentTimeMillis();</span><br><span class=line></span><br><span class=line> MappedFile mappedFile;</span><br><span class=line> <span class=keyword>if</span> (messageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {</span><br><span class=line> <span class=keyword>try</span> {</span><br><span class=line> <span class=comment>// 通过 transientStorePool 创建</span></span><br><span class=line> mappedFile = ServiceLoader.load(MappedFile.class).iterator().next();</span><br><span class=line> mappedFile.init(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool());</span><br><span class=line> } <span class=keyword>catch</span> (RuntimeException e) {</span><br><span class=line> log.warn(<span class=string>"Use default implementation."</span>);</span><br><span class=line> <span class=comment>// 默认创建</span></span><br><span class=line> mappedFile = <span class=keyword>new</span> <span class="title class_">MappedFile</span>(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool());</span><br><span class=line> }</span><br><span class=line> } <span class=keyword>else</span> {</span><br><span class=line> <span class=comment>// 默认创建</span></span><br><span class=line> mappedFile = <span class=keyword>new</span> <span class="title class_">MappedFile</span>(req.getFilePath(), req.getFileSize());</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=type>long</span> <span class=variable>eclipseTime</span> <span class=operator>=</span> UtilAll.computeEclipseTimeMilliseconds(beginTime);</span><br><span class=line> <span class=keyword>if</span> (eclipseTime > <span class=number>10</span>) {</span><br><span class=line> <span class=type>int</span> <span class=variable>queueSize</span> <span class=operator>=</span> <span class=built_in>this</span>.requestQueue.size();</span><br><span class=line> log.warn(<span class=string>"create mappedFile spent time(ms) "</span> + eclipseTime + <span class=string>" queue size "</span> + queueSize</span><br><span class=line> + <span class=string>" "</span> + req.getFilePath() + <span class=string>" "</span> + req.getFileSize());</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=comment>// pre write mappedFile</span></span><br><span class=line> <span class=keyword>if</span> (mappedFile.getFileSize() >= <span class=built_in>this</span>.messageStore.getMessageStoreConfig()</span><br><span class=line> .getMapedFileSizeCommitLog()</span><br><span class=line> &&</span><br><span class=line> <span class=built_in>this</span>.messageStore.getMessageStoreConfig().isWarmMapedFileEnable()) {</span><br><span class=line> mappedFile.warmMappedFile(<span class=built_in>this</span>.messageStore.getMessageStoreConfig().getFlushDiskType(),</span><br><span class=line> <span class=built_in>this</span>.messageStore.getMessageStoreConfig().getFlushLeastPagesWhenWarmMapedFile());</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> req.setMappedFile(mappedFile);</span><br><span class=line> <span class=built_in>this</span>.hasException = <span class=literal>false</span>;</span><br><span class=line> isSuccess = <span class=literal>true</span>;</span><br><span class=line> }</span><br><span class=line> } <span class=keyword>catch</span> (InterruptedException e) {</span><br><span class=line> log.warn(<span class=built_in>this</span>.getServiceName() + <span class=string>" interrupted, possibly by shutdown."</span>);</span><br><span class=line> <span class=built_in>this</span>.hasException = <span class=literal>true</span>;</span><br><span class=line> <span class=keyword>return</span> <span class=literal>false</span>;</span><br><span class=line> } <span class=keyword>catch</span> (IOException e) {</span><br><span class=line> log.warn(<span class=built_in>this</span>.getServiceName() + <span class=string>" service has exception. "</span>, e);</span><br><span class=line> <span class=built_in>this</span>.hasException = <span class=literal>true</span>;</span><br><span class=line> <span class=keyword>if</span> (<span class=literal>null</span> != req) {</span><br><span class=line> requestQueue.offer(req);</span><br><span class=line> <span class=keyword>try</span> {</span><br><span class=line> Thread.sleep(<span class=number>1</span>);</span><br><span class=line> } <span class=keyword>catch</span> (InterruptedException ignored) {</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line> } <span class=keyword>finally</span> {</span><br><span class=line> <span class=keyword>if</span> (req != <span class=literal>null</span> && isSuccess)</span><br><span class=line> <span class=comment>// 通知前面等待的</span></span><br><span class=line> req.getCountDownLatch().countDown();</span><br><span class=line> }</span><br><span class=line> <span class=keyword>return</span> <span class=literal>true</span>;</span><br><span class=line> }</span><br></pre></table></figure></div><footer class=post-footer><div class=post-eof></div></footer></article></div><div class=post-block><article class=post-content itemscope itemtype=http://schema.org/Article><link href=https://nicksxs.me/2021/09/04/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8/ itemprop=mainEntityOfPage><span hidden itemprop=author itemscope itemtype=http://schema.org/Person><meta content=/uploads/avatar.jpg itemprop=image><meta content=Nicksxs itemprop=name></span><span hidden itemprop=publisher itemscope itemtype=http://schema.org/Organization><meta content="Nicksxs's Blog" itemprop=name><meta content="learn from zero,技术博客,Nicksxs,史学森" itemprop=description></span><span hidden itemprop=post itemscope itemtype=http://schema.org/CreativeWork><meta content="undefined | Nicksxs's Blog" itemprop=name><meta itemprop=description></span><header class=post-header><h2 itemprop="name headline" class=post-title><a class=post-title-link href=/2021/09/04/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8/ itemprop=url>聊一下 RocketMQ 的消息存储之 MMAP</a></h2><div class=post-meta-container><div class=post-meta><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar"></i> </span><span class=post-meta-item-text>发表于</span> <time itemprop="dateCreated datePublished" title="创建时间:2021-09-04 10:29:41" datetime=2021-09-04T10:29:41+08:00>2021-09-04</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar-check"></i> </span><span class=post-meta-item-text>更新于</span> <time title="修改时间:2022-06-11 22:45:11" datetime=2022-06-11T22:45:11+08:00 itemprop=dateModified>2022-06-11</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-folder"></i> </span><span class=post-meta-item-text>分类于</span> <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/MQ/ itemprop=url rel=index><span itemprop=name>MQ</span></a> </span>, <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/MQ/RocketMQ/ itemprop=url rel=index><span itemprop=name>RocketMQ</span></a> </span>, <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/MQ/RocketMQ/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/ itemprop=url rel=index><span itemprop=name>消息队列</span></a> </span></span><span class="post-meta-item leancloud_visitors" data-flag-title="聊一下 RocketMQ 的消息存储之 MMAP" id=/2021/09/04/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8/ title=阅读次数><span class=post-meta-item-icon><i class="far fa-eye"></i> </span><span class=post-meta-item-text>阅读次数:</span> <span class=leancloud-visitors-count></span> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-comment"></i> </span><span class=post-meta-item-text>Disqus:</span> <a href=/2021/09/04/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8/#disqus_thread itemprop=discussionUrl title=disqus><span class="post-comments-count disqus-comment-count" data-disqus-identifier=2021/09/04/聊一下-RocketMQ-的消息存储/ itemprop=commentCount></span></a></span></div></div></header><div class=post-body itemprop=articleBody><p>这是个很大的话题了,可能会分成两部分说,第一部分就是所谓的零拷贝 ( zero-copy ),这一块其实也不新鲜,我对零拷贝的概念主要来自<a href=https://www.linuxjournal.com/article/6345 rel=noopener target=_blank>这篇文章</a>,个人感觉写得非常好,在 rocketmq 中,最大的一块存储就是消息存储,也就是 CommitLog ,当然还有 ConsumeQueue 和 IndexFile,以及其他一些文件,CommitLog 的存储是以一个 1G 大小的文件作为存储单位,写完了就再建一个,那么如何提高这 1G 文件的读写效率呢,就是 mmap,传统意义的读写文件,read,write 都需要由系统调用,来回地在用户态跟内核态进行拷贝切换,<figure class="highlight c"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>read(file, tmp_buf, len);</span><br><span class=line>write(socket, tmp_buf, len);</span><br></pre></table></figure><p><img alt=vms95Z data-src=https://img.nicksxs.com/uPic/vms95Z.jpg><p>如上面的图显示的,要在用户态跟内核态进行切换,数据还需要在内核缓冲跟用户缓冲之间拷贝多次,<blockquote><ol><li>第一步是调用 read,需要在用户态切换成内核态,<a href=https://en.wikipedia.org/wiki/Direct_memory_access rel=noopener target=_blank>DMA</a>模块从磁盘中读取文件,并存储在内核缓冲区,相当于是第一次复制<li>数据从内核缓冲区被拷贝到用户缓冲区,read 调用返回,伴随着内核态又切换成用户态,完成了第二次复制<li>然后是write 写入,这里也会伴随着用户态跟内核态的切换,数据从用户缓冲区被复制到内核空间缓冲区,完成了第三次复制,这次有点不一样的是数据不是在内核缓冲区了,会复制到 socket buffer 中。<li>write 系统调用返回,又切换回了用户态,然后数据由 DMA 拷贝到协议引擎。</ol></blockquote><p>如此就能看出其实默认的读写操作代价是非常大的,而在 rocketmq 等高性能中间件中都有使用的零拷贝技术,其中 rocketmq 使用的是 mmap<h3 id=mmap><a class=headerlink href=#mmap title=mmap></a>mmap</h3><p>mmap基于 OS 的 <a href=https://en.wikipedia.org/wiki/Mmap rel=noopener target=_blank>mmap</a> 的内存映射技术,通过<a href=https://en.wikipedia.org/wiki/Memory_management_unit rel=noopener target=_blank>MMU</a> 映射文件,将文件直接映射到用户态的内存地址,使得对文件的操作不再是 write/read,而转化为直接对内存地址的操作,使随机读写文件和读写内存相似的速度。<blockquote><p>mmap 把文件映射到用户空间里的虚拟内存,省去了从内核缓冲区复制到用户空间的过程,文件中的位置在虚拟内存中有了对应的地址,可以像操作内存一样操作这个文件,这样的文件读写文件方式少了数据从内核缓存到用户空间的拷贝,效率很高。</blockquote><figure class="highlight c"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>tmp_buf = mmap(file, len);</span><br><span class=line>write(socket, tmp_buf, len);</span><br></pre></table></figure><p><img alt=I68mFx data-src=https://img.nicksxs.com/uPic/I68mFx.jpg><blockquote><p>第一步:mmap系统调用使得文件内容被DMA引擎复制到内核缓冲区。然后该缓冲区与用户进程共享,在内核和用户内存空间之间不进行任何拷贝。<p>第二步:写系统调用使得内核将数据从原来的内核缓冲区复制到与套接字相关的内核缓冲区。<p>第三步:第三次拷贝发生在DMA引擎将数据从内核套接字缓冲区传递给协议引擎时。<p>通过使用mmap而不是read,我们将内核需要拷贝的数据量减少了一半。当大量的数据被传输时,这将有很好的效果。然而,这种改进并不是没有代价的;在使用mmap+write方法时,有一些隐藏的陷阱。例如当你对一个文件进行内存映射,然后在另一个进程截断同一文件时调用写。你的写系统调用将被总线错误信号SIGBUS打断,因为你执行了一个错误的内存访问。该信号的默认行为是杀死进程并dumpcore–这对网络服务器来说不是最理想的操作。<p>有两种方法可以解决这个问题。<p>第一种方法是为SIGBUS信号安装一个信号处理程序,然后在处理程序中简单地调用返回。通过这样做,写系统调用会返回它在被打断之前所写的字节数,并将errno设置为成功。让我指出,这将是一个糟糕的解决方案,一个治标不治本的解决方案。因为SIGBUS预示着进程出了严重的问题,所以不鼓励使用这种解决方案。<p>第二个解决方案涉及内核的文件租赁(在Windows中称为 “机会锁”)。这是解决这个问题的正确方法。通过在文件描述符上使用租赁,你与内核在一个特定的文件上达成租约。然后你可以向内核请求一个读/写租约。当另一个进程试图截断你正在传输的文件时,内核会向你发送一个实时信号,即RT_SIGNAL_LEASE信号。它告诉你内核即将终止你对该文件的写或读租约。在你的程序访问一个无效的地址和被SIGBUS信号杀死之前,你的写调用会被打断了。写入调用的返回值是中断前写入的字节数,errno将被设置为成功。下面是一些示例代码,显示了如何从内核中获得租约。<figure class="highlight c"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br></pre><td class=code><pre><span class=line><span class=keyword>if</span>(fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == <span class=number>-1</span>) {</span><br><span class=line> perror(<span class=string>"kernel lease set signal"</span>);</span><br><span class=line> <span class=keyword>return</span> <span class=number>-1</span>;</span><br><span class=line>}</span><br><span class=line><span class=comment>/* l_type can be F_RDLCK F_WRLCK */</span></span><br><span class=line><span class=keyword>if</span>(fcntl(fd, F_SETLEASE, l_type)){</span><br><span class=line> perror(<span class=string>"kernel lease set type"</span>);</span><br><span class=line> <span class=keyword>return</span> <span class=number>-1</span>;</span><br><span class=line>}</span><br></pre></table></figure></blockquote></div><footer class=post-footer><div class=post-eof></div></footer></article></div><div class=post-block><article class=post-content itemscope itemtype=http://schema.org/Article><link href=https://nicksxs.me/2021/08/29/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E9%A1%BA%E5%BA%8F%E6%B6%88%E6%81%AF/ itemprop=mainEntityOfPage><span hidden itemprop=author itemscope itemtype=http://schema.org/Person><meta content=/uploads/avatar.jpg itemprop=image><meta content=Nicksxs itemprop=name></span><span hidden itemprop=publisher itemscope itemtype=http://schema.org/Organization><meta content="Nicksxs's Blog" itemprop=name><meta content="learn from zero,技术博客,Nicksxs,史学森" itemprop=description></span><span hidden itemprop=post itemscope itemtype=http://schema.org/CreativeWork><meta content="undefined | Nicksxs's Blog" itemprop=name><meta itemprop=description></span><header class=post-header><h2 itemprop="name headline" class=post-title><a class=post-title-link href=/2021/08/29/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E9%A1%BA%E5%BA%8F%E6%B6%88%E6%81%AF/ itemprop=url>聊一下 RocketMQ 的顺序消息</a></h2><div class=post-meta-container><div class=post-meta><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar"></i> </span><span class=post-meta-item-text>发表于</span> <time itemprop="dateCreated datePublished" title="创建时间:2021-08-29 20:41:17 / 修改时间:23:16:21" datetime=2021-08-29T20:41:17+08:00>2021-08-29</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-folder"></i> </span><span class=post-meta-item-text>分类于</span> <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/MQ/ itemprop=url rel=index><span itemprop=name>MQ</span></a> </span>, <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/MQ/RocketMQ/ itemprop=url rel=index><span itemprop=name>RocketMQ</span></a> </span>, <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/MQ/RocketMQ/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/ itemprop=url rel=index><span itemprop=name>消息队列</span></a> </span></span><span class="post-meta-item leancloud_visitors" data-flag-title="聊一下 RocketMQ 的顺序消息" id=/2021/08/29/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E9%A1%BA%E5%BA%8F%E6%B6%88%E6%81%AF/ title=阅读次数><span class=post-meta-item-icon><i class="far fa-eye"></i> </span><span class=post-meta-item-text>阅读次数:</span> <span class=leancloud-visitors-count></span> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-comment"></i> </span><span class=post-meta-item-text>Disqus:</span> <a href=/2021/08/29/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E9%A1%BA%E5%BA%8F%E6%B6%88%E6%81%AF/#disqus_thread itemprop=discussionUrl title=disqus><span class="post-comments-count disqus-comment-count" data-disqus-identifier=2021/08/29/聊一下-RocketMQ-的顺序消息/ itemprop=commentCount></span></a></span></div></div></header><div class=post-body itemprop=articleBody><p>rocketmq 里有一种比较特殊的用法,就是顺序消息,比如订单的生命周期里,在创建,支付,签收等状态轮转中,会发出来对应的消息,这里面就比较需要去保证他们的顺序,当然在处理的业务代码也可以做对应的处理,结合消息重投,但是如果这里消息就能保证顺序性了,那么业务代码就能更好的关注业务代码的处理。<p>首先有一种情况是全局的有序,比如对于一个 topic 里就得发送线程保证只有一个,内部的 queue 也只有一个,消费线程也只有一个,这样就能比较容易的保证全局顺序性了,但是这里的问题就是完全限制了性能,不是很现实,在真实场景里很多都是比如对于同一个订单,需要去保证状态的轮转是按照预期的顺序来,而不必要全局的有序性。<p>对于这类的有序性,需要在发送和接收方都有对应的处理,在发送消息中,需要去指定 selector,即<code>MessageQueueSelector</code>,能够以固定的方式是分配到对应的 <code>MessageQueue</code><p>比如像 RocketMQ 中的示例<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br></pre><td class=code><pre><span class=line><span class=type>SendResult</span> <span class=variable>sendResult</span> <span class=operator>=</span> producer.send(msg, <span class=keyword>new</span> <span class="title class_">MessageQueueSelector</span>() {</span><br><span class=line> <span class=meta>@Override</span></span><br><span class=line> <span class=keyword>public</span> MessageQueue <span class="title function_">select</span><span class=params>(List<MessageQueue> mqs, Message msg, Object arg)</span> {</span><br><span class=line> <span class=type>Long</span> <span class=variable>id</span> <span class=operator>=</span> (Long) arg; <span class=comment>//message queue is selected by #salesOrderID</span></span><br><span class=line> <span class=type>long</span> <span class=variable>index</span> <span class=operator>=</span> id % mqs.size();</span><br><span class=line> <span class=keyword>return</span> mqs.get((<span class=type>int</span>) index);</span><br><span class=line> }</span><br><span class=line> }, orderList.get(i).getOrderId());</span><br></pre></table></figure><p>而在消费侧有几个点比较重要,首先我们要保证一个 MessageQueue只被一个消费者消费,消费队列存在broker端,要保证 MessageQueue 只被一个消费者消费,那么消费者在进行消息拉取消费时就<strong>必须向mq服务器申请队列锁</strong>,消费者申请队列锁的代码存在于RebalanceService消息队列负载的实现代码中。<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br><span class=line>17</span><br><span class=line>18</span><br><span class=line>19</span><br><span class=line>20</span><br><span class=line>21</span><br><span class=line>22</span><br><span class=line>23</span><br><span class=line>24</span><br><span class=line>25</span><br><span class=line>26</span><br><span class=line>27</span><br><span class=line>28</span><br><span class=line>29</span><br><span class=line>30</span><br><span class=line>31</span><br></pre><td class=code><pre><span class=line>List<PullRequest> pullRequestList = <span class=keyword>new</span> <span class="title class_">ArrayList</span><PullRequest>();</span><br><span class=line> <span class=keyword>for</span> (MessageQueue mq : mqSet) {</span><br><span class=line> <span class=keyword>if</span> (!<span class=built_in>this</span>.processQueueTable.containsKey(mq)) {</span><br><span class=line> <span class=comment>// 判断是否顺序,如果是顺序消费的,则需要加锁</span></span><br><span class=line> <span class=keyword>if</span> (isOrder && !<span class=built_in>this</span>.lock(mq)) {</span><br><span class=line> log.warn(<span class=string>"doRebalance, {}, add a new mq failed, {}, because lock failed"</span>, consumerGroup, mq);</span><br><span class=line> <span class=keyword>continue</span>;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=built_in>this</span>.removeDirtyOffset(mq);</span><br><span class=line> <span class=type>ProcessQueue</span> <span class=variable>pq</span> <span class=operator>=</span> <span class=keyword>new</span> <span class="title class_">ProcessQueue</span>();</span><br><span class=line> <span class=type>long</span> <span class=variable>nextOffset</span> <span class=operator>=</span> <span class=built_in>this</span>.computePullFromWhere(mq);</span><br><span class=line> <span class=keyword>if</span> (nextOffset >= <span class=number>0</span>) {</span><br><span class=line> <span class=type>ProcessQueue</span> <span class=variable>pre</span> <span class=operator>=</span> <span class=built_in>this</span>.processQueueTable.putIfAbsent(mq, pq);</span><br><span class=line> <span class=keyword>if</span> (pre != <span class=literal>null</span>) {</span><br><span class=line> log.info(<span class=string>"doRebalance, {}, mq already exists, {}"</span>, consumerGroup, mq);</span><br><span class=line> } <span class=keyword>else</span> {</span><br><span class=line> log.info(<span class=string>"doRebalance, {}, add a new mq, {}"</span>, consumerGroup, mq);</span><br><span class=line> <span class=type>PullRequest</span> <span class=variable>pullRequest</span> <span class=operator>=</span> <span class=keyword>new</span> <span class="title class_">PullRequest</span>();</span><br><span class=line> pullRequest.setConsumerGroup(consumerGroup);</span><br><span class=line> pullRequest.setNextOffset(nextOffset);</span><br><span class=line> pullRequest.setMessageQueue(mq);</span><br><span class=line> pullRequest.setProcessQueue(pq);</span><br><span class=line> pullRequestList.add(pullRequest);</span><br><span class=line> changed = <span class=literal>true</span>;</span><br><span class=line> }</span><br><span class=line> } <span class=keyword>else</span> {</span><br><span class=line> log.warn(<span class=string>"doRebalance, {}, add new mq failed, {}"</span>, consumerGroup, mq);</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line> }</span><br></pre></table></figure><p>在申请到锁之后会创建 pullRequest 进行消息拉取,消息拉取部分的代码实现在PullMessageService中,<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br></pre><td class=code><pre><span class=line><span class=meta>@Override</span></span><br><span class=line> <span class=keyword>public</span> <span class=keyword>void</span> <span class="title function_">run</span><span class=params>()</span> {</span><br><span class=line> log.info(<span class=built_in>this</span>.getServiceName() + <span class=string>" service started"</span>);</span><br><span class=line></span><br><span class=line> <span class=keyword>while</span> (!<span class=built_in>this</span>.isStopped()) {</span><br><span class=line> <span class=keyword>try</span> {</span><br><span class=line> <span class=type>PullRequest</span> <span class=variable>pullRequest</span> <span class=operator>=</span> <span class=built_in>this</span>.pullRequestQueue.take();</span><br><span class=line> <span class=built_in>this</span>.pullMessage(pullRequest);</span><br><span class=line> } <span class=keyword>catch</span> (InterruptedException ignored) {</span><br><span class=line> } <span class=keyword>catch</span> (Exception e) {</span><br><span class=line> log.error(<span class=string>"Pull Message Service Run Method exception"</span>, e);</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> log.info(<span class=built_in>this</span>.getServiceName() + <span class=string>" service end"</span>);</span><br><span class=line> }</span><br></pre></table></figure><p>消息拉取完后,需要提交到ConsumeMessageService中进行消费,顺序消费的实现为ConsumeMessageOrderlyService,提交消息进行消费的方法为ConsumeMessageOrderlyService#submitConsumeRequest,具体实现如下:<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br></pre><td class=code><pre><span class=line><span class=meta>@Override</span></span><br><span class=line><span class=keyword>public</span> <span class=keyword>void</span> <span class="title function_">submitConsumeRequest</span><span class=params>(</span></span><br><span class=line><span class=params> <span class=keyword>final</span> List<MessageExt> msgs,</span></span><br><span class=line><span class=params> <span class=keyword>final</span> ProcessQueue processQueue,</span></span><br><span class=line><span class=params> <span class=keyword>final</span> MessageQueue messageQueue,</span></span><br><span class=line><span class=params> <span class=keyword>final</span> <span class=type>boolean</span> dispathToConsume)</span> {</span><br><span class=line> <span class=keyword>if</span> (dispathToConsume) {</span><br><span class=line> <span class=type>ConsumeRequest</span> <span class=variable>consumeRequest</span> <span class=operator>=</span> <span class=keyword>new</span> <span class="title class_">ConsumeRequest</span>(processQueue, messageQueue);</span><br><span class=line> <span class=built_in>this</span>.consumeExecutor.submit(consumeRequest);</span><br><span class=line> }</span><br><span class=line>}</span><br></pre></table></figure><p>构建了一个ConsumeRequest对象,它是个实现了 runnable 接口的类,并提交给了线程池来并行消费,看下顺序消费的ConsumeRequest的run方法实现:<figure class="highlight java"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br><span class=line>3</span><br><span class=line>4</span><br><span class=line>5</span><br><span class=line>6</span><br><span class=line>7</span><br><span class=line>8</span><br><span class=line>9</span><br><span class=line>10</span><br><span class=line>11</span><br><span class=line>12</span><br><span class=line>13</span><br><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br><span class=line>17</span><br><span class=line>18</span><br><span class=line>19</span><br><span class=line>20</span><br><span class=line>21</span><br><span class=line>22</span><br><span class=line>23</span><br><span class=line>24</span><br><span class=line>25</span><br><span class=line>26</span><br><span class=line>27</span><br><span class=line>28</span><br><span class=line>29</span><br><span class=line>30</span><br><span class=line>31</span><br><span class=line>32</span><br><span class=line>33</span><br><span class=line>34</span><br><span class=line>35</span><br><span class=line>36</span><br><span class=line>37</span><br><span class=line>38</span><br><span class=line>39</span><br><span class=line>40</span><br><span class=line>41</span><br><span class=line>42</span><br><span class=line>43</span><br><span class=line>44</span><br><span class=line>45</span><br><span class=line>46</span><br><span class=line>47</span><br><span class=line>48</span><br><span class=line>49</span><br><span class=line>50</span><br><span class=line>51</span><br><span class=line>52</span><br><span class=line>53</span><br><span class=line>54</span><br><span class=line>55</span><br><span class=line>56</span><br><span class=line>57</span><br><span class=line>58</span><br><span class=line>59</span><br><span class=line>60</span><br><span class=line>61</span><br><span class=line>62</span><br><span class=line>63</span><br><span class=line>64</span><br><span class=line>65</span><br><span class=line>66</span><br><span class=line>67</span><br><span class=line>68</span><br><span class=line>69</span><br><span class=line>70</span><br><span class=line>71</span><br><span class=line>72</span><br><span class=line>73</span><br><span class=line>74</span><br><span class=line>75</span><br><span class=line>76</span><br><span class=line>77</span><br><span class=line>78</span><br><span class=line>79</span><br><span class=line>80</span><br><span class=line>81</span><br><span class=line>82</span><br><span class=line>83</span><br><span class=line>84</span><br><span class=line>85</span><br><span class=line>86</span><br><span class=line>87</span><br><span class=line>88</span><br><span class=line>89</span><br><span class=line>90</span><br><span class=line>91</span><br><span class=line>92</span><br><span class=line>93</span><br><span class=line>94</span><br><span class=line>95</span><br><span class=line>96</span><br><span class=line>97</span><br><span class=line>98</span><br><span class=line>99</span><br><span class=line>100</span><br><span class=line>101</span><br><span class=line>102</span><br><span class=line>103</span><br><span class=line>104</span><br><span class=line>105</span><br><span class=line>106</span><br><span class=line>107</span><br><span class=line>108</span><br><span class=line>109</span><br><span class=line>110</span><br><span class=line>111</span><br><span class=line>112</span><br><span class=line>113</span><br><span class=line>114</span><br><span class=line>115</span><br><span class=line>116</span><br><span class=line>117</span><br><span class=line>118</span><br><span class=line>119</span><br><span class=line>120</span><br><span class=line>121</span><br><span class=line>122</span><br><span class=line>123</span><br><span class=line>124</span><br><span class=line>125</span><br><span class=line>126</span><br><span class=line>127</span><br><span class=line>128</span><br><span class=line>129</span><br><span class=line>130</span><br><span class=line>131</span><br><span class=line>132</span><br><span class=line>133</span><br><span class=line>134</span><br><span class=line>135</span><br><span class=line>136</span><br><span class=line>137</span><br><span class=line>138</span><br><span class=line>139</span><br><span class=line>140</span><br><span class=line>141</span><br><span class=line>142</span><br><span class=line>143</span><br><span class=line>144</span><br><span class=line>145</span><br><span class=line>146</span><br></pre><td class=code><pre><span class=line><span class=meta>@Override</span></span><br><span class=line> <span class=keyword>public</span> <span class=keyword>void</span> <span class="title function_">run</span><span class=params>()</span> {</span><br><span class=line> <span class=keyword>if</span> (<span class=built_in>this</span>.processQueue.isDropped()) {</span><br><span class=line> log.warn(<span class=string>"run, the message queue not be able to consume, because it's dropped. {}"</span>, <span class=built_in>this</span>.messageQueue);</span><br><span class=line> <span class=keyword>return</span>;</span><br><span class=line> }</span><br><span class=line> <span class=comment>// 获得 Consumer 消息队列锁,即单个线程独占</span></span><br><span class=line> <span class=keyword>final</span> <span class=type>Object</span> <span class=variable>objLock</span> <span class=operator>=</span> messageQueueLock.fetchLockObject(<span class=built_in>this</span>.messageQueue);</span><br><span class=line> <span class=keyword>synchronized</span> (objLock) {</span><br><span class=line> <span class=comment>// (广播模式) 或者 (集群模式 && Broker消息队列锁有效)</span></span><br><span class=line> <span class=keyword>if</span> (MessageModel.BROADCASTING.equals(ConsumeMessageOrderlyService.<span class=built_in>this</span>.defaultMQPushConsumerImpl.messageModel())</span><br><span class=line> || (<span class=built_in>this</span>.processQueue.isLocked() && !<span class=built_in>this</span>.processQueue.isLockExpired())) {</span><br><span class=line> <span class=keyword>final</span> <span class=type>long</span> <span class=variable>beginTime</span> <span class=operator>=</span> System.currentTimeMillis();</span><br><span class=line> <span class=comment>// 循环</span></span><br><span class=line> <span class=keyword>for</span> (<span class=type>boolean</span> <span class=variable>continueConsume</span> <span class=operator>=</span> <span class=literal>true</span>; continueConsume; ) {</span><br><span class=line> <span class=keyword>if</span> (<span class=built_in>this</span>.processQueue.isDropped()) {</span><br><span class=line> log.warn(<span class=string>"the message queue not be able to consume, because it's dropped. {}"</span>, <span class=built_in>this</span>.messageQueue);</span><br><span class=line> <span class=keyword>break</span>;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=comment>// 消息队列分布式锁未锁定,提交延迟获得锁并消费请求</span></span><br><span class=line> <span class=keyword>if</span> (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.<span class=built_in>this</span>.defaultMQPushConsumerImpl.messageModel())</span><br><span class=line> && !<span class=built_in>this</span>.processQueue.isLocked()) {</span><br><span class=line> log.warn(<span class=string>"the message queue not locked, so consume later, {}"</span>, <span class=built_in>this</span>.messageQueue);</span><br><span class=line> ConsumeMessageOrderlyService.<span class=built_in>this</span>.tryLockLaterAndReconsume(<span class=built_in>this</span>.messageQueue, <span class=built_in>this</span>.processQueue, <span class=number>10</span>);</span><br><span class=line> <span class=keyword>break</span>;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=comment>// 消息队列分布式锁已经过期,提交延迟获得锁并消费请求</span></span><br><span class=line> <span class=keyword>if</span> (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.<span class=built_in>this</span>.defaultMQPushConsumerImpl.messageModel())</span><br><span class=line> && <span class=built_in>this</span>.processQueue.isLockExpired()) {</span><br><span class=line> log.warn(<span class=string>"the message queue lock expired, so consume later, {}"</span>, <span class=built_in>this</span>.messageQueue);</span><br><span class=line> ConsumeMessageOrderlyService.<span class=built_in>this</span>.tryLockLaterAndReconsume(<span class=built_in>this</span>.messageQueue, <span class=built_in>this</span>.processQueue, <span class=number>10</span>);</span><br><span class=line> <span class=keyword>break</span>;</span><br><span class=line> }</span><br><span class=line> <span class=comment>// 当前周期消费时间超过连续时长,默认:60s,提交延迟消费请求。默认情况下,每消费1分钟休息10ms。</span></span><br><span class=line> <span class=type>long</span> <span class=variable>interval</span> <span class=operator>=</span> System.currentTimeMillis() - beginTime;</span><br><span class=line> <span class=keyword>if</span> (interval > MAX_TIME_CONSUME_CONTINUOUSLY) {</span><br><span class=line> ConsumeMessageOrderlyService.<span class=built_in>this</span>.submitConsumeRequestLater(processQueue, messageQueue, <span class=number>10</span>);</span><br><span class=line> <span class=keyword>break</span>;</span><br><span class=line> }</span><br><span class=line> <span class=comment>// 获取消费消息。此处和并发消息请求不同,并发消息请求已经带了消费哪些消息。</span></span><br><span class=line> <span class=keyword>final</span> <span class=type>int</span> <span class=variable>consumeBatchSize</span> <span class=operator>=</span></span><br><span class=line> ConsumeMessageOrderlyService.<span class=built_in>this</span>.defaultMQPushConsumer.getConsumeMessageBatchMaxSize();</span><br><span class=line></span><br><span class=line> List<MessageExt> msgs = <span class=built_in>this</span>.processQueue.takeMessags(consumeBatchSize);</span><br><span class=line> defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, defaultMQPushConsumer.getConsumerGroup());</span><br><span class=line> <span class=keyword>if</span> (!msgs.isEmpty()) {</span><br><span class=line> <span class=keyword>final</span> <span class=type>ConsumeOrderlyContext</span> <span class=variable>context</span> <span class=operator>=</span> <span class=keyword>new</span> <span class="title class_">ConsumeOrderlyContext</span>(<span class=built_in>this</span>.messageQueue);</span><br><span class=line></span><br><span class=line> <span class=type>ConsumeOrderlyStatus</span> <span class=variable>status</span> <span class=operator>=</span> <span class=literal>null</span>;</span><br><span class=line></span><br><span class=line> <span class=type>ConsumeMessageContext</span> <span class=variable>consumeMessageContext</span> <span class=operator>=</span> <span class=literal>null</span>;</span><br><span class=line> <span class=keyword>if</span> (ConsumeMessageOrderlyService.<span class=built_in>this</span>.defaultMQPushConsumerImpl.hasHook()) {</span><br><span class=line> consumeMessageContext = <span class=keyword>new</span> <span class="title class_">ConsumeMessageContext</span>();</span><br><span class=line> consumeMessageContext</span><br><span class=line> .setConsumerGroup(ConsumeMessageOrderlyService.<span class=built_in>this</span>.defaultMQPushConsumer.getConsumerGroup());</span><br><span class=line> consumeMessageContext.setNamespace(defaultMQPushConsumer.getNamespace());</span><br><span class=line> consumeMessageContext.setMq(messageQueue);</span><br><span class=line> consumeMessageContext.setMsgList(msgs);</span><br><span class=line> consumeMessageContext.setSuccess(<span class=literal>false</span>);</span><br><span class=line> <span class=comment>// init the consume context type</span></span><br><span class=line> consumeMessageContext.setProps(<span class=keyword>new</span> <span class="title class_">HashMap</span><String, String>());</span><br><span class=line> ConsumeMessageOrderlyService.<span class=built_in>this</span>.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext);</span><br><span class=line> }</span><br><span class=line> <span class=comment>// 执行消费</span></span><br><span class=line> <span class=type>long</span> <span class=variable>beginTimestamp</span> <span class=operator>=</span> System.currentTimeMillis();</span><br><span class=line> <span class=type>ConsumeReturnType</span> <span class=variable>returnType</span> <span class=operator>=</span> ConsumeReturnType.SUCCESS;</span><br><span class=line> <span class=type>boolean</span> <span class=variable>hasException</span> <span class=operator>=</span> <span class=literal>false</span>;</span><br><span class=line> <span class=keyword>try</span> {</span><br><span class=line> <span class=built_in>this</span>.processQueue.getLockConsume().lock(); <span class=comment>// 锁定处理队列</span></span><br><span class=line> <span class=keyword>if</span> (<span class=built_in>this</span>.processQueue.isDropped()) {</span><br><span class=line> log.warn(<span class=string>"consumeMessage, the message queue not be able to consume, because it's dropped. {}"</span>,</span><br><span class=line> <span class=built_in>this</span>.messageQueue);</span><br><span class=line> <span class=keyword>break</span>;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> status = messageListener.consumeMessage(Collections.unmodifiableList(msgs), context);</span><br><span class=line> } <span class=keyword>catch</span> (Throwable e) {</span><br><span class=line> log.warn(<span class=string>"consumeMessage exception: {} Group: {} Msgs: {} MQ: {}"</span>,</span><br><span class=line> RemotingHelper.exceptionSimpleDesc(e),</span><br><span class=line> ConsumeMessageOrderlyService.<span class=built_in>this</span>.consumerGroup,</span><br><span class=line> msgs,</span><br><span class=line> messageQueue);</span><br><span class=line> hasException = <span class=literal>true</span>;</span><br><span class=line> } <span class=keyword>finally</span> {</span><br><span class=line> <span class=built_in>this</span>.processQueue.getLockConsume().unlock(); <span class=comment>// 解锁</span></span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>if</span> (<span class=literal>null</span> == status</span><br><span class=line> || ConsumeOrderlyStatus.ROLLBACK == status</span><br><span class=line> || ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT == status) {</span><br><span class=line> log.warn(<span class=string>"consumeMessage Orderly return not OK, Group: {} Msgs: {} MQ: {}"</span>,</span><br><span class=line> ConsumeMessageOrderlyService.<span class=built_in>this</span>.consumerGroup,</span><br><span class=line> msgs,</span><br><span class=line> messageQueue);</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=type>long</span> <span class=variable>consumeRT</span> <span class=operator>=</span> System.currentTimeMillis() - beginTimestamp;</span><br><span class=line> <span class=keyword>if</span> (<span class=literal>null</span> == status) {</span><br><span class=line> <span class=keyword>if</span> (hasException) {</span><br><span class=line> returnType = ConsumeReturnType.EXCEPTION;</span><br><span class=line> } <span class=keyword>else</span> {</span><br><span class=line> returnType = ConsumeReturnType.RETURNNULL;</span><br><span class=line> }</span><br><span class=line> } <span class=keyword>else</span> <span class=keyword>if</span> (consumeRT >= defaultMQPushConsumer.getConsumeTimeout() * <span class=number>60</span> * <span class=number>1000</span>) {</span><br><span class=line> returnType = ConsumeReturnType.TIME_OUT;</span><br><span class=line> } <span class=keyword>else</span> <span class=keyword>if</span> (ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT == status) {</span><br><span class=line> returnType = ConsumeReturnType.FAILED;</span><br><span class=line> } <span class=keyword>else</span> <span class=keyword>if</span> (ConsumeOrderlyStatus.SUCCESS == status) {</span><br><span class=line> returnType = ConsumeReturnType.SUCCESS;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>if</span> (ConsumeMessageOrderlyService.<span class=built_in>this</span>.defaultMQPushConsumerImpl.hasHook()) {</span><br><span class=line> consumeMessageContext.getProps().put(MixAll.CONSUME_CONTEXT_TYPE, returnType.name());</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>if</span> (<span class=literal>null</span> == status) {</span><br><span class=line> status = ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> <span class=keyword>if</span> (ConsumeMessageOrderlyService.<span class=built_in>this</span>.defaultMQPushConsumerImpl.hasHook()) {</span><br><span class=line> consumeMessageContext.setStatus(status.toString());</span><br><span class=line> consumeMessageContext</span><br><span class=line> .setSuccess(ConsumeOrderlyStatus.SUCCESS == status || ConsumeOrderlyStatus.COMMIT == status);</span><br><span class=line> ConsumeMessageOrderlyService.<span class=built_in>this</span>.defaultMQPushConsumerImpl.executeHookAfter(consumeMessageContext);</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> ConsumeMessageOrderlyService.<span class=built_in>this</span>.getConsumerStatsManager()</span><br><span class=line> .incConsumeRT(ConsumeMessageOrderlyService.<span class=built_in>this</span>.consumerGroup, messageQueue.getTopic(), consumeRT);</span><br><span class=line></span><br><span class=line> continueConsume = ConsumeMessageOrderlyService.<span class=built_in>this</span>.processConsumeResult(msgs, status, context, <span class=built_in>this</span>);</span><br><span class=line> } <span class=keyword>else</span> {</span><br><span class=line> continueConsume = <span class=literal>false</span>;</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line> } <span class=keyword>else</span> {</span><br><span class=line> <span class=keyword>if</span> (<span class=built_in>this</span>.processQueue.isDropped()) {</span><br><span class=line> log.warn(<span class=string>"the message queue not be able to consume, because it's dropped. {}"</span>, <span class=built_in>this</span>.messageQueue);</span><br><span class=line> <span class=keyword>return</span>;</span><br><span class=line> }</span><br><span class=line></span><br><span class=line> ConsumeMessageOrderlyService.<span class=built_in>this</span>.tryLockLaterAndReconsume(<span class=built_in>this</span>.messageQueue, <span class=built_in>this</span>.processQueue, <span class=number>100</span>);</span><br><span class=line> }</span><br><span class=line> }</span><br><span class=line> }</span><br></pre></table></figure><p>获取到锁对象后,使用synchronized尝试申请线程级独占锁。<p><strong>如果加锁成功</strong>,同一时刻只有一个线程进行消息消费。<p><strong>如果加锁失败</strong>,会延迟100ms重新尝试向broker端申请锁定messageQueue,锁定成功后重新提交消费请求<p>创建消息拉取任务时,消息客户端向broker端申请锁定MessageQueue,使得一个MessageQueue同一个时刻只能被一个消费客户端消费。<p>消息消费时,多线程针对同一个消息队列的消费先尝试使用synchronized申请独占锁,加锁成功才能进行消费,使得一个MessageQueue同一个时刻只能被一个消费客户端中一个线程消费。<br>这里其实还有很重要的一点是对processQueue的加锁,这里其实是保证了在 rebalance的过程中如果 processQueue 被分配给了另一个 consumer,但是当前已经被我这个 consumer 再消费,但是没提交,就有可能出现被两个消费者消费,所以得进行加锁保证不受 rebalance 影响。</div><footer class=post-footer><div class=post-eof></div></footer></article></div><div class=post-block><article class=post-content itemscope itemtype=http://schema.org/Article><link href=https://nicksxs.me/2021/08/19/%E8%81%8A%E5%9C%A8%E4%B8%9C%E4%BA%AC%E5%A5%A5%E8%BF%90%E4%BC%9A%E9%97%AD%E5%B9%95%E5%BC%8F%E8%BF%99%E5%A4%A9-%E4%BA%8C/ itemprop=mainEntityOfPage><span hidden itemprop=author itemscope itemtype=http://schema.org/Person><meta content=/uploads/avatar.jpg itemprop=image><meta content=Nicksxs itemprop=name></span><span hidden itemprop=publisher itemscope itemtype=http://schema.org/Organization><meta content="Nicksxs's Blog" itemprop=name><meta content="learn from zero,技术博客,Nicksxs,史学森" itemprop=description></span><span hidden itemprop=post itemscope itemtype=http://schema.org/CreativeWork><meta content="undefined | Nicksxs's Blog" itemprop=name><meta itemprop=description></span><header class=post-header><h2 itemprop="name headline" class=post-title><a class=post-title-link href=/2021/08/19/%E8%81%8A%E5%9C%A8%E4%B8%9C%E4%BA%AC%E5%A5%A5%E8%BF%90%E4%BC%9A%E9%97%AD%E5%B9%95%E5%BC%8F%E8%BF%99%E5%A4%A9-%E4%BA%8C/ itemprop=url>聊在东京奥运会闭幕式这天-二</a></h2><div class=post-meta-container><div class=post-meta><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar"></i> </span><span class=post-meta-item-text>发表于</span> <time itemprop="dateCreated datePublished" title="创建时间:2021-08-19 14:56:58" datetime=2021-08-19T14:56:58+08:00>2021-08-19</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar-check"></i> </span><span class=post-meta-item-text>更新于</span> <time title="修改时间:2021-08-22 22:14:21" datetime=2021-08-22T22:14:21+08:00 itemprop=dateModified>2021-08-22</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-folder"></i> </span><span class=post-meta-item-text>分类于</span> <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/%E7%94%9F%E6%B4%BB/ itemprop=url rel=index><span itemprop=name>生活</span></a> </span>, <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/%E7%94%9F%E6%B4%BB/%E8%BF%90%E5%8A%A8/ itemprop=url rel=index><span itemprop=name>运动</span></a> </span></span><span class="post-meta-item leancloud_visitors" data-flag-title=聊在东京奥运会闭幕式这天-二 id=/2021/08/19/%E8%81%8A%E5%9C%A8%E4%B8%9C%E4%BA%AC%E5%A5%A5%E8%BF%90%E4%BC%9A%E9%97%AD%E5%B9%95%E5%BC%8F%E8%BF%99%E5%A4%A9-%E4%BA%8C/ title=阅读次数><span class=post-meta-item-icon><i class="far fa-eye"></i> </span><span class=post-meta-item-text>阅读次数:</span> <span class=leancloud-visitors-count></span> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-comment"></i> </span><span class=post-meta-item-text>Disqus:</span> <a href=/2021/08/19/%E8%81%8A%E5%9C%A8%E4%B8%9C%E4%BA%AC%E5%A5%A5%E8%BF%90%E4%BC%9A%E9%97%AD%E5%B9%95%E5%BC%8F%E8%BF%99%E5%A4%A9-%E4%BA%8C/#disqus_thread itemprop=discussionUrl title=disqus><span class="post-comments-count disqus-comment-count" data-disqus-identifier=2021/08/19/聊在东京奥运会闭幕式这天-二/ itemprop=commentCount></span></a></span></div></div></header><div class=post-body itemprop=articleBody><p>前面主要还是说了乒乓球的,因为整体还是乒乓球的比赛赛程比较长,比较激烈,扣人心弦,记得那会在公司没法看视频直播,就偶尔看看奥运会官网的比分,还几场马龙樊振东,陈梦被赢了一局就吓尿了,已经被混双那场留下了阴影,其实后面去看看16 年的比赛什么的,中国队虽然拿了这么多冠军,但是自改成 11 分制以来,其实都没办法那么完全彻底地碾压,而且像张继科,樊振东,陈梦都多少有些慢热,现在看来是马龙比较全面,不过看过了马龙,刘国梁,许昕等的一些过往经历,都是起起伏伏,即使是张怡宁这样的大魔王,也经历过逢王楠不赢的阶段,心态无法调整好。<p>其实最开始是举重项目,侯志慧是女子 49 公斤级的冠军,这场比赛是全场都看,其实看中国队的举重比赛跟跳水有点像,每一轮都需要到最后才能等到中国队,跳水其实每轮都有,举重会按照自己报的试举重量进行排名,重量大的会在后面举,抓举和挺举各三次试举机会,有时候会看着比较焦虑,一直等不来,怕一上来就没试举成功,而且中国队一般试举重量就是很大的,容易一次试举不成功就马上下一次,连着举其实压力会非常大,说实话真的是外行看热闹,每次都是多懂一点点,这次由于实在是比较无聊,所以看的会比较专心点,对于对应的规则知识点也会多了解一点,同时对于举重,没想到我们国家的这些运动员有这么强,最后八块金牌拿了七块,有一块拿到银牌也是有点因为教练的策略问题,这里其实也稍微知道一点,因为报上去的试举重量是谁小谁先举,并且我们国家都是实力非常强的,所以都会报大一些,并且如果这个项目有实力相近的选手,会比竞对多报一公斤,这样子如果前面竞争对手没举成功,我们把握就很大了,最坏的情况即使对手试举成功了,我们还有机会搏一把,比如谌利军这样的,只是说说感想,举重运动员真的是个比较单纯的群体,而且训练是非常痛苦枯燥的,非常容易受伤,像挺举就有点会压迫呼吸通道,看到好几个都是脸憋得通红,甚至直接因为压迫气道而没法完成后面的挺举,像之前 16 年的举重比赛,有个运动员没成功夺冠就非常愧疚地哭着说对不起祖国,没有获得冠军,这是怎么样的一种歉疚,怎么样的一种纯粹的感情呢,相对应地来说,我又要举男足,男篮的例子了,很多人在那嘲笑我这样对男足男篮愤愤不平的人,说可能我这样的人都没交个税(从缴纳个税的数量比例来算有可能),只是这里有两个打脸的事情,我足额缴纳个税,接近 20%的薪资都缴了个税,并且我买的所有东西都缴了增值税,如果让我这样缴纳了个税,缴纳了增值税的有个人的投票权,我一定会投票不让男足男篮使用我缴纳我的税金,用我们的缴纳的税,打出这么烂的表现,想乒乓球混双,拿个亚军都会被喷,那可是世界第二了,而且是就输了那么一场,足球篮球呢,我觉得是一方面成绩差,因为比赛真的有状态跟心态的影响,偶尔有一场失误非常正常,NBA 被黑八的有这么多强队,但是如果像男足男篮,成绩是越来越差,用范志毅的话来说就是脸都不要了,还有就是精气神,要在比赛中打出胜负欲,保持这种争胜心,才有机会再进步,前火箭队主教练鲁迪·汤姆贾诺维奇的话,“永远不要低估冠军的决心”,即使我现在打不过你,我会在下一次,下下次打败你,竞技体育永远要有这种精神,可以接受一时的失败,但是要保持永远争胜的心。<p>第一块金牌是杨倩拿下的,中国队拿奥运会首金也是有政治任务的,而恰恰杨倩这个金牌也有点碰巧是对手最后一枪失误了,当然竞技体育,特别是射击,真的是容不得一点点失误,像前面几届的美国神通埃蒙斯,失之毫厘差之千里,但是这个具体评价就比较少,唯一一点让我比较出戏的就是杨倩真的非常像王刚的徒弟漆二娃,哈哈,微博上也有挺多人觉得像,射击还是个比较可以接受年纪稍大的运动员,需要经验和稳定性,相对来说爆发力体力稍好一点,像庞伟这样的,混合团体10米气手枪金牌,36 岁可能其他项目已经是年龄很大了,不过前面说的举重的吕小军军神也是年纪蛮大了,但是非常强,而且在油管上简直就是个神,相对来说射击是关注比较少,杨倩的也只是看了后面拿到冠军这个结果,有些因为时间或者电视上没放,但是成绩还是不错的,没多少喷点。<p>第二篇先到这,纯主观,轻喷。</div><footer class=post-footer><div class=post-eof></div></footer></article></div><nav class=pagination><a class="extend prev" aria-label=上一页 href=/page/39/ rel=prev title=上一页><i class="fa fa-angle-left"></i></a><a class=page-number href=/>1</a><span class=space>…</span><a class=page-number href=/page/39/>39</a><span class="page-number current">40</span><a class=page-number href=/page/41/>41</a><span class=space>…</span><a class=page-number href=/page/64/>64</a><a class="extend next" aria-label=下一页 href=/page/41/ rel=next title=下一页><i class="fa fa-angle-right"></i></a></nav></div></main><footer class=footer><div class=footer-inner><div class=beian><a href=https://beian.miit.gov.cn/ rel=noopener target=_blank>浙ICP备16002580号-2 </a><img alt src=/uploads/beian.png><a href=https://beian.mps.gov.cn/#/query/webSearch?code=33010602011094 rel=noopener target=_blank>浙公网安备 33010602011094号</a></div><div class=copyright>© <span itemprop=copyrightYear>2025</span><span class=with-love><i class="fa fa-heart"></i> </span><span class=author itemprop=copyrightHolder>Nicksxs</span></div><div class=busuanzi-count><span class=post-meta-item id=busuanzi_container_site_uv><span class=post-meta-item-icon><i class="fa fa-user"></i> </span><span class=site-uv title=总访客量><span id=busuanzi_value_site_uv></span> </span></span><span class=post-meta-item id=busuanzi_container_site_pv><span class=post-meta-item-icon><i class="fa fa-eye"></i> </span><span class=site-pv title=总访问量><span id=busuanzi_value_site_pv></span></span></span></div></div></footer><div aria-label=返回顶部 class=back-to-top role=button><i class="fa fa-arrow-up fa-lg"></i><span>0%</span></div><a aria-label="在 GitHub 上关注我" title="在 GitHub 上关注我" class=github-corner href=https://github.com/nicksxs rel=noopener target=_blank><svg viewbox="0 0 250 250" aria-hidden=true height=80 width=80><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" style="transform-origin:130px 106px" class=octo-arm fill=currentColor></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" class=octo-body fill=currentColor></path></svg></a><noscript><div class=noscript-warning>Theme NexT works best with JavaScript enabled</div></noscript><script crossorigin=anonymous integrity=sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY= src=https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js></script><script crossorigin=anonymous integrity=sha256-ytMJGN3toR+a84u7g7NuHm91VIR06Q41kMWDr2pq7Zo= src=https://cdnjs.cloudflare.com/ajax/libs/fancyapps-ui/5.0.28/fancybox/fancybox.umd.js></script><script crossorigin=anonymous integrity=sha256-mOFREFhqmHeQbXpK2lp4nA3qooVgACfh88fpJftLBbc= src=https://cdnjs.cloudflare.com/ajax/libs/lozad.js/1.16.0/lozad.min.js></script><script src=/js/comments.js></script><script src=/js/utils.js></script><script src=/js/motion.js></script><script src=/js/next-boot.js></script><script crossorigin=anonymous integrity=sha256-DABVk+hYj0mdUzo+7ViJC6cwLahQIejFvC+my2M/wfM= src=https://cdnjs.cloudflare.com/ajax/libs/algoliasearch/4.20.0/algoliasearch-lite.umd.js></script><script crossorigin=anonymous integrity=sha256-9242vN47QUX50UG5Gf5XDO1YREWCEJRyXHofh5fsl24= src=https://cdnjs.cloudflare.com/ajax/libs/instantsearch.js/4.60.0/instantsearch.production.min.js></script><script src=/js/third-party/search/algolia-search.js></script><script src=/js/third-party/fancybox.js></script><script async src=https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js></script><script class=next-config data-name=leancloud_visitors type=application/json>{"enable":true,"app_id":"ysza182Vghlqjdt7QiwGLLJy-gzGzoHsz","app_key":"s9GDqbn7gnGGkusf66YRVccw","server_url":"https://leancloud.cn","security":true}</script><script src=/js/third-party/statistics/lean-analytics.js></script><script crossorigin=anonymous integrity=sha256-yvJQOINiH9fWemHn0vCA5lsHWJaHs6/ZmO+1Ft04SvM= src=https://cdnjs.cloudflare.com/ajax/libs/quicklink/2.3.0/quicklink.umd.js></script><script class=next-config data-name=quicklink type=application/json>{"enable":false,"home":false,"archive":false,"delay":true,"timeout":3000,"priority":true,"url":"https://nicksxs.me/page/40/"}</script><script src=/js/third-party/quicklink.js></script><script class=next-config data-name=disqus type=application/json>{"enable":true,"shortname":"nicksxs","count":true,"i18n":{"disqus":"disqus"}}</script><script src=/js/third-party/comments/disqus.js></script>
|