-
Notifications
You must be signed in to change notification settings - Fork 0
/
db.json
1 lines (1 loc) · 205 KB
/
db.json
1
{"meta":{"version":1,"warehouse":"4.0.0"},"models":{"Asset":[{"_id":"source/images/host/misc/keyboardx3.jpg","path":"images/host/misc/keyboardx3.jpg","modified":0,"renderable":0},{"_id":"source/images/host/misc/richang.png","path":"images/host/misc/richang.png","modified":0,"renderable":0},{"_id":"source/images/host/redis/redis-ht.png","path":"images/host/redis/redis-ht.png","modified":0,"renderable":0},{"_id":"source/images/host/redis/redis-icon-logo.png","path":"images/host/redis/redis-icon-logo.png","modified":0,"renderable":0},{"_id":"source/images/host/redis/redis-intset.jpg","path":"images/host/redis/redis-intset.jpg","modified":0,"renderable":0},{"_id":"source/images/host/redis/redis-list.png","path":"images/host/redis/redis-list.png","modified":0,"renderable":0},{"_id":"source/images/host/redis/redis-sds.png","path":"images/host/redis/redis-sds.png","modified":0,"renderable":0},{"_id":"source/images/host/redis/redis-zskip.jpg","path":"images/host/redis/redis-zskip.jpg","modified":0,"renderable":0},{"_id":"node_modules/hexo-theme-aurora/source/favicon.ico","path":"favicon.ico","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/icons/favicon-16x16.png","path":"icons/favicon-16x16.png","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/icons/favicon-32x32.png","path":"icons/favicon-32x32.png","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/css/404.1a6cd5bd.css","path":"static/css/404.1a6cd5bd.css","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/css/about.32dfa3b0.css","path":"static/css/about.32dfa3b0.css","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/css/app.0d31776f.css","path":"static/css/app.0d31776f.css","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/css/archives.c0d49bd5.css","path":"static/css/archives.c0d49bd5.css","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/css/categories.10e2be12.css","path":"static/css/categories.10e2be12.css","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/css/chunk-libs.eebac533.css","path":"static/css/chunk-libs.eebac533.css","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/css/page.749ad047.css","path":"static/css/page.749ad047.css","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/css/post.9f951a60.css","path":"static/css/post.9f951a60.css","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/css/result.10e2be12.css","path":"static/css/result.10e2be12.css","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/css/tags.10e2be12.css","path":"static/css/tags.10e2be12.css","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/img/default-cover.df7c128c.jpg","path":"static/img/default-cover.df7c128c.jpg","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/js/404.00d640a8.js","path":"static/js/404.00d640a8.js","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/js/about.024aacd1.js","path":"static/js/about.024aacd1.js","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/js/app.6d2c358d.js","path":"static/js/app.6d2c358d.js","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/js/archives.574ac664.js","path":"static/js/archives.574ac664.js","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/js/categories.90aa5475.js","path":"static/js/categories.90aa5475.js","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/js/chunk-libs.dc6146cd.js","path":"static/js/chunk-libs.dc6146cd.js","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/js/page.a02618ad.js","path":"static/js/page.a02618ad.js","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/js/post.cebfbaa4.js","path":"static/js/post.cebfbaa4.js","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/js/result.39470350.js","path":"static/js/result.39470350.js","modified":0,"renderable":1},{"_id":"node_modules/hexo-theme-aurora/source/static/js/tags.2ad613f5.js","path":"static/js/tags.2ad613f5.js","modified":0,"renderable":1}],"Cache":[{"_id":"source/_posts/go1.md","hash":"fe37e17f4bdcb54d96ee5cf32ae62a93c811e13c","modified":1624910984798},{"_id":"source/_posts/misc1.md","hash":"2f4b5af348758fa64a095d23921dc0e5facde777","modified":1624683389216},{"_id":"source/_posts/mysql1.md","hash":"e93b457fa887fb45097c5d9e308dccd1d6aa1a60","modified":1624683393378},{"_id":"source/_posts/mysql2.md","hash":"0f87c8184e1b556579607eecf020dcb4f4e56bb1","modified":1624683487354},{"_id":"source/_posts/network1.md","hash":"f1669738e757c49e360fbe556645b7bb8a5bf8a3","modified":1624683402484},{"_id":"source/_posts/network2.md","hash":"4ba6ccdb3981574e5722bbcbcce46d7acff6f519","modified":1624683406044},{"_id":"source/_posts/redis1.md","hash":"156f6c09cb73211b2ee8ab08ee26e7a7e68a1d3a","modified":1624683643378},{"_id":"source/_posts/redis2.md","hash":"4f79cbdb9dbe4492f0445a7fe8b56b59668d2eaf","modified":1624683563299},{"_id":"source/_posts/redis3.md","hash":"b72dade9f864d5c66e24df8e75687a13f6be1072","modified":1624683766620},{"_id":"source/_posts/redis4.md","hash":"363c7c58ae118cd5739b9419d22e61f16a5ab8cb","modified":1624910675527},{"_id":"source/_posts/redis5.md","hash":"6d49241edad901549cc6dc0dfd3c77e5a155cf1e","modified":1624683429473},{"_id":"source/images/host/misc/richang.png","hash":"3b6c05432b7479d29888cbf5415859d7bc8c29f3","modified":1618742386522},{"_id":"source/images/host/redis/redis-ht.png","hash":"97e40818038a4a1cb57ff981a329c6aa0f3d9ea1","modified":1615296347405},{"_id":"source/images/host/redis/redis-icon-logo.png","hash":"f559c701daee42e80b8c5d7c816777d5791079e7","modified":1615013101036},{"_id":"source/images/host/redis/redis-intset.jpg","hash":"c2cf42146685fb24815d1d202ac53097f035a3dd","modified":1615557474468},{"_id":"source/images/host/redis/redis-sds.png","hash":"8aefb86912e4b69f10705cd86e0bc37d15ba25d5","modified":1615012072890},{"_id":"source/images/host/redis/redis-zskip.jpg","hash":"3a04005e7e0fc59a4cb4b84244f745b47741a145","modified":1615553769525},{"_id":"node_modules/hexo-theme-aurora/.browserslistrc","hash":"db215b841182d2af3259a1c1d6e1957bca333970","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/CHANGELOG.md","hash":"e68f3af8af17d326a48e1b317e01d7429193217e","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/CHANGELOG_CN.md","hash":"5d13c251e8b77fd86870da50f3be98f8822edc66","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/LICENSE","hash":"21cf269018b0cf04e1126b6b9b1a5e6af7c5e61f","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/README.md","hash":"c50f58b08b75780990c18ab85761e3e24aac0d90","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/_config.yml","hash":"e6b2335cb393444b8a1fc1ad65331f4a30bc8915","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/jest.config.js","hash":"425f08cb718a56630ce61ae0686f35c55ef565df","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/package.json","hash":"1b312a6a2de107ba9dc952614f1078c78664a4e4","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/.circleci/config.yml","hash":"534051be311581f69aa8287120275a04539290e4","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/data/cn.yml","hash":"4f5dcc1e2953df7a8e4e683c1a9115f6e0041f5e","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/data/en.yml","hash":"7bbd6c0da0ae2f93f8a786a3be77fbe4e95a787e","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/layout/index.ejs","hash":"94732830a65b718d2fa506b8598df44f14399fd8","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/index.js","hash":"2a3c62a860581ee6813ca4bcaa7c952f614e818c","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/favicon.ico","hash":"c39d3ad80489cffed0d3df82c8cb05a049ab39a3","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/icons/favicon-16x16.png","hash":"849cad2a5ae49fa54c20372f7a21ae95192bafcf","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/icons/favicon-32x32.png","hash":"9dbabf6f4d825da99dcb2e91583755ae8d3b3ff4","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/filters/index.js","hash":"5eacff9446dd6c9a7eb0a0c84be9187055440454","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/generators/category.js","hash":"0ae21ffcf6e471ebfb72ac62a833991fae86ec8a","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/generators/index.js","hash":"dfbb0fdbf990bd67684ed891a4aaa1fd8dcd23f2","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/generators/page.js","hash":"1be54ea9cdf8e293e67bf457075fcdd8a72b8779","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/generators/post.js","hash":"76c3268e8b6887fb1fd11c4d71004aad9dc215fc","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/generators/search.js","hash":"a2a636e1df76efdcaca37226b490afa539ce766b","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/generators/site.js","hash":"b5f5ce813b4b0b322eceb11b036dc65201604256","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/generators/statistic.js","hash":"1c641956e15cec96490de16a88ccc7bec0c9d5c4","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/generators/tag.js","hash":"975e79e73d2503a33dbc63655b948100cc46d44f","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/helpers/toc.js","hash":"5c7348c550ef7f164d492847801a360cdefc60a2","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/helpers/utils.js","hash":"93ff75f0e35a1dc40d5406cc097f5988dd820600","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/injector/index.js","hash":"7ca562ea3af3068ee925b5a8afdce0eaa1e15e64","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/css/404.1a6cd5bd.css","hash":"a7a4d83756520d8f6c410ac0ffe9a45d63868113","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/css/about.32dfa3b0.css","hash":"614ef7c4e52877c76ffddb26192bf8fffddcb8d8","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/css/archives.c0d49bd5.css","hash":"c56213315c57254d0a6e6301ef24c2186fef4d2b","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/css/categories.10e2be12.css","hash":"e0f686c442936311dd85f11a06f3937007758b90","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/css/chunk-libs.eebac533.css","hash":"41226b6c29aadcc6190fe7c2c4c37464855b8453","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/css/page.749ad047.css","hash":"6fcf0e6f3c628954c5f8105791e0e7f3e6512da6","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/css/post.9f951a60.css","hash":"8e360582d745d6483fdfc18c46f75897b44721a6","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/css/result.10e2be12.css","hash":"e0f686c442936311dd85f11a06f3937007758b90","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/css/tags.10e2be12.css","hash":"e0f686c442936311dd85f11a06f3937007758b90","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/img/default-cover.df7c128c.jpg","hash":"1934ace0c6f2397d15729f9b08cc4d42e45bf437","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/js/404.00d640a8.js","hash":"74f1da9e0f953e73e4fe1a6554433f56fdcdf54d","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/js/about.024aacd1.js","hash":"7ce2b3f7d232dd3b3930ac993bb841452e2cde89","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/js/archives.574ac664.js","hash":"fbb094fb4f7a01bffd6975aca27142935bfdca73","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/js/categories.90aa5475.js","hash":"0dde3d731fc665962a4c330338a2c663c330be26","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/js/page.a02618ad.js","hash":"db775fbfbaa1cf8bbf4cc4f2b0a869519d508e3a","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/js/post.cebfbaa4.js","hash":"53586f9dab5886a366e9397eb17088e74af5cde1","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/js/result.39470350.js","hash":"f6f5ab2ca7274dad169ec600a419d1a2aeb2347c","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/js/tags.2ad613f5.js","hash":"4f49139e435788710115fc884dc26a5b1e0d4833","modified":1624682554475},{"_id":"source/images/host/redis/redis-list.png","hash":"21aba4ba07f212d2b52c2a312d19672e54cc4d8e","modified":1615292718384},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/helpers/truncate-html.js","hash":"49d4832af027eabe5b8383a24e66dceae761533e","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/helpers/symbols-count-time.js","hash":"d1a81e31b2988edcdf4b4761cec7326a980f097a","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/scripts/lib/helpers/mapper.js","hash":"7a99508d910321b90b4afa0df02ad90f62336901","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/css/app.0d31776f.css","hash":"43b43acd76bd8dc22f34cc49c7e026c45c3a5004","modified":1624682554475},{"_id":"source/images/host/misc/keyboardx3.jpg","hash":"69761317d3b4033dac2249cb8f77884478dd2a5f","modified":1618743332049},{"_id":"node_modules/hexo-theme-aurora/source/static/js/app.6d2c358d.js","hash":"e176c20c22ce36e38935f463977e9b89b281c715","modified":1624682554475},{"_id":"node_modules/hexo-theme-aurora/source/static/js/chunk-libs.dc6146cd.js","hash":"534266637d09abf39278f4d3a21bd36728740105","modified":1624682554475},{"_id":"source/_posts/go-option.md","hash":"6a1dbcec881d52aa86a6d11d706e1eb2fa250619","modified":1624988483147},{"_id":"public/api/posts/1.json","hash":"fb360e7bafdded564f68056ff83981460223d748","modified":1624988535172},{"_id":"public/api/articles/go-option.json","hash":"9b7aeeb4ba4f87f48a60b986deeb6834048024d0","modified":1624988535172},{"_id":"public/api/articles/go1.json","hash":"4ac7e2d06f349df96053040439b3fae030ad9e71","modified":1624988535172},{"_id":"public/api/articles/mysql2.json","hash":"53c5c98b21eb7f602d62afc5efaefe9d44b759cf","modified":1624988535172},{"_id":"public/api/articles/mysql1.json","hash":"728dba8d1ec172d892388ef28d48d2f8867da33c","modified":1624988535172},{"_id":"public/api/articles/network2.json","hash":"a7d7ad9389f5e189f024b0005e56a74d21d29e6b","modified":1624988535172},{"_id":"public/api/articles/network1.json","hash":"20a9cd70e9a0d2a8621787ad4bb096e2a81aa98e","modified":1624988535172},{"_id":"public/api/articles/misc1.json","hash":"02008a2016d891e6c5ca1adbb08fe7cf40977bd6","modified":1624988535172},{"_id":"public/api/articles/redis5.json","hash":"36ef2cd0a95831577811716f15cd50d3dbd213ed","modified":1624988535172},{"_id":"public/api/articles/redis4.json","hash":"0de051ff1f6c5c5513de85bfdb0f4f61e44b739d","modified":1624988535172},{"_id":"public/api/articles/redis3.json","hash":"c919e666982beeac253990427915fd3dae5fe716","modified":1624988535172},{"_id":"public/api/articles/redis2.json","hash":"bfd099583fa2b02a8aa929d825e8f1aee277e517","modified":1624988535172},{"_id":"public/api/articles/redis1.json","hash":"dc3b9420c8f9aa01304385c824b152b55ff64485","modified":1624988535172},{"_id":"public/api/features.json","hash":"d5e7f6ebd25831610f19f3e3f6d75a1ecac10b83","modified":1624988535172},{"_id":"public/api/authors/blog-author.json","hash":"e2bfc4d2405e5a5402138c9dc8bdcb849b19d38e","modified":1624988535172},{"_id":"public/api/site.json","hash":"f710166f2a09c6f35008a46ac5bd1a085c625376","modified":1624988535172},{"_id":"public/api/categories.json","hash":"22ebd86b970bb1b8d0bd7febd9e573cb8c7c76a0","modified":1624988535172},{"_id":"public/api/categories/速查.json","hash":"217170b8847e1617dbbdc7a76e80ab75a6e446ad","modified":1624988535172},{"_id":"public/api/categories/杂谈.json","hash":"cd8fc6fb3769b63f0bf2d6293999b25fee32ad13","modified":1624988535172},{"_id":"public/api/categories/学习笔记.json","hash":"1edbf6441703a500931c90ac8bd41428bb86b29b","modified":1624988535172},{"_id":"public/api/categories/速查/项目部署.json","hash":"3fd97d2e6d99248724bbbc1c942d2298399c7306","modified":1624988535172},{"_id":"public/api/categories/杂谈/折腾日常.json","hash":"50daaee6b5c54c2b57123b571c8b9c3bdf543569","modified":1624988535172},{"_id":"public/api/categories/学习笔记/Redis.json","hash":"cbb567e844f5242945cc0d605dae382d11dfe892","modified":1624988535172},{"_id":"public/api/categories/学习笔记/MySQL.json","hash":"fbf79a2de2847f6edc2b48e1d4ff2b87c26e094f","modified":1624988535172},{"_id":"public/api/categories/学习笔记/mysql.json","hash":"496c719c40701c043e2193f3a7b69d72deafee70","modified":1624988535172},{"_id":"public/api/categories/学习笔记/网络.json","hash":"279b218858a6189a6c4905e524f01dad5f4b2957","modified":1624988535172},{"_id":"public/api/categories/编程.json","hash":"8c410990adaad5c0894710d5553437643590b693","modified":1624988535172},{"_id":"public/api/tags.json","hash":"e02c2885a5a24f85001c2c6f42445bdfbd5e2808","modified":1624988535172},{"_id":"public/api/tags/golang.json","hash":"50ae27583b711156e0fb061d1a4f24726f52eb2c","modified":1624988535172},{"_id":"public/api/tags/编译.json","hash":"fb9c225c4ac9c5d81a6aaa7de6d1a9693501021d","modified":1624988535172},{"_id":"public/api/tags/硬件.json","hash":"6151d8bec7ae4da1f7c4ac81ca6ef80c12c093df","modified":1624988535172},{"_id":"public/api/tags/键盘.json","hash":"cbeef5199520e2a14a70ddd919208f758223844e","modified":1624988535172},{"_id":"public/api/tags/mysql.json","hash":"ace770903a604b4b080ea0d1973ac9d3f36e720b","modified":1624988535172},{"_id":"public/api/tags/事务.json","hash":"bbdf8a537652f317c49d42720a6f3c2be5cae83b","modified":1624988535172},{"_id":"public/api/tags/调优.json","hash":"4eead7963abbe982486ab7c12c8cd7fad63f9859","modified":1624988535172},{"_id":"public/api/tags/mysql实现原理.json","hash":"2485db3a791395d0feb5e172a7c760314994e3df","modified":1624988535172},{"_id":"public/api/tags/tcp.json","hash":"b22ade7c75ba1eb737661767de2b7a99e70453a0","modified":1624988535172},{"_id":"public/api/tags/Redis数据结构与对象.json","hash":"52113f77471b85f635a76b8028d84c6f7b35c15e","modified":1624988535172},{"_id":"public/api/tags/最佳实践.json","hash":"9d4a898b59df60acdd1ee9e88d5c08ed69a69bc0","modified":1624988535172},{"_id":"public/api/statistic.json","hash":"6021fe5776951c70277a1228c17064dac642bfa2","modified":1624988535172},{"_id":"public/api/search.json","hash":"1ca1d979e491b6e0b86384b784e57ab4fc842c8e","modified":1624988535172},{"_id":"public/post/go-option.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/post/go1.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/post/mysql2.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/post/mysql1.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/post/network2.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/post/network1.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/post/misc1.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/post/redis5.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/post/redis4.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/post/redis3.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/post/redis2.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/post/redis1.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/index.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/page/2/index.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/tags/index.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/tags/search/index.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/archives/index.html","hash":"4980ee6a73cc1d776b95fee856c25565d1afc601","modified":1624988535172},{"_id":"public/images/host/misc/richang.png","hash":"3b6c05432b7479d29888cbf5415859d7bc8c29f3","modified":1624988535172},{"_id":"public/images/host/redis/redis-ht.png","hash":"97e40818038a4a1cb57ff981a329c6aa0f3d9ea1","modified":1624988535172},{"_id":"public/images/host/redis/redis-icon-logo.png","hash":"f559c701daee42e80b8c5d7c816777d5791079e7","modified":1624988535172},{"_id":"public/images/host/redis/redis-intset.jpg","hash":"c2cf42146685fb24815d1d202ac53097f035a3dd","modified":1624988535172},{"_id":"public/images/host/redis/redis-sds.png","hash":"8aefb86912e4b69f10705cd86e0bc37d15ba25d5","modified":1624988535172},{"_id":"public/images/host/redis/redis-zskip.jpg","hash":"3a04005e7e0fc59a4cb4b84244f745b47741a145","modified":1624988535172},{"_id":"public/icons/favicon-16x16.png","hash":"849cad2a5ae49fa54c20372f7a21ae95192bafcf","modified":1624988535172},{"_id":"public/favicon.ico","hash":"c39d3ad80489cffed0d3df82c8cb05a049ab39a3","modified":1624988535172},{"_id":"public/icons/favicon-32x32.png","hash":"9dbabf6f4d825da99dcb2e91583755ae8d3b3ff4","modified":1624988535172},{"_id":"public/static/img/default-cover.df7c128c.jpg","hash":"1934ace0c6f2397d15729f9b08cc4d42e45bf437","modified":1624988535172},{"_id":"public/images/host/redis/redis-list.png","hash":"21aba4ba07f212d2b52c2a312d19672e54cc4d8e","modified":1624988535172},{"_id":"public/static/css/404.1a6cd5bd.css","hash":"a7a4d83756520d8f6c410ac0ffe9a45d63868113","modified":1624988535172},{"_id":"public/static/css/about.32dfa3b0.css","hash":"614ef7c4e52877c76ffddb26192bf8fffddcb8d8","modified":1624988535172},{"_id":"public/static/css/archives.c0d49bd5.css","hash":"c56213315c57254d0a6e6301ef24c2186fef4d2b","modified":1624988535172},{"_id":"public/static/css/categories.10e2be12.css","hash":"e0f686c442936311dd85f11a06f3937007758b90","modified":1624988535172},{"_id":"public/static/css/page.749ad047.css","hash":"6fcf0e6f3c628954c5f8105791e0e7f3e6512da6","modified":1624988535172},{"_id":"public/static/css/result.10e2be12.css","hash":"e0f686c442936311dd85f11a06f3937007758b90","modified":1624988535172},{"_id":"public/static/css/tags.10e2be12.css","hash":"e0f686c442936311dd85f11a06f3937007758b90","modified":1624988535172},{"_id":"public/static/js/404.00d640a8.js","hash":"74f1da9e0f953e73e4fe1a6554433f56fdcdf54d","modified":1624988535172},{"_id":"public/static/css/chunk-libs.eebac533.css","hash":"41226b6c29aadcc6190fe7c2c4c37464855b8453","modified":1624988535172},{"_id":"public/static/js/about.024aacd1.js","hash":"7ce2b3f7d232dd3b3930ac993bb841452e2cde89","modified":1624988535172},{"_id":"public/static/css/post.9f951a60.css","hash":"8e360582d745d6483fdfc18c46f75897b44721a6","modified":1624988535172},{"_id":"public/static/js/archives.574ac664.js","hash":"fbb094fb4f7a01bffd6975aca27142935bfdca73","modified":1624988535172},{"_id":"public/static/js/categories.90aa5475.js","hash":"0dde3d731fc665962a4c330338a2c663c330be26","modified":1624988535172},{"_id":"public/static/js/page.a02618ad.js","hash":"db775fbfbaa1cf8bbf4cc4f2b0a869519d508e3a","modified":1624988535172},{"_id":"public/static/js/result.39470350.js","hash":"f6f5ab2ca7274dad169ec600a419d1a2aeb2347c","modified":1624988535172},{"_id":"public/static/js/tags.2ad613f5.js","hash":"4f49139e435788710115fc884dc26a5b1e0d4833","modified":1624988535172},{"_id":"public/static/js/post.cebfbaa4.js","hash":"53586f9dab5886a366e9397eb17088e74af5cde1","modified":1624988535172},{"_id":"public/static/css/app.0d31776f.css","hash":"43b43acd76bd8dc22f34cc49c7e026c45c3a5004","modified":1624988535172},{"_id":"public/static/js/app.6d2c358d.js","hash":"e176c20c22ce36e38935f463977e9b89b281c715","modified":1624988535172},{"_id":"public/static/js/chunk-libs.dc6146cd.js","hash":"534266637d09abf39278f4d3a21bd36728740105","modified":1624988535172},{"_id":"public/images/host/misc/keyboardx3.jpg","hash":"69761317d3b4033dac2249cb8f77884478dd2a5f","modified":1624988535172}],"Category":[{"name":"速查","_id":"ckqh4n00y0002ykeads5chpts"},{"name":"杂谈","_id":"ckqh4n01g0007ykea2yuy2ag3"},{"name":"学习笔记","_id":"ckqh4n020000bykeag0pm33i9"},{"name":"项目部署","parent":"ckqh4n00y0002ykeads5chpts","_id":"ckqh4n02y000lykea82q600h9"},{"name":"折腾日常","parent":"ckqh4n01g0007ykea2yuy2ag3","_id":"ckqh4n03b000vykeagi6h2j7x"},{"name":"Redis","parent":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n03j0012ykeaahecckzs"},{"name":"MySQL","parent":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n03n0018ykeaelmrdox5"},{"name":"mysql","parent":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n046001lykeag6oyg63p"},{"name":"网络","parent":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n04f001vykeaecfn59gs"},{"name":"编程","_id":"ckqic7vj70001yueacqn0hzb5"}],"Data":[],"Page":[],"Post":[{"title":"Golang:交叉编译","date":"2021-05-06T03:38:41.000Z","keywords":["golang交叉编译","CGO"],"comments":0,"summary":"与解释性语言不同,golang代码需要经过编译后才能运行。当开发平台和运行平台不同时,要么在运行平台重新编译代码,要么使用交叉编译。这里简单记录一下第二种方法。","cover":"https://w.wallhaven.cc/full/y8/wallhaven-y85ojk.png","_content":"\n<!--more-->\n\n### 什么是交叉编译\n\n交叉编译是指在一个平台上生成另一个平台上的可执行程序。\n\n比如要在Windows系统上生成可以在Linux上运行的程序时,就需要用到交叉编译。\n\n### Golang交叉编译\n\n#### 编译参数\n\n不同的编译参数决定了最后生成的程序能在什么平台上运行,默认为当前编译平台。\n\n`GOOS` 目标平台的系统,常用值:windows、darwin(macOS)、linux\n\n`GOARCH` 目标平台CPU架构, 常用的值 :amd64、386、arm64、arm\n\n`GOARM ` 只有 `GOARCH` 是 `arm64` 才有效, 表示 `arm` 的版本, 目前只能是 5, 6, 7 其中之一\n\n`CGO_ENABLED` 值为1或0,分别表示开启或关闭CGO,默认开启(值为1)。CGO用于Go调用C代码\n\n`CC` 当支持交叉汇编时(即 `CGO_ENABLED=1`), 编译目标文件使用的 `c` 编译器\n\n`CXX` 当支持交叉汇编时(即 `CGO_ENABLED=1`), 编译目标文件使用的 `c++` 编译器\n\n`AR` 当支持交叉汇编时(即 `CGO_ENABLED=1`), 编译目标文件使用的创建库文件命令\n\n#### Go支持的平台\n\n`GOOS` 和 `GOARCH`的不同组合就代表不同平台,可以使用`go tool dist list`命令查看支持的平台\n\ngo 1.16.3 版本支持的平台如下:\n\n- linux\n - 386,amd64,arm,arm64,mips,mips64,mips64le,mipsle,riscv64,ppc64,ppc64le,s390x\n\n- windows\n - 386,amd64,arm\n\n- darwin,ios\n - amd64,arm64\n\n- android\n - 386,amd64,arm,arm64\n\n- freebsd,netbsd\n - 386,amd64,arm,arm64\n\n- openbsd\n - 386,amd64,arm,arm64,mips64\n\n- dragonfly,illumos,solaris\n - amd64\n\n- plan9\n - 386,amd64,arm\n\n- js\n - wasm\n\n- aix\n - ppc64\n\n其中386表示x86 32位,amd64表示x86 64位。\n\n### 具体操作\n\n下面列举几种常见的交叉编译场景的具体命令操作\n\n**Mac 下编译 Linux 和 Windows 64位可执行程序**\n\n```shell\nCGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go\n```\n\n**Linux 下编译 Mac 和 Windows 64位可执行程序**\n\n```shell\nCGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go\n```\n\n**Windows 下编译 Mac 和 Linux 64位可执行程序**\n\n```powershell\nSET CGO_ENABLED=0\nSET GOOS=darwin\nSET GOARCH=amd64\ngo build main.go\n\nSET CGO_ENABLED=0\nSET GOOS=linux\nSET GOARCH=amd64\ngo build main.go\n```\n\n可以通过将参数写入环境变量,这样就不用每次都要设置参数\n\n### 难点:CGO\n\n上文操作示例都关闭了CGO,但当go项目包含C代码的调用就需要开启CGO,然而这会带来问题。\n\n例如在一个嵌入式开发场景中,需要使用`github.com/mattn/go-sqlite3`驱动sqlite,并且目标平台是arm架构,此时go本身的工具链不足以完成编译。\n\n解决方法就是下载对应的编译工具链\n\n[arm编译工具链官网地址](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads)\n\n以Windows为例,下载`gcc-arm-none-eabi-XXXX-major-win32.zip`\n\n下载完成后解压并将其下`bin`文件夹加入环境变量,最后将编译参数`CC`的值设为`arm-none-eabi-gcc`\n\n这里简单提一下交叉编译工具链的命名规则:\n\narch [-vendor] [-os] [-(gnu)eabi]\n\n- arch - 体系架构,如ARM,MIPS\n- vendor - 工具链提供商\n- os - 目标操作系统\n- eabi - 嵌入式应用二进制接口(Embedded Application Binary Interface)\n\n","source":"_posts/go1.md","raw":"---\ntitle: \"Golang:交叉编译\"\ndate: 2021-05-06T19:38:41+08:00\ncategories:\n- 速查\n- 项目部署\ntags:\n- golang\n- 编译\nkeywords:\n- golang交叉编译\n- CGO\ncomments: false\nsummary: 与解释性语言不同,golang代码需要经过编译后才能运行。当开发平台和运行平台不同时,要么在运行平台重新编译代码,要么使用交叉编译。这里简单记录一下第二种方法。\n#thumbnailImage: //example.com/image.jpg\ncover: https://w.wallhaven.cc/full/y8/wallhaven-y85ojk.png\n---\n\n<!--more-->\n\n### 什么是交叉编译\n\n交叉编译是指在一个平台上生成另一个平台上的可执行程序。\n\n比如要在Windows系统上生成可以在Linux上运行的程序时,就需要用到交叉编译。\n\n### Golang交叉编译\n\n#### 编译参数\n\n不同的编译参数决定了最后生成的程序能在什么平台上运行,默认为当前编译平台。\n\n`GOOS` 目标平台的系统,常用值:windows、darwin(macOS)、linux\n\n`GOARCH` 目标平台CPU架构, 常用的值 :amd64、386、arm64、arm\n\n`GOARM ` 只有 `GOARCH` 是 `arm64` 才有效, 表示 `arm` 的版本, 目前只能是 5, 6, 7 其中之一\n\n`CGO_ENABLED` 值为1或0,分别表示开启或关闭CGO,默认开启(值为1)。CGO用于Go调用C代码\n\n`CC` 当支持交叉汇编时(即 `CGO_ENABLED=1`), 编译目标文件使用的 `c` 编译器\n\n`CXX` 当支持交叉汇编时(即 `CGO_ENABLED=1`), 编译目标文件使用的 `c++` 编译器\n\n`AR` 当支持交叉汇编时(即 `CGO_ENABLED=1`), 编译目标文件使用的创建库文件命令\n\n#### Go支持的平台\n\n`GOOS` 和 `GOARCH`的不同组合就代表不同平台,可以使用`go tool dist list`命令查看支持的平台\n\ngo 1.16.3 版本支持的平台如下:\n\n- linux\n - 386,amd64,arm,arm64,mips,mips64,mips64le,mipsle,riscv64,ppc64,ppc64le,s390x\n\n- windows\n - 386,amd64,arm\n\n- darwin,ios\n - amd64,arm64\n\n- android\n - 386,amd64,arm,arm64\n\n- freebsd,netbsd\n - 386,amd64,arm,arm64\n\n- openbsd\n - 386,amd64,arm,arm64,mips64\n\n- dragonfly,illumos,solaris\n - amd64\n\n- plan9\n - 386,amd64,arm\n\n- js\n - wasm\n\n- aix\n - ppc64\n\n其中386表示x86 32位,amd64表示x86 64位。\n\n### 具体操作\n\n下面列举几种常见的交叉编译场景的具体命令操作\n\n**Mac 下编译 Linux 和 Windows 64位可执行程序**\n\n```shell\nCGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go\n```\n\n**Linux 下编译 Mac 和 Windows 64位可执行程序**\n\n```shell\nCGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go\n```\n\n**Windows 下编译 Mac 和 Linux 64位可执行程序**\n\n```powershell\nSET CGO_ENABLED=0\nSET GOOS=darwin\nSET GOARCH=amd64\ngo build main.go\n\nSET CGO_ENABLED=0\nSET GOOS=linux\nSET GOARCH=amd64\ngo build main.go\n```\n\n可以通过将参数写入环境变量,这样就不用每次都要设置参数\n\n### 难点:CGO\n\n上文操作示例都关闭了CGO,但当go项目包含C代码的调用就需要开启CGO,然而这会带来问题。\n\n例如在一个嵌入式开发场景中,需要使用`github.com/mattn/go-sqlite3`驱动sqlite,并且目标平台是arm架构,此时go本身的工具链不足以完成编译。\n\n解决方法就是下载对应的编译工具链\n\n[arm编译工具链官网地址](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads)\n\n以Windows为例,下载`gcc-arm-none-eabi-XXXX-major-win32.zip`\n\n下载完成后解压并将其下`bin`文件夹加入环境变量,最后将编译参数`CC`的值设为`arm-none-eabi-gcc`\n\n这里简单提一下交叉编译工具链的命名规则:\n\narch [-vendor] [-os] [-(gnu)eabi]\n\n- arch - 体系架构,如ARM,MIPS\n- vendor - 工具链提供商\n- os - 目标操作系统\n- eabi - 嵌入式应用二进制接口(Embedded Application Binary Interface)\n\n","slug":"go1","published":1,"updated":"2021-06-28T20:09:44.798Z","layout":"post","photos":[],"link":"","_id":"ckqh4n00c0000ykea338kcok7","content":"<span id=\"more\"></span>\n\n<h3 id=\"什么是交叉编译\"><a href=\"#什么是交叉编译\" class=\"headerlink\" title=\"什么是交叉编译\"></a>什么是交叉编译</h3><p>交叉编译是指在一个平台上生成另一个平台上的可执行程序。</p>\n<p>比如要在Windows系统上生成可以在Linux上运行的程序时,就需要用到交叉编译。</p>\n<h3 id=\"Golang交叉编译\"><a href=\"#Golang交叉编译\" class=\"headerlink\" title=\"Golang交叉编译\"></a>Golang交叉编译</h3><h4 id=\"编译参数\"><a href=\"#编译参数\" class=\"headerlink\" title=\"编译参数\"></a>编译参数</h4><p>不同的编译参数决定了最后生成的程序能在什么平台上运行,默认为当前编译平台。</p>\n<p><code>GOOS</code> 目标平台的系统,常用值:windows、darwin(macOS)、linux</p>\n<p><code>GOARCH</code> 目标平台CPU架构, 常用的值 :amd64、386、arm64、arm</p>\n<p><code>GOARM </code> 只有 <code>GOARCH</code> 是 <code>arm64</code> 才有效, 表示 <code>arm</code> 的版本, 目前只能是 5, 6, 7 其中之一</p>\n<p><code>CGO_ENABLED</code> 值为1或0,分别表示开启或关闭CGO,默认开启(值为1)。CGO用于Go调用C代码</p>\n<p><code>CC</code> 当支持交叉汇编时(即 <code>CGO_ENABLED=1</code>), 编译目标文件使用的 <code>c</code> 编译器</p>\n<p><code>CXX</code> 当支持交叉汇编时(即 <code>CGO_ENABLED=1</code>), 编译目标文件使用的 <code>c++</code> 编译器</p>\n<p><code>AR</code> 当支持交叉汇编时(即 <code>CGO_ENABLED=1</code>), 编译目标文件使用的创建库文件命令</p>\n<h4 id=\"Go支持的平台\"><a href=\"#Go支持的平台\" class=\"headerlink\" title=\"Go支持的平台\"></a>Go支持的平台</h4><p><code>GOOS</code> 和 <code>GOARCH</code>的不同组合就代表不同平台,可以使用<code>go tool dist list</code>命令查看支持的平台</p>\n<p>go 1.16.3 版本支持的平台如下:</p>\n<ul>\n<li><p>linux</p>\n<ul>\n<li>386,amd64,arm,arm64,mips,mips64,mips64le,mipsle,riscv64,ppc64,ppc64le,s390x</li>\n</ul>\n</li>\n<li><p>windows</p>\n<ul>\n<li>386,amd64,arm</li>\n</ul>\n</li>\n<li><p>darwin,ios</p>\n<ul>\n<li>amd64,arm64</li>\n</ul>\n</li>\n<li><p>android</p>\n<ul>\n<li>386,amd64,arm,arm64</li>\n</ul>\n</li>\n<li><p>freebsd,netbsd</p>\n<ul>\n<li>386,amd64,arm,arm64</li>\n</ul>\n</li>\n<li><p>openbsd</p>\n<ul>\n<li>386,amd64,arm,arm64,mips64</li>\n</ul>\n</li>\n<li><p>dragonfly,illumos,solaris</p>\n<ul>\n<li>amd64</li>\n</ul>\n</li>\n<li><p>plan9</p>\n<ul>\n<li>386,amd64,arm</li>\n</ul>\n</li>\n<li><p>js</p>\n<ul>\n<li>wasm</li>\n</ul>\n</li>\n<li><p>aix</p>\n<ul>\n<li>ppc64</li>\n</ul>\n</li>\n</ul>\n<p>其中386表示x86 32位,amd64表示x86 64位。</p>\n<h3 id=\"具体操作\"><a href=\"#具体操作\" class=\"headerlink\" title=\"具体操作\"></a>具体操作</h3><p>下面列举几种常见的交叉编译场景的具体命令操作</p>\n<p><strong>Mac 下编译 Linux 和 Windows 64位可执行程序</strong></p>\n<pre class=\"line-numbers language-shell\" data-language=\"shell\"><code class=\"language-shell\">CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go</code></pre>\n\n<p><strong>Linux 下编译 Mac 和 Windows 64位可执行程序</strong></p>\n<pre class=\"line-numbers language-shell\" data-language=\"shell\"><code class=\"language-shell\">CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go</code></pre>\n\n<p><strong>Windows 下编译 Mac 和 Linux 64位可执行程序</strong></p>\n<pre class=\"line-numbers language-powershell\" data-language=\"powershell\"><code class=\"language-powershell\">SET CGO_ENABLED=0\nSET GOOS=darwin\nSET GOARCH=amd64\ngo build main.go\n\nSET CGO_ENABLED=0\nSET GOOS=linux\nSET GOARCH=amd64\ngo build main.go</code></pre>\n\n<p>可以通过将参数写入环境变量,这样就不用每次都要设置参数</p>\n<h3 id=\"难点:CGO\"><a href=\"#难点:CGO\" class=\"headerlink\" title=\"难点:CGO\"></a>难点:CGO</h3><p>上文操作示例都关闭了CGO,但当go项目包含C代码的调用就需要开启CGO,然而这会带来问题。</p>\n<p>例如在一个嵌入式开发场景中,需要使用<code>github.com/mattn/go-sqlite3</code>驱动sqlite,并且目标平台是arm架构,此时go本身的工具链不足以完成编译。</p>\n<p>解决方法就是下载对应的编译工具链</p>\n<p><a href=\"https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads\">arm编译工具链官网地址</a></p>\n<p>以Windows为例,下载<code>gcc-arm-none-eabi-XXXX-major-win32.zip</code></p>\n<p>下载完成后解压并将其下<code>bin</code>文件夹加入环境变量,最后将编译参数<code>CC</code>的值设为<code>arm-none-eabi-gcc</code></p>\n<p>这里简单提一下交叉编译工具链的命名规则:</p>\n<p>arch [-vendor] [-os] [-(gnu)eabi]</p>\n<ul>\n<li>arch - 体系架构,如ARM,MIPS</li>\n<li>vendor - 工具链提供商</li>\n<li>os - 目标操作系统</li>\n<li>eabi - 嵌入式应用二进制接口(Embedded Application Binary Interface)</li>\n</ul>\n","site":{"data":{}},"excerpt":"","more":"<h3 id=\"什么是交叉编译\"><a href=\"#什么是交叉编译\" class=\"headerlink\" title=\"什么是交叉编译\"></a>什么是交叉编译</h3><p>交叉编译是指在一个平台上生成另一个平台上的可执行程序。</p>\n<p>比如要在Windows系统上生成可以在Linux上运行的程序时,就需要用到交叉编译。</p>\n<h3 id=\"Golang交叉编译\"><a href=\"#Golang交叉编译\" class=\"headerlink\" title=\"Golang交叉编译\"></a>Golang交叉编译</h3><h4 id=\"编译参数\"><a href=\"#编译参数\" class=\"headerlink\" title=\"编译参数\"></a>编译参数</h4><p>不同的编译参数决定了最后生成的程序能在什么平台上运行,默认为当前编译平台。</p>\n<p><code>GOOS</code> 目标平台的系统,常用值:windows、darwin(macOS)、linux</p>\n<p><code>GOARCH</code> 目标平台CPU架构, 常用的值 :amd64、386、arm64、arm</p>\n<p><code>GOARM </code> 只有 <code>GOARCH</code> 是 <code>arm64</code> 才有效, 表示 <code>arm</code> 的版本, 目前只能是 5, 6, 7 其中之一</p>\n<p><code>CGO_ENABLED</code> 值为1或0,分别表示开启或关闭CGO,默认开启(值为1)。CGO用于Go调用C代码</p>\n<p><code>CC</code> 当支持交叉汇编时(即 <code>CGO_ENABLED=1</code>), 编译目标文件使用的 <code>c</code> 编译器</p>\n<p><code>CXX</code> 当支持交叉汇编时(即 <code>CGO_ENABLED=1</code>), 编译目标文件使用的 <code>c++</code> 编译器</p>\n<p><code>AR</code> 当支持交叉汇编时(即 <code>CGO_ENABLED=1</code>), 编译目标文件使用的创建库文件命令</p>\n<h4 id=\"Go支持的平台\"><a href=\"#Go支持的平台\" class=\"headerlink\" title=\"Go支持的平台\"></a>Go支持的平台</h4><p><code>GOOS</code> 和 <code>GOARCH</code>的不同组合就代表不同平台,可以使用<code>go tool dist list</code>命令查看支持的平台</p>\n<p>go 1.16.3 版本支持的平台如下:</p>\n<ul>\n<li><p>linux</p>\n<ul>\n<li>386,amd64,arm,arm64,mips,mips64,mips64le,mipsle,riscv64,ppc64,ppc64le,s390x</li>\n</ul>\n</li>\n<li><p>windows</p>\n<ul>\n<li>386,amd64,arm</li>\n</ul>\n</li>\n<li><p>darwin,ios</p>\n<ul>\n<li>amd64,arm64</li>\n</ul>\n</li>\n<li><p>android</p>\n<ul>\n<li>386,amd64,arm,arm64</li>\n</ul>\n</li>\n<li><p>freebsd,netbsd</p>\n<ul>\n<li>386,amd64,arm,arm64</li>\n</ul>\n</li>\n<li><p>openbsd</p>\n<ul>\n<li>386,amd64,arm,arm64,mips64</li>\n</ul>\n</li>\n<li><p>dragonfly,illumos,solaris</p>\n<ul>\n<li>amd64</li>\n</ul>\n</li>\n<li><p>plan9</p>\n<ul>\n<li>386,amd64,arm</li>\n</ul>\n</li>\n<li><p>js</p>\n<ul>\n<li>wasm</li>\n</ul>\n</li>\n<li><p>aix</p>\n<ul>\n<li>ppc64</li>\n</ul>\n</li>\n</ul>\n<p>其中386表示x86 32位,amd64表示x86 64位。</p>\n<h3 id=\"具体操作\"><a href=\"#具体操作\" class=\"headerlink\" title=\"具体操作\"></a>具体操作</h3><p>下面列举几种常见的交叉编译场景的具体命令操作</p>\n<p><strong>Mac 下编译 Linux 和 Windows 64位可执行程序</strong></p>\n<pre class=\"line-numbers language-shell\" data-language=\"shell\"><code class=\"language-shell\">CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go</code></pre>\n\n<p><strong>Linux 下编译 Mac 和 Windows 64位可执行程序</strong></p>\n<pre class=\"line-numbers language-shell\" data-language=\"shell\"><code class=\"language-shell\">CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go</code></pre>\n\n<p><strong>Windows 下编译 Mac 和 Linux 64位可执行程序</strong></p>\n<pre class=\"line-numbers language-powershell\" data-language=\"powershell\"><code class=\"language-powershell\">SET CGO_ENABLED=0\nSET GOOS=darwin\nSET GOARCH=amd64\ngo build main.go\n\nSET CGO_ENABLED=0\nSET GOOS=linux\nSET GOARCH=amd64\ngo build main.go</code></pre>\n\n<p>可以通过将参数写入环境变量,这样就不用每次都要设置参数</p>\n<h3 id=\"难点:CGO\"><a href=\"#难点:CGO\" class=\"headerlink\" title=\"难点:CGO\"></a>难点:CGO</h3><p>上文操作示例都关闭了CGO,但当go项目包含C代码的调用就需要开启CGO,然而这会带来问题。</p>\n<p>例如在一个嵌入式开发场景中,需要使用<code>github.com/mattn/go-sqlite3</code>驱动sqlite,并且目标平台是arm架构,此时go本身的工具链不足以完成编译。</p>\n<p>解决方法就是下载对应的编译工具链</p>\n<p><a href=\"https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads\">arm编译工具链官网地址</a></p>\n<p>以Windows为例,下载<code>gcc-arm-none-eabi-XXXX-major-win32.zip</code></p>\n<p>下载完成后解压并将其下<code>bin</code>文件夹加入环境变量,最后将编译参数<code>CC</code>的值设为<code>arm-none-eabi-gcc</code></p>\n<p>这里简单提一下交叉编译工具链的命名规则:</p>\n<p>arch [-vendor] [-os] [-(gnu)eabi]</p>\n<ul>\n<li>arch - 体系架构,如ARM,MIPS</li>\n<li>vendor - 工具链提供商</li>\n<li>os - 目标操作系统</li>\n<li>eabi - 嵌入式应用二进制接口(Embedded Application Binary Interface)</li>\n</ul>"},{"title":"杂谈(一)","date":"2021-04-18T02:17:09.000Z","keywords":["黑峡谷x3"],"comments":0,"summary":"好久没写博客,刚好今天新买的键盘到了,写篇博客记录下体验总结,供想买键盘的小伙伴参考。","_content":"\n<!--more-->\n\n![](/images/host/misc/keyboardx3.jpg)\n\n今天到手的就是上面这把黑峡谷x3了,现在打字用的就是它。\n\n### 直肠子的一句话总结\n\n**300价位 、 87配列、双模(2.4g wifi和有线)、手感不错 (凯华box轴体)、可充电**\n\n配件送了type-c数据线、手托、拔键器、防尘盖,还算良心。目前没发现啥问题。\n\n之所以买了这把键盘呢,主要还是早有预谋加热血上头。本来一直用的是100价位的入门键盘,自从摸了同事的键盘就想着换把键盘,加上最近桌面被各种线搞得乱七八糟的,半夜趁自己不备买了键盘。其实300价位的机械键盘倒也不是很贵,主要是没钱.jpg\n\n## 细节\n\n- 上手的第一感觉就是沉,1.5kg的毛重对便携性还是有点影响的。\n- 无线接收器可以磁吸在背面(有个凹槽),取放比较方便,吸力还可以,带出门时得注意点。\n- 线材1.5m左右,个人觉得长度合适,我原来那个键盘线就长的离谱。普通USB口加type-C口,可分离(废话),表面是编织网,质量看上去不错。\n- 据说可能碰到歪轴问题,黑白配色可以一定程度上遮盖这一问题,厂商真是鬼才。\n\n### 关于手感\n\n手感这个东西比较主观,我选的是凯华box流沙金轴,对应常说的茶轴。对比我原来用的青轴,有声音但是不会吵得让人心烦,也没丢了段落感,哒哒哒敲得还是挺开心的。\n\n### 关于颜值\n\n颜值这个东西比较主观(似曾相识燕归来),我选的是黑白双色的,还有其他花里胡哨的配色。但是个人觉得花里胡哨得不够惊艳,值得一提的是还有一款猛男配色hh。总之颜值这块从我看来不算高。\n\n### 性价比\n\n性价比的前提是符合核心要求,抛开要求讲性价比其实不太合理。但硬要说的话,那就一个字:高。相信大多数人最在意的还是手感和功能性,这把键盘这两项都还算可以的情况下300的价位也比较亲民。\n","source":"_posts/misc1.md","raw":"---\ntitle: \"杂谈(一)\"\ndate: 2021-04-18T18:17:09+08:00\ncategories:\n- 杂谈\n- 折腾日常\ntags:\n- 硬件\n- 键盘\nkeywords:\n- 黑峡谷x3\n#thumbnailImage: images/host/misc/richang.png\ncomments: false\nsummary: 好久没写博客,刚好今天新买的键盘到了,写篇博客记录下体验总结,供想买键盘的小伙伴参考。\n---\n\n<!--more-->\n\n![](/images/host/misc/keyboardx3.jpg)\n\n今天到手的就是上面这把黑峡谷x3了,现在打字用的就是它。\n\n### 直肠子的一句话总结\n\n**300价位 、 87配列、双模(2.4g wifi和有线)、手感不错 (凯华box轴体)、可充电**\n\n配件送了type-c数据线、手托、拔键器、防尘盖,还算良心。目前没发现啥问题。\n\n之所以买了这把键盘呢,主要还是早有预谋加热血上头。本来一直用的是100价位的入门键盘,自从摸了同事的键盘就想着换把键盘,加上最近桌面被各种线搞得乱七八糟的,半夜趁自己不备买了键盘。其实300价位的机械键盘倒也不是很贵,主要是没钱.jpg\n\n## 细节\n\n- 上手的第一感觉就是沉,1.5kg的毛重对便携性还是有点影响的。\n- 无线接收器可以磁吸在背面(有个凹槽),取放比较方便,吸力还可以,带出门时得注意点。\n- 线材1.5m左右,个人觉得长度合适,我原来那个键盘线就长的离谱。普通USB口加type-C口,可分离(废话),表面是编织网,质量看上去不错。\n- 据说可能碰到歪轴问题,黑白配色可以一定程度上遮盖这一问题,厂商真是鬼才。\n\n### 关于手感\n\n手感这个东西比较主观,我选的是凯华box流沙金轴,对应常说的茶轴。对比我原来用的青轴,有声音但是不会吵得让人心烦,也没丢了段落感,哒哒哒敲得还是挺开心的。\n\n### 关于颜值\n\n颜值这个东西比较主观(似曾相识燕归来),我选的是黑白双色的,还有其他花里胡哨的配色。但是个人觉得花里胡哨得不够惊艳,值得一提的是还有一款猛男配色hh。总之颜值这块从我看来不算高。\n\n### 性价比\n\n性价比的前提是符合核心要求,抛开要求讲性价比其实不太合理。但硬要说的话,那就一个字:高。相信大多数人最在意的还是手感和功能性,这把键盘这两项都还算可以的情况下300的价位也比较亲民。\n","slug":"misc1","published":1,"updated":"2021-06-26T04:56:29.216Z","layout":"post","photos":[],"link":"","_id":"ckqh4n00s0001ykeae79p0kqv","content":"<span id=\"more\"></span>\n\n<p><img src=\"/images/host/misc/keyboardx3.jpg\"></p>\n<p>今天到手的就是上面这把黑峡谷x3了,现在打字用的就是它。</p>\n<h3 id=\"直肠子的一句话总结\"><a href=\"#直肠子的一句话总结\" class=\"headerlink\" title=\"直肠子的一句话总结\"></a>直肠子的一句话总结</h3><p><strong>300价位 、 87配列、双模(2.4g wifi和有线)、手感不错 (凯华box轴体)、可充电</strong></p>\n<p>配件送了type-c数据线、手托、拔键器、防尘盖,还算良心。目前没发现啥问题。</p>\n<p>之所以买了这把键盘呢,主要还是早有预谋加热血上头。本来一直用的是100价位的入门键盘,自从摸了同事的键盘就想着换把键盘,加上最近桌面被各种线搞得乱七八糟的,半夜趁自己不备买了键盘。其实300价位的机械键盘倒也不是很贵,主要是没钱.jpg</p>\n<h2 id=\"细节\"><a href=\"#细节\" class=\"headerlink\" title=\"细节\"></a>细节</h2><ul>\n<li>上手的第一感觉就是沉,1.5kg的毛重对便携性还是有点影响的。</li>\n<li>无线接收器可以磁吸在背面(有个凹槽),取放比较方便,吸力还可以,带出门时得注意点。</li>\n<li>线材1.5m左右,个人觉得长度合适,我原来那个键盘线就长的离谱。普通USB口加type-C口,可分离(废话),表面是编织网,质量看上去不错。</li>\n<li>据说可能碰到歪轴问题,黑白配色可以一定程度上遮盖这一问题,厂商真是鬼才。</li>\n</ul>\n<h3 id=\"关于手感\"><a href=\"#关于手感\" class=\"headerlink\" title=\"关于手感\"></a>关于手感</h3><p>手感这个东西比较主观,我选的是凯华box流沙金轴,对应常说的茶轴。对比我原来用的青轴,有声音但是不会吵得让人心烦,也没丢了段落感,哒哒哒敲得还是挺开心的。</p>\n<h3 id=\"关于颜值\"><a href=\"#关于颜值\" class=\"headerlink\" title=\"关于颜值\"></a>关于颜值</h3><p>颜值这个东西比较主观(似曾相识燕归来),我选的是黑白双色的,还有其他花里胡哨的配色。但是个人觉得花里胡哨得不够惊艳,值得一提的是还有一款猛男配色hh。总之颜值这块从我看来不算高。</p>\n<h3 id=\"性价比\"><a href=\"#性价比\" class=\"headerlink\" title=\"性价比\"></a>性价比</h3><p>性价比的前提是符合核心要求,抛开要求讲性价比其实不太合理。但硬要说的话,那就一个字:高。相信大多数人最在意的还是手感和功能性,这把键盘这两项都还算可以的情况下300的价位也比较亲民。</p>\n","site":{"data":{}},"excerpt":"","more":"<p><img src=\"/images/host/misc/keyboardx3.jpg\"></p>\n<p>今天到手的就是上面这把黑峡谷x3了,现在打字用的就是它。</p>\n<h3 id=\"直肠子的一句话总结\"><a href=\"#直肠子的一句话总结\" class=\"headerlink\" title=\"直肠子的一句话总结\"></a>直肠子的一句话总结</h3><p><strong>300价位 、 87配列、双模(2.4g wifi和有线)、手感不错 (凯华box轴体)、可充电</strong></p>\n<p>配件送了type-c数据线、手托、拔键器、防尘盖,还算良心。目前没发现啥问题。</p>\n<p>之所以买了这把键盘呢,主要还是早有预谋加热血上头。本来一直用的是100价位的入门键盘,自从摸了同事的键盘就想着换把键盘,加上最近桌面被各种线搞得乱七八糟的,半夜趁自己不备买了键盘。其实300价位的机械键盘倒也不是很贵,主要是没钱.jpg</p>\n<h2 id=\"细节\"><a href=\"#细节\" class=\"headerlink\" title=\"细节\"></a>细节</h2><ul>\n<li>上手的第一感觉就是沉,1.5kg的毛重对便携性还是有点影响的。</li>\n<li>无线接收器可以磁吸在背面(有个凹槽),取放比较方便,吸力还可以,带出门时得注意点。</li>\n<li>线材1.5m左右,个人觉得长度合适,我原来那个键盘线就长的离谱。普通USB口加type-C口,可分离(废话),表面是编织网,质量看上去不错。</li>\n<li>据说可能碰到歪轴问题,黑白配色可以一定程度上遮盖这一问题,厂商真是鬼才。</li>\n</ul>\n<h3 id=\"关于手感\"><a href=\"#关于手感\" class=\"headerlink\" title=\"关于手感\"></a>关于手感</h3><p>手感这个东西比较主观,我选的是凯华box流沙金轴,对应常说的茶轴。对比我原来用的青轴,有声音但是不会吵得让人心烦,也没丢了段落感,哒哒哒敲得还是挺开心的。</p>\n<h3 id=\"关于颜值\"><a href=\"#关于颜值\" class=\"headerlink\" title=\"关于颜值\"></a>关于颜值</h3><p>颜值这个东西比较主观(似曾相识燕归来),我选的是黑白双色的,还有其他花里胡哨的配色。但是个人觉得花里胡哨得不够惊艳,值得一提的是还有一款猛男配色hh。总之颜值这块从我看来不算高。</p>\n<h3 id=\"性价比\"><a href=\"#性价比\" class=\"headerlink\" title=\"性价比\"></a>性价比</h3><p>性价比的前提是符合核心要求,抛开要求讲性价比其实不太合理。但硬要说的话,那就一个字:高。相信大多数人最在意的还是手感和功能性,这把键盘这两项都还算可以的情况下300的价位也比较亲民。</p>"},{"title":"MySQL:redo 和 undo","date":"2021-04-27T04:55:29.000Z","keywords":["mysql事务的实现","redo log","undo log"],"comments":0,"summary":"MySQL通过 redo log (重做日志)和 undo log 实现了事务的持久性、原子性和一致性,本文对这两种日志进行了简单的阐述和总结。","_content":"\n<!--more-->\n\n### redo log 作用\n\nredo log 用以保证事务的持久性,当数据库突然宕机后重启就需要使用 redo log 恢复已经提交但是没来得及同步到磁盘上的事务。\n\n再深挖一下为什么数据库宕机重启就需要恢复数据呢,这是因为直接将修改的数据页同步到磁盘意味着频繁的随机写入操作。懂点磁盘运行原理的同学都应该知道这样效率多低,但一直放在内存而不实时刷盘又容易丢失数据。 redo log 就可以解决这个问题,虽然也需要同步到磁盘,但是由于采取顺序写入的方式,所以同时具有效率高和不易失的优点。\n\n这样一来,数据库就可以从容地将修改的数据页写入磁盘而不用怕突然宕机,因为可以根据 redo log 进行恢复。\n\n### 工作机制\n\n在InnoDB中通过Force Log at Commit机制实现事务的持久性,即当事务提交时,必须将重做日志缓冲写入重做日志文件后才算提交成功。这样就能保证成功提交的事务都被记录了下来,但是也不是绝对,后面再讲。\n\n**两阶段提交**\n\n这个与MySQL的binlog有关,binlog是MySQL Server层实现的,主要用于数据备份恢复和主从复制。为了保证两份日志的逻辑一致性MySQL采用了两阶段提交,提交过程可简略表示为:\n\n写redo log (第一阶段:prepare)-> 写binlog -> 写redo log (第二阶段:commit)\n\n恢复策略可简略表示为:\n\n- 如果redo log显示处于commit状态,说明redo log和binlog均已记录了事务,则直接提交事务\n\n- 如果redo log显示处于prepare状态,还要看binlog是否记录了完整的事务,如果是那么就提交,否则就回滚事务\n\n这样可以保证直接通过redo log恢复的数据库和通过binlog恢复或者说同步的(从)数据库一致\n\n**组提交**\n\n虽然redo log是顺序写入比随机写入快不少,但总归还是会被内存缓冲的写入速度降维打击。组提交就是用来解决这个瓶颈的,简单来说就将多个事务的写入的重做日志缓冲一次性同步到硬盘。\n\n但是开启binlog后,binlog也有这个瓶颈,并且由于要保持两份日志的一性使得redo log组提交也废了(需要加锁以同步提交顺序和写入顺序,从而导致其他事务无法提交)。于是乎binlog也搞了个组提交,称为BLGC,就是binlog组提交的首字母缩写。\n\nBLGC分为三个阶段:\n\n1. flush阶段,将binlog写入内存\n2. sync,一次性将多个事务的binlog刷新到磁盘(与redo log组提交同理,提高了效率)\n3. commit阶段,按顺序完成事务的提交\n\n最精华的就是最后按顺序完成事务的提交,MySQL将要提交的事务按顺序放入一个队列,队列中的第一个事务称为leader,其他事务称为follower,在最后阶段leader按顺序调用InnoDB事务的提交。\n\n### 特点\n\n如果数据页已经同步到了磁盘(通过LSN判断),真正持久化了,那么对应的 redo log 也就没用了。所以 redo log 有用的往往只有最新的一小段,于是可以采用循环写的方式,整体所占空间是固定的。\n\nredo log的存储都是以 块(block) 为单位进行存储的,每个块的大小为512字节。同磁盘扇区大小一致,可以保证块的写入是原子操作。\n\n### 参数调优\n\n`innodb_flush_log_at_trx_commit`这个参数决定了将redo log缓存刷入磁盘的时机,默认为1,代表提交时立即刷入,0代表交给master thread完成,而master thread每一秒刷入1次,2代表写入文件系统缓存,刷入交给操作系统完成。可以将参数修改为0或2提高数据库性能,但是会失去事物的ACID特性。\n\n### undo log 的作用\n\nundo log的作用有两个:1. 回滚事务。2. 实现MVCC(这个有空另写一篇)\n\n### 工作机制\n\nundo log是逻辑日志,所以进行的是逻辑上的反向操作,插入一条数据就删除一条数据,更新一条数据就反向更新一条数据。由于同时其他事务,并不能保证数据和原来一模一样,因此不能理解成让数据库回到某一状态。\n\n### 特点\n\n由于undo log也需要持久化,所以undo log会产生redo log\n\n事务提交后不能马上删除undo log,因为在MVCC还可能用的到。最后删除时记录还会寻找同一数据页中寻找其他可以删除的记录,这是为了避免随机读写。\n\n### 参数调优\n\n`innodb_purge_batch_size`可以设置每次purge要清理的undo page数量。参数越大则回收的页越多,磁盘空间和分配开销越小,CPU和磁盘IO开销越大。\n\n\n\n","source":"_posts/mysql1.md","raw":"---\ntitle: \"MySQL:redo 和 undo\"\ndate: 2021-04-27T20:55:29+08:00\ncategories:\n- 学习笔记\n- MySQL\ntags:\n- mysql\n- 事务\n- 调优\nkeywords:\n- mysql事务的实现\n- redo log\n- undo log\ncomments: false\n#thumbnailImage: //example.com/image.jpg\nsummary: MySQL通过 redo log (重做日志)和 undo log 实现了事务的持久性、原子性和一致性,本文对这两种日志进行了简单的阐述和总结。\n\n---\n\n<!--more-->\n\n### redo log 作用\n\nredo log 用以保证事务的持久性,当数据库突然宕机后重启就需要使用 redo log 恢复已经提交但是没来得及同步到磁盘上的事务。\n\n再深挖一下为什么数据库宕机重启就需要恢复数据呢,这是因为直接将修改的数据页同步到磁盘意味着频繁的随机写入操作。懂点磁盘运行原理的同学都应该知道这样效率多低,但一直放在内存而不实时刷盘又容易丢失数据。 redo log 就可以解决这个问题,虽然也需要同步到磁盘,但是由于采取顺序写入的方式,所以同时具有效率高和不易失的优点。\n\n这样一来,数据库就可以从容地将修改的数据页写入磁盘而不用怕突然宕机,因为可以根据 redo log 进行恢复。\n\n### 工作机制\n\n在InnoDB中通过Force Log at Commit机制实现事务的持久性,即当事务提交时,必须将重做日志缓冲写入重做日志文件后才算提交成功。这样就能保证成功提交的事务都被记录了下来,但是也不是绝对,后面再讲。\n\n**两阶段提交**\n\n这个与MySQL的binlog有关,binlog是MySQL Server层实现的,主要用于数据备份恢复和主从复制。为了保证两份日志的逻辑一致性MySQL采用了两阶段提交,提交过程可简略表示为:\n\n写redo log (第一阶段:prepare)-> 写binlog -> 写redo log (第二阶段:commit)\n\n恢复策略可简略表示为:\n\n- 如果redo log显示处于commit状态,说明redo log和binlog均已记录了事务,则直接提交事务\n\n- 如果redo log显示处于prepare状态,还要看binlog是否记录了完整的事务,如果是那么就提交,否则就回滚事务\n\n这样可以保证直接通过redo log恢复的数据库和通过binlog恢复或者说同步的(从)数据库一致\n\n**组提交**\n\n虽然redo log是顺序写入比随机写入快不少,但总归还是会被内存缓冲的写入速度降维打击。组提交就是用来解决这个瓶颈的,简单来说就将多个事务的写入的重做日志缓冲一次性同步到硬盘。\n\n但是开启binlog后,binlog也有这个瓶颈,并且由于要保持两份日志的一性使得redo log组提交也废了(需要加锁以同步提交顺序和写入顺序,从而导致其他事务无法提交)。于是乎binlog也搞了个组提交,称为BLGC,就是binlog组提交的首字母缩写。\n\nBLGC分为三个阶段:\n\n1. flush阶段,将binlog写入内存\n2. sync,一次性将多个事务的binlog刷新到磁盘(与redo log组提交同理,提高了效率)\n3. commit阶段,按顺序完成事务的提交\n\n最精华的就是最后按顺序完成事务的提交,MySQL将要提交的事务按顺序放入一个队列,队列中的第一个事务称为leader,其他事务称为follower,在最后阶段leader按顺序调用InnoDB事务的提交。\n\n### 特点\n\n如果数据页已经同步到了磁盘(通过LSN判断),真正持久化了,那么对应的 redo log 也就没用了。所以 redo log 有用的往往只有最新的一小段,于是可以采用循环写的方式,整体所占空间是固定的。\n\nredo log的存储都是以 块(block) 为单位进行存储的,每个块的大小为512字节。同磁盘扇区大小一致,可以保证块的写入是原子操作。\n\n### 参数调优\n\n`innodb_flush_log_at_trx_commit`这个参数决定了将redo log缓存刷入磁盘的时机,默认为1,代表提交时立即刷入,0代表交给master thread完成,而master thread每一秒刷入1次,2代表写入文件系统缓存,刷入交给操作系统完成。可以将参数修改为0或2提高数据库性能,但是会失去事物的ACID特性。\n\n### undo log 的作用\n\nundo log的作用有两个:1. 回滚事务。2. 实现MVCC(这个有空另写一篇)\n\n### 工作机制\n\nundo log是逻辑日志,所以进行的是逻辑上的反向操作,插入一条数据就删除一条数据,更新一条数据就反向更新一条数据。由于同时其他事务,并不能保证数据和原来一模一样,因此不能理解成让数据库回到某一状态。\n\n### 特点\n\n由于undo log也需要持久化,所以undo log会产生redo log\n\n事务提交后不能马上删除undo log,因为在MVCC还可能用的到。最后删除时记录还会寻找同一数据页中寻找其他可以删除的记录,这是为了避免随机读写。\n\n### 参数调优\n\n`innodb_purge_batch_size`可以设置每次purge要清理的undo page数量。参数越大则回收的页越多,磁盘空间和分配开销越小,CPU和磁盘IO开销越大。\n\n\n\n","slug":"mysql1","published":1,"updated":"2021-06-26T04:56:33.378Z","layout":"post","photos":[],"link":"","_id":"ckqh4n0150004ykeabd8ufdrk","content":"<span id=\"more\"></span>\n\n<h3 id=\"redo-log-作用\"><a href=\"#redo-log-作用\" class=\"headerlink\" title=\"redo log 作用\"></a>redo log 作用</h3><p>redo log 用以保证事务的持久性,当数据库突然宕机后重启就需要使用 redo log 恢复已经提交但是没来得及同步到磁盘上的事务。</p>\n<p>再深挖一下为什么数据库宕机重启就需要恢复数据呢,这是因为直接将修改的数据页同步到磁盘意味着频繁的随机写入操作。懂点磁盘运行原理的同学都应该知道这样效率多低,但一直放在内存而不实时刷盘又容易丢失数据。 redo log 就可以解决这个问题,虽然也需要同步到磁盘,但是由于采取顺序写入的方式,所以同时具有效率高和不易失的优点。</p>\n<p>这样一来,数据库就可以从容地将修改的数据页写入磁盘而不用怕突然宕机,因为可以根据 redo log 进行恢复。</p>\n<h3 id=\"工作机制\"><a href=\"#工作机制\" class=\"headerlink\" title=\"工作机制\"></a>工作机制</h3><p>在InnoDB中通过Force Log at Commit机制实现事务的持久性,即当事务提交时,必须将重做日志缓冲写入重做日志文件后才算提交成功。这样就能保证成功提交的事务都被记录了下来,但是也不是绝对,后面再讲。</p>\n<p><strong>两阶段提交</strong></p>\n<p>这个与MySQL的binlog有关,binlog是MySQL Server层实现的,主要用于数据备份恢复和主从复制。为了保证两份日志的逻辑一致性MySQL采用了两阶段提交,提交过程可简略表示为:</p>\n<p>写redo log (第一阶段:prepare)-> 写binlog -> 写redo log (第二阶段:commit)</p>\n<p>恢复策略可简略表示为:</p>\n<ul>\n<li><p>如果redo log显示处于commit状态,说明redo log和binlog均已记录了事务,则直接提交事务</p>\n</li>\n<li><p>如果redo log显示处于prepare状态,还要看binlog是否记录了完整的事务,如果是那么就提交,否则就回滚事务</p>\n</li>\n</ul>\n<p>这样可以保证直接通过redo log恢复的数据库和通过binlog恢复或者说同步的(从)数据库一致</p>\n<p><strong>组提交</strong></p>\n<p>虽然redo log是顺序写入比随机写入快不少,但总归还是会被内存缓冲的写入速度降维打击。组提交就是用来解决这个瓶颈的,简单来说就将多个事务的写入的重做日志缓冲一次性同步到硬盘。</p>\n<p>但是开启binlog后,binlog也有这个瓶颈,并且由于要保持两份日志的一性使得redo log组提交也废了(需要加锁以同步提交顺序和写入顺序,从而导致其他事务无法提交)。于是乎binlog也搞了个组提交,称为BLGC,就是binlog组提交的首字母缩写。</p>\n<p>BLGC分为三个阶段:</p>\n<ol>\n<li>flush阶段,将binlog写入内存</li>\n<li>sync,一次性将多个事务的binlog刷新到磁盘(与redo log组提交同理,提高了效率)</li>\n<li>commit阶段,按顺序完成事务的提交</li>\n</ol>\n<p>最精华的就是最后按顺序完成事务的提交,MySQL将要提交的事务按顺序放入一个队列,队列中的第一个事务称为leader,其他事务称为follower,在最后阶段leader按顺序调用InnoDB事务的提交。</p>\n<h3 id=\"特点\"><a href=\"#特点\" class=\"headerlink\" title=\"特点\"></a>特点</h3><p>如果数据页已经同步到了磁盘(通过LSN判断),真正持久化了,那么对应的 redo log 也就没用了。所以 redo log 有用的往往只有最新的一小段,于是可以采用循环写的方式,整体所占空间是固定的。</p>\n<p>redo log的存储都是以 块(block) 为单位进行存储的,每个块的大小为512字节。同磁盘扇区大小一致,可以保证块的写入是原子操作。</p>\n<h3 id=\"参数调优\"><a href=\"#参数调优\" class=\"headerlink\" title=\"参数调优\"></a>参数调优</h3><p><code>innodb_flush_log_at_trx_commit</code>这个参数决定了将redo log缓存刷入磁盘的时机,默认为1,代表提交时立即刷入,0代表交给master thread完成,而master thread每一秒刷入1次,2代表写入文件系统缓存,刷入交给操作系统完成。可以将参数修改为0或2提高数据库性能,但是会失去事物的ACID特性。</p>\n<h3 id=\"undo-log-的作用\"><a href=\"#undo-log-的作用\" class=\"headerlink\" title=\"undo log 的作用\"></a>undo log 的作用</h3><p>undo log的作用有两个:1. 回滚事务。2. 实现MVCC(这个有空另写一篇)</p>\n<h3 id=\"工作机制-1\"><a href=\"#工作机制-1\" class=\"headerlink\" title=\"工作机制\"></a>工作机制</h3><p>undo log是逻辑日志,所以进行的是逻辑上的反向操作,插入一条数据就删除一条数据,更新一条数据就反向更新一条数据。由于同时其他事务,并不能保证数据和原来一模一样,因此不能理解成让数据库回到某一状态。</p>\n<h3 id=\"特点-1\"><a href=\"#特点-1\" class=\"headerlink\" title=\"特点\"></a>特点</h3><p>由于undo log也需要持久化,所以undo log会产生redo log</p>\n<p>事务提交后不能马上删除undo log,因为在MVCC还可能用的到。最后删除时记录还会寻找同一数据页中寻找其他可以删除的记录,这是为了避免随机读写。</p>\n<h3 id=\"参数调优-1\"><a href=\"#参数调优-1\" class=\"headerlink\" title=\"参数调优\"></a>参数调优</h3><p><code>innodb_purge_batch_size</code>可以设置每次purge要清理的undo page数量。参数越大则回收的页越多,磁盘空间和分配开销越小,CPU和磁盘IO开销越大。</p>\n","site":{"data":{}},"excerpt":"","more":"<h3 id=\"redo-log-作用\"><a href=\"#redo-log-作用\" class=\"headerlink\" title=\"redo log 作用\"></a>redo log 作用</h3><p>redo log 用以保证事务的持久性,当数据库突然宕机后重启就需要使用 redo log 恢复已经提交但是没来得及同步到磁盘上的事务。</p>\n<p>再深挖一下为什么数据库宕机重启就需要恢复数据呢,这是因为直接将修改的数据页同步到磁盘意味着频繁的随机写入操作。懂点磁盘运行原理的同学都应该知道这样效率多低,但一直放在内存而不实时刷盘又容易丢失数据。 redo log 就可以解决这个问题,虽然也需要同步到磁盘,但是由于采取顺序写入的方式,所以同时具有效率高和不易失的优点。</p>\n<p>这样一来,数据库就可以从容地将修改的数据页写入磁盘而不用怕突然宕机,因为可以根据 redo log 进行恢复。</p>\n<h3 id=\"工作机制\"><a href=\"#工作机制\" class=\"headerlink\" title=\"工作机制\"></a>工作机制</h3><p>在InnoDB中通过Force Log at Commit机制实现事务的持久性,即当事务提交时,必须将重做日志缓冲写入重做日志文件后才算提交成功。这样就能保证成功提交的事务都被记录了下来,但是也不是绝对,后面再讲。</p>\n<p><strong>两阶段提交</strong></p>\n<p>这个与MySQL的binlog有关,binlog是MySQL Server层实现的,主要用于数据备份恢复和主从复制。为了保证两份日志的逻辑一致性MySQL采用了两阶段提交,提交过程可简略表示为:</p>\n<p>写redo log (第一阶段:prepare)-> 写binlog -> 写redo log (第二阶段:commit)</p>\n<p>恢复策略可简略表示为:</p>\n<ul>\n<li><p>如果redo log显示处于commit状态,说明redo log和binlog均已记录了事务,则直接提交事务</p>\n</li>\n<li><p>如果redo log显示处于prepare状态,还要看binlog是否记录了完整的事务,如果是那么就提交,否则就回滚事务</p>\n</li>\n</ul>\n<p>这样可以保证直接通过redo log恢复的数据库和通过binlog恢复或者说同步的(从)数据库一致</p>\n<p><strong>组提交</strong></p>\n<p>虽然redo log是顺序写入比随机写入快不少,但总归还是会被内存缓冲的写入速度降维打击。组提交就是用来解决这个瓶颈的,简单来说就将多个事务的写入的重做日志缓冲一次性同步到硬盘。</p>\n<p>但是开启binlog后,binlog也有这个瓶颈,并且由于要保持两份日志的一性使得redo log组提交也废了(需要加锁以同步提交顺序和写入顺序,从而导致其他事务无法提交)。于是乎binlog也搞了个组提交,称为BLGC,就是binlog组提交的首字母缩写。</p>\n<p>BLGC分为三个阶段:</p>\n<ol>\n<li>flush阶段,将binlog写入内存</li>\n<li>sync,一次性将多个事务的binlog刷新到磁盘(与redo log组提交同理,提高了效率)</li>\n<li>commit阶段,按顺序完成事务的提交</li>\n</ol>\n<p>最精华的就是最后按顺序完成事务的提交,MySQL将要提交的事务按顺序放入一个队列,队列中的第一个事务称为leader,其他事务称为follower,在最后阶段leader按顺序调用InnoDB事务的提交。</p>\n<h3 id=\"特点\"><a href=\"#特点\" class=\"headerlink\" title=\"特点\"></a>特点</h3><p>如果数据页已经同步到了磁盘(通过LSN判断),真正持久化了,那么对应的 redo log 也就没用了。所以 redo log 有用的往往只有最新的一小段,于是可以采用循环写的方式,整体所占空间是固定的。</p>\n<p>redo log的存储都是以 块(block) 为单位进行存储的,每个块的大小为512字节。同磁盘扇区大小一致,可以保证块的写入是原子操作。</p>\n<h3 id=\"参数调优\"><a href=\"#参数调优\" class=\"headerlink\" title=\"参数调优\"></a>参数调优</h3><p><code>innodb_flush_log_at_trx_commit</code>这个参数决定了将redo log缓存刷入磁盘的时机,默认为1,代表提交时立即刷入,0代表交给master thread完成,而master thread每一秒刷入1次,2代表写入文件系统缓存,刷入交给操作系统完成。可以将参数修改为0或2提高数据库性能,但是会失去事物的ACID特性。</p>\n<h3 id=\"undo-log-的作用\"><a href=\"#undo-log-的作用\" class=\"headerlink\" title=\"undo log 的作用\"></a>undo log 的作用</h3><p>undo log的作用有两个:1. 回滚事务。2. 实现MVCC(这个有空另写一篇)</p>\n<h3 id=\"工作机制-1\"><a href=\"#工作机制-1\" class=\"headerlink\" title=\"工作机制\"></a>工作机制</h3><p>undo log是逻辑日志,所以进行的是逻辑上的反向操作,插入一条数据就删除一条数据,更新一条数据就反向更新一条数据。由于同时其他事务,并不能保证数据和原来一模一样,因此不能理解成让数据库回到某一状态。</p>\n<h3 id=\"特点-1\"><a href=\"#特点-1\" class=\"headerlink\" title=\"特点\"></a>特点</h3><p>由于undo log也需要持久化,所以undo log会产生redo log</p>\n<p>事务提交后不能马上删除undo log,因为在MVCC还可能用的到。最后删除时记录还会寻找同一数据页中寻找其他可以删除的记录,这是为了避免随机读写。</p>\n<h3 id=\"参数调优-1\"><a href=\"#参数调优-1\" class=\"headerlink\" title=\"参数调优\"></a>参数调优</h3><p><code>innodb_purge_batch_size</code>可以设置每次purge要清理的undo page数量。参数越大则回收的页越多,磁盘空间和分配开销越小,CPU和磁盘IO开销越大。</p>"},{"title":"MySQL:MVCC","date":"2021-05-05T05:30:02.000Z","keywords":["MySQL多版本控制","MVCC"],"comments":0,"summary":"MVCC是MySQL多版本并发控制(Multi-Version Concurrency Control)的缩写。同文浅谈MVCC如何基于版本而不是基于锁来实现并发控制。","_content":"\n<!--more-->\n\n### 实现原理\n\ninnoDB的行记录格式中有6字节事务ID的和7字节的回滚指针,通过为每一行记录添加这两个额外的隐藏值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。\n\n但是InnoDB并不存储这些事件发生时的实际时间,相反它只存储这些事件发生时的系统版本号。这是一个随着事务的创建而不断增长的数字。每个事务在事务开始时会记录它自己的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同。\n\n当隔离级别是REPEATABLE READ时这种策略是如何应用到特定的操作的。\n\n**SELECT**\n\n当隔离级别是REPEATABLE READ时select操作,InnoDB必须每行数据来保证它符合两个条件:\n\n1. InnoDB必须找到一个行的版本,它至少要和事务的版本一样老(也即它的版本号不大于事务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这行数据是存在的。\n2. 这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这行数据没有被删除。\n 符合这两个条件的行可能会被当作查询结果而返回。\n\n**INSERT**\n\nInnoDB为这个新行记录当前的系统版本号。\n\n**DELETE**\n\nInnoDB将当前的系统版本号设置为这一行的删除ID。\n\n**UPDATE**\n\nInnoDB会写一个这行数据的新拷贝,这个拷贝的版本为当前的系统版本号。它同时也会将这个版本号写到旧行的删除版本里。\n\n当记录不满足SELECT的第一个条件时,就读取它的历史版本,而具有回滚作用的`undo log`就在这里用上了。\n\n### 优缺点\n\n这种额外的记录所带来的结果就是对于大多数查询来说根本就不需要获得一个锁。他们只是简单地以最快的速度来读取数据,确保只选择符合条件的行。这个方案的缺点在于存储引擎必须为每一行存储更多的数据,做更多的检查工作,处理更多的善后操作。\n\n### 兼容性\n\nMVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下。READ UNCOMMITED不是MVCC兼容的,因为查询不能找到适合他们事务版本的行版本;它们每次都只能读到最新的版本。SERIABLABLE也不与MVCC兼容,因为读操作会锁定他们返回的每一行数据。\n\n\n\n","source":"_posts/mysql2.md","raw":"---\ntitle: \"MySQL:MVCC\"\ndate: 2021-05-05T21:30:02+08:00\ncategories:\n- 学习笔记\n- mysql\ntags:\n- mysql实现原理\nkeywords:\n- MySQL多版本控制\n- MVCC\ncomments: false\nsummary: MVCC是MySQL多版本并发控制(Multi-Version Concurrency Control)的缩写。同文浅谈MVCC如何基于版本而不是基于锁来实现并发控制。\n#thumbnailImage: //example.com/image.jpg\n---\n\n<!--more-->\n\n### 实现原理\n\ninnoDB的行记录格式中有6字节事务ID的和7字节的回滚指针,通过为每一行记录添加这两个额外的隐藏值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。\n\n但是InnoDB并不存储这些事件发生时的实际时间,相反它只存储这些事件发生时的系统版本号。这是一个随着事务的创建而不断增长的数字。每个事务在事务开始时会记录它自己的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同。\n\n当隔离级别是REPEATABLE READ时这种策略是如何应用到特定的操作的。\n\n**SELECT**\n\n当隔离级别是REPEATABLE READ时select操作,InnoDB必须每行数据来保证它符合两个条件:\n\n1. InnoDB必须找到一个行的版本,它至少要和事务的版本一样老(也即它的版本号不大于事务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这行数据是存在的。\n2. 这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这行数据没有被删除。\n 符合这两个条件的行可能会被当作查询结果而返回。\n\n**INSERT**\n\nInnoDB为这个新行记录当前的系统版本号。\n\n**DELETE**\n\nInnoDB将当前的系统版本号设置为这一行的删除ID。\n\n**UPDATE**\n\nInnoDB会写一个这行数据的新拷贝,这个拷贝的版本为当前的系统版本号。它同时也会将这个版本号写到旧行的删除版本里。\n\n当记录不满足SELECT的第一个条件时,就读取它的历史版本,而具有回滚作用的`undo log`就在这里用上了。\n\n### 优缺点\n\n这种额外的记录所带来的结果就是对于大多数查询来说根本就不需要获得一个锁。他们只是简单地以最快的速度来读取数据,确保只选择符合条件的行。这个方案的缺点在于存储引擎必须为每一行存储更多的数据,做更多的检查工作,处理更多的善后操作。\n\n### 兼容性\n\nMVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下。READ UNCOMMITED不是MVCC兼容的,因为查询不能找到适合他们事务版本的行版本;它们每次都只能读到最新的版本。SERIABLABLE也不与MVCC兼容,因为读操作会锁定他们返回的每一行数据。\n\n\n\n","slug":"mysql2","published":1,"updated":"2021-06-26T04:58:07.354Z","layout":"post","photos":[],"link":"","_id":"ckqh4n0190005ykeaa5d2hx18","content":"<span id=\"more\"></span>\n\n<h3 id=\"实现原理\"><a href=\"#实现原理\" class=\"headerlink\" title=\"实现原理\"></a>实现原理</h3><p>innoDB的行记录格式中有6字节事务ID的和7字节的回滚指针,通过为每一行记录添加这两个额外的隐藏值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。</p>\n<p>但是InnoDB并不存储这些事件发生时的实际时间,相反它只存储这些事件发生时的系统版本号。这是一个随着事务的创建而不断增长的数字。每个事务在事务开始时会记录它自己的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同。</p>\n<p>当隔离级别是REPEATABLE READ时这种策略是如何应用到特定的操作的。</p>\n<p><strong>SELECT</strong></p>\n<p>当隔离级别是REPEATABLE READ时select操作,InnoDB必须每行数据来保证它符合两个条件:</p>\n<ol>\n<li>InnoDB必须找到一个行的版本,它至少要和事务的版本一样老(也即它的版本号不大于事务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这行数据是存在的。</li>\n<li>这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这行数据没有被删除。<br>符合这两个条件的行可能会被当作查询结果而返回。</li>\n</ol>\n<p><strong>INSERT</strong></p>\n<p>InnoDB为这个新行记录当前的系统版本号。</p>\n<p><strong>DELETE</strong></p>\n<p>InnoDB将当前的系统版本号设置为这一行的删除ID。</p>\n<p><strong>UPDATE</strong></p>\n<p>InnoDB会写一个这行数据的新拷贝,这个拷贝的版本为当前的系统版本号。它同时也会将这个版本号写到旧行的删除版本里。</p>\n<p>当记录不满足SELECT的第一个条件时,就读取它的历史版本,而具有回滚作用的<code>undo log</code>就在这里用上了。</p>\n<h3 id=\"优缺点\"><a href=\"#优缺点\" class=\"headerlink\" title=\"优缺点\"></a>优缺点</h3><p>这种额外的记录所带来的结果就是对于大多数查询来说根本就不需要获得一个锁。他们只是简单地以最快的速度来读取数据,确保只选择符合条件的行。这个方案的缺点在于存储引擎必须为每一行存储更多的数据,做更多的检查工作,处理更多的善后操作。</p>\n<h3 id=\"兼容性\"><a href=\"#兼容性\" class=\"headerlink\" title=\"兼容性\"></a>兼容性</h3><p>MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下。READ UNCOMMITED不是MVCC兼容的,因为查询不能找到适合他们事务版本的行版本;它们每次都只能读到最新的版本。SERIABLABLE也不与MVCC兼容,因为读操作会锁定他们返回的每一行数据。</p>\n","site":{"data":{}},"excerpt":"","more":"<h3 id=\"实现原理\"><a href=\"#实现原理\" class=\"headerlink\" title=\"实现原理\"></a>实现原理</h3><p>innoDB的行记录格式中有6字节事务ID的和7字节的回滚指针,通过为每一行记录添加这两个额外的隐藏值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。</p>\n<p>但是InnoDB并不存储这些事件发生时的实际时间,相反它只存储这些事件发生时的系统版本号。这是一个随着事务的创建而不断增长的数字。每个事务在事务开始时会记录它自己的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同。</p>\n<p>当隔离级别是REPEATABLE READ时这种策略是如何应用到特定的操作的。</p>\n<p><strong>SELECT</strong></p>\n<p>当隔离级别是REPEATABLE READ时select操作,InnoDB必须每行数据来保证它符合两个条件:</p>\n<ol>\n<li>InnoDB必须找到一个行的版本,它至少要和事务的版本一样老(也即它的版本号不大于事务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这行数据是存在的。</li>\n<li>这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这行数据没有被删除。<br>符合这两个条件的行可能会被当作查询结果而返回。</li>\n</ol>\n<p><strong>INSERT</strong></p>\n<p>InnoDB为这个新行记录当前的系统版本号。</p>\n<p><strong>DELETE</strong></p>\n<p>InnoDB将当前的系统版本号设置为这一行的删除ID。</p>\n<p><strong>UPDATE</strong></p>\n<p>InnoDB会写一个这行数据的新拷贝,这个拷贝的版本为当前的系统版本号。它同时也会将这个版本号写到旧行的删除版本里。</p>\n<p>当记录不满足SELECT的第一个条件时,就读取它的历史版本,而具有回滚作用的<code>undo log</code>就在这里用上了。</p>\n<h3 id=\"优缺点\"><a href=\"#优缺点\" class=\"headerlink\" title=\"优缺点\"></a>优缺点</h3><p>这种额外的记录所带来的结果就是对于大多数查询来说根本就不需要获得一个锁。他们只是简单地以最快的速度来读取数据,确保只选择符合条件的行。这个方案的缺点在于存储引擎必须为每一行存储更多的数据,做更多的检查工作,处理更多的善后操作。</p>\n<h3 id=\"兼容性\"><a href=\"#兼容性\" class=\"headerlink\" title=\"兼容性\"></a>兼容性</h3><p>MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下。READ UNCOMMITED不是MVCC兼容的,因为查询不能找到适合他们事务版本的行版本;它们每次都只能读到最新的版本。SERIABLABLE也不与MVCC兼容,因为读操作会锁定他们返回的每一行数据。</p>"},{"title":"TCP:握手挥手","date":"2021-04-18T05:08:29.000Z","keywords":["tcp","三次握手","四次挥手"],"comments":0,"summary":"最近看了一些网络方面的博客和书,以前在这方面总是似懂非懂,现在换了个角度去重新认识和学习总算搞清楚了一点。就从非常基础的TCP三次握手四次挥手过程开始,梳理网络知识。","_content":"\n<!--more-->\n\n### 什么是三次握手和四次挥手\n\n三次握手和四次挥手是http建立连接和关闭连接过程的形象描述。这个说法比较有名,因为我们通常喜欢以类比的方式来认识未知的事物。\n\n但是从本质原因出发去认识事物或许可以获得更深刻的认识。\n\n### 为什么要三次握手\n\n**主要是因为要防止旧的重复连接初始化造成混乱**\n\n在网络状况较差的情况下,客户端在和服务端建立连接前可能会多次发起连接,为了防止因此引起混乱就需要有能够及时中止错误连接的机制。\n\n首先要明白的一点是服务端无法知道当前收到的连接请求报文是否是最新的,所以服务端要向客户端确认是否为历史连接。\n\n那么客户端怎么判断是否为历史连接呢?简单来说就是客户端随机发给服务端一个数,服务端把这个数加一发还给客户端,客户端判断发送的数和收到的数和收到的数是否满足加一关系就可以判断是否为历史连接。\n\n相对具体点的流程如下:\n\n1. 客户端发送SYN报文,其中包含客户端初始序列号seq_c\n2. 服务端收到SYN报文,发送ACK确认报文,其中包含序列号seq_c+1\n3. 服务端发送SYN报文,其中包含服务端初始序列号seq_s\n4. 客户端收到ACK确认报文和SYN报文\n - 如果收到seq_c+1,发送ACK确认报文,包含序列号seq_s+1,连接建立\n - 否则,发送RST报文中止连接\n\n其中有两点要**注意**:\n\n- 因为第二步和第三步都是由服务端发送报文,所以可以并到一起,**最终只要三次通信**。\n- 服务端也要发送自己的初始序列号,因为序列号在之后的通信中也要用到。\n\n### 为什么要四次挥手\n\nTCP连接是双向的,所以关闭连接同样需要双向确认。一方发起关闭连接请求报文,一方确认,从而关闭一个方向的连接。这个过程重复两次就有了四次挥手。\n\n相对具体点的流程如下:\n\n1. A端发送FIN报文\n2. B端收到FIN报文,发送ACK确认报文\n3. 等待一端时间后,B端发送FIN报文\n4. A端收到FIN报文,发送ACK确认报文并等待一段时间\n\n其中两段等待时间需要注意:\n\n- 第三步中的需要等待时间是因为TCP协议栈不能替上层应用做出关闭连接的决定,所以要等上层应用反应,这也是不能合并为三次通信的原因\n- 第四步需要等待时间有两个原因\n - 防止旧连接延迟到达的包影响新连接,空窗期可以直接丢弃这些包\n - 保证对方连接正确关闭,如果最后的ACK确认报文丢了,还可以处理重发的FIN包\n\n当然最后的等待时间太长也不好,端口资源是有限的,占着茅坑不拉屎容易出问题。\n\n","source":"_posts/network1.md","raw":"---\ntitle: \"TCP:握手挥手\"\ndate: 2021-04-18T21:08:29+08:00\ncategories:\n- 学习笔记\n- 网络\ntags:\n- tcp\nkeywords:\n- tcp\n- 三次握手\n- 四次挥手\n#thumbnailImage: /images/host/misc/tcp.png\ncomments: false\nsummary: 最近看了一些网络方面的博客和书,以前在这方面总是似懂非懂,现在换了个角度去重新认识和学习总算搞清楚了一点。就从非常基础的TCP三次握手四次挥手过程开始,梳理网络知识。\n---\n\n<!--more-->\n\n### 什么是三次握手和四次挥手\n\n三次握手和四次挥手是http建立连接和关闭连接过程的形象描述。这个说法比较有名,因为我们通常喜欢以类比的方式来认识未知的事物。\n\n但是从本质原因出发去认识事物或许可以获得更深刻的认识。\n\n### 为什么要三次握手\n\n**主要是因为要防止旧的重复连接初始化造成混乱**\n\n在网络状况较差的情况下,客户端在和服务端建立连接前可能会多次发起连接,为了防止因此引起混乱就需要有能够及时中止错误连接的机制。\n\n首先要明白的一点是服务端无法知道当前收到的连接请求报文是否是最新的,所以服务端要向客户端确认是否为历史连接。\n\n那么客户端怎么判断是否为历史连接呢?简单来说就是客户端随机发给服务端一个数,服务端把这个数加一发还给客户端,客户端判断发送的数和收到的数和收到的数是否满足加一关系就可以判断是否为历史连接。\n\n相对具体点的流程如下:\n\n1. 客户端发送SYN报文,其中包含客户端初始序列号seq_c\n2. 服务端收到SYN报文,发送ACK确认报文,其中包含序列号seq_c+1\n3. 服务端发送SYN报文,其中包含服务端初始序列号seq_s\n4. 客户端收到ACK确认报文和SYN报文\n - 如果收到seq_c+1,发送ACK确认报文,包含序列号seq_s+1,连接建立\n - 否则,发送RST报文中止连接\n\n其中有两点要**注意**:\n\n- 因为第二步和第三步都是由服务端发送报文,所以可以并到一起,**最终只要三次通信**。\n- 服务端也要发送自己的初始序列号,因为序列号在之后的通信中也要用到。\n\n### 为什么要四次挥手\n\nTCP连接是双向的,所以关闭连接同样需要双向确认。一方发起关闭连接请求报文,一方确认,从而关闭一个方向的连接。这个过程重复两次就有了四次挥手。\n\n相对具体点的流程如下:\n\n1. A端发送FIN报文\n2. B端收到FIN报文,发送ACK确认报文\n3. 等待一端时间后,B端发送FIN报文\n4. A端收到FIN报文,发送ACK确认报文并等待一段时间\n\n其中两段等待时间需要注意:\n\n- 第三步中的需要等待时间是因为TCP协议栈不能替上层应用做出关闭连接的决定,所以要等上层应用反应,这也是不能合并为三次通信的原因\n- 第四步需要等待时间有两个原因\n - 防止旧连接延迟到达的包影响新连接,空窗期可以直接丢弃这些包\n - 保证对方连接正确关闭,如果最后的ACK确认报文丢了,还可以处理重发的FIN包\n\n当然最后的等待时间太长也不好,端口资源是有限的,占着茅坑不拉屎容易出问题。\n\n","slug":"network1","published":1,"updated":"2021-06-26T04:56:42.484Z","layout":"post","photos":[],"link":"","_id":"ckqh4n01d0006ykea72sob0i7","content":"<span id=\"more\"></span>\n\n<h3 id=\"什么是三次握手和四次挥手\"><a href=\"#什么是三次握手和四次挥手\" class=\"headerlink\" title=\"什么是三次握手和四次挥手\"></a>什么是三次握手和四次挥手</h3><p>三次握手和四次挥手是http建立连接和关闭连接过程的形象描述。这个说法比较有名,因为我们通常喜欢以类比的方式来认识未知的事物。</p>\n<p>但是从本质原因出发去认识事物或许可以获得更深刻的认识。</p>\n<h3 id=\"为什么要三次握手\"><a href=\"#为什么要三次握手\" class=\"headerlink\" title=\"为什么要三次握手\"></a>为什么要三次握手</h3><p><strong>主要是因为要防止旧的重复连接初始化造成混乱</strong></p>\n<p>在网络状况较差的情况下,客户端在和服务端建立连接前可能会多次发起连接,为了防止因此引起混乱就需要有能够及时中止错误连接的机制。</p>\n<p>首先要明白的一点是服务端无法知道当前收到的连接请求报文是否是最新的,所以服务端要向客户端确认是否为历史连接。</p>\n<p>那么客户端怎么判断是否为历史连接呢?简单来说就是客户端随机发给服务端一个数,服务端把这个数加一发还给客户端,客户端判断发送的数和收到的数和收到的数是否满足加一关系就可以判断是否为历史连接。</p>\n<p>相对具体点的流程如下:</p>\n<ol>\n<li>客户端发送SYN报文,其中包含客户端初始序列号seq_c</li>\n<li>服务端收到SYN报文,发送ACK确认报文,其中包含序列号seq_c+1</li>\n<li>服务端发送SYN报文,其中包含服务端初始序列号seq_s</li>\n<li>客户端收到ACK确认报文和SYN报文<ul>\n<li>如果收到seq_c+1,发送ACK确认报文,包含序列号seq_s+1,连接建立</li>\n<li>否则,发送RST报文中止连接</li>\n</ul>\n</li>\n</ol>\n<p>其中有两点要<strong>注意</strong>:</p>\n<ul>\n<li>因为第二步和第三步都是由服务端发送报文,所以可以并到一起,<strong>最终只要三次通信</strong>。</li>\n<li>服务端也要发送自己的初始序列号,因为序列号在之后的通信中也要用到。</li>\n</ul>\n<h3 id=\"为什么要四次挥手\"><a href=\"#为什么要四次挥手\" class=\"headerlink\" title=\"为什么要四次挥手\"></a>为什么要四次挥手</h3><p>TCP连接是双向的,所以关闭连接同样需要双向确认。一方发起关闭连接请求报文,一方确认,从而关闭一个方向的连接。这个过程重复两次就有了四次挥手。</p>\n<p>相对具体点的流程如下:</p>\n<ol>\n<li>A端发送FIN报文</li>\n<li>B端收到FIN报文,发送ACK确认报文</li>\n<li>等待一端时间后,B端发送FIN报文</li>\n<li>A端收到FIN报文,发送ACK确认报文并等待一段时间</li>\n</ol>\n<p>其中两段等待时间需要注意:</p>\n<ul>\n<li>第三步中的需要等待时间是因为TCP协议栈不能替上层应用做出关闭连接的决定,所以要等上层应用反应,这也是不能合并为三次通信的原因</li>\n<li>第四步需要等待时间有两个原因<ul>\n<li>防止旧连接延迟到达的包影响新连接,空窗期可以直接丢弃这些包</li>\n<li>保证对方连接正确关闭,如果最后的ACK确认报文丢了,还可以处理重发的FIN包</li>\n</ul>\n</li>\n</ul>\n<p>当然最后的等待时间太长也不好,端口资源是有限的,占着茅坑不拉屎容易出问题。</p>\n","site":{"data":{}},"excerpt":"","more":"<h3 id=\"什么是三次握手和四次挥手\"><a href=\"#什么是三次握手和四次挥手\" class=\"headerlink\" title=\"什么是三次握手和四次挥手\"></a>什么是三次握手和四次挥手</h3><p>三次握手和四次挥手是http建立连接和关闭连接过程的形象描述。这个说法比较有名,因为我们通常喜欢以类比的方式来认识未知的事物。</p>\n<p>但是从本质原因出发去认识事物或许可以获得更深刻的认识。</p>\n<h3 id=\"为什么要三次握手\"><a href=\"#为什么要三次握手\" class=\"headerlink\" title=\"为什么要三次握手\"></a>为什么要三次握手</h3><p><strong>主要是因为要防止旧的重复连接初始化造成混乱</strong></p>\n<p>在网络状况较差的情况下,客户端在和服务端建立连接前可能会多次发起连接,为了防止因此引起混乱就需要有能够及时中止错误连接的机制。</p>\n<p>首先要明白的一点是服务端无法知道当前收到的连接请求报文是否是最新的,所以服务端要向客户端确认是否为历史连接。</p>\n<p>那么客户端怎么判断是否为历史连接呢?简单来说就是客户端随机发给服务端一个数,服务端把这个数加一发还给客户端,客户端判断发送的数和收到的数和收到的数是否满足加一关系就可以判断是否为历史连接。</p>\n<p>相对具体点的流程如下:</p>\n<ol>\n<li>客户端发送SYN报文,其中包含客户端初始序列号seq_c</li>\n<li>服务端收到SYN报文,发送ACK确认报文,其中包含序列号seq_c+1</li>\n<li>服务端发送SYN报文,其中包含服务端初始序列号seq_s</li>\n<li>客户端收到ACK确认报文和SYN报文<ul>\n<li>如果收到seq_c+1,发送ACK确认报文,包含序列号seq_s+1,连接建立</li>\n<li>否则,发送RST报文中止连接</li>\n</ul>\n</li>\n</ol>\n<p>其中有两点要<strong>注意</strong>:</p>\n<ul>\n<li>因为第二步和第三步都是由服务端发送报文,所以可以并到一起,<strong>最终只要三次通信</strong>。</li>\n<li>服务端也要发送自己的初始序列号,因为序列号在之后的通信中也要用到。</li>\n</ul>\n<h3 id=\"为什么要四次挥手\"><a href=\"#为什么要四次挥手\" class=\"headerlink\" title=\"为什么要四次挥手\"></a>为什么要四次挥手</h3><p>TCP连接是双向的,所以关闭连接同样需要双向确认。一方发起关闭连接请求报文,一方确认,从而关闭一个方向的连接。这个过程重复两次就有了四次挥手。</p>\n<p>相对具体点的流程如下:</p>\n<ol>\n<li>A端发送FIN报文</li>\n<li>B端收到FIN报文,发送ACK确认报文</li>\n<li>等待一端时间后,B端发送FIN报文</li>\n<li>A端收到FIN报文,发送ACK确认报文并等待一段时间</li>\n</ol>\n<p>其中两段等待时间需要注意:</p>\n<ul>\n<li>第三步中的需要等待时间是因为TCP协议栈不能替上层应用做出关闭连接的决定,所以要等上层应用反应,这也是不能合并为三次通信的原因</li>\n<li>第四步需要等待时间有两个原因<ul>\n<li>防止旧连接延迟到达的包影响新连接,空窗期可以直接丢弃这些包</li>\n<li>保证对方连接正确关闭,如果最后的ACK确认报文丢了,还可以处理重发的FIN包</li>\n</ul>\n</li>\n</ul>\n<p>当然最后的等待时间太长也不好,端口资源是有限的,占着茅坑不拉屎容易出问题。</p>"},{"title":"TCP:序列号与滑动窗口","date":"2021-04-20T05:40:26.000Z","keywords":["tcp","序列号","滑动窗口"],"comments":0,"summary":"tcp最重要的特征就是面向连接和可靠,本文尝试用比较容易理解的方式讲解tcp是怎么做到可靠传输的。","_content":"\n<!--more-->\n\n### 序列号的作用\n\n上一篇博客中提到了序列号在建立tcp连接中的作用。不光如此,序列号还是tcp可靠传输的基础。\n\n为了可靠传输就要防止丢包,所以接受端需要向发送端确认,就像我们收到快递包裹也要确认收货。类比来讲,序列号就像是快递单号。\n\n但是与现实不同的是,tcp包数量庞大。这就造成:\n\n- 单个单个确认会**浪费网络资源**\n- 发送端在等待确认时无事可做会**浪费时间和硬件资源**\n\n所以最好批量发送和确认,发送端一次性发送一组tcp包,接受端一次性确认一组tcp包。\n\n### 怎么一次性确认多个包\n\n很简单,序列号设计为连续的,确认顺序最靠后的包就代表收到了之前所有的包。考虑到可能出现丢包,如果收到如下一组包(数字代表包的序列号):\n\n{... ...,233,234,235,237,238}\n\n此时为了保证收到缺失的包应确认235,而237,238都会当做没收到,这样发送端就会再次发送235之后的包。为了保证可靠性,这么做是值得的。\n\n实际中确认报文(ACK)发送的序列号要加1,表示从这个序列号的包开始发送。\n\n### 滑动窗口的作用\n\n在批量发包时,一次性发多少数量是个问题,滑动窗口就代表这个数量。由于主要需要考虑接受端的能力,所以由接收端发送给发送端(两端间会往来数据,这里指广义上的数据接受端和发送端)。\n\n发送端收到窗口大小后,就会计算发了多少包,剩余多少容量以保证保证不超过窗口大小,直到窗口更新。\n\n### 发送的时机\n\n将窗口更新和ACK报文合并发送可以提高网络效率。\n\n","source":"_posts/network2.md","raw":"---\ntitle: \"TCP:序列号与滑动窗口\"\ndate: 2021-04-20T21:40:26+08:00\ncategories:\n- 学习笔记\n- 网络\ntags:\n- tcp\nkeywords:\n- tcp\n- 序列号\n- 滑动窗口\ncomments: false\n#thumbnailImage: //example.com/image.jpg\nsummary: tcp最重要的特征就是面向连接和可靠,本文尝试用比较容易理解的方式讲解tcp是怎么做到可靠传输的。\n---\n\n<!--more-->\n\n### 序列号的作用\n\n上一篇博客中提到了序列号在建立tcp连接中的作用。不光如此,序列号还是tcp可靠传输的基础。\n\n为了可靠传输就要防止丢包,所以接受端需要向发送端确认,就像我们收到快递包裹也要确认收货。类比来讲,序列号就像是快递单号。\n\n但是与现实不同的是,tcp包数量庞大。这就造成:\n\n- 单个单个确认会**浪费网络资源**\n- 发送端在等待确认时无事可做会**浪费时间和硬件资源**\n\n所以最好批量发送和确认,发送端一次性发送一组tcp包,接受端一次性确认一组tcp包。\n\n### 怎么一次性确认多个包\n\n很简单,序列号设计为连续的,确认顺序最靠后的包就代表收到了之前所有的包。考虑到可能出现丢包,如果收到如下一组包(数字代表包的序列号):\n\n{... ...,233,234,235,237,238}\n\n此时为了保证收到缺失的包应确认235,而237,238都会当做没收到,这样发送端就会再次发送235之后的包。为了保证可靠性,这么做是值得的。\n\n实际中确认报文(ACK)发送的序列号要加1,表示从这个序列号的包开始发送。\n\n### 滑动窗口的作用\n\n在批量发包时,一次性发多少数量是个问题,滑动窗口就代表这个数量。由于主要需要考虑接受端的能力,所以由接收端发送给发送端(两端间会往来数据,这里指广义上的数据接受端和发送端)。\n\n发送端收到窗口大小后,就会计算发了多少包,剩余多少容量以保证保证不超过窗口大小,直到窗口更新。\n\n### 发送的时机\n\n将窗口更新和ACK报文合并发送可以提高网络效率。\n\n","slug":"network2","published":1,"updated":"2021-06-26T04:56:46.044Z","layout":"post","photos":[],"link":"","_id":"ckqh4n01j0009ykea2b428t5o","content":"<span id=\"more\"></span>\n\n<h3 id=\"序列号的作用\"><a href=\"#序列号的作用\" class=\"headerlink\" title=\"序列号的作用\"></a>序列号的作用</h3><p>上一篇博客中提到了序列号在建立tcp连接中的作用。不光如此,序列号还是tcp可靠传输的基础。</p>\n<p>为了可靠传输就要防止丢包,所以接受端需要向发送端确认,就像我们收到快递包裹也要确认收货。类比来讲,序列号就像是快递单号。</p>\n<p>但是与现实不同的是,tcp包数量庞大。这就造成:</p>\n<ul>\n<li>单个单个确认会<strong>浪费网络资源</strong></li>\n<li>发送端在等待确认时无事可做会<strong>浪费时间和硬件资源</strong></li>\n</ul>\n<p>所以最好批量发送和确认,发送端一次性发送一组tcp包,接受端一次性确认一组tcp包。</p>\n<h3 id=\"怎么一次性确认多个包\"><a href=\"#怎么一次性确认多个包\" class=\"headerlink\" title=\"怎么一次性确认多个包\"></a>怎么一次性确认多个包</h3><p>很简单,序列号设计为连续的,确认顺序最靠后的包就代表收到了之前所有的包。考虑到可能出现丢包,如果收到如下一组包(数字代表包的序列号):</p>\n<p>{… …,233,234,235,237,238}</p>\n<p>此时为了保证收到缺失的包应确认235,而237,238都会当做没收到,这样发送端就会再次发送235之后的包。为了保证可靠性,这么做是值得的。</p>\n<p>实际中确认报文(ACK)发送的序列号要加1,表示从这个序列号的包开始发送。</p>\n<h3 id=\"滑动窗口的作用\"><a href=\"#滑动窗口的作用\" class=\"headerlink\" title=\"滑动窗口的作用\"></a>滑动窗口的作用</h3><p>在批量发包时,一次性发多少数量是个问题,滑动窗口就代表这个数量。由于主要需要考虑接受端的能力,所以由接收端发送给发送端(两端间会往来数据,这里指广义上的数据接受端和发送端)。</p>\n<p>发送端收到窗口大小后,就会计算发了多少包,剩余多少容量以保证保证不超过窗口大小,直到窗口更新。</p>\n<h3 id=\"发送的时机\"><a href=\"#发送的时机\" class=\"headerlink\" title=\"发送的时机\"></a>发送的时机</h3><p>将窗口更新和ACK报文合并发送可以提高网络效率。</p>\n","site":{"data":{}},"excerpt":"","more":"<h3 id=\"序列号的作用\"><a href=\"#序列号的作用\" class=\"headerlink\" title=\"序列号的作用\"></a>序列号的作用</h3><p>上一篇博客中提到了序列号在建立tcp连接中的作用。不光如此,序列号还是tcp可靠传输的基础。</p>\n<p>为了可靠传输就要防止丢包,所以接受端需要向发送端确认,就像我们收到快递包裹也要确认收货。类比来讲,序列号就像是快递单号。</p>\n<p>但是与现实不同的是,tcp包数量庞大。这就造成:</p>\n<ul>\n<li>单个单个确认会<strong>浪费网络资源</strong></li>\n<li>发送端在等待确认时无事可做会<strong>浪费时间和硬件资源</strong></li>\n</ul>\n<p>所以最好批量发送和确认,发送端一次性发送一组tcp包,接受端一次性确认一组tcp包。</p>\n<h3 id=\"怎么一次性确认多个包\"><a href=\"#怎么一次性确认多个包\" class=\"headerlink\" title=\"怎么一次性确认多个包\"></a>怎么一次性确认多个包</h3><p>很简单,序列号设计为连续的,确认顺序最靠后的包就代表收到了之前所有的包。考虑到可能出现丢包,如果收到如下一组包(数字代表包的序列号):</p>\n<p>{… …,233,234,235,237,238}</p>\n<p>此时为了保证收到缺失的包应确认235,而237,238都会当做没收到,这样发送端就会再次发送235之后的包。为了保证可靠性,这么做是值得的。</p>\n<p>实际中确认报文(ACK)发送的序列号要加1,表示从这个序列号的包开始发送。</p>\n<h3 id=\"滑动窗口的作用\"><a href=\"#滑动窗口的作用\" class=\"headerlink\" title=\"滑动窗口的作用\"></a>滑动窗口的作用</h3><p>在批量发包时,一次性发多少数量是个问题,滑动窗口就代表这个数量。由于主要需要考虑接受端的能力,所以由接收端发送给发送端(两端间会往来数据,这里指广义上的数据接受端和发送端)。</p>\n<p>发送端收到窗口大小后,就会计算发了多少包,剩余多少容量以保证保证不超过窗口大小,直到窗口更新。</p>\n<h3 id=\"发送的时机\"><a href=\"#发送的时机\" class=\"headerlink\" title=\"发送的时机\"></a>发送的时机</h3><p>将窗口更新和ACK报文合并发送可以提高网络效率。</p>"},{"title":"Redis(一):SDS","date":"2021-03-05T08:24:45.000Z","keywords":["redis","sds"],"comments":0,"summary":"Sds (Simple Dynamic String,简单动态字符串)是 Redis 底层所使用的字符串表示, 几乎所有的 Redis 模块中都用了 sds。本文将对 sds 的实现、性能和功能等方面进行介绍, 并说明 Redis 使用 sds 而不是传统 C 字符串的原因。","_content":"\n<!--more-->\n\nRedis 是 C 语言实现的,但是 Redis 放弃了 C 语言传统的字符串而是自己创建了一种名为}简单动态字符串 SDS(Simple Dynamic String)的抽象类型,并将 SDS 用作 Redis 的默认字符串表示,其主要原因就是传统的字符串表示方式并不能满足 Redis 对字符串在安全性、效率、以及功能方面的要求。先来看看SDS长什么样吧\n\n##### SDS 数据结构示意图:\n\n![sds结构示意图](/images/host/redis/redis-sds.png)\n\n可以看到字符串`\"redis\"`以字符数组的形式存放在了`buf`中,空字符`'\\0'`表示字符串结尾,这一点和C语言的字符串是一样的。而多出来的`len`,`alloc`,`flags`让SDS**更安全、性能更好、功能性更强**,具体来说就是:\n\n1. **可以常数复杂度获取字符串长度**。\n2. **杜绝缓冲区溢出。**\n3. **减少修改字符串长度时所需的内存重分配次数。**\n4. **二进制安全。**\n5. **兼容部分C字符串函数。**\n\n下面就来逐条分析SDS是怎么做到这几点的。\n\n##### 一、 常数复杂度获取字符串长度\n\nC语言字符串没有记录自身的长度信息,所以要获取字符串长度时,就要从头开始挨个读字符,直到读取到意味着结尾的空字符`'\\0'`, 复杂度为**O(n)**。而SDS把长度信息记录在了`len`字段,需要获取字符串长度时直接读`len`的值就OK了,复杂度降到了**O(1)**。\n\n##### 二、 杜绝缓冲区溢出\n\n缓冲区溢出(buffer overflow)是指当程序将数据写入缓冲区并且数据过长时,会超过缓冲区的边界,并覆盖相邻的内存位置而造成的异常。C字符串会有缓冲区溢出的风险同样是因为没有记录自身长度,不会自动进行边界检查。而SDS就不会粗暴地把大象塞进冰箱。\n\n当SDS要进行修改时,Redis首先会检查SDS的空间是否满足修改所需的要求,如果不满足的话,就会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作。\n\n##### 三、减小修改字符串长度时所需的内存重分配次数\n\n上一节提到SDS会视情况扩展空间,这涉及到了重新分配内存,然而频繁分配内存会显著影响性能,这可不行。SDS通过空间预分配和惰性空间释放两种优化策略减小修改字符串长度时所需的内存重分配次数。\n\n###### 1)空间预分配\n\n空间预分配用于优化SDS的字符串**增长**操作,如果对SDS进行修改之后,SDS的长度(也即是len的值)将小于1MB,那么程序分配和len属性同样大小的未使用空间(结构图中的sdsavail)。\n\n\nalloc = len * 2\n\n\n而当SDS的长度将大于等于1MB,那么程序会直接分配1MB的未使用空间,避免浪费。\n\n\nalloc = len + 1MB\n\n这样的话当字符串增长的长度没有超过未使用的空间,就可以直接进行修改而不用重新分配内存了。注意,由于空字符`'\\0'`也占一字节长度,`buf`的实际长度为`alloc + 1`。\n\n###### 2)惰性空间释放\n\n惰性空间释放用于优化SDS的字符串**缩短**操作,当需要缩短SDS保存的字符串时,Redis并不\n立即使用内存重分配来回收缩短后多出来的字节。多出来的长度被记录到`alloc`,等待被使用或在合适的时机释放。\n\n##### 二进制安全\n\nC字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能\n包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。\n\n使用SDS来保存之前提到的特殊数据格式就没有任何问题,因为SDS使用`len`属性的值而不是空\n字符来判断字符串是否结束。\n\n那么SDS为什么还要遵循以空字符结尾的规则呢?\n\n##### 兼容部分C字符串函数\n\n因为SDS遵循C字符串以空字符结尾的惯例:数据的末尾设置为空字符,并且总会在为buf数组分配空间时多分配一个字节来容纳这个空字符,所以的SDS可以重用一部分<string.h>库定义的函数。这样避免了重复造轮子,去实现字符串对比之类的函数了。\n\n##### 总结\n\nC字符串虽然简单高效,但是在特定情况下也存在安全和性能问题。Redis通过封装SDS解决了这些问题。分析Redis解决这些问题的方法,也可以获取一定的启发,比如针对应用场景做优化,适当预留资源,在不破坏整体功能的情况遵循惯例来提高兼容性。\n\n","source":"_posts/redis1.md","raw":"---\ntitle: \"Redis(一):SDS\"\ndate: 2021-03-06T00:24:45+08:00\ncategories:\n- 学习笔记\n- Redis\ntags:\n- Redis数据结构与对象\nkeywords:\n- redis\n- sds\ncomments: false\n#thumbnailImage: images/host/redis/redis-icon-logo.png\nsummary: Sds (Simple Dynamic String,简单动态字符串)是 Redis 底层所使用的字符串表示, 几乎所有的 Redis 模块中都用了 sds。本文将对 sds 的实现、性能和功能等方面进行介绍, 并说明 Redis 使用 sds 而不是传统 C 字符串的原因。\n---\n\n<!--more-->\n\nRedis 是 C 语言实现的,但是 Redis 放弃了 C 语言传统的字符串而是自己创建了一种名为}简单动态字符串 SDS(Simple Dynamic String)的抽象类型,并将 SDS 用作 Redis 的默认字符串表示,其主要原因就是传统的字符串表示方式并不能满足 Redis 对字符串在安全性、效率、以及功能方面的要求。先来看看SDS长什么样吧\n\n##### SDS 数据结构示意图:\n\n![sds结构示意图](/images/host/redis/redis-sds.png)\n\n可以看到字符串`\"redis\"`以字符数组的形式存放在了`buf`中,空字符`'\\0'`表示字符串结尾,这一点和C语言的字符串是一样的。而多出来的`len`,`alloc`,`flags`让SDS**更安全、性能更好、功能性更强**,具体来说就是:\n\n1. **可以常数复杂度获取字符串长度**。\n2. **杜绝缓冲区溢出。**\n3. **减少修改字符串长度时所需的内存重分配次数。**\n4. **二进制安全。**\n5. **兼容部分C字符串函数。**\n\n下面就来逐条分析SDS是怎么做到这几点的。\n\n##### 一、 常数复杂度获取字符串长度\n\nC语言字符串没有记录自身的长度信息,所以要获取字符串长度时,就要从头开始挨个读字符,直到读取到意味着结尾的空字符`'\\0'`, 复杂度为**O(n)**。而SDS把长度信息记录在了`len`字段,需要获取字符串长度时直接读`len`的值就OK了,复杂度降到了**O(1)**。\n\n##### 二、 杜绝缓冲区溢出\n\n缓冲区溢出(buffer overflow)是指当程序将数据写入缓冲区并且数据过长时,会超过缓冲区的边界,并覆盖相邻的内存位置而造成的异常。C字符串会有缓冲区溢出的风险同样是因为没有记录自身长度,不会自动进行边界检查。而SDS就不会粗暴地把大象塞进冰箱。\n\n当SDS要进行修改时,Redis首先会检查SDS的空间是否满足修改所需的要求,如果不满足的话,就会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作。\n\n##### 三、减小修改字符串长度时所需的内存重分配次数\n\n上一节提到SDS会视情况扩展空间,这涉及到了重新分配内存,然而频繁分配内存会显著影响性能,这可不行。SDS通过空间预分配和惰性空间释放两种优化策略减小修改字符串长度时所需的内存重分配次数。\n\n###### 1)空间预分配\n\n空间预分配用于优化SDS的字符串**增长**操作,如果对SDS进行修改之后,SDS的长度(也即是len的值)将小于1MB,那么程序分配和len属性同样大小的未使用空间(结构图中的sdsavail)。\n\n\nalloc = len * 2\n\n\n而当SDS的长度将大于等于1MB,那么程序会直接分配1MB的未使用空间,避免浪费。\n\n\nalloc = len + 1MB\n\n这样的话当字符串增长的长度没有超过未使用的空间,就可以直接进行修改而不用重新分配内存了。注意,由于空字符`'\\0'`也占一字节长度,`buf`的实际长度为`alloc + 1`。\n\n###### 2)惰性空间释放\n\n惰性空间释放用于优化SDS的字符串**缩短**操作,当需要缩短SDS保存的字符串时,Redis并不\n立即使用内存重分配来回收缩短后多出来的字节。多出来的长度被记录到`alloc`,等待被使用或在合适的时机释放。\n\n##### 二进制安全\n\nC字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能\n包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。\n\n使用SDS来保存之前提到的特殊数据格式就没有任何问题,因为SDS使用`len`属性的值而不是空\n字符来判断字符串是否结束。\n\n那么SDS为什么还要遵循以空字符结尾的规则呢?\n\n##### 兼容部分C字符串函数\n\n因为SDS遵循C字符串以空字符结尾的惯例:数据的末尾设置为空字符,并且总会在为buf数组分配空间时多分配一个字节来容纳这个空字符,所以的SDS可以重用一部分<string.h>库定义的函数。这样避免了重复造轮子,去实现字符串对比之类的函数了。\n\n##### 总结\n\nC字符串虽然简单高效,但是在特定情况下也存在安全和性能问题。Redis通过封装SDS解决了这些问题。分析Redis解决这些问题的方法,也可以获取一定的启发,比如针对应用场景做优化,适当预留资源,在不破坏整体功能的情况遵循惯例来提高兼容性。\n\n","slug":"redis1","published":1,"updated":"2021-06-26T05:00:43.378Z","layout":"post","photos":[],"link":"","_id":"ckqh4n01n000aykea8yxi67el","content":"<span id=\"more\"></span>\n\n<p>Redis 是 C 语言实现的,但是 Redis 放弃了 C 语言传统的字符串而是自己创建了一种名为}简单动态字符串 SDS(Simple Dynamic String)的抽象类型,并将 SDS 用作 Redis 的默认字符串表示,其主要原因就是传统的字符串表示方式并不能满足 Redis 对字符串在安全性、效率、以及功能方面的要求。先来看看SDS长什么样吧</p>\n<h5 id=\"SDS-数据结构示意图:\"><a href=\"#SDS-数据结构示意图:\" class=\"headerlink\" title=\"SDS 数据结构示意图:\"></a>SDS 数据结构示意图:</h5><p><img src=\"/images/host/redis/redis-sds.png\" alt=\"sds结构示意图\"></p>\n<p>可以看到字符串<code>"redis"</code>以字符数组的形式存放在了<code>buf</code>中,空字符<code>'\\0'</code>表示字符串结尾,这一点和C语言的字符串是一样的。而多出来的<code>len</code>,<code>alloc</code>,<code>flags</code>让SDS<strong>更安全、性能更好、功能性更强</strong>,具体来说就是:</p>\n<ol>\n<li><strong>可以常数复杂度获取字符串长度</strong>。</li>\n<li><strong>杜绝缓冲区溢出。</strong></li>\n<li><strong>减少修改字符串长度时所需的内存重分配次数。</strong></li>\n<li><strong>二进制安全。</strong></li>\n<li><strong>兼容部分C字符串函数。</strong></li>\n</ol>\n<p>下面就来逐条分析SDS是怎么做到这几点的。</p>\n<h5 id=\"一、-常数复杂度获取字符串长度\"><a href=\"#一、-常数复杂度获取字符串长度\" class=\"headerlink\" title=\"一、 常数复杂度获取字符串长度\"></a>一、 常数复杂度获取字符串长度</h5><p>C语言字符串没有记录自身的长度信息,所以要获取字符串长度时,就要从头开始挨个读字符,直到读取到意味着结尾的空字符<code>'\\0'</code>, 复杂度为**O(n)<strong>。而SDS把长度信息记录在了<code>len</code>字段,需要获取字符串长度时直接读<code>len</code>的值就OK了,复杂度降到了</strong>O(1)**。</p>\n<h5 id=\"二、-杜绝缓冲区溢出\"><a href=\"#二、-杜绝缓冲区溢出\" class=\"headerlink\" title=\"二、 杜绝缓冲区溢出\"></a>二、 杜绝缓冲区溢出</h5><p>缓冲区溢出(buffer overflow)是指当程序将数据写入缓冲区并且数据过长时,会超过缓冲区的边界,并覆盖相邻的内存位置而造成的异常。C字符串会有缓冲区溢出的风险同样是因为没有记录自身长度,不会自动进行边界检查。而SDS就不会粗暴地把大象塞进冰箱。</p>\n<p>当SDS要进行修改时,Redis首先会检查SDS的空间是否满足修改所需的要求,如果不满足的话,就会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作。</p>\n<h5 id=\"三、减小修改字符串长度时所需的内存重分配次数\"><a href=\"#三、减小修改字符串长度时所需的内存重分配次数\" class=\"headerlink\" title=\"三、减小修改字符串长度时所需的内存重分配次数\"></a>三、减小修改字符串长度时所需的内存重分配次数</h5><p>上一节提到SDS会视情况扩展空间,这涉及到了重新分配内存,然而频繁分配内存会显著影响性能,这可不行。SDS通过空间预分配和惰性空间释放两种优化策略减小修改字符串长度时所需的内存重分配次数。</p>\n<h6 id=\"1)空间预分配\"><a href=\"#1)空间预分配\" class=\"headerlink\" title=\"1)空间预分配\"></a>1)空间预分配</h6><p>空间预分配用于优化SDS的字符串<strong>增长</strong>操作,如果对SDS进行修改之后,SDS的长度(也即是len的值)将小于1MB,那么程序分配和len属性同样大小的未使用空间(结构图中的sdsavail)。</p>\n<p>alloc = len * 2</p>\n<p>而当SDS的长度将大于等于1MB,那么程序会直接分配1MB的未使用空间,避免浪费。</p>\n<p>alloc = len + 1MB</p>\n<p>这样的话当字符串增长的长度没有超过未使用的空间,就可以直接进行修改而不用重新分配内存了。注意,由于空字符<code>'\\0'</code>也占一字节长度,<code>buf</code>的实际长度为<code>alloc + 1</code>。</p>\n<h6 id=\"2)惰性空间释放\"><a href=\"#2)惰性空间释放\" class=\"headerlink\" title=\"2)惰性空间释放\"></a>2)惰性空间释放</h6><p>惰性空间释放用于优化SDS的字符串<strong>缩短</strong>操作,当需要缩短SDS保存的字符串时,Redis并不<br>立即使用内存重分配来回收缩短后多出来的字节。多出来的长度被记录到<code>alloc</code>,等待被使用或在合适的时机释放。</p>\n<h5 id=\"二进制安全\"><a href=\"#二进制安全\" class=\"headerlink\" title=\"二进制安全\"></a>二进制安全</h5><p>C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能<br>包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。</p>\n<p>使用SDS来保存之前提到的特殊数据格式就没有任何问题,因为SDS使用<code>len</code>属性的值而不是空<br>字符来判断字符串是否结束。</p>\n<p>那么SDS为什么还要遵循以空字符结尾的规则呢?</p>\n<h5 id=\"兼容部分C字符串函数\"><a href=\"#兼容部分C字符串函数\" class=\"headerlink\" title=\"兼容部分C字符串函数\"></a>兼容部分C字符串函数</h5><p>因为SDS遵循C字符串以空字符结尾的惯例:数据的末尾设置为空字符,并且总会在为buf数组分配空间时多分配一个字节来容纳这个空字符,所以的SDS可以重用一部分<string.h>库定义的函数。这样避免了重复造轮子,去实现字符串对比之类的函数了。</p>\n<h5 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h5><p>C字符串虽然简单高效,但是在特定情况下也存在安全和性能问题。Redis通过封装SDS解决了这些问题。分析Redis解决这些问题的方法,也可以获取一定的启发,比如针对应用场景做优化,适当预留资源,在不破坏整体功能的情况遵循惯例来提高兼容性。</p>\n","site":{"data":{}},"excerpt":"","more":"<p>Redis 是 C 语言实现的,但是 Redis 放弃了 C 语言传统的字符串而是自己创建了一种名为}简单动态字符串 SDS(Simple Dynamic String)的抽象类型,并将 SDS 用作 Redis 的默认字符串表示,其主要原因就是传统的字符串表示方式并不能满足 Redis 对字符串在安全性、效率、以及功能方面的要求。先来看看SDS长什么样吧</p>\n<h5 id=\"SDS-数据结构示意图:\"><a href=\"#SDS-数据结构示意图:\" class=\"headerlink\" title=\"SDS 数据结构示意图:\"></a>SDS 数据结构示意图:</h5><p><img src=\"/images/host/redis/redis-sds.png\" alt=\"sds结构示意图\"></p>\n<p>可以看到字符串<code>"redis"</code>以字符数组的形式存放在了<code>buf</code>中,空字符<code>'\\0'</code>表示字符串结尾,这一点和C语言的字符串是一样的。而多出来的<code>len</code>,<code>alloc</code>,<code>flags</code>让SDS<strong>更安全、性能更好、功能性更强</strong>,具体来说就是:</p>\n<ol>\n<li><strong>可以常数复杂度获取字符串长度</strong>。</li>\n<li><strong>杜绝缓冲区溢出。</strong></li>\n<li><strong>减少修改字符串长度时所需的内存重分配次数。</strong></li>\n<li><strong>二进制安全。</strong></li>\n<li><strong>兼容部分C字符串函数。</strong></li>\n</ol>\n<p>下面就来逐条分析SDS是怎么做到这几点的。</p>\n<h5 id=\"一、-常数复杂度获取字符串长度\"><a href=\"#一、-常数复杂度获取字符串长度\" class=\"headerlink\" title=\"一、 常数复杂度获取字符串长度\"></a>一、 常数复杂度获取字符串长度</h5><p>C语言字符串没有记录自身的长度信息,所以要获取字符串长度时,就要从头开始挨个读字符,直到读取到意味着结尾的空字符<code>'\\0'</code>, 复杂度为**O(n)<strong>。而SDS把长度信息记录在了<code>len</code>字段,需要获取字符串长度时直接读<code>len</code>的值就OK了,复杂度降到了</strong>O(1)**。</p>\n<h5 id=\"二、-杜绝缓冲区溢出\"><a href=\"#二、-杜绝缓冲区溢出\" class=\"headerlink\" title=\"二、 杜绝缓冲区溢出\"></a>二、 杜绝缓冲区溢出</h5><p>缓冲区溢出(buffer overflow)是指当程序将数据写入缓冲区并且数据过长时,会超过缓冲区的边界,并覆盖相邻的内存位置而造成的异常。C字符串会有缓冲区溢出的风险同样是因为没有记录自身长度,不会自动进行边界检查。而SDS就不会粗暴地把大象塞进冰箱。</p>\n<p>当SDS要进行修改时,Redis首先会检查SDS的空间是否满足修改所需的要求,如果不满足的话,就会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作。</p>\n<h5 id=\"三、减小修改字符串长度时所需的内存重分配次数\"><a href=\"#三、减小修改字符串长度时所需的内存重分配次数\" class=\"headerlink\" title=\"三、减小修改字符串长度时所需的内存重分配次数\"></a>三、减小修改字符串长度时所需的内存重分配次数</h5><p>上一节提到SDS会视情况扩展空间,这涉及到了重新分配内存,然而频繁分配内存会显著影响性能,这可不行。SDS通过空间预分配和惰性空间释放两种优化策略减小修改字符串长度时所需的内存重分配次数。</p>\n<h6 id=\"1)空间预分配\"><a href=\"#1)空间预分配\" class=\"headerlink\" title=\"1)空间预分配\"></a>1)空间预分配</h6><p>空间预分配用于优化SDS的字符串<strong>增长</strong>操作,如果对SDS进行修改之后,SDS的长度(也即是len的值)将小于1MB,那么程序分配和len属性同样大小的未使用空间(结构图中的sdsavail)。</p>\n<p>alloc = len * 2</p>\n<p>而当SDS的长度将大于等于1MB,那么程序会直接分配1MB的未使用空间,避免浪费。</p>\n<p>alloc = len + 1MB</p>\n<p>这样的话当字符串增长的长度没有超过未使用的空间,就可以直接进行修改而不用重新分配内存了。注意,由于空字符<code>'\\0'</code>也占一字节长度,<code>buf</code>的实际长度为<code>alloc + 1</code>。</p>\n<h6 id=\"2)惰性空间释放\"><a href=\"#2)惰性空间释放\" class=\"headerlink\" title=\"2)惰性空间释放\"></a>2)惰性空间释放</h6><p>惰性空间释放用于优化SDS的字符串<strong>缩短</strong>操作,当需要缩短SDS保存的字符串时,Redis并不<br>立即使用内存重分配来回收缩短后多出来的字节。多出来的长度被记录到<code>alloc</code>,等待被使用或在合适的时机释放。</p>\n<h5 id=\"二进制安全\"><a href=\"#二进制安全\" class=\"headerlink\" title=\"二进制安全\"></a>二进制安全</h5><p>C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能<br>包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。</p>\n<p>使用SDS来保存之前提到的特殊数据格式就没有任何问题,因为SDS使用<code>len</code>属性的值而不是空<br>字符来判断字符串是否结束。</p>\n<p>那么SDS为什么还要遵循以空字符结尾的规则呢?</p>\n<h5 id=\"兼容部分C字符串函数\"><a href=\"#兼容部分C字符串函数\" class=\"headerlink\" title=\"兼容部分C字符串函数\"></a>兼容部分C字符串函数</h5><p>因为SDS遵循C字符串以空字符结尾的惯例:数据的末尾设置为空字符,并且总会在为buf数组分配空间时多分配一个字节来容纳这个空字符,所以的SDS可以重用一部分<string.h>库定义的函数。这样避免了重复造轮子,去实现字符串对比之类的函数了。</p>\n<h5 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h5><p>C字符串虽然简单高效,但是在特定情况下也存在安全和性能问题。Redis通过封装SDS解决了这些问题。分析Redis解决这些问题的方法,也可以获取一定的启发,比如针对应用场景做优化,适当预留资源,在不破坏整体功能的情况遵循惯例来提高兼容性。</p>"},{"title":"Redis(二):链表","date":"2021-03-09T04:13:45.000Z","keywords":["redis","链表"],"comments":0,"summary":"链表被广泛用于实现Redis的各种功能,比如列表键、发布与订阅、慢查询、监视器等。本文介绍了Redis链表的结构和特征。","_content":"\n<!--more-->\n\n**链表**被广泛用于实现Redis的各种功能,比如列表键、发布与订阅、慢查询、监视器等。直接看结构示意图:\n\n![链表结构示意图](/images/host/redis/redis-list.png)\n\n##### 特征总结如下:\n\n- **双端**:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)。\n- **无环**:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。\n- **带表头指针和表尾指针**:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)。\n- **带链表长度计数器**:程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。\n- **多态**:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。\n - dup函数用于复制链表节点所保存的值;\n - free函数用于释放链表节点所保存的值;\n - match函数则用于对比链表节点所保存的值和另一个输入值是否相等。\n\n##### 总结\n\n链表在数据结构中相当经典实用。\n\n","source":"_posts/redis2.md","raw":"---\ntitle: \"Redis(二):链表\"\ndate: 2021-03-09T20:13:45+08:00\ncategories:\n- 学习笔记\n- Redis\ntags:\n- Redis数据结构与对象\nkeywords:\n- redis\n- 链表\ncomments: false\n#thumbnailImage: images/host/redis/redis-icon-logo.png\nsummary: 链表被广泛用于实现Redis的各种功能,比如列表键、发布与订阅、慢查询、监视器等。本文介绍了Redis链表的结构和特征。\n---\n\n<!--more-->\n\n**链表**被广泛用于实现Redis的各种功能,比如列表键、发布与订阅、慢查询、监视器等。直接看结构示意图:\n\n![链表结构示意图](/images/host/redis/redis-list.png)\n\n##### 特征总结如下:\n\n- **双端**:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)。\n- **无环**:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。\n- **带表头指针和表尾指针**:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)。\n- **带链表长度计数器**:程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。\n- **多态**:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。\n - dup函数用于复制链表节点所保存的值;\n - free函数用于释放链表节点所保存的值;\n - match函数则用于对比链表节点所保存的值和另一个输入值是否相等。\n\n##### 总结\n\n链表在数据结构中相当经典实用。\n\n","slug":"redis2","published":1,"updated":"2021-06-26T04:59:23.299Z","layout":"post","photos":[],"link":"","_id":"ckqh4n026000dykeabtaf2fxz","content":"<span id=\"more\"></span>\n\n<p><strong>链表</strong>被广泛用于实现Redis的各种功能,比如列表键、发布与订阅、慢查询、监视器等。直接看结构示意图:</p>\n<p><img src=\"/images/host/redis/redis-list.png\" alt=\"链表结构示意图\"></p>\n<h5 id=\"特征总结如下:\"><a href=\"#特征总结如下:\" class=\"headerlink\" title=\"特征总结如下:\"></a>特征总结如下:</h5><ul>\n<li><strong>双端</strong>:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)。</li>\n<li><strong>无环</strong>:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。</li>\n<li><strong>带表头指针和表尾指针</strong>:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)。</li>\n<li><strong>带链表长度计数器</strong>:程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。</li>\n<li><strong>多态</strong>:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。<ul>\n<li>dup函数用于复制链表节点所保存的值;</li>\n<li>free函数用于释放链表节点所保存的值;</li>\n<li>match函数则用于对比链表节点所保存的值和另一个输入值是否相等。</li>\n</ul>\n</li>\n</ul>\n<h5 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h5><p>链表在数据结构中相当经典实用。</p>\n","site":{"data":{}},"excerpt":"","more":"<p><strong>链表</strong>被广泛用于实现Redis的各种功能,比如列表键、发布与订阅、慢查询、监视器等。直接看结构示意图:</p>\n<p><img src=\"/images/host/redis/redis-list.png\" alt=\"链表结构示意图\"></p>\n<h5 id=\"特征总结如下:\"><a href=\"#特征总结如下:\" class=\"headerlink\" title=\"特征总结如下:\"></a>特征总结如下:</h5><ul>\n<li><strong>双端</strong>:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)。</li>\n<li><strong>无环</strong>:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。</li>\n<li><strong>带表头指针和表尾指针</strong>:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)。</li>\n<li><strong>带链表长度计数器</strong>:程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。</li>\n<li><strong>多态</strong>:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。<ul>\n<li>dup函数用于复制链表节点所保存的值;</li>\n<li>free函数用于释放链表节点所保存的值;</li>\n<li>match函数则用于对比链表节点所保存的值和另一个输入值是否相等。</li>\n</ul>\n</li>\n</ul>\n<h5 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h5><p>链表在数据结构中相当经典实用。</p>"},{"title":"Redis(三):字典","date":"2021-03-09T05:11:45.000Z","keywords":["redis","字典"],"comments":0,"summary":"字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。本文介绍了Redis字典的底层实现,包括如何计算索引,解决键冲突和rehash。","_content":"\n<!--more-->\n\n**字典**在Redis中的应用相当广泛,比如Redis的数据库就是使用字典来作为底层实现的,对数据库的增、删、查、改操作也是构建在对字典的操作之上的。\n\n##### 字典结构解析\n\nRedis的字典使用哈希表作为底层实现,结构如图:\n\n![字典结构示意图](/images/host/redis/redis-ht.png)\n\n`table`属性是一个数组,数组中的每个元素都是一个指向`dict.h/dictEntry`结构的指针,每个`dictEntry`结构保存着一个键值对。`size`属性记录了哈希表的大小,也即是`table`数组的大小,而`used`属性则记录了哈希表目前已有节点(键值对)的数量。`sizemask`属性的值总是等于`size-1`,这个属性和哈希值一起决定一个键应该被放到`table`数组的哪个索引上面。\n\n\n\nindex = hash & sizemask;\n\n\n\n其中hash是对key进行哈希运算得出的。\n\n##### 解决键冲突\n\n当出现两个键值对计算出的索引值index相同即发生键冲突时,Redis通过链地址法来解决冲突,索引值相同的键值对通过next指针以单向链表的形式连接在一起。\n\n要注意的一点是由于没有表尾指针,出于速度考虑,新节点直接插入表头,所以插入节点的复杂度为**O(1)**。\n\n##### rehash(重新散列)\n\n随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让哈希表的负载因子(load factor)维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩。扩展和收缩哈希表的工作可以通过执行rehash(重新散列)操作来完成。\n\n这个过程类似于搬家,所以会有旧房子ht[0]和新房子ht[1],首先要判断是否需要搬家,通过公式计算负载因子:\n\n\n\nload_factor = ht[0].used / ht[0].size\n\n\n\n###### 满足以下条件时执行扩展操作\n\n1)服务器目前没有在执行`BGSAVE`命令或者`BGREWRITEAOF`命令,并且哈希表的**负载因子大于等于1**。\n\n2)服务器目前正在执行`BGSAVE`命令或者`BGREWRITEAOF`命令,并且哈希表的**负载因子大于等于5**。\n\n满足**负载因子小于0.1**时执行收缩操作。\n\n###### rehash具体操作步骤:\n\n1. 为字典的ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(也即是ht[0].used属性的值):\n - 如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2的n次方幂;\n - 如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2的n次方幂。\n\n2. 在字典中维持一个索引计数器变量rehashidx,并将它的值**设置为0**,表示rehash工作正式开始。\n\n3. 在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成之后,程序**将rehashidx属性的值增一**。\n\n4. 随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1],这时程序**将rehashidx属性的值设为-1**,表示rehash操作已完成。\n\n5. 当ht[0]包含的所有键值对都迁移到了ht[1]之后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。\n\n \n\n这种渐进式rehash的好处在于它采取分而治之的方式,将rehash键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式rehash而带来的庞大计算量。\n\n缺点是因为同时有两张表保存数据,数据访问操作可能需要进行两次才能完成。\n\n##### 总结\n\n字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。本文介绍了Redis字典的底层实现,包括如何计算索引,解决键冲突和rehash。渐进式的rehash让我印象最深,这种大而化小的解决思路值得学习。\n\n","source":"_posts/redis3.md","raw":"---\ntitle: \"Redis(三):字典\"\ndate: 2021-03-09T21:11:45+08:00\ncategories:\n- 学习笔记\n- Redis\ntags:\n- Redis数据结构与对象\nkeywords:\n- redis\n- 字典\ncomments: false\n#thumbnailImage: images/host/redis/redis-icon-logo.png\nsummary: 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。本文介绍了Redis字典的底层实现,包括如何计算索引,解决键冲突和rehash。\n---\n\n<!--more-->\n\n**字典**在Redis中的应用相当广泛,比如Redis的数据库就是使用字典来作为底层实现的,对数据库的增、删、查、改操作也是构建在对字典的操作之上的。\n\n##### 字典结构解析\n\nRedis的字典使用哈希表作为底层实现,结构如图:\n\n![字典结构示意图](/images/host/redis/redis-ht.png)\n\n`table`属性是一个数组,数组中的每个元素都是一个指向`dict.h/dictEntry`结构的指针,每个`dictEntry`结构保存着一个键值对。`size`属性记录了哈希表的大小,也即是`table`数组的大小,而`used`属性则记录了哈希表目前已有节点(键值对)的数量。`sizemask`属性的值总是等于`size-1`,这个属性和哈希值一起决定一个键应该被放到`table`数组的哪个索引上面。\n\n\n\nindex = hash & sizemask;\n\n\n\n其中hash是对key进行哈希运算得出的。\n\n##### 解决键冲突\n\n当出现两个键值对计算出的索引值index相同即发生键冲突时,Redis通过链地址法来解决冲突,索引值相同的键值对通过next指针以单向链表的形式连接在一起。\n\n要注意的一点是由于没有表尾指针,出于速度考虑,新节点直接插入表头,所以插入节点的复杂度为**O(1)**。\n\n##### rehash(重新散列)\n\n随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让哈希表的负载因子(load factor)维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩。扩展和收缩哈希表的工作可以通过执行rehash(重新散列)操作来完成。\n\n这个过程类似于搬家,所以会有旧房子ht[0]和新房子ht[1],首先要判断是否需要搬家,通过公式计算负载因子:\n\n\n\nload_factor = ht[0].used / ht[0].size\n\n\n\n###### 满足以下条件时执行扩展操作\n\n1)服务器目前没有在执行`BGSAVE`命令或者`BGREWRITEAOF`命令,并且哈希表的**负载因子大于等于1**。\n\n2)服务器目前正在执行`BGSAVE`命令或者`BGREWRITEAOF`命令,并且哈希表的**负载因子大于等于5**。\n\n满足**负载因子小于0.1**时执行收缩操作。\n\n###### rehash具体操作步骤:\n\n1. 为字典的ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(也即是ht[0].used属性的值):\n - 如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2的n次方幂;\n - 如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2的n次方幂。\n\n2. 在字典中维持一个索引计数器变量rehashidx,并将它的值**设置为0**,表示rehash工作正式开始。\n\n3. 在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成之后,程序**将rehashidx属性的值增一**。\n\n4. 随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1],这时程序**将rehashidx属性的值设为-1**,表示rehash操作已完成。\n\n5. 当ht[0]包含的所有键值对都迁移到了ht[1]之后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。\n\n \n\n这种渐进式rehash的好处在于它采取分而治之的方式,将rehash键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式rehash而带来的庞大计算量。\n\n缺点是因为同时有两张表保存数据,数据访问操作可能需要进行两次才能完成。\n\n##### 总结\n\n字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。本文介绍了Redis字典的底层实现,包括如何计算索引,解决键冲突和rehash。渐进式的rehash让我印象最深,这种大而化小的解决思路值得学习。\n\n","slug":"redis3","published":1,"updated":"2021-06-26T05:02:46.620Z","layout":"post","photos":[],"link":"","_id":"ckqh4n02b000fykeaf6s62n71","content":"<span id=\"more\"></span>\n\n<p><strong>字典</strong>在Redis中的应用相当广泛,比如Redis的数据库就是使用字典来作为底层实现的,对数据库的增、删、查、改操作也是构建在对字典的操作之上的。</p>\n<h5 id=\"字典结构解析\"><a href=\"#字典结构解析\" class=\"headerlink\" title=\"字典结构解析\"></a>字典结构解析</h5><p>Redis的字典使用哈希表作为底层实现,结构如图:</p>\n<p><img src=\"/images/host/redis/redis-ht.png\" alt=\"字典结构示意图\"></p>\n<p><code>table</code>属性是一个数组,数组中的每个元素都是一个指向<code>dict.h/dictEntry</code>结构的指针,每个<code>dictEntry</code>结构保存着一个键值对。<code>size</code>属性记录了哈希表的大小,也即是<code>table</code>数组的大小,而<code>used</code>属性则记录了哈希表目前已有节点(键值对)的数量。<code>sizemask</code>属性的值总是等于<code>size-1</code>,这个属性和哈希值一起决定一个键应该被放到<code>table</code>数组的哪个索引上面。</p>\n<p>index = hash & sizemask;</p>\n<p>其中hash是对key进行哈希运算得出的。</p>\n<h5 id=\"解决键冲突\"><a href=\"#解决键冲突\" class=\"headerlink\" title=\"解决键冲突\"></a>解决键冲突</h5><p>当出现两个键值对计算出的索引值index相同即发生键冲突时,Redis通过链地址法来解决冲突,索引值相同的键值对通过next指针以单向链表的形式连接在一起。</p>\n<p>要注意的一点是由于没有表尾指针,出于速度考虑,新节点直接插入表头,所以插入节点的复杂度为**O(1)**。</p>\n<h5 id=\"rehash(重新散列)\"><a href=\"#rehash(重新散列)\" class=\"headerlink\" title=\"rehash(重新散列)\"></a>rehash(重新散列)</h5><p>随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让哈希表的负载因子(load factor)维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩。扩展和收缩哈希表的工作可以通过执行rehash(重新散列)操作来完成。</p>\n<p>这个过程类似于搬家,所以会有旧房子ht[0]和新房子ht[1],首先要判断是否需要搬家,通过公式计算负载因子:</p>\n<p>load_factor = ht[0].used / ht[0].size</p>\n<h6 id=\"满足以下条件时执行扩展操作\"><a href=\"#满足以下条件时执行扩展操作\" class=\"headerlink\" title=\"满足以下条件时执行扩展操作\"></a>满足以下条件时执行扩展操作</h6><p>1)服务器目前没有在执行<code>BGSAVE</code>命令或者<code>BGREWRITEAOF</code>命令,并且哈希表的<strong>负载因子大于等于1</strong>。</p>\n<p>2)服务器目前正在执行<code>BGSAVE</code>命令或者<code>BGREWRITEAOF</code>命令,并且哈希表的<strong>负载因子大于等于5</strong>。</p>\n<p>满足<strong>负载因子小于0.1</strong>时执行收缩操作。</p>\n<h6 id=\"rehash具体操作步骤:\"><a href=\"#rehash具体操作步骤:\" class=\"headerlink\" title=\"rehash具体操作步骤:\"></a>rehash具体操作步骤:</h6><ol>\n<li><p>为字典的ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(也即是ht[0].used属性的值):</p>\n<ul>\n<li>如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2的n次方幂;</li>\n<li>如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2的n次方幂。</li>\n</ul>\n</li>\n<li><p>在字典中维持一个索引计数器变量rehashidx,并将它的值<strong>设置为0</strong>,表示rehash工作正式开始。</p>\n</li>\n<li><p>在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成之后,程序<strong>将rehashidx属性的值增一</strong>。</p>\n</li>\n<li><p>随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1],这时程序<strong>将rehashidx属性的值设为-1</strong>,表示rehash操作已完成。</p>\n</li>\n<li><p>当ht[0]包含的所有键值对都迁移到了ht[1]之后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。</p>\n</li>\n</ol>\n<p>这种渐进式rehash的好处在于它采取分而治之的方式,将rehash键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式rehash而带来的庞大计算量。</p>\n<p>缺点是因为同时有两张表保存数据,数据访问操作可能需要进行两次才能完成。</p>\n<h5 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h5><p>字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。本文介绍了Redis字典的底层实现,包括如何计算索引,解决键冲突和rehash。渐进式的rehash让我印象最深,这种大而化小的解决思路值得学习。</p>\n","site":{"data":{}},"excerpt":"","more":"<p><strong>字典</strong>在Redis中的应用相当广泛,比如Redis的数据库就是使用字典来作为底层实现的,对数据库的增、删、查、改操作也是构建在对字典的操作之上的。</p>\n<h5 id=\"字典结构解析\"><a href=\"#字典结构解析\" class=\"headerlink\" title=\"字典结构解析\"></a>字典结构解析</h5><p>Redis的字典使用哈希表作为底层实现,结构如图:</p>\n<p><img src=\"/images/host/redis/redis-ht.png\" alt=\"字典结构示意图\"></p>\n<p><code>table</code>属性是一个数组,数组中的每个元素都是一个指向<code>dict.h/dictEntry</code>结构的指针,每个<code>dictEntry</code>结构保存着一个键值对。<code>size</code>属性记录了哈希表的大小,也即是<code>table</code>数组的大小,而<code>used</code>属性则记录了哈希表目前已有节点(键值对)的数量。<code>sizemask</code>属性的值总是等于<code>size-1</code>,这个属性和哈希值一起决定一个键应该被放到<code>table</code>数组的哪个索引上面。</p>\n<p>index = hash & sizemask;</p>\n<p>其中hash是对key进行哈希运算得出的。</p>\n<h5 id=\"解决键冲突\"><a href=\"#解决键冲突\" class=\"headerlink\" title=\"解决键冲突\"></a>解决键冲突</h5><p>当出现两个键值对计算出的索引值index相同即发生键冲突时,Redis通过链地址法来解决冲突,索引值相同的键值对通过next指针以单向链表的形式连接在一起。</p>\n<p>要注意的一点是由于没有表尾指针,出于速度考虑,新节点直接插入表头,所以插入节点的复杂度为**O(1)**。</p>\n<h5 id=\"rehash(重新散列)\"><a href=\"#rehash(重新散列)\" class=\"headerlink\" title=\"rehash(重新散列)\"></a>rehash(重新散列)</h5><p>随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让哈希表的负载因子(load factor)维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩。扩展和收缩哈希表的工作可以通过执行rehash(重新散列)操作来完成。</p>\n<p>这个过程类似于搬家,所以会有旧房子ht[0]和新房子ht[1],首先要判断是否需要搬家,通过公式计算负载因子:</p>\n<p>load_factor = ht[0].used / ht[0].size</p>\n<h6 id=\"满足以下条件时执行扩展操作\"><a href=\"#满足以下条件时执行扩展操作\" class=\"headerlink\" title=\"满足以下条件时执行扩展操作\"></a>满足以下条件时执行扩展操作</h6><p>1)服务器目前没有在执行<code>BGSAVE</code>命令或者<code>BGREWRITEAOF</code>命令,并且哈希表的<strong>负载因子大于等于1</strong>。</p>\n<p>2)服务器目前正在执行<code>BGSAVE</code>命令或者<code>BGREWRITEAOF</code>命令,并且哈希表的<strong>负载因子大于等于5</strong>。</p>\n<p>满足<strong>负载因子小于0.1</strong>时执行收缩操作。</p>\n<h6 id=\"rehash具体操作步骤:\"><a href=\"#rehash具体操作步骤:\" class=\"headerlink\" title=\"rehash具体操作步骤:\"></a>rehash具体操作步骤:</h6><ol>\n<li><p>为字典的ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(也即是ht[0].used属性的值):</p>\n<ul>\n<li>如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2的n次方幂;</li>\n<li>如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2的n次方幂。</li>\n</ul>\n</li>\n<li><p>在字典中维持一个索引计数器变量rehashidx,并将它的值<strong>设置为0</strong>,表示rehash工作正式开始。</p>\n</li>\n<li><p>在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成之后,程序<strong>将rehashidx属性的值增一</strong>。</p>\n</li>\n<li><p>随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1],这时程序<strong>将rehashidx属性的值设为-1</strong>,表示rehash操作已完成。</p>\n</li>\n<li><p>当ht[0]包含的所有键值对都迁移到了ht[1]之后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。</p>\n</li>\n</ol>\n<p>这种渐进式rehash的好处在于它采取分而治之的方式,将rehash键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式rehash而带来的庞大计算量。</p>\n<p>缺点是因为同时有两张表保存数据,数据访问操作可能需要进行两次才能完成。</p>\n<h5 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h5><p>字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。本文介绍了Redis字典的底层实现,包括如何计算索引,解决键冲突和rehash。渐进式的rehash让我印象最深,这种大而化小的解决思路值得学习。</p>"},{"title":"Redis(四):跳跃表","date":"2021-03-11T05:26:55.000Z","keywords":["redis","跳跃表"],"comments":0,"summary":"Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员(member)是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现。本文分析了redis跳跃表的实现和特点。","_content":"\n<!--more-->\n\n跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。在大部分情况下,跳跃表的效率可以和平衡树相媲美,而且实现还更加简单。\n\n##### 跳跃表结构解析\n\n![跳跃表结构示意图](/images/host/redis/redis-zskip.jpg)\n\n- `header`:指向跳跃表的表头节点。\n\n- `tail`:指向跳跃表的表尾节点。\n\n- `level`:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)。\n\n- `length`:记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内)。\n\n首先要注意的是跳表是有序的,从结构示意图中可以看到节点按`score`(即图中的1.0、2.0、3.0)从小到大排列,离表头越远,`score`越大。\n\n重点是节点的结构,节点包含的属性可以分为两部分讲,一部分用来跳跃,一部分用来保存对象。\n\n跳跃可以按方向分为两种:向表尾跳和向表头跳,分别通过**层**和**后退指针**完成。\n\n结构示意图中L1、L2、L3 …代表的就是层,可以看到一个节点的层不止一个,高层跳的远(跨度大),低层跳的近(跨度小)。而后退指针只有一个(图中的BW),所以只能以固定的跨度1向表头跳。\n\n###### 查找节点\n\n以升序表为例,查找节点的流程如下:\n\n1. 找到表头节点顶层\n\n2. 比较当前层下一节点(层指针指向的节点)和目标节点\n - 如果相等,找到节点,流程结束\n - 如果小于目标节点,那么跳到下一节点的当前层,重复步骤2\n - 如果大于目标节点或者到了表尾(指向null),那么跳到当前节点的下一层,重复步骤2\n\n如果无法再重复步骤2还没找到节点,说明节点不存在。要注意的是由于Redis允许重复的score值,所以进行对比操作时,不仅要检查 score 值,还要检查 member域。\n\n查找节点的复杂度平均**O(logN)**,最坏**O(N)**。\n\n##### 插入节点\n\n1. 找到要插入的位置,为其插入第一层。\n2. 通过指定的概率p和随机算法确定是否插入第N层,N不大于最大层数,默认32。\n\n如果概率p为0.5,那么可以用抛硬币来类比这个过程,如果是正面插入一层,然后再抛,否则退出。\n\n可以推导出如果p越大,那么高层节点越多,查找节点越快,占用空间越多。\n\n插入新节点复杂度平均 **O(logN)**,最坏**O(N)** 。\n\n##### 删除节点\n\n相比之下,删除节点操作较为简单,和单链表删除节点所做操作大体相同。删除节点的复杂度也是平均 **O(logN)**,最坏**O(N)**。删除分为内节点复杂度为**O(N)**,N为删除节点数量。\n\n##### 总结\n\nredis跳跃表以一种较为简单的方式实现和平衡树相同的效率,基本原理是建立一些“捷径”,免于逐节点比对。但是由于捷径是随机产生的,稳定性上还是不如平衡树。\n\n","source":"_posts/redis4.md","raw":"---\ntitle: \"Redis(四):跳跃表\"\ndate: 2021-03-11T21:26:55+08:00\ncategories:\n- 学习笔记\n- Redis\ntags:\n- Redis数据结构与对象\nkeywords:\n- redis\n- 跳跃表\ncomments: false\n#cover: images/host/redis/redis-icon-logo.png\nsummary: Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员(member)是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现。本文分析了redis跳跃表的实现和特点。\n---\n\n<!--more-->\n\n跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。在大部分情况下,跳跃表的效率可以和平衡树相媲美,而且实现还更加简单。\n\n##### 跳跃表结构解析\n\n![跳跃表结构示意图](/images/host/redis/redis-zskip.jpg)\n\n- `header`:指向跳跃表的表头节点。\n\n- `tail`:指向跳跃表的表尾节点。\n\n- `level`:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)。\n\n- `length`:记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内)。\n\n首先要注意的是跳表是有序的,从结构示意图中可以看到节点按`score`(即图中的1.0、2.0、3.0)从小到大排列,离表头越远,`score`越大。\n\n重点是节点的结构,节点包含的属性可以分为两部分讲,一部分用来跳跃,一部分用来保存对象。\n\n跳跃可以按方向分为两种:向表尾跳和向表头跳,分别通过**层**和**后退指针**完成。\n\n结构示意图中L1、L2、L3 …代表的就是层,可以看到一个节点的层不止一个,高层跳的远(跨度大),低层跳的近(跨度小)。而后退指针只有一个(图中的BW),所以只能以固定的跨度1向表头跳。\n\n###### 查找节点\n\n以升序表为例,查找节点的流程如下:\n\n1. 找到表头节点顶层\n\n2. 比较当前层下一节点(层指针指向的节点)和目标节点\n - 如果相等,找到节点,流程结束\n - 如果小于目标节点,那么跳到下一节点的当前层,重复步骤2\n - 如果大于目标节点或者到了表尾(指向null),那么跳到当前节点的下一层,重复步骤2\n\n如果无法再重复步骤2还没找到节点,说明节点不存在。要注意的是由于Redis允许重复的score值,所以进行对比操作时,不仅要检查 score 值,还要检查 member域。\n\n查找节点的复杂度平均**O(logN)**,最坏**O(N)**。\n\n##### 插入节点\n\n1. 找到要插入的位置,为其插入第一层。\n2. 通过指定的概率p和随机算法确定是否插入第N层,N不大于最大层数,默认32。\n\n如果概率p为0.5,那么可以用抛硬币来类比这个过程,如果是正面插入一层,然后再抛,否则退出。\n\n可以推导出如果p越大,那么高层节点越多,查找节点越快,占用空间越多。\n\n插入新节点复杂度平均 **O(logN)**,最坏**O(N)** 。\n\n##### 删除节点\n\n相比之下,删除节点操作较为简单,和单链表删除节点所做操作大体相同。删除节点的复杂度也是平均 **O(logN)**,最坏**O(N)**。删除分为内节点复杂度为**O(N)**,N为删除节点数量。\n\n##### 总结\n\nredis跳跃表以一种较为简单的方式实现和平衡树相同的效率,基本原理是建立一些“捷径”,免于逐节点比对。但是由于捷径是随机产生的,稳定性上还是不如平衡树。\n\n","slug":"redis4","published":1,"updated":"2021-06-28T20:04:35.527Z","layout":"post","photos":[],"link":"","_id":"ckqh4n02q000jykea4b0179qy","content":"<span id=\"more\"></span>\n\n<p>跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。在大部分情况下,跳跃表的效率可以和平衡树相媲美,而且实现还更加简单。</p>\n<h5 id=\"跳跃表结构解析\"><a href=\"#跳跃表结构解析\" class=\"headerlink\" title=\"跳跃表结构解析\"></a>跳跃表结构解析</h5><p><img src=\"/images/host/redis/redis-zskip.jpg\" alt=\"跳跃表结构示意图\"></p>\n<ul>\n<li><p><code>header</code>:指向跳跃表的表头节点。</p>\n</li>\n<li><p><code>tail</code>:指向跳跃表的表尾节点。</p>\n</li>\n<li><p><code>level</code>:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)。</p>\n</li>\n<li><p><code>length</code>:记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内)。</p>\n</li>\n</ul>\n<p>首先要注意的是跳表是有序的,从结构示意图中可以看到节点按<code>score</code>(即图中的1.0、2.0、3.0)从小到大排列,离表头越远,<code>score</code>越大。</p>\n<p>重点是节点的结构,节点包含的属性可以分为两部分讲,一部分用来跳跃,一部分用来保存对象。</p>\n<p>跳跃可以按方向分为两种:向表尾跳和向表头跳,分别通过<strong>层</strong>和<strong>后退指针</strong>完成。</p>\n<p>结构示意图中L1、L2、L3 …代表的就是层,可以看到一个节点的层不止一个,高层跳的远(跨度大),低层跳的近(跨度小)。而后退指针只有一个(图中的BW),所以只能以固定的跨度1向表头跳。</p>\n<h6 id=\"查找节点\"><a href=\"#查找节点\" class=\"headerlink\" title=\"查找节点\"></a>查找节点</h6><p>以升序表为例,查找节点的流程如下:</p>\n<ol>\n<li><p>找到表头节点顶层</p>\n</li>\n<li><p>比较当前层下一节点(层指针指向的节点)和目标节点</p>\n<ul>\n<li>如果相等,找到节点,流程结束</li>\n<li>如果小于目标节点,那么跳到下一节点的当前层,重复步骤2</li>\n<li>如果大于目标节点或者到了表尾(指向null),那么跳到当前节点的下一层,重复步骤2</li>\n</ul>\n</li>\n</ol>\n<p>如果无法再重复步骤2还没找到节点,说明节点不存在。要注意的是由于Redis允许重复的score值,所以进行对比操作时,不仅要检查 score 值,还要检查 member域。</p>\n<p>查找节点的复杂度平均**O(logN)<strong>,最坏</strong>O(N)**。</p>\n<h5 id=\"插入节点\"><a href=\"#插入节点\" class=\"headerlink\" title=\"插入节点\"></a>插入节点</h5><ol>\n<li>找到要插入的位置,为其插入第一层。</li>\n<li>通过指定的概率p和随机算法确定是否插入第N层,N不大于最大层数,默认32。</li>\n</ol>\n<p>如果概率p为0.5,那么可以用抛硬币来类比这个过程,如果是正面插入一层,然后再抛,否则退出。</p>\n<p>可以推导出如果p越大,那么高层节点越多,查找节点越快,占用空间越多。</p>\n<p>插入新节点复杂度平均 <strong>O(logN)<strong>,最坏</strong>O(N)</strong> 。</p>\n<h5 id=\"删除节点\"><a href=\"#删除节点\" class=\"headerlink\" title=\"删除节点\"></a>删除节点</h5><p>相比之下,删除节点操作较为简单,和单链表删除节点所做操作大体相同。删除节点的复杂度也是平均 **O(logN)<strong>,最坏</strong>O(N)<strong>。删除分为内节点复杂度为</strong>O(N)**,N为删除节点数量。</p>\n<h5 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h5><p>redis跳跃表以一种较为简单的方式实现和平衡树相同的效率,基本原理是建立一些“捷径”,免于逐节点比对。但是由于捷径是随机产生的,稳定性上还是不如平衡树。</p>\n","site":{"data":{}},"excerpt":"","more":"<p>跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。在大部分情况下,跳跃表的效率可以和平衡树相媲美,而且实现还更加简单。</p>\n<h5 id=\"跳跃表结构解析\"><a href=\"#跳跃表结构解析\" class=\"headerlink\" title=\"跳跃表结构解析\"></a>跳跃表结构解析</h5><p><img src=\"/images/host/redis/redis-zskip.jpg\" alt=\"跳跃表结构示意图\"></p>\n<ul>\n<li><p><code>header</code>:指向跳跃表的表头节点。</p>\n</li>\n<li><p><code>tail</code>:指向跳跃表的表尾节点。</p>\n</li>\n<li><p><code>level</code>:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)。</p>\n</li>\n<li><p><code>length</code>:记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内)。</p>\n</li>\n</ul>\n<p>首先要注意的是跳表是有序的,从结构示意图中可以看到节点按<code>score</code>(即图中的1.0、2.0、3.0)从小到大排列,离表头越远,<code>score</code>越大。</p>\n<p>重点是节点的结构,节点包含的属性可以分为两部分讲,一部分用来跳跃,一部分用来保存对象。</p>\n<p>跳跃可以按方向分为两种:向表尾跳和向表头跳,分别通过<strong>层</strong>和<strong>后退指针</strong>完成。</p>\n<p>结构示意图中L1、L2、L3 …代表的就是层,可以看到一个节点的层不止一个,高层跳的远(跨度大),低层跳的近(跨度小)。而后退指针只有一个(图中的BW),所以只能以固定的跨度1向表头跳。</p>\n<h6 id=\"查找节点\"><a href=\"#查找节点\" class=\"headerlink\" title=\"查找节点\"></a>查找节点</h6><p>以升序表为例,查找节点的流程如下:</p>\n<ol>\n<li><p>找到表头节点顶层</p>\n</li>\n<li><p>比较当前层下一节点(层指针指向的节点)和目标节点</p>\n<ul>\n<li>如果相等,找到节点,流程结束</li>\n<li>如果小于目标节点,那么跳到下一节点的当前层,重复步骤2</li>\n<li>如果大于目标节点或者到了表尾(指向null),那么跳到当前节点的下一层,重复步骤2</li>\n</ul>\n</li>\n</ol>\n<p>如果无法再重复步骤2还没找到节点,说明节点不存在。要注意的是由于Redis允许重复的score值,所以进行对比操作时,不仅要检查 score 值,还要检查 member域。</p>\n<p>查找节点的复杂度平均**O(logN)<strong>,最坏</strong>O(N)**。</p>\n<h5 id=\"插入节点\"><a href=\"#插入节点\" class=\"headerlink\" title=\"插入节点\"></a>插入节点</h5><ol>\n<li>找到要插入的位置,为其插入第一层。</li>\n<li>通过指定的概率p和随机算法确定是否插入第N层,N不大于最大层数,默认32。</li>\n</ol>\n<p>如果概率p为0.5,那么可以用抛硬币来类比这个过程,如果是正面插入一层,然后再抛,否则退出。</p>\n<p>可以推导出如果p越大,那么高层节点越多,查找节点越快,占用空间越多。</p>\n<p>插入新节点复杂度平均 <strong>O(logN)<strong>,最坏</strong>O(N)</strong> 。</p>\n<h5 id=\"删除节点\"><a href=\"#删除节点\" class=\"headerlink\" title=\"删除节点\"></a>删除节点</h5><p>相比之下,删除节点操作较为简单,和单链表删除节点所做操作大体相同。删除节点的复杂度也是平均 **O(logN)<strong>,最坏</strong>O(N)<strong>。删除分为内节点复杂度为</strong>O(N)**,N为删除节点数量。</p>\n<h5 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h5><p>redis跳跃表以一种较为简单的方式实现和平衡树相同的效率,基本原理是建立一些“捷径”,免于逐节点比对。但是由于捷径是随机产生的,稳定性上还是不如平衡树。</p>"},{"title":"Redis(五):整数集合","date":"2021-03-12T05:47:30.000Z","keywords":["redis","整数集合"],"comments":0,"summary":"整数集合(intset)是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。本文分析了整数集合的实现和特点。","_content":"\n<!--more-->\n\n整数集合(intset)是Redis用于保存整数值的集合抽象数据结构,它可以保存类型为int16_t、int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素。\n\n**整数集合结构示意图**\n\n![整数集合结构示意图](/images/host/redis/redis-intset.jpg)\n\n\n\n- `encoding`属性的值表示底层数组的整数类型。\n- `length`属性的值表示数组的长度。\n- `contents`就是底层整数数组。\n\n\n\nredis支持将不同类型的整数添加到整数数组中,但是不同类型的整数所占空间不同,这意味着需要额外的操作。\n\n当新元素的类型比整数集合现有所有元素的类型都要长时,需要升级操作。\n\n##### 升级操作\n\n1. 根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。\n\n2. 将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变。从末尾开始搬可以避免位置冲突。\n\n3. 将新元素添加到底层数组里面。\n\n因为引发升级的新元素的长度总是比整数集合现有所有元素的长度都大,所以这个新元素的值要么就大于所有现有元素而被放在末尾,要么就小于所有现有元素而被放在开头。\n\n \n\n通过升级操作,整数集合在具备灵活性的同时还能节约内存。要注意的是没有相对应的降级操作。\n\n\n\n##### 总结\n\n整数集合比较简单,要注意的是超出长度的的整数类型时引起的升级操作。\n\n\n\n\n\n","source":"_posts/redis5.md","raw":"---\ntitle: \"Redis(五):整数集合\"\ndate: 2021-03-12T21:47:30+08:00\ncategories:\n- 学习笔记\n- Redis\ntags:\n- Redis数据结构与对象\nkeywords:\n- redis\n- 整数集合\ncomments: false\n#thumbnailImage: images/host/redis/redis-icon-logo.png\nsummary: 整数集合(intset)是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。本文分析了整数集合的实现和特点。\n---\n\n<!--more-->\n\n整数集合(intset)是Redis用于保存整数值的集合抽象数据结构,它可以保存类型为int16_t、int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素。\n\n**整数集合结构示意图**\n\n![整数集合结构示意图](/images/host/redis/redis-intset.jpg)\n\n\n\n- `encoding`属性的值表示底层数组的整数类型。\n- `length`属性的值表示数组的长度。\n- `contents`就是底层整数数组。\n\n\n\nredis支持将不同类型的整数添加到整数数组中,但是不同类型的整数所占空间不同,这意味着需要额外的操作。\n\n当新元素的类型比整数集合现有所有元素的类型都要长时,需要升级操作。\n\n##### 升级操作\n\n1. 根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。\n\n2. 将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变。从末尾开始搬可以避免位置冲突。\n\n3. 将新元素添加到底层数组里面。\n\n因为引发升级的新元素的长度总是比整数集合现有所有元素的长度都大,所以这个新元素的值要么就大于所有现有元素而被放在末尾,要么就小于所有现有元素而被放在开头。\n\n \n\n通过升级操作,整数集合在具备灵活性的同时还能节约内存。要注意的是没有相对应的降级操作。\n\n\n\n##### 总结\n\n整数集合比较简单,要注意的是超出长度的的整数类型时引起的升级操作。\n\n\n\n\n\n","slug":"redis5","published":1,"updated":"2021-06-26T04:57:09.473Z","layout":"post","photos":[],"link":"","_id":"ckqh4n02u000kykea6x9baj2t","content":"<span id=\"more\"></span>\n\n<p>整数集合(intset)是Redis用于保存整数值的集合抽象数据结构,它可以保存类型为int16_t、int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素。</p>\n<p><strong>整数集合结构示意图</strong></p>\n<p><img src=\"/images/host/redis/redis-intset.jpg\" alt=\"整数集合结构示意图\"></p>\n<ul>\n<li><code>encoding</code>属性的值表示底层数组的整数类型。</li>\n<li><code>length</code>属性的值表示数组的长度。</li>\n<li><code>contents</code>就是底层整数数组。</li>\n</ul>\n<p>redis支持将不同类型的整数添加到整数数组中,但是不同类型的整数所占空间不同,这意味着需要额外的操作。</p>\n<p>当新元素的类型比整数集合现有所有元素的类型都要长时,需要升级操作。</p>\n<h5 id=\"升级操作\"><a href=\"#升级操作\" class=\"headerlink\" title=\"升级操作\"></a>升级操作</h5><ol>\n<li><p>根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。</p>\n</li>\n<li><p>将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变。从末尾开始搬可以避免位置冲突。</p>\n</li>\n<li><p>将新元素添加到底层数组里面。</p>\n</li>\n</ol>\n<p>因为引发升级的新元素的长度总是比整数集合现有所有元素的长度都大,所以这个新元素的值要么就大于所有现有元素而被放在末尾,要么就小于所有现有元素而被放在开头。</p>\n<p>通过升级操作,整数集合在具备灵活性的同时还能节约内存。要注意的是没有相对应的降级操作。</p>\n<h5 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h5><p>整数集合比较简单,要注意的是超出长度的的整数类型时引起的升级操作。</p>\n","site":{"data":{}},"excerpt":"","more":"<p>整数集合(intset)是Redis用于保存整数值的集合抽象数据结构,它可以保存类型为int16_t、int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素。</p>\n<p><strong>整数集合结构示意图</strong></p>\n<p><img src=\"/images/host/redis/redis-intset.jpg\" alt=\"整数集合结构示意图\"></p>\n<ul>\n<li><code>encoding</code>属性的值表示底层数组的整数类型。</li>\n<li><code>length</code>属性的值表示数组的长度。</li>\n<li><code>contents</code>就是底层整数数组。</li>\n</ul>\n<p>redis支持将不同类型的整数添加到整数数组中,但是不同类型的整数所占空间不同,这意味着需要额外的操作。</p>\n<p>当新元素的类型比整数集合现有所有元素的类型都要长时,需要升级操作。</p>\n<h5 id=\"升级操作\"><a href=\"#升级操作\" class=\"headerlink\" title=\"升级操作\"></a>升级操作</h5><ol>\n<li><p>根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。</p>\n</li>\n<li><p>将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变。从末尾开始搬可以避免位置冲突。</p>\n</li>\n<li><p>将新元素添加到底层数组里面。</p>\n</li>\n</ol>\n<p>因为引发升级的新元素的长度总是比整数集合现有所有元素的长度都大,所以这个新元素的值要么就大于所有现有元素而被放在末尾,要么就小于所有现有元素而被放在开头。</p>\n<p>通过升级操作,整数集合在具备灵活性的同时还能节约内存。要注意的是没有相对应的降级操作。</p>\n<h5 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h5><p>整数集合比较简单,要注意的是超出长度的的整数类型时引起的升级操作。</p>"},{"title":"Golang最佳实践:Functional Option","date":"2021-06-29T03:38:41.000Z","cover":"https://w.wallhaven.cc/full/y8/wallhaven-y85ojk.png","_content":"\n## 使用场景\n\n在初始化结构体时常常会碰到这样的情况,一些字段是不能为空且没有默认值,一些字段是不能为空但有默认值,还有的的则可以为空。\n\n举例来说,有这么一个结构体\n\n```go\ntype Pool struct {\n\tCapacity int32\n\tExpiryDuration time.Duration\n\tLogger Logger\n}\n```\n\n- `Capacity`表示池容量,不能为空且没有默认值(可以有,这里只是假设)\n\n- `ExpiryDuration`表示过期时间间隔,不能为空但有默认值\n\n- `Logger`表示日志,可以为空\n\n在这种情况下,由于需要使用默认值,所以直接用字面量初始化是不合适的。一般做法是提供初始化函数,比如如下:\n\n```go\nfunc NewDefaultPool(cap int32) (*Pool,error) {...}\n\nfunc NewPoolWithLogger(cap int32, logger Logger) (*Pool, error) {...}\n```\n\n### 缺点\n\n比较繁琐,并且因为go不支持重载,所以还得使用不同的函数名。\n\n## 解决方案一:Config\n\n### 做法\n\n把可选配置(有默认值或可以为空)的字段提到单独的结构体里\n\n```go\ntype Config struct {\n\tExpiryDuration time.Duration\n\tLogger Logger\n}\n```\n\n然后提供一个初始化函数\n\n```go\nfunc NewPool(cap int32, conf *Config) (*Pool, error) {...}\n```\n\n### 缺点\n\n这是比较常见的做法,但是需要额外的结构体,并且要判断`Config`是否为`nil`\n\n## 解决方案二:Builder\n\n### 做法\n\n把结构体包装为一个`Builder`\n\n```go\ntype PoolBuilder struct {\n Pool *Pool\n Err error\n}\n\nfunc (pb *PoolBuilder) Create(cap int32) *PoolBUilder {...}\n\nfunc (pb *PoolBuilder) WithLogger(logger Logger) *PoolBUilder {...}\n\nfunc (pb *PoolBuilder) Build() (*Pool, error)\n```\n\n然后通过链式调用构造结构体\n\n```go\npb := &PoolBuilder{}\np, err := pb.Create(100).WithLogger(logger).Build()\n```\n\n### 缺点\n\n这样还是需要额外的结构体,如果直接为`Pool`实现方法,无法很好进行错误处理\n\n## 最佳实践:Functional Options\n\n### 做法\n\n首先声明一个函数类型\n\n```go\ntype Option func(*Pool)\n```\n\n然后声明一组函数\n\n```go\nfunc WithExpiryDuration(t time.Duration) Option {\n return func(p *Pool) {\n p.ExpiryDuration = t\n }\n}\n\nfunc WithLogger(logger Logger) Option {\n return func(p *Pool) {\n p.Logger = logger\n }\n}\n\nfunc NewPool(cap int32, options ...func(*Pool)) (*Pool, error) {\n p := &Pool{\n Capacity: \t\tcap\n ExpiryDuration: time.Hour //默认值\n }\n \n for _, option := range options {\n option(p)\n }\n \n //...\n \n return p, nil\n}\n```\n\n最后构造方式如下\n\n```go\np, err := NewPool(100, WithLogger(Logger))\n```\n\n### 优点\n\n- 直觉式的编程\n- 高度的可配置化\n- 很容易维护和扩展\n- 自文档\n- 对于新来的人很容易上手\n- 没有什么令人困惑的事(是nil 还是空)\n\nOK,吹完了,收工\n","source":"_posts/go-option.md","raw":"---\ntitle: \"Golang最佳实践:Functional Option\"\ndate: 2021-06-29T19:38:41+08:00\ncategories:\n- 编程\ntags:\n- golang\n- 最佳实践\ncover: https://w.wallhaven.cc/full/y8/wallhaven-y85ojk.png\n---\n\n## 使用场景\n\n在初始化结构体时常常会碰到这样的情况,一些字段是不能为空且没有默认值,一些字段是不能为空但有默认值,还有的的则可以为空。\n\n举例来说,有这么一个结构体\n\n```go\ntype Pool struct {\n\tCapacity int32\n\tExpiryDuration time.Duration\n\tLogger Logger\n}\n```\n\n- `Capacity`表示池容量,不能为空且没有默认值(可以有,这里只是假设)\n\n- `ExpiryDuration`表示过期时间间隔,不能为空但有默认值\n\n- `Logger`表示日志,可以为空\n\n在这种情况下,由于需要使用默认值,所以直接用字面量初始化是不合适的。一般做法是提供初始化函数,比如如下:\n\n```go\nfunc NewDefaultPool(cap int32) (*Pool,error) {...}\n\nfunc NewPoolWithLogger(cap int32, logger Logger) (*Pool, error) {...}\n```\n\n### 缺点\n\n比较繁琐,并且因为go不支持重载,所以还得使用不同的函数名。\n\n## 解决方案一:Config\n\n### 做法\n\n把可选配置(有默认值或可以为空)的字段提到单独的结构体里\n\n```go\ntype Config struct {\n\tExpiryDuration time.Duration\n\tLogger Logger\n}\n```\n\n然后提供一个初始化函数\n\n```go\nfunc NewPool(cap int32, conf *Config) (*Pool, error) {...}\n```\n\n### 缺点\n\n这是比较常见的做法,但是需要额外的结构体,并且要判断`Config`是否为`nil`\n\n## 解决方案二:Builder\n\n### 做法\n\n把结构体包装为一个`Builder`\n\n```go\ntype PoolBuilder struct {\n Pool *Pool\n Err error\n}\n\nfunc (pb *PoolBuilder) Create(cap int32) *PoolBUilder {...}\n\nfunc (pb *PoolBuilder) WithLogger(logger Logger) *PoolBUilder {...}\n\nfunc (pb *PoolBuilder) Build() (*Pool, error)\n```\n\n然后通过链式调用构造结构体\n\n```go\npb := &PoolBuilder{}\np, err := pb.Create(100).WithLogger(logger).Build()\n```\n\n### 缺点\n\n这样还是需要额外的结构体,如果直接为`Pool`实现方法,无法很好进行错误处理\n\n## 最佳实践:Functional Options\n\n### 做法\n\n首先声明一个函数类型\n\n```go\ntype Option func(*Pool)\n```\n\n然后声明一组函数\n\n```go\nfunc WithExpiryDuration(t time.Duration) Option {\n return func(p *Pool) {\n p.ExpiryDuration = t\n }\n}\n\nfunc WithLogger(logger Logger) Option {\n return func(p *Pool) {\n p.Logger = logger\n }\n}\n\nfunc NewPool(cap int32, options ...func(*Pool)) (*Pool, error) {\n p := &Pool{\n Capacity: \t\tcap\n ExpiryDuration: time.Hour //默认值\n }\n \n for _, option := range options {\n option(p)\n }\n \n //...\n \n return p, nil\n}\n```\n\n最后构造方式如下\n\n```go\np, err := NewPool(100, WithLogger(Logger))\n```\n\n### 优点\n\n- 直觉式的编程\n- 高度的可配置化\n- 很容易维护和扩展\n- 自文档\n- 对于新来的人很容易上手\n- 没有什么令人困惑的事(是nil 还是空)\n\nOK,吹完了,收工\n","slug":"go-option","published":1,"updated":"2021-06-29T17:41:23.147Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckqic7viq0000yueah9e6baqr","content":"<h2 id=\"使用场景\"><a href=\"#使用场景\" class=\"headerlink\" title=\"使用场景\"></a>使用场景</h2><p>在初始化结构体时常常会碰到这样的情况,一些字段是不能为空且没有默认值,一些字段是不能为空但有默认值,还有的的则可以为空。</p>\n<p>举例来说,有这么一个结构体</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">type Pool struct {\n\tCapacity int32\n\tExpiryDuration time.Duration\n\tLogger Logger\n}</code></pre>\n\n<ul>\n<li><p><code>Capacity</code>表示池容量,不能为空且没有默认值(可以有,这里只是假设)</p>\n</li>\n<li><p><code>ExpiryDuration</code>表示过期时间间隔,不能为空但有默认值</p>\n</li>\n<li><p><code>Logger</code>表示日志,可以为空</p>\n</li>\n</ul>\n<p>在这种情况下,由于需要使用默认值,所以直接用字面量初始化是不合适的。一般做法是提供初始化函数,比如如下:</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">func NewDefaultPool(cap int32) (*Pool,error) {...}\n\nfunc NewPoolWithLogger(cap int32, logger Logger) (*Pool, error) {...}</code></pre>\n\n<h3 id=\"缺点\"><a href=\"#缺点\" class=\"headerlink\" title=\"缺点\"></a>缺点</h3><p>比较繁琐,并且因为go不支持重载,所以还得使用不同的函数名。</p>\n<h2 id=\"解决方案一:Config\"><a href=\"#解决方案一:Config\" class=\"headerlink\" title=\"解决方案一:Config\"></a>解决方案一:Config</h2><h3 id=\"做法\"><a href=\"#做法\" class=\"headerlink\" title=\"做法\"></a>做法</h3><p>把可选配置(有默认值或可以为空)的字段提到单独的结构体里</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">type Config struct {\n\tExpiryDuration time.Duration\n\tLogger Logger\n}</code></pre>\n\n<p>然后提供一个初始化函数</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">func NewPool(cap int32, conf *Config) (*Pool, error) {...}</code></pre>\n\n<h3 id=\"缺点-1\"><a href=\"#缺点-1\" class=\"headerlink\" title=\"缺点\"></a>缺点</h3><p>这是比较常见的做法,但是需要额外的结构体,并且要判断<code>Config</code>是否为<code>nil</code></p>\n<h2 id=\"解决方案二:Builder\"><a href=\"#解决方案二:Builder\" class=\"headerlink\" title=\"解决方案二:Builder\"></a>解决方案二:Builder</h2><h3 id=\"做法-1\"><a href=\"#做法-1\" class=\"headerlink\" title=\"做法\"></a>做法</h3><p>把结构体包装为一个<code>Builder</code></p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">type PoolBuilder struct {\n Pool *Pool\n Err error\n}\n\nfunc (pb *PoolBuilder) Create(cap int32) *PoolBUilder {...}\n\nfunc (pb *PoolBuilder) WithLogger(logger Logger) *PoolBUilder {...}\n\nfunc (pb *PoolBuilder) Build() (*Pool, error)</code></pre>\n\n<p>然后通过链式调用构造结构体</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">pb := &PoolBuilder{}\np, err := pb.Create(100).WithLogger(logger).Build()</code></pre>\n\n<h3 id=\"缺点-2\"><a href=\"#缺点-2\" class=\"headerlink\" title=\"缺点\"></a>缺点</h3><p>这样还是需要额外的结构体,如果直接为<code>Pool</code>实现方法,无法很好进行错误处理</p>\n<h2 id=\"最佳实践:Functional-Options\"><a href=\"#最佳实践:Functional-Options\" class=\"headerlink\" title=\"最佳实践:Functional Options\"></a>最佳实践:Functional Options</h2><h3 id=\"做法-2\"><a href=\"#做法-2\" class=\"headerlink\" title=\"做法\"></a>做法</h3><p>首先声明一个函数类型</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">type Option func(*Pool)</code></pre>\n\n<p>然后声明一组函数</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">func WithExpiryDuration(t time.Duration) Option {\n return func(p *Pool) {\n p.ExpiryDuration = t\n }\n}\n\nfunc WithLogger(logger Logger) Option {\n return func(p *Pool) {\n p.Logger = logger\n }\n}\n\nfunc NewPool(cap int32, options ...func(*Pool)) (*Pool, error) {\n p := &Pool{\n Capacity: \t\tcap\n ExpiryDuration: time.Hour //默认值\n }\n \n for _, option := range options {\n option(p)\n }\n \n //...\n \n return p, nil\n}</code></pre>\n\n<p>最后构造方式如下</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">p, err := NewPool(100, WithLogger(Logger))</code></pre>\n\n<h3 id=\"优点\"><a href=\"#优点\" class=\"headerlink\" title=\"优点\"></a>优点</h3><ul>\n<li>直觉式的编程</li>\n<li>高度的可配置化</li>\n<li>很容易维护和扩展</li>\n<li>自文档</li>\n<li>对于新来的人很容易上手</li>\n<li>没有什么令人困惑的事(是nil 还是空)</li>\n</ul>\n<p>OK,吹完了,收工</p>\n","site":{"data":{}},"excerpt":"","more":"<h2 id=\"使用场景\"><a href=\"#使用场景\" class=\"headerlink\" title=\"使用场景\"></a>使用场景</h2><p>在初始化结构体时常常会碰到这样的情况,一些字段是不能为空且没有默认值,一些字段是不能为空但有默认值,还有的的则可以为空。</p>\n<p>举例来说,有这么一个结构体</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">type Pool struct {\n\tCapacity int32\n\tExpiryDuration time.Duration\n\tLogger Logger\n}</code></pre>\n\n<ul>\n<li><p><code>Capacity</code>表示池容量,不能为空且没有默认值(可以有,这里只是假设)</p>\n</li>\n<li><p><code>ExpiryDuration</code>表示过期时间间隔,不能为空但有默认值</p>\n</li>\n<li><p><code>Logger</code>表示日志,可以为空</p>\n</li>\n</ul>\n<p>在这种情况下,由于需要使用默认值,所以直接用字面量初始化是不合适的。一般做法是提供初始化函数,比如如下:</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">func NewDefaultPool(cap int32) (*Pool,error) {...}\n\nfunc NewPoolWithLogger(cap int32, logger Logger) (*Pool, error) {...}</code></pre>\n\n<h3 id=\"缺点\"><a href=\"#缺点\" class=\"headerlink\" title=\"缺点\"></a>缺点</h3><p>比较繁琐,并且因为go不支持重载,所以还得使用不同的函数名。</p>\n<h2 id=\"解决方案一:Config\"><a href=\"#解决方案一:Config\" class=\"headerlink\" title=\"解决方案一:Config\"></a>解决方案一:Config</h2><h3 id=\"做法\"><a href=\"#做法\" class=\"headerlink\" title=\"做法\"></a>做法</h3><p>把可选配置(有默认值或可以为空)的字段提到单独的结构体里</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">type Config struct {\n\tExpiryDuration time.Duration\n\tLogger Logger\n}</code></pre>\n\n<p>然后提供一个初始化函数</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">func NewPool(cap int32, conf *Config) (*Pool, error) {...}</code></pre>\n\n<h3 id=\"缺点-1\"><a href=\"#缺点-1\" class=\"headerlink\" title=\"缺点\"></a>缺点</h3><p>这是比较常见的做法,但是需要额外的结构体,并且要判断<code>Config</code>是否为<code>nil</code></p>\n<h2 id=\"解决方案二:Builder\"><a href=\"#解决方案二:Builder\" class=\"headerlink\" title=\"解决方案二:Builder\"></a>解决方案二:Builder</h2><h3 id=\"做法-1\"><a href=\"#做法-1\" class=\"headerlink\" title=\"做法\"></a>做法</h3><p>把结构体包装为一个<code>Builder</code></p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">type PoolBuilder struct {\n Pool *Pool\n Err error\n}\n\nfunc (pb *PoolBuilder) Create(cap int32) *PoolBUilder {...}\n\nfunc (pb *PoolBuilder) WithLogger(logger Logger) *PoolBUilder {...}\n\nfunc (pb *PoolBuilder) Build() (*Pool, error)</code></pre>\n\n<p>然后通过链式调用构造结构体</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">pb := &PoolBuilder{}\np, err := pb.Create(100).WithLogger(logger).Build()</code></pre>\n\n<h3 id=\"缺点-2\"><a href=\"#缺点-2\" class=\"headerlink\" title=\"缺点\"></a>缺点</h3><p>这样还是需要额外的结构体,如果直接为<code>Pool</code>实现方法,无法很好进行错误处理</p>\n<h2 id=\"最佳实践:Functional-Options\"><a href=\"#最佳实践:Functional-Options\" class=\"headerlink\" title=\"最佳实践:Functional Options\"></a>最佳实践:Functional Options</h2><h3 id=\"做法-2\"><a href=\"#做法-2\" class=\"headerlink\" title=\"做法\"></a>做法</h3><p>首先声明一个函数类型</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">type Option func(*Pool)</code></pre>\n\n<p>然后声明一组函数</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">func WithExpiryDuration(t time.Duration) Option {\n return func(p *Pool) {\n p.ExpiryDuration = t\n }\n}\n\nfunc WithLogger(logger Logger) Option {\n return func(p *Pool) {\n p.Logger = logger\n }\n}\n\nfunc NewPool(cap int32, options ...func(*Pool)) (*Pool, error) {\n p := &Pool{\n Capacity: \t\tcap\n ExpiryDuration: time.Hour //默认值\n }\n \n for _, option := range options {\n option(p)\n }\n \n //...\n \n return p, nil\n}</code></pre>\n\n<p>最后构造方式如下</p>\n<pre class=\"line-numbers language-go\" data-language=\"go\"><code class=\"language-go\">p, err := NewPool(100, WithLogger(Logger))</code></pre>\n\n<h3 id=\"优点\"><a href=\"#优点\" class=\"headerlink\" title=\"优点\"></a>优点</h3><ul>\n<li>直觉式的编程</li>\n<li>高度的可配置化</li>\n<li>很容易维护和扩展</li>\n<li>自文档</li>\n<li>对于新来的人很容易上手</li>\n<li>没有什么令人困惑的事(是nil 还是空)</li>\n</ul>\n<p>OK,吹完了,收工</p>\n"}],"PostAsset":[],"PostCategory":[{"post_id":"ckqh4n00c0000ykea338kcok7","category_id":"ckqh4n00y0002ykeads5chpts","_id":"ckqh4n037000sykea3390bgg6"},{"post_id":"ckqh4n00c0000ykea338kcok7","category_id":"ckqh4n02y000lykea82q600h9","_id":"ckqh4n03a000uykea4mkmev10"},{"post_id":"ckqh4n00s0001ykeae79p0kqv","category_id":"ckqh4n01g0007ykea2yuy2ag3","_id":"ckqh4n03k0013ykea4lxiaa6y"},{"post_id":"ckqh4n00s0001ykeae79p0kqv","category_id":"ckqh4n03b000vykeagi6h2j7x","_id":"ckqh4n03l0015ykea6c2ndvww"},{"post_id":"ckqh4n026000dykeabtaf2fxz","category_id":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n03v001bykea0ocpghsg"},{"post_id":"ckqh4n026000dykeabtaf2fxz","category_id":"ckqh4n03j0012ykeaahecckzs","_id":"ckqh4n03x001dykeaapl8fbsc"},{"post_id":"ckqh4n0150004ykeabd8ufdrk","category_id":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n041001hykeaem4wb5ae"},{"post_id":"ckqh4n0150004ykeabd8ufdrk","category_id":"ckqh4n03n0018ykeaelmrdox5","_id":"ckqh4n044001jykeafhrp47ym"},{"post_id":"ckqh4n02b000fykeaf6s62n71","category_id":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n047001mykeadlf9a5za"},{"post_id":"ckqh4n02b000fykeaf6s62n71","category_id":"ckqh4n03j0012ykeaahecckzs","_id":"ckqh4n049001oykeae5ct2wmp"},{"post_id":"ckqh4n02q000jykea4b0179qy","category_id":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n04b001qykea1x25gx3f"},{"post_id":"ckqh4n02q000jykea4b0179qy","category_id":"ckqh4n03j0012ykeaahecckzs","_id":"ckqh4n04d001tykea4yr92wmx"},{"post_id":"ckqh4n0190005ykeaa5d2hx18","category_id":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n04g001wykea4eaw22un"},{"post_id":"ckqh4n0190005ykeaa5d2hx18","category_id":"ckqh4n046001lykeag6oyg63p","_id":"ckqh4n04h001xykea1tszfbb6"},{"post_id":"ckqh4n02u000kykea6x9baj2t","category_id":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n04i001zykea6y1bg20i"},{"post_id":"ckqh4n02u000kykea6x9baj2t","category_id":"ckqh4n03j0012ykeaahecckzs","_id":"ckqh4n04k0021ykea1vda8jmd"},{"post_id":"ckqh4n01d0006ykea72sob0i7","category_id":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n04l0023ykea7ta801ks"},{"post_id":"ckqh4n01d0006ykea72sob0i7","category_id":"ckqh4n04f001vykeaecfn59gs","_id":"ckqh4n04m0024ykea8cox02xd"},{"post_id":"ckqh4n01j0009ykea2b428t5o","category_id":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n04n0025ykea2wlm110c"},{"post_id":"ckqh4n01j0009ykea2b428t5o","category_id":"ckqh4n04f001vykeaecfn59gs","_id":"ckqh4n04o0026ykea9w0cfa77"},{"post_id":"ckqh4n01n000aykea8yxi67el","category_id":"ckqh4n020000bykeag0pm33i9","_id":"ckqh4n04o0027ykeab7rt30tq"},{"post_id":"ckqh4n01n000aykea8yxi67el","category_id":"ckqh4n03j0012ykeaahecckzs","_id":"ckqh4n04p0028ykeadmjgdvet"},{"post_id":"ckqic7viq0000yueah9e6baqr","category_id":"ckqic7vj70001yueacqn0hzb5","_id":"ckqic7vjr0005yuea11p89aen"}],"PostTag":[{"post_id":"ckqh4n00c0000ykea338kcok7","tag_id":"ckqh4n0130003ykea5aywbx3d","_id":"ckqh4n029000eykea11xzg4e1"},{"post_id":"ckqh4n00c0000ykea338kcok7","tag_id":"ckqh4n01h0008ykeab0cm7szh","_id":"ckqh4n02g000gykea68ky4qri"},{"post_id":"ckqh4n00s0001ykeae79p0kqv","tag_id":"ckqh4n022000cykea02lc7u3z","_id":"ckqh4n032000nykea9wwwflfc"},{"post_id":"ckqh4n00s0001ykeae79p0kqv","tag_id":"ckqh4n02j000iykeaf5lu4c9h","_id":"ckqh4n033000oykeaf6w162uz"},{"post_id":"ckqh4n0150004ykeabd8ufdrk","tag_id":"ckqh4n030000mykea60p006xq","_id":"ckqh4n03e000xykea6pxq502q"},{"post_id":"ckqh4n0150004ykeabd8ufdrk","tag_id":"ckqh4n034000qykeaczhy8mzl","_id":"ckqh4n03g000zykea27465cx4"},{"post_id":"ckqh4n0150004ykeabd8ufdrk","tag_id":"ckqh4n039000tykeag4rjapbc","_id":"ckqh4n03i0011ykea9de1hvvp"},{"post_id":"ckqh4n0190005ykeaa5d2hx18","tag_id":"ckqh4n03d000wykeabngv4dab","_id":"ckqh4n03k0014ykeahmmrf053"},{"post_id":"ckqh4n01d0006ykea72sob0i7","tag_id":"ckqh4n03h0010ykeaejnn6398","_id":"ckqh4n03n0017ykea3frjffew"},{"post_id":"ckqh4n01j0009ykea2b428t5o","tag_id":"ckqh4n03h0010ykeaejnn6398","_id":"ckqh4n03r001aykea4uofav0z"},{"post_id":"ckqh4n01n000aykea8yxi67el","tag_id":"ckqh4n03p0019ykea7urafnop","_id":"ckqh4n040001fykea74lx8vue"},{"post_id":"ckqh4n026000dykeabtaf2fxz","tag_id":"ckqh4n03p0019ykea7urafnop","_id":"ckqh4n045001kykea017xbbq3"},{"post_id":"ckqh4n02b000fykeaf6s62n71","tag_id":"ckqh4n03p0019ykea7urafnop","_id":"ckqh4n04a001pykeaf9s17ie7"},{"post_id":"ckqh4n02q000jykea4b0179qy","tag_id":"ckqh4n03p0019ykea7urafnop","_id":"ckqh4n04e001uykeaa9zq4kmk"},{"post_id":"ckqh4n02u000kykea6x9baj2t","tag_id":"ckqh4n03p0019ykea7urafnop","_id":"ckqh4n04h001yykea4hpc8yw2"},{"post_id":"ckqic7viq0000yueah9e6baqr","tag_id":"ckqh4n0130003ykea5aywbx3d","_id":"ckqic7vjp0003yuea2js67dl9"},{"post_id":"ckqic7viq0000yueah9e6baqr","tag_id":"ckqic7vjm0002yueaehq5c5d9","_id":"ckqic7vjq0004yueae7y5661x"}],"Tag":[{"name":"golang","_id":"ckqh4n0130003ykea5aywbx3d"},{"name":"编译","_id":"ckqh4n01h0008ykeab0cm7szh"},{"name":"硬件","_id":"ckqh4n022000cykea02lc7u3z"},{"name":"键盘","_id":"ckqh4n02j000iykeaf5lu4c9h"},{"name":"mysql","_id":"ckqh4n030000mykea60p006xq"},{"name":"事务","_id":"ckqh4n034000qykeaczhy8mzl"},{"name":"调优","_id":"ckqh4n039000tykeag4rjapbc"},{"name":"mysql实现原理","_id":"ckqh4n03d000wykeabngv4dab"},{"name":"tcp","_id":"ckqh4n03h0010ykeaejnn6398"},{"name":"Redis数据结构与对象","_id":"ckqh4n03p0019ykea7urafnop"},{"name":"最佳实践","_id":"ckqic7vjm0002yueaehq5c5d9"}]}}