├── .github └── FUNDING.yml ├── .gitignore ├── .readthedocs.yaml ├── Makefile ├── README.rst ├── _image ├── clone.png ├── encoding.png ├── filetransfer.png ├── getkeys.png ├── goweb │ ├── concurrency │ │ └── 扇出扇入.png │ └── gocode阅读.png ├── login1.png ├── login2.png ├── login3.png ├── makekeys.png ├── microservice_distribute │ ├── slowflake.png │ ├── 服务发现对比.png │ └── 熔断器原理.png ├── register.png ├── reset1.png ├── reset2.png ├── reset3.png ├── uml.png ├── xshell_font.png ├── xshell_font_btn.png └── xshell_ft.png ├── _static ├── aaaaa.png ├── addgroup.png ├── addgroup2.png ├── addtogroup.png ├── adduser.png ├── addusertogroup.png ├── banshou.png ├── basic.css ├── favicon.ico ├── figs │ └── magic-2.png ├── login.png ├── main.png ├── notify.png ├── rst.png ├── sync.png ├── translations.js └── unreadnotify.png ├── algorithms └── algorithms.rst ├── base └── basics.rst ├── codingstyle └── codingstyle.rst ├── codingtools └── codingtools.rst ├── conf.py ├── database ├── index.rst ├── mongo.rst ├── mysql.rst └── redis.rst ├── debug └── index.rst ├── design └── design.rst ├── devtools ├── hg.rst ├── index.rst ├── ipython.rst ├── other.rst ├── vim.rst └── xshell.rst ├── docker.rst ├── favicon.ico ├── go-note ├── algorithms.rst ├── concurrency_patterns.rst ├── design_patterns.rst ├── index.rst ├── mistakes.rst ├── optimize.rst ├── tricks.rst └── web.rst ├── idiom └── idiom.rst ├── index.rst ├── memo └── memo.rst ├── meta.rst ├── microservice_distribute ├── index.rst ├── library.rst └── theory.rst ├── mush.rst ├── python-note ├── crawler.rst ├── ffmpeg.rst ├── flaskapi.rst ├── funny.rst ├── index.rst ├── pdf.rst └── uncurl.rst ├── requirements.txt ├── skillstack ├── docker.rst ├── example.py ├── index.rst ├── linux.rst ├── rst.rst └── shell.rst └── tracing ├── index.rst └── sentry.rst /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [PegasusWang] 2 | custom: ["https://www.paypal.me/pegasuswang"] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | build/* 3 | _build/* 4 | .ropeproject 5 | .DS_Store 6 | .todo_cache 7 | database/Mysql_bast_practice.htm 8 | .python-version 9 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: "ubuntu-22.04" 5 | tools: 6 | python: "3.10" 7 | 8 | # Build from the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: conf.py 11 | 12 | # Explicitly set the version of Python and its requirements 13 | python: 14 | install: 15 | - requirements: requirements.txt 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: sphinx 3 | 4 | BUILD_DIR=_build 5 | SPHINXOPTS=-n -W -d $(BUILD_DIR)/doctrees . 6 | 7 | .PHONY: html 8 | html: 9 | sphinx-build -b html $(SPHINXOPTS) $(BUILD_DIR)/html 10 | 11 | .PHONY: epub 12 | epub: 13 | sphinx-build -b epub $(SPHINXOPTS) $(BUILD_DIR)/epub 14 | @echo 15 | @echo "Build finished. The epub file is in $(BUILD_DIR)/epub." 16 | 17 | .PHONY: coverage 18 | coverage: 19 | sphinx-build -b coverage ${SPHINXOPTS} $(BUILD_DIR)/coverage 20 | cat build/coverage/python.txt 21 | 22 | .PHONY: latex 23 | latex: 24 | sphinx-build -b latex $(SPHINXOPTS) $(BUILD_DIR)/latex 25 | 26 | # Building a pdf requires a latex installation. For macports, the needed 27 | # packages are texlive-latex-extra and texlive-fonts-recommended. 28 | # The output is in build/latex/tornado.pdf 29 | .PHONY: pdf 30 | pdf: latex 31 | cd $(BUILD_DIR)/latex && pdflatex -interaction=nonstopmode tornado.tex 32 | 33 | clean: 34 | rm -rf $(BUILD_DIR) 35 | 36 | .PHONY: livehtml 37 | livehtml: 38 | sphinx-autobuild -b html $(SPHINXOPTS) $(BUILD_DIR)/html 39 | 40 | .PHONY: serve 41 | serve: 42 | sphinx-autobuild -b html $(SPHINXOPTS) $(BUILD_DIR)/html 43 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================================== 2 | Python/Golang Web 入坑指南 3 | ================================== 4 | 5 | .. code-block:: text 6 | 7 | ____ _ _ ______ __ __ _ ____ _ _ 8 | | _ \ _ _| |_| |__ ___ _ __ / / ___| ___ \ \ / /__| |__ / ___|_ _(_) __| | ___ 9 | | |_) | | | | __| '_ \ / _ \| '_ \ / / | _ / _ \ \ \ /\ / / _ \ '_ \ | | _| | | | |/ _` |/ _ \ 10 | | __/| |_| | |_| | | | (_) | | | |/ /| |_| | (_) | \ V V / __/ |_) | | |_| | |_| | | (_| | __/ 11 | |_| \__, |\__|_| |_|\___/|_| |_/_/ \____|\___/ \_/\_/ \___|_.__/ \____|\__,_|_|\__,_|\___| 12 | |___/ 13 | 14 | `本电子书线上访问地址 https://python-web-guide.readthedocs.io/zh/latest/ `_ 15 | 16 | 本指南根据作者的自学和工作经历提供(吐槽)一下python/golang 17 | web的学习路线,主要包括概念介绍,参考书籍,开发工具和开发流程等,希望可以帮助非科班人士通过自学入门python/golang 18 | 网站开发,弥补学校教育和公司需求之间的鸿沟(也作为自己的学习笔记和面试参考手册),同时也希望可以作为公司菜鸟实习生的培训手册,帮助公司快速培训新人上手开发,减轻招聘压力。 19 | 笔者目前能力有限,希望有经验的python圈人士可以一起协作。 20 | 本小书灵感来自于 requests 库作者的 `python-guide `_ 。 21 | 你可以使用强大的电子书阅读软件 `calibre `_ 下载epub格式阅读。 22 | 23 | 如果您感兴趣,也可以参考慕课网教程 `《Python工程师面试宝典》 `_ 。 24 | 本课程提供了详细的Python后端知识大纲和常考面试题,帮助自学的同学就业。如果本文档有误,您可以在 github 直接提 issue. 25 | 26 | 注意:Python 不适合工程管理不完善的团队构建大型项目。如果贵团队没有编码规范、单元测试、静态检测、持续集成、文档注释中的一个或者几个,请慎用动态语言。 27 | Python 结合 Go 基本可以解决大部分业务场景,Python 用来快速实现业务和想法,Go 来解决性能瓶颈,这俩也是笔者目前使用最多的语言。 28 | 如果因为某些网络原因打不开 readthedoc 网站,您可以参考下方快速上手使用 sphinx 本地构建电子书访问。 29 | 30 | 31 | .. image:: https://readthedocs.org/projects/z42/badge/?version=latest 32 | 33 | .. code-block:: python 34 | 35 | # 快速上手构建本地电子书 36 | # 使用方式 1 37 | # 本项目页面托管在 readthedoc,如果国内因为网络原因打不开,可以使用如下方式在本地构建 38 | git clone https://github.com/PegasusWang/python-web-guide.git # 协作请fork一份你自己的地址 39 | pip install -r requirements.txt # 安装 Sphinx==1.3.4 40 | make html # 构建 html 电子书,之后会在本地生成一个 _build/html 文件夹 41 | cd _build/html # 切换到构建好的 html 静态文件夹里 42 | python3 -m http.server # 启动一个本地文件服务器,或者 python2 用 python -m SimpleHTTPServer 43 | # 之后打开 http://127.0.0.1:8000/ 即可本地访问电子书 44 | 45 | # 方式2(推荐):加入 sphinx-autobuild 自动编辑刷新 46 | git clone https://github.com/PegasusWang/python-web-guide.git # 协作请fork一份你自己的地址 47 | pip install -r requirements.txt # 安装 Sphinx==1.3.4, sphinx-autobuild 48 | make serve # 之后打开 http://127.0.0.1:8000/ 即可本地访问电子书,编辑保存直接自动刷新浏览器 49 | 50 | 文档采用rst格式书写,用 `readthedocs `_ 托管。一个快速的rst语法demo `教程 `_。 如果使用vim编写可以使用rst插件 `riv.vim `_ 配合 `InstantRst `_ 本地预览,定期pull一下拉取更新。 51 | 欢迎你fork一份然后添加自己的章节,本书主要面对经验尚浅的同学作为自学的指导手册,并非速成指南,内容来自笔者日常学习和工作经验的持续总结。 52 | 本电子版书集合了同事的智慧结晶,非常感谢你们带我入坑。 53 | 本指南同时会有一些不负责任的吐槽。学到东西的请狂点 star,让笔者有动力更新更多业界实战干货,更多技术分享请关注作者知乎帐号 `pegasuswang `_ ,知乎专栏 `Python 学习之路 `_ ,`个人博客 `_ 。 54 | 笔者还维护了一个 vim 视频教程专栏,感兴趣可以访问 `玩转vim `_ 55 | 56 | TODO: 57 | ================================================================= 58 | 如果您觉得有用,可以打赏支持作者继续创作! 59 | 60 | 61 | .. raw:: html 62 | 63 |
64 | 微信打赏 65 |
66 | -------------------------------------------------------------------------------- /_image/clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/clone.png -------------------------------------------------------------------------------- /_image/encoding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/encoding.png -------------------------------------------------------------------------------- /_image/filetransfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/filetransfer.png -------------------------------------------------------------------------------- /_image/getkeys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/getkeys.png -------------------------------------------------------------------------------- /_image/goweb/concurrency/扇出扇入.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/goweb/concurrency/扇出扇入.png -------------------------------------------------------------------------------- /_image/goweb/gocode阅读.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/goweb/gocode阅读.png -------------------------------------------------------------------------------- /_image/login1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/login1.png -------------------------------------------------------------------------------- /_image/login2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/login2.png -------------------------------------------------------------------------------- /_image/login3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/login3.png -------------------------------------------------------------------------------- /_image/makekeys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/makekeys.png -------------------------------------------------------------------------------- /_image/microservice_distribute/slowflake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/microservice_distribute/slowflake.png -------------------------------------------------------------------------------- /_image/microservice_distribute/服务发现对比.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/microservice_distribute/服务发现对比.png -------------------------------------------------------------------------------- /_image/microservice_distribute/熔断器原理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/microservice_distribute/熔断器原理.png -------------------------------------------------------------------------------- /_image/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/register.png -------------------------------------------------------------------------------- /_image/reset1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/reset1.png -------------------------------------------------------------------------------- /_image/reset2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/reset2.png -------------------------------------------------------------------------------- /_image/reset3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/reset3.png -------------------------------------------------------------------------------- /_image/uml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/uml.png -------------------------------------------------------------------------------- /_image/xshell_font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/xshell_font.png -------------------------------------------------------------------------------- /_image/xshell_font_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/xshell_font_btn.png -------------------------------------------------------------------------------- /_image/xshell_ft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_image/xshell_ft.png -------------------------------------------------------------------------------- /_static/aaaaa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/aaaaa.png -------------------------------------------------------------------------------- /_static/addgroup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/addgroup.png -------------------------------------------------------------------------------- /_static/addgroup2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/addgroup2.png -------------------------------------------------------------------------------- /_static/addtogroup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/addtogroup.png -------------------------------------------------------------------------------- /_static/adduser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/adduser.png -------------------------------------------------------------------------------- /_static/addusertogroup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/addusertogroup.png -------------------------------------------------------------------------------- /_static/banshou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/banshou.png -------------------------------------------------------------------------------- /_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | /* -- relbar ---------------------------------------------------------------- */ 19 | 20 | div.related { 21 | width: 100%; 22 | font-size: 90%; 23 | } 24 | 25 | div.related h3 { 26 | display: none; 27 | } 28 | 29 | div.related ul { 30 | margin: 0; 31 | padding: 0 0 0 10px; 32 | list-style: none; 33 | } 34 | 35 | div.related li { 36 | display: inline; 37 | } 38 | 39 | div.related li.right { 40 | float: right; 41 | margin-right: 5px; 42 | } 43 | 44 | /* -- sidebar --------------------------------------------------------------- */ 45 | 46 | div.sphinxsidebarwrapper { 47 | padding: 10px 5px 0 10px; 48 | } 49 | 50 | div.sphinxsidebar { 51 | float: left; 52 | width: 230px; 53 | margin-left: -100%; 54 | font-size: 90%; 55 | } 56 | 57 | div.sphinxsidebar ul { 58 | list-style: none; 59 | } 60 | 61 | div.sphinxsidebar ul ul, 62 | div.sphinxsidebar ul.want-points { 63 | margin-left: 20px; 64 | list-style: square; 65 | } 66 | 67 | div.sphinxsidebar ul ul { 68 | margin-top: 0; 69 | margin-bottom: 0; 70 | } 71 | 72 | div.sphinxsidebar form { 73 | margin-top: 10px; 74 | } 75 | 76 | div.sphinxsidebar input { 77 | border: 1px solid #98dbcc; 78 | font-family: sans-serif; 79 | font-size: 1em; 80 | } 81 | 82 | div.sphinxsidebar input[type="text"] { 83 | width: 170px; 84 | } 85 | 86 | div.sphinxsidebar input[type="submit"] { 87 | width: 30px; 88 | } 89 | 90 | img { 91 | border: 0; 92 | } 93 | 94 | /* -- search page ----------------------------------------------------------- */ 95 | 96 | ul.search { 97 | margin: 10px 0 0 20px; 98 | padding: 0; 99 | } 100 | 101 | ul.search li { 102 | padding: 5px 0 5px 20px; 103 | background-image: url(file.png); 104 | background-repeat: no-repeat; 105 | background-position: 0 7px; 106 | } 107 | 108 | ul.search li a { 109 | font-weight: bold; 110 | } 111 | 112 | ul.search li div.context { 113 | color: #888; 114 | margin: 2px 0 0 30px; 115 | text-align: left; 116 | } 117 | 118 | ul.keywordmatches li.goodmatch a { 119 | font-weight: bold; 120 | } 121 | 122 | /* -- index page ------------------------------------------------------------ */ 123 | 124 | table.contentstable { 125 | width: 90%; 126 | } 127 | 128 | table.contentstable p.biglink { 129 | line-height: 150%; 130 | } 131 | 132 | a.biglink { 133 | font-size: 1.3em; 134 | } 135 | 136 | span.linkdescr { 137 | font-style: italic; 138 | padding-top: 5px; 139 | font-size: 90%; 140 | } 141 | 142 | /* -- general index --------------------------------------------------------- */ 143 | 144 | table.indextable { 145 | width: 100%; 146 | } 147 | 148 | table.indextable td { 149 | text-align: left; 150 | vertical-align: top; 151 | } 152 | 153 | table.indextable dl, table.indextable dd { 154 | margin-top: 0; 155 | margin-bottom: 0; 156 | } 157 | 158 | table.indextable tr.pcap { 159 | height: 10px; 160 | } 161 | 162 | table.indextable tr.cap { 163 | margin-top: 10px; 164 | background-color: #f2f2f2; 165 | } 166 | 167 | img.toggler { 168 | margin-right: 3px; 169 | margin-top: 3px; 170 | cursor: pointer; 171 | } 172 | 173 | div.modindex-jumpbox { 174 | border-top: 1px solid #ddd; 175 | border-bottom: 1px solid #ddd; 176 | margin: 1em 0 1em 0; 177 | padding: 0.4em; 178 | } 179 | 180 | div.genindex-jumpbox { 181 | border-top: 1px solid #ddd; 182 | border-bottom: 1px solid #ddd; 183 | margin: 1em 0 1em 0; 184 | padding: 0.4em; 185 | } 186 | 187 | /* -- general body styles --------------------------------------------------- */ 188 | 189 | a.headerlink { 190 | visibility: hidden; 191 | } 192 | 193 | h1:hover > a.headerlink, 194 | h2:hover > a.headerlink, 195 | h3:hover > a.headerlink, 196 | h4:hover > a.headerlink, 197 | h5:hover > a.headerlink, 198 | h6:hover > a.headerlink, 199 | dt:hover > a.headerlink { 200 | visibility: visible; 201 | } 202 | 203 | div.body p.caption { 204 | text-align: inherit; 205 | } 206 | 207 | div.body td { 208 | text-align: left; 209 | } 210 | 211 | .field-list ul { 212 | padding-left: 1em; 213 | } 214 | 215 | .first { 216 | margin-top: 0 !important; 217 | } 218 | 219 | p.rubric { 220 | margin-top: 30px; 221 | font-weight: bold; 222 | } 223 | 224 | img.align-left, .figure.align-left, object.align-left { 225 | clear: left; 226 | float: left; 227 | margin-right: 1em; 228 | } 229 | 230 | img.align-right, .figure.align-right, object.align-right { 231 | clear: right; 232 | float: right; 233 | margin-left: 1em; 234 | } 235 | 236 | img.align-center, .figure.align-center, object.align-center { 237 | display: block; 238 | margin-left: auto; 239 | margin-right: auto; 240 | } 241 | 242 | .align-left { 243 | text-align: left; 244 | } 245 | 246 | .align-center { 247 | text-align: center; 248 | } 249 | 250 | .align-right { 251 | text-align: right; 252 | } 253 | 254 | /* -- sidebars -------------------------------------------------------------- */ 255 | 256 | div.sidebar { 257 | margin: 0 0 0.5em 1em; 258 | border: 1px solid #ddb; 259 | padding: 7px 7px 0 7px; 260 | background-color: #ffe; 261 | width: 40%; 262 | float: right; 263 | } 264 | 265 | p.sidebar-title { 266 | font-weight: bold; 267 | } 268 | 269 | /* -- topics ---------------------------------------------------------------- */ 270 | 271 | div.topic { 272 | border: 1px solid #ccc; 273 | padding: 7px 7px 0 7px; 274 | margin: 10px 0 10px 0; 275 | } 276 | 277 | p.topic-title { 278 | font-size: 1.1em; 279 | font-weight: bold; 280 | margin-top: 10px; 281 | } 282 | 283 | /* -- admonitions ----------------------------------------------------------- */ 284 | 285 | div.admonition { 286 | margin-top: 10px; 287 | margin-bottom: 10px; 288 | padding: 7px; 289 | } 290 | 291 | div.admonition dt { 292 | font-weight: bold; 293 | } 294 | 295 | div.admonition dl { 296 | margin-bottom: 0; 297 | } 298 | 299 | p.admonition-title { 300 | margin: 0px 10px 5px 0px; 301 | font-weight: bold; 302 | } 303 | 304 | div.body p.centered { 305 | text-align: center; 306 | margin-top: 25px; 307 | } 308 | 309 | /* -- tables ---------------------------------------------------------------- */ 310 | 311 | table.docutils { 312 | border: 0; 313 | border-collapse: collapse; 314 | } 315 | 316 | table.docutils td, table.docutils th { 317 | padding: 1px 8px 1px 5px; 318 | border-top: 0; 319 | border-left: 0; 320 | border-right: 0; 321 | border-bottom: 1px solid #aaa; 322 | } 323 | 324 | table.field-list td, table.field-list th { 325 | border: 0 !important; 326 | } 327 | 328 | table.footnote td, table.footnote th { 329 | border: 0 !important; 330 | } 331 | 332 | th { 333 | text-align: left; 334 | padding-right: 5px; 335 | } 336 | 337 | table.citation { 338 | border-left: solid 1px gray; 339 | margin-left: 1px; 340 | } 341 | 342 | table.citation td { 343 | border-bottom: none; 344 | } 345 | 346 | /* -- other body styles ----------------------------------------------------- */ 347 | 348 | ol.arabic { 349 | list-style: decimal; 350 | } 351 | 352 | ol.loweralpha { 353 | list-style: lower-alpha; 354 | } 355 | 356 | ol.upperalpha { 357 | list-style: upper-alpha; 358 | } 359 | 360 | ol.lowerroman { 361 | list-style: lower-roman; 362 | } 363 | 364 | ol.upperroman { 365 | list-style: upper-roman; 366 | } 367 | 368 | dl { 369 | margin-bottom: 15px; 370 | } 371 | 372 | dd p { 373 | margin-top: 0px; 374 | } 375 | 376 | dd ul, dd table { 377 | margin-bottom: 10px; 378 | } 379 | 380 | dd { 381 | margin-top: 3px; 382 | margin-bottom: 10px; 383 | margin-left: 30px; 384 | } 385 | 386 | dt:target, .highlighted { 387 | background-color: #fbe54e; 388 | } 389 | 390 | dl.glossary dt { 391 | font-weight: bold; 392 | font-size: 1.1em; 393 | } 394 | 395 | .field-list ul { 396 | margin: 0; 397 | padding-left: 1em; 398 | } 399 | 400 | .field-list p { 401 | margin: 0; 402 | } 403 | 404 | .refcount { 405 | color: #060; 406 | } 407 | 408 | .optional { 409 | font-size: 1.3em; 410 | } 411 | 412 | .versionmodified { 413 | font-style: italic; 414 | } 415 | 416 | .system-message { 417 | background-color: #fda; 418 | padding: 5px; 419 | border: 3px solid red; 420 | } 421 | 422 | .footnote:target { 423 | background-color: #ffa; 424 | } 425 | 426 | .line-block { 427 | display: block; 428 | margin-top: 1em; 429 | margin-bottom: 1em; 430 | } 431 | 432 | .line-block .line-block { 433 | margin-top: 0; 434 | margin-bottom: 0; 435 | margin-left: 1.5em; 436 | } 437 | 438 | .guilabel, .menuselection { 439 | font-family: sans-serif; 440 | } 441 | 442 | .accelerator { 443 | text-decoration: underline; 444 | } 445 | 446 | .classifier { 447 | font-style: oblique; 448 | } 449 | 450 | abbr, acronym { 451 | border-bottom: dotted 1px; 452 | cursor: help; 453 | } 454 | 455 | /* -- code displays --------------------------------------------------------- */ 456 | 457 | pre { 458 | overflow: auto; 459 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 460 | } 461 | 462 | td.linenos pre { 463 | padding: 5px 0px; 464 | border: 0; 465 | background-color: transparent; 466 | color: #aaa; 467 | } 468 | 469 | table.highlighttable { 470 | margin-left: 0.5em; 471 | } 472 | 473 | table.highlighttable td { 474 | padding: 0 0.5em 0 0.5em; 475 | } 476 | 477 | tt.descname { 478 | background-color: transparent; 479 | font-weight: bold; 480 | font-size: 1.2em; 481 | } 482 | 483 | tt.descclassname { 484 | background-color: transparent; 485 | } 486 | 487 | tt.xref, a tt { 488 | background-color: transparent; 489 | font-weight: bold; 490 | } 491 | 492 | h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { 493 | background-color: transparent; 494 | } 495 | 496 | .viewcode-link { 497 | float: right; 498 | } 499 | 500 | .viewcode-back { 501 | float: right; 502 | font-family: sans-serif; 503 | } 504 | 505 | div.viewcode-block:target { 506 | margin: -1px -10px; 507 | padding: 0 10px; 508 | } 509 | 510 | /* -- math display ---------------------------------------------------------- */ 511 | 512 | img.math { 513 | vertical-align: middle; 514 | } 515 | 516 | div.body div.math p { 517 | text-align: center; 518 | } 519 | 520 | span.eqno { 521 | float: right; 522 | } 523 | 524 | /* -- printout stylesheet --------------------------------------------------- */ 525 | 526 | @media print { 527 | div.document, 528 | div.documentwrapper, 529 | div.bodywrapper { 530 | margin: 0 !important; 531 | width: 100%; 532 | } 533 | 534 | div.sphinxsidebar, 535 | div.related, 536 | div.foot, 537 | #top-link { 538 | display: none; 539 | } 540 | } 541 | a em{ 542 | border-bottom:1px dotted #01a; 543 | } 544 | 545 | em{ 546 | font-style:normal; 547 | } 548 | 549 | div.body li{ 550 | padding:2px 0; 551 | } 552 | 553 | div.body{ 554 | background:#F8F8D8 555 | } 556 | -------------------------------------------------------------------------------- /_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/favicon.ico -------------------------------------------------------------------------------- /_static/figs/magic-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/figs/magic-2.png -------------------------------------------------------------------------------- /_static/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/login.png -------------------------------------------------------------------------------- /_static/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/main.png -------------------------------------------------------------------------------- /_static/notify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/notify.png -------------------------------------------------------------------------------- /_static/rst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/rst.png -------------------------------------------------------------------------------- /_static/sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/sync.png -------------------------------------------------------------------------------- /_static/translations.js: -------------------------------------------------------------------------------- 1 | Documentation.addTranslations({"locale": "zh_CN", "plural_expr": "0", "messages": {"Hide Search Matches": "\u9690\u85cf\u641c\u7d22\u7ed3\u679c", "Permalink to this definition": "\u6c38\u4e45\u94fe\u63a5\u81f3\u76ee\u6807", "Expand sidebar": "", "Permalink to this headline": "\u6c38\u4e45\u94fe\u63a5\u81f3\u6807\u9898", "Collapse sidebar": ""}}); 2 | 3 | $(function(){ 4 | $(".external").attr('target',"_blank") 5 | }) 6 | -------------------------------------------------------------------------------- /_static/unreadnotify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/_static/unreadnotify.png -------------------------------------------------------------------------------- /base/basics.rst: -------------------------------------------------------------------------------- 1 | .. _basics: 2 | 3 | 入门基础 4 | ===================================================================== 5 | 6 | .. 7 | 8 | Python is a language for consenting adults. —Alan Runyan 9 | 10 | 编程语言: Python(Golang请参考 go 章节) 11 | ---------------------------------------------------------------------- 12 | Python入门相对容易又可以干很多事(网站,运维,数据分析,爬虫,AI,量化投资等),是一门方便的工具语言。2016年TIOBE排名显示Python已经名列第四,成为脚本语言之首。 13 | 国外的Youtube,Instagram,Pinterest,Reddit,Dropbox,Disqus, 14 | Quora等知名应用一开始都是基于Python构建,国内的豆瓣,知乎,今日头条,果壳,饿了么,搜狐等也是Python应用的典型。这也给了国内Python开发者一阵强心剂,Python的生态环境可以支撑起重量级的 15 | 产品。这里不想挑起语言之争,php,nodejs,java,ruby等都有丰富的生态环境。不过目前来看,技术选型用Python在招聘、学习、培训、敏捷开发等方面还是一个比较折中的选择(主要在于人,而不是语言)。 16 | python,ruby之类的动态语言优势在于其生产力,你能在极短时间内就搭建出原型从而赢得产品竞争。 17 | 推荐一下几本个人认为比较好的Python书籍: 18 | 19 | * `《python-guide》 `_ requests作者写的guide,偏向工程方面 20 | 21 | * `《use python》 `_ use python 22 | 23 | * `《A Byte of Python》 `_ 一百多页的小书,可以快速熟悉Python语言。 24 | 25 | * `《Python核心编程》 `_ 比较全面的Python书籍,介绍了Python语言的方方面面。 26 | 27 | * `《Dive Into Python》 `_ 一本免费的开源书 28 | 29 | * `《Fluent Python》 `_ Python进阶的好书,没有之一,涉及了很多Python高级主题和实现特性。 30 | 31 | * `《Python3 Cookbook》 `_ Python进阶读物,汇集了很多技巧。 32 | 33 | * `《Dabeaz》 `_ Python 培训领域 number one,Pycon 常客,有很多高质量的教程 34 | 35 | * `《Python Cheatsheet》 `_ Python 语法快速参考 36 | 37 | 当然还有Python的官方文档作为参考,不过有些文档比较晦涩,还是推荐书籍入门。网上目前也可以搜到很多免费的电子书。 38 | 如果有时间可以看看国内廖雪峰写的Python教程,简单易懂,就是跳跃有点大(有些章节对新人来说不是很友好)。 39 | 40 | 41 | 算法与数据结构 42 | ---------------------------- 43 | 编写良好的代码需要了解常用的算法和数据结构,虽然你可能很少会自己实现,但是对于Python语言中一些常用数据结构如list, tuple, set, frozenset, dict和collections模块中的OrderedDict, defaultdict, deque, namedtuple, Counter等应该知道什么时候用。最主要的还是了解算法中递归,二分等常用思想,写出高效易用的代码。如果你想在线练习,可以做一些Acm基础题或者去leetcode等网站刷题。 44 | 推荐书籍: 45 | 46 | * `《Python 算法与数据结构中文教程》 `_ 笔者自己撸的一个教程,包含免费的讲义和代码以及付费视频。 47 | * `《网易云课堂-Python 算法与数据结构教程》 `_ Python 算法和数据结构视频教程 48 | * `《算法图解》 `_ 49 | * `《算法导论》 `_ 你可以挑选感兴趣的章节啃一啃,也可以去网易公开课看下视频教程。如果不是计算机专业的可以看下《计算机科学导论》这门公开课,正好也是以Python语言讲解的。 50 | * `《awesome-algorithm》 `_ 51 | 52 | 53 | 计算机网络 54 | ---------------------------- 55 | 对于应用开发者来说大部分时间可能不太会接触特别底层的问题,但是了解网络的运行原理还是必要的。网上有个面试题 `从输入URL 到页面加载完成的过程中都发生了什么事情? `_ 如果对其中大部分的概念都了解就算是入门了。网络相关书籍可以随便找一本看看。Http协议对于web开发者来说比较重要,需要深入了解。推荐书籍: 56 | 57 | * `《图解Http》 `_ 58 | 一本小白入门Http协议的好书,有大量图片示例。 59 | * `《Http权威指南》 `_ 60 | Http协议最权威的讲解,大部头著作,可以看看最基础的部分。 61 | * `《网络爬虫教程》 `_ 62 | 非常不错的爬虫教程。感谢原作者,其实感觉这种把学习的内容总结成小书的方式很好。 63 | * `《Python3 网络爬虫实战》 `_ 64 | * `《使用 Flask-RESTful 设计 RESTful API》 `_ 65 | * `《restful-api-guidelines》 `_ 66 | 67 | 网络/并发编程(进阶) 68 | ----------------------------------- 69 | 如果想要深入了解一些网络框架的底层原理就需要学习底层socket网络编程了,比如《unix 网络编程》,《Linux 高性能服务器编程》等。 70 | 推荐一些入门资料: 71 | 72 | * `《Beej's Guide to Network Programming》 `_ 73 | * `《Python并行编程》 `_ 74 | 75 | 76 | Linux系统 77 | ---------- 78 | 互联网时代软件慢慢从传统的桌面软件到移动端、浏览器演化(这其实也是脚本语言能在 IO 密集的 web 领域使用广泛的原因),虽然 Linux 在桌面领域上完败,但是其开源和低成本的特性在网站服务器端、学术和工程领域使用广泛。 79 | 大部分Python应用都是跑在Linux服务器上的,大部分开源服务器软件使用的也是linux系统,即使日常工作不使用linux,一些基本的linux命令也要了解。 80 | 比如常用的文件操作,目录操作,进程操作等。你可以使用类unix系统mac或者linux版本ubuntu作为学习环境。 81 | 推荐: 82 | 83 | * `《Linux工具快速教程》 `_ 84 | * `《CONQUERING THE COMMAND LINE》 `_ 掌握这上面的命令基本就可以满足日常需求了。 85 | * `《13 Linux Network Configuration and Troubleshooting Commands》 `_ 86 | * `《鸟哥的Linux私房菜.基础学习篇》 `_ 浅显易懂,入门Linux命令的好书。 87 | 88 | 89 | 数据库 90 | ---------- 91 | 了解常用的关系型和非关系型(NoSQL, Not Only SQL)的数据库的使用。 92 | 现在网站业务后端用得比较多的数据库: 93 | 94 | - 传统关系型数据库(mysql等),使用广泛,不够灵活 95 | - 内存型(k-v型)数据库(redis等) 96 | - 文档型数据库(mongodb等) 97 | - 列式存储(HBase),大数据场景下的 IO 问题 98 | - 全文搜索型(Elasticsearch) 99 | 100 | 每种数据库各有优势和其使用场景,后端程序员需要了解下不同类型数据库的使用方法和应用场景,灵活应用到后端存储设计之中。 101 | 关于各种数据库网上已经有不少资料,读者可以自行搜索学习,mysql 和 redis(包括使用、设计、原理、优化)是重中之重。可以看看《Mysql 必知必会》和《Redis 实战》 102 | 对大数据感兴趣的可以学习下 Hadoop 生态系统。 103 | 104 | 105 | * `《Designing Data-Intensive Applications》 `_ 106 | 了解各种数据存储模型,本书覆盖面很广,适合有一定基础的人阅读 107 | 108 | 109 | python 相关库的使用 110 | ------------------- 111 | python一大优势在于数量丰富的库,灵活使用各种python库能帮助我们快速做出产品。作为web开发者,你需要了解常用python库和框架的使用,比如django/flask/tornado/sqlalchemy/requests/pandas等。 112 | 113 | web 框架 114 | ------------------- 115 | 大部分后端业务逻辑开发中都会使用 web 框架,提升开发效率。常用的 python web 框架有 Django、Flask、Tornado 等,一般 web 116 | 框架都包含 http 处理、表单验证、模板引擎、ORM 、Restful 接口等,最好能熟练掌握一个 web 框架,帮助快速做出产品。 117 | 118 | 版本控制 119 | ---------- 120 | 目前最流行的应该就是git了,很多公司使用 gitlab 管理代码仓库。版本控制工具是多人协作必不可少的工具,入门的程序员需要掌握基本的git命令,可以把github作为个人练习的工具。 121 | 遵守你们公司的 git 工作流程,git 是一个很强大但是很容易被初学者错误使用的工具。 122 | 123 | * `《语义化版本控制》 `_ 124 | * `《Pro Git》 `_ 125 | 126 | Web 服务器 127 | ---------- 128 | Nginx 目前很流行,使用比较广泛,推荐学习和使用。熟悉基础的 LNMP 架构(Linux + Nginx + Mysql + Python),目前很多公司采用了都是多语言+微服务架构(基于 docker)。 129 | 你可能需要了解常见的 web 应用部署方式以及如何使用 nginx 等负载均衡 130 | 131 | 132 | 微服务架构 133 | ------------------- 134 | 目前很多流行的网站采用了微服务架构,每个团队负责维护自己的服务(逃离单体地狱)。以下是学习微服务的一些比较好的书籍。 135 | 136 | * `《微服务设计》 `_ 入门微服务概念的一本书 137 | * `《微服务架构设计模式》 `_ 评价颇高的一本微服务实践书籍(java语言) 138 | 139 | 140 | 前端知识 141 | ---------- 142 | 基本的 html,css,javascript 需要有所了解。很多后端工程师需要做一些工具或者管理后台之类的,了解前端知识会有帮助。如果有兴趣深入前端可以了解下 Vue/React/Angular 等流行的框架。 143 | 144 | 学习和搜索能力 145 | -------------- 146 | 初学者碰到的大部分技术问题都是可以通过 google 解决的,用好 google/stackoverflow/github 和各种技术论坛、牛人博客等能帮助你了解最新的技术。 147 | 148 | * `《Instagram Engineering》 `_ Instagram 技术博客,有不少 python 相关的技术文章 149 | 150 | 151 | 业务领域知识 152 | ------------ 153 | 不同公司业务不同,经营(挣钱)领域不同(游戏、广告、媒体、社交、金融等),可能需要了解相关领域知识,方便业务建模。建议找工作之前研调下相应公司、经营领域、使用的技术栈等,不要太盲目,找到自己感兴趣的方向(后端知识很广),有时候方向和平台很重要,直接决定了你的工资和发展。 154 | 比如基金公司可能需要了解投资相关知识,社交公司可能要懂一些 feed 设计知识,媒体公司可能需要懂多媒体相关知识。(当然重点还是用 python 实现业务逻辑) 155 | 156 | 专业素养 157 | ---------- 158 | 公司做项目不是自己过家家,需要你具备写文档,注释,单元测试,沟通表达、与人协作、处理业务的能力。如果你现在还不了解一个正规python项目都有哪些组建构成,请去github克隆一份知名的代码仓库,花点时间仔细分析下它的项目结构和源代码。 159 | 比如著名网站reddit代码已经开源,大部分python实现,可以参考下。另外很多著名的python库,比如requests/flask等也可以作为参考。从笔者短暂的从业经历来看,大部分自学python的人不怎么遵守代码规范(pep8), 160 | 不知道或者不重视单元测试(写个函数print下就觉得OK了),不知道怎么写注释和文档(docstring听过吗?)。所以希望学习python的你能遵守工程实践,具备良好的职业素养和编码习惯,推荐阅读《代码大全》《编程匠艺》之类的工程相关的书。 161 | 162 | * `《程序员的职业素养》 `_ 163 | 164 | 165 | 软件工程 166 | ------------ 167 | 如果有时间我建议了解下软件工程相关的东西,在你没工作之前看书本的东西不会有太多体会,但是工作以后就会感受到做项目远远不是只有写代码这么简单。包括整个开发流程、进度管理、质量管理等还是有很多学问的。 168 | 这里推荐一本邹欣(现任微软Windows中国工程团队首席研发总监)的书,读起来比较接地气。 169 | 170 | * `《构建之法-现代软件工程》 `_ 171 | 172 | 173 | 后端技术栈 174 | ---------- 175 | web 后端工程师的主要工作职责是实现网站、app 业务后端逻辑(产品业务逻辑),涉及到的技术相关知识点基本就是上边列举的这些。 176 | 对于技能需求可以在拉勾上搜一下Python的职位,看看各个公司对Python的要求。或者你可以写个拉勾网的爬虫,对数据做一个简单的统计,笔者当初找工作就是这么干的。找工作之前最好研究下期望公司的业务和使用的技术栈,有针对性学习。 177 | 另外,真正做项目还需要你熟悉python的各种库和框架,比如django/flask/tornado/requests/sqlalchemy/unittest/celery等等,掌握了合适的工具才能快速上手做东西,公司恨不得你第一天入职第二天就能写项目。 178 | 所以,在你入了门以后请尽快熟悉python web的技术栈。公司不管你会什么算法,只在乎你的生产力(有时候技术本身不重要,它的价值在于对业务、用户、顾客的贡献)。 179 | 推荐一些文章供参考: 180 | 181 | 182 | * `《Python Web 学习路线图[视频]》 `_ 183 | * `《全栈增长工程师指南》 `_ 184 | * `《web开发路线图》 `_ 185 | * `《后端都要学习什么?》 `_ 186 | * `《PYTHON招聘需求与技能体系》 `_ 187 | * `《PYTHON后端相关技术/工具栈》 `_ 188 | 189 | 190 | 学习路线 191 | ---------- 192 | 看了这么多是不是还有点懵,笔者当时自学的时候也没人带,没什么方向,走了很多弯路,找工作也不是一帆风顺。如果不是科班出身受过系统的计算机科学理论的训练,是比较吃亏的,只能通过大量针对性学习和练习来弥补。 193 | 大概整理下自己学习 python web 的路线,方便大家做个参考(一个合格的工程师不是短时间能练成的)。其实这基本上也是后端工程师的学习路线,换一个编程语言或者框架都差不多。技术更新迭代非常快,后端技术还算比较稳定的,但是知识点很多很杂,有针对性学习比较好。 194 | 如果你觉得这个教程列出的东西太多,建议就找最重要的知识点,每个知识点挑一本最合适的书学习,我列举的很多资料对于初学者来说可能短时间内难以消化,会有畏难心理和学习焦虑,建议多加练习通过正反馈提升自己学习的乐趣。(如果你还是个学生那很好,有大把的时间准备) 195 | 196 | - 学习并熟练掌握一门编程语言(学好英语)。这里笔者选择的是最近特别火的 python,它能干很多事。挑一本好的入门教材,通读并实践书中所有代码示例和练习题(练手感,坚持敲,大量敲)。了解该语言如何操作文本、进程、网络编程等,最后达到能熟练运用编程语言表达逻辑的能力。 197 | - 搭建好开发环境。初学者个人比较推荐 Ubuntu 系统 + Pycharm 社区版,都是可以免费获取的,我经常安利用 linux 或者 mac,和桌面端不同,企业大部分用的都是 linux server 部署 web 应用的(包括 docker 容器技术等都是基于 linux),熟悉 linux 命令行、文件、进程操作等会给你找工作和日常工作带来便利。 198 | - 熟悉算法和数据结构。对于编程语言内置的数据结构、算法等要数量掌握和使用,常用数据结构和算法了解其原理,会计算时间空间复杂度,会自己实现(常见算法面试笔试常考)。 199 | - 熟悉网络协议 TCP/IP,HTTP,了解互联网是怎么运作的。既然是做网站,需要对网络运行原理比较了解。 200 | - 学习 web 框架和 python 库。做东西我们需要大量现成的轮子帮助我们,看下 django、flask 等流行的 tutorial,然后做个简单的网站出来(比如博客网站,一般按照教程撸一遍就入门了,python web 框架的文档非常完善)。最好能深入一个框架了解原理,比如看看 flask 和 Werkzeug 源码。 201 | - 了解前端知识。如果能独立做一个博客出来,大概对 html、css、js 就有所了解了。虽然是做后端,但是基本的前端知识也是必不可少的。 202 | - 学习业务常用数据库 mysql 和 redis,业界用得比较多的数据库。了解关系型数据库 mysql 基础概念、语句、索引优化等,了解内存型数据库 redis 的常用数据结构,使用场景、结构设计等。 203 | - 学习 git 版本控制。公司项目协作的时候都是有版本控制的,方便我们协作、记录、回滚代码等。学习编码规范,培养良好的编程习惯。我建议一开始就遵守 pep8,用好 autopep8,pylint 等工具,写出格式规范的代码,不要走野路子。(学习下文档和规范很棒的 python 开源代码) 204 | - 在 linux server 部署你的 python web 服务。你需要学习 linux 常用命令,web 服务器 nginx 等。最好能独立部署一个网站出来。(笔者经常安利 linux 或者 mac,即使不用来作为开发环境,也要熟悉 linux 命令,能帮助你在服务器上快速修改和调试代码) 205 | - 对照招聘网站中意的公司的招聘需求查漏补缺。初期就是要多学多练多 google,不是做项目就是在刷题。可以做一些博客、论坛、管理后台等小网站练手。 206 | - 老实说相比 java 和 php,python 后端岗位是比较少的,如果你学完了还没找到工作然后来臭骂我一顿我会感觉委屈的。我个人倾向于 python 是因为真爱,并且学习python 性价比很高,可以做很多事。如果你觉得不好找工作或者只是把 python 当玩具玩(比如用 pandas 分析自己的投资收益,回测等),换个语言和技术栈后端路线图基本上还是这些,不会白学的。 207 | - 建议坚持写技术博客,学习笔记等,总结输出(比如所谓的费曼学习法就是强调你要把学到的讲给别人听才是真正理解了)。你可以使用 hexo 之类的静态博客,或者知乎专栏等现成的服务,或者 readthedoc、gitbook 之类的文档工具。好的技术博客是找工作的一大加分项,笔者工作以后依然坚持写博客记录日常所学,可以是读书笔记、学习心得、对某个技术的理解和实践、甚至是备忘录等。 208 | - 进阶建议:看《Fluent Python》 之类的进阶书籍;看优秀的源码,比如 python 一些内置库,flask 等优秀的框架源码(可以用 gitx 之类的工具从代码的最初提交开始看起),能学到很多惯用法和稍微底层一些的东西。尝试仿写,比如实现个简单的 web 小框架,大概就了解框架的运行原理了。 209 | 210 | * `《How to be a Programmer 中文版》 `_ 211 | * `《Roadmap to becoming a web developer in 2019》 `_ 212 | 213 | 214 | 求职与面试 215 | ------------ 216 | 之前求职的时候每次面试都会充分准备(自己挂过很多次),提前一个月左右开始回顾重点理论知识(看面试相关的书),刷常用算法,练习手写代码,看相对岗位的招聘需求等。最近面试就发现很多面试者无论是否是有经验都准备不足,忽略了基础知识。 217 | 如果没有知名公司或者项目相关背景,很多招聘要求比较高的公司都会比较看重理论基础和学习能力。公司最好能有一份针对初级、中级、高级岗位的题目,尽量覆盖面广泛、难度适中,防止因为面试官的个人喜好影响面试结果。 218 | 219 | - 我建议你闲着没事的时候可以多看看招聘信息,熟悉各个公司对当前技术栈的要求,看看自己和意向公司差距在哪,查漏补缺 220 | - 电子简历尽量用 pdf 格式,方便跨平台打开。doc 等格式在不同的电脑上打开会有排版问题,很多后端技术面试官可能使用的是 mac 或者 linux。 221 | - 提前复习回顾重点知识,防止卡在基础上。比如 mac 下著名的 brew 工具作者面试 google 就因为没写出来反转二叉树被拒,后来去了苹果😂.(这就只能看人品和运气和眼缘了,如果没见到二面面试官或者 hr,大概率是挂了)。(树、链表、哈希表、二分、快排、TCP/UDP、HTTP、数据库ACID、索引优化等常考点)。 222 | - 白板编程,练习在纸上手写代码。虽然很多求职者都很抵触手写代码,但是白板编程确实是一种比较好的区分方式。你的思考过程、编码习惯、编码规范等都能看出来。 223 | - 如果被问到工程里不会使用但是比较刁钻的算法题,建议你和面试官沟通的时候问问这个算法或者题目在开发中有哪些实际使用场景,看看对方怎么说😎。 224 | - 面试的时候准备充分,简历要与招聘方需求对等。笔者每次面试都会带上白纸、笔、简历、电脑等,即使面试没过,至少也让面试官感觉我是有诚意的,给对方留下好印象。 225 | - 加分项:github、个人技术博客、开源项目、技术论坛帐号等,让面试官有更多渠道了解你,有时候仅仅根据几十分钟的面试来评判面试者是有失偏颇的。(比如面试者临场发挥不好;面试官个人偏好;会的都不问,问的都不会等) 226 | - 面试之前可以适当刷题,这几年招聘难度越来越大, 很多互联网公司都会问一些 leetcode 上的题目,如果不准备很有可能当场写不出来 227 | 228 | * `《interview_python》 `_ python 面试题 229 | * `《程序员面试金典》 `_ 程序员面试,很多公司会比较重视基础知识 230 | * `《Python后端工程师必备技能》 `_ 231 | 232 | 233 | 系统/架构设计进阶 234 | ----------------------- 235 | 对于有经验的工程师来说,系统设计也是一项重要的能力(也是除了存储系统、程序设计、网络通讯、操作系统之外经常被面试考到的)。比如设计一个短网址服务、简单的 feed 流系统、推荐系统、发号器服务等。笔者也处于学习中,推荐个资料供参考: 236 | (其实中高级后端涉及的其他东西还挺多的,系统设计、大数据存储、消息队列、分布式、缓存、并发优化、软件工程等) 237 | 238 | * `《backend-architectures》 `_ 239 | * `《http://highscalability.com/》 `_ 240 | * `《https://github.com/PegasusWang/system-design-interview》 `_ 241 | * `《System Design》 `_ 常见系统设计题目 242 | * `《https://github.com/PegasusWang/system-design-primer》 `_ 关于系统设计和架构设计相关的资料 243 | * `《system-design-and-architecture》 `_ 系统和架构设计 244 | * `《高并发设计知乎回答》 `_ 245 | 246 | 247 | Web 开发常用 Python 库(Golang 常用库请参考 golang 章节) 248 | ------------------------------------------------------------------------------ 249 | 列举平常开发常用的一些库和框架(你可以很容易 google 到它们的用法),你不必一开始就掌握它们,但需要的时候了解它们的用法会大大提升你的开发效率, 250 | 在开发工具章节我还会列举到更多能够提升开发效率的工具。 251 | 252 | - web/restful 框架:Django/Flask/Tornado 253 | - 异步http web框架:FastApi/aiohttp/Sanic 254 | - ORM: sqlalchemy, Peewee 255 | - 表单验证:WTForms, marshmallow 256 | - 数据处理和分析:Numpy, Pandas, Matplotlib 257 | - 异步:celery, asyncio, tornado 258 | - 并发:gevent, threading, concurrent.futures 259 | - 部署:uwsgi, gunicorn(推荐) 260 | - html 处理: lxml, beautifulsoup 261 | - 爬虫:requests, Scrapy 262 | - 单元测试:unittest, nose, pytest(推荐) 263 | - 图片处理:pillow 264 | - python2/3 兼容:six, 2to3 265 | - 代码检测:autopep8, pylint, flake8, mypy(python3) 266 | - 调试:Ipython, Ipdb, pdbpp 267 | - 终端:rich(美化颜色输出) 268 | - 命令行:click 269 | - 打包:Nuitka 270 | - 任务队列:apscheduler, huey 271 | 272 | 裁员、劳动法与法律援助 273 | ------------------------------------------------------------------------------ 274 | 最近几年裁员事件逐渐增多,互联网 ToC 端增长见顶,很多收益不好的公司或者创业公司效益不行。 275 | 作为一个码农,要适当了解法律常识,学会维护自己的合法权益。 276 | 277 | - `如何看待网传网易裁员,让保安把身患绝症的 5 年老员工赶出公司一事? `_ 278 | - `网易裁员事件引发的思考:5点建议,越早懂,越能保护自己 `_ 279 | -------------------------------------------------------------------------------- /conf.py: -------------------------------------------------------------------------------- 1 | # Ensure we get the local copy of tornado instead of what's on the standard path 2 | import os 3 | import sys 4 | sys.path.insert(0, os.path.abspath("..")) 5 | 6 | import sphinx.environment 7 | from docutils.utils import get_source_line 8 | 9 | 10 | def _warn_node(self, msg, node, **kw): 11 | """ Monkey patch to ignore specific errors """ 12 | if not msg.startswith(('py:obj reference target not found', 'nonlocal image URI found')): 13 | self._warnfunc(msg, '%s:%s' % get_source_line(node)) 14 | 15 | sphinx.environment.BuildEnvironment.warn_node = _warn_node 16 | 17 | master_doc = "index" 18 | 19 | project = "python-golang-web-guide" 20 | copyright = "2025, 公众号|知乎|B站 PegasusWang" 21 | 22 | version = release = "0.1" 23 | 24 | extensions = [ 25 | "sphinx.ext.autodoc", 26 | "sphinx.ext.coverage", 27 | "sphinx.ext.extlinks", 28 | "sphinx.ext.intersphinx", 29 | "sphinx.ext.viewcode", 30 | ] 31 | 32 | primary_domain = 'py' 33 | default_role = 'py:obj' 34 | highlight_language = "none" 35 | 36 | autodoc_member_order = "bysource" 37 | autoclass_content = "both" 38 | 39 | pygments_style = 'sphinx' 40 | 41 | # Without this line sphinx includes a copy of object.__init__'s docstring 42 | # on any class that doesn't define __init__. 43 | # https://bitbucket.org/birkenfeld/sphinx/issue/1337/autoclass_content-both-uses-object__init__ 44 | autodoc_docstring_signature = False 45 | 46 | coverage_skip_undoc_in_source = True 47 | 48 | coverage_ignore_functions = [ 49 | # various modules 50 | "doctests", 51 | "main", 52 | 53 | # tornado.escape 54 | # parse_qs_bytes should probably be documented but it's complicated by 55 | # having different implementations between py2 and py3. 56 | "parse_qs_bytes", 57 | 58 | # tornado.gen 59 | "multi_future", 60 | ] 61 | 62 | html_favicon = 'favicon.ico' 63 | 64 | # nore README.rst 65 | exclude_patterns = ['README.rst'] 66 | 67 | latex_documents = [ 68 | ('documentation', 'manual', False), 69 | ] 70 | 71 | # HACK: sphinx has limited support for substitutions with the |version| 72 | # variable, but there doesn't appear to be any way to use this in a link 73 | # target. 74 | # http://stackoverflow.com/questions/1227037/substitutions-inside-links-in-rest-sphinx 75 | # The extlink extension can be used to do link substitutions, but it requires a 76 | # portion of the url to be literally contained in the document. Therefore, 77 | # this link must be referenced as :current_tarball:`z` 78 | 79 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 80 | 81 | # On RTD we can't import sphinx_rtd_theme, but it will be applied by 82 | # default anyway. This block will use the same theme when building locally 83 | # as on RTD. 84 | if not on_rtd: 85 | import sphinx_rtd_theme 86 | html_theme = 'sphinx_rtd_theme' 87 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 88 | 89 | # Disable epub mimetype warnings https://github.com/sphinx-doc/sphinx/issues/10350 90 | suppress_warnings = ["epub.unknown_project_files"] 91 | 92 | -------------------------------------------------------------------------------- /database/index.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | 数据库 3 | =================================== 4 | 5 | 6 | .. toctree:: 7 | :glob: 8 | :maxdepth: 2 9 | 10 | * 11 | 12 | -------------------------------------------------------------------------------- /database/mongo.rst: -------------------------------------------------------------------------------- 1 | .. _mongo: 2 | 3 | ============= 4 | MongoDB 5 | ============= 6 | 7 | :Author: lzy kzinglzy@gmail.com, tonghs tonghuashuai@gmail.com 8 | 9 | 简介 10 | ===================================================================== 11 | MongoDB是文档型的非关系型数据库,其优势在于查询功能强大,能存储海量数据. 是懒人借以代替SQL型数据库的产品. 12 | 13 | 在API选择上, 我们用的是基于PyMongo的MongoKit, 并在此基础上进行了小的封装. 14 | 所以如果你遇到了问题, 可以去查阅PyMongo或MongoKit的官方文档 15 | 16 | 基本用法 17 | ===================================================================== 18 | 19 | - find 20 | 21 | - limit 22 | - skip 23 | - sort 24 | 25 | - delete 26 | - remove(删除条件) 27 | - save 28 | - 填充默认值 29 | - upsert 30 | 31 | 32 | - 时间用int保存 33 | - mongo默认值需要是一个生成函数 34 | - pyhton常见的默认值陷阱,以create\_time=time()为例 35 | 36 | 37 | 创建文档 38 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 39 | 在mongo里,用"文档"的概念代替SQL里的"表". 例如, 下面定义了一个UserIm文档:: 40 | 41 | class UserIM(Doc): 42 | 43 | structure = dict( 44 | user_id=int, 45 | phone=str, 46 | qq=str, 47 | weixin=str, 48 | ) 49 | 50 | indexes = [{ 51 | 'fields': 'user_id', 52 | 'unique': True 53 | }] 54 | default_values = { 55 | 'user_id': 0 56 | } 57 | 58 | 其中, UserIm继承自类Doc 59 | 60 | structure定义了文档的字段, 接受一个Python字典对象; 61 | 62 | indexes定义了索引, 接受一个列表 63 | 64 | default_values定义了初始化时的默认值. 65 | 66 | 除此之外, 你还可以添加更多的信息, 这些可以在 `MongoKit document `_ 里找到. 67 | 68 | 初始化 69 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 70 | 初始化一个文档对象可以这么写:: 71 | 72 | ui = UserIM() 73 | 74 | 此时, 所有的文档属性值都会被设为 None , 因为我们并没有给他传递任何值. 75 | 76 | 这可以通过传递一个字典对象或者JsOB对象来初始化属性值, 如:: 77 | 78 | ui = UserIM(dict(user_id=123, phone=456)) # 字典 79 | 80 | ui = UserIM(JsOb(user_id=123, phone=456)) # JsOb 对象 81 | 82 | 但此时, 其它的没有被初始化的值还是会被设为 None, 若要使我们设置的default_values生效, 可以通过将第二个参数设为True来实现, 如:: 83 | 84 | ui = UserIM(dict(user_id=123, phone=456), True) 85 | 86 | 87 | 更新 88 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 89 | 下面我们想更新数据, 如:: 90 | 91 | ui = UserIm() 92 | ui.user_id = 123 93 | ui.mail = 'abc@gmail.com' 94 | ui.save() 95 | 96 | 这将会添加一个user_id为123, mail为abc@gmail.com 的记录. 97 | 98 | 其中未被赋值的属性会被设为None, 不存在的属性会被忽略. 如果要添加的记录已存在, 那么旧的记录会被替换掉, 否则,会创建一个新的记录. 99 | 100 | 除此之外, 还有一种更优雅的方式可以实现数据的更新:: 101 | 102 | ui = UserIM({'mail': 'abc@gmail.com'}) 103 | user_im.upsert({'user_id': '123'}) 104 | 105 | 这里使用了upsert这个函数, 它的效果和 save 是一样的.但用起来更优雅更简单. 106 | 107 | 108 | 查询 109 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 110 | 常用的查询要用到两个函数:: 111 | 112 | UserIM.find(dict(phone=456)) 113 | 114 | 这会返回所有的phone值为456的Python列表. :: 115 | 116 | UserIM.find_one(dict(user_id=123)) 117 | 118 | 这会返回一个用户记录, 其 id 为123. 119 | 120 | 当然, 还可以添加更多的查询条件来实现复杂的查询, 如:: 121 | 122 | UserIM.find( 123 | {'$or': [{'phone' :456}, {'mail': abc@gmail.com}]}, 124 | limit=10, 125 | skip=0 126 | ) 127 | 128 | 如上会返回最多包含10条的, phone 为456或者 mail 为 abc@gmail.com 的记录列表 129 | 130 | 备份和恢复 131 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 132 | Mongodb自带了mongodump和mongorestore这两个工具来实现对数据的备份和恢复。 133 | mongodump能够在Mongodb运行时进行备份,它的工作原理是对运行的Mongodb做查询,然后将所有查到的文档写入磁盘。但是存在的问题时使用mongodump产生的备份不一定是数据库的实时快照,如果我们在备份时对数据库进行了写入操作,则备份出来的文件可能不完全和Mongodb实时数据相等。另外在备份时可能会对其它客户端性能产生不利的影响。 134 | 135 | 备份:: 136 | 137 | mongodump -d SITE -o ~/download/mongobak/SITE/ 138 | 139 | 恢复:: 140 | 141 | mongorestore -d SITE --directoryperdb ~/download/mongobak/SITE/ --drop 142 | 143 | 注意: --drop 参数代表恢复前删除原数据 144 | 145 | 官方文档: http://docs.mongodb.org/manual/core/import-export/ 146 | 147 | 源码 148 | ===================================================================== 149 | "源码面前, 了无秘密" -- 侯捷 150 | 151 | 当你愤怒的发现上面的某些用法不是标准的MongoKit用法时, 就是时候看看源码了:: 152 | 153 | /home/zz/42web/z42/web/mongo.py 154 | 155 | 156 | 阅读资料 157 | ===================================================================== 158 | 159 | `MongoKit document `_ 160 | 161 | `PyMongo document `_ 162 | 163 | `MongoDB document `_ 164 | 165 | `MongoDB 资料汇总 `_ 166 | -------------------------------------------------------------------------------- /database/mysql.rst: -------------------------------------------------------------------------------- 1 | .. _mysql: 2 | 3 | ============= 4 | Mysql 5 | ============= 6 | 7 | 8 | 数据库操作 9 | ===================================================================== 10 | 11 | 批量 kill 掉查询 12 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 13 | 14 | 有时候需要批量 kill 掉查询进程,有几种方式,比如生成 sql 文件执行: 15 | 16 | .. code-block:: sql 17 | 18 | mysql> select concat('KILL ',id,';') from information_schema.processlist 19 | where user='root' and time > 200 into outfile '/tmp/a.txt'; 20 | 21 | mysql> source /tmp/a.txt; 22 | 23 | 24 | 有时候没法生成文件(权限原因),可以直接生成 sql 语句 copy 下来复制到命令行也可以,或者连接成一行方便复制: 25 | 26 | .. code-block:: bash 27 | 28 | mysql > select concat('KILL ',id,';') from information_schema.processlist where db='dbname';` 29 | 30 | mysql > select GROUP_CONCAT(stat SEPARATOR ' ') from (select concat('KILL ',id,';') as stat from information_schema.processlist where db='dbname') as stats; 31 | 32 | # 按客户端 IP 分组,看哪个客户端的链接数最多 33 | select client_ip,count(client_ip) as client_num from (select substring_index(host,':' ,1) as client_ip from processlist ) as connect_info group by client_ip order by client_num desc; 34 | 35 | # 查看正在执行的线程,并按 Time 倒排序,看看有没有执行时间特别长的线程 36 | select * from information_schema.processlist where Command != 'Sleep' order by Time desc; 37 | 38 | # 找出所有执行时间超过 5 分钟的线程,拼凑出 kill 语句,方便后面查杀 39 | select concat('kill ', id, ';') from information_schema.processlist where Command != 'Sleep' and Time > 300 order by Time desc; 40 | 41 | 42 | 也可以通过 python 脚本来完成,原理也是查询进程 id 然后删除: 43 | 44 | .. code-block:: py 45 | 46 | # https://stackoverflow.com/questions/1903838/how-do-i-kill-all-the-processes-in-mysql-show-processlist 47 | 48 | import pymysql # pip install pymysql 49 | 50 | connection = pymysql.connect(host='', 51 | user='', 52 | db='', 53 | password='', 54 | cursorclass=pymysql.cursors.DictCursor) 55 | 56 | with connection.cursor() as cursor: 57 | cursor.execute('SHOW PROCESSLIST') 58 | for item in cursor.fetchall(): 59 | if item.get('db') == 'dbname': # 过滤条件 60 | _id = item.get('Id') 61 | print('kill %s' % item) 62 | cursor.execute('kill %s', _id) 63 | connection.close() 64 | 65 | 删除大表(借助一个临时表) 66 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 67 | 68 | .. code-block:: bash 69 | 70 | # https://stackoverflow.com/questions/879327/quickest-way-to-delete-enormous-mysql-table 71 | CREATE TABLE new_foo LIKE foo; 72 | RENAME TABLE foo TO old_foo, new_foo TO foo; 73 | DROP TABLE old_foo; 74 | # 发现好像直接用 truncate table tablename; 清理千万级别表也挺快的 75 | 76 | 统计表的大小并排序 77 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 78 | 79 | .. code-block:: sql 80 | 81 | # https://stackoverflow.com/questions/9620198/how-to-get-the-sizes-of-the-tables-of-a-mysql-database 82 | SELECT 83 | table_schema as `Database`, 84 | table_name AS `Table`, 85 | round(((data_length + index_length) / 1024 / 1024), 2) `Size in MB` 86 | FROM information_schema.TABLES 87 | ORDER BY (data_length + index_length) DESC; 88 | 89 | 统计数据库大小 90 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 91 | 92 | .. code-block:: bash 93 | 94 | # https://stackoverflow.com/questions/1733507/how-to-get-size-of-mysql-database 95 | SELECT table_schema "DB Name", 96 | ROUND(SUM(data_length + index_length) / 1024 / 1024, 1) "DB Size in MB" 97 | FROM information_schema.tables 98 | GROUP BY table_schema; 99 | 100 | # 输出前十个大表 101 | select table_schema as database_name, 102 | table_name, 103 | round( (data_length + index_length) / 1024 / 1024, 2) as total_size, 104 | round( (data_length) / 1024 / 1024, 2) as data_size, 105 | round( (index_length) / 1024 / 1024, 2) as index_size 106 | from information_schema.tables 107 | where table_schema not in ('information_schema', 'mysql', 108 | 'performance_schema' ,'sys') 109 | and table_type = 'BASE TABLE' 110 | -- and table_schema = 'your database name' 111 | order by total_size desc 112 | limit 10; 113 | 114 | 115 | 查看表信息 116 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 117 | 118 | .. code-block:: sql 119 | 120 | mysql > show table status; 121 | mysql > show table status where Rows>100000; 122 | 123 | 纵向显示 124 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 125 | 有时候表字段比较多的时候,查询结果显示会很乱,可以使用竖屏显示的方式,结尾加上 ``\G`` 126 | 127 | .. code-block:: bash 128 | 129 | mysql > select * from user limit 10 \G 130 | 131 | 导出和导入表的数据 132 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 133 | 134 | .. code-block:: sql 135 | 136 | shell > mysqldump -u user -h host -p pass db_name table_name > out.sql 137 | mysql > source /path/to/out.sql 138 | 139 | 重命名数据库 140 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 141 | 142 | .. code-block:: sql 143 | 144 | # https://stackoverflow.com/questions/67093/how-do-i-quickly-rename-a-mysql-database-change-schema-name 145 | mysqldump -u username -p -v olddatabase > olddbdump.sql 146 | mysqladmin -u username -p create newdatabase 147 | mysql -u username -p newdatabase < olddbdump.sql 148 | 149 | 150 | Python Mysql 操作 151 | ===================================================================== 152 | 153 | Sqlalchemy 示例 154 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 155 | 156 | .. code-block:: py 157 | 158 | # -*- coding: utf-8 -*- 159 | 160 | """ 161 | sqlalchemy 快速读取 mysql 数据示例 162 | 163 | # pip install SQLAlchemy -i https://pypi.doubanio.com/simple --user 164 | pip install SQLAlchemy==1.3.20 -i https://pypi.doubanio.com/simple --user 165 | pip install pymysql -i https://pypi.doubanio.com/simple --user 166 | """ 167 | 168 | import sqlalchemy as db 169 | from sqlalchemy import text 170 | 171 | """ 172 | # 本机 mysql 创建一个测试表 173 | 174 | CREATE TABLE `area_code` ( 175 | `id` int(11) NOT NULL AUTO_INCREMENT, 176 | `code` bigint(12) NOT NULL DEFAULT '0' COMMENT '行政区划代码', 177 | `name` varchar(32) NOT NULL DEFAULT '' COMMENT '名称', 178 | PRIMARY KEY (`id`), 179 | KEY `idx_code` (`code`) 180 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 181 | 182 | """ 183 | 184 | def sqlalchemy_demo(): 185 | # https://towardsdatascience.com/sqlalchemy-python-tutorial-79a577141a91 186 | url = "mysql+pymysql://root:wnnwnn@127.0.0.1:3306/testdb" # 本地用的测试地址 187 | engine = db.create_engine(url) 188 | connection = engine.connect() 189 | metadata = db.MetaData() 190 | table = db.Table('area_code', metadata, autoload=True, autoload_with=engine) 191 | 192 | # 插入单个数据 193 | query = db.insert(table).values(code=10010, name="北京") 194 | connection.execute(query) 195 | 196 | # 插入多个数据 197 | query = db.insert(table) 198 | values = [ 199 | {'code': 10020, 'name': '上海'}, 200 | {'code': 10030, 'name': '杭州'}, 201 | ] 202 | connection.execute(query, values) 203 | 204 | # 查询 205 | query = db.select([table]).order_by(db.desc(table.columns.id)).limit(10) 206 | rows = connection.execute(query).fetchall() 207 | for row in rows: 208 | print(row.id, row.code, row.name) 209 | 210 | # 修改 211 | query = db.update(table).values(name="帝都").where(table.columns.code == 10010) 212 | connection.execute(query) 213 | 214 | # 删除行 215 | query = db.delete(table).where(table.columns.code == 10010) 216 | connection.execute(query) 217 | 218 | def sqlalchemy_text_demo(): 219 | """直接执行 sql 语句 """ 220 | url = "mysql+pymysql://root:wnnwnn@127.0.0.1:3306/testdb" # 本地用的测试地址 221 | engine = db.create_engine(url) 222 | connection = engine.connect() 223 | 224 | sql = text("show tables;") 225 | res = connection.execute(sql) 226 | for i in res: 227 | print(i) 228 | 229 | if __name__ == "__main__": 230 | sqlalchemy_demo() 231 | 232 | 233 | Go Mysql 操作 234 | ===================================================================== 235 | 236 | go 可以使用 gorm 或者 database/sql 237 | 238 | .. code-block:: go 239 | 240 | package main 241 | 242 | import ( 243 | "database/sql" 244 | "fmt" 245 | "log" 246 | "time" 247 | 248 | _ "github.com/go-sql-driver/mysql" 249 | ) 250 | 251 | func main() { 252 | db, err := sql.Open("mysql", "root:root@(127.0.0.1:3306)/root?parseTime=true") 253 | if err != nil { 254 | log.Fatal(err) 255 | } 256 | if err := db.Ping(); err != nil { 257 | log.Fatal(err) 258 | } 259 | 260 | { // Create a new table 261 | query := ` 262 | CREATE TABLE users ( 263 | id INT AUTO_INCREMENT, 264 | username TEXT NOT NULL, 265 | password TEXT NOT NULL, 266 | created_at DATETIME, 267 | PRIMARY KEY (id) 268 | );` 269 | 270 | if _, err := db.Exec(query); err != nil { 271 | log.Fatal(err) 272 | } 273 | } 274 | 275 | { // Insert a new user 276 | username := "johndoe" 277 | password := "secret" 278 | createdAt := time.Now() 279 | 280 | result, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)`, username, password, createdAt) 281 | if err != nil { 282 | log.Fatal(err) 283 | } 284 | 285 | id, err := result.LastInsertId() 286 | fmt.Println(id) 287 | } 288 | 289 | { // Query a single user 290 | var ( 291 | id int 292 | username string 293 | password string 294 | createdAt time.Time 295 | ) 296 | 297 | query := "SELECT id, username, password, created_at FROM users WHERE id = ?" 298 | if err := db.QueryRow(query, 1).Scan(&id, &username, &password, &createdAt); err != nil { 299 | log.Fatal(err) 300 | } 301 | 302 | fmt.Println(id, username, password, createdAt) 303 | } 304 | 305 | { // Query all users 306 | type user struct { 307 | id int 308 | username string 309 | password string 310 | createdAt time.Time 311 | } 312 | 313 | rows, err := db.Query(`SELECT id, username, password, created_at FROM users`) 314 | if err != nil { 315 | log.Fatal(err) 316 | } 317 | defer rows.Close() 318 | 319 | var users []user 320 | for rows.Next() { 321 | var u user 322 | 323 | err := rows.Scan(&u.id, &u.username, &u.password, &u.createdAt) 324 | if err != nil { 325 | log.Fatal(err) 326 | } 327 | users = append(users, u) 328 | } 329 | if err := rows.Err(); err != nil { 330 | log.Fatal(err) 331 | } 332 | 333 | fmt.Printf("%#v", users) 334 | } 335 | 336 | { 337 | _, err := db.Exec(`DELETE FROM users WHERE id = ?`, 1) 338 | if err != nil { 339 | log.Fatal(err) 340 | } 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /database/redis.rst: -------------------------------------------------------------------------------- 1 | .. _redis: 2 | 3 | ============= 4 | Redis 5 | ============= 6 | 7 | 8 | Scan 命令代码示例 9 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 | 有一些需求需要我们扫描 redis 的所有键值,如果用 keys 会阻塞redis 非常危险,推荐用 scan 命令。如果需要扫描一个hash/zset 11 | 等也有对应的 hscan, zscan 等命令可以使用。 12 | 13 | - 返回的结果可能会有重复,需要客户端去重复,这点非常重要; 14 | - 遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的; 15 | - 单次返回的结果是空的并不意味着遍历结束,而要看返回的游标值是否为零; 16 | 17 | 参考:https://www.lixueduan.com/post/redis/redis-scan/ Redis Scan 原理解析与踩坑 18 | 19 | .. code-block:: python 20 | 21 | import redis # pip install redis 22 | 23 | 24 | def init_redis(): 25 | HOST, PORT, PWD = "host", 6379, "pwd" # 用你的 redis 配置替代,最好读取配置不要硬编码密码等保证安全 26 | return redis.Redis(host=HOST, port=PORT, password=PWD) 27 | 28 | 29 | def get_all_node_ids(r): 30 | """ 获取所有的腾讯云 redis 集群 master node_id。扫描的需要覆盖所有 master 节点。如果scan 一个复合结构不需要扫所有节点 31 | redis cluster 架构 32 | u'ip:port@12028': {u'connected': True, [149/176] 33 | u'epoch': u'17', 34 | u'flags': u'master', 35 | u'last_ping_sent': u'0', 36 | u'last_pong_rcvd': u'1580544851000', 37 | u'master_id': u'-', 38 | u'node_id': u'XXXXXXXXXXXXX', 39 | u'slots': [[u'8192', u'10239']]}, 40 | """ 41 | from collections import defaultdict 42 | node_dict = r.execute_command("cluster nodes") 43 | master_slaves = defaultdict(list) # {master_id : [slave_ids]} 44 | for _addr_id, info in node_dict.items(): 45 | if info.get("flags", "") == "slave": 46 | master_id = info["master_id"] 47 | master_slaves[master_id].append(info["node_id"]) 48 | master_ids = list(master_slaves.keys()) 49 | slave_ids = [] # 获取其中一个 slave id 50 | for _, slave_ids in master_slaves.items(): 51 | slave_ids.append(slave_ids[0]) 52 | return master_ids, slave_ids 53 | 54 | 55 | def scan(r, node_id='', cursor=0, match=None, count=None): 56 | pieces = [cursor] 57 | if match is not None: 58 | pieces.extend([b'MATCH', match]) 59 | if count is not None: 60 | pieces.extend([b'COUNT', count]) 61 | if node_id: 62 | pieces.append(node_id) 63 | return r.execute_command('SCAN', *pieces) 64 | 65 | 66 | def scan_playcount(r): # r redis client 67 | _master_ids, slave_ids = get_all_node_ids(r) 68 | num = 0 69 | for node_id in slave_ids: # scan 每一个 redis slave 节点 70 | cursor = 0 # reset cursor if error 71 | 72 | while True: 73 | cursor, keys = scan(r, node_id, cursor, "UR_*", 10000) # 这里 match 设置你需要的前缀 74 | for key in set(keys): # 注意如果不是幂等的,这里可能重复,需要去重 75 | print(key) # 这里根据你的需求处理 key 76 | 77 | if cursor == 0: # 这里说明没有多余数据了,退出 78 | break 79 | print("all nums:{}".format(num)) 80 | 81 | 82 | def main(): 83 | redis_client = init_redis() 84 | scan_playcount(redis_client) 85 | 86 | 87 | if __name__ == '__main__': 88 | main() 89 | 90 | 91 | 也可以 scan 单个复合结构, golang 代码示例如下(不用获取所有 master 节点了,只有一个 key): 92 | 93 | .. code-block:: go 94 | 95 | // 遍历数据源 redis zset 获取点赞数 96 | func getRedisZsetNum(key string) ([]string, error) { 97 | rc := storage.LikeRedisClient // redigo/redis 的 client 98 | iter := 0 99 | var memberScores []string 100 | for { 101 | arr, err := redis.Values(rc.Do("ZSCAN", key, iter, "MATCH", "*")) 102 | if err != nil { 103 | return nil, fmt.Errorf("key:%s error retrieving '%s' keys", key, "*") 104 | } 105 | 106 | iter, err = redis.Int(arr[0], nil) 107 | k, err := redis.Strings(arr[1], nil) // k [m1 score1 m2 score2] 108 | 109 | logger.Debugf("redis scan key:%s k:%+v, err:%v", key, k, err) 110 | memberScores = append(memberScores, k...) 111 | 112 | if iter == 0 { 113 | break 114 | } 115 | } 116 | return memberScores, nil 117 | } 118 | -------------------------------------------------------------------------------- /devtools/hg.rst: -------------------------------------------------------------------------------- 1 | .. _hg: 2 | 3 | =========== 4 | hg 简明教程 5 | =========== 6 | 7 | 作者: 王然 kxxoling@gmail.com 8 | 9 | 10 | 关于 hg 11 | ---------------------- 12 | 13 | hg 即 Mercurial,是一个跨平台的分布式版本控制软件,主要由Python语言实现, 14 | 但也包含一个用C语言实现的二进制比较工具。其使用方式即原理与 git 相似, 15 | 如果你已经有 git 使用经验,可以访问 16 | `git 和 hg 面对面 `_ 17 | 18 | 19 | hg 基本流程 20 | ----------------------- 21 | 22 | 以 42web https://bitbucket.org/z42/z42 为例,演示 hg 开发流程。 23 | 24 | 1. 从远程仓库复制到本地 25 | https 协议从远程仓库获取代码:: 26 | 27 | hg clone https://bitbucket.org/z42/z42 28 | 29 | 或者也可以通过 ssh 协议:: 30 | 31 | hg clone ssh://hg@bitbucket.org/z42/z42 32 | 33 | #. 添加新创建的文件:: 34 | 35 | hg add file_name 36 | 37 | #. 修改本地代码并提交 38 | 39 | 保存修改后,提交本地代码::: 40 | 41 | hg commit -m "change log" 42 | 43 | 或者也可以简写为::: 44 | 45 | hg ci -m "change log" 46 | 47 | -m 表示关于本次提交的相关信息 48 | 49 | commit 时 hg 会自动 add 代码库中已修改的文件。 50 | 51 | 52 | #. 提交到远程仓库前,首先检查远程代码状态 53 | 54 | 可以使用 hg diff branches 查看不同分支间差异 55 | 56 | 使用 hg fetch 命令从其它分支拉取代码并合并,如合并代码出现问题, 57 | 需要手动合并后再提交。 58 | 59 | 60 | #. 将代码提交到远程仓库:: 61 | 62 | hg push 63 | 64 | #. 发起 pull request 65 | 66 | 在 https://bitbucket.org/你的用户名/项目名/pull-request/new 发起一个新的 pull request 67 | 68 | 69 | 解决冲突 70 | --------------------- 71 | 72 | 在 fetch 他人代码的时候,时常会遇到合并冲突的问题,因为有可能两人同时修改了同一个文件,这时需要先解决冲突(直接修改有冲突的文件),解决冲突后将其标注为已解决:: 73 | 74 | hg resolve -m file.name 75 | 76 | 77 | Tips 78 | ---------------------- 79 | 80 | * 向版本库添加/删除文件 ``hg add/rm `` 81 | 82 | * 移动版本库中的文件 ``hg mv`` 83 | 84 | * 查找某段代码的责任人 ``hg blame`` 85 | 86 | * 从现有代码初始化版本仓库 ``hg init`` 87 | 88 | * 建立 hg 服务器 ``hg serve`` 89 | 90 | * 更新代码库至最新提交 ``hg update`` 91 | 92 | * 切换至分支 ``hg update -r `` 93 | 94 | * 放弃所有修改,返回至上一个提交 ``hg update -C`` 95 | 96 | * 搜索 ``hg grep`` 97 | 98 | * 放弃某个文件的修改 ``hg revert`` 99 | 100 | 101 | 分支命名规则 102 | ------------ 103 | 104 | 命名示例: \* bug/index\_page \* feature/founder\_page 105 | 106 | 分支的目的如果是 bug 修复以 ``bug/`` 作为开头;同理,新需求则以 107 | ``feature/`` 开头。这样能够明显地区分需求与 bug,并且以 ``/`` 108 | 为分隔符能够得到 SourceTree 这样的图形化版本控制工具更好的支持。 109 | 110 | 分支名中不需要添加创建者,因为一个分支通常会有多个开发者(一个前端一个后端)同时使用。 111 | 112 | 113 | 内部 Hg 服务器 114 | -------------- 115 | 116 | 公司内部基于 `Kallithea `_ 开源系统搭建了一套 Hg web 117 | 服务,内网配置 DNS 后可访问 `hg.pe.vc `_ 注册账号并 fork 所需要的项目。 118 | 使用流程如下: 119 | 120 | 1. `注册 `_ 并联系管理员激活 121 | 122 | #. 登录并 `添加项目组 `_ 。 123 | 124 | #. 前往 ``http://hg.pe.vc/<项目名>/fork`` 页面 fork 相应的项目。 125 | 126 | 其余操作和 GitHub、BitBucket 并无显著区别。 127 | 128 | 配置 Hg 记住密码功能 129 | ~~~~~~~~~~~~~~~~~~~~ 130 | 131 | 通过 Hg HTTP 协议操作项目时,往往需要重复输入密码,不像 ssh 方式便捷,可以通过插件实现 Hg 记住密码功能: 132 | 133 | .. code-block:: shell 134 | 135 | pip install mercurial_keyring 136 | 137 | 在 ``~/.hgrc`` 文件后写入: 138 | 139 | .. code-block:: shell 140 | 141 | [extensions] 142 | mercurial_keyring = 143 | 144 | 145 | 146 | 扩展阅读 147 | ---------------------- 148 | 149 | `Hg-42 区漫游指南 `_ 150 | 151 | `git 和 hg 面对面 `_ 152 | 153 | `HgInit 中文版 `_ 154 | -------------------------------------------------------------------------------- /devtools/index.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | 开发工具 3 | =================================== 4 | 5 | 6 | .. toctree:: 7 | :glob: 8 | :maxdepth: 2 9 | 10 | * 11 | 12 | -------------------------------------------------------------------------------- /devtools/ipython.rst: -------------------------------------------------------------------------------- 1 | .. _ipython: 2 | 3 | ================================================== 4 | IPython 用法 5 | ================================================== 6 | 7 | :作者: 王然 kxxoling@gmail.com 8 | 9 | 启动 10 | ============ 11 | 12 | 启动IPython就是运行可执行文件ipython。你会看到一个提示符,如果你曾经玩过标准Python命令行提示符,你会发现这个有点儿不同:: 13 | 14 | [jjones@cerberus ~]$ /usr/local/python24/bin/ipython 15 | Python 2.4 (#2, Nov 30 2004, 09:22:54) 16 | Type "copyright", "credits" or "license" for more information. 17 | 18 | IPython 0.6.6 -- An enhanced Interactive Python. 19 | ? -> Introduction to IPython's features. 20 | %magic -> Information about IPython's 'magic' % functions. 21 | help -> Python's own help system. 22 | object? -> Details about 'object'. ?object also works, ?? prints more. 23 | 24 | In [1]: 25 | 26 | 要退出IPython(Linux系统上)就输入Ctrl-D(会要求你确认),也可以输入Exit或Quit(注意大小写)退出而不需要确认。 27 | 28 | 特性 29 | ============ 30 | 31 | Magic 32 | ------------ 33 | 34 | IPython有一些"magic"关键字:: 35 | 36 | %Exit, %Pprint, %Quit, %alias, %autocall, %autoindent, %automagic, 37 | %bookmark, %cd, %color_info, %colors, %config, %dhist, %dirs, %ed, 38 | %edit, %env, %hist, %logoff, %logon, %logstart, %logstate, %lsmagic, 39 | %macro, %magic, %p, %page, %pdb, %pdef, %pdoc, %pfile, %pinfo, %popd, 40 | %profile, %prun, %psource, %pushd, %pwd, %r, %rehash, %rehashx, %reset, 41 | %run, %runlog, %save, %sc, %sx, %system_verbose, %unalias, %who, 42 | %who_ls, %whos, %xmode 43 | 44 | IPython 会检查传给它的命令是否包含magic关键字。如果命令是一个magic关键字,IPython就自己来处理。如果不是magic关键字,就交给 Python(标准解释器)去处理。如果automagic打开(默认),你不需要在magic关键字前加%符号。相反,如果automagic是关闭的,则%是必须的。在命令提示符下输入命令magic就会显示所有magic关键字列表以及它们的简短的用法说明。良好的文档对于一个软件的任何一部分来说都是重要的,从在线IPython用户手册到内嵌文档(%magic),IPython当然不会在这方面有所缺失。 45 | 46 | Tab自动补全 47 | ------------ 48 | 49 | IPython一个非常强大的功能是tab自动补全。如果你对Python很了解,可能会想,标准Python交互式解释器也可以tab自动补全啊。你要做的只是:: 50 | 51 | [jjones@cerberus ~]$ /usr/local/python24/bin/python 52 | Python 2.4 (#2, Nov 30 2004, 09:22:54) 53 | [GCC 3.4.2 20041017 (Red Hat 3.4.2-6.fc3)] on linux2 54 | Type "help", "copyright", "credits" or "license" for more information. 55 | >>> import rlcompleter, readline 56 | >>> readline.parse_and_bind('tab: complete') 57 | >>> 58 | 59 | 是的,标准Python交互式解释器和IPython都支持“普通”自动补全和菜单补全。使用自动补全,你要先输入一个匹配模型,然后按Tab键。如果是“普通”自动补全模式(默认),Tab后会: 60 | 61 | * 匹配模型按最大匹配展开。 62 | * 列出所有匹配的结果。 63 | 64 | 例如:: 65 | 66 | In [1]: import os 67 | In [2]: os.po 68 | os.popen os.popen2 os.popen3 os.popen4 69 | In [2]: os.popen 70 | 71 | 输入os.po然后按Tab键,os.po被展开成os.popen(就象在In [2]:提示符显示的那样),并显示os所有以po开头的模块,类和函数,它们是popen,popen2, popen3和popen4。 72 | 73 | 菜单补全稍有不同。关闭默认Tab补全,使用菜单补全,你需要修改配置文件$HOME/.ipython/ipythonrc。注释掉: 74 | readline_parse_and_bind tab: complete 75 | 76 | 取消注释: 77 | readline_parse_and_bind tab: menu-complete 78 | 79 | 不同于“普通”自动补全的显示当前命令所有匹配列表,菜单补全会随着你每按一次Tab键而循环显示匹配列表中的项目。例如:: 80 | 81 | In [1]: import os 82 | In [2]: os.po 83 | 84 | 结果是: 85 | 86 | In [3]: os.popen 87 | 88 | 接下来每次按Tab键就会循环显示匹配列表中的其它项目:popen2,popen3,popen4,最后回到po。菜单补全模式下查看所有匹配列表的快捷键是Ctrl-L:: 89 | 90 | In [2]: os.po 91 | os.popen os.popen2 os.popen3 os.popen4 92 | In [2]: os.po 93 | 94 | 自省 95 | ------------ 96 | 97 | Python有几个内置的函数用于自省。IPython不仅可以调用所有标准Python函数,对于那些Python shell内置函数同样适用。典型的使用标准Python shell进行自省是使用内置的dir()函数:: 98 | 99 | >>> import SimpleXMLRPCServer 100 | >>> dir(SimpleXMLRPCServer.SimpleXMLRPCServer) 101 | ['__doc__', '__init__', '__module__', '_dispatch', 102 | '_marshaled_dispatch', 'address_family', 'allow_reuse_address', 103 | 'close_request', 'fileno', 'finish_request', 'get_request', 104 | 'handle_error', 'handle_request', 'process_request', 105 | 'register_function', 'register_instance', 106 | 'register_introspection_functions', 'register_multicall_functions', 107 | 'request_queue_size', 'serve_forever', 'server_activate', 'server_bind', 108 | 'server_close', 'socket_type', 'system_listMethods', 109 | 'system_methodHelp', 'system_methodSignature', 'system_multicall', 110 | 'verify_request'] 111 | 112 | 嗯,非常棒。事实上非常实用。几年来我一直这么做,对此非常满意。这是一个漂亮的列表,包含了 SimpleXMLRPCServer.SimpleXMLRPCServer的所有方法,类,模块等等。因为dir()是一个内置函数,在 IPython中也能很好的使用它们。但是IPython的操作符?和??功能还要强大:: 113 | 114 | In [1]: import SimpleXMLRPCServer 115 | 116 | In [2]: ? SimpleXMLRPCServer.SimpleXMLRPCServer 117 | Type: classobj 118 | String Form: SimpleXMLRPCServer.SimpleXMLRPCServer 119 | Namespace: Interactive 120 | File: /usr/local/python24/lib/python2.4/SimpleXMLRPCServer.py 121 | Docstring: 122 | Simple XML-RPC server. 123 | 124 | Simple XML-RPC server that allows functions and a single instance 125 | to be installed to handle requests. The default implementation 126 | attempts to dispatch XML-RPC calls to the functions or instance 127 | installed in the server. Override the _dispatch method inherited 128 | from SimpleXMLRPCDispatcher to change this behavior. 129 | 130 | Constructor information: 131 | Definition: SimpleXMLRPCServer.SimpleXMLRPCServer(self, addr, 132 | requestHandler=, logRequests=1) 133 | 134 | ? 操作符会截断长的字符串。相反,?? 不会截断长字符串,如果有源代码的话还会以语法高亮形式显示它们。 135 | 136 | 历史 137 | ----------- 138 | 139 | 当你在IPython shell下交互的输入了大量命令,语句等等,就象这样:: 140 | 141 | In [1]: a = 1 142 | 143 | In [2]: b = 2 144 | 145 | In [3]: c = 3 146 | 147 | In [4]: d = {} 148 | 149 | In [5]: e = [] 150 | 151 | In [6]: for i in range(20): 152 | ...: e.append(i) 153 | ...: d[i] = b 154 | ...: 155 | 156 | 你可以快速查看那些输入的历史记录:: 157 | 158 | In [7]: hist 159 | 1: a = 1 160 | 2: b = 2 161 | 3: c = 3 162 | 4: d = {} 163 | 5: e = [] 164 | 6: 165 | for i in range(20): 166 | e.append(i) 167 | d[i] = b 168 | 169 | 要去掉历史记录中的序号(这里是1至6),使用命令hist -n:: 170 | 171 | In [8]: hist -n 172 | a = 1 173 | b = 2 174 | c = 3 175 | d = {} 176 | e = [] 177 | for i in range(20): 178 | e.append(i) 179 | d[i] = b 180 | 181 | 这样你就可以方便的将代码复制到一个文本编辑器中。要在历史记录中搜索,可以先输入一个匹配模型,然后按Ctrl-P。找到一个匹配后,继续按Ctrl-P会向后搜索再上一个匹配,Ctrl-N则是向前搜索最近的匹配。 182 | 183 | 184 | 编辑 185 | =========== 186 | 187 | 当在Python提示符下试验一个想法时,经常需要通过编辑器修改源代码(甚至是反复修改)。在IPython下输入edit就会根据环境变量$EDITOR调用相应的编辑器。如果$EDITOR为空,则会调用vi(Unix)或记事本(Windows)。要回到IPython提示符,直接退出编辑器即可。如果是保存并退出编辑器,输入编辑器的代码会在当前名字空间下被自动执行。如果你不想这样,使用edit -x。如果要再次编辑上次最后编辑的代码,使用edit -p。在上一个特性里,我提到使用hist -n可以很容易的将代码拷贝到编辑器。一个更简单的方法是edit加Python列表的切片(slice)语法。假定hist输出如下:: 188 | 189 | In [29]: hist 190 | 1 : a = 1 191 | 2 : b = 2 192 | 3 : c = 3 193 | 4 : d = {} 194 | 5 : e = [] 195 | 6 : 196 | for i in range(20): 197 | e.append(i) 198 | d[i] = b 199 | 200 | 7 : %hist 201 | 202 | 现在要将第4,5,6句代码导出到编辑器,只要输入: 203 | 204 | edit 4:7 205 | 206 | 207 | Debugger接口 208 | ------------- 209 | 210 | IPython 的另一特性是它与Python debugger的接口。在IPython shell下输入magic关键字pdb就会在产生一个异常时自动开关debugging功能。在自动pdb呼叫启用的情况下,当Python遇到一个未处理的异常时Python debugger就会自动启动。你在debugger中的当前行就是异常发生的那一行。IPython的作者说有时候当他需要在某行代码处debug时,他会在开始debug的地方放一个表达式1/0。启用pdb,在IPython中运行代码。当解释器处理到1/0那一行时,就会产生一个 ZeroDivisionError异常,然后他就在指定的代码处被带到一个debugging session中了。 211 | 212 | 运行 213 | ------------- 214 | 有时候当你在一个交互式shell中时,如果可以运行某个源文件中的内容将会很有用。运行magic关键字run带一个源文件名就可以在IPython解释器中运行一个文件了(例如run <源文件> <运行源文件所需参数>)。参数主要有以下这些: 215 | 216 | * -n 阻止运行源文件代码时__name__变量被设为"__main__"。这会防止 :: 217 | 218 | if __name__ == "__main__": 219 | 220 | 块中的代码被执行 221 | 222 | * -i 源文件就在当前IPython的名字空间下运行而不是在一个新的名字空间中。如果你需要源代码可以使用在交互式session中定义的变量就会很有用。 223 | 224 | * -p 使用Python的profiler模块运行并分析源代码。使用该选项代码不会运行在当前名字空间。 225 | 226 | 227 | 宏 228 | ------------- 229 | 230 | 宏允许用户为一段代码定义一个名字,这样你可以在以后使用这个名字来运行这段代码。就象在magic关键字edit中提到的,列表切片法也适用于宏定义。假设有一个历史记录如下:: 231 | 232 | In [3]: hist 233 | 1: l = [] 234 | 2: 235 | for i in l: 236 | print i 237 | 238 | 你可以这样来定义一个宏:: 239 | 240 | In [4]: macro print_l 2 241 | Macro `print_l` created. To execute, type its name (without quotes). 242 | Macro contents: 243 | for i in l: 244 | print i 245 | 246 | 运行宏:: 247 | 248 | In [5]: print_l 249 | Out[5]: Executing Macro... 250 | 251 | 在这里,列表l是空的,所以没有东西被输出。但这其实是一个很强大的功能,我们可以赋予列表l某些实际值,再次运行宏就会看到不同的结果:: 252 | 253 | In [6]: l = range(5) 254 | 255 | In [7]: print_l 256 | Out[7]: Executing Macro... 257 | 0 258 | 1 259 | 2 260 | 3 261 | 4 262 | 263 | 当运行一个宏时就好象你重新输入了一遍包含在宏print_1中的代码。它还可以使用新定义的变量l。由于Python语法中没有宏结构(也许永远也不会有),在一个交互式shell中它更显得是一个有用的特性。 264 | 265 | 环境(Profiles) 266 | =================================== 267 | 268 | 就象早前提到的那样,IPython安装了多个配置文件用于不同的环境。配置文件的命名规则是ipythonrc-。要使用特定的配置启动IPython,需要这样 :: 269 | 270 | ipython -p 271 | 272 | 一个创建你自己环境的方法是在$HOME/.ipython目录下创建一个IPython配置文件,名字就叫做ipythonrc- ,这里是你想要的环境的名字。如果你同时进行好几个项目,而这些项目又用到互不相同的特殊的库,这时候每个项目都有自己的环境就很有用了。你可以为每个项目建立一个配置文件,然后在每个配置文件中import该项目中经常用到的模块。 273 | 274 | 使用操作系统的Shell 275 | ================================= 276 | 277 | 使用默认的IPython配置文件,有几个Unix Shell命令(当然,是在Unix系统上),cd,pwd和ls都能象在bash下一样工作。运行其它的shell命令需要在命令前加!或!!。使用magic关键字%sc和%sx可以捕捉shell命令的输出。 278 | 279 | pysh环境可以被用来替换掉shell。使用-p pysh参数启动的IPython,可以接受并执行用户$PATH中的所有命令,同时还可以使用所有的Python模块,Python关键字和内置函数。例如,我想要创建500个目录,命名规则是从d_0_d到d_500_d(译注:呵呵,作者这里犯了个小小的计算错误,你能看出来吗),我可以使用-p pysh启动IPython,然后就象这样:: 280 | 281 | jjones@cerberus[foo]|2> for i in range(500): 282 | |.> mkdir d_${i}_d 283 | |.> 284 | 285 | 这就会创建500个目录:: 286 | 287 | jjones@cerberus[foo]|8> ls -d d* | wc -l 288 | 500 289 | 290 | 注意这里混合了Python的range函数和Unix的mkdir命令。 291 | 292 | 注意,虽然ipython -p pysh提供了一个强大的shell替代品,但它缺少正确的job控制。在运行某个很耗时的任务时按下Ctrl-z将会停止IPython session而不是那个子进程。 293 | 294 | 问题和方法 295 | ============== 296 | 297 | 虽然作为标准Python shell的替换,IPython总的来说很完美。还是有两个问题给我带来了一些麻烦。感谢IPython的开发者,这两个问题都可以通过配置来解决,每个配置都有清晰的文档。 298 | 299 | 第一个问题是关于颜色的。在我的一个系统上,我使用的是一个白色背景的xterm。当我使用?和??查询一个对象或模块的信息时,对象的定义会被显示,但看起来好象那些参数丢失了。那是因为在构造函数中的的参数默认显示为白色。我的解决办法是在IPython shell中输入colors LightBG。 300 | 301 | 第二个问题是关于自动缩进和代码粘贴的。如果autoindent被启用,IPython会对我粘贴的已排好缩进的代码再次应用缩进。例如下面的代码:: 302 | 303 | for i in range(10): 304 | for j in range(10): 305 | for k in range(10): 306 | pass 307 | 308 | 会变成:: 309 | 310 | for i in range(10): 311 | for j in range(10): 312 | for k in range(10): 313 | pass 314 | 315 | 在这里它并不是个问题,因为在它自身中缩进都保持一致。在其它一些情况下(例子一下子举不出来了),可能会成为真正的问题。可以使用magic关键字autoindent来开关自动缩进,告诉IPython不要添加多余的缩进──就象在vim中设置粘贴set paste一样。 316 | 317 | 结论 318 | ============== 319 | 320 | IPython 并不是囗囗性的,也不是完全创新的。Tab自动补全,历史记录搜索,配置环境,配置文件等都早已在其它shells中存在有些年头了。Python拥有各种级别的自省能力也有一段时间了。但IPython把来自成熟的Unix shell,标准Python shell以及Python语言中的一些最强大的功能整合到了一起。产生出了一个强大的令人难以置信的性能增强工具,我想我会很乐意在接下来的几年中一直使用它。套用阿基米德的话来说,给我一个强大而又灵活的文本编辑器(vim),一个交互式shell(IPython)以及一个语言(Python),我就能撬动整个世界。 321 | 322 | -------------------------------------------------------------------------------- /devtools/other.rst: -------------------------------------------------------------------------------- 1 | .. _other_dev_tools: 2 | 3 | ============ 4 | 其它开发工具 5 | ============ 6 | 7 | 8 | - 开发工具 9 | - bitbucket 10 | - iTerm2 11 | - xshell 12 | - filezilla 13 | 14 | - Grunt 15 | - Firefox 16 | 17 | - firebug 18 | - scrapbook 19 | 20 | - sip(Mac) 21 | - Postman 22 | 23 | 24 | -------------------------------------------------------------------------------- /devtools/vim.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _vim_tutorial: 4 | 5 | ================================================== 6 | vim 快速入门 7 | ================================================== 8 | 9 | :作者: 王然 kxxoling@gmail.com 10 | 11 | vim 简介 12 | ----------------------- 13 | 14 | 在这个蔚蓝色的星球上,流传着两大神器的传说:Emacs是神的编辑器,而Vim是编辑器之神。 15 | 16 | VIM , 全称 Vi Improved , Vi的增强版 。 17 | 18 | Vi 在 1976 年发布,奉行 Unix 传统的“Do one thing and do it well”哲学,每个程序只做一件事但做到最好,通过程序之间的配合得到强大的功能。 19 | 20 | Emacs则奉行“Everything at reach”设计哲学,通过强大的扩展性,达到在一个软件里做所有的事。Emacs可以用来编辑文档、时间管理、浏览图片、阅读pdf、听音乐、写程序、运行程序、调试程序、接受发送邮件、看新闻组、玩游戏、管理系统、Telnet/FTP、版本控制、写LaTex…被称为“伪装成编辑器的操作系统”。 21 | 22 | 江湖中有一句话: “ 世界上的程序员分三种,一种使用Emacs,一种使用Vim,剩余的是其它” 。 23 | 24 | 25 | 基本概念 26 | ------------------ 27 | 28 | Vim 有两种模式——Normal 模式和 Insert 模,所有命令都是在 Normal 模式下执行。启动 Vim 后,默认进入 Normal 模式, 29 | 可以按 i 键进入 Insert 模式,或者 s 删除当前字符并进入 Insert 模式,退出 Insert 模式进入 Normal 按 ESC 。 30 | 31 | 下面的教程中约定 + 表示同时按其左右的按键,小写字母(如 i)表示按该字母一次,大写字母(如 G)表示同时按 shift+g。 32 | 33 | 34 | 基本用法 35 | ------------------ 36 | 37 | * i insert 输入 38 | * v 行选中 39 | * ctrl+v 列选中 40 | * G 至文末 41 | * gg 至文首 42 | * :q 未修改退出 43 | * :q! 强制不保存退出 44 | * :x / :wq 保存并退出 45 | * J 合并多行 46 | * d 删除当前所选 47 | * dd 删除多行并存在剪贴板中(剪切) 48 | * y 复制当前所选 49 | * yy 复制整行 50 | * p 粘贴 51 | * u 撤销操作 52 | * w 光标移动到下一个单词处 53 | * b 光标移动到上一个单词处 54 | * ^ 光标移动到行首 55 | * $ 光标移动到行尾 56 | * kjhl 或者上下左右键移动光标 57 | * shift+上下键 翻页 58 | * shift+左右 光标乙至上/下一个单词(以空格/标点区分单词)词首 59 | * u 撤销上一步操作 60 | * zo/zn/zc 折叠/展开代码块 61 | * :vsp 新建工作区 62 | * ctrl+w 松手后再按 方向键 切换工作区 63 | * :MR 选择最近打开的文件(需安装插件) 64 | * F12 运行当前文件 65 | * # 搜索光标处短语 66 | * :set paste 进入粘贴模式 67 | * :%s/target/something/g 替换全部 target 字段 68 | * :s/target/something/g 替换选中区域 target 字段 69 | 70 | 组合用法 71 | ------------------ 72 | 73 | 注释大段文字 74 | 75 | * 光标移至行首 76 | * I 进入插入模式 77 | * 输入注释符号 78 | * 双击 ESC 79 | 80 | 如何配置 81 | ------------------ 82 | 83 | `VIM as an IDE `_ 84 | 85 | 86 | 进阶文档 87 | ------------------ 88 | 89 | `简明 Vim 练级攻略 `_ 90 | 91 | 92 | vim名词解释 93 | ----------- 94 | 95 | 模式 96 | ~~~~ 97 | 98 | vim有5中基本模式,分别是 99 | 100 | - Normal Mode 也就是最一般的普通模式,默认进入vim之后,处于这种模式。 101 | 102 | - Visual Mode 一般译作可视模式,在这种模式下选定一些字符、行、多列。 103 | 在普通模式下,可以按v进入。 104 | 105 | - Insert Mode 106 | 插入模式,其实就是指处在编辑输入的状态。普通模式下,可以按i进入。 107 | 108 | - Select Mode 109 | 在gvim下常用的模式,可以叫作选择模式吧。用鼠标拖选区域的时候,就进入了选择模式。 110 | 和可视模式不同的是,在这个模式下,选择完了高亮区域后,敲任何按键就直接输入并替换选择的文本了。 111 | 和windows下的编辑器选定编辑的效果一致。普通模式下,可以按gh进入。 112 | ps:这种模式好无用啊 113 | 114 | - Command-Line/Ex Mode 115 | 就叫命令行模式和Ex模式吧。两者略有不同,普通模式下按冒号(:)进入Command-Line模式,可以输入各种命令, 116 | 使用vim的各种强大功能。普通模式下按Q进入Ex模式,其实就是多行的Command-Line模式。 117 | ps:经常使用EX模式的都是神阶vimer 118 | 119 | *注:本文中所说的快捷键若无特殊说明则是Normal Mode下的模式* 120 | 121 | Windows/窗口 122 | ~~~~~~~~~~~~ 123 | 124 | vim可以将窗口分成好多个独立的块来显示不同的文件,我们把这些块叫做窗口。当然你可以选择竖着分或者横着分,横着分的叫做split 125 | window,竖着分的叫做vsplit window。 126 | 127 | Tab/标签页 128 | ~~~~~~~~~~ 129 | 130 | 就像chrome的标签页一样。 131 | 132 | Buffer/缓冲区 133 | ~~~~~~~~~~~~~ 134 | 135 | 缓冲区(Buffer)是一块内存区域,里面存储着正在编辑的文件。如果没有把缓冲区里的文件存盘,那么原始文件不会被更改。 136 | 可以通过:ls或:buffer命令查看缓冲区 137 | 138 | -------------- 139 | 140 | 插件 141 | ---- 142 | 143 | Vundle插件管理器 144 | ~~~~~~~~~~~~~~~~ 145 | 146 | Vundle的功能是用于安装和管理其他插件,它能够直接从github上下载并自动安装置顶的插件。其本身也托管在github上,我们可以使用下面的命令快捷的安装它。 147 | 148 | :: 149 | 150 | git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim 151 | 152 | 然后只需要在.vimrc文件中写入要安装的插件然后在vim中运行Vundle的插件安装命令就可以自动下载安装指定的插件了。 153 | 更详细的使用方法可参考\ `Vundle的文档 `__\ 。 154 | 155 | MRU最近打开的文件 156 | ~~~~~~~~~~~~~~~~~ 157 | 158 | MRU的功能是从底部弹出一个最近打开的插件列表,其默认启动命令是:MRU,为了使用方便我将其设为mr。 159 | 160 | nerdtree文件目录树 161 | ~~~~~~~~~~~~~~~~~~ 162 | 163 | nerdtree是一个用于显示目录树的工具默认启动方式是:NERDtree,好难打的样子,所以我把它设成了nt。 164 | 165 | 关于这个插件有一个非常实用的设置是它可以忽略指定类型的文件,例如我们希望将所有的pyc或者其他没用的文件从目录树中过滤调的时候就可以使用它的这个功能,就像这样 166 | 167 | :: 168 | 169 | let NERDTreeIgnore=['\.pyc$', '\~$'] 170 | 171 | 它在github上的readme比较渣,没有介绍什么具体的用法和功能,我这里列出一些我用过的,还有一些大家可以通过:help 172 | Nerdtree查看。 173 | 174 | - o.......打开文件并将焦点移动到打开的文件或展开当前文件夹 175 | - Enter...跟o一样 176 | - go......跟o一样,但将焦点留在NerdTree 177 | - t.......在新tab中打开文件 178 | - T.......同t,但保留焦点 179 | - i.......在一个新的 split window中打开文件 180 | - gi......同i,保留焦点 181 | - s.......在新的 vsplit 窗口打开文件 182 | - gs......同s保留焦点 183 | - O.......递归打开当前文件夹 184 | - x.......关闭当前文件夹的父文件夹 185 | - X.......递归关闭当前文件夹 186 | - P.......跳到根目录 187 | - p.......跳到当前目录的父目录 188 | - q.......退出NerdTree 189 | 190 | CtrlP模糊搜索文件 191 | ~~~~~~~~~~~~~~~~~ 192 | 193 | CtrlP是一个用于模糊搜索文件的插件,其文档比较健全看看它的\ `readme `__\ 就能学会其用法,而且其默认启动快捷键就是Ctrl+p。 194 | 195 | 其跟NerdTree一样,也可以在split(Ctrl+s)和vsplit(Ctrl+v)窗口以及新tab(Ctrl+t)打开文件。 196 | 197 | 还有一个使用的设置就是忽略指定类型文件,像这样 198 | 199 | :: 200 | 201 | let g:ctrlp_custom_ignore = { 202 | \'file' : '\v\.(pyc|html\.py)$', 203 | \} 204 | 205 | ag和CtrlSF 206 | ~~~~~~~~~~ 207 | 208 | ag是一个linux下非常好用的代码搜索工具(代码在\ `github上 `__\ 需要手动安装),它可以快速搜索你的代码内容。vim的ag插件允许我们在vim中使用ag命令搜索代码,CtrlSF插件跟ag插件的不同在于前者可以显示代码的上下文,显然是CtrlSF更为强大。 209 | 210 | CtrlSF的默认命令就是:CtrlSF,然后输入要搜索的字符再敲回车。好难打,好难用,所以我设置了下面的快捷键: 211 | 212 | :: 213 | 214 | nmap f :CtrlSF 215 | nmap o :CtrlSFOpen 216 | nmap ss :CtrlSF 217 | vnoremap ss y:CtrlSF " 218 | 219 | - Ctrl+s+f................适用于normal模式,就跟:CtrlSF一样 220 | - Ctrl+s+o................适用于normal模式,打开搜索结果的窗口 221 | - ss......................适用于normal模式,搜索当前光标所在的单词 222 | - ss......................适用于visual模式,搜索当前选中的文字 223 | 224 | tpope/vim-commentary 批量注释 225 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 226 | 227 | 这货可以批量注释代码,就像eclipse的Ctrl+j一样。当然你以可以使用选中再批量插入的方式开实现批量注释,但你需要按5个键(Ctrl,v,Shift,I,Est)而commentry只需三个键就可以了。 228 | 229 | 首先,我们设个快捷键以及将python的注释符设为# 230 | 231 | :: 232 | 233 | vnoremap :Commentary 234 | autocmd FileType python set commentstring=#\ %s 235 | 236 | 这样在可视模式选中要注释的内容后可以按退格键批量注释,再次选中按退格键就解除注释。 237 | 238 | Syntastic语法检查 239 | ~~~~~~~~~~~~~~~~~ 240 | 241 | 该插件的功能是检查和标记语法错误及不规范的问题,在我们的项目下 242 | 243 | supertab,补全插件 244 | ~~~~~~~~~~~~~~~~~ 245 | 246 | vim-coffee-script,CoffeeScript语法高亮 247 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 248 | 249 | mako.vim,mako语法高亮 250 | ~~~~~~~~~~~~~~~~~~~~~ 251 | 252 | vim-mercenary 支持hg blame和diff 253 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 254 | 255 | vim-colors-solarized 漂亮的颜色主题 256 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 257 | 258 | luochen1990/rainbow 彩虹括号,匹配的括号显示为同一颜色 259 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 260 | 261 | godlygeek/tabular 自动对齐 262 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 263 | 264 | hynek/vim-python-pep8-indent python自动缩进 265 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 266 | 267 | indentLine垂直缩进对齐线 268 | ~~~~~~~~~~~~~~~~~~~~~~~~ 269 | 270 | MatchTag高亮显示匹配的html标签 271 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 272 | 273 | 移动 274 | ---- 275 | 276 | 1. :[n] 移动光标当第n行。 277 | 2. H,M,L 分别移动光标到当前屏幕首行,中间行和尾行。 278 | 3. Ctrl+f和Ctrl+b向下和向上翻页,相当于pageup和pagedown。 279 | 4. h,j,k,l向左下上右移动一个字符。 280 | 5. f和F加字符,将光标移动到下一个或上一个该字符的位置,例如fa会将光标移动到下一个a的位置。 281 | 6. m设定标记,\`跳转到指定标记。例如可以用ma在某行设定标记,再使用\`a跳转到改行。 282 | 7. shift+左右 光标移至上/下一个单词(以空格/标点区分单词)词首。 283 | 8. w 光标移动到下一个单词处 284 | 9. b 光标移动到上一个单词处 285 | 10. ^ 光标移动到行首 286 | 11. $ 光标移动到行尾 287 | 288 | 插入 289 | ---- 290 | 291 | 1. a和i分别在当前字符前和后插入。 292 | 2. A和I分别在当前行尾和行首插入。 293 | 3. 批量插入。首先在可视模式下选中要插入的行,然后按I可在选中处之前批量插入字符 294 | 295 | 复制、粘贴、替换和删除 296 | ---------------------- 297 | 298 | 1. r可以替换当前字符。 299 | 2. yy和dd可以分别复制和剪切当前行。 300 | 3. y2y和d2d可以分别复制和剪切当前行开始的2行。 301 | 4. :3,8y和:3,8d可以分别复制和剪切第3到第8行。 302 | 5. yw和dw可以分别复制和剪切光标所在的单词。 303 | 6. d(可视模式) 删除当前所选 304 | 7. dd 删除多行并存在剪贴板中(剪切) 305 | 8. y 复制当前所选 306 | 9. p 粘贴 307 | 308 | 分屏相关 309 | -------- 310 | 311 | 1. :vsp和:sp分别竖着和横着分割当前窗口。 312 | 2. Ctrl + v和Ctrl + s也可以竖向和横向分屏。 313 | 3. Ctrl + w + 箭头键(hjkl)在不同窗口键移动。 314 | 4. Ctrl + = 将所有的窗口大小调成相同大。 315 | 316 | 查找和替换 317 | ---------- 318 | 319 | 1. / + 要搜索的内容搜索。 320 | 2. n和N跳到下一个或上一个搜索结果。 321 | 3. # 搜索当前光标所在的单词。 322 | 323 | 折叠代码 324 | -------- 325 | 326 | 1. zc,zC,zo,zO折叠或打开折叠当前行的代码,其中大写Z和O表示折叠或打开折叠所有层。 327 | 2. zn,zm折叠或打开折叠当前文件的所有代码。 328 | 329 | 定制的快捷键 330 | ------------ 331 | 332 | - F12..................运行python文件或使用zencode补全html 333 | - F11..................格式化代码 334 | - F5,F6,F7,F8..........调整窗口大小 335 | - Ctrl+s+f.............使用ag搜索 336 | - Ctrl+s+o.............打开ag搜索结果 337 | - ss...................使用ag搜索光标所在的单词 338 | - ss(可视模式).........搜索选中的单词 339 | - Backspace(可视模式)..注释/解除注释代码 340 | - nt...................打开NerdTree 341 | - mr...................打开MRU 342 | - tl...................打开taglist 343 | - bn...................打开下一个buffer 344 | - bp...................打开上一个buffer 345 | 346 | 其他 347 | ---- 348 | 349 | 1. u和CTRL+r分别是undo和redo的功能。 350 | 2. :set nu和:set nonu分别为显示和不显示行号。 351 | 3. Shift + < 或 >分表表示向左或向右缩进一层,也可以选中后批量缩进。 352 | 4. n + Shift + < 或 >可批量缩进n层。 353 | 354 | - i insert 输入 355 | - v 行选中 356 | - ctrl+v 列选中 357 | - :q 未修改退出 358 | - :q! 强制不保存退出 359 | - :x / :wq 保存并退出 360 | - J 合并多行 361 | 362 | - shift+上下键 翻页 363 | - :set paste 进入粘贴模式 364 | - :%s/target/something/g 替换全部 target 字段 365 | 366 | 367 | Vim 实用技巧 368 | ------------ 369 | 370 | http://segmentfault.com/q/1010000000166577 文字 371 | 372 | vim-multiple-cursors Sublime Text 373 | 支持多个光标选择功能,在重构时非常有用。这个插件将 Sublime Text 374 | 中的这个邪恶功能引入了 375 | Vim。想要修改变量名时,只需要将光标放在变量名内,然后多次敲击 Ctrl + 376 | n,即可将多个同名变量选中,此时再按 s 就能同时将这些变量重命名了。 377 | 378 | http://doc.42qu.com/school/vim.html?highlight=vim 379 | -------------------------------------------------------------------------------- /devtools/xshell.rst: -------------------------------------------------------------------------------- 1 | .. _xshell: 2 | 3 | xshell 客户端 4 | =========================================== 5 | 6 | .. _ssh_login: 7 | 8 | 9 | Xshell : 极好用的免费SSH客户端 10 | ------------------------------------------- 11 | 12 | `点此下载 Xshell `_ 13 | 14 | 15 | 创建session 16 | ------------------------------------------- 17 | 18 | 安装好xshell后,打开软件,点击菜单栏“File”中的“new”,将出现下图所示弹窗,填写相应信息。其中Host为邮件中所给的主机ip地址(name栏可以不用修改,但一般为便于在xshell中区分不同的主机,自己一般会修改为“用户名@主机”,也可以用“用户名@42qu”),然后点击“ok”。 19 | 20 | .. image:: ../_image/register.png 21 | :alt: 首次登录 22 | 23 | 24 | 25 | 登录 26 | ------------------------------------------ 27 | 28 | 在随后弹出的弹窗中选择自己创建的帐号,点击“connect”,在弹出窗口中输入邮件中提供的用户名,新窗口中选择“keyboard Interactive” 29 | 30 | 并确定,最后输入邮件中提供的密码,即可登录到自己的vps中。 31 | 32 | 33 | #. .. image:: ../_image/login1.png 34 | 35 | #. .. image:: ../_image/login2.png 36 | 37 | #. .. image:: ../_image/login3.png 38 | 39 | 40 | 41 | 设置显示编码 42 | ------------------------------------------- 43 | 44 | 简单设置一下xshell的字符显示,在下图所示的导航按钮中,勾选“UTF-8”。 45 | 46 | .. image:: ../_image/encoding.png 47 | 48 | 顺便可以设置下字体 49 | 50 | .. image:: ../_image/xshell_ft.png 51 | 52 | 如果觉得粗体看着碍眼, 那么可以修正下显示设置 53 | 54 | #. .. image:: ../_image/xshell_font_btn.png 55 | 56 | #. .. image:: ../_image/xshell_font.png 57 | 58 | 59 | 60 | 配置密钥登录 , 无需每次输入密码 61 | --------------------------------------- 62 | 63 | 为避免每次登录vps都需要重复输入用户名和密码的步骤,可以通过生成.ssh/authorized_keys来减少麻烦。 64 | 65 | 执行:: 66 | 67 | cd ~ 68 | 69 | 命令,来到home(家)目录 70 | 71 | 执行:: 72 | 73 | ssh-keygen 74 | 75 | 命令 , 然后按两次回车, 生成密钥 76 | 77 | 执行:: 78 | 79 | cd .ssh 80 | 81 | 进入.ssh目录 82 | 83 | 执行:: 84 | 85 | cat id_rsa.pub >> authorized_keys 86 | 87 | 将把当前目录下 88 | 89 | id_rsa.pub中的数据拷贝一份到新建的authorized_keys档案中。 90 | 91 | .. image:: ../_image/makekeys.png 92 | 93 | 点击导航中的“new file transfer”图标,如下图所示。 94 | 95 | .. image:: ../_image/filetransfer.png 96 | 97 | 弹出窗口中忽视警告,确定后输入密码,在.ssh目录下执行“get id_rsa”命令,id_rsa将被保存到下图红线所示的本地目录中。 98 | 99 | .. image:: ../_image/getkeys.png 100 | 101 | 在xshell菜单栏中依次点击“File”->“open”,选中你的session用户,并点击“Properties”,如下图所示。 102 | 103 | .. image:: ../_image/reset1.png 104 | 105 | 做下图所示修改,点击“Browse”按钮。 106 | 107 | .. image:: ../_image/reset2.png 108 | 109 | 点击import按钮,选择id_rsa,之后一路确定,再次登录是就可以不用再输入用户名和密码了。 110 | 111 | .. image:: ../_image/reset3.png 112 | 113 | 114 | 克隆代码 115 | -------------------------------------------------- 116 | 117 | 重新登录后,为了方便学习各种命令; 我们可以首先克隆一份 42qu.com 的源代码, 执行:: 118 | 119 | hg clone https://bitbucket.org/zuroc/zpage 120 | 121 | 等上十分钟 , 会在当前目录下生成新目录zpage,其中包括了项目的所有代码,如图所示。 122 | 123 | .. image:: ../_image/clone.png 124 | 125 | 126 | 安装 virtualenv 127 | -------------------------------------------------- 128 | 129 | 首先运行 :: 130 | 131 | virtualenv . 132 | 133 | 然后修改 ~/.bash_profile 如下 :: 134 | 135 | [[ -f ~/.bashrc ]] && . ~/.bashrc 136 | export PATH=$HOME/bin:$HOME/sbin:$PATH:/usr/sbin:/sbin 137 | 138 | 再运行 :: 139 | 140 | source ~/.bash_profile 141 | 142 | 再运行 :: 143 | 144 | pip install setuptools --upgrad 145 | 146 | 然后就可以使用 pip 或者 easy_install 安装python的库了 147 | -------------------------------------------------------------------------------- /docker.rst: -------------------------------------------------------------------------------- 1 | Docker 快速入门 2 | =============== 3 | 4 | 基本概念 5 | -------- 6 | 7 | Docker 是基于 Linux kernel 的虚拟化工具,仅需要极低的系统资源使用就提供了 8 | 强大的虚拟化、资源隔离能力。使用 Docker,用户只需要几分钟即可以将应用程序 9 | “Docker 化”,并且由于其易于复制分享的优点,能够保证开发与部署环境的一致性。 10 | 11 | Docker 的基本结构包括: 12 | 13 | 1. Docker 客户端和服务器(C/S) 14 | #. Image 15 | #. Registry 16 | #. Container 17 | 18 | 在本地安装 Docker 之后,即完成了 Docker C/S 的安装。用户使用 Docker 客户端向 19 | Docker 的守护进程发送命令操作 Container。 20 | 21 | Image(镜像)类似于应用的源代码,你可以通过修改源代码(Image)来构建出不同的 Container。 22 | Registry()类似于源码的托管服务器,默认的 Registry 是 Docker 公司提供的 Docker Hub, 23 | 你可以将 Docker Hub 类比于 GitHub。 24 | 25 | Container 是 Docker 最重要的概念,它通过操作相应的镜像提供一个执行环境。 26 | 27 | 使用 Docker,可以快速构建很分享应用程序部署服务器、开发环境、CI 环境或者一个应用服务。 28 | 29 | 30 | 首先声明,这里的系统环境设置为 Ubuntu 14.04,Mac OS 和 Windows 31 | 系统请在虚拟机里安装 Ubuntu, 其它 Linux 发行版 和 Ubuntu 32 | 不会有太大区别,请自行修改命令。 33 | 34 | 使用 Ubuntu(我自己安装的是 Xubuntu) 的好处: 35 | 36 | - 相比 Boot2docker 来说更加强大,比如我可以通过安装 zsh + 37 | zsh-docker-completetion 提供 docker 自动补全功能。 38 | - 获得最新最强大的官方支持,Docker 官方推荐使用 Ubuntu,对 Ubuntu 39 | 下的问题也能够最及时解决, 甚至某些工具不提供 Mac OS 或者 Windows 40 | 兼容方案。 41 | - 相对于其它 Linux 发行版用户更多,使用更简单。 42 | - 推荐在虚拟机中安装 Xubuntu,和 Ubuntu 相比,Xubuntu 43 | 更轻量,在虚拟机中性能更好; 在虚拟机中安装 Xubuntu 44 | 便于对开发环境的迁移以及内部共享。 45 | 46 | Docker 命令 47 | ----------- 48 | 49 | Docker 可以通过命令来构建镜像,也可以根据 Dockerfile 配置来构建。 docker 50 | 命令与 Dockerfile 的对应关系如下: 51 | 52 | Dockerfile 53 | ---------- 54 | 55 | 命令 56 | ~~~~ 57 | 58 | WORKDIR 59 | ^^^^^^^ 60 | 61 | ENV 62 | ^^^ 63 | 64 | USER 65 | ^^^^ 66 | 67 | VOLUME 68 | ^^^^^^ 69 | 70 | ADD 71 | ^^^ 72 | 73 | 向镜像中添加特定文件,可以是主机中或者 web 文件。以 WordPress 的 74 | Dockerfile 为例: 75 | 76 | .. code:: dockerfile 77 | 78 | ADD http://wordpress.org/latest.zip /var/www/wordpress.zip 79 | 80 | ONBUILD 81 | ^^^^^^^ 82 | 83 | RUN 84 | ^^^ 85 | 86 | CMD 87 | ^^^ 88 | 89 | ENTRYPOINT 90 | ^^^^^^^^^^ 91 | 92 | CentOS 93 | ~~~~~~ 94 | 95 | MongoDB 96 | ^^^^^^^ 97 | 98 | 参考:\ `CentOS/CentOS-Dockerfiles `__ 99 | 100 | .. code:: dockerfile 101 | 102 | FROM centos:latest 103 | MAINTAINER Kane Blueriver 104 | 105 | RUN yum -y update; yum clean all 106 | RUN yum -y install epel-release; yum clean all 107 | RUN yum -y install mongodb-server; yum clean all 108 | 109 | RUN mkdir -p /data/ 110 | 111 | # 设置挂载点 112 | VOLUME ["/data/mongo"] 113 | 114 | # Define working directory. 115 | WORKDIR /data 116 | 117 | CMD ["mongod"] 118 | 119 | EXPOSE 27017 120 | ENTRYPOINT ["/usr/bin/mongod"] 121 | 122 | Redis 123 | ^^^^^ 124 | 125 | 参考:\ `CentOS/CentOS-Dockerfiles `__ 126 | 127 | .. code:: dockerfile 128 | 129 | FROM centos:latest 130 | MAINTAINER Kane Blueriver 131 | 132 | RUN yum -y update; yum clean all 133 | RUN yum -y install epel-release; yum clean all 134 | RUN yum -y install redis; yum clean all 135 | 136 | # 设置挂载点 137 | VOLUME ["/data/redis"] 138 | 139 | # Define working directory. 140 | WORKDIR /data 141 | 142 | EXPOSE 6379 143 | 144 | CMD ["redis-server"] 145 | 146 | Memcached 147 | ^^^^^^^^^ 148 | 149 | 参考:\ `CentOS/CentOS-Dockerfiles `__ 150 | 151 | .. code:: dockerfile 152 | 153 | FROM centos:latest 154 | MAINTAINER Kane Blueriver 155 | RUN yum -y update; yum clean all 156 | RUN yum -y install epel-release; yum clean all 157 | RUN yum -y install memcached; yum clean all 158 | 159 | VOLUME ["/data/mc"] 160 | 161 | WORKDIR /data 162 | 163 | CMD ["memcached"] 164 | 165 | EXPOSE 11211 166 | 167 | CMD ["memcached", "-u", "daemon"] 168 | 169 | Ubuntu 170 | ~~~~~~ 171 | 172 | 参考\ `dockerfile/mongodb `__ 173 | 174 | .. code:: dockerfile 175 | 176 | FROM dockerfile/ubuntu 177 | 178 | # 从官网安装 MongoDB 179 | RUN \ 180 | apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 && \ 181 | echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' > /etc/apt/sources.list.d/mongodb.list && \ 182 | apt-get update && \ 183 | apt-get install -y mongodb-org && \ 184 | rm -rf /var/lib/apt/lists/* 185 | 186 | # 设置挂载点 187 | VOLUME ["/data/db"] 188 | 189 | # Define working directory. 190 | WORKDIR /data 191 | 192 | CMD ["mongod"] 193 | 194 | # 27017: process 195 | # 28017: http 196 | EXPOSE 27017 197 | EXPOSE 28017 198 | 199 | 分享 200 | ---- 201 | 202 | 构建好自己的镜像后可以将其 push 到 Registry 上进行分享,默认的 Registry 203 | 由 Docker Hub 提供, 如果镜像中存在隐私内容也可以使用 Docker 204 | 公司的源代码搭建内部的共享服务器。 205 | 206 | 登录 207 | ~~~~ 208 | 209 | Push 210 | ~~~~ 211 | 212 | 自动构建 213 | ~~~~~~~~ 214 | 215 | 搭建自己的 Docker Registry 216 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 217 | 218 | 使用 Docker 进行 web 开发 219 | ------------------------- 220 | 221 | 多个容器互联 222 | ~~~~~~~~~~~~ 223 | 224 | 持续集成 225 | ~~~~~~~~ 226 | 227 | Drone Shippable 228 | 229 | 230 | 快速编配 231 | -------- 232 | 233 | Docker 公司还提供了快速编配工具 compose(原 Fig)用于加速 Docker 234 | 环境的构建。 235 | 236 | 安装: 237 | 238 | .. code-block:: shell 239 | 240 | pip install docker-compose 241 | 242 | 下面以一个标准的 Python WSGI 应用为例,介绍 compose 的使用方法。 243 | 244 | WSGI 应用 245 | ~~~~~~~~~ 246 | 247 | 首先你需要一个 WSGI 应用,这里以一个简单的 Flask 应用为例: 248 | 249 | .. code-block:: python 250 | 251 | from flask import Flask 252 | from redis import Redis 253 | import os 254 | app = Flask(__name__) 255 | redis = Redis(host='redis', port=6379) 256 | 257 | @app.route('/') 258 | def hello(): 259 | redis.incr('hits') 260 | return 'Hello World! I have been seen %s times.' % redis.get('hits') 261 | 262 | if __name__ == "__main__": 263 | app.run(host="0.0.0.0", debug=True) 264 | 265 | 标准的 Python 应用还需要提供一个 requirements.txt 记录其依赖——flask 和 redis: 266 | 267 | .. code-block:: text 268 | 269 | flask 270 | redis 271 | 272 | Dockerfile 273 | ~~~~~~~~~~ 274 | 275 | 定制一个 Flask 应用的运行环境: 276 | 277 | .. code-block:: dockerfile 278 | 279 | FROM python:2.7 280 | ADD . /code 281 | WORKDIR /code 282 | RUN pip install -r requirements.txt 283 | 284 | 定义服务 285 | ~~~~~~~~ 286 | 287 | 定义 ``docker-compose.yml`` 配置文件,装配应用运行环境所需组件(Container、Volume 等): 288 | 289 | .. code-block:: yaml 290 | 291 | web: 292 | build: . 293 | command: python app.py 294 | ports: 295 | - "5000:5000" 296 | volumes: 297 | - .:/code 298 | links: 299 | - redis 300 | redis: 301 | image: redis 302 | 303 | 上面的配置文件定义了 2 个服务: 304 | 305 | - web:根据本地 Dockerfile 构建出的 Image,提供一个 WSGI 应用的运行环境,将 Docker 中的 5000 端口 306 | 映射到主机的 5000 端口,并将本目录挂载到 Container 的 /code 目录。并且链接到 redis 服务。 307 | - redis:基于 Docker Hub 的 redis 公开镜像构建的 Container。 308 | 309 | 构建以及运行 310 | ~~~~~~~~~~~~ 311 | 312 | 构建: 313 | 314 | .. code-block:: shell 315 | 316 | docker-compose up 317 | 318 | 运行: 319 | 320 | .. code-block:: shell 321 | 322 | docker-compose run web env 323 | 324 | 停止: 325 | 326 | .. code-block:: shell 327 | 328 | docker-compose stop 329 | 330 | 331 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PegasusWang/python-web-guide/f14cdd2decd0a0f0d987caafadf02700aa932fb5/favicon.ico -------------------------------------------------------------------------------- /go-note/algorithms.rst: -------------------------------------------------------------------------------- 1 | .. _go_algorithms: 2 | 3 | Go 算法和数据结构 4 | ===================================================================== 5 | 6 | 刷题过程中会遇到一些通用数据结构的封装,在这里记录一下。注意因为是刷算法题用的,没有考虑 goroutine 安全, 7 | 不要直接用在并发环境,如果是在生产环境中使用请加锁改造。(没有泛型写起来挺繁琐的) 8 | 9 | Stack 10 | -------------------------------------------------- 11 | 12 | .. code-block:: go 13 | 14 | type Stack struct { 15 | st []int 16 | } 17 | 18 | func NewStack() *Stack { 19 | return &Stack{st:make([]int,0)} 20 | } 21 | func (s *Stack) Push(x int) { 22 | s.st = append(s.st, x) 23 | } 24 | 25 | func (s *Stack) Peek() int{ 26 | return s.st[len(s.st)-1] 27 | } 28 | 29 | func (s *Stack) Pop() int{ 30 | n := len(s.st) 31 | top := s.st[n-1] 32 | s.st = s.st[:n-1] 33 | return top 34 | } 35 | 36 | func (s *Stack) Empty() bool{ 37 | return len(s.st) == 0 38 | } 39 | 40 | Queue 41 | -------------------------------------------------- 42 | 43 | .. code-block:: go 44 | 45 | type Item struct { 46 | Num int 47 | Order int 48 | } 49 | type Queue struct { 50 | items []Item 51 | } 52 | 53 | func NewQueue() *Queue { 54 | return &Queue{ 55 | items: make([]Item, 0), 56 | } 57 | } 58 | func (q *Queue) Push(x Item) { 59 | q.items = append(q.items, x) 60 | } 61 | 62 | func (q *Queue) Pop() Item { 63 | x := q.items[0] 64 | q.items = q.items[1:] 65 | return x 66 | } 67 | 68 | func (q *Queue) Front() Item { 69 | return q.items[0] 70 | } 71 | 72 | func (q *Queue) End() Item { 73 | return q.items[len(q.items)-1] 74 | } 75 | 76 | func (q *Queue) Empty() bool { 77 | return len(q.items) == 0 78 | } 79 | 80 | Deque 双端队列 81 | -------------------------------------------------- 82 | 83 | .. code-block:: go 84 | 85 | import ( 86 | "container/list" 87 | "fmt" 88 | ) 89 | 90 | // 滑动窗口最大值 91 | 92 | type Deque struct { 93 | ll *list.List 94 | } 95 | 96 | func NewDeque() *Deque { 97 | return &Deque{ll: list.New()} 98 | } 99 | 100 | func (dq *Deque) PushFront(x int) { 101 | dq.ll.PushFront(x) 102 | } 103 | 104 | func (dq *Deque) PushBack(x int) { 105 | dq.ll.PushBack(x) 106 | } 107 | 108 | func (dq *Deque) Pop() { // remove back 109 | dq.ll.Remove(dq.ll.Back()) 110 | } 111 | 112 | func (dq *Deque) PopFront() { // remove first 113 | dq.ll.Remove(dq.ll.Front()) 114 | } 115 | 116 | func (dq *Deque) Front() int { 117 | return dq.ll.Front().Value.(int) 118 | } 119 | 120 | func (dq *Deque) Back() int { 121 | return dq.ll.Back().Value.(int) 122 | } 123 | 124 | func (dq *Deque) Len() int { 125 | return dq.ll.Len() 126 | } 127 | 128 | 129 | Linked List 130 | -------------------------------------------------- 131 | 132 | .. code-block:: go 133 | 134 | package main 135 | 136 | import "fmt" 137 | 138 | // 测试链表。在 redigo 里边使用到了链表作为 pool 的实现 139 | type IntList struct { 140 | count int 141 | // front,back 分别指向第一个和最后一个 node,或者是 nil。front.prev back.next 都是空 142 | front, back *Node 143 | } 144 | 145 | // 链表节点 146 | type Node struct { 147 | next, prev *Node 148 | } 149 | 150 | func (l *IntList) Count() int { 151 | return l.count 152 | } 153 | 154 | func (l *IntList) pushFront(node *Node) { 155 | node.next = l.front 156 | node.prev = nil 157 | if l.count == 0 { // note when list is empty 158 | l.back = node 159 | } else { 160 | l.front.prev = node 161 | } 162 | l.front = node 163 | l.count++ 164 | } 165 | 166 | func (l *IntList) popFront() { 167 | first := l.front 168 | l.count-- 169 | if l.count == 0 { 170 | l.front, l.back = nil, nil 171 | } else { 172 | first.next.prev = nil 173 | l.front = first.next 174 | } 175 | first.next, first.prev = nil, nil // clear first 176 | } 177 | 178 | func (l *IntList) popBack() { 179 | last := l.back 180 | l.count-- 181 | if l.count == 0 { 182 | l.front, l.back = nil, nil 183 | } else { 184 | last.prev.next = nil 185 | l.back = last.prev 186 | } 187 | last.prev, last.next = nil, nil 188 | } 189 | 190 | func (l *IntList) Print() { 191 | cur := l.front 192 | for cur != l.back { 193 | fmt.Println(cur) 194 | cur = cur.next 195 | } 196 | if l.back != nil { 197 | fmt.Println(l.back) 198 | } 199 | } 200 | 201 | 202 | Trie 字典树 203 | -------------------------------------------------- 204 | 205 | .. code-block:: go 206 | 207 | // Package main provides ... 208 | package main 209 | 210 | import "fmt" 211 | 212 | // https://golangbyexample.com/trie-implementation-in-go/ 213 | 214 | const ( 215 | ALBHABET_SIZE = 26 216 | ) 217 | 218 | type node struct { 219 | childrens [ALBHABET_SIZE]*node 220 | isWordEnd bool 221 | } 222 | 223 | type trie struct { 224 | root *node 225 | } 226 | 227 | func newTrie() *trie { 228 | return &trie{ 229 | root: &node{}, 230 | } 231 | } 232 | 233 | func (t *trie) insert(word string) { 234 | wordLength := len(word) 235 | current := t.root 236 | for i := 0; i < wordLength; i++ { 237 | idx := word[i] - 'a' 238 | if current.childrens[idx] == nil { 239 | current.childrens[idx] = &node{} 240 | } 241 | current = current.childrens[idx] 242 | } 243 | current.isWordEnd = true 244 | } 245 | func (t *trie) find(word string) bool { 246 | wordLength := len(word) 247 | current := t.root 248 | for i := 0; i < wordLength; i++ { 249 | idx := word[i] - 'a' 250 | if current.childrens[idx] == nil { 251 | return false 252 | } 253 | current = current.childrens[idx] 254 | } 255 | if current.isWordEnd { 256 | return true 257 | } 258 | return false 259 | } 260 | 261 | func main() { 262 | trie := newTrie() 263 | words := []string{"zhang", "wang", "li", "zhao"} 264 | for i := 0; i < len(words); i++ { 265 | trie.insert(words[i]) 266 | } 267 | toFind := []string{"zhang", "wang", "li", "zhao", "gong"} 268 | for i := 0; i < len(toFind); i++ { 269 | c := toFind[i] 270 | if trie.find(c) { 271 | fmt.Printf("word[%s] found in trie.\n", c) 272 | } else { 273 | fmt.Printf("word[%s] not found in trie\n", c) 274 | } 275 | } 276 | } 277 | 278 | Lru Cache (不是并发安全的,仅示例) 279 | -------------------------------------------------- 280 | 281 | .. code-block:: go 282 | 283 | package main 284 | 285 | import "container/list" 286 | 287 | type LRUCache struct { 288 | lis *list.List 289 | m map[int]*list.Element 290 | capacity int 291 | } 292 | 293 | // 包装成一个 struct 294 | type KV struct { 295 | Key int 296 | Val int 297 | } 298 | 299 | func Constructor(capacity int) LRUCache { 300 | return LRUCache{ 301 | lis: list.New(), 302 | m: make(map[int]*list.Element), 303 | capacity: capacity, 304 | } 305 | } 306 | 307 | func (this *LRUCache) Get(key int) int { 308 | if ele, ok := this.m[key]; ok { 309 | this.lis.MoveToFront(ele) 310 | return ele.Value.(*KV).Val 311 | } 312 | return -1 313 | } 314 | 315 | func (this *LRUCache) Put(key int, value int) { 316 | if ele, ok := this.m[key]; ok { 317 | ele.Value.(*KV).Val = value 318 | this.lis.MoveToFront(ele) 319 | return 320 | } 321 | 322 | ele := this.lis.PushFront(&KV{key, value}) 323 | this.m[key] = ele // map 保存的是 节点信息 324 | 325 | if this.lis.Len() > this.capacity { 326 | back := this.lis.Back() 327 | delete(this.m, back.Value.(*KV).Key) 328 | this.lis.Remove(back) 329 | } 330 | } 331 | 332 | OrderedMap (类似 python collections.OrderedDict) 333 | -------------------------------------------------- 334 | 模拟 python collections.OrderedDict 写的,可以方便的实现 lru 等。注意这里的 order 指的是 key 插入的顺序,不是指 key 字典序。 335 | 336 | .. code-block:: go 337 | 338 | package main 339 | 340 | import ( 341 | "container/list" 342 | "fmt" 343 | ) 344 | 345 | // 按照 key 插入顺序遍历 map,类似 python collections.OrderedDict。注意不是 key 的字典序,而是插入顺序 346 | type OrderedMap struct { 347 | m map[string]int 348 | me map[string]*list.Element 349 | ll *list.List // 记录 key order 350 | } 351 | 352 | func NewOrderedMap() *OrderedMap { 353 | return &OrderedMap{ 354 | m: make(map[string]int), 355 | me: make(map[string]*list.Element), 356 | ll: list.New(), 357 | } 358 | } 359 | 360 | func (o *OrderedMap) Set(k string, v int) { 361 | if _, found := o.m[k]; !found { 362 | e := o.ll.PushBack(k) 363 | o.me[k] = e 364 | } 365 | o.m[k] = v 366 | } 367 | 368 | func (o *OrderedMap) Exist(k string) bool { 369 | _, found := o.m[k] 370 | return found 371 | } 372 | 373 | func (o *OrderedMap) Get(k string) int { 374 | return o.m[k] 375 | } 376 | 377 | func (o *OrderedMap) Delete(k string) { 378 | delete(o.m, k) 379 | 380 | node := o.me[k] 381 | o.ll.Remove(node) 382 | delete(o.me, k) 383 | } 384 | 385 | func (o *OrderedMap) Len() int { 386 | return len(o.m) 387 | } 388 | 389 | // 按照 key 进入顺序返回 390 | func (o *OrderedMap) Keys() []string { 391 | keys := make([]string, o.ll.Len()) 392 | i := 0 393 | for e := o.ll.Front(); e != nil; e = e.Next() { 394 | keys[i] = e.Value.(string) 395 | i++ 396 | } 397 | return keys 398 | } 399 | 400 | Heap 堆 401 | -------------------------------------------------- 402 | go 自带了一个 ``container/heap`` 模块可以用来实现堆。 403 | 404 | .. code-block:: go 405 | 406 | // This example demonstrates an integer heap built using the heap interface. 407 | // A heap is a tree with the property that each node is the minimum-valued node in its subtree. 408 | // 可以用来实现优先级队列 priority queue 409 | package main 410 | 411 | import ( 412 | "container/heap" 413 | "fmt" 414 | ) 415 | 416 | // An IntHeap is a min-heap of ints. 417 | type IntHeap []int 418 | 419 | func (h IntHeap) Len() int { return len(h) } 420 | func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] } 421 | func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 422 | 423 | // 最后追加一个元素 424 | func (h *IntHeap) Push(x interface{}) { 425 | // Push and Pop use pointer receivers because they modify the slice's length, 426 | // not just its contents. 427 | *h = append(*h, x.(int)) 428 | } 429 | 430 | // 移除并且返回最后一个元素 431 | func (h *IntHeap) Pop() interface{} { 432 | old := *h 433 | n := len(old) 434 | x := old[n-1] 435 | *h = old[0 : n-1] 436 | return x 437 | } 438 | 439 | // This example inserts several ints into an IntHeap, checks the minimum, 440 | // and removes them in order of priority. 441 | func main() { 442 | h := &IntHeap{2, 1, 5} 443 | heap.Init(h) 444 | fmt.Println(h) 445 | heap.Push(h, 3) 446 | fmt.Println(h) 447 | fmt.Printf("minimum: %d\n", (*h)[0]) // h[0] 最小的元素 448 | for h.Len() > 0 { 449 | fmt.Printf("%d ", heap.Pop(h)) 450 | } 451 | } 452 | 453 | 454 | 参考: 455 | 456 | - https://github.com/emirpasic/gods 457 | 458 | -------------------------------------------------------------------------------- /go-note/concurrency_patterns.rst: -------------------------------------------------------------------------------- 1 | .. _go_concurrency_patterns: 2 | 3 | Go 并发设计模式 4 | ===================================================================== 5 | 6 | 并发编程常见问题 7 | -------------------------------------------------- 8 | 9 | 1. 资源共享问题。竞争条件(race condition), 死锁(deadlock),资源争用(contention) 10 | 11 | - 比如并发读写原生的 map 导致 panic 12 | - 读锁重入导致思索。获取读锁的协程,不要二次重入再去获取读锁 13 | 14 | 2. 协程管理 15 | 3. 通道使用 16 | 17 | 屏障模式(Barrier Mode) 18 | -------------------------------------------------- 19 | 屏障模式(Barrier Mode),用来阻塞goroutine直到聚合所有goroutine返回结果,可以用通道实现。使用场景: 20 | 21 | 1. 多个网络请求并发,聚合结果 22 | 2. 粗粒度任务拆分并发执行,聚合结果 23 | 24 | .. code-block:: go 25 | 26 | package main 27 | 28 | import ( 29 | "fmt" 30 | "io/ioutil" 31 | "net/http" 32 | "time" 33 | ) 34 | 35 | // 封装屏障模式响应的结构体(一般就是返回值和错误) 36 | type BarrierResponse struct { 37 | Err error 38 | Resp string 39 | Status int 40 | } 41 | 42 | // 执行单个请求,结果放入 channel 43 | func doRequest(out chan<- BarrierResponse, url string) { 44 | res := BarrierResponse{} 45 | client := http.Client{Timeout: time.Duration(3 * time.Second)} 46 | resp, err := client.Get(url) 47 | if resp != nil { 48 | res.Status = resp.StatusCode 49 | } 50 | if err != nil { 51 | res.Err = err 52 | out <- res 53 | return 54 | } 55 | 56 | b, err := ioutil.ReadAll(resp.Body) 57 | defer resp.Body.Close() 58 | if err != nil { 59 | res.Err = err 60 | out <- res 61 | return 62 | } 63 | res.Resp = string(b) 64 | out <- res // 结果放入通道 65 | } 66 | 67 | // 并发请求并聚合结果 68 | func Barrier(urls ...string) { 69 | n := len(urls) 70 | in := make(chan BarrierResponse, n) 71 | response := make([]BarrierResponse, n) 72 | defer close(in) 73 | 74 | for _, url := range urls { 75 | go doRequest(in, url) 76 | } 77 | 78 | var hasError bool 79 | for i := 0; i < n; i++ { 80 | resp := <-in 81 | if resp.Err != nil { 82 | fmt.Println("Error: ", resp.Err, resp.Status) 83 | hasError = true 84 | } 85 | response[i] = resp 86 | } 87 | if !hasError { 88 | for _, resp := range response { 89 | fmt.Println(resp.Status) 90 | } 91 | } 92 | } 93 | 94 | func main() { 95 | urls := []string{ 96 | "https://www.baidu.com", 97 | "https://www.weibo.com", 98 | "https://www.zhihu.com", 99 | } 100 | Barrier(urls...) 101 | } 102 | 103 | 未来模式(Future Mode) 104 | -------------------------------------------------- 105 | Future模式(也称为Promise Mode)。使用 ``fire-and-forget`` 方式,主进程不等子进程执行完就直接返回,然后等到未来执行完的时候再去获取结果。 106 | 未来模式中主goroutine不用等待子goroutine返回的结果,可以先去做其他事情,等未来需要子goroutine结果的时候再来取。 107 | 如果子goroutine还没有返回结果,则一直等待。以下简单的代码示例说明了该模式的原理: 108 | 109 | .. code-block:: go 110 | 111 | c := make(chan int) // future 112 | go func() { c <- f() }() // async 113 | value := <-c // await 114 | 115 | 可以针对 future 模式做一个统一的封装,方便后续使用,代码示例如下: 116 | 117 | .. code-block:: go 118 | 119 | /* https://github.com/golang-collections/go-datastructures/blob/59788d5eb259/futures/futures.go 120 | Package futures is useful for broadcasting an identical message to a multitude 121 | of listeners as opposed to channels which will choose a listener at random 122 | if multiple listeners are listening to the same channel. The future will 123 | also cache the result so any future interest will be immediately returned 124 | to the consumer. 125 | */ 126 | package main 127 | 128 | import ( 129 | "fmt" 130 | "sync" 131 | "time" 132 | ) 133 | 134 | // Completer is a channel that the future expects to receive 135 | // a result on. The future only receives on this channel. 136 | type Completer <-chan interface{} 137 | 138 | // Future represents an object that can be used to perform asynchronous 139 | // tasks. The constructor of the future will complete it, and listeners 140 | // will block on getresult until a result is received. This is different 141 | // from a channel in that the future is only completed once, and anyone 142 | // listening on the future will get the result, regardless of the number 143 | // of listeners. 144 | type Future struct { 145 | triggered bool // because item can technically be nil and still be valid 146 | item interface{} 147 | err error 148 | lock sync.Mutex 149 | wg sync.WaitGroup 150 | } 151 | 152 | // GetResult will immediately fetch the result if it exists 153 | // or wait on the result until it is ready. 154 | func (f *Future) GetResult() (interface{}, error) { 155 | f.lock.Lock() 156 | if f.triggered { 157 | f.lock.Unlock() 158 | return f.item, f.err 159 | } 160 | f.lock.Unlock() 161 | 162 | f.wg.Wait() 163 | return f.item, f.err 164 | } 165 | 166 | func (f *Future) setItem(item interface{}, err error) { 167 | f.lock.Lock() 168 | f.triggered = true 169 | f.item = item 170 | f.err = err 171 | f.lock.Unlock() 172 | f.wg.Done() 173 | } 174 | 175 | func listenForResult(f *Future, ch Completer, timeout time.Duration, wg *sync.WaitGroup) { 176 | wg.Done() 177 | select { 178 | case item := <-ch: 179 | f.setItem(item, nil) 180 | case <-time.After(timeout): 181 | f.setItem(nil, fmt.Errorf(`Timeout after %f seconds.`, timeout.Seconds())) 182 | } 183 | } 184 | 185 | // New is the constructor to generate a new future. Pass the completed 186 | // item to the toComplete channel and any listeners will get 187 | // notified. If timeout is hit before toComplete is called, 188 | // any listeners will get passed an error. 189 | func New(completer Completer, timeout time.Duration) *Future { 190 | f := &Future{} 191 | f.wg.Add(1) 192 | var wg sync.WaitGroup 193 | wg.Add(1) 194 | go listenForResult(f, completer, timeout, &wg) 195 | wg.Wait() 196 | return f 197 | } 198 | 199 | // 使用示例 200 | func main() { 201 | c := make(chan interface{}) 202 | 203 | go func() { 204 | time.Sleep(time.Second) 205 | c <- "hehe" 206 | }() 207 | 208 | f := New(c, time.Second*3) 209 | res, err := f.GetResult() 210 | fmt.Println(res, err) 211 | } 212 | 213 | 214 | 管道模式(Pipeline Mode) 215 | -------------------------------------------------- 216 | 也称作流水线模式,一般有以下几个步骤: 217 | 218 | 1. 流水线由一道道工序构成,每道工序通过通道把数据传递到下一个工序 219 | 2. 每道工序一般会对应一个函数,函数里有协程和通道,协程一般用于处理数据并把它放入通道中,每道工序会返回这个通道以供下一道工序使用 220 | 3. 最终要有一个组织者(示例中的main()函数)把这些工序串起来,这样就形成了一个完整的流水线,对于数据来说就是数据流 221 | 222 | .. code-block:: go 223 | 224 | // 以组装计算机为例。三道工序:配件采购(Buy)-> 配件组装(Build) -> 打包成品(Pack) 225 | func Buy(n int) <-chan string { 226 | out := make(chan string) 227 | go func() { 228 | defer close(out) 229 | for i := 1; i <= n; i++ { 230 | out <- fmt.Sprintf("配件%d", i) 231 | } 232 | }() 233 | return out 234 | } 235 | 236 | func Build(in <-chan string) <-chan string { 237 | out := make(chan string) 238 | go func() { 239 | defer close(out) 240 | for c := range in { 241 | out <- fmt.Sprintf("组装(%s)", c) 242 | } 243 | }() 244 | return out 245 | } 246 | 247 | func Pack(in <-chan string) <-chan string { 248 | out := make(chan string) 249 | go func() { 250 | defer close(out) 251 | for c := range in { 252 | out <- fmt.Sprintf("打包(%s)", c) 253 | } 254 | }() 255 | return out 256 | } 257 | 258 | func main() { 259 | accessories := Buy(6) 260 | computers := Build(accessories) 261 | packs := Pack(computers) 262 | for p := range packs { 263 | fmt.Println(p) 264 | } 265 | } 266 | 267 | .. code-block:: go 268 | 269 | package main 270 | 271 | import "fmt" 272 | 273 | // 工序 1:数组生成器 274 | func Generator(max int) <-chan int { 275 | out := make(chan int, 100) 276 | go func() { 277 | for i := 1; i <= max; i++ { 278 | out <- i 279 | } 280 | close(out) 281 | }() 282 | return out 283 | } 284 | 285 | // 工序 2:求整数的平方 286 | func Square(in <-chan int) <-chan int { 287 | out := make(chan int, 100) 288 | go func() { 289 | for v := range in { 290 | out <- v * v 291 | } 292 | close(out) 293 | }() 294 | return out 295 | } 296 | 297 | // 工序 3:求和 298 | func Sum(in <-chan int) <-chan int { 299 | out := make(chan int, 100) 300 | go func() { 301 | var sum int 302 | for v := range in { 303 | sum += v 304 | } 305 | out <- sum 306 | close(out) 307 | }() 308 | return out 309 | } 310 | 311 | func main() { 312 | arr := Generator(5) 313 | squ := Square(arr) 314 | sum := <-Sum(squ) 315 | fmt.Println(sum) 316 | } 317 | 318 | 319 | 扇出和扇入模式(Fan-out Fan-in) 320 | -------------------------------------------------- 321 | 扇出(Fan-out)是指多个函数可以从同一个通道读取数据,直到该通道关闭。扇入(Fan-in)是指一个函数可以从多个输入中读取数据并继续进行, 322 | 直到所有输入都关闭。扇出和扇入模式的方法是将输入通道多路复用到一个通道上,当所有输入都关闭时,该通道才关闭。 323 | 扇出的数据流向是发散传递出去,是输出流;扇入的数据流向是汇聚进来,是输入流。 324 | 325 | .. image:: ../_image/goweb/concurrency/扇出扇入.png 326 | 327 | .. code-block:: go 328 | 329 | // 扇入函数,把多个channel 中的数据发送到一个 channel 中 330 | func Merge(ins ...<-chan string) <-chan string { 331 | var wg sync.WaitGroup 332 | out := make(chan string) 333 | 334 | p := func(in <-chan string) { 335 | defer wg.Done() 336 | for c := range in { 337 | out <- c 338 | } 339 | } 340 | 341 | wg.Add(len(ins)) 342 | // 扇入 343 | for _, cs := range ins { 344 | go p(cs) 345 | } 346 | go func() { 347 | wg.Wait() 348 | close(out) 349 | }() 350 | return out 351 | } 352 | 353 | func main() { 354 | accessories := Buy(12) 355 | computers1 := Build(accessories) 356 | computers2 := Build(accessories) 357 | computers3 := Build(accessories) 358 | computers := Merge(computers1, computers2, computers3) 359 | packs := Pack(computers) 360 | for p := range packs { 361 | fmt.Println(p) 362 | } 363 | } 364 | 365 | 366 | 协程池模式 367 | -------------------------------------------------- 368 | 即便 go 的协程比较轻量,但是当需要操作大量 goroutine 的时候,依然有内存开销和 GC 的压力。可以考虑使用协程池减少频繁创建销毁协程的开销。 369 | 370 | .. code-block:: go 371 | 372 | package main 373 | 374 | import ( 375 | "fmt" 376 | "sync" 377 | "sync/atomic" 378 | ) 379 | 380 | // 任务处理器 381 | type TaskHandler func(interface{}) 382 | 383 | // 任务结构体 384 | type Task struct { 385 | Param interface{} 386 | Handler TaskHandler 387 | } 388 | 389 | // 协程池接口 390 | type WorkerPoolImpl interface { 391 | AddWorker() 392 | SendTask(Task) 393 | Release() 394 | } 395 | 396 | // 协程池 397 | type WorkerPool struct { 398 | wg sync.WaitGroup 399 | inCh chan Task 400 | } 401 | 402 | func (d *WorkerPool) AddWorker() { 403 | d.wg.Add(1) 404 | go func() { 405 | defer d.wg.Done() 406 | for task := range d.inCh { 407 | task.Handler(task.Param) 408 | } 409 | }() 410 | } 411 | 412 | func (d *WorkerPool) Release() { 413 | close(d.inCh) 414 | d.wg.Wait() 415 | } 416 | 417 | func (d *WorkerPool) SendTask(t Task) { 418 | d.inCh <- t 419 | } 420 | 421 | func NewWorkerPool(buffer int) WorkerPoolImpl { 422 | return &WorkerPool{ 423 | inCh: make(chan Task, buffer), 424 | } 425 | } 426 | 427 | func main() { 428 | bufferSize := 100 429 | var workerPool = NewWorkerPool(bufferSize) 430 | workers := 4 431 | for i := 0; i < workers; i++ { 432 | workerPool.AddWorker() 433 | } 434 | 435 | var sum int32 436 | testFunc := func(i interface{}) { 437 | n := i.(int32) 438 | atomic.AddInt32(&sum, n) 439 | } 440 | 441 | var i, n int32 442 | n = 100 443 | for ; i < n; i++ { 444 | task := Task{ 445 | i, 446 | testFunc, 447 | } 448 | workerPool.SendTask(task) 449 | } 450 | workerPool.Release() 451 | fmt.Println(sum) // 4950 452 | } 453 | 454 | 455 | 发布订阅模式 456 | -------------------------------------------------- 457 | 基于消息通知的并发设计模式。发送者发送消息,订阅者通过订阅感兴趣的主题(Topic) 接收消息。 458 | 459 | .. code-block:: go 460 | 461 | package main 462 | 463 | import ( 464 | "fmt" 465 | "strings" 466 | "time" 467 | ) 468 | 469 | import ( 470 | "sync" 471 | ) 472 | 473 | type ( 474 | //订阅者通道 475 | Subscriber chan interface{} 476 | //主题函数 477 | TopicFunc func(v interface{}) bool 478 | ) 479 | 480 | //发布者结构体 481 | type Publisher struct { 482 | // subscribers 是程序的核心,订阅者都会注册在这里, 483 | // publisher发布消息的时候也会从这里开始 484 | subscribers map[Subscriber]TopicFunc 485 | buffer int // 订阅者的缓冲区长度 486 | timeout time.Duration // publisher 发送消息的超时时间 487 | // m 用来保护 subscribers 488 | // 当修改 subscribers 的时候(即新加订阅者或删除订阅者)使用写锁 489 | // 当向某个订阅者发送消息的时候(即向某个 Subscriber channel 中写入数据),使用读锁 490 | m sync.RWMutex 491 | } 492 | 493 | //实例化 494 | func NewPublisher(publishTimeout time.Duration, buffer int) *Publisher { 495 | return &Publisher{ 496 | buffer: buffer, 497 | timeout: publishTimeout, 498 | subscribers: make(map[Subscriber]TopicFunc), 499 | } 500 | } 501 | 502 | //发布者订阅方法 503 | func (p *Publisher) Subscribe() Subscriber { 504 | return p.SubscribeTopic(nil) 505 | } 506 | 507 | //发布者订阅主题 508 | func (p *Publisher) SubscribeTopic(topic TopicFunc) Subscriber { 509 | ch := make(Subscriber, p.buffer) 510 | p.m.Lock() 511 | p.subscribers[ch] = topic 512 | p.m.Unlock() 513 | 514 | return ch 515 | } 516 | 517 | //Delete 删除掉某个订阅者 518 | func (p *Publisher) Delete(sub Subscriber) { 519 | p.m.Lock() 520 | defer p.m.Unlock() 521 | 522 | delete(p.subscribers, sub) 523 | close(sub) 524 | } 525 | 526 | //发布者发布 527 | func (p *Publisher) Publish(v interface{}) { 528 | p.m.RLock() 529 | defer p.m.RUnlock() 530 | 531 | var wg sync.WaitGroup 532 | // 同时向所有订阅者写消息,订阅者利用 topic 过滤消息 533 | for sub, topic := range p.subscribers { 534 | wg.Add(1) 535 | go p.sendTopic(sub, topic, v, &wg) 536 | } 537 | 538 | wg.Wait() 539 | } 540 | 541 | //Close 关闭 Publisher,删除所有订阅者 542 | func (p *Publisher) Close() { 543 | p.m.Lock() 544 | defer p.m.Unlock() 545 | 546 | for sub := range p.subscribers { 547 | delete(p.subscribers, sub) 548 | close(sub) 549 | } 550 | } 551 | 552 | //发送主题 553 | func (p *Publisher) sendTopic(sub Subscriber, topic TopicFunc, v interface{}, wg *sync.WaitGroup) { 554 | defer wg.Done() 555 | 556 | if topic != nil && !topic(v) { 557 | return 558 | } 559 | 560 | select { 561 | case sub <- v: 562 | case <-time.After(p.timeout): 563 | } 564 | } 565 | 566 | func main() { 567 | //实例化 568 | p := NewPublisher(100*time.Millisecond, 10) 569 | defer p.Close() 570 | 571 | // 订阅者订阅所有消息 572 | all := p.Subscribe() 573 | //订阅者仅订阅包含 golang 的消息 574 | golang := p.SubscribeTopic(func(v interface{}) bool { 575 | if s, ok := v.(string); ok { 576 | return strings.Contains(s, "golang") 577 | } 578 | return false 579 | }) 580 | 581 | //发布消息 582 | p.Publish("hello, world!") 583 | p.Publish("hello, golang!") 584 | 585 | //加锁 586 | var wg sync.WaitGroup 587 | wg.Add(2) 588 | 589 | //开启goroutine 590 | go func() { 591 | for msg := range all { 592 | _, ok := msg.(string) 593 | fmt.Println(ok) 594 | } 595 | wg.Done() 596 | }() 597 | 598 | //开启goroutine 599 | go func() { 600 | for msg := range golang { 601 | v, ok := msg.(string) 602 | fmt.Println(v) 603 | fmt.Println(ok) 604 | } 605 | wg.Done() 606 | }() 607 | 608 | p.Close() 609 | wg.Wait() 610 | } 611 | 612 | 参考:《Go 语言高级开发与实战》 613 | -------------------------------------------------------------------------------- /go-note/design_patterns.rst: -------------------------------------------------------------------------------- 1 | .. _go_design_patterns: 2 | 3 | Go 设计模式 4 | ===================================================================== 5 | 6 | 单例模式(Singleton) 7 | -------------------------------------------------- 8 | .. code-block:: go 9 | 10 | package main 11 | 12 | import ( 13 | "fmt" 14 | "sync" 15 | ) 16 | 17 | /* 18 | go 单例模式实现方式: 19 | 20 | 1. 使用 lock,为了并发安全,使用 lock + double check 21 | 2. 使用 sync.Once (推荐👍) 22 | 3. 使用 init()。The init function is only called once per file in a package, so we can be sure that only a single instance will be created 23 | 24 | 参考: 25 | 26 | - https://blog.cyeam.com/designpattern/2015/08/12/singleton 27 | - https://golangbyexample.com/all-design-patterns-golang/ 28 | - https://medium.com/golang-issue/how-singleton-pattern-works-with-golang-2fdd61cd5a7f 29 | */ 30 | 31 | var lock = &sync.Mutex{} 32 | 33 | type single struct { 34 | } 35 | 36 | var singleInstance *single 37 | 38 | func getInstance() *single { 39 | if singleInstance == nil { // 防止每次调用 getInstance 都频繁加锁 40 | lock.Lock() 41 | defer lock.Unlock() 42 | // double check. 如果超过一个goroutine进入这里,保证只有一个 goroutine 创建 43 | if singleInstance == nil { 44 | fmt.Println("Creting Single Instance Now") 45 | singleInstance = &single{} 46 | } else { 47 | fmt.Println("Single Instance already created-1") 48 | } 49 | } else { 50 | fmt.Println("Single Instance already created-2") 51 | } 52 | return singleInstance 53 | } 54 | 55 | func main() { 56 | for i := 0; i < 100; i++ { 57 | go getInstance() 58 | } 59 | // Scanln is similar to Scan, but stops scanning at a newline and 60 | // after the final item there must be a newline or EOF. 61 | fmt.Scanln() 62 | } 63 | 64 | 65 | .. code-block:: go 66 | 67 | // 推荐使用 once,实现更加简洁优雅 68 | var once sync.Once 69 | 70 | type single struct { 71 | } 72 | 73 | var singleInstance *single 74 | 75 | func getInstance() *single { 76 | once.Do(func() { 77 | fmt.Println("Creting Single Instance Now") 78 | singleInstance = &single{} 79 | }) 80 | return singleInstance 81 | } 82 | 83 | 参考: 84 | -------------------------------------------------- 85 | 86 | - `Go Patterns (github) `_ 87 | - `All Design Patterns in Go (Golang) `_ 88 | - `设计模式Golang实现 `_ 89 | 90 | -------------------------------------------------------------------------------- /go-note/index.rst: -------------------------------------------------------------------------------- 1 | ========================================== 2 | Golang 快速入门 Go For Pythonisa ❤️ 3 | ========================================== 4 | 5 | 6 | .. toctree:: 7 | :glob: 8 | :maxdepth: 2 9 | 10 | web 11 | tricks 12 | mistakes 13 | optimize 14 | algorithms 15 | design_patterns 16 | concurrency_patterns 17 | -------------------------------------------------------------------------------- /go-note/mistakes.rst: -------------------------------------------------------------------------------- 1 | .. _gomistakes: 2 | 3 | Go 常见错误 4 | ===================================================================== 5 | 6 | 100 Go Mistakes and How to Avoid Them 7 | --------------------------------------------------------------- 8 | 《100 Go Mistakes and How to Avoid Them》这本书的一些重要内容笔记。 9 | 10 | 2 Code and project organization 11 | -------------------------------------------------- 12 | 13 | 2.1 #1: Unintended variable shadowing 14 | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 15 | 16 | .. code-block:: go 17 | 18 | var client *http.Client // 以下代码运行后,client 依然还是 nil 19 | if tracing { 20 | // Declares a client variable 21 | client, err := createClientWithTracing() // 新创建的同名变量覆盖外层同名的 client 22 | if err != nil { 23 | return err 24 | } 25 | log.Println(client) 26 | } else { 27 | client, err := createDefaultClient() 28 | if err != nil { 29 | return err 30 | } 31 | log.Println(client) 32 | } 33 | 34 | // 解决方式:不要使用 := 赋值一个新变量,需要声明一下 client 和 err 35 | var client *http.Client 36 | var err error // Declares an err variable 37 | if tracing { 38 | client, err = createClientWithTracing() 39 | if err != nil { 40 | return err 41 | } 42 | } else { 43 | // Same logic 44 | } 45 | 46 | 2.5 #5: Interface pollution 47 | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 48 | When to use interfaces: 49 | 50 | - Common behavior 51 | - Decoupling 52 | - Restricting behavior 53 | -------------------------------------------------------------------------------- /go-note/optimize.rst: -------------------------------------------------------------------------------- 1 | .. _optimize: 2 | 3 | Go 性能优化 4 | ===================================================================== 5 | 6 | 常见的优化手段 7 | --------------------------------------------------------------- 8 | - 池化技术。比如 sync.Pool 对象池(频繁分配同一类型的多个对象);协程池(Jeffail/tunny, panjf2000/ants);连接池(buraksezer/connpool);内存池等 9 | - 复用对象。比如 string2bytes 10 | - 优化反射。https://github.com/goccy/go-reflect 11 | - 减小锁消耗。减小锁粒度;使用原子操作(atomic)代替互斥锁。读多写少的场景使用读写锁(RWMutex) 12 | - 减小锁竞争。比如 go-cache/bigcache 等缓存库都是通过分片功能减少锁竞争 13 | 14 | 优化建议: 15 | 16 | - 预分配内存。slice/map 如果预知容量信息,初始化应该提供,减少内存重分配和复制元素的消耗。 ``make([]int, 0, cap); make(map[int]int, cap)`` 17 | - 使用 strings.Builder 拼接大量字符串 18 | - 使用空的结构体作为占位符,空结构体 struct{} 不占内存。 比如实现 set 使用 map[string]struct{}{} 19 | - 使用 atomic 代替 sync 包。atomic 包的操作指令级支持 20 | - struct 合理的布局可以减少内存占用,提升性能(内存对齐)。一个简单的规则是按照字节大小倒序排列。 21 | - 尽量减少逃逸,将变量限制在栈上,减少堆变量分配,降低 GC 成本,提高程序性能 22 | 23 | - 局部切片尽可能确定长度或者容量。长度和容量尽可能是一个常量,如果是一个变量go无法判断其大小会逃逸到堆上分配 24 | - 函数返回指针可以减少拷贝但是会导致内存分配逃逸到堆中。如果要修改原对象或者返回内存比较大的对象,返回指针。对于只读或 25 | 者占用内存小的对象,可以直接返回值。 26 | - 如果返回值的类型可以确定,就不要用 interface{} 27 | 28 | string 与 []byte 互转 29 | --------------------------------------------------------------- 30 | 利用了底层 string 和 byte slice 实现的技巧,如果需要大量互转可以使用这种方式。 31 | 32 | .. code-block:: go 33 | 34 | /* 35 | type StringHeader struct { // reflect.StringHeader 36 | Data uintptr 37 | Len int 38 | } 39 | type SliceHeader struct { // reflect.SliceHeader 40 | Data uintptr 41 | Len int 42 | Cap int 43 | } 44 | */ 45 | 46 | // NOTE:注意之后不要修改 string 47 | func str2bytes(s string) []byte { 48 | x := (*[2]uintptr)(unsafe.Pointer(&s)) 49 | b := [3]uintptr{x[0], x[1], x[1]} 50 | return *(*[]byte)(unsafe.Pointer(&b)) 51 | } 52 | 53 | func bytes2str(b []byte) string { 54 | return *(*string)(unsafe.Pointer(&b)) 55 | } 56 | 57 | 58 | - `Go性能优化技巧 `_ 59 | - `Golang 中 string 与 []byte 互转优化 `_ 60 | - `Go 语言高性能编程 `_ 61 | 62 | 大量字符串拼接 63 | --------------------------------------------------------------- 64 | 字符串在 go 中是不可变对象。大量字符串(一般超过 5 个字符串)拼接不要用 + ,使用 bytes.Buffer 或者 strings.Builder。 65 | 不过对于个数比较少的字符串拼接,直接用 + 效率也很高,比 fmt.Sprintf 更快,所以少量字符串拼接可以放心使用 + 。 66 | 67 | .. code-block:: go 68 | 69 | // bytes.Buffer 70 | package main 71 | 72 | import ( 73 | "fmt" 74 | "bytes" 75 | ) 76 | 77 | func main() { 78 | var b bytes.Buffer 79 | 80 | b.WriteString("abc") 81 | b.WriteString("def") 82 | 83 | fmt.Println(b.String()) // abcdef 84 | } 85 | 86 | .. code-block:: go 87 | 88 | // strings.Builder 89 | package main 90 | 91 | import ( 92 | "fmt" 93 | "strings" 94 | ) 95 | 96 | func main() { 97 | var sb strings.Builder 98 | sb.WriteString("First") 99 | sb.WriteString("Second") 100 | fmt.Println(sb.String()) // FirstSecond 101 | } 102 | 103 | - `concatenate strings in golang `_ 104 | - `How to efficiently concatenate strings in go `_ 105 | 106 | 107 | 更快的随机数 108 | --------------------------------------------------------------- 109 | Go 内置的 rand.Int()在生成随机数时,为了并发安全底层使用了锁,在高并发常见下会有性能问题。 110 | 可以使用 github.com/valyala/fastrand 等三方库替换。 111 | 112 | 113 | 更快的Json序列化 114 | --------------------------------------------------------------- 115 | 可以使用字节开源的 sonic 库 https://github.com/bytedance/sonic 替换内置的 json 116 | 117 | 118 | 伪共享问题(false sharing) 119 | --------------------------------------------------------------- 120 | 如果并发更新一个结构体的字段,我们可以通过填充空字节防止字段被 cpu 缓存到一个 cache line 单位中,需要不断同步降低效率。 121 | 可以在 https://github.com/uber-go/ratelimit 中找到一个例子: 122 | 123 | .. code-block:: go 124 | 125 | type leakyBucketLimiter struct { 126 | state unsafe.Pointer // 是一个状态的指针,用于存储上一次的执行的时间,以及需要 sleep 的时间 127 | 128 | //lint:ignore U1000 Padding is unused but it is crucial to maintain performance 129 | // of this rate limiter in case of collocation with other frequently accessed memory. 130 | padding [56]byte // cache line size - state pointer size = 64 - 8; created to avoid false sharing.(伪共享) 131 | // cpu cache 一般是以 cache line 为单位的,在 64 位的机器上一般是 64 字节 132 | // 所以如果我们高频并发访问的数据小于 64 字节的时候就可能会和其他数据一起缓存,其他数据如果出现改变就会导致 cpu 认为缓存失效,这就是 false sharing 133 | // 所以在这里为了尽可能提高性能,填充了 56 字节的无意义数据,因为 state 是一个指针占用了 8 个字节,所以 64 - 8 = 56 134 | 135 | perRequest time.Duration // perRequest = 1s / rate,每个请求间隔 1s/perRequest 136 | maxSlack time.Duration // 松弛时间,也就是可以允许的突发流量的大小,默认是 Pre / 10 137 | } 138 | 139 | 140 | 正确设置容器 CPU 配额 141 | --------------------------------------------------------------- 142 | 容器中运行 Go 程序需要正确设置 GOMAXPROCS,推荐使用 https://github.com/uber-go/automaxprocs 这个库,直接一行代码就可以。 143 | ``import _ "go.uber.org/automaxprocs"`` 144 | 145 | 146 | 日志延迟序列化 147 | --------------------------------------------------------------- 148 | 经常需要在日志里打印 json 数据,但是免不了序列化的开销。即使你只在 debug 级别下打印,还是要先序列化参数之后传入数据。 149 | 可以使用延迟序列化的方式,这样正式环境下的 debug 日志不会真正序列化,减少 cpu 开销。 150 | 151 | .. code-block:: go 152 | 153 | package main 154 | 155 | import ( 156 | "time" 157 | 158 | "xxxx/logs" // 你们用的日志库 159 | "github.com/bytedance/sonic" 160 | ) 161 | 162 | func GetLog(data interface{}) string { 163 | if log, err := sonic.MarshalString(data); err == nil { 164 | return log 165 | } 166 | return "" 167 | } 168 | 169 | // 延迟序列化,避免 debug 模式下序列化 json 开销 170 | type LazyInfo struct { 171 | Data any 172 | } 173 | 174 | func (l *LazyInfo) String() string { 175 | dataStr, err := sonic.MarshalString(l.Data) 176 | if err != nil { 177 | panic(err) 178 | } 179 | return dataStr 180 | } 181 | 182 | func NewLazyInfo(d any) *LazyInfo { 183 | return &LazyInfo{Data: d} 184 | } 185 | 186 | type S struct { 187 | a string 188 | b string 189 | } 190 | 191 | func init() { 192 | // logs.SetLevel(logs.LevelDebug) // 测试环境 193 | logs.SetLevel(logs.LevelInfo) // 正式环境 194 | } 195 | 196 | func main() { 197 | s := S{a: "a", b: "b"} 198 | logs.Info("info getLog:%s", GetLog(s)) 199 | logs.Debug("debug getLog:%s", GetLog(s)) 200 | 201 | logs.Info("info lazyInfo:%s", NewLazyInfo(s)) // 正式环境使用info 202 | logs.Debug("debug lazyInfo:%s", NewLazyInfo(s)) // 正式环境使用 info 级别后,debug 函数参数不会真序列化,减少开销 203 | 204 | time.Sleep(time.Second) 205 | } 206 | 207 | 208 | 使用 context cache 避免重复下游调用(调用放大) 209 | --------------------------------------------------------------- 210 | TODO 211 | -------------------------------------------------------------------------------- /idiom/idiom.rst: -------------------------------------------------------------------------------- 1 | .. _idiom: 2 | 3 | Write Idiom Python 4 | ===================================================================== 5 | 6 | 7 | Python支持链式比较 8 | --------------------------------------------------------------- 9 | .. code-block:: python 10 | 11 | # bad 12 | a = 5 13 | if a > 1 and a < 7: 14 | pass 15 | # good 16 | if 1 < a < 7: 17 | pass 18 | 19 | 20 | Python交换变量 21 | --------------------------------------------------------------- 22 | .. code-block:: python 23 | 24 | # bad 25 | x = 10 26 | y = 5 27 | tmp = x 28 | x = y 29 | y = tmp 30 | 31 | # good 32 | x = 10 33 | y = 5 34 | x, y = y, x 35 | 36 | 37 | Python中替代三目运算符?: 38 | --------------------------------------------------------------- 39 | .. code-block:: python 40 | 41 | # bad 42 | a = 10 43 | b = 5 44 | if a > b: 45 | c = a 46 | else: 47 | c = b 48 | # good 49 | c = a if a > b else b 50 | 51 | 52 | 拼接字符列表时,用join方法去实现 53 | --------------------------------------------------------------- 54 | .. code-block:: python 55 | 56 | # bad 57 | 58 | 59 | 格式化字符时多使用format函数 60 | --------------------------------------------------------------- 61 | .. code-block:: python 62 | 63 | # bad 64 | name = "tony" 65 | age = 100 66 | str = "myname : " + name + " my age : " + str(age) 67 | str1 = "myname : %s my age : %d" % (name, age) 68 | # good 69 | str2 = "myname : {} my age {}".format(name, age) 70 | 71 | 72 | 使用列表或者字典comprehension 73 | --------------------------------------------------------------- 74 | .. code-block:: python 75 | 76 | # bad 77 | mylist = range(20) 78 | odd_list = [] 79 | for e in mylist: 80 | if e % 2 == 1: 81 | odd_list.append(e) 82 | # good 83 | odd_list = [e for e in mylist if e % 2 == 1] 84 | 85 | # bad 86 | user_list = [{'name': 'lucy', 'email': 'lucy@g.com'}, {'name': 'lily', 'email': 'lily@g.com'}] 87 | user_email = {} 88 | for user in user_list: 89 | if 'email' in user: 90 | user_email[user['name']] = user['email'] 91 | # good 92 | {user['name']: user['email'] for user in user_list if 'email' in user} 93 | 94 | 95 | 条件判断时,避免直接和True, False, None进行比较(==) 96 | --------------------------------------------------------------- 97 | .. code-block:: python 98 | 99 | # bad 100 | if l == []: 101 | pass 102 | # good 103 | if l: # 实际调用l.__len__() == 0 104 | pass 105 | 106 | # bad 107 | if something == None: 108 | # good, None 是单例对象 109 | if something is None: 110 | 111 | 112 | 使用enumerate代替for循环中的index变量访问 113 | --------------------------------------------------------------- 114 | .. code-block:: python 115 | 116 | # bad 117 | my_container = ['lily', 'lucy', 'tom'] 118 | index = 0 119 | for element in my_container: 120 | print '{} {}'.format(index, element) 121 | index += 1 122 | 123 | # good 124 | for index, element in enumerate(my_container): 125 | print '%d %s' % (index, element) 126 | 127 | 128 | 避免使用可变(mutable)变量作为函数参数的默认初始化值 129 | --------------------------------------------------------------- 130 | .. code-block:: python 131 | 132 | # bad 133 | def function(l = []): 134 | l.append(1) 135 | return l 136 | 137 | print function() 138 | print function() 139 | print function() 140 | 141 | # print 142 | [1] 143 | [1, 1] 144 | [1, 1, 1] 145 | 146 | # good 使用None作为可变对象占位符 147 | def function(l=None): 148 | if l is None: 149 | l = [] 150 | l.append(1) 151 | return l 152 | 153 | 154 | 一切皆对象 155 | --------------------------------------------------------------- 156 | .. code-block:: python 157 | 158 | # bad 159 | def print_addition_table(): 160 | for x in range(1, 3): 161 | for y in range(1, 3): 162 | print(str(x + y) + '\n') 163 | 164 | def print_subtraction_table(): 165 | for x in range(1, 3): 166 | for y in range(1, 3): 167 | print(str(x - y) + '\n') 168 | 169 | def print_multiplication_table(): 170 | for x in range(1, 3): 171 | for y in range(1, 3): 172 | print(str(x * y) + '\n') 173 | 174 | def print_division_table(): 175 | for x in range(1, 3): 176 | for y in range(1, 3): 177 | print(str(x / y) + '\n') 178 | 179 | print_addition_table() 180 | print_subtraction_table() 181 | print_multiplication_table() 182 | print_division_table() 183 | 184 | # good, python一切都是对象,可以函数作为参数,类似技巧可以用来简化代码 185 | import operator as op 186 | 187 | def print_table(operator): 188 | for x in range(1, 3): 189 | for y in range(1, 3): 190 | print(str(operator(x, y)) + '\n') 191 | 192 | for operator in (op.add, op.sub, op.mul, op.div): 193 | print_table(operator) 194 | 195 | 196 | 防御式编程EAFP vs LBYL 197 | --------------------------------------------------------------- 198 | * EAFP:easier to ask forgiveness than permission 199 | * LBYL:look before you leap 200 | 201 | EAFP可以理解成一切按正常的逻辑编码,不用管可能出现的错误,等出了错误再说;而LBYL就是尽可能每写一行代码,都要提前考虑下当前的前置条件是否成立; 202 | 203 | .. code-block:: python 204 | 205 | # LBYL 206 | def getPersonInfo(person): 207 | if person == None: 208 | print 'person must be not null!' 209 | print person.info 210 | 211 | # EAFP 212 | def getPersonInfo(person): 213 | try: 214 | print person.info 215 | except NameError: 216 | print 'person must be not null!' 217 | 218 | 其实用EAFP风格的代码最大的好处是代码逻辑清晰,而LBYL会导致本来两句话说清楚的事,往往因为穿插了很多条件检查的语句使代码逻辑变得混乱。Python社区更提倡EAFP形式的。另外还有一个原因,在高并发场景下, if条件如果是个表达式,会造成一致性问题,这个时候必须用EAFP形式。这个可以参考Glow团队的技术博客[Glow cache structure](http://tech.glowing.com/cn/glow-cache-structure). 219 | 220 | 221 | 用dict对象完成switch...case...的功能 222 | --------------------------------------------------------------- 223 | 224 | .. code-block:: python 225 | 226 | # bad 227 | def apply_operation(left_operand, right_operand, operator): 228 | if operator == '+': 229 | return left_operand + right_operand 230 | elif operator == '-': 231 | return left_operand - right_operand 232 | elif operator == '*': 233 | return left_operand * right_operand 234 | elif operator == '/': 235 | return left_operand / right_operand 236 | # good 237 | def apply_operation(left_operand, right_operand, operator): 238 | import operator as op 239 | operator_mapper = {'+': op.add, '-': op.sub, '*': op.mul, '/': op.truediv} 240 | return operator_mapper[operator](left_operand, right_operand) 241 | 242 | 243 | 244 | 访问tuple的数据项时,可以用namedtuple代替index的方式访问 245 | --------------------------------------------------------------- 246 | 247 | .. code-block:: python 248 | 249 | # bad 250 | rows = [('lily', 20, 2000), ('lucy', 19, 2500)] 251 | for row in rows: 252 | print '{}`age is {}, salary is {} '.format(row[0], row[1], row[2]) 253 | 254 | # good 255 | from collections import namedtuple 256 | Employee = namedtuple('Employee', 'name, age, salary') 257 | for row in rows: 258 | employee = Employee._make(row) 259 | print '{}`age is {}, salary is {} '.format(employee.name, employee.age, employee.salary) 260 | 261 | 262 | 263 | 用isinstance来判断对象的类型 264 | --------------------------------------------------------------- 265 | 因为在python中定义变量时,不用像其它静态语言,如java, 要指定其变量数据类型,如int = 4. 但是这并不意味在python中没有数据类型,只是一个变量的数据类型是在运行的时候根据具体的赋值才最终确定。比如下面的代码是计算一个对象的长度值,如果是序列类型(str,list,set,dict)的, 直接调用len方法,如果是True, False, None则返回1,如果是数值的,则返回其int值. 266 | 267 | .. code-block:: python 268 | 269 | # bad 270 | def get_size(some_object): 271 | try: 272 | return len(some_object) 273 | except TypeError: 274 | if some_object in (True, False, None): 275 | return 1 276 | else: 277 | return int(some_object) 278 | 279 | print(get_size('hello')) 280 | print(get_size([1, 2, 3, 4, 5])) 281 | print(get_size(10.0)) 282 | 283 | # good 284 | def get_size(some_object): 285 | if isinstance(some_object, (list, dict, str, tuple)): 286 | return len(some_object) 287 | elif isinstance(some_object, (bool, type(None))): 288 | return 1 289 | elif isinstance(some_object, (int, float)): 290 | return int(some_object) 291 | 292 | 293 | 用with管理操作资源的上下文环境 294 | --------------------------------------------------------------- 295 | 在一个比较典型的场景里,如数据库操作,我们操作connection时一般要正常关闭连接,而不管是正常退出还是异常退出。如下: 296 | 297 | .. code-block:: python 298 | 299 | # bad 300 | class Connection(object): 301 | def execute(self, sql): 302 | raise Exception('ohoh, exception!') 303 | 304 | def close(self): 305 | print 'closed the Connection' 306 | 307 | try: 308 | conn = Connection() 309 | conn.execute('select * from t_users') 310 | finally: 311 | conn.close() 312 | 313 | # good 314 | class Connection(object): 315 | def execute(self, sql): 316 | raise Exception('ohoh, exception!') 317 | 318 | def close(self): 319 | print 'closed the Connection' 320 | 321 | def __enter__(self): 322 | return self 323 | 324 | def __exit__(self, errorType, errorValue, error): 325 | self.close() 326 | 327 | with Connection() as conn: 328 | conn.execute('select * from t_users') 329 | 330 | 331 | 使用generator返回耗费内存的对象 332 | --------------------------------------------------------------- 333 | 334 | .. code-block:: python 335 | 336 | # bad 337 | def f(): 338 | # ... 339 | return biglist 340 | 341 | # good 342 | def f(): 343 | # ... 344 | for i in biglist: 345 | yield i 346 | 347 | 348 | 更多资源: 349 | --------------------------------------------------------------- 350 | 351 | * `《分享书籍[writing idiomatic python ebook]》 `_ 352 | * `《Python 3 Patterns, Recipes and Idioms》 `_ 353 | * `《30个有关Python的小技巧》 `_ 354 | * `《Hidden features of Python》 `_ 355 | * `《Python程序员的10个常见错误》 `_ 356 | * `《Python高级编程slide》 `_ 357 | * `《Effective Python》 `_ 358 | * `《编写高质量代码:改善Python程序的91个建议》 `_ 359 | * `《Code Like a Pythonista: Idiomatic Python》 `_ 360 | * `《The Little Book of Python Anti-Patterns》 `_ 361 | -------------------------------------------------------------------------------- /index.rst: -------------------------------------------------------------------------------- 1 | .. include:: README.rst 2 | 3 | .. include:: meta.rst 4 | 5 | .. toctree:: 6 | :glob: 7 | :maxdepth: 2 8 | 9 | base/* 10 | go-note/index 11 | codingstyle/* 12 | debug/index 13 | codingtools/* 14 | idiom/* 15 | design/* 16 | algorithms/* 17 | database/index 18 | skillstack/index 19 | devtools/index 20 | python-note/index 21 | microservice_distribute/index 22 | tracing/index 23 | memo/* 24 | * 25 | -------------------------------------------------------------------------------- /meta.rst: -------------------------------------------------------------------------------- 1 | 如何编译 reST 文档 2 | ------------------ 3 | 4 | reST 文档的编译依赖 make 和 sphinx,安装完依赖后在文档的根目录执行 5 | ``make html`` 构建 HTML 文档,如无错误即可在 ``_build/html`` 目录中生成对应的 HTML 文件, 6 | 可以在浏览器中直接打开 ``_build/html/index.html`` 预览生成的 7 | HTML。或者用python起一个本地的server查看。 8 | 9 | 本文档托管在 ReadTheDocs,文档合并之主分支后将会自动构建,预览请访问 `RTFD `_ 。 10 | -------------------------------------------------------------------------------- /microservice_distribute/index.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | 微服务/分布式 web 组件 3 | =================================== 4 | 5 | 6 | .. toctree:: 7 | :glob: 8 | :maxdepth: 2 9 | 10 | * 11 | -------------------------------------------------------------------------------- /microservice_distribute/library.rst: -------------------------------------------------------------------------------- 1 | .. _library: 2 | 3 | ========================================= 4 | 云原生时代-微服务/分布式系统组件 5 | ========================================= 6 | 7 | 介绍一些微服务中的常见问题,包括常见概念,中间件,开源系统等,佛系更新中。 8 | 9 | 限流器 10 | ---------------------- 11 | 12 | 某些业务需要针对 IP 或者用户来进行一个限流操作,减少恶意请求,减轻服务器压力或者根据业务需求来进行频率控制。 13 | 限流可以在很多层面去做,比如 nginx+lua, API Gateway,或者在接口层直接来做,一般可以结合 redis 来做为计数器。 14 | 解决高并发问题有一下一些实现方式,可以根据业务场景选择: 15 | 16 | - 扩容 17 | - 静态化(cdn) 18 | - 限流 19 | - 缓存 20 | - 队列(削峰,解耦) 21 | 22 | 常见的几种限流算法有计数器算法、漏桶算法、令牌桶算法。 23 | 24 | - token bucket。令牌桶算法。允许突发流量。https://github.com/juju/ratelimit 或者 go 自带的 https://github.com/golang/time 25 | - leaky bucket。漏桶算法。以恒定速率处理(恒定速率漏水) https://github.com/uber-go/ratelimit 26 | - redis incr/expire。最简单的一种方式,通过 redis 针对用户或者 ip key 来计数,可以加上用户信息作为 key 27 | - redis zset。可以实现基于时间窗口来限流。不过不适合短期内大量 qps 限流,适合用户行为限流 28 | 29 | 你可以在网上很方便的搜索 『rate limiter』 来找到对应的实现。 30 | 31 | - `分布式系统高可用实战之限流器(Go 版本实现) `_ 32 | - `5种常见限流算法 `_ 33 | 34 | 35 | 断路器/熔断器(Circuit Breaker) 36 | ------------------------------------------- 37 | 38 | 在分布式系统中,为了防止级联错误,导致服务雪崩,经常需要使用断路器来保护系统。断路器原理如下: 39 | 40 | .. image:: ../_image/microservice_distribute/熔断器原理.png 41 | 42 | Netflix 开源的 Hystrix 是比较流行的开源实现,对应的有各种其他语言的实现,比如 Hystrix-go 43 | 44 | 参考: 45 | 46 | - `Circuit Breaker and Retry `_ 47 | 48 | 49 | 分布式 id 生成器(发号器) 50 | ------------------------------- 51 | 单机 mysql 一般我们直接使用 mysql 提供的自增 id 作为主键,但是分布式系统下需要别的算法来保证多机 mysql 全局自增唯一 id。 52 | 一般有如下一些实现方式,各有优劣,可以根据自己的业务灵活选取: 53 | 54 | - 发号器(snowflake 算法) 55 | - uuid 56 | 57 | 一般来说单调递增整形 id 对于索引更加友好,所以一般我们可以使用 snowflake 之类的算法来分配 id,其大致原理如下: 58 | 59 | .. image:: ../_image/microservice_distribute/slowflake.png 60 | 61 | 网上依旧很多开源实现,你可以搜索到一些 snowflake 的 golang 实现,注意依赖时间戳有些可能有时钟回拨问题。 62 | 63 | - `bwmarrin/snowflake `_ 64 | - `9种分布式ID生成方式 `_ 65 | 66 | RPC 67 | ---------------------- 68 | 在分布式系统中,不同业务模块之间可以通过消息队列或者 rpc 来进行通信。 69 | 70 | 71 | 服务注册与发现 72 | ---------------------- 73 | 74 | - 注册: 服务启动的时候通过某种形式比如 http 请求、消息等将自己的信息通知到服务注册中心(zookeeper/consul等) 75 | - 维护: 健康检查、异常剔除 76 | - 发现: 服务名称获取 ip 等信息。高可用(本机文件缓存);高性能(内存缓存);负载均衡(优先级) 77 | 78 | 常用组件(倾向于 AP 模型优先保证可用性): 79 | 80 | - Consul 81 | - Etcd 82 | - Zookeeper: CP 模型 83 | - Eureka: AP 模型 84 | 85 | .. image:: ../_image/microservice_distribute/服务发现对比.png 86 | 87 | 微服务网关 88 | ---------------------- 89 | 入口网关的功能: 90 | 91 | - 协议转换。为客户端提供统一的接入地址和协议,屏蔽掉后端服务不同的协议细节 92 | - 植入服务熔断、服务降级、流量控制、分流控制等服务治理相关的策略 93 | - 认证和授权。统一处理不同端的认证和授权,为后端服务屏蔽掉认证细节 94 | - 黑白名单限制 95 | 96 | 常见组件: 97 | 98 | - Nginx 99 | - Netflix Zuul 100 | - Kong 101 | 102 | 配置中心 103 | ---------------------- 104 | - Apollo: https://github.com/ctripcorp/apollo 105 | - Spring Cloud Config 106 | - Disconf 107 | 108 | 健康检查 109 | ---------------------- 110 | 111 | 日志聚合(ELK) 112 | ---------------------- 113 | 114 | 分布式跟踪(tracing) 115 | ---------------------- 116 | 117 | 异常跟踪 118 | ---------------------- 119 | 分布式系统中,和追踪日志一样也会遇到集中收集程序异常的问题。这里推荐笔者公司常用的一个系统叫做 sentry。它可以收集 120 | 和展示比如 Python 的异常或者 golang 的 error并收集上下文,方便我们快速排查程序中的严重问题。 121 | 122 | 应用程序指标 123 | ---------------------- 124 | 125 | 分布式锁 126 | ---------------------- 127 | 单机上可以使用 go 提供的 sync 包加锁,分布式情况下一般有几种方式: 128 | 129 | - redis: 借助 setnx。性能较高 130 | - Redlock算法: https://github.com/go-redsync/redsync 131 | - zookpeer: 适合分布式调度,不适合高频率持有时间短的抢锁场景 132 | - etcd 133 | 134 | 参考: 135 | 136 | - `彻底理解分布式锁原理并附上常用的分布式锁实现 `_ 137 | 138 | 消息队列 139 | ---------------------- 140 | 消息队列在分布式系统中一般用在异步解耦、削峰填谷等场景。消息队列的核心模型由生产者消费者和消息中间件(Broker)组成。 141 | 常用的开源解决方案有ActiveMQ、RabbitMQ、Kafka、RocketMQ和近年比较火的Pulsar。 142 | 但是消息会有延迟、乱序、丢失等问题,需要根据业务做好设计和取舍。 143 | 144 | 场景:系统崩溃;服务处理能力受限;链路耗时长尾请求;日志处理 145 | 146 | 延时队列(延迟队列) 147 | ---------------------- 148 | 在分布式系统中经常需要触发一些延后执行的任务,比如用户下单超过30 分钟未支付取消订单、定时给预定会议的人员发送消息、外卖下单后提醒小哥即将超时, 149 | 这个时候一般会使用到延时队列。延时队列很像是一种以时间为权重的堆结构。常见的实现方式是使用 redis zset/死信队列/时间轮/多层时间轮等。 150 | 从调研结果来看,很多方案和框架都是使用的基于 redis 实现。 151 | 152 | - 定时轮询数据库。只适合非常小规模的业务比如一些公司内部系统,拿出所有任务扫一遍执行到期任务。 153 | - redis zset/redis过期回调。把topic作为key,时间作为score加入到 zset,定时器通过 ZREANGEBYSCORE 查询 zset 中 score 最小的元素拿出来执行。(防止大 key 一般可能分散多个zset) 154 | - RabbitMQ: 依赖 TTL 和死信队列实现延迟队列效果。(需要有熟悉的运维支持) 155 | - RocketMQ 支持延时消息。消息延迟级别分别为1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,共18个级别。 156 | - kafka: 用 kafka topic 模拟死信队列(不过不太优雅) 157 | - 时间轮/多级时间轮: 在 kafka/netty 内部实现中有用到 158 | 159 | 有一些语言框架直接帮我们实现好了,也可以直接拿来用,一般需要一个消息队列作为broker。现有方案: 160 | 161 | - celery: python 社区常用的异步任务框架,支持定时、延时任务 162 | - machinery: golang 社区的 celery,支持延时任务 163 | - LMSTFY: 美图开源的基于 go和redis 实现的任务队列 164 | - asynq: https://github.com/hibiken/asynq 基于go redis的简单高效的任务队列 165 | 166 | 参考: 167 | 168 | - `你真的知道怎么实现一个延迟队列吗 `_ 169 | - https://juejin.cn/post/7052894117105238053 延时消息常见实现方案 170 | 171 | 分布式缓存 172 | ---------------------- 173 | 174 | 常见缓存使用模式 175 | ---------------------- 176 | 177 | - Cache Aside: 如果数据在缓存中直接读取缓存;如果没有缓存 **应用从数据库读取** ;更新数据到缓存(下次直接可以从缓存读取了) 178 | - Read Through: 如果数据在缓存中直接读取缓存;如果不存在 **缓存负责从数据库读取** ;缓存返回给应用。应用只和缓存交互 179 | - Write Through: 应用写到缓存;缓存直接写到数据库 180 | - Write Back (Write Behind): 应用直接写到缓存(缓存高可用);缓存定期把更新刷新到数据库。Write behind 模式下适合大量写操作的场景,常用于电商秒杀场景中库存的扣减。 181 | - Write-Around。对一致性的要求较弱,可以选择在 cache aside 读模式下增加一个缓存过期时间,在写请求中仅仅更新数据库,不做任何删除或更新缓存的操作,这样,缓存仅能通过过期时间失效 182 | 183 | 读多写少的场景下,可以选择采用“ Cache-Aside 结合消费数据库日志做补偿”的方案,写多的场景下,可以选择采用“ Write-Through 结合分布式锁”的方案 , 184 | 写多的极端场景下,可以选择采用“ Write-Behind ” 的方案。 185 | 186 | 187 | 参考: 188 | 189 | - https://bluzelle.com/blog/things-you-should-know-about-database-caching 190 | - https://zhuanlan.zhihu.com/p/554879252 浅谈缓存最终一致性的解决方案 191 | 192 | 缓存问题(雪崩,击穿,穿透,回源,预热) 193 | ------------------------------------------------- 194 | - 缓存和数据库双写一致性问题 195 | 196 | - 缓存雪崩: 缓存同一时间全部失效导致数据库瞬间压力陡增引起雪崩。缓存宕机,设置相同过期时间可能导致。(热数据集中淘汰) 197 | 198 | - 做好熔断 199 | - 缓存时间加上超时随机,防止同时大量缓存失效 200 | - 加锁或者队列的方式保证不会同时对数据库进行读写 201 | 202 | - 缓存击穿: 某个key缓存过期的那一刻,同时大量请求击穿打到数据库,瞬时数据库压力陡增。可以使用 singleflight 模式避免,原 203 | 理就是当缓存失效的时候,相同 key 的请求只放行一个到后台数据库,减少请求压力。多线程获取应该用锁限制只有一个线程回源。 204 | 205 | - 缓存穿透: 大量查询 key 不存在导致请求回源到数据库,导致数据库压力增大甚至宕机。(比如爬虫遍历抓取碰到大量不存在内容) 206 | 207 | - 业务层直接过滤不合理数据 208 | - 可以把所有可能存在的数据放到足够到的bitmap 或者布隆过滤器中,查询之前如果不在其中则过滤掉 209 | - 查询不到的值也放到缓存中加上较短的失效时间 210 | 211 | - 缓存污染:爬虫批量抓取导致缓存了很多冷数据 212 | 213 | - 缓存并发竞争: 串行化操作或者加锁 214 | 215 | - 缓存预热。上线之前可以通过脚本来进行预热,定期刷新 216 | 217 | - 热点key。热点 key 导致单机 redis 压力陡增,通过 key hash分散热点或者使用本地缓存的方式(多级缓存),减小 redis 压力 218 | 219 | - 大 key。string过大或者复合结构存的值过多,可能造成内存使用不均、网络阻塞、带宽占满。可以通过对 key 进行 hash 的方式分散到 220 | 多个 key 存储。 221 | 222 | - 回源。过期 key 会回源一般有两种方式,一种是被动更新,一种是主动更新。 223 | 224 | - 被动更新:缓存过期的时候回源到 db,注意防止击穿,使用 singleflight 模式或者分布式锁保证只有一个线程回源。 225 | - 主动更新:db 数据更新之后可以写入消息队列,消费者拉取信息更新本地缓存。 226 | 227 | 参考: 228 | 229 | - https://help.aliyun.com/document_detail/353223.html 230 | 231 | 双写不一致性问题 232 | ---------------------- 233 | 234 | 分布式事务 235 | ---------------------- 236 | 237 | 超卖问题 238 | ---------------------- 239 | 在关系数据库之外进行热卖商品的库存扣减操作。使用分布式锁会比较重。有以下两种方式: 240 | 241 | - 基于乐观锁实现库存扣减。redis WATCH/MULTI/EXEC 命令结合即可实现乐观锁效果。 242 | - 结合 lua 脚本实现库存扣减。 redis执行 EVAL/EVALSHA 把它当做单条命令在执行,操作原子。扣减成功后,可以写入到消息队列实现削峰,保证写入到数据库的流量可控。 243 | 244 | 分布式高并发系统保护措施 245 | --------------------------- 246 | - 限流。限制资源数量上限,超过上限被缓冲或者失败。保护底层资源。常见有计数器、漏桶、令牌桶、滑动窗口等算法。 247 | - 熔断。防止级联错误雪崩(底层旁路故障导致雪崩)。一般由调用端提供,用在不太重要的旁路请求上,避免因为不重要的服务异常或者超时影响重要的逻辑业务。 248 | - 降级。一般考虑整体性从源头切断流量来源,比如暂停一些不重要服务,防止资源争夺。降级不重要服务,保证最核心服务的稳定 249 | - 预热。一般是由于冷启动或者负载均衡重分配,缓存没有准备完成,可以提前预热。避免资源死锁或者被打挂 250 | - 被压(Back Pressure): 智能化限流。被调用方通过反馈自己的处理能力,让调用方实时调整发送频率。典型的是 TCP 滑动窗口 251 | 252 | 容错策略: 253 | 254 | - 故障转移(Failover): 自动切换其他副本(具备幂等性) 255 | - 快速失败(Failfast): 非幂等,比如转账 256 | - 安全失败(Failsafe): 旁路调用失败,也当成正确的来返回 257 | - 沉默失败(Failsilent): 默认服务一段时间无法继续提供服务 258 | - 故障恢复(Failback): 失败信息放入消息队列,自动异步重试(最大重试次数)。适合幂等性的对实时性要求不高的主路或者不需要返回 259 | 值的旁路逻辑 260 | - 并行调用(Forking): 双重保障,选择第一个返回成功的 261 | - 广播调用(Broadcast): 要求所有请求都成功,比如“刷新分布式缓存”这类操作 262 | 263 | 容错设计模式:(熔断、隔离、重试、降级、超时) 264 | 265 | - 断路器模式:hystrix/sentinel (快速失败) 266 | - 舱壁隔离模式: 每个服务最大线程数限制 (静默失败) 267 | - 重试模式(注意幂等性;重试风暴;超时设置) 268 | 269 | - 主路关键逻辑同步重试 270 | - 仅对瞬时故障重试。比如 http 状态码 271 | - 仅对幂等性服务重试 272 | - 重试必须有终止条件:超时终止;次数终止 273 | 274 | 流量统计指标: 275 | 276 | - 每秒事务数(TPS): 衡量吞吐量最终标准。事务理解为一个逻辑上具备原子性的业务操作 277 | - 每秒请求数(HPS): 客户端向服务端的请求数 278 | - 每秒查询数(QPS): 一台服务器能够响应查询次数 279 | 280 | 限流设计模式: 281 | 282 | - 流量计数器模式 283 | - 滑动窗口限流 (否决式限流,超过阈值必须失败或者降级) 284 | - 漏桶模式 285 | - 令牌桶模式 286 | - 自适应模式。根据机器的 cpu/io/内存利用率等超过阈值自动触发 287 | - 单机与分布式 288 | 289 | 搜索引擎(Elasticsearch) 290 | ------------------------------- 291 | 292 | 业务边界划分(领域驱动设计) 293 | ------------------------------- 294 | 笔者感觉微服务的业务划分不光是一个技术问题,还是一个业务问题。笔者经历过的一些项目有时候感觉拆分太细,不像是微服务,反而 295 | 是微函数或者微接口了,维护和部署成本急剧升高。粒度太粗了可能又成了一个大的单体项目。 296 | 微服务有自己的优势,但也有缺点,比如需要较高的 devops 水平,良好的基础设施,合理的业务代码划分等,如果做不好可能微 297 | 服务带来的问题会比收益要多。所以微服务可能也不是银弹,需要根据当前的业务合理选择。 298 | 299 | 参考: 300 | ---------------------- 301 | 302 | - https://github.com/doocs/advanced-java 303 | - 《微服架构设计模式》 一本比较好的讲微服务架构实现的书籍 304 | - 《凤凰架构》 305 | - https://github.com/theanalyst/awesome-distributed-systems 306 | - https://github.com/ty4z2008/Qix/blob/master/ds.md# 307 | -------------------------------------------------------------------------------- /microservice_distribute/theory.rst: -------------------------------------------------------------------------------- 1 | .. _theory: 2 | 3 | 分布式理论知识 4 | ========================================= 5 | 6 | 本章介绍后端常用到的分布式理论知识,包括 cap,base理论,一致性协议算法等。 7 | 8 | 一致性模型 9 | ---------------------------------------- 10 | 11 | 弱一致性(最终一致性): 12 | 13 | - DNS(Domain Name System) 14 | - Gossip 15 | 16 | 17 | 强一致性: 18 | 19 | - 主从同步(一致性高,可用性低) 20 | 21 | - master 接受写清球 22 | - master 复制日志到 slave 23 | - master 等待直到所有从库返回。 24 | 25 | - Paxos 26 | - Raft (multi-paxos) 27 | - ZAB (multi-paxos) 28 | 29 | 最终一致性根据业务场景可以分为五种: 30 | 31 | - 因果一致性(causal consistency)。因果一致性指的是:如果节点A在更新完某个数据后通知了节点B,那么节点B之后对该数据的访问和修改都基于节点A更新后的值, 32 | 而和节点A无因果关系的节点C的数据访问则没有这样的限制。例如,电商业务里的下单和支付就属于这一种。 33 | - 读己之所写(read your writes)。读己之所写指的是:当节点A更新一个数据后,它总是能访问到自身更新过的最新值,而不会看到旧值。 34 | 这也是因果一致性的一种,只不过是单个事件的内部逻辑,而因果一致性可能是多个事件的因果关系。例如,支付系统扣款完成之后再刷新余额就属于这一种。 35 | - 会话一致性(session consistency)。会话一致性指的是:将对系统数据的访问过程限定在一个会话当中,系统能保证在同一个有效的会话中实现“读己之所写”的一致性。 36 | 也就是说,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。会话一致性是读己之所写的延伸和扩展。例如,找回密码的操作分为很多步骤,每个步骤都依赖前一个步骤是否成功,所有前置步骤全部按照次序完成才允许修改密码。 37 | - 单调读一致性(monotonic read consistency)。单调读一致性指的是:如果一个节点从系统中读取出一个数据项的某个值,那么系统对于该节点后续的任何数据访问都不应该返回更旧的值。 38 | 例如,多机房间用户授权token的同步,只要一个新的token已通过数据同步存储下来了,后面允许存储的token就不应该比这个token更早。这样读取到的token一定是用户更新的授权信息。 39 | - 单调写一致性(monotonic write consistency)。单调写一致性指的是:一个系统要能够保证来自同一个节点的写操作被顺序地执行。 40 | 例如,用户多次修改订单信息,那么通过消息队列进行分发最终落地数据库修改时,需要保障用户的操作是按照时间先后顺序被执行的。 41 | 基于Kafka的单分区可以保障数据有序,但是这种方式性能有限,也可考虑将每个信息都带上时间戳,再依据时间戳的先后顺序覆盖写入。 42 | 43 | Paxos 44 | ----------------------------------------- 45 | 46 | Bacic Paxos 47 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 48 | 49 | 角色: 50 | 51 | - Client: 系统外部角色,请求发起者。(民众) 52 | - Proposer: 接收 client 请求,向集群踢出提议(propose)。(议员) 53 | - Accpetor(Voter): 提议投票和接收者,只有在形成法定人数(Quorum,一般为majority多数派)时,提议才会被接受。(过会) 54 | - Learner: 提议接受者, backup,备份。对一群一致性无影响。(记录员) 55 | 56 | 阶段: 57 | 58 | - 1a: Prepare: Proposer提出一个提案编号N,此 N 大于这个 proposer 之前踢出的编号。请求 Accpetor的quorum接 59 | - 1b: Promise: 请求 N 大于此 Acceptor 之前接受的任何提案编号则接受,否则拒绝 60 | - 2a: Accept: 如果达到了多数派,Proposer 会发出 accept 请求(包含N)和提案内容 61 | - 2b: Accepted: 如果Acceptor在此期间没有接收到任何编号大于N的提案,则接受此提案内容,否则忽略 62 | 63 | 潜在问题:活锁(liveness)或dueling。解决方案,随机超时 64 | 65 | 难实现,效率低(2轮RPC),活锁 66 | 67 | Multi Paxos 68 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 69 | 70 | 新概念,Leader: 唯一的Proposer,所有请求都需要经过此 Leader 71 | 72 | Fast Paxos 73 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 74 | 75 | 76 | Raft 77 | ----------------------------------------- 78 | 79 | - http://www.kailing.pub/raft/index.html raft动画演示 80 | - https://github.com/hashicorp/raft 代码实现 81 | 82 | 参考: 83 | ----------------------------------------- 84 | - 《设计数据密集型应用》 85 | - https://martinfowler.com/articles/patterns-of-distributed-systems/ 86 | -------------------------------------------------------------------------------- /mush.rst: -------------------------------------------------------------------------------- 1 | ================================================== 2 | 蘑菇碎碎念 3 | ================================================== 4 | 5 | HG 6 | ----------------------- 7 | 8 | 1. fetch 某个分支 9 | 10 | ``hg fetch http://xxx.xxx.xxx.xxx:8000 -r <分支名>`` 11 | 12 | #. 在docker中互相fetch 13 | 14 | docker额外映射了一个端口到8000,可以通过这个端口 15 | 16 | #. 关闭无名分支 17 | 18 | :: 19 | 20 | hg update -r <版本号> 21 | hg commit --close-branch -m 'Closing old branch' 22 | hg update -C default 23 | 24 | 翻墙有道 25 | ----------------------- 26 | 27 | gentoo下emerge访问墙外资源 28 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 29 | 30 | 1. emerge设置HTTP代理 31 | 32 | 在 ``/etc/make.conf`` 写入代理配置 33 | 34 | :: 35 | 36 | http_proxy="http://127.0.0.1:8080" 37 | https_proxy="http://127.0.0.1:8080" 38 | 39 | #. 安装配置HTTP代理工具polipo 40 | 41 | ``sudo emerge polipo`` 即可完成安装,安装后在 ``/etc/polipo/conf`` 中写入 42 | 43 | :: 44 | 45 | daemonise=false 46 | diskCacheRoot=/var/cache/polipo/ 47 | proxyAddress=127.0.0.1 48 | proxyName=localhost 49 | cacheIsShared=true 50 | allowedClients=127.0.0.1 51 | proxyPort=8080 52 | socksParentProxy = 127.0.0.1:1080 53 | socksProxyType = socks5 54 | 55 | #. 配置shadowsocks 56 | 57 | :: 58 | 59 | { 60 | "server":"vpn.mushapi.com", 61 | "server_port":1081, 62 | "local_address": "127.0.0.1", 63 | "local_port":1080, 64 | "password":"btyh17mxy", 65 | "timeout":300, 66 | "method":"aes-256-cfb", 67 | "fast_open": false, 68 | "workers": 1 69 | } 70 | 71 | vim黑科技 72 | ----------------------- 73 | 74 | 1. 遇到gbk乱码囧木办 75 | 76 | :: 77 | 78 | set encoding=utf-8 79 | set fenc=cp936 80 | set fileencodings=cp936,ucs-bom,utf-8 81 | if v:lang =~? '^\(zh\)\|\(ja\)\|\(ko\)' 82 | set ambiwidth=double 83 | endif 84 | set nobomb 85 | 86 | #. 粘贴后格式错乱怎么办 87 | 88 | 有的时候,在插入模式下从系统粘贴板粘贴文本到vim中会出现缩进异常的情况,为了解决这种问题,在粘贴前应该设置vim为粘贴模式并在粘贴完成后取消粘贴模式 89 | 90 | ``:set paste`` 91 | 92 | ``:set nopaste`` 93 | 94 | #. 你滚开 95 | 96 | 如果你在一个文件中滚动屏幕,那么另一个文件也会自动滚动以显示相同的位置。你可以使用以下命令,取消联动: 97 | 98 | ``:set noscrollbind`` 99 | 100 | 使用以下命令,将重新绑定联动: 101 | 102 | ``:set scrollbind`` 103 | 104 | 利用以下命令,可以定义滚动方式: 105 | 106 | ``:set scrollopt ver,hor,jump`` 107 | 108 | 其中:选项ver ,启用垂直同步滚动;选项hor ,启用水平同步滚动;而jump 选项,则在切换窗口时,使垂直滚动始终同步。 109 | 110 | 如果光标停留在两个文件的不同位置,那么可以使用下面的命令同步滚动: 111 | 112 | ``:syncbind`` 113 | 114 | vim插件及使用 115 | ----------------------- 116 | 117 | 1. syntastic 在错误之间跳转 118 | 119 | :lnext 跳到下一个 120 | 121 | :lprev 跳到上一个 122 | 123 | #. 使用pyflakes进行语法检查 124 | 125 | :SyntasticCheck pyflakes 126 | 127 | iptables 128 | ----------------------- 129 | 130 | 1. 列出所有规则 131 | 132 | ``iptables -nvL -t nat --line-number `` 133 | 134 | 列出nat表的所有规则并显示行号 135 | 136 | #. 清零流量统计 137 | 138 | ``iptables -Z `` 139 | 140 | #. 删除 141 | 142 | ``iptables -t nat -D DOCKER 13`` 143 | 144 | 删除nat表DOCKER链的第13行的规则 145 | 146 | #. 用iptables给Docker添加端口映射 147 | 148 | ``iptables -t nat -A DOCKER --in-interface \!docker0 -p tcp --dport 6666 -j DNAT --to 172.17.0.5:6666`` 149 | 150 | docker会在系统中创建一个叫docker0的网卡,本例中172.17.0.5就是docker0的IP地址 151 | 152 | linux命令 153 | ----------------------- 154 | 155 | ssh客户端配置文件 156 | ^^^^^^^^^^^^^^^^^^^^^^^ 157 | 158 | 当主机较多的时候,不方便记住所有的IP、用户、端口以及密码,为了解决这个问题我们可以使用一个ssh的配置文件来记录这些服务器。 159 | 160 | 常用的配置有 161 | 162 | :: 163 | 164 | Host 主机别名 165 | HostName 主机地址 166 | User 登陆用户名 167 | Port 端口号 168 | IdentityFile 公钥 169 | 170 | 在~/.ssh/目录下创建一个config文件,在config中写入相应的配置后就可以使用 ``ssh \<主机别名\>`` 直接连接服务器了 171 | 172 | 保障服务器安全 173 | ^^^^^^^^^^^^^^^^^^^^^^^ 174 | 175 | 1. 禁用密码登陆, 使用密钥登陆 176 | 177 | 编辑/etc/ssh/sshd_config 178 | 179 | :: 180 | 181 | PasswordAuthentication no 182 | 183 | 184 | #. 禁用root登陆 185 | 186 | 编辑/etc/ssh/sshd_config 187 | 188 | :: 189 | 190 | PermitRootLogin no 191 | 192 | 193 | 多线程下载工具axel 194 | ^^^^^^^^^^^^^^^^^^^^^^^ 195 | 196 | curl和wget是单线程的,使用这货的多线程方式下载文件会显著提高下载速度 197 | 198 | 1. 安装 199 | 200 | gentoo下 ``sudo emerge axel`` 201 | 202 | centos下 ``sudo yum install axel`` 203 | 204 | #. 使用 205 | 206 | :: 207 | 208 | axel -n <线程数> -o <保存文件的目录> <下载地址> 209 | 210 | docker 的一个奇怪命令 211 | ^^^^^^^^^^^^^^^^^^^^^^^ 212 | 213 | docker run -e MYSQL_ROOT_PASSWORD=rstfsgbcedh --expose 3306 --entrypoint="/entrypoint.sh" --name mysql-hg -d mush/mysql-hg mysqld 214 | 215 | 如果遇到 TERM environment variable not set. 就执行 ``export TERM=dumb`` 216 | 217 | redis in docker 218 | ^^^^^^^^^^^^^^^^^^^^^^^ 219 | 220 | 当我们在使用docker提供redis服务时, 如果我们需要执行一个redis命令就需要使用 ``docker exec