-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdb.json
1 lines (1 loc) · 866 KB
/
db.json
1
{"meta":{"version":1,"warehouse":"5.0.1"},"models":{"Asset":[{"_id":"themes/stellar/source/css/darkmode.css","path":"css/darkmode.css","modified":0,"renderable":1},{"_id":"themes/stellar/source/css/main.styl","path":"css/main.styl","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/darkmode.js","path":"js/darkmode.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/main.js","path":"js/main.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/plugins/copycode.js","path":"js/plugins/copycode.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/services/fcircle.js","path":"js/services/fcircle.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/services/friends.js","path":"js/services/friends.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/services/ghinfo.js","path":"js/services/ghinfo.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/services/mdrender.js","path":"js/services/mdrender.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/services/memos.js","path":"js/services/memos.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/services/siteinfo.js","path":"js/services/siteinfo.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/services/sites.js","path":"js/services/sites.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/services/timeline.js","path":"js/services/timeline.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/services/weibo.js","path":"js/services/weibo.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/search/algolia-search.js","path":"js/search/algolia-search.js","modified":0,"renderable":1},{"_id":"themes/stellar/source/js/search/local-search.js","path":"js/search/local-search.js","modified":0,"renderable":1},{"_id":"source/images/dark.jpeg","path":"images/dark.jpeg","modified":0,"renderable":0},{"_id":"source/images/gc.png","path":"images/gc.png","modified":0,"renderable":0},{"_id":"source/images/git.png","path":"images/git.png","modified":0,"renderable":0},{"_id":"source/images/io60c.png","path":"images/io60c.png","modified":0,"renderable":0},{"_id":"source/images/ioload30.png","path":"images/ioload30.png","modified":0,"renderable":0},{"_id":"source/images/ioload10.png","path":"images/ioload10.png","modified":0,"renderable":0},{"_id":"source/images/ioload60.png","path":"images/ioload60.png","modified":0,"renderable":0},{"_id":"source/images/java.jpg","path":"images/java.jpg","modified":0,"renderable":0},{"_id":"source/images/javafire.png","path":"images/javafire.png","modified":0,"renderable":0},{"_id":"source/images/javaperftop.png","path":"images/javaperftop.png","modified":0,"renderable":0},{"_id":"source/images/liuyanban.jpeg","path":"images/liuyanban.jpeg","modified":0,"renderable":0},{"_id":"source/images/load10.png","path":"images/load10.png","modified":0,"renderable":0},{"_id":"source/images/load30.png","path":"images/load30.png","modified":0,"renderable":0},{"_id":"source/images/load60.png","path":"images/load60.png","modified":0,"renderable":0},{"_id":"source/images/loadhigh.png","path":"images/loadhigh.png","modified":0,"renderable":0},{"_id":"source/images/logo.jpeg","path":"images/logo.jpeg","modified":0,"renderable":0},{"_id":"source/images/music.jpeg","path":"images/music.jpeg","modified":0,"renderable":0},{"_id":"source/images/perftop.png","path":"images/perftop.png","modified":0,"renderable":0},{"_id":"source/images/psauxf.png","path":"images/psauxf.png","modified":0,"renderable":0},{"_id":"source/images/server.jpg","path":"images/server.jpg","modified":0,"renderable":0},{"_id":"source/images/shouye.jpeg","path":"images/shouye.jpeg","modified":0,"renderable":0},{"_id":"source/images/stress-cpu.png","path":"images/stress-cpu.png","modified":0,"renderable":0},{"_id":"source/images/top-process.png","path":"images/top-process.png","modified":0,"renderable":0},{"_id":"source/images/top.png","path":"images/top.png","modified":0,"renderable":0},{"_id":"source/images/vmstat.png","path":"images/vmstat.png","modified":0,"renderable":0},{"_id":"source/images/ansible-logo.png","path":"images/ansible-logo.png","modified":0,"renderable":0},{"_id":"source/images/free.png","path":"images/free.png","modified":0,"renderable":0},{"_id":"source/images/mat.jpeg","path":"images/mat.jpeg","modified":0,"renderable":0},{"_id":"source/images/tmp-mem.png","path":"images/tmp-mem.png","modified":0,"renderable":0},{"_id":"source/images/iostat.png","path":"images/iostat.png","modified":0,"renderable":0},{"_id":"source/images/iotop.png","path":"images/iotop.png","modified":0,"renderable":0},{"_id":"source/images/suiji.jpeg","path":"images/suiji.jpeg","modified":0,"renderable":0},{"_id":"source/images/ansible-diagram.png","path":"images/ansible-diagram.png","modified":0,"renderable":0},{"_id":"source/images/ldirectord.png","path":"images/ldirectord.png","modified":0,"renderable":0},{"_id":"source/images/chrony.jpeg","path":"images/chrony.jpeg","modified":0,"renderable":0}],"Cache":[{"_id":"source/liuyanban.jpeg","hash":"0f8795cf75733493ffa43a6b3cded703ce4a2d48","modified":1723964137897},{"_id":"source/images/dark.jpeg","hash":"dc6c5cd179fb4795afbcb6e0fbc278e561a2bef1","modified":1723965458968},{"_id":"source/_posts/线上问题排查方法汇总.md","hash":"3480082b18bb79d9d97a65433f48c3623315cdb8","modified":1725525856056},{"_id":"source/_posts/JAVA问题定位.md","hash":"c6a5680ac7eb49c36aa4c0ef3d5ac567e31ce647","modified":1723907474404},{"_id":"source/images/git.png","hash":"468fcceae3d9260bd64fb531106d3cfd28d9d321","modified":1723963530079},{"_id":"source/images/io60c.png","hash":"6630c2853124fe1c89e30e8e96397124ab8ef1b9","modified":1724053201299},{"_id":"source/images/liuyanban.jpeg","hash":"0f8795cf75733493ffa43a6b3cded703ce4a2d48","modified":1723964157109},{"_id":"source/images/load10.png","hash":"4076ee1111fa2062139614ef45057ba6886d74e4","modified":1723805518721},{"_id":"source/images/load30.png","hash":"c689d35af16fe9f040e40598db2065b0cb0189e3","modified":1723803469458},{"_id":"source/images/load60.png","hash":"73051eb2c54f2bbbf54c6907c0d766829da12109","modified":1723803502068},{"_id":"source/images/music.jpeg","hash":"d2f291d9993f84032375bdce650d2ad5abb954e7","modified":1723963918742},{"_id":"source/images/logo.jpeg","hash":"1e0f89eb6c2df039baeb70230638cf584a1cb1d3","modified":1723968422143},{"_id":"source/images/server.jpg","hash":"02e933b77901f8df62d9576c5a0a0211790f8167","modified":1723884776667},{"_id":"source/images/perftop.png","hash":"1489c907cc0278a6803c204792803bff98fd66ce","modified":1723783369753},{"_id":"source/images/stress-cpu.png","hash":"91839b4f8c115981fd31ce2dc7a957143e5d6f08","modified":1723722218268},{"_id":"source/images/vmstat.png","hash":"c9cf2b97a3fee6dea79ac46252999b5bf28a7510","modified":1723541076248},{"_id":"source/images/top.png","hash":"a16eb91f4a11db4cefa61e4559588aac3626793b","modified":1723719871917},{"_id":"source/friends/index.md","hash":"93c6466b6dfb05b810f37f6370ea566b95223f19","modified":1723904822654},{"_id":"source/comments/index.md","hash":"c3320621b69c638c5b75e48fbb244afc0d4bdd84","modified":1723964749280},{"_id":"source/about/index.md","hash":"8cb3ae222812e44fa65f553f4992b239e6b6f717","modified":1725445593544},{"_id":"source/images/ioload30.png","hash":"9bc8e346914d1e23f889dd2583e4359859aa63d9","modified":1724055156306},{"_id":"source/images/gc.png","hash":"ba28184e39dac6e2f18da06e481337441226c8c6","modified":1723540745522},{"_id":"source/images/ioload10.png","hash":"d6e08bea439fcc0040db5a70a9614c943b9f892b","modified":1724055199926},{"_id":"source/_posts/image.png","hash":"2d94494a628427c738e8a09484740c8b4fce1bbb","modified":1724056788333},{"_id":"source/images/java.jpg","hash":"f8b81e75fd682ce8efdb21144be635ccd1ed0cdd","modified":1723884130363},{"_id":"source/images/ioload60.png","hash":"a1377e41182d0d8cbcad6083a24416a17ea23978","modified":1724055228042},{"_id":"source/images/psauxf.png","hash":"2d94494a628427c738e8a09484740c8b4fce1bbb","modified":1724056792844},{"_id":"source/images/javafire.png","hash":"118c0a92a404c68bb98c7da781617ac3632c2899","modified":1723798820453},{"_id":"source/images/top-process.png","hash":"582d184a7c7af782bd7040ead0d5d8c8abee4030","modified":1723721873257},{"_id":"source/images/loadhigh.png","hash":"b1331cf964a90c34de2517eef57a04da739ead6d","modified":1724058952995},{"_id":"source/images/javaperftop.png","hash":"cc8aed116972b9e62e9b6f961e17cd14dca24f5d","modified":1723788874806},{"_id":"themes/stellar/layout/_partial/widgets/search.ejs","hash":"da39a3ee5e6b4b0d3255bfef95601890afd80709","modified":1723860608693},{"_id":"themes/stellar/source/css/_components/widgets/tree.styl","hash":"da39a3ee5e6b4b0d3255bfef95601890afd80709","modified":1723860608713},{"_id":"themes/stellar/.gitignore","hash":"cf4e2dcaa760ff6f7520fd032ce44574e2970e54","modified":1723860608683},{"_id":"themes/stellar/package.json","hash":"5ad06200f2df1c9430d6feb7ca666b99dd16a5d6","modified":1723860608696},{"_id":"themes/stellar/.npmignore","hash":"9f66a3a5bea36f32c51cbfff88f1a45b74c80592","modified":1723860608683},{"_id":"themes/stellar/_config.yml","hash":"4bceb979dd01df9ec3456a45be7cfc6a82de5474","modified":1724066376266},{"_id":"themes/stellar/LICENSE","hash":"7fdfdb5dbc7d672fa28a2a3c9efa03ff8df5917d","modified":1723860608683},{"_id":"themes/stellar/README.md","hash":"d474d9b14cafda27ab291336cbdb4ac91bfec756","modified":1723860608683},{"_id":"themes/stellar/npm-publish.sh","hash":"b137d2f0d48fccd065ba1750b886f33ad7a7236a","modified":1723860608696},{"_id":"themes/stellar/giscus.json","hash":"af57a96a9dc188079bdcd0ad4273f1765778f5ea","modified":1723860608684},{"_id":"themes/stellar/languages/en.yml","hash":"5397b48828cceb50a071ee0f3b1432e304cad8ae","modified":1723860608684},{"_id":"themes/stellar/languages/zh-CN.yml","hash":"faaf9f9e0728f4fff885f9591cd4daef0b195f25","modified":1723860608684},{"_id":"themes/stellar/layout/archive.ejs","hash":"af072e9b75e8d5371771237e71a1f1381078526a","modified":1723860608695},{"_id":"themes/stellar/layout/404.ejs","hash":"d84f01256feb3c0bc0b280031fb78ae9f0d86f26","modified":1723860608685},{"_id":"themes/stellar/layout/index.ejs","hash":"ea5fa65b2049bc1c4c8c247972a79310468e3c68","modified":1723860608695},{"_id":"themes/stellar/languages/zh-TW.yml","hash":"fa911ea00c326a1b261cc3e5f1770b27e0238ccf","modified":1723860608685},{"_id":"themes/stellar/layout/index_topic.ejs","hash":"6bffb1488ce9c2c4b04386cb7fe61792ae5491f2","modified":1723860608695},{"_id":"themes/stellar/layout/categories.ejs","hash":"eaf71831abd6780690f26fa1a7cf83bc6f08d7e9","modified":1723860608695},{"_id":"themes/stellar/layout/index_wiki.ejs","hash":"9e7dc6941c5205ed60fa7abfb1f36ae19e2f3834","modified":1723860608695},{"_id":"themes/stellar/layout/notebooks.ejs","hash":"7519887f825bc9ae1ca842e02bc5b2ad909e77a9","modified":1723860608696},{"_id":"themes/stellar/layout/page.ejs","hash":"af600622274600941309fb7c06ebe860ceca24f2","modified":1723860608696},{"_id":"themes/stellar/layout/notes.ejs","hash":"242f663e983fc496c51aab9531ccfe46b0d1b05e","modified":1723860608696},{"_id":"themes/stellar/layout/tags.ejs","hash":"4ff8ccff9a9c8d373df788fbc0bebdc87302056e","modified":1723860608696},{"_id":"themes/stellar/_data/widgets.yml","hash":"52a5e36cee2010244a04add472dae2850cf9908d","modified":1724119396639},{"_id":"themes/stellar/layout/layout.ejs","hash":"1202be25819c066d85b2807a80a22e4020c0431e","modified":1723957201403},{"_id":"themes/stellar/.github/ISSUE_TEMPLATE/any-question.yaml","hash":"312caa37c7c3090b496167e6da5f82099ca4de4e","modified":1723860608682},{"_id":"themes/stellar/_data/icons.yml","hash":"1ba122dcac1ed8fa1443b0baa0c9b9f1d007b7fe","modified":1723860608684},{"_id":"themes/stellar/scripts/events/index.js","hash":"01a8d06a48af2f20aacfac76962bde5edd83dd3f","modified":1723860608696},{"_id":"themes/stellar/scripts/filters/index.js","hash":"5667f028990dd556133080090a5fcb00c64f05ac","modified":1723860608698},{"_id":"themes/stellar/scripts/generators/404.js","hash":"66b53d2b35b18d5f3835b47467c23f31eb322553","modified":1723860608698},{"_id":"themes/stellar/.github/ISSUE_TEMPLATE/article-share.md","hash":"f88a131062c94d8dadd0536e841966bf8547e1a7","modified":1723860608683},{"_id":"themes/stellar/scripts/generators/author.js","hash":"0de0824fdbc4f67c84910267901095504b253751","modified":1723860608698},{"_id":"themes/stellar/.github/configs/label-commenter-config.yml","hash":"acae6f9e872bd6a462e711771aeedcb7cdca0a86","modified":1723860608683},{"_id":"themes/stellar/scripts/generators/categories.js","hash":"936d4d406fd401359bab1e5f74c6e1e097e8f092","modified":1723860608698},{"_id":"themes/stellar/scripts/generators/search.js","hash":"404f28ea5d77cee462a551d8f74c9f7c41f73ba0","modified":1723860608698},{"_id":"themes/stellar/scripts/generators/wiki.js","hash":"61efe22787c3fea9d935df4b088a9dd2d1af0868","modified":1723860608699},{"_id":"themes/stellar/scripts/generators/topic.js","hash":"88c0f2990365fb0ce751682815e07f36cf333d3f","modified":1723860608699},{"_id":"themes/stellar/scripts/generators/notebooks.js","hash":"9ea0dc7408e11ae0e5abc89bd0c929d640a6e069","modified":1723860608698},{"_id":"themes/stellar/scripts/generators/tags.js","hash":"ed29755154d8e7a9346019ba4ac8782a8649b177","modified":1723860608699},{"_id":"themes/stellar/.github/workflows/label-commenter.yml","hash":"f89d116ba78e3fd4a0695c2ac4176e46e3c92028","modified":1723860608683},{"_id":"themes/stellar/scripts/helpers/icon.js","hash":"b7c84be7f08abc1725f351ed8718791a861466c3","modified":1723860608699},{"_id":"themes/stellar/scripts/helpers/category_color.js","hash":"20b19d6b6307cdeb0b0832bf4931366abe972490","modified":1723860608699},{"_id":"themes/stellar/scripts/helpers/scrollreveal.js","hash":"57e3da4a3dd751b3ebd384a674e26f08f69da018","modified":1723860608699},{"_id":"themes/stellar/.github/workflows/npm-publish.yml","hash":"9e95a1f04628056117df5ab8615acd36f3d63283","modified":1723860608683},{"_id":"themes/stellar/scripts/helpers/utils.js","hash":"05c127baa250b192c9c673355bf90d7134e1ea11","modified":1723860608699},{"_id":"themes/stellar/scripts/helpers/stellar_info.js","hash":"5b7a10c8b09237a467767f5467749c7d9378c2c1","modified":1723860608699},{"_id":"themes/stellar/scripts/tags/index.js","hash":"fca7eca8f4dde5876e7b4b9242b1b034ef540c5a","modified":1723860608700},{"_id":"themes/stellar/scripts/helpers/related_posts.js","hash":"6763a97fa25669fa1d1aa8e5291919deb5dc7f67","modified":1723860608699},{"_id":"themes/stellar/scripts/tags/inline-labels.js","hash":"a9cb7520af8a95f467c048128c036cbb3167fb8d","modified":1723860608700},{"_id":"themes/stellar/scripts/helpers/parse_config.js","hash":"1251d82318972f22283dc7a089b77a3f0135b1b6","modified":1723860608699},{"_id":"themes/stellar/_data/widgets.yml.bak","hash":"57a3d4932e555d43c3c168b0bbb30a3474637416","modified":1724062931429},{"_id":"themes/stellar/layout/_partial/head.ejs","hash":"3fac057477e1fbddbfba9189f598998a52d4fadb","modified":1723860608687},{"_id":"themes/stellar/layout/_plugins/copycode.ejs","hash":"7cedd8eced00f6813f7d772334ec92acbc8bd982","modified":1723860608694},{"_id":"themes/stellar/layout/_partial/menubtn.ejs","hash":"36775d16431301a6ac02858f32a8ee470ef8332f","modified":1723860608690},{"_id":"themes/stellar/layout/_plugins/fancybox.ejs","hash":"dcb638d9320eea6c9a6b7b0212e6167341bec05b","modified":1723860608694},{"_id":"themes/stellar/layout/_plugins/heti.ejs","hash":"c3956c8fd5fabd4ec8cea05dc076bf7a03eb012a","modified":1723860608694},{"_id":"themes/stellar/layout/_partial/scripts.ejs","hash":"25a6ec0b1fa8eda21f3d25539f59dc0d84491bec","modified":1723860608690},{"_id":"themes/stellar/layout/_plugins/index.ejs","hash":"29407e95f14d4d50c1f8a8b9a5af242927676432","modified":1723860608694},{"_id":"themes/stellar/layout/_plugins/lazyload.ejs","hash":"e2b07b0c97d531c82cc02a80610fe0c1a39bc837","modified":1723860608694},{"_id":"themes/stellar/layout/_plugins/mermaid.ejs","hash":"8ef22ac075890554521f464f6b92dc1fb7538b37","modified":1723860608694},{"_id":"themes/stellar/layout/_plugins/preload.ejs","hash":"f25523da5633b249da8cebe7c866265bf7825697","modified":1723860608694},{"_id":"themes/stellar/layout/_plugins/mathjax.ejs","hash":"c1621e718747a6eb34734e7e4a8364f5a78dc714","modified":1723860608694},{"_id":"themes/stellar/layout/_plugins/scrollreveal.ejs","hash":"1f3b98068a4db7ec709b47e134520af16f59fa71","modified":1723860608694},{"_id":"themes/stellar/layout/_plugins/tianli_gpt.ejs","hash":"c63f65df336bd10b2c5f068830c3086f3f13f3a3","modified":1723860608695},{"_id":"themes/stellar/scripts/events/lib/authors.js","hash":"eedefb5430fc4176bdc2814c8a88295f0e524d10","modified":1723860608697},{"_id":"themes/stellar/layout/_plugins/swiper.ejs","hash":"b80d6185c0263375df82498705a98ad32238b12f","modified":1723860608695},{"_id":"themes/stellar/source/css/_custom.styl","hash":"58e0e4d48aa890a48604873e76cb8a44a04b6d3d","modified":1723860608713},{"_id":"themes/stellar/source/css/main.styl","hash":"44b5008a50682b442f38f1cc2ffd117e4dfc9ec5","modified":1723860608715},{"_id":"themes/stellar/scripts/events/lib/doc_tree.js","hash":"e217f244af98b1151e7f57e9e77812f3a1e2ad78","modified":1723860608697},{"_id":"themes/stellar/scripts/events/lib/config.js","hash":"ab74c3df31ad21b842d859af40c1131dbd8fd2d7","modified":1723860608697},{"_id":"themes/stellar/scripts/events/lib/links.js","hash":"881cac75e4071d219a18156738e18eb397d83c00","modified":1723860608697},{"_id":"themes/stellar/scripts/events/lib/notebooks.js","hash":"ad5eae17a77a92100c748fd668c73e1b09f7a21f","modified":1723860608697},{"_id":"themes/stellar/scripts/events/lib/topic_tree.js","hash":"7f735f2573c87d202e2d4b845307d0725d619e8e","modified":1723860608697},{"_id":"themes/stellar/scripts/events/lib/merge_posts.js","hash":"4e45b8dc5317f7d66bc14879ae6b5f972123ce5d","modified":1723860608697},{"_id":"themes/stellar/scripts/events/lib/utils.js","hash":"271ba6c8cc997e4f55b2e146a99bde8301818400","modified":1723860608697},{"_id":"themes/stellar/scripts/filters/lib/img_onerror.js","hash":"d44a8e20d4d537c0cf85b980e1fc3bc84865a2d3","modified":1723860608698},{"_id":"themes/stellar/scripts/tags/lib/audio.js","hash":"9b094b16dce131a5c09373661c88a1de9b5326f7","modified":1723860608700},{"_id":"themes/stellar/scripts/tags/lib/banner.js","hash":"1fceb77a37ecaddf38c03aa655eec878701427c0","modified":1723860608700},{"_id":"themes/stellar/scripts/tags/lib/about.js","hash":"c9778c35c1ccd9f2018174bafbd37b23dd52cf62","modified":1723860608700},{"_id":"themes/stellar/scripts/tags/lib/albums.js","hash":"9fd16a8ceffd1806c25582062af7a72eca0e5c56","modified":1723860608700},{"_id":"themes/stellar/scripts/tags/lib/button.js","hash":"e0890a759335b1c5ad09db084bcf62073b2557cb","modified":1723860608700},{"_id":"themes/stellar/scripts/tags/lib/emoji.js","hash":"e10f68f69206fc6e9d0c478630be2b4514d328d7","modified":1723860608701},{"_id":"themes/stellar/scripts/tags/lib/box.js","hash":"753ae16a343ae82628f2057bd9cf09ea376fdbb5","modified":1723860608700},{"_id":"themes/stellar/scripts/tags/lib/checkbox.js","hash":"69caf7488b6c92cb2524df81ece6f33a46a89fe0","modified":1723860608700},{"_id":"themes/stellar/scripts/tags/lib/copy.js","hash":"d22a82b6fd9c96c7ac49677b427e6c629fa889d7","modified":1723860608701},{"_id":"themes/stellar/scripts/tags/lib/folding.js","hash":"b1efa6e86b1f8f25a58376349f21117902e5bb01","modified":1723860608701},{"_id":"themes/stellar/scripts/tags/lib/frame.js","hash":"1251a8622260af8efc55c2f0aac8ee5cf79d9043","modified":1723860608701},{"_id":"themes/stellar/scripts/tags/lib/folders.js","hash":"853a75c8b4f445f64a18420929c31865db30cd39","modified":1723860608701},{"_id":"themes/stellar/scripts/tags/lib/grid.js","hash":"dad59b638c2fd30538e3a69bf0c8d76d34cf3ece","modified":1723860608701},{"_id":"themes/stellar/scripts/tags/lib/ghcard.js","hash":"039ee39d8dba7c0aa0e267de38f9064b30855a55","modified":1723860608701},{"_id":"themes/stellar/scripts/tags/lib/icon.js","hash":"273898ae29a07c1baccb432efa0d33ccfabe7db9","modified":1723860608702},{"_id":"themes/stellar/scripts/tags/lib/friends.js","hash":"da08a75ad3579464debe8da2dd57314bd641dbb2","modified":1723860608701},{"_id":"themes/stellar/scripts/tags/lib/link.js","hash":"8a1297c324749f98e24036d3aa91ad374ad1d930","modified":1723860608702},{"_id":"themes/stellar/scripts/tags/lib/hashtag.js","hash":"f38ce98fff40ed35be9b1f1be3194bc4c5d44dc3","modified":1723860608701},{"_id":"themes/stellar/scripts/tags/lib/mark.js","hash":"cfd8198f349dba60fbdf53042b6ebd9a0ba521c7","modified":1723860608702},{"_id":"themes/stellar/scripts/tags/lib/md.js","hash":"6ed5db6d055dbfe01ecaeddbd0ead13a20ba7e74","modified":1723860608702},{"_id":"themes/stellar/scripts/tags/lib/navbar.js","hash":"e78ca3469c44362c7d8c3ad8899f49a119b326ff","modified":1723860608702},{"_id":"themes/stellar/scripts/tags/lib/note.js","hash":"a70d1fcb440ad029ddddb72b053a59b1ed3bfef8","modified":1723860608702},{"_id":"themes/stellar/scripts/tags/lib/poetry.js","hash":"c36321caeec845ed131bdd0922bff25eb59f086a","modified":1723860608702},{"_id":"themes/stellar/scripts/tags/lib/posters.js","hash":"713e1367f3a60e5903954a8fab15b0d9d9cfd89a","modified":1723860608703},{"_id":"themes/stellar/scripts/filters/lib/img_lazyload.js","hash":"b3dedcc1fc4189589e63d4fa6f169a70e9d63cd1","modified":1723860608698},{"_id":"themes/stellar/scripts/tags/lib/image.js","hash":"aa10441f0ec79b2f33829e5b1b8b4b4c21d865e9","modified":1723860608702},{"_id":"themes/stellar/scripts/tags/lib/quot.js","hash":"f15aa5c0fc87114a98e51baefb048b22302f7474","modified":1723860608703},{"_id":"themes/stellar/scripts/tags/lib/gallery.js","hash":"56a9309b096cbe1876a1e8c2b7c3f692325135e1","modified":1723860608701},{"_id":"themes/stellar/scripts/tags/lib/tabs.js","hash":"1b3d7b6b962293b44430e7b40560b8d1fa5c618d","modified":1723860608703},{"_id":"themes/stellar/scripts/tags/lib/okr.js","hash":"8e7a7d912e4f5e1fe1feb6125e4762c378b11ba3","modified":1723860608702},{"_id":"themes/stellar/scripts/tags/lib/swiper.js","hash":"dc025c79b190d233383fec001ddbe478f2675cc8","modified":1723860608703},{"_id":"themes/stellar/scripts/tags/lib/toc.js","hash":"d7b51b66d7b83a77e501930e735092a9967d5d18","modified":1723860608704},{"_id":"themes/stellar/scripts/tags/lib/timeline.js","hash":"f1defeed67e56c055f37ee7fab8a597a9d14f89e","modified":1723860608703},{"_id":"themes/stellar/scripts/tags/lib/video.js","hash":"181f1408fdfe355cafc5715618015ad10caca425","modified":1723860608704},{"_id":"themes/stellar/source/js/darkmode.js","hash":"f475db6eb867d4790843eed74b75dd76d932f42a","modified":1723965057879},{"_id":"themes/stellar/source/js/main.js","hash":"41ddd41a9f1896ad548a8eaa8610da2f823009ba","modified":1723860608715},{"_id":"themes/stellar/layout/_partial/comments/layout.ejs","hash":"3dbdb141d295d25b4f67fd0e299c40baa38241a8","modified":1723860608686},{"_id":"themes/stellar/layout/_partial/comments/script.ejs","hash":"62be59ef634ceee342c81ecc4e23cb664cdbf620","modified":1723860608686},{"_id":"themes/stellar/layout/_partial/sidebar/index_rightbar.ejs","hash":"a16ecd6d9421c30ffe2ca1a44cc9597309b5b70c","modified":1723860608691},{"_id":"themes/stellar/layout/_partial/sidebar/logo.ejs","hash":"cd08385b0026953cd9adf5c22af528b9c3e924e7","modified":1723860608692},{"_id":"themes/stellar/source/css/darkmode.css","hash":"acaad72e2c3eb183b54ae9ba23981141fc025140","modified":1723965100155},{"_id":"themes/stellar/layout/_partial/sidebar/menu.ejs","hash":"59d579a0eaec7572485d8d4d22341de79a890d00","modified":1723860608692},{"_id":"themes/stellar/layout/_partial/sidebar/search.ejs","hash":"770056e023a00b22d2853c4fa65bb48035ade3b2","modified":1723860608692},{"_id":"themes/stellar/layout/_partial/cover/post_cover.ejs","hash":"59e6ae6726ded33e3d84208fd4a4872a6431fcc7","modified":1723860608687},{"_id":"themes/stellar/layout/_partial/sidebar/index_leftbar.ejs","hash":"3ce47de8a4e832454da10e271e4855b15cb3d9e7","modified":1723860608691},{"_id":"themes/stellar/layout/_partial/cover/index.ejs","hash":"4b317700640749b1e04e6d51a542a2bcd28c8b72","modified":1723860608687},{"_id":"themes/stellar/layout/_partial/widgets/author.ejs","hash":"4c7eab461e45a7a5863333e9904dadbed6c8ca0c","modified":1723860608692},{"_id":"themes/stellar/layout/_partial/main/footer.ejs.bak","hash":"52e2f251a775109a9d612028383e103ea1a47806","modified":1723971570597},{"_id":"themes/stellar/layout/_partial/widgets/ghissues.ejs","hash":"410f56e6da87e7a1476d033c6939a0241658a598","modified":1723860608692},{"_id":"themes/stellar/scripts/tags/lib/sites.js","hash":"298f42f3097a19e5d229099964e15dac7d46e1b0","modified":1723860608703},{"_id":"themes/stellar/layout/_partial/widgets/ghrepo.ejs","hash":"e7d23ff8f8c96f77685e441c7f6040430d07a21a","modified":1723860608692},{"_id":"themes/stellar/layout/_partial/widgets/ghuser.ejs","hash":"5d8cf68b091f8bf4b88ac230495bc2dec561ad6a","modified":1723860608693},{"_id":"themes/stellar/layout/_partial/widgets/linklist.ejs","hash":"813336cbbe4505e9be42d5682fd7b720dba25194","modified":1723860608693},{"_id":"themes/stellar/layout/_partial/widgets/recent.ejs","hash":"3962a8b6f3c1adc80ecc846e6ad0888722ec5464","modified":1723860608693},{"_id":"themes/stellar/layout/_partial/widgets/markdown.ejs","hash":"7ce0ce941c55e42c545c1fffa77a3db07e1989b1","modified":1723860608693},{"_id":"themes/stellar/layout/_partial/cover/wiki_cover.ejs","hash":"8374cf58d5cc52799b8e1c8af95dd84c65372173","modified":1723860608687},{"_id":"themes/stellar/layout/_partial/widgets/related.ejs","hash":"ac6040f2067c799836d5dc4abacf0b879a156d88","modified":1723860608693},{"_id":"themes/stellar/layout/_partial/widgets/timeline.ejs","hash":"745b384871291e0cf2f6ceb4a0c12989b4e8cd62","modified":1723860608693},{"_id":"themes/stellar/layout/_partial/widgets/tagtree.ejs","hash":"9941285c0af5d5fbdf5e8e5b1c73f67304d9afed","modified":1723860608693},{"_id":"themes/stellar/layout/_partial/widgets/toc.ejs","hash":"fd0be7f0f60cea0e0e637d122af47a601f56010c","modified":1723860608693},{"_id":"themes/stellar/layout/_partial/scripts/services.ejs","hash":"ebe96e8b8edef9f48c80cff0e97b588dc8a1c859","modified":1723860608691},{"_id":"themes/stellar/layout/_partial/widgets/tagcloud.ejs","hash":"d95c26f84c7d9061ba3ef6188d58fa14f1c63bf3","modified":1723860608693},{"_id":"themes/stellar/layout/_partial/widgets/tree.ejs","hash":"2a7d37a843e32d073be4637bc17980e368a16003","modified":1723860608693},{"_id":"themes/stellar/layout/_partial/scripts/sidebar.ejs","hash":"403b6986bfc54177a7ee3ddba9e0a55e7b79e3a1","modified":1723860608691},{"_id":"themes/stellar/layout/_partial/scripts/defines.ejs","hash":"93393f0fbd25f09363ada3adefd97ccb9ebd5893","modified":1723860608691},{"_id":"themes/stellar/layout/_partial/scripts/tagtree.ejs","hash":"c16031694fee3e0c305139136126dee88e1e88cf","modified":1723860608691},{"_id":"themes/stellar/layout/_partial/main/footer.ejs","hash":"52e2f251a775109a9d612028383e103ea1a47806","modified":1723972007277},{"_id":"themes/stellar/layout/_partial/scripts/utils.ejs","hash":"a2a0d37c33b05c9020dbebe457e53283bff72a95","modified":1723860608691},{"_id":"themes/stellar/layout/_plugins/search/local_search.ejs","hash":"38a3641bb532583b699fda42175cb14242f07575","modified":1723860608695},{"_id":"themes/stellar/layout/_plugins/search/algolia_search.ejs","hash":"654fa8e2d695e5aa16988dfcd7dfab1b9b27d6f9","modified":1723860608695},{"_id":"themes/stellar/source/css/_common/base.styl","hash":"2986c5771652c353a320f4404ceeb1c6b257a6ed","modified":1723860608704},{"_id":"themes/stellar/source/css/_common/blur.styl","hash":"43441caaee7b7ab011a26016362b023263cd64a8","modified":1723860608704},{"_id":"themes/stellar/source/css/_common/button.styl","hash":"9afb3e566294f95f404a608b666367081a691e6b","modified":1723860608704},{"_id":"themes/stellar/source/css/_common/blockquote.styl","hash":"f107e6f399382a6663722bc2e823986c257da9a3","modified":1723860608704},{"_id":"themes/stellar/source/css/_common/cap.styl","hash":"c99286644d4a44dc76e1fe9a3af1815112f65acc","modified":1723860608704},{"_id":"themes/stellar/source/css/_common/control.styl","hash":"75081ca9d522a76ec4acffb8111c918b2297650a","modified":1723860608704},{"_id":"themes/stellar/source/css/_common/device.styl","hash":"fa135f5d93f8a333cc77f7dd4f6b8bf88e0f43bd","modified":1723860608705},{"_id":"themes/stellar/source/css/_common/html.styl","hash":"e7f596ff7294517096f5cb27f5af53b1a797e047","modified":1723860608705},{"_id":"themes/stellar/source/css/_common/image.styl","hash":"cca1103a9185202b13be49e16d77d259e9ffb482","modified":1723860608705},{"_id":"themes/stellar/source/css/_common/loading.styl","hash":"6962fd568ad9779146742b8b8928cec107972e8e","modified":1723860608705},{"_id":"themes/stellar/source/css/_common/input.styl","hash":"953fc87072cd760d92dfef55310addbb17bb2b51","modified":1723860608705},{"_id":"themes/stellar/source/css/_common/layout.styl","hash":"c7d0d3c70087e14bd1f6d8d50d56762d8c5ad44d","modified":1723860608705},{"_id":"themes/stellar/source/css/_common/pre.styl","hash":"be7ecc0a1cdd2e6f1594c4cc238734b2dc3a2c4e","modified":1723860608705},{"_id":"themes/stellar/source/css/_common/span.styl","hash":"86462ffaf1603b5d5ab6534c0f7fe0eb495aad2d","modified":1723860608706},{"_id":"themes/stellar/source/css/_common/title.styl","hash":"c9009b6c52a1ea4dfb6c2f56b1c3fc6de8a2c63a","modified":1723860608706},{"_id":"themes/stellar/source/css/_common/media.styl","hash":"fa7dbcaa31089fe547acb01a767af97fb019bba6","modified":1723860608705},{"_id":"themes/stellar/source/css/_common/highlight.styl","hash":"2e1191dbfa6e77a53ea9e95cecf66dc5b3d63b81","modified":1723860608705},{"_id":"themes/stellar/source/css/_common/svg.styl","hash":"aa2ae391db2ea028b9221740b24fca7f7e9ff16c","modified":1723860608706},{"_id":"themes/stellar/source/css/_defines/func.styl","hash":"058d01ec243db6c123503274ca691586c9859d09","modified":1723860608713},{"_id":"themes/stellar/source/css/_defines/const.styl","hash":"c62a434f1eef895d343cb8255a19c6f1a6c73195","modified":1723860608713},{"_id":"themes/stellar/source/css/_defines/theme.styl","hash":"467e23f3c13834ccfcc814c0efae3e13b9dfe8e0","modified":1723860608714},{"_id":"themes/stellar/source/css/_plugins/aplayer.styl","hash":"00b6e9209638eef9f70e75a24a5a90b05f7b5347","modified":1723860608714},{"_id":"themes/stellar/source/css/_plugins/katex.styl","hash":"64a0208a475811c8a022536441188a161d9dd05d","modified":1723860608715},{"_id":"themes/stellar/source/css/_plugins/copycode.styl","hash":"a9f27136143eb779d1aeb903e863c94a98b82aac","modified":1723860608714},{"_id":"themes/stellar/source/css/_plugins/index.styl","hash":"f96caf6f1d582973bc71199eee478cfc9fe88085","modified":1723860608714},{"_id":"themes/stellar/source/css/_plugins/lazyload.styl","hash":"6cd216067594d07c097a66e2403df36a1abbe6d6","modified":1723860608715},{"_id":"themes/stellar/source/css/_plugins/fancybox.styl","hash":"f4a709dcac4e21ca0fbeca552a8dde2ac2bf8d97","modified":1723860608714},{"_id":"themes/stellar/source/css/_common/toast.styl","hash":"f2930874174763da2338fa95087c345bf5c41800","modified":1723860608706},{"_id":"themes/stellar/source/css/_plugins/swiper.styl","hash":"46179e1e9ca273f4e095388d981acdc9fce5bdb8","modified":1723860608715},{"_id":"themes/stellar/source/css/_plugins/mermaid.styl","hash":"b01f298bb006abfe00bf68566bcc53c3cad4a6e7","modified":1723860608715},{"_id":"themes/stellar/source/css/_components/layout.styl","hash":"b7eb188db92bb9a32ae180969838f6eda2ec9640","modified":1723860608706},{"_id":"themes/stellar/source/css/_plugins/scrollreveal.styl","hash":"21b9125c1bae3e5348547e85500ceb992de21923","modified":1723860608715},{"_id":"themes/stellar/source/css/_components/main.styl","hash":"41040b445d23ff4e56656ce3ca4e0fd7fd67d67b","modified":1723860608706},{"_id":"themes/stellar/source/css/_components/index.styl","hash":"7a7ab293e065116646102546c6bab01166836059","modified":1723860608706},{"_id":"themes/stellar/layout/_partial/scripts/theme.ejs","hash":"87022a734dc2f4be8549463111696bbbf6f950b1","modified":1723860608691},{"_id":"themes/stellar/source/css/_components/md.styl","hash":"5cc0379fd0d4abf7a34944ccff1cf0b7dafdf091","modified":1723860608706},{"_id":"themes/stellar/source/css/_components/list.styl","hash":"326015062dd68c7ee8272416cf4f7732d20482cb","modified":1723860608706},{"_id":"themes/stellar/source/css/_plugins/tianli_gpt.styl","hash":"6cc2d27b8ec0296de09ce38d15abdd966702b79a","modified":1723860608715},{"_id":"themes/stellar/source/js/plugins/copycode.js","hash":"af017eeaaf12e78b1700d78b199b4368d001a193","modified":1723860608715},{"_id":"themes/stellar/source/js/services/ghinfo.js","hash":"94b86ce42dd765e9a4c1befa72ee9648d7ec6f88","modified":1723860608716},{"_id":"themes/stellar/source/js/services/fcircle.js","hash":"add550bfb204a2057661d4d6630d78ebf485754d","modified":1723860608716},{"_id":"themes/stellar/source/js/services/friends.js","hash":"581a2baa085df422b752cbec098ca56aac691790","modified":1723860608716},{"_id":"themes/stellar/source/js/services/mdrender.js","hash":"947f476e6dd8ca6a75f87f6ac6f2fbb2b0861e82","modified":1723860608716},{"_id":"themes/stellar/source/js/services/sites.js","hash":"5626b70d13360933c581983dcda422e4fccdc810","modified":1723860608717},{"_id":"themes/stellar/scripts/tags/lib/read/paper.js","hash":"e87bbcf10f743e1fcfadc03cf6281aee87c255f5","modified":1723860608703},{"_id":"themes/stellar/scripts/tags/lib/read/reel.js","hash":"3a833a0c7d29f98972d6141103d62d5abfd913e4","modified":1723860608703},{"_id":"themes/stellar/source/js/services/memos.js","hash":"2d430c7ae4d9f3a44653eda0214d18fb60bcfed1","modified":1723860608716},{"_id":"themes/stellar/source/js/search/algolia-search.js","hash":"2b4cc743d66fbdbe37f2311963d54d957637ca3e","modified":1723860608716},{"_id":"themes/stellar/source/js/search/local-search.js","hash":"cf710d711fcbc21122cdc01edd43d9b4087f1b9d","modified":1723860608716},{"_id":"themes/stellar/source/js/services/siteinfo.js","hash":"74da788f29862604cd53f6b82b036ff56c715d21","modified":1723860608716},{"_id":"themes/stellar/source/js/services/weibo.js","hash":"437e631539f3a50cf8b46ff17008404609c1c481","modified":1723860608717},{"_id":"themes/stellar/source/js/services/timeline.js","hash":"41cde82427726c3366b64b1f54f1aed9bc20fcf1","modified":1723860608717},{"_id":"themes/stellar/layout/_partial/comments/beaudar/layout.ejs","hash":"431848ce38c18589ddb54d5e705bd7792872c199","modified":1723860608685},{"_id":"themes/stellar/layout/_partial/comments/beaudar/script.ejs","hash":"69a2582d4e9e3203368196f4806adab2274e41fb","modified":1723860608685},{"_id":"themes/stellar/layout/_partial/comments/artalk/script.ejs","hash":"beee3759c0501f1ed5266fc6725332b1e1d54dce","modified":1723860608685},{"_id":"themes/stellar/layout/_partial/comments/artalk/layout.ejs","hash":"14b26d696ba6644ef9d5854e1b4a8fda028bddb9","modified":1723860608685},{"_id":"themes/stellar/layout/_partial/comments/giscus/layout.ejs","hash":"144e313ab45889c715ea1cfff3976b1f1322469e","modified":1723860608686},{"_id":"themes/stellar/layout/_partial/comments/giscus/script.ejs","hash":"49fd68b7752d0ca06d80a52bf800cf03845909e0","modified":1723860608686},{"_id":"themes/stellar/layout/_partial/comments/utterances/layout.ejs","hash":"431848ce38c18589ddb54d5e705bd7792872c199","modified":1723860608686},{"_id":"themes/stellar/layout/_partial/comments/waline/layout.ejs","hash":"2abe764ffeab5603645b0e5148a17e9373de5eb4","modified":1723860608687},{"_id":"themes/stellar/layout/_partial/comments/waline/script.ejs","hash":"ef4b59476f4edf03cb25bdffe449b5da161d1d8e","modified":1723860608687},{"_id":"themes/stellar/layout/_partial/comments/utterances/script.ejs","hash":"0c54bbd610c1b31d7f45b9b4ded65690b673482c","modified":1723860608687},{"_id":"themes/stellar/layout/_partial/comments/twikoo/script.ejs","hash":"3c93fe825769cd70027281c28980021b3ff8c4b0","modified":1723860608686},{"_id":"themes/stellar/layout/_partial/comments/twikoo/layout.ejs","hash":"a3d429f4eafc582f739dd48865b39afd7c22ed56","modified":1723860608686},{"_id":"themes/stellar/layout/_partial/main/article/read_next.ejs","hash":"30c9e20e6835fa92a528de61041742ff42cc2af0","modified":1723860608688},{"_id":"themes/stellar/layout/_partial/main/article/related_posts.ejs","hash":"ef485a4dc2db40bd68b35128f2140520db859b45","modified":1723860608688},{"_id":"themes/stellar/layout/_partial/main/navbar/article_banner.ejs","hash":"1651576216695b49980cf4531a0020ab82fb65c7","modified":1723860608688},{"_id":"themes/stellar/layout/_partial/main/navbar/dateinfo.ejs","hash":"f0ce9221931e9113de3df27eac39313eb0df217b","modified":1723860608689},{"_id":"themes/stellar/layout/_partial/main/navbar/ghinfo.ejs","hash":"f5177430efd994b7c2d9d3104a58b0f1f60f4a00","modified":1723860608689},{"_id":"themes/stellar/layout/_partial/main/navbar/nav_tabs_blog.ejs","hash":"9a37405585fdabb01aca7b367ebb3ca4ed20d1ff","modified":1723860608689},{"_id":"themes/stellar/layout/_partial/main/navbar/nav_tabs_wiki.ejs","hash":"bb8644df466868a879fc97bc364f1aba6b1137cf","modified":1723860608689},{"_id":"themes/stellar/layout/_partial/main/post_list/paginator.ejs","hash":"1a4b3bc64325ec7d5932ea78c36f2ec4978c09eb","modified":1723860608690},{"_id":"themes/stellar/layout/_partial/main/post_list/post_card.ejs","hash":"dab8decf3ebf7a2d4d829a534af76e8500fbffbb","modified":1723860608690},{"_id":"themes/stellar/layout/_partial/main/post_list/topic_card.ejs","hash":"9f100016e7cfa07c8728f9c3cf9543ed3ed43cfe","modified":1723860608690},{"_id":"themes/stellar/layout/_partial/main/post_list/wiki_card.ejs","hash":"d2676c1817f09be21008fbf1c2a25e3c9dc1793f","modified":1723860608690},{"_id":"themes/stellar/layout/_partial/widgets/components/link.ejs","hash":"eee3e54cca066643d973f7bc1e2af6230121e634","modified":1723860608692},{"_id":"themes/stellar/layout/_partial/widgets/components/edit.ejs","hash":"9d976ca956c73128f72f39ca7641ceb5240b15a1","modified":1723860608692},{"_id":"themes/stellar/layout/_partial/widgets/components/linklist.ejs","hash":"91f3a83ff54284200951637c0cfcd3e4e613942e","modified":1723860608692},{"_id":"themes/stellar/layout/_partial/main/notebook/note_card.ejs","hash":"f9b87418fbecf9e7e3a5d074ee9e9a26f5cc1788","modified":1723860608689},{"_id":"themes/stellar/layout/_partial/main/notebook/note_tags.ejs","hash":"6103372e1d17396550da909aed9de1f26e483962","modified":1723860608689},{"_id":"themes/stellar/layout/_partial/main/notebook/notebook_card.ejs","hash":"999b9c8895d84db707b1654c42326dfbb5fbe0fa","modified":1723860608690},{"_id":"themes/stellar/source/css/_plugins/comments/twikoo.styl","hash":"c30662f7635bbfd7b4ecde949fdec40aee4b6bce","modified":1723860608714},{"_id":"themes/stellar/source/css/_plugins/comments/artalk.styl","hash":"279d7185e0ea65a8f5e1f783eaa3f83bc7bf3555","modified":1723860608714},{"_id":"themes/stellar/source/css/_plugins/comments/beaudar.styl","hash":"e9800f67a650f1c022aee494768e05da76e6a6b7","modified":1723860608714},{"_id":"themes/stellar/source/css/_plugins/comments/utterances.styl","hash":"be43f728d9515acaf050fbb3eed83cfefa2fe702","modified":1723860608714},{"_id":"themes/stellar/source/css/_plugins/comments/waline.styl","hash":"9efd82d46da9bb4adb09f7ad1eea31a599608163","modified":1723860608714},{"_id":"themes/stellar/source/css/_components/pages/error.styl","hash":"91f9df285a87bc7b7e9da19d547ea4b1dc392828","modified":1723860608707},{"_id":"themes/stellar/layout/_partial/main/notebook/paginator.ejs","hash":"3c2f797fd3cf7e6e8c1b1dac168db26e92430b60","modified":1723860608690},{"_id":"themes/stellar/source/css/_components/pages/archives.styl","hash":"a99f09f4cc948588d071f8cd95362f2d70ec4c40","modified":1723860608707},{"_id":"themes/stellar/source/css/_components/partial/article-banner.styl","hash":"325a1398d84e50cfae13b70f01aeaaf037ef54c1","modified":1723860608707},{"_id":"themes/stellar/source/css/_components/pages/notebook.styl","hash":"2f9199575a7a58490d5faf023511781066370dae","modified":1723860608707},{"_id":"themes/stellar/source/css/_components/partial/bread-nav.styl","hash":"daf58b32af0b5dfea6cbfa1c3cab695976e57164","modified":1723860608707},{"_id":"themes/stellar/source/css/_components/partial/article-footer.styl","hash":"762c38e63aebbd028b5aed264349fa1d2a14e8af","modified":1723860608707},{"_id":"themes/stellar/source/css/_components/partial/cover.styl","hash":"ef44c47d0a70feb84d69cf8bb2bc6977f61b94f2","modified":1723860608707},{"_id":"themes/stellar/source/css/_components/partial/footer.styl","hash":"363fd4daf060fbf7de56fe5df787bc325565791b","modified":1723860608707},{"_id":"themes/stellar/source/css/_components/partial/paginator.styl","hash":"d5a64f3820ffc0913086c9fa35b26391eb023e61","modified":1723860608708},{"_id":"themes/stellar/source/css/_components/partial/navbar.styl","hash":"282291ee0f876ea14cff671555ab9defe8fc2318","modified":1723860608708},{"_id":"themes/stellar/source/css/_components/partial/related.styl","hash":"b413ab434cfb778384fbac64d43da41437435aa0","modified":1723860608708},{"_id":"themes/stellar/source/css/_components/sidebar/footer.styl","hash":"ac3423d488259ea467e95325e12f57476a9bdc9c","modified":1723860608708},{"_id":"themes/stellar/source/css/_components/sidebar/logo.styl","hash":"f81eb3d7a49c71f728d8bf42081bde30c6ff596e","modified":1723860608708},{"_id":"themes/stellar/source/css/_components/sidebar/menu.styl","hash":"57f772066edaa2cbe560622de28035780ad9000b","modified":1723860608708},{"_id":"themes/stellar/source/css/_components/sidebar/search.styl","hash":"ea880602a99ed8cb9b87f410e1f340b4d23d38e8","modified":1723860608708},{"_id":"themes/stellar/source/css/_components/sidebar/sidebar.styl","hash":"aaac0bc311badd146a11d3276ce89a4361c52ad4","modified":1723860608709},{"_id":"themes/stellar/source/css/_components/widgets/components.styl","hash":"34280561524a535342da3f246443b2a94fef074d","modified":1723860608712},{"_id":"themes/stellar/source/css/_components/sidebar/nav-area.styl","hash":"5302a72d15f7c35b391da723e84592f1129fca57","modified":1723860608708},{"_id":"themes/stellar/source/css/_components/widgets/ghrepo.styl","hash":"73d5baa3dfcc9e73fc7470e1ebe244857ffd75c1","modified":1723860608712},{"_id":"themes/stellar/source/css/_components/widgets/list.styl","hash":"176281f5ccc64f87da4bbd4e34316e32017bc3b4","modified":1723860608712},{"_id":"themes/stellar/source/css/_components/widgets/ghuser.styl","hash":"16c62c701f9cf6a253c6390d43eaa01cfd7600b1","modified":1723860608712},{"_id":"themes/stellar/source/css/_components/widgets/markdown.styl","hash":"f5a431cd88fca7f328634e13eff50a55b34b734b","modified":1723860608713},{"_id":"themes/stellar/source/css/_components/widgets/related.styl","hash":"8464b5adb8ffcd086d0e748849d907871d149223","modified":1723860608713},{"_id":"themes/stellar/source/css/_components/widgets/tagcloud.styl","hash":"c452b18f1242c634c0e124a46414cbc7c65da494","modified":1723860608713},{"_id":"themes/stellar/source/css/_components/widgets/timeline.styl","hash":"ab1901de4acfe89b642cd721d7d08b1b0009661b","modified":1723860608713},{"_id":"themes/stellar/source/css/_components/widgets/toc.styl","hash":"f290d98a4d721b523b0c972b7de61a86b05b1086","modified":1723860608713},{"_id":"themes/stellar/source/css/_components/widgets/widgets.styl","hash":"7d383e58d54bf40806027fa22ab0ba70b5ab4fde","modified":1723860608713},{"_id":"themes/stellar/source/css/_components/tag-plugins/about.styl","hash":"cdaf1ccd782db961cecda0802c94dabe27656731","modified":1723860608709},{"_id":"themes/stellar/source/css/_components/tag-plugins/banner.styl","hash":"f49a123a61cb02c65eebb79adefae9a6f8b8e2fc","modified":1723860608709},{"_id":"themes/stellar/source/css/_components/tag-plugins/button.styl","hash":"19469c881d8798916ea45ec11ecd5348146e7927","modified":1723860608709},{"_id":"themes/stellar/source/css/_components/tag-plugins/common.styl","hash":"a0a0e36d7672271147853bae34eb15b1ad2f0eef","modified":1723860608709},{"_id":"themes/stellar/source/css/_components/tag-plugins/checkbox.styl","hash":"1cd33e27fd539ed2a6cb41d4fc59294f1dd315b4","modified":1723860608709},{"_id":"themes/stellar/source/css/_components/tag-plugins/copy.styl","hash":"2e00b4923bf8a4cf1b742d19cf26165cc65fcffe","modified":1723860608709},{"_id":"themes/stellar/source/css/_components/tag-plugins/emoji.styl","hash":"100d2e0c43496464dd97fa83fa3e603e68bf30e9","modified":1723860608709},{"_id":"themes/stellar/source/css/_components/tag-plugins/folding.styl","hash":"84eb11a5a5808f3a4bd0985d97a7b108a36fd044","modified":1723860608710},{"_id":"themes/stellar/layout/_partial/main/article/article_footer.ejs","hash":"e93326cdd00f97d363c4830a81d7adb4dcae507c","modified":1723860608688},{"_id":"themes/stellar/source/css/_components/tag-plugins/folders.styl","hash":"2eae18b521a613a19dd6399dcb5504f843dcbb88","modified":1723860608709},{"_id":"themes/stellar/source/css/_components/tag-plugins/frame.styl","hash":"f8621b5a155ea78c5828e7ccbd1dfe6a72c9733c","modified":1723860608710},{"_id":"themes/stellar/source/css/_components/tag-plugins/friends.styl","hash":"4d412ca8388ff65979b9a619a7ae2fe212a90bac","modified":1723860608710},{"_id":"themes/stellar/source/css/_components/tag-plugins/grid.styl","hash":"606311fa8326152f99fc721284a09e06490d0cca","modified":1723860608710},{"_id":"themes/stellar/source/css/_components/tag-plugins/image.styl","hash":"beb0eb953d11489d4eaf16282784625062c81d9f","modified":1723860608710},{"_id":"themes/stellar/source/css/_components/tag-plugins/gallery.styl","hash":"52be8d049ce7533084aa3a6f361b4f365086af02","modified":1723860608710},{"_id":"themes/stellar/source/css/_components/tag-plugins/icon.styl","hash":"ea5372d1042db221f6c3ce5a31e18153c5e00d39","modified":1723860608710},{"_id":"themes/stellar/source/css/_components/tag-plugins/inline-labels.styl","hash":"96aad4c7710ed7097842fab77473b66a298b6e3f","modified":1723860608711},{"_id":"themes/stellar/source/css/_components/tag-plugins/index.styl","hash":"d76245514a5a0aecb65120b7117cd125a70617fb","modified":1723860608710},{"_id":"themes/stellar/source/css/_components/tag-plugins/ghcard.styl","hash":"98a50d3fab79ce03dd7f161fe3442d803712c284","modified":1723860608710},{"_id":"themes/stellar/source/css/_components/tag-plugins/link.styl","hash":"b91bb69a2f84567f87c2665c3dbaeb8831487185","modified":1723860608711},{"_id":"themes/stellar/source/css/_components/tag-plugins/mark.styl","hash":"1227da0705c294abab7030439d1e505c6b304ccf","modified":1723860608711},{"_id":"themes/stellar/source/css/_components/tag-plugins/media.styl","hash":"61541156e59cd946b86626b4b2af0a2f24546f25","modified":1723860608711},{"_id":"themes/stellar/source/css/_components/tag-plugins/navbar.styl","hash":"c2c3da16355f2dfd316edd11d9f2f2c50bc5299c","modified":1723860608711},{"_id":"themes/stellar/source/css/_components/tag-plugins/note.styl","hash":"5e59f5ab728c2ad5b5e4e06b0dd694407bb14096","modified":1723860608711},{"_id":"themes/stellar/source/css/_components/tag-plugins/okr.styl","hash":"0c2d281e90c8cb1a0de5ff516dca0e08ae7db141","modified":1723860608711},{"_id":"themes/stellar/source/css/_components/tag-plugins/override.styl","hash":"6a31ef8c4dc7caa655ed7d852100abb2711ab5ac","modified":1723860608711},{"_id":"themes/stellar/source/css/_components/tag-plugins/poetry.styl","hash":"6d15463121a741b69249318cadabf4bd06b4b3c6","modified":1723860608711},{"_id":"themes/stellar/source/css/_components/tag-plugins/quot.styl","hash":"15c6726b2ea1d5ff7a12a310766145dffde4e778","modified":1723860608711},{"_id":"themes/stellar/source/css/_components/tag-plugins/sites.styl","hash":"0b765b154695d544fa5dfab562e60a48c49f8547","modified":1723860608712},{"_id":"themes/stellar/source/css/_components/tag-plugins/tabs.styl","hash":"b9bb4cf9442c0d4c09951c2fb886dbf8d0fe8de0","modified":1723860608712},{"_id":"themes/stellar/source/css/_components/tag-plugins/toc.styl","hash":"18577b1c775f14da4b70cf3a8db56aa229f733c8","modified":1723860608712},{"_id":"themes/stellar/layout/_partial/main/navbar/breadcrumb/blog.ejs","hash":"0e3ff0a54e75acf562fd06d3d2c3ea03e0df7e81","modified":1723860608688},{"_id":"themes/stellar/layout/_partial/main/navbar/breadcrumb/note.ejs","hash":"815962a80cbcfe242339e1461707455a776b11dd","modified":1723860608689},{"_id":"themes/stellar/layout/_partial/main/navbar/breadcrumb/page.ejs","hash":"66fca0ad5ae3ceaa8fa1f698713c4882fc0341c3","modified":1723860608689},{"_id":"themes/stellar/layout/_partial/main/navbar/breadcrumb/wiki.ejs","hash":"c878619b0e8835e9a53a3b460ab5c707e9a3fb61","modified":1723860608689},{"_id":"themes/stellar/source/css/_components/tag-plugins/read/paper.styl","hash":"e35b61ab74124a019f8a4c06f8611ddd671df35c","modified":1723860608712},{"_id":"themes/stellar/source/css/_components/tag-plugins/read/reel.styl","hash":"2f129dd75e98406bbcc0b94d5bca448f6531aaf3","modified":1723860608712},{"_id":"themes/stellar/source/css/_components/tag-plugins/hashtag.styl","hash":"0e2e74fcff5c30be9533e451a1c06ed1477dc9aa","modified":1723860608710},{"_id":"themes/stellar/source/css/_components/tag-plugins/timeline.styl","hash":"a5ef35db9c54905770c0ab68471ec054b0a83d9e","modified":1723860608712},{"_id":"source/images/shouye.jpeg","hash":"d7476674b556372df7ddfb8ad6cb638797033dc9","modified":1723905077327},{"_id":"public/atom.xml","hash":"8f828edfd5af794753d0fdae7328d24607d7ffcd","modified":1725870221388},{"_id":"public/search.json","hash":"57a18bb9f98167fec19bcd895e97c90d67dfa6af","modified":1725870066755},{"_id":"public/404.html","hash":"e3e0f684c15eac4a3c5c75ed9ec6915d63906186","modified":1725869793839},{"_id":"public/about/index.html","hash":"d9e6c048cd7176eff00f1f3a2d0497d11145b933","modified":1725869793839},{"_id":"public/comments/index.html","hash":"b1688090a7bdfe21a38ed3c2d9e81235ff889ff4","modified":1725869793839},{"_id":"public/p/线上问题排查方法汇总/index.html","hash":"b8bb1bd3cfae2cc80a1f544000f97f3ab2dbcf76","modified":1725869793839},{"_id":"public/friends/index.html","hash":"ce3692988e76ec2d8bb00eafff20dfae4fc109d5","modified":1725869793839},{"_id":"public/archives/2024/index.html","hash":"79b5f4e5ed30d675fe95ce3e1fabe8e36c04a5a3","modified":1725869793839},{"_id":"public/archives/2024/07/index.html","hash":"573260e08158fa24d1410b7cae2fb640040f1c34","modified":1725869793839},{"_id":"public/archives/2024/08/index.html","hash":"cdac585ec94e96d845222c15d77a01003d86a434","modified":1725869793839},{"_id":"public/p/JAVA问题定位/index.html","hash":"66d88026918d0417603e7b28c5671a885eb63e42","modified":1725869793839},{"_id":"public/categories/问题排查/index.html","hash":"fedce4a9d4ebdcfd2b48d0a136cc756edf319bbf","modified":1725869793839},{"_id":"public/tags/JAVA/index.html","hash":"3baf2887f86367ededcc858a90b2ed2e38919cf3","modified":1725869793839},{"_id":"public/tags/Linux/index.html","hash":"28642e1b094f9b285a566c35b3ebeb1a272d0e53","modified":1725870221388},{"_id":"public/tags/Java/index.html","hash":"d5b00f82a7f796faf5171e43a75b1fe475eeac59","modified":1725869793839},{"_id":"public/archives/index.html","hash":"c04ecbe96e858f6db7c1980e7b0d6c3dc64c1962","modified":1725869793839},{"_id":"public/tags/Golang/index.html","hash":"65d0f58112928326f3ff4bdb4f056888ce506a7e","modified":1725869793839},{"_id":"public/tags/commands/index.html","hash":"bba246667c82343344d5d37f0d3350149139a71c","modified":1725869793839},{"_id":"public/index.html","hash":"eae31a6fd45f21e7f61567600f35f450b04d6f73","modified":1725870221388},{"_id":"public/notebooks/index.html","hash":"5df54e7bc66a4e895ba157a9b6a9ab26deb47337","modified":1725439756182},{"_id":"public/tags/index.html","hash":"7597b2963e84774f12d8d1cdc61abdc03e405319","modified":1725869793839},{"_id":"public/categories/index.html","hash":"db92484ccc8de7862a1662fd9df16cbe81ecef6c","modified":1725870066755},{"_id":"public/liuyanban.jpeg","hash":"0f8795cf75733493ffa43a6b3cded703ce4a2d48","modified":1724065434193},{"_id":"public/images/dark.jpeg","hash":"dc6c5cd179fb4795afbcb6e0fbc278e561a2bef1","modified":1725439756182},{"_id":"public/images/io60c.png","hash":"6630c2853124fe1c89e30e8e96397124ab8ef1b9","modified":1725439756182},{"_id":"public/images/liuyanban.jpeg","hash":"0f8795cf75733493ffa43a6b3cded703ce4a2d48","modified":1725439756182},{"_id":"public/images/load10.png","hash":"4076ee1111fa2062139614ef45057ba6886d74e4","modified":1725439756182},{"_id":"public/images/load30.png","hash":"c689d35af16fe9f040e40598db2065b0cb0189e3","modified":1725439756182},{"_id":"public/images/load60.png","hash":"73051eb2c54f2bbbf54c6907c0d766829da12109","modified":1725439756182},{"_id":"public/images/logo.jpeg","hash":"1e0f89eb6c2df039baeb70230638cf584a1cb1d3","modified":1725439756182},{"_id":"public/images/music.jpeg","hash":"d2f291d9993f84032375bdce650d2ad5abb954e7","modified":1725439756182},{"_id":"public/images/perftop.png","hash":"1489c907cc0278a6803c204792803bff98fd66ce","modified":1725439756182},{"_id":"public/images/server.jpg","hash":"02e933b77901f8df62d9576c5a0a0211790f8167","modified":1725439756182},{"_id":"public/images/vmstat.png","hash":"c9cf2b97a3fee6dea79ac46252999b5bf28a7510","modified":1725439756182},{"_id":"public/images/stress-cpu.png","hash":"91839b4f8c115981fd31ce2dc7a957143e5d6f08","modified":1725439756182},{"_id":"public/images/git.png","hash":"468fcceae3d9260bd64fb531106d3cfd28d9d321","modified":1725439756182},{"_id":"public/images/top.png","hash":"a16eb91f4a11db4cefa61e4559588aac3626793b","modified":1725439756182},{"_id":"public/images/gc.png","hash":"ba28184e39dac6e2f18da06e481337441226c8c6","modified":1725439756182},{"_id":"public/images/ioload30.png","hash":"9bc8e346914d1e23f889dd2583e4359859aa63d9","modified":1725439756182},{"_id":"public/images/ioload10.png","hash":"d6e08bea439fcc0040db5a70a9614c943b9f892b","modified":1725439756182},{"_id":"public/images/java.jpg","hash":"f8b81e75fd682ce8efdb21144be635ccd1ed0cdd","modified":1725439756182},{"_id":"public/images/javafire.png","hash":"118c0a92a404c68bb98c7da781617ac3632c2899","modified":1725439756182},{"_id":"public/images/ioload60.png","hash":"a1377e41182d0d8cbcad6083a24416a17ea23978","modified":1725439756182},{"_id":"public/images/psauxf.png","hash":"2d94494a628427c738e8a09484740c8b4fce1bbb","modified":1725439756182},{"_id":"public/images/top-process.png","hash":"582d184a7c7af782bd7040ead0d5d8c8abee4030","modified":1725439756182},{"_id":"public/css/darkmode.css","hash":"acaad72e2c3eb183b54ae9ba23981141fc025140","modified":1725439756182},{"_id":"public/js/darkmode.js","hash":"f475db6eb867d4790843eed74b75dd76d932f42a","modified":1725439756182},{"_id":"public/js/main.js","hash":"41ddd41a9f1896ad548a8eaa8610da2f823009ba","modified":1725439756182},{"_id":"public/js/plugins/copycode.js","hash":"af017eeaaf12e78b1700d78b199b4368d001a193","modified":1725439756182},{"_id":"public/js/services/fcircle.js","hash":"add550bfb204a2057661d4d6630d78ebf485754d","modified":1725439756182},{"_id":"public/js/services/friends.js","hash":"581a2baa085df422b752cbec098ca56aac691790","modified":1725439756182},{"_id":"public/js/services/mdrender.js","hash":"947f476e6dd8ca6a75f87f6ac6f2fbb2b0861e82","modified":1725439756182},{"_id":"public/js/services/memos.js","hash":"2d430c7ae4d9f3a44653eda0214d18fb60bcfed1","modified":1725439756182},{"_id":"public/js/services/timeline.js","hash":"41cde82427726c3366b64b1f54f1aed9bc20fcf1","modified":1725439756182},{"_id":"public/js/services/sites.js","hash":"5626b70d13360933c581983dcda422e4fccdc810","modified":1725439756182},{"_id":"public/js/services/siteinfo.js","hash":"74da788f29862604cd53f6b82b036ff56c715d21","modified":1725439756182},{"_id":"public/js/services/weibo.js","hash":"437e631539f3a50cf8b46ff17008404609c1c481","modified":1725439756182},{"_id":"public/js/services/ghinfo.js","hash":"94b86ce42dd765e9a4c1befa72ee9648d7ec6f88","modified":1725439756182},{"_id":"public/js/search/local-search.js","hash":"cf710d711fcbc21122cdc01edd43d9b4087f1b9d","modified":1725439756182},{"_id":"public/js/search/algolia-search.js","hash":"2b4cc743d66fbdbe37f2311963d54d957637ca3e","modified":1725439756182},{"_id":"public/css/main.css","hash":"13281d2f679605225bb52299b7b412b1a9ed1d7f","modified":1725439756182},{"_id":"public/images/loadhigh.png","hash":"b1331cf964a90c34de2517eef57a04da739ead6d","modified":1725439756182},{"_id":"public/images/javaperftop.png","hash":"cc8aed116972b9e62e9b6f961e17cd14dca24f5d","modified":1725439756182},{"_id":"public/images/shouye.jpeg","hash":"d7476674b556372df7ddfb8ad6cb638797033dc9","modified":1725439756182},{"_id":"source/_posts/ansible工具使用.md","hash":"9ecb6ef6b1a46cec55384229f8b08bed8f8865b4","modified":1724315294361},{"_id":"public/tags/Programming/index.html","hash":"24d2a5d606a0c6963f774f6b6b06c452028fda85","modified":1724119416880},{"_id":"public/p/ansible工具使用/index.html","hash":"445221565863bfc0bb17d795fb0e573291b1702b","modified":1725869793839},{"_id":"public/tags/Hexo/index.html","hash":"0eb5892cf8ab1bc78277e9fac4d99c381cfab525","modified":1724119416880},{"_id":"public/categories/Technology/index.html","hash":"8f85e2d6c228757c4c4b45dfd791f6f0d510652c","modified":1725870011868},{"_id":"public/tags/ansible/index.html","hash":"76c80f5fbe72871a796437d498cd0f8fc51def60","modified":1725869793839},{"_id":"public/tags/自动化运维工具/index.html","hash":"5b5169179f439c653fde7bb2d979ae85eeddd67e","modified":1724120500082},{"_id":"public/categories/运维工具/index.html","hash":"b114966ef23ca476f489ef4b40e765d2882cbb8d","modified":1725870221388},{"_id":"public/tags/运维工具/index.html","hash":"6f6fa8f1862291e62299590dd9c329961958b44b","modified":1725869793839},{"_id":"source/images/ansible-logo.png","hash":"dabd83dfc337591acb432845717c1c6463690928","modified":1724120900686},{"_id":"public/images/ansible-logo.png","hash":"dabd83dfc337591acb432845717c1c6463690928","modified":1725439756182},{"_id":"source/images/free.png","hash":"6eced5aaf12858e10341356679e30452538b91dd","modified":1724137609354},{"_id":"source/images/mat.jpeg","hash":"a92be2c7e1bc3a01abc3a91dfcf2e64d3e2fa39c","modified":1724140093260},{"_id":"public/images/free.png","hash":"6eced5aaf12858e10341356679e30452538b91dd","modified":1725439756182},{"_id":"public/images/mat.jpeg","hash":"a92be2c7e1bc3a01abc3a91dfcf2e64d3e2fa39c","modified":1725439756182},{"_id":"source/images/tmp-mem.png","hash":"45af3e52dff1e2dadfa061232450a021a9ddeaad","modified":1724143847713},{"_id":"public/images/tmp-mem.png","hash":"45af3e52dff1e2dadfa061232450a021a9ddeaad","modified":1725439756182},{"_id":"source/images/iotop.png","hash":"8479e5f86a7ef1d443f59a3c032b5a09a8987042","modified":1724210046704},{"_id":"source/images/iostat.png","hash":"788ace19d189ea03f5b764ef7ad21ad56a69c416","modified":1724209766364},{"_id":"public/images/iostat.png","hash":"788ace19d189ea03f5b764ef7ad21ad56a69c416","modified":1725439756182},{"_id":"public/images/iotop.png","hash":"8479e5f86a7ef1d443f59a3c032b5a09a8987042","modified":1725439756182},{"_id":"source/_posts/工作随记.md","hash":"c13992a7a30ad2b0c64a99e30f69f217a57ba038","modified":1725863446094},{"_id":"source/images/suiji.jpeg","hash":"699fd4c80b19ed0d9ad5adab1767bf65b9b7fbf9","modified":1724238835200},{"_id":"public/images/suiji.jpeg","hash":"699fd4c80b19ed0d9ad5adab1767bf65b9b7fbf9","modified":1725439756182},{"_id":"public/p/工作随记/index.html","hash":"2267010c396fe6c58e52bdfdd51b131daec2a748","modified":1725869793839},{"_id":"public/categories/工作随记/index.html","hash":"947d7fa74f1577c525f88612ac3f52ad22565d2b","modified":1725869793839},{"_id":"public/tags/ebpf/index.html","hash":"bdeac3031d3b38bdef4cc3fb8a9e76e6bf8c237c","modified":1725869793839},{"_id":"source/images/ansible-diagram.png","hash":"6a12b498de62c263f6f9c2175af7704d3710c55c","modified":1724293963009},{"_id":"public/images/ansible-diagram.png","hash":"6a12b498de62c263f6f9c2175af7704d3710c55c","modified":1725439756182},{"_id":"source/_posts/Pacemaker-Corosync使用简介.md","hash":"ab80d8f53e437e4066e33ab1f0be6bf82335d2c0","modified":1725525214724},{"_id":"public/p/Pacemaker-Corosync使用简介/index.html","hash":"ae5dbc0f3adba32d80efa1905079b609040d76a7","modified":1725869793839},{"_id":"public/categories/高可用/index.html","hash":"56019cf94d79628add60c0698c003f50dce5ec8d","modified":1725869793839},{"_id":"public/tags/Pacemaker/index.html","hash":"ca79fd9723c944cf0875c90fd9c8fd89fe29dcd0","modified":1725869793839},{"_id":"public/tags/Corosync/index.html","hash":"a5b8153047d532b80fa771d524d4b9f8c28f84ea","modified":1725869793839},{"_id":"public/tags/高可用方案/index.html","hash":"4eda579b4173f3b7bb31fa265696a930728bad0d","modified":1725869793839},{"_id":"source/images/ldirectord.png","hash":"10b8c957b8ac8f5d3acabe94a8f634b44982bacd","modified":1725438469054},{"_id":"public/images/ldirectord.png","hash":"10b8c957b8ac8f5d3acabe94a8f634b44982bacd","modified":1725439756182},{"_id":"source/_posts/Chronyd服务详解.md","hash":"6113defe8e5118b111144a14cb014b7c7fd2cca1","modified":1725870215873},{"_id":"public/p/Chronyd服务详解/index.html","hash":"4ff64c6e0dc03a994ad878fd8123b3192b308fa3","modified":1725870221388},{"_id":"public/archives/2024/09/index.html","hash":"1752b86574159b538780405c4f9ef48a807a21df","modified":1725869793839},{"_id":"public/tags/Chronyd/index.html","hash":"cf77fbfb8a53c6bc5d593ffb29a6d8ff28a0f286","modified":1725870221388},{"_id":"public/tags/时钟同步/index.html","hash":"7de262f911120eb6c91b03dc59b703d5f1c1916c","modified":1725870221388},{"_id":"source/images/chrony.jpeg","hash":"59b92b9c4b6c31568b6d1b9dd1e09727c0a56516","modified":1725870185849},{"_id":"public/images/chrony.jpeg","hash":"59b92b9c4b6c31568b6d1b9dd1e09727c0a56516","modified":1725870221388}],"Category":[{"name":"问题排查","_id":"cm00w1n8w0003p5ondffobwbd"},{"name":"Technology","_id":"cm00w9dq60001zeone4m9af6c"},{"name":"zido","_id":"cm01sxuox0000baon99uifmdl"},{"name":"zi","_id":"cm01sxvn90002baondass5zcp"},{"name":"自动化运维g","_id":"cm01sxxe70004baonhkw2ebpx"},{"name":"自动化运维工具","_id":"cm01sxxx90006baon6c9h2c72"},{"name":"运维工具","_id":"cm01sy2xy0008baon4n6fdlgq"},{"name":"工作随记","_id":"cm03rbqwr000189onei977a6a"},{"name":"高可用","_id":"cm0gkxi4m0001qzonby0c5k2y"},{"name":"yu","_id":"cm0uqgp7h000025on1ca99jz4"}],"Data":[],"Page":[{"title":"个人介绍","date":"2024-08-16T04:00:00.000Z","banner":"/images/shouye.jpeg","_content":"\n## 简介\n- **职业**: 运维开发工程师\n- **出生时间**: 98年\n- **性别**: 男\n- **联系方式**: 446302864@qq.com/baixiaozhou96@gmail.com\n\n## 工作履历\n- 2020.7-2023.6 杭州齐治科技 软件开发工程师\n- 2023.7-2024.1 腾讯云 ES SRE\n- 2024.3- 金山云 SRE\n\n\n## 个人项目\n- [SysStress](https://github.com/baixiaozhou/SysStress) 性能压测工具,还在不断完善中\n- [perfmonitorscan](https://github.com/baixiaozhou/perfmonitorscan) 性能自采集工具\n","source":"about/index.md","raw":"---\ntitle: 个人介绍\ndate: 2024-08-16 12:00:00\nbanner: /images/shouye.jpeg\n---\n\n## 简介\n- **职业**: 运维开发工程师\n- **出生时间**: 98年\n- **性别**: 男\n- **联系方式**: 446302864@qq.com/baixiaozhou96@gmail.com\n\n## 工作履历\n- 2020.7-2023.6 杭州齐治科技 软件开发工程师\n- 2023.7-2024.1 腾讯云 ES SRE\n- 2024.3- 金山云 SRE\n\n\n## 个人项目\n- [SysStress](https://github.com/baixiaozhou/SysStress) 性能压测工具,还在不断完善中\n- [perfmonitorscan](https://github.com/baixiaozhou/perfmonitorscan) 性能自采集工具\n","updated":"2024-09-04T10:26:33.544Z","path":"about/index.html","_id":"cm00w1n8t0000p5ones4c6lv1","comments":1,"layout":"page","content":"<h2 id=\"简介\"><a href=\"#简介\" class=\"headerlink\" title=\"简介\"></a>简介</h2><ul>\n<li><strong>职业</strong>: 运维开发工程师</li>\n<li><strong>出生时间</strong>: 98年</li>\n<li><strong>性别</strong>: 男</li>\n<li><strong>联系方式</strong>: <a href=\"mailto:446302864@qq.com\">446302864@qq.com</a>/<a href=\"mailto:baixiaozhou96@gmail.com\">baixiaozhou96@gmail.com</a></li>\n</ul>\n<h2 id=\"工作履历\"><a href=\"#工作履历\" class=\"headerlink\" title=\"工作履历\"></a>工作履历</h2><ul>\n<li>2020.7-2023.6 杭州齐治科技 软件开发工程师</li>\n<li>2023.7-2024.1 腾讯云 ES SRE</li>\n<li>2024.3- 金山云 SRE</li>\n</ul>\n<h2 id=\"个人项目\"><a href=\"#个人项目\" class=\"headerlink\" title=\"个人项目\"></a>个人项目</h2><ul>\n<li><a href=\"https://github.com/baixiaozhou/SysStress\">SysStress</a> 性能压测工具,还在不断完善中</li>\n<li><a href=\"https://github.com/baixiaozhou/perfmonitorscan\">perfmonitorscan</a> 性能自采集工具</li>\n</ul>\n","excerpt":"","more":"<h2 id=\"简介\"><a href=\"#简介\" class=\"headerlink\" title=\"简介\"></a>简介</h2><ul>\n<li><strong>职业</strong>: 运维开发工程师</li>\n<li><strong>出生时间</strong>: 98年</li>\n<li><strong>性别</strong>: 男</li>\n<li><strong>联系方式</strong>: <a href=\"mailto:446302864@qq.com\">446302864@qq.com</a>/<a href=\"mailto:baixiaozhou96@gmail.com\">baixiaozhou96@gmail.com</a></li>\n</ul>\n<h2 id=\"工作履历\"><a href=\"#工作履历\" class=\"headerlink\" title=\"工作履历\"></a>工作履历</h2><ul>\n<li>2020.7-2023.6 杭州齐治科技 软件开发工程师</li>\n<li>2023.7-2024.1 腾讯云 ES SRE</li>\n<li>2024.3- 金山云 SRE</li>\n</ul>\n<h2 id=\"个人项目\"><a href=\"#个人项目\" class=\"headerlink\" title=\"个人项目\"></a>个人项目</h2><ul>\n<li><a href=\"https://github.com/baixiaozhou/SysStress\">SysStress</a> 性能压测工具,还在不断完善中</li>\n<li><a href=\"https://github.com/baixiaozhou/perfmonitorscan\">perfmonitorscan</a> 性能自采集工具</li>\n</ul>\n"},{"title":"个人介绍","date":"2024-08-16T04:00:00.000Z","banner":"../images/persion.png","_content":"\n# Test\n","source":"friends/index.md","raw":"---\ntitle: 个人介绍\ndate: 2024-08-16 12:00:00\nbanner: ../images/persion.png\n---\n\n# Test\n","updated":"2024-08-17T14:27:02.654Z","path":"friends/index.html","comments":1,"layout":"page","_id":"cm00w1n8v0002p5onf4kd59kn","content":"<h1 id=\"Test\"><a href=\"#Test\" class=\"headerlink\" title=\"Test\"></a>Test</h1>","excerpt":"","more":"<h1 id=\"Test\"><a href=\"#Test\" class=\"headerlink\" title=\"Test\"></a>Test</h1>"},{"date":"2024-08-18T07:00:00.000Z","_content":"\n{% quot 留言板%}\n\n欢迎大家在这里进行留言,进行问题探讨\n","source":"comments/index.md","raw":"---\ndate: 2024-08-18 15:00:00\n---\n\n{% quot 留言板%}\n\n欢迎大家在这里进行留言,进行问题探讨\n","updated":"2024-08-18T07:05:49.280Z","path":"comments/index.html","title":"","comments":1,"layout":"page","_id":"cm00w1n8w0005p5on90658nf8","content":"<div class=\"tag-plugin quot\"><p class=\"content\" type=\"text\"><span class=\"empty\"></span><span class=\"text\">留言板</span><span class=\"empty\"></span></p></div>\n\n<p>欢迎大家在这里进行留言,进行问题探讨</p>\n","excerpt":"","more":"<div class=\"tag-plugin quot\"><p class=\"content\" type=\"text\"><span class=\"empty\"></span><span class=\"text\">留言板</span><span class=\"empty\"></span></p></div>\n\n<p>欢迎大家在这里进行留言,进行问题探讨</p>\n"}],"Post":[{"title":"JAVA问题定位","author":"baixiaozhou","description":"java常见问题和排查的基本方法和工具介绍","date":"2024-07-30T07:32:13.000Z","cover":"/images/java.jpg","banner":"/images/java.jpg","_content":"\n<!-- more -->\n\n\n# 一、JAVA 相关命令\n\n## 1.jps\njps - Lists the instrumented Java Virtual Machines (JVMs) on the target system. This command is experimental and unsupported.\n\n相关参数\n```\nOPTIONS\n The jps command supports a number of options that modify the output of the command. These options are subject to change or removal in the future.\n -q\n Suppresses the output of the class name, JAR file name, and arguments passed to the main method, producing only a list of local JVM identifiers.\n -m\n Displays the arguments passed to the main method. The output may be null for embedded JVMs.\n -l\n Displays the full package name for the application's main class or the full path name to the application's JAR file.\n -v\n Displays the arguments passed to the JVM.\n -V\n Suppresses the output of the class name, JAR file name, and arguments passed to the main method, producing only a list of local JVM identifiers.\n -Joption\n Passes option to the JVM, where option is one of the options described on the reference page for the Java application launcher. For example, -J-Xms48m sets the\n startup memory to 48 MB. See java(1).\n```\n\n## 2.jinfo\njinfo(Java Virtual Machine Configuration Information)是JDK提供的一个可以实时查看Java虚拟机各种配置参数和系统属性的命令行工具。使用jps命令的-v参数可以查看Java虚拟机启动时显式指定的配置参数,如果想查看没有显式指定的配置参数就可以使用jinfo命令进行查看。另外,jinfo命令还可以查询Java虚拟机进程的System.getProperties()的内容。\n\n以tomcat进程为例\n```\nAttaching to process ID 2045, please wait...\nDebugger attached successfully.\nServer compiler detected.\nJVM version is 25.242-b08\nJava System Properties:\n\njava.vendor = Huawei Technologies Co., Ltd\nsun.java.launcher = SUN_STANDARD\ncatalina.base = /usr/share/tomcat\nsun.management.compiler = HotSpot 64-Bit Tiered Compilers\nsun.nio.ch.bugLevel = \ncatalina.useNaming = true\njnidispatch.path = /var/cache/tomcat/temp/jna--903012287/jna4240128671455089550.tmp\nos.name = Linux\nsun.boot.class.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/resources.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/rt.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/sunrsasign.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jsse.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jce.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/charsets.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jfr.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/classes\njava.vm.specification.vendor = Oracle Corporation\njava.runtime.version = 1.8.0_242-b08\njna.loaded = true\nuser.name = xxx\ntomcat.util.scan.StandardJarScanFilter.jarsToScan = taglibs-standard-impl*.jar\nshared.loader = \ntomcat.util.buf.StringCache.byte.enabled = true\nuser.language = en\njava.naming.factory.initial = org.apache.naming.java.javaURLContextFactory\nsun.boot.library.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/amd64\njava.version = 1.8.0_242\njava.util.logging.manager = org.apache.juli.ClassLoaderLogManager\nuser.timezone = Asia/Shanghai\nsun.arch.data.model = 64\njava.util.concurrent.ForkJoinPool.common.threadFactory = org.apache.catalina.startup.SafeForkJoinWorkerThreadFactory\njava.endorsed.dirs = \nsun.cpu.isalist = \nsun.jnu.encoding = UTF-8\nfile.encoding.pkg = sun.io\npackage.access = sun.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.tomcat.\nfile.separator = /\njava.specification.name = Java Platform API Specification\njava.class.version = 52.0\nuser.country = US\njava.home = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre\njava.vm.info = mixed mode\nos.version = 4.19.90-24.4.v2101.ky10.x86_64\nsun.font.fontmanager = sun.awt.X11FontManager\npath.separator = :\njava.vm.version = 25.242-b08\njboss.i18n.generate-proxies = true\njava.awt.printerjob = sun.print.PSPrinterJob\nsun.io.unicode.encoding = UnicodeLittle\nawt.toolkit = sun.awt.X11.XToolkit\npackage.definition = sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.naming.,org.apache.tomcat.\njava.naming.factory.url.pkgs = org.apache.naming\nmail.mime.splitlongparameters = false\njava.security.egd = file:/dev/./urandom\nuser.home = /home/shterm\njava.specification.vendor = Oracle Corporation\ntomcat.util.scan.StandardJarScanFilter.jarsToSkip = activ*.jar,amqp-client.jar,annotations-api.jar,ant-junit*.jar,ant-launcher.jar,ant.jar,antlr.jar,aopalliance.jar,asm-*.jar,aspectj*.jar,bcp*.jar,bootstrap.jar,catalina-ant.jar,catalina-ha.jar,catalina-jmx-remote.jar,catalina-storeconfig.jar,catalina-tribes.jar,catalina-ws.jar,catalina.jar,cglib-*.jar,classmate.jar,cobertura-*.jar,commons-*.jar,compress-lzf.jar,curator-*.jar,db2-jdbc.jar,dom4j-*.jar,easymock-*.jar,ecj-*.jar,el-api.jar,elasticsearch.jar,geronimo-spec-jaxrpc*.jar,groovy-all.jar,guava.jar,h2*.jar,hamcrest-*.jar,hibernate*.jar,hppc.jar,http*.jar,icu4j-*.jar,itext*.jar,jackson-*.jar,jandex.jar,jasper-el.jar,jasper.jar,jasperreports*.jar,jaspic-api.jar,javamail.jar,javassist.jar,jaxb-*.jar,jaxen*.jar,jboss*.jar,jc*.jar,jdom-*.jar,jedis.jar,jetty-*.jar,jfreechart.jar,jgit.jar,jline.jar,jmx-tools.jar,jmx.jar,jna.jar,joda-time.jar,jr-*.jar,jsch.jar,json*.jar,jsoup.jar,jsp-api.jar,jsr166e.jar,jstl.jar,jta*.jar,junit-*.jar,junit.jar,liquibase-*.jar,log4j*.jar,lucene*.jar,mail*.jar,mariadb-jdbc.jar,mssql-jdbc.jar,mybatis.jar,netty.jar,nmap4j.jar,objenesis*.jar,olap4j.jar,opc*.jar,oracle-jdbc.jar,oraclepki.jar,oro-*.jar,poi*.jar,postgresql-jdbc.jar,quartz.jar,servlet-api-*.jar,servlet-api.jar,slf4j*.jar,snakeyaml.jar,snmp4j.jar,spring*.jar,sshd-core.jar,taglibs-standard-spec-*.jar,tagsoup-*.jar,t-digest.jar,tomcat-api.jar,tomcat-coyote.jar,tomcat-dbcp.jar,tomcat-i18n-*.jar,tomcat-jdbc.jar,tomcat-jni.jar,tomcat-juli-adapters.jar,tomcat-juli.jar,tomcat-util-scan.jar,tomcat-util.jar,tomcat-websocket.jar,tools.jar,validation-api.jar,velocypack.jar,websocket-api.jar,wl*.jar,wsdl4j*.jar,xercesImpl.jar,xml-apis.jar,xmlbeans.jar,xmlParserAPIs-*.jar,xmlParserAPIs.jar,xom-*.jar,xz.jar,zip4j.jar,zookeeper.jar\njava.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib\njava.vendor.url = http://jdk.rnd.huawei.com/\njava.vm.vendor = Huawei Technologies Co., Ltd\ncommon.loader = \"${catalina.base}/lib\",\"${catalina.base}/lib/*.jar\",\"${catalina.home}/lib\",\"${catalina.home}/lib/*.jar\"\njava.runtime.name = OpenJDK Runtime Environment\nsun.java.command = org.apache.catalina.startup.Bootstrap start\njava.class.path = /usr/share/tomcat/bin/bootstrap.jar:/usr/share/tomcat/bin/tomcat-juli.jar:/usr/lib/java/commons-daemon.jar\njava.vm.specification.name = Java Virtual Machine Specification\njava.vm.specification.version = 1.8\ncatalina.home = /usr/share/tomcat\nsun.cpu.endian = little\nsun.os.patch.level = unknown\njava.awt.headless = true\njava.io.tmpdir = /var/cache/tomcat/temp\njava.vendor.url.bug = http://jdk.rnd.huawei.com/\nserver.loader = \njava.rmi.server.hostname = 127.0.0.1\njna.platform.library.path = /usr/lib64:/lib64:/usr/lib:/lib:/usr/lib64/tracker-miners-2.0:/usr/lib64/tracker-2.0:/usr/lib64/dyninst:/usr/libexec/sudo:/usr/lib64/sssd:/usr/pgsql-9.6/lib:/usr/lib64/perl5/CORE:/usr/lib64/opencryptoki:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/amd64/jli:/usr/lib64/bind9-export\nos.arch = amd64\njava.awt.graphicsenv = sun.awt.X11GraphicsEnvironment\njava.ext.dirs = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/ext:/usr/java/packages/lib/ext\nuser.dir = /usr/share/tomcat\nline.separator = \n\njava.vm.name = OpenJDK 64-Bit Server VM\nlog4j.configurationFile = /etc/tomcat/log4j2.xml\nfile.encoding = UTF-8\ncom.sun.jndi.ldap.object.disableEndpointIdentification = \njava.specification.version = 1.8\n\nVM Flags:\nNon-default VM flags: -XX:CICompilerCount=4 -XX:GCLogFileSize=20971520 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=243269632 -XX:MaxHeapSize=1610612736 -XX:MaxNewSize=536870912 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=80740352 -XX:NumberOfGCLogFiles=15 -XX:OldSize=162529280 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseGCLogFileRotation -XX:+UseParallelGC \nCommand line: -Xmx1536m -Djava.security.egd=file:/dev/./urandom -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tomcat -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=15 -XX:GCLogFileSize=20m -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/var/log/tomcat/tomcat-gc-%t.log -Dcom.sun.jndi.ldap.object.disableEndpointIdentification -Dcatalina.base=/usr/share/tomcat -Dcatalina.home=/usr/share/tomcat -Djava.endorsed.dirs= -Djava.io.tmpdir=/var/cache/tomcat/temp -Dlog4j.configurationFile=/etc/tomcat/log4j2.xml -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager\n```\n\n## 3.jstat\n命令参数说明:\n- generalOptions:通用选项,如果指定一个通用选项,就不能指定任何其他选项或参数。它包括如下两个选项:\n- -help:显示帮助信息。\n- -options:显示outputOptions参数的列表。\n- outputOptions:输出选项,指定显示某一种Java虚拟机信息。\n- -t:把时间戳列显示为输出的第一列。这个时间戳是从Java虚拟机的开始运行到现在的秒数。\n- -h n:每显示n行显示一次表头,其中n为正整数。默认值为 0,即仅在第一行数据显示一次表头。\n- vmid:虚拟机唯一ID(LVMID,Local Virtual Machine Identifier),如果查看本机就是Java进程的进程ID。\n- interval:显示信息的时间间隔,单位默认毫秒。也可以指定秒为单位,比如:1s。如果指定了该参数,jstat命令将每隔这段时间显示一次统计信息。\n- count:显示数据的次数,默认值是无穷大,这将导致jstat命令一直显示统计信息,直到目标JVM终止或jstat命令终止。\n输出选项\n如果不指定通用选项(generalOptions),则可以指定输出选项(outputOptions)。输出选项决定jstat命令显示的内容和格式,具体如下:\n- -class:显示类加载、卸载数量、总空间和装载耗时的统计信息。\n- -compiler:显示即时编译的方法、耗时等信息。\n- -gc:显示堆各个区域内存使用和垃圾回收的统计信息。\n- -gccapacity:显示堆各个区域的容量及其对应的空间的统计信息。\n- -gcutil:显示有关垃圾收集统计信息的摘要。\n- -gccause:显示关于垃圾收集统计信息的摘要(与-gcutil相同),以及最近和当前垃圾回收的原因。\n- -gcnew:显示新生代的垃圾回收统计信息。\n- -gcnewcapacity:显示新生代的大小及其对应的空间的统计信息。\n- -gcold: 显示老年代和元空间的垃圾回收统计信息。\n- -gcoldcapacity:显示老年代的大小统计信息。\n- -gcmetacapacity:显示元空间的大小的统计信息。\n- -printcompilation:显示即时编译方法的统计信息。\n\n# 二、线程堆栈\n## 1.输出\nJava虚拟机提供了线程转储(Thread dump)的后门,通过这个后门,可以将线程堆栈打印出来。这个后门就是通过向Java进程发送一个QUIT信号,Java虚拟机收到该信号之后,将系统当前的JAVA线程调用堆栈打印出来。\n\n打印方法:\n- jstack -l pid > xxx.txt 需要注意的是,linux系统中往往会用不同的用户去执行不同的程序,此时可能需要通过sudu -u xxx jstack的形式\n- kill -3\n***同时请确保Java命令行中没有DISABLE_JAVADUMP运行选项***\n## 2.线程分析\n通过输出堆栈进行分析 `jstack -l $(jps | grep xxx | awk '{print $1}')` > /tmp/xxx.jstack\n```Lua\n\"SYS_STATUS_CHECKER\" #14 daemon prio=5 os_prio=0 tid=0x00007f5e047bf000 nid=0xe15 waiting on condition [0x00007f5dd43d1000]\n java.lang.Thread.State: TIMED_WAITING (sleeping)\n at java.lang.Thread.sleep(Native Method)\nru at com.xxx.xxx.SystemStatusChecker.run(SystemStatusChecker.java:xx)\n at java.lang.Thread.run(Thread.java:748) \n Locked ownable synchronizers:\n - None\n \n\"RMI Reaper\" #39 prio=5 os_prio=0 tid=0x00007f5e04e4c800 nid=0xf0b in Object.wait() [0x00007f5dae2c4000]\n java.lang.Thread.State: WAITING (on object monitor)\n at java.lang.Object.wait(Native Method)\n - waiting on <0x00000000c0c88d20> (a java.lang.ref.ReferenceQueue$Lock)\n at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)\n - locked <0x00000000c0c88d20> (a java.lang.ref.ReferenceQueue$Lock)\n at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)\n at sun.rmi.transport.ObjectTable$Reaper.run(ObjectTable.java:351)\n at java.lang.Thread.run(Thread.java:748)\n Locked ownable synchronizers:\n - None\n \n\"main\" #1 prio=5 os_prio=0 tid=0x00007f5e0400a000 nid=0xdcb runnable [0x00007f5e0b393000]\n java.lang.Thread.State: RUNNABLE\n at java.net.PlainSocketImpl.socketAccept(Native Method)\n at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)\n at java.net.ServerSocket.implAccept(ServerSocket.java:545)\n at java.net.ServerSocket.accept(ServerSocket.java:513)\n at com.xxx.common.xxx.await(CommonMain.java:244)\n at com.xxx.common.xxx.startup(CommonMain.java:207)\n at com.xxx.common.xxx.main(CommonMain.java:147)\n Locked ownable synchronizers:\n - None\n```\n在RMI线程中可以看到 \" - locked <0x00000000c0c88d20> (a java.lang.ref.ReferenceQueue$Lock)\" 表示该线程已经使用了ID为\"0x00000000c0c88d2\"的锁,锁的ID由系统自动产生\n```\n\"main\" prio=5 os_prio=0 tid=0x00007f5e0400a000 nid=0xdcb runnable [0x00007f5e0b393000]\n| | | | | | |\n线程名称 线程优先级 操作系统级别的优先级 线程id 对应的本地线程ID 状态 线程占用内存地址\n```\n\n其中\"线程对应的本地线程id号\"所指的\"本地线程\"是指该Java线程所对应的虚拟机中的本地线程。我们知道Java是解析型语言,执行的实体是Java虚拟机,因此Java语言中的线程是 依附于Java虚拟机中的本地线程来运行的,实际上是本地线程在执行Java线程代码。\n\nJava代码 中创建一个thread,虚拟机在运行期就会创建一个对应的本地线程,而这个本地线程才是真正的线程实体。为了更加深入得理解本地线程和Java线程的关系,在Unix/Linux下,我们可以通 如下方式把Java虚拟机的本地线程打印出来:\n- 使用ps -ef | grep java 获得Java进程ID。\n- 使用pstack <java pid>获得Java虚拟机的本地线程的堆栈\n其中本地线程各项含义如下:\n```\nThread 56 (Thread 0x7f5e0b394700 (LWP 3531))\n| | |\n| | +----本地线程id(另一种表示,LWP-light weight process)\n| +-------------------本地线程id\n+------------------------------线程名称\n```\n而通过jstack输出的main本地线程ID为0xdcb,其10进制正好为3531。\n\n\"runnable\"表示当前线程处于运行状态。这个runnable状态是从虚拟机的角度来看的, 表示这个线程正在运行\n<p><strong>⚠️ NOTE:</strong> 但是处于Runnable状态的线程不一定真的消耗CPU. 处于Runnable的线程只能说明该线程没有阻塞在java的wait或者sleep方法上,同时也没等待在锁上面。但是如果该线程调用了本地方法,而本地方法处于等待状态,这个时候虚拟机是不知道本地代码中发生 了什么(但操作系统是知道的,pstack就是操作提供的一个命令,它知道当前线程正在执行的本地代码上下文),此时尽管当前线程实际上也是阻塞的状态,但实际上显示出来的还是runnable状态, 这种情况下是不消耗CPU的</p>\n\n```\n1. 处于waittig和blocked状态的线程都不会消耗CPU \n2. 线程频繁地挂起和唤醒需要消耗CPU, 而且代价颇大\n```\n- TIMED_WAITING(on object monitor) 表示当前线程被挂起一段时间,说明该线程正在 执行obj.wait(int time)方法.\n- TIMED_WAITING(sleeping) 表示当前线程被挂起一段时间,即正在执行Thread.sleep(int time)方法. \n- TIMED_WAITING(parking) 当前线程被挂起一段时间,即正在执行Thread.sleep(int time)方法.\n- WAINTING(on object monitor) 当前线程被挂起,即正在执行obj.wait()方法(无参数的wait()方法).\n```\n处于TIMED_WAITING、WAINTING状态的线程一定不消耗CPU. 处于RUNNABLE的线程,要结合当前线程代码的性质判断,是否消耗CPU.\n• 如果是纯Java运算代码,则消耗CPU.\n• 如果是网络IO,很少消耗CPU.\n• 如果是本地代码,结合本地代码的性质判断(可以通过pstack/gstack获取本地线程堆栈), 如果是纯运算代码,则消耗CPU, 如果被挂起,则不消耗CPU,如果是IO,则不怎么消 耗CPU。\n```\n# 三、相关的排查方法\n## 1.CPU\n生产环境中往往会出现CPU飙高的情况,对于JAVA应用而言,此类问题相对较好确定问题方向。\n### 1.1 使用jstack确定CPU占用高的线程\\\n通过`top`指令,可以看到进程占用的一些基础资源信息,然后“P”键可以按照CPU使用率进行排序,“M”键可以按照内存占用情况进行排序\n\n找到CPU占用高的进程pid,然后将jstack信息定向到一个文件中去,通过`top -Hp pid`查看具体的情况。\n\n通过 `printf '%x\\n' pid`将pid转换为16进制,然后在jstack文件中根据对应的数字进行查找,然后针对性的进行分析\n### 1.2 频繁GC\n有时候我们可以先确定下gc是不是太频繁,使用`jstat -gc pid 1000`命令来对gc分代变化情况进行观察,1000表示采样间隔(ms),`S0C/S1C、S0U/S1U、EC/EU、OC/OU、MC/MU`分别代表两个Survivor区、Eden区、老年代、元数据区的容量和使用量。`YGC/YGT、FGC/FGCT、GCT`则代表YoungGc、FullGc的耗时和次数以及总耗时。如果看到gc比较频繁,再针对gc方面做进一步分析。\n![alt text](../images/gc.png)\n### 1.3 频繁上下文切换\n针对频繁上下文问题,我们可以使用vmstat命令来进行查看\n![alt text](../images/vmstat.png)\ncs(context switch)一列则代表了上下文切换的次数。\n\n如果我们希望对特定的pid进行监控那么可以使用 `pidstat -w pid`命令,cswch和nvcswch表示自愿及非自愿切换。\n\n## 2.内存\n对于JAVA应用,涉及到的内存问题主要包括OOM、GC问题和堆外内存。\n### 2.1 OOM\nJVM中的内存不足,OOM大致可以分为以下几种情况\n- `Exception in thread \"main\" java.lang.OutOfMemoryError: unable to create new native thread` 这个意思是没有足够的内存空间给线程分配java栈,基本上还是线程池代码写的有问题,比如说忘记shutdown,所以说应该首先从代码层面来寻找问题,使用jstack或者jmap。如果一切都正常,JVM方面可以通过指定Xss来减少单个thread stack的大小。另外也可以在系统层面,可以通过修改`/etc/security/limits.confnofile`和`nproc`来增大os对线程的限制\n- `Exception in thread \"main\" java.lang.OutOfMemoryError: Java heap space ` 这个意思是堆的内存占用已经达到-Xmx设置的最大值,应该是最常见的OOM错误了。解决思路仍然是先应该在代码中找,怀疑存在内存泄漏,通过jstack和jmap去定位问题。如果说一切都正常,才需要通过调整Xmx的值来扩大内存。\n- `Caused by: java.lang.OutOfMemoryError: Meta space` 这个意思是元数据区的内存占用已经达到`XX:MaxMetaspaceSize`设置的最大值,排查思路和上面的一致,参数方面可以通过`XX:MaxPermSize`来进行调整\n- `Exception in thread \"main\" java.lang.StackOverflowError` 表示线程栈需要的内存大于Xss值,同样也是先进行排查,参数方面通过Xss来调整,但调整的太大可能又会引起OOM。\n### 2.2 GC问题\ngc问题除了影响cpu也会影响内存,排查思路也是一致的。一般先使用jstat来查看分代变化情况,比如youngGC或者fullGC次数是不是太多呀;EU、OU等指标增长是不是异常等。\n\n线程的话太多而且不被及时gc也会引发oom,大部分就是之前说的`unable to create new native thread`。除了jstack细细分析dump文件外,我们一般先会看下总体线程,通过`pstreee -p pid |wc -l`\n### 2.3 堆外内存\nJVM 的堆外内存主要包括:\n- JVM 自身运行占用的空间;\n- 线程栈分配占用的系统内存;\n- DirectByteBuffer 占用的内存;\n- JNI 里分配的内存;\n- Java 8 开始的元数据空间;\n- NIO 缓存\n- Unsafe 调用分配的内存;\n- codecache\n\n冰山对象:冰山对象是指在 JVM 堆里占用的内存很小,但其实引用了一块很大的本地内存。DirectByteBuffer 和 线程都属于这类对象。\n#### 2.3.1NMT分析堆外内存\nNMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。\n\nNMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。\n\n#### 2.3.2 开启 NMT\n启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。\n启动命令: `-XX:NativeMemoryTracking=[off | summary | detail]`。\n- off:NMT 默认是关闭的;\n- summary:只收集子系统的内存使用的总计数据;\n- detail:收集每个调用点的内存使用数据。\n\n#### 2.3.3 jcmd 访问 NMT 数据\n命令:\n`jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]`\n\n","source":"_posts/JAVA问题定位.md","raw":"---\ntitle: JAVA问题定位\nauthor: baixiaozhou\ncategories:\n - 问题排查\ntags:\n - JAVA\n - Linux\ndescription: java常见问题和排查的基本方法和工具介绍\ndate: 2024-07-30 15:32:13\ncover: /images/java.jpg\nbanner: /images/java.jpg\n---\n\n<!-- more -->\n\n\n# 一、JAVA 相关命令\n\n## 1.jps\njps - Lists the instrumented Java Virtual Machines (JVMs) on the target system. This command is experimental and unsupported.\n\n相关参数\n```\nOPTIONS\n The jps command supports a number of options that modify the output of the command. These options are subject to change or removal in the future.\n -q\n Suppresses the output of the class name, JAR file name, and arguments passed to the main method, producing only a list of local JVM identifiers.\n -m\n Displays the arguments passed to the main method. The output may be null for embedded JVMs.\n -l\n Displays the full package name for the application's main class or the full path name to the application's JAR file.\n -v\n Displays the arguments passed to the JVM.\n -V\n Suppresses the output of the class name, JAR file name, and arguments passed to the main method, producing only a list of local JVM identifiers.\n -Joption\n Passes option to the JVM, where option is one of the options described on the reference page for the Java application launcher. For example, -J-Xms48m sets the\n startup memory to 48 MB. See java(1).\n```\n\n## 2.jinfo\njinfo(Java Virtual Machine Configuration Information)是JDK提供的一个可以实时查看Java虚拟机各种配置参数和系统属性的命令行工具。使用jps命令的-v参数可以查看Java虚拟机启动时显式指定的配置参数,如果想查看没有显式指定的配置参数就可以使用jinfo命令进行查看。另外,jinfo命令还可以查询Java虚拟机进程的System.getProperties()的内容。\n\n以tomcat进程为例\n```\nAttaching to process ID 2045, please wait...\nDebugger attached successfully.\nServer compiler detected.\nJVM version is 25.242-b08\nJava System Properties:\n\njava.vendor = Huawei Technologies Co., Ltd\nsun.java.launcher = SUN_STANDARD\ncatalina.base = /usr/share/tomcat\nsun.management.compiler = HotSpot 64-Bit Tiered Compilers\nsun.nio.ch.bugLevel = \ncatalina.useNaming = true\njnidispatch.path = /var/cache/tomcat/temp/jna--903012287/jna4240128671455089550.tmp\nos.name = Linux\nsun.boot.class.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/resources.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/rt.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/sunrsasign.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jsse.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jce.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/charsets.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jfr.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/classes\njava.vm.specification.vendor = Oracle Corporation\njava.runtime.version = 1.8.0_242-b08\njna.loaded = true\nuser.name = xxx\ntomcat.util.scan.StandardJarScanFilter.jarsToScan = taglibs-standard-impl*.jar\nshared.loader = \ntomcat.util.buf.StringCache.byte.enabled = true\nuser.language = en\njava.naming.factory.initial = org.apache.naming.java.javaURLContextFactory\nsun.boot.library.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/amd64\njava.version = 1.8.0_242\njava.util.logging.manager = org.apache.juli.ClassLoaderLogManager\nuser.timezone = Asia/Shanghai\nsun.arch.data.model = 64\njava.util.concurrent.ForkJoinPool.common.threadFactory = org.apache.catalina.startup.SafeForkJoinWorkerThreadFactory\njava.endorsed.dirs = \nsun.cpu.isalist = \nsun.jnu.encoding = UTF-8\nfile.encoding.pkg = sun.io\npackage.access = sun.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.tomcat.\nfile.separator = /\njava.specification.name = Java Platform API Specification\njava.class.version = 52.0\nuser.country = US\njava.home = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre\njava.vm.info = mixed mode\nos.version = 4.19.90-24.4.v2101.ky10.x86_64\nsun.font.fontmanager = sun.awt.X11FontManager\npath.separator = :\njava.vm.version = 25.242-b08\njboss.i18n.generate-proxies = true\njava.awt.printerjob = sun.print.PSPrinterJob\nsun.io.unicode.encoding = UnicodeLittle\nawt.toolkit = sun.awt.X11.XToolkit\npackage.definition = sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.naming.,org.apache.tomcat.\njava.naming.factory.url.pkgs = org.apache.naming\nmail.mime.splitlongparameters = false\njava.security.egd = file:/dev/./urandom\nuser.home = /home/shterm\njava.specification.vendor = Oracle Corporation\ntomcat.util.scan.StandardJarScanFilter.jarsToSkip = activ*.jar,amqp-client.jar,annotations-api.jar,ant-junit*.jar,ant-launcher.jar,ant.jar,antlr.jar,aopalliance.jar,asm-*.jar,aspectj*.jar,bcp*.jar,bootstrap.jar,catalina-ant.jar,catalina-ha.jar,catalina-jmx-remote.jar,catalina-storeconfig.jar,catalina-tribes.jar,catalina-ws.jar,catalina.jar,cglib-*.jar,classmate.jar,cobertura-*.jar,commons-*.jar,compress-lzf.jar,curator-*.jar,db2-jdbc.jar,dom4j-*.jar,easymock-*.jar,ecj-*.jar,el-api.jar,elasticsearch.jar,geronimo-spec-jaxrpc*.jar,groovy-all.jar,guava.jar,h2*.jar,hamcrest-*.jar,hibernate*.jar,hppc.jar,http*.jar,icu4j-*.jar,itext*.jar,jackson-*.jar,jandex.jar,jasper-el.jar,jasper.jar,jasperreports*.jar,jaspic-api.jar,javamail.jar,javassist.jar,jaxb-*.jar,jaxen*.jar,jboss*.jar,jc*.jar,jdom-*.jar,jedis.jar,jetty-*.jar,jfreechart.jar,jgit.jar,jline.jar,jmx-tools.jar,jmx.jar,jna.jar,joda-time.jar,jr-*.jar,jsch.jar,json*.jar,jsoup.jar,jsp-api.jar,jsr166e.jar,jstl.jar,jta*.jar,junit-*.jar,junit.jar,liquibase-*.jar,log4j*.jar,lucene*.jar,mail*.jar,mariadb-jdbc.jar,mssql-jdbc.jar,mybatis.jar,netty.jar,nmap4j.jar,objenesis*.jar,olap4j.jar,opc*.jar,oracle-jdbc.jar,oraclepki.jar,oro-*.jar,poi*.jar,postgresql-jdbc.jar,quartz.jar,servlet-api-*.jar,servlet-api.jar,slf4j*.jar,snakeyaml.jar,snmp4j.jar,spring*.jar,sshd-core.jar,taglibs-standard-spec-*.jar,tagsoup-*.jar,t-digest.jar,tomcat-api.jar,tomcat-coyote.jar,tomcat-dbcp.jar,tomcat-i18n-*.jar,tomcat-jdbc.jar,tomcat-jni.jar,tomcat-juli-adapters.jar,tomcat-juli.jar,tomcat-util-scan.jar,tomcat-util.jar,tomcat-websocket.jar,tools.jar,validation-api.jar,velocypack.jar,websocket-api.jar,wl*.jar,wsdl4j*.jar,xercesImpl.jar,xml-apis.jar,xmlbeans.jar,xmlParserAPIs-*.jar,xmlParserAPIs.jar,xom-*.jar,xz.jar,zip4j.jar,zookeeper.jar\njava.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib\njava.vendor.url = http://jdk.rnd.huawei.com/\njava.vm.vendor = Huawei Technologies Co., Ltd\ncommon.loader = \"${catalina.base}/lib\",\"${catalina.base}/lib/*.jar\",\"${catalina.home}/lib\",\"${catalina.home}/lib/*.jar\"\njava.runtime.name = OpenJDK Runtime Environment\nsun.java.command = org.apache.catalina.startup.Bootstrap start\njava.class.path = /usr/share/tomcat/bin/bootstrap.jar:/usr/share/tomcat/bin/tomcat-juli.jar:/usr/lib/java/commons-daemon.jar\njava.vm.specification.name = Java Virtual Machine Specification\njava.vm.specification.version = 1.8\ncatalina.home = /usr/share/tomcat\nsun.cpu.endian = little\nsun.os.patch.level = unknown\njava.awt.headless = true\njava.io.tmpdir = /var/cache/tomcat/temp\njava.vendor.url.bug = http://jdk.rnd.huawei.com/\nserver.loader = \njava.rmi.server.hostname = 127.0.0.1\njna.platform.library.path = /usr/lib64:/lib64:/usr/lib:/lib:/usr/lib64/tracker-miners-2.0:/usr/lib64/tracker-2.0:/usr/lib64/dyninst:/usr/libexec/sudo:/usr/lib64/sssd:/usr/pgsql-9.6/lib:/usr/lib64/perl5/CORE:/usr/lib64/opencryptoki:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/amd64/jli:/usr/lib64/bind9-export\nos.arch = amd64\njava.awt.graphicsenv = sun.awt.X11GraphicsEnvironment\njava.ext.dirs = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/ext:/usr/java/packages/lib/ext\nuser.dir = /usr/share/tomcat\nline.separator = \n\njava.vm.name = OpenJDK 64-Bit Server VM\nlog4j.configurationFile = /etc/tomcat/log4j2.xml\nfile.encoding = UTF-8\ncom.sun.jndi.ldap.object.disableEndpointIdentification = \njava.specification.version = 1.8\n\nVM Flags:\nNon-default VM flags: -XX:CICompilerCount=4 -XX:GCLogFileSize=20971520 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=243269632 -XX:MaxHeapSize=1610612736 -XX:MaxNewSize=536870912 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=80740352 -XX:NumberOfGCLogFiles=15 -XX:OldSize=162529280 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseGCLogFileRotation -XX:+UseParallelGC \nCommand line: -Xmx1536m -Djava.security.egd=file:/dev/./urandom -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tomcat -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=15 -XX:GCLogFileSize=20m -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/var/log/tomcat/tomcat-gc-%t.log -Dcom.sun.jndi.ldap.object.disableEndpointIdentification -Dcatalina.base=/usr/share/tomcat -Dcatalina.home=/usr/share/tomcat -Djava.endorsed.dirs= -Djava.io.tmpdir=/var/cache/tomcat/temp -Dlog4j.configurationFile=/etc/tomcat/log4j2.xml -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager\n```\n\n## 3.jstat\n命令参数说明:\n- generalOptions:通用选项,如果指定一个通用选项,就不能指定任何其他选项或参数。它包括如下两个选项:\n- -help:显示帮助信息。\n- -options:显示outputOptions参数的列表。\n- outputOptions:输出选项,指定显示某一种Java虚拟机信息。\n- -t:把时间戳列显示为输出的第一列。这个时间戳是从Java虚拟机的开始运行到现在的秒数。\n- -h n:每显示n行显示一次表头,其中n为正整数。默认值为 0,即仅在第一行数据显示一次表头。\n- vmid:虚拟机唯一ID(LVMID,Local Virtual Machine Identifier),如果查看本机就是Java进程的进程ID。\n- interval:显示信息的时间间隔,单位默认毫秒。也可以指定秒为单位,比如:1s。如果指定了该参数,jstat命令将每隔这段时间显示一次统计信息。\n- count:显示数据的次数,默认值是无穷大,这将导致jstat命令一直显示统计信息,直到目标JVM终止或jstat命令终止。\n输出选项\n如果不指定通用选项(generalOptions),则可以指定输出选项(outputOptions)。输出选项决定jstat命令显示的内容和格式,具体如下:\n- -class:显示类加载、卸载数量、总空间和装载耗时的统计信息。\n- -compiler:显示即时编译的方法、耗时等信息。\n- -gc:显示堆各个区域内存使用和垃圾回收的统计信息。\n- -gccapacity:显示堆各个区域的容量及其对应的空间的统计信息。\n- -gcutil:显示有关垃圾收集统计信息的摘要。\n- -gccause:显示关于垃圾收集统计信息的摘要(与-gcutil相同),以及最近和当前垃圾回收的原因。\n- -gcnew:显示新生代的垃圾回收统计信息。\n- -gcnewcapacity:显示新生代的大小及其对应的空间的统计信息。\n- -gcold: 显示老年代和元空间的垃圾回收统计信息。\n- -gcoldcapacity:显示老年代的大小统计信息。\n- -gcmetacapacity:显示元空间的大小的统计信息。\n- -printcompilation:显示即时编译方法的统计信息。\n\n# 二、线程堆栈\n## 1.输出\nJava虚拟机提供了线程转储(Thread dump)的后门,通过这个后门,可以将线程堆栈打印出来。这个后门就是通过向Java进程发送一个QUIT信号,Java虚拟机收到该信号之后,将系统当前的JAVA线程调用堆栈打印出来。\n\n打印方法:\n- jstack -l pid > xxx.txt 需要注意的是,linux系统中往往会用不同的用户去执行不同的程序,此时可能需要通过sudu -u xxx jstack的形式\n- kill -3\n***同时请确保Java命令行中没有DISABLE_JAVADUMP运行选项***\n## 2.线程分析\n通过输出堆栈进行分析 `jstack -l $(jps | grep xxx | awk '{print $1}')` > /tmp/xxx.jstack\n```Lua\n\"SYS_STATUS_CHECKER\" #14 daemon prio=5 os_prio=0 tid=0x00007f5e047bf000 nid=0xe15 waiting on condition [0x00007f5dd43d1000]\n java.lang.Thread.State: TIMED_WAITING (sleeping)\n at java.lang.Thread.sleep(Native Method)\nru at com.xxx.xxx.SystemStatusChecker.run(SystemStatusChecker.java:xx)\n at java.lang.Thread.run(Thread.java:748) \n Locked ownable synchronizers:\n - None\n \n\"RMI Reaper\" #39 prio=5 os_prio=0 tid=0x00007f5e04e4c800 nid=0xf0b in Object.wait() [0x00007f5dae2c4000]\n java.lang.Thread.State: WAITING (on object monitor)\n at java.lang.Object.wait(Native Method)\n - waiting on <0x00000000c0c88d20> (a java.lang.ref.ReferenceQueue$Lock)\n at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)\n - locked <0x00000000c0c88d20> (a java.lang.ref.ReferenceQueue$Lock)\n at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)\n at sun.rmi.transport.ObjectTable$Reaper.run(ObjectTable.java:351)\n at java.lang.Thread.run(Thread.java:748)\n Locked ownable synchronizers:\n - None\n \n\"main\" #1 prio=5 os_prio=0 tid=0x00007f5e0400a000 nid=0xdcb runnable [0x00007f5e0b393000]\n java.lang.Thread.State: RUNNABLE\n at java.net.PlainSocketImpl.socketAccept(Native Method)\n at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)\n at java.net.ServerSocket.implAccept(ServerSocket.java:545)\n at java.net.ServerSocket.accept(ServerSocket.java:513)\n at com.xxx.common.xxx.await(CommonMain.java:244)\n at com.xxx.common.xxx.startup(CommonMain.java:207)\n at com.xxx.common.xxx.main(CommonMain.java:147)\n Locked ownable synchronizers:\n - None\n```\n在RMI线程中可以看到 \" - locked <0x00000000c0c88d20> (a java.lang.ref.ReferenceQueue$Lock)\" 表示该线程已经使用了ID为\"0x00000000c0c88d2\"的锁,锁的ID由系统自动产生\n```\n\"main\" prio=5 os_prio=0 tid=0x00007f5e0400a000 nid=0xdcb runnable [0x00007f5e0b393000]\n| | | | | | |\n线程名称 线程优先级 操作系统级别的优先级 线程id 对应的本地线程ID 状态 线程占用内存地址\n```\n\n其中\"线程对应的本地线程id号\"所指的\"本地线程\"是指该Java线程所对应的虚拟机中的本地线程。我们知道Java是解析型语言,执行的实体是Java虚拟机,因此Java语言中的线程是 依附于Java虚拟机中的本地线程来运行的,实际上是本地线程在执行Java线程代码。\n\nJava代码 中创建一个thread,虚拟机在运行期就会创建一个对应的本地线程,而这个本地线程才是真正的线程实体。为了更加深入得理解本地线程和Java线程的关系,在Unix/Linux下,我们可以通 如下方式把Java虚拟机的本地线程打印出来:\n- 使用ps -ef | grep java 获得Java进程ID。\n- 使用pstack <java pid>获得Java虚拟机的本地线程的堆栈\n其中本地线程各项含义如下:\n```\nThread 56 (Thread 0x7f5e0b394700 (LWP 3531))\n| | |\n| | +----本地线程id(另一种表示,LWP-light weight process)\n| +-------------------本地线程id\n+------------------------------线程名称\n```\n而通过jstack输出的main本地线程ID为0xdcb,其10进制正好为3531。\n\n\"runnable\"表示当前线程处于运行状态。这个runnable状态是从虚拟机的角度来看的, 表示这个线程正在运行\n<p><strong>⚠️ NOTE:</strong> 但是处于Runnable状态的线程不一定真的消耗CPU. 处于Runnable的线程只能说明该线程没有阻塞在java的wait或者sleep方法上,同时也没等待在锁上面。但是如果该线程调用了本地方法,而本地方法处于等待状态,这个时候虚拟机是不知道本地代码中发生 了什么(但操作系统是知道的,pstack就是操作提供的一个命令,它知道当前线程正在执行的本地代码上下文),此时尽管当前线程实际上也是阻塞的状态,但实际上显示出来的还是runnable状态, 这种情况下是不消耗CPU的</p>\n\n```\n1. 处于waittig和blocked状态的线程都不会消耗CPU \n2. 线程频繁地挂起和唤醒需要消耗CPU, 而且代价颇大\n```\n- TIMED_WAITING(on object monitor) 表示当前线程被挂起一段时间,说明该线程正在 执行obj.wait(int time)方法.\n- TIMED_WAITING(sleeping) 表示当前线程被挂起一段时间,即正在执行Thread.sleep(int time)方法. \n- TIMED_WAITING(parking) 当前线程被挂起一段时间,即正在执行Thread.sleep(int time)方法.\n- WAINTING(on object monitor) 当前线程被挂起,即正在执行obj.wait()方法(无参数的wait()方法).\n```\n处于TIMED_WAITING、WAINTING状态的线程一定不消耗CPU. 处于RUNNABLE的线程,要结合当前线程代码的性质判断,是否消耗CPU.\n• 如果是纯Java运算代码,则消耗CPU.\n• 如果是网络IO,很少消耗CPU.\n• 如果是本地代码,结合本地代码的性质判断(可以通过pstack/gstack获取本地线程堆栈), 如果是纯运算代码,则消耗CPU, 如果被挂起,则不消耗CPU,如果是IO,则不怎么消 耗CPU。\n```\n# 三、相关的排查方法\n## 1.CPU\n生产环境中往往会出现CPU飙高的情况,对于JAVA应用而言,此类问题相对较好确定问题方向。\n### 1.1 使用jstack确定CPU占用高的线程\\\n通过`top`指令,可以看到进程占用的一些基础资源信息,然后“P”键可以按照CPU使用率进行排序,“M”键可以按照内存占用情况进行排序\n\n找到CPU占用高的进程pid,然后将jstack信息定向到一个文件中去,通过`top -Hp pid`查看具体的情况。\n\n通过 `printf '%x\\n' pid`将pid转换为16进制,然后在jstack文件中根据对应的数字进行查找,然后针对性的进行分析\n### 1.2 频繁GC\n有时候我们可以先确定下gc是不是太频繁,使用`jstat -gc pid 1000`命令来对gc分代变化情况进行观察,1000表示采样间隔(ms),`S0C/S1C、S0U/S1U、EC/EU、OC/OU、MC/MU`分别代表两个Survivor区、Eden区、老年代、元数据区的容量和使用量。`YGC/YGT、FGC/FGCT、GCT`则代表YoungGc、FullGc的耗时和次数以及总耗时。如果看到gc比较频繁,再针对gc方面做进一步分析。\n![alt text](../images/gc.png)\n### 1.3 频繁上下文切换\n针对频繁上下文问题,我们可以使用vmstat命令来进行查看\n![alt text](../images/vmstat.png)\ncs(context switch)一列则代表了上下文切换的次数。\n\n如果我们希望对特定的pid进行监控那么可以使用 `pidstat -w pid`命令,cswch和nvcswch表示自愿及非自愿切换。\n\n## 2.内存\n对于JAVA应用,涉及到的内存问题主要包括OOM、GC问题和堆外内存。\n### 2.1 OOM\nJVM中的内存不足,OOM大致可以分为以下几种情况\n- `Exception in thread \"main\" java.lang.OutOfMemoryError: unable to create new native thread` 这个意思是没有足够的内存空间给线程分配java栈,基本上还是线程池代码写的有问题,比如说忘记shutdown,所以说应该首先从代码层面来寻找问题,使用jstack或者jmap。如果一切都正常,JVM方面可以通过指定Xss来减少单个thread stack的大小。另外也可以在系统层面,可以通过修改`/etc/security/limits.confnofile`和`nproc`来增大os对线程的限制\n- `Exception in thread \"main\" java.lang.OutOfMemoryError: Java heap space ` 这个意思是堆的内存占用已经达到-Xmx设置的最大值,应该是最常见的OOM错误了。解决思路仍然是先应该在代码中找,怀疑存在内存泄漏,通过jstack和jmap去定位问题。如果说一切都正常,才需要通过调整Xmx的值来扩大内存。\n- `Caused by: java.lang.OutOfMemoryError: Meta space` 这个意思是元数据区的内存占用已经达到`XX:MaxMetaspaceSize`设置的最大值,排查思路和上面的一致,参数方面可以通过`XX:MaxPermSize`来进行调整\n- `Exception in thread \"main\" java.lang.StackOverflowError` 表示线程栈需要的内存大于Xss值,同样也是先进行排查,参数方面通过Xss来调整,但调整的太大可能又会引起OOM。\n### 2.2 GC问题\ngc问题除了影响cpu也会影响内存,排查思路也是一致的。一般先使用jstat来查看分代变化情况,比如youngGC或者fullGC次数是不是太多呀;EU、OU等指标增长是不是异常等。\n\n线程的话太多而且不被及时gc也会引发oom,大部分就是之前说的`unable to create new native thread`。除了jstack细细分析dump文件外,我们一般先会看下总体线程,通过`pstreee -p pid |wc -l`\n### 2.3 堆外内存\nJVM 的堆外内存主要包括:\n- JVM 自身运行占用的空间;\n- 线程栈分配占用的系统内存;\n- DirectByteBuffer 占用的内存;\n- JNI 里分配的内存;\n- Java 8 开始的元数据空间;\n- NIO 缓存\n- Unsafe 调用分配的内存;\n- codecache\n\n冰山对象:冰山对象是指在 JVM 堆里占用的内存很小,但其实引用了一块很大的本地内存。DirectByteBuffer 和 线程都属于这类对象。\n#### 2.3.1NMT分析堆外内存\nNMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。\n\nNMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。\n\n#### 2.3.2 开启 NMT\n启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。\n启动命令: `-XX:NativeMemoryTracking=[off | summary | detail]`。\n- off:NMT 默认是关闭的;\n- summary:只收集子系统的内存使用的总计数据;\n- detail:收集每个调用点的内存使用数据。\n\n#### 2.3.3 jcmd 访问 NMT 数据\n命令:\n`jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]`\n\n","slug":"JAVA问题定位","published":1,"updated":"2024-08-17T15:11:14.404Z","comments":1,"layout":"post","photos":[],"_id":"cm00w1n8u0001p5on0ekjb9o1","content":"<span id=\"more\"></span>\n\n\n<h1 id=\"一、JAVA-相关命令\"><a href=\"#一、JAVA-相关命令\" class=\"headerlink\" title=\"一、JAVA 相关命令\"></a>一、JAVA 相关命令</h1><h2 id=\"1-jps\"><a href=\"#1-jps\" class=\"headerlink\" title=\"1.jps\"></a>1.jps</h2><p>jps - Lists the instrumented Java Virtual Machines (JVMs) on the target system. This command is experimental and unsupported.</p>\n<p>相关参数</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">OPTIONS</span><br><span class=\"line\"> The jps command supports a number of options that modify the output of the command. These options are subject to change or removal in the future.</span><br><span class=\"line\"> -q</span><br><span class=\"line\"> Suppresses the output of the class name, JAR file name, and arguments passed to the main method, producing only a list of local JVM identifiers.</span><br><span class=\"line\"> -m</span><br><span class=\"line\"> Displays the arguments passed to the main method. The output may be null for embedded JVMs.</span><br><span class=\"line\"> -l</span><br><span class=\"line\"> Displays the full package name for the application's main class or the full path name to the application's JAR file.</span><br><span class=\"line\"> -v</span><br><span class=\"line\"> Displays the arguments passed to the JVM.</span><br><span class=\"line\"> -V</span><br><span class=\"line\"> Suppresses the output of the class name, JAR file name, and arguments passed to the main method, producing only a list of local JVM identifiers.</span><br><span class=\"line\"> -Joption</span><br><span class=\"line\"> Passes option to the JVM, where option is one of the options described on the reference page for the Java application launcher. For example, -J-Xms48m sets the</span><br><span class=\"line\"> startup memory to 48 MB. See java(1).</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"2-jinfo\"><a href=\"#2-jinfo\" class=\"headerlink\" title=\"2.jinfo\"></a>2.jinfo</h2><p>jinfo(Java Virtual Machine Configuration Information)是JDK提供的一个可以实时查看Java虚拟机各种配置参数和系统属性的命令行工具。使用jps命令的-v参数可以查看Java虚拟机启动时显式指定的配置参数,如果想查看没有显式指定的配置参数就可以使用jinfo命令进行查看。另外,jinfo命令还可以查询Java虚拟机进程的System.getProperties()的内容。</p>\n<p>以tomcat进程为例</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Attaching to process ID 2045, please wait...</span><br><span class=\"line\">Debugger attached successfully.</span><br><span class=\"line\">Server compiler detected.</span><br><span class=\"line\">JVM version is 25.242-b08</span><br><span class=\"line\">Java System Properties:</span><br><span class=\"line\"></span><br><span class=\"line\">java.vendor = Huawei Technologies Co., Ltd</span><br><span class=\"line\">sun.java.launcher = SUN_STANDARD</span><br><span class=\"line\">catalina.base = /usr/share/tomcat</span><br><span class=\"line\">sun.management.compiler = HotSpot 64-Bit Tiered Compilers</span><br><span class=\"line\">sun.nio.ch.bugLevel = </span><br><span class=\"line\">catalina.useNaming = true</span><br><span class=\"line\">jnidispatch.path = /var/cache/tomcat/temp/jna--903012287/jna4240128671455089550.tmp</span><br><span class=\"line\">os.name = Linux</span><br><span class=\"line\">sun.boot.class.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/resources.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/rt.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/sunrsasign.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jsse.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jce.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/charsets.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jfr.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/classes</span><br><span class=\"line\">java.vm.specification.vendor = Oracle Corporation</span><br><span class=\"line\">java.runtime.version = 1.8.0_242-b08</span><br><span class=\"line\">jna.loaded = true</span><br><span class=\"line\">user.name = xxx</span><br><span class=\"line\">tomcat.util.scan.StandardJarScanFilter.jarsToScan = taglibs-standard-impl*.jar</span><br><span class=\"line\">shared.loader = </span><br><span class=\"line\">tomcat.util.buf.StringCache.byte.enabled = true</span><br><span class=\"line\">user.language = en</span><br><span class=\"line\">java.naming.factory.initial = org.apache.naming.java.javaURLContextFactory</span><br><span class=\"line\">sun.boot.library.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/amd64</span><br><span class=\"line\">java.version = 1.8.0_242</span><br><span class=\"line\">java.util.logging.manager = org.apache.juli.ClassLoaderLogManager</span><br><span class=\"line\">user.timezone = Asia/Shanghai</span><br><span class=\"line\">sun.arch.data.model = 64</span><br><span class=\"line\">java.util.concurrent.ForkJoinPool.common.threadFactory = org.apache.catalina.startup.SafeForkJoinWorkerThreadFactory</span><br><span class=\"line\">java.endorsed.dirs = </span><br><span class=\"line\">sun.cpu.isalist = </span><br><span class=\"line\">sun.jnu.encoding = UTF-8</span><br><span class=\"line\">file.encoding.pkg = sun.io</span><br><span class=\"line\">package.access = sun.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.tomcat.</span><br><span class=\"line\">file.separator = /</span><br><span class=\"line\">java.specification.name = Java Platform API Specification</span><br><span class=\"line\">java.class.version = 52.0</span><br><span class=\"line\">user.country = US</span><br><span class=\"line\">java.home = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre</span><br><span class=\"line\">java.vm.info = mixed mode</span><br><span class=\"line\">os.version = 4.19.90-24.4.v2101.ky10.x86_64</span><br><span class=\"line\">sun.font.fontmanager = sun.awt.X11FontManager</span><br><span class=\"line\">path.separator = :</span><br><span class=\"line\">java.vm.version = 25.242-b08</span><br><span class=\"line\">jboss.i18n.generate-proxies = true</span><br><span class=\"line\">java.awt.printerjob = sun.print.PSPrinterJob</span><br><span class=\"line\">sun.io.unicode.encoding = UnicodeLittle</span><br><span class=\"line\">awt.toolkit = sun.awt.X11.XToolkit</span><br><span class=\"line\">package.definition = sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.naming.,org.apache.tomcat.</span><br><span class=\"line\">java.naming.factory.url.pkgs = org.apache.naming</span><br><span class=\"line\">mail.mime.splitlongparameters = false</span><br><span class=\"line\">java.security.egd = file:/dev/./urandom</span><br><span class=\"line\">user.home = /home/shterm</span><br><span class=\"line\">java.specification.vendor = Oracle Corporation</span><br><span class=\"line\">tomcat.util.scan.StandardJarScanFilter.jarsToSkip = activ*.jar,amqp-client.jar,annotations-api.jar,ant-junit*.jar,ant-launcher.jar,ant.jar,antlr.jar,aopalliance.jar,asm-*.jar,aspectj*.jar,bcp*.jar,bootstrap.jar,catalina-ant.jar,catalina-ha.jar,catalina-jmx-remote.jar,catalina-storeconfig.jar,catalina-tribes.jar,catalina-ws.jar,catalina.jar,cglib-*.jar,classmate.jar,cobertura-*.jar,commons-*.jar,compress-lzf.jar,curator-*.jar,db2-jdbc.jar,dom4j-*.jar,easymock-*.jar,ecj-*.jar,el-api.jar,elasticsearch.jar,geronimo-spec-jaxrpc*.jar,groovy-all.jar,guava.jar,h2*.jar,hamcrest-*.jar,hibernate*.jar,hppc.jar,http*.jar,icu4j-*.jar,itext*.jar,jackson-*.jar,jandex.jar,jasper-el.jar,jasper.jar,jasperreports*.jar,jaspic-api.jar,javamail.jar,javassist.jar,jaxb-*.jar,jaxen*.jar,jboss*.jar,jc*.jar,jdom-*.jar,jedis.jar,jetty-*.jar,jfreechart.jar,jgit.jar,jline.jar,jmx-tools.jar,jmx.jar,jna.jar,joda-time.jar,jr-*.jar,jsch.jar,json*.jar,jsoup.jar,jsp-api.jar,jsr166e.jar,jstl.jar,jta*.jar,junit-*.jar,junit.jar,liquibase-*.jar,log4j*.jar,lucene*.jar,mail*.jar,mariadb-jdbc.jar,mssql-jdbc.jar,mybatis.jar,netty.jar,nmap4j.jar,objenesis*.jar,olap4j.jar,opc*.jar,oracle-jdbc.jar,oraclepki.jar,oro-*.jar,poi*.jar,postgresql-jdbc.jar,quartz.jar,servlet-api-*.jar,servlet-api.jar,slf4j*.jar,snakeyaml.jar,snmp4j.jar,spring*.jar,sshd-core.jar,taglibs-standard-spec-*.jar,tagsoup-*.jar,t-digest.jar,tomcat-api.jar,tomcat-coyote.jar,tomcat-dbcp.jar,tomcat-i18n-*.jar,tomcat-jdbc.jar,tomcat-jni.jar,tomcat-juli-adapters.jar,tomcat-juli.jar,tomcat-util-scan.jar,tomcat-util.jar,tomcat-websocket.jar,tools.jar,validation-api.jar,velocypack.jar,websocket-api.jar,wl*.jar,wsdl4j*.jar,xercesImpl.jar,xml-apis.jar,xmlbeans.jar,xmlParserAPIs-*.jar,xmlParserAPIs.jar,xom-*.jar,xz.jar,zip4j.jar,zookeeper.jar</span><br><span class=\"line\">java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib</span><br><span class=\"line\">java.vendor.url = http://jdk.rnd.huawei.com/</span><br><span class=\"line\">java.vm.vendor = Huawei Technologies Co., Ltd</span><br><span class=\"line\">common.loader = "${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"</span><br><span class=\"line\">java.runtime.name = OpenJDK Runtime Environment</span><br><span class=\"line\">sun.java.command = org.apache.catalina.startup.Bootstrap start</span><br><span class=\"line\">java.class.path = /usr/share/tomcat/bin/bootstrap.jar:/usr/share/tomcat/bin/tomcat-juli.jar:/usr/lib/java/commons-daemon.jar</span><br><span class=\"line\">java.vm.specification.name = Java Virtual Machine Specification</span><br><span class=\"line\">java.vm.specification.version = 1.8</span><br><span class=\"line\">catalina.home = /usr/share/tomcat</span><br><span class=\"line\">sun.cpu.endian = little</span><br><span class=\"line\">sun.os.patch.level = unknown</span><br><span class=\"line\">java.awt.headless = true</span><br><span class=\"line\">java.io.tmpdir = /var/cache/tomcat/temp</span><br><span class=\"line\">java.vendor.url.bug = http://jdk.rnd.huawei.com/</span><br><span class=\"line\">server.loader = </span><br><span class=\"line\">java.rmi.server.hostname = 127.0.0.1</span><br><span class=\"line\">jna.platform.library.path = /usr/lib64:/lib64:/usr/lib:/lib:/usr/lib64/tracker-miners-2.0:/usr/lib64/tracker-2.0:/usr/lib64/dyninst:/usr/libexec/sudo:/usr/lib64/sssd:/usr/pgsql-9.6/lib:/usr/lib64/perl5/CORE:/usr/lib64/opencryptoki:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/amd64/jli:/usr/lib64/bind9-export</span><br><span class=\"line\">os.arch = amd64</span><br><span class=\"line\">java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment</span><br><span class=\"line\">java.ext.dirs = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/ext:/usr/java/packages/lib/ext</span><br><span class=\"line\">user.dir = /usr/share/tomcat</span><br><span class=\"line\">line.separator = </span><br><span class=\"line\"></span><br><span class=\"line\">java.vm.name = OpenJDK 64-Bit Server VM</span><br><span class=\"line\">log4j.configurationFile = /etc/tomcat/log4j2.xml</span><br><span class=\"line\">file.encoding = UTF-8</span><br><span class=\"line\">com.sun.jndi.ldap.object.disableEndpointIdentification = </span><br><span class=\"line\">java.specification.version = 1.8</span><br><span class=\"line\"></span><br><span class=\"line\">VM Flags:</span><br><span class=\"line\">Non-default VM flags: -XX:CICompilerCount=4 -XX:GCLogFileSize=20971520 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=243269632 -XX:MaxHeapSize=1610612736 -XX:MaxNewSize=536870912 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=80740352 -XX:NumberOfGCLogFiles=15 -XX:OldSize=162529280 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseGCLogFileRotation -XX:+UseParallelGC </span><br><span class=\"line\">Command line: -Xmx1536m -Djava.security.egd=file:/dev/./urandom -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tomcat -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=15 -XX:GCLogFileSize=20m -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/var/log/tomcat/tomcat-gc-%t.log -Dcom.sun.jndi.ldap.object.disableEndpointIdentification -Dcatalina.base=/usr/share/tomcat -Dcatalina.home=/usr/share/tomcat -Djava.endorsed.dirs= -Djava.io.tmpdir=/var/cache/tomcat/temp -Dlog4j.configurationFile=/etc/tomcat/log4j2.xml -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"3-jstat\"><a href=\"#3-jstat\" class=\"headerlink\" title=\"3.jstat\"></a>3.jstat</h2><p>命令参数说明:</p>\n<ul>\n<li>generalOptions:通用选项,如果指定一个通用选项,就不能指定任何其他选项或参数。它包括如下两个选项:</li>\n<li>-help:显示帮助信息。</li>\n<li>-options:显示outputOptions参数的列表。</li>\n<li>outputOptions:输出选项,指定显示某一种Java虚拟机信息。</li>\n<li>-t:把时间戳列显示为输出的第一列。这个时间戳是从Java虚拟机的开始运行到现在的秒数。</li>\n<li>-h n:每显示n行显示一次表头,其中n为正整数。默认值为 0,即仅在第一行数据显示一次表头。</li>\n<li>vmid:虚拟机唯一ID(LVMID,Local Virtual Machine Identifier),如果查看本机就是Java进程的进程ID。</li>\n<li>interval:显示信息的时间间隔,单位默认毫秒。也可以指定秒为单位,比如:1s。如果指定了该参数,jstat命令将每隔这段时间显示一次统计信息。</li>\n<li>count:显示数据的次数,默认值是无穷大,这将导致jstat命令一直显示统计信息,直到目标JVM终止或jstat命令终止。<br>输出选项<br>如果不指定通用选项(generalOptions),则可以指定输出选项(outputOptions)。输出选项决定jstat命令显示的内容和格式,具体如下:</li>\n<li>-class:显示类加载、卸载数量、总空间和装载耗时的统计信息。</li>\n<li>-compiler:显示即时编译的方法、耗时等信息。</li>\n<li>-gc:显示堆各个区域内存使用和垃圾回收的统计信息。</li>\n<li>-gccapacity:显示堆各个区域的容量及其对应的空间的统计信息。</li>\n<li>-gcutil:显示有关垃圾收集统计信息的摘要。</li>\n<li>-gccause:显示关于垃圾收集统计信息的摘要(与-gcutil相同),以及最近和当前垃圾回收的原因。</li>\n<li>-gcnew:显示新生代的垃圾回收统计信息。</li>\n<li>-gcnewcapacity:显示新生代的大小及其对应的空间的统计信息。</li>\n<li>-gcold: 显示老年代和元空间的垃圾回收统计信息。</li>\n<li>-gcoldcapacity:显示老年代的大小统计信息。</li>\n<li>-gcmetacapacity:显示元空间的大小的统计信息。</li>\n<li>-printcompilation:显示即时编译方法的统计信息。</li>\n</ul>\n<h1 id=\"二、线程堆栈\"><a href=\"#二、线程堆栈\" class=\"headerlink\" title=\"二、线程堆栈\"></a>二、线程堆栈</h1><h2 id=\"1-输出\"><a href=\"#1-输出\" class=\"headerlink\" title=\"1.输出\"></a>1.输出</h2><p>Java虚拟机提供了线程转储(Thread dump)的后门,通过这个后门,可以将线程堆栈打印出来。这个后门就是通过向Java进程发送一个QUIT信号,Java虚拟机收到该信号之后,将系统当前的JAVA线程调用堆栈打印出来。</p>\n<p>打印方法:</p>\n<ul>\n<li>jstack -l pid > xxx.txt 需要注意的是,linux系统中往往会用不同的用户去执行不同的程序,此时可能需要通过sudu -u xxx jstack的形式</li>\n<li>kill -3<br><em><strong>同时请确保Java命令行中没有DISABLE_JAVADUMP运行选项</strong></em></li>\n</ul>\n<h2 id=\"2-线程分析\"><a href=\"#2-线程分析\" class=\"headerlink\" title=\"2.线程分析\"></a>2.线程分析</h2><p>通过输出堆栈进行分析 <code>jstack -l $(jps | grep xxx | awk '{print $1}')</code> > /tmp/xxx.jstack</p>\n<figure class=\"highlight lua\"><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><td class=\"code\"><pre><span class=\"line\"><span class=\"string\">"SYS_STATUS_CHECKER"</span> #<span class=\"number\">14</span> daemon prio=<span class=\"number\">5</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f5e047bf000</span> nid=<span class=\"number\">0xe15</span> waiting on condition [<span class=\"number\">0x00007f5dd43d1000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: TIMED_WAITING (sleeping)</span><br><span class=\"line\"> at java.lang.Thread.sleep(Native Method)</span><br><span class=\"line\">ru at com.xxx.xxx.SystemStatusChecker.run(SystemStatusChecker.java:xx)</span><br><span class=\"line\"> at java.lang.Thread.run(Thread.java:<span class=\"number\">748</span>) </span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\"> - None</span><br><span class=\"line\"> </span><br><span class=\"line\"><span class=\"string\">"RMI Reaper"</span> #<span class=\"number\">39</span> prio=<span class=\"number\">5</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f5e04e4c800</span> nid=<span class=\"number\">0xf0b</span> <span class=\"keyword\">in</span> Object.wait() [<span class=\"number\">0x00007f5dae2c4000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: WAITING (on object monitor)</span><br><span class=\"line\"> at java.lang.Object.wait(Native Method)</span><br><span class=\"line\"> - waiting on <<span class=\"number\">0x00000000c0c88d20</span>> (a java.lang.ref.ReferenceQueue$Lock)</span><br><span class=\"line\"> at java.lang.ref.ReferenceQueue.<span class=\"built_in\">remove</span>(ReferenceQueue.java:<span class=\"number\">144</span>)</span><br><span class=\"line\"> - locked <<span class=\"number\">0x00000000c0c88d20</span>> (a java.lang.ref.ReferenceQueue$Lock)</span><br><span class=\"line\"> at java.lang.ref.ReferenceQueue.<span class=\"built_in\">remove</span>(ReferenceQueue.java:<span class=\"number\">165</span>)</span><br><span class=\"line\"> at sun.rmi.transport.ObjectTable$Reaper.run(ObjectTable.java:<span class=\"number\">351</span>)</span><br><span class=\"line\"> at java.lang.Thread.run(Thread.java:<span class=\"number\">748</span>)</span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\"> - None</span><br><span class=\"line\"> </span><br><span class=\"line\"><span class=\"string\">"main"</span> #<span class=\"number\">1</span> prio=<span class=\"number\">5</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f5e0400a000</span> nid=<span class=\"number\">0xdcb</span> runnable [<span class=\"number\">0x00007f5e0b393000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"> at java.net.PlainSocketImpl.socketAccept(Native Method)</span><br><span class=\"line\"> at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:<span class=\"number\">409</span>)</span><br><span class=\"line\"> at java.net.ServerSocket.implAccept(ServerSocket.java:<span class=\"number\">545</span>)</span><br><span class=\"line\"> at java.net.ServerSocket.accept(ServerSocket.java:<span class=\"number\">513</span>)</span><br><span class=\"line\"> at com.xxx.common.xxx.await(CommonMain.java:<span class=\"number\">244</span>)</span><br><span class=\"line\"> at com.xxx.common.xxx.startup(CommonMain.java:<span class=\"number\">207</span>)</span><br><span class=\"line\"> at com.xxx.common.xxx.main(CommonMain.java:<span class=\"number\">147</span>)</span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\"> - None</span><br></pre></td></tr></table></figure>\n<p>在RMI线程中可以看到 “ - locked <0x00000000c0c88d20> (a java.lang.ref.ReferenceQueue$Lock)” 表示该线程已经使用了ID为”0x00000000c0c88d2”的锁,锁的ID由系统自动产生</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">"main" prio=5 os_prio=0 tid=0x00007f5e0400a000 nid=0xdcb runnable [0x00007f5e0b393000]</span><br><span class=\"line\">| | | | | | |</span><br><span class=\"line\">线程名称 线程优先级 操作系统级别的优先级 线程id 对应的本地线程ID 状态 线程占用内存地址</span><br></pre></td></tr></table></figure>\n\n<p>其中”线程对应的本地线程id号”所指的”本地线程”是指该Java线程所对应的虚拟机中的本地线程。我们知道Java是解析型语言,执行的实体是Java虚拟机,因此Java语言中的线程是 依附于Java虚拟机中的本地线程来运行的,实际上是本地线程在执行Java线程代码。</p>\n<p>Java代码 中创建一个thread,虚拟机在运行期就会创建一个对应的本地线程,而这个本地线程才是真正的线程实体。为了更加深入得理解本地线程和Java线程的关系,在Unix/Linux下,我们可以通 如下方式把Java虚拟机的本地线程打印出来:</p>\n<ul>\n<li>使用ps -ef | grep java 获得Java进程ID。</li>\n<li>使用pstack <java pid>获得Java虚拟机的本地线程的堆栈<br>其中本地线程各项含义如下:<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Thread 56 (Thread 0x7f5e0b394700 (LWP 3531))</span><br><span class=\"line\">| | |</span><br><span class=\"line\">| | +----本地线程id(另一种表示,LWP-light weight process)</span><br><span class=\"line\">| +-------------------本地线程id</span><br><span class=\"line\">+------------------------------线程名称</span><br></pre></td></tr></table></figure>\n而通过jstack输出的main本地线程ID为0xdcb,其10进制正好为3531。</li>\n</ul>\n<p>“runnable”表示当前线程处于运行状态。这个runnable状态是从虚拟机的角度来看的, 表示这个线程正在运行</p>\n<p><strong>⚠️ NOTE:</strong> 但是处于Runnable状态的线程不一定真的消耗CPU. 处于Runnable的线程只能说明该线程没有阻塞在java的wait或者sleep方法上,同时也没等待在锁上面。但是如果该线程调用了本地方法,而本地方法处于等待状态,这个时候虚拟机是不知道本地代码中发生 了什么(但操作系统是知道的,pstack就是操作提供的一个命令,它知道当前线程正在执行的本地代码上下文),此时尽管当前线程实际上也是阻塞的状态,但实际上显示出来的还是runnable状态, 这种情况下是不消耗CPU的</p>\n\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">1. 处于waittig和blocked状态的线程都不会消耗CPU </span><br><span class=\"line\">2. 线程频繁地挂起和唤醒需要消耗CPU, 而且代价颇大</span><br></pre></td></tr></table></figure>\n<ul>\n<li>TIMED_WAITING(on object monitor) 表示当前线程被挂起一段时间,说明该线程正在 执行obj.wait(int time)方法.</li>\n<li>TIMED_WAITING(sleeping) 表示当前线程被挂起一段时间,即正在执行Thread.sleep(int time)方法. </li>\n<li>TIMED_WAITING(parking) 当前线程被挂起一段时间,即正在执行Thread.sleep(int time)方法.</li>\n<li>WAINTING(on object monitor) 当前线程被挂起,即正在执行obj.wait()方法(无参数的wait()方法).<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">处于TIMED_WAITING、WAINTING状态的线程一定不消耗CPU. 处于RUNNABLE的线程,要结合当前线程代码的性质判断,是否消耗CPU.</span><br><span class=\"line\">• 如果是纯Java运算代码,则消耗CPU.</span><br><span class=\"line\">• 如果是网络IO,很少消耗CPU.</span><br><span class=\"line\">• 如果是本地代码,结合本地代码的性质判断(可以通过pstack/gstack获取本地线程堆栈), 如果是纯运算代码,则消耗CPU, 如果被挂起,则不消耗CPU,如果是IO,则不怎么消 耗CPU。</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h1 id=\"三、相关的排查方法\"><a href=\"#三、相关的排查方法\" class=\"headerlink\" title=\"三、相关的排查方法\"></a>三、相关的排查方法</h1><h2 id=\"1-CPU\"><a href=\"#1-CPU\" class=\"headerlink\" title=\"1.CPU\"></a>1.CPU</h2><p>生产环境中往往会出现CPU飙高的情况,对于JAVA应用而言,此类问题相对较好确定问题方向。</p>\n<h3 id=\"1-1-使用jstack确定CPU占用高的线程\"><a href=\"#1-1-使用jstack确定CPU占用高的线程\" class=\"headerlink\" title=\"1.1 使用jstack确定CPU占用高的线程\\\"></a>1.1 使用jstack确定CPU占用高的线程\\</h3><p>通过<code>top</code>指令,可以看到进程占用的一些基础资源信息,然后“P”键可以按照CPU使用率进行排序,“M”键可以按照内存占用情况进行排序</p>\n<p>找到CPU占用高的进程pid,然后将jstack信息定向到一个文件中去,通过<code>top -Hp pid</code>查看具体的情况。</p>\n<p>通过 <code>printf '%x\\n' pid</code>将pid转换为16进制,然后在jstack文件中根据对应的数字进行查找,然后针对性的进行分析</p>\n<h3 id=\"1-2-频繁GC\"><a href=\"#1-2-频繁GC\" class=\"headerlink\" title=\"1.2 频繁GC\"></a>1.2 频繁GC</h3><p>有时候我们可以先确定下gc是不是太频繁,使用<code>jstat -gc pid 1000</code>命令来对gc分代变化情况进行观察,1000表示采样间隔(ms),<code>S0C/S1C、S0U/S1U、EC/EU、OC/OU、MC/MU</code>分别代表两个Survivor区、Eden区、老年代、元数据区的容量和使用量。<code>YGC/YGT、FGC/FGCT、GCT</code>则代表YoungGc、FullGc的耗时和次数以及总耗时。如果看到gc比较频繁,再针对gc方面做进一步分析。<br><img src=\"/../images/gc.png\" alt=\"alt text\"></p>\n<h3 id=\"1-3-频繁上下文切换\"><a href=\"#1-3-频繁上下文切换\" class=\"headerlink\" title=\"1.3 频繁上下文切换\"></a>1.3 频繁上下文切换</h3><p>针对频繁上下文问题,我们可以使用vmstat命令来进行查看<br><img src=\"/../images/vmstat.png\" alt=\"alt text\"><br>cs(context switch)一列则代表了上下文切换的次数。</p>\n<p>如果我们希望对特定的pid进行监控那么可以使用 <code>pidstat -w pid</code>命令,cswch和nvcswch表示自愿及非自愿切换。</p>\n<h2 id=\"2-内存\"><a href=\"#2-内存\" class=\"headerlink\" title=\"2.内存\"></a>2.内存</h2><p>对于JAVA应用,涉及到的内存问题主要包括OOM、GC问题和堆外内存。</p>\n<h3 id=\"2-1-OOM\"><a href=\"#2-1-OOM\" class=\"headerlink\" title=\"2.1 OOM\"></a>2.1 OOM</h3><p>JVM中的内存不足,OOM大致可以分为以下几种情况</p>\n<ul>\n<li><code>Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread</code> 这个意思是没有足够的内存空间给线程分配java栈,基本上还是线程池代码写的有问题,比如说忘记shutdown,所以说应该首先从代码层面来寻找问题,使用jstack或者jmap。如果一切都正常,JVM方面可以通过指定Xss来减少单个thread stack的大小。另外也可以在系统层面,可以通过修改<code>/etc/security/limits.confnofile</code>和<code>nproc</code>来增大os对线程的限制</li>\n<li><code>Exception in thread "main" java.lang.OutOfMemoryError: Java heap space </code> 这个意思是堆的内存占用已经达到-Xmx设置的最大值,应该是最常见的OOM错误了。解决思路仍然是先应该在代码中找,怀疑存在内存泄漏,通过jstack和jmap去定位问题。如果说一切都正常,才需要通过调整Xmx的值来扩大内存。</li>\n<li><code>Caused by: java.lang.OutOfMemoryError: Meta space</code> 这个意思是元数据区的内存占用已经达到<code>XX:MaxMetaspaceSize</code>设置的最大值,排查思路和上面的一致,参数方面可以通过<code>XX:MaxPermSize</code>来进行调整</li>\n<li><code>Exception in thread "main" java.lang.StackOverflowError</code> 表示线程栈需要的内存大于Xss值,同样也是先进行排查,参数方面通过Xss来调整,但调整的太大可能又会引起OOM。</li>\n</ul>\n<h3 id=\"2-2-GC问题\"><a href=\"#2-2-GC问题\" class=\"headerlink\" title=\"2.2 GC问题\"></a>2.2 GC问题</h3><p>gc问题除了影响cpu也会影响内存,排查思路也是一致的。一般先使用jstat来查看分代变化情况,比如youngGC或者fullGC次数是不是太多呀;EU、OU等指标增长是不是异常等。</p>\n<p>线程的话太多而且不被及时gc也会引发oom,大部分就是之前说的<code>unable to create new native thread</code>。除了jstack细细分析dump文件外,我们一般先会看下总体线程,通过<code>pstreee -p pid |wc -l</code></p>\n<h3 id=\"2-3-堆外内存\"><a href=\"#2-3-堆外内存\" class=\"headerlink\" title=\"2.3 堆外内存\"></a>2.3 堆外内存</h3><p>JVM 的堆外内存主要包括:</p>\n<ul>\n<li>JVM 自身运行占用的空间;</li>\n<li>线程栈分配占用的系统内存;</li>\n<li>DirectByteBuffer 占用的内存;</li>\n<li>JNI 里分配的内存;</li>\n<li>Java 8 开始的元数据空间;</li>\n<li>NIO 缓存</li>\n<li>Unsafe 调用分配的内存;</li>\n<li>codecache</li>\n</ul>\n<p>冰山对象:冰山对象是指在 JVM 堆里占用的内存很小,但其实引用了一块很大的本地内存。DirectByteBuffer 和 线程都属于这类对象。</p>\n<h4 id=\"2-3-1NMT分析堆外内存\"><a href=\"#2-3-1NMT分析堆外内存\" class=\"headerlink\" title=\"2.3.1NMT分析堆外内存\"></a>2.3.1NMT分析堆外内存</h4><p>NMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。</p>\n<p>NMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。</p>\n<h4 id=\"2-3-2-开启-NMT\"><a href=\"#2-3-2-开启-NMT\" class=\"headerlink\" title=\"2.3.2 开启 NMT\"></a>2.3.2 开启 NMT</h4><p>启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。<br>启动命令: <code>-XX:NativeMemoryTracking=[off | summary | detail]</code>。</p>\n<ul>\n<li>off:NMT 默认是关闭的;</li>\n<li>summary:只收集子系统的内存使用的总计数据;</li>\n<li>detail:收集每个调用点的内存使用数据。</li>\n</ul>\n<h4 id=\"2-3-3-jcmd-访问-NMT-数据\"><a href=\"#2-3-3-jcmd-访问-NMT-数据\" class=\"headerlink\" title=\"2.3.3 jcmd 访问 NMT 数据\"></a>2.3.3 jcmd 访问 NMT 数据</h4><p>命令:<br><code>jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]</code></p>\n","excerpt":"","more":"<h1 id=\"一、JAVA-相关命令\"><a href=\"#一、JAVA-相关命令\" class=\"headerlink\" title=\"一、JAVA 相关命令\"></a>一、JAVA 相关命令</h1><h2 id=\"1-jps\"><a href=\"#1-jps\" class=\"headerlink\" title=\"1.jps\"></a>1.jps</h2><p>jps - Lists the instrumented Java Virtual Machines (JVMs) on the target system. This command is experimental and unsupported.</p>\n<p>相关参数</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">OPTIONS</span><br><span class=\"line\"> The jps command supports a number of options that modify the output of the command. These options are subject to change or removal in the future.</span><br><span class=\"line\"> -q</span><br><span class=\"line\"> Suppresses the output of the class name, JAR file name, and arguments passed to the main method, producing only a list of local JVM identifiers.</span><br><span class=\"line\"> -m</span><br><span class=\"line\"> Displays the arguments passed to the main method. The output may be null for embedded JVMs.</span><br><span class=\"line\"> -l</span><br><span class=\"line\"> Displays the full package name for the application's main class or the full path name to the application's JAR file.</span><br><span class=\"line\"> -v</span><br><span class=\"line\"> Displays the arguments passed to the JVM.</span><br><span class=\"line\"> -V</span><br><span class=\"line\"> Suppresses the output of the class name, JAR file name, and arguments passed to the main method, producing only a list of local JVM identifiers.</span><br><span class=\"line\"> -Joption</span><br><span class=\"line\"> Passes option to the JVM, where option is one of the options described on the reference page for the Java application launcher. For example, -J-Xms48m sets the</span><br><span class=\"line\"> startup memory to 48 MB. See java(1).</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"2-jinfo\"><a href=\"#2-jinfo\" class=\"headerlink\" title=\"2.jinfo\"></a>2.jinfo</h2><p>jinfo(Java Virtual Machine Configuration Information)是JDK提供的一个可以实时查看Java虚拟机各种配置参数和系统属性的命令行工具。使用jps命令的-v参数可以查看Java虚拟机启动时显式指定的配置参数,如果想查看没有显式指定的配置参数就可以使用jinfo命令进行查看。另外,jinfo命令还可以查询Java虚拟机进程的System.getProperties()的内容。</p>\n<p>以tomcat进程为例</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Attaching to process ID 2045, please wait...</span><br><span class=\"line\">Debugger attached successfully.</span><br><span class=\"line\">Server compiler detected.</span><br><span class=\"line\">JVM version is 25.242-b08</span><br><span class=\"line\">Java System Properties:</span><br><span class=\"line\"></span><br><span class=\"line\">java.vendor = Huawei Technologies Co., Ltd</span><br><span class=\"line\">sun.java.launcher = SUN_STANDARD</span><br><span class=\"line\">catalina.base = /usr/share/tomcat</span><br><span class=\"line\">sun.management.compiler = HotSpot 64-Bit Tiered Compilers</span><br><span class=\"line\">sun.nio.ch.bugLevel = </span><br><span class=\"line\">catalina.useNaming = true</span><br><span class=\"line\">jnidispatch.path = /var/cache/tomcat/temp/jna--903012287/jna4240128671455089550.tmp</span><br><span class=\"line\">os.name = Linux</span><br><span class=\"line\">sun.boot.class.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/resources.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/rt.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/sunrsasign.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jsse.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jce.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/charsets.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jfr.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/classes</span><br><span class=\"line\">java.vm.specification.vendor = Oracle Corporation</span><br><span class=\"line\">java.runtime.version = 1.8.0_242-b08</span><br><span class=\"line\">jna.loaded = true</span><br><span class=\"line\">user.name = xxx</span><br><span class=\"line\">tomcat.util.scan.StandardJarScanFilter.jarsToScan = taglibs-standard-impl*.jar</span><br><span class=\"line\">shared.loader = </span><br><span class=\"line\">tomcat.util.buf.StringCache.byte.enabled = true</span><br><span class=\"line\">user.language = en</span><br><span class=\"line\">java.naming.factory.initial = org.apache.naming.java.javaURLContextFactory</span><br><span class=\"line\">sun.boot.library.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/amd64</span><br><span class=\"line\">java.version = 1.8.0_242</span><br><span class=\"line\">java.util.logging.manager = org.apache.juli.ClassLoaderLogManager</span><br><span class=\"line\">user.timezone = Asia/Shanghai</span><br><span class=\"line\">sun.arch.data.model = 64</span><br><span class=\"line\">java.util.concurrent.ForkJoinPool.common.threadFactory = org.apache.catalina.startup.SafeForkJoinWorkerThreadFactory</span><br><span class=\"line\">java.endorsed.dirs = </span><br><span class=\"line\">sun.cpu.isalist = </span><br><span class=\"line\">sun.jnu.encoding = UTF-8</span><br><span class=\"line\">file.encoding.pkg = sun.io</span><br><span class=\"line\">package.access = sun.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.tomcat.</span><br><span class=\"line\">file.separator = /</span><br><span class=\"line\">java.specification.name = Java Platform API Specification</span><br><span class=\"line\">java.class.version = 52.0</span><br><span class=\"line\">user.country = US</span><br><span class=\"line\">java.home = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre</span><br><span class=\"line\">java.vm.info = mixed mode</span><br><span class=\"line\">os.version = 4.19.90-24.4.v2101.ky10.x86_64</span><br><span class=\"line\">sun.font.fontmanager = sun.awt.X11FontManager</span><br><span class=\"line\">path.separator = :</span><br><span class=\"line\">java.vm.version = 25.242-b08</span><br><span class=\"line\">jboss.i18n.generate-proxies = true</span><br><span class=\"line\">java.awt.printerjob = sun.print.PSPrinterJob</span><br><span class=\"line\">sun.io.unicode.encoding = UnicodeLittle</span><br><span class=\"line\">awt.toolkit = sun.awt.X11.XToolkit</span><br><span class=\"line\">package.definition = sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.naming.,org.apache.tomcat.</span><br><span class=\"line\">java.naming.factory.url.pkgs = org.apache.naming</span><br><span class=\"line\">mail.mime.splitlongparameters = false</span><br><span class=\"line\">java.security.egd = file:/dev/./urandom</span><br><span class=\"line\">user.home = /home/shterm</span><br><span class=\"line\">java.specification.vendor = Oracle Corporation</span><br><span class=\"line\">tomcat.util.scan.StandardJarScanFilter.jarsToSkip = activ*.jar,amqp-client.jar,annotations-api.jar,ant-junit*.jar,ant-launcher.jar,ant.jar,antlr.jar,aopalliance.jar,asm-*.jar,aspectj*.jar,bcp*.jar,bootstrap.jar,catalina-ant.jar,catalina-ha.jar,catalina-jmx-remote.jar,catalina-storeconfig.jar,catalina-tribes.jar,catalina-ws.jar,catalina.jar,cglib-*.jar,classmate.jar,cobertura-*.jar,commons-*.jar,compress-lzf.jar,curator-*.jar,db2-jdbc.jar,dom4j-*.jar,easymock-*.jar,ecj-*.jar,el-api.jar,elasticsearch.jar,geronimo-spec-jaxrpc*.jar,groovy-all.jar,guava.jar,h2*.jar,hamcrest-*.jar,hibernate*.jar,hppc.jar,http*.jar,icu4j-*.jar,itext*.jar,jackson-*.jar,jandex.jar,jasper-el.jar,jasper.jar,jasperreports*.jar,jaspic-api.jar,javamail.jar,javassist.jar,jaxb-*.jar,jaxen*.jar,jboss*.jar,jc*.jar,jdom-*.jar,jedis.jar,jetty-*.jar,jfreechart.jar,jgit.jar,jline.jar,jmx-tools.jar,jmx.jar,jna.jar,joda-time.jar,jr-*.jar,jsch.jar,json*.jar,jsoup.jar,jsp-api.jar,jsr166e.jar,jstl.jar,jta*.jar,junit-*.jar,junit.jar,liquibase-*.jar,log4j*.jar,lucene*.jar,mail*.jar,mariadb-jdbc.jar,mssql-jdbc.jar,mybatis.jar,netty.jar,nmap4j.jar,objenesis*.jar,olap4j.jar,opc*.jar,oracle-jdbc.jar,oraclepki.jar,oro-*.jar,poi*.jar,postgresql-jdbc.jar,quartz.jar,servlet-api-*.jar,servlet-api.jar,slf4j*.jar,snakeyaml.jar,snmp4j.jar,spring*.jar,sshd-core.jar,taglibs-standard-spec-*.jar,tagsoup-*.jar,t-digest.jar,tomcat-api.jar,tomcat-coyote.jar,tomcat-dbcp.jar,tomcat-i18n-*.jar,tomcat-jdbc.jar,tomcat-jni.jar,tomcat-juli-adapters.jar,tomcat-juli.jar,tomcat-util-scan.jar,tomcat-util.jar,tomcat-websocket.jar,tools.jar,validation-api.jar,velocypack.jar,websocket-api.jar,wl*.jar,wsdl4j*.jar,xercesImpl.jar,xml-apis.jar,xmlbeans.jar,xmlParserAPIs-*.jar,xmlParserAPIs.jar,xom-*.jar,xz.jar,zip4j.jar,zookeeper.jar</span><br><span class=\"line\">java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib</span><br><span class=\"line\">java.vendor.url = http://jdk.rnd.huawei.com/</span><br><span class=\"line\">java.vm.vendor = Huawei Technologies Co., Ltd</span><br><span class=\"line\">common.loader = "${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"</span><br><span class=\"line\">java.runtime.name = OpenJDK Runtime Environment</span><br><span class=\"line\">sun.java.command = org.apache.catalina.startup.Bootstrap start</span><br><span class=\"line\">java.class.path = /usr/share/tomcat/bin/bootstrap.jar:/usr/share/tomcat/bin/tomcat-juli.jar:/usr/lib/java/commons-daemon.jar</span><br><span class=\"line\">java.vm.specification.name = Java Virtual Machine Specification</span><br><span class=\"line\">java.vm.specification.version = 1.8</span><br><span class=\"line\">catalina.home = /usr/share/tomcat</span><br><span class=\"line\">sun.cpu.endian = little</span><br><span class=\"line\">sun.os.patch.level = unknown</span><br><span class=\"line\">java.awt.headless = true</span><br><span class=\"line\">java.io.tmpdir = /var/cache/tomcat/temp</span><br><span class=\"line\">java.vendor.url.bug = http://jdk.rnd.huawei.com/</span><br><span class=\"line\">server.loader = </span><br><span class=\"line\">java.rmi.server.hostname = 127.0.0.1</span><br><span class=\"line\">jna.platform.library.path = /usr/lib64:/lib64:/usr/lib:/lib:/usr/lib64/tracker-miners-2.0:/usr/lib64/tracker-2.0:/usr/lib64/dyninst:/usr/libexec/sudo:/usr/lib64/sssd:/usr/pgsql-9.6/lib:/usr/lib64/perl5/CORE:/usr/lib64/opencryptoki:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/amd64/jli:/usr/lib64/bind9-export</span><br><span class=\"line\">os.arch = amd64</span><br><span class=\"line\">java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment</span><br><span class=\"line\">java.ext.dirs = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/ext:/usr/java/packages/lib/ext</span><br><span class=\"line\">user.dir = /usr/share/tomcat</span><br><span class=\"line\">line.separator = </span><br><span class=\"line\"></span><br><span class=\"line\">java.vm.name = OpenJDK 64-Bit Server VM</span><br><span class=\"line\">log4j.configurationFile = /etc/tomcat/log4j2.xml</span><br><span class=\"line\">file.encoding = UTF-8</span><br><span class=\"line\">com.sun.jndi.ldap.object.disableEndpointIdentification = </span><br><span class=\"line\">java.specification.version = 1.8</span><br><span class=\"line\"></span><br><span class=\"line\">VM Flags:</span><br><span class=\"line\">Non-default VM flags: -XX:CICompilerCount=4 -XX:GCLogFileSize=20971520 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=243269632 -XX:MaxHeapSize=1610612736 -XX:MaxNewSize=536870912 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=80740352 -XX:NumberOfGCLogFiles=15 -XX:OldSize=162529280 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseGCLogFileRotation -XX:+UseParallelGC </span><br><span class=\"line\">Command line: -Xmx1536m -Djava.security.egd=file:/dev/./urandom -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tomcat -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=15 -XX:GCLogFileSize=20m -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/var/log/tomcat/tomcat-gc-%t.log -Dcom.sun.jndi.ldap.object.disableEndpointIdentification -Dcatalina.base=/usr/share/tomcat -Dcatalina.home=/usr/share/tomcat -Djava.endorsed.dirs= -Djava.io.tmpdir=/var/cache/tomcat/temp -Dlog4j.configurationFile=/etc/tomcat/log4j2.xml -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"3-jstat\"><a href=\"#3-jstat\" class=\"headerlink\" title=\"3.jstat\"></a>3.jstat</h2><p>命令参数说明:</p>\n<ul>\n<li>generalOptions:通用选项,如果指定一个通用选项,就不能指定任何其他选项或参数。它包括如下两个选项:</li>\n<li>-help:显示帮助信息。</li>\n<li>-options:显示outputOptions参数的列表。</li>\n<li>outputOptions:输出选项,指定显示某一种Java虚拟机信息。</li>\n<li>-t:把时间戳列显示为输出的第一列。这个时间戳是从Java虚拟机的开始运行到现在的秒数。</li>\n<li>-h n:每显示n行显示一次表头,其中n为正整数。默认值为 0,即仅在第一行数据显示一次表头。</li>\n<li>vmid:虚拟机唯一ID(LVMID,Local Virtual Machine Identifier),如果查看本机就是Java进程的进程ID。</li>\n<li>interval:显示信息的时间间隔,单位默认毫秒。也可以指定秒为单位,比如:1s。如果指定了该参数,jstat命令将每隔这段时间显示一次统计信息。</li>\n<li>count:显示数据的次数,默认值是无穷大,这将导致jstat命令一直显示统计信息,直到目标JVM终止或jstat命令终止。<br>输出选项<br>如果不指定通用选项(generalOptions),则可以指定输出选项(outputOptions)。输出选项决定jstat命令显示的内容和格式,具体如下:</li>\n<li>-class:显示类加载、卸载数量、总空间和装载耗时的统计信息。</li>\n<li>-compiler:显示即时编译的方法、耗时等信息。</li>\n<li>-gc:显示堆各个区域内存使用和垃圾回收的统计信息。</li>\n<li>-gccapacity:显示堆各个区域的容量及其对应的空间的统计信息。</li>\n<li>-gcutil:显示有关垃圾收集统计信息的摘要。</li>\n<li>-gccause:显示关于垃圾收集统计信息的摘要(与-gcutil相同),以及最近和当前垃圾回收的原因。</li>\n<li>-gcnew:显示新生代的垃圾回收统计信息。</li>\n<li>-gcnewcapacity:显示新生代的大小及其对应的空间的统计信息。</li>\n<li>-gcold: 显示老年代和元空间的垃圾回收统计信息。</li>\n<li>-gcoldcapacity:显示老年代的大小统计信息。</li>\n<li>-gcmetacapacity:显示元空间的大小的统计信息。</li>\n<li>-printcompilation:显示即时编译方法的统计信息。</li>\n</ul>\n<h1 id=\"二、线程堆栈\"><a href=\"#二、线程堆栈\" class=\"headerlink\" title=\"二、线程堆栈\"></a>二、线程堆栈</h1><h2 id=\"1-输出\"><a href=\"#1-输出\" class=\"headerlink\" title=\"1.输出\"></a>1.输出</h2><p>Java虚拟机提供了线程转储(Thread dump)的后门,通过这个后门,可以将线程堆栈打印出来。这个后门就是通过向Java进程发送一个QUIT信号,Java虚拟机收到该信号之后,将系统当前的JAVA线程调用堆栈打印出来。</p>\n<p>打印方法:</p>\n<ul>\n<li>jstack -l pid > xxx.txt 需要注意的是,linux系统中往往会用不同的用户去执行不同的程序,此时可能需要通过sudu -u xxx jstack的形式</li>\n<li>kill -3<br><em><strong>同时请确保Java命令行中没有DISABLE_JAVADUMP运行选项</strong></em></li>\n</ul>\n<h2 id=\"2-线程分析\"><a href=\"#2-线程分析\" class=\"headerlink\" title=\"2.线程分析\"></a>2.线程分析</h2><p>通过输出堆栈进行分析 <code>jstack -l $(jps | grep xxx | awk '{print $1}')</code> > /tmp/xxx.jstack</p>\n<figure class=\"highlight lua\"><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><td class=\"code\"><pre><span class=\"line\"><span class=\"string\">"SYS_STATUS_CHECKER"</span> #<span class=\"number\">14</span> daemon prio=<span class=\"number\">5</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f5e047bf000</span> nid=<span class=\"number\">0xe15</span> waiting on condition [<span class=\"number\">0x00007f5dd43d1000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: TIMED_WAITING (sleeping)</span><br><span class=\"line\"> at java.lang.Thread.sleep(Native Method)</span><br><span class=\"line\">ru at com.xxx.xxx.SystemStatusChecker.run(SystemStatusChecker.java:xx)</span><br><span class=\"line\"> at java.lang.Thread.run(Thread.java:<span class=\"number\">748</span>) </span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\"> - None</span><br><span class=\"line\"> </span><br><span class=\"line\"><span class=\"string\">"RMI Reaper"</span> #<span class=\"number\">39</span> prio=<span class=\"number\">5</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f5e04e4c800</span> nid=<span class=\"number\">0xf0b</span> <span class=\"keyword\">in</span> Object.wait() [<span class=\"number\">0x00007f5dae2c4000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: WAITING (on object monitor)</span><br><span class=\"line\"> at java.lang.Object.wait(Native Method)</span><br><span class=\"line\"> - waiting on <<span class=\"number\">0x00000000c0c88d20</span>> (a java.lang.ref.ReferenceQueue$Lock)</span><br><span class=\"line\"> at java.lang.ref.ReferenceQueue.<span class=\"built_in\">remove</span>(ReferenceQueue.java:<span class=\"number\">144</span>)</span><br><span class=\"line\"> - locked <<span class=\"number\">0x00000000c0c88d20</span>> (a java.lang.ref.ReferenceQueue$Lock)</span><br><span class=\"line\"> at java.lang.ref.ReferenceQueue.<span class=\"built_in\">remove</span>(ReferenceQueue.java:<span class=\"number\">165</span>)</span><br><span class=\"line\"> at sun.rmi.transport.ObjectTable$Reaper.run(ObjectTable.java:<span class=\"number\">351</span>)</span><br><span class=\"line\"> at java.lang.Thread.run(Thread.java:<span class=\"number\">748</span>)</span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\"> - None</span><br><span class=\"line\"> </span><br><span class=\"line\"><span class=\"string\">"main"</span> #<span class=\"number\">1</span> prio=<span class=\"number\">5</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f5e0400a000</span> nid=<span class=\"number\">0xdcb</span> runnable [<span class=\"number\">0x00007f5e0b393000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"> at java.net.PlainSocketImpl.socketAccept(Native Method)</span><br><span class=\"line\"> at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:<span class=\"number\">409</span>)</span><br><span class=\"line\"> at java.net.ServerSocket.implAccept(ServerSocket.java:<span class=\"number\">545</span>)</span><br><span class=\"line\"> at java.net.ServerSocket.accept(ServerSocket.java:<span class=\"number\">513</span>)</span><br><span class=\"line\"> at com.xxx.common.xxx.await(CommonMain.java:<span class=\"number\">244</span>)</span><br><span class=\"line\"> at com.xxx.common.xxx.startup(CommonMain.java:<span class=\"number\">207</span>)</span><br><span class=\"line\"> at com.xxx.common.xxx.main(CommonMain.java:<span class=\"number\">147</span>)</span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\"> - None</span><br></pre></td></tr></table></figure>\n<p>在RMI线程中可以看到 “ - locked <0x00000000c0c88d20> (a java.lang.ref.ReferenceQueue$Lock)” 表示该线程已经使用了ID为”0x00000000c0c88d2”的锁,锁的ID由系统自动产生</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">"main" prio=5 os_prio=0 tid=0x00007f5e0400a000 nid=0xdcb runnable [0x00007f5e0b393000]</span><br><span class=\"line\">| | | | | | |</span><br><span class=\"line\">线程名称 线程优先级 操作系统级别的优先级 线程id 对应的本地线程ID 状态 线程占用内存地址</span><br></pre></td></tr></table></figure>\n\n<p>其中”线程对应的本地线程id号”所指的”本地线程”是指该Java线程所对应的虚拟机中的本地线程。我们知道Java是解析型语言,执行的实体是Java虚拟机,因此Java语言中的线程是 依附于Java虚拟机中的本地线程来运行的,实际上是本地线程在执行Java线程代码。</p>\n<p>Java代码 中创建一个thread,虚拟机在运行期就会创建一个对应的本地线程,而这个本地线程才是真正的线程实体。为了更加深入得理解本地线程和Java线程的关系,在Unix/Linux下,我们可以通 如下方式把Java虚拟机的本地线程打印出来:</p>\n<ul>\n<li>使用ps -ef | grep java 获得Java进程ID。</li>\n<li>使用pstack <java pid>获得Java虚拟机的本地线程的堆栈<br>其中本地线程各项含义如下:<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Thread 56 (Thread 0x7f5e0b394700 (LWP 3531))</span><br><span class=\"line\">| | |</span><br><span class=\"line\">| | +----本地线程id(另一种表示,LWP-light weight process)</span><br><span class=\"line\">| +-------------------本地线程id</span><br><span class=\"line\">+------------------------------线程名称</span><br></pre></td></tr></table></figure>\n而通过jstack输出的main本地线程ID为0xdcb,其10进制正好为3531。</li>\n</ul>\n<p>“runnable”表示当前线程处于运行状态。这个runnable状态是从虚拟机的角度来看的, 表示这个线程正在运行</p>\n<p><strong>⚠️ NOTE:</strong> 但是处于Runnable状态的线程不一定真的消耗CPU. 处于Runnable的线程只能说明该线程没有阻塞在java的wait或者sleep方法上,同时也没等待在锁上面。但是如果该线程调用了本地方法,而本地方法处于等待状态,这个时候虚拟机是不知道本地代码中发生 了什么(但操作系统是知道的,pstack就是操作提供的一个命令,它知道当前线程正在执行的本地代码上下文),此时尽管当前线程实际上也是阻塞的状态,但实际上显示出来的还是runnable状态, 这种情况下是不消耗CPU的</p>\n\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">1. 处于waittig和blocked状态的线程都不会消耗CPU </span><br><span class=\"line\">2. 线程频繁地挂起和唤醒需要消耗CPU, 而且代价颇大</span><br></pre></td></tr></table></figure>\n<ul>\n<li>TIMED_WAITING(on object monitor) 表示当前线程被挂起一段时间,说明该线程正在 执行obj.wait(int time)方法.</li>\n<li>TIMED_WAITING(sleeping) 表示当前线程被挂起一段时间,即正在执行Thread.sleep(int time)方法. </li>\n<li>TIMED_WAITING(parking) 当前线程被挂起一段时间,即正在执行Thread.sleep(int time)方法.</li>\n<li>WAINTING(on object monitor) 当前线程被挂起,即正在执行obj.wait()方法(无参数的wait()方法).<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">处于TIMED_WAITING、WAINTING状态的线程一定不消耗CPU. 处于RUNNABLE的线程,要结合当前线程代码的性质判断,是否消耗CPU.</span><br><span class=\"line\">• 如果是纯Java运算代码,则消耗CPU.</span><br><span class=\"line\">• 如果是网络IO,很少消耗CPU.</span><br><span class=\"line\">• 如果是本地代码,结合本地代码的性质判断(可以通过pstack/gstack获取本地线程堆栈), 如果是纯运算代码,则消耗CPU, 如果被挂起,则不消耗CPU,如果是IO,则不怎么消 耗CPU。</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h1 id=\"三、相关的排查方法\"><a href=\"#三、相关的排查方法\" class=\"headerlink\" title=\"三、相关的排查方法\"></a>三、相关的排查方法</h1><h2 id=\"1-CPU\"><a href=\"#1-CPU\" class=\"headerlink\" title=\"1.CPU\"></a>1.CPU</h2><p>生产环境中往往会出现CPU飙高的情况,对于JAVA应用而言,此类问题相对较好确定问题方向。</p>\n<h3 id=\"1-1-使用jstack确定CPU占用高的线程\"><a href=\"#1-1-使用jstack确定CPU占用高的线程\" class=\"headerlink\" title=\"1.1 使用jstack确定CPU占用高的线程\\\"></a>1.1 使用jstack确定CPU占用高的线程\\</h3><p>通过<code>top</code>指令,可以看到进程占用的一些基础资源信息,然后“P”键可以按照CPU使用率进行排序,“M”键可以按照内存占用情况进行排序</p>\n<p>找到CPU占用高的进程pid,然后将jstack信息定向到一个文件中去,通过<code>top -Hp pid</code>查看具体的情况。</p>\n<p>通过 <code>printf '%x\\n' pid</code>将pid转换为16进制,然后在jstack文件中根据对应的数字进行查找,然后针对性的进行分析</p>\n<h3 id=\"1-2-频繁GC\"><a href=\"#1-2-频繁GC\" class=\"headerlink\" title=\"1.2 频繁GC\"></a>1.2 频繁GC</h3><p>有时候我们可以先确定下gc是不是太频繁,使用<code>jstat -gc pid 1000</code>命令来对gc分代变化情况进行观察,1000表示采样间隔(ms),<code>S0C/S1C、S0U/S1U、EC/EU、OC/OU、MC/MU</code>分别代表两个Survivor区、Eden区、老年代、元数据区的容量和使用量。<code>YGC/YGT、FGC/FGCT、GCT</code>则代表YoungGc、FullGc的耗时和次数以及总耗时。如果看到gc比较频繁,再针对gc方面做进一步分析。<br><img src=\"/../images/gc.png\" alt=\"alt text\"></p>\n<h3 id=\"1-3-频繁上下文切换\"><a href=\"#1-3-频繁上下文切换\" class=\"headerlink\" title=\"1.3 频繁上下文切换\"></a>1.3 频繁上下文切换</h3><p>针对频繁上下文问题,我们可以使用vmstat命令来进行查看<br><img src=\"/../images/vmstat.png\" alt=\"alt text\"><br>cs(context switch)一列则代表了上下文切换的次数。</p>\n<p>如果我们希望对特定的pid进行监控那么可以使用 <code>pidstat -w pid</code>命令,cswch和nvcswch表示自愿及非自愿切换。</p>\n<h2 id=\"2-内存\"><a href=\"#2-内存\" class=\"headerlink\" title=\"2.内存\"></a>2.内存</h2><p>对于JAVA应用,涉及到的内存问题主要包括OOM、GC问题和堆外内存。</p>\n<h3 id=\"2-1-OOM\"><a href=\"#2-1-OOM\" class=\"headerlink\" title=\"2.1 OOM\"></a>2.1 OOM</h3><p>JVM中的内存不足,OOM大致可以分为以下几种情况</p>\n<ul>\n<li><code>Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread</code> 这个意思是没有足够的内存空间给线程分配java栈,基本上还是线程池代码写的有问题,比如说忘记shutdown,所以说应该首先从代码层面来寻找问题,使用jstack或者jmap。如果一切都正常,JVM方面可以通过指定Xss来减少单个thread stack的大小。另外也可以在系统层面,可以通过修改<code>/etc/security/limits.confnofile</code>和<code>nproc</code>来增大os对线程的限制</li>\n<li><code>Exception in thread "main" java.lang.OutOfMemoryError: Java heap space </code> 这个意思是堆的内存占用已经达到-Xmx设置的最大值,应该是最常见的OOM错误了。解决思路仍然是先应该在代码中找,怀疑存在内存泄漏,通过jstack和jmap去定位问题。如果说一切都正常,才需要通过调整Xmx的值来扩大内存。</li>\n<li><code>Caused by: java.lang.OutOfMemoryError: Meta space</code> 这个意思是元数据区的内存占用已经达到<code>XX:MaxMetaspaceSize</code>设置的最大值,排查思路和上面的一致,参数方面可以通过<code>XX:MaxPermSize</code>来进行调整</li>\n<li><code>Exception in thread "main" java.lang.StackOverflowError</code> 表示线程栈需要的内存大于Xss值,同样也是先进行排查,参数方面通过Xss来调整,但调整的太大可能又会引起OOM。</li>\n</ul>\n<h3 id=\"2-2-GC问题\"><a href=\"#2-2-GC问题\" class=\"headerlink\" title=\"2.2 GC问题\"></a>2.2 GC问题</h3><p>gc问题除了影响cpu也会影响内存,排查思路也是一致的。一般先使用jstat来查看分代变化情况,比如youngGC或者fullGC次数是不是太多呀;EU、OU等指标增长是不是异常等。</p>\n<p>线程的话太多而且不被及时gc也会引发oom,大部分就是之前说的<code>unable to create new native thread</code>。除了jstack细细分析dump文件外,我们一般先会看下总体线程,通过<code>pstreee -p pid |wc -l</code></p>\n<h3 id=\"2-3-堆外内存\"><a href=\"#2-3-堆外内存\" class=\"headerlink\" title=\"2.3 堆外内存\"></a>2.3 堆外内存</h3><p>JVM 的堆外内存主要包括:</p>\n<ul>\n<li>JVM 自身运行占用的空间;</li>\n<li>线程栈分配占用的系统内存;</li>\n<li>DirectByteBuffer 占用的内存;</li>\n<li>JNI 里分配的内存;</li>\n<li>Java 8 开始的元数据空间;</li>\n<li>NIO 缓存</li>\n<li>Unsafe 调用分配的内存;</li>\n<li>codecache</li>\n</ul>\n<p>冰山对象:冰山对象是指在 JVM 堆里占用的内存很小,但其实引用了一块很大的本地内存。DirectByteBuffer 和 线程都属于这类对象。</p>\n<h4 id=\"2-3-1NMT分析堆外内存\"><a href=\"#2-3-1NMT分析堆外内存\" class=\"headerlink\" title=\"2.3.1NMT分析堆外内存\"></a>2.3.1NMT分析堆外内存</h4><p>NMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。</p>\n<p>NMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。</p>\n<h4 id=\"2-3-2-开启-NMT\"><a href=\"#2-3-2-开启-NMT\" class=\"headerlink\" title=\"2.3.2 开启 NMT\"></a>2.3.2 开启 NMT</h4><p>启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。<br>启动命令: <code>-XX:NativeMemoryTracking=[off | summary | detail]</code>。</p>\n<ul>\n<li>off:NMT 默认是关闭的;</li>\n<li>summary:只收集子系统的内存使用的总计数据;</li>\n<li>detail:收集每个调用点的内存使用数据。</li>\n</ul>\n<h4 id=\"2-3-3-jcmd-访问-NMT-数据\"><a href=\"#2-3-3-jcmd-访问-NMT-数据\" class=\"headerlink\" title=\"2.3.3 jcmd 访问 NMT 数据\"></a>2.3.3 jcmd 访问 NMT 数据</h4><p>命令:<br><code>jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]</code></p>"},{"title":"线上故障排查方法和工具介绍","author":"baixiaozhou","description":"线上故障问题的排查方法和工具介绍,包括 CPU、内存、负载、磁盘、IO、网络等","date":"2024-08-15T03:12:21.000Z","references":["[Examining Load Average](https://www.linuxjournal.com/article/9001)","[What-is-CPU-Load-Average](https://community.tenable.com/s/article/What-is-CPU-Load-Average)"],"cover":"/images/server.jpg","banner":"/images/server.jpg","repo":"baixiaozhou/SysStress","_content":"\n<!-- more -->\n\n{% quot 参考文档 %}\n\n[Examining Load Average](https://www.linuxjournal.com/article/9001)\n[What-is-CPU-Load-Average](https://community.tenable.com/s/article/What-is-CPU-Load-Average)\n[Brendan Gregg个人网站](www.brendangregg.com)\n\n\n## 写在前面\n\n在很多文章中,每当提到去解决线上问题的时候,大部分的处理方式就是登录环境,哐哐各种敲命令。操作本身没什么问题,但是对于很多人而言,我觉得这种做法其实是本末倒置的,过于在乎去快速抓住重点问题,而忽略了从全局去看问题。那么如果最开始不去操作各种命令,那应该干什么呢?\n\n***看监控!!!!***\n\n首先不要觉得这个是废话,对于很多场景来说,业务规模是不断变化的,有的时候并发超过了极限的性能,那么这种情况下都没有必要去后台进行各种查询。举个简单的例子,假如说某套业务系统,本身只能支持 500 并发,现在实际上的量到了 2000,导致线上各种内存、CPU、负载的告警,这种情况下还有必要去后台敲`top`、`free`吗?答案当然是否定的,这种情况下,就需要考虑对业务系统进行快速的扩容等。\n\n看监控的意义在于尽可能的找到更多的性能瓶颈或者异常的点,从全局出发,对系统当前存在的问题和异常点有全面的了解。\n\n监控系统多种多样,从较早的 zabbix 到现在比较流行的prometheus+grafana(举两个常用的例子),对于系统业务都有比较完善的监控,可以帮助我们更加具体的了解到系统运行全貌。如果你对这些都不喜欢,那么你自己写一个监控系统也没什么问题。\n\n当我们看完监控之后(假设你真的看了),接下来进入实际操作环节,我会从这些指标的详细含义出发,然后尽可能地将各种处理方式分享给大家。\n\n## Linux性能谱图\n\n在分析问题前,我们首先需要明确 Linux 有哪些性能分析工具,我们先上一下LINUX 性能专家 Brendan Gregg 总结的图(大家如果对性能分析等感兴趣的话,可以认真看下这位大佬的个人网站):\n\n{% image https://www.brendangregg.com/Perf/linux_observability_tools.png Linux Performance Observability Tool fancybox:true %}\n\n上面这张图是引用大佬文章的图,原文链接在这里: https://www.brendangregg.com/linuxperf.html\n\n## CPU使用率飙升\n\n### 如何让CPU使用率飙升\n\n这个问题其实很简单,只要有计算任务一直存在,让 CPU 一直处于繁忙之中,那么 CPU 必然飙升。我们可以通过一系列的工具去模拟这个情况。\n\n[github SysStress](https://github.com/baixiaozhou/SysStress) 这是我自己用 golang 写的压测工具(还在开发中,可以点个 star 让我更有动力😂)\n\n使用方法:\n```\n./sysstress cpu --cpu-number 10 --duration 10m\n```\n这个就是模拟占用 10 核心的 CPU 并持续 10min,当然大家也可以用其他的压测工具,比如`stress-ng`\n\n### 如何判断和发现CPU使用率飙升\n\n首先我们先看一下,跟 CPU 使用率相关的有哪些指标。我们通过 `top` 命令就可以看到具体的信息\n\n{% image /images/top.png top fancybox:true %}\n<!-- ![top](../images/top.png) -->\n这些输出中有一行是 `%Cpu(s)`, 这行展示了 CPU 的整体使用情况,是一个百分比的形式,我们详细阐述下这几个字段的含义\n```\nus, user : time running un-niced user processes 未降低优先级的用户进程所占用的时间\nsy, system : time running kernel processes 内核进程所占用的时间\nni, nice : time running niced user processes 降低优先级的用户进程所占用的时间\nid, idle : time spent in the kernel idle handler 空闲的时间\nwa, IO-wait : time waiting for I/O completion 等待 I/O 操作完成所花费的时间\nhi : time spent servicing hardware interrupts 处理硬件中断所花费的时间\nsi : time spent servicing software interrupts 处理软件中断所花费的时间\nst : time stolen from this vm by the hypervisor 被虚拟机管理程序从此虚拟机中窃取的时间\n```\n在这些指标中,一般关注的比较多的就是 us、sy、id、wa(其他几个指标很高的情况我个人目前基本上没有遇到过)\n\n上述指标反映了系统整体的 CPU 情况。而程序在操作系统中实际上是以一个个的进程存在的,那我们如何确定到占用 CPU 高的进程呢?让我们的目光从 top 的头部信息往下移动,下面就展示了详细的进程信息\n{% image /images/top-process.png fancybox:true %}\n<!-- !cess](../imagescess.png) -->\n\n这些程序默认是按照 CPU 的使用率从高到底进行排序的,当然你也可以通过在`top`的时候输入`P`进行排序,这样我们就可以看到系统中消耗 CPU 资源的详细进程信息\n\n上面是我通过 `./sysstress cpu --cpu-number 10 --duration 10m` 压测程序跑出来的,可以看到这里的 sysstress 程序占用了 1002 的 %CPU,也就是说基本上是 10 个核心,那我们跑一个更高的,将`--cpu-number`加到 60 看看发生了什么\n<!-- ![stress-cpu](../images/stress-cpu.png) -->\n{% image /images/stress-cpu.png stress-cpu fancybox:true %}\n\n我们可以看到这次%CPU打到了 6000,那很多人就好奇我日常的程序跑到多高算高呢?\n\n这里我们需要明确一点,现在的服务器绝大部分都是多核心 CPU(1C2G这种自己用来玩的忽略),CPU 的核心数决定了我们程序在同一时间能够执行多少个线程,也就是说,这个高不高是相对于机器配置而言的。如果你的机器只有 16C,那么单个进程占用的 %CPU 到 1000,那么其实已经算是比较高了。如果是 256C 的CPU(土豪级配置),那么单个进程占用的 %CPU 到 6000,对于系统的稳定性影响就没有那么大了。\n\n上述我们说的情况是进程占用 CPU 对整个系统的影响,那么进程占用的 CPU 对系统的影响不大就代表这个程序一定没有问题吗?答案显然是未必的。\n\n我们还是要回归到业务本身,如果进程的 CPU 占用在业务变动不大的情况下,发生了异常波动,或者正常情况下业务不会消耗这么高的 CPU,那么我们就需要继续排查了。\n\n### 如何确定CPU飙升的根源\n这个问题的 核心是 CPU 上在运行什么东西。 多核心CPU 下,每个核心都可以执行不同的程序,我们如何确定一个进程中那些方法在消耗 CPU 呢?从而引申下面详细的问题:\n 1. 程序的调用栈是什么样的?\n 2. 调用栈信息中哪些是需要关注的,那些是可以忽略的?\n 3. 热点函数是什么?\n\n老话说得好,\"工欲善其事,必先利其器\", 我们需要这些东西,就必须了解到什么样的工具可以拿到上面我提到的一些信息。接下来我将通过常用的后端语言:`golang` 和 `java` 为例构造一些高 CPU 的程序来进行展示。\n\n#### perf命令\n**perf是一款Linux性能分析工具。Linux性能计数器是一个新的基于内核的子系统,它提供一个性能分析框架,比如硬件(CPU、PMU(Performance Monitoring Unit))功能和软件(软件计数器、tracepoint)功能。**\n\n安装:\n```\nyum install perf #Centos\n```\n安装完成后,我们可以首先看下 `perf`的用法,这里不展开具体用法,只列出我平常使用的几个命令:\n```\ntop System profiling tool. #对系统性能进行实时分析。\nrecord Run a command and record its profile into perf.data #收集采样信息\nreport Read perf.data (created by perf record) and display the profile #分析采样信息,和record配合使用\n```\nrecord 和 report 的使用更多在于 dump 当前环境的信息用于后续分析,如果在自己环境上测试,可以用 top 进行一些简单的实时分析(类似于 top 命令)。\n\n还是用之前的压测工具,我们模拟一个 10 核心的 10min 的压测场景\n```\nnohup ./sysstress cpu --cpu-number 10 --duration 10m > /dev/null 2>&1 &\n```\n执行这个语句,让压测程序在后台执行,然后我们通过`perf top`查看具体的情况(可以通过-p 指定 pid)\n\n{% image /images/perftop.png perf top, fancybox:true %}\n<!-- ![perf top](../images/perftop.png) -->\n\n从截图的信息中我们可以看到占用资源最多的一些方法,包括 sysstress 进程的各种方法(从图片中基本上就可以确定高消耗的方法在哪里)以及底层的 `__vdso_clock_gettime`, 那再结合压测工具的代码分析下:\n\n``` golang\nfunc burnCpu(wg *sync.WaitGroup, start time.Time, durSec int64) {\n\tdefer wg.Done()\n\tfor {\n\t\t_ = 1 * 1\n\t\tnow := time.Now()\n\t\tif now.Sub(start) > time.Duration(durSec)*time.Second {\n\t\t\tbreak\n\t\t}\n\t}\n}\n```\n这是方法的核心,其实就是做无意义的计算,外加时间的判断,超过 duration 就结束。这样和上面的 perf top 信息就能对应起来。\n\n然后我们用 java 写一个同样的程序,再看看 `perf top`的情况:\n{% image /images/javaperftop.png perf top, fancybox:true %}\n<!-- ![perf top](../images/javaperftop.png) -->\n从这一大段显示来看,是不是看的一脸懵逼,很难发现到底是什么程序在占用CPU 资源。大家可以看一下源程序:\n``` java\nimport java.time.LocalDateTime;\n\npublic class Main {\n public static void main(String[] args) {\n int n = 10;\n\n for (int i = 0; i < 10; i++) {\n new Thread(new Runnable() {\n public void run() {\n while (true) {\n Math.sin(Math.random());\n LocalDateTime currentTime = LocalDateTime.now();\n }\n }\n }).start();\n }\n }\n}\n```\n这里的程序也是非常简单,启动 10 个线程,做一个无意义的数学运算,然后获取当前时间。从这段代码中是不是很难和上面`perf top`的显示关联起来? 原因也非常简单, 像Java 这种通过 JVM 来运行的应用程序,运行堆栈用的都是 JVM 内置的函数和堆栈管理。所以,从系统层面只能看到 JVM 的函数堆栈,而不能直接得到 Java 应用程序的堆栈。那我们好能通过 perf 去看到 java 相关的堆栈吗?答案是可以的。\n\n可以借助 [perf-map-agent](https://github.com/jvm-profiling-tools/perf-map-agent) 这样的开源工具,去生成和`perf` 工具一起使用的方法映射,但是需要做额外的一些配置。这里的方法大家可以自己探究,为什么不详细的讲这个呢,原因也简单,排查问题的工具多种多样,没必要在一棵树上吊死。\n\n#### jstack\n\n既然 perf top 去查看 JAVA 的调用栈不太方便,我们就直接上 java 提供的 jstack 工具去分析。\n- jstack -l pid > xxx.txt 需要注意的是,linux系统中往往会用不同的用户去执行不同的程序,此时可能需要通过sudu -u xxx jstack的形式\n- kill -3, jstack 用不了的情况下可以使用 kill -3 pid 的形式,堆栈默认会输出在系统日志中(根据不同的配置,信息也可能输出在其他地方,比如这个程序的日志中)。\n\n具体的操作步骤:\n1. `top -Hp $pid` 找到占用 CPU 的具体线程\n2. `jstack -l $pid > /tmp/$pid.jstack` 或者 `kill -3 $pid`将 java 进程的堆栈情况输出的日志中,然后根据 `top -Hp` 看到的线程信息在输出的堆栈日志中进行查找(`top -Hp` 输出的是 10 进制的 id,`jstack` 输出的是 16 进制的,在查找时注意进制转换)\n\n我们看下上面 java 程序的堆栈的信息:\n``` Lua\n2024-08-16 15:15:40\nFull thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode):\n\n\"Attach Listener\" #35 daemon prio=9 os_prio=0 tid=0x00007f52b4001000 nid=0x71f4 waiting on condition [0x0000000000000000]\n java.lang.Thread.State: RUNNABLE\n\n Locked ownable synchronizers:\n\t- None\n\n\"DestroyJavaVM\" #34 prio=5 os_prio=0 tid=0x00007f53e0009800 nid=0x1693 waiting on condition [0x0000000000000000]\n java.lang.Thread.State: RUNNABLE\n\n Locked ownable synchronizers:\n\t- None\n\n\"Thread-1\" #25 prio=5 os_prio=0 tid=0x00007f53e015a800 nid=0x16d9 runnable [0x00007f52f64e3000]\n java.lang.Thread.State: RUNNABLE\n\tat sun.misc.Unsafe.getObjectVolatile(Native Method)\n\tat java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:755)\n\tat java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:938)\n\tat java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:267)\n\tat java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:227)\n\tat java.time.ZoneRegion.ofId(ZoneRegion.java:120)\n\tat java.time.ZoneId.of(ZoneId.java:411)\n\tat java.time.ZoneId.of(ZoneId.java:359)\n\tat java.time.ZoneId.of(ZoneId.java:315)\n\tat java.util.TimeZone.toZoneId(TimeZone.java:556)\n\tat java.time.ZoneId.systemDefault(ZoneId.java:274)\n\tat java.time.Clock.systemDefaultZone(Clock.java:178)\n\tat java.time.LocalDateTime.now(LocalDateTime.java:180)\n\tat Main$1.run(Main.java:12)\n\tat java.lang.Thread.run(Thread.java:748)\n\n Locked ownable synchronizers:\n\t- None\n\n\"Thread-0\" #24 prio=5 os_prio=0 tid=0x00007f53e0159000 nid=0x16d8 runnable [0x00007f52f65e4000]\n java.lang.Thread.State: RUNNABLE\n\tat sun.misc.Unsafe.getObjectVolatile(Native Method)\n\tat java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:755)\n\tat java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:938)\n\tat java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:267)\n\tat java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:227)\n\tat java.time.ZoneRegion.ofId(ZoneRegion.java:120)\n\tat java.time.ZoneId.of(ZoneId.java:411)\n\tat java.time.ZoneId.of(ZoneId.java:359)\n\tat java.time.ZoneId.of(ZoneId.java:315)\n\tat java.util.TimeZone.toZoneId(TimeZone.java:556)\n\tat java.time.ZoneId.systemDefault(ZoneId.java:274)\n\tat java.time.Clock.systemDefaultZone(Clock.java:178)\n\tat java.time.LocalDateTime.now(LocalDateTime.java:180)\n\tat Main$1.run(Main.java:12)\n\tat java.lang.Thread.run(Thread.java:748)\n\n Locked ownable synchronizers:\n\t- None\n --- 10 个 thread\n\n\"Service Thread\" #23 daemon prio=9 os_prio=0 tid=0x00007f53e0143800 nid=0x16d6 runnable [0x0000000000000000]\n java.lang.Thread.State: RUNNABLE\n\n Locked ownable synchronizers:\n\t- None\n\n\"C2 CompilerThread1\" #6 daemon prio=9 os_prio=0 tid=0x00007f53e010e000 nid=0x16c5 waiting on condition [0x0000000000000000]\n java.lang.Thread.State: RUNNABLE\n\n Locked ownable synchronizers:\n\t- None\n --- 一大堆 C2 CompilerThread\n\n\"C2 CompilerThread0\" #5 daemon prio=9 os_prio=0 tid=0x00007f53e010b000 nid=0x16c4 waiting on condition [0x0000000000000000]\n java.lang.Thread.State: RUNNABLE\n\n Locked ownable synchronizers:\n\t- None\n\n\"Signal Dispatcher\" #4 daemon prio=9 os_prio=0 tid=0x00007f53e0109800 nid=0x16c3 runnable [0x0000000000000000]\n java.lang.Thread.State: RUNNABLE\n\n Locked ownable synchronizers:\n\t- None\n\n\"Finalizer\" #3 daemon prio=8 os_prio=0 tid=0x00007f53e00d8800 nid=0x16c2 in Object.wait() [0x00007f52f7bfa000]\n java.lang.Thread.State: WAITING (on object monitor)\n\tat java.lang.Object.wait(Native Method)\n\t- waiting on <0x000000008021a5e8> (a java.lang.ref.ReferenceQueue$Lock)\n\tat java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)\n\t- locked <0x000000008021a5e8> (a java.lang.ref.ReferenceQueue$Lock)\n\tat java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)\n\tat java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)\n\n Locked ownable synchronizers:\n\t- None\n\n\"Reference Handler\" #2 daemon prio=10 os_prio=0 tid=0x00007f53e00d3800 nid=0x16c1 in Object.wait() [0x00007f52f7cfb000]\n java.lang.Thread.State: WAITING (on object monitor)\n\tat java.lang.Object.wait(Native Method)\n\t- waiting on <0x0000000080218d38> (a java.lang.ref.Reference$Lock)\n\tat java.lang.Object.wait(Object.java:502)\n\tat java.lang.ref.Reference.tryHandlePending(Reference.java:191)\n\t- locked <0x0000000080218d38> (a java.lang.ref.Reference$Lock)\n\tat java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)\n\n Locked ownable synchronizers:\n\t- None\n\n\"VM Thread\" os_prio=0 tid=0x00007f53e00ca000 nid=0x16c0 runnable\n\n\"GC task thread#0 (ParallelGC)\" os_prio=0 tid=0x00007f53e001f000 nid=0x1694 runnable\n\n--- 一大堆 GC task thread\n\n\"VM Periodic Task Thread\" os_prio=0 tid=0x00007f53e0146000 nid=0x16d7 waiting on condition\n\nJNI global references: 202\n```\n我们通过 top -Hp 的信息就可以快速定位到 Thread-[0-9] 这几个线程,而每个线程的调用栈都是 `java.time.LocalDateTime.now`, 也说明了这个方法在不停消耗 CPU。(但是 jstack 只能捕获短时间或者瞬时的堆栈信息,没法处理长时间的,所以我们在获取时可以多打印几次或者使用其他方法)\n\n至于 jstack 的详细用法,请参考我的另一篇博客:[java问题定位](https://baixiaozhou.github.io/2024/08/13/JAVA%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/)\n\n除此之外,还有非常多的分析工具,pstack\\gstack\\strace\\gdb等等,大家可以自行探索使用\n\n#### 火焰图\n\n上面我们介绍了很多操作的命令和方法,那么有没有一种比较直观的方式能够直接看到各种方法执行的耗时比重等情况呢?火焰图就是为了解决这种情况而生的。\n\n火焰图的分类有很多,常用的包括:\n1. CPU 火焰图 (CPU Flame Graph)\n\t-\t描述:展示 CPU 在不同方法上的消耗情况,显示每个方法调用所占用的 CPU 时间。\n\t-\t用途:用于分析 CPU 性能瓶颈,识别哪些方法消耗了最多的 CPU 资源。\n\t-\t应用:Java、C++ 等多种编程语言的性能分析。\n2. 内存火焰图 (Memory Flame Graph)\n\t- 描述:展示内存分配情况,显示每个方法调用分配的内存量。\n - 用途:用于检测内存泄漏、过度内存分配问题,帮助优化内存使用。\n\t- 应用:常用于分析内存密集型应用,如 Java 应用的堆内存分析。\n3. I/O 火焰图 (I/O Flame Graph)\n\t-\t描述:展示 I/O 操作的耗时情况,显示不同方法的 I/O 操作占用的时间。\n\t-\t用途:用于分析应用程序的 I/O 性能,识别慢速或频繁的 I/O 操作。\n\t-\t应用:数据库查询、文件系统操作、网络通信等场景的性能调优。\n\n我们这里通过 [async-profiler](https://github.com/async-profiler/async-profiler) 对文章上面的java压测程序进行抓取(这个工具只能抓 java 的, 对于 golang 程序,可以利用 golang 提供的 pprof)\n\n```\ntar -xzf async-profiler-3.0-linux-x64.tar.gz\ncd async-profiler-3.0-linux-x64/bin\n./asprof -d 60 pid -f /tmp/javastress.html\n```\n我们用浏览器打开生成的 html 文件,可以看到如下的火焰图信息(可以在网页进行点击,查看更细节的方法)\n{% image /images/javafire.png java 程序的火焰图, fancybox:true %}\n<!-- ![java 程序的火焰图](../images/javafire.png) -->\n\n这样看起来就比 jstack这些信息更加直观一点。\n\n## 负载飙升\n\n### 负载的定义以及如何查看负载\n\n我们先看下系统负载的官方描述:\n```\nSystem load averages is the average number of processes that are either in a runnable or uninterruptable state. A process in arunnable state is either using the CPU or waiting to use the CPU. A process in uninterruptable state is waiting for some I/O access,eg waiting for disk. The averages are taken over the three time intervals. Load averages are not normalized for the number of CPUs ina system, so a load average of 1 means a single CPU system is loaded all the time while on a 4 CPU system it means it was idle 75% of the time.\n```\n\n系统负载平均值表示处于可运行或不可中断状态的进程的平均数量。处于可运行状态的进程要么正在使用 CPU,要么正在等待使用 CPU。处于不可中断状态的进程正在等待某些 I/O 访问,例如等待磁盘。这里的核心概念就是 loadavg 这个数值体现了某些特定状态进程的数量。\n\n那引申出两个问题:\n1. 进程的状态有哪些? 如何在 Linux 上查看进程状态\n2. 可运行和不可中断状态的进程具体含义是什么\n\n查看的方式,我们可以通过 ps 命令进行查看,比如通过`ps -auxf`, 我么可以看到有一列为 `STAT`,这列就代表该进程的状态:\n\n{% image /images/psauxf.png 进程状态 fancybox:true %}\n\n进程的状态和具体含义:\n- D uninterruptible sleep (usually IO)\n- R running or runnable (on run queue)\n- S interruptible sleep (waiting for an event to complete)\n- T stopped by job control signal\n- t stopped by debugger during the tracing\n- W paging (not valid since the 2.6.xx kernel)\n- X dead (should never be seen) \n- Z defunct (\"zombie\") process, terminated but not reaped by its parent\n\n这里我们看到处于不可中断的状态的进程和正在运行的进程分别为 `D` 和 `R`,换个说法,也就是说造成负载升高的原因也就是这两个状态的进程引起的。\n\n(插个题外话,按照官方的说法,X 状态的进程应该是不应该被看到的, 但是之前在腾讯云做ES的时候,偶然间碰到了一次,当时还截了个图用做留念😂,但是没有捕获到具体的信息)\n\n负载的指标可以通过 `top` 以及 `uptime` 指令获取\n```\n23:35:00 up 1 day, 46 min, 1 user, load average: 49.16, 18.35, 7.87\n```\n这里展示了 loadavg 的三个数值: 分别代表的含义是 1min、5min、15min 的系统平均负载\n\n那我们如何判断系统的负载是高是低呢?\n\n这里一般有个经验值,我们一般和 CPU 和核心数进行对比,一般负载在 CPU 核心的 70% 左右以及以下,对系统一般没什么影响,超过 70%,系统可能收到影响。但是这里还需要注意的一点就是,负载的比例在 70% 以下时不一定代表系统就没问题,举个简单的例子,如果一个系统上基本上没有业务在运行,那么负载基本上就在零点几左右,那么这种情况下,负载有升高不一定是合理的(后面举一个简单的例子)\n\n### 如何让系统负载飙高\n\n#### 纯计算任务对负载的影响\n\n既然说正在运行的进程会引起负载的变化,那么跑一些程序,让程序不停运行,那么自然而然就能构造出持续运行的进程了。\n我这里找了三台机器(64C),用我的压测工具先跑一些纯 CPU 的运算,然后观察下效果:\n\n测试分为三组,测试前关闭不必要的服务和进程:\n1. 10 并发 30min\n - `nohup ./sysstress cpu --cpu-number 10 --duration 30m > /dev/null 2>&1`\n2. 30 并发 30min\n - `nohup ./sysstress cpu --cpu-number 30 --duration 30m > /dev/null 2>&1`\n3. 60 并发 30min\n - `nohup ./sysstress cpu --cpu-number 60 --duration 30m > /dev/null 2>&1`\n\n效果如下:\n{% image /images/load10.png 10并发负载, fancybox:true %}\n{% image /images/load30.png 30并发负载, fancybox:true %}\n{% image /images/load60.png 60并发负载, fancybox:true %}\n<!-- ![10并发负载](../images/load10.png)\n![30并发负载](../images/load30.png)\n![60并发负载](../images/load60.png) -->\n\n从上述测试过程中,我们可以发现,在纯运算这种场景下,并发的量基本上和负载是对应的。也就是说随着 CPU的使用量 上涨,负载也会不断变高。\n\n#### 磁盘 IO 对负载的影响\n\n在刚才的例子中,我们看到了纯运算对负载的影响(R 进程的代表),然后在关于 D 进程的说明中,我们可以看到有一个比较明显的说明 `(usually IO)` ,即通常是 IO 引起的,那么接下来我们通过磁盘 IO 来测试一下\n\n测试分为三组,测试前关闭不必要的服务和进程:\n1. 10 并发 15min\n - `nohup ./sysstress io --operation read --filepath test.access.log -p 10 -d 15m > /dev/null 2>&1 &`\n2. 30 并发 15min\n - `nohup ./sysstress io --operation read --filepath test.access.log -p 30 -d 15m > /dev/null 2>&1 &`\n3. 60 并发 15min\n - `nohup ./sysstress io --operation read --filepath test.access.log -p 60 -d 15m > /dev/null 2>&1 &`\n\n效果如下:\n\n{% image /images/ioload10.png 10并发负载 fancybox:true %}\n{% image /images/ioload30.png 30并发负载 fancybox:true %}\n{% image /images/ioload60.png 60并发负载 fancybox:true %}\n\n我们也顺便看一下,60 并发下 CPU 的情况:\n\n{% image /images/io60c.png 60并发系统整体情况 fancybox:true %}\n\n这里我们可以观察到,系统的 CPU 基本上已经跑满了。us 和 sy 都占的比较多,但是这种读取非常有可能走到缓存中,我们想测绕过缓存,可以通过 DIRECT 的方式。\n\n但是上面的例子其实也证明了一件事,IO 的操作也是会导致负载产生飙升。\n\n那么问题来了,磁盘IO 和 CPU 操作都会导致系统负载飙升,那么负载飙升一定会是这两个原因吗?答案也是未必的,因为上述我们曾经提到过 D 状态的进程,到目前为止我们好像还没介绍过,那么我们来继续模拟,既然 D 状态的进程是 IO 操作引起的,普通的磁盘读写 IO 很难模拟,那我们就换个 IO 场景继续模拟 -- 网络 IO。\n\n#### 通过网络 IO 模拟 D 状态进程观察负载影响\n\n这里直接上一个模拟方法:\n- A 机器开启 NFS Server\n- B 机器作为客户端进行挂载\n- 断开网络\n- 疯狂 df -h\n\n详细的操作步骤:\n```\n# 安装 Centos\nsudo yum install nfs-utils\n\n# 服务端配置\nsudo mkdir -p /mnt/nfs_share\nsudo chown nobody:nogroup /mnt/nfs_share\nsudo chmod 755 /mnt/nfs_share\n## 打开 /etc/exports 配置,添加一行来定义共享目录及其权限。例如,将 /mnt/nfs_share 共享给网络 192.168.1.0/24,并提供读写权限:\n/mnt/nfs_share 192.168.1.0/24(rw,sync,no_subtree_check)\n\n## 启动 NFS\nsudo exportfs -a\nsudo systemctl restart nfs-kernel-server\nsudo systemctl enable nfs-kernel-server\n\n# 客户端配置\nsudo mkdir -p /mnt/nfs_client\nsudo mount -t nfs 192.168.1.100(server ip):/mnt/nfs_share /mnt/nfs_client\n## 验证\ndf -h /mnt/nfs_client\n\n# 断网模拟(客户端)\niptables -I INPUT -s serverip -j DROP\n\n# 持续(疯狂)执行:\ndu -sh /mnt/nfs_client\n```\n因为网络已经断掉,所以`du -sh /mnt/nfs_client`,而且这个程序没有自动退出或者报错,这样就导致程序无法顺利执行下去,继而阻塞住就变成了 D 状态的进程。\n\n基于这种模拟方法大家可以自行测试下,笔者之前做过一个场景,将一个两核心的 CPU负载干到了 200 多,但是因为**这种情况下更多是阻塞在网络中,所以此时的负载虽高,并不一定影响系统运行**。\n\n当然这只是其中一个例子,笔者曾经也因为见过 ping 操作阻塞导致的负载飙升,所以这种场景是多种多样的😂,大家有更多的例子也可以在下方留言,共同学习进步。\n\n\n### 负载飙升如何排查\n基于上面的例子和场景模拟,我们其实应该已经有一套基本的排查方法了,下面这张图是我个人的一些总结(图还会不断完善)\n\n{% image /images/loadhigh.png 负载高排查导图 fancybox:true %}\n\n## 内存占用过高\n\n我们先来了解一下有哪些常用的内存性能工具:\n| 工具 | 功能 | 用法 | 常用选项 |\n|--------|-----------------------------------------|-------------------------|-----------------------------------|\n| `free` | 显示系统的内存使用情况 | `free -h` | `-h`:以人类可读的格式显示 |\n| `top` | 实时显示系统的进程和内存使用情况 | `top` | 无 |\n| `htop` | 提供更友好的用户界面的进程监控工具 | `htop` | 无 |\n| `vmstat`| 报告虚拟内存、进程、CPU 活动等统计信息 | `vmstat 1` | `1`:每秒更新一次数据 |\n| `ps` | 查看系统中进程的内存使用情况 | `ps aux --sort=-%mem` | `aux`:显示所有用户的进程, `--sort=-%mem`:按内存使用量降序排列 |\n| `pmap` | 显示进程的内存映射 | `pmap -x <pid>` | `-x`:显示详细信息 |\n| `smem` | 提供详细的内存使用报告 | `smem -r` | 无 |\n| `/proc`| 提供系统和进程的详细内存信息 | `cat /proc/meminfo`<br>`cat /proc/<pid>/status` | 无 |\n\n### 如何判断内存占用过高\n\n系统内存的使用情况可以通过 `top` 以及 `free` 命令进行查看,以 `free -m`为例:\n\n{% image /images/free.png free查看内存信息 fancybox:true %}\n\n字段说明:\n- total: 系统总内存(RAM)或交换空间(Swap)的总量。\n- used: 已用内存或交换空间的量。这包括正在使用的内存以及系统缓存(对于内存)或已经使用的交换空间。\n- free: 空闲的内存或交换空间的量。表示当前未被任何进程使用的内存或交换空间。\n- shared: 被多个进程共享的内存量(对于内存)。通常是共享库和进程间通信的内存。\n- buff/cache: 用作文件系统缓存和缓冲区的内存量。这包括缓存的文件数据(cache)和缓冲区(buff)。这些内存可以被系统用作其他用途。\n- available: 可以分配给新启动的应用程序的内存量,而不需要交换到磁盘。这个数字更能反映系统的实际可用内存\n\n这里我们需要关注下,一般可用内存我们就是以 available 为准。当 available 的指标越来越小时,我们就需要关注系统内存的整体使用情况。\n\n这里还有一个需要关注的点:Swap,我们先看一下详细介绍:\n```\nSwap space in Linux is used when the amount of physical memory (RAM) is full. If the system \nneeds more memory resources and the RAM is full, inactive pages in memory are moved to the \nswap space. While swap space can help machines with a small amount of RAM, it should not be \nconsidered a replacement for more RAM. Swap space is located on hard drives, which have a \nslower access time than physical memory.Swap space can be a dedicated swap partition (recommended),\n a swap file, or a combination of swap partitions and swap files.\n```\n\nSWAP意思是交换,顾名思义,当某进程向OS请求内存发现不足时,OS会把内存中暂时不用的数据交换出去,放在SWAP分区中,这个过程称为SWAP OUT。当某进程又需要这些数据且OS发现还有空闲物理内存时,又会把SWAP分区中的数据交换回物理内存中,这个过程称为SWAP IN。\n简单来说,就是物理内存不足时,把磁盘空间当作 swap 分区,解决容量不足的问题。\n\n这里我们其实会发现一个问题,物理内存的读写性能肯定要比磁盘强不少,使用了磁盘空间作为内存存储,本身读写的性能就不高,还涉及到频繁的交换,反而增加了系统的负载,所以在线上环境中我们一般建议关闭 swap,具体的关闭方法请自行百度。\n\n### 如何定位内存占用高\n\n针对内存的占用,常规情况下(当然也有非常规情况),我们需要找到占用内存高的具体进程,详细的操作方式是:\n`top`的时候输入`M`进行排序,这样我们就可以看到系统中进程消耗内存的详细占比了:\n\n{% image /images/top-process.png fancybox:true %}\n\n这里我们重点关注两列: `%MEM` 和 `RES`, 前者是这个进程占用系统内存的百分比,后者是占用实际内存的大小。\n\n我们还是以 JAVA 和 GOLANG 程序为例来分析内存高该如何排查\n\n#### JAVA 占用内存过高\nJava 应用的内存管理依赖于 JVM (Java Virtual Machine),通常会涉及到堆内存(Heap)、非堆内存(Non-Heap)以及本地内存(Native Memory)。\n\n##### 堆内内存分析\n1. 查看 JVM 启动参数,尤其是 -Xms 和 -Xmx 选项,这两个参数分别设置了初始堆内存大小和最大堆内存大小。可以通过 ps 命令查看这些参数:`ps -auxf | grep 程序名称`\n2. 使用 jstat 查看 gc 情况, 实例:`jstat -gc pid 1000`\n{% image /images/gc.png gc 信息 fancybox:true %}\n如果 GC 频繁,那么我们需要进一步进行分析\n3. 通过 jmap 生成进程的堆内存快照 (在 JVM启动时,建议添加 OOM 时自动生成 heap dump: `-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumpfile`):\n``` bash\njmap -dump:live,format=b,file=heapdump.hprof <pid>\n```\n4. 拿到快照文件后,我们可以通过 MAT 或者 Jprofiler 这样的工具去具体分析\n以 MAT 为例:\n\n{% image /images/mat.jpeg MAT 分析 JAVA内存 fancybox:true %}\n\n##### 本地内存分析\n\nNMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。\nNMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。\n\n启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。\n启动命令: `-XX:NativeMemoryTracking=[off | summary | detail]`。\n- off:NMT 默认是关闭的;\n- summary:只收集子系统的内存使用的总计数据;\n- detail:收集每个调用点的内存使用数据。\n\n开启后,我们可以通过如下命令访问 NMT 内存:\n```\njcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]\n```\n\n##### 其他非堆内存\n主要包括:\n- JVM 自身运行占用的空间;\n- 线程栈分配占用的系统内存;\n- DirectByteBuffer 占用的内存;\n- JNI 里分配的内存;\n- Java 8 开始的元数据空间;\n- NIO 缓存\n- Unsafe 调用分配的内存;\n- codecache\n\n对于这些问题,遇到内存升高的情况较少,所以也没有进行过详细的排查,如果有读者朋友有做过类似的排查,可以在下面留言讨论。\n\n#### golang程序占用内存高\n\ngolang 内置了 pprof 性能分析工具,支持 CPU、内存(Heap)、栈、协程等的分析。可以通过 HTTP 服务暴露 pprof 接口,并使用浏览器或 go tool pprof 进行分析:\n在代码中引入 net/http/pprof 包:\n```golang\nimport _ \"net/http/pprof\"\n\nfunc main() {\n go func() {\n log.Println(http.ListenAndServe(\"localhost:6060\", nil))\n }()\n // 你的应用代码\n}\n```\n启动应用后,通过浏览器访问 `http://localhost:6060/debug/pprof/heap` 下载堆内存快照,并使用以下命令分析:\n```bash\ngo tool pprof -http=:8080 heap.prof\n```\n这将启动一个 Web 界面,供你分析内存占用的热点。\n具体的使用方式大家可以自行研究。\n\n#### 基于内存的文件系统占用\n\n在上文中,我们说了常规情况下的排查,那来个非常规的,先上张图:\n\n{% image /images/tmp-mem.png 内存占用模拟 fancybox:true %}\n\n这里大家看看到,内存总量大概有 250 个 G,可用内存只有 126G,但是我们通过 top 看内存占比跟实际的数值相差甚远。\n\n这种场景是不是非常奇怪呢?没有进程占用内存,但是内存被消耗了,这种情况下,很有可能跟基于内存的文件系统有关。\n\n这里我说一下模拟方法:\n```\n# systemd 的包中会默认自带一个 tmp.mount服务,这个服务默认是关闭的(默认是总内存的一半)\n# 这个服务本质上是一个基于内存的文件系统(把内存当磁盘使,在上面可以创建文件)\nsystemctl start tmp.mount\n\n# 然后我们通过 df -h /tmp 可以看到\nFilesystem Size Used Avail Use% Mounted on\ntmpfs 126G 111G 16G 88% /tmp\n\n这里挂载了/tmp 目录,文件系统是 tmpfs\n\n# 然后我们进入 /tmp目录\n# 模拟文件创建\ndd if=/dev/zero of=test.txt bs=G count=100\n\n# 然后我们就可以看到文件创建成功,然后 free -g 就可以看到内存成功消耗了100G😂\n```\n\n这种场景也是我之前在排查问题的时候遇到的,大家有其他类似场景也可以进行补充\n\n## 磁盘问题\n\n线上的磁盘问题,除了磁盘故障(坏块、文件系统损坏、RAID 等)之外,常见的比较多的问题可以分为两大类:磁盘空间不足和 IO 性能问题\n\n### 磁盘空间不足\n对于这类问题,一般情况下,本身可能就是数据文件、日志信息等过多导致的**真实占用**,还有一类是文件句柄泄露导致磁盘没有释放从而占据了多余的磁盘空间\n\n对于这类问题我们一般通过以下手段进行排查,假如说我们现在接到告警 说根目录的磁盘空间不足了\n\n1. 通过`df -h`命令查看磁盘分区的使用情况。\n2. 进入 `\"/\"` 目录下,可以对常用的目录进行 `du -sh`操作,然后进行简单的相加\n - 如果加起来的磁盘空间,和分区使用的磁盘空间相差不多,那就基本上说明磁盘空间是被真实占用的,找到占用高的目录继续通过`du -sh` 目录,不断递归。或者想快速找到磁盘中超过 1G 的文件,可以通过`find 目录 -type f -size +1G` 这样的方式。\n 如果想快速统计到所有子目录的磁盘信息,那么可以通过这样的方式进行操作\n ```\n du -ah / 2>/dev/null | grep -E '^([0-9]+([KMGT]?)|([0-9]+(\\.[0-9]+)?[KMGT]))' | sort -rh | head -10\n ```\n - 如果加起来的磁盘空间,和分区使用的磁盘空间相差的比较大,那么可能存在句柄泄露,比如日志文件本身被删掉,但是句柄没有释放,可以通过一下方式:\n ```\n #列出进打开文件最多的 10 个进程;\n lsof +L1 | awk '{print $2}' | sort | uniq -c | sort -rn | head -n 10\n\n # 查看某个进程的占用\n lsof -p $pid (查看是否存在已经删除的文件句柄没有释放,可以通过 grep deleted 过滤)\n\n # 或者 ls /proc/$pid/fd 进行查看\n ```\n### 磁盘IO性能问题\n\n我们可以通过 iostat 看下磁盘整体的读写情况:\n\n{% image /images/iostat.png iostat fancybox:true %}\n\n详细的介绍如下:\n1. CPU 使用情况\n\n| Metric | Description |\n|-----------|-------------------------------------------|\n| %user | 用户模式下消耗的 CPU 时间百分比 |\n| %nice | 调整优先级的用户模式下的 CPU 时间百分比 |\n| %system | 内核模式下消耗的 CPU 时间百分比 |\n| %iowait | CPU 等待 I/O 操作完成的时间百分比 |\n| %steal | 等待虚拟 CPU 被实际 CPU 服务的时间百分比 |\n| %idle | CPU 空闲时间百分比 |\n\n2. 设备 I/O 使用情况\n\n| Metric | Description |\n|---------------|---------------------------------------|\n| Device | 设备名称 |\n| tps | 每秒传输数(I/O 请求数) |\n| kB_read/s | 每秒读取的 kB 数 |\n| kB_wrtn/s | 每秒写入的 kB 数 |\n| kB_read | 读取的 kB 总数 |\n| kB_wrtn | 写入的 kB 总数 |\n| rrqm/s | 每秒进行的合并读请求数 |\n| wrqm/s | 每秒进行的合并写请求数 |\n| r/s | 每秒完成的读请求数 |\n| w/s | 每秒完成的写请求数 |\n| rMB/s | 每秒读取的 MB 数 |\n| wMB/s | 每秒写入的 MB 数 |\n| avgrq-sz | 平均每次 I/O 请求的数据大小 |\n| avgqu-sz | 平均 I/O 队列长度 |\n| await | 平均每次 I/O 操作的等待时间 |\n| svctm | 平均每次 I/O 操作的服务时间 |\n| %util | 设备 I/O 活动时间百分比(表示设备忙碌度)|\n\n通过这个我们就可以获取到磁盘的整体情况。\n\n当我们想查看进程占用的 io 情况时,可以通过 `iotop`命令进行查看,如下:\n\n{% image /images/iotop.png iotop fancybox:true %}\n\n结合上文讲的一些堆栈分析工具,推测到进程具体在做那些操作然后针对性进行处理。\n","source":"_posts/线上问题排查方法汇总.md","raw":"---\ntitle: 线上故障排查方法和工具介绍\nauthor: baixiaozhou\ncategories:\n - 问题排查\ntags:\n - Linux\n - Java\n - Golang\n - commands\ndescription: 线上故障问题的排查方法和工具介绍,包括 CPU、内存、负载、磁盘、IO、网络等\ndate: 2024-08-15 11:12:21\nreferences:\n - '[Examining Load Average](https://www.linuxjournal.com/article/9001)'\n - '[What-is-CPU-Load-Average](https://community.tenable.com/s/article/What-is-CPU-Load-Average)'\ncover: /images/server.jpg\nbanner: /images/server.jpg\nrepo: baixiaozhou/SysStress\n---\n\n<!-- more -->\n\n{% quot 参考文档 %}\n\n[Examining Load Average](https://www.linuxjournal.com/article/9001)\n[What-is-CPU-Load-Average](https://community.tenable.com/s/article/What-is-CPU-Load-Average)\n[Brendan Gregg个人网站](www.brendangregg.com)\n\n\n## 写在前面\n\n在很多文章中,每当提到去解决线上问题的时候,大部分的处理方式就是登录环境,哐哐各种敲命令。操作本身没什么问题,但是对于很多人而言,我觉得这种做法其实是本末倒置的,过于在乎去快速抓住重点问题,而忽略了从全局去看问题。那么如果最开始不去操作各种命令,那应该干什么呢?\n\n***看监控!!!!***\n\n首先不要觉得这个是废话,对于很多场景来说,业务规模是不断变化的,有的时候并发超过了极限的性能,那么这种情况下都没有必要去后台进行各种查询。举个简单的例子,假如说某套业务系统,本身只能支持 500 并发,现在实际上的量到了 2000,导致线上各种内存、CPU、负载的告警,这种情况下还有必要去后台敲`top`、`free`吗?答案当然是否定的,这种情况下,就需要考虑对业务系统进行快速的扩容等。\n\n看监控的意义在于尽可能的找到更多的性能瓶颈或者异常的点,从全局出发,对系统当前存在的问题和异常点有全面的了解。\n\n监控系统多种多样,从较早的 zabbix 到现在比较流行的prometheus+grafana(举两个常用的例子),对于系统业务都有比较完善的监控,可以帮助我们更加具体的了解到系统运行全貌。如果你对这些都不喜欢,那么你自己写一个监控系统也没什么问题。\n\n当我们看完监控之后(假设你真的看了),接下来进入实际操作环节,我会从这些指标的详细含义出发,然后尽可能地将各种处理方式分享给大家。\n\n## Linux性能谱图\n\n在分析问题前,我们首先需要明确 Linux 有哪些性能分析工具,我们先上一下LINUX 性能专家 Brendan Gregg 总结的图(大家如果对性能分析等感兴趣的话,可以认真看下这位大佬的个人网站):\n\n{% image https://www.brendangregg.com/Perf/linux_observability_tools.png Linux Performance Observability Tool fancybox:true %}\n\n上面这张图是引用大佬文章的图,原文链接在这里: https://www.brendangregg.com/linuxperf.html\n\n## CPU使用率飙升\n\n### 如何让CPU使用率飙升\n\n这个问题其实很简单,只要有计算任务一直存在,让 CPU 一直处于繁忙之中,那么 CPU 必然飙升。我们可以通过一系列的工具去模拟这个情况。\n\n[github SysStress](https://github.com/baixiaozhou/SysStress) 这是我自己用 golang 写的压测工具(还在开发中,可以点个 star 让我更有动力😂)\n\n使用方法:\n```\n./sysstress cpu --cpu-number 10 --duration 10m\n```\n这个就是模拟占用 10 核心的 CPU 并持续 10min,当然大家也可以用其他的压测工具,比如`stress-ng`\n\n### 如何判断和发现CPU使用率飙升\n\n首先我们先看一下,跟 CPU 使用率相关的有哪些指标。我们通过 `top` 命令就可以看到具体的信息\n\n{% image /images/top.png top fancybox:true %}\n<!-- ![top](../images/top.png) -->\n这些输出中有一行是 `%Cpu(s)`, 这行展示了 CPU 的整体使用情况,是一个百分比的形式,我们详细阐述下这几个字段的含义\n```\nus, user : time running un-niced user processes 未降低优先级的用户进程所占用的时间\nsy, system : time running kernel processes 内核进程所占用的时间\nni, nice : time running niced user processes 降低优先级的用户进程所占用的时间\nid, idle : time spent in the kernel idle handler 空闲的时间\nwa, IO-wait : time waiting for I/O completion 等待 I/O 操作完成所花费的时间\nhi : time spent servicing hardware interrupts 处理硬件中断所花费的时间\nsi : time spent servicing software interrupts 处理软件中断所花费的时间\nst : time stolen from this vm by the hypervisor 被虚拟机管理程序从此虚拟机中窃取的时间\n```\n在这些指标中,一般关注的比较多的就是 us、sy、id、wa(其他几个指标很高的情况我个人目前基本上没有遇到过)\n\n上述指标反映了系统整体的 CPU 情况。而程序在操作系统中实际上是以一个个的进程存在的,那我们如何确定到占用 CPU 高的进程呢?让我们的目光从 top 的头部信息往下移动,下面就展示了详细的进程信息\n{% image /images/top-process.png fancybox:true %}\n<!-- !cess](../imagescess.png) -->\n\n这些程序默认是按照 CPU 的使用率从高到底进行排序的,当然你也可以通过在`top`的时候输入`P`进行排序,这样我们就可以看到系统中消耗 CPU 资源的详细进程信息\n\n上面是我通过 `./sysstress cpu --cpu-number 10 --duration 10m` 压测程序跑出来的,可以看到这里的 sysstress 程序占用了 1002 的 %CPU,也就是说基本上是 10 个核心,那我们跑一个更高的,将`--cpu-number`加到 60 看看发生了什么\n<!-- ![stress-cpu](../images/stress-cpu.png) -->\n{% image /images/stress-cpu.png stress-cpu fancybox:true %}\n\n我们可以看到这次%CPU打到了 6000,那很多人就好奇我日常的程序跑到多高算高呢?\n\n这里我们需要明确一点,现在的服务器绝大部分都是多核心 CPU(1C2G这种自己用来玩的忽略),CPU 的核心数决定了我们程序在同一时间能够执行多少个线程,也就是说,这个高不高是相对于机器配置而言的。如果你的机器只有 16C,那么单个进程占用的 %CPU 到 1000,那么其实已经算是比较高了。如果是 256C 的CPU(土豪级配置),那么单个进程占用的 %CPU 到 6000,对于系统的稳定性影响就没有那么大了。\n\n上述我们说的情况是进程占用 CPU 对整个系统的影响,那么进程占用的 CPU 对系统的影响不大就代表这个程序一定没有问题吗?答案显然是未必的。\n\n我们还是要回归到业务本身,如果进程的 CPU 占用在业务变动不大的情况下,发生了异常波动,或者正常情况下业务不会消耗这么高的 CPU,那么我们就需要继续排查了。\n\n### 如何确定CPU飙升的根源\n这个问题的 核心是 CPU 上在运行什么东西。 多核心CPU 下,每个核心都可以执行不同的程序,我们如何确定一个进程中那些方法在消耗 CPU 呢?从而引申下面详细的问题:\n 1. 程序的调用栈是什么样的?\n 2. 调用栈信息中哪些是需要关注的,那些是可以忽略的?\n 3. 热点函数是什么?\n\n老话说得好,\"工欲善其事,必先利其器\", 我们需要这些东西,就必须了解到什么样的工具可以拿到上面我提到的一些信息。接下来我将通过常用的后端语言:`golang` 和 `java` 为例构造一些高 CPU 的程序来进行展示。\n\n#### perf命令\n**perf是一款Linux性能分析工具。Linux性能计数器是一个新的基于内核的子系统,它提供一个性能分析框架,比如硬件(CPU、PMU(Performance Monitoring Unit))功能和软件(软件计数器、tracepoint)功能。**\n\n安装:\n```\nyum install perf #Centos\n```\n安装完成后,我们可以首先看下 `perf`的用法,这里不展开具体用法,只列出我平常使用的几个命令:\n```\ntop System profiling tool. #对系统性能进行实时分析。\nrecord Run a command and record its profile into perf.data #收集采样信息\nreport Read perf.data (created by perf record) and display the profile #分析采样信息,和record配合使用\n```\nrecord 和 report 的使用更多在于 dump 当前环境的信息用于后续分析,如果在自己环境上测试,可以用 top 进行一些简单的实时分析(类似于 top 命令)。\n\n还是用之前的压测工具,我们模拟一个 10 核心的 10min 的压测场景\n```\nnohup ./sysstress cpu --cpu-number 10 --duration 10m > /dev/null 2>&1 &\n```\n执行这个语句,让压测程序在后台执行,然后我们通过`perf top`查看具体的情况(可以通过-p 指定 pid)\n\n{% image /images/perftop.png perf top, fancybox:true %}\n<!-- ![perf top](../images/perftop.png) -->\n\n从截图的信息中我们可以看到占用资源最多的一些方法,包括 sysstress 进程的各种方法(从图片中基本上就可以确定高消耗的方法在哪里)以及底层的 `__vdso_clock_gettime`, 那再结合压测工具的代码分析下:\n\n``` golang\nfunc burnCpu(wg *sync.WaitGroup, start time.Time, durSec int64) {\n\tdefer wg.Done()\n\tfor {\n\t\t_ = 1 * 1\n\t\tnow := time.Now()\n\t\tif now.Sub(start) > time.Duration(durSec)*time.Second {\n\t\t\tbreak\n\t\t}\n\t}\n}\n```\n这是方法的核心,其实就是做无意义的计算,外加时间的判断,超过 duration 就结束。这样和上面的 perf top 信息就能对应起来。\n\n然后我们用 java 写一个同样的程序,再看看 `perf top`的情况:\n{% image /images/javaperftop.png perf top, fancybox:true %}\n<!-- ![perf top](../images/javaperftop.png) -->\n从这一大段显示来看,是不是看的一脸懵逼,很难发现到底是什么程序在占用CPU 资源。大家可以看一下源程序:\n``` java\nimport java.time.LocalDateTime;\n\npublic class Main {\n public static void main(String[] args) {\n int n = 10;\n\n for (int i = 0; i < 10; i++) {\n new Thread(new Runnable() {\n public void run() {\n while (true) {\n Math.sin(Math.random());\n LocalDateTime currentTime = LocalDateTime.now();\n }\n }\n }).start();\n }\n }\n}\n```\n这里的程序也是非常简单,启动 10 个线程,做一个无意义的数学运算,然后获取当前时间。从这段代码中是不是很难和上面`perf top`的显示关联起来? 原因也非常简单, 像Java 这种通过 JVM 来运行的应用程序,运行堆栈用的都是 JVM 内置的函数和堆栈管理。所以,从系统层面只能看到 JVM 的函数堆栈,而不能直接得到 Java 应用程序的堆栈。那我们好能通过 perf 去看到 java 相关的堆栈吗?答案是可以的。\n\n可以借助 [perf-map-agent](https://github.com/jvm-profiling-tools/perf-map-agent) 这样的开源工具,去生成和`perf` 工具一起使用的方法映射,但是需要做额外的一些配置。这里的方法大家可以自己探究,为什么不详细的讲这个呢,原因也简单,排查问题的工具多种多样,没必要在一棵树上吊死。\n\n#### jstack\n\n既然 perf top 去查看 JAVA 的调用栈不太方便,我们就直接上 java 提供的 jstack 工具去分析。\n- jstack -l pid > xxx.txt 需要注意的是,linux系统中往往会用不同的用户去执行不同的程序,此时可能需要通过sudu -u xxx jstack的形式\n- kill -3, jstack 用不了的情况下可以使用 kill -3 pid 的形式,堆栈默认会输出在系统日志中(根据不同的配置,信息也可能输出在其他地方,比如这个程序的日志中)。\n\n具体的操作步骤:\n1. `top -Hp $pid` 找到占用 CPU 的具体线程\n2. `jstack -l $pid > /tmp/$pid.jstack` 或者 `kill -3 $pid`将 java 进程的堆栈情况输出的日志中,然后根据 `top -Hp` 看到的线程信息在输出的堆栈日志中进行查找(`top -Hp` 输出的是 10 进制的 id,`jstack` 输出的是 16 进制的,在查找时注意进制转换)\n\n我们看下上面 java 程序的堆栈的信息:\n``` Lua\n2024-08-16 15:15:40\nFull thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode):\n\n\"Attach Listener\" #35 daemon prio=9 os_prio=0 tid=0x00007f52b4001000 nid=0x71f4 waiting on condition [0x0000000000000000]\n java.lang.Thread.State: RUNNABLE\n\n Locked ownable synchronizers:\n\t- None\n\n\"DestroyJavaVM\" #34 prio=5 os_prio=0 tid=0x00007f53e0009800 nid=0x1693 waiting on condition [0x0000000000000000]\n java.lang.Thread.State: RUNNABLE\n\n Locked ownable synchronizers:\n\t- None\n\n\"Thread-1\" #25 prio=5 os_prio=0 tid=0x00007f53e015a800 nid=0x16d9 runnable [0x00007f52f64e3000]\n java.lang.Thread.State: RUNNABLE\n\tat sun.misc.Unsafe.getObjectVolatile(Native Method)\n\tat java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:755)\n\tat java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:938)\n\tat java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:267)\n\tat java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:227)\n\tat java.time.ZoneRegion.ofId(ZoneRegion.java:120)\n\tat java.time.ZoneId.of(ZoneId.java:411)\n\tat java.time.ZoneId.of(ZoneId.java:359)\n\tat java.time.ZoneId.of(ZoneId.java:315)\n\tat java.util.TimeZone.toZoneId(TimeZone.java:556)\n\tat java.time.ZoneId.systemDefault(ZoneId.java:274)\n\tat java.time.Clock.systemDefaultZone(Clock.java:178)\n\tat java.time.LocalDateTime.now(LocalDateTime.java:180)\n\tat Main$1.run(Main.java:12)\n\tat java.lang.Thread.run(Thread.java:748)\n\n Locked ownable synchronizers:\n\t- None\n\n\"Thread-0\" #24 prio=5 os_prio=0 tid=0x00007f53e0159000 nid=0x16d8 runnable [0x00007f52f65e4000]\n java.lang.Thread.State: RUNNABLE\n\tat sun.misc.Unsafe.getObjectVolatile(Native Method)\n\tat java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:755)\n\tat java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:938)\n\tat java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:267)\n\tat java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:227)\n\tat java.time.ZoneRegion.ofId(ZoneRegion.java:120)\n\tat java.time.ZoneId.of(ZoneId.java:411)\n\tat java.time.ZoneId.of(ZoneId.java:359)\n\tat java.time.ZoneId.of(ZoneId.java:315)\n\tat java.util.TimeZone.toZoneId(TimeZone.java:556)\n\tat java.time.ZoneId.systemDefault(ZoneId.java:274)\n\tat java.time.Clock.systemDefaultZone(Clock.java:178)\n\tat java.time.LocalDateTime.now(LocalDateTime.java:180)\n\tat Main$1.run(Main.java:12)\n\tat java.lang.Thread.run(Thread.java:748)\n\n Locked ownable synchronizers:\n\t- None\n --- 10 个 thread\n\n\"Service Thread\" #23 daemon prio=9 os_prio=0 tid=0x00007f53e0143800 nid=0x16d6 runnable [0x0000000000000000]\n java.lang.Thread.State: RUNNABLE\n\n Locked ownable synchronizers:\n\t- None\n\n\"C2 CompilerThread1\" #6 daemon prio=9 os_prio=0 tid=0x00007f53e010e000 nid=0x16c5 waiting on condition [0x0000000000000000]\n java.lang.Thread.State: RUNNABLE\n\n Locked ownable synchronizers:\n\t- None\n --- 一大堆 C2 CompilerThread\n\n\"C2 CompilerThread0\" #5 daemon prio=9 os_prio=0 tid=0x00007f53e010b000 nid=0x16c4 waiting on condition [0x0000000000000000]\n java.lang.Thread.State: RUNNABLE\n\n Locked ownable synchronizers:\n\t- None\n\n\"Signal Dispatcher\" #4 daemon prio=9 os_prio=0 tid=0x00007f53e0109800 nid=0x16c3 runnable [0x0000000000000000]\n java.lang.Thread.State: RUNNABLE\n\n Locked ownable synchronizers:\n\t- None\n\n\"Finalizer\" #3 daemon prio=8 os_prio=0 tid=0x00007f53e00d8800 nid=0x16c2 in Object.wait() [0x00007f52f7bfa000]\n java.lang.Thread.State: WAITING (on object monitor)\n\tat java.lang.Object.wait(Native Method)\n\t- waiting on <0x000000008021a5e8> (a java.lang.ref.ReferenceQueue$Lock)\n\tat java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)\n\t- locked <0x000000008021a5e8> (a java.lang.ref.ReferenceQueue$Lock)\n\tat java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)\n\tat java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)\n\n Locked ownable synchronizers:\n\t- None\n\n\"Reference Handler\" #2 daemon prio=10 os_prio=0 tid=0x00007f53e00d3800 nid=0x16c1 in Object.wait() [0x00007f52f7cfb000]\n java.lang.Thread.State: WAITING (on object monitor)\n\tat java.lang.Object.wait(Native Method)\n\t- waiting on <0x0000000080218d38> (a java.lang.ref.Reference$Lock)\n\tat java.lang.Object.wait(Object.java:502)\n\tat java.lang.ref.Reference.tryHandlePending(Reference.java:191)\n\t- locked <0x0000000080218d38> (a java.lang.ref.Reference$Lock)\n\tat java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)\n\n Locked ownable synchronizers:\n\t- None\n\n\"VM Thread\" os_prio=0 tid=0x00007f53e00ca000 nid=0x16c0 runnable\n\n\"GC task thread#0 (ParallelGC)\" os_prio=0 tid=0x00007f53e001f000 nid=0x1694 runnable\n\n--- 一大堆 GC task thread\n\n\"VM Periodic Task Thread\" os_prio=0 tid=0x00007f53e0146000 nid=0x16d7 waiting on condition\n\nJNI global references: 202\n```\n我们通过 top -Hp 的信息就可以快速定位到 Thread-[0-9] 这几个线程,而每个线程的调用栈都是 `java.time.LocalDateTime.now`, 也说明了这个方法在不停消耗 CPU。(但是 jstack 只能捕获短时间或者瞬时的堆栈信息,没法处理长时间的,所以我们在获取时可以多打印几次或者使用其他方法)\n\n至于 jstack 的详细用法,请参考我的另一篇博客:[java问题定位](https://baixiaozhou.github.io/2024/08/13/JAVA%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/)\n\n除此之外,还有非常多的分析工具,pstack\\gstack\\strace\\gdb等等,大家可以自行探索使用\n\n#### 火焰图\n\n上面我们介绍了很多操作的命令和方法,那么有没有一种比较直观的方式能够直接看到各种方法执行的耗时比重等情况呢?火焰图就是为了解决这种情况而生的。\n\n火焰图的分类有很多,常用的包括:\n1. CPU 火焰图 (CPU Flame Graph)\n\t-\t描述:展示 CPU 在不同方法上的消耗情况,显示每个方法调用所占用的 CPU 时间。\n\t-\t用途:用于分析 CPU 性能瓶颈,识别哪些方法消耗了最多的 CPU 资源。\n\t-\t应用:Java、C++ 等多种编程语言的性能分析。\n2. 内存火焰图 (Memory Flame Graph)\n\t- 描述:展示内存分配情况,显示每个方法调用分配的内存量。\n - 用途:用于检测内存泄漏、过度内存分配问题,帮助优化内存使用。\n\t- 应用:常用于分析内存密集型应用,如 Java 应用的堆内存分析。\n3. I/O 火焰图 (I/O Flame Graph)\n\t-\t描述:展示 I/O 操作的耗时情况,显示不同方法的 I/O 操作占用的时间。\n\t-\t用途:用于分析应用程序的 I/O 性能,识别慢速或频繁的 I/O 操作。\n\t-\t应用:数据库查询、文件系统操作、网络通信等场景的性能调优。\n\n我们这里通过 [async-profiler](https://github.com/async-profiler/async-profiler) 对文章上面的java压测程序进行抓取(这个工具只能抓 java 的, 对于 golang 程序,可以利用 golang 提供的 pprof)\n\n```\ntar -xzf async-profiler-3.0-linux-x64.tar.gz\ncd async-profiler-3.0-linux-x64/bin\n./asprof -d 60 pid -f /tmp/javastress.html\n```\n我们用浏览器打开生成的 html 文件,可以看到如下的火焰图信息(可以在网页进行点击,查看更细节的方法)\n{% image /images/javafire.png java 程序的火焰图, fancybox:true %}\n<!-- ![java 程序的火焰图](../images/javafire.png) -->\n\n这样看起来就比 jstack这些信息更加直观一点。\n\n## 负载飙升\n\n### 负载的定义以及如何查看负载\n\n我们先看下系统负载的官方描述:\n```\nSystem load averages is the average number of processes that are either in a runnable or uninterruptable state. A process in arunnable state is either using the CPU or waiting to use the CPU. A process in uninterruptable state is waiting for some I/O access,eg waiting for disk. The averages are taken over the three time intervals. Load averages are not normalized for the number of CPUs ina system, so a load average of 1 means a single CPU system is loaded all the time while on a 4 CPU system it means it was idle 75% of the time.\n```\n\n系统负载平均值表示处于可运行或不可中断状态的进程的平均数量。处于可运行状态的进程要么正在使用 CPU,要么正在等待使用 CPU。处于不可中断状态的进程正在等待某些 I/O 访问,例如等待磁盘。这里的核心概念就是 loadavg 这个数值体现了某些特定状态进程的数量。\n\n那引申出两个问题:\n1. 进程的状态有哪些? 如何在 Linux 上查看进程状态\n2. 可运行和不可中断状态的进程具体含义是什么\n\n查看的方式,我们可以通过 ps 命令进行查看,比如通过`ps -auxf`, 我么可以看到有一列为 `STAT`,这列就代表该进程的状态:\n\n{% image /images/psauxf.png 进程状态 fancybox:true %}\n\n进程的状态和具体含义:\n- D uninterruptible sleep (usually IO)\n- R running or runnable (on run queue)\n- S interruptible sleep (waiting for an event to complete)\n- T stopped by job control signal\n- t stopped by debugger during the tracing\n- W paging (not valid since the 2.6.xx kernel)\n- X dead (should never be seen) \n- Z defunct (\"zombie\") process, terminated but not reaped by its parent\n\n这里我们看到处于不可中断的状态的进程和正在运行的进程分别为 `D` 和 `R`,换个说法,也就是说造成负载升高的原因也就是这两个状态的进程引起的。\n\n(插个题外话,按照官方的说法,X 状态的进程应该是不应该被看到的, 但是之前在腾讯云做ES的时候,偶然间碰到了一次,当时还截了个图用做留念😂,但是没有捕获到具体的信息)\n\n负载的指标可以通过 `top` 以及 `uptime` 指令获取\n```\n23:35:00 up 1 day, 46 min, 1 user, load average: 49.16, 18.35, 7.87\n```\n这里展示了 loadavg 的三个数值: 分别代表的含义是 1min、5min、15min 的系统平均负载\n\n那我们如何判断系统的负载是高是低呢?\n\n这里一般有个经验值,我们一般和 CPU 和核心数进行对比,一般负载在 CPU 核心的 70% 左右以及以下,对系统一般没什么影响,超过 70%,系统可能收到影响。但是这里还需要注意的一点就是,负载的比例在 70% 以下时不一定代表系统就没问题,举个简单的例子,如果一个系统上基本上没有业务在运行,那么负载基本上就在零点几左右,那么这种情况下,负载有升高不一定是合理的(后面举一个简单的例子)\n\n### 如何让系统负载飙高\n\n#### 纯计算任务对负载的影响\n\n既然说正在运行的进程会引起负载的变化,那么跑一些程序,让程序不停运行,那么自然而然就能构造出持续运行的进程了。\n我这里找了三台机器(64C),用我的压测工具先跑一些纯 CPU 的运算,然后观察下效果:\n\n测试分为三组,测试前关闭不必要的服务和进程:\n1. 10 并发 30min\n - `nohup ./sysstress cpu --cpu-number 10 --duration 30m > /dev/null 2>&1`\n2. 30 并发 30min\n - `nohup ./sysstress cpu --cpu-number 30 --duration 30m > /dev/null 2>&1`\n3. 60 并发 30min\n - `nohup ./sysstress cpu --cpu-number 60 --duration 30m > /dev/null 2>&1`\n\n效果如下:\n{% image /images/load10.png 10并发负载, fancybox:true %}\n{% image /images/load30.png 30并发负载, fancybox:true %}\n{% image /images/load60.png 60并发负载, fancybox:true %}\n<!-- ![10并发负载](../images/load10.png)\n![30并发负载](../images/load30.png)\n![60并发负载](../images/load60.png) -->\n\n从上述测试过程中,我们可以发现,在纯运算这种场景下,并发的量基本上和负载是对应的。也就是说随着 CPU的使用量 上涨,负载也会不断变高。\n\n#### 磁盘 IO 对负载的影响\n\n在刚才的例子中,我们看到了纯运算对负载的影响(R 进程的代表),然后在关于 D 进程的说明中,我们可以看到有一个比较明显的说明 `(usually IO)` ,即通常是 IO 引起的,那么接下来我们通过磁盘 IO 来测试一下\n\n测试分为三组,测试前关闭不必要的服务和进程:\n1. 10 并发 15min\n - `nohup ./sysstress io --operation read --filepath test.access.log -p 10 -d 15m > /dev/null 2>&1 &`\n2. 30 并发 15min\n - `nohup ./sysstress io --operation read --filepath test.access.log -p 30 -d 15m > /dev/null 2>&1 &`\n3. 60 并发 15min\n - `nohup ./sysstress io --operation read --filepath test.access.log -p 60 -d 15m > /dev/null 2>&1 &`\n\n效果如下:\n\n{% image /images/ioload10.png 10并发负载 fancybox:true %}\n{% image /images/ioload30.png 30并发负载 fancybox:true %}\n{% image /images/ioload60.png 60并发负载 fancybox:true %}\n\n我们也顺便看一下,60 并发下 CPU 的情况:\n\n{% image /images/io60c.png 60并发系统整体情况 fancybox:true %}\n\n这里我们可以观察到,系统的 CPU 基本上已经跑满了。us 和 sy 都占的比较多,但是这种读取非常有可能走到缓存中,我们想测绕过缓存,可以通过 DIRECT 的方式。\n\n但是上面的例子其实也证明了一件事,IO 的操作也是会导致负载产生飙升。\n\n那么问题来了,磁盘IO 和 CPU 操作都会导致系统负载飙升,那么负载飙升一定会是这两个原因吗?答案也是未必的,因为上述我们曾经提到过 D 状态的进程,到目前为止我们好像还没介绍过,那么我们来继续模拟,既然 D 状态的进程是 IO 操作引起的,普通的磁盘读写 IO 很难模拟,那我们就换个 IO 场景继续模拟 -- 网络 IO。\n\n#### 通过网络 IO 模拟 D 状态进程观察负载影响\n\n这里直接上一个模拟方法:\n- A 机器开启 NFS Server\n- B 机器作为客户端进行挂载\n- 断开网络\n- 疯狂 df -h\n\n详细的操作步骤:\n```\n# 安装 Centos\nsudo yum install nfs-utils\n\n# 服务端配置\nsudo mkdir -p /mnt/nfs_share\nsudo chown nobody:nogroup /mnt/nfs_share\nsudo chmod 755 /mnt/nfs_share\n## 打开 /etc/exports 配置,添加一行来定义共享目录及其权限。例如,将 /mnt/nfs_share 共享给网络 192.168.1.0/24,并提供读写权限:\n/mnt/nfs_share 192.168.1.0/24(rw,sync,no_subtree_check)\n\n## 启动 NFS\nsudo exportfs -a\nsudo systemctl restart nfs-kernel-server\nsudo systemctl enable nfs-kernel-server\n\n# 客户端配置\nsudo mkdir -p /mnt/nfs_client\nsudo mount -t nfs 192.168.1.100(server ip):/mnt/nfs_share /mnt/nfs_client\n## 验证\ndf -h /mnt/nfs_client\n\n# 断网模拟(客户端)\niptables -I INPUT -s serverip -j DROP\n\n# 持续(疯狂)执行:\ndu -sh /mnt/nfs_client\n```\n因为网络已经断掉,所以`du -sh /mnt/nfs_client`,而且这个程序没有自动退出或者报错,这样就导致程序无法顺利执行下去,继而阻塞住就变成了 D 状态的进程。\n\n基于这种模拟方法大家可以自行测试下,笔者之前做过一个场景,将一个两核心的 CPU负载干到了 200 多,但是因为**这种情况下更多是阻塞在网络中,所以此时的负载虽高,并不一定影响系统运行**。\n\n当然这只是其中一个例子,笔者曾经也因为见过 ping 操作阻塞导致的负载飙升,所以这种场景是多种多样的😂,大家有更多的例子也可以在下方留言,共同学习进步。\n\n\n### 负载飙升如何排查\n基于上面的例子和场景模拟,我们其实应该已经有一套基本的排查方法了,下面这张图是我个人的一些总结(图还会不断完善)\n\n{% image /images/loadhigh.png 负载高排查导图 fancybox:true %}\n\n## 内存占用过高\n\n我们先来了解一下有哪些常用的内存性能工具:\n| 工具 | 功能 | 用法 | 常用选项 |\n|--------|-----------------------------------------|-------------------------|-----------------------------------|\n| `free` | 显示系统的内存使用情况 | `free -h` | `-h`:以人类可读的格式显示 |\n| `top` | 实时显示系统的进程和内存使用情况 | `top` | 无 |\n| `htop` | 提供更友好的用户界面的进程监控工具 | `htop` | 无 |\n| `vmstat`| 报告虚拟内存、进程、CPU 活动等统计信息 | `vmstat 1` | `1`:每秒更新一次数据 |\n| `ps` | 查看系统中进程的内存使用情况 | `ps aux --sort=-%mem` | `aux`:显示所有用户的进程, `--sort=-%mem`:按内存使用量降序排列 |\n| `pmap` | 显示进程的内存映射 | `pmap -x <pid>` | `-x`:显示详细信息 |\n| `smem` | 提供详细的内存使用报告 | `smem -r` | 无 |\n| `/proc`| 提供系统和进程的详细内存信息 | `cat /proc/meminfo`<br>`cat /proc/<pid>/status` | 无 |\n\n### 如何判断内存占用过高\n\n系统内存的使用情况可以通过 `top` 以及 `free` 命令进行查看,以 `free -m`为例:\n\n{% image /images/free.png free查看内存信息 fancybox:true %}\n\n字段说明:\n- total: 系统总内存(RAM)或交换空间(Swap)的总量。\n- used: 已用内存或交换空间的量。这包括正在使用的内存以及系统缓存(对于内存)或已经使用的交换空间。\n- free: 空闲的内存或交换空间的量。表示当前未被任何进程使用的内存或交换空间。\n- shared: 被多个进程共享的内存量(对于内存)。通常是共享库和进程间通信的内存。\n- buff/cache: 用作文件系统缓存和缓冲区的内存量。这包括缓存的文件数据(cache)和缓冲区(buff)。这些内存可以被系统用作其他用途。\n- available: 可以分配给新启动的应用程序的内存量,而不需要交换到磁盘。这个数字更能反映系统的实际可用内存\n\n这里我们需要关注下,一般可用内存我们就是以 available 为准。当 available 的指标越来越小时,我们就需要关注系统内存的整体使用情况。\n\n这里还有一个需要关注的点:Swap,我们先看一下详细介绍:\n```\nSwap space in Linux is used when the amount of physical memory (RAM) is full. If the system \nneeds more memory resources and the RAM is full, inactive pages in memory are moved to the \nswap space. While swap space can help machines with a small amount of RAM, it should not be \nconsidered a replacement for more RAM. Swap space is located on hard drives, which have a \nslower access time than physical memory.Swap space can be a dedicated swap partition (recommended),\n a swap file, or a combination of swap partitions and swap files.\n```\n\nSWAP意思是交换,顾名思义,当某进程向OS请求内存发现不足时,OS会把内存中暂时不用的数据交换出去,放在SWAP分区中,这个过程称为SWAP OUT。当某进程又需要这些数据且OS发现还有空闲物理内存时,又会把SWAP分区中的数据交换回物理内存中,这个过程称为SWAP IN。\n简单来说,就是物理内存不足时,把磁盘空间当作 swap 分区,解决容量不足的问题。\n\n这里我们其实会发现一个问题,物理内存的读写性能肯定要比磁盘强不少,使用了磁盘空间作为内存存储,本身读写的性能就不高,还涉及到频繁的交换,反而增加了系统的负载,所以在线上环境中我们一般建议关闭 swap,具体的关闭方法请自行百度。\n\n### 如何定位内存占用高\n\n针对内存的占用,常规情况下(当然也有非常规情况),我们需要找到占用内存高的具体进程,详细的操作方式是:\n`top`的时候输入`M`进行排序,这样我们就可以看到系统中进程消耗内存的详细占比了:\n\n{% image /images/top-process.png fancybox:true %}\n\n这里我们重点关注两列: `%MEM` 和 `RES`, 前者是这个进程占用系统内存的百分比,后者是占用实际内存的大小。\n\n我们还是以 JAVA 和 GOLANG 程序为例来分析内存高该如何排查\n\n#### JAVA 占用内存过高\nJava 应用的内存管理依赖于 JVM (Java Virtual Machine),通常会涉及到堆内存(Heap)、非堆内存(Non-Heap)以及本地内存(Native Memory)。\n\n##### 堆内内存分析\n1. 查看 JVM 启动参数,尤其是 -Xms 和 -Xmx 选项,这两个参数分别设置了初始堆内存大小和最大堆内存大小。可以通过 ps 命令查看这些参数:`ps -auxf | grep 程序名称`\n2. 使用 jstat 查看 gc 情况, 实例:`jstat -gc pid 1000`\n{% image /images/gc.png gc 信息 fancybox:true %}\n如果 GC 频繁,那么我们需要进一步进行分析\n3. 通过 jmap 生成进程的堆内存快照 (在 JVM启动时,建议添加 OOM 时自动生成 heap dump: `-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumpfile`):\n``` bash\njmap -dump:live,format=b,file=heapdump.hprof <pid>\n```\n4. 拿到快照文件后,我们可以通过 MAT 或者 Jprofiler 这样的工具去具体分析\n以 MAT 为例:\n\n{% image /images/mat.jpeg MAT 分析 JAVA内存 fancybox:true %}\n\n##### 本地内存分析\n\nNMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。\nNMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。\n\n启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。\n启动命令: `-XX:NativeMemoryTracking=[off | summary | detail]`。\n- off:NMT 默认是关闭的;\n- summary:只收集子系统的内存使用的总计数据;\n- detail:收集每个调用点的内存使用数据。\n\n开启后,我们可以通过如下命令访问 NMT 内存:\n```\njcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]\n```\n\n##### 其他非堆内存\n主要包括:\n- JVM 自身运行占用的空间;\n- 线程栈分配占用的系统内存;\n- DirectByteBuffer 占用的内存;\n- JNI 里分配的内存;\n- Java 8 开始的元数据空间;\n- NIO 缓存\n- Unsafe 调用分配的内存;\n- codecache\n\n对于这些问题,遇到内存升高的情况较少,所以也没有进行过详细的排查,如果有读者朋友有做过类似的排查,可以在下面留言讨论。\n\n#### golang程序占用内存高\n\ngolang 内置了 pprof 性能分析工具,支持 CPU、内存(Heap)、栈、协程等的分析。可以通过 HTTP 服务暴露 pprof 接口,并使用浏览器或 go tool pprof 进行分析:\n在代码中引入 net/http/pprof 包:\n```golang\nimport _ \"net/http/pprof\"\n\nfunc main() {\n go func() {\n log.Println(http.ListenAndServe(\"localhost:6060\", nil))\n }()\n // 你的应用代码\n}\n```\n启动应用后,通过浏览器访问 `http://localhost:6060/debug/pprof/heap` 下载堆内存快照,并使用以下命令分析:\n```bash\ngo tool pprof -http=:8080 heap.prof\n```\n这将启动一个 Web 界面,供你分析内存占用的热点。\n具体的使用方式大家可以自行研究。\n\n#### 基于内存的文件系统占用\n\n在上文中,我们说了常规情况下的排查,那来个非常规的,先上张图:\n\n{% image /images/tmp-mem.png 内存占用模拟 fancybox:true %}\n\n这里大家看看到,内存总量大概有 250 个 G,可用内存只有 126G,但是我们通过 top 看内存占比跟实际的数值相差甚远。\n\n这种场景是不是非常奇怪呢?没有进程占用内存,但是内存被消耗了,这种情况下,很有可能跟基于内存的文件系统有关。\n\n这里我说一下模拟方法:\n```\n# systemd 的包中会默认自带一个 tmp.mount服务,这个服务默认是关闭的(默认是总内存的一半)\n# 这个服务本质上是一个基于内存的文件系统(把内存当磁盘使,在上面可以创建文件)\nsystemctl start tmp.mount\n\n# 然后我们通过 df -h /tmp 可以看到\nFilesystem Size Used Avail Use% Mounted on\ntmpfs 126G 111G 16G 88% /tmp\n\n这里挂载了/tmp 目录,文件系统是 tmpfs\n\n# 然后我们进入 /tmp目录\n# 模拟文件创建\ndd if=/dev/zero of=test.txt bs=G count=100\n\n# 然后我们就可以看到文件创建成功,然后 free -g 就可以看到内存成功消耗了100G😂\n```\n\n这种场景也是我之前在排查问题的时候遇到的,大家有其他类似场景也可以进行补充\n\n## 磁盘问题\n\n线上的磁盘问题,除了磁盘故障(坏块、文件系统损坏、RAID 等)之外,常见的比较多的问题可以分为两大类:磁盘空间不足和 IO 性能问题\n\n### 磁盘空间不足\n对于这类问题,一般情况下,本身可能就是数据文件、日志信息等过多导致的**真实占用**,还有一类是文件句柄泄露导致磁盘没有释放从而占据了多余的磁盘空间\n\n对于这类问题我们一般通过以下手段进行排查,假如说我们现在接到告警 说根目录的磁盘空间不足了\n\n1. 通过`df -h`命令查看磁盘分区的使用情况。\n2. 进入 `\"/\"` 目录下,可以对常用的目录进行 `du -sh`操作,然后进行简单的相加\n - 如果加起来的磁盘空间,和分区使用的磁盘空间相差不多,那就基本上说明磁盘空间是被真实占用的,找到占用高的目录继续通过`du -sh` 目录,不断递归。或者想快速找到磁盘中超过 1G 的文件,可以通过`find 目录 -type f -size +1G` 这样的方式。\n 如果想快速统计到所有子目录的磁盘信息,那么可以通过这样的方式进行操作\n ```\n du -ah / 2>/dev/null | grep -E '^([0-9]+([KMGT]?)|([0-9]+(\\.[0-9]+)?[KMGT]))' | sort -rh | head -10\n ```\n - 如果加起来的磁盘空间,和分区使用的磁盘空间相差的比较大,那么可能存在句柄泄露,比如日志文件本身被删掉,但是句柄没有释放,可以通过一下方式:\n ```\n #列出进打开文件最多的 10 个进程;\n lsof +L1 | awk '{print $2}' | sort | uniq -c | sort -rn | head -n 10\n\n # 查看某个进程的占用\n lsof -p $pid (查看是否存在已经删除的文件句柄没有释放,可以通过 grep deleted 过滤)\n\n # 或者 ls /proc/$pid/fd 进行查看\n ```\n### 磁盘IO性能问题\n\n我们可以通过 iostat 看下磁盘整体的读写情况:\n\n{% image /images/iostat.png iostat fancybox:true %}\n\n详细的介绍如下:\n1. CPU 使用情况\n\n| Metric | Description |\n|-----------|-------------------------------------------|\n| %user | 用户模式下消耗的 CPU 时间百分比 |\n| %nice | 调整优先级的用户模式下的 CPU 时间百分比 |\n| %system | 内核模式下消耗的 CPU 时间百分比 |\n| %iowait | CPU 等待 I/O 操作完成的时间百分比 |\n| %steal | 等待虚拟 CPU 被实际 CPU 服务的时间百分比 |\n| %idle | CPU 空闲时间百分比 |\n\n2. 设备 I/O 使用情况\n\n| Metric | Description |\n|---------------|---------------------------------------|\n| Device | 设备名称 |\n| tps | 每秒传输数(I/O 请求数) |\n| kB_read/s | 每秒读取的 kB 数 |\n| kB_wrtn/s | 每秒写入的 kB 数 |\n| kB_read | 读取的 kB 总数 |\n| kB_wrtn | 写入的 kB 总数 |\n| rrqm/s | 每秒进行的合并读请求数 |\n| wrqm/s | 每秒进行的合并写请求数 |\n| r/s | 每秒完成的读请求数 |\n| w/s | 每秒完成的写请求数 |\n| rMB/s | 每秒读取的 MB 数 |\n| wMB/s | 每秒写入的 MB 数 |\n| avgrq-sz | 平均每次 I/O 请求的数据大小 |\n| avgqu-sz | 平均 I/O 队列长度 |\n| await | 平均每次 I/O 操作的等待时间 |\n| svctm | 平均每次 I/O 操作的服务时间 |\n| %util | 设备 I/O 活动时间百分比(表示设备忙碌度)|\n\n通过这个我们就可以获取到磁盘的整体情况。\n\n当我们想查看进程占用的 io 情况时,可以通过 `iotop`命令进行查看,如下:\n\n{% image /images/iotop.png iotop fancybox:true %}\n\n结合上文讲的一些堆栈分析工具,推测到进程具体在做那些操作然后针对性进行处理。\n","slug":"线上问题排查方法汇总","published":1,"updated":"2024-09-05T08:44:16.056Z","_id":"cm00w1n8x000ap5on3hlyhfk3","comments":1,"layout":"post","photos":[],"content":"<span id=\"more\"></span>\n\n<div class=\"tag-plugin quot\"><p class=\"content\" type=\"text\"><span class=\"empty\"></span><span class=\"text\">参考文档</span><span class=\"empty\"></span></p></div>\n\n<p><a href=\"https://www.linuxjournal.com/article/9001\">Examining Load Average</a><br><a href=\"https://community.tenable.com/s/article/What-is-CPU-Load-Average\">What-is-CPU-Load-Average</a><br><a href=\"www.brendangregg.com\">Brendan Gregg个人网站</a></p>\n<h2 id=\"写在前面\"><a href=\"#写在前面\" class=\"headerlink\" title=\"写在前面\"></a>写在前面</h2><p>在很多文章中,每当提到去解决线上问题的时候,大部分的处理方式就是登录环境,哐哐各种敲命令。操作本身没什么问题,但是对于很多人而言,我觉得这种做法其实是本末倒置的,过于在乎去快速抓住重点问题,而忽略了从全局去看问题。那么如果最开始不去操作各种命令,那应该干什么呢?</p>\n<p><em><strong>看监控!!!!</strong></em></p>\n<p>首先不要觉得这个是废话,对于很多场景来说,业务规模是不断变化的,有的时候并发超过了极限的性能,那么这种情况下都没有必要去后台进行各种查询。举个简单的例子,假如说某套业务系统,本身只能支持 500 并发,现在实际上的量到了 2000,导致线上各种内存、CPU、负载的告警,这种情况下还有必要去后台敲<code>top</code>、<code>free</code>吗?答案当然是否定的,这种情况下,就需要考虑对业务系统进行快速的扩容等。</p>\n<p>看监控的意义在于尽可能的找到更多的性能瓶颈或者异常的点,从全局出发,对系统当前存在的问题和异常点有全面的了解。</p>\n<p>监控系统多种多样,从较早的 zabbix 到现在比较流行的prometheus+grafana(举两个常用的例子),对于系统业务都有比较完善的监控,可以帮助我们更加具体的了解到系统运行全貌。如果你对这些都不喜欢,那么你自己写一个监控系统也没什么问题。</p>\n<p>当我们看完监控之后(假设你真的看了),接下来进入实际操作环节,我会从这些指标的详细含义出发,然后尽可能地将各种处理方式分享给大家。</p>\n<h2 id=\"Linux性能谱图\"><a href=\"#Linux性能谱图\" class=\"headerlink\" title=\"Linux性能谱图\"></a>Linux性能谱图</h2><p>在分析问题前,我们首先需要明确 Linux 有哪些性能分析工具,我们先上一下LINUX 性能专家 Brendan Gregg 总结的图(大家如果对性能分析等感兴趣的话,可以认真看下这位大佬的个人网站):</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"https://www.brendangregg.com/Perf/linux_observability_tools.png\" alt=\"Linux Performance Observability Tool\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">Linux Performance Observability Tool</span></div></div>\n\n<p>上面这张图是引用大佬文章的图,原文链接在这里: <a href=\"https://www.brendangregg.com/linuxperf.html\">https://www.brendangregg.com/linuxperf.html</a></p>\n<h2 id=\"CPU使用率飙升\"><a href=\"#CPU使用率飙升\" class=\"headerlink\" title=\"CPU使用率飙升\"></a>CPU使用率飙升</h2><h3 id=\"如何让CPU使用率飙升\"><a href=\"#如何让CPU使用率飙升\" class=\"headerlink\" title=\"如何让CPU使用率飙升\"></a>如何让CPU使用率飙升</h3><p>这个问题其实很简单,只要有计算任务一直存在,让 CPU 一直处于繁忙之中,那么 CPU 必然飙升。我们可以通过一系列的工具去模拟这个情况。</p>\n<p><a href=\"https://github.com/baixiaozhou/SysStress\">github SysStress</a> 这是我自己用 golang 写的压测工具(还在开发中,可以点个 star 让我更有动力😂)</p>\n<p>使用方法:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">./sysstress cpu --cpu-number 10 --duration 10m</span><br></pre></td></tr></table></figure>\n<p>这个就是模拟占用 10 核心的 CPU 并持续 10min,当然大家也可以用其他的压测工具,比如<code>stress-ng</code></p>\n<h3 id=\"如何判断和发现CPU使用率飙升\"><a href=\"#如何判断和发现CPU使用率飙升\" class=\"headerlink\" title=\"如何判断和发现CPU使用率飙升\"></a>如何判断和发现CPU使用率飙升</h3><p>首先我们先看一下,跟 CPU 使用率相关的有哪些指标。我们通过 <code>top</code> 命令就可以看到具体的信息</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/top.png\" alt=\"top\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">top</span></div></div>\n<!-- ![top](../images/top.png) -->\n<p>这些输出中有一行是 <code>%Cpu(s)</code>, 这行展示了 CPU 的整体使用情况,是一个百分比的形式,我们详细阐述下这几个字段的含义</p>\n<figure class=\"highlight plaintext\"><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><td class=\"code\"><pre><span class=\"line\">us, user : time running un-niced user processes 未降低优先级的用户进程所占用的时间</span><br><span class=\"line\">sy, system : time running kernel processes 内核进程所占用的时间</span><br><span class=\"line\">ni, nice : time running niced user processes 降低优先级的用户进程所占用的时间</span><br><span class=\"line\">id, idle : time spent in the kernel idle handler 空闲的时间</span><br><span class=\"line\">wa, IO-wait : time waiting for I/O completion 等待 I/O 操作完成所花费的时间</span><br><span class=\"line\">hi : time spent servicing hardware interrupts 处理硬件中断所花费的时间</span><br><span class=\"line\">si : time spent servicing software interrupts 处理软件中断所花费的时间</span><br><span class=\"line\">st : time stolen from this vm by the hypervisor 被虚拟机管理程序从此虚拟机中窃取的时间</span><br></pre></td></tr></table></figure>\n<p>在这些指标中,一般关注的比较多的就是 us、sy、id、wa(其他几个指标很高的情况我个人目前基本上没有遇到过)</p>\n<p>上述指标反映了系统整体的 CPU 情况。而程序在操作系统中实际上是以一个个的进程存在的,那我们如何确定到占用 CPU 高的进程呢?让我们的目光从 top 的头部信息往下移动,下面就展示了详细的进程信息</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/top-process.png\" data-fancybox=\"true\"/></div></div>\n<!-- !cess](../imagescess.png) -->\n\n<p>这些程序默认是按照 CPU 的使用率从高到底进行排序的,当然你也可以通过在<code>top</code>的时候输入<code>P</code>进行排序,这样我们就可以看到系统中消耗 CPU 资源的详细进程信息</p>\n<p>上面是我通过 <code>./sysstress cpu --cpu-number 10 --duration 10m</code> 压测程序跑出来的,可以看到这里的 sysstress 程序占用了 1002 的 %CPU,也就是说基本上是 10 个核心,那我们跑一个更高的,将<code>--cpu-number</code>加到 60 看看发生了什么</p>\n<!-- ![stress-cpu](../images/stress-cpu.png) -->\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/stress-cpu.png\" alt=\"stress-cpu\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">stress-cpu</span></div></div>\n\n<p>我们可以看到这次%CPU打到了 6000,那很多人就好奇我日常的程序跑到多高算高呢?</p>\n<p>这里我们需要明确一点,现在的服务器绝大部分都是多核心 CPU(1C2G这种自己用来玩的忽略),CPU 的核心数决定了我们程序在同一时间能够执行多少个线程,也就是说,这个高不高是相对于机器配置而言的。如果你的机器只有 16C,那么单个进程占用的 %CPU 到 1000,那么其实已经算是比较高了。如果是 256C 的CPU(土豪级配置),那么单个进程占用的 %CPU 到 6000,对于系统的稳定性影响就没有那么大了。</p>\n<p>上述我们说的情况是进程占用 CPU 对整个系统的影响,那么进程占用的 CPU 对系统的影响不大就代表这个程序一定没有问题吗?答案显然是未必的。</p>\n<p>我们还是要回归到业务本身,如果进程的 CPU 占用在业务变动不大的情况下,发生了异常波动,或者正常情况下业务不会消耗这么高的 CPU,那么我们就需要继续排查了。</p>\n<h3 id=\"如何确定CPU飙升的根源\"><a href=\"#如何确定CPU飙升的根源\" class=\"headerlink\" title=\"如何确定CPU飙升的根源\"></a>如何确定CPU飙升的根源</h3><p>这个问题的 核心是 CPU 上在运行什么东西。 多核心CPU 下,每个核心都可以执行不同的程序,我们如何确定一个进程中那些方法在消耗 CPU 呢?从而引申下面详细的问题:</p>\n<ol>\n<li>程序的调用栈是什么样的?</li>\n<li>调用栈信息中哪些是需要关注的,那些是可以忽略的?</li>\n<li>热点函数是什么?</li>\n</ol>\n<p>老话说得好,”工欲善其事,必先利其器”, 我们需要这些东西,就必须了解到什么样的工具可以拿到上面我提到的一些信息。接下来我将通过常用的后端语言:<code>golang</code> 和 <code>java</code> 为例构造一些高 CPU 的程序来进行展示。</p>\n<h4 id=\"perf命令\"><a href=\"#perf命令\" class=\"headerlink\" title=\"perf命令\"></a>perf命令</h4><p><strong>perf是一款Linux性能分析工具。Linux性能计数器是一个新的基于内核的子系统,它提供一个性能分析框架,比如硬件(CPU、PMU(Performance Monitoring Unit))功能和软件(软件计数器、tracepoint)功能。</strong></p>\n<p>安装:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">yum install perf #Centos</span><br></pre></td></tr></table></figure>\n<p>安装完成后,我们可以首先看下 <code>perf</code>的用法,这里不展开具体用法,只列出我平常使用的几个命令:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">top System profiling tool. #对系统性能进行实时分析。</span><br><span class=\"line\">record Run a command and record its profile into perf.data #收集采样信息</span><br><span class=\"line\">report Read perf.data (created by perf record) and display the profile #分析采样信息,和record配合使用</span><br></pre></td></tr></table></figure>\n<p>record 和 report 的使用更多在于 dump 当前环境的信息用于后续分析,如果在自己环境上测试,可以用 top 进行一些简单的实时分析(类似于 top 命令)。</p>\n<p>还是用之前的压测工具,我们模拟一个 10 核心的 10min 的压测场景</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">nohup ./sysstress cpu --cpu-number 10 --duration 10m > /dev/null 2>&1 &</span><br></pre></td></tr></table></figure>\n<p>执行这个语句,让压测程序在后台执行,然后我们通过<code>perf top</code>查看具体的情况(可以通过-p 指定 pid)</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/perftop.png\" alt=\"perf top,\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">perf top,</span></div></div>\n<!-- ![perf top](../images/perftop.png) -->\n\n<p>从截图的信息中我们可以看到占用资源最多的一些方法,包括 sysstress 进程的各种方法(从图片中基本上就可以确定高消耗的方法在哪里)以及底层的 <code>__vdso_clock_gettime</code>, 那再结合压测工具的代码分析下:</p>\n<figure class=\"highlight golang\"><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><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"title\">burnCpu</span><span class=\"params\">(wg *sync.WaitGroup, start time.Time, durSec <span class=\"type\">int64</span>)</span></span> {</span><br><span class=\"line\">\t<span class=\"keyword\">defer</span> wg.Done()</span><br><span class=\"line\">\t<span class=\"keyword\">for</span> {</span><br><span class=\"line\">\t\t_ = <span class=\"number\">1</span> * <span class=\"number\">1</span></span><br><span class=\"line\">\t\tnow := time.Now()</span><br><span class=\"line\">\t\t<span class=\"keyword\">if</span> now.Sub(start) > time.Duration(durSec)*time.Second {</span><br><span class=\"line\">\t\t\t<span class=\"keyword\">break</span></span><br><span class=\"line\">\t\t}</span><br><span class=\"line\">\t}</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>这是方法的核心,其实就是做无意义的计算,外加时间的判断,超过 duration 就结束。这样和上面的 perf top 信息就能对应起来。</p>\n<p>然后我们用 java 写一个同样的程序,再看看 <code>perf top</code>的情况:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/javaperftop.png\" alt=\"perf top,\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">perf top,</span></div></div>\n<!-- ![perf top](../images/javaperftop.png) -->\n<p>从这一大段显示来看,是不是看的一脸懵逼,很难发现到底是什么程序在占用CPU 资源。大家可以看一下源程序:</p>\n<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><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">import</span> java.time.LocalDateTime;</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">public</span> <span class=\"keyword\">class</span> <span class=\"title class_\">Main</span> {</span><br><span class=\"line\"> <span class=\"keyword\">public</span> <span class=\"keyword\">static</span> <span class=\"keyword\">void</span> <span class=\"title function_\">main</span><span class=\"params\">(String[] args)</span> {</span><br><span class=\"line\"> <span class=\"type\">int</span> <span class=\"variable\">n</span> <span class=\"operator\">=</span> <span class=\"number\">10</span>;</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">for</span> (<span class=\"type\">int</span> <span class=\"variable\">i</span> <span class=\"operator\">=</span> <span class=\"number\">0</span>; i < <span class=\"number\">10</span>; i++) {</span><br><span class=\"line\"> <span class=\"keyword\">new</span> <span class=\"title class_\">Thread</span>(<span class=\"keyword\">new</span> <span class=\"title class_\">Runnable</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\">while</span> (<span class=\"literal\">true</span>) {</span><br><span class=\"line\"> Math.sin(Math.random());</span><br><span class=\"line\"> <span class=\"type\">LocalDateTime</span> <span class=\"variable\">currentTime</span> <span class=\"operator\">=</span> LocalDateTime.now();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }).start();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>这里的程序也是非常简单,启动 10 个线程,做一个无意义的数学运算,然后获取当前时间。从这段代码中是不是很难和上面<code>perf top</code>的显示关联起来? 原因也非常简单, 像Java 这种通过 JVM 来运行的应用程序,运行堆栈用的都是 JVM 内置的函数和堆栈管理。所以,从系统层面只能看到 JVM 的函数堆栈,而不能直接得到 Java 应用程序的堆栈。那我们好能通过 perf 去看到 java 相关的堆栈吗?答案是可以的。</p>\n<p>可以借助 <a href=\"https://github.com/jvm-profiling-tools/perf-map-agent\">perf-map-agent</a> 这样的开源工具,去生成和<code>perf</code> 工具一起使用的方法映射,但是需要做额外的一些配置。这里的方法大家可以自己探究,为什么不详细的讲这个呢,原因也简单,排查问题的工具多种多样,没必要在一棵树上吊死。</p>\n<h4 id=\"jstack\"><a href=\"#jstack\" class=\"headerlink\" title=\"jstack\"></a>jstack</h4><p>既然 perf top 去查看 JAVA 的调用栈不太方便,我们就直接上 java 提供的 jstack 工具去分析。</p>\n<ul>\n<li>jstack -l pid > xxx.txt 需要注意的是,linux系统中往往会用不同的用户去执行不同的程序,此时可能需要通过sudu -u xxx jstack的形式</li>\n<li>kill -3, jstack 用不了的情况下可以使用 kill -3 pid 的形式,堆栈默认会输出在系统日志中(根据不同的配置,信息也可能输出在其他地方,比如这个程序的日志中)。</li>\n</ul>\n<p>具体的操作步骤:</p>\n<ol>\n<li><code>top -Hp $pid</code> 找到占用 CPU 的具体线程</li>\n<li><code>jstack -l $pid > /tmp/$pid.jstack</code> 或者 <code>kill -3 $pid</code>将 java 进程的堆栈情况输出的日志中,然后根据 <code>top -Hp</code> 看到的线程信息在输出的堆栈日志中进行查找(<code>top -Hp</code> 输出的是 10 进制的 id,<code>jstack</code> 输出的是 16 进制的,在查找时注意进制转换)</li>\n</ol>\n<p>我们看下上面 java 程序的堆栈的信息:</p>\n<figure class=\"highlight lua\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"number\">2024</span><span class=\"number\">-08</span><span class=\"number\">-16</span> <span class=\"number\">15</span>:<span class=\"number\">15</span>:<span class=\"number\">40</span></span><br><span class=\"line\">Full thread <span class=\"built_in\">dump</span> Java HotSpot(TM) <span class=\"number\">64</span>-Bit Server VM (<span class=\"number\">25.221</span>-b11 mixed mode):</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Attach Listener"</span> #<span class=\"number\">35</span> daemon prio=<span class=\"number\">9</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f52b4001000</span> nid=<span class=\"number\">0x71f4</span> waiting on condition [<span class=\"number\">0x0000000000000000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"DestroyJavaVM"</span> #<span class=\"number\">34</span> prio=<span class=\"number\">5</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e0009800</span> nid=<span class=\"number\">0x1693</span> waiting on condition [<span class=\"number\">0x0000000000000000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Thread-1"</span> #<span class=\"number\">25</span> prio=<span class=\"number\">5</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e015a800</span> nid=<span class=\"number\">0x16d9</span> runnable [<span class=\"number\">0x00007f52f64e3000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\">\tat sun.misc.Unsafe.getObjectVolatile(Native Method)</span><br><span class=\"line\">\tat java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:<span class=\"number\">755</span>)</span><br><span class=\"line\">\tat java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:<span class=\"number\">938</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:<span class=\"number\">267</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:<span class=\"number\">227</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneRegion.ofId(ZoneRegion.java:<span class=\"number\">120</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.of(ZoneId.java:<span class=\"number\">411</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.of(ZoneId.java:<span class=\"number\">359</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.of(ZoneId.java:<span class=\"number\">315</span>)</span><br><span class=\"line\">\tat java.util.TimeZone.toZoneId(TimeZone.java:<span class=\"number\">556</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.systemDefault(ZoneId.java:<span class=\"number\">274</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.Clock.systemDefaultZone(Clock.java:<span class=\"number\">178</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.LocalDateTime.now(LocalDateTime.java:<span class=\"number\">180</span>)</span><br><span class=\"line\">\tat Main$<span class=\"number\">1.</span>run(Main.java:<span class=\"number\">12</span>)</span><br><span class=\"line\">\tat java.lang.Thread.run(Thread.java:<span class=\"number\">748</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Thread-0"</span> #<span class=\"number\">24</span> prio=<span class=\"number\">5</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e0159000</span> nid=<span class=\"number\">0x16d8</span> runnable [<span class=\"number\">0x00007f52f65e4000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\">\tat sun.misc.Unsafe.getObjectVolatile(Native Method)</span><br><span class=\"line\">\tat java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:<span class=\"number\">755</span>)</span><br><span class=\"line\">\tat java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:<span class=\"number\">938</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:<span class=\"number\">267</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:<span class=\"number\">227</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneRegion.ofId(ZoneRegion.java:<span class=\"number\">120</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.of(ZoneId.java:<span class=\"number\">411</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.of(ZoneId.java:<span class=\"number\">359</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.of(ZoneId.java:<span class=\"number\">315</span>)</span><br><span class=\"line\">\tat java.util.TimeZone.toZoneId(TimeZone.java:<span class=\"number\">556</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.systemDefault(ZoneId.java:<span class=\"number\">274</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.Clock.systemDefaultZone(Clock.java:<span class=\"number\">178</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.LocalDateTime.now(LocalDateTime.java:<span class=\"number\">180</span>)</span><br><span class=\"line\">\tat Main$<span class=\"number\">1.</span>run(Main.java:<span class=\"number\">12</span>)</span><br><span class=\"line\">\tat java.lang.Thread.run(Thread.java:<span class=\"number\">748</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"> <span class=\"comment\">--- 10 个 thread</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Service Thread"</span> #<span class=\"number\">23</span> daemon prio=<span class=\"number\">9</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e0143800</span> nid=<span class=\"number\">0x16d6</span> runnable [<span class=\"number\">0x0000000000000000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"C2 CompilerThread1"</span> #<span class=\"number\">6</span> daemon prio=<span class=\"number\">9</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e010e000</span> nid=<span class=\"number\">0x16c5</span> waiting on condition [<span class=\"number\">0x0000000000000000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"> <span class=\"comment\">--- 一大堆 C2 CompilerThread</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"C2 CompilerThread0"</span> #<span class=\"number\">5</span> daemon prio=<span class=\"number\">9</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e010b000</span> nid=<span class=\"number\">0x16c4</span> waiting on condition [<span class=\"number\">0x0000000000000000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Signal Dispatcher"</span> #<span class=\"number\">4</span> daemon prio=<span class=\"number\">9</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e0109800</span> nid=<span class=\"number\">0x16c3</span> runnable [<span class=\"number\">0x0000000000000000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Finalizer"</span> #<span class=\"number\">3</span> daemon prio=<span class=\"number\">8</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e00d8800</span> nid=<span class=\"number\">0x16c2</span> <span class=\"keyword\">in</span> Object.wait() [<span class=\"number\">0x00007f52f7bfa000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: WAITING (on object monitor)</span><br><span class=\"line\">\tat java.lang.Object.wait(Native Method)</span><br><span class=\"line\">\t- waiting on <<span class=\"number\">0x000000008021a5e8</span>> (a java.lang.ref.ReferenceQueue$Lock)</span><br><span class=\"line\">\tat java.lang.ref.ReferenceQueue.<span class=\"built_in\">remove</span>(ReferenceQueue.java:<span class=\"number\">144</span>)</span><br><span class=\"line\">\t- locked <<span class=\"number\">0x000000008021a5e8</span>> (a java.lang.ref.ReferenceQueue$Lock)</span><br><span class=\"line\">\tat java.lang.ref.ReferenceQueue.<span class=\"built_in\">remove</span>(ReferenceQueue.java:<span class=\"number\">165</span>)</span><br><span class=\"line\">\tat java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:<span class=\"number\">216</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Reference Handler"</span> #<span class=\"number\">2</span> daemon prio=<span class=\"number\">10</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e00d3800</span> nid=<span class=\"number\">0x16c1</span> <span class=\"keyword\">in</span> Object.wait() [<span class=\"number\">0x00007f52f7cfb000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: WAITING (on object monitor)</span><br><span class=\"line\">\tat java.lang.Object.wait(Native Method)</span><br><span class=\"line\">\t- waiting on <<span class=\"number\">0x0000000080218d38</span>> (a java.lang.ref.Reference$Lock)</span><br><span class=\"line\">\tat java.lang.Object.wait(Object.java:<span class=\"number\">502</span>)</span><br><span class=\"line\">\tat java.lang.ref.Reference.tryHandlePending(Reference.java:<span class=\"number\">191</span>)</span><br><span class=\"line\">\t- locked <<span class=\"number\">0x0000000080218d38</span>> (a java.lang.ref.Reference$Lock)</span><br><span class=\"line\">\tat java.lang.ref.Reference$ReferenceHandler.run(Reference.java:<span class=\"number\">153</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"VM Thread"</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e00ca000</span> nid=<span class=\"number\">0x16c0</span> runnable</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"GC task thread#0 (ParallelGC)"</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e001f000</span> nid=<span class=\"number\">0x1694</span> runnable</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">--- 一大堆 GC task thread</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"VM Periodic Task Thread"</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e0146000</span> nid=<span class=\"number\">0x16d7</span> waiting on condition</span><br><span class=\"line\"></span><br><span class=\"line\">JNI global references: <span class=\"number\">202</span></span><br></pre></td></tr></table></figure>\n<p>我们通过 top -Hp 的信息就可以快速定位到 Thread-[0-9] 这几个线程,而每个线程的调用栈都是 <code>java.time.LocalDateTime.now</code>, 也说明了这个方法在不停消耗 CPU。(但是 jstack 只能捕获短时间或者瞬时的堆栈信息,没法处理长时间的,所以我们在获取时可以多打印几次或者使用其他方法)</p>\n<p>至于 jstack 的详细用法,请参考我的另一篇博客:<a href=\"https://baixiaozhou.github.io/2024/08/13/JAVA%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/\">java问题定位</a></p>\n<p>除此之外,还有非常多的分析工具,pstack\\gstack\\strace\\gdb等等,大家可以自行探索使用</p>\n<h4 id=\"火焰图\"><a href=\"#火焰图\" class=\"headerlink\" title=\"火焰图\"></a>火焰图</h4><p>上面我们介绍了很多操作的命令和方法,那么有没有一种比较直观的方式能够直接看到各种方法执行的耗时比重等情况呢?火焰图就是为了解决这种情况而生的。</p>\n<p>火焰图的分类有很多,常用的包括:</p>\n<ol>\n<li>CPU 火焰图 (CPU Flame Graph)<ul>\n<li> 描述:展示 CPU 在不同方法上的消耗情况,显示每个方法调用所占用的 CPU 时间。</li>\n<li> 用途:用于分析 CPU 性能瓶颈,识别哪些方法消耗了最多的 CPU 资源。</li>\n<li> 应用:Java、C++ 等多种编程语言的性能分析。</li>\n</ul>\n</li>\n<li>内存火焰图 (Memory Flame Graph)<ul>\n<li>描述:展示内存分配情况,显示每个方法调用分配的内存量。</li>\n<li>用途:用于检测内存泄漏、过度内存分配问题,帮助优化内存使用。</li>\n<li>应用:常用于分析内存密集型应用,如 Java 应用的堆内存分析。</li>\n</ul>\n</li>\n<li>I/O 火焰图 (I/O Flame Graph)<ul>\n<li> 描述:展示 I/O 操作的耗时情况,显示不同方法的 I/O 操作占用的时间。</li>\n<li> 用途:用于分析应用程序的 I/O 性能,识别慢速或频繁的 I/O 操作。</li>\n<li> 应用:数据库查询、文件系统操作、网络通信等场景的性能调优。</li>\n</ul>\n</li>\n</ol>\n<p>我们这里通过 <a href=\"https://github.com/async-profiler/async-profiler\">async-profiler</a> 对文章上面的java压测程序进行抓取(这个工具只能抓 java 的, 对于 golang 程序,可以利用 golang 提供的 pprof)</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">tar -xzf async-profiler-3.0-linux-x64.tar.gz</span><br><span class=\"line\">cd async-profiler-3.0-linux-x64/bin</span><br><span class=\"line\">./asprof -d 60 pid -f /tmp/javastress.html</span><br></pre></td></tr></table></figure>\n<p>我们用浏览器打开生成的 html 文件,可以看到如下的火焰图信息(可以在网页进行点击,查看更细节的方法)</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/javafire.png\" alt=\"java 程序的火焰图,\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">java 程序的火焰图,</span></div></div>\n<!-- ![java 程序的火焰图](../images/javafire.png) -->\n\n<p>这样看起来就比 jstack这些信息更加直观一点。</p>\n<h2 id=\"负载飙升\"><a href=\"#负载飙升\" class=\"headerlink\" title=\"负载飙升\"></a>负载飙升</h2><h3 id=\"负载的定义以及如何查看负载\"><a href=\"#负载的定义以及如何查看负载\" class=\"headerlink\" title=\"负载的定义以及如何查看负载\"></a>负载的定义以及如何查看负载</h3><p>我们先看下系统负载的官方描述:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">System load averages is the average number of processes that are either in a runnable or uninterruptable state. A process in arunnable state is either using the CPU or waiting to use the CPU. A process in uninterruptable state is waiting for some I/O access,eg waiting for disk. The averages are taken over the three time intervals. Load averages are not normalized for the number of CPUs ina system, so a load average of 1 means a single CPU system is loaded all the time while on a 4 CPU system it means it was idle 75% of the time.</span><br></pre></td></tr></table></figure>\n\n<p>系统负载平均值表示处于可运行或不可中断状态的进程的平均数量。处于可运行状态的进程要么正在使用 CPU,要么正在等待使用 CPU。处于不可中断状态的进程正在等待某些 I/O 访问,例如等待磁盘。这里的核心概念就是 loadavg 这个数值体现了某些特定状态进程的数量。</p>\n<p>那引申出两个问题:</p>\n<ol>\n<li>进程的状态有哪些? 如何在 Linux 上查看进程状态</li>\n<li>可运行和不可中断状态的进程具体含义是什么</li>\n</ol>\n<p>查看的方式,我们可以通过 ps 命令进行查看,比如通过<code>ps -auxf</code>, 我么可以看到有一列为 <code>STAT</code>,这列就代表该进程的状态:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/psauxf.png\" alt=\"进程状态\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">进程状态</span></div></div>\n\n<p>进程的状态和具体含义:</p>\n<ul>\n<li>D uninterruptible sleep (usually IO)</li>\n<li>R running or runnable (on run queue)</li>\n<li>S interruptible sleep (waiting for an event to complete)</li>\n<li>T stopped by job control signal</li>\n<li>t stopped by debugger during the tracing</li>\n<li>W paging (not valid since the 2.6.xx kernel)</li>\n<li>X dead (should never be seen) </li>\n<li>Z defunct (“zombie”) process, terminated but not reaped by its parent</li>\n</ul>\n<p>这里我们看到处于不可中断的状态的进程和正在运行的进程分别为 <code>D</code> 和 <code>R</code>,换个说法,也就是说造成负载升高的原因也就是这两个状态的进程引起的。</p>\n<p>(插个题外话,按照官方的说法,X 状态的进程应该是不应该被看到的, 但是之前在腾讯云做ES的时候,偶然间碰到了一次,当时还截了个图用做留念😂,但是没有捕获到具体的信息)</p>\n<p>负载的指标可以通过 <code>top</code> 以及 <code>uptime</code> 指令获取</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">23:35:00 up 1 day, 46 min, 1 user, load average: 49.16, 18.35, 7.87</span><br></pre></td></tr></table></figure>\n<p>这里展示了 loadavg 的三个数值: 分别代表的含义是 1min、5min、15min 的系统平均负载</p>\n<p>那我们如何判断系统的负载是高是低呢?</p>\n<p>这里一般有个经验值,我们一般和 CPU 和核心数进行对比,一般负载在 CPU 核心的 70% 左右以及以下,对系统一般没什么影响,超过 70%,系统可能收到影响。但是这里还需要注意的一点就是,负载的比例在 70% 以下时不一定代表系统就没问题,举个简单的例子,如果一个系统上基本上没有业务在运行,那么负载基本上就在零点几左右,那么这种情况下,负载有升高不一定是合理的(后面举一个简单的例子)</p>\n<h3 id=\"如何让系统负载飙高\"><a href=\"#如何让系统负载飙高\" class=\"headerlink\" title=\"如何让系统负载飙高\"></a>如何让系统负载飙高</h3><h4 id=\"纯计算任务对负载的影响\"><a href=\"#纯计算任务对负载的影响\" class=\"headerlink\" title=\"纯计算任务对负载的影响\"></a>纯计算任务对负载的影响</h4><p>既然说正在运行的进程会引起负载的变化,那么跑一些程序,让程序不停运行,那么自然而然就能构造出持续运行的进程了。<br>我这里找了三台机器(64C),用我的压测工具先跑一些纯 CPU 的运算,然后观察下效果:</p>\n<p>测试分为三组,测试前关闭不必要的服务和进程:</p>\n<ol>\n<li>10 并发 30min<ul>\n<li><code>nohup ./sysstress cpu --cpu-number 10 --duration 30m > /dev/null 2>&1</code></li>\n</ul>\n</li>\n<li>30 并发 30min<ul>\n<li><code>nohup ./sysstress cpu --cpu-number 30 --duration 30m > /dev/null 2>&1</code></li>\n</ul>\n</li>\n<li>60 并发 30min<ul>\n<li><code>nohup ./sysstress cpu --cpu-number 60 --duration 30m > /dev/null 2>&1</code></li>\n</ul>\n</li>\n</ol>\n<p>效果如下:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/load10.png\" alt=\"10并发负载,\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">10并发负载,</span></div></div>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/load30.png\" alt=\"30并发负载,\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">30并发负载,</span></div></div>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/load60.png\" alt=\"60并发负载,\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">60并发负载,</span></div></div>\n<!-- ![10并发负载](../images/load10.png)\n![30并发负载](../images/load30.png)\n![60并发负载](../images/load60.png) -->\n\n<p>从上述测试过程中,我们可以发现,在纯运算这种场景下,并发的量基本上和负载是对应的。也就是说随着 CPU的使用量 上涨,负载也会不断变高。</p>\n<h4 id=\"磁盘-IO-对负载的影响\"><a href=\"#磁盘-IO-对负载的影响\" class=\"headerlink\" title=\"磁盘 IO 对负载的影响\"></a>磁盘 IO 对负载的影响</h4><p>在刚才的例子中,我们看到了纯运算对负载的影响(R 进程的代表),然后在关于 D 进程的说明中,我们可以看到有一个比较明显的说明 <code>(usually IO)</code> ,即通常是 IO 引起的,那么接下来我们通过磁盘 IO 来测试一下</p>\n<p>测试分为三组,测试前关闭不必要的服务和进程:</p>\n<ol>\n<li>10 并发 15min<ul>\n<li><code>nohup ./sysstress io --operation read --filepath test.access.log -p 10 -d 15m > /dev/null 2>&1 &</code></li>\n</ul>\n</li>\n<li>30 并发 15min<ul>\n<li><code>nohup ./sysstress io --operation read --filepath test.access.log -p 30 -d 15m > /dev/null 2>&1 &</code></li>\n</ul>\n</li>\n<li>60 并发 15min<ul>\n<li><code>nohup ./sysstress io --operation read --filepath test.access.log -p 60 -d 15m > /dev/null 2>&1 &</code></li>\n</ul>\n</li>\n</ol>\n<p>效果如下:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/ioload10.png\" alt=\"10并发负载\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">10并发负载</span></div></div>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/ioload30.png\" alt=\"30并发负载\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">30并发负载</span></div></div>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/ioload60.png\" alt=\"60并发负载\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">60并发负载</span></div></div>\n\n<p>我们也顺便看一下,60 并发下 CPU 的情况:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/io60c.png\" alt=\"60并发系统整体情况\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">60并发系统整体情况</span></div></div>\n\n<p>这里我们可以观察到,系统的 CPU 基本上已经跑满了。us 和 sy 都占的比较多,但是这种读取非常有可能走到缓存中,我们想测绕过缓存,可以通过 DIRECT 的方式。</p>\n<p>但是上面的例子其实也证明了一件事,IO 的操作也是会导致负载产生飙升。</p>\n<p>那么问题来了,磁盘IO 和 CPU 操作都会导致系统负载飙升,那么负载飙升一定会是这两个原因吗?答案也是未必的,因为上述我们曾经提到过 D 状态的进程,到目前为止我们好像还没介绍过,那么我们来继续模拟,既然 D 状态的进程是 IO 操作引起的,普通的磁盘读写 IO 很难模拟,那我们就换个 IO 场景继续模拟 – 网络 IO。</p>\n<h4 id=\"通过网络-IO-模拟-D-状态进程观察负载影响\"><a href=\"#通过网络-IO-模拟-D-状态进程观察负载影响\" class=\"headerlink\" title=\"通过网络 IO 模拟 D 状态进程观察负载影响\"></a>通过网络 IO 模拟 D 状态进程观察负载影响</h4><p>这里直接上一个模拟方法:</p>\n<ul>\n<li>A 机器开启 NFS Server</li>\n<li>B 机器作为客户端进行挂载</li>\n<li>断开网络</li>\n<li>疯狂 df -h</li>\n</ul>\n<p>详细的操作步骤:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"># 安装 Centos</span><br><span class=\"line\">sudo yum install nfs-utils</span><br><span class=\"line\"></span><br><span class=\"line\"># 服务端配置</span><br><span class=\"line\">sudo mkdir -p /mnt/nfs_share</span><br><span class=\"line\">sudo chown nobody:nogroup /mnt/nfs_share</span><br><span class=\"line\">sudo chmod 755 /mnt/nfs_share</span><br><span class=\"line\">## 打开 /etc/exports 配置,添加一行来定义共享目录及其权限。例如,将 /mnt/nfs_share 共享给网络 192.168.1.0/24,并提供读写权限:</span><br><span class=\"line\">/mnt/nfs_share 192.168.1.0/24(rw,sync,no_subtree_check)</span><br><span class=\"line\"></span><br><span class=\"line\">## 启动 NFS</span><br><span class=\"line\">sudo exportfs -a</span><br><span class=\"line\">sudo systemctl restart nfs-kernel-server</span><br><span class=\"line\">sudo systemctl enable nfs-kernel-server</span><br><span class=\"line\"></span><br><span class=\"line\"># 客户端配置</span><br><span class=\"line\">sudo mkdir -p /mnt/nfs_client</span><br><span class=\"line\">sudo mount -t nfs 192.168.1.100(server ip):/mnt/nfs_share /mnt/nfs_client</span><br><span class=\"line\">## 验证</span><br><span class=\"line\">df -h /mnt/nfs_client</span><br><span class=\"line\"></span><br><span class=\"line\"># 断网模拟(客户端)</span><br><span class=\"line\">iptables -I INPUT -s serverip -j DROP</span><br><span class=\"line\"></span><br><span class=\"line\"># 持续(疯狂)执行:</span><br><span class=\"line\">du -sh /mnt/nfs_client</span><br></pre></td></tr></table></figure>\n<p>因为网络已经断掉,所以<code>du -sh /mnt/nfs_client</code>,而且这个程序没有自动退出或者报错,这样就导致程序无法顺利执行下去,继而阻塞住就变成了 D 状态的进程。</p>\n<p>基于这种模拟方法大家可以自行测试下,笔者之前做过一个场景,将一个两核心的 CPU负载干到了 200 多,但是因为<strong>这种情况下更多是阻塞在网络中,所以此时的负载虽高,并不一定影响系统运行</strong>。</p>\n<p>当然这只是其中一个例子,笔者曾经也因为见过 ping 操作阻塞导致的负载飙升,所以这种场景是多种多样的😂,大家有更多的例子也可以在下方留言,共同学习进步。</p>\n<h3 id=\"负载飙升如何排查\"><a href=\"#负载飙升如何排查\" class=\"headerlink\" title=\"负载飙升如何排查\"></a>负载飙升如何排查</h3><p>基于上面的例子和场景模拟,我们其实应该已经有一套基本的排查方法了,下面这张图是我个人的一些总结(图还会不断完善)</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/loadhigh.png\" alt=\"负载高排查导图\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">负载高排查导图</span></div></div>\n\n<h2 id=\"内存占用过高\"><a href=\"#内存占用过高\" class=\"headerlink\" title=\"内存占用过高\"></a>内存占用过高</h2><p>我们先来了解一下有哪些常用的内存性能工具:</p>\n<table>\n<thead>\n<tr>\n<th>工具</th>\n<th>功能</th>\n<th>用法</th>\n<th>常用选项</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>free</code></td>\n<td>显示系统的内存使用情况</td>\n<td><code>free -h</code></td>\n<td><code>-h</code>:以人类可读的格式显示</td>\n</tr>\n<tr>\n<td><code>top</code></td>\n<td>实时显示系统的进程和内存使用情况</td>\n<td><code>top</code></td>\n<td>无</td>\n</tr>\n<tr>\n<td><code>htop</code></td>\n<td>提供更友好的用户界面的进程监控工具</td>\n<td><code>htop</code></td>\n<td>无</td>\n</tr>\n<tr>\n<td><code>vmstat</code></td>\n<td>报告虚拟内存、进程、CPU 活动等统计信息</td>\n<td><code>vmstat 1</code></td>\n<td><code>1</code>:每秒更新一次数据</td>\n</tr>\n<tr>\n<td><code>ps</code></td>\n<td>查看系统中进程的内存使用情况</td>\n<td><code>ps aux --sort=-%mem</code></td>\n<td><code>aux</code>:显示所有用户的进程, <code>--sort=-%mem</code>:按内存使用量降序排列</td>\n</tr>\n<tr>\n<td><code>pmap</code></td>\n<td>显示进程的内存映射</td>\n<td><code>pmap -x <pid></code></td>\n<td><code>-x</code>:显示详细信息</td>\n</tr>\n<tr>\n<td><code>smem</code></td>\n<td>提供详细的内存使用报告</td>\n<td><code>smem -r</code></td>\n<td>无</td>\n</tr>\n<tr>\n<td><code>/proc</code></td>\n<td>提供系统和进程的详细内存信息</td>\n<td><code>cat /proc/meminfo</code><br><code>cat /proc/<pid>/status</code></td>\n<td>无</td>\n</tr>\n</tbody></table>\n<h3 id=\"如何判断内存占用过高\"><a href=\"#如何判断内存占用过高\" class=\"headerlink\" title=\"如何判断内存占用过高\"></a>如何判断内存占用过高</h3><p>系统内存的使用情况可以通过 <code>top</code> 以及 <code>free</code> 命令进行查看,以 <code>free -m</code>为例:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/free.png\" alt=\"free查看内存信息\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">free查看内存信息</span></div></div>\n\n<p>字段说明:</p>\n<ul>\n<li>total: 系统总内存(RAM)或交换空间(Swap)的总量。</li>\n<li>used: 已用内存或交换空间的量。这包括正在使用的内存以及系统缓存(对于内存)或已经使用的交换空间。</li>\n<li>free: 空闲的内存或交换空间的量。表示当前未被任何进程使用的内存或交换空间。</li>\n<li>shared: 被多个进程共享的内存量(对于内存)。通常是共享库和进程间通信的内存。</li>\n<li>buff/cache: 用作文件系统缓存和缓冲区的内存量。这包括缓存的文件数据(cache)和缓冲区(buff)。这些内存可以被系统用作其他用途。</li>\n<li>available: 可以分配给新启动的应用程序的内存量,而不需要交换到磁盘。这个数字更能反映系统的实际可用内存</li>\n</ul>\n<p>这里我们需要关注下,一般可用内存我们就是以 available 为准。当 available 的指标越来越小时,我们就需要关注系统内存的整体使用情况。</p>\n<p>这里还有一个需要关注的点:Swap,我们先看一下详细介绍:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Swap space in Linux is used when the amount of physical memory (RAM) is full. If the system </span><br><span class=\"line\">needs more memory resources and the RAM is full, inactive pages in memory are moved to the </span><br><span class=\"line\">swap space. While swap space can help machines with a small amount of RAM, it should not be </span><br><span class=\"line\">considered a replacement for more RAM. Swap space is located on hard drives, which have a </span><br><span class=\"line\">slower access time than physical memory.Swap space can be a dedicated swap partition (recommended),</span><br><span class=\"line\"> a swap file, or a combination of swap partitions and swap files.</span><br></pre></td></tr></table></figure>\n\n<p>SWAP意思是交换,顾名思义,当某进程向OS请求内存发现不足时,OS会把内存中暂时不用的数据交换出去,放在SWAP分区中,这个过程称为SWAP OUT。当某进程又需要这些数据且OS发现还有空闲物理内存时,又会把SWAP分区中的数据交换回物理内存中,这个过程称为SWAP IN。<br>简单来说,就是物理内存不足时,把磁盘空间当作 swap 分区,解决容量不足的问题。</p>\n<p>这里我们其实会发现一个问题,物理内存的读写性能肯定要比磁盘强不少,使用了磁盘空间作为内存存储,本身读写的性能就不高,还涉及到频繁的交换,反而增加了系统的负载,所以在线上环境中我们一般建议关闭 swap,具体的关闭方法请自行百度。</p>\n<h3 id=\"如何定位内存占用高\"><a href=\"#如何定位内存占用高\" class=\"headerlink\" title=\"如何定位内存占用高\"></a>如何定位内存占用高</h3><p>针对内存的占用,常规情况下(当然也有非常规情况),我们需要找到占用内存高的具体进程,详细的操作方式是:<br><code>top</code>的时候输入<code>M</code>进行排序,这样我们就可以看到系统中进程消耗内存的详细占比了:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/top-process.png\" data-fancybox=\"true\"/></div></div>\n\n<p>这里我们重点关注两列: <code>%MEM</code> 和 <code>RES</code>, 前者是这个进程占用系统内存的百分比,后者是占用实际内存的大小。</p>\n<p>我们还是以 JAVA 和 GOLANG 程序为例来分析内存高该如何排查</p>\n<h4 id=\"JAVA-占用内存过高\"><a href=\"#JAVA-占用内存过高\" class=\"headerlink\" title=\"JAVA 占用内存过高\"></a>JAVA 占用内存过高</h4><p>Java 应用的内存管理依赖于 JVM (Java Virtual Machine),通常会涉及到堆内存(Heap)、非堆内存(Non-Heap)以及本地内存(Native Memory)。</p>\n<h5 id=\"堆内内存分析\"><a href=\"#堆内内存分析\" class=\"headerlink\" title=\"堆内内存分析\"></a>堆内内存分析</h5><ol>\n<li>查看 JVM 启动参数,尤其是 -Xms 和 -Xmx 选项,这两个参数分别设置了初始堆内存大小和最大堆内存大小。可以通过 ps 命令查看这些参数:<code>ps -auxf | grep 程序名称</code></li>\n<li>使用 jstat 查看 gc 情况, 实例:<code>jstat -gc pid 1000</code><div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/gc.png\" alt=\"gc 信息\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">gc 信息</span></div></div>\n如果 GC 频繁,那么我们需要进一步进行分析</li>\n<li>通过 jmap 生成进程的堆内存快照 (在 JVM启动时,建议添加 OOM 时自动生成 heap dump: <code>-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumpfile</code>):<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">jmap -dump:live,format=b,file=heapdump.hprof <pid></span><br></pre></td></tr></table></figure></li>\n<li>拿到快照文件后,我们可以通过 MAT 或者 Jprofiler 这样的工具去具体分析<br>以 MAT 为例:</li>\n</ol>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/mat.jpeg\" alt=\"MAT 分析 JAVA内存\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">MAT 分析 JAVA内存</span></div></div>\n\n<h5 id=\"本地内存分析\"><a href=\"#本地内存分析\" class=\"headerlink\" title=\"本地内存分析\"></a>本地内存分析</h5><p>NMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。<br>NMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。</p>\n<p>启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。<br>启动命令: <code>-XX:NativeMemoryTracking=[off | summary | detail]</code>。</p>\n<ul>\n<li>off:NMT 默认是关闭的;</li>\n<li>summary:只收集子系统的内存使用的总计数据;</li>\n<li>detail:收集每个调用点的内存使用数据。</li>\n</ul>\n<p>开启后,我们可以通过如下命令访问 NMT 内存:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]</span><br></pre></td></tr></table></figure>\n\n<h5 id=\"其他非堆内存\"><a href=\"#其他非堆内存\" class=\"headerlink\" title=\"其他非堆内存\"></a>其他非堆内存</h5><p>主要包括:</p>\n<ul>\n<li>JVM 自身运行占用的空间;</li>\n<li>线程栈分配占用的系统内存;</li>\n<li>DirectByteBuffer 占用的内存;</li>\n<li>JNI 里分配的内存;</li>\n<li>Java 8 开始的元数据空间;</li>\n<li>NIO 缓存</li>\n<li>Unsafe 调用分配的内存;</li>\n<li>codecache</li>\n</ul>\n<p>对于这些问题,遇到内存升高的情况较少,所以也没有进行过详细的排查,如果有读者朋友有做过类似的排查,可以在下面留言讨论。</p>\n<h4 id=\"golang程序占用内存高\"><a href=\"#golang程序占用内存高\" class=\"headerlink\" title=\"golang程序占用内存高\"></a>golang程序占用内存高</h4><p>golang 内置了 pprof 性能分析工具,支持 CPU、内存(Heap)、栈、协程等的分析。可以通过 HTTP 服务暴露 pprof 接口,并使用浏览器或 go tool pprof 进行分析:<br>在代码中引入 net/http/pprof 包:</p>\n<figure class=\"highlight golang\"><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><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">import</span> _ <span class=\"string\">"net/http/pprof"</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"title\">main</span><span class=\"params\">()</span></span> {</span><br><span class=\"line\"> <span class=\"keyword\">go</span> <span class=\"function\"><span class=\"keyword\">func</span><span class=\"params\">()</span></span> {</span><br><span class=\"line\"> log.Println(http.ListenAndServe(<span class=\"string\">"localhost:6060"</span>, <span class=\"literal\">nil</span>))</span><br><span class=\"line\"> }()</span><br><span class=\"line\"> <span class=\"comment\">// 你的应用代码</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>启动应用后,通过浏览器访问 <code>http://localhost:6060/debug/pprof/heap</code> 下载堆内存快照,并使用以下命令分析:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">go tool pprof -http=:8080 heap.prof</span><br></pre></td></tr></table></figure>\n<p>这将启动一个 Web 界面,供你分析内存占用的热点。<br>具体的使用方式大家可以自行研究。</p>\n<h4 id=\"基于内存的文件系统占用\"><a href=\"#基于内存的文件系统占用\" class=\"headerlink\" title=\"基于内存的文件系统占用\"></a>基于内存的文件系统占用</h4><p>在上文中,我们说了常规情况下的排查,那来个非常规的,先上张图:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/tmp-mem.png\" alt=\"内存占用模拟\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">内存占用模拟</span></div></div>\n\n<p>这里大家看看到,内存总量大概有 250 个 G,可用内存只有 126G,但是我们通过 top 看内存占比跟实际的数值相差甚远。</p>\n<p>这种场景是不是非常奇怪呢?没有进程占用内存,但是内存被消耗了,这种情况下,很有可能跟基于内存的文件系统有关。</p>\n<p>这里我说一下模拟方法:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"># systemd 的包中会默认自带一个 tmp.mount服务,这个服务默认是关闭的(默认是总内存的一半)</span><br><span class=\"line\"># 这个服务本质上是一个基于内存的文件系统(把内存当磁盘使,在上面可以创建文件)</span><br><span class=\"line\">systemctl start tmp.mount</span><br><span class=\"line\"></span><br><span class=\"line\"># 然后我们通过 df -h /tmp 可以看到</span><br><span class=\"line\">Filesystem Size Used Avail Use% Mounted on</span><br><span class=\"line\">tmpfs 126G 111G 16G 88% /tmp</span><br><span class=\"line\"></span><br><span class=\"line\">这里挂载了/tmp 目录,文件系统是 tmpfs</span><br><span class=\"line\"></span><br><span class=\"line\"># 然后我们进入 /tmp目录</span><br><span class=\"line\"># 模拟文件创建</span><br><span class=\"line\">dd if=/dev/zero of=test.txt bs=G count=100</span><br><span class=\"line\"></span><br><span class=\"line\"># 然后我们就可以看到文件创建成功,然后 free -g 就可以看到内存成功消耗了100G😂</span><br></pre></td></tr></table></figure>\n\n<p>这种场景也是我之前在排查问题的时候遇到的,大家有其他类似场景也可以进行补充</p>\n<h2 id=\"磁盘问题\"><a href=\"#磁盘问题\" class=\"headerlink\" title=\"磁盘问题\"></a>磁盘问题</h2><p>线上的磁盘问题,除了磁盘故障(坏块、文件系统损坏、RAID 等)之外,常见的比较多的问题可以分为两大类:磁盘空间不足和 IO 性能问题</p>\n<h3 id=\"磁盘空间不足\"><a href=\"#磁盘空间不足\" class=\"headerlink\" title=\"磁盘空间不足\"></a>磁盘空间不足</h3><p>对于这类问题,一般情况下,本身可能就是数据文件、日志信息等过多导致的<strong>真实占用</strong>,还有一类是文件句柄泄露导致磁盘没有释放从而占据了多余的磁盘空间</p>\n<p>对于这类问题我们一般通过以下手段进行排查,假如说我们现在接到告警 说根目录的磁盘空间不足了</p>\n<ol>\n<li>通过<code>df -h</code>命令查看磁盘分区的使用情况。</li>\n<li>进入 <code>"/"</code> 目录下,可以对常用的目录进行 <code>du -sh</code>操作,然后进行简单的相加<ul>\n<li>如果加起来的磁盘空间,和分区使用的磁盘空间相差不多,那就基本上说明磁盘空间是被真实占用的,找到占用高的目录继续通过<code>du -sh</code> 目录,不断递归。或者想快速找到磁盘中超过 1G 的文件,可以通过<code>find 目录 -type f -size +1G</code> 这样的方式。<br>如果想快速统计到所有子目录的磁盘信息,那么可以通过这样的方式进行操作<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">du -ah / 2>/dev/null | grep -E '^([0-9]+([KMGT]?)|([0-9]+(\\.[0-9]+)?[KMGT]))' | sort -rh | head -10</span><br></pre></td></tr></table></figure></li>\n<li>如果加起来的磁盘空间,和分区使用的磁盘空间相差的比较大,那么可能存在句柄泄露,比如日志文件本身被删掉,但是句柄没有释放,可以通过一下方式: <figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">#列出进打开文件最多的 10 个进程;</span><br><span class=\"line\">lsof +L1 | awk '{print $2}' | sort | uniq -c | sort -rn | head -n 10</span><br><span class=\"line\"></span><br><span class=\"line\"># 查看某个进程的占用</span><br><span class=\"line\">lsof -p $pid (查看是否存在已经删除的文件句柄没有释放,可以通过 grep deleted 过滤)</span><br><span class=\"line\"></span><br><span class=\"line\"># 或者 ls /proc/$pid/fd 进行查看</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n</ol>\n<h3 id=\"磁盘IO性能问题\"><a href=\"#磁盘IO性能问题\" class=\"headerlink\" title=\"磁盘IO性能问题\"></a>磁盘IO性能问题</h3><p>我们可以通过 iostat 看下磁盘整体的读写情况:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/iostat.png\" alt=\"iostat\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">iostat</span></div></div>\n\n<p>详细的介绍如下:</p>\n<ol>\n<li>CPU 使用情况</li>\n</ol>\n<table>\n<thead>\n<tr>\n<th>Metric</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>%user</td>\n<td>用户模式下消耗的 CPU 时间百分比</td>\n</tr>\n<tr>\n<td>%nice</td>\n<td>调整优先级的用户模式下的 CPU 时间百分比</td>\n</tr>\n<tr>\n<td>%system</td>\n<td>内核模式下消耗的 CPU 时间百分比</td>\n</tr>\n<tr>\n<td>%iowait</td>\n<td>CPU 等待 I/O 操作完成的时间百分比</td>\n</tr>\n<tr>\n<td>%steal</td>\n<td>等待虚拟 CPU 被实际 CPU 服务的时间百分比</td>\n</tr>\n<tr>\n<td>%idle</td>\n<td>CPU 空闲时间百分比</td>\n</tr>\n</tbody></table>\n<ol start=\"2\">\n<li>设备 I/O 使用情况</li>\n</ol>\n<table>\n<thead>\n<tr>\n<th>Metric</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>Device</td>\n<td>设备名称</td>\n</tr>\n<tr>\n<td>tps</td>\n<td>每秒传输数(I/O 请求数)</td>\n</tr>\n<tr>\n<td>kB_read/s</td>\n<td>每秒读取的 kB 数</td>\n</tr>\n<tr>\n<td>kB_wrtn/s</td>\n<td>每秒写入的 kB 数</td>\n</tr>\n<tr>\n<td>kB_read</td>\n<td>读取的 kB 总数</td>\n</tr>\n<tr>\n<td>kB_wrtn</td>\n<td>写入的 kB 总数</td>\n</tr>\n<tr>\n<td>rrqm/s</td>\n<td>每秒进行的合并读请求数</td>\n</tr>\n<tr>\n<td>wrqm/s</td>\n<td>每秒进行的合并写请求数</td>\n</tr>\n<tr>\n<td>r/s</td>\n<td>每秒完成的读请求数</td>\n</tr>\n<tr>\n<td>w/s</td>\n<td>每秒完成的写请求数</td>\n</tr>\n<tr>\n<td>rMB/s</td>\n<td>每秒读取的 MB 数</td>\n</tr>\n<tr>\n<td>wMB/s</td>\n<td>每秒写入的 MB 数</td>\n</tr>\n<tr>\n<td>avgrq-sz</td>\n<td>平均每次 I/O 请求的数据大小</td>\n</tr>\n<tr>\n<td>avgqu-sz</td>\n<td>平均 I/O 队列长度</td>\n</tr>\n<tr>\n<td>await</td>\n<td>平均每次 I/O 操作的等待时间</td>\n</tr>\n<tr>\n<td>svctm</td>\n<td>平均每次 I/O 操作的服务时间</td>\n</tr>\n<tr>\n<td>%util</td>\n<td>设备 I/O 活动时间百分比(表示设备忙碌度)</td>\n</tr>\n</tbody></table>\n<p>通过这个我们就可以获取到磁盘的整体情况。</p>\n<p>当我们想查看进程占用的 io 情况时,可以通过 <code>iotop</code>命令进行查看,如下:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/iotop.png\" alt=\"iotop\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">iotop</span></div></div>\n\n<p>结合上文讲的一些堆栈分析工具,推测到进程具体在做那些操作然后针对性进行处理。</p>\n","excerpt":"","more":"<div class=\"tag-plugin quot\"><p class=\"content\" type=\"text\"><span class=\"empty\"></span><span class=\"text\">参考文档</span><span class=\"empty\"></span></p></div>\n\n<p><a href=\"https://www.linuxjournal.com/article/9001\">Examining Load Average</a><br><a href=\"https://community.tenable.com/s/article/What-is-CPU-Load-Average\">What-is-CPU-Load-Average</a><br><a href=\"www.brendangregg.com\">Brendan Gregg个人网站</a></p>\n<h2 id=\"写在前面\"><a href=\"#写在前面\" class=\"headerlink\" title=\"写在前面\"></a>写在前面</h2><p>在很多文章中,每当提到去解决线上问题的时候,大部分的处理方式就是登录环境,哐哐各种敲命令。操作本身没什么问题,但是对于很多人而言,我觉得这种做法其实是本末倒置的,过于在乎去快速抓住重点问题,而忽略了从全局去看问题。那么如果最开始不去操作各种命令,那应该干什么呢?</p>\n<p><em><strong>看监控!!!!</strong></em></p>\n<p>首先不要觉得这个是废话,对于很多场景来说,业务规模是不断变化的,有的时候并发超过了极限的性能,那么这种情况下都没有必要去后台进行各种查询。举个简单的例子,假如说某套业务系统,本身只能支持 500 并发,现在实际上的量到了 2000,导致线上各种内存、CPU、负载的告警,这种情况下还有必要去后台敲<code>top</code>、<code>free</code>吗?答案当然是否定的,这种情况下,就需要考虑对业务系统进行快速的扩容等。</p>\n<p>看监控的意义在于尽可能的找到更多的性能瓶颈或者异常的点,从全局出发,对系统当前存在的问题和异常点有全面的了解。</p>\n<p>监控系统多种多样,从较早的 zabbix 到现在比较流行的prometheus+grafana(举两个常用的例子),对于系统业务都有比较完善的监控,可以帮助我们更加具体的了解到系统运行全貌。如果你对这些都不喜欢,那么你自己写一个监控系统也没什么问题。</p>\n<p>当我们看完监控之后(假设你真的看了),接下来进入实际操作环节,我会从这些指标的详细含义出发,然后尽可能地将各种处理方式分享给大家。</p>\n<h2 id=\"Linux性能谱图\"><a href=\"#Linux性能谱图\" class=\"headerlink\" title=\"Linux性能谱图\"></a>Linux性能谱图</h2><p>在分析问题前,我们首先需要明确 Linux 有哪些性能分析工具,我们先上一下LINUX 性能专家 Brendan Gregg 总结的图(大家如果对性能分析等感兴趣的话,可以认真看下这位大佬的个人网站):</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"https://www.brendangregg.com/Perf/linux_observability_tools.png\" alt=\"Linux Performance Observability Tool\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">Linux Performance Observability Tool</span></div></div>\n\n<p>上面这张图是引用大佬文章的图,原文链接在这里: <a href=\"https://www.brendangregg.com/linuxperf.html\">https://www.brendangregg.com/linuxperf.html</a></p>\n<h2 id=\"CPU使用率飙升\"><a href=\"#CPU使用率飙升\" class=\"headerlink\" title=\"CPU使用率飙升\"></a>CPU使用率飙升</h2><h3 id=\"如何让CPU使用率飙升\"><a href=\"#如何让CPU使用率飙升\" class=\"headerlink\" title=\"如何让CPU使用率飙升\"></a>如何让CPU使用率飙升</h3><p>这个问题其实很简单,只要有计算任务一直存在,让 CPU 一直处于繁忙之中,那么 CPU 必然飙升。我们可以通过一系列的工具去模拟这个情况。</p>\n<p><a href=\"https://github.com/baixiaozhou/SysStress\">github SysStress</a> 这是我自己用 golang 写的压测工具(还在开发中,可以点个 star 让我更有动力😂)</p>\n<p>使用方法:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">./sysstress cpu --cpu-number 10 --duration 10m</span><br></pre></td></tr></table></figure>\n<p>这个就是模拟占用 10 核心的 CPU 并持续 10min,当然大家也可以用其他的压测工具,比如<code>stress-ng</code></p>\n<h3 id=\"如何判断和发现CPU使用率飙升\"><a href=\"#如何判断和发现CPU使用率飙升\" class=\"headerlink\" title=\"如何判断和发现CPU使用率飙升\"></a>如何判断和发现CPU使用率飙升</h3><p>首先我们先看一下,跟 CPU 使用率相关的有哪些指标。我们通过 <code>top</code> 命令就可以看到具体的信息</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/top.png\" alt=\"top\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">top</span></div></div>\n<!-- ![top](../images/top.png) -->\n<p>这些输出中有一行是 <code>%Cpu(s)</code>, 这行展示了 CPU 的整体使用情况,是一个百分比的形式,我们详细阐述下这几个字段的含义</p>\n<figure class=\"highlight plaintext\"><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><td class=\"code\"><pre><span class=\"line\">us, user : time running un-niced user processes 未降低优先级的用户进程所占用的时间</span><br><span class=\"line\">sy, system : time running kernel processes 内核进程所占用的时间</span><br><span class=\"line\">ni, nice : time running niced user processes 降低优先级的用户进程所占用的时间</span><br><span class=\"line\">id, idle : time spent in the kernel idle handler 空闲的时间</span><br><span class=\"line\">wa, IO-wait : time waiting for I/O completion 等待 I/O 操作完成所花费的时间</span><br><span class=\"line\">hi : time spent servicing hardware interrupts 处理硬件中断所花费的时间</span><br><span class=\"line\">si : time spent servicing software interrupts 处理软件中断所花费的时间</span><br><span class=\"line\">st : time stolen from this vm by the hypervisor 被虚拟机管理程序从此虚拟机中窃取的时间</span><br></pre></td></tr></table></figure>\n<p>在这些指标中,一般关注的比较多的就是 us、sy、id、wa(其他几个指标很高的情况我个人目前基本上没有遇到过)</p>\n<p>上述指标反映了系统整体的 CPU 情况。而程序在操作系统中实际上是以一个个的进程存在的,那我们如何确定到占用 CPU 高的进程呢?让我们的目光从 top 的头部信息往下移动,下面就展示了详细的进程信息</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/top-process.png\" data-fancybox=\"true\"/></div></div>\n<!-- !cess](../imagescess.png) -->\n\n<p>这些程序默认是按照 CPU 的使用率从高到底进行排序的,当然你也可以通过在<code>top</code>的时候输入<code>P</code>进行排序,这样我们就可以看到系统中消耗 CPU 资源的详细进程信息</p>\n<p>上面是我通过 <code>./sysstress cpu --cpu-number 10 --duration 10m</code> 压测程序跑出来的,可以看到这里的 sysstress 程序占用了 1002 的 %CPU,也就是说基本上是 10 个核心,那我们跑一个更高的,将<code>--cpu-number</code>加到 60 看看发生了什么</p>\n<!-- ![stress-cpu](../images/stress-cpu.png) -->\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/stress-cpu.png\" alt=\"stress-cpu\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">stress-cpu</span></div></div>\n\n<p>我们可以看到这次%CPU打到了 6000,那很多人就好奇我日常的程序跑到多高算高呢?</p>\n<p>这里我们需要明确一点,现在的服务器绝大部分都是多核心 CPU(1C2G这种自己用来玩的忽略),CPU 的核心数决定了我们程序在同一时间能够执行多少个线程,也就是说,这个高不高是相对于机器配置而言的。如果你的机器只有 16C,那么单个进程占用的 %CPU 到 1000,那么其实已经算是比较高了。如果是 256C 的CPU(土豪级配置),那么单个进程占用的 %CPU 到 6000,对于系统的稳定性影响就没有那么大了。</p>\n<p>上述我们说的情况是进程占用 CPU 对整个系统的影响,那么进程占用的 CPU 对系统的影响不大就代表这个程序一定没有问题吗?答案显然是未必的。</p>\n<p>我们还是要回归到业务本身,如果进程的 CPU 占用在业务变动不大的情况下,发生了异常波动,或者正常情况下业务不会消耗这么高的 CPU,那么我们就需要继续排查了。</p>\n<h3 id=\"如何确定CPU飙升的根源\"><a href=\"#如何确定CPU飙升的根源\" class=\"headerlink\" title=\"如何确定CPU飙升的根源\"></a>如何确定CPU飙升的根源</h3><p>这个问题的 核心是 CPU 上在运行什么东西。 多核心CPU 下,每个核心都可以执行不同的程序,我们如何确定一个进程中那些方法在消耗 CPU 呢?从而引申下面详细的问题:</p>\n<ol>\n<li>程序的调用栈是什么样的?</li>\n<li>调用栈信息中哪些是需要关注的,那些是可以忽略的?</li>\n<li>热点函数是什么?</li>\n</ol>\n<p>老话说得好,”工欲善其事,必先利其器”, 我们需要这些东西,就必须了解到什么样的工具可以拿到上面我提到的一些信息。接下来我将通过常用的后端语言:<code>golang</code> 和 <code>java</code> 为例构造一些高 CPU 的程序来进行展示。</p>\n<h4 id=\"perf命令\"><a href=\"#perf命令\" class=\"headerlink\" title=\"perf命令\"></a>perf命令</h4><p><strong>perf是一款Linux性能分析工具。Linux性能计数器是一个新的基于内核的子系统,它提供一个性能分析框架,比如硬件(CPU、PMU(Performance Monitoring Unit))功能和软件(软件计数器、tracepoint)功能。</strong></p>\n<p>安装:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">yum install perf #Centos</span><br></pre></td></tr></table></figure>\n<p>安装完成后,我们可以首先看下 <code>perf</code>的用法,这里不展开具体用法,只列出我平常使用的几个命令:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">top System profiling tool. #对系统性能进行实时分析。</span><br><span class=\"line\">record Run a command and record its profile into perf.data #收集采样信息</span><br><span class=\"line\">report Read perf.data (created by perf record) and display the profile #分析采样信息,和record配合使用</span><br></pre></td></tr></table></figure>\n<p>record 和 report 的使用更多在于 dump 当前环境的信息用于后续分析,如果在自己环境上测试,可以用 top 进行一些简单的实时分析(类似于 top 命令)。</p>\n<p>还是用之前的压测工具,我们模拟一个 10 核心的 10min 的压测场景</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">nohup ./sysstress cpu --cpu-number 10 --duration 10m > /dev/null 2>&1 &</span><br></pre></td></tr></table></figure>\n<p>执行这个语句,让压测程序在后台执行,然后我们通过<code>perf top</code>查看具体的情况(可以通过-p 指定 pid)</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/perftop.png\" alt=\"perf top,\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">perf top,</span></div></div>\n<!-- ![perf top](../images/perftop.png) -->\n\n<p>从截图的信息中我们可以看到占用资源最多的一些方法,包括 sysstress 进程的各种方法(从图片中基本上就可以确定高消耗的方法在哪里)以及底层的 <code>__vdso_clock_gettime</code>, 那再结合压测工具的代码分析下:</p>\n<figure class=\"highlight golang\"><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><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"title\">burnCpu</span><span class=\"params\">(wg *sync.WaitGroup, start time.Time, durSec <span class=\"type\">int64</span>)</span></span> {</span><br><span class=\"line\">\t<span class=\"keyword\">defer</span> wg.Done()</span><br><span class=\"line\">\t<span class=\"keyword\">for</span> {</span><br><span class=\"line\">\t\t_ = <span class=\"number\">1</span> * <span class=\"number\">1</span></span><br><span class=\"line\">\t\tnow := time.Now()</span><br><span class=\"line\">\t\t<span class=\"keyword\">if</span> now.Sub(start) > time.Duration(durSec)*time.Second {</span><br><span class=\"line\">\t\t\t<span class=\"keyword\">break</span></span><br><span class=\"line\">\t\t}</span><br><span class=\"line\">\t}</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>这是方法的核心,其实就是做无意义的计算,外加时间的判断,超过 duration 就结束。这样和上面的 perf top 信息就能对应起来。</p>\n<p>然后我们用 java 写一个同样的程序,再看看 <code>perf top</code>的情况:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/javaperftop.png\" alt=\"perf top,\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">perf top,</span></div></div>\n<!-- ![perf top](../images/javaperftop.png) -->\n<p>从这一大段显示来看,是不是看的一脸懵逼,很难发现到底是什么程序在占用CPU 资源。大家可以看一下源程序:</p>\n<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><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">import</span> java.time.LocalDateTime;</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">public</span> <span class=\"keyword\">class</span> <span class=\"title class_\">Main</span> {</span><br><span class=\"line\"> <span class=\"keyword\">public</span> <span class=\"keyword\">static</span> <span class=\"keyword\">void</span> <span class=\"title function_\">main</span><span class=\"params\">(String[] args)</span> {</span><br><span class=\"line\"> <span class=\"type\">int</span> <span class=\"variable\">n</span> <span class=\"operator\">=</span> <span class=\"number\">10</span>;</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">for</span> (<span class=\"type\">int</span> <span class=\"variable\">i</span> <span class=\"operator\">=</span> <span class=\"number\">0</span>; i < <span class=\"number\">10</span>; i++) {</span><br><span class=\"line\"> <span class=\"keyword\">new</span> <span class=\"title class_\">Thread</span>(<span class=\"keyword\">new</span> <span class=\"title class_\">Runnable</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\">while</span> (<span class=\"literal\">true</span>) {</span><br><span class=\"line\"> Math.sin(Math.random());</span><br><span class=\"line\"> <span class=\"type\">LocalDateTime</span> <span class=\"variable\">currentTime</span> <span class=\"operator\">=</span> LocalDateTime.now();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }).start();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>这里的程序也是非常简单,启动 10 个线程,做一个无意义的数学运算,然后获取当前时间。从这段代码中是不是很难和上面<code>perf top</code>的显示关联起来? 原因也非常简单, 像Java 这种通过 JVM 来运行的应用程序,运行堆栈用的都是 JVM 内置的函数和堆栈管理。所以,从系统层面只能看到 JVM 的函数堆栈,而不能直接得到 Java 应用程序的堆栈。那我们好能通过 perf 去看到 java 相关的堆栈吗?答案是可以的。</p>\n<p>可以借助 <a href=\"https://github.com/jvm-profiling-tools/perf-map-agent\">perf-map-agent</a> 这样的开源工具,去生成和<code>perf</code> 工具一起使用的方法映射,但是需要做额外的一些配置。这里的方法大家可以自己探究,为什么不详细的讲这个呢,原因也简单,排查问题的工具多种多样,没必要在一棵树上吊死。</p>\n<h4 id=\"jstack\"><a href=\"#jstack\" class=\"headerlink\" title=\"jstack\"></a>jstack</h4><p>既然 perf top 去查看 JAVA 的调用栈不太方便,我们就直接上 java 提供的 jstack 工具去分析。</p>\n<ul>\n<li>jstack -l pid > xxx.txt 需要注意的是,linux系统中往往会用不同的用户去执行不同的程序,此时可能需要通过sudu -u xxx jstack的形式</li>\n<li>kill -3, jstack 用不了的情况下可以使用 kill -3 pid 的形式,堆栈默认会输出在系统日志中(根据不同的配置,信息也可能输出在其他地方,比如这个程序的日志中)。</li>\n</ul>\n<p>具体的操作步骤:</p>\n<ol>\n<li><code>top -Hp $pid</code> 找到占用 CPU 的具体线程</li>\n<li><code>jstack -l $pid > /tmp/$pid.jstack</code> 或者 <code>kill -3 $pid</code>将 java 进程的堆栈情况输出的日志中,然后根据 <code>top -Hp</code> 看到的线程信息在输出的堆栈日志中进行查找(<code>top -Hp</code> 输出的是 10 进制的 id,<code>jstack</code> 输出的是 16 进制的,在查找时注意进制转换)</li>\n</ol>\n<p>我们看下上面 java 程序的堆栈的信息:</p>\n<figure class=\"highlight lua\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"number\">2024</span><span class=\"number\">-08</span><span class=\"number\">-16</span> <span class=\"number\">15</span>:<span class=\"number\">15</span>:<span class=\"number\">40</span></span><br><span class=\"line\">Full thread <span class=\"built_in\">dump</span> Java HotSpot(TM) <span class=\"number\">64</span>-Bit Server VM (<span class=\"number\">25.221</span>-b11 mixed mode):</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Attach Listener"</span> #<span class=\"number\">35</span> daemon prio=<span class=\"number\">9</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f52b4001000</span> nid=<span class=\"number\">0x71f4</span> waiting on condition [<span class=\"number\">0x0000000000000000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"DestroyJavaVM"</span> #<span class=\"number\">34</span> prio=<span class=\"number\">5</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e0009800</span> nid=<span class=\"number\">0x1693</span> waiting on condition [<span class=\"number\">0x0000000000000000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Thread-1"</span> #<span class=\"number\">25</span> prio=<span class=\"number\">5</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e015a800</span> nid=<span class=\"number\">0x16d9</span> runnable [<span class=\"number\">0x00007f52f64e3000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\">\tat sun.misc.Unsafe.getObjectVolatile(Native Method)</span><br><span class=\"line\">\tat java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:<span class=\"number\">755</span>)</span><br><span class=\"line\">\tat java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:<span class=\"number\">938</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:<span class=\"number\">267</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:<span class=\"number\">227</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneRegion.ofId(ZoneRegion.java:<span class=\"number\">120</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.of(ZoneId.java:<span class=\"number\">411</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.of(ZoneId.java:<span class=\"number\">359</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.of(ZoneId.java:<span class=\"number\">315</span>)</span><br><span class=\"line\">\tat java.util.TimeZone.toZoneId(TimeZone.java:<span class=\"number\">556</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.systemDefault(ZoneId.java:<span class=\"number\">274</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.Clock.systemDefaultZone(Clock.java:<span class=\"number\">178</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.LocalDateTime.now(LocalDateTime.java:<span class=\"number\">180</span>)</span><br><span class=\"line\">\tat Main$<span class=\"number\">1.</span>run(Main.java:<span class=\"number\">12</span>)</span><br><span class=\"line\">\tat java.lang.Thread.run(Thread.java:<span class=\"number\">748</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Thread-0"</span> #<span class=\"number\">24</span> prio=<span class=\"number\">5</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e0159000</span> nid=<span class=\"number\">0x16d8</span> runnable [<span class=\"number\">0x00007f52f65e4000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\">\tat sun.misc.Unsafe.getObjectVolatile(Native Method)</span><br><span class=\"line\">\tat java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:<span class=\"number\">755</span>)</span><br><span class=\"line\">\tat java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:<span class=\"number\">938</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:<span class=\"number\">267</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:<span class=\"number\">227</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneRegion.ofId(ZoneRegion.java:<span class=\"number\">120</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.of(ZoneId.java:<span class=\"number\">411</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.of(ZoneId.java:<span class=\"number\">359</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.of(ZoneId.java:<span class=\"number\">315</span>)</span><br><span class=\"line\">\tat java.util.TimeZone.toZoneId(TimeZone.java:<span class=\"number\">556</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.ZoneId.systemDefault(ZoneId.java:<span class=\"number\">274</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.Clock.systemDefaultZone(Clock.java:<span class=\"number\">178</span>)</span><br><span class=\"line\">\tat java.<span class=\"built_in\">time</span>.LocalDateTime.now(LocalDateTime.java:<span class=\"number\">180</span>)</span><br><span class=\"line\">\tat Main$<span class=\"number\">1.</span>run(Main.java:<span class=\"number\">12</span>)</span><br><span class=\"line\">\tat java.lang.Thread.run(Thread.java:<span class=\"number\">748</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"> <span class=\"comment\">--- 10 个 thread</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Service Thread"</span> #<span class=\"number\">23</span> daemon prio=<span class=\"number\">9</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e0143800</span> nid=<span class=\"number\">0x16d6</span> runnable [<span class=\"number\">0x0000000000000000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"C2 CompilerThread1"</span> #<span class=\"number\">6</span> daemon prio=<span class=\"number\">9</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e010e000</span> nid=<span class=\"number\">0x16c5</span> waiting on condition [<span class=\"number\">0x0000000000000000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"> <span class=\"comment\">--- 一大堆 C2 CompilerThread</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"C2 CompilerThread0"</span> #<span class=\"number\">5</span> daemon prio=<span class=\"number\">9</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e010b000</span> nid=<span class=\"number\">0x16c4</span> waiting on condition [<span class=\"number\">0x0000000000000000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Signal Dispatcher"</span> #<span class=\"number\">4</span> daemon prio=<span class=\"number\">9</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e0109800</span> nid=<span class=\"number\">0x16c3</span> runnable [<span class=\"number\">0x0000000000000000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: RUNNABLE</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Finalizer"</span> #<span class=\"number\">3</span> daemon prio=<span class=\"number\">8</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e00d8800</span> nid=<span class=\"number\">0x16c2</span> <span class=\"keyword\">in</span> Object.wait() [<span class=\"number\">0x00007f52f7bfa000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: WAITING (on object monitor)</span><br><span class=\"line\">\tat java.lang.Object.wait(Native Method)</span><br><span class=\"line\">\t- waiting on <<span class=\"number\">0x000000008021a5e8</span>> (a java.lang.ref.ReferenceQueue$Lock)</span><br><span class=\"line\">\tat java.lang.ref.ReferenceQueue.<span class=\"built_in\">remove</span>(ReferenceQueue.java:<span class=\"number\">144</span>)</span><br><span class=\"line\">\t- locked <<span class=\"number\">0x000000008021a5e8</span>> (a java.lang.ref.ReferenceQueue$Lock)</span><br><span class=\"line\">\tat java.lang.ref.ReferenceQueue.<span class=\"built_in\">remove</span>(ReferenceQueue.java:<span class=\"number\">165</span>)</span><br><span class=\"line\">\tat java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:<span class=\"number\">216</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"Reference Handler"</span> #<span class=\"number\">2</span> daemon prio=<span class=\"number\">10</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e00d3800</span> nid=<span class=\"number\">0x16c1</span> <span class=\"keyword\">in</span> Object.wait() [<span class=\"number\">0x00007f52f7cfb000</span>]</span><br><span class=\"line\"> java.lang.Thread.State: WAITING (on object monitor)</span><br><span class=\"line\">\tat java.lang.Object.wait(Native Method)</span><br><span class=\"line\">\t- waiting on <<span class=\"number\">0x0000000080218d38</span>> (a java.lang.ref.Reference$Lock)</span><br><span class=\"line\">\tat java.lang.Object.wait(Object.java:<span class=\"number\">502</span>)</span><br><span class=\"line\">\tat java.lang.ref.Reference.tryHandlePending(Reference.java:<span class=\"number\">191</span>)</span><br><span class=\"line\">\t- locked <<span class=\"number\">0x0000000080218d38</span>> (a java.lang.ref.Reference$Lock)</span><br><span class=\"line\">\tat java.lang.ref.Reference$ReferenceHandler.run(Reference.java:<span class=\"number\">153</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> Locked ownable synchronizers:</span><br><span class=\"line\">\t- None</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"VM Thread"</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e00ca000</span> nid=<span class=\"number\">0x16c0</span> runnable</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"GC task thread#0 (ParallelGC)"</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e001f000</span> nid=<span class=\"number\">0x1694</span> runnable</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">--- 一大堆 GC task thread</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"VM Periodic Task Thread"</span> os_prio=<span class=\"number\">0</span> tid=<span class=\"number\">0x00007f53e0146000</span> nid=<span class=\"number\">0x16d7</span> waiting on condition</span><br><span class=\"line\"></span><br><span class=\"line\">JNI global references: <span class=\"number\">202</span></span><br></pre></td></tr></table></figure>\n<p>我们通过 top -Hp 的信息就可以快速定位到 Thread-[0-9] 这几个线程,而每个线程的调用栈都是 <code>java.time.LocalDateTime.now</code>, 也说明了这个方法在不停消耗 CPU。(但是 jstack 只能捕获短时间或者瞬时的堆栈信息,没法处理长时间的,所以我们在获取时可以多打印几次或者使用其他方法)</p>\n<p>至于 jstack 的详细用法,请参考我的另一篇博客:<a href=\"https://baixiaozhou.github.io/2024/08/13/JAVA%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/\">java问题定位</a></p>\n<p>除此之外,还有非常多的分析工具,pstack\\gstack\\strace\\gdb等等,大家可以自行探索使用</p>\n<h4 id=\"火焰图\"><a href=\"#火焰图\" class=\"headerlink\" title=\"火焰图\"></a>火焰图</h4><p>上面我们介绍了很多操作的命令和方法,那么有没有一种比较直观的方式能够直接看到各种方法执行的耗时比重等情况呢?火焰图就是为了解决这种情况而生的。</p>\n<p>火焰图的分类有很多,常用的包括:</p>\n<ol>\n<li>CPU 火焰图 (CPU Flame Graph)<ul>\n<li> 描述:展示 CPU 在不同方法上的消耗情况,显示每个方法调用所占用的 CPU 时间。</li>\n<li> 用途:用于分析 CPU 性能瓶颈,识别哪些方法消耗了最多的 CPU 资源。</li>\n<li> 应用:Java、C++ 等多种编程语言的性能分析。</li>\n</ul>\n</li>\n<li>内存火焰图 (Memory Flame Graph)<ul>\n<li>描述:展示内存分配情况,显示每个方法调用分配的内存量。</li>\n<li>用途:用于检测内存泄漏、过度内存分配问题,帮助优化内存使用。</li>\n<li>应用:常用于分析内存密集型应用,如 Java 应用的堆内存分析。</li>\n</ul>\n</li>\n<li>I/O 火焰图 (I/O Flame Graph)<ul>\n<li> 描述:展示 I/O 操作的耗时情况,显示不同方法的 I/O 操作占用的时间。</li>\n<li> 用途:用于分析应用程序的 I/O 性能,识别慢速或频繁的 I/O 操作。</li>\n<li> 应用:数据库查询、文件系统操作、网络通信等场景的性能调优。</li>\n</ul>\n</li>\n</ol>\n<p>我们这里通过 <a href=\"https://github.com/async-profiler/async-profiler\">async-profiler</a> 对文章上面的java压测程序进行抓取(这个工具只能抓 java 的, 对于 golang 程序,可以利用 golang 提供的 pprof)</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">tar -xzf async-profiler-3.0-linux-x64.tar.gz</span><br><span class=\"line\">cd async-profiler-3.0-linux-x64/bin</span><br><span class=\"line\">./asprof -d 60 pid -f /tmp/javastress.html</span><br></pre></td></tr></table></figure>\n<p>我们用浏览器打开生成的 html 文件,可以看到如下的火焰图信息(可以在网页进行点击,查看更细节的方法)</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/javafire.png\" alt=\"java 程序的火焰图,\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">java 程序的火焰图,</span></div></div>\n<!-- ![java 程序的火焰图](../images/javafire.png) -->\n\n<p>这样看起来就比 jstack这些信息更加直观一点。</p>\n<h2 id=\"负载飙升\"><a href=\"#负载飙升\" class=\"headerlink\" title=\"负载飙升\"></a>负载飙升</h2><h3 id=\"负载的定义以及如何查看负载\"><a href=\"#负载的定义以及如何查看负载\" class=\"headerlink\" title=\"负载的定义以及如何查看负载\"></a>负载的定义以及如何查看负载</h3><p>我们先看下系统负载的官方描述:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">System load averages is the average number of processes that are either in a runnable or uninterruptable state. A process in arunnable state is either using the CPU or waiting to use the CPU. A process in uninterruptable state is waiting for some I/O access,eg waiting for disk. The averages are taken over the three time intervals. Load averages are not normalized for the number of CPUs ina system, so a load average of 1 means a single CPU system is loaded all the time while on a 4 CPU system it means it was idle 75% of the time.</span><br></pre></td></tr></table></figure>\n\n<p>系统负载平均值表示处于可运行或不可中断状态的进程的平均数量。处于可运行状态的进程要么正在使用 CPU,要么正在等待使用 CPU。处于不可中断状态的进程正在等待某些 I/O 访问,例如等待磁盘。这里的核心概念就是 loadavg 这个数值体现了某些特定状态进程的数量。</p>\n<p>那引申出两个问题:</p>\n<ol>\n<li>进程的状态有哪些? 如何在 Linux 上查看进程状态</li>\n<li>可运行和不可中断状态的进程具体含义是什么</li>\n</ol>\n<p>查看的方式,我们可以通过 ps 命令进行查看,比如通过<code>ps -auxf</code>, 我么可以看到有一列为 <code>STAT</code>,这列就代表该进程的状态:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/psauxf.png\" alt=\"进程状态\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">进程状态</span></div></div>\n\n<p>进程的状态和具体含义:</p>\n<ul>\n<li>D uninterruptible sleep (usually IO)</li>\n<li>R running or runnable (on run queue)</li>\n<li>S interruptible sleep (waiting for an event to complete)</li>\n<li>T stopped by job control signal</li>\n<li>t stopped by debugger during the tracing</li>\n<li>W paging (not valid since the 2.6.xx kernel)</li>\n<li>X dead (should never be seen) </li>\n<li>Z defunct (“zombie”) process, terminated but not reaped by its parent</li>\n</ul>\n<p>这里我们看到处于不可中断的状态的进程和正在运行的进程分别为 <code>D</code> 和 <code>R</code>,换个说法,也就是说造成负载升高的原因也就是这两个状态的进程引起的。</p>\n<p>(插个题外话,按照官方的说法,X 状态的进程应该是不应该被看到的, 但是之前在腾讯云做ES的时候,偶然间碰到了一次,当时还截了个图用做留念😂,但是没有捕获到具体的信息)</p>\n<p>负载的指标可以通过 <code>top</code> 以及 <code>uptime</code> 指令获取</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">23:35:00 up 1 day, 46 min, 1 user, load average: 49.16, 18.35, 7.87</span><br></pre></td></tr></table></figure>\n<p>这里展示了 loadavg 的三个数值: 分别代表的含义是 1min、5min、15min 的系统平均负载</p>\n<p>那我们如何判断系统的负载是高是低呢?</p>\n<p>这里一般有个经验值,我们一般和 CPU 和核心数进行对比,一般负载在 CPU 核心的 70% 左右以及以下,对系统一般没什么影响,超过 70%,系统可能收到影响。但是这里还需要注意的一点就是,负载的比例在 70% 以下时不一定代表系统就没问题,举个简单的例子,如果一个系统上基本上没有业务在运行,那么负载基本上就在零点几左右,那么这种情况下,负载有升高不一定是合理的(后面举一个简单的例子)</p>\n<h3 id=\"如何让系统负载飙高\"><a href=\"#如何让系统负载飙高\" class=\"headerlink\" title=\"如何让系统负载飙高\"></a>如何让系统负载飙高</h3><h4 id=\"纯计算任务对负载的影响\"><a href=\"#纯计算任务对负载的影响\" class=\"headerlink\" title=\"纯计算任务对负载的影响\"></a>纯计算任务对负载的影响</h4><p>既然说正在运行的进程会引起负载的变化,那么跑一些程序,让程序不停运行,那么自然而然就能构造出持续运行的进程了。<br>我这里找了三台机器(64C),用我的压测工具先跑一些纯 CPU 的运算,然后观察下效果:</p>\n<p>测试分为三组,测试前关闭不必要的服务和进程:</p>\n<ol>\n<li>10 并发 30min<ul>\n<li><code>nohup ./sysstress cpu --cpu-number 10 --duration 30m > /dev/null 2>&1</code></li>\n</ul>\n</li>\n<li>30 并发 30min<ul>\n<li><code>nohup ./sysstress cpu --cpu-number 30 --duration 30m > /dev/null 2>&1</code></li>\n</ul>\n</li>\n<li>60 并发 30min<ul>\n<li><code>nohup ./sysstress cpu --cpu-number 60 --duration 30m > /dev/null 2>&1</code></li>\n</ul>\n</li>\n</ol>\n<p>效果如下:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/load10.png\" alt=\"10并发负载,\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">10并发负载,</span></div></div>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/load30.png\" alt=\"30并发负载,\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">30并发负载,</span></div></div>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/load60.png\" alt=\"60并发负载,\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">60并发负载,</span></div></div>\n<!-- ![10并发负载](../images/load10.png)\n![30并发负载](../images/load30.png)\n![60并发负载](../images/load60.png) -->\n\n<p>从上述测试过程中,我们可以发现,在纯运算这种场景下,并发的量基本上和负载是对应的。也就是说随着 CPU的使用量 上涨,负载也会不断变高。</p>\n<h4 id=\"磁盘-IO-对负载的影响\"><a href=\"#磁盘-IO-对负载的影响\" class=\"headerlink\" title=\"磁盘 IO 对负载的影响\"></a>磁盘 IO 对负载的影响</h4><p>在刚才的例子中,我们看到了纯运算对负载的影响(R 进程的代表),然后在关于 D 进程的说明中,我们可以看到有一个比较明显的说明 <code>(usually IO)</code> ,即通常是 IO 引起的,那么接下来我们通过磁盘 IO 来测试一下</p>\n<p>测试分为三组,测试前关闭不必要的服务和进程:</p>\n<ol>\n<li>10 并发 15min<ul>\n<li><code>nohup ./sysstress io --operation read --filepath test.access.log -p 10 -d 15m > /dev/null 2>&1 &</code></li>\n</ul>\n</li>\n<li>30 并发 15min<ul>\n<li><code>nohup ./sysstress io --operation read --filepath test.access.log -p 30 -d 15m > /dev/null 2>&1 &</code></li>\n</ul>\n</li>\n<li>60 并发 15min<ul>\n<li><code>nohup ./sysstress io --operation read --filepath test.access.log -p 60 -d 15m > /dev/null 2>&1 &</code></li>\n</ul>\n</li>\n</ol>\n<p>效果如下:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/ioload10.png\" alt=\"10并发负载\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">10并发负载</span></div></div>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/ioload30.png\" alt=\"30并发负载\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">30并发负载</span></div></div>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/ioload60.png\" alt=\"60并发负载\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">60并发负载</span></div></div>\n\n<p>我们也顺便看一下,60 并发下 CPU 的情况:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/io60c.png\" alt=\"60并发系统整体情况\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">60并发系统整体情况</span></div></div>\n\n<p>这里我们可以观察到,系统的 CPU 基本上已经跑满了。us 和 sy 都占的比较多,但是这种读取非常有可能走到缓存中,我们想测绕过缓存,可以通过 DIRECT 的方式。</p>\n<p>但是上面的例子其实也证明了一件事,IO 的操作也是会导致负载产生飙升。</p>\n<p>那么问题来了,磁盘IO 和 CPU 操作都会导致系统负载飙升,那么负载飙升一定会是这两个原因吗?答案也是未必的,因为上述我们曾经提到过 D 状态的进程,到目前为止我们好像还没介绍过,那么我们来继续模拟,既然 D 状态的进程是 IO 操作引起的,普通的磁盘读写 IO 很难模拟,那我们就换个 IO 场景继续模拟 – 网络 IO。</p>\n<h4 id=\"通过网络-IO-模拟-D-状态进程观察负载影响\"><a href=\"#通过网络-IO-模拟-D-状态进程观察负载影响\" class=\"headerlink\" title=\"通过网络 IO 模拟 D 状态进程观察负载影响\"></a>通过网络 IO 模拟 D 状态进程观察负载影响</h4><p>这里直接上一个模拟方法:</p>\n<ul>\n<li>A 机器开启 NFS Server</li>\n<li>B 机器作为客户端进行挂载</li>\n<li>断开网络</li>\n<li>疯狂 df -h</li>\n</ul>\n<p>详细的操作步骤:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"># 安装 Centos</span><br><span class=\"line\">sudo yum install nfs-utils</span><br><span class=\"line\"></span><br><span class=\"line\"># 服务端配置</span><br><span class=\"line\">sudo mkdir -p /mnt/nfs_share</span><br><span class=\"line\">sudo chown nobody:nogroup /mnt/nfs_share</span><br><span class=\"line\">sudo chmod 755 /mnt/nfs_share</span><br><span class=\"line\">## 打开 /etc/exports 配置,添加一行来定义共享目录及其权限。例如,将 /mnt/nfs_share 共享给网络 192.168.1.0/24,并提供读写权限:</span><br><span class=\"line\">/mnt/nfs_share 192.168.1.0/24(rw,sync,no_subtree_check)</span><br><span class=\"line\"></span><br><span class=\"line\">## 启动 NFS</span><br><span class=\"line\">sudo exportfs -a</span><br><span class=\"line\">sudo systemctl restart nfs-kernel-server</span><br><span class=\"line\">sudo systemctl enable nfs-kernel-server</span><br><span class=\"line\"></span><br><span class=\"line\"># 客户端配置</span><br><span class=\"line\">sudo mkdir -p /mnt/nfs_client</span><br><span class=\"line\">sudo mount -t nfs 192.168.1.100(server ip):/mnt/nfs_share /mnt/nfs_client</span><br><span class=\"line\">## 验证</span><br><span class=\"line\">df -h /mnt/nfs_client</span><br><span class=\"line\"></span><br><span class=\"line\"># 断网模拟(客户端)</span><br><span class=\"line\">iptables -I INPUT -s serverip -j DROP</span><br><span class=\"line\"></span><br><span class=\"line\"># 持续(疯狂)执行:</span><br><span class=\"line\">du -sh /mnt/nfs_client</span><br></pre></td></tr></table></figure>\n<p>因为网络已经断掉,所以<code>du -sh /mnt/nfs_client</code>,而且这个程序没有自动退出或者报错,这样就导致程序无法顺利执行下去,继而阻塞住就变成了 D 状态的进程。</p>\n<p>基于这种模拟方法大家可以自行测试下,笔者之前做过一个场景,将一个两核心的 CPU负载干到了 200 多,但是因为<strong>这种情况下更多是阻塞在网络中,所以此时的负载虽高,并不一定影响系统运行</strong>。</p>\n<p>当然这只是其中一个例子,笔者曾经也因为见过 ping 操作阻塞导致的负载飙升,所以这种场景是多种多样的😂,大家有更多的例子也可以在下方留言,共同学习进步。</p>\n<h3 id=\"负载飙升如何排查\"><a href=\"#负载飙升如何排查\" class=\"headerlink\" title=\"负载飙升如何排查\"></a>负载飙升如何排查</h3><p>基于上面的例子和场景模拟,我们其实应该已经有一套基本的排查方法了,下面这张图是我个人的一些总结(图还会不断完善)</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/loadhigh.png\" alt=\"负载高排查导图\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">负载高排查导图</span></div></div>\n\n<h2 id=\"内存占用过高\"><a href=\"#内存占用过高\" class=\"headerlink\" title=\"内存占用过高\"></a>内存占用过高</h2><p>我们先来了解一下有哪些常用的内存性能工具:</p>\n<table>\n<thead>\n<tr>\n<th>工具</th>\n<th>功能</th>\n<th>用法</th>\n<th>常用选项</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>free</code></td>\n<td>显示系统的内存使用情况</td>\n<td><code>free -h</code></td>\n<td><code>-h</code>:以人类可读的格式显示</td>\n</tr>\n<tr>\n<td><code>top</code></td>\n<td>实时显示系统的进程和内存使用情况</td>\n<td><code>top</code></td>\n<td>无</td>\n</tr>\n<tr>\n<td><code>htop</code></td>\n<td>提供更友好的用户界面的进程监控工具</td>\n<td><code>htop</code></td>\n<td>无</td>\n</tr>\n<tr>\n<td><code>vmstat</code></td>\n<td>报告虚拟内存、进程、CPU 活动等统计信息</td>\n<td><code>vmstat 1</code></td>\n<td><code>1</code>:每秒更新一次数据</td>\n</tr>\n<tr>\n<td><code>ps</code></td>\n<td>查看系统中进程的内存使用情况</td>\n<td><code>ps aux --sort=-%mem</code></td>\n<td><code>aux</code>:显示所有用户的进程, <code>--sort=-%mem</code>:按内存使用量降序排列</td>\n</tr>\n<tr>\n<td><code>pmap</code></td>\n<td>显示进程的内存映射</td>\n<td><code>pmap -x <pid></code></td>\n<td><code>-x</code>:显示详细信息</td>\n</tr>\n<tr>\n<td><code>smem</code></td>\n<td>提供详细的内存使用报告</td>\n<td><code>smem -r</code></td>\n<td>无</td>\n</tr>\n<tr>\n<td><code>/proc</code></td>\n<td>提供系统和进程的详细内存信息</td>\n<td><code>cat /proc/meminfo</code><br><code>cat /proc/<pid>/status</code></td>\n<td>无</td>\n</tr>\n</tbody></table>\n<h3 id=\"如何判断内存占用过高\"><a href=\"#如何判断内存占用过高\" class=\"headerlink\" title=\"如何判断内存占用过高\"></a>如何判断内存占用过高</h3><p>系统内存的使用情况可以通过 <code>top</code> 以及 <code>free</code> 命令进行查看,以 <code>free -m</code>为例:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/free.png\" alt=\"free查看内存信息\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">free查看内存信息</span></div></div>\n\n<p>字段说明:</p>\n<ul>\n<li>total: 系统总内存(RAM)或交换空间(Swap)的总量。</li>\n<li>used: 已用内存或交换空间的量。这包括正在使用的内存以及系统缓存(对于内存)或已经使用的交换空间。</li>\n<li>free: 空闲的内存或交换空间的量。表示当前未被任何进程使用的内存或交换空间。</li>\n<li>shared: 被多个进程共享的内存量(对于内存)。通常是共享库和进程间通信的内存。</li>\n<li>buff/cache: 用作文件系统缓存和缓冲区的内存量。这包括缓存的文件数据(cache)和缓冲区(buff)。这些内存可以被系统用作其他用途。</li>\n<li>available: 可以分配给新启动的应用程序的内存量,而不需要交换到磁盘。这个数字更能反映系统的实际可用内存</li>\n</ul>\n<p>这里我们需要关注下,一般可用内存我们就是以 available 为准。当 available 的指标越来越小时,我们就需要关注系统内存的整体使用情况。</p>\n<p>这里还有一个需要关注的点:Swap,我们先看一下详细介绍:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Swap space in Linux is used when the amount of physical memory (RAM) is full. If the system </span><br><span class=\"line\">needs more memory resources and the RAM is full, inactive pages in memory are moved to the </span><br><span class=\"line\">swap space. While swap space can help machines with a small amount of RAM, it should not be </span><br><span class=\"line\">considered a replacement for more RAM. Swap space is located on hard drives, which have a </span><br><span class=\"line\">slower access time than physical memory.Swap space can be a dedicated swap partition (recommended),</span><br><span class=\"line\"> a swap file, or a combination of swap partitions and swap files.</span><br></pre></td></tr></table></figure>\n\n<p>SWAP意思是交换,顾名思义,当某进程向OS请求内存发现不足时,OS会把内存中暂时不用的数据交换出去,放在SWAP分区中,这个过程称为SWAP OUT。当某进程又需要这些数据且OS发现还有空闲物理内存时,又会把SWAP分区中的数据交换回物理内存中,这个过程称为SWAP IN。<br>简单来说,就是物理内存不足时,把磁盘空间当作 swap 分区,解决容量不足的问题。</p>\n<p>这里我们其实会发现一个问题,物理内存的读写性能肯定要比磁盘强不少,使用了磁盘空间作为内存存储,本身读写的性能就不高,还涉及到频繁的交换,反而增加了系统的负载,所以在线上环境中我们一般建议关闭 swap,具体的关闭方法请自行百度。</p>\n<h3 id=\"如何定位内存占用高\"><a href=\"#如何定位内存占用高\" class=\"headerlink\" title=\"如何定位内存占用高\"></a>如何定位内存占用高</h3><p>针对内存的占用,常规情况下(当然也有非常规情况),我们需要找到占用内存高的具体进程,详细的操作方式是:<br><code>top</code>的时候输入<code>M</code>进行排序,这样我们就可以看到系统中进程消耗内存的详细占比了:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/top-process.png\" data-fancybox=\"true\"/></div></div>\n\n<p>这里我们重点关注两列: <code>%MEM</code> 和 <code>RES</code>, 前者是这个进程占用系统内存的百分比,后者是占用实际内存的大小。</p>\n<p>我们还是以 JAVA 和 GOLANG 程序为例来分析内存高该如何排查</p>\n<h4 id=\"JAVA-占用内存过高\"><a href=\"#JAVA-占用内存过高\" class=\"headerlink\" title=\"JAVA 占用内存过高\"></a>JAVA 占用内存过高</h4><p>Java 应用的内存管理依赖于 JVM (Java Virtual Machine),通常会涉及到堆内存(Heap)、非堆内存(Non-Heap)以及本地内存(Native Memory)。</p>\n<h5 id=\"堆内内存分析\"><a href=\"#堆内内存分析\" class=\"headerlink\" title=\"堆内内存分析\"></a>堆内内存分析</h5><ol>\n<li>查看 JVM 启动参数,尤其是 -Xms 和 -Xmx 选项,这两个参数分别设置了初始堆内存大小和最大堆内存大小。可以通过 ps 命令查看这些参数:<code>ps -auxf | grep 程序名称</code></li>\n<li>使用 jstat 查看 gc 情况, 实例:<code>jstat -gc pid 1000</code><div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/gc.png\" alt=\"gc 信息\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">gc 信息</span></div></div>\n如果 GC 频繁,那么我们需要进一步进行分析</li>\n<li>通过 jmap 生成进程的堆内存快照 (在 JVM启动时,建议添加 OOM 时自动生成 heap dump: <code>-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumpfile</code>):<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">jmap -dump:live,format=b,file=heapdump.hprof <pid></span><br></pre></td></tr></table></figure></li>\n<li>拿到快照文件后,我们可以通过 MAT 或者 Jprofiler 这样的工具去具体分析<br>以 MAT 为例:</li>\n</ol>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/mat.jpeg\" alt=\"MAT 分析 JAVA内存\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">MAT 分析 JAVA内存</span></div></div>\n\n<h5 id=\"本地内存分析\"><a href=\"#本地内存分析\" class=\"headerlink\" title=\"本地内存分析\"></a>本地内存分析</h5><p>NMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。<br>NMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。</p>\n<p>启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。<br>启动命令: <code>-XX:NativeMemoryTracking=[off | summary | detail]</code>。</p>\n<ul>\n<li>off:NMT 默认是关闭的;</li>\n<li>summary:只收集子系统的内存使用的总计数据;</li>\n<li>detail:收集每个调用点的内存使用数据。</li>\n</ul>\n<p>开启后,我们可以通过如下命令访问 NMT 内存:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]</span><br></pre></td></tr></table></figure>\n\n<h5 id=\"其他非堆内存\"><a href=\"#其他非堆内存\" class=\"headerlink\" title=\"其他非堆内存\"></a>其他非堆内存</h5><p>主要包括:</p>\n<ul>\n<li>JVM 自身运行占用的空间;</li>\n<li>线程栈分配占用的系统内存;</li>\n<li>DirectByteBuffer 占用的内存;</li>\n<li>JNI 里分配的内存;</li>\n<li>Java 8 开始的元数据空间;</li>\n<li>NIO 缓存</li>\n<li>Unsafe 调用分配的内存;</li>\n<li>codecache</li>\n</ul>\n<p>对于这些问题,遇到内存升高的情况较少,所以也没有进行过详细的排查,如果有读者朋友有做过类似的排查,可以在下面留言讨论。</p>\n<h4 id=\"golang程序占用内存高\"><a href=\"#golang程序占用内存高\" class=\"headerlink\" title=\"golang程序占用内存高\"></a>golang程序占用内存高</h4><p>golang 内置了 pprof 性能分析工具,支持 CPU、内存(Heap)、栈、协程等的分析。可以通过 HTTP 服务暴露 pprof 接口,并使用浏览器或 go tool pprof 进行分析:<br>在代码中引入 net/http/pprof 包:</p>\n<figure class=\"highlight golang\"><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><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">import</span> _ <span class=\"string\">"net/http/pprof"</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"title\">main</span><span class=\"params\">()</span></span> {</span><br><span class=\"line\"> <span class=\"keyword\">go</span> <span class=\"function\"><span class=\"keyword\">func</span><span class=\"params\">()</span></span> {</span><br><span class=\"line\"> log.Println(http.ListenAndServe(<span class=\"string\">"localhost:6060"</span>, <span class=\"literal\">nil</span>))</span><br><span class=\"line\"> }()</span><br><span class=\"line\"> <span class=\"comment\">// 你的应用代码</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>启动应用后,通过浏览器访问 <code>http://localhost:6060/debug/pprof/heap</code> 下载堆内存快照,并使用以下命令分析:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">go tool pprof -http=:8080 heap.prof</span><br></pre></td></tr></table></figure>\n<p>这将启动一个 Web 界面,供你分析内存占用的热点。<br>具体的使用方式大家可以自行研究。</p>\n<h4 id=\"基于内存的文件系统占用\"><a href=\"#基于内存的文件系统占用\" class=\"headerlink\" title=\"基于内存的文件系统占用\"></a>基于内存的文件系统占用</h4><p>在上文中,我们说了常规情况下的排查,那来个非常规的,先上张图:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/tmp-mem.png\" alt=\"内存占用模拟\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">内存占用模拟</span></div></div>\n\n<p>这里大家看看到,内存总量大概有 250 个 G,可用内存只有 126G,但是我们通过 top 看内存占比跟实际的数值相差甚远。</p>\n<p>这种场景是不是非常奇怪呢?没有进程占用内存,但是内存被消耗了,这种情况下,很有可能跟基于内存的文件系统有关。</p>\n<p>这里我说一下模拟方法:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"># systemd 的包中会默认自带一个 tmp.mount服务,这个服务默认是关闭的(默认是总内存的一半)</span><br><span class=\"line\"># 这个服务本质上是一个基于内存的文件系统(把内存当磁盘使,在上面可以创建文件)</span><br><span class=\"line\">systemctl start tmp.mount</span><br><span class=\"line\"></span><br><span class=\"line\"># 然后我们通过 df -h /tmp 可以看到</span><br><span class=\"line\">Filesystem Size Used Avail Use% Mounted on</span><br><span class=\"line\">tmpfs 126G 111G 16G 88% /tmp</span><br><span class=\"line\"></span><br><span class=\"line\">这里挂载了/tmp 目录,文件系统是 tmpfs</span><br><span class=\"line\"></span><br><span class=\"line\"># 然后我们进入 /tmp目录</span><br><span class=\"line\"># 模拟文件创建</span><br><span class=\"line\">dd if=/dev/zero of=test.txt bs=G count=100</span><br><span class=\"line\"></span><br><span class=\"line\"># 然后我们就可以看到文件创建成功,然后 free -g 就可以看到内存成功消耗了100G😂</span><br></pre></td></tr></table></figure>\n\n<p>这种场景也是我之前在排查问题的时候遇到的,大家有其他类似场景也可以进行补充</p>\n<h2 id=\"磁盘问题\"><a href=\"#磁盘问题\" class=\"headerlink\" title=\"磁盘问题\"></a>磁盘问题</h2><p>线上的磁盘问题,除了磁盘故障(坏块、文件系统损坏、RAID 等)之外,常见的比较多的问题可以分为两大类:磁盘空间不足和 IO 性能问题</p>\n<h3 id=\"磁盘空间不足\"><a href=\"#磁盘空间不足\" class=\"headerlink\" title=\"磁盘空间不足\"></a>磁盘空间不足</h3><p>对于这类问题,一般情况下,本身可能就是数据文件、日志信息等过多导致的<strong>真实占用</strong>,还有一类是文件句柄泄露导致磁盘没有释放从而占据了多余的磁盘空间</p>\n<p>对于这类问题我们一般通过以下手段进行排查,假如说我们现在接到告警 说根目录的磁盘空间不足了</p>\n<ol>\n<li>通过<code>df -h</code>命令查看磁盘分区的使用情况。</li>\n<li>进入 <code>"/"</code> 目录下,可以对常用的目录进行 <code>du -sh</code>操作,然后进行简单的相加<ul>\n<li>如果加起来的磁盘空间,和分区使用的磁盘空间相差不多,那就基本上说明磁盘空间是被真实占用的,找到占用高的目录继续通过<code>du -sh</code> 目录,不断递归。或者想快速找到磁盘中超过 1G 的文件,可以通过<code>find 目录 -type f -size +1G</code> 这样的方式。<br>如果想快速统计到所有子目录的磁盘信息,那么可以通过这样的方式进行操作<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">du -ah / 2>/dev/null | grep -E '^([0-9]+([KMGT]?)|([0-9]+(\\.[0-9]+)?[KMGT]))' | sort -rh | head -10</span><br></pre></td></tr></table></figure></li>\n<li>如果加起来的磁盘空间,和分区使用的磁盘空间相差的比较大,那么可能存在句柄泄露,比如日志文件本身被删掉,但是句柄没有释放,可以通过一下方式: <figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">#列出进打开文件最多的 10 个进程;</span><br><span class=\"line\">lsof +L1 | awk '{print $2}' | sort | uniq -c | sort -rn | head -n 10</span><br><span class=\"line\"></span><br><span class=\"line\"># 查看某个进程的占用</span><br><span class=\"line\">lsof -p $pid (查看是否存在已经删除的文件句柄没有释放,可以通过 grep deleted 过滤)</span><br><span class=\"line\"></span><br><span class=\"line\"># 或者 ls /proc/$pid/fd 进行查看</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n</ol>\n<h3 id=\"磁盘IO性能问题\"><a href=\"#磁盘IO性能问题\" class=\"headerlink\" title=\"磁盘IO性能问题\"></a>磁盘IO性能问题</h3><p>我们可以通过 iostat 看下磁盘整体的读写情况:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/iostat.png\" alt=\"iostat\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">iostat</span></div></div>\n\n<p>详细的介绍如下:</p>\n<ol>\n<li>CPU 使用情况</li>\n</ol>\n<table>\n<thead>\n<tr>\n<th>Metric</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>%user</td>\n<td>用户模式下消耗的 CPU 时间百分比</td>\n</tr>\n<tr>\n<td>%nice</td>\n<td>调整优先级的用户模式下的 CPU 时间百分比</td>\n</tr>\n<tr>\n<td>%system</td>\n<td>内核模式下消耗的 CPU 时间百分比</td>\n</tr>\n<tr>\n<td>%iowait</td>\n<td>CPU 等待 I/O 操作完成的时间百分比</td>\n</tr>\n<tr>\n<td>%steal</td>\n<td>等待虚拟 CPU 被实际 CPU 服务的时间百分比</td>\n</tr>\n<tr>\n<td>%idle</td>\n<td>CPU 空闲时间百分比</td>\n</tr>\n</tbody></table>\n<ol start=\"2\">\n<li>设备 I/O 使用情况</li>\n</ol>\n<table>\n<thead>\n<tr>\n<th>Metric</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>Device</td>\n<td>设备名称</td>\n</tr>\n<tr>\n<td>tps</td>\n<td>每秒传输数(I/O 请求数)</td>\n</tr>\n<tr>\n<td>kB_read/s</td>\n<td>每秒读取的 kB 数</td>\n</tr>\n<tr>\n<td>kB_wrtn/s</td>\n<td>每秒写入的 kB 数</td>\n</tr>\n<tr>\n<td>kB_read</td>\n<td>读取的 kB 总数</td>\n</tr>\n<tr>\n<td>kB_wrtn</td>\n<td>写入的 kB 总数</td>\n</tr>\n<tr>\n<td>rrqm/s</td>\n<td>每秒进行的合并读请求数</td>\n</tr>\n<tr>\n<td>wrqm/s</td>\n<td>每秒进行的合并写请求数</td>\n</tr>\n<tr>\n<td>r/s</td>\n<td>每秒完成的读请求数</td>\n</tr>\n<tr>\n<td>w/s</td>\n<td>每秒完成的写请求数</td>\n</tr>\n<tr>\n<td>rMB/s</td>\n<td>每秒读取的 MB 数</td>\n</tr>\n<tr>\n<td>wMB/s</td>\n<td>每秒写入的 MB 数</td>\n</tr>\n<tr>\n<td>avgrq-sz</td>\n<td>平均每次 I/O 请求的数据大小</td>\n</tr>\n<tr>\n<td>avgqu-sz</td>\n<td>平均 I/O 队列长度</td>\n</tr>\n<tr>\n<td>await</td>\n<td>平均每次 I/O 操作的等待时间</td>\n</tr>\n<tr>\n<td>svctm</td>\n<td>平均每次 I/O 操作的服务时间</td>\n</tr>\n<tr>\n<td>%util</td>\n<td>设备 I/O 活动时间百分比(表示设备忙碌度)</td>\n</tr>\n</tbody></table>\n<p>通过这个我们就可以获取到磁盘的整体情况。</p>\n<p>当我们想查看进程占用的 io 情况时,可以通过 <code>iotop</code>命令进行查看,如下:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/iotop.png\" alt=\"iotop\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">iotop</span></div></div>\n\n<p>结合上文讲的一些堆栈分析工具,推测到进程具体在做那些操作然后针对性进行处理。</p>"},{"title":"ansible详细介绍","author":"baixiaozhou","description":"本文将对 ansible 工具的具体使用做一个详细的介绍","repo":"baixiaozhou/SysStress","date":"2024-08-19T11:09:34.000Z","banner":"/images/ansible-logo.png","cover":"/images/ansible-logo.png","references":["[ansible 英文文档](https://docs.ansible.com/ansible/latest/index.html)","[ansible 中文文档](http://www.ansible.com.cn/docs/intro_adhoc.html)"],"_content":"\n<!-- Your content starts here -->\n\n{% quot 官方文档 %}\n\n[英文文档](https://docs.ansible.com/ansible/latest/index.html)\n[中文文档](http://www.ansible.com.cn/docs/intro_adhoc.html)\n\n# ansible 介绍\n\n我们先看一下 ansible 的相关介绍:\n```\nAnsible is a radically simple IT automation platform that makes your applications and systems easier to deploy and maintain. Automate everything from code deployment to network configuration to cloud management, in a language that approaches plain English, using SSH, with no agents to install on remote systems. https://docs.ansible.com.\n```\n\n这里有几点核心:\n\n1. ansible 是一个自动化平台\n2. ansible 使用 ssh 协议(通过密码或者密钥等方式进行访问),部署简单,没有客户端,只需在主控端部署 Ansible 环境,无需在远程系统上安装代理程序\n3. 模块化:调用特定的模块,完成特定任务\n4. 支持自定义扩展\n\n每开发一个工具或者平台的时候,这些工具和平台提供了各种各样的功能,那么我们能用 ansible 来干什么呢? 下面就是一些 ansible 核心的功能介绍:\n\n| 功能 | 描述 | 常见场景示例 |\n|----------------------------|------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|\n| 配置管理 | 自动化管理服务器和设备的配置,确保它们处于期望的状态。 | 自动化安装和配置 Web 服务器,确保所有服务器配置一致。 |\n| 应用部署 | 自动化应用程序的部署过程,从代码库拉取到在服务器上部署和配置应用。 | 自动部署多层 Web 应用程序,包括数据库设置、应用服务部署、负载均衡配置等。 |\n| 持续交付与持续集成(CI/CD) | 与 CI/CD 工具集成,实现自动化的构建、测试和部署流程。 | 代码提交后自动执行测试、构建容器镜像,并部署到 Kubernetes 集群中。 |\n| 基础设施即代码(IaC) | 编写和管理基础设施的配置文件,使其像管理代码一样。 | 使用 Ansible Playbooks 定义云环境资源配置,实现可重复的基础设施部署。 |\n| 云管理 | 自动化云服务资源的管理,包括虚拟机、存储、网络等的创建和配置。 | 自动化创建和管理 AWS EC2 实例、配置 VPC 和安全组。 |\n| 网络自动化 | 管理和配置网络设备,使得大规模的网络设备配置变得简单和一致。 | 自动化配置多台交换机的 VLAN 设置和路由协议。 |\n| 安全与合规 | 自动化安全补丁的部署、系统安全配置的强化以及合规性检查。 | 自动化应用系统安全补丁,配置防火墙规则,执行安全扫描和合规性检查。 |\n| 多平台环境管理 | 支持多种操作系统,在混合环境中统一管理平台上的配置和应用。 | 在混合的 Linux 和 Windows 服务器环境中,统一部署和配置监控软件。 |\n| 灾难恢复 | 自动化灾难恢复流程,如备份、数据恢复和服务恢复。 | 自动化数据库备份并在需要时恢复和重建数据库服务。 |\n| 任务调度与批量操作 | 通过 Playbooks 调度定期任务或对大量服务器执行批量操作。 | 定期清理服务器上的临时文件或批量更新多个服务器的操作系统。 |\n\n## ansible的工作机制\n\nAnsible 在管理节点将 Ansible 模块通过 SSH 协议推送到被管理端执行,执行完之后自动删除,可以使用 SVN 等来管理自定义模块及编排\n\n{% image /images/ansible-diagram.png ansible结构 fancybox:true %}\n\n从这张图中我们可以看到,ansible 由以下模块组成\n```\nAnsible: ansible的核心模块\nHost Inventory:主机清单,也就是被管理的主机列表\nPlaybooks:ansible的剧本,可想象为将多个任务放置在一起,一块执行\nCore Modules:ansible的核心模块\nCustom Modules:自定义模块\nConnection Plugins:连接插件,用于与被管控主机之间基于SSH建立连接关系\nPlugins:其他插件,包括记录日志等\n```\n\n## ansible 安装\n\n在 centos 环境下,我们可以通过:\n```\nyum install ansible -y 进行安装\n```\n安装完成后我们看下 ansible 提供了哪些命令:\n| 命令 | 描述 | 示例 |\n|--------------------|------------------------------------------------------------------------------------------|-----------------------------------------------------|\n| `ansible` | 用于在一个或多个主机上运行单个模块(通常用于临时命令)。 | `ansible all -m ping` |\n| `ansible-playbook` | 用于运行 Ansible playbook 文件,是 Ansible 的核心命令之一。 | `ansible-playbook site.yml` |\n| `ansible-galaxy` | 用于管理 Ansible 角色和集合。可以下载、创建和分享角色。 | `ansible-galaxy install geerlingguy.apache` |\n| `ansible-vault` | 用于加密和解密敏感数据(如密码、密钥)。 | `ansible-vault encrypt secrets.yml` |\n| `ansible-doc` | 显示 Ansible 模块的文档和示例用法。 | `ansible-doc -l` |\n| `ansible-config` | 用于查看、验证和管理 Ansible 配置文件。 | `ansible-config list` |\n| `ansible-inventory`| 管理和检索 Ansible inventory 信息。 | `ansible-inventory --list -i inventory.yml` |\n| `ansible-pull` | 用于从远程版本控制系统(如 Git)拉取 playbook 并在本地执行,常用于自动化部署。 | `ansible-pull -U https://github.com/username/repo.git` |\n| `ansible-console` | 提供一个交互式命令行接口,可用于动态执行 Ansible 任务和命令。 | `ansible-console` |\n\n对两个比较常用的命令:`ansible` 和 `ansible-plabook` 做一下具体的介绍:\n\n### ansible 命令\n\n命令格式:\n``` bash\nansible 组名 -m 模块名 -a '参数'\n```\n这里的组名是自定义的一系列组信息,组的定义在后面会讲到。\n\n模块名是ansible提供的一些列支持模块,默认模块是 command,查看 ansible 支持的模块:\n``` bash\nansible-doc -l #大概有 3000 多个\n```\nansible涉及到的模块非常非常多,按照实际需要使用,初步先掌握一些比较常用的就可以\n\n查看模块描述:\n``` bash\nansible-doc -s 模块名称\n```\n实例:\n``` bash\n# 查看webserver 组机器的时间信息\nansible webserver -m shell -a \"date\" # 这里的组就是 webserver,shell 是模块名(比较常用的模块)date 是具体执行的命令\n# 将本机的/tmp/test.sh 拷贝到其他机器上的 /etc目录下\nansible webserver -m copy -a \"src=/tmp/test.sh dest=/etc/test.sh\" # 这里的 copy 是模块名,里面的 src 是源路径,dest 是目标路径\n```\n\n### ansible-playbook命令\n用于执行 Ansible Playbooks。Playbooks 是一系列任务的集合,用于自动化配置管理、应用部署、任务执行等操作。\n基本语法:\n``` bash\nansible-playbook [options] playbook.yml\n```\n命令帮助:\n```\npositional arguments:\n playbook Playbook(s)\n\noptional arguments:\n --ask-vault-pass ask for vault password\n --flush-cache clear the fact cache for every host in inventory\n --force-handlers run handlers even if a task fails\n --list-hosts outputs a list of matching hosts; does not execute\n anything else\n --list-tags list all available tags\n --list-tasks list all tasks that would be executed\n --skip-tags SKIP_TAGS\n only run plays and tasks whose tags do not match these\n values\n --start-at-task START_AT_TASK\n start the playbook at the task matching this name\n --step one-step-at-a-time: confirm each task before running\n --syntax-check perform a syntax check on the playbook, but do not\n execute it\n --vault-id VAULT_IDS the vault identity to use\n --vault-password-file VAULT_PASSWORD_FILES\n vault password file\n --version show program's version number, config file location,\n configured module search path, module location,\n executable location and exit\n -C, --check don't make any changes; instead, try to predict some\n of the changes that may occur\n -D, --diff when changing (small) files and templates, show the\n differences in those files; works great with --check\n -M MODULE_PATH, --module-path MODULE_PATH\n prepend colon-separated path(s) to module library (def\n ault=~/.ansible/plugins/modules:/usr/share/ansible/plu\n gins/modules)\n -e EXTRA_VARS, --extra-vars EXTRA_VARS\n set additional variables as key=value or YAML/JSON, if\n filename prepend with @\n -f FORKS, --forks FORKS\n specify number of parallel processes to use\n (default=64)\n -h, --help show this help message and exit\n -i INVENTORY, --inventory INVENTORY, --inventory-file INVENTORY\n specify inventory host path or comma separated host\n list. --inventory-file is deprecated\n -l SUBSET, --limit SUBSET\n further limit selected hosts to an additional pattern\n -t TAGS, --tags TAGS only run plays and tasks tagged with these values\n -v, --verbose verbose mode (-vvv for more, -vvvv to enable\n connection debugging)\n\nConnection Options:\n control as whom and how to connect to hosts\n\n --private-key PRIVATE_KEY_FILE, --key-file PRIVATE_KEY_FILE\n use this file to authenticate the connection\n --scp-extra-args SCP_EXTRA_ARGS\n specify extra arguments to pass to scp only (e.g. -l)\n --sftp-extra-args SFTP_EXTRA_ARGS\n specify extra arguments to pass to sftp only (e.g. -f,\n -l)\n --ssh-common-args SSH_COMMON_ARGS\n specify common arguments to pass to sftp/scp/ssh (e.g.\n ProxyCommand)\n --ssh-extra-args SSH_EXTRA_ARGS\n specify extra arguments to pass to ssh only (e.g. -R)\n -T TIMEOUT, --timeout TIMEOUT\n override the connection timeout in seconds\n (default=10)\n -c CONNECTION, --connection CONNECTION\n connection type to use (default=smart)\n -k, --ask-pass ask for connection password\n -u REMOTE_USER, --user REMOTE_USER\n connect as this user (default=None)\n\nPrivilege Escalation Options:\n control how and which user you become as on target hosts\n\n --become-method BECOME_METHOD\n privilege escalation method to use (default=sudo), use\n `ansible-doc -t become -l` to list valid choices.\n --become-user BECOME_USER\n run operations as this user (default=root)\n -K, --ask-become-pass\n ask for privilege escalation password\n -b, --become run operations with become (does not imply password\n prompting)\n```\n\n# ansible 基本使用\n\n我们先看一个最基本的 ansible 组成:\n\n{% image https://docs.ansible.com/ansible/latest/_images/ansible_inv_start.svg ansible基本组成 fancybox:true %}\n\n如上图所示,大部分的 ansible 环境都包含以下三个组件:\n1. 控制节点:安装了Ansible的系统。可以在控制节点上运行Ansible命令,例如ansible或ansible-inventory。\n2. Inventory: 按逻辑组织的托管节点的列表。可以在控制节点上创建一个清单,以向Ansible描述主机部署。\n3. 被管理节点: Ansible控制的远程系统或主机。\n\n## Inventory文件\n\ninventory 主要包括主机和组两个概念, 默认文件 `/etc/ansible/hosts`\n\n### 主机\n主机是 Ansible 可以管理的单个设备或虚拟机。主机可以是物理服务器、虚拟机、容器,甚至是网络设备(如路由器和交换机)。每个主机都有一个唯一的标识(通常是主机名或 IP 地址),并且可以通过 Ansible 的 inventory 文件或其他动态方法来定义\n```\n# 定义单个主机\nweb1.example.com\n\n# 定义多个主机\nweb2.example.com\n192.168.1.10\n\n# 主机变量\n[atlanta]\nhost1 http_port=80 maxRequestsPerChild=808\nhost2 http_port=303 maxRequestsPerChild=909\n```\n\n### 组\n组是主机的集合,可以对一组主机应用相同的配置或操作。组允许用户在多个主机上批量执行任务。一个主机可以属于多个组。组之间还可以嵌套,例如,你可以将所有 Web 服务器放在一个组中,然后将该组嵌套在一个更大的生产环境组中\n``` ini\n# 定义一下所有节点的信息\n[all_hosts]\nnode1 public_ip=192.168.1.101 ansible_host=192.168.1.101 ansible_user=your_user ansible_ssh_pass=your_password ansible_port=22\nnode2 public_ip=192.168.1.102 ansible_host=192.168.1.102 ansible_user=your_user ansible_ssh_pass=your_password ansible_port=22\nnode3 public_ip=192.168.1.103 ansible_host=192.168.1.103 ansible_user=your_user ansible_ssh_pass=your_password ansible_port=22\n\n# 定义一个 mysql 组,假如要在 node1 和 node2上部署 mysql\n[mysql]\nnode1\nnode2\n\n# 定义一个 nginx 组,假如要在 node1 和 node3上部署 nginx\n[nginx]\nnode1\nnode3\n\n# 假如我们还想部署一个 redis 组,mysql 部署在那里,redis 就部署在那里,重新写一遍很麻烦,那么我们可以把 redis 当做 mysql 的子集\n# 这里有一个疑问,我们既然用一个一模一样的组再了,为啥搞个 children,直接用原来的组不行吗,这里可以是可以,但是从模块划分来看,做一下区分易于后续的管理\n[redis:children]\nmysql\n\n# 定义组变量\n[atlanta:vars]\nntp_server=ntp.atlanta.example.com\nproxy=proxy.atlanta.example.com\n```\n\n## 简单使用\n\n在我们了解完 inventory 之后,我们开始做一些简单的模拟:\n\n1. 在 node1 上安装 ansible,作为控制节点,在/etc/ansbile/hosts中加入三个节点的信息\n2. 如果是通过密码连接的话,需要在 ansible_ssh_pass中输入机器密码,如果是通过密钥链接,这里可不填;配置 ssh 免密登录,可以自行百度,这里只需要配置 node1 到所有节点的免密(包括 node1 到 node1 自己)\n3. 免密配置完成后,我们可以做一下简单的操作:\n ``` bash\n # 查看 mysql 组机器的时间信息\n ansilbe mysql -m shell -a \"date\"\n\n # 查看 nginx 组机器的启动时间\n ansible nginx -m shell -a \"uptime\"\n ```\n\n# playbooks\nplaybook是由一个或多个play组成的列表,play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色。从根本上来讲,所谓的task无非是调用ansible的一个module。将多个play组织在一个playbook中,即可以让它们联合起来按事先编排的机制完成某一任务\n\n## playbook语法:\n```\nplaybook使用yaml语法格式,后缀可以是yaml,也可以是yml。\n\n在单个playbook文件中,可以连续三个连子号(---)区分多个play。还有选择性的连续三个点好(...)用来表示play的结尾,也可省略。\n\n次行开始正常写playbook的内容,一般都会写上描述该playbook的功能。\n\n使用#号注释代码。\n\n缩进必须统一,不能空格和tab混用。\n\n缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行实现的。\n\nYAML文件内容和Linux系统大小写判断方式保持一致,是区分大小写的,k/v的值均需大小写敏感\n\nk/v的值可同行写也可以换行写。同行使用:分隔。\n\nv可以是个字符串,也可以是一个列表\n\n一个完整的代码块功能需要最少元素包括 name: task\n```\n\n## playbook核心元素\n\n### 1. Play\n\nPlay 是 Playbook 的基本单元,用于定义在一组主机上执行的一系列任务。一个 Playbook 可以包含多个 Play,每个 Play 在不同的主机或组上执行不同的任务。\n\n关键字:\n- hosts: 指定要在哪些主机或主机组上执行 Play。\n- tasks: 包含一系列任务,这些任务会按顺序执行。\n- vars: 定义在 Play 中使用的变量。\n- roles: 指定要应用的角色。\n- gather_facts: 控制是否收集主机的事实信息(默认 true)。\n- become: 是否使用 sudo 或其他特权提升执行任务。\n\n### 2. Tasks\n\nTasks 是 Play 中的核心部分,定义了要执行的具体操作。每个 Task 通常使用一个 Ansible 模块,并可包含条件、循环、错误处理等。\n\n关键字:\n- name: 任务的描述性名称(可选,但推荐使用)。\n-\taction 或模块名称: 具体执行的操作,如 apt、yum、copy 等。\n-\twhen: 定义条件,满足时才会执行任务。\n-\twith_items: 用于循环执行任务。\n-\tregister: 保存任务的结果到变量。\n-\tignore_errors: 忽略任务执行失败(设为 yes 时)。\n\n### 3. Variables\n\nVariables 是 Playbook 中的动态值,用于提高复用性和灵活性。可以在多个地方定义变量,如 vars、group_vars、host_vars、inventory 文件,或通过命令行传递。\n\n关键字:\n-\tvars: 在 Play 或 Task 中定义变量。\n-\tvars_files: 引入外部变量文件。\n-\tvars_prompt: 运行时提示用户输入变量值。\n\n### 4. Handlers\n\nHandlers 是一种特殊类型的 Task,只会在被触发时执行。通常用于在配置更改后执行动作,如重启服务。比如我要等服务重启完检查端口监听,就可以用handler\n\n关键字:\n-\tname: Handler 的名称。\n-\tnotify: 在普通 Task 中调用 notify 触发对应的 Handler。\n\n### 5. Roles\nRoles 是 Playbook 中组织和复用任务、变量、文件、模板等的一种方式。Roles 使得 Playbook 更加模块化和可维护。举个例子,我现在要在服务器上部署各种各样的组件,webserver、mysql、redis、ng 等等,我们就可以用这个不同的 roles 来管理,我们可以在创建四个文件夹,分别对应起名 webserver、mysql、redis、ng,然后在这些文件夹里面添加服务部署或者更新需要的东西。\n\n角色的目录结构:\n```\nmy_role/\n├── tasks/\n│ └── main.yml\n├── handlers/\n│ └── main.yml\n├── templates/\n├── files/\n├── vars/\n│ └── main.yml\n├── defaults/\n│ └── main.yml\n└── meta/\n └── main.yml\n```\n\nroles内各自目录含义:\n- files\t用来存放copy模块或script模块调用的文件\n- templates\t用来存放jinjia2模板,template模块会自动在此目录中寻找jinjia2模板文件\n- tasks\t此目录应当包含一个main.yml文件,用于定义此角色的任务列表,此文件可以使用include包含其它的位于此目录的task文件\n- handlers\t此目录应当包含一个main.yml文件,用于定义此角色中触发条件时执行的动作\n- vars\t此目录应当包含一个main.yml文件,用于定义此角色用到的变量\n- defailts\t此目录应当包含一个main.yml文件,用于为当前角色设定默认变量\n- meta\t此目录应当包含一个main.yml文件,用于定义此角色的特殊设及其依赖关系\n\n### 6. Includes and Imports\n\nIncludes 和 Imports 用于在 Playbook 中包含其他任务、变量、文件等。import_tasks 和 include_tasks 的区别在于,import_tasks 在解析 Playbook 时执行,而 include_tasks 在运行时执行。\n\n### 7. Templates\n\nTemplates 是使用 Jinja2 模板引擎的文件,用于动态生成配置文件或其他文件。模板通常存放在 templates/ 目录下,并通过 template 模块应用到目标主机\n\n### 8. Tags\n\n标签是用于对 play 进行标注,当你写了一个很长的playbook,其中有很多的任务,这并没有什么问题,不过在实际使用这个剧本时,你可能只是想要执行其中的一部分任务而已,或者,你只想要执行其中一类任务而已,而并非想要执行整个剧本中的全部任务,这时,我们可以借助tags模块为任务进行打标签操作,任务存在标签后,我们可以在执行playbook时利用标签,指定执行哪些任务,或者不执行哪些任务\n\n比如说在实际线上环境中,我们有更新二进制包的操作,那么我们可以在更新二进制的相关 task 中添加名为bin 的 tags,有更新配置文件的操作,那么可以在相关的 tasks 中添加 conf 的 tags\n\n### 完整示例\n我们通过一个安装 nginx 的操作来完整演示一下:\n\n设置项目目录结构:\n```\n.\n├── ansible.cfg\n├── inventory\n├── playbook.yml\n└── roles\n └── nginx\n ├── tasks\n │ ├── main.yml\n │ └── install.yml\n ├── handlers\n │ └── main.yml\n ├── templates\n │ └── nginx.conf.j2\n ├── files\n ├── vars\n │ └── main.yml\n └── defaults\n └── main.yml\n```\n\ninventory配置文件\n``` ini\n[webservers]\n192.168.1.101 ansible_ssh_user=your_user ansible_ssh_pass=your_password ansible_host=203.0.113.1\n```\n\nplaybook.yml:\n```yaml\n---\n- name: Update and Install Nginx\n hosts: webservers #引用组\n become: yes #开启 sudo\n roles: \n - role: nginx #角色是nginx,对应到 roles/nginx 目录\n tags: #两个 tags\n - bin\n - conf\n```\n\n任务文件 roles/nginx/tasks/main.yml:\n```yaml\n---\n# 包含其他任务文件, 这里直接用 install.yml里面的文件内容肯定也是没问题的,但是我们可以通过这样的方式更好的进行管理\n- include_tasks: install.yml\n```\n\n安装任务 roles/nginx/tasks/install.yml:\n``` yaml\n---\n# 更新安装 Nginx\n- name: Update apt cache and install Nginx\n apt: #安装nginx 相关包, 不同平台不太一样,比如 centos 可以使用 package\n name: nginx\n state: latest\n update_cache: yes\n tags:\n - bin #这里添加了 bin 的 tag\n\n# 部署 Nginx 配置文件\n- name: Deploy Nginx configuration from template\n template: #这里是更新 nginx 配置文件\n src: nginx.conf.j2\n dest: /etc/nginx/nginx.conf\n mode: '0644'\n notify: Restart Nginx #这里配合handler 使用,handlers 里面会有一个名称为 “Restart Nginx”的操作\n tags:\n - conf #这里添加了 conf 的 tag\n\n# 检查 Nginx 是否监听正确端口\n- name: Check Nginx is listening on port 80\n command: ss -tuln | grep :80 #通过 command 模块,ss 命令监听 80 端口是否启动\n register: result\n ignore_errors: yes\n\n- name: Print Nginx listening port check result\n debug:\n msg: \"{{ result.stdout }}\"\n when: result.rc == 0\n```\n\n处理程序 roles/nginx/handlers/main.yml:\n```yaml\n---\n# 当配置文件变更时,重启 Nginx, 和上面的 notify 是对应的\n- name: Restart Nginx\n service:\n name: nginx\n state: restarted\n```\n\n模板文件 roles/nginx/templates/nginx.conf.j2:\n```\nserver {\n listen {{ nginx_port }};\n server_name localhost;\n\n location / {\n root /usr/share/nginx/html;\n index index.html index.htm;\n }\n\n error_page 404 /404.html;\n location = /40x.html {\n }\n\n error_page 500 502 503 504 /50x.html;\n location = /50x.html {\n }\n}\n```\n\n默认变量 roles/nginx/defaults/main.yml:\n```yaml\n---\nnginx_port: 80\n```\n\n当我们更新安装时,可以通过(在正式执行前,可以在后面加-C -D 做测试使用):\n``` bash\nansible-playbook playbook.yml \n```\n后续有二进制更新时,可以通过:\n``` bash\nansible-playbook playbook.yml -t bin\n```\n后续有配置文件更新时,可以通过:\n``` bash\nansible-playbook playbook.yml -t conf\n```\n\n## 逻辑控制语句\n\n### 条件语句when\n\nwhen条件支持多种判断类型,主要用于根据某些条件决定是否执行某个任务。这些判断类型通常基于Python的语法,因为Ansible的任务是用Python编写的\n\n主要支持的判断类型:\n- 比较运算符:==, !=, >, <, >=, <= 用于比较两个值。\n- 字符串方法:.startswith(), .endswith(), .find(), .contains() 等字符串方法可以用来检查字符串的特性。\n- 逻辑运算符:and, or, not 用于组合多个条件。\n- Jinja2模板表达式:由于Ansible使用Jinja2作为模板引擎,因此你也可以在when条件中使用Jinja2的表达式和过滤器。\n- Ansible事实(facts)和变量:你可以使用Ansible收集的主机事实(facts)和定义的变量来进行条件判断。\n- 函数和内置方法:Python的内置函数和方法也可以在when条件中使用,比如isinstance(), len(), 等等。\n- 正则表达式:使用Python的正则表达式模块(如re)进行更复杂的字符串匹配。\n\n其中facts涉及到的判断条件非常多,可以通过如下形式获取\n``` yaml\n---\n- hosts: mysql\n tasks:\n - name: show ansible facts\n debug:\n var: ansible_facts\n```\n执行以上yml文件之后,会输出一个json串,我们就可以获取到所有的fact信息了.\n\n示例:假如我们想在ip为 192.168.0.102 的机器上创建文件\n\n``` yaml\n---\n- name: when测试练习\n hosts: webservers\n tasks:\n - name: 文件测试创建\n file: \n path: /tmp/when.txt\n state: touch\n when: \"'192.168.0.102' in ansible_all_ipv4_addresses\"\n```\n\n### 循环语句loop\n用于在任务中循环执行操作\n\n示例:\n``` yaml\n---\n- name: Install multiple packages\n hosts: webservers\n become: yes\n\n tasks:\n - name: Install packages\n apt:\n name: \"{{ item }}\"\n state: present\n loop:\n - nginx\n - git\n - curl\n```\n上面的示例就是循环装包\n\n### 块语句block\n\n在 Ansible 中,block 关键字允许你将多个任务组合成一个逻辑块,并对这个块应用一些条件或错误处理逻辑。这是 Ansible 2.5 版本及以后引入的一个功能,它提供了更高级的任务组织方式。简单来说,block任务块就是一组逻辑的tasks。使用block可以将多个任务合并为一个组。\n\nplaybook会定义三种块,三种块的作用分别如下:\n- block: block里的tasks,如果运行正确,则不会运行rescue;\n- rescue:block里的tasks,如果运行失败,才会运行rescue里的tasks\n- always:block和rescue里的tasks无论是否运行成功,都会运行always里的tasks\n\n","source":"_posts/ansible工具使用.md","raw":"---\ntitle: ansible详细介绍\nauthor: baixiaozhou\ncategories:\n - 运维工具\ntags:\n - ansible\n - 运维工具\ndescription: 本文将对 ansible 工具的具体使用做一个详细的介绍\nrepo: baixiaozhou/SysStress\ndate: 2024-08-19 19:09:34\nbanner: /images/ansible-logo.png\ncover: /images/ansible-logo.png\nreferences:\n - '[ansible 英文文档](https://docs.ansible.com/ansible/latest/index.html)'\n - '[ansible 中文文档](http://www.ansible.com.cn/docs/intro_adhoc.html)'\n---\n\n<!-- Your content starts here -->\n\n{% quot 官方文档 %}\n\n[英文文档](https://docs.ansible.com/ansible/latest/index.html)\n[中文文档](http://www.ansible.com.cn/docs/intro_adhoc.html)\n\n# ansible 介绍\n\n我们先看一下 ansible 的相关介绍:\n```\nAnsible is a radically simple IT automation platform that makes your applications and systems easier to deploy and maintain. Automate everything from code deployment to network configuration to cloud management, in a language that approaches plain English, using SSH, with no agents to install on remote systems. https://docs.ansible.com.\n```\n\n这里有几点核心:\n\n1. ansible 是一个自动化平台\n2. ansible 使用 ssh 协议(通过密码或者密钥等方式进行访问),部署简单,没有客户端,只需在主控端部署 Ansible 环境,无需在远程系统上安装代理程序\n3. 模块化:调用特定的模块,完成特定任务\n4. 支持自定义扩展\n\n每开发一个工具或者平台的时候,这些工具和平台提供了各种各样的功能,那么我们能用 ansible 来干什么呢? 下面就是一些 ansible 核心的功能介绍:\n\n| 功能 | 描述 | 常见场景示例 |\n|----------------------------|------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|\n| 配置管理 | 自动化管理服务器和设备的配置,确保它们处于期望的状态。 | 自动化安装和配置 Web 服务器,确保所有服务器配置一致。 |\n| 应用部署 | 自动化应用程序的部署过程,从代码库拉取到在服务器上部署和配置应用。 | 自动部署多层 Web 应用程序,包括数据库设置、应用服务部署、负载均衡配置等。 |\n| 持续交付与持续集成(CI/CD) | 与 CI/CD 工具集成,实现自动化的构建、测试和部署流程。 | 代码提交后自动执行测试、构建容器镜像,并部署到 Kubernetes 集群中。 |\n| 基础设施即代码(IaC) | 编写和管理基础设施的配置文件,使其像管理代码一样。 | 使用 Ansible Playbooks 定义云环境资源配置,实现可重复的基础设施部署。 |\n| 云管理 | 自动化云服务资源的管理,包括虚拟机、存储、网络等的创建和配置。 | 自动化创建和管理 AWS EC2 实例、配置 VPC 和安全组。 |\n| 网络自动化 | 管理和配置网络设备,使得大规模的网络设备配置变得简单和一致。 | 自动化配置多台交换机的 VLAN 设置和路由协议。 |\n| 安全与合规 | 自动化安全补丁的部署、系统安全配置的强化以及合规性检查。 | 自动化应用系统安全补丁,配置防火墙规则,执行安全扫描和合规性检查。 |\n| 多平台环境管理 | 支持多种操作系统,在混合环境中统一管理平台上的配置和应用。 | 在混合的 Linux 和 Windows 服务器环境中,统一部署和配置监控软件。 |\n| 灾难恢复 | 自动化灾难恢复流程,如备份、数据恢复和服务恢复。 | 自动化数据库备份并在需要时恢复和重建数据库服务。 |\n| 任务调度与批量操作 | 通过 Playbooks 调度定期任务或对大量服务器执行批量操作。 | 定期清理服务器上的临时文件或批量更新多个服务器的操作系统。 |\n\n## ansible的工作机制\n\nAnsible 在管理节点将 Ansible 模块通过 SSH 协议推送到被管理端执行,执行完之后自动删除,可以使用 SVN 等来管理自定义模块及编排\n\n{% image /images/ansible-diagram.png ansible结构 fancybox:true %}\n\n从这张图中我们可以看到,ansible 由以下模块组成\n```\nAnsible: ansible的核心模块\nHost Inventory:主机清单,也就是被管理的主机列表\nPlaybooks:ansible的剧本,可想象为将多个任务放置在一起,一块执行\nCore Modules:ansible的核心模块\nCustom Modules:自定义模块\nConnection Plugins:连接插件,用于与被管控主机之间基于SSH建立连接关系\nPlugins:其他插件,包括记录日志等\n```\n\n## ansible 安装\n\n在 centos 环境下,我们可以通过:\n```\nyum install ansible -y 进行安装\n```\n安装完成后我们看下 ansible 提供了哪些命令:\n| 命令 | 描述 | 示例 |\n|--------------------|------------------------------------------------------------------------------------------|-----------------------------------------------------|\n| `ansible` | 用于在一个或多个主机上运行单个模块(通常用于临时命令)。 | `ansible all -m ping` |\n| `ansible-playbook` | 用于运行 Ansible playbook 文件,是 Ansible 的核心命令之一。 | `ansible-playbook site.yml` |\n| `ansible-galaxy` | 用于管理 Ansible 角色和集合。可以下载、创建和分享角色。 | `ansible-galaxy install geerlingguy.apache` |\n| `ansible-vault` | 用于加密和解密敏感数据(如密码、密钥)。 | `ansible-vault encrypt secrets.yml` |\n| `ansible-doc` | 显示 Ansible 模块的文档和示例用法。 | `ansible-doc -l` |\n| `ansible-config` | 用于查看、验证和管理 Ansible 配置文件。 | `ansible-config list` |\n| `ansible-inventory`| 管理和检索 Ansible inventory 信息。 | `ansible-inventory --list -i inventory.yml` |\n| `ansible-pull` | 用于从远程版本控制系统(如 Git)拉取 playbook 并在本地执行,常用于自动化部署。 | `ansible-pull -U https://github.com/username/repo.git` |\n| `ansible-console` | 提供一个交互式命令行接口,可用于动态执行 Ansible 任务和命令。 | `ansible-console` |\n\n对两个比较常用的命令:`ansible` 和 `ansible-plabook` 做一下具体的介绍:\n\n### ansible 命令\n\n命令格式:\n``` bash\nansible 组名 -m 模块名 -a '参数'\n```\n这里的组名是自定义的一系列组信息,组的定义在后面会讲到。\n\n模块名是ansible提供的一些列支持模块,默认模块是 command,查看 ansible 支持的模块:\n``` bash\nansible-doc -l #大概有 3000 多个\n```\nansible涉及到的模块非常非常多,按照实际需要使用,初步先掌握一些比较常用的就可以\n\n查看模块描述:\n``` bash\nansible-doc -s 模块名称\n```\n实例:\n``` bash\n# 查看webserver 组机器的时间信息\nansible webserver -m shell -a \"date\" # 这里的组就是 webserver,shell 是模块名(比较常用的模块)date 是具体执行的命令\n# 将本机的/tmp/test.sh 拷贝到其他机器上的 /etc目录下\nansible webserver -m copy -a \"src=/tmp/test.sh dest=/etc/test.sh\" # 这里的 copy 是模块名,里面的 src 是源路径,dest 是目标路径\n```\n\n### ansible-playbook命令\n用于执行 Ansible Playbooks。Playbooks 是一系列任务的集合,用于自动化配置管理、应用部署、任务执行等操作。\n基本语法:\n``` bash\nansible-playbook [options] playbook.yml\n```\n命令帮助:\n```\npositional arguments:\n playbook Playbook(s)\n\noptional arguments:\n --ask-vault-pass ask for vault password\n --flush-cache clear the fact cache for every host in inventory\n --force-handlers run handlers even if a task fails\n --list-hosts outputs a list of matching hosts; does not execute\n anything else\n --list-tags list all available tags\n --list-tasks list all tasks that would be executed\n --skip-tags SKIP_TAGS\n only run plays and tasks whose tags do not match these\n values\n --start-at-task START_AT_TASK\n start the playbook at the task matching this name\n --step one-step-at-a-time: confirm each task before running\n --syntax-check perform a syntax check on the playbook, but do not\n execute it\n --vault-id VAULT_IDS the vault identity to use\n --vault-password-file VAULT_PASSWORD_FILES\n vault password file\n --version show program's version number, config file location,\n configured module search path, module location,\n executable location and exit\n -C, --check don't make any changes; instead, try to predict some\n of the changes that may occur\n -D, --diff when changing (small) files and templates, show the\n differences in those files; works great with --check\n -M MODULE_PATH, --module-path MODULE_PATH\n prepend colon-separated path(s) to module library (def\n ault=~/.ansible/plugins/modules:/usr/share/ansible/plu\n gins/modules)\n -e EXTRA_VARS, --extra-vars EXTRA_VARS\n set additional variables as key=value or YAML/JSON, if\n filename prepend with @\n -f FORKS, --forks FORKS\n specify number of parallel processes to use\n (default=64)\n -h, --help show this help message and exit\n -i INVENTORY, --inventory INVENTORY, --inventory-file INVENTORY\n specify inventory host path or comma separated host\n list. --inventory-file is deprecated\n -l SUBSET, --limit SUBSET\n further limit selected hosts to an additional pattern\n -t TAGS, --tags TAGS only run plays and tasks tagged with these values\n -v, --verbose verbose mode (-vvv for more, -vvvv to enable\n connection debugging)\n\nConnection Options:\n control as whom and how to connect to hosts\n\n --private-key PRIVATE_KEY_FILE, --key-file PRIVATE_KEY_FILE\n use this file to authenticate the connection\n --scp-extra-args SCP_EXTRA_ARGS\n specify extra arguments to pass to scp only (e.g. -l)\n --sftp-extra-args SFTP_EXTRA_ARGS\n specify extra arguments to pass to sftp only (e.g. -f,\n -l)\n --ssh-common-args SSH_COMMON_ARGS\n specify common arguments to pass to sftp/scp/ssh (e.g.\n ProxyCommand)\n --ssh-extra-args SSH_EXTRA_ARGS\n specify extra arguments to pass to ssh only (e.g. -R)\n -T TIMEOUT, --timeout TIMEOUT\n override the connection timeout in seconds\n (default=10)\n -c CONNECTION, --connection CONNECTION\n connection type to use (default=smart)\n -k, --ask-pass ask for connection password\n -u REMOTE_USER, --user REMOTE_USER\n connect as this user (default=None)\n\nPrivilege Escalation Options:\n control how and which user you become as on target hosts\n\n --become-method BECOME_METHOD\n privilege escalation method to use (default=sudo), use\n `ansible-doc -t become -l` to list valid choices.\n --become-user BECOME_USER\n run operations as this user (default=root)\n -K, --ask-become-pass\n ask for privilege escalation password\n -b, --become run operations with become (does not imply password\n prompting)\n```\n\n# ansible 基本使用\n\n我们先看一个最基本的 ansible 组成:\n\n{% image https://docs.ansible.com/ansible/latest/_images/ansible_inv_start.svg ansible基本组成 fancybox:true %}\n\n如上图所示,大部分的 ansible 环境都包含以下三个组件:\n1. 控制节点:安装了Ansible的系统。可以在控制节点上运行Ansible命令,例如ansible或ansible-inventory。\n2. Inventory: 按逻辑组织的托管节点的列表。可以在控制节点上创建一个清单,以向Ansible描述主机部署。\n3. 被管理节点: Ansible控制的远程系统或主机。\n\n## Inventory文件\n\ninventory 主要包括主机和组两个概念, 默认文件 `/etc/ansible/hosts`\n\n### 主机\n主机是 Ansible 可以管理的单个设备或虚拟机。主机可以是物理服务器、虚拟机、容器,甚至是网络设备(如路由器和交换机)。每个主机都有一个唯一的标识(通常是主机名或 IP 地址),并且可以通过 Ansible 的 inventory 文件或其他动态方法来定义\n```\n# 定义单个主机\nweb1.example.com\n\n# 定义多个主机\nweb2.example.com\n192.168.1.10\n\n# 主机变量\n[atlanta]\nhost1 http_port=80 maxRequestsPerChild=808\nhost2 http_port=303 maxRequestsPerChild=909\n```\n\n### 组\n组是主机的集合,可以对一组主机应用相同的配置或操作。组允许用户在多个主机上批量执行任务。一个主机可以属于多个组。组之间还可以嵌套,例如,你可以将所有 Web 服务器放在一个组中,然后将该组嵌套在一个更大的生产环境组中\n``` ini\n# 定义一下所有节点的信息\n[all_hosts]\nnode1 public_ip=192.168.1.101 ansible_host=192.168.1.101 ansible_user=your_user ansible_ssh_pass=your_password ansible_port=22\nnode2 public_ip=192.168.1.102 ansible_host=192.168.1.102 ansible_user=your_user ansible_ssh_pass=your_password ansible_port=22\nnode3 public_ip=192.168.1.103 ansible_host=192.168.1.103 ansible_user=your_user ansible_ssh_pass=your_password ansible_port=22\n\n# 定义一个 mysql 组,假如要在 node1 和 node2上部署 mysql\n[mysql]\nnode1\nnode2\n\n# 定义一个 nginx 组,假如要在 node1 和 node3上部署 nginx\n[nginx]\nnode1\nnode3\n\n# 假如我们还想部署一个 redis 组,mysql 部署在那里,redis 就部署在那里,重新写一遍很麻烦,那么我们可以把 redis 当做 mysql 的子集\n# 这里有一个疑问,我们既然用一个一模一样的组再了,为啥搞个 children,直接用原来的组不行吗,这里可以是可以,但是从模块划分来看,做一下区分易于后续的管理\n[redis:children]\nmysql\n\n# 定义组变量\n[atlanta:vars]\nntp_server=ntp.atlanta.example.com\nproxy=proxy.atlanta.example.com\n```\n\n## 简单使用\n\n在我们了解完 inventory 之后,我们开始做一些简单的模拟:\n\n1. 在 node1 上安装 ansible,作为控制节点,在/etc/ansbile/hosts中加入三个节点的信息\n2. 如果是通过密码连接的话,需要在 ansible_ssh_pass中输入机器密码,如果是通过密钥链接,这里可不填;配置 ssh 免密登录,可以自行百度,这里只需要配置 node1 到所有节点的免密(包括 node1 到 node1 自己)\n3. 免密配置完成后,我们可以做一下简单的操作:\n ``` bash\n # 查看 mysql 组机器的时间信息\n ansilbe mysql -m shell -a \"date\"\n\n # 查看 nginx 组机器的启动时间\n ansible nginx -m shell -a \"uptime\"\n ```\n\n# playbooks\nplaybook是由一个或多个play组成的列表,play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色。从根本上来讲,所谓的task无非是调用ansible的一个module。将多个play组织在一个playbook中,即可以让它们联合起来按事先编排的机制完成某一任务\n\n## playbook语法:\n```\nplaybook使用yaml语法格式,后缀可以是yaml,也可以是yml。\n\n在单个playbook文件中,可以连续三个连子号(---)区分多个play。还有选择性的连续三个点好(...)用来表示play的结尾,也可省略。\n\n次行开始正常写playbook的内容,一般都会写上描述该playbook的功能。\n\n使用#号注释代码。\n\n缩进必须统一,不能空格和tab混用。\n\n缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行实现的。\n\nYAML文件内容和Linux系统大小写判断方式保持一致,是区分大小写的,k/v的值均需大小写敏感\n\nk/v的值可同行写也可以换行写。同行使用:分隔。\n\nv可以是个字符串,也可以是一个列表\n\n一个完整的代码块功能需要最少元素包括 name: task\n```\n\n## playbook核心元素\n\n### 1. Play\n\nPlay 是 Playbook 的基本单元,用于定义在一组主机上执行的一系列任务。一个 Playbook 可以包含多个 Play,每个 Play 在不同的主机或组上执行不同的任务。\n\n关键字:\n- hosts: 指定要在哪些主机或主机组上执行 Play。\n- tasks: 包含一系列任务,这些任务会按顺序执行。\n- vars: 定义在 Play 中使用的变量。\n- roles: 指定要应用的角色。\n- gather_facts: 控制是否收集主机的事实信息(默认 true)。\n- become: 是否使用 sudo 或其他特权提升执行任务。\n\n### 2. Tasks\n\nTasks 是 Play 中的核心部分,定义了要执行的具体操作。每个 Task 通常使用一个 Ansible 模块,并可包含条件、循环、错误处理等。\n\n关键字:\n- name: 任务的描述性名称(可选,但推荐使用)。\n-\taction 或模块名称: 具体执行的操作,如 apt、yum、copy 等。\n-\twhen: 定义条件,满足时才会执行任务。\n-\twith_items: 用于循环执行任务。\n-\tregister: 保存任务的结果到变量。\n-\tignore_errors: 忽略任务执行失败(设为 yes 时)。\n\n### 3. Variables\n\nVariables 是 Playbook 中的动态值,用于提高复用性和灵活性。可以在多个地方定义变量,如 vars、group_vars、host_vars、inventory 文件,或通过命令行传递。\n\n关键字:\n-\tvars: 在 Play 或 Task 中定义变量。\n-\tvars_files: 引入外部变量文件。\n-\tvars_prompt: 运行时提示用户输入变量值。\n\n### 4. Handlers\n\nHandlers 是一种特殊类型的 Task,只会在被触发时执行。通常用于在配置更改后执行动作,如重启服务。比如我要等服务重启完检查端口监听,就可以用handler\n\n关键字:\n-\tname: Handler 的名称。\n-\tnotify: 在普通 Task 中调用 notify 触发对应的 Handler。\n\n### 5. Roles\nRoles 是 Playbook 中组织和复用任务、变量、文件、模板等的一种方式。Roles 使得 Playbook 更加模块化和可维护。举个例子,我现在要在服务器上部署各种各样的组件,webserver、mysql、redis、ng 等等,我们就可以用这个不同的 roles 来管理,我们可以在创建四个文件夹,分别对应起名 webserver、mysql、redis、ng,然后在这些文件夹里面添加服务部署或者更新需要的东西。\n\n角色的目录结构:\n```\nmy_role/\n├── tasks/\n│ └── main.yml\n├── handlers/\n│ └── main.yml\n├── templates/\n├── files/\n├── vars/\n│ └── main.yml\n├── defaults/\n│ └── main.yml\n└── meta/\n └── main.yml\n```\n\nroles内各自目录含义:\n- files\t用来存放copy模块或script模块调用的文件\n- templates\t用来存放jinjia2模板,template模块会自动在此目录中寻找jinjia2模板文件\n- tasks\t此目录应当包含一个main.yml文件,用于定义此角色的任务列表,此文件可以使用include包含其它的位于此目录的task文件\n- handlers\t此目录应当包含一个main.yml文件,用于定义此角色中触发条件时执行的动作\n- vars\t此目录应当包含一个main.yml文件,用于定义此角色用到的变量\n- defailts\t此目录应当包含一个main.yml文件,用于为当前角色设定默认变量\n- meta\t此目录应当包含一个main.yml文件,用于定义此角色的特殊设及其依赖关系\n\n### 6. Includes and Imports\n\nIncludes 和 Imports 用于在 Playbook 中包含其他任务、变量、文件等。import_tasks 和 include_tasks 的区别在于,import_tasks 在解析 Playbook 时执行,而 include_tasks 在运行时执行。\n\n### 7. Templates\n\nTemplates 是使用 Jinja2 模板引擎的文件,用于动态生成配置文件或其他文件。模板通常存放在 templates/ 目录下,并通过 template 模块应用到目标主机\n\n### 8. Tags\n\n标签是用于对 play 进行标注,当你写了一个很长的playbook,其中有很多的任务,这并没有什么问题,不过在实际使用这个剧本时,你可能只是想要执行其中的一部分任务而已,或者,你只想要执行其中一类任务而已,而并非想要执行整个剧本中的全部任务,这时,我们可以借助tags模块为任务进行打标签操作,任务存在标签后,我们可以在执行playbook时利用标签,指定执行哪些任务,或者不执行哪些任务\n\n比如说在实际线上环境中,我们有更新二进制包的操作,那么我们可以在更新二进制的相关 task 中添加名为bin 的 tags,有更新配置文件的操作,那么可以在相关的 tasks 中添加 conf 的 tags\n\n### 完整示例\n我们通过一个安装 nginx 的操作来完整演示一下:\n\n设置项目目录结构:\n```\n.\n├── ansible.cfg\n├── inventory\n├── playbook.yml\n└── roles\n └── nginx\n ├── tasks\n │ ├── main.yml\n │ └── install.yml\n ├── handlers\n │ └── main.yml\n ├── templates\n │ └── nginx.conf.j2\n ├── files\n ├── vars\n │ └── main.yml\n └── defaults\n └── main.yml\n```\n\ninventory配置文件\n``` ini\n[webservers]\n192.168.1.101 ansible_ssh_user=your_user ansible_ssh_pass=your_password ansible_host=203.0.113.1\n```\n\nplaybook.yml:\n```yaml\n---\n- name: Update and Install Nginx\n hosts: webservers #引用组\n become: yes #开启 sudo\n roles: \n - role: nginx #角色是nginx,对应到 roles/nginx 目录\n tags: #两个 tags\n - bin\n - conf\n```\n\n任务文件 roles/nginx/tasks/main.yml:\n```yaml\n---\n# 包含其他任务文件, 这里直接用 install.yml里面的文件内容肯定也是没问题的,但是我们可以通过这样的方式更好的进行管理\n- include_tasks: install.yml\n```\n\n安装任务 roles/nginx/tasks/install.yml:\n``` yaml\n---\n# 更新安装 Nginx\n- name: Update apt cache and install Nginx\n apt: #安装nginx 相关包, 不同平台不太一样,比如 centos 可以使用 package\n name: nginx\n state: latest\n update_cache: yes\n tags:\n - bin #这里添加了 bin 的 tag\n\n# 部署 Nginx 配置文件\n- name: Deploy Nginx configuration from template\n template: #这里是更新 nginx 配置文件\n src: nginx.conf.j2\n dest: /etc/nginx/nginx.conf\n mode: '0644'\n notify: Restart Nginx #这里配合handler 使用,handlers 里面会有一个名称为 “Restart Nginx”的操作\n tags:\n - conf #这里添加了 conf 的 tag\n\n# 检查 Nginx 是否监听正确端口\n- name: Check Nginx is listening on port 80\n command: ss -tuln | grep :80 #通过 command 模块,ss 命令监听 80 端口是否启动\n register: result\n ignore_errors: yes\n\n- name: Print Nginx listening port check result\n debug:\n msg: \"{{ result.stdout }}\"\n when: result.rc == 0\n```\n\n处理程序 roles/nginx/handlers/main.yml:\n```yaml\n---\n# 当配置文件变更时,重启 Nginx, 和上面的 notify 是对应的\n- name: Restart Nginx\n service:\n name: nginx\n state: restarted\n```\n\n模板文件 roles/nginx/templates/nginx.conf.j2:\n```\nserver {\n listen {{ nginx_port }};\n server_name localhost;\n\n location / {\n root /usr/share/nginx/html;\n index index.html index.htm;\n }\n\n error_page 404 /404.html;\n location = /40x.html {\n }\n\n error_page 500 502 503 504 /50x.html;\n location = /50x.html {\n }\n}\n```\n\n默认变量 roles/nginx/defaults/main.yml:\n```yaml\n---\nnginx_port: 80\n```\n\n当我们更新安装时,可以通过(在正式执行前,可以在后面加-C -D 做测试使用):\n``` bash\nansible-playbook playbook.yml \n```\n后续有二进制更新时,可以通过:\n``` bash\nansible-playbook playbook.yml -t bin\n```\n后续有配置文件更新时,可以通过:\n``` bash\nansible-playbook playbook.yml -t conf\n```\n\n## 逻辑控制语句\n\n### 条件语句when\n\nwhen条件支持多种判断类型,主要用于根据某些条件决定是否执行某个任务。这些判断类型通常基于Python的语法,因为Ansible的任务是用Python编写的\n\n主要支持的判断类型:\n- 比较运算符:==, !=, >, <, >=, <= 用于比较两个值。\n- 字符串方法:.startswith(), .endswith(), .find(), .contains() 等字符串方法可以用来检查字符串的特性。\n- 逻辑运算符:and, or, not 用于组合多个条件。\n- Jinja2模板表达式:由于Ansible使用Jinja2作为模板引擎,因此你也可以在when条件中使用Jinja2的表达式和过滤器。\n- Ansible事实(facts)和变量:你可以使用Ansible收集的主机事实(facts)和定义的变量来进行条件判断。\n- 函数和内置方法:Python的内置函数和方法也可以在when条件中使用,比如isinstance(), len(), 等等。\n- 正则表达式:使用Python的正则表达式模块(如re)进行更复杂的字符串匹配。\n\n其中facts涉及到的判断条件非常多,可以通过如下形式获取\n``` yaml\n---\n- hosts: mysql\n tasks:\n - name: show ansible facts\n debug:\n var: ansible_facts\n```\n执行以上yml文件之后,会输出一个json串,我们就可以获取到所有的fact信息了.\n\n示例:假如我们想在ip为 192.168.0.102 的机器上创建文件\n\n``` yaml\n---\n- name: when测试练习\n hosts: webservers\n tasks:\n - name: 文件测试创建\n file: \n path: /tmp/when.txt\n state: touch\n when: \"'192.168.0.102' in ansible_all_ipv4_addresses\"\n```\n\n### 循环语句loop\n用于在任务中循环执行操作\n\n示例:\n``` yaml\n---\n- name: Install multiple packages\n hosts: webservers\n become: yes\n\n tasks:\n - name: Install packages\n apt:\n name: \"{{ item }}\"\n state: present\n loop:\n - nginx\n - git\n - curl\n```\n上面的示例就是循环装包\n\n### 块语句block\n\n在 Ansible 中,block 关键字允许你将多个任务组合成一个逻辑块,并对这个块应用一些条件或错误处理逻辑。这是 Ansible 2.5 版本及以后引入的一个功能,它提供了更高级的任务组织方式。简单来说,block任务块就是一组逻辑的tasks。使用block可以将多个任务合并为一个组。\n\nplaybook会定义三种块,三种块的作用分别如下:\n- block: block里的tasks,如果运行正确,则不会运行rescue;\n- rescue:block里的tasks,如果运行失败,才会运行rescue里的tasks\n- always:block和rescue里的tasks无论是否运行成功,都会运行always里的tasks\n\n","slug":"ansible工具使用","published":1,"updated":"2024-08-22T08:28:14.361Z","_id":"cm00w9dq40000zeonf4g6cvaq","comments":1,"layout":"post","photos":[],"content":"<!-- Your content starts here -->\n\n<div class=\"tag-plugin quot\"><p class=\"content\" type=\"text\"><span class=\"empty\"></span><span class=\"text\">官方文档</span><span class=\"empty\"></span></p></div>\n\n<p><a href=\"https://docs.ansible.com/ansible/latest/index.html\">英文文档</a><br><a href=\"http://www.ansible.com.cn/docs/intro_adhoc.html\">中文文档</a></p>\n<h1 id=\"ansible-介绍\"><a href=\"#ansible-介绍\" class=\"headerlink\" title=\"ansible 介绍\"></a>ansible 介绍</h1><p>我们先看一下 ansible 的相关介绍:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">Ansible is a radically simple IT automation platform that makes your applications and systems easier to deploy and maintain. Automate everything from code deployment to network configuration to cloud management, in a language that approaches plain English, using SSH, with no agents to install on remote systems. https://docs.ansible.com.</span><br></pre></td></tr></table></figure>\n\n<p>这里有几点核心:</p>\n<ol>\n<li>ansible 是一个自动化平台</li>\n<li>ansible 使用 ssh 协议(通过密码或者密钥等方式进行访问),部署简单,没有客户端,只需在主控端部署 Ansible 环境,无需在远程系统上安装代理程序</li>\n<li>模块化:调用特定的模块,完成特定任务</li>\n<li>支持自定义扩展</li>\n</ol>\n<p>每开发一个工具或者平台的时候,这些工具和平台提供了各种各样的功能,那么我们能用 ansible 来干什么呢? 下面就是一些 ansible 核心的功能介绍:</p>\n<table>\n<thead>\n<tr>\n<th>功能</th>\n<th>描述</th>\n<th>常见场景示例</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>配置管理</td>\n<td>自动化管理服务器和设备的配置,确保它们处于期望的状态。</td>\n<td>自动化安装和配置 Web 服务器,确保所有服务器配置一致。</td>\n</tr>\n<tr>\n<td>应用部署</td>\n<td>自动化应用程序的部署过程,从代码库拉取到在服务器上部署和配置应用。</td>\n<td>自动部署多层 Web 应用程序,包括数据库设置、应用服务部署、负载均衡配置等。</td>\n</tr>\n<tr>\n<td>持续交付与持续集成(CI/CD)</td>\n<td>与 CI/CD 工具集成,实现自动化的构建、测试和部署流程。</td>\n<td>代码提交后自动执行测试、构建容器镜像,并部署到 Kubernetes 集群中。</td>\n</tr>\n<tr>\n<td>基础设施即代码(IaC)</td>\n<td>编写和管理基础设施的配置文件,使其像管理代码一样。</td>\n<td>使用 Ansible Playbooks 定义云环境资源配置,实现可重复的基础设施部署。</td>\n</tr>\n<tr>\n<td>云管理</td>\n<td>自动化云服务资源的管理,包括虚拟机、存储、网络等的创建和配置。</td>\n<td>自动化创建和管理 AWS EC2 实例、配置 VPC 和安全组。</td>\n</tr>\n<tr>\n<td>网络自动化</td>\n<td>管理和配置网络设备,使得大规模的网络设备配置变得简单和一致。</td>\n<td>自动化配置多台交换机的 VLAN 设置和路由协议。</td>\n</tr>\n<tr>\n<td>安全与合规</td>\n<td>自动化安全补丁的部署、系统安全配置的强化以及合规性检查。</td>\n<td>自动化应用系统安全补丁,配置防火墙规则,执行安全扫描和合规性检查。</td>\n</tr>\n<tr>\n<td>多平台环境管理</td>\n<td>支持多种操作系统,在混合环境中统一管理平台上的配置和应用。</td>\n<td>在混合的 Linux 和 Windows 服务器环境中,统一部署和配置监控软件。</td>\n</tr>\n<tr>\n<td>灾难恢复</td>\n<td>自动化灾难恢复流程,如备份、数据恢复和服务恢复。</td>\n<td>自动化数据库备份并在需要时恢复和重建数据库服务。</td>\n</tr>\n<tr>\n<td>任务调度与批量操作</td>\n<td>通过 Playbooks 调度定期任务或对大量服务器执行批量操作。</td>\n<td>定期清理服务器上的临时文件或批量更新多个服务器的操作系统。</td>\n</tr>\n</tbody></table>\n<h2 id=\"ansible的工作机制\"><a href=\"#ansible的工作机制\" class=\"headerlink\" title=\"ansible的工作机制\"></a>ansible的工作机制</h2><p>Ansible 在管理节点将 Ansible 模块通过 SSH 协议推送到被管理端执行,执行完之后自动删除,可以使用 SVN 等来管理自定义模块及编排</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/ansible-diagram.png\" alt=\"ansible结构\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">ansible结构</span></div></div>\n\n<p>从这张图中我们可以看到,ansible 由以下模块组成</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Ansible: ansible的核心模块</span><br><span class=\"line\">Host Inventory:主机清单,也就是被管理的主机列表</span><br><span class=\"line\">Playbooks:ansible的剧本,可想象为将多个任务放置在一起,一块执行</span><br><span class=\"line\">Core Modules:ansible的核心模块</span><br><span class=\"line\">Custom Modules:自定义模块</span><br><span class=\"line\">Connection Plugins:连接插件,用于与被管控主机之间基于SSH建立连接关系</span><br><span class=\"line\">Plugins:其他插件,包括记录日志等</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"ansible-安装\"><a href=\"#ansible-安装\" class=\"headerlink\" title=\"ansible 安装\"></a>ansible 安装</h2><p>在 centos 环境下,我们可以通过:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">yum install ansible -y 进行安装</span><br></pre></td></tr></table></figure>\n<p>安装完成后我们看下 ansible 提供了哪些命令:</p>\n<table>\n<thead>\n<tr>\n<th>命令</th>\n<th>描述</th>\n<th>示例</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>ansible</code></td>\n<td>用于在一个或多个主机上运行单个模块(通常用于临时命令)。</td>\n<td><code>ansible all -m ping</code></td>\n</tr>\n<tr>\n<td><code>ansible-playbook</code></td>\n<td>用于运行 Ansible playbook 文件,是 Ansible 的核心命令之一。</td>\n<td><code>ansible-playbook site.yml</code></td>\n</tr>\n<tr>\n<td><code>ansible-galaxy</code></td>\n<td>用于管理 Ansible 角色和集合。可以下载、创建和分享角色。</td>\n<td><code>ansible-galaxy install geerlingguy.apache</code></td>\n</tr>\n<tr>\n<td><code>ansible-vault</code></td>\n<td>用于加密和解密敏感数据(如密码、密钥)。</td>\n<td><code>ansible-vault encrypt secrets.yml</code></td>\n</tr>\n<tr>\n<td><code>ansible-doc</code></td>\n<td>显示 Ansible 模块的文档和示例用法。</td>\n<td><code>ansible-doc -l</code></td>\n</tr>\n<tr>\n<td><code>ansible-config</code></td>\n<td>用于查看、验证和管理 Ansible 配置文件。</td>\n<td><code>ansible-config list</code></td>\n</tr>\n<tr>\n<td><code>ansible-inventory</code></td>\n<td>管理和检索 Ansible inventory 信息。</td>\n<td><code>ansible-inventory --list -i inventory.yml</code></td>\n</tr>\n<tr>\n<td><code>ansible-pull</code></td>\n<td>用于从远程版本控制系统(如 Git)拉取 playbook 并在本地执行,常用于自动化部署。</td>\n<td><code>ansible-pull -U https://github.com/username/repo.git</code></td>\n</tr>\n<tr>\n<td><code>ansible-console</code></td>\n<td>提供一个交互式命令行接口,可用于动态执行 Ansible 任务和命令。</td>\n<td><code>ansible-console</code></td>\n</tr>\n</tbody></table>\n<p>对两个比较常用的命令:<code>ansible</code> 和 <code>ansible-plabook</code> 做一下具体的介绍:</p>\n<h3 id=\"ansible-命令\"><a href=\"#ansible-命令\" class=\"headerlink\" title=\"ansible 命令\"></a>ansible 命令</h3><p>命令格式:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible 组名 -m 模块名 -a <span class=\"string\">'参数'</span></span><br></pre></td></tr></table></figure>\n<p>这里的组名是自定义的一系列组信息,组的定义在后面会讲到。</p>\n<p>模块名是ansible提供的一些列支持模块,默认模块是 command,查看 ansible 支持的模块:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible-doc -l <span class=\"comment\">#大概有 3000 多个</span></span><br></pre></td></tr></table></figure>\n<p>ansible涉及到的模块非常非常多,按照实际需要使用,初步先掌握一些比较常用的就可以</p>\n<p>查看模块描述:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible-doc -s 模块名称</span><br></pre></td></tr></table></figure>\n<p>实例:</p>\n<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># 查看webserver 组机器的时间信息</span></span><br><span class=\"line\">ansible webserver -m shell -a <span class=\"string\">"date"</span> <span class=\"comment\"># 这里的组就是 webserver,shell 是模块名(比较常用的模块)date 是具体执行的命令</span></span><br><span class=\"line\"><span class=\"comment\"># 将本机的/tmp/test.sh 拷贝到其他机器上的 /etc目录下</span></span><br><span class=\"line\">ansible webserver -m copy -a <span class=\"string\">"src=/tmp/test.sh dest=/etc/test.sh"</span> <span class=\"comment\"># 这里的 copy 是模块名,里面的 src 是源路径,dest 是目标路径</span></span><br></pre></td></tr></table></figure>\n\n<h3 id=\"ansible-playbook命令\"><a href=\"#ansible-playbook命令\" class=\"headerlink\" title=\"ansible-playbook命令\"></a>ansible-playbook命令</h3><p>用于执行 Ansible Playbooks。Playbooks 是一系列任务的集合,用于自动化配置管理、应用部署、任务执行等操作。<br>基本语法:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible-playbook [options] playbook.yml</span><br></pre></td></tr></table></figure>\n<p>命令帮助:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">positional arguments:</span><br><span class=\"line\"> playbook Playbook(s)</span><br><span class=\"line\"></span><br><span class=\"line\">optional arguments:</span><br><span class=\"line\"> --ask-vault-pass ask for vault password</span><br><span class=\"line\"> --flush-cache clear the fact cache for every host in inventory</span><br><span class=\"line\"> --force-handlers run handlers even if a task fails</span><br><span class=\"line\"> --list-hosts outputs a list of matching hosts; does not execute</span><br><span class=\"line\"> anything else</span><br><span class=\"line\"> --list-tags list all available tags</span><br><span class=\"line\"> --list-tasks list all tasks that would be executed</span><br><span class=\"line\"> --skip-tags SKIP_TAGS</span><br><span class=\"line\"> only run plays and tasks whose tags do not match these</span><br><span class=\"line\"> values</span><br><span class=\"line\"> --start-at-task START_AT_TASK</span><br><span class=\"line\"> start the playbook at the task matching this name</span><br><span class=\"line\"> --step one-step-at-a-time: confirm each task before running</span><br><span class=\"line\"> --syntax-check perform a syntax check on the playbook, but do not</span><br><span class=\"line\"> execute it</span><br><span class=\"line\"> --vault-id VAULT_IDS the vault identity to use</span><br><span class=\"line\"> --vault-password-file VAULT_PASSWORD_FILES</span><br><span class=\"line\"> vault password file</span><br><span class=\"line\"> --version show program's version number, config file location,</span><br><span class=\"line\"> configured module search path, module location,</span><br><span class=\"line\"> executable location and exit</span><br><span class=\"line\"> -C, --check don't make any changes; instead, try to predict some</span><br><span class=\"line\"> of the changes that may occur</span><br><span class=\"line\"> -D, --diff when changing (small) files and templates, show the</span><br><span class=\"line\"> differences in those files; works great with --check</span><br><span class=\"line\"> -M MODULE_PATH, --module-path MODULE_PATH</span><br><span class=\"line\"> prepend colon-separated path(s) to module library (def</span><br><span class=\"line\"> ault=~/.ansible/plugins/modules:/usr/share/ansible/plu</span><br><span class=\"line\"> gins/modules)</span><br><span class=\"line\"> -e EXTRA_VARS, --extra-vars EXTRA_VARS</span><br><span class=\"line\"> set additional variables as key=value or YAML/JSON, if</span><br><span class=\"line\"> filename prepend with @</span><br><span class=\"line\"> -f FORKS, --forks FORKS</span><br><span class=\"line\"> specify number of parallel processes to use</span><br><span class=\"line\"> (default=64)</span><br><span class=\"line\"> -h, --help show this help message and exit</span><br><span class=\"line\"> -i INVENTORY, --inventory INVENTORY, --inventory-file INVENTORY</span><br><span class=\"line\"> specify inventory host path or comma separated host</span><br><span class=\"line\"> list. --inventory-file is deprecated</span><br><span class=\"line\"> -l SUBSET, --limit SUBSET</span><br><span class=\"line\"> further limit selected hosts to an additional pattern</span><br><span class=\"line\"> -t TAGS, --tags TAGS only run plays and tasks tagged with these values</span><br><span class=\"line\"> -v, --verbose verbose mode (-vvv for more, -vvvv to enable</span><br><span class=\"line\"> connection debugging)</span><br><span class=\"line\"></span><br><span class=\"line\">Connection Options:</span><br><span class=\"line\"> control as whom and how to connect to hosts</span><br><span class=\"line\"></span><br><span class=\"line\"> --private-key PRIVATE_KEY_FILE, --key-file PRIVATE_KEY_FILE</span><br><span class=\"line\"> use this file to authenticate the connection</span><br><span class=\"line\"> --scp-extra-args SCP_EXTRA_ARGS</span><br><span class=\"line\"> specify extra arguments to pass to scp only (e.g. -l)</span><br><span class=\"line\"> --sftp-extra-args SFTP_EXTRA_ARGS</span><br><span class=\"line\"> specify extra arguments to pass to sftp only (e.g. -f,</span><br><span class=\"line\"> -l)</span><br><span class=\"line\"> --ssh-common-args SSH_COMMON_ARGS</span><br><span class=\"line\"> specify common arguments to pass to sftp/scp/ssh (e.g.</span><br><span class=\"line\"> ProxyCommand)</span><br><span class=\"line\"> --ssh-extra-args SSH_EXTRA_ARGS</span><br><span class=\"line\"> specify extra arguments to pass to ssh only (e.g. -R)</span><br><span class=\"line\"> -T TIMEOUT, --timeout TIMEOUT</span><br><span class=\"line\"> override the connection timeout in seconds</span><br><span class=\"line\"> (default=10)</span><br><span class=\"line\"> -c CONNECTION, --connection CONNECTION</span><br><span class=\"line\"> connection type to use (default=smart)</span><br><span class=\"line\"> -k, --ask-pass ask for connection password</span><br><span class=\"line\"> -u REMOTE_USER, --user REMOTE_USER</span><br><span class=\"line\"> connect as this user (default=None)</span><br><span class=\"line\"></span><br><span class=\"line\">Privilege Escalation Options:</span><br><span class=\"line\"> control how and which user you become as on target hosts</span><br><span class=\"line\"></span><br><span class=\"line\"> --become-method BECOME_METHOD</span><br><span class=\"line\"> privilege escalation method to use (default=sudo), use</span><br><span class=\"line\"> `ansible-doc -t become -l` to list valid choices.</span><br><span class=\"line\"> --become-user BECOME_USER</span><br><span class=\"line\"> run operations as this user (default=root)</span><br><span class=\"line\"> -K, --ask-become-pass</span><br><span class=\"line\"> ask for privilege escalation password</span><br><span class=\"line\"> -b, --become run operations with become (does not imply password</span><br><span class=\"line\"> prompting)</span><br></pre></td></tr></table></figure>\n\n<h1 id=\"ansible-基本使用\"><a href=\"#ansible-基本使用\" class=\"headerlink\" title=\"ansible 基本使用\"></a>ansible 基本使用</h1><p>我们先看一个最基本的 ansible 组成:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"https://docs.ansible.com/ansible/latest/_images/ansible_inv_start.svg\" alt=\"ansible基本组成\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">ansible基本组成</span></div></div>\n\n<p>如上图所示,大部分的 ansible 环境都包含以下三个组件:</p>\n<ol>\n<li>控制节点:安装了Ansible的系统。可以在控制节点上运行Ansible命令,例如ansible或ansible-inventory。</li>\n<li>Inventory: 按逻辑组织的托管节点的列表。可以在控制节点上创建一个清单,以向Ansible描述主机部署。</li>\n<li>被管理节点: Ansible控制的远程系统或主机。</li>\n</ol>\n<h2 id=\"Inventory文件\"><a href=\"#Inventory文件\" class=\"headerlink\" title=\"Inventory文件\"></a>Inventory文件</h2><p>inventory 主要包括主机和组两个概念, 默认文件 <code>/etc/ansible/hosts</code></p>\n<h3 id=\"主机\"><a href=\"#主机\" class=\"headerlink\" title=\"主机\"></a>主机</h3><p>主机是 Ansible 可以管理的单个设备或虚拟机。主机可以是物理服务器、虚拟机、容器,甚至是网络设备(如路由器和交换机)。每个主机都有一个唯一的标识(通常是主机名或 IP 地址),并且可以通过 Ansible 的 inventory 文件或其他动态方法来定义</p>\n<figure class=\"highlight plaintext\"><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><td class=\"code\"><pre><span class=\"line\"># 定义单个主机</span><br><span class=\"line\">web1.example.com</span><br><span class=\"line\"></span><br><span class=\"line\"># 定义多个主机</span><br><span class=\"line\">web2.example.com</span><br><span class=\"line\">192.168.1.10</span><br><span class=\"line\"></span><br><span class=\"line\"># 主机变量</span><br><span class=\"line\">[atlanta]</span><br><span class=\"line\">host1 http_port=80 maxRequestsPerChild=808</span><br><span class=\"line\">host2 http_port=303 maxRequestsPerChild=909</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"组\"><a href=\"#组\" class=\"headerlink\" title=\"组\"></a>组</h3><p>组是主机的集合,可以对一组主机应用相同的配置或操作。组允许用户在多个主机上批量执行任务。一个主机可以属于多个组。组之间还可以嵌套,例如,你可以将所有 Web 服务器放在一个组中,然后将该组嵌套在一个更大的生产环境组中</p>\n<figure class=\"highlight ini\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># 定义一下所有节点的信息</span></span><br><span class=\"line\"><span class=\"section\">[all_hosts]</span></span><br><span class=\"line\">node1 <span class=\"attr\">public_ip</span>=<span class=\"number\">192.168</span>.<span class=\"number\">1.101</span> ansible_host=<span class=\"number\">192.168</span>.<span class=\"number\">1.101</span> ansible_user=your_user ansible_ssh_pass=your_password ansible_port=<span class=\"number\">22</span></span><br><span class=\"line\">node2 <span class=\"attr\">public_ip</span>=<span class=\"number\">192.168</span>.<span class=\"number\">1.102</span> ansible_host=<span class=\"number\">192.168</span>.<span class=\"number\">1.102</span> ansible_user=your_user ansible_ssh_pass=your_password ansible_port=<span class=\"number\">22</span></span><br><span class=\"line\">node3 <span class=\"attr\">public_ip</span>=<span class=\"number\">192.168</span>.<span class=\"number\">1.103</span> ansible_host=<span class=\"number\">192.168</span>.<span class=\"number\">1.103</span> ansible_user=your_user ansible_ssh_pass=your_password ansible_port=<span class=\"number\">22</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 定义一个 mysql 组,假如要在 node1 和 node2上部署 mysql</span></span><br><span class=\"line\"><span class=\"section\">[mysql]</span></span><br><span class=\"line\">node1</span><br><span class=\"line\">node2</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 定义一个 nginx 组,假如要在 node1 和 node3上部署 nginx</span></span><br><span class=\"line\"><span class=\"section\">[nginx]</span></span><br><span class=\"line\">node1</span><br><span class=\"line\">node3</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 假如我们还想部署一个 redis 组,mysql 部署在那里,redis 就部署在那里,重新写一遍很麻烦,那么我们可以把 redis 当做 mysql 的子集</span></span><br><span class=\"line\"><span class=\"comment\"># 这里有一个疑问,我们既然用一个一模一样的组再了,为啥搞个 children,直接用原来的组不行吗,这里可以是可以,但是从模块划分来看,做一下区分易于后续的管理</span></span><br><span class=\"line\"><span class=\"section\">[redis:children]</span></span><br><span class=\"line\">mysql</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 定义组变量</span></span><br><span class=\"line\"><span class=\"section\">[atlanta:vars]</span></span><br><span class=\"line\"><span class=\"attr\">ntp_server</span>=ntp.atlanta.example.com</span><br><span class=\"line\"><span class=\"attr\">proxy</span>=proxy.atlanta.example.com</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"简单使用\"><a href=\"#简单使用\" class=\"headerlink\" title=\"简单使用\"></a>简单使用</h2><p>在我们了解完 inventory 之后,我们开始做一些简单的模拟:</p>\n<ol>\n<li>在 node1 上安装 ansible,作为控制节点,在/etc/ansbile/hosts中加入三个节点的信息</li>\n<li>如果是通过密码连接的话,需要在 ansible_ssh_pass中输入机器密码,如果是通过密钥链接,这里可不填;配置 ssh 免密登录,可以自行百度,这里只需要配置 node1 到所有节点的免密(包括 node1 到 node1 自己)</li>\n<li>免密配置完成后,我们可以做一下简单的操作:<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># 查看 mysql 组机器的时间信息</span></span><br><span class=\"line\">ansilbe mysql -m shell -a <span class=\"string\">"date"</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 查看 nginx 组机器的启动时间</span></span><br><span class=\"line\">ansible nginx -m shell -a <span class=\"string\">"uptime"</span></span><br></pre></td></tr></table></figure></li>\n</ol>\n<h1 id=\"playbooks\"><a href=\"#playbooks\" class=\"headerlink\" title=\"playbooks\"></a>playbooks</h1><p>playbook是由一个或多个play组成的列表,play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色。从根本上来讲,所谓的task无非是调用ansible的一个module。将多个play组织在一个playbook中,即可以让它们联合起来按事先编排的机制完成某一任务</p>\n<h2 id=\"playbook语法\"><a href=\"#playbook语法\" class=\"headerlink\" title=\"playbook语法:\"></a>playbook语法:</h2><figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">playbook使用yaml语法格式,后缀可以是yaml,也可以是yml。</span><br><span class=\"line\"></span><br><span class=\"line\">在单个playbook文件中,可以连续三个连子号(---)区分多个play。还有选择性的连续三个点好(...)用来表示play的结尾,也可省略。</span><br><span class=\"line\"></span><br><span class=\"line\">次行开始正常写playbook的内容,一般都会写上描述该playbook的功能。</span><br><span class=\"line\"></span><br><span class=\"line\">使用#号注释代码。</span><br><span class=\"line\"></span><br><span class=\"line\">缩进必须统一,不能空格和tab混用。</span><br><span class=\"line\"></span><br><span class=\"line\">缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行实现的。</span><br><span class=\"line\"></span><br><span class=\"line\">YAML文件内容和Linux系统大小写判断方式保持一致,是区分大小写的,k/v的值均需大小写敏感</span><br><span class=\"line\"></span><br><span class=\"line\">k/v的值可同行写也可以换行写。同行使用:分隔。</span><br><span class=\"line\"></span><br><span class=\"line\">v可以是个字符串,也可以是一个列表</span><br><span class=\"line\"></span><br><span class=\"line\">一个完整的代码块功能需要最少元素包括 name: task</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"playbook核心元素\"><a href=\"#playbook核心元素\" class=\"headerlink\" title=\"playbook核心元素\"></a>playbook核心元素</h2><h3 id=\"1-Play\"><a href=\"#1-Play\" class=\"headerlink\" title=\"1. Play\"></a>1. Play</h3><p>Play 是 Playbook 的基本单元,用于定义在一组主机上执行的一系列任务。一个 Playbook 可以包含多个 Play,每个 Play 在不同的主机或组上执行不同的任务。</p>\n<p>关键字:</p>\n<ul>\n<li>hosts: 指定要在哪些主机或主机组上执行 Play。</li>\n<li>tasks: 包含一系列任务,这些任务会按顺序执行。</li>\n<li>vars: 定义在 Play 中使用的变量。</li>\n<li>roles: 指定要应用的角色。</li>\n<li>gather_facts: 控制是否收集主机的事实信息(默认 true)。</li>\n<li>become: 是否使用 sudo 或其他特权提升执行任务。</li>\n</ul>\n<h3 id=\"2-Tasks\"><a href=\"#2-Tasks\" class=\"headerlink\" title=\"2. Tasks\"></a>2. Tasks</h3><p>Tasks 是 Play 中的核心部分,定义了要执行的具体操作。每个 Task 通常使用一个 Ansible 模块,并可包含条件、循环、错误处理等。</p>\n<p>关键字:</p>\n<ul>\n<li>name: 任务的描述性名称(可选,但推荐使用)。</li>\n<li> action 或模块名称: 具体执行的操作,如 apt、yum、copy 等。</li>\n<li> when: 定义条件,满足时才会执行任务。</li>\n<li> with_items: 用于循环执行任务。</li>\n<li> register: 保存任务的结果到变量。</li>\n<li> ignore_errors: 忽略任务执行失败(设为 yes 时)。</li>\n</ul>\n<h3 id=\"3-Variables\"><a href=\"#3-Variables\" class=\"headerlink\" title=\"3. Variables\"></a>3. Variables</h3><p>Variables 是 Playbook 中的动态值,用于提高复用性和灵活性。可以在多个地方定义变量,如 vars、group_vars、host_vars、inventory 文件,或通过命令行传递。</p>\n<p>关键字:<br>-\tvars: 在 Play 或 Task 中定义变量。<br>-\tvars_files: 引入外部变量文件。<br>-\tvars_prompt: 运行时提示用户输入变量值。</p>\n<h3 id=\"4-Handlers\"><a href=\"#4-Handlers\" class=\"headerlink\" title=\"4. Handlers\"></a>4. Handlers</h3><p>Handlers 是一种特殊类型的 Task,只会在被触发时执行。通常用于在配置更改后执行动作,如重启服务。比如我要等服务重启完检查端口监听,就可以用handler</p>\n<p>关键字:<br>-\tname: Handler 的名称。<br>-\tnotify: 在普通 Task 中调用 notify 触发对应的 Handler。</p>\n<h3 id=\"5-Roles\"><a href=\"#5-Roles\" class=\"headerlink\" title=\"5. Roles\"></a>5. Roles</h3><p>Roles 是 Playbook 中组织和复用任务、变量、文件、模板等的一种方式。Roles 使得 Playbook 更加模块化和可维护。举个例子,我现在要在服务器上部署各种各样的组件,webserver、mysql、redis、ng 等等,我们就可以用这个不同的 roles 来管理,我们可以在创建四个文件夹,分别对应起名 webserver、mysql、redis、ng,然后在这些文件夹里面添加服务部署或者更新需要的东西。</p>\n<p>角色的目录结构:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">my_role/</span><br><span class=\"line\">├── tasks/</span><br><span class=\"line\">│ └── main.yml</span><br><span class=\"line\">├── handlers/</span><br><span class=\"line\">│ └── main.yml</span><br><span class=\"line\">├── templates/</span><br><span class=\"line\">├── files/</span><br><span class=\"line\">├── vars/</span><br><span class=\"line\">│ └── main.yml</span><br><span class=\"line\">├── defaults/</span><br><span class=\"line\">│ └── main.yml</span><br><span class=\"line\">└── meta/</span><br><span class=\"line\"> └── main.yml</span><br></pre></td></tr></table></figure>\n\n<p>roles内各自目录含义:</p>\n<ul>\n<li>files\t用来存放copy模块或script模块调用的文件</li>\n<li>templates\t用来存放jinjia2模板,template模块会自动在此目录中寻找jinjia2模板文件</li>\n<li>tasks\t此目录应当包含一个main.yml文件,用于定义此角色的任务列表,此文件可以使用include包含其它的位于此目录的task文件</li>\n<li>handlers\t此目录应当包含一个main.yml文件,用于定义此角色中触发条件时执行的动作</li>\n<li>vars\t此目录应当包含一个main.yml文件,用于定义此角色用到的变量</li>\n<li>defailts\t此目录应当包含一个main.yml文件,用于为当前角色设定默认变量</li>\n<li>meta\t此目录应当包含一个main.yml文件,用于定义此角色的特殊设及其依赖关系</li>\n</ul>\n<h3 id=\"6-Includes-and-Imports\"><a href=\"#6-Includes-and-Imports\" class=\"headerlink\" title=\"6. Includes and Imports\"></a>6. Includes and Imports</h3><p>Includes 和 Imports 用于在 Playbook 中包含其他任务、变量、文件等。import_tasks 和 include_tasks 的区别在于,import_tasks 在解析 Playbook 时执行,而 include_tasks 在运行时执行。</p>\n<h3 id=\"7-Templates\"><a href=\"#7-Templates\" class=\"headerlink\" title=\"7. Templates\"></a>7. Templates</h3><p>Templates 是使用 Jinja2 模板引擎的文件,用于动态生成配置文件或其他文件。模板通常存放在 templates/ 目录下,并通过 template 模块应用到目标主机</p>\n<h3 id=\"8-Tags\"><a href=\"#8-Tags\" class=\"headerlink\" title=\"8. Tags\"></a>8. Tags</h3><p>标签是用于对 play 进行标注,当你写了一个很长的playbook,其中有很多的任务,这并没有什么问题,不过在实际使用这个剧本时,你可能只是想要执行其中的一部分任务而已,或者,你只想要执行其中一类任务而已,而并非想要执行整个剧本中的全部任务,这时,我们可以借助tags模块为任务进行打标签操作,任务存在标签后,我们可以在执行playbook时利用标签,指定执行哪些任务,或者不执行哪些任务</p>\n<p>比如说在实际线上环境中,我们有更新二进制包的操作,那么我们可以在更新二进制的相关 task 中添加名为bin 的 tags,有更新配置文件的操作,那么可以在相关的 tasks 中添加 conf 的 tags</p>\n<h3 id=\"完整示例\"><a href=\"#完整示例\" class=\"headerlink\" title=\"完整示例\"></a>完整示例</h3><p>我们通过一个安装 nginx 的操作来完整演示一下:</p>\n<p>设置项目目录结构:</p>\n<figure class=\"highlight plaintext\"><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><td class=\"code\"><pre><span class=\"line\">.</span><br><span class=\"line\">├── ansible.cfg</span><br><span class=\"line\">├── inventory</span><br><span class=\"line\">├── playbook.yml</span><br><span class=\"line\">└── roles</span><br><span class=\"line\"> └── nginx</span><br><span class=\"line\"> ├── tasks</span><br><span class=\"line\"> │ ├── main.yml</span><br><span class=\"line\"> │ └── install.yml</span><br><span class=\"line\"> ├── handlers</span><br><span class=\"line\"> │ └── main.yml</span><br><span class=\"line\"> ├── templates</span><br><span class=\"line\"> │ └── nginx.conf.j2</span><br><span class=\"line\"> ├── files</span><br><span class=\"line\"> ├── vars</span><br><span class=\"line\"> │ └── main.yml</span><br><span class=\"line\"> └── defaults</span><br><span class=\"line\"> └── main.yml</span><br></pre></td></tr></table></figure>\n\n<p>inventory配置文件</p>\n<figure class=\"highlight ini\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"section\">[webservers]</span></span><br><span class=\"line\">192.168.1.101 <span class=\"attr\">ansible_ssh_user</span>=your_user ansible_ssh_pass=your_password ansible_host=<span class=\"number\">203.0</span>.<span class=\"number\">113.1</span></span><br></pre></td></tr></table></figure>\n\n<p>playbook.yml:</p>\n<figure class=\"highlight yaml\"><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><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Update</span> <span class=\"string\">and</span> <span class=\"string\">Install</span> <span class=\"string\">Nginx</span></span><br><span class=\"line\"> <span class=\"attr\">hosts:</span> <span class=\"string\">webservers</span> <span class=\"comment\">#引用组</span></span><br><span class=\"line\"> <span class=\"attr\">become:</span> <span class=\"literal\">yes</span> <span class=\"comment\">#开启 sudo</span></span><br><span class=\"line\"> <span class=\"attr\">roles:</span> </span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">role:</span> <span class=\"string\">nginx</span> <span class=\"comment\">#角色是nginx,对应到 roles/nginx 目录</span></span><br><span class=\"line\"> <span class=\"attr\">tags:</span> <span class=\"comment\">#两个 tags</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">bin</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">conf</span></span><br></pre></td></tr></table></figure>\n\n<p>任务文件 roles/nginx/tasks/main.yml:</p>\n<figure class=\"highlight yaml\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"comment\"># 包含其他任务文件, 这里直接用 install.yml里面的文件内容肯定也是没问题的,但是我们可以通过这样的方式更好的进行管理</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">include_tasks:</span> <span class=\"string\">install.yml</span></span><br></pre></td></tr></table></figure>\n\n<p>安装任务 roles/nginx/tasks/install.yml:</p>\n<figure class=\"highlight yaml\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"comment\"># 更新安装 Nginx</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Update</span> <span class=\"string\">apt</span> <span class=\"string\">cache</span> <span class=\"string\">and</span> <span class=\"string\">install</span> <span class=\"string\">Nginx</span></span><br><span class=\"line\"> <span class=\"attr\">apt:</span> <span class=\"comment\">#安装nginx 相关包, 不同平台不太一样,比如 centos 可以使用 package</span></span><br><span class=\"line\"> <span class=\"attr\">name:</span> <span class=\"string\">nginx</span></span><br><span class=\"line\"> <span class=\"attr\">state:</span> <span class=\"string\">latest</span></span><br><span class=\"line\"> <span class=\"attr\">update_cache:</span> <span class=\"literal\">yes</span></span><br><span class=\"line\"> <span class=\"attr\">tags:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">bin</span> <span class=\"comment\">#这里添加了 bin 的 tag</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 部署 Nginx 配置文件</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Deploy</span> <span class=\"string\">Nginx</span> <span class=\"string\">configuration</span> <span class=\"string\">from</span> <span class=\"string\">template</span></span><br><span class=\"line\"> <span class=\"attr\">template:</span> <span class=\"comment\">#这里是更新 nginx 配置文件</span></span><br><span class=\"line\"> <span class=\"attr\">src:</span> <span class=\"string\">nginx.conf.j2</span></span><br><span class=\"line\"> <span class=\"attr\">dest:</span> <span class=\"string\">/etc/nginx/nginx.conf</span></span><br><span class=\"line\"> <span class=\"attr\">mode:</span> <span class=\"string\">'0644'</span></span><br><span class=\"line\"> <span class=\"attr\">notify:</span> <span class=\"string\">Restart</span> <span class=\"string\">Nginx</span> <span class=\"comment\">#这里配合handler 使用,handlers 里面会有一个名称为 “Restart Nginx”的操作</span></span><br><span class=\"line\"> <span class=\"attr\">tags:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">conf</span> <span class=\"comment\">#这里添加了 conf 的 tag</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 检查 Nginx 是否监听正确端口</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Check</span> <span class=\"string\">Nginx</span> <span class=\"string\">is</span> <span class=\"string\">listening</span> <span class=\"string\">on</span> <span class=\"string\">port</span> <span class=\"number\">80</span></span><br><span class=\"line\"> <span class=\"attr\">command:</span> <span class=\"string\">ss</span> <span class=\"string\">-tuln</span> <span class=\"string\">|</span> <span class=\"string\">grep</span> <span class=\"string\">:80</span> <span class=\"comment\">#通过 command 模块,ss 命令监听 80 端口是否启动</span></span><br><span class=\"line\"> <span class=\"attr\">register:</span> <span class=\"string\">result</span></span><br><span class=\"line\"> <span class=\"attr\">ignore_errors:</span> <span class=\"literal\">yes</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Print</span> <span class=\"string\">Nginx</span> <span class=\"string\">listening</span> <span class=\"string\">port</span> <span class=\"string\">check</span> <span class=\"string\">result</span></span><br><span class=\"line\"> <span class=\"attr\">debug:</span></span><br><span class=\"line\"> <span class=\"attr\">msg:</span> <span class=\"string\">"<span class=\"template-variable\">{{ result.stdout }}</span>"</span></span><br><span class=\"line\"> <span class=\"attr\">when:</span> <span class=\"string\">result.rc</span> <span class=\"string\">==</span> <span class=\"number\">0</span></span><br></pre></td></tr></table></figure>\n\n<p>处理程序 roles/nginx/handlers/main.yml:</p>\n<figure class=\"highlight yaml\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"comment\"># 当配置文件变更时,重启 Nginx, 和上面的 notify 是对应的</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Restart</span> <span class=\"string\">Nginx</span></span><br><span class=\"line\"> <span class=\"attr\">service:</span></span><br><span class=\"line\"> <span class=\"attr\">name:</span> <span class=\"string\">nginx</span></span><br><span class=\"line\"> <span class=\"attr\">state:</span> <span class=\"string\">restarted</span></span><br></pre></td></tr></table></figure>\n\n<p>模板文件 roles/nginx/templates/nginx.conf.j2:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">server {</span><br><span class=\"line\"> listen {{ nginx_port }};</span><br><span class=\"line\"> server_name localhost;</span><br><span class=\"line\"></span><br><span class=\"line\"> location / {</span><br><span class=\"line\"> root /usr/share/nginx/html;</span><br><span class=\"line\"> index index.html index.htm;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> error_page 404 /404.html;</span><br><span class=\"line\"> location = /40x.html {</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> error_page 500 502 503 504 /50x.html;</span><br><span class=\"line\"> location = /50x.html {</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>默认变量 roles/nginx/defaults/main.yml:</p>\n<figure class=\"highlight yaml\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"attr\">nginx_port:</span> <span class=\"number\">80</span></span><br></pre></td></tr></table></figure>\n\n<p>当我们更新安装时,可以通过(在正式执行前,可以在后面加-C -D 做测试使用):</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible-playbook playbook.yml </span><br></pre></td></tr></table></figure>\n<p>后续有二进制更新时,可以通过:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible-playbook playbook.yml -t bin</span><br></pre></td></tr></table></figure>\n<p>后续有配置文件更新时,可以通过:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible-playbook playbook.yml -t conf</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"逻辑控制语句\"><a href=\"#逻辑控制语句\" class=\"headerlink\" title=\"逻辑控制语句\"></a>逻辑控制语句</h2><h3 id=\"条件语句when\"><a href=\"#条件语句when\" class=\"headerlink\" title=\"条件语句when\"></a>条件语句when</h3><p>when条件支持多种判断类型,主要用于根据某些条件决定是否执行某个任务。这些判断类型通常基于Python的语法,因为Ansible的任务是用Python编写的</p>\n<p>主要支持的判断类型:</p>\n<ul>\n<li>比较运算符:==, !=, >, <, >=, <= 用于比较两个值。</li>\n<li>字符串方法:.startswith(), .endswith(), .find(), .contains() 等字符串方法可以用来检查字符串的特性。</li>\n<li>逻辑运算符:and, or, not 用于组合多个条件。</li>\n<li>Jinja2模板表达式:由于Ansible使用Jinja2作为模板引擎,因此你也可以在when条件中使用Jinja2的表达式和过滤器。</li>\n<li>Ansible事实(facts)和变量:你可以使用Ansible收集的主机事实(facts)和定义的变量来进行条件判断。</li>\n<li>函数和内置方法:Python的内置函数和方法也可以在when条件中使用,比如isinstance(), len(), 等等。</li>\n<li>正则表达式:使用Python的正则表达式模块(如re)进行更复杂的字符串匹配。</li>\n</ul>\n<p>其中facts涉及到的判断条件非常多,可以通过如下形式获取</p>\n<figure class=\"highlight yaml\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">hosts:</span> <span class=\"string\">mysql</span></span><br><span class=\"line\"> <span class=\"attr\">tasks:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">show</span> <span class=\"string\">ansible</span> <span class=\"string\">facts</span></span><br><span class=\"line\"> <span class=\"attr\">debug:</span></span><br><span class=\"line\"> <span class=\"attr\">var:</span> <span class=\"string\">ansible_facts</span></span><br></pre></td></tr></table></figure>\n<p>执行以上yml文件之后,会输出一个json串,我们就可以获取到所有的fact信息了.</p>\n<p>示例:假如我们想在ip为 192.168.0.102 的机器上创建文件</p>\n<figure class=\"highlight yaml\"><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><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">when测试练习</span></span><br><span class=\"line\"> <span class=\"attr\">hosts:</span> <span class=\"string\">webservers</span></span><br><span class=\"line\"> <span class=\"attr\">tasks:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">文件测试创建</span></span><br><span class=\"line\"> <span class=\"attr\">file:</span> </span><br><span class=\"line\"> <span class=\"attr\">path:</span> <span class=\"string\">/tmp/when.txt</span></span><br><span class=\"line\"> <span class=\"attr\">state:</span> <span class=\"string\">touch</span></span><br><span class=\"line\"> <span class=\"attr\">when:</span> <span class=\"string\">"'192.168.0.102' in ansible_all_ipv4_addresses"</span></span><br></pre></td></tr></table></figure>\n\n<h3 id=\"循环语句loop\"><a href=\"#循环语句loop\" class=\"headerlink\" title=\"循环语句loop\"></a>循环语句loop</h3><p>用于在任务中循环执行操作</p>\n<p>示例:</p>\n<figure class=\"highlight yaml\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Install</span> <span class=\"string\">multiple</span> <span class=\"string\">packages</span></span><br><span class=\"line\"> <span class=\"attr\">hosts:</span> <span class=\"string\">webservers</span></span><br><span class=\"line\"> <span class=\"attr\">become:</span> <span class=\"literal\">yes</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"attr\">tasks:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Install</span> <span class=\"string\">packages</span></span><br><span class=\"line\"> <span class=\"attr\">apt:</span></span><br><span class=\"line\"> <span class=\"attr\">name:</span> <span class=\"string\">"<span class=\"template-variable\">{{ item }}</span>"</span></span><br><span class=\"line\"> <span class=\"attr\">state:</span> <span class=\"string\">present</span></span><br><span class=\"line\"> <span class=\"attr\">loop:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">nginx</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">git</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">curl</span></span><br></pre></td></tr></table></figure>\n<p>上面的示例就是循环装包</p>\n<h3 id=\"块语句block\"><a href=\"#块语句block\" class=\"headerlink\" title=\"块语句block\"></a>块语句block</h3><p>在 Ansible 中,block 关键字允许你将多个任务组合成一个逻辑块,并对这个块应用一些条件或错误处理逻辑。这是 Ansible 2.5 版本及以后引入的一个功能,它提供了更高级的任务组织方式。简单来说,block任务块就是一组逻辑的tasks。使用block可以将多个任务合并为一个组。</p>\n<p>playbook会定义三种块,三种块的作用分别如下:</p>\n<ul>\n<li>block: block里的tasks,如果运行正确,则不会运行rescue;</li>\n<li>rescue:block里的tasks,如果运行失败,才会运行rescue里的tasks</li>\n<li>always:block和rescue里的tasks无论是否运行成功,都会运行always里的tasks</li>\n</ul>\n","excerpt":"","more":"<!-- Your content starts here -->\n\n<div class=\"tag-plugin quot\"><p class=\"content\" type=\"text\"><span class=\"empty\"></span><span class=\"text\">官方文档</span><span class=\"empty\"></span></p></div>\n\n<p><a href=\"https://docs.ansible.com/ansible/latest/index.html\">英文文档</a><br><a href=\"http://www.ansible.com.cn/docs/intro_adhoc.html\">中文文档</a></p>\n<h1 id=\"ansible-介绍\"><a href=\"#ansible-介绍\" class=\"headerlink\" title=\"ansible 介绍\"></a>ansible 介绍</h1><p>我们先看一下 ansible 的相关介绍:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">Ansible is a radically simple IT automation platform that makes your applications and systems easier to deploy and maintain. Automate everything from code deployment to network configuration to cloud management, in a language that approaches plain English, using SSH, with no agents to install on remote systems. https://docs.ansible.com.</span><br></pre></td></tr></table></figure>\n\n<p>这里有几点核心:</p>\n<ol>\n<li>ansible 是一个自动化平台</li>\n<li>ansible 使用 ssh 协议(通过密码或者密钥等方式进行访问),部署简单,没有客户端,只需在主控端部署 Ansible 环境,无需在远程系统上安装代理程序</li>\n<li>模块化:调用特定的模块,完成特定任务</li>\n<li>支持自定义扩展</li>\n</ol>\n<p>每开发一个工具或者平台的时候,这些工具和平台提供了各种各样的功能,那么我们能用 ansible 来干什么呢? 下面就是一些 ansible 核心的功能介绍:</p>\n<table>\n<thead>\n<tr>\n<th>功能</th>\n<th>描述</th>\n<th>常见场景示例</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>配置管理</td>\n<td>自动化管理服务器和设备的配置,确保它们处于期望的状态。</td>\n<td>自动化安装和配置 Web 服务器,确保所有服务器配置一致。</td>\n</tr>\n<tr>\n<td>应用部署</td>\n<td>自动化应用程序的部署过程,从代码库拉取到在服务器上部署和配置应用。</td>\n<td>自动部署多层 Web 应用程序,包括数据库设置、应用服务部署、负载均衡配置等。</td>\n</tr>\n<tr>\n<td>持续交付与持续集成(CI/CD)</td>\n<td>与 CI/CD 工具集成,实现自动化的构建、测试和部署流程。</td>\n<td>代码提交后自动执行测试、构建容器镜像,并部署到 Kubernetes 集群中。</td>\n</tr>\n<tr>\n<td>基础设施即代码(IaC)</td>\n<td>编写和管理基础设施的配置文件,使其像管理代码一样。</td>\n<td>使用 Ansible Playbooks 定义云环境资源配置,实现可重复的基础设施部署。</td>\n</tr>\n<tr>\n<td>云管理</td>\n<td>自动化云服务资源的管理,包括虚拟机、存储、网络等的创建和配置。</td>\n<td>自动化创建和管理 AWS EC2 实例、配置 VPC 和安全组。</td>\n</tr>\n<tr>\n<td>网络自动化</td>\n<td>管理和配置网络设备,使得大规模的网络设备配置变得简单和一致。</td>\n<td>自动化配置多台交换机的 VLAN 设置和路由协议。</td>\n</tr>\n<tr>\n<td>安全与合规</td>\n<td>自动化安全补丁的部署、系统安全配置的强化以及合规性检查。</td>\n<td>自动化应用系统安全补丁,配置防火墙规则,执行安全扫描和合规性检查。</td>\n</tr>\n<tr>\n<td>多平台环境管理</td>\n<td>支持多种操作系统,在混合环境中统一管理平台上的配置和应用。</td>\n<td>在混合的 Linux 和 Windows 服务器环境中,统一部署和配置监控软件。</td>\n</tr>\n<tr>\n<td>灾难恢复</td>\n<td>自动化灾难恢复流程,如备份、数据恢复和服务恢复。</td>\n<td>自动化数据库备份并在需要时恢复和重建数据库服务。</td>\n</tr>\n<tr>\n<td>任务调度与批量操作</td>\n<td>通过 Playbooks 调度定期任务或对大量服务器执行批量操作。</td>\n<td>定期清理服务器上的临时文件或批量更新多个服务器的操作系统。</td>\n</tr>\n</tbody></table>\n<h2 id=\"ansible的工作机制\"><a href=\"#ansible的工作机制\" class=\"headerlink\" title=\"ansible的工作机制\"></a>ansible的工作机制</h2><p>Ansible 在管理节点将 Ansible 模块通过 SSH 协议推送到被管理端执行,执行完之后自动删除,可以使用 SVN 等来管理自定义模块及编排</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/ansible-diagram.png\" alt=\"ansible结构\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">ansible结构</span></div></div>\n\n<p>从这张图中我们可以看到,ansible 由以下模块组成</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Ansible: ansible的核心模块</span><br><span class=\"line\">Host Inventory:主机清单,也就是被管理的主机列表</span><br><span class=\"line\">Playbooks:ansible的剧本,可想象为将多个任务放置在一起,一块执行</span><br><span class=\"line\">Core Modules:ansible的核心模块</span><br><span class=\"line\">Custom Modules:自定义模块</span><br><span class=\"line\">Connection Plugins:连接插件,用于与被管控主机之间基于SSH建立连接关系</span><br><span class=\"line\">Plugins:其他插件,包括记录日志等</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"ansible-安装\"><a href=\"#ansible-安装\" class=\"headerlink\" title=\"ansible 安装\"></a>ansible 安装</h2><p>在 centos 环境下,我们可以通过:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">yum install ansible -y 进行安装</span><br></pre></td></tr></table></figure>\n<p>安装完成后我们看下 ansible 提供了哪些命令:</p>\n<table>\n<thead>\n<tr>\n<th>命令</th>\n<th>描述</th>\n<th>示例</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>ansible</code></td>\n<td>用于在一个或多个主机上运行单个模块(通常用于临时命令)。</td>\n<td><code>ansible all -m ping</code></td>\n</tr>\n<tr>\n<td><code>ansible-playbook</code></td>\n<td>用于运行 Ansible playbook 文件,是 Ansible 的核心命令之一。</td>\n<td><code>ansible-playbook site.yml</code></td>\n</tr>\n<tr>\n<td><code>ansible-galaxy</code></td>\n<td>用于管理 Ansible 角色和集合。可以下载、创建和分享角色。</td>\n<td><code>ansible-galaxy install geerlingguy.apache</code></td>\n</tr>\n<tr>\n<td><code>ansible-vault</code></td>\n<td>用于加密和解密敏感数据(如密码、密钥)。</td>\n<td><code>ansible-vault encrypt secrets.yml</code></td>\n</tr>\n<tr>\n<td><code>ansible-doc</code></td>\n<td>显示 Ansible 模块的文档和示例用法。</td>\n<td><code>ansible-doc -l</code></td>\n</tr>\n<tr>\n<td><code>ansible-config</code></td>\n<td>用于查看、验证和管理 Ansible 配置文件。</td>\n<td><code>ansible-config list</code></td>\n</tr>\n<tr>\n<td><code>ansible-inventory</code></td>\n<td>管理和检索 Ansible inventory 信息。</td>\n<td><code>ansible-inventory --list -i inventory.yml</code></td>\n</tr>\n<tr>\n<td><code>ansible-pull</code></td>\n<td>用于从远程版本控制系统(如 Git)拉取 playbook 并在本地执行,常用于自动化部署。</td>\n<td><code>ansible-pull -U https://github.com/username/repo.git</code></td>\n</tr>\n<tr>\n<td><code>ansible-console</code></td>\n<td>提供一个交互式命令行接口,可用于动态执行 Ansible 任务和命令。</td>\n<td><code>ansible-console</code></td>\n</tr>\n</tbody></table>\n<p>对两个比较常用的命令:<code>ansible</code> 和 <code>ansible-plabook</code> 做一下具体的介绍:</p>\n<h3 id=\"ansible-命令\"><a href=\"#ansible-命令\" class=\"headerlink\" title=\"ansible 命令\"></a>ansible 命令</h3><p>命令格式:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible 组名 -m 模块名 -a <span class=\"string\">'参数'</span></span><br></pre></td></tr></table></figure>\n<p>这里的组名是自定义的一系列组信息,组的定义在后面会讲到。</p>\n<p>模块名是ansible提供的一些列支持模块,默认模块是 command,查看 ansible 支持的模块:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible-doc -l <span class=\"comment\">#大概有 3000 多个</span></span><br></pre></td></tr></table></figure>\n<p>ansible涉及到的模块非常非常多,按照实际需要使用,初步先掌握一些比较常用的就可以</p>\n<p>查看模块描述:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible-doc -s 模块名称</span><br></pre></td></tr></table></figure>\n<p>实例:</p>\n<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># 查看webserver 组机器的时间信息</span></span><br><span class=\"line\">ansible webserver -m shell -a <span class=\"string\">"date"</span> <span class=\"comment\"># 这里的组就是 webserver,shell 是模块名(比较常用的模块)date 是具体执行的命令</span></span><br><span class=\"line\"><span class=\"comment\"># 将本机的/tmp/test.sh 拷贝到其他机器上的 /etc目录下</span></span><br><span class=\"line\">ansible webserver -m copy -a <span class=\"string\">"src=/tmp/test.sh dest=/etc/test.sh"</span> <span class=\"comment\"># 这里的 copy 是模块名,里面的 src 是源路径,dest 是目标路径</span></span><br></pre></td></tr></table></figure>\n\n<h3 id=\"ansible-playbook命令\"><a href=\"#ansible-playbook命令\" class=\"headerlink\" title=\"ansible-playbook命令\"></a>ansible-playbook命令</h3><p>用于执行 Ansible Playbooks。Playbooks 是一系列任务的集合,用于自动化配置管理、应用部署、任务执行等操作。<br>基本语法:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible-playbook [options] playbook.yml</span><br></pre></td></tr></table></figure>\n<p>命令帮助:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">positional arguments:</span><br><span class=\"line\"> playbook Playbook(s)</span><br><span class=\"line\"></span><br><span class=\"line\">optional arguments:</span><br><span class=\"line\"> --ask-vault-pass ask for vault password</span><br><span class=\"line\"> --flush-cache clear the fact cache for every host in inventory</span><br><span class=\"line\"> --force-handlers run handlers even if a task fails</span><br><span class=\"line\"> --list-hosts outputs a list of matching hosts; does not execute</span><br><span class=\"line\"> anything else</span><br><span class=\"line\"> --list-tags list all available tags</span><br><span class=\"line\"> --list-tasks list all tasks that would be executed</span><br><span class=\"line\"> --skip-tags SKIP_TAGS</span><br><span class=\"line\"> only run plays and tasks whose tags do not match these</span><br><span class=\"line\"> values</span><br><span class=\"line\"> --start-at-task START_AT_TASK</span><br><span class=\"line\"> start the playbook at the task matching this name</span><br><span class=\"line\"> --step one-step-at-a-time: confirm each task before running</span><br><span class=\"line\"> --syntax-check perform a syntax check on the playbook, but do not</span><br><span class=\"line\"> execute it</span><br><span class=\"line\"> --vault-id VAULT_IDS the vault identity to use</span><br><span class=\"line\"> --vault-password-file VAULT_PASSWORD_FILES</span><br><span class=\"line\"> vault password file</span><br><span class=\"line\"> --version show program's version number, config file location,</span><br><span class=\"line\"> configured module search path, module location,</span><br><span class=\"line\"> executable location and exit</span><br><span class=\"line\"> -C, --check don't make any changes; instead, try to predict some</span><br><span class=\"line\"> of the changes that may occur</span><br><span class=\"line\"> -D, --diff when changing (small) files and templates, show the</span><br><span class=\"line\"> differences in those files; works great with --check</span><br><span class=\"line\"> -M MODULE_PATH, --module-path MODULE_PATH</span><br><span class=\"line\"> prepend colon-separated path(s) to module library (def</span><br><span class=\"line\"> ault=~/.ansible/plugins/modules:/usr/share/ansible/plu</span><br><span class=\"line\"> gins/modules)</span><br><span class=\"line\"> -e EXTRA_VARS, --extra-vars EXTRA_VARS</span><br><span class=\"line\"> set additional variables as key=value or YAML/JSON, if</span><br><span class=\"line\"> filename prepend with @</span><br><span class=\"line\"> -f FORKS, --forks FORKS</span><br><span class=\"line\"> specify number of parallel processes to use</span><br><span class=\"line\"> (default=64)</span><br><span class=\"line\"> -h, --help show this help message and exit</span><br><span class=\"line\"> -i INVENTORY, --inventory INVENTORY, --inventory-file INVENTORY</span><br><span class=\"line\"> specify inventory host path or comma separated host</span><br><span class=\"line\"> list. --inventory-file is deprecated</span><br><span class=\"line\"> -l SUBSET, --limit SUBSET</span><br><span class=\"line\"> further limit selected hosts to an additional pattern</span><br><span class=\"line\"> -t TAGS, --tags TAGS only run plays and tasks tagged with these values</span><br><span class=\"line\"> -v, --verbose verbose mode (-vvv for more, -vvvv to enable</span><br><span class=\"line\"> connection debugging)</span><br><span class=\"line\"></span><br><span class=\"line\">Connection Options:</span><br><span class=\"line\"> control as whom and how to connect to hosts</span><br><span class=\"line\"></span><br><span class=\"line\"> --private-key PRIVATE_KEY_FILE, --key-file PRIVATE_KEY_FILE</span><br><span class=\"line\"> use this file to authenticate the connection</span><br><span class=\"line\"> --scp-extra-args SCP_EXTRA_ARGS</span><br><span class=\"line\"> specify extra arguments to pass to scp only (e.g. -l)</span><br><span class=\"line\"> --sftp-extra-args SFTP_EXTRA_ARGS</span><br><span class=\"line\"> specify extra arguments to pass to sftp only (e.g. -f,</span><br><span class=\"line\"> -l)</span><br><span class=\"line\"> --ssh-common-args SSH_COMMON_ARGS</span><br><span class=\"line\"> specify common arguments to pass to sftp/scp/ssh (e.g.</span><br><span class=\"line\"> ProxyCommand)</span><br><span class=\"line\"> --ssh-extra-args SSH_EXTRA_ARGS</span><br><span class=\"line\"> specify extra arguments to pass to ssh only (e.g. -R)</span><br><span class=\"line\"> -T TIMEOUT, --timeout TIMEOUT</span><br><span class=\"line\"> override the connection timeout in seconds</span><br><span class=\"line\"> (default=10)</span><br><span class=\"line\"> -c CONNECTION, --connection CONNECTION</span><br><span class=\"line\"> connection type to use (default=smart)</span><br><span class=\"line\"> -k, --ask-pass ask for connection password</span><br><span class=\"line\"> -u REMOTE_USER, --user REMOTE_USER</span><br><span class=\"line\"> connect as this user (default=None)</span><br><span class=\"line\"></span><br><span class=\"line\">Privilege Escalation Options:</span><br><span class=\"line\"> control how and which user you become as on target hosts</span><br><span class=\"line\"></span><br><span class=\"line\"> --become-method BECOME_METHOD</span><br><span class=\"line\"> privilege escalation method to use (default=sudo), use</span><br><span class=\"line\"> `ansible-doc -t become -l` to list valid choices.</span><br><span class=\"line\"> --become-user BECOME_USER</span><br><span class=\"line\"> run operations as this user (default=root)</span><br><span class=\"line\"> -K, --ask-become-pass</span><br><span class=\"line\"> ask for privilege escalation password</span><br><span class=\"line\"> -b, --become run operations with become (does not imply password</span><br><span class=\"line\"> prompting)</span><br></pre></td></tr></table></figure>\n\n<h1 id=\"ansible-基本使用\"><a href=\"#ansible-基本使用\" class=\"headerlink\" title=\"ansible 基本使用\"></a>ansible 基本使用</h1><p>我们先看一个最基本的 ansible 组成:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"https://docs.ansible.com/ansible/latest/_images/ansible_inv_start.svg\" alt=\"ansible基本组成\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">ansible基本组成</span></div></div>\n\n<p>如上图所示,大部分的 ansible 环境都包含以下三个组件:</p>\n<ol>\n<li>控制节点:安装了Ansible的系统。可以在控制节点上运行Ansible命令,例如ansible或ansible-inventory。</li>\n<li>Inventory: 按逻辑组织的托管节点的列表。可以在控制节点上创建一个清单,以向Ansible描述主机部署。</li>\n<li>被管理节点: Ansible控制的远程系统或主机。</li>\n</ol>\n<h2 id=\"Inventory文件\"><a href=\"#Inventory文件\" class=\"headerlink\" title=\"Inventory文件\"></a>Inventory文件</h2><p>inventory 主要包括主机和组两个概念, 默认文件 <code>/etc/ansible/hosts</code></p>\n<h3 id=\"主机\"><a href=\"#主机\" class=\"headerlink\" title=\"主机\"></a>主机</h3><p>主机是 Ansible 可以管理的单个设备或虚拟机。主机可以是物理服务器、虚拟机、容器,甚至是网络设备(如路由器和交换机)。每个主机都有一个唯一的标识(通常是主机名或 IP 地址),并且可以通过 Ansible 的 inventory 文件或其他动态方法来定义</p>\n<figure class=\"highlight plaintext\"><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><td class=\"code\"><pre><span class=\"line\"># 定义单个主机</span><br><span class=\"line\">web1.example.com</span><br><span class=\"line\"></span><br><span class=\"line\"># 定义多个主机</span><br><span class=\"line\">web2.example.com</span><br><span class=\"line\">192.168.1.10</span><br><span class=\"line\"></span><br><span class=\"line\"># 主机变量</span><br><span class=\"line\">[atlanta]</span><br><span class=\"line\">host1 http_port=80 maxRequestsPerChild=808</span><br><span class=\"line\">host2 http_port=303 maxRequestsPerChild=909</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"组\"><a href=\"#组\" class=\"headerlink\" title=\"组\"></a>组</h3><p>组是主机的集合,可以对一组主机应用相同的配置或操作。组允许用户在多个主机上批量执行任务。一个主机可以属于多个组。组之间还可以嵌套,例如,你可以将所有 Web 服务器放在一个组中,然后将该组嵌套在一个更大的生产环境组中</p>\n<figure class=\"highlight ini\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># 定义一下所有节点的信息</span></span><br><span class=\"line\"><span class=\"section\">[all_hosts]</span></span><br><span class=\"line\">node1 <span class=\"attr\">public_ip</span>=<span class=\"number\">192.168</span>.<span class=\"number\">1.101</span> ansible_host=<span class=\"number\">192.168</span>.<span class=\"number\">1.101</span> ansible_user=your_user ansible_ssh_pass=your_password ansible_port=<span class=\"number\">22</span></span><br><span class=\"line\">node2 <span class=\"attr\">public_ip</span>=<span class=\"number\">192.168</span>.<span class=\"number\">1.102</span> ansible_host=<span class=\"number\">192.168</span>.<span class=\"number\">1.102</span> ansible_user=your_user ansible_ssh_pass=your_password ansible_port=<span class=\"number\">22</span></span><br><span class=\"line\">node3 <span class=\"attr\">public_ip</span>=<span class=\"number\">192.168</span>.<span class=\"number\">1.103</span> ansible_host=<span class=\"number\">192.168</span>.<span class=\"number\">1.103</span> ansible_user=your_user ansible_ssh_pass=your_password ansible_port=<span class=\"number\">22</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 定义一个 mysql 组,假如要在 node1 和 node2上部署 mysql</span></span><br><span class=\"line\"><span class=\"section\">[mysql]</span></span><br><span class=\"line\">node1</span><br><span class=\"line\">node2</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 定义一个 nginx 组,假如要在 node1 和 node3上部署 nginx</span></span><br><span class=\"line\"><span class=\"section\">[nginx]</span></span><br><span class=\"line\">node1</span><br><span class=\"line\">node3</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 假如我们还想部署一个 redis 组,mysql 部署在那里,redis 就部署在那里,重新写一遍很麻烦,那么我们可以把 redis 当做 mysql 的子集</span></span><br><span class=\"line\"><span class=\"comment\"># 这里有一个疑问,我们既然用一个一模一样的组再了,为啥搞个 children,直接用原来的组不行吗,这里可以是可以,但是从模块划分来看,做一下区分易于后续的管理</span></span><br><span class=\"line\"><span class=\"section\">[redis:children]</span></span><br><span class=\"line\">mysql</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 定义组变量</span></span><br><span class=\"line\"><span class=\"section\">[atlanta:vars]</span></span><br><span class=\"line\"><span class=\"attr\">ntp_server</span>=ntp.atlanta.example.com</span><br><span class=\"line\"><span class=\"attr\">proxy</span>=proxy.atlanta.example.com</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"简单使用\"><a href=\"#简单使用\" class=\"headerlink\" title=\"简单使用\"></a>简单使用</h2><p>在我们了解完 inventory 之后,我们开始做一些简单的模拟:</p>\n<ol>\n<li>在 node1 上安装 ansible,作为控制节点,在/etc/ansbile/hosts中加入三个节点的信息</li>\n<li>如果是通过密码连接的话,需要在 ansible_ssh_pass中输入机器密码,如果是通过密钥链接,这里可不填;配置 ssh 免密登录,可以自行百度,这里只需要配置 node1 到所有节点的免密(包括 node1 到 node1 自己)</li>\n<li>免密配置完成后,我们可以做一下简单的操作:<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># 查看 mysql 组机器的时间信息</span></span><br><span class=\"line\">ansilbe mysql -m shell -a <span class=\"string\">"date"</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 查看 nginx 组机器的启动时间</span></span><br><span class=\"line\">ansible nginx -m shell -a <span class=\"string\">"uptime"</span></span><br></pre></td></tr></table></figure></li>\n</ol>\n<h1 id=\"playbooks\"><a href=\"#playbooks\" class=\"headerlink\" title=\"playbooks\"></a>playbooks</h1><p>playbook是由一个或多个play组成的列表,play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色。从根本上来讲,所谓的task无非是调用ansible的一个module。将多个play组织在一个playbook中,即可以让它们联合起来按事先编排的机制完成某一任务</p>\n<h2 id=\"playbook语法\"><a href=\"#playbook语法\" class=\"headerlink\" title=\"playbook语法:\"></a>playbook语法:</h2><figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">playbook使用yaml语法格式,后缀可以是yaml,也可以是yml。</span><br><span class=\"line\"></span><br><span class=\"line\">在单个playbook文件中,可以连续三个连子号(---)区分多个play。还有选择性的连续三个点好(...)用来表示play的结尾,也可省略。</span><br><span class=\"line\"></span><br><span class=\"line\">次行开始正常写playbook的内容,一般都会写上描述该playbook的功能。</span><br><span class=\"line\"></span><br><span class=\"line\">使用#号注释代码。</span><br><span class=\"line\"></span><br><span class=\"line\">缩进必须统一,不能空格和tab混用。</span><br><span class=\"line\"></span><br><span class=\"line\">缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行实现的。</span><br><span class=\"line\"></span><br><span class=\"line\">YAML文件内容和Linux系统大小写判断方式保持一致,是区分大小写的,k/v的值均需大小写敏感</span><br><span class=\"line\"></span><br><span class=\"line\">k/v的值可同行写也可以换行写。同行使用:分隔。</span><br><span class=\"line\"></span><br><span class=\"line\">v可以是个字符串,也可以是一个列表</span><br><span class=\"line\"></span><br><span class=\"line\">一个完整的代码块功能需要最少元素包括 name: task</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"playbook核心元素\"><a href=\"#playbook核心元素\" class=\"headerlink\" title=\"playbook核心元素\"></a>playbook核心元素</h2><h3 id=\"1-Play\"><a href=\"#1-Play\" class=\"headerlink\" title=\"1. Play\"></a>1. Play</h3><p>Play 是 Playbook 的基本单元,用于定义在一组主机上执行的一系列任务。一个 Playbook 可以包含多个 Play,每个 Play 在不同的主机或组上执行不同的任务。</p>\n<p>关键字:</p>\n<ul>\n<li>hosts: 指定要在哪些主机或主机组上执行 Play。</li>\n<li>tasks: 包含一系列任务,这些任务会按顺序执行。</li>\n<li>vars: 定义在 Play 中使用的变量。</li>\n<li>roles: 指定要应用的角色。</li>\n<li>gather_facts: 控制是否收集主机的事实信息(默认 true)。</li>\n<li>become: 是否使用 sudo 或其他特权提升执行任务。</li>\n</ul>\n<h3 id=\"2-Tasks\"><a href=\"#2-Tasks\" class=\"headerlink\" title=\"2. Tasks\"></a>2. Tasks</h3><p>Tasks 是 Play 中的核心部分,定义了要执行的具体操作。每个 Task 通常使用一个 Ansible 模块,并可包含条件、循环、错误处理等。</p>\n<p>关键字:</p>\n<ul>\n<li>name: 任务的描述性名称(可选,但推荐使用)。</li>\n<li> action 或模块名称: 具体执行的操作,如 apt、yum、copy 等。</li>\n<li> when: 定义条件,满足时才会执行任务。</li>\n<li> with_items: 用于循环执行任务。</li>\n<li> register: 保存任务的结果到变量。</li>\n<li> ignore_errors: 忽略任务执行失败(设为 yes 时)。</li>\n</ul>\n<h3 id=\"3-Variables\"><a href=\"#3-Variables\" class=\"headerlink\" title=\"3. Variables\"></a>3. Variables</h3><p>Variables 是 Playbook 中的动态值,用于提高复用性和灵活性。可以在多个地方定义变量,如 vars、group_vars、host_vars、inventory 文件,或通过命令行传递。</p>\n<p>关键字:<br>-\tvars: 在 Play 或 Task 中定义变量。<br>-\tvars_files: 引入外部变量文件。<br>-\tvars_prompt: 运行时提示用户输入变量值。</p>\n<h3 id=\"4-Handlers\"><a href=\"#4-Handlers\" class=\"headerlink\" title=\"4. Handlers\"></a>4. Handlers</h3><p>Handlers 是一种特殊类型的 Task,只会在被触发时执行。通常用于在配置更改后执行动作,如重启服务。比如我要等服务重启完检查端口监听,就可以用handler</p>\n<p>关键字:<br>-\tname: Handler 的名称。<br>-\tnotify: 在普通 Task 中调用 notify 触发对应的 Handler。</p>\n<h3 id=\"5-Roles\"><a href=\"#5-Roles\" class=\"headerlink\" title=\"5. Roles\"></a>5. Roles</h3><p>Roles 是 Playbook 中组织和复用任务、变量、文件、模板等的一种方式。Roles 使得 Playbook 更加模块化和可维护。举个例子,我现在要在服务器上部署各种各样的组件,webserver、mysql、redis、ng 等等,我们就可以用这个不同的 roles 来管理,我们可以在创建四个文件夹,分别对应起名 webserver、mysql、redis、ng,然后在这些文件夹里面添加服务部署或者更新需要的东西。</p>\n<p>角色的目录结构:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">my_role/</span><br><span class=\"line\">├── tasks/</span><br><span class=\"line\">│ └── main.yml</span><br><span class=\"line\">├── handlers/</span><br><span class=\"line\">│ └── main.yml</span><br><span class=\"line\">├── templates/</span><br><span class=\"line\">├── files/</span><br><span class=\"line\">├── vars/</span><br><span class=\"line\">│ └── main.yml</span><br><span class=\"line\">├── defaults/</span><br><span class=\"line\">│ └── main.yml</span><br><span class=\"line\">└── meta/</span><br><span class=\"line\"> └── main.yml</span><br></pre></td></tr></table></figure>\n\n<p>roles内各自目录含义:</p>\n<ul>\n<li>files\t用来存放copy模块或script模块调用的文件</li>\n<li>templates\t用来存放jinjia2模板,template模块会自动在此目录中寻找jinjia2模板文件</li>\n<li>tasks\t此目录应当包含一个main.yml文件,用于定义此角色的任务列表,此文件可以使用include包含其它的位于此目录的task文件</li>\n<li>handlers\t此目录应当包含一个main.yml文件,用于定义此角色中触发条件时执行的动作</li>\n<li>vars\t此目录应当包含一个main.yml文件,用于定义此角色用到的变量</li>\n<li>defailts\t此目录应当包含一个main.yml文件,用于为当前角色设定默认变量</li>\n<li>meta\t此目录应当包含一个main.yml文件,用于定义此角色的特殊设及其依赖关系</li>\n</ul>\n<h3 id=\"6-Includes-and-Imports\"><a href=\"#6-Includes-and-Imports\" class=\"headerlink\" title=\"6. Includes and Imports\"></a>6. Includes and Imports</h3><p>Includes 和 Imports 用于在 Playbook 中包含其他任务、变量、文件等。import_tasks 和 include_tasks 的区别在于,import_tasks 在解析 Playbook 时执行,而 include_tasks 在运行时执行。</p>\n<h3 id=\"7-Templates\"><a href=\"#7-Templates\" class=\"headerlink\" title=\"7. Templates\"></a>7. Templates</h3><p>Templates 是使用 Jinja2 模板引擎的文件,用于动态生成配置文件或其他文件。模板通常存放在 templates/ 目录下,并通过 template 模块应用到目标主机</p>\n<h3 id=\"8-Tags\"><a href=\"#8-Tags\" class=\"headerlink\" title=\"8. Tags\"></a>8. Tags</h3><p>标签是用于对 play 进行标注,当你写了一个很长的playbook,其中有很多的任务,这并没有什么问题,不过在实际使用这个剧本时,你可能只是想要执行其中的一部分任务而已,或者,你只想要执行其中一类任务而已,而并非想要执行整个剧本中的全部任务,这时,我们可以借助tags模块为任务进行打标签操作,任务存在标签后,我们可以在执行playbook时利用标签,指定执行哪些任务,或者不执行哪些任务</p>\n<p>比如说在实际线上环境中,我们有更新二进制包的操作,那么我们可以在更新二进制的相关 task 中添加名为bin 的 tags,有更新配置文件的操作,那么可以在相关的 tasks 中添加 conf 的 tags</p>\n<h3 id=\"完整示例\"><a href=\"#完整示例\" class=\"headerlink\" title=\"完整示例\"></a>完整示例</h3><p>我们通过一个安装 nginx 的操作来完整演示一下:</p>\n<p>设置项目目录结构:</p>\n<figure class=\"highlight plaintext\"><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><td class=\"code\"><pre><span class=\"line\">.</span><br><span class=\"line\">├── ansible.cfg</span><br><span class=\"line\">├── inventory</span><br><span class=\"line\">├── playbook.yml</span><br><span class=\"line\">└── roles</span><br><span class=\"line\"> └── nginx</span><br><span class=\"line\"> ├── tasks</span><br><span class=\"line\"> │ ├── main.yml</span><br><span class=\"line\"> │ └── install.yml</span><br><span class=\"line\"> ├── handlers</span><br><span class=\"line\"> │ └── main.yml</span><br><span class=\"line\"> ├── templates</span><br><span class=\"line\"> │ └── nginx.conf.j2</span><br><span class=\"line\"> ├── files</span><br><span class=\"line\"> ├── vars</span><br><span class=\"line\"> │ └── main.yml</span><br><span class=\"line\"> └── defaults</span><br><span class=\"line\"> └── main.yml</span><br></pre></td></tr></table></figure>\n\n<p>inventory配置文件</p>\n<figure class=\"highlight ini\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"section\">[webservers]</span></span><br><span class=\"line\">192.168.1.101 <span class=\"attr\">ansible_ssh_user</span>=your_user ansible_ssh_pass=your_password ansible_host=<span class=\"number\">203.0</span>.<span class=\"number\">113.1</span></span><br></pre></td></tr></table></figure>\n\n<p>playbook.yml:</p>\n<figure class=\"highlight yaml\"><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><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Update</span> <span class=\"string\">and</span> <span class=\"string\">Install</span> <span class=\"string\">Nginx</span></span><br><span class=\"line\"> <span class=\"attr\">hosts:</span> <span class=\"string\">webservers</span> <span class=\"comment\">#引用组</span></span><br><span class=\"line\"> <span class=\"attr\">become:</span> <span class=\"literal\">yes</span> <span class=\"comment\">#开启 sudo</span></span><br><span class=\"line\"> <span class=\"attr\">roles:</span> </span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">role:</span> <span class=\"string\">nginx</span> <span class=\"comment\">#角色是nginx,对应到 roles/nginx 目录</span></span><br><span class=\"line\"> <span class=\"attr\">tags:</span> <span class=\"comment\">#两个 tags</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">bin</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">conf</span></span><br></pre></td></tr></table></figure>\n\n<p>任务文件 roles/nginx/tasks/main.yml:</p>\n<figure class=\"highlight yaml\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"comment\"># 包含其他任务文件, 这里直接用 install.yml里面的文件内容肯定也是没问题的,但是我们可以通过这样的方式更好的进行管理</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">include_tasks:</span> <span class=\"string\">install.yml</span></span><br></pre></td></tr></table></figure>\n\n<p>安装任务 roles/nginx/tasks/install.yml:</p>\n<figure class=\"highlight yaml\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"comment\"># 更新安装 Nginx</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Update</span> <span class=\"string\">apt</span> <span class=\"string\">cache</span> <span class=\"string\">and</span> <span class=\"string\">install</span> <span class=\"string\">Nginx</span></span><br><span class=\"line\"> <span class=\"attr\">apt:</span> <span class=\"comment\">#安装nginx 相关包, 不同平台不太一样,比如 centos 可以使用 package</span></span><br><span class=\"line\"> <span class=\"attr\">name:</span> <span class=\"string\">nginx</span></span><br><span class=\"line\"> <span class=\"attr\">state:</span> <span class=\"string\">latest</span></span><br><span class=\"line\"> <span class=\"attr\">update_cache:</span> <span class=\"literal\">yes</span></span><br><span class=\"line\"> <span class=\"attr\">tags:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">bin</span> <span class=\"comment\">#这里添加了 bin 的 tag</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 部署 Nginx 配置文件</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Deploy</span> <span class=\"string\">Nginx</span> <span class=\"string\">configuration</span> <span class=\"string\">from</span> <span class=\"string\">template</span></span><br><span class=\"line\"> <span class=\"attr\">template:</span> <span class=\"comment\">#这里是更新 nginx 配置文件</span></span><br><span class=\"line\"> <span class=\"attr\">src:</span> <span class=\"string\">nginx.conf.j2</span></span><br><span class=\"line\"> <span class=\"attr\">dest:</span> <span class=\"string\">/etc/nginx/nginx.conf</span></span><br><span class=\"line\"> <span class=\"attr\">mode:</span> <span class=\"string\">'0644'</span></span><br><span class=\"line\"> <span class=\"attr\">notify:</span> <span class=\"string\">Restart</span> <span class=\"string\">Nginx</span> <span class=\"comment\">#这里配合handler 使用,handlers 里面会有一个名称为 “Restart Nginx”的操作</span></span><br><span class=\"line\"> <span class=\"attr\">tags:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">conf</span> <span class=\"comment\">#这里添加了 conf 的 tag</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 检查 Nginx 是否监听正确端口</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Check</span> <span class=\"string\">Nginx</span> <span class=\"string\">is</span> <span class=\"string\">listening</span> <span class=\"string\">on</span> <span class=\"string\">port</span> <span class=\"number\">80</span></span><br><span class=\"line\"> <span class=\"attr\">command:</span> <span class=\"string\">ss</span> <span class=\"string\">-tuln</span> <span class=\"string\">|</span> <span class=\"string\">grep</span> <span class=\"string\">:80</span> <span class=\"comment\">#通过 command 模块,ss 命令监听 80 端口是否启动</span></span><br><span class=\"line\"> <span class=\"attr\">register:</span> <span class=\"string\">result</span></span><br><span class=\"line\"> <span class=\"attr\">ignore_errors:</span> <span class=\"literal\">yes</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Print</span> <span class=\"string\">Nginx</span> <span class=\"string\">listening</span> <span class=\"string\">port</span> <span class=\"string\">check</span> <span class=\"string\">result</span></span><br><span class=\"line\"> <span class=\"attr\">debug:</span></span><br><span class=\"line\"> <span class=\"attr\">msg:</span> <span class=\"string\">"<span class=\"template-variable\">{{ result.stdout }}</span>"</span></span><br><span class=\"line\"> <span class=\"attr\">when:</span> <span class=\"string\">result.rc</span> <span class=\"string\">==</span> <span class=\"number\">0</span></span><br></pre></td></tr></table></figure>\n\n<p>处理程序 roles/nginx/handlers/main.yml:</p>\n<figure class=\"highlight yaml\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"comment\"># 当配置文件变更时,重启 Nginx, 和上面的 notify 是对应的</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Restart</span> <span class=\"string\">Nginx</span></span><br><span class=\"line\"> <span class=\"attr\">service:</span></span><br><span class=\"line\"> <span class=\"attr\">name:</span> <span class=\"string\">nginx</span></span><br><span class=\"line\"> <span class=\"attr\">state:</span> <span class=\"string\">restarted</span></span><br></pre></td></tr></table></figure>\n\n<p>模板文件 roles/nginx/templates/nginx.conf.j2:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">server {</span><br><span class=\"line\"> listen {{ nginx_port }};</span><br><span class=\"line\"> server_name localhost;</span><br><span class=\"line\"></span><br><span class=\"line\"> location / {</span><br><span class=\"line\"> root /usr/share/nginx/html;</span><br><span class=\"line\"> index index.html index.htm;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> error_page 404 /404.html;</span><br><span class=\"line\"> location = /40x.html {</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> error_page 500 502 503 504 /50x.html;</span><br><span class=\"line\"> location = /50x.html {</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>默认变量 roles/nginx/defaults/main.yml:</p>\n<figure class=\"highlight yaml\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"attr\">nginx_port:</span> <span class=\"number\">80</span></span><br></pre></td></tr></table></figure>\n\n<p>当我们更新安装时,可以通过(在正式执行前,可以在后面加-C -D 做测试使用):</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible-playbook playbook.yml </span><br></pre></td></tr></table></figure>\n<p>后续有二进制更新时,可以通过:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible-playbook playbook.yml -t bin</span><br></pre></td></tr></table></figure>\n<p>后续有配置文件更新时,可以通过:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ansible-playbook playbook.yml -t conf</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"逻辑控制语句\"><a href=\"#逻辑控制语句\" class=\"headerlink\" title=\"逻辑控制语句\"></a>逻辑控制语句</h2><h3 id=\"条件语句when\"><a href=\"#条件语句when\" class=\"headerlink\" title=\"条件语句when\"></a>条件语句when</h3><p>when条件支持多种判断类型,主要用于根据某些条件决定是否执行某个任务。这些判断类型通常基于Python的语法,因为Ansible的任务是用Python编写的</p>\n<p>主要支持的判断类型:</p>\n<ul>\n<li>比较运算符:==, !=, >, <, >=, <= 用于比较两个值。</li>\n<li>字符串方法:.startswith(), .endswith(), .find(), .contains() 等字符串方法可以用来检查字符串的特性。</li>\n<li>逻辑运算符:and, or, not 用于组合多个条件。</li>\n<li>Jinja2模板表达式:由于Ansible使用Jinja2作为模板引擎,因此你也可以在when条件中使用Jinja2的表达式和过滤器。</li>\n<li>Ansible事实(facts)和变量:你可以使用Ansible收集的主机事实(facts)和定义的变量来进行条件判断。</li>\n<li>函数和内置方法:Python的内置函数和方法也可以在when条件中使用,比如isinstance(), len(), 等等。</li>\n<li>正则表达式:使用Python的正则表达式模块(如re)进行更复杂的字符串匹配。</li>\n</ul>\n<p>其中facts涉及到的判断条件非常多,可以通过如下形式获取</p>\n<figure class=\"highlight yaml\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">hosts:</span> <span class=\"string\">mysql</span></span><br><span class=\"line\"> <span class=\"attr\">tasks:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">show</span> <span class=\"string\">ansible</span> <span class=\"string\">facts</span></span><br><span class=\"line\"> <span class=\"attr\">debug:</span></span><br><span class=\"line\"> <span class=\"attr\">var:</span> <span class=\"string\">ansible_facts</span></span><br></pre></td></tr></table></figure>\n<p>执行以上yml文件之后,会输出一个json串,我们就可以获取到所有的fact信息了.</p>\n<p>示例:假如我们想在ip为 192.168.0.102 的机器上创建文件</p>\n<figure class=\"highlight yaml\"><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><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">when测试练习</span></span><br><span class=\"line\"> <span class=\"attr\">hosts:</span> <span class=\"string\">webservers</span></span><br><span class=\"line\"> <span class=\"attr\">tasks:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">文件测试创建</span></span><br><span class=\"line\"> <span class=\"attr\">file:</span> </span><br><span class=\"line\"> <span class=\"attr\">path:</span> <span class=\"string\">/tmp/when.txt</span></span><br><span class=\"line\"> <span class=\"attr\">state:</span> <span class=\"string\">touch</span></span><br><span class=\"line\"> <span class=\"attr\">when:</span> <span class=\"string\">"'192.168.0.102' in ansible_all_ipv4_addresses"</span></span><br></pre></td></tr></table></figure>\n\n<h3 id=\"循环语句loop\"><a href=\"#循环语句loop\" class=\"headerlink\" title=\"循环语句loop\"></a>循环语句loop</h3><p>用于在任务中循环执行操作</p>\n<p>示例:</p>\n<figure class=\"highlight yaml\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">---</span></span><br><span class=\"line\"><span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Install</span> <span class=\"string\">multiple</span> <span class=\"string\">packages</span></span><br><span class=\"line\"> <span class=\"attr\">hosts:</span> <span class=\"string\">webservers</span></span><br><span class=\"line\"> <span class=\"attr\">become:</span> <span class=\"literal\">yes</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"attr\">tasks:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">name:</span> <span class=\"string\">Install</span> <span class=\"string\">packages</span></span><br><span class=\"line\"> <span class=\"attr\">apt:</span></span><br><span class=\"line\"> <span class=\"attr\">name:</span> <span class=\"string\">"<span class=\"template-variable\">{{ item }}</span>"</span></span><br><span class=\"line\"> <span class=\"attr\">state:</span> <span class=\"string\">present</span></span><br><span class=\"line\"> <span class=\"attr\">loop:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">nginx</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">git</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">curl</span></span><br></pre></td></tr></table></figure>\n<p>上面的示例就是循环装包</p>\n<h3 id=\"块语句block\"><a href=\"#块语句block\" class=\"headerlink\" title=\"块语句block\"></a>块语句block</h3><p>在 Ansible 中,block 关键字允许你将多个任务组合成一个逻辑块,并对这个块应用一些条件或错误处理逻辑。这是 Ansible 2.5 版本及以后引入的一个功能,它提供了更高级的任务组织方式。简单来说,block任务块就是一组逻辑的tasks。使用block可以将多个任务合并为一个组。</p>\n<p>playbook会定义三种块,三种块的作用分别如下:</p>\n<ul>\n<li>block: block里的tasks,如果运行正确,则不会运行rescue;</li>\n<li>rescue:block里的tasks,如果运行失败,才会运行rescue里的tasks</li>\n<li>always:block和rescue里的tasks无论是否运行成功,都会运行always里的tasks</li>\n</ul>\n"},{"title":"工作随记","author":"baixiaozhou","description":"记录工作中的一些日常操作","repo":"baixiaozhou/SysStresss","date":"2024-08-21T10:28:27.000Z","references":null,"cover":"/images/suiji.jpeg","banner":"/images/suiji.jpeg","_content":"\n<!-- Your content starts here -->\n\n## Centos 安装 EBPF\n\n安装必要工具和依赖:\n``` bash\nsudo yum install python3 python3-pip python3-devel gcc gcc-c++ make bcc bcc-tools bcc-devel\n```\n\n安装 BCC Python 模块\n``` bash\npip3 install bcc\n```\n\n离线安装的方式如下:\n\n1. 下载 bcc 和 Python 模块源包\n``` bash\n# 下载 bcc 的源码包\nwget https://github.com/iovisor/bcc/archive/refs/tags/v0.22.0.tar.gz -O bcc.tar.gz\n\n# 下载 bcc 的 Python 模块源代码包\n# 可以去这里 https://pypi.org/project/bcc/#files 进行查找\nwget https://files.pythonhosted.org/packages/38/dc/3ca34874926789f8df53f3c1d1c38e77ebf876f43760e8745316bb8bd1c0/bcc-0.1.10.tar.gz\n```\n2. 上传文件到离线环境上,解压并进行安装:\n``` bash\ntar -xzf bcc.tar.gz\ncd bcc-0.22.0 # 目录名可能会有所不同\n\n# 安装系统依赖(可能需要根权限)\nsudo yum install -y gcc gcc-c++ make bpfcc-tools # CentOS/RHEL\nsudo apt-get install -y gcc g++ make bpfcc-tools # Ubuntu/Debian\n\n# 编译和安装 bcc\nmkdir build\ncd build\ncmake ..\nmake\nsudo make install\n```\n3. 安装 bcc Python 模块\n``` bash\n# 解压下载的 Python 模块源代码包\ntar -xzf bcc-python.tar.gz\ncd bcc-python # 目录名可能会有所不同\n\n# 安装 Python 模块\npip3 install .\n```","source":"_posts/工作随记.md","raw":"---\ntitle: 工作随记\nauthor: baixiaozhou\ncategories:\n - 工作随记\ntags:\n - Linux\n - ebpf\ndescription: 记录工作中的一些日常操作\nrepo: baixiaozhou/SysStresss\ndate: 2024-08-21 18:28:27\nreferences:\ncover: /images/suiji.jpeg\nbanner: /images/suiji.jpeg\n---\n\n<!-- Your content starts here -->\n\n## Centos 安装 EBPF\n\n安装必要工具和依赖:\n``` bash\nsudo yum install python3 python3-pip python3-devel gcc gcc-c++ make bcc bcc-tools bcc-devel\n```\n\n安装 BCC Python 模块\n``` bash\npip3 install bcc\n```\n\n离线安装的方式如下:\n\n1. 下载 bcc 和 Python 模块源包\n``` bash\n# 下载 bcc 的源码包\nwget https://github.com/iovisor/bcc/archive/refs/tags/v0.22.0.tar.gz -O bcc.tar.gz\n\n# 下载 bcc 的 Python 模块源代码包\n# 可以去这里 https://pypi.org/project/bcc/#files 进行查找\nwget https://files.pythonhosted.org/packages/38/dc/3ca34874926789f8df53f3c1d1c38e77ebf876f43760e8745316bb8bd1c0/bcc-0.1.10.tar.gz\n```\n2. 上传文件到离线环境上,解压并进行安装:\n``` bash\ntar -xzf bcc.tar.gz\ncd bcc-0.22.0 # 目录名可能会有所不同\n\n# 安装系统依赖(可能需要根权限)\nsudo yum install -y gcc gcc-c++ make bpfcc-tools # CentOS/RHEL\nsudo apt-get install -y gcc g++ make bpfcc-tools # Ubuntu/Debian\n\n# 编译和安装 bcc\nmkdir build\ncd build\ncmake ..\nmake\nsudo make install\n```\n3. 安装 bcc Python 模块\n``` bash\n# 解压下载的 Python 模块源代码包\ntar -xzf bcc-python.tar.gz\ncd bcc-python # 目录名可能会有所不同\n\n# 安装 Python 模块\npip3 install .\n```","slug":"工作随记","published":1,"updated":"2024-09-09T06:30:46.094Z","_id":"cm03rbqwp000089on4momc5ee","comments":1,"layout":"post","photos":[],"content":"<!-- Your content starts here -->\n\n<h2 id=\"Centos-安装-EBPF\"><a href=\"#Centos-安装-EBPF\" class=\"headerlink\" title=\"Centos 安装 EBPF\"></a>Centos 安装 EBPF</h2><p>安装必要工具和依赖:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"built_in\">sudo</span> yum install python3 python3-pip python3-devel gcc gcc-c++ make bcc bcc-tools bcc-devel</span><br></pre></td></tr></table></figure>\n\n<p>安装 BCC Python 模块</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pip3 install bcc</span><br></pre></td></tr></table></figure>\n\n<p>离线安装的方式如下:</p>\n<ol>\n<li>下载 bcc 和 Python 模块源包<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># 下载 bcc 的源码包</span></span><br><span class=\"line\">wget https://github.com/iovisor/bcc/archive/refs/tags/v0.22.0.tar.gz -O bcc.tar.gz</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 下载 bcc 的 Python 模块源代码包</span></span><br><span class=\"line\"><span class=\"comment\"># 可以去这里 https://pypi.org/project/bcc/#files 进行查找</span></span><br><span class=\"line\">wget https://files.pythonhosted.org/packages/38/dc/3ca34874926789f8df53f3c1d1c38e77ebf876f43760e8745316bb8bd1c0/bcc-0.1.10.tar.gz</span><br></pre></td></tr></table></figure></li>\n<li>上传文件到离线环境上,解压并进行安装:<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">tar -xzf bcc.tar.gz</span><br><span class=\"line\"><span class=\"built_in\">cd</span> bcc-0.22.0 <span class=\"comment\"># 目录名可能会有所不同</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 安装系统依赖(可能需要根权限)</span></span><br><span class=\"line\"><span class=\"built_in\">sudo</span> yum install -y gcc gcc-c++ make bpfcc-tools <span class=\"comment\"># CentOS/RHEL</span></span><br><span class=\"line\"><span class=\"built_in\">sudo</span> apt-get install -y gcc g++ make bpfcc-tools <span class=\"comment\"># Ubuntu/Debian</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 编译和安装 bcc</span></span><br><span class=\"line\"><span class=\"built_in\">mkdir</span> build</span><br><span class=\"line\"><span class=\"built_in\">cd</span> build</span><br><span class=\"line\">cmake ..</span><br><span class=\"line\">make</span><br><span class=\"line\"><span class=\"built_in\">sudo</span> make install</span><br></pre></td></tr></table></figure></li>\n<li>安装 bcc Python 模块<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># 解压下载的 Python 模块源代码包</span></span><br><span class=\"line\">tar -xzf bcc-python.tar.gz</span><br><span class=\"line\"><span class=\"built_in\">cd</span> bcc-python <span class=\"comment\"># 目录名可能会有所不同</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 安装 Python 模块</span></span><br><span class=\"line\">pip3 install .</span><br></pre></td></tr></table></figure></li>\n</ol>\n","excerpt":"","more":"<!-- Your content starts here -->\n\n<h2 id=\"Centos-安装-EBPF\"><a href=\"#Centos-安装-EBPF\" class=\"headerlink\" title=\"Centos 安装 EBPF\"></a>Centos 安装 EBPF</h2><p>安装必要工具和依赖:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"built_in\">sudo</span> yum install python3 python3-pip python3-devel gcc gcc-c++ make bcc bcc-tools bcc-devel</span><br></pre></td></tr></table></figure>\n\n<p>安装 BCC Python 模块</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pip3 install bcc</span><br></pre></td></tr></table></figure>\n\n<p>离线安装的方式如下:</p>\n<ol>\n<li>下载 bcc 和 Python 模块源包<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># 下载 bcc 的源码包</span></span><br><span class=\"line\">wget https://github.com/iovisor/bcc/archive/refs/tags/v0.22.0.tar.gz -O bcc.tar.gz</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 下载 bcc 的 Python 模块源代码包</span></span><br><span class=\"line\"><span class=\"comment\"># 可以去这里 https://pypi.org/project/bcc/#files 进行查找</span></span><br><span class=\"line\">wget https://files.pythonhosted.org/packages/38/dc/3ca34874926789f8df53f3c1d1c38e77ebf876f43760e8745316bb8bd1c0/bcc-0.1.10.tar.gz</span><br></pre></td></tr></table></figure></li>\n<li>上传文件到离线环境上,解压并进行安装:<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">tar -xzf bcc.tar.gz</span><br><span class=\"line\"><span class=\"built_in\">cd</span> bcc-0.22.0 <span class=\"comment\"># 目录名可能会有所不同</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 安装系统依赖(可能需要根权限)</span></span><br><span class=\"line\"><span class=\"built_in\">sudo</span> yum install -y gcc gcc-c++ make bpfcc-tools <span class=\"comment\"># CentOS/RHEL</span></span><br><span class=\"line\"><span class=\"built_in\">sudo</span> apt-get install -y gcc g++ make bpfcc-tools <span class=\"comment\"># Ubuntu/Debian</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 编译和安装 bcc</span></span><br><span class=\"line\"><span class=\"built_in\">mkdir</span> build</span><br><span class=\"line\"><span class=\"built_in\">cd</span> build</span><br><span class=\"line\">cmake ..</span><br><span class=\"line\">make</span><br><span class=\"line\"><span class=\"built_in\">sudo</span> make install</span><br></pre></td></tr></table></figure></li>\n<li>安装 bcc Python 模块<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># 解压下载的 Python 模块源代码包</span></span><br><span class=\"line\">tar -xzf bcc-python.tar.gz</span><br><span class=\"line\"><span class=\"built_in\">cd</span> bcc-python <span class=\"comment\"># 目录名可能会有所不同</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># 安装 Python 模块</span></span><br><span class=\"line\">pip3 install .</span><br></pre></td></tr></table></figure></li>\n</ol>\n"},{"title":"Pacemaker+Corosync使用简介","author":"baixiaozhou","description":"Pacemaker+Corosync使用简介","repo":"baixiaozhou/SysStress","date":"2024-08-30T09:59:26.000Z","references":["[红帽官方文档](https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/7/html/high_availability_add-on_reference/index)"],"cover":"https://th.bing.com/th/id/OIP.Rm6ykLUtQ5OOT3x4JTrWNAAAAA?rs=1&pid=ImgDetMain","banner":"https://th.bing.com/th/id/OIP.Rm6ykLUtQ5OOT3x4JTrWNAAAAA?rs=1&pid=ImgDetMain","_content":"\n\n{% quot 参考文档 %}\n\n[红帽官方文档](https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/7/html/high_availability_add-on_reference/index)\n\n# 介绍\n\npacemaker 和 corosync是两种开源软件组件,通常结合使用以构建高可用性(HA)集群。\n\n## Pacemaker\n\nPacemaker 是一个集群资源管理器,负责管理集群中所有资源的启动、停止、迁移等操作。它通过与 Corosync 协作,确保在节点故障或服务异常时,资源能够自动在其他健康节点上接管。从这里我们就可以发现,pacemaker 的核心在于管理\n\n### 组件\n\npacemaker 主要包括以下组件:\n\n-\tCIB (Cluster Information Base):存储集群的配置信息,包括资源、约束、节点等。\n-\tCRM (Cluster Resource Manager):决定如何在集群中分配和管理资源。\n-\tPEngine (Policy Engine):根据集群状态和配置策略做出决策。\n-\tFencing:通过 STONITH(Shoot The Other Node In The Head)机制来隔离失效的节点,防止脑裂。\n\n### 使用场景\n\n-\t管理集群中的各种资源(如虚拟 IP、数据库服务、文件系统等)。\n-\t确保服务的高可用性,在故障发生时自动切换资源到其他节点。\n\n## Corosync\n\nCorosync 是一个集群通信引擎,负责在集群节点之间提供消息传递、组成员资格管理、心跳检测等功能。它确保集群中所有节点之间的信息同步,监控节点的健康状况,并在节点故障时通知 pacemaker。\n\n###\t组件\n\n-\t组通信:用于确保集群中所有节点保持一致的视图。\n-\t故障检测:通过心跳机制监控节点状态,当节点失联时,通知 Pacemaker。\n-\t配置管理:管理集群节点的配置和成员资格。\n\n### 使用场景\n\n-\t集群中节点间的实时通信。\n-\t监控节点的可用性,并在节点失效时做出响应。\n\n# 安装部署\n\n## 安装依赖\n\n1. 在集群的所有节点上安装相关依赖:\n```\nyum install -y pcs pacemaker corosync # Centos\n```\n2. 启动相关服务并设置服务开机自启动:\n```\nsystemctl start pcsd \nsystemctl enable pcsd\n\n```\n3. 设置`hacluster`用户的密码(此用户在包安装的过程中会自动创建)\n```\nsudo passwd hacluster\n```\n4. 在 `/etc/hosts` 中加入节点配置,例如:\n```\n192.168.1.2 node2\n192.168.1.3 node3\n```\n\n## 命令操作\n\n集群的命令行操作基本上都是通过 pcs 进行,pcs 提供了如下一些命令:\n\n| 命令 | 说明 | 示例命令 |\n|-------------|-------------------------------------|---------------------------------------|\n| `cluster` | 配置集群选项和节点 | `pcs cluster start` 启动集群 |\n| `resource` | 管理集群资源 | `pcs resource create myresource ocf:heartbeat:IPaddr2 ip=192.168.1.1` 创建一个资源 |\n| `stonith` | 管理 fence 设备 | `pcs stonith create myfence fence_ipmilan ipaddr=192.168.1.100 login=admin passwd=password lanplus=1` 创建 STONITH 设备 |\n| `constraint`| 管理资源约束 | `pcs constraint location myresource prefers node1=100` 设置资源约束 |\n| `property` | 管理 Pacemaker 属性 | `pcs property set stonith-enabled=false` 禁用 STONITH |\n| `acl` | 管理 Pacemaker 访问控制列表 | `pcs acl role create readonly` 创建只读角色 |\n| `qdevice` | 管理本地主机上的仲裁设备提供程序 | `pcs qdevice add model net` 添加网络仲裁设备 |\n| `quorum` | 管理集群仲裁设置 | `pcs quorum status` 查看仲裁状态 |\n| `booth` | 管理 booth (集群票据管理器) | `pcs booth status` 查看 booth 状态 |\n| `status` | 查看集群状态 | `pcs status` 查看集群运行状态 |\n| `config` | 查看和管理集群配置 | `pcs config show` 显示集群配置 |\n| `pcsd` | 管理 pcs 守护进程 | `pcs pcsd status` 查看 pcsd 服务状态 |\n| `node` | 管理集群节点 | `pcs node standby node1` 将节点设置为备用 |\n| `alert` | 管理 Pacemaker 警报 | `pcs alert create node=node1 severity=critical` 创建警报 |\n| `client` | 管理 pcsd 客户端配置 | `pcs client cert-key-gen --force` 生成新的客户端证书 |\n\n此外,packmaker 还提供了其他的命令,比如 crm 的一系列工具:\n\n| 工具名 | 解释 | 示例 |\n|-----------------------|------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|\n| `crm_attribute` | 管理集群属性,包括设置、修改或删除节点属性。 | `crm_attribute --node node1 --name attr_name --update attr_value` 设置节点属性。 |\n| `crm_diff` | 比较两个 CIB 配置文件的差异,便于配置版本管理。 | `crm_diff cib_old.xml cib_new.xml` 比较两个 CIB 文件的差异。 |\n| `crm_error` | 显示集群运行过程中遇到的错误信息,帮助排查故障。 | `crm_error -s 12345` 显示特定错误代码的详细信息。 |\n| `crm_failcount` | 查看或管理资源的失败计数,影响资源的自动重新调度。 | `crm_failcount --query --resource my_resource --node node1` 查看失败计数。 |\n| `crm_master` | 管理主从资源(如 DRBD)状态的工具,用于启动或停止主从资源。 | `crm_master --promote my_resource` 提升资源为主状态。 |\n| `crm_mon` | 实时监控集群状态,显示资源、节点、失败信息。 | `crm_mon --interval=5s --show-detail` 每5秒更新监控,显示详细信息。 |\n| `crm_node` | 管理集群节点的工具,包括查看节点状态、删除节点等。 | `crm_node -l` 列出所有集群节点。 |\n| `crm_report` | 生成集群故障报告的工具,汇总集群状态、日志和诊断信息。 | `crm_report -f report.tar.bz2` 生成详细的故障报告。 |\n| `crm_resource` | 管理集群资源,包括启动、停止、迁移和清除资源。 | `crm_resource --move my_resource --node node2` 将资源迁移到另一个节点。 |\n| `crm_shadow` | 允许对 CIB 进行“影子”配置,便于测试和调试。 | `crm_shadow --create shadow_test` 创建影子配置。 |\n| `crm_simulate` | 模拟集群运行状态的工具,用于测试集群配置的行为。 | `crm_simulate --live --save-output output.xml` 运行模拟,并保存输出。 |\n| `crm_standby` | 将节点设置为待机状态,临时不参与资源调度,或重新激活节点。 | `crm_standby --node node1 --off` 将节点设置为待机状态。 |\n| `crm_ticket` | 管理集群的 ticket,用于决定哪些资源在哪些位置可以运行(多站点集群)。 | `crm_ticket --grant my_ticket --node node1` 授权 ticket 给指定节点。 |\n| `crm_verify` | 验证当前集群配置的工具,检查配置文件的完整性和正确性。 | `crm_verify --live-check` 验证当前运行中的集群配置。 |\n\npacemaker 和 crm 的命令对比:\n\n1. pcs(Pacemaker/Corosync Shell)\n - 简介: pcs 是 Pacemaker 和 Corosync 集群管理的命令行工具。它主要用于 Red Hat 系列操作系统(例如 RHEL、CentOS 等)。pcs 提供了一个简单的命令行界面,用于管理集群、资源、节点、约束等功能。\n - 功能:\n - 管理 Pacemaker 集群、Corosync 配置、STONITH 设备、资源和约束等。\n - 提供集群的创建、启动、停止、删除、资源添加、约束设置等命令。\n - 提供简单易用的命令接口,能够将集群管理的命令封装成一步到位的操作。\n - 支持通过 pcsd 提供 Web 界面的管理。\n - 适用场景: pcs 更加适用于初学者和需要快速操作的用户,因为它提供了很多高层次的命令,简化了集群管理。\n\n2. crm(Cluster Resource Manager Shell)\n - 简介: crm 是 Pacemaker 的原生命令行工具,提供更加底层的控制。crm 主要用于 Pacemaker 集群资源管理和调度,支持在更细粒度上配置和管理集群资源。\n - 功能:\n - 提供更细致的资源管理和集群控制功能。\n - crm 的指令可以进行更复杂的操作,比如编辑 CIB (Cluster Information Base) 的 XML 配置文件。\n - 允许更加精细的配置,适合对集群系统有深度了解的用户。\n - 适用场景: crm 更加适合高级用户,特别是那些需要精确配置、排查问题或操作底层 Pacemaker 资源的场景。\n\n## 节点认证和集群创建\n\n在集群中的任意一个节点上执行:\n1. 认证:\n``` bash\npcs cluster auth node2 node3 -u hacluster\n```\n2. 认证完整后创建集群\n``` bash\npcs cluster setup --name mycluster node2 node3 (同时添加所有节点)\n```\n创建完成后,会生成 corosync 的配置文件,默认位置`/etc/corosync/corosync.conf`, 其中的内容如下:\n``` json\ntotem {\n version: 2\n cluster_name: mycluster\n secauth: off\n transport: udpu\n}\n\nnodelist {\n node {\n ring0_addr: node2\n nodeid: 1\n }\n\n node {\n ring0_addr: node3\n nodeid: 2\n }\n}\n\nquorum {\n provider: corosync_votequorum\n two_node: 1\n}\n\nlogging {\n to_logfile: yes\n logfile: /var/log/cluster/corosync.log\n to_syslog: yes\n}\n```\n\n3. 启动集群\n``` bash\npcs cluster start --all # 这里启动失败的话,可以后面加上 --debug参数查看更详细的信息,可能会因为防火墙等问题导致启动失败\npcs cluster enable --all # 设置自启动\n```\n4. 查看集群状态\n``` bash\npcs status\n```\n我们看下输出情况:\n```\nCluster name: mycluster\n\nWARNINGS:\nNo stonith devices and stonith-enabled is not false\n\nStack: corosync\nCurrent DC: node2 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum\nLast updated: Mon Sep 2 18:43:57 2024\nLast change: Mon Sep 2 18:35:08 2024 by hacluster via crmd on node2\n\n2 nodes configured\n0 resource instances configured\n\nOnline: [ node2 node3 ]\n\nNo resources\n\n\nDaemon Status:\n corosync: active/disabled\n pacemaker: active/disabled\n pcsd: active/enabled\n```\n这里我们可以看到集群的总体情况,包括节点状态、服务状态(有两个服务还处于 disabled 状态, 通过`systemctl enable corosync pacemaker` 设置开机启动)、资源信息(还没有添加 resource)等\n\n## stonith 配置\n\n在上文集群的状态输出中还包括了一个告警信息: `No stonith devices and stonith-enabled is not false`, 这里的 stonith(Shoot The Other Node In The Head) 是一种防止“脑裂” (split-brain) 的机制。当集群中的一个节点失去与其他节点的连接时,stonith 设备可以强制重启或关闭这个失联的节点,避免两个或多个节点同时操作同一个资源,导致数据损坏。想要消除这个告警,有两种解决方案:\n1. 禁用 stonith: 如果是自己的测试环境,那么可以禁用掉 stonith 来消除告警,操作方法为:\n ``` bash\n pcs property set stonith-enabled=false\n ```\n2. 配置 stonith 设备: 在生产环境中,建议配置 stonith\n\n我们先根据官方文档的指示看一下stonith 有哪些可用代理:\n``` bash\n[root@node2 corosync]# pcs stonith list\nError: No stonith agents available. Do you have fence agents installed?\n```\n这里提示没有代理的 agent 可用,所以我们首先需要安装`fence agent`:\n``` shell\nyum install -y fence-agents\n```\n我们再 list 一下,就可以看到支持的代理了\n\n| Fence Agent | 描述 |\n|-------------|------|\n| `fence_amt_ws` | 适用于 AMT (WS) 的 Fence 代理 |\n| `fence_apc` | 通过 telnet/ssh 控制 APC 的 Fence 代理 |\n| `fence_apc_snmp` | 适用于 APC 和 Tripplite PDU 的 SNMP Fence 代理 |\n| `fence_bladecenter` | 适用于 IBM BladeCenter 的 Fence 代理 |\n| `fence_brocade` | 通过 telnet/ssh 控制 HP Brocade 的 Fence 代理 |\n| `fence_cisco_mds` | 适用于 Cisco MDS 的 Fence 代理 |\n| `fence_cisco_ucs` | 适用于 Cisco UCS 的 Fence 代理 |\n| `fence_compute` | 用于自动复活 OpenStack 计算实例的 Fence 代理 |\n| `fence_drac5` | 适用于 Dell DRAC CMC/5 的 Fence 代理 |\n| `fence_eaton_snmp` | 适用于 Eaton 的 SNMP Fence 代理 |\n| `fence_emerson` | 适用于 Emerson 的 SNMP Fence 代理 |\n| `fence_eps` | 适用于 ePowerSwitch 的 Fence 代理 |\n| `fence_evacuate` | 用于自动复活 OpenStack 计算实例的 Fence 代理 |\n| `fence_heuristics_ping` | 基于 ping 进行启发式 Fencing 的代理 |\n| `fence_hpblade` | 适用于 HP BladeSystem 的 Fence 代理 |\n| `fence_ibmblade` | 通过 SNMP 控制 IBM BladeCenter 的 Fence 代理 |\n| `fence_idrac` | 适用于 IPMI 的 Fence 代理 |\n| `fence_ifmib` | 适用于 IF MIB 的 Fence 代理 |\n| `fence_ilo` | 适用于 HP iLO 的 Fence 代理 |\n| `fence_ilo2` | 适用于 HP iLO2 的 Fence 代理 |\n| `fence_ilo3` | 适用于 IPMI 的 Fence 代理 |\n| `fence_ilo3_ssh` | 通过 SSH 控制 HP iLO3 的 Fence 代理 |\n| `fence_ilo4` | 适用于 IPMI 的 Fence 代理 |\n| `fence_ilo4_ssh` | 通过 SSH 控制 HP iLO4 的 Fence 代理 |\n| `fence_ilo5` | 适用于 IPMI 的 Fence 代理 |\n| `fence_ilo5_ssh` | 通过 SSH 控制 HP iLO5 的 Fence 代理 |\n| `fence_ilo_moonshot` | 适用于 HP Moonshot iLO 的 Fence 代理 |\n| `fence_ilo_mp` | 适用于 HP iLO MP 的 Fence 代理 |\n| `fence_ilo_ssh` | 通过 SSH 控制 HP iLO 的 Fence 代理 |\n| `fence_imm` | 适用于 IPMI 的 Fence 代理 |\n| `fence_intelmodular` | 适用于 Intel Modular 的 Fence 代理 |\n| `fence_ipdu` | 通过 SNMP 控制 iPDU 的 Fence 代理 |\n| `fence_ipmilan` | 适用于 IPMI 的 Fence 代理 |\n| `fence_kdump` | 与 kdump 崩溃恢复服务一起使用的 Fence 代理 |\n| `fence_mpath` | 用于多路径持久保留的 Fence 代理 |\n| `fence_redfish` | 适用于 Redfish 的 I/O Fencing 代理 |\n| `fence_rhevm` | 适用于 RHEV-M REST API 的 Fence 代理 |\n| `fence_rsa` | 适用于 IBM RSA 的 Fence 代理 |\n| `fence_rsb` | 适用于 Fujitsu-Siemens RSB 的 I/O Fencing 代理 |\n| `fence_sbd` | 适用于 SBD 的 Fence 代理 |\n| `fence_scsi` | 用于 SCSI 持久保留的 Fence 代理 |\n| `fence_virt` | 适用于虚拟机的 Fence 代理 |\n| `fence_vmware_rest` | 适用于 VMware REST API 的 Fence 代理 |\n| `fence_vmware_soap` | 通过 SOAP API 控制 VMware 的 Fence 代理 |\n| `fence_wti` | 适用于 WTI 的 Fence 代理 |\n| `fence_xvm` | 适用于虚拟机的 Fence 代理 |\n\n想查看代理的具体用法,可以使用:\n``` bash\npcs stonith describe stonith_agent\n```\n\n使用 `fence_heuristics_ping` 作为代理,先通过`pcs stonith describe fence_heuristics_ping` 看下具体的用法和配置\n```\nfence_heuristics_ping - Fence agent for ping-heuristic based fencing\n\nfence_heuristics_ping uses ping-heuristics to control execution of another fence agent on the same fencing level.\n\nThis is not a fence agent by itself! Its only purpose is to enable/disable another fence agent that lives on the same fencing level but after fence_heuristics_ping.\n\nStonith options:\n method: Method to fence\n ping_count: The number of ping-probes that is being sent per target\n ping_good_count: The number of positive ping-probes required to account a target as available\n ping_interval: The interval in seconds between ping-probes\n ping_maxfail: The number of failed ping-targets to still account as overall success\n ping_targets (required): A comma separated list of ping-targets (optionally prepended by 'inet:' or 'inet6:') to be probed\n ping_timeout: The timeout in seconds till an individual ping-probe is accounted as lost\n quiet: Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.\n verbose: Verbose mode\n debug: Write debug information to given file\n delay: Wait X seconds before fencing is started\n login_timeout: Wait X seconds for cmd prompt after login\n power_timeout: Test X seconds for status change after ON/OFF\n power_wait: Wait X seconds after issuing ON/OFF\n shell_timeout: Wait X seconds for cmd prompt after issuing command\n retry_on: Count of attempts to retry power on\n pcmk_host_map: A mapping of host names to ports numbers for devices that do not support host names. Eg. node1:1;node2:2,3 would tell the cluster to use port 1 for node1 and ports 2 and 3\n for node2\n pcmk_host_list: A list of machines controlled by this device (Optional unless pcmk_host_check=static-list).\n pcmk_host_check: How to determine which machines are controlled by the device. Allowed values: dynamic-list (query the device via the 'list' command), static-list (check the pcmk_host_list\n attribute), status (query the device via the 'status' command), none (assume every device can fence every machine)\n pcmk_delay_max: Enable a random delay for stonith actions and specify the maximum of random delay. This prevents double fencing when using slow devices such as sbd. Use this to enable a\n random delay for stonith actions. The overall delay is derived from this random delay value adding a static delay so that the sum is kept below the maximum delay.\n pcmk_delay_base: Enable a base delay for stonith actions and specify base delay value. This prevents double fencing when different delays are configured on the nodes. Use this to enable a\n static delay for stonith actions. The overall delay is derived from a random delay value adding this static delay so that the sum is kept below the maximum delay.\n pcmk_action_limit: The maximum number of actions can be performed in parallel on this device Pengine property concurrent-fencing=true needs to be configured first. Then use this to specify\n the maximum number of actions can be performed in parallel on this device. -1 is unlimited.\n\nDefault operations:\n monitor: interval=60s\n```\n\n这里我们进行创建(其他参数都有默认值,按需修改即可):\n``` bash\npcs stonith create my_ping_fence_device fence_heuristics_ping \\\n ping_targets=\"node2,node3\"\n```\n\n创建完成后`pcs status`查看状态\n```\nCluster name: mycluster\nStack: corosync\nCurrent DC: node2 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum\nLast updated: Tue Sep 3 14:40:56 2024\nLast change: Tue Sep 3 14:35:39 2024 by root via cibadmin on node2\n\n2 nodes configured\n1 resource instance configured\n\nOnline: [ node2 node3 ]\n\nFull list of resources:\n\n my_ping_fence_device\t(stonith:fence_heuristics_ping):\tStarted node2\n\nFailed Fencing Actions:\n* reboot of my_apc_fence_device failed: delegate=, client=stonith_admin.40341, origin=node2,\n last-failed='Tue Sep 3 14:12:23 2024'\n\nDaemon Status:\n corosync: active/enabled\n pacemaker: active/enabled\n pcsd: active/enabled\n```\n验证 stonith 是否生效:\n``` bash\n[root@node2 cluster]# pcs stonith fence node3\nNode: node3 fenced\n```\n执行完这个操作后,节点会离线,pcs 服务会停止,想要加回来的话,在停止的节点上重新启动集群即可:`pcs cluster start && pcs cluster enable`\n\n# 实战操作\n\n## 添加节点\n\n上文中,我们构建了一个两节点的集群,我们可以尝试增加一个节点,构建一个三节点的集群\n\n1. 首先在新节点上安装各种依赖,设置密码等。\n2. 在原集群上认证新 node\n3. 在原集群上添加 node:`pcs cluster node add node4`\n4. 在 node4 上执行:`pcs cluster start && pcs cluster enable`\n\n再通过`pcs status`就可以看到新的节点已经加入\n\n添加完之后还需要更新新节点的一些配置,比如上文提到的 stonith:\n``` bash\npcs stonith update my_ping_fence_device fence_heuristics_ping \\\n ping_targets=\"node2,node3,node4\"\n```\n\n## 配置 resource\n\n### 资源类型\n\n创建 resource 的基本格式为:\n``` bash\npcs resource create resource-name ocf:heartbeat:apache [--options]\n```\n这里的 ocf:heartbeat:apache 第一个部分ocf,指明了这个资源采用的标准(类型),第二个部分标明这个资源脚本的在ocf中的名字空间,在这个例子中是heartbeat。最后一个部分指明了资源脚本的名称。\n\n我们先看下有哪些标准类型\n``` bash\n[root@node3 ~]# pcs resource standards\nlsb\nocf\nservice\nsystemd\n```\n查看可用的ocf资源提供者:\n``` bash\n[root@node3 ~]# pcs resource providers\nheartbeat\nopenstack\npacemaker\n```\n查看特定标准下所支持的脚本,例:ofc:heartbeat 下的脚本(列举了部分):\n``` bash\n[root@node3 ~]# pcs resource agents ocf:heartbeat\naliyun-vpc-move-ip\napache\naws-vpc-move-ip\naws-vpc-route53\nawseip\nawsvip\nazure-events\nazure-lb\nclvm\nconntrackd\nCTDB\ndb2\nDelay\ndhcpd\ndocker\nDummy\nethmonitor\nexportfs\nFilesystem\ngalera\ngarbd\niface-vlan\nIPaddr\nIPaddr2\n```\n\n### 设置虚拟 ip\n\n虚拟 IP(Virtual IP)是在高可用性集群中使用的一种技术,通过为服务提供一个不依赖于特定物理节点的 IP 地址来实现服务的高可用性。当集群中的某个节点出现故障时,虚拟 IP 可以迅速转移到另一个健康的节点上,从而保证服务的连续性。\n\n虚拟 IP 的使用场景\n\n1. 高可用性:虚拟 IP 最常见的使用场景是高可用性集群(如 Pacemaker 或 Keepalived),它允许一个服务在集群中的多个节点之间进行切换,而不会更改客户端访问的 IP 地址。\n2. 负载均衡:虚拟 IP 可以结合负载均衡器使用,将来自客户端的请求分配到多个后端服务器,以实现流量的均匀分布。\n3. 灾难恢复:在灾难恢复场景中,虚拟 IP 可以用于快速恢复服务,将业务流量从故障节点转移到备用节点上\n\n在 pcs 集群中,我们可以通过以下方式增加一个虚拟 ip:\n``` bash\npcs resource create virtual_ip ocf:heartbeat:IPaddr2 ip=x.x.x.x cidr_netmask=32 nic=bond1 op monitor interval=30s\n```\n执行完成后,通过`pcs status`就可以看到 ip 绑定在哪里:\n```\nvirtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node3\n```\n当我们关停 node3 的服务时,就会发现这个虚拟ip 绑定到了其他节点的 bond1 网卡上。\n\n### 增加服务\n\n我们以 httpd 服务为例,在集群中创建资源,首先安装对应服务:\n``` bash\nsudo yum install httpd -y\nsudo systemctl start httpd # 这里可选择不启动,后续如果通过pcs 直接托管,需要先停掉,\nsudo systemctl enable httpd \n```\n\n创建 resource:\n```\npcs resource create WebService ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s\n```\n\n## 结合 LVS + ldirectord 进行使用\n\n如果环境是一套多节点集群,在生产中我们肯定需要充分利用起这些节点,所以就要考虑流量分发。在这一层面上,我们可以使用 lvs 进行流量分发。这里首先对 lvs 对一个简单的介绍\n\nLVS(Linux Virtual Server)是一个基于 IP 负载均衡技术的开源软件项目,主要用于构建高可用、高性能的负载均衡集群。LVS 是 Linux 内核的一部分,通过网络层的负载均衡技术,将来自客户端的请求分发到多个后端服务器,从而实现分布式处理、提高系统的处理能力和可靠性。\n\nLVS 主要通过三种负载均衡模式(NAT 模式、DR 模式、TUN 模式)来实现流量的分发,支持大规模并发请求的处理,通常用于大型网站、电子商务平台和高访问量的 Web 应用中。\n\nLVS 的特点\n1. 高性能: LVS 工作在网络层(第4层),基于 IP 进行流量转发,性能极高。它能够处理大量的并发连接,适合高流量、大规模的网站和服务。\n2. 高可用性: LVS 通常与 Keepalived、Pacemaker 等高可用性工具配合使用,以实现负载均衡器的自动故障切换,确保服务的高可用性和稳定性。\n3. 多种负载均衡算法: LVS 提供了多种负载均衡算法,如轮询(Round Robin)、最小连接(Least Connection)、基于目标地址哈希(Destination Hashing)等,可以根据具体需求选择合适的算法进行流量分发。\n4. 多种工作模式, LVS 支持三种主要工作模式:\n\t-\tNAT 模式(网络地址转换模式):LVS 充当请求和响应的中介,适用于小规模集群。\n\t-\tDR 模式(直接路由模式):请求由 LVS 转发,但响应直接返回给客户端,适用于大型集群,性能高。\n\t-\tTUN 模式(IP 隧道模式):类似于 DR 模式,但支持跨网络部署,非常适合广域网负载均衡。\n5. 高扩展性: LVS 可以轻松地扩展和管理多台服务器,支持动态添加或移除后端服务器,适应业务需求的变化,且不影响服务的正常运行。\n6. 透明性: 对客户端和后端服务器来说,LVS 的存在是透明的。客户端并不感知负载均衡的存在,访问体验一致。后端服务器也不需要做特殊的配置,只需处理 LVS 转发的请求。\n7. 成熟且稳定: 作为一个成熟的负载均衡解决方案,LVS 被广泛应用于生产环境中,经过多年发展,功能完备,稳定性高。\n8. 安全性: LVS 可以与防火墙等安全工具结合使用,增强系统的安全性。此外,LVS 还支持 IP 地址过滤、端口过滤等功能,提供一定程度的安全保护。\n\nldirectord 是一个守护进程,用于管理和监控由 LVS 提供的虚拟服务(Virtual Services)。其主要功能包括:\n\n1.\t监控后端服务器:ldirectord 定期检查后端服务器的健康状况,确保只有健康的服务器参与流量分配。\n2.\t动态配置:基于后端服务器的健康状况,ldirectord 可以动态调整 LVS 的配置。例如,当一台服务器宕机时,ldirectord 会自动将其从 LVS 配置中移除。\n3.\t高可用性:结合 heartbeat 等高可用性工具,ldirectord 可以确保在主节点故障时,负载均衡服务能够自动切换到备用节点,继续提供服务。\n\n### 安装部署\n\n安装lvs:\n``` bash\nyum install lvm2 ipvsadm -y\n```\n在这里找包有一些技巧,比如一开始 chatgpt 提供的说法是要安装`lvs` 和 `ipvsadm`,但是在我的环境上通过`yum install -y lvs` 的时候提示没有这个包,那我们可以通过 yum 提供的一些命令来简单锁定一下,比如 `yum provides lvs`,这样就会把包含了这个命令的包显示出来(适用于知道命令但是不知道是哪个包的场景),\n\n安装 ldirector:\n```\n# 这里我用 yum 下载是没有找到对应包的,找了一圈也没找到安装方法,所以直接找的 rpm 包\n# 下载地址: ftp://ftp.icm.edu.pl/vol/rzm3/linux-opensuse/update/leap/15.2/oss/x86_64/ldirectord-4.4.0+git57.70549516-lp152.2.9.1.x86_64.rpm\n# 上传到机器上后,进行安装\nrpm -Uvh --force ldirectord-4.4.0+git57.70549516-lp152.2.9.1.x86_64.rpm\n\n# 需要依赖,先安装依赖,再装包\nyum install -y perl-IO-Socket-INET6 perl-MailTools perl-Net-SSLeay perl-Socket6 perl-libwww-perl\n# 操作完成之后,启动服务\nsystemctl start ldirectord.service\n```\n服务启动失败:\n{% image /images/ldirectord.png ldirectord服务 fancybox:true %}\n\n这里有点坑,缺少了依赖的文件,但是装包的时候没有提示,需要再安装: `yum install -y perl-Sys-Syslog`, 安装完成后此问题消失,但是此时配置文件还没配置,所以服务还起不来。\n\n### pcs 结合 lvs、ldirectord\n\n在上文中,我们创建了一个 httpd 服务和 vip 资源。 在实际生产中,要充分利用节点性能,我们可能要在多个节点上启动httpd 示例,我们在每个节点上都启动一个实例,然后将他们归到一个组中:\n``` bash\npcs resource delete WebService # 移除之前创建的服务\npcs resource create WebService1 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s\npcs resource create WebService2 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s --force\npcs resource create WebService3 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s --force # 创建三个服务\n\npcs constraint location WebService1 prefers node2\npcs constraint location WebService2 prefers node3\npcs constraint location WebService3 prefers node4 # 限制对应 resource 服务只能在指定节点上运行\n```\n\n配置 ldirectord:\n``` conf\nchecktimeout=10\ncheckinterval=2\nautoreload=yes\nlogfile=\"/var/log/ldirectord.log\"\nquiescent=yes\n\nvirtual=vip:80 # 之前绑定的 VIP\n real=192.168.1.2:80 gate\n real=192.168.1.3:80 gate\n real=192.168.1.4:80 gate\n fallback=127.0.0.1:80\n service=http\n request=\"index.html\"\n receive=\"HTTP/1.1 200 OK\"\n scheduler=rr\n protocol=tcp\n checktype=negotiate\n```\n\n然后在通过 `ipvsadm -ln` 就可以查看到详细的信息:\n``` bash\nIP Virtual Server version 1.2.1 (size=4096)\nProt LocalAddress:Port Scheduler Flags\n -> RemoteAddress:Port Forward Weight ActiveConn InActConn\nTCP vip:80 rr\n -> 192.168.1.2:80 Route 0 0 0\n -> 192.168.1.3:80 Route 0 0 0\n -> 192.168.1.4:80 Route 0 0 0\n -> 127.0.0.1:80 Route 1 0 0\n```\n\n然后我们可以在 pcs 上创建一个资源 lvs 相关的资源:\n``` bash\npcs resource create my_lvs ocf:heartbeat:ldirectord \\\n configfile=/etc/ha.d/ldirectord.cf \\\n ldirectord=/usr/sbin/ldirectord \\\n op monitor interval=15s timeout=60s \\\n op stop timeout=60s\n```\n这里的ocf:heartbeat:ldirectord 在有的版本中会默认安装,有的版本不会,如果没有的话需要手动下载: https://github.com/ClusterLabs/resource-agents/blob/main/ldirectord/OCF/ldirectord.in\n存放到: `/usr/lib/ocf/resource.d/heartbeat/ldirectord` 并添加可执行权限: `chmod +x /usr/lib/ocf/resource.d/heartbeat/ldirectord`\n\n创建完成后,我们可以将 vip 和 lvs 绑定到一个组中,这样 lvs 就会跟着 vip 进行转移了:\n``` bash\npcs resource group add balanceGroup virtual_ip my_lvs\n```\n通过`pcs status`查看就可以看到:\n```\nResource Group: balanceGroup\n virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node2\n my_lvs\t(ocf::heartbeat:ldirectord):\tStarted node2\n```\n不断对节点进行关闭测试,可以看到 lvs 和 vip 始终都在同一个节点上\n\n## 增加节点属性\n\n我们这里使用另外一个观察集群状态的命令:`crm_mon`, 比如`crm_mon -A1`\n``` bash\n[root@node2 rpm]# crm_mon -A1\nStack: corosync\nCurrent DC: node3 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum\nLast updated: Thu Sep 5 22:09:53 2024\nLast change: Wed Sep 4 18:17:25 2024 by root via cibadmin on node3\n\n3 nodes configured\n6 resource instances configured\n\nOnline: [ node2 node3 node4 ]\n\nActive resources:\n\n my_ping_fence_device\t(stonith:fence_heuristics_ping):\tStarted node3\n WebService1\t(ocf::heartbeat:apache):\tStarted node4\n WebService2\t(ocf::heartbeat:apache):\tStarted node3\n WebService3\t(ocf::heartbeat:apache):\tStarted node4\n Resource Group: balanceGroup\n virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node2\n my_lvs\t(ocf::heartbeat:ldirectord):\tStarted node2\n\nNode Attributes:\n* Node node2:\n* Node node3:\n* Node node4:\n.....\n```\n输出和`pcs status`查看到的效果基本上是差不多的。但是在下面有`Node Attributes`,这里我们看下节点属性怎么设置:\n``` bash\npcs node attribute node2 role=master\npcs node attribute node3 role=standby\npcs node attribute node4 role=standby\n```\n或者\n``` bash\ncrm_attribute --node node2 --name mysql --update master\ncrm_attribute --node node3 --name mysql --update standby\n```\n设置完成之后,我们就可以看到节点属性:\n```\nNode Attributes:\n* Node node2:\n + mysql \t: master\n + role \t: master\n* Node node3:\n + mysql \t: standby\n + role \t: standby\n* Node node4:\n + role \t: standby\n```\n那有人就会好奇这样设置有什么用呢?主要用途是在哪里呢。\n\n这里的指标往往是动态的,可以根据自己喜好结合一些扩展进行变化,比如部署了一套 postgresql 集群,集群中有主有备,有同步节点也有异步节点,有的节点状态可能有问题,那我们怎么能够显示出这个集群的整体情况呢,这样就可以使用 Node Attributes进行设置,关于如果搭建 pcs + postgresql 的集群,大家可以参考这篇文章: [基于Pacemaker的PostgreSQL高可用集群](https://www.cnblogs.com/Alicebat/p/14148933.html)\n\n最终我们看到的效果如下:\n```\nNode Attributes:\n* Node pg01:\n + master-pgsql \t: 1000 \n + pgsql-data-status \t: LATEST \n + pgsql-master-baseline \t: 0000000008000098\n + pgsql-status \t: PRI \n* Node pg02:\n + master-pgsql \t: -INFINITY \n + pgsql-data-status \t: STREAMING|ASYNC\n + pgsql-status \t: HS:async \n* Node pg03:\n + master-pgsql \t: 100 \n + pgsql-data-status \t: STREAMING|SYNC\n + pgsql-status \t: HS:sync \n```\n当集群发生节点变动,状态异常时,我们就可以根据 attibutes 的一些信息查看定位。","source":"_posts/Pacemaker-Corosync使用简介.md","raw":"---\ntitle: Pacemaker+Corosync使用简介\nauthor: baixiaozhou\ncategories:\n - 高可用\ntags:\n - Pacemaker\n - Corosync\n - Linux\n - 高可用方案\ndescription: Pacemaker+Corosync使用简介\nrepo: baixiaozhou/SysStress\ndate: 2024-08-30 17:59:26\nreferences:\n - '[红帽官方文档](https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/7/html/high_availability_add-on_reference/index)'\ncover: https://th.bing.com/th/id/OIP.Rm6ykLUtQ5OOT3x4JTrWNAAAAA?rs=1&pid=ImgDetMain\nbanner: https://th.bing.com/th/id/OIP.Rm6ykLUtQ5OOT3x4JTrWNAAAAA?rs=1&pid=ImgDetMain\n---\n\n\n{% quot 参考文档 %}\n\n[红帽官方文档](https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/7/html/high_availability_add-on_reference/index)\n\n# 介绍\n\npacemaker 和 corosync是两种开源软件组件,通常结合使用以构建高可用性(HA)集群。\n\n## Pacemaker\n\nPacemaker 是一个集群资源管理器,负责管理集群中所有资源的启动、停止、迁移等操作。它通过与 Corosync 协作,确保在节点故障或服务异常时,资源能够自动在其他健康节点上接管。从这里我们就可以发现,pacemaker 的核心在于管理\n\n### 组件\n\npacemaker 主要包括以下组件:\n\n-\tCIB (Cluster Information Base):存储集群的配置信息,包括资源、约束、节点等。\n-\tCRM (Cluster Resource Manager):决定如何在集群中分配和管理资源。\n-\tPEngine (Policy Engine):根据集群状态和配置策略做出决策。\n-\tFencing:通过 STONITH(Shoot The Other Node In The Head)机制来隔离失效的节点,防止脑裂。\n\n### 使用场景\n\n-\t管理集群中的各种资源(如虚拟 IP、数据库服务、文件系统等)。\n-\t确保服务的高可用性,在故障发生时自动切换资源到其他节点。\n\n## Corosync\n\nCorosync 是一个集群通信引擎,负责在集群节点之间提供消息传递、组成员资格管理、心跳检测等功能。它确保集群中所有节点之间的信息同步,监控节点的健康状况,并在节点故障时通知 pacemaker。\n\n###\t组件\n\n-\t组通信:用于确保集群中所有节点保持一致的视图。\n-\t故障检测:通过心跳机制监控节点状态,当节点失联时,通知 Pacemaker。\n-\t配置管理:管理集群节点的配置和成员资格。\n\n### 使用场景\n\n-\t集群中节点间的实时通信。\n-\t监控节点的可用性,并在节点失效时做出响应。\n\n# 安装部署\n\n## 安装依赖\n\n1. 在集群的所有节点上安装相关依赖:\n```\nyum install -y pcs pacemaker corosync # Centos\n```\n2. 启动相关服务并设置服务开机自启动:\n```\nsystemctl start pcsd \nsystemctl enable pcsd\n\n```\n3. 设置`hacluster`用户的密码(此用户在包安装的过程中会自动创建)\n```\nsudo passwd hacluster\n```\n4. 在 `/etc/hosts` 中加入节点配置,例如:\n```\n192.168.1.2 node2\n192.168.1.3 node3\n```\n\n## 命令操作\n\n集群的命令行操作基本上都是通过 pcs 进行,pcs 提供了如下一些命令:\n\n| 命令 | 说明 | 示例命令 |\n|-------------|-------------------------------------|---------------------------------------|\n| `cluster` | 配置集群选项和节点 | `pcs cluster start` 启动集群 |\n| `resource` | 管理集群资源 | `pcs resource create myresource ocf:heartbeat:IPaddr2 ip=192.168.1.1` 创建一个资源 |\n| `stonith` | 管理 fence 设备 | `pcs stonith create myfence fence_ipmilan ipaddr=192.168.1.100 login=admin passwd=password lanplus=1` 创建 STONITH 设备 |\n| `constraint`| 管理资源约束 | `pcs constraint location myresource prefers node1=100` 设置资源约束 |\n| `property` | 管理 Pacemaker 属性 | `pcs property set stonith-enabled=false` 禁用 STONITH |\n| `acl` | 管理 Pacemaker 访问控制列表 | `pcs acl role create readonly` 创建只读角色 |\n| `qdevice` | 管理本地主机上的仲裁设备提供程序 | `pcs qdevice add model net` 添加网络仲裁设备 |\n| `quorum` | 管理集群仲裁设置 | `pcs quorum status` 查看仲裁状态 |\n| `booth` | 管理 booth (集群票据管理器) | `pcs booth status` 查看 booth 状态 |\n| `status` | 查看集群状态 | `pcs status` 查看集群运行状态 |\n| `config` | 查看和管理集群配置 | `pcs config show` 显示集群配置 |\n| `pcsd` | 管理 pcs 守护进程 | `pcs pcsd status` 查看 pcsd 服务状态 |\n| `node` | 管理集群节点 | `pcs node standby node1` 将节点设置为备用 |\n| `alert` | 管理 Pacemaker 警报 | `pcs alert create node=node1 severity=critical` 创建警报 |\n| `client` | 管理 pcsd 客户端配置 | `pcs client cert-key-gen --force` 生成新的客户端证书 |\n\n此外,packmaker 还提供了其他的命令,比如 crm 的一系列工具:\n\n| 工具名 | 解释 | 示例 |\n|-----------------------|------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|\n| `crm_attribute` | 管理集群属性,包括设置、修改或删除节点属性。 | `crm_attribute --node node1 --name attr_name --update attr_value` 设置节点属性。 |\n| `crm_diff` | 比较两个 CIB 配置文件的差异,便于配置版本管理。 | `crm_diff cib_old.xml cib_new.xml` 比较两个 CIB 文件的差异。 |\n| `crm_error` | 显示集群运行过程中遇到的错误信息,帮助排查故障。 | `crm_error -s 12345` 显示特定错误代码的详细信息。 |\n| `crm_failcount` | 查看或管理资源的失败计数,影响资源的自动重新调度。 | `crm_failcount --query --resource my_resource --node node1` 查看失败计数。 |\n| `crm_master` | 管理主从资源(如 DRBD)状态的工具,用于启动或停止主从资源。 | `crm_master --promote my_resource` 提升资源为主状态。 |\n| `crm_mon` | 实时监控集群状态,显示资源、节点、失败信息。 | `crm_mon --interval=5s --show-detail` 每5秒更新监控,显示详细信息。 |\n| `crm_node` | 管理集群节点的工具,包括查看节点状态、删除节点等。 | `crm_node -l` 列出所有集群节点。 |\n| `crm_report` | 生成集群故障报告的工具,汇总集群状态、日志和诊断信息。 | `crm_report -f report.tar.bz2` 生成详细的故障报告。 |\n| `crm_resource` | 管理集群资源,包括启动、停止、迁移和清除资源。 | `crm_resource --move my_resource --node node2` 将资源迁移到另一个节点。 |\n| `crm_shadow` | 允许对 CIB 进行“影子”配置,便于测试和调试。 | `crm_shadow --create shadow_test` 创建影子配置。 |\n| `crm_simulate` | 模拟集群运行状态的工具,用于测试集群配置的行为。 | `crm_simulate --live --save-output output.xml` 运行模拟,并保存输出。 |\n| `crm_standby` | 将节点设置为待机状态,临时不参与资源调度,或重新激活节点。 | `crm_standby --node node1 --off` 将节点设置为待机状态。 |\n| `crm_ticket` | 管理集群的 ticket,用于决定哪些资源在哪些位置可以运行(多站点集群)。 | `crm_ticket --grant my_ticket --node node1` 授权 ticket 给指定节点。 |\n| `crm_verify` | 验证当前集群配置的工具,检查配置文件的完整性和正确性。 | `crm_verify --live-check` 验证当前运行中的集群配置。 |\n\npacemaker 和 crm 的命令对比:\n\n1. pcs(Pacemaker/Corosync Shell)\n - 简介: pcs 是 Pacemaker 和 Corosync 集群管理的命令行工具。它主要用于 Red Hat 系列操作系统(例如 RHEL、CentOS 等)。pcs 提供了一个简单的命令行界面,用于管理集群、资源、节点、约束等功能。\n - 功能:\n - 管理 Pacemaker 集群、Corosync 配置、STONITH 设备、资源和约束等。\n - 提供集群的创建、启动、停止、删除、资源添加、约束设置等命令。\n - 提供简单易用的命令接口,能够将集群管理的命令封装成一步到位的操作。\n - 支持通过 pcsd 提供 Web 界面的管理。\n - 适用场景: pcs 更加适用于初学者和需要快速操作的用户,因为它提供了很多高层次的命令,简化了集群管理。\n\n2. crm(Cluster Resource Manager Shell)\n - 简介: crm 是 Pacemaker 的原生命令行工具,提供更加底层的控制。crm 主要用于 Pacemaker 集群资源管理和调度,支持在更细粒度上配置和管理集群资源。\n - 功能:\n - 提供更细致的资源管理和集群控制功能。\n - crm 的指令可以进行更复杂的操作,比如编辑 CIB (Cluster Information Base) 的 XML 配置文件。\n - 允许更加精细的配置,适合对集群系统有深度了解的用户。\n - 适用场景: crm 更加适合高级用户,特别是那些需要精确配置、排查问题或操作底层 Pacemaker 资源的场景。\n\n## 节点认证和集群创建\n\n在集群中的任意一个节点上执行:\n1. 认证:\n``` bash\npcs cluster auth node2 node3 -u hacluster\n```\n2. 认证完整后创建集群\n``` bash\npcs cluster setup --name mycluster node2 node3 (同时添加所有节点)\n```\n创建完成后,会生成 corosync 的配置文件,默认位置`/etc/corosync/corosync.conf`, 其中的内容如下:\n``` json\ntotem {\n version: 2\n cluster_name: mycluster\n secauth: off\n transport: udpu\n}\n\nnodelist {\n node {\n ring0_addr: node2\n nodeid: 1\n }\n\n node {\n ring0_addr: node3\n nodeid: 2\n }\n}\n\nquorum {\n provider: corosync_votequorum\n two_node: 1\n}\n\nlogging {\n to_logfile: yes\n logfile: /var/log/cluster/corosync.log\n to_syslog: yes\n}\n```\n\n3. 启动集群\n``` bash\npcs cluster start --all # 这里启动失败的话,可以后面加上 --debug参数查看更详细的信息,可能会因为防火墙等问题导致启动失败\npcs cluster enable --all # 设置自启动\n```\n4. 查看集群状态\n``` bash\npcs status\n```\n我们看下输出情况:\n```\nCluster name: mycluster\n\nWARNINGS:\nNo stonith devices and stonith-enabled is not false\n\nStack: corosync\nCurrent DC: node2 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum\nLast updated: Mon Sep 2 18:43:57 2024\nLast change: Mon Sep 2 18:35:08 2024 by hacluster via crmd on node2\n\n2 nodes configured\n0 resource instances configured\n\nOnline: [ node2 node3 ]\n\nNo resources\n\n\nDaemon Status:\n corosync: active/disabled\n pacemaker: active/disabled\n pcsd: active/enabled\n```\n这里我们可以看到集群的总体情况,包括节点状态、服务状态(有两个服务还处于 disabled 状态, 通过`systemctl enable corosync pacemaker` 设置开机启动)、资源信息(还没有添加 resource)等\n\n## stonith 配置\n\n在上文集群的状态输出中还包括了一个告警信息: `No stonith devices and stonith-enabled is not false`, 这里的 stonith(Shoot The Other Node In The Head) 是一种防止“脑裂” (split-brain) 的机制。当集群中的一个节点失去与其他节点的连接时,stonith 设备可以强制重启或关闭这个失联的节点,避免两个或多个节点同时操作同一个资源,导致数据损坏。想要消除这个告警,有两种解决方案:\n1. 禁用 stonith: 如果是自己的测试环境,那么可以禁用掉 stonith 来消除告警,操作方法为:\n ``` bash\n pcs property set stonith-enabled=false\n ```\n2. 配置 stonith 设备: 在生产环境中,建议配置 stonith\n\n我们先根据官方文档的指示看一下stonith 有哪些可用代理:\n``` bash\n[root@node2 corosync]# pcs stonith list\nError: No stonith agents available. Do you have fence agents installed?\n```\n这里提示没有代理的 agent 可用,所以我们首先需要安装`fence agent`:\n``` shell\nyum install -y fence-agents\n```\n我们再 list 一下,就可以看到支持的代理了\n\n| Fence Agent | 描述 |\n|-------------|------|\n| `fence_amt_ws` | 适用于 AMT (WS) 的 Fence 代理 |\n| `fence_apc` | 通过 telnet/ssh 控制 APC 的 Fence 代理 |\n| `fence_apc_snmp` | 适用于 APC 和 Tripplite PDU 的 SNMP Fence 代理 |\n| `fence_bladecenter` | 适用于 IBM BladeCenter 的 Fence 代理 |\n| `fence_brocade` | 通过 telnet/ssh 控制 HP Brocade 的 Fence 代理 |\n| `fence_cisco_mds` | 适用于 Cisco MDS 的 Fence 代理 |\n| `fence_cisco_ucs` | 适用于 Cisco UCS 的 Fence 代理 |\n| `fence_compute` | 用于自动复活 OpenStack 计算实例的 Fence 代理 |\n| `fence_drac5` | 适用于 Dell DRAC CMC/5 的 Fence 代理 |\n| `fence_eaton_snmp` | 适用于 Eaton 的 SNMP Fence 代理 |\n| `fence_emerson` | 适用于 Emerson 的 SNMP Fence 代理 |\n| `fence_eps` | 适用于 ePowerSwitch 的 Fence 代理 |\n| `fence_evacuate` | 用于自动复活 OpenStack 计算实例的 Fence 代理 |\n| `fence_heuristics_ping` | 基于 ping 进行启发式 Fencing 的代理 |\n| `fence_hpblade` | 适用于 HP BladeSystem 的 Fence 代理 |\n| `fence_ibmblade` | 通过 SNMP 控制 IBM BladeCenter 的 Fence 代理 |\n| `fence_idrac` | 适用于 IPMI 的 Fence 代理 |\n| `fence_ifmib` | 适用于 IF MIB 的 Fence 代理 |\n| `fence_ilo` | 适用于 HP iLO 的 Fence 代理 |\n| `fence_ilo2` | 适用于 HP iLO2 的 Fence 代理 |\n| `fence_ilo3` | 适用于 IPMI 的 Fence 代理 |\n| `fence_ilo3_ssh` | 通过 SSH 控制 HP iLO3 的 Fence 代理 |\n| `fence_ilo4` | 适用于 IPMI 的 Fence 代理 |\n| `fence_ilo4_ssh` | 通过 SSH 控制 HP iLO4 的 Fence 代理 |\n| `fence_ilo5` | 适用于 IPMI 的 Fence 代理 |\n| `fence_ilo5_ssh` | 通过 SSH 控制 HP iLO5 的 Fence 代理 |\n| `fence_ilo_moonshot` | 适用于 HP Moonshot iLO 的 Fence 代理 |\n| `fence_ilo_mp` | 适用于 HP iLO MP 的 Fence 代理 |\n| `fence_ilo_ssh` | 通过 SSH 控制 HP iLO 的 Fence 代理 |\n| `fence_imm` | 适用于 IPMI 的 Fence 代理 |\n| `fence_intelmodular` | 适用于 Intel Modular 的 Fence 代理 |\n| `fence_ipdu` | 通过 SNMP 控制 iPDU 的 Fence 代理 |\n| `fence_ipmilan` | 适用于 IPMI 的 Fence 代理 |\n| `fence_kdump` | 与 kdump 崩溃恢复服务一起使用的 Fence 代理 |\n| `fence_mpath` | 用于多路径持久保留的 Fence 代理 |\n| `fence_redfish` | 适用于 Redfish 的 I/O Fencing 代理 |\n| `fence_rhevm` | 适用于 RHEV-M REST API 的 Fence 代理 |\n| `fence_rsa` | 适用于 IBM RSA 的 Fence 代理 |\n| `fence_rsb` | 适用于 Fujitsu-Siemens RSB 的 I/O Fencing 代理 |\n| `fence_sbd` | 适用于 SBD 的 Fence 代理 |\n| `fence_scsi` | 用于 SCSI 持久保留的 Fence 代理 |\n| `fence_virt` | 适用于虚拟机的 Fence 代理 |\n| `fence_vmware_rest` | 适用于 VMware REST API 的 Fence 代理 |\n| `fence_vmware_soap` | 通过 SOAP API 控制 VMware 的 Fence 代理 |\n| `fence_wti` | 适用于 WTI 的 Fence 代理 |\n| `fence_xvm` | 适用于虚拟机的 Fence 代理 |\n\n想查看代理的具体用法,可以使用:\n``` bash\npcs stonith describe stonith_agent\n```\n\n使用 `fence_heuristics_ping` 作为代理,先通过`pcs stonith describe fence_heuristics_ping` 看下具体的用法和配置\n```\nfence_heuristics_ping - Fence agent for ping-heuristic based fencing\n\nfence_heuristics_ping uses ping-heuristics to control execution of another fence agent on the same fencing level.\n\nThis is not a fence agent by itself! Its only purpose is to enable/disable another fence agent that lives on the same fencing level but after fence_heuristics_ping.\n\nStonith options:\n method: Method to fence\n ping_count: The number of ping-probes that is being sent per target\n ping_good_count: The number of positive ping-probes required to account a target as available\n ping_interval: The interval in seconds between ping-probes\n ping_maxfail: The number of failed ping-targets to still account as overall success\n ping_targets (required): A comma separated list of ping-targets (optionally prepended by 'inet:' or 'inet6:') to be probed\n ping_timeout: The timeout in seconds till an individual ping-probe is accounted as lost\n quiet: Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.\n verbose: Verbose mode\n debug: Write debug information to given file\n delay: Wait X seconds before fencing is started\n login_timeout: Wait X seconds for cmd prompt after login\n power_timeout: Test X seconds for status change after ON/OFF\n power_wait: Wait X seconds after issuing ON/OFF\n shell_timeout: Wait X seconds for cmd prompt after issuing command\n retry_on: Count of attempts to retry power on\n pcmk_host_map: A mapping of host names to ports numbers for devices that do not support host names. Eg. node1:1;node2:2,3 would tell the cluster to use port 1 for node1 and ports 2 and 3\n for node2\n pcmk_host_list: A list of machines controlled by this device (Optional unless pcmk_host_check=static-list).\n pcmk_host_check: How to determine which machines are controlled by the device. Allowed values: dynamic-list (query the device via the 'list' command), static-list (check the pcmk_host_list\n attribute), status (query the device via the 'status' command), none (assume every device can fence every machine)\n pcmk_delay_max: Enable a random delay for stonith actions and specify the maximum of random delay. This prevents double fencing when using slow devices such as sbd. Use this to enable a\n random delay for stonith actions. The overall delay is derived from this random delay value adding a static delay so that the sum is kept below the maximum delay.\n pcmk_delay_base: Enable a base delay for stonith actions and specify base delay value. This prevents double fencing when different delays are configured on the nodes. Use this to enable a\n static delay for stonith actions. The overall delay is derived from a random delay value adding this static delay so that the sum is kept below the maximum delay.\n pcmk_action_limit: The maximum number of actions can be performed in parallel on this device Pengine property concurrent-fencing=true needs to be configured first. Then use this to specify\n the maximum number of actions can be performed in parallel on this device. -1 is unlimited.\n\nDefault operations:\n monitor: interval=60s\n```\n\n这里我们进行创建(其他参数都有默认值,按需修改即可):\n``` bash\npcs stonith create my_ping_fence_device fence_heuristics_ping \\\n ping_targets=\"node2,node3\"\n```\n\n创建完成后`pcs status`查看状态\n```\nCluster name: mycluster\nStack: corosync\nCurrent DC: node2 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum\nLast updated: Tue Sep 3 14:40:56 2024\nLast change: Tue Sep 3 14:35:39 2024 by root via cibadmin on node2\n\n2 nodes configured\n1 resource instance configured\n\nOnline: [ node2 node3 ]\n\nFull list of resources:\n\n my_ping_fence_device\t(stonith:fence_heuristics_ping):\tStarted node2\n\nFailed Fencing Actions:\n* reboot of my_apc_fence_device failed: delegate=, client=stonith_admin.40341, origin=node2,\n last-failed='Tue Sep 3 14:12:23 2024'\n\nDaemon Status:\n corosync: active/enabled\n pacemaker: active/enabled\n pcsd: active/enabled\n```\n验证 stonith 是否生效:\n``` bash\n[root@node2 cluster]# pcs stonith fence node3\nNode: node3 fenced\n```\n执行完这个操作后,节点会离线,pcs 服务会停止,想要加回来的话,在停止的节点上重新启动集群即可:`pcs cluster start && pcs cluster enable`\n\n# 实战操作\n\n## 添加节点\n\n上文中,我们构建了一个两节点的集群,我们可以尝试增加一个节点,构建一个三节点的集群\n\n1. 首先在新节点上安装各种依赖,设置密码等。\n2. 在原集群上认证新 node\n3. 在原集群上添加 node:`pcs cluster node add node4`\n4. 在 node4 上执行:`pcs cluster start && pcs cluster enable`\n\n再通过`pcs status`就可以看到新的节点已经加入\n\n添加完之后还需要更新新节点的一些配置,比如上文提到的 stonith:\n``` bash\npcs stonith update my_ping_fence_device fence_heuristics_ping \\\n ping_targets=\"node2,node3,node4\"\n```\n\n## 配置 resource\n\n### 资源类型\n\n创建 resource 的基本格式为:\n``` bash\npcs resource create resource-name ocf:heartbeat:apache [--options]\n```\n这里的 ocf:heartbeat:apache 第一个部分ocf,指明了这个资源采用的标准(类型),第二个部分标明这个资源脚本的在ocf中的名字空间,在这个例子中是heartbeat。最后一个部分指明了资源脚本的名称。\n\n我们先看下有哪些标准类型\n``` bash\n[root@node3 ~]# pcs resource standards\nlsb\nocf\nservice\nsystemd\n```\n查看可用的ocf资源提供者:\n``` bash\n[root@node3 ~]# pcs resource providers\nheartbeat\nopenstack\npacemaker\n```\n查看特定标准下所支持的脚本,例:ofc:heartbeat 下的脚本(列举了部分):\n``` bash\n[root@node3 ~]# pcs resource agents ocf:heartbeat\naliyun-vpc-move-ip\napache\naws-vpc-move-ip\naws-vpc-route53\nawseip\nawsvip\nazure-events\nazure-lb\nclvm\nconntrackd\nCTDB\ndb2\nDelay\ndhcpd\ndocker\nDummy\nethmonitor\nexportfs\nFilesystem\ngalera\ngarbd\niface-vlan\nIPaddr\nIPaddr2\n```\n\n### 设置虚拟 ip\n\n虚拟 IP(Virtual IP)是在高可用性集群中使用的一种技术,通过为服务提供一个不依赖于特定物理节点的 IP 地址来实现服务的高可用性。当集群中的某个节点出现故障时,虚拟 IP 可以迅速转移到另一个健康的节点上,从而保证服务的连续性。\n\n虚拟 IP 的使用场景\n\n1. 高可用性:虚拟 IP 最常见的使用场景是高可用性集群(如 Pacemaker 或 Keepalived),它允许一个服务在集群中的多个节点之间进行切换,而不会更改客户端访问的 IP 地址。\n2. 负载均衡:虚拟 IP 可以结合负载均衡器使用,将来自客户端的请求分配到多个后端服务器,以实现流量的均匀分布。\n3. 灾难恢复:在灾难恢复场景中,虚拟 IP 可以用于快速恢复服务,将业务流量从故障节点转移到备用节点上\n\n在 pcs 集群中,我们可以通过以下方式增加一个虚拟 ip:\n``` bash\npcs resource create virtual_ip ocf:heartbeat:IPaddr2 ip=x.x.x.x cidr_netmask=32 nic=bond1 op monitor interval=30s\n```\n执行完成后,通过`pcs status`就可以看到 ip 绑定在哪里:\n```\nvirtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node3\n```\n当我们关停 node3 的服务时,就会发现这个虚拟ip 绑定到了其他节点的 bond1 网卡上。\n\n### 增加服务\n\n我们以 httpd 服务为例,在集群中创建资源,首先安装对应服务:\n``` bash\nsudo yum install httpd -y\nsudo systemctl start httpd # 这里可选择不启动,后续如果通过pcs 直接托管,需要先停掉,\nsudo systemctl enable httpd \n```\n\n创建 resource:\n```\npcs resource create WebService ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s\n```\n\n## 结合 LVS + ldirectord 进行使用\n\n如果环境是一套多节点集群,在生产中我们肯定需要充分利用起这些节点,所以就要考虑流量分发。在这一层面上,我们可以使用 lvs 进行流量分发。这里首先对 lvs 对一个简单的介绍\n\nLVS(Linux Virtual Server)是一个基于 IP 负载均衡技术的开源软件项目,主要用于构建高可用、高性能的负载均衡集群。LVS 是 Linux 内核的一部分,通过网络层的负载均衡技术,将来自客户端的请求分发到多个后端服务器,从而实现分布式处理、提高系统的处理能力和可靠性。\n\nLVS 主要通过三种负载均衡模式(NAT 模式、DR 模式、TUN 模式)来实现流量的分发,支持大规模并发请求的处理,通常用于大型网站、电子商务平台和高访问量的 Web 应用中。\n\nLVS 的特点\n1. 高性能: LVS 工作在网络层(第4层),基于 IP 进行流量转发,性能极高。它能够处理大量的并发连接,适合高流量、大规模的网站和服务。\n2. 高可用性: LVS 通常与 Keepalived、Pacemaker 等高可用性工具配合使用,以实现负载均衡器的自动故障切换,确保服务的高可用性和稳定性。\n3. 多种负载均衡算法: LVS 提供了多种负载均衡算法,如轮询(Round Robin)、最小连接(Least Connection)、基于目标地址哈希(Destination Hashing)等,可以根据具体需求选择合适的算法进行流量分发。\n4. 多种工作模式, LVS 支持三种主要工作模式:\n\t-\tNAT 模式(网络地址转换模式):LVS 充当请求和响应的中介,适用于小规模集群。\n\t-\tDR 模式(直接路由模式):请求由 LVS 转发,但响应直接返回给客户端,适用于大型集群,性能高。\n\t-\tTUN 模式(IP 隧道模式):类似于 DR 模式,但支持跨网络部署,非常适合广域网负载均衡。\n5. 高扩展性: LVS 可以轻松地扩展和管理多台服务器,支持动态添加或移除后端服务器,适应业务需求的变化,且不影响服务的正常运行。\n6. 透明性: 对客户端和后端服务器来说,LVS 的存在是透明的。客户端并不感知负载均衡的存在,访问体验一致。后端服务器也不需要做特殊的配置,只需处理 LVS 转发的请求。\n7. 成熟且稳定: 作为一个成熟的负载均衡解决方案,LVS 被广泛应用于生产环境中,经过多年发展,功能完备,稳定性高。\n8. 安全性: LVS 可以与防火墙等安全工具结合使用,增强系统的安全性。此外,LVS 还支持 IP 地址过滤、端口过滤等功能,提供一定程度的安全保护。\n\nldirectord 是一个守护进程,用于管理和监控由 LVS 提供的虚拟服务(Virtual Services)。其主要功能包括:\n\n1.\t监控后端服务器:ldirectord 定期检查后端服务器的健康状况,确保只有健康的服务器参与流量分配。\n2.\t动态配置:基于后端服务器的健康状况,ldirectord 可以动态调整 LVS 的配置。例如,当一台服务器宕机时,ldirectord 会自动将其从 LVS 配置中移除。\n3.\t高可用性:结合 heartbeat 等高可用性工具,ldirectord 可以确保在主节点故障时,负载均衡服务能够自动切换到备用节点,继续提供服务。\n\n### 安装部署\n\n安装lvs:\n``` bash\nyum install lvm2 ipvsadm -y\n```\n在这里找包有一些技巧,比如一开始 chatgpt 提供的说法是要安装`lvs` 和 `ipvsadm`,但是在我的环境上通过`yum install -y lvs` 的时候提示没有这个包,那我们可以通过 yum 提供的一些命令来简单锁定一下,比如 `yum provides lvs`,这样就会把包含了这个命令的包显示出来(适用于知道命令但是不知道是哪个包的场景),\n\n安装 ldirector:\n```\n# 这里我用 yum 下载是没有找到对应包的,找了一圈也没找到安装方法,所以直接找的 rpm 包\n# 下载地址: ftp://ftp.icm.edu.pl/vol/rzm3/linux-opensuse/update/leap/15.2/oss/x86_64/ldirectord-4.4.0+git57.70549516-lp152.2.9.1.x86_64.rpm\n# 上传到机器上后,进行安装\nrpm -Uvh --force ldirectord-4.4.0+git57.70549516-lp152.2.9.1.x86_64.rpm\n\n# 需要依赖,先安装依赖,再装包\nyum install -y perl-IO-Socket-INET6 perl-MailTools perl-Net-SSLeay perl-Socket6 perl-libwww-perl\n# 操作完成之后,启动服务\nsystemctl start ldirectord.service\n```\n服务启动失败:\n{% image /images/ldirectord.png ldirectord服务 fancybox:true %}\n\n这里有点坑,缺少了依赖的文件,但是装包的时候没有提示,需要再安装: `yum install -y perl-Sys-Syslog`, 安装完成后此问题消失,但是此时配置文件还没配置,所以服务还起不来。\n\n### pcs 结合 lvs、ldirectord\n\n在上文中,我们创建了一个 httpd 服务和 vip 资源。 在实际生产中,要充分利用节点性能,我们可能要在多个节点上启动httpd 示例,我们在每个节点上都启动一个实例,然后将他们归到一个组中:\n``` bash\npcs resource delete WebService # 移除之前创建的服务\npcs resource create WebService1 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s\npcs resource create WebService2 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s --force\npcs resource create WebService3 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s --force # 创建三个服务\n\npcs constraint location WebService1 prefers node2\npcs constraint location WebService2 prefers node3\npcs constraint location WebService3 prefers node4 # 限制对应 resource 服务只能在指定节点上运行\n```\n\n配置 ldirectord:\n``` conf\nchecktimeout=10\ncheckinterval=2\nautoreload=yes\nlogfile=\"/var/log/ldirectord.log\"\nquiescent=yes\n\nvirtual=vip:80 # 之前绑定的 VIP\n real=192.168.1.2:80 gate\n real=192.168.1.3:80 gate\n real=192.168.1.4:80 gate\n fallback=127.0.0.1:80\n service=http\n request=\"index.html\"\n receive=\"HTTP/1.1 200 OK\"\n scheduler=rr\n protocol=tcp\n checktype=negotiate\n```\n\n然后在通过 `ipvsadm -ln` 就可以查看到详细的信息:\n``` bash\nIP Virtual Server version 1.2.1 (size=4096)\nProt LocalAddress:Port Scheduler Flags\n -> RemoteAddress:Port Forward Weight ActiveConn InActConn\nTCP vip:80 rr\n -> 192.168.1.2:80 Route 0 0 0\n -> 192.168.1.3:80 Route 0 0 0\n -> 192.168.1.4:80 Route 0 0 0\n -> 127.0.0.1:80 Route 1 0 0\n```\n\n然后我们可以在 pcs 上创建一个资源 lvs 相关的资源:\n``` bash\npcs resource create my_lvs ocf:heartbeat:ldirectord \\\n configfile=/etc/ha.d/ldirectord.cf \\\n ldirectord=/usr/sbin/ldirectord \\\n op monitor interval=15s timeout=60s \\\n op stop timeout=60s\n```\n这里的ocf:heartbeat:ldirectord 在有的版本中会默认安装,有的版本不会,如果没有的话需要手动下载: https://github.com/ClusterLabs/resource-agents/blob/main/ldirectord/OCF/ldirectord.in\n存放到: `/usr/lib/ocf/resource.d/heartbeat/ldirectord` 并添加可执行权限: `chmod +x /usr/lib/ocf/resource.d/heartbeat/ldirectord`\n\n创建完成后,我们可以将 vip 和 lvs 绑定到一个组中,这样 lvs 就会跟着 vip 进行转移了:\n``` bash\npcs resource group add balanceGroup virtual_ip my_lvs\n```\n通过`pcs status`查看就可以看到:\n```\nResource Group: balanceGroup\n virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node2\n my_lvs\t(ocf::heartbeat:ldirectord):\tStarted node2\n```\n不断对节点进行关闭测试,可以看到 lvs 和 vip 始终都在同一个节点上\n\n## 增加节点属性\n\n我们这里使用另外一个观察集群状态的命令:`crm_mon`, 比如`crm_mon -A1`\n``` bash\n[root@node2 rpm]# crm_mon -A1\nStack: corosync\nCurrent DC: node3 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum\nLast updated: Thu Sep 5 22:09:53 2024\nLast change: Wed Sep 4 18:17:25 2024 by root via cibadmin on node3\n\n3 nodes configured\n6 resource instances configured\n\nOnline: [ node2 node3 node4 ]\n\nActive resources:\n\n my_ping_fence_device\t(stonith:fence_heuristics_ping):\tStarted node3\n WebService1\t(ocf::heartbeat:apache):\tStarted node4\n WebService2\t(ocf::heartbeat:apache):\tStarted node3\n WebService3\t(ocf::heartbeat:apache):\tStarted node4\n Resource Group: balanceGroup\n virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node2\n my_lvs\t(ocf::heartbeat:ldirectord):\tStarted node2\n\nNode Attributes:\n* Node node2:\n* Node node3:\n* Node node4:\n.....\n```\n输出和`pcs status`查看到的效果基本上是差不多的。但是在下面有`Node Attributes`,这里我们看下节点属性怎么设置:\n``` bash\npcs node attribute node2 role=master\npcs node attribute node3 role=standby\npcs node attribute node4 role=standby\n```\n或者\n``` bash\ncrm_attribute --node node2 --name mysql --update master\ncrm_attribute --node node3 --name mysql --update standby\n```\n设置完成之后,我们就可以看到节点属性:\n```\nNode Attributes:\n* Node node2:\n + mysql \t: master\n + role \t: master\n* Node node3:\n + mysql \t: standby\n + role \t: standby\n* Node node4:\n + role \t: standby\n```\n那有人就会好奇这样设置有什么用呢?主要用途是在哪里呢。\n\n这里的指标往往是动态的,可以根据自己喜好结合一些扩展进行变化,比如部署了一套 postgresql 集群,集群中有主有备,有同步节点也有异步节点,有的节点状态可能有问题,那我们怎么能够显示出这个集群的整体情况呢,这样就可以使用 Node Attributes进行设置,关于如果搭建 pcs + postgresql 的集群,大家可以参考这篇文章: [基于Pacemaker的PostgreSQL高可用集群](https://www.cnblogs.com/Alicebat/p/14148933.html)\n\n最终我们看到的效果如下:\n```\nNode Attributes:\n* Node pg01:\n + master-pgsql \t: 1000 \n + pgsql-data-status \t: LATEST \n + pgsql-master-baseline \t: 0000000008000098\n + pgsql-status \t: PRI \n* Node pg02:\n + master-pgsql \t: -INFINITY \n + pgsql-data-status \t: STREAMING|ASYNC\n + pgsql-status \t: HS:async \n* Node pg03:\n + master-pgsql \t: 100 \n + pgsql-data-status \t: STREAMING|SYNC\n + pgsql-status \t: HS:sync \n```\n当集群发生节点变动,状态异常时,我们就可以根据 attibutes 的一些信息查看定位。","slug":"Pacemaker-Corosync使用简介","published":1,"updated":"2024-09-05T08:33:34.724Z","_id":"cm0gkxi4j0000qzonghw7dafa","comments":1,"layout":"post","photos":[],"content":"<div class=\"tag-plugin quot\"><p class=\"content\" type=\"text\"><span class=\"empty\"></span><span class=\"text\">参考文档</span><span class=\"empty\"></span></p></div>\n\n<p><a href=\"https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/7/html/high_availability_add-on_reference/index\">红帽官方文档</a></p>\n<h1 id=\"介绍\"><a href=\"#介绍\" class=\"headerlink\" title=\"介绍\"></a>介绍</h1><p>pacemaker 和 corosync是两种开源软件组件,通常结合使用以构建高可用性(HA)集群。</p>\n<h2 id=\"Pacemaker\"><a href=\"#Pacemaker\" class=\"headerlink\" title=\"Pacemaker\"></a>Pacemaker</h2><p>Pacemaker 是一个集群资源管理器,负责管理集群中所有资源的启动、停止、迁移等操作。它通过与 Corosync 协作,确保在节点故障或服务异常时,资源能够自动在其他健康节点上接管。从这里我们就可以发现,pacemaker 的核心在于管理</p>\n<h3 id=\"组件\"><a href=\"#组件\" class=\"headerlink\" title=\"组件\"></a>组件</h3><p>pacemaker 主要包括以下组件:</p>\n<ul>\n<li> CIB (Cluster Information Base):存储集群的配置信息,包括资源、约束、节点等。</li>\n<li> CRM (Cluster Resource Manager):决定如何在集群中分配和管理资源。</li>\n<li> PEngine (Policy Engine):根据集群状态和配置策略做出决策。</li>\n<li> Fencing:通过 STONITH(Shoot The Other Node In The Head)机制来隔离失效的节点,防止脑裂。</li>\n</ul>\n<h3 id=\"使用场景\"><a href=\"#使用场景\" class=\"headerlink\" title=\"使用场景\"></a>使用场景</h3><ul>\n<li> 管理集群中的各种资源(如虚拟 IP、数据库服务、文件系统等)。</li>\n<li> 确保服务的高可用性,在故障发生时自动切换资源到其他节点。</li>\n</ul>\n<h2 id=\"Corosync\"><a href=\"#Corosync\" class=\"headerlink\" title=\"Corosync\"></a>Corosync</h2><p>Corosync 是一个集群通信引擎,负责在集群节点之间提供消息传递、组成员资格管理、心跳检测等功能。它确保集群中所有节点之间的信息同步,监控节点的健康状况,并在节点故障时通知 pacemaker。</p>\n<h3 id=\"组件-1\"><a href=\"#组件-1\" class=\"headerlink\" title=\"组件\"></a>组件</h3><ul>\n<li> 组通信:用于确保集群中所有节点保持一致的视图。</li>\n<li> 故障检测:通过心跳机制监控节点状态,当节点失联时,通知 Pacemaker。</li>\n<li> 配置管理:管理集群节点的配置和成员资格。</li>\n</ul>\n<h3 id=\"使用场景-1\"><a href=\"#使用场景-1\" class=\"headerlink\" title=\"使用场景\"></a>使用场景</h3><ul>\n<li> 集群中节点间的实时通信。</li>\n<li> 监控节点的可用性,并在节点失效时做出响应。</li>\n</ul>\n<h1 id=\"安装部署\"><a href=\"#安装部署\" class=\"headerlink\" title=\"安装部署\"></a>安装部署</h1><h2 id=\"安装依赖\"><a href=\"#安装依赖\" class=\"headerlink\" title=\"安装依赖\"></a>安装依赖</h2><ol>\n<li>在集群的所有节点上安装相关依赖:<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">yum install -y pcs pacemaker corosync # Centos</span><br></pre></td></tr></table></figure></li>\n<li>启动相关服务并设置服务开机自启动:<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">systemctl start pcsd </span><br><span class=\"line\">systemctl enable pcsd</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure></li>\n<li>设置<code>hacluster</code>用户的密码(此用户在包安装的过程中会自动创建)<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">sudo passwd hacluster</span><br></pre></td></tr></table></figure></li>\n<li>在 <code>/etc/hosts</code> 中加入节点配置,例如:<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">192.168.1.2 node2</span><br><span class=\"line\">192.168.1.3 node3</span><br></pre></td></tr></table></figure></li>\n</ol>\n<h2 id=\"命令操作\"><a href=\"#命令操作\" class=\"headerlink\" title=\"命令操作\"></a>命令操作</h2><p>集群的命令行操作基本上都是通过 pcs 进行,pcs 提供了如下一些命令:</p>\n<table>\n<thead>\n<tr>\n<th>命令</th>\n<th>说明</th>\n<th>示例命令</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>cluster</code></td>\n<td>配置集群选项和节点</td>\n<td><code>pcs cluster start</code> 启动集群</td>\n</tr>\n<tr>\n<td><code>resource</code></td>\n<td>管理集群资源</td>\n<td><code>pcs resource create myresource ocf:heartbeat:IPaddr2 ip=192.168.1.1</code> 创建一个资源</td>\n</tr>\n<tr>\n<td><code>stonith</code></td>\n<td>管理 fence 设备</td>\n<td><code>pcs stonith create myfence fence_ipmilan ipaddr=192.168.1.100 login=admin passwd=password lanplus=1</code> 创建 STONITH 设备</td>\n</tr>\n<tr>\n<td><code>constraint</code></td>\n<td>管理资源约束</td>\n<td><code>pcs constraint location myresource prefers node1=100</code> 设置资源约束</td>\n</tr>\n<tr>\n<td><code>property</code></td>\n<td>管理 Pacemaker 属性</td>\n<td><code>pcs property set stonith-enabled=false</code> 禁用 STONITH</td>\n</tr>\n<tr>\n<td><code>acl</code></td>\n<td>管理 Pacemaker 访问控制列表</td>\n<td><code>pcs acl role create readonly</code> 创建只读角色</td>\n</tr>\n<tr>\n<td><code>qdevice</code></td>\n<td>管理本地主机上的仲裁设备提供程序</td>\n<td><code>pcs qdevice add model net</code> 添加网络仲裁设备</td>\n</tr>\n<tr>\n<td><code>quorum</code></td>\n<td>管理集群仲裁设置</td>\n<td><code>pcs quorum status</code> 查看仲裁状态</td>\n</tr>\n<tr>\n<td><code>booth</code></td>\n<td>管理 booth (集群票据管理器)</td>\n<td><code>pcs booth status</code> 查看 booth 状态</td>\n</tr>\n<tr>\n<td><code>status</code></td>\n<td>查看集群状态</td>\n<td><code>pcs status</code> 查看集群运行状态</td>\n</tr>\n<tr>\n<td><code>config</code></td>\n<td>查看和管理集群配置</td>\n<td><code>pcs config show</code> 显示集群配置</td>\n</tr>\n<tr>\n<td><code>pcsd</code></td>\n<td>管理 pcs 守护进程</td>\n<td><code>pcs pcsd status</code> 查看 pcsd 服务状态</td>\n</tr>\n<tr>\n<td><code>node</code></td>\n<td>管理集群节点</td>\n<td><code>pcs node standby node1</code> 将节点设置为备用</td>\n</tr>\n<tr>\n<td><code>alert</code></td>\n<td>管理 Pacemaker 警报</td>\n<td><code>pcs alert create node=node1 severity=critical</code> 创建警报</td>\n</tr>\n<tr>\n<td><code>client</code></td>\n<td>管理 pcsd 客户端配置</td>\n<td><code>pcs client cert-key-gen --force</code> 生成新的客户端证书</td>\n</tr>\n</tbody></table>\n<p>此外,packmaker 还提供了其他的命令,比如 crm 的一系列工具:</p>\n<table>\n<thead>\n<tr>\n<th>工具名</th>\n<th>解释</th>\n<th>示例</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>crm_attribute</code></td>\n<td>管理集群属性,包括设置、修改或删除节点属性。</td>\n<td><code>crm_attribute --node node1 --name attr_name --update attr_value</code> 设置节点属性。</td>\n</tr>\n<tr>\n<td><code>crm_diff</code></td>\n<td>比较两个 CIB 配置文件的差异,便于配置版本管理。</td>\n<td><code>crm_diff cib_old.xml cib_new.xml</code> 比较两个 CIB 文件的差异。</td>\n</tr>\n<tr>\n<td><code>crm_error</code></td>\n<td>显示集群运行过程中遇到的错误信息,帮助排查故障。</td>\n<td><code>crm_error -s 12345</code> 显示特定错误代码的详细信息。</td>\n</tr>\n<tr>\n<td><code>crm_failcount</code></td>\n<td>查看或管理资源的失败计数,影响资源的自动重新调度。</td>\n<td><code>crm_failcount --query --resource my_resource --node node1</code> 查看失败计数。</td>\n</tr>\n<tr>\n<td><code>crm_master</code></td>\n<td>管理主从资源(如 DRBD)状态的工具,用于启动或停止主从资源。</td>\n<td><code>crm_master --promote my_resource</code> 提升资源为主状态。</td>\n</tr>\n<tr>\n<td><code>crm_mon</code></td>\n<td>实时监控集群状态,显示资源、节点、失败信息。</td>\n<td><code>crm_mon --interval=5s --show-detail</code> 每5秒更新监控,显示详细信息。</td>\n</tr>\n<tr>\n<td><code>crm_node</code></td>\n<td>管理集群节点的工具,包括查看节点状态、删除节点等。</td>\n<td><code>crm_node -l</code> 列出所有集群节点。</td>\n</tr>\n<tr>\n<td><code>crm_report</code></td>\n<td>生成集群故障报告的工具,汇总集群状态、日志和诊断信息。</td>\n<td><code>crm_report -f report.tar.bz2</code> 生成详细的故障报告。</td>\n</tr>\n<tr>\n<td><code>crm_resource</code></td>\n<td>管理集群资源,包括启动、停止、迁移和清除资源。</td>\n<td><code>crm_resource --move my_resource --node node2</code> 将资源迁移到另一个节点。</td>\n</tr>\n<tr>\n<td><code>crm_shadow</code></td>\n<td>允许对 CIB 进行“影子”配置,便于测试和调试。</td>\n<td><code>crm_shadow --create shadow_test</code> 创建影子配置。</td>\n</tr>\n<tr>\n<td><code>crm_simulate</code></td>\n<td>模拟集群运行状态的工具,用于测试集群配置的行为。</td>\n<td><code>crm_simulate --live --save-output output.xml</code> 运行模拟,并保存输出。</td>\n</tr>\n<tr>\n<td><code>crm_standby</code></td>\n<td>将节点设置为待机状态,临时不参与资源调度,或重新激活节点。</td>\n<td><code>crm_standby --node node1 --off</code> 将节点设置为待机状态。</td>\n</tr>\n<tr>\n<td><code>crm_ticket</code></td>\n<td>管理集群的 ticket,用于决定哪些资源在哪些位置可以运行(多站点集群)。</td>\n<td><code>crm_ticket --grant my_ticket --node node1</code> 授权 ticket 给指定节点。</td>\n</tr>\n<tr>\n<td><code>crm_verify</code></td>\n<td>验证当前集群配置的工具,检查配置文件的完整性和正确性。</td>\n<td><code>crm_verify --live-check</code> 验证当前运行中的集群配置。</td>\n</tr>\n</tbody></table>\n<p>pacemaker 和 crm 的命令对比:</p>\n<ol>\n<li>pcs(Pacemaker/Corosync Shell)</li>\n</ol>\n<ul>\n<li>简介: pcs 是 Pacemaker 和 Corosync 集群管理的命令行工具。它主要用于 Red Hat 系列操作系统(例如 RHEL、CentOS 等)。pcs 提供了一个简单的命令行界面,用于管理集群、资源、节点、约束等功能。</li>\n<li>功能:<ul>\n<li>管理 Pacemaker 集群、Corosync 配置、STONITH 设备、资源和约束等。</li>\n<li>提供集群的创建、启动、停止、删除、资源添加、约束设置等命令。</li>\n<li>提供简单易用的命令接口,能够将集群管理的命令封装成一步到位的操作。</li>\n<li>支持通过 pcsd 提供 Web 界面的管理。</li>\n</ul>\n</li>\n<li>适用场景: pcs 更加适用于初学者和需要快速操作的用户,因为它提供了很多高层次的命令,简化了集群管理。</li>\n</ul>\n<ol start=\"2\">\n<li>crm(Cluster Resource Manager Shell)</li>\n</ol>\n<ul>\n<li>简介: crm 是 Pacemaker 的原生命令行工具,提供更加底层的控制。crm 主要用于 Pacemaker 集群资源管理和调度,支持在更细粒度上配置和管理集群资源。</li>\n<li>功能:<ul>\n<li>提供更细致的资源管理和集群控制功能。</li>\n<li>crm 的指令可以进行更复杂的操作,比如编辑 CIB (Cluster Information Base) 的 XML 配置文件。</li>\n<li>允许更加精细的配置,适合对集群系统有深度了解的用户。</li>\n</ul>\n</li>\n<li>适用场景: crm 更加适合高级用户,特别是那些需要精确配置、排查问题或操作底层 Pacemaker 资源的场景。</li>\n</ul>\n<h2 id=\"节点认证和集群创建\"><a href=\"#节点认证和集群创建\" class=\"headerlink\" title=\"节点认证和集群创建\"></a>节点认证和集群创建</h2><p>在集群中的任意一个节点上执行:</p>\n<ol>\n<li><p>认证:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs cluster auth node2 node3 -u hacluster</span><br></pre></td></tr></table></figure></li>\n<li><p>认证完整后创建集群</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs cluster setup --name mycluster node2 node3 (同时添加所有节点)</span><br></pre></td></tr></table></figure>\n<p>创建完成后,会生成 corosync 的配置文件,默认位置<code>/etc/corosync/corosync.conf</code>, 其中的内容如下:</p>\n<figure class=\"highlight json\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">totem <span class=\"punctuation\">{</span></span><br><span class=\"line\"> version<span class=\"punctuation\">:</span> <span class=\"number\">2</span></span><br><span class=\"line\"> cluster_name<span class=\"punctuation\">:</span> mycluster</span><br><span class=\"line\"> secauth<span class=\"punctuation\">:</span> off</span><br><span class=\"line\"> transport<span class=\"punctuation\">:</span> udpu</span><br><span class=\"line\"><span class=\"punctuation\">}</span></span><br><span class=\"line\"></span><br><span class=\"line\">nodelist <span class=\"punctuation\">{</span></span><br><span class=\"line\"> node <span class=\"punctuation\">{</span></span><br><span class=\"line\"> ring0_addr<span class=\"punctuation\">:</span> node2</span><br><span class=\"line\"> nodeid<span class=\"punctuation\">:</span> <span class=\"number\">1</span></span><br><span class=\"line\"> <span class=\"punctuation\">}</span></span><br><span class=\"line\"></span><br><span class=\"line\"> node <span class=\"punctuation\">{</span></span><br><span class=\"line\"> ring0_addr<span class=\"punctuation\">:</span> node3</span><br><span class=\"line\"> nodeid<span class=\"punctuation\">:</span> <span class=\"number\">2</span></span><br><span class=\"line\"> <span class=\"punctuation\">}</span></span><br><span class=\"line\"><span class=\"punctuation\">}</span></span><br><span class=\"line\"></span><br><span class=\"line\">quorum <span class=\"punctuation\">{</span></span><br><span class=\"line\"> provider<span class=\"punctuation\">:</span> corosync_votequorum</span><br><span class=\"line\"> two_node<span class=\"punctuation\">:</span> <span class=\"number\">1</span></span><br><span class=\"line\"><span class=\"punctuation\">}</span></span><br><span class=\"line\"></span><br><span class=\"line\">logging <span class=\"punctuation\">{</span></span><br><span class=\"line\"> to_logfile<span class=\"punctuation\">:</span> yes</span><br><span class=\"line\"> logfile<span class=\"punctuation\">:</span> /var/log/cluster/corosync.log</span><br><span class=\"line\"> to_syslog<span class=\"punctuation\">:</span> yes</span><br><span class=\"line\"><span class=\"punctuation\">}</span></span><br></pre></td></tr></table></figure>\n</li>\n<li><p>启动集群</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs cluster start --all <span class=\"comment\"># 这里启动失败的话,可以后面加上 --debug参数查看更详细的信息,可能会因为防火墙等问题导致启动失败</span></span><br><span class=\"line\">pcs cluster <span class=\"built_in\">enable</span> --all <span class=\"comment\"># 设置自启动</span></span><br></pre></td></tr></table></figure></li>\n<li><p>查看集群状态</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs status</span><br></pre></td></tr></table></figure>\n<p>我们看下输出情况:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Cluster name: mycluster</span><br><span class=\"line\"></span><br><span class=\"line\">WARNINGS:</span><br><span class=\"line\">No stonith devices and stonith-enabled is not false</span><br><span class=\"line\"></span><br><span class=\"line\">Stack: corosync</span><br><span class=\"line\">Current DC: node2 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum</span><br><span class=\"line\">Last updated: Mon Sep 2 18:43:57 2024</span><br><span class=\"line\">Last change: Mon Sep 2 18:35:08 2024 by hacluster via crmd on node2</span><br><span class=\"line\"></span><br><span class=\"line\">2 nodes configured</span><br><span class=\"line\">0 resource instances configured</span><br><span class=\"line\"></span><br><span class=\"line\">Online: [ node2 node3 ]</span><br><span class=\"line\"></span><br><span class=\"line\">No resources</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">Daemon Status:</span><br><span class=\"line\"> corosync: active/disabled</span><br><span class=\"line\"> pacemaker: active/disabled</span><br><span class=\"line\"> pcsd: active/enabled</span><br></pre></td></tr></table></figure>\n<p>这里我们可以看到集群的总体情况,包括节点状态、服务状态(有两个服务还处于 disabled 状态, 通过<code>systemctl enable corosync pacemaker</code> 设置开机启动)、资源信息(还没有添加 resource)等</p>\n</li>\n</ol>\n<h2 id=\"stonith-配置\"><a href=\"#stonith-配置\" class=\"headerlink\" title=\"stonith 配置\"></a>stonith 配置</h2><p>在上文集群的状态输出中还包括了一个告警信息: <code>No stonith devices and stonith-enabled is not false</code>, 这里的 stonith(Shoot The Other Node In The Head) 是一种防止“脑裂” (split-brain) 的机制。当集群中的一个节点失去与其他节点的连接时,stonith 设备可以强制重启或关闭这个失联的节点,避免两个或多个节点同时操作同一个资源,导致数据损坏。想要消除这个告警,有两种解决方案:</p>\n<ol>\n<li>禁用 stonith: 如果是自己的测试环境,那么可以禁用掉 stonith 来消除告警,操作方法为: <figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs property <span class=\"built_in\">set</span> stonith-enabled=<span class=\"literal\">false</span></span><br></pre></td></tr></table></figure></li>\n<li>配置 stonith 设备: 在生产环境中,建议配置 stonith</li>\n</ol>\n<p>我们先根据官方文档的指示看一下stonith 有哪些可用代理:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node2 corosync]# pcs stonith list</span><br><span class=\"line\">Error: No stonith agents available. Do you have fence agents installed?</span><br></pre></td></tr></table></figure>\n<p>这里提示没有代理的 agent 可用,所以我们首先需要安装<code>fence agent</code>:</p>\n<figure class=\"highlight shell\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">yum install -y fence-agents</span><br></pre></td></tr></table></figure>\n<p>我们再 list 一下,就可以看到支持的代理了</p>\n<table>\n<thead>\n<tr>\n<th>Fence Agent</th>\n<th>描述</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>fence_amt_ws</code></td>\n<td>适用于 AMT (WS) 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_apc</code></td>\n<td>通过 telnet/ssh 控制 APC 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_apc_snmp</code></td>\n<td>适用于 APC 和 Tripplite PDU 的 SNMP Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_bladecenter</code></td>\n<td>适用于 IBM BladeCenter 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_brocade</code></td>\n<td>通过 telnet/ssh 控制 HP Brocade 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_cisco_mds</code></td>\n<td>适用于 Cisco MDS 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_cisco_ucs</code></td>\n<td>适用于 Cisco UCS 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_compute</code></td>\n<td>用于自动复活 OpenStack 计算实例的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_drac5</code></td>\n<td>适用于 Dell DRAC CMC/5 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_eaton_snmp</code></td>\n<td>适用于 Eaton 的 SNMP Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_emerson</code></td>\n<td>适用于 Emerson 的 SNMP Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_eps</code></td>\n<td>适用于 ePowerSwitch 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_evacuate</code></td>\n<td>用于自动复活 OpenStack 计算实例的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_heuristics_ping</code></td>\n<td>基于 ping 进行启发式 Fencing 的代理</td>\n</tr>\n<tr>\n<td><code>fence_hpblade</code></td>\n<td>适用于 HP BladeSystem 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ibmblade</code></td>\n<td>通过 SNMP 控制 IBM BladeCenter 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_idrac</code></td>\n<td>适用于 IPMI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ifmib</code></td>\n<td>适用于 IF MIB 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo</code></td>\n<td>适用于 HP iLO 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo2</code></td>\n<td>适用于 HP iLO2 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo3</code></td>\n<td>适用于 IPMI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo3_ssh</code></td>\n<td>通过 SSH 控制 HP iLO3 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo4</code></td>\n<td>适用于 IPMI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo4_ssh</code></td>\n<td>通过 SSH 控制 HP iLO4 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo5</code></td>\n<td>适用于 IPMI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo5_ssh</code></td>\n<td>通过 SSH 控制 HP iLO5 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo_moonshot</code></td>\n<td>适用于 HP Moonshot iLO 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo_mp</code></td>\n<td>适用于 HP iLO MP 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo_ssh</code></td>\n<td>通过 SSH 控制 HP iLO 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_imm</code></td>\n<td>适用于 IPMI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_intelmodular</code></td>\n<td>适用于 Intel Modular 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ipdu</code></td>\n<td>通过 SNMP 控制 iPDU 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ipmilan</code></td>\n<td>适用于 IPMI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_kdump</code></td>\n<td>与 kdump 崩溃恢复服务一起使用的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_mpath</code></td>\n<td>用于多路径持久保留的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_redfish</code></td>\n<td>适用于 Redfish 的 I/O Fencing 代理</td>\n</tr>\n<tr>\n<td><code>fence_rhevm</code></td>\n<td>适用于 RHEV-M REST API 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_rsa</code></td>\n<td>适用于 IBM RSA 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_rsb</code></td>\n<td>适用于 Fujitsu-Siemens RSB 的 I/O Fencing 代理</td>\n</tr>\n<tr>\n<td><code>fence_sbd</code></td>\n<td>适用于 SBD 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_scsi</code></td>\n<td>用于 SCSI 持久保留的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_virt</code></td>\n<td>适用于虚拟机的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_vmware_rest</code></td>\n<td>适用于 VMware REST API 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_vmware_soap</code></td>\n<td>通过 SOAP API 控制 VMware 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_wti</code></td>\n<td>适用于 WTI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_xvm</code></td>\n<td>适用于虚拟机的 Fence 代理</td>\n</tr>\n</tbody></table>\n<p>想查看代理的具体用法,可以使用:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs stonith describe stonith_agent</span><br></pre></td></tr></table></figure>\n\n<p>使用 <code>fence_heuristics_ping</code> 作为代理,先通过<code>pcs stonith describe fence_heuristics_ping</code> 看下具体的用法和配置</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">fence_heuristics_ping - Fence agent for ping-heuristic based fencing</span><br><span class=\"line\"></span><br><span class=\"line\">fence_heuristics_ping uses ping-heuristics to control execution of another fence agent on the same fencing level.</span><br><span class=\"line\"></span><br><span class=\"line\">This is not a fence agent by itself! Its only purpose is to enable/disable another fence agent that lives on the same fencing level but after fence_heuristics_ping.</span><br><span class=\"line\"></span><br><span class=\"line\">Stonith options:</span><br><span class=\"line\"> method: Method to fence</span><br><span class=\"line\"> ping_count: The number of ping-probes that is being sent per target</span><br><span class=\"line\"> ping_good_count: The number of positive ping-probes required to account a target as available</span><br><span class=\"line\"> ping_interval: The interval in seconds between ping-probes</span><br><span class=\"line\"> ping_maxfail: The number of failed ping-targets to still account as overall success</span><br><span class=\"line\"> ping_targets (required): A comma separated list of ping-targets (optionally prepended by 'inet:' or 'inet6:') to be probed</span><br><span class=\"line\"> ping_timeout: The timeout in seconds till an individual ping-probe is accounted as lost</span><br><span class=\"line\"> quiet: Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.</span><br><span class=\"line\"> verbose: Verbose mode</span><br><span class=\"line\"> debug: Write debug information to given file</span><br><span class=\"line\"> delay: Wait X seconds before fencing is started</span><br><span class=\"line\"> login_timeout: Wait X seconds for cmd prompt after login</span><br><span class=\"line\"> power_timeout: Test X seconds for status change after ON/OFF</span><br><span class=\"line\"> power_wait: Wait X seconds after issuing ON/OFF</span><br><span class=\"line\"> shell_timeout: Wait X seconds for cmd prompt after issuing command</span><br><span class=\"line\"> retry_on: Count of attempts to retry power on</span><br><span class=\"line\"> pcmk_host_map: A mapping of host names to ports numbers for devices that do not support host names. Eg. node1:1;node2:2,3 would tell the cluster to use port 1 for node1 and ports 2 and 3</span><br><span class=\"line\"> for node2</span><br><span class=\"line\"> pcmk_host_list: A list of machines controlled by this device (Optional unless pcmk_host_check=static-list).</span><br><span class=\"line\"> pcmk_host_check: How to determine which machines are controlled by the device. Allowed values: dynamic-list (query the device via the 'list' command), static-list (check the pcmk_host_list</span><br><span class=\"line\"> attribute), status (query the device via the 'status' command), none (assume every device can fence every machine)</span><br><span class=\"line\"> pcmk_delay_max: Enable a random delay for stonith actions and specify the maximum of random delay. This prevents double fencing when using slow devices such as sbd. Use this to enable a</span><br><span class=\"line\"> random delay for stonith actions. The overall delay is derived from this random delay value adding a static delay so that the sum is kept below the maximum delay.</span><br><span class=\"line\"> pcmk_delay_base: Enable a base delay for stonith actions and specify base delay value. This prevents double fencing when different delays are configured on the nodes. Use this to enable a</span><br><span class=\"line\"> static delay for stonith actions. The overall delay is derived from a random delay value adding this static delay so that the sum is kept below the maximum delay.</span><br><span class=\"line\"> pcmk_action_limit: The maximum number of actions can be performed in parallel on this device Pengine property concurrent-fencing=true needs to be configured first. Then use this to specify</span><br><span class=\"line\"> the maximum number of actions can be performed in parallel on this device. -1 is unlimited.</span><br><span class=\"line\"></span><br><span class=\"line\">Default operations:</span><br><span class=\"line\"> monitor: interval=60s</span><br></pre></td></tr></table></figure>\n\n<p>这里我们进行创建(其他参数都有默认值,按需修改即可):</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs stonith create my_ping_fence_device fence_heuristics_ping \\</span><br><span class=\"line\"> ping_targets=<span class=\"string\">"node2,node3"</span></span><br></pre></td></tr></table></figure>\n\n<p>创建完成后<code>pcs status</code>查看状态</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Cluster name: mycluster</span><br><span class=\"line\">Stack: corosync</span><br><span class=\"line\">Current DC: node2 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum</span><br><span class=\"line\">Last updated: Tue Sep 3 14:40:56 2024</span><br><span class=\"line\">Last change: Tue Sep 3 14:35:39 2024 by root via cibadmin on node2</span><br><span class=\"line\"></span><br><span class=\"line\">2 nodes configured</span><br><span class=\"line\">1 resource instance configured</span><br><span class=\"line\"></span><br><span class=\"line\">Online: [ node2 node3 ]</span><br><span class=\"line\"></span><br><span class=\"line\">Full list of resources:</span><br><span class=\"line\"></span><br><span class=\"line\"> my_ping_fence_device\t(stonith:fence_heuristics_ping):\tStarted node2</span><br><span class=\"line\"></span><br><span class=\"line\">Failed Fencing Actions:</span><br><span class=\"line\">* reboot of my_apc_fence_device failed: delegate=, client=stonith_admin.40341, origin=node2,</span><br><span class=\"line\"> last-failed='Tue Sep 3 14:12:23 2024'</span><br><span class=\"line\"></span><br><span class=\"line\">Daemon Status:</span><br><span class=\"line\"> corosync: active/enabled</span><br><span class=\"line\"> pacemaker: active/enabled</span><br><span class=\"line\"> pcsd: active/enabled</span><br></pre></td></tr></table></figure>\n<p>验证 stonith 是否生效:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node2 cluster]# pcs stonith fence node3</span><br><span class=\"line\">Node: node3 fenced</span><br></pre></td></tr></table></figure>\n<p>执行完这个操作后,节点会离线,pcs 服务会停止,想要加回来的话,在停止的节点上重新启动集群即可:<code>pcs cluster start && pcs cluster enable</code></p>\n<h1 id=\"实战操作\"><a href=\"#实战操作\" class=\"headerlink\" title=\"实战操作\"></a>实战操作</h1><h2 id=\"添加节点\"><a href=\"#添加节点\" class=\"headerlink\" title=\"添加节点\"></a>添加节点</h2><p>上文中,我们构建了一个两节点的集群,我们可以尝试增加一个节点,构建一个三节点的集群</p>\n<ol>\n<li>首先在新节点上安装各种依赖,设置密码等。</li>\n<li>在原集群上认证新 node</li>\n<li>在原集群上添加 node:<code>pcs cluster node add node4</code></li>\n<li>在 node4 上执行:<code>pcs cluster start && pcs cluster enable</code></li>\n</ol>\n<p>再通过<code>pcs status</code>就可以看到新的节点已经加入</p>\n<p>添加完之后还需要更新新节点的一些配置,比如上文提到的 stonith:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs stonith update my_ping_fence_device fence_heuristics_ping \\</span><br><span class=\"line\"> ping_targets=<span class=\"string\">"node2,node3,node4"</span></span><br></pre></td></tr></table></figure>\n\n<h2 id=\"配置-resource\"><a href=\"#配置-resource\" class=\"headerlink\" title=\"配置 resource\"></a>配置 resource</h2><h3 id=\"资源类型\"><a href=\"#资源类型\" class=\"headerlink\" title=\"资源类型\"></a>资源类型</h3><p>创建 resource 的基本格式为:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs resource create resource-name ocf:heartbeat:apache [--options]</span><br></pre></td></tr></table></figure>\n<p>这里的 ocf:heartbeat:apache 第一个部分ocf,指明了这个资源采用的标准(类型),第二个部分标明这个资源脚本的在ocf中的名字空间,在这个例子中是heartbeat。最后一个部分指明了资源脚本的名称。</p>\n<p>我们先看下有哪些标准类型</p>\n<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node3 ~]# pcs resource standards</span><br><span class=\"line\">lsb</span><br><span class=\"line\">ocf</span><br><span class=\"line\">service</span><br><span class=\"line\">systemd</span><br></pre></td></tr></table></figure>\n<p>查看可用的ocf资源提供者:</p>\n<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node3 ~]# pcs resource providers</span><br><span class=\"line\">heartbeat</span><br><span class=\"line\">openstack</span><br><span class=\"line\">pacemaker</span><br></pre></td></tr></table></figure>\n<p>查看特定标准下所支持的脚本,例:ofc:heartbeat 下的脚本(列举了部分):</p>\n<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node3 ~]# pcs resource agents ocf:heartbeat</span><br><span class=\"line\">aliyun-vpc-move-ip</span><br><span class=\"line\">apache</span><br><span class=\"line\">aws-vpc-move-ip</span><br><span class=\"line\">aws-vpc-route53</span><br><span class=\"line\">awseip</span><br><span class=\"line\">awsvip</span><br><span class=\"line\">azure-events</span><br><span class=\"line\">azure-lb</span><br><span class=\"line\">clvm</span><br><span class=\"line\">conntrackd</span><br><span class=\"line\">CTDB</span><br><span class=\"line\">db2</span><br><span class=\"line\">Delay</span><br><span class=\"line\">dhcpd</span><br><span class=\"line\">docker</span><br><span class=\"line\">Dummy</span><br><span class=\"line\">ethmonitor</span><br><span class=\"line\">exportfs</span><br><span class=\"line\">Filesystem</span><br><span class=\"line\">galera</span><br><span class=\"line\">garbd</span><br><span class=\"line\">iface-vlan</span><br><span class=\"line\">IPaddr</span><br><span class=\"line\">IPaddr2</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"设置虚拟-ip\"><a href=\"#设置虚拟-ip\" class=\"headerlink\" title=\"设置虚拟 ip\"></a>设置虚拟 ip</h3><p>虚拟 IP(Virtual IP)是在高可用性集群中使用的一种技术,通过为服务提供一个不依赖于特定物理节点的 IP 地址来实现服务的高可用性。当集群中的某个节点出现故障时,虚拟 IP 可以迅速转移到另一个健康的节点上,从而保证服务的连续性。</p>\n<p>虚拟 IP 的使用场景</p>\n<ol>\n<li>高可用性:虚拟 IP 最常见的使用场景是高可用性集群(如 Pacemaker 或 Keepalived),它允许一个服务在集群中的多个节点之间进行切换,而不会更改客户端访问的 IP 地址。</li>\n<li>负载均衡:虚拟 IP 可以结合负载均衡器使用,将来自客户端的请求分配到多个后端服务器,以实现流量的均匀分布。</li>\n<li>灾难恢复:在灾难恢复场景中,虚拟 IP 可以用于快速恢复服务,将业务流量从故障节点转移到备用节点上</li>\n</ol>\n<p>在 pcs 集群中,我们可以通过以下方式增加一个虚拟 ip:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs resource create virtual_ip ocf:heartbeat:IPaddr2 ip=x.x.x.x cidr_netmask=32 nic=bond1 op monitor interval=30s</span><br></pre></td></tr></table></figure>\n<p>执行完成后,通过<code>pcs status</code>就可以看到 ip 绑定在哪里:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node3</span><br></pre></td></tr></table></figure>\n<p>当我们关停 node3 的服务时,就会发现这个虚拟ip 绑定到了其他节点的 bond1 网卡上。</p>\n<h3 id=\"增加服务\"><a href=\"#增加服务\" class=\"headerlink\" title=\"增加服务\"></a>增加服务</h3><p>我们以 httpd 服务为例,在集群中创建资源,首先安装对应服务:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"built_in\">sudo</span> yum install httpd -y</span><br><span class=\"line\"><span class=\"built_in\">sudo</span> systemctl start httpd <span class=\"comment\"># 这里可选择不启动,后续如果通过pcs 直接托管,需要先停掉,</span></span><br><span class=\"line\"><span class=\"built_in\">sudo</span> systemctl <span class=\"built_in\">enable</span> httpd </span><br></pre></td></tr></table></figure>\n\n<p>创建 resource:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs resource create WebService ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"结合-LVS-ldirectord-进行使用\"><a href=\"#结合-LVS-ldirectord-进行使用\" class=\"headerlink\" title=\"结合 LVS + ldirectord 进行使用\"></a>结合 LVS + ldirectord 进行使用</h2><p>如果环境是一套多节点集群,在生产中我们肯定需要充分利用起这些节点,所以就要考虑流量分发。在这一层面上,我们可以使用 lvs 进行流量分发。这里首先对 lvs 对一个简单的介绍</p>\n<p>LVS(Linux Virtual Server)是一个基于 IP 负载均衡技术的开源软件项目,主要用于构建高可用、高性能的负载均衡集群。LVS 是 Linux 内核的一部分,通过网络层的负载均衡技术,将来自客户端的请求分发到多个后端服务器,从而实现分布式处理、提高系统的处理能力和可靠性。</p>\n<p>LVS 主要通过三种负载均衡模式(NAT 模式、DR 模式、TUN 模式)来实现流量的分发,支持大规模并发请求的处理,通常用于大型网站、电子商务平台和高访问量的 Web 应用中。</p>\n<p>LVS 的特点</p>\n<ol>\n<li>高性能: LVS 工作在网络层(第4层),基于 IP 进行流量转发,性能极高。它能够处理大量的并发连接,适合高流量、大规模的网站和服务。</li>\n<li>高可用性: LVS 通常与 Keepalived、Pacemaker 等高可用性工具配合使用,以实现负载均衡器的自动故障切换,确保服务的高可用性和稳定性。</li>\n<li>多种负载均衡算法: LVS 提供了多种负载均衡算法,如轮询(Round Robin)、最小连接(Least Connection)、基于目标地址哈希(Destination Hashing)等,可以根据具体需求选择合适的算法进行流量分发。</li>\n<li>多种工作模式, LVS 支持三种主要工作模式:<ul>\n<li> NAT 模式(网络地址转换模式):LVS 充当请求和响应的中介,适用于小规模集群。</li>\n<li> DR 模式(直接路由模式):请求由 LVS 转发,但响应直接返回给客户端,适用于大型集群,性能高。</li>\n<li> TUN 模式(IP 隧道模式):类似于 DR 模式,但支持跨网络部署,非常适合广域网负载均衡。</li>\n</ul>\n</li>\n<li>高扩展性: LVS 可以轻松地扩展和管理多台服务器,支持动态添加或移除后端服务器,适应业务需求的变化,且不影响服务的正常运行。</li>\n<li>透明性: 对客户端和后端服务器来说,LVS 的存在是透明的。客户端并不感知负载均衡的存在,访问体验一致。后端服务器也不需要做特殊的配置,只需处理 LVS 转发的请求。</li>\n<li>成熟且稳定: 作为一个成熟的负载均衡解决方案,LVS 被广泛应用于生产环境中,经过多年发展,功能完备,稳定性高。</li>\n<li>安全性: LVS 可以与防火墙等安全工具结合使用,增强系统的安全性。此外,LVS 还支持 IP 地址过滤、端口过滤等功能,提供一定程度的安全保护。</li>\n</ol>\n<p>ldirectord 是一个守护进程,用于管理和监控由 LVS 提供的虚拟服务(Virtual Services)。其主要功能包括:</p>\n<ol>\n<li> 监控后端服务器:ldirectord 定期检查后端服务器的健康状况,确保只有健康的服务器参与流量分配。</li>\n<li> 动态配置:基于后端服务器的健康状况,ldirectord 可以动态调整 LVS 的配置。例如,当一台服务器宕机时,ldirectord 会自动将其从 LVS 配置中移除。</li>\n<li> 高可用性:结合 heartbeat 等高可用性工具,ldirectord 可以确保在主节点故障时,负载均衡服务能够自动切换到备用节点,继续提供服务。</li>\n</ol>\n<h3 id=\"安装部署-1\"><a href=\"#安装部署-1\" class=\"headerlink\" title=\"安装部署\"></a>安装部署</h3><p>安装lvs:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">yum install lvm2 ipvsadm -y</span><br></pre></td></tr></table></figure>\n<p>在这里找包有一些技巧,比如一开始 chatgpt 提供的说法是要安装<code>lvs</code> 和 <code>ipvsadm</code>,但是在我的环境上通过<code>yum install -y lvs</code> 的时候提示没有这个包,那我们可以通过 yum 提供的一些命令来简单锁定一下,比如 <code>yum provides lvs</code>,这样就会把包含了这个命令的包显示出来(适用于知道命令但是不知道是哪个包的场景),</p>\n<p>安装 ldirector:</p>\n<figure class=\"highlight plaintext\"><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><td class=\"code\"><pre><span class=\"line\"># 这里我用 yum 下载是没有找到对应包的,找了一圈也没找到安装方法,所以直接找的 rpm 包</span><br><span class=\"line\"># 下载地址: ftp://ftp.icm.edu.pl/vol/rzm3/linux-opensuse/update/leap/15.2/oss/x86_64/ldirectord-4.4.0+git57.70549516-lp152.2.9.1.x86_64.rpm</span><br><span class=\"line\"># 上传到机器上后,进行安装</span><br><span class=\"line\">rpm -Uvh --force ldirectord-4.4.0+git57.70549516-lp152.2.9.1.x86_64.rpm</span><br><span class=\"line\"></span><br><span class=\"line\"># 需要依赖,先安装依赖,再装包</span><br><span class=\"line\">yum install -y perl-IO-Socket-INET6 perl-MailTools perl-Net-SSLeay perl-Socket6 perl-libwww-perl</span><br><span class=\"line\"># 操作完成之后,启动服务</span><br><span class=\"line\">systemctl start ldirectord.service</span><br></pre></td></tr></table></figure>\n<p>服务启动失败:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/ldirectord.png\" alt=\"ldirectord服务\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">ldirectord服务</span></div></div>\n\n<p>这里有点坑,缺少了依赖的文件,但是装包的时候没有提示,需要再安装: <code>yum install -y perl-Sys-Syslog</code>, 安装完成后此问题消失,但是此时配置文件还没配置,所以服务还起不来。</p>\n<h3 id=\"pcs-结合-lvs、ldirectord\"><a href=\"#pcs-结合-lvs、ldirectord\" class=\"headerlink\" title=\"pcs 结合 lvs、ldirectord\"></a>pcs 结合 lvs、ldirectord</h3><p>在上文中,我们创建了一个 httpd 服务和 vip 资源。 在实际生产中,要充分利用节点性能,我们可能要在多个节点上启动httpd 示例,我们在每个节点上都启动一个实例,然后将他们归到一个组中:</p>\n<figure class=\"highlight bash\"><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><td class=\"code\"><pre><span class=\"line\">pcs resource delete WebService <span class=\"comment\"># 移除之前创建的服务</span></span><br><span class=\"line\">pcs resource create WebService1 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s</span><br><span class=\"line\">pcs resource create WebService2 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s --force</span><br><span class=\"line\">pcs resource create WebService3 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s --force <span class=\"comment\"># 创建三个服务</span></span><br><span class=\"line\"></span><br><span class=\"line\">pcs constraint location WebService1 prefers node2</span><br><span class=\"line\">pcs constraint location WebService2 prefers node3</span><br><span class=\"line\">pcs constraint location WebService3 prefers node4 <span class=\"comment\"># 限制对应 resource 服务只能在指定节点上运行</span></span><br></pre></td></tr></table></figure>\n\n<p>配置 ldirectord:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">checktimeout=10</span><br><span class=\"line\">checkinterval=2</span><br><span class=\"line\">autoreload=yes</span><br><span class=\"line\">logfile="/var/log/ldirectord.log"</span><br><span class=\"line\">quiescent=yes</span><br><span class=\"line\"></span><br><span class=\"line\">virtual=vip:80 # 之前绑定的 VIP</span><br><span class=\"line\"> real=192.168.1.2:80 gate</span><br><span class=\"line\"> real=192.168.1.3:80 gate</span><br><span class=\"line\"> real=192.168.1.4:80 gate</span><br><span class=\"line\"> fallback=127.0.0.1:80</span><br><span class=\"line\"> service=http</span><br><span class=\"line\"> request="index.html"</span><br><span class=\"line\"> receive="HTTP/1.1 200 OK"</span><br><span class=\"line\"> scheduler=rr</span><br><span class=\"line\"> protocol=tcp</span><br><span class=\"line\"> checktype=negotiate</span><br></pre></td></tr></table></figure>\n\n<p>然后在通过 <code>ipvsadm -ln</code> 就可以查看到详细的信息:</p>\n<figure class=\"highlight bash\"><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><td class=\"code\"><pre><span class=\"line\">IP Virtual Server version 1.2.1 (size=4096)</span><br><span class=\"line\">Prot LocalAddress:Port Scheduler Flags</span><br><span class=\"line\"> -> RemoteAddress:Port Forward Weight ActiveConn InActConn</span><br><span class=\"line\">TCP vip:80 rr</span><br><span class=\"line\"> -> 192.168.1.2:80 Route 0 0 0</span><br><span class=\"line\"> -> 192.168.1.3:80 Route 0 0 0</span><br><span class=\"line\"> -> 192.168.1.4:80 Route 0 0 0</span><br><span class=\"line\"> -> 127.0.0.1:80 Route 1 0 0</span><br></pre></td></tr></table></figure>\n\n<p>然后我们可以在 pcs 上创建一个资源 lvs 相关的资源:</p>\n<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">pcs resource create my_lvs ocf:heartbeat:ldirectord \\</span><br><span class=\"line\"> configfile=/etc/ha.d/ldirectord.cf \\</span><br><span class=\"line\"> ldirectord=/usr/sbin/ldirectord \\</span><br><span class=\"line\"> op monitor interval=15s <span class=\"built_in\">timeout</span>=60s \\</span><br><span class=\"line\"> op stop <span class=\"built_in\">timeout</span>=60s</span><br></pre></td></tr></table></figure>\n<p>这里的ocf:heartbeat:ldirectord 在有的版本中会默认安装,有的版本不会,如果没有的话需要手动下载: <a href=\"https://github.com/ClusterLabs/resource-agents/blob/main/ldirectord/OCF/ldirectord.in\">https://github.com/ClusterLabs/resource-agents/blob/main/ldirectord/OCF/ldirectord.in</a><br>存放到: <code>/usr/lib/ocf/resource.d/heartbeat/ldirectord</code> 并添加可执行权限: <code>chmod +x /usr/lib/ocf/resource.d/heartbeat/ldirectord</code></p>\n<p>创建完成后,我们可以将 vip 和 lvs 绑定到一个组中,这样 lvs 就会跟着 vip 进行转移了:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs resource group add balanceGroup virtual_ip my_lvs</span><br></pre></td></tr></table></figure>\n<p>通过<code>pcs status</code>查看就可以看到:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">Resource Group: balanceGroup</span><br><span class=\"line\"> virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node2</span><br><span class=\"line\"> my_lvs\t(ocf::heartbeat:ldirectord):\tStarted node2</span><br></pre></td></tr></table></figure>\n<p>不断对节点进行关闭测试,可以看到 lvs 和 vip 始终都在同一个节点上</p>\n<h2 id=\"增加节点属性\"><a href=\"#增加节点属性\" class=\"headerlink\" title=\"增加节点属性\"></a>增加节点属性</h2><p>我们这里使用另外一个观察集群状态的命令:<code>crm_mon</code>, 比如<code>crm_mon -A1</code></p>\n<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node2 rpm]# crm_mon -A1</span><br><span class=\"line\">Stack: corosync</span><br><span class=\"line\">Current DC: node3 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum</span><br><span class=\"line\">Last updated: Thu Sep 5 22:09:53 2024</span><br><span class=\"line\">Last change: Wed Sep 4 18:17:25 2024 by root via cibadmin on node3</span><br><span class=\"line\"></span><br><span class=\"line\">3 nodes configured</span><br><span class=\"line\">6 resource instances configured</span><br><span class=\"line\"></span><br><span class=\"line\">Online: [ node2 node3 node4 ]</span><br><span class=\"line\"></span><br><span class=\"line\">Active resources:</span><br><span class=\"line\"></span><br><span class=\"line\"> my_ping_fence_device\t(stonith:fence_heuristics_ping):\tStarted node3</span><br><span class=\"line\"> WebService1\t(ocf::heartbeat:apache):\tStarted node4</span><br><span class=\"line\"> WebService2\t(ocf::heartbeat:apache):\tStarted node3</span><br><span class=\"line\"> WebService3\t(ocf::heartbeat:apache):\tStarted node4</span><br><span class=\"line\"> Resource Group: balanceGroup</span><br><span class=\"line\"> virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node2</span><br><span class=\"line\"> my_lvs\t(ocf::heartbeat:ldirectord):\tStarted node2</span><br><span class=\"line\"></span><br><span class=\"line\">Node Attributes:</span><br><span class=\"line\">* Node node2:</span><br><span class=\"line\">* Node node3:</span><br><span class=\"line\">* Node node4:</span><br><span class=\"line\">.....</span><br></pre></td></tr></table></figure>\n<p>输出和<code>pcs status</code>查看到的效果基本上是差不多的。但是在下面有<code>Node Attributes</code>,这里我们看下节点属性怎么设置:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs node attribute node2 role=master</span><br><span class=\"line\">pcs node attribute node3 role=standby</span><br><span class=\"line\">pcs node attribute node4 role=standby</span><br></pre></td></tr></table></figure>\n<p>或者</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">crm_attribute --node node2 --name mysql --update master</span><br><span class=\"line\">crm_attribute --node node3 --name mysql --update standby</span><br></pre></td></tr></table></figure>\n<p>设置完成之后,我们就可以看到节点属性:</p>\n<figure class=\"highlight plaintext\"><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><td class=\"code\"><pre><span class=\"line\">Node Attributes:</span><br><span class=\"line\">* Node node2:</span><br><span class=\"line\"> + mysql \t: master</span><br><span class=\"line\"> + role \t: master</span><br><span class=\"line\">* Node node3:</span><br><span class=\"line\"> + mysql \t: standby</span><br><span class=\"line\"> + role \t: standby</span><br><span class=\"line\">* Node node4:</span><br><span class=\"line\"> + role \t: standby</span><br></pre></td></tr></table></figure>\n<p>那有人就会好奇这样设置有什么用呢?主要用途是在哪里呢。</p>\n<p>这里的指标往往是动态的,可以根据自己喜好结合一些扩展进行变化,比如部署了一套 postgresql 集群,集群中有主有备,有同步节点也有异步节点,有的节点状态可能有问题,那我们怎么能够显示出这个集群的整体情况呢,这样就可以使用 Node Attributes进行设置,关于如果搭建 pcs + postgresql 的集群,大家可以参考这篇文章: <a href=\"https://www.cnblogs.com/Alicebat/p/14148933.html\">基于Pacemaker的PostgreSQL高可用集群</a></p>\n<p>最终我们看到的效果如下:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Node Attributes:</span><br><span class=\"line\">* Node pg01:</span><br><span class=\"line\"> + master-pgsql \t: 1000 </span><br><span class=\"line\"> + pgsql-data-status \t: LATEST </span><br><span class=\"line\"> + pgsql-master-baseline \t: 0000000008000098</span><br><span class=\"line\"> + pgsql-status \t: PRI </span><br><span class=\"line\">* Node pg02:</span><br><span class=\"line\"> + master-pgsql \t: -INFINITY </span><br><span class=\"line\"> + pgsql-data-status \t: STREAMING|ASYNC</span><br><span class=\"line\"> + pgsql-status \t: HS:async </span><br><span class=\"line\">* Node pg03:</span><br><span class=\"line\"> + master-pgsql \t: 100 </span><br><span class=\"line\"> + pgsql-data-status \t: STREAMING|SYNC</span><br><span class=\"line\"> + pgsql-status \t: HS:sync </span><br></pre></td></tr></table></figure>\n<p>当集群发生节点变动,状态异常时,我们就可以根据 attibutes 的一些信息查看定位。</p>\n","excerpt":"","more":"<div class=\"tag-plugin quot\"><p class=\"content\" type=\"text\"><span class=\"empty\"></span><span class=\"text\">参考文档</span><span class=\"empty\"></span></p></div>\n\n<p><a href=\"https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/7/html/high_availability_add-on_reference/index\">红帽官方文档</a></p>\n<h1 id=\"介绍\"><a href=\"#介绍\" class=\"headerlink\" title=\"介绍\"></a>介绍</h1><p>pacemaker 和 corosync是两种开源软件组件,通常结合使用以构建高可用性(HA)集群。</p>\n<h2 id=\"Pacemaker\"><a href=\"#Pacemaker\" class=\"headerlink\" title=\"Pacemaker\"></a>Pacemaker</h2><p>Pacemaker 是一个集群资源管理器,负责管理集群中所有资源的启动、停止、迁移等操作。它通过与 Corosync 协作,确保在节点故障或服务异常时,资源能够自动在其他健康节点上接管。从这里我们就可以发现,pacemaker 的核心在于管理</p>\n<h3 id=\"组件\"><a href=\"#组件\" class=\"headerlink\" title=\"组件\"></a>组件</h3><p>pacemaker 主要包括以下组件:</p>\n<ul>\n<li> CIB (Cluster Information Base):存储集群的配置信息,包括资源、约束、节点等。</li>\n<li> CRM (Cluster Resource Manager):决定如何在集群中分配和管理资源。</li>\n<li> PEngine (Policy Engine):根据集群状态和配置策略做出决策。</li>\n<li> Fencing:通过 STONITH(Shoot The Other Node In The Head)机制来隔离失效的节点,防止脑裂。</li>\n</ul>\n<h3 id=\"使用场景\"><a href=\"#使用场景\" class=\"headerlink\" title=\"使用场景\"></a>使用场景</h3><ul>\n<li> 管理集群中的各种资源(如虚拟 IP、数据库服务、文件系统等)。</li>\n<li> 确保服务的高可用性,在故障发生时自动切换资源到其他节点。</li>\n</ul>\n<h2 id=\"Corosync\"><a href=\"#Corosync\" class=\"headerlink\" title=\"Corosync\"></a>Corosync</h2><p>Corosync 是一个集群通信引擎,负责在集群节点之间提供消息传递、组成员资格管理、心跳检测等功能。它确保集群中所有节点之间的信息同步,监控节点的健康状况,并在节点故障时通知 pacemaker。</p>\n<h3 id=\"组件-1\"><a href=\"#组件-1\" class=\"headerlink\" title=\"组件\"></a>组件</h3><ul>\n<li> 组通信:用于确保集群中所有节点保持一致的视图。</li>\n<li> 故障检测:通过心跳机制监控节点状态,当节点失联时,通知 Pacemaker。</li>\n<li> 配置管理:管理集群节点的配置和成员资格。</li>\n</ul>\n<h3 id=\"使用场景-1\"><a href=\"#使用场景-1\" class=\"headerlink\" title=\"使用场景\"></a>使用场景</h3><ul>\n<li> 集群中节点间的实时通信。</li>\n<li> 监控节点的可用性,并在节点失效时做出响应。</li>\n</ul>\n<h1 id=\"安装部署\"><a href=\"#安装部署\" class=\"headerlink\" title=\"安装部署\"></a>安装部署</h1><h2 id=\"安装依赖\"><a href=\"#安装依赖\" class=\"headerlink\" title=\"安装依赖\"></a>安装依赖</h2><ol>\n<li>在集群的所有节点上安装相关依赖:<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">yum install -y pcs pacemaker corosync # Centos</span><br></pre></td></tr></table></figure></li>\n<li>启动相关服务并设置服务开机自启动:<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">systemctl start pcsd </span><br><span class=\"line\">systemctl enable pcsd</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure></li>\n<li>设置<code>hacluster</code>用户的密码(此用户在包安装的过程中会自动创建)<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">sudo passwd hacluster</span><br></pre></td></tr></table></figure></li>\n<li>在 <code>/etc/hosts</code> 中加入节点配置,例如:<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">192.168.1.2 node2</span><br><span class=\"line\">192.168.1.3 node3</span><br></pre></td></tr></table></figure></li>\n</ol>\n<h2 id=\"命令操作\"><a href=\"#命令操作\" class=\"headerlink\" title=\"命令操作\"></a>命令操作</h2><p>集群的命令行操作基本上都是通过 pcs 进行,pcs 提供了如下一些命令:</p>\n<table>\n<thead>\n<tr>\n<th>命令</th>\n<th>说明</th>\n<th>示例命令</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>cluster</code></td>\n<td>配置集群选项和节点</td>\n<td><code>pcs cluster start</code> 启动集群</td>\n</tr>\n<tr>\n<td><code>resource</code></td>\n<td>管理集群资源</td>\n<td><code>pcs resource create myresource ocf:heartbeat:IPaddr2 ip=192.168.1.1</code> 创建一个资源</td>\n</tr>\n<tr>\n<td><code>stonith</code></td>\n<td>管理 fence 设备</td>\n<td><code>pcs stonith create myfence fence_ipmilan ipaddr=192.168.1.100 login=admin passwd=password lanplus=1</code> 创建 STONITH 设备</td>\n</tr>\n<tr>\n<td><code>constraint</code></td>\n<td>管理资源约束</td>\n<td><code>pcs constraint location myresource prefers node1=100</code> 设置资源约束</td>\n</tr>\n<tr>\n<td><code>property</code></td>\n<td>管理 Pacemaker 属性</td>\n<td><code>pcs property set stonith-enabled=false</code> 禁用 STONITH</td>\n</tr>\n<tr>\n<td><code>acl</code></td>\n<td>管理 Pacemaker 访问控制列表</td>\n<td><code>pcs acl role create readonly</code> 创建只读角色</td>\n</tr>\n<tr>\n<td><code>qdevice</code></td>\n<td>管理本地主机上的仲裁设备提供程序</td>\n<td><code>pcs qdevice add model net</code> 添加网络仲裁设备</td>\n</tr>\n<tr>\n<td><code>quorum</code></td>\n<td>管理集群仲裁设置</td>\n<td><code>pcs quorum status</code> 查看仲裁状态</td>\n</tr>\n<tr>\n<td><code>booth</code></td>\n<td>管理 booth (集群票据管理器)</td>\n<td><code>pcs booth status</code> 查看 booth 状态</td>\n</tr>\n<tr>\n<td><code>status</code></td>\n<td>查看集群状态</td>\n<td><code>pcs status</code> 查看集群运行状态</td>\n</tr>\n<tr>\n<td><code>config</code></td>\n<td>查看和管理集群配置</td>\n<td><code>pcs config show</code> 显示集群配置</td>\n</tr>\n<tr>\n<td><code>pcsd</code></td>\n<td>管理 pcs 守护进程</td>\n<td><code>pcs pcsd status</code> 查看 pcsd 服务状态</td>\n</tr>\n<tr>\n<td><code>node</code></td>\n<td>管理集群节点</td>\n<td><code>pcs node standby node1</code> 将节点设置为备用</td>\n</tr>\n<tr>\n<td><code>alert</code></td>\n<td>管理 Pacemaker 警报</td>\n<td><code>pcs alert create node=node1 severity=critical</code> 创建警报</td>\n</tr>\n<tr>\n<td><code>client</code></td>\n<td>管理 pcsd 客户端配置</td>\n<td><code>pcs client cert-key-gen --force</code> 生成新的客户端证书</td>\n</tr>\n</tbody></table>\n<p>此外,packmaker 还提供了其他的命令,比如 crm 的一系列工具:</p>\n<table>\n<thead>\n<tr>\n<th>工具名</th>\n<th>解释</th>\n<th>示例</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>crm_attribute</code></td>\n<td>管理集群属性,包括设置、修改或删除节点属性。</td>\n<td><code>crm_attribute --node node1 --name attr_name --update attr_value</code> 设置节点属性。</td>\n</tr>\n<tr>\n<td><code>crm_diff</code></td>\n<td>比较两个 CIB 配置文件的差异,便于配置版本管理。</td>\n<td><code>crm_diff cib_old.xml cib_new.xml</code> 比较两个 CIB 文件的差异。</td>\n</tr>\n<tr>\n<td><code>crm_error</code></td>\n<td>显示集群运行过程中遇到的错误信息,帮助排查故障。</td>\n<td><code>crm_error -s 12345</code> 显示特定错误代码的详细信息。</td>\n</tr>\n<tr>\n<td><code>crm_failcount</code></td>\n<td>查看或管理资源的失败计数,影响资源的自动重新调度。</td>\n<td><code>crm_failcount --query --resource my_resource --node node1</code> 查看失败计数。</td>\n</tr>\n<tr>\n<td><code>crm_master</code></td>\n<td>管理主从资源(如 DRBD)状态的工具,用于启动或停止主从资源。</td>\n<td><code>crm_master --promote my_resource</code> 提升资源为主状态。</td>\n</tr>\n<tr>\n<td><code>crm_mon</code></td>\n<td>实时监控集群状态,显示资源、节点、失败信息。</td>\n<td><code>crm_mon --interval=5s --show-detail</code> 每5秒更新监控,显示详细信息。</td>\n</tr>\n<tr>\n<td><code>crm_node</code></td>\n<td>管理集群节点的工具,包括查看节点状态、删除节点等。</td>\n<td><code>crm_node -l</code> 列出所有集群节点。</td>\n</tr>\n<tr>\n<td><code>crm_report</code></td>\n<td>生成集群故障报告的工具,汇总集群状态、日志和诊断信息。</td>\n<td><code>crm_report -f report.tar.bz2</code> 生成详细的故障报告。</td>\n</tr>\n<tr>\n<td><code>crm_resource</code></td>\n<td>管理集群资源,包括启动、停止、迁移和清除资源。</td>\n<td><code>crm_resource --move my_resource --node node2</code> 将资源迁移到另一个节点。</td>\n</tr>\n<tr>\n<td><code>crm_shadow</code></td>\n<td>允许对 CIB 进行“影子”配置,便于测试和调试。</td>\n<td><code>crm_shadow --create shadow_test</code> 创建影子配置。</td>\n</tr>\n<tr>\n<td><code>crm_simulate</code></td>\n<td>模拟集群运行状态的工具,用于测试集群配置的行为。</td>\n<td><code>crm_simulate --live --save-output output.xml</code> 运行模拟,并保存输出。</td>\n</tr>\n<tr>\n<td><code>crm_standby</code></td>\n<td>将节点设置为待机状态,临时不参与资源调度,或重新激活节点。</td>\n<td><code>crm_standby --node node1 --off</code> 将节点设置为待机状态。</td>\n</tr>\n<tr>\n<td><code>crm_ticket</code></td>\n<td>管理集群的 ticket,用于决定哪些资源在哪些位置可以运行(多站点集群)。</td>\n<td><code>crm_ticket --grant my_ticket --node node1</code> 授权 ticket 给指定节点。</td>\n</tr>\n<tr>\n<td><code>crm_verify</code></td>\n<td>验证当前集群配置的工具,检查配置文件的完整性和正确性。</td>\n<td><code>crm_verify --live-check</code> 验证当前运行中的集群配置。</td>\n</tr>\n</tbody></table>\n<p>pacemaker 和 crm 的命令对比:</p>\n<ol>\n<li>pcs(Pacemaker/Corosync Shell)</li>\n</ol>\n<ul>\n<li>简介: pcs 是 Pacemaker 和 Corosync 集群管理的命令行工具。它主要用于 Red Hat 系列操作系统(例如 RHEL、CentOS 等)。pcs 提供了一个简单的命令行界面,用于管理集群、资源、节点、约束等功能。</li>\n<li>功能:<ul>\n<li>管理 Pacemaker 集群、Corosync 配置、STONITH 设备、资源和约束等。</li>\n<li>提供集群的创建、启动、停止、删除、资源添加、约束设置等命令。</li>\n<li>提供简单易用的命令接口,能够将集群管理的命令封装成一步到位的操作。</li>\n<li>支持通过 pcsd 提供 Web 界面的管理。</li>\n</ul>\n</li>\n<li>适用场景: pcs 更加适用于初学者和需要快速操作的用户,因为它提供了很多高层次的命令,简化了集群管理。</li>\n</ul>\n<ol start=\"2\">\n<li>crm(Cluster Resource Manager Shell)</li>\n</ol>\n<ul>\n<li>简介: crm 是 Pacemaker 的原生命令行工具,提供更加底层的控制。crm 主要用于 Pacemaker 集群资源管理和调度,支持在更细粒度上配置和管理集群资源。</li>\n<li>功能:<ul>\n<li>提供更细致的资源管理和集群控制功能。</li>\n<li>crm 的指令可以进行更复杂的操作,比如编辑 CIB (Cluster Information Base) 的 XML 配置文件。</li>\n<li>允许更加精细的配置,适合对集群系统有深度了解的用户。</li>\n</ul>\n</li>\n<li>适用场景: crm 更加适合高级用户,特别是那些需要精确配置、排查问题或操作底层 Pacemaker 资源的场景。</li>\n</ul>\n<h2 id=\"节点认证和集群创建\"><a href=\"#节点认证和集群创建\" class=\"headerlink\" title=\"节点认证和集群创建\"></a>节点认证和集群创建</h2><p>在集群中的任意一个节点上执行:</p>\n<ol>\n<li><p>认证:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs cluster auth node2 node3 -u hacluster</span><br></pre></td></tr></table></figure></li>\n<li><p>认证完整后创建集群</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs cluster setup --name mycluster node2 node3 (同时添加所有节点)</span><br></pre></td></tr></table></figure>\n<p>创建完成后,会生成 corosync 的配置文件,默认位置<code>/etc/corosync/corosync.conf</code>, 其中的内容如下:</p>\n<figure class=\"highlight json\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">totem <span class=\"punctuation\">{</span></span><br><span class=\"line\"> version<span class=\"punctuation\">:</span> <span class=\"number\">2</span></span><br><span class=\"line\"> cluster_name<span class=\"punctuation\">:</span> mycluster</span><br><span class=\"line\"> secauth<span class=\"punctuation\">:</span> off</span><br><span class=\"line\"> transport<span class=\"punctuation\">:</span> udpu</span><br><span class=\"line\"><span class=\"punctuation\">}</span></span><br><span class=\"line\"></span><br><span class=\"line\">nodelist <span class=\"punctuation\">{</span></span><br><span class=\"line\"> node <span class=\"punctuation\">{</span></span><br><span class=\"line\"> ring0_addr<span class=\"punctuation\">:</span> node2</span><br><span class=\"line\"> nodeid<span class=\"punctuation\">:</span> <span class=\"number\">1</span></span><br><span class=\"line\"> <span class=\"punctuation\">}</span></span><br><span class=\"line\"></span><br><span class=\"line\"> node <span class=\"punctuation\">{</span></span><br><span class=\"line\"> ring0_addr<span class=\"punctuation\">:</span> node3</span><br><span class=\"line\"> nodeid<span class=\"punctuation\">:</span> <span class=\"number\">2</span></span><br><span class=\"line\"> <span class=\"punctuation\">}</span></span><br><span class=\"line\"><span class=\"punctuation\">}</span></span><br><span class=\"line\"></span><br><span class=\"line\">quorum <span class=\"punctuation\">{</span></span><br><span class=\"line\"> provider<span class=\"punctuation\">:</span> corosync_votequorum</span><br><span class=\"line\"> two_node<span class=\"punctuation\">:</span> <span class=\"number\">1</span></span><br><span class=\"line\"><span class=\"punctuation\">}</span></span><br><span class=\"line\"></span><br><span class=\"line\">logging <span class=\"punctuation\">{</span></span><br><span class=\"line\"> to_logfile<span class=\"punctuation\">:</span> yes</span><br><span class=\"line\"> logfile<span class=\"punctuation\">:</span> /var/log/cluster/corosync.log</span><br><span class=\"line\"> to_syslog<span class=\"punctuation\">:</span> yes</span><br><span class=\"line\"><span class=\"punctuation\">}</span></span><br></pre></td></tr></table></figure>\n</li>\n<li><p>启动集群</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs cluster start --all <span class=\"comment\"># 这里启动失败的话,可以后面加上 --debug参数查看更详细的信息,可能会因为防火墙等问题导致启动失败</span></span><br><span class=\"line\">pcs cluster <span class=\"built_in\">enable</span> --all <span class=\"comment\"># 设置自启动</span></span><br></pre></td></tr></table></figure></li>\n<li><p>查看集群状态</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs status</span><br></pre></td></tr></table></figure>\n<p>我们看下输出情况:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Cluster name: mycluster</span><br><span class=\"line\"></span><br><span class=\"line\">WARNINGS:</span><br><span class=\"line\">No stonith devices and stonith-enabled is not false</span><br><span class=\"line\"></span><br><span class=\"line\">Stack: corosync</span><br><span class=\"line\">Current DC: node2 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum</span><br><span class=\"line\">Last updated: Mon Sep 2 18:43:57 2024</span><br><span class=\"line\">Last change: Mon Sep 2 18:35:08 2024 by hacluster via crmd on node2</span><br><span class=\"line\"></span><br><span class=\"line\">2 nodes configured</span><br><span class=\"line\">0 resource instances configured</span><br><span class=\"line\"></span><br><span class=\"line\">Online: [ node2 node3 ]</span><br><span class=\"line\"></span><br><span class=\"line\">No resources</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">Daemon Status:</span><br><span class=\"line\"> corosync: active/disabled</span><br><span class=\"line\"> pacemaker: active/disabled</span><br><span class=\"line\"> pcsd: active/enabled</span><br></pre></td></tr></table></figure>\n<p>这里我们可以看到集群的总体情况,包括节点状态、服务状态(有两个服务还处于 disabled 状态, 通过<code>systemctl enable corosync pacemaker</code> 设置开机启动)、资源信息(还没有添加 resource)等</p>\n</li>\n</ol>\n<h2 id=\"stonith-配置\"><a href=\"#stonith-配置\" class=\"headerlink\" title=\"stonith 配置\"></a>stonith 配置</h2><p>在上文集群的状态输出中还包括了一个告警信息: <code>No stonith devices and stonith-enabled is not false</code>, 这里的 stonith(Shoot The Other Node In The Head) 是一种防止“脑裂” (split-brain) 的机制。当集群中的一个节点失去与其他节点的连接时,stonith 设备可以强制重启或关闭这个失联的节点,避免两个或多个节点同时操作同一个资源,导致数据损坏。想要消除这个告警,有两种解决方案:</p>\n<ol>\n<li>禁用 stonith: 如果是自己的测试环境,那么可以禁用掉 stonith 来消除告警,操作方法为: <figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs property <span class=\"built_in\">set</span> stonith-enabled=<span class=\"literal\">false</span></span><br></pre></td></tr></table></figure></li>\n<li>配置 stonith 设备: 在生产环境中,建议配置 stonith</li>\n</ol>\n<p>我们先根据官方文档的指示看一下stonith 有哪些可用代理:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node2 corosync]# pcs stonith list</span><br><span class=\"line\">Error: No stonith agents available. Do you have fence agents installed?</span><br></pre></td></tr></table></figure>\n<p>这里提示没有代理的 agent 可用,所以我们首先需要安装<code>fence agent</code>:</p>\n<figure class=\"highlight shell\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">yum install -y fence-agents</span><br></pre></td></tr></table></figure>\n<p>我们再 list 一下,就可以看到支持的代理了</p>\n<table>\n<thead>\n<tr>\n<th>Fence Agent</th>\n<th>描述</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>fence_amt_ws</code></td>\n<td>适用于 AMT (WS) 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_apc</code></td>\n<td>通过 telnet/ssh 控制 APC 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_apc_snmp</code></td>\n<td>适用于 APC 和 Tripplite PDU 的 SNMP Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_bladecenter</code></td>\n<td>适用于 IBM BladeCenter 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_brocade</code></td>\n<td>通过 telnet/ssh 控制 HP Brocade 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_cisco_mds</code></td>\n<td>适用于 Cisco MDS 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_cisco_ucs</code></td>\n<td>适用于 Cisco UCS 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_compute</code></td>\n<td>用于自动复活 OpenStack 计算实例的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_drac5</code></td>\n<td>适用于 Dell DRAC CMC/5 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_eaton_snmp</code></td>\n<td>适用于 Eaton 的 SNMP Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_emerson</code></td>\n<td>适用于 Emerson 的 SNMP Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_eps</code></td>\n<td>适用于 ePowerSwitch 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_evacuate</code></td>\n<td>用于自动复活 OpenStack 计算实例的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_heuristics_ping</code></td>\n<td>基于 ping 进行启发式 Fencing 的代理</td>\n</tr>\n<tr>\n<td><code>fence_hpblade</code></td>\n<td>适用于 HP BladeSystem 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ibmblade</code></td>\n<td>通过 SNMP 控制 IBM BladeCenter 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_idrac</code></td>\n<td>适用于 IPMI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ifmib</code></td>\n<td>适用于 IF MIB 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo</code></td>\n<td>适用于 HP iLO 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo2</code></td>\n<td>适用于 HP iLO2 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo3</code></td>\n<td>适用于 IPMI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo3_ssh</code></td>\n<td>通过 SSH 控制 HP iLO3 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo4</code></td>\n<td>适用于 IPMI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo4_ssh</code></td>\n<td>通过 SSH 控制 HP iLO4 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo5</code></td>\n<td>适用于 IPMI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo5_ssh</code></td>\n<td>通过 SSH 控制 HP iLO5 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo_moonshot</code></td>\n<td>适用于 HP Moonshot iLO 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo_mp</code></td>\n<td>适用于 HP iLO MP 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ilo_ssh</code></td>\n<td>通过 SSH 控制 HP iLO 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_imm</code></td>\n<td>适用于 IPMI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_intelmodular</code></td>\n<td>适用于 Intel Modular 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ipdu</code></td>\n<td>通过 SNMP 控制 iPDU 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_ipmilan</code></td>\n<td>适用于 IPMI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_kdump</code></td>\n<td>与 kdump 崩溃恢复服务一起使用的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_mpath</code></td>\n<td>用于多路径持久保留的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_redfish</code></td>\n<td>适用于 Redfish 的 I/O Fencing 代理</td>\n</tr>\n<tr>\n<td><code>fence_rhevm</code></td>\n<td>适用于 RHEV-M REST API 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_rsa</code></td>\n<td>适用于 IBM RSA 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_rsb</code></td>\n<td>适用于 Fujitsu-Siemens RSB 的 I/O Fencing 代理</td>\n</tr>\n<tr>\n<td><code>fence_sbd</code></td>\n<td>适用于 SBD 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_scsi</code></td>\n<td>用于 SCSI 持久保留的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_virt</code></td>\n<td>适用于虚拟机的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_vmware_rest</code></td>\n<td>适用于 VMware REST API 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_vmware_soap</code></td>\n<td>通过 SOAP API 控制 VMware 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_wti</code></td>\n<td>适用于 WTI 的 Fence 代理</td>\n</tr>\n<tr>\n<td><code>fence_xvm</code></td>\n<td>适用于虚拟机的 Fence 代理</td>\n</tr>\n</tbody></table>\n<p>想查看代理的具体用法,可以使用:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs stonith describe stonith_agent</span><br></pre></td></tr></table></figure>\n\n<p>使用 <code>fence_heuristics_ping</code> 作为代理,先通过<code>pcs stonith describe fence_heuristics_ping</code> 看下具体的用法和配置</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">fence_heuristics_ping - Fence agent for ping-heuristic based fencing</span><br><span class=\"line\"></span><br><span class=\"line\">fence_heuristics_ping uses ping-heuristics to control execution of another fence agent on the same fencing level.</span><br><span class=\"line\"></span><br><span class=\"line\">This is not a fence agent by itself! Its only purpose is to enable/disable another fence agent that lives on the same fencing level but after fence_heuristics_ping.</span><br><span class=\"line\"></span><br><span class=\"line\">Stonith options:</span><br><span class=\"line\"> method: Method to fence</span><br><span class=\"line\"> ping_count: The number of ping-probes that is being sent per target</span><br><span class=\"line\"> ping_good_count: The number of positive ping-probes required to account a target as available</span><br><span class=\"line\"> ping_interval: The interval in seconds between ping-probes</span><br><span class=\"line\"> ping_maxfail: The number of failed ping-targets to still account as overall success</span><br><span class=\"line\"> ping_targets (required): A comma separated list of ping-targets (optionally prepended by 'inet:' or 'inet6:') to be probed</span><br><span class=\"line\"> ping_timeout: The timeout in seconds till an individual ping-probe is accounted as lost</span><br><span class=\"line\"> quiet: Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.</span><br><span class=\"line\"> verbose: Verbose mode</span><br><span class=\"line\"> debug: Write debug information to given file</span><br><span class=\"line\"> delay: Wait X seconds before fencing is started</span><br><span class=\"line\"> login_timeout: Wait X seconds for cmd prompt after login</span><br><span class=\"line\"> power_timeout: Test X seconds for status change after ON/OFF</span><br><span class=\"line\"> power_wait: Wait X seconds after issuing ON/OFF</span><br><span class=\"line\"> shell_timeout: Wait X seconds for cmd prompt after issuing command</span><br><span class=\"line\"> retry_on: Count of attempts to retry power on</span><br><span class=\"line\"> pcmk_host_map: A mapping of host names to ports numbers for devices that do not support host names. Eg. node1:1;node2:2,3 would tell the cluster to use port 1 for node1 and ports 2 and 3</span><br><span class=\"line\"> for node2</span><br><span class=\"line\"> pcmk_host_list: A list of machines controlled by this device (Optional unless pcmk_host_check=static-list).</span><br><span class=\"line\"> pcmk_host_check: How to determine which machines are controlled by the device. Allowed values: dynamic-list (query the device via the 'list' command), static-list (check the pcmk_host_list</span><br><span class=\"line\"> attribute), status (query the device via the 'status' command), none (assume every device can fence every machine)</span><br><span class=\"line\"> pcmk_delay_max: Enable a random delay for stonith actions and specify the maximum of random delay. This prevents double fencing when using slow devices such as sbd. Use this to enable a</span><br><span class=\"line\"> random delay for stonith actions. The overall delay is derived from this random delay value adding a static delay so that the sum is kept below the maximum delay.</span><br><span class=\"line\"> pcmk_delay_base: Enable a base delay for stonith actions and specify base delay value. This prevents double fencing when different delays are configured on the nodes. Use this to enable a</span><br><span class=\"line\"> static delay for stonith actions. The overall delay is derived from a random delay value adding this static delay so that the sum is kept below the maximum delay.</span><br><span class=\"line\"> pcmk_action_limit: The maximum number of actions can be performed in parallel on this device Pengine property concurrent-fencing=true needs to be configured first. Then use this to specify</span><br><span class=\"line\"> the maximum number of actions can be performed in parallel on this device. -1 is unlimited.</span><br><span class=\"line\"></span><br><span class=\"line\">Default operations:</span><br><span class=\"line\"> monitor: interval=60s</span><br></pre></td></tr></table></figure>\n\n<p>这里我们进行创建(其他参数都有默认值,按需修改即可):</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs stonith create my_ping_fence_device fence_heuristics_ping \\</span><br><span class=\"line\"> ping_targets=<span class=\"string\">"node2,node3"</span></span><br></pre></td></tr></table></figure>\n\n<p>创建完成后<code>pcs status</code>查看状态</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Cluster name: mycluster</span><br><span class=\"line\">Stack: corosync</span><br><span class=\"line\">Current DC: node2 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum</span><br><span class=\"line\">Last updated: Tue Sep 3 14:40:56 2024</span><br><span class=\"line\">Last change: Tue Sep 3 14:35:39 2024 by root via cibadmin on node2</span><br><span class=\"line\"></span><br><span class=\"line\">2 nodes configured</span><br><span class=\"line\">1 resource instance configured</span><br><span class=\"line\"></span><br><span class=\"line\">Online: [ node2 node3 ]</span><br><span class=\"line\"></span><br><span class=\"line\">Full list of resources:</span><br><span class=\"line\"></span><br><span class=\"line\"> my_ping_fence_device\t(stonith:fence_heuristics_ping):\tStarted node2</span><br><span class=\"line\"></span><br><span class=\"line\">Failed Fencing Actions:</span><br><span class=\"line\">* reboot of my_apc_fence_device failed: delegate=, client=stonith_admin.40341, origin=node2,</span><br><span class=\"line\"> last-failed='Tue Sep 3 14:12:23 2024'</span><br><span class=\"line\"></span><br><span class=\"line\">Daemon Status:</span><br><span class=\"line\"> corosync: active/enabled</span><br><span class=\"line\"> pacemaker: active/enabled</span><br><span class=\"line\"> pcsd: active/enabled</span><br></pre></td></tr></table></figure>\n<p>验证 stonith 是否生效:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node2 cluster]# pcs stonith fence node3</span><br><span class=\"line\">Node: node3 fenced</span><br></pre></td></tr></table></figure>\n<p>执行完这个操作后,节点会离线,pcs 服务会停止,想要加回来的话,在停止的节点上重新启动集群即可:<code>pcs cluster start && pcs cluster enable</code></p>\n<h1 id=\"实战操作\"><a href=\"#实战操作\" class=\"headerlink\" title=\"实战操作\"></a>实战操作</h1><h2 id=\"添加节点\"><a href=\"#添加节点\" class=\"headerlink\" title=\"添加节点\"></a>添加节点</h2><p>上文中,我们构建了一个两节点的集群,我们可以尝试增加一个节点,构建一个三节点的集群</p>\n<ol>\n<li>首先在新节点上安装各种依赖,设置密码等。</li>\n<li>在原集群上认证新 node</li>\n<li>在原集群上添加 node:<code>pcs cluster node add node4</code></li>\n<li>在 node4 上执行:<code>pcs cluster start && pcs cluster enable</code></li>\n</ol>\n<p>再通过<code>pcs status</code>就可以看到新的节点已经加入</p>\n<p>添加完之后还需要更新新节点的一些配置,比如上文提到的 stonith:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs stonith update my_ping_fence_device fence_heuristics_ping \\</span><br><span class=\"line\"> ping_targets=<span class=\"string\">"node2,node3,node4"</span></span><br></pre></td></tr></table></figure>\n\n<h2 id=\"配置-resource\"><a href=\"#配置-resource\" class=\"headerlink\" title=\"配置 resource\"></a>配置 resource</h2><h3 id=\"资源类型\"><a href=\"#资源类型\" class=\"headerlink\" title=\"资源类型\"></a>资源类型</h3><p>创建 resource 的基本格式为:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs resource create resource-name ocf:heartbeat:apache [--options]</span><br></pre></td></tr></table></figure>\n<p>这里的 ocf:heartbeat:apache 第一个部分ocf,指明了这个资源采用的标准(类型),第二个部分标明这个资源脚本的在ocf中的名字空间,在这个例子中是heartbeat。最后一个部分指明了资源脚本的名称。</p>\n<p>我们先看下有哪些标准类型</p>\n<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node3 ~]# pcs resource standards</span><br><span class=\"line\">lsb</span><br><span class=\"line\">ocf</span><br><span class=\"line\">service</span><br><span class=\"line\">systemd</span><br></pre></td></tr></table></figure>\n<p>查看可用的ocf资源提供者:</p>\n<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node3 ~]# pcs resource providers</span><br><span class=\"line\">heartbeat</span><br><span class=\"line\">openstack</span><br><span class=\"line\">pacemaker</span><br></pre></td></tr></table></figure>\n<p>查看特定标准下所支持的脚本,例:ofc:heartbeat 下的脚本(列举了部分):</p>\n<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node3 ~]# pcs resource agents ocf:heartbeat</span><br><span class=\"line\">aliyun-vpc-move-ip</span><br><span class=\"line\">apache</span><br><span class=\"line\">aws-vpc-move-ip</span><br><span class=\"line\">aws-vpc-route53</span><br><span class=\"line\">awseip</span><br><span class=\"line\">awsvip</span><br><span class=\"line\">azure-events</span><br><span class=\"line\">azure-lb</span><br><span class=\"line\">clvm</span><br><span class=\"line\">conntrackd</span><br><span class=\"line\">CTDB</span><br><span class=\"line\">db2</span><br><span class=\"line\">Delay</span><br><span class=\"line\">dhcpd</span><br><span class=\"line\">docker</span><br><span class=\"line\">Dummy</span><br><span class=\"line\">ethmonitor</span><br><span class=\"line\">exportfs</span><br><span class=\"line\">Filesystem</span><br><span class=\"line\">galera</span><br><span class=\"line\">garbd</span><br><span class=\"line\">iface-vlan</span><br><span class=\"line\">IPaddr</span><br><span class=\"line\">IPaddr2</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"设置虚拟-ip\"><a href=\"#设置虚拟-ip\" class=\"headerlink\" title=\"设置虚拟 ip\"></a>设置虚拟 ip</h3><p>虚拟 IP(Virtual IP)是在高可用性集群中使用的一种技术,通过为服务提供一个不依赖于特定物理节点的 IP 地址来实现服务的高可用性。当集群中的某个节点出现故障时,虚拟 IP 可以迅速转移到另一个健康的节点上,从而保证服务的连续性。</p>\n<p>虚拟 IP 的使用场景</p>\n<ol>\n<li>高可用性:虚拟 IP 最常见的使用场景是高可用性集群(如 Pacemaker 或 Keepalived),它允许一个服务在集群中的多个节点之间进行切换,而不会更改客户端访问的 IP 地址。</li>\n<li>负载均衡:虚拟 IP 可以结合负载均衡器使用,将来自客户端的请求分配到多个后端服务器,以实现流量的均匀分布。</li>\n<li>灾难恢复:在灾难恢复场景中,虚拟 IP 可以用于快速恢复服务,将业务流量从故障节点转移到备用节点上</li>\n</ol>\n<p>在 pcs 集群中,我们可以通过以下方式增加一个虚拟 ip:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs resource create virtual_ip ocf:heartbeat:IPaddr2 ip=x.x.x.x cidr_netmask=32 nic=bond1 op monitor interval=30s</span><br></pre></td></tr></table></figure>\n<p>执行完成后,通过<code>pcs status</code>就可以看到 ip 绑定在哪里:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node3</span><br></pre></td></tr></table></figure>\n<p>当我们关停 node3 的服务时,就会发现这个虚拟ip 绑定到了其他节点的 bond1 网卡上。</p>\n<h3 id=\"增加服务\"><a href=\"#增加服务\" class=\"headerlink\" title=\"增加服务\"></a>增加服务</h3><p>我们以 httpd 服务为例,在集群中创建资源,首先安装对应服务:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"built_in\">sudo</span> yum install httpd -y</span><br><span class=\"line\"><span class=\"built_in\">sudo</span> systemctl start httpd <span class=\"comment\"># 这里可选择不启动,后续如果通过pcs 直接托管,需要先停掉,</span></span><br><span class=\"line\"><span class=\"built_in\">sudo</span> systemctl <span class=\"built_in\">enable</span> httpd </span><br></pre></td></tr></table></figure>\n\n<p>创建 resource:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs resource create WebService ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"结合-LVS-ldirectord-进行使用\"><a href=\"#结合-LVS-ldirectord-进行使用\" class=\"headerlink\" title=\"结合 LVS + ldirectord 进行使用\"></a>结合 LVS + ldirectord 进行使用</h2><p>如果环境是一套多节点集群,在生产中我们肯定需要充分利用起这些节点,所以就要考虑流量分发。在这一层面上,我们可以使用 lvs 进行流量分发。这里首先对 lvs 对一个简单的介绍</p>\n<p>LVS(Linux Virtual Server)是一个基于 IP 负载均衡技术的开源软件项目,主要用于构建高可用、高性能的负载均衡集群。LVS 是 Linux 内核的一部分,通过网络层的负载均衡技术,将来自客户端的请求分发到多个后端服务器,从而实现分布式处理、提高系统的处理能力和可靠性。</p>\n<p>LVS 主要通过三种负载均衡模式(NAT 模式、DR 模式、TUN 模式)来实现流量的分发,支持大规模并发请求的处理,通常用于大型网站、电子商务平台和高访问量的 Web 应用中。</p>\n<p>LVS 的特点</p>\n<ol>\n<li>高性能: LVS 工作在网络层(第4层),基于 IP 进行流量转发,性能极高。它能够处理大量的并发连接,适合高流量、大规模的网站和服务。</li>\n<li>高可用性: LVS 通常与 Keepalived、Pacemaker 等高可用性工具配合使用,以实现负载均衡器的自动故障切换,确保服务的高可用性和稳定性。</li>\n<li>多种负载均衡算法: LVS 提供了多种负载均衡算法,如轮询(Round Robin)、最小连接(Least Connection)、基于目标地址哈希(Destination Hashing)等,可以根据具体需求选择合适的算法进行流量分发。</li>\n<li>多种工作模式, LVS 支持三种主要工作模式:<ul>\n<li> NAT 模式(网络地址转换模式):LVS 充当请求和响应的中介,适用于小规模集群。</li>\n<li> DR 模式(直接路由模式):请求由 LVS 转发,但响应直接返回给客户端,适用于大型集群,性能高。</li>\n<li> TUN 模式(IP 隧道模式):类似于 DR 模式,但支持跨网络部署,非常适合广域网负载均衡。</li>\n</ul>\n</li>\n<li>高扩展性: LVS 可以轻松地扩展和管理多台服务器,支持动态添加或移除后端服务器,适应业务需求的变化,且不影响服务的正常运行。</li>\n<li>透明性: 对客户端和后端服务器来说,LVS 的存在是透明的。客户端并不感知负载均衡的存在,访问体验一致。后端服务器也不需要做特殊的配置,只需处理 LVS 转发的请求。</li>\n<li>成熟且稳定: 作为一个成熟的负载均衡解决方案,LVS 被广泛应用于生产环境中,经过多年发展,功能完备,稳定性高。</li>\n<li>安全性: LVS 可以与防火墙等安全工具结合使用,增强系统的安全性。此外,LVS 还支持 IP 地址过滤、端口过滤等功能,提供一定程度的安全保护。</li>\n</ol>\n<p>ldirectord 是一个守护进程,用于管理和监控由 LVS 提供的虚拟服务(Virtual Services)。其主要功能包括:</p>\n<ol>\n<li> 监控后端服务器:ldirectord 定期检查后端服务器的健康状况,确保只有健康的服务器参与流量分配。</li>\n<li> 动态配置:基于后端服务器的健康状况,ldirectord 可以动态调整 LVS 的配置。例如,当一台服务器宕机时,ldirectord 会自动将其从 LVS 配置中移除。</li>\n<li> 高可用性:结合 heartbeat 等高可用性工具,ldirectord 可以确保在主节点故障时,负载均衡服务能够自动切换到备用节点,继续提供服务。</li>\n</ol>\n<h3 id=\"安装部署-1\"><a href=\"#安装部署-1\" class=\"headerlink\" title=\"安装部署\"></a>安装部署</h3><p>安装lvs:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">yum install lvm2 ipvsadm -y</span><br></pre></td></tr></table></figure>\n<p>在这里找包有一些技巧,比如一开始 chatgpt 提供的说法是要安装<code>lvs</code> 和 <code>ipvsadm</code>,但是在我的环境上通过<code>yum install -y lvs</code> 的时候提示没有这个包,那我们可以通过 yum 提供的一些命令来简单锁定一下,比如 <code>yum provides lvs</code>,这样就会把包含了这个命令的包显示出来(适用于知道命令但是不知道是哪个包的场景),</p>\n<p>安装 ldirector:</p>\n<figure class=\"highlight plaintext\"><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><td class=\"code\"><pre><span class=\"line\"># 这里我用 yum 下载是没有找到对应包的,找了一圈也没找到安装方法,所以直接找的 rpm 包</span><br><span class=\"line\"># 下载地址: ftp://ftp.icm.edu.pl/vol/rzm3/linux-opensuse/update/leap/15.2/oss/x86_64/ldirectord-4.4.0+git57.70549516-lp152.2.9.1.x86_64.rpm</span><br><span class=\"line\"># 上传到机器上后,进行安装</span><br><span class=\"line\">rpm -Uvh --force ldirectord-4.4.0+git57.70549516-lp152.2.9.1.x86_64.rpm</span><br><span class=\"line\"></span><br><span class=\"line\"># 需要依赖,先安装依赖,再装包</span><br><span class=\"line\">yum install -y perl-IO-Socket-INET6 perl-MailTools perl-Net-SSLeay perl-Socket6 perl-libwww-perl</span><br><span class=\"line\"># 操作完成之后,启动服务</span><br><span class=\"line\">systemctl start ldirectord.service</span><br></pre></td></tr></table></figure>\n<p>服务启动失败:</p>\n<div class=\"tag-plugin image\"><div class=\"image-bg\"><img src=\"/images/ldirectord.png\" alt=\"ldirectord服务\" data-fancybox=\"true\"/></div><div class=\"image-meta\"><span class=\"image-caption center\">ldirectord服务</span></div></div>\n\n<p>这里有点坑,缺少了依赖的文件,但是装包的时候没有提示,需要再安装: <code>yum install -y perl-Sys-Syslog</code>, 安装完成后此问题消失,但是此时配置文件还没配置,所以服务还起不来。</p>\n<h3 id=\"pcs-结合-lvs、ldirectord\"><a href=\"#pcs-结合-lvs、ldirectord\" class=\"headerlink\" title=\"pcs 结合 lvs、ldirectord\"></a>pcs 结合 lvs、ldirectord</h3><p>在上文中,我们创建了一个 httpd 服务和 vip 资源。 在实际生产中,要充分利用节点性能,我们可能要在多个节点上启动httpd 示例,我们在每个节点上都启动一个实例,然后将他们归到一个组中:</p>\n<figure class=\"highlight bash\"><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><td class=\"code\"><pre><span class=\"line\">pcs resource delete WebService <span class=\"comment\"># 移除之前创建的服务</span></span><br><span class=\"line\">pcs resource create WebService1 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s</span><br><span class=\"line\">pcs resource create WebService2 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s --force</span><br><span class=\"line\">pcs resource create WebService3 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s --force <span class=\"comment\"># 创建三个服务</span></span><br><span class=\"line\"></span><br><span class=\"line\">pcs constraint location WebService1 prefers node2</span><br><span class=\"line\">pcs constraint location WebService2 prefers node3</span><br><span class=\"line\">pcs constraint location WebService3 prefers node4 <span class=\"comment\"># 限制对应 resource 服务只能在指定节点上运行</span></span><br></pre></td></tr></table></figure>\n\n<p>配置 ldirectord:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">checktimeout=10</span><br><span class=\"line\">checkinterval=2</span><br><span class=\"line\">autoreload=yes</span><br><span class=\"line\">logfile="/var/log/ldirectord.log"</span><br><span class=\"line\">quiescent=yes</span><br><span class=\"line\"></span><br><span class=\"line\">virtual=vip:80 # 之前绑定的 VIP</span><br><span class=\"line\"> real=192.168.1.2:80 gate</span><br><span class=\"line\"> real=192.168.1.3:80 gate</span><br><span class=\"line\"> real=192.168.1.4:80 gate</span><br><span class=\"line\"> fallback=127.0.0.1:80</span><br><span class=\"line\"> service=http</span><br><span class=\"line\"> request="index.html"</span><br><span class=\"line\"> receive="HTTP/1.1 200 OK"</span><br><span class=\"line\"> scheduler=rr</span><br><span class=\"line\"> protocol=tcp</span><br><span class=\"line\"> checktype=negotiate</span><br></pre></td></tr></table></figure>\n\n<p>然后在通过 <code>ipvsadm -ln</code> 就可以查看到详细的信息:</p>\n<figure class=\"highlight bash\"><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><td class=\"code\"><pre><span class=\"line\">IP Virtual Server version 1.2.1 (size=4096)</span><br><span class=\"line\">Prot LocalAddress:Port Scheduler Flags</span><br><span class=\"line\"> -> RemoteAddress:Port Forward Weight ActiveConn InActConn</span><br><span class=\"line\">TCP vip:80 rr</span><br><span class=\"line\"> -> 192.168.1.2:80 Route 0 0 0</span><br><span class=\"line\"> -> 192.168.1.3:80 Route 0 0 0</span><br><span class=\"line\"> -> 192.168.1.4:80 Route 0 0 0</span><br><span class=\"line\"> -> 127.0.0.1:80 Route 1 0 0</span><br></pre></td></tr></table></figure>\n\n<p>然后我们可以在 pcs 上创建一个资源 lvs 相关的资源:</p>\n<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">pcs resource create my_lvs ocf:heartbeat:ldirectord \\</span><br><span class=\"line\"> configfile=/etc/ha.d/ldirectord.cf \\</span><br><span class=\"line\"> ldirectord=/usr/sbin/ldirectord \\</span><br><span class=\"line\"> op monitor interval=15s <span class=\"built_in\">timeout</span>=60s \\</span><br><span class=\"line\"> op stop <span class=\"built_in\">timeout</span>=60s</span><br></pre></td></tr></table></figure>\n<p>这里的ocf:heartbeat:ldirectord 在有的版本中会默认安装,有的版本不会,如果没有的话需要手动下载: <a href=\"https://github.com/ClusterLabs/resource-agents/blob/main/ldirectord/OCF/ldirectord.in\">https://github.com/ClusterLabs/resource-agents/blob/main/ldirectord/OCF/ldirectord.in</a><br>存放到: <code>/usr/lib/ocf/resource.d/heartbeat/ldirectord</code> 并添加可执行权限: <code>chmod +x /usr/lib/ocf/resource.d/heartbeat/ldirectord</code></p>\n<p>创建完成后,我们可以将 vip 和 lvs 绑定到一个组中,这样 lvs 就会跟着 vip 进行转移了:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs resource group add balanceGroup virtual_ip my_lvs</span><br></pre></td></tr></table></figure>\n<p>通过<code>pcs status</code>查看就可以看到:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">Resource Group: balanceGroup</span><br><span class=\"line\"> virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node2</span><br><span class=\"line\"> my_lvs\t(ocf::heartbeat:ldirectord):\tStarted node2</span><br></pre></td></tr></table></figure>\n<p>不断对节点进行关闭测试,可以看到 lvs 和 vip 始终都在同一个节点上</p>\n<h2 id=\"增加节点属性\"><a href=\"#增加节点属性\" class=\"headerlink\" title=\"增加节点属性\"></a>增加节点属性</h2><p>我们这里使用另外一个观察集群状态的命令:<code>crm_mon</code>, 比如<code>crm_mon -A1</code></p>\n<figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node2 rpm]# crm_mon -A1</span><br><span class=\"line\">Stack: corosync</span><br><span class=\"line\">Current DC: node3 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorum</span><br><span class=\"line\">Last updated: Thu Sep 5 22:09:53 2024</span><br><span class=\"line\">Last change: Wed Sep 4 18:17:25 2024 by root via cibadmin on node3</span><br><span class=\"line\"></span><br><span class=\"line\">3 nodes configured</span><br><span class=\"line\">6 resource instances configured</span><br><span class=\"line\"></span><br><span class=\"line\">Online: [ node2 node3 node4 ]</span><br><span class=\"line\"></span><br><span class=\"line\">Active resources:</span><br><span class=\"line\"></span><br><span class=\"line\"> my_ping_fence_device\t(stonith:fence_heuristics_ping):\tStarted node3</span><br><span class=\"line\"> WebService1\t(ocf::heartbeat:apache):\tStarted node4</span><br><span class=\"line\"> WebService2\t(ocf::heartbeat:apache):\tStarted node3</span><br><span class=\"line\"> WebService3\t(ocf::heartbeat:apache):\tStarted node4</span><br><span class=\"line\"> Resource Group: balanceGroup</span><br><span class=\"line\"> virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node2</span><br><span class=\"line\"> my_lvs\t(ocf::heartbeat:ldirectord):\tStarted node2</span><br><span class=\"line\"></span><br><span class=\"line\">Node Attributes:</span><br><span class=\"line\">* Node node2:</span><br><span class=\"line\">* Node node3:</span><br><span class=\"line\">* Node node4:</span><br><span class=\"line\">.....</span><br></pre></td></tr></table></figure>\n<p>输出和<code>pcs status</code>查看到的效果基本上是差不多的。但是在下面有<code>Node Attributes</code>,这里我们看下节点属性怎么设置:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">pcs node attribute node2 role=master</span><br><span class=\"line\">pcs node attribute node3 role=standby</span><br><span class=\"line\">pcs node attribute node4 role=standby</span><br></pre></td></tr></table></figure>\n<p>或者</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">crm_attribute --node node2 --name mysql --update master</span><br><span class=\"line\">crm_attribute --node node3 --name mysql --update standby</span><br></pre></td></tr></table></figure>\n<p>设置完成之后,我们就可以看到节点属性:</p>\n<figure class=\"highlight plaintext\"><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><td class=\"code\"><pre><span class=\"line\">Node Attributes:</span><br><span class=\"line\">* Node node2:</span><br><span class=\"line\"> + mysql \t: master</span><br><span class=\"line\"> + role \t: master</span><br><span class=\"line\">* Node node3:</span><br><span class=\"line\"> + mysql \t: standby</span><br><span class=\"line\"> + role \t: standby</span><br><span class=\"line\">* Node node4:</span><br><span class=\"line\"> + role \t: standby</span><br></pre></td></tr></table></figure>\n<p>那有人就会好奇这样设置有什么用呢?主要用途是在哪里呢。</p>\n<p>这里的指标往往是动态的,可以根据自己喜好结合一些扩展进行变化,比如部署了一套 postgresql 集群,集群中有主有备,有同步节点也有异步节点,有的节点状态可能有问题,那我们怎么能够显示出这个集群的整体情况呢,这样就可以使用 Node Attributes进行设置,关于如果搭建 pcs + postgresql 的集群,大家可以参考这篇文章: <a href=\"https://www.cnblogs.com/Alicebat/p/14148933.html\">基于Pacemaker的PostgreSQL高可用集群</a></p>\n<p>最终我们看到的效果如下:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Node Attributes:</span><br><span class=\"line\">* Node pg01:</span><br><span class=\"line\"> + master-pgsql \t: 1000 </span><br><span class=\"line\"> + pgsql-data-status \t: LATEST </span><br><span class=\"line\"> + pgsql-master-baseline \t: 0000000008000098</span><br><span class=\"line\"> + pgsql-status \t: PRI </span><br><span class=\"line\">* Node pg02:</span><br><span class=\"line\"> + master-pgsql \t: -INFINITY </span><br><span class=\"line\"> + pgsql-data-status \t: STREAMING|ASYNC</span><br><span class=\"line\"> + pgsql-status \t: HS:async </span><br><span class=\"line\">* Node pg03:</span><br><span class=\"line\"> + master-pgsql \t: 100 </span><br><span class=\"line\"> + pgsql-data-status \t: STREAMING|SYNC</span><br><span class=\"line\"> + pgsql-status \t: HS:sync </span><br></pre></td></tr></table></figure>\n<p>当集群发生节点变动,状态异常时,我们就可以根据 attibutes 的一些信息查看定位。</p>\n"},{"title":"Chronyd服务详解","author":"baixiaozhou","description":"时钟同步服务 chronyd 介绍","repo":"baixiaozhou/SysStress","date":"2024-09-09T06:31:17.000Z","references":["[Why does \"chronyc sources\" output unexpected NTP servers](https://access.redhat.com/solutions/4072781)","‘[官方文档](https://chrony-project.org/)’"],"cover":"/images/chrony.jpeg","banner":"/images/chrony.jpeg","_content":"\n<!-- Your content starts here -->\n# 介绍\n\n我们先看一下官网的介绍:\n```\nchrony is a versatile implementation of the Network Time Protocol (NTP). It can synchronise the system clock with NTP servers, reference clocks (e.g. GPS receiver), and manual input using wristwatch and keyboard. It can also operate as an NTPv4 (RFC 5905) server and peer to provide a time service to other computers in the network.\n```\n\n简而言之,chronyd 就是用来校准时间的,基于 ntp 协议(ntp 加强版),既可以做服务器,也可以做客户端(既能为其他机器提供时钟同步服务,也能从其他机器上同步时间)\n\n主要功能:\n\n - 时间同步:chronyd 主要负责将系统时间与网络时间服务器进行同步。它通过网络时间协议(NTP)或精确时间协议(PTP)从外部时间源获取时间信息,并调整本地系统时间。\n - 系统时间校准:Chrony 能够处理系统时钟漂移问题,尤其是在系统启动时或在虚拟化环境中,Chrony 能够在更短的时间内校准系统时间。\n - 网络时钟漂移:chronyd 能够处理网络延迟和时钟漂移,使得系统时间更加准确。\n\n# 部署安装\n\n现在很多 linux 发行版默认都会安装 chronyd 服务,如果没有安装,我们需要手动进行安装:\n``` bash\nyum install -y chrony\n```\n安装完成后,我们可以看下 chrony 包中都提供了哪些东西:\n``` shell\n[root@node2 ~]# rpm -ql chrony\n/etc/NetworkManager/dispatcher.d/20-chrony\n/etc/chrony.conf\n/etc/chrony.keys\n/etc/dhcp/dhclient.d/chrony.sh\n/etc/logrotate.d/chrony\n/etc/sysconfig/chronyd\n/usr/bin/chronyc\n/usr/lib/systemd/ntp-units.d/50-chronyd.list\n/usr/lib/systemd/system/chrony-dnssrv@.service\n/usr/lib/systemd/system/chrony-dnssrv@.timer\n/usr/lib/systemd/system/chrony-wait.service\n/usr/lib/systemd/system/chronyd.service\n/usr/libexec/chrony-helper\n/usr/sbin/chronyd\n/usr/share/doc/chrony-3.4\n/usr/share/doc/chrony-3.4/COPYING\n/usr/share/doc/chrony-3.4/FAQ\n/usr/share/doc/chrony-3.4/NEWS\n/usr/share/doc/chrony-3.4/README\n/usr/share/man/man1/chronyc.1.gz\n/usr/share/man/man5/chrony.conf.5.gz\n/usr/share/man/man8/chronyd.8.gz\n/var/lib/chrony\n/var/lib/chrony/drift\n/var/lib/chrony/rtc\n/var/log/chrony\n```\n经常涉及的主要包括:\n\n1. /etc/chrony.conf: 配置文件\n2. /usr/bin/chronyc: 命令行工具\n3. /usr/sbin/chronyd: 启动程序\n\n## 配置文件详解\n\n下面是 chronyd 配置文件中提供的参数及每项含义:\n| 配置项 | 解释 |\n|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `server <ntp-server> iburst` | 指定 NTP 服务器地址,`iburst` 选项表示在启动时快速发送一系列请求,以加快初始同步过程。 |\n| `driftfile /var/lib/chrony/drift` | 指定时钟漂移文件的位置,该文件记录系统时钟的增益或损失率,以帮助 Chrony 在重启后更快地调整时钟。 |\n| `makestep 1.0 3` | 允许系统时钟在前 3 次更新中,若时钟偏差超过 1 秒,可以进行大步调整(即直接调至正确时间),而不是逐渐调整。 |\n| `rtcsync` | 启用内核与实时时钟(RTC)的同步功能,以确保系统时钟与硬件时钟保持一致。 |\n| `hwtimestamp *` | 启用所有支持的接口上的硬件时间戳(该行被注释掉,未启用)。 |\n| `minsources 2` | 增加可选时间源的最小数量,当达到该数量时,才会调整系统时钟(该行被注释掉,未启用)。 |\n| `allow 192.168.0.0/16` | 允许从指定的局域网(如 192.168.0.0/16)访问 NTP 客户端,这通常用于允许局域网内的其他设备与本机进行时间同步(该行被注释掉,未启用)。 |\n| `local stratum 10` | 即使本地时间未与时间源同步,也允许本地系统作为时间服务器。`stratum` 指定了时间层级,`stratum 10` 表示较低的层次,用于避免对外部 NTP 服务器的依赖(该行被注释掉,未启用)。 |\n| `keyfile /etc/chrony.keys` | 指定 NTP 认证的密钥文件路径,用于确保 NTP 客户端和服务器之间的通信是安全的(该行被注释掉,未启用)。 |\n| `logdir /var/log/chrony` | 指定日志文件的目录,Chrony 会将日志存储在该目录下。 |\n| `log measurements statistics tracking` | 选择哪些信息需要被记录到日志中,包含测量结果、统计信息和跟踪信息(该行被注释掉,未启用)。 |\n\n## chronyc 命令详解\n\n| 命令 | 说明 |\n|-------------------|------------------------------------------------------------------------------------------|\n| `tracking` | 显示当前时间跟踪状态,包括时间源、时钟偏移、频率偏移等信息。 |\n| `sources` | 列出当前配置的 NTP 时间源及其状态。 |\n| `sourcestats` | 显示各个 NTP 时间源的统计信息,包括时间源的延迟、偏移、抖动等。 |\n| `ntpdata` | 显示与各个时间源相关的低级 NTP 数据,如参考时钟 ID 和偏移量等。 |\n| `makestep` | 立即将系统时间同步到时间源(即大幅度调整系统时间,而非逐步调整)。 |\n| `burst` | 向所有时间源发送一组时间请求,以加快同步速度。 |\n| `reload sources` | 重新加载配置文件中的时间源,而不需要重新启动 `chronyd`。 |\n| `clients` | 列出通过 `chronyd` 进行时间同步的客户端。 |\n| `settime` | 手动设置系统时间。 |\n| `rtcdata` | 显示实时时钟(RTC)的状态信息。 |\n| `manual` | 启动手动时间输入模式,用于手动设置时间(如通过键盘或手表)。 |\n| `dump` | 输出 `chronyd` 内存中时间源的状态信息。 |\n| `waitsync` | 等待系统时钟与时间源同步,通常用于确保系统启动时时间同步完成。 |\n| `activity` | 显示当前活动的时间源数量和状态。 |\n| `reselect` | 强制 `chronyd` 重新选择最佳的时间源。 |\n| `serverstats` | 显示 `chronyd` 的服务器统计信息,如请求数量、响应延迟等。 |\n| `manual list` | 列出手动设置的时间值。 |\n| `trimrtc` | 通过调整实时时钟(RTC)的频率,使其更接近系统时钟的频率。 |\n| `quit` | 退出 `chronyc` 交互模式。 |\n\n\n### 常用命令\n\n我们先看一下常用的一些命令:\n\n#### chronyc sources\n``` bash\n[root@node2 ~]# chronyc sources\n210 Number of sources = 3\nMS Name/IP address Stratum Poll Reach LastRx Last sample\n===============================================================================\n^+ node1 3 6 377 46 +330us[ +400us] +/- 3523us\n^+ 8.8.8.8 2 6 377 47 +570us[ +640us] +/- 2602us\n^* 8.8.8.9 2 6 377 45 +366us[ +436us] +/- 3478us\n```\n这里显示了当前这个机器同步的一些源信息,包含\n1. 源节点: `node1`、`8.8.8.8`、`9.9.9.9`, 最前面的*表示最优先的时钟源,\n2. Stratum: NTP 时间源的层级(stratum),表示时间源的准确性层次。Stratum 0 是参考时钟(如 GPS),Stratum 1 是直接从参考时钟获取时间的服务器,以此类推。较高的层级表示离参考时钟的距离越远,时钟的准确性也会降低。\n3. Poll: 系统与时间源之间的轮询间隔(以秒为单位)。该值表示在上一次时间同步之后,系统等待多久再次向该时间源请求同步。Poll 值会根据网络状况自动调整,通常范围是 64 到 1024 秒。\n4. Reach: 到达值(reach),是一个 8 位的八进制值,表示最近 8 次对该时间源的请求是否成功。该值通常为 377(所有 8 次请求都成功),较低的值表示有请求失败。\n5. LastRx: 最近一次从该时间源接收 NTP 数据包的时间(以秒为单位),表示距离上次成功接收数据包的时间长度。值越大表示该时间源未及时响应,可能存在问题。\n6. Last sample:最近一次时间样本的偏差值,表示系统时钟与时间源时钟之间的偏移量。正数表示系统时钟快于时间源,负数表示系统时钟慢于时间源。偏差通常以毫秒(ms)或微秒(µs)为单位显示。\n\n我们可以看下这个节点的配置文件:\n``` conf\nserver node1\ncommandkey 1\nkeyfile /etc/chrony.keys\n```\n这里大家会好奇,我这里明明只配置了 node1 为源服务器,为啥会多出两个 ip? 正好 Redhat 官方给了解释:\n[Why does \"chronyc sources\" output unexpected NTP servers](https://access.redhat.com/solutions/4072781)\n\n但是有点烦的是这个文档需要开通红帽订阅才能看,这里我直接粘贴一下原因:\n```\nResolution:\n\nAdd \"PEERNTP=no\" entry to /etc/sysconfig/network will prevent dhclient from receiving a list of NTP servers from the DHCP server.\nIf you already set the network connection down but still gets the NTP server in chronyc sources, delete /var/lib/dhclient/chrony.servers.* file and restart chronyd service.\n\nRoot Cause:\n\nIf a network connection is set to use DHCP to get IP address, when NetworkManager starts or network connection is up, dhclient receives a list of NTP servers from the DHCP server and generates /var/lib/dhclient/chrony.servers.* file (since chrony 4.1-1, the file is /run/chrony-helper/nm-dhcp.*).\nChronyd not only reads /etc/chrony.conf file, but also reads /var/lib/dhclient/chrony.servers.* file to get NTP server list.\n\nIf an NTP server has already been configured in /etc/chrony.conf file, it won't appear in /var/run/chrony-helper/added_servers.\nThus, user can confirm the added servers in /var/run/chrony-helper/added_servers.\n\nNote:\nThe environment variable, PEERNTP is used in /etc/dhcp/dhclient.d/chrony.sh(chrony rpm ) and /etc/dhcp/dhclient.d/ntp.sh(ntp rpm)\n```\n简单来说就是 chronyd 不仅会从 chrony.conf中去获取源,也会去 dhcp client 生成的信息中去获取源服务器\n\n#### chronyc tracking\n``` bash\n[root@\\node2 ~]# chronyc tracking\nReference ID : xxx (8.8.8.8)\nStratum : 3\nRef time (UTC) : Mon Sep 09 07:58:14 2024\nSystem time : 0.000109912 seconds fast of NTP time\nLast offset : +0.000090043 seconds\nRMS offset : 0.000686857 seconds\nFrequency : 11.188 ppm slow\nResidual freq : +0.026 ppm\nSkew : 0.786 ppm\nRoot delay : 0.003966943 seconds\nRoot dispersion : 0.000785699 seconds\nUpdate interval : 64.8 seconds\nLeap status : Normal\n```\n这里能看到更加细致的同步信息,比如时间的便宜量、更新间隔等\n\n#### chronyc makestep\n``` bash\n[root@node2 ~]# chronyc makestep\n200 OK\n```\n这个命令主要用于时间跨度太大一次性直接进行同步的,由于时钟的调整是非常微妙要求精确的,时间跨度太大的话同步完成的周期可能比较久,所以可以通过这个命令直接同步。","source":"_posts/Chronyd服务详解.md","raw":"---\ntitle: Chronyd服务详解\nauthor: baixiaozhou\ncategories:\n - 运维工具\ntags:\n - Linux\n - Chronyd\n - 时钟同步\ndescription: 时钟同步服务 chronyd 介绍\nrepo: baixiaozhou/SysStress\ndate: 2024-09-09 14:31:17\nreferences:\n - '[Why does \"chronyc sources\" output unexpected NTP servers](https://access.redhat.com/solutions/4072781)'\n - ‘[官方文档](https://chrony-project.org/)’\ncover: /images/chrony.jpeg\nbanner: /images/chrony.jpeg\n---\n\n<!-- Your content starts here -->\n# 介绍\n\n我们先看一下官网的介绍:\n```\nchrony is a versatile implementation of the Network Time Protocol (NTP). It can synchronise the system clock with NTP servers, reference clocks (e.g. GPS receiver), and manual input using wristwatch and keyboard. It can also operate as an NTPv4 (RFC 5905) server and peer to provide a time service to other computers in the network.\n```\n\n简而言之,chronyd 就是用来校准时间的,基于 ntp 协议(ntp 加强版),既可以做服务器,也可以做客户端(既能为其他机器提供时钟同步服务,也能从其他机器上同步时间)\n\n主要功能:\n\n - 时间同步:chronyd 主要负责将系统时间与网络时间服务器进行同步。它通过网络时间协议(NTP)或精确时间协议(PTP)从外部时间源获取时间信息,并调整本地系统时间。\n - 系统时间校准:Chrony 能够处理系统时钟漂移问题,尤其是在系统启动时或在虚拟化环境中,Chrony 能够在更短的时间内校准系统时间。\n - 网络时钟漂移:chronyd 能够处理网络延迟和时钟漂移,使得系统时间更加准确。\n\n# 部署安装\n\n现在很多 linux 发行版默认都会安装 chronyd 服务,如果没有安装,我们需要手动进行安装:\n``` bash\nyum install -y chrony\n```\n安装完成后,我们可以看下 chrony 包中都提供了哪些东西:\n``` shell\n[root@node2 ~]# rpm -ql chrony\n/etc/NetworkManager/dispatcher.d/20-chrony\n/etc/chrony.conf\n/etc/chrony.keys\n/etc/dhcp/dhclient.d/chrony.sh\n/etc/logrotate.d/chrony\n/etc/sysconfig/chronyd\n/usr/bin/chronyc\n/usr/lib/systemd/ntp-units.d/50-chronyd.list\n/usr/lib/systemd/system/chrony-dnssrv@.service\n/usr/lib/systemd/system/chrony-dnssrv@.timer\n/usr/lib/systemd/system/chrony-wait.service\n/usr/lib/systemd/system/chronyd.service\n/usr/libexec/chrony-helper\n/usr/sbin/chronyd\n/usr/share/doc/chrony-3.4\n/usr/share/doc/chrony-3.4/COPYING\n/usr/share/doc/chrony-3.4/FAQ\n/usr/share/doc/chrony-3.4/NEWS\n/usr/share/doc/chrony-3.4/README\n/usr/share/man/man1/chronyc.1.gz\n/usr/share/man/man5/chrony.conf.5.gz\n/usr/share/man/man8/chronyd.8.gz\n/var/lib/chrony\n/var/lib/chrony/drift\n/var/lib/chrony/rtc\n/var/log/chrony\n```\n经常涉及的主要包括:\n\n1. /etc/chrony.conf: 配置文件\n2. /usr/bin/chronyc: 命令行工具\n3. /usr/sbin/chronyd: 启动程序\n\n## 配置文件详解\n\n下面是 chronyd 配置文件中提供的参数及每项含义:\n| 配置项 | 解释 |\n|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `server <ntp-server> iburst` | 指定 NTP 服务器地址,`iburst` 选项表示在启动时快速发送一系列请求,以加快初始同步过程。 |\n| `driftfile /var/lib/chrony/drift` | 指定时钟漂移文件的位置,该文件记录系统时钟的增益或损失率,以帮助 Chrony 在重启后更快地调整时钟。 |\n| `makestep 1.0 3` | 允许系统时钟在前 3 次更新中,若时钟偏差超过 1 秒,可以进行大步调整(即直接调至正确时间),而不是逐渐调整。 |\n| `rtcsync` | 启用内核与实时时钟(RTC)的同步功能,以确保系统时钟与硬件时钟保持一致。 |\n| `hwtimestamp *` | 启用所有支持的接口上的硬件时间戳(该行被注释掉,未启用)。 |\n| `minsources 2` | 增加可选时间源的最小数量,当达到该数量时,才会调整系统时钟(该行被注释掉,未启用)。 |\n| `allow 192.168.0.0/16` | 允许从指定的局域网(如 192.168.0.0/16)访问 NTP 客户端,这通常用于允许局域网内的其他设备与本机进行时间同步(该行被注释掉,未启用)。 |\n| `local stratum 10` | 即使本地时间未与时间源同步,也允许本地系统作为时间服务器。`stratum` 指定了时间层级,`stratum 10` 表示较低的层次,用于避免对外部 NTP 服务器的依赖(该行被注释掉,未启用)。 |\n| `keyfile /etc/chrony.keys` | 指定 NTP 认证的密钥文件路径,用于确保 NTP 客户端和服务器之间的通信是安全的(该行被注释掉,未启用)。 |\n| `logdir /var/log/chrony` | 指定日志文件的目录,Chrony 会将日志存储在该目录下。 |\n| `log measurements statistics tracking` | 选择哪些信息需要被记录到日志中,包含测量结果、统计信息和跟踪信息(该行被注释掉,未启用)。 |\n\n## chronyc 命令详解\n\n| 命令 | 说明 |\n|-------------------|------------------------------------------------------------------------------------------|\n| `tracking` | 显示当前时间跟踪状态,包括时间源、时钟偏移、频率偏移等信息。 |\n| `sources` | 列出当前配置的 NTP 时间源及其状态。 |\n| `sourcestats` | 显示各个 NTP 时间源的统计信息,包括时间源的延迟、偏移、抖动等。 |\n| `ntpdata` | 显示与各个时间源相关的低级 NTP 数据,如参考时钟 ID 和偏移量等。 |\n| `makestep` | 立即将系统时间同步到时间源(即大幅度调整系统时间,而非逐步调整)。 |\n| `burst` | 向所有时间源发送一组时间请求,以加快同步速度。 |\n| `reload sources` | 重新加载配置文件中的时间源,而不需要重新启动 `chronyd`。 |\n| `clients` | 列出通过 `chronyd` 进行时间同步的客户端。 |\n| `settime` | 手动设置系统时间。 |\n| `rtcdata` | 显示实时时钟(RTC)的状态信息。 |\n| `manual` | 启动手动时间输入模式,用于手动设置时间(如通过键盘或手表)。 |\n| `dump` | 输出 `chronyd` 内存中时间源的状态信息。 |\n| `waitsync` | 等待系统时钟与时间源同步,通常用于确保系统启动时时间同步完成。 |\n| `activity` | 显示当前活动的时间源数量和状态。 |\n| `reselect` | 强制 `chronyd` 重新选择最佳的时间源。 |\n| `serverstats` | 显示 `chronyd` 的服务器统计信息,如请求数量、响应延迟等。 |\n| `manual list` | 列出手动设置的时间值。 |\n| `trimrtc` | 通过调整实时时钟(RTC)的频率,使其更接近系统时钟的频率。 |\n| `quit` | 退出 `chronyc` 交互模式。 |\n\n\n### 常用命令\n\n我们先看一下常用的一些命令:\n\n#### chronyc sources\n``` bash\n[root@node2 ~]# chronyc sources\n210 Number of sources = 3\nMS Name/IP address Stratum Poll Reach LastRx Last sample\n===============================================================================\n^+ node1 3 6 377 46 +330us[ +400us] +/- 3523us\n^+ 8.8.8.8 2 6 377 47 +570us[ +640us] +/- 2602us\n^* 8.8.8.9 2 6 377 45 +366us[ +436us] +/- 3478us\n```\n这里显示了当前这个机器同步的一些源信息,包含\n1. 源节点: `node1`、`8.8.8.8`、`9.9.9.9`, 最前面的*表示最优先的时钟源,\n2. Stratum: NTP 时间源的层级(stratum),表示时间源的准确性层次。Stratum 0 是参考时钟(如 GPS),Stratum 1 是直接从参考时钟获取时间的服务器,以此类推。较高的层级表示离参考时钟的距离越远,时钟的准确性也会降低。\n3. Poll: 系统与时间源之间的轮询间隔(以秒为单位)。该值表示在上一次时间同步之后,系统等待多久再次向该时间源请求同步。Poll 值会根据网络状况自动调整,通常范围是 64 到 1024 秒。\n4. Reach: 到达值(reach),是一个 8 位的八进制值,表示最近 8 次对该时间源的请求是否成功。该值通常为 377(所有 8 次请求都成功),较低的值表示有请求失败。\n5. LastRx: 最近一次从该时间源接收 NTP 数据包的时间(以秒为单位),表示距离上次成功接收数据包的时间长度。值越大表示该时间源未及时响应,可能存在问题。\n6. Last sample:最近一次时间样本的偏差值,表示系统时钟与时间源时钟之间的偏移量。正数表示系统时钟快于时间源,负数表示系统时钟慢于时间源。偏差通常以毫秒(ms)或微秒(µs)为单位显示。\n\n我们可以看下这个节点的配置文件:\n``` conf\nserver node1\ncommandkey 1\nkeyfile /etc/chrony.keys\n```\n这里大家会好奇,我这里明明只配置了 node1 为源服务器,为啥会多出两个 ip? 正好 Redhat 官方给了解释:\n[Why does \"chronyc sources\" output unexpected NTP servers](https://access.redhat.com/solutions/4072781)\n\n但是有点烦的是这个文档需要开通红帽订阅才能看,这里我直接粘贴一下原因:\n```\nResolution:\n\nAdd \"PEERNTP=no\" entry to /etc/sysconfig/network will prevent dhclient from receiving a list of NTP servers from the DHCP server.\nIf you already set the network connection down but still gets the NTP server in chronyc sources, delete /var/lib/dhclient/chrony.servers.* file and restart chronyd service.\n\nRoot Cause:\n\nIf a network connection is set to use DHCP to get IP address, when NetworkManager starts or network connection is up, dhclient receives a list of NTP servers from the DHCP server and generates /var/lib/dhclient/chrony.servers.* file (since chrony 4.1-1, the file is /run/chrony-helper/nm-dhcp.*).\nChronyd not only reads /etc/chrony.conf file, but also reads /var/lib/dhclient/chrony.servers.* file to get NTP server list.\n\nIf an NTP server has already been configured in /etc/chrony.conf file, it won't appear in /var/run/chrony-helper/added_servers.\nThus, user can confirm the added servers in /var/run/chrony-helper/added_servers.\n\nNote:\nThe environment variable, PEERNTP is used in /etc/dhcp/dhclient.d/chrony.sh(chrony rpm ) and /etc/dhcp/dhclient.d/ntp.sh(ntp rpm)\n```\n简单来说就是 chronyd 不仅会从 chrony.conf中去获取源,也会去 dhcp client 生成的信息中去获取源服务器\n\n#### chronyc tracking\n``` bash\n[root@\\node2 ~]# chronyc tracking\nReference ID : xxx (8.8.8.8)\nStratum : 3\nRef time (UTC) : Mon Sep 09 07:58:14 2024\nSystem time : 0.000109912 seconds fast of NTP time\nLast offset : +0.000090043 seconds\nRMS offset : 0.000686857 seconds\nFrequency : 11.188 ppm slow\nResidual freq : +0.026 ppm\nSkew : 0.786 ppm\nRoot delay : 0.003966943 seconds\nRoot dispersion : 0.000785699 seconds\nUpdate interval : 64.8 seconds\nLeap status : Normal\n```\n这里能看到更加细致的同步信息,比如时间的便宜量、更新间隔等\n\n#### chronyc makestep\n``` bash\n[root@node2 ~]# chronyc makestep\n200 OK\n```\n这个命令主要用于时间跨度太大一次性直接进行同步的,由于时钟的调整是非常微妙要求精确的,时间跨度太大的话同步完成的周期可能比较久,所以可以通过这个命令直接同步。","slug":"Chronyd服务详解","published":1,"updated":"2024-09-09T08:23:35.873Z","_id":"cm0uqbc8k00003ponhvvr14w2","comments":1,"layout":"post","photos":[],"content":"<!-- Your content starts here -->\n<h1 id=\"介绍\"><a href=\"#介绍\" class=\"headerlink\" title=\"介绍\"></a>介绍</h1><p>我们先看一下官网的介绍:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">chrony is a versatile implementation of the Network Time Protocol (NTP). It can synchronise the system clock with NTP servers, reference clocks (e.g. GPS receiver), and manual input using wristwatch and keyboard. It can also operate as an NTPv4 (RFC 5905) server and peer to provide a time service to other computers in the network.</span><br></pre></td></tr></table></figure>\n\n<p>简而言之,chronyd 就是用来校准时间的,基于 ntp 协议(ntp 加强版),既可以做服务器,也可以做客户端(既能为其他机器提供时钟同步服务,也能从其他机器上同步时间)</p>\n<p>主要功能:</p>\n<ul>\n<li>时间同步:chronyd 主要负责将系统时间与网络时间服务器进行同步。它通过网络时间协议(NTP)或精确时间协议(PTP)从外部时间源获取时间信息,并调整本地系统时间。</li>\n<li>系统时间校准:Chrony 能够处理系统时钟漂移问题,尤其是在系统启动时或在虚拟化环境中,Chrony 能够在更短的时间内校准系统时间。</li>\n<li>网络时钟漂移:chronyd 能够处理网络延迟和时钟漂移,使得系统时间更加准确。</li>\n</ul>\n<h1 id=\"部署安装\"><a href=\"#部署安装\" class=\"headerlink\" title=\"部署安装\"></a>部署安装</h1><p>现在很多 linux 发行版默认都会安装 chronyd 服务,如果没有安装,我们需要手动进行安装:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">yum install -y chrony</span><br></pre></td></tr></table></figure>\n<p>安装完成后,我们可以看下 chrony 包中都提供了哪些东西:</p>\n<figure class=\"highlight shell\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node2 ~]# rpm -ql chrony</span><br><span class=\"line\">/etc/NetworkManager/dispatcher.d/20-chrony</span><br><span class=\"line\">/etc/chrony.conf</span><br><span class=\"line\">/etc/chrony.keys</span><br><span class=\"line\">/etc/dhcp/dhclient.d/chrony.sh</span><br><span class=\"line\">/etc/logrotate.d/chrony</span><br><span class=\"line\">/etc/sysconfig/chronyd</span><br><span class=\"line\">/usr/bin/chronyc</span><br><span class=\"line\">/usr/lib/systemd/ntp-units.d/50-chronyd.list</span><br><span class=\"line\">/usr/lib/systemd/system/chrony-dnssrv@.service</span><br><span class=\"line\">/usr/lib/systemd/system/chrony-dnssrv@.timer</span><br><span class=\"line\">/usr/lib/systemd/system/chrony-wait.service</span><br><span class=\"line\">/usr/lib/systemd/system/chronyd.service</span><br><span class=\"line\">/usr/libexec/chrony-helper</span><br><span class=\"line\">/usr/sbin/chronyd</span><br><span class=\"line\">/usr/share/doc/chrony-3.4</span><br><span class=\"line\">/usr/share/doc/chrony-3.4/COPYING</span><br><span class=\"line\">/usr/share/doc/chrony-3.4/FAQ</span><br><span class=\"line\">/usr/share/doc/chrony-3.4/NEWS</span><br><span class=\"line\">/usr/share/doc/chrony-3.4/README</span><br><span class=\"line\">/usr/share/man/man1/chronyc.1.gz</span><br><span class=\"line\">/usr/share/man/man5/chrony.conf.5.gz</span><br><span class=\"line\">/usr/share/man/man8/chronyd.8.gz</span><br><span class=\"line\">/var/lib/chrony</span><br><span class=\"line\">/var/lib/chrony/drift</span><br><span class=\"line\">/var/lib/chrony/rtc</span><br><span class=\"line\">/var/log/chrony</span><br></pre></td></tr></table></figure>\n<p>经常涉及的主要包括:</p>\n<ol>\n<li>/etc/chrony.conf: 配置文件</li>\n<li>/usr/bin/chronyc: 命令行工具</li>\n<li>/usr/sbin/chronyd: 启动程序</li>\n</ol>\n<h2 id=\"配置文件详解\"><a href=\"#配置文件详解\" class=\"headerlink\" title=\"配置文件详解\"></a>配置文件详解</h2><p>下面是 chronyd 配置文件中提供的参数及每项含义:</p>\n<table>\n<thead>\n<tr>\n<th>配置项</th>\n<th>解释</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>server <ntp-server> iburst</code></td>\n<td>指定 NTP 服务器地址,<code>iburst</code> 选项表示在启动时快速发送一系列请求,以加快初始同步过程。</td>\n</tr>\n<tr>\n<td><code>driftfile /var/lib/chrony/drift</code></td>\n<td>指定时钟漂移文件的位置,该文件记录系统时钟的增益或损失率,以帮助 Chrony 在重启后更快地调整时钟。</td>\n</tr>\n<tr>\n<td><code>makestep 1.0 3</code></td>\n<td>允许系统时钟在前 3 次更新中,若时钟偏差超过 1 秒,可以进行大步调整(即直接调至正确时间),而不是逐渐调整。</td>\n</tr>\n<tr>\n<td><code>rtcsync</code></td>\n<td>启用内核与实时时钟(RTC)的同步功能,以确保系统时钟与硬件时钟保持一致。</td>\n</tr>\n<tr>\n<td><code>hwtimestamp *</code></td>\n<td>启用所有支持的接口上的硬件时间戳(该行被注释掉,未启用)。</td>\n</tr>\n<tr>\n<td><code>minsources 2</code></td>\n<td>增加可选时间源的最小数量,当达到该数量时,才会调整系统时钟(该行被注释掉,未启用)。</td>\n</tr>\n<tr>\n<td><code>allow 192.168.0.0/16</code></td>\n<td>允许从指定的局域网(如 192.168.0.0/16)访问 NTP 客户端,这通常用于允许局域网内的其他设备与本机进行时间同步(该行被注释掉,未启用)。</td>\n</tr>\n<tr>\n<td><code>local stratum 10</code></td>\n<td>即使本地时间未与时间源同步,也允许本地系统作为时间服务器。<code>stratum</code> 指定了时间层级,<code>stratum 10</code> 表示较低的层次,用于避免对外部 NTP 服务器的依赖(该行被注释掉,未启用)。</td>\n</tr>\n<tr>\n<td><code>keyfile /etc/chrony.keys</code></td>\n<td>指定 NTP 认证的密钥文件路径,用于确保 NTP 客户端和服务器之间的通信是安全的(该行被注释掉,未启用)。</td>\n</tr>\n<tr>\n<td><code>logdir /var/log/chrony</code></td>\n<td>指定日志文件的目录,Chrony 会将日志存储在该目录下。</td>\n</tr>\n<tr>\n<td><code>log measurements statistics tracking</code></td>\n<td>选择哪些信息需要被记录到日志中,包含测量结果、统计信息和跟踪信息(该行被注释掉,未启用)。</td>\n</tr>\n</tbody></table>\n<h2 id=\"chronyc-命令详解\"><a href=\"#chronyc-命令详解\" class=\"headerlink\" title=\"chronyc 命令详解\"></a>chronyc 命令详解</h2><table>\n<thead>\n<tr>\n<th>命令</th>\n<th>说明</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>tracking</code></td>\n<td>显示当前时间跟踪状态,包括时间源、时钟偏移、频率偏移等信息。</td>\n</tr>\n<tr>\n<td><code>sources</code></td>\n<td>列出当前配置的 NTP 时间源及其状态。</td>\n</tr>\n<tr>\n<td><code>sourcestats</code></td>\n<td>显示各个 NTP 时间源的统计信息,包括时间源的延迟、偏移、抖动等。</td>\n</tr>\n<tr>\n<td><code>ntpdata</code></td>\n<td>显示与各个时间源相关的低级 NTP 数据,如参考时钟 ID 和偏移量等。</td>\n</tr>\n<tr>\n<td><code>makestep</code></td>\n<td>立即将系统时间同步到时间源(即大幅度调整系统时间,而非逐步调整)。</td>\n</tr>\n<tr>\n<td><code>burst</code></td>\n<td>向所有时间源发送一组时间请求,以加快同步速度。</td>\n</tr>\n<tr>\n<td><code>reload sources</code></td>\n<td>重新加载配置文件中的时间源,而不需要重新启动 <code>chronyd</code>。</td>\n</tr>\n<tr>\n<td><code>clients</code></td>\n<td>列出通过 <code>chronyd</code> 进行时间同步的客户端。</td>\n</tr>\n<tr>\n<td><code>settime</code></td>\n<td>手动设置系统时间。</td>\n</tr>\n<tr>\n<td><code>rtcdata</code></td>\n<td>显示实时时钟(RTC)的状态信息。</td>\n</tr>\n<tr>\n<td><code>manual</code></td>\n<td>启动手动时间输入模式,用于手动设置时间(如通过键盘或手表)。</td>\n</tr>\n<tr>\n<td><code>dump</code></td>\n<td>输出 <code>chronyd</code> 内存中时间源的状态信息。</td>\n</tr>\n<tr>\n<td><code>waitsync</code></td>\n<td>等待系统时钟与时间源同步,通常用于确保系统启动时时间同步完成。</td>\n</tr>\n<tr>\n<td><code>activity</code></td>\n<td>显示当前活动的时间源数量和状态。</td>\n</tr>\n<tr>\n<td><code>reselect</code></td>\n<td>强制 <code>chronyd</code> 重新选择最佳的时间源。</td>\n</tr>\n<tr>\n<td><code>serverstats</code></td>\n<td>显示 <code>chronyd</code> 的服务器统计信息,如请求数量、响应延迟等。</td>\n</tr>\n<tr>\n<td><code>manual list</code></td>\n<td>列出手动设置的时间值。</td>\n</tr>\n<tr>\n<td><code>trimrtc</code></td>\n<td>通过调整实时时钟(RTC)的频率,使其更接近系统时钟的频率。</td>\n</tr>\n<tr>\n<td><code>quit</code></td>\n<td>退出 <code>chronyc</code> 交互模式。</td>\n</tr>\n</tbody></table>\n<h3 id=\"常用命令\"><a href=\"#常用命令\" class=\"headerlink\" title=\"常用命令\"></a>常用命令</h3><p>我们先看一下常用的一些命令:</p>\n<h4 id=\"chronyc-sources\"><a href=\"#chronyc-sources\" class=\"headerlink\" title=\"chronyc sources\"></a>chronyc sources</h4><figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node2 ~]# chronyc sources</span><br><span class=\"line\">210 Number of sources = 3</span><br><span class=\"line\">MS Name/IP address Stratum Poll Reach LastRx Last sample</span><br><span class=\"line\">===============================================================================</span><br><span class=\"line\">^+ node1 3 6 377 46 +330us[ +400us] +/- 3523us</span><br><span class=\"line\">^+ 8.8.8.8 2 6 377 47 +570us[ +640us] +/- 2602us</span><br><span class=\"line\">^* 8.8.8.9 2 6 377 45 +366us[ +436us] +/- 3478us</span><br></pre></td></tr></table></figure>\n<p>这里显示了当前这个机器同步的一些源信息,包含</p>\n<ol>\n<li>源节点: <code>node1</code>、<code>8.8.8.8</code>、<code>9.9.9.9</code>, 最前面的*表示最优先的时钟源,</li>\n<li>Stratum: NTP 时间源的层级(stratum),表示时间源的准确性层次。Stratum 0 是参考时钟(如 GPS),Stratum 1 是直接从参考时钟获取时间的服务器,以此类推。较高的层级表示离参考时钟的距离越远,时钟的准确性也会降低。</li>\n<li>Poll: 系统与时间源之间的轮询间隔(以秒为单位)。该值表示在上一次时间同步之后,系统等待多久再次向该时间源请求同步。Poll 值会根据网络状况自动调整,通常范围是 64 到 1024 秒。</li>\n<li>Reach: 到达值(reach),是一个 8 位的八进制值,表示最近 8 次对该时间源的请求是否成功。该值通常为 377(所有 8 次请求都成功),较低的值表示有请求失败。</li>\n<li>LastRx: 最近一次从该时间源接收 NTP 数据包的时间(以秒为单位),表示距离上次成功接收数据包的时间长度。值越大表示该时间源未及时响应,可能存在问题。</li>\n<li>Last sample:最近一次时间样本的偏差值,表示系统时钟与时间源时钟之间的偏移量。正数表示系统时钟快于时间源,负数表示系统时钟慢于时间源。偏差通常以毫秒(ms)或微秒(µs)为单位显示。</li>\n</ol>\n<p>我们可以看下这个节点的配置文件:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">server node1</span><br><span class=\"line\">commandkey 1</span><br><span class=\"line\">keyfile /etc/chrony.keys</span><br></pre></td></tr></table></figure>\n<p>这里大家会好奇,我这里明明只配置了 node1 为源服务器,为啥会多出两个 ip? 正好 Redhat 官方给了解释:<br><a href=\"https://access.redhat.com/solutions/4072781\">Why does “chronyc sources” output unexpected NTP servers</a></p>\n<p>但是有点烦的是这个文档需要开通红帽订阅才能看,这里我直接粘贴一下原因:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Resolution:</span><br><span class=\"line\"></span><br><span class=\"line\">Add "PEERNTP=no" entry to /etc/sysconfig/network will prevent dhclient from receiving a list of NTP servers from the DHCP server.</span><br><span class=\"line\">If you already set the network connection down but still gets the NTP server in chronyc sources, delete /var/lib/dhclient/chrony.servers.* file and restart chronyd service.</span><br><span class=\"line\"></span><br><span class=\"line\">Root Cause:</span><br><span class=\"line\"></span><br><span class=\"line\">If a network connection is set to use DHCP to get IP address, when NetworkManager starts or network connection is up, dhclient receives a list of NTP servers from the DHCP server and generates /var/lib/dhclient/chrony.servers.* file (since chrony 4.1-1, the file is /run/chrony-helper/nm-dhcp.*).</span><br><span class=\"line\">Chronyd not only reads /etc/chrony.conf file, but also reads /var/lib/dhclient/chrony.servers.* file to get NTP server list.</span><br><span class=\"line\"></span><br><span class=\"line\">If an NTP server has already been configured in /etc/chrony.conf file, it won't appear in /var/run/chrony-helper/added_servers.</span><br><span class=\"line\">Thus, user can confirm the added servers in /var/run/chrony-helper/added_servers.</span><br><span class=\"line\"></span><br><span class=\"line\">Note:</span><br><span class=\"line\">The environment variable, PEERNTP is used in /etc/dhcp/dhclient.d/chrony.sh(chrony rpm ) and /etc/dhcp/dhclient.d/ntp.sh(ntp rpm)</span><br></pre></td></tr></table></figure>\n<p>简单来说就是 chronyd 不仅会从 chrony.conf中去获取源,也会去 dhcp client 生成的信息中去获取源服务器</p>\n<h4 id=\"chronyc-tracking\"><a href=\"#chronyc-tracking\" class=\"headerlink\" title=\"chronyc tracking\"></a>chronyc tracking</h4><figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@\\node2 ~]# chronyc tracking</span><br><span class=\"line\">Reference ID : xxx (8.8.8.8)</span><br><span class=\"line\">Stratum : 3</span><br><span class=\"line\">Ref time (UTC) : Mon Sep 09 07:58:14 2024</span><br><span class=\"line\">System time : 0.000109912 seconds fast of NTP time</span><br><span class=\"line\">Last offset : +0.000090043 seconds</span><br><span class=\"line\">RMS offset : 0.000686857 seconds</span><br><span class=\"line\">Frequency : 11.188 ppm slow</span><br><span class=\"line\">Residual freq : +0.026 ppm</span><br><span class=\"line\">Skew : 0.786 ppm</span><br><span class=\"line\">Root delay : 0.003966943 seconds</span><br><span class=\"line\">Root dispersion : 0.000785699 seconds</span><br><span class=\"line\">Update interval : 64.8 seconds</span><br><span class=\"line\">Leap status : Normal</span><br></pre></td></tr></table></figure>\n<p>这里能看到更加细致的同步信息,比如时间的便宜量、更新间隔等</p>\n<h4 id=\"chronyc-makestep\"><a href=\"#chronyc-makestep\" class=\"headerlink\" title=\"chronyc makestep\"></a>chronyc makestep</h4><figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node2 ~]# chronyc makestep</span><br><span class=\"line\">200 OK</span><br></pre></td></tr></table></figure>\n<p>这个命令主要用于时间跨度太大一次性直接进行同步的,由于时钟的调整是非常微妙要求精确的,时间跨度太大的话同步完成的周期可能比较久,所以可以通过这个命令直接同步。</p>\n","excerpt":"","more":"<!-- Your content starts here -->\n<h1 id=\"介绍\"><a href=\"#介绍\" class=\"headerlink\" title=\"介绍\"></a>介绍</h1><p>我们先看一下官网的介绍:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">chrony is a versatile implementation of the Network Time Protocol (NTP). It can synchronise the system clock with NTP servers, reference clocks (e.g. GPS receiver), and manual input using wristwatch and keyboard. It can also operate as an NTPv4 (RFC 5905) server and peer to provide a time service to other computers in the network.</span><br></pre></td></tr></table></figure>\n\n<p>简而言之,chronyd 就是用来校准时间的,基于 ntp 协议(ntp 加强版),既可以做服务器,也可以做客户端(既能为其他机器提供时钟同步服务,也能从其他机器上同步时间)</p>\n<p>主要功能:</p>\n<ul>\n<li>时间同步:chronyd 主要负责将系统时间与网络时间服务器进行同步。它通过网络时间协议(NTP)或精确时间协议(PTP)从外部时间源获取时间信息,并调整本地系统时间。</li>\n<li>系统时间校准:Chrony 能够处理系统时钟漂移问题,尤其是在系统启动时或在虚拟化环境中,Chrony 能够在更短的时间内校准系统时间。</li>\n<li>网络时钟漂移:chronyd 能够处理网络延迟和时钟漂移,使得系统时间更加准确。</li>\n</ul>\n<h1 id=\"部署安装\"><a href=\"#部署安装\" class=\"headerlink\" title=\"部署安装\"></a>部署安装</h1><p>现在很多 linux 发行版默认都会安装 chronyd 服务,如果没有安装,我们需要手动进行安装:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">yum install -y chrony</span><br></pre></td></tr></table></figure>\n<p>安装完成后,我们可以看下 chrony 包中都提供了哪些东西:</p>\n<figure class=\"highlight shell\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node2 ~]# rpm -ql chrony</span><br><span class=\"line\">/etc/NetworkManager/dispatcher.d/20-chrony</span><br><span class=\"line\">/etc/chrony.conf</span><br><span class=\"line\">/etc/chrony.keys</span><br><span class=\"line\">/etc/dhcp/dhclient.d/chrony.sh</span><br><span class=\"line\">/etc/logrotate.d/chrony</span><br><span class=\"line\">/etc/sysconfig/chronyd</span><br><span class=\"line\">/usr/bin/chronyc</span><br><span class=\"line\">/usr/lib/systemd/ntp-units.d/50-chronyd.list</span><br><span class=\"line\">/usr/lib/systemd/system/chrony-dnssrv@.service</span><br><span class=\"line\">/usr/lib/systemd/system/chrony-dnssrv@.timer</span><br><span class=\"line\">/usr/lib/systemd/system/chrony-wait.service</span><br><span class=\"line\">/usr/lib/systemd/system/chronyd.service</span><br><span class=\"line\">/usr/libexec/chrony-helper</span><br><span class=\"line\">/usr/sbin/chronyd</span><br><span class=\"line\">/usr/share/doc/chrony-3.4</span><br><span class=\"line\">/usr/share/doc/chrony-3.4/COPYING</span><br><span class=\"line\">/usr/share/doc/chrony-3.4/FAQ</span><br><span class=\"line\">/usr/share/doc/chrony-3.4/NEWS</span><br><span class=\"line\">/usr/share/doc/chrony-3.4/README</span><br><span class=\"line\">/usr/share/man/man1/chronyc.1.gz</span><br><span class=\"line\">/usr/share/man/man5/chrony.conf.5.gz</span><br><span class=\"line\">/usr/share/man/man8/chronyd.8.gz</span><br><span class=\"line\">/var/lib/chrony</span><br><span class=\"line\">/var/lib/chrony/drift</span><br><span class=\"line\">/var/lib/chrony/rtc</span><br><span class=\"line\">/var/log/chrony</span><br></pre></td></tr></table></figure>\n<p>经常涉及的主要包括:</p>\n<ol>\n<li>/etc/chrony.conf: 配置文件</li>\n<li>/usr/bin/chronyc: 命令行工具</li>\n<li>/usr/sbin/chronyd: 启动程序</li>\n</ol>\n<h2 id=\"配置文件详解\"><a href=\"#配置文件详解\" class=\"headerlink\" title=\"配置文件详解\"></a>配置文件详解</h2><p>下面是 chronyd 配置文件中提供的参数及每项含义:</p>\n<table>\n<thead>\n<tr>\n<th>配置项</th>\n<th>解释</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>server <ntp-server> iburst</code></td>\n<td>指定 NTP 服务器地址,<code>iburst</code> 选项表示在启动时快速发送一系列请求,以加快初始同步过程。</td>\n</tr>\n<tr>\n<td><code>driftfile /var/lib/chrony/drift</code></td>\n<td>指定时钟漂移文件的位置,该文件记录系统时钟的增益或损失率,以帮助 Chrony 在重启后更快地调整时钟。</td>\n</tr>\n<tr>\n<td><code>makestep 1.0 3</code></td>\n<td>允许系统时钟在前 3 次更新中,若时钟偏差超过 1 秒,可以进行大步调整(即直接调至正确时间),而不是逐渐调整。</td>\n</tr>\n<tr>\n<td><code>rtcsync</code></td>\n<td>启用内核与实时时钟(RTC)的同步功能,以确保系统时钟与硬件时钟保持一致。</td>\n</tr>\n<tr>\n<td><code>hwtimestamp *</code></td>\n<td>启用所有支持的接口上的硬件时间戳(该行被注释掉,未启用)。</td>\n</tr>\n<tr>\n<td><code>minsources 2</code></td>\n<td>增加可选时间源的最小数量,当达到该数量时,才会调整系统时钟(该行被注释掉,未启用)。</td>\n</tr>\n<tr>\n<td><code>allow 192.168.0.0/16</code></td>\n<td>允许从指定的局域网(如 192.168.0.0/16)访问 NTP 客户端,这通常用于允许局域网内的其他设备与本机进行时间同步(该行被注释掉,未启用)。</td>\n</tr>\n<tr>\n<td><code>local stratum 10</code></td>\n<td>即使本地时间未与时间源同步,也允许本地系统作为时间服务器。<code>stratum</code> 指定了时间层级,<code>stratum 10</code> 表示较低的层次,用于避免对外部 NTP 服务器的依赖(该行被注释掉,未启用)。</td>\n</tr>\n<tr>\n<td><code>keyfile /etc/chrony.keys</code></td>\n<td>指定 NTP 认证的密钥文件路径,用于确保 NTP 客户端和服务器之间的通信是安全的(该行被注释掉,未启用)。</td>\n</tr>\n<tr>\n<td><code>logdir /var/log/chrony</code></td>\n<td>指定日志文件的目录,Chrony 会将日志存储在该目录下。</td>\n</tr>\n<tr>\n<td><code>log measurements statistics tracking</code></td>\n<td>选择哪些信息需要被记录到日志中,包含测量结果、统计信息和跟踪信息(该行被注释掉,未启用)。</td>\n</tr>\n</tbody></table>\n<h2 id=\"chronyc-命令详解\"><a href=\"#chronyc-命令详解\" class=\"headerlink\" title=\"chronyc 命令详解\"></a>chronyc 命令详解</h2><table>\n<thead>\n<tr>\n<th>命令</th>\n<th>说明</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>tracking</code></td>\n<td>显示当前时间跟踪状态,包括时间源、时钟偏移、频率偏移等信息。</td>\n</tr>\n<tr>\n<td><code>sources</code></td>\n<td>列出当前配置的 NTP 时间源及其状态。</td>\n</tr>\n<tr>\n<td><code>sourcestats</code></td>\n<td>显示各个 NTP 时间源的统计信息,包括时间源的延迟、偏移、抖动等。</td>\n</tr>\n<tr>\n<td><code>ntpdata</code></td>\n<td>显示与各个时间源相关的低级 NTP 数据,如参考时钟 ID 和偏移量等。</td>\n</tr>\n<tr>\n<td><code>makestep</code></td>\n<td>立即将系统时间同步到时间源(即大幅度调整系统时间,而非逐步调整)。</td>\n</tr>\n<tr>\n<td><code>burst</code></td>\n<td>向所有时间源发送一组时间请求,以加快同步速度。</td>\n</tr>\n<tr>\n<td><code>reload sources</code></td>\n<td>重新加载配置文件中的时间源,而不需要重新启动 <code>chronyd</code>。</td>\n</tr>\n<tr>\n<td><code>clients</code></td>\n<td>列出通过 <code>chronyd</code> 进行时间同步的客户端。</td>\n</tr>\n<tr>\n<td><code>settime</code></td>\n<td>手动设置系统时间。</td>\n</tr>\n<tr>\n<td><code>rtcdata</code></td>\n<td>显示实时时钟(RTC)的状态信息。</td>\n</tr>\n<tr>\n<td><code>manual</code></td>\n<td>启动手动时间输入模式,用于手动设置时间(如通过键盘或手表)。</td>\n</tr>\n<tr>\n<td><code>dump</code></td>\n<td>输出 <code>chronyd</code> 内存中时间源的状态信息。</td>\n</tr>\n<tr>\n<td><code>waitsync</code></td>\n<td>等待系统时钟与时间源同步,通常用于确保系统启动时时间同步完成。</td>\n</tr>\n<tr>\n<td><code>activity</code></td>\n<td>显示当前活动的时间源数量和状态。</td>\n</tr>\n<tr>\n<td><code>reselect</code></td>\n<td>强制 <code>chronyd</code> 重新选择最佳的时间源。</td>\n</tr>\n<tr>\n<td><code>serverstats</code></td>\n<td>显示 <code>chronyd</code> 的服务器统计信息,如请求数量、响应延迟等。</td>\n</tr>\n<tr>\n<td><code>manual list</code></td>\n<td>列出手动设置的时间值。</td>\n</tr>\n<tr>\n<td><code>trimrtc</code></td>\n<td>通过调整实时时钟(RTC)的频率,使其更接近系统时钟的频率。</td>\n</tr>\n<tr>\n<td><code>quit</code></td>\n<td>退出 <code>chronyc</code> 交互模式。</td>\n</tr>\n</tbody></table>\n<h3 id=\"常用命令\"><a href=\"#常用命令\" class=\"headerlink\" title=\"常用命令\"></a>常用命令</h3><p>我们先看一下常用的一些命令:</p>\n<h4 id=\"chronyc-sources\"><a href=\"#chronyc-sources\" class=\"headerlink\" title=\"chronyc sources\"></a>chronyc sources</h4><figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node2 ~]# chronyc sources</span><br><span class=\"line\">210 Number of sources = 3</span><br><span class=\"line\">MS Name/IP address Stratum Poll Reach LastRx Last sample</span><br><span class=\"line\">===============================================================================</span><br><span class=\"line\">^+ node1 3 6 377 46 +330us[ +400us] +/- 3523us</span><br><span class=\"line\">^+ 8.8.8.8 2 6 377 47 +570us[ +640us] +/- 2602us</span><br><span class=\"line\">^* 8.8.8.9 2 6 377 45 +366us[ +436us] +/- 3478us</span><br></pre></td></tr></table></figure>\n<p>这里显示了当前这个机器同步的一些源信息,包含</p>\n<ol>\n<li>源节点: <code>node1</code>、<code>8.8.8.8</code>、<code>9.9.9.9</code>, 最前面的*表示最优先的时钟源,</li>\n<li>Stratum: NTP 时间源的层级(stratum),表示时间源的准确性层次。Stratum 0 是参考时钟(如 GPS),Stratum 1 是直接从参考时钟获取时间的服务器,以此类推。较高的层级表示离参考时钟的距离越远,时钟的准确性也会降低。</li>\n<li>Poll: 系统与时间源之间的轮询间隔(以秒为单位)。该值表示在上一次时间同步之后,系统等待多久再次向该时间源请求同步。Poll 值会根据网络状况自动调整,通常范围是 64 到 1024 秒。</li>\n<li>Reach: 到达值(reach),是一个 8 位的八进制值,表示最近 8 次对该时间源的请求是否成功。该值通常为 377(所有 8 次请求都成功),较低的值表示有请求失败。</li>\n<li>LastRx: 最近一次从该时间源接收 NTP 数据包的时间(以秒为单位),表示距离上次成功接收数据包的时间长度。值越大表示该时间源未及时响应,可能存在问题。</li>\n<li>Last sample:最近一次时间样本的偏差值,表示系统时钟与时间源时钟之间的偏移量。正数表示系统时钟快于时间源,负数表示系统时钟慢于时间源。偏差通常以毫秒(ms)或微秒(µs)为单位显示。</li>\n</ol>\n<p>我们可以看下这个节点的配置文件:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">server node1</span><br><span class=\"line\">commandkey 1</span><br><span class=\"line\">keyfile /etc/chrony.keys</span><br></pre></td></tr></table></figure>\n<p>这里大家会好奇,我这里明明只配置了 node1 为源服务器,为啥会多出两个 ip? 正好 Redhat 官方给了解释:<br><a href=\"https://access.redhat.com/solutions/4072781\">Why does “chronyc sources” output unexpected NTP servers</a></p>\n<p>但是有点烦的是这个文档需要开通红帽订阅才能看,这里我直接粘贴一下原因:</p>\n<figure class=\"highlight plaintext\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">Resolution:</span><br><span class=\"line\"></span><br><span class=\"line\">Add "PEERNTP=no" entry to /etc/sysconfig/network will prevent dhclient from receiving a list of NTP servers from the DHCP server.</span><br><span class=\"line\">If you already set the network connection down but still gets the NTP server in chronyc sources, delete /var/lib/dhclient/chrony.servers.* file and restart chronyd service.</span><br><span class=\"line\"></span><br><span class=\"line\">Root Cause:</span><br><span class=\"line\"></span><br><span class=\"line\">If a network connection is set to use DHCP to get IP address, when NetworkManager starts or network connection is up, dhclient receives a list of NTP servers from the DHCP server and generates /var/lib/dhclient/chrony.servers.* file (since chrony 4.1-1, the file is /run/chrony-helper/nm-dhcp.*).</span><br><span class=\"line\">Chronyd not only reads /etc/chrony.conf file, but also reads /var/lib/dhclient/chrony.servers.* file to get NTP server list.</span><br><span class=\"line\"></span><br><span class=\"line\">If an NTP server has already been configured in /etc/chrony.conf file, it won't appear in /var/run/chrony-helper/added_servers.</span><br><span class=\"line\">Thus, user can confirm the added servers in /var/run/chrony-helper/added_servers.</span><br><span class=\"line\"></span><br><span class=\"line\">Note:</span><br><span class=\"line\">The environment variable, PEERNTP is used in /etc/dhcp/dhclient.d/chrony.sh(chrony rpm ) and /etc/dhcp/dhclient.d/ntp.sh(ntp rpm)</span><br></pre></td></tr></table></figure>\n<p>简单来说就是 chronyd 不仅会从 chrony.conf中去获取源,也会去 dhcp client 生成的信息中去获取源服务器</p>\n<h4 id=\"chronyc-tracking\"><a href=\"#chronyc-tracking\" class=\"headerlink\" title=\"chronyc tracking\"></a>chronyc tracking</h4><figure class=\"highlight bash\"><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></pre></td><td class=\"code\"><pre><span class=\"line\">[root@\\node2 ~]# chronyc tracking</span><br><span class=\"line\">Reference ID : xxx (8.8.8.8)</span><br><span class=\"line\">Stratum : 3</span><br><span class=\"line\">Ref time (UTC) : Mon Sep 09 07:58:14 2024</span><br><span class=\"line\">System time : 0.000109912 seconds fast of NTP time</span><br><span class=\"line\">Last offset : +0.000090043 seconds</span><br><span class=\"line\">RMS offset : 0.000686857 seconds</span><br><span class=\"line\">Frequency : 11.188 ppm slow</span><br><span class=\"line\">Residual freq : +0.026 ppm</span><br><span class=\"line\">Skew : 0.786 ppm</span><br><span class=\"line\">Root delay : 0.003966943 seconds</span><br><span class=\"line\">Root dispersion : 0.000785699 seconds</span><br><span class=\"line\">Update interval : 64.8 seconds</span><br><span class=\"line\">Leap status : Normal</span><br></pre></td></tr></table></figure>\n<p>这里能看到更加细致的同步信息,比如时间的便宜量、更新间隔等</p>\n<h4 id=\"chronyc-makestep\"><a href=\"#chronyc-makestep\" class=\"headerlink\" title=\"chronyc makestep\"></a>chronyc makestep</h4><figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">[root@node2 ~]# chronyc makestep</span><br><span class=\"line\">200 OK</span><br></pre></td></tr></table></figure>\n<p>这个命令主要用于时间跨度太大一次性直接进行同步的,由于时钟的调整是非常微妙要求精确的,时间跨度太大的话同步完成的周期可能比较久,所以可以通过这个命令直接同步。</p>\n"}],"PostAsset":[],"PostCategory":[{"post_id":"cm00w1n8u0001p5on0ekjb9o1","category_id":"cm00w1n8w0003p5ondffobwbd","_id":"cm00w1n8x0007p5on7c6iaq6l"},{"post_id":"cm00w9dq40000zeonf4g6cvaq","category_id":"cm01sy2xy0008baon4n6fdlgq","_id":"cm01sy2xz0009baonfgfc5u4t"},{"post_id":"cm00w1n8x000ap5on3hlyhfk3","category_id":"cm00w1n8w0003p5ondffobwbd","_id":"cm027allq0002kconfan383dp"},{"post_id":"cm03rbqwp000089on4momc5ee","category_id":"cm03rbqwr000189onei977a6a","_id":"cm03rbqws000589onhmo964ww"},{"post_id":"cm0gkxi4j0000qzonghw7dafa","category_id":"cm0gkxi4m0001qzonby0c5k2y","_id":"cm0gkxi4n0004qzon12z40zq0"},{"post_id":"cm0uqbc8k00003ponhvvr14w2","category_id":"cm01sy2xy0008baon4n6fdlgq","_id":"cm0uqgr3t000225ona0gxfejt"}],"PostTag":[{"post_id":"cm00w1n8u0001p5on0ekjb9o1","tag_id":"cm00w1n8w0004p5onf99g1z86","_id":"cm00w1n8x0008p5onfcw4dzyl"},{"post_id":"cm00w1n8u0001p5on0ekjb9o1","tag_id":"cm00w1n8x0006p5onfocb2z3o","_id":"cm00w1n8x0009p5ondqt7grt9"},{"post_id":"cm00w9dq40000zeonf4g6cvaq","tag_id":"cm01stwff0000jmon2dpl3ozr","_id":"cm01stwfg0002jmonb8299tn1"},{"post_id":"cm00w9dq40000zeonf4g6cvaq","tag_id":"cm01sy4x6000abaon7r1377p8","_id":"cm01sy4x7000bbaon0kcu7es5"},{"post_id":"cm00w1n8x000ap5on3hlyhfk3","tag_id":"cm00w1n8x0006p5onfocb2z3o","_id":"cm027allp0000kcon2i51db52"},{"post_id":"cm00w1n8x000ap5on3hlyhfk3","tag_id":"cm00w1n8x000bp5ongo5j0ix1","_id":"cm027allq0001kcon07n231kx"},{"post_id":"cm00w1n8x000ap5on3hlyhfk3","tag_id":"cm00w1n8y000dp5oncjc89tk0","_id":"cm027allq0003kconchtj6qw1"},{"post_id":"cm00w1n8x000ap5on3hlyhfk3","tag_id":"cm00w1n8y000ep5onbgmndtea","_id":"cm027allq0004kcon0fqce6kl"},{"post_id":"cm03rbqwp000089on4momc5ee","tag_id":"cm00w1n8x0006p5onfocb2z3o","_id":"cm03rbqws000389ongc513q4h"},{"post_id":"cm03rbqwp000089on4momc5ee","tag_id":"cm03rbqws000289ongz7p6l1s","_id":"cm03rbqws000489onhthu88r3"},{"post_id":"cm0gkxi4j0000qzonghw7dafa","tag_id":"cm0gkxi4n0002qzonbcucd5dq","_id":"cm0gkxi4o0006qzon5kkr7gei"},{"post_id":"cm0gkxi4j0000qzonghw7dafa","tag_id":"cm0gkxi4n0003qzon2b0l7vm4","_id":"cm0gkxi4o0007qzon1n6hhs0f"},{"post_id":"cm0gkxi4j0000qzonghw7dafa","tag_id":"cm00w1n8x0006p5onfocb2z3o","_id":"cm0gkxi4o0008qzonbw4068oj"},{"post_id":"cm0gkxi4j0000qzonghw7dafa","tag_id":"cm0gkxi4n0005qzongo98e4uu","_id":"cm0gkxi4o0009qzon6eyh04m4"},{"post_id":"cm0uqbc8k00003ponhvvr14w2","tag_id":"cm00w1n8x0006p5onfocb2z3o","_id":"cm0uqbc8o00043ponbd4qd7sv"},{"post_id":"cm0uqbc8k00003ponhvvr14w2","tag_id":"cm0uqbc8n00013ponaqqq2r06","_id":"cm0uqbc8o00053ponbef05bwe"},{"post_id":"cm0uqbc8k00003ponhvvr14w2","tag_id":"cm0uqbc8o00033pong8jf4kag","_id":"cm0uqbc8o00063pon4i75gpqs"}],"Tag":[{"name":"JAVA","_id":"cm00w1n8w0004p5onf99g1z86"},{"name":"Linux","_id":"cm00w1n8x0006p5onfocb2z3o"},{"name":"Java","_id":"cm00w1n8x000bp5ongo5j0ix1"},{"name":"Golang","_id":"cm00w1n8y000dp5oncjc89tk0"},{"name":"commands","_id":"cm00w1n8y000ep5onbgmndtea"},{"name":"Programming","_id":"cm00w9dq70002zeonczir1cku"},{"name":"Hexo","_id":"cm00w9dq70003zeon5hw26gt3"},{"name":"ansible","_id":"cm01stwff0000jmon2dpl3ozr"},{"name":"自动化运维工具","_id":"cm01stwfg0001jmon0mh94b6s"},{"name":"运维工具","_id":"cm01sy4x6000abaon7r1377p8"},{"name":"ebpf","_id":"cm03rbqws000289ongz7p6l1s"},{"name":"Pacemaker","_id":"cm0gkxi4n0002qzonbcucd5dq"},{"name":"Corosync","_id":"cm0gkxi4n0003qzon2b0l7vm4"},{"name":"高可用方案","_id":"cm0gkxi4n0005qzongo98e4uu"},{"name":"Chronyd","_id":"cm0uqbc8n00013ponaqqq2r06"},{"name":"时钟同步","_id":"cm0uqbc8o00033pong8jf4kag"}]}}