一、mac端
+mac是typec的,用了个转接头+网线直连主机,初始化的时候跟下面一致,默认都是自动的
From 824c9b3534b9be82d677c16f20107f821ffddebe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:36:35 +0000 Subject: [PATCH] Deploy to GitHub pages --- 404.html | 47 + CNAME | 1 + about-the-author/index.html | 47 + .../personal-life/2024-07-24.html | 47 + ...\350\277\252\346\226\257\345\260\274.html" | 47 + about-the-author/personal-life/index.html | 47 + about-the-author/personal-life/wewe.html | 47 + about-the-author/works/index.html | 47 + ...\344\272\272\344\275\234\345\223\201.html" | 47 + ads.txt | 1 + article/index.html | 59 + ...\346\236\266\346\236\204.html-B9tl8qmz.js" | 1 + ...\346\224\271\347\211\210.html-BVYNR54w.js" | 72 + assets/1mysql.html-CW43wjS_.js | 1 + ...\344\275\277\347\224\250.html-9FjHGi9W.js" | 226 + assets/2017.html-DzgcI9cv.js | 1 + assets/2018.html-UHfvoYE6.js | 1 + assets/2019.html-Df2iUZPh.js | 1 + assets/2024-07-24.html-9GUkpnJj.js | 1 + ...\346\226\257\345\260\274.html-CU80Bk9D.js" | 1 + ...\344\273\273\345\212\241.html-NSgzVWXM.js" | 61 + ...\347\263\273\347\273\237.html-BH3nB-v9.js" | 206 + assets/404.html-BMHKR2ag.js | 1 + ...\351\203\250\347\275\262.html-BN5IQG4o.js" | 96 + ...\345\244\207\344\273\275.html-Ca5VIWc1.js" | 61 + ...\346\217\222\344\273\266.html-Br7NfWE4.js" | 13 + ...\345\210\206\346\236\220.html-DopYDuB6.js" | 1 + ...\344\274\230\345\214\226.html-8ms_Vznn.js" | 1 + ...HTTPS\343\200\201HTTP2.0.html-DvomVRAD.js" | 1 + ...\345\217\202\346\225\260.html-BtZgPFK4.js" | 24 + ...\346\250\241\345\236\213.html-Bis3Hglq.js" | 150 + ...\347\254\224\350\256\260.html--8cnUWGp.js" | 16 + ...\346\224\266\351\233\206.html-SN4rN7dm.js" | 161 + ...\345\210\206\346\236\220.html-DBlO1MVO.js" | 199 + ...\344\275\277\347\224\250.html-CvPEGSZS.js" | 49 + assets/aop.html-C7ud1qex.js | 51 + assets/app-ftEjETWs.js | 330 + assets/chatgpt.html-BgeRWiYw.js | 101 + assets/cms.html-Dfw5I__q.js | 1 + assets/devops-ping-tai.html-CjTXGrBr.js | 237 + assets/elastic-spark.html-Cz2jVT52.js | 27 + ...272\220\347\240\201debug.html-Bo6HcrbI.js" | 1 + assets/feed.html-BcapapFL.js | 1 + assets/g1.html-CcgAHdWl.js | 1 + assets/giscus-BZxmVUME.js | 66 + assets/icon/apple-icon-152.png | Bin 0 -> 3086 bytes assets/icon/chrome-192.png | Bin 0 -> 3760 bytes assets/icon/chrome-512.png | Bin 0 -> 9230 bytes assets/icon/chrome-mask-192.png | Bin 0 -> 4079 bytes assets/icon/chrome-mask-512.png | Bin 0 -> 9208 bytes assets/icon/guide-maskable.png | Bin 0 -> 1691 bytes assets/icon/guide-monochrome.png | Bin 0 -> 1465 bytes assets/icon/ms-icon-144.png | Bin 0 -> 3445 bytes ...\350\256\276\347\275\256.html-BThIkzaw.js" | 17 + assets/index-AN989yVn.js | 61 + assets/index.html-0EEVTum5.js | 1 + assets/index.html-5ZTxzR_p.js | 1 + assets/index.html-7tsgb-dC.js | 1 + assets/index.html-B1Akg086.js | 1 + assets/index.html-B2fh3Mku.js | 1 + assets/index.html-B8Qu0Pmr.js | 1 + assets/index.html-B9smc87Y.js | 1 + assets/index.html-BAbeWXfD.js | 1 + assets/index.html-BEv74XKv.js | 1 + assets/index.html-BKAyyte1.js | 1 + assets/index.html-BTDM2Wpl.js | 1 + assets/index.html-BXiz75su.js | 1 + assets/index.html-BcZZ4qCt.js | 1 + assets/index.html-BwVnwmVL.js | 1 + assets/index.html-By-pQ7Wl.js | 1 + assets/index.html-C9P5ouBn.js | 1 + assets/index.html-CD6aFyf5.js | 1 + assets/index.html-CMe--mJk.js | 1 + assets/index.html-CY6pLx2d.js | 1 + assets/index.html-CdxL9VtY.js | 1 + assets/index.html-CpCz4LWW.js | 1 + assets/index.html-D1R9zQcp.js | 1 + assets/index.html-D4WG7yPk.js | 1 + assets/index.html-DGy09Ap4.js | 1 + assets/index.html-DJHdkCFZ.js | 1 + assets/index.html-DJZmVM8a.js | 1 + assets/index.html-DQFfT5fQ.js | 1 + assets/index.html-DRyhNgG6.js | 1 + assets/index.html-DWXiwN5o.js | 1 + assets/index.html-DYmvtxA9.js | 1 + assets/index.html-DaAoZU-K.js | 1 + assets/index.html-DlwW5Vje.js | 1 + assets/index.html-DqVhfNm4.js | 1 + assets/index.html-DtDo2qGn.js | 1 + assets/index.html-KwQlr7R4.js | 1 + assets/index.html-SdCcjUz-.js | 1 + assets/index.html-TDa4J3OS.js | 1 + assets/index.html-Uqis1PLJ.js | 1 + assets/index.html-jGjDZXTI.js | 1 + assets/index.html-muK9iEFv.js | 1 + assets/index.html-nZcQpO1z.js | 1 + assets/index.html-wU0YCmBl.js | 1 + ...346\213\237\346\234\272).html-CpFuSPpy.js" | 7 + assets/kafka.html-tMeeu2BM.js | 1 + ...\344\270\273\346\234\272.html-BbWWw_2k.js" | 41 + assets/main.html-B2OQipck.js | 1 + assets/nio.html-DVcQhX_V.js | 1 + assets/photoswipe.esm-GXRgw7eJ.js | 4 + ...\347\274\223\345\255\230.html-H1xCPktw.js" | 87 + assets/request_limit.html-CBNYeI2L.js | 7 + assets/serverlog.html-bICZU0Nm.js | 2 + assets/spark on k8s operator.html-D9Tpznzk.js | 67 + assets/springcloud_sentinel.html-CFCKUB71.js | 1 + assets/starcraft-ai.html-C018rwGF.js | 1 + assets/style-BjEFg8CQ.css | 1 + assets/synchronized.html-B2MTfSju.js | 59 + assets/tesla.html-K0riacrL.js | 23 + assets/tiktok2023.html-BxK5gJWF.js | 1 + assets/volatile.html-C-UWQAjc.js | 1 + assets/webflux.html-KjGFiVkN.js | 1 + assets/wewe.html-BLY5_jfL.js | 1 + assets/zgc.html-Cj33F57G.js | 4 + assets/zookeeper.html-Dd8HToeq.js | 1 + ...\350\257\246\350\247\243.html-Dg8HV8OO.js" | 275 + ...\345\221\275\344\273\244.html-BtEABYaE.js" | 1 + ...\350\277\207\347\250\213.html-zaM0dK5_.js" | 4 + ...\345\270\214\346\247\275.html-a1P2YWrj.js" | 1 + ...\344\275\234\345\223\201.html-DDpX9I3v.js" | 1 + ...\345\260\217\350\256\260.html-Bb5S9Lg5.js" | 4 + ...\344\270\273\346\234\272.html-DMCiuYMs.js" | 56 + ...\344\275\277\347\224\250.html-DfOcEEjE.js" | 1 + ...\347\272\277\347\250\213.html-DsVzFk6c.js" | 54 + ...\347\233\221\346\216\247.html-BNdB--VB.js" | 1 + ...\347\210\254\350\231\253.html-D6Y4vXXb.js" | 179 + ...\351\231\220\346\265\201.html-B1ZIsZdu.js" | 49 + ...\345\237\272\347\241\200.html-Bsofr9G8.js" | 1 + ...\347\274\226\350\257\221.html-BNnmGgbc.js" | 8 + ...\346\224\266\345\231\250.html-Br8UVQp8.js" | 5 + ...\346\212\223\345\217\226.html-CPwBtyqR.js" | 108 + ...\346\225\264\347\220\206.html-BKoUISo4.js" | 1 + ...\347\256\227\346\263\225.html-BVqGPH4F.js" | 1 + ...\346\246\202\350\277\260.html-CyDH_f4g.js" | 1 + ...\345\205\245\351\227\250.html-DF2q_M-N.js" | 106 + ...\347\274\223\345\255\230.html-DNuD8_hU.js" | 1 + ...\226\346\216\222LiteFlow.html-CvCVGQll.js" | 124 + ...\347\224\265\345\225\206.html-CwtUep3k.js" | 1 + ...\345\220\221\351\224\201.html-DwU2qcUM.js" | 1 + ...\346\200\273\347\273\223.html-Zcj8usmP.js" | 1 + ...\346\200\235\350\267\257.html-DzuQiit4.js" | 1 + ...\345\212\233\346\226\257.html-BrY99iGU.js" | 1 + ...\345\217\257\347\224\250.html-DjOPo7tP.js" | 1 + ...\345\271\266\345\217\221.html-B4hi1h21.js" | 1 + ...\346\200\247\350\203\275.html-CN7rmRWu.js" | 1 + ...\346\200\235\350\200\203.html-DLPHuyVJ.js" | 1 + atom.xml | 7493 +++++++++++++++++ atom.xsl | 534 ++ bdunion.txt | 1 + bigdata/index.html | 47 + bigdata/spark/elastic-spark.html | 73 + bigdata/spark/index.html | 47 + category/index.html | 47 + cloudnative/index.html | 47 + ...csearch\346\272\220\347\240\201debug.html" | 47 + database/elasticsearch/index.html | 47 + ...\347\250\213\350\257\246\350\247\243.html" | 321 + .../\345\237\272\347\241\200.html" | 47 + database/index.html | 47 + database/mysql/1mysql.html | 47 + database/mysql/index.html | 47 + ...\345\272\223\347\274\223\345\255\230.html" | 47 + ...\346\204\217\345\220\221\351\224\201.html" | 47 + ...\347\240\201\345\210\206\346\236\220.html" | 245 + database/redis/index.html | 47 + .../redis/redis\347\274\223\345\255\230.html" | 133 + ...\345\223\210\345\270\214\346\247\275.html" | 47 + donate/index.html | 47 + favicon.ico | Bin 0 -> 21722 bytes feed.json | 1158 +++ index.html | 59 + interesting/chatgpt.html | 147 + .../idea\350\256\276\347\275\256.html" | 63 + interesting/index.html | 47 + .../mini\344\270\273\346\234\272/index.html" | 47 + ...\346\216\245\344\270\273\346\234\272.html" | 87 + ...\270\252mini\344\270\273\346\234\272.html" | 102 + interesting/starcraft-ai.html | 47 + interesting/tesla.html | 69 + ...\347\232\204\345\221\275\344\273\244.html" | 47 + ...\344\270\216\346\236\266\346\236\204.html" | 47 + ...\347\273\210\346\224\271\347\211\210.html" | 118 + ...\347\232\204\344\275\277\347\224\250.html" | 272 + ...\346\227\266\344\273\273\345\212\241.html" | 107 + ...\345\277\227\347\263\273\347\273\237.html" | 252 + ...\347\276\244\351\203\250\347\275\262.html" | 142 + ...\345\272\223\345\244\207\344\273\275.html" | 107 + ...\347\232\204\346\217\222\344\273\266.html" | 59 + ...\346\204\237\345\210\206\346\236\220.html" | 47 + ...\350\203\275\344\274\230\345\214\226.html" | 47 + .../index.html" | 47 + ...\345\212\241\347\233\221\346\216\247.html" | 47 + ...\345\217\215\347\274\226\350\257\221.html" | 54 + ...\351\230\205\346\212\223\345\217\226.html" | 154 + ...\345\205\267\346\225\264\347\220\206.html" | 47 + ...\344\271\213\347\224\265\345\225\206.html" | 47 + interview/index.html | 47 + interview/tiktok2023.html | 47 + ...\344\274\230\345\217\202\346\225\260.html" | 70 + ...\345\255\230\346\250\241\345\236\213.html" | 196 + java/JVM/cms.html | 47 + java/JVM/g1.html | 47 + java/JVM/index.html | 47 + ...350\231\232\346\213\237\346\234\272).html" | 53 + java/JVM/zgc.html | 50 + ...\344\274\230\350\277\207\347\250\213.html" | 50 + ...\345\233\236\346\224\266\345\231\250.html" | 51 + ...\350\276\276\347\256\227\346\263\225.html" | 47 + ...\344\274\230\346\200\235\350\267\257.html" | 47 + ...t Prometheus\344\275\277\347\224\250.html" | 95 + java/SpringBoot/aop.html | 97 + java/SpringBoot/index.html | 47 + java/SpringBoot/webflux.html | 47 + java/index.html | 47 + java/io/index.html | 47 + java/io/nio.html | 47 + java/serverlog.html | 48 + ...\346\213\237\347\272\277\347\250\213.html" | 100 + ...\345\274\217\351\231\220\346\265\201.html" | 95 + ...\213\347\274\226\346\216\222LiteFlow.html" | 170 + "java/\347\272\277\347\250\213/index.html" | 47 + .../synchronized.html" | 105 + "java/\347\272\277\347\250\213/volatile.html" | 47 + ...\343\200\201HTTPS\343\200\201HTTP2.0.html" | 47 + "java/\347\275\221\347\273\234/index.html" | 47 + ...\351\253\230\345\217\257\347\224\250.html" | 47 + ...\351\253\230\345\271\266\345\217\221.html" | 47 + ...\351\253\230\346\200\247\350\203\275.html" | 47 + ...\344\272\233\346\200\235\350\200\203.html" | 47 + ...\344\272\233\347\254\224\350\256\260.html" | 62 + ...\345\277\227\346\224\266\351\233\206.html" | 207 + kubernetes/devops/devops-ping-tai.html | 283 + kubernetes/devops/index.html | 47 + kubernetes/index.html | 47 + kubernetes/request_limit.html | 53 + kubernetes/spark on k8s operator.html | 113 + life/2017.html | 47 + life/2018.html | 47 + life/2019.html | 47 + life/index.html | 47 + link/index.html | 47 + link/main.html | 47 + logo.png | Bin 0 -> 21722 bytes logo.svg | 101 + middleware/canal/index.html | 47 + ...200\224canal\345\260\217\350\256\260.html" | 50 + middleware/index.html | 47 + middleware/kafka/index.html | 47 + middleware/kafka/kafka.html | 47 + middleware/sentinel/index.html | 47 + middleware/sentinel/springcloud_sentinel.html | 47 + ...\351\227\250\344\275\277\347\224\250.html" | 47 + middleware/zookeeper/index.html | 47 + middleware/zookeeper/zookeeper.html | 47 + ...\345\210\227\347\210\254\350\231\253.html" | 225 + open-source-project/index.html | 47 + robots.txt | 5 + rss.xml | 7048 ++++++++++++++++ rss.xsl | 506 ++ sitemap.xml | 3 + sitemap.xsl | 207 + slide/index.html | 47 + star/index.html | 47 + stock/index.html | 47 + ...\350\265\233\345\212\233\346\226\257.html" | 47 + system-design/feed.html | 47 + system-design/index.html | 47 + .../index.html" | 47 + ...347\250\213-\346\246\202\350\277\260.html" | 47 + ...\347\273\237\345\205\245\351\227\250.html" | 152 + .../index.html" | 47 + ...\351\227\250\346\200\273\347\273\223.html" | 47 + tag/index.html | 47 + timeline/index.html | 47 + 277 files changed, 29538 insertions(+) create mode 100644 404.html create mode 100644 CNAME create mode 100644 about-the-author/index.html create mode 100644 about-the-author/personal-life/2024-07-24.html create mode 100644 "about-the-author/personal-life/2024-11-09\344\270\212\346\265\267\350\277\252\346\226\257\345\260\274.html" create mode 100644 about-the-author/personal-life/index.html create mode 100644 about-the-author/personal-life/wewe.html create mode 100644 about-the-author/works/index.html create mode 100644 "about-the-author/works/\344\270\252\344\272\272\344\275\234\345\223\201.html" create mode 100644 ads.txt create mode 100644 article/index.html create mode 100644 "assets/1.\345\216\206\345\217\262\344\270\216\346\236\266\346\236\204.html-B9tl8qmz.js" create mode 100644 "assets/10.\345\216\206\346\227\2668\345\271\264\346\234\200\347\273\210\346\224\271\347\211\210.html-BVYNR54w.js" create mode 100644 assets/1mysql.html-CW43wjS_.js create mode 100644 "assets/2.Lucene\347\232\204\344\275\277\347\224\250.html-9FjHGi9W.js" create mode 100644 assets/2017.html-DzgcI9cv.js create mode 100644 assets/2018.html-UHfvoYE6.js create mode 100644 assets/2019.html-Df2iUZPh.js create mode 100644 assets/2024-07-24.html-9GUkpnJj.js create mode 100644 "assets/2024-11-09\344\270\212\346\265\267\350\277\252\346\226\257\345\260\274.html-CU80Bk9D.js" create mode 100644 "assets/3.\345\256\232\346\227\266\344\273\273\345\212\241.html-NSgzVWXM.js" create mode 100644 "assets/4.\346\227\245\345\277\227\347\263\273\347\273\237.html-BH3nB-v9.js" create mode 100644 assets/404.html-BMHKR2ag.js create mode 100644 "assets/5.\345\260\217\351\233\206\347\276\244\351\203\250\347\275\262.html-BN5IQG4o.js" create mode 100644 "assets/6.\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html-Ca5VIWc1.js" create mode 100644 "assets/7.\351\202\243\344\272\233\347\211\233\351\200\274\347\232\204\346\217\222\344\273\266.html-Br7NfWE4.js" create mode 100644 "assets/8.\345\237\272\344\272\216\350\264\235\345\217\266\346\226\257\347\232\204\346\203\205\346\204\237\345\210\206\346\236\220.html-DopYDuB6.js" create mode 100644 "assets/9.\347\275\221\347\253\231\346\200\247\350\203\275\344\274\230\345\214\226.html-8ms_Vznn.js" create mode 100644 "assets/IP\343\200\201HTTP\343\200\201HTTPS\343\200\201HTTP2.0.html-DvomVRAD.js" create mode 100644 "assets/JVM\350\260\203\344\274\230\345\217\202\346\225\260.html-BtZgPFK4.js" create mode 100644 "assets/Java\345\206\205\345\255\230\346\250\241\345\236\213.html-Bis3Hglq.js" create mode 100644 "assets/Jenkins\347\232\204\344\270\200\344\272\233\347\254\224\350\256\260.html--8cnUWGp.js" create mode 100644 "assets/Kubernetes\345\256\271\345\231\250\346\227\245\345\277\227\346\224\266\351\233\206.html-SN4rN7dm.js" create mode 100644 "assets/RedissonLock\345\210\206\345\270\203\345\274\217\351\224\201\346\272\220\347\240\201\345\210\206\346\236\220.html-DBlO1MVO.js" create mode 100644 "assets/Spring Boot Prometheus\344\275\277\347\224\250.html-CvPEGSZS.js" create mode 100644 assets/aop.html-C7ud1qex.js create mode 100644 assets/app-ftEjETWs.js create mode 100644 assets/chatgpt.html-BgeRWiYw.js create mode 100644 assets/cms.html-Dfw5I__q.js create mode 100644 assets/devops-ping-tai.html-CjTXGrBr.js create mode 100644 assets/elastic-spark.html-Cz2jVT52.js create mode 100644 "assets/elasticsearch\346\272\220\347\240\201debug.html-Bo6HcrbI.js" create mode 100644 assets/feed.html-BcapapFL.js create mode 100644 assets/g1.html-CcgAHdWl.js create mode 100644 assets/giscus-BZxmVUME.js create mode 100644 assets/icon/apple-icon-152.png create mode 100644 assets/icon/chrome-192.png create mode 100644 assets/icon/chrome-512.png create mode 100644 assets/icon/chrome-mask-192.png create mode 100644 assets/icon/chrome-mask-512.png create mode 100644 assets/icon/guide-maskable.png create mode 100644 assets/icon/guide-monochrome.png create mode 100644 assets/icon/ms-icon-144.png create mode 100644 "assets/idea\350\256\276\347\275\256.html-BThIkzaw.js" create mode 100644 assets/index-AN989yVn.js create mode 100644 assets/index.html-0EEVTum5.js create mode 100644 assets/index.html-5ZTxzR_p.js create mode 100644 assets/index.html-7tsgb-dC.js create mode 100644 assets/index.html-B1Akg086.js create mode 100644 assets/index.html-B2fh3Mku.js create mode 100644 assets/index.html-B8Qu0Pmr.js create mode 100644 assets/index.html-B9smc87Y.js create mode 100644 assets/index.html-BAbeWXfD.js create mode 100644 assets/index.html-BEv74XKv.js create mode 100644 assets/index.html-BKAyyte1.js create mode 100644 assets/index.html-BTDM2Wpl.js create mode 100644 assets/index.html-BXiz75su.js create mode 100644 assets/index.html-BcZZ4qCt.js create mode 100644 assets/index.html-BwVnwmVL.js create mode 100644 assets/index.html-By-pQ7Wl.js create mode 100644 assets/index.html-C9P5ouBn.js create mode 100644 assets/index.html-CD6aFyf5.js create mode 100644 assets/index.html-CMe--mJk.js create mode 100644 assets/index.html-CY6pLx2d.js create mode 100644 assets/index.html-CdxL9VtY.js create mode 100644 assets/index.html-CpCz4LWW.js create mode 100644 assets/index.html-D1R9zQcp.js create mode 100644 assets/index.html-D4WG7yPk.js create mode 100644 assets/index.html-DGy09Ap4.js create mode 100644 assets/index.html-DJHdkCFZ.js create mode 100644 assets/index.html-DJZmVM8a.js create mode 100644 assets/index.html-DQFfT5fQ.js create mode 100644 assets/index.html-DRyhNgG6.js create mode 100644 assets/index.html-DWXiwN5o.js create mode 100644 assets/index.html-DYmvtxA9.js create mode 100644 assets/index.html-DaAoZU-K.js create mode 100644 assets/index.html-DlwW5Vje.js create mode 100644 assets/index.html-DqVhfNm4.js create mode 100644 assets/index.html-DtDo2qGn.js create mode 100644 assets/index.html-KwQlr7R4.js create mode 100644 assets/index.html-SdCcjUz-.js create mode 100644 assets/index.html-TDa4J3OS.js create mode 100644 assets/index.html-Uqis1PLJ.js create mode 100644 assets/index.html-jGjDZXTI.js create mode 100644 assets/index.html-muK9iEFv.js create mode 100644 assets/index.html-nZcQpO1z.js create mode 100644 assets/index.html-wU0YCmBl.js create mode 100644 "assets/jvm(java\350\231\232\346\213\237\346\234\272).html-CpFuSPpy.js" create mode 100644 assets/kafka.html-tMeeu2BM.js create mode 100644 "assets/mac\350\277\236\346\216\245\344\270\273\346\234\272.html-BbWWw_2k.js" create mode 100644 assets/main.html-B2OQipck.js create mode 100644 assets/nio.html-DVcQhX_V.js create mode 100644 assets/photoswipe.esm-GXRgw7eJ.js create mode 100644 "assets/redis\347\274\223\345\255\230.html-H1xCPktw.js" create mode 100644 assets/request_limit.html-CBNYeI2L.js create mode 100644 assets/serverlog.html-bICZU0Nm.js create mode 100644 assets/spark on k8s operator.html-D9Tpznzk.js create mode 100644 assets/springcloud_sentinel.html-CFCKUB71.js create mode 100644 assets/starcraft-ai.html-C018rwGF.js create mode 100644 assets/style-BjEFg8CQ.css create mode 100644 assets/synchronized.html-B2MTfSju.js create mode 100644 assets/tesla.html-K0riacrL.js create mode 100644 assets/tiktok2023.html-BxK5gJWF.js create mode 100644 assets/volatile.html-C-UWQAjc.js create mode 100644 assets/webflux.html-KjGFiVkN.js create mode 100644 assets/wewe.html-BLY5_jfL.js create mode 100644 assets/zgc.html-Cj33F57G.js create mode 100644 assets/zookeeper.html-Dd8HToeq.js create mode 100644 "assets/\343\200\220elasticsearch\343\200\221\346\220\234\347\264\242\350\277\207\347\250\213\350\257\246\350\247\243.html-Dg8HV8OO.js" create mode 100644 "assets/\344\270\200\344\272\233\344\270\215\345\270\270\347\224\250\344\275\206\345\276\210\345\256\236\347\224\250\347\232\204\345\221\275\344\273\244.html-BtEABYaE.js" create mode 100644 "assets/\344\270\200\346\254\241jvm\350\260\203\344\274\230\350\277\207\347\250\213.html-zaM0dK5_.js" create mode 100644 "assets/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\225\345\222\214\345\223\210\345\270\214\346\247\275.html-a1P2YWrj.js" create mode 100644 "assets/\344\270\252\344\272\272\344\275\234\345\223\201.html-DDpX9I3v.js" create mode 100644 "assets/\344\270\255\351\227\264\344\273\266\342\200\224\342\200\224canal\345\260\217\350\256\260.html-Bb5S9Lg5.js" create mode 100644 "assets/\344\271\260\344\272\206\344\270\252mini\344\270\273\346\234\272.html-DMCiuYMs.js" create mode 100644 "assets/\345\205\245\351\227\250\344\275\277\347\224\250.html-DfOcEEjE.js" create mode 100644 "assets/\345\234\250 Spring 6 \344\270\255\344\275\277\347\224\250\350\231\232\346\213\237\347\272\277\347\250\213.html-DsVzFk6c.js" create mode 100644 "assets/\345\237\272\344\272\216OLAP\345\201\232\344\270\232\345\212\241\347\233\221\346\216\247.html-BNdB--VB.js" create mode 100644 "assets/\345\237\272\344\272\216ZooKeeper\347\232\204\351\230\237\345\210\227\347\210\254\350\231\253.html-D6Y4vXXb.js" create mode 100644 "assets/\345\237\272\344\272\216kubernetes\347\232\204\345\210\206\345\270\203\345\274\217\351\231\220\346\265\201.html-B1ZIsZdu.js" create mode 100644 "assets/\345\237\272\347\241\200.html-Bsofr9G8.js" create mode 100644 "assets/\345\260\217\347\250\213\345\272\217\345\217\215\347\274\226\350\257\221.html-BNnmGgbc.js" create mode 100644 "assets/\345\270\270\347\224\250GC\345\233\236\346\224\266\345\231\250.html-Br8UVQp8.js" create mode 100644 "assets/\345\271\277\345\267\236\345\233\276\344\271\246\351\246\206\345\200\237\351\230\205\346\212\223\345\217\226.html-CPwBtyqR.js" create mode 100644 "assets/\345\274\200\345\217\221\345\267\245\345\205\267\346\225\264\347\220\206.html-BKoUISo4.js" create mode 100644 "assets/\345\274\225\347\224\250\350\256\241\346\225\260\345\222\214\346\240\271\345\217\257\350\276\276\347\256\227\346\263\225.html-BVqGPH4F.js" create mode 100644 "assets/\346\216\250\350\215\220\345\267\245\347\250\213-\346\246\202\350\277\260.html-CyDH_f4g.js" create mode 100644 "assets/\346\216\250\350\215\220\347\263\273\347\273\237\345\205\245\351\227\250.html-DF2q_M-N.js" create mode 100644 "assets/\346\225\260\346\215\256\345\272\223\347\274\223\345\255\230.html-DNuD8_hU.js" create mode 100644 "assets/\346\265\201\347\250\213\347\274\226\346\216\222LiteFlow.html-CvCVGQll.js" create mode 100644 "assets/\347\250\213\345\272\217\345\221\230\345\211\257\344\270\232\346\216\242\347\264\242\344\271\213\347\224\265\345\225\206.html-CwtUep3k.js" create mode 100644 "assets/\350\241\214\351\224\201\357\274\214\350\241\250\351\224\201\357\274\214\346\204\217\345\220\221\351\224\201.html-DwU2qcUM.js" create mode 100644 "assets/\350\256\241\347\256\227\345\271\277\345\221\212\345\237\272\346\234\254\346\246\202\345\277\265\345\205\245\351\227\250\346\200\273\347\273\223.html-Zcj8usmP.js" create mode 100644 "assets/\350\260\203\344\274\230\346\200\235\350\267\257.html-DzuQiit4.js" create mode 100644 "assets/\350\265\233\345\212\233\346\226\257.html-BrY99iGU.js" create mode 100644 "assets/\351\253\230\345\217\257\347\224\250.html-DjOPo7tP.js" create mode 100644 "assets/\351\253\230\345\271\266\345\217\221.html-B4hi1h21.js" create mode 100644 "assets/\351\253\230\346\200\247\350\203\275.html-CN7rmRWu.js" create mode 100644 "assets/\351\253\230\346\200\247\350\203\275\351\253\230\345\271\266\345\217\221\351\253\230\345\217\257\347\224\250\347\232\204\344\270\200\344\272\233\346\200\235\350\200\203.html-DLPHuyVJ.js" create mode 100644 atom.xml create mode 100644 atom.xsl create mode 100644 bdunion.txt create mode 100644 bigdata/index.html create mode 100644 bigdata/spark/elastic-spark.html create mode 100644 bigdata/spark/index.html create mode 100644 category/index.html create mode 100644 cloudnative/index.html create mode 100644 "database/elasticsearch/elasticsearch\346\272\220\347\240\201debug.html" create mode 100644 database/elasticsearch/index.html create mode 100644 "database/elasticsearch/\343\200\220elasticsearch\343\200\221\346\220\234\347\264\242\350\277\207\347\250\213\350\257\246\350\247\243.html" create mode 100644 "database/elasticsearch/\345\237\272\347\241\200.html" create mode 100644 database/index.html create mode 100644 database/mysql/1mysql.html create mode 100644 database/mysql/index.html create mode 100644 "database/mysql/\346\225\260\346\215\256\345\272\223\347\274\223\345\255\230.html" create mode 100644 "database/mysql/\350\241\214\351\224\201\357\274\214\350\241\250\351\224\201\357\274\214\346\204\217\345\220\221\351\224\201.html" create mode 100644 "database/redis/RedissonLock\345\210\206\345\270\203\345\274\217\351\224\201\346\272\220\347\240\201\345\210\206\346\236\220.html" create mode 100644 database/redis/index.html create mode 100644 "database/redis/redis\347\274\223\345\255\230.html" create mode 100644 "database/redis/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\225\345\222\214\345\223\210\345\270\214\346\247\275.html" create mode 100644 donate/index.html create mode 100644 favicon.ico create mode 100644 feed.json create mode 100644 index.html create mode 100644 interesting/chatgpt.html create mode 100644 "interesting/idea\350\256\276\347\275\256.html" create mode 100644 interesting/index.html create mode 100644 "interesting/mini\344\270\273\346\234\272/index.html" create mode 100644 "interesting/mini\344\270\273\346\234\272/mac\350\277\236\346\216\245\344\270\273\346\234\272.html" create mode 100644 "interesting/mini\344\270\273\346\234\272/\344\271\260\344\272\206\344\270\252mini\344\270\273\346\234\272.html" create mode 100644 interesting/starcraft-ai.html create mode 100644 interesting/tesla.html create mode 100644 "interesting/\344\270\200\344\272\233\344\270\215\345\270\270\347\224\250\344\275\206\345\276\210\345\256\236\347\224\250\347\232\204\345\221\275\344\273\244.html" create mode 100644 "interesting/\344\270\252\344\272\272\347\275\221\347\253\231/1.\345\216\206\345\217\262\344\270\216\346\236\266\346\236\204.html" create mode 100644 "interesting/\344\270\252\344\272\272\347\275\221\347\253\231/10.\345\216\206\346\227\2668\345\271\264\346\234\200\347\273\210\346\224\271\347\211\210.html" create mode 100644 "interesting/\344\270\252\344\272\272\347\275\221\347\253\231/2.Lucene\347\232\204\344\275\277\347\224\250.html" create mode 100644 "interesting/\344\270\252\344\272\272\347\275\221\347\253\231/3.\345\256\232\346\227\266\344\273\273\345\212\241.html" create mode 100644 "interesting/\344\270\252\344\272\272\347\275\221\347\253\231/4.\346\227\245\345\277\227\347\263\273\347\273\237.html" create mode 100644 "interesting/\344\270\252\344\272\272\347\275\221\347\253\231/5.\345\260\217\351\233\206\347\276\244\351\203\250\347\275\262.html" create mode 100644 "interesting/\344\270\252\344\272\272\347\275\221\347\253\231/6.\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html" create mode 100644 "interesting/\344\270\252\344\272\272\347\275\221\347\253\231/7.\351\202\243\344\272\233\347\211\233\351\200\274\347\232\204\346\217\222\344\273\266.html" create mode 100644 "interesting/\344\270\252\344\272\272\347\275\221\347\253\231/8.\345\237\272\344\272\216\350\264\235\345\217\266\346\226\257\347\232\204\346\203\205\346\204\237\345\210\206\346\236\220.html" create mode 100644 "interesting/\344\270\252\344\272\272\347\275\221\347\253\231/9.\347\275\221\347\253\231\346\200\247\350\203\275\344\274\230\345\214\226.html" create mode 100644 "interesting/\344\270\252\344\272\272\347\275\221\347\253\231/index.html" create mode 100644 "interesting/\345\237\272\344\272\216OLAP\345\201\232\344\270\232\345\212\241\347\233\221\346\216\247.html" create mode 100644 "interesting/\345\260\217\347\250\213\345\272\217\345\217\215\347\274\226\350\257\221.html" create mode 100644 "interesting/\345\271\277\345\267\236\345\233\276\344\271\246\351\246\206\345\200\237\351\230\205\346\212\223\345\217\226.html" create mode 100644 "interesting/\345\274\200\345\217\221\345\267\245\345\205\267\346\225\264\347\220\206.html" create mode 100644 "interesting/\347\250\213\345\272\217\345\221\230\345\211\257\344\270\232\346\216\242\347\264\242\344\271\213\347\224\265\345\225\206.html" create mode 100644 interview/index.html create mode 100644 interview/tiktok2023.html create mode 100644 "java/JVM/JVM\350\260\203\344\274\230\345\217\202\346\225\260.html" create mode 100644 "java/JVM/Java\345\206\205\345\255\230\346\250\241\345\236\213.html" create mode 100644 java/JVM/cms.html create mode 100644 java/JVM/g1.html create mode 100644 java/JVM/index.html create mode 100644 "java/JVM/jvm(java\350\231\232\346\213\237\346\234\272).html" create mode 100644 java/JVM/zgc.html create mode 100644 "java/JVM/\344\270\200\346\254\241jvm\350\260\203\344\274\230\350\277\207\347\250\213.html" create mode 100644 "java/JVM/\345\270\270\347\224\250GC\345\233\236\346\224\266\345\231\250.html" create mode 100644 "java/JVM/\345\274\225\347\224\250\350\256\241\346\225\260\345\222\214\346\240\271\345\217\257\350\276\276\347\256\227\346\263\225.html" create mode 100644 "java/JVM/\350\260\203\344\274\230\346\200\235\350\267\257.html" create mode 100644 "java/SpringBoot/Spring Boot Prometheus\344\275\277\347\224\250.html" create mode 100644 java/SpringBoot/aop.html create mode 100644 java/SpringBoot/index.html create mode 100644 java/SpringBoot/webflux.html create mode 100644 java/index.html create mode 100644 java/io/index.html create mode 100644 java/io/nio.html create mode 100644 java/serverlog.html create mode 100644 "java/\345\234\250 Spring 6 \344\270\255\344\275\277\347\224\250\350\231\232\346\213\237\347\272\277\347\250\213.html" create mode 100644 "java/\345\237\272\344\272\216kubernetes\347\232\204\345\210\206\345\270\203\345\274\217\351\231\220\346\265\201.html" create mode 100644 "java/\346\265\201\347\250\213\347\274\226\346\216\222LiteFlow.html" create mode 100644 "java/\347\272\277\347\250\213/index.html" create mode 100644 "java/\347\272\277\347\250\213/synchronized.html" create mode 100644 "java/\347\272\277\347\250\213/volatile.html" create mode 100644 "java/\347\275\221\347\273\234/IP\343\200\201HTTP\343\200\201HTTPS\343\200\201HTTP2.0.html" create mode 100644 "java/\347\275\221\347\273\234/index.html" create mode 100644 "java/\351\253\230\345\217\257\347\224\250.html" create mode 100644 "java/\351\253\230\345\271\266\345\217\221.html" create mode 100644 "java/\351\253\230\346\200\247\350\203\275.html" create mode 100644 "java/\351\253\230\346\200\247\350\203\275\351\253\230\345\271\266\345\217\221\351\253\230\345\217\257\347\224\250\347\232\204\344\270\200\344\272\233\346\200\235\350\200\203.html" create mode 100644 "kubernetes/Jenkins\347\232\204\344\270\200\344\272\233\347\254\224\350\256\260.html" create mode 100644 "kubernetes/Kubernetes\345\256\271\345\231\250\346\227\245\345\277\227\346\224\266\351\233\206.html" create mode 100644 kubernetes/devops/devops-ping-tai.html create mode 100644 kubernetes/devops/index.html create mode 100644 kubernetes/index.html create mode 100644 kubernetes/request_limit.html create mode 100644 kubernetes/spark on k8s operator.html create mode 100644 life/2017.html create mode 100644 life/2018.html create mode 100644 life/2019.html create mode 100644 life/index.html create mode 100644 link/index.html create mode 100644 link/main.html create mode 100644 logo.png create mode 100644 logo.svg create mode 100644 middleware/canal/index.html create mode 100644 "middleware/canal/\344\270\255\351\227\264\344\273\266\342\200\224\342\200\224canal\345\260\217\350\256\260.html" create mode 100644 middleware/index.html create mode 100644 middleware/kafka/index.html create mode 100644 middleware/kafka/kafka.html create mode 100644 middleware/sentinel/index.html create mode 100644 middleware/sentinel/springcloud_sentinel.html create mode 100644 "middleware/sentinel/\345\205\245\351\227\250\344\275\277\347\224\250.html" create mode 100644 middleware/zookeeper/index.html create mode 100644 middleware/zookeeper/zookeeper.html create mode 100644 "middleware/zookeeper/\345\237\272\344\272\216ZooKeeper\347\232\204\351\230\237\345\210\227\347\210\254\350\231\253.html" create mode 100644 open-source-project/index.html create mode 100644 robots.txt create mode 100644 rss.xml create mode 100644 rss.xsl create mode 100644 sitemap.xml create mode 100644 sitemap.xsl create mode 100644 slide/index.html create mode 100644 star/index.html create mode 100644 stock/index.html create mode 100644 "stock/\350\265\233\345\212\233\346\226\257.html" create mode 100644 system-design/feed.html create mode 100644 system-design/index.html create mode 100644 "system-design/\346\216\250\350\215\220\347\263\273\347\273\237/index.html" create mode 100644 "system-design/\346\216\250\350\215\220\347\263\273\347\273\237/\346\216\250\350\215\220\345\267\245\347\250\213-\346\246\202\350\277\260.html" create mode 100644 "system-design/\346\216\250\350\215\220\347\263\273\347\273\237/\346\216\250\350\215\220\347\263\273\347\273\237\345\205\245\351\227\250.html" create mode 100644 "system-design/\350\256\241\347\256\227\345\271\277\345\221\212/index.html" create mode 100644 "system-design/\350\256\241\347\256\227\345\271\277\345\221\212/\350\256\241\347\256\227\345\271\277\345\221\212\345\237\272\346\234\254\346\246\202\345\277\265\345\205\245\351\227\250\346\200\273\347\273\223.html" create mode 100644 tag/index.html create mode 100644 timeline/index.html diff --git a/404.html b/404.html new file mode 100644 index 00000000..e2fcb701 --- /dev/null +++ b/404.html @@ -0,0 +1,47 @@ + + +
+ + + + + + +各位大佬瞄一眼我的个人网站呗 。如果觉得不错,希望能在GitHub上麻烦给个star,GitHub地址https://github.com/Zephery/newblog 。
大学的时候萌生的一个想法,就是建立一个个人网站,前前后后全部推翻重构了4、5遍,现在终于能看了,下面是目前的首页。
由原本的ssh变成ssm,再变成ssm+shiro+lucene,到现在的前后台分离。前台使用ssm+lucene,后台使用spring boot+shiro。其中,使用pagehelper作为分页,lucene用来搜索和自动补全,使用百度统计的API做了个日志系统,统计pv和uv什么的,同时,还有使用了JMX来观察JVM的使用和cpu的使用率,机器学习方面,使用了adaboost和朴素贝叶斯对微博进行分类,有兴趣的可以点点有点意思这个页面。
本文从下面这几个方面来讲讲网站的建立:
起初,是因为学习的时候老是找不到什么好玩而又有挑战性的项目,看着struts、spring、hibernate的书,全都是一些小项目,做了感觉也没啥意义,有时候在博客园看到别人还有自己的网站,特羡慕,最终就选择了自己做一个个人网站。期初刚学的ssh,于是开始了自己的ssh搭建个人网站的行程,但是对于一个后端的人来说,前端是个大问题啊。。。。所以初版的时候差不多全是纯生的html、css、js,至少发博客这个功能实现了,但是确实没法看。前前后后折腾了一个多月,决定推翻重做,到百度看看别人怎么做的。首先看到的是杨青的网站,已经好几年没更新了,前端的代码看起来比较简单,也是自己能够掌握的,但是不够美观,继续找,在模板之家发现了一个高大上的模板。
第二版的界面确实是这样的,只是把图片的切换变成了wowslider,也是简单的用bootstrap和pagehelper做了下分页,现在的最终版保留了它的header,然后评论框使用了多说(超级怀念多说)。后端也由原来的ssh变成了ssm,之后加上了lucene来对文章进行索引。之后,随着多说要关闭了,突然之间有很多div都不适应了(我写死了的。。。),再一次,没法看,不想看,一怒之下再次推翻重做,变成了现在这个版本。
最终版本在考虑时,也找了很多模板,影响深刻的是tale和欲思这两个主题,期中,tale使用的是java语言写的,刚知道的那一刻我就没好感了,java后端我是要自己全部写的,tale这个页面简洁但是不够炫,而且内容量太低,可能就只是个纯博客,之后发现了欲思,拓展性强,只可惜没有静态的版本,后台是纯生的PHP(嗯,PHP是世界上最好的语言),看了看,没事,保存网页,前端自己改,后端自己全部重写,最终变成了现在这个版本,虽然拼接的时候各种css、js混入。。。。还好在做网站性能优化的时候差不多全部去掉了。最终版加入redis、quartz、shiro等,还有python机器学习、flask的restful api,可谓是大杂烩了。
页面看着还算凑合,至少不是那种看都看不过去的那种了,但是仔细看看,还是有不少问题的,比如瀑布流,还有排版什么的。只能等自己什么时候想认真学学前端的东西了。
已经部署在腾讯云服务器上,存储使用了七牛云的cdn。
最终版的技术架构图如下:
网站核心主要采用Spring SpringMVC和Mybatis,下图是当访问一篇博客的时候的运行流程,参考了张开涛的博客。
运行流程分析
日志系统架构如下:
日志系统曾经尝试采用过ELK,实时监控实在是让人不能不称赞,本地也跑起来了,但是一到服务器,卡卡卡,毕竟(1Ghz CPU、1G内存),只能放弃ELK,采用百度统计。百度统计提供了Tongji API供开发者使用,只是有次数限制,2000/日,实时的话实在有点低,只能统计前几天的PV、UV等开放出来。其实这个存放在mysql也行,不过这些零碎的数据还是放在redis中,方便管理。
出了日志系统,自己对服务器的一些使用率也是挺关心的,毕竟服务器配置太低,于是利用了使用了tomcat的JMX来对CPU和jvm使用情况进行监控,这两个都是实时的。出了这两个,对内存的分配做了监控,Eden、Survivor、Tenured的使用情况。
本人大学里的毕业设计就是基于AdaBoost算法的情感分类,学到的东西还是要经常拿出来看看,要不然真的浪费了我这么久努力做的毕业设计啊。构建了一个基本的情感分类小系统,每天抓取微博进行分类存储在MySql上,并使用flask提供Restful API给java调用,可以点击这里尝试(请忽略Google的图片)。目前分类效果不是很明显,准确率大概只有百分之70%,因为训练样本只有500条(找不到训练样本),机器学习真的太依赖样本的标注。这个,只能请教各位路人大神指导指导了。
断断续续,也总算做了一个能拿得出手仍要遮遮掩掩才能给别人看的网站,哈哈哈哈哈,也劳烦各位帮忙找找bug。前前后后从初学Java EE就一直设想的事也还算有了个了结,以后要多多看书,写点精品文章。PS:GitHub上求给个Star,以后面试能讲讲这个网站。
',27)]))}const s=t(n,[["render",l],["__file","1.历史与架构.html.vue"]]),p=JSON.parse('{"path":"/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/1.%E5%8E%86%E5%8F%B2%E4%B8%8E%E6%9E%B6%E6%9E%84.html","title":"1.历史与架构","lang":"zh-CN","frontmatter":{"description":"1.历史与架构 各位大佬瞄一眼我的个人网站呗 。如果觉得不错,希望能在GitHub上麻烦给个star,GitHub地址https://github.com/Zephery/newblog 。 大学的时候萌生的一个想法,就是建立一个个人网站,前前后后全部推翻重构了4、5遍,现在终于能看了,下面是目前的首页。 由原本的ssh变成ssm,再变成ssm+shi...","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/1.%E5%8E%86%E5%8F%B2%E4%B8%8E%E6%9E%B6%E6%9E%84.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"1.历史与架构"}],["meta",{"property":"og:description","content":"1.历史与架构 各位大佬瞄一眼我的个人网站呗 。如果觉得不错,希望能在GitHub上麻烦给个star,GitHub地址https://github.com/Zephery/newblog 。 大学的时候萌生的一个想法,就是建立一个个人网站,前前后后全部推翻重构了4、5遍,现在终于能看了,下面是目前的首页。 由原本的ssh变成ssm,再变成ssm+shi..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://github-images.wenzhihuai.com/images/600-20240126113210252.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-05-11T18:24:35.000Z"}],["meta",{"property":"article:modified_time","content":"2024-05-11T18:24:35.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"1.历史与架构\\",\\"image\\":[\\"https://github-images.wenzhihuai.com/images/600-20240126113210252.png\\",\\"https://github-images.wenzhihuai.com/images/500.png\\",\\"https://github-images.wenzhihuai.com/images/awfawefwefwef.png\\",\\"https://github-images.wenzhihuai.com/images/awefaweagregrgbwerbwer.png\\",\\"https://github-images.wenzhihuai.com/images/awfawefwefawefwef.png\\",\\"https://github-images.wenzhihuai.com/images/QQ%E6%88%AA%E5%9B%BE20170825141127.png\\"],\\"dateModified\\":\\"2024-05-11T18:24:35.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[{"level":2,"title":"1.建站故事与网站架构","slug":"_1-建站故事与网站架构","link":"#_1-建站故事与网站架构","children":[{"level":3,"title":"1.1建站过程","slug":"_1-1建站过程","link":"#_1-1建站过程","children":[]},{"level":3,"title":"1.2 网站整体技术架构","slug":"_1-2-网站整体技术架构","link":"#_1-2-网站整体技术架构","children":[]},{"level":3,"title":"1.3 日志系统","slug":"_1-3-日志系统","link":"#_1-3-日志系统","children":[]},{"level":3,"title":"1.4 【有点意思】自然语言处理","slug":"_1-4-【有点意思】自然语言处理","link":"#_1-4-【有点意思】自然语言处理","children":[]}]},{"level":2,"title":"总结","slug":"总结","link":"#总结","children":[]}],"git":{"createdTime":1579574285000,"updatedTime":1715451875000,"contributors":[{"name":"zhihuaiwen","email":"zhihuaiwen@tencent.com","commits":1}]},"readingTime":{"minutes":6.79,"words":2038},"filePathRelative":"interesting/个人网站/1.历史与架构.md","localizedDate":"2020年1月21日","excerpt":"\\n各位大佬瞄一眼我的个人网站呗 。如果觉得不错,希望能在GitHub上麻烦给个star,GitHub地址https://github.com/Zephery/newblog 。
\\n大学的时候萌生的一个想法,就是建立一个个人网站,前前后后全部推翻重构了4、5遍,现在终于能看了,下面是目前的首页。
不知不觉,自建站https://www.wenzhihuai.com已经接近8年了,大二的时候开启使用ssh+jsp框架来做了一个自己的网站,完全自写前后端,过程中不断进化,改用ssm,整合es做文章搜索,加kafka,加redis缓存,整体上对个人来说还是学习到了不少东西。但是随之而来的问题也不少,被挖矿攻击、服务器被黑等等。有时候因为要用服务器搞一些别的东西,直接没有备份数据库就重装,导致不少文章丢失,虽然别的平台可能零散分布。
总体而言,自建站对学习知识,了解整个建站的原理能够起到非常重要的作用,但是维护成本实在是太高了,每个月要支付服务器的费用,而且一旦想拿服务器来做点什么,都得提防一下会不会造成破坏。最终还是选择采用vuepress2来重构一下自建站,毕竟把markdown放到github,把图片放到cos里减少了不少的维护量。下面是使用vuepress2建站的代码地址。
具体vuepress2官网讲解的很详细了,不用再处理什么,按照步骤创建一个项目即可,为了网站的美观,个人使用了theme hope这款主题。
导航栏、侧边栏官网也有详细的讲解,也不再阐述,需要注意的是自动目录,之前看JavaGuide的样式,他那边的每篇文章都需要写一次ts文件(children),后来发现官网可以把children设置为structure,即可实现根据md文件生成侧边栏目录。注意的是,这里不是根据markdown的文件名来目录名,而是取markdown文件的标题。
{
+ text: "Redis",
+ prefix: "redis/",
+ icon: "redis",
+ collapsible: false,
+ children: "structure"
+ },
vuepress-plugin-comment2,使用了Giscus,Giscus绑定了github账号,所以可以从一定程度上防止被别人刷广告,需要再个人的项目Settings->General把Discussions这个选项给勾选上。
然后去config.ts配置插件。
commentPlugin({
+ provider: "Giscus",
+ comment: true, //启用评论功能
+ repo: "Zephery/MyWebsite", //远程仓库
+ repoId: "MDEwOlJlcG9zaXRvcnkyMDM2MDIyMDQ=", //对应自己的仓库Id
+ category: "General",
+ categoryId: "DIC_kwDODCK5HM4Ccp32" //对应自己的分类Id
+ }),
即可在页面上看到效果
官网也有讲解部署的情况,具体可以看官网Github Pages,整体上看速度还是挺慢的,可以尝试去gitee上部署看一下,之后就可以在pages通过域名访问了。需要在项目下创建.github/workflows/docs.yml文件,具体配置参考官网,不需做任何改动。
github自带的io域名zephery.github.io,做为一名开发,肯定是用自己的域名是比较好的。需要注意下中间的红色框,前面的是分支,后面的是你项目的路径。一般默认即可,不用修改。
购买域名->域名解析,即把我的个人域名wenzhihuai.com指向zephery.github.io(通过cname)即可,然后开启强制https。如果DNS一直没有校验通过,那么可能是CAA的原因。通过DNS诊断工具来判断。
上面的custom domain配置好了之后,但DNS一直没有校验正确,原因是CAA没有正确解析,需要加上即可。
0 issue "trust-provider.com"
+0 issuewild "trust-provider.com"
之后就可以看到Github Pages的DNS校验成功,并且可以强制开启https了。
之前的图床使用的是七牛云和又拍云,都有免费的额度吧,不过看情况未来前景似乎经营不太好,目前改用了腾讯云。存储容量50GB,每个月外网访问流量10GB,满足个人网站使用。具体的配置过程比较简单,就不再阐述了,可以直接看uPic的官方介绍。
有钱才有写作的动力,之前的网站开启了几年的捐赠,总共都没有收到过50块钱,只能从广告这一处想想办法,百度、腾讯广告似乎都不支持个人网站,谷歌可以。配置谷歌广告,网上的教程不少,例如: vuepress配置谷歌广告-通过vue-google-adsense库,缺点是,大部分的文章都是需要在自己的markdown文件中新增特定的标识符。比如:
# js 模板引擎 mustache 用法
+
+<ArticleTopAd></ArticleTopAd>
+
+## 一. 使用步骤
每一篇文章都要新增显然不符合懒惰的人,下面是个人尝试的解决办法,用的是vuepress2提供的slots插槽。
上面的目的是为了获取data-ad-client和data-ad-slot,其中,data-ad-slot为广告单元,不一样。并且,配置完之后可能需要等一个小时才会生效,不要着急。
docs/.vuepress/config.ts
head: [
+ [
+ "script",
+ {
+ "data-ad-client": "ca-pub-9037099208128116",
+ async: true,
+ src: "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"
+ }
+ ],
+]
+
+...
+alias: {
+ "@theme-hope/components/NormalPage": path.resolve(
+ __dirname,
+ "./components/NormalPage.vue",
+ ),
+},
docs/.vuepress/components/NormalPage.vue
<template>
+ <normal-page>
+ <template #contentBefore>
+ <ins class="adsbygoogle"
+ style="display:block; text-align:center;width: 90%;margin: 0 auto;"
+ data-ad-layout="in-article"
+ data-ad-format="fluid"
+ data-ad-client="ca-pub-9037099208128116"
+ data-ad-slot="8206550629"></ins>
+ </template>
+ </normal-page>
+</template>
+<script>
+import NormalPage from "vuepress-theme-hope/components/NormalPage.js";
+
+export default {
+ name: "adsense-inline",
+ components: {
+ 'normal-page': NormalPage,
+ },
+ mounted() {
+ this.adsenseAddLoad();
+ },
+ methods: {
+ adsenseAddLoad() {
+ let inlineScript = document.createElement("script");
+ inlineScript.type = "text/javascript";
+ inlineScript.text = '(adsbygoogle = window.adsbygoogle || []).push({});'
+ document.getElementsByTagName('body')[0].appendChild(inlineScript);
+ }
+ }
+}
+</script>
+
+
+<style lang="scss" scoped>
+</style>
本地是没办法进行调试的,可以从官网插槽演示的文章中用div进行调试,等修改完毕发布之后,即可在自己的网站上看到相关的广告和收入(浏览器要把封禁广告的插件关闭)。
收入虽然低,但是基本上个人没有成本,只需要域名的85块钱。
"建议多写原创高质量的文章出来,AdSense才会匹配出合适的广告,用户感兴趣了才会浏览量增加,你才会有更多的广告收入。"
还是得多写一写优质的文章。
最后,多帮忙点一下个人网站的广告吧,感恩
网站地址:https://www.wenzhihuai.com
源码地址:https://github.com/Zephery/MyWebsite
`,50)]))}const r=i(t,[["render",l],["__file","10.历时8年最终改版.html.vue"]]),k=JSON.parse('{"path":"/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/10.%E5%8E%86%E6%97%B68%E5%B9%B4%E6%9C%80%E7%BB%88%E6%94%B9%E7%89%88.html","title":"10.历时8年最终改版","lang":"zh-CN","frontmatter":{"description":"10.历时8年最终改版 不知不觉,自建站https://www.wenzhihuai.com已经接近8年了,大二的时候开启使用ssh+jsp框架来做了一个自己的网站,完全自写前后端,过程中不断进化,改用ssm,整合es做文章搜索,加kafka,加redis缓存,整体上对个人来说还是学习到了不少东西。但是随之而来的问题也不少,被挖矿攻击、服务器被黑等等。...","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/10.%E5%8E%86%E6%97%B68%E5%B9%B4%E6%9C%80%E7%BB%88%E6%94%B9%E7%89%88.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"10.历时8年最终改版"}],["meta",{"property":"og:description","content":"10.历时8年最终改版 不知不觉,自建站https://www.wenzhihuai.com已经接近8年了,大二的时候开启使用ssh+jsp框架来做了一个自己的网站,完全自写前后端,过程中不断进化,改用ssm,整合es做文章搜索,加kafka,加redis缓存,整体上对个人来说还是学习到了不少东西。但是随之而来的问题也不少,被挖矿攻击、服务器被黑等等。..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://github-images.wenzhihuai.com/images/600-20240126113210252-20240131213935629.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-05-11T18:24:35.000Z"}],["meta",{"property":"article:modified_time","content":"2024-05-11T18:24:35.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"10.历时8年最终改版\\",\\"image\\":[\\"https://github-images.wenzhihuai.com/images/600-20240126113210252-20240131213935629.png\\",\\"https://github-images.wenzhihuai.com/github/image-20240201153605099.png\\",\\"https://github-images.wenzhihuai.com/images/image-20240201205909384.png\\",\\"https://github-images.wenzhihuai.com/github/image-20240201144459067.png\\",\\"https://github-images.wenzhihuai.com/github/image-20240201151253084.png\\",\\"https://github-images.wenzhihuai.com/github/image-20240201151243938.png\\",\\"https://github-images.wenzhihuai.com/github/image-20240201150802070.png\\",\\"https://github-images.wenzhihuai.com/github/image-20240201151218077.png\\",\\"https://github-images.wenzhihuai.com/github/image-20240201151334064.png\\"],\\"dateModified\\":\\"2024-05-11T18:24:35.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[{"level":2,"title":"一、博客的安装","slug":"一、博客的安装","link":"#一、博客的安装","children":[]},{"level":2,"title":"二、配置","slug":"二、配置","link":"#二、配置","children":[]},{"level":2,"title":"三、为文章增加评论","slug":"三、为文章增加评论","link":"#三、为文章增加评论","children":[]},{"level":2,"title":"四、博客的部署","slug":"四、博客的部署","link":"#四、博客的部署","children":[]},{"level":2,"title":"五、Github pages自定义域名","slug":"五、github-pages自定义域名","link":"#五、github-pages自定义域名","children":[]},{"level":2,"title":"六、Typora图床","slug":"六、typora图床","link":"#六、typora图床","children":[]},{"level":2,"title":"七、为自己的内容增加收入","slug":"七、为自己的内容增加收入","link":"#七、为自己的内容增加收入","children":[]},{"level":2,"title":"常见问题","slug":"常见问题","link":"#常见问题","children":[]}],"git":{"createdTime":1706165044000,"updatedTime":1715451875000,"contributors":[{"name":"zhihuaiwen","email":"zhihuaiwen@tencent.com","commits":1}]},"readingTime":{"minutes":5.78,"words":1733},"filePathRelative":"interesting/个人网站/10.历时8年最终改版.md","localizedDate":"2024年1月25日","excerpt":"\\n不知不觉,自建站https://www.wenzhihuai.com已经接近8年了,大二的时候开启使用ssh+jsp框架来做了一个自己的网站,完全自写前后端,过程中不断进化,改用ssm,整合es做文章搜索,加kafka,加redis缓存,整体上对个人来说还是学习到了不少东西。但是随之而来的问题也不少,被挖矿攻击、服务器被黑等等。有时候因为要用服务器搞一些别的东西,直接没有备份数据库就重装,导致不少文章丢失,虽然别的平台可能零散分布。
","autoDesc":true}');export{r as comp,k as data}; diff --git a/assets/1mysql.html-CW43wjS_.js b/assets/1mysql.html-CW43wjS_.js new file mode 100644 index 00000000..114bea80 --- /dev/null +++ b/assets/1mysql.html-CW43wjS_.js @@ -0,0 +1 @@ +import{_ as i,c as a,a as e,o as m}from"./app-ftEjETWs.js";const n={};function o(s,t){return m(),a("div",null,t[0]||(t[0]=[e("h1",{id:"mysql",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#mysql"},[e("span",null,"Mysql")])],-1),e("figure",null,[e("img",{src:"https://github-images.wenzhihuai.com/images/img.png",alt:"img",tabindex:"0",loading:"lazy"}),e("figcaption",null,"img")],-1)]))}const l=i(n,[["render",o],["__file","1mysql.html.vue"]]),c=JSON.parse('{"path":"/database/mysql/1mysql.html","title":"Mysql","lang":"zh-CN","frontmatter":{"description":"Mysql imgimg","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/database/mysql/1mysql.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"Mysql"}],["meta",{"property":"og:description","content":"Mysql imgimg"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://github-images.wenzhihuai.com/images/img.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-02-06T07:53:39.000Z"}],["meta",{"property":"article:modified_time","content":"2024-02-06T07:53:39.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Mysql\\",\\"image\\":[\\"https://github-images.wenzhihuai.com/images/img.png\\"],\\"dateModified\\":\\"2024-02-06T07:53:39.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[],"git":{"createdTime":1679834663000,"updatedTime":1707206019000,"contributors":[{"name":"zhihuaiwen","email":"zhihuaiwen@tencent.com","commits":4}]},"readingTime":{"minutes":0.02,"words":5},"filePathRelative":"database/mysql/1mysql.md","localizedDate":"2023年3月26日","excerpt":"\\n","autoDesc":true}');export{l as comp,c as data}; diff --git "a/assets/2.Lucene\347\232\204\344\275\277\347\224\250.html-9FjHGi9W.js" "b/assets/2.Lucene\347\232\204\344\275\277\347\224\250.html-9FjHGi9W.js" new file mode 100644 index 00000000..7a4cdfc3 --- /dev/null +++ "b/assets/2.Lucene\347\232\204\344\275\277\347\224\250.html-9FjHGi9W.js" @@ -0,0 +1,226 @@ +import{_ as s,c as a,d as n,o as h}from"./app-ftEjETWs.js";const l={};function k(t,i){return h(),a("div",null,i[0]||(i[0]=[n(`首先,帮忙点击一下我的网站http://www.wenzhihuai.com/ 。谢谢啊,如果可以,GitHub上麻烦给个star,以后面试能讲讲这个项目,GitHub地址https://github.com/Zephery/newblog 。
倒排索引:将文档中的词作为关键字,建立词与文档的映射关系,通过对倒排索引的检索,可以根据词快速获取包含这个词的文档列表。倒排索引一般需要对句子做去除停用词。
停用词:在一段句子中,去掉之后对句子的表达意向没有印象的词语,如“非常”、“如果”,中文中主要包括冠词,副词等。
排序:搜索引擎在对一个关键词进行搜索时,可能会命中许多文档,这个时候,搜索引擎就需要快速的查找的用户所需要的文档,因此,相关度大的结果需要进行排序,这个设计到搜索引擎的相关度算法。
索引的建立
索引的搜索
注意:本文使用最新的lucene,版本6.6.0。lucene的版本更新很快,每跨越一次大版本,使用方式就不一样。首先需要导入lucene所使用的包。使用maven:
<dependency>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-core</artifactId><!--lucene核心-->
+ <version>\${lucene.version}</version>
+</dependency>
+<dependency>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-analyzers-common</artifactId><!--分词器-->
+ <version>\${lucene.version}</version>
+</dependency>
+<dependency>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-analyzers-smartcn</artifactId><!--中文分词器-->
+ <version>\${lucene.version}</version>
+</dependency>
+<dependency>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-queryparser</artifactId><!--格式化-->
+ <version>\${lucene.version}</version>
+</dependency>
+<dependency>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-highlighter</artifactId><!--lucene高亮-->
+ <version>\${lucene.version}</version>
+</dependency>
Directory dir = FSDirectory.open(Paths.get("blog_index"));//索引存储的位置
+SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();//简单的分词器
+IndexWriterConfig config = new IndexWriterConfig(analyzer);
+IndexWriter writer = new IndexWriter(dir, config);
+Document doc = new Document();
+doc.add(new TextField("title", blog.getTitle(), Field.Store.YES)); //对标题做索引
+doc.add(new TextField("content", Jsoup.parse(blog.getContent()).text(), Field.Store.YES));//对文章内容做索引
+writer.addDocument(doc);
+writer.close();
IndexWriter writer = getWriter();
+Document doc = new Document();
+doc.add(new TextField("title", blog.getTitle(), Field.Store.YES));
+doc.add(new TextField("content", Jsoup.parse(blog.getContent()).text(), Field.Store.YES));
+writer.updateDocument(new Term("blogid", String.valueOf(blog.getBlogid())), doc); //更新索引
+writer.close();
private static void search_index(String keyword) {
+ try {
+ Directory dir = FSDirectory.open(Paths.get("blog_index")); //获取要查询的路径,也就是索引所在的位置
+ IndexReader reader = DirectoryReader.open(dir);
+ IndexSearcher searcher = new IndexSearcher(reader);
+ SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
+ QueryParser parser = new QueryParser("content", analyzer); //查询解析器
+ Query query = parser.parse(keyword); //通过解析要查询的String,获取查询对象
+ TopDocs docs = searcher.search(query, 10);//开始查询,查询前10条数据,将记录保存在docs中,
+ for (ScoreDoc scoreDoc : docs.scoreDocs) { //取出每条查询结果
+ Document doc = searcher.doc(scoreDoc.doc); //scoreDoc.doc相当于docID,根据这个docID来获取文档
+ System.out.println(doc.get("title")); //fullPath是刚刚建立索引的时候我们定义的一个字段
+ }
+ reader.close();
+ } catch (IOException | ParseException e) {
+ logger.error(e.toString());
+ }
+}
Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
+SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<b><font color='red'>", "</font></b>");
+Highlighter highlighter = new Highlighter(simpleHTMLFormatter, scorer);
+highlighter.setTextFragmenter(fragmenter);
+for (ScoreDoc scoreDoc : docs.scoreDocs) { //取出每条查询结果
+ Document doc = searcher.doc(scoreDoc.doc); //scoreDoc.doc相当于docID,根据这个docID来获取文档
+ String title = doc.get("title");
+ TokenStream tokenStream = analyzer.tokenStream("title", new StringReader(title));
+ String hTitle = highlighter.getBestFragment(tokenStream, title);
+ System.out.println(hTitle);
+}
结果
<b><font color='red'>Java</font></b>堆.栈和常量池 笔记
ScoreDoc lastBottom = null;//相当于pageSize
+BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
+QueryParser parser1 = new QueryParser("title", analyzer);//对文章标题进行搜索
+Query query1 = parser1.parse(q);
+booleanQuery.add(query1, BooleanClause.Occur.SHOULD);
+TopDocs hits = search.searchAfter(lastBottom, booleanQuery.build(), pagehits); //lastBottom(pageSize),pagehits(pagenum)
百度、谷歌等在输入文字的时候会弹出补全框,如下图:
在搭建lucene自动补全的时候,也有考虑过使用SQL语句中使用like来进行,主要还是like对数据库压力会大,而且相关度没有lucene的高。主要使用了官方suggest库以及autocompelte.js这个插件。
suggest的原理看这,以及索引结构看这。
<dependency>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-suggest</artifactId>
+ <version>6.6.0</version>
+</dependency>
public class Blog implements Serializable {
InputIterator的几个方法:
+long weight():返回的权重值,大小会影响排序,默认是1L
+BytesRef payload():对某个对象进行序列化
+boolean hasPayloads():是否有设置payload信息
+Set<BytesRef> contexts():存入context,context里可以是任意的自定义数据,一般用于数据过滤
+boolean hasContexts():判断是否有下一个,默认为false
public class BlogIterator implements InputIterator {
+ /**
+ * logger
+ */
+ private static final Logger logger = LoggerFactory.getLogger(BlogIterator.class);
+ private Iterator<Blog> blogIterator;
+ private Blog currentBlog;
+
+ public BlogIterator(Iterator<Blog> blogIterator) {
+ this.blogIterator = blogIterator;
+ }
+
+ @Override
+ public boolean hasContexts() {
+ return true;
+ }
+
+ @Override
+ public boolean hasPayloads() {
+ return true;
+ }
+
+ public Comparator<BytesRef> getComparator() {
+ return null;
+ }
+
+ @Override
+ public BytesRef next() {
+ if (blogIterator.hasNext()) {
+ currentBlog = blogIterator.next();
+ try {
+ //返回当前Project的name值,把blog类的name属性值作为key
+ return new BytesRef(Jsoup.parse(currentBlog.getTitle()).text().getBytes("utf8"));
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * 将Blog对象序列化存入payload
+ * 可以只将所需要的字段存入payload,这里对整个实体类进行序列化,方便以后需求,不建议采用这种方法
+ */
+ @Override
+ public BytesRef payload() {
+ try {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutputStream out = new ObjectOutputStream(bos);
+ out.writeObject(currentBlog);
+ out.close();
+ BytesRef bytesRef = new BytesRef(bos.toByteArray());
+ return bytesRef;
+ } catch (IOException e) {
+ logger.error("", e);
+ return null;
+ }
+ }
+
+ /**
+ * 文章标题
+ */
+ @Override
+ public Set<BytesRef> contexts() {
+ try {
+ Set<BytesRef> regions = new HashSet<BytesRef>();
+ regions.add(new BytesRef(currentBlog.getTitle().getBytes("UTF8")));
+ return regions;
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Couldn't convert to UTF-8");
+ }
+ }
+
+ /**
+ * 返回权重值,这个值会影响排序
+ * 这里以产品的销售量作为权重值,weight值即最终返回的热词列表里每个热词的权重值
+ */
+ @Override
+ public long weight() {
+ return currentBlog.getHits(); //change to hits
+ }
+}
/**
+ * ajax建立索引
+ */
+@Override
+public void ajaxbuild() {
+ try {
+ Directory dir = FSDirectory.open(Paths.get("autocomplete"));
+ SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
+ AnalyzingInfixSuggester suggester = new AnalyzingInfixSuggester(dir, analyzer);
+ //创建Blog测试数据
+ List<Blog> blogs = blogMapper.getAllBlog();
+ suggester.build(new BlogIterator(blogs.iterator()));
+ } catch (IOException e) {
+ System.err.println("Error!");
+ }
+}
@Override
+public Set<String> ajaxsearch(String keyword) {
+ try {
+ Directory dir = FSDirectory.open(Paths.get("autocomplete"));
+ SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
+ AnalyzingInfixSuggester suggester = new AnalyzingInfixSuggester(dir, analyzer);
+ List<String> list = lookup(suggester, keyword);
+ list.sort(new Comparator<String>() {
+ @Override
+ public int compare(String o1, String o2) {
+ if (o1.length() > o2.length()) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ });
+ Set<String> set = new LinkedHashSet<>();
+ for (String string : list) {
+ set.add(string);
+ }
+ ssubSet(set, 7);
+ return set;
+ } catch (IOException e) {
+ System.err.println("Error!");
+ return null;
+ }
+}
@RequestMapping("ajaxsearch")
+public void ajaxsearch(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ String keyword = request.getParameter("keyword");
+ if (StringUtils.isEmpty(keyword)) {
+ return;
+ }
+ Set<String> set = blogService.ajaxsearch(keyword);
+ Gson gson = new Gson();
+ response.getWriter().write(gson.toJson(set));//返回的数据使用json
+}
<link rel="stylesheet" href="js/autocomplete/jquery.autocomplete.css">
+<script src="js/autocomplete/jquery.autocomplete.js" type="text/javascript"></script>
+<script type="text/javascript">
+ /******************** remote start **********************/
+ $('#remote_input').autocomplete({
+ source: [
+ {
+ url: "ajaxsearch.html?keyword=%QUERY%",
+ type: 'remote'
+ }
+ ]
+ });
+ /********************* remote end ********************/
+</script>
参考:
https://www.ibm.com/developerworks/cn/java/j-lo-lucene1/
http://iamyida.iteye.com/blog/2205114
`,55)]))}const p=s(l,[["render",k],["__file","2.Lucene的使用.html.vue"]]),r=JSON.parse('{"path":"/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/2.Lucene%E7%9A%84%E4%BD%BF%E7%94%A8.html","title":"2.Lucene的使用","lang":"zh-CN","frontmatter":{"description":"2.Lucene的使用 首先,帮忙点击一下我的网站http://www.wenzhihuai.com/ 。谢谢啊,如果可以,GitHub上麻烦给个star,以后面试能讲讲这个项目,GitHub地址https://github.com/Zephery/newblog 。 Lucene的整体架构 imageimage 搜索引擎的几个重要概念: 倒排索引:将...","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/2.Lucene%E7%9A%84%E4%BD%BF%E7%94%A8.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"2.Lucene的使用"}],["meta",{"property":"og:description","content":"2.Lucene的使用 首先,帮忙点击一下我的网站http://www.wenzhihuai.com/ 。谢谢啊,如果可以,GitHub上麻烦给个star,以后面试能讲讲这个项目,GitHub地址https://github.com/Zephery/newblog 。 Lucene的整体架构 imageimage 搜索引擎的几个重要概念: 倒排索引:将..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://github-images.wenzhihuai.com/images/lucenejoijfoaiwheifuwhifu.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-05-11T18:24:35.000Z"}],["meta",{"property":"article:modified_time","content":"2024-05-11T18:24:35.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2.Lucene的使用\\",\\"image\\":[\\"https://github-images.wenzhihuai.com/images/lucenejoijfoaiwheifuwhifu.png\\",\\"https://github-images.wenzhihuai.com/images/fewfwaefawe20170903183124.png\\",\\"https://github-images.wenzhihuai.com/images/fweafwe170903175148.png\\",\\"https://github-images.wenzhihuai.com/images/fwefawfagergwerg20170903184512.png\\",\\"https://github-images.wenzhihuai.com/images/bervererfwaefewf20170903200915.png\\",\\"https://github-images.wenzhihuai.com/images/QQ%E6%88%AA%E5%9B%BE20170728102929.png\\",\\"https://github-images.wenzhihuai.com/images/QQ%E6%88%AA%E5%9B%BE20170728105628.png\\"],\\"dateModified\\":\\"2024-05-11T18:24:35.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[{"level":3,"title":"Lucene的整体架构","slug":"lucene的整体架构","link":"#lucene的整体架构","children":[]},{"level":3,"title":"搜索引擎的几个重要概念:","slug":"搜索引擎的几个重要概念","link":"#搜索引擎的几个重要概念","children":[]},{"level":3,"title":"Lucene中的几个概念","slug":"lucene中的几个概念","link":"#lucene中的几个概念","children":[]},{"level":3,"title":"lucene在本网站的使用:","slug":"lucene在本网站的使用","link":"#lucene在本网站的使用","children":[]},{"level":2,"title":"一、搜索","slug":"一、搜索","link":"#一、搜索","children":[]},{"level":2,"title":"二、lucene自动补全","slug":"二、lucene自动补全","link":"#二、lucene自动补全","children":[]}],"git":{"createdTime":1579574285000,"updatedTime":1715451875000,"contributors":[{"name":"zhihuaiwen","email":"zhihuaiwen@tencent.com","commits":1}]},"readingTime":{"minutes":6.82,"words":2046},"filePathRelative":"interesting/个人网站/2.Lucene的使用.md","localizedDate":"2020年1月21日","excerpt":"\\n首先,帮忙点击一下我的网站http://www.wenzhihuai.com/ 。谢谢啊,如果可以,GitHub上麻烦给个star,以后面试能讲讲这个项目,GitHub地址https://github.com/Zephery/newblog 。
","autoDesc":true}');export{p as comp,r as data}; diff --git a/assets/2017.html-DzgcI9cv.js b/assets/2017.html-DzgcI9cv.js new file mode 100644 index 00000000..52f2bb24 --- /dev/null +++ b/assets/2017.html-DzgcI9cv.js @@ -0,0 +1 @@ +import{_ as a,c as i,d as t,o as n}from"./app-ftEjETWs.js";const p={};function r(o,e){return n(),i("div",null,e[0]||(e[0]=[t('2016年6月,大三,考完最后一科,默默的看着大四那群人一个一个的走了,想着想着,明年毕业的时候,自己将会失去你所拥有的一切,大学那群认识的人,可能这辈子都不会见到了——致远离家乡出省读书的娃。然而,我并没有想到,自己失去的,不仅仅是那群人,几乎是一切。。。。
17年1月,因为受不了北京雾霾(其实是次因),从实习的公司跑路了,很不舍,只是,感觉自己还有很多事要做,很多梦想要去实现,毕竟,看着身边的人,去了美团,拿着15k的月薪,有点不平衡,感觉他也不是很厉害吧,我努力努力10k应该没问题吧?
回到学校,待了几天,那个整天给别人灌毒鸡汤的人还是不着急找工作:该有的时候总会有的啦,然后继续打游戏、那个天天喊着吃麻辣香锅的人,记忆中好像今年都没有吃上,哈哈、厕所还是那样偶尔炸一下,嗯,感觉一切都很熟悉,只是,我有了点伤感,毕竟身边又要换一群不认识的人了。考完试,终于能回广东了,一年没回了,开心不得了,更多的是,为了那个喜欢了好几年没有表白的妹纸,之前从没想过真的能每天跟一个妹纸道晚安,秒回,但是,对她,我还真做到了。1月份的广州,不热不冷,空气很清新,比北京、天津那股泥土味好多了,只是,我被拒了,男闺蜜还是男闺蜜,备胎还是备胎,那会还没想过放弃,直到...
春节期间,把自己的网站整理了一下,看了看JVM和并发的知识,偶尔刷刷牛客网的题,觉得自己没多大问题了,跟同学出发,去深圳,我并没有一个做马化腾、马云的梦,但是我想,要做出点东西,至少,不是个平庸的人。投了几个,第一个面试,知道是培训兼外包后果断弃了,春招还没开始,再多准备准备,有时间就往深圳图书馆里跑,找个位置,静静的复习,那会,感觉自己一定能去个大公司吧,搞搞分布式、大数据之类的~
2月份底开始慢慢投简历,跑招聘会,深圳招聘的太少了,那就去广州,大学城,于是,开始了自己一周两天都在广深往返。有的公司笔试要跑一次广州,一面要跑一次,二面还要跑一次,每次看着路费,真的承受不起,但是有希望就行。乐观之下,到来的是一重又一重的打击。
**数码,很看好的公司,在广州也算是老品牌了,笔试的时候面试官没有带够卷子,就拿了大数据的做,还过了,面试的时候被面到union和union all的区别,答反了,还有left join和right join的原理,一脸懵逼,过后去了百度搜了搜,这东西哪来的原理?
然后是个企业建站的,笔试完了,面试官照着题问然后没说啥就让我走了,额,我好像还没自我介绍呢。
有点想回北京了,往北京投了一份,A轮公司,月薪10k,过了,觉得自己技术应该不差吧,不过还是拒了,因为一想起那里满脑子都是雾霾。
没事没事,下一家,某游戏公司,笔试过了,一面也过了,然后被问flume收集的时候断网处理机制、MD5和RSA原理、Angulajs和Vuejs用没用过、千万级的数据是如何存储的?额,跟你们想的一样,我一道也答出来,然后就挂了,面试官跟我说,你要是来早三个月这些都会啦,说的我很紧张,广东的同学都那么强么?
这个时候还没质疑自己的技术,去了趟深圳某家小公司,金融的,HR问我是不是培训过来的,心里开始突然间好苦涩,大学花那么多时间学习居然被别人用来和培训机构的人相提并论,笔试不知从哪整来的英文逻辑题,做完了然后上机笔试,做不出来不能见面试官,开始感觉这公司有点。。。。还是撤了撤了。
突然感觉面试面的绝望,是自己的问题么?是自己技术太菜了么?开始怀疑自己,感觉也是,已经3月份底了,毕竟别人都在实习,而我已经离职3个月没工作了,但是那些问题不是有点离谱了,一开始是开始心态爆炸觉得怎么可以对应届生问这些问题,不应该是基础么?算法什么的,后来是无奈了,开始上网搜各种不沾边的技术。
还有其他的面试。。。有的工资不高(6K不到),有的面试问题全答对就因为说了有点想做大数据就不要你,有的聊得很欢快却没消息,有的说招java但是笔试一道java题都没有全是前端的,有的打着互联网实际却是外包的公司。。。。
4月初真的很绝望很绝望,不想跟别人说话,是不是很可笑,一个从北京B+轮的公司回广东,面试近15个,居然只能拿到2个offer,其中之一国企外包的,都不知说什么好。13号要拍毕业照了,没有回去,大学四年,毕竟大家个个圈子之间没什么交流,没什么友谊,果然,整个专业的人只有2/3的人去拍了毕业照。4月23号,拉勾网深圳站,梦想着市集,抱着一点点的小希望,去投了5个,结果一个面试邀请都没有,换之而来的是一片绝望、黑暗。。。。
5月初回学校了赶毕业设计,回学校也是,天天往图书馆跑,为了面试,毕业设计拉下太多了。悠闲的时候,总会想起我喜欢的那个人,过的怎么样了,微信一开,好像自己被屏蔽了还是被删了?这三个月来的面试打击,真的不知道怎么应对,我更不知道怎么跟她聊天,以至于慢慢隔离,这段感情,或许只有我知道自己有多心疼,只有我自己sb得一点点的呵护着。想起上次跟她聊天是4月中,还是她提了个问题其他什么都没问,彼此之前已经没有关心了。删了吧删了吧,就这样一句再见都没有说,一句“祝福”都没有说。
5月底遇到两次还算开心的事,导师问我要不要评优秀论文,不是很想折腾,拒了,导师仍旧劝我,只是写个3页的小总结而已,评上的话能留图书馆收藏。另一个件是珍爱网打电话过来说我一面过了,什么时候二面,天啊,3月份一面,5月底才通知,心里想骂,但是真的好开心好开心,黑暗中的光芒,泪奔。。。。约到了6月15号面试。
6月初开始自己的回家倒计时,请那唯一一个跟我好的妹纸吃饭,忙完答辩,然后是优秀论文的小报告,13号下午交了上去,再回到宿舍,空无一人,一遍忍着不要悲伤,一遍收拾东西,然后向每个宿舍敲门,告别,一个人拖着行李箱,嗯,我要淡定。
准备了有点久,我以为自己准备好了,15号,深圳,珍爱网,被问到:有没有JVM调优经验?ZK是如何保持一致性的?有没有写过ELK索引经验?谈谈你对dubbo的理解。。。事后,得知自己没有过。心态真的炸了,通宵打了两天游戏。月底得知,优秀论文也没有被评上,已经没有骂人吐槽的想法,只是哦的一声,6月,宣告了自己春招结束,宣告自己要去外包,同时也宣告自己大学这些努力全部白费,跟那个灌毒鸡汤天天打游戏的人同一起跑线。
七月初入职的时候,问了问那些一起应届入职的同学,跟别人交流一下面试被问的问题,只有线程池,JVM回收算法,他们也没用过,我无奈,为什么我被问到的,全是Angularjs、MD5加密算法、ZK一致性原理这种奇葩的问题。。。。。
没有环境,就自己创造,下班有时间就改改自己的网站http://www.wenzhihuai.com,GitHub目前有154个star了,感觉也是个小作品吧,把常用的Java技术栈(RabbitMQ、MongoDB等)加了上去,虽然没啥使用场景,但是自己好好努力吧,毕竟高并发、高可用是自己的梦想。今年GitHub自己的提交次数。
决心先好好呆着,学习学习,把公司的产品业务技术专研专研,7、8月份是挺开心的,没怎么加班,虽然下班我都留在公司学习,虽然公司的产品不咋地,但是还是有可学之处的,随后,我没想到的是,公司要实行996,,,9月份至12月,每天都是改bug,开发模块,写业务,全都是增删改查,没有涉及redis、rabbitmq,就真的只有使用Hibernate、spring,而做出的东西,要使用JDK6来运行,至今都能看到sun的类。每天的状态就是累累累,我们组还不是最惨的,隔壁项目组,一周通宵两次,9月到10都是如此,国庆加班,每次经过他们组,我都觉得自己还算是幸福。很厌恶这种不把员工当人看的行为,有同事调侃,明年估计我们这些应届过来的,是不是要走掉2/3的人?疯狂加班的代价是牺牲积极性,我问过那些一周通宵两次的人,敲代码,程序员你喜欢这份工作么?不喜欢!!!有那么一两次,实在是太累了,我都感觉自己有点不行了,想离职,可是,会有公司要我么?会有公司不问那些奇葩的问题么?
双十一,花了500多买了十本书,神书啊,计算机届的圣经啊,然而至今一本都没看完,甚至是都没怎么翻,每天下班回去之后就是9点半了,烧水,洗澡,洗衣服,近11点了,躺床上,一遍看书一遍看鹌鹑直播,一小时才翻几页,996真的太累了,实在看不下书。经过这几个月的加班,感觉自己技术增长为负数,也病了几次,同学见我,都感觉我老了不少,心里不平,可是又没有什么办法。
珠江新城,一年四季都那么美,今晚应该又是万人一起倒计时了吧?已经好久没跟别人一起出去玩过了。今年貌似得了自闭症?社交全靠黄段子。想找人出门走走,然而手机已经没有那种几年的朋友,大学同学一个都不在,哈哈哈,别人说社交软件让人离得更近,我怎么发现我自己一个好朋友都没有了呢,哭笑ing。或许是自己的确不善于跟别人保持联系吧,这个要改,一定要改!!!
远看:
近看:
题外话:
有些时候,遇到不顺,感觉自己不是神,没必要忍住,就开始宣泄自己的情绪,比如疯狂打游戏,只能这样度过了吧。
今年听过最多的一句话,你好厉害,你好牛逼==我感觉也是,可惜,要被业务耽误了不少时间吧。。
《企业IT架构转型之道》是今年最好的架构图书,没有之一。
想起初中,老师让我在黑板写自己的英文作文,好像是信件,描述什么状况来这?最后夸了夸我在文章后面加的一段大概是劝别人不要悲伤要灿烂。也想起一个故事,二战以后,德国满目疮痍,他们处在这样一个凄惨的境地,还能想到在桌上摆设一些花,就一定能在废墟上重建家园。我所看到的是即使在再黑暗的状态下,要永远抱着希望。明年要更加努力的学习,而不是工作,是该挽留一些人了,多运动。说了那么多,其实,我只希望自己的运气不要再差了,再差就没法活了。人在遇到不顺的时候,往往最难过的一关便是自己。哈哈哈,只能学我那个同学给自己灌毒鸡汤了。加油加油↖(^ω^)↗
',35)]))}const h=a(p,[["render",r],["__file","2017.html.vue"]]),m=JSON.parse('{"path":"/life/2017.html","title":"2017","lang":"zh-CN","frontmatter":{"description":"2017 2016年6月,大三,考完最后一科,默默的看着大四那群人一个一个的走了,想着想着,明年毕业的时候,自己将会失去你所拥有的一切,大学那群认识的人,可能这辈子都不会见到了——致远离家乡出省读书的娃。然而,我并没有想到,自己失去的,不仅仅是那群人,几乎是一切。。。。 17年1月,因为受不了北京雾霾(其实是次因),从实习的公司跑路了,很不舍,只是,感...","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/life/2017.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"2017"}],["meta",{"property":"og:description","content":"2017 2016年6月,大三,考完最后一科,默默的看着大四那群人一个一个的走了,想着想着,明年毕业的时候,自己将会失去你所拥有的一切,大学那群认识的人,可能这辈子都不会见到了——致远离家乡出省读书的娃。然而,我并没有想到,自己失去的,不仅仅是那群人,几乎是一切。。。。 17年1月,因为受不了北京雾霾(其实是次因),从实习的公司跑路了,很不舍,只是,感..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"http://image.wenzhihuai.com/images/20171231044153.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-01-26T04:53:47.000Z"}],["meta",{"property":"article:modified_time","content":"2024-01-26T04:53:47.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2017\\",\\"image\\":[\\"http://image.wenzhihuai.com/images/20171231044153.png\\",\\"http://image.wenzhihuai.com/images/20171231050705.png\\",\\"http://image.wenzhihuai.com/images/20171231073414.png\\",\\"http://image.wenzhihuai.com/images/20171231080129.png\\",\\"http://image.wenzhihuai.com/images/20171231075917.png\\",\\"http://image.wenzhihuai.com/images/20171231082716.png\\",\\"http://image.wenzhihuai.com/images/20171231082725.png\\",\\"http://image.wenzhihuai.com/images/20171231085744.png\\"],\\"dateModified\\":\\"2024-01-26T04:53:47.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[],"git":{"createdTime":1579958290000,"updatedTime":1706244827000,"contributors":[{"name":"zhihuaiwen","email":"zhihuaiwen@tencent.com","commits":2}]},"readingTime":{"minutes":11.22,"words":3366},"filePathRelative":"life/2017.md","localizedDate":"2020年1月25日","excerpt":"\\n2016年6月,大三,考完最后一科,默默的看着大四那群人一个一个的走了,想着想着,明年毕业的时候,自己将会失去你所拥有的一切,大学那群认识的人,可能这辈子都不会见到了——致远离家乡出省读书的娃。然而,我并没有想到,自己失去的,不仅仅是那群人,几乎是一切。。。。
\\n17年1月,因为受不了北京雾霾(其实是次因),从实习的公司跑路了,很不舍,只是,感觉自己还有很多事要做,很多梦想要去实现,毕竟,看着身边的人,去了美团,拿着15k的月薪,有点不平衡,感觉他也不是很厉害吧,我努力努力10k应该没问题吧?
年底老姐结婚,跑回家去了,忙着一直没写,本来想回深圳的时候空余的时候写写,没想到一直加班,嗯,9点半下班那种,偶尔也趁着间隙的时间,一段一段的凑着吧。
想着17年12月31号写的那篇文章https://www.cnblogs.com/w1570631036/p/8158284.html,感叹18年还算恢复了点。
其实校招过去的那家公司,真的不是很喜欢,996、技术差、产品差,实在受不了,春节前提出了离职,老大也挽留了下,以“来了个阿里的带我们重构,能学不了东西”来挽留,虽然我对技术比较痴迷,但离职去深圳的决心还是没有动摇,嗯,就这么开始了自己的裸辞过程。3月8号拿到离职,回公司的时候跟跟同事吹吹水,吃个饭,某同事还喊:“周末来公司玩玩么,我给你开门”,哈哈哈,泼出去的水,回不去了,偶尔有点伤感。
去深圳面试,第一家随手记,之前超级想去这家公司的,金融这一块,有钱,只可惜,没过,一面面试官一直夸我,我觉得稳了,二面没转过来,就这么挂了,有点不甘心吧,在这,感谢那个内推我的人吧,面经在这,之后就是大大小小的面试,联想、恒大、期待金融什么的,都没拿到offer,裸辞真的慌得一比,一天没工作,感觉一年没有工作没人要的样子。。。。没面试就跑去广州图书馆,复习,反思,一天一天的过着,回家就去楼下吃烧烤,打游戏,2点3点才睡觉,boss直聘、拉勾网见一个问一个,迷迷糊糊慌慌张张,那段期间真的想有点把所有书卖掉回家啃老的想法,好在最后联系了一家公司,电商、大小周,知乎上全是差评,不过评价的都不是技术的,更重要的是,岗位是中间件的,嗯,去吧。
and ... 广州拜拜,晚上人少的时候,跟妹纸去珠江新城真的很美好
入职的时候是做日志收集的,就是flume+kafka+es这一套,遇到了不少大佬,嗯,感觉挺好的,打算好好干,一个半月后不知怎么滴,被拉去做容器云了,然后就开始了天天调jenkins、gitlab这些没什么用的api,开始了增删改查的历程,很烦,研究jenkins、gitlab对于自己的技术没什么提升,又不得不做,而且大小周+加班这么做,回到家自己看java、netty、golang的书籍,可是没项目做没实践,过几个月就忘光了,有段时间真的很烦很烦,根本不想工作,天天调api,而且是jenkins、gitlab这些没什么卵用的api,至今也没找到什么办法。。。。
公司发展太快,也实在让人不知所措,一不小心部门被架空了,什么预兆都没有,被分到其他部门,然后发现这个部门领导内斗真的是,“三国争霸”吧,无奈成为炮灰,不同的领导不同的要求,安安静静敲个代码怎么这么难。。。。
去年的学习拉下了不少,文章也写的少了,总写那些入门的文章确实没有什么意思,买的书虽然没有17年的多吧,不过也不少了,也看了挺多本书的,虽然我还是不满意自己的阅读量。
这几年要开始抓准一个框架了,好好专研一下,也要开始学习一下go了,希望能参与一些github的项目,好好努力吧。
平时没事总是宅在家里打游戏,差不多持续到下半年吧,那会打游戏去楼下吃东西的时候老是觉得眼神恍惚,盯着电子产品太多了,然后就跟别人跑步去了,每周去一次人才公园,跑步健身,人才公园真的是深圳跑步最好的一个地方了,桥上总有一群摄影师,好想在深圳湾买一套房子,看了看房价,算了算了,深圳的房,真的贵的离谱。19年也要多多运动,健康第一健康第一。
想不起来还有什么要说的了,毕竟程序员,好像每天的生活都一样,就展望一下19年吧。今年,最最重要的是家里人以及身边还有所有人都健健康康的,哈哈哈。然后是安安静静的敲代码~~就酱
',18)]))}const h=t(o,[["render",r],["__file","2018.html.vue"]]),c=JSON.parse('{"path":"/life/2018.html","title":"2018","lang":"zh-CN","frontmatter":{"description":"2018 年底老姐结婚,跑回家去了,忙着一直没写,本来想回深圳的时候空余的时候写写,没想到一直加班,嗯,9点半下班那种,偶尔也趁着间隙的时间,一段一段的凑着吧。 想着17年12月31号写的那篇文章https://www.cnblogs.com/w1570631036/p/8158284.html,感叹18年还算恢复了点。 心惊胆战的裸辞经历 其实校招过...","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/life/2018.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"2018"}],["meta",{"property":"og:description","content":"2018 年底老姐结婚,跑回家去了,忙着一直没写,本来想回深圳的时候空余的时候写写,没想到一直加班,嗯,9点半下班那种,偶尔也趁着间隙的时间,一段一段的凑着吧。 想着17年12月31号写的那篇文章https://www.cnblogs.com/w1570631036/p/8158284.html,感叹18年还算恢复了点。 心惊胆战的裸辞经历 其实校招过..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"http://image.wenzhihuai.com/images/2019011011294921712062.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-01-26T04:53:47.000Z"}],["meta",{"property":"article:modified_time","content":"2024-01-26T04:53:47.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2018\\",\\"image\\":[\\"http://image.wenzhihuai.com/images/2019011011294921712062.png\\",\\"http://image.wenzhihuai.com/images/201901130418261145369442.png\\",\\"https://upyuncdn.wenzhihuai.com/201901130414532037038633.png\\",\\"https://upyuncdn.wenzhihuai.com/20190113103109865107434.png\\"],\\"dateModified\\":\\"2024-01-26T04:53:47.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[{"level":2,"title":"心惊胆战的裸辞经历","slug":"心惊胆战的裸辞经历","link":"#心惊胆战的裸辞经历","children":[]},{"level":2,"title":"工作工作","slug":"工作工作","link":"#工作工作","children":[]},{"level":2,"title":"跑步跑步","slug":"跑步跑步","link":"#跑步跑步","children":[]},{"level":2,"title":"Others","slug":"others","link":"#others","children":[]},{"level":2,"title":"2019","slug":"_2019","link":"#_2019","children":[]}],"git":{"createdTime":1579957849000,"updatedTime":1706244827000,"contributors":[{"name":"zhihuaiwen","email":"zhihuaiwen@tencent.com","commits":2}]},"readingTime":{"minutes":4.35,"words":1306},"filePathRelative":"life/2018.md","localizedDate":"2020年1月25日","excerpt":"\\n年底老姐结婚,跑回家去了,忙着一直没写,本来想回深圳的时候空余的时候写写,没想到一直加班,嗯,9点半下班那种,偶尔也趁着间隙的时间,一段一段的凑着吧。
","autoDesc":true}');export{h as comp,c as data}; diff --git a/assets/2019.html-Df2iUZPh.js b/assets/2019.html-Df2iUZPh.js new file mode 100644 index 00000000..05b32be2 --- /dev/null +++ b/assets/2019.html-Df2iUZPh.js @@ -0,0 +1 @@ +import{_ as t,c as a,d as i,o as n}from"./app-ftEjETWs.js";const p={};function r(h,e){return n(),a("div",null,e[0]||(e[0]=[i('2019,本命年,1字头开头的年份,就这么过去了,迎来了2开头的十年,12月过的不是很好,每隔几天就吵架,都没怎么想起写自己的年终总结了,对这个跨年也不是很重视,貌似有点浑浑噩噩的样子。今天1号,就继续来公司学习学习,就写写文章吧。
19年一月,没发生什么事,对老板也算是失望,打算过完春节就出去外面试试,完全的架构错误啊,怎么守得了,然后开了个年会,没什么好看的,唯一的例外就是看见漂漂亮亮的她,可惜不敢搭话,就这么的呗。2月以奇奇怪怪的方式走到了一起,开心,又有点伤心,然后就开始了这个欢喜而又悲剧的本命年。
从18年底开始吧,大概就知道领导对容器云这么快的完全架构设计错误,就是一个完完全全错误的产品,其他人也觉得有点很有问题,但老板就是固执不听,结果一个跑路,一个被压得懒得反抗,然后我一个人跟老板和项目经理怼了前前后后半年,真是惨啊,还要被骂,各种拉到小黑屋里批斗,一直在对他们强调:没有这么做的!!!然后被以年轻人,不懂事,不要抱怨,长大了以后就领会了怼回来,当时气得真是无语。。。。。自己调研的阿里云、腾讯云、青云,还有其他小公司的容器,发现真的只有我们这么设计,完全错误的方向,,,,直到老板走了之后,才开始决定重构,走上正轨。
之后有了点起色,然后开始着重强调了DevOps的重要性,期初也没人理解,还被骂天天研究技术不干活。。。这东西没技术,但是真的是公司十分需要的公司好么!!可惜公司对这个产品很失望,也不给人,期间还说要暂停项目,唉,这么大的项目就给几个人怎么做,谁都不乐意,就这么过的很憋屈吧,开始了维护的日子。。。
工作上,蛮多人对我挺好的,领导,同事,不过有时候憋屈的时候还是有了点情绪化,有时候有点悲伤的想法,浮躁、暴躁,出去面试了几次,感觉不是自己能力不好,而是市场上的真的需要3+年以上的。。。。不过感觉自己能在这里憋屈的过了这么久,也是很佩服自己的,哈哈,用同事的话来说就是:当做游戏里的练级,锻炼自己哈哈哈
项目的方向错误,导致自己写的代码都是很简单的增删改查,没有技术含量的那种,也最终导致自己浪费了不少时间,上半年算是很气愤吧,不想就这么浪费掉这些时间,虽然期间看了各种各样的东西,比如Netty、Go、操作系统什么的,最终发现,如果不在项目中使用的话,真的会忘,而且很快就忘掉,最后的还是决定学习项目相关度比较大的东西了,比如Go,如果项目不是很大,小项目的话,就用Go来写,这么做感觉提升还是挺大的。
过去一年往南山图书馆借了不少书,省了不少钱,虽然没怎么看,但我至少有个奋斗的心啊,哈哈哈哈哈哈哈哈,文章写得少了,因为感觉写不出好东西,总是入门的那种,不深,不值得学习,想想到时候把别人给坑了,也不好意思。
操作系统感觉还是要好好学学了,加油
Kubernetes就是未来的方向,有时候翻开源码看看,又不敢往下面看了,对底层不熟吧,今年要多多研究下Kubernetes的源码了,至于Spring那一套,也不知道该不该放弃,或者都学习一下?云原生就是趋势。
吵架了,没心思写
过去一年还是把运动算是坚持了下来,每个月必去深圳湾跑一次。还是没怎么睡好,工作感情上都有点不顺,加上自己本身就难以入睡,有时候躺床上就是怎么也睡不着,还长了痘痘,要跑去医院那种,可怕,老了,还是要早点睡觉,多走走多运动,好久没打羽毛球了,自己也不想有空的时候只会打游戏,今年继续加油,多运动吧。
还爬了两次南山
看了周围蛮多同事去了腾讯阿里,有点心动,好想去csig,没到3年真的让人很抓狂。
过去一年过的蛮憋屈的,特别是工作不顺,加上跟女朋友吵架,心态爆炸。。。
每年这个时候,都不敢有太大的期望了,祝大家都健健康康的,工作顺利!!
当然,如果有可能的话,我想去CSIG
',23)]))}const c=t(p,[["render",r],["__file","2019.html.vue"]]),l=JSON.parse('{"path":"/life/2019.html","title":"2019","lang":"zh-CN","frontmatter":{"description":"2019 2019,本命年,1字头开头的年份,就这么过去了,迎来了2开头的十年,12月过的不是很好,每隔几天就吵架,都没怎么想起写自己的年终总结了,对这个跨年也不是很重视,貌似有点浑浑噩噩的样子。今天1号,就继续来公司学习学习,就写写文章吧。 19年一月,没发生什么事,对老板也算是失望,打算过完春节就出去外面试试,完全的架构错误啊,怎么守得了,然后开了...","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/life/2019.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"2019"}],["meta",{"property":"og:description","content":"2019 2019,本命年,1字头开头的年份,就这么过去了,迎来了2开头的十年,12月过的不是很好,每隔几天就吵架,都没怎么想起写自己的年终总结了,对这个跨年也不是很重视,貌似有点浑浑噩噩的样子。今天1号,就继续来公司学习学习,就写写文章吧。 19年一月,没发生什么事,对老板也算是失望,打算过完春节就出去外面试试,完全的架构错误啊,怎么守得了,然后开了..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://upyuncdn.wenzhihuai.com/202001010544131765044792.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-01-26T04:53:47.000Z"}],["meta",{"property":"article:modified_time","content":"2024-01-26T04:53:47.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2019\\",\\"image\\":[\\"https://upyuncdn.wenzhihuai.com/202001010544131765044792.png\\",\\"https://upyuncdn.wenzhihuai.com/202001010516141216483720.png\\",\\"http://image.wenzhihuai.com/images/20200101052650730023760.png\\",\\"http://image.wenzhihuai.com/images/202001010535231861948911.png\\"],\\"dateModified\\":\\"2024-01-26T04:53:47.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[{"level":2,"title":"工作","slug":"工作","link":"#工作","children":[]},{"level":2,"title":"学习","slug":"学习","link":"#学习","children":[]},{"level":2,"title":"感情","slug":"感情","link":"#感情","children":[]},{"level":2,"title":"运动","slug":"运动","link":"#运动","children":[]},{"level":2,"title":"其他","slug":"其他","link":"#其他","children":[]},{"level":2,"title":"2020展望","slug":"_2020展望","link":"#_2020展望","children":[]}],"git":{"createdTime":1579958290000,"updatedTime":1706244827000,"contributors":[{"name":"zhihuaiwen","email":"zhihuaiwen@tencent.com","commits":2}]},"readingTime":{"minutes":4.43,"words":1329},"filePathRelative":"life/2019.md","localizedDate":"2020年1月25日","excerpt":"\\n2019,本命年,1字头开头的年份,就这么过去了,迎来了2开头的十年,12月过的不是很好,每隔几天就吵架,都没怎么想起写自己的年终总结了,对这个跨年也不是很重视,貌似有点浑浑噩噩的样子。今天1号,就继续来公司学习学习,就写写文章吧。
\\n19年一月,没发生什么事,对老板也算是失望,打算过完春节就出去外面试试,完全的架构错误啊,怎么守得了,然后开了个年会,没什么好看的,唯一的例外就是看见漂漂亮亮的她,可惜不敢搭话,就这么的呗。2月以奇奇怪怪的方式走到了一起,开心,又有点伤心,然后就开始了这个欢喜而又悲剧的本命年。
新买了台air m3,午夜色的,心心念念了好久
读写速度
和以前的m1 pro对比了下,感觉差了半截,使用上没有区别吧
非果粉,但好多苹果设备
',9)]))}const n=t(r,[["render",c],["__file","2024-07-24.html.vue"]]),s=JSON.parse('{"path":"/about-the-author/personal-life/2024-07-24.html","title":"2024-07-24","lang":"zh-CN","frontmatter":{"description":"2024-07-24 新买了台air m3,午夜色的,心心念念了好久 IMG_20240723_235443 读写速度 c6c117ff1f5f6371b3ab682621369c1f 和以前的m1 pro对比了下,感觉差了半截,使用上没有区别吧 096e891a0278263d0c790e74f32ef911_720 非果粉,但好多苹果设备 0742...","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/about-the-author/personal-life/2024-07-24.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"2024-07-24"}],["meta",{"property":"og:description","content":"2024-07-24 新买了台air m3,午夜色的,心心念念了好久 IMG_20240723_235443 读写速度 c6c117ff1f5f6371b3ab682621369c1f 和以前的m1 pro对比了下,感觉差了半截,使用上没有区别吧 096e891a0278263d0c790e74f32ef911_720 非果粉,但好多苹果设备 0742..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-07-24T15:52:12.000Z"}],["meta",{"property":"article:modified_time","content":"2024-07-24T15:52:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2024-07-24\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2024-07-24T15:52:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[],"git":{"createdTime":1721836332000,"updatedTime":1721836332000,"contributors":[{"name":"zhihuaiwen","email":"1570631036@qq.com","commits":1}]},"readingTime":{"minutes":0.34,"words":102},"filePathRelative":"about-the-author/personal-life/2024-07-24.md","localizedDate":"2024年7月24日","excerpt":"\\n新买了台air m3,午夜色的,心心念念了好久
\\n","autoDesc":true}');export{n as comp,s as data}; diff --git "a/assets/2024-11-09\344\270\212\346\265\267\350\277\252\346\226\257\345\260\274.html-CU80Bk9D.js" "b/assets/2024-11-09\344\270\212\346\265\267\350\277\252\346\226\257\345\260\274.html-CU80Bk9D.js" new file mode 100644 index 00000000..44ffcc45 --- /dev/null +++ "b/assets/2024-11-09\344\270\212\346\265\267\350\277\252\346\226\257\345\260\274.html-CU80Bk9D.js" @@ -0,0 +1 @@ +import{_ as a,c as r,a as e,b as i,o as n,r as c}from"./app-ftEjETWs.js";const p={};function l(m,t){const o=c("BiliBili");return n(),r("div",null,[t[0]||(t[0]=e("h1",{id:"_2024-11-09上海迪斯尼",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2024-11-09上海迪斯尼"},[e("span",null,"2024-11-09上海迪斯尼")])],-1)),i(o,{bvid:"BV1LSmrYqEDw"})])}const h=a(p,[["render",l],["__file","2024-11-09上海迪斯尼.html.vue"]]),d=JSON.parse('{"path":"/about-the-author/personal-life/2024-11-09%E4%B8%8A%E6%B5%B7%E8%BF%AA%E6%96%AF%E5%B0%BC.html","title":"2024-11-09上海迪斯尼","lang":"zh-CN","frontmatter":{"description":"2024-11-09上海迪斯尼","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/about-the-author/personal-life/2024-11-09%E4%B8%8A%E6%B5%B7%E8%BF%AA%E6%96%AF%E5%B0%BC.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"2024-11-09上海迪斯尼"}],["meta",{"property":"og:description","content":"2024-11-09上海迪斯尼"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-11T16:10:11.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-11T16:10:11.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2024-11-09上海迪斯尼\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2024-11-11T16:10:11.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[],"git":{"createdTime":1731254136000,"updatedTime":1731341411000,"contributors":[{"name":"zhihuaiwen","email":"1570631036@qq.com","commits":2}]},"readingTime":{"minutes":0.04,"words":12},"filePathRelative":"about-the-author/personal-life/2024-11-09上海迪斯尼.md","localizedDate":"2024年11月10日","excerpt":"\\n","autoDesc":true}');export{h as comp,d as data}; diff --git "a/assets/3.\345\256\232\346\227\266\344\273\273\345\212\241.html-NSgzVWXM.js" "b/assets/3.\345\256\232\346\227\266\344\273\273\345\212\241.html-NSgzVWXM.js" new file mode 100644 index 00000000..09a2fb62 --- /dev/null +++ "b/assets/3.\345\256\232\346\227\266\344\273\273\345\212\241.html-NSgzVWXM.js" @@ -0,0 +1,61 @@ +import{_ as s,c as a,d as n,o as t}from"./app-ftEjETWs.js";const l={};function e(h,i){return t(),a("div",null,i[0]||(i[0]=[n(`先看一下Quartz的架构图:
<dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context-support</artifactId>
+ <version>4.2.6.RELEASE</version>
+</dependency>
(2)导入quartz包
<dependency>
+ <groupId>org.quartz-scheduler</groupId>
+ <artifactId>quartz</artifactId>
+ <version>2.3.0</version>
+</dependency>
(3)mysql远程备份
使用命令行工具仅仅需要一行:
mysqldump -u [username] -p[password] -h [hostip] database > file
但是java不能直接执行linux的命令,仍旧需要依赖第三方库ganymed
<dependency>
+ <groupId>ch.ethz.ganymed</groupId>
+ <artifactId>ganymed-ssh2</artifactId>
+ <version>262</version>
+</dependency>
完整代码如下:
@Component("mysqlService")//在spring中注册一个mysqlService的Bean
+public class MysqlUtil {
+ ...
+ StringBuffer sb = new StringBuffer();
+ sb.append("mysqldump -u " + username + " -p" + password + " -h " + host + " " +
+ database + " >" + file);
+ String sql = sb.toString();
+ Connection connection = new Connection(s_host);
+ connection.connect();
+ boolean isAuth = connection.authenticateWithPassword(s_username, s_password);//进行远程服务器登陆认证
+ if (!isAuth) {
+ logger.error("server login error");
+ }
+ Session session = connection.openSession();
+ session.execCommand(sql);//执行linux语句
+ InputStream stdout = new StreamGobbler(session.getStdout());
+ BufferedReader br = new BufferedReader(new InputStreamReader(stdout));
+ ...
+}
(4)spring中配置quartz
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
+ <property name="targetObject" ref="mysqlService"/>
+ <property name="targetMethod" value="exportDataBase"/>
+</bean>
+<!--定义触发时间 -->
+<bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
+ <property name="jobDetail" ref="jobDetail"/>
+ <!-- cron表达式,每周五2点59分运行-->
+ <property name="cronExpression" value="0 59 2 ? * FRI"/>
+</bean>
+<!-- 总管理类 如果将lazy-init='false'那么容器启动就会执行调度程序 -->
+<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
+ <property name="triggers">
+ <list>
+ <ref bean="myTrigger"/>
+ </list>
+ </property>
+</bean>
(5)java完整文件在这
java ee项目的定时任务中除了运行quartz之外,spring3+还提供了task,可以看做是一个轻量级的Quartz,而且使用起来比Quartz简单的多。
(1)spring配置文件中配置:
<task:annotation-driven/>
(2)最简单的例子,在所需要的函数上添加定时任务即可运行
@Scheduled(fixedRate = 5000)
+ public void reportCurrentTime() {
+ System.out.println("每隔5秒运行一次" + sdf.format(new Date()));
+ }
(3)运行的时候会报错:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.scheduling.TaskScheduler] is defined
+ at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:372)
+ at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:332)
+ at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.finishRegistration(ScheduledAnnotationBeanPostProcessor.java:192)
+ at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:171)
+ at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:86)
+ at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:163)
+ at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:136)
+ at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:380)
参考:
1.http://blog.csdn.net/oarsman/article/details/52801877
2.http://stackoverflow.com/questions/31199888/spring-task-scheduler-no-qualifying-bean-of-type-org-springframework-scheduli
Spring的定时任务调度器会尝试获取一个注册过的 task scheduler来做任务调度,它会尝试通过BeanFactory.getBean的方法来获取一个注册过的scheduler bean,获取的步骤如下:
1.尝试从配置中找到一个TaskScheduler Bean
2.寻找ScheduledExecutorService Bean
3.使用默认的scheduler
修改log4j.properties即可:
log4j.logger.org.springframework.scheduling=INFO
其实这个功能不影响定时器的功能。
(4)结果:
每隔5秒运行一次14:44:34
+每隔5秒运行一次14:44:39
+每隔5秒运行一次14:44:44
先看一下Quartz的架构图:
\\n","autoDesc":true}');export{p as comp,r as data}; diff --git "a/assets/4.\346\227\245\345\277\227\347\263\273\347\273\237.html-BH3nB-v9.js" "b/assets/4.\346\227\245\345\277\227\347\263\273\347\273\237.html-BH3nB-v9.js" new file mode 100644 index 00000000..eeb64c4d --- /dev/null +++ "b/assets/4.\346\227\245\345\277\227\347\263\273\347\273\237.html-BH3nB-v9.js" @@ -0,0 +1,206 @@ +import{_ as s,c as a,d as n,o as t}from"./app-ftEjETWs.js";const h={};function l(e,i){return t(),a("div",null,i[0]||(i[0]=[n(`欢迎访问我的网站http://www.wenzhihuai.com/ 。感谢,如果可以,希望能在GitHub上给个star,GitHub地址https://github.com/Zephery/newblog 。
建立网站少不了日志系统,用来查看网站的访问次数、停留时间、抓取量、目录抓取统计、页面抓取统计等,其中,最常用的方法还是使用ELK,但是,本网站的服务器配置实在太低了(1GHZ、2G内存),压根就跑不起ELK,所以只能寻求其他方式,目前最常用的有百度统计和友盟,这里,本人使用的是百度统计,提供了API给开发者使用,能够将自己所需要的图表移植到自己的网站上。日志是网站及其重要的文件,通过对日志进行统计、分析、综合,就能有效地掌握网站运行状况,发现和排除错误原因,了解客户访问分布等,更好的加强系统的维护和管理。下面是我的百度统计的概览页面:
企业级的网站日志不能公开,但是我的是个人网站,用来跟大家一起学习的,所以,需要将百度的统计页面展示出来,但是,百度并不提供日志的图像,只提供API给开发者调用,而且还限制访问次数,一天不能超过2000次,这个对于实时统计来说,确实不够,所以只能展示前几天的访问统计。这里的日志系统分为三个步骤:1.API获取数据;2.存储数据;3.展示数据。页面效果如下,也可以点开我的网站的日志系统:
百度统计提供了Tongji API的Java和Python版本,这两个版本及其复杂,可用性极低,所以,本人用Python写了个及其简单的通用版本,整体只有28行,代码在这,https://github.com/Zephery/baidutongji。下面是具体过程
先在百度统计中注册登录之后,进入管理页面,新增网站,然后在代码管理中获取安装代码,大部分人的代码都是类似的,除了hm.js?后面的参数,是记录该网站的唯一标识。
<script>
+var _hmt = _hmt || [];
+(function() {
+ var hm = document.createElement("script");
+ hm.src = "https://hm.baidu.com/hm.js?code";
+ var s = document.getElementsByTagName("script")[0];
+ s.parentNode.insertBefore(hm, s);
+})();
+</script>
同时,需要在申请其他设置->数据导出服务中开通数据导出服务,百度统计Tongji API可以为网站接入者提供便捷的获取网站流量数据的通道。
至此,我们获得了username、password、token,然后开始使用三个参数来获取数据。
官网的API详细的记录了接口的参数以及解释,
链接:https://api.baidu.com/json/tongji/v1/ReportService/getData,详细的官方报告请访问官网TongjiApi
所需参数(必须):
参数名称 | 参数类型 | 描述 |
---|---|---|
method | string | 要查询的报告 |
start_date | string | 查询起始时间 |
end_date | string | 查询结束时间 |
metrics | string | 自定义指标 |
其中,参数start_date和end_date的规定为:yyyyMMdd,这里我们使用python的原生库,datetime、time,获取昨天的时间以及前七天的日期。
today = datetime.date.today() # 获取今天的日期
+yesterday = today - datetime.timedelta(days=1) # 获取昨天的日期
+fifteenago = today - datetime.timedelta(days=7) # 获取前七天的日期
+end, start = str(yesterday).replace("-", ""), str(fifteenago).replace("-", "") # 格式化成yyyyMMdd格式
说明:siteId可以根据个人百度统计的链接获取,也可以使用Tongji API的第一个接口列表获取用户的站点列表。首先,我们构建一个类,由于username、password、token都是通用的,所以我们将它设置为构造方法的参数。
class Baidu(object):
+ def __init__(self, siteId, username, password, token):
+ self.siteId = siteId
+ self.username = username
+ self.password = password
+ self.token = token
然后构建一个共同的方法,用来获取提交数据之后返回的结果,其中提供了4个可变参数,分别是(start_date:起始日期,end_date:结束日期,method:方法,metrics:指标),返回的是字节,最后需要decode("utf-8")一下变成字符:
def getresult(self, start_date, end_date, method, metrics):
+ body = {"header": {"account_type": 1, "password": self.password, "token": self.token,
+ "username": self.username},
+ "body": {"siteId": self.siteId, "method": method, "start_date": start_date,
+ "end_date": end_date,
+ "metrics": metrics}}
+ data = bytes(json.dumps(body), 'utf8')
+ req = urllib.request.Request(base_url, data)
+ response = urllib.request.urlopen(req)
+ the_page = response.read()
+ return the_page.decode("utf-8")
至此,python获取百度统计的过程基本就没了,没错,就是那么简简单单的几行,完整代码见https://github.com/Zephery/baidutongji/blob/master/baidu.py,但是,想要实现获取各种数据,仍需要做很多工作。
(1)需要使用其他参数怎么办
python中提供了个可变参数来解决这一烦恼,详细请看http://www.jianshu.com/p/98f7e34845b5,可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple,而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
def getresult(self, start_date, end_date, method, metrics, **kw):
+ base_url = "https://api.baidu.com/json/tongji/v1/ReportService/getData"
+ body = {"header": {"account_type": 1, "password": self.password, "token": self.token,
+ "username": self.username},
+ "body": {"siteId": self.siteId, "method": method, "start_date": start_date,
+ "end_date": end_date, "metrics": metrics}}
+ for key in kw: #对可变参数进行遍历,如果有的话就往body中加入
+ body['body'][key] = kw[key]
使用方式:
result = self.getresult(start, end, "source/all/a",
+ "pv_count,visitor_count,avg_visit_time", viewType='visitor') #其中viewTYpe便是可变参数
(2)获取的数据如何解析
百度统计返回的结果比较简洁而又反人类,以获取概览中的pv_count,visitor_count,ip_count,bounce_ratio,avg_visit_time为例子:
result = bd.getresult(start, end, "overview/getTimeTrendRpt",
+ "pv_count,visitor_count,ip_count,bounce_ratio,avg_visit_time")
返回的结果是:
[[['2017/09/12'], ['2017/09/13'], ['2017/09/14'], ['2017/09/15'], ['2017/09/16'], ['2017/09/17'], ['2017/09/18']],
+[[422, 76, 76, 41.94, 221],
+ [284, 67, 65, 50.63, 215],
+ [67, 23, 22, 52.17, 153],
+ [104, 13, 13, 36.36, 243],
+ [13, 4, 4, 33.33, 66],
+ [73, 7, 6, 37.5, 652],
+ [63, 11, 11, 33.33, 385]
+ ], [], []]
即:翻译成人话就是:
[[[date1,date2,...]],
+ [[date1的pv_count, date1的visitor_count, date1的ip_count, date1的bounce_ratio, date1的avg_visit_time],
+ [date2的pv_count, date2的visitor_count, date2的ip_count, date2的bounce_ratio, date2的avg_visit_time],
+ ...,[]
+ ],[],[]]
极其反人类的设计。还好用的python,python数组的特性实在太强了。出了可以运用[x for x in range]这类语法之外,还能与三元符(x if y else x+1,如果y成立,那么结果是x,如果y不成立,那么结果是x+1)一起使用,这里注意:如果当天访问量为0,其返回的json结果是'--',所以要判断是不是为'--',归0化,才能在折线图等各种图上显示。下面是pv_count的例子:
pv_count = [x[0] if x[0] != '--' else 0 for x in result[1]]
(3)每周限制2000次
在开通数据导出服务的时候,不知道大家有没有注意到它的说明,即我们是不能实时监控的,只能将它放在临时数据库中,这里我们选择了Redis,并在centos里定义一个定时任务,每天全部更新一次即可。
python中redis的使用方法很简单,连接跟mysql类似:
# 字符串
+pool = redis.ConnectionPool(host='your host ip', port=port, password='your auth') # TODO redis地址
+r = redis.Redis(connection_pool=pool)
本网站使用redis的数据结构只有set,方法也很简单,就是定义一个key,然后value是数组的字符串获取json。
ip_count = [x[2] if x[2] != '--' else 0 for x in result[1]]
+r.set("ip_count", ip_count)
+# json
+name = [item[0]['name'] for item in data[0]]
+count = 0
+tojson = []
+for item in data[1]:
+ temp = {}
+ temp["name"] = name[count]
+ temp["pv_count"] = item[0]
+ temp["visitor_count"] = item[1]
+ temp["average_stay_time"] = item[2]
+ tojson.append(temp)
+ count = count + 1
+r.set("rukouyemian", json.dumps(tojson[:5]))
下面是基本的使用代码,完整的使用代码就不贴了,有兴趣可以去我的github上看看,完整代码,希望能给个star哈哈哈,感谢
import json
+import time
+import datetime
+import urllib.parse
+import urllib.request
+
+base_url = "https://api.baidu.com/json/tongji/v1/ReportService/getData"
+
+class Baidu(object):
+ def __init__(self, siteId, username, password, token):
+ self.siteId = siteId
+ self.username = username
+ self.password = password
+ self.token = token
+
+ def getresult(self, start_date, end_date, method, metrics, **kw):
+ base_url = "https://api.baidu.com/json/tongji/v1/ReportService/getData"
+ body = {"header": {"account_type": 1, "password": self.password, "token": self.token,
+ "username": self.username},
+ "body": {"siteId": self.siteId, "method": method, "start_date": start_date,
+ "end_date": end_date, "metrics": metrics}}
+ for key in kw:
+ body['body'][key] = kw[key]
+ data = bytes(json.dumps(body), 'utf8')
+ req = urllib.request.Request(base_url, data)
+ response = urllib.request.urlopen(req)
+ the_page = response.read()
+ return the_page.decode("utf-8")
+
+if __name__ == '__main__':
+ # 日期开始
+ today = datetime.date.today()
+ yesterday = today - datetime.timedelta(days=1)
+ fifteenago = today - datetime.timedelta(days=7)
+ end, start = str(yesterday).replace("-", ""), str(fifteenago).replace("-", "")
+ # 日期结束
+ bd = Baidu(yoursiteid, "username", "password", "token")
+ result = bd.getresult(start, end, "overview/getTimeTrendRpt",
+ "pv_count,visitor_count,ip_count,bounce_ratio,avg_visit_time")
+ result = json.loads(result)
+ base = result["body"]["data"][0]["result"]["items"]
+ print(base)
在将数据存进redis中之后,我们需要在博客中使用这些数据来制作图表。在newblog中使用方式也很简单,大概就是使用jedis读取数据,然后使用echarts或者highcharts展示。其中折线图以及线型图我都使用了highcharts,确实比echarts好看的多,但是地域图还是选择了echarts,毕竟中国的产品还是对中国的支持较好。
(1)PV、UV折线图
以图表PV、UV为例,由于存储进redis的是一个数组,所以,可以直接从redis中读取然后放到一个attribute里即可:
String pv_count = jedis.get("pv_count");
+String visitor_count = jedis.get("visitor_count");
+mv.addObject("pv_count", pv_count);
+mv.addObject("visitor_count", visitor_count);
jsp中的使用如下:
<div class="panel-heading" style="background-color: rgba(187,255,255,0.7)">
+ <div class="card-title">
+ <strong>PV和UV折线图</strong>
+
+
+ <div class="panel-body">
+ <div id="linecontainer" style="width: auto;height: 330px">
+ <script>
+ var chart = new Highcharts.Chart('linecontainer', {
+ title: {
+ text: null
+ },
+ credits: {
+ enabled: false
+ },
+ xAxis: {
+ categories: ${daterange}
+ },
+ yAxis: {
+ title: {
+ text: '次数'
+ },
+ plotLines: [{
+ value: 0,
+ width: 1,
+ color: '#808080'
+ }]
+ },
+ tooltip: {
+ valueSuffix: '次'
+ },
+ legend: {
+ borderWidth: 0,
+ align: "center", //程度标的目标地位
+ verticalAlign: "top", //垂直标的目标地位
+ x: 0, //间隔x轴的间隔
+ y: 0 //间隔Y轴的间隔
+ },
+ series: [{
+ name: 'pv',
+ data:${pv_count}
+ }, {
+ name: 'uv',
+ data:${visitor_count}
+ }]
+ })
+ </script>
效果如下:
(2)地域访问量
在python代码中先获取地域的数据,其结果如下,百度统计跟echarts都是百度的,果然,自家人对自己人的支持真是特别友好的。
[{'pv_count': 649, 'pv_ratio': 7, 'visitor_count': 2684, 'name': '广东'}, {'pv_count': 2, 'pv_ratio': 2, 'visitor_count': 76, 'name': '四川'}, {'pv_count': 1, 'pv_ratio': 1, 'visitor_count': 3, 'name': '江苏'}]
地域图目前支持最好的还是百度的echarts,使用方法见echarts的官网吧,这里不再阐述,展示地域图的时候需要获取下载两个文件,china.js(其提供了js和json,这里使用的js),echarts.js。
部分代码:
<script type="text/javascript">
+ var myChart = echarts.init(document.getElementById('diyu'));
+ option = {
+ tooltip: {
+ trigger: 'item'
+ },
+ legend: {
+ orient: 'vertical',
+ left: 'left'
+ },
+ visualMap: {
+ min: 0,
+ max:\${diyumax},
+ left: 'left',
+ top: 'bottom',
+ text: ['高', '低'], // 文本,默认为数值文本
+ calculable: true
+ },
+ toolbox: {
+ show: true,
+ orient: 'vertical',
+ left: 'right',
+ top: 'center',
+ feature: {
+ dataView: {readOnly: false},
+ restore: {},
+ saveAsImage: {}
+ }
+ },
+ series: [
+ {
+ name: '访问量',
+ type: 'map',
+ mapType: 'china',
+ roam: false,
+ label: {
+ normal: {
+ show: true
+ },
+ emphasis: {
+ show: true
+ }
+ },
+ data: [
+ <c:forEach var="diyu" items="\${diyu}">
+ {name: '\${diyu.name}', value: \${to.pv_count}},
+ </c:forEach>
+ ]
+ }
+ ]
+ };
+ myChart.setOption(option);
+</script>
结果如下:
网上关于日志系统的几乎都是ELK,对于小网站的,隐私不是很重要的还是可以用用百度统计的,这套系统也折磨了我挺久的,特别是它那反人类的返回数据。期初本来是想使用百度统计的,后来考虑了一下ELK,尝试之后发现,服务器配置跑不起来,还是安安稳稳的使用了百度统计,于此做成了这个系统,美观度还是不高,颜色需要优化一下。最后,希望能在GitHub上给我个star吧。
日志系统地址:http://www.wenzhihuai.com/log.html
个人网站网址:http://www.wenzhihuai.com
个人网站代码地址:https://github.com/Zephery/newblog
百度统计python代码地址:https://github.com/Zephery/baidutongji
万分感谢
欢迎访问我的网站http://www.wenzhihuai.com/ 。感谢,如果可以,希望能在GitHub上给个star,GitHub地址https://github.com/Zephery/newblog 。
","autoDesc":true}');export{p as comp,r as data}; diff --git a/assets/404.html-BMHKR2ag.js b/assets/404.html-BMHKR2ag.js new file mode 100644 index 00000000..b9e45dea --- /dev/null +++ b/assets/404.html-BMHKR2ag.js @@ -0,0 +1 @@ +import{_ as e,c as o,a as n,o as r}from"./app-ftEjETWs.js";const a={};function p(c,t){return r(),o("div",null,t[0]||(t[0]=[n("p",null,"404 Not Found",-1)]))}const i=e(a,[["render",p],["__file","404.html.vue"]]),l=JSON.parse('{"path":"/404.html","title":"","lang":"zh-CN","frontmatter":{"layout":"NotFound","description":"404 Not Found","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/404.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:description","content":"404 Not Found"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"\\",\\"description\\":\\"404 Not Found\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0.01,"words":3},"filePathRelative":null,"excerpt":"404 Not Found
\\n","autoDesc":true}');export{i as comp,l as data}; diff --git "a/assets/5.\345\260\217\351\233\206\347\276\244\351\203\250\347\275\262.html-BN5IQG4o.js" "b/assets/5.\345\260\217\351\233\206\347\276\244\351\203\250\347\275\262.html-BN5IQG4o.js" new file mode 100644 index 00000000..1aa6f734 --- /dev/null +++ "b/assets/5.\345\260\217\351\233\206\347\276\244\351\203\250\347\275\262.html-BN5IQG4o.js" @@ -0,0 +1,96 @@ +import{_ as s,c as a,d as n,o as e}from"./app-ftEjETWs.js";const t={};function l(h,i){return e(),a("div",null,i[0]||(i[0]=[n(`欢迎访问我的个人网站O(∩_∩)O哈哈~希望大佬们能给个star,个人网站网址:http://www.wenzhihuai.com,个人网站代码地址:https://github.com/Zephery/newblog。
洋洋洒洒的买了两个服务器,用来学习分布式、集群之类的东西,整来整去,感觉分布式这种东西没人指导一下真的是太抽象了,先从网站的分布式部署一步一步学起来吧,虽然网站本身的访问量不大==。
一般情况下,当单实例无法支撑起用户的请求时,就需要就行扩容,部署的服务器可以分机房、分地域。而分地域会导致请求分配到太远的地区,比如:深圳的用户却访问到了北京的节点,然后还得从北京返回处理之后的数据,光是来回就至少得30ms。这部分可以通过智能DNS(就近访问)解决。而分机房,需要将请求合理的分配到不同的服务器,这部分就是我们所需要处理的。
通常,负载均衡分为硬件和软件两种,硬件层的比较牛逼,将4-7层负载均衡功能做到一个硬件里面,如F5,梭子鱼等。目前主流的软件负载均衡分为四层和七层,LVS属于四层负载均衡,工作在tcp/ip协议栈上,通过修改网络包的ip地址和端口来转发, 由于效率比七层高,一般放在架构的前端。七层的负载均衡有nginx, haproxy, apache等,虽然nginx自1.9.0版本后也开始支持四层的负载均衡,但是暂不讨论(我木有硬件条件)。下图来自张开涛的《亿级流量网站架构核心技术》
本站并没有那么多的服务器,目前只有两台,搭建不了那么大型的架构,就简陋的用两台服务器来模拟一下负载均衡的搭建。下图是本站的简单架构:
其中服务器A(119.23.46.71)为深圳节点,服务器B(47.95.10.139)为北京节点,搭建Nginx之后流量是这么走的:user->A->B-A->user或者user->A->user,第一条中A将请求转发给B,然后B返回的是其运行结果的静态资源。因为这里仅仅是用来学习,所以请不要考虑因为地域导致延时的问题。。。。下面是过程。
可以选择tar.gz、yum、rpm安装等,这里,由于编译、nginx配置比较复杂,要是没有把握还是使用rpm来安装吧,比较简单。从https://pkgs.org/download/nginx可以找到最新的rpm包,然后rpm -ivh 文件,然后在命令行中输入nginx即可启动,可以使用netstat检查一下端口。
启动后页面如下:
记一下常用命令
启动nginx,由于是采用rpm方式,所以环境变量什么的都配置好了。
+[root@beijingali ~]# nginx #启动nginx
+[root@beijingali ~]# nginx -s reload #重启nginx
+[root@beijingali ~]# nginx -t #校验nginx配置文件
+nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
+nginx: configuration file /etc/nginx/nginx.conf test is successful
Nginx常用的算法有:
(1)round-robin:轮询,nginx默认的算法,从词语上可以看出,轮流访问服务器,也可以通过weight来控制访问次数。
(2)ip_hash:根据访客的ip,一个ip地址对应一个服务器。
(3)hash算法:hash算法常用的方式有根据uri、动态指定的consistent_key两种。
使用hash算法的缺点是当添加服务器的时候,只有少部分的uri能够被重新分配到新的服务器。这里,本站使用的是hash uri的算法,将不同的uri分配到不同的服务器,但是由于是不同的服务器,tomcat中的session是不一致,解决办法是tomcat session的共享。额。。。可惜本站目前没有什么能够涉及到登陆什么session的问题。
http{
+ ...
+ upstream backend {
+ hash $uri;
+ # 北京节点
+ server 47.95.10.139:8080;
+ # 深圳节点
+ server 119.23.46.71:8080;
+ }
+
+ server {
+ ...
+ location / {
+ root html;
+ index index.html index.htm;
+ proxy_pass http://backend;
+ ...
+ }
+ ...
之前有使用过ELK来跟踪日志,所以将日志格式化成了json的格式,这里贴一下吧
...
+ log_format main '{"@timestamp":"$time_iso8601",'
+ '"host":"$server_addr",'
+ '"clientip":"$remote_addr",'
+ '"size":$body_bytes_sent,'
+ '"responsetime":$request_time,'
+ '"upstreamtime":"$upstream_response_time",'
+ '"upstreamhost":"$upstream_addr",'
+ '"http_host":"$host",'
+ '"url":"$uri",'
+ '"xff":"$http_x_forwarded_for",'
+ '"referer":"$http_referer",'
+ '"agent":"$http_user_agent",'
+ '"status":"$status"}';
+ access_log logs/access.log main;
+ ...
配置完上流服务器之后,需要配置Http的代理,将请求的端口转发到proxy_pass设定的上流服务器,即当我们访问http://wwww.wenzhihuai.com的时候,请求会被转发到backend中配置的服务器,此处为http://47.95.10.139:8080或者http://119.23.46.71:8080。但是,仔细注意之后,我们会发现,tomcat中的访问日志ip来源都是127.0.0.1,相当于本地访问自己的资源。由于后台中有处理ip的代码,对客户端的ip、访问uri等记录下来,所以需要设置nginx来获取用户的实际ip,参考nginx 配置。参考文中的一句话:经过反向代理后,由于在客户端和web服务器之间增加了中间层,因此web服务器无法直接拿到客户端的ip,通过$remote_addr变量拿到的将是反向代理服务器的ip地址”。nginx是可以获得用户的真实ip的,也就是说nginx使用$remote_addr变量时获得的是用户的真实ip,如果我们想要在web端获得用户的真实ip,就必须在nginx这里作一个赋值操作,如下:
location / {
+ root html;
+ index index.html index.htm;
+ proxy_pass http://backend;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header Host $host;
+ proxy_set_header REMOTE-HOST $remote_addr;
+ }
(1)proxy_set_header X-real-ip $remote_addr;
其中这个X-real-ip是一个自定义的变量名,名字可以随意取,这样做完之后,用户的真实ip就被放在X-real-ip这个变量里了,然后,在web端可以这样获取:
request.getAttribute("X-real-ip")
(2)proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
X-Forwarded-For:squid开发的,用于识别通过HTTP代理或负载平衡器原始IP一个连接到Web服务器的客户机地址的非rfc标准,这个不是默认有的,其经过代理转发之后,格式为client1, proxy1, proxy2,如果想通过这个变量来获取用户的ip,那么需要和$proxy_add_x_forwarded_for一起使用。
$proxy_add_x_forwarded_for:现在的$proxy_add_x_forwarded_for变量,X-Forwarded-For部分包含的是用户的真实ip,$remote_addr部分的值是上一台nginx的ip地址,于是通过这个赋值以后现在的X-Forwarded-For的值就变成了“用户的真实ip,第一台nginx的ip”。
HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。一般情况下,能通过服务器的ssh来生成ssl证书,但是如果使用是自己的,一般浏览器(谷歌、360等)都会报证书不安全的错误,正常用户都不敢访问吧==,所以现在使用的是腾讯跟别的机构颁发的:
首先需要下载证书,放在nginx.conf相同目录下,nginx上的配置也需要有所改变,在nginx.conf中设置listen 443 ssl;开启https。然后配置证书和私钥:
ssl_certificate 1_www.wenzhihuai.com_bundle.crt; #主要文件路径
+ ssl_certificate_key 2_www.wenzhihuai.com.key;
+ ssl_session_timeout 5m; # 超时时间
+ ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置
+ ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;#按照这个套件配置
+ ssl_prefer_server_ciphers on;
至此,可以使用https来访问了。https带来的安全性(保证信息安全、识别钓鱼网站等)是http远远不能比拟的,目前大部分网站都是实现全站https,还能将http自动重定向为https,此处,需要在server中添加rewrite ^(.*) https://$server_name$1 permanent;即可
配置好了负载均衡之后,如果有一台服务器挂了怎么办?nginx中提供了可配置的服务器存活的识别,主要是通过max_fails失败请求次数,fail_timeout超时时间,weight为权重,下面的配置的意思是当服务器超时10秒,并失败了两次的时候,nginx将认为上游服务器不可用,将会摘掉上游服务器,fail_timeout时间后会再次将该服务器加入到存活上游服务器列表进行重试
upstream backend_server {
+ server 10.23.46.71:8080 max_fails=2 fail_timeout=10s weight=1;
+ server 47.95.10.139:8080 max_fails=2 fail_timeout=10s weight=1;
+}
分布式情况下难免会要解决session共享的问题,目前推荐的方法基本上都是使用redis,网上查找的方法目前流行的有下面四种,参考自tomcat 集群中 session 共:
1.使用 filter 方法存储。(推荐,因为它的服务器使用范围比较多,不仅限于tomcat ,而且实现的原理比较简单容易控制。)
2.使用 tomcat sessionmanager 方法存储。(直接配置即可)
3.使用 terracotta 服务器共享。(不知道,不了解)
4.使用spring-session。(spring的一个小项目,其原理也和第一种基本一致)
本站使用spring-session,毕竟是spring下的子项目,学习下还是挺好的。参考Spring-Session官网。官方文档提供了spring-boot、spring等例子,可以参考参考。目前最新版本是2.0.0,不同版本使用方式不同,建议看官网的文档吧。
首先,添加相关依赖
<dependency>
+ <groupId>org.springframework.session</groupId>
+ <artifactId>spring-session-data-redis</artifactId>
+ <version>1.3.1.RELEASE</version>
+ <type>pom</type>
+ </dependency>
+ <dependency>
+ <groupId>redis.clients</groupId>
+ <artifactId>jedis</artifactId>
+ <version>\${jedis.version}</version>
+ </dependency>
新建一个session.xml,然后在spring的配置文件中添加该文件,然后在session.xml中添加:
<!-- redis -->
+ <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
+ </bean>
+
+ <bean id="jedisConnectionFactory"
+ class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
+ <property name="hostName" value="\${host}" />
+ <property name="port" value="\${port}" />
+ <property name="password" value="\${password}" />
+ <property name="timeout" value="\${timeout}" />
+ <property name="poolConfig" ref="jedisPoolConfig" />
+ <property name="usePool" value="true" />
+ </bean>
+
+ <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
+ <property name="connectionFactory" ref="jedisConnectionFactory" />
+ </bean>
+
+ <!-- 将session放入redis -->
+ <bean id="redisHttpSessionConfiguration"
+ class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
+ <property name="maxInactiveIntervalInSeconds" value="1800" />
+ </bean>
然后我们需要保证servlet容器(tomcat)针对每一个请求都使用springSessionRepositoryFilter来拦截
<filter>
+ <filter-name>springSessionRepositoryFilter</filter-name>
+ <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+</filter>
+<filter-mapping>
+ <filter-name>springSessionRepositoryFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ <dispatcher>REQUEST</dispatcher>
+ <dispatcher>ERROR</dispatcher>
+</filter-mapping>
配置完成,使用RedisDesktopManager查看结果:
访问http://www.wenzhihuai.com
tail -f localhost_access_log.2017-11-05.txt查看日志,然后清空一下当前记录
访问技术杂谈页面,此时nginx将请求转发到119.23.46.71服务器,session为28424f91-5bc5-4bba-99ec-f725401d7318。
点击生活笔记页面,转发到的服务器为47.95.10.139,session为28424f91-5bc5-4bba-99ec-f725401d7318,与上面相同。session已保持一致。
值得注意的是:同一个浏览器,在没有关闭的情况下,即使通过域名访问和ip访问得到的session是不同的。
欢迎访问我的个人网站O(∩_∩)O哈哈~希望能给个star
个人网站网址:http://www.wenzhihuai.com
个人网站代码地址:https://github.com/Zephery/newblog
欢迎访问我的个人网站O(∩_∩)O哈哈~希望大佬们能给个star,个人网站网址:http://www.wenzhihuai.com,个人网站代码地址:https://github.com/Zephery/newblog。
\\n洋洋洒洒的买了两个服务器,用来学习分布式、集群之类的东西,整来整去,感觉分布式这种东西没人指导一下真的是太抽象了,先从网站的分布式部署一步一步学起来吧,虽然网站本身的访问量不大==。
先来回顾一下上一篇的小集群架构,tomcat集群,nginx进行反向代理,服务器异地:
由上一篇讲到,部署的时候,将war部署在不同的服务器里,通过spring-session实现了session共享,基本的分布式部署还算是完善了点,但是想了想数据库的访问会不会延迟太大,毕竟一个服务器在北京,一个在深圳,然后试着ping了一下:
果然,36ms。。。看起来挺小的,但是对比一下sql执行语句的时间:
大部分都能在10ms内完成,而最长的语句是insert语句,可见,由于异地导致的36ms延时还是比较大的,捣鼓了一下,最后还是选择换个架构,每个服务器读取自己的数据库,然后数据库底层做一下主主复制,让数据同步。最终架构如下:
数据库复制的基本问题就是让一台服务器的数据与其他服务器保持同步。MySql目前支持两种复制方式:基于行的复制和基于语句的复制,这两者的基本过程都是在主库上记录二进制的日志、在备库上重放日志的方式来实现异步的数据复制。其过程分为三步:
(1)master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events);
(2)slave将master的binary log events拷贝到它的中继日志(relay log);
(3)slave重做中继日志中的事件,将改变反映它自己的数据。
该过程的第一部分就是master记录二进制日志。在每个事务更新数据完成之前,master在二日志记录这些改变。MySQL将事务串行的写入二进制日志,即使事务中的语句都是交叉执行的。在事件写入二进制日志完成后,master通知存储引擎提交事务。
下一步就是slave将master的binary log拷贝到它自己的中继日志。首先,slave开始一个工作线程——I/O线程。I/O线程在master上打开一个普通的连接,然后开始binlog dump process。Binlog dump process从master的二进制日志中读取事件,如果已经跟上master,它会睡眠并等待master产生新的事件。I/O线程将这些事件写入中继日志。
SQL slave thread处理该过程的最后一步。SQL线程从中继日志读取事件,更新slave的数据,使其与master中的数据一致。只要该线程与I/O线程保持一致,中继日志通常会位于OS的缓存中,所以中继日志的开销很小。
此外,在master中也有一个工作线程:和其它MySQL的连接一样,slave在master中打开一个连接也会使得master开始一个线程。复制过程有一个很重要的限制——复制在slave上是串行化的,也就是说master上的并行更新操作不能在slave上并行操作。
MySql的基本复制方式有主从复制、主主复制,主主复制即把主从复制的配置倒过来再配置一遍即可,下面的配置则是主从复制的过程,到时候可自行改为主主复制。其他的架构如:一主库多备库、环形复制、树或者金字塔型都是基于这两种方式,可参考《高性能MySql》。
由于是个自己的小网站,就不做过多的操作了,直接使用root账号
接下来要对mysql的serverID,日志位置,复制方式等进行操作,使用vim打开my.cnf。
[client]
+default-character-set=utf8
+
+[mysqld]
+character_set_server=utf8
+init_connect= SET NAMES utf8
+
+datadir=/var/lib/mysql
+socket=/var/lib/mysql/mysql.sock
+
+symbolic-links=0
+
+log-error=/var/log/mysqld.log
+pid-file=/var/run/mysqld/mysqld.pid
+
+# master
+log-bin=mysql-bin
+# 设为基于行的复制
+binlog-format=ROW
+# 设置server的唯一id
+server-id=2
+# 忽略的数据库,不使用备份
+binlog-ignore-db=information_schema
+binlog-ignore-db=cluster
+binlog-ignore-db=mysql
+# 要进行备份的数据库
+binlog-do-db=myblog
重启Mysql之后,查看主库状态,show master status。
其中,File为日志文件,指定Slave从哪个日志文件开始读复制数据,Position为偏移,从哪个POSITION号开始读,Binlog_Do_DB为要备份的数据库。
从库的配置跟主库类似,vim /etc/my.cnf配置从库信息。
+[client]
+default-character-set=utf8
+
+[mysqld]
+character_set_server=utf8
+init_connect= SET NAMES utf8
+
+datadir=/var/lib/mysql
+socket=/var/lib/mysql/mysql.sock
+
+symbolic-links=0
+
+log-error=/var/log/mysqld.log
+pid-file=/var/run/mysqld/mysqld.pid
+
+# slave
+log-bin=mysql-bin
+# 服务器唯一id
+server-id=3
+# 不备份的数据库
+binlog-ignore-db=information_schema
+binlog-ignore-db=cluster
+binlog-ignore-db=mysql
+# 需要备份的数据库
+replicate-do-db=myblog
+# 其他相关信息
+slave-skip-errors=all
+slave-net-timeout=60
+# 开启中继日志
+relay_log = mysql-relay-bin
+#
+log_slave_updates = 1
+# 防止改变数据
+read_only = 1
重启slave,同时启动复制,还需要调整一下命令。
mysql> CHANGE MASTER TO MASTER_HOST = '119.23.46.71', MASTER_USER = 'root', MASTER_PASSWORD = 'helloroot', MASTER_PORT = 3306, MASTER_LOG_FILE = 'mysql-bin.000009', MASTER_LOG_POS = 346180;
可以看见slave已经开始进行同步了。我们使用show slave status\\G来查看slave的状态。
其中日志文件和POSITION不一致是合理的,配置好了的话,即使重启,也不会影响到主从复制的配置。
某天在Github上漂游,发现了阿里的canal,同时才知道上面这个业务是叫异地跨机房同步,早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务。下面是基本的原理:
原理相对比较简单:
1.canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议
2.mysql master收到dump请求,开始推送binary log给slave(也就是canal)
3.canal解析binary log对象(原始为byte流)
其中,配置过程如下:https://github.com/alibaba/canal,可以搭配Zookeeper使用。在ZKUI中能够查看到节点:
一般情况下,还要配合阿里的另一个开源产品使用otter,相关文档还是找找GitHub吧,个人搭建完了之后,用起来还是不如直接使用mysql的主主复制,而且异地机房同步这种大企业才有的业务。
公司又要996了,实在是忙不过来,感觉自己写的还是急躁了点,困==
`,38)]))}const h=i(l,[["render",t],["__file","6.数据库备份.html.vue"]]),d=JSON.parse('{"path":"/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/6.%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A4%87%E4%BB%BD.html","title":"6.数据库备份","lang":"zh-CN","frontmatter":{"description":"6.数据库备份 先来回顾一下上一篇的小集群架构,tomcat集群,nginx进行反向代理,服务器异地: 由上一篇讲到,部署的时候,将war部署在不同的服务器里,通过spring-session实现了session共享,基本的分布式部署还算是完善了点,但是想了想数据库的访问会不会延迟太大,毕竟一个服务器在北京,一个在深圳,然后试着ping了一下: 果然,...","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/6.%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A4%87%E4%BB%BD.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"6.数据库备份"}],["meta",{"property":"og:description","content":"6.数据库备份 先来回顾一下上一篇的小集群架构,tomcat集群,nginx进行反向代理,服务器异地: 由上一篇讲到,部署的时候,将war部署在不同的服务器里,通过spring-session实现了session共享,基本的分布式部署还算是完善了点,但是想了想数据库的访问会不会延迟太大,毕竟一个服务器在北京,一个在深圳,然后试着ping了一下: 果然,..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://github-images.wenzhihuai.com/images/20171018051437-20240126113709561.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-05-11T18:24:35.000Z"}],["meta",{"property":"article:modified_time","content":"2024-05-11T18:24:35.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"6.数据库备份\\",\\"image\\":[\\"https://github-images.wenzhihuai.com/images/20171018051437-20240126113709561.png\\",\\"https://github-images.wenzhihuai.com/images/20171118033130.png\\",\\"https://github-images.wenzhihuai.com/images/20171118034129.png\\",\\"https://github-images.wenzhihuai.com/images/20171118035150.png\\",\\"https://github-images.wenzhihuai.com/images/20171118040843.png\\",\\"https://github-images.wenzhihuai.com/images/20171118050128.png\\",\\"https://github-images.wenzhihuai.com/images/20171118051031.png\\",\\"https://github-images.wenzhihuai.com/images/20171120094405.png\\",\\"https://github-images.wenzhihuai.com/images/20171120100237.png\\"],\\"dateModified\\":\\"2024-05-11T18:24:35.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[{"level":3,"title":"2.1 创建所用的复制账号","slug":"_2-1-创建所用的复制账号","link":"#_2-1-创建所用的复制账号","children":[]},{"level":3,"title":"2.2 配置master","slug":"_2-2-配置master","link":"#_2-2-配置master","children":[]},{"level":3,"title":"2.3 配置slave","slug":"_2-3-配置slave","link":"#_2-3-配置slave","children":[]}],"git":{"createdTime":1579574285000,"updatedTime":1715451875000,"contributors":[{"name":"zhihuaiwen","email":"zhihuaiwen@tencent.com","commits":1}]},"readingTime":{"minutes":5.77,"words":1732},"filePathRelative":"interesting/个人网站/6.数据库备份.md","localizedDate":"2020年1月21日","excerpt":"\\n先来回顾一下上一篇的小集群架构,tomcat集群,nginx进行反向代理,服务器异地:
","autoDesc":true}');export{h as comp,d as data}; diff --git "a/assets/7.\351\202\243\344\272\233\347\211\233\351\200\274\347\232\204\346\217\222\344\273\266.html-Br7NfWE4.js" "b/assets/7.\351\202\243\344\272\233\347\211\233\351\200\274\347\232\204\346\217\222\344\273\266.html-Br7NfWE4.js" new file mode 100644 index 00000000..7d7c0fbf --- /dev/null +++ "b/assets/7.\351\202\243\344\272\233\347\211\233\351\200\274\347\232\204\346\217\222\344\273\266.html-Br7NfWE4.js" @@ -0,0 +1,13 @@ +import{_ as e,c as a,d as s,o as t}from"./app-ftEjETWs.js";const n={};function r(h,i){return t(),a("div",null,i[0]||(i[0]=[s(`欢迎访问我的网站http://www.wenzhihuai.com/ 。感谢,如果可以,希望能在GitHub上给个star,GitHub地址https://github.com/Zephery/newblog 。
建站的一开始,我也想自己全部实现,各种布局,各种炫丽的效果,想做点能让大家佩服的UI出来,但是,事实上,自己作为专注Java的程序员,前端的东西一碰脑子就有“我又不是前端,浪费时间在这合适么?”这种想法,捣鼓来捣鼓去,做出的东西实在是没法看,我就觉得,如果自己的“产品”连自己都看不下去了,那还好意思给别人看?特别是留言板那块,初版的页面简直low的要死。所以,还是踏踏实实的“站在巨人的肩膀上”吧,改用别人的插件。但不要纯粹的使用别人的博客模板了,如hexo,wordpress这些,就算是自己拼凑过来的也比这些强。下面是本博客中所用到的插件,给大家介绍介绍,共同学习学习。
本站主要用到的插件有:
1.wowslider
2.畅言
3.Editor.md
4.highchart、echart
5.百度分享
6.waterfall.js
7.心知天气
8.标签云
可能是我这网站中最炫的东西了,图片能够自动像幻灯片一样自动滚动,让网站的首页一看起来就高大上,简直就是建站必备的东西,而且安装也及其简单,有兴趣可以点击官网看看。GitHub里也开放了源代码。安装过程:自己选择“幻灯片”切换效果,保存为html就行了,WORDPREESS中好像有集成这个插件的,做的还更好。感兴趣可以点击我的博客首页看一看。
不过还有个值得注意的问题,就是wowslider里面带有一个googleapis的服务,即https://fonts.googleapis.com/css?family=Arimo&subset=latin,cyrillic,latin-ext,由于一般用户不能访问谷歌,会导致网页加载速度及其缓慢,所以,去掉为妙
作为社交评论的工具,虽然说表示还是想念以前的多说,但是畅言现在做得还是好了,有评论审核,评论导出导入等功能,如果浏览量大的话,还能提供广告服务,让站长也能拿到一丢丢的广告费。本博客中使用了畅言的基本评论、获取某篇文章评论数的功能。可以到我这里留言哈
一款能将markdown解析为html的插件,国人的作品,博客的文章编辑器一开始想使用的是markdown,想法是:写文章、保存数据库都是markdown格式的,保存在数据库中,读取时有需要解析markdown,这个过程是有点耗时的,但是相比把html式的网页保存在数据库中友好点吧,因为如果一篇文章比较长的话,转成html的格式,光是大小估计也得超过几十kb?所以,还是本人选择的是一切都用源markdown。
editor.md,是一款开源的、可嵌入的 Markdown 在线编辑器(组件),基于 CodeMirror、jQuery 和 Marked 构建。页面看起来还是美观的,相比WORDPRESS的那些牛逼插件还差那么一点点,不过从普通人的眼光来看,应该是可以的了。此处,我用它作为解析网页的利器,还有就是后台编辑也是用的这个。
代码样式,这一点是不如WORDPRESS的插件了,不过已经可以了。
目前最常用的是highcharts跟echart,目前个人博客中的日志系统主要还是采用了highcharts,主要还是颜色什么的格调比较相符吧,其次是因为对echarts印象不太友好,比如下面做这张,打开网页后,缩小浏览器,百度的地域图却不能自适应,出现了越界,而highcharts的全部都能自适应调整。想起有次面试,我说我用的highcharts,面试官一脸嫌弃。。。(网上这么多人鄙视百度是假的?)
不过地图确确实实是echarts的优势,毕竟还是自家的东西了解自家,不过前段时间去看了看echarts的官网,已经不提供下载了。如果有需要,还是去csdn上搜索吧,或者替换为highmap。
作为一个以博客为主的网站,免不了使用一些社会化分享的工具,目前主要是jiathis和百度分享,这两者的ui都是相似的(丑爆了)。凭我个人的感觉,jiathis加载实在是太过于缓慢,这点是无法让人忍受的,只好投靠百度。百度分享类似于jiathis,安装也很简单,具体见官网http://share.baidu.com/code/advance#tools。一直点点点,配置完之后,就是下图这种,丑爆了是不是?
好在对它的美观改变不是很难,此处参考了别人的UI设计,原作者我忘记怎么联系他了。其原理主要是使用图片来替换掉原本的东西。完整的源码可以点击此处。
#share a {
+ width: 34px;
+ height: 34px;
+ padding: 0;
+ margin: 6px;
+ border-radius: 25px;
+ transition: all .4s;
+}
+/*主要的是替换掉backgound的背景图片*/
+#share a.bds_qzone {
+ background: url(http://image.wenzhihuai.com/t_QQZone.png) no-repeat;
+ background-size: 34px 34px;
+}
改完之后的效果。
有段时间,瀑布流特别流行?还有段时间,瀑布流开始遭到各种抵制。。。看看知乎的人怎么说,大部分人不喜欢的原因是让人觉得视觉疲劳,不过瀑布流最大的好处还是有的:提高发现好图的效率以及图片列表页极强的视觉感染力。没错,我还是想把自己的网站弄得铉一些==,所以采用了瀑布流(不过效果不是很好,某些浏览器甚至加载出错),这个大bug有时间再改,毕竟花了很多时间做的这个,效果确实不咋地。目前主要的瀑布流有waterfall.js和masory.js。这一块目前还不是很完善,希望能得到各位大佬的指点。
此类咨询服务还是网上还是挺多的,这一块不知道是不是所谓的“画蛇添足”这部分,主要是我觉得网站右边这部分老是少了点什么,所以加上了天气插件。目前常用的天气插件有中国天气网,心知天气等。安装方式见各自的官网,这里不再阐述,我使用的是心知天气。注意:心知天气限制流量的,一个小时内只能使用400次,如果超出了会失效,当然也可以付费使用。
标签云,弄得好的话应该说是一个网站的点缀。现在好像比较流行3D的标签云?像下面这种。
从个人的网站风格来看,比较适应PHP形式的,有点颜色而又不绚丽的即可,之前用的跟分类的一样的样式,即双纵列的样式,美观度还行,虽然老是感觉有点怪怪的,如果某个标签的字数过长怎么办,岂不是要顶出div了。所以还是选择换另一种风格,最终偶然一次找到了下面这种,能够自适应宽度,颜色虽然鲜艳了点(以后有空再调一下吧),源码见style.css。 下图为目前的标签页。
作为一个后端人员,调css和js真是痛苦==,好在坚持下来了,虽然还是很多不足,以后有时间慢慢改。说了那么多,感觉自己还是菜的抠脚。
题外话,搭建一个博客,对于一个新近程序员来说真的是锻炼自己的一个好机会,能够认识到从前端、java后台、linux、jvm等等知识,只是真的有点耗时间(还不如把时间耗在Spring源码),如果不采用别人的框架的话,付出的代价还是蛮大的(所以不要鄙视我啦)。没有什么能够一举两得,看自己的取舍吧。加油💪(ง •_•)ง
欢迎访问我的网站http://www.wenzhihuai.com/ 。感谢,如果可以,希望能在GitHub上给个star,GitHub地址https://github.com/Zephery/newblog 。
`,40)]))}const p=e(n,[["render",r],["__file","7.那些牛逼的插件.html.vue"]]),g=JSON.parse('{"path":"/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/7.%E9%82%A3%E4%BA%9B%E7%89%9B%E9%80%BC%E7%9A%84%E6%8F%92%E4%BB%B6.html","title":"7.那些牛逼的插件","lang":"zh-CN","frontmatter":{"description":"7.那些牛逼的插件 欢迎访问我的网站http://www.wenzhihuai.com/ 。感谢,如果可以,希望能在GitHub上给个star,GitHub地址https://github.com/Zephery/newblog 。 建站的一开始,我也想自己全部实现,各种布局,各种炫丽的效果,想做点能让大家佩服的UI出来,但是,事实上,自己作为专注Ja...","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/7.%E9%82%A3%E4%BA%9B%E7%89%9B%E9%80%BC%E7%9A%84%E6%8F%92%E4%BB%B6.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"7.那些牛逼的插件"}],["meta",{"property":"og:description","content":"7.那些牛逼的插件 欢迎访问我的网站http://www.wenzhihuai.com/ 。感谢,如果可以,希望能在GitHub上给个star,GitHub地址https://github.com/Zephery/newblog 。 建站的一开始,我也想自己全部实现,各种布局,各种炫丽的效果,想做点能让大家佩服的UI出来,但是,事实上,自己作为专注Ja..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://github-images.wenzhihuai.com/images/20171121023427.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-05-11T18:24:35.000Z"}],["meta",{"property":"article:modified_time","content":"2024-05-11T18:24:35.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"7.那些牛逼的插件\\",\\"image\\":[\\"https://github-images.wenzhihuai.com/images/20171121023427.png\\",\\"https://github-images.wenzhihuai.com/images/20171121024358.png\\",\\"https://github-images.wenzhihuai.com/images/20171122030819.png\\",\\"https://github-images.wenzhihuai.com/images/20171122034349.png\\",\\"https://github-images.wenzhihuai.com/images/20171121023342.png\\",\\"https://github-images.wenzhihuai.com/images/20171125122526.png\\",\\"https://github-images.wenzhihuai.com/images/20171121022116.png\\",\\"https://github-images.wenzhihuai.com/images/20171127034920.png\\",\\"https://github-images.wenzhihuai.com/images/20171125103716.png\\",\\"https://github-images.wenzhihuai.com/images/20171125105127.png\\",\\"https://github-images.wenzhihuai.com/images/20171127033202.png\\",\\"https://github-images.wenzhihuai.com/images/20171127033945.png\\"],\\"dateModified\\":\\"2024-05-11T18:24:35.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[{"level":2,"title":"wowslider","slug":"wowslider","link":"#wowslider","children":[]},{"level":2,"title":"畅言","slug":"畅言","link":"#畅言","children":[]},{"level":2,"title":"Editor.md","slug":"editor-md","link":"#editor-md","children":[]},{"level":2,"title":"图表","slug":"图表","link":"#图表","children":[]},{"level":2,"title":"百度分享","slug":"百度分享","link":"#百度分享","children":[]},{"level":2,"title":"瀑布流","slug":"瀑布流","link":"#瀑布流","children":[]},{"level":2,"title":"天气插件","slug":"天气插件","link":"#天气插件","children":[]},{"level":2,"title":"标签云","slug":"标签云","link":"#标签云","children":[]}],"git":{"createdTime":1579574285000,"updatedTime":1715451875000,"contributors":[{"name":"zhihuaiwen","email":"zhihuaiwen@tencent.com","commits":1}]},"readingTime":{"minutes":7.24,"words":2173},"filePathRelative":"interesting/个人网站/7.那些牛逼的插件.md","localizedDate":"2020年1月21日","excerpt":"\\n欢迎访问我的网站http://www.wenzhihuai.com/ 。感谢,如果可以,希望能在GitHub上给个star,GitHub地址https://github.com/Zephery/newblog 。
\\n建站的一开始,我也想自己全部实现,各种布局,各种炫丽的效果,想做点能让大家佩服的UI出来,但是,事实上,自己作为专注Java的程序员,前端的东西一碰脑子就有“我又不是前端,浪费时间在这合适么?”这种想法,捣鼓来捣鼓去,做出的东西实在是没法看,我就觉得,如果自己的“产品”连自己都看不下去了,那还好意思给别人看?特别是留言板那块,初版的页面简直low的要死。所以,还是踏踏实实的“站在巨人的肩膀上”吧,改用别人的插件。但不要纯粹的使用别人的博客模板了,如hexo,wordpress这些,就算是自己拼凑过来的也比这些强。下面是本博客中所用到的插件,给大家介绍介绍,共同学习学习。
\\n本站主要用到的插件有:
\\n1.wowslider
\\n2.畅言
\\n3.Editor.md
\\n4.highchart、echart
\\n5.百度分享
\\n6.waterfall.js
\\n7.心知天气
\\n8.标签云
HTTP,全称超文本传输协议(HTTP,HyperText Transfer Protocol),是一个客户端和服务器端请求和应答的标准(TCP),互联网上应用最为广泛的一种网络协议。客户端是终端用户,服务器端是网站。通过使用Web浏览器、网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口(默认端口为80)的HTTP请求。
HTTPS,即加密后的HTTP。HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全。HTTPS都是用的TLS协议,但是由于SSL出现的时间比较早,并且依旧被现在浏览器所支持,因此SSL依然是HTTPS的代名词,但无论是TLS还是SSL都是上个世纪的事情,SSL最后一个版本是3.0,今后TLS将会继承SSL优良血统继续为我们进行加密服务。目前TLS的版本是1.2,定义在RFC 5246中,暂时还没有被广泛的使用。
HTTP2.0,下一代的HTTP协议。相比于HTTP1.x,大幅度的提升了web性能,进一步减少了网络延时和拥塞。
各自的RFC相关文档自己去搜吧,https://www.rfc-editor.org/。
为了了解HTTP,有必要先理解一下TCP/IP。目前,存在两种划分模型的方法,OSI七层模型和TCP/IP模型,具体的区别不在阐述。HTTP是建立在TCP协议之上,所以HTTP协议的瓶颈及其优化技巧都是基于TCP协议本身的特性,例如tcp建立连接的3次握手和断开连接的4次挥手以及每次建立连接带来的RTT延迟时间。
TCP三次握手四次挥手的原理,由于篇幅关系,具体请看TCP协议的三次握手和四次挥手,
超文本传输协议(HyperText Transfer Protocol) 是伴随着计算机网络和浏览器而诞生,在浏览器出现之前,人们是怎么使用网络的?,不管怎么说,那个时代对于现在的我们,有点难以想象。。。之后,网景发布了Netscape Navigator浏览器,才慢慢打开了互联网的幕布。如果根据OSI来划分的话,HTML属于表示层,而HTTP属于应用层。HTTP发展至今,经过了HTTP0.9、HTTP1.0、HTTP1.1、HTTP2.0的时代,虽然2.0很久之前就正式提出标准,大多浏览器也支持了,但是网络支持HTTP2.0的却很少。
报文,是网络中交换和传输的基本单元,即一次性发送的数据块。HTTP的报文是由一行一行组成的,纯文本,而且是明文,即:如果能监听你的网络,那么你发送的所有账号密码都是可以看见的,为了保障数据隐秘性,HTTPS随之而生。
为了形象点,我们把报文标准和实际的结合起来看。
下面是实际报文,以访问自己的网站(http://www.wenzhihuai.com)中的一个链接为例。
请求行由方法字段、URL 字段 和HTTP 协议版本字段 3 个部分组成,他们之间使用空格隔开。常用的 HTTP 请求方法有 GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT,这里我们使用的是GET方法,访问的是/biaoqianyun.do,协议使用的是HTTP/1.1。
GET:当客户端要从服务器中读取某个资源时,使用GET 方法。如果需要加传参数的话,需要在URL之后加个"?",然后把参数名字和值用=连接起来,传递参数长度受限制,通常IE8的为4076,Chrome的为7675。例如,/index.jsp?id=100&op=bind。
POST:当客户端给服务器提供信息较多时可以使用POST 方法,POST 方法向服务器提交数据,比如完成表单数据的提交,将数据提交给服务器处理。GET 一般用于获取/查询资源信息,POST 会附带用户数据,一般用于更新资源信息。POST 方法将请求参数封装在HTTP 请求数据中,以名称/值的形式出现,可以传输大量数据;
请求头部由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。请求头部通知服务器有关于客户端请求的信息,典型的请求头有:
User-Agent:产生请求的浏览器类型;
Accept:客户端可识别的响应内容类型列表;星号 “ * ” 用于按范围将类型分组,用 “ / ” 指示可接受全部类型,用“ type/* ”指示可接受 type 类型的所有子类型;
Accept-Language:客户端可接受的自然语言;
Accept-Encoding:客户端可接受的编码压缩格式;
Accept-Charset:可接受的应答的字符集;
Host:请求的主机名,允许多个域名同处一个IP 地址,即虚拟主机;
connection:连接方式(close 或 keepalive),如果是close的话就需要进行TCP四次挥手关闭连接,如果是keepalive,表明还能继续使用,这是HTTP1.1对1.0的新增,加快了网络传输,默认是keepalive;
Cookie:存储于客户端扩展字段,向同一域名的服务端发送属于该域的cookie;
最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头;
请求包体不在 GET 方法中使用,而是在POST 方法中使用。POST 方法适用于需要客户填写表单的场合。与请求包体相关的最常使用的是包体类型 Content-Type 和包体长度 Content-Length;
同样,先粘贴报文标准。
抓包,以访问(http://www.wenzhihuai.com)为例。
状态行由 HTTP 协议版本字段、状态码和状态码的描述文本 3 个部分组成,他们之间使用空格隔开,描述文本一般不显示;
状态码:由三位数字组成,第一位数字表示响应的类型,常用的状态码有五大类如下所示:
1xx:服务器已接收,但客户端可能仍要继续发送;
2xx:成功;
3xx:重定向;
4xx:请求非法,或者请求不可达;
5xx:服务器内部错误;
Location:Location响应报头域用于重定向接受者到一个新的位置。例如:客户端所请求的页面已不存在原先的位置,为了让客户端重定向到这个页面新的位置,服务器端可以发回Location响应报头后使用重定向语句,让客户端去访问新的域名所对应的服务器上的资源;
Server:Server 响应报头域包含了服务器用来处理请求的软件信息及其版本。它和 User-Agent 请求报头域是相对应的,前者发送服务器端软件的信息,后者发送客户端软件(浏览器)和操作系统的信息。
Vary:指示不可缓存的请求头列表;
Connection:连接方式,这个跟rquest的类似。
空行:最后一个响应头部之后是一个空行,发送回车符和换行符,通知服务器以下不再有响应头部。
响应包体:服务器返回给客户端的文本信息;
HTTP的主要特点主要能概括如下:
即,当客户端访问完一次服务器再次访问的时候,服务器是无法知道这个客户端之前是否已经访问过了。优点是不需要先前的信息,能够更快的应答,缺点是每次连接传送的数据量增大。这种做法不利于信息的交互,随后,Cookie和Session就应运而生,至于它俩有什么区别,可以看看COOKIE和SESSION有什么区别?
。
HTTP1.1 使用持久连接keepalive,所谓持久连接,就是服务器在发送响应后仍然在一段时间内保持这条连接,允许在同一个连接中存在多次数据请求和响应,即在持久连接情况下,服务器在发送完响应后并不关闭TCP连接,客户端可以通过这个连接继续请求其他对象。
支持客户/服务器模式、简单快速(请求方法简单Get和POST)、灵活(数据对象任意)
影响HTTP请求的因素:
(2) DNS查询
浏览器建立连接是需要知道服务器的IP的,DNS用来将域名解析为IP地址,这个可以通过刷新DNS缓存来加快速度。
(3) 建立连接
由之前第一章的就可以看出,HTTP是基于TCP协议的,即使网络、浏览器再快也要进行TCP的三次握手,在高延迟的场景下影响比较明显,慢启动则对文件请求影响较大。
BTW:明文传输有多危险,可以去试试,下面是某个政府网站,采用wireshark抓包,身份证、电话号码、住址什么的全暴露出来,所以,,,只要在路由器做点小动作,你的信息是全部能拿得到的,毕竟政府。
由于涉及的隐私太多,打了马赛克
由于HTTP报文的不安全性,网景在1994年就创建了HTTPS,并用在浏览器中。最初HTTPS是和SSL一起使用,然后演化为TLS。SSL/TLS在OSI模型中都是表示层的协议。SSL使 用40 位关键字作为RC4流加密算法,这对于商业信息的加密是合适的。
SSL(Secure Sockets Layer),简称安全套接入层,最初由上世纪90年代由网景公司设计。开启 SSL 会增加内存、CPU、网络带宽的开销,后二者跟你使用的 cipher suite 密切相关,其中参数很多,很难一概而论。开启 SSL 的前提是你的 cert 和 key 必须放在 TCP endpoint,你是否信得过那台设备。
TLS(Transport Layer Security),简称安全传输层协议,该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。较低的层为 TLS 记录协议,位于某个可靠的传输协议(例如 TCP)上面,与具体的应用无关,所以,一般把TLS协议归为传输层安全协议。
由于本人在加密算法上面知识匮乏,就不误人子弟了,有兴趣可以看看百度百科里的资料,SSL,TLS
2012年google提出了SPDY的方案,大家才开始从正面看待和解决老版本HTTP协议本身的问题,SPDY可以说是综合了HTTPS和HTTP两者有点于一体的传输协议,主要解决:
降低延迟,针对HTTP高延迟的问题,SPDY优雅的采取了多路复用(multiplexing)。多路复用通过多个请求stream共享一个tcp连接的方式,解决了HOL blocking的问题,降低了延迟同时提高了带宽的利用率。
请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接共享的基础之上有可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,这样重要的请求就会优先得到响应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文件等加载,这样可以保证用户能第一时间看到网页内容。
header压缩。前面提到HTTP1.x的header很多时候都是重复多余的。选择合适的压缩算法可以减小包的大小和数量。
基于HTTPS的加密协议传输,大大提高了传输数据的可靠性。
服务端推送(server push),采用了SPDY的网页,例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。SPDY构成图。
跟之前的报文分析一样,我们使用wireshark来抓包分析,以在百度上搜索点东西为例。
192.168.1.103为本地电脑的ip地址,14.215.177.39为百度服务器地址。下面是步骤:
当我们追踪流的数据的时候,可以看到,基本上都是乱码,经过加密,数据是看不到,如果需要在wireshark上看到,则需要在wireshark中配置ssl。
现今,感觉只要和商业利益有关的,就不得不涉及到加密这类东西。淘宝、京东、唯品会这些电商可谓是最早推行全站https的,这类电商是离用户金钱最近的企业。截止今年底,基本所有商业网站也基本实现了HTTPS。。。。至于小站点,比如个人网站,玩玩还是可以的。如果一个网站需要由HTTP全部变为HTTPS,那么需要关注下面几点:
HTTP2.0,相较于HTTP1.x,大幅度的提升了web性能。在与HTTP/1.1完全语义兼容的基础上,进一步减少了网络延迟和传输的安全性。HTTP2.0可以说是SPDY的升级版(基于SPDY设计的),但是依然存在一些不同点:HTTP2.0支持明文传输,而SPDY强制使用HTTPS;HTTP2.0消息头的压缩算法采用HPACK,而非SPDY采用的DEFLATE。
HTTP 2.0在2013年8月进行首次合作共事性测试。在开放互联网上HTTP 2.0将只用于https://网址,而 http://网址将继续使用HTTP/1,目的是在开放互联网上增加使用加密技术,以提供强有力的保护去遏制主动攻击。HTTP 2.0是在SPDY(An experimental protocol for a faster web, The Chromium Projects)基础上形成的下一代互联网通信协议。HTTP/2 的目的是通过支持请求与响应的多路复用来较少延迟,通过压缩HTTPS首部字段将协议开销降低,同时增加请求优先级和服务器端推送的支持。
相较于HTTP1.1,HTTP2.0的主要优点有采用二进制帧封装,传输变成多路复用,流量控制算法优化,服务器端推送,首部压缩,优先级等特点。
HTTP1.x的解析是基于文本的,基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多。而HTTP/2会将所有传输的信息分割为更小的消息和帧,然后采用二进制的格式进行编码,HTTP1.x的头部信息会被封装到HEADER frame,而相应的Request Body则封装到DATA frame里面。不改动HTTP的语义,使用二进制编码,实现方便且健壮。
所有的请求都是通过一个 TCP 连接并发完成。HTTP/1.x 虽然通过 pipeline 也能并发请求,但是多个请求之间的响应会被阻塞的,所以 pipeline 至今也没有被普及应用,而 HTTP/2 做到了真正的并发请求。同时,流还支持优先级和流量控制。当流并发时,就会涉及到流的优先级和依赖。即:HTTP2.0对于同一域名下所有请求都是基于流的,不管对于同一域名访问多少文件,也只建立一路连接。优先级高的流会被优先发送。图片请求的优先级要低于 CSS 和 SCRIPT,这个设计可以确保重要的东西可以被优先加载完。
TCP协议通过sliding window的算法来做流量控制。发送方有个sending window,接收方有receive window。http2.0的flow control是类似receive window的做法,数据的接收方通过告知对方自己的flow window大小表明自己还能接收多少数据。只有Data类型的frame才有flow control的功能。对于flow control,如果接收方在flow window为零的情况下依然更多的frame,则会返回block类型的frame,这张场景一般表明http2.0的部署出了问题。
服务器端的推送,就是服务器可以对一个客户端请求发送多个响应。除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确地请求。当浏览器请求一个html,服务器其实大概知道你是接下来要请求资源了,而不需要等待浏览器得到html后解析页面再发送资源请求。
HTTP 2.0 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;通信期间几乎不会改变的通用键-值对(用户代理、可接受的媒体类型,等等)只 需发送一次。事实上,如果请求中不包含首部(例如对同一资源的轮询请求),那么 首部开销就是零字节。此时所有首部都自动使用之前请求发送的首部。
如果首部发生变化了,那么只需要发送变化了数据在Headers帧里面,新增或修改的首部帧会被追加到“首部表”。首部表在 HTTP 2.0 的连接存续期内始终存在,由客户端和服务器共同渐进地更新 。本质上,当然是为了减少请求啦,通过多个js或css合并成一个文件,多张小图片拼合成Sprite图,可以让多个HTTP请求减少为一个,减少额外的协议开销,而提升性能。当然,一个HTTP的请求的body太大也是不合理的,有个度。文件的合并也会牺牲模块化和缓存粒度,可以把“稳定”的代码or 小图 合并为一个文件or一张Sprite,让其充分地缓存起来,从而区分开迭代快的文件。
以访问https://http2.akamai.com/demo为例。
访问https://http2.akamai.com/demo,谷歌浏览器的报文没有显示出协议,此处使用火狐浏览器。
响应头部分如下。
请求头如下。
采用淘宝网站为例,淘宝目前采用主站使用HTTP1.1,资源使用HTTP2.0,少些使用SPDY协议。目前也是业界比较流行的做法。
欢迎访问我的个人网站。https://wenzhihuai.com
',103)]))}const o=t(n,[["render",h],["__file","IP、HTTP、HTTPS、HTTP2.0.html.vue"]]),T=JSON.parse('{"path":"/java/%E7%BD%91%E7%BB%9C/IP%E3%80%81HTTP%E3%80%81HTTPS%E3%80%81HTTP2.0.html","title":"TCP/IP、HTTP、HTTPS、HTTP2.0","lang":"zh-CN","frontmatter":{"description":"TCP/IP、HTTP、HTTPS、HTTP2.0 HTTP,全称超文本传输协议(HTTP,HyperText Transfer Protocol),是一个客户端和服务器端请求和应答的标准(TCP),互联网上应用最为广泛的一种网络协议。客户端是终端用户,服务器端是网站。通过使用Web浏览器、网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口(默认...","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/java/%E7%BD%91%E7%BB%9C/IP%E3%80%81HTTP%E3%80%81HTTPS%E3%80%81HTTP2.0.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"TCP/IP、HTTP、HTTPS、HTTP2.0"}],["meta",{"property":"og:description","content":"TCP/IP、HTTP、HTTPS、HTTP2.0 HTTP,全称超文本传输协议(HTTP,HyperText Transfer Protocol),是一个客户端和服务器端请求和应答的标准(TCP),互联网上应用最为广泛的一种网络协议。客户端是终端用户,服务器端是网站。通过使用Web浏览器、网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口(默认..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://github-images.wenzhihuai.com/images/20171224034237.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-02-14T17:59:53.000Z"}],["meta",{"property":"article:modified_time","content":"2024-02-14T17:59:53.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"TCP/IP、HTTP、HTTPS、HTTP2.0\\",\\"image\\":[\\"https://github-images.wenzhihuai.com/images/20171224034237.png\\",\\"https://github-images.wenzhihuai.com/images/20171221101751.png\\",\\"https://github-images.wenzhihuai.com/images/20171221111615.png\\",\\"https://github-images.wenzhihuai.com/images/20171221043103.png\\",\\"https://github-images.wenzhihuai.com/images/20171224035755.png\\",\\"https://github-images.wenzhihuai.com/images/20171224035850.png\\",\\"https://github-images.wenzhihuai.com/images/20171226081508.png\\",\\"https://github-images.wenzhihuai.com/images/20171224044825.png\\",\\"https://github-images.wenzhihuai.com/images/20171226042016.png\\",\\"https://github-images.wenzhihuai.com/images/20171226044521.png\\",\\"https://github-images.wenzhihuai.com/images/20171226044137.png\\",\\"https://github-images.wenzhihuai.com/images/20171226045845.png\\",\\"https://github-images.wenzhihuai.com/images/20171226103043.png\\",\\"https://github-images.wenzhihuai.com/images/20171226105514.png\\",\\"https://github-images.wenzhihuai.com/images/20171226051221.png\\",\\"https://github-images.wenzhihuai.com/images/20171226071845.png\\",\\"https://github-images.wenzhihuai.com/images/20171226075106.png\\",\\"https://github-images.wenzhihuai.com/images/20171226065216.png\\"],\\"dateModified\\":\\"2024-02-14T17:59:53.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[{"level":2,"title":"一、TCP/IP","slug":"一、tcp-ip","link":"#一、tcp-ip","children":[]},{"level":2,"title":"二、HTTP","slug":"二、http","link":"#二、http","children":[{"level":3,"title":"2.1 HTTP报文分析","slug":"_2-1-http报文分析","link":"#_2-1-http报文分析","children":[]},{"level":3,"title":"2.2 HTTP特性","slug":"_2-2-http特性","link":"#_2-2-http特性","children":[]},{"level":3,"title":"2.3 影响HTTP的因素","slug":"_2-3-影响http的因素","link":"#_2-3-影响http的因素","children":[]}]},{"level":2,"title":"三、HTTPS","slug":"三、https","link":"#三、https","children":[{"level":3,"title":"3.1 SSL/TLS","slug":"_3-1-ssl-tls","link":"#_3-1-ssl-tls","children":[]},{"level":3,"title":"3.2 SPDY","slug":"_3-2-spdy","link":"#_3-2-spdy","children":[]},{"level":3,"title":"3.3 HTTPS报文分析","slug":"_3-3-https报文分析","link":"#_3-3-https报文分析","children":[]},{"level":3,"title":"3.4 HTTPS全站化","slug":"_3-4-https全站化","link":"#_3-4-https全站化","children":[]}]},{"level":2,"title":"四、HTTP2.0","slug":"四、http2-0","link":"#四、http2-0","children":[{"level":3,"title":"4.1 历史","slug":"_4-1-历史","link":"#_4-1-历史","children":[]},{"level":3,"title":"4.2 HTTP2.0新特性","slug":"_4-2-http2-0新特性","link":"#_4-2-http2-0新特性","children":[]},{"level":3,"title":"4.3 HTTP1.1与HTTP2.0的对比","slug":"_4-3-http1-1与http2-0的对比","link":"#_4-3-http1-1与http2-0的对比","children":[]},{"level":3,"title":"4.4 报文","slug":"_4-4-报文","link":"#_4-4-报文","children":[]}]},{"level":2,"title":"参考","slug":"参考","link":"#参考","children":[]}],"git":{"createdTime":1707203159000,"updatedTime":1707933593000,"contributors":[{"name":"zhihuaiwen","email":"zhihuaiwen@tencent.com","commits":3}]},"readingTime":{"minutes":19.51,"words":5852},"filePathRelative":"java/网络/IP、HTTP、HTTPS、HTTP2.0.md","localizedDate":"2024年2月6日","excerpt":"\\nHTTP,全称超文本传输协议(HTTP,HyperText Transfer Protocol),是一个客户端和服务器端请求和应答的标准(TCP),互联网上应用最为广泛的一种网络协议。客户端是终端用户,服务器端是网站。通过使用Web浏览器、网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口(默认端口为80)的HTTP请求。
","autoDesc":true}');export{o as comp,T as data}; diff --git "a/assets/JVM\350\260\203\344\274\230\345\217\202\346\225\260.html-BtZgPFK4.js" "b/assets/JVM\350\260\203\344\274\230\345\217\202\346\225\260.html-BtZgPFK4.js" new file mode 100644 index 00000000..8bfbfe3e --- /dev/null +++ "b/assets/JVM\350\260\203\344\274\230\345\217\202\346\225\260.html-BtZgPFK4.js" @@ -0,0 +1,24 @@ +import{_ as i,c as a,d as e,o as n}from"./app-ftEjETWs.js";const l={};function t(r,s){return n(),a("div",null,s[0]||(s[0]=[e(`JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统 下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k: 设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内 存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。
如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
典型配置:
java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
典型配置:
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
JVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:
-XX:+PrintGC
输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails
输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
-XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可与上面两个混合使用
输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
-XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用
输出形式:Application time: 0.5291524 seconds
-XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用
输出形式:Total time for which application threads were stopped: 0.0468229 seconds
-XX:PrintHeapAtGC:打印GC前后的详细堆栈信息
输出形式:
34.702: [GC {Heap before gc invocations=7:
+ def new generation total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
+eden space 49152K, 99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)
+from space 6144K, 55% used [0x221d0000, 0x22527e10, 0x227d0000)
+ to space 6144K, 0% used [0x21bd0000, 0x21bd0000, 0x221d0000)
+ tenured generation total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)
+the space 69632K, 3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)
+ compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
+ the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
+ ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
+ rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
+34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:
+ def new generation total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)
+eden space 49152K, 0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)
+ from space 6144K, 55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)
+ to space 6144K, 0% used [0x221d0000, 0x221d0000, 0x227d0000)
+ tenured generation total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)
+the space 69632K, 4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)
+ compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
+ the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
+ ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
+ rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
+}
+, 0.0757599 secs]
-Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。
常见配置汇总
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
并发垃圾收集信息
持久代并发收集次数
传统GC信息
花在年轻代和年老代回收上的时间比例
减少年轻代和年老代花费的时间,一般会提高应用的效率
吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他 会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么 并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统 下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
","autoDesc":true}');export{d as comp,p as data}; diff --git "a/assets/Java\345\206\205\345\255\230\346\250\241\345\236\213.html-Bis3Hglq.js" "b/assets/Java\345\206\205\345\255\230\346\250\241\345\236\213.html-Bis3Hglq.js" new file mode 100644 index 00000000..afadc5fc --- /dev/null +++ "b/assets/Java\345\206\205\345\255\230\346\250\241\345\236\213.html-Bis3Hglq.js" @@ -0,0 +1,150 @@ +import{_ as s,c as a,d as e,o as n}from"./app-ftEjETWs.js";const l={};function t(h,i){return n(),a("div",null,i[0]||(i[0]=[e(`本文转载自深入理解JVM-内存模型(jmm)和GC
了解jvm内存模型前,了解下cpu和计算机内存的交互情况。【因为Java虚拟机内存模型定义的访问操作与计算机十分相似】
有篇很棒的文章,从cpu讲到内存模型:什么是java内存模型
在计算机中,cpu和内存的交互最为频繁,相比内存,磁盘读写太慢,内存相当于高速的缓冲区。
但是随着cpu的发展,内存的读写速度也远远赶不上cpu。因此cpu厂商在每颗cpu上加上高速缓存,用于缓解这种情况。现在cpu和内存的交互大致如下。
cpu上加入了高速缓存这样做解决了处理器和内存的矛盾(一快一慢),但是引来的新的问题 - 缓存一致性
在多核cpu中,每个处理器都有各自的高速缓存(L1,L2,L3),而主内存确只有一个 。
以我的pc为例,因为cpu成本高,缓存区一般也很小。
CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找,每个cpu有且只有一套自己的缓存。
如何保证多个处理器运算涉及到同一个内存区域时,多线程场景下会存在缓存一致性问题,那么运行时保证数据一致性?
为了解决这个问题,各个处理器需遵循一些协议保证一致性。【如MSI,MESI啥啥的协议。。】
大概如下
在CPU层面,内存屏障提供了个充分必要条件
CPU中,每个CPU又有多级缓存【上图统一定义为高速缓存】,一般分为L1,L2,L3,因为这些缓存的出现,提高了数据访问性能,避免每次都向内存索取,但是弊端也很明显,不能实时的和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同。
Load Barrier
和 Store Barrier
即读屏障和写屏障。【内存屏障是硬件层的】由于现代操作系统都是多处理器操作系统,每个处理器都会有自己的缓存,可能存再不同处理器缓存不一致的问题,而且由于操作系统可能存在重排序,导致读取到错误的数据,因此,操作系统提供了一些内存屏障以解决这种问题.
+简单来说:
+1.在不同CPU执行的不同线程对同一个变量的缓存值不同,为了解决这个问题。
+2.用volatile可以解决上面的问题,不同硬件对内存屏障的实现方式不一样。java屏蔽掉这些差异,通过jvm生成内存屏障的指令。
+对于读屏障:在指令前插入读屏障,可以让高速缓存中的数据失效,强制从主内存取。
cpu执行指令可能是无序的,它有两个比较重要的作用
+1.阻止屏障两侧指令重排序
+2.强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
当我们声明某个变量为volatile修饰时,这个变量就有了线程可见性,volatile通过在读写操作前后添加内存屏障。
用代码可以这么理解
//相当于读写时加锁,保证及时可见性,并发时不被随意修改。
+public class SynchronizedInteger {
+ private long value;
+
+ public synchronized int get() {
+ return value;
+ }
+
+ public synchronized void set(long value) {
+ this.value = value;
+ }
+}
volatile型变量拥有如下特性
可见性,对于一个该变量的读,一定能看到读之前最后的写入。
+防止指令重排序,执行代码时,为了提高执行效率,会在不影响最后结果的前提下对指令进行重新排序,使用volatile可以防止,比如单例模式双重校验锁的创建中有使用到,如(https://www.jianshu.com/p/b30a4d568be4)
+
+注意的是volatile不具有原子性,如volatile++这样的复合操作,这里感谢大家的指正。
至于volatile底层是怎么实现保证不同线程可见性的,这里涉及到的就是硬件上的,被volatile修饰的变量在进行写操作时,会生成一个特殊的汇编指令,该指令会触发mesi协议,会存在一个总线嗅探机制的东西,简单来说就是这个cpu会不停检测总线中该变量的变化,如果该变量一旦变化了,由于这个嗅探机制,其它cpu会立马将该变量的cpu缓存数据清空掉,重新的去从主内存拿到这个数据。简单画了个图。
前提:本文讲的基本都是以Sun HotSpot虚拟机为基础的,Oracle收购了Sun后目前得到了两个【Sun的HotSpot和JRockit(以后可能合并这两个),还有一个是IBM的IBMJVM】
之所以扯了那么多计算机内存模型,是因为java内存模型的设定符合了计算机的规范。
Java程序内存的分配是在JVM虚拟机内存分配机制下完成。
Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
简要言之,jmm是jvm的一种规范,定义了jvm的内存模型。它屏蔽了各种硬件和操作系统的访问差异,不像c那样直接访问硬件内存,相对安全很多,它的主要目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。可以保证并发编程场景中的原子性、可见性和有序性。
从下面这张图可以看出来,Java数据区域分为五大数据区域。这些区域各有各的用途,创建及销毁时间。
其中方法区和堆是所有线程共享的,栈,本地方法栈和程序虚拟机则为线程私有的。
根据java虚拟机规范,java虚拟机管理的内存将分为下面五大区域。
程序计数器是一块很小的内存空间,它是线程私有的,可以认作为当前线程的行号指示器。
为什么需要程序计数器
我们知道对于一个处理器(如果是多核cpu那就是一核),在一个确定的时刻都只会执行一条线程中的指令,一条线程中有多个指令,为了线程切换可以恢复到正确执行位置,每个线程都需有独立的一个程序计数器,不同线程之间的程序计数器互不影响,独立存储。
注意:如果线程执行的是个java方法,那么计数器记录虚拟机字节码指令的地址。如果为native【底层方法】,那么计数器为空。这块内存区域是虚拟机规范中唯一没有OutOfMemoryError的区域。
同计数器也为线程私有,生命周期与相同,就是我们平时说的栈,栈描述的是Java方法执行的内存模型。
每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。【栈先进后出,下图栈1先进最后出来】
对于栈帧的解释参考 Java虚拟机运行时栈帧结构
栈帧: 是用来存储数据和部分过程结果的数据结构。
+栈帧的位置: 内存 -> 运行时数据区 -> 某个线程对应的虚拟机栈 -> here[在这里]
+栈帧大小确定时间: 编译期确定,不受运行期数据影响。
通常有人将java内存区分为栈和堆,实际上java内存比这复杂,这么区分可能是因为我们最关注,与对象内存分配关系最密切的是这两个。
平时说的栈一般指局部变量表部分。
局部变量表:一片连续的内存空间,用来存放方法参数,以及方法内定义的局部变量,存放着编译期间已知的数据类型(八大基本类型和对象引用(reference类型),returnAddress类型。它的最小的局部变量表空间单位为Slot,虚拟机没有指明Slot的大小,但在jvm中,long和double类型数据明确规定为64位,这两个类型占2个Slot,其它基本类型固定占用1个Slot。
reference类型:与基本类型不同的是它不等同本身,即使是String,内部也是char数组组成,它可能是指向一个对象起始位置指针,也可能指向一个代表对象的句柄或其他与该对象有关的位置。
returnAddress类型:指向一条字节码指令的地址【深入理解Java虚拟机】怎么理解returnAddress
需要注意的是,局部变量表所需要的内存空间在编译期完成分配,当进入一个方法时,这个方法在栈中需要分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表大小。
Java虚拟机栈可能出现两种类型的异常:
- 线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。
- 虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。
本地方法栈是与虚拟机栈发挥的作用十分相似,区别是虚拟机栈执行的是Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务,可能底层调用的c或者c++,我们打开jdk安装目录可以看到也有很多用c编写的文件,可能就是native方法所调用的c代码。
对于大多数应用来说,堆是java虚拟机管理内存最大的一块内存区域,因为堆存放的对象是线程共享的,所以多线程的时候也需要同步机制。因此需要重点了解下。
java虚拟机规范对这块的描述是:所有对象实例及数组都要在堆上分配内存,但随着JIT编译器的发展和逃逸分析技术的成熟,这个说法也不是那么绝对,但是大多数情况都是这样的。
即时编译器:可以把把Java的字节码,包括需要被解释的指令的程序)转换成可以直接发送给处理器的指令的程序)
逃逸分析:通过逃逸分析来决定某些实例或者变量是否要在堆中进行分配,如果开启了逃逸分析,即可将这些变量直接在栈上进行分配,而非堆上进行分配。这些变量的指针可以被全局所引用,或者其其它线程所引用。
注意:它是所有线程共享的,它的目的是存放对象实例。同时它也是GC所管理的主要区域,因此常被称为GC堆,又由于现在收集器常使用分代算法,Java堆中还可以细分为新生代和老年代,再细致点还有Eden(伊甸园)空间之类的不做深究。
根据虚拟机规范,Java堆可以存在物理上不连续的内存空间,就像磁盘空间只要逻辑是连续的即可。它的内存大小可以设为固定大小,也可以扩展。
当前主流的虚拟机如HotPot都能按扩展实现(通过设置 -Xmx和-Xms),如果堆中没有内存内存完成实例分配,而且堆无法扩展将报OOM错误(OutOfMemoryError)
方法区同堆一样,是所有线程共享的内存区域,为了区分堆,又被称为非堆。
用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。
运行时常量池
是方法区的一部分,class文件除了有类的字段、接口、方法等描述信息之外,还有常量池用于存放编译期间生成的各种字面量和符号引用。
在老版jdk,方法区也被称为永久代【因为没有强制要求方法区必须实现垃圾回收,HotSpot虚拟机以永久代来实现方法区,从而JVM的垃圾收集器可以像管理堆区一样管理这部分区域,从而不需要专门为这部分设计垃圾回收机制。不过自从JDK7之后,Hotspot虚拟机便将运行时常量池从永久代移除了。】
jdk1.7开始逐步去永久代。从String.interns()方法可以看出来
+String.interns()
+native方法:作用是如果字符串常量池已经包含一个等于这个String对象的字符串,则返回代表池中的这个字符串的String对象,在jdk1.6及以前常量池分配在永久代中。可通过 -XX:PermSize和-XX:MaxPermSize限制方法区大小。
public class StringIntern {
+ //运行如下代码探究运行时常量池的位置
+ public static void main(String[] args) throws Throwable {
+ //用list保持着引用 防止full gc回收常量池
+ List<String> list = new ArrayList<String>();
+ int i = 0;
+ while(true){
+ list.add(String.valueOf(i++).intern());
+ }
+ }
+}
+//如果在jdk1.6环境下运行 同时限制方法区大小 将报OOM后面跟着PermGen space说明方法区OOM,即常量池在永久代
+//如果是jdk1.7或1.8环境下运行 同时限制堆的大小 将报heap space 即常量池在堆中
这边不用全局的方式,设置main方法的vm参数。
做相关设置,比如说这边设定堆大小。(-Xmx5m -Xms5m -XX:-UseGCOverheadLimit)
这边如果不设置UseGCOverheadLimit将报java.lang.OutOfMemoryError: GC overhead limit exceeded,
+这个错是因为GC占用了多余98%(默认值)的CPU时间却只回收了少于2%(默认值)的堆空间。目的是为了让应用终止,给开发者机会去诊断问题。一般是应用程序在有限的内存上创建了大量的临时对象或者弱引用对象,从而导致该异常。虽然加大内存可以暂时解决这个问题,但是还是强烈建议去优化代码,后者更加有效,也可通过UseGCOverheadLimit避免[不推荐,这里是因为测试用,并不能解决根本问题]
jdk8真正开始废弃永久代,而使用元空间(Metaspace)
java虚拟机对方法区比较宽松,除了跟堆一样可以不存在连续的内存空间,定义空间和可扩展空间,还可以选择不实现垃圾收集。
在HotSpot虚拟机中。对象在内存中存储的布局分为
1.对象头
+2.实例数据
+3.对齐填充
在32位系统下,对象头8字节,64位则是16个字节【未开启压缩指针,开启后12字节】。
markword很像网络协议报文头,划分为多个区间,并且会根据对象的状态复用自己的存储空间。
+为什么这么做:省空间,对象需要存储的数据很多,32bit/64bit是不够的,它被设计成非固定的数据结构以便在极小的空间存储更多的信息,
假设当前为32bit,在对象未被锁定情况下。25bit为存储对象的哈希码、4bit用于存储分代年龄,2bit用于存储锁标志位,1bit固定为0。
不同状态下存放数据
这其中锁标识位需要特别关注下。锁标志位与是否为偏向锁对应到唯一的锁状态。
锁的状态分为四种无锁状态
、偏向锁
、轻量级锁
和重量级锁
不同状态时对象头的区间含义,如图所示。
HotSpot底层通过markOop实现Mark Word,具体实现位于markOop.hpp
文件。
markOop中提供了大量方法用于查看当前对象头的状态,以及更新对象头的数据,为synchronized锁的实现提供了基础。[比如说我们知道synchronized锁的是对象而不是代码,而锁的状态保存在对象头中,进而实现锁住对象]。
关于对象头和锁之间的转换,网上大神总结
存放对象程序中各种类型的字段类型,不管是从父类中继承下来的还是在子类中定义的。
+分配策略:相同宽度的字段总是放在一起,比如double和long
这部分没有特殊的含义,仅仅起到占位符的作用满足JVM要求。
由于HotSpot规定对象的大小必须是8的整数倍,对象头刚好是整数倍,如果实例数据不是的话,就需要占位符对齐填充。
java程序需要通过引用(ref)数据来操作堆上面的对象,那么如何通过引用定位、访问到对象的具体位置。
对象的访问方式由虚拟机决定,java虚拟机提供两种主流的方式
+1.句柄访问对象
+2.直接指针访问对象。(Sun HotSpot使用这种方式)
简单来说就是java堆划出一块内存作为句柄池,引用中存储对象的句柄地址,句柄中包含对象实例数据、类型数据的地址信息。
优点:引用中存储的是稳定的句柄地址,在对象被移动【垃圾收集时移动对象是常态】只需改变句柄中实例数据的指针,不需要改动引用【ref】本身。
与句柄访问不同的是,ref中直接存储的就是对象的实例数据,但是类型数据跟句柄访问方式一样。
优点:优势很明显,就是速度快,相比于句柄访问少了一次指针定位的开销时间。【可能是出于Java中对象的访问时十分频繁的,平时我们常用的JVM HotSpot采用此种方式】
两种内存溢出异常[注意内存溢出是error级别的]
+1.StackOverFlowError:当请求的栈深度大于虚拟机所允许的最大深度
+2.OutOfMemoryError:虚拟机在扩展栈时无法申请到足够的内存空间[一般都能设置扩大]
java -verbose:class -version 可以查看刚开始加载的类,可以发现这两个类并不是异常出现的时候才去加载,而是jvm启动的时候就已经加载。这么做的原因是在vm启动过程中我们把类加载起来,并创建几个没有堆栈的对象缓存起来,只需要设置下不同的提示信息即可,当需要抛出特定类型的OutOfMemoryError异常的时候,就直接拿出缓存里的这几个对象就可以了。
比如说OutOfMemoryError对象,jvm预留出4个对象【固定常量】,这就为什么最多出现4次有堆栈的OutOfMemoryError异常及大部分情况下都将看到没有堆栈的OutOfMemoryError对象的原因。
两个基本的例子
public class MemErrorTest {
+ public static void main(String[] args) {
+ try {
+ List<Object> list = new ArrayList<Object>();
+ for(;;) {
+ list.add(new Object()); //创建对象速度可能高于jvm回收速度
+ }
+ } catch (OutOfMemoryError e) {
+ e.printStackTrace();
+ }
+
+ try {
+ hi();//递归造成StackOverflowError 这边因为每运行一个方法将创建一个栈帧,栈帧创建太多无法继续申请到内存扩展
+ } catch (StackOverflowError e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ public static void hi() {
+ hi();
+ }
+}
GC(Garbage Collection):即垃圾回收器,诞生于1960年MIT的Lisp语言,主要是用来回收,释放垃圾占用的空间。
java GC泛指java的垃圾回收机制,该机制是java与C/C++的主要区别之一,我们在日常写java代码的时候,一般都不需要编写内存回收或者垃圾清理的代码,也不需要像C/C++那样做类似delete/free的操作。
对象的内存分配在java虚拟机的自动内存分配机制下,一般不容易出现内存泄漏问题。但是写代码难免会遇到一些特殊情况,比如OOM神马的。。尽管虚拟机内存的动态分配与内存回收技术很成熟,可万一出现了这样那样的内存溢出问题,那么将难以定位错误的原因所在。
对于本人来说,由于水平有限,而且作为小开发,并没必要深入到GC的底层实现,但至少想要说学会看懂gc及定位一些内存泄漏问题。
从三个角度切入来学习GC
1.哪些内存要回收
2.什么时候回收
3.怎么回收
哪些内存要回收
java内存模型中分为五大区域已经有所了解。我们知道
程序计数器
、虚拟机栈
、本地方法栈
,由线程而生,随线程而灭,其中栈中的栈帧随着方法的进入顺序的执行的入栈和出栈的操作,一个栈帧需要分配多少内存取决于具体的虚拟机实现并且在编译期间即确定下来【忽略JIT编译器做的优化,基本当成编译期间可知】,当方法或线程执行完毕后,内存就随着回收,因此无需关心。而
Java堆
、方法区
则不一样。方法区存放着类加载信息,但是一个接口中多个实现类需要的内存可能不太一样,一个方法中多个分支需要的内存也可能不一样【只有在运行期间才可知道这个方法创建了哪些对象没需要多少内存】,这部分内存的分配和回收都是动态的,gc关注的也正是这部分的内存。
Java堆是GC回收的“重点区域”。堆中基本存放着所有对象实例,gc进行回收前,第一件事就是确认哪些对象存活,哪些死去[即不可能再被引用]
为了高效的回收,jvm将堆分为三个区域
+1.新生代(Young Generation)NewSize和MaxNewSize分别可以控制年轻代的初始大小和最大的大小
+2.老年代(Old Generation)
+3.永久代(Permanent Generation)【1.8以后采用元空间,就不在堆中了】
1.引用计数算法
+早期判断对象是否存活大多都是以这种算法,这种算法判断很简单,简单来说就是给对象添加一个引用计数器,每当对象被引用一次就加1,引用失效时就减1。当为0的时候就判断对象不会再被引用。
+优点:实现简单效率高,被广泛使用与如python何游戏脚本语言上。
+缺点:难以解决循环引用的问题,就是假如两个对象互相引用已经不会再被其它其它引用,导致一直不会为0就无法进行回收。
+
+2.可达性分析算法
+目前主流的商用语言[如java、c#]采用的是可达性分析算法判断对象是否存活。这个算法有效解决了循环利用的弊端。
+它的基本思路是通过一个称为“GC Roots”的对象为起始点,搜索所经过的路径称为引用链,当一个对象到GC Roots没有任何引用跟它连接则证明对象是不可用的。
可作为GC Roots的对象有四种
①虚拟机栈(栈桢中的本地变量表)中的引用的对象。
+②方法区中的类静态属性引用的对象,一般指被static修饰引用的对象,加载类的时候就加载到内存中。
+③方法区中的常量引用的对象,
+④本地方法栈中JNI(native方法)引用的对象
即使可达性算法中不可达的对象,也不是一定要马上被回收,还有可能被抢救一下。网上例子很多,基本上和深入理解JVM一书讲的一样对象的生存还是死亡
要真正宣告对象死亡需经过两个过程。
+1.可达性分析后没有发现引用链
+2.查看对象是否有finalize方法,如果有重写且在方法内完成自救[比如再建立引用],还是可以抢救一下,注意这边一个类的finalize只执行一次,这就会出现一样的代码第一次自救成功第二次失败的情况。[如果类重写finalize且还没调用过,会将这个对象放到一个叫做F-Queue的序列里,这边finalize不承诺一定会执行,这么做是因为如果里面死循环的话可能会时F-Queue队列处于等待,严重会导致内存崩溃,这是我们不希望看到的。]
jvm中,可达性分析算法帮我们解决了哪些对象可以回收的问题,垃圾收集算法则关心怎么回收。
1.标记/清除算法【最基础】
+2.复制算法
+3.标记/整理算法
+jvm采用\`分代收集算法\`对不同区域采用不同的回收算法。
新生代采用复制算法
新生代中因为对象都是"朝生夕死的",【深入理解JVM虚拟机上说98%的对象,不知道是不是这么多,总之就是存活率很低】,适用于复制算法【复制算法比较适合用于存活率低的内存区域】。它优化了标记/清除算法的效率和内存碎片问题,且JVM不以5:5分配内存【由于存活率低,不需要复制保留那么大的区域造成空间上的浪费,因此不需要按1:1【原有区域:保留空间】划分内存区域,而是将内存分为一块Eden空间和From Survivor、To Survivor【保留空间】,三者默认比例为8:1:1,优先使用Eden区,若Eden区满,则将对象复制到第二块内存区上。但是不能保证每次回收都只有不多于10%的对象存货,所以Survivor区不够的话,则会依赖老年代年存进行分配】。
GC开始时,对象只会存于Eden和From Survivor区域,To Survivor【保留空间】为空。
GC进行时,Eden区所有存活的对象都被复制到To Survivor区,而From Survivor区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阈值(默认15是因为对象头中年龄战4bit,新生代每熬过一次垃圾回收,年龄+1),则移到老年代,没有达到则复制到To Survivor。
老年代采用标记/清除算法
或标记/整理算法
由于老年代存活率高,没有额外空间给他做担保,必须使用这两种算法。
GC Roots
被虚拟机用来判断对象是否存活
可作为GC Roos的节点主要是在一些全局引用【如常量或静态属性】、执行上下文【如栈帧中本地变量表】中。那么如何在这么多全局变量和本地变量表找到【枚举】根节点将是个问题。
可达性分析算法需考虑
1.如果方法区几百兆,一个个检查里面的引用,将耗费大量资源。
2.在分析时,需保证这个对象引用关系不再变化,否则结果将不准确。【因此GC进行时需停掉其它所有java执行线程(Sun把这种行为称为‘Stop the World’),即使是号称几乎不会停顿的CMS收集器,枚举根节点时也需停掉线程】
解决办法:实际上当系统停下来后JVM不需要一个个检查引用,而是通过OopMap数据结构【HotSpot的叫法】来标记对象引用。
虚拟机先得知哪些地方存放对象的引用,在类加载完时。HotSpot把对象内什么偏移量什么类型的数据算出来,在jit编译过程中,也会在特定位置记录下栈和寄存器哪些位置是引用,这样GC在扫描时就可以知道这些信息。【目前主流JVM使用准确式GC】
OopMap可以帮助HotSpot快速且准确完成GC Roots枚举以及确定相关信息。但是也存在一个问题,可能导致引用关系变化。
这个时候有个safepoint(安全点)的概念。
HotSpot中GC不是在任意位置都可以进入,而只能在safepoint处进入。 GC时对一个Java线程来说,它要么处在safepoint,要么不在safepoint。
safepoint不能太少,否则GC等待的时间会很久
safepoint不能太多,否则将增加运行GC的负担
安全点主要存放的位置
1:循环的末尾
+2:方法临返回前/调用方法的call指令后
+3:可能抛异常的位置
如果说垃圾回收算法是内存回收的方法论,那么垃圾收集器就是具体实现。jvm会结合针对不同的场景及用户的配置使用不同的收集器。
年轻代收集器
+Serial、ParNew、Parallel Scavenge
+老年代收集器
+Serial Old、Parallel Old、CMS收集器
+特殊收集器
+G1收集器[新型,不在年轻、老年代范畴内]
收集器,连线代表可结合使用
最基本、发展最久的收集器,在jdk3以前是gc收集器的唯一选择,Serial是单线程收集器,Serial收集器只能使用一条线程进行收集工作,在收集的时候必须得停掉其它线程,等待收集工作完成其它线程才可以继续工作。
虽然Serial看起来很坑,需停掉别的线程以完成自己的gc工作,但是也不是完全没用的,比如说Serial在运行在Client模式下优于其它收集器[简单高效,不过一般都是用Server模式,64bit的jvm甚至没Client模式]
优点:对于Client模式下的jvm来说是个好的选择。适用于单核CPU【现在基本都是多核了】
缺点:收集时要暂停其它线程,有点浪费资源,多核下显得。
可以认为是Serial的升级版,因为它支持多线程[GC线程],而且收集算法、Stop The World、回收策略和Serial一样,就是可以有多个GC线程并发运行,它是HotSpot第一个真正意义实现并发的收集器。默认开启线程数和当前cpu数量相同【几核就是几个,超线程cpu的话就不清楚了 - -】,如果cpu核数很多不想用那么多,可以通过*-XX:ParallelGCThreads*来控制垃圾收集线程的数量。
优点:
+1.支持多线程,多核CPU下可以充分的利用CPU资源
+2.运行在Server模式下新生代首选的收集器【重点是因为新生代的这几个收集器只有它和Serial可以配合CMS收集器一起使用】
+
+缺点: 在单核下表现不会比Serial好,由于在单核能利用多核的优势,在线程收集过程中可能会出现频繁上下文切换,导致额外的开销。
采用复制算法的收集器,和ParNew一样支持多线程。
但是该收集器重点关心的是吞吐量【吞吐量 = 代码运行时间 / (代码运行时间 + 垃圾收集时间) 如果代码运行100min垃圾收集1min,则为99%】
对于用户界面,适合使用GC停顿时间短,不然因为卡顿导致交互界面卡顿将很影响用户体验。
对于后台
高吞吐量可以高效率的利用cpu尽快完成程序运算任务,适合后台运算
Parallel Scavenge注重吞吐量,所以也成为"吞吐量优先"收集器。
和新生代的Serial一样为单线程,Serial的老年代版本,不过它采用"标记-整理算法",这个模式主要是给Client模式下的JVM使用。
如果是Server模式有两大用途
1.jdk5前和Parallel Scavenge搭配使用,jdk5前也只有这个老年代收集器可以和它搭配。
2.作为CMS收集器的后备。
支持多线程,Parallel Scavenge的老年版本,jdk6开始出现, 采用"标记-整理算法"【老年代的收集器大都采用此算法】
在jdk6以前,新生代的Parallel Scavenge只能和Serial Old配合使用【根据图,没有这个的话只剩Serial Old,而Parallel Scavenge又不能和CMS配合使用】,而且Serial Old为单线程Server模式下会拖后腿【多核cpu下无法充分利用】,这种结合并不能让应用的吞吐量最大化。
Parallel Old的出现结合Parallel Scavenge,真正的形成“吞吐量优先”的收集器组合。
CMS收集器(Concurrent Mark Sweep)是以一种获取最短回收停顿时间为目标的收集器。【重视响应,可以带来好的用户体验,被sun称为并发低停顿收集器】
启用CMS:-XX:+UseConcMarkSweepGC
正如其名,CMS采用的是"标记-清除"(Mark Sweep)算法,而且是支持并发(Concurrent)的
它的运作分为4个阶段
1.初始标记:标记一下GC Roots能直接关联到的对象,速度很快
+2.并发标记:GC Roots Tarcing过程,即可达性分析
+3.重新标记:为了修正因并发标记期间用户程序运作而产生变动的那一部分对象的标记记录,会有些许停顿,时间上一般 初始标记 < 重新标记 < 并发标记
+4.并发清除
以上初始标记和重新标记需要stw(停掉其它运行java线程)
之所以说CMS的用户体验好,是因为CMS收集器的内存回收工作是可以和用户线程一起并发执行。
总体上CMS是款优秀的收集器,但是它也有些缺点。
1.cms堆cpu特别敏感,cms运行线程和应用程序并发执行需要多核cpu,如果cpu核数多的话可以发挥它并发执行的优势,但是cms默认配置启动的时候垃圾线程数为 (cpu数量+3)/4,它的性能很容易受cpu核数影响,当cpu的数目少的时候比如说为为2核,如果这个时候cpu运算压力比较大,还要分一半给cms运作,这可能会很大程度的影响到计算机性能。
2.cms无法处理浮动垃圾,可能导致Concurrent Mode Failure(并发模式故障)而触发full GC
3.由于cms是采用"标记-清除“算法,因此就会存在垃圾碎片的问题,为了解决这个问题cms提供了 -XX:+UseCMSCompactAtFullCollection选项,这个选项相当于一个开关【默认开启】,用于CMS顶不住要进行full GC时开启内存碎片合并,内存整理的过程是无法并发的,且开启这个选项会影响性能(比如停顿时间变长)
浮动垃圾:由于cms支持运行的时候用户线程也在运行,程序运行的时候会产生新的垃圾,这里产生的垃圾就是浮动垃圾,cms无法当次处理,得等下次才可以。
G1(garbage first:尽可能多收垃圾,避免full gc)收集器是当前最为前沿的收集器之一(1.7以后才开始有),同cms一样也是关注降低延迟,是用于替代cms功能更为强大的新型收集器,因为它解决了cms产生空间碎片等一系列缺陷。
摘自甲骨文:适用于 Java HotSpot VM 的低暂停、服务器风格的分代式垃圾回收器。G1 GC 使用并发和并行阶段实现其目标暂停时间,并保持良好的吞吐量。当 G1 GC 确定有必要进行垃圾回收时,它会先收集存活数据最少的区域(垃圾优先)
g1的特别之处在于它强化了分区,弱化了分代的概念,是区域化、增量式的收集器,它不属于新生代也不属于老年代收集器。
用到的算法为标记-清理、复制算法
jdk1.7,1.8的都是默认关闭的,更高版本的还不知道
+开启选项 -XX:+UseG1GC
+比如在tomcat的catania.sh启动参数加上
g1是区域化的,它将java堆内存划分为若干个大小相同的区域【region】,jvm可以设置每个region的大小(1-32m,大小得看堆内存大小,必须是2的幂),它会根据当前的堆内存分配合理的region大小。
jdk7中计算region的源码,这边博主看了下也看不怎么懂,也翻了下openjdk8的看了下关于region的处理似乎不太一样。。
g1通过并发(并行)标记阶段查找老年代存活对象,通过并行复制压缩存活对象【这样可以省出连续空间供大对象使用】。
g1将一组或多组区域中存活对象以增量并行的方式复制到不同区域进行压缩,从而减少堆碎片,目标是尽可能多回收堆空间【垃圾优先】,且尽可能不超出暂停目标以达到低延迟的目的。
g1提供三种垃圾回收模式 young gc、mixed gc 和 full gc,不像其它的收集器,根据区域而不是分代,新生代老年代的对象它都能回收。
几个重要的默认值,更多的查看官方文档oracle官方g1中文文档
g1是自适应的回收器,提供了若干个默认值,无需修改就可高效运作
+-XX:G1HeapRegionSize=n 设置g1 region大小,不设置的话自己会根据堆大小算,目标是根据最小堆内存划分2048个区域
+-XX:MaxGCPauseMillis=200 最大停顿时间 默认200毫秒
在年轻代
Young space
(包括Eden区和Survivor区)中的垃圾回收称之为 Minor GC,Minor GC只会清理年轻代.
Major GC清理老年代(old GC),但是通常也可以指和Full GC是等价,因为收集老年代的时候往往也会伴随着升级年轻代,收集整个Java堆。所以有人问的时候需问清楚它指的是full GC还是old GC。
full gc是对新生代、老年代、永久代【jdk1.8后没有这个概念了】统一的回收。
【知乎R大的回答:收集整个堆,包括young gen、old gen、perm gen(如果存在的话)、元空间(1.8及以上)等所有部分的模式】
混合GC
收集整个young gen以及部分old gen的GC。只有G1有这个模式
要看得懂并理解GC,需要看懂GC日志。
这边我在idea上试了个小例子,需要在idea配置参数(-XX:+PrintGCDetails)。
public class GCtest {
+ public static void main(String[] args) {
+ for(int i = 0; i < 10000; i++) {
+ List<String> list = new ArrayList<>();
+ list.add("aaaaaaaaaaaaa");
+ }
+ System.gc();
+ }
+}
[GC (System.gc()) [PSYoungGen: 3998K->688K(38400K)] 3998K->696K(125952K), 0.0016551 secs[本次回收时间]] [Times: user=0.01 sys=0.00, real=0.00 secs]
+[Full GC (System.gc()) [PSYoungGen: 688K->0K(38400K)] [ParOldGen: 8K->603K(87552K)] 696K->603K(125952K), [Metaspace: 3210K->3210K(1056768K)], 0.0121034 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
+Heap
+ PSYoungGen[年轻代] total 38400K, used 333K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
+ eden space 33280K, 1% used [0x0000000795580000,0x00000007955d34a8,0x0000000797600000)
+ from space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
+ to space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
+ ParOldGen[老年代] total 87552K, used 603K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
+ object space 87552K, 0% used [0x0000000740000000,0x0000000740096fe8,0x0000000745580000)
+ Metaspace[元空间] used 3217K, capacity 4496K, committed 4864K, reserved 1056768K
+ class space used 352K, capacity 388K, committed 512K, reserved 1048576K
比如sun的gchisto,gcviewer离线分析工具,做个笔记先了解下还没用过,可视化好像很好用的样子。
终端输入jconsole就会出现jdk自带的gui监控工具
可以根据内存使用情况间接了解内存使用和gc情况
jstat命令
比如jstat -gcutil pid查看对应java进程gc情况
s0: 新生代survivor space0简称 就是准备复制的那块 单位为%
+s1:指新生代s1已使用百分比,为0的话说明没有存活对象到这边
+e:新生代eden(伊甸园)区域(%)
+o:老年代(%)
+ygc:新生代 次数
+ygct:minor gc耗时
+fgct:full gc耗时(秒)
+GCT: ygct+fgct 耗时
通过枚举根节点的方式,通过jvm提供的一种oopMap的数据结构,简单来说就是不要再通过去遍历内存里的东西,而是通过OOPMap的数据结构去记录该记录的信息,比如说它可以不用去遍历整个栈,而是扫描栈上面引用的信息并记录下来。
总结:通过OOPMap把栈上代表引用的位置全部记录下来,避免全栈扫描,加快枚举根节点的速度,除此之外还有一个极为重要的作用,可以帮HotSpot实现准确式GC【这边的准确关键就是类型,可以根据给定位置的某块数据知道它的准确类型,HotSpot是通过oopMap外部记录下这些信息,存成映射表一样的东西】。
简单来说,触发的条件就是GC算法区域满了或将满了。
minor GC(young GC):当年轻代中eden区分配满的时候触发[值得一提的是因为young GC后部分存活的对象会已到老年代(比如对象熬过15轮),所以过后old gen的占用量通常会变高]
+
+full GC:
+①手动调用System.gc()方法 [增加了full GC频率,不建议使用而是让jvm自己管理内存,可以设置-XX:+ DisableExplicitGC来禁止RMI调用System.gc]
+②发现perm gen(如果存在永久代的话)需分配空间但已经没有足够空间
+③老年代空间不足,比如说新生代的大对象大数组晋升到老年代就可能导致老年代空间不足。
+④CMS GC时出现Promotion Faield[pf]
+⑤统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间。
+这个比较难理解,这是HotSpot为了避免由于新生代晋升到老年代导致老年代空间不足而触发的FUll GC。
+比如程序第一次触发Minor GC后,有5m的对象晋升到老年代,姑且现在平均算5m,那么下次Minor GC发生时,先判断现在老年代剩余空间大小是否超过5m,如果小于5m,则HotSpot则会触发full GC(这点挺智能的)
Promotion Faield:minor GC时 survivor space放不下[满了或对象太大],对象只能放到老年代,而老年代也放不下会导致这个错误。
+Concurrent Model Failure:cms时特有的错误,因为cms时垃圾清理和用户线程可以是并发执行的,如果在清理的过程中
+可能原因:
+1 cms触发太晚,可以把XX:CMSInitiatingOccupancyFraction调小[比如-XX:CMSInitiatingOccupancyFraction=70 是指设定CMS在对内存占用率达到70%的时候开始GC(因为CMS会有浮动垃圾,所以一般都较早启动GC)]
+2 垃圾产生速度大于清理速度,可能是晋升阈值设置过小,Survivor空间小导致跑到老年代,eden区太小,存在大对象、数组对象等情况
+3.空间碎片过多,可以开启空间碎片整理并合理设置周期时间
full gc导致了concurrent mode failure,而不是因为concurrent mode failure错误导致触发full gc,真正触发full gc的原因可能是ygc时发生的promotion failure。
会,在初始标记的时候会扫描新生代。
虽然cms是老年代收集器,但是我们知道年轻代的对象是可以晋升为老年代的,为了空间分配担保,还是有必要去扫描年轻代。
在minor gc前,jvm会先检查老年代最大可用空间是否大于新生代所有对象总空间,如果是的话,则minor gc可以确保是安全的,
如果担保失败,会检查一个配置(HandlePromotionFailire),即是否允许担保失败。
如果允许:继续检查老年代最大可用可用的连续空间是否大于之前晋升的平均大小,比如说剩10m,之前每次都有9m左右的新生代到老年代,那么将尝试一次minor gc(大于的情况),这会比较冒险。
如果不允许,而且还小于的情况,则会触发full gc。【为了避免经常full GC 该参数建议打开】
这边为什么说是冒险是因为minor gc过后如果出现大对象,由于新生代采用复制算法,survivor无法容纳将跑到老年代,所以才会去计算之前的平均值作为一种担保的条件与老年代剩余空间比较,这就是分配担保。
这种担保是动态概率的手段,但是也有可能出现之前平均都比较低,突然有一次minor gc对象变得很多远高于以往的平均值,这个时候就会导致担保失败【Handle Promotion Failure】,这就只好再失败后再触发一次FULL GC,
这样做的话效率可能会更高,但是old区一般都是熬过多次可达性分析算法过后的存活的对象,要求比较苛刻且空间有限,而不能直接移过去,这将导致一系列问题(比如老年代容易被撑爆)
分两个Survivor(from/to),自然是为了保证复制算法运行以提高效率。
准确来说,垃圾收集器的使用跟当前jvm也有很大的关系,比如说g1是jdk7以后的版本才开始出现。
并不是所有的垃圾收集器都是默认开启的,有些得通过设置相应的开关参数才会使用。比如说cms,需设置(XX:+UseConcMarkSweepGC)
这边有几个实用的命令,比如说server模式下
#UnlockExperimentalVMOptions UnlockDiagnosticVMOptions解锁获取jvm参数,PrintFlagsFinal用于输出xx相关参数,以Benchmark类测试,这边会有很多结果 大都看不懂- - 在这边查(usexxxxxxgc会看到jvm不同收集器的开关情况)
+java -server -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal Benchmark
+
+#后面跟| grep ":"获取已赋值的参数[加:代表被赋值过]
+java -server -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal Benchmark| grep ":"
+
+#获得用户自定义的设置或者jvm设置的详细的xx参数和值
+java -server -XX:+PrintCommandLineFlags Benchmark
本人用的jdk8,这边UseParallelGC为true,参考深入理解jvm那本书说这个是Parallel Scavenge+Serial old搭配组合的开关,但是网上又说8默认是Parallel Scavenge+Parallel Old,我还是信书的吧 - -。
更多相关参数来源
常用参数
据说更高版本的jvm默认使用g1
stop the world简单来说就是gc的时候,停掉除gc外的java线程。
无论什么gc都难以避免停顿,即使是g1也会在初始标记阶段发生,stw并不可怕,可以尽可能的减少停顿时间。
对象优先分配在eden区,eden区满时会触发一次minor GC
对象晋升规则
1 长期存活的对象进入老年代,对象每熬过一次GC年龄+1(默认年龄阈值15,可配置)。
2 对象太大新生代无法容纳则会分配到老年代
3 eden区满了,进行minor gc后,eden和一个survivor区仍然存活的对象无法放到(to survivor区)则会通过分配担保机制放到老年代,这种情况一般是minor gc后新生代存活的对象太多。
4 动态年龄判定,为了使内存分配更灵活,jvm不一定要求对象年龄达到MaxTenuringThreshold(15)才晋升为老年代,若survior区相同年龄对象总大小大于survior区空间的一半,则大于等于这个年龄的对象将会在minor gc时移到老年代
G1 GC 是区域化、并行-并发、增量式垃圾回收器,相比其他 HotSpot 垃圾回收器,可提供更多可预测的暂停。增量的特性使 G1 GC 适用于更大的堆,在最坏的情况下仍能提供不错的响应。G1 GC 的自适应特性使 JVM 命令行只需要软实时暂停时间目标的最大值以及 Java 堆大小的最大值和最小值,即可开始工作。
g1不再区分老年代、年轻代这样的内存空间,这是较以往收集器很大的差异,所有的内存空间就是一块划分为不同子区域,每个区域大小为1m-32m,最多支持的内存为64g左右,且由于它为了的特性适用于大内存机器。
适用场景:
1.像cms能与应用程序并发执行,GC停顿短【短而且可控】,用户体验好的场景。
2.面向服务端,大内存,高cpu的应用机器。【网上说差不多是6g或更大】
3.应用在运行过程中经常会产生大量内存碎片,需要压缩空间【比cms好的地方之一,g1具备压缩功能】。
深入理解Java虚拟机
JVM 垃圾回收 Minor gc vs Major gc vs Full gc
JMM 是一种规范,是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题,而且写java代码的时候难免会经常和内存打交道,遇到各种内存溢出问题,有时候又难以定位问题,因此是一定要学习jmm以及GC的。
`,308)]))}const r=s(l,[["render",t],["__file","Java内存模型.html.vue"]]),k=JSON.parse('{"path":"/java/JVM/Java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.html","title":"Java内存模型(JMM)","lang":"zh-CN","frontmatter":{"description":"Java内存模型(JMM) 本文转载自深入理解JVM-内存模型(jmm)和GC 1 CPU和内存的交互 了解jvm内存模型前,了解下cpu和计算机内存的交互情况。【因为Java虚拟机内存模型定义的访问操作与计算机十分相似】 有篇很棒的文章,从cpu讲到内存模型:什么是java内存模型 在计算机中,cpu和内存的交互最为频繁,相比内存,磁盘读写太慢,内存...","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/java/JVM/Java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"Java内存模型(JMM)"}],["meta",{"property":"og:description","content":"Java内存模型(JMM) 本文转载自深入理解JVM-内存模型(jmm)和GC 1 CPU和内存的交互 了解jvm内存模型前,了解下cpu和计算机内存的交互情况。【因为Java虚拟机内存模型定义的访问操作与计算机十分相似】 有篇很棒的文章,从cpu讲到内存模型:什么是java内存模型 在计算机中,cpu和内存的交互最为频繁,相比内存,磁盘读写太慢,内存..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://github-images.wenzhihuai.com/images/10006199-d3fc8462f127a2c7-20240216171550523.jpg"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-02-16T09:55:09.000Z"}],["meta",{"property":"article:modified_time","content":"2024-02-16T09:55:09.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Java内存模型(JMM)\\",\\"image\\":[\\"https://github-images.wenzhihuai.com/images/10006199-d3fc8462f127a2c7-20240216171550523.jpg\\",\\"https://github-images.wenzhihuai.com/images/image-20240216172615735.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-0a3299bca20f13ad.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-61e3c01d0b265732.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-a4108d8fb7810a71.jpeg\\",\\"https://github-images.wenzhihuai.com/images/10006199-728567b81e7abff5.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-b55cc68293d1807d.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-76054110706ff110.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-b0fd456c33c09fce.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-9b3fe05daab42136.jpg\\",\\"https://github-images.wenzhihuai.com/images/10006199-318ad80ccb29abe4.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-27ef5c978077ed1c.jpg\\",\\"https://github-images.wenzhihuai.com/images/10006199-6cefc46d23c2d549.jpg\\",\\"https://github-images.wenzhihuai.com/images/10006199-07496d628d676815.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-04a7ea1247b98809.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-854e1de91f66764b.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-975ca350889de014.jpg\\",\\"https://github-images.wenzhihuai.com/images/10006199-239c6e3a1d84a447.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-d409f452f8364937.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-c0eb6418cf4bade9.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-6ed3ee78469592e8.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-a3524986c654e356.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-324780351133d59a.png\\",\\"https://github-images.wenzhihuai.com/images/10006199-8c124d281e0c6dd1.png\\"],\\"dateModified\\":\\"2024-02-16T09:55:09.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[{"level":2,"title":"1 CPU和内存的交互","slug":"_1-cpu和内存的交互","link":"#_1-cpu和内存的交互","children":[{"level":3,"title":"1.1.1 内存屏障(Memory Barrier)","slug":"_1-1-1-内存屏障-memory-barrier","link":"#_1-1-1-内存屏障-memory-barrier","children":[]}]},{"level":2,"title":"2. Java内存区域","slug":"_2-java内存区域","link":"#_2-java内存区域","children":[{"level":3,"title":"2.1 五大内存区域","slug":"_2-1-五大内存区域","link":"#_2-1-五大内存区域","children":[]}]},{"level":2,"title":"3.内存溢出","slug":"_3-内存溢出","link":"#_3-内存溢出","children":[]},{"level":2,"title":"4.GC简介","slug":"_4-gc简介","link":"#_4-gc简介","children":[]},{"level":2,"title":"4.1.为什么需要学习GC","slug":"_4-1-为什么需要学习gc","link":"#_4-1-为什么需要学习gc","children":[]},{"level":2,"title":"4.2 堆的回收区域","slug":"_4-2-堆的回收区域","link":"#_4-2-堆的回收区域","children":[]},{"level":2,"title":"5 判断对象是否存活算法","slug":"_5-判断对象是否存活算法","link":"#_5-判断对象是否存活算法","children":[]},{"level":2,"title":"5 垃圾收集算法","slug":"_5-垃圾收集算法","link":"#_5-垃圾收集算法","children":[{"level":3,"title":"5.1 三大垃圾收集算法","slug":"_5-1-三大垃圾收集算法","link":"#_5-1-三大垃圾收集算法","children":[]},{"level":3,"title":"5.2 枚举根节点算法","slug":"_5-2-枚举根节点算法","link":"#_5-2-枚举根节点算法","children":[]}]},{"level":2,"title":"6.垃圾收集器","slug":"_6-垃圾收集器","link":"#_6-垃圾收集器","children":[{"level":3,"title":"新生代收集器","slug":"新生代收集器","link":"#新生代收集器","children":[]},{"level":3,"title":"6.1 Serial","slug":"_6-1-serial","link":"#_6-1-serial","children":[]},{"level":3,"title":"6.2 ParNew收集器","slug":"_6-2-parnew收集器","link":"#_6-2-parnew收集器","children":[]},{"level":3,"title":"6.3 Parallel Scavenge","slug":"_6-3-parallel-scavenge","link":"#_6-3-parallel-scavenge","children":[]},{"level":3,"title":"老年代收集器","slug":"老年代收集器","link":"#老年代收集器","children":[]}]},{"level":2,"title":"6.4 Serial Old","slug":"_6-4-serial-old","link":"#_6-4-serial-old","children":[]},{"level":2,"title":"6.5 Parallel Old","slug":"_6-5-parallel-old","link":"#_6-5-parallel-old","children":[{"level":3,"title":"6.6 CMS","slug":"_6-6-cms","link":"#_6-6-cms","children":[]},{"level":3,"title":"6.7 G1收集器","slug":"_6-7-g1收集器","link":"#_6-7-g1收集器","children":[]}]},{"level":2,"title":"7 Minor GC、Major GC、FULL GC、mixed gc","slug":"_7-minor-gc、major-gc、full-gc、mixed-gc","link":"#_7-minor-gc、major-gc、full-gc、mixed-gc","children":[{"level":3,"title":"7.1 Minor GC","slug":"_7-1-minor-gc","link":"#_7-1-minor-gc","children":[]},{"level":3,"title":"7.2 Major GC","slug":"_7-2-major-gc","link":"#_7-2-major-gc","children":[]},{"level":3,"title":"7.3 Full GC","slug":"_7-3-full-gc","link":"#_7-3-full-gc","children":[]},{"level":3,"title":"7.4 mixed GC【g1特有】","slug":"_7-4-mixed-gc【g1特有】","link":"#_7-4-mixed-gc【g1特有】","children":[]}]},{"level":2,"title":"8 查看GC日志","slug":"_8-查看gc日志","link":"#_8-查看gc日志","children":[{"level":3,"title":"8.3 自带的jconsole工具、jstat命令","slug":"_8-3-自带的jconsole工具、jstat命令","link":"#_8-3-自带的jconsole工具、jstat命令","children":[]}]},{"level":2,"title":"几个疑问","slug":"几个疑问","link":"#几个疑问","children":[{"level":3,"title":"1.GC是怎么判断对象是被标记的","slug":"_1-gc是怎么判断对象是被标记的","link":"#_1-gc是怎么判断对象是被标记的","children":[]},{"level":3,"title":"2.什么时候触发GC","slug":"_2-什么时候触发gc","link":"#_2-什么时候触发gc","children":[]},{"level":3,"title":"3.cms收集器是否会扫描年轻代","slug":"_3-cms收集器是否会扫描年轻代","link":"#_3-cms收集器是否会扫描年轻代","children":[]},{"level":3,"title":"4.什么是空间分配担保","slug":"_4-什么是空间分配担保","link":"#_4-什么是空间分配担保","children":[]},{"level":3,"title":"5.为什么复制算法要分两个Survivor,而不直接移到老年代","slug":"_5-为什么复制算法要分两个survivor-而不直接移到老年代","link":"#_5-为什么复制算法要分两个survivor-而不直接移到老年代","children":[]},{"level":3,"title":"6.各个版本的JVM使用的垃圾收集器是怎么样的","slug":"_6-各个版本的jvm使用的垃圾收集器是怎么样的","link":"#_6-各个版本的jvm使用的垃圾收集器是怎么样的","children":[]},{"level":3,"title":"7 stop the world具体是什么,有没有办法避免","slug":"_7-stop-the-world具体是什么-有没有办法避免","link":"#_7-stop-the-world具体是什么-有没有办法避免","children":[]},{"level":3,"title":"8 新生代什么样的情况会晋升为老年代","slug":"_8-新生代什么样的情况会晋升为老年代","link":"#_8-新生代什么样的情况会晋升为老年代","children":[]},{"level":3,"title":"8.怎么理解g1,适用于什么场景","slug":"_8-怎么理解g1-适用于什么场景","link":"#_8-怎么理解g1-适用于什么场景","children":[]}]},{"level":2,"title":"总结","slug":"总结","link":"#总结","children":[]}],"git":{"createdTime":1708075355000,"updatedTime":1708077309000,"contributors":[{"name":"zhihuaiwen","email":"zhihuaiwen@tencent.com","commits":3}]},"readingTime":{"minutes":46.11,"words":13833},"filePathRelative":"java/JVM/Java内存模型.md","localizedDate":"2024年2月16日","excerpt":"\\n本文转载自深入理解JVM-内存模型(jmm)和GC
","autoDesc":true}');export{r as comp,k as data}; diff --git "a/assets/Jenkins\347\232\204\344\270\200\344\272\233\347\254\224\350\256\260.html--8cnUWGp.js" "b/assets/Jenkins\347\232\204\344\270\200\344\272\233\347\254\224\350\256\260.html--8cnUWGp.js" new file mode 100644 index 00000000..cc0397d1 --- /dev/null +++ "b/assets/Jenkins\347\232\204\344\270\200\344\272\233\347\254\224\350\256\260.html--8cnUWGp.js" @@ -0,0 +1,16 @@ +import{_ as s,c as a,d as n,o as e}from"./app-ftEjETWs.js";const t={};function h(l,i){return e(),a("div",null,i[0]||(i[0]=[n(`公司主要要开发自己的paas平台,集成了Jenkins,真的是遇到了很多很多困难,特别是在api调用的权限这一块,这里,把自己遇到的一些坑的解决方法做一下笔记吧。当然,首先要讲的,就是如何在开启安全的情况下进行API调用。
如果勾选不对,那么Jenkins有可能崩溃掉,亲身经历,之前一直没有勾选安全域,然后授权策略为登录用户可以做任何事,之后权限这一块就彻底崩溃了,重装了又重装,才知道,需要勾选安全域。
同时开启跨站请求伪造保护,Jenkins的一些API需要用到的。
Jenkins的用户token可以在用户的设置下面获得,但是这种方式如果需要重装Jenkins的话,就得重新修改一次配置文件
经过对Jenkins-client的抓包分析,token可以由username+":"+password,然后进行base64加密组成,之后在token前面加上"Basic "即可,代码如下:
在远程API调用的时候,Jenkins对于某些接口的要求不仅限于Authorization,还必须要有Jenkins-Crumb,这个东西之前在进行获取的时候,有时候会变来变去,比如用curl命令和f12查看的时候发现不一致,实在受不了,感觉毫无规律可言,之后才发现上面的Authorization来直接调用接口获取的才是正确的,再然后想想,可能是之前调用api的时候,没有开启启用安全,再或者是有没有勾选上使用碎片算法。
另,附上curl查询Jenkins-Crumb的命令:
curl -s 'http://admin:yourtoken@jenkins-url/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)'
替换掉yourtoken和jenkins-url即可。
Jenkins的API设计可谓是独领风骚,能把一个提交设计成这样真实佩服测试之后才发现只要提交个表单,key为json,value为值即可,其他的都不需要,这个设计我也不知道怎么来的,感觉超级坑。
由于我们是将Jenkins集成在我们自己的平台里面,并不暴露Jenkins给用户,所以,创建一个job的时候,必须由我们平台的参数往Jenkins里面提交,这一提交,发现的问题不少。
一是Jenkins的整个job的提交是由两步组成的,先是创建job,再提交配置。即:/createItem?name=xxx接口。
二是提交的配置参数,提交的是整个xml,而不是由一个一个参数组成的。对于java来说,就得使用xstream或者其他来转化,甚是折腾,如图这种转化。
在点击立即构建的时候,Jenkins是没有返回任何信息,但是在Jenkins的内部,它是通过放到队列里等待的,如果有空闲,就开始构建,否则等待,这个队列是可以获取得到的,我们从里面可以获取上一次构建的信息,是成功还是失败。这种情况下,假设我们多个人同时点击,这下子就有点慌了,如何获取到具体某个人的构建结果,有点虐心。想了半天,最终得出的事:代码相同,意味着每次构建的结果相同,为什么要允许多个人同时点击?就这么解决了:从一个job的构建队列中获取最后一次构建的信息,如果是正在构建,那么不允许构建了,直到构建结果出来。
需要将Jenkins中的构建进度移植到我们自有的平台,Jenkins的构建进度时通过ajax轮询实现的,获取文本的规则主要从response header里面的两个字段获取
(1)X-More-Data:是否有更多的数据
(2)X-Text-Size:从开始到该次调用的文本大小
我们是通过websocket来将文本内容推送到前端,使用的stomp协议,部分代码如下:
while (true) {
+ ...
+ String string = response.body().string();
+ String header = response.header("X-More-Data");
+ if (!Strings.isNullOrEmpty(header) || start == 0) {
+ template.convertAndSend("/topic/" + uuid, string);
+ String textSize = response.header("X-Text-Size");
+ if (!Strings.isNullOrEmpty(textSize)) {
+ start = Integer.parseInt(textSize);
+ }
+ TimeUnit.SECONDS.sleep(5);
+ } else {
+ template.convertAndSend("/topic/" + uuid, string);
+ return;
+ }
+ }
参考:
1.通过jenkins API去build一个job
2.Jenkins Remote API
公司主要要开发自己的paas平台,集成了Jenkins,真的是遇到了很多很多困难,特别是在api调用的权限这一块,这里,把自己遇到的一些坑的解决方法做一下笔记吧。当然,首先要讲的,就是如何在开启安全的情况下进行API调用。
","autoDesc":true}');export{k as comp,r as data}; diff --git "a/assets/Kubernetes\345\256\271\345\231\250\346\227\245\345\277\227\346\224\266\351\233\206.html-SN4rN7dm.js" "b/assets/Kubernetes\345\256\271\345\231\250\346\227\245\345\277\227\346\224\266\351\233\206.html-SN4rN7dm.js" new file mode 100644 index 00000000..ea30bd8c --- /dev/null +++ "b/assets/Kubernetes\345\256\271\345\231\250\346\227\245\345\277\227\346\224\266\351\233\206.html-SN4rN7dm.js" @@ -0,0 +1,161 @@ +import{_ as i,c as a,d as n,o as e}from"./app-ftEjETWs.js";const l={};function t(h,s){return e(),a("div",null,s[0]||(s[0]=[n(`日志从传统方式演进到容器方式的过程就不详细讲了,可以参考一下这篇文章Docker日志收集最佳实践,由于容器的漂移、自动伸缩等特性,日志收集也就必须使用新的方式来实现,Kubernetes官方给出的方式基本是这三种:原生方式、DaemonSet方式和Sidecar方式。
1.原生方式 使用 kubectl logs 直接在查看本地保留的日志,或者通过docker engine的 log driver 把日志重定向到文件、syslog、fluentd等系统中。
2.DaemonSet方式 在K8S的每个node上部署日志agent,由agent采集所有容器的日志到服务端。
3.Sidecar方式 一个POD中运行一个sidecar的日志agent容器,用于采集该POD主容器产生的日志。
三种方式都有利有弊,没有哪种方式能够完美的解决100%问题的,所以要根据场景来贴合。
简单的说,原生方式就是直接使用kubectl logs来查看日志,或者将docker的日志通过日志驱动来打到syslog、journal等去,然后再通过命令来排查,这种方式最好的优势就是简单、资源占用率低等,但是,在多容器、弹性伸缩情况下,日志的排查会十分困难,仅仅适用于刚开始研究Kubernetes的公司吧。不过,原生方式确实其他两种方式的基础,因为它的两种最基础的理念,daemonset和sidecar模式都是基于这两种方式而来的。
这种方式是daemonset方式的基础。将日志全部输出到控制台,然后docker开启journal,然后就能在/var/log/journal下面看到二进制的journal日志,如果要查看二进制的日志的话,可以使用journalctl来查看日志:journalctl -u docker.service -n 1 --no-pager -o json -o json-pretty
{
+ "__CURSOR" : "s=113d7df2f5ff4d0985b08222b365c27a;i=1a5744e3;b=05e0fdf6d1814557939e52c0ac7ea76c;m=5cffae4cd4;t=58a452ca82da8;x=29bef852bcd70ae2",
+ "__REALTIME_TIMESTAMP" : "1559404590149032",
+ "__MONOTONIC_TIMESTAMP" : "399426604244",
+ "_BOOT_ID" : "05e0fdf6d1814557939e52c0ac7ea76c",
+ "PRIORITY" : "6",
+ "CONTAINER_ID_FULL" : "f2108df841b1f72684713998c976db72665f353a3b4ea17cd06b5fc5f0b8ae27",
+ "CONTAINER_NAME" : "k8s_controllers_master-controllers-dev4.gcloud.set_kube-system_dcab37be702c9ab6c2b17122c867c74a_1",
+ "CONTAINER_TAG" : "f2108df841b1",
+ "CONTAINER_ID" : "f2108df841b1",
+ "_TRANSPORT" : "journal",
+ "_PID" : "6418",
+ "_UID" : "0",
+ "_GID" : "0",
+ "_COMM" : "dockerd-current",
+ "_EXE" : "/usr/bin/dockerd-current",
+ "_CMDLINE" : "/usr/bin/dockerd-current --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current --default-runtime=docker-runc --exec-opt native.cgroupdriver=systemd --userland-proxy-path=/usr/libexec/docker/docker-proxy-current --init-path=/usr/libexec/docker/docker-init-current --seccomp-profile=/etc/docker/seccomp.json --selinux-enabled=false --log-driver=journald --insecure-registry hub.paas.kjtyun.com --insecure-registry hub.gcloud.lab --insecure-registry 172.30.0.0/16 --log-level=warn --signature-verification=false --max-concurrent-downloads=20 --max-concurrent-uploads=20 --storage-driver devicemapper --storage-opt dm.fs=xfs --storage-opt dm.thinpooldev=/dev/mapper/docker--vg-docker--pool --storage-opt dm.use_deferred_removal=true --storage-opt dm.use_deferred_deletion=true --mtu=1450",
+ "_CAP_EFFECTIVE" : "1fffffffff",
+ "_SYSTEMD_CGROUP" : "/system.slice/docker.service",
+ "_SYSTEMD_UNIT" : "docker.service",
+ "_SYSTEMD_SLICE" : "system.slice",
+ "_MACHINE_ID" : "225adcce13bd233a56ab481df7413e0b",
+ "_HOSTNAME" : "dev4.gcloud.set",
+ "MESSAGE" : "I0601 23:56:30.148153 1 event.go:221] Event(v1.ObjectReference{Kind:\\"DaemonSet\\", Namespace:\\"openshift-monitoring\\", Name:\\"node-exporter\\", UID:\\"f6d2bdc1-6658-11e9-aca2-fa163e938959\\", APIVersion:\\"apps/v1\\", ResourceVersion:\\"15378688\\", FieldPath:\\"\\"}): type: 'Normal' reason: 'SuccessfulCreate' Created pod: node-exporter-hvrpf",
+ "_SOURCE_REALTIME_TIMESTAMP" : "1559404590148488"
+}
在上面的json中,_CMDLINE以及其他字段占用量比较大,而且这些没有什么意义,会导致一条简短的日志却被封装成多了几十倍的量,所以的在日志量特别大的情况下,最好进行一下字段的定制,能够减少就减少。
我们一般需要的字段是CONTAINER_NAME以及MESSAGE,通过CONTAINER_NAME可以获取到Kubernetes的namespace和podName,比如CONTAINER_NAME为k8s_controllers_master-controllers-dev4.gcloud.set_kube-system_dcab37be702c9ab6c2b17122c867c74a_1的时候
container name in pod: controllers
**pod name: **master-controllers-dev4.gcloud.set
namespace: kube-system
**pod uid: **dcab37be702c9ab6c2b17122c867c74a_1
journal方式算是比较标准的方式,如果采用hostPath方式,能够直接将日志输出这里。这种方式唯一的缺点就是在旧Kubernetes中无法获取到podName,但是最新版的Kubernetes1.14的一些特性subPathExpr,就是可以将目录挂载的时候同时将podName写进目录里,但是这个特性仍旧是alpha版本,谨慎使用。
简单说下实现原理:容器中填写的日志目录,挂载到宿主机的/data/logs/namespace/service_name/$(PodName)/xxx.log里面,如果是sidecar模式,则将改目录挂载到sidecar的收集目录里面进行推送。如果是宿主机安装fluentd模式,则需要匹配编写代码实现识别namespace、service_name、PodName等,然后发送到日志系统。
可参考:https://github.com/kubernetes/enhancements/blob/master/keps/sig-storage/20181029-volume-subpath-env-expansion.md
日志落盘参考细节:
env:
+ - name: POD_NAME
+ valueFrom:
+ fieldRef:
+ apiVersion: v1
+ fieldPath: metadata.name
+ ...
+ volumeMounts:
+ - name: workdir1
+ mountPath: /logs
+ subPathExpr: $(POD_NAME)
我们主要使用了在Pod里的主容器挂载了一个fluent-agent的收集器,来将日志进行收集,其中我们修改了Kubernetes-Client的源码使之支持subPathExpr,然后发送到日志系统的kafka。这种方式能够处理多种日志的收集,比如业务方的日志打到控制台了,但是jvm的日志不能同时打到控制台,否则会发生错乱,所以,如果能够将业务日志挂载到宿主机上,同时将一些其他的日志比如jvm的日志挂载到容器上,就可以使用该种方式。
{
+ "_fileName":"/data/work/logs/epaas_2019-05-22-0.log",
+ "_sortedId":"660c2ce8-aacc-42c4-80d1-d3f6d4c071ea",
+ "_collectTime":"2019-05-22 17:23:58",
+ "_log":"[33m2019-05-22 17:23:58[0;39m |[34mINFO [0;39m |[34mmain[0;39m |[34mSpringApplication.java:679[0;39m |[32mcom.hqyg.epaas.EpaasPortalApplication[0;39m | The following profiles are active: dev",
+ "_domain":"rongqiyun-dev",
+ "_podName":"aofjweojo-5679849765-gncbf",
+ "_hostName":"dev4.gcloud.set"
+}
daemonset方式也是基于journal,日志使用journal的log-driver,变成二进制的日志,然后在每个node节点上部署一个日志收集的agent,挂载/var/log/journal的日志进行解析,然后发送到kafka或者es,如果节点或者日志量比较大的话,对es的压力实在太大,所以,我们选择将日志推送到kafka。容器日志收集普遍使用fluentd,资源要求较少,性能高,是目前最成熟的日志收集方案,可惜是使用了ruby来写的,普通人根本没时间去话时间学习这个然后进行定制,好在openshift中提供了origin-aggregated-logging方案。
我们可以通过fluent.conf来看origin-aggregated-logging做了哪些工作,把注释,空白的一些东西去掉,然后我稍微根据自己的情况修改了下,结果如下:
@include configs.d/openshift/system.conf
+设置fluent的日志级别
+@include configs.d/openshift/input-pre-*.conf
+最主要的地方,读取journal的日志
+@include configs.d/dynamic/input-syslog-*.conf
+读取syslog,即操作日志
+<label @INGRESS>
+ @include configs.d/openshift/filter-retag-journal.conf
+ 进行匹配
+ @include configs.d/openshift/filter-k8s-meta.conf
+ 获取Kubernetes的相关信息
+ @include configs.d/openshift/filter-viaq-data-model.conf
+ 进行模型的定义
+ @include configs.d/openshift/filter-post-*.conf
+ 生成es的索引id
+ @include configs.d/openshift/filter-k8s-record-transform.conf
+ 修改日志记录,我们在这里进行了字段的定制,移除了不需要的字段
+ @include configs.d/openshift/output-applications.conf
+ 输出,默认是es,如果想使用其他的比如kafka,需要自己定制
+</label>
当然,细节上并没有那么好理解,换成一步步理解如下:
1. 解析journal日志
origin-aggregated-logging会将二进制的journal日志中的CONTAINER_NAME进行解析,根据匹配规则将字段进行拆解
"kubernetes": {
+ "container_name": "fas-dataservice-dev-new",
+ "namespace_name": "fas-cost-dev",
+ "pod_name": "fas-dataservice-dev-new-5c48d7c967-kb79l",
+ "pod_id": "4ad125bb7558f52e30dceb3c5e88dc7bc160980527356f791f78ffcaa6d1611c",
+ "namespace_id": "f95238a6-3a67-11e9-a211-20040fe7b690"
+ }
2. es封装
主要用的是elasticsearch_genid_ext插件,写在了filter-post-genid.conf上。
3. 日志分类
通过origin-aggregated-logging来收集journal的日志,然后推送至es,origin-aggregated-logging在推送过程中做了不少优化,即适应高ops的、带有等待队列的、推送重试等,详情可以具体查看一下。
还有就是对日志进行了分类,分为三种:
(1).操作日志(在es中以.operations匹配的),记录了对Kubernetes的操作
(2).项目日志(在es中以project匹配的),业务日志,日志收集中最重要的
(3).孤儿日志(在es中以.orphaned.*匹配的),没有namespace的日志都会打到这里
4. 日志字段定制
经过origin-aggregated-logging推送至后采集的一条日志如下:
{
+ "CONTAINER_TAG": "4ad125bb7558",
+ "docker": {
+ "container_id": "4ad125bb7558f52e30dceb3c5e88dc7bc160980527356f791f78ffcaa6d1611c"
+ },
+ "kubernetes": {
+ "container_name": "fas-dataservice-dev-new",
+ "namespace_name": "fas-cost-dev",
+ "pod_name": "fas-dataservice-dev-new-5c48d7c967-kb79l",
+ "pod_id": "4ad125bb7558f52e30dceb3c5e88dc7bc160980527356f791f78ffcaa6d1611c",
+ "namespace_id": "f95238a6-3a67-11e9-a211-20040fe7b690"
+ },
+ "systemd": {
+ "t": {
+ "BOOT_ID": "6246327d7ea441339d6d14b44498b177",
+ "CAP_EFFECTIVE": "1fffffffff",
+ "CMDLINE": "/usr/bin/dockerd-current --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current --default-runtime=docker-runc --exec-opt native.cgroupdriver=systemd --userland-proxy-path=/usr/libexec/docker/docker-proxy-current --init-path=/usr/libexec/docker/docker-init-current --seccomp-profile=/etc/docker/seccomp.json --selinux-enabled=false --log-driver=journald --insecure-registry hub.paas.kjtyun.com --insecure-registry 10.77.0.0/16 --log-level=warn --signature-verification=false --bridge=none --max-concurrent-downloads=20 --max-concurrent-uploads=20 --storage-driver devicemapper --storage-opt dm.fs=xfs --storage-opt dm.thinpooldev=/dev/mapper/docker--vg-docker--pool --storage-opt dm.use_deferred_removal=true --storage-opt dm.use_deferred_deletion=true --mtu=1450",
+ "COMM": "dockerd-current",
+ "EXE": "/usr/bin/dockerd-current",
+ "GID": "0",
+ "MACHINE_ID": "0096083eb4204215a24efd202176f3ec",
+ "PID": "17181",
+ "SYSTEMD_CGROUP": "/system.slice/docker.service",
+ "SYSTEMD_SLICE": "system.slice",
+ "SYSTEMD_UNIT": "docker.service",
+ "TRANSPORT": "journal",
+ "UID": "0"
+ }
+ },
+ "level": "info",
+ "message": "\\tat com.sun.proxy.$Proxy242.execute(Unknown Source)",
+ "hostname": "host11.rqy.kx",
+ "pipeline_metadata": {
+ "collector": {
+ "ipaddr4": "10.76.232.16",
+ "ipaddr6": "fe80::a813:abff:fe66:3b0c",
+ "inputname": "fluent-plugin-systemd",
+ "name": "fluentd",
+ "received_at": "2019-05-15T09:22:39.297151+00:00",
+ "version": "0.12.43 1.6.0"
+ }
+ },
+ "@timestamp": "2019-05-06T01:41:01.960000+00:00",
+ "viaq_msg_id": "NjllNmI1ZWQtZGUyMi00NDdkLWEyNzEtMTY3MDQ0ZjEyZjZh"
+ }
可以看出,跟原生的journal日志类似,增加了几个字段为了写进es中而已,总体而言,其他字段并没有那么重要,所以我们对其中的字段进行了定制,以减少日志的大小,定制化字段之后,一段日志的输出变为(不是同一段,只是举个例子):
{
+ "hostname":"dev18.gcloud.set",
+ "@timestamp":"2019-05-17T04:22:33.139608+00:00",
+ "pod_name":"istio-pilot-8588fcb99f-rqtkd",
+ "appName":"discovery",
+ "container_name":"epaas-discovery",
+ "domain":"istio-system",
+ "sortedId":"NjA3ODVhODMtZDMyYy00ZWMyLWE4NjktZjcwZDMwMjNkYjQ3",
+ "log":"spiffluster.local/ns/istio-system/sa/istio-galley-service-account"
+}
5.部署
最后,在node节点上添加logging-infra-fluentd: "true"的标签,就可以在namespace为openshift-logging中看到节点的收集器了。
logging-fluentd-29p8z 1/1 Running 0 6d
+logging-fluentd-bpkjt 1/1 Running 0 6d
+logging-fluentd-br9z5 1/1 Running 0 6d
+logging-fluentd-dkb24 1/1 Running 1 5d
+logging-fluentd-lbvbw 1/1 Running 0 6d
+logging-fluentd-nxmk9 1/1 Running 1 5d
6.关于ip
业务方不仅仅想要podName,同时还有对ip的需求,控制台方式正常上是没有记录ip的,所以这算是一个难点中的难点,我们在kubernetes_metadata_common.rb的kubernetes_metadata中添加了 'pod_ip' => pod_object['status']['podIP'],最终是有些有ip,有些没有ip,这个问题我们继续排查。
这种方式的好处是能够获取日志的文件名、容器的ip地址等,并且配置性比较高,能够很好的进行一系列定制化的操作,比如使用log-pilot或者filebeat或者其他的收集器,还能定制一些特定的字段,比如文件名、ip地址等。
sidecar模式用来解决日志收集的问题的话,需要将日志目录挂载到宿主机的目录上,然后再mount到收集agent的目录里面,以达到文件共享的目的,默认情况下,使用emptydir来实现文件共享的目的,这里简单介绍下emptyDir的作用。
EmptyDir类型的volume创建于pod被调度到某个宿主机上的时候,而同一个pod内的容器都能读写EmptyDir中的同一个文件。一旦这个pod离开了这个宿主机,EmptyDir中的数据就会被永久删除。所以目前EmptyDir类型的volume主要用作临时空间,比如Web服务器写日志或者tmp文件需要的临时目录。
日志如果丢失的话,会对业务造成的影响不可估量,所以,我们使用了尚未成熟的subPathExpr来实现,即挂载到宿主的固定目录/data/logs下,然后是namespace,deploymentName,podName,再然后是日志文件,合成一块便是/data/logs/\${namespace}/\${deploymentName}/\${podName}/xxx.log。
具体的做法就不在演示了,这里只贴一下yaml文件。
apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ name: xxxx
+ namespace: element-dev
+spec:
+ template:
+ spec:
+ volumes:
+ - name: host-log-path-0
+ hostPath:
+ path: /data/logs/element-dev/xxxx
+ type: DirectoryOrCreate
+ containers:
+ - name: xxxx
+ image: 'xxxxxxx'
+ volumeMounts:
+ - name: host-log-path-0
+ mountPath: /data/work/logs/
+ subPathExpr: $(POD_NAME)
+ - name: xxxx-elog-agent
+ image: 'agent'
+ volumeMounts:
+ - name: host-log-path-0
+ mountPath: /data/work/logs/
+ subPathExpr: $(POD_NAME)
fluent.conf的配置文件由于保密关系就不贴了,收集后的一条数据如下:
{
+ "_fileName":"/data/work/logs/xxx_2019-05-22-0.log",
+ "_sortedId":"660c2ce8-aacc-42c4-80d1-d3f6d4c071ea",
+ "_collectTime":"2019-05-22 17:23:58",
+ "_log":"[33m2019-05-22 17:23:58[0;39m |[34mINFO [0;39m |[34mmain[0;39m |[34mSpringApplication.java:679[0;39m |[32mcom.hqyg.epaas.EpaasPortalApplication[0;39m | The following profiles are active: dev",
+ "_domain":"namespace",
+ "_ip":"10.128.93.31",
+ "_podName":"xxxx-5679849765-gncbf",
+ "_hostName":"dev4.gcloud.set"
+}
总的来说,daemonset方式比较简单,而且适合更加适合微服务化,当然,不是完美的,比如业务方想把业务日志打到控制台上,但是同时也想知道jvm的日志,这种情况下或许sidecar模式更好。但是sidecar也有不完美的地方,每个pod里都要存在一个日志收集的agent实在是太消耗资源了,而且很多问题也难以解决,比如:主容器挂了,agent还没收集完,就把它给kill掉,这个时候日志怎么处理,业务会不会受到要杀掉才能启动新的这一短暂过程的影响等。所以,我们实际使用中首选daemonset方式,但是提供了sidecar模式让用户选择。
参考:
1.Kubernetes日志官方文档
2.Kubernetes日志采集Sidecar模式介绍
3.Docker日志收集最佳实践
日志从传统方式演进到容器方式的过程就不详细讲了,可以参考一下这篇文章Docker日志收集最佳实践,由于容器的漂移、自动伸缩等特性,日志收集也就必须使用新的方式来实现,Kubernetes官方给出的方式基本是这三种:原生方式、DaemonSet方式和Sidecar方式。
","autoDesc":true}');export{k as comp,d as data}; diff --git "a/assets/RedissonLock\345\210\206\345\270\203\345\274\217\351\224\201\346\272\220\347\240\201\345\210\206\346\236\220.html-DBlO1MVO.js" "b/assets/RedissonLock\345\210\206\345\270\203\345\274\217\351\224\201\346\272\220\347\240\201\345\210\206\346\236\220.html-DBlO1MVO.js" new file mode 100644 index 00000000..f21b831c --- /dev/null +++ "b/assets/RedissonLock\345\210\206\345\270\203\345\274\217\351\224\201\346\272\220\347\240\201\345\210\206\346\236\220.html-DBlO1MVO.js" @@ -0,0 +1,199 @@ +import{_ as i,c as a,d as n,o as l}from"./app-ftEjETWs.js";const h={};function k(t,s){return l(),a("div",null,s[0]||(s[0]=[n(`最近碰到的一个问题,Java代码中写了一个定时器,分布式部署的时候,多台同时执行的话就会出现重复的数据,为了避免这种情况,之前是通过在配置文件里写上可以执行这段代码的IP,代码中判断如果跟这个IP相等,则执行,否则不执行,想想也是一种比较简单的方式吧,但是感觉很low很low,所以改用分布式锁。
目前分布式锁常用的三种方式:1.数据库的锁;2.基于Redis的分布式锁;3.基于ZooKeeper的分布式锁。其中数据库中的锁有共享锁和排他锁,这两种都无法直接解决数据库的单点和可重入的问题,所以,本章还是来讲讲基于Redis的分布式锁,也可以用其他缓存(Memcache、Tair等)来实现。
1.SETNX key val
当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
2.expire key timeout
为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
3.delete key
删除key,此处用来解锁使用。
4.HEXISTS key field
当key 中存储着field的时候返回1,如果key或者field至少有一个不存在返回0。
5.HINCRBY key field increment
将存储在 key 中的哈希(Hash)对象中的指定字段 field 的值加上增量 increment。如果键 key 不存在,一个保存了哈希对象的新建将被创建。如果字段 field 不存在,在进行当前操作前,其将被创建,且对应的值被置为 0。返回值是增量之后的值。
由上面三个命令,我们可以很快的写一个分布式锁出来:
if (conn.setnx("lock","1").equals(1L)) {
+ //do something
+ return true;
+}
+return false;
但是这样也会存在问题,如果获取该锁的客户端挂掉了怎么办?一般而言,我们可以通过设置expire的过期时间来防止客户端挂掉所带来的影响,可以降低应用挂掉所带来的影响,不过当时间失效的时候,要保证其他客户端只有一台能够获取。
Redisson在基于NIO的Netty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。——摘自百度百科
先在pom引入Redssion
<dependency>
+ <groupId>org.redisson</groupId>
+ <artifactId>redisson</artifactId>
+ <version>3.6.1</version>
+</dependency>
起100个线程,同时对count进行操作,每次操作减1,加锁的时候能够保持顺序输出,不加的话为随机。
public class RedissonTest implements Runnable {
+ private static RedissonClient redisson;
+ private static int count = 10000;
+
+ private static void init() {
+ Config config = new Config();
+ config.useSingleServer()
+ .setAddress("redis://119.23.46.71:6340")
+ .setPassword("root")
+ .setDatabase(10);
+ redisson = Redisson.create(config);
+ }
+
+ @Override
+ public void run() {
+ RLock lock = redisson.getLock("anyLock");
+ lock.lock();
+ count--;
+ System.out.println(count);
+ lock.unlock();
+ }
+
+ public static void main(String[] args) {
+ init();
+ for (int i = 0; i < 100; i++) {
+ new Thread(new RedissonTest()).start();
+ }
+ }
+}
输出结果(部分结果):
...
+9930
+9929
+9928
+9927
+9926
+9925
+9924
+9923
+9922
+9921
+
+...
去掉lock.lock()和lock.unlock()之后(部分结果):
...
+9930
+9931
+9933
+9935
+9938
+9937
+9940
+9941
+9942
+9944
+9947
+9946
+9914
+...
最新版的Redisson要求redis能够支持eval的命令,否则无法实现,即Redis要求2.6版本以上。在lua脚本中可以调用大部分的Redis命令,使用脚本的好处如下:
(1)减少网络开销:在Redis操作需求需要向Redis发送5次请求,而使用脚本功能完成同样的操作只需要发送一个请求即可,减少了网络往返时延。
(2)原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说在编写脚本的过程中无需担心会出现竞态条件,也就无需使用事务。事务可以完成的所有功能都可以用脚本来实现。
(3)复用:客户端发送的脚本会永久存储在Redis中,这就意味着其他客户端(可以是其他语言开发的项目)可以复用这一脚本而不需要使用代码完成同样的逻辑。
全局变量:
expirationRenewalMap:存储entryName和其过期时间,底层用的netty的PlatformDependent.newConcurrentHashMap()
internalLockLeaseTime:锁默认释放的时间:30 * 1000,即30秒
id:UUID,用作客户端的唯一标识
PUBSUB:订阅者模式,当释放锁的时候,其他客户端能够知道锁已经被释放的消息,并让队列中的第一个消费者获取锁。使用PUB/SUB消息机制的优点:减少申请锁时的等待时间、安全、 锁带有超时时间、锁的标识唯一,防止死锁 锁设计为可重入,避免死锁。
commandExecutor:命令执行器,异步执行器
以lock.lock()为例,调用lock之后,底层使用的是lockInterruptibly,之后调用lockInterruptibly(-1, null);
(1)我们来看一下lockInterruptibly的源码,如果别的客户端没有加锁,则当前客户端进行加锁并且订阅,其他客户端尝试加锁,并且获取ttl,然后等待已经加了锁的客户端解锁。
//leaseTime默认为-1
+public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
+ long threadId = Thread.currentThread().getId();//获取当前线程ID
+ Long ttl = tryAcquire(leaseTime, unit, threadId);//尝试加锁
+ // 如果为空,当前线程获取锁成功,否则已经被其他客户端加锁
+ if (ttl == null) {
+ return;
+ }
+ //等待释放,并订阅锁
+ RFuture<RedissonLockEntry> future = subscribe(threadId);
+ commandExecutor.syncSubscription(future);
+ try {
+ while (true) {
+ // 重新尝试获取锁
+ ttl = tryAcquire(leaseTime, unit, threadId);
+ // 成功获取锁
+ if (ttl == null) {
+ break;
+ }
+ // 等待锁释放
+ if (ttl >= 0) {
+ getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
+ } else {
+ getEntry(threadId).getLatch().acquire();
+ }
+ }
+ } finally {
+ // 取消订阅
+ unsubscribe(future, threadId);
+ }
+}
(2)下面是tryAcquire的实现,调用的是tryAcquireAsync
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
+ return get(tryAcquireAsync(leaseTime, unit, threadId));
+ }
(3)下面是tryAcquireAsync的实现,异步尝试进行加锁,尝试加锁的时候leaseTime为-1。通常如果客户端没有加锁成功,则会进行阻塞,leaseTime为锁释放的时间。
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {
+ if (leaseTime != -1) { //在lock.lock()的时候,已经声明了leaseTime为-1,尝试加锁
+ return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
+ }
+ RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
+ //监听事件,订阅消息
+ ttlRemainingFuture.addListener(new FutureListener<Long>() {
+ @Override
+ public void operationComplete(Future<Long> future) throws Exception {
+ if (!future.isSuccess()) {
+ return;
+ }
+ Long ttlRemaining = future.getNow();
+ // lock acquired
+ if (ttlRemaining == null) {
+ //获取新的超时时间
+ scheduleExpirationRenewal(threadId);
+ }
+ }
+ });
+ return ttlRemainingFuture; //返回ttl时间
+}
(4)下面是tryLockInnerAsyncy异步加锁,使用lua能够保证操作是原子性的
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
+ internalLockLeaseTime = unit.toMillis(leaseTime);
+ return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
+ "if (redis.call('exists', KEYS[1]) == 0) then " +
+ "redis.call('hset', KEYS[1], ARGV[2], 1); " +
+ "redis.call('pexpire', KEYS[1], ARGV[1]); " +
+ "return nil; " +
+ "end; " +
+ "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
+ "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
+ "redis.call('pexpire', KEYS[1], ARGV[1]); " +
+ "return nil; " +
+ "end; " +
+ "return redis.call('pttl', KEYS[1]);",
+ Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
+}
参数
KEYS[1](getName()) :需要加锁的key,这里需要是字符串类型。
ARGV[1](internalLockLeaseTime) :锁的超时时间,防止死锁
ARGV[2](getLockName(threadId)) :锁的唯一标识,也就是刚才介绍的 id(UUID.randomUUID()) + “:” + threadId
lua脚本解释
--检查key是否被占用了,如果没有则设置超时时间和唯一标识,初始化value=1
+if (redis.call('exists', KEYS[1]) == 0) then
+ redis.call('hset', KEYS[1], ARGV[2], 1);
+ redis.call('pexpire', KEYS[1], ARGV[1]);
+ return nil;
+end;
+--如果锁重入,需要判断锁的key field 都一致情况下 value 加一
+if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
+ redis.call('hincrby', KEYS[1], ARGV[2], 1);
+ --锁重入重新设置超时时间
+ redis.call('pexpire', KEYS[1], ARGV[1]);
+ return nil;
+end;
+--返回剩余的过期时间
+return redis.call('pttl', KEYS[1]);
(5)流程图
解锁的代码很简单,大意是将该节点删除,并发布消息。
(1)unlock源码
public void unlock() {
+ Boolean opStatus = get(unlockInnerAsync(Thread.currentThread().getId()));
+ if (opStatus == null) {
+ throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
+ + id + " thread-id: " + Thread.currentThread().getId());
+ }
+ if (opStatus) {
+ cancelExpirationRenewal();
+ }
(2)异步解锁,并返回是否成功
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
+ return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
+ "if (redis.call('exists', KEYS[1]) == 0) then " +
+ "redis.call('publish', KEYS[2], ARGV[1]); " +
+ "return 1; " +
+ "end;" +
+ "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
+ "return nil;" +
+ "end; " +
+ "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
+ "if (counter > 0) then " +
+ "redis.call('pexpire', KEYS[1], ARGV[2]); " +
+ "return 0; " +
+ "else " +
+ "redis.call('del', KEYS[1]); " +
+ "redis.call('publish', KEYS[2], ARGV[1]); " +
+ "return 1; "+
+ "end; " +
+ "return nil;",
+ Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));
+
+ }
输入的参数有:
参数:
KEYS[1](getName()):需要加锁的key,这里需要是字符串类型。
KEYS[2](getChannelName()):redis消息的ChannelName,一个分布式锁对应唯一的一个 channelName:“redisson_lock__channel__{” + getName() + “}”
ARGV[1](LockPubSub.unlockMessage):redis消息体,这里只需要一个字节的标记就可以,主要标记redis的key已经解锁,再结合redis的Subscribe,能唤醒其他订阅解锁消息的客户端线程申请锁。
ARGV[2](internalLockLeaseTime):锁的超时时间,防止死锁
ARGV[3](getLockName(threadId)) :锁的唯一标识,也就是刚才介绍的 id(UUID.randomUUID()) + “:” + threadId
此处lua脚本的作用:
--如果keys[1]不存在,则发布消息,说明已经被解锁了
+if (redis.call('exists', KEYS[1]) == 0) then
+ redis.call('publish', KEYS[2], ARGV[1]);
+ return 1;
+end;
+--key和field不匹配,说明当前客户端线程没有持有锁,不能主动解锁。
+if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
+ return nil;
+end;
+--将value减1,这里主要用在重入锁
+local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
+if (counter > 0) then
+ redis.call('pexpire', KEYS[1], ARGV[2]);
+ return 0;
+else
+--删除key并消息
+ redis.call('del', KEYS[1]);
+ redis.call('publish', KEYS[2], ARGV[1]);
+ return 1;
+end;
+return nil;
(3)删除过期信息
void cancelExpirationRenewal() {
+ Timeout task = expirationRenewalMap.remove(getEntryName());
+ if (task != null) {
+ task.cancel();
+ }
+}
Redis2.6版本之后引入了eval,能够支持lua脚本,更好的保证了redis的原子性,而且redisson采用了大量异步的写法来避免性能所带来的影响。本文只是讲解了下redisson的重入锁,其还有公平锁、联锁、红锁、读写锁等,有兴趣的可以看下。感觉这篇文章写得也不是很好,毕竟netty还没开始学,有些api也不太清楚,希望各位大佬能够建议建议~~
参考:
1.redisson
2.Redis分布式锁的正确实现方式
3.分布式锁的多种实现方式
4.用Redis构建分布式锁
5.基于Redis的分布式锁实现
6.基于Redis实现分布式锁,Redisson使用及源码分析
最近碰到的一个问题,Java代码中写了一个定时器,分布式部署的时候,多台同时执行的话就会出现重复的数据,为了避免这种情况,之前是通过在配置文件里写上可以执行这段代码的IP,代码中判断如果跟这个IP相等,则执行,否则不执行,想想也是一种比较简单的方式吧,但是感觉很low很low,所以改用分布式锁。
\\n目前分布式锁常用的三种方式:1.数据库的锁;2.基于Redis的分布式锁;3.基于ZooKeeper的分布式锁。其中数据库中的锁有共享锁和排他锁,这两种都无法直接解决数据库的单点和可重入的问题,所以,本章还是来讲讲基于Redis的分布式锁,也可以用其他缓存(Memcache、Tair等)来实现。
Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口就可以接入监控。不需要任何SDK或者其他的集成过程。这样做非常适合做虚拟化环境监控系统,比如VM、Docker、Kubernetes等。输出被监控组件信息的HTTP接口被叫做exporter 。目前互联网公司常用的组件大部分都有exporter可以直接使用,比如Varnish、Haproxy、Nginx、MySQL、Linux系统信息(包括磁盘、内存、CPU、网络等等)。
<dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+</dependency>
+<dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-registry-prometheus</artifactId>
+</dependency>
management:
+ endpoints:
+ web:
+ exposure:
+ include: prometheus
+ metrics:
+ tags:
+ application: \${spring.application.name}
之后查看/actuator/prometheus就可以看到
腾讯云上面有个prometheus的服务,接入云原生监控还要配置一个Servicemonitor、PodMonitor等,详细的可以访问腾讯云的官方文档(https://cloud.tencent.com/document/product/1416/56031)
大部分现实场景中,如果每增加一个服务,都需要开发去配置的话,不仅沟通成本高,也导致因配错而起的运维成本很高,采用主动上报方式比较简单,方便规避一些问题。
主动上报除了需要引入spring-boot-starter-actuator、micrometer-registry-prometheus,还要引入simpleclient_pushgateway,三个都是必须的,少一个都不行。
<dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-registry-prometheus</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.prometheus</groupId>
+ <artifactId>simpleclient_pushgateway</artifactId>
+ </dependency>
之后,为了安全考虑,需要把endpoints的所有信息都关闭掉,免得被泄露出去。
management:
+ endpoints:
+ #一定要设为false,防止不小心暴露/actuator相关接口触发安全工单
+ enabled-by-default: false
+ metrics:
+ tags:
+ #要设置应用的名称,否则会聚合到别的项目里面去
+ application: \${spring.application.name}
+ export:
+ prometheus:
+ pushgateway:
+ base-url: http://xxxxx
+ push-rate: 20s
+ job: \${spring.application.name}
+ enabled: true
+ username: xxxx
+ password: xxx
+ grouping-key:
+ instance: \${HOSTNAME}
最好多看一眼/actuator,确认一下是否已经关闭endpoints
大致看了下 simpleclient_pushgateway和spring-boot-starter-actuator的源码,并没有对http的请求发起日志,调试的时候都不知道是不是正常上报过去了,只能采取抓包来研究下。
平均间隔5s左右,符合配置文件里的设置。
对于java来说,常用的dashboard是https://grafana.com/grafana/dashboards/4701,也可以用spring boot的https://grafana.com/grafana/dashboards/6756
除了通用的封装好的指标之外,也可以自定义prometheus的监控。对于Spring Boot来说,只要如下代码即可实现:
@Resource
+ private MeterRegistry meterRegistry;
+
+ public void report() {
+ meterRegistry.counter("指标", "tag的名称", "tag的值").increment();
+ }
也可以通过抓包来查看,以及在push-gateway上看到。
`,35)]))}const r=s(t,[["render",l],["__file","Spring Boot Prometheus使用.html.vue"]]),k=JSON.parse('{"path":"/java/SpringBoot/Spring%20Boot%20Prometheus%E4%BD%BF%E7%94%A8.html","title":"Spring Boot Prometheus使用","lang":"zh-CN","frontmatter":{"description":"Spring Boot Prometheus使用 一、基本原理 Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口就可以接入监控。不需要任何SDK或者其他的集成过程。这样做非常适合做虚拟化环境监控系统,比如VM、Docker、Kubernetes等。输出被监控组件信息的HTTP接口被叫做exp...","head":[["meta",{"property":"og:url","content":"http://www.wenzhihuai.com/java/SpringBoot/Spring%20Boot%20Prometheus%E4%BD%BF%E7%94%A8.html"}],["meta",{"property":"og:site_name","content":"个人博客"}],["meta",{"property":"og:title","content":"Spring Boot Prometheus使用"}],["meta",{"property":"og:description","content":"Spring Boot Prometheus使用 一、基本原理 Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口就可以接入监控。不需要任何SDK或者其他的集成过程。这样做非常适合做虚拟化环境监控系统,比如VM、Docker、Kubernetes等。输出被监控组件信息的HTTP接口被叫做exp..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://github-images.wenzhihuai.com/images/image-20240127202704612.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-01-27T13:52:01.000Z"}],["meta",{"property":"article:modified_time","content":"2024-01-27T13:52:01.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Spring Boot Prometheus使用\\",\\"image\\":[\\"https://github-images.wenzhihuai.com/images/image-20240127202704612.png\\",\\"https://github-images.wenzhihuai.com/images/image-20240127202722776.png\\",\\"https://github-images.wenzhihuai.com/images/image-20240127202830908.png\\",\\"https://github-images.wenzhihuai.com/images/image-20240127202853218.png\\",\\"https://github-images.wenzhihuai.com/images/image-20240127202944264.png\\",\\"https://github-images.wenzhihuai.com/images/image-20240127203001776.png\\",\\"https://github-images.wenzhihuai.com/images/image-20240127203024477.png\\"],\\"dateModified\\":\\"2024-01-27T13:52:01.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zephery\\",\\"url\\":\\"https://wenzhihuai.com/article/\\"}]}"]]},"headers":[{"level":2,"title":"(1)引入库","slug":"_1-引入库","link":"#_1-引入库","children":[]},{"level":2,"title":"(2)修改配置文件","slug":"_2-修改配置文件","link":"#_2-修改配置文件","children":[]},{"level":2,"title":"(3)抓包看一下metrics(可跳过)","slug":"_3-抓包看一下metrics-可跳过","link":"#_3-抓包看一下metrics-可跳过","children":[]}],"git":{"createdTime":1706363521000,"updatedTime":1706363521000,"contributors":[{"name":"zhihuaiwen","email":"zhihuaiwen@tencent.com","commits":1}]},"readingTime":{"minutes":3.49,"words":1046},"filePathRelative":"java/SpringBoot/Spring Boot Prometheus使用.md","localizedDate":"2024年1月27日","excerpt":"\\nPrometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口就可以接入监控。不需要任何SDK或者其他的集成过程。这样做非常适合做虚拟化环境监控系统,比如VM、Docker、Kubernetes等。输出被监控组件信息的HTTP接口被叫做exporter 。目前互联网公司常用的组件大部分都有exporter可以直接使用,比如Varnish、Haproxy、Nginx、MySQL、Linux系统信息(包括磁盘、内存、CPU、网络等等)。
","autoDesc":true}');export{r as comp,k as data}; diff --git a/assets/aop.html-C7ud1qex.js b/assets/aop.html-C7ud1qex.js new file mode 100644 index 00000000..72fd0dc5 --- /dev/null +++ b/assets/aop.html-C7ud1qex.js @@ -0,0 +1,51 @@ +import{_ as e,c as s,d as a,o as n}from"./app-ftEjETWs.js";const l={};function t(r,i){return n(),s("div",null,i[0]||(i[0]=[a(`在通常的开发过程中,我们调用的顺序通常是controller->service-dao,其中,service中包含着太多的业务逻辑,并且还要不断调用dao来实现自身的业务逻辑,经常会导致业务耗时过久,在aop出现之前,方式一般是在函数中开始写一个startTime,结尾再写一个endTime来查看执行该函数的耗时,过多的使用此类方式会导致代码的耦合性太高,不利于管理,于是,AOP(面向切面)出现了。AOP关注的是横向的,而OOP的是纵向。
Spring自2.0版本开始采用@AspectJ注解非常容易的定义一个切面。@AspectJ注解使用AspectJ切点表达式语法进行切点定义,可以通过切点函数、运算符、通配符等高级功能进行切点定义,拥有强大的连接点描述能力。
AOP(Aspect Oriented Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的横向多模块统一控制的一种技术。AOP是OOP的补充,是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP可以分为静态织入与动态织入,静态织入即在编译前将需织入内容写入目标模块中,这样成本非常高。动态织入则不需要改变目标模块。Spring框架实现了AOP,使用注解配置完成AOP比使用XML配置要更加方便与直观。
Aspect:一个模块用来关注多个类的切面。在JAVA EE的应用中,事务是AOP的典型例子。
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.
Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义.
Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
Target(目标对象):代理的目标对象
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程.spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入.
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类Aspect(切面): 是切入点和通知(引介)的结合
Spring实现AOP主要是由IOC容器来负责生成、管理的。其创建的方式有两种:
[1] Before:前置通知,在方法执行之前执行
[2] After:后置通知,在方法执行之后执行
[3] AfterRunning:返回通知,在方法返回结果之后执行
[4] AfterThrowing:异常通知,在方法抛出异常之后执行
[5] Around:环绕通知,围绕着方法执行
其中,环绕通知是最常见的一种通知注解,特别是在缓存的使用中,例如:Spring-Cache中的使用,在service的方法中添加一个cache的注解,通过AOP来拦截,如果缓存中已经存在,则直接返回结果,如果没有,再进行service的访问。
Spring AOP的实现原理是基于动态织入的动态代理技术,而AspectJ则是静态织入,而动态代理技术又分为Java JDK动态代理和CGLIB动态代理,前者是基于反射技术的实现,后者是基于继承的机制实现。Spring AOP 在使用时机上也进行自动化调整,当有接口时会自动选择JDK动态代理技术,如果没有则选择CGLIB技术,当然Spring AOP的底层实现并没有这么简单,为更简便生成代理对象,Spring AOP 内部实现了一个专注于生成代理对象的工厂类,这样就避免了大量的手动编码,这点也是十分人性化的,但最核心的还是动态代理技术。从性能上来说,Spring AOP 虽然无需特殊编译器协助,但性能上并不优于AspectJ的静态织入,这点了解一下即可。
具体的原理请看Spring AOP
网上看别人写了很多入门的例子,自己就不再阐述了,毕竟自己还是菜,下面是关于AOP入门的资料:
我们为什么要使用AOP?
Spring中AOP的实现
关于AOP
下面是自己在个人网站中的使用,主要是用来统计一个方法的执行消耗了多少时间,需要引入aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar的包。
<!--自动扫描自定义切面-->
+ <aop:aspectj-autoproxy/>
/**
+ * 可以使用 @Order 注解指定切面的优先级, 值越小优先级越高
+ */
+@Order(2)
+@Aspect
+@Component
+public class TimeInterceptor {
+}
@Pointcut("execution(* com.myblog.service.impl.BlogServiceImpl.*(..))")
+ public void pointcut() {
+ }
@Before("pointcut()")
+ public void before(JoinPoint jp) {
+ logger.info(jp.getSignature().getName());
+ logger.info("----------前置通知----------");
+ }
@After("pointcut()")
+ public void after(JoinPoint jp) {
+ logger.info("----------最终通知----------");
+ }
这里,特别要注意的是要抛出Throwable异常,否则方法执行报错的时候无法处理也无法查看
@Around("execution(* (com.myblog.service.impl.*+&&!com.myblog.service.impl.AsyncServiceImpl).*(..))")
+ public Object timeAround(ProceedingJoinPoint joinPoint) throws Throwable {
+ Object obj = null;
+ Object[] args = joinPoint.getArgs();
+ long startTime = System.currentTimeMillis();
+ obj = joinPoint.proceed(args);
+ // 获取执行的方法名
+ long endTime = System.currentTimeMillis();
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+ String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
+ // 打印耗时的信息
+ this.printExecTime(methodName, startTime, endTime);
+ return obj;
+ }
@AfterReturning(pointcut = "execution(* com.myblog.service.impl.BlogServiceImpl.*(..))", returning = "result")
+ public void afterReturning(JoinPoint jp, Object result) {
+ logger.info(jp.getSignature().getName());
+ logger.info("结果是:" + result);
+ logger.info("----------返回结果----------");
+ }
@AfterThrowing(pointcut = "execution(* com.myblog.service.impl.BlogServiceImpl.*(..))", throwing = "exp")
+ public void afterThrowing(JoinPoint jp, Exception exp) {
+ logger.info(jp.getSignature().getName());
+ logger.info("异常消息:" + exp.getMessage());
+ logger.info("----------异常通知----------");
+ }
2018-02-04 17:22:46.287 [http-nio-9090-exec-3] INFO com.myblog.aspect.TimeInterceptor - getAllBlog
+2018-02-04 17:22:46.288 [http-nio-9090-exec-3] INFO com.myblog.aspect.TimeInterceptor - ----------前置通知----------
+2018-02-04 17:22:46.288 [http-nio-9090-exec-3] DEBUG com.myblog.dao.BlogMapper - Cache Hit Ratio [com.myblog.dao.BlogMapper]: 0.6
+2018-02-04 17:22:46.288 [http-nio-9090-exec-3] DEBUG com.myblog.dao.BlogMapper - Cache Hit Ratio [com.myblog.dao.BlogMapper]: 0.6666666666666666
+2018-02-04 17:22:46.289 [http-nio-9090-exec-3] INFO com.myblog.cache.EhRedisCache - ===========================Cache L1 (ehcache)
+2018-02-04 17:22:46.292 [http-nio-9090-exec-3] INFO com.myblog.aspect.TimeInterceptor - com.myblog.service.IBlogService.getAllBlog method take time: **5 ms**
+2018-02-04 17:22:46.292 [http-nio-9090-exec-3] INFO com.myblog.aspect.TimeInterceptor - ----------最终通知----------
+2018-02-04 17:22:46.292 [http-nio-9090-exec-3] INFO com.myblog.aspect.TimeInterceptor - getAllBlog
+2018-02-04 17:22:46.292 [http-nio-9090-exec-3] INFO com.myblog.aspect.TimeInterceptor - 结果是:Page{count=true, pageNum=1, pageSize=15, startRow=0, endRow=15, total=462, pages=31, countSignal=false, orderBy='null', orderByOnly=false, reasonable=true, pageSizeZero=true}
+2018-02-04 17:22:46.292 [http-nio-9090-exec-3] INFO com.myblog.aspect.TimeInterceptor - ----------返回结果----------
+2018-02-04 17:22:46.292 [http-nio-9090-exec-3] INFO com.myblog.cache.EhRedisCache - ===========================Cache L1 (ehcache) :{myCache}{com.myblog.service.impl.BlogServiceImpl.getBanner}={[ key = com.myblog.service.impl.BlogServiceImpl.getBanner, value=[com.myblog.model.Blog@2a5de6bc, com.myblog.model.Blog@544159b3, com.myblog.model.Blog@1de1421c, com.myblog.model.Blog@6dbb79bb, com.myblog.model.Blog@28160ab6], version=1, hitCount=2, CreationTime = 1517736161430, LastAccessTime = 1517736166292 ]}
由结果可以看到,整个方法的执行耗时5ms,算是客观吧,如果太大则要对其进行优化。
主要的源码在这:
也可以下载我的博客源码参考参考:
newblog
在通常的开发过程中,我们调用的顺序通常是controller->service-dao,其中,service中包含着太多的业务逻辑,并且还要不断调用dao来实现自身的业务逻辑,经常会导致业务耗时过久,在aop出现之前,方式一般是在函数中开始写一个startTime,结尾再写一个endTime来查看执行该函数的耗时,过多的使用此类方式会导致代码的耦合性太高,不利于管理,于是,AOP(面向切面)出现了。AOP关注的是横向的,而OOP的是纵向。
","autoDesc":true}');export{h as comp,o as data}; diff --git a/assets/app-ftEjETWs.js b/assets/app-ftEjETWs.js new file mode 100644 index 00000000..f2025531 --- /dev/null +++ b/assets/app-ftEjETWs.js @@ -0,0 +1,330 @@ +var Xh=Object.defineProperty;var ep=(e,t,n)=>t in e?Xh(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var ui=(e,t,n)=>ep(e,typeof t!="symbol"?t+"":t,n);/** +* @vue/shared v3.5.13 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**//*! #__NO_SIDE_EFFECTS__ */function wa(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const xe={},Un=[],$t=()=>{},tp=()=>!1,ql=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Ba=e=>e.startsWith("onUpdate:"),Me=Object.assign,Ca=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},np=Object.prototype.hasOwnProperty,_e=(e,t)=>np.call(e,t),re=Array.isArray,Al=e=>Ur(e)==="[object Map]",lp=e=>Ur(e)==="[object Set]",ie=e=>typeof e=="function",ze=e=>typeof e=="string",ol=e=>typeof e=="symbol",De=e=>e!==null&&typeof e=="object",iu=e=>(De(e)||ie(e))&&ie(e.then)&&ie(e.catch),rp=Object.prototype.toString,Ur=e=>rp.call(e),ip=e=>Ur(e).slice(8,-1),ap=e=>Ur(e)==="[object Object]",xa=e=>ze(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Gn=wa(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Gr=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},op=/-(\w)/g,at=Gr(e=>e.replace(op,(t,n)=>n?n.toUpperCase():"")),sp=/\B([A-Z])/g,Pn=Gr(e=>e.replace(sp,"-$1").toLowerCase()),Wl=Gr(e=>e.charAt(0).toUpperCase()+e.slice(1)),di=Gr(e=>e?`on${Wl(e)}`:""),dn=(e,t)=>!Object.is(e,t),fi=(e,...t)=>{for(let n=0;n留空
+`,r:{minutes:.02,words:5},t:"关于我",y:"a"}}],["/bigdata/",{loader:()=>C(()=>import("./index.html-D1R9zQcp.js"),[]),meta:{d:156672164e4,e:` +`,r:{minutes:0,words:1},t:"Spark",y:"a"}}],["/cloudnative/",{loader:()=>C(()=>import("./index.html-DlwW5Vje.js"),[]),meta:{d:156672164e4,t:"",y:"a"}}],["/database/",{loader:()=>C(()=>import("./index.html-DWXiwN5o.js"),[]),meta:{d:1706182936e3,e:` +目录留空
+`,r:{minutes:.02,words:5},t:"Database",y:"a"}}],["/donate/",{loader:()=>C(()=>import("./index.html-D4WG7yPk.js"),[]),meta:{d:1706169851e3,e:` +`,r:{minutes:.04,words:12},t:"微信支付",y:"a"}}],["/interesting/",{loader:()=>C(()=>import("./index.html-B1Akg086.js"),[]),meta:{d:171606205e4,e:` +`,r:{minutes:.02,words:6},t:"好玩的",y:"a"}}],["/interesting/chatgpt.html",{loader:()=>C(()=>import("./chatgpt.html-BgeRWiYw.js"),[]),meta:{d:1706186538e3,e:` +想体验的可以去微信上搜索【旅行的树】公众号。
+想要在生成类的时候,自动带上author和时间,效果如下:
`,r:{minutes:1.33,words:399},t:"Jetbrains Idea设置",y:"a"}}],["/interesting/starcraft-ai.html",{loader:()=>C(()=>import("./starcraft-ai.html-C018rwGF.js"),[]),meta:{d:1706288023e3,e:` +非深度学习人士,仅仅是兴趣驱动,可能有很多不对的地方,也欢迎大家指正。这里主要讲解如何将AI运行起来、机器人对战、人机对战、天梯上分等等,希望能对大家的人工智能工程道路上有所帮助。
`,r:{minutes:2.72,words:817},t:"StarCraft Ⅱ 人工智能教程",y:"a"}}],["/interesting/tesla.html",{loader:()=>C(()=>import("./tesla.html-K0riacrL.js"),[]),meta:{d:1706186538e3,e:` +如果您还没有 Tesla 账户,请创建账户。验证您的电子邮件并设置多重身份验证。
`,r:{minutes:4.5,words:1351},t:"Tesla api",y:"a"}}],["/interesting/%E4%B8%80%E4%BA%9B%E4%B8%8D%E5%B8%B8%E7%94%A8%E4%BD%86%E5%BE%88%E5%AE%9E%E7%94%A8%E7%9A%84%E5%91%BD%E4%BB%A4.html",{loader:()=>C(()=>import("./一些不常用但很实用的命令.html-BtEABYaE.js"),[]),meta:{d:1708752707e3,e:` +curl -w "TCP handshake: %{time_connect}, SSL handshake: %{time_appconnect}\\n" -so /dev/null https://zhls.qq.com/test-nginx
+在监控领域,Prometheus 与 Grafana 常被认为是无敌组合。然而,随着成本的增加,不少人开始审慎考虑其他替代方案。本文将深入探讨 Prometheus 的特点,并讨论 Grafana 中关键参数 interval
和 for
的意义及其影响。此外,还将分析 OLAP 数据库作为监控数据持久性存储的优势。
先找到小程序保存的地址,一般先找到微信的文件管理下
`,r:{minutes:.82,words:246},t:"小程序反编译",y:"a"}}],["/interesting/%E5%B9%BF%E5%B7%9E%E5%9B%BE%E4%B9%A6%E9%A6%86%E5%80%9F%E9%98%85%E6%8A%93%E5%8F%96.html",{loader:()=>C(()=>import("./广州图书馆借阅抓取.html-CPwBtyqR.js"),[]),meta:{d:1706596625e3,e:` +欢迎访问我的个人网站,要是能在GitHub上对网站源码给个star就更好了。
`,r:{minutes:6.83,words:2049},t:"广州图书馆借阅抓取",y:"a"}}],["/interesting/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7%E6%95%B4%E7%90%86.html",{loader:()=>C(()=>import("./开发工具整理.html-BKoUISo4.js"),[]),meta:{d:171008157e4,e:` +mac端:https://github.com/shadowsocks/ShadowsocksX-NG
+win端:https://github.com/shadowsocks/shadowsocks-windows
+安卓:https://github.com/shadowsocks/shadowsocks-android
在腾讯广告工作期间,我主要负责小程序电商与广告业务,见证了互联网电商行业的剧变,特别是众多电商公司纷纷拥抱私域流量,直播带货成为新风尚,广告投入也在持续增加。通过这些经历,我积累了不少关于互联网电商的经验,并萌生了尝试电商副业的想法。
`,r:{minutes:10.01,words:3002},t:"程序员副业探索之电商",y:"a"}}],["/interview/tiktok2023.html",{loader:()=>C(()=>import("./tiktok2023.html-BxK5gJWF.js"),[]),meta:{d:1680437706e3,e:`tt一面:
+1.全程项目
+2.lc3,最长无重复子串,滑动窗口解决
tt二面:
+全程基础,一直追问
+1.java内存模型介绍一下
+2.volatile原理
+3.内存屏障,使用场景?(我提了在gc中有使用)
+4.gc中具体是怎么使用内存屏障的,详细介绍
+5.cms和g1介绍一下,g1能取代cms吗?g1和cms各自的使用场景是什么
+6.线程内存分配方式,tlab相关
+7.happens-before介绍一下
+8.总线风暴是什么,怎么解决
+9.网络相关,time_wait,close_wait产生原因,带来的影响,怎么解决
+10.算法题,给你一个数字n,求用这个n的各位上的数字组合出最大的小于n的数字m,注意边界case,如2222
+11.场景题,设计一个微信朋友圈,功能包含feed流拉取,评论,转发,点赞等操作
服务器被黑,进程占用率高达99%,查看了一下,是/usr/sbin/bashd的原因,怎么删也删不掉,使用ps查看:
+
+stratum+tcp://get.bi-chi.com:3333 -u 47EAoaBc5TWDZKVaAYvQ7Y4ZfoJMFathAR882gabJ43wHEfxEp81vfJ3J3j6FQGJxJNQTAwvmJYS2Ei8dbkKcwfPFst8FhG
在这个简短的教程中,我们将了解如何在 Spring Boot 应用程序中利用虚拟线程的强大功能。
`,r:{minutes:4.83,words:1448},t:"在 Spring 6 中使用虚拟线程",y:"a"}}],["/java/%E5%9F%BA%E4%BA%8Ekubernetes%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E9%99%90%E6%B5%81.html",{loader:()=>C(()=>import("./基于kubernetes的分布式限流.html-B1ZIsZdu.js"),[]),meta:{d:1649482551e3,e:` +做为一个数据上报系统,随着接入量越来越大,由于 API 接口无法控制调用方的行为,因此当遇到瞬时请求量激增时,会导致接口占用过多服务器资源,使得其他请求响应速度降低或是超时,更有甚者可能导致服务器宕机。
`,r:{minutes:7.18,words:2155},t:"基于kubernetes的分布式限流",y:"a"}}],["/java/%E6%B5%81%E7%A8%8B%E7%BC%96%E6%8E%92LiteFlow.html",{loader:()=>C(()=>import("./流程编排LiteFlow.html-CvCVGQll.js"),[]),meta:{d:1725814553e3,e:` +LiteFlow真的是相见恨晚啊,之前做过的很多系统,都会用各种if else,switch这些来解决不同业务方提出的问题,有时候还要“切一个分支”来搞这些额外的事情,把代码搞得一团糟,毫无可读性而言。如何打破僵局?LiteFlow为解耦逻辑而生,为编排而生,在使用LiteFlow之后,你会发现打造一个低耦合,灵活的系统会变得易如反掌!
`,r:{minutes:7.11,words:2134},t:"流程编排LiteFlow",y:"a"}}],["/java/%E9%AB%98%E5%8F%AF%E7%94%A8.html",{loader:()=>C(()=>import("./高可用.html-DjOPo7tP.js"),[]),meta:{d:171008157e4,e:` +4个9(99.99)
++ | Sentinel | +Hystrix(维护状态) | +Resilience4j(Spring推荐) | +
---|---|---|---|
隔离策略 | +信号量隔离(并发线程数限流) | +线程池隔离/信号量隔离 | +信号量隔离 | +
熔断降级策略 | +基于响应时间、异常比率、异常数 | +基于异常比率 | +基于异常比率、响应时间 | +
实时统计实现 | +滑动窗口 | +滑动窗口 | +Ring Bit Buffer | +
动态规则配置 | +支持多种数据源 | +支持多种数据源 | +有限支持 | +
扩展性 | +支持多种数据源 | +支持多种数据源 | +有限支持 | +
限流 | +基于 QPS,支持基于调用关系的限流 | +有限的支持 | +Rate Limiter | +
流量整形 | +支持预热模式、匀速器模式、预热排队模式 | +不支持 | +简单的 Rate Limiter 模式 | +
系统的自适应保护 | +支持 | +不支持 | +不支持 | +
控制台 | +提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 | +简单的监控查看 | +不提供控制台,可对接其他监控系统 | +
QPS
+高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。 高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。
`,r:{minutes:4.28,words:1285},t:"高并发",y:"a"}}],["/java/%E9%AB%98%E6%80%A7%E8%83%BD.html",{loader:()=>C(()=>import("./高性能.html-CN7rmRWu.js"),[]),meta:{d:171008157e4,e:` +响应时间
+性能直接影响用户的感官体验,访问一个系统,如果超过5秒没有响应,绝大数用户会选择离开。
`,r:{minutes:3.03,words:909},t:"高性能",y:"a"}}],["/java/%E9%AB%98%E6%80%A7%E8%83%BD%E9%AB%98%E5%B9%B6%E5%8F%91%E9%AB%98%E5%8F%AF%E7%94%A8%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%9D%E8%80%83.html",{loader:()=>C(()=>import("./高性能高并发高可用的一些思考.html-DLPHuyVJ.js"),[]),meta:{d:1706983034e3,e:` +TODO待补充
+公司主要要开发自己的paas平台,集成了Jenkins,真的是遇到了很多很多困难,特别是在api调用的权限这一块,这里,把自己遇到的一些坑的解决方法做一下笔记吧。当然,首先要讲的,就是如何在开启安全的情况下进行API调用。
`,r:{minutes:3.98,words:1195},t:"Jenkins的一些笔记",y:"a"}}],["/kubernetes/Kubernetes%E5%AE%B9%E5%99%A8%E6%97%A5%E5%BF%97%E6%94%B6%E9%9B%86.html",{loader:()=>C(()=>import("./Kubernetes容器日志收集.html-SN4rN7dm.js"),[]),meta:{d:1708751576e3,e:` +日志从传统方式演进到容器方式的过程就不详细讲了,可以参考一下这篇文章Docker日志收集最佳实践,由于容器的漂移、自动伸缩等特性,日志收集也就必须使用新的方式来实现,Kubernetes官方给出的方式基本是这三种:原生方式、DaemonSet方式和Sidecar方式。
`,r:{minutes:10.62,words:3187},t:"Kubernetes容器日志收集",y:"a"}}],["/kubernetes/",{loader:()=>C(()=>import("./index.html-BEv74XKv.js"),[]),meta:{d:156672164e4,e:` +包含CICD、Kubernetes
+`,r:{minutes:.03,words:8},t:"目录",y:"a"}}],["/kubernetes/request_limit.html",{loader:()=>C(()=>import("./request_limit.html-CBNYeI2L.js"),[]),meta:{d:1708083988e3,e:` +我们都知道 Kubernetes 中最小的原子调度单位是Pod,那么就意味着资源管理和资源调度相关的属性都应该Pod对象的字段,其中我们最常见的就是 Pod 的 CPU 和内存配置,而为了实现 Kubernetes 集群中资源的有效调度和充分利用,Kubernetes采用 requests 和 limits 两种限制类型来对CPU和内存资源进行容器粒度的分配。
`,r:{minutes:2.21,words:663},t:"Kubernetes之request 和 limit详解",y:"a"}}],["/kubernetes/spark%20on%20k8s%20operator.html",{loader:()=>C(()=>import("./spark on k8s operator.html-D9Tpznzk.js"),[]),meta:{d:1655036363e3,e:` +Spark operator是一个管理Kubernetes上的Apache Spark应用程序生命周期的operator,旨在像在Kubernetes上运行其他工作负载一样简单的指定和运行spark应用程序。
`,r:{minutes:3.51,words:1054},t:"spark on k8s operator",y:"a"}}],["/life/2017.html",{loader:()=>C(()=>import("./2017.html-DzgcI9cv.js"),[]),meta:{d:157995829e4,e:` +2016年6月,大三,考完最后一科,默默的看着大四那群人一个一个的走了,想着想着,明年毕业的时候,自己将会失去你所拥有的一切,大学那群认识的人,可能这辈子都不会见到了——致远离家乡出省读书的娃。然而,我并没有想到,自己失去的,不仅仅是那群人,几乎是一切。。。。
+17年1月,因为受不了北京雾霾(其实是次因),从实习的公司跑路了,很不舍,只是,感觉自己还有很多事要做,很多梦想要去实现,毕竟,看着身边的人,去了美团,拿着15k的月薪,有点不平衡,感觉他也不是很厉害吧,我努力努力10k应该没问题吧?
年底老姐结婚,跑回家去了,忙着一直没写,本来想回深圳的时候空余的时候写写,没想到一直加班,嗯,9点半下班那种,偶尔也趁着间隙的时间,一段一段的凑着吧。
`,r:{minutes:4.35,words:1306},t:"2018",y:"a"}}],["/life/2019.html",{loader:()=>C(()=>import("./2019.html-Df2iUZPh.js"),[]),meta:{d:157995829e4,e:` +2019,本命年,1字头开头的年份,就这么过去了,迎来了2开头的十年,12月过的不是很好,每隔几天就吵架,都没怎么想起写自己的年终总结了,对这个跨年也不是很重视,貌似有点浑浑噩噩的样子。今天1号,就继续来公司学习学习,就写写文章吧。
+19年一月,没发生什么事,对老板也算是失望,打算过完春节就出去外面试试,完全的架构错误啊,怎么守得了,然后开了个年会,没什么好看的,唯一的例外就是看见漂漂亮亮的她,可惜不敢搭话,就这么的呗。2月以奇奇怪怪的方式走到了一起,开心,又有点伤心,然后就开始了这个欢喜而又悲剧的本命年。
jfaowejfoewj
+`,r:{minutes:0,words:1},t:"",y:"a"}}],["/stock/",{loader:()=>C(()=>import("./index.html-nZcQpO1z.js"),[]),meta:{d:1719930789e3,e:` +`,r:{minutes:3.15,words:946},t:"股票预测",y:"a"}}],["/stock/%E8%B5%9B%E5%8A%9B%E6%96%AF.html",{loader:()=>C(()=>import("./赛力斯.html-BrY99iGU.js"),[]),meta:{d:1719930789e3,e:` +update
+`,r:{minutes:.01,words:4},t:"赛力斯",y:"a"}}],["/system-design/feed.html",{loader:()=>C(()=>import("./feed.html-BcapapFL.js"),[]),meta:{d:171008157e4,e:` +https://blog.csdn.net/weixin_45583158/article/details/128195940
`,r:{minutes:.02,words:7},t:"Feed系统设计",y:"a"}}],["/about-the-author/personal-life/2024-07-24.html",{loader:()=>C(()=>import("./2024-07-24.html-9GUkpnJj.js"),[]),meta:{d:1721836332e3,e:` +新买了台air m3,午夜色的,心心念念了好久
+`,r:{minutes:.34,words:102},t:"2024-07-24",y:"a"}}],["/about-the-author/personal-life/2024-11-09%E4%B8%8A%E6%B5%B7%E8%BF%AA%E6%96%AF%E5%B0%BC.html",{loader:()=>C(()=>import("./2024-11-09上海迪斯尼.html-CU80Bk9D.js"),[]),meta:{d:1731254136e3,e:` +`,r:{minutes:.04,words:12},t:"2024-11-09上海迪斯尼",y:"a"}}],["/about-the-author/personal-life/wewe.html",{loader:()=>C(()=>import("./wewe.html-BLY5_jfL.js"),[]),meta:{d:1706244827e3,e:` +安安静静的开发,搞点好玩的。
+`,r:{minutes:.07,words:21},t:"自我介绍",y:"a"}}],["/about-the-author/works/%E4%B8%AA%E4%BA%BA%E4%BD%9C%E5%93%81.html",{loader:()=>C(()=>import("./个人作品.html-DDpX9I3v.js"),[]),meta:{d:1706349103e3,e:` +`,r:{minutes:.05,words:14},t:"小程序",y:"a"}}],["/bigdata/spark/elastic-spark.html",{loader:()=>C(()=>import("./elastic-spark.html-Cz2jVT52.js"),[]),meta:{d:165157815e4,e:` +Hadoop允许Elasticsearch在Spark中以两种方式使用:通过自2.1以来的原生RDD支持,或者通过自2.0以来的Map/Reduce桥接器。从5.0版本开始,elasticsearch-hadoop就支持Spark 2.0。目前spark支持的数据源有:
+(1)文件系统:LocalFS、HDFS、Hive、text、parquet、orc、json、csv
+(2)数据RDBMS:mysql、oracle、mssql
+(3)NOSQL数据库:HBase、ES、Redis
+(4)消息对象:Redis
直接用idea下载代码https://github.com/elastic/elasticsearch.git
+
本文基于elasticsearch8.1。在es搜索中,经常会使用索引+星号,采用时间戳来进行搜索,比如aaaa-*在es中是怎么处理这类请求的呢?是对匹配的进行搜索呢还是仅仅根据时间找出索引,然后才遍历索引进行搜索。在了解其原理前先了解一些基本知识。
`,r:{minutes:8.92,words:2677},t:"【elasticsearch】搜索过程详解",y:"a"}}],["/database/elasticsearch/%E5%9F%BA%E7%A1%80.html",{loader:()=>C(()=>import("./基础.html-Bsofr9G8.js"),[]),meta:{d:1708786978e3,e:` +1、什么是Elasticsearch:
+Elasticsearch 是基于 Lucene 的 Restful 的分布式实时全文搜索引擎,每个字段都被索引并可被搜索,可以快速存储、搜索、分析海量的数据。
+全文检索是指对每一个词建立一个索引,指明该词在文章中出现的次数和位置。当查询时,根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。
在MySQL 5.6开始,就已经默认禁用查询缓存了。在MySQL 8.0,就已经删除查询缓存功能了。
`,r:{minutes:.34,words:101},t:"数据库缓存",y:"a"}}],["/database/mysql/%E8%A1%8C%E9%94%81%EF%BC%8C%E8%A1%A8%E9%94%81%EF%BC%8C%E6%84%8F%E5%90%91%E9%94%81.html",{loader:()=>C(()=>import("./行锁,表锁,意向锁.html-DwU2qcUM.js"),[]),meta:{d:171008157e4,e:` +`,r:{minutes:.03,words:9},t:"行锁,表锁,意向锁",I:!1,y:"a"}}],["/database/redis/RedissonLock%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.html",{loader:()=>C(()=>import("./RedissonLock分布式锁源码分析.html-DBlO1MVO.js"),[]),meta:{d:1707204155e3,e:` +最近碰到的一个问题,Java代码中写了一个定时器,分布式部署的时候,多台同时执行的话就会出现重复的数据,为了避免这种情况,之前是通过在配置文件里写上可以执行这段代码的IP,代码中判断如果跟这个IP相等,则执行,否则不执行,想想也是一种比较简单的方式吧,但是感觉很low很low,所以改用分布式锁。
+目前分布式锁常用的三种方式:1.数据库的锁;2.基于Redis的分布式锁;3.基于ZooKeeper的分布式锁。其中数据库中的锁有共享锁和排他锁,这两种都无法直接解决数据库的单点和可重入的问题,所以,本章还是来讲讲基于Redis的分布式锁,也可以用其他缓存(Memcache、Tair等)来实现。
系统的性能指标一般包括响应时间、延迟时间、吞吐量,并发用户数和资源利用率等。在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。
+缓存常用语:
+数据不一致性、缓存更新机制、缓存可用性、缓存服务降级、缓存预热、缓存穿透
+可查看Redis实战(一) 使用缓存合理性
mac是typec的,用了个转接头+网线直连主机,初始化的时候跟下面一致,默认都是自动的
`,r:{minutes:1.72,words:515},t:"mac通过网线连接主机(fnOS)",y:"a"}}],["/interesting/mini%E4%B8%BB%E6%9C%BA/%E4%B9%B0%E4%BA%86%E4%B8%AAmini%E4%B8%BB%E6%9C%BA.html",{loader:()=>C(()=>import("./买了个mini主机.html-DMCiuYMs.js"),[]),meta:{d:1730035985e3,e:` +虽然有苹果的电脑,但是在安装一些软件的时候,总想着能不能有一个小型的服务器,免得各种设置导致 Mac 出现异常。整体上看了一些小型主机,也看过苹果的 Mac mini,但是发现它太贵了,大概要 3000 多,特别是如果要更高配置的话,价格会更高,甚至更贵。所以,我就考虑一些别的小型主机。也看了一些像 NUC 这些服务器,但是觉得还是太贵了。于是我自己去淘宝搜索,找到了这一款 N100 版的主机。
`,r:{minutes:5.88,words:1763},t:"买了个mini主机",y:"a"}}],["/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/1.%E5%8E%86%E5%8F%B2%E4%B8%8E%E6%9E%B6%E6%9E%84.html",{loader:()=>C(()=>import("./1.历史与架构.html-B9tl8qmz.js"),[]),meta:{d:1579574285e3,e:` +各位大佬瞄一眼我的个人网站呗 。如果觉得不错,希望能在GitHub上麻烦给个star,GitHub地址https://github.com/Zephery/newblog 。
+大学的时候萌生的一个想法,就是建立一个个人网站,前前后后全部推翻重构了4、5遍,现在终于能看了,下面是目前的首页。
不知不觉,自建站https://www.wenzhihuai.com已经接近8年了,大二的时候开启使用ssh+jsp框架来做了一个自己的网站,完全自写前后端,过程中不断进化,改用ssm,整合es做文章搜索,加kafka,加redis缓存,整体上对个人来说还是学习到了不少东西。但是随之而来的问题也不少,被挖矿攻击、服务器被黑等等。有时候因为要用服务器搞一些别的东西,直接没有备份数据库就重装,导致不少文章丢失,虽然别的平台可能零散分布。
`,r:{minutes:5.78,words:1733},t:"10.历时8年最终改版",y:"a"}}],["/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/2.Lucene%E7%9A%84%E4%BD%BF%E7%94%A8.html",{loader:()=>C(()=>import("./2.Lucene的使用.html-9FjHGi9W.js"),[]),meta:{d:1579574285e3,e:` +首先,帮忙点击一下我的网站http://www.wenzhihuai.com/ 。谢谢啊,如果可以,GitHub上麻烦给个star,以后面试能讲讲这个项目,GitHub地址https://github.com/Zephery/newblog 。
`,r:{minutes:6.82,words:2046},t:"2.Lucene的使用",y:"a"}}],["/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/3.%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1.html",{loader:()=>C(()=>import("./3.定时任务.html-NSgzVWXM.js"),[]),meta:{d:1579574285e3,e:` +先看一下Quartz的架构图:
+`,r:{minutes:3.55,words:1064},t:"3.定时任务",y:"a"}}],["/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/4.%E6%97%A5%E5%BF%97%E7%B3%BB%E7%BB%9F.html",{loader:()=>C(()=>import("./4.日志系统.html-BH3nB-v9.js"),[]),meta:{d:1579574285e3,e:` +欢迎访问我的网站http://www.wenzhihuai.com/ 。感谢,如果可以,希望能在GitHub上给个star,GitHub地址https://github.com/Zephery/newblog 。
`,r:{minutes:9.64,words:2891},t:"4.日志系统.md",y:"a"}}],["/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/5.%E5%B0%8F%E9%9B%86%E7%BE%A4%E9%83%A8%E7%BD%B2.html",{loader:()=>C(()=>import("./5.小集群部署.html-BN5IQG4o.js"),[]),meta:{d:1579574285e3,e:` +欢迎访问我的个人网站O(∩_∩)O哈哈~希望大佬们能给个star,个人网站网址:http://www.wenzhihuai.com,个人网站代码地址:https://github.com/Zephery/newblog。
+洋洋洒洒的买了两个服务器,用来学习分布式、集群之类的东西,整来整去,感觉分布式这种东西没人指导一下真的是太抽象了,先从网站的分布式部署一步一步学起来吧,虽然网站本身的访问量不大==。
先来回顾一下上一篇的小集群架构,tomcat集群,nginx进行反向代理,服务器异地:
`,r:{minutes:5.77,words:1732},t:"6.数据库备份",y:"a"}}],["/interesting/%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99/7.%E9%82%A3%E4%BA%9B%E7%89%9B%E9%80%BC%E7%9A%84%E6%8F%92%E4%BB%B6.html",{loader:()=>C(()=>import("./7.那些牛逼的插件.html-Br7NfWE4.js"),[]),meta:{d:1579574285e3,e:` +欢迎访问我的网站http://www.wenzhihuai.com/ 。感谢,如果可以,希望能在GitHub上给个star,GitHub地址https://github.com/Zephery/newblog 。
+建站的一开始,我也想自己全部实现,各种布局,各种炫丽的效果,想做点能让大家佩服的UI出来,但是,事实上,自己作为专注Java的程序员,前端的东西一碰脑子就有“我又不是前端,浪费时间在这合适么?”这种想法,捣鼓来捣鼓去,做出的东西实在是没法看,我就觉得,如果自己的“产品”连自己都看不下去了,那还好意思给别人看?特别是留言板那块,初版的页面简直low的要死。所以,还是踏踏实实的“站在巨人的肩膀上”吧,改用别人的插件。但不要纯粹的使用别人的博客模板了,如hexo,wordpress这些,就算是自己拼凑过来的也比这些强。下面是本博客中所用到的插件,给大家介绍介绍,共同学习学习。
+本站主要用到的插件有:
+1.wowslider
+2.畅言
+3.Editor.md
+4.highchart、echart
+5.百度分享
+6.waterfall.js
+7.心知天气
+8.标签云
jofjweoiaejof
+`,r:{minutes:.05,words:15},t:"个人网站",i:"pen",O:1,y:"a"}}],["/java/JVM/JVM%E8%B0%83%E4%BC%98%E5%8F%82%E6%95%B0.html",{loader:()=>C(()=>import("./JVM调优参数.html-BtZgPFK4.js"),[]),meta:{d:1579957849e3,e:` +JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统 下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
`,r:{minutes:9.13,words:2739},t:"JVM调优参数",y:"a"}}],["/java/JVM/Java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.html",{loader:()=>C(()=>import("./Java内存模型.html-Bis3Hglq.js"),[]),meta:{d:1708075355e3,e:` +本文转载自深入理解JVM-内存模型(jmm)和GC
`,r:{minutes:46.11,words:13833},t:"Java内存模型(JMM)",y:"a"}}],["/java/JVM/",{loader:()=>C(()=>import("./index.html-B9smc87Y.js"),[]),meta:{d:1708056026e3,r:{minutes:.04,words:11},t:"JVM",i:"pen",O:100,y:"a"}}],["/java/JVM/cms.html",{loader:()=>C(()=>import("./cms.html-Dfw5I__q.js"),[]),meta:{d:1708056026e3,e:` +JDK14已经将CMS回收器完全移除,这里只需要记住它的缺点即可。
+CPU资源消耗:CMS垃圾回收器在运行过程中会与应用线程并发执行,这可能会导致较高的CPU资源消耗。
`,r:{minutes:.73,words:218},t:"CMS",y:"a"}}],["/java/JVM/g1.html",{loader:()=>C(()=>import("./g1.html-CcgAHdWl.js"),[]),meta:{d:1708056026e3,e:` +`,r:{minutes:39.5,words:11851},t:"G1",y:"a"}}],["/java/JVM/jvm(java%E8%99%9A%E6%8B%9F%E6%9C%BA).html",{loader:()=>C(()=>import("./jvm(java虚拟机).html-CpFuSPpy.js"),[]),meta:{d:171008157e4,e:`JVM是Java Virtual Machine(Java虚拟机)的缩写,是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟计算机功能来实现的,JVM屏蔽了与具体操作系统平台相关的信息,Java程序只需生成在Java虚拟机上运行的字节码,就可以在多种平台上不加修改的运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。
`,r:{minutes:12.39,words:3717},t:"JVM(java虚拟机)",i:"pen",I:!1,y:"a"}}],["/java/JVM/zgc.html",{loader:()=>C(()=>import("./zgc.html-Cj33F57G.js"),[]),meta:{d:1708056026e3,e:` +本文转载自12 张图带你彻底理解 ZGC
`,r:{minutes:11.65,words:3494},t:"ZGC",y:"a"}}],["/java/JVM/%E4%B8%80%E6%AC%A1jvm%E8%B0%83%E4%BC%98%E8%BF%87%E7%A8%8B.html",{loader:()=>C(()=>import("./一次jvm调优过程.html-zaM0dK5_.js"),[]),meta:{d:1579573736e3,e:` +前端时间把公司的一个分布式定时调度的系统弄上了容器云,部署在kubernetes,在容器运行的动不动就出现问题,特别容易jvm溢出,导致程序不可用,终端无法进入,日志一直在刷错误,kubernetes也没有将该容器自动重启。业务方基本每天都在反馈task不稳定,后续就协助接手看了下,先主要讲下该程序的架构吧。
+该程序task主要分为三个模块:
+console进行一些cron的配置(表达式、任务名称、任务组等);
+schedule主要从数据库中读取配置然后装载到quartz再然后进行命令下发;
+client接收任务执行,然后向schedule返回运行的信息(成功、失败原因等)。
+整体架构跟github上开源的xxl-job类似,也可以参考一下。
垃圾回收器 | +采用的GC算法 | +代次 | +
---|---|---|
Serial | +复制 | +新生代 | +
Parallel | +复制 | +新生代 | +
ParNew | +复制 | +新生代 | +
CMS (Concurrent Mark-Sweep) | +标记-清除 | +老年代 | +
G1 (Garbage-First) | +标记-整理 | +老年代 | +
ZGC (Z Garbage Collector) | +标记-整理 | +老年代 | +
Shenandoah | +标记-复制(独立的全局复制阶段) | +老年代 | +
在项目开发过程中、生产环境中,任何问题的解决、性能的调优总结下来都是三个步骤,即发现问题、定位问题、解决问题,本文将从这个步骤入手,详细阐述内存溢出(OOM、OutOfMemeory)、CPU飙高、GC频繁等JVM问题的排查、定位,以及调优。
`,r:{minutes:.7,words:210},t:"JVM调优思路",I:!1,y:"a"}}],["/java/SpringBoot/",{loader:()=>C(()=>import("./index.html-DGy09Ap4.js"),[]),meta:{d:1708056026e3,r:{minutes:.04,words:11},t:"SpringBoot",i:"pen",O:100,y:"a"}}],["/java/SpringBoot/Spring%20Boot%20Prometheus%E4%BD%BF%E7%94%A8.html",{loader:()=>C(()=>import("./Spring Boot Prometheus使用.html-CvPEGSZS.js"),[]),meta:{d:1706363521e3,e:` +Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口就可以接入监控。不需要任何SDK或者其他的集成过程。这样做非常适合做虚拟化环境监控系统,比如VM、Docker、Kubernetes等。输出被监控组件信息的HTTP接口被叫做exporter 。目前互联网公司常用的组件大部分都有exporter可以直接使用,比如Varnish、Haproxy、Nginx、MySQL、Linux系统信息(包括磁盘、内存、CPU、网络等等)。
`,r:{minutes:3.49,words:1046},t:"Spring Boot Prometheus使用",y:"a"}}],["/java/SpringBoot/aop.html",{loader:()=>C(()=>import("./aop.html-C7ud1qex.js"),[]),meta:{d:1706596625e3,e:` +在通常的开发过程中,我们调用的顺序通常是controller->service-dao,其中,service中包含着太多的业务逻辑,并且还要不断调用dao来实现自身的业务逻辑,经常会导致业务耗时过久,在aop出现之前,方式一般是在函数中开始写一个startTime,结尾再写一个endTime来查看执行该函数的耗时,过多的使用此类方式会导致代码的耦合性太高,不利于管理,于是,AOP(面向切面)出现了。AOP关注的是横向的,而OOP的是纵向。
`,r:{minutes:7.17,words:2150},t:"AOP",y:"a"}}],["/java/SpringBoot/webflux.html",{loader:()=>C(()=>import("./webflux.html-KjGFiVkN.js"),[]),meta:{d:1706349103e3,e:` +ChatGpt:
+Servlet 3.1 和 WebFlux 虽然底层都使用了 Java 的 NIO(非阻塞 IO),但是他们的编程模型和如何使用 NIO 是不同的,这也是导致他们性能差异的原因。
1)Java BIO : 同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。
`,r:{minutes:1.96,words:587},t:"JAVA NIO",y:"a"}}],["/java/%E7%BA%BF%E7%A8%8B/",{loader:()=>C(()=>import("./index.html-CMe--mJk.js"),[]),meta:{d:1708056026e3,r:{minutes:.04,words:12},t:"线程",i:"pen",O:100,y:"a"}}],["/java/%E7%BA%BF%E7%A8%8B/synchronized.html",{loader:()=>C(()=>import("./synchronized.html-B2MTfSju.js"),[]),meta:{d:1708075355e3,e:` +偏向锁在JDK 15后已经废弃
+Volatile是Java中的一种轻量级同步机制,用于保证变量的可见性和禁止指令重排。当一个变量被声明为Volatile类型时,任何修改该变量的操作都会立即被所有线程看到。也就是说,Volatile修饰的变量在每次修改时都会强制将修改刷新到主内存中,具有很好的可见性和线程安全性。
`,r:{minutes:.54,words:161},t:"volatile",y:"a"}}],["/java/%E7%BD%91%E7%BB%9C/IP%E3%80%81HTTP%E3%80%81HTTPS%E3%80%81HTTP2.0.html",{loader:()=>C(()=>import("./IP、HTTP、HTTPS、HTTP2.0.html-DvomVRAD.js"),[]),meta:{d:1707203159e3,e:` +HTTP,全称超文本传输协议(HTTP,HyperText Transfer Protocol),是一个客户端和服务器端请求和应答的标准(TCP),互联网上应用最为广泛的一种网络协议。客户端是终端用户,服务器端是网站。通过使用Web浏览器、网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口(默认端口为80)的HTTP请求。
`,r:{minutes:19.51,words:5852},t:"TCP/IP、HTTP、HTTPS、HTTP2.0",y:"a"}}],["/java/%E7%BD%91%E7%BB%9C/",{loader:()=>C(()=>import("./index.html-DaAoZU-K.js"),[]),meta:{d:1708056026e3,r:{minutes:.04,words:12},t:"网络",i:"pen",O:100,y:"a"}}],["/kubernetes/devops/",{loader:()=>C(()=>import("./index.html-muK9iEFv.js"),[]),meta:{d:1708056026e3,e:`jofjweoiaejof
+`,r:{minutes:.04,words:12},t:"DevOps",i:"pen",O:1,y:"a"}}],["/kubernetes/devops/devops-ping-tai.html",{loader:()=>C(()=>import("./devops-ping-tai.html-CjTXGrBr.js"),[]),meta:{d:1566437746e3,e:` +DevOps定义(来自维基百科): DevOps(Development和Operations的组合词)是一种重视“软件开发人员(Dev)”和“IT运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。透过自动化“软件交付”和“架构变更”的流程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。
`,r:{minutes:11.96,words:3589},t:"DevOps平台.md",y:"a"}}],["/middleware/canal/%E4%B8%AD%E9%97%B4%E4%BB%B6%E2%80%94%E2%80%94canal%E5%B0%8F%E8%AE%B0.html",{loader:()=>C(()=>import("./中间件——canal小记.html-Bb5S9Lg5.js"),[]),meta:{d:1707204155e3,e:` +接到个小需求,将mysql的部分数据增量同步到es,但是不仅仅是使用canal而已,整体的流程是mysql>>canal>>flume>>kafka>>es,说难倒也不难,只是做起来碰到的坑实在太多,特别是中间套了那么多中间件,出了故障找起来真的特别麻烦。
`,r:{minutes:5.37,words:1610},t:"canal小记",y:"a"}}],["/middleware/kafka/kafka.html",{loader:()=>C(()=>import("./kafka.html-tMeeu2BM.js"),[]),meta:{d:164778928e4,e:` +1、请说明什么是Apache Kafka?
+Apache Kafka是由Apache开发的一种发布订阅消息系统,它是一个分布式的、分区的和可复制的提交日志服务。
留空
+`,r:{minutes:.01,words:3},t:"Zookeeper",y:"a"}}],["/middleware/zookeeper/%E5%9F%BA%E4%BA%8EZooKeeper%E7%9A%84%E9%98%9F%E5%88%97%E7%88%AC%E8%99%AB.html",{loader:()=>C(()=>import("./基于ZooKeeper的队列爬虫.html-D6Y4vXXb.js"),[]),meta:{d:1707204155e3,e:` +一直琢磨着分布式的东西怎么搞,公司也没有相关的项目能够参与,所以还是回归自己的专长来吧——基于ZooKeeper的分布式队列爬虫,由于没什么人能够一起沟通分布式的相关知识,下面的小项目纯属“胡编乱造”。
+简单介绍下ZooKeeper:ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
+基本的知识就不过多介绍了,可以参考参考下面这些人的:
+ZooKeeper官网
+http://www.cnblogs.com/wuxl360/p/5817471.html
随着移动互联网的飞速发展,人们已经处于信息过载的时代。在这个时代,信息的生产者很难将信息呈现在对其感兴趣的消费者面前,而信息消费者也难以从海量信息中找到自己感兴趣的内容。推荐系统充当了将信息生产者和信息消费者连接起来的桥梁,平台通常作为推荐系统的载体,实现信息生产者和消费者之间的匹配。
`,r:{minutes:6.19,words:1856},t:"推荐工程-概述",O:1,y:"a"}}],["/system-design/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E5%85%A5%E9%97%A8.html",{loader:()=>C(()=>import("./推荐系统入门.html-DF2q_M-N.js"),[]),meta:{d:1725198884e3,e:` +转载自:https://www.cnblogs.com/cgli/p/17225189.html
`,r:{minutes:16.79,words:5036},t:"推荐系统入门",y:"a"}}],["/system-design/%E8%AE%A1%E7%AE%97%E5%B9%BF%E5%91%8A/%E8%AE%A1%E7%AE%97%E5%B9%BF%E5%91%8A%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5%E5%85%A5%E9%97%A8%E6%80%BB%E7%BB%93.html",{loader:()=>C(()=>import("./计算广告基本概念入门总结.html-Zcj8usmP.js"),[]),meta:{d:1725198884e3,e:` +广告业务是各大互联网公司主要的商业化变现营收来源,近年来随着互联网和移动端技术的普及,使得广告的用户触达成本大大降低,而广告的形态和内涵也逐渐变得复杂,广告不再是单纯的展示和计费,而是结合了推荐、大数据等各类计算技术,以达到广告的精准受众定向,计算广告概念由此诞生。本文基于《计算广告》及互联网内容,整理了计算广告核心概念及入门知识,希望对入行广告行业的RD和非技术同学有所帮助。
`,r:{minutes:12.31,words:3692},t:"计算广告基本概念入门总结",y:"a"}}],["/404.html",{loader:()=>C(()=>import("./404.html-BMHKR2ag.js"),[]),meta:{t:""}}],["/interview/",{loader:()=>C(()=>import("./index.html-DQFfT5fQ.js"),[]),meta:{t:"Interview"}}],["/life/",{loader:()=>C(()=>import("./index.html-DJHdkCFZ.js"),[]),meta:{t:"Life"}}],["/link/",{loader:()=>C(()=>import("./index.html-7tsgb-dC.js"),[]),meta:{t:"Link"}}],["/system-design/",{loader:()=>C(()=>import("./index.html-KwQlr7R4.js"),[]),meta:{t:"System Design"}}],["/about-the-author/personal-life/",{loader:()=>C(()=>import("./index.html-DtDo2qGn.js"),[]),meta:{t:"Personal Life"}}],["/about-the-author/works/",{loader:()=>C(()=>import("./index.html-TDa4J3OS.js"),[]),meta:{t:"Works"}}],["/bigdata/spark/",{loader:()=>C(()=>import("./index.html-BAbeWXfD.js"),[]),meta:{t:"Spark"}}],["/database/elasticsearch/",{loader:()=>C(()=>import("./index.html-BTDM2Wpl.js"),[]),meta:{t:"Elasticsearch"}}],["/database/mysql/",{loader:()=>C(()=>import("./index.html-Uqis1PLJ.js"),[]),meta:{t:"Mysql"}}],["/database/redis/",{loader:()=>C(()=>import("./index.html-B2fh3Mku.js"),[]),meta:{t:"Redis"}}],["/middleware/canal/",{loader:()=>C(()=>import("./index.html-DRyhNgG6.js"),[]),meta:{t:"Canal"}}],["/middleware/kafka/",{loader:()=>C(()=>import("./index.html-CY6pLx2d.js"),[]),meta:{t:"Kafka"}}],["/middleware/sentinel/",{loader:()=>C(()=>import("./index.html-BwVnwmVL.js"),[]),meta:{t:"Sentinel"}}],["/middleware/zookeeper/",{loader:()=>C(()=>import("./index.html-5ZTxzR_p.js"),[]),meta:{t:"Zookeeper"}}],["/system-design/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F/",{loader:()=>C(()=>import("./index.html-C9P5ouBn.js"),[]),meta:{t:"推荐系统"}}],["/system-design/%E8%AE%A1%E7%AE%97%E5%B9%BF%E5%91%8A/",{loader:()=>C(()=>import("./index.html-SdCcjUz-.js"),[]),meta:{t:"计算广告"}}],["/category/",{loader:()=>C(()=>import("./index.html-DYmvtxA9.js"),[]),meta:{t:"分类",I:!1}}],["/tag/",{loader:()=>C(()=>import("./index.html-BKAyyte1.js"),[]),meta:{t:"标签",I:!1}}],["/article/",{loader:()=>C(()=>import("./index.html-CD6aFyf5.js"),[]),meta:{t:"文章",I:!1}}],["/star/",{loader:()=>C(()=>import("./index.html-B8Qu0Pmr.js"),[]),meta:{t:"星标",I:!1}}],["/timeline/",{loader:()=>C(()=>import("./index.html-CdxL9VtY.js"),[]),meta:{t:"时间轴",I:!1}}],["/slide/",{loader:()=>C(()=>import("./index.html-DJZmVM8a.js"),[]),meta:{t:""}}]]);/*! + * vue-router v4.5.0 + * (c) 2024 Eduardo San Martin Morote + * @license MIT + */const jn=typeof document<"u";function yd(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function X0(e){return e.__esModule||e[Symbol.toStringTag]==="Module"||e.default&&yd(e.default)}const Ee=Object.assign;function Bi(e,t){const n={};for(const l in t){const r=t[l];n[l]=Pt(r)?r.map(e):e(r)}return n}const Tl=()=>{},Pt=Array.isArray,Ad=/#/g,e2=/&/g,t2=/\//g,n2=/=/g,l2=/\?/g,kd=/\+/g,r2=/%5B/g,i2=/%5D/g,wd=/%5E/g,a2=/%60/g,Bd=/%7B/g,o2=/%7C/g,Cd=/%7D/g,s2=/%20/g;function qa(e){return encodeURI(""+e).replace(o2,"|").replace(r2,"[").replace(i2,"]")}function c2(e){return qa(e).replace(Bd,"{").replace(Cd,"}").replace(wd,"^")}function Zi(e){return qa(e).replace(kd,"%2B").replace(s2,"+").replace(Ad,"%23").replace(e2,"%26").replace(a2,"`").replace(Bd,"{").replace(Cd,"}").replace(wd,"^")}function u2(e){return Zi(e).replace(n2,"%3D")}function d2(e){return qa(e).replace(Ad,"%23").replace(l2,"%3F")}function f2(e){return e==null?"":d2(e).replace(t2,"%2F")}function Nl(e){try{return decodeURIComponent(""+e)}catch{}return""+e}const h2=/\/$/,p2=e=>e.replace(h2,"");function Ci(e,t,n="/"){let l,r={},i="",a="";const o=t.indexOf("#");let c=t.indexOf("?");return o