From 09ef3f49fa58ef725a7186f2d51a870b1cb2bf27 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Wed, 8 May 2019 20:23:38 +0800 Subject: [PATCH 001/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=89=E7=AF=87?= =?UTF-8?q?=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c03/c03_01.md | 89 ++++++++++++++++++++++++++++++++++- source/c04/c04_15.md | 108 +++++++++++++++++++++++++++++++++++++++---- source/c08/c08_05.md | 18 +++++++- 3 files changed, 203 insertions(+), 12 deletions(-) diff --git a/source/c03/c03_01.md b/source/c03/c03_01.md index bd87d0f..d888b6f 100644 --- a/source/c03/c03_01.md +++ b/source/c03/c03_01.md @@ -225,8 +225,93 @@ say("hello") say hello! ``` +## 3.1.7 wrapper 装饰器有啥用? -## 3.1.7 内置装饰器:property +在 functools 标准库中有提供一个 wrapper 装饰器,你应该也经常见过,那他有啥用呢? + +先来看一个例子 + +```python +def wrapper(func): + def inner_function(): + pass + return inner_function + +@wrapper +def wrapped(): + pass + +print(wrapped.__name__) +#inner_function +``` + +为什么会这样子?不是应该返回 `func ` 吗? + +这也不难理解,因为上边执行`func` 和下边 `decorator(func)` 是等价的,所以上面 `func.__name__` 是等价于下面`decorator(func).__name__` 的,那当然名字是 `inner_function` + +```python +def wrapper(func): + def inner_function(): + pass + return inner_function + +def wrapped(): + pass + +print(wrapper(wrapped).__name__) +#inner_function +``` + +那如何避免这种情况的产生?方法是使用 functools .wrapper 装饰器,它的作用就是将 **被修饰的函数(wrapped)** 的一些属性值赋值给 **修饰器函数(wrapper)** ,最终让属性的显示更符合我们的直觉。 + +```python +from functools import wraps + +def wrapper(func): + @wraps(func) + def inner_function(): + pass + return inner_function + +@wrapper +def wrapped(): + pass + +print(wrapped.__name__) +# wrapped +``` + +准确点说,wrapper 其实是一个偏函数对象(partial),源码如下 + +```python +def wraps(wrapped, + assigned = WRAPPER_ASSIGNMENTS, + updated = WRAPPER_UPDATES): + return partial(update_wrapper, wrapped=wrapped, + assigned=assigned, updated=updated) +``` + +可以看到wraps其实就是调用了一个函数` update_wrapper`,知道原理后,我们改写上面的代码,在不使用 wraps的情况下,也可以让 `wrapped.__name__` 打印出 wrapped,代码如下: + +```python +from functools import update_wrapper + +def wrapper(func): + def inner_function(): + pass + update_wrapper(func, inner_function) + return inner_function + +def wrapped(): + pass + +print(wrapped.__name__) +# wrapped +``` + + + +## 3.1.8 内置装饰器:property 以上,我们介绍的都是自定义的装饰器。 @@ -334,7 +419,7 @@ del XiaoMing.age `@age.deleter` 使得我们可以使用`del XiaoMing.age`这样的方式来删除属性。 -## 3.1.8 其他装饰器:装饰器实战 +## 3.1.9 其他装饰器:装饰器实战 读完并理解了上面的内容,你可以说是Python高手了。别怀疑,自信点,因为很多人都不知道装饰器有这么多用法呢。 diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index c69f8e7..8969c50 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -250,11 +250,77 @@ Pycharm 本身会实时地对变量名进行检查,如果变量名不是一个 ![](http://image.python-online.cn/FgJCtNYkjPfBaTbRxwb3Z6icHqkf) -## 4.15.11 使用Git控制版本 +## 4.15.11 一键进行代码性能分析 -输入仓库地址 +在 Python 中有许多模块可以帮助你分析并找出你的项目中哪里出现了性能问题。 -![](http://image.python-online.cn/20190419151948.png) +比如,常用的模块有 cProfile,在某些框架中,也内置了中间件帮助你进行性能分析,比如 Django ,WSGI。 + +做为Python 的第一 IDE, PyCharm 本身就支持了这项功能。而且使用非常方便,小白。 + +假设现在要分析如下这段代码的性能损耗情况,找出到底哪个函数耗时最多 + +```python +import time + +def fun1(): + time.sleep(1) + +def fun2(): + time.sleep(1) + +def fun3(): + time.sleep(2) + +def fun4(): + time.sleep(1) + +def fun5(): + time.sleep(1) + fun4() + +fun1() +fun2() +fun3() +fun5() +``` + +点击 Run -> Profile '程序' ,即可进行性能分析。 + +![](http://image.python-online.cn/20190507222856.png) + +运行完毕后,会自动跳出一个性能统计界面。 + +![](http://image.python-online.cn/20190507222119.png) + +性能统计界面由Name、Call Count、Time(ms)、Own Time(ms) ,4列组成一个表格,见下图。 + +1. 表头Name显示被调用的模块或者函数;Call Count显示被调用的次数;Time(ms)显示运行时间和时间百分比,时间单位为毫秒(ms)。 +2. 点击表头上的小三角可以升序或降序排列表格。 +3. 在Name这一个列中双击某一行可以跳转到对应的代码。 +4. 以fun4这一行举例:fun4被调用了一次,运行时间为1000ms,占整个运行时间的16.7% + +点击 Call Graph(调用关系图)界面直观展示了各函数直接的调用关系、运行时间和时间百分比,见下图。 + +![](http://image.python-online.cn/20190507223313.png) + +右上角的4个按钮表示放大、缩小、真实大小、合适大小; + +1. 箭头表示调用关系,由调用者指向被调用者; +2. 矩形的左上角显示模块或者函数的名称,右上角显示被调用的次数; +3. 矩形中间显示运行时间和时间百分比; +4. 矩形的颜色表示运行时间或者时间百分比大小的趋势:红色 > 黄绿色 > 绿色,由图可以看出fun3的矩形为黄绿色,fun1为绿色,所有fun3运行时间比fun1长。 +5. 从图中可以看出Test.py直接调用了fun3、fun1、fun2和fun5函数;fun5函数直接调用了fun4函数;fun1、fun2、fun3、fun4和fun5都直接调用了print以及sleep函数;整个测试代码运行的总时间为6006ms,其中fun3的运行时间为1999ms,所占的时间百分比为33.3%,也就是 1999ms / 6006ms = 33.3%。 + +## 4.15.12 使用Git做版本控制 + +按照如下提示点击 Git 仓库配置 + +![](http://image.python-online.cn/20190507215525.png) + +接着输入仓库地址 + +![](http://image.python-online.cn/20190507220101.png) 点击 Test,测试连通性,会要求输入密码 @@ -264,9 +330,13 @@ Pycharm 本身会实时地对变量名进行检查,如果变量名不是一个 ![](http://image.python-online.cn/20190419152145.png) +测试连接成功后,点击 Clone 就可以克隆下来了。 +对于以前使用 Git 命令来管理的,现在可以直接使用 PyCharm 的菜单栏来操作,这些功能已经可以满足大多数人的日常需求了,应该是够用了。 -## 4.15.12 Tab轻松转空格 +![](http://image.python-online.cn/20190507220740.png) + +## 4.15.13 Tab轻松转空格 在团队协作中,你难免会动到别人编辑的文件,有的人喜欢做tab做缩进,有的人喜欢用四个空格做缩进。 @@ -284,9 +354,7 @@ Pycharm 本身会实时地对变量名进行检查,如果变量名不是一个 ![](http://image.python-online.cn/20190423163341.png) -## 4.15.13 代码性能分析 - - +5. ## 4.15.14 一次注册,永久激活 @@ -354,9 +422,33 @@ BIG3CLIK6F-eyJsaWNlbnNlSWQiOiJCSUczQ0xJSzZGIiwibGljZW5zZWVOYW1lIjoibGFuIHl1IiwiY ![](http://image.python-online.cn/20190507001350.png) -另外,以上仅做交流和个人学习使用,有能力的朋友还是希望多支持正版! +另外,以上仅做交流和个人学习使用,请勿商用,有能力的朋友还是希望多支持正版! + +## 4.15.15 源码文档,快速预览 + +Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函数跳转查看源码, 这几乎是每一个 PyCharmer 都会的一个惯用技巧。 + +这里再另外介绍两个类似的小技巧,快速 `查看函数文档` 和 `预览源代码` 。 + +在函数的开头处,使用三个引号 ` "` 包含的内容,叫做函数文档 (DocString)。 + +在编写代码时,顺便写好函数的接口文档,是一个很好的编码习惯。它介绍了该函数的参数类型及说明,返回值类型及范例,写得好一点的还会写出 几个简单的 Example Usage 有助于理解使用。在这一点上,Flask 可以说做得相当好。这边随便截一个 Werkzeug 的例子。 + +![](http://image.python-online.cn/20190507152911.png) + +假如我们在使用这个类的时候,忘记了这个用法,可以按住 Ctrl + q(Mac暂时未找到对应快捷键),在当前页面就可以快速预览 LocalStack 的接口文档。 + +![](http://image.python-online.cn/20190507152840.png) + +同样的,如果你对这个类或者函数的代码逻辑感兴趣,也可以使用快速预览的方式在当前页面展示源代码。快捷键是:Ctrl + shift + i (Mac:Command + shift + i)。效果如下 + +![](http://image.python-online.cn/20190507153847.png) + +如果 PyCharm 检测到的同名函数有多个,可以点这里进行选择查看 +![](http://image.python-online.cn/20190507154027.png) +这两个快捷键比起使用 Ctrl + 鼠标左键 跳进源代码来说,更加方便,,就像微信小程序一样,用完即焚,不会新产生一个标签页,也不需要来回跳转页面。 diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index dfcc651..a2b265f 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -60,9 +60,23 @@ nova 提供了一个配置项:notify_on_state_change,本意是想,如果 ## 8.5.5 快照镜像如何实现? -入口函数在:`nova/virt/libvirt/driver.py:snapshot()` +nova-api 的入口如下 -会先获取imagebackend的类型,然后找到对应的backend +![](http://image.python-online.cn/20190508110723.png) + +接着会调用 nova/compute/api.py + +![](http://image.python-online.cn/20190508111109.png) + +在nova-compute 层面:nova/compute/manager.py:_snapshot_instance() + +![](http://image.python-online.cn/20190508095028.png) + +接下来会调用 `nova/virt/libvirt/driver.py:snapshot()` + +![](http://image.python-online.cn/20190508111527.png) + +先获取imagebackend的类型,然后找到对应的backend ```python disk_path, source_format = libvirt_utils.find_disk(virt_dom) From 0db6d8e6568e7199a51b30c2491a5e6628271f32 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 11 May 2019 17:20:10 +0800 Subject: [PATCH 002/302] =?UTF-8?q?=E6=9B=B4=E6=8D=A2=E5=9B=BE=E5=BA=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 54 +++++++----- source/aboutme.rst | 2 +- source/c01/c01_01.md | 6 +- source/c01/c01_01.rst | 6 +- source/c01/c01_04.md | 6 +- source/c01/c01_04.rst | 6 ++ source/c01/c01_05.md | 2 +- source/c01/c01_05.rst | 2 +- source/c01/c01_06.md | 2 +- source/c01/c01_06.rst | 2 +- source/c01/c01_07.md | 2 +- source/c01/c01_07.rst | 2 +- source/c01/c01_08.md | 2 +- source/c01/c01_08.rst | 2 +- source/c01/c01_09.md | 2 +- source/c01/c01_09.rst | 2 +- source/c01/c01_10.md | 8 +- source/c01/c01_10.rst | 8 +- source/c01/c01_11.md | 2 +- source/c01/c01_11.rst | 2 +- source/c01/c01_12.md | 2 +- source/c01/c01_12.rst | 2 +- source/c01/c01_13.md | 2 +- source/c01/c01_13.rst | 2 +- source/c01/c01_14.md | 2 +- source/c01/c01_14.rst | 2 +- source/c01/c01_15.md | 2 +- source/c01/c01_15.rst | 2 +- source/c01/c01_16.md | 2 +- source/c01/c01_16.rst | 2 +- source/c01/c01_17.md | 2 +- source/c01/c01_17.rst | 2 +- source/c02/c02_01.md | 2 +- source/c02/c02_01.rst | 2 +- source/c02/c02_02.md | 2 +- source/c02/c02_02.rst | 2 +- source/c02/c02_03.md | 2 +- source/c02/c02_03.rst | 2 +- source/c02/c02_04.md | 2 +- source/c02/c02_04.rst | 2 +- source/c02/c02_05.md | 2 +- source/c02/c02_05.rst | 2 +- source/c02/c02_06.md | 2 +- source/c02/c02_06.rst | 2 +- source/c02/c02_07.md | 2 +- source/c02/c02_07.rst | 2 +- source/c02/c02_08.md | 2 +- source/c02/c02_08.rst | 2 +- source/c02/c02_09.md | 3 +- source/c02/c02_09.rst | 2 +- source/c02/c02_10.md | 2 +- source/c02/c02_10.rst | 2 +- source/c02/c02_11.md | 2 +- source/c02/c02_11.rst | 2 +- source/c02/c02_12.md | 4 +- source/c02/c02_12.rst | 6 ++ source/c03/c03_01.md | 2 +- source/c03/c03_01.rst | 98 +++++++++++++++++++++- source/c03/c03_02.md | 2 +- source/c03/c03_02.rst | 2 +- source/c03/c03_03.md | 2 +- source/c03/c03_03.rst | 2 +- source/c03/c03_04.md | 2 +- source/c03/c03_04.rst | 2 +- source/c03/c03_05.md | 2 +- source/c03/c03_05.rst | 2 +- source/c04/c04_01.md | 2 +- source/c04/c04_01.rst | 2 +- source/c04/c04_02.md | 12 +-- source/c04/c04_02.rst | 21 +++-- source/c04/c04_03.md | 29 ++++--- source/c04/c04_03.rst | 28 ++++--- source/c04/c04_04.md | 20 ++--- source/c04/c04_04.rst | 20 ++--- source/c04/c04_05.md | 38 +++++---- source/c04/c04_05.rst | 52 +++++++----- source/c04/c04_06.md | 4 +- source/c04/c04_06.rst | 4 +- source/c04/c04_07.md | 2 +- source/c04/c04_07.rst | 2 +- source/c04/c04_08.md | 2 +- source/c04/c04_08.rst | 2 +- source/c04/c04_09.md | 2 +- source/c04/c04_09.rst | 2 +- source/c04/c04_10.md | 2 +- source/c04/c04_10.rst | 2 +- source/c04/c04_11.md | 2 +- source/c04/c04_11.rst | 2 +- source/c04/c04_12.md | 2 +- source/c04/c04_12.rst | 2 +- source/c04/c04_13.md | 2 +- source/c04/c04_13.rst | 2 +- source/c04/c04_14.md | 2 +- source/c04/c04_14.rst | 2 +- source/c04/c04_15.md | 2 +- source/c04/c04_15.rst | 189 +++++++++++++++++++++++++++++++++++------- source/c04/c04_16.md | 2 +- source/c04/c04_16.rst | 2 +- source/c04/c04_17.md | 2 +- source/c04/c04_17.rst | 2 +- source/c05/c05_01.md | 2 +- source/c05/c05_01.rst | 2 +- source/c05/c05_02.md | 2 +- source/c05/c05_02.rst | 2 +- source/c05/c05_03.md | 2 +- source/c05/c05_03.rst | 2 +- source/c06/c06_01.md | 6 +- source/c06/c06_01.rst | 10 +-- source/c06/c06_02.md | 22 ++--- source/c06/c06_02.rst | 48 +++++------ source/c06/c06_03.md | 12 +-- source/c06/c06_03.rst | 12 +-- source/c06/c06_04.md | 16 ++-- source/c06/c06_04.rst | 16 ++-- source/c06/c06_05.md | 2 +- source/c06/c06_05.rst | 2 +- source/c06/c06_06.md | 4 +- source/c06/c06_06.rst | 4 +- source/c07/C07_08.md | 2 +- source/c07/c07_01.md | 2 +- source/c07/c07_01.rst | 2 +- source/c07/c07_02.md | 2 +- source/c07/c07_02.rst | 2 +- source/c07/c07_03.md | 2 +- source/c07/c07_03.rst | 2 +- source/c07/c07_04.md | 2 +- source/c07/c07_04.rst | 2 +- source/c07/c07_05.md | 2 +- source/c07/c07_05.rst | 2 +- source/c07/c07_06.md | 2 +- source/c07/c07_06.rst | 2 +- source/c07/c07_07.md | 2 +- source/c07/c07_07.rst | 2 +- source/c07/c07_08.rst | 2 +- source/c08/c08_01.md | 2 +- source/c08/c08_01.rst | 2 +- source/c08/c08_02.md | 2 +- source/c08/c08_02.rst | 2 +- source/c08/c08_03.md | 2 +- source/c08/c08_03.rst | 2 +- source/c08/c08_04.md | 2 +- source/c08/c08_04.rst | 2 +- source/c08/c08_05.md | 2 +- source/c08/c08_05.rst | 80 +++++++++++------- source/c08/c08_06.md | 2 +- source/c08/c08_06.rst | 2 +- source/c08/c08_07.md | 2 +- source/c08/c08_07.rst | 2 +- source/preface.rst | 2 +- 149 files changed, 681 insertions(+), 403 deletions(-) diff --git a/README.md b/README.md index d28bc4a..7163d68 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -这是我的个人博客( [MING's BLOG](http://mings-blog.rtfd.io) ),主要写关于Python的一些思考总结。 +这是我的个人博客( [MING's BLOG]([http://python-online.cn](http://python-online.cn/)) ),主要写关于Python的一些思考总结。 关于搭建教程,感兴趣的可以查看这边:[Sphinx 搭建博客的图文教程](https://github.com/BingmingWong/MING-BLOG/blob/master/source/c04/c04_03.rst) @@ -19,6 +19,10 @@ - 1.11 [正则表达式必知必会](http://python-online.cn/zh_CN/latest/c01/c01_11.html) - 1.12 [搞懂字符编码的前世今生](http://python-online.cn/zh_CN/latest/c01/c01_12.html) - 1.13 [Python几个高阶函数](http://python-online.cn/zh_CN/latest/c01/c01_13.html) +- 1.14 [with 与 上下文管理器](http://python-online.cn/zh_CN/latest/c01/c01_14.html) +- 1.15 [提升Python性能的7个习惯](http://python-online.cn/zh_CN/latest/c01/c01_15.html) +- 1.16 [泛型函数怎么写?](http://python-online.cn/zh_CN/latest/c01/c01_16.html) +- 1.17 [深入理解「描述符」](http://python-online.cn/zh_CN/latest/c01/c01_17.html) ## 第二章:并发编程 - 2.1 [从性能角度来初探并发编程](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_01.html) @@ -32,12 +36,14 @@ - 2.9 [初识异步IO框架:asyncio 上篇](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_09.html) - 2.10 [学习异步IO框架:asyncio 中篇](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_10.html) - 2.11 [实战异步IO框架:asyncio 下篇](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_11.html) +- 2.12 [生成器与协程,你分清了吗?](http://python-online.cn/zh_CN/latest/c02/c02_12.html) ## 第三章:高级编程 - 3.1 [装饰器从入门到高阶](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c03_01.html) - 3.2 [学会使用元类定制类](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c03_02.html) - 3.3 [学会使用socket网络编程](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c03_03.html) - 3.4 [Django+Uwsgi部署网站](http://python-online.cn/zh_CN/latest/c03/c03_04.html) +- 3.5 [源码解读:Flask上下文与代理模式](http://python-online.cn/zh_CN/latest/c03/c03_05.html) ## 第四章:开发工具 - 4.1 [虚拟环境的搭建与使用](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c04_01.html) @@ -46,13 +52,17 @@ - 4.4 [Jupyter NoteBook 的使用指南](https://mings-blog.readthedocs.io/zh_CN/latest/c04/c04_04.html) - 4.5 [Win10 + Ubuntu 双系统安装指南](https://mings-blog.readthedocs.io/zh_CN/latest/c04/c04_05.html) - 4.6 [我的 Git 使用指南](http://python-online.cn/zh_CN/latest/c04/c04_06.html) -- [4.7 Hexo 搭建博客教程](http://python-online.cn/zh_CN/latest/c04/c04_07.html) -- [4.8 珍惜生命,远离鼠标](http://python-online.cn/zh_CN/latest/c04/c04_08.html) -- [4.9 MySQL的基本使用](http://python-online.cn/zh_CN/latest/c04/c04_09.html) -- [4.10 MySQL的高级进阶](http://python-online.cn/zh_CN/latest/c04/c04_10.html) -- [4.11 不能不会的远程调试技巧](http://python-online.cn/zh_CN/latest/c04/c04_11.html) -- [4.12 服务器调试神器:pdb](http://python-online.cn/zh_CN/latest/c04/c04_12.html) -- [4.13 命令行解析工具:argparse](http://python-online.cn/zh_CN/latest/c04/c04_13.html) +- 4.7 [ Hexo 搭建博客教程](http://python-online.cn/zh_CN/latest/c04/c04_07.html) +- 4.8[珍惜生命,远离鼠标](http://python-online.cn/zh_CN/latest/c04/c04_08.html) +- 4.9 [MySQL的基本使用](http://python-online.cn/zh_CN/latest/c04/c04_09.html) +- 4.10 [MySQL的高级进阶](http://python-online.cn/zh_CN/latest/c04/c04_10.html) +- 4.11 [不能不会的远程调试技巧](http://python-online.cn/zh_CN/latest/c04/c04_11.html) +- 4.12 [服务器调试神器:pdb](http://python-online.cn/zh_CN/latest/c04/c04_12.html) +- 4.13 [命令行解析工具:argparse](http://python-online.cn/zh_CN/latest/c04/c04_13.html) +- 4.14 [虚拟环境:Pipenv](http://python-online.cn/zh_CN/latest/c04/c04_14.html) +- 4.15 [15个 PyCharm 小技巧](http://python-online.cn/zh_CN/latest/c04/c04_15.html) +- 4.16 [Python 开发技巧集合](http://python-online.cn/zh_CN/latest/c04/c04_16.html) +- 4.17 [Python 实现 23 种设计模式](http://python-online.cn/zh_CN/latest/c04/c04_17.html) ## 第五章:算法教程 - 5.1 [图解九大经典排序算法(一)](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c05_01.html) @@ -66,24 +76,28 @@ ## 第七章:运维人生 -- [7.1 Linux 命令行的艺术](http://python-online.cn/zh_CN/latest/c07/c07_01.html) -- [7.2 Zabbix 监控部署文档](http://python-online.cn/zh_CN/latest/c07/c07_02.html) -- [7.3 Docker:Hello world](http://python-online.cn/zh_CN/latest/c07/c07_03.html) -- [7.4 Docker:关于镜像](http://python-online.cn/zh_CN/latest/c07/c07_04.html) -- [7.5 Docker:网络通信](http://python-online.cn/zh_CN/latest/c07/c07_05.html) -- [7.6 Docker:存储与多主机](http://python-online.cn/zh_CN/latest/c07/c07_06.html) -- [7.7 SaltStack 入门指南](http://python-online.cn/zh_CN/latest/c07/c07_07.html) +- 7.1 [Linux 命令行的艺术](http://python-online.cn/zh_CN/latest/c07/c07_01.html) +- 7.2 [Zabbix 监控部署文档](http://python-online.cn/zh_CN/latest/c07/c07_02.html) +- 7.3 [Docker:Hello world](http://python-online.cn/zh_CN/latest/c07/c07_03.html) +- 7.4 [Docker:关于镜像](http://python-online.cn/zh_CN/latest/c07/c07_04.html) +- 7.5 [Docker:网络通信](http://python-online.cn/zh_CN/latest/c07/c07_05.html) +- 7.6 [Docker:存储与多主机](http://python-online.cn/zh_CN/latest/c07/c07_06.html) +- 7.7 [SaltStack 入门指南](http://python-online.cn/zh_CN/latest/c07/c07_07.html) +- 7.8 [Keepalived 部署文档](http://python-online.cn/zh_CN/latest/c07/c07_08.html) ## 第八章:OpenStack -- [8.1 OpenStack 运维命令](http://python-online.cn/zh_CN/latest/c08/c08_01.html) -- [8.2 OpenStack 部署SR-IOV](http://python-online.cn/zh_CN/latest/c08/c08_02.html) -- [8.3 制作 OpenStack 镜像](http://python-online.cn/zh_CN/latest/c08/c08_03.html) -- [8.4 虚拟化入门概念](http://python-online.cn/zh_CN/latest/c08/c08_04.html) +- 8.1 [OpenStack 运维命令](http://python-online.cn/zh_CN/latest/c08/c08_01.html) +- 8.2 [OpenStack 部署SR-IOV](http://python-online.cn/zh_CN/latest/c08/c08_02.html) +- 8.3 [制作 OpenStack 镜像](http://python-online.cn/zh_CN/latest/c08/c08_03.html) +- 8.4 [虚拟化入门概念](http://python-online.cn/zh_CN/latest/c08/c08_04.html) +- 8.5 [OpenStack 源码剖析](http://python-online.cn/zh_CN/latest/c08/c08_05.html) +- 8.6 [深度解读Cloud-init源码](http://python-online.cn/zh_CN/latest/c08/c08_06.html) +- 8.7 [OpenStack 实现GPU直通](http://python-online.cn/zh_CN/latest/c08/c08_07.html) ## LeetCode Challenge - [NO.1-10](http://mings-blog.readthedocs.io/zh_CN/latest/lc01/1-10.html) --- -![欢迎关注个人公众号](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/aboutme.rst b/source/aboutme.rst index b2ced40..cc5a0d4 100755 --- a/source/aboutme.rst +++ b/source/aboutme.rst @@ -10,5 +10,5 @@ -------------------------------------------- -.. image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. image:: http://image.python-online.cn/20190511161447.png diff --git a/source/c01/c01_01.md b/source/c01/c01_01.md index 33971c6..a3ff911 100644 --- a/source/c01/c01_01.md +++ b/source/c01/c01_01.md @@ -22,10 +22,10 @@ 这个地址里,有所有Python历史版本(2.0+)。 点击左边,Release Version栏目 对应的版本。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi6n986boj20ix0aa41g.jpg) +![](http://image.python-online.cn/20190511165542.png) 进入对应详情页后,找到如图 `what's new in Python xx` 就可以查看此版本的新特性。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi6nti81ij20fz07jac0.jpg) +![](http://image.python-online.cn/20190511165551.png) 网页是全英文的,需要你有一定的英文阅读能力。快去感觉一下吧。 @@ -268,4 +268,4 @@ class Person(metaclass=MetaPerson): 实际上,当我熟悉一个版本后,基本上是可以无缝过渡到另一个版本的。这篇文章,更多的是为了科普和应对面试。 ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c01/c01_01.rst b/source/c01/c01_01.rst index 34b9c15..138fff4 100755 --- a/source/c01/c01_01.rst +++ b/source/c01/c01_01.rst @@ -314,10 +314,10 @@ Python3.x 没有经典类,只有新式类,而且有三种写法 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! -.. |image0| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi6n986boj20ix0aa41g.jpg -.. |image1| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi6nti81ij20fz07jac0.jpg +.. |image0| image:: http://image.python-online.cn/20190511165542.png +.. |image1| image:: http://image.python-online.cn/20190511165551.png diff --git a/source/c01/c01_04.md b/source/c01/c01_04.md index 58ca9e7..99c9036 100644 --- a/source/c01/c01_04.md +++ b/source/c01/c01_04.md @@ -49,4 +49,8 @@ del pd.DataFrame.just_foo_cols # you can also remove the new method ![](http://image.python-online.cn/20190404215330.png) -还有就是gevent中也有用到。 \ No newline at end of file +还有就是gevent中也有用到。 + +--- + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c01/c01_04.rst b/source/c01/c01_04.rst index b27684e..7fab61d 100755 --- a/source/c01/c01_04.rst +++ b/source/c01/c01_04.rst @@ -66,5 +66,11 @@ 还有就是gevent中也有用到。 +-------------- + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + + .. |image0| image:: http://image.python-online.cn/20190404215330.png diff --git a/source/c01/c01_05.md b/source/c01/c01_05.md index 8911351..1493a29 100644 --- a/source/c01/c01_05.md +++ b/source/c01/c01_05.md @@ -166,4 +166,4 @@ foobar() ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c01/c01_05.rst b/source/c01/c01_05.rst index 97a5e49..c397743 100755 --- a/source/c01/c01_05.rst +++ b/source/c01/c01_05.rst @@ -177,6 +177,6 @@ locals() -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c01/c01_06.md b/source/c01/c01_06.md index f915ca0..ecf06e6 100644 --- a/source/c01/c01_06.md +++ b/source/c01/c01_06.md @@ -99,4 +99,4 @@ print(Xiamen_dict) -------------- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c01/c01_06.rst b/source/c01/c01_06.rst index 2afa29b..550426a 100755 --- a/source/c01/c01_06.rst +++ b/source/c01/c01_06.rst @@ -104,6 +104,6 @@ Python中有一个基础的数据结构,叫做元组(tuple),但是一般 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c01/c01_07.md b/source/c01/c01_07.md index df97fee..61c1756 100644 --- a/source/c01/c01_07.md +++ b/source/c01/c01_07.md @@ -291,4 +291,4 @@ b = 2 if a > 2 else 1 - https://foofish.net/idiomatic_part2.html ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c01/c01_07.rst b/source/c01/c01_07.rst index 4052015..600049c 100755 --- a/source/c01/c01_07.rst +++ b/source/c01/c01_07.rst @@ -348,6 +348,6 @@ Pythonic -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c01/c01_08.md b/source/c01/c01_08.md index 3d3c1e7..35f897a 100644 --- a/source/c01/c01_08.md +++ b/source/c01/c01_08.md @@ -250,4 +250,4 @@ A.__mro__ --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c01/c01_08.rst b/source/c01/c01_08.rst index 5c267a6..4772f18 100755 --- a/source/c01/c01_08.rst +++ b/source/c01/c01_08.rst @@ -269,7 +269,7 @@ C 搜索顺序中 X 和 Y 互换仍然不能解决问题,这时候它又会和 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c01/c01_09.md b/source/c01/c01_09.md index afbab09..1f69aff 100644 --- a/source/c01/c01_09.md +++ b/source/c01/c01_09.md @@ -66,5 +66,5 @@ class Airplane(Vehicle, PlaneMixin): --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c01/c01_09.rst b/source/c01/c01_09.rst index 275aa2d..78ba86d 100755 --- a/source/c01/c01_09.rst +++ b/source/c01/c01_09.rst @@ -73,6 +73,6 @@ C3 算法,如果你还不清楚,可以点击我的另一篇文章 ,了解 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c01/c01_10.md b/source/c01/c01_10.md index 7cea8d1..e94ffc3 100644 --- a/source/c01/c01_10.md +++ b/source/c01/c01_10.md @@ -157,7 +157,7 @@ Python 中的 def 语句在每次执行的时候都初始化一个函数对象 对于参数中提供了初始值的参数,由于 Python 中的函数参数传递的是对象,也可以认为是传地址,在第一次初始化 def 的时候,会先生成这个可变对象的内存地址,然后将这个默认参数 item_list 会与这个内存地址绑定。在后面的函数调用中,如果调用方指定了新的默认值,就会将原来的默认值覆盖。如果调用方没有指定新的默认值,那就会使用原来的默认值。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi7gsxkm4j20o007576y.jpg) +![](http://image.python-online.cn/20190511165650.png) ## 07. 访问类中的私有方法 @@ -598,7 +598,7 @@ python -m SimpleHTTPServer 8888 python3 -m http.server 8888 ``` -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi7e76gnjj20u00m3wkq.jpg) +![](http://image.python-online.cn/20190511165716.png) SimpleHTTPServer有一个特性,如果待共享的目录下有index.html,那么index.html文件会被视为默认主页;如果不存在index.html文件,那么就会显示整个目录列表。 @@ -915,7 +915,7 @@ Namespaces are one honking great idea -- let's do more of those! ``` 就会自动打开一个网页。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi7f9yuwgj20ko0kpdqj.jpg) +![](http://image.python-online.cn/20190511165735.png) ## 30. 局部/全局变量分不清? @@ -1053,4 +1053,4 @@ _666(f’是不是非常的_666’) --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c01/c01_10.rst b/source/c01/c01_10.rst index 9ac202d..cca1720 100755 --- a/source/c01/c01_10.rst +++ b/source/c01/c01_10.rst @@ -1160,11 +1160,11 @@ import 是 Python 导包的方式。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! -.. |image0| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi7gsxkm4j20o007576y.jpg -.. |image1| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi7e76gnjj20u00m3wkq.jpg -.. |image2| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi7f9yuwgj20ko0kpdqj.jpg +.. |image0| image:: http://image.python-online.cn/20190511165650.png +.. |image1| image:: http://image.python-online.cn/20190511165716.png +.. |image2| image:: http://image.python-online.cn/20190511165735.png diff --git a/source/c01/c01_11.md b/source/c01/c01_11.md index 405e2d4..22d5022 100644 --- a/source/c01/c01_11.md +++ b/source/c01/c01_11.md @@ -322,4 +322,4 @@ match.span() --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c01/c01_11.rst b/source/c01/c01_11.rst index e308ed8..7c75af0 100755 --- a/source/c01/c01_11.rst +++ b/source/c01/c01_11.rst @@ -344,6 +344,6 @@ start(),end(),end() -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c01/c01_12.md b/source/c01/c01_12.md index 2bea0f3..e0260f7 100644 --- a/source/c01/c01_12.md +++ b/source/c01/c01_12.md @@ -134,4 +134,4 @@ GB 18030 与 GB 2312-1980 和 GBK 兼容,共收录汉字70244个。 --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c01/c01_12.rst b/source/c01/c01_12.rst index c18c253..e02f73a 100755 --- a/source/c01/c01_12.rst +++ b/source/c01/c01_12.rst @@ -154,7 +154,7 @@ Python2默认是使用ASCII编码,这也是出现编码问题的罪魁祸首 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c01/c01_13.md b/source/c01/c01_13.md index aa70938..97b283c 100644 --- a/source/c01/c01_13.md +++ b/source/c01/c01_13.md @@ -124,4 +124,4 @@ from functools import reduce --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c01/c01_13.rst b/source/c01/c01_13.rst index fc28fb9..7f9b0b4 100755 --- a/source/c01/c01_13.rst +++ b/source/c01/c01_13.rst @@ -157,7 +157,7 @@ Pythonic ,在某一程度上代码看起来更加的简洁。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c01/c01_14.md b/source/c01/c01_14.md index b9206b5..bcdd72f 100644 --- a/source/c01/c01_14.md +++ b/source/c01/c01_14.md @@ -195,4 +195,4 @@ with open_func('/Users/MING/mytest.txt') as file_in: ------ -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c01/c01_14.rst b/source/c01/c01_14.rst index 8962b8f..a010e6d 100644 --- a/source/c01/c01_14.rst +++ b/source/c01/c01_14.rst @@ -209,7 +209,7 @@ open)的上下文管理器。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c01/c01_15.md b/source/c01/c01_15.md index a9fa441..9088a5c 100644 --- a/source/c01/c01_15.md +++ b/source/c01/c01_15.md @@ -68,4 +68,4 @@ a = [1,2,3]#迭代元素for item in a: print(item)#迭代索引for i in range ------ -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c01/c01_15.rst b/source/c01/c01_15.rst index 43792dc..150d384 100644 --- a/source/c01/c01_15.rst +++ b/source/c01/c01_15.rst @@ -78,6 +78,6 @@ comprehension),会产生整个列表,对大量数据的迭代会产生负 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c01/c01_16.md b/source/c01/c01_16.md index e0a2bd0..6618822 100644 --- a/source/c01/c01_16.md +++ b/source/c01/c01_16.md @@ -159,4 +159,4 @@ hello, world ------ -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c01/c01_16.rst b/source/c01/c01_16.rst index f2cec23..035d06f 100644 --- a/source/c01/c01_16.rst +++ b/source/c01/c01_16.rst @@ -166,6 +166,6 @@ -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c01/c01_17.md b/source/c01/c01_17.md index 6cbe461..e68c0be 100644 --- a/source/c01/c01_17.md +++ b/source/c01/c01_17.md @@ -289,4 +289,4 @@ https://blog.csdn.net/BF02jgtRS00XKtCx/article/details/82882622 --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c01/c01_17.rst b/source/c01/c01_17.rst index ac9e5bc..6a13002 100644 --- a/source/c01/c01_17.rst +++ b/source/c01/c01_17.rst @@ -309,7 +309,7 @@ https://blog.csdn.net/BF02jgtRS00XKtCx/article/details/82882622 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c02/c02_01.md b/source/c02/c02_01.md index ec95711..796a7b5 100644 --- a/source/c02/c02_01.md +++ b/source/c02/c02_01.md @@ -235,4 +235,4 @@ multi_process(io_simulation, type="模拟IO密集型") - 多进程虽然总是最快的,但是不一定是最优的选择,因为它需要CPU资源支持下才能体现优势 ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c02/c02_01.rst b/source/c02/c02_01.rst index 62a142e..280a1b5 100755 --- a/source/c02/c02_01.rst +++ b/source/c02/c02_01.rst @@ -248,7 +248,7 @@ -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c02/c02_02.md b/source/c02/c02_02.md index 948d5f9..999c561 100644 --- a/source/c02/c02_02.md +++ b/source/c02/c02_02.md @@ -133,4 +133,4 @@ t.name = "My-Thread" 至此,Python线程基础知识,我们大概都介绍完了。 ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c02/c02_02.rst b/source/c02/c02_02.rst index c89574d..4b58de6 100755 --- a/source/c02/c02_02.rst +++ b/source/c02/c02_02.rst @@ -144,6 +144,6 @@ -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c02/c02_03.md b/source/c02/c02_03.md index 87f8525..c4eacd1 100644 --- a/source/c02/c02_03.md +++ b/source/c02/c02_03.md @@ -328,4 +328,4 @@ t2.start() ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c02/c02_03.rst b/source/c02/c02_03.rst index dcd569b..5b549a6 100755 --- a/source/c02/c02_03.rst +++ b/source/c02/c02_03.rst @@ -356,6 +356,6 @@ CPython,所以也就默许了Python具有GIL锁这个事。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c02/c02_04.md b/source/c02/c02_04.md index a0fa09f..c01b09a 100644 --- a/source/c02/c02_04.md +++ b/source/c02/c02_04.md @@ -263,4 +263,4 @@ teacher.call('小亮') ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c02/c02_04.rst b/source/c02/c02_04.rst index 53e7b59..2f66d3a 100755 --- a/source/c02/c02_04.rst +++ b/source/c02/c02_04.rst @@ -287,6 +287,6 @@ Condition和Event 是类似的,并没有多大区别。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c02/c02_05.md b/source/c02/c02_05.md index 9587a2e..55579da 100644 --- a/source/c02/c02_05.md +++ b/source/c02/c02_05.md @@ -249,4 +249,4 @@ item5 ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c02/c02_05.rst b/source/c02/c02_05.rst index be04cb2..d09cbe4 100755 --- a/source/c02/c02_05.rst +++ b/source/c02/c02_05.rst @@ -259,6 +259,6 @@ Out),就是先进入队列的消息,将优先被消费。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c02/c02_06.md b/source/c02/c02_06.md index dbbd784..9b3cdc6 100644 --- a/source/c02/c02_06.md +++ b/source/c02/c02_06.md @@ -100,4 +100,4 @@ running thread-12504:1 构建线程池的方法,是可以很灵活的,大家有举可以自己多研究。但是建议只要掌握一种自己熟悉的,能快速上手的就好了。 ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c02/c02_06.rst b/source/c02/c02_06.rst index 3f79954..734669e 100755 --- a/source/c02/c02_06.rst +++ b/source/c02/c02_06.rst @@ -109,6 +109,6 @@ -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c02/c02_07.md b/source/c02/c02_07.md index 2979516..5b59491 100644 --- a/source/c02/c02_07.md +++ b/source/c02/c02_07.md @@ -355,4 +355,4 @@ if __name__ == '__main__': 下一章,我将讲一个Python3.5新引入的语法:`yield from`。篇幅也比较多,所以就单独拿出来讲。 ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c02/c02_07.rst b/source/c02/c02_07.rst index 8c3ed30..2df59be 100755 --- a/source/c02/c02_07.rst +++ b/source/c02/c02_07.rst @@ -383,7 +383,7 @@ -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c02/c02_08.md b/source/c02/c02_08.md index 9e38bf2..eedaee7 100644 --- a/source/c02/c02_08.md +++ b/source/c02/c02_08.md @@ -333,4 +333,4 @@ RESULT = _r --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c02/c02_08.rst b/source/c02/c02_08.rst index 93724ea..47f28f3 100755 --- a/source/c02/c02_08.rst +++ b/source/c02/c02_08.rst @@ -359,6 +359,6 @@ from后面加上可迭代对象,他可以把可迭代对象里的每个元素 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c02/c02_09.md b/source/c02/c02_09.md index 84b5451..a068cca 100644 --- a/source/c02/c02_09.md +++ b/source/c02/c02_09.md @@ -218,7 +218,6 @@ loop.run_until_complete(task) ``` emmm,和上面的结果是一样的。nice - --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c02/c02_09.rst b/source/c02/c02_09.rst index f5fd737..76d8bfd 100755 --- a/source/c02/c02_09.rst +++ b/source/c02/c02_09.rst @@ -244,7 +244,7 @@ emmm,和上面的结果是一样的。nice -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c02/c02_10.md b/source/c02/c02_10.md index b6a101f..2112599 100644 --- a/source/c02/c02_10.md +++ b/source/c02/c02_10.md @@ -458,4 +458,4 @@ loop.close() ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c02/c02_10.rst b/source/c02/c02_10.rst index fe24d4c..2959ba6 100755 --- a/source/c02/c02_10.rst +++ b/source/c02/c02_10.rst @@ -507,6 +507,6 @@ asyncio.gather -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c02/c02_11.md b/source/c02/c02_11.md index 241c042..3db06e1 100644 --- a/source/c02/c02_11.md +++ b/source/c02/c02_11.md @@ -211,4 +211,4 @@ Thu May 31 23:42:48 2018 ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c02/c02_11.rst b/source/c02/c02_11.rst index 396eca0..03a9295 100755 --- a/source/c02/c02_11.rst +++ b/source/c02/c02_11.rst @@ -216,7 +216,7 @@ -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c02/c02_12.md b/source/c02/c02_12.md index 9568330..b452d33 100644 --- a/source/c02/c02_12.md +++ b/source/c02/c02_12.md @@ -104,6 +104,8 @@ MING.send('香肠') +协程很类似于Javascript单线程下异步处理的概念,协程同样是单线程的,之所以能够进行并发是因为通过某种方式保存了执行栈的上下文,在一定条件下将执行权交由其他栈,在一定条件下又通过执行栈上下文恢复栈。 +--- -协程很类似于Javascript单线程下异步处理的概念,协程同样是单线程的,之所以能够进行并发是因为通过某种方式保存了执行栈的上下文,在一定条件下将执行权交由其他栈,在一定条件下又通过执行栈上下文恢复栈。 \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c02/c02_12.rst b/source/c02/c02_12.rst index 54d3833..4caac97 100644 --- a/source/c02/c02_12.rst +++ b/source/c02/c02_12.rst @@ -104,3 +104,9 @@ yield   (2)进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序 协程很类似于Javascript单线程下异步处理的概念,协程同样是单线程的,之所以能够进行并发是因为通过某种方式保存了执行栈的上下文,在一定条件下将执行权交由其他栈,在一定条件下又通过执行栈上下文恢复栈。 + +-------------- + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + diff --git a/source/c03/c03_01.md b/source/c03/c03_01.md index d888b6f..91af06b 100644 --- a/source/c03/c03_01.md +++ b/source/c03/c03_01.md @@ -456,4 +456,4 @@ def timeout_limit(timeout_time): ``` ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c03/c03_01.rst b/source/c03/c03_01.rst index 66cb5f2..2ba08a6 100755 --- a/source/c03/c03_01.rst +++ b/source/c03/c03_01.rst @@ -251,7 +251,99 @@ emmmm,这很NB。。。 [WARNING]: the function say() is running... say hello! -3.1.7 内置装饰器:property +3.1.7 wrapper 装饰器有啥用? +---------------------------- + +在 functools 标准库中有提供一个 wrapper +装饰器,你应该也经常见过,那他有啥用呢? + +先来看一个例子 + +.. code:: python + + def wrapper(func): + def inner_function(): + pass + return inner_function + + @wrapper + def wrapped(): + pass + + print(wrapped.__name__) + #inner_function + +为什么会这样子?不是应该返回 ``func`` 吗? + +这也不难理解,因为上边执行\ ``func`` 和下边 ``decorator(func)`` +是等价的,所以上面 ``func.__name__`` +是等价于下面\ ``decorator(func).__name__`` 的,那当然名字是 +``inner_function`` + +.. code:: python + + def wrapper(func): + def inner_function(): + pass + return inner_function + + def wrapped(): + pass + + print(wrapper(wrapped).__name__) + #inner_function + +那如何避免这种情况的产生?方法是使用 functools .wrapper +装饰器,它的作用就是将 **被修饰的函数(wrapped)** 的一些属性值赋值给 +**修饰器函数(wrapper)** ,最终让属性的显示更符合我们的直觉。 + +.. code:: python + + from functools import wraps + + def wrapper(func): + @wraps(func) + def inner_function(): + pass + return inner_function + + @wrapper + def wrapped(): + pass + + print(wrapped.__name__) + # wrapped + +准确点说,wrapper 其实是一个偏函数对象(partial),源码如下 + +.. code:: python + + def wraps(wrapped, + assigned = WRAPPER_ASSIGNMENTS, + updated = WRAPPER_UPDATES): + return partial(update_wrapper, wrapped=wrapped, + assigned=assigned, updated=updated) + +可以看到wraps其实就是调用了一个函数\ ``update_wrapper``\ ,知道原理后,我们改写上面的代码,在不使用 +wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码如下: + +.. code:: python + + from functools import update_wrapper + + def wrapper(func): + def inner_function(): + pass + update_wrapper(func, inner_function) + return inner_function + + def wrapped(): + pass + + print(wrapped.__name__) + # wrapped + +3.1.8 内置装饰器:property -------------------------- 以上,我们介绍的都是自定义的装饰器。 @@ -369,7 +461,7 @@ emmmm,这很NB。。。 ``@age.deleter`` 使得我们可以使用\ ``del XiaoMing.age``\ 这样的方式来删除属性。 -3.1.8 其他装饰器:装饰器实战 +3.1.9 其他装饰器:装饰器实战 ---------------------------- 读完并理解了上面的内容,你可以说是Python高手了。别怀疑,自信点,因为很多人都不知道装饰器有这么多用法呢。 @@ -407,6 +499,6 @@ emmmm,这很NB。。。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c03/c03_02.md b/source/c03/c03_02.md index 3fbeebd..93521da 100644 --- a/source/c03/c03_02.md +++ b/source/c03/c03_02.md @@ -271,4 +271,4 @@ class ModelMetaClass(type): ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c03/c03_02.rst b/source/c03/c03_02.rst index 7f6a227..bc6cccb 100755 --- a/source/c03/c03_02.rst +++ b/source/c03/c03_02.rst @@ -296,6 +296,6 @@ ORM的一个类(User),就对应数据库中的一张表。id,name,email,passwo -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c03/c03_03.md b/source/c03/c03_03.md index bf91eca..acc45a3 100644 --- a/source/c03/c03_03.md +++ b/source/c03/c03_03.md @@ -257,4 +257,4 @@ if __name__ == '__main__': ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c03/c03_03.rst b/source/c03/c03_03.rst index 2e274db..8a515d5 100755 --- a/source/c03/c03_03.rst +++ b/source/c03/c03_03.rst @@ -292,7 +292,7 @@ -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c03/c03_04.md b/source/c03/c03_04.md index e13da44..3b75ab8 100644 --- a/source/c03/c03_04.md +++ b/source/c03/c03_04.md @@ -451,4 +451,4 @@ vim /etc/nginx/nginx.conf --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c03/c03_04.rst b/source/c03/c03_04.rst index 850b0e4..bd7fba0 100755 --- a/source/c03/c03_04.rst +++ b/source/c03/c03_04.rst @@ -510,7 +510,7 @@ -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c03/c03_05.md b/source/c03/c03_05.md index d8c29a6..2e32aac 100644 --- a/source/c03/c03_05.md +++ b/source/c03/c03_05.md @@ -457,4 +457,4 @@ with app.app_context(): --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c03/c03_05.rst b/source/c03/c03_05.rst index c8299a7..2a99cae 100644 --- a/source/c03/c03_05.rst +++ b/source/c03/c03_05.rst @@ -498,7 +498,7 @@ app 的上下文信息是否已经 push 进去了,如果没有的话,就会 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c04/c04_01.md b/source/c04/c04_01.md index 66dce83..4b888f1 100644 --- a/source/c04/c04_01.md +++ b/source/c04/c04_01.md @@ -233,4 +233,4 @@ $ ./ming.py ![](https://i.loli.net/2018/06/11/5b1e812db603f.png) ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c04/c04_01.rst b/source/c04/c04_01.rst index 6f4688b..194a761 100755 --- a/source/c04/c04_01.rst +++ b/source/c04/c04_01.rst @@ -255,7 +255,7 @@ https://virtualenvwrapper.readthedocs.io/en/latest/command_ref.html -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c04/c04_02.md b/source/c04/c04_02.md index 38b0012..6bd3551 100644 --- a/source/c04/c04_02.md +++ b/source/c04/c04_02.md @@ -2,6 +2,8 @@ --- +![](http://image.python-online.cn/20190511162815.png) + 做为一名开发人员,我们难免都会与服务器打交道。 有时候是公司的线上生产环境,你需要上去部署公司的项目。 @@ -22,12 +24,12 @@ 查看 -> 快速命令 双击底部自定义快速命令。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi7ufv5q7j20bc00vt8t.jpg) +![](http://image.python-online.cn/20190511162524.png) **使用收藏栏** 点击最左侧按按钮添加收藏。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi7vlhtfjj20b700xt8t.jpg) +![](http://image.python-online.cn/20190511162607.png) **快捷设置** @@ -37,14 +39,14 @@ ② 双击分隔符 ③ 选中即复制 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi7w4nnp9j20cu09ojtv.jpg) +![](http://image.python-online.cn/20190511162716.png) **设置Meta键** 文件 -> 属性 -> 键盘 一定要打钩,这是后面诸多快捷使用的前提。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi7wybscyj20fo0dojwj.jpg) +![](http://image.python-online.cn/20190511162730.png) ## 移动光标 ``` @@ -154,4 +156,4 @@ name0=ubuntu --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c04/c04_02.rst b/source/c04/c04_02.rst index 92f729d..28a3f0e 100755 --- a/source/c04/c04_02.rst +++ b/source/c04/c04_02.rst @@ -3,6 +3,8 @@ -------------- +|image0| + 做为一名开发人员,我们难免都会与服务器打交道。 有时候是公司的线上生产环境,你需要上去部署公司的项目。 @@ -23,11 +25,11 @@ 查看 -> 快速命令 -双击底部自定义快速命令。 |image0| +双击底部自定义快速命令。 |image1| **使用收藏栏** -点击最左侧按按钮添加收藏。 |image1| +点击最左侧按按钮添加收藏。 |image2| **快捷设置** @@ -35,13 +37,13 @@ ① 右键粘贴 ② 双击分隔符 ③ 选中即复制 -|image2| +|image3| **设置Meta键** 文件 -> 属性 -> 键盘 -一定要打钩,这是后面诸多快捷使用的前提。 |image3| +一定要打钩,这是后面诸多快捷使用的前提。 |image4| 移动光标 -------- @@ -164,12 +166,13 @@ -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! -.. |image0| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi7ufv5q7j20bc00vt8t.jpg -.. |image1| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi7vlhtfjj20b700xt8t.jpg -.. |image2| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi7w4nnp9j20cu09ojtv.jpg -.. |image3| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi7wybscyj20fo0dojwj.jpg +.. |image0| image:: http://image.python-online.cn/20190511162815.png +.. |image1| image:: http://image.python-online.cn/20190511162524.png +.. |image2| image:: http://image.python-online.cn/20190511162607.png +.. |image3| image:: http://image.python-online.cn/20190511162716.png +.. |image4| image:: http://image.python-online.cn/20190511162730.png diff --git a/source/c04/c04_03.md b/source/c04/c04_03.md index b1facb7..2d4025b 100644 --- a/source/c04/c04_03.md +++ b/source/c04/c04_03.md @@ -27,20 +27,18 @@ 以我的博客(`python-online.cn`)为例,先给大家展示一下。 这是首页。显示了你所有的文章索引。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi5kfvq7uj20oq0jv10c.jpg) - +![](http://image.python-online.cn/20190511160523.png) 这是我的导航栏。是不是结构很清晰,很方便索引。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi5npgge0j20u00hwajm.jpg) - +![](http://image.python-online.cn/20190511161056.png) 点击文章后,还可以很方便查看标题,跳转。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi5pkdho7j20u00nhh42.jpg) +![](http://image.python-online.cn/20190511161130.png) 体验下搜索功能,速度很快。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi5r963x7j20tq0hfqed.jpg) +![](http://image.python-online.cn/20190511161147.png) 看完这些你是不是也很想拥有这样一个博客呢? @@ -207,11 +205,10 @@ The HTML pages are in build\html. 执行完了后,你可以发现原先的build,不再是空文件夹了。 我们点进去 build\html\ 目录,使用浏览器打开index.html文件。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi5rx5jb5j20ps0dpdjk.jpg) - +![](http://image.python-online.cn/20190511161212.png) 真棒,已经完成了一半了。点击 我们刚写的 暴富指南。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi5soj3fgj20p00bmgoh.jpg) +![](http://image.python-online.cn/20190511161240.png) @@ -240,18 +237,20 @@ build/ 你需要先去 Read the Docs 注册下帐号。 关联一下GitHub -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi5tldl8lj20pu0fhwhn.jpg) +![](http://image.python-online.cn/20190511161255.png) -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi5tyvdivj20hv0jdq6t.jpg) +![](http://image.python-online.cn/20190511161311.png) 导入代码库。填好与你对应的信息。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi5usvta9j20nx0a5dhy.jpg) +![](http://image.python-online.cn/20190511161334.png) + +![](http://image.python-online.cn/20190511161414.png) -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi5vacypjj20p80eewhh.jpg) +构建网页后。右下方,你可以看见你的在线地址。 -构建网页后。右下方,你可以看见你的在线地址。 ![](https://ws1.sinaimg.cn/large/8f640247gy1fyi5vum6o1j20j40g7tcp.jpg) +![](http://image.python-online.cn/20190511161426.png) 这里要提醒一下的是,Sphinx的文档格式,默认是 rst 格式,如果你习惯了使用Markdown来写文章,可以使用 Pandoc 这个神器转换一下。 @@ -277,4 +276,4 @@ pandoc -V mainfont="SimSun" -f markdown -t rst hello.md -o hello.rst --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c04/c04_03.rst b/source/c04/c04_03.rst index cdc9a7b..dc9101e 100755 --- a/source/c04/c04_03.rst +++ b/source/c04/c04_03.rst @@ -251,7 +251,9 @@ source:raw-latex:`\index`.rst,千万要注意中间的空行不可忽略。 |image9| -构建网页后。右下方,你可以看见你的在线地址。 |image10| +构建网页后。右下方,你可以看见你的在线地址。 + +|image10| 这里要提醒一下的是,Sphinx的文档格式,默认是 rst 格式,如果你习惯了使用Markdown来写文章,可以使用 Pandoc @@ -278,19 +280,19 @@ source:raw-latex:`\index`.rst,千万要注意中间的空行不可忽略。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! -.. |image0| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi5kfvq7uj20oq0jv10c.jpg -.. |image1| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi5npgge0j20u00hwajm.jpg -.. |image2| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi5pkdho7j20u00nhh42.jpg -.. |image3| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi5r963x7j20tq0hfqed.jpg -.. |image4| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi5rx5jb5j20ps0dpdjk.jpg -.. |image5| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi5soj3fgj20p00bmgoh.jpg -.. |image6| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi5tldl8lj20pu0fhwhn.jpg -.. |image7| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi5tyvdivj20hv0jdq6t.jpg -.. |image8| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi5usvta9j20nx0a5dhy.jpg -.. |image9| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi5vacypjj20p80eewhh.jpg -.. |image10| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi5vum6o1j20j40g7tcp.jpg +.. |image0| image:: http://image.python-online.cn/20190511160523.png +.. |image1| image:: http://image.python-online.cn/20190511161056.png +.. |image2| image:: http://image.python-online.cn/20190511161130.png +.. |image3| image:: http://image.python-online.cn/20190511161147.png +.. |image4| image:: http://image.python-online.cn/20190511161212.png +.. |image5| image:: http://image.python-online.cn/20190511161240.png +.. |image6| image:: http://image.python-online.cn/20190511161255.png +.. |image7| image:: http://image.python-online.cn/20190511161311.png +.. |image8| image:: http://image.python-online.cn/20190511161334.png +.. |image9| image:: http://image.python-online.cn/20190511161414.png +.. |image10| image:: http://image.python-online.cn/20190511161426.png diff --git a/source/c04/c04_04.md b/source/c04/c04_04.md index df08068..9200afc 100644 --- a/source/c04/c04_04.md +++ b/source/c04/c04_04.md @@ -27,7 +27,7 @@ Anaconda 号称是适用于企业级大数据分析的Python工具。它包含 ## 4.4.1 下载安装 到官网下载 [Anaconda](https://www.anaconda.com/download/) -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi7zx63usj20pd0cnacs.jpg) +![](http://image.python-online.cn/20190511163102.png) 我这里选择下载 Py3.6,下载下来是 exe 文件(600多M)。安装即可。 安装完成后,还会提示你是否安装 VS Code,自行选择。不安装直接 Skip 就好。 @@ -35,20 +35,20 @@ Anaconda 号称是适用于企业级大数据分析的Python工具。它包含 ## 4.4.2 启动程序 点击打开 Anaconda 。会出现这个界面。第一眼,我看到了很多工具,包含 NoteBook,qtconsole,VS Ccode等。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi80i9egzj20u00it7cr.jpg) +![](http://image.python-online.cn/20190511163123.png) 这里就介绍一下,我最经常使用的 NoteBook 就好了。其他的大家自行尝试。 选择 NoteBook,点击 Launch,会启动浏览器打开一个 web 页面。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi816wlzsj20u00d6418.jpg) +![](http://image.python-online.cn/20190511163137.png) 点击右上角的new,就可以进入 Python3 的交互界面。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi81o93ooj20cu0630tc.jpg) +![](http://image.python-online.cn/20190511163145.png) 来感受一下,如何使用这个工具。 每一行的命令,都在一个可编辑的输入框。即使你的命令输入有误,你也不用从头写代码,直接编辑后重新执行即可。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi81u4cb1j20u00bdq5l.jpg) +![](http://image.python-online.cn/20190511163200.png) 还有一点,命令的执行是可间断的,某个命令执行错误,不会导致整个程序中断,这将很方便我们调试代码,只要改完代码,再重新执行该行代码即可,而不用重新执行全部代码。 @@ -151,21 +151,21 @@ Anaconda 号称是适用于企业级大数据分析的Python工具。它包含 其实以上快捷键,在非编辑模式下,按 h 就会出现快捷键帮助菜单。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi85d6y8sj20ja0ks498.jpg) -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi85qa2hyj20j70gyjx4.jpg) +![](http://image.python-online.cn/20190511163245.png) +![](http://image.python-online.cn/20190511163253.png) ## 4.4.4 导出笔记文件 NoteBook 既然支持 Markdown ,你已经也能想到它可以用来记录学习笔记。 它提供多种常用的文件格式,md,rst,pdf等。如果你希望再次编辑,可以保存为ipynb,这是Jupyter的文件格式,可以再次打开进行编辑。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi84fbfwtj20jo0ebae1.jpg) +![](http://image.python-online.cn/20190511163304.png) 以前我学习 Pandas 的时候,也曾经使用它做过笔记,输出的是PDF文件,可以按目录导航,相当方便。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi83w3zuij20t00ilq8x.jpg) +![](http://image.python-online.cn/20190511163311.png) 好了,大概就是这些内容。 ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c04/c04_04.rst b/source/c04/c04_04.rst index 04959e0..f642750 100755 --- a/source/c04/c04_04.rst +++ b/source/c04/c04_04.rst @@ -237,17 +237,17 @@ NoteBook 既然支持 Markdown ,你已经也能想到它可以用来记录学 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! -.. |image0| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi7zx63usj20pd0cnacs.jpg -.. |image1| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi80i9egzj20u00it7cr.jpg -.. |image2| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi816wlzsj20u00d6418.jpg -.. |image3| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi81o93ooj20cu0630tc.jpg -.. |image4| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi81u4cb1j20u00bdq5l.jpg -.. |image5| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi85d6y8sj20ja0ks498.jpg -.. |image6| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi85qa2hyj20j70gyjx4.jpg -.. |image7| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi84fbfwtj20jo0ebae1.jpg -.. |image8| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi83w3zuij20t00ilq8x.jpg +.. |image0| image:: http://image.python-online.cn/20190511163102.png +.. |image1| image:: http://image.python-online.cn/20190511163123.png +.. |image2| image:: http://image.python-online.cn/20190511163137.png +.. |image3| image:: http://image.python-online.cn/20190511163145.png +.. |image4| image:: http://image.python-online.cn/20190511163200.png +.. |image5| image:: http://image.python-online.cn/20190511163245.png +.. |image6| image:: http://image.python-online.cn/20190511163253.png +.. |image7| image:: http://image.python-online.cn/20190511163304.png +.. |image8| image:: http://image.python-online.cn/20190511163311.png diff --git a/source/c04/c04_05.md b/source/c04/c04_05.md index 5eeb4e5..7e2f405 100644 --- a/source/c04/c04_05.md +++ b/source/c04/c04_05.md @@ -36,15 +36,15 @@ Linux 的发行版本有很多,这里选择 Ubuntu 并没有什么特殊的缘 这里上一张我安装硬盘的记念图。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi876yfb2j20mi0f7k5i.jpg) +![](http://image.python-online.cn/20190511163441.png) 你没有看错,我的是台式机。说起这台电脑,还是前年我自己搜罗配置单,自己从各大电商平台,有京东,淘宝,还有天猫买了所有的配件,然后自己一件一件组装起来的。还是挺有感情的,虽然也是渣渣配置,但是这个过程还是很愉快的。在有了每一次装体验后,后面我还给别人装过好几台,如果说高三是我知识储备最高的时期,那前年就是我动手能力最强的时期的。组装过将近十台的电脑。这都是题外话了。 第一次装好硬盘后呢,要进入原先的 Windows 系统,检查一下,我们安装的硬盘有没有装成功。由于我装了工具,开始键和你们的可能不一样(不过真的是Win10),你也可以右击桌面 「我的电脑」,再点击「管理」。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi87yu95mj20ix0fkaf2.jpg) +![](http://image.python-online.cn/20190511163457.png) 如果硬盘安装成功,这里会有一块未分配的盘,如图中的 硬盘0。 第一次使用,需要初始化硬盘,记得选 GPT。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi885fqgzj20ku076772.jpg) +![](http://image.python-online.cn/20190511163510.png) 这里可以不用急着分区(在后面安装系统时会让你分的),如果你要提前分好(使用DiskGenius),也没有关系。 @@ -60,11 +60,11 @@ iso 和 UltraISO 都准备完成后,就可以安装U盘系统了。 首先,打开软件,点击 文件 - 打开,选择你所下载的 iso 文件。出现如下界面 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi88gwzuoj20le0ftk12.jpg) +![](http://image.python-online.cn/20190511163520.png) 再点击 启动 - 写入硬盘映像 - 写入 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi88xywp1j20f00dxabl.jpg) +![](http://image.python-online.cn/20190511163531.png) 如一切顺利,U盘就制作完成,一般 99.99% 都不会在这地方出错。 @@ -74,7 +74,7 @@ iso 和 UltraISO 都准备完成后,就可以安装U盘系统了。 但在这里,必须关闭 win10的快速启动功能。方法如下:取消勾选「启用快速启动」,点击保存修改。然后就可以正常关机了。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi894tqeuj20lt0ga77x.jpg) +![](http://image.python-online.cn/20190511163542.png) ## 4.5.5 安装Ubuntu @@ -84,7 +84,7 @@ iso 和 UltraISO 都准备完成后,就可以安装U盘系统了。 查找完后,你可以对电脑开机了,按住你的快捷键,选择启动方式。由于我们要从U盘启动,所以这里选择 这里一定要注意,选择最后一个,自定义选项。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi89df34mj20pb0hd0zh.jpg) +![](http://image.python-online.cn/20190511163550.png) 终于到了分区的这一步了,这是最关键的一步。对于Linux不熟悉的人,到这里可能会懵逼。不用怕,这里给你提供一个最简单的分区配置。具体为什么这么分,我想你并不关心吧? @@ -96,27 +96,35 @@ iso 和 UltraISO 都准备完成后,就可以安装U盘系统了。 对,就是这么简单粗暴。如果你想更加精细一点,你还可以自定义 /home,/usr等。 分区完成后,一定要注意如下这二个红框,选择安装启动引导器的设备为 我们刚刚设置的efi分区。检查无误后,就可以点击「现在安装」。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi89ns0l9j20rd0kqaso.jpg) +![](http://image.python-online.cn/20190511163559.png) 接下来就是 选择时区 - 配置键盘。 -![选择时区](https://ws1.sinaimg.cn/large/8f640247gy1fyi8aflvq7j20je0ilwhc.jpg) +![](http://image.python-online.cn/20190511163612.png) + +![](http://image.python-online.cn/20190511163633.png) + -![配置键盘](https://ws1.sinaimg.cn/large/8f640247gy1fyi89t69q1j20jj0ipgnx.jpg) 到了这里,你应该可以长舒第一口气了。成功了一半了。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi8apjj1pj20je0e57ci.jpg) +![](http://image.python-online.cn/20190511163700.png) 如果你和我一样使用 SSD ,应该不出5分钟系统就可以安装完毕。弹出如下界面。点击现在重启。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi8dad24hj20jk03qwfh.jpg) +![](http://image.python-online.cn/20190511163711.png) 重启的过程,记住还是一样按住你的快捷键,我这里仍然是 F11,看到没有,已经有一个叫 ubuntu的启动设备。就它了,选择进入系统。接下来,就是选择要以哪种模式进入ubuntu,你根据需要去选吧。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi8douz7ij20r80kaqfq.jpg) +![](http://image.python-online.cn/20190511163722.png) ## 4.5.6 效果展示 由于默认的Ubuntu主题也是丑得可以,经过一个晚上的美化,它变成如下这般帅气逼人。 -https://www.jianshu.com/p/16b36b912b02 +![](http://image.python-online.cn/20190511163731.png) + +![](http://image.python-online.cn/20190511163750.png) + +![](http://image.python-online.cn/20190511163757.png) + +![](http://image.python-online.cn/20190511163805.png) ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c04/c04_05.rst b/source/c04/c04_05.rst index c00b1fd..830d49a 100755 --- a/source/c04/c04_05.rst +++ b/source/c04/c04_05.rst @@ -127,47 +127,55 @@ Bios,更改顺序。但是我这里不想这么麻烦,因为安装完后又 分区完成后,一定要注意如下这二个红框,选择安装启动引导器的设备为 我们刚刚设置的efi分区。检查无误后,就可以点击「现在安装」。 |image7| -接下来就是 选择时区 - 配置键盘。 |选择时区| +接下来就是 选择时区 - 配置键盘。 |image8| -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi89t69q1j20jj0ipgnx.jpg - :alt: 配置键盘 +|image9| - 配置键盘 - -到了这里,你应该可以长舒第一口气了。成功了一半了。 |image9| +到了这里,你应该可以长舒第一口气了。成功了一半了。 |image10| 如果你和我一样使用 SSD ,应该不出5分钟系统就可以安装完毕。弹出如下界面。点击现在重启。 -|image10| +|image11| 重启的过程,记住还是一样按住你的快捷键,我这里仍然是 F11,看到没有,已经有一个叫 ubuntu的启动设备。就它了,选择进入系统。接下来,就是选择要以哪种模式进入ubuntu,你根据需要去选吧。 -|image11| +|image12| 4.5.6 效果展示 -------------- 由于默认的Ubuntu主题也是丑得可以,经过一个晚上的美化,它变成如下这般帅气逼人。 -https://www.jianshu.com/p/16b36b912b02 +|image13| + +|image14| + +|image15| + +|image16| -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! -.. |image0| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi876yfb2j20mi0f7k5i.jpg -.. |image1| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi87yu95mj20ix0fkaf2.jpg -.. |image2| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi885fqgzj20ku076772.jpg -.. |image3| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi88gwzuoj20le0ftk12.jpg -.. |image4| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi88xywp1j20f00dxabl.jpg -.. |image5| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi894tqeuj20lt0ga77x.jpg -.. |image6| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi89df34mj20pb0hd0zh.jpg -.. |image7| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi89ns0l9j20rd0kqaso.jpg -.. |选择时区| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi8aflvq7j20je0ilwhc.jpg -.. |image9| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi8apjj1pj20je0e57ci.jpg -.. |image10| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi8dad24hj20jk03qwfh.jpg -.. |image11| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi8douz7ij20r80kaqfq.jpg +.. |image0| image:: http://image.python-online.cn/20190511163441.png +.. |image1| image:: http://image.python-online.cn/20190511163457.png +.. |image2| image:: http://image.python-online.cn/20190511163510.png +.. |image3| image:: http://image.python-online.cn/20190511163520.png +.. |image4| image:: http://image.python-online.cn/20190511163531.png +.. |image5| image:: http://image.python-online.cn/20190511163542.png +.. |image6| image:: http://image.python-online.cn/20190511163550.png +.. |image7| image:: http://image.python-online.cn/20190511163559.png +.. |image8| image:: http://image.python-online.cn/20190511163612.png +.. |image9| image:: http://image.python-online.cn/20190511163633.png +.. |image10| image:: http://image.python-online.cn/20190511163700.png +.. |image11| image:: http://image.python-online.cn/20190511163711.png +.. |image12| image:: http://image.python-online.cn/20190511163722.png +.. |image13| image:: http://image.python-online.cn/20190511163731.png +.. |image14| image:: http://image.python-online.cn/20190511163750.png +.. |image15| image:: http://image.python-online.cn/20190511163757.png +.. |image16| image:: http://image.python-online.cn/20190511163805.png diff --git a/source/c04/c04_06.md b/source/c04/c04_06.md index e814162..83f564e 100644 --- a/source/c04/c04_06.md +++ b/source/c04/c04_06.md @@ -315,7 +315,7 @@ ssh-keygen -t rsa -C "wongbingming@163.com" 生成的仅钥 `/c/Users/wangbm/.ssh/id_rsa.pub`,需要在Github上添加。 在github上添加ssh keys方法如下: -![](https://i.loli.net/2018/04/15/5ad2c14727198.png) +![](http://image.python-online.cn/20190511163855.png) 添加完后,可以使用这个命令测试一下。 ``` @@ -359,4 +359,4 @@ Git 的使用全都是命令行的,由于没有那么直观,所以很容易 --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c04/c04_06.rst b/source/c04/c04_06.rst index 6fbb85e..ea1f78e 100755 --- a/source/c04/c04_06.rst +++ b/source/c04/c04_06.rst @@ -397,12 +397,12 @@ Desktop真心觉得不好用)。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! .. |image0| image:: https://i.loli.net/2018/04/15/5ad2c06e8893d.png -.. |image1| image:: https://i.loli.net/2018/04/15/5ad2c14727198.png +.. |image1| image:: http://image.python-online.cn/20190511163855.png .. |image2| image:: https://i.loli.net/2018/04/15/5ad2c2a9813b9.png .. |image3| image:: http://image.python-online.cn/20190430235625.png diff --git a/source/c04/c04_07.md b/source/c04/c04_07.md index 6a54a0f..2716b14 100644 --- a/source/c04/c04_07.md +++ b/source/c04/c04_07.md @@ -724,4 +724,4 @@ git clone git@github.com:BingmingWong/BingmingWong.github.io.git --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c04/c04_07.rst b/source/c04/c04_07.rst index 625c739..7612799 100755 --- a/source/c04/c04_07.rst +++ b/source/c04/c04_07.rst @@ -821,7 +821,7 @@ bash窗口,不然会提示找不到npm命令) -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c04/c04_08.md b/source/c04/c04_08.md index afd4c5d..c339ae4 100644 --- a/source/c04/c04_08.md +++ b/source/c04/c04_08.md @@ -386,4 +386,4 @@ i 输入模式(没什么用),注意的是退出输入模式(搜索框 --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c04/c04_08.rst b/source/c04/c04_08.rst index 8ecd202..d590ded 100755 --- a/source/c04/c04_08.rst +++ b/source/c04/c04_08.rst @@ -412,6 +412,6 @@ URL相关 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c04/c04_09.md b/source/c04/c04_09.md index f51f6ff..bec495b 100644 --- a/source/c04/c04_09.md +++ b/source/c04/c04_09.md @@ -617,4 +617,4 @@ set character_set_results = gbk; --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c04/c04_09.rst b/source/c04/c04_09.rst index 95c16cc..a7744e8 100755 --- a/source/c04/c04_09.rst +++ b/source/c04/c04_09.rst @@ -705,7 +705,7 @@ SO,如果是提交的是英文的话,不会有冲突。因为都是一个字 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c04/c04_10.md b/source/c04/c04_10.md index da8537b..92b160b 100644 --- a/source/c04/c04_10.md +++ b/source/c04/c04_10.md @@ -468,4 +468,4 @@ WHERE table_schema LIKE '%zabbix%' ORDER BY Total desc; --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c04/c04_10.rst b/source/c04/c04_10.rst index de63a93..cbf4e14 100755 --- a/source/c04/c04_10.rst +++ b/source/c04/c04_10.rst @@ -536,7 +536,7 @@ limit子句:限制返回数据的量。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c04/c04_11.md b/source/c04/c04_11.md index defe5a9..58d1742 100644 --- a/source/c04/c04_11.md +++ b/source/c04/c04_11.md @@ -158,4 +158,4 @@ WantedBy=multi-user.target --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c04/c04_11.rst b/source/c04/c04_11.rst index ca5f986..cc5afcb 100644 --- a/source/c04/c04_11.rst +++ b/source/c04/c04_11.rst @@ -173,7 +173,7 @@ Host -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c04/c04_12.md b/source/c04/c04_12.md index 5006ab0..3e9a2de 100644 --- a/source/c04/c04_12.md +++ b/source/c04/c04_12.md @@ -147,5 +147,5 @@ pdb.set_trace() ------ -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c04/c04_12.rst b/source/c04/c04_12.rst index 9b35dee..1ce62d7 100644 --- a/source/c04/c04_12.rst +++ b/source/c04/c04_12.rst @@ -174,7 +174,7 @@ pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c04/c04_13.md b/source/c04/c04_13.md index 0c00254..df956dd 100644 --- a/source/c04/c04_13.md +++ b/source/c04/c04_13.md @@ -347,4 +347,4 @@ single,"--name", '-n',--frequency,module_args,处理函数:main_single --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c04/c04_13.rst b/source/c04/c04_13.rst index f9370fd..dc5d3d7 100644 --- a/source/c04/c04_13.rst +++ b/source/c04/c04_13.rst @@ -374,7 +374,7 @@ arguments)。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c04/c04_14.md b/source/c04/c04_14.md index 951b7b6..294a838 100644 --- a/source/c04/c04_14.md +++ b/source/c04/c04_14.md @@ -132,4 +132,4 @@ $ pipenv check --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c04/c04_14.rst b/source/c04/c04_14.rst index f430b0c..f24a60a 100644 --- a/source/c04/c04_14.rst +++ b/source/c04/c04_14.rst @@ -137,7 +137,7 @@ mytest是我们的项目目录。如果是python3.6 应该是自带的。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index 8969c50..c528a8f 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -454,4 +454,4 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c04/c04_15.rst b/source/c04/c04_15.rst index f9acbeb..85a52b9 100644 --- a/source/c04/c04_15.rst +++ b/source/c04/c04_15.rst @@ -307,22 +307,104 @@ myname 会被当成是一个单词对待,由于它在单词库里并没有它 |image29| -4.15.11 使用Git控制版本 ------------------------ +4.15.11 一键进行代码性能分析 +---------------------------- + +在 Python 中有许多模块可以帮助你分析并找出你的项目中哪里出现了性能问题。 + +比如,常用的模块有 +cProfile,在某些框架中,也内置了中间件帮助你进行性能分析,比如 Django +,WSGI。 + +做为Python 的第一 IDE, PyCharm +本身就支持了这项功能。而且使用非常方便,小白。 + +假设现在要分析如下这段代码的性能损耗情况,找出到底哪个函数耗时最多 + +.. code:: python + + import time + + def fun1(): + time.sleep(1) + + def fun2(): + time.sleep(1) -输入仓库地址 + def fun3(): + time.sleep(2) + + def fun4(): + time.sleep(1) + + def fun5(): + time.sleep(1) + fun4() + + fun1() + fun2() + fun3() + fun5() + +点击 Run -> Profile ‘程序’ ,即可进行性能分析。 |image30| -点击 Test,测试连通性,会要求输入密码 +运行完毕后,会自动跳出一个性能统计界面。 |image31| -若一切顺利,则会看到如下界面 +性能统计界面由Name、Call Count、Time(ms)、Own Time(ms) +,4列组成一个表格,见下图。 + +1. 表头Name显示被调用的模块或者函数;Call + Count显示被调用的次数;Time(ms)显示运行时间和时间百分比,时间单位为毫秒(ms)。 +2. 点击表头上的小三角可以升序或降序排列表格。 +3. 在Name这一个列中双击某一行可以跳转到对应的代码。 +4. 以fun4这一行举例:fun4被调用了一次,运行时间为1000ms,占整个运行时间的16.7% + +点击 Call +Graph(调用关系图)界面直观展示了各函数直接的调用关系、运行时间和时间百分比,见下图。 |image32| -4.15.12 Tab轻松转空格 +右上角的4个按钮表示放大、缩小、真实大小、合适大小; + +1. 箭头表示调用关系,由调用者指向被调用者; +2. 矩形的左上角显示模块或者函数的名称,右上角显示被调用的次数; +3. 矩形中间显示运行时间和时间百分比; +4. 矩形的颜色表示运行时间或者时间百分比大小的趋势:红色 > 黄绿色 > + 绿色,由图可以看出fun3的矩形为黄绿色,fun1为绿色,所有fun3运行时间比fun1长。 +5. 从图中可以看出Test.py直接调用了fun3、fun1、fun2和fun5函数;fun5函数直接调用了fun4函数;fun1、fun2、fun3、fun4和fun5都直接调用了print以及sleep函数;整个测试代码运行的总时间为6006ms,其中fun3的运行时间为1999ms,所占的时间百分比为33.3%,也就是 + 1999ms / 6006ms = 33.3%。 + +4.15.12 使用Git做版本控制 +------------------------- + +按照如下提示点击 Git 仓库配置 + +|image33| + +接着输入仓库地址 + +|image34| + +点击 Test,测试连通性,会要求输入密码 + +|image35| + +若一切顺利,则会看到如下界面 + +|image36| + +测试连接成功后,点击 Clone 就可以克隆下来了。 + +对于以前使用 Git 命令来管理的,现在可以直接使用 PyCharm +的菜单栏来操作,这些功能已经可以满足大多数人的日常需求了,应该是够用了。 + +|image37| + +4.15.13 Tab轻松转空格 --------------------- 在团队协作中,你难免会动到别人编辑的文件,有的人喜欢做tab做缩进,有的人喜欢用四个空格做缩进。 @@ -334,7 +416,7 @@ Pycharm 在图示位置打勾即可开启自动检测。 -|image33| +|image38| 上面是对一个旧的 Python 模块进行修改时,如何决定当前编辑的缩进方式。 @@ -343,10 +425,9 @@ Pycharm 如下图,若在 ``Use tab character`` 打上勾,则你新建一个 Python 后,就会使用 TAB 进行缩进,反之,则使用四个空格进行缩进。 -|image34| +|image39| -4.15.13 代码性能分析 --------------------- +5. 4.15.14 一次注册,永久激活 -------------------------- @@ -354,7 +435,7 @@ Pycharm PyCharm 有分两个版本,一个是社区版(免费功能有限),一个是专业版(有一些增强功能),详细差异你可以参考这个图,一般来说,社区版用作学习用途是没有问题的。 -|image35| +|image40| 如果需要使用专业版,网上也有一些注册服务器使用,非常方便,缺点是过一段时间,可能就会失效。这里有一种一劳永逸的方法,但可能仅对早期的 PyCharm 版本有效,可以实现永久激活(到 2099 / @@ -384,21 +465,21 @@ PyCharm 版本有效,可以实现永久激活(到 2099 / 将第一步下载的 jar 包放入这个目录,并打开如下两个以 ``vmoptions`` 后缀结尾的文件: -|image36| +|image41| 添加如下这一行(请根据你的实际安装目录自行调整) -|image37| +|image42| 若是 Mac OS 系统,请找到并进入你的 Pycharm 安装启动目录(以我的为例) 将第一步下载的 jar 包放入这个目录 -|image38| +|image43| 并打开如下一个以 ``vmoptions`` 后缀结尾的文件: -|image39| +|image44| **第三步**\ : @@ -414,18 +495,55 @@ PyCharm 版本有效,可以实现永久激活(到 2099 / 若是 Windows 系统,重启 PyCharm 后,查看激活信息:Help -> About -|image40| +|image45| 如果是 Mac OS 系统,重启 PyCharm 后,查看激活信息:PyCharm -> About PyCharm -|image41| +|image46| + +另外,以上仅做交流和个人学习使用,请勿商用,有能力的朋友还是希望多支持正版! + +4.15.15 源码文档,快速预览 +-------------------------- + +Ctrl + 鼠标左键 (Mac 上是:Command + +鼠标左键),可以实现函数跳转查看源码, 这几乎是每一个 PyCharmer +都会的一个惯用技巧。 + +这里再另外介绍两个类似的小技巧,快速 ``查看函数文档`` 和 ``预览源代码`` +。 + +在函数的开头处,使用三个引号 ``"`` 包含的内容,叫做函数文档 +(DocString)。 + +在编写代码时,顺便写好函数的接口文档,是一个很好的编码习惯。它介绍了该函数的参数类型及说明,返回值类型及范例,写得好一点的还会写出 +几个简单的 Example Usage 有助于理解使用。在这一点上,Flask +可以说做得相当好。这边随便截一个 Werkzeug 的例子。 + +|image47| + +假如我们在使用这个类的时候,忘记了这个用法,可以按住 Ctrl + +q(Mac暂时未找到对应快捷键),在当前页面就可以快速预览 LocalStack +的接口文档。 + +|image48| + +同样的,如果你对这个类或者函数的代码逻辑感兴趣,也可以使用快速预览的方式在当前页面展示源代码。快捷键是:Ctrl ++ shift + i (Mac:Command + shift + i)。效果如下 + +|image49| + +如果 PyCharm 检测到的同名函数有多个,可以点这里进行选择查看 + +|image50| -另外,以上仅做交流和个人学习使用,有能力的朋友还是希望多支持正版! +这两个快捷键比起使用 Ctrl + 鼠标左键 +跳进源代码来说,更加方便,,就像微信小程序一样,用完即焚,不会新产生一个标签页,也不需要来回跳转页面。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! @@ -459,16 +577,25 @@ PyCharm .. |image27| image:: http://image.python-online.cn/FiKyU6tjQauWXfaVfKLhwi3NkXBf .. |image28| image:: http://image.python-online.cn/FsAM-8HyzSrLWZJ_lg3ofw84_ibf .. |image29| image:: http://image.python-online.cn/FgJCtNYkjPfBaTbRxwb3Z6icHqkf -.. |image30| image:: http://image.python-online.cn/20190419151948.png -.. |image31| image:: http://image.python-online.cn/20190419152120.png -.. |image32| image:: http://image.python-online.cn/20190419152145.png -.. |image33| image:: http://image.python-online.cn/20190423162328.png -.. |image34| image:: http://image.python-online.cn/20190423163341.png -.. |image35| image:: http://image.python-online.cn/20190506150523.png -.. |image36| image:: http://image.python-online.cn/20190506150010.png -.. |image37| image:: http://image.python-online.cn/20190506150100.png -.. |image38| image:: http://image.python-online.cn/20190507000850.png -.. |image39| image:: http://image.python-online.cn/20190507001025.png -.. |image40| image:: http://image.python-online.cn/20190507001422.png -.. |image41| image:: http://image.python-online.cn/20190507001350.png +.. |image30| image:: http://image.python-online.cn/20190507222856.png +.. |image31| image:: http://image.python-online.cn/20190507222119.png +.. |image32| image:: http://image.python-online.cn/20190507223313.png +.. |image33| image:: http://image.python-online.cn/20190507215525.png +.. |image34| image:: http://image.python-online.cn/20190507220101.png +.. |image35| image:: http://image.python-online.cn/20190419152120.png +.. |image36| image:: http://image.python-online.cn/20190419152145.png +.. |image37| image:: http://image.python-online.cn/20190507220740.png +.. |image38| image:: http://image.python-online.cn/20190423162328.png +.. |image39| image:: http://image.python-online.cn/20190423163341.png +.. |image40| image:: http://image.python-online.cn/20190506150523.png +.. |image41| image:: http://image.python-online.cn/20190506150010.png +.. |image42| image:: http://image.python-online.cn/20190506150100.png +.. |image43| image:: http://image.python-online.cn/20190507000850.png +.. |image44| image:: http://image.python-online.cn/20190507001025.png +.. |image45| image:: http://image.python-online.cn/20190507001422.png +.. |image46| image:: http://image.python-online.cn/20190507001350.png +.. |image47| image:: http://image.python-online.cn/20190507152911.png +.. |image48| image:: http://image.python-online.cn/20190507152840.png +.. |image49| image:: http://image.python-online.cn/20190507153847.png +.. |image50| image:: http://image.python-online.cn/20190507154027.png diff --git a/source/c04/c04_16.md b/source/c04/c04_16.md index d93dde2..84360f5 100644 --- a/source/c04/c04_16.md +++ b/source/c04/c04_16.md @@ -72,4 +72,4 @@ caller name: f1 --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c04/c04_16.rst b/source/c04/c04_16.rst index f910e95..501bcc8 100644 --- a/source/c04/c04_16.rst +++ b/source/c04/c04_16.rst @@ -72,6 +72,6 @@ Python:如何在被调用方法中获取调用者的方法名? -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c04/c04_17.md b/source/c04/c04_17.md index 087841d..a692423 100644 --- a/source/c04/c04_17.md +++ b/source/c04/c04_17.md @@ -235,4 +235,4 @@ class BestPromo(Promotion): --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c04/c04_17.rst b/source/c04/c04_17.rst index 22d3e45..eb6e61f 100644 --- a/source/c04/c04_17.rst +++ b/source/c04/c04_17.rst @@ -242,7 +242,7 @@ Order -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c05/c05_01.md b/source/c05/c05_01.md index b63e20d..743f4a8 100644 --- a/source/c05/c05_01.md +++ b/source/c05/c05_01.md @@ -409,4 +409,4 @@ def radix_sort(array, reverse=False): -------------- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c05/c05_01.rst b/source/c05/c05_01.rst index 809eccc..7c81df7 100755 --- a/source/c05/c05_01.rst +++ b/source/c05/c05_01.rst @@ -419,7 +419,7 @@ sgnificant digital),LSD 的排序方式由键值的最右边开始,而 MSD -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c05/c05_02.md b/source/c05/c05_02.md index 3e44313..d3078af 100644 --- a/source/c05/c05_02.md +++ b/source/c05/c05_02.md @@ -112,4 +112,4 @@ calc_step_loop(40) --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c05/c05_02.rst b/source/c05/c05_02.rst index 9541c2d..65c4af2 100755 --- a/source/c05/c05_02.rst +++ b/source/c05/c05_02.rst @@ -120,6 +120,6 @@ -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c05/c05_03.md b/source/c05/c05_03.md index e7b3269..c9565e0 100644 --- a/source/c05/c05_03.md +++ b/source/c05/c05_03.md @@ -144,5 +144,5 @@ print(calc(365, 23)) # 0.500001752183 --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c05/c05_03.rst b/source/c05/c05_03.rst index 5a5db14..7c94d15 100755 --- a/source/c05/c05_03.rst +++ b/source/c05/c05_03.rst @@ -159,7 +159,7 @@ B 可以读取和更改用户 A 的信息,这无疑带来了很大的安全隐 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c06/c06_01.md b/source/c06/c06_01.md index c3b6a6d..5b5a4c0 100644 --- a/source/c06/c06_01.md +++ b/source/c06/c06_01.md @@ -11,8 +11,6 @@ Matplotlib 是 Python 的一个绘图库。它包含了大量的工具,你可 学习 matplotlib 最好的途径是 官网(http://matplotlib.org/),以下内容都是本人从官网上一点一点消化总结出来的,都是精华提炼,毫不含糊。希望你看完也能有所收获。 -[TOC] - ## 01. 使用工具 在上一篇文章中,小明首先讲了一个工具,以后文章里的所有操作都将在这里面实现,还没有阅读的小伙伴,记得先去学习下:Jupyter NoteBook。 @@ -110,7 +108,7 @@ plt.show() 以上的注释,可以说是很直白啦。一张图表该有的东西都有了,不花哨,但实用。 看看我们的代码输出的图表是啥样的。 -![你的第一张图表](https://ws1.sinaimg.cn/large/8f640247gy1fyjdnp1ohnj20b107rwf1.jpg) +![](http://image.python-online.cn/20190511164650.png) ---- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c06/c06_01.rst b/source/c06/c06_01.rst index 2f72941..531fd93 100755 --- a/source/c06/c06_01.rst +++ b/source/c06/c06_01.rst @@ -16,8 +16,6 @@ Matplotlib 是 Python 学习 matplotlib 最好的途径是 官网(http://matplotlib.org/),以下内容都是本人从官网上一点一点消化总结出来的,都是精华提炼,毫不含糊。希望你看完也能有所收获。 -[TOC] - 01. 使用工具 ------------ @@ -129,16 +127,14 @@ ticks(由Locator对象定义),还有ticklabel(由Formatter对象定义 以上的注释,可以说是很直白啦。一张图表该有的东西都有了,不花哨,但实用。 看看我们的代码输出的图表是啥样的。 -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyjdnp1ohnj20b107rwf1.jpg - :alt: 你的第一张图表 - - 你的第一张图表 +|image1| -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! .. |image0| image:: https://i.loli.net/2018/08/12/5b6ff3716fdc0.png +.. |image1| image:: http://image.python-online.cn/20190511164650.png diff --git a/source/c06/c06_02.md b/source/c06/c06_02.md index 7cb9621..c49c3ee 100644 --- a/source/c06/c06_02.md +++ b/source/c06/c06_02.md @@ -33,7 +33,7 @@ plt.show() ``` show image -![折线图](https://ws1.sinaimg.cn/large/8f640247gy1fyj9cr6hjxj20c7084q3q.jpg) +![](http://image.python-online.cn/20190511164738.png) ## 02. 散点图 @@ -52,7 +52,7 @@ plt.plot(x, x, 'r--', x, x**2, 'bs', x, x**3, 'g^') plt.show() ``` show image -![散点图](https://ws1.sinaimg.cn/large/8f640247gy1fyj9e684uoj20cm080aam.jpg) +![](http://image.python-online.cn/20190511164753.png) ## 03. 直方图 @@ -93,7 +93,7 @@ plt.show() show image -![直方图](https://ws1.sinaimg.cn/large/8f640247gy1fyj9f1id9aj20ck07zq40.jpg) +![](http://image.python-online.cn/20190511164802.png) ## 04. 柱状图 @@ -126,7 +126,7 @@ plt.show() ``` show image -![并列柱状图](https://ws1.sinaimg.cn/large/8f640247gy1fyj9fv44tnj20bo07a0t1.jpg) +![](http://image.python-online.cn/20190511164814.png) ### 4.2 叠加柱状图 ```python @@ -152,7 +152,7 @@ plt.show() ``` show image -![叠加柱状图](https://ws1.sinaimg.cn/large/8f640247gy1fyj9gfrev6j20b607ht9c.jpg) +![](http://image.python-online.cn/20190511164825.png) ## 05. 饼图 @@ -178,7 +178,7 @@ plt.show() ``` show image -![饼图](https://ws1.sinaimg.cn/large/8f640247gy1fyj9gzk8qpj20ao06pjs5.jpg) +![](http://image.python-online.cn/20190511164835.png) ### 5.2 嵌套饼图 @@ -212,7 +212,7 @@ plt.axis('equal') plt.show() ``` show image -.![嵌套饼图](https://ws1.sinaimg.cn/large/8f640247gy1fyj9ir1rnnj209w06ut9f.jpg) +![](http://image.python-online.cn/20190511164843.png) ### 5.3 极轴饼图 @@ -243,7 +243,7 @@ plt.show() ``` show image -![极轴饼图](https://ws1.sinaimg.cn/large/8f640247gy1fyj9jc59g6j209y07ugn1.jpg) +![](http://image.python-online.cn/20190511164852.png) ## 06. 三维图 @@ -270,7 +270,7 @@ plt.show() ``` show image -![三维散点图](https://ws1.sinaimg.cn/large/8f640247gy1fyj9junouuj20ly0dk12v.jpg) +![](http://image.python-online.cn/20190511164900.png) ### 6.2 绘制三维平面图 @@ -293,8 +293,8 @@ ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='rainbow') plt.show() ``` show image -![绘制三维平面图](https://ws1.sinaimg.cn/large/8f640247gy1fyj9kfd08gj20re0h6qe0.jpg) +![](http://image.python-online.cn/20190511164915.png) --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c06/c06_02.rst b/source/c06/c06_02.rst index d3fb8bd..b95a0eb 100755 --- a/source/c06/c06_02.rst +++ b/source/c06/c06_02.rst @@ -36,10 +36,7 @@ show image -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9cr6hjxj20c7084q3q.jpg - :alt: 折线图 - - 折线图 +|image0| 02. 散点图 ---------- @@ -59,7 +56,7 @@ show image plt.plot(x, x, 'r--', x, x**2, 'bs', x, x**3, 'g^') plt.show() -show image |散点图| +show image |image1| 03. 直方图 ---------- @@ -101,10 +98,7 @@ show image |散点图| show image -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9f1id9aj20ck07zq40.jpg - :alt: 直方图 - - 直方图 +|image2| 04. 柱状图 ---------- @@ -140,10 +134,7 @@ show image show image -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9fv44tnj20bo07a0t1.jpg - :alt: 并列柱状图 - - 并列柱状图 +|image3| 4.2 叠加柱状图 ~~~~~~~~~~~~~~ @@ -170,7 +161,7 @@ show image plt.grid(True) plt.show() -show image |叠加柱状图| +show image |image4| 05. 饼图 -------- @@ -196,7 +187,7 @@ show image |叠加柱状图| plt.show() -show image |饼图| +show image |image5| 5.2 嵌套饼图 ~~~~~~~~~~~~ @@ -230,7 +221,7 @@ show image |饼图| plt.axis('equal') plt.show() -show image .\ |嵌套饼图| +show image |image6| 5.3 极轴饼图 ~~~~~~~~~~~~ @@ -262,7 +253,7 @@ show image .\ |嵌套饼图| plt.show() -show image |极轴饼图| +show image |image7| 06. 三维图 ---------- @@ -290,7 +281,7 @@ show image |极轴饼图| ax.set_xlabel('X') plt.show() -show image |三维散点图| +show image |image8| 6.2 绘制三维平面图 ~~~~~~~~~~~~~~~~~~ @@ -314,19 +305,22 @@ show image |三维散点图| plt.show() -show image |绘制三维平面图| +show image |image9| -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! -.. |散点图| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9e684uoj20cm080aam.jpg -.. |叠加柱状图| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9gfrev6j20b607ht9c.jpg -.. |饼图| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9gzk8qpj20ao06pjs5.jpg -.. |嵌套饼图| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9ir1rnnj209w06ut9f.jpg -.. |极轴饼图| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9jc59g6j209y07ugn1.jpg -.. |三维散点图| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9junouuj20ly0dk12v.jpg -.. |绘制三维平面图| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9kfd08gj20re0h6qe0.jpg +.. |image0| image:: http://image.python-online.cn/20190511164738.png +.. |image1| image:: http://image.python-online.cn/20190511164753.png +.. |image2| image:: http://image.python-online.cn/20190511164802.png +.. |image3| image:: http://image.python-online.cn/20190511164814.png +.. |image4| image:: http://image.python-online.cn/20190511164825.png +.. |image5| image:: http://image.python-online.cn/20190511164835.png +.. |image6| image:: http://image.python-online.cn/20190511164843.png +.. |image7| image:: http://image.python-online.cn/20190511164852.png +.. |image8| image:: http://image.python-online.cn/20190511164900.png +.. |image9| image:: http://image.python-online.cn/20190511164915.png diff --git a/source/c06/c06_03.md b/source/c06/c06_03.md index decf517..f7c2759 100644 --- a/source/c06/c06_03.md +++ b/source/c06/c06_03.md @@ -23,7 +23,7 @@ plot(x,S) show() ``` show image -![](https://ws1.sinaimg.cn/large/8f640247gy1fyj9lzp79pj20ch07adgn.jpg) +![](http://image.python-online.cn/20190511164936.png) ## 6.3.2 设置基本元素 @@ -63,7 +63,7 @@ plt.legend() plt.show() ``` show image -![](https://ws1.sinaimg.cn/large/8f640247gy1fyj9n8tvb0j20j60bj75t.jpg) +![](http://image.python-online.cn/20190511164949.png) ## 6.3.3 移动轴线 还记得我们在初高中学习的三角函数图象,可不是这样,它应该是有四个象限的。而这里却是一个四四方方的图表。 @@ -87,7 +87,7 @@ ax.spines['bottom'].set_position(('data',0)) ax.spines['left'].set_position(('data',0)) ``` 关于`set_position()`这个函数中的data是啥意思?我查了下官网。解释如下 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyj9nze1v5j20ou080n09.jpg) +![](http://image.python-online.cn/20190511165003.png) 然后最后发现,上面的写法可以用一定更简洁的方式设置,是等价的。 ```python ax.spines['bottom'].set_position('zero') @@ -95,7 +95,7 @@ ax.spines['left'].set_position('zero') ``` show image -![](https://ws1.sinaimg.cn/large/8f640247gy1fyj9of91ruj20kx0atwfz.jpg) +![](http://image.python-online.cn/20190511165013.png) ## 6.3.4 添加注释 现在的图形部分已经成型,接下让我们现在使用annotate命令注解一些我们感兴趣的点。 @@ -135,7 +135,7 @@ plt.annotate(r'$cos(\frac{2\pi}{3})=-\frac{1}{2}$', 第七个参数,`arrowprops`,对箭头的类型的一些设置。 show image -![](https://ws1.sinaimg.cn/large/8f640247gy1fyj9oz9wnyj20j80azgnr.jpg) +![](http://image.python-online.cn/20190511165020.png) ## 6.3.5 完整代码 @@ -204,4 +204,4 @@ plt.show() --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c06/c06_03.rst b/source/c06/c06_03.rst index 4d53fd3..ca05e72 100755 --- a/source/c06/c06_03.rst +++ b/source/c06/c06_03.rst @@ -205,13 +205,13 @@ show image |image4| -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! -.. |image0| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9lzp79pj20ch07adgn.jpg -.. |image1| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9n8tvb0j20j60bj75t.jpg -.. |image2| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9nze1v5j20ou080n09.jpg -.. |image3| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9of91ruj20kx0atwfz.jpg -.. |image4| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9oz9wnyj20j80azgnr.jpg +.. |image0| image:: http://image.python-online.cn/20190511164936.png +.. |image1| image:: http://image.python-online.cn/20190511164949.png +.. |image2| image:: http://image.python-online.cn/20190511165003.png +.. |image3| image:: http://image.python-online.cn/20190511165013.png +.. |image4| image:: http://image.python-online.cn/20190511165020.png diff --git a/source/c06/c06_04.md b/source/c06/c06_04.md index bb2e9de..8bfde62 100644 --- a/source/c06/c06_04.md +++ b/source/c06/c06_04.md @@ -17,7 +17,7 @@ plt.subplot(2, 2, 1) ``` 是将当前图像(figure)按 2 行 2 列的布局进行分割,然后取索引为 1 的子图。注意 matlibplot 是完全借鉴了 MATLAB 的思想,所以的起始索引为 1,不像 Python 的起始索引为 0。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyj9qvvnicj20bw094jrl.jpg) +![](http://image.python-online.cn/20190511165103.png) 他有好几种写法,这里写我在官网学到的几个方法。 @@ -57,7 +57,7 @@ plt.subplot(212) plt.plot(t2, np.cos(2*np.pi*t2), 'r--') plt.show() ``` -![](https://ws1.sinaimg.cn/large/8f640247gy1fyj9rfj9byj20fu0catba.jpg) +![](http://image.python-online.cn/20190511165132.png) @@ -66,7 +66,7 @@ plt.show() 子图(axes),和子区(subplot)非常相似,一个子图可能是由一个或多个子区域构成的。它比子区更加灵活。 它可以是这样 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyj9rwy3a9j20by094aa6.jpg) +![](http://image.python-online.cn/20190511165152.png) 要实现如上这个效果。常用的有两种方法。 @@ -93,7 +93,7 @@ ax5 = plt.subplot(gs[-1, -2]) 这个比较规则的划分我们举个例子看看。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyj9sb3vp7j20gx0codh4.jpg) +![](http://image.python-online.cn/20190511165159.png) 代码如下: ```python @@ -126,10 +126,10 @@ plt.show() 为什么说,子图的灵活性更高呢,因为它允许把图片放置到图像(figure)中的任何地方(如下图)。所以如果我们想要在一个大图片中嵌套一个小点的图片,我们通过子图(axes)来完成它。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyj9srk4nxj20d00a0glv.jpg) +![](http://image.python-online.cn/20190511165211.png) 图中的 axes 是如何实现的,刚开始我也有点懵逼,在查阅了官方文档后,我才明白。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyj9sx6vobj20py07t0vy.jpg) +![](http://image.python-online.cn/20190511165221.png) `left` 是指,离左边界的距离。 `bottom` 是指,离底边的距离。 @@ -143,7 +143,7 @@ plt.show() 同样地,这个我们也来看一个例子。 这个图的亮点,在于中间,多了两个子图,就像往图中贴上了两个插画一样。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyj9tgkvtpj20i50d9773.jpg) +![](http://image.python-online.cn/20190511165229.png) 那么这个如何实现呢? ```python @@ -188,4 +188,4 @@ plt.show() --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c06/c06_04.rst b/source/c06/c06_04.rst index 0df15eb..15ab4d6 100755 --- a/source/c06/c06_04.rst +++ b/source/c06/c06_04.rst @@ -199,15 +199,15 @@ subplot,一个是axes。这两个概念将贯穿整个 matplotlib -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! -.. |image0| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9qvvnicj20bw094jrl.jpg -.. |image1| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9rfj9byj20fu0catba.jpg -.. |image2| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9rwy3a9j20by094aa6.jpg -.. |image3| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9sb3vp7j20gx0codh4.jpg -.. |image4| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9srk4nxj20d00a0glv.jpg -.. |image5| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9sx6vobj20py07t0vy.jpg -.. |image6| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyj9tgkvtpj20i50d9773.jpg +.. |image0| image:: http://image.python-online.cn/20190511165103.png +.. |image1| image:: http://image.python-online.cn/20190511165132.png +.. |image2| image:: http://image.python-online.cn/20190511165152.png +.. |image3| image:: http://image.python-online.cn/20190511165159.png +.. |image4| image:: http://image.python-online.cn/20190511165211.png +.. |image5| image:: http://image.python-online.cn/20190511165221.png +.. |image6| image:: http://image.python-online.cn/20190511165229.png diff --git a/source/c06/c06_05.md b/source/c06/c06_05.md index d80f4cf..921da78 100644 --- a/source/c06/c06_05.md +++ b/source/c06/c06_05.md @@ -126,4 +126,4 @@ Image(url='./ming.gif') ![](https://i.loli.net/2018/12/25/5c2226078799b.gif) --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c06/c06_05.rst b/source/c06/c06_05.rst index bcee5ee..0ef3347 100755 --- a/source/c06/c06_05.rst +++ b/source/c06/c06_05.rst @@ -141,7 +141,7 @@ matplotlib 给我们提供了一个函数,\ ``animation.FuncAnimation`` -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c06/c06_06.md b/source/c06/c06_06.md index 7491a80..a39476a 100644 --- a/source/c06/c06_06.md +++ b/source/c06/c06_06.md @@ -8,7 +8,7 @@ 由于我使用的是 Anaconda ,我需要将其安装到我的环境中。首先打开我们的命令行(注意不是一般的CMD),使用windows 的查找入口: -![](https://ws1.sinaimg.cn/large/8f640247gy1fyja2fz18gj20ba0daq5c.jpg) +![](http://image.python-online.cn/20190511165315.png) 然后执行如下命令安装 ``` @@ -121,4 +121,4 @@ HTML(ani.to_html5_video()) 我将这个小短片下载并上传至后台,你可以点击 [公众号原文](https://mp.weixin.qq.com/s/BU4DtJQxtxwEMhGZE8t3CQ) 感受一下。 --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c06/c06_06.rst b/source/c06/c06_06.rst index 855309f..fdbc18c 100755 --- a/source/c06/c06_06.rst +++ b/source/c06/c06_06.rst @@ -137,9 +137,9 @@ Jupyter NoteBook 里观察整个变化的过程。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! -.. |image0| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyja2fz18gj20ba0daq5c.jpg +.. |image0| image:: http://image.python-online.cn/20190511165315.png diff --git a/source/c07/C07_08.md b/source/c07/C07_08.md index 86c94fb..884acb3 100644 --- a/source/c07/C07_08.md +++ b/source/c07/C07_08.md @@ -171,4 +171,4 @@ fi --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c07/c07_01.md b/source/c07/c07_01.md index ace63f8..7c83bec 100644 --- a/source/c07/c07_01.md +++ b/source/c07/c07_01.md @@ -1614,4 +1614,4 @@ yum makecache --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c07/c07_01.rst b/source/c07/c07_01.rst index a47c2a5..ad0834b 100755 --- a/source/c07/c07_01.rst +++ b/source/c07/c07_01.rst @@ -1765,7 +1765,7 @@ CentOS -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c07/c07_02.md b/source/c07/c07_02.md index 3e0dd7e..b986dad 100644 --- a/source/c07/c07_02.md +++ b/source/c07/c07_02.md @@ -629,4 +629,4 @@ update items set history='30d' where hostid in (select hostid from hosts where h --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c07/c07_02.rst b/source/c07/c07_02.rst index f8db9bf..ea7717a 100755 --- a/source/c07/c07_02.rst +++ b/source/c07/c07_02.rst @@ -687,7 +687,7 @@ float ,log, text 等,所以计算存在一定的误差,需留有冗余 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c07/c07_03.md b/source/c07/c07_03.md index dd08bb3..1e766dc 100644 --- a/source/c07/c07_03.md +++ b/source/c07/c07_03.md @@ -225,4 +225,4 @@ namespace 有下面六种 --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c07/c07_03.rst b/source/c07/c07_03.rst index 834b464..6eb4fd6 100755 --- a/source/c07/c07_03.rst +++ b/source/c07/c07_03.rst @@ -259,7 +259,7 @@ namespace 有下面六种 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c07/c07_04.md b/source/c07/c07_04.md index 572b756..3cf129d 100644 --- a/source/c07/c07_04.md +++ b/source/c07/c07_04.md @@ -308,4 +308,4 @@ docker history image --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c07/c07_04.rst b/source/c07/c07_04.rst index 56e6a01..c856ca3 100755 --- a/source/c07/c07_04.rst +++ b/source/c07/c07_04.rst @@ -358,7 +358,7 @@ CMD:两个功能 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c07/c07_05.md b/source/c07/c07_05.md index 88d9bfc..52010b1 100644 --- a/source/c07/c07_05.md +++ b/source/c07/c07_05.md @@ -193,4 +193,4 @@ docker exec bbox2 ip r --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c07/c07_05.rst b/source/c07/c07_05.rst index 07825c1..734fdaf 100755 --- a/source/c07/c07_05.rst +++ b/source/c07/c07_05.rst @@ -227,7 +227,7 @@ br0 上。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c07/c07_06.md b/source/c07/c07_06.md index f88f703..0e1e405 100644 --- a/source/c07/c07_06.md +++ b/source/c07/c07_06.md @@ -280,4 +280,4 @@ docker-machine scp bm-docker-01:/tmp/a bm-docker-02:/tmp/b --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c07/c07_06.rst b/source/c07/c07_06.rst index 379f838..7523d1b 100755 --- a/source/c07/c07_06.rst +++ b/source/c07/c07_06.rst @@ -326,7 +326,7 @@ centos的配置文件路径如下,ubuntu的有所不同 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c07/c07_07.md b/source/c07/c07_07.md index f1c0912..b6b451f 100644 --- a/source/c07/c07_07.md +++ b/source/c07/c07_07.md @@ -353,4 +353,4 @@ salt minion01 saltutil.sync_grains # 只同步grains --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c07/c07_07.rst b/source/c07/c07_07.rst index cc624f4..38c0a6b 100755 --- a/source/c07/c07_07.rst +++ b/source/c07/c07_07.rst @@ -416,6 +416,6 @@ salt命令格式 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c07/c07_08.rst b/source/c07/c07_08.rst index 558cdcd..e177ced 100644 --- a/source/c07/c07_08.rst +++ b/source/c07/c07_08.rst @@ -169,6 +169,6 @@ chk_zabbix.sh -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c08/c08_01.md b/source/c08/c08_01.md index 38e541b..a16a924 100644 --- a/source/c08/c08_01.md +++ b/source/c08/c08_01.md @@ -373,4 +373,4 @@ rabbitmqctl set_user_tags openstack administrator ``` --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_01.rst b/source/c08/c08_01.rst index 5e44dd9..61d55c4 100755 --- a/source/c08/c08_01.rst +++ b/source/c08/c08_01.rst @@ -387,6 +387,6 @@ aggregate管理 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c08/c08_02.md b/source/c08/c08_02.md index 293b7b5..ef5a366 100644 --- a/source/c08/c08_02.md +++ b/source/c08/c08_02.md @@ -226,4 +226,4 @@ nova boot --flavor [flavor_id] --image [image_id] --nic port-id=$port_id [sriov_ ------ -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_02.rst b/source/c08/c08_02.rst index da32cf3..c22f835 100755 --- a/source/c08/c08_02.rst +++ b/source/c08/c08_02.rst @@ -261,7 +261,7 @@ vim /etc/neutron/plugins/ml2/ml2_conf.ini |image4| -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c08/c08_03.md b/source/c08/c08_03.md index e13752f..768dbc8 100644 --- a/source/c08/c08_03.md +++ b/source/c08/c08_03.md @@ -466,4 +466,4 @@ shutdown -h now --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c08/c08_03.rst b/source/c08/c08_03.rst index 2e3665b..981c2b0 100755 --- a/source/c08/c08_03.rst +++ b/source/c08/c08_03.rst @@ -511,7 +511,7 @@ CentOS6 创建快照前需要先删除\ ``75-persistent-net-generator.rules`` -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c08/c08_04.md b/source/c08/c08_04.md index 6ec057f..e8238a4 100644 --- a/source/c08/c08_04.md +++ b/source/c08/c08_04.md @@ -314,4 +314,4 @@ $ /usr/libexec/qemu-kvm \ --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_04.rst b/source/c08/c08_04.rst index 27be864..918ff98 100644 --- a/source/c08/c08_04.rst +++ b/source/c08/c08_04.rst @@ -345,7 +345,7 @@ virsh 创建 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index a2b265f..d1cd174 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -197,4 +197,4 @@ nova-scheduler 的调度主要由两部分组成 --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index 5ca5c08..a7fe415 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -63,9 +63,23 @@ nova 8.5.5 快照镜像如何实现? ------------------------ -入口函数在:\ ``nova/virt/libvirt/driver.py:snapshot()`` +nova-api 的入口如下 -会先获取imagebackend的类型,然后找到对应的backend +|image0| + +接着会调用 nova/compute/api.py + +|image1| + +在nova-compute 层面:nova/compute/manager.py:_snapshot_instance() + +|image2| + +接下来会调用 ``nova/virt/libvirt/driver.py:snapshot()`` + +|image3| + +先获取imagebackend的类型,然后找到对应的backend .. code:: python @@ -78,13 +92,13 @@ nova 接下来,会调用对应的imagebackend的\ ``snapshot_extract`` 方法。 -|image0| +|image4| ``snapshot_extract`` 方法最终会调用\ ``nova/virt/images.py:_convert_image()`` ,可以看出其实底层调用的是 ``qemu-img`` 提供的\ ``convert`` 接口。 -|image1| +|image5| 如果是qcow2的backend,不是调用这边,而是调用 ``nova/virt/libvirt/utils.py:extract_snapshot()`` @@ -107,11 +121,11 @@ nova 在\ ``libvirt_utils.get_disk_type_from_path`` 里没有相应的修改,导致返回的是lvm。 -|image2| +|image6| 后面的imagebackend也相应的变成 lvm的 -|image3| +|image7| 然后会进入 lvm这个backend的init函数。由于\ ``path`` 是\ ``/dev/sdb`` 并不是一个lv,所以这边会报错。 @@ -135,7 +149,7 @@ compute的资源上报,是在 其调用的函数是\ ``virt/libvirt/driver.py`` 里的 ``get_available_resource`` 函数 -|image4| +|image8| 从数据库获取旧数据 ``self.compute_node = self._get_compute_node(context)`` @@ -153,11 +167,11 @@ compute的资源上报,是在 nova-compute 发创建请求前,会先让 nova-scheduler 选出一台资源充足的计算节点。 -|image5| +|image9| nova-scheduler 的调度主要由两部分组成 -|image6| +|image10| - 过滤器:filter,将不满足条件(硬性条件,比如内存,cpu,磁盘,pci设备等)的计算节点,直接过滤掉。意义:从过滤器出来的那些计算节点,理论上都可以创建虚拟机。 - 称重器:weigher,对满足硬性条件的众多主机按照一定的规则进行权重配比。意义:经过称重器计算,选出你更希望在哪台节点上创建虚拟机。 @@ -171,25 +185,25 @@ nova-scheduler 的调度主要由两部分组成 compute 的 id,可以在 ``_update_from_compute_node`` 函数中添加。它会从compute_nodes 表中取得你想要的信息。 - |image7| + |image11| - spec_obj:你所要请求创建的虚拟机信息(模板,镜像等)。它是从 objects.RequestSpec.from_primitives 中取得的 - |image8| + |image12| 过滤器,它的代码如下: -|image9| +|image13| 称重器,它的规则主要看这段代码。 -|image10| +|image14| 我在代码中,加了几段日志。从左到右,三个不同颜色的内容分别为,原始权值,配重系数(越高说明越占比越大,越影响最终结果),经过 nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮点数)。 -|image11| +|image15| 那最终的权值如何计算呢? @@ -203,30 +217,34 @@ nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮 1. 如果有请求req(在nova-api里),可以使用这种 -|image12| +|image16| 2. 其他地方可以使用这种 -|image13| +|image17| -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! -.. |image0| image:: http://image.python-online.cn/FhRPy4B1xEI9SfoD2RcunJl15ZE3 -.. |image1| image:: http://image.python-online.cn/FuyMWZS6HF4g3rPwTlLcereZxg4L -.. |image2| image:: http://image.python-online.cn/FnJA8RNIvJN2lAEXbKtJDpOLg1vg -.. |image3| image:: http://image.python-online.cn/FnGyI8jCQFLCGi0pGVmI3SV6pDrv -.. |image4| image:: http://image.python-online.cn/FrbE6oEZ3vtTWwDfMNQ16MGi6SWr -.. |image5| image:: http://image.python-online.cn/20190424212211.png -.. |image6| image:: http://image.python-online.cn/20190424213430.png -.. |image7| image:: http://image.python-online.cn/20190424214653.png -.. |image8| image:: http://image.python-online.cn/20190424214540.png -.. |image9| image:: http://image.python-online.cn/20190424221602.png -.. |image10| image:: http://image.python-online.cn/20190424215735.png -.. |image11| image:: http://image.python-online.cn/20190424220008.png -.. |image12| image:: http://image.python-online.cn/20190426153322.png -.. |image13| image:: http://image.python-online.cn/20190426152148.png +.. |image0| image:: http://image.python-online.cn/20190508110723.png +.. |image1| image:: http://image.python-online.cn/20190508111109.png +.. |image2| image:: http://image.python-online.cn/20190508095028.png +.. |image3| image:: http://image.python-online.cn/20190508111527.png +.. |image4| image:: http://image.python-online.cn/FhRPy4B1xEI9SfoD2RcunJl15ZE3 +.. |image5| image:: http://image.python-online.cn/FuyMWZS6HF4g3rPwTlLcereZxg4L +.. |image6| image:: http://image.python-online.cn/FnJA8RNIvJN2lAEXbKtJDpOLg1vg +.. |image7| image:: http://image.python-online.cn/FnGyI8jCQFLCGi0pGVmI3SV6pDrv +.. |image8| image:: http://image.python-online.cn/FrbE6oEZ3vtTWwDfMNQ16MGi6SWr +.. |image9| image:: http://image.python-online.cn/20190424212211.png +.. |image10| image:: http://image.python-online.cn/20190424213430.png +.. |image11| image:: http://image.python-online.cn/20190424214653.png +.. |image12| image:: http://image.python-online.cn/20190424214540.png +.. |image13| image:: http://image.python-online.cn/20190424221602.png +.. |image14| image:: http://image.python-online.cn/20190424215735.png +.. |image15| image:: http://image.python-online.cn/20190424220008.png +.. |image16| image:: http://image.python-online.cn/20190426153322.png +.. |image17| image:: http://image.python-online.cn/20190426152148.png diff --git a/source/c08/c08_06.md b/source/c08/c08_06.md index 9322b95..f9b3136 100644 --- a/source/c08/c08_06.md +++ b/source/c08/c08_06.md @@ -180,4 +180,4 @@ Apr 29 11:13:29 localhost NetworkManager[1365]: exiting (error) --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_06.rst b/source/c08/c08_06.rst index c34ba27..f313500 100644 --- a/source/c08/c08_06.rst +++ b/source/c08/c08_06.rst @@ -227,7 +227,7 @@ CentOS 6.5)。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/c08/c08_07.md b/source/c08/c08_07.md index 1838884..24c8a5f 100644 --- a/source/c08/c08_07.md +++ b/source/c08/c08_07.md @@ -70,4 +70,4 @@ openstack image set IMG-UUID --property img_hide_hypervisor_id=true --- -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_07.rst b/source/c08/c08_07.rst index 011c42f..cb0026a 100644 --- a/source/c08/c08_07.rst +++ b/source/c08/c08_07.rst @@ -72,7 +72,7 @@ id。 -------------- -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! diff --git a/source/preface.rst b/source/preface.rst index 571c7f1..ca72b10 100755 --- a/source/preface.rst +++ b/source/preface.rst @@ -31,7 +31,7 @@ ------------------------------ -.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg +.. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新文章 From f087030f4ea8647d8eaed7a6e6247010e3bc7adb Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 12 May 2019 00:45:32 +0800 Subject: [PATCH 003/302] =?UTF-8?q?=E5=85=83=E7=B1=BB=E3=80=81=E5=8D=95?= =?UTF-8?q?=E4=BE=8B=E6=A8=A1=E5=BC=8F=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c03/c03_02.md | 43 +++++++++++++++++++-- source/c03/c03_02.rst | 48 ++++++++++++++++++++++-- source/c04/c04_17.md | 87 +++++++++++++++++++++++++++++++++++++++++++ source/c04/c04_17.rst | 80 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+), 6 deletions(-) diff --git a/source/c03/c03_02.md b/source/c03/c03_02.md index 93521da..b25d568 100644 --- a/source/c03/c03_02.md +++ b/source/c03/c03_02.md @@ -37,7 +37,44 @@ def say(self): User = type("User", (BaseClass, ), {"name":"user", "say":say}) ``` -## 3.2.3 理解什么是元类 +## 3.2.3 \__new__ 有什么用? + +在没有元类的情况下,每次创建实例,在先进入 \_\_init__ 之前都会先进入 \_\_new__ 。 + +```python +class User: + def __new__(cls, *args, **kwargs): + print("in BaseClass") + return super().__new__(cls) + + def __init__(self, name): + print("in User") + self.name = name + +# ======== 上下写法效果一样 ========== + +class BaseClass(): + def __new__(cls, *args, **kwargs): + print("in BaseClass") + return super().__new__(cls) + +class User(BaseClass): + def __init__(self, name): + print("in User") + self.name = name +``` +使用如下 +```python +>>> u = User('wangbm') +in BaseClass +in User +>>> u.name +'wangbm' +``` + +在后面元类那里,\_\_new__ 会在创建类时,先在元类中进入(这是第一次进入)。 + +## 3.2.4 理解什么是元类 什么是类?可能谁都知道,类就是用来创建对象的「模板」。 @@ -109,7 +146,7 @@ user = User("wangbm") ``` -## 3.3.4 使用元类的意义 +## 3.3.5 使用元类的意义 正常情况下,我们都不会使用到元类。但是这并不意味着,它不重要。假如某一天,我们需要写一个框架,很有可能就需要用到元类。 @@ -126,7 +163,7 @@ user = User("wangbm") 但是,这样说,你一定不会服气,到底元类用来干什么?其实元类的作用就是`创建API`,一个最典型的应用是 `Django ORM。` -## 3.3.5 元类实战:ORM +## 3.3.6 元类实战:ORM 使用过Django ORM的人都知道,有了ORM,使得我们操作数据库,变得异常简单。 diff --git a/source/c03/c03_02.rst b/source/c03/c03_02.rst index bc6cccb..5a1cee4 100755 --- a/source/c03/c03_02.rst +++ b/source/c03/c03_02.rst @@ -41,7 +41,49 @@ type?这不是判断对象类型的函数吗? # 使用type来创建User类 User = type("User", (BaseClass, ), {"name":"user", "say":say}) -3.2.3 理解什么是元类 +3.2.3 \__new_\_ 有什么用? +-------------------------- + +在没有元类的情况下,每次创建实例,在先进入 \__init_\_ 之前都会先进入 +\__new_\_ 。 + +.. code:: python + + class User: + def __new__(cls, *args, **kwargs): + print("in BaseClass") + return super().__new__(cls) + + def __init__(self, name): + print("in User") + self.name = name + + # ======== 上下写法效果一样 ========== + + class BaseClass(): + def __new__(cls, *args, **kwargs): + print("in BaseClass") + return super().__new__(cls) + + class User(BaseClass): + def __init__(self, name): + print("in User") + self.name = name + +使用如下 + +.. code:: python + + >>> u = User('wangbm') + in BaseClass + in User + >>> u.name + 'wangbm' + +在后面元类那里,__new_\_ +会在创建类时,先在元类中进入(这是第一次进入)。 + +3.2.4 理解什么是元类 -------------------- 什么是类?可能谁都知道,类就是用来创建对象的「模板」。 @@ -120,7 +162,7 @@ type是Python在背后用来创建所有类的元类,我们熟知的类的始 user = User("wangbm") -3.3.4 使用元类的意义 +3.3.5 使用元类的意义 -------------------- 正常情况下,我们都不会使用到元类。但是这并不意味着,它不重要。假如某一天,我们需要写一个框架,很有可能就需要用到元类。 @@ -138,7 +180,7 @@ type是Python在背后用来创建所有类的元类,我们熟知的类的始 但是,这样说,你一定不会服气,到底元类用来干什么?其实元类的作用就是\ ``创建API``\ ,一个最典型的应用是 ``Django ORM。`` -3.3.5 元类实战:ORM +3.3.6 元类实战:ORM ------------------- 使用过Django ORM的人都知道,有了ORM,使得我们操作数据库,变得异常简单。 diff --git a/source/c04/c04_17.md b/source/c04/c04_17.md index a692423..f491278 100644 --- a/source/c04/c04_17.md +++ b/source/c04/c04_17.md @@ -229,6 +229,93 @@ class BestPromo(Promotion): 以上,就是今天关于 `策略模式` 的一些个人分享,如有讲得不到位的,还请后台留言指正! +## 4.17.2 单例模式 + + + +使用单例模式,有几个方法,这里写两个 + +- 使用 \_\_new__ + +```python +class User: + _instance = None + def __new__(cls, *args, **kwargs): + print('===== 1 ====') + if not cls._instance: + print("===== 2 ====") + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self, name): + print('===== 3 ====') + self.name = name +``` + +验证结果 + +``` +>>> u1 = User('wangbm1') +===== 1 ==== +===== 2 ==== +===== 3 ==== +>>> u1.age = 20 +>>> u2 = User('wangbm2') +===== 1 ==== +===== 3 ==== +>>> u2.age +20 +>>> u1 is u2 +True +``` + + + +- 使用装饰器 + +```python +instances = {} + +def singleton(cls): + def get_instance(*args, **kw): + cls_name = cls.__name__ + print('===== 1 ====') + if not cls_name in instances: + print('===== 2 ====') + instance = cls(*args, **kw) + instances[cls_name] = instance + return instances[cls_name] + return get_instance + +@singleton +class User: + _instance = None + + def __init__(self, name): + print('===== 3 ====') + self.name = name +``` + +验证结果 + +``` +>>> u1 = User('wangbm1') +===== 1 ==== +===== 2 ==== +===== 3 ==== +>>> u1.age = 20 +>>> u2 = User('wangbm2') +===== 1 ==== +>>> u2.age +20 +>>> u1 is u2 +True +``` + + + + + ## 参考文档 - 《流畅的Python》 diff --git a/source/c04/c04_17.rst b/source/c04/c04_17.rst index eb6e61f..9bf9a1f 100644 --- a/source/c04/c04_17.rst +++ b/source/c04/c04_17.rst @@ -235,6 +235,86 @@ Order 以上,就是今天关于 ``策略模式`` 的一些个人分享,如有讲得不到位的,还请后台留言指正! +4.17.2 单例模式 +--------------- + +使用单例模式,有几个方法,这里写两个 + +- 使用 \__new_\_ + +.. code:: python + + class User: + _instance = None + def __new__(cls, *args, **kwargs): + print('===== 1 ====') + if not cls._instance: + print("===== 2 ====") + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self, name): + print('===== 3 ====') + self.name = name + +验证结果 + +:: + + >>> u1 = User('wangbm1') + ===== 1 ==== + ===== 2 ==== + ===== 3 ==== + >>> u1.age = 20 + >>> u2 = User('wangbm2') + ===== 1 ==== + ===== 3 ==== + >>> u2.age + 20 + >>> u1 is u2 + True + +- 使用装饰器 + +.. code:: python + + instances = {} + + def singleton(cls): + def get_instance(*args, **kw): + cls_name = cls.__name__ + print('===== 1 ====') + if not cls_name in instances: + print('===== 2 ====') + instance = cls(*args, **kw) + instances[cls_name] = instance + return instances[cls_name] + return get_instance + + @singleton + class User: + _instance = None + + def __init__(self, name): + print('===== 3 ====') + self.name = name + +验证结果 + +:: + + >>> u1 = User('wangbm1') + ===== 1 ==== + ===== 2 ==== + ===== 3 ==== + >>> u1.age = 20 + >>> u2 = User('wangbm2') + ===== 1 ==== + >>> u2.age + 20 + >>> u1 is u2 + True + 参考文档 -------- From 00aa85ce9eaf5911012f6efd83d818e99e3571c9 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 12 May 2019 13:09:08 +0800 Subject: [PATCH 004/302] =?UTF-8?q?=E5=8D=95=E4=BE=8B=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E6=96=B0=E5=86=99=E6=B3=95=EF=BC=8C=E5=85=83=E7=B1=BB=E4=BD=BF?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- source/c03/c03_02.md | 154 +++++++++++++++++++++++--------------- source/c03/c03_02.rst | 170 +++++++++++++++++++++++++----------------- source/c04/c04_17.md | 45 ++++++++++- source/c04/c04_17.rst | 52 ++++++++++++- 5 files changed, 292 insertions(+), 131 deletions(-) diff --git a/README.md b/README.md index 7163d68..ce03d5b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -这是我的个人博客( [MING's BLOG]([http://python-online.cn](http://python-online.cn/)) ),主要写关于Python的一些思考总结。 +这是我的个人博客( [MING's BLOG](http://python-online.cn/) ),主要写关于Python的一些思考总结。 关于搭建教程,感兴趣的可以查看这边:[Sphinx 搭建博客的图文教程](https://github.com/BingmingWong/MING-BLOG/blob/master/source/c04/c04_03.rst) diff --git a/source/c03/c03_02.md b/source/c03/c03_02.md index b25d568..f5b1d17 100644 --- a/source/c03/c03_02.md +++ b/source/c03/c03_02.md @@ -6,7 +6,7 @@ 类是如何产生? -这个问题肯定很傻。实则不然,很多人只知道使用继承的表面形式来创建一个类,却不知道其内部真正的创建是由type来创建的。 +这个问题肯定很傻。实则不然,很多初学者只知道使用继承的表面形式来创建一个类,却不知道其内部真正的创建是由type来创建的。 type?这不是判断对象类型的函数吗? @@ -37,44 +37,7 @@ def say(self): User = type("User", (BaseClass, ), {"name":"user", "say":say}) ``` -## 3.2.3 \__new__ 有什么用? - -在没有元类的情况下,每次创建实例,在先进入 \_\_init__ 之前都会先进入 \_\_new__ 。 - -```python -class User: - def __new__(cls, *args, **kwargs): - print("in BaseClass") - return super().__new__(cls) - - def __init__(self, name): - print("in User") - self.name = name - -# ======== 上下写法效果一样 ========== - -class BaseClass(): - def __new__(cls, *args, **kwargs): - print("in BaseClass") - return super().__new__(cls) - -class User(BaseClass): - def __init__(self, name): - print("in User") - self.name = name -``` -使用如下 -```python ->>> u = User('wangbm') -in BaseClass -in User ->>> u.name -'wangbm' -``` - -在后面元类那里,\_\_new__ 会在创建类时,先在元类中进入(这是第一次进入)。 - -## 3.2.4 理解什么是元类 +## 3.2.3 理解什么是元类 什么是类?可能谁都知道,类就是用来创建对象的「模板」。 @@ -87,7 +50,7 @@ type是Python在背后用来创建所有类的元类,我们熟知的类的始 >>> type(type) ->>> type(object +>>> type(object) >>> type(int) @@ -110,7 +73,7 @@ type:是用来创建类对象的类。 一个类的类型,是元类 一个元类的类型,是type ``` -来看下实例 +写个简单的小示例来验证下 ``` # Python3.7 >>> class MetaPerson(type): @@ -128,9 +91,7 @@ type:是用来创建类对象的类。 ``` -上面是一个简单的示例。 - -下面看一个稍微完整的 +下面再来看一个稍微完整的 ```python # 注意要从type继承 class BaseClass(type): @@ -140,30 +101,77 @@ class BaseClass(type): class User(metaclass=BaseClass): def __init__(self, name): + print("in User") self.name = name + +# in BaseClass user = User("wangbm") +# in User ``` +综上,我们知道了类是元类的实例,所以在创建一个普通类时,其实会走元类的 `__new__`。 -## 3.3.5 使用元类的意义 +同时,我们又知道在类里实现了 `__call__` 就可以让这个类的实例变成可调用。 -正常情况下,我们都不会使用到元类。但是这并不意味着,它不重要。假如某一天,我们需要写一个框架,很有可能就需要用到元类。 +所以在我们对普通类进行实例化时,实际是对一个元类的实例(也就是普通类)进行直接调用,所以会走进元类的 `__call__` -但是,为什么要用它呢?不要它会怎样? +在这里可以借助 「单例的实现」举一个例子,你就清楚了 -经过我的总结,元类的作用过程如下 +```python +class MetaSingleton(type): + def __call__(cls, *args, **kwargs): + print("cls:{}".format(cls.__name__)) + print("====1====") + if not hasattr(cls, "_instance"): + print("====2====") + cls._instance = type.__call__(cls, *args, **kwargs) + return cls._instance + +class User(metaclass=MetaSingleton): + def __init__(self, *args, **kw): + print("====3====") + for k,v in kw: + setattr(self, k, v) +``` + +验证结果 + +```python +>>> u1 = User('wangbm1') +cls:User +====1==== +====2==== +====3==== +>>> u1.age = 20 +>>> u2 = User('wangbm2') +cls:User +====1==== +>>> u2.age +20 +>>> u1 is u2 +True +``` + + + +## 3.3.4 使用元类的意义 + +正常情况下,我们都不会使用到元类。但是这并不意味着,它不重要。假如某一天,我们需要写一个框架,很有可能就需要你对元类要有进一步的研究。 + +元类有啥用,用我通俗的理解,元类的作用过程: 1. 拦截类的创建 2. 拦截下后,进行修改 3. 修改完后,返回修改后的类 +所以,很明显,为什么要用它呢?不要它会怎样? -很明显,使用元类,是要对类进行定制修改。使用元类来动态生成元类的实例,而99%的开发人员是不需要动态修改类的,因为这应该是框架才需要考虑的事。 +使用元类,是要对类进行定制修改。使用元类来动态生成元类的实例,而99%的开发人员是不需要动态修改类的,因为这应该是框架才需要考虑的事。 -但是,这样说,你一定不会服气,到底元类用来干什么?其实元类的作用就是`创建API`,一个最典型的应用是 `Django ORM。` +但是,这样说,你一定不会服气,到底元类用来干什么?其实元类的作用就是`创建API`,一个最典型的应用是 `Django ORM`。 -## 3.3.6 元类实战:ORM +## 3.3.5 元类实战:ORM 使用过Django ORM的人都知道,有了ORM,使得我们操作数据库,变得异常简单。 @@ -194,7 +202,7 @@ u.save() 从上面的`User`类中,我们看到`StrField`和`IntField`,从字段意思上看,我们很容易看出这代表两个字段类型。字段名分别是`id`,`username`,`email`,`password`。 -`StrField`和`IntField`在这里的用法,叫做`属性描述符`,如果对这个不了解的可以查看文章底部的参考文章,也是我写的。 +`StrField`和`IntField`在这里的用法,叫做`属性描述符`。 简单来说呢,`属性描述符`可以实现对属性值的类型,范围等一切做约束,意思就是说变量id只能是int类型,变量name只能是str类型,否则将会抛出异常。 那如何实现这两个`属性描述符`呢?请看代码。 @@ -251,15 +259,13 @@ class BaseModel(metaclass=ModelMetaClass): pass ``` -从`BaseModel`类中,save函数里面有几个新变量, +从`BaseModel`类中,save函数里面有几个新变量。 1. fields: 存放所有的字段属性 2. db_table:表名 ->**注意**:上面代码中class BaseModel(metaclass=ModelMetaClass)请替换成class BaseModel(object) 再阅读。这样更贴合思考顺序。 - 我们思考一下这个`u`实例的创建过程: -`type` -> `object` -> `BaseModel` -> `User` -> `u` +`type` -> `ModelMetaClass` -> `BaseModel` -> `User` -> `u` 这里会有几个问题。 @@ -269,9 +275,6 @@ class BaseModel(metaclass=ModelMetaClass): 上面这几个问题,其实都可以通过元类的`__new__`函数来完成。 ->元类的`__new__`和普通类的可不一样,元类的`__new__`,可以获取到上层类的一切属性和方法,包括类名,魔法方法。 ->而普通类的`__new__` 只能获取到实例化时外界传入的属性。 - 下面就来看看,如何用元类来解决这些问题呢?请看代码。 ```python @@ -302,7 +305,40 @@ class ModelMetaClass(type): return super().__new__(cls, name, bases, attrs) ``` +## 3.2.6 \__new__ 有什么用? + +在没有元类的情况下,每次创建实例,在先进入 `__init__` 之前都会先进入 ` __new__`。 + +```python +class User: + def __new__(cls, *args, **kwargs): + print("in BaseClass") + return super().__new__(cls) + + def __init__(self, name): + print("in User") + self.name = name +``` + +使用如下 + +```python +>>> u = User('wangbm') +in BaseClass +in User +>>> u.name +'wangbm' +``` + +在有元类的情况下,每次创建类时,会都先进入 元类的 `__new__` 方法,如果你要对类进行定制,可以在这时做一些手脚。 + +综上,元类的`__new__`和普通类的不一样: + +- 元类的`__new__` 在创建类时就会进入,它可以获取到上层类的一切属性和方法,包括类名,魔法方法。 +- 而普通类的`__new__` 在实例化时就会进入,它仅能获取到实例化时外界传入的属性。 + ## 附录:参考文章 + - [Python Cookbook - 元编程](http://python3-cookbook.readthedocs.io/zh_CN/latest/chapters/p09_meta_programming.html) - [深刻理解Python中的元类](http://blog.jobbole.com/21351/) diff --git a/source/c03/c03_02.rst b/source/c03/c03_02.rst index 5a1cee4..6f1aa66 100755 --- a/source/c03/c03_02.rst +++ b/source/c03/c03_02.rst @@ -8,7 +8,7 @@ 类是如何产生? -这个问题肯定很傻。实则不然,很多人只知道使用继承的表面形式来创建一个类,却不知道其内部真正的创建是由type来创建的。 +这个问题肯定很傻。实则不然,很多初学者只知道使用继承的表面形式来创建一个类,却不知道其内部真正的创建是由type来创建的。 type?这不是判断对象类型的函数吗? @@ -41,49 +41,7 @@ type?这不是判断对象类型的函数吗? # 使用type来创建User类 User = type("User", (BaseClass, ), {"name":"user", "say":say}) -3.2.3 \__new_\_ 有什么用? --------------------------- - -在没有元类的情况下,每次创建实例,在先进入 \__init_\_ 之前都会先进入 -\__new_\_ 。 - -.. code:: python - - class User: - def __new__(cls, *args, **kwargs): - print("in BaseClass") - return super().__new__(cls) - - def __init__(self, name): - print("in User") - self.name = name - - # ======== 上下写法效果一样 ========== - - class BaseClass(): - def __new__(cls, *args, **kwargs): - print("in BaseClass") - return super().__new__(cls) - - class User(BaseClass): - def __init__(self, name): - print("in User") - self.name = name - -使用如下 - -.. code:: python - - >>> u = User('wangbm') - in BaseClass - in User - >>> u.name - 'wangbm' - -在后面元类那里,__new_\_ -会在创建类时,先在元类中进入(这是第一次进入)。 - -3.2.4 理解什么是元类 +3.2.3 理解什么是元类 -------------------- 什么是类?可能谁都知道,类就是用来创建对象的「模板」。 @@ -100,7 +58,7 @@ type是Python在背后用来创建所有类的元类,我们熟知的类的始 >>> type(type) - >>> type(object + >>> type(object) >>> type(int) @@ -125,7 +83,7 @@ type是Python在背后用来创建所有类的元类,我们熟知的类的始 一个类的类型,是元类 一个元类的类型,是type -来看下实例 +写个简单的小示例来验证下 :: @@ -144,9 +102,7 @@ type是Python在背后用来创建所有类的元类,我们熟知的类的始 >>> print(type(Tom.__class__.__class__)) -上面是一个简单的示例。 - -下面看一个稍微完整的 +下面再来看一个稍微完整的 .. code:: python @@ -158,29 +114,79 @@ type是Python在背后用来创建所有类的元类,我们熟知的类的始 class User(metaclass=BaseClass): def __init__(self, name): + print("in User") self.name = name + + # in BaseClass user = User("wangbm") + # in User -3.3.5 使用元类的意义 --------------------- +综上,我们知道了类是元类的实例,所以在创建一个普通类时,其实会走元类的 +``__new__``\ 。 + +同时,我们又知道在类里实现了 ``__call__`` +就可以让这个类的实例变成可调用。 -正常情况下,我们都不会使用到元类。但是这并不意味着,它不重要。假如某一天,我们需要写一个框架,很有可能就需要用到元类。 +所以在我们对普通类进行实例化时,实际是对一个元类的实例(也就是普通类)进行直接调用,所以会走进元类的 +``__call__`` -但是,为什么要用它呢?不要它会怎样? +在这里可以借助 「单例的实现」举一个例子,你就清楚了 -经过我的总结,元类的作用过程如下 +.. code:: python + + class MetaSingleton(type): + def __call__(cls, *args, **kwargs): + print("cls:{}".format(cls.__name__)) + print("====1====") + if not hasattr(cls, "_instance"): + print("====2====") + cls._instance = type.__call__(cls, *args, **kwargs) + return cls._instance + + class User(metaclass=MetaSingleton): + def __init__(self, *args, **kw): + print("====3====") + for k,v in kw: + setattr(self, k, v) + +验证结果 + +.. code:: python + + >>> u1 = User('wangbm1') + cls:User + ====1==== + ====2==== + ====3==== + >>> u1.age = 20 + >>> u2 = User('wangbm2') + cls:User + ====1==== + >>> u2.age + 20 + >>> u1 is u2 + True + +3.3.4 使用元类的意义 +-------------------- + +正常情况下,我们都不会使用到元类。但是这并不意味着,它不重要。假如某一天,我们需要写一个框架,很有可能就需要你对元类要有进一步的研究。 + +元类有啥用,用我通俗的理解,元类的作用过程: 1. 拦截类的创建 2. 拦截下后,进行修改 3. 修改完后,返回修改后的类 -很明显,使用元类,是要对类进行定制修改。使用元类来动态生成元类的实例,而99%的开发人员是不需要动态修改类的,因为这应该是框架才需要考虑的事。 +所以,很明显,为什么要用它呢?不要它会怎样? + +使用元类,是要对类进行定制修改。使用元类来动态生成元类的实例,而99%的开发人员是不需要动态修改类的,因为这应该是框架才需要考虑的事。 但是,这样说,你一定不会服气,到底元类用来干什么?其实元类的作用就是\ ``创建API``\ ,一个最典型的应用是 -``Django ORM。`` +``Django ORM``\ 。 -3.3.6 元类实战:ORM +3.3.5 元类实战:ORM ------------------- 使用过Django ORM的人都知道,有了ORM,使得我们操作数据库,变得异常简单。 @@ -216,7 +222,7 @@ ORM的一个类(User),就对应数据库中的一张表。id,name,email,passwo 从上面的\ ``User``\ 类中,我们看到\ ``StrField``\ 和\ ``IntField``\ ,从字段意思上看,我们很容易看出这代表两个字段类型。字段名分别是\ ``id``,\ ``username``,\ ``email``,\ ``password``\ 。 -``StrField``\ 和\ ``IntField``\ 在这里的用法,叫做\ ``属性描述符``\ ,如果对这个不了解的可以查看文章底部的参考文章,也是我写的。 +``StrField``\ 和\ ``IntField``\ 在这里的用法,叫做\ ``属性描述符``\ 。 简单来说呢,\ ``属性描述符``\ 可以实现对属性值的类型,范围等一切做约束,意思就是说变量id只能是int类型,变量name只能是str类型,否则将会抛出异常。 那如何实现这两个\ ``属性描述符``\ 呢?请看代码。 @@ -276,16 +282,12 @@ ORM的一个类(User),就对应数据库中的一张表。id,name,email,passwo values=','.join(db_values)) pass -从\ ``BaseModel``\ 类中,save函数里面有几个新变量, 1. fields: +从\ ``BaseModel``\ 类中,save函数里面有几个新变量。 1. fields: 存放所有的字段属性 2. db_table:表名 - **注意**\ :上面代码中class - BaseModel(metaclass=ModelMetaClass)请替换成class BaseModel(object) - 再阅读。这样更贴合思考顺序。 - 我们思考一下这个\ ``u``\ 实例的创建过程: -``type`` -> ``object`` -> ``BaseModel`` -> ``User`` -> ``u`` +``type`` -> ``ModelMetaClass`` -> ``BaseModel`` -> ``User`` -> ``u`` 这里会有几个问题。 @@ -296,9 +298,6 @@ ORM的一个类(User),就对应数据库中的一张表。id,name,email,passwo 上面这几个问题,其实都可以通过元类的\ ``__new__``\ 函数来完成。 - 元类的\ ``__new__``\ 和普通类的可不一样,元类的\ ``__new__``\ ,可以获取到上层类的一切属性和方法,包括类名,魔法方法。 - 而普通类的\ ``__new__`` 只能获取到实例化时外界传入的属性。 - 下面就来看看,如何用元类来解决这些问题呢?请看代码。 .. code:: python @@ -329,6 +328,43 @@ ORM的一个类(User),就对应数据库中的一张表。id,name,email,passwo attrs["fields"] = fields return super().__new__(cls, name, bases, attrs) +3.2.6 \__new_\_ 有什么用? +-------------------------- + +在没有元类的情况下,每次创建实例,在先进入 ``__init__`` 之前都会先进入 +``__new__``\ 。 + +.. code:: python + + class User: + def __new__(cls, *args, **kwargs): + print("in BaseClass") + return super().__new__(cls) + + def __init__(self, name): + print("in User") + self.name = name + +使用如下 + +.. code:: python + + >>> u = User('wangbm') + in BaseClass + in User + >>> u.name + 'wangbm' + +在有元类的情况下,每次创建类时,会都先进入 元类的 ``__new__`` +方法,如果你要对类进行定制,可以在这时做一些手脚。 + +综上,元类的\ ``__new__``\ 和普通类的不一样: + +- 元类的\ ``__new__`` + 在创建类时就会进入,它可以获取到上层类的一切属性和方法,包括类名,魔法方法。 +- 而普通类的\ ``__new__`` + 在实例化时就会进入,它仅能获取到实例化时外界传入的属性。 + 附录:参考文章 -------------- diff --git a/source/c04/c04_17.md b/source/c04/c04_17.md index f491278..8fa80f8 100644 --- a/source/c04/c04_17.md +++ b/source/c04/c04_17.md @@ -1,4 +1,4 @@ -# 4.17 23种设计模式 +# 4.17 详解 23 种设计模式 @@ -269,7 +269,7 @@ class User: True ``` - +![](http://image.python-online.cn/20190512113846.png) - 使用装饰器 @@ -312,7 +312,48 @@ class User: True ``` +![](http://image.python-online.cn/20190512113917.png) + +- 使用元类 + +```python +class MetaSingleton(type): + def __call__(cls, *args, **kwargs): + print("cls:{}".format(cls.__name__)) + print("====1====") + if not hasattr(cls, "_instance"): + print("====2====") + cls._instance = type.__call__(cls, *args, **kwargs) + return cls._instance + +class User(metaclass=MetaSingleton): + def __init__(self, *args, **kw): + print("====3====") + for k,v in kw: + setattr(self, k, v) +``` + +验证结果 + +``` +>>> u1 = User('wangbm1') +cls:User +====1==== +====2==== +====3==== +>>> u1.age = 20 +>>> u2 = User('wangbm2') +cls:User +====1==== +>>> u2.age +20 +>>> u1 is u2 +True +``` + +![](http://image.python-online.cn/20190512114028.png) +某群友由这个单例引出另一个 nb 的写法。 diff --git a/source/c04/c04_17.rst b/source/c04/c04_17.rst index 9bf9a1f..184a946 100644 --- a/source/c04/c04_17.rst +++ b/source/c04/c04_17.rst @@ -1,5 +1,5 @@ -4.17 23种设计模式 -================= +4.17 详解 23 种设计模式 +======================= 4.17.1 策略模式 --------------- @@ -274,6 +274,8 @@ Order >>> u1 is u2 True +|image1| + - 使用装饰器 .. code:: python @@ -315,6 +317,49 @@ Order >>> u1 is u2 True +|image2| + +- 使用元类 + +.. code:: python + + class MetaSingleton(type): + def __call__(cls, *args, **kwargs): + print("cls:{}".format(cls.__name__)) + print("====1====") + if not hasattr(cls, "_instance"): + print("====2====") + cls._instance = type.__call__(cls, *args, **kwargs) + return cls._instance + + class User(metaclass=MetaSingleton): + def __init__(self, *args, **kw): + print("====3====") + for k,v in kw: + setattr(self, k, v) + +验证结果 + +:: + + >>> u1 = User('wangbm1') + cls:User + ====1==== + ====2==== + ====3==== + >>> u1.age = 20 + >>> u2 = User('wangbm2') + cls:User + ====1==== + >>> u2.age + 20 + >>> u1 is u2 + True + +|image3| + +某群友由这个单例引出另一个 nb 的写法。 + 参考文档 -------- @@ -327,4 +372,7 @@ Order .. |image0| image:: http://image.python-online.cn/20190414144511.png +.. |image1| image:: http://image.python-online.cn/20190512113846.png +.. |image2| image:: http://image.python-online.cn/20190512113917.png +.. |image3| image:: http://image.python-online.cn/20190512114028.png From ce988482659d2b3c07c1de2fce72a38a4342da31 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 12 May 2019 19:31:28 +0800 Subject: [PATCH 005/302] =?UTF-8?q?=E5=8D=95=E4=BE=8B=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E6=96=87=E7=AB=A0=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_17.md | 80 ++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 48 deletions(-) diff --git a/source/c04/c04_17.md b/source/c04/c04_17.md index 8fa80f8..487c00c 100644 --- a/source/c04/c04_17.md +++ b/source/c04/c04_17.md @@ -231,9 +231,40 @@ class BestPromo(Promotion): ## 4.17.2 单例模式 +单例(Singleton)模式,应该是设计模式里面最好理解的一个模式。 +使用它,就是为了保证全局环境下只能有一个该类的实例。 -使用单例模式,有几个方法,这里写两个 +它的目的,我这里将其分为两类: + +一类是**必须这样做** + +大家在解释单例模式时,经常要提到的一个例子是 Windows 的任务管理器。如果我们打开多个任务管理器窗口。显示的内容完全一致,如果在内部是两个一模一样的对象,那就是重复对象,就造成了内存的浪费;相反,如果两个窗口的内容不一致,那就会至少有一个窗口展示的内容是错误的,会给用户造成误解,到底哪个才是当前真实的状态呢? + +还有一个常见的例子是,一个网站的访问量、在线人数,在项目中是全局唯一(不考虑分布式),在这种情况下,使用单例模式是一种很好的方式。 + +而另一类是**最好这样做** + +比如,一个项目中多个地方需要读取同一份配置文件,如果每次使用都是导入重新创建实例,读取文件,用完后再销毁,这样做的话,就造成不必要的IO浪费,可以使用单例模式只生成一份配置在内存中。 + +从上面看来,在系统中确保某个对象的唯一性即一个类只能有一个实例有时是非常重要的。 + +总结一下,单例模式有如下优点 + +1、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用; +2、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间; +3、单例可长驻内存,减少系统开销。 + +和其他设计模式一样,单例模式有一定的适用场景,但同时它也会给我们带来一些问题。 + +1、单例模式的扩展是比较困难的; +2、赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到); +3、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试; +4、单例模式在某种情况下会导致“资源瓶颈”。 + +理论说完了,再来说说如何用 Python 写单例模式。 + +这里介绍了三个较为常用的。 - 使用 \_\_new__ @@ -254,21 +285,6 @@ class User: 验证结果 -``` ->>> u1 = User('wangbm1') -===== 1 ==== -===== 2 ==== -===== 3 ==== ->>> u1.age = 20 ->>> u2 = User('wangbm2') -===== 1 ==== -===== 3 ==== ->>> u2.age -20 ->>> u1 is u2 -True -``` - ![](http://image.python-online.cn/20190512113846.png) - 使用装饰器 @@ -298,20 +314,6 @@ class User: 验证结果 -``` ->>> u1 = User('wangbm1') -===== 1 ==== -===== 2 ==== -===== 3 ==== ->>> u1.age = 20 ->>> u2 = User('wangbm2') -===== 1 ==== ->>> u2.age -20 ->>> u1 is u2 -True -``` - ![](http://image.python-online.cn/20190512113917.png) - 使用元类 @@ -335,26 +337,8 @@ class User(metaclass=MetaSingleton): 验证结果 -``` ->>> u1 = User('wangbm1') -cls:User -====1==== -====2==== -====3==== ->>> u1.age = 20 ->>> u2 = User('wangbm2') -cls:User -====1==== ->>> u2.age -20 ->>> u1 is u2 -True -``` - ![](http://image.python-online.cn/20190512114028.png) -某群友由这个单例引出另一个 nb 的写法。 - ## 参考文档 From 14c5dd6453783da4c8fdfac8bbf7ad1951470d73 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 13 May 2019 00:30:28 +0800 Subject: [PATCH 006/302] =?UTF-8?q?=E5=8D=95=E4=BE=8B=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E5=9C=BA=E6=99=AF=E4=B8=8B=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_17.md | 121 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 4 deletions(-) diff --git a/source/c04/c04_17.md b/source/c04/c04_17.md index 487c00c..fd0b768 100644 --- a/source/c04/c04_17.md +++ b/source/c04/c04_17.md @@ -257,10 +257,11 @@ class BestPromo(Promotion): 和其他设计模式一样,单例模式有一定的适用场景,但同时它也会给我们带来一些问题。 -1、单例模式的扩展是比较困难的; -2、赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到); -3、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试; -4、单例模式在某种情况下会导致“资源瓶颈”。 +1、由于单例对象是全局共享,所以其状态维护需要特别小心。 +2、单例对象没有抽象层,扩展不便。 +3、赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到); +4、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试; +5、单例模式在某种情况下会导致“资源瓶颈”。 理论说完了,再来说说如何用 Python 写单例模式。 @@ -339,6 +340,118 @@ class User(metaclass=MetaSingleton): ![](http://image.python-online.cn/20190512114028.png) +以上的代码,一般情况下没有问题,但在并发场景中,就会出现线程安全的问题。 + +如下这段代码我开启10个线程来模拟 + +```python +import time +import threading + +class User: + _instance = None + + def __new__(cls, *args, **kwargs): + if not cls._instance: + time.sleep(1) + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self, name): + self.name = name + +def task(): + u = User("wangbm") + print(u) + +for i in range(10): + t = threading.Thread(target=task) + t.start() +``` + +从结果来观察,很容易就发现,单例械式失效了,在10个线程下,并发创建实例,并不能保证一个类只有一个实例。 + +```python +<__main__.User object at 0x1050563c8> +<__main__.User object at 0x10551a208> +<__main__.User object at 0x1050563c8> +<__main__.User object at 0x1055a93c8> +<__main__.User object at 0x1050563c8> +<__main__.User object at 0x105527160> +<__main__.User object at 0x1055f4e48> +<__main__.User object at 0x1055e6c88> +<__main__.User object at 0x1055afcf8> +<__main__.User object at 0x105605940> +``` + +这在 Java 中,是可以使用饿汉模式(在实例化之前就将单例对象创建出来)来避免这个问题,但在 Python 中,好像没法实现这样的饿汉模式,但是同样有解决方法,就是加锁。 + +首先实现一个给函数加锁的装饰器 + +```python +import threading + +def synchronized(func): + + func.__lock__ = threading.Lock() + + def lock_func(*args, **kwargs): + with func.__lock__: + return func(*args, **kwargs) + return lock_func +``` + +然后在实例化对象的函数上,使用这个装饰函数。 + +```python +import time +import threading + +class User: + _instance = None + + @synchronized + def __new__(cls, *args, **kwargs): + if not cls._instance: + time.sleep(1) + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self, name): + self.name = name + +def task(): + u = User("wangbm") + print(u) + +for i in range(10): + t = threading.Thread(target=task) + t.start() +``` + +结果如下,如预期只生成了一个实例。 + +```python +<__main__.User object at 0x10ff503c8> +<__main__.User object at 0x10ff503c8> +<__main__.User object at 0x10ff503c8> +<__main__.User object at 0x10ff503c8> +<__main__.User object at 0x10ff503c8> +<__main__.User object at 0x10ff503c8> +<__main__.User object at 0x10ff503c8> +<__main__.User object at 0x10ff503c8> +<__main__.User object at 0x10ff503c8> +<__main__.User object at 0x10ff503c8> +``` + + + +引用文章 + +- [Python与设计模式--单例模式](https://yq.aliyun.com/articles/70418?utm_content=m_14908#comment) +- [python设计模式 - 单例模式之饿汉懒汉](https://www.jianshu.com/p/73901db378dc) +- [Python线程安全的单例模式](https://blog.csdn.net/lucky404/article/details/79668131) + ## 参考文档 From 247bfbc8da9ea76a9da2514337a5af665baf5fd7 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 14 May 2019 19:25:03 +0800 Subject: [PATCH 007/302] =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E7=AC=A6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/aboutme.rst | 2 +- source/c01/c01_17.md | 86 ++++++++++++++++++++++++++++--------------- source/c04/c04_08.rst | 6 +-- source/preface.rst | 2 +- 4 files changed, 62 insertions(+), 34 deletions(-) diff --git a/source/aboutme.rst b/source/aboutme.rst index cc5a0d4..a0d5205 100755 --- a/source/aboutme.rst +++ b/source/aboutme.rst @@ -2,7 +2,7 @@ 关于作者 ============== -* 姓名: 小明同学 +* 姓名: 王炳明 * 微信: mrbensonwon * 公众号: Python编程时光 * Email: wongbingming@163.com diff --git a/source/c01/c01_17.md b/source/c01/c01_17.md index e68c0be..79595b8 100644 --- a/source/c01/c01_17.md +++ b/source/c01/c01_17.md @@ -186,51 +186,79 @@ class Student: --- -## 描述符注意事项 +## 1.17.2 描述符的访问规则 -描述符的创建只能放在类的层级上,否则所有实例都将共享相同的属性值。 +描述符分两种: + +- 数据描述符:实现了`__get__` 和 `__set__` 两种方法的描述符 +- 非数据描述符:只实现了`_get__` 一种方法的描述符 + +你一定会问,他们有什么区别呢?网上的讲解,我看过几个,很多都把一个简单的东西讲得复杂了。 + +其实就一句话,**数据描述器和非数据描述器的区别在于:相对于实例的字典的优先级**。 + +如果实例字典中有与描述器同名的属性,如果描述器是数据描述器,优先使用数据描述器,如果是非数据描述器,优先使用字典中的属性。 + +这边还是以上节的成绩管理的例子来说明,方便你理解。 ```python -class Score: +# 数据描述符 +class DataDes: def __init__(self, default=0): - self._value = default + self._score = default + + def __set__(self, instance, value): + self._score = value def __get__(self, instance, owner): - return self._value + print("访问数据描述符里的 __get__") + return self._score - def __set__(self, instance, value): - print(value) - if 0 <= value <= 100: - self._value = value - else: - raise ValueError +# 非数据描述符 +class NoDataDes: + def __init__(self, default=0): + self._score = default + + def __get__(self, instance, owner): + print("访问非数据描述符里的 __get__") + return self._score class Student: - math = Score(0) - chinese = Score(0) + math = DataDes(0) + chinese = NoDataDes(0) - def __init__(self, math, chinese, english): + def __init__(self, name, math, chinese): + self.name = name self.math = math self.chinese = chinese - Student.english = Score(english) - + + def __getattribute__(self, item): + print("调用 __getattribute__") + return super(Student, self).__getattribute__(item) + def __repr__(self): - return "".format(self.math, self.chinese, self.english) + return "".format( + self.name, self.math, self.chinese) ``` -看一下结果 +需要注意的是,math 是数据描述符,而 chinese 是非数据描述符。从下面的验证中,可以看出,当实例属性和数据描述符同名时,会优先访问数据描述符(如下面的math),而当实例属性和非数据描述符同名时,会优先访问实例属性(`__getattribute__`) ```python ->> std1 = Student(80,80,100) ->>> std1 - ->>> std2 = Student(80,80,0) ->>> std1 - +>>> std = Student('xm', 88, 99) +>>> +>>> std.math +调用 __getattribute__ +访问数据描述符里的 __get__ +88 +>>> std.chinese +调用 __getattribute__ +99 ``` -或者 +## 所有实例共享描述符 + +若要合理使用描述符,利用描述符给我们带来的编码上的便利。有一个坑,需要注意,比如下面这个Student我们没有定义构造函数 ```python class Score: @@ -256,7 +284,7 @@ class Student: return "".format(self.math, self.chinese, self.english) ``` -看一下 +看一下会出现什么样的问题,std2 居然共享了std1 的属性值,因为它被当成了一个类变量了,而每个实例都没有自己的实例变量,自然访问的是同一个变量。这样是很难达到我们使用描述符的初衷。 ```python >>> std1 = Student() @@ -273,9 +301,7 @@ class Student: ``` -以上例子说明,所有实例的属性是共享的,这是不合理的。 - -正当的做法应该是,所有的实例数据只属于该实例本身(通过实例初始化传入)。而需要所有实例共享的数据,不能通过实例化指定。 +而正确的做法应该是,所有的实例数据只属于该实例本身(通过实例初始化传入),具体写法可参考上一节。 @@ -285,6 +311,8 @@ https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p09_define_decorators_a https://blog.csdn.net/BF02jgtRS00XKtCx/article/details/82882622 +https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html + --- diff --git a/source/c04/c04_08.rst b/source/c04/c04_08.rst index d590ded..41fd32f 100755 --- a/source/c04/c04_08.rst +++ b/source/c04/c04_08.rst @@ -44,12 +44,12 @@ Ctrl+{ 或者 Ctrl+} 定位跳转到代码块(类和函数)的首末位置 - Ctrl+E 最近编辑的文件列表 + Ctrl+e 最近编辑的文件列表 Ctrl+p 显示参数列表 Alt+shift+c 查看最近修改的记录 - ctrl+shift+A 万能键 + ctrl+shift+a 万能键 ctrl+alt+L 格式代码 ctrl+alt+T 添加try/catch @@ -78,9 +78,9 @@ 参考文章: - https://zhuanlan.zhihu.com/p/36147819 - - https://resources.jetbrains.com/storage/products/intellij-idea/docs/IntelliJIDEA_ReferenceCard.pdf - https://www.jetbrains.com/help/pycharm/ctrl-alt.html +- https://www.zhihu.com/question/37787004 4.8.2 Sublime Text 3 -------------------- diff --git a/source/preface.rst b/source/preface.rst index ca72b10..ef7f090 100755 --- a/source/preface.rst +++ b/source/preface.rst @@ -5,7 +5,7 @@ ---------------------------------- 关于博客 ---------------------------------- -这个博客于2016年6月22日发布完成,使用的是 Sphinx 来生成文档,使用 Github 托管文档,并使用 Read the Doc 发布文档。 +这个博客于2018年6月29日发布完成,使用的是 Sphinx 来生成文档,使用 Github 托管文档,并使用 Read the Doc 发布文档。 具体搭建教程请查阅 博客构建教程_ From 73816788f48c07eda358e803e24e97e970be1592 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 14 May 2019 20:48:49 +0800 Subject: [PATCH 008/302] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=87=E7=AB=A0?= =?UTF-8?q?=EF=BC=9Aopenstack=E4=BD=BF=E7=94=A8dhcp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_08.md | 184 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 source/c08/c08_08.md diff --git a/source/c08/c08_08.md b/source/c08/c08_08.md new file mode 100644 index 0000000..719b586 --- /dev/null +++ b/source/c08/c08_08.md @@ -0,0 +1,184 @@ +# 8.8 OpenStack 如何使用DHCP? + +## 8.8.1 为何要使用dhcp? + +在创建虚拟机时,虚拟机的ip获取一般有两种方式: + +1. 静态IP:通过 ConfigDrive 注入虚拟机,由 Cloud-Init 负责配置。 +2. 动态IP:使用DHCP方式动态获取,由 NetworkManager 负责配置。 + +相比静态 ip ,动态IP更有利于管理。比如需要对已分配的虚拟机的ip进行更换。 + +若是使用 静态ip(写在配置文件里),更换 ip 需要在平台层面将虚拟机的旧ip改成新ip(通过挂卸网卡实现),手动登陆到虚拟机内部修改配置,并重启网络。 + +若是使用 动态ip(通过DHCP),可以做到修改IP地址无需重启虚拟机,动态生效。 + +## 8.8.2 集群网络拓扑结构 + +如下图所示 + +![](http://image.python-online.cn/20190514202013.png) + +红色部分是虚拟机内部需要增加的网卡设备。 + +br-int :为openvswitch-agent自动创建的ovs网桥,其主要的作用是供neutron-dhcp-agent挂设备的。 + +dchp-ovs:连接物理网卡与br-int之间的ovs桥,作用是打通宿主机与虚拟机内部的二层网络 + +tap : veth pair的tap设备端,主要作用是连接Linux namespace 下的dhcp server网卡设备与ovs网桥。一个network一个tap设备。 + + + +## 8.8.3 集群环境部署支持 + +### 8.8.3.1 网卡与网络 + +在控制节点上新增一个网卡,通过xml动态增加。 + +```xml + + + + + + + + +``` + +到tmp.xml所在目录里执行命令 + +```shell +virsh attach-device ws_controller01 ./tmp.xml --persistent --live +``` + +网卡配置文件:/etc/sysconfig/network-scripts/ifcfg-eth2 + +``` +DEVICE=eth2 +TYPE=OVSPort +ONBOOT=yes +BOOTPROTO=none +OVS_BRIDGE=dchp-ovs +DEVICETYPE=ovs +``` + +/etc/sysconfig/network-scripts/ifcfg-dhcp-ovs + +``` +BOOTPROTO=static +DEVICE=dhcp-ovs +ONBOOT=yes +TYPE=OVSBridge +``` + +重启网络 + +### 8.8.3.2 依赖包与配置 + +确保控制节点都安装了 `neutron-openvswitch-agent` 和 `neutron-dhcp-agent` 等几个包 + +![](http://image.python-online.cn/20190514202442.png) + +确保如下几个配置无误 + +1、**/etc/neutron/dhcp_agent.ini** + +``` +[DEFAULT] +ovs_integration_bridge = br-int +ovs_use_veth = True +interface_driver = openvswitch + +[OVS] +ovsdb_interface=vsctl +``` + +2、**/etc/neutron/plugins/ml2/openvswitch_agent.ini** + +``` +[ovs] +ovsdb_interface = vsctl +bridge_mappings = phynet0:dhcp-ovs +``` + +启动服务 + +```shell +systemctl enable neutron-dhcp-agent +systemctl enable neutron-openvswitch-agent +systemctl restart neutron-dhcp-agent +systemctl restart neutron-openvswitch-agent +``` + +通过 ovs-vsctl show 检查环境是否正常 + +![](http://image.python-online.cn/20190514202736.png) + + + +### 8.8.3.3 镜像支持 + +使用 dhcp 创建虚拟机时,cloudinit 检查到是 dhcp 模式,会跳过网卡配置文件的配置,而后启动的 NetworkManager (centos 是 NetworkManager, ubuntu 是network-manager)才负责获取ip。 + +所以为了支持DHCP,需要在镜像内部安装 NetworkManager ,并设置开机自启,关闭 network 服务并取消开机自启,以免冲突。 + +## 8.8.4 重要:场景验证 + +1. 启用DHCP后,dhcp server 会占用一个allocation_pools中的ip。 +2. 子网启用了dhcp,ConfigDrive不会携带静态的ip地址(类型为ipv4,左图),而是携带了将类型变更为ipv4_dhcp(右图),虚拟机启动时发送广播消息DHCP discover从dhcp server获得真正的ip地址。 + ![](http://image.python-online.cn/20190514203612.png) +3. 若子网启用了dhcp,并且创建了虚拟机,此时 disable dhcp,只是表象上 dhcp 被关了,而实际 dhcp port 被是占用着,因为 dhcp server 还要给之前创建的虚拟机提供服务。如果再用这个子网创建的虚拟机会使用dhcp ip而不是使用 static ip,而当有删除(新增)其他子网的动作时,这个dhcp-port 又会被删除。如果一个子网开启了dhcp,并且这个子网下**没有虚拟机**,更新子网 disable dhcp,dhcp-port**会立即删除**。再用这个子网创建的虚拟机会使用static ip。如果一个子网开启了dhcp,并且这个子网下**有虚拟机**,更新子网 disable dhcp,dhcp-port**不会立即删除**。再用这个子网创建的虚拟机会使用dhcp获取ip。 +4. dnsmasq进程是由dhcp-agent 服务管理的。如果停用 dhcp-agent 时,dnsmasq 进程并不会关闭。如果同一个网络下有多个dnsmasq ,不会影响正常的dhcp获取ip。 +5. 如果三个控制节点上的 dhcp-agent 都是关闭状态,此时创建虚拟机时,ip仍然会正常分配、port会正常创建,但由于nova-compute等不到neutron的port plugged事件,过一段时间就超时导致创建虚拟机失败。而如果在超时范围内将dhcp-agent启动起来,就可以立即创建成功。 +6. 如果一台节点上的 dhcp-agent 关闭了,neutron-server 会等待150s(agent_down_time*2)后再重新调度,将负责这个节点上的dnsmasq进程在另一台上启动起来。 +7. 虚拟机通过dhcp获取ip,和用config drive 注入静态ip配置的时间差不多,经验证从创建到ping通,dhcp花了22s,静态ip花了23s +8. 如果一个子网没有配置 dns,那么用这个子网创建虚拟机,虚拟机内部会将这个子网的dhcp server 的ip拿来做dns配在 /etc/resolv.conf 里,而且在排在最上面,可能会导致虚拟机上不了网。 +9. 使用 dhcp 的模式,cloudinit 从 configure drive 中知道是dhcp后就不会去刷新配置文件将static 改为dhcp(使用的是NetworkManager自动获取ip,这在开机启动时 NetworkManager就会先于cloudinit 去做),所以如果这个镜像原网卡配置文件里是静态ip,那么使用这个镜像创建dhcp 的虚拟机,就会暴露旧ip,但是这对于配置ip没有影响,NetworkManager 配置ip的顺序是先dhcp,获取不到再从配置文件读。 +10. 如果一个子网只有dhcp port,子网可以被删除,如果有其他port,则子网不能删除。 + + + +## 8.8.5 镜像问题排查 + +在**CentOS 7.x** 中,所有的服务都是通过systemd 管理的,服务的启动依赖也可以在 Service 中声明,会开机时 systemd 做为第一个服务启动,会将所有的依赖关系都理出来,按顺序去启动。 + +比如 local 阶段作为第一个执行的。 + +![](http://image.python-online.cn/20190430204707.png) + +而后的几个服务(如init阶段),都需要保证 后于 local 已经启动过才能运行。 + +![](http://image.python-online.cn/20190430204933.png) + +在 **CentOS 6.x** 中,由于系统过于古老并没有 systemd ,它的服务启动顺序是由 `/etc/rc.d/rc3.d` 目录进行管理的,按照编号进行从小到大执行。 + +![](http://image.python-online.cn/20190430205449.png) + +这个启动顺序相当重要,在 CentOS6.x 上会因此出现问题 + +假如我们要创建 一个CentOS 6.5 的虚拟机,而我们希望这个虚拟机的 ip 是静态ip(写在配置文件里),当你在虚拟机里使用的是NetworkManager(而非 network)时,会发现 ip 并不会自动配置上。而如果创建的是DHCP ip 的虚拟机时,就能正常配置ip。 + +在解释这个问题之前,先要理解 cloudinit 在local 阶段做了啥事。通过阅读源代码可知,在local阶段它会从 datasource 里读取网络配置信息,如果发现使用的是静态ip,cloudinit就会将网络信息(ip,dns,gateway等) 写入ifcfg配置文件。而如果发现使用的是 DHCP,cloudinit 并不会创建刷新网卡配置文件,配置ip的工作就交由 NetworkManager 会去自动获取。 + +从以信息可知,如果创建静态ip的虚拟机,NetworkManager 这个服务必须在 cloudinit-local 之后启动才可正常从配置文件中读取 ip 并配置。而当你在镜像里安装 NetworkManager后,默认情况下它的启动顺序是会在 cloudinit-local 之前的。 + +![img](http://image.python-online.cn/20190430211900.png) + +**解决方法**也很简单,将 `S23NetworkManager` 重命名为 `S54NetworkManager` 即可。 + +## 8.8.6 源码解读 + +发生dhcp-agent DOWN后会触发重新调度将dnsmasq迁到另一台,对应函数:reschedule_resources_from_down_agents,这个函数里默认会等待150s:wait_down_agents + +**dhcp-port 是如何被创建出来的?** + +从 neutron\neutron-0.0.1.dev2\neutron\agent\linux\dhcp.py: setup() 开始 + +再进入 setup_dhcp_port(),从这个函数里可以知道,dhcp-port的创建顺序:existint_dhcp-port -> reserved-dhcp-port -> setup_dhcp_port() + + + +--- + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file From 226cd79a2e932ecc7227171784129e3232e80689 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 14 May 2019 20:57:40 +0800 Subject: [PATCH 009/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E7=AB=A0?= =?UTF-8?q?=EF=BC=9Agpu=E7=9B=B4=E9=80=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_07.md | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/source/c08/c08_07.md b/source/c08/c08_07.md index 24c8a5f..b199967 100644 --- a/source/c08/c08_07.md +++ b/source/c08/c08_07.md @@ -56,9 +56,7 @@ openstack image set IMG-UUID --property img_hide_hypervisor_id=true ``` - - - +由于我们当前使用的是 Newton 版本,所以需参考 P 版的代码进行相应的改动,在生成虚拟机的xml的时候,在feature标签内添加如下一段内容 ```xml @@ -66,7 +64,30 @@ openstack image set IMG-UUID --property img_hide_hypervisor_id=true ``` +具体 P版的代码如何实现,主要函数是这一行 + +![](http://image.python-online.cn/20190514205605.png) + +## 8.7.2 使用说明 + +在环境部署好,gpu 资源也充足的情况下,想要创建一台gpu直通的虚拟机,还需要确保以下两个步骤完成。 + +### 8.7.2.1 模板配置 + +GPU 作为一种硬件资源,同样需要在模板中配置,配置方式是在 flavor 中添加Extra_spec +- "pci_passthrough:alias"="M60:1" : M60是上面控制节点中,在nova.conf 中配置的 pci_alias 别名。1 表示,需要一个gpu设备。 +- "pci_passthrough:alias"="P4:2" : P4是上面控制节点中,在nova.conf 中配置的 pci_alias 别名。2 表示,需要两个gpu设备。 + +``` +`nova flavor-key ``set` `pci_passthrough:``alias``=``'nvidia:1'` +``` + +### 8.7.2.2 镜像配置 + +``` +`openstack image ``set` ` --property img_hide_hypervisor_id=``true` +``` --- From 5ae90237e25111640fd7cb9eb85319d75b403619 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 14 May 2019 21:05:00 +0800 Subject: [PATCH 010/302] =?UTF-8?q?=E6=96=B0=E5=A2=83cloudinit=20userdata?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_06.md | 156 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 1 deletion(-) diff --git a/source/c08/c08_06.md b/source/c08/c08_06.md index f9b3136..f5473f5 100644 --- a/source/c08/c08_06.md +++ b/source/c08/c08_06.md @@ -1,4 +1,4 @@ -# 8.6 深度解读Cloud-init源码 +# 8.6 源码解读:Cloud-Init cloud-init 是 linux 的一个工具,当系统启动时,cloud-init 可从 nova metadata 服务或者 config drive 中获取 metadata,完成一些虚拟机的初始化工作,这些工作有可能是每台虚拟机例行的动作,如配置ip,也有可能是定制化动作,如注入密码等。 @@ -176,7 +176,161 @@ Apr 29 11:13:29 localhost NetworkManager[1365]: exiting (error) ![](http://image.python-online.cn/20190430232911.png) +## 8.6.5 userdata 使用说明 +现在 Userdate 可以支持如下三种格式 + +- User-Data Script +- Cloud Config Data +- Upstart Job + +### User-Data Script + +**作用**:写入shell脚本内容,在虚拟机创建的时候执行脚本 + +**配置文件**:myscript.sh + +content-types:text/x-shellscript + +```shell +#!/bin/sh +echo "Hello World. The time is now $(date -R)!" | tee /root/output.txt +``` + +### Cloud Config Data + +**作用**:注入用户数据。 + +配置项格式注意是yaml 格式。 + +1. 可以用write_files,将用户的数据写入指定路径文件中,并设置权限,指定编码等功能; + +2. 可以用ws_virt_network_dep,指定虚IP创建虚拟机。 + +3. 可以用runcmd,在虚拟机创建的时候执行,执行的输出结果将记录在/var/log/cloud-init-output.log + +4. 可以用 resolv_conf,(仅RHEL系列系统可用)指定创建虚拟机后配置的 dns,注意要同时配置上manage-resolv-conf: true + +5. 可以用 ssh_authorized_keys,将指定的公钥注入虚拟机 + + + +**配置文件**:myconfig.cfg + +content-types:text/cloud-config + +```shell +#cloud-config +write_files: +- encoding: b64 + content: CiMgVGhpcyBmaWxlIGNvbnRyb2xzIHRoZSBzdGF0ZSBvZiBTRUxpbnV4 + owner: root:root + path: /etc/sysconfig/selinux + permissions: '0644' +- content: | + # My new /etc/sysconfig/samba file + SMBDOPTIONS="-D" + path: /etc/sysconfig/samba +- content: !!binary | + H4sIAIDb/U8C/1NW1E/KzNMvzuBKTc7IV8hIzcnJVyjPL8pJ4QIA6N+MVxsAAAA= + path: /bin/arch + permissions: '0555' +- encoding: gzip + content: !!binary | + H4sIAIDb/U8C/1NW1E/KzNMvzuBKTc7IV8hIzcnJVyjPL8pJ4QIA6N+MVxsAAAA= + path: /usr/bin/hello +permissions: '0755' + +ws_virt_network_dep: + 36.250.74.xx: + port_0: + ip: 192.168.2.10 + netmask: 255.255.255.0 + 10.10.10.xx: + port_0: + ip: 192.168.3.10 + netmask: 255.255.255.0 + gateway: 192.168.3.254 + + +runcmd: + - [ sed, -i, -e, '%s/x/y/g', some_file] + - echo "modified some_file" + - [cat, some_file] + +manage-resolv-conf: true +resolv_conf: + nameservers: + - '8.8.8.8' + - '114.114.114.114' + +ssh_authorized_keys: + - ssh_key_1 + - ssh_key_2 +``` + +### Upstart Job + +用处:设置开机启动项,将文件放入 /etc/init/ 目录下,生成的文件,名为 [filename].conf,对于这个例子,文件名即为myjob.cfg.conf + + + +**配置文件**:myjob.cfg + +content-types:text/upstart-job + +```shell +#upstart-job +#!/bin/sh +echo "Hello World. The time is now $(date -R)!" | tee /root/output.txt +``` + +### 混合模式 + +配置好所需配置文件内容后。这里一个辅助脚本来将三种格式的配置合并。 + +**辅助脚本**:generate_mime_messages.py + +```python +#!/usr/bin/python + +import sys + +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText + +if len(sys.argv) == 1: + print("%s input-file:type ..." % (sys.argv[0])) + sys.exit(1) + +combined_message = MIMEMultipart() +for i in sys.argv[1:]: + (filename, format_type) = i.split(":", 1) + with open(filename) as fh: + contents = fh.read() + sub_message = MIMEText(contents, format_type, sys.getdefaultencoding()) + sub_message.add_header('Content-Disposition', 'attachment; filename="%s"' % (filename)) + combined_message.attach(sub_message) + +print(combined_message) +``` + +执行如下命令 + +```shell +python generate_mime_messages.py \ +myscript.sh:text/x-shellscript \ +myconfig.cfg:text/cloud-config \ +myjob.cfg:text/upstart-job +``` + +命令说明: + +- myscript.sh、myconfig.cfg、myjob.cfg 是配置文件 + +- text/x-shellscript、text/cloud-config、text/upstart-job 是对应的内容content-types + +执行完后就会生成一段内容,再将这段内容进行 base64 编码处理,就是userdata 的参数。 --- From 17ecba579ff4b663102ef21ca6a110a9691320c9 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Wed, 15 May 2019 12:32:48 +0800 Subject: [PATCH 011/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0rst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_17.rst | 91 ++++++++++----- source/c04/c04_08.rst | 6 +- source/c04/c04_15.md | 8 +- source/c04/c04_15.rst | 11 +- source/c04/c04_17.rst | 195 ++++++++++++++++++++++++-------- source/c08/c08_06.rst | 165 ++++++++++++++++++++++++++- source/c08/c08_07.rst | 36 ++++++ source/c08/c08_08.rst | 255 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 683 insertions(+), 84 deletions(-) create mode 100644 source/c08/c08_08.rst diff --git a/source/c01/c01_17.rst b/source/c01/c01_17.rst index 6a13002..c16c07e 100644 --- a/source/c01/c01_17.rst +++ b/source/c01/c01_17.rst @@ -208,52 +208,82 @@ math、chinese、english这三个属性的时候,都会经过 Score -------------- -描述符注意事项 --------------- +1.17.2 描述符的访问规则 +----------------------- + +描述符分两种: + +- 数据描述符:实现了\ ``__get__`` 和 ``__set__`` 两种方法的描述符 +- 非数据描述符:只实现了\ ``_get__`` 一种方法的描述符 + +你一定会问,他们有什么区别呢?网上的讲解,我看过几个,很多都把一个简单的东西讲得复杂了。 + +其实就一句话,\ **数据描述器和非数据描述器的区别在于:相对于实例的字典的优先级**\ 。 -描述符的创建只能放在类的层级上,否则所有实例都将共享相同的属性值。 +如果实例字典中有与描述器同名的属性,如果描述器是数据描述器,优先使用数据描述器,如果是非数据描述器,优先使用字典中的属性。 + +这边还是以上节的成绩管理的例子来说明,方便你理解。 .. code:: python - class Score: + # 数据描述符 + class DataDes: def __init__(self, default=0): - self._value = default + self._score = default + + def __set__(self, instance, value): + self._score = value def __get__(self, instance, owner): - return self._value + print("访问数据描述符里的 __get__") + return self._score - def __set__(self, instance, value): - print(value) - if 0 <= value <= 100: - self._value = value - else: - raise ValueError + # 非数据描述符 + class NoDataDes: + def __init__(self, default=0): + self._score = default + + def __get__(self, instance, owner): + print("访问非数据描述符里的 __get__") + return self._score class Student: - math = Score(0) - chinese = Score(0) + math = DataDes(0) + chinese = NoDataDes(0) - def __init__(self, math, chinese, english): + def __init__(self, name, math, chinese): + self.name = name self.math = math self.chinese = chinese - Student.english = Score(english) - + + def __getattribute__(self, item): + print("调用 __getattribute__") + return super(Student, self).__getattribute__(item) + def __repr__(self): - return "".format(self.math, self.chinese, self.english) + return "".format( + self.name, self.math, self.chinese) -看一下结果 +需要注意的是,math 是数据描述符,而 chinese +是非数据描述符。从下面的验证中,可以看出,当实例属性和数据描述符同名时,会优先访问数据描述符(如下面的math),而当实例属性和非数据描述符同名时,会优先访问实例属性(\ ``__getattribute__``\ ) .. code:: python - >> std1 = Student(80,80,100) - >>> std1 - - >>> std2 = Student(80,80,0) - >>> std1 - + >>> std = Student('xm', 88, 99) + >>> + >>> std.math + 调用 __getattribute__ + 访问数据描述符里的 __get__ + 88 + >>> std.chinese + 调用 __getattribute__ + 99 + +所有实例共享描述符 +------------------ -或者 +若要合理使用描述符,利用描述符给我们带来的编码上的便利。有一个坑,需要注意,比如下面这个Student我们没有定义构造函数 .. code:: python @@ -279,7 +309,8 @@ math、chinese、english这三个属性的时候,都会经过 Score def __repr__(self): return "".format(self.math, self.chinese, self.english) -看一下 +看一下会出现什么样的问题,std2 居然共享了std1 +的属性值,因为它被当成了一个类变量了,而每个实例都没有自己的实例变量,自然访问的是同一个变量。这样是很难达到我们使用描述符的初衷。 .. code:: python @@ -296,9 +327,7 @@ math、chinese、english这三个属性的时候,都会经过 Score >>> std1 # std2 也会改变std1 的属性值 -以上例子说明,所有实例的属性是共享的,这是不合理的。 - -正当的做法应该是,所有的实例数据只属于该实例本身(通过实例初始化传入)。而需要所有实例共享的数据,不能通过实例化指定。 +而正确的做法应该是,所有的实例数据只属于该实例本身(通过实例初始化传入),具体写法可参考上一节。 参考文档 -------- @@ -307,6 +336,8 @@ https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p09_define_decorators_a https://blog.csdn.net/BF02jgtRS00XKtCx/article/details/82882622 +https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html + -------------- .. figure:: http://image.python-online.cn/20190511161447.png diff --git a/source/c04/c04_08.rst b/source/c04/c04_08.rst index 41fd32f..d590ded 100755 --- a/source/c04/c04_08.rst +++ b/source/c04/c04_08.rst @@ -44,12 +44,12 @@ Ctrl+{ 或者 Ctrl+} 定位跳转到代码块(类和函数)的首末位置 - Ctrl+e 最近编辑的文件列表 + Ctrl+E 最近编辑的文件列表 Ctrl+p 显示参数列表 Alt+shift+c 查看最近修改的记录 - ctrl+shift+a 万能键 + ctrl+shift+A 万能键 ctrl+alt+L 格式代码 ctrl+alt+T 添加try/catch @@ -78,9 +78,9 @@ 参考文章: - https://zhuanlan.zhihu.com/p/36147819 + - https://resources.jetbrains.com/storage/products/intellij-idea/docs/IntelliJIDEA_ReferenceCard.pdf - https://www.jetbrains.com/help/pycharm/ctrl-alt.html -- https://www.zhihu.com/question/37787004 4.8.2 Sublime Text 3 -------------------- diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index c528a8f..899a3db 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -1,4 +1,4 @@ -# 4.15 10个 PyCharm 小技巧 +# 4.15 15个 PyCharm 小技巧 刚开始做公众号的时候,更新频率正常是一周两到三篇。老读者应该有注意到,以前都是写系列教程,对于读者而言,系列教程会更加友好,学习起来会更容易深入浅出,而对于作者来说,写系列教程,更有一种使命感,而这种使命感是维持更新一大动力。 @@ -452,6 +452,12 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 +## 附录 + +- [PyCharm 快捷键 Mac 版 ](https://resources.jetbrains.com/storage/products/pycharm/docs/PyCharm_ReferenceCard_mac.pdf) +- [PyCharm 快捷键 Win 版](https://resources.jetbrains.com/storage/products/pycharm/docs/PyCharm_ReferenceCard.pdf) +- [PyCharm Lanyu 注册码](http://idea.lanyus.com/) + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c04/c04_15.rst b/source/c04/c04_15.rst index 85a52b9..e8c8446 100644 --- a/source/c04/c04_15.rst +++ b/source/c04/c04_15.rst @@ -1,4 +1,4 @@ -4.15 10个 PyCharm 小技巧 +4.15 15个 PyCharm 小技巧 ======================== 刚开始做公众号的时候,更新频率正常是一周两到三篇。老读者应该有注意到,以前都是写系列教程,对于读者而言,系列教程会更加友好,学习起来会更容易深入浅出,而对于作者来说,写系列教程,更有一种使命感,而这种使命感是维持更新一大动力。 @@ -541,6 +541,15 @@ q(Mac暂时未找到对应快捷键),在当前页面就可以快速预览 这两个快捷键比起使用 Ctrl + 鼠标左键 跳进源代码来说,更加方便,,就像微信小程序一样,用完即焚,不会新产生一个标签页,也不需要来回跳转页面。 +附录 +---- + +- `PyCharm 快捷键 Mac + 版 `__ +- `PyCharm 快捷键 Win + 版 `__ +- `PyCharm Lanyu 注册码 `__ + -------------- .. figure:: http://image.python-online.cn/20190511161447.png diff --git a/source/c04/c04_17.rst b/source/c04/c04_17.rst index 184a946..f3dc492 100644 --- a/source/c04/c04_17.rst +++ b/source/c04/c04_17.rst @@ -238,7 +238,42 @@ Order 4.17.2 单例模式 --------------- -使用单例模式,有几个方法,这里写两个 +单例(Singleton)模式,应该是设计模式里面最好理解的一个模式。 + +使用它,就是为了保证全局环境下只能有一个该类的实例。 + +它的目的,我这里将其分为两类: + +一类是\ **必须这样做** + +大家在解释单例模式时,经常要提到的一个例子是 Windows +的任务管理器。如果我们打开多个任务管理器窗口。显示的内容完全一致,如果在内部是两个一模一样的对象,那就是重复对象,就造成了内存的浪费;相反,如果两个窗口的内容不一致,那就会至少有一个窗口展示的内容是错误的,会给用户造成误解,到底哪个才是当前真实的状态呢? + +还有一个常见的例子是,一个网站的访问量、在线人数,在项目中是全局唯一(不考虑分布式),在这种情况下,使用单例模式是一种很好的方式。 + +而另一类是\ **最好这样做** + +比如,一个项目中多个地方需要读取同一份配置文件,如果每次使用都是导入重新创建实例,读取文件,用完后再销毁,这样做的话,就造成不必要的IO浪费,可以使用单例模式只生成一份配置在内存中。 + +从上面看来,在系统中确保某个对象的唯一性即一个类只能有一个实例有时是非常重要的。 + +总结一下,单例模式有如下优点 + +1、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用; +2、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间; +3、单例可长驻内存,减少系统开销。 + +和其他设计模式一样,单例模式有一定的适用场景,但同时它也会给我们带来一些问题。 + +1、由于单例对象是全局共享,所以其状态维护需要特别小心。 +2、单例对象没有抽象层,扩展不便。 +3、赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到); +4、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试; +5、单例模式在某种情况下会导致“资源瓶颈”。 + +理论说完了,再来说说如何用 Python 写单例模式。 + +这里介绍了三个较为常用的。 - 使用 \__new_\_ @@ -259,21 +294,6 @@ Order 验证结果 -:: - - >>> u1 = User('wangbm1') - ===== 1 ==== - ===== 2 ==== - ===== 3 ==== - >>> u1.age = 20 - >>> u2 = User('wangbm2') - ===== 1 ==== - ===== 3 ==== - >>> u2.age - 20 - >>> u1 is u2 - True - |image1| - 使用装饰器 @@ -303,20 +323,6 @@ Order 验证结果 -:: - - >>> u1 = User('wangbm1') - ===== 1 ==== - ===== 2 ==== - ===== 3 ==== - >>> u1.age = 20 - >>> u2 = User('wangbm2') - ===== 1 ==== - >>> u2.age - 20 - >>> u1 is u2 - True - |image2| - 使用元类 @@ -340,25 +346,120 @@ Order 验证结果 -:: - - >>> u1 = User('wangbm1') - cls:User - ====1==== - ====2==== - ====3==== - >>> u1.age = 20 - >>> u2 = User('wangbm2') - cls:User - ====1==== - >>> u2.age - 20 - >>> u1 is u2 - True - |image3| -某群友由这个单例引出另一个 nb 的写法。 +以上的代码,一般情况下没有问题,但在并发场景中,就会出现线程安全的问题。 + +如下这段代码我开启10个线程来模拟 + +.. code:: python + + import time + import threading + + class User: + _instance = None + + def __new__(cls, *args, **kwargs): + if not cls._instance: + time.sleep(1) + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self, name): + self.name = name + + def task(): + u = User("wangbm") + print(u) + + for i in range(10): + t = threading.Thread(target=task) + t.start() + +从结果来观察,很容易就发现,单例械式失效了,在10个线程下,并发创建实例,并不能保证一个类只有一个实例。 + +.. code:: python + + <__main__.User object at 0x1050563c8> + <__main__.User object at 0x10551a208> + <__main__.User object at 0x1050563c8> + <__main__.User object at 0x1055a93c8> + <__main__.User object at 0x1050563c8> + <__main__.User object at 0x105527160> + <__main__.User object at 0x1055f4e48> + <__main__.User object at 0x1055e6c88> + <__main__.User object at 0x1055afcf8> + <__main__.User object at 0x105605940> + +这在 Java +中,是可以使用饿汉模式(在实例化之前就将单例对象创建出来)来避免这个问题,但在 +Python 中,好像没法实现这样的饿汉模式,但是同样有解决方法,就是加锁。 + +首先实现一个给函数加锁的装饰器 + +.. code:: python + + import threading + + def synchronized(func): + + func.__lock__ = threading.Lock() + + def lock_func(*args, **kwargs): + with func.__lock__: + return func(*args, **kwargs) + return lock_func + +然后在实例化对象的函数上,使用这个装饰函数。 + +.. code:: python + + import time + import threading + + class User: + _instance = None + + @synchronized + def __new__(cls, *args, **kwargs): + if not cls._instance: + time.sleep(1) + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self, name): + self.name = name + + def task(): + u = User("wangbm") + print(u) + + for i in range(10): + t = threading.Thread(target=task) + t.start() + +结果如下,如预期只生成了一个实例。 + +.. code:: python + + <__main__.User object at 0x10ff503c8> + <__main__.User object at 0x10ff503c8> + <__main__.User object at 0x10ff503c8> + <__main__.User object at 0x10ff503c8> + <__main__.User object at 0x10ff503c8> + <__main__.User object at 0x10ff503c8> + <__main__.User object at 0x10ff503c8> + <__main__.User object at 0x10ff503c8> + <__main__.User object at 0x10ff503c8> + <__main__.User object at 0x10ff503c8> + +引用文章 + +- `Python与设计模式–单例模式 `__ +- `python设计模式 - + 单例模式之饿汉懒汉 `__ +- `Python线程安全的单例模式 `__ 参考文档 -------- diff --git a/source/c08/c08_06.rst b/source/c08/c08_06.rst index f313500..dfcdbde 100644 --- a/source/c08/c08_06.rst +++ b/source/c08/c08_06.rst @@ -1,5 +1,5 @@ -8.6 深度解读Cloud-init源码 -========================== +8.6 源码解读:Cloud-Init +======================== cloud-init 是 linux 的一个工具,当系统启动时,cloud-init 可从 nova metadata 服务或者 config drive 中获取 @@ -225,6 +225,167 @@ CentOS 6.5)。 |image21| +8.6.5 userdata 使用说明 +----------------------- + +现在 Userdate 可以支持如下三种格式 + +- User-Data Script +- Cloud Config Data +- Upstart Job + +User-Data Script +~~~~~~~~~~~~~~~~ + +**作用**\ :写入shell脚本内容,在虚拟机创建的时候执行脚本 + +**配置文件**\ :myscript.sh + +content-types:text/x-shellscript + +.. code:: shell + + #!/bin/sh + echo "Hello World. The time is now $(date -R)!" | tee /root/output.txt + +Cloud Config Data +~~~~~~~~~~~~~~~~~ + +**作用**\ :注入用户数据。 + +配置项格式注意是yaml 格式。 + +1. 可以用write_files,将用户的数据写入指定路径文件中,并设置权限,指定编码等功能; + +2. 可以用ws_virt_network_dep,指定虚IP创建虚拟机。 + +3. 可以用runcmd,在虚拟机创建的时候执行,执行的输出结果将记录在/var/log/cloud-init-output.log + +4. 可以用 resolv_conf,(仅RHEL系列系统可用)指定创建虚拟机后配置的 + dns,注意要同时配置上manage-resolv-conf: true + +5. 可以用 ssh_authorized_keys,将指定的公钥注入虚拟机 + +**配置文件**\ :myconfig.cfg + +content-types:text/cloud-config + +.. code:: shell + + #cloud-config + write_files: + - encoding: b64 + content: CiMgVGhpcyBmaWxlIGNvbnRyb2xzIHRoZSBzdGF0ZSBvZiBTRUxpbnV4 + owner: root:root + path: /etc/sysconfig/selinux + permissions: '0644' + - content: | + # My new /etc/sysconfig/samba file + SMBDOPTIONS="-D" + path: /etc/sysconfig/samba + - content: !!binary | + H4sIAIDb/U8C/1NW1E/KzNMvzuBKTc7IV8hIzcnJVyjPL8pJ4QIA6N+MVxsAAAA= + path: /bin/arch + permissions: '0555' + - encoding: gzip + content: !!binary | + H4sIAIDb/U8C/1NW1E/KzNMvzuBKTc7IV8hIzcnJVyjPL8pJ4QIA6N+MVxsAAAA= + path: /usr/bin/hello + permissions: '0755' + + ws_virt_network_dep: + 36.250.74.xx: + port_0: + ip: 192.168.2.10 + netmask: 255.255.255.0 + 10.10.10.xx: + port_0: + ip: 192.168.3.10 + netmask: 255.255.255.0 + gateway: 192.168.3.254 + + + runcmd: + - [ sed, -i, -e, '%s/x/y/g', some_file] + - echo "modified some_file" + - [cat, some_file] + + manage-resolv-conf: true + resolv_conf: + nameservers: + - '8.8.8.8' + - '114.114.114.114' + + ssh_authorized_keys: + - ssh_key_1 + - ssh_key_2 + +Upstart Job +~~~~~~~~~~~ + +用处:设置开机启动项,将文件放入 /etc/init/ 目录下,生成的文件,名为 +[filename].conf,对于这个例子,文件名即为myjob.cfg.conf + +**配置文件**\ :myjob.cfg + +content-types:text/upstart-job + +.. code:: shell + + #upstart-job + #!/bin/sh + echo "Hello World. The time is now $(date -R)!" | tee /root/output.txt + +混合模式 +~~~~~~~~ + +配置好所需配置文件内容后。这里一个辅助脚本来将三种格式的配置合并。 + +**辅助脚本**\ :generate_mime_messages.py + +.. code:: python + + #!/usr/bin/python + + import sys + + from email.mime.multipart import MIMEMultipart + from email.mime.text import MIMEText + + if len(sys.argv) == 1: + print("%s input-file:type ..." % (sys.argv[0])) + sys.exit(1) + + combined_message = MIMEMultipart() + for i in sys.argv[1:]: + (filename, format_type) = i.split(":", 1) + with open(filename) as fh: + contents = fh.read() + sub_message = MIMEText(contents, format_type, sys.getdefaultencoding()) + sub_message.add_header('Content-Disposition', 'attachment; filename="%s"' % (filename)) + combined_message.attach(sub_message) + + print(combined_message) + +执行如下命令 + +.. code:: shell + + python generate_mime_messages.py \ + myscript.sh:text/x-shellscript \ + myconfig.cfg:text/cloud-config \ + myjob.cfg:text/upstart-job + +命令说明: + +- myscript.sh、myconfig.cfg、myjob.cfg 是配置文件 + +- text/x-shellscript、text/cloud-config、text/upstart-job + 是对应的内容content-types + +执行完后就会生成一段内容,再将这段内容进行 base64 编码处理,就是userdata +的参数。 + -------------- .. figure:: http://image.python-online.cn/20190511161447.png diff --git a/source/c08/c08_07.rst b/source/c08/c08_07.rst index cb0026a..807b031 100644 --- a/source/c08/c08_07.rst +++ b/source/c08/c08_07.rst @@ -64,12 +64,47 @@ id。 openstack image set IMG-UUID --property img_hide_hypervisor_id=true +由于我们当前使用的是 Newton 版本,所以需参考 P +版的代码进行相应的改动,在生成虚拟机的xml的时候,在feature标签内添加如下一段内容 + .. code:: xml +具体 P版的代码如何实现,主要函数是这一行 + +|image7| + +8.7.2 使用说明 +-------------- + +在环境部署好,gpu +资源也充足的情况下,想要创建一台gpu直通的虚拟机,还需要确保以下两个步骤完成。 + +8.7.2.1 模板配置 +~~~~~~~~~~~~~~~~ + +GPU 作为一种硬件资源,同样需要在模板中配置,配置方式是在 flavor +中添加Extra_spec + +- “pci_passthrough:alias”=“M60:1” : M60是上面控制节点中,在nova.conf + 中配置的 pci_alias 别名。1 表示,需要一个gpu设备。 +- “pci_passthrough:alias”=“P4:2” : P4是上面控制节点中,在nova.conf + 中配置的 pci_alias 别名。2 表示,需要两个gpu设备。 + +:: + + `nova flavor-key ``set` `pci_passthrough:``alias``=``'nvidia:1'` + +8.7.2.2 镜像配置 +~~~~~~~~~~~~~~~~ + +:: + + `openstack image ``set` ` --property img_hide_hypervisor_id=``true` + -------------- .. figure:: http://image.python-online.cn/20190511161447.png @@ -83,4 +118,5 @@ id。 .. |image4| image:: http://image.python-online.cn/20190422201117.png .. |image5| image:: http://image.python-online.cn/20190422205222.png .. |image6| image:: http://image.python-online.cn/20190422204755.png +.. |image7| image:: http://image.python-online.cn/20190514205605.png diff --git a/source/c08/c08_08.rst b/source/c08/c08_08.rst new file mode 100644 index 0000000..60ff8dd --- /dev/null +++ b/source/c08/c08_08.rst @@ -0,0 +1,255 @@ +8.8 OpenStack 如何使用DHCP? +============================ + +8.8.1 为何要使用dhcp? +---------------------- + +在创建虚拟机时,虚拟机的ip获取一般有两种方式: + +1. 静态IP:通过 ConfigDrive 注入虚拟机,由 Cloud-Init 负责配置。 +2. 动态IP:使用DHCP方式动态获取,由 NetworkManager 负责配置。 + +相比静态 ip ,动态IP更有利于管理。比如需要对已分配的虚拟机的ip进行更换。 + +若是使用 静态ip(写在配置文件里),更换 ip +需要在平台层面将虚拟机的旧ip改成新ip(通过挂卸网卡实现),手动登陆到虚拟机内部修改配置,并重启网络。 + +若是使用 +动态ip(通过DHCP),可以做到修改IP地址无需重启虚拟机,动态生效。 + +8.8.2 集群网络拓扑结构 +---------------------- + +如下图所示 + +|image0| + +红色部分是虚拟机内部需要增加的网卡设备。 + +br-int +:为openvswitch-agent自动创建的ovs网桥,其主要的作用是供neutron-dhcp-agent挂设备的。 + +dchp-ovs:连接物理网卡与br-int之间的ovs桥,作用是打通宿主机与虚拟机内部的二层网络 + +tap : veth pair的tap设备端,主要作用是连接Linux namespace 下的dhcp +server网卡设备与ovs网桥。一个network一个tap设备。 + +8.8.3 集群环境部署支持 +---------------------- + +8.8.3.1 网卡与网络 +~~~~~~~~~~~~~~~~~~ + +在控制节点上新增一个网卡,通过xml动态增加。 + +.. code:: xml + + + + + + + + + + +到tmp.xml所在目录里执行命令 + +.. code:: shell + + virsh attach-device ws_controller01 ./tmp.xml --persistent --live + +网卡配置文件:/etc/sysconfig/network-scripts/ifcfg-eth2 + +:: + + DEVICE=eth2 + TYPE=OVSPort + ONBOOT=yes + BOOTPROTO=none + OVS_BRIDGE=dchp-ovs + DEVICETYPE=ovs + +/etc/sysconfig/network-scripts/ifcfg-dhcp-ovs + +:: + + BOOTPROTO=static + DEVICE=dhcp-ovs + ONBOOT=yes + TYPE=OVSBridge + +重启网络 + +8.8.3.2 依赖包与配置 +~~~~~~~~~~~~~~~~~~~~ + +确保控制节点都安装了 ``neutron-openvswitch-agent`` 和 +``neutron-dhcp-agent`` 等几个包 + +|image1| + +确保如下几个配置无误 + +1、\ **/etc/neutron/dhcp_agent.ini** + +:: + + [DEFAULT] + ovs_integration_bridge = br-int + ovs_use_veth = True + interface_driver = openvswitch + + [OVS] + ovsdb_interface=vsctl + +2、\ **/etc/neutron/plugins/ml2/openvswitch_agent.ini** + +:: + + [ovs] + ovsdb_interface = vsctl + bridge_mappings = phynet0:dhcp-ovs + +启动服务 + +.. code:: shell + + systemctl enable neutron-dhcp-agent + systemctl enable neutron-openvswitch-agent + systemctl restart neutron-dhcp-agent + systemctl restart neutron-openvswitch-agent + +通过 ovs-vsctl show 检查环境是否正常 + +|image2| + +8.8.3.3 镜像支持 +~~~~~~~~~~~~~~~~ + +使用 dhcp 创建虚拟机时,cloudinit 检查到是 dhcp +模式,会跳过网卡配置文件的配置,而后启动的 NetworkManager (centos 是 +NetworkManager, ubuntu 是network-manager)才负责获取ip。 + +所以为了支持DHCP,需要在镜像内部安装 NetworkManager +,并设置开机自启,关闭 network 服务并取消开机自启,以免冲突。 + +8.8.4 重要:场景验证 +-------------------- + +1. 启用DHCP后,dhcp server 会占用一个allocation_pools中的ip。 +2. 子网启用了dhcp,ConfigDrive不会携带静态的ip地址(类型为ipv4,左图),而是携带了将类型变更为ipv4_dhcp(右图),虚拟机启动时发送广播消息DHCP + discover从dhcp server获得真正的ip地址。 |image3| +3. 若子网启用了dhcp,并且创建了虚拟机,此时 disable dhcp,只是表象上 + dhcp 被关了,而实际 dhcp port 被是占用着,因为 dhcp server + 还要给之前创建的虚拟机提供服务。如果再用这个子网创建的虚拟机会使用dhcp + ip而不是使用 static + ip,而当有删除(新增)其他子网的动作时,这个dhcp-port + 又会被删除。如果一个子网开启了dhcp,并且这个子网下\ **没有虚拟机**\ ,更新子网 + disable + dhcp,dhcp-port\ **会立即删除**\ 。再用这个子网创建的虚拟机会使用static + ip。如果一个子网开启了dhcp,并且这个子网下\ **有虚拟机**\ ,更新子网 + disable + dhcp,dhcp-port\ **不会立即删除**\ 。再用这个子网创建的虚拟机会使用dhcp获取ip。 +4. dnsmasq进程是由dhcp-agent 服务管理的。如果停用 dhcp-agent + 时,dnsmasq 进程并不会关闭。如果同一个网络下有多个dnsmasq + ,不会影响正常的dhcp获取ip。 +5. 如果三个控制节点上的 dhcp-agent + 都是关闭状态,此时创建虚拟机时,ip仍然会正常分配、port会正常创建,但由于nova-compute等不到neutron的port + plugged事件,过一段时间就超时导致创建虚拟机失败。而如果在超时范围内将dhcp-agent启动起来,就可以立即创建成功。 +6. 如果一台节点上的 dhcp-agent 关闭了,neutron-server + 会等待150s(agent_down_time*2)后再重新调度,将负责这个节点上的dnsmasq进程在另一台上启动起来。 +7. 虚拟机通过dhcp获取ip,和用config drive + 注入静态ip配置的时间差不多,经验证从创建到ping通,dhcp花了22s,静态ip花了23s +8. 如果一个子网没有配置 + dns,那么用这个子网创建虚拟机,虚拟机内部会将这个子网的dhcp server + 的ip拿来做dns配在 /etc/resolv.conf + 里,而且在排在最上面,可能会导致虚拟机上不了网。 +9. 使用 dhcp 的模式,cloudinit 从 configure drive + 中知道是dhcp后就不会去刷新配置文件将static + 改为dhcp(使用的是NetworkManager自动获取ip,这在开机启动时 + NetworkManager就会先于cloudinit + 去做),所以如果这个镜像原网卡配置文件里是静态ip,那么使用这个镜像创建dhcp + 的虚拟机,就会暴露旧ip,但是这对于配置ip没有影响,NetworkManager + 配置ip的顺序是先dhcp,获取不到再从配置文件读。 +10. 如果一个子网只有dhcp + port,子网可以被删除,如果有其他port,则子网不能删除。 + +8.8.5 镜像问题排查 +------------------ + +在\ **CentOS 7.x** 中,所有的服务都是通过systemd +管理的,服务的启动依赖也可以在 Service 中声明,会开机时 systemd +做为第一个服务启动,会将所有的依赖关系都理出来,按顺序去启动。 + +比如 local 阶段作为第一个执行的。 + +|image4| + +而后的几个服务(如init阶段),都需要保证 后于 local 已经启动过才能运行。 + +|image5| + +在 **CentOS 6.x** 中,由于系统过于古老并没有 systemd +,它的服务启动顺序是由 ``/etc/rc.d/rc3.d`` +目录进行管理的,按照编号进行从小到大执行。 + +|image6| + +这个启动顺序相当重要,在 CentOS6.x 上会因此出现问题 + +假如我们要创建 一个CentOS 6.5 的虚拟机,而我们希望这个虚拟机的 ip +是静态ip(写在配置文件里),当你在虚拟机里使用的是NetworkManager(而非 +network)时,会发现 ip 并不会自动配置上。而如果创建的是DHCP ip +的虚拟机时,就能正常配置ip。 + +在解释这个问题之前,先要理解 cloudinit 在local +阶段做了啥事。通过阅读源代码可知,在local阶段它会从 datasource +里读取网络配置信息,如果发现使用的是静态ip,cloudinit就会将网络信息(ip,dns,gateway等) +写入ifcfg配置文件。而如果发现使用的是 DHCP,cloudinit +并不会创建刷新网卡配置文件,配置ip的工作就交由 NetworkManager +会去自动获取。 + +从以信息可知,如果创建静态ip的虚拟机,NetworkManager 这个服务必须在 +cloudinit-local 之后启动才可正常从配置文件中读取 ip +并配置。而当你在镜像里安装 +NetworkManager后,默认情况下它的启动顺序是会在 cloudinit-local 之前的。 + +.. figure:: http://image.python-online.cn/20190430211900.png + :alt: img + + img + +**解决方法**\ 也很简单,将 ``S23NetworkManager`` 重命名为 +``S54NetworkManager`` 即可。 + +8.8.6 源码解读 +-------------- + +发生dhcp-agent +DOWN后会触发重新调度将dnsmasq迁到另一台,对应函数:reschedule_resources_from_down_agents,这个函数里默认会等待150s:wait_down_agents + +**dhcp-port 是如何被创建出来的?** + +从 +neutron:raw-latex:`\neutron`-0.0.1.dev2:raw-latex:`\neutron`:raw-latex:`\agent`:raw-latex:`\linux`:raw-latex:`\dhcp`.py: +setup() 开始 + +再进入 +setup_dhcp_port(),从这个函数里可以知道,dhcp-port的创建顺序:existint_dhcp-port +-> reserved-dhcp-port -> setup_dhcp_port() + +-------------- + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + + +.. |image0| image:: http://image.python-online.cn/20190514202013.png +.. |image1| image:: http://image.python-online.cn/20190514202442.png +.. |image2| image:: http://image.python-online.cn/20190514202736.png +.. |image3| image:: http://image.python-online.cn/20190514203612.png +.. |image4| image:: http://image.python-online.cn/20190430204707.png +.. |image5| image:: http://image.python-online.cn/20190430204933.png +.. |image6| image:: http://image.python-online.cn/20190430205449.png + From 923d1f0b9cb61b24fdd28b1797854ed1d5405f91 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Wed, 22 May 2019 12:55:55 +0800 Subject: [PATCH 012/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=8F=8F=E8=BF=B0?= =?UTF-8?q?=E7=AC=A6=E5=92=8C=E8=A3=85=E9=A5=B0=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_17.md | 218 +++++++++++++++++++++++++++++++++++- source/c01/c01_17.rst | 252 +++++++++++++++++++++++++++++++++++++++++- source/c03/c03_01.md | 76 ++++++++++++- source/c03/c03_01.rst | 84 +++++++++++++- 4 files changed, 612 insertions(+), 18 deletions(-) diff --git a/source/c01/c01_17.md b/source/c01/c01_17.md index 79595b8..9839912 100644 --- a/source/c01/c01_17.md +++ b/source/c01/c01_17.md @@ -191,11 +191,11 @@ class Student: 描述符分两种: - 数据描述符:实现了`__get__` 和 `__set__` 两种方法的描述符 -- 非数据描述符:只实现了`_get__` 一种方法的描述符 +- 非数据描述符:只实现了`__get__` 一种方法的描述符 你一定会问,他们有什么区别呢?网上的讲解,我看过几个,很多都把一个简单的东西讲得复杂了。 -其实就一句话,**数据描述器和非数据描述器的区别在于:相对于实例的字典的优先级**。 +其实就一句话,**数据描述器和非数据描述器的区别在于:它们相对于实例的字典的优先级不同**。 如果实例字典中有与描述器同名的属性,如果描述器是数据描述器,优先使用数据描述器,如果是非数据描述器,优先使用字典中的属性。 @@ -256,7 +256,217 @@ class Student: 99 ``` -## 所有实例共享描述符 +讲完了数据描述符和非数据描述符,我们还需要了解的对象属性的查找规律。 + +当我们对一个实例属性进行访问时,Python 会按 `obj.__dict__` → `type(obj).__dict__` → `type(obj)的父类.__dict__` 顺序进行查找,如果查找到目标属性并发现是一个描述符,Python 会调用描述符协议来改变默认的控制行为。 + +## 1.17.3 基于描述符如何实现property + +经过上面的讲解,我们已经知道如何定义描述符,且明白了描述符是如何工作的。 + +正常人所见过的描述符的用法就是上篇文章提到的那些,我想说的是那只是描述符协议最常见的应用之一,或许你还不知道,其实有很多 Python 的特性的底层实现机制都是基于 `描述符协议` 的,比如我们熟悉的`@property` 、`@classmethod` 、`@staticmethod` 和 `super` 等。 + +先来说说 `property` 吧。 + +有了第一篇的基础,我们知道了 property 的基本用法。这里我直接切入主题,从第一篇的例子里精简了一下。 + +```python +class Student: + def __init__(self, name): + self.name = name + + @property + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") +``` + +不防再简单回顾一下它的用法,通过property装饰的函数,如例子中的 math 会变成 Student 实例的属性。而对 math 属性赋值会进入 使用 `math.setter` 装饰函数的逻辑代码块。 + +为什么说 property 底层是基于描述符协议的呢?通过 PyCharm 点击进入 property 的源码,很可惜,只是一份类似文档一样的伪源码,并没有其具体的实现逻辑。 + +不过,从这份伪源码的魔法函数结构组成,可以大体知道其实现逻辑。 + +这里我自己通过模仿其函数结构,结合「描述符协议」来自己实现类 `property` 特性。 + +代码如下: + +```python +class TestProperty(object): + + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + self.fget = fget + self.fset = fset + self.fdel = fdel + self.__doc__ = doc + + def __get__(self, obj, objtype=None): + print("in __get__") + if obj is None: + return self + if self.fget is None: + raise AttributeError + return self.fget(obj) + + def __set__(self, obj, value): + print("in __set__") + if self.fset is None: + raise AttributeError + self.fset(obj, value) + + def __delete__(self, obj): + print("in __delete__") + if self.fdel is None: + raise AttributeError + self.fdel(obj) + + + def getter(self, fget): + print("in getter") + return type(self)(fget, self.fset, self.fdel, self.__doc__) + + def setter(self, fset): + print("in setter") + return type(self)(self.fget, fset, self.fdel, self.__doc__) + + def deleter(self, fdel): + print("in deleter") + return type(self)(self.fget, self.fset, fdel, self.__doc__) +``` + +然后 Student 类,我们也相应改成如下 + +```python +class Student: + def __init__(self, name): + self.name = name + + # 其实只有这里改变 + @TestProperty + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") +``` + +为了尽量让你少产生一点疑惑,我这里做两点说明: + +1. 使用`TestProperty`装饰后,`math` 不再是一个函数,而是`TestProperty` 类的一个实例。所以第二个math函数可以使用 `math.setter` 来装饰,本质是调用`TestProperty.setter` 来产生一个新的 `TestProperty` 实例赋值给第二个`math`。 + +2. 第一个 `math` 和第二个 `math` 是两个不同 `TestProperty` 实例。但他们都属于同一个描述符类(TestProperty),当对 math 对于赋值时,就会进入 `TestProperty.__set__`,当对math 进行取值里,就会进入 `TestProperty.__get__`。仔细一看,其实最终访问的还是Student实例的 `_math` 属性。 + +说了这么多,还是运行一下,更加直观一点。 + +```python +# 运行后,会直接打印这一行,这是在实例化 TestProperty 并赋值给第二个math +in setter +>>> +>>> s1.math = 90 +in __set__ +>>> s1.math +in __get__ +90 +``` + +对于以上理解 `property` 的运行原理有困难的同学,请务必参照我上面写的两点说明。如有其他疑问,可以加微信与我进行探讨。 + +## 1.17.4 基于描述符如何实现staticmethod + +说完了 `property` ,这里再来讲讲 `@classmethod` 和 `@staticmethod` 的实现原理。 + +我这里定义了一个类,用了两种方式来实现静态方法。 + +```python +class Test: + @staticmethod + def myfunc(): + print("hello") + +# 上下两种写法等价 + +class Test: + def myfunc(): + print("hello") + # 重点:这就是描述符的体现 + myfunc = staticmethod(myfunc) +``` + +这两种写法是等价的,就好像在 `property` 一样,其实以下两种写法也是等价的。 + +```python +@TestProperty +def math(self): + return self._math + +math = TestProperty(fget=math) +``` + +话题还是转回到 `staticmethod` 这边来吧。 + +由上面的注释,可以看出 `staticmethod` 其实就相当于一个描述符类,而`myfunc` 在此刻变成了一个描述符。关于 `staticmethod` 的实现,你可以参照下面这段我自己写的代码,加以理解。 + +![](http://image.python-online.cn/20190519001930.png) + +调用这个方法可以知道,每调用一次,它都会经过描述符类的 `__get__` 。 + +```python +>>> Test.myfunc() +in staticmethod __get__ +hello +>>> Test().myfunc() +in staticmethod __get__ +hello +``` + +## 1.17.4 基于描述符如何实现classmethod + +同样的 ` classmethod` 也是一样。 + +```python +class classmethod(object): + def __init__(self, f): + self.f = f + + def __get__(self, instance, owner=None): + print("in classmethod __get__") + + def newfunc(*args): + return self.f(owner, *args) + return newfunc + +class Test: + def myfunc(cls): + print("hello") + + # 重点:这就是描述符的体现 + myfunc = classmethod(myfunc) +``` + +验证结果如下 + +```python +>>> Test.myfunc() +in classmethod __get__ +hello +>>> Test().myfunc() +in classmethod __get__ +hello +``` + +讲完了 `property`、`staticmethod`和`classmethod` 与 描述符的关系。我想你应该对描述符在 Python 中的应用有了更深的理解。对于 super 的实现原理,就交由你来自己完成。 + +## 1.17.5 所有实例共享描述符 若要合理使用描述符,利用描述符给我们带来的编码上的便利。有一个坑,需要注意,比如下面这个Student我们没有定义构造函数 @@ -313,6 +523,8 @@ https://blog.csdn.net/BF02jgtRS00XKtCx/article/details/82882622 https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html +https://zhuanlan.zhihu.com/p/42485483 + --- diff --git a/source/c01/c01_17.rst b/source/c01/c01_17.rst index c16c07e..5534e39 100644 --- a/source/c01/c01_17.rst +++ b/source/c01/c01_17.rst @@ -214,11 +214,11 @@ math、chinese、english这三个属性的时候,都会经过 Score 描述符分两种: - 数据描述符:实现了\ ``__get__`` 和 ``__set__`` 两种方法的描述符 -- 非数据描述符:只实现了\ ``_get__`` 一种方法的描述符 +- 非数据描述符:只实现了\ ``__get__`` 一种方法的描述符 你一定会问,他们有什么区别呢?网上的讲解,我看过几个,很多都把一个简单的东西讲得复杂了。 -其实就一句话,\ **数据描述器和非数据描述器的区别在于:相对于实例的字典的优先级**\ 。 +其实就一句话,\ **数据描述器和非数据描述器的区别在于:它们相对于实例的字典的优先级不同**\ 。 如果实例字典中有与描述器同名的属性,如果描述器是数据描述器,优先使用数据描述器,如果是非数据描述器,优先使用字典中的属性。 @@ -280,8 +280,249 @@ math、chinese、english这三个属性的时候,都会经过 Score 调用 __getattribute__ 99 -所有实例共享描述符 ------------------- +讲完了数据描述符和非数据描述符,我们还需要了解的对象属性的查找规律。 + +当我们对一个实例属性进行访问时,Python 会按 ``obj.__dict__`` → +``type(obj).__dict__`` → ``type(obj)的父类.__dict__`` +顺序进行查找,如果查找到目标属性并发现是一个描述符,Python +会调用描述符协议来改变默认的控制行为。 + +1.17.3 基于描述符如何实现property +--------------------------------- + +经过上面的讲解,我们已经知道如何定义描述符,且明白了描述符是如何工作的。 + +正常人所见过的描述符的用法就是上篇文章提到的那些,我想说的是那只是描述符协议最常见的应用之一,或许你还不知道,其实有很多 +Python 的特性的底层实现机制都是基于 ``描述符协议`` +的,比如我们熟悉的\ ``@property`` 、\ ``@classmethod`` +、\ ``@staticmethod`` 和 ``super`` 等。 + +先来说说 ``property`` 吧。 + +有了第一篇的基础,我们知道了 property +的基本用法。这里我直接切入主题,从第一篇的例子里精简了一下。 + +.. code:: python + + class Student: + def __init__(self, name): + self.name = name + + @property + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") + +不防再简单回顾一下它的用法,通过property装饰的函数,如例子中的 math +会变成 Student 实例的属性。而对 math 属性赋值会进入 使用 ``math.setter`` +装饰函数的逻辑代码块。 + +为什么说 property 底层是基于描述符协议的呢?通过 PyCharm 点击进入 +property +的源码,很可惜,只是一份类似文档一样的伪源码,并没有其具体的实现逻辑。 + +不过,从这份伪源码的魔法函数结构组成,可以大体知道其实现逻辑。 + +这里我自己通过模仿其函数结构,结合「描述符协议」来自己实现类 +``property`` 特性。 + +代码如下: + +.. code:: python + + class TestProperty(object): + + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + self.fget = fget + self.fset = fset + self.fdel = fdel + self.__doc__ = doc + + def __get__(self, obj, objtype=None): + print("in __get__") + if obj is None: + return self + if self.fget is None: + raise AttributeError + return self.fget(obj) + + def __set__(self, obj, value): + print("in __set__") + if self.fset is None: + raise AttributeError + self.fset(obj, value) + + def __delete__(self, obj): + print("in __delete__") + if self.fdel is None: + raise AttributeError + self.fdel(obj) + + + def getter(self, fget): + print("in getter") + return type(self)(fget, self.fset, self.fdel, self.__doc__) + + def setter(self, fset): + print("in setter") + return type(self)(self.fget, fset, self.fdel, self.__doc__) + + def deleter(self, fdel): + print("in deleter") + return type(self)(self.fget, self.fset, fdel, self.__doc__) + +然后 Student 类,我们也相应改成如下 + +.. code:: python + + class Student: + def __init__(self, name): + self.name = name + + # 其实只有这里改变 + @TestProperty + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") + +为了尽量让你少产生一点疑惑,我这里做两点说明: + +1. 使用\ ``TestProperty``\ 装饰后,\ ``math`` + 不再是一个函数,而是\ ``TestProperty`` + 类的一个实例。所以第二个math函数可以使用 ``math.setter`` + 来装饰,本质是调用\ ``TestProperty.setter`` 来产生一个新的 + ``TestProperty`` 实例赋值给第二个\ ``math``\ 。 + +2. 第一个 ``math`` 和第二个 ``math`` 是两个不同 ``TestProperty`` + 实例。但他们都属于同一个描述符类(TestProperty),当对 math + 对于赋值时,就会进入 ``TestProperty.__set__``\ ,当对math + 进行取值里,就会进入 + ``TestProperty.__get__``\ 。仔细一看,其实最终访问的还是Student实例的 + ``_math`` 属性。 + +说了这么多,还是运行一下,更加直观一点。 + +.. code:: python + + # 运行后,会直接打印这一行,这是在实例化 TestProperty 并赋值给第二个math + in setter + >>> + >>> s1.math = 90 + in __set__ + >>> s1.math + in __get__ + 90 + +对于以上理解 ``property`` +的运行原理有困难的同学,请务必参照我上面写的两点说明。如有其他疑问,可以加微信与我进行探讨。 + +1.17.4 基于描述符如何实现staticmethod +------------------------------------- + +说完了 ``property`` ,这里再来讲讲 ``@classmethod`` 和 ``@staticmethod`` +的实现原理。 + +我这里定义了一个类,用了两种方式来实现静态方法。 + +.. code:: python + + class Test: + @staticmethod + def myfunc(): + print("hello") + + # 上下两种写法等价 + + class Test: + def myfunc(): + print("hello") + # 重点:这就是描述符的体现 + myfunc = staticmethod(myfunc) + +这两种写法是等价的,就好像在 ``property`` +一样,其实以下两种写法也是等价的。 + +.. code:: python + + @TestProperty + def math(self): + return self._math + + math = TestProperty(fget=math) + +话题还是转回到 ``staticmethod`` 这边来吧。 + +由上面的注释,可以看出 ``staticmethod`` +其实就相当于一个描述符类,而\ ``myfunc`` 在此刻变成了一个描述符。关于 +``staticmethod`` 的实现,你可以参照下面这段我自己写的代码,加以理解。 + +|image3| + +调用这个方法可以知道,每调用一次,它都会经过描述符类的 ``__get__`` 。 + +.. code:: python + + >>> Test.myfunc() + in staticmethod __get__ + hello + >>> Test().myfunc() + in staticmethod __get__ + hello + +1.17.4 基于描述符如何实现classmethod +------------------------------------ + +同样的 ``classmethod`` 也是一样。 + +.. code:: python + + class classmethod(object): + def __init__(self, f): + self.f = f + + def __get__(self, instance, owner=None): + print("in classmethod __get__") + + def newfunc(*args): + return self.f(owner, *args) + return newfunc + + class Test: + def myfunc(cls): + print("hello") + + # 重点:这就是描述符的体现 + myfunc = classmethod(myfunc) + +验证结果如下 + +.. code:: python + + >>> Test.myfunc() + in classmethod __get__ + hello + >>> Test().myfunc() + in classmethod __get__ + hello + +讲完了 ``property``\ 、\ ``staticmethod``\ 和\ ``classmethod`` 与 +描述符的关系。我想你应该对描述符在 Python 中的应用有了更深的理解。对于 +super 的实现原理,就交由你来自己完成。 + +1.17.5 所有实例共享描述符 +------------------------- 若要合理使用描述符,利用描述符给我们带来的编码上的便利。有一个坑,需要注意,比如下面这个Student我们没有定义构造函数 @@ -338,6 +579,8 @@ https://blog.csdn.net/BF02jgtRS00XKtCx/article/details/82882622 https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html +https://zhuanlan.zhihu.com/p/42485483 + -------------- .. figure:: http://image.python-online.cn/20190511161447.png @@ -347,4 +590,5 @@ https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html .. |image0| image:: http://image.python-online.cn/20190425221322.png .. |image1| image:: http://image.python-online.cn/20190425221322.png .. |image2| image:: http://image.python-online.cn/20190425221233.png +.. |image3| image:: http://image.python-online.cn/20190519001930.png diff --git a/source/c03/c03_01.md b/source/c03/c03_01.md index 91af06b..2421ac3 100644 --- a/source/c03/c03_01.md +++ b/source/c03/c03_01.md @@ -6,13 +6,13 @@ 如果你接触 Python 有一段时间了的话,想必你对 `@` 符号一定不陌生了,没错 `@` 符号就是装饰器的语法糖。 -它放在函数开始定义的地方,它就像一顶帽子一样戴在函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为`装饰函数` 或 `装饰器`。 +它放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为`装饰函数` 或 `装饰器`。 -你要问我装饰器可以实现什么功能?这个真的是无解,小明只能说你的脑洞有多大,装饰器就有多强大。 +你要问我装饰器可以实现什么功能?我只能说你的脑洞有多大,装饰器就有多强大。 装饰器的使用方法很固定: -- 先定义一个装饰函数(帽子) -- 再定义你的业务函数(人) +- 先定义一个装饰函数(帽子)(也可以用类、偏函数实现) +- 再定义你的业务函数、或者类(人) - 最后把这顶帽子带在这个人头上 装饰器的简单的用法有很多,这里举两个常见的。 @@ -160,7 +160,7 @@ I am from America ``` emmmm,这很NB。。。 -## 3.1.5高阶用法:不带参数的类装饰器 +## 3.1.5 高阶用法:不带参数的类装饰器 以上都是基于函数实现的装饰器,在阅读别人代码时,还可以时常发现还有基于类实现的装饰器。 @@ -225,6 +225,72 @@ say("hello") say hello! ``` +## 3.1.7 使用偏函数与类实现装饰器 + +绝大多数装饰器都是基于函数和闭包实现的,但这并非制造装饰器的唯一方式。 + +事实上,Python 对某个对象是否能通过装饰器( `@decorator`)形式使用只有一个要求:**decorator 必须是一个“可被调用(callable)的对象**。 + +对于这个 callable 对象,我们最熟悉的就是函数了。 + +除函数之外,类也可以是 callable 对象,只要实现了`__call__` 函数(上面几个盒子已经接触过了),还有比较少人使用的偏函数也是 callable 对象。 + +接下来就来说说,如何使用 类和偏函数结合实现一个与众不同的装饰器。 + +如下所示,DelayFunc 是一个实现了 `__call__` 的类,delay 返回一个偏函数,在这里 delay 就可以做为一个装饰器。(以下代码摘自 Python工匠:使用装饰器的小技巧) + +```python +import time +import functools + +class DelayFunc: + def __init__(self, duration, func): + self.duration = duration + self.func = func + + def __call__(self, *args, **kwargs): + print(f'Wait for {self.duration} seconds...') + time.sleep(self.duration) + return self.func(*args, **kwargs) + + def eager_call(self, *args, **kwargs): + print('Call without delay') + return self.func(*args, **kwargs) + +def delay(duration): + """ + 装饰器:推迟某个函数的执行。 + 同时提供 .eager_call 方法立即执行 + """ + # 此处为了避免定义额外函数, + # 直接使用 functools.partial 帮助构造 DelayFunc 实例 + return functools.partial(DelayFunc, duration) +``` + +我们的业务函数很简单,就是相加 + +```python +@delay(duration=2) +def add(a, b): + return a+b +``` + +来看一下执行过程 + +```python +>>> add # 可见 add 变成了 Delay 的实例 +<__main__.DelayFunc object at 0x107bd0be0> +>>> +>>> add(3,5) # 直接调用实例,进入 __call__ +Wait for 2 seconds... +8 +>>> +>>> add.func # 实现实例方法 + +``` + + + ## 3.1.7 wrapper 装饰器有啥用? 在 functools 标准库中有提供一个 wrapper 装饰器,你应该也经常见过,那他有啥用呢? diff --git a/source/c03/c03_01.rst b/source/c03/c03_01.rst index 2ba08a6..9ae2620 100755 --- a/source/c03/c03_01.rst +++ b/source/c03/c03_01.rst @@ -9,13 +9,14 @@ 如果你接触 Python 有一段时间了的话,想必你对 ``@`` 符号一定不陌生了,没错 ``@`` 符号就是装饰器的语法糖。 -它放在函数开始定义的地方,它就像一顶帽子一样戴在函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为\ ``装饰函数`` +它放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为\ ``装饰函数`` 或 ``装饰器``\ 。 -你要问我装饰器可以实现什么功能?这个真的是无解,小明只能说你的脑洞有多大,装饰器就有多强大。 +你要问我装饰器可以实现什么功能?我只能说你的脑洞有多大,装饰器就有多强大。 -装饰器的使用方法很固定: - 先定义一个装饰函数(帽子) - -再定义你的业务函数(人) - 最后把这顶帽子带在这个人头上 +装饰器的使用方法很固定: - +先定义一个装饰函数(帽子)(也可以用类、偏函数实现) - +再定义你的业务函数、或者类(人) - 最后把这顶帽子带在这个人头上 装饰器的简单的用法有很多,这里举两个常见的。 - 日志打印器 - 时间计时器 @@ -180,8 +181,8 @@ emmmm,这很NB。。。 -3.1.5高阶用法:不带参数的类装饰器 ---------------------------------- +3.1.5 高阶用法:不带参数的类装饰器 +---------------------------------- 以上都是基于函数实现的装饰器,在阅读别人代码时,还可以时常发现还有基于类实现的装饰器。 @@ -251,6 +252,77 @@ emmmm,这很NB。。。 [WARNING]: the function say() is running... say hello! +3.1.7 使用偏函数与类实现装饰器 +------------------------------ + +绝大多数装饰器都是基于函数和闭包实现的,但这并非制造装饰器的唯一方式。 + +事实上,Python 对某个对象是否能通过装饰器( +``@decorator``\ )形式使用只有一个要求:\ **decorator +必须是一个“可被调用(callable)的对象**\ 。 + +对于这个 callable 对象,我们最熟悉的就是函数了。 + +除函数之外,类也可以是 callable 对象,只要实现了\ ``__call__`` +函数(上面几个盒子已经接触过了),还有比较少人使用的偏函数也是 callable +对象。 + +接下来就来说说,如何使用 类和偏函数结合实现一个与众不同的装饰器。 + +如下所示,DelayFunc 是一个实现了 ``__call__`` 的类,delay +返回一个偏函数,在这里 delay 就可以做为一个装饰器。(以下代码摘自 +Python工匠:使用装饰器的小技巧) + +.. code:: python + + import time + import functools + + class DelayFunc: + def __init__(self, duration, func): + self.duration = duration + self.func = func + + def __call__(self, *args, **kwargs): + print(f'Wait for {self.duration} seconds...') + time.sleep(self.duration) + return self.func(*args, **kwargs) + + def eager_call(self, *args, **kwargs): + print('Call without delay') + return self.func(*args, **kwargs) + + def delay(duration): + """ + 装饰器:推迟某个函数的执行。 + 同时提供 .eager_call 方法立即执行 + """ + # 此处为了避免定义额外函数, + # 直接使用 functools.partial 帮助构造 DelayFunc 实例 + return functools.partial(DelayFunc, duration) + +我们的业务函数很简单,就是相加 + +.. code:: python + + @delay(duration=2) + def add(a, b): + return a+b + +来看一下执行过程 + +.. code:: python + + >>> add # 可见 add 变成了 Delay 的实例 + <__main__.DelayFunc object at 0x107bd0be0> + >>> + >>> add(3,5) # 直接调用实例,进入 __call__ + Wait for 2 seconds... + 8 + >>> + >>> add.func # 实现实例方法 + + 3.1.7 wrapper 装饰器有啥用? ---------------------------- From 9a5c3478b2825f08d9ce164f7f44a3cab90ab14c Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 26 May 2019 13:53:49 +0800 Subject: [PATCH 013/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/c07_01.md | 8 ++++++++ source/c08/c08_01.md | 7 +++++++ source/c08/c08_05.md | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/source/c07/c07_01.md b/source/c07/c07_01.md index 7c83bec..3dd0844 100644 --- a/source/c07/c07_01.md +++ b/source/c07/c07_01.md @@ -695,6 +695,14 @@ lsb_release -a # 查看CPU信息 $ cat /proc/cpuinfo $ numactk -H + + +# ubuntu 查看更新有哪些是安全更新 +$ apt-get -s --no-download dist-upgrade -V | grep "^Inst.*security.*$" | cut -d " " -f 2 + +# ubuntu 登陆时,显示的安全更新是如何来的,内容:/etc/update-motd.d/90-updates-available +/usr/lib/update-notifier/apt-check --human-readable + ``` diff --git a/source/c08/c08_01.md b/source/c08/c08_01.md index a16a924..8960a1b 100644 --- a/source/c08/c08_01.md +++ b/source/c08/c08_01.md @@ -60,6 +60,9 @@ $ nova secgroup-list $ nova secgroup-list-rules $ 增加安全组规则(开放ssh) $ nova secgroup-add-rule default tcp <22> <22> <0.0.0.0/0> + +# 设置metadata +nova meta xxxxxx set ws:evacuate_interval=10 ``` #### flavor管理 @@ -371,6 +374,10 @@ rabbitmqctl add_user openstack openstack12#$ rabbitmqctl set_permissions -p / openstack ".*" ".*" ".*" rabbitmqctl set_user_tags openstack administrator ``` +``` +# 指定节点执行命令 +rabbitmq -n rabbit@ws_controller02 [command] +``` --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index d1cd174..148ec3d 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -193,6 +193,45 @@ nova-scheduler 的调度主要由两部分组成 ![](http://image.python-online.cn/20190426152148.png) +## 8.5.9 指定ip时检查allocation_pools + +在原生的 neutron 中,当你指定 ip(172.20.22.64) 来创建虚拟机时,假如子网的 allocation_pools 是 172.20.20.100 - 172.20.20.200 ,那 neutron 是不会去检查你指定的ip是否在 allocation_pools 中的。 + +若要解决这个问题,可以修改源代码来解决。 + +我在 `neutron\neutron\db\ipam_pluggable_backend.py` 文件中添加如下检查allocation_pools 的代码。 + +![](http://image.python-online.cn/20190526134519.png) + +```python + # 代码如下:方便复制 + @staticmethod + def _is_ip_in_allocation_pools(ip_address, allocation_pools): + from neutron.ipam.exceptions import InvalidIpForAllocationPools + + for ap in allocation_pools: + ap_start_ip = netaddr.IPAddress(ap['start']) + ap_end_ip = netaddr.IPAddress(ap['end']) + if ap_start_ip <= ip_address <= ap_end_ip: + return True + raise InvalidIpForAllocationPools(ip_address='ip_address') + + def _validate_allocation_pool_for_fixed_ip(self, subnet, fixed): + ip_address = netaddr.IPAddress(fixed["ip_address"]) + allocation_pools = subnet["allocation_pools"] + return self._is_ip_in_allocation_pools(ip_address, allocation_pools) +``` + +若指定的ip在allocation pool 里,则正常创建,若不在allocation 里,就会在 nova-compute 日志中报错。 + +![](http://image.python-online.cn/20190526134543.png) + +可以发现我们的ip 172.20.22.64 并不在子网的allocation pool,理所当然在nova的日志中可以看到相应的报错。 + +![](http://image.python-online.cn/20190526134618.png) + + + --- From 95943d0487d970e2ff321002ba51a40a12829299 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 26 May 2019 14:06:47 +0800 Subject: [PATCH 014/302] =?UTF-8?q?=E5=A6=82=E4=BD=95=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E3=80=8Cattach=20port=E6=97=B6ip=E5=8D=A0=E7=94=A8=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_05.md | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 148ec3d..0012f9b 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -1,4 +1,4 @@ -# 8.5 OpenStack 源码剖析 +# 8.5 OpenStack 源码剖析与改造 ## 8.5.1 虚拟机是如何创建出来的? @@ -54,7 +54,7 @@ block_device_mapping nova 提供了一个配置项:notify_on_state_change,本意是想,如果配置`vm_state`就只在vm_state -第一次,在`manager.py:2050`的函数 `_do_build_and_run_instance`里,看instance.save()大 +第一次,在`manager.py:2050`的函数 `_do_build_and_run_instance`里,看instance.save() @@ -230,6 +230,44 @@ nova-scheduler 的调度主要由两部分组成 ![](http://image.python-online.cn/20190526134618.png) +## 8.5.10 attach port时ip占用提示 + +当你调用 `os-interface` (指定了ip)接口给一台虚拟机添加一张网卡时,若这个ip已经被使用。 + +nova-api 返回的结果令人无法理解: + +``` + [{"computeFault": {"message": "Unexpected API Error. Please report this at http://bugs.launchpad.net/nova/ and attach the Nova API log if possible.\n", "code": 500}}]. +``` + +究其原因,是 nova 在调用neutron的api 创建port时,如果ip已被占用,必须neutron会抛出 IpAddressAlreadyAllocated,而在 neutronclient 只有 IpAddressInUseClient 的异常,并不匹配,在neutronclient 端与neutron 对应的异常应该为 IpAddressAlreadyAllocatedClient 。 + +![](http://image.python-online.cn/20190526140213.png) + +如何让nova-api能够返回具体的错误信息呢? + +解决方法有两种, + +一种是,在 neutronclient/common/exceptions.py 里添加 IpAddressAlreadyAllocatedClient 异常。 + +并且在nova 创建port的代码处,捕获这个异常 + +![](http://image.python-online.cn/20190526140301.png) + +这种要改两个组件,而且要将neutronclient 的代码也管理起来,较为麻烦 + +一种是,只改neutron,在neutron/ipam/exceptions.py 添加一个与 neutronclient 相对应的异常。 + +![](http://image.python-online.cn/20190526140315.png) + +然后修改 neutron/ipam/drivers/neutrondb_ipam/drivers.py 修改异常类型 + +![](http://image.python-online.cn/20190526140336.png) + +通过 postman 进行模拟,已经可以返回具体的信息 + +![](http://image.python-online.cn/20190526140410.png) + From 54828a684a938c84bc69d1f9babaec1f516f4349 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 26 May 2019 22:23:30 +0800 Subject: [PATCH 015/302] =?UTF-8?q?openstack=E6=98=AF=E5=A6=82=E4=BD=95?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0rpc=E8=BF=9C=E7=A8=8B=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_05.md | 138 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 2 deletions(-) diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 0012f9b..16e66fc 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -4,8 +4,108 @@ +生成xml,准备网络(plug_vif)创建domain 是在 `_create_domain_and_network` 这个函数中 + +![](http://image.python-online.cn/20190526144846.png) + +这个函数,只有在 `spawn` 、`_hard_reboot` 和 `finish_migration` 才会执行。 + + + ## 8.5.2 rpc 是如何调用的? +OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul API方式,另一种则是RPC调用。两种通信方式的应用场景有所不同,在OpenStack中,前者主要用于各组件之间的通信(如nova与glance的通信),而后者则用于同一组件中各个不同模块之间的通信(如nova组件中nova-compute与nova-scheduler的通信)。 +关于OpenStack中基于RESTFul API的通信方式主要是应用了WSGI,我会在以后的博文中详细讨论。这里主要还是分享一下关于RPC调用的一些理解。 + +首先,什么是RPC呢?百度百科给出的解释是这样的:“RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议”。这个概念听起来还是比较抽象,下面我会结合OpenStack中RPC调用的具体应用来具体分析一下这个协议。 + +其次,为什么要采用RPC呢?单纯的依靠RESTFul API不可以吗?其实原因有下面这几个: + +1. 由于RESTFul API是基于HTTP协议的,因此客户端与服务端之间所能传输的消息仅限于文本 +2. RESTFul API客户端与服务端之间采用的是同步机制,当发送HTTP请求时,客户端需要等待服务端的响应。当然对于这一点是可以通过一些技术来实现异步的机制的 +3. 采用RESTFul API,客户端与服务端之间虽然可以独立开发,但还是存在耦合。比如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常工作 + +基于上面这几个原因,所以OpenStack才采用了另一种远程通信机制,这就是我们今天要讨论的鼎鼎大名的RPC。 + +今天的话题,就是源码解读OpenStack是如何通过rpc进行远程调用的。 + +如果你不想用现成的 `notification_event_types`,而想新定义一个,可以这样做 + +首先在这里先定义合法的 `notification_event_types`,相当于添加白名单。 + +![](http://image.python-online.cn/20190526172514.png) + +然后在调用处,使用 `rpc.get_notifier` 来发送消息给ceilometer。 + +![](http://image.python-online.cn/20190526172725.png) + +继续查看 `rpc.get_notifier` 做了什么事?如何实现直接info 就能发送消息的。 + +![](http://image.python-online.cn/20190526173314.png) + +当你使用的event_types 不在白名单内,或者是异常信息。就会给打印warn日志 + +![](http://image.python-online.cn/20190526175100.png) + + + +在nova.confg里有这一项配置,rabbit 指明了通信方式是rabbitmq。后面我标名了user,passwd,主机域名,端口,你应该能理解。 + +``` +transport_url=rabbit://user:passwd@ctrl.openstack.com:5672 +``` + +如果有多个主机,可以参照下面的 docstring 使用 逗号分隔进行添加。 + +![](http://image.python-online.cn/20190526182125.png) + +指明了使用 messagingv2 这个driver,其他可用的还有routing, log, test, noop + +``` +[oslo_messaging_notifications] +driver = messagingv2 +``` + +在rabbit里查看队列,notification 是 topic + +![](http://image.python-online.cn/20190526180708.png) + +而 debug ,info 等是event priority + +![](http://image.python-online.cn/20190526181433.png) + +当nova-api发送rpc消息给conducotr的时候,是如何发送过去的呢? + +这边是 nova/conducotr/rpcapi.py,是发送端。 + +target 里的 namespace 指定了要调用哪个类。 + +cctxt.call 里的 'build_instances' 指定了要调用哪个函数。 + +![](http://image.python-online.cn/20190526185217.png) + +在nova/conducotr/manager.py 里有两个类,各有一个类变量,声明了target,发送端的target需要和这边target匹配上,才会进入相应的处理逻辑。 + +![](http://image.python-online.cn/20190526184854.png) + +你一定很好奇,这个target是如何绑定到rpc server里的呢? + +首先我们要知道 rpc_server 是如何创建的? + +如果你看了 8.5.11 那节 (nova的各项服务是怎么启动?),然后再去看 nova-conductor的启动代码,你就会发现在 nova\nova\service.py:Service.start() 里有这么一段代码,是创建rpc server的,endpoints 里面的self.manager 就是ConductorManager 类 + +![](http://image.python-online.cn/20190526221219.png) + +![](http://image.python-online.cn/20190526221636.png) + +从oslo_messaging 的代码可以看出,它会先根据endpoints创建dispatcher,dispatcher就会根据类变量target对象,创建消息的分发规则。这样从client就可以成功地调用远程(服务端)的函数。 + +![](http://image.python-online.cn/20190526220809.png) + +![](http://image.python-online.cn/20190526220605.png) + +参考文章:[OpenStack之RPC调用(一)](https://blog.csdn.net/qiuhan0314/article/details/42671965) + ## 8.5.3 创建快照代码解读? @@ -197,9 +297,17 @@ nova-scheduler 的调度主要由两部分组成 在原生的 neutron 中,当你指定 ip(172.20.22.64) 来创建虚拟机时,假如子网的 allocation_pools 是 172.20.20.100 - 172.20.20.200 ,那 neutron 是不会去检查你指定的ip是否在 allocation_pools 中的。 -若要解决这个问题,可以修改源代码来解决。 +先来看看,port 是如何创建的 + +![](http://image.python-online.cn/20190526141815.png) -我在 `neutron\neutron\db\ipam_pluggable_backend.py` 文件中添加如下检查allocation_pools 的代码。 + + +若要解决这个问题,可以参考原生代码中,在为子网添加allocation_pool时,验证是否合法的的逻辑,代码如下 + +![](http://image.python-online.cn/20190526142453.png) + +然后在 `neutron\neutron\db\ipam_pluggable_backend.py` 文件中添加我们检查 ip是否在 allocation_pools 中的逻辑代码。 ![](http://image.python-online.cn/20190526134519.png) @@ -222,6 +330,10 @@ nova-scheduler 的调度主要由两部分组成 return self._is_ip_in_allocation_pools(ip_address, allocation_pools) ``` +然后还要定义一个异常类型 + +![](http://image.python-online.cn/20190526141226.png) + 若指定的ip在allocation pool 里,则正常创建,若不在allocation 里,就会在 nova-compute 日志中报错。 ![](http://image.python-online.cn/20190526134543.png) @@ -268,6 +380,28 @@ nova-api 返回的结果令人无法理解: ![](http://image.python-online.cn/20190526140410.png) +另附:neutron 是如何判断ip是否已经占用?代码如下 + +![](http://image.python-online.cn/20190526143235.png) + +## 8.5.11 nova的各项服务服务是如何启动的? + +nova 里有不少服务,比如 nova-compute,nova-api,nova-conductor,nova-scheduler 等。 + +这些服务如何都是如何启动的呢?他们其实都是用同一套代码,所以只要分析一个就行,这里以nova-compute为例来了解一下。 + +从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 `nova.cmd.compute:main()` + +![](http://image.python-online.cn/20190526205152.png) + +从这个入口进去,会开启一个 `nova-compute` 的服务。 + +![](http://image.python-online.cn/20190526165007.png) + +当调用 service.Service.create 时,实际是返回实际化 service.Service 对象。当没有传入 manager 时,就以binary 里的为准。比如binary 是` nova-compute`,那manager_cls 就是 `compute_manager`,对应的manager 导入路径,就会从配置里读取。 + +![](http://image.python-online.cn/20190526204328.png) + From da81ead71816effa7dfbb57f3e45432705be7c6b Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 26 May 2019 23:21:44 +0800 Subject: [PATCH 016/302] =?UTF-8?q?=E8=A3=85=E9=A5=B0=E5=99=A8=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_17.md | 4 +- source/c03/c03_01.md | 153 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 151 insertions(+), 6 deletions(-) diff --git a/source/c01/c01_17.md b/source/c01/c01_17.md index 9839912..b2bed38 100644 --- a/source/c01/c01_17.md +++ b/source/c01/c01_17.md @@ -517,11 +517,9 @@ class Student: ## 参考文档 -https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p09_define_decorators_as_classes.html -https://blog.csdn.net/BF02jgtRS00XKtCx/article/details/82882622 -https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html +- [Python描述器引导(翻译)](https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html#id8)[¶](https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html#python) https://zhuanlan.zhihu.com/p/42485483 diff --git a/source/c03/c03_01.md b/source/c03/c03_01.md index 2421ac3..df40a02 100644 --- a/source/c03/c03_01.md +++ b/source/c03/c03_01.md @@ -289,9 +289,42 @@ Wait for 2 seconds... ``` +## 3.18 如何写能装饰类的装饰器? +用 Python 写单例模式的时候,常用的有三种写法。其中一种,是用装饰器来实现的。 -## 3.1.7 wrapper 装饰器有啥用? +以下便是我自己写的装饰器版的单例写法。 + +```python +instances = {} + +def singleton(cls): + def get_instance(*args, **kw): + cls_name = cls.__name__ + print('===== 1 ====') + if not cls_name in instances: + print('===== 2 ====') + instance = cls(*args, **kw) + instances[cls_name] = instance + return instances[cls_name] + return get_instance + +@singleton +class User: + _instance = None + + def __init__(self, name): + print('===== 3 ====') + self.name = name +``` + +可以看到我们用singleton 这个装饰函数来装饰 User 这个类。装饰器用在类上,并不是很常见,但只要熟悉装饰器的实现过程,就不难以实现对类的装饰。在上面这个例子中,装饰器就只是实现对类实例的生成的控制而已。 + +其实例化的过程,你可以参考我这里的调试过程,加以理解。 + +![](http://image.python-online.cn/20190512113917.png) + +## 3.1.9 wrapper 装饰器有啥用? 在 functools 标准库中有提供一个 wrapper 装饰器,你应该也经常见过,那他有啥用呢? @@ -377,7 +410,7 @@ print(wrapped.__name__) -## 3.1.8 内置装饰器:property +## 3.1.10 内置装饰器:property 以上,我们介绍的都是自定义的装饰器。 @@ -484,8 +517,122 @@ del XiaoMing.age `@age.setter` 使得我们可以使用`XiaoMing.age = 25`这样的方式直接赋值。 `@age.deleter` 使得我们可以使用`del XiaoMing.age`这样的方式来删除属性。 + property 的底层实现机制是「描述符」,为此我还写过一篇文章。 + +这里也介绍一下吧,正好将这些看似零散的文章全部串起来。 + +如下,我写了一个类,里面使用了 property 将 math 变成了类实例的属性 + +```python +class Student: + def __init__(self, name): + self.name = name + + @property + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") +``` + +为什么说 property 底层是基于描述符协议的呢?通过 PyCharm 点击进入 property 的源码,很可惜,只是一份类似文档一样的伪源码,并没有其具体的实现逻辑。 + +不过,从这份伪源码的魔法函数结构组成,可以大体知道其实现逻辑。 + +这里我自己通过模仿其函数结构,结合「描述符协议」来自己实现类 `property` 特性。 + +代码如下: + +```python +class TestProperty(object): + + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + self.fget = fget + self.fset = fset + self.fdel = fdel + self.__doc__ = doc + + def __get__(self, obj, objtype=None): + print("in __get__") + if obj is None: + return self + if self.fget is None: + raise AttributeError + return self.fget(obj) + + def __set__(self, obj, value): + print("in __set__") + if self.fset is None: + raise AttributeError + self.fset(obj, value) + + def __delete__(self, obj): + print("in __delete__") + if self.fdel is None: + raise AttributeError + self.fdel(obj) + + + def getter(self, fget): + print("in getter") + return type(self)(fget, self.fset, self.fdel, self.__doc__) + + def setter(self, fset): + print("in setter") + return type(self)(self.fget, fset, self.fdel, self.__doc__) + + def deleter(self, fdel): + print("in deleter") + return type(self)(self.fget, self.fset, fdel, self.__doc__) +``` + +然后 Student 类,我们也相应改成如下 + +```python +class Student: + def __init__(self, name): + self.name = name + + # 其实只有这里改变 + @TestProperty + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") +``` + +为了尽量让你少产生一点疑惑,我这里做两点说明: + +1. 使用`TestProperty`装饰后,`math` 不再是一个函数,而是`TestProperty` 类的一个实例。所以第二个math函数可以使用 `math.setter` 来装饰,本质是调用`TestProperty.setter` 来产生一个新的 `TestProperty` 实例赋值给第二个`math`。 +2. 第一个 `math` 和第二个 `math` 是两个不同 `TestProperty` 实例。但他们都属于同一个描述符类(TestProperty),当对 math 对于赋值时,就会进入 `TestProperty.__set__`,当对math 进行取值里,就会进入 `TestProperty.__get__`。仔细一看,其实最终访问的还是Student实例的 `_math` 属性。 + +说了这么多,还是运行一下,更加直观一点。 + +```python +# 运行后,会直接打印这一行,这是在实例化 TestProperty 并赋值给第二个math +in setter +>>> +>>> s1.math = 90 +in __set__ +>>> s1.math +in __get__ +90 +``` + +如对上面代码的运行原理,有疑问的同学,请务必结合上面两点说明加以理解,那两点相当关键。 + -## 3.1.9 其他装饰器:装饰器实战 +## 3.1.11 其他装饰器:装饰器实战 读完并理解了上面的内容,你可以说是Python高手了。别怀疑,自信点,因为很多人都不知道装饰器有这么多用法呢。 From 7d32312f015e543001769c5500f29332d0e78b01 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 27 May 2019 12:50:51 +0800 Subject: [PATCH 017/302] =?UTF-8?q?=E8=BF=AD=E4=BB=A3=E5=99=A8=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c02/c02_07.md | 23 ++++++++++++++++------- source/c08/c08_05.md | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/source/c02/c02_07.md b/source/c02/c02_07.md index 5b59491..2386fec 100644 --- a/source/c02/c02_07.md +++ b/source/c02/c02_07.md @@ -22,6 +22,9 @@ 可迭代的对象,很好理解,我们很熟悉的:`字符串`,`list`,`dict`,`tuple`,`deque`等 为了验证我说的,需要借助`collections.abc`这个模块(Python2没有),使用`isinstance()`来类别一个对象是否是可迭代的(`Iterable`),是否是迭代器(`Iterator`),是否是生成器(`Generator`)。 + +这几个判断方法,在这里适用,但并不是绝对适用,原因见后面补充说明。 + ```python import collections from collections.abc import Iterable, Iterator, Generator @@ -78,11 +81,14 @@ False ``` 从结果来看,这些可迭代对象都不是迭代器,也不是生成器。它们有一个共同点,就是它们都可以使用`for`来循环。这一点,大家都知道,我们就不去验证了。 ->**扩展知识**: ->可迭代对象,是其内部实现了,`__iter__` 这个魔术方法。 ->可以通过,`dir()`方法来查看是否有`__iter__`来判断一个变量是否是可迭代的。 +**关于可迭代对象,有几点需要补充说明** + +1. 可以通过,`dir()`方法查看,若有有`__iter__`说明是可迭代的,但是如果没有,也不能说明不可迭代,原因见第二条。 +2. 判断是否可迭代,不能仅看是否有`__iter__` 来草率决定,因为只实现了`__getitem__` 方法的也有可能是可迭代的。因为当没有`__iter__`时, Python 解释器会去找`__getitem__`,尝试按顺序(从索引0开始)获取元素,不抛异常,即是可迭代。 +3. 所以,最好的判断方法应该是通过 `for循环`或者` iter()` 去真实运行。 + +![](http://image.python-online.cn/20190527123516.png) -![](https://i.loli.net/2018/05/19/5affbd70eddbf.png) 接下来是,`迭代器`。 @@ -175,9 +181,12 @@ next(aIterator) # c next(aIterator) # d ``` ->**扩展知识**: ->迭代器,是其内部实现了,`__next__` 这个魔术方法。(Python3.x) ->可以通过,`dir()`方法来查看是否有`__next__`来判断一个变量是否是迭代器的。 +**补充说明**: + +1. 迭代器,是其内部实现了,`__next__` 这个魔术方法。(Python3.x) +2. 可以通过,`dir()`方法来查看是否有`__next__`来判断一个变量是否是迭代器的。 + + 接下来,是我们的重点,`生成器`。 diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 16e66fc..0be3da0 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -74,7 +74,7 @@ driver = messagingv2 ![](http://image.python-online.cn/20190526181433.png) -当nova-api发送rpc消息给conducotr的时候,是如何发送过去的呢? +当nova-api发送rpc远程调用给conducotr的时候,是如何发送过去的呢? 这边是 nova/conducotr/rpcapi.py,是发送端。 From df2bdb09f2882c8bc2ac9edcba4e4add4f6b1e22 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 27 May 2019 22:37:57 +0800 Subject: [PATCH 018/302] =?UTF-8?q?rpc=20=E8=BF=9C=E7=A8=8B=E8=B0=83?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_17.md | 4 +- source/c08/c08_05.md | 97 ------------------ source/c08/c08_09.md | 237 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+), 100 deletions(-) create mode 100644 source/c08/c08_09.md diff --git a/source/c01/c01_17.md b/source/c01/c01_17.md index b2bed38..13375b6 100644 --- a/source/c01/c01_17.md +++ b/source/c01/c01_17.md @@ -517,11 +517,9 @@ class Student: ## 参考文档 +- [Python描述器引导(翻译)](https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html#python) -- [Python描述器引导(翻译)](https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html#id8)[¶](https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html#python) - -https://zhuanlan.zhihu.com/p/42485483 diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 0be3da0..74576d3 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -11,103 +11,6 @@ 这个函数,只有在 `spawn` 、`_hard_reboot` 和 `finish_migration` 才会执行。 - -## 8.5.2 rpc 是如何调用的? - -OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul API方式,另一种则是RPC调用。两种通信方式的应用场景有所不同,在OpenStack中,前者主要用于各组件之间的通信(如nova与glance的通信),而后者则用于同一组件中各个不同模块之间的通信(如nova组件中nova-compute与nova-scheduler的通信)。 -关于OpenStack中基于RESTFul API的通信方式主要是应用了WSGI,我会在以后的博文中详细讨论。这里主要还是分享一下关于RPC调用的一些理解。 - -首先,什么是RPC呢?百度百科给出的解释是这样的:“RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议”。这个概念听起来还是比较抽象,下面我会结合OpenStack中RPC调用的具体应用来具体分析一下这个协议。 - -其次,为什么要采用RPC呢?单纯的依靠RESTFul API不可以吗?其实原因有下面这几个: - -1. 由于RESTFul API是基于HTTP协议的,因此客户端与服务端之间所能传输的消息仅限于文本 -2. RESTFul API客户端与服务端之间采用的是同步机制,当发送HTTP请求时,客户端需要等待服务端的响应。当然对于这一点是可以通过一些技术来实现异步的机制的 -3. 采用RESTFul API,客户端与服务端之间虽然可以独立开发,但还是存在耦合。比如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常工作 - -基于上面这几个原因,所以OpenStack才采用了另一种远程通信机制,这就是我们今天要讨论的鼎鼎大名的RPC。 - -今天的话题,就是源码解读OpenStack是如何通过rpc进行远程调用的。 - -如果你不想用现成的 `notification_event_types`,而想新定义一个,可以这样做 - -首先在这里先定义合法的 `notification_event_types`,相当于添加白名单。 - -![](http://image.python-online.cn/20190526172514.png) - -然后在调用处,使用 `rpc.get_notifier` 来发送消息给ceilometer。 - -![](http://image.python-online.cn/20190526172725.png) - -继续查看 `rpc.get_notifier` 做了什么事?如何实现直接info 就能发送消息的。 - -![](http://image.python-online.cn/20190526173314.png) - -当你使用的event_types 不在白名单内,或者是异常信息。就会给打印warn日志 - -![](http://image.python-online.cn/20190526175100.png) - - - -在nova.confg里有这一项配置,rabbit 指明了通信方式是rabbitmq。后面我标名了user,passwd,主机域名,端口,你应该能理解。 - -``` -transport_url=rabbit://user:passwd@ctrl.openstack.com:5672 -``` - -如果有多个主机,可以参照下面的 docstring 使用 逗号分隔进行添加。 - -![](http://image.python-online.cn/20190526182125.png) - -指明了使用 messagingv2 这个driver,其他可用的还有routing, log, test, noop - -``` -[oslo_messaging_notifications] -driver = messagingv2 -``` - -在rabbit里查看队列,notification 是 topic - -![](http://image.python-online.cn/20190526180708.png) - -而 debug ,info 等是event priority - -![](http://image.python-online.cn/20190526181433.png) - -当nova-api发送rpc远程调用给conducotr的时候,是如何发送过去的呢? - -这边是 nova/conducotr/rpcapi.py,是发送端。 - -target 里的 namespace 指定了要调用哪个类。 - -cctxt.call 里的 'build_instances' 指定了要调用哪个函数。 - -![](http://image.python-online.cn/20190526185217.png) - -在nova/conducotr/manager.py 里有两个类,各有一个类变量,声明了target,发送端的target需要和这边target匹配上,才会进入相应的处理逻辑。 - -![](http://image.python-online.cn/20190526184854.png) - -你一定很好奇,这个target是如何绑定到rpc server里的呢? - -首先我们要知道 rpc_server 是如何创建的? - -如果你看了 8.5.11 那节 (nova的各项服务是怎么启动?),然后再去看 nova-conductor的启动代码,你就会发现在 nova\nova\service.py:Service.start() 里有这么一段代码,是创建rpc server的,endpoints 里面的self.manager 就是ConductorManager 类 - -![](http://image.python-online.cn/20190526221219.png) - -![](http://image.python-online.cn/20190526221636.png) - -从oslo_messaging 的代码可以看出,它会先根据endpoints创建dispatcher,dispatcher就会根据类变量target对象,创建消息的分发规则。这样从client就可以成功地调用远程(服务端)的函数。 - -![](http://image.python-online.cn/20190526220809.png) - -![](http://image.python-online.cn/20190526220605.png) - -参考文章:[OpenStack之RPC调用(一)](https://blog.csdn.net/qiuhan0314/article/details/42671965) - - - ## 8.5.3 创建快照代码解读? diff --git a/source/c08/c08_09.md b/source/c08/c08_09.md new file mode 100644 index 0000000..47ef912 --- /dev/null +++ b/source/c08/c08_09.md @@ -0,0 +1,237 @@ +# 8.9 rpc 是如何调用的? + +OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul API方式,另一种则是RPC调用。两种通信方式的应用场景有所不同,在OpenStack中,前者主要用于各组件之间的通信(如nova与glance的通信),而后者则用于同一组件中各个不同模块之间的通信(如nova组件中nova-compute与nova-scheduler的通信)。 +关于OpenStack中基于RESTFul API的通信方式主要是应用了WSGI,我会在以后的博文中详细讨论。这里主要还是分享一下关于RPC调用的一些理解。 + +首先,什么是RPC呢?百度百科给出的解释是这样的:“RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议”。这个概念听起来还是比较抽象,下面我会结合OpenStack中RPC调用的具体应用来具体分析一下这个协议。 + +## 8.9.1 为什么要使用 RPC? + +其次,为什么要采用RPC呢?单纯的依靠RESTFul API不可以吗?其实原因有下面这几个: + +1. 由于RESTFul API是基于HTTP协议的,因此客户端与服务端之间所能传输的消息仅限于文本 +2. RESTFul API客户端与服务端之间采用的是同步机制,当发送HTTP请求时,客户端需要等待服务端的响应。当然对于这一点是可以通过一些技术来实现异步的机制的 +3. 采用RESTFul API,客户端与服务端之间虽然可以独立开发,但还是存在耦合。比如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常工作 + +基于上面这几个原因,所以OpenStack才采用了另一种远程通信机制,这就是我们今天要讨论的鼎鼎大名的RPC。 + +今天的话题,就是源码解读OpenStack是如何通过rpc进行远程调用的。 + +谈到 OpenStack 的 rpc,就不得不提到 oslo_messaging 这个 OpenStack 专用工具库,oslo_messaging只是对多种transport做了进一步封装,底层也是用到了kombu这个AMQP库,当transport是amqp时,kombu会进行环境监测判断是否安装了librabbitmq,如果安装了就使用librabbitmq,没有则使用pyamqp;因为librabbitmq是使用C编写的库,所以比pyamqp速度快很多。 + +关于oslo_messaging库,主要提供了两种独立的API: + +1. oslo.messaging.rpc(实现了客户端-服务器远程过程调用) +2. oslo.messaging.notify(实现了事件的通知机制) + +## 8.9.2 如何实现 rpc 远程调用 + +**先来讲解第一个 rpc 的远程调用。** + +要了解 rpc 的调用机制呢,首先要知道 oslo_messaging 的几个概念 + +- transport:RPC功能的底层实现方法,这里是rabbitmq的消息队列的访问路径 + + transport 就是定义你如何访连接消息中间件,比如你使用的是 Rabbitmq,那在nova.conf中应该有一行`transport_url`的配置,可以很清楚地看出指定了 rabbitmq 为消息中间件,并配置了连接rabbitmq的user,passwd,主机,端口。 + + ```python + transport_url=rabbit://user:passwd@ctrl.openstack.com:5672 + ``` + + ![](http://image.python-online.cn/20190526182125.png) + + ```python + def get_transport(conf, url=None, allowed_remote_exmods=None): + return _get_transport(conf, url, allowed_remote_exmods, + transport_cls=RPCTransport) + ``` + + transport 的 driver 有如下几种: + + 1. amqp : + 2. fake :测试使用,该driver将消息发送到内存中。 + 3. kafka :experimental + 4. kombu :RabbitMQ Driver。openstack默认的。 + 5. pika :successor to the existing rabbit/kombu driver。 + 6. rabbit :RabbitMQ Driver + 7. zmq :通过ZeroMQ 实现了RPC和Notifer API.详见: + +- target:指定RPC topic交换机的匹配信息和绑定主机 + + target用来表述 RPC 服务器监听topic,server名称和server监听的exchange,是否广播fanout + + ```python + class Target(object): + def __init__(self, exchange=None, topic=None, namespace=None, + version=None, server=None, fanout=None, + legacy_namespaces=None): + self.exchange = exchange + self.topic = topic + self.namespace = namespace + self.version = version + self.server = server + self.fanout = fanout + self.accepted_namespaces = [namespace] + (legacy_namespaces or []) + ``` + + rpc server 要获取消息,需要定义target,就像一个门牌号一样。 + + ![](http://image.python-online.cn/20190526184854.png) + + + + rpc client 要发送消息,也需要有target,说明消息要发到哪去。 + + ![](http://image.python-online.cn/20190526185217.png) + +- endpoints:是可供别人远程调用对象 + + RPC服务器暴露出endpoint,每个 endpoint 包涵一系列的可被远程客户端通过 transport 调用的方法。直观理解,可以参考nova-conductor创建rpc server的代码,这边的endpoints就是 nova/manager.py:ConductorManager()![](http://image.python-online.cn/20190526221219.png) + + + +- dispatcher:分发器,这是 rpc server 才有的概念 ![](http://image.python-online.cn/20190526220809.png)只有它才知道server端接收到的哪些rpc调用是我要处理的,并且知道远方client端是我调用我的哪个方法? + + 在client端,是这样指定要调用哪个方法的。 + + ![](http://image.python-online.cn/20190527220820.png) + + 而在server端,是如何知道要执行这个方法的呢?这就是dispatcher 要干的事,它从 endpoint 里找到这个方法,然后执行,最后返回。 + + ![](http://image.python-online.cn/20190527220012.png) + + + +- Serializer:在 python 对象和message(notification) 之间数据做序列化或是反序列化的基类。 + + 主要方法有四个: + + 1. deserialize_context(ctxt) :对字典变成 request contenxt. + 2. deserialize_entity(ctxt, entity) :对entity做反序列化,其中ctxt是已经deserialize过的,entity是要处理的。 + 3. serialize_context(ctxt) :将Request context变成字典类型 + 4. serialize_entity(ctxt, entity) :对entity做序列化,其中ctxt是已经deserialize过的,entity是要处理的。 + +- executor:服务的运行方式,单线程或者多线程 + + 每个notification listener都和一个executor绑定,来控制收到的notification如何分配。默认情况下,使用的是blocking executor(具体特性参加executor一节) + + ```python + oslo_messaging.get_notification_listener(transport, targets, endpoints, executor=’blocking’, serializer=None, allow_requeue=False, pool=None) + ``` + + + +rpc server 和rpc client 的四个重要方法 + +1. `reset()`:Reset service. +2. `start()`:该方法调用后,server开始poll,从transport中接收message,然后转发给dispatcher.该message处理过程一直进行,直到stop方法被调用。executor决定server的IO处理策略。可能会是用一个新进程、新协程来做poll操作,或是直接简单的在一个循环中注册一个回调。同样,executor也决定分配message的方式,是在一个新线程中dispatch或是..... * +3. `stop()`:当调用stop之后,新的message不会被处理。但是,server可能还在处理一些之前没有处理完的message,并且底层driver资源也还一直没有释放。 +4. `wait()`:在stop调用之后,可能还有message正在被处理,使用wait方法来阻塞当前进程,直到所有的message都处理完成。之后,底层的driver资源会释放。 + + + +**简单的 rpc client** + +```python +import oslo_messaging +from oslo_config import cfg + +transport = messaging.get_transport(cfg.CONF) +target = messaging.Target(topic='test', version='2.0') +client = messaging.RPCClient(transport, target) +client.call(ctxt, 'test', arg=arg) +``` + +**简单的rpc server** + +```python +from oslo_config import cfg +import oslo_messaging +import time + +# 定义endpoint类 +class ServerControlEndpoint(object): + target = oslo_messaging.Target(namespace='control', + version='2.0') + + def __init__(self, server): + self.server = server + + def stop(self, ctx): + if self.server: + self.server.stop() + + +class TestEndpoint(object): + + def test(self, ctx, arg): + return arg + + +# 创建rpc server +transport = oslo_messaging.get_transport(cfg.CONF) +target = oslo_messaging.Target(topic='test', server='server1') +endpoints = [ + ServerControlEndpoint(None), + TestEndpoint(), +] +server = oslo_messaging.get_rpc_server(transport, target, endpoints, + executor='blocking') +try: + server.start() + while True: + time.sleep(1) +except KeyboardInterrupt: + print("Stopping server") + +server.stop() +server.wait() +``` + + + +## 8.5.3 如何实现 rpc 事件通知 + +说完了 rpc 调用,**再来了解它的事件通知机制**,这个比较简单。 + +如果你不想用现成的 `notification_event_types`,而想新定义一个,可以这样做 + +首先在这里先定义合法的 `notification_event_types`,相当于添加白名单。 + +![](http://image.python-online.cn/20190526172514.png) + +然后在调用处,使用 `rpc.get_notifier` 来发送消息给ceilometer。 + +![](http://image.python-online.cn/20190526172725.png) + +继续查看 `rpc.get_notifier` 做了什么事?如何实现直接info 就能发送消息的。 + +![](http://image.python-online.cn/20190526173314.png) + +当你使用的event_types 不在白名单内,或者是异常信息。就会给打印warn日志 + +![](http://image.python-online.cn/20190526175100.png) + + + +在rabbit里查看队列,notification 是 topic + +![](http://image.python-online.cn/20190526180708.png) + +而 debug ,info 等是event priority + +![](http://image.python-online.cn/20190526181433.png) + + + +参考文章: + +- [OpenStack之RPC调用(一)](https://blog.csdn.net/qiuhan0314/article/details/42671965) +- [openstack oslo_messaging 译文](https://blog.csdn.net/youyou1543724847/article/details/71169501) +- [模仿OpenStack写自己的RPC](https://www.cnblogs.com/goldsunshine/p/10205058.html) +- [python 64式: 第1式 编写rpc的call和cast](https://blog.csdn.net/qingyuanluofeng/article/details/80546961) +- [Openstack RPC 通信原理](https://www.ibm.com/developerworks/cn/cloud/library/1403_renmm_opestackrpc/) + +--- + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file From d659ab6f764296b595c63f89ad15bb821fcda68c Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 27 May 2019 23:33:15 +0800 Subject: [PATCH 019/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20rst=20=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_17.rst | 8 +- source/c02/c02_07.rst | 23 ++-- source/c03/c03_01.md | 21 ++-- source/c03/c03_01.rst | 200 +++++++++++++++++++++++++++--- source/c07/c07_01.rst | 7 ++ source/c08/c08_01.rst | 8 ++ source/c08/c08_05.rst | 231 ++++++++++++++++++++++++++++------- source/c08/c08_09.rst | 275 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 691 insertions(+), 82 deletions(-) create mode 100644 source/c08/c08_09.rst diff --git a/source/c01/c01_17.rst b/source/c01/c01_17.rst index 5534e39..d70fafd 100644 --- a/source/c01/c01_17.rst +++ b/source/c01/c01_17.rst @@ -573,13 +573,7 @@ super 的实现原理,就交由你来自己完成。 参考文档 -------- -https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p09_define_decorators_as_classes.html - -https://blog.csdn.net/BF02jgtRS00XKtCx/article/details/82882622 - -https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html - -https://zhuanlan.zhihu.com/p/42485483 +- `Python描述器引导(翻译) `__ -------------- diff --git a/source/c02/c02_07.rst b/source/c02/c02_07.rst index 2df59be..98708d1 100755 --- a/source/c02/c02_07.rst +++ b/source/c02/c02_07.rst @@ -25,6 +25,8 @@ 为了验证我说的,需要借助\ ``collections.abc``\ 这个模块(Python2没有),使用\ ``isinstance()``\ 来类别一个对象是否是可迭代的(\ ``Iterable``\ ),是否是迭代器(\ ``Iterator``\ ),是否是生成器(\ ``Generator``\ )。 +这几个判断方法,在这里适用,但并不是绝对适用,原因见后面补充说明。 + .. code:: python import collections @@ -84,9 +86,15 @@ 从结果来看,这些可迭代对象都不是迭代器,也不是生成器。它们有一个共同点,就是它们都可以使用\ ``for``\ 来循环。这一点,大家都知道,我们就不去验证了。 - **扩展知识**: 可迭代对象,是其内部实现了,\ ``__iter__`` - 这个魔术方法。 - 可以通过,\ ``dir()``\ 方法来查看是否有\ ``__iter__``\ 来判断一个变量是否是可迭代的。 +**关于可迭代对象,有几点需要补充说明** + +1. 可以通过,\ ``dir()``\ 方法查看,若有有\ ``__iter__``\ 说明是可迭代的,但是如果没有,也不能说明不可迭代,原因见第二条。 +2. 判断是否可迭代,不能仅看是否有\ ``__iter__`` + 来草率决定,因为只实现了\ ``__getitem__`` + 方法的也有可能是可迭代的。因为当没有\ ``__iter__``\ 时, Python + 解释器会去找\ ``__getitem__``\ ,尝试按顺序(从索引0开始)获取元素,不抛异常,即是可迭代。 +3. 所以,最好的判断方法应该是通过 ``for循环``\ 或者\ ``iter()`` + 去真实运行。 |image0| @@ -183,11 +191,10 @@ next(aIterator) # c next(aIterator) # d -.. +**补充说明**: - **扩展知识**: 迭代器,是其内部实现了,\ ``__next__`` - 这个魔术方法。(Python3.x) - 可以通过,\ ``dir()``\ 方法来查看是否有\ ``__next__``\ 来判断一个变量是否是迭代器的。 +1. 迭代器,是其内部实现了,\ ``__next__`` 这个魔术方法。(Python3.x) +2. 可以通过,\ ``dir()``\ 方法来查看是否有\ ``__next__``\ 来判断一个变量是否是迭代器的。 接下来,是我们的重点,\ ``生成器``\ 。 @@ -387,6 +394,6 @@ :alt: 关注公众号,获取最新干货! -.. |image0| image:: https://i.loli.net/2018/05/19/5affbd70eddbf.png +.. |image0| image:: http://image.python-online.cn/20190527123516.png .. |image1| image:: https://i.loli.net/2018/05/19/5affd48c34e3f.png diff --git a/source/c03/c03_01.md b/source/c03/c03_01.md index df40a02..f88b528 100644 --- a/source/c03/c03_01.md +++ b/source/c03/c03_01.md @@ -22,9 +22,10 @@ ## 3.1.2 入门用法:日志打印器 首先是**日志打印器**。 + 实现的功能: ->在函数执行前,先打印一行日志告知一下主人,我要执行函数了。 ->在函数执行完,也不能拍拍屁股就走人了,咱可是有礼貌的代码,再打印一行日志告知下主人,我执行完啦。 +- 在函数执行前,先打印一行日志告知一下主人,我要执行函数了。 +- 在函数执行完,也不能拍拍屁股就走人了,咱可是有礼貌的代码,再打印一行日志告知下主人,我执行完啦。 ```python # 这是装饰函数 @@ -58,8 +59,8 @@ add(200, 50) ## 3.1.3 入门用法:时间计时器 再来看看 **时间计时器** -实现功能: ->顾名思义,就是计算一个函数的执行时长。 +实现功能:顾名思义,就是计算一个函数的执行时长。 + ```python # 这是装饰函数 def timer(func): @@ -74,7 +75,7 @@ def timer(func): print("花费时间:{}秒".format(cost_time)) return wrapper ``` -假如,我们的函数是要睡眠10秒(冏~,小明实在不知道要举什么例子了)。这样也能更好的看出这个计算时长到底靠不靠谱。 +假如,我们的函数是要睡眠10秒。这样也能更好的看出这个计算时长到底靠不靠谱。 ```python import time @@ -84,9 +85,9 @@ def want_sleep(sleep_time): want_sleep(10) ``` -来看看,输出。真的是2秒耶。真历害!!! +来看看,输出。真的是10秒耶。真历害!!! ``` -花费时间:2.0073800086975098秒 +花费时间:10.0073800086975098秒 ``` @@ -289,7 +290,7 @@ Wait for 2 seconds... ``` -## 3.18 如何写能装饰类的装饰器? +## 3.1.8 如何写能装饰类的装饰器? 用 Python 写单例模式的时候,常用的有三种写法。其中一种,是用装饰器来实现的。 @@ -636,12 +637,12 @@ in __get__ 读完并理解了上面的内容,你可以说是Python高手了。别怀疑,自信点,因为很多人都不知道装饰器有这么多用法呢。 -在小明看来,使用装饰器,可以达到如下目的: +在我看来,使用装饰器,可以达到如下目的: - 使代码可读性更高,逼格更高; - 代码结构更加清晰,代码冗余度更低; -刚好小明在最近也有一个场景,可以用装饰器很好的实现,暂且放上来看看。 +刚好我在最近也有一个场景,可以用装饰器很好的实现,暂且放上来看看。 这是一个实现控制函数运行超时的装饰器。如果超时,则会抛出超时异常。 diff --git a/source/c03/c03_01.rst b/source/c03/c03_01.rst index 9ae2620..d4edeca 100755 --- a/source/c03/c03_01.rst +++ b/source/c03/c03_01.rst @@ -23,9 +23,11 @@ 3.1.2 入门用法:日志打印器 -------------------------- -首先是\ **日志打印器**\ 。 实现的功能: ->在函数执行前,先打印一行日志告知一下主人,我要执行函数了。 ->在函数执行完,也不能拍拍屁股就走人了,咱可是有礼貌的代码,再打印一行日志告知下主人,我执行完啦。 +首先是\ **日志打印器**\ 。 + +实现的功能: - +在函数执行前,先打印一行日志告知一下主人,我要执行函数了。 - +在函数执行完,也不能拍拍屁股就走人了,咱可是有礼貌的代码,再打印一行日志告知下主人,我执行完啦。 .. code:: python @@ -65,8 +67,7 @@ 3.1.3 入门用法:时间计时器 -------------------------- -再来看看 **时间计时器** 实现功能: ->顾名思义,就是计算一个函数的执行时长。 +再来看看 **时间计时器** 实现功能:顾名思义,就是计算一个函数的执行时长。 .. code:: python @@ -83,7 +84,7 @@ print("花费时间:{}秒".format(cost_time)) return wrapper -假如,我们的函数是要睡眠10秒(冏~,小明实在不知道要举什么例子了)。这样也能更好的看出这个计算时长到底靠不靠谱。 +假如,我们的函数是要睡眠10秒。这样也能更好的看出这个计算时长到底靠不靠谱。 .. code:: python @@ -95,11 +96,11 @@ want_sleep(10) -来看看,输出。真的是2秒耶。真历害!!! +来看看,输出。真的是10秒耶。真历害!!! :: - 花费时间:2.0073800086975098秒 + 花费时间:10.0073800086975098秒 3.1.4 进阶用法:带参数的函数装饰器 ---------------------------------- @@ -323,7 +324,45 @@ Python工匠:使用装饰器的小技巧) >>> add.func # 实现实例方法 -3.1.7 wrapper 装饰器有啥用? +3.1.8 如何写能装饰类的装饰器? +------------------------------ + +用 Python +写单例模式的时候,常用的有三种写法。其中一种,是用装饰器来实现的。 + +以下便是我自己写的装饰器版的单例写法。 + +.. code:: python + + instances = {} + + def singleton(cls): + def get_instance(*args, **kw): + cls_name = cls.__name__ + print('===== 1 ====') + if not cls_name in instances: + print('===== 2 ====') + instance = cls(*args, **kw) + instances[cls_name] = instance + return instances[cls_name] + return get_instance + + @singleton + class User: + _instance = None + + def __init__(self, name): + print('===== 3 ====') + self.name = name + +可以看到我们用singleton 这个装饰函数来装饰 User +这个类。装饰器用在类上,并不是很常见,但只要熟悉装饰器的实现过程,就不难以实现对类的装饰。在上面这个例子中,装饰器就只是实现对类实例的生成的控制而已。 + +其实例化的过程,你可以参考我这里的调试过程,加以理解。 + +|image0| + +3.1.9 wrapper 装饰器有啥用? ---------------------------- 在 functools 标准库中有提供一个 wrapper @@ -415,8 +454,8 @@ wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码 print(wrapped.__name__) # wrapped -3.1.8 内置装饰器:property --------------------------- +3.1.10 内置装饰器:property +--------------------------- 以上,我们介绍的都是自定义的装饰器。 @@ -533,15 +572,141 @@ wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码 ``@age.deleter`` 使得我们可以使用\ ``del XiaoMing.age``\ 这样的方式来删除属性。 -3.1.9 其他装饰器:装饰器实战 ----------------------------- +property 的底层实现机制是「描述符」,为此我还写过一篇文章。 + +这里也介绍一下吧,正好将这些看似零散的文章全部串起来。 + +如下,我写了一个类,里面使用了 property 将 math 变成了类实例的属性 + +.. code:: python + + class Student: + def __init__(self, name): + self.name = name + + @property + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") + +为什么说 property 底层是基于描述符协议的呢?通过 PyCharm 点击进入 +property +的源码,很可惜,只是一份类似文档一样的伪源码,并没有其具体的实现逻辑。 + +不过,从这份伪源码的魔法函数结构组成,可以大体知道其实现逻辑。 + +这里我自己通过模仿其函数结构,结合「描述符协议」来自己实现类 +``property`` 特性。 + +代码如下: + +.. code:: python + + class TestProperty(object): + + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + self.fget = fget + self.fset = fset + self.fdel = fdel + self.__doc__ = doc + + def __get__(self, obj, objtype=None): + print("in __get__") + if obj is None: + return self + if self.fget is None: + raise AttributeError + return self.fget(obj) + + def __set__(self, obj, value): + print("in __set__") + if self.fset is None: + raise AttributeError + self.fset(obj, value) + + def __delete__(self, obj): + print("in __delete__") + if self.fdel is None: + raise AttributeError + self.fdel(obj) + + + def getter(self, fget): + print("in getter") + return type(self)(fget, self.fset, self.fdel, self.__doc__) + + def setter(self, fset): + print("in setter") + return type(self)(self.fget, fset, self.fdel, self.__doc__) + + def deleter(self, fdel): + print("in deleter") + return type(self)(self.fget, self.fset, fdel, self.__doc__) + +然后 Student 类,我们也相应改成如下 + +.. code:: python + + class Student: + def __init__(self, name): + self.name = name + + # 其实只有这里改变 + @TestProperty + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") + +为了尽量让你少产生一点疑惑,我这里做两点说明: + +1. 使用\ ``TestProperty``\ 装饰后,\ ``math`` + 不再是一个函数,而是\ ``TestProperty`` + 类的一个实例。所以第二个math函数可以使用 ``math.setter`` + 来装饰,本质是调用\ ``TestProperty.setter`` 来产生一个新的 + ``TestProperty`` 实例赋值给第二个\ ``math``\ 。 +2. 第一个 ``math`` 和第二个 ``math`` 是两个不同 ``TestProperty`` + 实例。但他们都属于同一个描述符类(TestProperty),当对 math + 对于赋值时,就会进入 ``TestProperty.__set__``\ ,当对math + 进行取值里,就会进入 + ``TestProperty.__get__``\ 。仔细一看,其实最终访问的还是Student实例的 + ``_math`` 属性。 + +说了这么多,还是运行一下,更加直观一点。 + +.. code:: python + + # 运行后,会直接打印这一行,这是在实例化 TestProperty 并赋值给第二个math + in setter + >>> + >>> s1.math = 90 + in __set__ + >>> s1.math + in __get__ + 90 + +如对上面代码的运行原理,有疑问的同学,请务必结合上面两点说明加以理解,那两点相当关键。 + +3.1.11 其他装饰器:装饰器实战 +----------------------------- 读完并理解了上面的内容,你可以说是Python高手了。别怀疑,自信点,因为很多人都不知道装饰器有这么多用法呢。 -在小明看来,使用装饰器,可以达到如下目的: - -使代码可读性更高,逼格更高; - 代码结构更加清晰,代码冗余度更低; +在我看来,使用装饰器,可以达到如下目的: - 使代码可读性更高,逼格更高; +- 代码结构更加清晰,代码冗余度更低; -刚好小明在最近也有一个场景,可以用装饰器很好的实现,暂且放上来看看。 +刚好我在最近也有一个场景,可以用装饰器很好的实现,暂且放上来看看。 这是一个实现控制函数运行超时的装饰器。如果超时,则会抛出超时异常。 @@ -574,3 +739,6 @@ wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码 .. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! + +.. |image0| image:: http://image.python-online.cn/20190512113917.png + diff --git a/source/c07/c07_01.rst b/source/c07/c07_01.rst index ad0834b..1e81033 100755 --- a/source/c07/c07_01.rst +++ b/source/c07/c07_01.rst @@ -787,6 +787,13 @@ Linux上的压缩格式比Windows上多很多,在 Windows 上最常见的不 $ cat /proc/cpuinfo $ numactk -H + + # ubuntu 查看更新有哪些是安全更新 + $ apt-get -s --no-download dist-upgrade -V | grep "^Inst.*security.*$" | cut -d " " -f 2 + + # ubuntu 登陆时,显示的安全更新是如何来的,内容:/etc/update-motd.d/90-updates-available + /usr/lib/update-notifier/apt-check --human-readable + 时间查询 ^^^^^^^^ diff --git a/source/c08/c08_01.rst b/source/c08/c08_01.rst index 61d55c4..6d6581a 100755 --- a/source/c08/c08_01.rst +++ b/source/c08/c08_01.rst @@ -66,6 +66,9 @@ $ 增加安全组规则(开放ssh) $ nova secgroup-add-rule default tcp <22> <22> <0.0.0.0/0> + # 设置metadata + nova meta xxxxxx set ws:evacuate_interval=10 + flavor管理 ^^^^^^^^^^ @@ -385,6 +388,11 @@ aggregate管理 rabbitmqctl set_permissions -p / openstack ".*" ".*" ".*" rabbitmqctl set_user_tags openstack administrator +:: + + # 指定节点执行命令 + rabbitmq -n rabbit@ws_controller02 [command] + -------------- .. figure:: http://image.python-online.cn/20190511161447.png diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index a7fe415..5fdbffb 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -1,11 +1,16 @@ -8.5 OpenStack 源码剖析 -====================== +8.5 OpenStack 源码剖析与改造 +============================ 8.5.1 虚拟机是如何创建出来的? ------------------------------ -8.5.2 rpc 是如何调用的? ------------------------- +生成xml,准备网络(plug_vif)创建domain 是在 +``_create_domain_and_network`` 这个函数中 + +|image0| + +这个函数,只有在 ``spawn`` 、\ ``_hard_reboot`` 和 ``finish_migration`` +才会执行。 8.5.3 创建快照代码解读? ------------------------ @@ -58,26 +63,26 @@ nova 提供了一个配置项:notify_on_state_change,本意是想,如果配置\ ``vm_state``\ 就只在vm_state 第一次,在\ ``manager.py:2050``\ 的函数 -``_do_build_and_run_instance``\ 里,看instance.save()大 +``_do_build_and_run_instance``\ 里,看instance.save() 8.5.5 快照镜像如何实现? ------------------------ nova-api 的入口如下 -|image0| +|image1| 接着会调用 nova/compute/api.py -|image1| +|image2| 在nova-compute 层面:nova/compute/manager.py:_snapshot_instance() -|image2| +|image3| 接下来会调用 ``nova/virt/libvirt/driver.py:snapshot()`` -|image3| +|image4| 先获取imagebackend的类型,然后找到对应的backend @@ -92,13 +97,13 @@ nova-api 的入口如下 接下来,会调用对应的imagebackend的\ ``snapshot_extract`` 方法。 -|image4| +|image5| ``snapshot_extract`` 方法最终会调用\ ``nova/virt/images.py:_convert_image()`` ,可以看出其实底层调用的是 ``qemu-img`` 提供的\ ``convert`` 接口。 -|image5| +|image6| 如果是qcow2的backend,不是调用这边,而是调用 ``nova/virt/libvirt/utils.py:extract_snapshot()`` @@ -121,11 +126,11 @@ nova-api 的入口如下 在\ ``libvirt_utils.get_disk_type_from_path`` 里没有相应的修改,导致返回的是lvm。 -|image6| +|image7| 后面的imagebackend也相应的变成 lvm的 -|image7| +|image8| 然后会进入 lvm这个backend的init函数。由于\ ``path`` 是\ ``/dev/sdb`` 并不是一个lv,所以这边会报错。 @@ -149,7 +154,7 @@ compute的资源上报,是在 其调用的函数是\ ``virt/libvirt/driver.py`` 里的 ``get_available_resource`` 函数 -|image8| +|image9| 从数据库获取旧数据 ``self.compute_node = self._get_compute_node(context)`` @@ -167,11 +172,11 @@ compute的资源上报,是在 nova-compute 发创建请求前,会先让 nova-scheduler 选出一台资源充足的计算节点。 -|image9| +|image10| nova-scheduler 的调度主要由两部分组成 -|image10| +|image11| - 过滤器:filter,将不满足条件(硬性条件,比如内存,cpu,磁盘,pci设备等)的计算节点,直接过滤掉。意义:从过滤器出来的那些计算节点,理论上都可以创建虚拟机。 - 称重器:weigher,对满足硬性条件的众多主机按照一定的规则进行权重配比。意义:经过称重器计算,选出你更希望在哪台节点上创建虚拟机。 @@ -185,25 +190,25 @@ nova-scheduler 的调度主要由两部分组成 compute 的 id,可以在 ``_update_from_compute_node`` 函数中添加。它会从compute_nodes 表中取得你想要的信息。 - |image11| + |image12| - spec_obj:你所要请求创建的虚拟机信息(模板,镜像等)。它是从 objects.RequestSpec.from_primitives 中取得的 - |image12| + |image13| 过滤器,它的代码如下: -|image13| +|image14| 称重器,它的规则主要看这段代码。 -|image14| +|image15| 我在代码中,加了几段日志。从左到右,三个不同颜色的内容分别为,原始权值,配重系数(越高说明越占比越大,越影响最终结果),经过 nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮点数)。 -|image15| +|image16| 那最终的权值如何计算呢? @@ -217,11 +222,139 @@ nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮 1. 如果有请求req(在nova-api里),可以使用这种 -|image16| +|image17| 2. 其他地方可以使用这种 -|image17| +|image18| + +8.5.9 指定ip时检查allocation_pools +---------------------------------- + +在原生的 neutron 中,当你指定 ip(172.20.22.64) +来创建虚拟机时,假如子网的 allocation_pools 是 172.20.20.100 - +172.20.20.200 ,那 neutron 是不会去检查你指定的ip是否在 allocation_pools +中的。 + +先来看看,port 是如何创建的 + +|image19| + +若要解决这个问题,可以参考原生代码中,在为子网添加allocation_pool时,验证是否合法的的逻辑,代码如下 + +|image20| + +然后在 ``neutron\neutron\db\ipam_pluggable_backend.py`` +文件中添加我们检查 ip是否在 allocation_pools 中的逻辑代码。 + +|image21| + +.. code:: python + + # 代码如下:方便复制 + @staticmethod + def _is_ip_in_allocation_pools(ip_address, allocation_pools): + from neutron.ipam.exceptions import InvalidIpForAllocationPools + + for ap in allocation_pools: + ap_start_ip = netaddr.IPAddress(ap['start']) + ap_end_ip = netaddr.IPAddress(ap['end']) + if ap_start_ip <= ip_address <= ap_end_ip: + return True + raise InvalidIpForAllocationPools(ip_address='ip_address') + + def _validate_allocation_pool_for_fixed_ip(self, subnet, fixed): + ip_address = netaddr.IPAddress(fixed["ip_address"]) + allocation_pools = subnet["allocation_pools"] + return self._is_ip_in_allocation_pools(ip_address, allocation_pools) + +然后还要定义一个异常类型 + +|image22| + +若指定的ip在allocation pool 里,则正常创建,若不在allocation 里,就会在 +nova-compute 日志中报错。 + +|image23| + +可以发现我们的ip 172.20.22.64 并不在子网的allocation +pool,理所当然在nova的日志中可以看到相应的报错。 + +|image24| + +8.5.10 attach port时ip占用提示 +------------------------------ + +当你调用 ``os-interface`` +(指定了ip)接口给一台虚拟机添加一张网卡时,若这个ip已经被使用。 + +nova-api 返回的结果令人无法理解: + +:: + + [{"computeFault": {"message": "Unexpected API Error. Please report this at http://bugs.launchpad.net/nova/ and attach the Nova API log if possible.\n", "code": 500}}]. + +究其原因,是 nova 在调用neutron的api +创建port时,如果ip已被占用,必须neutron会抛出 +IpAddressAlreadyAllocated,而在 neutronclient 只有 IpAddressInUseClient +的异常,并不匹配,在neutronclient 端与neutron 对应的异常应该为 +IpAddressAlreadyAllocatedClient 。 + +|image25| + +如何让nova-api能够返回具体的错误信息呢? + +解决方法有两种, + +一种是,在 neutronclient/common/exceptions.py 里添加 +IpAddressAlreadyAllocatedClient 异常。 + +并且在nova 创建port的代码处,捕获这个异常 + +|image26| + +这种要改两个组件,而且要将neutronclient 的代码也管理起来,较为麻烦 + +一种是,只改neutron,在neutron/ipam/exceptions.py 添加一个与 +neutronclient 相对应的异常。 + +|image27| + +然后修改 neutron/ipam/drivers/neutrondb_ipam/drivers.py 修改异常类型 + +|image28| + +通过 postman 进行模拟,已经可以返回具体的信息 + +|image29| + +另附:neutron 是如何判断ip是否已经占用?代码如下 + +|image30| + +8.5.11 nova的各项服务服务是如何启动的? +--------------------------------------- + +nova 里有不少服务,比如 +nova-compute,nova-api,nova-conductor,nova-scheduler 等。 + +这些服务如何都是如何启动的呢?他们其实都是用同一套代码,所以只要分析一个就行,这里以nova-compute为例来了解一下。 + +从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 +``nova.cmd.compute:main()`` + +|image31| + +从这个入口进去,会开启一个 ``nova-compute`` 的服务。 + +|image32| + +当调用 service.Service.create 时,实际是返回实际化 service.Service +对象。当没有传入 manager 时,就以binary 里的为准。比如binary +是\ ``nova-compute``\ ,那manager_cls 就是 +``compute_manager``\ ,对应的manager 导入路径,就会从配置里读取。 + +|image33| -------------- @@ -229,22 +362,38 @@ nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮 :alt: 关注公众号,获取最新干货! -.. |image0| image:: http://image.python-online.cn/20190508110723.png -.. |image1| image:: http://image.python-online.cn/20190508111109.png -.. |image2| image:: http://image.python-online.cn/20190508095028.png -.. |image3| image:: http://image.python-online.cn/20190508111527.png -.. |image4| image:: http://image.python-online.cn/FhRPy4B1xEI9SfoD2RcunJl15ZE3 -.. |image5| image:: http://image.python-online.cn/FuyMWZS6HF4g3rPwTlLcereZxg4L -.. |image6| image:: http://image.python-online.cn/FnJA8RNIvJN2lAEXbKtJDpOLg1vg -.. |image7| image:: http://image.python-online.cn/FnGyI8jCQFLCGi0pGVmI3SV6pDrv -.. |image8| image:: http://image.python-online.cn/FrbE6oEZ3vtTWwDfMNQ16MGi6SWr -.. |image9| image:: http://image.python-online.cn/20190424212211.png -.. |image10| image:: http://image.python-online.cn/20190424213430.png -.. |image11| image:: http://image.python-online.cn/20190424214653.png -.. |image12| image:: http://image.python-online.cn/20190424214540.png -.. |image13| image:: http://image.python-online.cn/20190424221602.png -.. |image14| image:: http://image.python-online.cn/20190424215735.png -.. |image15| image:: http://image.python-online.cn/20190424220008.png -.. |image16| image:: http://image.python-online.cn/20190426153322.png -.. |image17| image:: http://image.python-online.cn/20190426152148.png +.. |image0| image:: http://image.python-online.cn/20190526144846.png +.. |image1| image:: http://image.python-online.cn/20190508110723.png +.. |image2| image:: http://image.python-online.cn/20190508111109.png +.. |image3| image:: http://image.python-online.cn/20190508095028.png +.. |image4| image:: http://image.python-online.cn/20190508111527.png +.. |image5| image:: http://image.python-online.cn/FhRPy4B1xEI9SfoD2RcunJl15ZE3 +.. |image6| image:: http://image.python-online.cn/FuyMWZS6HF4g3rPwTlLcereZxg4L +.. |image7| image:: http://image.python-online.cn/FnJA8RNIvJN2lAEXbKtJDpOLg1vg +.. |image8| image:: http://image.python-online.cn/FnGyI8jCQFLCGi0pGVmI3SV6pDrv +.. |image9| image:: http://image.python-online.cn/FrbE6oEZ3vtTWwDfMNQ16MGi6SWr +.. |image10| image:: http://image.python-online.cn/20190424212211.png +.. |image11| image:: http://image.python-online.cn/20190424213430.png +.. |image12| image:: http://image.python-online.cn/20190424214653.png +.. |image13| image:: http://image.python-online.cn/20190424214540.png +.. |image14| image:: http://image.python-online.cn/20190424221602.png +.. |image15| image:: http://image.python-online.cn/20190424215735.png +.. |image16| image:: http://image.python-online.cn/20190424220008.png +.. |image17| image:: http://image.python-online.cn/20190426153322.png +.. |image18| image:: http://image.python-online.cn/20190426152148.png +.. |image19| image:: http://image.python-online.cn/20190526141815.png +.. |image20| image:: http://image.python-online.cn/20190526142453.png +.. |image21| image:: http://image.python-online.cn/20190526134519.png +.. |image22| image:: http://image.python-online.cn/20190526141226.png +.. |image23| image:: http://image.python-online.cn/20190526134543.png +.. |image24| image:: http://image.python-online.cn/20190526134618.png +.. |image25| image:: http://image.python-online.cn/20190526140213.png +.. |image26| image:: http://image.python-online.cn/20190526140301.png +.. |image27| image:: http://image.python-online.cn/20190526140315.png +.. |image28| image:: http://image.python-online.cn/20190526140336.png +.. |image29| image:: http://image.python-online.cn/20190526140410.png +.. |image30| image:: http://image.python-online.cn/20190526143235.png +.. |image31| image:: http://image.python-online.cn/20190526205152.png +.. |image32| image:: http://image.python-online.cn/20190526165007.png +.. |image33| image:: http://image.python-online.cn/20190526204328.png diff --git a/source/c08/c08_09.rst b/source/c08/c08_09.rst new file mode 100644 index 0000000..544dd24 --- /dev/null +++ b/source/c08/c08_09.rst @@ -0,0 +1,275 @@ +8.9 rpc 是如何调用的? +====================== + +OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul +API方式,另一种则是RPC调用。两种通信方式的应用场景有所不同,在OpenStack中,前者主要用于各组件之间的通信(如nova与glance的通信),而后者则用于同一组件中各个不同模块之间的通信(如nova组件中nova-compute与nova-scheduler的通信)。 +关于OpenStack中基于RESTFul +API的通信方式主要是应用了WSGI,我会在以后的博文中详细讨论。这里主要还是分享一下关于RPC调用的一些理解。 + +首先,什么是RPC呢?百度百科给出的解释是这样的:“RPC(Remote Procedure +Call +Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议”。这个概念听起来还是比较抽象,下面我会结合OpenStack中RPC调用的具体应用来具体分析一下这个协议。 + +8.9.1 为什么要使用 RPC? +------------------------ + +其次,为什么要采用RPC呢?单纯的依靠RESTFul +API不可以吗?其实原因有下面这几个: + +1. 由于RESTFul + API是基于HTTP协议的,因此客户端与服务端之间所能传输的消息仅限于文本 +2. RESTFul + API客户端与服务端之间采用的是同步机制,当发送HTTP请求时,客户端需要等待服务端的响应。当然对于这一点是可以通过一些技术来实现异步的机制的 +3. 采用RESTFul + API,客户端与服务端之间虽然可以独立开发,但还是存在耦合。比如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常工作 + +基于上面这几个原因,所以OpenStack才采用了另一种远程通信机制,这就是我们今天要讨论的鼎鼎大名的RPC。 + +今天的话题,就是源码解读OpenStack是如何通过rpc进行远程调用的。 + +谈到 OpenStack 的 rpc,就不得不提到 oslo_messaging 这个 OpenStack +专用工具库,oslo_messaging只是对多种transport做了进一步封装,底层也是用到了kombu这个AMQP库,当transport是amqp时,kombu会进行环境监测判断是否安装了librabbitmq,如果安装了就使用librabbitmq,没有则使用pyamqp;因为librabbitmq是使用C编写的库,所以比pyamqp速度快很多。 + +关于oslo_messaging库,主要提供了两种独立的API: + +1. oslo.messaging.rpc(实现了客户端-服务器远程过程调用) +2. oslo.messaging.notify(实现了事件的通知机制) + +8.9.2 如何实现 rpc 远程调用 +--------------------------- + +**先来讲解第一个 rpc 的远程调用。** + +要了解 rpc 的调用机制呢,首先要知道 oslo_messaging 的几个概念 + +- transport:RPC功能的底层实现方法,这里是rabbitmq的消息队列的访问路径 + + transport 就是定义你如何访连接消息中间件,比如你使用的是 + Rabbitmq,那在nova.conf中应该有一行\ ``transport_url``\ 的配置,可以很清楚地看出指定了 + rabbitmq + 为消息中间件,并配置了连接rabbitmq的user,passwd,主机,端口。 + + .. code:: python + + transport_url=rabbit://user:passwd@ctrl.openstack.com:5672 + + |image0| + + .. code:: python + + def get_transport(conf, url=None, allowed_remote_exmods=None): + return _get_transport(conf, url, allowed_remote_exmods, + transport_cls=RPCTransport) + + transport 的 driver 有如下几种: + + 1. amqp + :\ https://docs.openstack.org/developer/oslo.messaging/AMQP1.0.html + 2. fake :测试使用,该driver将消息发送到内存中。 + 3. kafka :experimental + 4. kombu :RabbitMQ Driver。openstack默认的。 + 5. pika :successor to the existing rabbit/kombu driver。 + https://docs.openstack.org/developer/oslo.messaging/pika_driver.html + 6. rabbit :RabbitMQ Driver + 7. zmq :通过ZeroMQ 实现了RPC和Notifer + API.详见:\ https://docs.openstack.org/developer/oslo.messaging/zmq_driver.html + +- target:指定RPC topic交换机的匹配信息和绑定主机 + + target用来表述 RPC + 服务器监听topic,server名称和server监听的exchange,是否广播fanout + + .. code:: python + + class Target(object): + def __init__(self, exchange=None, topic=None, namespace=None, + version=None, server=None, fanout=None, + legacy_namespaces=None): + self.exchange = exchange + self.topic = topic + self.namespace = namespace + self.version = version + self.server = server + self.fanout = fanout + self.accepted_namespaces = [namespace] + (legacy_namespaces or []) + + rpc server 要获取消息,需要定义target,就像一个门牌号一样。 + + |image1| + + rpc client 要发送消息,也需要有target,说明消息要发到哪去。 + + |image2| + +- endpoints:是可供别人远程调用对象 + + RPC服务器暴露出endpoint,每个 endpoint 包涵一系列的可被远程客户端通过 + transport 调用的方法。直观理解,可以参考nova-conductor创建rpc + server的代码,这边的endpoints就是 + nova/manager.py:ConductorManager()\ |image3| + +- dispatcher:分发器,这是 rpc server 才有的概念 + |image4|\ 只有它才知道server端接收到的哪些rpc调用是我要处理的,并且知道远方client端是我调用我的哪个方法? + + 在client端,是这样指定要调用哪个方法的。 + + |image5| + + 而在server端,是如何知道要执行这个方法的呢?这就是dispatcher + 要干的事,它从 endpoint 里找到这个方法,然后执行,最后返回。 + + |image6| + +- Serializer:在 python 对象和message(notification) + 之间数据做序列化或是反序列化的基类。 + + 主要方法有四个: + + 1. deserialize_context(ctxt) :对字典变成 request contenxt. + 2. deserialize_entity(ctxt, entity) + :对entity做反序列化,其中ctxt是已经deserialize过的,entity是要处理的。 + 3. serialize_context(ctxt) :将Request context变成字典类型 + 4. serialize_entity(ctxt, entity) + :对entity做序列化,其中ctxt是已经deserialize过的,entity是要处理的。 + +- executor:服务的运行方式,单线程或者多线程 + + 每个notification + listener都和一个executor绑定,来控制收到的notification如何分配。默认情况下,使用的是blocking + executor(具体特性参加executor一节) + + .. code:: python + + oslo_messaging.get_notification_listener(transport, targets, endpoints, executor=’blocking’, serializer=None, allow_requeue=False, pool=None) + +rpc server 和rpc client 的四个重要方法 + +1. ``reset()``\ :Reset service. +2. ``start()``\ :该方法调用后,server开始poll,从transport中接收message,然后转发给dispatcher.该message处理过程一直进行,直到stop方法被调用。executor决定server的IO处理策略。可能会是用一个新进程、新协程来做poll操作,或是直接简单的在一个循环中注册一个回调。同样,executor也决定分配message的方式,是在一个新线程中dispatch或是….. + \* +3. ``stop()``:当调用stop之后,新的message不会被处理。但是,server可能还在处理一些之前没有处理完的message,并且底层driver资源也还一直没有释放。 +4. ``wait()``\ :在stop调用之后,可能还有message正在被处理,使用wait方法来阻塞当前进程,直到所有的message都处理完成。之后,底层的driver资源会释放。 + +**简单的 rpc client** + +.. code:: python + + import oslo_messaging + from oslo_config import cfg + + transport = messaging.get_transport(cfg.CONF) + target = messaging.Target(topic='test', version='2.0') + client = messaging.RPCClient(transport, target) + client.call(ctxt, 'test', arg=arg) + +**简单的rpc server** + +.. code:: python + + from oslo_config import cfg + import oslo_messaging + import time + + # 定义endpoint类 + class ServerControlEndpoint(object): + target = oslo_messaging.Target(namespace='control', + version='2.0') + + def __init__(self, server): + self.server = server + + def stop(self, ctx): + if self.server: + self.server.stop() + + + class TestEndpoint(object): + + def test(self, ctx, arg): + return arg + + + # 创建rpc server + transport = oslo_messaging.get_transport(cfg.CONF) + target = oslo_messaging.Target(topic='test', server='server1') + endpoints = [ + ServerControlEndpoint(None), + TestEndpoint(), + ] + server = oslo_messaging.get_rpc_server(transport, target, endpoints, + executor='blocking') + try: + server.start() + while True: + time.sleep(1) + except KeyboardInterrupt: + print("Stopping server") + + server.stop() + server.wait() + +8.5.3 如何实现 rpc 事件通知 +--------------------------- + +说完了 rpc 调用,\ **再来了解它的事件通知机制**\ ,这个比较简单。 + +如果你不想用现成的 +``notification_event_types``\ ,而想新定义一个,可以这样做 + +首先在这里先定义合法的 +``notification_event_types``\ ,相当于添加白名单。 + +|image7| + +然后在调用处,使用 ``rpc.get_notifier`` 来发送消息给ceilometer。 + +|image8| + +继续查看 ``rpc.get_notifier`` 做了什么事?如何实现直接info +就能发送消息的。 + +|image9| + +当你使用的event_types 不在白名单内,或者是异常信息。就会给打印warn日志 + +|image10| + +在rabbit里查看队列,notification 是 topic + +|image11| + +而 debug ,info 等是event priority + +|image12| + +参考文章: + +- `OpenStack之RPC调用(一) `__ +- `openstack oslo_messaging + 译文 `__ +- `模仿OpenStack写自己的RPC `__ +- `python 64式: 第1式 + 编写rpc的call和cast `__ +- `Openstack RPC + 通信原理 `__ + +-------------- + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + + +.. |image0| image:: http://image.python-online.cn/20190526182125.png +.. |image1| image:: http://image.python-online.cn/20190526184854.png +.. |image2| image:: http://image.python-online.cn/20190526185217.png +.. |image3| image:: http://image.python-online.cn/20190526221219.png +.. |image4| image:: http://image.python-online.cn/20190526220809.png +.. |image5| image:: http://image.python-online.cn/20190527220820.png +.. |image6| image:: http://image.python-online.cn/20190527220012.png +.. |image7| image:: http://image.python-online.cn/20190526172514.png +.. |image8| image:: http://image.python-online.cn/20190526172725.png +.. |image9| image:: http://image.python-online.cn/20190526173314.png +.. |image10| image:: http://image.python-online.cn/20190526175100.png +.. |image11| image:: http://image.python-online.cn/20190526180708.png +.. |image12| image:: http://image.python-online.cn/20190526181433.png + From d9628fb719e8ff4ef891197b2b94fe8e7b089392 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 27 May 2019 23:57:28 +0800 Subject: [PATCH 020/302] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=A0=87=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_09.md | 6 +++--- source/c08/c08_09.rst | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/c08/c08_09.md b/source/c08/c08_09.md index 47ef912..0998e82 100644 --- a/source/c08/c08_09.md +++ b/source/c08/c08_09.md @@ -1,4 +1,4 @@ -# 8.9 rpc 是如何调用的? +# 8.9 OpenStack中的rpc通信机制 OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul API方式,另一种则是RPC调用。两种通信方式的应用场景有所不同,在OpenStack中,前者主要用于各组件之间的通信(如nova与glance的通信),而后者则用于同一组件中各个不同模块之间的通信(如nova组件中nova-compute与nova-scheduler的通信)。 关于OpenStack中基于RESTFul API的通信方式主要是应用了WSGI,我会在以后的博文中详细讨论。这里主要还是分享一下关于RPC调用的一些理解。 @@ -84,9 +84,9 @@ OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul API方 ![](http://image.python-online.cn/20190526185217.png) -- endpoints:是可供别人远程调用对象 +- endpoints:是可供别人远程调用的对象 - RPC服务器暴露出endpoint,每个 endpoint 包涵一系列的可被远程客户端通过 transport 调用的方法。直观理解,可以参考nova-conductor创建rpc server的代码,这边的endpoints就是 nova/manager.py:ConductorManager()![](http://image.python-online.cn/20190526221219.png) + RPC服务器暴露出endpoint,每个 endpoint 包涵一系列的可被远程客户端通过 transport 调用的方法。直观理解,可以参考nova-conductor创建rpc server的代码,这边的endpoints就是 `nova/manager.py:ConductorManager()`![](http://image.python-online.cn/20190526221219.png) diff --git a/source/c08/c08_09.rst b/source/c08/c08_09.rst index 544dd24..2129d4e 100644 --- a/source/c08/c08_09.rst +++ b/source/c08/c08_09.rst @@ -1,5 +1,5 @@ -8.9 rpc 是如何调用的? -====================== +8.9 OpenStack中的rpc通信机制 +============================ OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul API方式,另一种则是RPC调用。两种通信方式的应用场景有所不同,在OpenStack中,前者主要用于各组件之间的通信(如nova与glance的通信),而后者则用于同一组件中各个不同模块之间的通信(如nova组件中nova-compute与nova-scheduler的通信)。 @@ -101,12 +101,12 @@ API不可以吗?其实原因有下面这几个: |image2| -- endpoints:是可供别人远程调用对象 +- endpoints:是可供别人远程调用的对象 RPC服务器暴露出endpoint,每个 endpoint 包涵一系列的可被远程客户端通过 transport 调用的方法。直观理解,可以参考nova-conductor创建rpc server的代码,这边的endpoints就是 - nova/manager.py:ConductorManager()\ |image3| + ``nova/manager.py:ConductorManager()``\ |image3| - dispatcher:分发器,这是 rpc server 才有的概念 |image4|\ 只有它才知道server端接收到的哪些rpc调用是我要处理的,并且知道远方client端是我调用我的哪个方法? From a3d4dfdb6f0b22334529891e73fc8f250cbb64d7 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 28 May 2019 10:32:10 +0800 Subject: [PATCH 021/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=A3=85=E9=A5=B0?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c03/c03_01.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/source/c03/c03_01.md b/source/c03/c03_01.md index f88b528..90846ef 100644 --- a/source/c03/c03_01.md +++ b/source/c03/c03_01.md @@ -22,7 +22,6 @@ ## 3.1.2 入门用法:日志打印器 首先是**日志打印器**。 - 实现的功能: - 在函数执行前,先打印一行日志告知一下主人,我要执行函数了。 - 在函数执行完,也不能拍拍屁股就走人了,咱可是有礼貌的代码,再打印一行日志告知下主人,我执行完啦。 @@ -85,9 +84,9 @@ def want_sleep(sleep_time): want_sleep(10) ``` -来看看,输出。真的是10秒耶。真历害!!! +来看看,输出。真的是2秒. ``` -花费时间:10.0073800086975098秒 +花费时间:2.0073800086975098秒 ``` @@ -109,10 +108,10 @@ want_sleep(10) 我们要在这两个函数的执行的时候,分别根据其国籍,来说出一段打招呼的话。 ```python -def american(): +def chinese(): print("我来自中国。") -def chinese(): +def american(): print("I am from America.") ``` 在给他们俩戴上装饰器的时候,就要跟装饰器说,这个人是哪国人,然后装饰器就会做出判断,打出对应的招呼。 @@ -120,11 +119,11 @@ def chinese(): 戴上帽子后,是这样的。 ```python @say_hello("china") -def american(): +def chinese(): print("我来自中国。") @say_hello("america") -def chinese(): +def american(): print("I am from America.") ``` @@ -327,7 +326,7 @@ class User: ## 3.1.9 wrapper 装饰器有啥用? -在 functools 标准库中有提供一个 wrapper 装饰器,你应该也经常见过,那他有啥用呢? +在 functools 标准库中有提供一个 wraps 装饰器,你应该也经常见过,那他有啥用呢? 先来看一个例子 @@ -362,7 +361,7 @@ print(wrapper(wrapped).__name__) #inner_function ``` -那如何避免这种情况的产生?方法是使用 functools .wrapper 装饰器,它的作用就是将 **被修饰的函数(wrapped)** 的一些属性值赋值给 **修饰器函数(wrapper)** ,最终让属性的显示更符合我们的直觉。 +那如何避免这种情况的产生?方法是使用 functools .wraps 装饰器,它的作用就是将 **被修饰的函数(wrapped)** 的一些属性值赋值给 **修饰器函数(wrapper)** ,最终让属性的显示更符合我们的直觉。 ```python from functools import wraps From 4ad542ea285810f6642ce3c05766a594b02a235e Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 28 May 2019 22:45:55 +0800 Subject: [PATCH 022/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=A3=85=E9=A5=B0?= =?UTF-8?q?=E5=99=A8=E4=B8=8E=E5=8D=95=E4=BE=8B=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c03/c03_01.md | 23 ++++++++++------- source/c03/c03_01.rst | 36 ++++++++++++++------------- source/c04/c04_17.md | 50 ++++++++++++++++++------------------- source/c04/c04_17.rst | 57 +++++++++++++++++++++---------------------- 4 files changed, 86 insertions(+), 80 deletions(-) diff --git a/source/c03/c03_01.md b/source/c03/c03_01.md index 90846ef..477e400 100644 --- a/source/c03/c03_01.md +++ b/source/c03/c03_01.md @@ -324,8 +324,7 @@ class User: ![](http://image.python-online.cn/20190512113917.png) -## 3.1.9 wrapper 装饰器有啥用? - +## 3.1.9 wraps 装饰器有啥用? 在 functools 标准库中有提供一个 wraps 装饰器,你应该也经常见过,那他有啥用呢? 先来看一个例子 @@ -342,6 +341,7 @@ def wrapped(): print(wrapped.__name__) #inner_function + ``` 为什么会这样子?不是应该返回 `func ` 吗? @@ -359,17 +359,22 @@ def wrapped(): print(wrapper(wrapped).__name__) #inner_function + ``` 那如何避免这种情况的产生?方法是使用 functools .wraps 装饰器,它的作用就是将 **被修饰的函数(wrapped)** 的一些属性值赋值给 **修饰器函数(wrapper)** ,最终让属性的显示更符合我们的直觉。 ```python -from functools import wraps +from functools import update_wrapper + +WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', + '__annotations__') def wrapper(func): - @wraps(func) def inner_function(): pass + + update_wrapper(inner_function, func, assigned=WRAPPER_ASSIGNMENTS) return inner_function @wrapper @@ -377,10 +382,9 @@ def wrapped(): pass print(wrapped.__name__) -# wrapped ``` -准确点说,wrapper 其实是一个偏函数对象(partial),源码如下 +准确点说,wraps 其实是一个偏函数对象(partial),源码如下 ```python def wraps(wrapped, @@ -388,6 +392,7 @@ def wraps(wrapped, updated = WRAPPER_UPDATES): return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated) + ``` 可以看到wraps其实就是调用了一个函数` update_wrapper`,知道原理后,我们改写上面的代码,在不使用 wraps的情况下,也可以让 `wrapped.__name__` 打印出 wrapped,代码如下: @@ -401,14 +406,14 @@ def wrapper(func): update_wrapper(func, inner_function) return inner_function +@wrapper def wrapped(): pass print(wrapped.__name__) # wrapped -``` - +``` ## 3.1.10 内置装饰器:property @@ -669,4 +674,4 @@ def timeout_limit(timeout_time): ``` ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c03/c03_01.rst b/source/c03/c03_01.rst index d4edeca..286b78f 100755 --- a/source/c03/c03_01.rst +++ b/source/c03/c03_01.rst @@ -23,9 +23,7 @@ 3.1.2 入门用法:日志打印器 -------------------------- -首先是\ **日志打印器**\ 。 - -实现的功能: - +首先是\ **日志打印器**\ 。 实现的功能: - 在函数执行前,先打印一行日志告知一下主人,我要执行函数了。 - 在函数执行完,也不能拍拍屁股就走人了,咱可是有礼貌的代码,再打印一行日志告知下主人,我执行完啦。 @@ -96,11 +94,11 @@ want_sleep(10) -来看看,输出。真的是10秒耶。真历害!!! +来看看,输出。真的是2秒. :: - 花费时间:10.0073800086975098秒 + 花费时间:2.0073800086975098秒 3.1.4 进阶用法:带参数的函数装饰器 ---------------------------------- @@ -123,10 +121,10 @@ .. code:: python - def american(): + def chinese(): print("我来自中国。") - def chinese(): + def american(): print("I am from America.") 在给他们俩戴上装饰器的时候,就要跟装饰器说,这个人是哪国人,然后装饰器就会做出判断,打出对应的招呼。 @@ -136,11 +134,11 @@ .. code:: python @say_hello("china") - def american(): + def chinese(): print("我来自中国。") @say_hello("america") - def chinese(): + def american(): print("I am from America.") 万事俱备,只差帽子了。来定义一下,这里需要两层嵌套。 @@ -362,10 +360,10 @@ Python工匠:使用装饰器的小技巧) |image0| -3.1.9 wrapper 装饰器有啥用? ----------------------------- +3.1.9 wraps 装饰器有啥用? +-------------------------- -在 functools 标准库中有提供一个 wrapper +在 functools 标准库中有提供一个 wraps 装饰器,你应该也经常见过,那他有啥用呢? 先来看一个例子 @@ -404,18 +402,22 @@ Python工匠:使用装饰器的小技巧) print(wrapper(wrapped).__name__) #inner_function -那如何避免这种情况的产生?方法是使用 functools .wrapper +那如何避免这种情况的产生?方法是使用 functools .wraps 装饰器,它的作用就是将 **被修饰的函数(wrapped)** 的一些属性值赋值给 **修饰器函数(wrapper)** ,最终让属性的显示更符合我们的直觉。 .. code:: python - from functools import wraps + from functools import update_wrapper + + WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', + '__annotations__') def wrapper(func): - @wraps(func) def inner_function(): pass + + update_wrapper(inner_function, func, assigned=WRAPPER_ASSIGNMENTS) return inner_function @wrapper @@ -423,9 +425,8 @@ Python工匠:使用装饰器的小技巧) pass print(wrapped.__name__) - # wrapped -准确点说,wrapper 其实是一个偏函数对象(partial),源码如下 +准确点说,wraps 其实是一个偏函数对象(partial),源码如下 .. code:: python @@ -448,6 +449,7 @@ wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码 update_wrapper(func, inner_function) return inner_function + @wrapper def wrapped(): pass diff --git a/source/c04/c04_17.md b/source/c04/c04_17.md index fd0b768..ae968c3 100644 --- a/source/c04/c04_17.md +++ b/source/c04/c04_17.md @@ -231,39 +231,23 @@ class BestPromo(Promotion): ## 4.17.2 单例模式 -单例(Singleton)模式,应该是设计模式里面最好理解的一个模式。 +之前在另一篇公众号文章看到一个挺搞笑的例子(原文在文末): -使用它,就是为了保证全局环境下只能有一个该类的实例。 +大意是讲,老婆在中国其实就是一个活生生的单例,你要娶一个老婆需要去民政局注册登记(要对类进行实例化),当你想再娶一个老婆时,这时民政局会说,不行,你已经有一个老婆了,并且告诉你老婆是谁。 -它的目的,我这里将其分为两类: +玩笑之后,再回到我们的话题,先举几类我们经常见到的例子: -一类是**必须这样做** +***1、***大家在解释单例模式时,经常要提到的一个例子是 Windows 的任务管理器。如果我们打开多个任务管理器窗口。显示的内容完全一致,如果在内部是两个一模一样的对象,那就是重复对象,就造成了内存的浪费;相反,如果两个窗口的内容不一致,那就会至少有一个窗口展示的内容是错误的,会给用户造成误解,到底哪个才是当前真实的状态呢? -大家在解释单例模式时,经常要提到的一个例子是 Windows 的任务管理器。如果我们打开多个任务管理器窗口。显示的内容完全一致,如果在内部是两个一模一样的对象,那就是重复对象,就造成了内存的浪费;相反,如果两个窗口的内容不一致,那就会至少有一个窗口展示的内容是错误的,会给用户造成误解,到底哪个才是当前真实的状态呢? +***2、***一个项目中多个地方需要读取同一份配置文件,如果每次使用都是导入重新创建实例,读取文件,用完后再销毁,这样做的话,就造成不必要的IO浪费,可以使用单例模式只生成一份配置在内存中。 -还有一个常见的例子是,一个网站的访问量、在线人数,在项目中是全局唯一(不考虑分布式),在这种情况下,使用单例模式是一种很好的方式。 - -而另一类是**最好这样做** - -比如,一个项目中多个地方需要读取同一份配置文件,如果每次使用都是导入重新创建实例,读取文件,用完后再销毁,这样做的话,就造成不必要的IO浪费,可以使用单例模式只生成一份配置在内存中。 +***3、***还有一个常见的例子是,一个网站的访问量、在线人数,在项目中是全局唯一(不考虑分布式),在这种情况下,使用单例模式是一种很好的方式。 从上面看来,在系统中确保某个对象的唯一性即一个类只能有一个实例有时是非常重要的。 -总结一下,单例模式有如下优点 - -1、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用; -2、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间; -3、单例可长驻内存,减少系统开销。 +按照惯例,我们先来用代码实践一下,看看如何用 Python 写单例模式。 -和其他设计模式一样,单例模式有一定的适用场景,但同时它也会给我们带来一些问题。 - -1、由于单例对象是全局共享,所以其状态维护需要特别小心。 -2、单例对象没有抽象层,扩展不便。 -3、赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到); -4、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试; -5、单例模式在某种情况下会导致“资源瓶颈”。 -理论说完了,再来说说如何用 Python 写单例模式。 这里介绍了三个较为常用的。 @@ -384,7 +368,7 @@ for i in range(10): <__main__.User object at 0x105605940> ``` -这在 Java 中,是可以使用饿汉模式(在实例化之前就将单例对象创建出来)来避免这个问题,但在 Python 中,好像没法实现这样的饿汉模式,但是同样有解决方法,就是加锁。 +这在 Java 中,是可以使用饿汉模式来避免这个问题,在 Python 中我想到的办法是**加锁**。 首先实现一个给函数加锁的装饰器 @@ -444,9 +428,25 @@ for i in range(10): <__main__.User object at 0x10ff503c8> ``` +学会写只是第一步,还有一点,相当重要,要知道为何会有这个设计模式,它有什么优势,有什么局限性? + +总结一下,单例模式有如下优点: +1. 全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用; +2. 由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间; +3. 单例可长驻内存,减少系统开销。 + +和其他设计模式一样,单例模式有一定的适用场景,但同时它也会给我们带来一些问题。 + +1. 由于单例对象是全局共享,所以其状态维护需要特别小心。一处修改,全局都会受到影响。 +2. 单例对象没有抽象层,扩展不便。 +3. 赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到); +4. 单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试; +5. 单例模式在某种情况下会导致“资源瓶颈”。 + +--- -引用文章 +参考文章 - [Python与设计模式--单例模式](https://yq.aliyun.com/articles/70418?utm_content=m_14908#comment) - [python设计模式 - 单例模式之饿汉懒汉](https://www.jianshu.com/p/73901db378dc) diff --git a/source/c04/c04_17.rst b/source/c04/c04_17.rst index f3dc492..e42f5ff 100644 --- a/source/c04/c04_17.rst +++ b/source/c04/c04_17.rst @@ -238,40 +238,22 @@ Order 4.17.2 单例模式 --------------- -单例(Singleton)模式,应该是设计模式里面最好理解的一个模式。 +之前在另一篇公众号文章看到一个挺搞笑的例子(原文在文末): -使用它,就是为了保证全局环境下只能有一个该类的实例。 +大意是讲,老婆在中国其实就是一个活生生的单例,你要娶一个老婆需要去民政局注册登记(要对类进行实例化),当你想再娶一个老婆时,这时民政局会说,不行,你已经有一个老婆了,并且告诉你老婆是谁。 -它的目的,我这里将其分为两类: +玩笑之后,再回到我们的话题,先举几类我们经常见到的例子: -一类是\ **必须这样做** - -大家在解释单例模式时,经常要提到的一个例子是 Windows +**1、**\ 大家在解释单例模式时,经常要提到的一个例子是 Windows 的任务管理器。如果我们打开多个任务管理器窗口。显示的内容完全一致,如果在内部是两个一模一样的对象,那就是重复对象,就造成了内存的浪费;相反,如果两个窗口的内容不一致,那就会至少有一个窗口展示的内容是错误的,会给用户造成误解,到底哪个才是当前真实的状态呢? -还有一个常见的例子是,一个网站的访问量、在线人数,在项目中是全局唯一(不考虑分布式),在这种情况下,使用单例模式是一种很好的方式。 - -而另一类是\ **最好这样做** +**2、**\ 一个项目中多个地方需要读取同一份配置文件,如果每次使用都是导入重新创建实例,读取文件,用完后再销毁,这样做的话,就造成不必要的IO浪费,可以使用单例模式只生成一份配置在内存中。 -比如,一个项目中多个地方需要读取同一份配置文件,如果每次使用都是导入重新创建实例,读取文件,用完后再销毁,这样做的话,就造成不必要的IO浪费,可以使用单例模式只生成一份配置在内存中。 +**3、**\ 还有一个常见的例子是,一个网站的访问量、在线人数,在项目中是全局唯一(不考虑分布式),在这种情况下,使用单例模式是一种很好的方式。 从上面看来,在系统中确保某个对象的唯一性即一个类只能有一个实例有时是非常重要的。 -总结一下,单例模式有如下优点 - -1、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用; -2、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间; -3、单例可长驻内存,减少系统开销。 - -和其他设计模式一样,单例模式有一定的适用场景,但同时它也会给我们带来一些问题。 - -1、由于单例对象是全局共享,所以其状态维护需要特别小心。 -2、单例对象没有抽象层,扩展不便。 -3、赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到); -4、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试; -5、单例模式在某种情况下会导致“资源瓶颈”。 - -理论说完了,再来说说如何用 Python 写单例模式。 +按照惯例,我们先来用代码实践一下,看看如何用 Python 写单例模式。 这里介绍了三个较为常用的。 @@ -392,9 +374,8 @@ Order <__main__.User object at 0x1055afcf8> <__main__.User object at 0x105605940> -这在 Java -中,是可以使用饿汉模式(在实例化之前就将单例对象创建出来)来避免这个问题,但在 -Python 中,好像没法实现这样的饿汉模式,但是同样有解决方法,就是加锁。 +这在 Java 中,是可以使用饿汉模式来避免这个问题,在 Python +中我想到的办法是\ **加锁**\ 。 首先实现一个给函数加锁的装饰器 @@ -454,7 +435,25 @@ Python 中,好像没法实现这样的饿汉模式,但是同样有解决方 <__main__.User object at 0x10ff503c8> <__main__.User object at 0x10ff503c8> -引用文章 +学会写只是第一步,还有一点,相当重要,要知道为何会有这个设计模式,它有什么优势,有什么局限性? + +总结一下,单例模式有如下优点: + +1. 全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用; +2. 由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间; +3. 单例可长驻内存,减少系统开销。 + +和其他设计模式一样,单例模式有一定的适用场景,但同时它也会给我们带来一些问题。 + +1. 由于单例对象是全局共享,所以其状态维护需要特别小心。一处修改,全局都会受到影响。 +2. 单例对象没有抽象层,扩展不便。 +3. 赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到); +4. 单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试; +5. 单例模式在某种情况下会导致“资源瓶颈”。 + +-------------- + +参考文章 - `Python与设计模式–单例模式 `__ - `python设计模式 - From 8f848298c119b78a077980cf83a07dcf4e0ff7f5 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Wed, 29 May 2019 22:02:59 +0800 Subject: [PATCH 023/302] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0openstack=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_02.md | 63 +++++++++++++++++++++++++++-------- source/c08/c08_05.md | 14 ++++++++ source/c08/c08_07.md | 48 +++++++++++++++++++++------ source/c08/c08_09.md | 6 ++++ source/c08/c08_10.md | 78 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 23 deletions(-) create mode 100644 source/c08/c08_10.md diff --git a/source/c08/c08_02.md b/source/c08/c08_02.md index ef5a366..4955d5e 100644 --- a/source/c08/c08_02.md +++ b/source/c08/c08_02.md @@ -6,15 +6,15 @@ SR-IOV 规范定义了新的标准,根据该标准,创建的新设备可允许将虚拟机直接连接到 I/O 设备。 -## 一、基础环境准备 +## 8.2.1 基础环境准备 --- -### 1.1 bios开启vt-d, sriov +### 8.2.1.1 bios开启vt-d, sriov ![](https://i.loli.net/2018/01/19/5a61bfa0ca66f.png) ![](https://i.loli.net/2018/01/19/5a61bfd243111.png) -### 1.2 开启iommu +### 8.2.1.2 开启iommu vim /etc/sysconfig/grub(或者/etc/default/grub):intel_iommu=on ![](https://i.loli.net/2018/01/19/5a61c022d68d3.png) @@ -23,14 +23,14 @@ vim /etc/sysconfig/grub(或者/etc/default/grub):intel_iommu=on grub2-mkconfig -o /etc/grub2.cfg ``` -### 1.3 开启igb模块 +### 8.2.1.3 开启igb模块 ``` modprobe igb max_vfs=32 # 开启模块 echo "options igb max_vfs=32" >>/etc/modprobe.d/igb.conf # 持久化 ``` -### 1.4 配置VF数量 +### 8.2.1.4 配置VF数量 ``` echo "echo '32' > /sys/class/net/eth0/device/sriov_numvfs" >> /etc/rc.local @@ -39,7 +39,7 @@ echo "echo '32' > /sys/class/net/eth0/device/sriov_numvfs" >> /etc/rc.local lcpci |grep Ethernet ``` -### 1.5 验证基础环境 +### 8.2.1.5 验证基础环境 尝试将VF分配给虚拟机 @@ -61,11 +61,11 @@ lcpci |grep Ethernet 如果以上可以顺利运行,就说明基础环境已经配置好。 -## 二、计算节点 +## 8.2.2 计算节点 --- -### 2.1 nova-compute +### 8.2.2.1 nova-compute 指定白名单,告诉OpenStack,允许的直通网卡是eth0所有的VF都来自于这张网卡,并且说明这个网络归属于physnet2 @@ -98,11 +98,11 @@ service openstack-nova-compute restart 查看数据库,compute_nodes,确认VF是否有被管理起来 ![](https://i.loli.net/2018/01/19/5a61c1cf51b58.png) -## 三、控制节点 +## 8.2.3 控制节点 --- -### 3.1 neutron-server +### 8.2.3.1 neutron-server vim /etc/neutron/plugins/ml2/ml2_conf.ini ![](https://i.loli.net/2018/01/19/5a61c1faac447.png) @@ -127,7 +127,7 @@ vim /usr/lib/systemd/system/neutron-server.service systemctl restart neutron-server ``` -### 3.2 nova-scheduler +### 8.2.3.2 nova-scheduler 添加filter ``` @@ -140,7 +140,7 @@ scheduler_available_filters = nova.scheduler.filters.all_filters systemctl restart openstack-nova-scheduler ``` -### 3.3 sriov-agent +### 8.2.3.3 sriov-agent 安装sriov-agent ``` @@ -185,7 +185,7 @@ systemctl enable neutron-sriov-nic-agent.service systemctl restart neutron-sriov-nic-agent.service ``` -### 3.4 验证SRIOV网络 +### 8.2.3.4 验证SRIOV网络 验证步骤 ``` @@ -214,6 +214,43 @@ nova boot --flavor [flavor_id] --image [image_id] --nic port-id=$port_id [sriov_ 4. 给虚拟机分配公网IP,可以访问外网,DNS正常 ``` +## 8.2.4 OpenStack 改造 + +### 8.2.4.1 支持挂卸网卡 + +Sriov虚拟机在openstack原生是不支持挂卸网卡操作的,即nova interface-attach不适用于sriov port。 + +为了实现挂载的操作,需要加几步手工操作,具体步骤如下: + +1、使用neutron port-show ,查看并记录原 port 的 `binding:profile` 信息,如果有多个port,把每个port的信息都记录下来。 + +![](http://image.python-online.cn/20190529202132.png) + +2、nova interface-detach卸载原来的port。 + +3、neutron port-create --vnic-type direct 。。。。。 创建新的sriov port + +4、更新新port的binding:profile,neutron port-update命令不支持,只能使用curl,需要修改port-id及binding:profile + +``` +source ~/admin-openrc; TOKEN=`openstack token issue |grep " id " |awk -F \| '{print $3}'` +curl -g -i -X PUT http://{vip}:port/v2.0/ports/3f0668f4-4b5a-4e11-93d4-10b7958668ae.json -H "User-Agent: python-neutronclient" -H "Content-Type: application/json" -H "Accept: application/json" -H "X-Auth-Token: $TOKEN" -d '{"port": {"binding:profile": {"pci_slot": "0000:02:13.6","physical_network": "phynet1", "pci_vendor_info": "8086:10ed"}}}' +``` + +5、挂载网卡 + +```shell +nova interface-attach 5a1c1828-4190-43fa-8e05-ae51b0196656 --port-id 3f0668f4-4b5a-4e11-93d4-10b7958668ae +``` + +如果port已经卸载,找不到pci_slot信息,可以在数据库中查找:登陆controller,登陆mysql数据库,2个port分别用2个address即可 + +``` +select * from pci_devices where instance_uuid='5a1c1828-4190-43fa-8e05-ae51b0196656'; +``` + +![](http://image.python-online.cn/20190529202440.png) + ## 附录:参考文档 --- diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 74576d3..d6ccbc2 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -10,6 +10,10 @@ 这个函数,只有在 `spawn` 、`_hard_reboot` 和 `finish_migration` 才会执行。 +通过libvirt的接口创建虚拟机 + +![](http://image.python-online.cn/20190529135942.png) + ## 8.5.3 创建快照代码解读? @@ -307,7 +311,17 @@ nova 里有不少服务,比如 nova-compute,nova-api,nova-conductor,nova +## 8.5.12 支持指定子网和指定ip + +在 nova-api 接收请求处。 + +![](http://image.python-online.cn/20190529203441.png) + +![](http://image.python-online.cn/20190529215953.png) + +对 network_info 进行解析,然后塞给 request 对象返回。 +![](http://image.python-online.cn/20190529215825.png) --- diff --git a/source/c08/c08_07.md b/source/c08/c08_07.md index b199967..fdb71a0 100644 --- a/source/c08/c08_07.md +++ b/source/c08/c08_07.md @@ -22,6 +22,14 @@ 修改内核,开启直通,在`/etc/default/grub` 文件里 `GRUB_CMDLINE_LINUX` 行,添加 `intel_iommu=on` +然后重启宿主机。 + +## 8.7.2 功能验证 + +### 8.7.2.1 Driver 变化 + +**下面将观察创建虚拟机前后 driver 的变化** + 未创建虚拟机 ![](http://image.python-online.cn/20190422201117.png) @@ -34,11 +42,13 @@ ![](http://image.python-online.cn/20190422201117.png) -在P版之前,创建gpu直通的虚拟机,在虚拟机内部能NIVIDIA显卡的驱动是能检测到自己是跑在虚拟机里的(如下图,先安装 `cpuid` 再执行`cpuid |grep hypervisor_id` ,显示KVM),如果在虚拟机里驱动就会出错,所以我们需要对显卡驱动隐藏hypervisor id。 +### 8.7.2.2 隐藏虚拟机标识 + +创建gpu直通的虚拟机,在虚拟机内部NIVIDIA显卡的驱动是能检测到自己是跑在虚拟机里的(如下图,先安装 `cpuid` 再执行`cpuid |grep hypervisor_id` ,显示KVM),一旦跑在虚拟机里,就会出错,所以我们需要对显卡驱动隐藏hypervisor id。 ![](http://image.python-online.cn/20190422205222.png) -在P版之前,需要开发者自己修改xml ,在 feature 标签里添加这段 +如何隐藏 hypervisor id,只需要在xml的feature加上这段。 ```xml @@ -46,17 +56,17 @@ ``` -然后destroy再start一下虚拟机,在虚拟机内部执行 +然后destroy再start一下虚拟机,在虚拟机内部再次查看。 ![](http://image.python-online.cn/20190422204755.png) -在OpenStack的Pike版本中的Glance 镜像引入了img_hide_hypervisor_id=true的property,所以可以对镜像执行如下的命令隐藏hupervisor id。 +在Pike 版本,OpenStack 已经可以根据镜像里是否有`img_hide_hypervisor_id=true` 的property,来选择是否加上隐藏hypervisor_id的xml。 ``` openstack image set IMG-UUID --property img_hide_hypervisor_id=true ``` -由于我们当前使用的是 Newton 版本,所以需参考 P 版的代码进行相应的改动,在生成虚拟机的xml的时候,在feature标签内添加如下一段内容 +如果你和我们一样使用比Pike 版本还旧的版本( 如Newton ),需要开发者参考 Pike 版本自己修改OpenStack 的源码,在xml里加上这段。 ```xml @@ -64,15 +74,31 @@ openstack image set IMG-UUID --property img_hide_hypervisor_id=true ``` -具体 P版的代码如何实现,主要函数是这一行 +具体 Pike版的代码如何实现,主要函数是这段 + +![](http://image.python-online.cn/20190528105408.png) + +记得在镜像meta里添加 img_hide_hypervisor_id 的字段 + +![](http://image.python-online.cn/20190528105021.png) -![](http://image.python-online.cn/20190514205605.png) +### 8.7.2.3. 是否直通成功 -## 8.7.2 使用说明 +在虚拟机内部执行 nvidia-smi![](http://image.python-online.cn/20190528114526.png) + +### 8.7.2.4 称重器 + +由于 GPU 资源较为稀缺,若普通类型的虚拟机被大量创建在 GPU 的机器上,那 GPU 就会成为一种浪费。为了避免出现这种情况,应该尽量让普通类型的虚拟机都调度到非 GPU 的宿主机上。 + +因此,需要增加一个称重器,,当创建普通类型的虚拟机时,含 GPU (通过pci_device 设备数来决定)的宿主机的权重为最低,为了保证这点,这个称重器的权重系数应最大,暂定为200(可以通过配置项 ws_gpu_weight_multiplier 设置)。 + +![](http://image.python-online.cn/20190528141106.png) + +## 8.7.3 使用说明 在环境部署好,gpu 资源也充足的情况下,想要创建一台gpu直通的虚拟机,还需要确保以下两个步骤完成。 -### 8.7.2.1 模板配置 +### 8.7.3.1 模板配置 GPU 作为一种硬件资源,同样需要在模板中配置,配置方式是在 flavor 中添加Extra_spec @@ -83,12 +109,14 @@ GPU 作为一种硬件资源,同样需要在模板中配置,配置方式是 `nova flavor-key ``set` `pci_passthrough:``alias``=``'nvidia:1'` ``` -### 8.7.2.2 镜像配置 +### 8.7.3.2 镜像配置 ``` `openstack image ``set` ` --property img_hide_hypervisor_id=``true` ``` + + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_09.md b/source/c08/c08_09.md index 0998e82..09d2e71 100644 --- a/source/c08/c08_09.md +++ b/source/c08/c08_09.md @@ -224,6 +224,12 @@ server.wait() +[nova event机制分析](https://blog.csdn.net/epugv/article/details/44872583) + + + + + 参考文章: - [OpenStack之RPC调用(一)](https://blog.csdn.net/qiuhan0314/article/details/42671965) diff --git a/source/c08/c08_10.md b/source/c08/c08_10.md new file mode 100644 index 0000000..3a4f081 --- /dev/null +++ b/source/c08/c08_10.md @@ -0,0 +1,78 @@ +# 8.10 semaphore 实现一键升级 + +## 8.10.1 部署环境 + +安装数据库(下载过程较慢,可以优化) + +```python +yum -y install mariadb-server +systemctl start mariadb +systemctl enable mariadb +``` + +安装好后,还没有设置密码,可以立即无密码登陆数据库,所以要先设置一下密码。 + +![](http://image.python-online.cn/20190529150907.png) + + + +安装semaphore + +```python +mkdir /home/semaphore; cd !& + +yum install -y git wget + +wget https://github.com/ansible-semaphore/semaphore/releases/download/v2.5.1/semaphore_2.5.1_linux_amd64.rpm + +rpm -ivh semaphore_2.5.1_linux_amd64.rpm + + +# ------------------ 以上命令可以用一条命令 ------------ +curl -L https://github.com/ansible-semaphore/semaphore/releases/download/v2.5.1/semaphore_2.5.1_linux_arm64.rpm > /usr/bin/semaphore +``` + +进入初始化 + +![](http://image.python-online.cn/20190529151350.png) + +![](http://image.python-online.cn/20190529151450.png) + +开始启动semaphore + +```shell +sh -c 'nohup semaphore -config /home/semaphore/config.json &' >/dev/null 2>&1 +``` + +启动后,就能使用 http://{ip}:3000 进行访问。前面设置的 8010 端口不会生效,很奇怪。 + +![](http://image.python-online.cn/20190529154348.png) + +## 8.10.2 配置 + +先生成 ssh-key + +```shell +[root@semaphore-master ~]# ssh-keygen -t rsa -C "root@semaphore-master" +Generating public/private rsa key pair. +Enter file in which to save the key (/root/.ssh/id_rsa): +Enter passphrase (empty for no passphrase): +Enter same passphrase again: +Your identification has been saved in /root/.ssh/id_rsa. +Your public key has been saved in /root/.ssh/id_rsa.pub. +The key fingerprint is: +SHA256:9YRZhzv5hISkCb3a6zFLxbWvnlvQelAwFBunF160uGI root@semaphore-master +The key's randomart image is: ++---[RSA 2048]----+ +| .. ..oBo+.o| +| ..o.+oX.o.| +| o.+.==+. | +| .o +==o | +| oS oE==. | +| . ... .=. | +| +. . + | +| ..+ = | +| .o .=. | ++----[SHA256]-----+ +``` + From 48f14fb23ddd3254987ac1fe7f2ba0cf6f2609f2 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Thu, 30 May 2019 22:25:10 +0800 Subject: [PATCH 024/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_10.md | 43 +++++++++++++++++++- source/c08/c08_05.md | 93 +++++++++++++++++++++++++++++++++++++++++++- source/c08/c08_10.md | 14 +++++++ source/c08/c08_11.md | 23 +++++++++++ 4 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 source/c08/c08_11.md diff --git a/source/c01/c01_10.md b/source/c01/c01_10.md index e94ffc3..376d977 100644 --- a/source/c01/c01_10.md +++ b/source/c01/c01_10.md @@ -1024,7 +1024,7 @@ _666(f’是不是非常的_666’) -### 34. 更新字典 +## 34. 更新字典 通常我们更新字典的方式是这样的 @@ -1047,6 +1047,47 @@ _666(f’是不是非常的_666’) +## 35. 嵌套上下文管理的另类写法 + +当我们要写一个嵌套的上下文管理器时,可能会这样写 + +```python +import contextlib + +@contextlib.contextmanager +def test_context(name): + print('enter, my name is {}'.format(name)) + + yield + + print('exit, my name is {}'.format(name)) + +with test_context('aaa'): + with test_context('bbb'): + print('========== in main ============') +``` + +输出结果如下 + +```python +enter, my name is aaa +enter, my name is bbb +========== in main ============ +exit, my name is bbb +exit, my name is aaa +``` + +除此之外,你可知道,还有另一种嵌套写法 + +```python +with test_context('aaa'), test_context('bbb'): + print('========== in main ============') +``` + + + + + ## 附录:参考文章 - [wtfpython](https://github.com/satwikkansal/wtfpython) diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index d6ccbc2..4e030e4 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -14,6 +14,38 @@ ![](http://image.python-online.cn/20190529135942.png) +## 8.5.2 同步虚机电源状态 + +虚拟机在 hypervisor 的真实状态和 OpenStack 数据库管理的状态可能有出入。 + +比如客户可能在虚拟机内部自己执行 `shutdown -h now` ,那么虚机就进入合理正常的关机状态了,可这个状态并不是由 OpenStack 触发的,所以OpenStack 需要定时任务去检查真实的电源状态,并实时更新。 + +这个定时任务是利用 `eventlet` 这个库去做的 + +```python +class ComputeManager(manager.Manager): + def __init__(self, compute_driver=None, *args, **kwargs): + # CONF.sync_power_state_pool_size 默认是 1000 + self._sync_power_pool = eventlet.GreenPool( + size=CONF.sync_power_state_pool_size) + self._syncs_in_progress = {} +``` + +默认是 600s,就是10分钟同步一次数据库的状态。 + +![](http://image.python-online.cn/20190530204839.png) + +具体的函数是在这个 `_sync_instance_power_state` + +![](http://image.python-online.cn/20190530204505.png) + +注意这个函数只有两个地方会调用 + +一个是上面 时间间隔 10分钟 的定时任务,时间明显有点长。 + +还有一个是由 libvirt 触发的`lifecycle event` 事件,这个只要在虚拟机在hypervisor层发生状态变动时,就会调用。 + +![](http://image.python-online.cn/20190530210912.png) ## 8.5.3 创建快照代码解读? @@ -309,9 +341,68 @@ nova 里有不少服务,比如 nova-compute,nova-api,nova-conductor,nova ![](http://image.python-online.cn/20190526204328.png) +## 8.5.12 nova-api wsgi 是如何启动 + +通过上面 nova-compute 的启动过程,nova-api 也是如何,不过和nova-compute不一样的是,nova-compute没有对外接口,而nova-api 有,所以它会启动wsgi。 + +一起来看看是如何启动的。 + +![](http://image.python-online.cn/20190530212557.png) + +![](http://image.python-online.cn/20190530212753.png) +再进入 wsgi.py 可以看到 wsgi 可以接收的请求并发量也是 1000 + +![](http://image.python-online.cn/20190530212956.png) + +最后,这个wsgi server 是如何监听的呢 + +涉及的代码精简如下 + +```python +self._pool = eventlet.GreenPool(self.pool_size) +bind_addr = (host, port) +self._socket = eventlet.listen(bind_addr, family, backlog=backlog) +dup_socket = self._socket.dup() + +wsgi_kwargs = { + 'func': eventlet.wsgi.server, + 'sock': dup_socket, + 'site': self.app, + 'protocol': self._protocol, + 'custom_pool': self._pool, + 'log': self._logger, + 'log_format': CONF.wsgi.wsgi_log_format, + 'debug': False, + 'keepalive': CONF.wsgi.keep_alive, + 'socket_timeout': self.client_socket_timeout +} + +self._server = utils.spawn(**wsgi_kwargs) +``` + + + +![](http://image.python-online.cn/20190530214805.png) + +![](http://image.python-online.cn/20190530214820.png) + +我们都知道 wsgi 要传入一个 application,那这里的 app 是哪个呢? + +就是下面这个返回的 + +![](http://image.python-online.cn/20190530221101.png) + +![](http://image.python-online.cn/20190530220957.png) + +从 `/etc/nova/api-paste.ini` 里找到 `osapi_compute` ,可以看到路由表,得到 application 的的路径。(对于 paste.ini 可以查阅这篇文章:[python 中paste.ini文件使用说明](https://blog.csdn.net/hzrandd/article/details/10834381) ) + +```shell +[app:osapi_compute_app_v21] +paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory +``` -## 8.5.12 支持指定子网和指定ip +## 8.5.13 支持指定子网和指定ip 在 nova-api 接收请求处。 diff --git a/source/c08/c08_10.md b/source/c08/c08_10.md index 3a4f081..be31317 100644 --- a/source/c08/c08_10.md +++ b/source/c08/c08_10.md @@ -76,3 +76,17 @@ The key's randomart image is: +----[SHA256]-----+ ``` + + +## 8.10.4 使用 AnsibleAble + +安装 + +```shell +docker run -p 80:8080 mmumshad/ansible-playable +``` + +打开web,登陆(使用默认帐号密码) + +![](http://image.python-online.cn/20190530143442.png) + diff --git a/source/c08/c08_11.md b/source/c08/c08_11.md new file mode 100644 index 0000000..3b76095 --- /dev/null +++ b/source/c08/c08_11.md @@ -0,0 +1,23 @@ +# 8.11 OpenStack 问题排查 + +## 8.11.1 虚拟机启动不了 + +问题描述 + +``` +[root@ws_compute01 ~]# virsh domblklist instance-00000699 +error: failed to get domain 'instance-00000699' +error: Domain not found: no domain with matching name 'instance-00000699' +``` + +`/var/log/messages` 报错如下 + +![](http://image.python-online.cn/20190530175817.png) + +解决方法 + +``` +service systemd-machined restart +service libvirtd restart +``` + From 79f23f773a1954290f521c3ba5081f1842fc4462 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Fri, 31 May 2019 00:30:52 +0800 Subject: [PATCH 025/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20rst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_10.rst | 40 ++++++++- source/c08/c08_02.rst | 96 +++++++++++++++------ source/c08/c08_05.md | 75 ---------------- source/c08/c08_05.rst | 193 +++++++++++++++++++++++++----------------- source/c08/c08_07.rst | 68 ++++++++++++--- source/c08/c08_09.rst | 3 + source/c08/c08_10.rst | 99 ++++++++++++++++++++++ source/c08/c08_11.rst | 27 ++++++ source/c08/c08_12.md | 89 +++++++++++++++++++ source/c08/c08_12.rst | 121 ++++++++++++++++++++++++++ 10 files changed, 617 insertions(+), 194 deletions(-) create mode 100644 source/c08/c08_10.rst create mode 100644 source/c08/c08_11.rst create mode 100644 source/c08/c08_12.md create mode 100644 source/c08/c08_12.rst diff --git a/source/c01/c01_10.rst b/source/c01/c01_10.rst index cca1720..d9133a2 100755 --- a/source/c01/c01_10.rst +++ b/source/c01/c01_10.rst @@ -1132,7 +1132,7 @@ import 是 Python 导包的方式。 _666(f’是不是非常的_666’) 34. 更新字典 -~~~~~~~~~~~~ +------------ 通常我们更新字典的方式是这样的 @@ -1153,6 +1153,44 @@ import 是 Python 导包的方式。 >>> dict3 {'namne': 'jane', 'gender': 'male', 'age': 24} +35. 嵌套上下文管理的另类写法 +---------------------------- + +当我们要写一个嵌套的上下文管理器时,可能会这样写 + +.. code:: python + + import contextlib + + @contextlib.contextmanager + def test_context(name): + print('enter, my name is {}'.format(name)) + + yield + + print('exit, my name is {}'.format(name)) + + with test_context('aaa'): + with test_context('bbb'): + print('========== in main ============') + +输出结果如下 + +.. code:: python + + enter, my name is aaa + enter, my name is bbb + ========== in main ============ + exit, my name is bbb + exit, my name is aaa + +除此之外,你可知道,还有另一种嵌套写法 + +.. code:: python + + with test_context('aaa'), test_context('bbb'): + print('========== in main ============') + 附录:参考文章 -------------- diff --git a/source/c08/c08_02.rst b/source/c08/c08_02.rst index c22f835..13da665 100755 --- a/source/c08/c08_02.rst +++ b/source/c08/c08_02.rst @@ -13,18 +13,18 @@ SR-IOV 规范定义了新的标准,根据该标准,创建的新设备可允许将虚拟机直接连接到 I/O 设备。 -一、基础环境准备 ----------------- +8.2.1 基础环境准备 +------------------ -------------- -1.1 bios开启vt-d, sriov -~~~~~~~~~~~~~~~~~~~~~~~ +8.2.1.1 bios开启vt-d, sriov +~~~~~~~~~~~~~~~~~~~~~~~~~~~ |image0| |image1| -1.2 开启iommu -~~~~~~~~~~~~~ +8.2.1.2 开启iommu +~~~~~~~~~~~~~~~~~ vim /etc/sysconfig/grub(或者/etc/default/grub):intel_iommu=on |image2| 改完后,重新生成配置 @@ -33,16 +33,16 @@ vim /etc/sysconfig/grub(或者/etc/default/grub):intel_iommu=on grub2-mkconfig -o /etc/grub2.cfg -1.3 开启igb模块 -~~~~~~~~~~~~~~~ +8.2.1.3 开启igb模块 +~~~~~~~~~~~~~~~~~~~ :: modprobe igb max_vfs=32 # 开启模块 echo "options igb max_vfs=32" >>/etc/modprobe.d/igb.conf # 持久化 -1.4 配置VF数量 -~~~~~~~~~~~~~~ +8.2.1.4 配置VF数量 +~~~~~~~~~~~~~~~~~~ :: @@ -51,8 +51,8 @@ vim /etc/sysconfig/grub(或者/etc/default/grub):intel_iommu=on # 验证VF是否创建完成 lcpci |grep Ethernet -1.5 验证基础环境 -~~~~~~~~~~~~~~~~ +8.2.1.5 验证基础环境 +~~~~~~~~~~~~~~~~~~~~ 尝试将VF分配给虚拟机 @@ -75,13 +75,13 @@ vim /etc/sysconfig/grub(或者/etc/default/grub):intel_iommu=on 如果以上可以顺利运行,就说明基础环境已经配置好。 -二、计算节点 ------------- +8.2.2 计算节点 +-------------- -------------- -2.1 nova-compute -~~~~~~~~~~~~~~~~ +8.2.2.1 nova-compute +~~~~~~~~~~~~~~~~~~~~ 指定白名单,告诉OpenStack,允许的直通网卡是eth0所有的VF都来自于这张网卡,并且说明这个网络归属于physnet2 @@ -120,13 +120,13 @@ vim /etc/nova/nova.conf 查看数据库,compute_nodes,确认VF是否有被管理起来 |image3| -三、控制节点 ------------- +8.2.3 控制节点 +-------------- -------------- -3.1 neutron-server -~~~~~~~~~~~~~~~~~~ +8.2.3.1 neutron-server +~~~~~~~~~~~~~~~~~~~~~~ vim /etc/neutron/plugins/ml2/ml2_conf.ini |image4| @@ -153,8 +153,8 @@ vim /etc/neutron/plugins/ml2/ml2_conf.ini |image4| systemctl restart neutron-server -3.2 nova-scheduler -~~~~~~~~~~~~~~~~~~ +8.2.3.2 nova-scheduler +~~~~~~~~~~~~~~~~~~~~~~ 添加filter @@ -170,8 +170,8 @@ vim /etc/neutron/plugins/ml2/ml2_conf.ini |image4| systemctl restart openstack-nova-scheduler -3.3 sriov-agent -~~~~~~~~~~~~~~~ +8.2.3.3 sriov-agent +~~~~~~~~~~~~~~~~~~~ 安装sriov-agent @@ -219,8 +219,8 @@ vim /etc/neutron/plugins/ml2/ml2_conf.ini |image4| systemctl enable neutron-sriov-nic-agent.service systemctl restart neutron-sriov-nic-agent.service -3.4 验证SRIOV网络 -~~~~~~~~~~~~~~~~~ +8.2.3.4 验证SRIOV网络 +~~~~~~~~~~~~~~~~~~~~~ 验证步骤 @@ -250,6 +250,48 @@ vim /etc/neutron/plugins/ml2/ml2_conf.ini |image4| 3. 不同宿主机上的虚拟机之间可以通信 4. 给虚拟机分配公网IP,可以访问外网,DNS正常 +8.2.4 OpenStack 改造 +-------------------- + +8.2.4.1 支持挂卸网卡 +~~~~~~~~~~~~~~~~~~~~ + +Sriov虚拟机在openstack原生是不支持挂卸网卡操作的,即nova +interface-attach不适用于sriov port。 + +为了实现挂载的操作,需要加几步手工操作,具体步骤如下: + +1、使用neutron port-show ,查看并记录原 port 的 ``binding:profile`` +信息,如果有多个port,把每个port的信息都记录下来。 + +|image6| + +2、nova interface-detach卸载原来的port。 + +3、neutron port-create –vnic-type direct 。。。。。 创建新的sriov port + +4、更新新port的binding:profile,neutron +port-update命令不支持,只能使用curl,需要修改port-id及binding:profile + +:: + + source ~/admin-openrc; TOKEN=`openstack token issue |grep " id " |awk -F \| '{print $3}'` + curl -g -i -X PUT http://{vip}:port/v2.0/ports/3f0668f4-4b5a-4e11-93d4-10b7958668ae.json -H "User-Agent: python-neutronclient" -H "Content-Type: application/json" -H "Accept: application/json" -H "X-Auth-Token: $TOKEN" -d '{"port": {"binding:profile": {"pci_slot": "0000:02:13.6","physical_network": "phynet1", "pci_vendor_info": "8086:10ed"}}}' + +5、挂载网卡 + +.. code:: shell + + nova interface-attach 5a1c1828-4190-43fa-8e05-ae51b0196656 --port-id 3f0668f4-4b5a-4e11-93d4-10b7958668ae + +如果port已经卸载,找不到pci_slot信息,可以在数据库中查找:登陆controller,登陆mysql数据库,2个port分别用2个address即可 + +:: + + select * from pci_devices where instance_uuid='5a1c1828-4190-43fa-8e05-ae51b0196656'; + +|image7| + 附录:参考文档 -------------- @@ -271,4 +313,6 @@ vim /etc/neutron/plugins/ml2/ml2_conf.ini |image4| .. |image3| image:: https://i.loli.net/2018/01/19/5a61c1cf51b58.png .. |image4| image:: https://i.loli.net/2018/01/19/5a61c1faac447.png .. |image5| image:: https://i.loli.net/2018/01/19/5a61c246451e7.png +.. |image6| image:: http://image.python-online.cn/20190529202132.png +.. |image7| image:: http://image.python-online.cn/20190529202440.png diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 4e030e4..fa7d310 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -325,82 +325,7 @@ nova-api 返回的结果令人无法理解: ## 8.5.11 nova的各项服务服务是如何启动的? -nova 里有不少服务,比如 nova-compute,nova-api,nova-conductor,nova-scheduler 等。 -这些服务如何都是如何启动的呢?他们其实都是用同一套代码,所以只要分析一个就行,这里以nova-compute为例来了解一下。 - -从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 `nova.cmd.compute:main()` - -![](http://image.python-online.cn/20190526205152.png) - -从这个入口进去,会开启一个 `nova-compute` 的服务。 - -![](http://image.python-online.cn/20190526165007.png) - -当调用 service.Service.create 时,实际是返回实际化 service.Service 对象。当没有传入 manager 时,就以binary 里的为准。比如binary 是` nova-compute`,那manager_cls 就是 `compute_manager`,对应的manager 导入路径,就会从配置里读取。 - -![](http://image.python-online.cn/20190526204328.png) - -## 8.5.12 nova-api wsgi 是如何启动 - -通过上面 nova-compute 的启动过程,nova-api 也是如何,不过和nova-compute不一样的是,nova-compute没有对外接口,而nova-api 有,所以它会启动wsgi。 - -一起来看看是如何启动的。 - -![](http://image.python-online.cn/20190530212557.png) - -![](http://image.python-online.cn/20190530212753.png) - -再进入 wsgi.py 可以看到 wsgi 可以接收的请求并发量也是 1000 - -![](http://image.python-online.cn/20190530212956.png) - -最后,这个wsgi server 是如何监听的呢 - -涉及的代码精简如下 - -```python -self._pool = eventlet.GreenPool(self.pool_size) -bind_addr = (host, port) -self._socket = eventlet.listen(bind_addr, family, backlog=backlog) -dup_socket = self._socket.dup() - -wsgi_kwargs = { - 'func': eventlet.wsgi.server, - 'sock': dup_socket, - 'site': self.app, - 'protocol': self._protocol, - 'custom_pool': self._pool, - 'log': self._logger, - 'log_format': CONF.wsgi.wsgi_log_format, - 'debug': False, - 'keepalive': CONF.wsgi.keep_alive, - 'socket_timeout': self.client_socket_timeout -} - -self._server = utils.spawn(**wsgi_kwargs) -``` - - - -![](http://image.python-online.cn/20190530214805.png) - -![](http://image.python-online.cn/20190530214820.png) - -我们都知道 wsgi 要传入一个 application,那这里的 app 是哪个呢? - -就是下面这个返回的 - -![](http://image.python-online.cn/20190530221101.png) - -![](http://image.python-online.cn/20190530220957.png) - -从 `/etc/nova/api-paste.ini` 里找到 `osapi_compute` ,可以看到路由表,得到 application 的的路径。(对于 paste.ini 可以查阅这篇文章:[python 中paste.ini文件使用说明](https://blog.csdn.net/hzrandd/article/details/10834381) ) - -```shell -[app:osapi_compute_app_v21] -paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory -``` ## 8.5.13 支持指定子网和指定ip diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index 5fdbffb..b04b84e 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -12,6 +12,47 @@ 这个函数,只有在 ``spawn`` 、\ ``_hard_reboot`` 和 ``finish_migration`` 才会执行。 +通过libvirt的接口创建虚拟机 + +|image1| + +8.5.2 同步虚机电源状态 +---------------------- + +虚拟机在 hypervisor 的真实状态和 OpenStack 数据库管理的状态可能有出入。 + +比如客户可能在虚拟机内部自己执行 ``shutdown -h now`` +,那么虚机就进入合理正常的关机状态了,可这个状态并不是由 OpenStack +触发的,所以OpenStack 需要定时任务去检查真实的电源状态,并实时更新。 + +这个定时任务是利用 ``eventlet`` 这个库去做的 + +.. code:: python + + class ComputeManager(manager.Manager): + def __init__(self, compute_driver=None, *args, **kwargs): + # CONF.sync_power_state_pool_size 默认是 1000 + self._sync_power_pool = eventlet.GreenPool( + size=CONF.sync_power_state_pool_size) + self._syncs_in_progress = {} + +默认是 600s,就是10分钟同步一次数据库的状态。 + +|image2| + +具体的函数是在这个 ``_sync_instance_power_state`` + +|image3| + +注意这个函数只有两个地方会调用 + +一个是上面 时间间隔 10分钟 的定时任务,时间明显有点长。 + +还有一个是由 libvirt 触发的\ ``lifecycle event`` +事件,这个只要在虚拟机在hypervisor层发生状态变动时,就会调用。 + +|image4| + 8.5.3 创建快照代码解读? ------------------------ @@ -70,19 +111,19 @@ nova nova-api 的入口如下 -|image1| +|image5| 接着会调用 nova/compute/api.py -|image2| +|image6| 在nova-compute 层面:nova/compute/manager.py:_snapshot_instance() -|image3| +|image7| 接下来会调用 ``nova/virt/libvirt/driver.py:snapshot()`` -|image4| +|image8| 先获取imagebackend的类型,然后找到对应的backend @@ -97,13 +138,13 @@ nova-api 的入口如下 接下来,会调用对应的imagebackend的\ ``snapshot_extract`` 方法。 -|image5| +|image9| ``snapshot_extract`` 方法最终会调用\ ``nova/virt/images.py:_convert_image()`` ,可以看出其实底层调用的是 ``qemu-img`` 提供的\ ``convert`` 接口。 -|image6| +|image10| 如果是qcow2的backend,不是调用这边,而是调用 ``nova/virt/libvirt/utils.py:extract_snapshot()`` @@ -126,11 +167,11 @@ nova-api 的入口如下 在\ ``libvirt_utils.get_disk_type_from_path`` 里没有相应的修改,导致返回的是lvm。 -|image7| +|image11| 后面的imagebackend也相应的变成 lvm的 -|image8| +|image12| 然后会进入 lvm这个backend的init函数。由于\ ``path`` 是\ ``/dev/sdb`` 并不是一个lv,所以这边会报错。 @@ -154,7 +195,7 @@ compute的资源上报,是在 其调用的函数是\ ``virt/libvirt/driver.py`` 里的 ``get_available_resource`` 函数 -|image9| +|image13| 从数据库获取旧数据 ``self.compute_node = self._get_compute_node(context)`` @@ -172,11 +213,11 @@ compute的资源上报,是在 nova-compute 发创建请求前,会先让 nova-scheduler 选出一台资源充足的计算节点。 -|image10| +|image14| nova-scheduler 的调度主要由两部分组成 -|image11| +|image15| - 过滤器:filter,将不满足条件(硬性条件,比如内存,cpu,磁盘,pci设备等)的计算节点,直接过滤掉。意义:从过滤器出来的那些计算节点,理论上都可以创建虚拟机。 - 称重器:weigher,对满足硬性条件的众多主机按照一定的规则进行权重配比。意义:经过称重器计算,选出你更希望在哪台节点上创建虚拟机。 @@ -190,25 +231,25 @@ nova-scheduler 的调度主要由两部分组成 compute 的 id,可以在 ``_update_from_compute_node`` 函数中添加。它会从compute_nodes 表中取得你想要的信息。 - |image12| + |image16| - spec_obj:你所要请求创建的虚拟机信息(模板,镜像等)。它是从 objects.RequestSpec.from_primitives 中取得的 - |image13| + |image17| 过滤器,它的代码如下: -|image14| +|image18| 称重器,它的规则主要看这段代码。 -|image15| +|image19| 我在代码中,加了几段日志。从左到右,三个不同颜色的内容分别为,原始权值,配重系数(越高说明越占比越大,越影响最终结果),经过 nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮点数)。 -|image16| +|image20| 那最终的权值如何计算呢? @@ -222,11 +263,11 @@ nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮 1. 如果有请求req(在nova-api里),可以使用这种 -|image17| +|image21| 2. 其他地方可以使用这种 -|image18| +|image22| 8.5.9 指定ip时检查allocation_pools ---------------------------------- @@ -238,16 +279,16 @@ nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮 先来看看,port 是如何创建的 -|image19| +|image23| 若要解决这个问题,可以参考原生代码中,在为子网添加allocation_pool时,验证是否合法的的逻辑,代码如下 -|image20| +|image24| 然后在 ``neutron\neutron\db\ipam_pluggable_backend.py`` 文件中添加我们检查 ip是否在 allocation_pools 中的逻辑代码。 -|image21| +|image25| .. code:: python @@ -270,17 +311,17 @@ nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮 然后还要定义一个异常类型 -|image22| +|image26| 若指定的ip在allocation pool 里,则正常创建,若不在allocation 里,就会在 nova-compute 日志中报错。 -|image23| +|image27| 可以发现我们的ip 172.20.22.64 并不在子网的allocation pool,理所当然在nova的日志中可以看到相应的报错。 -|image24| +|image28| 8.5.10 attach port时ip占用提示 ------------------------------ @@ -300,7 +341,7 @@ IpAddressAlreadyAllocated,而在 neutronclient 只有 IpAddressInUseClient 的异常,并不匹配,在neutronclient 端与neutron 对应的异常应该为 IpAddressAlreadyAllocatedClient 。 -|image25| +|image29| 如何让nova-api能够返回具体的错误信息呢? @@ -311,50 +352,42 @@ IpAddressAlreadyAllocatedClient 异常。 并且在nova 创建port的代码处,捕获这个异常 -|image26| +|image30| 这种要改两个组件,而且要将neutronclient 的代码也管理起来,较为麻烦 一种是,只改neutron,在neutron/ipam/exceptions.py 添加一个与 neutronclient 相对应的异常。 -|image27| +|image31| 然后修改 neutron/ipam/drivers/neutrondb_ipam/drivers.py 修改异常类型 -|image28| +|image32| 通过 postman 进行模拟,已经可以返回具体的信息 -|image29| +|image33| 另附:neutron 是如何判断ip是否已经占用?代码如下 -|image30| +|image34| 8.5.11 nova的各项服务服务是如何启动的? --------------------------------------- -nova 里有不少服务,比如 -nova-compute,nova-api,nova-conductor,nova-scheduler 等。 +8.5.13 支持指定子网和指定ip +--------------------------- -这些服务如何都是如何启动的呢?他们其实都是用同一套代码,所以只要分析一个就行,这里以nova-compute为例来了解一下。 +在 nova-api 接收请求处。 -从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 -``nova.cmd.compute:main()`` +|image35| -|image31| +|image36| -从这个入口进去,会开启一个 ``nova-compute`` 的服务。 +对 network_info 进行解析,然后塞给 request 对象返回。 -|image32| - -当调用 service.Service.create 时,实际是返回实际化 service.Service -对象。当没有传入 manager 时,就以binary 里的为准。比如binary -是\ ``nova-compute``\ ,那manager_cls 就是 -``compute_manager``\ ,对应的manager 导入路径,就会从配置里读取。 - -|image33| +|image37| -------------- @@ -363,37 +396,41 @@ nova-compute,nova-api,nova-conductor,nova-scheduler 等。 .. |image0| image:: http://image.python-online.cn/20190526144846.png -.. |image1| image:: http://image.python-online.cn/20190508110723.png -.. |image2| image:: http://image.python-online.cn/20190508111109.png -.. |image3| image:: http://image.python-online.cn/20190508095028.png -.. |image4| image:: http://image.python-online.cn/20190508111527.png -.. |image5| image:: http://image.python-online.cn/FhRPy4B1xEI9SfoD2RcunJl15ZE3 -.. |image6| image:: http://image.python-online.cn/FuyMWZS6HF4g3rPwTlLcereZxg4L -.. |image7| image:: http://image.python-online.cn/FnJA8RNIvJN2lAEXbKtJDpOLg1vg -.. |image8| image:: http://image.python-online.cn/FnGyI8jCQFLCGi0pGVmI3SV6pDrv -.. |image9| image:: http://image.python-online.cn/FrbE6oEZ3vtTWwDfMNQ16MGi6SWr -.. |image10| image:: http://image.python-online.cn/20190424212211.png -.. |image11| image:: http://image.python-online.cn/20190424213430.png -.. |image12| image:: http://image.python-online.cn/20190424214653.png -.. |image13| image:: http://image.python-online.cn/20190424214540.png -.. |image14| image:: http://image.python-online.cn/20190424221602.png -.. |image15| image:: http://image.python-online.cn/20190424215735.png -.. |image16| image:: http://image.python-online.cn/20190424220008.png -.. |image17| image:: http://image.python-online.cn/20190426153322.png -.. |image18| image:: http://image.python-online.cn/20190426152148.png -.. |image19| image:: http://image.python-online.cn/20190526141815.png -.. |image20| image:: http://image.python-online.cn/20190526142453.png -.. |image21| image:: http://image.python-online.cn/20190526134519.png -.. |image22| image:: http://image.python-online.cn/20190526141226.png -.. |image23| image:: http://image.python-online.cn/20190526134543.png -.. |image24| image:: http://image.python-online.cn/20190526134618.png -.. |image25| image:: http://image.python-online.cn/20190526140213.png -.. |image26| image:: http://image.python-online.cn/20190526140301.png -.. |image27| image:: http://image.python-online.cn/20190526140315.png -.. |image28| image:: http://image.python-online.cn/20190526140336.png -.. |image29| image:: http://image.python-online.cn/20190526140410.png -.. |image30| image:: http://image.python-online.cn/20190526143235.png -.. |image31| image:: http://image.python-online.cn/20190526205152.png -.. |image32| image:: http://image.python-online.cn/20190526165007.png -.. |image33| image:: http://image.python-online.cn/20190526204328.png +.. |image1| image:: http://image.python-online.cn/20190529135942.png +.. |image2| image:: http://image.python-online.cn/20190530204839.png +.. |image3| image:: http://image.python-online.cn/20190530204505.png +.. |image4| image:: http://image.python-online.cn/20190530210912.png +.. |image5| image:: http://image.python-online.cn/20190508110723.png +.. |image6| image:: http://image.python-online.cn/20190508111109.png +.. |image7| image:: http://image.python-online.cn/20190508095028.png +.. |image8| image:: http://image.python-online.cn/20190508111527.png +.. |image9| image:: http://image.python-online.cn/FhRPy4B1xEI9SfoD2RcunJl15ZE3 +.. |image10| image:: http://image.python-online.cn/FuyMWZS6HF4g3rPwTlLcereZxg4L +.. |image11| image:: http://image.python-online.cn/FnJA8RNIvJN2lAEXbKtJDpOLg1vg +.. |image12| image:: http://image.python-online.cn/FnGyI8jCQFLCGi0pGVmI3SV6pDrv +.. |image13| image:: http://image.python-online.cn/FrbE6oEZ3vtTWwDfMNQ16MGi6SWr +.. |image14| image:: http://image.python-online.cn/20190424212211.png +.. |image15| image:: http://image.python-online.cn/20190424213430.png +.. |image16| image:: http://image.python-online.cn/20190424214653.png +.. |image17| image:: http://image.python-online.cn/20190424214540.png +.. |image18| image:: http://image.python-online.cn/20190424221602.png +.. |image19| image:: http://image.python-online.cn/20190424215735.png +.. |image20| image:: http://image.python-online.cn/20190424220008.png +.. |image21| image:: http://image.python-online.cn/20190426153322.png +.. |image22| image:: http://image.python-online.cn/20190426152148.png +.. |image23| image:: http://image.python-online.cn/20190526141815.png +.. |image24| image:: http://image.python-online.cn/20190526142453.png +.. |image25| image:: http://image.python-online.cn/20190526134519.png +.. |image26| image:: http://image.python-online.cn/20190526141226.png +.. |image27| image:: http://image.python-online.cn/20190526134543.png +.. |image28| image:: http://image.python-online.cn/20190526134618.png +.. |image29| image:: http://image.python-online.cn/20190526140213.png +.. |image30| image:: http://image.python-online.cn/20190526140301.png +.. |image31| image:: http://image.python-online.cn/20190526140315.png +.. |image32| image:: http://image.python-online.cn/20190526140336.png +.. |image33| image:: http://image.python-online.cn/20190526140410.png +.. |image34| image:: http://image.python-online.cn/20190526143235.png +.. |image35| image:: http://image.python-online.cn/20190529203441.png +.. |image36| image:: http://image.python-online.cn/20190529215953.png +.. |image37| image:: http://image.python-online.cn/20190529215825.png diff --git a/source/c08/c08_07.rst b/source/c08/c08_07.rst index 807b031..1adeea1 100644 --- a/source/c08/c08_07.rst +++ b/source/c08/c08_07.rst @@ -25,6 +25,16 @@ 修改内核,开启直通,在\ ``/etc/default/grub`` 文件里 ``GRUB_CMDLINE_LINUX`` 行,添加 ``intel_iommu=on`` +然后重启宿主机。 + +8.7.2 功能验证 +-------------- + +8.7.2.1 Driver 变化 +~~~~~~~~~~~~~~~~~~~ + +**下面将观察创建虚拟机前后 driver 的变化** + 未创建虚拟机 |image2| @@ -37,14 +47,17 @@ |image4| -在P版之前,创建gpu直通的虚拟机,在虚拟机内部能NIVIDIA显卡的驱动是能检测到自己是跑在虚拟机里的(如下图,先安装 +8.7.2.2 隐藏虚拟机标识 +~~~~~~~~~~~~~~~~~~~~~~ + +创建gpu直通的虚拟机,在虚拟机内部NIVIDIA显卡的驱动是能检测到自己是跑在虚拟机里的(如下图,先安装 ``cpuid`` 再执行\ ``cpuid |grep hypervisor_id`` -,显示KVM),如果在虚拟机里驱动就会出错,所以我们需要对显卡驱动隐藏hypervisor +,显示KVM),一旦跑在虚拟机里,就会出错,所以我们需要对显卡驱动隐藏hypervisor id。 |image5| -在P版之前,需要开发者自己修改xml ,在 feature 标签里添加这段 +如何隐藏 hypervisor id,只需要在xml的feature加上这段。 .. code:: xml @@ -52,20 +65,20 @@ id。 -然后destroy再start一下虚拟机,在虚拟机内部执行 +然后destroy再start一下虚拟机,在虚拟机内部再次查看。 |image6| -在OpenStack的Pike版本中的Glance -镜像引入了img_hide_hypervisor_id=true的property,所以可以对镜像执行如下的命令隐藏hupervisor -id。 +在Pike 版本,OpenStack +已经可以根据镜像里是否有\ ``img_hide_hypervisor_id=true`` +的property,来选择是否加上隐藏hypervisor_id的xml。 :: openstack image set IMG-UUID --property img_hide_hypervisor_id=true -由于我们当前使用的是 Newton 版本,所以需参考 P -版的代码进行相应的改动,在生成虚拟机的xml的时候,在feature标签内添加如下一段内容 +如果你和我们一样使用比Pike 版本还旧的版本( 如Newton ),需要开发者参考 +Pike 版本自己修改OpenStack 的源码,在xml里加上这段。 .. code:: xml @@ -73,17 +86,41 @@ id。 -具体 P版的代码如何实现,主要函数是这一行 +具体 Pike版的代码如何实现,主要函数是这段 |image7| -8.7.2 使用说明 +记得在镜像meta里添加 img_hide_hypervisor_id 的字段 + +|image8| + +8.7.2.3. 是否直通成功 +~~~~~~~~~~~~~~~~~~~~~ + +在虚拟机内部执行 nvidia-smi\ |image9| + +8.7.2.4 称重器 +~~~~~~~~~~~~~~ + +由于 GPU 资源较为稀缺,若普通类型的虚拟机被大量创建在 GPU 的机器上,那 +GPU +就会成为一种浪费。为了避免出现这种情况,应该尽量让普通类型的虚拟机都调度到非 +GPU 的宿主机上。 + +因此,需要增加一个称重器,,当创建普通类型的虚拟机时,含 GPU +(通过pci_device +设备数来决定)的宿主机的权重为最低,为了保证这点,这个称重器的权重系数应最大,暂定为200(可以通过配置项 +ws_gpu_weight_multiplier 设置)。 + +|image10| + +8.7.3 使用说明 -------------- 在环境部署好,gpu 资源也充足的情况下,想要创建一台gpu直通的虚拟机,还需要确保以下两个步骤完成。 -8.7.2.1 模板配置 +8.7.3.1 模板配置 ~~~~~~~~~~~~~~~~ GPU 作为一种硬件资源,同样需要在模板中配置,配置方式是在 flavor @@ -98,7 +135,7 @@ GPU 作为一种硬件资源,同样需要在模板中配置,配置方式是 `nova flavor-key ``set` `pci_passthrough:``alias``=``'nvidia:1'` -8.7.2.2 镜像配置 +8.7.3.2 镜像配置 ~~~~~~~~~~~~~~~~ :: @@ -118,5 +155,8 @@ GPU 作为一种硬件资源,同样需要在模板中配置,配置方式是 .. |image4| image:: http://image.python-online.cn/20190422201117.png .. |image5| image:: http://image.python-online.cn/20190422205222.png .. |image6| image:: http://image.python-online.cn/20190422204755.png -.. |image7| image:: http://image.python-online.cn/20190514205605.png +.. |image7| image:: http://image.python-online.cn/20190528105408.png +.. |image8| image:: http://image.python-online.cn/20190528105021.png +.. |image9| image:: http://image.python-online.cn/20190528114526.png +.. |image10| image:: http://image.python-online.cn/20190528141106.png diff --git a/source/c08/c08_09.rst b/source/c08/c08_09.rst index 2129d4e..8950d1b 100644 --- a/source/c08/c08_09.rst +++ b/source/c08/c08_09.rst @@ -242,6 +242,9 @@ rpc server 和rpc client 的四个重要方法 |image12| +`nova +event机制分析 `__ + 参考文章: - `OpenStack之RPC调用(一) `__ diff --git a/source/c08/c08_10.rst b/source/c08/c08_10.rst new file mode 100644 index 0000000..ed73e63 --- /dev/null +++ b/source/c08/c08_10.rst @@ -0,0 +1,99 @@ +8.10 semaphore 实现一键升级 +=========================== + +8.10.1 部署环境 +--------------- + +安装数据库(下载过程较慢,可以优化) + +.. code:: python + + yum -y install mariadb-server + systemctl start mariadb + systemctl enable mariadb + +安装好后,还没有设置密码,可以立即无密码登陆数据库,所以要先设置一下密码。 + +|image0| + +安装semaphore + +.. code:: python + + mkdir /home/semaphore; cd !& + + yum install -y git wget + + wget https://github.com/ansible-semaphore/semaphore/releases/download/v2.5.1/semaphore_2.5.1_linux_amd64.rpm + + rpm -ivh semaphore_2.5.1_linux_amd64.rpm + + + # ------------------ 以上命令可以用一条命令 ------------ + curl -L https://github.com/ansible-semaphore/semaphore/releases/download/v2.5.1/semaphore_2.5.1_linux_arm64.rpm > /usr/bin/semaphore + +进入初始化 + +|image1| + +|image2| + +开始启动semaphore + +.. code:: shell + + sh -c 'nohup semaphore -config /home/semaphore/config.json &' >/dev/null 2>&1 + +启动后,就能使用 http://{ip}:3000 进行访问。前面设置的 8010 +端口不会生效,很奇怪。 + +|image3| + +8.10.2 配置 +----------- + +先生成 ssh-key + +.. code:: shell + + [root@semaphore-master ~]# ssh-keygen -t rsa -C "root@semaphore-master" + Generating public/private rsa key pair. + Enter file in which to save the key (/root/.ssh/id_rsa): + Enter passphrase (empty for no passphrase): + Enter same passphrase again: + Your identification has been saved in /root/.ssh/id_rsa. + Your public key has been saved in /root/.ssh/id_rsa.pub. + The key fingerprint is: + SHA256:9YRZhzv5hISkCb3a6zFLxbWvnlvQelAwFBunF160uGI root@semaphore-master + The key's randomart image is: + +---[RSA 2048]----+ + | .. ..oBo+.o| + | ..o.+oX.o.| + | o.+.==+. | + | .o +==o | + | oS oE==. | + | . ... .=. | + | +. . + | + | ..+ = | + | .o .=. | + +----[SHA256]-----+ + +8.10.4 使用 AnsibleAble +----------------------- + +安装 + +.. code:: shell + + docker run -p 80:8080 mmumshad/ansible-playable + +打开web,登陆(使用默认帐号密码) + +|image4| + +.. |image0| image:: http://image.python-online.cn/20190529150907.png +.. |image1| image:: http://image.python-online.cn/20190529151350.png +.. |image2| image:: http://image.python-online.cn/20190529151450.png +.. |image3| image:: http://image.python-online.cn/20190529154348.png +.. |image4| image:: http://image.python-online.cn/20190530143442.png + diff --git a/source/c08/c08_11.rst b/source/c08/c08_11.rst new file mode 100644 index 0000000..e6163a5 --- /dev/null +++ b/source/c08/c08_11.rst @@ -0,0 +1,27 @@ +8.11 OpenStack 问题排查 +======================= + +8.11.1 虚拟机启动不了 +--------------------- + +问题描述 + +:: + + [root@ws_compute01 ~]# virsh domblklist instance-00000699 + error: failed to get domain 'instance-00000699' + error: Domain not found: no domain with matching name 'instance-00000699' + +``/var/log/messages`` 报错如下 + +|image0| + +解决方法 + +:: + + service systemd-machined restart + service libvirtd restart + +.. |image0| image:: http://image.python-online.cn/20190530175817.png + diff --git a/source/c08/c08_12.md b/source/c08/c08_12.md new file mode 100644 index 0000000..39b1e92 --- /dev/null +++ b/source/c08/c08_12.md @@ -0,0 +1,89 @@ +# 8.12 从nova-api的启动理解wsgi + +nova 里有不少服务,比如 nova-compute,nova-api,nova-conductor,nova-scheduler 等。 + +这些服务如何都是如何启动的呢?他们其实都是用同一套代码,所以只要分析一个就行,这里以nova-compute为例来了解一下。 + +从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 `nova.cmd.compute:main()` + +![](http://image.python-online.cn/20190526205152.png) + +从这个入口进去,会开启一个 `nova-compute` 的服务。 + +![](http://image.python-online.cn/20190526165007.png) + +当调用 service.Service.create 时(create 是一个工厂函数),实际是返回实例化的 service.Service 对象。当没有传入 manager 时,就以binary 里的为准。比如binary 是` nova-compute`,那manager_cls 就是 `compute_manager`,对应的manager 导入路径,就会从配置里读取。 + +![](http://image.python-online.cn/20190526204328.png) + +通过了解了上面 nova-compute 的启动过程,nova-api 也是相同的思路,它的入口是`nova.cmd.api:main()`,不过和nova-compute不一样的是,nova-compute没有对外接口,而nova-api 有,所以它会启动一个 wsgi 服务器。 + +接下来,我们就来一起看看是 OpenStack Nova 是如何启动一个 WSGI 服务器的。 + +在如下的黄框里,可以看到在这里启动了一个 server,就是我们所说的的 wsgi server + +![](http://image.python-online.cn/20190530212557.png) + +![](http://image.python-online.cn/20190530212753.png) + +再进入 wsgi.py 可以看到这里使用了 eventlet 这个并发库,它开启了一个绿色线程池,从配置里可以看到这个wsgi 服务器可以接收的请求并发量是 1000 。 + +![](http://image.python-online.cn/20190530212956.png) + +你接下来感兴趣的应该是,这个线程池里的每个线程都是啥?是如何接收请求的? + +通过对源码的阅读,可以得知是通过socket接收请求的。 + +由于代码较多,我把提取了主要的代码,精简如下 + +```python +# 创建绿色线程池 +self._pool = eventlet.GreenPool(self.pool_size) + +# 创建 socket:有监听的ip,端口 +bind_addr = (host, port) +self._socket = eventlet.listen(bind_addr, family, backlog=backlog) +dup_socket = self._socket.dup() + +# 整理孵化协程所需的各项参数 +wsgi_kwargs = { + 'func': eventlet.wsgi.server, + 'sock': dup_socket, + 'site': self.app, # 这个就是 wsgi 的 application 函数 + 'protocol': self._protocol, + 'custom_pool': self._pool, + 'log': self._logger, + 'log_format': CONF.wsgi.wsgi_log_format, + 'debug': False, + 'keepalive': CONF.wsgi.keep_alive, + 'socket_timeout': self.client_socket_timeout +} + +# 孵化协程 +self._server = utils.spawn(**wsgi_kwargs) +``` + + + +![](http://image.python-online.cn/20190530214820.png) + +我们都知道 wsgi 要传入一个 application,用来处理接收到的请求,是我们整个服务的关键入口,那这里的 app 是哪个呢?其实在上面代码中我有注释: self.app 。 + +下面这行就是 self.app 的来源,通过查看我打印的 DEBUG 内容得知 config_url 和 app name 的值 + +![](http://image.python-online.cn/20190530221101.png) + +再往代码中看,其实这个 app 不是直接写死成一个具体的函数对象,而是通过解析 paste.ini 配置文件来取得具体的 application 路径,然后导入。 + +而 paste.ini 文件的解析是通过 Python 的第三方库 `paste` + +下图我截取了其主要的代码 + +![](http://image.python-online.cn/20190530220957.png) + +通过上面DEBUG日志,我们知道了 `uri =/etc/nova/api-paste.ini` ,查看 `/etc/nova/api-paste.ini` ,果然可以找到 `osapi_compute` 这个app,从这个路由表,可以得到 application 的的路径(对于 paste.ini 可以查阅这篇文章:[python 中paste.ini文件使用说明](https://blog.csdn.net/hzrandd/article/details/10834381) ),是`nova.api.openstack.compute` 这个模块下的 APIRouterV21 类 的factory方法。 + +```shell +[app:osapi_compute_app_v21] +paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory +``` \ No newline at end of file diff --git a/source/c08/c08_12.rst b/source/c08/c08_12.rst new file mode 100644 index 0000000..c72e9f6 --- /dev/null +++ b/source/c08/c08_12.rst @@ -0,0 +1,121 @@ +8.12 从nova-api的启动理解wsgi +============================= + +nova 里有不少服务,比如 +nova-compute,nova-api,nova-conductor,nova-scheduler 等。 + +这些服务如何都是如何启动的呢?他们其实都是用同一套代码,所以只要分析一个就行,这里以nova-compute为例来了解一下。 + +从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 +``nova.cmd.compute:main()`` + +|image0| + +从这个入口进去,会开启一个 ``nova-compute`` 的服务。 + +|image1| + +当调用 service.Service.create 时(create +是一个工厂函数),实际是返回实例化的 service.Service 对象。当没有传入 +manager 时,就以binary 里的为准。比如binary +是\ ``nova-compute``\ ,那manager_cls 就是 +``compute_manager``\ ,对应的manager 导入路径,就会从配置里读取。 + +|image2| + +通过了解了上面 nova-compute 的启动过程,nova-api +也是相同的思路,它的入口是\ ``nova.cmd.api:main()``\ ,不过和nova-compute不一样的是,nova-compute没有对外接口,而nova-api +有,所以它会启动一个 wsgi 服务器。 + +接下来,我们就来一起看看是 OpenStack Nova 是如何启动一个 WSGI 服务器的。 + +在如下的黄框里,可以看到在这里启动了一个 server,就是我们所说的的 wsgi +server + +|image3| + +|image4| + +再进入 wsgi.py 可以看到这里使用了 eventlet +这个并发库,它开启了一个绿色线程池,从配置里可以看到这个wsgi +服务器可以接收的请求并发量是 1000 。 + +|image5| + +你接下来感兴趣的应该是,这个线程池里的每个线程都是啥?是如何接收请求的? + +通过对源码的阅读,可以得知是通过socket接收请求的。 + +由于代码较多,我把提取了主要的代码,精简如下 + +.. code:: python + + # 创建绿色线程池 + self._pool = eventlet.GreenPool(self.pool_size) + + # 创建 socket:有监听的ip,端口 + bind_addr = (host, port) + self._socket = eventlet.listen(bind_addr, family, backlog=backlog) + dup_socket = self._socket.dup() + + # 整理孵化协程所需的各项参数 + wsgi_kwargs = { + 'func': eventlet.wsgi.server, + 'sock': dup_socket, + 'site': self.app, # 这个就是 wsgi 的 application 函数 + 'protocol': self._protocol, + 'custom_pool': self._pool, + 'log': self._logger, + 'log_format': CONF.wsgi.wsgi_log_format, + 'debug': False, + 'keepalive': CONF.wsgi.keep_alive, + 'socket_timeout': self.client_socket_timeout + } + + # 孵化协程 + self._server = utils.spawn(**wsgi_kwargs) + +|image6| + +我们都知道 wsgi 要传入一个 +application,用来处理接收到的请求,是我们整个服务的关键入口,那这里的 +app 是哪个呢?其实在上面代码中我有注释: self.app 。 + +下面这行就是 self.app 的来源,通过查看我打印的 DEBUG 内容得知 config_url +和 app name 的值 + +|image7| + +再往代码中看,其实这个 app +不是直接写死成一个具体的函数对象,而是通过解析 paste.ini +配置文件来取得具体的 application 路径,然后导入。 + +而 paste.ini 文件的解析是通过 Python 的第三方库 ``paste`` + +下图我截取了其主要的代码 + +|image8| + +通过上面DEBUG日志,我们知道了 ``uri =/etc/nova/api-paste.ini`` ,查看 +``/etc/nova/api-paste.ini`` ,果然可以找到 ``osapi_compute`` +这个app,从这个路由表,可以得到 application 的的路径(对于 paste.ini +可以查阅这篇文章:\ `python +中paste.ini文件使用说明 `__ +),是\ ``nova.api.openstack.compute`` 这个模块下的 APIRouterV21 类 +的factory方法。 + +.. code:: shell + + [app:osapi_compute_app_v21] + paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory + +.. |image0| image:: http://image.python-online.cn/20190526205152.png +.. |image1| image:: http://image.python-online.cn/20190526165007.png +.. |image2| image:: http://image.python-online.cn/20190526204328.png +.. |image3| image:: http://image.python-online.cn/20190530212557.png +.. |image4| image:: http://image.python-online.cn/20190530212753.png +.. |image5| image:: http://image.python-online.cn/20190530212956.png +.. |image6| image:: http://image.python-online.cn/20190530214820.png +.. |image7| image:: http://image.python-online.cn/20190530221101.png +.. |image8| image:: http://image.python-online.cn/20190530220957.png + From a8db72b1180300a0ca0ea2a9eff7b22c6a1f96ec Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Fri, 31 May 2019 23:19:29 +0800 Subject: [PATCH 026/302] =?UTF-8?q?nova-api=20=E8=A7=A3=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c03/c03_01.md | 17 ++- source/c08/c08_12.md | 286 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 293 insertions(+), 10 deletions(-) diff --git a/source/c03/c03_01.md b/source/c03/c03_01.md index 477e400..4ff1316 100644 --- a/source/c03/c03_01.md +++ b/source/c03/c03_01.md @@ -365,16 +365,12 @@ print(wrapper(wrapped).__name__) 那如何避免这种情况的产生?方法是使用 functools .wraps 装饰器,它的作用就是将 **被修饰的函数(wrapped)** 的一些属性值赋值给 **修饰器函数(wrapper)** ,最终让属性的显示更符合我们的直觉。 ```python -from functools import update_wrapper - -WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', - '__annotations__') +from functools import wraps def wrapper(func): + @wraps(func) def inner_function(): pass - - update_wrapper(inner_function, func, assigned=WRAPPER_ASSIGNMENTS) return inner_function @wrapper @@ -382,6 +378,7 @@ def wrapped(): pass print(wrapped.__name__) +# wrapped ``` 准确点说,wraps 其实是一个偏函数对象(partial),源码如下 @@ -400,10 +397,14 @@ def wraps(wrapped, ```python from functools import update_wrapper +WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', + '__annotations__') + def wrapper(func): def inner_function(): pass - update_wrapper(func, inner_function) + + update_wrapper(inner_function, func, assigned=WRAPPER_ASSIGNMENTS) return inner_function @wrapper @@ -411,8 +412,6 @@ def wrapped(): pass print(wrapped.__name__) -# wrapped - ``` ## 3.1.10 内置装饰器:property diff --git a/source/c08/c08_12.md b/source/c08/c08_12.md index 39b1e92..87322b6 100644 --- a/source/c08/c08_12.md +++ b/source/c08/c08_12.md @@ -84,6 +84,290 @@ self._server = utils.spawn(**wsgi_kwargs) 通过上面DEBUG日志,我们知道了 `uri =/etc/nova/api-paste.ini` ,查看 `/etc/nova/api-paste.ini` ,果然可以找到 `osapi_compute` 这个app,从这个路由表,可以得到 application 的的路径(对于 paste.ini 可以查阅这篇文章:[python 中paste.ini文件使用说明](https://blog.csdn.net/hzrandd/article/details/10834381) ),是`nova.api.openstack.compute` 这个模块下的 APIRouterV21 类 的factory方法。 ```shell +# 先路由一下 +[composite:osapi_compute] +use = call:nova.api.openstack.urlmap:urlmap_factory +/: oscomputeversions +/v2: openstack_compute_api_v21_legacy_v2_compatible +/v2.1: openstack_compute_api_v21 + [app:osapi_compute_app_v21] paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory -``` \ No newline at end of file +``` + + + +真正的callable对象,但这个不是app + +![](http://image.python-online.cn/20190531205427.png) + +真正的app是这个 + +![](http://image.python-online.cn/20190531211542.png) + +重要的是在这个 match ,这是在 `RoutesMiddleware.__call__()` 里塞进 `req.environ` 的。 + +里面放着什么东西呢? + +我把这个打印出来 + +``` +{'action': u'detail', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'} + +{'action': u'index', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'} + +{'action': u'show', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9', 'id': u'68323d9c-ebe5-499a-92e9-32fea900a892'} +``` + + + +这里的controller就是在APIRouter初始化中设置的controller,也就是使用相应 Controller 类初始化的 Resource 实例。这些 Resource 是如何注册上去的呢,这个在 `APIRouterV21` 初始化的时候就会去做,将这些都通过 C:\Python27\Lib\site-packages\routes\mapper.py 注册到 route map上去 + +![](http://image.python-online.cn/20190531225529.png) + +通过日志打印,可以发现有这些Resource + +``` +os-server-groups +os-keypairs +os-availability-zone +remote-consoles +os-simple-tenant-usage +os-instance-actions +os-migrations +os-hypervisors +diagnostics +os-agents +images +os-fixed-ips +os-networks +os-security-groups +os-security-groups +os-security-group-rules +flavors +os-floating-ips-bulk +os-console-auth-tokens +os-baremetal-nodes +os-cloudpipe +os-server-external-events +os-instance_usage_audit_log +os-floating-ips +os-security-group-default-rules +os-tenant-networks +os-certificates +os-quota-class-sets +os-floating-ip-pools +os-floating-ip-dns +entries +os-aggregates +os-fping +os-server-password +os-flavor-access +consoles +os-extra_specs +os-interface +os-services +servers +extensions +metadata +metadata +limits +ips +os-cells +versions +tags +migrations +os-hosts +os-virtual-interfaces +os-assisted-volume-snapshots +os-quota-sets +os-volumes +os-volumes_boot +os-volume_attachments +os-snapshots +os-server-groups +os-keypairs +os-availability-zone +remote-consoles +os-simple-tenant-usage +os-instance-actions +os-migrations +os-hypervisors +diagnostics +os-agents +images +os-fixed-ips +os-networks +os-security-groups +os-security-groups +os-security-group-rules +flavors +os-floating-ips-bulk +os-console-auth-tokens +os-baremetal-nodes +os-cloudpipe +os-server-external-events +os-instance_usage_audit_log +os-floating-ips +os-security-group-default-rules +os-tenant-networks +os-certificates +os-quota-class-sets +os-floating-ip-pools +os-floating-ip-dns +entries +os-aggregates +os-fping +os-server-password +os-flavor-access +consoles +os-extra_specs +os-interface +os-services +servers +extensions +metadata +metadata +limits +ips +os-cells +versions +tags +migrations +os-hosts +os-virtual-interfaces +os-assisted-volume-snapshots +os-quota-sets +os-volumes +os-volumes_boot +os-volume_attachments +os-snapshots +``` + + + +外部的请求,是如何映射到这些controller的呢? + +这些 Resource都有一个 + + + +![](http://image.python-online.cn/20190531230749.png) + +所以接着调用`nova.api.openstack.wsgi.Resource.__call__` 函数, + +![](http://image.python-online.cn/20190531215728.png) + +该函数最终是调用 `_process_stack` 函数,通过environ['wsgiorg.routing_args'],获取上面设置的match。该match有一个 action 属性,它指定了所要调用Controller成员函数的名字,以及其它相关的调用参数。 + +```python +def _process_stack(self, request, action, action_args, + content_type, body, accept): + """Implement the processing stack.""" + + # Get the implementing method + try: + meth, extensions = self.get_method(request, action, + content_type, body) + except (AttributeError, TypeError): + return Fault(webob.exc.HTTPNotFound()) + except KeyError as ex: + msg = _("There is no such action: %s") % ex.args[0] + return Fault(webob.exc.HTTPBadRequest(explanation=msg)) + except exception.MalformedRequestBody: + msg = _("Malformed request body") + return Fault(webob.exc.HTTPBadRequest(explanation=msg)) + + if body: + msg = _("Action: '%(action)s', calling method: %(meth)s, body: " + "%(body)s") % {'action': action, + 'body': six.text_type(body, 'utf-8'), + 'meth': str(meth)} + LOG.debug(strutils.mask_password(msg)) + else: + LOG.debug("Calling method '%(meth)s'", + {'meth': str(meth)}) + + # Now, deserialize the request body... + try: + contents = {} + if self._should_have_body(request): + # allow empty body with PUT and POST + if request.content_length == 0: + contents = {'body': None} + else: + contents = self.deserialize(body) + except exception.MalformedRequestBody: + msg = _("Malformed request body") + return Fault(webob.exc.HTTPBadRequest(explanation=msg)) + + # Update the action args + action_args.update(contents) + + project_id = action_args.pop("project_id", None) + context = request.environ.get('nova.context') + if (context and project_id and (project_id != context.project_id)): + msg = _("Malformed request URL: URL's project_id '%(project_id)s'" + " doesn't match Context's project_id" + " '%(context_project_id)s'") % \ + {'project_id': project_id, + 'context_project_id': context.project_id} + return Fault(webob.exc.HTTPBadRequest(explanation=msg)) + + response = None + try: + with ResourceExceptionHandler(): + action_result = self.dispatch(meth, request, action_args) + except Fault as ex: + response = ex + + if not response: + # No exceptions; convert action_result into a + # ResponseObject + resp_obj = None + if type(action_result) is dict or action_result is None: + resp_obj = ResponseObject(action_result) + elif isinstance(action_result, ResponseObject): + resp_obj = action_result + else: + response = action_result + + # Run post-processing extensions + if resp_obj: + # Do a preserialize to set up the response object + if hasattr(meth, 'wsgi_code'): + resp_obj._default_code = meth.wsgi_code + # Process extensions + response = self.process_extensions(extensions, resp_obj, + request, action_args) + + if resp_obj and not response: + response = resp_obj.serialize(request, accept) + + if hasattr(response, 'headers'): + for hdr, val in list(response.headers.items()): + # Headers must be utf-8 strings + response.headers[hdr] = utils.utf8(val) + + if not request.api_version_request.is_null(): + response.headers[API_VERSION_REQUEST_HEADER] = \ + 'compute ' + request.api_version_request.get_string() + response.headers[LEGACY_API_VERSION_REQUEST_HEADER] = \ + request.api_version_request.get_string() + response.headers.add('Vary', API_VERSION_REQUEST_HEADER) + response.headers.add('Vary', LEGACY_API_VERSION_REQUEST_HEADER) + + return response +``` + + + +在我们定义Controller的成员函数时,一般需要通过nova.api.openstack.wsgi.{serializers, deserializers}来指定解释body内容的模板,可以是xml或者json格式的。前面说过重定义nova.api.openstack.urlmap.URLMap的目的是为了判断content_type。Resource接着在解析body时会参考content_type,然后调用相应的解析器进行body解析(如XMLDeserializer、JSONDeserializer),接着将解析出的参数更新进action_args,使用action_args来调用Controller成员函数,即最终的http请求处理函数。最后将执行结果使用指定的序列化器序列化,并返回结果。 + + + +这里特别说明一下对常见RESTful请求之外的action请求的处理。以/servers/xxx/action请求为例,请求调用的函数实际包含在请求的body中。经过routes.middleware.RoutesMiddleware的__call__函数解析后,此时即将调用的Resource已经确定为哪个模块中的Controller所构建的Resource,而action参数为"action",接下来在Resource的__call__函数里面会因为action=="action"从而开始解析body的内容,找出Controller中所对应的方法。Controller在构建的过程中会由于MetaClass的影响将其所有action类型的方法填入一个字典中,key由每个_action_xxx方法前的@wsgi.action('xxx')装饰函数给出,value为每个_action_xxx方法的名字(从中可以看出规律,在body里面请求的方法名前加上_aciton_即为Controller中对应调用的方法)。之后在使用Controller构建Resource对象的过程中会向Resource注册该Controller的这个字典中的内容。这样,只需在请求的body中给出调用方法的key,然后就可以找到这个key所映射的方法,最后在Resource的__call__函数中会调用Controller类的这个函数! + + + +[nova-api源码分析(APP的调用)](https://www.cnblogs.com/littlebugfish/p/4660595.html) \ No newline at end of file From 1eb99e2e8ae27a33e55e7db549f2fc3e2c32c5f5 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 3 Jun 2019 00:01:48 +0800 Subject: [PATCH 027/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_14.md | 4 +- source/c04/c04_18.md | 30 ++++++++++++ source/c08/c08_12.md | 33 ++++++++++++- source/c08/c08_13.md | 112 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 source/c04/c04_18.md create mode 100644 source/c08/c08_13.md diff --git a/source/c04/c04_14.md b/source/c04/c04_14.md index 294a838..dae1df1 100644 --- a/source/c04/c04_14.md +++ b/source/c04/c04_14.md @@ -50,6 +50,7 @@ $ pipenv --two # 相当于 pipenv --python /usr/bin/python2 $ pipenv --three # 相当于 pipenv --python /usr/bin/python3 $ pipenv --python 3.7 # 也可以指定具体的版本 +pipenv install --python 2 ``` ## 4.14.3 查询虚拟环境 @@ -72,7 +73,8 @@ $ pipenv --py $ pipenv shell # 退出这个虚拟环境 -$ exit +$ exit (有的终端使用这个会退出) +$ deactivate # 移除当前目录的虚拟环境 $ pipenv --rm diff --git a/source/c04/c04_18.md b/source/c04/c04_18.md new file mode 100644 index 0000000..477125b --- /dev/null +++ b/source/c04/c04_18.md @@ -0,0 +1,30 @@ +# 4.18 Mac 高效工具 + +## 4.18.1 iTerm2 + +介绍一下快捷键 + +``` +# 分窗口操作 +shift + command + d(横切)command + d(竖切) + +2、历史信息查找和粘贴:command + f,呼出查找功能,找到后 tab 键可以选中找到的文本,通过option + 回车粘贴。 +3、自动完成:command + ; ,呼出自动完成窗口,根据上下文提供内容选择项 +# 粘贴历史 +shift + command + h +# 回放功能 +option + command + b +# 光标去哪了? +command + / + +# Expose Tabs: +option + command + e +``` + +## 4.18.2 Finder + +``` +command + up # 文件夹后退 +command + down # 文件夹前进 +``` + diff --git a/source/c08/c08_12.md b/source/c08/c08_12.md index 87322b6..8dbc68f 100644 --- a/source/c08/c08_12.md +++ b/source/c08/c08_12.md @@ -248,10 +248,37 @@ os-snapshots 外部的请求,是如何映射到这些controller的呢? -这些 Resource都有一个 +那就要归功于 `routes` ,用来给服务内部定义url到具体函数的映射,这个和 deploy 提供的url到服务映射功能有相同也有不同,相同的是都是url到服务,不同的是deploy的层级更高一点,比如 `/v2/project_id/servers/`这个请求的url,deploy会将 `/v2` 的请求交给openstack_compute_api_v2,而再后面的活就全交给` routes `,routes 会将 `project_id/servers/` 的GET请求交给nova.api.openstack.compute.servers.Controller.index(),而 POST请求交给 create() 方法处理。这就是 routes.mappers 所提供的功能,根据path和请求方法,将请求映射到具体的函数上。 + +deploy 前面已经讲过,现在要来看看 routes。 + +```python +self.resources['servers'] = servers.create_resource(ext_mgr) + +mapper.resource("server", "servers", + controller=self.resources['servers'], + collection={'list_vm_state': 'GET', 'os_vmsum': 'GET'}) +``` + + + +```python +routes.mapper.connect("server", + "/{project_id}/servers/list_vm_state", + controller=self.resources['servers'], + action='list_vm_state', + conditions={'list_vm_state': 'GET'}) +mapper.connect("server", + "/{project_id}/servers/os_vmsum", + controller=self.resources['servers'], + action='os_vmsum', + conditions={'os_vmsum': 'GET'}) +``` +这些 Resource都有一个 + ![](http://image.python-online.cn/20190531230749.png) 所以接着调用`nova.api.openstack.wsgi.Resource.__call__` 函数, @@ -370,4 +397,6 @@ def _process_stack(self, request, action, action_args, -[nova-api源码分析(APP的调用)](https://www.cnblogs.com/littlebugfish/p/4660595.html) \ No newline at end of file +[nova-api源码分析(APP的调用)](https://www.cnblogs.com/littlebugfish/p/4660595.html) + +[route 举例总结](https://blog.csdn.net/bellwhl/article/details/8956088) \ No newline at end of file diff --git a/source/c08/c08_13.md b/source/c08/c08_13.md new file mode 100644 index 0000000..d706632 --- /dev/null +++ b/source/c08/c08_13.md @@ -0,0 +1,112 @@ +# 8.13 WSGI工作原理:Route + +最近在看 OpenStack 的源码中,准备研究一下一个 API 请求需要经过怎样的过程才能到达指定的 application 函数。 + +在阅读过程中,发现有好几个陌生的库,导致阅读的过程中,理解出现了断层,越深入源码越抽象。在这时先一个一个库进行攻库。 + +OpenStack 的路由分两部分,这里先举个例子,如下面这个请求 + +``` +curl -g -i -X GET http://localhost:8774/v2.1//servers/detail +``` + +一个是上层的路由,由 deploy.paste 来完成的,主要是 api 请求的 url 来判断所调用的api版本,是 v2 还是 v2.1,再决定请求的去向,一个是下层的路由,根据url 再具体的信息,决定由哪个 Controller 的哪个 application 来处理。 + +## 8.13.1 PasteDeploy + +谈到WSGI,就免不了要了解paste,其中paste deploy是用来发现和配置WSGI应用的一套系统,对于WSGI应用的使用者而言,可以方便地从配置文件汇总加载WSGI应用(loadapp);对于WSGI应用的开发人员而言,只需要给自己的应用提供一套简单的入口点即可。 + +nova 服务启动的时候,会先使用 eventlet.wsgi 启动一个 wsgi 服务器。我们知道 wsgi 启动后,必然要注册一系列的 application,而nova 中资源种类非常多, nova 是如何实现灵绑定注册的呢? + +往代码中看,其实这个 app 不是直接写死成一个具体的函数对象,而是通过解析 paste.ini 配置文件来取得具体的 application 路径。 + +通过查看我在 load_app 函数打印的 DEBUG 内容得知 + +- 配置文件 config_url:/etc/nova/api-paste.ini +- 读取的 app name:osapi_compute + +![](http://image.python-online.cn/20190530221101.png) + + + +而 paste.ini 文件是通过 PasteDeploy 这个库来解析的,对应的源码如下 + +![](http://image.python-online.cn/20190530220957.png) + +查看一下`/etc/nova/api-paste.ini` ,通过关键字 `osapi_compute` ,我将一些有用的信息提取出来。 + +``` +[composite:osapi_compute] +use = call:nova.api.openstack.urlmap:urlmap_factory +/: oscomputeversions +/v2: openstack_compute_api_v21_legacy_v2_compatible +/v2.1: openstack_compute_api_v21 + +[app:osapi_compute_app_v21] +paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory +``` + +[composite:xxx]:表示需要将一个请求调度定向(dispatched)到多个,或者多种应用上。以下是一个简单的例子,例子中,使用了composite,根据 url 里的 api 版本号,通过urlmap来实现载入不同的应用,我这里使用的是 `/v2.1` 的版本,所以被调度到了 `osapi_compute_app_v21` 这个app,而这个app 的函数在nova 组件中对应的地址是 `nova.api.openstack.compute:APIRouterV21.factory`。 + +至此,PasteDeploy 的任务就完成了。 + +若你对 paste.ini 不甚了解,可以读一下这篇文章:[python 中paste.ini文件使用说明](https://blog.csdn.net/hzrandd/article/details/10834381) + +## 8.13.2 webob 库 + +经过了 PasteDeploy 的路由调度,我们找到了 `nova.api.openstack.compute:APIRouterV21.factory` 这个 application 的入口,它其实是 APIRouterV21 的一个实例。 + +![](http://image.python-online.cn/20190602173212.png) + +以前我们知道,application 必须是一个 callable 的对象,函数都是 callable 的,但若是一个类实例,就要求这个类实现 `__call__` 的方法。 + +APIRouterV21 本身没有实现 `__call__` ,但它的父类 Router实现了 `__call__` + +![](http://image.python-online.cn/20190602173956.png) + +很奇怪的是,这个 `__call__` 里没有对 req 进行任何处理,而只是返回另一个 callable 对象。你发现了没有 `__call__` 有被一个叫 wsgify 的装饰器装饰着,那它的作用是什么呢? + +wsgify 在这里,指定了一个参数 `RequestClass=Request`,它所起的作用就是将 req 这个原始请求(dict对象)封装成 Request 对象。 + +通过查看 `webob.dec.wsgify()` 源码,wsgify 这个类主要是用于对 WSGI app进行封装,简化wsgi app的定义与编写,它可以很方便的将一个 callable 的函数或对象,封装成一个 WSGI app。 + + + +## 8.13.2 routes 库 + +在OpenStack 中很多组件都用到了 routes.Mapper 作为 WSGI app 的路由控制。 + +首先是 `routes.middleware.RoutesMiddleware` ,从命名上看,这是一个中间件,它将接受到的url,自动调用map.match()方法,将url进行路由匹配并将结果存入request请求的环境变量['wsgiorg.routing_args'],最后会调用其第一个参数给出的函数接口,即 self.dispatch。 + +self.dispatch 若不能拿到对应的 Controller信息,就由webob 抛出异常。 + +![](http://image.python-online.cn/20190602214129.png) + +若能匹配到,还是返回一个 callable 对象,需要注意的是这边的 app,还不是controller 对象,而是 nova.api.openstack.wsgi.ResourceV21 对象。 + +再去看看 ResourceV21 的 `__call__` + +![](http://image.python-online.cn/20190602220246.png) + +nova show 的接口,在这边获取到的 action_args 是 + +``` +action_args:{'action': u'show', 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9', 'id': u'1c250b15-a346-43c5-9b41-20767ec7c94b'} +``` + +最后调用 `_process_stack` 方法 + +![](http://image.python-online.cn/20190602220511.png) + +在图标处,取得具体的处理函数 meth + +```python +meth :> +``` + +最后,再执行这个函数,取得 action_result,封装成 response 再返回。 + +![](http://image.python-online.cn/20190602220700.png) + + + From 81fff21fee4ff5392ec414230a8ad3308adcce5d Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 3 Jun 2019 00:17:44 +0800 Subject: [PATCH 028/302] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_13.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/source/c08/c08_13.md b/source/c08/c08_13.md index d706632..c752a75 100644 --- a/source/c08/c08_13.md +++ b/source/c08/c08_13.md @@ -68,9 +68,9 @@ APIRouterV21 本身没有实现 `__call__` ,但它的父类 Router实现了 `_ wsgify 在这里,指定了一个参数 `RequestClass=Request`,它所起的作用就是将 req 这个原始请求(dict对象)封装成 Request 对象。 -通过查看 `webob.dec.wsgify()` 源码,wsgify 这个类主要是用于对 WSGI app进行封装,简化wsgi app的定义与编写,它可以很方便的将一个 callable 的函数或对象,封装成一个 WSGI app。 - +通过查看一下wsgify源码,可以发现除此之外,有了 wsgify装饰,被装饰器函数可以返回 wsgi app,wsgify发现如果函数返回的是wsgi app 时,它还会被继续调用,如果返回的还是 wsgi app,就再一层一层往下深入,脱掉外层的大衣,直到最里层的核心 app,执行它并返回它的处理结果。 +总结,wsgify 这个类主要是用于对 WSGI app进行封装,简化wsgi app的定义与编写,它可以很方便的将一个 callable 的函数或对象,封装成一个 WSGI app。 ## 8.13.2 routes 库 @@ -108,5 +108,14 @@ meth : Date: Tue, 4 Jun 2019 00:34:43 +0800 Subject: [PATCH 029/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20pastedeploy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_13.md | 228 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 226 insertions(+), 2 deletions(-) diff --git a/source/c08/c08_13.md b/source/c08/c08_13.md index c752a75..ce0d036 100644 --- a/source/c08/c08_13.md +++ b/source/c08/c08_13.md @@ -14,7 +14,229 @@ curl -g -i -X GET http://localhost:8774/v2.1//servers/detail ## 8.13.1 PasteDeploy -谈到WSGI,就免不了要了解paste,其中paste deploy是用来发现和配置WSGI应用的一套系统,对于WSGI应用的使用者而言,可以方便地从配置文件汇总加载WSGI应用(loadapp);对于WSGI应用的开发人员而言,只需要给自己的应用提供一套简单的入口点即可。 +谈到WSGI,就免不了要了解 Paste,而这边讲的 PasteDeploy 全称是Paste Deployment,原先是Python Paste的一个子项目,现在已经独立出来了。 + +那么 PasteDeploy 到底是做什么的呢? + +根据官方文档(https://pastedeploy.readthedocs.io/en/latest/#introduction)的解释。 + +PasteDeploy 是用来寻找和配置WSGI应用和服务的系统。PasteDeploy给开发者提供了一个简单的函数loadapp。通过这个函数,可以从一个配置文件或者Python egg中加载一个WSGI应用。 + +使用PasteDeploy的其中一个重要意义在于,系统管理员可以安装和管理WSGI应用,而无需掌握与Python和WSGI相关知识。 + +由于 PasteDeploy 原来是属于 Paste 的,现在独立出来了,但是安装的时候还是会安装到paste目录(site-packages\paste\deploy)下。 + +掌握 PasteDeploy ,你只要按照以下三个步骤逐个完成即可。 + +1、配置 PasteDeploy使用的ini文件; + +2、定义WSGI应用; + +3、通过loadapp函数加载WSGI应用; + + + +咱先来做第一步:写paste.ini 文件。 + +在写之前,咱得知道 ini 文件的格式吧。 + +首先,像下面这样一个段叫做 `section`。 + +```ini +[type:name] +key = value +... +``` + +其上的type,主要有如下几种 + +1. `composite` (组合):多个app的路由分发; + + ```ini + [composite:main] + use = egg:Paste#urlmap + / = home + /blog = blog + /wiki = wiki + ``` + +2. app(应用):指明 WSGI 应用的路径; + + ```ini + [app:home] + paste.app_factory = example:Home.factory + ``` + +3. pipeline(管道):给一个 app 绑定多个过滤器。将多个filter和最后一个WSGI应用串联起来。 + + ```ini + [pipeline:main] + pipeline = filter1 filter2 filter3 myapp + + [filter:filter1] + ... + + [filter:filter2] + ... + + [app:myapp] + ... + ``` + +4. filter(过滤器):以 app 做为唯一参数的函数,并返回一个“过滤”后的app。通过键值next可以指定需要将请求传递给谁。next指定的可以是一个普通的WSGI应用,也可以是另一个过滤器。虽然名称上是过滤器,但是功能上不局限于过滤功能,可以是其它功能,例如日志功能,即将认为重要的请求数据记录下来。 + + ```ini + [app-filter:filter_name] + use = egg:... + next = next_app + + [app:next_app] + ... + ``` + + + +最后,必须要知道的 factory 函数: + +1. **paste.app_factory**: 最常用的一个,它指定wsgi app 的路径,通常的用法是paste.app_factory = <模块名>:<类名>.<类方法>。下面几个做个了解就行,不常用。 +2. **paste.composite_factory**: +3. **paste.filter_factory** +4. **paste.filter_app_factory** +5. **paste.server_factory** +6. **paste.server_runner** + +以 nova 中的例子来看下 + +```ini +[app:osapi_compute_app_v21] +paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory +``` + +对 ini 文件有了一定的了解后,就写出如下的 ini 配置文件。 + +```ini +[composite:main] +use = egg:Paste#urlmap +/ = home +/blog = blog +/wiki = wiki + +[app:home] +paste.app_factory = example:Home.factory + +[app:blog] +paste.app_factory = example:Blog.factory + +[app:wiki] +paste.app_factory = example:Wiki.factory +``` + + + +接下来,第二步是定义一个符合 WSGI 规范的 applicaiton 对象。规范的内容可参考我以前写的一篇文章。 + +```python +import os +from paste.deploy import loadapp +from wsgiref.simple_server import make_server + + +class Home(object): + def __init__(self): + print("Init Home.") + + def __call__(self, environ, start_response): + status_code = "200 OK" + response_headers = [("Content-Type", "text/plain")] + response_body = "This is Home's response body.".encode('utf-8') + + start_response(status_code, response_headers) + return [response_body] + + @classmethod + def factory(cls, global_conf, **kwargs): + print("Home factory.") + return Home() +``` + +最后,第三步是使用 loadapp 函数加载 WSGI 应用。 + +loadapp 是 PasteDeploy 提供的一个函数,使用它可以很方便地从第一步的ini配置文件里,加载第二步的 app 对象。 + +loadapp 函数可以接收两个实参: + +- URI:"config:<配置文件的全路径>" +- name:WSGI应用的名称 + +```python +configfile = "paste.ini" +application_name = "main" +applications = loadapp("config:%s" % os.path.abspath(configfile), application_name) +``` + + + +完善并整合第二步和第三步的内容,写成一个 Python 文件(example.py)。内容如下 + +```python +class Blog(object): + def __init__(self): + print("Init Blog.") + + def __call__(self, environ, start_response): + status_code = "200 OK" + response_headers = [("Content-Type", "text/plain")] + response_body = "This is Blog's response body.".encode('utf-8') + + start_response(status_code, response_headers) + return [response_body] + + @classmethod + def factory(cls, global_conf, **kwargs): + print("Blog factory.") + return Blog() + + +class Wiki(object): + def __init__(self): + print("Init Wiki.") + + def __call__(self, environ, start_response): + status_code = "200 OK" + response_headers = [("Content-Type", "text/plain")] + response_body = "This is Wiki's response body.".encode('utf-8') + + start_response(status_code, response_headers) + return [response_body] + + @classmethod + def factory(cls, global_conf, **kwargs): + print("Wiki factory.") + return Wiki() + + +if __name__ == "__main__": + configfile = "paste.ini" + application_name = "main" + port = 8000 + applications = loadapp("config:%s" % os.path.abspath(configfile), application_name) + server = make_server("localhost", port, applications) + print('Started web server at port {}'.format(port)) + server.serve_forever() +``` + + + +一切都准备好后,通过执行下面这条命令来启动 web server + +```shell +python example.py +``` + +如果一切正常,那么打开浏览器,访问http://127.0.0.1:8000/,应该显示:This is Home's response body.;访问http://127.0.0.1:8000/blog,应该显示:This is Blog's response body.;访问http://127.0.0.1:8000/wiki,应该显示:This is Wiki's response body.。 +注意:urlmap对url的大小写是敏感的,例如如果访问http://127.0.0.1:8000/BLOG,在url映射中未能找到大写的BLOG,会使用/映射,最终会显示home页面,即显示:This is Home's response body.。 + + nova 服务启动的时候,会先使用 eventlet.wsgi 启动一个 wsgi 服务器。我们知道 wsgi 启动后,必然要注册一系列的 application,而nova 中资源种类非常多, nova 是如何实现灵绑定注册的呢? @@ -118,4 +340,6 @@ meth : Date: Thu, 6 Jun 2019 18:58:24 +0800 Subject: [PATCH 030/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 1 + source/c04/c04_17.md | 8 ++++++-- source/c07/c07_02.md | 9 ++++++++- source/c08/c08_01.md | 13 +++++++------ source/c08/c08_07.md | 6 +++++- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index 56c4d4b..47a657a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ requests==2.4.3 requests-oauthlib==0.4.2 whitenoise==1.0.3 openpyxl==2.1.5 + diff --git a/source/c04/c04_17.md b/source/c04/c04_17.md index ae968c3..c3eb923 100644 --- a/source/c04/c04_17.md +++ b/source/c04/c04_17.md @@ -231,9 +231,13 @@ class BestPromo(Promotion): ## 4.17.2 单例模式 -之前在另一篇公众号文章看到一个挺搞笑的例子(原文在文末): +之前在另一篇公众号文章看到一个挺搞笑的例子: -大意是讲,老婆在中国其实就是一个活生生的单例,你要娶一个老婆需要去民政局注册登记(要对类进行实例化),当你想再娶一个老婆时,这时民政局会说,不行,你已经有一个老婆了,并且告诉你老婆是谁。 +大意是讲,老婆在中国其实就是一个很形象的单例,你要娶一个老婆需要去民政局注册登记(要对类进行实例化),当你想再娶一个老婆时,这时民政局会说,不行,你已经有一个老婆了,并且它还会告诉你的老婆是谁。 + +然后有个朋友,还很生趣地评论说 + +> **单例模式** 允许你讨无数个老婆,但最终你会发现你讨来的老婆都是同一个人 玩笑之后,再回到我们的话题,先举几类我们经常见到的例子: diff --git a/source/c07/c07_02.md b/source/c07/c07_02.md index b986dad..00fd0e2 100644 --- a/source/c07/c07_02.md +++ b/source/c07/c07_02.md @@ -477,6 +477,10 @@ chmod +x sendmail.sh ![](http://image.python-online.cn/20190404204534.png) +在创建用户的时候,要指定用户组,要注意这个用户组的权限一定要有相应主机组的权限,否则无法发送告警邮件。 + +![](http://image.python-online.cn/20190605173956.png) + 配置好收件人后 ,发件人呢? 发件人需要你在 Zabbix Server 所在的服务器上安装并正确配置好 smtp 服务器。 @@ -491,7 +495,9 @@ service postfix stop 具体你可以参考这篇文章:[zabbix 服务器设置邮件报警](https://www.cnblogs.com/zoulongbin/p/6420239.html) -## 7.2.6 监控数据库 + + +## 7.2.4 监控数据库 zabbix 自带 mysql 的监控模板,监控项不多,只有 14 项。 @@ -626,6 +632,7 @@ update items set history='30d' where hostid in (select hostid from hosts where h - [rsync部署](https://www.cnblogs.com/skyflask/p/7501104.html) - [zabbix客户端自动注册](http://www.ttlsa.com/zabbix/zabbix-active-agent-auto-registration) - [Download and install Zabbix](https://www.zabbix.com/download?zabbix=3.4&os_distribution=centos&os_version=7&db=MySQL) +- [zabbix 自带宏作用域](https://www.zabbix.com/documentation/4.0/manual/appendix/macros/supported_by_location) --- diff --git a/source/c08/c08_01.md b/source/c08/c08_01.md index 8960a1b..2b2869e 100644 --- a/source/c08/c08_01.md +++ b/source/c08/c08_01.md @@ -124,11 +124,12 @@ $ service neutron-linuxbridge-agent restart $ neutron net-create --provider:physical_network phynet1 --provider:network_type flat private # 创建子网 全 -$ neutron subnet-create [--name ming-subnet(通常不写自动生成)] \ - private 192.168.2.0/24 \ - --allocation-pool start=192.168.2.200,end=192.168.2.230 \ - --gateway 192.168.2.253 \ - private PROVIDER_NETWORK_CIDR +$neutron subnet-create --name public\ + --allocation-pool start=172.20.20.100,end=172.20.20.199 \ + --gateway 172.20.20.200 \ + --enable_dhcp=False \ + --dns_nameserver 114.114.114.114 \ + public 172.20.20.0/24 # 创建子网,更多选项可以查看 neutron subnet-create -h neutron subnet-create --name 192.168.2.0/24 --allocation-pool start=192.168.2.200,end=192.168.2.230 --gateway 192.168.2.253 --dns-nameserver 114.114.114.114 --disable-dhcp private 192.168.2.0/24 @@ -155,7 +156,7 @@ glance镜像存放:/var/lib/image https://docs.openstack.org/project-install-guide/baremetal/draft/configure-glance-images.html # 上传镜像 (具体看哪glance help image-create) -$ glance image-create --name centos6.5-old --visibility public --disk-format qcow2 --container-format bare --property ws:predownload=True --file /home/ +$ glance image-create --name centos6.5-old --visibility public --disk-format qcow2 --container-format bare --property ws:predownload=True --file /home/ ``` ### 1.4 keystone diff --git a/source/c08/c08_07.md b/source/c08/c08_07.md index fdb71a0..20b4f69 100644 --- a/source/c08/c08_07.md +++ b/source/c08/c08_07.md @@ -4,6 +4,8 @@ 检查是否有 GPU 设备:`lspci | grep NVIDIA` +(若没有lspci命令,则安装 yum install pciutils -y) + ![](http://image.python-online.cn/20190419144135.png) 此时查看,驱动是 nvidia @@ -46,6 +48,8 @@ 创建gpu直通的虚拟机,在虚拟机内部NIVIDIA显卡的驱动是能检测到自己是跑在虚拟机里的(如下图,先安装 `cpuid` 再执行`cpuid |grep hypervisor_id` ,显示KVM),一旦跑在虚拟机里,就会出错,所以我们需要对显卡驱动隐藏hypervisor id。 +(若没有cpuid,则安装 yum install -y cpuid) + ![](http://image.python-online.cn/20190422205222.png) 如何隐藏 hypervisor id,只需要在xml的feature加上这段。 @@ -92,7 +96,7 @@ openstack image set IMG-UUID --property img_hide_hypervisor_id=true 因此,需要增加一个称重器,,当创建普通类型的虚拟机时,含 GPU (通过pci_device 设备数来决定)的宿主机的权重为最低,为了保证这点,这个称重器的权重系数应最大,暂定为200(可以通过配置项 ws_gpu_weight_multiplier 设置)。 -![](http://image.python-online.cn/20190528141106.png) +![](http://image.python-online.cn/20190606185531.png) ## 8.7.3 使用说明 From a4bba715fe592bc8ce2fd7feec220343d4fae4bf Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 8 Jun 2019 12:00:24 +0800 Subject: [PATCH 031/302] =?UTF-8?q?WSGI=20=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c03/c03_06.md | 725 +++++++++++++++++++++++++++++++++++++++++++ source/c08/c08_05.md | 12 +- source/c08/c08_12.md | 402 ------------------------ source/c08/c08_13.md | 345 -------------------- 4 files changed, 736 insertions(+), 748 deletions(-) create mode 100644 source/c03/c03_06.md delete mode 100644 source/c08/c08_12.md delete mode 100644 source/c08/c08_13.md diff --git a/source/c03/c03_06.md b/source/c03/c03_06.md new file mode 100644 index 0000000..99768a0 --- /dev/null +++ b/source/c03/c03_06.md @@ -0,0 +1,725 @@ +# 3.6 一篇文章搞懂WSGI + +在 三百六十行,行行转 IT 的现状下,很多来自各行各业的同学,都选择 Python 这门胶水语言做为踏入互联网大门的第一块敲门砖,在这些人里,又有相当大比例的同学选择了 Web 开发这个方向(包括我)。而从事 web 开发,绕不过一个知识点,就是 WSGI。 + +不管你是否是这些如上同学中的一员,都应该好好地学习一下这个知识点。 + +由于我本人不从事专业的 python web 开发,所以在写这篇文章的时候,借鉴了许多优秀的网络博客,并花了很多的精力阅读了大量的 OpenStack 代码。 + +为了写这篇文章,零零散散地花了大概两个星期。本来可以拆成多篇文章,写成一个系列的,经过一番思虑,还是准备一篇讲完,这就是本篇文章这么长的原因。 + +另外,一篇文章是不能吃透一个知识点的,本篇涉及的背景知识也比较多的,若我有讲得不到位的,还请你多多查阅其他人的网络博客进一步学习。 + +在你往下看之前,我先问你几个问题,你带着这些问题往下看,可能更有目的性,学习可能更有效果。 + +问1:一个 HTTP 请求到达对应的 application处理函数要经过怎样的过程? + +问2:如何不通过流行的 web 框架来写一个简单的web服务? + +一个HTTP请求的过程可以分为两个阶段,第一阶段是从客户端到WSGI Server,第二阶段是从WSGI Server 到WSGI Application + +![](http://image.python-online.cn/20190607131728.png) + +今天主要是讲第二阶段,主要内容有以下几点: + +1. WSGI 是什么,因何而生? +2. HTTP请求是如何到应用程序的? +3. 实现一个简单的 WSGI Server +4. 实现“高并发”的WSGI Server +5. 第一次路由:PasteDeploy +6. PasteDeploy 使用说明 +7. webob.dec.wsgify 装饰器 +8. 第二次路由:中间件 routes 路由 + +## 3.6.1 WSGI 是什么,因何而生? + +WSGI是 Web Server Gateway Interface 的缩写。 + +它是 Python应用程序(application)或框架(如 Django)和 Web服务器之间的一种接口,已经被广泛接受。 + +它是一种协议,一种规范,其是在 [PEP 3333](https://zhuanlan.zhihu.com/p/27600327) 提出的。这个协议旨在解决众多 web 框架和web server软件的兼容问题。有了WSGI,你不用再因为你使用的web 框架而去选择特定的 web server软件。 + +常见的web应用框架有:Django,Flask等 + +常用的web服务器软件有:uWSGI,Gunicorn等 + +那这个 WSGI 协议内容是什么呢?知乎上有人将 PEP 3333 翻译成中文,写得非常好,我将这段协议的内容搬运过来。 + +> WSGI 接口有服务端和应用端两部分,服务端也可以叫网关端,应用端也叫框架端。服务端调用一个由应用端提供的可调用对象。如何提供这个对象,由服务端决定。例如某些服务器或者网关需要应用的部署者写一段脚本,以创建服务器或者网关的实例,并且为这个实例提供一个应用实例。另一些服务器或者网关则可能使用配置文件或其他方法以指定应用实例应该从哪里导入或获取。 + +WSGI 对于 application 对象有如下三点要求 + +1. 必须是一个可调用的对象 +2. 接收两个必选参数environ、start_response。 +3. 返回值必须是可迭代对象,用来表示http body。 + +## 3.6.2 HTTP请求是如何到应用程序的? + +当客户端发出一个 HTTP 请求后,是如何转到我们的应用程序处理并返回的呢? + +关于这个过程,细节的点这里没法细讲,只能讲个大概。 + +我根据其架构组成的不同将这个过程的实现分为两种: + +![](http://image.python-online.cn/20190607191954.png) + +**1、两级结构** +在这种结构里,uWSGI作为服务器,它用到了HTTP协议以及wsgi协议,flask应用作为application,实现了wsgi协议。当有客户端发来请求,uWSGI接受请求,调用flask app得到相应,之后相应给客户端。 +这里说一点,通常来说,Flask等web框架会自己附带一个wsgi服务器(这就是flask应用可以直接启动的原因),但是这只是在开发阶段用到的,在生产环境是不够用的,所以用到了uwsgi这个性能高的wsgi服务器。 + +**2、三级结构** +这种结构里,uWSGI作为中间件,它用到了uwsgi协议(与nginx通信),wsgi协议(调用Flask app)。当有客户端发来请求,nginx先做处理(静态资源是nginx的强项),无法处理的请求(uWSGI),最后的相应也是nginx回复给客户端的。 +多了一层反向代理有什么好处? + +提高web server性能(uWSGI处理静态资源不如nginx;nginx会在收到一个完整的http请求后再转发给wWSGI) + +nginx可以做负载均衡(前提是有多个服务器),保护了实际的web服务器(客户端是和nginx交互而不是uWSGI) + +## 3.6.3 实现一个简单的 WSGI Server + +在上面的架构图里,不知道你发现没有,有个库叫做 `wsgiref` ,它是 Python 自带的一个 wsgi 服务器模块。 + +从其名字上就看出,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。 + +有了 wsgiref 这个模块,你就可以很快速的启动一个wsgi server。 + +```python +from wsgiref.simple_server import make_server + +# 这里的 appclass 暂且不说,后面会讲到 +app = appclass() +server = make_server('', 64570, app) +server.serve_forever() +``` + +当你运行这段代码后,就会开启一个 wsgi server,监听 `0.0.0.0:64570` ,并接收请求。 + +使用 lsof 命令可以查到确实开启了这个端口 + +![](http://image.python-online.cn/20190607134310.png) + +以上使用 wsgiref 写了一个demo,让你对wsgi有个初步的了解。其由于只适合在学习测试使用,在生产环境中应该另寻他道。 + +## 3.6.4 实现“高并发”的 WSGI Server + +上面我们说不能在生产中使用 wsgiref ,那在生产中应该使用什么呢?选择有挺多的,比如优秀的 uWSGI,Gunicore等。但是今天我并不准备讲这些,一是因为我不怎么熟悉,二是因为我本人从事 OpenStack 的二次开发,对它比较熟悉。 + +所以下面,是我花了几天时间阅读 OpenStack 中的 Nova 组件代码的实现,刚好可以拿过来学习记录一下,若有理解偏差,还望你批评指出。 + +在 nova 组件里有不少服务,比如 nova-api,nova-compute,nova-conductor,nova-scheduler 等等。 + +其中,只有 nova-api 有对外开启 http 接口。 + +要了解这个http 接口是如何实现的,从服务启动入口开始看代码,肯定能找到一些线索。 + +从 Service 文件可以得知 nova-api 的入口是 `nova.cmd.api:main()` + +![](http://image.python-online.cn/20190607140817.png) + +![](http://image.python-online.cn/20190607140922.png) + +打开`nova.cmd.api:main()` ,一起看看是 OpenStack Nova 的代码。 + +在如下的黄框里,可以看到在这里使用了service.WSGIService 启动了一个 server,就是我们所说的的 wsgi server + +![](http://image.python-online.cn/20190530212557.png) + +那这里的 WSGI Server 是依靠什么实现的呢?让我们继续深入源代码。 + +![](http://image.python-online.cn/20190530212753.png) + +wsgi.py 可以看到这里使用了 eventlet 这个网络并发框架,它先开启了一个绿色线程池,从配置里可以看到这个服务器可以接收的请求并发量是 1000 。 + +![](http://image.python-online.cn/20190530212956.png) + +可是我们还没有看到 WSGI Server 的身影,上面使用eventlet 开启了线程池,那线程池里的每个线程应该都是一个服务器吧?它是如何接收请求的? + +再继续往下,可以发现,每个线程都是使用 eventlet.wsgi.server 开启的 WSGI Server,还是使用的 eventlet。 + +由于源代码比较多,我提取了主要的代码,精简如下 + +```python +# 创建绿色线程池 +self._pool = eventlet.GreenPool(self.pool_size) + +# 创建 socket:监听的ip,端口 +bind_addr = (host, port) +self._socket = eventlet.listen(bind_addr, family, backlog=backlog) +dup_socket = self._socket.dup() + +# 整理孵化协程所需的各项参数 +wsgi_kwargs = { + 'func': eventlet.wsgi.server, + 'sock': dup_socket, + 'site': self.app, # 这个就是 wsgi 的 application 函数 + 'protocol': self._protocol, + 'custom_pool': self._pool, + 'log': self._logger, + 'log_format': CONF.wsgi.wsgi_log_format, + 'debug': False, + 'keepalive': CONF.wsgi.keep_alive, + 'socket_timeout': self.client_socket_timeout +} + +# 孵化协程 +self._server = utils.spawn(**wsgi_kwargs) +``` + +![](http://image.python-online.cn/20190530214820.png) + +就这样,nova 开启了一个可以接受1000个绿色协程并发的 WSGI Server。 + +## 3.6.5 第一次路由:PasteDeploy + +上面我们提到 WSGI Server 的创建要传入一个 Application,用来处理接收到的请求,对于一个有多个 app 的项目。 + +比如,你有一个个人网站提供了如下几个模块 + +``` +/blog # 博客 app +/wiki # wiki app +``` + +如何根据 请求的url 地址,将请求转发到对应的application上呢? + +答案是,使用 PasteDeploy 这个库(在 OpenStack 中各组件被广泛使用)。 + +PasteDeploy 到底是做什么的呢? + +根据 [官方文档](https://pastedeploy.readthedocs.io/en/latest/#introduction) 的说明,翻译如下 + +> PasteDeploy 是用来寻找和配置WSGI应用和服务的系统。PasteDeploy给开发者提供了一个简单的函数loadapp。通过这个函数,可以从一个配置文件或者Python egg中加载一个WSGI应用。 + +使用PasteDeploy的其中一个重要意义在于,系统管理员可以安装和管理WSGI应用,而无需掌握与Python和WSGI相关知识。 + +由于 PasteDeploy 原来是属于 Paste 的,现在独立出来了,但是安装的时候还是会安装到paste目录(site-packages\paste\deploy)下。 + +我会先讲下在 Nova 中,是如何借助 PasteDeploy 实现对url的路由转发。 + +还记得在上面创建WSGI Server的时候,传入了一个 self.app 参数,这个app并不是一个固定的app,而是使用 PasteDeploy 中提供的 loadapp 函数从 paste.ini 配置文件中加载application。 + +具体可以,看下nova的实现。 + +![](http://image.python-online.cn/20190530221101.png) + +通过打印的 DEBUG 内容得知 config_url 和 app name 的值 + +``` +app: osapi_compute +config_url: /etc/nova/api-paste.inia +``` + +通过查看 `/etc/nova/api-paste.ini` ,在 composite 段里找到了 `osapi_compute` 这个app(这里的app和wsgi app 是两个概念,需要注意区分) ,可以看出 nova 目前有两个版本的api,一个是 v2,一个是v2.1,目前我们在用的是 v2.1,从配置文件中,可以得到其指定的 application 的路径是`nova.api.openstack.compute` 这个模块下的 APIRouterV21 类 的factory方法,这是一个工厂函数,返回 APIRouterV21 实例。 + +```ini +[composite:osapi_compute] +use = call:nova.api.openstack.urlmap:urlmap_factory +/: oscomputeversions +/v2: openstack_compute_api_v21_legacy_v2_compatible +/v2.1: openstack_compute_api_v21 + +[app:osapi_compute_app_v21] +paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory +``` + +这是 OpenStack 使用 PasteDeploy 实现的第一层的路由,如果你不感兴趣,可以直接略过本节,进入下一节,下一节是 介绍 PasteDeploy 的使用,教你实现一个简易的web server demo。推荐一定要看。 + +## 3.6.6 PasteDeploy 使用说明 + +到上一步,我已经得到了 application 的有用的线索。考虑到很多人是第一次接触 PasteDeploy,所以这里结合网上博客做了下总结。对你入门会有帮助。 + +掌握 PasteDeploy ,你只要按照以下三个步骤逐个完成即可。 + +1、配置 PasteDeploy使用的ini文件; + +2、定义WSGI应用; + +3、通过loadapp函数加载WSGI应用; + + + +**第一步:写 paste.ini 文件** + +在写之前,咱得知道 ini 文件的格式吧。 + +首先,像下面这样一个段叫做 `section`。 + +```ini +[type:name] +key = value +... +``` + +其上的type,主要有如下几种 + +1. `composite` (组合):多个app的路由分发; + + ```ini + [composite:main] + use = egg:Paste#urlmap + / = home + /blog = blog + /wiki = wiki + ``` + +2. app(应用):指明 WSGI 应用的路径; + + ```ini + [app:home] + paste.app_factory = example:Home.factory + ``` + +3. pipeline(管道):给一个 app 绑定多个过滤器。将多个filter和最后一个WSGI应用串联起来。 + + ```ini + [pipeline:main] + pipeline = filter1 filter2 filter3 myapp + + [filter:filter1] + ... + + [filter:filter2] + ... + + [app:myapp] + ... + ``` + +4. filter(过滤器):以 app 做为唯一参数的函数,并返回一个“过滤”后的app。通过键值next可以指定需要将请求传递给谁。next指定的可以是一个普通的WSGI应用,也可以是另一个过滤器。虽然名称上是过滤器,但是功能上不局限于过滤功能,可以是其它功能,例如日志功能,即将认为重要的请求数据记录下来。 + + ```ini + [app-filter:filter_name] + use = egg:... + next = next_app + + [app:next_app] + ... + ``` + +对 ini 文件有了一定的了解后,就可以看懂下面这个 ini 配置文件了 + +```ini +[composite:main] +use = egg:Paste#urlmap +/blog = blog +/wiki = wiki + +[app:blog] +paste.app_factory = example:Blog.factory + +[app:wiki] +paste.app_factory = example:Wiki.factory +``` + +**第二步是定义一个符合 WSGI 规范的 applicaiton 对象。** + +符合 WSGI 规范的 application 对象,可以有多种形式,函数,方法,类,实例对象。这里仅以实例对象为例(需要实现 `__call__` 方法),做一个演示。 + +```python +import os +from paste import deploy +from wsgiref.simple_server import make_server + +class Blog(object): + def __init__(self): + print("Init Blog.") + + def __call__(self, environ, start_response): + status_code = "200 OK" + response_headers = [("Content-Type", "text/plain")] + response_body = "This is Blog's response body.".encode('utf-8') + + start_response(status_code, response_headers) + return [response_body] + + @classmethod + def factory(cls, global_conf, **kwargs): + print("Blog factory.") + return Blog() +``` + +**最后,第三步是使用 loadapp 函数加载 WSGI 应用。** + +loadapp 是 PasteDeploy 提供的一个函数,使用它可以很方便地从第一步的ini配置文件里加载 app + +loadapp 函数可以接收两个实参: + +- URI:"config:<配置文件的全路径>" +- name:WSGI应用的名称 + +```python +conf_path = os.path.abspath('paste.ini') + +# 加载 app +applications = deploy.loadapp("config:{}".format(conf_path) , "main") + +# 启动 server, 监听 localhost:22800 +server = make_server("localhost", "22800", applications) +server.serve_forever() +``` + +applications 是URLMap 对象。 + +![](http://image.python-online.cn/20190607154119.png) + +完善并整合第二步和第三步的内容,写成一个 Python 文件(wsgi_server.py)。内容如下 + +```python +import os +from paste import deploy +from wsgiref.simple_server import make_server + +class Blog(object): + def __init__(self): + print("Init Blog.") + + def __call__(self, environ, start_response): + status_code = "200 OK" + response_headers = [("Content-Type", "text/plain")] + response_body = "This is Blog's response body.".encode('utf-8') + + start_response(status_code, response_headers) + return [response_body] + + @classmethod + def factory(cls, global_conf, **kwargs): + print("Blog factory.") + return Blog() + + +class Wiki(object): + def __init__(self): + print("Init Wiki.") + + def __call__(self, environ, start_response): + status_code = "200 OK" + response_headers = [("Content-Type", "text/plain")] + response_body = "This is Wiki's response body.".encode('utf-8') + + start_response(status_code, response_headers) + return [response_body] + + @classmethod + def factory(cls, global_conf, **kwargs): + print("Wiki factory.") + return Wiki() + + +if __name__ == "__main__": + app = "main" + port = 22800 + conf_path = os.path.abspath('paste.ini') + + # 加载 app + applications = deploy.loadapp("config:{}".format(conf_path) , app) + server = make_server("localhost", port, applications) + + print('Started web server at port {}'.format(port)) + server.serve_forever() +``` + +一切都准备好后,在终端执行 ` python wsgi_server.py`来启动 web server + +![](http://image.python-online.cn/20190607155432.png) + +如果像上图一样一切正常,那么打开浏览器 + +- 访问http://127.0.0.1:8000/blog,应该显示:This is Blog's response body. +- 访问http://127.0.0.1:8000/wiki,应该显示:This is Wiki's response body.。 + +注意:urlmap对url的大小写是敏感的,例如如果访问http://127.0.0.1:8000/BLOG,在url映射中未能找到大写的BLOG。 + +到此,你学会了使用 PasteDeploy 的简单使用。 + +## 3.6.7 webob.dec.wsgify 装饰器 + +经过了 PasteDeploy 的路由调度,我们找到了 `nova.api.openstack.compute:APIRouterV21.factory` 这个 application 的入口,看代码知道它其实返回了 APIRouterV21 类的一个实例。 + +![](http://image.python-online.cn/20190602173212.png) + +WSGI规定 application 必须是一个 callable 的对象,函数、方法、类、实例,若是一个类实例,就要求这个实例所属的类实现 `__call__` 的方法。 + +APIRouterV21 本身没有实现 `__call__` ,但它的父类 Router实现了 `__call__` + +![](http://image.python-online.cn/20190602173956.png) + +我们知道,application 必须遵丛 WSGI 的规范 + +1. 必须接收`environ`, `start_response`两个参数; +2. 必须返回 「可迭代的对象」。 + +但从 Router 的 `__call__` 代码来看,它并没有遵从这个规范,它不接收这两个参数,也不返回 response,而只是返回另一个 callable 的对象,就这样我们的视线被一次又一次的转移,但没有关系,这些`__call__`都是外衣,只要扒掉这些外衣,我们就能看到核心app。 + +而负责扒掉这层外衣的,就是其头上的装饰器 `@webob.dec.wsgify` ,wsgify 是一个类,其 `__call__` 源码实现如下:![](http://image.python-online.cn/20190605203016.png) + +可以看出,wsgify 在这里,会将 req 这个原始请求(dict对象)封装成 Request 对象(就是规范1里提到的 environ)。然后会一层一层地往里地执行被wsgify装饰的函数(self._route), 得到最内部的核心application。 + +上面提到了规范1里的第一个参数,补充下第二个参数start_response,它是在哪定义并传入的呢? + +其实这个无需我们操心,它是由 wsgi server 提供的,如果我们使用的是 wsgiref 库做为 server 的话。那这时的 start_response 就由 wsgiref 提供。 + +再回到 wsgify,它的作用主要是对 WSGI app 进行封装,简化wsgi app的定义与编写,它可以很方便的将一个 callable 的函数或对象,封装成一个 WSGI app。 + +上面,其实留下了一个问题,self.\_route(routes 中间件 RoutesMiddleware对象)是如何找到真正的 application呢? + +带着这个问题,我们了解下 routes 是如何为我们实现第二次路由。 + +## 3.6.8 第二次路由:中间件 routes 路由 + +routes 库在 WSGI app 的路由调度上有着举足轻重的作用。 + +首先是上面提到的 `routes.middleware.RoutesMiddleware` ,从命名上看,知道这是一个中间件,它将接受到的 url,自动调用 `map.match() `方法,对 url 进行路由匹配,并将匹配的结果存入request请求的环境变量`['wsgiorg.routing_args']`,最后会调用`self._dispatch`,它会判断匹配结果,如果匹配到了application就继续,反之没有匹配结果,就抛出异常。 + +在 `self._dispatch` 函数里,我们看到了 app,controller 这几个很重要的字眼,其是否就是我们苦苦追寻的 application 对象呢? + +![](http://image.python-online.cn/20190531211542.png) + +要难这个问题,只要看清 match 到是什么东西? + +这个 match 对象 是在 `RoutesMiddleware.__call__()` 里塞进 `req.environ` 的,它是什么东西呢,我将其打印出来。 + +``` +{'action': u'detail', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'} + +{'action': u'index', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'} + +{'action': u'show', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9', 'id': u'68323d9c-ebe5-499a-92e9-32fea900a892'} +``` + +结果令人在失所望呀,这个 app 并不是我们要寻找的 Controller 对象。而是 nova.api.openstack.wsgi.ResourceV21 类的实例对象,说白了就是Resource 对象。 + +看到这里,我有心态有点要崩了,怎么还没到 Controller?OpenStack 框架的代码绕来绕去的,没有点耐心还真的很难读下去。 + +既然已经开了头,没办法还得硬着头皮继续读了下去。 + +终于我发现,在APIRouter初始化的时候,它会去注册所有的 Resource,同时将这些 Resource 交由 routes.Mapper 来管理、创建路由映射,所以上面提到的 routes.middleware.RoutesMiddleware 才能根据url通过 mapper.match 获取到相应的Resource。 + +从 Nova 代码中看出每个Resource 对应一个 Controller 对象,因为 Controller 对象本身就是对一种资源的操作集合。 + +![](http://image.python-online.cn/20190531225529.png) + +通过日志的打印,可以发现 nova 管理的 Resource 对象有多么的多而杂 + +``` +os-server-groups +os-keypairs +os-availability-zone +remote-consoles +os-simple-tenant-usage +os-instance-actions +os-migrations +os-hypervisors +diagnostics +os-agents +images +os-fixed-ips +os-networks +os-security-groups +os-security-groups +os-security-group-rules +flavors +os-floating-ips-bulk +os-console-auth-tokens +os-baremetal-nodes +os-cloudpipe +os-server-external-events +os-instance_usage_audit_log +os-floating-ips +os-security-group-default-rules +os-tenant-networks +os-certificates +os-quota-class-sets +os-floating-ip-pools +os-floating-ip-dns +entries +os-aggregates +os-fping +os-server-password +os-flavor-access +consoles +os-extra_specs +os-interface +os-services +servers +extensions +metadata +metadata +limits +ips +os-cells +versions +tags +migrations +os-hosts +os-virtual-interfaces +os-assisted-volume-snapshots +os-quota-sets +os-volumes +os-volumes_boot +os-volume_attachments +os-snapshots +os-server-groups +os-keypairs +os-availability-zone +remote-consoles +os-simple-tenant-usage +os-instance-actions +os-migrations +os-hypervisors +diagnostics +os-agents +images +os-fixed-ips +os-networks +os-security-groups +os-security-groups +os-security-group-rules +flavors +os-floating-ips-bulk +os-console-auth-tokens +os-baremetal-nodes +os-cloudpipe +os-server-external-events +os-instance_usage_audit_log +os-floating-ips +os-security-group-default-rules +os-tenant-networks +os-certificates +os-quota-class-sets +os-floating-ip-pools +os-floating-ip-dns +entries +os-aggregates +os-fping +os-server-password +os-flavor-access +consoles +os-extra_specs +os-interface +os-services +servers +extensions +metadata +metadata +limits +ips +os-cells +versions +tags +migrations +os-hosts +os-virtual-interfaces +os-assisted-volume-snapshots +os-quota-sets +os-volumes +os-volumes_boot +os-volume_attachments +os-snapshots +``` + +你一定很好奇,这路由是如何创建的吧,关键代码就是如下一行。如果你想要了解更多路由的创建过程,可以看一下这篇文章([Python Route总结](https://blog.csdn.net/bellwhl/article/details/8956088)),写得不错。 + +```python +routes.mapper.connect("server", + "/{project_id}/servers/list_vm_state", + controller=self.resources['servers'], + action='list_vm_state', + conditions={'list_vm_state': 'GET'}) + +``` + +历尽了千辛万苦,我终于找到了 Controller 对象,知道了请求发出后,wsgi server是如何根据url找到对应的Controller(根据routes.Mapper路由映射)。 + +但是很快,你又会问。对于一个资源的操作(action),有很多,比如新增,删除,更新等 + +不同的操作要执行Controller 里不同的函数。 + +如果是新增资源,就调用 create() + +如果是删除资源,就调用 delete() + +如果是更新资源,就调用 update() + +那代码如何怎样知道要执行哪个函数呢? + +以/servers/xxx/action请求为例,请求调用的函数实际包含在请求的body中。 + +经过routes.middleware.RoutesMiddleware的`__call__`函数解析后,此时即将调用的Resource已经确定为哪个模块中的Controller所构建的Resource,而 action 参数为"action",接下来在Resource的`__call__` 函数里面会因为action=="action"从而开始解析body的内容,找出Controller中所对应的方法。 + +Controller在构建的过程中会由于MetaClass的影响将其所有action类型的方法填入一个字典中,key由每个`_action_xxx`方法前的 `@wsgi.action('xxx')`装饰函数给出,value为每个_action_xxx方法的名字(从中可以看出规律,在body里面请求的方法名前加上_aciton_即为Controller中对应调用的方法)。 + +之后在使用Controller构建Resource对象的过程中会向Resource注册该Controller的这个字典中的内容。这样,只需在请求的body中给出调用方法的key,然后就可以找到这个key所映射的方法,最后在Resource的__call__函数中会调用Controller类的这个函数! + +其实我在上面我们打印 match 对象时,就已经将对应的函数打印出来了。 + +这边以 nova show(展示资源为例),来理解一下。 + +当你调用 nova show [uuid] 命令,novaclient 就会给 nova-api 发送一个http的请求 + +```shell +nova show 1c250b15-a346-43c5-9b41-20767ec7c94b + +``` + +通过打印得到的 match 对象如下 + +``` +{'action': u'show', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'} + +``` + +其中 action 就是对应的处理函数,而controller 就对应的 Resource 对象,project_id 是租户id(你可以不理会)。 + +继续看 ResourceV21 类里的 `__call__` 函数的代码。 + +图示地方,会从 environ 里获取中看到获取 action 的具体代码 + +![](http://image.python-online.cn/20190602220246.png) + +我将这边的 action_args打印出来 + +``` +{'action': 'show', 'project_id': '2ac17c7c792d45eaa764c30bac37fad9', 'id': '1c250b15-a346-43c5-9b41-20767ec7c94b'} + +``` + +其中 action 还是是函数名,id 是要操作的资源的唯一id标识。 + +在 `__call__` 的最后,会 调用 `_process_stack` 方法 + +![](http://image.python-online.cn/20190602220511.png) + +在图标处,get_method 会根据 action(函数名) 取得处理函数对象。 + +```python +meth :> + +``` + +最后,再执行这个函数,取得 action_result,在 `_process_stack` 会对 response 进行初步封装。 + +![](http://image.python-online.cn/20190602220700.png) + +然后将 response 再返回到 wsgify ,由这个专业的工具函数,进行 response 的最后封装和返回给客户端。 + +![](http://image.python-online.cn/20190605203016.png) + +至此,一个请求从发出到响应就结束了。 + +------ + +## 附录:参考文章: + +- [PEP 3333 中文翻译](https://zhuanlan.zhihu.com/p/27600327) +- [nova-api源码分析(APP的调用)](https://www.cnblogs.com/littlebugfish/p/4660595.html) +- [Python Route总结](https://blog.csdn.net/bellwhl/article/details/8956088) +- [Python routes Mapper 的使用](https://blog.csdn.net/bellwhl/article/details/8956088) +- [详解 Paste deploy](https://www.cnblogs.com/Security-Darren/p/4087587.html) +- [paste.ini 文件使用说明](https://blog.csdn.net/hzrandd/article/details/10834381) +- [PasteDeploy 小白教程](http://www.fmttr.com/python/thirdpartylibrary/pastedeploy/) +- [WSGI 两种架构图](https://blog.csdn.net/baidu_35085676/article/details/80184874) +- [伯乐在线:Python Web开发最难懂的WSGI协议](http://python.jobbole.com/88653/) + +------ + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index fa7d310..b37b5fd 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -323,9 +323,19 @@ nova-api 返回的结果令人无法理解: ![](http://image.python-online.cn/20190526143235.png) -## 8.5.11 nova的各项服务服务是如何启动的? +## 8.5.11 nova-compute 如何启动的? +从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 `nova.cmd.compute:main()` +![](http://image.python-online.cn/20190526205152.png) + +从这个入口进去,会开启一个 `nova-compute` 的服务。 + +![](http://image.python-online.cn/20190526165007.png) + +当调用 service.Service.create 时(create 是一个工厂函数),实际是返回实例化的 service.Service 对象。当没有传入 manager 时,就会以binary 里的为准。比如binary 是` nova-compute`,那manager_cls 就是 `compute_manager`,对应的manager 导入路径,会从配置里读取。 + +![](http://image.python-online.cn/20190526204328.png) ## 8.5.13 支持指定子网和指定ip diff --git a/source/c08/c08_12.md b/source/c08/c08_12.md deleted file mode 100644 index 8dbc68f..0000000 --- a/source/c08/c08_12.md +++ /dev/null @@ -1,402 +0,0 @@ -# 8.12 从nova-api的启动理解wsgi - -nova 里有不少服务,比如 nova-compute,nova-api,nova-conductor,nova-scheduler 等。 - -这些服务如何都是如何启动的呢?他们其实都是用同一套代码,所以只要分析一个就行,这里以nova-compute为例来了解一下。 - -从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 `nova.cmd.compute:main()` - -![](http://image.python-online.cn/20190526205152.png) - -从这个入口进去,会开启一个 `nova-compute` 的服务。 - -![](http://image.python-online.cn/20190526165007.png) - -当调用 service.Service.create 时(create 是一个工厂函数),实际是返回实例化的 service.Service 对象。当没有传入 manager 时,就以binary 里的为准。比如binary 是` nova-compute`,那manager_cls 就是 `compute_manager`,对应的manager 导入路径,就会从配置里读取。 - -![](http://image.python-online.cn/20190526204328.png) - -通过了解了上面 nova-compute 的启动过程,nova-api 也是相同的思路,它的入口是`nova.cmd.api:main()`,不过和nova-compute不一样的是,nova-compute没有对外接口,而nova-api 有,所以它会启动一个 wsgi 服务器。 - -接下来,我们就来一起看看是 OpenStack Nova 是如何启动一个 WSGI 服务器的。 - -在如下的黄框里,可以看到在这里启动了一个 server,就是我们所说的的 wsgi server - -![](http://image.python-online.cn/20190530212557.png) - -![](http://image.python-online.cn/20190530212753.png) - -再进入 wsgi.py 可以看到这里使用了 eventlet 这个并发库,它开启了一个绿色线程池,从配置里可以看到这个wsgi 服务器可以接收的请求并发量是 1000 。 - -![](http://image.python-online.cn/20190530212956.png) - -你接下来感兴趣的应该是,这个线程池里的每个线程都是啥?是如何接收请求的? - -通过对源码的阅读,可以得知是通过socket接收请求的。 - -由于代码较多,我把提取了主要的代码,精简如下 - -```python -# 创建绿色线程池 -self._pool = eventlet.GreenPool(self.pool_size) - -# 创建 socket:有监听的ip,端口 -bind_addr = (host, port) -self._socket = eventlet.listen(bind_addr, family, backlog=backlog) -dup_socket = self._socket.dup() - -# 整理孵化协程所需的各项参数 -wsgi_kwargs = { - 'func': eventlet.wsgi.server, - 'sock': dup_socket, - 'site': self.app, # 这个就是 wsgi 的 application 函数 - 'protocol': self._protocol, - 'custom_pool': self._pool, - 'log': self._logger, - 'log_format': CONF.wsgi.wsgi_log_format, - 'debug': False, - 'keepalive': CONF.wsgi.keep_alive, - 'socket_timeout': self.client_socket_timeout -} - -# 孵化协程 -self._server = utils.spawn(**wsgi_kwargs) -``` - - - -![](http://image.python-online.cn/20190530214820.png) - -我们都知道 wsgi 要传入一个 application,用来处理接收到的请求,是我们整个服务的关键入口,那这里的 app 是哪个呢?其实在上面代码中我有注释: self.app 。 - -下面这行就是 self.app 的来源,通过查看我打印的 DEBUG 内容得知 config_url 和 app name 的值 - -![](http://image.python-online.cn/20190530221101.png) - -再往代码中看,其实这个 app 不是直接写死成一个具体的函数对象,而是通过解析 paste.ini 配置文件来取得具体的 application 路径,然后导入。 - -而 paste.ini 文件的解析是通过 Python 的第三方库 `paste` - -下图我截取了其主要的代码 - -![](http://image.python-online.cn/20190530220957.png) - -通过上面DEBUG日志,我们知道了 `uri =/etc/nova/api-paste.ini` ,查看 `/etc/nova/api-paste.ini` ,果然可以找到 `osapi_compute` 这个app,从这个路由表,可以得到 application 的的路径(对于 paste.ini 可以查阅这篇文章:[python 中paste.ini文件使用说明](https://blog.csdn.net/hzrandd/article/details/10834381) ),是`nova.api.openstack.compute` 这个模块下的 APIRouterV21 类 的factory方法。 - -```shell -# 先路由一下 -[composite:osapi_compute] -use = call:nova.api.openstack.urlmap:urlmap_factory -/: oscomputeversions -/v2: openstack_compute_api_v21_legacy_v2_compatible -/v2.1: openstack_compute_api_v21 - -[app:osapi_compute_app_v21] -paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory -``` - - - -真正的callable对象,但这个不是app - -![](http://image.python-online.cn/20190531205427.png) - -真正的app是这个 - -![](http://image.python-online.cn/20190531211542.png) - -重要的是在这个 match ,这是在 `RoutesMiddleware.__call__()` 里塞进 `req.environ` 的。 - -里面放着什么东西呢? - -我把这个打印出来 - -``` -{'action': u'detail', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'} - -{'action': u'index', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'} - -{'action': u'show', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9', 'id': u'68323d9c-ebe5-499a-92e9-32fea900a892'} -``` - - - -这里的controller就是在APIRouter初始化中设置的controller,也就是使用相应 Controller 类初始化的 Resource 实例。这些 Resource 是如何注册上去的呢,这个在 `APIRouterV21` 初始化的时候就会去做,将这些都通过 C:\Python27\Lib\site-packages\routes\mapper.py 注册到 route map上去 - -![](http://image.python-online.cn/20190531225529.png) - -通过日志打印,可以发现有这些Resource - -``` -os-server-groups -os-keypairs -os-availability-zone -remote-consoles -os-simple-tenant-usage -os-instance-actions -os-migrations -os-hypervisors -diagnostics -os-agents -images -os-fixed-ips -os-networks -os-security-groups -os-security-groups -os-security-group-rules -flavors -os-floating-ips-bulk -os-console-auth-tokens -os-baremetal-nodes -os-cloudpipe -os-server-external-events -os-instance_usage_audit_log -os-floating-ips -os-security-group-default-rules -os-tenant-networks -os-certificates -os-quota-class-sets -os-floating-ip-pools -os-floating-ip-dns -entries -os-aggregates -os-fping -os-server-password -os-flavor-access -consoles -os-extra_specs -os-interface -os-services -servers -extensions -metadata -metadata -limits -ips -os-cells -versions -tags -migrations -os-hosts -os-virtual-interfaces -os-assisted-volume-snapshots -os-quota-sets -os-volumes -os-volumes_boot -os-volume_attachments -os-snapshots -os-server-groups -os-keypairs -os-availability-zone -remote-consoles -os-simple-tenant-usage -os-instance-actions -os-migrations -os-hypervisors -diagnostics -os-agents -images -os-fixed-ips -os-networks -os-security-groups -os-security-groups -os-security-group-rules -flavors -os-floating-ips-bulk -os-console-auth-tokens -os-baremetal-nodes -os-cloudpipe -os-server-external-events -os-instance_usage_audit_log -os-floating-ips -os-security-group-default-rules -os-tenant-networks -os-certificates -os-quota-class-sets -os-floating-ip-pools -os-floating-ip-dns -entries -os-aggregates -os-fping -os-server-password -os-flavor-access -consoles -os-extra_specs -os-interface -os-services -servers -extensions -metadata -metadata -limits -ips -os-cells -versions -tags -migrations -os-hosts -os-virtual-interfaces -os-assisted-volume-snapshots -os-quota-sets -os-volumes -os-volumes_boot -os-volume_attachments -os-snapshots -``` - - - -外部的请求,是如何映射到这些controller的呢? - -那就要归功于 `routes` ,用来给服务内部定义url到具体函数的映射,这个和 deploy 提供的url到服务映射功能有相同也有不同,相同的是都是url到服务,不同的是deploy的层级更高一点,比如 `/v2/project_id/servers/`这个请求的url,deploy会将 `/v2` 的请求交给openstack_compute_api_v2,而再后面的活就全交给` routes `,routes 会将 `project_id/servers/` 的GET请求交给nova.api.openstack.compute.servers.Controller.index(),而 POST请求交给 create() 方法处理。这就是 routes.mappers 所提供的功能,根据path和请求方法,将请求映射到具体的函数上。 - -deploy 前面已经讲过,现在要来看看 routes。 - -```python -self.resources['servers'] = servers.create_resource(ext_mgr) - -mapper.resource("server", "servers", - controller=self.resources['servers'], - collection={'list_vm_state': 'GET', 'os_vmsum': 'GET'}) -``` - - - -```python -routes.mapper.connect("server", - "/{project_id}/servers/list_vm_state", - controller=self.resources['servers'], - action='list_vm_state', - conditions={'list_vm_state': 'GET'}) -mapper.connect("server", - "/{project_id}/servers/os_vmsum", - controller=self.resources['servers'], - action='os_vmsum', - conditions={'os_vmsum': 'GET'}) -``` - - - -这些 Resource都有一个 - -![](http://image.python-online.cn/20190531230749.png) - -所以接着调用`nova.api.openstack.wsgi.Resource.__call__` 函数, - -![](http://image.python-online.cn/20190531215728.png) - -该函数最终是调用 `_process_stack` 函数,通过environ['wsgiorg.routing_args'],获取上面设置的match。该match有一个 action 属性,它指定了所要调用Controller成员函数的名字,以及其它相关的调用参数。 - -```python -def _process_stack(self, request, action, action_args, - content_type, body, accept): - """Implement the processing stack.""" - - # Get the implementing method - try: - meth, extensions = self.get_method(request, action, - content_type, body) - except (AttributeError, TypeError): - return Fault(webob.exc.HTTPNotFound()) - except KeyError as ex: - msg = _("There is no such action: %s") % ex.args[0] - return Fault(webob.exc.HTTPBadRequest(explanation=msg)) - except exception.MalformedRequestBody: - msg = _("Malformed request body") - return Fault(webob.exc.HTTPBadRequest(explanation=msg)) - - if body: - msg = _("Action: '%(action)s', calling method: %(meth)s, body: " - "%(body)s") % {'action': action, - 'body': six.text_type(body, 'utf-8'), - 'meth': str(meth)} - LOG.debug(strutils.mask_password(msg)) - else: - LOG.debug("Calling method '%(meth)s'", - {'meth': str(meth)}) - - # Now, deserialize the request body... - try: - contents = {} - if self._should_have_body(request): - # allow empty body with PUT and POST - if request.content_length == 0: - contents = {'body': None} - else: - contents = self.deserialize(body) - except exception.MalformedRequestBody: - msg = _("Malformed request body") - return Fault(webob.exc.HTTPBadRequest(explanation=msg)) - - # Update the action args - action_args.update(contents) - - project_id = action_args.pop("project_id", None) - context = request.environ.get('nova.context') - if (context and project_id and (project_id != context.project_id)): - msg = _("Malformed request URL: URL's project_id '%(project_id)s'" - " doesn't match Context's project_id" - " '%(context_project_id)s'") % \ - {'project_id': project_id, - 'context_project_id': context.project_id} - return Fault(webob.exc.HTTPBadRequest(explanation=msg)) - - response = None - try: - with ResourceExceptionHandler(): - action_result = self.dispatch(meth, request, action_args) - except Fault as ex: - response = ex - - if not response: - # No exceptions; convert action_result into a - # ResponseObject - resp_obj = None - if type(action_result) is dict or action_result is None: - resp_obj = ResponseObject(action_result) - elif isinstance(action_result, ResponseObject): - resp_obj = action_result - else: - response = action_result - - # Run post-processing extensions - if resp_obj: - # Do a preserialize to set up the response object - if hasattr(meth, 'wsgi_code'): - resp_obj._default_code = meth.wsgi_code - # Process extensions - response = self.process_extensions(extensions, resp_obj, - request, action_args) - - if resp_obj and not response: - response = resp_obj.serialize(request, accept) - - if hasattr(response, 'headers'): - for hdr, val in list(response.headers.items()): - # Headers must be utf-8 strings - response.headers[hdr] = utils.utf8(val) - - if not request.api_version_request.is_null(): - response.headers[API_VERSION_REQUEST_HEADER] = \ - 'compute ' + request.api_version_request.get_string() - response.headers[LEGACY_API_VERSION_REQUEST_HEADER] = \ - request.api_version_request.get_string() - response.headers.add('Vary', API_VERSION_REQUEST_HEADER) - response.headers.add('Vary', LEGACY_API_VERSION_REQUEST_HEADER) - - return response -``` - - - -在我们定义Controller的成员函数时,一般需要通过nova.api.openstack.wsgi.{serializers, deserializers}来指定解释body内容的模板,可以是xml或者json格式的。前面说过重定义nova.api.openstack.urlmap.URLMap的目的是为了判断content_type。Resource接着在解析body时会参考content_type,然后调用相应的解析器进行body解析(如XMLDeserializer、JSONDeserializer),接着将解析出的参数更新进action_args,使用action_args来调用Controller成员函数,即最终的http请求处理函数。最后将执行结果使用指定的序列化器序列化,并返回结果。 - - - -这里特别说明一下对常见RESTful请求之外的action请求的处理。以/servers/xxx/action请求为例,请求调用的函数实际包含在请求的body中。经过routes.middleware.RoutesMiddleware的__call__函数解析后,此时即将调用的Resource已经确定为哪个模块中的Controller所构建的Resource,而action参数为"action",接下来在Resource的__call__函数里面会因为action=="action"从而开始解析body的内容,找出Controller中所对应的方法。Controller在构建的过程中会由于MetaClass的影响将其所有action类型的方法填入一个字典中,key由每个_action_xxx方法前的@wsgi.action('xxx')装饰函数给出,value为每个_action_xxx方法的名字(从中可以看出规律,在body里面请求的方法名前加上_aciton_即为Controller中对应调用的方法)。之后在使用Controller构建Resource对象的过程中会向Resource注册该Controller的这个字典中的内容。这样,只需在请求的body中给出调用方法的key,然后就可以找到这个key所映射的方法,最后在Resource的__call__函数中会调用Controller类的这个函数! - - - -[nova-api源码分析(APP的调用)](https://www.cnblogs.com/littlebugfish/p/4660595.html) - -[route 举例总结](https://blog.csdn.net/bellwhl/article/details/8956088) \ No newline at end of file diff --git a/source/c08/c08_13.md b/source/c08/c08_13.md deleted file mode 100644 index ce0d036..0000000 --- a/source/c08/c08_13.md +++ /dev/null @@ -1,345 +0,0 @@ -# 8.13 WSGI工作原理:Route - -最近在看 OpenStack 的源码中,准备研究一下一个 API 请求需要经过怎样的过程才能到达指定的 application 函数。 - -在阅读过程中,发现有好几个陌生的库,导致阅读的过程中,理解出现了断层,越深入源码越抽象。在这时先一个一个库进行攻库。 - -OpenStack 的路由分两部分,这里先举个例子,如下面这个请求 - -``` -curl -g -i -X GET http://localhost:8774/v2.1//servers/detail -``` - -一个是上层的路由,由 deploy.paste 来完成的,主要是 api 请求的 url 来判断所调用的api版本,是 v2 还是 v2.1,再决定请求的去向,一个是下层的路由,根据url 再具体的信息,决定由哪个 Controller 的哪个 application 来处理。 - -## 8.13.1 PasteDeploy - -谈到WSGI,就免不了要了解 Paste,而这边讲的 PasteDeploy 全称是Paste Deployment,原先是Python Paste的一个子项目,现在已经独立出来了。 - -那么 PasteDeploy 到底是做什么的呢? - -根据官方文档(https://pastedeploy.readthedocs.io/en/latest/#introduction)的解释。 - -PasteDeploy 是用来寻找和配置WSGI应用和服务的系统。PasteDeploy给开发者提供了一个简单的函数loadapp。通过这个函数,可以从一个配置文件或者Python egg中加载一个WSGI应用。 - -使用PasteDeploy的其中一个重要意义在于,系统管理员可以安装和管理WSGI应用,而无需掌握与Python和WSGI相关知识。 - -由于 PasteDeploy 原来是属于 Paste 的,现在独立出来了,但是安装的时候还是会安装到paste目录(site-packages\paste\deploy)下。 - -掌握 PasteDeploy ,你只要按照以下三个步骤逐个完成即可。 - -1、配置 PasteDeploy使用的ini文件; - -2、定义WSGI应用; - -3、通过loadapp函数加载WSGI应用; - - - -咱先来做第一步:写paste.ini 文件。 - -在写之前,咱得知道 ini 文件的格式吧。 - -首先,像下面这样一个段叫做 `section`。 - -```ini -[type:name] -key = value -... -``` - -其上的type,主要有如下几种 - -1. `composite` (组合):多个app的路由分发; - - ```ini - [composite:main] - use = egg:Paste#urlmap - / = home - /blog = blog - /wiki = wiki - ``` - -2. app(应用):指明 WSGI 应用的路径; - - ```ini - [app:home] - paste.app_factory = example:Home.factory - ``` - -3. pipeline(管道):给一个 app 绑定多个过滤器。将多个filter和最后一个WSGI应用串联起来。 - - ```ini - [pipeline:main] - pipeline = filter1 filter2 filter3 myapp - - [filter:filter1] - ... - - [filter:filter2] - ... - - [app:myapp] - ... - ``` - -4. filter(过滤器):以 app 做为唯一参数的函数,并返回一个“过滤”后的app。通过键值next可以指定需要将请求传递给谁。next指定的可以是一个普通的WSGI应用,也可以是另一个过滤器。虽然名称上是过滤器,但是功能上不局限于过滤功能,可以是其它功能,例如日志功能,即将认为重要的请求数据记录下来。 - - ```ini - [app-filter:filter_name] - use = egg:... - next = next_app - - [app:next_app] - ... - ``` - - - -最后,必须要知道的 factory 函数: - -1. **paste.app_factory**: 最常用的一个,它指定wsgi app 的路径,通常的用法是paste.app_factory = <模块名>:<类名>.<类方法>。下面几个做个了解就行,不常用。 -2. **paste.composite_factory**: -3. **paste.filter_factory** -4. **paste.filter_app_factory** -5. **paste.server_factory** -6. **paste.server_runner** - -以 nova 中的例子来看下 - -```ini -[app:osapi_compute_app_v21] -paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory -``` - -对 ini 文件有了一定的了解后,就写出如下的 ini 配置文件。 - -```ini -[composite:main] -use = egg:Paste#urlmap -/ = home -/blog = blog -/wiki = wiki - -[app:home] -paste.app_factory = example:Home.factory - -[app:blog] -paste.app_factory = example:Blog.factory - -[app:wiki] -paste.app_factory = example:Wiki.factory -``` - - - -接下来,第二步是定义一个符合 WSGI 规范的 applicaiton 对象。规范的内容可参考我以前写的一篇文章。 - -```python -import os -from paste.deploy import loadapp -from wsgiref.simple_server import make_server - - -class Home(object): - def __init__(self): - print("Init Home.") - - def __call__(self, environ, start_response): - status_code = "200 OK" - response_headers = [("Content-Type", "text/plain")] - response_body = "This is Home's response body.".encode('utf-8') - - start_response(status_code, response_headers) - return [response_body] - - @classmethod - def factory(cls, global_conf, **kwargs): - print("Home factory.") - return Home() -``` - -最后,第三步是使用 loadapp 函数加载 WSGI 应用。 - -loadapp 是 PasteDeploy 提供的一个函数,使用它可以很方便地从第一步的ini配置文件里,加载第二步的 app 对象。 - -loadapp 函数可以接收两个实参: - -- URI:"config:<配置文件的全路径>" -- name:WSGI应用的名称 - -```python -configfile = "paste.ini" -application_name = "main" -applications = loadapp("config:%s" % os.path.abspath(configfile), application_name) -``` - - - -完善并整合第二步和第三步的内容,写成一个 Python 文件(example.py)。内容如下 - -```python -class Blog(object): - def __init__(self): - print("Init Blog.") - - def __call__(self, environ, start_response): - status_code = "200 OK" - response_headers = [("Content-Type", "text/plain")] - response_body = "This is Blog's response body.".encode('utf-8') - - start_response(status_code, response_headers) - return [response_body] - - @classmethod - def factory(cls, global_conf, **kwargs): - print("Blog factory.") - return Blog() - - -class Wiki(object): - def __init__(self): - print("Init Wiki.") - - def __call__(self, environ, start_response): - status_code = "200 OK" - response_headers = [("Content-Type", "text/plain")] - response_body = "This is Wiki's response body.".encode('utf-8') - - start_response(status_code, response_headers) - return [response_body] - - @classmethod - def factory(cls, global_conf, **kwargs): - print("Wiki factory.") - return Wiki() - - -if __name__ == "__main__": - configfile = "paste.ini" - application_name = "main" - port = 8000 - applications = loadapp("config:%s" % os.path.abspath(configfile), application_name) - server = make_server("localhost", port, applications) - print('Started web server at port {}'.format(port)) - server.serve_forever() -``` - - - -一切都准备好后,通过执行下面这条命令来启动 web server - -```shell -python example.py -``` - -如果一切正常,那么打开浏览器,访问http://127.0.0.1:8000/,应该显示:This is Home's response body.;访问http://127.0.0.1:8000/blog,应该显示:This is Blog's response body.;访问http://127.0.0.1:8000/wiki,应该显示:This is Wiki's response body.。 -注意:urlmap对url的大小写是敏感的,例如如果访问http://127.0.0.1:8000/BLOG,在url映射中未能找到大写的BLOG,会使用/映射,最终会显示home页面,即显示:This is Home's response body.。 - - - -nova 服务启动的时候,会先使用 eventlet.wsgi 启动一个 wsgi 服务器。我们知道 wsgi 启动后,必然要注册一系列的 application,而nova 中资源种类非常多, nova 是如何实现灵绑定注册的呢? - -往代码中看,其实这个 app 不是直接写死成一个具体的函数对象,而是通过解析 paste.ini 配置文件来取得具体的 application 路径。 - -通过查看我在 load_app 函数打印的 DEBUG 内容得知 - -- 配置文件 config_url:/etc/nova/api-paste.ini -- 读取的 app name:osapi_compute - -![](http://image.python-online.cn/20190530221101.png) - - - -而 paste.ini 文件是通过 PasteDeploy 这个库来解析的,对应的源码如下 - -![](http://image.python-online.cn/20190530220957.png) - -查看一下`/etc/nova/api-paste.ini` ,通过关键字 `osapi_compute` ,我将一些有用的信息提取出来。 - -``` -[composite:osapi_compute] -use = call:nova.api.openstack.urlmap:urlmap_factory -/: oscomputeversions -/v2: openstack_compute_api_v21_legacy_v2_compatible -/v2.1: openstack_compute_api_v21 - -[app:osapi_compute_app_v21] -paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory -``` - -[composite:xxx]:表示需要将一个请求调度定向(dispatched)到多个,或者多种应用上。以下是一个简单的例子,例子中,使用了composite,根据 url 里的 api 版本号,通过urlmap来实现载入不同的应用,我这里使用的是 `/v2.1` 的版本,所以被调度到了 `osapi_compute_app_v21` 这个app,而这个app 的函数在nova 组件中对应的地址是 `nova.api.openstack.compute:APIRouterV21.factory`。 - -至此,PasteDeploy 的任务就完成了。 - -若你对 paste.ini 不甚了解,可以读一下这篇文章:[python 中paste.ini文件使用说明](https://blog.csdn.net/hzrandd/article/details/10834381) - -## 8.13.2 webob 库 - -经过了 PasteDeploy 的路由调度,我们找到了 `nova.api.openstack.compute:APIRouterV21.factory` 这个 application 的入口,它其实是 APIRouterV21 的一个实例。 - -![](http://image.python-online.cn/20190602173212.png) - -以前我们知道,application 必须是一个 callable 的对象,函数都是 callable 的,但若是一个类实例,就要求这个类实现 `__call__` 的方法。 - -APIRouterV21 本身没有实现 `__call__` ,但它的父类 Router实现了 `__call__` - -![](http://image.python-online.cn/20190602173956.png) - -很奇怪的是,这个 `__call__` 里没有对 req 进行任何处理,而只是返回另一个 callable 对象。你发现了没有 `__call__` 有被一个叫 wsgify 的装饰器装饰着,那它的作用是什么呢? - -wsgify 在这里,指定了一个参数 `RequestClass=Request`,它所起的作用就是将 req 这个原始请求(dict对象)封装成 Request 对象。 - -通过查看一下wsgify源码,可以发现除此之外,有了 wsgify装饰,被装饰器函数可以返回 wsgi app,wsgify发现如果函数返回的是wsgi app 时,它还会被继续调用,如果返回的还是 wsgi app,就再一层一层往下深入,脱掉外层的大衣,直到最里层的核心 app,执行它并返回它的处理结果。 - -总结,wsgify 这个类主要是用于对 WSGI app进行封装,简化wsgi app的定义与编写,它可以很方便的将一个 callable 的函数或对象,封装成一个 WSGI app。 - -## 8.13.2 routes 库 - -在OpenStack 中很多组件都用到了 routes.Mapper 作为 WSGI app 的路由控制。 - -首先是 `routes.middleware.RoutesMiddleware` ,从命名上看,这是一个中间件,它将接受到的url,自动调用map.match()方法,将url进行路由匹配并将结果存入request请求的环境变量['wsgiorg.routing_args'],最后会调用其第一个参数给出的函数接口,即 self.dispatch。 - -self.dispatch 若不能拿到对应的 Controller信息,就由webob 抛出异常。 - -![](http://image.python-online.cn/20190602214129.png) - -若能匹配到,还是返回一个 callable 对象,需要注意的是这边的 app,还不是controller 对象,而是 nova.api.openstack.wsgi.ResourceV21 对象。 - -再去看看 ResourceV21 的 `__call__` - -![](http://image.python-online.cn/20190602220246.png) - -nova show 的接口,在这边获取到的 action_args 是 - -``` -action_args:{'action': u'show', 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9', 'id': u'1c250b15-a346-43c5-9b41-20767ec7c94b'} -``` - -最后调用 `_process_stack` 方法 - -![](http://image.python-online.cn/20190602220511.png) - -在图标处,取得具体的处理函数 meth - -```python -meth :> -``` - -最后,再执行这个函数,取得 action_result,封装成 response 再返回。 - -![](http://image.python-online.cn/20190602220700.png) - -参考文章: - -[Python Route总结](https://blog.csdn.net/bellwhl/article/details/8956088) - -[Python routes Mapper 的使用](https://blog.csdn.net/bellwhl/article/details/8956088) - -[进一步讲解 WSGI](https://blog.csdn.net/bellwhl/article/details/8956088) - -[详解 Paste deploy](https://www.cnblogs.com/Security-Darren/p/4087587.html) - -[paste.ini 文件使用说明](https://blog.csdn.net/hzrandd/article/details/10834381) - -[PasteDeploy 小白教程](http://www.fmttr.com/python/thirdpartylibrary/pastedeploy/) \ No newline at end of file From 800d6885cbe842b9a8ab636d2f3c01ba3e587e5a Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 9 Jun 2019 00:03:39 +0800 Subject: [PATCH 032/302] =?UTF-8?q?=E5=A2=9E=E5=8A=A0vim=E6=96=87=E7=AB=A0?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0wsgi=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c03/c03_06.md | 31 ++- source/c04/c04_18.md | 7 + source/c04/c04_19.md | 641 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 674 insertions(+), 5 deletions(-) create mode 100644 source/c04/c04_19.md diff --git a/source/c03/c03_06.md b/source/c03/c03_06.md index 99768a0..2b0cb72 100644 --- a/source/c03/c03_06.md +++ b/source/c03/c03_06.md @@ -37,7 +37,7 @@ WSGI是 Web Server Gateway Interface 的缩写。 它是 Python应用程序(application)或框架(如 Django)和 Web服务器之间的一种接口,已经被广泛接受。 -它是一种协议,一种规范,其是在 [PEP 3333](https://zhuanlan.zhihu.com/p/27600327) 提出的。这个协议旨在解决众多 web 框架和web server软件的兼容问题。有了WSGI,你不用再因为你使用的web 框架而去选择特定的 web server软件。 +它是一种协议,一种规范,其是在 PEP 333提出的,并在 [PEP 3333](https://zhuanlan.zhihu.com/p/27600327) 进行补充(主要是为了支持 Python3.x)。这个协议旨在解决众多 web 框架和web server软件的兼容问题。有了WSGI,你不用再因为你使用的web 框架而去选择特定的 web server软件。 常见的web应用框架有:Django,Flask等 @@ -466,15 +466,35 @@ APIRouterV21 本身没有实现 `__call__` ,但它的父类 Router实现了 `_ ## 3.6.8 第二次路由:中间件 routes 路由 -routes 库在 WSGI app 的路由调度上有着举足轻重的作用。 +在文章最开始处,我们给大家画了一张图。 -首先是上面提到的 `routes.middleware.RoutesMiddleware` ,从命名上看,知道这是一个中间件,它将接受到的 url,自动调用 `map.match() `方法,对 url 进行路由匹配,并将匹配的结果存入request请求的环境变量`['wsgiorg.routing_args']`,最后会调用`self._dispatch`,它会判断匹配结果,如果匹配到了application就继续,反之没有匹配结果,就抛出异常。 +![](http://image.python-online.cn/20190607131728.png) + +这张图把一个 HTTP 请求粗略简单地划分为两个过程。但事实上,整个过程远比这个过程要复杂得多。 + +实际上在 WSGI Server 到 WSGI Application 这个过程中,我们加很多的功能(比如鉴权、URL路由),而这些功能的实现方式,我们称之为中间件。 + +中间件,对服务器而言,它是一个应用程序,是一个可调用对象, 有两个参数,返回一个可调用对象。而对应用程序而言,它是一个服务器,为应用程序提供了参数,并且调用了应用程序。 + +今天以URL路由为例,来讲讲中间件在实际生产中是如何起作用的。 + +当服务器拿到了客户端请求的URL,不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing。 + +在 Nova 中是用 routes 这个库来实现对URL的的路由调度。接下来,我将从源代码处分析一下这个过程。 + +在routes模块里有个中间件,叫 `routes.middleware.RoutesMiddleware` ,它将接受到的 url,自动调用 `map.match()`方法,对 url 进行路由匹配,并将匹配的结果存入request请求的环境变量`['wsgiorg.routing_args']`,最后会调用`self._dispatch`(dispatch返回真正的application)返回response,最后会将这个response返回给 WSGI Server。 + +![](http://image.python-online.cn/20190608211233.png) + +这个中间件的原理,看起来是挺简单的。并没有很复杂的逻辑。 + +但是,我在阅读 routes 代码的时候,却发现了另一个令我困惑的点。 -在 `self._dispatch` 函数里,我们看到了 app,controller 这几个很重要的字眼,其是否就是我们苦苦追寻的 application 对象呢? +`self._dispatch` (也就上图中的self.app)函数里,我们看到了 app,controller 这几个很重要的字眼,其是否是我苦苦追寻的 application 对象呢? ![](http://image.python-online.cn/20190531211542.png) -要难这个问题,只要看清 match 到是什么东西? +要搞明白这个问题,只要看清 match 到是什么东西? 这个 match 对象 是在 `RoutesMiddleware.__call__()` 里塞进 `req.environ` 的,它是什么东西呢,我将其打印出来。 @@ -719,6 +739,7 @@ meth : + +【匹配行首,行尾】 +/^hello +/world$ + +``` + + +## 替换命令 +--- +```shell +~ 反转游标字母大小写 + +r<字母> 将当前字符替换为所写字母 +R<字母><字母>... 连续替换字母 + +cc 替换整行(就是删除当前行,并在下一行插入) +cw 替换一个单词(就是删除一个单词,就进入插入模式),前提是游标处于单词第一个字母(可用b定位) +C (大写C)替换至行尾(和D有所区别,D是删除(剪切)至行尾,C是删除至行位并进入插入模式) + +:s/old/new/ 用old替换new,替换当前行的第一个匹配 +:s/old/new/g 用old替换new,替换当前行的所有匹配 + +:%s/old/new/ 用old替换new,替换所有行的第一个匹配 +:%s/old/new/g 用old替换new,替换整个文件的所有匹配 + + + +:10,20 s/^/ /g 在第10行至第20行每行前面加四个空格,用于缩进。 + +ddp 交换光标所在行和其下紧邻的一行。 + +``` + + + +## 撤销和重做 + +--- + +```shell + +u 撤销(Undo) + +U 撤销对整行的操作 + +Ctrl + r 重做(Redo),即撤销的撤销。 + +``` + + + +## 删除命令 +--- +```shell +x 删除当前字符 +3x 删除当前字符3次 + +X 删除当前字符的前一个字符。 +3X 删除当前光标向前三个字符 + +dw 删除当前字符到单词尾 +daw 删除当前字符所在单词 + +dl 删除当前字符, dl=x +dh 删除前一个字符,X=dh + + +dd 删除当前行 +dj 删除下一行 +dk 删除上一行 + +dgg 删除当前行至文档首部 +d1G 删除当前行至文档首部 +dG 删除当前行至文档尾部 + +kdgg 删除当前行之前所有行(不包括当前行) +jdG 删除当前行之后所有行(不包括当前行) + + + +D 删除当前字符至行尾。D=d$ +d$ 删除当前字符至行尾 +d^ 删除当前字符之前至行首 + + +10d 删除当前行开始的10行。 +:1,10d 删除1-10行 +:11,$d 删除11行及以后所有的行 +:1,$d 删除所有行 +J   删除两行之间的空行,实际上是合并两行。 + +``` +## 复制粘贴 +--- +普通模式中使用y复制 +``` +yy 复制游标所在的整行(3yy表示复制3行) + +y^ 复制至行首,或y0。不含光标所在处字符。 +y$ 复制至行尾。含光标所在处字符。 + +yw 复制一个单词。 +y2w 复制两个单词。 + +yG 复制至文本末。 +y1G 复制至文本开头。 +``` + +普通模式中使用p粘贴 +``` +p(小写)代表粘贴至光标后(下) +P(大写)代表粘贴至光标前(上) +``` + +## 剪切粘贴 + +--- + +```shell +dd 其实就是剪切命令,剪切当前行 +ddp 剪切当前行并粘贴,可实现当前行和下一行调换位置 + + +正常模式下按v(逐字)或V(逐行)进入可视模式 +然后用jklh命令移动即可选择某些行或字符,再按d即可剪切 + +ndd 剪切当前行之后的n行。利用p命令可以对剪切的内容进行粘贴 + +:1,10d 将1-10行剪切。利用p命令可将剪切后的内容进行粘贴。 + +:1, 10 m 20 将第1-10行移动到第20行之后。 +``` + + + +## 退出命令 + +--- + +```shell + +:wq 保存并退出 + +ZZ 保存并退出 + +:q! 强制退出并忽略所有更改 + +:e! 放弃所有修改,并打开原来文件。 + +ZZ 保存并退出 + +:sav(eas) new.txt 另存为一个新文件,退出原文件的编辑且不会保存 +:f(ile) new.txt 新开一个文件,并不保存,退出原文件的编辑且不会保存 + +``` + +## 移动命令 + +--- + +``` + +h 左移一个字符 +l 右移一个字符 +k 上移一个字符 +j 下移一个字符 + + +# 10指代所有数字,可任意指定 + +10h 左移10个字符 +10l 右移10个字符 +10k 上移10行 +10j 下移10行 + + +w 向前移动一个单词(光标停在单词首部) +b 向后移动一个单词 +e,同w,只不过是光标停在单词尾部 +ge 同b,光标停在单词尾部。 + + + +^ 移动到本行第一个非空白字符上。 +0 移动到本行第一个字符上(可以是空格) + +$ 移动到行尾 +3$ 移动到下面3行的行尾 + +gg 移动到文件头。 = [[ +G 移动到文件尾。 = ]] + +( 移动到句首 +) 移动到句尾 + + +【定位字符】f和F + +fx 找到光标后第一个为x的字符 +3fd 找到光标后第三个为d的字符 + +F 同f,反向查找。 + +【返回一次定位】: +使用 `` 可以返回一次跳转的位置。只记录一次。 + +【具名标记】 +使用 ma ,可以将此处标记为 a,使用 'a 进行跳转 +使用 :marks 可以查看所有的标记 +使用 :delm!可以删除所有的标记 + +这个很好用,可以跨文件。 + + + +``` + +## 排版/缩进 +--- +### 缩进 +``` +:set shiftwidth? 查看缩进值 +:set shiftwidth=4 设置缩进值为4 + +# 缩进相关 最好写到配置文件中 ~/.vimrc +:set tabstop=4 +:set softtabstop=4 +:set shiftwidth=4 +:set expandtab + +>> 向右缩进 +<< 取消缩进 +``` + +### 排版 +``` +:ce 居中 +:le 靠左 +:ri 靠右 +``` +对代码缩进,还可以用 `==` 对当前行缩进,如果要对多行对待缩进,则使用 n`==`。 + +## 注释命令 + +--- + +### 多行注释 +``` +进入命令行模式,按ctrl + v进入 visual block模式,然后按j, 或者k选中多行,把需要注释的行标记起来 + +按大写字母I,再插入注释符,例如// + +按esc键就会全部注释了 + +``` +### 取消多行注释 +``` +进入命令行模式,按ctrl + v进入 visual block模式,按字母l横向选中列的个数,例如 // 需要选中2列 + +按字母j,或者k选中注释符号 + +按d键就可全部取消注释 +``` +### 复杂注释 +```shell + +:3,5 s/^/#/g 注释第3-5行 +:3,5 s/^#//g 解除3-5行的注释 + + +:1,$ s/^/#/g 注释整个文档 +:1,$ s/^#//g 取消注释整个文档 + + +:%s/^/#/g 注释整个文档,此法更快 +:%s/^#//g 取消注释整个文档 +``` + +## 调整视野 +``` +"zz":命令会把当前行置为屏幕正中央, +"zt":命令会把当前行置于屏幕顶端 +"zb":则把当前行置于屏幕底端. + +Ctrl + e 向下滚动一行 +Ctrl + y 向上滚动一行 + +Ctrl + d 向下滚动半屏 +Ctrl + u 向上滚动半屏 + +Ctrl + f 向下滚动一屏 +Ctrl + b 向上滚动一屏 + + +【跳到指定行】:两种方法 + +可以先把行号打开 +:set nu 打开行号 + +:20 跳到第20行 +20G 跳到第20行 +``` + +## 区域选择 + +``` +要进行区域选择,要先进入可视模式 + +v 以字符为单位,上下左右选择 +V 以行为单位,上下选择 + +选择后可进行操作 +d 剪切/删除 +y 复制 + +Ctrl+v 如果当前是V(大写)模式,就变成v(小写) + 如果当前是v(小写)模式,就变成普通模式。 + 如果当前是普通模式,就进入v(小写)模式 + +利用这个,可以进行多行缩进。 + +ggVG 选择全文 +``` + + +## 窗口控制 +--- +### 新建窗口 +```shell +# 打开两个文件分属两个窗口 +vim -o 1.txt 2.txt + + +# 假设现在已经打开了1.txt + +:sp 2.txt 开启一个横向的窗口,编辑2.txt +:vsp 2.txt 开启一个竖向的窗口,编辑2.txt + +:split 将当前窗口再复制一个窗口出来,内容同步,游标可以不同 +:split 2.txt 在新窗口打开2.txt的横向窗口 + +# 需要注意:内容同步,但是游标位置是独立的 + +Ctrl-w s 将当前窗口分成水平窗口 +Ctrl-w v 将当前窗口分成竖直窗口 + +Ctrl-w q 等同:q 结束分割出来的视窗。 +Ctrl-w q! 等同:q! 结束分割出来的视窗。 +Ctrl-w o 打开一个视窗并且隐藏之前的所有视窗 +``` +### 窗口切换 +```shell +# 特别说明:Ctrl w <字母> 不需要同时按 + +Ctrl-w h 切换到左边窗口 +Ctrl-w l 切换到右边窗口 + +Ctrl-w j 切换到下边窗口 +Ctrl-w k 切换到上边窗口 + + +# 特别说明:全屏模式下 +:n 切换下一个窗口 +:N 切换上一个窗口 +:bp 切换上一个窗口 + +# 特别说明:非全屏模式 + +:bn 切换下一个窗口,就当前位置的窗口的内容变了,其他窗口不变 +:bN 切换上一个窗口,就当前位置的窗口的内容变了,其他窗口不变 +``` +### 窗口移动 +```shell +# 特别说明:Ctrl w <字母> 不需要同时按 + +Ctrl-w J 将当前视窗移至最下面 +Ctrl-w K 将当前视窗移最上面 + +Ctrl-w H 将当前视窗移至最左边 +Ctrl-w L 将当前视窗移至最右边 + +Ctrl-ww 按顺序切换窗口 +``` +### 调整尺寸 +```shell +# 友情提示:键盘切记不要处于中文状态 + +Ctrl-w + 增加窗口高度 +Ctrl-w - 减少窗口高度 +``` +### 退出窗口 +```shell +:close 关闭当前窗口 +:close! 强制关闭当前窗口 + +:q 退出,不保存 +:q! 强制退出,不保存 + +:x 保存退出 +:wq 保存退出 +:wq! 强制保存退出 + +:w <[路径/]文件名> 另存为 +:savesa <[路径/]文件名> 另存为 + +ZZ 保存并退出。 + +:only 关闭所有窗口,只保留当前窗口(前提:其他窗口内容有改变的话都要先保存) +:only! 关闭所有窗口,只保留当前窗口 + + :qall 放弃所有操作并退出 + :wall 保存所有, + :wqall 保存所有并退出。 +``` + +## 文档加密 +--- +``` +vim -x file_name + +然后输入密码: +确认密码: + +如果不修改内容也要保存。:wq,不然密码设定不会生效。 +``` + + +## 录制宏 + +--- + +```shell + +按q键加任意字母开始录制,再按q键结束录制(这意味着vim中的宏不可嵌套),使用的时候@加宏名,比如qa。。。q录制名为a的宏,@a使用这个宏。 + +执行shell命令 + +:!command + +:!ls 列出当前目录下文件 + +:!perl -c script.pl 检查perl脚本语法,可以不用退出vim,非常方便。 + +:!perl script.pl 执行perl脚本,可以不用退出vim,非常方便。 + +:suspend或Ctrl - Z 挂起vim,回到shell,按fg可以返回vim。 + +``` + + + +## 帮助命令 + +--- + +```shell + + +在Unix/Linux系统上 +$ vimtutor + +# 普通模式下 +键盘输入vim或F1 + +# 命令行模式下 + +:help 显示整个帮助 +:help xxx 显示xxx的帮助,比如 :help i, :help CTRL-[(即Ctrl+[的帮助)。 +:help 'number' Vim选项的帮助用单引号括起 + + +在Windows系统上 +:help tutor +``` + + + +## 配置命令 +--- + +### 显示当前设定 +```shell +set或者:se显示所有修改过的配置 +set all 显示所有的设定值 +set option? 显示option的设定值 +set nooption 取消当期设定值 +``` + +### 更改设定 +```shell +:set nu 显示行号 + +set autoindent(ai) 设置自动缩进 +set autowrite(aw) 设置自动存档,默认未打开 +set backup(bk) 设置自动备份,默认未打开 + +set background=dark或light,设置背景风格 + +set cindent(cin) 设置C语言风格缩进 + +:set ts=4 设置tab键转换为4个空格 + +:set shiftwidth? 查看缩进值 +:set shiftwidth=4 设置缩进值为4 + +:set ignorecase  忽略大小写的查找 +:set noignorecase  不忽略大小写的查找 + +:set paste # insert模式下,粘贴格式不会乱掉 + +:set ruler?  查看是否设置了ruler,在.vimrc中,使用set命令设制的选项都可以通过这个命令查看 + +:scriptnames  查看vim脚本文件的位置,比如.vimrc文件,语法文件及plugin等。 + +:set list 显示非打印字符,如tab,空格,行尾等。如果tab无法显示,请确定用set lcs=tab:>-命令设置了.vimrc文件,并确保你的文件中的确有tab,如果开启了expendtab,那么tab将被扩展为空格。 +``` + +## 其他命令 +--- +```shell +. 重复前一次命令 + + +:ver 显示vim的所有信息(包括版本和参数等) + + +# 需要注意:全屏模式下 +:args 查看当前打开的文件列表,当前正在编辑的文件会用[]括起来 + + +:syntax 列出已经定义的语法项 +:syntax clear 清除已定义的语法规则 + +:syntax case match 大小写敏感,int和Int将视为不同的语法元素 +:syntax case ignore 大小写无关,int和Int将视为相同的语法元素,并使用同样的配色方案 + +J 将两行合并为一行 +``` + + + +--- + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file From 4e68b8bee5f249317c87a039569e7a05a2a03f98 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 10 Jun 2019 13:42:57 +0800 Subject: [PATCH 033/302] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20md2rst=20=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- md2rst.py | 35 +++++++++ test.md | 230 ------------------------------------------------------ 2 files changed, 35 insertions(+), 230 deletions(-) create mode 100644 md2rst.py delete mode 100644 test.md diff --git a/md2rst.py b/md2rst.py new file mode 100644 index 0000000..8daf141 --- /dev/null +++ b/md2rst.py @@ -0,0 +1,35 @@ +# coding:utf-8 +import os +import commands +import subprocess + + +blog_path = 'F:\MyGit\PythonCodingTime/source' + +file_list = os.listdir(blog_path) + +dir_list = [] +for item in file_list: + abs_path = os.path.join(blog_path, item) + if os.path.isdir(abs_path): + dir_list.append(abs_path) + +for folder in dir_list: + os.chdir(folder) + print('==== Processing folder {}'.format(folder)) + all_file = os.listdir(folder) + all_md_file = [file for file in all_file if file.endswith('md')] + for file in all_md_file: + (filename, extension) = os.path.splitext(file) + convert_cmd = 'pandoc -V mainfont="SimSun" -f markdown -t rst {md_file} -o {rst_file}'.format( + md_file=filename+'.md', rst_file=filename+'.rst' + ) + # status, output = commands.getstatusoutput(convert_cmd) + retcode = subprocess.call(convert_cmd) + # if status != 0: + # print(output) + if retcode == 0: + print(file + ' 处理完成') + else: + print(file + '处理失败') + diff --git a/test.md b/test.md deleted file mode 100644 index d1f2db5..0000000 --- a/test.md +++ /dev/null @@ -1,230 +0,0 @@ -前段时间,有朋友在我的读者群里问了几个关于单例模式的问题。 - -为了回答他的问题,我整理了单例模式的知识点,正好我也在写设计模式的系列。 - -上一篇是讲「[策略模式](http://mp.weixin.qq.com/s?__biz=MzU4OTUwMDE1Mw==&mid=2247484565&idx=1&sn=5e39c83a1b5db7065a99b22fddb617d5&chksm=fdcddf03caba5615bdf26038d428ec1fbb2b600070186e33f975d0235c57c7a167a15a5efcc7&scene=21#wechat_redirect)」,若你还未阅读,可以点击这里查看: - -[商场促销活动背后的代码哲学](https://mp.weixin.qq.com/s?__biz=MzU4OTUwMDE1Mw==&mid=2247484565&idx=1&sn=5e39c83a1b5db7065a99b22fddb617d5&scene=21#wechat_redirect) - -本篇做为「设计模式系列」的第二篇,来一起看看「单例模式」。 - -之前在另一篇公众号文章看到一个挺搞笑的例子: - -大意是讲,老婆在中国其实就是一个很形象的单例,你要娶一个老婆需要去民政局注册登记(要对类进行实例化),当你想再娶一个老婆时,这时民政局会说,不行,你已经有一个老婆了,并且它还会告诉你的老婆是谁。 - -玩笑之后,再回到我们的话题,先举几类我们经常见到的例子: - -***1、***大家在解释单例模式时,经常要提到的一个例子是 Windows 的任务管理器。如果我们打开多个任务管理器窗口。显示的内容完全一致,如果在内部是两个一模一样的对象,那就是重复对象,就造成了内存的浪费;相反,如果两个窗口的内容不一致,那就会至少有一个窗口展示的内容是错误的,会给用户造成误解,到底哪个才是当前真实的状态呢? - -***2、***一个项目中多个地方需要读取同一份配置文件,如果每次使用都是导入重新创建实例,读取文件,用完后再销毁,这样做的话,就造成不必要的IO浪费,可以使用单例模式只生成一份配置在内存中。 - -***3、***还有一个常见的例子是,一个网站的访问量、在线人数,在项目中是全局唯一(不考虑分布式),在这种情况下,使用单例模式是一种很好的方式。 - -从上面看来,在系统中确保某个对象的唯一性即一个类只能有一个实例有时是非常重要的。 - -按照惯例,我们先来用代码实践一下,看看如何用 Python 写单例模式。 - - - -这里介绍了三个较为常用的。 - -- 使用 \_\_new__ - -```python -class User: - _instance = None - def __new__(cls, *args, **kwargs): - print('===== 1 ====') - if not cls._instance: - print("===== 2 ====") - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self, name): - print('===== 3 ====') - self.name = name -``` - -验证结果 - -![](http://image.python-online.cn/20190512113846.png) - -- 使用装饰器 - -```python -instances = {} - -def singleton(cls): - def get_instance(*args, **kw): - cls_name = cls.__name__ - print('===== 1 ====') - if not cls_name in instances: - print('===== 2 ====') - instance = cls(*args, **kw) - instances[cls_name] = instance - return instances[cls_name] - return get_instance - -@singleton -class User: - _instance = None - - def __init__(self, name): - print('===== 3 ====') - self.name = name -``` - -验证结果 - -![](http://image.python-online.cn/20190512113917.png) - -- 使用元类 - -```python -class MetaSingleton(type): - def __call__(cls, *args, **kwargs): - print("cls:{}".format(cls.__name__)) - print("====1====") - if not hasattr(cls, "_instance"): - print("====2====") - cls._instance = type.__call__(cls, *args, **kwargs) - return cls._instance - -class User(metaclass=MetaSingleton): - def __init__(self, *args, **kw): - print("====3====") - for k,v in kw: - setattr(self, k, v) -``` - -验证结果 - -![](http://image.python-online.cn/20190512114028.png) - -以上的代码,一般情况下没有问题,但在并发场景中,就会出现线程安全的问题。 - -如下这段代码我开启10个线程来模拟 - -```python -import time -import threading - -class User: - _instance = None - - def __new__(cls, *args, **kwargs): - if not cls._instance: - time.sleep(1) - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self, name): - self.name = name - -def task(): - u = User("wangbm") - print(u) - -for i in range(10): - t = threading.Thread(target=task) - t.start() -``` - -从结果来观察,很容易就发现,单例模式失效了,在10个线程下,并发创建实例,并不能保证一个类只有一个实例。 - -```python -<__main__.User object at 0x1050563c8> -<__main__.User object at 0x10551a208> -<__main__.User object at 0x1050563c8> -<__main__.User object at 0x1055a93c8> -<__main__.User object at 0x1050563c8> -<__main__.User object at 0x105527160> -<__main__.User object at 0x1055f4e48> -<__main__.User object at 0x1055e6c88> -<__main__.User object at 0x1055afcf8> -<__main__.User object at 0x105605940> -``` - -这在 Java 中,是可以使用饿汉模式来避免这个问题,在 Python 中我想到的办法是**加锁**。 - -首先实现一个给函数加锁的装饰器 - -```python -import threading - -def synchronized(func): - - func.__lock__ = threading.Lock() - - def lock_func(*args, **kwargs): - with func.__lock__: - return func(*args, **kwargs) - return lock_func -``` - -然后在实例化对象的函数上,使用这个装饰函数。 - -```python -import time -import threading - -class User: - _instance = None - - @synchronized - def __new__(cls, *args, **kwargs): - if not cls._instance: - time.sleep(1) - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self, name): - self.name = name - -def task(): - u = User("wangbm") - print(u) - -for i in range(10): - t = threading.Thread(target=task) - t.start() -``` - -结果如下,如预期只生成了一个实例。 - -```python -<__main__.User object at 0x10ff503c8> -<__main__.User object at 0x10ff503c8> -<__main__.User object at 0x10ff503c8> -<__main__.User object at 0x10ff503c8> -<__main__.User object at 0x10ff503c8> -<__main__.User object at 0x10ff503c8> -<__main__.User object at 0x10ff503c8> -<__main__.User object at 0x10ff503c8> -<__main__.User object at 0x10ff503c8> -<__main__.User object at 0x10ff503c8> -``` - -学会写只是第一步,还有一点,相当重要,要知道为何会有这个设计模式,它有什么优势,有什么局限性? - -总结一下,单例模式有如下优点: - -1. 全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用; -2. 由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间; -3. 单例可长驻内存,减少系统开销。 - -和其他设计模式一样,单例模式有一定的适用场景,但同时它也会给我们带来一些问题。 - -1. 由于单例对象是全局共享,所以其状态维护需要特别小心。一处修改,全局都会受到影响。 -2. 单例对象没有抽象层,扩展不便。 -3. 赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到); -4. 单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试; -5. 单例模式在某种情况下会导致“资源瓶颈”。 - ------- - -参考文章 - -- [Python与设计模式--单例模式](https://yq.aliyun.com/articles/70418?utm_content=m_14908#comment) -- [Python线程安全的单例模式](https://blog.csdn.net/lucky404/article/details/79668131) - From 8b1b6a237face4c449d7a8fe75bcb0f7fd2cefed Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 10 Jun 2019 23:56:22 +0800 Subject: [PATCH 034/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20rst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- md2rst.py | 12 +- source/c03/c03_01.rst | 16 +- source/c03/c03_06.rst | 883 ++++++++++++++++++++++++++++++++++++++++++ source/c04/c04_14.rst | 4 +- source/c04/c04_17.rst | 9 +- source/c04/c04_18.rst | 38 ++ source/c04/c04_19.rst | 676 ++++++++++++++++++++++++++++++++ source/c07/c07_02.rst | 17 +- source/c08/c08_01.rst | 13 +- source/c08/c08_05.rst | 36 +- source/c08/c08_07.rst | 6 +- 11 files changed, 1673 insertions(+), 37 deletions(-) create mode 100644 source/c03/c03_06.rst create mode 100644 source/c04/c04_18.rst create mode 100644 source/c04/c04_19.rst diff --git a/md2rst.py b/md2rst.py index 8daf141..07fb79c 100644 --- a/md2rst.py +++ b/md2rst.py @@ -4,7 +4,7 @@ import subprocess -blog_path = 'F:\MyGit\PythonCodingTime/source' +blog_path = '/Users/MING/Github/PythonCodingTime/source' file_list = os.listdir(blog_path) @@ -24,11 +24,11 @@ convert_cmd = 'pandoc -V mainfont="SimSun" -f markdown -t rst {md_file} -o {rst_file}'.format( md_file=filename+'.md', rst_file=filename+'.rst' ) - # status, output = commands.getstatusoutput(convert_cmd) - retcode = subprocess.call(convert_cmd) - # if status != 0: - # print(output) - if retcode == 0: + status, output = commands.getstatusoutput(convert_cmd) + #retcode = subprocess.call(convert_cmd) + if status != 0: + print(output) + if status == 0: print(file + ' 处理完成') else: print(file + '处理失败') diff --git a/source/c03/c03_01.rst b/source/c03/c03_01.rst index 286b78f..a89d41a 100755 --- a/source/c03/c03_01.rst +++ b/source/c03/c03_01.rst @@ -408,16 +408,12 @@ Python工匠:使用装饰器的小技巧) .. code:: python - from functools import update_wrapper - - WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', - '__annotations__') + from functools import wraps def wrapper(func): + @wraps(func) def inner_function(): pass - - update_wrapper(inner_function, func, assigned=WRAPPER_ASSIGNMENTS) return inner_function @wrapper @@ -425,6 +421,7 @@ Python工匠:使用装饰器的小技巧) pass print(wrapped.__name__) + # wrapped 准确点说,wraps 其实是一个偏函数对象(partial),源码如下 @@ -443,10 +440,14 @@ wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码 from functools import update_wrapper + WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', + '__annotations__') + def wrapper(func): def inner_function(): pass - update_wrapper(func, inner_function) + + update_wrapper(inner_function, func, assigned=WRAPPER_ASSIGNMENTS) return inner_function @wrapper @@ -454,7 +455,6 @@ wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码 pass print(wrapped.__name__) - # wrapped 3.1.10 内置装饰器:property --------------------------- diff --git a/source/c03/c03_06.rst b/source/c03/c03_06.rst new file mode 100644 index 0000000..d6c63c5 --- /dev/null +++ b/source/c03/c03_06.rst @@ -0,0 +1,883 @@ +3.6 一篇文章搞懂WSGI +==================== + +在 三百六十行,行行转 IT 的现状下,很多来自各行各业的同学,都选择 Python +这门胶水语言做为踏入互联网大门的第一块敲门砖,在这些人里,又有相当大比例的同学选择了 +Web 开发这个方向(包括我)。而从事 web 开发,绕不过一个知识点,就是 +WSGI。 + +不管你是否是这些如上同学中的一员,都应该好好地学习一下这个知识点。 + +由于我本人不从事专业的 python web +开发,所以在写这篇文章的时候,借鉴了许多优秀的网络博客,并花了很多的精力阅读了大量的 +OpenStack 代码。 + +为了写这篇文章,零零散散地花了大概两个星期。本来可以拆成多篇文章,写成一个系列的,经过一番思虑,还是准备一篇讲完,这就是本篇文章这么长的原因。 + +另外,一篇文章是不能吃透一个知识点的,本篇涉及的背景知识也比较多的,若我有讲得不到位的,还请你多多查阅其他人的网络博客进一步学习。 + +在你往下看之前,我先问你几个问题,你带着这些问题往下看,可能更有目的性,学习可能更有效果。 + +问1:一个 HTTP 请求到达对应的 application处理函数要经过怎样的过程? + +问2:如何不通过流行的 web 框架来写一个简单的web服务? + +一个HTTP请求的过程可以分为两个阶段,第一阶段是从客户端到WSGI +Server,第二阶段是从WSGI Server 到WSGI Application + +|image0| + +今天主要是讲第二阶段,主要内容有以下几点: + +1. WSGI 是什么,因何而生? +2. HTTP请求是如何到应用程序的? +3. 实现一个简单的 WSGI Server +4. 实现“高并发”的WSGI Server +5. 第一次路由:PasteDeploy +6. PasteDeploy 使用说明 +7. webob.dec.wsgify 装饰器 +8. 第二次路由:中间件 routes 路由 + +3.6.1 WSGI 是什么,因何而生? +----------------------------- + +WSGI是 Web Server Gateway Interface 的缩写。 + +它是 Python应用程序(application)或框架(如 Django)和 +Web服务器之间的一种接口,已经被广泛接受。 + +它是一种协议,一种规范,其是在 PEP 333提出的,并在 `PEP +3333 `__ 进行补充(主要是为了支持 +Python3.x)。这个协议旨在解决众多 web 框架和web +server软件的兼容问题。有了WSGI,你不用再因为你使用的web +框架而去选择特定的 web server软件。 + +常见的web应用框架有:Django,Flask等 + +常用的web服务器软件有:uWSGI,Gunicorn等 + +那这个 WSGI 协议内容是什么呢?知乎上有人将 PEP 3333 +翻译成中文,写得非常好,我将这段协议的内容搬运过来。 + + WSGI + 接口有服务端和应用端两部分,服务端也可以叫网关端,应用端也叫框架端。服务端调用一个由应用端提供的可调用对象。如何提供这个对象,由服务端决定。例如某些服务器或者网关需要应用的部署者写一段脚本,以创建服务器或者网关的实例,并且为这个实例提供一个应用实例。另一些服务器或者网关则可能使用配置文件或其他方法以指定应用实例应该从哪里导入或获取。 + +WSGI 对于 application 对象有如下三点要求 + +1. 必须是一个可调用的对象 +2. 接收两个必选参数environ、start_response。 +3. 返回值必须是可迭代对象,用来表示http body。 + +3.6.2 HTTP请求是如何到应用程序的? +---------------------------------- + +当客户端发出一个 HTTP 请求后,是如何转到我们的应用程序处理并返回的呢? + +关于这个过程,细节的点这里没法细讲,只能讲个大概。 + +我根据其架构组成的不同将这个过程的实现分为两种: + +|image1| + +**1、两级结构** +在这种结构里,uWSGI作为服务器,它用到了HTTP协议以及wsgi协议,flask应用作为application,实现了wsgi协议。当有客户端发来请求,uWSGI接受请求,调用flask +app得到相应,之后相应给客户端。 +这里说一点,通常来说,Flask等web框架会自己附带一个wsgi服务器(这就是flask应用可以直接启动的原因),但是这只是在开发阶段用到的,在生产环境是不够用的,所以用到了uwsgi这个性能高的wsgi服务器。 + +**2、三级结构** +这种结构里,uWSGI作为中间件,它用到了uwsgi协议(与nginx通信),wsgi协议(调用Flask +app)。当有客户端发来请求,nginx先做处理(静态资源是nginx的强项),无法处理的请求(uWSGI),最后的相应也是nginx回复给客户端的。 +多了一层反向代理有什么好处? + +提高web +server性能(uWSGI处理静态资源不如nginx;nginx会在收到一个完整的http请求后再转发给wWSGI) + +nginx可以做负载均衡(前提是有多个服务器),保护了实际的web服务器(客户端是和nginx交互而不是uWSGI) + +3.6.3 实现一个简单的 WSGI Server +-------------------------------- + +在上面的架构图里,不知道你发现没有,有个库叫做 ``wsgiref`` ,它是 Python +自带的一个 wsgi 服务器模块。 + +从其名字上就看出,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。 + +有了 wsgiref 这个模块,你就可以很快速的启动一个wsgi server。 + +.. code:: python + + from wsgiref.simple_server import make_server + + # 这里的 appclass 暂且不说,后面会讲到 + app = appclass() + server = make_server('', 64570, app) + server.serve_forever() + +当你运行这段代码后,就会开启一个 wsgi server,监听 ``0.0.0.0:64570`` +,并接收请求。 + +使用 lsof 命令可以查到确实开启了这个端口 + +|image2| + +以上使用 wsgiref +写了一个demo,让你对wsgi有个初步的了解。其由于只适合在学习测试使用,在生产环境中应该另寻他道。 + +3.6.4 实现“高并发”的 WSGI Server +-------------------------------- + +上面我们说不能在生产中使用 wsgiref +,那在生产中应该使用什么呢?选择有挺多的,比如优秀的 +uWSGI,Gunicore等。但是今天我并不准备讲这些,一是因为我不怎么熟悉,二是因为我本人从事 +OpenStack 的二次开发,对它比较熟悉。 + +所以下面,是我花了几天时间阅读 OpenStack 中的 Nova +组件代码的实现,刚好可以拿过来学习记录一下,若有理解偏差,还望你批评指出。 + +在 nova 组件里有不少服务,比如 +nova-api,nova-compute,nova-conductor,nova-scheduler 等等。 + +其中,只有 nova-api 有对外开启 http 接口。 + +要了解这个http +接口是如何实现的,从服务启动入口开始看代码,肯定能找到一些线索。 + +从 Service 文件可以得知 nova-api 的入口是 ``nova.cmd.api:main()`` + +|image3| + +|image4| + +打开\ ``nova.cmd.api:main()`` ,一起看看是 OpenStack Nova 的代码。 + +在如下的黄框里,可以看到在这里使用了service.WSGIService 启动了一个 +server,就是我们所说的的 wsgi server + +|image5| + +那这里的 WSGI Server 是依靠什么实现的呢?让我们继续深入源代码。 + +|image6| + +wsgi.py 可以看到这里使用了 eventlet +这个网络并发框架,它先开启了一个绿色线程池,从配置里可以看到这个服务器可以接收的请求并发量是 +1000 。 + +|image7| + +可是我们还没有看到 WSGI Server 的身影,上面使用eventlet +开启了线程池,那线程池里的每个线程应该都是一个服务器吧?它是如何接收请求的? + +再继续往下,可以发现,每个线程都是使用 eventlet.wsgi.server 开启的 WSGI +Server,还是使用的 eventlet。 + +由于源代码比较多,我提取了主要的代码,精简如下 + +.. code:: python + + # 创建绿色线程池 + self._pool = eventlet.GreenPool(self.pool_size) + + # 创建 socket:监听的ip,端口 + bind_addr = (host, port) + self._socket = eventlet.listen(bind_addr, family, backlog=backlog) + dup_socket = self._socket.dup() + + # 整理孵化协程所需的各项参数 + wsgi_kwargs = { + 'func': eventlet.wsgi.server, + 'sock': dup_socket, + 'site': self.app, # 这个就是 wsgi 的 application 函数 + 'protocol': self._protocol, + 'custom_pool': self._pool, + 'log': self._logger, + 'log_format': CONF.wsgi.wsgi_log_format, + 'debug': False, + 'keepalive': CONF.wsgi.keep_alive, + 'socket_timeout': self.client_socket_timeout + } + + # 孵化协程 + self._server = utils.spawn(**wsgi_kwargs) + +|image8| + +就这样,nova 开启了一个可以接受1000个绿色协程并发的 WSGI Server。 + +3.6.5 第一次路由:PasteDeploy +----------------------------- + +上面我们提到 WSGI Server 的创建要传入一个 +Application,用来处理接收到的请求,对于一个有多个 app 的项目。 + +比如,你有一个个人网站提供了如下几个模块 + +:: + + /blog # 博客 app + /wiki # wiki app + +如何根据 请求的url 地址,将请求转发到对应的application上呢? + +答案是,使用 PasteDeploy 这个库(在 OpenStack 中各组件被广泛使用)。 + +PasteDeploy 到底是做什么的呢? + +根据 +`官方文档 `__ +的说明,翻译如下 + + PasteDeploy + 是用来寻找和配置WSGI应用和服务的系统。PasteDeploy给开发者提供了一个简单的函数loadapp。通过这个函数,可以从一个配置文件或者Python + egg中加载一个WSGI应用。 + +使用PasteDeploy的其中一个重要意义在于,系统管理员可以安装和管理WSGI应用,而无需掌握与Python和WSGI相关知识。 + +由于 PasteDeploy 原来是属于 Paste +的,现在独立出来了,但是安装的时候还是会安装到paste目录(site-packages:raw-latex:`\paste`:raw-latex:`\deploy`)下。 + +我会先讲下在 Nova 中,是如何借助 PasteDeploy 实现对url的路由转发。 + +还记得在上面创建WSGI Server的时候,传入了一个 self.app +参数,这个app并不是一个固定的app,而是使用 PasteDeploy 中提供的 loadapp +函数从 paste.ini 配置文件中加载application。 + +具体可以,看下nova的实现。 + +|image9| + +通过打印的 DEBUG 内容得知 config_url 和 app name 的值 + +:: + + app: osapi_compute + config_url: /etc/nova/api-paste.inia + +通过查看 ``/etc/nova/api-paste.ini`` ,在 composite 段里找到了 +``osapi_compute`` 这个app(这里的app和wsgi app +是两个概念,需要注意区分) ,可以看出 nova 目前有两个版本的api,一个是 +v2,一个是v2.1,目前我们在用的是 v2.1,从配置文件中,可以得到其指定的 +application 的路径是\ ``nova.api.openstack.compute`` 这个模块下的 +APIRouterV21 类 的factory方法,这是一个工厂函数,返回 APIRouterV21 +实例。 + +.. code:: ini + + [composite:osapi_compute] + use = call:nova.api.openstack.urlmap:urlmap_factory + /: oscomputeversions + /v2: openstack_compute_api_v21_legacy_v2_compatible + /v2.1: openstack_compute_api_v21 + + [app:osapi_compute_app_v21] + paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory + +这是 OpenStack 使用 PasteDeploy +实现的第一层的路由,如果你不感兴趣,可以直接略过本节,进入下一节,下一节是 +介绍 PasteDeploy 的使用,教你实现一个简易的web server +demo。推荐一定要看。 + +3.6.6 PasteDeploy 使用说明 +-------------------------- + +到上一步,我已经得到了 application +的有用的线索。考虑到很多人是第一次接触 +PasteDeploy,所以这里结合网上博客做了下总结。对你入门会有帮助。 + +掌握 PasteDeploy ,你只要按照以下三个步骤逐个完成即可。 + +1、配置 PasteDeploy使用的ini文件; + +2、定义WSGI应用; + +3、通过loadapp函数加载WSGI应用; + +**第一步:写 paste.ini 文件** + +在写之前,咱得知道 ini 文件的格式吧。 + +首先,像下面这样一个段叫做 ``section``\ 。 + +.. code:: ini + + [type:name] + key = value + ... + +其上的type,主要有如下几种 + +1. ``composite`` (组合):多个app的路由分发; + + .. code:: ini + + [composite:main] + use = egg:Paste#urlmap + / = home + /blog = blog + /wiki = wiki + +2. app(应用):指明 WSGI 应用的路径; + + .. code:: ini + + [app:home] + paste.app_factory = example:Home.factory + +3. pipeline(管道):给一个 app + 绑定多个过滤器。将多个filter和最后一个WSGI应用串联起来。 + + .. code:: ini + + [pipeline:main] + pipeline = filter1 filter2 filter3 myapp + + [filter:filter1] + ... + + [filter:filter2] + ... + + [app:myapp] + ... + +4. filter(过滤器):以 app + 做为唯一参数的函数,并返回一个“过滤”后的app。通过键值next可以指定需要将请求传递给谁。next指定的可以是一个普通的WSGI应用,也可以是另一个过滤器。虽然名称上是过滤器,但是功能上不局限于过滤功能,可以是其它功能,例如日志功能,即将认为重要的请求数据记录下来。 + + .. code:: ini + + [app-filter:filter_name] + use = egg:... + next = next_app + + [app:next_app] + ... + +对 ini 文件有了一定的了解后,就可以看懂下面这个 ini 配置文件了 + +.. code:: ini + + [composite:main] + use = egg:Paste#urlmap + /blog = blog + /wiki = wiki + + [app:blog] + paste.app_factory = example:Blog.factory + + [app:wiki] + paste.app_factory = example:Wiki.factory + +**第二步是定义一个符合 WSGI 规范的 applicaiton 对象。** + +符合 WSGI 规范的 application +对象,可以有多种形式,函数,方法,类,实例对象。这里仅以实例对象为例(需要实现 +``__call__`` 方法),做一个演示。 + +.. code:: python + + import os + from paste import deploy + from wsgiref.simple_server import make_server + + class Blog(object): + def __init__(self): + print("Init Blog.") + + def __call__(self, environ, start_response): + status_code = "200 OK" + response_headers = [("Content-Type", "text/plain")] + response_body = "This is Blog's response body.".encode('utf-8') + + start_response(status_code, response_headers) + return [response_body] + + @classmethod + def factory(cls, global_conf, **kwargs): + print("Blog factory.") + return Blog() + +**最后,第三步是使用 loadapp 函数加载 WSGI 应用。** + +loadapp 是 PasteDeploy +提供的一个函数,使用它可以很方便地从第一步的ini配置文件里加载 app + +loadapp 函数可以接收两个实参: + +- URI:“config:” +- name:WSGI应用的名称 + +.. code:: python + + conf_path = os.path.abspath('paste.ini') + + # 加载 app + applications = deploy.loadapp("config:{}".format(conf_path) , "main") + + # 启动 server, 监听 localhost:22800 + server = make_server("localhost", "22800", applications) + server.serve_forever() + +applications 是URLMap 对象。 + +|image10| + +完善并整合第二步和第三步的内容,写成一个 Python +文件(wsgi_server.py)。内容如下 + +.. code:: python + + import os + from paste import deploy + from wsgiref.simple_server import make_server + + class Blog(object): + def __init__(self): + print("Init Blog.") + + def __call__(self, environ, start_response): + status_code = "200 OK" + response_headers = [("Content-Type", "text/plain")] + response_body = "This is Blog's response body.".encode('utf-8') + + start_response(status_code, response_headers) + return [response_body] + + @classmethod + def factory(cls, global_conf, **kwargs): + print("Blog factory.") + return Blog() + + + class Wiki(object): + def __init__(self): + print("Init Wiki.") + + def __call__(self, environ, start_response): + status_code = "200 OK" + response_headers = [("Content-Type", "text/plain")] + response_body = "This is Wiki's response body.".encode('utf-8') + + start_response(status_code, response_headers) + return [response_body] + + @classmethod + def factory(cls, global_conf, **kwargs): + print("Wiki factory.") + return Wiki() + + + if __name__ == "__main__": + app = "main" + port = 22800 + conf_path = os.path.abspath('paste.ini') + + # 加载 app + applications = deploy.loadapp("config:{}".format(conf_path) , app) + server = make_server("localhost", port, applications) + + print('Started web server at port {}'.format(port)) + server.serve_forever() + +一切都准备好后,在终端执行 ``python wsgi_server.py``\ 来启动 web server + +|image11| + +如果像上图一样一切正常,那么打开浏览器 + +- 访问http://127.0.0.1:8000/blog,应该显示:This is Blog’s response + body. +- 访问http://127.0.0.1:8000/wiki,应该显示:This is Wiki’s response + body.。 + +注意:urlmap对url的大小写是敏感的,例如如果访问http://127.0.0.1:8000/BLOG,在url映射中未能找到大写的BLOG。 + +到此,你学会了使用 PasteDeploy 的简单使用。 + +3.6.7 webob.dec.wsgify 装饰器 +----------------------------- + +经过了 PasteDeploy 的路由调度,我们找到了 +``nova.api.openstack.compute:APIRouterV21.factory`` 这个 application +的入口,看代码知道它其实返回了 APIRouterV21 类的一个实例。 + +|image12| + +WSGI规定 application 必须是一个 callable +的对象,函数、方法、类、实例,若是一个类实例,就要求这个实例所属的类实现 +``__call__`` 的方法。 + +APIRouterV21 本身没有实现 ``__call__`` ,但它的父类 Router实现了 +``__call__`` + +|image13| + +我们知道,application 必须遵丛 WSGI 的规范 + +1. 必须接收\ ``environ``, ``start_response``\ 两个参数; +2. 必须返回 「可迭代的对象」。 + +但从 Router 的 ``__call__`` +代码来看,它并没有遵从这个规范,它不接收这两个参数,也不返回 +response,而只是返回另一个 callable +的对象,就这样我们的视线被一次又一次的转移,但没有关系,这些\ ``__call__``\ 都是外衣,只要扒掉这些外衣,我们就能看到核心app。 + +而负责扒掉这层外衣的,就是其头上的装饰器 ``@webob.dec.wsgify`` ,wsgify +是一个类,其 ``__call__`` 源码实现如下:\ |image14| + +可以看出,wsgify 在这里,会将 req 这个原始请求(dict对象)封装成 Request +对象(就是规范1里提到的 +environ)。然后会一层一层地往里地执行被wsgify装饰的函数(self._route), +得到最内部的核心application。 + +上面提到了规范1里的第一个参数,补充下第二个参数start_response,它是在哪定义并传入的呢? + +其实这个无需我们操心,它是由 wsgi server 提供的,如果我们使用的是 +wsgiref 库做为 server 的话。那这时的 start_response 就由 wsgiref 提供。 + +再回到 wsgify,它的作用主要是对 WSGI app 进行封装,简化wsgi +app的定义与编写,它可以很方便的将一个 callable 的函数或对象,封装成一个 +WSGI app。 + +上面,其实留下了一个问题,self._route(routes 中间件 +RoutesMiddleware对象)是如何找到真正的 application呢? + +带着这个问题,我们了解下 routes 是如何为我们实现第二次路由。 + +3.6.8 第二次路由:中间件 routes 路由 +------------------------------------ + +在文章最开始处,我们给大家画了一张图。 + +|image15| + +这张图把一个 HTTP +请求粗略简单地划分为两个过程。但事实上,整个过程远比这个过程要复杂得多。 + +实际上在 WSGI Server 到 WSGI Application +这个过程中,我们加很多的功能(比如鉴权、URL路由),而这些功能的实现方式,我们称之为中间件。 + +中间件,对服务器而言,它是一个应用程序,是一个可调用对象, +有两个参数,返回一个可调用对象。而对应用程序而言,它是一个服务器,为应用程序提供了参数,并且调用了应用程序。 + +今天以URL路由为例,来讲讲中间件在实际生产中是如何起作用的。 + +当服务器拿到了客户端请求的URL,不同的URL需要交由不同的函数处理,这个功能叫做 +URL Routing。 + +在 Nova 中是用 routes +这个库来实现对URL的的路由调度。接下来,我将从源代码处分析一下这个过程。 + +在routes模块里有个中间件,叫 ``routes.middleware.RoutesMiddleware`` +,它将接受到的 url,自动调用 ``map.match()``\ 方法,对 url +进行路由匹配,并将匹配的结果存入request请求的环境变量\ ``['wsgiorg.routing_args']``\ ,最后会调用\ ``self._dispatch``\ (dispatch返回真正的application)返回response,最后会将这个response返回给 +WSGI Server。 + +|image16| + +这个中间件的原理,看起来是挺简单的。并没有很复杂的逻辑。 + +但是,我在阅读 routes 代码的时候,却发现了另一个令我困惑的点。 + +``self._dispatch`` (也就上图中的self.app)函数里,我们看到了 +app,controller 这几个很重要的字眼,其是否是我苦苦追寻的 application +对象呢? + +|image17| + +要搞明白这个问题,只要看清 match 到是什么东西? + +这个 match 对象 是在 ``RoutesMiddleware.__call__()`` 里塞进 +``req.environ`` 的,它是什么东西呢,我将其打印出来。 + +:: + + {'action': u'detail', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'} + + {'action': u'index', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'} + + {'action': u'show', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9', 'id': u'68323d9c-ebe5-499a-92e9-32fea900a892'} + +结果令人在失所望呀,这个 app 并不是我们要寻找的 Controller 对象。而是 +nova.api.openstack.wsgi.ResourceV21 类的实例对象,说白了就是Resource +对象。 + +看到这里,我有心态有点要崩了,怎么还没到 Controller?OpenStack +框架的代码绕来绕去的,没有点耐心还真的很难读下去。 + +既然已经开了头,没办法还得硬着头皮继续读了下去。 + +终于我发现,在APIRouter初始化的时候,它会去注册所有的 +Resource,同时将这些 Resource 交由 routes.Mapper +来管理、创建路由映射,所以上面提到的 routes.middleware.RoutesMiddleware +才能根据url通过 mapper.match 获取到相应的Resource。 + +从 Nova 代码中看出每个Resource 对应一个 Controller 对象,因为 Controller +对象本身就是对一种资源的操作集合。 + +|image18| + +通过日志的打印,可以发现 nova 管理的 Resource 对象有多么的多而杂 + +:: + + os-server-groups + os-keypairs + os-availability-zone + remote-consoles + os-simple-tenant-usage + os-instance-actions + os-migrations + os-hypervisors + diagnostics + os-agents + images + os-fixed-ips + os-networks + os-security-groups + os-security-groups + os-security-group-rules + flavors + os-floating-ips-bulk + os-console-auth-tokens + os-baremetal-nodes + os-cloudpipe + os-server-external-events + os-instance_usage_audit_log + os-floating-ips + os-security-group-default-rules + os-tenant-networks + os-certificates + os-quota-class-sets + os-floating-ip-pools + os-floating-ip-dns + entries + os-aggregates + os-fping + os-server-password + os-flavor-access + consoles + os-extra_specs + os-interface + os-services + servers + extensions + metadata + metadata + limits + ips + os-cells + versions + tags + migrations + os-hosts + os-virtual-interfaces + os-assisted-volume-snapshots + os-quota-sets + os-volumes + os-volumes_boot + os-volume_attachments + os-snapshots + os-server-groups + os-keypairs + os-availability-zone + remote-consoles + os-simple-tenant-usage + os-instance-actions + os-migrations + os-hypervisors + diagnostics + os-agents + images + os-fixed-ips + os-networks + os-security-groups + os-security-groups + os-security-group-rules + flavors + os-floating-ips-bulk + os-console-auth-tokens + os-baremetal-nodes + os-cloudpipe + os-server-external-events + os-instance_usage_audit_log + os-floating-ips + os-security-group-default-rules + os-tenant-networks + os-certificates + os-quota-class-sets + os-floating-ip-pools + os-floating-ip-dns + entries + os-aggregates + os-fping + os-server-password + os-flavor-access + consoles + os-extra_specs + os-interface + os-services + servers + extensions + metadata + metadata + limits + ips + os-cells + versions + tags + migrations + os-hosts + os-virtual-interfaces + os-assisted-volume-snapshots + os-quota-sets + os-volumes + os-volumes_boot + os-volume_attachments + os-snapshots + +你一定很好奇,这路由是如何创建的吧,关键代码就是如下一行。如果你想要了解更多路由的创建过程,可以看一下这篇文章(\ `Python +Route总结 `__\ ),写得不错。 + +.. code:: python + + routes.mapper.connect("server", + "/{project_id}/servers/list_vm_state", + controller=self.resources['servers'], + action='list_vm_state', + conditions={'list_vm_state': 'GET'}) + +历尽了千辛万苦,我终于找到了 Controller 对象,知道了请求发出后,wsgi +server是如何根据url找到对应的Controller(根据routes.Mapper路由映射)。 + +但是很快,你又会问。对于一个资源的操作(action),有很多,比如新增,删除,更新等 + +不同的操作要执行Controller 里不同的函数。 + +如果是新增资源,就调用 create() + +如果是删除资源,就调用 delete() + +如果是更新资源,就调用 update() + +那代码如何怎样知道要执行哪个函数呢? + +以/servers/xxx/action请求为例,请求调用的函数实际包含在请求的body中。 + +经过routes.middleware.RoutesMiddleware的\ ``__call__``\ 函数解析后,此时即将调用的Resource已经确定为哪个模块中的Controller所构建的Resource,而 +action 参数为“action”,接下来在Resource的\ ``__call__`` +函数里面会因为action==“action”从而开始解析body的内容,找出Controller中所对应的方法。 + +Controller在构建的过程中会由于MetaClass的影响将其所有action类型的方法填入一个字典中,key由每个\ ``_action_xxx``\ 方法前的 +``@wsgi.action('xxx')``\ 装饰函数给出,value为每个_action_xxx方法的名字(从中可以看出规律,在body里面请求的方法名前加上_aciton_即为Controller中对应调用的方法)。 + +之后在使用Controller构建Resource对象的过程中会向Resource注册该Controller的这个字典中的内容。这样,只需在请求的body中给出调用方法的key,然后就可以找到这个key所映射的方法,最后在Resource的__call__函数中会调用Controller类的这个函数! + +其实我在上面我们打印 match 对象时,就已经将对应的函数打印出来了。 + +这边以 nova show(展示资源为例),来理解一下。 + +当你调用 nova show [uuid] 命令,novaclient 就会给 nova-api +发送一个http的请求 + +.. code:: shell + + nova show 1c250b15-a346-43c5-9b41-20767ec7c94b + +通过打印得到的 match 对象如下 + +:: + + {'action': u'show', 'controller': , 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'} + +其中 action 就是对应的处理函数,而controller 就对应的 Resource +对象,project_id 是租户id(你可以不理会)。 + +继续看 ResourceV21 类里的 ``__call__`` 函数的代码。 + +图示地方,会从 environ 里获取中看到获取 action 的具体代码 + +|image19| + +我将这边的 action_args打印出来 + +:: + + {'action': 'show', 'project_id': '2ac17c7c792d45eaa764c30bac37fad9', 'id': '1c250b15-a346-43c5-9b41-20767ec7c94b'} + +其中 action 还是是函数名,id 是要操作的资源的唯一id标识。 + +在 ``__call__`` 的最后,会 调用 ``_process_stack`` 方法 + +|image20| + +在图标处,get_method 会根据 action(函数名) 取得处理函数对象。 + +.. code:: python + + meth :> + +最后,再执行这个函数,取得 action_result,在 ``_process_stack`` 会对 +response 进行初步封装。 + +|image21| + +然后将 response 再返回到 wsgify ,由这个专业的工具函数,进行 response +的最后封装和返回给客户端。 + +|image22| + +至此,一个请求从发出到响应就结束了。 + +-------------- + +附录:参考文章: +---------------- + +- `PEP 3333 中文翻译 `__ +- `nova-api源码分析(APP的调用) `__ +- `Python + Route总结 `__ +- `Python routes Mapper + 的使用 `__ +- `详解 Paste + deploy `__ +- `paste.ini + 文件使用说明 `__ +- `PasteDeploy + 小白教程 `__ +- `WSGI + 两种架构图 `__ +- `伯乐在线:Python + Web开发最难懂的WSGI协议 `__ +- `WSGI 简介 `__ + +-------------- + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + + +.. |image0| image:: http://image.python-online.cn/20190607131728.png +.. |image1| image:: http://image.python-online.cn/20190607191954.png +.. |image2| image:: http://image.python-online.cn/20190607134310.png +.. |image3| image:: http://image.python-online.cn/20190607140817.png +.. |image4| image:: http://image.python-online.cn/20190607140922.png +.. |image5| image:: http://image.python-online.cn/20190530212557.png +.. |image6| image:: http://image.python-online.cn/20190530212753.png +.. |image7| image:: http://image.python-online.cn/20190530212956.png +.. |image8| image:: http://image.python-online.cn/20190530214820.png +.. |image9| image:: http://image.python-online.cn/20190530221101.png +.. |image10| image:: http://image.python-online.cn/20190607154119.png +.. |image11| image:: http://image.python-online.cn/20190607155432.png +.. |image12| image:: http://image.python-online.cn/20190602173212.png +.. |image13| image:: http://image.python-online.cn/20190602173956.png +.. |image14| image:: http://image.python-online.cn/20190605203016.png +.. |image15| image:: http://image.python-online.cn/20190607131728.png +.. |image16| image:: http://image.python-online.cn/20190608211233.png +.. |image17| image:: http://image.python-online.cn/20190531211542.png +.. |image18| image:: http://image.python-online.cn/20190531225529.png +.. |image19| image:: http://image.python-online.cn/20190602220246.png +.. |image20| image:: http://image.python-online.cn/20190602220511.png +.. |image21| image:: http://image.python-online.cn/20190602220700.png +.. |image22| image:: http://image.python-online.cn/20190605203016.png + diff --git a/source/c04/c04_14.rst b/source/c04/c04_14.rst index f24a60a..cefe3f5 100644 --- a/source/c04/c04_14.rst +++ b/source/c04/c04_14.rst @@ -54,6 +54,7 @@ mytest是我们的项目目录。如果是python3.6 应该是自带的。 $ pipenv --three # 相当于 pipenv --python /usr/bin/python3 $ pipenv --python 3.7 # 也可以指定具体的版本 + pipenv install --python 2 4.14.3 查询虚拟环境 ------------------- @@ -78,7 +79,8 @@ mytest是我们的项目目录。如果是python3.6 应该是自带的。 $ pipenv shell # 退出这个虚拟环境 - $ exit + $ exit (有的终端使用这个会退出) + $ deactivate # 移除当前目录的虚拟环境 $ pipenv --rm diff --git a/source/c04/c04_17.rst b/source/c04/c04_17.rst index e42f5ff..61a6472 100644 --- a/source/c04/c04_17.rst +++ b/source/c04/c04_17.rst @@ -238,9 +238,14 @@ Order 4.17.2 单例模式 --------------- -之前在另一篇公众号文章看到一个挺搞笑的例子(原文在文末): +之前在另一篇公众号文章看到一个挺搞笑的例子: -大意是讲,老婆在中国其实就是一个活生生的单例,你要娶一个老婆需要去民政局注册登记(要对类进行实例化),当你想再娶一个老婆时,这时民政局会说,不行,你已经有一个老婆了,并且告诉你老婆是谁。 +大意是讲,老婆在中国其实就是一个很形象的单例,你要娶一个老婆需要去民政局注册登记(要对类进行实例化),当你想再娶一个老婆时,这时民政局会说,不行,你已经有一个老婆了,并且它还会告诉你的老婆是谁。 + +然后有个朋友,还很生趣地评论说 + + **单例模式** + 允许你讨无数个老婆,但最终你会发现你讨来的老婆都是同一个人 玩笑之后,再回到我们的话题,先举几类我们经常见到的例子: diff --git a/source/c04/c04_18.rst b/source/c04/c04_18.rst new file mode 100644 index 0000000..82c67d1 --- /dev/null +++ b/source/c04/c04_18.rst @@ -0,0 +1,38 @@ +4.18 Mac 高效工具 +================= + +4.18.1 iTerm2 +------------- + +介绍一下快捷键 + +:: + + # 分窗口操作 + shift + command + d(横切)command + d(竖切) + + 2、历史信息查找和粘贴:command + f,呼出查找功能,找到后 tab 键可以选中找到的文本,通过option + 回车粘贴。 + 3、自动完成:command + ; ,呼出自动完成窗口,根据上下文提供内容选择项 + # 粘贴历史 + shift + command + h + # 回放功能 + option + command + b + # 光标去哪了? + command + / + + # Expose Tabs: + option + command + e + +4.18.2 Finder +------------- + +:: + + command + up # 文件夹后退 + command + down # 文件夹前进 + +-------------- + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + diff --git a/source/c04/c04_19.rst b/source/c04/c04_19.rst new file mode 100644 index 0000000..ebb3990 --- /dev/null +++ b/source/c04/c04_19.rst @@ -0,0 +1,676 @@ +4.19 程序员编码必学:Vim +======================== + +|image0| + +## vim模式 +---------- + +.. code:: shell + + 正常模式(按Esc或Ctrl+[进入) 左下角显示文件名或为空 + 插入模式(按i进入) 左下角显示--INSERT-- + 可视模式(按v进入) 左下角显示--VISUAL-- + +打开文档 +-------- + +-------------- + +.. code:: shell + + + vim file 打开单个文件 + vim file1 file2.. 同时打开多个文件 + + + :open [file] 在vim窗口中打开一个新文件 + + 【举个例子】 + # 当前打开1.txt,做了一些编辑没保存 + :open! 放弃这些修改,并重新打开未修改的文件 + + # 当前打开1.txt,做了一些编辑并保存 + :open 2.txt 直接退出对1.txt的编辑,直接打开2.txt编辑,省了退出:wq再重新vim 2.txt的步骤 + + + # 打开远程文件,比如ftp或者share folder + + :e ftp://192.168.10.76/abc.txt + :e \qadrive\test\1.txt + + + vim -R file 仅是查看文件,不向文件写入内容,可以用只读形式编辑文件。 + vim -M file 如果是想强制性地避免对文件进行修改, + +插入命令 +-------- + +-------------- + +.. code:: shell + + + i 在当前位置生前插入 + + I 在当前行首插入 + + a 在当前位置后插入 + + A 在当前行尾插入 + + o 在当前行之后插入一行 + + O 在当前行之前插入一行 + +## 查找命令 +----------- + +.. code:: shell + + 【简单查找】 + /text  查找text,按n健查找下一个,按N健查找前一个。 + ?text  查找text,反向查找,按n健查找下一个,按N健查找前一个。 + + vim中有一些特殊字符在查找时需要转义  .*[]^%/?~$ + + :set ignorecase  忽略大小写的查找 + :set noignorecase  不忽略大小写的查找 + +精准查找 + +:: + + + 【高级查找】 + * 寻找游标所在处的单词 + # 同上,但 \# 是向前(上)找,\*则是向后(下)找 + g\*同\* 但部分符合该单词即可 + g\#同\# 但部分符合该单词即可 + + 以上查找n,N 的继续查找命令依然可以用 + + 【匹配单词查找】 + 如果文本中有 hello helloworld hellopython + + 那我使用 /hello ,这三个词都会匹配到。 + 如何精准查找呢? + + 使用 /hello\> + + 【匹配行首,行尾】 + /^hello + /world$ + +## 替换命令 +----------- + +.. code:: shell + + ~ 反转游标字母大小写 + + r<字母> 将当前字符替换为所写字母 + R<字母><字母>... 连续替换字母 + + cc 替换整行(就是删除当前行,并在下一行插入) + cw 替换一个单词(就是删除一个单词,就进入插入模式),前提是游标处于单词第一个字母(可用b定位) + C (大写C)替换至行尾(和D有所区别,D是删除(剪切)至行尾,C是删除至行位并进入插入模式) + + :s/old/new/ 用old替换new,替换当前行的第一个匹配 + :s/old/new/g 用old替换new,替换当前行的所有匹配 + + :%s/old/new/ 用old替换new,替换所有行的第一个匹配 + :%s/old/new/g 用old替换new,替换整个文件的所有匹配 + + + + :10,20 s/^/ /g 在第10行至第20行每行前面加四个空格,用于缩进。 + + ddp 交换光标所在行和其下紧邻的一行。 + +撤销和重做 +---------- + +-------------- + +.. code:: shell + + + u 撤销(Undo) + + U 撤销对整行的操作 + + Ctrl + r 重做(Redo),即撤销的撤销。 + +## 删除命令 +----------- + +.. code:: shell + + x 删除当前字符 + 3x 删除当前字符3次 + + X 删除当前字符的前一个字符。 + 3X 删除当前光标向前三个字符 + + dw 删除当前字符到单词尾 + daw 删除当前字符所在单词 + + dl 删除当前字符, dl=x + dh 删除前一个字符,X=dh + + + dd 删除当前行 + dj 删除下一行 + dk 删除上一行 + + dgg 删除当前行至文档首部 + d1G 删除当前行至文档首部 + dG 删除当前行至文档尾部 + + kdgg 删除当前行之前所有行(不包括当前行) + jdG 删除当前行之后所有行(不包括当前行) + + + + D 删除当前字符至行尾。D=d$ + d$ 删除当前字符至行尾 + d^ 删除当前字符之前至行首 + + + 10d 删除当前行开始的10行。 + :1,10d 删除1-10行 + :11,$d 删除11行及以后所有的行 + :1,$d 删除所有行 + J   删除两行之间的空行,实际上是合并两行。 + +## 复制粘贴 +----------- + +普通模式中使用y复制 + +:: + + yy 复制游标所在的整行(3yy表示复制3行) + + y^ 复制至行首,或y0。不含光标所在处字符。 + y$ 复制至行尾。含光标所在处字符。 + + yw 复制一个单词。 + y2w 复制两个单词。 + + yG 复制至文本末。 + y1G 复制至文本开头。 + +普通模式中使用p粘贴 + +:: + + p(小写)代表粘贴至光标后(下) + P(大写)代表粘贴至光标前(上) + +剪切粘贴 +-------- + +-------------- + +.. code:: shell + + dd 其实就是剪切命令,剪切当前行 + ddp 剪切当前行并粘贴,可实现当前行和下一行调换位置 + + + 正常模式下按v(逐字)或V(逐行)进入可视模式 + 然后用jklh命令移动即可选择某些行或字符,再按d即可剪切 + + ndd 剪切当前行之后的n行。利用p命令可以对剪切的内容进行粘贴 + + :1,10d 将1-10行剪切。利用p命令可将剪切后的内容进行粘贴。 + + :1, 10 m 20 将第1-10行移动到第20行之后。 + +退出命令 +-------- + +-------------- + +.. code:: shell + + + :wq 保存并退出 + + ZZ 保存并退出 + + :q! 强制退出并忽略所有更改 + + :e! 放弃所有修改,并打开原来文件。 + + ZZ 保存并退出 + + :sav(eas) new.txt 另存为一个新文件,退出原文件的编辑且不会保存 + :f(ile) new.txt 新开一个文件,并不保存,退出原文件的编辑且不会保存 + +移动命令 +-------- + +-------------- + +:: + + + h 左移一个字符 + l 右移一个字符 + k 上移一个字符 + j 下移一个字符 + + + # 10指代所有数字,可任意指定 + + 10h 左移10个字符 + 10l 右移10个字符 + 10k 上移10行 + 10j 下移10行 + + + w 向前移动一个单词(光标停在单词首部) + b 向后移动一个单词 + e,同w,只不过是光标停在单词尾部 + ge 同b,光标停在单词尾部。 + + + + ^ 移动到本行第一个非空白字符上。 + 0 移动到本行第一个字符上(可以是空格) + + $ 移动到行尾 + 3$ 移动到下面3行的行尾 + + gg 移动到文件头。 = [[ + G 移动到文件尾。 = ]] + + ( 移动到句首 + ) 移动到句尾 + + + 【定位字符】f和F + + fx 找到光标后第一个为x的字符 + 3fd 找到光标后第三个为d的字符 + + F 同f,反向查找。 + + 【返回一次定位】: + 使用 `` 可以返回一次跳转的位置。只记录一次。 + + 【具名标记】 + 使用 ma ,可以将此处标记为 a,使用 'a 进行跳转 + 使用 :marks 可以查看所有的标记 + 使用 :delm!可以删除所有的标记 + + 这个很好用,可以跨文件。 + + +## 排版/缩进 +------------ + +缩进 +~~~~ + +:: + + :set shiftwidth? 查看缩进值 + :set shiftwidth=4 设置缩进值为4 + + # 缩进相关 最好写到配置文件中 ~/.vimrc + :set tabstop=4 + :set softtabstop=4 + :set shiftwidth=4 + :set expandtab + + >> 向右缩进 + << 取消缩进 + +排版 +~~~~ + +:: + + :ce 居中 + :le 靠左 + :ri 靠右 + +对代码缩进,还可以用 ``==`` 对当前行缩进,如果要对多行对待缩进,则使用 +n\ ``==``\ 。 + +注释命令 +-------- + +-------------- + +多行注释 +~~~~~~~~ + +:: + + 进入命令行模式,按ctrl + v进入 visual block模式,然后按j, 或者k选中多行,把需要注释的行标记起来 + + 按大写字母I,再插入注释符,例如// + + 按esc键就会全部注释了 + +取消多行注释 +~~~~~~~~~~~~ + +:: + + 进入命令行模式,按ctrl + v进入 visual block模式,按字母l横向选中列的个数,例如 // 需要选中2列 + + 按字母j,或者k选中注释符号 + + 按d键就可全部取消注释 + +复杂注释 +~~~~~~~~ + +.. code:: shell + + + :3,5 s/^/#/g 注释第3-5行 + :3,5 s/^#//g 解除3-5行的注释 + + + :1,$ s/^/#/g 注释整个文档 + :1,$ s/^#//g 取消注释整个文档 + + + :%s/^/#/g 注释整个文档,此法更快 + :%s/^#//g 取消注释整个文档 + +调整视野 +-------- + +:: + + "zz":命令会把当前行置为屏幕正中央, + "zt":命令会把当前行置于屏幕顶端 + "zb":则把当前行置于屏幕底端. + + Ctrl + e 向下滚动一行 + Ctrl + y 向上滚动一行 + + Ctrl + d 向下滚动半屏 + Ctrl + u 向上滚动半屏 + + Ctrl + f 向下滚动一屏 + Ctrl + b 向上滚动一屏 + + + 【跳到指定行】:两种方法 + + 可以先把行号打开 + :set nu 打开行号 + + :20 跳到第20行 + 20G 跳到第20行 + +区域选择 +-------- + +:: + + 要进行区域选择,要先进入可视模式 + + v 以字符为单位,上下左右选择 + V 以行为单位,上下选择 + + 选择后可进行操作 + d 剪切/删除 + y 复制 + + Ctrl+v 如果当前是V(大写)模式,就变成v(小写) + 如果当前是v(小写)模式,就变成普通模式。 + 如果当前是普通模式,就进入v(小写)模式 + + 利用这个,可以进行多行缩进。 + + ggVG 选择全文 + +## 窗口控制 +----------- + +新建窗口 +~~~~~~~~ + +.. code:: shell + + # 打开两个文件分属两个窗口 + vim -o 1.txt 2.txt + + + # 假设现在已经打开了1.txt + + :sp 2.txt 开启一个横向的窗口,编辑2.txt + :vsp 2.txt 开启一个竖向的窗口,编辑2.txt + + :split 将当前窗口再复制一个窗口出来,内容同步,游标可以不同 + :split 2.txt 在新窗口打开2.txt的横向窗口 + + # 需要注意:内容同步,但是游标位置是独立的 + + Ctrl-w s 将当前窗口分成水平窗口 + Ctrl-w v 将当前窗口分成竖直窗口 + + Ctrl-w q 等同:q 结束分割出来的视窗。 + Ctrl-w q! 等同:q! 结束分割出来的视窗。 + Ctrl-w o 打开一个视窗并且隐藏之前的所有视窗 + +窗口切换 +~~~~~~~~ + +.. code:: shell + + # 特别说明:Ctrl w <字母> 不需要同时按 + + Ctrl-w h 切换到左边窗口 + Ctrl-w l 切换到右边窗口 + + Ctrl-w j 切换到下边窗口 + Ctrl-w k 切换到上边窗口 + + + # 特别说明:全屏模式下 + :n 切换下一个窗口 + :N 切换上一个窗口 + :bp 切换上一个窗口 + + # 特别说明:非全屏模式 + + :bn 切换下一个窗口,就当前位置的窗口的内容变了,其他窗口不变 + :bN 切换上一个窗口,就当前位置的窗口的内容变了,其他窗口不变 + +窗口移动 +~~~~~~~~ + +.. code:: shell + + # 特别说明:Ctrl w <字母> 不需要同时按 + + Ctrl-w J 将当前视窗移至最下面 + Ctrl-w K 将当前视窗移最上面 + + Ctrl-w H 将当前视窗移至最左边 + Ctrl-w L 将当前视窗移至最右边 + + Ctrl-ww 按顺序切换窗口 + +调整尺寸 +~~~~~~~~ + +.. code:: shell + + # 友情提示:键盘切记不要处于中文状态 + + Ctrl-w + 增加窗口高度 + Ctrl-w - 减少窗口高度 + +退出窗口 +~~~~~~~~ + +.. code:: shell + + :close 关闭当前窗口 + :close! 强制关闭当前窗口 + + :q 退出,不保存 + :q! 强制退出,不保存 + + :x 保存退出 + :wq 保存退出 + :wq! 强制保存退出 + + :w <[路径/]文件名> 另存为 + :savesa <[路径/]文件名> 另存为 + + ZZ 保存并退出。 + + :only 关闭所有窗口,只保留当前窗口(前提:其他窗口内容有改变的话都要先保存) + :only! 关闭所有窗口,只保留当前窗口 + + :qall 放弃所有操作并退出 + :wall 保存所有, + :wqall 保存所有并退出。 + +## 文档加密 +----------- + +:: + + vim -x file_name + + 然后输入密码: + 确认密码: + + 如果不修改内容也要保存。:wq,不然密码设定不会生效。 + +录制宏 +------ + +-------------- + +.. code:: shell + + + 按q键加任意字母开始录制,再按q键结束录制(这意味着vim中的宏不可嵌套),使用的时候@加宏名,比如qa。。。q录制名为a的宏,@a使用这个宏。 + + 执行shell命令 + + :!command + + :!ls 列出当前目录下文件 + + :!perl -c script.pl 检查perl脚本语法,可以不用退出vim,非常方便。 + + :!perl script.pl 执行perl脚本,可以不用退出vim,非常方便。 + + :suspend或Ctrl - Z 挂起vim,回到shell,按fg可以返回vim。 + +帮助命令 +-------- + +-------------- + +.. code:: shell + + + + 在Unix/Linux系统上 + $ vimtutor + + # 普通模式下 + 键盘输入vim或F1 + + # 命令行模式下 + + :help 显示整个帮助 + :help xxx 显示xxx的帮助,比如 :help i, :help CTRL-[(即Ctrl+[的帮助)。 + :help 'number' Vim选项的帮助用单引号括起 + + + 在Windows系统上 + :help tutor + +## 配置命令 +----------- + +显示当前设定 +~~~~~~~~~~~~ + +.. code:: shell + + set或者:se显示所有修改过的配置 + set all 显示所有的设定值 + set option? 显示option的设定值 + set nooption 取消当期设定值 + +更改设定 +~~~~~~~~ + +.. code:: shell + + :set nu 显示行号 + + set autoindent(ai) 设置自动缩进 + set autowrite(aw) 设置自动存档,默认未打开 + set backup(bk) 设置自动备份,默认未打开 + + set background=dark或light,设置背景风格 + + set cindent(cin) 设置C语言风格缩进 + + :set ts=4 设置tab键转换为4个空格 + + :set shiftwidth? 查看缩进值 + :set shiftwidth=4 设置缩进值为4 + + :set ignorecase  忽略大小写的查找 + :set noignorecase  不忽略大小写的查找 + + :set paste # insert模式下,粘贴格式不会乱掉 + + :set ruler?  查看是否设置了ruler,在.vimrc中,使用set命令设制的选项都可以通过这个命令查看 + + :scriptnames  查看vim脚本文件的位置,比如.vimrc文件,语法文件及plugin等。 + + :set list 显示非打印字符,如tab,空格,行尾等。如果tab无法显示,请确定用set lcs=tab:>-命令设置了.vimrc文件,并确保你的文件中的确有tab,如果开启了expendtab,那么tab将被扩展为空格。 + +## 其他命令 +----------- + +.. code:: shell + + . 重复前一次命令 + + + :ver 显示vim的所有信息(包括版本和参数等) + + + # 需要注意:全屏模式下 + :args 查看当前打开的文件列表,当前正在编辑的文件会用[]括起来 + + + :syntax 列出已经定义的语法项 + :syntax clear 清除已定义的语法规则 + + :syntax case match 大小写敏感,int和Int将视为不同的语法元素 + :syntax case ignore 大小写无关,int和Int将视为相同的语法元素,并使用同样的配色方案 + + J 将两行合并为一行 + +-------------- + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + + +.. |image0| image:: https://i.loli.net/2017/08/21/599af0db81bfe.png + diff --git a/source/c07/c07_02.rst b/source/c07/c07_02.rst index ea7717a..ae1f985 100755 --- a/source/c07/c07_02.rst +++ b/source/c07/c07_02.rst @@ -522,6 +522,10 @@ Script parameters。 |image18| +在创建用户的时候,要指定用户组,要注意这个用户组的权限一定要有相应主机组的权限,否则无法发送告警邮件。 + +|image19| + 配置好收件人后 ,发件人呢? 发件人需要你在 Zabbix Server 所在的服务器上安装并正确配置好 smtp @@ -538,12 +542,12 @@ Script parameters。 具体你可以参考这篇文章:\ `zabbix 服务器设置邮件报警 `__ -7.2.6 监控数据库 +7.2.4 监控数据库 ---------------- zabbix 自带 mysql 的监控模板,监控项不多,只有 14 项。 -|image19| +|image20| 这个模板在使用前,需要进行两个配置。 @@ -573,7 +577,7 @@ zabbix 自带 mysql 的监控模板,监控项不多,只有 14 项。 能够及时将配置文件路径修改过来就行。如下图所示,你只要修改下方 ``HOME`` 变量值。 -|image20| +|image21| 然后记得去 web界面在 该host主机上link到 ``Template DB MySQL`` 这个模板上。 @@ -684,6 +688,8 @@ float ,log, text 等,所以计算存在一定的误差,需留有冗余 - `zabbix客户端自动注册 `__ - `Download and install Zabbix `__ +- `zabbix + 自带宏作用域 `__ -------------- @@ -710,6 +716,7 @@ float ,log, text 等,所以计算存在一定的误差,需留有冗余 .. |image16| image:: http://image.python-online.cn/20190411205822.png .. |image17| image:: http://image.python-online.cn/20190417202834.png .. |image18| image:: http://image.python-online.cn/20190404204534.png -.. |image19| image:: http://image.python-online.cn/20190409103417.png -.. |image20| image:: http://image.python-online.cn/20190409104026.png +.. |image19| image:: http://image.python-online.cn/20190605173956.png +.. |image20| image:: http://image.python-online.cn/20190409103417.png +.. |image21| image:: http://image.python-online.cn/20190409104026.png diff --git a/source/c08/c08_01.rst b/source/c08/c08_01.rst index 6d6581a..5a11c1c 100755 --- a/source/c08/c08_01.rst +++ b/source/c08/c08_01.rst @@ -133,11 +133,12 @@ aggregate管理 $ neutron net-create --provider:physical_network phynet1 --provider:network_type flat private # 创建子网 全 - $ neutron subnet-create [--name ming-subnet(通常不写自动生成)] \ - private 192.168.2.0/24 \ - --allocation-pool start=192.168.2.200,end=192.168.2.230 \ - --gateway 192.168.2.253 \ - private PROVIDER_NETWORK_CIDR + $neutron subnet-create --name public\ + --allocation-pool start=172.20.20.100,end=172.20.20.199 \ + --gateway 172.20.20.200 \ + --enable_dhcp=False \ + --dns_nameserver 114.114.114.114 \ + public 172.20.20.0/24 # 创建子网,更多选项可以查看 neutron subnet-create -h neutron subnet-create --name 192.168.2.0/24 --allocation-pool start=192.168.2.200,end=192.168.2.230 --gateway 192.168.2.253 --dns-nameserver 114.114.114.114 --disable-dhcp private 192.168.2.0/24 @@ -165,7 +166,7 @@ aggregate管理 https://docs.openstack.org/project-install-guide/baremetal/draft/configure-glance-images.html # 上传镜像 (具体看哪glance help image-create) - $ glance image-create --name centos6.5-old --visibility public --disk-format qcow2 --container-format bare --property ws:predownload=True --file /home/ + $ glance image-create --name centos6.5-old --visibility public --disk-format qcow2 --container-format bare --property ws:predownload=True --file /home/ 1.4 keystone ~~~~~~~~~~~~ diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index b04b84e..32d04c4 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -373,21 +373,38 @@ neutronclient 相对应的异常。 |image34| -8.5.11 nova的各项服务服务是如何启动的? ---------------------------------------- +8.5.11 nova-compute 如何启动的? +-------------------------------- + +从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 +``nova.cmd.compute:main()`` + +|image35| + +从这个入口进去,会开启一个 ``nova-compute`` 的服务。 + +|image36| + +当调用 service.Service.create 时(create +是一个工厂函数),实际是返回实例化的 service.Service 对象。当没有传入 +manager 时,就会以binary 里的为准。比如binary +是\ ``nova-compute``\ ,那manager_cls 就是 +``compute_manager``\ ,对应的manager 导入路径,会从配置里读取。 + +|image37| 8.5.13 支持指定子网和指定ip --------------------------- 在 nova-api 接收请求处。 -|image35| +|image38| -|image36| +|image39| 对 network_info 进行解析,然后塞给 request 对象返回。 -|image37| +|image40| -------------- @@ -430,7 +447,10 @@ neutronclient 相对应的异常。 .. |image32| image:: http://image.python-online.cn/20190526140336.png .. |image33| image:: http://image.python-online.cn/20190526140410.png .. |image34| image:: http://image.python-online.cn/20190526143235.png -.. |image35| image:: http://image.python-online.cn/20190529203441.png -.. |image36| image:: http://image.python-online.cn/20190529215953.png -.. |image37| image:: http://image.python-online.cn/20190529215825.png +.. |image35| image:: http://image.python-online.cn/20190526205152.png +.. |image36| image:: http://image.python-online.cn/20190526165007.png +.. |image37| image:: http://image.python-online.cn/20190526204328.png +.. |image38| image:: http://image.python-online.cn/20190529203441.png +.. |image39| image:: http://image.python-online.cn/20190529215953.png +.. |image40| image:: http://image.python-online.cn/20190529215825.png diff --git a/source/c08/c08_07.rst b/source/c08/c08_07.rst index 1adeea1..7f2dbb9 100644 --- a/source/c08/c08_07.rst +++ b/source/c08/c08_07.rst @@ -6,6 +6,8 @@ 检查是否有 GPU 设备:\ ``lspci | grep NVIDIA`` +(若没有lspci命令,则安装 yum install pciutils -y) + |image0| 此时查看,驱动是 nvidia @@ -55,6 +57,8 @@ ,显示KVM),一旦跑在虚拟机里,就会出错,所以我们需要对显卡驱动隐藏hypervisor id。 +(若没有cpuid,则安装 yum install -y cpuid) + |image5| 如何隐藏 hypervisor id,只需要在xml的feature加上这段。 @@ -158,5 +162,5 @@ GPU 作为一种硬件资源,同样需要在模板中配置,配置方式是 .. |image7| image:: http://image.python-online.cn/20190528105408.png .. |image8| image:: http://image.python-online.cn/20190528105021.png .. |image9| image:: http://image.python-online.cn/20190528114526.png -.. |image10| image:: http://image.python-online.cn/20190528141106.png +.. |image10| image:: http://image.python-online.cn/20190606185531.png From 843a4f21f780fc7eaa84b71fcd870c8f68b40e15 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 11 Jun 2019 00:04:40 +0800 Subject: [PATCH 035/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0vim=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_19.md | 30 +++++++--------------- source/c04/c04_19.rst | 58 +++++++++++++++---------------------------- 2 files changed, 29 insertions(+), 59 deletions(-) diff --git a/source/c04/c04_19.md b/source/c04/c04_19.md index bc2e0b3..db7784f 100644 --- a/source/c04/c04_19.md +++ b/source/c04/c04_19.md @@ -5,7 +5,7 @@ ## vim模式 ---- + ```shell 正常模式(按Esc或Ctrl+[进入) 左下角显示文件名或为空 插入模式(按i进入) 左下角显示--INSERT-- @@ -13,7 +13,6 @@ ``` ## 打开文档 ---- ```shell @@ -45,7 +44,6 @@ vim -M file 如果是想强制性地避免对文件进行修改, ## 插入命令 ---- ```shell @@ -64,7 +62,7 @@ O 在当前行之前插入一行 ``` ## 查找命令 ---- + ```shell 【简单查找】 /text  查找text,按n健查找下一个,按N健查找前一个。 @@ -103,7 +101,7 @@ g\#同\# 但部分符合该单词即可 ## 替换命令 ---- + ```shell ~ 反转游标字母大小写 @@ -132,7 +130,6 @@ ddp 交换光标所在行和其下紧邻的一行。 ## 撤销和重做 ---- ```shell @@ -147,7 +144,7 @@ Ctrl + r 重做(Redo),即撤销的撤销。 ## 删除命令 ---- + ```shell x 删除当前字符 3x 删除当前字符3次 @@ -188,7 +185,7 @@ J   删除两行之间的空行,实际上是合并两行。 ``` ## 复制粘贴 ---- + 普通模式中使用y复制 ``` yy 复制游标所在的整行(3yy表示复制3行) @@ -211,7 +208,6 @@ P(大写)代表粘贴至光标前(上) ## 剪切粘贴 ---- ```shell dd 其实就是剪切命令,剪切当前行 @@ -232,7 +228,6 @@ ndd 剪切当前行之后的n行。利用p命令可以对剪切的内容进行 ## 退出命令 ---- ```shell @@ -253,7 +248,6 @@ ZZ 保存并退出 ## 移动命令 ---- ``` @@ -313,7 +307,7 @@ F 同f,反向查找。 ``` ## 排版/缩进 ---- + ### 缩进 ``` :set shiftwidth? 查看缩进值 @@ -339,7 +333,6 @@ F 同f,反向查找。 ## 注释命令 ---- ### 多行注释 ``` @@ -421,7 +414,7 @@ ggVG 选择全文 ## 窗口控制 ---- + ### 新建窗口 ```shell # 打开两个文件分属两个窗口 @@ -511,7 +504,7 @@ ZZ 保存并退出。 ``` ## 文档加密 ---- + ``` vim -x file_name @@ -524,7 +517,6 @@ vim -x file_name ## 录制宏 ---- ```shell @@ -548,7 +540,6 @@ vim -x file_name ## 帮助命令 ---- ```shell @@ -573,7 +564,6 @@ $ vimtutor ## 配置命令 ---- ### 显示当前设定 ```shell @@ -613,7 +603,7 @@ set cindent(cin) 设置C语言风格缩进 ``` ## 其他命令 ---- + ```shell . 重复前一次命令 @@ -634,8 +624,6 @@ set cindent(cin) 设置C语言风格缩进 J 将两行合并为一行 ``` - - --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c04/c04_19.rst b/source/c04/c04_19.rst index ebb3990..35709e8 100644 --- a/source/c04/c04_19.rst +++ b/source/c04/c04_19.rst @@ -3,8 +3,8 @@ |image0| -## vim模式 ----------- +vim模式 +------- .. code:: shell @@ -15,8 +15,6 @@ 打开文档 -------- --------------- - .. code:: shell @@ -46,8 +44,6 @@ 插入命令 -------- --------------- - .. code:: shell @@ -63,8 +59,8 @@ O 在当前行之前插入一行 -## 查找命令 ------------ +查找命令 +-------- .. code:: shell @@ -102,8 +98,8 @@ /^hello /world$ -## 替换命令 ------------ +替换命令 +-------- .. code:: shell @@ -131,8 +127,6 @@ 撤销和重做 ---------- --------------- - .. code:: shell @@ -142,8 +136,8 @@ Ctrl + r 重做(Redo),即撤销的撤销。 -## 删除命令 ------------ +删除命令 +-------- .. code:: shell @@ -184,8 +178,8 @@ :1,$d 删除所有行 J   删除两行之间的空行,实际上是合并两行。 -## 复制粘贴 ------------ +复制粘贴 +-------- 普通模式中使用y复制 @@ -212,8 +206,6 @@ 剪切粘贴 -------- --------------- - .. code:: shell dd 其实就是剪切命令,剪切当前行 @@ -232,8 +224,6 @@ 退出命令 -------- --------------- - .. code:: shell @@ -253,8 +243,6 @@ 移动命令 -------- --------------- - :: @@ -310,8 +298,8 @@ 这个很好用,可以跨文件。 -## 排版/缩进 ------------- +排版/缩进 +--------- 缩进 ~~~~ @@ -345,8 +333,6 @@ n\ ``==``\ 。 注释命令 -------- --------------- - 多行注释 ~~~~~~~~ @@ -435,8 +421,8 @@ n\ ``==``\ 。 ggVG 选择全文 -## 窗口控制 ------------ +窗口控制 +-------- 新建窗口 ~~~~~~~~ @@ -540,8 +526,8 @@ n\ ``==``\ 。 :wall 保存所有, :wqall 保存所有并退出。 -## 文档加密 ------------ +文档加密 +-------- :: @@ -555,8 +541,6 @@ n\ ``==``\ 。 录制宏 ------ --------------- - .. code:: shell @@ -577,8 +561,6 @@ n\ ``==``\ 。 帮助命令 -------- --------------- - .. code:: shell @@ -599,8 +581,8 @@ n\ ``==``\ 。 在Windows系统上 :help tutor -## 配置命令 ------------ +配置命令 +-------- 显示当前设定 ~~~~~~~~~~~~ @@ -643,8 +625,8 @@ n\ ``==``\ 。 :set list 显示非打印字符,如tab,空格,行尾等。如果tab无法显示,请确定用set lcs=tab:>-命令设置了.vimrc文件,并确保你的文件中的确有tab,如果开启了expendtab,那么tab将被扩展为空格。 -## 其他命令 ------------ +其他命令 +-------- .. code:: shell From 0e501fca9aaae96a2591e81d67d2fb1c22feda3d Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Fri, 14 Jun 2019 19:54:07 +0800 Subject: [PATCH 036/302] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E6=9B=B4=E6=96=B0pycharm=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=8A=80=E5=B7=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_06.md | 3 +++ source/c04/c04_15.md | 24 ++++++++++++++++++++++++ source/c07/c07_09.md | 9 +++++++++ 3 files changed, 36 insertions(+) create mode 100644 source/c07/c07_09.md diff --git a/source/c04/c04_06.md b/source/c04/c04_06.md index 83f564e..1f89981 100644 --- a/source/c04/c04_06.md +++ b/source/c04/c04_06.md @@ -153,6 +153,9 @@ $ git reset $ git reset --hard HEAD^ #HEAD是当前版本,HEAD^是上一个版本,HEAD^^是上上个版本,HEAD~100是前100个版本 #第二种方法 +$ git revert HEAD # 会生成一次新的提交,提交重新会有三次提交 +$ git reset HEAD # 直接将指针指向上一次提交之前,会有 0 次提交 + $ git reset --hard 04c632e244 # hard后面这一串字符是commit id(版本号),只要前面几位就ok,但是如果我们关掉git,想恢复到之前的新版本,但是不知道id了,那就要用第三种方法了 diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index 899a3db..d4a9e90 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -450,6 +450,30 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 这两个快捷键比起使用 Ctrl + 鼠标左键 跳进源代码来说,更加方便,,就像微信小程序一样,用完即焚,不会新产生一个标签页,也不需要来回跳转页面。 +## 4.15.16 快速定位到错误行 + +前几天我在看 OpenStack 框架的时候,不小心改动了几行代码,导致了语法错误,这也是事后过了许久才在导航栏这里出现的波浪线发现了。 + +![](http://image.python-online.cn/20190613154147.png) + +由于是手误,我也不知道我改动了哪一行,这个文件有将近8000行的代码,难道一行一行地去找?不太现实。 + +PyCharm 提供给我们一个 Keymap 的面板,可以很方便的设置、查询快捷键。 + +我在搜索框输入 Error,就找到了快速定位到错误位置的快捷键 `F2` + +![](http://image.python-online.cn/20190613154401.png) + +查看了下原来是这里缩进有问题 + +![](http://image.python-online.cn/20190613160905.png) + +## 4.15.17 静态代码检查 + +https://blog.csdn.net/xiemanR/article/details/73379163 + + + ## 附录 diff --git a/source/c07/c07_09.md b/source/c07/c07_09.md new file mode 100644 index 0000000..7d20eff --- /dev/null +++ b/source/c07/c07_09.md @@ -0,0 +1,9 @@ +# 7.9 Ansible 使用总结 + + + +``` +# 批量推送文件 +ansible ws_compute01 -m copy -a 'src= dest=' +``` + From 1bad33f801bc858f0c8588a858072324c9d0a6fa Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Fri, 14 Jun 2019 22:40:30 +0800 Subject: [PATCH 037/302] =?UTF-8?q?pipenv=20=E4=BD=BF=E7=94=A8=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/.DS_Store | Bin 6148 -> 10244 bytes source/c04/c04_14.md | 43 +++++++++++++++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/source/.DS_Store b/source/.DS_Store index 7d619815203a0ffbdf5560a93d27cb0f238800f0..a844301a929a70829ef33ee36c224ef4e22a7566 100644 GIT binary patch literal 10244 zcmeHMU2GIp6h5adbY@!U6iYiSh^w_yN`ZylvXno!KL`yX=oVUjbeY|m(h0LO%g$^| zsU@8@U0Kxzu(8Z)&PuV<)>Dg70s{EQ3l1TmlE@VN64Uh+?!aAr3 z5C{+m5C{+m5C{;s7Z9L5nu>ML)zq?oRqJGbfqwN`aPo`p`h10`Gq}m zzyy~;9v~1PP>BGG-RnSwG}urmiQnA;)6S$#J53hsPqm${_FG<}5W!&m8?X*g~q@;z2n*DP9GD+rPxEs>s1 zj@si%S9J?cubMx`?hdN%Xxc9i+O|1YR?~+KeLN*M_1TuI8kU~-oDEGUljB2%rP<@X zcEQpdubm`U2!bexDOs7Ej7GaU!_io5sxv$pk4HMgvFMhmDN$%?+q84wM0Vorv@M{N^Wq7C<7VWI+)j)ekfy`g{OH2JG|uYIu&bYO zS?Q=_=iNTfFeS?cM}JBuJ7Y}mCu?WepyrsFjN;Y$<)H^!+t+W7@93FXva~^#mPt)R zV}@g-P5scgp}C_+R41cbnqi$hN<6b%W5m$&Vk4%+K}*f*GxE}gCCx30GF(U-rwfKV zA-4*GSG#hRA|2PL%l)>-yfpa}LG(1?Rf=+qY5MXy<(lvCaGN3xI&5UBZ@9KYk_Jx^ z;blkUt{XN=${|xt>!z%Uyq{=HQijGjV)8z}a%;Dw3}@_YmWJ#!Vy9odbC;q#!+L&P zb=(6v-J((5@VoD&Q5|8r9!+Co5?T}O-a`hlH4)(@+W%M33X*`maGc&D=HNVBgiG)O zyb15YHTW35fa`DrzJ_n$Tlf)vhF{=U_znJmzu^`VTTsGgY(WJd#7D3l*J1}oaVu`a z?YIMX<38MvPv8L@#uJ#v3~HFgF`U4&IEfeVWqcK1!^`*%eu!7`8h(VI;%9tZRkMHN z0e_snz%QwAn}_}?RW&pp4@0|~q&;_shd+1@BgB3yC5znCsd? z9fBZNan1VBMv*8hS99HFDJBp(v!X?5PV`8U4D8<| z{wun@3s>QDqS|+G6MiC^{Z177i|AF0jUK&L;woH?YlvbUxDMCj7K~#z(X0>qiDrB7 zAU=sn9K^$z@~Nhyg*KkXJi0iA=WrUI!}E9%FX4;$5>f9Jd<);^tePX+UY}*Y!1r&H zZQea2R=OK_oAs5V&6*Q6HljJ1G;N5iSzgsu=E!#IO2;jwR^D!1X%#PSUZ8D$lnw?t zI#lTFgFsmmY*#C8qL%D!uwaB1fgDVfS+m`(*jfaGY&B~6%mT*_I~*{s<5S&igwON|-)@>qs-g6se+&rXfA;bw Pi2sR><*%FmFY*6B0k%f- delta 105 zcmZn(XfcprU|?W$DortDU=RQ@Ie-{Mvv5r;6q~50$SAxqU^g?P@Ma!?dyJbeiOVuA rX6N7#WCkh$0s(Fy;R;f{vG6 # 更新指定的包 -# 在当前虚拟环境中运行 -$ pipenv run python # 进入交互式 -$ pipenv run python 文件名 # 运行文件 -$ pipenv run pip ... # 运行pip - - # 将Pipfile和Pipfile.lock文件里面的包导出为requirements.txt文件 $ pipenv run pip freeze # 相当于pipenv run pip freeze >requirements.txt @@ -130,7 +145,15 @@ $ pipenv graph $ pipenv check ``` -`.env`文件,用来存放一些环境变量。 +打印该虚拟环境下所有包的依赖关系图 + +![](http://image.python-online.cn/20190614000336.png) + +有的python第三方包旧版本会有安全漏洞,使用 pipenv check 可以检查安全漏洞。 + +![](http://image.python-online.cn/20190612215924.png) + +.env`文件,用来存放一些环境变量。 --- From 9b57bfa17d5d384a578daa16b9904d36574e7ea6 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 15 Jun 2019 00:32:48 +0800 Subject: [PATCH 038/302] =?UTF-8?q?mac=E4=B8=8A=E5=AE=89=E8=A3=85mysql-pyt?= =?UTF-8?q?hon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_18.md | 72 ++++++++++++++++++++++++++++++++++++ source/c01/c01_18.rst | 85 +++++++++++++++++++++++++++++++++++++++++++ source/c04/c04_06.rst | 3 ++ source/c04/c04_14.rst | 55 +++++++++++++++++++++++----- source/c04/c04_15.md | 2 + source/c04/c04_15.rst | 34 +++++++++++++++++ source/c07/c07_09.rst | 7 ++++ 7 files changed, 248 insertions(+), 10 deletions(-) create mode 100644 source/c01/c01_18.md create mode 100644 source/c01/c01_18.rst create mode 100644 source/c07/c07_09.rst diff --git a/source/c01/c01_18.md b/source/c01/c01_18.md new file mode 100644 index 0000000..9eb3d0a --- /dev/null +++ b/source/c01/c01_18.md @@ -0,0 +1,72 @@ +# 1.18 Mac上安装MySQL-python + +这玩意实在是太难装了,为了以防后面再踩坑,这里还是记录一下吧。 + +以下是我成功安装后的总结,别看总结起来很顺利,其实中间经过很痛苦的挣扎。 + +遇到各种各样的错误,幸亏自己对于这些错误还有一定的辨识处理能力。 + +首先,要顺利安装 MySQL-python,请先保证你的环境是干净的(MySQL) + +因为在我准备安装 MySQL-python 之前,我电脑上使用的MySQL,是 *XAMPP*这个功能强大的建站集成软件包自带的,安装它,就会自动安装一些开发常用的软件 如 Apache,MySQL,PHP,PERL。 + +使用它的话,MySQL会装在 /Applications/XAMPP/xamppfiles/ 下,与我们正常使用brew 安装的路径不一样,所以经常会有各种使用问题,因此为了不必要的麻烦,保证环境的干净,请先卸载所有其他安装来源的 mysql。 + +除了 XAMPP 之外,如果之前使用 brew 安装过 mysql,这时也请将其卸载再重新安装吧。 + +```shell +brew remove mysql +``` + +什么?没有用过 brew?这么好用的包管理器,必须安利你用起来。 + +由于后面安装mysql也将使用 brew,所以你必须安装。方法如下 + +```shell +/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +``` + +由于默认的 brew 源是国外的,非常非常地慢,建议换成国内的源,方法如下 + +```shell +cd "$(brew --repo)" +git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git + +cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core" +git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git + +brew update +``` + +卸载掉所有的 mysql 和 替换源成功后,就可以开始mysql了,这里我还是选择安装的 5.7 版本的。 + +```shell +brew install mysql@5.7 +``` + +经过漫长的等待后,mysql 终于安装成功 + +![](http://image.python-online.cn/20190615001340.png) + +这时候,再 执行 pip install MySQL-python,发现还是报错。 + +![](http://image.python-online.cn/20190615001414.png) + +有经验的我,立马知道了 `mysql_config` 这个文件的路径可能没有在环境变量中。 + +![](http://image.python-online.cn/20190615001633.png) + +然后,我又重新执行 `pip install MySQL-python` ,发现还是报错。 + +![](http://image.python-online.cn/20190615001706.png) + +但是这个错误相对比较明显,明眼人一看就知道是权限不足。 + +那我就以 root 权限去安装好了。 + +![](http://image.python-online.cn/20190615001908.png) + +终于安装成功,折腾了两个晚上(主要是网速慢)。。 + + + diff --git a/source/c01/c01_18.rst b/source/c01/c01_18.rst new file mode 100644 index 0000000..363681a --- /dev/null +++ b/source/c01/c01_18.rst @@ -0,0 +1,85 @@ +1.18 Mac上安装MySQL-python +========================== + +这玩意实在是太难装了,为了以防后面再踩坑,这里还是记录一下吧。 + +以下是我成功安装后的总结,别看总结起来很顺利,其实中间经过很痛苦的挣扎。 + +遇到各种各样的错误,幸亏自己对于这些错误还有一定的辨识处理能力。 + +首先,要顺利安装 MySQL-python,请先保证你的环境是干净的(MySQL) + +因为在我准备安装 MySQL-python 之前,我电脑上使用的MySQL,是 +*XAMPP*\ 这个功能强大的建站集成软件包自带的,安装它,就会自动安装一些开发常用的软件 +如 Apache,MySQL,PHP,PERL。 + +使用它的话,MySQL会装在 /Applications/XAMPP/xamppfiles/ +下,与我们正常使用brew +安装的路径不一样,所以经常会有各种使用问题,因此为了不必要的麻烦,保证环境的干净,请先卸载所有其他安装来源的 +mysql。 + +除了 XAMPP 之外,如果之前使用 brew 安装过 +mysql,这时也请将其卸载再重新安装吧。 + +.. code:: shell + + brew remove mysql + +什么?没有用过 brew?这么好用的包管理器,必须安利你用起来。 + +由于后面安装mysql也将使用 brew,所以你必须安装。方法如下 + +.. code:: shell + + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + +由于默认的 brew 源是国外的,非常非常地慢,建议换成国内的源,方法如下 + +.. code:: shell + + cd "$(brew --repo)" + git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git + + cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core" + git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git + + brew update + +卸载掉所有的 mysql 和 +替换源成功后,就可以开始mysql了,这里我还是选择安装的 5.7 版本的。 + +.. code:: shell + + brew install mysql@5.7 + +经过漫长的等待后,mysql 终于安装成功 + +|image0| + +这时候,再 执行 pip install MySQL-python,发现还是报错。 + +|image1| + +有经验的我,立马知道了 ``mysql_config`` +这个文件的路径可能没有在环境变量中。 + +|image2| + +然后,我又重新执行 ``pip install MySQL-python`` ,发现还是报错。 + +|image3| + +但是这个错误相对比较明显,明眼人一看就知道是权限不足。 + +那我就以 root 权限去安装好了。 + +|image4| + +终于安装成功,折腾了两个晚上(主要是网速慢)。。 + +.. |image0| image:: http://image.python-online.cn/20190615001340.png +.. |image1| image:: http://image.python-online.cn/20190615001414.png +.. |image2| image:: http://image.python-online.cn/20190615001633.png +.. |image3| image:: http://image.python-online.cn/20190615001706.png +.. |image4| image:: http://image.python-online.cn/20190615001908.png + diff --git a/source/c04/c04_06.rst b/source/c04/c04_06.rst index ea1f78e..8dcce2f 100755 --- a/source/c04/c04_06.rst +++ b/source/c04/c04_06.rst @@ -163,6 +163,9 @@ add/commit 前撤销对文件的修改 $ git reset --hard HEAD^ #HEAD是当前版本,HEAD^是上一个版本,HEAD^^是上上个版本,HEAD~100是前100个版本 #第二种方法 + $ git revert HEAD # 会生成一次新的提交,提交重新会有三次提交 + $ git reset HEAD # 直接将指针指向上一次提交之前,会有 0 次提交 + $ git reset --hard 04c632e244 # hard后面这一串字符是commit id(版本号),只要前面几位就ok,但是如果我们关掉git,想恢复到之前的新版本,但是不知道id了,那就要用第三种方法了 diff --git a/source/c04/c04_14.rst b/source/c04/c04_14.rst index cefe3f5..92f31f6 100644 --- a/source/c04/c04_14.rst +++ b/source/c04/c04_14.rst @@ -37,11 +37,11 @@ 4.14.2 创建虚拟环境 ------------------- -mytest是我们的项目目录。如果是python3.6 应该是自带的。 +DjangoWebBlog 是我们的项目目录,进入这个目录下创建虚拟环境 .. code:: shell - $ mkdir mytest && cd mytest + $ mkdir DjangoWebBlog && cd DjangoWebBlog # 在当前目录下创建一个虚拟环境(默认的Python版本) $ pipenv install @@ -56,6 +56,16 @@ mytest是我们的项目目录。如果是python3.6 应该是自带的。 $ pipenv --python 3.7 # 也可以指定具体的版本 pipenv install --python 2 +这边以安装 python2 版本的虚拟环境为例说明。 + +|image2| + +如果你原项目使用的是 requirements.txt 这个管理包的方式,这时候执行 +``pipenv --tow`` 创建一个虚拟环境后,会找到 requirements.txt +,并根据这里面的依赖包生成 Pipfile文件。 + +|image3| + 4.14.3 查询虚拟环境 ------------------- @@ -70,6 +80,10 @@ mytest是我们的项目目录。如果是python3.6 应该是自带的。 # 返回该虚拟环境的解释器 $ pipenv --py +演示如下: + +|image4| + 4.14.4 操作虚拟环境 ------------------- @@ -79,12 +93,24 @@ mytest是我们的项目目录。如果是python3.6 应该是自带的。 $ pipenv shell # 退出这个虚拟环境 - $ exit (有的终端使用这个会退出) + $ exit $ deactivate # 移除当前目录的虚拟环境 $ pipenv --rm +执行 ``pipenv shell`` +就可以进入这个虚拟环境,在头部会有虚拟环境的标识名称。有这个标识,说明已经进入虚拟环境。 + +|image5| + +.. code:: python + + # 在当前虚拟环境中运行 + $ pipenv run python # 进入交互式,跟直接执行 python 一样 + $ pipenv run python 文件名 # 运行文件 + $ pipenv run pip ... # 运行pip + 4.14.5 虚拟环境包管理 --------------------- @@ -108,12 +134,6 @@ mytest是我们的项目目录。如果是python3.6 应该是自带的。 $ pipenv update --outdated # 打印所有要更新的包 $ pipenv update <包名> # 更新指定的包 - # 在当前虚拟环境中运行 - $ pipenv run python # 进入交互式 - $ pipenv run python 文件名 # 运行文件 - $ pipenv run pip ... # 运行pip - - # 将Pipfile和Pipfile.lock文件里面的包导出为requirements.txt文件 $ pipenv run pip freeze # 相当于pipenv run pip freeze >requirements.txt @@ -135,7 +155,16 @@ mytest是我们的项目目录。如果是python3.6 应该是自带的。 # 检查安全漏洞 $ pipenv check -``.env``\ 文件,用来存放一些环境变量。 +打印该虚拟环境下所有包的依赖关系图 + +|image6| + +有的python第三方包旧版本会有安全漏洞,使用 pipenv check +可以检查安全漏洞。 + +|image7| + +.env`文件,用来存放一些环境变量。 -------------- @@ -145,4 +174,10 @@ mytest是我们的项目目录。如果是python3.6 应该是自带的。 .. |image0| image:: http://image.python-online.cn/Fk6WZ2xbqg2DM3AvnYCpsiKQ4xOn .. |image1| image:: http://image.python-online.cn/FjuJ8yZsgjkzVuBRZHxK1ZnnzaEX +.. |image2| image:: http://image.python-online.cn/20190612211330.png +.. |image3| image:: http://image.python-online.cn/20190612213015.png +.. |image4| image:: http://image.python-online.cn/20190612213950.png +.. |image5| image:: http://image.python-online.cn/20190612211925.png +.. |image6| image:: http://image.python-online.cn/20190614000336.png +.. |image7| image:: http://image.python-online.cn/20190612215924.png diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index d4a9e90..69e4b94 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -472,7 +472,9 @@ PyCharm 提供给我们一个 Keymap 的面板,可以很方便的设置、查 https://blog.csdn.net/xiemanR/article/details/73379163 +## 4.15.18 快速查看最近的修改 +![](http://image.python-online.cn/20190614235120.png) diff --git a/source/c04/c04_15.rst b/source/c04/c04_15.rst index e8c8446..6556995 100644 --- a/source/c04/c04_15.rst +++ b/source/c04/c04_15.rst @@ -541,6 +541,36 @@ q(Mac暂时未找到对应快捷键),在当前页面就可以快速预览 这两个快捷键比起使用 Ctrl + 鼠标左键 跳进源代码来说,更加方便,,就像微信小程序一样,用完即焚,不会新产生一个标签页,也不需要来回跳转页面。 +4.15.16 快速定位到错误行 +------------------------ + +前几天我在看 OpenStack +框架的时候,不小心改动了几行代码,导致了语法错误,这也是事后过了许久才在导航栏这里出现的波浪线发现了。 + +|image51| + +由于是手误,我也不知道我改动了哪一行,这个文件有将近8000行的代码,难道一行一行地去找?不太现实。 + +PyCharm 提供给我们一个 Keymap 的面板,可以很方便的设置、查询快捷键。 + +我在搜索框输入 Error,就找到了快速定位到错误位置的快捷键 ``F2`` + +|image52| + +查看了下原来是这里缩进有问题 + +|image53| + +4.15.17 静态代码检查 +-------------------- + +https://blog.csdn.net/xiemanR/article/details/73379163 + +4.15.18 快速查看最近的修改 +-------------------------- + +|image54| + 附录 ---- @@ -607,4 +637,8 @@ q(Mac暂时未找到对应快捷键),在当前页面就可以快速预览 .. |image48| image:: http://image.python-online.cn/20190507152840.png .. |image49| image:: http://image.python-online.cn/20190507153847.png .. |image50| image:: http://image.python-online.cn/20190507154027.png +.. |image51| image:: http://image.python-online.cn/20190613154147.png +.. |image52| image:: http://image.python-online.cn/20190613154401.png +.. |image53| image:: http://image.python-online.cn/20190613160905.png +.. |image54| image:: http://image.python-online.cn/20190614235120.png diff --git a/source/c07/c07_09.rst b/source/c07/c07_09.rst new file mode 100644 index 0000000..0ae222d --- /dev/null +++ b/source/c07/c07_09.rst @@ -0,0 +1,7 @@ +7.9 Ansible 使用总结 +==================== + +:: + + # 批量推送文件 + ansible ws_compute01 -m copy -a 'src= dest=' From 01f2f3c5a25cbe20ed1fae02492218a19238731b Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 16 Jun 2019 23:54:02 +0800 Subject: [PATCH 039/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20pycharm=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=8A=80=E5=B7=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_18.md | 72 ++++++++++++++++++++- source/c01/c01_18.rst | 79 ++++++++++++++++++++++- source/c04/c04_15.md | 116 +++++++++++++++++++++++++++++++--- source/c04/c04_15.rst | 142 ++++++++++++++++++++++++++++++++++++++---- 4 files changed, 382 insertions(+), 27 deletions(-) diff --git a/source/c01/c01_18.md b/source/c01/c01_18.md index 9eb3d0a..240a279 100644 --- a/source/c01/c01_18.md +++ b/source/c01/c01_18.md @@ -1,6 +1,8 @@ -# 1.18 Mac上安装MySQL-python +# 1.18 MySQL 使用总结 -这玩意实在是太难装了,为了以防后面再踩坑,这里还是记录一下吧。 +## 1.18.1 安装MySQL-python + +MySQL-python 这玩意实在是太难装了,为了以防后面再踩坑,这里还是记录一下吧。 以下是我成功安装后的总结,别看总结起来很顺利,其实中间经过很痛苦的挣扎。 @@ -66,7 +68,71 @@ brew install mysql@5.7 ![](http://image.python-online.cn/20190615001908.png) -终于安装成功,折腾了两个晚上(主要是网速慢)。。 +终于安装成功,折腾了两个晚上(主要是网速慢)。 + +## 1.18.2 Mac 启动MySQL服务 + +使用 brew 安装 mysql 成功后,又陷入了一个坑。。 + +就是无法通过正常的 `mysqld start` 去启动,也无法使用图形界面去启动。 + +我原以为是旧数据的影响,然后把所有的旧数据清空后发现,仍然无法启动,从错误日志中没有发现可行的解决方案。 + +最后我才认定这是安装方法导致的启动方式的差异。使用brew 安装的mysql也需要使用brew启动myql + +```shell +# 启动 mysql, 并设置为开机启动 +brew services start mysql +# 关闭 mysql +brew services stop mysql +# 重启 mysql +brew services restart mysql +``` + +启动后,如何设置初始化密码呢? + +```shell +cd /usr/local/Cellar/mysql@5.7/5.7.25/bin +./mysql_secure_installation +``` + +选择密码强度,视情况而写,我这边选最强的,长度大于8,有数字,有大小写,有特殊字符。 + +![](http://image.python-online.cn/20190615112422.png) +接下来还会问你,是否删除其他匿名用户,是否删除 test 数据库,是否允许远程使用root登陆(安全起见我选不允许)。 +一切设置完成后,就可以直接使用 root 登陆数据库。 + +```shell +mysql -uroot -p +``` + + + +## 1.18.3 Win上忘记密码 + +```shell + +# 先将mysql服务停掉,可以通过命令行,也可以通过“服务”图形界面关闭 +net stop mysql; + +# 切换到 mysql 的 bin 目录下 +cd E:\Program Files\MySQL\MySQL Server 5.6\bin + +# 开启免密服务 +mysqld --defaults-file="E:\ProgramData\MySQL\MySQL Server 5.6\my.ini" --skip-grant-tables + +# 再开一个cmd窗口 +mysql -uroot -p # 直接回车 + +# 修改密码 +>use mysql; +>UPDATE user SET Password=PASSWORD('123456') where USER='root' and Host='localhost'; +>FLUSH PRIVILEGES; +>quit + +# 再重新登陆,用新的密码登陆,发现可以生效 +mysql -uroot -p +``` diff --git a/source/c01/c01_18.rst b/source/c01/c01_18.rst index 363681a..f434df2 100644 --- a/source/c01/c01_18.rst +++ b/source/c01/c01_18.rst @@ -1,6 +1,10 @@ -1.18 Mac上安装MySQL-python -========================== +1.18 MySQL 使用总结 +=================== +1.18.1 安装MySQL-python +----------------------- + +MySQL-python 这玩意实在是太难装了,为了以防后面再踩坑,这里还是记录一下吧。 以下是我成功安装后的总结,别看总结起来很顺利,其实中间经过很痛苦的挣扎。 @@ -75,11 +79,80 @@ mysql,这时也请将其卸载再重新安装吧。 |image4| -终于安装成功,折腾了两个晚上(主要是网速慢)。。 +终于安装成功,折腾了两个晚上(主要是网速慢)。 + +1.18.2 Mac 启动MySQL服务 +------------------------ + +使用 brew 安装 mysql 成功后,又陷入了一个坑。。 + +就是无法通过正常的 ``mysqld start`` 去启动,也无法使用图形界面去启动。 + +我原以为是旧数据的影响,然后把所有的旧数据清空后发现,仍然无法启动,从错误日志中没有发现可行的解决方案。 + +最后我才认定这是安装方法导致的启动方式的差异。使用brew +安装的mysql也需要使用brew启动myql + +.. code:: shell + + # 启动 mysql, 并设置为开机启动 + brew services start mysql + # 关闭 mysql + brew services stop mysql + # 重启 mysql + brew services restart mysql + +启动后,如何设置初始化密码呢? + +.. code:: shell + + cd /usr/local/Cellar/mysql@5.7/5.7.25/bin + ./mysql_secure_installation + +选择密码强度,视情况而写,我这边选最强的,长度大于8,有数字,有大小写,有特殊字符。 + +|image5| + +接下来还会问你,是否删除其他匿名用户,是否删除 test +数据库,是否允许远程使用root登陆(安全起见我选不允许)。 + +一切设置完成后,就可以直接使用 root 登陆数据库。 + +.. code:: shell + + mysql -uroot -p + +1.18.3 Win上忘记密码 +-------------------- + +.. code:: shell + + + # 先将mysql服务停掉,可以通过命令行,也可以通过“服务”图形界面关闭 + net stop mysql; + + # 切换到 mysql 的 bin 目录下 + cd E:\Program Files\MySQL\MySQL Server 5.6\bin + + # 开启免密服务 + mysqld --defaults-file="E:\ProgramData\MySQL\MySQL Server 5.6\my.ini" --skip-grant-tables + + # 再开一个cmd窗口 + mysql -uroot -p # 直接回车 + + # 修改密码 + >use mysql; + >UPDATE user SET Password=PASSWORD('123456') where USER='root' and Host='localhost'; + >FLUSH PRIVILEGES; + >quit + + # 再重新登陆,用新的密码登陆,发现可以生效 + mysql -uroot -p .. |image0| image:: http://image.python-online.cn/20190615001340.png .. |image1| image:: http://image.python-online.cn/20190615001414.png .. |image2| image:: http://image.python-online.cn/20190615001633.png .. |image3| image:: http://image.python-online.cn/20190615001706.png .. |image4| image:: http://image.python-online.cn/20190615001908.png +.. |image5| image:: http://image.python-online.cn/20190615112422.png diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index 69e4b94..dcfc6c0 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -1,4 +1,4 @@ -# 4.15 15个 PyCharm 小技巧 +# 4.15 20个 PyCharm 小技巧 刚开始做公众号的时候,更新频率正常是一周两到三篇。老读者应该有注意到,以前都是写系列教程,对于读者而言,系列教程会更加友好,学习起来会更容易深入浅出,而对于作者来说,写系列教程,更有一种使命感,而这种使命感是维持更新一大动力。 @@ -452,30 +452,128 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 ## 4.15.16 快速定位到错误行 -前几天我在看 OpenStack 框架的时候,不小心改动了几行代码,导致了语法错误,这也是事后过了许久才在导航栏这里出现的波浪线发现了。 +前几天打开 PyCharm,发现在导航栏这里出现了很多波浪线,有过 PyCharm 使用经验的同学,就会知道,这是代码中出现了语法错误。 ![](http://image.python-online.cn/20190613154147.png) -由于是手误,我也不知道我改动了哪一行,这个文件有将近8000行的代码,难道一行一行地去找?不太现实。 +顺着波浪线,我一层一层地展开目录树,终于找到了那个包含语法错误的文件。由于是手误,我也不知道我改动了哪一行,看了下这个文件,有将近8000行的代码,难道一行一行地去找? -PyCharm 提供给我们一个 Keymap 的面板,可以很方便的设置、查询快捷键。 +不,这绝对不是使用 IDE 正确的方式。 -我在搜索框输入 Error,就找到了快速定位到错误位置的快捷键 `F2` +遇到问题,就应该尝试去寻找快捷方法,有没有办法,可以一下子定位到错误代码呢? + +这时候,我想起了PyCharm 有提供给我们一个 Keymap 的面板,可以很方便的设置、查询快捷键。说不定我在那里可以找到我想要的答案 + +我在搜索框输入 Error,就找到了快速定位到错误位置的快捷键 `F2` 和 `Shift+F2` 可以快速的定位到错误行。 ![](http://image.python-online.cn/20190613154401.png) -查看了下原来是这里缩进有问题 +使用快捷键 F2 查看了下原来是这里缩进有问题。 ![](http://image.python-online.cn/20190613160905.png) -## 4.15.17 静态代码检查 +## 4.15.17 快速查看最近的修改 -https://blog.csdn.net/xiemanR/article/details/73379163 +上面为了恢复因为手误造成的语法错误,我使用了快捷键来定位错误行,虽然解决了问题,但总有种绕了几个弯的感觉。 -## 4.15.18 快速查看最近的修改 +假如有种方法,可以项目查看最近的修改记录的话(没有git做版本控制的情况下),那就太好了。 + +太巧的是,今天我打开 PyCharm ,就给我推了这条 tip,(在Mac上)使用 option+shift+C 可以快速查看最近修改的内容(windows 上应该是alt+shift+c吧) ![](http://image.python-online.cn/20190614235120.png) +## 4.15.18 静态代码分析检查 + +对于编译型的语言,如 Java,需要将代码编译成机器可识别的语言才可运行,在编译过程中,就可以通过分析或检查源程序的语法、结构、过程、接口等来检查程序的正确性,找出代码隐藏的错误和缺陷。这个过程叫做静态代码分析检查。 + +那对于 Python 这种解释型的语言来说,代码是边运行边翻译的,不需要经过编译这个过程。很多肉眼无法一下子看出的错误,通常都是跑一下(反正跑一下这么方便)才能发现。 + +由于Python 运行是如此的方便,以至于我们都不太需要关注静态分析工具。 + +但也不是说,静态分析工具完全没有用武之地,我认为还是有。 + +如果你的编码能力还没有很成熟,代码中可以有许许多多的隐藏bug,由于 Python 是运行到的时候才解释,导致一次运行只能发现一个错误,要发现100个bug,要运行100次,数字有点夸大,其实就是想说,如果这么多的错误都能通过一次静态检查发现就立马修改,开发调试的效率就可以有所提升。当然啦,并不是说所有的错误静态分析都能提前发现,这点希望你不要误解。 + +做为 Python 最强 IDE,PyCharm本身内置了这个功能,不需要你安装任何插件。 + +你只需要像下面这样点击项目文件夹,然后右键,选择 `Inspect Code`,就可以开启静态检查。 + +![](http://image.python-online.cn/20190616211359.png) + +我对开源组件 nova 的静态检查发现,其有不规范的地方有数千处。 + +![](http://image.python-online.cn/20190616214310.png) + +## 4.15.19 全方位无死角精准定位 + +一直觉得使用鼠标是一种非常低效的习惯。 + +我热衷于使用各种键盘快捷键来提高操作的精准度,在编辑器上我可以相当熟练的使用 vim 指令完成我各种需求,因此我给 PyCharm 装上了ideaVim,给 Chrome 装上了 Vimium。 + +由于我有长期阅读 OpenStack 源码的需要,而其代码量又是数百万级别的。如果没有使用精准定位来快速跳转,这种极差的体验将很难使我坚持下来。 + +这里暂时先介绍几种我最常用的精准定位方法,主要可以定位跳转到如下三种 + +- 精准定位到文件:Windows(Ctrl+Shift+N),Mac(Command+ shift +N) + +![](http://image.python-online.cn/20190616221620.png) + +- 精准定位到类:Windows(Ctrl+N),Mac(Command+N) + +![](http://image.python-online.cn/20190616232746.png) + +- 精准定位到符号:类的所有成员(函数、变量等)都可以称之为符号,Windows(Ctrl+Alt+Shift+N),Mac(Option+Shift+Command+N) + +![](http://image.python-online.cn/20190616233827.png) + +- 精准定位到文件结构:文件结构包括类、函数、变量,这说明上面定位到类和定位到符号的方法,你都可以用这个来代替。 + + Windows:Ctrl+F12,Mac:Command+F12,如果和我一样是Mac是带touchbar的,键盘上是没有F12的,那你应该先按住 Command + fn,这时 touchbar 上会出现 F12,再按F12即可。 + +![](http://image.python-online.cn/20190616235007.png) + +- 精准定位到某行:Windows(Ctrl+G),Mac(Command+G),如下图定位到第510行第9个字符处。 + +![](http://image.python-online.cn/20190616234038.png) + +## 4.15.20 再也不怕被人打断思路 + +一个程序员,如果能够一天都只和代码打交道,是一件多么难得的事情。 + +可能外行人不知道,做为同样是程序员的你,是不是和我有一样的烦恼。 + +代码写着写着,测试突然就喊道:小明,你的代码有bug,ug,g(回声)。。 + +代码写着写着,运维突然一个弹窗:小明,这个线上问题快紧急处理一下。。 + +代码写着写着,产品突然就跳出来:小明,能做一个根据手机壳颜色自动改变app主题的app不?? + +一旦编码的过程被打断,就好像在做梦的时候强行被人拉回现实,再想入梦就很困难了。 + +所以 王建硕 在<< [入静和入世](http://blog.jobbole.com/24682/) >>一文中写道: + +> “当看到一个程序员冥思苦想的时候,不要过去打扰,甚至在极端的情况下,一句友好的问候都是多余的。 ” + +为了避免这个情况,我通常在别人打断我的时候,请对方给我一分钟的时间,使用PyCharn 的 TODO 功能快速记录下当前的思绪状态,以及下一步要做的事情。 + +使用方法跟注释差不多,只要固定要以 TODO 开头。然后,你要查看全局项目中的所有 TODO 事项的时候,可以使用快捷键调出 TODO 面板。如果你是 Mac, 快捷键 是Command + 6,而 Windows 是 Alt+6。 + +![](http://image.python-online.cn/20190616231649.png) + +另外,我还使用这个来记录下个版本要优化的代码逻辑,要添加的功能。 + +如果是比较紧急的 BUG,可以使用类似 TODO 的标记 — `FIXME` 来区分紧急程度。 + +![](http://image.python-online.cn/20190616232527.png) + + + + + + + + + ## 附录 diff --git a/source/c04/c04_15.rst b/source/c04/c04_15.rst index 6556995..b342c05 100644 --- a/source/c04/c04_15.rst +++ b/source/c04/c04_15.rst @@ -1,4 +1,4 @@ -4.15 15个 PyCharm 小技巧 +4.15 20个 PyCharm 小技巧 ======================== 刚开始做公众号的时候,更新频率正常是一周两到三篇。老读者应该有注意到,以前都是写系列教程,对于读者而言,系列教程会更加友好,学习起来会更容易深入浅出,而对于作者来说,写系列教程,更有一种使命感,而这种使命感是维持更新一大动力。 @@ -544,33 +544,142 @@ q(Mac暂时未找到对应快捷键),在当前页面就可以快速预览 4.15.16 快速定位到错误行 ------------------------ -前几天我在看 OpenStack -框架的时候,不小心改动了几行代码,导致了语法错误,这也是事后过了许久才在导航栏这里出现的波浪线发现了。 +前几天打开 PyCharm,发现在导航栏这里出现了很多波浪线,有过 PyCharm +使用经验的同学,就会知道,这是代码中出现了语法错误。 |image51| -由于是手误,我也不知道我改动了哪一行,这个文件有将近8000行的代码,难道一行一行地去找?不太现实。 +顺着波浪线,我一层一层地展开目录树,终于找到了那个包含语法错误的文件。由于是手误,我也不知道我改动了哪一行,看了下这个文件,有将近8000行的代码,难道一行一行地去找? -PyCharm 提供给我们一个 Keymap 的面板,可以很方便的设置、查询快捷键。 +不,这绝对不是使用 IDE 正确的方式。 -我在搜索框输入 Error,就找到了快速定位到错误位置的快捷键 ``F2`` +遇到问题,就应该尝试去寻找快捷方法,有没有办法,可以一下子定位到错误代码呢? + +这时候,我想起了PyCharm 有提供给我们一个 Keymap +的面板,可以很方便的设置、查询快捷键。说不定我在那里可以找到我想要的答案 + +我在搜索框输入 Error,就找到了快速定位到错误位置的快捷键 ``F2`` 和 +``Shift+F2`` 可以快速的定位到错误行。 |image52| -查看了下原来是这里缩进有问题 +使用快捷键 F2 查看了下原来是这里缩进有问题。 |image53| -4.15.17 静态代码检查 --------------------- +4.15.17 快速查看最近的修改 +-------------------------- -https://blog.csdn.net/xiemanR/article/details/73379163 +上面为了恢复因为手误造成的语法错误,我使用了快捷键来定位错误行,虽然解决了问题,但总有种绕了几个弯的感觉。 -4.15.18 快速查看最近的修改 --------------------------- +假如有种方法,可以项目查看最近的修改记录的话(没有git做版本控制的情况下),那就太好了。 + +太巧的是,今天我打开 PyCharm ,就给我推了这条 tip,(在Mac上)使用 +option+shift+C 可以快速查看最近修改的内容(windows +上应该是alt+shift+c吧) |image54| +4.15.18 静态代码分析检查 +------------------------ + +对于编译型的语言,如 +Java,需要将代码编译成机器可识别的语言才可运行,在编译过程中,就可以通过分析或检查源程序的语法、结构、过程、接口等来检查程序的正确性,找出代码隐藏的错误和缺陷。这个过程叫做静态代码分析检查。 + +那对于 Python +这种解释型的语言来说,代码是边运行边翻译的,不需要经过编译这个过程。很多肉眼无法一下子看出的错误,通常都是跑一下(反正跑一下这么方便)才能发现。 + +由于Python 运行是如此的方便,以至于我们都不太需要关注静态分析工具。 + +但也不是说,静态分析工具完全没有用武之地,我认为还是有。 + +如果你的编码能力还没有很成熟,代码中可以有许许多多的隐藏bug,由于 Python +是运行到的时候才解释,导致一次运行只能发现一个错误,要发现100个bug,要运行100次,数字有点夸大,其实就是想说,如果这么多的错误都能通过一次静态检查发现就立马修改,开发调试的效率就可以有所提升。当然啦,并不是说所有的错误静态分析都能提前发现,这点希望你不要误解。 + +做为 Python 最强 IDE,PyCharm本身内置了这个功能,不需要你安装任何插件。 + +你只需要像下面这样点击项目文件夹,然后右键,选择 +``Inspect Code``\ ,就可以开启静态检查。 + +|image55| + +我对开源组件 nova 的静态检查发现,其有不规范的地方有数千处。 + +|image56| + +4.15.19 全方位无死角精准定位 +---------------------------- + +一直觉得使用鼠标是一种非常低效的习惯。 + +我热衷于使用各种键盘快捷键来提高操作的精准度,在编辑器上我可以相当熟练的使用 +vim 指令完成我各种需求,因此我给 PyCharm 装上了ideaVim,给 Chrome 装上了 +Vimium。 + +由于我有长期阅读 OpenStack +源码的需要,而其代码量又是数百万级别的。如果没有使用精准定位来快速跳转,这种极差的体验将很难使我坚持下来。 + +这里暂时先介绍几种我最常用的精准定位方法,主要可以定位跳转到如下三种 + +- 精准定位到文件:Windows(Ctrl+Shift+N),Mac(Command+ shift +N) + +|image57| + +- 精准定位到类:Windows(Ctrl+N),Mac(Command+N) + +|image58| + +- 精准定位到符号:类的所有成员(函数、变量等)都可以称之为符号,Windows(Ctrl+Alt+Shift+N),Mac(Option+Shift+Command+N) + +|image59| + +- 精准定位到文件结构:文件结构包括类、函数、变量,这说明上面定位到类和定位到符号的方法,你都可以用这个来代替。 + + Windows:Ctrl+F12,Mac:Command+F12,如果和我一样是Mac是带touchbar的,键盘上是没有F12的,那你应该先按住 + Command + fn,这时 touchbar 上会出现 F12,再按F12即可。 + +|image60| + +- 精准定位到某行:Windows(Ctrl+G),Mac(Command+G),如下图定位到第510行第9个字符处。 + +|image61| + +4.15.20 再也不怕被人打断思路 +---------------------------- + +一个程序员,如果能够一天都只和代码打交道,是一件多么难得的事情。 + +可能外行人不知道,做为同样是程序员的你,是不是和我有一样的烦恼。 + +代码写着写着,测试突然就喊道:小明,你的代码有bug,ug,g(回声)。。 + +代码写着写着,运维突然一个弹窗:小明,这个线上问题快紧急处理一下。。 + +代码写着写着,产品突然就跳出来:小明,能做一个根据手机壳颜色自动改变app主题的app不?? + +一旦编码的过程被打断,就好像在做梦的时候强行被人拉回现实,再想入梦就很困难了。 + +所以 王建硕 在<< `入静和入世 `__ +>>一文中写道: + + “当看到一个程序员冥思苦想的时候,不要过去打扰,甚至在极端的情况下,一句友好的问候都是多余的。” + +为了避免这个情况,我通常在别人打断我的时候,请对方给我一分钟的时间,使用PyCharn +的 TODO 功能快速记录下当前的思绪状态,以及下一步要做的事情。 + +使用方法跟注释差不多,只要固定要以 TODO +开头。然后,你要查看全局项目中的所有 TODO 事项的时候,可以使用快捷键调出 +TODO 面板。如果你是 Mac, 快捷键 是Command + 6,而 Windows 是 Alt+6。 + +|image62| + +另外,我还使用这个来记录下个版本要优化的代码逻辑,要添加的功能。 + +如果是比较紧急的 BUG,可以使用类似 TODO 的标记 — ``FIXME`` +来区分紧急程度。 + +|image63| + 附录 ---- @@ -641,4 +750,13 @@ https://blog.csdn.net/xiemanR/article/details/73379163 .. |image52| image:: http://image.python-online.cn/20190613154401.png .. |image53| image:: http://image.python-online.cn/20190613160905.png .. |image54| image:: http://image.python-online.cn/20190614235120.png +.. |image55| image:: http://image.python-online.cn/20190616211359.png +.. |image56| image:: http://image.python-online.cn/20190616214310.png +.. |image57| image:: http://image.python-online.cn/20190616221620.png +.. |image58| image:: http://image.python-online.cn/20190616232746.png +.. |image59| image:: http://image.python-online.cn/20190616233827.png +.. |image60| image:: http://image.python-online.cn/20190616235007.png +.. |image61| image:: http://image.python-online.cn/20190616234038.png +.. |image62| image:: http://image.python-online.cn/20190616231649.png +.. |image63| image:: http://image.python-online.cn/20190616232527.png From 7861be166aba6e9a3c04de5ebc152b9ac211a169 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 17 Jun 2019 00:36:43 +0800 Subject: [PATCH 040/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_15.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index dcfc6c0..2985db4 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -510,7 +510,7 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 我热衷于使用各种键盘快捷键来提高操作的精准度,在编辑器上我可以相当熟练的使用 vim 指令完成我各种需求,因此我给 PyCharm 装上了ideaVim,给 Chrome 装上了 Vimium。 -由于我有长期阅读 OpenStack 源码的需要,而其代码量又是数百万级别的。如果没有使用精准定位来快速跳转,这种极差的体验将很难使我坚持下来。 +同样地阅读框架代码,我也都是使用全键盘进行操作。因为我有长期阅读 OpenStack 源码的需求,其代码量是数百万级别的。如果没有使用精准定位来快速跳转,这种极差的体验将很难使我坚持下来。 这里暂时先介绍几种我最常用的精准定位方法,主要可以定位跳转到如下三种 @@ -536,7 +536,7 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 ![](http://image.python-online.cn/20190616234038.png) -## 4.15.20 再也不怕被人打断思路 +## 4.15.20 利用 TODO 解救“中年痴呆” 一个程序员,如果能够一天都只和代码打交道,是一件多么难得的事情。 @@ -548,7 +548,7 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 代码写着写着,产品突然就跳出来:小明,能做一个根据手机壳颜色自动改变app主题的app不?? -一旦编码的过程被打断,就好像在做梦的时候强行被人拉回现实,再想入梦就很困难了。 +这样的噩梦每天都在重复不间断地上演着,或许我知道了为什么程序员要在深夜里码代码了,因为那是白日里得不到的宁静。 所以 王建硕 在<< [入静和入世](http://blog.jobbole.com/24682/) >>一文中写道: From 82a43c06534db22514dbad32cfd886e85815423b Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 17 Jun 2019 20:40:39 +0800 Subject: [PATCH 041/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_15.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index 2985db4..50bceec 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -452,11 +452,11 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 ## 4.15.16 快速定位到错误行 -前几天打开 PyCharm,发现在导航栏这里出现了很多波浪线,有过 PyCharm 使用经验的同学,就会知道,这是代码中出现了语法错误。 +前几天打开 PyCharm,发现在导航栏这里出现了很多波浪线,有过 PyCharm 使用经验的同学,就会知道,这是代码中出现了错误。 ![](http://image.python-online.cn/20190613154147.png) -顺着波浪线,我一层一层地展开目录树,终于找到了那个包含语法错误的文件。由于是手误,我也不知道我改动了哪一行,看了下这个文件,有将近8000行的代码,难道一行一行地去找? +顺着波浪线,我一层一层地展开目录树,终于找到了那个包含错误的文件。由于是手误,我也不知道我改动了哪一行,看了下这个文件,有将近8000行的代码,难道一行一行地去找? 不,这绝对不是使用 IDE 正确的方式。 @@ -544,7 +544,7 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 代码写着写着,测试突然就喊道:小明,你的代码有bug,ug,g(回声)。。 -代码写着写着,运维突然一个弹窗:小明,这个线上问题快紧急处理一下。。 +代码写着写着,运维突然一个弹窗:小明,这个线上问题赶紧排查一下。。 代码写着写着,产品突然就跳出来:小明,能做一个根据手机壳颜色自动改变app主题的app不?? From 74d6d63b17a3aa9b965b51c75539e0ebbdec0d59 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 25 Jun 2019 12:35:00 +0800 Subject: [PATCH 042/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E3=80=8Crpc=20?= =?UTF-8?q?=E8=BF=9C=E7=A8=8B=E8=B0=83=E7=94=A8=E3=80=8D=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_10.md | 45 +-- source/c01/c01_10.rst | 51 ++-- source/c04/c04_15.rst | 17 +- source/c08/c08_06.md | 10 + source/c08/c08_06.rst | 13 + source/c08/c08_09.md | 474 +++++++++++++++++++++++++++---- source/c08/c08_09.rst | 633 ++++++++++++++++++++++++++++++++++++------ 7 files changed, 1069 insertions(+), 174 deletions(-) diff --git a/source/c01/c01_10.md b/source/c01/c01_10.md index 376d977..bc0811d 100644 --- a/source/c01/c01_10.md +++ b/source/c01/c01_10.md @@ -1,4 +1,4 @@ -# 1.10 Python 冷知识 32 讲 +# 1.10 Python 冷知识 40 讲 --- @@ -989,8 +989,6 @@ SyntaxError: EOL while scanning string literal SyntaxError: EOL while scanning string literal ``` - - ## 32. 字符串分割 ```python @@ -1008,23 +1006,11 @@ b ['a', 'b'] ``` -## 33. 中文变量 -使用 python3.7 -```python -__builtins__.我们来运行下这行代码看看=“666” - -#!/usr/bin/env python -# encoding: utf-8 -def _666(_666): - print(_666) -我们来运行下这行代码看看 -_666(f’是不是非常的_666’) -``` -## 34. 更新字典 +## 33. 更新字典 通常我们更新字典的方式是这样的 @@ -1047,7 +1033,7 @@ _666(f’是不是非常的_666’) -## 35. 嵌套上下文管理的另类写法 +## 34. 嵌套上下文管理的另类写法 当我们要写一个嵌套的上下文管理器时,可能会这样写 @@ -1084,7 +1070,32 @@ with test_context('aaa'), test_context('bbb'): print('========== in main ============') ``` +## 35. += 不等同于=+ +对列表 进行`+=` 操作相当于 extend,而使用 `=+` 操作是新增了一个列表。 + +因此会有如下两者的差异。 + +```python +# =+ +>>> a = [1, 2, 3, 4] +>>> b = a +>>> a = a + [5, 6, 7, 8] +>>> a +[1, 2, 3, 4, 5, 6, 7, 8] +>>> b +[1, 2, 3, 4] + + +# += +>>> a = [1, 2, 3, 4] +>>> b = a +>>> a += [5, 6, 7, 8] +>>> a +[1, 2, 3, 4, 5, 6, 7, 8] +>>> b +[1, 2, 3, 4, 5, 6, 7, 8] +``` diff --git a/source/c01/c01_10.rst b/source/c01/c01_10.rst index d9133a2..141a60e 100755 --- a/source/c01/c01_10.rst +++ b/source/c01/c01_10.rst @@ -1,4 +1,4 @@ -1.10 Python 冷知识 32 讲 +1.10 Python 冷知识 40 讲 ======================== -------------- @@ -1115,23 +1115,7 @@ import 是 Python 导包的方式。 >>> str.splitlines() ['a', 'b'] -33. 中文变量 ------------- - -使用 python3.7 - -.. code:: python - - __builtins__.我们来运行下这行代码看看=“666” - - #!/usr/bin/env python - # encoding: utf-8 - def _666(_666): - print(_666) - 我们来运行下这行代码看看 - _666(f’是不是非常的_666’) - -34. 更新字典 +33. 更新字典 ------------ 通常我们更新字典的方式是这样的 @@ -1153,7 +1137,7 @@ import 是 Python 导包的方式。 >>> dict3 {'namne': 'jane', 'gender': 'male', 'age': 24} -35. 嵌套上下文管理的另类写法 +34. 嵌套上下文管理的另类写法 ---------------------------- 当我们要写一个嵌套的上下文管理器时,可能会这样写 @@ -1191,6 +1175,35 @@ import 是 Python 导包的方式。 with test_context('aaa'), test_context('bbb'): print('========== in main ============') +35. += 不等同于=+ +----------------- + +对列表 进行\ ``+=`` 操作相当于 extend,而使用 ``=+`` +操作是新增了一个列表。 + +因此会有如下两者的差异。 + +.. code:: python + + # =+ + >>> a = [1, 2, 3, 4] + >>> b = a + >>> a = a + [5, 6, 7, 8] + >>> a + [1, 2, 3, 4, 5, 6, 7, 8] + >>> b + [1, 2, 3, 4] + + + # += + >>> a = [1, 2, 3, 4] + >>> b = a + >>> a += [5, 6, 7, 8] + >>> a + [1, 2, 3, 4, 5, 6, 7, 8] + >>> b + [1, 2, 3, 4, 5, 6, 7, 8] + 附录:参考文章 -------------- diff --git a/source/c04/c04_15.rst b/source/c04/c04_15.rst index b342c05..76fbbb2 100644 --- a/source/c04/c04_15.rst +++ b/source/c04/c04_15.rst @@ -545,11 +545,11 @@ q(Mac暂时未找到对应快捷键),在当前页面就可以快速预览 ------------------------ 前几天打开 PyCharm,发现在导航栏这里出现了很多波浪线,有过 PyCharm -使用经验的同学,就会知道,这是代码中出现了语法错误。 +使用经验的同学,就会知道,这是代码中出现了错误。 |image51| -顺着波浪线,我一层一层地展开目录树,终于找到了那个包含语法错误的文件。由于是手误,我也不知道我改动了哪一行,看了下这个文件,有将近8000行的代码,难道一行一行地去找? +顺着波浪线,我一层一层地展开目录树,终于找到了那个包含错误的文件。由于是手误,我也不知道我改动了哪一行,看了下这个文件,有将近8000行的代码,难道一行一行地去找? 不,这绝对不是使用 IDE 正确的方式。 @@ -616,8 +616,9 @@ Java,需要将代码编译成机器可识别的语言才可运行,在编译 vim 指令完成我各种需求,因此我给 PyCharm 装上了ideaVim,给 Chrome 装上了 Vimium。 -由于我有长期阅读 OpenStack -源码的需要,而其代码量又是数百万级别的。如果没有使用精准定位来快速跳转,这种极差的体验将很难使我坚持下来。 +同样地阅读框架代码,我也都是使用全键盘进行操作。因为我有长期阅读 +OpenStack +源码的需求,其代码量是数百万级别的。如果没有使用精准定位来快速跳转,这种极差的体验将很难使我坚持下来。 这里暂时先介绍几种我最常用的精准定位方法,主要可以定位跳转到如下三种 @@ -644,8 +645,8 @@ Vimium。 |image61| -4.15.20 再也不怕被人打断思路 ----------------------------- +4.15.20 利用 TODO 解救“中年痴呆” +-------------------------------- 一个程序员,如果能够一天都只和代码打交道,是一件多么难得的事情。 @@ -653,11 +654,11 @@ Vimium。 代码写着写着,测试突然就喊道:小明,你的代码有bug,ug,g(回声)。。 -代码写着写着,运维突然一个弹窗:小明,这个线上问题快紧急处理一下。。 +代码写着写着,运维突然一个弹窗:小明,这个线上问题赶紧排查一下。。 代码写着写着,产品突然就跳出来:小明,能做一个根据手机壳颜色自动改变app主题的app不?? -一旦编码的过程被打断,就好像在做梦的时候强行被人拉回现实,再想入梦就很困难了。 +这样的噩梦每天都在重复不间断地上演着,或许我知道了为什么程序员要在深夜里码代码了,因为那是白日里得不到的宁静。 所以 王建硕 在<< `入静和入世 `__ >>一文中写道: diff --git a/source/c08/c08_06.md b/source/c08/c08_06.md index f5473f5..6a861da 100644 --- a/source/c08/c08_06.md +++ b/source/c08/c08_06.md @@ -332,6 +332,16 @@ myjob.cfg:text/upstart-job 执行完后就会生成一段内容,再将这段内容进行 base64 编码处理,就是userdata 的参数。 +## 8.6.6 网卡名被重命名 + +当你在虚拟机上卸载网卡后,在没有指定原来网卡的mac地址的时候下,创建新的port再挂给虚拟机使用,如果虚拟机不经过重启的话,是不会有任何问题的。 + +但是,如果虚拟机重启了,你可能会发现没有了原来的eth0或者eth1,而多了一个 cirename0 的网卡。 + +这就是cloudinit搞的鬼,在cloudinit的local阶段,好像会记录之前的mac地址,如果发现不一致,就会触发rename_interface。 + +![](http://image.python-online.cn/20190623091911.png) + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_06.rst b/source/c08/c08_06.rst index dfcdbde..76014ee 100644 --- a/source/c08/c08_06.rst +++ b/source/c08/c08_06.rst @@ -386,6 +386,18 @@ content-types:text/upstart-job 执行完后就会生成一段内容,再将这段内容进行 base64 编码处理,就是userdata 的参数。 +8.6.6 网卡名被重命名 +-------------------- + +当你在虚拟机上卸载网卡后,在没有指定原来网卡的mac地址的时候下,创建新的port再挂给虚拟机使用,如果虚拟机不经过重启的话,是不会有任何问题的。 + +但是,如果虚拟机重启了,你可能会发现没有了原来的eth0或者eth1,而多了一个 +cirename0 的网卡。 + +这就是cloudinit搞的鬼,在cloudinit的local阶段,好像会记录之前的mac地址,如果发现不一致,就会触发rename_interface。 + +|image22| + -------------- .. figure:: http://image.python-online.cn/20190511161447.png @@ -414,4 +426,5 @@ content-types:text/upstart-job .. |image19| image:: http://image.python-online.cn/20190430232309.png .. |image20| image:: http://image.python-online.cn/20190429205735.png .. |image21| image:: http://image.python-online.cn/20190430232911.png +.. |image22| image:: http://image.python-online.cn/20190623091911.png diff --git a/source/c08/c08_09.md b/source/c08/c08_09.md index 09d2e71..e4eba61 100644 --- a/source/c08/c08_09.md +++ b/source/c08/c08_09.md @@ -1,41 +1,425 @@ -# 8.9 OpenStack中的rpc通信机制 +# 8.9 RPC -OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul API方式,另一种则是RPC调用。两种通信方式的应用场景有所不同,在OpenStack中,前者主要用于各组件之间的通信(如nova与glance的通信),而后者则用于同一组件中各个不同模块之间的通信(如nova组件中nova-compute与nova-scheduler的通信)。 -关于OpenStack中基于RESTFul API的通信方式主要是应用了WSGI,我会在以后的博文中详细讨论。这里主要还是分享一下关于RPC调用的一些理解。 +什么是RPC呢? -首先,什么是RPC呢?百度百科给出的解释是这样的:“RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议”。这个概念听起来还是比较抽象,下面我会结合OpenStack中RPC调用的具体应用来具体分析一下这个协议。 +百度百科给出的解释是这样的:“RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议”。这个概念听起来还是比较抽象,没关系,继续往后看,后面概念性的东西,我会讲得足够清楚,让你完全掌握 RPC 的基础内容。在后面的篇章中还会结合其在 OpenStack 中实际应用,一步一步揭开 rpc 的神秘面纱。 -## 8.9.1 为什么要使用 RPC? +有的读者,可能会问,为啥我举的例子老是 OpenStack 里的东西呢? -其次,为什么要采用RPC呢?单纯的依靠RESTFul API不可以吗?其实原因有下面这几个: +因为每个人的业务中接触的框架都不一样(我主要接触的就是 OpenStack 框架),我无法为每个人去定制写一篇文章,但其技术原理都是一样的。即使如此,我也会尽力将文章写得通用,不会因为你没接触过 OpenStack 而成为你理解 rpc 的瓶颈。 -1. 由于RESTFul API是基于HTTP协议的,因此客户端与服务端之间所能传输的消息仅限于文本 -2. RESTFul API客户端与服务端之间采用的是同步机制,当发送HTTP请求时,客户端需要等待服务端的响应。当然对于这一点是可以通过一些技术来实现异步的机制的 -3. 采用RESTFul API,客户端与服务端之间虽然可以独立开发,但还是存在耦合。比如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常工作 +## 8.9.1 既 REST,何 RPC ? -基于上面这几个原因,所以OpenStack才采用了另一种远程通信机制,这就是我们今天要讨论的鼎鼎大名的RPC。 +在 OpenStack 里的进程间通信方式主要有两种,一种是基于HTTP协议的RESTFul API方式,另一种则是RPC调用。 -今天的话题,就是源码解读OpenStack是如何通过rpc进行远程调用的。 +那么这两种方式在应用场景上有何区别呢? -谈到 OpenStack 的 rpc,就不得不提到 oslo_messaging 这个 OpenStack 专用工具库,oslo_messaging只是对多种transport做了进一步封装,底层也是用到了kombu这个AMQP库,当transport是amqp时,kombu会进行环境监测判断是否安装了librabbitmq,如果安装了就使用librabbitmq,没有则使用pyamqp;因为librabbitmq是使用C编写的库,所以比pyamqp速度快很多。 +有使用经验的人,就会知道: + +- 前者(RESTful)主要用于**各组件之间**的通信(如nova与glance的通信),或者说用于组件对外提供调用接口 +- 而后者(RPC)则用于**同一组件中各个不同模块之间**的通信(如nova组件中nova-compute与nova-scheduler的通信)。 + +关于OpenStack中基于RESTful API的通信方式主要是应用了WSGI,这个知识点,我在前一篇文章中,有深入地讲解过,你可以点击查看。 + +对于不熟悉 OpenStack 的人,也别担心听不懂,这样吧,我给你提两个问题: + +1. RPC 和 REST 区别是什么? +2. 为什么要采用RPC呢? + +**第一个问题:RPC 和 REST 区别是什么?** + +你一定会觉得这个问题很奇怪,是的,包括我,但是你在网络上一搜,会发现类似对比的文章比比皆是,我在想可能很多初学者由于基础不牢固,才会将不相干的二者拿出来对比吧。既然是这样,那为了让你更加了解陌生的RPC,就从你熟悉得不能再熟悉的 REST 入手吧。 + +**01、所属类别不同** + +REST,是Representational State Transfer 的简写,中文描述表述性状态传递(是指某个瞬间状态的资源数据的快照,包括资源数据的内容、表述格式(XML、JSON)等信息。) + +REST 是一种软件架构风格。 这种风格的典型应用,就是HTTP。其因为简单、扩展性强的特点而广受开发者的青睐。 + +而RPC 呢,是 Remote Procedure Call Protocol 的简写,中文描述是远程过程调用,它可以实现客户端像调用本地服务(方法)一样调用服务器的服务(方法)。 + +RPC 是一种基于 TCP 的通信协议,按理说它和REST不是一个层面上的东西,不应该放在一起讨论,但是谁让REST这么流行呢,它是目前最流行的一套互联网应用程序的API设计标准,某种意义下,我们说 REST 可以其实就是指代 HTTP 协议。 + +**02、使用方式不同** + +**从使用上来看**,HTTP 接口只关注服务提供方,对于客户端怎么调用并不关心。接口只要保证有客户端调用时,返回对应的数据就行了。而RPC则要求客户端接口保持和服务端的一致。 + +- REST 是服务端把方法写好,客户端并不知道具体方法。客户端只想获取资源,所以发起HTTP请求,而服务端接收到请求后根据URI经过一系列的路由才定位到方法上面去 +- PRC是服务端提供好方法给客户端调用,客户端需要知道服务端的具体类,具体方法,然后像调用本地方法一样直接调用它。 + +**03、面向对象不同** + +从设计上来看,RPC,所谓的远程过程调用 ,是面向方法的 ,REST:所谓的 Representational state transfer ,是面向资源的,除此之外,还有一种叫做 SOA,所谓的面向服务的架构,它是面向消息的,这个接触不多,就不多说了。 + +**04、序列化协议不同** + +接口调用通常包含两个部分,序列化和通信协议。 + +通信协议,上面已经提及了,REST 是 基于 HTTP 协议,而 RPC 可以基于 TCP/UDP,也可以基于 HTTP 协议进行传输的。 + +常见的序列化协议,有:json、xml、hession、protobuf、thrift、text、bytes等,REST 通常使用的是 JSON或者XML,而 RPC 使用的是 JSON-RPC,或者 XML-RPC。 + +通过以上几点,我们知道了 REST 和 RPC 之间有很明显的差异。 + +**第二个问题:为什么要采用RPC呢?** + +那到底为何要使用 RPC,单纯的依靠RESTful API不可以吗?为什么要搞这么多复杂的协议,渣渣表示真的学不过来了。 + +关于这一点,以下几点仅是我的个人猜想,仅供交流哈: + +1. RPC 和 REST 两者的定位不同,REST 面向资源,更注重接口的规范,因为要保证通用性更强,所以对外最好通过 REST。而 RPC 面向方法,主要用于函数方法的调用,可以适合更复杂通信需求的场景。 +2. RESTful API客户端与服务端之间采用的是同步机制,当发送HTTP请求时,客户端需要等待服务端的响应。当然对于这一点是可以通过一些技术来实现异步的机制的。 +3. 采用RESTful API,客户端与服务端之间虽然可以独立开发,但还是存在耦合。比如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常工作。而 rpc + ralbbimq中间件可以实现低耦合的分布式集群架构。 + +说了这么多,我们该如何选择这两者呢?我总结了如下两点,供你参考: + +- REST 接口更加规范,通用适配性要求高,建议对外的接口都统一成 REST(也有例外,比如我接触过 zabbix,其 API 就是基于 JSON-RPC 2.0协议的)。而组件内部的各个模块,可以选择 RPC,一个是不用耗费太多精力去开发和维护多套的HTTP接口,一个RPC的调用性能更高(见下条) + +- 从性能角度看,由于HTTP本身提供了丰富的状态功能与扩展功能,但也正由于HTTP提供的功能过多,导致在网络传输时,需要携带的信息更多,从性能角度上讲,较为低效。而RPC服务网络传输上仅传输与业务内容相关的数据,传输数据更小,性能更高。 + +## 8.9.2 实现远程调用的三种方式 + +“远程调用”意思就是:被调用方法的具体实现不在程序运行本地,而是在别的某个地方(分布到各个服务器),调用者只想要函数运算的结果,却不需要实现函数的具体细节。 + +**01、基于 xml-rpc** + +Python实现 rpc,可以使用标准库里的 SimpleXMLRPCServer,它是基于XML-RPC 协议的。 + +有了这个模块,开启一个 rpc server,就变得相当简单了。执行以下代码: + +```python +import SimpleXMLRPCServer + +class calculate: + def add(self, x, y): + return x + y + + def multiply(self, x, y): + return x * y + + def subtract(self, x, y): + return abs(x-y) + + def divide(self, x, y): + return x/y + + +obj = calculate() +server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 8088)) +# 将实例注册给rpc server +server.register_instance(obj) + +print "Listening on port 8088" +server.serve_forever() +``` + +有了 rpc server,接下来就是 rpc client,由于我们上面使用的是 XML-RPC,所以 rpc clinet 需要使用xmlrpclib 这个库。 + +```python +import xmlrpclib + +server = xmlrpclib.ServerProxy("http://localhost:8088") +``` + +然后,我们通过 server_proxy 对象就可以远程调用之前的rpc server的函数了。 + +```python +>> server.add(2, 3) +5 +>>> server.multiply(2, 3) +6 +>>> server.subtract(2, 3) +1 +>>> server.divide(2, 3) +0 +``` + +SimpleXMLRPCServer是一个单线程的服务器。这意味着,如果几个客户端同时发出多个请求,其它的请求就必须等待第一个请求完成以后才能继续。 + +若非要使用 SimpleXMLRPCServer 实现多线程并发,其实也不难。只要将代码改成如下即可。 + +```python +from SimpleXMLRPCServer import SimpleXMLRPCServer +from SocketServer import ThreadingMixIn +class ThreadXMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer):pass + +class MyObject: + def hello(self): + return "hello xmlprc" + +obj = MyObject() +server = ThreadXMLRPCServer(("localhost", 8088), allow_none=True) +server.register_instance(obj) + +print "Listening on port 8088" +server.serve_forever() +``` + +**02、基于json-rpc** + +SimpleXMLRPCServer 是基于 xml-rpc 实现的远程调用,上面我们也提到 除了 xml-rpc 之外,还有 json-rpc 协议。 + +那 python 如何实现基于 json-rpc 协议呢? + +答案是很多,很多web框架其自身都自己实现了json-rpc,但我们要独立这些框架之外,要寻求一种较为干净的解决方案,我查找到的选择有两种 + +第一种是 `jsonrpclib` + +```shell +pip install jsonrpclib -i https://pypi.douban.com/simple +``` + +第二种是 ` python-jsonrpc` + +```shell +pip install python-jsonrpc -i https://pypi.douban.com/simple +``` + +先来看第一种 [jsonrpclib](https://github.com/joshmarshall/jsonrpclib/) + +它与 Python 标准库的 SimpleXMLRPCServer 很类似(因为它的类名就叫做 SimpleJSONRPCServer ,不明真相的人真以为它们是亲兄弟)。或许可以说,jsonrpclib 就是仿照 SimpleXMLRPCServer 标准库来进行编写的。 + +它的导入与 SimpleXMLRPCServer 略有不同,因为SimpleJSONRPCServer分布在jsonrpclib库中。 + +服务端 + +```python +from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer + +server = SimpleJSONRPCServer(('localhost', 8080)) +server.register_function(lambda x,y: x+y, 'add') +server.serve_forever() +``` + +客户端 + +```python +import jsonrpclib + +server = jsonrpclib.Server("http://localhost:8080") +``` + +![](http://image.python-online.cn/20190623185008.png) + +再来看第二种python-jsonrpc,写起来貌似有些复杂。 + +服务端 + +```python +import pyjsonrpc + + +class RequestHandler(pyjsonrpc.HttpRequestHandler): + + @pyjsonrpc.rpcmethod + def add(self, a, b): + """Test method""" + return a + b + +http_server = pyjsonrpc.ThreadingHttpServer( + server_address=('localhost', 8080), + RequestHandlerClass=RequestHandler +) +print "Starting HTTP server ..." +print "URL: http://localhost:8080" +http_server.serve_forever() +``` + +客户端 + +```python +import pyjsonrpc + +http_client = pyjsonrpc.HttpClient( + url="http://localhost:8080/jsonrpc" +) +``` + +![](http://image.python-online.cn/20190623165341.png) + +还记得上面我提到过的 zabbix API,因为我有接触过,所以也拎出来讲讲。zabbix API 也是基于 json-rpc 2.0协议实现的。 + +因为内容较多,这里只带大家打个,zabbix 是如何调用的:直接指明要调用 zabbix server 的哪个方法,要传给这个方法的参数有哪些。 + +![](http://image.python-online.cn/20190623171138.png) + +**03、基于 zerorpc** + +以上介绍的两种rpc远程调用方式,如果你足够细心,可以发现他们都是http+rpc 两种协议结合实现的。 + +接下来,我们要介绍的这种([zerorpc](https://github.com/0rpc/zerorpc-python)),就不再使用走 http 了。 + + [zerorpc](https://github.com/0rpc/zerorpc-python) 这个第三方库,它是基于TCP协议、 ZeroMQ 和 MessagePack的,速度相对快,响应时间短,并发高。zerorpc 和 pyjsonrpc 一样,需要额外安装,虽然SimpleXMLRPCServer不需要额外安装,但是SimpleXMLRPCServer性能相对差一些。 + +```shell +pip install zerorpc -i https://pypi.douban.com/simple +``` + +服务端代码 + +```python +import zerorpc + +class caculate(object): + def hello(self, name): + return 'hello, {}'.format(name) + + def add(self, x, y): + return x + y + + def multiply(self, x, y): + return x * y + + def subtract(self, x, y): + return abs(x-y) + + def divide(self, x, y): + return x/y + +s = zerorpc.Server(caculate()) + +s.bind("tcp://0.0.0.0:4242") +s.run() +``` + +客户端 + +```python +import zerorpc + +c = zerorpc.Client() +c.connect("tcp://127.0.0.1:4242") +``` + +![](http://image.python-online.cn/20190623155955.png) + +客户端除了可以使用zerorpc框架实现代码调用之外,它还支持使用“命令行”的方式调用。 + +![](http://image.python-online.cn/20190623162725.png) + +客户端可以使用命令行,那服务端是不是也可以呢? + +是的,通过 Github 上的文档几个 demo 可以体验到这个第三方库做真的是优秀。 + +比如我们可以用下面这个命令,创建一个rpc server,后面这个 `time` Python 标准库中的 time 模块,zerorpc 会将 time 注册绑定以供client调用。 + +```shell +zerorpc --server --bind tcp://127.0.0.1:1234 time +``` + +在客户端,就可以用这条命令来远程调用这个 time 函数。 + +```shell +zerorpc --client --connect tcp://127.0.0.1:1234 strftime %Y/%m/%d +``` + +![](http://image.python-online.cn/20190623191042.png) + +## 8.9.3 往rpc中引入消息中间件 + +经过了上面的学习,我们已经学会了如何使用多种方式实现rpc远程调用。 + +通过对比,zerorpc 可以说是脱颖而出,一支独秀。 + +但为何在 OpenStack 中,rpc client 不直接 rpc 调用 rpc server ,而是先把 rpc 调用请求发给 RabbitMQ ,再由订阅者(rpc server)来取消息,最终实现远程调用呢? + +为此,我也做了一番思考: + +OpenStack 组件繁多,在一个较大的集群内部每个组件内部通过rpc通信频繁,如果都采用rpc直连调用的方式,连接数会非常地多,开销大,若有些 server 是单线程的模式,超时会非常的严重。 + +OpenStack 是复杂的分布式集群架构,会有多个 rpc server 同时工作,假设有 server01,server02,server03 三个server,当 rpc client 要发出rpc请求时,发给哪个好呢?这是问题一。 + +你可能会说轮循或者随机,这样对大家都公平。这样的话还会引出另一个问题,倘若请求刚好发到server01,而server01刚好不凑巧,可能由于机器或者其他因为导致服务没在工作,那这个rpc消息可就直接失败了呀。要知道做为一个集群,高可用是基本要求,如果出现刚刚那样的情况其实是很尴尬的。这是问题二。 + +集群有可能根据实际需要扩充节点数量,如果使用直接调用,耦合度太高,不利于部署和生产。这是问题三。 + +引入消息中间件,可以很好的解决这些问题。 + +**解决问题一**:消息只有一份,接收者由AMQP的负载算法决定,默认为在所有Receiver中均匀发送(round robin)。 + +**解决问题二**:有了消息中间件做缓冲站,client 可以任性随意的发,server 都挂掉了?没有关系,等 server 正常工作后,自己来消息中间件取就行了。 + +**解决问题三**:无数有多少节点,它们只要认识消息中间件这一个中介就足够了。 + +## 8.9.4 消息队列你应该知道什么? + +由于后面,我将实例讲解 OpenStack 中如何将 rpc 和 mq broker 结合使用。 + +而在此之前,你必须对消息队列的一些基本知识有个概念。 + +首先,RPC只是定义了一个通信接口,其底层的实现可以各不相同,可以是 socket,也可以是今天要讲的 AMQP。 + +AMQP(Advanced Message Queuing Protocol)是一种基于队列的可靠消息服务协议,作为一种通信协议,AMQP同样存在多个实现,如Apache Qpid,RabbitMQ等。 + +以下是 AMQP 中的几个必知的概念: + +- Publisher:消息发布者 + +- Receiver:消息接收者,在RabbitMQ中叫订阅者:Subscriber。 + +- Queue:用来保存消息的存储空间,消息没有被receiver前,保存在队列中。 + +- Exchange:用来接收Publisher发出的消息,根据Routing key 转发消息到对应的Message Queue中,至于转到哪个队列里,这个路由算法又由exchange type决定的。 + + exchange type:主要四种描述exchange的类型。 + + direct:消息路由到满足此条件的队列中(queue,可以有多个): routing key = binding key + + topic:消息路由到满足此条件的队列中(queue,可以有多个):routing key 匹配 binding pattern. binding pattern是类似正则表达式的字符串,可以满足复杂的路由条件。 + + fanout:消息路由到多有绑定到该exchange的队列中。 + +- binding :binding是用来描述exchange和queue之间的关系的概念,一个exchang可以绑定多个队列,这些关系由binding建立。前面说的binding key /binding pattern也是在binding中给出。 + +在网上找了个图,可以很清晰地描述几个名词的关系。 + +![](http://image.python-online.cn/20190623205052.png) + +关于AMQP,有几下几点值得注意: + +1. 每个receiver/subscriber 在接收消息前都需要创建binding。 +2. 一个队列可以有多个receiver,队列里的一个消息只能发给一个receiver。 +3. 一个消息可以被发送到一个队列中,也可以被发送到多个多列中。多队列情况下,一个消息可以被多个receiver收到并处理。Openstack RPC中这两种情况都会用到。 + +## 8.9.5 OpenStack中如何使用RPC? + +前面铺垫了那么久,终于到了讲真实应用的场景。在生产中RPC是如何应用的呢? + +其他模型我不太清楚,在 OpenStack 中的应用模型是这样的 + +![](http://image.python-online.cn/20190623201427.png) + +至于为什么要如此设计,前面我已经给出了自己的观点。 + +接下来,就是源码解读 OpenStack ,看看其是如何通过rpc进行远程调用的。如若你对此没有兴趣(我知道很多人对此都没有兴趣,所以不浪费大家时间),可以直接跳过这一节,进入下一节。 + +目前Openstack中有两种RPC实现,一种是在oslo messaging,一种是在openstack.common.rpc。 + +openstack.common.rpc是旧的实现,oslo messaging是对openstack.common.rpc的重构。openstack.common.rpc在每个项目中都存在一份拷贝,oslo messaging即将这些公共代码抽取出来,形成一个新的项目。oslo messaging也对RPC API 进行了重新设计,对多种 transport 做了进一步封装,底层也是用到了kombu这个AMQP库。(注:Kombu 是Python中的messaging库。Kombu旨在通过为AMQ协议提供惯用的高级接口,使Python中的消息传递尽可能简单,并为常见的消息传递问题提供经过验证和测试的解决方案。) 关于oslo_messaging库,主要提供了两种独立的API: 1. oslo.messaging.rpc(实现了客户端-服务器远程过程调用) 2. oslo.messaging.notify(实现了事件的通知机制) -## 8.9.2 如何实现 rpc 远程调用 +因为 notify 实现是太简单了,所以这里我就不多说了,如果有人想要看这方面内容,可以收藏我的博客(http://python-online.cn) ,我会更新补充 notify 的内容。 -**先来讲解第一个 rpc 的远程调用。** +OpenStack RPC 模块提供了 rpc.call,rpc.cast, rpc.fanout_cast 三种 RPC 调用方法,发送和接收 RPC 请求。 + +- rpc.call 发送 RPC **同步请求**并返回请求处理结果。 +- rpc.cast 发送 RPC **异步请求**,与 rpc.call 不同之处在于,不需要请求处理结果的返回。 +- rpc.fanout_cast 用于发送 RPC 广播信息无返回结果 + +rpc.call 和 rpc.rpc.cast 从实现代码上看,他们的区别很小,就是call调用时候会带有wait_for_reply=True参数,而cast不带。 要了解 rpc 的调用机制呢,首先要知道 oslo_messaging 的几个概念 - transport:RPC功能的底层实现方法,这里是rabbitmq的消息队列的访问路径 - transport 就是定义你如何访连接消息中间件,比如你使用的是 Rabbitmq,那在nova.conf中应该有一行`transport_url`的配置,可以很清楚地看出指定了 rabbitmq 为消息中间件,并配置了连接rabbitmq的user,passwd,主机,端口。 + transport 就是定义你如何访连接消息中间件,比如你使用的是 Rabbitmq,那在 nova.conf中应该有一行`transport_url`的配置,可以很清楚地看出指定了 rabbitmq 为消息中间件,并配置了连接rabbitmq的user,passwd,主机,端口。 ```python - transport_url=rabbit://user:passwd@ctrl.openstack.com:5672 + transport_url=rabbit://user:passwd@host:5672 ``` ![](http://image.python-online.cn/20190526182125.png) @@ -46,19 +430,9 @@ OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul API方 transport_cls=RPCTransport) ``` - transport 的 driver 有如下几种: - - 1. amqp : - 2. fake :测试使用,该driver将消息发送到内存中。 - 3. kafka :experimental - 4. kombu :RabbitMQ Driver。openstack默认的。 - 5. pika :successor to the existing rabbit/kombu driver。 - 6. rabbit :RabbitMQ Driver - 7. zmq :通过ZeroMQ 实现了RPC和Notifer API.详见: +- target:指定RPC topic交换机的匹配信息和绑定主机。 -- target:指定RPC topic交换机的匹配信息和绑定主机 - - target用来表述 RPC 服务器监听topic,server名称和server监听的exchange,是否广播fanout + target用来表述 RPC 服务器监听topic,server名称和server监听的exchange,是否广播fanout。 ```python class Target(object): @@ -78,8 +452,6 @@ OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul API方 ![](http://image.python-online.cn/20190526184854.png) - - rpc client 要发送消息,也需要有target,说明消息要发到哪去。 ![](http://image.python-online.cn/20190526185217.png) @@ -88,9 +460,7 @@ OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul API方 RPC服务器暴露出endpoint,每个 endpoint 包涵一系列的可被远程客户端通过 transport 调用的方法。直观理解,可以参考nova-conductor创建rpc server的代码,这边的endpoints就是 `nova/manager.py:ConductorManager()`![](http://image.python-online.cn/20190526221219.png) - - -- dispatcher:分发器,这是 rpc server 才有的概念 ![](http://image.python-online.cn/20190526220809.png)只有它才知道server端接收到的哪些rpc调用是我要处理的,并且知道远方client端是我调用我的哪个方法? +- dispatcher:分发器,这是 rpc server 才有的概念 ![](http://image.python-online.cn/20190526220809.png)只有通过它 server 端才知道接收到的rpc请求,要交给谁处理,怎么处理? 在client端,是这样指定要调用哪个方法的。 @@ -100,8 +470,6 @@ OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul API方 ![](http://image.python-online.cn/20190527220012.png) - - - Serializer:在 python 对象和message(notification) 之间数据做序列化或是反序列化的基类。 主要方法有四个: @@ -119,7 +487,6 @@ OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul API方 oslo_messaging.get_notification_listener(transport, targets, endpoints, executor=’blocking’, serializer=None, allow_requeue=False, pool=None) ``` - rpc server 和rpc client 的四个重要方法 @@ -128,23 +495,32 @@ rpc server 和rpc client 的四个重要方法 3. `stop()`:当调用stop之后,新的message不会被处理。但是,server可能还在处理一些之前没有处理完的message,并且底层driver资源也还一直没有释放。 4. `wait()`:在stop调用之后,可能还有message正在被处理,使用wait方法来阻塞当前进程,直到所有的message都处理完成。之后,底层的driver资源会释放。 +## 8.9.6 模仿OpenStack写rpc调用 + +模仿是一种很高效的学习方法,我这里根据 OpenStack 的调用方式,抽取出核心内容,写成一个简单的 demo,有对 OpenStack 感兴趣的可以了解一下,**大部分人也可以直接跳过这章节**。 +以下代码不能直接运行,你还需要配置 rabbitmq 的连接方式,你可以写在配置文件中,通过 get_transport 从cfg.CONF 中读取,也可以直接将其写成url的格式做成参数,传给 get_transport 。 **简单的 rpc client** ```python +#coding=utf-8 import oslo_messaging from oslo_config import cfg -transport = messaging.get_transport(cfg.CONF) -target = messaging.Target(topic='test', version='2.0') -client = messaging.RPCClient(transport, target) +# 创建 rpc client +transport = oslo_messaging.get_transport(cfg.CONF, url="") +target = oslo_messaging.Target(topic='test', version='2.0') +client = oslo_messaging.RPCClient(transport, target) + +# rpc同步调用 client.call(ctxt, 'test', arg=arg) ``` -**简单的rpc server** +**简单的 rpc server** ```python +#coding=utf-8 from oslo_config import cfg import oslo_messaging import time @@ -169,14 +545,13 @@ class TestEndpoint(object): # 创建rpc server -transport = oslo_messaging.get_transport(cfg.CONF) +transport = oslo_messaging.get_transport(cfg.CONF, url="") target = oslo_messaging.Target(topic='test', server='server1') endpoints = [ ServerControlEndpoint(None), TestEndpoint(), ] -server = oslo_messaging.get_rpc_server(transport, target, endpoints, - executor='blocking') +server = oslo_messaging.get_rpc_server(transport, target,endpoints,executor='blocking') try: server.start() while True: @@ -190,7 +565,7 @@ server.wait() -## 8.5.3 如何实现 rpc 事件通知 +## 8.9.7 如何实现 rpc 事件通知 说完了 rpc 调用,**再来了解它的事件通知机制**,这个比较简单。 @@ -224,12 +599,6 @@ server.wait() -[nova event机制分析](https://blog.csdn.net/epugv/article/details/44872583) - - - - - 参考文章: - [OpenStack之RPC调用(一)](https://blog.csdn.net/qiuhan0314/article/details/42671965) @@ -237,6 +606,11 @@ server.wait() - [模仿OpenStack写自己的RPC](https://www.cnblogs.com/goldsunshine/p/10205058.html) - [python 64式: 第1式 编写rpc的call和cast](https://blog.csdn.net/qingyuanluofeng/article/details/80546961) - [Openstack RPC 通信原理](https://www.ibm.com/developerworks/cn/cloud/library/1403_renmm_opestackrpc/) +- [RPC、REST API深入理解](https://blog.csdn.net/huojiao2006/article/details/82186389) +- [分布式RPC框架性能大比拼](https://colobu.com/2016/09/05/benchmarks-of-popular-rpc-frameworks/) +- [ython中使用XMLRPC(入门)](https://www.cnblogs.com/lxt287994374/p/3904219.html) +- [(译) JSON-RPC 2.0 规范(中文版)](https://colobu.com/2016/09/05/benchmarks-of-popular-rpc-frameworks/) +- [nova event机制分析](https://blog.csdn.net/epugv/article/details/44872583) --- diff --git a/source/c08/c08_09.rst b/source/c08/c08_09.rst index 8950d1b..c4db580 100644 --- a/source/c08/c08_09.rst +++ b/source/c08/c08_09.rst @@ -1,59 +1,517 @@ -8.9 OpenStack中的rpc通信机制 -============================ +8.9 RPC +======= -OpenStack的通信方式有两种,一种是基于HTTP协议的RESTFul -API方式,另一种则是RPC调用。两种通信方式的应用场景有所不同,在OpenStack中,前者主要用于各组件之间的通信(如nova与glance的通信),而后者则用于同一组件中各个不同模块之间的通信(如nova组件中nova-compute与nova-scheduler的通信)。 -关于OpenStack中基于RESTFul -API的通信方式主要是应用了WSGI,我会在以后的博文中详细讨论。这里主要还是分享一下关于RPC调用的一些理解。 +什么是RPC呢? -首先,什么是RPC呢?百度百科给出的解释是这样的:“RPC(Remote Procedure -Call -Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议”。这个概念听起来还是比较抽象,下面我会结合OpenStack中RPC调用的具体应用来具体分析一下这个协议。 +百度百科给出的解释是这样的:“RPC(Remote Procedure Call +Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议”。这个概念听起来还是比较抽象,没关系,继续往后看,后面概念性的东西,我会讲得足够清楚,让你完全掌握 +RPC 的基础内容。在后面的篇章中还会结合其在 OpenStack +中实际应用,一步一步揭开 rpc 的神秘面纱。 -8.9.1 为什么要使用 RPC? +有的读者,可能会问,为啥我举的例子老是 OpenStack 里的东西呢? + +因为每个人的业务中接触的框架都不一样(我主要接触的就是 OpenStack +框架),我无法为每个人去定制写一篇文章,但其技术原理都是一样的。即使如此,我也会尽力将文章写得通用,不会因为你没接触过 +OpenStack 而成为你理解 rpc 的瓶颈。 + +8.9.1 既 REST,何 RPC ? ------------------------ -其次,为什么要采用RPC呢?单纯的依靠RESTFul -API不可以吗?其实原因有下面这几个: +在 OpenStack 里的进程间通信方式主要有两种,一种是基于HTTP协议的RESTFul +API方式,另一种则是RPC调用。 + +那么这两种方式在应用场景上有何区别呢? + +有使用经验的人,就会知道: + +- 前者(RESTful)主要用于\ **各组件之间**\ 的通信(如nova与glance的通信),或者说用于组件对外提供调用接口 +- 而后者(RPC)则用于\ **同一组件中各个不同模块之间**\ 的通信(如nova组件中nova-compute与nova-scheduler的通信)。 + +关于OpenStack中基于RESTful +API的通信方式主要是应用了WSGI,这个知识点,我在前一篇文章中,有深入地讲解过,你可以点击查看。 + +对于不熟悉 OpenStack 的人,也别担心听不懂,这样吧,我给你提两个问题: + +1. RPC 和 REST 区别是什么? +2. 为什么要采用RPC呢? + +**第一个问题:RPC 和 REST 区别是什么?** + +你一定会觉得这个问题很奇怪,是的,包括我,但是你在网络上一搜,会发现类似对比的文章比比皆是,我在想可能很多初学者由于基础不牢固,才会将不相干的二者拿出来对比吧。既然是这样,那为了让你更加了解陌生的RPC,就从你熟悉得不能再熟悉的 +REST 入手吧。 + +**01、所属类别不同** + +REST,是Representational State Transfer +的简写,中文描述表述性状态传递(是指某个瞬间状态的资源数据的快照,包括资源数据的内容、表述格式(XML、JSON)等信息。) + +REST 是一种软件架构风格。 +这种风格的典型应用,就是HTTP。其因为简单、扩展性强的特点而广受开发者的青睐。 + +而RPC 呢,是 Remote Procedure Call Protocol +的简写,中文描述是远程过程调用,它可以实现客户端像调用本地服务(方法)一样调用服务器的服务(方法)。 + +RPC 是一种基于 TCP +的通信协议,按理说它和REST不是一个层面上的东西,不应该放在一起讨论,但是谁让REST这么流行呢,它是目前最流行的一套互联网应用程序的API设计标准,某种意义下,我们说 +REST 可以其实就是指代 HTTP 协议。 + +**02、使用方式不同** + +**从使用上来看**\ ,HTTP +接口只关注服务提供方,对于客户端怎么调用并不关心。接口只要保证有客户端调用时,返回对应的数据就行了。而RPC则要求客户端接口保持和服务端的一致。 + +- REST + 是服务端把方法写好,客户端并不知道具体方法。客户端只想获取资源,所以发起HTTP请求,而服务端接收到请求后根据URI经过一系列的路由才定位到方法上面去 +- PRC是服务端提供好方法给客户端调用,客户端需要知道服务端的具体类,具体方法,然后像调用本地方法一样直接调用它。 + +**03、面向对象不同** + +从设计上来看,RPC,所谓的远程过程调用 ,是面向方法的 ,REST:所谓的 +Representational state transfer ,是面向资源的,除此之外,还有一种叫做 +SOA,所谓的面向服务的架构,它是面向消息的,这个接触不多,就不多说了。 + +**04、序列化协议不同** + +接口调用通常包含两个部分,序列化和通信协议。 + +通信协议,上面已经提及了,REST 是 基于 HTTP 协议,而 RPC 可以基于 +TCP/UDP,也可以基于 HTTP 协议进行传输的。 + +常见的序列化协议,有:json、xml、hession、protobuf、thrift、text、bytes等,REST +通常使用的是 JSON或者XML,而 RPC 使用的是 JSON-RPC,或者 XML-RPC。 -1. 由于RESTFul - API是基于HTTP协议的,因此客户端与服务端之间所能传输的消息仅限于文本 -2. RESTFul - API客户端与服务端之间采用的是同步机制,当发送HTTP请求时,客户端需要等待服务端的响应。当然对于这一点是可以通过一些技术来实现异步的机制的 -3. 采用RESTFul - API,客户端与服务端之间虽然可以独立开发,但还是存在耦合。比如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常工作 +通过以上几点,我们知道了 REST 和 RPC 之间有很明显的差异。 -基于上面这几个原因,所以OpenStack才采用了另一种远程通信机制,这就是我们今天要讨论的鼎鼎大名的RPC。 +**第二个问题:为什么要采用RPC呢?** + +那到底为何要使用 RPC,单纯的依靠RESTful +API不可以吗?为什么要搞这么多复杂的协议,渣渣表示真的学不过来了。 + +关于这一点,以下几点仅是我的个人猜想,仅供交流哈: + +1. RPC 和 REST 两者的定位不同,REST + 面向资源,更注重接口的规范,因为要保证通用性更强,所以对外最好通过 + REST。而 RPC + 面向方法,主要用于函数方法的调用,可以适合更复杂通信需求的场景。 +2. RESTful + API客户端与服务端之间采用的是同步机制,当发送HTTP请求时,客户端需要等待服务端的响应。当然对于这一点是可以通过一些技术来实现异步的机制的。 +3. 采用RESTful + API,客户端与服务端之间虽然可以独立开发,但还是存在耦合。比如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常工作。而 + rpc + ralbbimq中间件可以实现低耦合的分布式集群架构。 + +说了这么多,我们该如何选择这两者呢?我总结了如下两点,供你参考: + +- REST 接口更加规范,通用适配性要求高,建议对外的接口都统一成 + REST(也有例外,比如我接触过 zabbix,其 API 就是基于 JSON-RPC + 2.0协议的)。而组件内部的各个模块,可以选择 + RPC,一个是不用耗费太多精力去开发和维护多套的HTTP接口,一个RPC的调用性能更高(见下条) + +- 从性能角度看,由于HTTP本身提供了丰富的状态功能与扩展功能,但也正由于HTTP提供的功能过多,导致在网络传输时,需要携带的信息更多,从性能角度上讲,较为低效。而RPC服务网络传输上仅传输与业务内容相关的数据,传输数据更小,性能更高。 + +8.9.2 实现远程调用的三种方式 +---------------------------- + +“远程调用”意思就是:被调用方法的具体实现不在程序运行本地,而是在别的某个地方(分布到各个服务器),调用者只想要函数运算的结果,却不需要实现函数的具体细节。 + +**01、基于 xml-rpc** + +Python实现 rpc,可以使用标准库里的 SimpleXMLRPCServer,它是基于XML-RPC +协议的。 + +有了这个模块,开启一个 rpc server,就变得相当简单了。执行以下代码: + +.. code:: python + + import SimpleXMLRPCServer + + class calculate: + def add(self, x, y): + return x + y + + def multiply(self, x, y): + return x * y + + def subtract(self, x, y): + return abs(x-y) + + def divide(self, x, y): + return x/y + + + obj = calculate() + server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 8088)) + # 将实例注册给rpc server + server.register_instance(obj) + + print "Listening on port 8088" + server.serve_forever() + +有了 rpc server,接下来就是 rpc client,由于我们上面使用的是 +XML-RPC,所以 rpc clinet 需要使用xmlrpclib 这个库。 + +.. code:: python -今天的话题,就是源码解读OpenStack是如何通过rpc进行远程调用的。 + import xmlrpclib -谈到 OpenStack 的 rpc,就不得不提到 oslo_messaging 这个 OpenStack -专用工具库,oslo_messaging只是对多种transport做了进一步封装,底层也是用到了kombu这个AMQP库,当transport是amqp时,kombu会进行环境监测判断是否安装了librabbitmq,如果安装了就使用librabbitmq,没有则使用pyamqp;因为librabbitmq是使用C编写的库,所以比pyamqp速度快很多。 + server = xmlrpclib.ServerProxy("http://localhost:8088") + +然后,我们通过 server_proxy 对象就可以远程调用之前的rpc server的函数了。 + +.. code:: python + + >> server.add(2, 3) + 5 + >>> server.multiply(2, 3) + 6 + >>> server.subtract(2, 3) + 1 + >>> server.divide(2, 3) + 0 + +SimpleXMLRPCServer是一个单线程的服务器。这意味着,如果几个客户端同时发出多个请求,其它的请求就必须等待第一个请求完成以后才能继续。 + +若非要使用 SimpleXMLRPCServer +实现多线程并发,其实也不难。只要将代码改成如下即可。 + +.. code:: python + + from SimpleXMLRPCServer import SimpleXMLRPCServer + from SocketServer import ThreadingMixIn + class ThreadXMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer):pass + + class MyObject: + def hello(self): + return "hello xmlprc" + + obj = MyObject() + server = ThreadXMLRPCServer(("localhost", 8088), allow_none=True) + server.register_instance(obj) + + print "Listening on port 8088" + server.serve_forever() + +**02、基于json-rpc** + +SimpleXMLRPCServer 是基于 xml-rpc 实现的远程调用,上面我们也提到 除了 +xml-rpc 之外,还有 json-rpc 协议。 + +那 python 如何实现基于 json-rpc 协议呢? + +答案是很多,很多web框架其自身都自己实现了json-rpc,但我们要独立这些框架之外,要寻求一种较为干净的解决方案,我查找到的选择有两种 + +第一种是 ``jsonrpclib`` + +.. code:: shell + + pip install jsonrpclib -i https://pypi.douban.com/simple + +第二种是 ``python-jsonrpc`` + +.. code:: shell + + pip install python-jsonrpc -i https://pypi.douban.com/simple + +先来看第一种 +`jsonrpclib `__ + +它与 Python 标准库的 SimpleXMLRPCServer 很类似(因为它的类名就叫做 +SimpleJSONRPCServer +,不明真相的人真以为它们是亲兄弟)。或许可以说,jsonrpclib 就是仿照 +SimpleXMLRPCServer 标准库来进行编写的。 + +它的导入与 SimpleXMLRPCServer +略有不同,因为SimpleJSONRPCServer分布在jsonrpclib库中。 + +服务端 + +.. code:: python + + from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer + + server = SimpleJSONRPCServer(('localhost', 8080)) + server.register_function(lambda x,y: x+y, 'add') + server.serve_forever() + +客户端 + +.. code:: python + + import jsonrpclib + + server = jsonrpclib.Server("http://localhost:8080") + +|image0| + +再来看第二种python-jsonrpc,写起来貌似有些复杂。 + +服务端 + +.. code:: python + + import pyjsonrpc + + + class RequestHandler(pyjsonrpc.HttpRequestHandler): + + @pyjsonrpc.rpcmethod + def add(self, a, b): + """Test method""" + return a + b + + http_server = pyjsonrpc.ThreadingHttpServer( + server_address=('localhost', 8080), + RequestHandlerClass=RequestHandler + ) + print "Starting HTTP server ..." + print "URL: http://localhost:8080" + http_server.serve_forever() + +客户端 + +.. code:: python + + import pyjsonrpc + + http_client = pyjsonrpc.HttpClient( + url="http://localhost:8080/jsonrpc" + ) + +|image1| + +还记得上面我提到过的 zabbix +API,因为我有接触过,所以也拎出来讲讲。zabbix API 也是基于 json-rpc +2.0协议实现的。 + +因为内容较多,这里只带大家打个,zabbix 是如何调用的:直接指明要调用 +zabbix server 的哪个方法,要传给这个方法的参数有哪些。 + +|image2| + +**03、基于 zerorpc** + +以上介绍的两种rpc远程调用方式,如果你足够细心,可以发现他们都是http+rpc +两种协议结合实现的。 + +接下来,我们要介绍的这种(\ `zerorpc `__\ ),就不再使用走 +http 了。 + +`zerorpc `__ +这个第三方库,它是基于TCP协议、 ZeroMQ 和 +MessagePack的,速度相对快,响应时间短,并发高。zerorpc 和 pyjsonrpc +一样,需要额外安装,虽然SimpleXMLRPCServer不需要额外安装,但是SimpleXMLRPCServer性能相对差一些。 + +.. code:: shell + + pip install zerorpc -i https://pypi.douban.com/simple + +服务端代码 + +.. code:: python + + import zerorpc + + class caculate(object): + def hello(self, name): + return 'hello, {}'.format(name) + + def add(self, x, y): + return x + y + + def multiply(self, x, y): + return x * y + + def subtract(self, x, y): + return abs(x-y) + + def divide(self, x, y): + return x/y + + s = zerorpc.Server(caculate()) + + s.bind("tcp://0.0.0.0:4242") + s.run() + +客户端 + +.. code:: python + + import zerorpc + + c = zerorpc.Client() + c.connect("tcp://127.0.0.1:4242") + +|image3| + +客户端除了可以使用zerorpc框架实现代码调用之外,它还支持使用“命令行”的方式调用。 + +|image4| + +客户端可以使用命令行,那服务端是不是也可以呢? + +是的,通过 Github 上的文档几个 demo 可以体验到这个第三方库做真的是优秀。 + +比如我们可以用下面这个命令,创建一个rpc server,后面这个 ``time`` Python +标准库中的 time 模块,zerorpc 会将 time 注册绑定以供client调用。 + +.. code:: shell + + zerorpc --server --bind tcp://127.0.0.1:1234 time + +在客户端,就可以用这条命令来远程调用这个 time 函数。 + +.. code:: shell + + zerorpc --client --connect tcp://127.0.0.1:1234 strftime %Y/%m/%d + +|image5| + +8.9.3 往rpc中引入消息中间件 +--------------------------- + +经过了上面的学习,我们已经学会了如何使用多种方式实现rpc远程调用。 + +通过对比,zerorpc 可以说是脱颖而出,一支独秀。 + +但为何在 OpenStack 中,rpc client 不直接 rpc 调用 rpc server ,而是先把 +rpc 调用请求发给 RabbitMQ ,再由订阅者(rpc +server)来取消息,最终实现远程调用呢? + +为此,我也做了一番思考: + +OpenStack +组件繁多,在一个较大的集群内部每个组件内部通过rpc通信频繁,如果都采用rpc直连调用的方式,连接数会非常地多,开销大,若有些 +server 是单线程的模式,超时会非常的严重。 + +OpenStack 是复杂的分布式集群架构,会有多个 rpc server 同时工作,假设有 +server01,server02,server03 三个server,当 rpc client +要发出rpc请求时,发给哪个好呢?这是问题一。 + +你可能会说轮循或者随机,这样对大家都公平。这样的话还会引出另一个问题,倘若请求刚好发到server01,而server01刚好不凑巧,可能由于机器或者其他因为导致服务没在工作,那这个rpc消息可就直接失败了呀。要知道做为一个集群,高可用是基本要求,如果出现刚刚那样的情况其实是很尴尬的。这是问题二。 + +集群有可能根据实际需要扩充节点数量,如果使用直接调用,耦合度太高,不利于部署和生产。这是问题三。 + +引入消息中间件,可以很好的解决这些问题。 + +**解决问题一**\ :消息只有一份,接收者由AMQP的负载算法决定,默认为在所有Receiver中均匀发送(round +robin)。 + +**解决问题二**\ :有了消息中间件做缓冲站,client +可以任性随意的发,server 都挂掉了?没有关系,等 server +正常工作后,自己来消息中间件取就行了。 + +**解决问题三**\ :无数有多少节点,它们只要认识消息中间件这一个中介就足够了。 + +8.9.4 消息队列你应该知道什么? +------------------------------ + +由于后面,我将实例讲解 OpenStack 中如何将 rpc 和 mq broker 结合使用。 + +而在此之前,你必须对消息队列的一些基本知识有个概念。 + +首先,RPC只是定义了一个通信接口,其底层的实现可以各不相同,可以是 +socket,也可以是今天要讲的 AMQP。 + +AMQP(Advanced Message Queuing +Protocol)是一种基于队列的可靠消息服务协议,作为一种通信协议,AMQP同样存在多个实现,如Apache +Qpid,RabbitMQ等。 + +以下是 AMQP 中的几个必知的概念: + +- Publisher:消息发布者 + +- Receiver:消息接收者,在RabbitMQ中叫订阅者:Subscriber。 + +- Queue:用来保存消息的存储空间,消息没有被receiver前,保存在队列中。 + +- Exchange:用来接收Publisher发出的消息,根据Routing key + 转发消息到对应的Message + Queue中,至于转到哪个队列里,这个路由算法又由exchange type决定的。 + + exchange type:主要四种描述exchange的类型。 + + direct:消息路由到满足此条件的队列中(queue,可以有多个): routing key + = binding key + + topic:消息路由到满足此条件的队列中(queue,可以有多个):routing key + 匹配 binding pattern. binding + pattern是类似正则表达式的字符串,可以满足复杂的路由条件。 + + fanout:消息路由到多有绑定到该exchange的队列中。 + +- binding + :binding是用来描述exchange和queue之间的关系的概念,一个exchang可以绑定多个队列,这些关系由binding建立。前面说的binding + key /binding pattern也是在binding中给出。 + +在网上找了个图,可以很清晰地描述几个名词的关系。 + +|image6| + +关于AMQP,有几下几点值得注意: + +1. 每个receiver/subscriber 在接收消息前都需要创建binding。 +2. 一个队列可以有多个receiver,队列里的一个消息只能发给一个receiver。 +3. 一个消息可以被发送到一个队列中,也可以被发送到多个多列中。多队列情况下,一个消息可以被多个receiver收到并处理。Openstack + RPC中这两种情况都会用到。 + +8.9.5 OpenStack中如何使用RPC? +------------------------------ + +前面铺垫了那么久,终于到了讲真实应用的场景。在生产中RPC是如何应用的呢? + +其他模型我不太清楚,在 OpenStack 中的应用模型是这样的 + +|image7| + +至于为什么要如此设计,前面我已经给出了自己的观点。 + +接下来,就是源码解读 OpenStack +,看看其是如何通过rpc进行远程调用的。如若你对此没有兴趣(我知道很多人对此都没有兴趣,所以不浪费大家时间),可以直接跳过这一节,进入下一节。 + +目前Openstack中有两种RPC实现,一种是在oslo +messaging,一种是在openstack.common.rpc。 + +openstack.common.rpc是旧的实现,oslo +messaging是对openstack.common.rpc的重构。openstack.common.rpc在每个项目中都存在一份拷贝,oslo +messaging即将这些公共代码抽取出来,形成一个新的项目。oslo +messaging也对RPC API 进行了重新设计,对多种 transport +做了进一步封装,底层也是用到了kombu这个AMQP库。(注:Kombu +是Python中的messaging库。Kombu旨在通过为AMQ协议提供惯用的高级接口,使Python中的消息传递尽可能简单,并为常见的消息传递问题提供经过验证和测试的解决方案。) 关于oslo_messaging库,主要提供了两种独立的API: 1. oslo.messaging.rpc(实现了客户端-服务器远程过程调用) 2. oslo.messaging.notify(实现了事件的通知机制) -8.9.2 如何实现 rpc 远程调用 ---------------------------- +因为 notify +实现是太简单了,所以这里我就不多说了,如果有人想要看这方面内容,可以收藏我的博客(http://python-online.cn) +,我会更新补充 notify 的内容。 + +OpenStack RPC 模块提供了 rpc.call,rpc.cast, rpc.fanout_cast 三种 RPC +调用方法,发送和接收 RPC 请求。 + +- rpc.call 发送 RPC **同步请求**\ 并返回请求处理结果。 +- rpc.cast 发送 RPC **异步请求**\ ,与 rpc.call + 不同之处在于,不需要请求处理结果的返回。 +- rpc.fanout_cast 用于发送 RPC 广播信息无返回结果 -**先来讲解第一个 rpc 的远程调用。** +rpc.call 和 rpc.rpc.cast +从实现代码上看,他们的区别很小,就是call调用时候会带有wait_for_reply=True参数,而cast不带。 要了解 rpc 的调用机制呢,首先要知道 oslo_messaging 的几个概念 - transport:RPC功能的底层实现方法,这里是rabbitmq的消息队列的访问路径 transport 就是定义你如何访连接消息中间件,比如你使用的是 - Rabbitmq,那在nova.conf中应该有一行\ ``transport_url``\ 的配置,可以很清楚地看出指定了 + Rabbitmq,那在 + nova.conf中应该有一行\ ``transport_url``\ 的配置,可以很清楚地看出指定了 rabbitmq 为消息中间件,并配置了连接rabbitmq的user,passwd,主机,端口。 .. code:: python - transport_url=rabbit://user:passwd@ctrl.openstack.com:5672 + transport_url=rabbit://user:passwd@host:5672 - |image0| + |image8| .. code:: python @@ -61,23 +519,10 @@ API不可以吗?其实原因有下面这几个: return _get_transport(conf, url, allowed_remote_exmods, transport_cls=RPCTransport) - transport 的 driver 有如下几种: - - 1. amqp - :\ https://docs.openstack.org/developer/oslo.messaging/AMQP1.0.html - 2. fake :测试使用,该driver将消息发送到内存中。 - 3. kafka :experimental - 4. kombu :RabbitMQ Driver。openstack默认的。 - 5. pika :successor to the existing rabbit/kombu driver。 - https://docs.openstack.org/developer/oslo.messaging/pika_driver.html - 6. rabbit :RabbitMQ Driver - 7. zmq :通过ZeroMQ 实现了RPC和Notifer - API.详见:\ https://docs.openstack.org/developer/oslo.messaging/zmq_driver.html - -- target:指定RPC topic交换机的匹配信息和绑定主机 +- target:指定RPC topic交换机的匹配信息和绑定主机。 target用来表述 RPC - 服务器监听topic,server名称和server监听的exchange,是否广播fanout + 服务器监听topic,server名称和server监听的exchange,是否广播fanout。 .. code:: python @@ -95,30 +540,30 @@ API不可以吗?其实原因有下面这几个: rpc server 要获取消息,需要定义target,就像一个门牌号一样。 - |image1| + |image9| rpc client 要发送消息,也需要有target,说明消息要发到哪去。 - |image2| + |image10| - endpoints:是可供别人远程调用的对象 RPC服务器暴露出endpoint,每个 endpoint 包涵一系列的可被远程客户端通过 transport 调用的方法。直观理解,可以参考nova-conductor创建rpc server的代码,这边的endpoints就是 - ``nova/manager.py:ConductorManager()``\ |image3| + ``nova/manager.py:ConductorManager()``\ |image11| -- dispatcher:分发器,这是 rpc server 才有的概念 - |image4|\ 只有它才知道server端接收到的哪些rpc调用是我要处理的,并且知道远方client端是我调用我的哪个方法? +- dispatcher:分发器,这是 rpc server 才有的概念 |image12|\ 只有通过它 + server 端才知道接收到的rpc请求,要交给谁处理,怎么处理? 在client端,是这样指定要调用哪个方法的。 - |image5| + |image13| 而在server端,是如何知道要执行这个方法的呢?这就是dispatcher 要干的事,它从 endpoint 里找到这个方法,然后执行,最后返回。 - |image6| + |image14| - Serializer:在 python 对象和message(notification) 之间数据做序列化或是反序列化的基类。 @@ -150,22 +595,38 @@ rpc server 和rpc client 的四个重要方法 3. ``stop()``:当调用stop之后,新的message不会被处理。但是,server可能还在处理一些之前没有处理完的message,并且底层driver资源也还一直没有释放。 4. ``wait()``\ :在stop调用之后,可能还有message正在被处理,使用wait方法来阻塞当前进程,直到所有的message都处理完成。之后,底层的driver资源会释放。 +8.9.6 模仿OpenStack写rpc调用 +---------------------------- + +模仿是一种很高效的学习方法,我这里根据 OpenStack +的调用方式,抽取出核心内容,写成一个简单的 demo,有对 OpenStack +感兴趣的可以了解一下,\ **大部分人也可以直接跳过这章节**\ 。 + +以下代码不能直接运行,你还需要配置 rabbitmq +的连接方式,你可以写在配置文件中,通过 get_transport 从cfg.CONF +中读取,也可以直接将其写成url的格式做成参数,传给 get_transport 。 + **简单的 rpc client** .. code:: python + #coding=utf-8 import oslo_messaging from oslo_config import cfg - transport = messaging.get_transport(cfg.CONF) - target = messaging.Target(topic='test', version='2.0') - client = messaging.RPCClient(transport, target) + # 创建 rpc client + transport = oslo_messaging.get_transport(cfg.CONF, url="") + target = oslo_messaging.Target(topic='test', version='2.0') + client = oslo_messaging.RPCClient(transport, target) + + # rpc同步调用 client.call(ctxt, 'test', arg=arg) -**简单的rpc server** +**简单的 rpc server** .. code:: python + #coding=utf-8 from oslo_config import cfg import oslo_messaging import time @@ -190,14 +651,13 @@ rpc server 和rpc client 的四个重要方法 # 创建rpc server - transport = oslo_messaging.get_transport(cfg.CONF) + transport = oslo_messaging.get_transport(cfg.CONF, url="") target = oslo_messaging.Target(topic='test', server='server1') endpoints = [ ServerControlEndpoint(None), TestEndpoint(), ] - server = oslo_messaging.get_rpc_server(transport, target, endpoints, - executor='blocking') + server = oslo_messaging.get_rpc_server(transport, target,endpoints,executor='blocking') try: server.start() while True: @@ -208,7 +668,7 @@ rpc server 和rpc client 的四个重要方法 server.stop() server.wait() -8.5.3 如何实现 rpc 事件通知 +8.9.7 如何实现 rpc 事件通知 --------------------------- 说完了 rpc 调用,\ **再来了解它的事件通知机制**\ ,这个比较简单。 @@ -219,31 +679,28 @@ rpc server 和rpc client 的四个重要方法 首先在这里先定义合法的 ``notification_event_types``\ ,相当于添加白名单。 -|image7| +|image15| 然后在调用处,使用 ``rpc.get_notifier`` 来发送消息给ceilometer。 -|image8| +|image16| 继续查看 ``rpc.get_notifier`` 做了什么事?如何实现直接info 就能发送消息的。 -|image9| +|image17| 当你使用的event_types 不在白名单内,或者是异常信息。就会给打印warn日志 -|image10| +|image18| 在rabbit里查看队列,notification 是 topic -|image11| +|image19| 而 debug ,info 等是event priority -|image12| - -`nova -event机制分析 `__ +|image20| 参考文章: @@ -255,6 +712,14 @@ event机制分析 `__ 编写rpc的call和cast `__ - `Openstack RPC 通信原理 `__ +- `RPC、REST + API深入理解 `__ +- `分布式RPC框架性能大比拼 `__ +- `ython中使用XMLRPC(入门) `__ +- `(译) JSON-RPC 2.0 + 规范(中文版) `__ +- `nova + event机制分析 `__ -------------- @@ -262,17 +727,25 @@ event机制分析 `__ :alt: 关注公众号,获取最新干货! -.. |image0| image:: http://image.python-online.cn/20190526182125.png -.. |image1| image:: http://image.python-online.cn/20190526184854.png -.. |image2| image:: http://image.python-online.cn/20190526185217.png -.. |image3| image:: http://image.python-online.cn/20190526221219.png -.. |image4| image:: http://image.python-online.cn/20190526220809.png -.. |image5| image:: http://image.python-online.cn/20190527220820.png -.. |image6| image:: http://image.python-online.cn/20190527220012.png -.. |image7| image:: http://image.python-online.cn/20190526172514.png -.. |image8| image:: http://image.python-online.cn/20190526172725.png -.. |image9| image:: http://image.python-online.cn/20190526173314.png -.. |image10| image:: http://image.python-online.cn/20190526175100.png -.. |image11| image:: http://image.python-online.cn/20190526180708.png -.. |image12| image:: http://image.python-online.cn/20190526181433.png +.. |image0| image:: http://image.python-online.cn/20190623185008.png +.. |image1| image:: http://image.python-online.cn/20190623165341.png +.. |image2| image:: http://image.python-online.cn/20190623171138.png +.. |image3| image:: http://image.python-online.cn/20190623155955.png +.. |image4| image:: http://image.python-online.cn/20190623162725.png +.. |image5| image:: http://image.python-online.cn/20190623191042.png +.. |image6| image:: http://image.python-online.cn/20190623205052.png +.. |image7| image:: http://image.python-online.cn/20190623201427.png +.. |image8| image:: http://image.python-online.cn/20190526182125.png +.. |image9| image:: http://image.python-online.cn/20190526184854.png +.. |image10| image:: http://image.python-online.cn/20190526185217.png +.. |image11| image:: http://image.python-online.cn/20190526221219.png +.. |image12| image:: http://image.python-online.cn/20190526220809.png +.. |image13| image:: http://image.python-online.cn/20190527220820.png +.. |image14| image:: http://image.python-online.cn/20190527220012.png +.. |image15| image:: http://image.python-online.cn/20190526172514.png +.. |image16| image:: http://image.python-online.cn/20190526172725.png +.. |image17| image:: http://image.python-online.cn/20190526173314.png +.. |image18| image:: http://image.python-online.cn/20190526175100.png +.. |image19| image:: http://image.python-online.cn/20190526180708.png +.. |image20| image:: http://image.python-online.cn/20190526181433.png From 072a8c966564c1157dd2876aedf4492d1773057c Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Wed, 26 Jun 2019 12:26:06 +0800 Subject: [PATCH 043/302] =?UTF-8?q?docker=20=E6=96=87=E7=AB=A0=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/c07_03.md | 4 ++-- source/c08/c08_09.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/c07/c07_03.md b/source/c07/c07_03.md index 1e766dc..7e4efee 100644 --- a/source/c07/c07_03.md +++ b/source/c07/c07_03.md @@ -92,7 +92,7 @@ docker exec -it bash -> exit 退出,bash必须指定 2. exec -it 进入交互模式 # 查看日志输出 -docker log [-f] # -f 和 tail -f 一样时时输出 +docker logs [-f] # -f 和 tail -f 一样时时输出 # 更名 docker rename @@ -149,7 +149,7 @@ docker run --name container_B -it -c 512 --cpu 1 ### 3.4 blkio配额 -**`Block IO` 限制不同容器的读写资源分配** +`Block IO` 限制不同容器的读写资源分配 Block IO 指的是磁盘的读写,docker 可通过设置权重、限制 bps 和 iops 的方式控制容器读写磁盘的带宽。 默认情况下,所有容器能平等地读写磁盘,可以通过设置 `--blkio-weight` 参数来改变容器 block IO 的优先级。设置的是相对权重值,默认为 500。 ``` diff --git a/source/c08/c08_09.md b/source/c08/c08_09.md index e4eba61..fba1cb6 100644 --- a/source/c08/c08_09.md +++ b/source/c08/c08_09.md @@ -1,4 +1,4 @@ -# 8.9 RPC +# 8.9 从0到1:全面理解RPC远程调用 什么是RPC呢? @@ -339,7 +339,7 @@ OpenStack 是复杂的分布式集群架构,会有多个 rpc server 同时工 **解决问题二**:有了消息中间件做缓冲站,client 可以任性随意的发,server 都挂掉了?没有关系,等 server 正常工作后,自己来消息中间件取就行了。 -**解决问题三**:无数有多少节点,它们只要认识消息中间件这一个中介就足够了。 +**解决问题三**:无论有多少节点,它们只要认识消息中间件这一个中介就足够了。 ## 8.9.4 消息队列你应该知道什么? From e08a5fd7da2f26b230a12aa79ed0322e8d4e5247 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Wed, 26 Jun 2019 13:24:13 +0800 Subject: [PATCH 044/302] =?UTF-8?q?rpc=20=E6=96=87=E7=AB=A0=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_09.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/c08/c08_09.md b/source/c08/c08_09.md index fba1cb6..020ea14 100644 --- a/source/c08/c08_09.md +++ b/source/c08/c08_09.md @@ -45,7 +45,7 @@ RPC 是一种基于 TCP 的通信协议,按理说它和REST不是一个层面 **从使用上来看**,HTTP 接口只关注服务提供方,对于客户端怎么调用并不关心。接口只要保证有客户端调用时,返回对应的数据就行了。而RPC则要求客户端接口保持和服务端的一致。 - REST 是服务端把方法写好,客户端并不知道具体方法。客户端只想获取资源,所以发起HTTP请求,而服务端接收到请求后根据URI经过一系列的路由才定位到方法上面去 -- PRC是服务端提供好方法给客户端调用,客户端需要知道服务端的具体类,具体方法,然后像调用本地方法一样直接调用它。 +- RPC是服务端提供好方法给客户端调用,客户端需要知道服务端的具体类,具体方法,然后像调用本地方法一样直接调用它。 **03、面向对象不同** @@ -69,11 +69,11 @@ RPC 是一种基于 TCP 的通信协议,按理说它和REST不是一个层面 1. RPC 和 REST 两者的定位不同,REST 面向资源,更注重接口的规范,因为要保证通用性更强,所以对外最好通过 REST。而 RPC 面向方法,主要用于函数方法的调用,可以适合更复杂通信需求的场景。 2. RESTful API客户端与服务端之间采用的是同步机制,当发送HTTP请求时,客户端需要等待服务端的响应。当然对于这一点是可以通过一些技术来实现异步的机制的。 -3. 采用RESTful API,客户端与服务端之间虽然可以独立开发,但还是存在耦合。比如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常工作。而 rpc + ralbbimq中间件可以实现低耦合的分布式集群架构。 +3. 采用RESTful API,客户端与服务端之间虽然可以独立开发,但还是存在耦合。比如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常工作。而 rpc + rabbitmq中间件可以实现低耦合的分布式集群架构。 说了这么多,我们该如何选择这两者呢?我总结了如下两点,供你参考: -- REST 接口更加规范,通用适配性要求高,建议对外的接口都统一成 REST(也有例外,比如我接触过 zabbix,其 API 就是基于 JSON-RPC 2.0协议的)。而组件内部的各个模块,可以选择 RPC,一个是不用耗费太多精力去开发和维护多套的HTTP接口,一个RPC的调用性能更高(见下条) +- REST 接口更加规范,通用适配性要求高,建议对外的接口都统一成 REST。而组件内部的各个模块,可以选择 RPC,一个是不用耗费太多精力去开发和维护多套的HTTP接口,一个RPC的调用性能更高(见下条) - 从性能角度看,由于HTTP本身提供了丰富的状态功能与扩展功能,但也正由于HTTP提供的功能过多,导致在网络传输时,需要携带的信息更多,从性能角度上讲,较为低效。而RPC服务网络传输上仅传输与业务内容相关的数据,传输数据更小,性能更高。 @@ -410,7 +410,7 @@ OpenStack RPC 模块提供了 rpc.call,rpc.cast, rpc.fanout_cast 三种 RPC - rpc.cast 发送 RPC **异步请求**,与 rpc.call 不同之处在于,不需要请求处理结果的返回。 - rpc.fanout_cast 用于发送 RPC 广播信息无返回结果 -rpc.call 和 rpc.rpc.cast 从实现代码上看,他们的区别很小,就是call调用时候会带有wait_for_reply=True参数,而cast不带。 +rpc.call 和 rpc.cast 从实现代码上看,他们的区别很小,就是call调用时候会带有wait_for_reply=True参数,而cast不带。 要了解 rpc 的调用机制呢,首先要知道 oslo_messaging 的几个概念 From 1a30fe361750d8371462074f415ae003d734239f Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Fri, 28 Jun 2019 20:04:17 +0800 Subject: [PATCH 045/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 83 ++++++++++++++++------------- source/c08/c08_10.md | 92 -------------------------------- source/c08/c08_12.rst | 121 ------------------------------------------ 3 files changed, 47 insertions(+), 249 deletions(-) delete mode 100644 source/c08/c08_12.rst diff --git a/README.md b/README.md index ce03d5b..a89de3b 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,16 @@ # 博客目录: ## 第一章:基础知识 -- 1.1 [13条Python2.x和3.x的区别?](http://mings-blog.readthedocs.io/zh_CN/latest/c01/c01_01.html) -- 1.2 [Python的自省机制](http://mings-blog.readthedocs.io/zh_CN/latest/c01/c01_02.html) -- 1.3 [谈谈深贝和浅拷贝的区别](http://mings-blog.readthedocs.io/zh_CN/latest/c01/c01_03.html) -- 1.4 [什么是猴子补丁?](http://mings-blog.readthedocs.io/zh_CN/latest/c01/c01_04.html) -- 1.5 [深入理解闭包与变量作用域](http://mings-blog.readthedocs.io/zh_CN/latest/c01/c01_05.html) -- 1.6 [深入理解元组存在的意义](http://mings-blog.readthedocs.io/zh_CN/latest/c01/c01_07.html) -- 1.7 [15个Pythonic的代码示例](https://mings-blog.readthedocs.io/zh_CN/latest/c01/c01_06.html) -- 1.8 [新式类和经典类的区别?](https://mings-blog.readthedocs.io/zh_CN/latest/c01/c01_08.html) -- 1.9 [多继承与Mixin设计模式](https://mings-blog.readthedocs.io/zh_CN/latest/c01/c01_09.html) -- 1.10 [盘点 Python 中那些冷知识](https://mings-blog.readthedocs.io/zh_CN/latest/c01/c01_10.html) +- 1.1 [13条Python2.x和3.x的区别?](http://python-online.cn/zh_CN/latest/c01/c01_01.html) +- 1.2 [Python的自省机制](http://python-online.cn/zh_CN/latest/c01/c01_02.html) +- 1.3 [谈谈深贝和浅拷贝的区别](http://python-online.cn/zh_CN/latest/c01/c01_03.html) +- 1.4 [什么是猴子补丁?](http://python-online.cn/zh_CN/latest/c01/c01_04.html) +- 1.5 [深入理解闭包与变量作用域](http://python-online.cn/zh_CN/latest/c01/c01_05.html) +- 1.6 [深入理解元组存在的意义](http://python-online.cn/zh_CN/latest/c01/c01_07.html) +- 1.7 [15个Pythonic的代码示例](https://python-online.cn/zh_CN/latest/c01/c01_06.html) +- 1.8 [新式类和经典类的区别?](https://python-online.cn/zh_CN/latest/c01/c01_08.html) +- 1.9 [多继承与Mixin设计模式](https://python-online.cn/zh_CN/latest/c01/c01_09.html) +- 1.10 [盘点 Python 中那些冷知识](https://python-online.cn/zh_CN/latest/c01/c01_10.html) - 1.11 [正则表达式必知必会](http://python-online.cn/zh_CN/latest/c01/c01_11.html) - 1.12 [搞懂字符编码的前世今生](http://python-online.cn/zh_CN/latest/c01/c01_12.html) - 1.13 [Python几个高阶函数](http://python-online.cn/zh_CN/latest/c01/c01_13.html) @@ -23,34 +23,36 @@ - 1.15 [提升Python性能的7个习惯](http://python-online.cn/zh_CN/latest/c01/c01_15.html) - 1.16 [泛型函数怎么写?](http://python-online.cn/zh_CN/latest/c01/c01_16.html) - 1.17 [深入理解「描述符」](http://python-online.cn/zh_CN/latest/c01/c01_17.html) +- 1.18 [MySQL使用总结](http://python-online.cn/zh_CN/latest/c01/c01_18.html) ## 第二章:并发编程 -- 2.1 [从性能角度来初探并发编程](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_01.html) -- 2.2 [创建多线程的几种方法](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_02.html) -- 2.3 [谈谈线程中的“锁机制”](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_03.html) -- 2.4 [线程消息通信机制任务协调](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_04.html) -- 2.5 [线程中的信息隔离](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_05.html) -- 2.6 [消息队列补充及如何创建线程池](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_06.html) -- 2.7 [从生成器使用入门协程](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_07.html) -- 2.8 [深入理解yield from语法](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_08.html) -- 2.9 [初识异步IO框架:asyncio 上篇](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_09.html) -- 2.10 [学习异步IO框架:asyncio 中篇](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_10.html) -- 2.11 [实战异步IO框架:asyncio 下篇](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c02_11.html) +- 2.1 [从性能角度来初探并发编程](http://python-online.cn/zh_CN/latest/c02/c02_01.html) +- 2.2 [创建多线程的几种方法](http://python-online.cn/zh_CN/latest/c02/c02_02.html) +- 2.3 [谈谈线程中的“锁机制”](http://python-online.cn/zh_CN/latest/c02/c02_03.html) +- 2.4 [线程消息通信机制任务协调](http://python-online.cn/zh_CN/latest/c02/c02_04.html) +- 2.5 [线程中的信息隔离](http://python-online.cn/zh_CN/latest/c02/c02_05.html) +- 2.6 [消息队列补充及如何创建线程池](http://python-online.cn/zh_CN/latest/c02/c02_06.html) +- 2.7 [从生成器使用入门协程](http://python-online.cn/zh_CN/latest/c02/c02_07.html) +- 2.8 [深入理解yield from语法](http://python-online.cn/zh_CN/latest/c02/c02_08.html) +- 2.9 [初识异步IO框架:asyncio 上篇](http://python-online.cn/zh_CN/latest/c02/c02_09.html) +- 2.10 [学习异步IO框架:asyncio 中篇](http://python-online.cn/zh_CN/latest/c02/c02_10.html) +- 2.11 [实战异步IO框架:asyncio 下篇](http://python-online.cn/zh_CN/latest/c02/c02_11.html) - 2.12 [生成器与协程,你分清了吗?](http://python-online.cn/zh_CN/latest/c02/c02_12.html) ## 第三章:高级编程 -- 3.1 [装饰器从入门到高阶](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c03_01.html) -- 3.2 [学会使用元类定制类](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c03_02.html) -- 3.3 [学会使用socket网络编程](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c03_03.html) +- 3.1 [装饰器从入门到高阶](http://python-online.cn/zh_CN/latest/c02/c03_01.html) +- 3.2 [学会使用元类定制类](http://python-online.cn/zh_CN/latest/c02/c03_02.html) +- 3.3 [学会使用socket网络编程](http://python-online.cn/zh_CN/latest/c02/c03_03.html) - 3.4 [Django+Uwsgi部署网站](http://python-online.cn/zh_CN/latest/c03/c03_04.html) - 3.5 [源码解读:Flask上下文与代理模式](http://python-online.cn/zh_CN/latest/c03/c03_05.html) +- 3.6 [Web开发者必看:理解WSGI](http://python-online.cn/zh_CN/latest/c03/c03_06.html) ## 第四章:开发工具 -- 4.1 [虚拟环境的搭建与使用](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c04_01.html) -- 4.2 [Xshell的高效使用手册](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c04_02.html) -- 4.3 [30分钟教你搭建顔值超高的博客](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c04_03.html) -- 4.4 [Jupyter NoteBook 的使用指南](https://mings-blog.readthedocs.io/zh_CN/latest/c04/c04_04.html) -- 4.5 [Win10 + Ubuntu 双系统安装指南](https://mings-blog.readthedocs.io/zh_CN/latest/c04/c04_05.html) +- 4.1 [虚拟环境的搭建与使用](http://python-online.cn/zh_CN/latest/c02/c04_01.html) +- 4.2 [Xshell的高效使用手册](http://python-online.cn/zh_CN/latest/c02/c04_02.html) +- 4.3 [30分钟教你搭建顔值超高的博客](http://python-online.cn/zh_CN/latest/c02/c04_03.html) +- 4.4 [Jupyter NoteBook 的使用指南](https://python-online.cn/zh_CN/latest/c04/c04_04.html) +- 4.5 [Win10 + Ubuntu 双系统安装指南](https://python-online.cn/zh_CN/latest/c04/c04_05.html) - 4.6 [我的 Git 使用指南](http://python-online.cn/zh_CN/latest/c04/c04_06.html) - 4.7 [ Hexo 搭建博客教程](http://python-online.cn/zh_CN/latest/c04/c04_07.html) - 4.8[珍惜生命,远离鼠标](http://python-online.cn/zh_CN/latest/c04/c04_08.html) @@ -63,16 +65,21 @@ - 4.15 [15个 PyCharm 小技巧](http://python-online.cn/zh_CN/latest/c04/c04_15.html) - 4.16 [Python 开发技巧集合](http://python-online.cn/zh_CN/latest/c04/c04_16.html) - 4.17 [Python 实现 23 种设计模式](http://python-online.cn/zh_CN/latest/c04/c04_17.html) +- 4.18 [Mac 高效工具](http://python-online.cn/zh_CN/latest/c04/c04_18.html) +- 4.19 [程序员编码必学:Vim](http://python-online.cn/zh_CN/latest/c04/c04_19.html) ## 第五章:算法教程 -- 5.1 [图解九大经典排序算法(一)](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c05_01.html) -- 5.2 [图解九大经典排序算法(二)](http://mings-blog.readthedocs.io/zh_CN/latest/c02/c05_02.html) +- 5.1 [图解九大经典排序算法](http://python-online.cn/zh_CN/latest/c02/c05_01.html) +- 5.2 [递归算法:走楼梯会思考的题](http://python-online.cn/zh_CN/latest/c04/c04_18.html) +- 5.3 [哈希算法:安全方面的算法](http://python-online.cn/zh_CN/latest/c04/c04_18.html) ## 第六章:数据分析 -- 6.1 [一张图带你入门matplotlib](https://mings-blog.readthedocs.io/zh_CN/latest/c06/c06_01.html) -- 6.2 [详解六种可视化图表](https://mings-blog.readthedocs.io/zh_CN/latest/c06/c06_02.html) -- 6.3 [如何绘制正余弦函数图象](https://mings-blog.readthedocs.io/zh_CN/latest/c06/c06_03.html) -- 6.4 [子图与子区 难点突破](https://mings-blog.readthedocs.io/zh_CN/latest/c06/c06_04.html) +- 6.1 [一张图带你入门matplotlib](https://python-online.cn/zh_CN/latest/c06/c06_01.html) +- 6.2 [详解六种可视化图表](https://python-online.cn/zh_CN/latest/c06/c06_02.html) +- 6.3 [如何绘制正余弦函数图象](https://python-online.cn/zh_CN/latest/c06/c06_03.html) +- 6.4 [子图与子区 难点突破](https://python-online.cn/zh_CN/latest/c06/c06_04.html) +- 6.5 [绘制酷炫的gif动图](http://python-online.cn/zh_CN/latest/c06/c06_05.html) +- 6.6 [自动生成图像视频](http://python-online.cn/zh_CN/latest/c06/c06_06.html) ## 第七章:运维人生 @@ -84,6 +91,7 @@ - 7.6 [Docker:存储与多主机](http://python-online.cn/zh_CN/latest/c07/c07_06.html) - 7.7 [SaltStack 入门指南](http://python-online.cn/zh_CN/latest/c07/c07_07.html) - 7.8 [Keepalived 部署文档](http://python-online.cn/zh_CN/latest/c07/c07_08.html) +- 7.9 [Ansible使用总结](http://python-online.cn/zh_CN/latest/c07/c07_09.html) ## 第八章:OpenStack @@ -94,10 +102,13 @@ - 8.5 [OpenStack 源码剖析](http://python-online.cn/zh_CN/latest/c08/c08_05.html) - 8.6 [深度解读Cloud-init源码](http://python-online.cn/zh_CN/latest/c08/c08_06.html) - 8.7 [OpenStack 实现GPU直通](http://python-online.cn/zh_CN/latest/c08/c08_07.html) +- 8.8 [OpenStack 如何使用DHCP?](http://python-online.cn/zh_CN/latest/c08/c08_08.html) +- 8.9 [全面理解RPC远程调用](http://python-online.cn/zh_CN/latest/c08/c08_09.html) +- 8.11 [OpenStack问题排查](http://python-online.cn/zh_CN/latest/c08/c08_11.html) ## LeetCode Challenge -- [NO.1-10](http://mings-blog.readthedocs.io/zh_CN/latest/lc01/1-10.html) +- [NO.1-10](http://python-online.cn/zh_CN/latest/lc01/1-10.html) --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) diff --git a/source/c08/c08_10.md b/source/c08/c08_10.md index be31317..e69de29 100644 --- a/source/c08/c08_10.md +++ b/source/c08/c08_10.md @@ -1,92 +0,0 @@ -# 8.10 semaphore 实现一键升级 - -## 8.10.1 部署环境 - -安装数据库(下载过程较慢,可以优化) - -```python -yum -y install mariadb-server -systemctl start mariadb -systemctl enable mariadb -``` - -安装好后,还没有设置密码,可以立即无密码登陆数据库,所以要先设置一下密码。 - -![](http://image.python-online.cn/20190529150907.png) - - - -安装semaphore - -```python -mkdir /home/semaphore; cd !& - -yum install -y git wget - -wget https://github.com/ansible-semaphore/semaphore/releases/download/v2.5.1/semaphore_2.5.1_linux_amd64.rpm - -rpm -ivh semaphore_2.5.1_linux_amd64.rpm - - -# ------------------ 以上命令可以用一条命令 ------------ -curl -L https://github.com/ansible-semaphore/semaphore/releases/download/v2.5.1/semaphore_2.5.1_linux_arm64.rpm > /usr/bin/semaphore -``` - -进入初始化 - -![](http://image.python-online.cn/20190529151350.png) - -![](http://image.python-online.cn/20190529151450.png) - -开始启动semaphore - -```shell -sh -c 'nohup semaphore -config /home/semaphore/config.json &' >/dev/null 2>&1 -``` - -启动后,就能使用 http://{ip}:3000 进行访问。前面设置的 8010 端口不会生效,很奇怪。 - -![](http://image.python-online.cn/20190529154348.png) - -## 8.10.2 配置 - -先生成 ssh-key - -```shell -[root@semaphore-master ~]# ssh-keygen -t rsa -C "root@semaphore-master" -Generating public/private rsa key pair. -Enter file in which to save the key (/root/.ssh/id_rsa): -Enter passphrase (empty for no passphrase): -Enter same passphrase again: -Your identification has been saved in /root/.ssh/id_rsa. -Your public key has been saved in /root/.ssh/id_rsa.pub. -The key fingerprint is: -SHA256:9YRZhzv5hISkCb3a6zFLxbWvnlvQelAwFBunF160uGI root@semaphore-master -The key's randomart image is: -+---[RSA 2048]----+ -| .. ..oBo+.o| -| ..o.+oX.o.| -| o.+.==+. | -| .o +==o | -| oS oE==. | -| . ... .=. | -| +. . + | -| ..+ = | -| .o .=. | -+----[SHA256]-----+ -``` - - - -## 8.10.4 使用 AnsibleAble - -安装 - -```shell -docker run -p 80:8080 mmumshad/ansible-playable -``` - -打开web,登陆(使用默认帐号密码) - -![](http://image.python-online.cn/20190530143442.png) - diff --git a/source/c08/c08_12.rst b/source/c08/c08_12.rst deleted file mode 100644 index c72e9f6..0000000 --- a/source/c08/c08_12.rst +++ /dev/null @@ -1,121 +0,0 @@ -8.12 从nova-api的启动理解wsgi -============================= - -nova 里有不少服务,比如 -nova-compute,nova-api,nova-conductor,nova-scheduler 等。 - -这些服务如何都是如何启动的呢?他们其实都是用同一套代码,所以只要分析一个就行,这里以nova-compute为例来了解一下。 - -从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 -``nova.cmd.compute:main()`` - -|image0| - -从这个入口进去,会开启一个 ``nova-compute`` 的服务。 - -|image1| - -当调用 service.Service.create 时(create -是一个工厂函数),实际是返回实例化的 service.Service 对象。当没有传入 -manager 时,就以binary 里的为准。比如binary -是\ ``nova-compute``\ ,那manager_cls 就是 -``compute_manager``\ ,对应的manager 导入路径,就会从配置里读取。 - -|image2| - -通过了解了上面 nova-compute 的启动过程,nova-api -也是相同的思路,它的入口是\ ``nova.cmd.api:main()``\ ,不过和nova-compute不一样的是,nova-compute没有对外接口,而nova-api -有,所以它会启动一个 wsgi 服务器。 - -接下来,我们就来一起看看是 OpenStack Nova 是如何启动一个 WSGI 服务器的。 - -在如下的黄框里,可以看到在这里启动了一个 server,就是我们所说的的 wsgi -server - -|image3| - -|image4| - -再进入 wsgi.py 可以看到这里使用了 eventlet -这个并发库,它开启了一个绿色线程池,从配置里可以看到这个wsgi -服务器可以接收的请求并发量是 1000 。 - -|image5| - -你接下来感兴趣的应该是,这个线程池里的每个线程都是啥?是如何接收请求的? - -通过对源码的阅读,可以得知是通过socket接收请求的。 - -由于代码较多,我把提取了主要的代码,精简如下 - -.. code:: python - - # 创建绿色线程池 - self._pool = eventlet.GreenPool(self.pool_size) - - # 创建 socket:有监听的ip,端口 - bind_addr = (host, port) - self._socket = eventlet.listen(bind_addr, family, backlog=backlog) - dup_socket = self._socket.dup() - - # 整理孵化协程所需的各项参数 - wsgi_kwargs = { - 'func': eventlet.wsgi.server, - 'sock': dup_socket, - 'site': self.app, # 这个就是 wsgi 的 application 函数 - 'protocol': self._protocol, - 'custom_pool': self._pool, - 'log': self._logger, - 'log_format': CONF.wsgi.wsgi_log_format, - 'debug': False, - 'keepalive': CONF.wsgi.keep_alive, - 'socket_timeout': self.client_socket_timeout - } - - # 孵化协程 - self._server = utils.spawn(**wsgi_kwargs) - -|image6| - -我们都知道 wsgi 要传入一个 -application,用来处理接收到的请求,是我们整个服务的关键入口,那这里的 -app 是哪个呢?其实在上面代码中我有注释: self.app 。 - -下面这行就是 self.app 的来源,通过查看我打印的 DEBUG 内容得知 config_url -和 app name 的值 - -|image7| - -再往代码中看,其实这个 app -不是直接写死成一个具体的函数对象,而是通过解析 paste.ini -配置文件来取得具体的 application 路径,然后导入。 - -而 paste.ini 文件的解析是通过 Python 的第三方库 ``paste`` - -下图我截取了其主要的代码 - -|image8| - -通过上面DEBUG日志,我们知道了 ``uri =/etc/nova/api-paste.ini`` ,查看 -``/etc/nova/api-paste.ini`` ,果然可以找到 ``osapi_compute`` -这个app,从这个路由表,可以得到 application 的的路径(对于 paste.ini -可以查阅这篇文章:\ `python -中paste.ini文件使用说明 `__ -),是\ ``nova.api.openstack.compute`` 这个模块下的 APIRouterV21 类 -的factory方法。 - -.. code:: shell - - [app:osapi_compute_app_v21] - paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory - -.. |image0| image:: http://image.python-online.cn/20190526205152.png -.. |image1| image:: http://image.python-online.cn/20190526165007.png -.. |image2| image:: http://image.python-online.cn/20190526204328.png -.. |image3| image:: http://image.python-online.cn/20190530212557.png -.. |image4| image:: http://image.python-online.cn/20190530212753.png -.. |image5| image:: http://image.python-online.cn/20190530212956.png -.. |image6| image:: http://image.python-online.cn/20190530214820.png -.. |image7| image:: http://image.python-online.cn/20190530221101.png -.. |image8| image:: http://image.python-online.cn/20190530220957.png - From 12ccbbde051d7180d34aaafdb06516ded4b2a3fb Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Fri, 28 Jun 2019 20:06:16 +0800 Subject: [PATCH 046/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0rst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/c07_03.rst | 4 +- source/c08/c08_09.rst | 15 ++++--- source/c08/c08_10.rst | 98 ------------------------------------------- 3 files changed, 9 insertions(+), 108 deletions(-) diff --git a/source/c07/c07_03.rst b/source/c07/c07_03.rst index 6eb4fd6..ce4ae89 100755 --- a/source/c07/c07_03.rst +++ b/source/c07/c07_03.rst @@ -113,7 +113,7 @@ Ubuntu:\ `Ubuntu 16.04 上安装 Docker 2. exec -it 进入交互模式 # 查看日志输出 - docker log [-f] # -f 和 tail -f 一样时时输出 + docker logs [-f] # -f 和 tail -f 一样时时输出 # 更名 docker rename @@ -177,7 +177,7 @@ Ubuntu:\ `Ubuntu 16.04 上安装 Docker 3.4 blkio配额 ~~~~~~~~~~~~~ -**``Block IO`` 限制不同容器的读写资源分配** Block IO +``Block IO`` 限制不同容器的读写资源分配 Block IO 指的是磁盘的读写,docker 可通过设置权重、限制 bps 和 iops 的方式控制容器读写磁盘的带宽。 默认情况下,所有容器能平等地读写磁盘,可以通过设置 ``--blkio-weight`` diff --git a/source/c08/c08_09.rst b/source/c08/c08_09.rst index c4db580..9750b50 100644 --- a/source/c08/c08_09.rst +++ b/source/c08/c08_09.rst @@ -1,5 +1,5 @@ -8.9 RPC -======= +8.9 从0到1:全面理解RPC远程调用 +=============================== 什么是RPC呢? @@ -62,7 +62,7 @@ REST 可以其实就是指代 HTTP 协议。 - REST 是服务端把方法写好,客户端并不知道具体方法。客户端只想获取资源,所以发起HTTP请求,而服务端接收到请求后根据URI经过一系列的路由才定位到方法上面去 -- PRC是服务端提供好方法给客户端调用,客户端需要知道服务端的具体类,具体方法,然后像调用本地方法一样直接调用它。 +- RPC是服务端提供好方法给客户端调用,客户端需要知道服务端的具体类,具体方法,然后像调用本地方法一样直接调用它。 **03、面向对象不同** @@ -97,13 +97,12 @@ API不可以吗?为什么要搞这么多复杂的协议,渣渣表示真的 API客户端与服务端之间采用的是同步机制,当发送HTTP请求时,客户端需要等待服务端的响应。当然对于这一点是可以通过一些技术来实现异步的机制的。 3. 采用RESTful API,客户端与服务端之间虽然可以独立开发,但还是存在耦合。比如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常工作。而 - rpc + ralbbimq中间件可以实现低耦合的分布式集群架构。 + rpc + rabbitmq中间件可以实现低耦合的分布式集群架构。 说了这么多,我们该如何选择这两者呢?我总结了如下两点,供你参考: - REST 接口更加规范,通用适配性要求高,建议对外的接口都统一成 - REST(也有例外,比如我接触过 zabbix,其 API 就是基于 JSON-RPC - 2.0协议的)。而组件内部的各个模块,可以选择 + REST。而组件内部的各个模块,可以选择 RPC,一个是不用耗费太多精力去开发和维护多套的HTTP接口,一个RPC的调用性能更高(见下条) - 从性能角度看,由于HTTP本身提供了丰富的状态功能与扩展功能,但也正由于HTTP提供的功能过多,导致在网络传输时,需要携带的信息更多,从性能角度上讲,较为低效。而RPC服务网络传输上仅传输与业务内容相关的数据,传输数据更小,性能更高。 @@ -399,7 +398,7 @@ robin)。 可以任性随意的发,server 都挂掉了?没有关系,等 server 正常工作后,自己来消息中间件取就行了。 -**解决问题三**\ :无数有多少节点,它们只要认识消息中间件这一个中介就足够了。 +**解决问题三**\ :无论有多少节点,它们只要认识消息中间件这一个中介就足够了。 8.9.4 消息队列你应该知道什么? ------------------------------ @@ -494,7 +493,7 @@ OpenStack RPC 模块提供了 rpc.call,rpc.cast, rpc.fanout_cast 三种 RPC 不同之处在于,不需要请求处理结果的返回。 - rpc.fanout_cast 用于发送 RPC 广播信息无返回结果 -rpc.call 和 rpc.rpc.cast +rpc.call 和 rpc.cast 从实现代码上看,他们的区别很小,就是call调用时候会带有wait_for_reply=True参数,而cast不带。 要了解 rpc 的调用机制呢,首先要知道 oslo_messaging 的几个概念 diff --git a/source/c08/c08_10.rst b/source/c08/c08_10.rst index ed73e63..8b13789 100644 --- a/source/c08/c08_10.rst +++ b/source/c08/c08_10.rst @@ -1,99 +1 @@ -8.10 semaphore 实现一键升级 -=========================== - -8.10.1 部署环境 ---------------- - -安装数据库(下载过程较慢,可以优化) - -.. code:: python - - yum -y install mariadb-server - systemctl start mariadb - systemctl enable mariadb - -安装好后,还没有设置密码,可以立即无密码登陆数据库,所以要先设置一下密码。 - -|image0| - -安装semaphore - -.. code:: python - - mkdir /home/semaphore; cd !& - - yum install -y git wget - - wget https://github.com/ansible-semaphore/semaphore/releases/download/v2.5.1/semaphore_2.5.1_linux_amd64.rpm - - rpm -ivh semaphore_2.5.1_linux_amd64.rpm - - - # ------------------ 以上命令可以用一条命令 ------------ - curl -L https://github.com/ansible-semaphore/semaphore/releases/download/v2.5.1/semaphore_2.5.1_linux_arm64.rpm > /usr/bin/semaphore - -进入初始化 - -|image1| - -|image2| - -开始启动semaphore - -.. code:: shell - - sh -c 'nohup semaphore -config /home/semaphore/config.json &' >/dev/null 2>&1 - -启动后,就能使用 http://{ip}:3000 进行访问。前面设置的 8010 -端口不会生效,很奇怪。 - -|image3| - -8.10.2 配置 ------------ - -先生成 ssh-key - -.. code:: shell - - [root@semaphore-master ~]# ssh-keygen -t rsa -C "root@semaphore-master" - Generating public/private rsa key pair. - Enter file in which to save the key (/root/.ssh/id_rsa): - Enter passphrase (empty for no passphrase): - Enter same passphrase again: - Your identification has been saved in /root/.ssh/id_rsa. - Your public key has been saved in /root/.ssh/id_rsa.pub. - The key fingerprint is: - SHA256:9YRZhzv5hISkCb3a6zFLxbWvnlvQelAwFBunF160uGI root@semaphore-master - The key's randomart image is: - +---[RSA 2048]----+ - | .. ..oBo+.o| - | ..o.+oX.o.| - | o.+.==+. | - | .o +==o | - | oS oE==. | - | . ... .=. | - | +. . + | - | ..+ = | - | .o .=. | - +----[SHA256]-----+ - -8.10.4 使用 AnsibleAble ------------------------ - -安装 - -.. code:: shell - - docker run -p 80:8080 mmumshad/ansible-playable - -打开web,登陆(使用默认帐号密码) - -|image4| - -.. |image0| image:: http://image.python-online.cn/20190529150907.png -.. |image1| image:: http://image.python-online.cn/20190529151350.png -.. |image2| image:: http://image.python-online.cn/20190529151450.png -.. |image3| image:: http://image.python-online.cn/20190529154348.png -.. |image4| image:: http://image.python-online.cn/20190530143442.png From 21e3e4788c8b52190d38dc7b62694a39aae81c8c Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 29 Jun 2019 13:47:38 +0800 Subject: [PATCH 047/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0wsgi=E6=96=87?= =?UTF-8?q?=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c03/c03_06.md | 59 ++++++++++++++++++++++++++++----------- source/c03/c03_06.rst | 65 +++++++++++++++++++++++++++++++------------ 2 files changed, 90 insertions(+), 34 deletions(-) diff --git a/source/c03/c03_06.md b/source/c03/c03_06.md index 2b0cb72..f464a1d 100644 --- a/source/c03/c03_06.md +++ b/source/c03/c03_06.md @@ -1,4 +1,4 @@ -# 3.6 一篇文章搞懂WSGI +# 3.6 从0到1:全面理解WSGI 在 三百六十行,行行转 IT 的现状下,很多来自各行各业的同学,都选择 Python 这门胶水语言做为踏入互联网大门的第一块敲门砖,在这些人里,又有相当大比例的同学选择了 Web 开发这个方向(包括我)。而从事 web 开发,绕不过一个知识点,就是 WSGI。 @@ -23,13 +23,14 @@ 今天主要是讲第二阶段,主要内容有以下几点: 1. WSGI 是什么,因何而生? -2. HTTP请求是如何到应用程序的? -3. 实现一个简单的 WSGI Server -4. 实现“高并发”的WSGI Server -5. 第一次路由:PasteDeploy -6. PasteDeploy 使用说明 -7. webob.dec.wsgify 装饰器 -8. 第二次路由:中间件 routes 路由 +2. 为什么要有 WSGI? +3. HTTP请求是如何到应用程序的? +4. 实现一个简单的 WSGI Server +5. 实现“高并发”的WSGI Server +6. 第一次路由:PasteDeploy +7. PasteDeploy 使用说明 +8. webob.dec.wsgify 装饰器 +9. 第二次路由:中间件 routes 路由 ## 3.6.1 WSGI 是什么,因何而生? @@ -53,7 +54,33 @@ WSGI 对于 application 对象有如下三点要求 2. 接收两个必选参数environ、start_response。 3. 返回值必须是可迭代对象,用来表示http body。 -## 3.6.2 HTTP请求是如何到应用程序的? + + +## 3.6.2 为什么要有 WSGI? + +这是来自我的知乎专栏一个朋友在评论区的一个问题,我觉得问得很好,所以来答一下,更新在这里。 + +> 请教下用wsgi协议的地方为何不直接用http?为什么要翻译一次? + +**以下是我的回答,个人理解,仅供交流。** + +web框架(即app)在生产中一般不用于直接接收http请求。 + +你可能会说,django不就可以直接接收http请求吗,也不需要uwsgi之类的所谓的服务器。 + +其实不是,django只是在其内部自己实现了一个简易的web服务器,以供开发调试之用。所以初学者往往会误以为,web app框架本身就可以接收http请求。 + +web 服务器 和 web 框架,分工不同,职责不同(web 服务器专注于接收并解析请求以调用的方式将请求的内容传web框架),缺一不可,可以说它们是两个组件,共同协作才能实现web网页的访问,既然是两个组件,那总要定义一些约定俗成的通讯协议,而这就是WSGI,所以必须有WSGI。 + +那接下来,就引出另一个问题了:如果它们不分开,而将二者整合在一起,对外只有一个组件,是不是就没有WSGI什么事了? + +答案,是的。 + +但是你也可以发现目前市场上有相当多的大大小小的web开发框架,如果每个框架都去自己实现web服务器,那岂不是重复造轮子? + +最好的情况应该是,由专业的团队去开发专业的web服务器,而开发出来的web服务器需要具备框架通用性,Django可以用,Flask也可以用,开发者也可以自由选择用哪个web 服务器软件,用哪个web 框架,灵活组合。 + +## 3.6.3 HTTP请求是如何到应用程序的? 当客户端发出一个 HTTP 请求后,是如何转到我们的应用程序处理并返回的呢? @@ -75,7 +102,7 @@ WSGI 对于 application 对象有如下三点要求 nginx可以做负载均衡(前提是有多个服务器),保护了实际的web服务器(客户端是和nginx交互而不是uWSGI) -## 3.6.3 实现一个简单的 WSGI Server +## 3.6.4 实现一个简单的 WSGI Server 在上面的架构图里,不知道你发现没有,有个库叫做 `wsgiref` ,它是 Python 自带的一个 wsgi 服务器模块。 @@ -100,7 +127,7 @@ server.serve_forever() 以上使用 wsgiref 写了一个demo,让你对wsgi有个初步的了解。其由于只适合在学习测试使用,在生产环境中应该另寻他道。 -## 3.6.4 实现“高并发”的 WSGI Server +## 3.6.5 实现“高并发”的 WSGI Server 上面我们说不能在生产中使用 wsgiref ,那在生产中应该使用什么呢?选择有挺多的,比如优秀的 uWSGI,Gunicore等。但是今天我并不准备讲这些,一是因为我不怎么熟悉,二是因为我本人从事 OpenStack 的二次开发,对它比较熟悉。 @@ -169,7 +196,7 @@ self._server = utils.spawn(**wsgi_kwargs) 就这样,nova 开启了一个可以接受1000个绿色协程并发的 WSGI Server。 -## 3.6.5 第一次路由:PasteDeploy +## 3.6.6 第一次路由:PasteDeploy 上面我们提到 WSGI Server 的创建要传入一个 Application,用来处理接收到的请求,对于一个有多个 app 的项目。 @@ -224,7 +251,7 @@ paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory 这是 OpenStack 使用 PasteDeploy 实现的第一层的路由,如果你不感兴趣,可以直接略过本节,进入下一节,下一节是 介绍 PasteDeploy 的使用,教你实现一个简易的web server demo。推荐一定要看。 -## 3.6.6 PasteDeploy 使用说明 +## 3.6.7 PasteDeploy 使用说明 到上一步,我已经得到了 application 的有用的线索。考虑到很多人是第一次接触 PasteDeploy,所以这里结合网上博客做了下总结。对你入门会有帮助。 @@ -431,7 +458,7 @@ if __name__ == "__main__": 到此,你学会了使用 PasteDeploy 的简单使用。 -## 3.6.7 webob.dec.wsgify 装饰器 +## 3.6.8 webob.dec.wsgify 装饰器 经过了 PasteDeploy 的路由调度,我们找到了 `nova.api.openstack.compute:APIRouterV21.factory` 这个 application 的入口,看代码知道它其实返回了 APIRouterV21 类的一个实例。 @@ -464,7 +491,7 @@ APIRouterV21 本身没有实现 `__call__` ,但它的父类 Router实现了 `_ 带着这个问题,我们了解下 routes 是如何为我们实现第二次路由。 -## 3.6.8 第二次路由:中间件 routes 路由 +## 3.6.9 第二次路由:中间件 routes 路由 在文章最开始处,我们给大家画了一张图。 @@ -728,7 +755,7 @@ meth :`__ - `nova-api源码分析(APP的调用) `__ From cde2f76ff37901258f8752252f37ea858e142f7b Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 29 Jun 2019 23:48:11 +0800 Subject: [PATCH 048/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0pycharm=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=8A=80=E5=B7=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_15.md | 110 ++++++++++++++++++++++++++++++- source/c04/c04_15.rst | 148 +++++++++++++++++++++++++++++++++++++++++- source/c04/c04_18.md | 6 ++ source/c04/c04_18.rst | 7 ++ 4 files changed, 268 insertions(+), 3 deletions(-) diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index 50bceec..ea941ad 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -536,7 +536,7 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 ![](http://image.python-online.cn/20190616234038.png) -## 4.15.20 利用 TODO 解救“中年痴呆” +## 4.15.20 TODO 解救“中年痴呆” 一个程序员,如果能够一天都只和代码打交道,是一件多么难得的事情。 @@ -566,13 +566,121 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 ![](http://image.python-online.cn/20190616232527.png) +## 4.15.21 随处折叠,实现代码自由 +PyCharm 里代码块的折叠功能,相当的显眼,在代码编辑框的左侧,你可以发现有 `+` 也有 `-`,很容易理解 `+` 代表代码块被折叠了,而 `-` 代表这个代码块处理未折叠状态,你可以用鼠标点击折叠。 +如果你和我一样是个键盘党,你可以使用快捷(Mac:按住Command键,再按`+`或者`-` )进行快速反折叠/折叠。 +![](http://image.python-online.cn/20190629183430.png) +代码块的折叠和反折叠,应该是一个代码编辑器的基本功能。在这一点上, PyCharm 做为一个 IDE,在这一点上势必要做得更出色,事实证明,它做到了。 +从上面,我们知道只有代码块才支持缩放,那什么样的代码PyCharm才会认为是一个代码块呢?其严格地定义我没有找到,从经验来看,一个类,一个函数,一个for循环,一个while循环,一个多行注释等都是代码块。都可以进行折叠、反折叠。 +有时候,我们并不希望整块代码进行折叠,而只想对其他一大段暂时对我们无用的代码进行折叠。那能做到吗? +答案是可以的。 + +只要你先选中你想折叠的代码,再按住 Command 紧接着按住 `.` 就可以了。效果如下: + +![](https://i.loli.net/2019/06/29/5d17589c1603755790.gif) + +(GIF动态只播放两次,重播请刷新页面) + +## 4.15.22 重构操作,一步到位 + +最近有一位同事走了,由我来接手他的全部工作。 + +可能由于我有代码洁癖,我花了一个星期对其代码进行了大量的重构。 + +重构代码,免不了要对变量进行重命名。 + +如果一个一个改,显然不太智能,要知道我们是在用IDE,你也许会说,用搜索全部替换不就行了?还真不行。 + +比如下面这段代码,我只想改myfun 里的的test_name,而对于全局下的同名变量是不应该修改的。如果你全局替换,就会有误伤。 + +![](http://image.python-online.cn/20190629211910.png) + +这时候,我们如何做呢? + +可以使用 PyCharm 的 Refactor 功能,它会自动匹配作用域,既做到批量更改,也做到不误伤。 + +操作方法很简单,先选中你的变量,然后使用快捷键 Shift+F6,就可以直接重命名了。 + +![](https://i.loli.net/2019/06/29/5d1764b94d11128912.gif) + +(GIF动态只播放两次,重播请刷新页面) + +## 4.15.23 复杂操作,录制成宏 + +如果你在使用PyCharm 的时候,遇到有一些操作是比较复杂(步骤多),且使用频率特别高。 + +那可以考虑一下,使用其自带的宏录制工具。 + +它会将你的一连串操作,录制下来。等你想用的时候,直接调用就行了。 + +这边,我以录制一个 `删除函数` 的宏为例:先按上面的方法折叠函数,再按 Command+y 删除该行,就删除了该函数。 + +做录制方法如下: + +![](https://i.loli.net/2019/06/29/5d176e9ba92e916696.gif) + +(GIF动态只播放两次,重播请刷新页面) + +录制好后,你可以先定位到你要删除的函数处,点菜单栏 Edit - Macro 然后选择我们刚刚录制的宏,就可以播放宏了。 + +这样播放宏显得有点繁琐,个人建议你为这个宏定义一个快捷键,这样会更方便播放宏。 + +![](http://image.python-online.cn/20190629221224.png) + +设置快捷键时,注意不要和已有的快捷键冲突。 + +设置好后,查看 Macro,发现PyCharm已经将这个快捷键绑定给这个宏。 + +![](http://image.python-online.cn/20190629221547.png) + +之后你就可以使用这个快捷键删除一个函数(其实这只是删除一个代码块,但是这里只讨论设置方法)。 + +## 4.15.24 多行标签页,一览无余 + +PyCharm 打开一个文件,就占用一个标签面。 + +你有没有发现,不知不觉地,打开的文件越来越多,多到一行标签都装不下,装不下的标签页 PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文件。 + +![](http://image.python-online.cn/20190629223534.png) + +点击数字5,你才可以查看隐藏了哪些文件。 + +这时你肯定会说,一行装不下 PyCharm 为什么不能多行显示呢? + +答案是,不是不能,而是需要你设置。 + +如下图,将单行显示取消勾选即可。 + +![](http://image.python-online.cn/20190629224229.png) + +设置完后,有哪些文件就一览无余了。 + +![](http://image.python-online.cn/20190629224430.png) + +## 4.15.25 应用搜索,阅读源码必备 + +你平时若有阅读框架源码的需求或习惯,那你一定要来掌握这个技能。 + +在阅读源码时,代码的入口、流程、走向特别重要。 + +假设你现在知道了一个特别关键的类,你特别想知道是哪里调用了这个类,你想知道源头,知道整个调用流程。 + +这个时候你使用全局搜索,就会有很多的干扰信息。 + +这个时候,你急需有一种方法,可以快速给你列出有哪些地方调用了这个类。 + +快捷键:Mac:Command+Option+F7 ,Windows:Ctrl+Alt+F7 + +如下图所示,按下快捷键后可以很轻松地看见调用列表。 + +![](http://image.python-online.cn/20190629231322.png) diff --git a/source/c04/c04_15.rst b/source/c04/c04_15.rst index 76fbbb2..23d33f7 100644 --- a/source/c04/c04_15.rst +++ b/source/c04/c04_15.rst @@ -645,8 +645,8 @@ OpenStack |image61| -4.15.20 利用 TODO 解救“中年痴呆” --------------------------------- +4.15.20 TODO 解救“中年痴呆” +--------------------------- 一个程序员,如果能够一天都只和代码打交道,是一件多么难得的事情。 @@ -681,6 +681,139 @@ TODO 面板。如果你是 Mac, 快捷键 是Command + 6,而 Windows 是 Alt |image63| +4.15.21 随处折叠,实现代码自由 +------------------------------ + +PyCharm 里代码块的折叠功能,相当的显眼,在代码编辑框的左侧,你可以发现有 +``+`` 也有 ``-``\ ,很容易理解 ``+`` 代表代码块被折叠了,而 ``-`` +代表这个代码块处理未折叠状态,你可以用鼠标点击折叠。 + +如果你和我一样是个键盘党,你可以使用快捷(Mac:按住Command键,再按\ ``+``\ 或者\ ``-`` +)进行快速反折叠/折叠。 + +|image64| + +代码块的折叠和反折叠,应该是一个代码编辑器的基本功能。在这一点上, +PyCharm 做为一个 IDE,在这一点上势必要做得更出色,事实证明,它做到了。 + +从上面,我们知道只有代码块才支持缩放,那什么样的代码PyCharm才会认为是一个代码块呢?其严格地定义我没有找到,从经验来看,一个类,一个函数,一个for循环,一个while循环,一个多行注释等都是代码块。都可以进行折叠、反折叠。 + +有时候,我们并不希望整块代码进行折叠,而只想对其他一大段暂时对我们无用的代码进行折叠。那能做到吗? + +答案是可以的。 + +只要你先选中你想折叠的代码,再按住 Command 紧接着按住 ``.`` +就可以了。效果如下: + +|image65| + +(GIF动态只播放两次,重播请刷新页面) + +4.15.22 重构操作,一步到位 +-------------------------- + +最近有一位同事走了,由我来接手他的全部工作。 + +可能由于我有代码洁癖,我花了一个星期对其代码进行了大量的重构。 + +重构代码,免不了要对变量进行重命名。 + +如果一个一个改,显然不太智能,要知道我们是在用IDE,你也许会说,用搜索全部替换不就行了?还真不行。 + +比如下面这段代码,我只想改myfun +里的的test_name,而对于全局下的同名变量是不应该修改的。如果你全局替换,就会有误伤。 + +|image66| + +这时候,我们如何做呢? + +可以使用 PyCharm 的 Refactor +功能,它会自动匹配作用域,既做到批量更改,也做到不误伤。 + +操作方法很简单,先选中你的变量,然后使用快捷键 +Shift+F6,就可以直接重命名了。 + +|image67| + +(GIF动态只播放两次,重播请刷新页面) + +4.15.23 复杂操作,录制成宏 +-------------------------- + +如果你在使用PyCharm +的时候,遇到有一些操作是比较复杂(步骤多),且使用频率特别高。 + +那可以考虑一下,使用其自带的宏录制工具。 + +它会将你的一连串操作,录制下来。等你想用的时候,直接调用就行了。 + +这边,我以录制一个 ``删除函数`` 的宏为例:先按上面的方法折叠函数,再按 +Command+y 删除该行,就删除了该函数。 + +做录制方法如下: + +|image68| + +(GIF动态只播放两次,重播请刷新页面) + +录制好后,你可以先定位到你要删除的函数处,点菜单栏 Edit - Macro +然后选择我们刚刚录制的宏,就可以播放宏了。 + +这样播放宏显得有点繁琐,个人建议你为这个宏定义一个快捷键,这样会更方便播放宏。 + +|image69| + +设置快捷键时,注意不要和已有的快捷键冲突。 + +设置好后,查看 Macro,发现PyCharm已经将这个快捷键绑定给这个宏。 + +|image70| + +之后你就可以使用这个快捷键删除一个函数(其实这只是删除一个代码块,但是这里只讨论设置方法)。 + +4.15.24 多行标签页,一览无余 +---------------------------- + +PyCharm 打开一个文件,就占用一个标签面。 + +你有没有发现,不知不觉地,打开的文件越来越多,多到一行标签都装不下,装不下的标签页 +PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文件。 + +|image71| + +点击数字5,你才可以查看隐藏了哪些文件。 + +这时你肯定会说,一行装不下 PyCharm 为什么不能多行显示呢? + +答案是,不是不能,而是需要你设置。 + +如下图,将单行显示取消勾选即可。 + +|image72| + +设置完后,有哪些文件就一览无余了。 + +|image73| + +4.15.25 应用搜索,阅读源码必备 +------------------------------ + +你平时若有阅读框架源码的需求或习惯,那你一定要来掌握这个技能。 + +在阅读源码时,代码的入口、流程、走向特别重要。 + +假设你现在知道了一个特别关键的类,你特别想知道是哪里调用了这个类,你想知道源头,知道整个调用流程。 + +这个时候你使用全局搜索,就会有很多的干扰信息。 + +这个时候,你急需有一种方法,可以快速给你列出有哪些地方调用了这个类。 + +快捷键:Mac:Command+Option+F7 ,Windows:Ctrl+Alt+F7 + +如下图所示,按下快捷键后可以很轻松地看见调用列表。 + +|image74| + 附录 ---- @@ -760,4 +893,15 @@ TODO 面板。如果你是 Mac, 快捷键 是Command + 6,而 Windows 是 Alt .. |image61| image:: http://image.python-online.cn/20190616234038.png .. |image62| image:: http://image.python-online.cn/20190616231649.png .. |image63| image:: http://image.python-online.cn/20190616232527.png +.. |image64| image:: http://image.python-online.cn/20190629183430.png +.. |image65| image:: https://i.loli.net/2019/06/29/5d17589c1603755790.gif +.. |image66| image:: http://image.python-online.cn/20190629211910.png +.. |image67| image:: https://i.loli.net/2019/06/29/5d1764b94d11128912.gif +.. |image68| image:: https://i.loli.net/2019/06/29/5d176e9ba92e916696.gif +.. |image69| image:: http://image.python-online.cn/20190629221224.png +.. |image70| image:: http://image.python-online.cn/20190629221547.png +.. |image71| image:: http://image.python-online.cn/20190629223534.png +.. |image72| image:: http://image.python-online.cn/20190629224229.png +.. |image73| image:: http://image.python-online.cn/20190629224430.png +.. |image74| image:: http://image.python-online.cn/20190629231322.png diff --git a/source/c04/c04_18.md b/source/c04/c04_18.md index 1680125..2cc9082 100644 --- a/source/c04/c04_18.md +++ b/source/c04/c04_18.md @@ -28,7 +28,13 @@ command + up # 文件夹后退 command + down # 文件夹前进 ``` +## 4.18.3 截图工具 +1.Xnip:取色+丰富的标注功能+滚动截屏; +2.Snip:小巧轻量+滚动截屏:; +3.QQ:截屏+录屏+OCR文字识别; +4.Snipaste:贴屏功能; +5.ScreenShot PSD:psd 文件,每种元素都有单独的图层。 diff --git a/source/c04/c04_18.rst b/source/c04/c04_18.rst index 82c67d1..953960c 100644 --- a/source/c04/c04_18.rst +++ b/source/c04/c04_18.rst @@ -31,6 +31,13 @@ command + up # 文件夹后退 command + down # 文件夹前进 +4.18.3 截图工具 +--------------- + +1.Xnip:取色+丰富的标注功能+滚动截屏; 2.Snip:小巧轻量+滚动截屏:; +3.QQ:截屏+录屏+OCR文字识别; 4.Snipaste:贴屏功能; 5.ScreenShot +PSD:psd 文件,每种元素都有单独的图层。 + -------------- .. figure:: http://image.python-online.cn/20190511161447.png From 053e8de7d37a9ea6d7f3a35caa81a681241f65de Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 29 Jun 2019 23:48:49 +0800 Subject: [PATCH 049/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=A0=87=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_15.md | 2 +- source/c04/c04_15.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index ea941ad..7912ee0 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -1,4 +1,4 @@ -# 4.15 20个 PyCharm 小技巧 +# 4.15 25个 PyCharm 小技巧 刚开始做公众号的时候,更新频率正常是一周两到三篇。老读者应该有注意到,以前都是写系列教程,对于读者而言,系列教程会更加友好,学习起来会更容易深入浅出,而对于作者来说,写系列教程,更有一种使命感,而这种使命感是维持更新一大动力。 diff --git a/source/c04/c04_15.rst b/source/c04/c04_15.rst index 23d33f7..f873516 100644 --- a/source/c04/c04_15.rst +++ b/source/c04/c04_15.rst @@ -1,4 +1,4 @@ -4.15 20个 PyCharm 小技巧 +4.15 25个 PyCharm 小技巧 ======================== 刚开始做公众号的时候,更新频率正常是一周两到三篇。老读者应该有注意到,以前都是写系列教程,对于读者而言,系列教程会更加友好,学习起来会更容易深入浅出,而对于作者来说,写系列教程,更有一种使命感,而这种使命感是维持更新一大动力。 From dd41b714c135391b0ae73727cf003885761ec4cf Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 30 Jun 2019 17:34:13 +0800 Subject: [PATCH 050/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=8C=B4=E5=AD=90?= =?UTF-8?q?=E8=A1=A5=E4=B8=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_04.md | 48 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/source/c01/c01_04.md b/source/c01/c01_04.md index 99c9036..d4cc8e8 100644 --- a/source/c01/c01_04.md +++ b/source/c01/c01_04.md @@ -51,6 +51,54 @@ del pd.DataFrame.just_foo_cols # you can also remove the new method 还有就是gevent中也有用到。 +## 1.4.3 如何使用猴子补丁? + +为实例绑定对象(扩展\__slot__) + +```python +import types +import logging + + +def get_logger(): + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + handler = logging.FileHandler('monkey_patch.log') + handler.setLevel(logging.INFO) + + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + + logger.addHandler(handler) + return logger + + +LOG = get_logger() + + +def info(self, msg, *args, **kwargs): + print(msg) + + INFO = 20 + if self.isEnabledFor(INFO): + self._log(INFO, msg, args, **kwargs) + +# 重点 +LOG.info = types.MethodType(info, LOG) + +LOG.info('hello, world') +``` + +给函数添加装饰器 + +```python +func = importutils.import_class("%s.%s" % (module, key)) +setattr(sys.modules[module], key, + decorator("%s.%s" % (module, key), func)) +``` + + + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file From 882224ded8c4592ff8eb23d7c6e2e1064d3aa505 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 30 Jun 2019 17:34:44 +0800 Subject: [PATCH 051/302] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E3=80=8C=E9=9D=99?= =?UTF-8?q?=E6=80=81=E6=96=B9=E6=B3=95=E7=9A=84=E6=96=87=E7=AB=A0=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_20.md | 100 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 source/c01/c01_20.md diff --git a/source/c01/c01_20.md b/source/c01/c01_20.md new file mode 100644 index 0000000..0471920 --- /dev/null +++ b/source/c01/c01_20.md @@ -0,0 +1,100 @@ +# 1.20 静态方法其实暗藏玄机 + +这个标题「静态方法其实暗藏玄机」其实只说明了文章了一个知识点。 + +其实这本篇文章,主要讲了以下两个知识点。 + +1、Python2.x和3.x中,函数和方法的区分有什么不同? + +2、Python3.x 中,静态方法有几种写法? + +带着这两个问题,你可以在下文中寻找答案。 + +--- + +在 Python 2 中的函数和方法的区别,十分清晰,很好分辨。但在 Python3中,我却发现完全又是另一套准则。 + +首先先来 Python2 的(以下在 Python2.7中测试通过) + +![](http://image.python-online.cn/20190630111243.png) + +可以得出结论: + +1、普通函数(未定位在类里),都是函数。 + +2、静态方法(@staticmethod),都是函数。 + +3、类方法(@classmethod),都是方法。 + +4、实例方法(首参数为self且非静态、非类方法的),都是方法。 + +你一定想说,类方法和实例方法,是方法没错呀,毕竟名字本身就有方法,普通函数是函数,我也理解呀。那静态方法,为什么不是方法而是函数呢? + +名字只是一个外在的表面称呼,你能说「赵铁男」就一定是汉子吗? + +我的理解是:方法是一种和对象(实例或者类)绑定后的函数。 + +类方法的首参是`cls`,调用时,无需显示传入。实例方法首参是self,调用时,也无需显示传入。 + +而静态方法,其实和普通函数没啥区别,唯一的区别就是,他定义的位置被放在了类里,做为类或者实例的一个函数。 + +那你肯定又要问了,既然静态方法和普通函数都是一样的,为什么要刻意将它放在类里呢? + +我上面说了,放在类里定义,就可以让它成为类的一个工具函数,这就像你身上随身携带了一把刀,这把刀与你本人并没有什么直接关系,唯一的联系就是这把刀是你的,而你把它带在身上,无论你去到哪里,只要需要,你就可以直接拿出来用上。对比将函数放在类外面,缺点是什么呢?就是当你出门在外(在别的模块里)发现你要用刀的时候,还要特地跑一趟去商店买一把刀(import 这个函数)。 + +另外,我觉得静态方法在业务和设计上的意义,会更多一些。 + +一般静态方法是做为类或者实例的一个工具函数,比如对变量的做一个合法性的检验,对数据进行序列化反序列化操作等等。 + +说完了 Python2 ,再来说说Python3. + +以前我觉得 Python2 对于方法和函数的界线更加清晰,但接触了 Python3,我反而觉得Python3里方法和函数的区分似乎更加合理。 + +还是刚刚那段代码,我更改了解释器为Python3.6(以下在 Python3.6中测试通过) + +![](http://image.python-online.cn/20190630104956.png) + +和Python2的唯一区别是,`People.jump` 在Python3 中变成了函数。 + +这一下颠覆了你刚刚才建立起来的知识体系有木有? + +先别急,我再做个试验,也许你就知道了。 + +**在 Python2中** + +执行People.jump('hello'),会报错说,jump的首参必须为People的实例对象,这可以理解,毕竟jump定义时,第一个参数为self。 + +![](http://image.python-online.cn/20190630105735.png) + +**在 Python3中** + +你可以发现,这里的jump的首参不再要求是 People 的一个实例,而可以是任意的对象,比如我使用字符串对象,也没有报错。 + +![](http://image.python-online.cn/20190630105600.png) + +也就是说,当你往jump中传入的首参为People的实例时,jump 就是方法,而当你传入的首参不是People的实例对象时,jump就是函数。 + +你看,多么灵活呀。 + +再总结一下,在Python3中: + +1、普通函数(未定位在类里),都是函数。 + +2、静态方法(@staticmethod),都是函数。 + +3、类方法(@classmethod),都是方法。 + +4、方法和函数区分没有那么明确,而是更加灵活了,一个函数有可能是方法也有可能是函数。 + +你肯定又要问了,那这是不是就意味着,Python3 中静态方法,可以不用再使用@staticmethod 装饰了呢,反正Python3都可以识别。 + +这是个好问题,是的,可以不用指定,但是最好指定,如果你不指定,你调用这个方法只能通过People.jump,而不能通过 self.jump了,因为首参不是 self,而如果使用@staticmethod 就可以使用self.jump。 + +所以说这是一个规范,就像类的私有方法,规范要求外部最好不要调用,但这不是强制要求,不是说外部就不能调用。 + +写这篇文章的起源,是前两天有位读者在交流里问到了相关的问题,正好没什么主题可以写,就拿过来做为素材整理一下,也正好没有写过静态方法、类方法的内容,没想到简单的东西,也能写出这么多的内容出来。 + + + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) + From 07a460a6b1d65268de10b2876cc5d25ac9fb84a3 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 30 Jun 2019 17:34:58 +0800 Subject: [PATCH 052/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0rpc=E7=9A=84=E6=96=87?= =?UTF-8?q?=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_09.md | 66 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/source/c08/c08_09.md b/source/c08/c08_09.md index 020ea14..88ba838 100644 --- a/source/c08/c08_09.md +++ b/source/c08/c08_09.md @@ -371,9 +371,9 @@ AMQP(Advanced Message Queuing Protocol)是一种基于队列的可靠消息服 - binding :binding是用来描述exchange和queue之间的关系的概念,一个exchang可以绑定多个队列,这些关系由binding建立。前面说的binding key /binding pattern也是在binding中给出。 -在网上找了个图,可以很清晰地描述几个名词的关系。 +为了让你明白这几者的关系,我画了一张模型图。 -![](http://image.python-online.cn/20190623205052.png) +![](http://image.python-online.cn/20190630160025.png) 关于AMQP,有几下几点值得注意: @@ -381,6 +381,67 @@ AMQP(Advanced Message Queuing Protocol)是一种基于队列的可靠消息服 2. 一个队列可以有多个receiver,队列里的一个消息只能发给一个receiver。 3. 一个消息可以被发送到一个队列中,也可以被发送到多个多列中。多队列情况下,一个消息可以被多个receiver收到并处理。Openstack RPC中这两种情况都会用到。 +在 Python 中如何发送消息,并接收消息呢? + +这边写个demo + +首先是生产者 + +```python +# coding:utf-8 +import sys,time +import pika + + +credentials = pika.PlainCredentials('account', 'password') + +def productor(): + connection = pika.BlockingConnection( + pika.ConnectionParameters('ctrl.openstack.com',5672,'/',credentials))#建立一个最基本的socket + chanel = connection.channel()#声明一个管道 + + chanel.queue_declare(queue='name')#给管道创建一个队列,参数是管道队列名。 + + chanel.basic_publish(exchange='', + routing_key='name', + body ='Hello World!')#要发送的消息。 + connection.close() +``` + +再者是消息费 + +```python +# coding:utf-8 +import sys,time +import pika + + +credentials = pika.PlainCredentials('account', 'password') + +def consumer(): + consumer = pika.BlockingConnection\ + (pika.ConnectionParameters('ctrl.openstack.com',5672,'/',credentials))#创建socket连接 + channel = consumer.channel()#创建管道 + channel.queue_declare(queue='name') + + def backcall(ch,method,properties,body):#参数body是发送过来的消息。 + print '已接收到消息!' + sys.exit() + +#backcall 回调函数 执行结束后立即执行另外一个函数返回给发送端是否执行完毕。 +#no_ack=True 不会告知服务端我是否收到消息。一般注释。 + + channel.basic_consume(backcall, + queue='name', + no_ack=True + )#如果注释掉,对方没有收到消息的话不会将消息丢失,始终在队列里等待下次发送。 + + + channel.start_consuming()#启动后进入死循环。一直等待消息。 +``` + + + ## 8.9.5 OpenStack中如何使用RPC? 前面铺垫了那么久,终于到了讲真实应用的场景。在生产中RPC是如何应用的呢? @@ -611,6 +672,7 @@ server.wait() - [ython中使用XMLRPC(入门)](https://www.cnblogs.com/lxt287994374/p/3904219.html) - [(译) JSON-RPC 2.0 规范(中文版)](https://colobu.com/2016/09/05/benchmarks-of-popular-rpc-frameworks/) - [nova event机制分析](https://blog.csdn.net/epugv/article/details/44872583) +- [RabbitMQ 三种Exchange](https://melin.iteye.com/blog/691265) --- From 19bbcf45de0658bd55b2f38cec214c014cf162df Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 30 Jun 2019 17:35:20 +0800 Subject: [PATCH 053/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20pycharm=20?= =?UTF-8?q?=E6=8A=80=E5=B7=A7=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_15.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index 7912ee0..53faab9 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -568,9 +568,9 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 ## 4.15.21 随处折叠,实现代码自由 -PyCharm 里代码块的折叠功能,相当的显眼,在代码编辑框的左侧,你可以发现有 `+` 也有 `-`,很容易理解 `+` 代表代码块被折叠了,而 `-` 代表这个代码块处理未折叠状态,你可以用鼠标点击折叠。 +PyCharm 里代码块的折叠功能,相当的显眼,在代码编辑框的左侧,你可以发现有 `+` 也有 `-`,很容易理解 `+` 代表代码块被折叠了可以点此展开,而 `-` 代表这个代码块处于展开状态可以点此折叠。 -如果你和我一样是个键盘党,你可以使用快捷(Mac:按住Command键,再按`+`或者`-` )进行快速反折叠/折叠。 +如果你和我一样是个键盘党,你可以使用快捷(Mac:按住Command键,再按`+`或者`-` ,Windows:按住Ctrl键,再按`+`或者`-` )进行快速反折叠/折叠。 ![](http://image.python-online.cn/20190629183430.png) @@ -660,7 +660,7 @@ PyCharm 打开一个文件,就占用一个标签面。 ![](http://image.python-online.cn/20190629224229.png) -设置完后,有哪些文件就一览无余了。 +设置完后,有哪些文件就非常清晰了。 ![](http://image.python-online.cn/20190629224430.png) From 7ad6f833ddab6518143529d424122cc0c45a626a Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Thu, 4 Jul 2019 20:59:12 +0800 Subject: [PATCH 054/302] update --- source/c02/c02_03.md | 3 +- source/c02/c02_08.md | 2 +- source/c02/c02_09.md | 2 +- source/c04/c04_15.md | 2 +- source/c08/c08_01.md | 5 ++ source/c08/c08_05.md | 148 +++++++++++++++++++++++++++++++++++++++++++ source/thanks.rst | 29 +++++++++ 7 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 source/thanks.rst diff --git a/source/c02/c02_03.md b/source/c02/c02_03.md index c4eacd1..9453b44 100644 --- a/source/c02/c02_03.md +++ b/source/c02/c02_03.md @@ -184,9 +184,8 @@ t1.start() 是因为,第二次获取锁时,发现锁已经被同一线程的人拿走了。自己也就理所当然,拿不到锁,程序就卡住了。 那么如何解决这个问题呢。 - -threading模块除了提供Lock锁之外,还提供了一种可重入锁RLock,专门来处理这个问题。 `threading`模块除了提供`Lock`锁之外,还提供了一种可重入锁`RLock`,专门来处理这个问题。 + ```python import threading diff --git a/source/c02/c02_08.md b/source/c02/c02_08.md index eedaee7..2429c3c 100644 --- a/source/c02/c02_08.md +++ b/source/c02/c02_08.md @@ -75,7 +75,7 @@ def gen(*args, **kw): for i in item: yield i -new_list=gen(astr, alist, adict, agen) +new_list=gen(astr, alist, adict, agen) print(list(new_list)) # ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7] ``` diff --git a/source/c02/c02_09.md b/source/c02/c02_09.md index a068cca..1dcc6fd 100644 --- a/source/c02/c02_09.md +++ b/source/c02/c02_09.md @@ -114,7 +114,7 @@ Hello, World 前面我们说,`await`用于挂起阻塞的异步调用接口。其作用在`一定程度上`类似于yield。 -注意这里是,一定程度上,意思是效果上一样(都能实现暂停的效果),但是功能上却不兼容。就是你不能在生成器中使用`await`,也不能在async 定义的协程中使用`yield`。 +注意这里是,一定程度上,意思是效果上一样(都能实现暂停的效果),但是功能上却不兼容。就是你不能在生成器中使用`await`,也不能在async 定义的协程中使用`yield from`。 小明不是胡说八道的。有实锤。 ![普通函数中 不能使用 await](https://i.loli.net/2018/05/26/5b09794f45340.png) diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index 53faab9..bf1811d 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -682,7 +682,7 @@ PyCharm 打开一个文件,就占用一个标签面。 ![](http://image.python-online.cn/20190629231322.png) - +如果你嫌这快捷键太长了,可以使用 `鼠标中键` 点击这个类,可以达到同样的效果。 ## 附录 diff --git a/source/c08/c08_01.md b/source/c08/c08_01.md index 2b2869e..2c11b0b 100644 --- a/source/c08/c08_01.md +++ b/source/c08/c08_01.md @@ -145,6 +145,11 @@ $ neutron net-list # 查看端口占用情况 $ neutron port-list + +# 指定 mac 创建port +neutron port-create --tenant-id 100001 --fixed-ip ip_address=192.168.0.22 --mac-address fa:16:3e:3a:e8:1b + +nova interface-attach b0cc47bc-25c3-48ca-a4fd-5523326b515a --port-id 8bcba4eb-ade0-403d-8f13-45ed70936f03 ``` ### 1.3 Glance diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index b37b5fd..55e1301 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -349,6 +349,154 @@ nova-api 返回的结果令人无法理解: ![](http://image.python-online.cn/20190529215825.png) +## 8.5.14 HTTP 状态码 + +在 标准库 WebOB 中 + +``` +Exception + HTTPException + HTTPOk + * 200 - :class:`HTTPOk` + * 201 - :class:`HTTPCreated` + * 202 - :class:`HTTPAccepted` + * 203 - :class:`HTTPNonAuthoritativeInformation` + * 204 - :class:`HTTPNoContent` + * 205 - :class:`HTTPResetContent` + * 206 - :class:`HTTPPartialContent` + HTTPRedirection + * 300 - :class:`HTTPMultipleChoices` + * 301 - :class:`HTTPMovedPermanently` + * 302 - :class:`HTTPFound` + * 303 - :class:`HTTPSeeOther` + * 304 - :class:`HTTPNotModified` + * 305 - :class:`HTTPUseProxy` + * 307 - :class:`HTTPTemporaryRedirect` + * 308 - :class:`HTTPPermanentRedirect` + HTTPError + HTTPClientError + * 400 - :class:`HTTPBadRequest` + * 401 - :class:`HTTPUnauthorized` + * 402 - :class:`HTTPPaymentRequired` + * 403 - :class:`HTTPForbidden` + * 404 - :class:`HTTPNotFound` + * 405 - :class:`HTTPMethodNotAllowed` + * 406 - :class:`HTTPNotAcceptable` + * 407 - :class:`HTTPProxyAuthenticationRequired` + * 408 - :class:`HTTPRequestTimeout` + * 409 - :class:`HTTPConflict` + * 410 - :class:`HTTPGone` + * 411 - :class:`HTTPLengthRequired` + * 412 - :class:`HTTPPreconditionFailed` + * 413 - :class:`HTTPRequestEntityTooLarge` + * 414 - :class:`HTTPRequestURITooLong` + * 415 - :class:`HTTPUnsupportedMediaType` + * 416 - :class:`HTTPRequestRangeNotSatisfiable` + * 417 - :class:`HTTPExpectationFailed` + * 422 - :class:`HTTPUnprocessableEntity` + * 423 - :class:`HTTPLocked` + * 424 - :class:`HTTPFailedDependency` + * 428 - :class:`HTTPPreconditionRequired` + * 429 - :class:`HTTPTooManyRequests` + * 431 - :class:`HTTPRequestHeaderFieldsTooLarge` + * 451 - :class:`HTTPUnavailableForLegalReasons` + HTTPServerError + * 500 - :class:`HTTPInternalServerError` + * 501 - :class:`HTTPNotImplemented` + * 502 - :class:`HTTPBadGateway` + * 503 - :class:`HTTPServiceUnavailable` + * 504 - :class:`HTTPGatewayTimeout` + * 505 - :class:`HTTPVersionNotSupported` + * 511 - :class:`HTTPNetworkAuthenticationRequired` +``` + + + +## 8.5.15 不同存储方式xml + +lvm + +```xml + + + + +
+ +``` + +qcow2 + +```xml + + + + +
+ +``` + +config(configdrive) + +```xml + + + + + +
+ +``` + +isolate + +```xml + + + + +
+ +``` + + + +其他都好理解,这个qcow2的类型,有一点点的不一样。 + +他是先创建一个在LVM存储池中,创建一个LV。 + +然后为这个LV,创建一个软连接,通过 `df -Th` 可以看到 这个LV 挂载到 一个目录下。这个目录下有一个名为 disk 的qcow2文件,而这个qcow2 文件的backing file 是指向一个 base 镜像文件(raw格式)。 + +![](http://image.python-online.cn/20190627213044.png) + +## 8.5.16 独立磁盘与LVM + +**独立磁盘** + +优点:虚拟机磁盘互不影响,IO不共享,不会因为一块物理盘挂了而影响所有的虚拟机。 + +缺点:无法像 LVM 存储池那样,做到精准而灵活的资源分配,有可能造成资源浪费或资源不足。 + +![](http://image.python-online.cn/20190627213609.png) + +**LVM** + +优点:可以根据业务或者其他条件(如介质类型),对多块物理盘组合成一个VG,实现精准而灵活的资源分配 + +缺点:不同虚拟机之间,磁盘IO共享,不同客户之间的性能会出现争抢的情况;稳定性不好,如果一块盘坏了,有可能有多个虚拟机受到影响。 + +## 8.5.17 主机组的使用 + +在 flavor 上有一个 extra_spec 可以设置多个参数,nova 本身自带了许多,当然这个参数可以自定义。 + +这个参数有什么用呢,需要搭配主机组使用。 + +只要在主机组上设置的metadata 的 key-value 和 extra_spec 的key-value一样就可以实现宿主机的过滤。 + +![](http://image.python-online.cn/20190627215038.png) + + + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/thanks.rst b/source/thanks.rst new file mode 100644 index 0000000..e5cb76f --- /dev/null +++ b/source/thanks.rst @@ -0,0 +1,29 @@ +================================== +贡献者名单 +================================== + + +自 2018 年初建立此博客开始,到现在越来越的人知道了这个网站。 + +浏览的人多了,对我而言是一种正向激励,鼓励我写出更多的文章。但同时,这些人也能对我进行监督,对于早期写出的文章,由于急于发表于个从公众号(Python编程时光),对于诸多细节没法一一兼顾,比如错别字、表述不当等 + +有不少的朋友还专门加我微信,给我指出不足,帮助我提高文章内容质量。 + +所以在这里准备开一个页面,感谢这些朋友。另外,对文章有任何疑问的同学都可以加我微信交流。 + +.. figure:: ![](http://image.python-online.cn/20190704205721.png) + :alt: 添加明哥好友 + + +---------------------------------- +贡献历史 +---------------------------------- + +2019.7.4 +@魏朝阳:提出在《并发编程》中三处笔误,现已更正! + + +------------------------------ + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注明哥公众号 From 28b23a903f49af8346c1550d309280bf2c9193ca Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Thu, 4 Jul 2019 22:15:07 +0800 Subject: [PATCH 055/302] update rst --- source/c01/c01_04.rst | 47 +++++++++++++ source/c01/c01_20.rst | 116 +++++++++++++++++++++++++++++++ source/c02/c02_03.rst | 2 - source/c02/c02_08.rst | 2 +- source/c02/c02_09.rst | 2 +- source/c04/c04_15.rst | 11 +-- source/c08/c08_01.rst | 5 ++ source/c08/c08_05.rst | 154 ++++++++++++++++++++++++++++++++++++++++++ source/c08/c08_09.rst | 64 +++++++++++++++++- 9 files changed, 393 insertions(+), 10 deletions(-) create mode 100644 source/c01/c01_20.rst diff --git a/source/c01/c01_04.rst b/source/c01/c01_04.rst index 7fab61d..5fce18c 100755 --- a/source/c01/c01_04.rst +++ b/source/c01/c01_04.rst @@ -66,6 +66,53 @@ 还有就是gevent中也有用到。 +1.4.3 如何使用猴子补丁? +------------------------ + +为实例绑定对象(扩展__slot__) + +.. code:: python + + import types + import logging + + + def get_logger(): + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + handler = logging.FileHandler('monkey_patch.log') + handler.setLevel(logging.INFO) + + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + + logger.addHandler(handler) + return logger + + + LOG = get_logger() + + + def info(self, msg, *args, **kwargs): + print(msg) + + INFO = 20 + if self.isEnabledFor(INFO): + self._log(INFO, msg, args, **kwargs) + + # 重点 + LOG.info = types.MethodType(info, LOG) + + LOG.info('hello, world') + +给函数添加装饰器 + +.. code:: python + + func = importutils.import_class("%s.%s" % (module, key)) + setattr(sys.modules[module], key, + decorator("%s.%s" % (module, key), func)) + -------------- .. figure:: http://image.python-online.cn/20190511161447.png diff --git a/source/c01/c01_20.rst b/source/c01/c01_20.rst new file mode 100644 index 0000000..ce99437 --- /dev/null +++ b/source/c01/c01_20.rst @@ -0,0 +1,116 @@ +1.20 静态方法其实暗藏玄机 +========================= + +这个标题「静态方法其实暗藏玄机」其实只说明了文章了一个知识点。 + +其实这本篇文章,主要讲了以下两个知识点。 + +1、Python2.x和3.x中,函数和方法的区分有什么不同? + +2、Python3.x 中,静态方法有几种写法? + +带着这两个问题,你可以在下文中寻找答案。 + +-------------- + +在 Python 2 中的函数和方法的区别,十分清晰,很好分辨。但在 +Python3中,我却发现完全又是另一套准则。 + +首先先来 Python2 的(以下在 Python2.7中测试通过) + +|image0| + +可以得出结论: + +1、普通函数(未定位在类里),都是函数。 + +2、静态方法(@staticmethod),都是函数。 + +3、类方法(@classmethod),都是方法。 + +4、实例方法(首参数为self且非静态、非类方法的),都是方法。 + +你一定想说,类方法和实例方法,是方法没错呀,毕竟名字本身就有方法,普通函数是函数,我也理解呀。那静态方法,为什么不是方法而是函数呢? + +名字只是一个外在的表面称呼,你能说「赵铁男」就一定是汉子吗? + +我的理解是:方法是一种和对象(实例或者类)绑定后的函数。 + +类方法的首参是\ ``cls``\ ,调用时,无需显示传入。实例方法首参是self,调用时,也无需显示传入。 + +而静态方法,其实和普通函数没啥区别,唯一的区别就是,他定义的位置被放在了类里,做为类或者实例的一个函数。 + +那你肯定又要问了,既然静态方法和普通函数都是一样的,为什么要刻意将它放在类里呢? + +我上面说了,放在类里定义,就可以让它成为类的一个工具函数,这就像你身上随身携带了一把刀,这把刀与你本人并没有什么直接关系,唯一的联系就是这把刀是你的,而你把它带在身上,无论你去到哪里,只要需要,你就可以直接拿出来用上。对比将函数放在类外面,缺点是什么呢?就是当你出门在外(在别的模块里)发现你要用刀的时候,还要特地跑一趟去商店买一把刀(import +这个函数)。 + +另外,我觉得静态方法在业务和设计上的意义,会更多一些。 + +一般静态方法是做为类或者实例的一个工具函数,比如对变量的做一个合法性的检验,对数据进行序列化反序列化操作等等。 + +说完了 Python2 ,再来说说Python3. + +以前我觉得 Python2 对于方法和函数的界线更加清晰,但接触了 +Python3,我反而觉得Python3里方法和函数的区分似乎更加合理。 + +还是刚刚那段代码,我更改了解释器为Python3.6(以下在 +Python3.6中测试通过) + +|image1| + +和Python2的唯一区别是,\ ``People.jump`` 在Python3 中变成了函数。 + +这一下颠覆了你刚刚才建立起来的知识体系有木有? + +先别急,我再做个试验,也许你就知道了。 + +**在 Python2中** + +执行People.jump(‘hello’),会报错说,jump的首参必须为People的实例对象,这可以理解,毕竟jump定义时,第一个参数为self。 + +|image2| + +**在 Python3中** + +你可以发现,这里的jump的首参不再要求是 People +的一个实例,而可以是任意的对象,比如我使用字符串对象,也没有报错。 + +|image3| + +也就是说,当你往jump中传入的首参为People的实例时,jump +就是方法,而当你传入的首参不是People的实例对象时,jump就是函数。 + +你看,多么灵活呀。 + +再总结一下,在Python3中: + +1、普通函数(未定位在类里),都是函数。 + +2、静态方法(@staticmethod),都是函数。 + +3、类方法(@classmethod),都是方法。 + +4、方法和函数区分没有那么明确,而是更加灵活了,一个函数有可能是方法也有可能是函数。 + +你肯定又要问了,那这是不是就意味着,Python3 +中静态方法,可以不用再使用@staticmethod +装饰了呢,反正Python3都可以识别。 + +这是个好问题,是的,可以不用指定,但是最好指定,如果你不指定,你调用这个方法只能通过People.jump,而不能通过 +self.jump了,因为首参不是 self,而如果使用@staticmethod +就可以使用self.jump。 + +所以说这是一个规范,就像类的私有方法,规范要求外部最好不要调用,但这不是强制要求,不是说外部就不能调用。 + +写这篇文章的起源,是前两天有位读者在交流里问到了相关的问题,正好没什么主题可以写,就拿过来做为素材整理一下,也正好没有写过静态方法、类方法的内容,没想到简单的东西,也能写出这么多的内容出来。 + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + + +.. |image0| image:: http://image.python-online.cn/20190630111243.png +.. |image1| image:: http://image.python-online.cn/20190630104956.png +.. |image2| image:: http://image.python-online.cn/20190630105735.png +.. |image3| image:: http://image.python-online.cn/20190630105600.png + diff --git a/source/c02/c02_03.rst b/source/c02/c02_03.rst index 5b549a6..ef2226c 100755 --- a/source/c02/c02_03.rst +++ b/source/c02/c02_03.rst @@ -204,8 +204,6 @@ lock.release()必须成对出现。否则就有可能造成死锁。 是因为,第二次获取锁时,发现锁已经被同一线程的人拿走了。自己也就理所当然,拿不到锁,程序就卡住了。 那么如何解决这个问题呢。 - -threading模块除了提供Lock锁之外,还提供了一种可重入锁RLock,专门来处理这个问题。 ``threading``\ 模块除了提供\ ``Lock``\ 锁之外,还提供了一种可重入锁\ ``RLock``\ ,专门来处理这个问题。 .. code:: python diff --git a/source/c02/c02_08.rst b/source/c02/c02_08.rst index 47f28f3..aeef076 100755 --- a/source/c02/c02_08.rst +++ b/source/c02/c02_08.rst @@ -84,7 +84,7 @@ for i in item: yield i - new_list=gen(astr, alist, adict, agen) + new_list=gen(astr, alist, adict, agen) print(list(new_list)) # ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7] diff --git a/source/c02/c02_09.rst b/source/c02/c02_09.rst index 76d8bfd..518ddc4 100755 --- a/source/c02/c02_09.rst +++ b/source/c02/c02_09.rst @@ -130,7 +130,7 @@ 前面我们说,\ ``await``\ 用于挂起阻塞的异步调用接口。其作用在\ ``一定程度上``\ 类似于yield。 注意这里是,一定程度上,意思是效果上一样(都能实现暂停的效果),但是功能上却不兼容。就是你不能在生成器中使用\ ``await``\ ,也不能在async -定义的协程中使用\ ``yield``\ 。 +定义的协程中使用\ ``yield from``\ 。 小明不是胡说八道的。有实锤。 |普通函数中 不能使用 await| 再来一锤。 |async 中 不能使用yield| diff --git a/source/c04/c04_15.rst b/source/c04/c04_15.rst index f873516..fdb1897 100644 --- a/source/c04/c04_15.rst +++ b/source/c04/c04_15.rst @@ -685,11 +685,11 @@ TODO 面板。如果你是 Mac, 快捷键 是Command + 6,而 Windows 是 Alt ------------------------------ PyCharm 里代码块的折叠功能,相当的显眼,在代码编辑框的左侧,你可以发现有 -``+`` 也有 ``-``\ ,很容易理解 ``+`` 代表代码块被折叠了,而 ``-`` -代表这个代码块处理未折叠状态,你可以用鼠标点击折叠。 +``+`` 也有 ``-``\ ,很容易理解 ``+`` 代表代码块被折叠了可以点此展开,而 +``-`` 代表这个代码块处于展开状态可以点此折叠。 如果你和我一样是个键盘党,你可以使用快捷(Mac:按住Command键,再按\ ``+``\ 或者\ ``-`` -)进行快速反折叠/折叠。 +,Windows:按住Ctrl键,再按\ ``+``\ 或者\ ``-`` )进行快速反折叠/折叠。 |image64| @@ -791,7 +791,7 @@ PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文 |image72| -设置完后,有哪些文件就一览无余了。 +设置完后,有哪些文件就非常清晰了。 |image73| @@ -814,6 +814,9 @@ PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文 |image74| +如果你嫌这快捷键太长了,可以使用 ``鼠标中键`` +点击这个类,可以达到同样的效果。 + 附录 ---- diff --git a/source/c08/c08_01.rst b/source/c08/c08_01.rst index 5a11c1c..f725f9a 100755 --- a/source/c08/c08_01.rst +++ b/source/c08/c08_01.rst @@ -155,6 +155,11 @@ aggregate管理 # 查看端口占用情况 $ neutron port-list + # 指定 mac 创建port + neutron port-create --tenant-id 100001 --fixed-ip ip_address=192.168.0.22 --mac-address fa:16:3e:3a:e8:1b + + nova interface-attach b0cc47bc-25c3-48ca-a4fd-5523326b515a --port-id 8bcba4eb-ade0-403d-8f13-45ed70936f03 + 1.3 Glance ~~~~~~~~~~ diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index 32d04c4..a8dac5c 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -406,6 +406,157 @@ manager 时,就会以binary 里的为准。比如binary |image40| +8.5.14 HTTP 状态码 +------------------ + +在 标准库 WebOB 中 + +:: + + Exception + HTTPException + HTTPOk + * 200 - :class:`HTTPOk` + * 201 - :class:`HTTPCreated` + * 202 - :class:`HTTPAccepted` + * 203 - :class:`HTTPNonAuthoritativeInformation` + * 204 - :class:`HTTPNoContent` + * 205 - :class:`HTTPResetContent` + * 206 - :class:`HTTPPartialContent` + HTTPRedirection + * 300 - :class:`HTTPMultipleChoices` + * 301 - :class:`HTTPMovedPermanently` + * 302 - :class:`HTTPFound` + * 303 - :class:`HTTPSeeOther` + * 304 - :class:`HTTPNotModified` + * 305 - :class:`HTTPUseProxy` + * 307 - :class:`HTTPTemporaryRedirect` + * 308 - :class:`HTTPPermanentRedirect` + HTTPError + HTTPClientError + * 400 - :class:`HTTPBadRequest` + * 401 - :class:`HTTPUnauthorized` + * 402 - :class:`HTTPPaymentRequired` + * 403 - :class:`HTTPForbidden` + * 404 - :class:`HTTPNotFound` + * 405 - :class:`HTTPMethodNotAllowed` + * 406 - :class:`HTTPNotAcceptable` + * 407 - :class:`HTTPProxyAuthenticationRequired` + * 408 - :class:`HTTPRequestTimeout` + * 409 - :class:`HTTPConflict` + * 410 - :class:`HTTPGone` + * 411 - :class:`HTTPLengthRequired` + * 412 - :class:`HTTPPreconditionFailed` + * 413 - :class:`HTTPRequestEntityTooLarge` + * 414 - :class:`HTTPRequestURITooLong` + * 415 - :class:`HTTPUnsupportedMediaType` + * 416 - :class:`HTTPRequestRangeNotSatisfiable` + * 417 - :class:`HTTPExpectationFailed` + * 422 - :class:`HTTPUnprocessableEntity` + * 423 - :class:`HTTPLocked` + * 424 - :class:`HTTPFailedDependency` + * 428 - :class:`HTTPPreconditionRequired` + * 429 - :class:`HTTPTooManyRequests` + * 431 - :class:`HTTPRequestHeaderFieldsTooLarge` + * 451 - :class:`HTTPUnavailableForLegalReasons` + HTTPServerError + * 500 - :class:`HTTPInternalServerError` + * 501 - :class:`HTTPNotImplemented` + * 502 - :class:`HTTPBadGateway` + * 503 - :class:`HTTPServiceUnavailable` + * 504 - :class:`HTTPGatewayTimeout` + * 505 - :class:`HTTPVersionNotSupported` + * 511 - :class:`HTTPNetworkAuthenticationRequired` + +8.5.15 不同存储方式xml +---------------------- + +lvm + +.. code:: xml + + + + + +
+ + +qcow2 + +.. code:: xml + + + + + +
+ + +config(configdrive) + +.. code:: xml + + + + + + +
+ + +isolate + +.. code:: xml + + + + + +
+ + +其他都好理解,这个qcow2的类型,有一点点的不一样。 + +他是先创建一个在LVM存储池中,创建一个LV。 + +然后为这个LV,创建一个软连接,通过 ``df -Th`` 可以看到 这个LV 挂载到 +一个目录下。这个目录下有一个名为 disk 的qcow2文件,而这个qcow2 +文件的backing file 是指向一个 base 镜像文件(raw格式)。 + +|image41| + +8.5.16 独立磁盘与LVM +-------------------- + +**独立磁盘** + +优点:虚拟机磁盘互不影响,IO不共享,不会因为一块物理盘挂了而影响所有的虚拟机。 + +缺点:无法像 LVM +存储池那样,做到精准而灵活的资源分配,有可能造成资源浪费或资源不足。 + +|image42| + +**LVM** + +优点:可以根据业务或者其他条件(如介质类型),对多块物理盘组合成一个VG,实现精准而灵活的资源分配 + +缺点:不同虚拟机之间,磁盘IO共享,不同客户之间的性能会出现争抢的情况;稳定性不好,如果一块盘坏了,有可能有多个虚拟机受到影响。 + +8.5.17 主机组的使用 +------------------- + +在 flavor 上有一个 extra_spec 可以设置多个参数,nova +本身自带了许多,当然这个参数可以自定义。 + +这个参数有什么用呢,需要搭配主机组使用。 + +只要在主机组上设置的metadata 的 key-value 和 extra_spec +的key-value一样就可以实现宿主机的过滤。 + +|image43| + -------------- .. figure:: http://image.python-online.cn/20190511161447.png @@ -453,4 +604,7 @@ manager 时,就会以binary 里的为准。比如binary .. |image38| image:: http://image.python-online.cn/20190529203441.png .. |image39| image:: http://image.python-online.cn/20190529215953.png .. |image40| image:: http://image.python-online.cn/20190529215825.png +.. |image41| image:: http://image.python-online.cn/20190627213044.png +.. |image42| image:: http://image.python-online.cn/20190627213609.png +.. |image43| image:: http://image.python-online.cn/20190627215038.png diff --git a/source/c08/c08_09.rst b/source/c08/c08_09.rst index 9750b50..0e96023 100644 --- a/source/c08/c08_09.rst +++ b/source/c08/c08_09.rst @@ -441,7 +441,7 @@ Qpid,RabbitMQ等。 :binding是用来描述exchange和queue之间的关系的概念,一个exchang可以绑定多个队列,这些关系由binding建立。前面说的binding key /binding pattern也是在binding中给出。 -在网上找了个图,可以很清晰地描述几个名词的关系。 +为了让你明白这几者的关系,我画了一张模型图。 |image6| @@ -452,6 +452,65 @@ Qpid,RabbitMQ等。 3. 一个消息可以被发送到一个队列中,也可以被发送到多个多列中。多队列情况下,一个消息可以被多个receiver收到并处理。Openstack RPC中这两种情况都会用到。 +在 Python 中如何发送消息,并接收消息呢? + +这边写个demo + +首先是生产者 + +.. code:: python + + # coding:utf-8 + import sys,time + import pika + + + credentials = pika.PlainCredentials('account', 'password') + + def productor(): + connection = pika.BlockingConnection( + pika.ConnectionParameters('ctrl.openstack.com',5672,'/',credentials))#建立一个最基本的socket + chanel = connection.channel()#声明一个管道 + + chanel.queue_declare(queue='name')#给管道创建一个队列,参数是管道队列名。 + + chanel.basic_publish(exchange='', + routing_key='name', + body ='Hello World!')#要发送的消息。 + connection.close() + +再者是消息费 + +.. code:: python + + # coding:utf-8 + import sys,time + import pika + + + credentials = pika.PlainCredentials('account', 'password') + + def consumer(): + consumer = pika.BlockingConnection\ + (pika.ConnectionParameters('ctrl.openstack.com',5672,'/',credentials))#创建socket连接 + channel = consumer.channel()#创建管道 + channel.queue_declare(queue='name') + + def backcall(ch,method,properties,body):#参数body是发送过来的消息。 + print '已接收到消息!' + sys.exit() + + #backcall 回调函数 执行结束后立即执行另外一个函数返回给发送端是否执行完毕。 + #no_ack=True 不会告知服务端我是否收到消息。一般注释。 + + channel.basic_consume(backcall, + queue='name', + no_ack=True + )#如果注释掉,对方没有收到消息的话不会将消息丢失,始终在队列里等待下次发送。 + + + channel.start_consuming()#启动后进入死循环。一直等待消息。 + 8.9.5 OpenStack中如何使用RPC? ------------------------------ @@ -719,6 +778,7 @@ rpc server 和rpc client 的四个重要方法 规范(中文版) `__ - `nova event机制分析 `__ +- `RabbitMQ 三种Exchange `__ -------------- @@ -732,7 +792,7 @@ rpc server 和rpc client 的四个重要方法 .. |image3| image:: http://image.python-online.cn/20190623155955.png .. |image4| image:: http://image.python-online.cn/20190623162725.png .. |image5| image:: http://image.python-online.cn/20190623191042.png -.. |image6| image:: http://image.python-online.cn/20190623205052.png +.. |image6| image:: http://image.python-online.cn/20190630160025.png .. |image7| image:: http://image.python-online.cn/20190623201427.png .. |image8| image:: http://image.python-online.cn/20190526182125.png .. |image9| image:: http://image.python-online.cn/20190526184854.png From 6a9364c44bccdacca6055c3802a3b12300c9c364 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Thu, 4 Jul 2019 23:57:31 +0800 Subject: [PATCH 056/302] new page: thanks list --- .gitignore | 2 ++ source/c07/c07_02.md | 6 +++--- source/c07/c07_02.rst | 6 +++--- source/c08/c08_03.md | 2 +- source/c08/c08_03.rst | 2 +- source/index.rst | 1 + source/thanks.rst | 4 ++-- 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 4ea4d38..8fcada4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ build/ .idea/ *.pyc +Makefile +Pipfile diff --git a/source/c07/c07_02.md b/source/c07/c07_02.md index 00fd0e2..bfccac8 100644 --- a/source/c07/c07_02.md +++ b/source/c07/c07_02.md @@ -535,7 +535,7 @@ port= ## 7.2.5 关键问题记录 -#### 7.2.5.1 agent失联的问题 +### 7.2.5.1 agent失联的问题 agent 失联的问题,使用默认的监控项会有不少问题。 @@ -547,7 +547,7 @@ agent 失联的问题,使用默认的监控项会有不少问题。 {$host:agent.ping.nodata(5m)}=1 and {$host:agent.ping.prev()}=1 ``` -#### 7.2.5.2 库表占用情况 +### 7.2.5.2 库表占用情况 ``` # 查询各监控项的占用情况 @@ -613,7 +613,7 @@ group by host) as mytable3; -#### 7.2.5.3 批量更新操作 +### 7.2.5.3 批量更新操作 ``` # 批量修改数据保存天数 diff --git a/source/c07/c07_02.rst b/source/c07/c07_02.rst index ae1f985..0302efd 100755 --- a/source/c07/c07_02.rst +++ b/source/c07/c07_02.rst @@ -589,7 +589,7 @@ zabbix 自带 mysql 的监控模板,监控项不多,只有 14 项。 ------------------ 7.2.5.1 agent失联的问题 -^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~ agent 失联的问题,使用默认的监控项会有不少问题。 @@ -602,7 +602,7 @@ agent 失联的问题,使用默认的监控项会有不少问题。 {$host:agent.ping.nodata(5m)}=1 and {$host:agent.ping.prev()}=1 7.2.5.2 库表占用情况 -^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~ :: @@ -670,7 +670,7 @@ float ,log, text 等,所以计算存在一定的误差,需留有冗余 group by host) as mytable3; 7.2.5.3 批量更新操作 -^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~ :: diff --git a/source/c08/c08_03.md b/source/c08/c08_03.md index 768dbc8..ad2f274 100644 --- a/source/c08/c08_03.md +++ b/source/c08/c08_03.md @@ -313,7 +313,7 @@ chkconfig acpid on 配置cloud-init: `/etc/cloud/cloud.cfg` -```sehll +```shell # 允许root登陆 disable_root: false diff --git a/source/c08/c08_03.rst b/source/c08/c08_03.rst index 981c2b0..24a184c 100755 --- a/source/c08/c08_03.rst +++ b/source/c08/c08_03.rst @@ -346,7 +346,7 @@ centos命令如下 配置cloud-init: ``/etc/cloud/cloud.cfg`` -.. code:: sehll +.. code:: shell # 允许root登陆 disable_root: false diff --git a/source/index.rst b/source/index.rst index 0c1aadf..7a33fd0 100755 --- a/source/index.rst +++ b/source/index.rst @@ -17,5 +17,6 @@ Contents: bookmark chapters/* leetcode/* + thanks.rst aboutme roadmap diff --git a/source/thanks.rst b/source/thanks.rst index e5cb76f..7b960a9 100644 --- a/source/thanks.rst +++ b/source/thanks.rst @@ -1,5 +1,5 @@ ================================== -贡献者名单 +特别说明 ================================== @@ -11,7 +11,7 @@ 所以在这里准备开一个页面,感谢这些朋友。另外,对文章有任何疑问的同学都可以加我微信交流。 -.. figure:: ![](http://image.python-online.cn/20190704205721.png) +.. figure:: http://image.python-online.cn/20190704205721.png :alt: 添加明哥好友 From d5eab91f9bd12dc8d5d1b09edc581ec352ffabe4 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 6 Jul 2019 18:45:57 +0800 Subject: [PATCH 057/302] add iptables article --- source/c01/c01_18.md | 6 ++ source/c01/c01_20.md | 10 +-- source/c01/c01_21.md | 10 +++ source/c07/c07_01.md | 9 +++ source/c08/c08_05.md | 43 ----------- source/c08/c08_12.md | 60 +++++++++++++++ source/c08/c08_13.md | 175 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 265 insertions(+), 48 deletions(-) create mode 100644 source/c01/c01_21.md create mode 100644 source/c08/c08_12.md create mode 100644 source/c08/c08_13.md diff --git a/source/c01/c01_18.md b/source/c01/c01_18.md index 240a279..1a63ec8 100644 --- a/source/c01/c01_18.md +++ b/source/c01/c01_18.md @@ -136,3 +136,9 @@ mysql -uroot -p # 直接回车 mysql -uroot -p ``` + + +## 1.18.4 命令行使用技巧 + +![](http://image.python-online.cn/20190705225651.png) + diff --git a/source/c01/c01_20.md b/source/c01/c01_20.md index 0471920..87d4ec5 100644 --- a/source/c01/c01_20.md +++ b/source/c01/c01_20.md @@ -1,14 +1,14 @@ # 1.20 静态方法其实暗藏玄机 -这个标题「静态方法其实暗藏玄机」其实只说明了文章了一个知识点。 - -其实这本篇文章,主要讲了以下两个知识点。 +这个标题「**静态方法其实暗藏玄机**」其实只是该文章的一个知识点。或许有些标题党,但没有关系,我相信有不少人对此并没有深入研究他们,不信我问你三个问题,你看能否答上来。 1、Python2.x和3.x中,函数和方法的区分有什么不同? -2、Python3.x 中,静态方法有几种写法? +2、有了类/实例方法和普通函数,为什么还会有静态方法? + +3、Python3.x 中,静态方法有几种写法? -带着这两个问题,你可以在下文中寻找答案。 +带着这三个问题,你可以尝试在下文中寻找答案。 --- diff --git a/source/c01/c01_21.md b/source/c01/c01_21.md new file mode 100644 index 0000000..9b1893f --- /dev/null +++ b/source/c01/c01_21.md @@ -0,0 +1,10 @@ +# 1.21 开发小技巧 + +解决网页鼠标限制 + +``` +解决网页不能选中,在console中输入:document.onselectstart=true +解决网页不能复制,在console中输入:document.oncopy=true +解决网页不能右键,在console中输入:document.oncontextmenu=true +``` + diff --git a/source/c07/c07_01.md b/source/c07/c07_01.md index 3dd0844..e719248 100644 --- a/source/c07/c07_01.md +++ b/source/c07/c07_01.md @@ -604,6 +604,15 @@ rpm -qf /path/filename rpmrebuild xxx ``` +### 1.10 ftp工具使用 + +```shell +$ ftp +ftp> help +``` + +![](http://image.python-online.cn/20190705182629.png) + ## 二、系统管理 diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 55e1301..9acfb67 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -175,49 +175,6 @@ compute的资源上报,是在 `nova/compute/resource_tracker.py:_init_compute_ 从数据库获取旧数据 `self.compute_node = self._get_compute_node(context)` -## 8.5.7 资源主机调度实现 - -一般情况下一个 OpenStack 中,会部署有许多个计算节点。当我们创建一个虚拟机时,OpenStack 如何决定要将我们的虚拟机创建在哪里呢?这就是 openstack-nova-scheduler 要做的事,顾名思义,它是对集群内的所有计算节点的资源情况进行比较,从而选出一台最适合我们当前虚拟机创建的节点,再把请求发到 这一台节点上的 openstack-nova-compute 去进行真正的创建过程。 - -从源代码中看,最开始是 nova-conductor (nova/conductor/manager.py)在给 nova-compute 发创建请求前,会先让 nova-scheduler 选出一台资源充足的计算节点。 - -![](http://image.python-online.cn/20190424212211.png) - -nova-scheduler 的调度主要由两部分组成 - -![](http://image.python-online.cn/20190424213430.png) - -- 过滤器:filter,将不满足条件(硬性条件,比如内存,cpu,磁盘,pci设备等)的计算节点,直接过滤掉。意义:从过滤器出来的那些计算节点,理论上都可以创建虚拟机。 -- 称重器:weigher,对满足硬性条件的众多主机按照一定的规则进行权重配比。意义:经过称重器计算,选出你更希望在哪台节点上创建虚拟机。 - -不管是过滤器,还是称重器,它们都需要两个参数 - -- hosts:多个 host_state 的集合,包含有当前可用的计算节点信息(资源,ip等)。其中单个元素是 HostState (nova/scheduler/host_manager.py)类的实例。如果你想添加其他原来没有的信息,比如 compute 的 id,可以在 `_update_from_compute_node` 函数中添加。它会从compute_nodes 表中取得你想要的信息。 - - ![](http://image.python-online.cn/20190424214653.png) - -- spec_obj:你所要请求创建的虚拟机信息(模板,镜像等)。它是从 objects.RequestSpec.from_primitives 中取得的 - - ![](http://image.python-online.cn/20190424214540.png) - - -过滤器,它的代码如下: - -![](http://image.python-online.cn/20190424221602.png) - -称重器,它的规则主要看这段代码。 - -![](http://image.python-online.cn/20190424215735.png) - -我在代码中,加了几段日志。从左到右,三个不同颜色的内容分别为,原始权值,配重系数(越高说明越占比越大,越影响最终结果),经过 nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮点数)。 - -![](http://image.python-online.cn/20190424220008.png) - -那最终的权值如何计算呢? - -1. 先计算每一个称重器后的权重: weights * multipier -2. 最后按不同的compute 将权重相加起来。 - ## 8.5.8 手动引入上下文环境 diff --git a/source/c08/c08_12.md b/source/c08/c08_12.md new file mode 100644 index 0000000..75b424e --- /dev/null +++ b/source/c08/c08_12.md @@ -0,0 +1,60 @@ +# 8.12 OpenStack之主机调度 + +一般情况下,一个 OpenStack 中,会部署有许多个计算节点。当我们创建一个虚拟机时,OpenStack 如何决定要将我们的虚拟机创建在哪里呢?这就是 openstack-nova-scheduler 要做的事。 + +顾名思义,它是对集群内的所有计算节点的资源情况进行比较。主要分为两个过程: + +1、根据计算节点的资源情况,过滤掉不符合创建虚拟机的规格的计算节点; + +2、根据既定的规则,对过滤器筛选出的合格的计算节点,进行权重计算,选出最优节点。 + +经过以上两个过程后,nova-scheduler 会将选到的主机返回给 nova-conductor,这时候 nova-conductor 才会去调用 nova-compute 去进行真正的创建过程。 + +接下来,我会从源码的角度来分析一下这个过程。 + +从源代码中看,最开始是 nova-conductor (nova/conductor/manager.py)在给 nova-compute 发创建请求前,会先让 nova-scheduler 选出一台资源充足的计算节点。 + +![](http://image.python-online.cn/20190424212211.png) + +nova-scheduler 的调度主要由两部分组成(nova/scheduler/filter_scheduler.py:FilterScheduler._schedule()) + +![](http://image.python-online.cn/20190424213430.png) + +- 过滤器:filter,将不满足条件(硬性条件,比如内存,cpu,磁盘,pci设备等)的计算节点,直接过滤掉。意义:从过滤器出来的那些计算节点,理论上都可以创建虚拟机。 +- 称重器:weigher,对满足硬性条件的众多主机按照一定的规则进行权重配比。意义:经过称重器计算,选出你更希望在哪台节点上创建虚拟机。 + +不管是过滤器,还是称重器,它们都需要两个参数 + +- hosts:多个 host_state 的集合,包含有当前可用的计算节点信息(资源,ip等)。其中单个元素是 HostState (nova/scheduler/host_manager.py)类的实例。如果你想添加其他原来没有的信息,比如 compute 的 id,可以在 `_update_from_compute_node` 函数中添加。它会从compute_nodes 表中取得你想要的信息。 + + ![](http://image.python-online.cn/20190424214653.png) + +- spec_obj:你所要请求创建的虚拟机信息(模板,镜像等)。它是从 objects.RequestSpec.from_primitives 中取得的 + + ![](http://image.python-online.cn/20190424214540.png) + +过滤器,它的代码如下: + +![](http://image.python-online.cn/20190424221602.png) + +称重器,它的规则主要看这段代码。 + +![](http://image.python-online.cn/20190424215735.png) + +我在代码中,加了几段日志。从左到右,三个不同颜色的内容分别为,原始权值,配重系数(越高说明越占比越大,越影响最终结果),经过 nomalize 后的权值(只有 0 和 1)。 + +![](http://image.python-online.cn/20190424220008.png) + +那最终的权值如何计算呢? + +1. 先计算每一个称重器后的权重: weights * multipier +2. 最后按不同的compute 将权重相加起来。 + +nova-scheduler 选择到主机后,在日志中会打印三条DEBUG信息,可以据此查看 + +``` +LOG.debug("Filtered %(hosts)s", {'hosts': hosts}) +LOG.debug("Weighed %(hosts)s", {'hosts': weighed_hosts}) +LOG.debug("Selected host: %(host)s", {'host': chosen_host}) +``` + diff --git a/source/c08/c08_13.md b/source/c08/c08_13.md new file mode 100644 index 0000000..d4833a1 --- /dev/null +++ b/source/c08/c08_13.md @@ -0,0 +1,175 @@ +# 8.13 网络知识必知必会 + +## 8.13.1 iptables + +推荐文章:[朱双印的 iptables 系列文章](http://www.zsythink.net/archives/tag/iptables/) + +### 概念相关 + +**动作** + +![](http://image.python-online.cn/20190706114314.png) + +### 命令相关 + +```shell +# 查看 filter 表的所有链 +iptables -t filter -L +iptables -L # 不指定,默认就为 filter + +# 查看其他表的规则 +iptables -t nat -L +iptables -t mangle -L +iptables -t raw -L + +# 从上面知道了如何查看指定表的规则,这边看看如何查看指定表里的某条链的规则 +# 只要在后面直接加上 <链名> 即可 +iptables -t nat -L POSTROUTING + +``` + +如果要查看更详细的信息,就再加个 -v 参数 + +```shell +$ iptables -t nat -vL POSTROUTING +Chain POSTROUTING (policy ACCEPT 21M packets, 1241M bytes) + pkts bytes target prot opt in out source destination + 179K 12M SNAT all -- any eth0 172.20.22.0/24 anywhere to:175.xx.xx.177 +``` + +多出来的几个字段如何理解呢?我摘取《[朱双印的个人日志](http://www.zsythink.net/archives/1493)》的解释到这边。 + +![](http://image.python-online.cn/20190706093904.png) + +iptables 默认为我们配置了域名反解(根据ip解析成域名),这个过程效率很低,我们可以指定参数 `-n` 跳过这个过程,从下面的例子可以看到 `in` 从上面的 `any` 变成了 `*` (0.0.0.0) + +```shell +$ iptables -t nat -nvL POSTROUTING +Chain POSTROUTING (policy ACCEPT 21M packets, 1241M bytes) + pkts bytes target prot opt in out source destination + 179K 12M SNAT all -- * eth0 172.20.22.0/24 0.0.0.0/0 to:175.xx.xx.177 +``` + +如果你想要展示行号,可以指定 `--line-numbers`, 在centos上可以缩短为 `--line` + +```shell +$ iptables -t nat -nvL POSTROUTING +Chain POSTROUTING (policy ACCEPT 21M packets, 1241M bytes) + pkts bytes target prot opt in out source destination + 179K 12M SNAT all -- * eth0 172.20.22.0/24 0.0.0.0/0 to:175.xx.xx.177 +``` + +从上面几个输出来看,pkts 和 bytes 都会自动转成 humanable 的单位。如果我们想看具体的数值,以方便查看变化,可以加个参数 `-x` + +```shell +$ iptables -t nat -nvxL POSTROUTING +``` + +如果你想清空某个表中的指定链的规则,比如清空 filter 表中的 input 链。 + +```shell +iptables -F INPUT +``` + +添加规则 + +```shell +# ======================基本条件================== +$ iptables -t filter -I INPUT 2 -s 172.20.20.201 -j DROP +# -t : 指定 filter 表(不指定就默认filter) +# -I : 指定 INPUT 链,I 是 insert 即插入的意思 +# 2 : 指定插入位置,插入在第二行。 +# -s : 匹配规则,来源是 172.20.20.201 +# -j : 动作,丢弃 + +# ======================其他常见条件================== + +还可以为你的规则添加其他的匹配条件 +-p : 匹配协议 +-m : 指定模块,引入其他模块的方法做匹配条件,如:-m tcp --dport 22,就是使用tcp扩展模块下的 --sport 22 做为匹配条件。 + +-s : 匹配源地址,也可以添加多个 -s 72.20.20.201,172.20.20.202,到iptables那会分成两条规则 +-d : 匹配目标地方,可以添加多个 -d 172.20.20.201,172.20.20.202,到iptables那会分成两条规则 + +--dport : 匹配目标端口,若要使用 --dport,必须指定 -p 协议类型 和 -m 模块类型, +--sport : 匹配源端口 + +# 指定多个端口 +-m multiport --dport 22,80-88,multiport只能用于 tcp 和 udp 协议,必须配置 -p tcp 或者 -p udp 使用 + +# ======================扩展模块================== + +# 匹配ip段 +-m iprange --src-range 172.20.20.10-172.20.20.20 +-m iprange --dst-range 172.20.20.10-172.20.20.20 + +# 链接数限制 +# 每个客户端ip ssh 的连接数最多为两个 +# --connlimit-mask 另外还用这个参数指定为哪个网段的ip进行限制 +-m connlimit --connlimit-above 2 -m tcp --dport 22 j REJECT + +# 匹配报文包含的内容 +# '-m string'表示使用string模块,'--algo bm'表示使用bm算法去匹配指定的字符串,其他可选项还有kmp,' --string "hello,world" '则表示我们想要匹配的字符串为"hello,world" +-m string --algo bm --string "hello,world" + +# 匹配连接数量,控制报文到达速率:http://www.zsythink.net/archives/1564 +-m limit --limit 10/minute + + +``` + +删除规则 + +```shell +# 删除 filter表、INPUT链的第三条规则 +$ iptables -t filter -D INPUT 3 + +# 指定匹配条件删除 +$ iptables -D INPUT -s 172.20.20.201 -j DROP + +# 删除某表中某条链的所有的规则 +$ iptables -t filter -F INPUT +``` + +修改规则 + +```shell +# 可用指定第几条规则进行修改,如果使用这种,记得匹配全条件。 +iptables -t fileter INPUT 2 -R -s 172.20.20.202 -j REJECT + +# 也可以先删除,再添加(更加靠谱) +iptables -t fileter -D INPUT 2 +iptables -t filter -I INPUT 2 -s 172.20.20.202 -j REJECT + +# 修改链的默认动作 +# 当报文没有命中规则,就按默认动作来做 +# 那如何更改默认动作呢? +iptables -t filter -P FORWARD DROP +``` + +![](http://image.python-online.cn/20190706160632.png) + +保存规则 + +```shell +# 通过以上命令对规则的所有修改都是临时的,如果将iptables重启。修改就会失败。 +# 所以要将规则尽快地保存到配置文件中。 + +# 在 centos6 +service iptables save + +# 在centos7 +# iptables 是默认安装的,会用 firewall 代替 iptables +# 而iptables-service 需要用户自己安装。有了它,才能像centos6一样使用 +service iptables save + +# 输出规则到当前屏幕,并不会保存到配置文件 +iptables-save + +# 或者用重重向的方式输出到文件中 +iptables-save > /etc/sysconfig/iptables.bak +iptables-restore < /etc/sysconfig/iptables.bak +``` + + + From 71c079972531642aec08d1a86861232141b5b31a Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 14 Jul 2019 11:44:44 +0800 Subject: [PATCH 058/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=99=9A=E6=8B=9F?= =?UTF-8?q?=E5=8C=96=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/c07_03.md | 9 ++++--- source/c08/c08_04.md | 58 ++++++++++++++++++++++++++++++++++++++++++-- source/index.rst | 2 +- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/source/c07/c07_03.md b/source/c07/c07_03.md index 7e4efee..e1eead0 100644 --- a/source/c07/c07_03.md +++ b/source/c07/c07_03.md @@ -158,9 +158,10 @@ docker run -it --name container_A --blkio-weight 600 ubuntu docker run -it --name container_B --blkio-weight 300 ubuntu ``` -**`bps` 和 `iops`限制单个容器的读写速度** -bps 是 byte per second,每秒读写的数据量。 -iops 是 io per second,每秒 IO 的次数。 +`bps` 和 `iops`限制单个容器的读写速度。 + +- bps 是 byte per second,每秒读写的数据量。 +- iops 是 io per second,每秒 IO 的次数。 ``` # 可通过以下参数控制容器的 bps 和 iops: @@ -178,7 +179,7 @@ time dd if=/dev/zero out=test.out bs=1M count=800 oflag=direct # 测试速度 ### 3.5 cgroup -`cgroup`全称`Control Group。Linux` 操作系统通过 cgroup 可以设置进程使用 CPU、内存 和 IO 资源的限额。 +`cgroup`全称`Control Group`。`Linux` 操作系统通过 cgroup 可以设置进程使用 CPU、内存 和 IO 资源的限额。 ``` 在相应路径下,每个容器都有对应一个以id命令的文件夹,里面有一些配置文件,就记录了配额信息。 diff --git a/source/c08/c08_04.md b/source/c08/c08_04.md index e8238a4..7efb4de 100644 --- a/source/c08/c08_04.md +++ b/source/c08/c08_04.md @@ -1,6 +1,60 @@ -# 8.4 虚拟化入门概念 +# 8.4 云计算与虚拟化 -## 8.4.1 虚拟化分类 +从前几年开始,云计算和如今的人工智能一样,火热得不行。 + +但依然有相当多的一部分人(我指的是程序员),并不明白“云计算”到底是个什么玩意,为此,我打算写这篇文章,来介绍一下这个行业、介绍我从事的工作。 + +这里面有两个概念,一个是云计算,一个虚拟化。 + +先来说说云计算。 + +## 8.4.1 云计算 + +维基百科上定义的**云计算**(英语:**cloud computing**),是一种基于[互联网](https://zh.wikipedia.org/wiki/互联网)的计算方式,通过这种方式,共享的软硬件资源和信息可以按需求提供给计算机各种终端和其他设备。 + +枯燥的定义,听起来还是不太好理解,我尝试用自己的语言来解释下。 + +计算,即计算资源,包括我们熟悉的 CPU,内存,磁盘,带宽等。 + +云,就是将这些零散实体资源变成一个巨大无比的资源池子,有了这个池子,做为个人用户,你不再需要自己你买一个电脑放在家里,做为小型公司,你不需要自己整一个机房,花很多的人力和设备成本去运营这些基础设施。一旦你需要,你就向池子拥有者申请即可。这极大的提高了资源的利用率,以及分配的灵活性。 + +还有人说,云就像是天上的云一样,聚焦的水汽多了就会下雨,落到地面的雨水又会蒸发到天上,继续等待下一次下雨。云计算里的云,正如大自然里的云一样,可以实现资源的循环利用。你在公有云提供商那里,购买了一年的云主机,一年后资源被回收,可以再分配给其他人使用。 + +云计算的模型,是以服务为导向的。根据服务层次的不同,可以分为三类: + +1. IaaS(Infrastructure as a Service):基础设施即服务,简单点说就是提供基础设施,你在阿里云,AWS上购买的云主机就属于这类。 +2. PaaS(Platform as a Service):平台即服务,简单点说就是提供一个平台,典型的应用有,GAE(Google App Engine),直接给你提供一个应用程序的运行环境。 +3. SaaS(Software as a Service):软件即服务,这个你再熟悉不过了,你手机上的APP都是属于这类。 + +以上三种模型,面向的群体各不相同,从上到下,用户的自主权越来越小,需关注的细节也越来越少。 + +另外根据部署方式的不同,可以将云计算分为三类:公有云、私有云和混合云。 + +私有云由专供一个企业或组织使用的云计算资源构成。私有云可在物理上位于组织的现场数据中心,也可由第三方服务提供商托管。而混合云,就是二者皆有。 + +网上很流行的一种比喻:男人找个女友或老婆是自建私有云,单身约炮或者到娱乐场所消费是公有云服务,按需使用并可弹性扩容,已婚男人找二奶小蜜则属于混合云。 + +这种解释方式虽然比较俗,但却挺恰当便于理解。 + +## 8.4.2 虚拟化 + +云计算,是为了提高资源的利用率,分配的灵活性而提出的一种解决方案。 + +而这个解决方案的底层,需要有技术支撑,目前主要是虚拟化技术和容器技术。 + +这次主要讲的是虚拟化。 + +你是不是又要蒙圈了,什么是虚拟化? + +它是一种可以将计算机的实体资源(CPU,内存,存储,网络等)进行抽象转化,并提供分割,重新组合,以达到最大化利用资源的一种技术。 + +虚拟机使用过吧? + +当你只有一个电脑装了windows系统,而你也想体验一下linux系统,如果不想折腾去装个双系统,最简单的方法就是用 VMWare 或者 VirtualBox 在你的电脑里用linux的镜像创建个虚拟机。 + +你有没有想过,这虚拟机是如何创建出来,怎么这么神奇,一台电脑上竟然可以同时运行着两个操作系统。 + +其实你后创建的这个虚拟机只是宿主机上的一个进程而已。只不过它从外观上、使用上看起来和你原来宿主机上的系统没有什么区别。这个虚拟机里有自己的内存,cpu,磁盘,网卡,这些都依赖虚拟化技术才得以实现的。 ### 全虚拟化 diff --git a/source/index.rst b/source/index.rst index 7a33fd0..34c35e8 100755 --- a/source/index.rst +++ b/source/index.rst @@ -17,6 +17,6 @@ Contents: bookmark chapters/* leetcode/* - thanks.rst + thanks aboutme roadmap From 424cf007cb010ae60a0eadde6e8cb5fd020a3c7f Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 14 Jul 2019 16:25:50 +0800 Subject: [PATCH 059/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_18.rst | 6 + source/c01/c01_20.rst | 10 +- source/c01/c01_21.rst | 10 ++ source/c07/c07_01.rst | 19 ++- source/c07/c07_03.rst | 8 +- source/c08/c08_04.md | 208 ++++++++++++++++++++------------ source/c08/c08_04.rst | 267 +++++++++++++++++++++++++++++++----------- source/c08/c08_05.rst | 155 ++++++++---------------- source/c08/c08_12.rst | 84 +++++++++++++ source/c08/c08_13.rst | 184 +++++++++++++++++++++++++++++ 10 files changed, 690 insertions(+), 261 deletions(-) create mode 100644 source/c01/c01_21.rst create mode 100644 source/c08/c08_12.rst create mode 100644 source/c08/c08_13.rst diff --git a/source/c01/c01_18.rst b/source/c01/c01_18.rst index f434df2..0ba5e6b 100644 --- a/source/c01/c01_18.rst +++ b/source/c01/c01_18.rst @@ -149,10 +149,16 @@ mysql,这时也请将其卸载再重新安装吧。 # 再重新登陆,用新的密码登陆,发现可以生效 mysql -uroot -p +1.18.4 命令行使用技巧 +--------------------- + +|image6| + .. |image0| image:: http://image.python-online.cn/20190615001340.png .. |image1| image:: http://image.python-online.cn/20190615001414.png .. |image2| image:: http://image.python-online.cn/20190615001633.png .. |image3| image:: http://image.python-online.cn/20190615001706.png .. |image4| image:: http://image.python-online.cn/20190615001908.png .. |image5| image:: http://image.python-online.cn/20190615112422.png +.. |image6| image:: http://image.python-online.cn/20190705225651.png diff --git a/source/c01/c01_20.rst b/source/c01/c01_20.rst index ce99437..fbd4c4f 100644 --- a/source/c01/c01_20.rst +++ b/source/c01/c01_20.rst @@ -1,15 +1,15 @@ 1.20 静态方法其实暗藏玄机 ========================= -这个标题「静态方法其实暗藏玄机」其实只说明了文章了一个知识点。 - -其实这本篇文章,主要讲了以下两个知识点。 +这个标题「\ **静态方法其实暗藏玄机**\ 」其实只是该文章的一个知识点。或许有些标题党,但没有关系,我相信有不少人对此并没有深入研究他们,不信我问你三个问题,你看能否答上来。 1、Python2.x和3.x中,函数和方法的区分有什么不同? -2、Python3.x 中,静态方法有几种写法? +2、有了类/实例方法和普通函数,为什么还会有静态方法? + +3、Python3.x 中,静态方法有几种写法? -带着这两个问题,你可以在下文中寻找答案。 +带着这三个问题,你可以尝试在下文中寻找答案。 -------------- diff --git a/source/c01/c01_21.rst b/source/c01/c01_21.rst new file mode 100644 index 0000000..5f99c9f --- /dev/null +++ b/source/c01/c01_21.rst @@ -0,0 +1,10 @@ +1.21 开发小技巧 +=============== + +解决网页鼠标限制 + +:: + + 解决网页不能选中,在console中输入:document.onselectstart=true + 解决网页不能复制,在console中输入:document.oncopy=true + 解决网页不能右键,在console中输入:document.oncontextmenu=true diff --git a/source/c07/c07_01.rst b/source/c07/c07_01.rst index 1e81033..81a8594 100755 --- a/source/c07/c07_01.rst +++ b/source/c07/c07_01.rst @@ -689,6 +689,16 @@ Linux上的压缩格式比Windows上多很多,在 Windows 上最常见的不 # 使用rpmrebuild需要依赖rpmbuild:yum install -y rpmbuild rpmrebuild xxx +1.10 ftp工具使用 +~~~~~~~~~~~~~~~~ + +.. code:: shell + + $ ftp + ftp> help + +|image1| + 二、系统管理 ------------ @@ -823,7 +833,7 @@ Linux的分区的过程经历以下几个步骤 2. 格式化:写入文件系统 3. 挂载:将分区挂载到目录上,才能访问数据 -关于硬件对应的设备文件名,可以参照下图 |image1| +关于硬件对应的设备文件名,可以参照下图 |image2| 其中以硬盘为例来说明 @@ -1428,7 +1438,7 @@ CentOS **修改文件权限** 文件权限有\ ``读``\ 、\ ``写``\ 、\ ``执行``\ 三种 -分别对应数字4,2,1,也就是2\ :sup:`2,2`\ 1,2^0 |image2| +分别对应数字4,2,1,也就是2\ :sup:`2,2`\ 1,2^0 |image3| 如何修改文件权限 @@ -1777,6 +1787,7 @@ CentOS .. |image0| image:: http://image.python-online.cn/17-9-20/47469030.jpg -.. |image1| image:: http://image.python-online.cn/17-10-15/97911325.jpg -.. |image2| image:: https://dn-anything-about-doc.qbox.me/linux_base/3-14.png/logoblackfont +.. |image1| image:: http://image.python-online.cn/20190705182629.png +.. |image2| image:: http://image.python-online.cn/17-10-15/97911325.jpg +.. |image3| image:: https://dn-anything-about-doc.qbox.me/linux_base/3-14.png/logoblackfont diff --git a/source/c07/c07_03.rst b/source/c07/c07_03.rst index ce4ae89..e6b0a3d 100755 --- a/source/c07/c07_03.rst +++ b/source/c07/c07_03.rst @@ -189,8 +189,10 @@ Ubuntu:\ `Ubuntu 16.04 上安装 Docker docker run -it --name container_A --blkio-weight 600 ubuntu docker run -it --name container_B --blkio-weight 300 ubuntu -**``bps`` 和 ``iops``\ 限制单个容器的读写速度** bps 是 byte per -second,每秒读写的数据量。 iops 是 io per second,每秒 IO 的次数。 +``bps`` 和 ``iops``\ 限制单个容器的读写速度。 + +- bps 是 byte per second,每秒读写的数据量。 +- iops 是 io per second,每秒 IO 的次数。 :: @@ -210,7 +212,7 @@ second,每秒读写的数据量。 iops 是 io per second,每秒 IO 的次 3.5 cgroup ~~~~~~~~~~ -``cgroup``\ 全称\ ``Control Group。Linux`` 操作系统通过 cgroup +``cgroup``\ 全称\ ``Control Group``\ 。\ ``Linux`` 操作系统通过 cgroup 可以设置进程使用 CPU、内存 和 IO 资源的限额。 :: diff --git a/source/c08/c08_04.md b/source/c08/c08_04.md index 7efb4de..fb984e1 100644 --- a/source/c08/c08_04.md +++ b/source/c08/c08_04.md @@ -1,14 +1,16 @@ -# 8.4 云计算与虚拟化 +# 8.4 云计算与虚拟化入门通识 -从前几年开始,云计算和如今的人工智能一样,火热得不行。 +经常有朋友问我,你是做什么的呢? -但依然有相当多的一部分人(我指的是程序员),并不明白“云计算”到底是个什么玩意,为此,我打算写这篇文章,来介绍一下这个行业、介绍我从事的工作。 +我回答说,云计算。 -这里面有两个概念,一个是云计算,一个虚拟化。 +不回答还好,一回答倒使他们更加蒙圈。听着挺高大上,挺牛b的。就是不知道是什么东西。为了解答大部分朋友的问题,我想着写这么一篇文章,对云计算和虚拟化这块入门级知识做一个梳理,如果刚好你也想进入这个领域,正好,这份入门通识指南,应该挺适合你的。 -先来说说云计算。 +这是本文的大纲,你可以根据大纲内容,选择是否继续浏览,或者根据大纲内容快速跳转至你感兴趣的内容。 -## 8.4.1 云计算 +![](http://image.python-online.cn/20190714161353.png) + +## 8.4.1 云计算是什么? 维基百科上定义的**云计算**(英语:**cloud computing**),是一种基于[互联网](https://zh.wikipedia.org/wiki/互联网)的计算方式,通过这种方式,共享的软硬件资源和信息可以按需求提供给计算机各种终端和其他设备。 @@ -32,11 +34,11 @@ 私有云由专供一个企业或组织使用的云计算资源构成。私有云可在物理上位于组织的现场数据中心,也可由第三方服务提供商托管。而混合云,就是二者皆有。 -网上很流行的一种比喻:男人找个女友或老婆是自建私有云,单身约炮或者到娱乐场所消费是公有云服务,按需使用并可弹性扩容,已婚男人找二奶小蜜则属于混合云。 +网上很流行的一种比喻:男人找个女友或老婆是自建私有云,单身约p或者到娱乐场所消费是公有云服务,按需使用并可弹性扩容,已婚男人找二奶小蜜则属于混合云。 -这种解释方式虽然比较俗,但却挺恰当便于理解。 +这种解释方式虽然比较俗,但却挺恰当,便于小白理解。 -## 8.4.2 虚拟化 +## 8.4.2 虚拟化是什么? 云计算,是为了提高资源的利用率,分配的灵活性而提出的一种解决方案。 @@ -54,62 +56,24 @@ 你有没有想过,这虚拟机是如何创建出来,怎么这么神奇,一台电脑上竟然可以同时运行着两个操作系统。 -其实你后创建的这个虚拟机只是宿主机上的一个进程而已。只不过它从外观上、使用上看起来和你原来宿主机上的系统没有什么区别。这个虚拟机里有自己的内存,cpu,磁盘,网卡,这些都依赖虚拟化技术才得以实现的。 - -### 全虚拟化 - -**全虚拟化(英语:Full virtualization)**是硬件虚拟化的一种。 - -通俗点讲,全虚拟化就是说,虚拟机的所有操作(CPU,内存,网络等)都需要经过一个运行在物理机上的虚拟化软件转发给物理机内核。而这个虚拟化软件,在windows上你常见且熟悉的有vmware,virtualbox。 - -允许**未经修改**的客操作系统(英语:Guest OS)隔离运行。在全虚拟化环境中,任何可以运行在裸机上的软件(通常是操作系统)都可以未经修改地运行在虚拟机中。 - -虽然适应性较强,但是由于完全依赖软件模拟硬件接口,全虚拟化的运行速度要快于硬件模拟,但是性能方面不如裸机,因为Hypervisor需要占用一些资源。不如半虚拟化。 - -![](http://image.python-online.cn/FqDTXLAxlDdtrsGndSurNeIEtehl) - -代表:VMWare(1998年) +其实你后创建的这个虚拟机只是原物理机上的一个进程而已。只不过它从外观上、使用上看起来和你原来宿主机上的系统没有什么区别。这个虚拟机里有自己的内存,cpu,磁盘,网卡,这些都依赖虚拟化技术才得以实现的。 -### 半虚拟化 +在虚拟机内部,如果要使用物理机上设备,除了虚拟化技术让其可以间接地使用物理设备,也可以使用设备的直通让虚拟机直接使用物理设备,这种直通技术,不需要经过VMM(虚拟机监控器,后面会介绍),所以性能会比虚拟化好。常见的有GPU直通。还有直通与虚拟化的结合,如SR-IOV,即单根IO虚拟化(Single-root I/O virtualization),将一个物理网卡(PF)虚拟化成多个虚拟网卡(VF),再将虚拟网卡直给挂给虚拟机使用。 -半虚拟化(英语:**Paravirtualization**)是另一种类似于全虚拟化的热门技术。 - -通俗点讲,半虚拟化对比全虚拟化,就是有一些可以直接操作物理内核空间,而不需要全部经过虚拟化软件。这就大大提高了虚拟机的性能。 +## 8.4.3 VMM 是什么? -它在HOST上使用Hpervisor(虚拟机管理程序)提供便利的接口,使得Guest OS能够调用接口访问虚拟硬件。而条件是,Guest OS 内部需要部署安装相应的驱动和软件逻辑,需要对操作系统进行修改。 +VMM,通常叫做 Hypervisor(下面我们也将以Hypervisor指代VMM),中文名:虚拟机监控器,英文全称:Virtual Machine Monitor。 -半虚拟化系统性能可以接近在裸机上的性能。 - -![](http://image.python-online.cn/FjJKZqDYDnrQrdk22fAWgYbKEneK) - -代表:Xen(2006) - -- Xen是一款虚拟化软件,支持半虚拟化和完全虚拟化。它在不支持VT技术的cpu上也能使用,但是只能以半虚拟化模式运行。 -- 半虚拟化的意思是需要修改被虚拟系统的内核,以实现系统能被完美的虚拟在Xen上面。完全虚拟化则是不需要修改系统内核则可以直接运行在Xen上面。 -- VMware是一款完全虚拟化软件。完全虚拟的弱点是效率不如半虚拟化的高。半虚拟化系统性能可以接近在裸机上的性能。 -- Xen是由一个后台守护进程维护的,叫做xend,要运行虚拟系统,必须先将它开启。 -- Xen的配置工具有许多,我使用的是virt-manager(GUI)、virt-install和xm。第一个用于管理和安装系统,第二个只用于安装系统,第三个用于启动系统。 - -### 硬件辅助虚拟化 - -硬件辅助虚拟化(英语:**Hardware-assisted virtualization**),直接从硬件层面开始支持虚拟化。由硬件支持并提供多个虚拟硬件设备接口,这些设备由虚拟机内核驱动传递给虚拟机使用。使用这种方式,虚拟机能获得和宿主机一样的硬件功能,性能也和宿主机相近,同时原生操作系统本来就支持这项技术,因此无需对操作系统进行修改。 - -缺点就是,硬件要支持虚拟化功能,在以前这可能是缺点,但是现在随着虚拟化技术的发展,越来越多的硬件都已经支持虚拟化,成本也越来越低,所以硬件辅助虚拟化是目前最流行,使用最广泛的虚拟化技术。 +Hypervisor 是为了实现虚拟化而引入的一个介于虚拟机操作系统和物理资源的软件层。当虚拟机要对物理资源进行操作时,Hypervisor将对其指令进行截取并且重定向,让虚拟机无感知地像物理操作系统一样使用物理资源。 -代表:KVM(2009) +常见的Hypervisor,有 -以上是三种常见的虚拟化方式,但是要注意的是,一个虚拟化软件不一定会只采用一种虚拟化方式,比如KVM通常使用**硬件辅助虚拟化**实现cpu、内存的虚拟化,**而使用半虚拟化**的virtio接口实现硬盘,网卡等io设备的虚拟化。 +- KVM +- Xen +- Hyper-V +- VMWare -kvm 的 内核模块: - -- kvm.ko -- Kvm_intel.ko 或者 kvm_amd.ko - -需要安装的软件 - -- qemu-kvm - -## 8.4.2 虚拟化技术 +## 8.4.4 虚拟化技术 **1. KVM** @@ -140,7 +104,7 @@ QEMU是一套由Fabrice Bellard所编写的模拟处理器的自由软件。Qemu **总结** 1. KVM 和 Xen 都是免费的。 -2. KVM 需要硬件支持(Intel VT或AMD-V),集成在内核中,而Xen可在所有的Linux上运行,不需要硬件支持。 +2. KVM 需要硬件支持(Intel VT或AMD-V),集成在内核中,而Xen可在所有的Linux上运行,可不需要硬件支持。 **4. libvirt** @@ -157,9 +121,82 @@ QEMU是一套由Fabrice Bellard所编写的模拟处理器的自由软件。Qemu 目前,libvirt 已经成为使用最为广泛的对各种虚拟机进行管理的工具和应用程序接口(API),而且一些常用的虚拟机管理工具(如virsh、virt-install、virt-manager等)和云计算框架平台(如OpenStack、OpenNebula、Eucalyptus等)都在底层使用libvirt的应用程序接口。 -![](https://i.loli.net/2019/02/25/5c73e070ef9e4.jpg) +![来源网络,侵删](https://i.loli.net/2019/02/25/5c73e070ef9e4.jpg) + +## 8.4.5 虚拟化分类 + +### 全虚拟化和半虚拟化 + +根据客户机系统是否需要修改定制可以分为 **全虚拟化** 和 **半虚拟化**。 + +**1. 全虚拟化** + +**全虚拟化(英语:Full virtualization)**,是需要依托于硬件虚拟化的。 + +在全虚拟化模式下,虚拟机的所有操作(CPU,内存,网络等)都需要经过一个运行在物理机上的虚拟化软件转发给物理机内核。而这个虚拟化软件,在windows上你常见且熟悉的有vmware,virtualbox。 + +允许**未经修改**的客操作系统(英语:Guest OS)隔离运行。在全虚拟化环境中,任何可以运行在裸机上的软件(通常是操作系统)都可以未经修改地运行在虚拟机中。 + +代表:VMWare(1998年),KVM + +**2. 半虚拟化** + +半虚拟化(英语:**Paravirtualization**)是另一种类似于全虚拟化的热门技术。 + +半虚拟化对比全虚拟化,就是有一些可以直接操作物理内核空间,而不需要全部经过虚拟化软件。这就大大提高了虚拟机的性能。 + +它在HOST上使用Hpervisor(虚拟机管理程序)提供便利的接口,使得Guest OS能够调用接口访问虚拟硬件。而条件是,Guest OS 内部需要部署安装相应的驱动和软件逻辑,需要对操作系统进行修改。 + +代表:Xen(2006) + +- Xen是一款虚拟化软件,支持半虚拟化和完全虚拟化。它在不支持VT技术的cpu上也能使用,但是只能以半虚拟化模式运行。 +- 半虚拟化的意思是需要修改被虚拟系统的内核,以实现系统能被完美的虚拟在Xen上面。完全虚拟化则是不需要修改系统内核则可以直接运行在Xen上面。 +- VMware是一款完全虚拟化软件。完全虚拟的弱点是效率不如半虚拟化的高。半虚拟化系统性能可以接近在裸机上的性能。 + +### 1型虚拟化和2型虚拟化 + +根据虚拟化层是直接位于硬件之上还是位于操作系统之上,可以分为 Type 1 虚拟化和 Type 2 虚拟化。 + +![](http://image.python-online.cn/20190714141644.png) + +Type 1:Xen,VMWare ESX + +Type 2:KVM,WMWare Workstation + + + +### 硬件虚拟化和软件虚拟化 + +**1. 软件虚拟化** + +在硬件虚拟化出现之前,市场上都是使用的软件虚拟化。 + +软件虚拟化,就是通过软件来实现虚拟化,原理是把从虚拟机传来的操作指令进行截取翻译,并传递给真实的物理硬件。 + +由于每条指令都需要经过“截取” -> “翻译” -> “转发”,所以其虚拟化性能会差一点。 + +哪些属于虚拟化软件呢? + +- KVM:负责cpu和内存的虚拟化,但cpu必须支持硬件虚拟化。 +- QEMU:负责IO设备(网卡、磁盘)的虚拟化 + +**2. 硬件虚拟化** + +硬件虚拟化,是指计算机硬件本身提供能力让客户机指令独立运行,而不需要Hypervisor 截获重定向。直接从硬件层面开始支持虚拟化。由硬件支持并提供多个虚拟硬件设备接口,这些设备由虚拟机内核驱动传递给虚拟机使用。使用这种方式,虚拟机能获得和宿主机一样的硬件功能,性能也和宿主机相近,同时原生操作系统本来就支持这项技术,因此无需对操作系统进行修改。 + +Intel 从2005年开始在 x86 cpu 上支持硬件虚拟化,大大推进了虚拟化的发展。 + +缺点就是,硬件要支持虚拟化功能,在以前这可能是缺点,但是现在随着虚拟化技术的发展,越来越多的硬件都已经支持虚拟化,成本也越来越低,所以硬件辅助虚拟化是目前最流行,使用最广泛的虚拟化技术。 + +KVM这种流行的虚拟化技术里,既有软件虚拟化,也有硬件虚拟化,软件虚拟化要基于硬件的虚拟化,二者是相辅的关系,而不是互斥。 -## 8.4.3 KVM工具 + + +## 8.4.6 KVM工具 + +有了虚拟化,就有了虚拟机,那如何对这些虚拟机进行管理呢。 + +在 Linux 下有许多的工具可以使用: - Virsh:基于 libvirt 的 命令行工具 (CLI) @@ -173,13 +210,13 @@ QEMU是一套由Fabrice Bellard所编写的模拟处理器的自由软件。Qemu ![](https://i.loli.net/2019/02/25/5c73e6160764a.png) -## 8.4.4 创建虚拟机 +## 8.4.5 创建虚拟机 ### 手工创建 -虚拟机的本质是宿主机上的一个进程,当你用OpenStack在界面,或者使用CLI创建了一个虚拟机时。你可以登陆到计算节点去,使用`ps -ef|grep kvm` 看下这个虚拟机的进程,是下面这样子的。 +虚拟机的本质是宿主机上的一个进程,当你用OpenStack在界面,或者使用virsg 创建了一个虚拟机时。你可以使用`ps -ef|grep kvm` 看下这个虚拟机的进程,是下面这样子的。 -天呐,参数多得让人头皮发麻。这要是没有通过 OpenStack 创建,我得写这么长一串命令才能创建一台虚拟机。瞬间觉得 OpenStack 牛逼了不少。 +参数多得让人头皮发麻。意思是,你可以使用这样一串命令才能创建一台虚拟机。 ```shell $ /usr/libexec/qemu-kvm \ @@ -209,16 +246,24 @@ $ /usr/libexec/qemu-kvm \ ### virsh 创建 -使用OpenStack创建的前提是你发搭建好OpenStack环境,由于这个环境部署比较复杂。所以你也可以使用virsh 命令来创建。 +前面我们看到,创建一台虚拟机需要诸多的参数。 + +如果一个一个去指定,非常不易于管理及复用。 -使用virsh 创建之前,你需要准备好一个镜像(可以qcow2格式的,也可以是raw格式的)。 +如果可以在创建时,指定一个配置文件,这个配置文件里包含上述所有的参数,不就大大简化了虚拟机创建过程。 -然后你要准备一个xml文件,并写入你的虚拟机配置。 +这时候就出现了virsh这个基于 libvirt 的 命令行工具 (CLI)。通过它我们可以指定一个 xml 配置文件来很轻松的创建一台虚拟机。 + +```shell +virsh define vm.xml +virsh start guest_vm +``` + +其中xml的内容如下 ```xml - ws_controller01 - 9f9424b1-57ea-4281-b650-8fb4de03fc68 + guest_vm 12582912 12582912 6 @@ -289,10 +334,6 @@ $ /usr/libexec/qemu-kvm \ - - - - @@ -356,6 +397,26 @@ $ /usr/libexec/qemu-kvm \ ``` +### OpenStack + +使用 virsh 来指定xml进行创建虽然能对虚拟机进行生命周期的管理,但是无法对成百上千台的机器进行集中式的管理。 + +这时候,OpenStack 这个开源的云计算管理平台就出现了。 + +有了OpenStack,你可以使用 Horizon提供的界面进行虚拟机的管理 + +![来源网络,侵删](http://image.python-online.cn/20190714151716.png) + +也可以使用nova 的 cli 命令进行创建。 + +```shell +nova boot \ +--flavor \ +--nic net-id=,v4-fixed-ip= \ +--image \ +--config-drive True +``` + ## 附录:参考文档 @@ -363,6 +424,7 @@ $ /usr/libexec/qemu-kvm \ - [libguestfs-tools工具常用命令](https://blog.csdn.net/wenwenxiong/article/details/52223731) - [ libvirt 介绍 Libvrit for KVM/QEMU ](https://www.cnblogs.com/sammyliu/p/4558638.html) - [我是虚拟机内核我困惑?](https://mp.weixin.qq.com/s?__biz=MzI1NzYzODk4OQ==&mid=2247483820&idx=1&sn=8a44b992491aea03e55eefb4815a1958&chksm=ea15168edd629f98e622dcb94e64fbb4a75055da98d620e7c83071b5d6d428904fa5c8e9c4ad&scene=21#wechat_redirect) +- 《KVM实战》 diff --git a/source/c08/c08_04.rst b/source/c08/c08_04.rst index 918ff98..cccd81b 100644 --- a/source/c08/c08_04.rst +++ b/source/c08/c08_04.rst @@ -1,70 +1,94 @@ -8.4 虚拟化入门概念 -================== +8.4 云计算与虚拟化入门通识 +========================== -8.4.1 虚拟化分类 ----------------- +经常有朋友问我,你是做什么的呢? -全虚拟化 -~~~~~~~~ +我回答说,云计算。 -**全虚拟化(英语:Full virtualization)**\ 是硬件虚拟化的一种。 +不回答还好,一回答倒使他们更加蒙圈。听着挺高大上,挺牛b的。就是不知道是什么东西。为了解答大部分朋友的问题,我想着写这么一篇文章,对云计算和虚拟化这块入门级知识做一个梳理,如果刚好你也想进入这个领域,正好,这份入门通识指南,应该挺适合你的。 -通俗点讲,全虚拟化就是说,虚拟机的所有操作(CPU,内存,网络等)都需要经过一个运行在物理机上的虚拟化软件转发给物理机内核。而这个虚拟化软件,在windows上你常见且熟悉的有vmware,virtualbox。 +这是本文的大纲,你可以根据大纲内容,选择是否继续浏览,或者根据大纲内容快速跳转至你感兴趣的内容。 -允许\ **未经修改**\ 的客操作系统(英语:Guest -OS)隔离运行。在全虚拟化环境中,任何可以运行在裸机上的软件(通常是操作系统)都可以未经修改地运行在虚拟机中。 +|image0| -虽然适应性较强,但是由于完全依赖软件模拟硬件接口,全虚拟化的运行速度要快于硬件模拟,但是性能方面不如裸机,因为Hypervisor需要占用一些资源。不如半虚拟化。 +8.4.1 云计算是什么? +-------------------- -|image0| +维基百科上定义的\ **云计算**\ (英语:\ **cloud +computing**\ ),是一种基于\ `互联网 `__\ 的计算方式,通过这种方式,共享的软硬件资源和信息可以按需求提供给计算机各种终端和其他设备。 -代表:VMWare(1998年) +枯燥的定义,听起来还是不太好理解,我尝试用自己的语言来解释下。 -半虚拟化 -~~~~~~~~ +计算,即计算资源,包括我们熟悉的 CPU,内存,磁盘,带宽等。 -半虚拟化(英语:\ **Paravirtualization**)是另一种类似于全虚拟化的热门技术。 +云,就是将这些零散实体资源变成一个巨大无比的资源池子,有了这个池子,做为个人用户,你不再需要自己你买一个电脑放在家里,做为小型公司,你不需要自己整一个机房,花很多的人力和设备成本去运营这些基础设施。一旦你需要,你就向池子拥有者申请即可。这极大的提高了资源的利用率,以及分配的灵活性。 -通俗点讲,半虚拟化对比全虚拟化,就是有一些可以直接操作物理内核空间,而不需要全部经过虚拟化软件。这就大大提高了虚拟机的性能。 +还有人说,云就像是天上的云一样,聚焦的水汽多了就会下雨,落到地面的雨水又会蒸发到天上,继续等待下一次下雨。云计算里的云,正如大自然里的云一样,可以实现资源的循环利用。你在公有云提供商那里,购买了一年的云主机,一年后资源被回收,可以再分配给其他人使用。 -它在HOST上使用Hpervisor(虚拟机管理程序)提供便利的接口,使得Guest -OS能够调用接口访问虚拟硬件。而条件是,Guest OS -内部需要部署安装相应的驱动和软件逻辑,需要对操作系统进行修改。 +云计算的模型,是以服务为导向的。根据服务层次的不同,可以分为三类: -半虚拟化系统性能可以接近在裸机上的性能。 +1. IaaS(Infrastructure as a + Service):基础设施即服务,简单点说就是提供基础设施,你在阿里云,AWS上购买的云主机就属于这类。 +2. PaaS(Platform as a + Service):平台即服务,简单点说就是提供一个平台,典型的应用有,GAE(Google + App Engine),直接给你提供一个应用程序的运行环境。 +3. SaaS(Software as a + Service):软件即服务,这个你再熟悉不过了,你手机上的APP都是属于这类。 -|image1| +以上三种模型,面向的群体各不相同,从上到下,用户的自主权越来越小,需关注的细节也越来越少。 -代表:Xen(2006) +另外根据部署方式的不同,可以将云计算分为三类:公有云、私有云和混合云。 -- Xen是一款虚拟化软件,支持半虚拟化和完全虚拟化。它在不支持VT技术的cpu上也能使用,但是只能以半虚拟化模式运行。 -- 半虚拟化的意思是需要修改被虚拟系统的内核,以实现系统能被完美的虚拟在Xen上面。完全虚拟化则是不需要修改系统内核则可以直接运行在Xen上面。 -- VMware是一款完全虚拟化软件。完全虚拟的弱点是效率不如半虚拟化的高。半虚拟化系统性能可以接近在裸机上的性能。 -- Xen是由一个后台守护进程维护的,叫做xend,要运行虚拟系统,必须先将它开启。 -- Xen的配置工具有许多,我使用的是virt-manager(GUI)、virt-install和xm。第一个用于管理和安装系统,第二个只用于安装系统,第三个用于启动系统。 +私有云由专供一个企业或组织使用的云计算资源构成。私有云可在物理上位于组织的现场数据中心,也可由第三方服务提供商托管。而混合云,就是二者皆有。 -硬件辅助虚拟化 -~~~~~~~~~~~~~~ +网上很流行的一种比喻:男人找个女友或老婆是自建私有云,单身约p或者到娱乐场所消费是公有云服务,按需使用并可弹性扩容,已婚男人找二奶小蜜则属于混合云。 -硬件辅助虚拟化(英语:\ **Hardware-assisted -virtualization**\ ),直接从硬件层面开始支持虚拟化。由硬件支持并提供多个虚拟硬件设备接口,这些设备由虚拟机内核驱动传递给虚拟机使用。使用这种方式,虚拟机能获得和宿主机一样的硬件功能,性能也和宿主机相近,同时原生操作系统本来就支持这项技术,因此无需对操作系统进行修改。 +这种解释方式虽然比较俗,但却挺恰当,便于小白理解。 -缺点就是,硬件要支持虚拟化功能,在以前这可能是缺点,但是现在随着虚拟化技术的发展,越来越多的硬件都已经支持虚拟化,成本也越来越低,所以硬件辅助虚拟化是目前最流行,使用最广泛的虚拟化技术。 +8.4.2 虚拟化是什么? +-------------------- + +云计算,是为了提高资源的利用率,分配的灵活性而提出的一种解决方案。 + +而这个解决方案的底层,需要有技术支撑,目前主要是虚拟化技术和容器技术。 + +这次主要讲的是虚拟化。 + +你是不是又要蒙圈了,什么是虚拟化? + +它是一种可以将计算机的实体资源(CPU,内存,存储,网络等)进行抽象转化,并提供分割,重新组合,以达到最大化利用资源的一种技术。 + +虚拟机使用过吧? + +当你只有一个电脑装了windows系统,而你也想体验一下linux系统,如果不想折腾去装个双系统,最简单的方法就是用 +VMWare 或者 VirtualBox 在你的电脑里用linux的镜像创建个虚拟机。 -代表:KVM(2009) +你有没有想过,这虚拟机是如何创建出来,怎么这么神奇,一台电脑上竟然可以同时运行着两个操作系统。 -以上是三种常见的虚拟化方式,但是要注意的是,一个虚拟化软件不一定会只采用一种虚拟化方式,比如KVM通常使用\ **硬件辅助虚拟化**\ 实现cpu、内存的虚拟化,\ **而使用半虚拟化**\ 的virtio接口实现硬盘,网卡等io设备的虚拟化。 +其实你后创建的这个虚拟机只是原物理机上的一个进程而已。只不过它从外观上、使用上看起来和你原来宿主机上的系统没有什么区别。这个虚拟机里有自己的内存,cpu,磁盘,网卡,这些都依赖虚拟化技术才得以实现的。 -kvm 的 内核模块: +在虚拟机内部,如果要使用物理机上设备,除了虚拟化技术让其可以间接地使用物理设备,也可以使用设备的直通让虚拟机直接使用物理设备,这种直通技术,不需要经过VMM(虚拟机监控器,后面会介绍),所以性能会比虚拟化好。常见的有GPU直通。还有直通与虚拟化的结合,如SR-IOV,即单根IO虚拟化(Single-root +I/O +virtualization),将一个物理网卡(PF)虚拟化成多个虚拟网卡(VF),再将虚拟网卡直给挂给虚拟机使用。 -- kvm.ko -- Kvm_intel.ko 或者 kvm_amd.ko +8.4.3 VMM 是什么? +------------------ -需要安装的软件 +VMM,通常叫做 +Hypervisor(下面我们也将以Hypervisor指代VMM),中文名:虚拟机监控器,英文全称:Virtual +Machine Monitor。 -- qemu-kvm +Hypervisor +是为了实现虚拟化而引入的一个介于虚拟机操作系统和物理资源的软件层。当虚拟机要对物理资源进行操作时,Hypervisor将对其指令进行截取并且重定向,让虚拟机无感知地像物理操作系统一样使用物理资源。 -8.4.2 虚拟化技术 +常见的Hypervisor,有 + +- KVM +- Xen +- Hyper-V +- VMWare + +8.4.4 虚拟化技术 ---------------- **1. KVM** @@ -98,13 +122,13 @@ kernel的一个模块。可以用命令modprobe去加载KVM模块。加载了模 说到了QEMU,其实它也是一个虚拟化软件。作用是什么呢,它相当于一个路由器,当Guest OS的内核想要操作物理硬件时,必须先经由Qemu转发,将操作指令转给真实的硬件。由于所有的指令都要从Qemu里面过一手,因而性能比较差。 -|image2| +|image1| **总结** 1. KVM 和 Xen 都是免费的。 2. KVM 需要硬件支持(Intel - VT或AMD-V),集成在内核中,而Xen可在所有的Linux上运行,不需要硬件支持。 + VT或AMD-V),集成在内核中,而Xen可在所有的Linux上运行,可不需要硬件支持。 **4. libvirt** @@ -124,11 +148,93 @@ OS的内核想要操作物理硬件时,必须先经由Qemu转发,将操作 目前,libvirt 已经成为使用最为广泛的对各种虚拟机进行管理的工具和应用程序接口(API),而且一些常用的虚拟机管理工具(如virsh、virt-install、virt-manager等)和云计算框架平台(如OpenStack、OpenNebula、Eucalyptus等)都在底层使用libvirt的应用程序接口。 -|image3| +.. figure:: https://i.loli.net/2019/02/25/5c73e070ef9e4.jpg + :alt: 来源网络,侵删 + + 来源网络,侵删 + +8.4.5 虚拟化分类 +---------------- + +全虚拟化和半虚拟化 +~~~~~~~~~~~~~~~~~~ + +根据客户机系统是否需要修改定制可以分为 **全虚拟化** 和 **半虚拟化**\ 。 + +**1. 全虚拟化** + +**全虚拟化(英语:Full virtualization)**\ ,是需要依托于硬件虚拟化的。 + +在全虚拟化模式下,虚拟机的所有操作(CPU,内存,网络等)都需要经过一个运行在物理机上的虚拟化软件转发给物理机内核。而这个虚拟化软件,在windows上你常见且熟悉的有vmware,virtualbox。 + +允许\ **未经修改**\ 的客操作系统(英语:Guest +OS)隔离运行。在全虚拟化环境中,任何可以运行在裸机上的软件(通常是操作系统)都可以未经修改地运行在虚拟机中。 + +代表:VMWare(1998年),KVM + +**2. 半虚拟化** + +半虚拟化(英语:\ **Paravirtualization**)是另一种类似于全虚拟化的热门技术。 + +半虚拟化对比全虚拟化,就是有一些可以直接操作物理内核空间,而不需要全部经过虚拟化软件。这就大大提高了虚拟机的性能。 + +它在HOST上使用Hpervisor(虚拟机管理程序)提供便利的接口,使得Guest +OS能够调用接口访问虚拟硬件。而条件是,Guest OS +内部需要部署安装相应的驱动和软件逻辑,需要对操作系统进行修改。 + +代表:Xen(2006) + +- Xen是一款虚拟化软件,支持半虚拟化和完全虚拟化。它在不支持VT技术的cpu上也能使用,但是只能以半虚拟化模式运行。 +- 半虚拟化的意思是需要修改被虚拟系统的内核,以实现系统能被完美的虚拟在Xen上面。完全虚拟化则是不需要修改系统内核则可以直接运行在Xen上面。 +- VMware是一款完全虚拟化软件。完全虚拟的弱点是效率不如半虚拟化的高。半虚拟化系统性能可以接近在裸机上的性能。 + +1型虚拟化和2型虚拟化 +~~~~~~~~~~~~~~~~~~~~ + +根据虚拟化层是直接位于硬件之上还是位于操作系统之上,可以分为 Type 1 +虚拟化和 Type 2 虚拟化。 + +|image2| + +Type 1:Xen,VMWare ESX + +Type 2:KVM,WMWare Workstation + +硬件虚拟化和软件虚拟化 +~~~~~~~~~~~~~~~~~~~~~~ + +**1. 软件虚拟化** + +在硬件虚拟化出现之前,市场上都是使用的软件虚拟化。 + +软件虚拟化,就是通过软件来实现虚拟化,原理是把从虚拟机传来的操作指令进行截取翻译,并传递给真实的物理硬件。 + +由于每条指令都需要经过“截取” -> “翻译” -> +“转发”,所以其虚拟化性能会差一点。 + +哪些属于虚拟化软件呢? + +- KVM:负责cpu和内存的虚拟化,但cpu必须支持硬件虚拟化。 +- QEMU:负责IO设备(网卡、磁盘)的虚拟化 + +**2. 硬件虚拟化** + +硬件虚拟化,是指计算机硬件本身提供能力让客户机指令独立运行,而不需要Hypervisor +截获重定向。直接从硬件层面开始支持虚拟化。由硬件支持并提供多个虚拟硬件设备接口,这些设备由虚拟机内核驱动传递给虚拟机使用。使用这种方式,虚拟机能获得和宿主机一样的硬件功能,性能也和宿主机相近,同时原生操作系统本来就支持这项技术,因此无需对操作系统进行修改。 + +Intel 从2005年开始在 x86 cpu 上支持硬件虚拟化,大大推进了虚拟化的发展。 + +缺点就是,硬件要支持虚拟化功能,在以前这可能是缺点,但是现在随着虚拟化技术的发展,越来越多的硬件都已经支持虚拟化,成本也越来越低,所以硬件辅助虚拟化是目前最流行,使用最广泛的虚拟化技术。 + +KVM这种流行的虚拟化技术里,既有软件虚拟化,也有硬件虚拟化,软件虚拟化要基于硬件的虚拟化,二者是相辅的关系,而不是互斥。 -8.4.3 KVM工具 +8.4.6 KVM工具 ------------- +有了虚拟化,就有了虚拟机,那如何对这些虚拟机进行管理呢。 + +在 Linux 下有许多的工具可以使用: + - Virsh:基于 libvirt 的 命令行工具 (CLI) - Virt-Manager:基于 libvirt 的 GUI 工具 @@ -142,20 +248,19 @@ OS的内核想要操作物理硬件时,必须先经由Qemu转发,将操作 - libguestfs-tools:一组 Linux 下的 C 语言的 API ,用来访问/修改虚拟机的磁盘映像文件。 -|image4| +|image3| -8.4.4 创建虚拟机 +8.4.5 创建虚拟机 ---------------- 手工创建 ~~~~~~~~ -虚拟机的本质是宿主机上的一个进程,当你用OpenStack在界面,或者使用CLI创建了一个虚拟机时。你可以登陆到计算节点去,使用\ ``ps -ef|grep kvm`` +虚拟机的本质是宿主机上的一个进程,当你用OpenStack在界面,或者使用virsg +创建了一个虚拟机时。你可以使用\ ``ps -ef|grep kvm`` 看下这个虚拟机的进程,是下面这样子的。 -天呐,参数多得让人头皮发麻。这要是没有通过 OpenStack -创建,我得写这么长一串命令才能创建一台虚拟机。瞬间觉得 OpenStack -牛逼了不少。 +参数多得让人头皮发麻。意思是,你可以使用这样一串命令才能创建一台虚拟机。 .. code:: shell @@ -186,19 +291,26 @@ OS的内核想要操作物理硬件时,必须先经由Qemu转发,将操作 virsh 创建 ~~~~~~~~~~ -使用OpenStack创建的前提是你发搭建好OpenStack环境,由于这个环境部署比较复杂。所以你也可以使用virsh -命令来创建。 +前面我们看到,创建一台虚拟机需要诸多的参数。 + +如果一个一个去指定,非常不易于管理及复用。 -使用virsh -创建之前,你需要准备好一个镜像(可以qcow2格式的,也可以是raw格式的)。 +如果可以在创建时,指定一个配置文件,这个配置文件里包含上述所有的参数,不就大大简化了虚拟机创建过程。 -然后你要准备一个xml文件,并写入你的虚拟机配置。 +这时候就出现了virsh这个基于 libvirt 的 命令行工具 +(CLI)。通过它我们可以指定一个 xml 配置文件来很轻松的创建一台虚拟机。 + +.. code:: shell + + virsh define vm.xml + virsh start guest_vm + +其中xml的内容如下 .. code:: xml - ws_controller01 - 9f9424b1-57ea-4281-b650-8fb4de03fc68 + guest_vm 12582912 12582912 6 @@ -269,10 +381,6 @@ virsh 创建 - - - - @@ -335,6 +443,31 @@ virsh 创建 +OpenStack +~~~~~~~~~ + +使用 virsh +来指定xml进行创建虽然能对虚拟机进行生命周期的管理,但是无法对成百上千台的机器进行集中式的管理。 + +这时候,OpenStack 这个开源的云计算管理平台就出现了。 + +有了OpenStack,你可以使用 Horizon提供的界面进行虚拟机的管理 + +.. figure:: http://image.python-online.cn/20190714151716.png + :alt: 来源网络,侵删 + + 来源网络,侵删 + +也可以使用nova 的 cli 命令进行创建。 + +.. code:: shell + + nova boot \ + --flavor \ + --nic net-id=,v4-fixed-ip= \ + --image \ + --config-drive True + 附录:参考文档 -------------- @@ -342,6 +475,7 @@ virsh 创建 - `libvirt 介绍 Libvrit for KVM/QEMU `__ - `我是虚拟机内核我困惑? `__ +- 《KVM实战》 -------------- @@ -349,9 +483,8 @@ virsh 创建 :alt: 关注公众号,获取最新干货! -.. |image0| image:: http://image.python-online.cn/FqDTXLAxlDdtrsGndSurNeIEtehl -.. |image1| image:: http://image.python-online.cn/FjJKZqDYDnrQrdk22fAWgYbKEneK -.. |image2| image:: http://image.python-online.cn/FjlPaQLTiYCde92WhurWsRx6z8CK -.. |image3| image:: https://i.loli.net/2019/02/25/5c73e070ef9e4.jpg -.. |image4| image:: https://i.loli.net/2019/02/25/5c73e6160764a.png +.. |image0| image:: http://image.python-online.cn/20190714161353.png +.. |image1| image:: http://image.python-online.cn/FjlPaQLTiYCde92WhurWsRx6z8CK +.. |image2| image:: http://image.python-online.cn/20190714141644.png +.. |image3| image:: https://i.loli.net/2019/02/25/5c73e6160764a.png diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index a8dac5c..c792884 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -200,62 +200,6 @@ compute的资源上报,是在 从数据库获取旧数据 ``self.compute_node = self._get_compute_node(context)`` -8.5.7 资源主机调度实现 ----------------------- - -一般情况下一个 OpenStack -中,会部署有许多个计算节点。当我们创建一个虚拟机时,OpenStack -如何决定要将我们的虚拟机创建在哪里呢?这就是 openstack-nova-scheduler -要做的事,顾名思义,它是对集群内的所有计算节点的资源情况进行比较,从而选出一台最适合我们当前虚拟机创建的节点,再把请求发到 -这一台节点上的 openstack-nova-compute 去进行真正的创建过程。 - -从源代码中看,最开始是 nova-conductor (nova/conductor/manager.py)在给 -nova-compute 发创建请求前,会先让 nova-scheduler -选出一台资源充足的计算节点。 - -|image14| - -nova-scheduler 的调度主要由两部分组成 - -|image15| - -- 过滤器:filter,将不满足条件(硬性条件,比如内存,cpu,磁盘,pci设备等)的计算节点,直接过滤掉。意义:从过滤器出来的那些计算节点,理论上都可以创建虚拟机。 -- 称重器:weigher,对满足硬性条件的众多主机按照一定的规则进行权重配比。意义:经过称重器计算,选出你更希望在哪台节点上创建虚拟机。 - -不管是过滤器,还是称重器,它们都需要两个参数 - -- hosts:多个 host_state - 的集合,包含有当前可用的计算节点信息(资源,ip等)。其中单个元素是 - HostState - (nova/scheduler/host_manager.py)类的实例。如果你想添加其他原来没有的信息,比如 - compute 的 id,可以在 ``_update_from_compute_node`` - 函数中添加。它会从compute_nodes 表中取得你想要的信息。 - - |image16| - -- spec_obj:你所要请求创建的虚拟机信息(模板,镜像等)。它是从 - objects.RequestSpec.from_primitives 中取得的 - - |image17| - -过滤器,它的代码如下: - -|image18| - -称重器,它的规则主要看这段代码。 - -|image19| - -我在代码中,加了几段日志。从左到右,三个不同颜色的内容分别为,原始权值,配重系数(越高说明越占比越大,越影响最终结果),经过 -nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮点数)。 - -|image20| - -那最终的权值如何计算呢? - -1. 先计算每一个称重器后的权重: weights \* multipier -2. 最后按不同的compute 将权重相加起来。 - 8.5.8 手动引入上下文环境 ------------------------ @@ -263,11 +207,11 @@ nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮 1. 如果有请求req(在nova-api里),可以使用这种 -|image21| +|image14| 2. 其他地方可以使用这种 -|image22| +|image15| 8.5.9 指定ip时检查allocation_pools ---------------------------------- @@ -279,16 +223,16 @@ nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮 先来看看,port 是如何创建的 -|image23| +|image16| 若要解决这个问题,可以参考原生代码中,在为子网添加allocation_pool时,验证是否合法的的逻辑,代码如下 -|image24| +|image17| 然后在 ``neutron\neutron\db\ipam_pluggable_backend.py`` 文件中添加我们检查 ip是否在 allocation_pools 中的逻辑代码。 -|image25| +|image18| .. code:: python @@ -311,17 +255,17 @@ nomalize 后的权值(只有 0 和 1,我觉得原代码这块应该要有浮 然后还要定义一个异常类型 -|image26| +|image19| 若指定的ip在allocation pool 里,则正常创建,若不在allocation 里,就会在 nova-compute 日志中报错。 -|image27| +|image20| 可以发现我们的ip 172.20.22.64 并不在子网的allocation pool,理所当然在nova的日志中可以看到相应的报错。 -|image28| +|image21| 8.5.10 attach port时ip占用提示 ------------------------------ @@ -341,7 +285,7 @@ IpAddressAlreadyAllocated,而在 neutronclient 只有 IpAddressInUseClient 的异常,并不匹配,在neutronclient 端与neutron 对应的异常应该为 IpAddressAlreadyAllocatedClient 。 -|image29| +|image22| 如何让nova-api能够返回具体的错误信息呢? @@ -352,26 +296,26 @@ IpAddressAlreadyAllocatedClient 异常。 并且在nova 创建port的代码处,捕获这个异常 -|image30| +|image23| 这种要改两个组件,而且要将neutronclient 的代码也管理起来,较为麻烦 一种是,只改neutron,在neutron/ipam/exceptions.py 添加一个与 neutronclient 相对应的异常。 -|image31| +|image24| 然后修改 neutron/ipam/drivers/neutrondb_ipam/drivers.py 修改异常类型 -|image32| +|image25| 通过 postman 进行模拟,已经可以返回具体的信息 -|image33| +|image26| 另附:neutron 是如何判断ip是否已经占用?代码如下 -|image34| +|image27| 8.5.11 nova-compute 如何启动的? -------------------------------- @@ -379,11 +323,11 @@ neutronclient 相对应的异常。 从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 ``nova.cmd.compute:main()`` -|image35| +|image28| 从这个入口进去,会开启一个 ``nova-compute`` 的服务。 -|image36| +|image29| 当调用 service.Service.create 时(create 是一个工厂函数),实际是返回实例化的 service.Service 对象。当没有传入 @@ -391,20 +335,20 @@ manager 时,就会以binary 里的为准。比如binary 是\ ``nova-compute``\ ,那manager_cls 就是 ``compute_manager``\ ,对应的manager 导入路径,会从配置里读取。 -|image37| +|image30| 8.5.13 支持指定子网和指定ip --------------------------- 在 nova-api 接收请求处。 -|image38| +|image31| -|image39| +|image32| 对 network_info 进行解析,然后塞给 request 对象返回。 -|image40| +|image33| 8.5.14 HTTP 状态码 ------------------ @@ -524,7 +468,7 @@ isolate 一个目录下。这个目录下有一个名为 disk 的qcow2文件,而这个qcow2 文件的backing file 是指向一个 base 镜像文件(raw格式)。 -|image41| +|image34| 8.5.16 独立磁盘与LVM -------------------- @@ -536,7 +480,7 @@ isolate 缺点:无法像 LVM 存储池那样,做到精准而灵活的资源分配,有可能造成资源浪费或资源不足。 -|image42| +|image35| **LVM** @@ -555,7 +499,7 @@ isolate 只要在主机组上设置的metadata 的 key-value 和 extra_spec 的key-value一样就可以实现宿主机的过滤。 -|image43| +|image36| -------------- @@ -577,34 +521,27 @@ isolate .. |image11| image:: http://image.python-online.cn/FnJA8RNIvJN2lAEXbKtJDpOLg1vg .. |image12| image:: http://image.python-online.cn/FnGyI8jCQFLCGi0pGVmI3SV6pDrv .. |image13| image:: http://image.python-online.cn/FrbE6oEZ3vtTWwDfMNQ16MGi6SWr -.. |image14| image:: http://image.python-online.cn/20190424212211.png -.. |image15| image:: http://image.python-online.cn/20190424213430.png -.. |image16| image:: http://image.python-online.cn/20190424214653.png -.. |image17| image:: http://image.python-online.cn/20190424214540.png -.. |image18| image:: http://image.python-online.cn/20190424221602.png -.. |image19| image:: http://image.python-online.cn/20190424215735.png -.. |image20| image:: http://image.python-online.cn/20190424220008.png -.. |image21| image:: http://image.python-online.cn/20190426153322.png -.. |image22| image:: http://image.python-online.cn/20190426152148.png -.. |image23| image:: http://image.python-online.cn/20190526141815.png -.. |image24| image:: http://image.python-online.cn/20190526142453.png -.. |image25| image:: http://image.python-online.cn/20190526134519.png -.. |image26| image:: http://image.python-online.cn/20190526141226.png -.. |image27| image:: http://image.python-online.cn/20190526134543.png -.. |image28| image:: http://image.python-online.cn/20190526134618.png -.. |image29| image:: http://image.python-online.cn/20190526140213.png -.. |image30| image:: http://image.python-online.cn/20190526140301.png -.. |image31| image:: http://image.python-online.cn/20190526140315.png -.. |image32| image:: http://image.python-online.cn/20190526140336.png -.. |image33| image:: http://image.python-online.cn/20190526140410.png -.. |image34| image:: http://image.python-online.cn/20190526143235.png -.. |image35| image:: http://image.python-online.cn/20190526205152.png -.. |image36| image:: http://image.python-online.cn/20190526165007.png -.. |image37| image:: http://image.python-online.cn/20190526204328.png -.. |image38| image:: http://image.python-online.cn/20190529203441.png -.. |image39| image:: http://image.python-online.cn/20190529215953.png -.. |image40| image:: http://image.python-online.cn/20190529215825.png -.. |image41| image:: http://image.python-online.cn/20190627213044.png -.. |image42| image:: http://image.python-online.cn/20190627213609.png -.. |image43| image:: http://image.python-online.cn/20190627215038.png +.. |image14| image:: http://image.python-online.cn/20190426153322.png +.. |image15| image:: http://image.python-online.cn/20190426152148.png +.. |image16| image:: http://image.python-online.cn/20190526141815.png +.. |image17| image:: http://image.python-online.cn/20190526142453.png +.. |image18| image:: http://image.python-online.cn/20190526134519.png +.. |image19| image:: http://image.python-online.cn/20190526141226.png +.. |image20| image:: http://image.python-online.cn/20190526134543.png +.. |image21| image:: http://image.python-online.cn/20190526134618.png +.. |image22| image:: http://image.python-online.cn/20190526140213.png +.. |image23| image:: http://image.python-online.cn/20190526140301.png +.. |image24| image:: http://image.python-online.cn/20190526140315.png +.. |image25| image:: http://image.python-online.cn/20190526140336.png +.. |image26| image:: http://image.python-online.cn/20190526140410.png +.. |image27| image:: http://image.python-online.cn/20190526143235.png +.. |image28| image:: http://image.python-online.cn/20190526205152.png +.. |image29| image:: http://image.python-online.cn/20190526165007.png +.. |image30| image:: http://image.python-online.cn/20190526204328.png +.. |image31| image:: http://image.python-online.cn/20190529203441.png +.. |image32| image:: http://image.python-online.cn/20190529215953.png +.. |image33| image:: http://image.python-online.cn/20190529215825.png +.. |image34| image:: http://image.python-online.cn/20190627213044.png +.. |image35| image:: http://image.python-online.cn/20190627213609.png +.. |image36| image:: http://image.python-online.cn/20190627215038.png diff --git a/source/c08/c08_12.rst b/source/c08/c08_12.rst new file mode 100644 index 0000000..5ff7041 --- /dev/null +++ b/source/c08/c08_12.rst @@ -0,0 +1,84 @@ +8.12 OpenStack之主机调度 +======================== + +一般情况下,一个 OpenStack +中,会部署有许多个计算节点。当我们创建一个虚拟机时,OpenStack +如何决定要将我们的虚拟机创建在哪里呢?这就是 openstack-nova-scheduler +要做的事。 + +顾名思义,它是对集群内的所有计算节点的资源情况进行比较。主要分为两个过程: + +1、根据计算节点的资源情况,过滤掉不符合创建虚拟机的规格的计算节点; + +2、根据既定的规则,对过滤器筛选出的合格的计算节点,进行权重计算,选出最优节点。 + +经过以上两个过程后,nova-scheduler 会将选到的主机返回给 +nova-conductor,这时候 nova-conductor 才会去调用 nova-compute +去进行真正的创建过程。 + +接下来,我会从源码的角度来分析一下这个过程。 + +从源代码中看,最开始是 nova-conductor (nova/conductor/manager.py)在给 +nova-compute 发创建请求前,会先让 nova-scheduler +选出一台资源充足的计算节点。 + +|image0| + +nova-scheduler +的调度主要由两部分组成(nova/scheduler/filter_scheduler.py:FilterScheduler._schedule()) + +|image1| + +- 过滤器:filter,将不满足条件(硬性条件,比如内存,cpu,磁盘,pci设备等)的计算节点,直接过滤掉。意义:从过滤器出来的那些计算节点,理论上都可以创建虚拟机。 +- 称重器:weigher,对满足硬性条件的众多主机按照一定的规则进行权重配比。意义:经过称重器计算,选出你更希望在哪台节点上创建虚拟机。 + +不管是过滤器,还是称重器,它们都需要两个参数 + +- hosts:多个 host_state + 的集合,包含有当前可用的计算节点信息(资源,ip等)。其中单个元素是 + HostState + (nova/scheduler/host_manager.py)类的实例。如果你想添加其他原来没有的信息,比如 + compute 的 id,可以在 ``_update_from_compute_node`` + 函数中添加。它会从compute_nodes 表中取得你想要的信息。 + + |image2| + +- spec_obj:你所要请求创建的虚拟机信息(模板,镜像等)。它是从 + objects.RequestSpec.from_primitives 中取得的 + + |image3| + +过滤器,它的代码如下: + +|image4| + +称重器,它的规则主要看这段代码。 + +|image5| + +我在代码中,加了几段日志。从左到右,三个不同颜色的内容分别为,原始权值,配重系数(越高说明越占比越大,越影响最终结果),经过 +nomalize 后的权值(只有 0 和 1)。 + +|image6| + +那最终的权值如何计算呢? + +1. 先计算每一个称重器后的权重: weights \* multipier +2. 最后按不同的compute 将权重相加起来。 + +nova-scheduler 选择到主机后,在日志中会打印三条DEBUG信息,可以据此查看 + +:: + + LOG.debug("Filtered %(hosts)s", {'hosts': hosts}) + LOG.debug("Weighed %(hosts)s", {'hosts': weighed_hosts}) + LOG.debug("Selected host: %(host)s", {'host': chosen_host}) + +.. |image0| image:: http://image.python-online.cn/20190424212211.png +.. |image1| image:: http://image.python-online.cn/20190424213430.png +.. |image2| image:: http://image.python-online.cn/20190424214653.png +.. |image3| image:: http://image.python-online.cn/20190424214540.png +.. |image4| image:: http://image.python-online.cn/20190424221602.png +.. |image5| image:: http://image.python-online.cn/20190424215735.png +.. |image6| image:: http://image.python-online.cn/20190424220008.png + diff --git a/source/c08/c08_13.rst b/source/c08/c08_13.rst new file mode 100644 index 0000000..552176d --- /dev/null +++ b/source/c08/c08_13.rst @@ -0,0 +1,184 @@ +8.13 网络知识必知必会 +===================== + +8.13.1 iptables +--------------- + +推荐文章:\ `朱双印的 iptables +系列文章 `__ + +概念相关 +~~~~~~~~ + +**动作** + +|image0| + +命令相关 +~~~~~~~~ + +.. code:: shell + + # 查看 filter 表的所有链 + iptables -t filter -L + iptables -L # 不指定,默认就为 filter + + # 查看其他表的规则 + iptables -t nat -L + iptables -t mangle -L + iptables -t raw -L + + # 从上面知道了如何查看指定表的规则,这边看看如何查看指定表里的某条链的规则 + # 只要在后面直接加上 <链名> 即可 + iptables -t nat -L POSTROUTING + +如果要查看更详细的信息,就再加个 -v 参数 + +.. code:: shell + + $ iptables -t nat -vL POSTROUTING + Chain POSTROUTING (policy ACCEPT 21M packets, 1241M bytes) + pkts bytes target prot opt in out source destination + 179K 12M SNAT all -- any eth0 172.20.22.0/24 anywhere to:175.xx.xx.177 + +多出来的几个字段如何理解呢?我摘取《\ `朱双印的个人日志 `__\ 》的解释到这边。 + +|image1| + +iptables +默认为我们配置了域名反解(根据ip解析成域名),这个过程效率很低,我们可以指定参数 +``-n`` 跳过这个过程,从下面的例子可以看到 ``in`` 从上面的 ``any`` 变成了 +``*`` (0.0.0.0) + +.. code:: shell + + $ iptables -t nat -nvL POSTROUTING + Chain POSTROUTING (policy ACCEPT 21M packets, 1241M bytes) + pkts bytes target prot opt in out source destination + 179K 12M SNAT all -- * eth0 172.20.22.0/24 0.0.0.0/0 to:175.xx.xx.177 + +如果你想要展示行号,可以指定 ``--line-numbers``\ , 在centos上可以缩短为 +``--line`` + +.. code:: shell + + $ iptables -t nat -nvL POSTROUTING + Chain POSTROUTING (policy ACCEPT 21M packets, 1241M bytes) + pkts bytes target prot opt in out source destination + 179K 12M SNAT all -- * eth0 172.20.22.0/24 0.0.0.0/0 to:175.xx.xx.177 + +从上面几个输出来看,pkts 和 bytes 都会自动转成 humanable +的单位。如果我们想看具体的数值,以方便查看变化,可以加个参数 ``-x`` + +.. code:: shell + + $ iptables -t nat -nvxL POSTROUTING + +如果你想清空某个表中的指定链的规则,比如清空 filter 表中的 input 链。 + +.. code:: shell + + iptables -F INPUT + +添加规则 + +.. code:: shell + + # ======================基本条件================== + $ iptables -t filter -I INPUT 2 -s 172.20.20.201 -j DROP + # -t : 指定 filter 表(不指定就默认filter) + # -I : 指定 INPUT 链,I 是 insert 即插入的意思 + # 2 : 指定插入位置,插入在第二行。 + # -s : 匹配规则,来源是 172.20.20.201 + # -j : 动作,丢弃 + + # ======================其他常见条件================== + + 还可以为你的规则添加其他的匹配条件 + -p : 匹配协议 + -m : 指定模块,引入其他模块的方法做匹配条件,如:-m tcp --dport 22,就是使用tcp扩展模块下的 --sport 22 做为匹配条件。 + + -s : 匹配源地址,也可以添加多个 -s 72.20.20.201,172.20.20.202,到iptables那会分成两条规则 + -d : 匹配目标地方,可以添加多个 -d 172.20.20.201,172.20.20.202,到iptables那会分成两条规则 + + --dport : 匹配目标端口,若要使用 --dport,必须指定 -p 协议类型 和 -m 模块类型, + --sport : 匹配源端口 + + # 指定多个端口 + -m multiport --dport 22,80-88,multiport只能用于 tcp 和 udp 协议,必须配置 -p tcp 或者 -p udp 使用 + + # ======================扩展模块================== + + # 匹配ip段 + -m iprange --src-range 172.20.20.10-172.20.20.20 + -m iprange --dst-range 172.20.20.10-172.20.20.20 + + # 链接数限制 + # 每个客户端ip ssh 的连接数最多为两个 + # --connlimit-mask 另外还用这个参数指定为哪个网段的ip进行限制 + -m connlimit --connlimit-above 2 -m tcp --dport 22 j REJECT + + # 匹配报文包含的内容 + # '-m string'表示使用string模块,'--algo bm'表示使用bm算法去匹配指定的字符串,其他可选项还有kmp,' --string "hello,world" '则表示我们想要匹配的字符串为"hello,world" + -m string --algo bm --string "hello,world" + + # 匹配连接数量,控制报文到达速率:http://www.zsythink.net/archives/1564 + -m limit --limit 10/minute + +删除规则 + +.. code:: shell + + # 删除 filter表、INPUT链的第三条规则 + $ iptables -t filter -D INPUT 3 + + # 指定匹配条件删除 + $ iptables -D INPUT -s 172.20.20.201 -j DROP + + # 删除某表中某条链的所有的规则 + $ iptables -t filter -F INPUT + +修改规则 + +.. code:: shell + + # 可用指定第几条规则进行修改,如果使用这种,记得匹配全条件。 + iptables -t fileter INPUT 2 -R -s 172.20.20.202 -j REJECT + + # 也可以先删除,再添加(更加靠谱) + iptables -t fileter -D INPUT 2 + iptables -t filter -I INPUT 2 -s 172.20.20.202 -j REJECT + + # 修改链的默认动作 + # 当报文没有命中规则,就按默认动作来做 + # 那如何更改默认动作呢? + iptables -t filter -P FORWARD DROP + +|image2| + +保存规则 + +.. code:: shell + + # 通过以上命令对规则的所有修改都是临时的,如果将iptables重启。修改就会失败。 + # 所以要将规则尽快地保存到配置文件中。 + + # 在 centos6 + service iptables save + + # 在centos7 + # iptables 是默认安装的,会用 firewall 代替 iptables + # 而iptables-service 需要用户自己安装。有了它,才能像centos6一样使用 + service iptables save + + # 输出规则到当前屏幕,并不会保存到配置文件 + iptables-save + + # 或者用重重向的方式输出到文件中 + iptables-save > /etc/sysconfig/iptables.bak + iptables-restore < /etc/sysconfig/iptables.bak + +.. |image0| image:: http://image.python-online.cn/20190706114314.png +.. |image1| image:: http://image.python-online.cn/20190706093904.png +.. |image2| image:: http://image.python-online.cn/20190706160632.png + From 399c2807d98cd596ee123d85f906c8123d61a787 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 16 Jul 2019 00:21:46 +0800 Subject: [PATCH 060/302] =?UTF-8?q?=E6=B7=BB=E5=8A=A0linux=E5=91=BD?= =?UTF-8?q?=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/c07_01.md | 52 ++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/source/c07/c07_01.md b/source/c07/c07_01.md index e719248..f522c1e 100644 --- a/source/c07/c07_01.md +++ b/source/c07/c07_01.md @@ -8,7 +8,8 @@ ``` . -> 当前目录 .. -> 上一级目录 -.file/.dir -> 隐藏文件/文件夹 +.file -> 隐藏文件 +.dir -> 文件夹 ``` 【ls】 查看指定目录文件 `ls`命令是最常用的linux命令,要配合着选项使用。 @@ -34,6 +35,7 @@ cd ~ # 家目录 cd # 家目录 cd .. # 上一目录 cd !$ # 将上命令的参数做为cd 参数 +cd ~zabbix # 进入zabbix用户的主目录 ``` 【touch】:新建文件 @@ -242,17 +244,6 @@ uniq -c 文件名 # 只显示有重复的行 uniq -d 文件名 ``` -#### 挑战题目 -``` -wget http://labfile.oss.aliyuncs.com/courses/1/data1 -# 里面是包含一些命令使用的列表 - -# 要求:从里面找出出现频率次数前3的命令并保存在/home/result - -$ cat data1 | cut -c 8-|cut -d " " -f 1 |sort | uniq -dc | sort -r -n -k1 | head -n 3 > /home/result - -``` - ### 1.3 文字处理 【tr】 @@ -423,7 +414,8 @@ nl -b a -n rz -w 3 text ``` ### 1.6 文件查找 -#### which:查询软件 +**which:查询软件** + 在PATH变量指定的路径中,搜索某个系统命令(`可执行文件`)的位置,并且返回第一个搜索结果。 参数选项(基本不用) @@ -434,7 +426,8 @@ nl -b a -n rz -w 3 text -V  显示版本信息 ``` -#### grep:搜索神器 +**grep:搜索神器** + 搜索并筛选显示结果。 该命令经常配合管道命令来控制输出。 以下 是常用的选项: @@ -454,7 +447,8 @@ $ grep -rnI "bingming" . # 当然这里也可以使用正则表达式 -I 忽略二进制文件 ``` -#### whreris:简单快速 +**whreris:简单快速** + 定位可执行文件、源代码文件、帮助文件在文件系统中的位置。 个搜索很快,因为它并没有从硬盘中依次查找,而是直接从数据库中查询。 ``` @@ -470,7 +464,7 @@ $ whereis who -M 指定搜索帮助文件的路径。 -S 指定搜索源代码文件的路径。 ``` -locate:快而全 +**locate:快而全** 通过`/var/lib/mlocate/mlocate.db`数据库查找,不过这个数据库也不是实时更新的,系统会使用定时任务每天自动执行`updatedb`命令更新一次,所以有时候你刚添加的文件,它可能会找不到,需要手动执行一次 `updatedb`命令(在我们的环境中必须先执行一次该命令)。 ``` @@ -479,7 +473,8 @@ $ locate /etc/sh # 查找/etc/目录下所有以sh开头的文件 ``` -#### find:小而细 +**find:小而细** + [鸟哥的Linux私房菜-find](http://linux.vbird.org/linux_basic/0220filemanager.php#find) [每天一个linux命令(19):find 命令概览](http://www.cnblogs.com/peida/archive/2012/11/13/2767374.html) [每天一个linux命令(20):find命令之exec](http://www.cnblogs.com/peida/archive/2012/11/14/2769248.html) @@ -1305,17 +1300,21 @@ Defaults:shiyanlou !requiretty **更改文件所有者和所属组** ``` -[sudo] chown 用户组:用户 文件/文件夹 +# [sudo] chown 用户组:用户 文件/文件夹,以下两种均可 +chown root:zabbix some.txt +chown root.zabbix some.txt -# 如下只更改所属用户 -[sudo] chowm 用户 文件/文件夹 +# 只更改所属用户,[sudo] chowm 用户 文件/文件夹 +chown zabbix some.txt + +# 只更改所属用户组,[sudo] chown .用户组 文件/文件夹 +chown .root some.txt ``` **修改文件权限** 文件权限有`读`、`写`、`执行`三种 分别对应数字4,2,1,也就是2^2,2^1,2^0 -![](https://dn-anything-about-doc.qbox.me/linux_base/3-14.png/logoblackfont) 如何修改文件权限 ``` @@ -1323,13 +1322,20 @@ Defaults:shiyanlou !requiretty chmod 777 文件 【第二种方法】:加减的方法 -g、o 还有 u 分别表示 group、others 和 user,+ 和 - 分别表示增加和去掉相应的权限。 +g、o、u、a分别表示 group、others、user和all ++、-、= 分别表示增加、去掉和设置相应的权限。 举个例子 比如一个文件权限是:-wr-wr-wr- chmod go-wr 文件 然后文件权限就变成:-wr------- + +再设置回原来的 +chmod ugo=wr 文件 + +或者将user和group改为可执行 +chmod ug=wrx,o=wr 文件 ``` **禁止修改、删除、移动文件** @@ -1494,8 +1500,6 @@ ip addr flush dev ens4 ## 五、Shell命令 - - ### 5.1 执行顺序控制 ``` 1、&& From 202fb0212d2400e5eb0844c558c345612a9d3f5d Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 16 Jul 2019 01:05:37 +0800 Subject: [PATCH 061/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20=E4=BA=91=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E7=9A=84=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/c07_01.rst | 53 ++++++++++++++++++++----------------------- source/c08/c08_04.md | 21 +++++++++-------- source/c08/c08_04.rst | 42 ++++++++++++++++++---------------- 3 files changed, 59 insertions(+), 57 deletions(-) diff --git a/source/c07/c07_01.rst b/source/c07/c07_01.rst index 81a8594..df51905 100755 --- a/source/c07/c07_01.rst +++ b/source/c07/c07_01.rst @@ -13,7 +13,8 @@ . -> 当前目录 .. -> 上一级目录 - .file/.dir -> 隐藏文件/文件夹 + .file -> 隐藏文件 + .dir -> 文件夹 【ls】 查看指定目录文件 ``ls``\ 命令是最常用的linux命令,要配合着选项使用。 @@ -43,6 +44,7 @@ cd # 家目录 cd .. # 上一目录 cd !$ # 将上命令的参数做为cd 参数 + cd ~zabbix # 进入zabbix用户的主目录 【touch】:新建文件 @@ -266,18 +268,6 @@ more、cat file \| head等。 # 只显示有重复的行 uniq -d 文件名 -挑战题目 -^^^^^^^^ - -:: - - wget http://labfile.oss.aliyuncs.com/courses/1/data1 - # 里面是包含一些命令使用的列表 - - # 要求:从里面找出出现频率次数前3的命令并保存在/home/result - - $ cat data1 | cut -c 8-|cut -d " " -f 1 |sort | uniq -dc | sort -r -n -k1 | head -n 3 > /home/result - 1.3 文字处理 ~~~~~~~~~~~~ @@ -473,8 +463,7 @@ awk是一个强大的文本分析工具。 1.6 文件查找 ~~~~~~~~~~~~ -which:查询软件 -^^^^^^^^^^^^^^^ +**which:查询软件** 在PATH变量指定的路径中,搜索某个系统命令(\ ``可执行文件``\ )的位置,并且返回第一个搜索结果。 @@ -487,8 +476,7 @@ which:查询软件 -w  指定输出时栏位的宽度。 -V  显示版本信息 -grep:搜索神器 -^^^^^^^^^^^^^^ +**grep:搜索神器** 搜索并筛选显示结果。 该命令经常配合管道命令来控制输出。 以下 是常用的选项: |image0| @@ -508,8 +496,7 @@ grep:搜索神器 -n 显示在文件中的第几行查询到 -I 忽略二进制文件 -whreris:简单快速 -^^^^^^^^^^^^^^^^^ +**whreris:简单快速** 定位可执行文件、源代码文件、帮助文件在文件系统中的位置。 个搜索很快,因为它并没有从硬盘中依次查找,而是直接从数据库中查询。 @@ -530,7 +517,7 @@ whreris:简单快速 -M 指定搜索帮助文件的路径。 -S 指定搜索源代码文件的路径。 -locate:快而全 +**locate:快而全** 通过\ ``/var/lib/mlocate/mlocate.db``\ 数据库查找,不过这个数据库也不是实时更新的,系统会使用定时任务每天自动执行\ ``updatedb``\ 命令更新一次,所以有时候你刚添加的文件,它可能会找不到,需要手动执行一次 ``updatedb``\ 命令(在我们的环境中必须先执行一次该命令)。 @@ -542,8 +529,7 @@ locate:快而全 # 查找/etc/目录下所有以sh开头的文件 -find:小而细 -^^^^^^^^^^^^ +**find:小而细** `鸟哥的Linux私房菜-find `__ `每天一个linux命令(19):find @@ -1430,15 +1416,20 @@ CentOS :: - [sudo] chown 用户组:用户 文件/文件夹 + # [sudo] chown 用户组:用户 文件/文件夹,以下两种均可 + chown root:zabbix some.txt + chown root.zabbix some.txt + + # 只更改所属用户,[sudo] chowm 用户 文件/文件夹 + chown zabbix some.txt - # 如下只更改所属用户 - [sudo] chowm 用户 文件/文件夹 + # 只更改所属用户组,[sudo] chown .用户组 文件/文件夹 + chown .root some.txt **修改文件权限** 文件权限有\ ``读``\ 、\ ``写``\ 、\ ``执行``\ 三种 -分别对应数字4,2,1,也就是2\ :sup:`2,2`\ 1,2^0 |image3| +分别对应数字4,2,1,也就是2\ :sup:`2,2`\ 1,2^0 如何修改文件权限 @@ -1448,7 +1439,8 @@ CentOS chmod 777 文件 【第二种方法】:加减的方法 - g、o 还有 u 分别表示 group、others 和 user,+ 和 - 分别表示增加和去掉相应的权限。 + g、o、u、a分别表示 group、others、user和all + +、-、= 分别表示增加、去掉和设置相应的权限。 举个例子 比如一个文件权限是:-wr-wr-wr- @@ -1456,6 +1448,12 @@ CentOS 然后文件权限就变成:-wr------- + 再设置回原来的 + chmod ugo=wr 文件 + + 或者将user和group改为可执行 + chmod ug=wrx,o=wr 文件 + **禁止修改、删除、移动文件** ``chattr -i``\ 和\ ``chattr +i`` @@ -1789,5 +1787,4 @@ CentOS .. |image0| image:: http://image.python-online.cn/17-9-20/47469030.jpg .. |image1| image:: http://image.python-online.cn/20190705182629.png .. |image2| image:: http://image.python-online.cn/17-10-15/97911325.jpg -.. |image3| image:: https://dn-anything-about-doc.qbox.me/linux_base/3-14.png/logoblackfont diff --git a/source/c08/c08_04.md b/source/c08/c08_04.md index fb984e1..5992458 100644 --- a/source/c08/c08_04.md +++ b/source/c08/c08_04.md @@ -20,6 +20,8 @@ 云,就是将这些零散实体资源变成一个巨大无比的资源池子,有了这个池子,做为个人用户,你不再需要自己你买一个电脑放在家里,做为小型公司,你不需要自己整一个机房,花很多的人力和设备成本去运营这些基础设施。一旦你需要,你就向池子拥有者申请即可。这极大的提高了资源的利用率,以及分配的灵活性。 +![](http://image.python-online.cn/20190716004341.png) + 还有人说,云就像是天上的云一样,聚焦的水汽多了就会下雨,落到地面的雨水又会蒸发到天上,继续等待下一次下雨。云计算里的云,正如大自然里的云一样,可以实现资源的循环利用。你在公有云提供商那里,购买了一年的云主机,一年后资源被回收,可以再分配给其他人使用。 云计算的模型,是以服务为导向的。根据服务层次的不同,可以分为三类: @@ -64,7 +66,11 @@ VMM,通常叫做 Hypervisor(下面我们也将以Hypervisor指代VMM),中文名:虚拟机监控器,英文全称:Virtual Machine Monitor。 -Hypervisor 是为了实现虚拟化而引入的一个介于虚拟机操作系统和物理资源的软件层。当虚拟机要对物理资源进行操作时,Hypervisor将对其指令进行截取并且重定向,让虚拟机无感知地像物理操作系统一样使用物理资源。 +Hypervisor 是为了实现虚拟化而引入的一个介于虚拟机操作系统和物理资源的软件层。 + +需要注意的是,Hypervisor并不是一款具体的软件,而是一类软件的统称。 + +当虚拟机要对物理资源进行操作时,Hypervisor将对其指令进行截取并且重定向,让虚拟机无感知地像物理操作系统一样使用物理资源。 常见的Hypervisor,有 @@ -121,7 +127,7 @@ QEMU是一套由Fabrice Bellard所编写的模拟处理器的自由软件。Qemu 目前,libvirt 已经成为使用最为广泛的对各种虚拟机进行管理的工具和应用程序接口(API),而且一些常用的虚拟机管理工具(如virsh、virt-install、virt-manager等)和云计算框架平台(如OpenStack、OpenNebula、Eucalyptus等)都在底层使用libvirt的应用程序接口。 -![来源网络,侵删](https://i.loli.net/2019/02/25/5c73e070ef9e4.jpg) +![](http://image.python-online.cn/20190716005951.png) ## 8.4.5 虚拟化分类 @@ -284,13 +290,6 @@ virsh start guest_vm - - - - - - - @@ -307,7 +306,7 @@ virsh start guest_vm /usr/libexec/qemu-kvm - + @@ -417,7 +416,9 @@ nova boot \ --config-drive True ``` +说了半天,线于引出了OpenStack,我的工作基本60%的时间都是围绕着它转,OpenStack 是一个开源框架,是使用Python语言开发的最大的项目,具说有数百万行的代码量,是动态语言的一个优秀典范。 +关于 OpenStck,你可能不太明白它是做什么的。这里引用我昨天看到的另一篇文章的一个说明:它有点像一个商店,负责管理所有的商品(计算资源、存储资源、网络资源等),卖给用户,但是它本身不制造商品(不具备虚拟化能力),它的商品来自KVM(当然也可以用Xen等其他Hypervisor)。 ## 附录:参考文档 diff --git a/source/c08/c08_04.rst b/source/c08/c08_04.rst index cccd81b..7d6bb48 100644 --- a/source/c08/c08_04.rst +++ b/source/c08/c08_04.rst @@ -23,6 +23,8 @@ computing**\ ),是一种基于\ `互联网 - - - - - - - @@ -354,7 +350,7 @@ virsh 创建 /usr/libexec/qemu-kvm - + @@ -468,6 +464,12 @@ OpenStack --image \ --config-drive True +说了半天,线于引出了OpenStack,我的工作基本60%的时间都是围绕着它转,OpenStack +是一个开源框架,是使用Python语言开发的最大的项目,具说有数百万行的代码量,是动态语言的一个优秀典范。 + +关于 +OpenStck,你可能不太明白它是做什么的。这里引用我昨天看到的另一篇文章的一个说明:它有点像一个商店,负责管理所有的商品(计算资源、存储资源、网络资源等),卖给用户,但是它本身不制造商品(不具备虚拟化能力),它的商品来自KVM(当然也可以用Xen等其他Hypervisor)。 + 附录:参考文档 -------------- @@ -484,7 +486,9 @@ OpenStack .. |image0| image:: http://image.python-online.cn/20190714161353.png -.. |image1| image:: http://image.python-online.cn/FjlPaQLTiYCde92WhurWsRx6z8CK -.. |image2| image:: http://image.python-online.cn/20190714141644.png -.. |image3| image:: https://i.loli.net/2019/02/25/5c73e6160764a.png +.. |image1| image:: http://image.python-online.cn/20190716004341.png +.. |image2| image:: http://image.python-online.cn/FjlPaQLTiYCde92WhurWsRx6z8CK +.. |image3| image:: http://image.python-online.cn/20190716005951.png +.. |image4| image:: http://image.python-online.cn/20190714141644.png +.. |image5| image:: https://i.loli.net/2019/02/25/5c73e6160764a.png From 8e1649bd504340c0067ed6b8045e1ade92a32a4d Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 16 Jul 2019 10:22:28 +0800 Subject: [PATCH 062/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20cloudinit=20?= =?UTF-8?q?=E7=9A=84=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_05.md | 17 +++++++++++++++++ source/c08/c08_06.md | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 9acfb67..365c053 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -454,6 +454,23 @@ isolate +## 8.5.18 生成config drive + +![](http://image.python-online.cn/20190708100902.png) + + + +![](http://image.python-online.cn/20190708103119.png) + +``` +network_metadata + +inst_md.ip_info +# {'fixed_ip6s': [u'2001:1001::3'], 'fixed_ips': [], 'floating_ips': []} +``` + +![1562565550118](C:\Users\wangbm\AppData\Roaming\Typora\typora-user-images\1562565550118.png) + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_06.md b/source/c08/c08_06.md index 6a861da..1ed2d23 100644 --- a/source/c08/c08_06.md +++ b/source/c08/c08_06.md @@ -342,6 +342,14 @@ myjob.cfg:text/upstart-job ![](http://image.python-online.cn/20190623091911.png) + + +## 8.6.7 虚拟机启动卡住 + +当创建一个有数据盘的虚拟机,nova会在configdrive里的ec2目录下生成 meta-data.json ,其中有一个字段是block-device-mapping,包含磁盘信息。在虚拟机创建后,如果 /etc/cloud/cloud.cfg里配置了 mounts,cloudinit会根据这个这下面文件中的 ephemeral0 拿到对应的 /dev/vdb,并将其写入 /etc/fstab 中。在下次重启时,会根据 fstab 挂载磁盘,如果挂载不上,就会导致虚拟机启动卡住。 + +![](http://image.python-online.cn/20190708175813.png) + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file From da08f304830a51454152324a9e88012fddc8198d Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 16 Jul 2019 11:42:20 +0800 Subject: [PATCH 063/302] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20ansible=20api=20?= =?UTF-8?q?=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/c07_09.md | 2 +- source/c07/c07_10.md | 175 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 source/c07/c07_10.md diff --git a/source/c07/c07_09.md b/source/c07/c07_09.md index 7d20eff..0a0db5d 100644 --- a/source/c07/c07_09.md +++ b/source/c07/c07_09.md @@ -1,4 +1,4 @@ -# 7.9 Ansible 使用总结 +# 7.9 Ansible 入门指南使用手册 diff --git a/source/c07/c07_10.md b/source/c07/c07_10.md new file mode 100644 index 0000000..365fa3a --- /dev/null +++ b/source/c07/c07_10.md @@ -0,0 +1,175 @@ +# 7.10 Ansible API 最全使用文档(中文) + +大家都知道 Ansible 是一个轻量级的部署工具,这里的轻量体现在哪里呢? + +master 与 节点间使用ssh通信,也不需要像salt那样需要在客户端装minion,然后在master和minion各起一个服务。 + +在以前 ansible 都是使用的命令行来执行playbook,但在数百个分布式集群部署,如果一台一台登陆进去使用命令行部署,效率极低。 + +因此,如果能使用 api 的方式进行playbook的调用,再对 api 做一层封装,会极大的提高效率。 + +无意中使用 pip search 进行搜索,还真的有 ansible-api + +![](http://image.python-online.cn/20190716111523.png) + +这是 2018 年10月份才开始的项目,目前来说成熟度还不够。 + +对于 ansible-api 有几点需要注意: + +1. ansible-api 需要依赖 python3.7+ 和 最新的 openssl,对ansible环境发动较大。 +2. ansible-api 成熟度还不够,在部署过程中,有遇到几个坑,需要修改源码解决。 +3. ansible-api 返回结果为json串,会包含所有节点的所有playbook 的所有task 的信息,虽然全,但信息量可能过大。 + + + +## 7.10.1 基础环境准备 + +使用最新的 openssl ,这一点让我在部署时候花了很多的时间,踩了很多的坑。在这里进行总结整理。 + +先根据( openssl 也要升级到 1.1.1 + +```shell +# 安装依赖库 +yum install -y zlib zlib-dev openssl-devel sqlite-devel bzip2-devel libffi libffi-devel gcc gcc-c++ + +# 安装最新版本的openssl +wget http://www.openssl.org/source/openssl-1.1.1.tar.gz +tar -zxvf openssl-1.1.1.tar.gz +cd openssl-1.1.1 +./config --prefix=$HOME/openssl shared zlib +make && make install + +# 设置环境变量LD_LIBRARY_PATH +echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/openssl/lib" >> $HOME/.bash_profile +source $HOME/.bash_profile +``` + +ansible-api 要求 python3.7,所以需要再安装 python3.7 + +```shell +mkdir /root/python37 && cd /root/Python-3.7.2 && wget https://www.python.org/ftp/python/3.7.2/Python-3.7.2.tgz && tar -xvf Python-3.7.2.tgz + + +cd /root/Python-3.7.2 && mkdir /usr/local/python3 +./configure --prefix=/usr/local/python3 --with-openssl=$HOME/openssl + +make && make install + +ln -s /usr/local/python3/bin/python3 /usr/bin/python3 +ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3 +``` + + + +## 7.10.2 部署 ansible-api + +手动配置一下 ansible-api 的配置 + +```shell +# ============================================== +# Config file for Ansible-Api +# +# A restful HTTP API for ansible +# I am not a part of ansible official code +# ============================================== + +## +# Base configuration part +# +[default] + +#listen host +host = 0.0.0.0 + +#listen port +port = 8765 + +#signature string for api call +sign_key = wangbm + +#log path if using daemon mode +log_path = /var/log/ansible-api.log + +#worker number (default: 1) +workers = 1 + +#response of a task will be timeout (sec, default: 3600 [1 hour], task will NOT break off after timeout) +timeout = 3600 + +#websocket subprotocols +ws_sub = + +#ip white list (multiple separated by space, leave a blank for all allowed) +allow_ip = + +## +# the path part for playbooks and scripts +# +[directory] + +#your playbook path (file *.yml in this dir will be worked) +playbook = /root/deployment/ + +#your script path (file *.sh in this dir will be worked) +script = +``` + +ansible-api 会调用 ansible 库的命令,这个过程不能指定 ansible.cfg 的文件路径(其默认是从 `/etc/ansible/ansible.cfg` 读取)。所以需要将我们当前的配置文件(`/root/deployment/ansible.cfg`)拷贝至`/etc/ansible/ansible.cfg` + +然后由于原生的 ansible-api 的bug,需要修改代码,在如下函数位置(`/usr/local/python3/lib/python3.7/site-packages/ansible_api/callback.py`)添加一个参数 + +![](http://image.python-online.cn/20190716112113.png) + +通过执行命令,即可开启 ansible server + +```shell +/usr/local/python3/bin/ansible-api -c /etc/ansible/api.cfg -d & +``` + +个人使用命令行启动的方式,不太优雅,可以写一个 service 文件,用服务的方式进行管理。 + +服务开启后,如何调用呢? + +在 github 仓库里,有提供一个简易的文档(https://github.com/lfbear/ansible-api/wiki/http-api-usage) + +你可以使用 postman 进行测试,也可以使用 curl 发送请求: + +``` +curl -X POST \ + http://127.0.0.1:8765/playbook \ + -H 'cache-control: no-cache' \ + -d '{ + "n": "wangbm", # playbook 的名字 + "h": "all", # 要执行 playbook 的节点 + "f": "backup_info.yml", # yml文件名字,不需要使用绝对路径 + "s": "735f88138d00c7eda6271f96fe99fa45", # 数字签名 + "c": 5 +}' +``` + +里面的参数都好理解,就 `s` 这个参数,这里要注意一下。 + +还记得我在ansible-api.cfg 的配置里,有一个配置项是 `sign_key=wangbm` + +那这里的 `s` 怎么计算呢? + +可以使用如下这条shell命令 + +```shell +shell :echo -n 'wangbmlocalhostbackup_info.ymlwangbm'|md5sum |cut -d ' ' -f1 +``` + +发送了请求后,返回的结果如下 + +![](http://image.python-online.cn/20190716112824.png) + +rc 为0,表示所有节点都没有出现 fatal 致命错误(有设置 ignore_errors 的错误也会返回0). + +rc 为非0,表示有 fatal 致命错误,说明有部分节点部署/升级失败。 + +![](http://image.python-online.cn/20190716112838.png) + +--- + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) + From bee4dc538e7e67c97105a15828a671fbceb8fb35 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 16 Jul 2019 18:17:07 +0800 Subject: [PATCH 064/302] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=8C=E7=BB=B4?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_11.md | 3 +++ source/c08/c08_12.md | 3 +++ source/c08/c08_13.md | 3 +++ 3 files changed, 9 insertions(+) diff --git a/source/c08/c08_11.md b/source/c08/c08_11.md index 3b76095..2485803 100644 --- a/source/c08/c08_11.md +++ b/source/c08/c08_11.md @@ -21,3 +21,6 @@ service systemd-machined restart service libvirtd restart ``` + + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_12.md b/source/c08/c08_12.md index 75b424e..2f25dfc 100644 --- a/source/c08/c08_12.md +++ b/source/c08/c08_12.md @@ -58,3 +58,6 @@ LOG.debug("Weighed %(hosts)s", {'hosts': weighed_hosts}) LOG.debug("Selected host: %(host)s", {'host': chosen_host}) ``` +--- + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_13.md b/source/c08/c08_13.md index d4833a1..00f8871 100644 --- a/source/c08/c08_13.md +++ b/source/c08/c08_13.md @@ -173,3 +173,6 @@ iptables-restore < /etc/sysconfig/iptables.bak +--- + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file From 5ae633323fd4ab49473d34d180e3f528092f1c3c Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 16 Jul 2019 18:17:24 +0800 Subject: [PATCH 065/302] =?UTF-8?q?=E6=96=B0=E5=A2=9Eipv6=E6=96=87?= =?UTF-8?q?=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_14.md | 117 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 source/c08/c08_14.md diff --git a/source/c08/c08_14.md b/source/c08/c08_14.md new file mode 100644 index 0000000..ac463b8 --- /dev/null +++ b/source/c08/c08_14.md @@ -0,0 +1,117 @@ +# 8.14 OpenStack 支持 ipv6 + +## 8.14.1 如何配置 ipv6 + +命令配置临时ip + +```shell +# 配置ip +ifconfig eth0 add 2408:xx:xx::2/120 + +# 配置网关 +sudo route -A inet6 add ::/0 gw 2408:xx:xx::1 +``` + +通过配置文件 + +```shell +DEVICE=eth0 +ONBOOT=yes +TYPE=Ethernet +BOOTPROTO=static +IPADDR=124.xx.xx.xx.2 +NETMASK=255.255.255.0 +GATEWAY=124.xx.xx.xx.2 +IPV6INIT=yes +IPV6_DEFAULTGW=2408:xx:xx::1 +IPV6ADDR_SECONDARIES="2408:xx:xx::2/120" +``` + +配置好ip和网关后,如何查看效果? + +```shell +# 查看ip +ip addr + +# 查看网关 +route -A inet6 + +# 查看网络连通性 +ping6 +``` + +## 8.14.2 nova 使用 ipv6 + +通过如下代码可知 + +![](http://image.python-online.cn/20190716175250.png) + +OpenStack 在生成 ConfigureDrive默认不开启ipv6,若要使用ipv6,需要在计算节点的 nova.conf 中添加配置 + +```shell +[default] +use_ipv6=true +``` + +使用ipv6,当然要先创建一个ipv6的子网,这里要注意,ipv6子网所属的网络,必须为flat(默认不指定就为vlan) + +```shell +neutron net-create --provider:physical_network phynet0 --provider:network_type flat ipv6_public +``` + +然后再创建一个子网 + +创建子网的时候,要注意自己是否要使用 dhcp + +如果使用static,使用这条命令 + +```shell +neutron subnet-create --name subnet_v6 --disable-dhcp \ + --ip-version 6 \ + --allocation-pool start=2408:xx:xx:0:0:0:0:2,end=2408:xx:xx:0:0:0:0:6 \ + --gateway 2408:xx:xx:0:0:0:0:1 \ + ipv6_public 2408:xx:xx:0000::/64 +``` + +如果使用dhcp,使用下面命令。 + +```shell +neutron subnet-create --name subnet_v6 --enable-dhcp \ + --ip-version 6 --ipv6-ra-mode dhcpv6-stateful \ + --allocation-pool start=2408:xx:xx:0:0:0:0:2,end=2408:xx:xx:0:0:0:0:5 \ + --gateway 2408:xx:xx:0:0:0:0:1 \ + --ipv6-address-mode dhcpv6-stateful \ + ipv6_public 2408:xx:xx:0000::/120 +``` + +一切都准备好了,就指定这个子网创建虚拟机了。 + +登陆出来的虚拟机,会发现cloud-init已经将ipv6的ip配置上去了,说明cloudinit本身就支持ipv6,无需额外修改和配置。 + +## 8.14.3 单张网卡多个ip + +接下来,要验证的一点是,nova 和 cloud-init 是否支持在一张网卡上配置多个ip呢(一个ipv4一个ipv6). + +nova 的http api 接口是不能直接支持的,但是可以通过先创建一个port,在这个port上指定两个ip。 + +然后创建虚拟机时,指定这个port去创建。 + +登陆一下虚拟机,查看下效果,你可以发现,两个ip如预期一样配置在一张网卡上了。 + +![](http://image.python-online.cn/20190716180952.png) + +一张网卡一个ip 和 一张网卡多个ip 的configdrive 有什么区别呢? + +如果是一张网卡两个ip,那么cloudinit会将同一步tap设备的归为一个同一张网卡。 + +![](http://image.python-online.cn/20190716180655.png) + +cloudinit 解析后,与每张网卡各一个ip的区别如下。 + +![](http://image.python-online.cn/20190716180726.png) + + + +--- + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file From 593bccb1048f2c39b3b5ce07b6e649363af67a0c Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 16 Jul 2019 23:15:14 +0800 Subject: [PATCH 066/302] =?UTF-8?q?=E7=94=9F=E6=88=90=20rst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/c07_09.rst | 4 +- source/c07/c07_10.rst | 195 ++++++++++++++++++++++++++++++++++++++++++ source/c08/c08_05.rst | 21 +++++ source/c08/c08_06.rst | 14 +++ source/c08/c08_11.rst | 4 + source/c08/c08_12.rst | 6 ++ source/c08/c08_13.rst | 6 ++ source/c08/c08_14.rst | 131 ++++++++++++++++++++++++++++ 8 files changed, 379 insertions(+), 2 deletions(-) create mode 100644 source/c07/c07_10.rst create mode 100644 source/c08/c08_14.rst diff --git a/source/c07/c07_09.rst b/source/c07/c07_09.rst index 0ae222d..0959b1c 100644 --- a/source/c07/c07_09.rst +++ b/source/c07/c07_09.rst @@ -1,5 +1,5 @@ -7.9 Ansible 使用总结 -==================== +7.9 Ansible 入门指南使用手册 +============================ :: diff --git a/source/c07/c07_10.rst b/source/c07/c07_10.rst new file mode 100644 index 0000000..eb7206f --- /dev/null +++ b/source/c07/c07_10.rst @@ -0,0 +1,195 @@ +7.10 Ansible API 最全使用文档(中文) +===================================== + +大家都知道 Ansible 是一个轻量级的部署工具,这里的轻量体现在哪里呢? + +master 与 +节点间使用ssh通信,也不需要像salt那样需要在客户端装minion,然后在master和minion各起一个服务。 + +在以前 ansible +都是使用的命令行来执行playbook,但在数百个分布式集群部署,如果一台一台登陆进去使用命令行部署,效率极低。 + +因此,如果能使用 api 的方式进行playbook的调用,再对 api +做一层封装,会极大的提高效率。 + +无意中使用 pip search 进行搜索,还真的有 ansible-api + +|image0| + +这是 2018 年10月份才开始的项目,目前来说成熟度还不够。 + +对于 ansible-api 有几点需要注意: + +1. ansible-api 需要依赖 python3.7+ 和 最新的 + openssl,对ansible环境发动较大。 +2. ansible-api + 成熟度还不够,在部署过程中,有遇到几个坑,需要修改源码解决。 +3. ansible-api 返回结果为json串,会包含所有节点的所有playbook 的所有task + 的信息,虽然全,但信息量可能过大。 + +7.10.1 基础环境准备 +------------------- + +使用最新的 openssl +,这一点让我在部署时候花了很多的时间,踩了很多的坑。在这里进行总结整理。 + +先根据(\ `https://www.jianshu.com/p/3ec24f563b81)将 `__ +openssl 也要升级到 1.1.1 + +.. code:: shell + + # 安装依赖库 + yum install -y zlib zlib-dev openssl-devel sqlite-devel bzip2-devel libffi libffi-devel gcc gcc-c++ + + # 安装最新版本的openssl + wget http://www.openssl.org/source/openssl-1.1.1.tar.gz + tar -zxvf openssl-1.1.1.tar.gz + cd openssl-1.1.1 + ./config --prefix=$HOME/openssl shared zlib + make && make install + + # 设置环境变量LD_LIBRARY_PATH + echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/openssl/lib" >> $HOME/.bash_profile + source $HOME/.bash_profile + +ansible-api 要求 python3.7,所以需要再安装 python3.7 + +.. code:: shell + + mkdir /root/python37 && cd /root/Python-3.7.2 && wget https://www.python.org/ftp/python/3.7.2/Python-3.7.2.tgz && tar -xvf Python-3.7.2.tgz + + + cd /root/Python-3.7.2 && mkdir /usr/local/python3 + ./configure --prefix=/usr/local/python3 --with-openssl=$HOME/openssl + + make && make install + + ln -s /usr/local/python3/bin/python3 /usr/bin/python3 + ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3 + +7.10.2 部署 ansible-api +----------------------- + +手动配置一下 ansible-api 的配置 + +.. code:: shell + + # ============================================== + # Config file for Ansible-Api + # + # A restful HTTP API for ansible + # I am not a part of ansible official code + # ============================================== + + ## + # Base configuration part + # + [default] + + #listen host + host = 0.0.0.0 + + #listen port + port = 8765 + + #signature string for api call + sign_key = wangbm + + #log path if using daemon mode + log_path = /var/log/ansible-api.log + + #worker number (default: 1) + workers = 1 + + #response of a task will be timeout (sec, default: 3600 [1 hour], task will NOT break off after timeout) + timeout = 3600 + + #websocket subprotocols + ws_sub = + + #ip white list (multiple separated by space, leave a blank for all allowed) + allow_ip = + + ## + # the path part for playbooks and scripts + # + [directory] + + #your playbook path (file *.yml in this dir will be worked) + playbook = /root/deployment/ + + #your script path (file *.sh in this dir will be worked) + script = + +ansible-api 会调用 ansible 库的命令,这个过程不能指定 ansible.cfg +的文件路径(其默认是从 ``/etc/ansible/ansible.cfg`` +读取)。所以需要将我们当前的配置文件(\ ``/root/deployment/ansible.cfg``\ )拷贝至\ ``/etc/ansible/ansible.cfg`` + +然后由于原生的 ansible-api +的bug,需要修改代码,在如下函数位置(\ ``/usr/local/python3/lib/python3.7/site-packages/ansible_api/callback.py``\ )添加一个参数 + +|image1| + +通过执行命令,即可开启 ansible server + +.. code:: shell + + /usr/local/python3/bin/ansible-api -c /etc/ansible/api.cfg -d & + +个人使用命令行启动的方式,不太优雅,可以写一个 service +文件,用服务的方式进行管理。 + +服务开启后,如何调用呢? + +在 github +仓库里,有提供一个简易的文档(https://github.com/lfbear/ansible-api/wiki/http-api-usage) + +你可以使用 postman 进行测试,也可以使用 curl 发送请求: + +:: + + curl -X POST \ + http://127.0.0.1:8765/playbook \ + -H 'cache-control: no-cache' \ + -d '{ + "n": "wangbm", # playbook 的名字 + "h": "all", # 要执行 playbook 的节点 + "f": "backup_info.yml", # yml文件名字,不需要使用绝对路径 + "s": "735f88138d00c7eda6271f96fe99fa45", # 数字签名 + "c": 5 + }' + +里面的参数都好理解,就 ``s`` 这个参数,这里要注意一下。 + +还记得我在ansible-api.cfg 的配置里,有一个配置项是 ``sign_key=wangbm`` + +那这里的 ``s`` 怎么计算呢? + +可以使用如下这条shell命令 + +.. code:: shell + + shell :echo -n 'wangbmlocalhostbackup_info.ymlwangbm'|md5sum |cut -d ' ' -f1 + +发送了请求后,返回的结果如下 + +|image2| + +rc 为0,表示所有节点都没有出现 fatal 致命错误(有设置 ignore_errors +的错误也会返回0). + +rc 为非0,表示有 fatal 致命错误,说明有部分节点部署/升级失败。 + +|image3| + +-------------- + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + + +.. |image0| image:: http://image.python-online.cn/20190716111523.png +.. |image1| image:: http://image.python-online.cn/20190716112113.png +.. |image2| image:: http://image.python-online.cn/20190716112824.png +.. |image3| image:: http://image.python-online.cn/20190716112838.png + diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index c792884..718ea80 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -501,6 +501,25 @@ isolate |image36| +8.5.18 生成config drive +----------------------- + +|image37| + +|image38| + +:: + + network_metadata + + inst_md.ip_info + # {'fixed_ip6s': [u'2001:1001::3'], 'fixed_ips': [], 'floating_ips': []} + +.. figure:: C:\Users\wangbm\AppData\Roaming\Typora\typora-user-images\1562565550118.png + :alt: 1562565550118 + + 1562565550118 + -------------- .. figure:: http://image.python-online.cn/20190511161447.png @@ -544,4 +563,6 @@ isolate .. |image34| image:: http://image.python-online.cn/20190627213044.png .. |image35| image:: http://image.python-online.cn/20190627213609.png .. |image36| image:: http://image.python-online.cn/20190627215038.png +.. |image37| image:: http://image.python-online.cn/20190708100902.png +.. |image38| image:: http://image.python-online.cn/20190708103119.png diff --git a/source/c08/c08_06.rst b/source/c08/c08_06.rst index 76014ee..8912b08 100644 --- a/source/c08/c08_06.rst +++ b/source/c08/c08_06.rst @@ -398,6 +398,19 @@ cirename0 的网卡。 |image22| +8.6.7 虚拟机启动卡住 +-------------------- + +当创建一个有数据盘的虚拟机,nova会在configdrive里的ec2目录下生成 +meta-data.json +,其中有一个字段是block-device-mapping,包含磁盘信息。在虚拟机创建后,如果 +/etc/cloud/cloud.cfg里配置了 mounts,cloudinit会根据这个这下面文件中的 +ephemeral0 拿到对应的 /dev/vdb,并将其写入 /etc/fstab +中。在下次重启时,会根据 fstab +挂载磁盘,如果挂载不上,就会导致虚拟机启动卡住。 + +|image23| + -------------- .. figure:: http://image.python-online.cn/20190511161447.png @@ -427,4 +440,5 @@ cirename0 的网卡。 .. |image20| image:: http://image.python-online.cn/20190429205735.png .. |image21| image:: http://image.python-online.cn/20190430232911.png .. |image22| image:: http://image.python-online.cn/20190623091911.png +.. |image23| image:: http://image.python-online.cn/20190708175813.png diff --git a/source/c08/c08_11.rst b/source/c08/c08_11.rst index e6163a5..4d1da10 100644 --- a/source/c08/c08_11.rst +++ b/source/c08/c08_11.rst @@ -23,5 +23,9 @@ service systemd-machined restart service libvirtd restart +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + + .. |image0| image:: http://image.python-online.cn/20190530175817.png diff --git a/source/c08/c08_12.rst b/source/c08/c08_12.rst index 5ff7041..a5173d2 100644 --- a/source/c08/c08_12.rst +++ b/source/c08/c08_12.rst @@ -74,6 +74,12 @@ nova-scheduler 选择到主机后,在日志中会打印三条DEBUG信息,可 LOG.debug("Weighed %(hosts)s", {'hosts': weighed_hosts}) LOG.debug("Selected host: %(host)s", {'host': chosen_host}) +-------------- + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + + .. |image0| image:: http://image.python-online.cn/20190424212211.png .. |image1| image:: http://image.python-online.cn/20190424213430.png .. |image2| image:: http://image.python-online.cn/20190424214653.png diff --git a/source/c08/c08_13.rst b/source/c08/c08_13.rst index 552176d..3cf4f4d 100644 --- a/source/c08/c08_13.rst +++ b/source/c08/c08_13.rst @@ -178,6 +178,12 @@ iptables iptables-save > /etc/sysconfig/iptables.bak iptables-restore < /etc/sysconfig/iptables.bak +-------------- + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + + .. |image0| image:: http://image.python-online.cn/20190706114314.png .. |image1| image:: http://image.python-online.cn/20190706093904.png .. |image2| image:: http://image.python-online.cn/20190706160632.png diff --git a/source/c08/c08_14.rst b/source/c08/c08_14.rst new file mode 100644 index 0000000..051a8dc --- /dev/null +++ b/source/c08/c08_14.rst @@ -0,0 +1,131 @@ +8.14 OpenStack 支持 ipv6 +======================== + +8.14.1 如何配置 ipv6 +-------------------- + +命令配置临时ip + +.. code:: shell + + # 配置ip + ifconfig eth0 add 2408:xx:xx::2/120 + + # 配置网关 + sudo route -A inet6 add ::/0 gw 2408:xx:xx::1 + +通过配置文件 + +.. code:: shell + + DEVICE=eth0 + ONBOOT=yes + TYPE=Ethernet + BOOTPROTO=static + IPADDR=124.xx.xx.xx.2 + NETMASK=255.255.255.0 + GATEWAY=124.xx.xx.xx.2 + IPV6INIT=yes + IPV6_DEFAULTGW=2408:xx:xx::1 + IPV6ADDR_SECONDARIES="2408:xx:xx::2/120" + +配置好ip和网关后,如何查看效果? + +.. code:: shell + + # 查看ip + ip addr + + # 查看网关 + route -A inet6 + + # 查看网络连通性 + ping6 + +8.14.2 nova 使用 ipv6 +--------------------- + +通过如下代码可知 + +|image0| + +OpenStack 在生成 +ConfigureDrive默认不开启ipv6,若要使用ipv6,需要在计算节点的 nova.conf +中添加配置 + +.. code:: shell + + [default] + use_ipv6=true + +使用ipv6,当然要先创建一个ipv6的子网,这里要注意,ipv6子网所属的网络,必须为flat(默认不指定就为vlan) + +.. code:: shell + + neutron net-create --provider:physical_network phynet0 --provider:network_type flat ipv6_public + +然后再创建一个子网 + +创建子网的时候,要注意自己是否要使用 dhcp + +如果使用static,使用这条命令 + +.. code:: shell + + neutron subnet-create --name subnet_v6 --disable-dhcp \ + --ip-version 6 \ + --allocation-pool start=2408:xx:xx:0:0:0:0:2,end=2408:xx:xx:0:0:0:0:6 \ + --gateway 2408:xx:xx:0:0:0:0:1 \ + ipv6_public 2408:xx:xx:0000::/64 + +如果使用dhcp,使用下面命令。 + +.. code:: shell + + neutron subnet-create --name subnet_v6 --enable-dhcp \ + --ip-version 6 --ipv6-ra-mode dhcpv6-stateful \ + --allocation-pool start=2408:xx:xx:0:0:0:0:2,end=2408:xx:xx:0:0:0:0:5 \ + --gateway 2408:xx:xx:0:0:0:0:1 \ + --ipv6-address-mode dhcpv6-stateful \ + ipv6_public 2408:xx:xx:0000::/120 + +一切都准备好了,就指定这个子网创建虚拟机了。 + +登陆出来的虚拟机,会发现cloud-init已经将ipv6的ip配置上去了,说明cloudinit本身就支持ipv6,无需额外修改和配置。 + +8.14.3 单张网卡多个ip +--------------------- + +接下来,要验证的一点是,nova 和 cloud-init +是否支持在一张网卡上配置多个ip呢(一个ipv4一个ipv6). + +nova 的http api +接口是不能直接支持的,但是可以通过先创建一个port,在这个port上指定两个ip。 + +然后创建虚拟机时,指定这个port去创建。 + +登陆一下虚拟机,查看下效果,你可以发现,两个ip如预期一样配置在一张网卡上了。 + +|image1| + +一张网卡一个ip 和 一张网卡多个ip 的configdrive 有什么区别呢? + +如果是一张网卡两个ip,那么cloudinit会将同一步tap设备的归为一个同一张网卡。 + +|image2| + +cloudinit 解析后,与每张网卡各一个ip的区别如下。 + +|image3| + +-------------- + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + + +.. |image0| image:: http://image.python-online.cn/20190716175250.png +.. |image1| image:: http://image.python-online.cn/20190716180952.png +.. |image2| image:: http://image.python-online.cn/20190716180655.png +.. |image3| image:: http://image.python-online.cn/20190716180726.png + From 6a5fdf1e4106ffeb29fbcbf9246f7c0cd80f8496 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Fri, 19 Jul 2019 14:49:39 +0800 Subject: [PATCH 067/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B9=A6=E7=AD=BE?= =?UTF-8?q?=E9=A1=B5=E5=8F=8A=E5=AF=BC=E8=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 ++ source/bookmark.rst | 172 +++++++++++++++++++------------------------ source/c03/c03_06.md | 2 +- source/c08/c08_04.md | 8 +- 4 files changed, 87 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index a89de3b..9b9c616 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ - 1.16 [泛型函数怎么写?](http://python-online.cn/zh_CN/latest/c01/c01_16.html) - 1.17 [深入理解「描述符」](http://python-online.cn/zh_CN/latest/c01/c01_17.html) - 1.18 [MySQL使用总结](http://python-online.cn/zh_CN/latest/c01/c01_18.html) +- 1.19 [MySQL 使用总结](http://python-online.cn/zh_CN/latest/c01/c01_18.html) +- 1.20 [静态方法其实暗藏玄机](http://python-online.cn/zh_CN/latest/c01/c01_20.html) +- 1.21 [开发小技巧](http://python-online.cn/zh_CN/latest/c01/c01_21.html) ## 第二章:并发编程 - 2.1 [从性能角度来初探并发编程](http://python-online.cn/zh_CN/latest/c02/c02_01.html) @@ -92,6 +95,7 @@ - 7.7 [SaltStack 入门指南](http://python-online.cn/zh_CN/latest/c07/c07_07.html) - 7.8 [Keepalived 部署文档](http://python-online.cn/zh_CN/latest/c07/c07_08.html) - 7.9 [Ansible使用总结](http://python-online.cn/zh_CN/latest/c07/c07_09.html) +- 7.10 [Ansible API 最全使用文档(中文)](http://python-online.cn/zh_CN/latest/c07/c07_10.html) ## 第八章:OpenStack @@ -105,6 +109,9 @@ - 8.8 [OpenStack 如何使用DHCP?](http://python-online.cn/zh_CN/latest/c08/c08_08.html) - 8.9 [全面理解RPC远程调用](http://python-online.cn/zh_CN/latest/c08/c08_09.html) - 8.11 [OpenStack问题排查](http://python-online.cn/zh_CN/latest/c08/c08_11.html) +- 8.12 [OpenStack之主机调度](http://python-online.cn/zh_CN/latest/c08/c08_12.html) +- 8.13 [网络知识必知必会](http://python-online.cn/zh_CN/latest/c08/c08_13.html) +- 8.14 [OpenStack 支持 ipv6](http://python-online.cn/zh_CN/latest/c08/c08_14.html) ## LeetCode Challenge diff --git a/source/bookmark.rst b/source/bookmark.rst index 65e8288..93fdb5f 100755 --- a/source/bookmark.rst +++ b/source/bookmark.rst @@ -13,33 +13,8 @@ Python电子教程 - `w3school&runoob教程合集-1:囊括世面各种语言 `__ - `w3school&runoob教程合集-2:囊括世面各种语言 `__ -Python参考文档 -~~~~~~~~~~~~~~ - -- `PEP8 代码规范 `__ -- `Pycharm - 快捷键指南 `__ -- `Python 语言参考 - - 中文 `__ -- `Python 语言参考 - - 英文 `__ -- `Python 标准库文档 - - 中文 `__ -- `Python 标准库文档 - 英文 `__ -- `Python - 资源大全中文版:Awesome-Python `__ -- `Python / C - API参考手册 `__ -- `Python Cookbook 3rd Edition - Documentation `__ -- `Twisted与异步编程入门 - - 中文文档 `__ - -Linux学堂 ---------- - -基础入门 -~~~~~~~~ +Linux入门 +~~~~~~~~~~~ - `本人Linux学习笔记:持续更新 `__ - `LINUX:命令行的艺术(Github) `__ @@ -47,69 +22,53 @@ Linux学堂 - `基础学习|每天一个Linux命令 `__ - `基础学习|《Linux云计算从入门到精通》系列实战笔记 `__ -在线资源 -~~~~~~~~ +云计算入门 +~~~~~~~~~~~ -- `Linux|离线包大全 `__ -- `Ubuntu|中文维基 `__ -- `CentOS|系统镜像(各版本) `__ -- `Linux|网易镜像源大全 `__ -- `Centos \| - 清华大学开源软件镜像站 `__ -- `Ubuntu|清华大学开源软件镜像站 `__ +- `CloudMan:致力于云计算学习和实践 `__ +- `每天5分钟玩转OpenStack `__ +- `每天5分钟玩转 Docker容器技术 `__ -云计算 ------- -入门教材 -~~~~~~~~ +数据库入门 +~~~~~~~~~~~ + +- `数据库|MySQL50题练习建库脚本 `__ +- `数据库|MySQL 50题练习答案 `__ + +正则表达式入门 +~~~~~~~~~~~~~~~ + +- `正则表达式|语法 - 基础教材 `__ +- `正则表达式|Everything的使用 `__ +- `Git|廖雪峰的 Git 教程 `__ +- `Git|Git-Book 教程 `__ -- `CloudMan:致力于云计算学习和实践 `__ -- `每天5分钟玩转 - OpenStack `__ -- `每天5分钟玩转 Docker - 容器技术 `__ 集群管理 ~~~~~~~~ -- `虚拟化管理 之 virsh 命令 - - 官方文档 `__ -- `crmsh 命令 官方文档 - - 英文 `__ -- `corosync pacemaker crmsh crm 实例 - 详解 `__ -- `HAProxy 使用手册(官方) - - 英文 `__ -- `HAProxy 使用手册(翻译) - - 中文 `__ - -开发工具 --------- - -正则表达式 -~~~~~~~~~~ +- `虚拟化管理 之 virsh 命令 - 官方文档 `__ +- `crmsh 命令 官方文档 - 英文 `__ +- `corosync pacemaker crmsh crm 实例详解 `__ +- `HAProxy 使用手册(官方) - 英文 `__ +- `HAProxy 使用手册(翻译) - 中文 `__ -- `正则表达式的语法 - - 基础教材 `__ -- `正则表达式 - Everything - 的使用 `__ -数据库 -~~~~~~ +Python参考文档 +~~~~~~~~~~~~~~ -- `MySQL 50题练习 - 建库脚本 `__ -- `MySQL 50题练习 - 答案 `__ +- `PEP8 代码规范 `__ +- `Python 语言参考 - 中文 `__ +- `Python 语言参考 - 英文 `__ +- `Python 标准库文档 - 中文 `__ +- `Python 标准库文档 - 英文 `__ +- `Python 资源大全中文版:Awesome-Python `__ +- `Python / C API参考手册 `__ +- `Python Cookbook 3rd Edition Documentation `__ +- `Twisted与异步编程入门 - 中文文档 `__ -Github -~~~~~~ -- `廖雪峰的 Git - 教程 `__ -- `Git-Book - 教程 `__ 在线工具 -------- @@ -117,43 +76,48 @@ Github 编程相关 ~~~~~~~~ -- `在线Markdown:Md2All `__ -- `在线Markdown:Cmd Markdown `__ +- `在线Markdown|Md2All `__ +- `在线Markdown|Cmd Markdown `__ - `在线编程 - 1:包含各种主流语言 `__ - `在线编程 - 2:包含各种主流语言 `__ -- `Smallpdf.com – - 您所有PDF问题的免费解决方案 `__ -- `Pycharm - 破解下载及激活方法 `__ -- `Pycharm 激活 Licence - Server `__ - -其他工具 -~~~~~~~~ - +- `PDF|Smallpdf.com – 您所有PDF问题的免费解决方案 `__ +- `Pycharm|破解下载及激活方法 `__ +- `Pycharm|激活 Licence Server `__ +- `代码美化|Carbon `__ +- `变量命名|CODELF `__ +- `代码美化|JSON `__ +- `Base64 编码 `__ +- `Linux|命令大全 `__ +- `数据库|库表可视化设计 `__ +- `IP地址|ipv4计算器 `__ +- `IP地址|ipv6计算器 `__ +- `文件中转|7天无限下载 `__ +- `项目推荐|HelloGithub `__ +- `工具集合 - MIKU `__ +- `工具集合 - 小森林导航 `__ - `吾皇控制台:KingFast `__ - `kiwivm:VPS管理后台 `__ - `Rufus - 轻松创建U盘启动盘 `__ -- `Screen To Gif - - 轻松制作gif动画 `__ +- `Screen To Gif - 轻松制作gif动画 `__ - `USER.ME - 在线PS/CAD/PPT/EXCEL/AI/XMind/Visio `__ - `五笔拆字图解:解决你的五笔问题 `__ - `SM.MS - Simple Free Image Hosting `__ - `集思会:一个电子书免费推送网站 `__ - `书伴:教你如何玩转Kindle的网站 `__ + 精品软件 --------- +~~~~~~~~ - `精品绿色便携软件 `__ - `我最喜欢的软件 Windows 版 - 小众软件 `__ - `MSDN, 我告诉你:微软产品下载中心 `__ - `大眼仔旭 - 爱软件 爱汉化 爱分享 `__ -- `Windows Apps That Amaze Us:Windows - 绝赞应用 `__ +- `Windows Apps That Amaze Us:Windows 绝赞应用 `__ +- `精品 Mac 软件分享 `__ 信息查询 --------- +~~~~~~~~ - `中国人民银行征信中心 `__ - `国家企业信用信息公示系统 `__ @@ -167,12 +131,24 @@ Github - `网站测速:全国地区 `__ - `TinEye - 最专业的以图搜图网站 `__ + +在线资源 +~~~~~~~~ + +- `Linux|离线包大全 `__ +- `Ubuntu|中文维基 `__ +- `CentOS|系统镜像(各版本) `__ +- `Linux|网易镜像源大全 `__ +- `Centos|清华大学开源软件镜像站 `__ +- `Ubuntu|清华大学开源软件镜像站 `__ +- `Python|各版本 Python下载源 `__ + + 设计素材 --------- +~~~~~~~~ - `IconFinder `__ - `easyIcon `__ - `Iconfont-阿里巴巴矢量图标库 `__ - `Icons for everything - Noun Project `__ -- `Font - Awesome,一套绝佳的图标字体库和CSS框架 `__ +- `Font Awesome,一套绝佳的图标字体库和CSS框架 `__ diff --git a/source/c03/c03_06.md b/source/c03/c03_06.md index f464a1d..92b4d77 100644 --- a/source/c03/c03_06.md +++ b/source/c03/c03_06.md @@ -1,4 +1,4 @@ -# 3.6 从0到1:全面理解WSGI +# 3.6 Web开发者必看:理解WSGI 在 三百六十行,行行转 IT 的现状下,很多来自各行各业的同学,都选择 Python 这门胶水语言做为踏入互联网大门的第一块敲门砖,在这些人里,又有相当大比例的同学选择了 Web 开发这个方向(包括我)。而从事 web 开发,绕不过一个知识点,就是 WSGI。 diff --git a/source/c08/c08_04.md b/source/c08/c08_04.md index 5992458..581cce1 100644 --- a/source/c08/c08_04.md +++ b/source/c08/c08_04.md @@ -4,9 +4,11 @@ 我回答说,云计算。 -不回答还好,一回答倒使他们更加蒙圈。听着挺高大上,挺牛b的。就是不知道是什么东西。为了解答大部分朋友的问题,我想着写这么一篇文章,对云计算和虚拟化这块入门级知识做一个梳理,如果刚好你也想进入这个领域,正好,这份入门通识指南,应该挺适合你的。 +不回答还好,一回答,他们更加疑惑了。听着挺高大上,挺牛b的,就是不知道是什么。在大多数Pythonista的认知里,学会了Python,不从事爬虫,就是做Web开发,不然就是数据分析/挖掘,人工智能的。 -这是本文的大纲,你可以根据大纲内容,选择是否继续浏览,或者根据大纲内容快速跳转至你感兴趣的内容。 +想一想,这也是合理的,大部分培训班为你让你交学费,一般都会把你的职业路线给规划好,什么Web开发,什么爬虫工程师,什么数据分析师等等,却没人告诉你,学会了Python,也可以去做一名云计算工程师(准确地说应该是OpenStack工程师,因为云计算涉及的范围更广,需要的技术栈更多,而不单单是一门编程语言)。 + +为了让你多了解一些云计算的内容,我想着写这么一篇文章,介绍一下我从事的领域,同时也对云计算和虚拟化这块入门级知识做一个梳理,如果刚好你也想进入这个领域,这份入门通识指南,应该挺适合你的。 ![](http://image.python-online.cn/20190714161353.png) @@ -216,7 +218,7 @@ KVM这种流行的虚拟化技术里,既有软件虚拟化,也有硬件虚 ![](https://i.loli.net/2019/02/25/5c73e6160764a.png) -## 8.4.5 创建虚拟机 +## 8.4.7 创建虚拟机 ### 手工创建 From 999aed8bf1ddcc73dfaa409b52a911a1e40d5624 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Fri, 19 Jul 2019 21:36:52 +0800 Subject: [PATCH 068/302] update --- source/c08/c08_05.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 365c053..df6e58a 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -471,6 +471,12 @@ inst_md.ip_info ![1562565550118](C:\Users\wangbm\AppData\Roaming\Typora\typora-user-images\1562565550118.png) +## 8.5.19 修改nova-api的接口 + +nova api 的接口参数是在 nova/api/openstack/compute/schemas/ 目录下,如创建虚拟机的接口如下: + +![](http://image.python-online.cn/20190719170825.png) + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file From f99e7d3d3fb709d254f6a55bbd9ff18decefef8f Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 21 Jul 2019 19:33:37 +0800 Subject: [PATCH 069/302] make rst --- source/bookmark.rst | 2 -- source/c03/c03_06.rst | 4 ++-- source/c04/c04_03.md | 2 +- source/c04/c04_03.rst | 2 +- source/c08/c08_04.rst | 8 +++++--- source/c08/c08_05.rst | 9 +++++++++ 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/source/bookmark.rst b/source/bookmark.rst index 93fdb5f..37b61e1 100755 --- a/source/bookmark.rst +++ b/source/bookmark.rst @@ -95,8 +95,6 @@ Python参考文档 - `项目推荐|HelloGithub `__ - `工具集合 - MIKU `__ - `工具集合 - 小森林导航 `__ -- `吾皇控制台:KingFast `__ -- `kiwivm:VPS管理后台 `__ - `Rufus - 轻松创建U盘启动盘 `__ - `Screen To Gif - 轻松制作gif动画 `__ - `USER.ME - 在线PS/CAD/PPT/EXCEL/AI/XMind/Visio `__ diff --git a/source/c03/c03_06.rst b/source/c03/c03_06.rst index f3e7cd7..95c8c52 100644 --- a/source/c03/c03_06.rst +++ b/source/c03/c03_06.rst @@ -1,5 +1,5 @@ -3.6 从0到1:全面理解WSGI -======================== +3.6 Web开发者必看:理解WSGI +=========================== 在 三百六十行,行行转 IT 的现状下,很多来自各行各业的同学,都选择 Python 这门胶水语言做为踏入互联网大门的第一块敲门砖,在这些人里,又有相当大比例的同学选择了 diff --git a/source/c04/c04_03.md b/source/c04/c04_03.md index 2d4025b..1b1e6a3 100644 --- a/source/c04/c04_03.md +++ b/source/c04/c04_03.md @@ -4,7 +4,7 @@ 10个优秀的程序员里,有9个人都有写博客的习惯。这是非常好的习惯,值得每个程序员,投入时间和精力去坚持做下去。 -写博客的平台有很多,CSDN,博客园,51CTO,还有人会使用Hexo+GitHub,WorkPress,比较会折腾的人还会自己使用Java,Python搭建,我就干过这样的事,不过每年还要支付域名和服务器,比较麻烦而且浪费钱。 +写博客的平台有很多,CSDN,博客园,51CTO,还有人会使用Hexo+GitHub,WordPress,比较会折腾的人还会自己使用Java,Python搭建,我就干过这样的事,不过每年还要支付域名和服务器,比较麻烦而且浪费钱。 以上博客我都有注册使用过,不过最终还是放弃。博客文章,比较零散,无法形成一个系统性的知识体系,不便索引。 diff --git a/source/c04/c04_03.rst b/source/c04/c04_03.rst index dc9101e..0c2921d 100755 --- a/source/c04/c04_03.rst +++ b/source/c04/c04_03.rst @@ -5,7 +5,7 @@ 10个优秀的程序员里,有9个人都有写博客的习惯。这是非常好的习惯,值得每个程序员,投入时间和精力去坚持做下去。 -写博客的平台有很多,CSDN,博客园,51CTO,还有人会使用Hexo+GitHub,WorkPress,比较会折腾的人还会自己使用Java,Python搭建,我就干过这样的事,不过每年还要支付域名和服务器,比较麻烦而且浪费钱。 +写博客的平台有很多,CSDN,博客园,51CTO,还有人会使用Hexo+GitHub,WordPress,比较会折腾的人还会自己使用Java,Python搭建,我就干过这样的事,不过每年还要支付域名和服务器,比较麻烦而且浪费钱。 以上博客我都有注册使用过,不过最终还是放弃。博客文章,比较零散,无法形成一个系统性的知识体系,不便索引。 diff --git a/source/c08/c08_04.rst b/source/c08/c08_04.rst index 7d6bb48..942c073 100644 --- a/source/c08/c08_04.rst +++ b/source/c08/c08_04.rst @@ -5,9 +5,11 @@ 我回答说,云计算。 -不回答还好,一回答倒使他们更加蒙圈。听着挺高大上,挺牛b的。就是不知道是什么东西。为了解答大部分朋友的问题,我想着写这么一篇文章,对云计算和虚拟化这块入门级知识做一个梳理,如果刚好你也想进入这个领域,正好,这份入门通识指南,应该挺适合你的。 +不回答还好,一回答,他们更加疑惑了。听着挺高大上,挺牛b的,就是不知道是什么。在大多数Pythonista的认知里,学会了Python,不从事爬虫,就是做Web开发,不然就是数据分析/挖掘,人工智能的。 -这是本文的大纲,你可以根据大纲内容,选择是否继续浏览,或者根据大纲内容快速跳转至你感兴趣的内容。 +想一想,这也是合理的,大部分培训班为你让你交学费,一般都会把你的职业路线给规划好,什么Web开发,什么爬虫工程师,什么数据分析师等等,却没人告诉你,学会了Python,也可以去做一名云计算工程师(准确地说应该是OpenStack工程师,因为云计算涉及的范围更广,需要的技术栈更多,而不单单是一门编程语言)。 + +为了让你多了解一些云计算的内容,我想着写这么一篇文章,介绍一下我从事的领域,同时也对云计算和虚拟化这块入门级知识做一个梳理,如果刚好你也想进入这个领域,这份入门通识指南,应该挺适合你的。 |image0| @@ -253,7 +255,7 @@ KVM这种流行的虚拟化技术里,既有软件虚拟化,也有硬件虚 |image5| -8.4.5 创建虚拟机 +8.4.7 创建虚拟机 ---------------- 手工创建 diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index 718ea80..8e2a1f7 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -520,6 +520,14 @@ isolate 1562565550118 +8.5.19 修改nova-api的接口 +------------------------- + +nova api 的接口参数是在 nova/api/openstack/compute/schemas/ +目录下,如创建虚拟机的接口如下: + +|image39| + -------------- .. figure:: http://image.python-online.cn/20190511161447.png @@ -565,4 +573,5 @@ isolate .. |image36| image:: http://image.python-online.cn/20190627215038.png .. |image37| image:: http://image.python-online.cn/20190708100902.png .. |image38| image:: http://image.python-online.cn/20190708103119.png +.. |image39| image:: http://image.python-online.cn/20190719170825.png From 7b25d4c9c06ef5a9fb0aa5a44d5f465f3e79eeb5 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 21 Jul 2019 19:33:54 +0800 Subject: [PATCH 070/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20pycharm=20?= =?UTF-8?q?=E6=8A=80=E5=B7=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_15.md | 89 ++++++++++++++++++++++++++++++++- source/c04/c04_15.rst | 112 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 198 insertions(+), 3 deletions(-) diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index bf1811d..6ee7ec6 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -1,4 +1,4 @@ -# 4.15 25个 PyCharm 小技巧 +# 4.15 30个 PyCharm 实用技巧 刚开始做公众号的时候,更新频率正常是一周两到三篇。老读者应该有注意到,以前都是写系列教程,对于读者而言,系列教程会更加友好,学习起来会更容易深入浅出,而对于作者来说,写系列教程,更有一种使命感,而这种使命感是维持更新一大动力。 @@ -684,6 +684,93 @@ PyCharm 打开一个文件,就占用一个标签面。 如果你嫌这快捷键太长了,可以使用 `鼠标中键` 点击这个类,可以达到同样的效果。 +## 4.15.26 文件差异,轻松比对 + +程序开发必备神器中,beyond compare 绝对可以排一号。 + +虽说好用,但这东西,是收费的。 + +如果是简单的单个文件的比对,其实可以使用PyCharm里自带的。 + +点击源文件,再点击`View` -> `Compare With …` -> 选择目标文件 + +对比示例,可以查看下面这张图,UI做的还是挺好看的。 + +![](http://image.python-online.cn/20190721125739.png) + +## 4.15.27 以列为单位的块编辑 + +先给你出道小题,像下面这段代码,如果在不影响代码的情况下,快速删除后面代码后面的注释呢? + +![](http://image.python-online.cn/20190721132238.png) + +我能想到的有两种方法,如果像如上这种有规律的注释,可以使用 `正则匹配` + `替换` 来实现。 + +![](http://image.python-online.cn/20190721133403.png) + +对于这个场景我想到了可以用 vim来轻松的解决,vim 支持块编辑,可以以列为单位选择区域然后进行操作,这在vim中是很常用的一个取消注释的操作。 + +同样回到 PyCharm 中来,你会发现它也支持块编辑。 + +当你按住 alt(windows)或者option(mac),然后使用鼠标进行选择,你会发现这样一件神奇的事情。 + +![](https://i.loli.net/2019/07/21/5d3401410087b61815.gif) + +## 4.15.28 智能补全,忽略大小写 + +智能搜索补全,是IDE的最吸引人的功能之一。 + +当你的对象是以大写字母开头时,而你使用小写字母编写代码时,是不能查找到该函数的,你必须得先切换成大写再输入一遍。 + +![](http://image.python-online.cn/20190721141327.png) + +如何避免这种尴尬的情况? + +只要在配置中关闭大小写匹配即可。 + +![](http://image.python-online.cn/20190721141653.png) + +效果如下: + +![](http://image.python-online.cn/20190721141751.png) + +## 4.15.29 保护眼睛,从PyCharm开始 + +记得刚毕业时,进入的第一家公司,有一个小姐姐,她把自己的电脑中对保护眼睛有用的配置都研究了一番。 + +其中就有程序的护眼色,什么word,excel,文件管理器,浏览器,能更改背景色的,全部设置个遍。 + +不能不说,合理的背景色,确实对保护眼睛有一定的作用,但个人觉得最重要的还是合理适时的休息。 + +这里就教大家如何设置 PyCharm 的背景色为护眼色,方法如下: + +![](http://image.python-online.cn/20190721143450.png) + +设置护眼色,会降低 PyCharm 的顔值,这需要你从中取一个取舍。 + +关于提高 PyCharm 顔值的,你可以参考我之前写的这篇文章: + +[手把手教你打造一个顔值超高的IDE](https://mp.weixin.qq.com/s/KdGasLFs4M_Tass6fqgwKQ) + +## 4.15.30 调试远程服务器的代码 + +一般情况下,我们开发调试都是在个人PC上完成,遇到问题,开一下 `Pycharm` 的调试器,很快就能找到问题所在。 + +可有些时候,项目代码的运行会对运行环境有依赖,必须在部署了相关依赖组件的服务器上才可以运行,这就直接导致了我们不能在本地进行调试。 + +对于这种特殊的场景,就我所知,有如下两种解决方案: + +- pdb +- 远程调试 + +关于 pdb,之前也写过专门的文章介绍使用方法,你可以点此查看:[无图形界面的代码调试方法 - pdb](https://mp.weixin.qq.com/s/tDufSUBrBBNfMEr5_dxM0g) + +而远程调试呢,是让我们可以在我们在 PC 上用 Pycharm 的图形化界面来进行调试远方服务器上代码,它和本地调试没有太大的区别,原来怎么调试的现在还是怎么调试。 + +区别就在于,本地调试不需要事前配置,只要你的代码准备好了,随时可以开始 Debug ,而远程调试需要不少前置步骤。 + +而这些配置步骤还挺多的,我专门写了一篇文章介绍设置过程,你可以点此查看:[图文教程|不能不会的远程调试技巧](https://mp.weixin.qq.com/s/ECWCJMQ6oEDaY1x1JfGfkg) + ## 附录 - [PyCharm 快捷键 Mac 版 ](https://resources.jetbrains.com/storage/products/pycharm/docs/PyCharm_ReferenceCard_mac.pdf) diff --git a/source/c04/c04_15.rst b/source/c04/c04_15.rst index fdb1897..94a6088 100644 --- a/source/c04/c04_15.rst +++ b/source/c04/c04_15.rst @@ -1,5 +1,5 @@ -4.15 25个 PyCharm 小技巧 -======================== +4.15 30个 PyCharm 实用技巧 +========================== 刚开始做公众号的时候,更新频率正常是一周两到三篇。老读者应该有注意到,以前都是写系列教程,对于读者而言,系列教程会更加友好,学习起来会更容易深入浅出,而对于作者来说,写系列教程,更有一种使命感,而这种使命感是维持更新一大动力。 @@ -817,6 +817,106 @@ PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文 如果你嫌这快捷键太长了,可以使用 ``鼠标中键`` 点击这个类,可以达到同样的效果。 +4.15.26 文件差异,轻松比对 +-------------------------- + +程序开发必备神器中,beyond compare 绝对可以排一号。 + +虽说好用,但这东西,是收费的。 + +如果是简单的单个文件的比对,其实可以使用PyCharm里自带的。 + +点击源文件,再点击\ ``View`` -> ``Compare With …`` -> 选择目标文件 + +对比示例,可以查看下面这张图,UI做的还是挺好看的。 + +|image75| + +4.15.27 以列为单位的块编辑 +-------------------------- + +先给你出道小题,像下面这段代码,如果在不影响代码的情况下,快速删除后面代码后面的注释呢? + +|image76| + +我能想到的有两种方法,如果像如上这种有规律的注释,可以使用 ``正则匹配`` ++ ``替换`` 来实现。 + +|image77| + +对于这个场景我想到了可以用 vim来轻松的解决,vim +支持块编辑,可以以列为单位选择区域然后进行操作,这在vim中是很常用的一个取消注释的操作。 + +同样回到 PyCharm 中来,你会发现它也支持块编辑。 + +当你按住 +alt(windows)或者option(mac),然后使用鼠标进行选择,你会发现这样一件神奇的事情。 + +|image78| + +4.15.28 智能补全,忽略大小写 +---------------------------- + +智能搜索补全,是IDE的最吸引人的功能之一。 + +当你的对象是以大写字母开头时,而你使用小写字母编写代码时,是不能查找到该函数的,你必须得先切换成大写再输入一遍。 + +|image79| + +如何避免这种尴尬的情况? + +只要在配置中关闭大小写匹配即可。 + +|image80| + +效果如下: + +|image81| + +4.15.29 保护眼睛,从PyCharm开始 +------------------------------- + +记得刚毕业时,进入的第一家公司,有一个小姐姐,她把自己的电脑中对保护眼睛有用的配置都研究了一番。 + +其中就有程序的护眼色,什么word,excel,文件管理器,浏览器,能更改背景色的,全部设置个遍。 + +不能不说,合理的背景色,确实对保护眼睛有一定的作用,但个人觉得最重要的还是合理适时的休息。 + +这里就教大家如何设置 PyCharm 的背景色为护眼色,方法如下: + +|image82| + +设置护眼色,会降低 PyCharm 的顔值,这需要你从中取一个取舍。 + +关于提高 PyCharm 顔值的,你可以参考我之前写的这篇文章: + +`手把手教你打造一个顔值超高的IDE `__ + +4.15.30 调试远程服务器的代码 +---------------------------- + +一般情况下,我们开发调试都是在个人PC上完成,遇到问题,开一下 ``Pycharm`` +的调试器,很快就能找到问题所在。 + +可有些时候,项目代码的运行会对运行环境有依赖,必须在部署了相关依赖组件的服务器上才可以运行,这就直接导致了我们不能在本地进行调试。 + +对于这种特殊的场景,就我所知,有如下两种解决方案: + +- pdb +- 远程调试 + +关于 +pdb,之前也写过专门的文章介绍使用方法,你可以点此查看:\ `无图形界面的代码调试方法 +- pdb `__ + +而远程调试呢,是让我们可以在我们在 PC 上用 Pycharm +的图形化界面来进行调试远方服务器上代码,它和本地调试没有太大的区别,原来怎么调试的现在还是怎么调试。 + +区别就在于,本地调试不需要事前配置,只要你的代码准备好了,随时可以开始 +Debug ,而远程调试需要不少前置步骤。 + +而这些配置步骤还挺多的,我专门写了一篇文章介绍设置过程,你可以点此查看:\ `图文教程|不能不会的远程调试技巧 `__ + 附录 ---- @@ -907,4 +1007,12 @@ PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文 .. |image72| image:: http://image.python-online.cn/20190629224229.png .. |image73| image:: http://image.python-online.cn/20190629224430.png .. |image74| image:: http://image.python-online.cn/20190629231322.png +.. |image75| image:: http://image.python-online.cn/20190721125739.png +.. |image76| image:: http://image.python-online.cn/20190721132238.png +.. |image77| image:: http://image.python-online.cn/20190721133403.png +.. |image78| image:: https://i.loli.net/2019/07/21/5d3401410087b61815.gif +.. |image79| image:: http://image.python-online.cn/20190721141327.png +.. |image80| image:: http://image.python-online.cn/20190721141653.png +.. |image81| image:: http://image.python-online.cn/20190721141751.png +.. |image82| image:: http://image.python-online.cn/20190721143450.png From 5d6efc48a79c4e40da833daf1be05106bc845a66 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 4 Aug 2019 11:27:12 +0800 Subject: [PATCH 071/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0ipv6=E6=96=87?= =?UTF-8?q?=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_05.md | 16 ++++ source/c08/c08_14.md | 191 +++++++++++++++++++++++++++++++++++++------ source/c08/c08_15.md | 33 ++++++++ 3 files changed, 213 insertions(+), 27 deletions(-) create mode 100644 source/c08/c08_15.md diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index df6e58a..eb3f479 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -477,6 +477,22 @@ nova api 的接口参数是在 nova/api/openstack/compute/schemas/ 目录下, ![](http://image.python-online.cn/20190719170825.png) + + +这些参数字段后面的类型是用来做参数类型的校验的。 + +在application 函数的头部,可以发现有如下这几种装饰器, + +![](http://image.python-online.cn/20190730140551.png) + +装饰器 schemas 的定义如下: + +![](http://image.python-online.cn/20190730142527.png) + +比如我们使用的 novaclient 发出的请求,是 v2.3.7的,所以 create() 顶部的五个schema 装饰器,上面四个都会空跑,不会进行校验,只有最后一个才会进入检验逻辑。 + +![](http://image.python-online.cn/20190730143003.png) + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_14.md b/source/c08/c08_14.md index ac463b8..aa8eead 100644 --- a/source/c08/c08_14.md +++ b/source/c08/c08_14.md @@ -1,8 +1,8 @@ -# 8.14 OpenStack 支持 ipv6 +# 8.14 支持 IPv6以及多运营商 -## 8.14.1 如何配置 ipv6 +## 8.14.1 手工如何配置 IPv6 -命令配置临时ip +使用命令设置临时的IPv6 ```shell # 配置ip @@ -12,7 +12,25 @@ ifconfig eth0 add 2408:xx:xx::2/120 sudo route -A inet6 add ::/0 gw 2408:xx:xx::1 ``` -通过配置文件 +通过配置文件永久设置IPv6 + +```shell +DEVICE=eth0 +BOOTPROTO=none +DEFROUTE=yes +HWADDR=fa:16:3e:4a:4f:55 +IPV6ADDR=2408:xx:xx::4/64 # IPv6 地址 +IPV6INIT=yes +IPV6_DEFAULTGW=2408:xx:xx::1 +MTU=1500 +ONBOOT=yes +TYPE=Ethernet +USERCTL=no +``` + + + +一张网卡上,可以允许同时存在IPv4及IPv6,配置方法如下 ```shell DEVICE=eth0 @@ -27,43 +45,83 @@ IPV6_DEFAULTGW=2408:xx:xx::1 IPV6ADDR_SECONDARIES="2408:xx:xx::2/120" ``` -配置好ip和网关后,如何查看效果? +一张网卡上,也可以通过子接口的试,配置多个(>=2个)的IPv6,配置方法如下: + +```shell +DEVICE=eth0 +BOOTPROTO=none +DEFROUTE=yes +HWADDR=fa:16:3e:4a:4f:55 +IPV6ADDR=2408:xx:xx::4/64 # IPv6 地址 +IPV6INIT=yes +IPV6_DEFAULTGW=2408:xx:xx::1 +MTU=1500 +ONBOOT=yes +TYPE=Ethernet +USERCTL=no + +# 若要配置子接口(下面以配置两个子接口为例) +IPV6ADDR_SECONDARIES="2408:xx:xx::6/120 +2408:xx:xx::7/120" +``` + + + +配置好IPv6和网关后,如何查看效果呢?这和IPv4有点不一样。 + +在IPv4中 + +```shell +# 查看ip +ip addr + +# 查看网关 +route -n 或者 route -4 + +# 查看网络连通性 +ping6 +``` + +而在IPv6中 ```shell # 查看ip ip addr # 查看网关 -route -A inet6 +route -A inet6 或者 route -6 # 查看网络连通性 ping6 ``` -## 8.14.2 nova 使用 ipv6 -通过如下代码可知 -![](http://image.python-online.cn/20190716175250.png) +## 8.14.2 Nova 如何使用 IPv6 -OpenStack 在生成 ConfigureDrive默认不开启ipv6,若要使用ipv6,需要在计算节点的 nova.conf 中添加配置 +原生的 Nova 和 Neutron 就已经支持 IPv6 了。 -```shell +对于 Nova 来说,需要在计算节点的配置中开启 IPv6 + +```ini [default] use_ipv6=true ``` -使用ipv6,当然要先创建一个ipv6的子网,这里要注意,ipv6子网所属的网络,必须为flat(默认不指定就为vlan) +才能将 IPv6 的 Port 信息写入ConfigDrive,也只有这样cloudinit才能自动为我们配置上 IPv6 的 ip。 + +![](http://image.python-online.cn/20190716175250.png) + +而对于 Neutron 来说,要使用 IPv6,当然要先创建一个 IPv6 的子网,这里要注意,IPv6 子网所属的网络,必须为flat,这个一定要注意,因为你不指定的话,默认就为 vlan。 ```shell +# 创建网络 neutron net-create --provider:physical_network phynet0 --provider:network_type flat ipv6_public ``` -然后再创建一个子网 +然后再创建一个子网,创建子网的时候,要确认下自己是否要使用 dhcp -创建子网的时候,要注意自己是否要使用 dhcp - -如果使用static,使用这条命令 +如果使用static,则使用这条命令 ```shell neutron subnet-create --name subnet_v6 --disable-dhcp \ @@ -84,34 +142,113 @@ neutron subnet-create --name subnet_v6 --enable-dhcp \ ipv6_public 2408:xx:xx:0000::/120 ``` -一切都准备好了,就指定这个子网创建虚拟机了。 +一切都准备好了,就可以指定这个子网创建虚拟机了。 -登陆出来的虚拟机,会发现cloud-init已经将ipv6的ip配置上去了,说明cloudinit本身就支持ipv6,无需额外修改和配置。 +登陆出来的虚拟机,会发现cloud-init已经将ipv6的ip配置上去了,说明 cloudinit 本身就支持 IPv6,无需额外修改和配置。 -## 8.14.3 单张网卡多个ip +## 8.14.3 一张网卡配置多个IP -接下来,要验证的一点是,nova 和 cloud-init 是否支持在一张网卡上配置多个ip呢(一个ipv4一个ipv6). +有这个需求的,通常是这两种场景: -nova 的http api 接口是不能直接支持的,但是可以通过先创建一个port,在这个port上指定两个ip。 +1、要配置双线IP,比如移动和电信各一个IP -然后创建虚拟机时,指定这个port去创建。 +2、要配置多个版本的IP,一个IPv4和一个IPv6 -登陆一下虚拟机,查看下效果,你可以发现,两个ip如预期一样配置在一张网卡上了。 -![](http://image.python-online.cn/20190716180952.png) -一张网卡一个ip 和 一张网卡多个ip 的configdrive 有什么区别呢? +通过第一节,我们已经知道,可以通过配置直接实现。 + + + +但是,在 OpenStack 中是否就已经支持这样的需求呢? + +经过验证可以得知 + +**第一点:** + +不管哪种场景,对于 OpenStack 来说,都是一样的,就是一个 Port 上指定两个IP,而这两个IP,是多线还是多版本,由你来定。 + +```shell +neutron port-create --fixed-ip \ + subnet_id=0d7f753d-3a8f-46d4-b931-2843b138388a,ip_address=175.xx.xx.11 \ + --fixed-ip subnet_id=36cdc565-4807-44bb-b6f6-8ee3f4114193,\ + ip_address=2408:xx.xx::11 you_network +``` + + + +不过要注意的是,Nova 本身的 api 接口是不支持指定一个Port上指定多个ip的,对于这种情况,有两种解决方法,一是,先创建一个多ip的Port,再指定这个 Port 去创建虚拟机,二是,修改 nova-api 的接口,使之支持。 + +```json +"networks":[ + { + "uuid": "{{public_network}}", + "fixed_ip": [{"ip_address": "172.20.20.11"},{"ip_address": "2408:xx.xx::4"}] + }] +``` + + -如果是一张网卡两个ip,那么cloudinit会将同一步tap设备的归为一个同一张网卡。 +**第二点:** + +在一张网卡上配置多个版本的IP(一个IPv4和一个IPv6),只在一个配置文件中配置就可以支持,因此cloudinit的处理的时候,会将同一个tap设备的归为一个同一张网卡。 ![](http://image.python-online.cn/20190716180655.png) -cloudinit 解析后,与每张网卡各一个ip的区别如下。 +也就是说,cloudinit本身很轻松地就可以支持单网卡多版本IP的配置。 + +![](http://image.python-online.cn/20190716180952.png) + +而对于多线的IP,由于 Nova 写入ConfigDrive时,尽管一个Port上有多个IPv4或者多个IPv6,同个版本的都会只取第一个写入,自然从cloudinit那侧就无法进一步操作。 + + + +对于多线IP,只有通过子接口实现,不同的Linux发行版,子接口的实现不一样,不仅如此,不同版本的子接口实现方式也有所差异。总之一句话,就是cloudinit本身不支持多线ip,个人感觉是Nova不支持导致的。 + + + +知道了原因后,若还是坚持要在一张网卡上配置多线IP。那就只有改 Nova 的代码实现了。 + +- Nova 接口参数要改: + + ```json + # 多线IPv4 + "networks":[ + { + "uuid": "{{public_network}}", + "fixed_ip": [{"ip_address": "192.168.10.5"},{"ip_address": "172.20.22.144"}], + "ip_version": 4, + "carriers": ["dx", "yd"] + }], + + # 多线IPv6 + "networks":[ + { + "uuid": "{{public_network}}", + "fixed_ip": [{"ip_address": "2408:xx.xx::3"},{"ip_address": "2408:xx.xx::4"}], + "ip_version": 6, + "carriers": ["dx", "yd"] + }], + ``` + +- Neutron 接口可以不改,因为创建Port的接口参数中的 fixed_ips 是一个数组类型,所以我们可以把这些版本信息,运营商信息放进这个数组的每一个元素中。![](http://image.python-online.cn/20190804110647.png) + + 所以我们只要修改Neutron 创建 Port 的代码逻辑。 + +- Nova 生成 ConfigDrive 的代码要改,由于Nova 只装饰每个 Port 的第一个ip传入ConfigDrive,所以我们要取出其他的ip,以user_data 的方式传入。有一点需要说明的是,这里的逻辑需要额外注意虚拟机重建的场景。 + +- Cloudinit 的代码要改,要新增一个读取user_user 来配置子接口的模块。实现虚拟机内部配置子接口。 + + + +最后另外再说一点无关紧要的,cloudinit 解析后,每张网卡一个ip与一张网卡两个ip的区别如下。 ![](http://image.python-online.cn/20190716180726.png) + + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_15.md b/source/c08/c08_15.md new file mode 100644 index 0000000..efc5f60 --- /dev/null +++ b/source/c08/c08_15.md @@ -0,0 +1,33 @@ +# 8.15 Neutron 源码解读 + +neutron api 的入口是在这里![](http://image.python-online.cn/20190804111844.png) + +在这里会校验并打印请求的信息![](http://image.python-online.cn/20190804111715.png) + +而对网络、子网、port操作的逻辑处理代码的入口都是从这里开始的![](http://image.python-online.cn/20190803181706.png) + +## 8.15.1 创建Port + +从 neutron api 请求过来后,就会经过这里![](http://image.python-online.cn/20190803182042.png) + +把 port 信息打印一下![](http://image.python-online.cn/20190803182223.png) + +点进上面的` self._create_port_db()`,可以看到这里先是创建了一个空壳的port + +![](http://image.python-online.cn/20190804091016.png) + +再分配ip![](http://image.python-online.cn/20190804091226.png) + +上图有一个函数 `_allocate_ips_for_port`,相当重要,一般人只要从这里关注即可 + +![](http://image.python-online.cn/20190804094131.png) + +它会先根据port创建请求里的内容,去数据库中一一比对,找出符合条件的子网,当然在找的过程中,会校验请求参数的准确性,比如它是指定ip和subnet创建的port,那么会检查这个ip是否在subnet内。 + +对于一个port,可能会有多个ip。 + +校验完参数后,会把创建这个port所需的信息都整理到最后返回的 fixed_ip_list 里。如果指定了ip,这个list里的元素就会有 ip_address,否则就只有 subnet_id。 + +![](http://image.python-online.cn/20190804092214.png) + +![](http://image.python-online.cn/20190804091911.png) \ No newline at end of file From bd347d63d68ff4062c746c7afd93c7fe9c0d03ca Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 5 Aug 2019 19:43:14 +0800 Subject: [PATCH 072/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20vim=20=E6=96=87?= =?UTF-8?q?=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_19.md | 323 +++++++++++++++++++++---------------- source/c04/c04_19.rst | 361 ++++++++++++++++++++++++------------------ 2 files changed, 395 insertions(+), 289 deletions(-) diff --git a/source/c04/c04_19.md b/source/c04/c04_19.md index db7784f..c5b184b 100644 --- a/source/c04/c04_19.md +++ b/source/c04/c04_19.md @@ -1,26 +1,31 @@ # 4.19 程序员编码必学:Vim +我本人是 Vim 的重度使用者,就因为喜欢上这种双手不离键盘就可以操控一切的feel,Vim 可以让我对文本的操作更加精准、高效。 -![](https://i.loli.net/2017/08/21/599af0db81bfe.png) +对于未使用过 Vim 的朋友来说,可能还无法体会到这种感觉。由于使用 Vim 有一定的学习成本,只有做到非常熟练的程度才能感受到它带来的快捷。 +这里我就自己日常有使用过的 Vim 指令做一个总结,总共分成 21 点,建议有想学习 Vim 的同学,可以按照文章**配合搜索引擎**多多尝试,相信你会慢慢喜欢上 Vim。 -## vim模式 +本文更倾向于有一定基础的同学,因为有很多点,如果写得太详细的话,一定会变得相当啰嗦。 + +## 1. vim模式 ```shell 正常模式(按Esc或Ctrl+[进入) 左下角显示文件名或为空 插入模式(按i进入) 左下角显示--INSERT-- 可视模式(按v进入) 左下角显示--VISUAL-- ``` -## 打开文档 +## 2. 打开文件 ```shell +# 打开单个文件 +vim file +# 同时打开多个文件 +vim file1 file2.. -vim file 打开单个文件 -vim file1 file2.. 同时打开多个文件 - - -:open [file] 在vim窗口中打开一个新文件 +# 在vim窗口中打开一个新文件 +:open [file] 【举个例子】 # 当前打开1.txt,做了一些编辑没保存 @@ -31,40 +36,35 @@ vim file1 file2.. 同时打开多个文件 # 打开远程文件,比如ftp或者share folder - :e ftp://192.168.10.76/abc.txt :e \qadrive\test\1.txt +# 以只读形式打开文件,但是仍然可以使用 :wq! 写入 +vim -R file -vim -R file 仅是查看文件,不向文件写入内容,可以用只读形式编辑文件。 -vim -M file 如果是想强制性地避免对文件进行修改, - - +# 强制性关闭修改功能,无法使用 :wq! 写入 +vim -M file ``` -## 插入命令 +## 3. 插入命令 ```shell - i 在当前位置生前插入 - I 在当前行首插入 a 在当前位置后插入 - A 在当前行尾插入 o 在当前行之后插入一行 - O 在当前行之前插入一行 - ``` -## 查找命令 +## 4. 查找命令 + +最简单的查找 ```shell -【简单查找】 /text  查找text,按n健查找下一个,按N健查找前一个。 ?text  查找text,反向查找,按n健查找下一个,按N健查找前一个。 @@ -74,33 +74,40 @@ vim中有一些特殊字符在查找时需要转义  .*[]^%/?~$ :set noignorecase  不忽略大小写的查找 ``` -精准查找 +快速查找,不需要手打字符即可查找 + ``` +* 向后(下)寻找游标所在处的单词 +# 向前(上)寻找游标所在处的单词 -【高级查找】 -* 寻找游标所在处的单词 -# 同上,但 \# 是向前(上)找,\*则是向后(下)找 -g\*同\* 但部分符合该单词即可 -g\#同\# 但部分符合该单词即可 -以上查找n,N 的继续查找命令依然可以用 +以上两种查找,n,N 的继续查找命令依然可以适用 +``` + +精准查找:匹配单词查找 -【匹配单词查找】 -如果文本中有 hello helloworld hellopython +如果文本中有 `hello`,` helloworld`,` hellopython` 那我使用 /hello ,这三个词都会匹配到。 -如何精准查找呢? -使用 /hello\> +有没有办法实现精准查找呢?可以使用 + +```shell +/hello\> +``` + +精准查找:匹配行首、行末 -【匹配行首,行尾】 +```shell +# hello位于行首 /^hello -/world$ +# world位于行末 +/world$ ``` -## 替换命令 +## 5. 替换命令 ```shell ~ 反转游标字母大小写 @@ -119,31 +126,27 @@ C (大写C)替换至行尾(和D有所区别,D是删除(剪切)至行 :%s/old/new/g 用old替换new,替换整个文件的所有匹配 - :10,20 s/^/ /g 在第10行至第20行每行前面加四个空格,用于缩进。 ddp 交换光标所在行和其下紧邻的一行。 - ``` - - -## 撤销和重做 +## 6. 撤销与重做 ```shell - u 撤销(Undo) U 撤销对整行的操作 Ctrl + r 重做(Redo),即撤销的撤销。 - ``` +## 7. 删除命令 +需要说明的是,vim 其实并没有单纯的删除命令,下面你或许理解为剪切更加准确。 -## 删除命令 +以字符为单位删除 ```shell x 删除当前字符 @@ -152,13 +155,24 @@ x 删除当前字符 X 删除当前字符的前一个字符。 3X 删除当前光标向前三个字符 -dw 删除当前字符到单词尾 -daw 删除当前字符所在单词 - dl 删除当前字符, dl=x dh 删除前一个字符,X=dh +D 删除当前字符至行尾。D=d$ +d$ 删除当前字符至行尾 +d^ 删除当前字符之前至行首 +``` + +以单词为单位删除 +```shell +dw 删除当前字符到单词尾 +daw 删除当前字符所在单词 +``` + +以行为单位删除 + +```shell dd 删除当前行 dj 删除下一行 dk 删除上一行 @@ -172,19 +186,16 @@ jdG 删除当前行之后所有行(不包括当前行) -D 删除当前字符至行尾。D=d$ -d$ 删除当前字符至行尾 -d^ 删除当前字符之前至行首 - - 10d 删除当前行开始的10行。 :1,10d 删除1-10行 :11,$d 删除11行及以后所有的行 :1,$d 删除所有行 J   删除两行之间的空行,实际上是合并两行。 - ``` -## 复制粘贴 + + + +## 8. 复制粘贴 普通模式中使用y复制 ``` @@ -202,11 +213,11 @@ y1G 复制至文本开头。 普通模式中使用p粘贴 ``` -p(小写)代表粘贴至光标后(下) -P(大写)代表粘贴至光标前(上) +p(小写):代表粘贴至光标后(下边,右边) +P(大写):代表粘贴至光标前(上边,左边) ``` -## 剪切粘贴 +## 9. 剪切粘贴 ```shell @@ -224,13 +235,10 @@ ndd 剪切当前行之后的n行。利用p命令可以对剪切的内容进行 :1, 10 m 20 将第1-10行移动到第20行之后。 ``` - - -## 退出命令 +## 10. 退出保存 ```shell - :wq 保存并退出 ZZ 保存并退出 @@ -243,72 +251,99 @@ ZZ 保存并退出 :sav(eas) new.txt 另存为一个新文件,退出原文件的编辑且不会保存 :f(ile) new.txt 新开一个文件,并不保存,退出原文件的编辑且不会保存 - ``` -## 移动命令 +## 11. 移动命令 +以字符为单位移动 -``` - +```shell h 左移一个字符 l 右移一个字符 k 上移一个字符 j 下移一个字符 -# 10指代所有数字,可任意指定 +# 【定位字符】f和F +fx 找到光标后第一个为x的字符 +3fd 找到光标后第三个为d的字符 + +F 同f,反向查找。 +``` + +以行为单位移动 +```shell +# 10指代所有数字,可任意指定 10h 左移10个字符 10l 右移10个字符 10k 上移10行 10j 下移10行 +$ 移动到行尾 +3$ 移动到下面3行的行尾 +``` +以单词为单位移动 + +```shell w 向前移动一个单词(光标停在单词首部) b 向后移动一个单词 e,同w,只不过是光标停在单词尾部 ge 同b,光标停在单词尾部。 +``` +以句为单位移动 - -^ 移动到本行第一个非空白字符上。 -0 移动到本行第一个字符上(可以是空格) - -$ 移动到行尾 -3$ 移动到下面3行的行尾 - -gg 移动到文件头。 = [[ -G 移动到文件尾。 = ]] - +```shell ( 移动到句首 ) 移动到句尾 +``` +跳转到文件的首尾 -【定位字符】f和F +```shell +gg 移动到文件头。 = [[ == `` +G 移动到文件尾。 = ]] +``` -fx 找到光标后第一个为x的字符 -3fd 找到光标后第三个为d的字符 +其他移动方法 -F 同f,反向查找。 +```shell +^ 移动到本行第一个非空白字符上。 +0 移动到本行第一个字符上(可以是空格) +``` + +使用 `具名标记` 跳转,个人感觉这个很好用,因为可以跨文件。 -【返回一次定位】: -使用 `` 可以返回一次跳转的位置。只记录一次。 -【具名标记】 +```shell 使用 ma ,可以将此处标记为 a,使用 'a 进行跳转 使用 :marks 可以查看所有的标记 使用 :delm!可以删除所有的标记 +``` -这个很好用,可以跨文件。 +当在查看错误日志时,正常的步骤是,vim打开文件,然后使用 `shift+g` 再跳转到最后一行,这里有个更简单的操作可以在打开文件时立即跳到最后一行。只要在 vim 和 文件 中间加个 `+` 即可。 +```shell +vim + you.log +``` +举一反三,当你想打开文件立即跳转到指定行时,可以这样 +```shell +# 打开文件并跳转到 20 行 +vim you.log +20 ``` -## 排版/缩进 +当你使用 `/` 搜索定位跳转或者使用 `:行号` 进行精准跳转时,有时我们想返回到上一次的位置,如何实现? + +只要使用 Ctrl+o 即可返回上一次的位置。 + +## 12. 排版功能 + +**缩进** -### 缩进 ``` :set shiftwidth? 查看缩进值 :set shiftwidth=4 设置缩进值为4 @@ -323,18 +358,19 @@ F 同f,反向查找。 << 取消缩进 ``` -### 排版 +如何你要对代码进行缩进,还可以用 `==` 对当前行缩进,如果要对多行对待缩进,则使用 n`==`,这种方式要求你所编辑的文件的扩展名是被vim所识别的,比如`.py`文件。 + +**排版** + ``` :ce 居中 :le 靠左 :ri 靠右 ``` -对代码缩进,还可以用 `==` 对当前行缩进,如果要对多行对待缩进,则使用 n`==`。 +## 13. 注释命令 -## 注释命令 +**多行注释** - -### 多行注释 ``` 进入命令行模式,按ctrl + v进入 visual block模式,然后按j, 或者k选中多行,把需要注释的行标记起来 @@ -343,7 +379,8 @@ F 同f,反向查找。 按esc键就会全部注释了 ``` -### 取消多行注释 +**取消多行注释** + ``` 进入命令行模式,按ctrl + v进入 visual block模式,按字母l横向选中列的个数,例如 // 需要选中2列 @@ -351,9 +388,9 @@ F 同f,反向查找。 按d键就可全部取消注释 ``` -### 复杂注释 -```shell +**复杂注释** +```shell :3,5 s/^/#/g 注释第3-5行 :3,5 s/^#//g 解除3-5行的注释 @@ -366,7 +403,7 @@ F 同f,反向查找。 :%s/^#//g 取消注释整个文档 ``` -## 调整视野 +## 14. 调整视野 ``` "zz":命令会把当前行置为屏幕正中央, "zt":命令会把当前行置于屏幕顶端 @@ -391,7 +428,7 @@ Ctrl + b 向上滚动一屏 20G 跳到第20行 ``` -## 区域选择 +## 15. 区域选择 ``` 要进行区域选择,要先进入可视模式 @@ -413,9 +450,10 @@ ggVG 选择全文 ``` -## 窗口控制 +## 16. 窗口控制 + +**新建窗口** -### 新建窗口 ```shell # 打开两个文件分属两个窗口 vim -o 1.txt 2.txt @@ -438,7 +476,8 @@ Ctrl-w q 等同:q 结束分割出来的视窗。 Ctrl-w q! 等同:q! 结束分割出来的视窗。 Ctrl-w o 打开一个视窗并且隐藏之前的所有视窗 ``` -### 窗口切换 +**窗口切换** + ```shell # 特别说明:Ctrl w <字母> 不需要同时按 @@ -459,7 +498,8 @@ Ctrl-w k 切换到上边窗口 :bn 切换下一个窗口,就当前位置的窗口的内容变了,其他窗口不变 :bN 切换上一个窗口,就当前位置的窗口的内容变了,其他窗口不变 ``` -### 窗口移动 +**窗口移动** + ```shell # 特别说明:Ctrl w <字母> 不需要同时按 @@ -471,14 +511,16 @@ Ctrl-w L 将当前视窗移至最右边 Ctrl-ww 按顺序切换窗口 ``` -### 调整尺寸 +**调整尺寸** + ```shell # 友情提示:键盘切记不要处于中文状态 Ctrl-w + 增加窗口高度 Ctrl-w - 减少窗口高度 ``` -### 退出窗口 +**退出窗口** + ```shell :close 关闭当前窗口 :close! 强制关闭当前窗口 @@ -498,12 +540,12 @@ ZZ 保存并退出。 :only 关闭所有窗口,只保留当前窗口(前提:其他窗口内容有改变的话都要先保存) :only! 关闭所有窗口,只保留当前窗口 - :qall 放弃所有操作并退出 - :wall 保存所有, - :wqall 保存所有并退出。 +:qall 放弃所有操作并退出 +:wall 保存所有, +:wqall 保存所有并退出。 ``` -## 文档加密 +## 17. 文档加密 ``` vim -x file_name @@ -514,36 +556,35 @@ vim -x file_name 如果不修改内容也要保存。:wq,不然密码设定不会生效。 ``` +## 18. 录制宏 -## 录制宏 +按q键加任意字母开始录制,再按q键结束录制(这意味着vim中的宏不可嵌套),使用的时候@加宏名,比如qa。。。q录制名为a的宏,@a使用这个宏。 +## 19. 执行命令 -```shell -按q键加任意字母开始录制,再按q键结束录制(这意味着vim中的宏不可嵌套),使用的时候@加宏名,比如qa。。。q录制名为a的宏,@a使用这个宏。 +```shell -执行shell命令 +# 重复前一次命令 +. +# 执行shell命令 :!command -:!ls 列出当前目录下文件 +# 比如列出当前目录下文件 +:!ls +# 执行脚本 :!perl -c script.pl 检查perl脚本语法,可以不用退出vim,非常方便。 - :!perl script.pl 执行perl脚本,可以不用退出vim,非常方便。 :suspend或Ctrl - Z 挂起vim,回到shell,按fg可以返回vim。 - ``` - - -## 帮助命令 +## 20. 帮助命令 ```shell - - 在Unix/Linux系统上 $ vimtutor @@ -561,19 +602,23 @@ $ vimtutor :help tutor ``` +## 21. 配置命令 +显示当前设定 -## 配置命令 - -### 显示当前设定 ```shell -set或者:se显示所有修改过的配置 -set all 显示所有的设定值 -set option? 显示option的设定值 -set nooption 取消当期设定值 +:set或者:se显示所有修改过的配置 +:set all 显示所有的设定值 +:set option? 显示option的设定值 +:set nooption 取消当期设定值 +:ver 显示vim的所有信息(包括版本和参数等) + +# 需要注意:全屏模式下 +:args 查看当前打开的文件列表,当前正在编辑的文件会用[]括起来 ``` -### 更改设定 +更改设定 + ```shell :set nu 显示行号 @@ -587,6 +632,8 @@ set cindent(cin) 设置C语言风格缩进 :set ts=4 设置tab键转换为4个空格 +:set ff=unix # 修改文件dos文件为unix + :set shiftwidth? 查看缩进值 :set shiftwidth=4 设置缩进值为4 @@ -600,29 +647,31 @@ set cindent(cin) 设置C语言风格缩进 :scriptnames  查看vim脚本文件的位置,比如.vimrc文件,语法文件及plugin等。 :set list 显示非打印字符,如tab,空格,行尾等。如果tab无法显示,请确定用set lcs=tab:>-命令设置了.vimrc文件,并确保你的文件中的确有tab,如果开启了expendtab,那么tab将被扩展为空格。 + + +:syntax 列出已经定义的语法项 +:syntax clear 清除已定义的语法规则 + +:syntax case match 大小写敏感,int和Int将视为不同的语法元素 +:syntax case ignore 大小写无关,int和Int将视为相同的语法元素,并使用同样的配色方案 + ``` -## 其他命令 -```shell -. 重复前一次命令 +以上就是我使用 Vim 的一些使用总结,希望对你能有帮助。 -:ver 显示vim的所有信息(包括版本和参数等) +------ +最后,送你一张 Vim 的键盘图,你可以将它设置为你的电脑桌面,对你初学 Vim 可能会有帮助。 -# 需要注意:全屏模式下 -:args 查看当前打开的文件列表,当前正在编辑的文件会用[]括起来 +你可以**关注本公众号「Python编程时光」**,在后台回复“**vim**” ,即可获取高清大图。 +![图1](http://image.python-online.cn/20190804222221.png) -:syntax 列出已经定义的语法项 -:syntax clear 清除已定义的语法规则 -:syntax case match 大小写敏感,int和Int将视为不同的语法元素 -:syntax case ignore 大小写无关,int和Int将视为相同的语法元素,并使用同样的配色方案 -J 将两行合并为一行 -``` +![图2](http://image.python-online.cn/20190804222247.png) --- diff --git a/source/c04/c04_19.rst b/source/c04/c04_19.rst index 35709e8..68ca0c1 100644 --- a/source/c04/c04_19.rst +++ b/source/c04/c04_19.rst @@ -1,10 +1,22 @@ 4.19 程序员编码必学:Vim ======================== -|image0| +我本人是 Vim +的重度使用者,就因为喜欢上这种双手不离键盘就可以操控一切的feel,Vim +可以让我对文本的操作更加精准、高效。 -vim模式 -------- +对于未使用过 Vim 的朋友来说,可能还无法体会到这种感觉。由于使用 Vim +有一定的学习成本,只有做到非常熟练的程度才能感受到它带来的快捷。 + +这里我就自己日常有使用过的 Vim 指令做一个总结,总共分成 21 +点,建议有想学习 Vim +的同学,可以按照文章\ **配合搜索引擎**\ 多多尝试,相信你会慢慢喜欢上 +Vim。 + +本文更倾向于有一定基础的同学,因为有很多点,如果写得太详细的话,一定会变得相当啰嗦。 + +1. vim模式 +---------- .. code:: shell @@ -12,17 +24,18 @@ vim模式 插入模式(按i进入) 左下角显示--INSERT-- 可视模式(按v进入) 左下角显示--VISUAL-- -打开文档 --------- +2. 打开文件 +----------- .. code:: shell + # 打开单个文件 + vim file + # 同时打开多个文件 + vim file1 file2.. - vim file 打开单个文件 - vim file1 file2.. 同时打开多个文件 - - - :open [file] 在vim窗口中打开一个新文件 + # 在vim窗口中打开一个新文件 + :open [file] 【举个例子】 # 当前打开1.txt,做了一些编辑没保存 @@ -33,38 +46,36 @@ vim模式 # 打开远程文件,比如ftp或者share folder - :e ftp://192.168.10.76/abc.txt :e \qadrive\test\1.txt + # 以只读形式打开文件,但是仍然可以使用 :wq! 写入 + vim -R file - vim -R file 仅是查看文件,不向文件写入内容,可以用只读形式编辑文件。 - vim -M file 如果是想强制性地避免对文件进行修改, + # 强制性关闭修改功能,无法使用 :wq! 写入 + vim -M file -插入命令 --------- +3. 插入命令 +----------- .. code:: shell - i 在当前位置生前插入 - I 在当前行首插入 a 在当前位置后插入 - A 在当前行尾插入 o 在当前行之后插入一行 - O 在当前行之前插入一行 -查找命令 --------- +4. 查找命令 +----------- + +最简单的查找 .. code:: shell - 【简单查找】 /text  查找text,按n健查找下一个,按N健查找前一个。 ?text  查找text,反向查找,按n健查找下一个,按N健查找前一个。 @@ -73,33 +84,40 @@ vim模式 :set ignorecase  忽略大小写的查找 :set noignorecase  不忽略大小写的查找 -精准查找 +快速查找,不需要手打字符即可查找 :: + * 向后(下)寻找游标所在处的单词 + # 向前(上)寻找游标所在处的单词 + + + 以上两种查找,n,N 的继续查找命令依然可以适用 + +精准查找:匹配单词查找 - 【高级查找】 - * 寻找游标所在处的单词 - # 同上,但 \# 是向前(上)找,\*则是向后(下)找 - g\*同\* 但部分符合该单词即可 - g\#同\# 但部分符合该单词即可 +如果文本中有 ``hello``\ ,\ ``helloworld``\ ,\ ``hellopython`` - 以上查找n,N 的继续查找命令依然可以用 +那我使用 /hello ,这三个词都会匹配到。 - 【匹配单词查找】 - 如果文本中有 hello helloworld hellopython +有没有办法实现精准查找呢?可以使用 - 那我使用 /hello ,这三个词都会匹配到。 - 如何精准查找呢? +.. code:: shell + + /hello\> - 使用 /hello\> +精准查找:匹配行首、行末 + +.. code:: shell - 【匹配行首,行尾】 + # hello位于行首 /^hello + + # world位于行末 /world$ -替换命令 --------- +5. 替换命令 +----------- .. code:: shell @@ -119,25 +137,28 @@ vim模式 :%s/old/new/g 用old替换new,替换整个文件的所有匹配 - :10,20 s/^/ /g 在第10行至第20行每行前面加四个空格,用于缩进。 ddp 交换光标所在行和其下紧邻的一行。 -撤销和重做 ----------- +6. 撤销与重做 +------------- .. code:: shell - u 撤销(Undo) U 撤销对整行的操作 Ctrl + r 重做(Redo),即撤销的撤销。 -删除命令 --------- +7. 删除命令 +----------- + +需要说明的是,vim +其实并没有单纯的删除命令,下面你或许理解为剪切更加准确。 + +以字符为单位删除 .. code:: shell @@ -147,12 +168,23 @@ vim模式 X 删除当前字符的前一个字符。 3X 删除当前光标向前三个字符 + dl 删除当前字符, dl=x + dh 删除前一个字符,X=dh + + D 删除当前字符至行尾。D=d$ + d$ 删除当前字符至行尾 + d^ 删除当前字符之前至行首 + +以单词为单位删除 + +.. code:: shell + dw 删除当前字符到单词尾 daw 删除当前字符所在单词 - dl 删除当前字符, dl=x - dh 删除前一个字符,X=dh +以行为单位删除 +.. code:: shell dd 删除当前行 dj 删除下一行 @@ -167,19 +199,14 @@ vim模式 - D 删除当前字符至行尾。D=d$ - d$ 删除当前字符至行尾 - d^ 删除当前字符之前至行首 - - 10d 删除当前行开始的10行。 :1,10d 删除1-10行 :11,$d 删除11行及以后所有的行 :1,$d 删除所有行 J   删除两行之间的空行,实际上是合并两行。 -复制粘贴 --------- +8. 复制粘贴 +----------- 普通模式中使用y复制 @@ -200,11 +227,11 @@ vim模式 :: - p(小写)代表粘贴至光标后(下) - P(大写)代表粘贴至光标前(上) + p(小写):代表粘贴至光标后(下边,右边) + P(大写):代表粘贴至光标前(上边,左边) -剪切粘贴 --------- +9. 剪切粘贴 +----------- .. code:: shell @@ -221,12 +248,11 @@ vim模式 :1, 10 m 20 将第1-10行移动到第20行之后。 -退出命令 --------- +10. 退出保存 +------------ .. code:: shell - :wq 保存并退出 ZZ 保存并退出 @@ -240,11 +266,12 @@ vim模式 :sav(eas) new.txt 另存为一个新文件,退出原文件的编辑且不会保存 :f(ile) new.txt 新开一个文件,并不保存,退出原文件的编辑且不会保存 -移动命令 --------- +11. 移动命令 +------------ -:: +以字符为单位移动 +.. code:: shell h 左移一个字符 l 右移一个字符 @@ -252,57 +279,87 @@ vim模式 j 下移一个字符 - # 10指代所有数字,可任意指定 + # 【定位字符】f和F + fx 找到光标后第一个为x的字符 + 3fd 找到光标后第三个为d的字符 + + F 同f,反向查找。 + +以行为单位移动 +.. code:: shell + + # 10指代所有数字,可任意指定 10h 左移10个字符 10l 右移10个字符 10k 上移10行 10j 下移10行 + $ 移动到行尾 + 3$ 移动到下面3行的行尾 + +以单词为单位移动 + +.. code:: shell w 向前移动一个单词(光标停在单词首部) b 向后移动一个单词 e,同w,只不过是光标停在单词尾部 ge 同b,光标停在单词尾部。 +以句为单位移动 +.. code:: shell - ^ 移动到本行第一个非空白字符上。 - 0 移动到本行第一个字符上(可以是空格) + ( 移动到句首 + ) 移动到句尾 - $ 移动到行尾 - 3$ 移动到下面3行的行尾 +跳转到文件的首尾 - gg 移动到文件头。 = [[ - G 移动到文件尾。 = ]] +.. code:: shell - ( 移动到句首 - ) 移动到句尾 + gg 移动到文件头。 = [[ == `` + G 移动到文件尾。 = ]] +其他移动方法 - 【定位字符】f和F +.. code:: shell - fx 找到光标后第一个为x的字符 - 3fd 找到光标后第三个为d的字符 + ^ 移动到本行第一个非空白字符上。 + 0 移动到本行第一个字符上(可以是空格) - F 同f,反向查找。 +使用 ``具名标记`` 跳转,个人感觉这个很好用,因为可以跨文件。 - 【返回一次定位】: - 使用 `` 可以返回一次跳转的位置。只记录一次。 +.. code:: shell - 【具名标记】 使用 ma ,可以将此处标记为 a,使用 'a 进行跳转 使用 :marks 可以查看所有的标记 使用 :delm!可以删除所有的标记 - 这个很好用,可以跨文件。 +当在查看错误日志时,正常的步骤是,vim打开文件,然后使用 ``shift+g`` +再跳转到最后一行,这里有个更简单的操作可以在打开文件时立即跳到最后一行。只要在 +vim 和 文件 中间加个 ``+`` 即可。 + +.. code:: shell + + vim + you.log + +举一反三,当你想打开文件立即跳转到指定行时,可以这样 + +.. code:: shell + # 打开文件并跳转到 20 行 + vim you.log +20 -排版/缩进 ---------- +当你使用 ``/`` 搜索定位跳转或者使用 ``:行号`` +进行精准跳转时,有时我们想返回到上一次的位置,如何实现? -缩进 -~~~~ +只要使用 Ctrl+o 即可返回上一次的位置。 + +12. 排版功能 +------------ + +**缩进** :: @@ -318,8 +375,11 @@ vim模式 >> 向右缩进 << 取消缩进 -排版 -~~~~ +如何你要对代码进行缩进,还可以用 ``==`` +对当前行缩进,如果要对多行对待缩进,则使用 +n\ ``==``\ ,这种方式要求你所编辑的文件的扩展名是被vim所识别的,比如\ ``.py``\ 文件。 + +**排版** :: @@ -327,14 +387,10 @@ vim模式 :le 靠左 :ri 靠右 -对代码缩进,还可以用 ``==`` 对当前行缩进,如果要对多行对待缩进,则使用 -n\ ``==``\ 。 +13. 注释命令 +------------ -注释命令 --------- - -多行注释 -~~~~~~~~ +**多行注释** :: @@ -344,8 +400,7 @@ n\ ``==``\ 。 按esc键就会全部注释了 -取消多行注释 -~~~~~~~~~~~~ +**取消多行注释** :: @@ -355,12 +410,10 @@ n\ ``==``\ 。 按d键就可全部取消注释 -复杂注释 -~~~~~~~~ +**复杂注释** .. code:: shell - :3,5 s/^/#/g 注释第3-5行 :3,5 s/^#//g 解除3-5行的注释 @@ -372,8 +425,8 @@ n\ ``==``\ 。 :%s/^/#/g 注释整个文档,此法更快 :%s/^#//g 取消注释整个文档 -调整视野 --------- +14. 调整视野 +------------ :: @@ -399,8 +452,8 @@ n\ ``==``\ 。 :20 跳到第20行 20G 跳到第20行 -区域选择 --------- +15. 区域选择 +------------ :: @@ -421,11 +474,10 @@ n\ ``==``\ 。 ggVG 选择全文 -窗口控制 --------- +16. 窗口控制 +------------ -新建窗口 -~~~~~~~~ +**新建窗口** .. code:: shell @@ -450,8 +502,7 @@ n\ ``==``\ 。 Ctrl-w q! 等同:q! 结束分割出来的视窗。 Ctrl-w o 打开一个视窗并且隐藏之前的所有视窗 -窗口切换 -~~~~~~~~ +**窗口切换** .. code:: shell @@ -474,8 +525,7 @@ n\ ``==``\ 。 :bn 切换下一个窗口,就当前位置的窗口的内容变了,其他窗口不变 :bN 切换上一个窗口,就当前位置的窗口的内容变了,其他窗口不变 -窗口移动 -~~~~~~~~ +**窗口移动** .. code:: shell @@ -489,8 +539,7 @@ n\ ``==``\ 。 Ctrl-ww 按顺序切换窗口 -调整尺寸 -~~~~~~~~ +**调整尺寸** .. code:: shell @@ -499,8 +548,7 @@ n\ ``==``\ 。 Ctrl-w + 增加窗口高度 Ctrl-w - 减少窗口高度 -退出窗口 -~~~~~~~~ +**退出窗口** .. code:: shell @@ -522,12 +570,12 @@ n\ ``==``\ 。 :only 关闭所有窗口,只保留当前窗口(前提:其他窗口内容有改变的话都要先保存) :only! 关闭所有窗口,只保留当前窗口 - :qall 放弃所有操作并退出 - :wall 保存所有, - :wqall 保存所有并退出。 + :qall 放弃所有操作并退出 + :wall 保存所有, + :wqall 保存所有并退出。 -文档加密 --------- +17. 文档加密 +------------ :: @@ -538,33 +586,37 @@ n\ ``==``\ 。 如果不修改内容也要保存。:wq,不然密码设定不会生效。 -录制宏 ------- +18. 录制宏 +---------- -.. code:: shell +按q键加任意字母开始录制,再按q键结束录制(这意味着vim中的宏不可嵌套),使用的时候@加宏名,比如qa。。。q录制名为a的宏,@a使用这个宏。 +19. 执行命令 +------------ + +.. code:: shell - 按q键加任意字母开始录制,再按q键结束录制(这意味着vim中的宏不可嵌套),使用的时候@加宏名,比如qa。。。q录制名为a的宏,@a使用这个宏。 - 执行shell命令 + # 重复前一次命令 + . + # 执行shell命令 :!command - :!ls 列出当前目录下文件 + # 比如列出当前目录下文件 + :!ls + # 执行脚本 :!perl -c script.pl 检查perl脚本语法,可以不用退出vim,非常方便。 - :!perl script.pl 执行perl脚本,可以不用退出vim,非常方便。 :suspend或Ctrl - Z 挂起vim,回到shell,按fg可以返回vim。 -帮助命令 --------- +20. 帮助命令 +------------ .. code:: shell - - 在Unix/Linux系统上 $ vimtutor @@ -581,21 +633,23 @@ n\ ``==``\ 。 在Windows系统上 :help tutor -配置命令 --------- +21. 配置命令 +------------ 显示当前设定 -~~~~~~~~~~~~ .. code:: shell - set或者:se显示所有修改过的配置 - set all 显示所有的设定值 - set option? 显示option的设定值 - set nooption 取消当期设定值 + :set或者:se显示所有修改过的配置 + :set all 显示所有的设定值 + :set option? 显示option的设定值 + :set nooption 取消当期设定值 + :ver 显示vim的所有信息(包括版本和参数等) + + # 需要注意:全屏模式下 + :args 查看当前打开的文件列表,当前正在编辑的文件会用[]括起来 更改设定 -~~~~~~~~ .. code:: shell @@ -611,6 +665,8 @@ n\ ``==``\ 。 :set ts=4 设置tab键转换为4个空格 + :set ff=unix # 修改文件dos文件为unix + :set shiftwidth? 查看缩进值 :set shiftwidth=4 设置缩进值为4 @@ -625,34 +681,35 @@ n\ ``==``\ 。 :set list 显示非打印字符,如tab,空格,行尾等。如果tab无法显示,请确定用set lcs=tab:>-命令设置了.vimrc文件,并确保你的文件中的确有tab,如果开启了expendtab,那么tab将被扩展为空格。 -其他命令 --------- -.. code:: shell + :syntax 列出已经定义的语法项 + :syntax clear 清除已定义的语法规则 - . 重复前一次命令 + :syntax case match 大小写敏感,int和Int将视为不同的语法元素 + :syntax case ignore 大小写无关,int和Int将视为相同的语法元素,并使用同样的配色方案 +以上就是我使用 Vim 的一些使用总结,希望对你能有帮助。 - :ver 显示vim的所有信息(包括版本和参数等) +-------------- +最后,送你一张 Vim 的键盘图,你可以将它设置为你的电脑桌面,对你初学 Vim +可能会有帮助。 - # 需要注意:全屏模式下 - :args 查看当前打开的文件列表,当前正在编辑的文件会用[]括起来 +你可以\ **关注本公众号「Python编程时光」**\ ,在后台回复“**vim**” +,即可获取高清大图。 +.. figure:: http://image.python-online.cn/20190804222221.png + :alt: 图1 - :syntax 列出已经定义的语法项 - :syntax clear 清除已定义的语法规则 + 图1 - :syntax case match 大小写敏感,int和Int将视为不同的语法元素 - :syntax case ignore 大小写无关,int和Int将视为相同的语法元素,并使用同样的配色方案 +.. figure:: http://image.python-online.cn/20190804222247.png + :alt: 图2 - J 将两行合并为一行 + 图2 -------------- .. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! - -.. |image0| image:: https://i.loli.net/2017/08/21/599af0db81bfe.png - From 73effb28ed3de636ee30553c8c089530a5e9846b Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 5 Aug 2019 19:43:36 +0800 Subject: [PATCH 073/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20arp=20=E6=AC=BA?= =?UTF-8?q?=E9=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_13.md | 72 ++++++++++++++++++++++++++++++++++++++ source/c08/c08_13.rst | 80 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/source/c08/c08_13.md b/source/c08/c08_13.md index 00f8871..d86ee9b 100644 --- a/source/c08/c08_13.md +++ b/source/c08/c08_13.md @@ -171,6 +171,78 @@ iptables-save > /etc/sysconfig/iptables.bak iptables-restore < /etc/sysconfig/iptables.bak ``` +## 8.13.2 arp欺骗 + +arp的中文释义是地址解析协议,全英文 address resolution protocol,是一个将局域网IP地址映射到网卡物理地址(MAC)的工作协议。 + +**ARP欺骗**(英语:**ARP spoofing**),又称**ARP毒化**(**ARP poisoning**,网上上多译为**ARP病毒**)或**ARP攻击**,是针对[以太网](https://baike.baidu.com/item/以太网)[地址解析协议](https://baike.baidu.com/item/地址解析协议)([ARP](https://baike.baidu.com/item/ARP))的一种攻击技术,通过欺骗局域网内访问者PC的网关MAC地址,使访问者PC错以为攻击者更改后的MAC地址是网关的MAC,导致网络不通。此种攻击可让攻击者获取[局域网](https://baike.baidu.com/item/局域网)上的数据包甚至可篡改数据包,且可让网上上特定计算机或所有计算机无法正常连线。 + + + +`arp -e` 是一个很常用的命令,用于查看与本机有过通信的机器的arp table,主要是 ip与mac地址的映射。如果在 /etc/hosts 里有填写ip与域名的对应关系,Address一列就会显示域名。你也可以使用 `arp -a` 实现相同功能,只是 `-a` 是标准输出格式,没有像使用 `-e` 一样类似表格一样的输出效果。 + +![](http://image.python-online.cn/20190804162402.png) + +此时,我们ping一下 172.20.22.3 这个ip,显然是可以的,因为这里的其对应的mac地址是真实准确的。 + +但是如果我们将172.20.22.3的mac地址,手动改成一个错误了。那么是不是就不能通了呢? + +首先,怎么为一个ip地址设置一个mac地址呢? + +```shell +arp -s 172.20.22.3 00:1b:d1:bb:1d:d8 +``` + +按如下命令,我们设置了一个在局域网内不存在的mac地址。 + +既然不存在,那当我们ping这个ip地址时,就会把icmp包发往一个不存在的地址,自然没有回应,你能看到的就是ping不通了。 + +```shell +[root@ws_controller01 ~]# ping 172.20.22.3 -c 1 +PING 172.20.22.3 (172.20.22.3) 56(84) bytes of data. + +--- 172.20.22.3 ping statistics --- +1 packets transmitted, 0 received, 100% packet loss, time 0ms +``` + +这就是我们据说的arp欺骗,设置通过错误的ip与mac映射关系,使得机器之间无法正常通信。 + +那如何恢复呢? + +很简单,只要删除arp table里错误的ip与mac映射关系,然后再去ping这个ip,当arp cache里没有这个ip,就会重新发送arp广播,获取到正确的mac地址。 + +```shell +# 从arp cache 里删除 +arp -d 172.20.22.3 + +# 重新 ping ip +ping 172.20.22.3 +``` + +也可以通过 arping 获取到正确 mac 地址,然后再用 `-s` 手动配置上去。 + +```shell +[root@ws_controller01 ~]# arping -I eth1 172.20.22.3 +ARPING 172.20.22.3 from 172.20.22.201 eth1 +Unicast reply from 172.20.22.3 [00:1B:21:BB:29:96] 1.090ms +Sent 1 probes (1 broadcast(s)) +Received 1 response(s) + +[root@ws_controller01 ~]# arp -s 172.20.22.3 00:1B:21:BB:29:96 +``` + + + +其他几个 arp 的参数 + +```shell +# 指定与 eth1 网卡有关的arp条目 +arp -e -i eth1 + +# 指定文件设置多个arp条目 +arp -f /etc/ethers +``` + --- diff --git a/source/c08/c08_13.rst b/source/c08/c08_13.rst index 3cf4f4d..f770980 100644 --- a/source/c08/c08_13.rst +++ b/source/c08/c08_13.rst @@ -178,6 +178,85 @@ iptables iptables-save > /etc/sysconfig/iptables.bak iptables-restore < /etc/sysconfig/iptables.bak +8.13.2 arp欺骗 +-------------- + +arp的中文释义是地址解析协议,全英文 address resolution +protocol,是一个将局域网IP地址映射到网卡物理地址(MAC)的工作协议。 + +**ARP欺骗**\ (英语:\ **ARP +spoofing**\ ),又称\ **ARP毒化**\ (\ **ARP +poisoning**\ ,网上上多译为\ **ARP病毒**\ )或\ **ARP攻击**\ ,是针对\ `以太网 `__\ `地址解析协议 `__\ (\ `ARP `__\ )的一种攻击技术,通过欺骗局域网内访问者PC的网关MAC地址,使访问者PC错以为攻击者更改后的MAC地址是网关的MAC,导致网络不通。此种攻击可让攻击者获取\ `局域网 `__\ 上的数据包甚至可篡改数据包,且可让网上上特定计算机或所有计算机无法正常连线。 + +``arp -e`` 是一个很常用的命令,用于查看与本机有过通信的机器的arp +table,主要是 ip与mac地址的映射。如果在 /etc/hosts +里有填写ip与域名的对应关系,Address一列就会显示域名。你也可以使用 +``arp -a`` 实现相同功能,只是 ``-a`` 是标准输出格式,没有像使用 ``-e`` +一样类似表格一样的输出效果。 + +|image3| + +此时,我们ping一下 172.20.22.3 +这个ip,显然是可以的,因为这里的其对应的mac地址是真实准确的。 + +但是如果我们将172.20.22.3的mac地址,手动改成一个错误了。那么是不是就不能通了呢? + +首先,怎么为一个ip地址设置一个mac地址呢? + +.. code:: shell + + arp -s 172.20.22.3 00:1b:d1:bb:1d:d8 + +按如下命令,我们设置了一个在局域网内不存在的mac地址。 + +既然不存在,那当我们ping这个ip地址时,就会把icmp包发往一个不存在的地址,自然没有回应,你能看到的就是ping不通了。 + +.. code:: shell + + [root@ws_controller01 ~]# ping 172.20.22.3 -c 1 + PING 172.20.22.3 (172.20.22.3) 56(84) bytes of data. + + --- 172.20.22.3 ping statistics --- + 1 packets transmitted, 0 received, 100% packet loss, time 0ms + +这就是我们据说的arp欺骗,设置通过错误的ip与mac映射关系,使得机器之间无法正常通信。 + +那如何恢复呢? + +很简单,只要删除arp +table里错误的ip与mac映射关系,然后再去ping这个ip,当arp +cache里没有这个ip,就会重新发送arp广播,获取到正确的mac地址。 + +.. code:: shell + + # 从arp cache 里删除 + arp -d 172.20.22.3 + + # 重新 ping ip + ping 172.20.22.3 + +也可以通过 arping 获取到正确 mac 地址,然后再用 ``-s`` 手动配置上去。 + +.. code:: shell + + [root@ws_controller01 ~]# arping -I eth1 172.20.22.3 + ARPING 172.20.22.3 from 172.20.22.201 eth1 + Unicast reply from 172.20.22.3 [00:1B:21:BB:29:96] 1.090ms + Sent 1 probes (1 broadcast(s)) + Received 1 response(s) + + [root@ws_controller01 ~]# arp -s 172.20.22.3 00:1B:21:BB:29:96 + +其他几个 arp 的参数 + +.. code:: shell + + # 指定与 eth1 网卡有关的arp条目 + arp -e -i eth1 + + # 指定文件设置多个arp条目 + arp -f /etc/ethers + -------------- .. figure:: http://image.python-online.cn/20190511161447.png @@ -187,4 +266,5 @@ iptables .. |image0| image:: http://image.python-online.cn/20190706114314.png .. |image1| image:: http://image.python-online.cn/20190706093904.png .. |image2| image:: http://image.python-online.cn/20190706160632.png +.. |image3| image:: http://image.python-online.cn/20190804162402.png From c818eee10c29aeeb65b3477697235fcf75b16285 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 5 Aug 2019 19:43:59 +0800 Subject: [PATCH 074/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20ipv6=20=E6=96=87?= =?UTF-8?q?=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_14.rst | 208 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 168 insertions(+), 40 deletions(-) diff --git a/source/c08/c08_14.rst b/source/c08/c08_14.rst index 051a8dc..d6fb0fa 100644 --- a/source/c08/c08_14.rst +++ b/source/c08/c08_14.rst @@ -1,10 +1,10 @@ -8.14 OpenStack 支持 ipv6 -======================== +8.14 支持 IPv6以及多运营商 +========================== -8.14.1 如何配置 ipv6 --------------------- +8.14.1 手工如何配置 IPv6 +------------------------ -命令配置临时ip +使用命令设置临时的IPv6 .. code:: shell @@ -14,7 +14,23 @@ # 配置网关 sudo route -A inet6 add ::/0 gw 2408:xx:xx::1 -通过配置文件 +通过配置文件永久设置IPv6 + +.. code:: shell + + DEVICE=eth0 + BOOTPROTO=none + DEFROUTE=yes + HWADDR=fa:16:3e:4a:4f:55 + IPV6ADDR=2408:xx:xx::4/64 # IPv6 地址 + IPV6INIT=yes + IPV6_DEFAULTGW=2408:xx:xx::1 + MTU=1500 + ONBOOT=yes + TYPE=Ethernet + USERCTL=no + +一张网卡上,可以允许同时存在IPv4及IPv6,配置方法如下 .. code:: shell @@ -29,7 +45,29 @@ IPV6_DEFAULTGW=2408:xx:xx::1 IPV6ADDR_SECONDARIES="2408:xx:xx::2/120" -配置好ip和网关后,如何查看效果? +一张网卡上,也可以通过子接口的试,配置多个(>=2个)的IPv6,配置方法如下: + +.. code:: shell + + DEVICE=eth0 + BOOTPROTO=none + DEFROUTE=yes + HWADDR=fa:16:3e:4a:4f:55 + IPV6ADDR=2408:xx:xx::4/64 # IPv6 地址 + IPV6INIT=yes + IPV6_DEFAULTGW=2408:xx:xx::1 + MTU=1500 + ONBOOT=yes + TYPE=Ethernet + USERCTL=no + + # 若要配置子接口(下面以配置两个子接口为例) + IPV6ADDR_SECONDARIES="2408:xx:xx::6/120 + 2408:xx:xx::7/120" + +配置好IPv6和网关后,如何查看效果呢?这和IPv4有点不一样。 + +在IPv4中 .. code:: shell @@ -37,38 +75,55 @@ ip addr # 查看网关 - route -A inet6 + route -n 或者 route -4 # 查看网络连通性 - ping6 + ping6 -8.14.2 nova 使用 ipv6 ---------------------- +而在IPv6中 -通过如下代码可知 +.. code:: shell -|image0| + # 查看ip + ip addr -OpenStack 在生成 -ConfigureDrive默认不开启ipv6,若要使用ipv6,需要在计算节点的 nova.conf -中添加配置 + # 查看网关 + route -A inet6 或者 route -6 -.. code:: shell + # 查看网络连通性 + ping6 + +8.14.2 Nova 如何使用 IPv6 +------------------------- + +原生的 Nova 和 Neutron 就已经支持 IPv6 了。 + +对于 Nova 来说,需要在计算节点的配置中开启 IPv6 + +.. code:: ini [default] use_ipv6=true -使用ipv6,当然要先创建一个ipv6的子网,这里要注意,ipv6子网所属的网络,必须为flat(默认不指定就为vlan) +才能将 IPv6 的 Port +信息写入ConfigDrive,也只有这样cloudinit才能自动为我们配置上 IPv6 的 +ip。 + +|image0| + +而对于 Neutron 来说,要使用 IPv6,当然要先创建一个 IPv6 +的子网,这里要注意,IPv6 +子网所属的网络,必须为flat,这个一定要注意,因为你不指定的话,默认就为 +vlan。 .. code:: shell + # 创建网络 neutron net-create --provider:physical_network phynet0 --provider:network_type flat ipv6_public -然后再创建一个子网 - -创建子网的时候,要注意自己是否要使用 dhcp +然后再创建一个子网,创建子网的时候,要确认下自己是否要使用 dhcp -如果使用static,使用这条命令 +如果使用static,则使用这条命令 .. code:: shell @@ -89,34 +144,106 @@ ConfigureDrive默认不开启ipv6,若要使用ipv6,需要在计算节点的 --ipv6-address-mode dhcpv6-stateful \ ipv6_public 2408:xx:xx:0000::/120 -一切都准备好了,就指定这个子网创建虚拟机了。 +一切都准备好了,就可以指定这个子网创建虚拟机了。 -登陆出来的虚拟机,会发现cloud-init已经将ipv6的ip配置上去了,说明cloudinit本身就支持ipv6,无需额外修改和配置。 +登陆出来的虚拟机,会发现cloud-init已经将ipv6的ip配置上去了,说明 +cloudinit 本身就支持 IPv6,无需额外修改和配置。 -8.14.3 单张网卡多个ip ---------------------- +8.14.3 一张网卡配置多个IP +------------------------- -接下来,要验证的一点是,nova 和 cloud-init -是否支持在一张网卡上配置多个ip呢(一个ipv4一个ipv6). +有这个需求的,通常是这两种场景: -nova 的http api -接口是不能直接支持的,但是可以通过先创建一个port,在这个port上指定两个ip。 +1、要配置双线IP,比如移动和电信各一个IP -然后创建虚拟机时,指定这个port去创建。 +2、要配置多个版本的IP,一个IPv4和一个IPv6 -登陆一下虚拟机,查看下效果,你可以发现,两个ip如预期一样配置在一张网卡上了。 +通过第一节,我们已经知道,可以通过配置直接实现。 -|image1| +但是,在 OpenStack 中是否就已经支持这样的需求呢? + +经过验证可以得知 + +**第一点:** + +不管哪种场景,对于 OpenStack 来说,都是一样的,就是一个 Port +上指定两个IP,而这两个IP,是多线还是多版本,由你来定。 + +.. code:: shell + + neutron port-create --fixed-ip \ + subnet_id=0d7f753d-3a8f-46d4-b931-2843b138388a,ip_address=175.xx.xx.11 \ + --fixed-ip subnet_id=36cdc565-4807-44bb-b6f6-8ee3f4114193,\ + ip_address=2408:xx.xx::11 you_network + +不过要注意的是,Nova 本身的 api +接口是不支持指定一个Port上指定多个ip的,对于这种情况,有两种解决方法,一是,先创建一个多ip的Port,再指定这个 +Port 去创建虚拟机,二是,修改 nova-api 的接口,使之支持。 + +.. code:: json -一张网卡一个ip 和 一张网卡多个ip 的configdrive 有什么区别呢? + "networks":[ + { + "uuid": "{{public_network}}", + "fixed_ip": [{"ip_address": "172.20.20.11"},{"ip_address": "2408:xx.xx::4"}] + }] -如果是一张网卡两个ip,那么cloudinit会将同一步tap设备的归为一个同一张网卡。 +**第二点:** + +在一张网卡上配置多个版本的IP(一个IPv4和一个IPv6),只在一个配置文件中配置就可以支持,因此cloudinit的处理的时候,会将同一个tap设备的归为一个同一张网卡。 + +|image1| + +也就是说,cloudinit本身很轻松地就可以支持单网卡多版本IP的配置。 |image2| -cloudinit 解析后,与每张网卡各一个ip的区别如下。 +而对于多线的IP,由于 Nova +写入ConfigDrive时,尽管一个Port上有多个IPv4或者多个IPv6,同个版本的都会只取第一个写入,自然从cloudinit那侧就无法进一步操作。 + +对于多线IP,只有通过子接口实现,不同的Linux发行版,子接口的实现不一样,不仅如此,不同版本的子接口实现方式也有所差异。总之一句话,就是cloudinit本身不支持多线ip,个人感觉是Nova不支持导致的。 + +知道了原因后,若还是坚持要在一张网卡上配置多线IP。那就只有改 Nova +的代码实现了。 + +- Nova 接口参数要改: + + .. code:: json + + # 多线IPv4 + "networks":[ + { + "uuid": "{{public_network}}", + "fixed_ip": [{"ip_address": "192.168.10.5"},{"ip_address": "172.20.22.144"}], + "ip_version": 4, + "carriers": ["dx", "yd"] + }], + + # 多线IPv6 + "networks":[ + { + "uuid": "{{public_network}}", + "fixed_ip": [{"ip_address": "2408:xx.xx::3"},{"ip_address": "2408:xx.xx::4"}], + "ip_version": 6, + "carriers": ["dx", "yd"] + }], + +- Neutron 接口可以不改,因为创建Port的接口参数中的 fixed_ips + 是一个数组类型,所以我们可以把这些版本信息,运营商信息放进这个数组的每一个元素中。\ |image3| + + 所以我们只要修改Neutron 创建 Port 的代码逻辑。 + +- Nova 生成 ConfigDrive 的代码要改,由于Nova 只装饰每个 Port + 的第一个ip传入ConfigDrive,所以我们要取出其他的ip,以user_data + 的方式传入。有一点需要说明的是,这里的逻辑需要额外注意虚拟机重建的场景。 + +- Cloudinit 的代码要改,要新增一个读取user_user + 来配置子接口的模块。实现虚拟机内部配置子接口。 + +最后另外再说一点无关紧要的,cloudinit +解析后,每张网卡一个ip与一张网卡两个ip的区别如下。 -|image3| +|image4| -------------- @@ -125,7 +252,8 @@ cloudinit 解析后,与每张网卡各一个ip的区别如下。 .. |image0| image:: http://image.python-online.cn/20190716175250.png -.. |image1| image:: http://image.python-online.cn/20190716180952.png -.. |image2| image:: http://image.python-online.cn/20190716180655.png -.. |image3| image:: http://image.python-online.cn/20190716180726.png +.. |image1| image:: http://image.python-online.cn/20190716180655.png +.. |image2| image:: http://image.python-online.cn/20190716180952.png +.. |image3| image:: http://image.python-online.cn/20190804110647.png +.. |image4| image:: http://image.python-online.cn/20190716180726.png From 42bd50955a2ee3000417bca1d5cd2d3166466a31 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 5 Aug 2019 19:44:20 +0800 Subject: [PATCH 075/302] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20neutron=20?= =?UTF-8?q?=E6=BA=90=E7=A0=81=E8=A7=A3=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_05.rst | 19 ++++++++++++++++ source/c08/c08_15.rst | 50 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 source/c08/c08_15.rst diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index 8e2a1f7..d9ece4d 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -528,6 +528,22 @@ nova api 的接口参数是在 nova/api/openstack/compute/schemas/ |image39| +这些参数字段后面的类型是用来做参数类型的校验的。 + +在application 函数的头部,可以发现有如下这几种装饰器, + +|image40| + +装饰器 schemas 的定义如下: + +|image41| + +比如我们使用的 novaclient 发出的请求,是 v2.3.7的,所以 create() +顶部的五个schema +装饰器,上面四个都会空跑,不会进行校验,只有最后一个才会进入检验逻辑。 + +|image42| + -------------- .. figure:: http://image.python-online.cn/20190511161447.png @@ -574,4 +590,7 @@ nova api 的接口参数是在 nova/api/openstack/compute/schemas/ .. |image37| image:: http://image.python-online.cn/20190708100902.png .. |image38| image:: http://image.python-online.cn/20190708103119.png .. |image39| image:: http://image.python-online.cn/20190719170825.png +.. |image40| image:: http://image.python-online.cn/20190730140551.png +.. |image41| image:: http://image.python-online.cn/20190730142527.png +.. |image42| image:: http://image.python-online.cn/20190730143003.png diff --git a/source/c08/c08_15.rst b/source/c08/c08_15.rst new file mode 100644 index 0000000..e4aec06 --- /dev/null +++ b/source/c08/c08_15.rst @@ -0,0 +1,50 @@ +8.15 Neutron 源码解读 +===================== + +neutron api 的入口是在这里\ |image0| + +在这里会校验并打印请求的信息\ |image1| + +而对网络、子网、port操作的逻辑处理代码的入口都是从这里开始的\ |image2| + +8.15.1 创建Port +--------------- + +从 neutron api 请求过来后,就会经过这里\ |image3| + +把 port 信息打印一下\ |image4| + +点进上面的\ ``self._create_port_db()``\ ,可以看到这里先是创建了一个空壳的port + +|image5| + +再分配ip\ |image6| + +上图有一个函数 +``_allocate_ips_for_port``\ ,相当重要,一般人只要从这里关注即可 + +|image7| + +它会先根据port创建请求里的内容,去数据库中一一比对,找出符合条件的子网,当然在找的过程中,会校验请求参数的准确性,比如它是指定ip和subnet创建的port,那么会检查这个ip是否在subnet内。 + +对于一个port,可能会有多个ip。 + +校验完参数后,会把创建这个port所需的信息都整理到最后返回的 fixed_ip_list +里。如果指定了ip,这个list里的元素就会有 ip_address,否则就只有 +subnet_id。 + +|image8| + +|image9| + +.. |image0| image:: http://image.python-online.cn/20190804111844.png +.. |image1| image:: http://image.python-online.cn/20190804111715.png +.. |image2| image:: http://image.python-online.cn/20190803181706.png +.. |image3| image:: http://image.python-online.cn/20190803182042.png +.. |image4| image:: http://image.python-online.cn/20190803182223.png +.. |image5| image:: http://image.python-online.cn/20190804091016.png +.. |image6| image:: http://image.python-online.cn/20190804091226.png +.. |image7| image:: http://image.python-online.cn/20190804094131.png +.. |image8| image:: http://image.python-online.cn/20190804092214.png +.. |image9| image:: http://image.python-online.cn/20190804091911.png + From aa005317f75ea9d8454f8decb619a6e8a4c75bd5 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 10 Aug 2019 11:11:43 +0800 Subject: [PATCH 076/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0vim=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_19.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/c04/c04_19.md b/source/c04/c04_19.md index c5b184b..6c505d6 100644 --- a/source/c04/c04_19.md +++ b/source/c04/c04_19.md @@ -14,6 +14,9 @@ 正常模式(按Esc或Ctrl+[进入) 左下角显示文件名或为空 插入模式(按i进入) 左下角显示--INSERT-- 可视模式(按v进入) 左下角显示--VISUAL-- +替换模式(按r或R开始) 左下角显示 --REPLACE-- +命令行模式(按:或者/或者?开始) +ex模式 没用过,有兴趣的同学可以自行了解 ``` ## 2. 打开文件 From 6b56ae929418fc5df1987dea38cecdb02ccef0a1 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 10 Aug 2019 22:52:59 +0800 Subject: [PATCH 077/302] update bookmark --- source/bookmark.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/bookmark.rst b/source/bookmark.rst index 37b61e1..c76b807 100755 --- a/source/bookmark.rst +++ b/source/bookmark.rst @@ -12,6 +12,8 @@ Python电子教程 - `RUNNOOB的Python教程 `__ - `w3school&runoob教程合集-1:囊括世面各种语言 `__ - `w3school&runoob教程合集-2:囊括世面各种语言 `__ +- `现代魔法学院:Python教室 `__ +- `Python中文学习大本营 `__ Linux入门 ~~~~~~~~~~~ @@ -150,3 +152,4 @@ Python参考文档 - `Iconfont-阿里巴巴矢量图标库 `__ - `Icons for everything - Noun Project `__ - `Font Awesome,一套绝佳的图标字体库和CSS框架 `__ +- `iSlide:制作高大上的PPT`__ From 5c056e9089976f45d4d98abb2368d760cb1e9e30 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 10 Aug 2019 22:53:22 +0800 Subject: [PATCH 078/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20mac=20=E6=8A=80?= =?UTF-8?q?=E5=B7=A7=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_18.md | 109 ++++++++++++++++++++++++++++++++++--- source/c04/c04_18.rst | 121 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 217 insertions(+), 13 deletions(-) diff --git a/source/c04/c04_18.md b/source/c04/c04_18.md index 2cc9082..d2c1a9b 100644 --- a/source/c04/c04_18.md +++ b/source/c04/c04_18.md @@ -21,22 +21,117 @@ command + / option + command + e ``` -## 4.18.2 Finder +## 4.18.2 截图工具 + +1. Xnip:取色+丰富的标注功能+滚动截屏; +2. Snip:小巧轻量+滚动截屏:; +3. QQ:截屏+录屏+OCR文字识别; +4. Snipaste:贴屏功能; +5. ScreenShot PSD:psd 文件,每种元素都有单独的图层。 + +## 4.18.3 快捷操作 ``` +# 返回桌面 +fn + f11 + +# finder 后退与前进 command + up # 文件夹后退 command + down # 文件夹前进 ``` -## 4.18.3 截图工具 -1.Xnip:取色+丰富的标注功能+滚动截屏; -2.Snip:小巧轻量+滚动截屏:; -3.QQ:截屏+录屏+OCR文字识别; -4.Snipaste:贴屏功能; -5.ScreenShot PSD:psd 文件,每种元素都有单独的图层。 +## 4.18.4 系统设置 + +关闭仪表盘 + +``` +点击系统偏好设置 -> 调度中心 -> 仪表盘 -> 关闭 +``` + +finder的显示 + +![](http://image.python-online.cn/20190810161513.png) + + + +**[防止电脑温度过高](https://mp.weixin.qq.com/s/qKQO616vxADFp1cVtA62Cw)** + +1. 不使用 Adobe Flash 播放器(改用 HTML5播放器),因为其效能极低,会耗费大量的系统资源,导致电脑温度快速上升。 + +2. 不将电脑放在软的地方,如沙发,枕头等,可以买个散热支架。 + + ![来自Mac派](http://image.python-online.cn/20190810162000.png) + + 3. 打开「活动监视器」(Alfred就可以打开),杀掉暂没用且cpu使用率最高的程序 + + ![](http://image.python-online.cn/20190810162315.png) + + 4. MacBook Pro CPU 温度在 5、60℃ 的时候,风扇会转到两三千转每分钟,只有 CPU 温度达到 70 多度或更高时,才会高速运转降温。但这时 Mac 已经很热了。 + + 我们可以借用软件,手动让散热风扇全速运转,这样就能更快的散热。常用的软件有 Macs Fan Control、TG Pro、smcFanControl,三个用下来我比较推荐 Macs Fan Control。 + + Macs Fan Control 可以查看各 CPU 核心的温度、主板、电池、内存温度等。可以分别调节两个风扇的转速,也可以设定条件自动调整转速。安装后就可以在系统状态栏看到电脑目前的温度和转速。 + + 风扇转得快了,散热自然也快了,但是风扇声音也更大了。建议只在非常烫(超过60℃?)的时候开启。 + + + +## 4.18.5 软件推荐 + +Alfred 3:快捷神器 + +iTerm2:终端神器 + +New File Menu:右键新建特定格式的文件。 + +Caffenie:讲PPT时,控制不息屏。 + +Tickeys:键盘模拟音效。 + +Magnet/Moon:窗口控制 + +Bartender 3:状态栏管理 + +SourceTree:Git可视化管理 + +FreeDownloadManagger:下载管理 + +PicGo:图床上传 + +Typora/Bear:Markdown写作工具 + +滴答清单:待办事项管理 + +Capture Gif:Gif 录制(不推荐) + +Kap Beta:录屏开源免费软件,支持GIF导出,快捷键:`Command Shift 5` + +TeamViewer:远程控制工具 + +iStat Menus:系统指标仪表盘 + +CheatSheet:快捷键帮助菜单 + +CCleaner:系统清理、软件卸载 + +印象笔记:笔记 + +WPS:Office套件 + +Snipaste:截图工具 + +Macs Fan Control:控制风扇转速,加快散热 + +ShortCat:在系统栏也可以搜索聚焦 + + + +## 参考文章 +1. [Mac 上值得推荐的录屏软件](https://mp.weixin.qq.com/s/cvS6BLI53JFQY2P3rvg9Xw) +2. [Mac 连显示器或电视需要买什么线?](https://mp.weixin.qq.com/s/V8A_1GBxtlN2WZrcTsi-YQ) --- diff --git a/source/c04/c04_18.rst b/source/c04/c04_18.rst index 953960c..26adcec 100644 --- a/source/c04/c04_18.rst +++ b/source/c04/c04_18.rst @@ -23,23 +23,132 @@ # Expose Tabs: option + command + e -4.18.2 Finder -------------- +4.18.2 截图工具 +--------------- + +1. Xnip:取色+丰富的标注功能+滚动截屏; +2. Snip:小巧轻量+滚动截屏:; +3. QQ:截屏+录屏+OCR文字识别; +4. Snipaste:贴屏功能; +5. ScreenShot PSD:psd 文件,每种元素都有单独的图层。 + +4.18.3 快捷操作 +--------------- :: + # 返回桌面 + fn + f11 + + # finder 后退与前进 command + up # 文件夹后退 command + down # 文件夹前进 -4.18.3 截图工具 +4.18.4 系统设置 +--------------- + +关闭仪表盘 + +:: + + 点击系统偏好设置 -> 调度中心 -> 仪表盘 -> 关闭 + +finder的显示 + +|image0| + +`防止电脑温度过高 `__ + +1. 不使用 Adobe Flash 播放器(改用 + HTML5播放器),因为其效能极低,会耗费大量的系统资源,导致电脑温度快速上升。 + +2. 不将电脑放在软的地方,如沙发,枕头等,可以买个散热支架。 + + .. figure:: http://image.python-online.cn/20190810162000.png + :alt: 来自Mac派 + + 来自Mac派 + + 3. 打开「活动监视器」(Alfred就可以打开),杀掉暂没用且cpu使用率最高的程序 + + |image1| + + 4. MacBook Pro CPU 温度在 5、60℃ + 的时候,风扇会转到两三千转每分钟,只有 CPU 温度达到 70 + 多度或更高时,才会高速运转降温。但这时 Mac 已经很热了。 + + 我们可以借用软件,手动让散热风扇全速运转,这样就能更快的散热。常用的软件有 + Macs Fan Control、TG Pro、smcFanControl,三个用下来我比较推荐 Macs + Fan Control。 + + Macs Fan Control 可以查看各 CPU + 核心的温度、主板、电池、内存温度等。可以分别调节两个风扇的转速,也可以设定条件自动调整转速。安装后就可以在系统状态栏看到电脑目前的温度和转速。 + + 风扇转得快了,散热自然也快了,但是风扇声音也更大了。建议只在非常烫(超过60℃?)的时候开启。 + +4.18.5 软件推荐 --------------- -1.Xnip:取色+丰富的标注功能+滚动截屏; 2.Snip:小巧轻量+滚动截屏:; -3.QQ:截屏+录屏+OCR文字识别; 4.Snipaste:贴屏功能; 5.ScreenShot -PSD:psd 文件,每种元素都有单独的图层。 +Alfred 3:快捷神器 + +iTerm2:终端神器 + +New File Menu:右键新建特定格式的文件。 + +Caffenie:讲PPT时,控制不息屏。 + +Tickeys:键盘模拟音效。 + +Magnet/Moon:窗口控制 + +Bartender 3:状态栏管理 + +SourceTree:Git可视化管理 + +FreeDownloadManagger:下载管理 + +PicGo:图床上传 + +Typora/Bear:Markdown写作工具 + +滴答清单:待办事项管理 + +Capture Gif:Gif 录制(不推荐) + +Kap Beta:录屏开源免费软件,支持GIF导出,快捷键:\ ``Command Shift 5`` + +TeamViewer:远程控制工具 + +iStat Menus:系统指标仪表盘 + +CheatSheet:快捷键帮助菜单 + +CCleaner:系统清理、软件卸载 + +印象笔记:笔记 + +WPS:Office套件 + +Snipaste:截图工具 + +Macs Fan Control:控制风扇转速,加快散热 + +ShortCat:在系统栏也可以搜索聚焦 + +参考文章 +-------- + +1. `Mac + 上值得推荐的录屏软件 `__ +2. `Mac + 连显示器或电视需要买什么线? `__ -------------- .. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! + +.. |image0| image:: http://image.python-online.cn/20190810161513.png +.. |image1| image:: http://image.python-online.cn/20190810162315.png + From 9a6841f583e2f64b237346e78bb065874243cf7e Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 10 Aug 2019 22:53:46 +0800 Subject: [PATCH 079/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20vim=20=E6=96=87?= =?UTF-8?q?=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_19.md | 10 ++++++++-- source/c04/c04_19.rst | 13 +++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/source/c04/c04_19.md b/source/c04/c04_19.md index 6c505d6..bdc995a 100644 --- a/source/c04/c04_19.md +++ b/source/c04/c04_19.md @@ -122,6 +122,10 @@ cc 替换整行(就是删除当前行,并在下一行插入) cw 替换一个单词(就是删除一个单词,就进入插入模式),前提是游标处于单词第一个字母(可用b定位) C (大写C)替换至行尾(和D有所区别,D是删除(剪切)至行尾,C是删除至行位并进入插入模式) +# % 就表示所有行,不加就表求当前行 +# 加g表示对每一行的所有匹配到的进行替换,不加就是第一个 +# 如果不加g,而改成c,就会让你再进行确认,选择a进行全部替换,选择y替换,选择n不替换 + :s/old/new/ 用old替换new,替换当前行的第一个匹配 :s/old/new/g 用old替换new,替换当前行的所有匹配 @@ -249,8 +253,7 @@ ZZ 保存并退出 :q! 强制退出并忽略所有更改 :e! 放弃所有修改,并打开原来文件。 - -ZZ 保存并退出 +:open! 放弃所有修改,并打开原来文件。 :sav(eas) new.txt 另存为一个新文件,退出原文件的编辑且不会保存 :f(ile) new.txt 新开一个文件,并不保存,退出原文件的编辑且不会保存 @@ -582,6 +585,9 @@ vim -x file_name :!perl script.pl 执行perl脚本,可以不用退出vim,非常方便。 :suspend或Ctrl - Z 挂起vim,回到shell,按fg可以返回vim。 + +# 执行完当前命令后,自动回到vim环境,但不知为何文件内容变成空了 +:silent !command ``` ## 20. 帮助命令 diff --git a/source/c04/c04_19.rst b/source/c04/c04_19.rst index 68ca0c1..c54989c 100644 --- a/source/c04/c04_19.rst +++ b/source/c04/c04_19.rst @@ -23,6 +23,9 @@ Vim。 正常模式(按Esc或Ctrl+[进入) 左下角显示文件名或为空 插入模式(按i进入) 左下角显示--INSERT-- 可视模式(按v进入) 左下角显示--VISUAL-- + 替换模式(按r或R开始) 左下角显示 --REPLACE-- + 命令行模式(按:或者/或者?开始) + ex模式 没用过,有兴趣的同学可以自行了解 2. 打开文件 ----------- @@ -130,6 +133,10 @@ Vim。 cw 替换一个单词(就是删除一个单词,就进入插入模式),前提是游标处于单词第一个字母(可用b定位) C (大写C)替换至行尾(和D有所区别,D是删除(剪切)至行尾,C是删除至行位并进入插入模式) + # % 就表示所有行,不加就表求当前行 + # 加g表示对每一行的所有匹配到的进行替换,不加就是第一个 + # 如果不加g,而改成c,就会让你再进行确认,选择a进行全部替换,选择y替换,选择n不替换 + :s/old/new/ 用old替换new,替换当前行的第一个匹配 :s/old/new/g 用old替换new,替换当前行的所有匹配 @@ -260,8 +267,7 @@ Vim。 :q! 强制退出并忽略所有更改 :e! 放弃所有修改,并打开原来文件。 - - ZZ 保存并退出 + :open! 放弃所有修改,并打开原来文件。 :sav(eas) new.txt 另存为一个新文件,退出原文件的编辑且不会保存 :f(ile) new.txt 新开一个文件,并不保存,退出原文件的编辑且不会保存 @@ -612,6 +618,9 @@ n\ ``==``\ ,这种方式要求你所编辑的文件的扩展名是被vim所识 :suspend或Ctrl - Z 挂起vim,回到shell,按fg可以返回vim。 + # 执行完当前命令后,自动回到vim环境,但不知为何文件内容变成空了 + :silent !command + 20. 帮助命令 ------------ From 5a46b31592fe0d457db31abeea715231eb0b4ebd Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 10 Aug 2019 22:54:04 +0800 Subject: [PATCH 080/302] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20gooble=20=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E6=8A=80=E5=B7=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_20.md | 38 ++++++++++++++++++++++++++++++++++++++ source/c04/c04_20.rst | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 source/c04/c04_20.md create mode 100644 source/c04/c04_20.rst diff --git a/source/c04/c04_20.md b/source/c04/c04_20.md new file mode 100644 index 0000000..8edd8ca --- /dev/null +++ b/source/c04/c04_20.md @@ -0,0 +1,38 @@ +# 4.20 学会使用谷歌搜索引擎 + +1、按文件类型搜索 加filetype + +``` +流畅的python filetype:pdf +``` + +2、过滤关键字 用减号 + +``` +Python协程 -CSDN +``` + +3、必须包含某关键字 用加号 + +``` +linux常用命令 +centos +``` + +4、搜索指定网站 加site + +``` +Python冷知识 site:python-online.cn +``` + +5、链接中包含字符串 加inurl + +``` +Python冷知识 inurl:python-online +``` + +6、完全匹配搜索结果 加双绰号包裹 + +``` +"装饰器进阶用法详解" +``` + diff --git a/source/c04/c04_20.rst b/source/c04/c04_20.rst new file mode 100644 index 0000000..9e31f91 --- /dev/null +++ b/source/c04/c04_20.rst @@ -0,0 +1,38 @@ +4.20 学会使用谷歌搜索引擎 +========================= + +1、按文件类型搜索 加filetype + +:: + + 流畅的python filetype:pdf + +2、过滤关键字 用减号 + +:: + + Python协程 -CSDN + +3、必须包含某关键字 用加号 + +:: + + linux常用命令 +centos + +4、搜索指定网站 加site + +:: + + Python冷知识 site:python-online.cn + +5、链接中包含字符串 加inurl + +:: + + Python冷知识 inurl:python-online + +6、完全匹配搜索结果 加双绰号包裹 + +:: + + "装饰器进阶用法详解" From c11fa64cea7f42f8a2d765b14c75aa3f35d01f87 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 10 Aug 2019 22:54:46 +0800 Subject: [PATCH 081/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0linux=E7=9F=A5?= =?UTF-8?q?=E8=AF=86=EF=BC=88=E5=88=AB=E5=90=8D=E6=8A=80=E5=B7=A7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/c07_01.md | 61 ++++++++++++++++++++++++++++++++++ source/c07/c07_01.rst | 76 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/source/c07/c07_01.md b/source/c07/c07_01.md index f522c1e..5bc4404 100644 --- a/source/c07/c07_01.md +++ b/source/c07/c07_01.md @@ -1548,8 +1548,69 @@ $ alias catgra='cat /var/lib/mysql/grastate.dat' # 比如我们的ll,系统默认给我们加了别名,ll='ls -l --color=auto',也就是加上颜色效果 # 如果我们不要颜色效果,可以这样使用转义符,使用原生的命令 $ \ll somedir +``` +这里转几条来自 「**良许Linux**」的总结,非常实用,你可以跟着设置一下 + +1、压缩包文件,特别是 tar 文件在 Linux 下使用非常广泛,但是 tar 命令的选项又非常多,也不好记住。所以我们可以将常用的几个选项定义为一个别名 **untar** ,这样我们需要解压 tar 文件时,直接 **untar filename** 即可。 + +``` +alias untar='tar -zxvf ' +``` + +2、我们下载一个很大的文件时,突然网络异常中断了,我们重新下载是不是很抓狂?别担心,我们的 wget 命令有个 -c 选项,支持断点下载,我们也可以将它设置为别名: + +``` +alias wget='wget -c ' +``` + +3、有时我们需要生成一个 20 个字符的随机数密码,我们可以使用 openssl 命令,但完整的命令又很长很不方便,我们可以设置别名: + +``` +alias getpass="openssl rand -base64 20" +``` + +4、下载一个文件之后,我们想要校验一下它的 checksum 值,可以将这个命令封装为一个别名 **sha** ,之后我们 **sha filename** 就可以校验文件的 checksum 值。 + +``` +alias sha='shasum -a 256 ' +``` + +5、正常情况下,ping 命令将无限次输出,但其实没多大意义。我们可以使用 -c 命令将其限制为 5 次输出,然后设置为别名 **ping** ,使用时,**ping url** 即可。 + +``` +alias ping='ping -c 5' +``` + +6、如果我们想随时随地启动一个 web 服务器,我们可以使用这个别名: + +``` +alias www='python -m SimpleHTTPServer 8000' +``` + +7、网速的测试在工作中也经常用到,但 Linux 没有自带命令可用,我们可以借助第三方工具 **speedtest-cli** 。这个工具可以直接从 Github 上下载,使用方法里面也有详细介绍。我们需要先使用 **speedtest-cli** 命令来选择离我们最近的服务器,然后设置如下别名: + +``` +alias speed='speedtest-cli --server 2406 --simple' +``` + +8、你的公网 IP 是多少?记性好的可以直接背下来,但如果你有 10 台上百台服务器呢?也可以背下来,然后参加最强大脑。其实有个命令可以直接查询,但那个命令太变态,不好记,果断设置为别名。 ``` +alias ipe='curl ipinfo.io/ip' +``` + +9、如何知道自己的局域网 IP ?这个命令同样变态,果断设置别名。 + +``` +alias ipi='ipconfig getifaddr en0' +``` + +10、最后,清屏,我们可以使用 **ctrl + l** 快捷键,也可以将 **clear** 命令定义得更短,这样使用起来更直接,更粗暴。 + +``` +alias c='clear' +``` + 【column】 一个很好用的命令,经常用于管道符后,进行文本展示 diff --git a/source/c07/c07_01.rst b/source/c07/c07_01.rst index df51905..a93b219 100755 --- a/source/c07/c07_01.rst +++ b/source/c07/c07_01.rst @@ -1682,6 +1682,82 @@ CentOS # 如果我们不要颜色效果,可以这样使用转义符,使用原生的命令 $ \ll somedir +这里转几条来自 「\ **良许Linux**\ 」的总结,非常实用,你可以跟着设置一下 + +1、压缩包文件,特别是 tar 文件在 Linux 下使用非常广泛,但是 tar +命令的选项又非常多,也不好记住。所以我们可以将常用的几个选项定义为一个别名 +**untar** ,这样我们需要解压 tar 文件时,直接 **untar filename** 即可。 + +:: + + alias untar='tar -zxvf ' + +2、我们下载一个很大的文件时,突然网络异常中断了,我们重新下载是不是很抓狂?别担心,我们的 +wget 命令有个 -c 选项,支持断点下载,我们也可以将它设置为别名: + +:: + + alias wget='wget -c ' + +3、有时我们需要生成一个 20 个字符的随机数密码,我们可以使用 openssl +命令,但完整的命令又很长很不方便,我们可以设置别名: + +:: + + alias getpass="openssl rand -base64 20" + +4、下载一个文件之后,我们想要校验一下它的 checksum +值,可以将这个命令封装为一个别名 **sha** ,之后我们 **sha filename** +就可以校验文件的 checksum 值。 + +:: + + alias sha='shasum -a 256 ' + +5、正常情况下,ping 命令将无限次输出,但其实没多大意义。我们可以使用 -c +命令将其限制为 5 次输出,然后设置为别名 **ping** ,使用时,\ **ping +url** 即可。 + +:: + + alias ping='ping -c 5' + +6、如果我们想随时随地启动一个 web 服务器,我们可以使用这个别名: + +:: + + alias www='python -m SimpleHTTPServer 8000' + +7、网速的测试在工作中也经常用到,但 Linux +没有自带命令可用,我们可以借助第三方工具 **speedtest-cli** +。这个工具可以直接从 Github +上下载,使用方法里面也有详细介绍。我们需要先使用 **speedtest-cli** +命令来选择离我们最近的服务器,然后设置如下别名: + +:: + + alias speed='speedtest-cli --server 2406 --simple' + +8、你的公网 IP 是多少?记性好的可以直接背下来,但如果你有 10 +台上百台服务器呢?也可以背下来,然后参加最强大脑。其实有个命令可以直接查询,但那个命令太变态,不好记,果断设置为别名。 + +:: + + alias ipe='curl ipinfo.io/ip' + +9、如何知道自己的局域网 IP ?这个命令同样变态,果断设置别名。 + +:: + + alias ipi='ipconfig getifaddr en0' + +10、最后,清屏,我们可以使用 **ctrl + l** 快捷键,也可以将 **clear** +命令定义得更短,这样使用起来更直接,更粗暴。 + +:: + + alias c='clear' + 【column】 一个很好用的命令,经常用于管道符后,进行文本展示 From 302e34e56667c40fe5966df9a9a564d351d82aa7 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 10 Aug 2019 22:55:00 +0800 Subject: [PATCH 082/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20docker=20=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E7=9F=A5=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/c07_05.md | 147 +++++++++++++++++++++++++-------- source/c07/c07_05.rst | 184 +++++++++++++++++++++++++++++++----------- 2 files changed, 251 insertions(+), 80 deletions(-) diff --git a/source/c07/c07_05.md b/source/c07/c07_05.md index 52010b1..7e685cc 100644 --- a/source/c07/c07_05.md +++ b/source/c07/c07_05.md @@ -2,21 +2,24 @@ --- -## 一、三种基础网络 +## 7.5.1 三种local网络 在docker安装的时候会自动创建三种网络可供使用。 ```shell $ docker network ls -【none】 :没有网络,与外界完全隔离。这个只能用在容器业务不需要联网,不需要集群,而只在本机运行业务时使用。目的就是隔绝网络。 +1.【none】 :没有网络,与外界完全隔离。这个只能用在容器业务不需要联网,不需要集群,而只在本机运行业务时使用。目的就是隔绝网络。 --> 指定: $ docker run -it --network=none busybox - -【host】 :使用 host 网络,容器的网络配置和 host 完全一样。连 hostname 也是一样。最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择 host 网络。当然不便之处就是牺牲一些灵活性,比如要考虑端口冲突问题,Docker host 上已经使用的端口就不能再用了。 + + +2. 【host】 :使用 host 网络,容器的网络配置和 host 完全一样。 --> 指定: $ docker run -it --network=host busybox -【bridge】: docker安装的时候默认会创建一个 “docker0” 的linux bridge,若不指定网络,就会都挂在这个网桥下。若宿主机可上网,用这个网络创建的容器也可以上网。 + + +3. 【bridge】: docker安装的时候默认会创建一个 “docker0” 的linux bridge,若不指定网络,就会都挂在这个网桥下。若宿主机可上网,用这个网络创建的容器也可以上网。 --> 1. 无需指定,就使用这个网络 2. $ docker run -it --network=bridge busybox @@ -29,47 +32,53 @@ $ docker network inspect bridge 说明一下,host 网络: 直接使用 Docker host 的网络最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择 host 网络。当然不便之处就是牺牲一些灵活性,比如要考虑端口冲突问题,Docker host 上已经使用的端口就不能再用了。 -## 二、自定义网络 -### 2.1 跨主机网络 +## 7.5.2 自定义网络 +### 7.5.2.1 跨主机网络 上面几种网络都是自带的,同时我们也可以自定义网络(通常是网桥)。 ``` # 指定driver=bridge $ docker network create --driver bridge my_net1 -# 这种无法指定ip,subnet创建的网络才能指定,会报如下错误 -docker: Error response from daemon: user specified IP address is supported only when connecting to networks with user configured subnets. # 如果不指定网络段,会自己生成 172.x.0.1/16 ,x 从17开始,每创建一个递增。 -# 就是第一次创建是,172.17.0.1/16 -# 第二次创建是,172.18.0.1/16 - - $ docker network create --driver bridge --subnet 192.168.7.0/24 --gatewat 192.168.7.1 my_net2 $ ``` 1. 同一网络(网桥)下的容器,可以通信 2. 不同网络(网桥)下的容器,docker 默认不允许通信(当然可以通过路由转发来实现),可以用命令将容器挂到另一网桥上 +特别说明: + +>不指定subnet创建的网络,在创建容器时无法指定ip创建,会报如下错误 +> +>docker: Error response from daemon: user specified IP address is supported only when connecting to networks with user configured subnets. + + + 命令:docker network connect my_net2 -上面不管是 `none` , `host`, `bridge` 都是 `local` 类型的网络,仅能在本机上进行通信。 +上面 `7.5.1节` 里的三种网络,不管是 `none` , `host`, `bridge` 都是 `local` 类型的网络,它们仅能在本机上进行通信。 -而如果如实现集群通信,基础网络是不能满足要求的。这就需要我们自定义网络。这些网络也同样也是有三种(主要是根据 `driver` 不同): +而如果如实现集群通信,这三种基础的local网络是不能满足要求的。这就需要我们使用自定义网络。自定义网络,同样也分为三种(主要是根据 `driver` 不同): + +- bridge +- overlay +- macvlan ### 2.2 bridge -``` -# 只要指定driver为bridge 就可以创建一个网桥 -# driver 有三种,一种是bridge、overlay、macvlan +先来看看 bridge的。 +``` # 创建网络:bridge - $ docker network create --driver bridge my_net $ docker network create --driver bridge --subnet 192.168.7.0/24 --gatewat 192.168.7.1 my_net2 + # 该网络没有指定子网,使用的是dhcp分配ip -$ docker run -it --network my_net +$ docker run -it --network my_net1 + # 该网络创建时有指定子网,所以可以指定ip创建 $ docker run -it --network my_net2 --ip 192.168.7.10 @@ -156,28 +165,25 @@ docker exec bbox2 ip r `br0` 除了连接所有的 `endpoint`,还会连接一个 `vxlan` 设备,用于与其他 `host` 建立 `vxlan tunnel`。容器之间的数据就是通过这个 `tunnel` 通信的。 -## 三、容器之间通信 +## 7.5.3 容器之间通信 -``` 1. 同一网络(网桥)下的容器,可以通信 2. 不同网络(网桥)下的容器,docker 默认不允许通信(当然可以通过路由转发来实现),可以通过在容器挂一个和另一容器在同一网桥下的网卡 - 命令:docker network connect my_net2 + 命令:`docker network connect my_net2 ` +3. 共享网络 + 两个容器是可以共享一个网络的。共享网卡和配置信息。 + 命令:`docker run -it --network:container: busy` + +检测容器间的通信,都是用 ping ip 来判断的。 +在我们不知道其他容器的ip时,其实是可以通过 ping 容器名,**前提是这些容器都处于自定义的网络中,必须是自定义的。** -以上是通用类型,都是用ping ip来检测能否通信,或者说通过ip来通信。 -下面介绍,在我们不知道其他容器的ip时,可以通过容器名来通信,前提是这些容器都处于自定义的网络中,必须是自定义的。 如:现有两个容器,bbox1和bbox2,都用的my_net2,在bbox1里可以通过 ping bbox2来与bbox2通信。 但若现有bbox3,是使用bridge的网络,则无法使用这样的方式 -3. 共享网络 -两个容器是可以共享一个网络的。共享网卡和配置信息。 - 命令:docker run -it --network:container: busy -``` - -## 四、容器与外部通信 +## 7.5.4 容器与外部通信 包括 `容器访问外部`和`外部访问容器`。 -``` 1. 容器访问外部 是通过 NAT 网络地址转换,来使用host的ip给外部发送数据包。 这个只要配置下iptales就可以。 @@ -186,10 +192,85 @@ docker exec bbox2 ip r 2. 外部访问容器 容器对外暴露一个端口,这个port和host的port,要映射起来,这个是由docker proxy来做的。docker proxy会时时监控host的端口,若有请求访问这个host port就重定向给容器的port -命令:docker run -it -p 8080:80 httpd # 将hostr的8080映射给httpd容器的80端口 +命令:`docker run -it -p 8080:80 httpd ` + +是将 host 的8080映射给httpd容器的80端口。 + +## 7.5.5 容器的通信原理 + +Docker 的网络是基于 `namespace` 和 `cgroup` 实现的。 + +这里来讲讲的 namespace。 + +在Linux下,我们一般用ip命令创建 Network Namespace,而在Docker的源码中,它并没有用ip命令,而是自己实现了ip命令内的一些功能——是用了Raw Socket发些“奇怪”的数据。 + +这里,我通过 ip命令的演示来重现这个过程。 + +```shell +## 首先,我们先增加一个网桥lxcbr0,模仿docker0 +brctl addbr lxcbr0 +brctl stp lxcbr0 off +ifconfig lxcbr0 192.168.10.1/24 up #为网桥设置IP地址 + +## 接下来,我们要创建一个network namespace - ns1 + +# 增加一个namesapce 命令为 ns1 (使用ip netns add命令) +ip netns add ns1 + +# 激活namespace中的loopback,即127.0.0.1(使用ip netns exec ns1来操作ns1中的命令) +ip netns exec ns1 ip link set dev lo up + +## 然后,我们需要增加一对虚拟网卡 + +# 增加一个pair虚拟网卡,注意其中的veth类型,其中一个网卡要按进容器中 +ip link add veth-ns1 type veth peer name lxcbr0.1 + +# 把 veth-ns1 按到namespace ns1中,这样容器中就会有一个新的网卡了 +ip link set veth-ns1 netns ns1 + +# 把容器里的 veth-ns1改名为 eth0 (容器外会冲突,容器内就不会了) +ip netns exec ns1 ip link set dev veth-ns1 name eth0 + +# 为容器中的网卡分配一个IP地址,并激活它 +ip netns exec ns1 ifconfig eth0 192.168.10.11/24 up + + +# 上面我们把veth-ns1这个网卡按到了容器中,然后我们要把lxcbr0.1添加上网桥上 +brctl addif lxcbr0 lxcbr0.1 + +# 为容器增加一个路由规则,让容器可以访问外面的网络 +ip netns exec ns1 ip route add default via 192.168.10.1 + +# 在/etc/netns下创建network namespce名称为ns1的目录, +# 然后为这个namespace设置resolv.conf,这样,容器内就可以访问域名了 +mkdir -p /etc/netns/ns1 +echo "nameserver 8.8.8.8" > /etc/netns/ns1/resolv.conf +``` + +上面基本上就是docker网络的原理了,只不过, + +- Docker的resolv.conf没有用这样的方式,而是用了[上篇中的Mount Namesapce的那种方式](https://coolshell.cn/articles/17010.html) +- 另外,docker是用进程的PID来做Network Namespace的名称的。 + +了解了这些后,你甚至可以为正在运行的docker容器增加一个新的网卡: + +```shell +`ip link add peerA ``type` `veth peer name peerB ``brctl addif docker0 peerA ``ip link ``set` `peerA up ``ip link ``set` `peerB netns ${container-pid} ``ip netns ``exec` `${container-pid} ip link ``set` `dev peerB name eth1 ``ip netns ``exec` `${container-pid} ip link ``set` `eth1 up ; ``ip netns ``exec` `${container-pid} ip addr add ${ROUTEABLE_IP} dev eth1 ;` ``` +上面的示例是我们为正在运行的docker容器,增加一个eth1的网卡,并给了一个静态的可被外部访问到的IP地址。 + +这个需要把外部的“物理网卡”配置成混杂模式,这样这个eth1网卡就会向外通过ARP协议发送自己的Mac地址,然后外部的交换机就会把到这个IP地址的包转到“物理网卡”上,因为是混杂模式,所以eth1就能收到相关的数据,一看,是自己的,那么就收到。这样,Docker容器的网络就和外部通了。 + +当然,无论是Docker的NAT方式,还是混杂模式都会有性能上的问题,NAT不用说了,存在一个转发的开销,混杂模式呢,网卡上收到的负载都会完全交给所有的虚拟网卡上,于是就算一个网卡上没有数据,但也会被其它网卡上的数据所影响。 + +这两种方式都不够完美,我们知道,真正解决这种网络问题需要使用VLAN技术,于是Google的同学们为Linux内核实现了一个[IPVLAN的驱动](https://lwn.net/Articles/620087/),这基本上就是为Docker量身定制的。 + + + +## 参考文章 +- 7.5.5 摘自:[DOCKER基础技术:LINUX NAMESPACE(下)](https://coolshell.cn/articles/17029.html) --- diff --git a/source/c07/c07_05.rst b/source/c07/c07_05.rst index 734fdaf..a474517 100755 --- a/source/c07/c07_05.rst +++ b/source/c07/c07_05.rst @@ -3,8 +3,8 @@ -------------- -一、三种基础网络 ----------------- +7.5.1 三种local网络 +------------------- 在docker安装的时候会自动创建三种网络可供使用。 @@ -12,15 +12,18 @@ $ docker network ls - 【none】 :没有网络,与外界完全隔离。这个只能用在容器业务不需要联网,不需要集群,而只在本机运行业务时使用。目的就是隔绝网络。 + 1.【none】 :没有网络,与外界完全隔离。这个只能用在容器业务不需要联网,不需要集群,而只在本机运行业务时使用。目的就是隔绝网络。 --> 指定: $ docker run -it --network=none busybox - - 【host】 :使用 host 网络,容器的网络配置和 host 完全一样。连 hostname 也是一样。最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择 host 网络。当然不便之处就是牺牲一些灵活性,比如要考虑端口冲突问题,Docker host 上已经使用的端口就不能再用了。 + + + 2. 【host】 :使用 host 网络,容器的网络配置和 host 完全一样。 --> 指定: $ docker run -it --network=host busybox - 【bridge】: docker安装的时候默认会创建一个 “docker0” 的linux bridge,若不指定网络,就会都挂在这个网桥下。若宿主机可上网,用这个网络创建的容器也可以上网。 + + + 3. 【bridge】: docker安装的时候默认会创建一个 “docker0” 的linux bridge,若不指定网络,就会都挂在这个网桥下。若宿主机可上网,用这个网络创建的容器也可以上网。 --> 1. 无需指定,就使用这个网络 2. $ docker run -it --network=bridge busybox @@ -37,11 +40,11 @@ host 网络。当然不便之处就是牺牲一些灵活性,比如要考虑端口冲突问题,Docker host 上已经使用的端口就不能再用了。 -二、自定义网络 --------------- +7.5.2 自定义网络 +---------------- -2.1 跨主机网络 -~~~~~~~~~~~~~~ +7.5.2.1 跨主机网络 +~~~~~~~~~~~~~~~~~~ 上面几种网络都是自带的,同时我们也可以自定义网络(通常是网桥)。 @@ -49,15 +52,9 @@ host 上已经使用的端口就不能再用了。 # 指定driver=bridge $ docker network create --driver bridge my_net1 - # 这种无法指定ip,subnet创建的网络才能指定,会报如下错误 - docker: Error response from daemon: user specified IP address is supported only when connecting to networks with user configured subnets. # 如果不指定网络段,会自己生成 172.x.0.1/16 ,x 从17开始,每创建一个递增。 - # 就是第一次创建是,172.17.0.1/16 - # 第二次创建是,172.18.0.1/16 - - $ docker network create --driver bridge --subnet 192.168.7.0/24 --gatewat 192.168.7.1 my_net2 $ @@ -65,29 +62,41 @@ host 上已经使用的端口就不能再用了。 2. 不同网络(网桥)下的容器,docker 默认不允许通信(当然可以通过路由转发来实现),可以用命令将容器挂到另一网桥上 +特别说明: + + 不指定subnet创建的网络,在创建容器时无法指定ip创建,会报如下错误 + + docker: Error response from daemon: user specified IP address is + supported only when connecting to networks with user configured + subnets. + 命令:docker network connect my_net2 -上面不管是 ``none`` , ``host``, ``bridge`` 都是 ``local`` -类型的网络,仅能在本机上进行通信。 +上面 ``7.5.1节`` 里的三种网络,不管是 ``none`` , ``host``, ``bridge`` +都是 ``local`` 类型的网络,它们仅能在本机上进行通信。 -而如果如实现集群通信,基础网络是不能满足要求的。这就需要我们自定义网络。这些网络也同样也是有三种(主要是根据 +而如果如实现集群通信,这三种基础的local网络是不能满足要求的。这就需要我们使用自定义网络。自定义网络,同样也分为三种(主要是根据 ``driver`` 不同): +- bridge +- overlay +- macvlan + 2.2 bridge ~~~~~~~~~~ -:: +先来看看 bridge的。 - # 只要指定driver为bridge 就可以创建一个网桥 - # driver 有三种,一种是bridge、overlay、macvlan +:: # 创建网络:bridge - $ docker network create --driver bridge my_net $ docker network create --driver bridge --subnet 192.168.7.0/24 --gatewat 192.168.7.1 my_net2 + # 该网络没有指定子网,使用的是dhcp分配ip - $ docker run -it --network my_net + $ docker run -it --network my_net1 + # 该网络创建时有指定子网,所以可以指定ip创建 $ docker run -it --network my_net2 --ip 192.168.7.10 @@ -190,40 +199,121 @@ br0 上。 设备,用于与其他 ``host`` 建立 ``vxlan tunnel``\ 。容器之间的数据就是通过这个 ``tunnel`` 通信的。 -三、容器之间通信 ----------------- - -:: +7.5.3 容器之间通信 +------------------ - 1. 同一网络(网桥)下的容器,可以通信 - 2. 不同网络(网桥)下的容器,docker 默认不允许通信(当然可以通过路由转发来实现),可以通过在容器挂一个和另一容器在同一网桥下的网卡 - 命令:docker network connect my_net2 +1. 同一网络(网桥)下的容器,可以通信 +2. 不同网络(网桥)下的容器,docker + 默认不允许通信(当然可以通过路由转发来实现),可以通过在容器挂一个和另一容器在同一网桥下的网卡 + 命令:\ ``docker network connect my_net2 `` +3. 共享网络 两个容器是可以共享一个网络的。共享网卡和配置信息。 + 命令:\ ``docker run -it --network:container: busy`` - 以上是通用类型,都是用ping ip来检测能否通信,或者说通过ip来通信。 - 下面介绍,在我们不知道其他容器的ip时,可以通过容器名来通信,前提是这些容器都处于自定义的网络中,必须是自定义的。 - 如:现有两个容器,bbox1和bbox2,都用的my_net2,在bbox1里可以通过 ping bbox2来与bbox2通信。 - 但若现有bbox3,是使用bridge的网络,则无法使用这样的方式 +检测容器间的通信,都是用 ping ip 来判断的。 +在我们不知道其他容器的ip时,其实是可以通过 ping +容器名,\ **前提是这些容器都处于自定义的网络中,必须是自定义的。** - 3. 共享网络 - 两个容器是可以共享一个网络的。共享网卡和配置信息。 - 命令:docker run -it --network:container: busy +如:现有两个容器,bbox1和bbox2,都用的my_net2,在bbox1里可以通过 ping +bbox2来与bbox2通信。 +但若现有bbox3,是使用bridge的网络,则无法使用这样的方式 -四、容器与外部通信 ------------------- +7.5.4 容器与外部通信 +-------------------- 包括 ``容器访问外部``\ 和\ ``外部访问容器``\ 。 -:: - - 1. 容器访问外部 - 是通过 NAT 网络地址转换,来使用host的ip给外部发送数据包。 +1. 容器访问外部 是通过 NAT + 网络地址转换,来使用host的ip给外部发送数据包。 这个只要配置下iptales就可以。 +2. 外部访问容器 + 容器对外暴露一个端口,这个port和host的port,要映射起来,这个是由docker + proxy来做的。docker proxy会时时监控host的端口,若有请求访问这个host + port就重定向给容器的port + +命令:\ ``docker run -it -p 8080:80 httpd`` + +是将 host 的8080映射给httpd容器的80端口。 + +7.5.5 容器的通信原理 +-------------------- + +Docker 的网络是基于 ``namespace`` 和 ``cgroup`` 实现的。 + +这里来讲讲的 namespace。 + +在Linux下,我们一般用ip命令创建 Network +Namespace,而在Docker的源码中,它并没有用ip命令,而是自己实现了ip命令内的一些功能——是用了Raw +Socket发些“奇怪”的数据。 + +这里,我通过 ip命令的演示来重现这个过程。 + +.. code:: shell + + ## 首先,我们先增加一个网桥lxcbr0,模仿docker0 + brctl addbr lxcbr0 + brctl stp lxcbr0 off + ifconfig lxcbr0 192.168.10.1/24 up #为网桥设置IP地址 + + ## 接下来,我们要创建一个network namespace - ns1 + + # 增加一个namesapce 命令为 ns1 (使用ip netns add命令) + ip netns add ns1 + + # 激活namespace中的loopback,即127.0.0.1(使用ip netns exec ns1来操作ns1中的命令) + ip netns exec ns1 ip link set dev lo up + + ## 然后,我们需要增加一对虚拟网卡 + + # 增加一个pair虚拟网卡,注意其中的veth类型,其中一个网卡要按进容器中 + ip link add veth-ns1 type veth peer name lxcbr0.1 + + # 把 veth-ns1 按到namespace ns1中,这样容器中就会有一个新的网卡了 + ip link set veth-ns1 netns ns1 + + # 把容器里的 veth-ns1改名为 eth0 (容器外会冲突,容器内就不会了) + ip netns exec ns1 ip link set dev veth-ns1 name eth0 + + # 为容器中的网卡分配一个IP地址,并激活它 + ip netns exec ns1 ifconfig eth0 192.168.10.11/24 up + + + # 上面我们把veth-ns1这个网卡按到了容器中,然后我们要把lxcbr0.1添加上网桥上 + brctl addif lxcbr0 lxcbr0.1 + + # 为容器增加一个路由规则,让容器可以访问外面的网络 + ip netns exec ns1 ip route add default via 192.168.10.1 + + # 在/etc/netns下创建network namespce名称为ns1的目录, + # 然后为这个namespace设置resolv.conf,这样,容器内就可以访问域名了 + mkdir -p /etc/netns/ns1 + echo "nameserver 8.8.8.8" > /etc/netns/ns1/resolv.conf + +上面基本上就是docker网络的原理了,只不过, + +- Docker的resolv.conf没有用这样的方式,而是用了\ `上篇中的Mount + Namesapce的那种方式 `__ +- 另外,docker是用进程的PID来做Network Namespace的名称的。 + +了解了这些后,你甚至可以为正在运行的docker容器增加一个新的网卡: + +.. code:: shell + + `ip link add peerA ``type` `veth peer name peerB ``brctl addif docker0 peerA ``ip link ``set` `peerA up ``ip link ``set` `peerB netns ${container-pid} ``ip netns ``exec` `${container-pid} ip link ``set` `dev peerB name eth1 ``ip netns ``exec` `${container-pid} ip link ``set` `eth1 up ; ``ip netns ``exec` `${container-pid} ip addr add ${ROUTEABLE_IP} dev eth1 ;` + +上面的示例是我们为正在运行的docker容器,增加一个eth1的网卡,并给了一个静态的可被外部访问到的IP地址。 + +这个需要把外部的“物理网卡”配置成混杂模式,这样这个eth1网卡就会向外通过ARP协议发送自己的Mac地址,然后外部的交换机就会把到这个IP地址的包转到“物理网卡”上,因为是混杂模式,所以eth1就能收到相关的数据,一看,是自己的,那么就收到。这样,Docker容器的网络就和外部通了。 + +当然,无论是Docker的NAT方式,还是混杂模式都会有性能上的问题,NAT不用说了,存在一个转发的开销,混杂模式呢,网卡上收到的负载都会完全交给所有的虚拟网卡上,于是就算一个网卡上没有数据,但也会被其它网卡上的数据所影响。 + +这两种方式都不够完美,我们知道,真正解决这种网络问题需要使用VLAN技术,于是Google的同学们为Linux内核实现了一个\ `IPVLAN的驱动 `__\ ,这基本上就是为Docker量身定制的。 - 2. 外部访问容器 - 容器对外暴露一个端口,这个port和host的port,要映射起来,这个是由docker proxy来做的。docker proxy会时时监控host的端口,若有请求访问这个host port就重定向给容器的port +参考文章 +-------- - 命令:docker run -it -p 8080:80 httpd # 将hostr的8080映射给httpd容器的80端口 +- 7.5.5 摘自:\ `DOCKER基础技术:LINUX + NAMESPACE(下) `__ -------------- From bd04a2aa75f16a2381db2614ea0d92fbd7c49a73 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 11 Aug 2019 10:26:34 +0800 Subject: [PATCH 083/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=A3=85=E9=A5=B0?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_19.md | 47 +++++++++++++++++++++++++++++++++++++++++++ source/c01/c01_19.rst | 44 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 source/c01/c01_19.md create mode 100644 source/c01/c01_19.rst diff --git a/source/c01/c01_19.md b/source/c01/c01_19.md new file mode 100644 index 0000000..1f084ab --- /dev/null +++ b/source/c01/c01_19.md @@ -0,0 +1,47 @@ +# 1.19 Django Blog笔记 + +```shell +# 运行项目 +python manager.py runserver 127.0.0.1:8080 + +# 登陆 xadmin 后台 +127.0.0.1:8080/xadmin + +# 帐号密码 +MING:wbm54378 + +# 更新表结构 +python manage.py makemigrations +python manage.py migrate +``` + + + +配置文件 + +```shell +https://www.cnblogs.com/yangxiaolan/p/5826661.html + +# 开发环境中 static 可以这样 +STATIC_URL = '/static/' +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, 'static'), +] + +# 生产环境 +STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE_DIR, 'static') +``` + +数据库密码 + +```shell +root:Wangbm@123 +wangbm:VfgiCtc42tpu +``` + + + +Django 中使用上传图片,显示图片 + +- [Django Media URL的配置](https://www.jianshu.com/p/7979d3e32495) \ No newline at end of file diff --git a/source/c01/c01_19.rst b/source/c01/c01_19.rst new file mode 100644 index 0000000..fc0d271 --- /dev/null +++ b/source/c01/c01_19.rst @@ -0,0 +1,44 @@ +1.19 Django Blog笔记 +==================== + +.. code:: shell + + # 运行项目 + python manager.py runserver 127.0.0.1:8080 + + # 登陆 xadmin 后台 + 127.0.0.1:8080/xadmin + + # 帐号密码 + MING:wbm54378 + + # 更新表结构 + python manage.py makemigrations + python manage.py migrate + +配置文件 + +.. code:: shell + + https://www.cnblogs.com/yangxiaolan/p/5826661.html + + # 开发环境中 static 可以这样 + STATIC_URL = '/static/' + STATICFILES_DIRS = [ + os.path.join(BASE_DIR, 'static'), + ] + + # 生产环境 + STATIC_URL = '/static/' + STATIC_ROOT = os.path.join(BASE_DIR, 'static') + +数据库密码 + +.. code:: shell + + root:Wangbm@123 + wangbm:VfgiCtc42tpu + +Django 中使用上传图片,显示图片 + +- `Django Media URL的配置 `__ From dfdc04de42dea8c6fcf30d0ed06635414413d0e0 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 27 Aug 2019 20:33:16 +0800 Subject: [PATCH 084/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0git=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=8A=80=E5=B7=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_06.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/c04/c04_06.md b/source/c04/c04_06.md index 1f89981..be02abf 100644 --- a/source/c04/c04_06.md +++ b/source/c04/c04_06.md @@ -65,6 +65,9 @@ $ git push -u learn_git master #learn_git是之前命名的远程仓库名,mas $ git clone [github url] # 举例 $ git clone git@github.com:BingmingWong/test.git + +# 更改远程仓库地址 +git remote set-url origin git@:you_repo.git ``` ### 1.5 GitHub合并至本地 @@ -360,6 +363,8 @@ Git 的使用全都是命令行的,由于没有那么直观,所以很容易 +如果想要用户名和密码登陆,可以将上面的 OpenSSH 改成 PuTTY/Plink就行了。 + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file From 914ebcd9344642ec13c9ca64baf6b2b375485bee Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 27 Aug 2019 20:42:11 +0800 Subject: [PATCH 085/302] =?UTF-8?q?=E6=B7=BB=E5=8A=A0fallocate=E5=91=BD?= =?UTF-8?q?=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/c07_01.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/c07/c07_01.md b/source/c07/c07_01.md index 5bc4404..1ae5b36 100644 --- a/source/c07/c07_01.md +++ b/source/c07/c07_01.md @@ -1127,6 +1127,12 @@ $ losetup -a $ losetup -d /dev/loop0 ``` +使用dd拷贝磁盘可以,但是如果想要快速生成大文件,速度就相当慢了,可以试试fallocate命令 + +```shell +fallocate -l 100G test_file +``` + ### 2.6 任务管理 #### 一次性任务(at) From 19d6da6c5898dc6533dda6b48fc50323f8073ce0 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 27 Aug 2019 20:43:59 +0800 Subject: [PATCH 086/302] update --- source/c08/c08_01.md | 10 +++++++++- source/c08/c08_03.md | 13 ++++++++----- source/c08/c08_06.md | 2 +- source/c08/c08_08.md | 4 ++++ source/c08/c08_14.md | 8 ++++++++ source/c08/c08_15.md | 22 +++++++++++++++++++++- 6 files changed, 51 insertions(+), 8 deletions(-) diff --git a/source/c08/c08_01.md b/source/c08/c08_01.md index 2c11b0b..fdd7618 100644 --- a/source/c08/c08_01.md +++ b/source/c08/c08_01.md @@ -128,7 +128,7 @@ $neutron subnet-create --name public\ --allocation-pool start=172.20.20.100,end=172.20.20.199 \ --gateway 172.20.20.200 \ --enable_dhcp=False \ - --dns_nameserver 114.114.114.114 \ + --dns-nameserver 114.114.114.114 \ public 172.20.20.0/24 # 创建子网,更多选项可以查看 neutron subnet-create -h @@ -384,6 +384,14 @@ rabbitmqctl set_user_tags openstack administrator # 指定节点执行命令 rabbitmq -n rabbit@ws_controller02 [command] ``` +## 3.3 pacemaker + +``` +pkill -9 pacemaker;service pacemaker restart +``` + + + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_03.md b/source/c08/c08_03.md index ad2f274..be0a6de 100644 --- a/source/c08/c08_03.md +++ b/source/c08/c08_03.md @@ -444,16 +444,19 @@ service firewalld stop sudo update-rc.d -f firewalld remove ``` -设置cloud-init参数 -```shell - -``` - 若有其他要修改的地方,可自行修改。然后关机虚拟机 ``` shutdown -h now ``` + + +## 8.3.3 修改镜像的文件 + +通过 guestfish 工具可以实现不用创建虚拟机就可以修改镜像里的文件内容。 + +![](http://image.python-online.cn/20190827200522.png) + ## 附录:参考文档 * [OpenStack社区:CentOS 镜像制作示例](http://docs.ocselected.org/openstack-manuals/kilo/image-guide/content/centos-image.html) diff --git a/source/c08/c08_06.md b/source/c08/c08_06.md index 1ed2d23..5129988 100644 --- a/source/c08/c08_06.md +++ b/source/c08/c08_06.md @@ -52,7 +52,7 @@ cloud-init 是 linux 的一个工具,当系统启动时,cloud-init 可从 no 3. cloud-init modules -m config 4. cloud-init modules -m final -除了这几个命令之外,还有几个 +除了这几个命令之外,还有几个: - cloud-init query -n [name] - cloud-init single -n [name] --frequency diff --git a/source/c08/c08_08.md b/source/c08/c08_08.md index 719b586..a654981 100644 --- a/source/c08/c08_08.md +++ b/source/c08/c08_08.md @@ -137,6 +137,10 @@ systemctl restart neutron-openvswitch-agent 9. 使用 dhcp 的模式,cloudinit 从 configure drive 中知道是dhcp后就不会去刷新配置文件将static 改为dhcp(使用的是NetworkManager自动获取ip,这在开机启动时 NetworkManager就会先于cloudinit 去做),所以如果这个镜像原网卡配置文件里是静态ip,那么使用这个镜像创建dhcp 的虚拟机,就会暴露旧ip,但是这对于配置ip没有影响,NetworkManager 配置ip的顺序是先dhcp,获取不到再从配置文件读。 10. 如果一个子网只有dhcp port,子网可以被删除,如果有其他port,则子网不能删除。 +11. 一个network一个ns,一个ns下面会有多个dhcp server的ip,正常情况下每个subnet一个dhcp server,若一个network下有多个开启了dhcp server 的子网,只关闭其中一个子网的dhcp,并不会立马删除这个dhcp port,因为这个port还有其他人占用(感觉这块neutron处理不好,应该更新一个port的fixed_ip,把关闭dhcp 的子网的ip给删除掉),当一个network里没有开启dhcp的子网且没有虚拟机使用dhcp的情况下,neutron才会删除这个dhcp port。 + + + ## 8.8.5 镜像问题排查 diff --git a/source/c08/c08_14.md b/source/c08/c08_14.md index aa8eead..5c728d7 100644 --- a/source/c08/c08_14.md +++ b/source/c08/c08_14.md @@ -247,6 +247,14 @@ neutron port-create --fixed-ip \ +重装 `openvswith.ko` 过程 + +```shell +ovs-dpctl del-dp ovs-system && rmmod openvswitch && insmod ./openvswitch.ko && service openvswitch restart && service network restart +``` + + + --- diff --git a/source/c08/c08_15.md b/source/c08/c08_15.md index efc5f60..1e13ac6 100644 --- a/source/c08/c08_15.md +++ b/source/c08/c08_15.md @@ -30,4 +30,24 @@ neutron api 的入口是在这里![](http://image.python-online.cn/2019080411184 ![](http://image.python-online.cn/20190804092214.png) -![](http://image.python-online.cn/20190804091911.png) \ No newline at end of file +![](http://image.python-online.cn/20190804091911.png) + + + + + +--- + +当不指定子网、也不指定ip时(也就是不传fixed_ip),而且 cluster 里 即 有ipv4 的子网也有ipv6的子网,这时候 neutron 会去创建一个既有ipv4也有ipv6的port,只要有一个version的子网里没有可用ip,都会失败。 + +![](http://image.python-online.cn/20190809213209.png) + +这里是遍历一个version的所有的子网列表,只要能找到一个子网有可用ip,就返回,如果遍历完都没有找到ip,那就要抛出异常了。 + +![](http://image.python-online.cn/20190809213223.png) + +```python +class IpAddressGenerationFailureAllSubnets(IpAddressGenerationFailure): + message = _("No more IP addresses available.") +``` + From 74818e918b1a2aa3404498dc5936cfacd833208f Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 27 Aug 2019 20:44:33 +0800 Subject: [PATCH 087/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0openstack=E7=AC=94?= =?UTF-8?q?=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_05.md | 57 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index eb3f479..21b6010 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -485,6 +485,31 @@ nova api 的接口参数是在 nova/api/openstack/compute/schemas/ 目录下, ![](http://image.python-online.cn/20190730140551.png) +装饰器里会传入一个参数,就是 schema 的内容,通过它可以约束一个请求内的参数合法性。 + +schemas 的文件都统一放在 `nova\api\openstack\compute\schemas` 目录下。 + +对于创建虚拟机来说,它的 schemas 文件是在上面目录下的 `servers.py` + +但并不是说,创建虚拟机的所有参数校验规则只在这里,nova 这边引入了 stevedore,它是 oslo 项目中为OpenStack其他项目提供动态加载功能的公共组件库(详细可以看这篇文章:https://blog.csdn.net/bill_xiang_/article/details/78852717)。通过它可以用添加扩展的方式,给 servers 更新 schemas。 + +![](http://image.python-online.cn/20190826210813.png) + +关键在于这个 map 函数,它会循环所有的扩展() + +![](http://image.python-online.cn/20190826211542.png) + +将调用传进去的 `self._create_extension_schema` 将已加载到的扩展schemas 更新到主schemas里去。 + +``` +# [ext.obj.name for ext in self.create_schema_manager.extensions] +['MultipleCreate', 'BlockDeviceMapping', 'BlockDeviceMappingV1', 'AvailabilityZone', 'UserData', 'Keypairs', 'SchedulerHints', 'SecurityGroups', 'ConfigDrive'] +``` + + + +![](http://image.python-online.cn/20190826211737.png) + 装饰器 schemas 的定义如下: ![](http://image.python-online.cn/20190730142527.png) @@ -493,6 +518,38 @@ nova api 的接口参数是在 nova/api/openstack/compute/schemas/ 目录下, ![](http://image.python-online.cn/20190730143003.png) + + +## 8.5.20 往数据库中加字段 + +先先定义好migrate 文件 + +```python +from sqlalchemy import MetaData, Text, Table, Column + + +def upgrade(migrate_engine): + meta = MetaData(bind=migrate_engine) + instances = Table('instances', meta, autoload=True) + + if not hasattr(instances.c, 'host_routes'): + instances.create_column(Column('host_routes', Text(), default=[])) +``` + +然后在 nova/objects/instances.py 中的fields加入相应的字段。 + +然后在 nova/db/sqlalchemy/models.py 也要加相应的 Column + +最后再执行这个命令,同步数据库 + +``` +nova-manage db sync +``` + + + + + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file From 7a6f61d772c2797b0f9a4d8b26a8e7b5baef41ec Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Wed, 28 Aug 2019 12:48:50 +0800 Subject: [PATCH 088/302] update --- source/c03/c03_01.md | 175 ++++++++++++++++++++++---------------- source/c03/c03_01.rst | 192 +++++++++++++++++++++++++----------------- source/c04/c04_09.md | 8 +- 3 files changed, 222 insertions(+), 153 deletions(-) diff --git a/source/c03/c03_01.md b/source/c03/c03_01.md index 4ff1316..7ebf962 100644 --- a/source/c03/c03_01.md +++ b/source/c03/c03_01.md @@ -2,24 +2,50 @@ --- -## 3.1.1 装饰器语法糖 +对于每一个学习 Python 的同学,想必对 `@` 符号一定不陌生了,正如你所知, @ 符号是装饰器的语法糖,@符号后面的函数就是我们本文的主角:**装饰器**。 -如果你接触 Python 有一段时间了的话,想必你对 `@` 符号一定不陌生了,没错 `@` 符号就是装饰器的语法糖。 +装饰器放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为 `装饰器` 。 -它放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为`装饰函数` 或 `装饰器`。 +曾经我在刚转行做程序员时的一次的面试中,被面试官问过这样的两个问题: -你要问我装饰器可以实现什么功能?我只能说你的脑洞有多大,装饰器就有多强大。 +> 1、你都用过装饰器实现过什么样的功能? +> +> 2、如何写一个可以传参的装饰器? -装饰器的使用方法很固定: -- 先定义一个装饰函数(帽子)(也可以用类、偏函数实现) -- 再定义你的业务函数、或者类(人) -- 最后把这顶帽子带在这个人头上 +对于当时实战经验非常有限的我,第一个问题只能回答一些非常简单的用法,而第二个问题却没能回答上来。 -装饰器的简单的用法有很多,这里举两个常见的。 -- 日志打印器 -- 时间计时器 +当时带着这两个问题,我就开始系统的学习装饰器的所有内容。这些一直整理在自己的博客中,今天对其进行了大量的补充和勘误,发表在这里分享给大家。希望对刚入门以及进阶的朋友可以提供一些参考。 -## 3.1.2 入门用法:日志打印器 +![](http://image.python-online.cn/20190811100737.png) + +## 3.1.1 Hello,装饰器 + +装饰器的使用方法很固定 +- 先定义一个装饰器(帽子) +- 再定义你的业务函数或者类(人) +- 最后把这装饰器(帽子)扣在这个函数(人)头上 + +就像下面这样子 + +```python +def decorator(func): + def wrapper(*args, **kw): + return func() + return wrapper + +@decorator +def function(): + print("hello, decorator") +``` + +实际上,装饰器并不是编码必须性,意思就是说,你不使用装饰器完全可以,它的出现,应该是使我们的代码 + +- 更加优雅,代码结构更加清晰 +- 将实现特定的功能代码封装成装饰器,提高代码复用率,增强代码可读性 + +接下来,我将以实例讲解,如何编写出各种简单及复杂的装饰器。 + +## 3.1.2 入门:日志打印器 首先是**日志打印器**。 实现的功能: @@ -27,15 +53,15 @@ - 在函数执行完,也不能拍拍屁股就走人了,咱可是有礼貌的代码,再打印一行日志告知下主人,我执行完啦。 ```python -# 这是装饰函数 +# 这是装饰器函数,参数 func 是被装饰的函数 def logger(func): def wrapper(*args, **kw): - print('我准备开始计算:{} 函数了:'.format(func.__name__)) + print('主人,我准备开始执行:{} 函数了:'.format(func.__name__)) # 真正执行的是这行。 func(*args, **kw) - print('啊哈,我计算完啦。给自己加个鸡腿!!') + print('主人,我执行完啦。') return wrapper ``` 假如,我的业务函数是,计算两个数之和。写好后,直接给它带上帽子。 @@ -44,18 +70,18 @@ def logger(func): def add(x, y): print('{} + {} = {}'.format(x, y, x+y)) ``` -然后我们来计算一下。 +然后执行一下 add 函数。 ```python add(200, 50) ``` -快来看看输出了什么,神奇不? +来看看输出了什么? ```python -我准备开始计算:add 函数了: +主人,我准备开始执行:add 函数了: 200 + 50 = 250 -啊哈,我计算完啦。给自己加个鸡腿! +主人,我执行完啦。 ``` -## 3.1.3 入门用法:时间计时器 +## 3.1.3 入门:时间计时器 再来看看 **时间计时器** 实现功能:顾名思义,就是计算一个函数的执行时长。 @@ -84,50 +110,54 @@ def want_sleep(sleep_time): want_sleep(10) ``` -来看看,输出。真的是2秒. +来看看输出,如预期一样,输出10秒。 ``` -花费时间:2.0073800086975098秒 +花费时间:10.0073800086975098秒 ``` -## 3.1.4 进阶用法:带参数的函数装饰器 - -通过上面简单的入门,你大概已经感受到了装饰的神奇魅力了。 +## 3.1.4 进阶:带参数的函数装饰器 -不过,装饰器的用法远不止如此。我们今天就要把这个知识点讲透。 +通过上面两个简单的入门示例,你应该能体会到装饰器的工作原理了。 -上面的例子,装饰器是不能接收参数的。其用法,只能适用于一些简单的场景。不传参的装饰器,只能对被装饰函数,执行固定逻辑。 +不过,装饰器的用法还远不止如此,深究下去,还大有文章。今天就一起来把这个知识点学透。 -如果你有经验,你一定经常在项目中,看到有的装饰器是带有参数的。 +回过头去看看上面的例子,装饰器是不能接收参数的。其用法,只能适用于一些简单的场景。不传参的装饰器,只能对被装饰函数,执行固定逻辑。 -装饰器本身是一个函数,既然做为一个函数都不能携带函数,那这个函数的功能就很受限。只能执行固定的逻辑。这无疑是非常不合理的。而如果我们要用到两个内容大体一致,只是某些地方不同的逻辑。不传参的话,我们就要写两个装饰器。小明觉得这不能忍。 +装饰器本身是一个函数,做为一个函数,如果不能传参,那这个函数的功能就会很受限,只能执行固定的逻辑。这意味着,如果装饰器的逻辑代码的执行需要根据不同场景进行调整,若不能传参的话,我们就要写两个装饰器,这显然是不合理的。 -那么装饰器如何实现`传参`呢,会比较复杂,需要两层嵌套。 +比如我们要实现一个可以定时发送邮件的任务(一分钟发送一封),定时进行时间同步的任务(一天同步一次),就可以自己实现一个 periodic_task (定时任务)的装饰器,这个装饰器可以接收一个时间间隔的参数,间隔多长时间执行一次任务。 -同样,我们也来举个例子。 +可以这样像下面这样写,由于这个功能代码比较复杂,不利于学习,这里就不贴了。 -我们要在这两个函数的执行的时候,分别根据其国籍,来说出一段打招呼的话。 ```python -def chinese(): - print("我来自中国。") - -def american(): - print("I am from America.") +@periodic_task(spacing=60) +def send_mail(): + pass + +@periodic_task(spacing=86400) +def ntp() + pass ``` -在给他们俩戴上装饰器的时候,就要跟装饰器说,这个人是哪国人,然后装饰器就会做出判断,打出对应的招呼。 -戴上帽子后,是这样的。 +那我们来自己创造一个伪场景,可以在装饰器里传入一个参数,指明国籍,并在函数执行前,用自己国家的母语打一个招呼。 + ```python +# 小明,中国人 @say_hello("china") -def chinese(): - print("我来自中国。") +def xiaoming(): + pass +# jack,美国人 @say_hello("america") -def american(): - print("I am from America.") +def jack(): + pass ``` -万事俱备,只差帽子了。来定义一下,这里需要两层嵌套。 +那我们如果实现这个装饰器,让其可以实现 `传参` 呢? + +会比较复杂,需要两层嵌套。 + ```python def say_hello(contry): def wrapper(func): @@ -144,23 +174,19 @@ def say_hello(contry): return deco return wrapper ``` -执行一下 +来执行一下 ```python -american() +xiaoming() print("------------") -chinese() +jack() ``` 看看输出结果。 ```python 你好! -我来自中国。 ------------ hello. -I am from America ``` -emmmm,这很NB。。。 - -## 3.1.5 高阶用法:不带参数的类装饰器 +## 3.1.5 高阶:不带参数的类装饰器 以上都是基于函数实现的装饰器,在阅读别人代码时,还可以时常发现还有基于类实现的装饰器。 @@ -168,6 +194,8 @@ emmmm,这很NB。。。 `__init__` :接收被装饰函数 `__call__` :实现装饰逻辑。 +还是以日志打印这个简单的例子为例 + ```python class logger(object): def __init__(self, func): @@ -190,9 +218,7 @@ say("hello") say hello! ``` - - -## 3.1.6 高阶用法:带参数的类装饰器 +## 3.1.6 高阶:带参数的类装饰器 上面不带参数的例子,你发现没有,只能打印`INFO`级别的日志,正常情况下,我们还需要打印`DEBUG` `WARNING`等级别的日志。 这就需要给类装饰器传入参数,给这个函数指定级别了。 @@ -233,7 +259,9 @@ say hello! 对于这个 callable 对象,我们最熟悉的就是函数了。 -除函数之外,类也可以是 callable 对象,只要实现了`__call__` 函数(上面几个盒子已经接触过了),还有比较少人使用的偏函数也是 callable 对象。 +除函数之外,类也可以是 callable 对象,只要实现了`__call__` 函数(上面几个例子已经接触过了)。 + +还有容易被人忽略的偏函数其实也是 callable 对象。 接下来就来说说,如何使用 类和偏函数结合实现一个与众不同的装饰器。 @@ -430,16 +458,16 @@ class Student(object): self.age = age # 实例化 -XiaoMing = Student("小明") +xiaoming = Student("小明") # 添加属性 -XiaoMing.age=25 +xiaoming.age=25 # 查询属性 -XiaoMing.age +xiaoming.age # 删除属性 -del XiaoMing.age +del xiaoming.age ``` 但是稍有经验的开发人员,一下就可以看出,这样直接把属性暴露出去,虽然写起来很简单,但是并不能对属性的值做合法性限制。为了实现这个功能,我们可以这样写。 ```python @@ -462,25 +490,25 @@ class Student(object): self._age = None -XiaoMing = Student("小明") +xiaoming = Student("小明") # 添加属性 -XiaoMing.set_age(25) +xiaoming.set_age(25) # 查询属性 -XiaoMing.get_age() +xiaoming.get_age() # 删除属性 -XiaoMing.del_age() +xiaoming.del_age() ``` 上面的代码设计虽然可以变量的定义,但是可以发现不管是获取还是赋值(通过函数)都和我们平时见到的不一样。 按照我们思维习惯应该是这样的。 ``` # 赋值 -XiaoMing.age = 25 +xiaoming.age = 25 # 获取 -XiaoMing.age +xiaoming.age ``` 那么这样的方式我们如何实现呢。请看下面的代码。 ```python @@ -505,16 +533,16 @@ class Student(object): def age(self): del self._age -XiaoMing = Student("小明") +xiaoming = Student("小明") # 设置属性 -XiaoMing.age = 25 +xiaoming.age = 25 # 查询属性 -XiaoMing.age +xiaoming.age # 删除属性 -del XiaoMing.age +del xiaoming.age ``` 用`@property`装饰过的函数,会将一个函数定义成一个属性,属性的值就是该函数return的内容。**同时**,会将这个函数变成另外一个装饰器。就像后面我们使用的`@age.setter`和`@age.deleter`。 @@ -644,7 +672,6 @@ in __get__ - 使代码可读性更高,逼格更高; - 代码结构更加清晰,代码冗余度更低; - 刚好我在最近也有一个场景,可以用装饰器很好的实现,暂且放上来看看。 这是一个实现控制函数运行超时的装饰器。如果超时,则会抛出超时异常。 @@ -673,4 +700,8 @@ def timeout_limit(timeout_time): ``` ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) +非常感谢你能阅读到这里,这篇文章我写了很久,算是比较干货的那种,文章有些长,但还是希望花点时间把这些知识点都搞明白,而不要只是收藏。 + +我的文章更新频率是远低于其他 Python 技术号,但我仍然坚持自己,坚持原创,每周虽然只有一篇,但我能保证我的每一篇文章都是诚意之作。希望那些对你有帮助的文章能够多多帮忙转发分享。这也是我更新的一大动力。非常感谢。 + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c03/c03_01.rst b/source/c03/c03_01.rst index a89d41a..0f01f88 100755 --- a/source/c03/c03_01.rst +++ b/source/c03/c03_01.rst @@ -3,25 +3,54 @@ -------------- -3.1.1 装饰器语法糖 ------------------- +对于每一个学习 Python 的同学,想必对 ``@`` +符号一定不陌生了,正如你所知, @ +符号是装饰器的语法糖,@符号后面的函数就是我们本文的主角:\ **装饰器**\ 。 -如果你接触 Python 有一段时间了的话,想必你对 ``@`` -符号一定不陌生了,没错 ``@`` 符号就是装饰器的语法糖。 +装饰器放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为 +``装饰器`` 。 -它放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为\ ``装饰函数`` -或 ``装饰器``\ 。 +曾经我在刚转行做程序员时的一次的面试中,被面试官问过这样的两个问题: -你要问我装饰器可以实现什么功能?我只能说你的脑洞有多大,装饰器就有多强大。 + 1、你都用过装饰器实现过什么样的功能? -装饰器的使用方法很固定: - -先定义一个装饰函数(帽子)(也可以用类、偏函数实现) - -再定义你的业务函数、或者类(人) - 最后把这顶帽子带在这个人头上 + 2、如何写一个可以传参的装饰器? -装饰器的简单的用法有很多,这里举两个常见的。 - 日志打印器 - 时间计时器 +对于当时实战经验非常有限的我,第一个问题只能回答一些非常简单的用法,而第二个问题却没能回答上来。 -3.1.2 入门用法:日志打印器 --------------------------- +当时带着这两个问题,我就开始系统的学习装饰器的所有内容。这些一直整理在自己的博客中,今天对其进行了大量的补充和勘误,发表在这里分享给大家。希望对刚入门以及进阶的朋友可以提供一些参考。 + +|image0| + +3.1.1 Hello,装饰器 +------------------- + +装饰器的使用方法很固定 - 先定义一个装饰器(帽子) - +再定义你的业务函数或者类(人) - +最后把这装饰器(帽子)扣在这个函数(人)头上 + +就像下面这样子 + +.. code:: python + + def decorator(func): + def wrapper(*args, **kw): + return func() + return wrapper + + @decorator + def function(): + print("hello, decorator") + +实际上,装饰器并不是编码必须性,意思就是说,你不使用装饰器完全可以,它的出现,应该是使我们的代码 + +- 更加优雅,代码结构更加清晰 +- 将实现特定的功能代码封装成装饰器,提高代码复用率,增强代码可读性 + +接下来,我将以实例讲解,如何编写出各种简单及复杂的装饰器。 + +3.1.2 入门:日志打印器 +---------------------- 首先是\ **日志打印器**\ 。 实现的功能: - 在函数执行前,先打印一行日志告知一下主人,我要执行函数了。 - @@ -29,15 +58,15 @@ .. code:: python - # 这是装饰函数 + # 这是装饰器函数,参数 func 是被装饰的函数 def logger(func): def wrapper(*args, **kw): - print('我准备开始计算:{} 函数了:'.format(func.__name__)) + print('主人,我准备开始执行:{} 函数了:'.format(func.__name__)) # 真正执行的是这行。 func(*args, **kw) - print('啊哈,我计算完啦。给自己加个鸡腿!!') + print('主人,我执行完啦。') return wrapper 假如,我的业务函数是,计算两个数之和。写好后,直接给它带上帽子。 @@ -48,22 +77,22 @@ def add(x, y): print('{} + {} = {}'.format(x, y, x+y)) -然后我们来计算一下。 +然后执行一下 add 函数。 .. code:: python add(200, 50) -快来看看输出了什么,神奇不? +来看看输出了什么? .. code:: python - 我准备开始计算:add 函数了: + 主人,我准备开始执行:add 函数了: 200 + 50 = 250 - 啊哈,我计算完啦。给自己加个鸡腿! + 主人,我执行完啦。 -3.1.3 入门用法:时间计时器 --------------------------- +3.1.3 入门:时间计时器 +---------------------- 再来看看 **时间计时器** 实现功能:顾名思义,就是计算一个函数的执行时长。 @@ -94,54 +123,56 @@ want_sleep(10) -来看看,输出。真的是2秒. +来看看输出,如预期一样,输出10秒。 :: - 花费时间:2.0073800086975098秒 - -3.1.4 进阶用法:带参数的函数装饰器 ----------------------------------- + 花费时间:10.0073800086975098秒 -通过上面简单的入门,你大概已经感受到了装饰的神奇魅力了。 - -不过,装饰器的用法远不止如此。我们今天就要把这个知识点讲透。 +3.1.4 进阶:带参数的函数装饰器 +------------------------------ -上面的例子,装饰器是不能接收参数的。其用法,只能适用于一些简单的场景。不传参的装饰器,只能对被装饰函数,执行固定逻辑。 +通过上面两个简单的入门示例,你应该能体会到装饰器的工作原理了。 -如果你有经验,你一定经常在项目中,看到有的装饰器是带有参数的。 +不过,装饰器的用法还远不止如此,深究下去,还大有文章。今天就一起来把这个知识点学透。 -装饰器本身是一个函数,既然做为一个函数都不能携带函数,那这个函数的功能就很受限。只能执行固定的逻辑。这无疑是非常不合理的。而如果我们要用到两个内容大体一致,只是某些地方不同的逻辑。不传参的话,我们就要写两个装饰器。小明觉得这不能忍。 +回过头去看看上面的例子,装饰器是不能接收参数的。其用法,只能适用于一些简单的场景。不传参的装饰器,只能对被装饰函数,执行固定逻辑。 -那么装饰器如何实现\ ``传参``\ 呢,会比较复杂,需要两层嵌套。 +装饰器本身是一个函数,做为一个函数,如果不能传参,那这个函数的功能就会很受限,只能执行固定的逻辑。这意味着,如果装饰器的逻辑代码的执行需要根据不同场景进行调整,若不能传参的话,我们就要写两个装饰器,这显然是不合理的。 -同样,我们也来举个例子。 +比如我们要实现一个可以定时发送邮件的任务(一分钟发送一封),定时进行时间同步的任务(一天同步一次),就可以自己实现一个 +periodic_task +(定时任务)的装饰器,这个装饰器可以接收一个时间间隔的参数,间隔多长时间执行一次任务。 -我们要在这两个函数的执行的时候,分别根据其国籍,来说出一段打招呼的话。 +可以这样像下面这样写,由于这个功能代码比较复杂,不利于学习,这里就不贴了。 .. code:: python - def chinese(): - print("我来自中国。") + @periodic_task(spacing=60) + def send_mail(): + pass + + @periodic_task(spacing=86400) + def ntp() + pass - def american(): - print("I am from America.") - -在给他们俩戴上装饰器的时候,就要跟装饰器说,这个人是哪国人,然后装饰器就会做出判断,打出对应的招呼。 - -戴上帽子后,是这样的。 +那我们来自己创造一个伪场景,可以在装饰器里传入一个参数,指明国籍,并在函数执行前,用自己国家的母语打一个招呼。 .. code:: python + # 小明,中国人 @say_hello("china") - def chinese(): - print("我来自中国。") + def xiaoming(): + pass + # jack,美国人 @say_hello("america") - def american(): - print("I am from America.") + def jack(): + pass -万事俱备,只差帽子了。来定义一下,这里需要两层嵌套。 +那我们如果实现这个装饰器,让其可以实现 ``传参`` 呢? + +会比较复杂,需要两层嵌套。 .. code:: python @@ -160,28 +191,24 @@ return deco return wrapper -执行一下 +来执行一下 .. code:: python - american() + xiaoming() print("------------") - chinese() + jack() 看看输出结果。 .. code:: python 你好! - 我来自中国。 ------------ hello. - I am from America - -emmmm,这很NB。。。 -3.1.5 高阶用法:不带参数的类装饰器 ----------------------------------- +3.1.5 高阶:不带参数的类装饰器 +------------------------------ 以上都是基于函数实现的装饰器,在阅读别人代码时,还可以时常发现还有基于类实现的装饰器。 @@ -189,6 +216,8 @@ emmmm,这很NB。。。 ``__init__``\ 两个内置函数。 ``__init__`` :接收被装饰函数 ``__call__`` :实现装饰逻辑。 +还是以日志打印这个简单的例子为例 + .. code:: python class logger(object): @@ -213,8 +242,8 @@ emmmm,这很NB。。。 [INFO]: the function say() is running... say hello! -3.1.6 高阶用法:带参数的类装饰器 --------------------------------- +3.1.6 高阶:带参数的类装饰器 +---------------------------- 上面不带参数的例子,你发现没有,只能打印\ ``INFO``\ 级别的日志,正常情况下,我们还需要打印\ ``DEBUG`` ``WARNING``\ 等级别的日志。 @@ -263,8 +292,9 @@ emmmm,这很NB。。。 对于这个 callable 对象,我们最熟悉的就是函数了。 除函数之外,类也可以是 callable 对象,只要实现了\ ``__call__`` -函数(上面几个盒子已经接触过了),还有比较少人使用的偏函数也是 callable -对象。 +函数(上面几个例子已经接触过了)。 + +还有容易被人忽略的偏函数其实也是 callable 对象。 接下来就来说说,如何使用 类和偏函数结合实现一个与众不同的装饰器。 @@ -358,7 +388,7 @@ Python工匠:使用装饰器的小技巧) 其实例化的过程,你可以参考我这里的调试过程,加以理解。 -|image0| +|image1| 3.1.9 wraps 装饰器有啥用? -------------------------- @@ -475,16 +505,16 @@ wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码 self.age = age # 实例化 - XiaoMing = Student("小明") + xiaoming = Student("小明") # 添加属性 - XiaoMing.age=25 + xiaoming.age=25 # 查询属性 - XiaoMing.age + xiaoming.age # 删除属性 - del XiaoMing.age + del xiaoming.age 但是稍有经验的开发人员,一下就可以看出,这样直接把属性暴露出去,虽然写起来很简单,但是并不能对属性的值做合法性限制。为了实现这个功能,我们可以这样写。 @@ -509,16 +539,16 @@ wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码 self._age = None - XiaoMing = Student("小明") + xiaoming = Student("小明") # 添加属性 - XiaoMing.set_age(25) + xiaoming.set_age(25) # 查询属性 - XiaoMing.get_age() + xiaoming.get_age() # 删除属性 - XiaoMing.del_age() + xiaoming.del_age() 上面的代码设计虽然可以变量的定义,但是可以发现不管是获取还是赋值(通过函数)都和我们平时见到的不一样。 按照我们思维习惯应该是这样的。 @@ -526,10 +556,10 @@ wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码 :: # 赋值 - XiaoMing.age = 25 + xiaoming.age = 25 # 获取 - XiaoMing.age + xiaoming.age 那么这样的方式我们如何实现呢。请看下面的代码。 @@ -556,16 +586,16 @@ wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码 def age(self): del self._age - XiaoMing = Student("小明") + xiaoming = Student("小明") # 设置属性 - XiaoMing.age = 25 + xiaoming.age = 25 # 查询属性 - XiaoMing.age + xiaoming.age # 删除属性 - del XiaoMing.age + del xiaoming.age 用\ ``@property``\ 装饰过的函数,会将一个函数定义成一个属性,属性的值就是该函数return的内容。\ **同时**\ ,会将这个函数变成另外一个装饰器。就像后面我们使用的\ ``@age.setter``\ 和\ ``@age.deleter``\ 。 @@ -738,9 +768,15 @@ property -------------- +非常感谢你能阅读到这里,这篇文章我写了很久,算是比较干货的那种,文章有些长,但还是希望花点时间把这些知识点都搞明白,而不要只是收藏。 + +我的文章更新频率是远低于其他 Python +技术号,但我仍然坚持自己,坚持原创,每周虽然只有一篇,但我能保证我的每一篇文章都是诚意之作。希望那些对你有帮助的文章能够多多帮忙转发分享。这也是我更新的一大动力。非常感谢。 + .. figure:: http://image.python-online.cn/20190511161447.png :alt: 关注公众号,获取最新干货! -.. |image0| image:: http://image.python-online.cn/20190512113917.png +.. |image0| image:: http://image.python-online.cn/20190811100737.png +.. |image1| image:: http://image.python-online.cn/20190512113917.png diff --git a/source/c04/c04_09.md b/source/c04/c04_09.md index bec495b..d00bb5d 100644 --- a/source/c04/c04_09.md +++ b/source/c04/c04_09.md @@ -2,7 +2,9 @@ --- -本文是数据库学习笔记系列-第一篇。仅介绍了MySQL的基本概念和使用。 +数据库是每个后端程序员都必须扎实掌握的一项基本技能,而做为最主流的关系型数据库 MySQL,上手也极为容易,这里是我几年前自学 MySQL 时做下的笔记,现整理出来,一共两篇,这是第一篇。 + +在这篇文章里,我就介绍了一些 MySQL 的基本操作(增删改查)及表结构的解析。 ## 一、库操作 @@ -312,11 +314,11 @@ timestamp默认是自动更新当前时间的(在记录创建或更新时更 插入数据 ![](https://i.loli.net/2017/08/26/59a0cebd074ab.png) -`**作用之一`** +**作用之一** 规范数据格式: 数据只能是规定的数据中的其中一个 -`**作用之二`** +**作用之二** 节省存储空间:枚举实际存储的是数值而不是字符串本身. From 22d5a56ce7512ae61c858ed8c75947e2a7c7bf9f Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Wed, 28 Aug 2019 12:49:28 +0800 Subject: [PATCH 089/302] =?UTF-8?q?=E7=94=9F=E6=88=90rst=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_06.rst | 5 ++++ source/c04/c04_09.rst | 11 ++++++-- source/c07/c07_01.rst | 6 ++++ source/c08/c08_01.rst | 9 +++++- source/c08/c08_03.rst | 12 +++++--- source/c08/c08_05.rst | 66 ++++++++++++++++++++++++++++++++++++++++--- source/c08/c08_06.rst | 2 +- source/c08/c08_08.rst | 8 ++++++ source/c08/c08_14.rst | 6 ++++ source/c08/c08_15.rst | 19 +++++++++++++ 10 files changed, 131 insertions(+), 13 deletions(-) diff --git a/source/c04/c04_06.rst b/source/c04/c04_06.rst index 8dcce2f..b13493a 100755 --- a/source/c04/c04_06.rst +++ b/source/c04/c04_06.rst @@ -75,6 +75,9 @@ add/commit 前撤销对文件的修改 # 举例 $ git clone git@github.com:BingmingWong/test.git + # 更改远程仓库地址 + git remote set-url origin git@:you_repo.git + 1.5 GitHub合并至本地 ~~~~~~~~~~~~~~~~~~~~ @@ -398,6 +401,8 @@ Desktop真心觉得不好用)。 |image3| +如果想要用户名和密码登陆,可以将上面的 OpenSSH 改成 PuTTY/Plink就行了。 + -------------- .. figure:: http://image.python-online.cn/20190511161447.png diff --git a/source/c04/c04_09.rst b/source/c04/c04_09.rst index a7744e8..b0830b1 100755 --- a/source/c04/c04_09.rst +++ b/source/c04/c04_09.rst @@ -3,7 +3,12 @@ -------------- -本文是数据库学习笔记系列-第一篇。仅介绍了MySQL的基本概念和使用。 +数据库是每个后端程序员都必须扎实掌握的一项基本技能,而做为最主流的关系型数据库 +MySQL,上手也极为容易,这里是我几年前自学 MySQL +时做下的笔记,现整理出来,一共两篇,这是第一篇。 + +在这篇文章里,我就介绍了一些 MySQL +的基本操作(增删改查)及表结构的解析。 一、库操作 ---------- @@ -360,11 +365,11 @@ timestamp默认是自动更新当前时间的(在记录创建或更新时更 | 插入数据 | |image10| -``**作用之一``\ \*\* +**作用之一** 规范数据格式: 数据只能是规定的数据中的其中一个 -``**作用之二``\ \*\* +**作用之二** 节省存储空间:枚举实际存储的是数值而不是字符串本身. diff --git a/source/c07/c07_01.rst b/source/c07/c07_01.rst index a93b219..12ea6b1 100755 --- a/source/c07/c07_01.rst +++ b/source/c07/c07_01.rst @@ -1227,6 +1227,12 @@ Linux上,硬件的设备驱动(如硬盘)和特殊设备文件(如\ ``/d # 卸载 $ losetup -d /dev/loop0 +使用dd拷贝磁盘可以,但是如果想要快速生成大文件,速度就相当慢了,可以试试fallocate命令 + +.. code:: shell + + fallocate -l 100G test_file + 2.6 任务管理 ~~~~~~~~~~~~ diff --git a/source/c08/c08_01.rst b/source/c08/c08_01.rst index f725f9a..481da0a 100755 --- a/source/c08/c08_01.rst +++ b/source/c08/c08_01.rst @@ -137,7 +137,7 @@ aggregate管理 --allocation-pool start=172.20.20.100,end=172.20.20.199 \ --gateway 172.20.20.200 \ --enable_dhcp=False \ - --dns_nameserver 114.114.114.114 \ + --dns-nameserver 114.114.114.114 \ public 172.20.20.0/24 # 创建子网,更多选项可以查看 neutron subnet-create -h @@ -399,6 +399,13 @@ aggregate管理 # 指定节点执行命令 rabbitmq -n rabbit@ws_controller02 [command] +3.3 pacemaker +------------- + +:: + + pkill -9 pacemaker;service pacemaker restart + -------------- .. figure:: http://image.python-online.cn/20190511161447.png diff --git a/source/c08/c08_03.rst b/source/c08/c08_03.rst index 24a184c..049fb4a 100755 --- a/source/c08/c08_03.rst +++ b/source/c08/c08_03.rst @@ -485,16 +485,19 @@ CentOS6 创建快照前需要先删除\ ``75-persistent-net-generator.rules`` service firewalld stop sudo update-rc.d -f firewalld remove -设置cloud-init参数 - -.. code:: shell - 若有其他要修改的地方,可自行修改。然后关机虚拟机 :: shutdown -h now +8.3.3 修改镜像的文件 +-------------------- + +通过 guestfish 工具可以实现不用创建虚拟机就可以修改镜像里的文件内容。 + +|image2| + 附录:参考文档 -------------- @@ -517,4 +520,5 @@ CentOS6 创建快照前需要先删除\ ``75-persistent-net-generator.rules`` .. |image0| image:: https://i.loli.net/2018/01/27/5a6c34714685d.png .. |image1| image:: https://i.loli.net/2018/01/27/5a6c34b14c6ec.png +.. |image2| image:: http://image.python-online.cn/20190827200522.png diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index d9ece4d..982051e 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -534,15 +534,70 @@ nova api 的接口参数是在 nova/api/openstack/compute/schemas/ |image40| -装饰器 schemas 的定义如下: +装饰器里会传入一个参数,就是 schema +的内容,通过它可以约束一个请求内的参数合法性。 + +schemas 的文件都统一放在 ``nova\api\openstack\compute\schemas`` 目录下。 + +对于创建虚拟机来说,它的 schemas 文件是在上面目录下的 ``servers.py`` + +但并不是说,创建虚拟机的所有参数校验规则只在这里,nova 这边引入了 +stevedore,它是 oslo +项目中为OpenStack其他项目提供动态加载功能的公共组件库(详细可以看这篇文章:https://blog.csdn.net/bill_xiang_/article/details/78852717)。通过它可以用添加扩展的方式,给 +servers 更新 schemas。 |image41| +关键在于这个 map 函数,它会循环所有的扩展() + +|image42| + +将调用传进去的 ``self._create_extension_schema`` 将已加载到的扩展schemas +更新到主schemas里去。 + +:: + + # [ext.obj.name for ext in self.create_schema_manager.extensions] + ['MultipleCreate', 'BlockDeviceMapping', 'BlockDeviceMappingV1', 'AvailabilityZone', 'UserData', 'Keypairs', 'SchedulerHints', 'SecurityGroups', 'ConfigDrive'] + +|image43| + +装饰器 schemas 的定义如下: + +|image44| + 比如我们使用的 novaclient 发出的请求,是 v2.3.7的,所以 create() 顶部的五个schema 装饰器,上面四个都会空跑,不会进行校验,只有最后一个才会进入检验逻辑。 -|image42| +|image45| + +8.5.20 往数据库中加字段 +----------------------- + +先先定义好migrate 文件 + +.. code:: python + + from sqlalchemy import MetaData, Text, Table, Column + + + def upgrade(migrate_engine): + meta = MetaData(bind=migrate_engine) + instances = Table('instances', meta, autoload=True) + + if not hasattr(instances.c, 'host_routes'): + instances.create_column(Column('host_routes', Text(), default=[])) + +然后在 nova/objects/instances.py 中的fields加入相应的字段。 + +然后在 nova/db/sqlalchemy/models.py 也要加相应的 Column + +最后再执行这个命令,同步数据库 + +:: + + nova-manage db sync -------------- @@ -591,6 +646,9 @@ nova api 的接口参数是在 nova/api/openstack/compute/schemas/ .. |image38| image:: http://image.python-online.cn/20190708103119.png .. |image39| image:: http://image.python-online.cn/20190719170825.png .. |image40| image:: http://image.python-online.cn/20190730140551.png -.. |image41| image:: http://image.python-online.cn/20190730142527.png -.. |image42| image:: http://image.python-online.cn/20190730143003.png +.. |image41| image:: http://image.python-online.cn/20190826210813.png +.. |image42| image:: http://image.python-online.cn/20190826211542.png +.. |image43| image:: http://image.python-online.cn/20190826211737.png +.. |image44| image:: http://image.python-online.cn/20190730142527.png +.. |image45| image:: http://image.python-online.cn/20190730143003.png diff --git a/source/c08/c08_06.rst b/source/c08/c08_06.rst index 8912b08..792eb81 100644 --- a/source/c08/c08_06.rst +++ b/source/c08/c08_06.rst @@ -79,7 +79,7 @@ NetworkManager后,默认情况下它的启动顺序是会在 cloudinit-local 3. cloud-init modules -m config 4. cloud-init modules -m final -除了这几个命令之外,还有几个 +除了这几个命令之外,还有几个: - cloud-init query -n [name] - cloud-init single -n [name] –frequency diff --git a/source/c08/c08_08.rst b/source/c08/c08_08.rst index 60ff8dd..c95f770 100644 --- a/source/c08/c08_08.rst +++ b/source/c08/c08_08.rst @@ -175,6 +175,14 @@ NetworkManager, ubuntu 是network-manager)才负责获取ip。 10. 如果一个子网只有dhcp port,子网可以被删除,如果有其他port,则子网不能删除。 +11. 一个network一个ns,一个ns下面会有多个dhcp + server的ip,正常情况下每个subnet一个dhcp + server,若一个network下有多个开启了dhcp server + 的子网,只关闭其中一个子网的dhcp,并不会立马删除这个dhcp + port,因为这个port还有其他人占用(感觉这块neutron处理不好,应该更新一个port的fixed_ip,把关闭dhcp + 的子网的ip给删除掉),当一个network里没有开启dhcp的子网且没有虚拟机使用dhcp的情况下,neutron才会删除这个dhcp + port。 + 8.8.5 镜像问题排查 ------------------ diff --git a/source/c08/c08_14.rst b/source/c08/c08_14.rst index d6fb0fa..c2bac3a 100644 --- a/source/c08/c08_14.rst +++ b/source/c08/c08_14.rst @@ -245,6 +245,12 @@ Port 去创建虚拟机,二是,修改 nova-api 的接口,使之支持。 |image4| +重装 ``openvswith.ko`` 过程 + +.. code:: shell + + ovs-dpctl del-dp ovs-system && rmmod openvswitch && insmod ./openvswitch.ko && service openvswitch restart && service network restart + -------------- .. figure:: http://image.python-online.cn/20190511161447.png diff --git a/source/c08/c08_15.rst b/source/c08/c08_15.rst index e4aec06..a063773 100644 --- a/source/c08/c08_15.rst +++ b/source/c08/c08_15.rst @@ -37,6 +37,23 @@ subnet_id。 |image9| +-------------- + +当不指定子网、也不指定ip时(也就是不传fixed_ip),而且 cluster 里 即 +有ipv4 的子网也有ipv6的子网,这时候 neutron +会去创建一个既有ipv4也有ipv6的port,只要有一个version的子网里没有可用ip,都会失败。 + +|image10| + +这里是遍历一个version的所有的子网列表,只要能找到一个子网有可用ip,就返回,如果遍历完都没有找到ip,那就要抛出异常了。 + +|image11| + +.. code:: python + + class IpAddressGenerationFailureAllSubnets(IpAddressGenerationFailure): + message = _("No more IP addresses available.") + .. |image0| image:: http://image.python-online.cn/20190804111844.png .. |image1| image:: http://image.python-online.cn/20190804111715.png .. |image2| image:: http://image.python-online.cn/20190803181706.png @@ -47,4 +64,6 @@ subnet_id。 .. |image7| image:: http://image.python-online.cn/20190804094131.png .. |image8| image:: http://image.python-online.cn/20190804092214.png .. |image9| image:: http://image.python-online.cn/20190804091911.png +.. |image10| image:: http://image.python-online.cn/20190809213209.png +.. |image11| image:: http://image.python-online.cn/20190809213223.png From ce094443ae01a25db0efe90bfcbf99f7317199c1 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sat, 31 Aug 2019 18:10:34 +0800 Subject: [PATCH 090/302] =?UTF-8?q?=E6=96=B0=E5=A2=83=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_22.md | 191 +++++++++++++++++++++++++++++++++++++++++++ source/c04/c04_15.md | 16 ++++ source/c07/c07_01.md | 3 + source/c08/c08_05.md | 20 +++++ source/c08/c08_06.md | 38 ++++++++- source/c08/c08_14.md | 40 +++++++++ 6 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 source/c01/c01_22.md diff --git a/source/c01/c01_22.md b/source/c01/c01_22.md new file mode 100644 index 0000000..5e94dc9 --- /dev/null +++ b/source/c01/c01_22.md @@ -0,0 +1,191 @@ +# 1.22 如何修改 CentOS 6.x 上默认Python + +最近在工作中遇到一个问题,就是有一个功能希望在各种服务器上实现,而服务器上的系统版本可能都不一样,有的是 CentOS 6.x,有的是 CentOS 7.x 。 + +需要说明的一点是,CentOS 6.x 上的 Python 版本是 2.6.x 的,而 CentOS 7.x 上的 Python 版本是 2.7.x 的,这意味着我要实现的功能要适配这两种版本的系统。 + +你可能会说,这有什么的,自己写的时候,注意一下就好了。 + +事情其实没有那么容易,我要实现的功能是基于一个框架进行定制,需要修改不少的框架代码。这个框架在不同的 Linux 版本上,是有不同的版本的,而且差异巨大,曾经想过在 CentOS 6.x 和 CentOS 7.x 将这个框架安装成同一个版本,最后还是失败了,无法安装,原因就是高版本需要 Python2.7,而 CentOS 6.x 上只有 Python2.6。 + +这个历史问题一直遗留到现在,由于这次的功能影响到的代码较多,如果要对两个版本的框架分别进行定制的话,需要花不少的时间,为了不维护两套版本,避免浪费多余的精力去做适配,我决定将 CentOS 6.x 上默认的 Python2.6 升级成 Python2.7。 + +下面是整个升级过程,别看步骤简单,这些精简步骤的背后可是有不少的坑,被我踩过后,你可以直接使用了。 + + + + +1. 首先确认下你机器上的默认的 Python 版本 + +```shell +$ python -V +Python 2.6.6 + +$ whereis python +python: /usr/bin/python /usr/bin/python2.6 /usr/lib/python2.6 /usr/lib64/python2.6 /usr/local/bin/python /usr/include/python2.6 /usr/share/man/man1/python.1.gz +``` + + + +2. 由于我们将使用编译安装的方式,所以要安装下 gcc,及一些工具包。 + +注意一定要全部安装,不然后面会发现有不少 python 的工具用不了。 + +比如不安装 zlib 会无法安装 setuptools,不装 openssl 和 openssl-devel,会无法使用 pip 工具等 + +``` +$ yum install gcc -y +$ yum groupinstall "Development tools" +$ yum install zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel -y +``` + + 如果你这里未按照我的步骤来安装,你后面使用的时候出现了各种各样的问题,不要慌,只要再回来这里,把没安装的包装上,安装完成后,你需要进入第四步重新编译安装Python。 + + + +3. 下载最新的 Python2.7.x 安装包,解压并进入指定目录 + +``` +$ wget https://www.python.org/ftp/python/2.7.14/Python-2.7.14.tgz +$ tar zxvf Python-2.7.14.tgz +$ cd Python-2.7.14 +``` + + + +4. 配置,编译,安装 + +```shell +# --prefix 指定 python 安装的路径 +$ ./configure --prefix=/usr/local/python/python2.7 +$ make +$ make install +``` + +`./configure` 命令执行完毕之后创建一个文件creating Makefile,供下面的make命令使用 执行 `make install` 之后就会把程序安装到我们指定的目录中去。 + +Configure是一个可执行脚本,它有很多选项,在待安装的源码路径下使用命令./configure –help输出详细的选项列表。其中 `--prefix` 选项是配置安装的路径,如果不配置该选项,安装后可执行文件默认放在/usr /local/bin,库文件默认放在 `/usr/local/lib` ,配置文件默认放在 `/usr/local/etc` ,其它的资源文件放在 `/usr /local/share`。如果配置 `--prefix`,如:`./configure --prefix=/usr/local/test` 可以把所有资源文件放在/usr/local/test的路径中,不会杂乱。 + +用了 `--prefix` 选项的另一个好处是卸载软件或移植软件。当某个安装的软件不再需要时,只须简单的删除该安装目录,就可以把软件卸载得干干净净;移植软件只需拷贝整个目录到另外一个机器即可(相同的操作系统)。当然要卸载程序,也可以在原来的make目录下用一次 `make uninstall`,但前提是make文件指定过uninstall。 + + + +5. 查看系统的 Python 版本 + +```shell +$ python -V +Python 2.6.6 +``` + + 如果你查看还是 Python 2.6.6 版本,请继续看第六步。 + + + +6. 修改系统默认的 Python 版本 + +查看新安装的Python版本,当前系统的Python版本,并将系统指向的Python从2.6.x修改为2.7.x,再次查看当前系统的Python版本,已经变更为2.7.x + +```shell +# 这是我们刚安装的 Python +$/usr/local/bin/python2.7 -V +Python 2.7.14 + +# 这是系统默认 Python +$ /usr/bin/python -V +Python 2.6.6 + +# 备份原来的 Python 文件 +$ mv /usr/bin/python /usr/bin/python.bak + +# 建立软链接,将我们刚安装的 python2.7 做为系统默认版本 +ln -s /usr/local/bin/python2.7 /usr/bin/python + +# 再次查看 Python 版本,已经成功切换过来 +$ python -V +Python 2.7.14 +``` + +7. 重新指定 yum 的Python版本 + +上面我们改了系统的默认 Python 版本,由于CentOS 6.x 的 yum 是基于Python2.6 的,为了不影响 yum 的使用,需单独将yum指向python2.6版本。 + +编辑: vim /usr/bin/yum ,将` /usr/bin/python` 改成 `/usr/bin/python2.6` + +```python +#!/usr/bin/python2.6 +``` + + + +8. 安装 setuptools 及 pip + +pip是python的安装工具,很多python的常用工具,都可以通过pip进行安装。要安装pip,首先要安装setuptools。从这个链接,你可以得到相关信息:https://pypi.python.org/pypi/setuptools + +```shell +# 下载 setuptools +$ wget https://pypi.python.org/packages/ff/d4/209f4939c49e31f5524fa0027bf1c8ec3107abaf7c61fdaad704a648c281/setuptools-21.0.0.tar.gz#md5=81964fdb89534118707742e6d1a1ddb4 +``` + +同样的,进行安装: + +```shell +$ tar vxf setuptools-21.0.0.tar.gz +$ cd setuptools-21.0.0 +$ python setup.py install +``` + +安装完成后,下载pip。其信息在如下网站:https://pypi.python.org/pypi/pip + +```shell +# 下载 pip +wget https://pypi.python.org/packages/41/27/9a8d24e1b55bd8c85e4d022da2922cb206f183e2d18fee4e320c9547e751/pip-8.1.1.tar.gz#md5=6b86f11841e89c8241d689956ba99ed7 +``` + +同样的,进行安装 + +```shell +$ tar vxf pip-8.1.1.tar.gz +$ cd pip-8.1.1 +$ python setup.py install +``` + +安装完成后,执行 `pip list` 查看一下安装的包,若无异常,则一切顺利。或者你也可以试着安装一下第三方包 `pip install requests` 。 + + + +8. 转移cloudinit + +上面说的项目,其实就是 cloudinit。接下来就要将 centos 7.2 上的cloudinit 的目录整体拷贝到 centos 6.5 的/usr/local/lib/python2.7/site-packages/ 目录下 + +![](http://image.python-online.cn/20190831160317.png) + +然后安装一些 cloudinit 的依赖包。 + +```shell +$ pip install six requests prettytable jsonpatch configobj + +# 默认还是安装在 python2.6 下 +$ yum install PyYAML -y + +# 将这些文件拷贝到 python2.7 目录下 +# 如果你不知道 python2.7 的目录,使用 import sys;print sys.path 就可以打印 +$ cd /usr/lib64/python2.6/site-packages +$ cp -r yaml/ /usr/local/lib/python2.7/site-packages/ +$ cp -p _yaml.so /usr/local/lib/python2.7/site-packages/ +$ cp -p PyYAML-3.10-py2.6.egg-info /usr/local/lib/python2.7/site-packages/ +``` + +执行一下 cloudinit 的几个命令,没有问题,任务就完成了。 + +```shell +$ cloud-init init -l +$ cloud-init init +``` + + + +**参考文章** + +- https://www.cnblogs.com/stonehe/p/7944366.html + +![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index 6ee7ec6..6a91021 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -771,6 +771,22 @@ PyCharm 打开一个文件,就占用一个标签面。 而这些配置步骤还挺多的,我专门写了一篇文章介绍设置过程,你可以点此查看:[图文教程|不能不会的远程调试技巧](https://mp.weixin.qq.com/s/ECWCJMQ6oEDaY1x1JfGfkg) + + +## 4.15.31 专属剪切板 + + + +ctrl + shift + c + + + +## 4.15.32 json 格式化 + + + + + ## 附录 - [PyCharm 快捷键 Mac 版 ](https://resources.jetbrains.com/storage/products/pycharm/docs/PyCharm_ReferenceCard_mac.pdf) diff --git a/source/c07/c07_01.md b/source/c07/c07_01.md index 1ae5b36..1be73a9 100644 --- a/source/c07/c07_01.md +++ b/source/c07/c07_01.md @@ -1637,7 +1637,10 @@ securityfs on /sys/kernel/security type secur ## 六、业务相关 + + 查看虚机的CPU是否支持虚拟化 + ``` egrep '(vmx|svm)' /proc/cpuinfo # 如果有标红的vmx(Intel)、svm(AMD)说明就支持,virtualbox不支持(坑) diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 21b6010..5404d92 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -520,6 +520,26 @@ schemas 的文件都统一放在 `nova\api\openstack\compute\schemas` 目录下 +是不是很呐闷,为什么创建server 的接口,会知道关联上面那9个 schemas,其实那各自代表一种资源,属于扩展资源(它们的中心是核心资源)。 + +那这些扩展资源是如何关联到核心资源的,这是在哪定义的呢? + +其实就是 nova 的 setup.cfg 里定义好的。 + +就像下面指明了 server 的 create 接口会去加载这9个扩展资源。 + +![](http://image.python-online.cn/20190830091540.png) + +搜索一下 server_create 方法 + +还真的只有这 9 个资源里才会定义。 + +![](http://image.python-online.cn/20190830092203.png) + +这就神奇了,servers 这个核心资源是如何加载到这些资源的,看了下代码是使用 stevedore 这个模块去动态加载,然后还会校验这些资源是否都有 `server_create` ,只有有这个函数,才会被正常加载进来。 + +![](http://image.python-online.cn/20190830093613.png) + ## 8.5.20 往数据库中加字段 先先定义好migrate 文件 diff --git a/source/c08/c08_06.md b/source/c08/c08_06.md index 5129988..dfeb939 100644 --- a/source/c08/c08_06.md +++ b/source/c08/c08_06.md @@ -80,6 +80,12 @@ cloud-init 是 linux 的一个工具,当系统启动时,cloud-init 可从 no ![](http://image.python-online.cn/20190430213729.png) +如果你在调试 single,而且你把 /var/lib/cloud 目录删除过,请务必要执行一下 cloud-init init ,不然会取不到 user_data + +centos 6.5 的cloudinit 使用的是 python2.6,没有 format 语法,请注意不要使用。这是和 centos7的一点区别。 + + + ## 8.6.3 数据源是如何读取的? 数据源的读取入口是在 入口文件(CentOS6.x 是在 `/usr/bin/cloud-init`,CentOS7.x 是在 `/usr/lib/python2.7/site-packages/cloudinit/cmd/main.py`)的 main_init 函数中,会调用 `stages.py:fetch()` 函数。 @@ -120,9 +126,21 @@ find_source 是通过一个一个去执行 对应source模块的 get_data(), ![](http://image.python-online.cn/20190429104357.png) -而在 CentOS7.x (cloud-init 0.7.9)中 或者 Ubuntu 16.04(cloud-init 17.1)中,并没有限定需要在 local 阶段时才可进入。所以如果你使用调试工具直接执行 init 阶段,也是可以配置网络的。 +而在 CentOS7.x (cloud-init 0.7.9)中 或者 Ubuntu 16.04(cloud-init 17.1)中,配置网络不是在 on_first_boot 函数里,而是在 stages.py:apply_network_config() 里。 + +![](http://image.python-online.cn/20190829141059.png) + +并且新版的cloudinit ,local 和 init 阶段都会去配置网络,区别在于,local阶段只写配置文件,而init阶段不仅写配置文件,还会启动网卡,将ip配置上。 -![](http://image.python-online.cn/20190429104307.png) +centos 7.x ,因为会跳过,它是在 stages.py:apply_network_config 函数里,这是在 local 阶段写入配置文件,而在 init 阶段 启动网卡。 + +![](http://image.python-online.cn/20190829113537.png) + +所以如果要调试的话可以直接执行 init 阶段。 + +而如果是虚拟机重启的话,是在stages.py 这边判断是否为新虚拟机的,这也和旧版本有所区别。 + +![](http://image.python-online.cn/20190829141059.png) 为了让你能更加清晰的了解这个网络配置过程,我阅读了这块的源代码。 @@ -350,6 +368,22 @@ myjob.cfg:text/upstart-job ![](http://image.python-online.cn/20190708175813.png) + + + + +查看机器里有哪几张网卡 + +ls -l /sys/class/net + + + +只查看 ipv4 或 ipv6的网卡ip + +ip -6 addr show + +ip -4 addr show + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_14.md b/source/c08/c08_14.md index 5c728d7..e5d03aa 100644 --- a/source/c08/c08_14.md +++ b/source/c08/c08_14.md @@ -253,9 +253,49 @@ neutron port-create --fixed-ip \ ovs-dpctl del-dp ovs-system && rmmod openvswitch && insmod ./openvswitch.ko && service openvswitch restart && service network restart ``` +## 8.14.4 cloudinit支持 +CentOS 6.x 在使用 ipv6 方面有问题 +![](http://image.python-online.cn/20190829103805.png) +导致在网卡配置文件里的配置的ip 都为none + + + + + +![](http://image.python-online.cn/20190829104544.png) + +上面的 callbak 是 这个函数 + +![](http://image.python-online.cn/20190829104806.png) + + + +centos6.x 的网络信息不是从 latest 里的 network_data.json 里读取的,而是从 content/0000 里读取 + +![](http://image.python-online.cn/20190829110541.png) + +这是因为 centos 6.x 配置网络是在 local 阶段做的,而local阶段做的话,就会从 content/0000 + +![](http://image.python-online.cn/20190829105558.png) + +而 centos7.x 其实也会走 on_first_boot 去配置网络,但是cloudinit 在centos7.x 里 sources.DSMODE_PASS + +![](http://image.python-online.cn/20190829112446.png) + +导致在 local 阶段,这里配置网络被pass掉,自然也就不会从 content/0000 读取了。 + +![](http://image.python-online.cn/20190829111917.png) + + + +centos 6.x 配置网络是在 on_first_boot 函数里,这是 local 阶段就会执行的。 + + + +![](http://image.python-online.cn/20190829161243.png) --- From 697ce448983779bf20f7de222d6d398f101f127b Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 1 Sep 2019 12:01:37 +0800 Subject: [PATCH 091/302] update rst files --- source/c01/c01_22.rst | 205 ++++++++++++++++++++++++++++++++++++++++++ source/c04/c04_15.rst | 8 ++ source/c08/c08_05.rst | 26 ++++++ source/c08/c08_06.rst | 72 ++++++++++----- source/c08/c08_14.rst | 48 ++++++++++ 5 files changed, 339 insertions(+), 20 deletions(-) create mode 100644 source/c01/c01_22.rst diff --git a/source/c01/c01_22.rst b/source/c01/c01_22.rst new file mode 100644 index 0000000..09a37f7 --- /dev/null +++ b/source/c01/c01_22.rst @@ -0,0 +1,205 @@ +1.22 如何修改 CentOS 6.x 上默认Python +===================================== + +最近在工作中遇到一个问题,就是有一个功能希望在各种服务器上实现,而服务器上的系统版本可能都不一样,有的是 +CentOS 6.x,有的是 CentOS 7.x 。 + +需要说明的一点是,CentOS 6.x 上的 Python 版本是 2.6.x 的,而 CentOS 7.x +上的 Python 版本是 2.7.x +的,这意味着我要实现的功能要适配这两种版本的系统。 + +你可能会说,这有什么的,自己写的时候,注意一下就好了。 + +事情其实没有那么容易,我要实现的功能是基于一个框架进行定制,需要修改不少的框架代码。这个框架在不同的 +Linux 版本上,是有不同的版本的,而且差异巨大,曾经想过在 CentOS 6.x 和 +CentOS 7.x +将这个框架安装成同一个版本,最后还是失败了,无法安装,原因就是高版本需要 +Python2.7,而 CentOS 6.x 上只有 Python2.6。 + +这个历史问题一直遗留到现在,由于这次的功能影响到的代码较多,如果要对两个版本的框架分别进行定制的话,需要花不少的时间,为了不维护两套版本,避免浪费多余的精力去做适配,我决定将 +CentOS 6.x 上默认的 Python2.6 升级成 Python2.7。 + +下面是整个升级过程,别看步骤简单,这些精简步骤的背后可是有不少的坑,被我踩过后,你可以直接使用了。 + +1. 首先确认下你机器上的默认的 Python 版本 + +.. code:: shell + + $ python -V + Python 2.6.6 + + $ whereis python + python: /usr/bin/python /usr/bin/python2.6 /usr/lib/python2.6 /usr/lib64/python2.6 /usr/local/bin/python /usr/include/python2.6 /usr/share/man/man1/python.1.gz + +2. 由于我们将使用编译安装的方式,所以要安装下 gcc,及一些工具包。 + +注意一定要全部安装,不然后面会发现有不少 python 的工具用不了。 + +比如不安装 zlib 会无法安装 setuptools,不装 openssl 和 +openssl-devel,会无法使用 pip 工具等 + +:: + + $ yum install gcc -y + $ yum groupinstall "Development tools" + $ yum install zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel -y + +如果你这里未按照我的步骤来安装,你后面使用的时候出现了各种各样的问题,不要慌,只要再回来这里,把没安装的包装上,安装完成后,你需要进入第四步重新编译安装Python。 + +3. 下载最新的 Python2.7.x 安装包,解压并进入指定目录 + +:: + + $ wget https://www.python.org/ftp/python/2.7.14/Python-2.7.14.tgz + $ tar zxvf Python-2.7.14.tgz + $ cd Python-2.7.14 + +4. 配置,编译,安装 + +.. code:: shell + + # --prefix 指定 python 安装的路径 + $ ./configure --prefix=/usr/local/python/python2.7 + $ make + $ make install + +``./configure`` 命令执行完毕之后创建一个文件creating +Makefile,供下面的make命令使用 执行 ``make install`` +之后就会把程序安装到我们指定的目录中去。 + +Configure是一个可执行脚本,它有很多选项,在待安装的源码路径下使用命令./configure +–help输出详细的选项列表。其中 ``--prefix`` +选项是配置安装的路径,如果不配置该选项,安装后可执行文件默认放在/usr +/local/bin,库文件默认放在 ``/usr/local/lib`` ,配置文件默认放在 +``/usr/local/etc`` ,其它的资源文件放在 +``/usr /local/share``\ 。如果配置 +``--prefix``\ ,如:\ ``./configure --prefix=/usr/local/test`` +可以把所有资源文件放在/usr/local/test的路径中,不会杂乱。 + +用了 ``--prefix`` +选项的另一个好处是卸载软件或移植软件。当某个安装的软件不再需要时,只须简单的删除该安装目录,就可以把软件卸载得干干净净;移植软件只需拷贝整个目录到另外一个机器即可(相同的操作系统)。当然要卸载程序,也可以在原来的make目录下用一次 +``make uninstall``\ ,但前提是make文件指定过uninstall。 + +5. 查看系统的 Python 版本 + +.. code:: shell + + $ python -V + Python 2.6.6 + +如果你查看还是 Python 2.6.6 版本,请继续看第六步。 + +6. 修改系统默认的 Python 版本 + +查看新安装的Python版本,当前系统的Python版本,并将系统指向的Python从2.6.x修改为2.7.x,再次查看当前系统的Python版本,已经变更为2.7.x + +.. code:: shell + + # 这是我们刚安装的 Python + $/usr/local/bin/python2.7 -V + Python 2.7.14 + + # 这是系统默认 Python + $ /usr/bin/python -V + Python 2.6.6 + + # 备份原来的 Python 文件 + $ mv /usr/bin/python /usr/bin/python.bak + + # 建立软链接,将我们刚安装的 python2.7 做为系统默认版本 + ln -s /usr/local/bin/python2.7 /usr/bin/python + + # 再次查看 Python 版本,已经成功切换过来 + $ python -V + Python 2.7.14 + +7. 重新指定 yum 的Python版本 + +上面我们改了系统的默认 Python 版本,由于CentOS 6.x 的 yum +是基于Python2.6 的,为了不影响 yum +的使用,需单独将yum指向python2.6版本。 + +编辑: vim /usr/bin/yum ,将\ ``/usr/bin/python`` 改成 +``/usr/bin/python2.6`` + +.. code:: python + + #!/usr/bin/python2.6 + +8. 安装 setuptools 及 pip + +pip是python的安装工具,很多python的常用工具,都可以通过pip进行安装。要安装pip,首先要安装setuptools。从这个链接,你可以得到相关信息:https://pypi.python.org/pypi/setuptools + +.. code:: shell + + # 下载 setuptools + $ wget https://pypi.python.org/packages/ff/d4/209f4939c49e31f5524fa0027bf1c8ec3107abaf7c61fdaad704a648c281/setuptools-21.0.0.tar.gz#md5=81964fdb89534118707742e6d1a1ddb4 + +同样的,进行安装: + +.. code:: shell + + $ tar vxf setuptools-21.0.0.tar.gz + $ cd setuptools-21.0.0 + $ python setup.py install + +安装完成后,下载pip。其信息在如下网站:https://pypi.python.org/pypi/pip + +.. code:: shell + + # 下载 pip + wget https://pypi.python.org/packages/41/27/9a8d24e1b55bd8c85e4d022da2922cb206f183e2d18fee4e320c9547e751/pip-8.1.1.tar.gz#md5=6b86f11841e89c8241d689956ba99ed7 + +同样的,进行安装 + +.. code:: shell + + $ tar vxf pip-8.1.1.tar.gz + $ cd pip-8.1.1 + $ python setup.py install + +安装完成后,执行 ``pip list`` +查看一下安装的包,若无异常,则一切顺利。或者你也可以试着安装一下第三方包 +``pip install requests`` 。 + +8. 转移cloudinit + +上面说的项目,其实就是 cloudinit。接下来就要将 centos 7.2 上的cloudinit +的目录整体拷贝到 centos 6.5 的/usr/local/lib/python2.7/site-packages/ +目录下 + +|image0| + +然后安装一些 cloudinit 的依赖包。 + +.. code:: shell + + $ pip install six requests prettytable jsonpatch configobj + + # 默认还是安装在 python2.6 下 + $ yum install PyYAML -y + + # 将这些文件拷贝到 python2.7 目录下 + # 如果你不知道 python2.7 的目录,使用 import sys;print sys.path 就可以打印 + $ cd /usr/lib64/python2.6/site-packages + $ cp -r yaml/ /usr/local/lib/python2.7/site-packages/ + $ cp -p _yaml.so /usr/local/lib/python2.7/site-packages/ + $ cp -p PyYAML-3.10-py2.6.egg-info /usr/local/lib/python2.7/site-packages/ + +执行一下 cloudinit 的几个命令,没有问题,任务就完成了。 + +.. code:: shell + + $ cloud-init init -l + $ cloud-init init + +**参考文章** + +- https://www.cnblogs.com/stonehe/p/7944366.html + +.. figure:: http://image.python-online.cn/20190511161447.png + :alt: 关注公众号,获取最新干货! + + +.. |image0| image:: http://image.python-online.cn/20190831160317.png + diff --git a/source/c04/c04_15.rst b/source/c04/c04_15.rst index 94a6088..d2584d4 100644 --- a/source/c04/c04_15.rst +++ b/source/c04/c04_15.rst @@ -917,6 +917,14 @@ Debug ,而远程调试需要不少前置步骤。 而这些配置步骤还挺多的,我专门写了一篇文章介绍设置过程,你可以点此查看:\ `图文教程|不能不会的远程调试技巧 `__ +4.15.31 专属剪切板 +------------------ + +ctrl + shift + c + +4.15.32 json 格式化 +------------------- + 附录 ---- diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index 982051e..f7e2e71 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -572,6 +572,29 @@ servers 更新 schemas。 |image45| +是不是很呐闷,为什么创建server 的接口,会知道关联上面那9个 +schemas,其实那各自代表一种资源,属于扩展资源(它们的中心是核心资源)。 + +那这些扩展资源是如何关联到核心资源的,这是在哪定义的呢? + +其实就是 nova 的 setup.cfg 里定义好的。 + +就像下面指明了 server 的 create 接口会去加载这9个扩展资源。 + +|image46| + +搜索一下 server_create 方法 + +还真的只有这 9 个资源里才会定义。 + +|image47| + +这就神奇了,servers 这个核心资源是如何加载到这些资源的,看了下代码是使用 +stevedore 这个模块去动态加载,然后还会校验这些资源是否都有 +``server_create`` ,只有有这个函数,才会被正常加载进来。 + +|image48| + 8.5.20 往数据库中加字段 ----------------------- @@ -651,4 +674,7 @@ servers 更新 schemas。 .. |image43| image:: http://image.python-online.cn/20190826211737.png .. |image44| image:: http://image.python-online.cn/20190730142527.png .. |image45| image:: http://image.python-online.cn/20190730143003.png +.. |image46| image:: http://image.python-online.cn/20190830091540.png +.. |image47| image:: http://image.python-online.cn/20190830092203.png +.. |image48| image:: http://image.python-online.cn/20190830093613.png diff --git a/source/c08/c08_06.rst b/source/c08/c08_06.rst index 792eb81..09d9335 100644 --- a/source/c08/c08_06.rst +++ b/source/c08/c08_06.rst @@ -110,6 +110,12 @@ NetworkManager后,默认情况下它的启动顺序是会在 cloudinit-local |image7| +如果你在调试 single,而且你把 /var/lib/cloud +目录删除过,请务必要执行一下 cloud-init init ,不然会取不到 user_data + +centos 6.5 的cloudinit 使用的是 python2.6,没有 format +语法,请注意不要使用。这是和 centos7的一点区别。 + 8.6.3 数据源是如何读取的? -------------------------- @@ -161,12 +167,26 @@ on_first_boot。 |image14| 而在 CentOS7.x (cloud-init 0.7.9)中 或者 Ubuntu 16.04(cloud-init -17.1)中,并没有限定需要在 local -阶段时才可进入。所以如果你使用调试工具直接执行 init -阶段,也是可以配置网络的。 +17.1)中,配置网络不是在 on_first_boot 函数里,而是在 +stages.py:apply_network_config() 里。 |image15| +并且新版的cloudinit ,local 和 init +阶段都会去配置网络,区别在于,local阶段只写配置文件,而init阶段不仅写配置文件,还会启动网卡,将ip配置上。 + +centos 7.x ,因为会跳过,它是在 stages.py:apply_network_config +函数里,这是在 local 阶段写入配置文件,而在 init 阶段 启动网卡。 + +|image16| + +所以如果要调试的话可以直接执行 init 阶段。 + +而如果是虚拟机重启的话,是在stages.py +这边判断是否为新虚拟机的,这也和旧版本有所区别。 + +|image17| + 为了让你能更加清晰的了解这个网络配置过程,我阅读了这块的源代码。 在 cloud-init 的比较重要的几个文件有: @@ -182,12 +202,12 @@ on_first_boot。 如果是按照旧虚拟机创建新的快照镜像,然后使用这个镜像创建新的虚拟机,有可能会在同一块网卡上出现新旧两个ip,这是因为虚拟机在启动过程中,会先读取原网络配置配置ip,然后才会运行 cloud-init 进行新ip的配置,而新ip的配置是使用 ``ifup`` -这个命令\ |image16| +这个命令\ |image18| -使用这种方式并不会将第一次配置的旧ip给清除掉。\ |image17| +使用这种方式并不会将第一次配置的旧ip给清除掉。\ |image19| 这个问题,目前我只在CentOS6 中遇到过。可以通过修改代码让其先 ``ifdown`` -再 ``ifup`` 就可以解决这个问题。\ |image18| +再 ``ifup`` 就可以解决这个问题。\ |image20| **坑二** @@ -197,7 +217,7 @@ NetworkManager 具体的创建逻辑是在这 -|image19| +|image21| **坑三** @@ -218,12 +238,12 @@ NetworkManager 一个是会自动DHCP获取到一个以ip命名的hostname,并将原来的覆盖掉。 -|image20| +|image22| 为了避免出现这些情况,请务必保证这些包都安装完整(左为 CentOS 7.2,右为 CentOS 6.5)。 -|image21| +|image23| 8.6.5 userdata 使用说明 ----------------------- @@ -396,7 +416,7 @@ cirename0 的网卡。 这就是cloudinit搞的鬼,在cloudinit的local阶段,好像会记录之前的mac地址,如果发现不一致,就会触发rename_interface。 -|image22| +|image24| 8.6.7 虚拟机启动卡住 -------------------- @@ -409,7 +429,17 @@ ephemeral0 拿到对应的 /dev/vdb,并将其写入 /etc/fstab 中。在下次重启时,会根据 fstab 挂载磁盘,如果挂载不上,就会导致虚拟机启动卡住。 -|image23| +|image25| + +查看机器里有哪几张网卡 + +ls -l /sys/class/net + +只查看 ipv4 或 ipv6的网卡ip + +ip -6 addr show + +ip -4 addr show -------------- @@ -432,13 +462,15 @@ ephemeral0 拿到对应的 /dev/vdb,并将其写入 /etc/fstab .. |image12| image:: http://image.python-online.cn/20190430230839.png .. |image13| image:: http://image.python-online.cn/20190430231108.png .. |image14| image:: http://image.python-online.cn/20190429104357.png -.. |image15| image:: http://image.python-online.cn/20190429104307.png -.. |image16| image:: http://image.python-online.cn/Fp1TeHSiIMIQoZygbW9VSfAagB_d -.. |image17| image:: http://image.python-online.cn/Fh-5SQ8qYjhJEKovI6LmIpabSy2c -.. |image18| image:: http://image.python-online.cn/20190430231812.png -.. |image19| image:: http://image.python-online.cn/20190430232309.png -.. |image20| image:: http://image.python-online.cn/20190429205735.png -.. |image21| image:: http://image.python-online.cn/20190430232911.png -.. |image22| image:: http://image.python-online.cn/20190623091911.png -.. |image23| image:: http://image.python-online.cn/20190708175813.png +.. |image15| image:: http://image.python-online.cn/20190829141059.png +.. |image16| image:: http://image.python-online.cn/20190829113537.png +.. |image17| image:: http://image.python-online.cn/20190829141059.png +.. |image18| image:: http://image.python-online.cn/Fp1TeHSiIMIQoZygbW9VSfAagB_d +.. |image19| image:: http://image.python-online.cn/Fh-5SQ8qYjhJEKovI6LmIpabSy2c +.. |image20| image:: http://image.python-online.cn/20190430231812.png +.. |image21| image:: http://image.python-online.cn/20190430232309.png +.. |image22| image:: http://image.python-online.cn/20190429205735.png +.. |image23| image:: http://image.python-online.cn/20190430232911.png +.. |image24| image:: http://image.python-online.cn/20190623091911.png +.. |image25| image:: http://image.python-online.cn/20190708175813.png diff --git a/source/c08/c08_14.rst b/source/c08/c08_14.rst index c2bac3a..ab5fea7 100644 --- a/source/c08/c08_14.rst +++ b/source/c08/c08_14.rst @@ -251,6 +251,46 @@ Port 去创建虚拟机,二是,修改 nova-api 的接口,使之支持。 ovs-dpctl del-dp ovs-system && rmmod openvswitch && insmod ./openvswitch.ko && service openvswitch restart && service network restart +8.14.4 cloudinit支持 +-------------------- + +CentOS 6.x 在使用 ipv6 方面有问题 + +|image5| + +导致在网卡配置文件里的配置的ip 都为none + +|image6| + +上面的 callbak 是 这个函数 + +|image7| + +centos6.x 的网络信息不是从 latest 里的 network_data.json +里读取的,而是从 content/0000 里读取 + +|image8| + +这是因为 centos 6.x 配置网络是在 local +阶段做的,而local阶段做的话,就会从 content/0000 + +|image9| + +而 centos7.x 其实也会走 on_first_boot 去配置网络,但是cloudinit +在centos7.x 里 sources.DSMODE_PASS + +|image10| + +导致在 local 阶段,这里配置网络被pass掉,自然也就不会从 content/0000 +读取了。 + +|image11| + +centos 6.x 配置网络是在 on_first_boot 函数里,这是 local +阶段就会执行的。 + +|image12| + -------------- .. figure:: http://image.python-online.cn/20190511161447.png @@ -262,4 +302,12 @@ Port 去创建虚拟机,二是,修改 nova-api 的接口,使之支持。 .. |image2| image:: http://image.python-online.cn/20190716180952.png .. |image3| image:: http://image.python-online.cn/20190804110647.png .. |image4| image:: http://image.python-online.cn/20190716180726.png +.. |image5| image:: http://image.python-online.cn/20190829103805.png +.. |image6| image:: http://image.python-online.cn/20190829104544.png +.. |image7| image:: http://image.python-online.cn/20190829104806.png +.. |image8| image:: http://image.python-online.cn/20190829110541.png +.. |image9| image:: http://image.python-online.cn/20190829105558.png +.. |image10| image:: http://image.python-online.cn/20190829112446.png +.. |image11| image:: http://image.python-online.cn/20190829111917.png +.. |image12| image:: http://image.python-online.cn/20190829161243.png From adc336bd5181dbab0c068f380893d6621e2f010a Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Wed, 4 Sep 2019 20:54:08 +0800 Subject: [PATCH 092/302] update file --- source/c08/c08_01.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/c08/c08_01.md b/source/c08/c08_01.md index fdd7618..5d6a469 100644 --- a/source/c08/c08_01.md +++ b/source/c08/c08_01.md @@ -150,6 +150,9 @@ $ neutron port-list neutron port-create --tenant-id 100001 --fixed-ip ip_address=192.168.0.22 --mac-address fa:16:3e:3a:e8:1b nova interface-attach b0cc47bc-25c3-48ca-a4fd-5523326b515a --port-id 8bcba4eb-ade0-403d-8f13-45ed70936f03 + +# 关闭port安全组 +neutron port-update --no-security-groups --port-security-enabled=False ``` ### 1.3 Glance From d1284e3eaa7de1511ef660e90b29ada963846c87 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Fri, 6 Sep 2019 21:56:09 +0800 Subject: [PATCH 093/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0cloudinit=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_14.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/source/c08/c08_14.md b/source/c08/c08_14.md index e5d03aa..aa705b3 100644 --- a/source/c08/c08_14.md +++ b/source/c08/c08_14.md @@ -261,10 +261,6 @@ CentOS 6.x 在使用 ipv6 方面有问题 导致在网卡配置文件里的配置的ip 都为none - - - - ![](http://image.python-online.cn/20190829104544.png) 上面的 callbak 是 这个函数 @@ -293,9 +289,29 @@ centos6.x 的网络信息不是从 latest 里的 network_data.json 里读取的 centos 6.x 配置网络是在 on_first_boot 函数里,这是 local 阶段就会执行的。 +![](http://image.python-online.cn/20190829161243.png) + + + +ubuntu 解析网卡配置 + +```python +from cloudinit.net import eni +config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') +``` + +![](http://image.python-online.cn/20190906091102.png) + +## 8.14.5 其他命令记录 + + + +``` +# 网络启动不来,先 flush 试一下 +ip addr flush dev ens3 +``` -![](http://image.python-online.cn/20190829161243.png) --- From fb26e3f2104ac07a54ff6228456c83c6f78d5989 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Fri, 6 Sep 2019 21:56:35 +0800 Subject: [PATCH 094/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E3=80=8C=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E6=9C=BA=E6=98=AF=E5=A6=82=E4=BD=95=E5=88=86=E9=85=8D?= =?UTF-8?q?ip=E7=9A=84=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_05.md | 49 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 5404d92..f491897 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -162,8 +162,6 @@ grep -E "Start executing commands|End executing commands" /var/log/nova/nova-com 下次修改方法:一个是最开始获取`source_type`时判断为isolate,一个是后面 `isolate`的`extract_snapshot` 也要和lvm一样实现一下。 - - ## 8.5.6 宿主机资源采集上报 compute的资源上报,是在 `nova/compute/resource_tracker.py:_init_compute_node` 里。 @@ -177,6 +175,53 @@ compute的资源上报,是在 `nova/compute/resource_tracker.py:_init_compute_ +## 8.5.7 虚拟机的ip是如何分配的? + +这一节主要讲两个点: + +1. Port 是如何创建的? +2. network_info 是如何组装的? + + + +虚拟机配置网络可以cloudinit从 ConfigDrive 里取得网络信息,然后配置。而这里的网络信息是怎么来的呢,这就是本节要讲的内容,它就是 `network_info` 信息,它的最终去向是 ConfigDrive。 + +在创建虚拟机时,需要的资源有很多,而网络就是其中一种(`network_info`),其他的还有 `block_device_info` 。 + +当你看完本节后,你会发现,network_info 其实是创建完 port 后,根据port的信息组装成一个对象。 + +虚拟机创建资源的代码是在 `nova/compute/manager.py` 里的 `_build_resource()` 里 + +![](http://image.python-online.cn/20190906093751.png) + +这里只讲一下 `network_info`,其他的我都忽略了。 + +![](http://image.python-online.cn/20190906094703.png) + +在这个函数`_allocate_network()` ,主要做如下两件事。 + +![](http://image.python-online.cn/20190906214536.png) + +继续进入这个函数,如果不指定重试次数,默认是一次。 + +![](http://image.python-online.cn/20190906100119.png) + +接下来就是调用我们非常熟悉的 `_create_port_minimal` 函数去创建port + +![](http://image.python-online.cn/20190906213038.png) + +如果你全局搜索,你会发现,在 network/rpc.py 下也有这个函数,这个是通过 nova interface-attach 为虚拟机添加网卡,从 nova-api 那边发起的 rpc 请求,才会走到这里 + +![](http://image.python-online.cn/20190906210825.png) + +上面创建完port了,后面最后一个函数,我已经标出来了,开始组装获取 network_info 对象。 + +![](http://image.python-online.cn/20190906213823.png) + + + + + ## 8.5.8 手动引入上下文环境 有两种方式可以生成context From 8ad10e8703aed99d00767813e590fea4696a578c Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Fri, 6 Sep 2019 21:57:23 +0800 Subject: [PATCH 095/302] =?UTF-8?q?=E4=BF=AE=E6=AD=A3keepalived=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/C07_08.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/c07/C07_08.md b/source/c07/C07_08.md index 884acb3..6fd3a16 100644 --- a/source/c07/C07_08.md +++ b/source/c07/C07_08.md @@ -64,6 +64,10 @@ vrrp_instance VI_1 { #vrrp实例定义部分 virtual_ipaddress { #设置虚拟ip地址,可以设置多个,每行一个 219.157.114.206/24 dev eth1 label eth1:0 } + track_interface { + eth1 + } + track_script { chk_zabbix } From e717e613fbecd344de5533c7a26956c7bd7de5c8 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Fri, 6 Sep 2019 23:20:47 +0800 Subject: [PATCH 096/302] update rst --- source/c01/c01_19.md | 5 +- source/c01/c01_19.rst | 5 +- source/c07/c07_08.rst | 4 + source/c08/c08_01.rst | 3 + source/c08/c08_05.rst | 197 +++++++++++++++++++++++++++--------------- source/c08/c08_14.rst | 18 ++++ 6 files changed, 158 insertions(+), 74 deletions(-) diff --git a/source/c01/c01_19.md b/source/c01/c01_19.md index 1f084ab..a43af06 100644 --- a/source/c01/c01_19.md +++ b/source/c01/c01_19.md @@ -7,8 +7,8 @@ python manager.py runserver 127.0.0.1:8080 # 登陆 xadmin 后台 127.0.0.1:8080/xadmin -# 帐号密码 -MING:wbm54378 +# 测试帐号密码 +wbm@54378@# # 更新表结构 python manage.py makemigrations @@ -36,6 +36,7 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'static') 数据库密码 ```shell +# 测试帐号密码 root:Wangbm@123 wangbm:VfgiCtc42tpu ``` diff --git a/source/c01/c01_19.rst b/source/c01/c01_19.rst index fc0d271..68d30f8 100644 --- a/source/c01/c01_19.rst +++ b/source/c01/c01_19.rst @@ -9,8 +9,8 @@ # 登陆 xadmin 后台 127.0.0.1:8080/xadmin - # 帐号密码 - MING:wbm54378 + # 测试帐号密码 + wbm@54378@# # 更新表结构 python manage.py makemigrations @@ -36,6 +36,7 @@ .. code:: shell + # 测试帐号密码 root:Wangbm@123 wangbm:VfgiCtc42tpu diff --git a/source/c07/c07_08.rst b/source/c07/c07_08.rst index e177ced..7dc9bd9 100644 --- a/source/c07/c07_08.rst +++ b/source/c07/c07_08.rst @@ -68,6 +68,10 @@ server双机高可用 `__ virtual_ipaddress { #设置虚拟ip地址,可以设置多个,每行一个 219.157.114.206/24 dev eth1 label eth1:0 } + track_interface { + eth1 + } + track_script { chk_zabbix } diff --git a/source/c08/c08_01.rst b/source/c08/c08_01.rst index 481da0a..3d3ef5f 100755 --- a/source/c08/c08_01.rst +++ b/source/c08/c08_01.rst @@ -160,6 +160,9 @@ aggregate管理 nova interface-attach b0cc47bc-25c3-48ca-a4fd-5523326b515a --port-id 8bcba4eb-ade0-403d-8f13-45ed70936f03 + # 关闭port安全组 + neutron port-update --no-security-groups --port-security-enabled=False + 1.3 Glance ~~~~~~~~~~ diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index f7e2e71..b6850d0 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -200,6 +200,56 @@ compute的资源上报,是在 从数据库获取旧数据 ``self.compute_node = self._get_compute_node(context)`` +8.5.7 虚拟机的ip是如何分配的? +------------------------------ + +这一节主要讲两个点: + +1. Port 是如何创建的? +2. network_info 是如何组装的? + +虚拟机配置网络可以cloudinit从 ConfigDrive +里取得网络信息,然后配置。而这里的网络信息是怎么来的呢,这就是本节要讲的内容,它就是 +``network_info`` 信息,它的最终去向是 ConfigDrive。 + +在创建虚拟机时,需要的资源有很多,而网络就是其中一种(\ ``network_info``\ ),其他的还有 +``block_device_info`` 。 + +当你看完本节后,你会发现,network_info 其实是创建完 port +后,根据port的信息组装成一个对象。 + +虚拟机创建资源的代码是在 ``nova/compute/manager.py`` 里的 +``_build_resource()`` 里 + +|image14| + +这里只讲一下 ``network_info``\ ,其他的我都忽略了。 + +|image15| + +在这个函数\ ``_allocate_network()`` ,主要做如下两件事。 + +|image16| + +继续进入这个函数,如果不指定重试次数,默认是一次。 + +|image17| + +接下来就是调用我们非常熟悉的 ``_create_port_minimal`` 函数去创建port + +|image18| + +如果你全局搜索,你会发现,在 network/rpc.py 下也有这个函数,这个是通过 +nova interface-attach 为虚拟机添加网卡,从 nova-api 那边发起的 rpc +请求,才会走到这里 + +|image19| + +上面创建完port了,后面最后一个函数,我已经标出来了,开始组装获取 +network_info 对象。 + +|image20| + 8.5.8 手动引入上下文环境 ------------------------ @@ -207,11 +257,11 @@ compute的资源上报,是在 1. 如果有请求req(在nova-api里),可以使用这种 -|image14| +|image21| 2. 其他地方可以使用这种 -|image15| +|image22| 8.5.9 指定ip时检查allocation_pools ---------------------------------- @@ -223,16 +273,16 @@ compute的资源上报,是在 先来看看,port 是如何创建的 -|image16| +|image23| 若要解决这个问题,可以参考原生代码中,在为子网添加allocation_pool时,验证是否合法的的逻辑,代码如下 -|image17| +|image24| 然后在 ``neutron\neutron\db\ipam_pluggable_backend.py`` 文件中添加我们检查 ip是否在 allocation_pools 中的逻辑代码。 -|image18| +|image25| .. code:: python @@ -255,17 +305,17 @@ compute的资源上报,是在 然后还要定义一个异常类型 -|image19| +|image26| 若指定的ip在allocation pool 里,则正常创建,若不在allocation 里,就会在 nova-compute 日志中报错。 -|image20| +|image27| 可以发现我们的ip 172.20.22.64 并不在子网的allocation pool,理所当然在nova的日志中可以看到相应的报错。 -|image21| +|image28| 8.5.10 attach port时ip占用提示 ------------------------------ @@ -285,7 +335,7 @@ IpAddressAlreadyAllocated,而在 neutronclient 只有 IpAddressInUseClient 的异常,并不匹配,在neutronclient 端与neutron 对应的异常应该为 IpAddressAlreadyAllocatedClient 。 -|image22| +|image29| 如何让nova-api能够返回具体的错误信息呢? @@ -296,26 +346,26 @@ IpAddressAlreadyAllocatedClient 异常。 并且在nova 创建port的代码处,捕获这个异常 -|image23| +|image30| 这种要改两个组件,而且要将neutronclient 的代码也管理起来,较为麻烦 一种是,只改neutron,在neutron/ipam/exceptions.py 添加一个与 neutronclient 相对应的异常。 -|image24| +|image31| 然后修改 neutron/ipam/drivers/neutrondb_ipam/drivers.py 修改异常类型 -|image25| +|image32| 通过 postman 进行模拟,已经可以返回具体的信息 -|image26| +|image33| 另附:neutron 是如何判断ip是否已经占用?代码如下 -|image27| +|image34| 8.5.11 nova-compute 如何启动的? -------------------------------- @@ -323,11 +373,11 @@ neutronclient 相对应的异常。 从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 ``nova.cmd.compute:main()`` -|image28| +|image35| 从这个入口进去,会开启一个 ``nova-compute`` 的服务。 -|image29| +|image36| 当调用 service.Service.create 时(create 是一个工厂函数),实际是返回实例化的 service.Service 对象。当没有传入 @@ -335,20 +385,20 @@ manager 时,就会以binary 里的为准。比如binary 是\ ``nova-compute``\ ,那manager_cls 就是 ``compute_manager``\ ,对应的manager 导入路径,会从配置里读取。 -|image30| +|image37| 8.5.13 支持指定子网和指定ip --------------------------- 在 nova-api 接收请求处。 -|image31| +|image38| -|image32| +|image39| 对 network_info 进行解析,然后塞给 request 对象返回。 -|image33| +|image40| 8.5.14 HTTP 状态码 ------------------ @@ -468,7 +518,7 @@ isolate 一个目录下。这个目录下有一个名为 disk 的qcow2文件,而这个qcow2 文件的backing file 是指向一个 base 镜像文件(raw格式)。 -|image34| +|image41| 8.5.16 独立磁盘与LVM -------------------- @@ -480,7 +530,7 @@ isolate 缺点:无法像 LVM 存储池那样,做到精准而灵活的资源分配,有可能造成资源浪费或资源不足。 -|image35| +|image42| **LVM** @@ -499,14 +549,14 @@ isolate 只要在主机组上设置的metadata 的 key-value 和 extra_spec 的key-value一样就可以实现宿主机的过滤。 -|image36| +|image43| 8.5.18 生成config drive ----------------------- -|image37| +|image44| -|image38| +|image45| :: @@ -526,13 +576,13 @@ isolate nova api 的接口参数是在 nova/api/openstack/compute/schemas/ 目录下,如创建虚拟机的接口如下: -|image39| +|image46| 这些参数字段后面的类型是用来做参数类型的校验的。 在application 函数的头部,可以发现有如下这几种装饰器, -|image40| +|image47| 装饰器里会传入一个参数,就是 schema 的内容,通过它可以约束一个请求内的参数合法性。 @@ -546,11 +596,11 @@ stevedore,它是 oslo 项目中为OpenStack其他项目提供动态加载功能的公共组件库(详细可以看这篇文章:https://blog.csdn.net/bill_xiang_/article/details/78852717)。通过它可以用添加扩展的方式,给 servers 更新 schemas。 -|image41| +|image48| 关键在于这个 map 函数,它会循环所有的扩展() -|image42| +|image49| 将调用传进去的 ``self._create_extension_schema`` 将已加载到的扩展schemas 更新到主schemas里去。 @@ -560,17 +610,17 @@ servers 更新 schemas。 # [ext.obj.name for ext in self.create_schema_manager.extensions] ['MultipleCreate', 'BlockDeviceMapping', 'BlockDeviceMappingV1', 'AvailabilityZone', 'UserData', 'Keypairs', 'SchedulerHints', 'SecurityGroups', 'ConfigDrive'] -|image43| +|image50| 装饰器 schemas 的定义如下: -|image44| +|image51| 比如我们使用的 novaclient 发出的请求,是 v2.3.7的,所以 create() 顶部的五个schema 装饰器,上面四个都会空跑,不会进行校验,只有最后一个才会进入检验逻辑。 -|image45| +|image52| 是不是很呐闷,为什么创建server 的接口,会知道关联上面那9个 schemas,其实那各自代表一种资源,属于扩展资源(它们的中心是核心资源)。 @@ -581,19 +631,19 @@ schemas,其实那各自代表一种资源,属于扩展资源(它们的中 就像下面指明了 server 的 create 接口会去加载这9个扩展资源。 -|image46| +|image53| 搜索一下 server_create 方法 还真的只有这 9 个资源里才会定义。 -|image47| +|image54| 这就神奇了,servers 这个核心资源是如何加载到这些资源的,看了下代码是使用 stevedore 这个模块去动态加载,然后还会校验这些资源是否都有 ``server_create`` ,只有有这个函数,才会被正常加载进来。 -|image48| +|image55| 8.5.20 往数据库中加字段 ----------------------- @@ -642,39 +692,46 @@ stevedore 这个模块去动态加载,然后还会校验这些资源是否都 .. |image11| image:: http://image.python-online.cn/FnJA8RNIvJN2lAEXbKtJDpOLg1vg .. |image12| image:: http://image.python-online.cn/FnGyI8jCQFLCGi0pGVmI3SV6pDrv .. |image13| image:: http://image.python-online.cn/FrbE6oEZ3vtTWwDfMNQ16MGi6SWr -.. |image14| image:: http://image.python-online.cn/20190426153322.png -.. |image15| image:: http://image.python-online.cn/20190426152148.png -.. |image16| image:: http://image.python-online.cn/20190526141815.png -.. |image17| image:: http://image.python-online.cn/20190526142453.png -.. |image18| image:: http://image.python-online.cn/20190526134519.png -.. |image19| image:: http://image.python-online.cn/20190526141226.png -.. |image20| image:: http://image.python-online.cn/20190526134543.png -.. |image21| image:: http://image.python-online.cn/20190526134618.png -.. |image22| image:: http://image.python-online.cn/20190526140213.png -.. |image23| image:: http://image.python-online.cn/20190526140301.png -.. |image24| image:: http://image.python-online.cn/20190526140315.png -.. |image25| image:: http://image.python-online.cn/20190526140336.png -.. |image26| image:: http://image.python-online.cn/20190526140410.png -.. |image27| image:: http://image.python-online.cn/20190526143235.png -.. |image28| image:: http://image.python-online.cn/20190526205152.png -.. |image29| image:: http://image.python-online.cn/20190526165007.png -.. |image30| image:: http://image.python-online.cn/20190526204328.png -.. |image31| image:: http://image.python-online.cn/20190529203441.png -.. |image32| image:: http://image.python-online.cn/20190529215953.png -.. |image33| image:: http://image.python-online.cn/20190529215825.png -.. |image34| image:: http://image.python-online.cn/20190627213044.png -.. |image35| image:: http://image.python-online.cn/20190627213609.png -.. |image36| image:: http://image.python-online.cn/20190627215038.png -.. |image37| image:: http://image.python-online.cn/20190708100902.png -.. |image38| image:: http://image.python-online.cn/20190708103119.png -.. |image39| image:: http://image.python-online.cn/20190719170825.png -.. |image40| image:: http://image.python-online.cn/20190730140551.png -.. |image41| image:: http://image.python-online.cn/20190826210813.png -.. |image42| image:: http://image.python-online.cn/20190826211542.png -.. |image43| image:: http://image.python-online.cn/20190826211737.png -.. |image44| image:: http://image.python-online.cn/20190730142527.png -.. |image45| image:: http://image.python-online.cn/20190730143003.png -.. |image46| image:: http://image.python-online.cn/20190830091540.png -.. |image47| image:: http://image.python-online.cn/20190830092203.png -.. |image48| image:: http://image.python-online.cn/20190830093613.png +.. |image14| image:: http://image.python-online.cn/20190906093751.png +.. |image15| image:: http://image.python-online.cn/20190906094703.png +.. |image16| image:: http://image.python-online.cn/20190906214536.png +.. |image17| image:: http://image.python-online.cn/20190906100119.png +.. |image18| image:: http://image.python-online.cn/20190906213038.png +.. |image19| image:: http://image.python-online.cn/20190906210825.png +.. |image20| image:: http://image.python-online.cn/20190906213823.png +.. |image21| image:: http://image.python-online.cn/20190426153322.png +.. |image22| image:: http://image.python-online.cn/20190426152148.png +.. |image23| image:: http://image.python-online.cn/20190526141815.png +.. |image24| image:: http://image.python-online.cn/20190526142453.png +.. |image25| image:: http://image.python-online.cn/20190526134519.png +.. |image26| image:: http://image.python-online.cn/20190526141226.png +.. |image27| image:: http://image.python-online.cn/20190526134543.png +.. |image28| image:: http://image.python-online.cn/20190526134618.png +.. |image29| image:: http://image.python-online.cn/20190526140213.png +.. |image30| image:: http://image.python-online.cn/20190526140301.png +.. |image31| image:: http://image.python-online.cn/20190526140315.png +.. |image32| image:: http://image.python-online.cn/20190526140336.png +.. |image33| image:: http://image.python-online.cn/20190526140410.png +.. |image34| image:: http://image.python-online.cn/20190526143235.png +.. |image35| image:: http://image.python-online.cn/20190526205152.png +.. |image36| image:: http://image.python-online.cn/20190526165007.png +.. |image37| image:: http://image.python-online.cn/20190526204328.png +.. |image38| image:: http://image.python-online.cn/20190529203441.png +.. |image39| image:: http://image.python-online.cn/20190529215953.png +.. |image40| image:: http://image.python-online.cn/20190529215825.png +.. |image41| image:: http://image.python-online.cn/20190627213044.png +.. |image42| image:: http://image.python-online.cn/20190627213609.png +.. |image43| image:: http://image.python-online.cn/20190627215038.png +.. |image44| image:: http://image.python-online.cn/20190708100902.png +.. |image45| image:: http://image.python-online.cn/20190708103119.png +.. |image46| image:: http://image.python-online.cn/20190719170825.png +.. |image47| image:: http://image.python-online.cn/20190730140551.png +.. |image48| image:: http://image.python-online.cn/20190826210813.png +.. |image49| image:: http://image.python-online.cn/20190826211542.png +.. |image50| image:: http://image.python-online.cn/20190826211737.png +.. |image51| image:: http://image.python-online.cn/20190730142527.png +.. |image52| image:: http://image.python-online.cn/20190730143003.png +.. |image53| image:: http://image.python-online.cn/20190830091540.png +.. |image54| image:: http://image.python-online.cn/20190830092203.png +.. |image55| image:: http://image.python-online.cn/20190830093613.png diff --git a/source/c08/c08_14.rst b/source/c08/c08_14.rst index ab5fea7..d0bd8d2 100644 --- a/source/c08/c08_14.rst +++ b/source/c08/c08_14.rst @@ -291,6 +291,23 @@ centos 6.x 配置网络是在 on_first_boot 函数里,这是 local |image12| +ubuntu 解析网卡配置 + +.. code:: python + + from cloudinit.net import eni + config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') + +|image13| + +8.14.5 其他命令记录 +------------------- + +:: + + # 网络启动不来,先 flush 试一下 + ip addr flush dev ens3 + -------------- .. figure:: http://image.python-online.cn/20190511161447.png @@ -310,4 +327,5 @@ centos 6.x 配置网络是在 on_first_boot 函数里,这是 local .. |image10| image:: http://image.python-online.cn/20190829112446.png .. |image11| image:: http://image.python-online.cn/20190829111917.png .. |image12| image:: http://image.python-online.cn/20190829161243.png +.. |image13| image:: http://image.python-online.cn/20190906091102.png From 95507dacdf032fc1ed18f5a186e96d54d85f80a7 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Wed, 11 Sep 2019 08:18:35 +0800 Subject: [PATCH 097/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20cloudinit=20?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_06.md | 155 +++++++++++++++++++++++++++++++++++++++++-- source/c08/c08_13.md | 19 ++++++ source/c08/c08_14.md | 18 ----- 3 files changed, 170 insertions(+), 22 deletions(-) diff --git a/source/c08/c08_06.md b/source/c08/c08_06.md index dfeb939..3d16f5a 100644 --- a/source/c08/c08_06.md +++ b/source/c08/c08_06.md @@ -370,20 +370,167 @@ myjob.cfg:text/upstart-job +## 8.6.8 模块是怎么执行的 +核心代码在 `_run_modules` 函数里: -查看机器里有哪几张网卡 +```python +# stages.py +def _run_modules(self, mostly_mods): + cc = self.init.cloudify() + # Return which ones ran + # and which ones failed + the exception of why it failed + failures = [] + which_ran = [] + for (mod, name, freq, args) in mostly_mods: + try: + # Try the modules frequency, otherwise fallback to a known one + if not freq: + freq = mod.frequency + if freq not in FREQUENCIES: + freq = PER_INSTANCE + LOG.debug("Running module %s (%s) with frequency %s", + name, mod, freq) + + # Use the configs logger and not our own + # TODO(harlowja): possibly check the module + # for having a LOG attr and just give it back + # its own logger? + func_args = [name, self.cfg, + cc, config.LOG, args] + # Mark it as having started running + which_ran.append(name) + # This name will affect the semaphore name created + run_name = "config-%s" % (name) + + desc = "running %s with frequency %s" % (run_name, freq) + myrep = events.ReportEventStack( + name=run_name, description=desc, parent=self.reporter) + + with myrep: + # 执行模块 + ran, _r = cc.run(run_name, mod.handle, func_args, + freq=freq) + if ran: + myrep.message = "%s ran successfully" % run_name + else: + myrep.message = "%s previously ran" % run_name +``` -ls -l /sys/class/net +上面的 cc.run(),代码如下: +```python +# helpers.py +def run(self, name, functor, args, freq=None, clear_on_fail=False): + + # 获取 sem 文件。 + sem = self._get_sem(freq) + if not sem: + sem = DummySemaphores() + if not args: + args = [] + + # 判断是否已经执行过(准确说是,是否需要执行),具体逻辑可以参考上面 8.6.8 章节的逻辑。 + if sem.has_run(name, freq): + LOG.debug("%s already ran (freq=%s)", name, freq) + return (False, None) + + # 如果需要执行,就在对应的 sem 目录下生成一个文件(锁) + with sem.lock(name, freq, clear_on_fail) as lk: + if not lk: + raise LockFailure("Failed to acquire lock for %s" % name) + else: + LOG.debug("Running %s using lock (%s)", name, lk) + if isinstance(args, (dict)): + # 然后去执行这个模块 + results = functor(**args) + else: + results = functor(*args) + return (True, results) +``` -只查看 ipv4 或 ipv6的网卡ip -ip -6 addr show +## 8.6.9 模块执行的频率 + +在 cloudinit 里的各个模块里,都可以配置执行频率。 + +- PER_ALWAYS:总是执行 +- PER_INSTANCE:每个虚拟机实例一次 +- PER_ONCE:每个镜像只执行一次 + +对于 `PER_INSTANCE` 和 `PER_ONCE` 和区别,从名字和代码实现上看,似乎没有区别,继续看 cloudinit 里的代码,只有一个模块使用了 PER_ONCE ,那就是 `cc_scripts_per_once.py`。 + +![](http://image.python-online.cn/20190910160035.png) + +其实他们还是有区别的,由下面的代码来看 + +- `PER_ONCE` 获取的 sem 文件是从 `/var/lib/cloud/` 下的文件获取的,这个目录一个镜像只会生成一次(若你不删除的话)。 +- `PER_INSTANCE` 获取 sem 文件是从 `/var/lib/cloud/instances/` 下的文件获取的,这个目录每个虚拟机一个。 + +![](http://image.python-online.cn/20190910150305.png) + +如果你的模块里已经配置了频率,则以此为准。若没有配置,则默认为 `PER_INSTANCE`。 + +具体函数:`_run_modules` + +![](http://image.python-online.cn/20190910142222.png) + + + +那么cloudinit 是如何控制模块的执行频率呢? + +通过代码可以发现,其在运行时,会先调用 `has_run` 函数,在这里会去取对应目录下(上面的 sem_path)的sem文件,如果有存在 sem 文件,说明已经执行过(返回 False ),如果不存在 sem 文件(返回 True) 说明未执行过。 + +![](http://image.python-online.cn/20190910153637.png) + +sem 文件是怎样的? + +这是 `PER_ONCE` 的 sem 文件: + +![](http://image.python-online.cn/20190910171359.png) +这是 `PER_INSTANCE` 的 sem 文件![](http://image.python-online.cn/20190910171538.png) + + + +## 8.6.10 如何解析网卡配置 + +ubuntu 的网卡配置不是正常我们常见的 json 或者 yaml 格式,若要将其转化成python的字典对象。这在cloudinit是怎么做的呢? + +将这个功能拿出来 ,你可以这样用。 + +```python +from cloudinit.net import eni +config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') +``` + +![](http://image.python-online.cn/20190906091102.png) + +## 8.6.11 低版本的日志输出 + +如果你用过 centos6.x 的 cloudinit,你会发现其根本不会将日志输出到 `/var/log/cloud-init.log `中。 + +经过排查,你可以在 `cloudinit/log.py` 中按下面的改法修改解决 + +![](http://image.python-online.cn/20190909172153.png) + +## 8.6.11 相关命令 + +```shell +# 查看机器里有哪几张网卡 +ls -l /sys/class/net + +# 只查看 ipv4 或 ipv6的网卡ip +ip -6 addr show ip -4 addr show +# 网络启动不来,先 flush 试一下 +ip addr flush dev ens3 +``` + + + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/c08/c08_13.md b/source/c08/c08_13.md index d86ee9b..d585fca 100644 --- a/source/c08/c08_13.md +++ b/source/c08/c08_13.md @@ -243,6 +243,25 @@ arp -e -i eth1 arp -f /etc/ethers ``` +## 8.13.3 ovs 流表 + +使用 `ovs-ofctl dump-flows br-int` 可以查看从 br-int 到 br0-ovs 的流表。 + +查看 `0x0000/0x1fff` 这一行后的 `actions=mod_vlan_vid:4` ,其中的 `4` 是vlan id,意思是从虚拟机的网卡出来的包如果tag=4,在经过 br0-ovs 的时候,就会把 tag 去掉,不会被过滤掉,使其能把包发向公网。 + +假如不设置tag,br-int 上的包就不会流往 br0-ovs。 + +假如你的虚拟机是连在 br-int 上,而且没有tag,那么需要你手动加tag + +``` +virsh domiflist vm_domain +ovs-vsctl set port vnet0 tag=4 +``` + + + + + --- diff --git a/source/c08/c08_14.md b/source/c08/c08_14.md index aa705b3..c0ee5e7 100644 --- a/source/c08/c08_14.md +++ b/source/c08/c08_14.md @@ -293,24 +293,6 @@ centos 6.x 配置网络是在 on_first_boot 函数里,这是 local 阶段就 -ubuntu 解析网卡配置 - -```python -from cloudinit.net import eni -config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') -``` - -![](http://image.python-online.cn/20190906091102.png) - -## 8.14.5 其他命令记录 - - - -``` -# 网络启动不来,先 flush 试一下 -ip addr flush dev ens3 -``` - --- From c1dc74269925d2302a2c5c85f77b4f74b9af6a4b Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Wed, 11 Sep 2019 21:19:55 +0800 Subject: [PATCH 098/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20cloudinit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_06.md | 229 ++++++++++++++++++++++++++++--------------- 1 file changed, 151 insertions(+), 78 deletions(-) diff --git a/source/c08/c08_06.md b/source/c08/c08_06.md index 3d16f5a..656ff4c 100644 --- a/source/c08/c08_06.md +++ b/source/c08/c08_06.md @@ -116,84 +116,6 @@ find_source 是通过一个一个去执行 对应source模块的 get_data(), -## 8.6.4 网络是如何配置的? - -在不同的 cloudinit 版本中,网络配置部分的代码是不同的。 - -首先要知道,配置网络是在 `on_first_boot` 函数里配置的。它是在cloudinit 判断该机器为新虚拟机时才会执行。也就是说,这个网络配置在一个虚拟机生命周期里,只会执行一次,如果要二次执行,需要执行 `rm -rf /var/lib/cloud` ,将缓存数据删除,这边才会重新认定为新虚拟机。 - -在 CentOS6.x (cloud-init 0.7.5)中,网络信息的读取与配置都是在且仅能在 local 阶段进行的,代码如下,只在 dsmode 为 local 时才会执行 on_first_boot。 - -![](http://image.python-online.cn/20190429104357.png) - -而在 CentOS7.x (cloud-init 0.7.9)中 或者 Ubuntu 16.04(cloud-init 17.1)中,配置网络不是在 on_first_boot 函数里,而是在 stages.py:apply_network_config() 里。 - -![](http://image.python-online.cn/20190829141059.png) - -并且新版的cloudinit ,local 和 init 阶段都会去配置网络,区别在于,local阶段只写配置文件,而init阶段不仅写配置文件,还会启动网卡,将ip配置上。 - -centos 7.x ,因为会跳过,它是在 stages.py:apply_network_config 函数里,这是在 local 阶段写入配置文件,而在 init 阶段 启动网卡。 - -![](http://image.python-online.cn/20190829113537.png) - -所以如果要调试的话可以直接执行 init 阶段。 - -而如果是虚拟机重启的话,是在stages.py 这边判断是否为新虚拟机的,这也和旧版本有所区别。 - -![](http://image.python-online.cn/20190829141059.png) - -为了让你能更加清晰的了解这个网络配置过程,我阅读了这块的源代码。 - -在 cloud-init 的比较重要的几个文件有: - -- 入口文件(上面已经说明过了) -- stages.py -- distros/rhel.py -- sources/DataSourceConfigDrive.py - -在网络配置这块,有几个大坑。 - -**坑一** - -如果是按照旧虚拟机创建新的快照镜像,然后使用这个镜像创建新的虚拟机,有可能会在同一块网卡上出现新旧两个ip,这是因为虚拟机在启动过程中,会先读取原网络配置配置ip,然后才会运行 cloud-init 进行新ip的配置,而新ip的配置是使用 `ifup` 这个命令![](http://image.python-online.cn/Fp1TeHSiIMIQoZygbW9VSfAagB_d) - -使用这种方式并不会将第一次配置的旧ip给清除掉。![](http://image.python-online.cn/Fh-5SQ8qYjhJEKovI6LmIpabSy2c) - -这个问题,目前我只在CentOS6 中遇到过。可以通过修改代码让其先 `ifdown` 再 `ifup` 就可以解决这个问题。![](http://image.python-online.cn/20190430231812.png) - -**坑二** - -如果使用dhcp,cloudinit不会创建或者刷新网卡配置文件,而把配置ip交由 NetworkManager ,让它自动获取。这是很重要。所以如果你用旧虚拟机创建快照的方式做镜像,然后用这个镜像创建的虚拟机会有旧虚拟机的配置文件会暴露原机器的ip地址。 - -具体的创建逻辑是在这 - -![](http://image.python-online.cn/20190430232309.png) - -**坑三** - -在 CentOS 6 上,安装NetworkManager 时不会安装完整。 - -会导致两个问题,一个是在启动时,会提示无法加载插件,导致启动失败。 - -``` -Apr 29 11:13:29 localhost NetworkManager[1365]: Loaded plugin keyfile: (cc -) 2007 - 2008 Red Hat, Inc. To report bugs please use the NetworkManager mailinn -g list. -Apr 29 11:13:29 localhost NetworkManager[1365]: [1556507609.466522] [maii -n.c:708] main(): failed to initialize the network manager: Could not load pluginn - 'ibft': /usr/lib64/NetworkManager/libnm-settings-plugin-ibft.so: undefined symbb -ol: g_slist_free_full -Apr 29 11:13:29 localhost NetworkManager[1365]: exiting (error) -``` - -一个是会自动DHCP获取到一个以ip命名的hostname,并将原来的覆盖掉。 - -![](http://image.python-online.cn/20190429205735.png) - -为了避免出现这些情况,请务必保证这些包都安装完整(左为 CentOS 7.2,右为 CentOS 6.5)。 - -![](http://image.python-online.cn/20190430232911.png) - ## 8.6.5 userdata 使用说明 现在 Userdate 可以支持如下三种格式 @@ -515,6 +437,157 @@ config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') ![](http://image.python-online.cn/20190909172153.png) +## 8.6.12 旧版本网络配置的三个巨坑 + +在不同的 cloudinit 版本中,网络配置部分的代码是不同的。 + +首先要知道,配置网络是在 `on_first_boot` 函数里配置的。它是在cloudinit 判断该机器为新虚拟机时才会执行。也就是说,这个网络配置在一个虚拟机生命周期里,只会执行一次,如果要二次执行,需要执行 `rm -rf /var/lib/cloud` ,将缓存数据删除,这边才会重新认定为新虚拟机。 + +这里仅以 cloudinit 0.7.5 ( CentOS6.x)的版本为例。 + +在 cloud-init 0.7.5中,网络信息的读取与配置都是在且仅能在 local 阶段进行的,代码如下,只在 dsmode 为 local 时才会执行 on_first_boot。 + +![](http://image.python-online.cn/20190429104357.png) + +而如果是虚拟机重启的话,是在stages.py 这边判断是否为新虚拟机的,这也和旧版本有所区别。 + +![](http://image.python-online.cn/20190829141059.png) + +为了让你能更加清晰的了解这个网络配置过程,我阅读了这块的源代码。 + +在 cloud-init 的比较重要的几个文件有: + +- 入口文件(上面已经说明过了) +- stages.py +- distros/rhel.py +- sources/DataSourceConfigDrive.py + +在网络配置这块,有几个大坑。 + +**坑一** + +如果是按照旧虚拟机创建新的快照镜像,然后使用这个镜像创建新的虚拟机,有可能会在同一块网卡上出现新旧两个ip,这是因为虚拟机在启动过程中,会先读取原网络配置配置ip,然后才会运行 cloud-init 进行新ip的配置,而新ip的配置是使用 `ifup` 这个命令![](http://image.python-online.cn/Fp1TeHSiIMIQoZygbW9VSfAagB_d) + +使用这种方式并不会将第一次配置的旧ip给清除掉。![](http://image.python-online.cn/Fh-5SQ8qYjhJEKovI6LmIpabSy2c) + +这个问题,目前我只在CentOS6 中遇到过。可以通过修改代码让其先 `ifdown` 再 `ifup` 就可以解决这个问题。![](http://image.python-online.cn/20190430231812.png) + +**坑二** + +如果使用dhcp,cloudinit不会创建或者刷新网卡配置文件,而把配置ip交由 NetworkManager ,让它自动获取。这是很重要。所以如果你用旧虚拟机创建快照的方式做镜像,然后用这个镜像创建的虚拟机会有旧虚拟机的配置文件会暴露原机器的ip地址。 + +具体的创建逻辑是在这 + +![](http://image.python-online.cn/20190430232309.png) + +**坑三** + +在 CentOS 6 上,安装NetworkManager 时不会安装完整。 + +会导致两个问题,一个是在启动时,会提示无法加载插件,导致启动失败。 + +``` +Apr 29 11:13:29 localhost NetworkManager[1365]: Loaded plugin keyfile: (cc +) 2007 - 2008 Red Hat, Inc. To report bugs please use the NetworkManager mailinn +g list. +Apr 29 11:13:29 localhost NetworkManager[1365]: [1556507609.466522] [maii +n.c:708] main(): failed to initialize the network manager: Could not load pluginn + 'ibft': /usr/lib64/NetworkManager/libnm-settings-plugin-ibft.so: undefined symbb +ol: g_slist_free_full +Apr 29 11:13:29 localhost NetworkManager[1365]: exiting (error) +``` + +一个是会自动DHCP获取到一个以ip命名的hostname,并将原来的覆盖掉。 + +![](http://image.python-online.cn/20190429205735.png) + +为了避免出现这些情况,请务必保证这些包都安装完整(左为 CentOS 7.2,右为 CentOS 6.5)。 + +![](http://image.python-online.cn/20190430232911.png) + +## + +## 8.6.13 网络是如何启动的?(新版本) + +这里仅以 cloudinit 18.5 的版本为例。 + +通过查看代码主流程时,获取ds的时候,有一个参数是 existing,它有两个值: + +- trust:说明 ds 的缓存是可以相信的,不用再去校验 instance_uuid +- check:说明会去校验 instance_uuid,如果相同直接返回 ds 的缓存,如果不同则返回None,让后的步骤再去从 CD-ROM 读取最新的。 + +在 local 阶段的时候,`existing` 是 `check` ,这是合理的。 + +![](http://image.python-online.cn/20190911175423.png) + +![](http://image.python-online.cn/20190911174648.png) + +在local阶段,on_first_boot 的函数 network 是 False ,所以这里也不会写网卡配置文件,自然也不会配置。 + +![](http://image.python-online.cn/20190911173615.png) + +![](http://image.python-online.cn/20190911195024.png) + + + +再往后面看,就可以发现,原来写配置文件的地方是在 `cmd/main.py` 里。 + +```python +# cmd/main.py +mode = sources.DSMODE_LOCAL if args.local else sources.DSMODE_NETWORK + +# 中间省略多行代码 ... +init.apply_network_config(bring_up=bool(mode != sources.DSMODE_LOCAL)) +``` + +第一行,如果是 local 阶段,mode 为 local,bring_up 为 False,意思是只写配置文件,而不启用网卡。 + +等到 init 阶段时,mode 为 net (`sources.DSMODE_NETWORK`)时,bring_up 为 True,意思是会启用网卡,配置ip。 + +网卡网卡的主要函数在下面 `apply_network_config` 这个函数里,这个函数主要做三个事情: + +1. 获取网卡配置 +2. 校正网卡名,以 ConfigDrive 的配置为准 +3. 将ip信息写入配置文件,并启动网卡 + +![](http://image.python-online.cn/20190911202425.png) + +那它是如何对网卡进行重命令的呢?看了代码,其实是用ip命令实现的。 + +![](http://image.python-online.cn/20190911202551.png) + +提取出来其实就三条命令,要注意的是,这三条命令执行是有顺序的 + +``` +1. ip link set ens3 down +2. ip link set ens3 name eth0 +3. ip link set eth0 up +``` + +还有一点要说的是,cloudinit 是如何取到本机真实的网卡信息的呢?他是从 `/sys/class/net/` 目录下获取的。每个网卡一个目录,每个目录下都有相应的文件记录相应的信息,比如 `/sys/class/net/ens3/address` 记录的是网卡的 mac 地址。 + +![](http://image.python-online.cn/20190911203953.png) + +接下来就要开始配置网络了,先写网络配置文件,再根据参数选择是否启用网络。 + +![](http://image.python-online.cn/20190911204805.png) + +如果是重启虚拟机或者 init 阶段进入这里呢,会不会又重复配置网络了呢? + +答案是:不会的。 + +cloudinit 会根据缓存中的虚拟机的uuid来与ConfigDrive 的对比,如果不一样,则认为这台虚拟机是新创建的虚拟机,只有新的虚拟机才会走入这里去配置网络。 + +![](http://image.python-online.cn/20190911205518.png) + +那问题又来了,虽然上面有个 bring_up 的参数,实际上,通过代码可以发现,在 local 阶段,bring_up 为 False 不会去启用网卡,而在 init 阶段呢,虽然 bring_up 为 True,但是此时,经过 local 阶段后,代码逻辑会认为这是台旧虚拟机,不会再走后面配置网络的函数,那就很奇怪了,网卡的ip是如何配置上的呢? + +这是个好问题,也是个很难察觉的点。 + +大多数人,可能不知道linux内部的服务是有启动顺序的。在这种情况下,我们必须保证,cloudinit 写入配置的服务(cloud-init-local)必须在 network 或者 Network-Manager 的服务之前。 + +这样在 cloud-init-local 执行完后,就会自动配置上网络了。 + ## 8.6.11 相关命令 ```shell From 02099c852ac80112b5eea193fe02306c013d8219 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 30 Sep 2019 08:59:21 +0800 Subject: [PATCH 099/302] =?UTF-8?q?ubuntu=20=E4=BD=BF=E7=94=A8=20ipv6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_14.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/source/c08/c08_14.md b/source/c08/c08_14.md index c0ee5e7..7e4d2f4 100644 --- a/source/c08/c08_14.md +++ b/source/c08/c08_14.md @@ -293,6 +293,38 @@ centos 6.x 配置网络是在 on_first_boot 函数里,这是 local 阶段就 +## 8.14.5 ubuntu禁用ipv6 + +如果你给你的ubuntu配置上了ipv6的ip,那当你使用 apt-get install 软件包的时候,会使用ipv6去源安装,这将会使你的安装过程卡住: + +![](http://image.python-online.cn/20190926171038.png) + +如何解决这个问题呢? + +参考文章:https://ubuntuqa.com/article/1577.html + +我使用的方法是 + +在终端中运行以下命令禁用IPv6,`0`表示已启用,而`1`表示已禁用。 + +``` +echo 1>/proc/sys/net/ipv6/conf/all/disable_ipv6 +``` + +然后在终端内禁用IPv6,需要再输入以下内容: + +``` +echo "#disable ipv6" | sudo tee -a /etc/sysctl.conf +echo "net.ipv6.conf.all.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf +echo "net.ipv6.conf.default.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf +echo "net.ipv6.conf.lo.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf +sudo sysctl -p +``` + + + + + --- From 2b3d07a8cbb7042d229f608d9d60533acf357520 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 30 Sep 2019 09:00:01 +0800 Subject: [PATCH 100/302] =?UTF-8?q?cloudinit=20=E5=A6=82=E4=BD=95=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E5=AE=A2=E6=88=B7=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_06.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/source/c08/c08_06.md b/source/c08/c08_06.md index 656ff4c..b77036e 100644 --- a/source/c08/c08_06.md +++ b/source/c08/c08_06.md @@ -192,7 +192,8 @@ ws_virt_network_dep: netmask: 255.255.255.0 gateway: 192.168.3.254 - +# 会将这些命令写入如下文件,但是并没有执行 +# /var/lib/cloud/instances/25be4404-4665-4b64-91cc-3289cf2a0af0/scripts/runcmd runcmd: - [ sed, -i, -e, '%s/x/y/g', some_file] - echo "modified some_file" @@ -505,8 +506,6 @@ Apr 29 11:13:29 localhost NetworkManager[1365]: exiting (error) ![](http://image.python-online.cn/20190430232911.png) -## - ## 8.6.13 网络是如何启动的?(新版本) 这里仅以 cloudinit 18.5 的版本为例。 @@ -588,7 +587,26 @@ cloudinit 会根据缓存中的虚拟机的uuid来与ConfigDrive 的对比,如 这样在 cloud-init-local 执行完后,就会自动配置上网络了。 -## 8.6.11 相关命令 +## 8.6.14 在虚拟机启动时执行命令 + +cloudinit 允许通过 user_data 指定你想在虚拟机启动时,执行的命令。 + +``` +# 会将这些命令写入如下文件,但是并没有执行 +# +runcmd: + - [ sed, -i, -e, '%s/x/y/g', some_file] + - echo "modified some_file" + - [cat, some_file] +``` + +当你进行了如此配置后,cloudinit 会通过 runcmd 模块,将这些命令组合起来写入 `/var/lib/cloud/instances//scripts/runcmd`。 + +但这仅仅只是写入,若要执行这些命令,还需要你在 /etc/cloud/cloud.cfg 中配置 `scripts_user`,这样 cloud-init 才会去执行它。 + + + +## 8.6.15 相关命令 ```shell # 查看机器里有哪几张网卡 From 80c8d2dceaaa3dfe635fbba9b9d1966fed258834 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 30 Sep 2019 09:00:18 +0800 Subject: [PATCH 101/302] =?UTF-8?q?openstack=20=E5=A6=82=E4=BD=95=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c08/c08_05.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index f491897..8e6eea4 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -613,7 +613,15 @@ nova-manage db sync +## 8.5.21 如何加action +``` +self._record_action_start(context, instance, instance_actions.REBOOT) +``` + +## 8.5.22 如何生成ConfigDrive + +![](http://image.python-online.cn/20190912135302.png) --- From 8010473e3ff763c5bb6ecbc20f22304b4d3a5db9 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 6 Oct 2019 19:24:25 +0800 Subject: [PATCH 102/302] update --- source/c01/c01_10.md | 23 ++++++++++++++--------- source/c01/c01_21.md | 6 ++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/source/c01/c01_10.md b/source/c01/c01_10.md index bc0811d..1056def 100644 --- a/source/c01/c01_10.md +++ b/source/c01/c01_10.md @@ -972,7 +972,7 @@ hello hello ``` -如果你用单`\`结尾是会报语法错误。 +但是如果你用单`\`结尾是会报语法错误的 ```python >>> str3="\" @@ -980,8 +980,11 @@ hello str3="\" ^ SyntaxError: EOL while scanning string literal ->>> ->>> +``` + +就算你指定它是个 raw 字符串,也不行。 + +```python >>> str3=r"\" File "", line 1 str3=r"\" @@ -1006,10 +1009,6 @@ b ['a', 'b'] ``` - - - - ## 33. 更新字典 通常我们更新字典的方式是这样的 @@ -1031,8 +1030,6 @@ b {'namne': 'jane', 'gender': 'male', 'age': 24} ``` - - ## 34. 嵌套上下文管理的另类写法 当我们要写一个嵌套的上下文管理器时,可能会这样写 @@ -1099,6 +1096,14 @@ with test_context('aaa'), test_context('bbb'): +## 36. Python 里也可以有 end + +有不少编程语言,循环、判断代码块需要用 end 标明结束,这样一定程序上会使代码逻辑更加清晰一点,其实这种语法在 Python 里并没有必要,但如果你想用,也不是没有办法,具体你看下面这个例子。 + +![](http://image.python-online.cn/20190915213412.png) + + + ## 附录:参考文章 - [wtfpython](https://github.com/satwikkansal/wtfpython) diff --git a/source/c01/c01_21.md b/source/c01/c01_21.md index 9b1893f..d35190f 100644 --- a/source/c01/c01_21.md +++ b/source/c01/c01_21.md @@ -8,3 +8,9 @@ 解决网页不能右键,在console中输入:document.oncontextmenu=true ``` +在 linux 上看 json 文件 + +``` +cat test.json | python -m json.tool +``` + From 7142b6b8d3d705819cef874d9aa45fa79a3b18cf Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 6 Oct 2019 19:24:36 +0800 Subject: [PATCH 103/302] =?UTF-8?q?=E5=AD=A6=E4=B9=A0js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_23.md | 125 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 source/c01/c01_23.md diff --git a/source/c01/c01_23.md b/source/c01/c01_23.md new file mode 100644 index 0000000..495309a --- /dev/null +++ b/source/c01/c01_23.md @@ -0,0 +1,125 @@ +# 1.23 Pythonista 学习 Js + +1. JavaScript的设计者希望用`null`表示一个空的值,而`undefined`表示值未定义。事实证明,这并没有什么卵用,区分两者的意义不大。大多数情况下,我们都应该用`null`。`undefined`仅仅在判断函数参数是否传递的情况下有用。 + + + +2. 在JavaScript中,使用等号`=`对变量进行赋值。可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,但是要注意只能用`var`申明一次。 + + + +3. var 申明的变量不是全局变量,如果不使用 var 申明变量,就会变成全局变量,多个js文件会共享这个变量,互相影响。这个语言设计弊端,在 ECMA 推出 strict 模式后得到解决,不使用 var 申明变量的会运行出错。 + + + +4. 直接操作数组的长度,或者对超出数组长度的索引赋值,可以扩容和压缩数组的大小。 + + + +5. 对象之间的比较和Python差异巨大,`=` 表示值的比较,`===` 表示对象类型的比较,主要分为下面三种情况: + + ```javascript + // 1. 对于string,number等【基础类型】,==和===是有区别的。 + // 如果是不同类型,== 会将两边转化成同一类型再看值是否相等 + // 如果是相同类型,直接对值进行比较。 + alert('1'==1);//结果是true + alert('1'===1);//结果是false + + + // 2. 如果是 Array,Object等高级类型,==和===是没有区别的。 + // 直接比较指针地址。,跟 Python 里的 is 等同。 + + + // 3. 基础类型与高级类型,==和===是有区别的。 + // 对于==,将高级转化为基础类型,进行“值”比较。 + // 因为类型不同,===结果为false。 + var a = new String('1');//定义一个string的高级类型 + var b = '1';//定一个基础类型字符串 + alert(b==a);//为true + alert(b===a);//为false + ``` + +6. 定义一个函数,其参数个数可以少于传入的参数个数,基于可以不定义参数,还可以传入参数。那传入的多余的参数如何获取呢?可以使用 arguments 这个变量取得所有的参数,但这个变量仅在参数内起作用。 + + ```python + function foo(a, b, ...rest) { + console.log('a = ' + a); + console.log('b = ' + b); + console.log(rest); + } + ``` + +7. JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部,但是并不会提前为变量赋值。 + + ```javascript + 'use strict'; + + function foo() { + var x = 'Hello, ' + y; + console.log(x); + var y = 'Bob'; + } + + foo(); + + // 对于 javascript 引擎,看到代码相当于 + function foo() { + var y; // 提升变量y的申明,此时y为undefined + var x = 'Hello, ' + y; + console.log(x); + y = 'Bob'; + } + ``` + + + +8. This有个巨坑,需要新手注意。 + + 如下,当没有 `var that = this;` 这句时,函数内部的函数里的this指向的是 window 对象。 + + ```javascript + 'use strict'; + + var xiaoming = { + name: '小明', + birth: 1990, + age: function () { + var that = this; // 在方法内部一开始就捕获this + function getAgeFromBirth() { + var y = new Date().getFullYear(); + return y - that.birth; // 用that而不是this + } + return getAgeFromBirth(); + } + }; + + xiaoming.age(); // 25 + ``` + + 或者,可以用 apply 的方法来解决这个问题,apply 的第一个参数是人该函数要绑定的对象,第二个参数是一个列表,装的是要传递给这个函数据参数。 + + ```javascript + function getAge() { + var y = new Date().getFullYear(); + return y - this.birth; + } + + var xiaoming = { + name: '小明', + birth: 1990, + age: getAge + }; + + xiaoming.age(); // 29 + getAge.apply(xiaoming, []); // 29, this指向xiaoming, 参数为空 + ``` + + 假如,不想绑定给任何对象,第一个参数可以用 null。如 + + ```javascript + Math.max.apply(null, [3, 5, 4]); // 5 + Math.max.call(null, 3, 5, 4); // 5 + ``` + + + From 2047786af0de93c75ed821df1e7e0762b508c005 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 6 Oct 2019 19:26:09 +0800 Subject: [PATCH 104/302] =?UTF-8?q?=E6=B7=BB=E5=8A=A0k8s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c07/c07_11.md | 88 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 source/c07/c07_11.md diff --git a/source/c07/c07_11.md b/source/c07/c07_11.md new file mode 100644 index 0000000..678bebb --- /dev/null +++ b/source/c07/c07_11.md @@ -0,0 +1,88 @@ +# 7.11 K8S:基础入门 + +一些基础命令 + +``` +# 启动cluster +minikube start + +# 查看集群信息 +kubectl cluster-info + +# 部署应用 +kubectl run kubernetes-bootcamp \ + --image=docker.io/jocatalin/kubernetes-bootcamp:v1 \ + --port=8080 + +# 查看所有的 deployments,在这里可以看到应用副本数 +kubectl get deployments + + +# 增加副本数 +kubectl scale deployments/kubernetes-bootcamp --replicas=3 +# 若要 scale down,就将 replicas 数值减小 +kubectl scale deployments/kubernetes-bootcamp --replicas=2 + +# 查看所有的副本 +kubectl get pods + +# 查看集群下的节点 +kubectl get nodes + +# 查看集群下的服务 +kubectl get services +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +kubernetes ClusterIP 10.96.0.1 443/TCP 19m +kubernetes-bootcamp NodePort 10.100.191.46 8080:30141/TCP 12s + +# 添加端口映射,主机上的端口是随机分配的 +kubectl expose deployment/kubernetes-bootcamp \ + --type="NodePort" \ + --port 8080 + +# 上面的 bootcamp 有两个副本,当我们curl访问应用时,请求会随机发到三个pod里 +# 其中 minikube 是该主机的 hostname,30141 是上面随机分配的端口号 +curl minikube:30141 + + +# 升级应用,从上面的 v1 升级到 v2,使用 get pods 可以发现这是一个删除再创建的过程。 +kubectl set image deployments/kubernetes-bootcamp \ + kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v2 + +# 回退,升级后后悔了,可以再回退到 之前的 v1 版本。 +kubectl rollout undo deployments/kubernetes-bootcamp + +# 查看 namespace,kubernetes 默认创建了两个 +# default -- 创建资源时如果不指定,将被放到这个 Namespace 中。 +# kube-system -- Kubernetes 自己创建的系统资源将放到这个 Namespace 中。 +kubectl get namespace + + +``` + + + +K8s 角色详解 + +![](http://image.python-online.cn/20190907162015.png) + +其中 Controller 还分为几种: + +1. Deployment:最常用的 Controller +2. ReplicaSet:实现 Pod 的多副本管理,一般不会直接使用 +3. Daemonset:用于运行 Daemon +4. StatefuleSet:保证每个副本的名称不变及启动/更新/删除顺序 +5. Job:用于运行完即删除的应用。 + +一个 Pod 里可以运行一个容器(最常用),也可以运行多个容器,若运行多个容器,那这几个容器的工作必定有着紧密的联系,而且需要直接 **共享资源**。 + + + + + + + + + + + From cb6fa5a967b76a51f45cec0eeafd6d2484c6e4aa Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 6 Oct 2019 19:27:48 +0800 Subject: [PATCH 105/302] make rst --- source/c01/c01_10.rst | 19 +- source/c01/c01_21.rst | 6 + source/c01/c01_23.rst | 119 ++++++++++ source/c07/c07_11.rst | 79 +++++++ source/c08/c08_05.rst | 13 ++ source/c08/c08_06.rst | 503 ++++++++++++++++++++++++++++++++---------- source/c08/c08_13.rst | 19 ++ source/c08/c08_14.rst | 33 ++- 8 files changed, 665 insertions(+), 126 deletions(-) create mode 100644 source/c01/c01_23.rst create mode 100644 source/c07/c07_11.rst diff --git a/source/c01/c01_10.rst b/source/c01/c01_10.rst index 141a60e..960bb93 100755 --- a/source/c01/c01_10.rst +++ b/source/c01/c01_10.rst @@ -1080,7 +1080,7 @@ import 是 Python 导包的方式。 >>> print(str2) hello -如果你用单\ ``\``\ 结尾是会报语法错误。 +但是如果你用单\ ``\``\ 结尾是会报语法错误的 .. code:: python @@ -1089,8 +1089,11 @@ import 是 Python 导包的方式。 str3="\" ^ SyntaxError: EOL while scanning string literal - >>> - >>> + +就算你指定它是个 raw 字符串,也不行。 + +.. code:: python + >>> str3=r"\" File "", line 1 str3=r"\" @@ -1204,6 +1207,15 @@ import 是 Python 导包的方式。 >>> b [1, 2, 3, 4, 5, 6, 7, 8] +36. Python 里也可以有 end +------------------------- + +有不少编程语言,循环、判断代码块需要用 end +标明结束,这样一定程序上会使代码逻辑更加清晰一点,其实这种语法在 Python +里并没有必要,但如果你想用,也不是没有办法,具体你看下面这个例子。 + +|image3| + 附录:参考文章 -------------- @@ -1218,4 +1230,5 @@ import 是 Python 导包的方式。 .. |image0| image:: http://image.python-online.cn/20190511165650.png .. |image1| image:: http://image.python-online.cn/20190511165716.png .. |image2| image:: http://image.python-online.cn/20190511165735.png +.. |image3| image:: http://image.python-online.cn/20190915213412.png diff --git a/source/c01/c01_21.rst b/source/c01/c01_21.rst index 5f99c9f..f76a2f6 100644 --- a/source/c01/c01_21.rst +++ b/source/c01/c01_21.rst @@ -8,3 +8,9 @@ 解决网页不能选中,在console中输入:document.onselectstart=true 解决网页不能复制,在console中输入:document.oncopy=true 解决网页不能右键,在console中输入:document.oncontextmenu=true + +在 linux 上看 json 文件 + +:: + + cat test.json | python -m json.tool diff --git a/source/c01/c01_23.rst b/source/c01/c01_23.rst new file mode 100644 index 0000000..4174cbd --- /dev/null +++ b/source/c01/c01_23.rst @@ -0,0 +1,119 @@ +1.23 Pythonista 学习 Js +======================= + +1. JavaScript的设计者希望用\ ``null``\ 表示一个空的值,而\ ``undefined``\ 表示值未定义。事实证明,这并没有什么卵用,区分两者的意义不大。大多数情况下,我们都应该用\ ``null``\ 。\ ``undefined``\ 仅仅在判断函数参数是否传递的情况下有用。 + +2. 在JavaScript中,使用等号\ ``=``\ 对变量进行赋值。可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,但是要注意只能用\ ``var``\ 申明一次。 + +3. var 申明的变量不是全局变量,如果不使用 var + 申明变量,就会变成全局变量,多个js文件会共享这个变量,互相影响。这个语言设计弊端,在 + ECMA 推出 strict 模式后得到解决,不使用 var 申明变量的会运行出错。 + +4. 直接操作数组的长度,或者对超出数组长度的索引赋值,可以扩容和压缩数组的大小。 + +5. 对象之间的比较和Python差异巨大,\ ``=`` 表示值的比较,\ ``===`` + 表示对象类型的比较,主要分为下面三种情况: + + .. code:: javascript + + // 1. 对于string,number等【基础类型】,==和===是有区别的。 + // 如果是不同类型,== 会将两边转化成同一类型再看值是否相等 + // 如果是相同类型,直接对值进行比较。 + alert('1'==1);//结果是true + alert('1'===1);//结果是false + + + // 2. 如果是 Array,Object等高级类型,==和===是没有区别的。 + // 直接比较指针地址。,跟 Python 里的 is 等同。 + + + // 3. 基础类型与高级类型,==和===是有区别的。 + // 对于==,将高级转化为基础类型,进行“值”比较。 + // 因为类型不同,===结果为false。 + var a = new String('1');//定义一个string的高级类型 + var b = '1';//定一个基础类型字符串 + alert(b==a);//为true + alert(b===a);//为false + +6. 定义一个函数,其参数个数可以少于传入的参数个数,基于可以不定义参数,还可以传入参数。那传入的多余的参数如何获取呢?可以使用 + arguments 这个变量取得所有的参数,但这个变量仅在参数内起作用。 + + .. code:: python + + function foo(a, b, ...rest) { + console.log('a = ' + a); + console.log('b = ' + b); + console.log(rest); + } + +7. JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部,但是并不会提前为变量赋值。 + + .. code:: javascript + + 'use strict'; + + function foo() { + var x = 'Hello, ' + y; + console.log(x); + var y = 'Bob'; + } + + foo(); + + // 对于 javascript 引擎,看到代码相当于 + function foo() { + var y; // 提升变量y的申明,此时y为undefined + var x = 'Hello, ' + y; + console.log(x); + y = 'Bob'; + } + +8. This有个巨坑,需要新手注意。 + + 如下,当没有 ``var that = this;`` + 这句时,函数内部的函数里的this指向的是 window 对象。 + + .. code:: javascript + + 'use strict'; + + var xiaoming = { + name: '小明', + birth: 1990, + age: function () { + var that = this; // 在方法内部一开始就捕获this + function getAgeFromBirth() { + var y = new Date().getFullYear(); + return y - that.birth; // 用that而不是this + } + return getAgeFromBirth(); + } + }; + + xiaoming.age(); // 25 + + 或者,可以用 apply 的方法来解决这个问题,apply + 的第一个参数是人该函数要绑定的对象,第二个参数是一个列表,装的是要传递给这个函数据参数。 + + .. code:: javascript + + function getAge() { + var y = new Date().getFullYear(); + return y - this.birth; + } + + var xiaoming = { + name: '小明', + birth: 1990, + age: getAge + }; + + xiaoming.age(); // 29 + getAge.apply(xiaoming, []); // 29, this指向xiaoming, 参数为空 + + 假如,不想绑定给任何对象,第一个参数可以用 null。如 + + .. code:: javascript + + Math.max.apply(null, [3, 5, 4]); // 5 + Math.max.call(null, 3, 5, 4); // 5 diff --git a/source/c07/c07_11.rst b/source/c07/c07_11.rst new file mode 100644 index 0000000..19b278b --- /dev/null +++ b/source/c07/c07_11.rst @@ -0,0 +1,79 @@ +7.11 K8S:基础入门 +================== + +一些基础命令 + +:: + + # 启动cluster + minikube start + + # 查看集群信息 + kubectl cluster-info + + # 部署应用 + kubectl run kubernetes-bootcamp \ + --image=docker.io/jocatalin/kubernetes-bootcamp:v1 \ + --port=8080 + + # 查看所有的 deployments,在这里可以看到应用副本数 + kubectl get deployments + + + # 增加副本数 + kubectl scale deployments/kubernetes-bootcamp --replicas=3 + # 若要 scale down,就将 replicas 数值减小 + kubectl scale deployments/kubernetes-bootcamp --replicas=2 + + # 查看所有的副本 + kubectl get pods + + # 查看集群下的节点 + kubectl get nodes + + # 查看集群下的服务 + kubectl get services + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + kubernetes ClusterIP 10.96.0.1 443/TCP 19m + kubernetes-bootcamp NodePort 10.100.191.46 8080:30141/TCP 12s + + # 添加端口映射,主机上的端口是随机分配的 + kubectl expose deployment/kubernetes-bootcamp \ + --type="NodePort" \ + --port 8080 + + # 上面的 bootcamp 有两个副本,当我们curl访问应用时,请求会随机发到三个pod里 + # 其中 minikube 是该主机的 hostname,30141 是上面随机分配的端口号 + curl minikube:30141 + + + # 升级应用,从上面的 v1 升级到 v2,使用 get pods 可以发现这是一个删除再创建的过程。 + kubectl set image deployments/kubernetes-bootcamp \ + kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v2 + + # 回退,升级后后悔了,可以再回退到 之前的 v1 版本。 + kubectl rollout undo deployments/kubernetes-bootcamp + + # 查看 namespace,kubernetes 默认创建了两个 + # default -- 创建资源时如果不指定,将被放到这个 Namespace 中。 + # kube-system -- Kubernetes 自己创建的系统资源将放到这个 Namespace 中。 + kubectl get namespace + +K8s 角色详解 + +|image0| + +其中 Controller 还分为几种: + +1. Deployment:最常用的 Controller +2. ReplicaSet:实现 Pod 的多副本管理,一般不会直接使用 +3. Daemonset:用于运行 Daemon +4. StatefuleSet:保证每个副本的名称不变及启动/更新/删除顺序 +5. Job:用于运行完即删除的应用。 + +一个 Pod +里可以运行一个容器(最常用),也可以运行多个容器,若运行多个容器,那这几个容器的工作必定有着紧密的联系,而且需要直接 +**共享资源**\ 。 + +.. |image0| image:: http://image.python-online.cn/20190907162015.png + diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index b6850d0..9fc2619 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -672,6 +672,18 @@ stevedore 这个模块去动态加载,然后还会校验这些资源是否都 nova-manage db sync +8.5.21 如何加action +------------------- + +:: + + self._record_action_start(context, instance, instance_actions.REBOOT) + +8.5.22 如何生成ConfigDrive +-------------------------- + +|image56| + -------------- .. figure:: http://image.python-online.cn/20190511161447.png @@ -734,4 +746,5 @@ stevedore 这个模块去动态加载,然后还会校验这些资源是否都 .. |image53| image:: http://image.python-online.cn/20190830091540.png .. |image54| image:: http://image.python-online.cn/20190830092203.png .. |image55| image:: http://image.python-online.cn/20190830093613.png +.. |image56| image:: http://image.python-online.cn/20190912135302.png diff --git a/source/c08/c08_06.rst b/source/c08/c08_06.rst index 09d9335..2dfb575 100644 --- a/source/c08/c08_06.rst +++ b/source/c08/c08_06.rst @@ -151,100 +151,6 @@ get_data(),如果获取到了数据就直接返回。 |image13| -8.6.4 网络是如何配置的? ------------------------- - -在不同的 cloudinit 版本中,网络配置部分的代码是不同的。 - -首先要知道,配置网络是在 ``on_first_boot`` 函数里配置的。它是在cloudinit -判断该机器为新虚拟机时才会执行。也就是说,这个网络配置在一个虚拟机生命周期里,只会执行一次,如果要二次执行,需要执行 -``rm -rf /var/lib/cloud`` ,将缓存数据删除,这边才会重新认定为新虚拟机。 - -在 CentOS6.x (cloud-init 0.7.5)中,网络信息的读取与配置都是在且仅能在 -local 阶段进行的,代码如下,只在 dsmode 为 local 时才会执行 -on_first_boot。 - -|image14| - -而在 CentOS7.x (cloud-init 0.7.9)中 或者 Ubuntu 16.04(cloud-init -17.1)中,配置网络不是在 on_first_boot 函数里,而是在 -stages.py:apply_network_config() 里。 - -|image15| - -并且新版的cloudinit ,local 和 init -阶段都会去配置网络,区别在于,local阶段只写配置文件,而init阶段不仅写配置文件,还会启动网卡,将ip配置上。 - -centos 7.x ,因为会跳过,它是在 stages.py:apply_network_config -函数里,这是在 local 阶段写入配置文件,而在 init 阶段 启动网卡。 - -|image16| - -所以如果要调试的话可以直接执行 init 阶段。 - -而如果是虚拟机重启的话,是在stages.py -这边判断是否为新虚拟机的,这也和旧版本有所区别。 - -|image17| - -为了让你能更加清晰的了解这个网络配置过程,我阅读了这块的源代码。 - -在 cloud-init 的比较重要的几个文件有: - -- 入口文件(上面已经说明过了) -- stages.py -- distros/rhel.py -- sources/DataSourceConfigDrive.py - -在网络配置这块,有几个大坑。 - -**坑一** - -如果是按照旧虚拟机创建新的快照镜像,然后使用这个镜像创建新的虚拟机,有可能会在同一块网卡上出现新旧两个ip,这是因为虚拟机在启动过程中,会先读取原网络配置配置ip,然后才会运行 -cloud-init 进行新ip的配置,而新ip的配置是使用 ``ifup`` -这个命令\ |image18| - -使用这种方式并不会将第一次配置的旧ip给清除掉。\ |image19| - -这个问题,目前我只在CentOS6 中遇到过。可以通过修改代码让其先 ``ifdown`` -再 ``ifup`` 就可以解决这个问题。\ |image20| - -**坑二** - -如果使用dhcp,cloudinit不会创建或者刷新网卡配置文件,而把配置ip交由 -NetworkManager -,让它自动获取。这是很重要。所以如果你用旧虚拟机创建快照的方式做镜像,然后用这个镜像创建的虚拟机会有旧虚拟机的配置文件会暴露原机器的ip地址。 - -具体的创建逻辑是在这 - -|image21| - -**坑三** - -在 CentOS 6 上,安装NetworkManager 时不会安装完整。 - -会导致两个问题,一个是在启动时,会提示无法加载插件,导致启动失败。 - -:: - - Apr 29 11:13:29 localhost NetworkManager[1365]: Loaded plugin keyfile: (cc - ) 2007 - 2008 Red Hat, Inc. To report bugs please use the NetworkManager mailinn - g list. - Apr 29 11:13:29 localhost NetworkManager[1365]: [1556507609.466522] [maii - n.c:708] main(): failed to initialize the network manager: Could not load pluginn - 'ibft': /usr/lib64/NetworkManager/libnm-settings-plugin-ibft.so: undefined symbb - ol: g_slist_free_full - Apr 29 11:13:29 localhost NetworkManager[1365]: exiting (error) - -一个是会自动DHCP获取到一个以ip命名的hostname,并将原来的覆盖掉。 - -|image22| - -为了避免出现这些情况,请务必保证这些包都安装完整(左为 CentOS 7.2,右为 -CentOS 6.5)。 - -|image23| - 8.6.5 userdata 使用说明 ----------------------- @@ -324,7 +230,8 @@ content-types:text/cloud-config netmask: 255.255.255.0 gateway: 192.168.3.254 - + # 会将这些命令写入如下文件,但是并没有执行 + # /var/lib/cloud/instances/25be4404-4665-4b64-91cc-3289cf2a0af0/scripts/runcmd runcmd: - [ sed, -i, -e, '%s/x/y/g', some_file] - echo "modified some_file" @@ -416,7 +323,7 @@ cirename0 的网卡。 这就是cloudinit搞的鬼,在cloudinit的local阶段,好像会记录之前的mac地址,如果发现不一致,就会触发rename_interface。 -|image24| +|image14| 8.6.7 虚拟机启动卡住 -------------------- @@ -429,17 +336,372 @@ ephemeral0 拿到对应的 /dev/vdb,并将其写入 /etc/fstab 中。在下次重启时,会根据 fstab 挂载磁盘,如果挂载不上,就会导致虚拟机启动卡住。 +|image15| + +8.6.8 模块是怎么执行的 +---------------------- + +核心代码在 ``_run_modules`` 函数里: + +.. code:: python + + # stages.py + def _run_modules(self, mostly_mods): + cc = self.init.cloudify() + # Return which ones ran + # and which ones failed + the exception of why it failed + failures = [] + which_ran = [] + for (mod, name, freq, args) in mostly_mods: + try: + # Try the modules frequency, otherwise fallback to a known one + if not freq: + freq = mod.frequency + if freq not in FREQUENCIES: + freq = PER_INSTANCE + LOG.debug("Running module %s (%s) with frequency %s", + name, mod, freq) + + # Use the configs logger and not our own + # TODO(harlowja): possibly check the module + # for having a LOG attr and just give it back + # its own logger? + func_args = [name, self.cfg, + cc, config.LOG, args] + # Mark it as having started running + which_ran.append(name) + # This name will affect the semaphore name created + run_name = "config-%s" % (name) + + desc = "running %s with frequency %s" % (run_name, freq) + myrep = events.ReportEventStack( + name=run_name, description=desc, parent=self.reporter) + + with myrep: + # 执行模块 + ran, _r = cc.run(run_name, mod.handle, func_args, + freq=freq) + if ran: + myrep.message = "%s ran successfully" % run_name + else: + myrep.message = "%s previously ran" % run_name + +上面的 cc.run(),代码如下: + +.. code:: python + + # helpers.py + def run(self, name, functor, args, freq=None, clear_on_fail=False): + + # 获取 sem 文件。 + sem = self._get_sem(freq) + if not sem: + sem = DummySemaphores() + if not args: + args = [] + + # 判断是否已经执行过(准确说是,是否需要执行),具体逻辑可以参考上面 8.6.8 章节的逻辑。 + if sem.has_run(name, freq): + LOG.debug("%s already ran (freq=%s)", name, freq) + return (False, None) + + # 如果需要执行,就在对应的 sem 目录下生成一个文件(锁) + with sem.lock(name, freq, clear_on_fail) as lk: + if not lk: + raise LockFailure("Failed to acquire lock for %s" % name) + else: + LOG.debug("Running %s using lock (%s)", name, lk) + if isinstance(args, (dict)): + # 然后去执行这个模块 + results = functor(**args) + else: + results = functor(*args) + return (True, results) + +8.6.9 模块执行的频率 +-------------------- + +在 cloudinit 里的各个模块里,都可以配置执行频率。 + +- PER_ALWAYS:总是执行 +- PER_INSTANCE:每个虚拟机实例一次 +- PER_ONCE:每个镜像只执行一次 + +对于 ``PER_INSTANCE`` 和 ``PER_ONCE`` +和区别,从名字和代码实现上看,似乎没有区别,继续看 cloudinit +里的代码,只有一个模块使用了 PER_ONCE ,那就是 +``cc_scripts_per_once.py``\ 。 + +|image16| + +其实他们还是有区别的,由下面的代码来看 + +- ``PER_ONCE`` 获取的 sem 文件是从 ``/var/lib/cloud/`` + 下的文件获取的,这个目录一个镜像只会生成一次(若你不删除的话)。 +- ``PER_INSTANCE`` 获取 sem 文件是从 + ``/var/lib/cloud/instances/`` + 下的文件获取的,这个目录每个虚拟机一个。 + +|image17| + +如果你的模块里已经配置了频率,则以此为准。若没有配置,则默认为 +``PER_INSTANCE``\ 。 + +具体函数:\ ``_run_modules`` + +|image18| + +那么cloudinit 是如何控制模块的执行频率呢? + +通过代码可以发现,其在运行时,会先调用 ``has_run`` +函数,在这里会去取对应目录下(上面的 sem_path)的sem文件,如果有存在 sem +文件,说明已经执行过(返回 False ),如果不存在 sem 文件(返回 True) +说明未执行过。 + +|image19| + +sem 文件是怎样的? + +这是 ``PER_ONCE`` 的 sem 文件: + +|image20| + +这是 ``PER_INSTANCE`` 的 sem 文件\ |image21| + +8.6.10 如何解析网卡配置 +----------------------- + +ubuntu 的网卡配置不是正常我们常见的 json 或者 yaml +格式,若要将其转化成python的字典对象。这在cloudinit是怎么做的呢? + +将这个功能拿出来 ,你可以这样用。 + +.. code:: python + + from cloudinit.net import eni + config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') + +|image22| + +8.6.11 低版本的日志输出 +----------------------- + +如果你用过 centos6.x 的 cloudinit,你会发现其根本不会将日志输出到 +``/var/log/cloud-init.log``\ 中。 + +经过排查,你可以在 ``cloudinit/log.py`` 中按下面的改法修改解决 + +|image23| + +8.6.12 旧版本网络配置的三个巨坑 +------------------------------- + +在不同的 cloudinit 版本中,网络配置部分的代码是不同的。 + +首先要知道,配置网络是在 ``on_first_boot`` 函数里配置的。它是在cloudinit +判断该机器为新虚拟机时才会执行。也就是说,这个网络配置在一个虚拟机生命周期里,只会执行一次,如果要二次执行,需要执行 +``rm -rf /var/lib/cloud`` ,将缓存数据删除,这边才会重新认定为新虚拟机。 + +这里仅以 cloudinit 0.7.5 ( CentOS6.x)的版本为例。 + +在 cloud-init 0.7.5中,网络信息的读取与配置都是在且仅能在 local +阶段进行的,代码如下,只在 dsmode 为 local 时才会执行 on_first_boot。 + +|image24| + +而如果是虚拟机重启的话,是在stages.py +这边判断是否为新虚拟机的,这也和旧版本有所区别。 + |image25| -查看机器里有哪几张网卡 +为了让你能更加清晰的了解这个网络配置过程,我阅读了这块的源代码。 + +在 cloud-init 的比较重要的几个文件有: + +- 入口文件(上面已经说明过了) +- stages.py +- distros/rhel.py +- sources/DataSourceConfigDrive.py + +在网络配置这块,有几个大坑。 + +**坑一** -ls -l /sys/class/net +如果是按照旧虚拟机创建新的快照镜像,然后使用这个镜像创建新的虚拟机,有可能会在同一块网卡上出现新旧两个ip,这是因为虚拟机在启动过程中,会先读取原网络配置配置ip,然后才会运行 +cloud-init 进行新ip的配置,而新ip的配置是使用 ``ifup`` +这个命令\ |image26| + +使用这种方式并不会将第一次配置的旧ip给清除掉。\ |image27| + +这个问题,目前我只在CentOS6 中遇到过。可以通过修改代码让其先 ``ifdown`` +再 ``ifup`` 就可以解决这个问题。\ |image28| + +**坑二** + +如果使用dhcp,cloudinit不会创建或者刷新网卡配置文件,而把配置ip交由 +NetworkManager +,让它自动获取。这是很重要。所以如果你用旧虚拟机创建快照的方式做镜像,然后用这个镜像创建的虚拟机会有旧虚拟机的配置文件会暴露原机器的ip地址。 + +具体的创建逻辑是在这 + +|image29| + +**坑三** + +在 CentOS 6 上,安装NetworkManager 时不会安装完整。 + +会导致两个问题,一个是在启动时,会提示无法加载插件,导致启动失败。 + +:: + + Apr 29 11:13:29 localhost NetworkManager[1365]: Loaded plugin keyfile: (cc + ) 2007 - 2008 Red Hat, Inc. To report bugs please use the NetworkManager mailinn + g list. + Apr 29 11:13:29 localhost NetworkManager[1365]: [1556507609.466522] [maii + n.c:708] main(): failed to initialize the network manager: Could not load pluginn + 'ibft': /usr/lib64/NetworkManager/libnm-settings-plugin-ibft.so: undefined symbb + ol: g_slist_free_full + Apr 29 11:13:29 localhost NetworkManager[1365]: exiting (error) + +一个是会自动DHCP获取到一个以ip命名的hostname,并将原来的覆盖掉。 + +|image30| + +为了避免出现这些情况,请务必保证这些包都安装完整(左为 CentOS 7.2,右为 +CentOS 6.5)。 + +|image31| + +8.6.13 网络是如何启动的?(新版本) +----------------------------------- + +这里仅以 cloudinit 18.5 的版本为例。 + +通过查看代码主流程时,获取ds的时候,有一个参数是 existing,它有两个值: + +- trust:说明 ds 的缓存是可以相信的,不用再去校验 instance_uuid +- check:说明会去校验 instance_uuid,如果相同直接返回 ds + 的缓存,如果不同则返回None,让后的步骤再去从 CD-ROM 读取最新的。 + +在 local 阶段的时候,\ ``existing`` 是 ``check`` ,这是合理的。 + +|image32| + +|image33| + +在local阶段,on_first_boot 的函数 network 是 False +,所以这里也不会写网卡配置文件,自然也不会配置。 + +|image34| + +|image35| + +再往后面看,就可以发现,原来写配置文件的地方是在 ``cmd/main.py`` 里。 + +.. code:: python + + # cmd/main.py + mode = sources.DSMODE_LOCAL if args.local else sources.DSMODE_NETWORK + + # 中间省略多行代码 ... + init.apply_network_config(bring_up=bool(mode != sources.DSMODE_LOCAL)) + +第一行,如果是 local 阶段,mode 为 local,bring_up 为 +False,意思是只写配置文件,而不启用网卡。 + +等到 init 阶段时,mode 为 net +(\ ``sources.DSMODE_NETWORK``\ )时,bring_up 为 +True,意思是会启用网卡,配置ip。 + +网卡网卡的主要函数在下面 ``apply_network_config`` +这个函数里,这个函数主要做三个事情: + +1. 获取网卡配置 +2. 校正网卡名,以 ConfigDrive 的配置为准 +3. 将ip信息写入配置文件,并启动网卡 + +|image36| + +那它是如何对网卡进行重命令的呢?看了代码,其实是用ip命令实现的。 + +|image37| + +提取出来其实就三条命令,要注意的是,这三条命令执行是有顺序的 + +:: + + 1. ip link set ens3 down + 2. ip link set ens3 name eth0 + 3. ip link set eth0 up + +还有一点要说的是,cloudinit 是如何取到本机真实的网卡信息的呢?他是从 +``/sys/class/net/`` +目录下获取的。每个网卡一个目录,每个目录下都有相应的文件记录相应的信息,比如 +``/sys/class/net/ens3/address`` 记录的是网卡的 mac 地址。 + +|image38| + +接下来就要开始配置网络了,先写网络配置文件,再根据参数选择是否启用网络。 + +|image39| + +如果是重启虚拟机或者 init 阶段进入这里呢,会不会又重复配置网络了呢? + +答案是:不会的。 + +cloudinit 会根据缓存中的虚拟机的uuid来与ConfigDrive +的对比,如果不一样,则认为这台虚拟机是新创建的虚拟机,只有新的虚拟机才会走入这里去配置网络。 + +|image40| + +那问题又来了,虽然上面有个 bring_up 的参数,实际上,通过代码可以发现,在 +local 阶段,bring_up 为 False 不会去启用网卡,而在 init 阶段呢,虽然 +bring_up 为 True,但是此时,经过 local +阶段后,代码逻辑会认为这是台旧虚拟机,不会再走后面配置网络的函数,那就很奇怪了,网卡的ip是如何配置上的呢? + +这是个好问题,也是个很难察觉的点。 + +大多数人,可能不知道linux内部的服务是有启动顺序的。在这种情况下,我们必须保证,cloudinit +写入配置的服务(cloud-init-local)必须在 network 或者 Network-Manager +的服务之前。 + +这样在 cloud-init-local 执行完后,就会自动配置上网络了。 + +8.6.14 在虚拟机启动时执行命令 +----------------------------- + +cloudinit 允许通过 user_data 指定你想在虚拟机启动时,执行的命令。 + +:: + + # 会将这些命令写入如下文件,但是并没有执行 + # + runcmd: + - [ sed, -i, -e, '%s/x/y/g', some_file] + - echo "modified some_file" + - [cat, some_file] + +当你进行了如此配置后,cloudinit 会通过 runcmd +模块,将这些命令组合起来写入 +``/var/lib/cloud/instances//scripts/runcmd``\ 。 + +但这仅仅只是写入,若要执行这些命令,还需要你在 /etc/cloud/cloud.cfg +中配置 ``scripts_user``\ ,这样 cloud-init 才会去执行它。 + +8.6.15 相关命令 +--------------- + +.. code:: shell -只查看 ipv4 或 ipv6的网卡ip + # 查看机器里有哪几张网卡 + ls -l /sys/class/net -ip -6 addr show + # 只查看 ipv4 或 ipv6的网卡ip + ip -6 addr show + ip -4 addr show -ip -4 addr show + # 网络启动不来,先 flush 试一下 + ip addr flush dev ens3 -------------- @@ -461,16 +723,31 @@ ip -4 addr show .. |image11| image:: http://image.python-online.cn/FpqcyL4hWwpaAGzsdreQwXvH4Rx8 .. |image12| image:: http://image.python-online.cn/20190430230839.png .. |image13| image:: http://image.python-online.cn/20190430231108.png -.. |image14| image:: http://image.python-online.cn/20190429104357.png -.. |image15| image:: http://image.python-online.cn/20190829141059.png -.. |image16| image:: http://image.python-online.cn/20190829113537.png -.. |image17| image:: http://image.python-online.cn/20190829141059.png -.. |image18| image:: http://image.python-online.cn/Fp1TeHSiIMIQoZygbW9VSfAagB_d -.. |image19| image:: http://image.python-online.cn/Fh-5SQ8qYjhJEKovI6LmIpabSy2c -.. |image20| image:: http://image.python-online.cn/20190430231812.png -.. |image21| image:: http://image.python-online.cn/20190430232309.png -.. |image22| image:: http://image.python-online.cn/20190429205735.png -.. |image23| image:: http://image.python-online.cn/20190430232911.png -.. |image24| image:: http://image.python-online.cn/20190623091911.png -.. |image25| image:: http://image.python-online.cn/20190708175813.png +.. |image14| image:: http://image.python-online.cn/20190623091911.png +.. |image15| image:: http://image.python-online.cn/20190708175813.png +.. |image16| image:: http://image.python-online.cn/20190910160035.png +.. |image17| image:: http://image.python-online.cn/20190910150305.png +.. |image18| image:: http://image.python-online.cn/20190910142222.png +.. |image19| image:: http://image.python-online.cn/20190910153637.png +.. |image20| image:: http://image.python-online.cn/20190910171359.png +.. |image21| image:: http://image.python-online.cn/20190910171538.png +.. |image22| image:: http://image.python-online.cn/20190906091102.png +.. |image23| image:: http://image.python-online.cn/20190909172153.png +.. |image24| image:: http://image.python-online.cn/20190429104357.png +.. |image25| image:: http://image.python-online.cn/20190829141059.png +.. |image26| image:: http://image.python-online.cn/Fp1TeHSiIMIQoZygbW9VSfAagB_d +.. |image27| image:: http://image.python-online.cn/Fh-5SQ8qYjhJEKovI6LmIpabSy2c +.. |image28| image:: http://image.python-online.cn/20190430231812.png +.. |image29| image:: http://image.python-online.cn/20190430232309.png +.. |image30| image:: http://image.python-online.cn/20190429205735.png +.. |image31| image:: http://image.python-online.cn/20190430232911.png +.. |image32| image:: http://image.python-online.cn/20190911175423.png +.. |image33| image:: http://image.python-online.cn/20190911174648.png +.. |image34| image:: http://image.python-online.cn/20190911173615.png +.. |image35| image:: http://image.python-online.cn/20190911195024.png +.. |image36| image:: http://image.python-online.cn/20190911202425.png +.. |image37| image:: http://image.python-online.cn/20190911202551.png +.. |image38| image:: http://image.python-online.cn/20190911203953.png +.. |image39| image:: http://image.python-online.cn/20190911204805.png +.. |image40| image:: http://image.python-online.cn/20190911205518.png diff --git a/source/c08/c08_13.rst b/source/c08/c08_13.rst index f770980..c882955 100644 --- a/source/c08/c08_13.rst +++ b/source/c08/c08_13.rst @@ -257,6 +257,25 @@ cache里没有这个ip,就会重新发送arp广播,获取到正确的mac地 # 指定文件设置多个arp条目 arp -f /etc/ethers +8.13.3 ovs 流表 +--------------- + +使用 ``ovs-ofctl dump-flows br-int`` 可以查看从 br-int 到 br0-ovs +的流表。 + +查看 ``0x0000/0x1fff`` 这一行后的 ``actions=mod_vlan_vid:4`` ,其中的 +``4`` 是vlan id,意思是从虚拟机的网卡出来的包如果tag=4,在经过 br0-ovs +的时候,就会把 tag 去掉,不会被过滤掉,使其能把包发向公网。 + +假如不设置tag,br-int 上的包就不会流往 br0-ovs。 + +假如你的虚拟机是连在 br-int 上,而且没有tag,那么需要你手动加tag + +:: + + virsh domiflist vm_domain + ovs-vsctl set port vnet0 tag=4 + -------------- .. figure:: http://image.python-online.cn/20190511161447.png diff --git a/source/c08/c08_14.rst b/source/c08/c08_14.rst index d0bd8d2..1094051 100644 --- a/source/c08/c08_14.rst +++ b/source/c08/c08_14.rst @@ -291,22 +291,35 @@ centos 6.x 配置网络是在 on_first_boot 函数里,这是 local |image12| -ubuntu 解析网卡配置 +8.14.5 ubuntu禁用ipv6 +--------------------- -.. code:: python - - from cloudinit.net import eni - config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') +如果你给你的ubuntu配置上了ipv6的ip,那当你使用 apt-get install +软件包的时候,会使用ipv6去源安装,这将会使你的安装过程卡住: |image13| -8.14.5 其他命令记录 -------------------- +如何解决这个问题呢? + +参考文章:https://ubuntuqa.com/article/1577.html + +我使用的方法是 + +在终端中运行以下命令禁用IPv6,\ ``0``\ 表示已启用,而\ ``1``\ 表示已禁用。 + +:: + + echo 1>/proc/sys/net/ipv6/conf/all/disable_ipv6 + +然后在终端内禁用IPv6,需要再输入以下内容: :: - # 网络启动不来,先 flush 试一下 - ip addr flush dev ens3 + echo "#disable ipv6" | sudo tee -a /etc/sysctl.conf + echo "net.ipv6.conf.all.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf + echo "net.ipv6.conf.default.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf + echo "net.ipv6.conf.lo.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf + sudo sysctl -p -------------- @@ -327,5 +340,5 @@ ubuntu 解析网卡配置 .. |image10| image:: http://image.python-online.cn/20190829112446.png .. |image11| image:: http://image.python-online.cn/20190829111917.png .. |image12| image:: http://image.python-online.cn/20190829161243.png -.. |image13| image:: http://image.python-online.cn/20190906091102.png +.. |image13| image:: http://image.python-online.cn/20190926171038.png From ba017c6769e2a36b31298d5c5cd2e3583f2330a7 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Tue, 8 Oct 2019 21:07:28 +0800 Subject: [PATCH 106/302] update --- source/c08/c08_05.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 8e6eea4..5196f37 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -339,6 +339,39 @@ nova-api 返回的结果令人无法理解: ![](http://image.python-online.cn/20190526204328.png) +## 8.5.12 往 spec_obj 里添加对象 + +在 nova-scheduler 里的 过滤器里,有俩非常重要的对象。 + +- host_state:包含每台 host 的所有信息 +- spec_obj:包含创建虚拟机请求的所有信息 + +![](http://image.python-online.cn/20191008173211.png) + +有时候,spec_obj 里并没有我们想要的信息(比如虚拟机的 metadata),这时候,我们就要手动添加。 + +这里我以 metadata 为例,来讲一下这个添加过程。 + +首先第一点要清楚的是,spec_obj 其实是 RequestSpec 对象:nova/objects/request_spec.py,所以下面的修改都是在这个文件里进行。 + +要往这个对象加属性,第一步是要定义这个字段。 + +![](http://image.python-online.cn/20191008174046.png) + +往 instance_fields 追加属性名 + +![](http://image.python-online.cn/20191008210127.png) + +最后在 `from_primitives` 这个函数里,把这个新属性赋值一下。 + +```python +spec.metadata = request_spec['instance_properties'].get('metadata', {}) +``` + + + + + ## 8.5.13 支持指定子网和指定ip 在 nova-api 接收请求处。 From 2f57552ebc8bb251a7db3cb000e159c12d9e359f Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Thu, 10 Oct 2019 09:04:38 +0800 Subject: [PATCH 107/302] update --- source/c08/c08_05.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 5196f37..5666422 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -358,11 +358,11 @@ nova-api 返回的结果令人无法理解: ![](http://image.python-online.cn/20191008174046.png) -往 instance_fields 追加属性名 +往 instance_fields 追加属性名,完了后,这个属性会出现在 `request_spec['instance_properties']` 里 ![](http://image.python-online.cn/20191008210127.png) -最后在 `from_primitives` 这个函数里,把这个新属性赋值一下。 +最后在 `from_primitives` 这个函数里,把这个新属性赋值给 request_spec 对象。 ```python spec.metadata = request_spec['instance_properties'].get('metadata', {}) From 48526cc15ca4321c6e5179083b163541032d741e Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Thu, 10 Oct 2019 23:53:59 +0800 Subject: [PATCH 108/302] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=86=B7=E7=9F=A5?= =?UTF-8?q?=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c01/c01_10.md | 128 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 24 deletions(-) diff --git a/source/c01/c01_10.md b/source/c01/c01_10.md index 1056def..6c6a4a0 100644 --- a/source/c01/c01_10.md +++ b/source/c01/c01_10.md @@ -1009,28 +1009,7 @@ b ['a', 'b'] ``` -## 33. 更新字典 - -通常我们更新字典的方式是这样的 - -```python ->>> dict1 = {'age':24} ->>> dict1.update({'age':30}) ->>> dict1 -{'age': 30} -``` - -除此之外还有另外一种方式,你可能比较陌生 - -``` ->>> dict1 = {'namne':'ming', 'gender':'male', 'age':24} ->>> dict2 = {'namne':'jane', 'gender':'male'} ->>> dict3 = {**dict1, **dict2} ->>> dict3 -{'namne': 'jane', 'gender': 'male', 'age': 24} -``` - -## 34. 嵌套上下文管理的另类写法 +## 33. 嵌套上下文管理的另类写法 当我们要写一个嵌套的上下文管理器时,可能会这样写 @@ -1067,7 +1046,7 @@ with test_context('aaa'), test_context('bbb'): print('========== in main ============') ``` -## 35. += 不等同于=+ +## 34. += 不等同于=+ 对列表 进行`+=` 操作相当于 extend,而使用 `=+` 操作是新增了一个列表。 @@ -1096,7 +1075,7 @@ with test_context('aaa'), test_context('bbb'): -## 36. Python 里也可以有 end +## 35. Python 里也可以有 end 有不少编程语言,循环、判断代码块需要用 end 标明结束,这样一定程序上会使代码逻辑更加清晰一点,其实这种语法在 Python 里并没有必要,但如果你想用,也不是没有办法,具体你看下面这个例子。 @@ -1104,6 +1083,107 @@ with test_context('aaa'), test_context('bbb'): +## 36. 花样更新字典的技巧 + +常规的更新字典的方式是这样的 + +```python +>>> profile={'name': 'wangbm'} +>>> extra={'age': 25, 'gender': 'male'} +>>> profile.update(extra) +>>> profile +{'gender': 'male', 'age': 25, 'name': 'wangbm'} +``` + +今天明哥来集中讲下字典的更新方式 + +```python +>>> profile={'name': 'wangbm'} +>>> extra=[('age', 25), ('gender', 'male')] +>>> profile.update(extra) +>>> profile +{'gender': 'male', 'age': 25, 'name': 'wangbm'} +``` + +如果你见过上面这种,那接下来这种我保证大部分人都没用过 + +```python +>>> profile={'name': 'wangbm'} +>>> profile.update(age=25, gender='male') +>>> profile +{'gender': 'male', 'age': 25, 'name': 'wangbm'} +``` + +## 37. 被低估的 print + +print 很多人只用来执行简单的打印功能。 + +print 作为一个函数,本身也附有各种各样的参数,只是很多人不知道。 + +你一定知道 join 这种很高效的字符串拼接方式。 + +```python +>>> alist = ['a', 'b', 'c'] +>>> ','.join(alist) +'a,b,c' +``` + +当如果 alist 里有非字符串的元素时,join 就会抛错。 + +```python +>>> alist = ['a', 'b', 3] +>>> ','.join(alist) +Traceback (most recent call last): + File "", line 1, in +TypeError: sequence item 2: expected str instance, int found +``` + +如果要解决这个问题,可能要提前将数值转化成字符串类型。 + +```python +>>> alist = ['a', 'b', 3] +>>> blist = [str(i) for i in alist] +>>> blist +['a', 'b', '3'] +>>> ','.join(blist) +'a,b,3' +``` + +这里再介绍一种更简洁的方法,就是使用 print 来实现,但这种有局限,好像只能在命令行模式下使用,毕竟 print 标准输出,无法进行赋值。 + +下面我使用 `_` 来获取上一次的返回值,再赋值给 blist。 + +```python +>>> print(*alist, sep=',') +a,b,3 +>>> blist = _ +>>> blist +'a,b,3' +``` + +既然说到了 print,那么再说一点print 的神技巧。 + +很多初学者,喜欢使用 print 来进行调试追踪。 + +一点不好的地方是,普通的 print 是输出到终端屏幕,而不能将保持在文件中。 + +其实 print 是支持将输出重定向到文件中的,不过说实话,个人感觉还不如使用 logging 模块呢,或者直接 f.write()。 + +```python +>>> with open('test.log', mode='w') as f: +... print('hello, python', file=f, flush=True) +>>> exit() + +$ cat test.log +hello, python +``` + + + +以上两点,学习自董伟明的文章:[一些你不知道的Python Tips](https://mp.weixin.qq.com/s/KTLRwzCM7lOvBF4IEGuBPg) + + + ## 附录:参考文章 - [wtfpython](https://github.com/satwikkansal/wtfpython) From 2c31e101823067c18900f30c67a04af078b685b2 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 14 Oct 2019 22:34:32 +0800 Subject: [PATCH 109/302] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/c04/c04_03.md | 22 ++++++++++++++++++++++ source/c08/c08_12.md | 8 ++++++++ source/conf.py | 6 +++++- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/source/c04/c04_03.md b/source/c04/c04_03.md index 1b1e6a3..9189725 100644 --- a/source/c04/c04_03.md +++ b/source/c04/c04_03.md @@ -266,6 +266,28 @@ pandoc -V mainfont="SimSun" -f markdown -t rst hello.md -o hello.rst 到这里,属于你的个人博客就搭建好了,快去试一下吧。 最后,整个项目的源码和模块包我都放在公众号(`Python编程时光`)后台,请关注后,回复「`Sphinx`」领取。 + + +## 4.3.7 自定义js + +将js文件放在 `source/_static/js/` 目录下,然后修改 conf.py + +```python +html_static_path = ['_static'] + +html_js_files = [ + 'css/readmore.js', +] +``` + +由于这个功能只在 sphinx 1.8 + 版本中才支持,所以你要确定你的版本。若低于这个版本,可以用Python 3.5(Python 2.7 不支持了)中新建一个虚拟环境,然后安装最新的 sphinx 包。 + +```shell +pip install Sphinx sphinx-rtd-theme -i https://pypi.douban.com/simple +``` + +但是这个功能添加的 js 都在header里,并不能添加到最后面。 + ## 附录:参考文档 - [Sphinx配置MarkDown解析](http://www.sphinx-doc.org/en/master/usage/markdown.html) diff --git a/source/c08/c08_12.md b/source/c08/c08_12.md index 2f25dfc..cab287c 100644 --- a/source/c08/c08_12.md +++ b/source/c08/c08_12.md @@ -10,6 +10,8 @@ 经过以上两个过程后,nova-scheduler 会将选到的主机返回给 nova-conductor,这时候 nova-conductor 才会去调用 nova-compute 去进行真正的创建过程。 +## 8.12.1 过滤器和称重器 + 接下来,我会从源码的角度来分析一下这个过程。 从源代码中看,最开始是 nova-conductor (nova/conductor/manager.py)在给 nova-compute 发创建请求前,会先让 nova-scheduler 选出一台资源充足的计算节点。 @@ -58,6 +60,12 @@ LOG.debug("Weighed %(hosts)s", {'hosts': weighed_hosts}) LOG.debug("Selected host: %(host)s", {'host': chosen_host}) ``` +## 8.12.2 指定宿主机创建 + +当指定宿主机进行虚拟机的创建后,以上所有的过滤器都会无效(不会走代码)。 + +![](http://image.python-online.cn/20191011103832.png) + --- ![关注公众号,获取最新干货!](http://image.python-online.cn/20190511161447.png) \ No newline at end of file diff --git a/source/conf.py b/source/conf.py index 64e5361..40ebc20 100755 --- a/source/conf.py +++ b/source/conf.py @@ -62,7 +62,7 @@ html_theme = 'default' -#html_static_path = ['_static'] +html_static_path = ['_static'] # Output file base name for HTML help builder. htmlhelp_basename = 'Python-Time BLOG' @@ -137,3 +137,7 @@ _exts = "../exts" sys.path.append(os.path.abspath(_exts)) + +html_js_files = [ + 'js/readmore.js', +] \ No newline at end of file From facd1c694aba1b0c57133748ec78633d3d3a7812 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Mon, 14 Oct 2019 22:35:02 +0800 Subject: [PATCH 110/302] =?UTF-8?q?=E6=B7=BB=E5=8A=A0readmore.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/_static/js/readmore.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 source/_static/js/readmore.js diff --git a/source/_static/js/readmore.js b/source/_static/js/readmore.js new file mode 100644 index 0000000..1168735 --- /dev/null +++ b/source/_static/js/readmore.js @@ -0,0 +1,12 @@ +!function (e, t) { "use strict"; "object" == typeof module && "object" == typeof module.exports ? module.exports = e.document ? t(e, !0) : function (e) { if (!e.document) throw new Error("jQuery requires a window with a document"); return t(e) } : t(e) }("undefined" != typeof window ? window : this, function (C, e) { "use strict"; var t = [], E = C.document, r = Object.getPrototypeOf, s = t.slice, g = t.concat, u = t.push, i = t.indexOf, n = {}, o = n.toString, v = n.hasOwnProperty, a = v.toString, l = a.call(Object), y = {}, m = function (e) { return "function" == typeof e && "number" != typeof e.nodeType }, x = function (e) { return null != e && e === e.window }, c = { type: !0, src: !0, nonce: !0, noModule: !0 }; function b(e, t, n) { var r, i, o = (n = n || E).createElement("script"); if (o.text = e, t) for (r in c) (i = t[r] || t.getAttribute && t.getAttribute(r)) && o.setAttribute(r, i); n.head.appendChild(o).parentNode.removeChild(o) } function w(e) { return null == e ? e + "" : "object" == typeof e || "function" == typeof e ? n[o.call(e)] || "object" : typeof e } var f = "3.4.1", k = function (e, t) { return new k.fn.init(e, t) }, p = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; function d(e) { var t = !!e && "length" in e && e.length, n = w(e); return !m(e) && !x(e) && ("array" === n || 0 === t || "number" == typeof t && 0 < t && t - 1 in e) } k.fn = k.prototype = { jquery: f, constructor: k, length: 0, toArray: function () { return s.call(this) }, get: function (e) { return null == e ? s.call(this) : e < 0 ? this[e + this.length] : this[e] }, pushStack: function (e) { var t = k.merge(this.constructor(), e); return t.prevObject = this, t }, each: function (e) { return k.each(this, e) }, map: function (n) { return this.pushStack(k.map(this, function (e, t) { return n.call(e, t, e) })) }, slice: function () { return this.pushStack(s.apply(this, arguments)) }, first: function () { return this.eq(0) }, last: function () { return this.eq(-1) }, eq: function (e) { var t = this.length, n = +e + (e < 0 ? t : 0); return this.pushStack(0 <= n && n < t ? [this[n]] : []) }, end: function () { return this.prevObject || this.constructor() }, push: u, sort: t.sort, splice: t.splice }, k.extend = k.fn.extend = function () { var e, t, n, r, i, o, a = arguments[0] || {}, s = 1, u = arguments.length, l = !1; for ("boolean" == typeof a && (l = a, a = arguments[s] || {}, s++), "object" == typeof a || m(a) || (a = {}), s === u && (a = this, s--); s < u; s++)if (null != (e = arguments[s])) for (t in e) r = e[t], "__proto__" !== t && a !== r && (l && r && (k.isPlainObject(r) || (i = Array.isArray(r))) ? (n = a[t], o = i && !Array.isArray(n) ? [] : i || k.isPlainObject(n) ? n : {}, i = !1, a[t] = k.extend(l, o, r)) : void 0 !== r && (a[t] = r)); return a }, k.extend({ expando: "jQuery" + (f + Math.random()).replace(/\D/g, ""), isReady: !0, error: function (e) { throw new Error(e) }, noop: function () { }, isPlainObject: function (e) { var t, n; return !(!e || "[object Object]" !== o.call(e)) && (!(t = r(e)) || "function" == typeof (n = v.call(t, "constructor") && t.constructor) && a.call(n) === l) }, isEmptyObject: function (e) { var t; for (t in e) return !1; return !0 }, globalEval: function (e, t) { b(e, { nonce: t && t.nonce }) }, each: function (e, t) { var n, r = 0; if (d(e)) { for (n = e.length; r < n; r++)if (!1 === t.call(e[r], r, e[r])) break } else for (r in e) if (!1 === t.call(e[r], r, e[r])) break; return e }, trim: function (e) { return null == e ? "" : (e + "").replace(p, "") }, makeArray: function (e, t) { var n = t || []; return null != e && (d(Object(e)) ? k.merge(n, "string" == typeof e ? [e] : e) : u.call(n, e)), n }, inArray: function (e, t, n) { return null == t ? -1 : i.call(t, e, n) }, merge: function (e, t) { for (var n = +t.length, r = 0, i = e.length; r < n; r++)e[i++] = t[r]; return e.length = i, e }, grep: function (e, t, n) { for (var r = [], i = 0, o = e.length, a = !n; i < o; i++)!t(e[i], i) !== a && r.push(e[i]); return r }, map: function (e, t, n) { var r, i, o = 0, a = []; if (d(e)) for (r = e.length; o < r; o++)null != (i = t(e[o], o, n)) && a.push(i); else for (o in e) null != (i = t(e[o], o, n)) && a.push(i); return g.apply([], a) }, guid: 1, support: y }), "function" == typeof Symbol && (k.fn[Symbol.iterator] = t[Symbol.iterator]), k.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "), function (e, t) { n["[object " + t + "]"] = t.toLowerCase() }); var h = function (n) { var e, d, b, o, i, h, f, g, w, u, l, T, C, a, E, v, s, c, y, k = "sizzle" + 1 * new Date, m = n.document, S = 0, r = 0, p = ue(), x = ue(), N = ue(), A = ue(), D = function (e, t) { return e === t && (l = !0), 0 }, j = {}.hasOwnProperty, t = [], q = t.pop, L = t.push, H = t.push, O = t.slice, P = function (e, t) { for (var n = 0, r = e.length; n < r; n++)if (e[n] === t) return n; return -1 }, R = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", M = "[\\x20\\t\\r\\n\\f]", I = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", W = "\\[" + M + "*(" + I + ")(?:" + M + "*([*^$|!~]?=)" + M + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + I + "))|)" + M + "*\\]", $ = ":(" + I + ")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|" + W + ")*)|.*)\\)|)", F = new RegExp(M + "+", "g"), B = new RegExp("^" + M + "+|((?:^|[^\\\\])(?:\\\\.)*)" + M + "+$", "g"), _ = new RegExp("^" + M + "*," + M + "*"), z = new RegExp("^" + M + "*([>+~]|" + M + ")" + M + "*"), U = new RegExp(M + "|>"), X = new RegExp($), V = new RegExp("^" + I + "$"), G = { ID: new RegExp("^#(" + I + ")"), CLASS: new RegExp("^\\.(" + I + ")"), TAG: new RegExp("^(" + I + "|[*])"), ATTR: new RegExp("^" + W), PSEUDO: new RegExp("^" + $), CHILD: new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + M + "*(even|odd|(([+-]|)(\\d*)n|)" + M + "*(?:([+-]|)" + M + "*(\\d+)|))" + M + "*\\)|)", "i"), bool: new RegExp("^(?:" + R + ")$", "i"), needsContext: new RegExp("^" + M + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + M + "*((?:-\\d)?\\d*)" + M + "*\\)|)(?=[^-]|$)", "i") }, Y = /HTML$/i, Q = /^(?:input|select|textarea|button)$/i, J = /^h\d$/i, K = /^[^{]+\{\s*\[native \w/, Z = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, ee = /[+~]/, te = new RegExp("\\\\([\\da-f]{1,6}" + M + "?|(" + M + ")|.)", "ig"), ne = function (e, t, n) { var r = "0x" + t - 65536; return r != r || n ? t : r < 0 ? String.fromCharCode(r + 65536) : String.fromCharCode(r >> 10 | 55296, 1023 & r | 56320) }, re = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, ie = function (e, t) { return t ? "\0" === e ? "\ufffd" : e.slice(0, -1) + "\\" + e.charCodeAt(e.length - 1).toString(16) + " " : "\\" + e }, oe = function () { T() }, ae = be(function (e) { return !0 === e.disabled && "fieldset" === e.nodeName.toLowerCase() }, { dir: "parentNode", next: "legend" }); try { H.apply(t = O.call(m.childNodes), m.childNodes), t[m.childNodes.length].nodeType } catch (e) { H = { apply: t.length ? function (e, t) { L.apply(e, O.call(t)) } : function (e, t) { var n = e.length, r = 0; while (e[n++] = t[r++]); e.length = n - 1 } } } function se(t, e, n, r) { var i, o, a, s, u, l, c, f = e && e.ownerDocument, p = e ? e.nodeType : 9; if (n = n || [], "string" != typeof t || !t || 1 !== p && 9 !== p && 11 !== p) return n; if (!r && ((e ? e.ownerDocument || e : m) !== C && T(e), e = e || C, E)) { if (11 !== p && (u = Z.exec(t))) if (i = u[1]) { if (9 === p) { if (!(a = e.getElementById(i))) return n; if (a.id === i) return n.push(a), n } else if (f && (a = f.getElementById(i)) && y(e, a) && a.id === i) return n.push(a), n } else { if (u[2]) return H.apply(n, e.getElementsByTagName(t)), n; if ((i = u[3]) && d.getElementsByClassName && e.getElementsByClassName) return H.apply(n, e.getElementsByClassName(i)), n } if (d.qsa && !A[t + " "] && (!v || !v.test(t)) && (1 !== p || "object" !== e.nodeName.toLowerCase())) { if (c = t, f = e, 1 === p && U.test(t)) { (s = e.getAttribute("id")) ? s = s.replace(re, ie) : e.setAttribute("id", s = k), o = (l = h(t)).length; while (o--) l[o] = "#" + s + " " + xe(l[o]); c = l.join(","), f = ee.test(t) && ye(e.parentNode) || e } try { return H.apply(n, f.querySelectorAll(c)), n } catch (e) { A(t, !0) } finally { s === k && e.removeAttribute("id") } } } return g(t.replace(B, "$1"), e, n, r) } function ue() { var r = []; return function e(t, n) { return r.push(t + " ") > b.cacheLength && delete e[r.shift()], e[t + " "] = n } } function le(e) { return e[k] = !0, e } function ce(e) { var t = C.createElement("fieldset"); try { return !!e(t) } catch (e) { return !1 } finally { t.parentNode && t.parentNode.removeChild(t), t = null } } function fe(e, t) { var n = e.split("|"), r = n.length; while (r--) b.attrHandle[n[r]] = t } function pe(e, t) { var n = t && e, r = n && 1 === e.nodeType && 1 === t.nodeType && e.sourceIndex - t.sourceIndex; if (r) return r; if (n) while (n = n.nextSibling) if (n === t) return -1; return e ? 1 : -1 } function de(t) { return function (e) { return "input" === e.nodeName.toLowerCase() && e.type === t } } function he(n) { return function (e) { var t = e.nodeName.toLowerCase(); return ("input" === t || "button" === t) && e.type === n } } function ge(t) { return function (e) { return "form" in e ? e.parentNode && !1 === e.disabled ? "label" in e ? "label" in e.parentNode ? e.parentNode.disabled === t : e.disabled === t : e.isDisabled === t || e.isDisabled !== !t && ae(e) === t : e.disabled === t : "label" in e && e.disabled === t } } function ve(a) { return le(function (o) { return o = +o, le(function (e, t) { var n, r = a([], e.length, o), i = r.length; while (i--) e[n = r[i]] && (e[n] = !(t[n] = e[n])) }) }) } function ye(e) { return e && "undefined" != typeof e.getElementsByTagName && e } for (e in d = se.support = {}, i = se.isXML = function (e) { var t = e.namespaceURI, n = (e.ownerDocument || e).documentElement; return !Y.test(t || n && n.nodeName || "HTML") }, T = se.setDocument = function (e) { var t, n, r = e ? e.ownerDocument || e : m; return r !== C && 9 === r.nodeType && r.documentElement && (a = (C = r).documentElement, E = !i(C), m !== C && (n = C.defaultView) && n.top !== n && (n.addEventListener ? n.addEventListener("unload", oe, !1) : n.attachEvent && n.attachEvent("onunload", oe)), d.attributes = ce(function (e) { return e.className = "i", !e.getAttribute("className") }), d.getElementsByTagName = ce(function (e) { return e.appendChild(C.createComment("")), !e.getElementsByTagName("*").length }), d.getElementsByClassName = K.test(C.getElementsByClassName), d.getById = ce(function (e) { return a.appendChild(e).id = k, !C.getElementsByName || !C.getElementsByName(k).length }), d.getById ? (b.filter.ID = function (e) { var t = e.replace(te, ne); return function (e) { return e.getAttribute("id") === t } }, b.find.ID = function (e, t) { if ("undefined" != typeof t.getElementById && E) { var n = t.getElementById(e); return n ? [n] : [] } }) : (b.filter.ID = function (e) { var n = e.replace(te, ne); return function (e) { var t = "undefined" != typeof e.getAttributeNode && e.getAttributeNode("id"); return t && t.value === n } }, b.find.ID = function (e, t) { if ("undefined" != typeof t.getElementById && E) { var n, r, i, o = t.getElementById(e); if (o) { if ((n = o.getAttributeNode("id")) && n.value === e) return [o]; i = t.getElementsByName(e), r = 0; while (o = i[r++]) if ((n = o.getAttributeNode("id")) && n.value === e) return [o] } return [] } }), b.find.TAG = d.getElementsByTagName ? function (e, t) { return "undefined" != typeof t.getElementsByTagName ? t.getElementsByTagName(e) : d.qsa ? t.querySelectorAll(e) : void 0 } : function (e, t) { var n, r = [], i = 0, o = t.getElementsByTagName(e); if ("*" === e) { while (n = o[i++]) 1 === n.nodeType && r.push(n); return r } return o }, b.find.CLASS = d.getElementsByClassName && function (e, t) { if ("undefined" != typeof t.getElementsByClassName && E) return t.getElementsByClassName(e) }, s = [], v = [], (d.qsa = K.test(C.querySelectorAll)) && (ce(function (e) { a.appendChild(e).innerHTML = "", e.querySelectorAll("[msallowcapture^='']").length && v.push("[*^$]=" + M + "*(?:''|\"\")"), e.querySelectorAll("[selected]").length || v.push("\\[" + M + "*(?:value|" + R + ")"), e.querySelectorAll("[id~=" + k + "-]").length || v.push("~="), e.querySelectorAll(":checked").length || v.push(":checked"), e.querySelectorAll("a#" + k + "+*").length || v.push(".#.+[+~]") }), ce(function (e) { e.innerHTML = ""; var t = C.createElement("input"); t.setAttribute("type", "hidden"), e.appendChild(t).setAttribute("name", "D"), e.querySelectorAll("[name=d]").length && v.push("name" + M + "*[*^$|!~]?="), 2 !== e.querySelectorAll(":enabled").length && v.push(":enabled", ":disabled"), a.appendChild(e).disabled = !0, 2 !== e.querySelectorAll(":disabled").length && v.push(":enabled", ":disabled"), e.querySelectorAll("*,:x"), v.push(",.*:") })), (d.matchesSelector = K.test(c = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.oMatchesSelector || a.msMatchesSelector)) && ce(function (e) { d.disconnectedMatch = c.call(e, "*"), c.call(e, "[s!='']:x"), s.push("!=", $) }), v = v.length && new RegExp(v.join("|")), s = s.length && new RegExp(s.join("|")), t = K.test(a.compareDocumentPosition), y = t || K.test(a.contains) ? function (e, t) { var n = 9 === e.nodeType ? e.documentElement : e, r = t && t.parentNode; return e === r || !(!r || 1 !== r.nodeType || !(n.contains ? n.contains(r) : e.compareDocumentPosition && 16 & e.compareDocumentPosition(r))) } : function (e, t) { if (t) while (t = t.parentNode) if (t === e) return !0; return !1 }, D = t ? function (e, t) { if (e === t) return l = !0, 0; var n = !e.compareDocumentPosition - !t.compareDocumentPosition; return n || (1 & (n = (e.ownerDocument || e) === (t.ownerDocument || t) ? e.compareDocumentPosition(t) : 1) || !d.sortDetached && t.compareDocumentPosition(e) === n ? e === C || e.ownerDocument === m && y(m, e) ? -1 : t === C || t.ownerDocument === m && y(m, t) ? 1 : u ? P(u, e) - P(u, t) : 0 : 4 & n ? -1 : 1) } : function (e, t) { if (e === t) return l = !0, 0; var n, r = 0, i = e.parentNode, o = t.parentNode, a = [e], s = [t]; if (!i || !o) return e === C ? -1 : t === C ? 1 : i ? -1 : o ? 1 : u ? P(u, e) - P(u, t) : 0; if (i === o) return pe(e, t); n = e; while (n = n.parentNode) a.unshift(n); n = t; while (n = n.parentNode) s.unshift(n); while (a[r] === s[r]) r++; return r ? pe(a[r], s[r]) : a[r] === m ? -1 : s[r] === m ? 1 : 0 }), C }, se.matches = function (e, t) { return se(e, null, null, t) }, se.matchesSelector = function (e, t) { if ((e.ownerDocument || e) !== C && T(e), d.matchesSelector && E && !A[t + " "] && (!s || !s.test(t)) && (!v || !v.test(t))) try { var n = c.call(e, t); if (n || d.disconnectedMatch || e.document && 11 !== e.document.nodeType) return n } catch (e) { A(t, !0) } return 0 < se(t, C, null, [e]).length }, se.contains = function (e, t) { return (e.ownerDocument || e) !== C && T(e), y(e, t) }, se.attr = function (e, t) { (e.ownerDocument || e) !== C && T(e); var n = b.attrHandle[t.toLowerCase()], r = n && j.call(b.attrHandle, t.toLowerCase()) ? n(e, t, !E) : void 0; return void 0 !== r ? r : d.attributes || !E ? e.getAttribute(t) : (r = e.getAttributeNode(t)) && r.specified ? r.value : null }, se.escape = function (e) { return (e + "").replace(re, ie) }, se.error = function (e) { throw new Error("Syntax error, unrecognized expression: " + e) }, se.uniqueSort = function (e) { var t, n = [], r = 0, i = 0; if (l = !d.detectDuplicates, u = !d.sortStable && e.slice(0), e.sort(D), l) { while (t = e[i++]) t === e[i] && (r = n.push(i)); while (r--) e.splice(n[r], 1) } return u = null, e }, o = se.getText = function (e) { var t, n = "", r = 0, i = e.nodeType; if (i) { if (1 === i || 9 === i || 11 === i) { if ("string" == typeof e.textContent) return e.textContent; for (e = e.firstChild; e; e = e.nextSibling)n += o(e) } else if (3 === i || 4 === i) return e.nodeValue } else while (t = e[r++]) n += o(t); return n }, (b = se.selectors = { cacheLength: 50, createPseudo: le, match: G, attrHandle: {}, find: {}, relative: { ">": { dir: "parentNode", first: !0 }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: !0 }, "~": { dir: "previousSibling" } }, preFilter: { ATTR: function (e) { return e[1] = e[1].replace(te, ne), e[3] = (e[3] || e[4] || e[5] || "").replace(te, ne), "~=" === e[2] && (e[3] = " " + e[3] + " "), e.slice(0, 4) }, CHILD: function (e) { return e[1] = e[1].toLowerCase(), "nth" === e[1].slice(0, 3) ? (e[3] || se.error(e[0]), e[4] = +(e[4] ? e[5] + (e[6] || 1) : 2 * ("even" === e[3] || "odd" === e[3])), e[5] = +(e[7] + e[8] || "odd" === e[3])) : e[3] && se.error(e[0]), e }, PSEUDO: function (e) { var t, n = !e[6] && e[2]; return G.CHILD.test(e[0]) ? null : (e[3] ? e[2] = e[4] || e[5] || "" : n && X.test(n) && (t = h(n, !0)) && (t = n.indexOf(")", n.length - t) - n.length) && (e[0] = e[0].slice(0, t), e[2] = n.slice(0, t)), e.slice(0, 3)) } }, filter: { TAG: function (e) { var t = e.replace(te, ne).toLowerCase(); return "*" === e ? function () { return !0 } : function (e) { return e.nodeName && e.nodeName.toLowerCase() === t } }, CLASS: function (e) { var t = p[e + " "]; return t || (t = new RegExp("(^|" + M + ")" + e + "(" + M + "|$)")) && p(e, function (e) { return t.test("string" == typeof e.className && e.className || "undefined" != typeof e.getAttribute && e.getAttribute("class") || "") }) }, ATTR: function (n, r, i) { return function (e) { var t = se.attr(e, n); return null == t ? "!=" === r : !r || (t += "", "=" === r ? t === i : "!=" === r ? t !== i : "^=" === r ? i && 0 === t.indexOf(i) : "*=" === r ? i && -1 < t.indexOf(i) : "$=" === r ? i && t.slice(-i.length) === i : "~=" === r ? -1 < (" " + t.replace(F, " ") + " ").indexOf(i) : "|=" === r && (t === i || t.slice(0, i.length + 1) === i + "-")) } }, CHILD: function (h, e, t, g, v) { var y = "nth" !== h.slice(0, 3), m = "last" !== h.slice(-4), x = "of-type" === e; return 1 === g && 0 === v ? function (e) { return !!e.parentNode } : function (e, t, n) { var r, i, o, a, s, u, l = y !== m ? "nextSibling" : "previousSibling", c = e.parentNode, f = x && e.nodeName.toLowerCase(), p = !n && !x, d = !1; if (c) { if (y) { while (l) { a = e; while (a = a[l]) if (x ? a.nodeName.toLowerCase() === f : 1 === a.nodeType) return !1; u = l = "only" === h && !u && "nextSibling" } return !0 } if (u = [m ? c.firstChild : c.lastChild], m && p) { d = (s = (r = (i = (o = (a = c)[k] || (a[k] = {}))[a.uniqueID] || (o[a.uniqueID] = {}))[h] || [])[0] === S && r[1]) && r[2], a = s && c.childNodes[s]; while (a = ++s && a && a[l] || (d = s = 0) || u.pop()) if (1 === a.nodeType && ++d && a === e) { i[h] = [S, s, d]; break } } else if (p && (d = s = (r = (i = (o = (a = e)[k] || (a[k] = {}))[a.uniqueID] || (o[a.uniqueID] = {}))[h] || [])[0] === S && r[1]), !1 === d) while (a = ++s && a && a[l] || (d = s = 0) || u.pop()) if ((x ? a.nodeName.toLowerCase() === f : 1 === a.nodeType) && ++d && (p && ((i = (o = a[k] || (a[k] = {}))[a.uniqueID] || (o[a.uniqueID] = {}))[h] = [S, d]), a === e)) break; return (d -= v) === g || d % g == 0 && 0 <= d / g } } }, PSEUDO: function (e, o) { var t, a = b.pseudos[e] || b.setFilters[e.toLowerCase()] || se.error("unsupported pseudo: " + e); return a[k] ? a(o) : 1 < a.length ? (t = [e, e, "", o], b.setFilters.hasOwnProperty(e.toLowerCase()) ? le(function (e, t) { var n, r = a(e, o), i = r.length; while (i--) e[n = P(e, r[i])] = !(t[n] = r[i]) }) : function (e) { return a(e, 0, t) }) : a } }, pseudos: { not: le(function (e) { var r = [], i = [], s = f(e.replace(B, "$1")); return s[k] ? le(function (e, t, n, r) { var i, o = s(e, null, r, []), a = e.length; while (a--) (i = o[a]) && (e[a] = !(t[a] = i)) }) : function (e, t, n) { return r[0] = e, s(r, null, n, i), r[0] = null, !i.pop() } }), has: le(function (t) { return function (e) { return 0 < se(t, e).length } }), contains: le(function (t) { return t = t.replace(te, ne), function (e) { return -1 < (e.textContent || o(e)).indexOf(t) } }), lang: le(function (n) { return V.test(n || "") || se.error("unsupported lang: " + n), n = n.replace(te, ne).toLowerCase(), function (e) { var t; do { if (t = E ? e.lang : e.getAttribute("xml:lang") || e.getAttribute("lang")) return (t = t.toLowerCase()) === n || 0 === t.indexOf(n + "-") } while ((e = e.parentNode) && 1 === e.nodeType); return !1 } }), target: function (e) { var t = n.location && n.location.hash; return t && t.slice(1) === e.id }, root: function (e) { return e === a }, focus: function (e) { return e === C.activeElement && (!C.hasFocus || C.hasFocus()) && !!(e.type || e.href || ~e.tabIndex) }, enabled: ge(!1), disabled: ge(!0), checked: function (e) { var t = e.nodeName.toLowerCase(); return "input" === t && !!e.checked || "option" === t && !!e.selected }, selected: function (e) { return e.parentNode && e.parentNode.selectedIndex, !0 === e.selected }, empty: function (e) { for (e = e.firstChild; e; e = e.nextSibling)if (e.nodeType < 6) return !1; return !0 }, parent: function (e) { return !b.pseudos.empty(e) }, header: function (e) { return J.test(e.nodeName) }, input: function (e) { return Q.test(e.nodeName) }, button: function (e) { var t = e.nodeName.toLowerCase(); return "input" === t && "button" === e.type || "button" === t }, text: function (e) { var t; return "input" === e.nodeName.toLowerCase() && "text" === e.type && (null == (t = e.getAttribute("type")) || "text" === t.toLowerCase()) }, first: ve(function () { return [0] }), last: ve(function (e, t) { return [t - 1] }), eq: ve(function (e, t, n) { return [n < 0 ? n + t : n] }), even: ve(function (e, t) { for (var n = 0; n < t; n += 2)e.push(n); return e }), odd: ve(function (e, t) { for (var n = 1; n < t; n += 2)e.push(n); return e }), lt: ve(function (e, t, n) { for (var r = n < 0 ? n + t : t < n ? t : n; 0 <= --r;)e.push(r); return e }), gt: ve(function (e, t, n) { for (var r = n < 0 ? n + t : n; ++r < t;)e.push(r); return e }) } }).pseudos.nth = b.pseudos.eq, { radio: !0, checkbox: !0, file: !0, password: !0, image: !0 }) b.pseudos[e] = de(e); for (e in { submit: !0, reset: !0 }) b.pseudos[e] = he(e); function me() { } function xe(e) { for (var t = 0, n = e.length, r = ""; t < n; t++)r += e[t].value; return r } function be(s, e, t) { var u = e.dir, l = e.next, c = l || u, f = t && "parentNode" === c, p = r++; return e.first ? function (e, t, n) { while (e = e[u]) if (1 === e.nodeType || f) return s(e, t, n); return !1 } : function (e, t, n) { var r, i, o, a = [S, p]; if (n) { while (e = e[u]) if ((1 === e.nodeType || f) && s(e, t, n)) return !0 } else while (e = e[u]) if (1 === e.nodeType || f) if (i = (o = e[k] || (e[k] = {}))[e.uniqueID] || (o[e.uniqueID] = {}), l && l === e.nodeName.toLowerCase()) e = e[u] || e; else { if ((r = i[c]) && r[0] === S && r[1] === p) return a[2] = r[2]; if ((i[c] = a)[2] = s(e, t, n)) return !0 } return !1 } } function we(i) { return 1 < i.length ? function (e, t, n) { var r = i.length; while (r--) if (!i[r](e, t, n)) return !1; return !0 } : i[0] } function Te(e, t, n, r, i) { for (var o, a = [], s = 0, u = e.length, l = null != t; s < u; s++)(o = e[s]) && (n && !n(o, r, i) || (a.push(o), l && t.push(s))); return a } function Ce(d, h, g, v, y, e) { return v && !v[k] && (v = Ce(v)), y && !y[k] && (y = Ce(y, e)), le(function (e, t, n, r) { var i, o, a, s = [], u = [], l = t.length, c = e || function (e, t, n) { for (var r = 0, i = t.length; r < i; r++)se(e, t[r], n); return n }(h || "*", n.nodeType ? [n] : n, []), f = !d || !e && h ? c : Te(c, s, d, n, r), p = g ? y || (e ? d : l || v) ? [] : t : f; if (g && g(f, p, n, r), v) { i = Te(p, u), v(i, [], n, r), o = i.length; while (o--) (a = i[o]) && (p[u[o]] = !(f[u[o]] = a)) } if (e) { if (y || d) { if (y) { i = [], o = p.length; while (o--) (a = p[o]) && i.push(f[o] = a); y(null, p = [], i, r) } o = p.length; while (o--) (a = p[o]) && -1 < (i = y ? P(e, a) : s[o]) && (e[i] = !(t[i] = a)) } } else p = Te(p === t ? p.splice(l, p.length) : p), y ? y(null, t, p, r) : H.apply(t, p) }) } function Ee(e) { for (var i, t, n, r = e.length, o = b.relative[e[0].type], a = o || b.relative[" "], s = o ? 1 : 0, u = be(function (e) { return e === i }, a, !0), l = be(function (e) { return -1 < P(i, e) }, a, !0), c = [function (e, t, n) { var r = !o && (n || t !== w) || ((i = t).nodeType ? u(e, t, n) : l(e, t, n)); return i = null, r }]; s < r; s++)if (t = b.relative[e[s].type]) c = [be(we(c), t)]; else { if ((t = b.filter[e[s].type].apply(null, e[s].matches))[k]) { for (n = ++s; n < r; n++)if (b.relative[e[n].type]) break; return Ce(1 < s && we(c), 1 < s && xe(e.slice(0, s - 1).concat({ value: " " === e[s - 2].type ? "*" : "" })).replace(B, "$1"), t, s < n && Ee(e.slice(s, n)), n < r && Ee(e = e.slice(n)), n < r && xe(e)) } c.push(t) } return we(c) } return me.prototype = b.filters = b.pseudos, b.setFilters = new me, h = se.tokenize = function (e, t) { var n, r, i, o, a, s, u, l = x[e + " "]; if (l) return t ? 0 : l.slice(0); a = e, s = [], u = b.preFilter; while (a) { for (o in n && !(r = _.exec(a)) || (r && (a = a.slice(r[0].length) || a), s.push(i = [])), n = !1, (r = z.exec(a)) && (n = r.shift(), i.push({ value: n, type: r[0].replace(B, " ") }), a = a.slice(n.length)), b.filter) !(r = G[o].exec(a)) || u[o] && !(r = u[o](r)) || (n = r.shift(), i.push({ value: n, type: o, matches: r }), a = a.slice(n.length)); if (!n) break } return t ? a.length : a ? se.error(e) : x(e, s).slice(0) }, f = se.compile = function (e, t) { var n, v, y, m, x, r, i = [], o = [], a = N[e + " "]; if (!a) { t || (t = h(e)), n = t.length; while (n--) (a = Ee(t[n]))[k] ? i.push(a) : o.push(a); (a = N(e, (v = o, m = 0 < (y = i).length, x = 0 < v.length, r = function (e, t, n, r, i) { var o, a, s, u = 0, l = "0", c = e && [], f = [], p = w, d = e || x && b.find.TAG("*", i), h = S += null == p ? 1 : Math.random() || .1, g = d.length; for (i && (w = t === C || t || i); l !== g && null != (o = d[l]); l++) { if (x && o) { a = 0, t || o.ownerDocument === C || (T(o), n = !E); while (s = v[a++]) if (s(o, t || C, n)) { r.push(o); break } i && (S = h) } m && ((o = !s && o) && u-- , e && c.push(o)) } if (u += l, m && l !== u) { a = 0; while (s = y[a++]) s(c, f, t, n); if (e) { if (0 < u) while (l--) c[l] || f[l] || (f[l] = q.call(r)); f = Te(f) } H.apply(r, f), i && !e && 0 < f.length && 1 < u + y.length && se.uniqueSort(r) } return i && (S = h, w = p), c }, m ? le(r) : r))).selector = e } return a }, g = se.select = function (e, t, n, r) { var i, o, a, s, u, l = "function" == typeof e && e, c = !r && h(e = l.selector || e); if (n = n || [], 1 === c.length) { if (2 < (o = c[0] = c[0].slice(0)).length && "ID" === (a = o[0]).type && 9 === t.nodeType && E && b.relative[o[1].type]) { if (!(t = (b.find.ID(a.matches[0].replace(te, ne), t) || [])[0])) return n; l && (t = t.parentNode), e = e.slice(o.shift().value.length) } i = G.needsContext.test(e) ? 0 : o.length; while (i--) { if (a = o[i], b.relative[s = a.type]) break; if ((u = b.find[s]) && (r = u(a.matches[0].replace(te, ne), ee.test(o[0].type) && ye(t.parentNode) || t))) { if (o.splice(i, 1), !(e = r.length && xe(o))) return H.apply(n, r), n; break } } } return (l || f(e, c))(r, t, !E, n, !t || ee.test(e) && ye(t.parentNode) || t), n }, d.sortStable = k.split("").sort(D).join("") === k, d.detectDuplicates = !!l, T(), d.sortDetached = ce(function (e) { return 1 & e.compareDocumentPosition(C.createElement("fieldset")) }), ce(function (e) { return e.innerHTML = "", "#" === e.firstChild.getAttribute("href") }) || fe("type|href|height|width", function (e, t, n) { if (!n) return e.getAttribute(t, "type" === t.toLowerCase() ? 1 : 2) }), d.attributes && ce(function (e) { return e.innerHTML = "", e.firstChild.setAttribute("value", ""), "" === e.firstChild.getAttribute("value") }) || fe("value", function (e, t, n) { if (!n && "input" === e.nodeName.toLowerCase()) return e.defaultValue }), ce(function (e) { return null == e.getAttribute("disabled") }) || fe(R, function (e, t, n) { var r; if (!n) return !0 === e[t] ? t.toLowerCase() : (r = e.getAttributeNode(t)) && r.specified ? r.value : null }), se }(C); k.find = h, k.expr = h.selectors, k.expr[":"] = k.expr.pseudos, k.uniqueSort = k.unique = h.uniqueSort, k.text = h.getText, k.isXMLDoc = h.isXML, k.contains = h.contains, k.escapeSelector = h.escape; var T = function (e, t, n) { var r = [], i = void 0 !== n; while ((e = e[t]) && 9 !== e.nodeType) if (1 === e.nodeType) { if (i && k(e).is(n)) break; r.push(e) } return r }, S = function (e, t) { for (var n = []; e; e = e.nextSibling)1 === e.nodeType && e !== t && n.push(e); return n }, N = k.expr.match.needsContext; function A(e, t) { return e.nodeName && e.nodeName.toLowerCase() === t.toLowerCase() } var D = /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i; function j(e, n, r) { return m(n) ? k.grep(e, function (e, t) { return !!n.call(e, t, e) !== r }) : n.nodeType ? k.grep(e, function (e) { return e === n !== r }) : "string" != typeof n ? k.grep(e, function (e) { return -1 < i.call(n, e) !== r }) : k.filter(n, e, r) } k.filter = function (e, t, n) { var r = t[0]; return n && (e = ":not(" + e + ")"), 1 === t.length && 1 === r.nodeType ? k.find.matchesSelector(r, e) ? [r] : [] : k.find.matches(e, k.grep(t, function (e) { return 1 === e.nodeType })) }, k.fn.extend({ find: function (e) { var t, n, r = this.length, i = this; if ("string" != typeof e) return this.pushStack(k(e).filter(function () { for (t = 0; t < r; t++)if (k.contains(i[t], this)) return !0 })); for (n = this.pushStack([]), t = 0; t < r; t++)k.find(e, i[t], n); return 1 < r ? k.uniqueSort(n) : n }, filter: function (e) { return this.pushStack(j(this, e || [], !1)) }, not: function (e) { return this.pushStack(j(this, e || [], !0)) }, is: function (e) { return !!j(this, "string" == typeof e && N.test(e) ? k(e) : e || [], !1).length } }); var q, L = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/; (k.fn.init = function (e, t, n) { var r, i; if (!e) return this; if (n = n || q, "string" == typeof e) { if (!(r = "<" === e[0] && ">" === e[e.length - 1] && 3 <= e.length ? [null, e, null] : L.exec(e)) || !r[1] && t) return !t || t.jquery ? (t || n).find(e) : this.constructor(t).find(e); if (r[1]) { if (t = t instanceof k ? t[0] : t, k.merge(this, k.parseHTML(r[1], t && t.nodeType ? t.ownerDocument || t : E, !0)), D.test(r[1]) && k.isPlainObject(t)) for (r in t) m(this[r]) ? this[r](t[r]) : this.attr(r, t[r]); return this } return (i = E.getElementById(r[2])) && (this[0] = i, this.length = 1), this } return e.nodeType ? (this[0] = e, this.length = 1, this) : m(e) ? void 0 !== n.ready ? n.ready(e) : e(k) : k.makeArray(e, this) }).prototype = k.fn, q = k(E); var H = /^(?:parents|prev(?:Until|All))/, O = { children: !0, contents: !0, next: !0, prev: !0 }; function P(e, t) { while ((e = e[t]) && 1 !== e.nodeType); return e } k.fn.extend({ has: function (e) { var t = k(e, this), n = t.length; return this.filter(function () { for (var e = 0; e < n; e++)if (k.contains(this, t[e])) return !0 }) }, closest: function (e, t) { var n, r = 0, i = this.length, o = [], a = "string" != typeof e && k(e); if (!N.test(e)) for (; r < i; r++)for (n = this[r]; n && n !== t; n = n.parentNode)if (n.nodeType < 11 && (a ? -1 < a.index(n) : 1 === n.nodeType && k.find.matchesSelector(n, e))) { o.push(n); break } return this.pushStack(1 < o.length ? k.uniqueSort(o) : o) }, index: function (e) { return e ? "string" == typeof e ? i.call(k(e), this[0]) : i.call(this, e.jquery ? e[0] : e) : this[0] && this[0].parentNode ? this.first().prevAll().length : -1 }, add: function (e, t) { return this.pushStack(k.uniqueSort(k.merge(this.get(), k(e, t)))) }, addBack: function (e) { return this.add(null == e ? this.prevObject : this.prevObject.filter(e)) } }), k.each({ parent: function (e) { var t = e.parentNode; return t && 11 !== t.nodeType ? t : null }, parents: function (e) { return T(e, "parentNode") }, parentsUntil: function (e, t, n) { return T(e, "parentNode", n) }, next: function (e) { return P(e, "nextSibling") }, prev: function (e) { return P(e, "previousSibling") }, nextAll: function (e) { return T(e, "nextSibling") }, prevAll: function (e) { return T(e, "previousSibling") }, nextUntil: function (e, t, n) { return T(e, "nextSibling", n) }, prevUntil: function (e, t, n) { return T(e, "previousSibling", n) }, siblings: function (e) { return S((e.parentNode || {}).firstChild, e) }, children: function (e) { return S(e.firstChild) }, contents: function (e) { return "undefined" != typeof e.contentDocument ? e.contentDocument : (A(e, "template") && (e = e.content || e), k.merge([], e.childNodes)) } }, function (r, i) { k.fn[r] = function (e, t) { var n = k.map(this, i, e); return "Until" !== r.slice(-5) && (t = e), t && "string" == typeof t && (n = k.filter(t, n)), 1 < this.length && (O[r] || k.uniqueSort(n), H.test(r) && n.reverse()), this.pushStack(n) } }); var R = /[^\x20\t\r\n\f]+/g; function M(e) { return e } function I(e) { throw e } function W(e, t, n, r) { var i; try { e && m(i = e.promise) ? i.call(e).done(t).fail(n) : e && m(i = e.then) ? i.call(e, t, n) : t.apply(void 0, [e].slice(r)) } catch (e) { n.apply(void 0, [e]) } } k.Callbacks = function (r) { var e, n; r = "string" == typeof r ? (e = r, n = {}, k.each(e.match(R) || [], function (e, t) { n[t] = !0 }), n) : k.extend({}, r); var i, t, o, a, s = [], u = [], l = -1, c = function () { for (a = a || r.once, o = i = !0; u.length; l = -1) { t = u.shift(); while (++l < s.length) !1 === s[l].apply(t[0], t[1]) && r.stopOnFalse && (l = s.length, t = !1) } r.memory || (t = !1), i = !1, a && (s = t ? [] : "") }, f = { add: function () { return s && (t && !i && (l = s.length - 1, u.push(t)), function n(e) { k.each(e, function (e, t) { m(t) ? r.unique && f.has(t) || s.push(t) : t && t.length && "string" !== w(t) && n(t) }) }(arguments), t && !i && c()), this }, remove: function () { return k.each(arguments, function (e, t) { var n; while (-1 < (n = k.inArray(t, s, n))) s.splice(n, 1), n <= l && l-- }), this }, has: function (e) { return e ? -1 < k.inArray(e, s) : 0 < s.length }, empty: function () { return s && (s = []), this }, disable: function () { return a = u = [], s = t = "", this }, disabled: function () { return !s }, lock: function () { return a = u = [], t || i || (s = t = ""), this }, locked: function () { return !!a }, fireWith: function (e, t) { return a || (t = [e, (t = t || []).slice ? t.slice() : t], u.push(t), i || c()), this }, fire: function () { return f.fireWith(this, arguments), this }, fired: function () { return !!o } }; return f }, k.extend({ Deferred: function (e) { var o = [["notify", "progress", k.Callbacks("memory"), k.Callbacks("memory"), 2], ["resolve", "done", k.Callbacks("once memory"), k.Callbacks("once memory"), 0, "resolved"], ["reject", "fail", k.Callbacks("once memory"), k.Callbacks("once memory"), 1, "rejected"]], i = "pending", a = { state: function () { return i }, always: function () { return s.done(arguments).fail(arguments), this }, "catch": function (e) { return a.then(null, e) }, pipe: function () { var i = arguments; return k.Deferred(function (r) { k.each(o, function (e, t) { var n = m(i[t[4]]) && i[t[4]]; s[t[1]](function () { var e = n && n.apply(this, arguments); e && m(e.promise) ? e.promise().progress(r.notify).done(r.resolve).fail(r.reject) : r[t[0] + "With"](this, n ? [e] : arguments) }) }), i = null }).promise() }, then: function (t, n, r) { var u = 0; function l(i, o, a, s) { return function () { var n = this, r = arguments, e = function () { var e, t; if (!(i < u)) { if ((e = a.apply(n, r)) === o.promise()) throw new TypeError("Thenable self-resolution"); t = e && ("object" == typeof e || "function" == typeof e) && e.then, m(t) ? s ? t.call(e, l(u, o, M, s), l(u, o, I, s)) : (u++ , t.call(e, l(u, o, M, s), l(u, o, I, s), l(u, o, M, o.notifyWith))) : (a !== M && (n = void 0, r = [e]), (s || o.resolveWith)(n, r)) } }, t = s ? e : function () { try { e() } catch (e) { k.Deferred.exceptionHook && k.Deferred.exceptionHook(e, t.stackTrace), u <= i + 1 && (a !== I && (n = void 0, r = [e]), o.rejectWith(n, r)) } }; i ? t() : (k.Deferred.getStackHook && (t.stackTrace = k.Deferred.getStackHook()), C.setTimeout(t)) } } return k.Deferred(function (e) { o[0][3].add(l(0, e, m(r) ? r : M, e.notifyWith)), o[1][3].add(l(0, e, m(t) ? t : M)), o[2][3].add(l(0, e, m(n) ? n : I)) }).promise() }, promise: function (e) { return null != e ? k.extend(e, a) : a } }, s = {}; return k.each(o, function (e, t) { var n = t[2], r = t[5]; a[t[1]] = n.add, r && n.add(function () { i = r }, o[3 - e][2].disable, o[3 - e][3].disable, o[0][2].lock, o[0][3].lock), n.add(t[3].fire), s[t[0]] = function () { return s[t[0] + "With"](this === s ? void 0 : this, arguments), this }, s[t[0] + "With"] = n.fireWith }), a.promise(s), e && e.call(s, s), s }, when: function (e) { var n = arguments.length, t = n, r = Array(t), i = s.call(arguments), o = k.Deferred(), a = function (t) { return function (e) { r[t] = this, i[t] = 1 < arguments.length ? s.call(arguments) : e, --n || o.resolveWith(r, i) } }; if (n <= 1 && (W(e, o.done(a(t)).resolve, o.reject, !n), "pending" === o.state() || m(i[t] && i[t].then))) return o.then(); while (t--) W(i[t], a(t), o.reject); return o.promise() } }); var $ = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; k.Deferred.exceptionHook = function (e, t) { C.console && C.console.warn && e && $.test(e.name) && C.console.warn("jQuery.Deferred exception: " + e.message, e.stack, t) }, k.readyException = function (e) { C.setTimeout(function () { throw e }) }; var F = k.Deferred(); function B() { E.removeEventListener("DOMContentLoaded", B), C.removeEventListener("load", B), k.ready() } k.fn.ready = function (e) { return F.then(e)["catch"](function (e) { k.readyException(e) }), this }, k.extend({ isReady: !1, readyWait: 1, ready: function (e) { (!0 === e ? --k.readyWait : k.isReady) || (k.isReady = !0) !== e && 0 < --k.readyWait || F.resolveWith(E, [k]) } }), k.ready.then = F.then, "complete" === E.readyState || "loading" !== E.readyState && !E.documentElement.doScroll ? C.setTimeout(k.ready) : (E.addEventListener("DOMContentLoaded", B), C.addEventListener("load", B)); var _ = function (e, t, n, r, i, o, a) { var s = 0, u = e.length, l = null == n; if ("object" === w(n)) for (s in i = !0, n) _(e, t, s, n[s], !0, o, a); else if (void 0 !== r && (i = !0, m(r) || (a = !0), l && (a ? (t.call(e, r), t = null) : (l = t, t = function (e, t, n) { return l.call(k(e), n) })), t)) for (; s < u; s++)t(e[s], n, a ? r : r.call(e[s], s, t(e[s], n))); return i ? e : l ? t.call(e) : u ? t(e[0], n) : o }, z = /^-ms-/, U = /-([a-z])/g; function X(e, t) { return t.toUpperCase() } function V(e) { return e.replace(z, "ms-").replace(U, X) } var G = function (e) { return 1 === e.nodeType || 9 === e.nodeType || !+e.nodeType }; function Y() { this.expando = k.expando + Y.uid++ } Y.uid = 1, Y.prototype = { cache: function (e) { var t = e[this.expando]; return t || (t = {}, G(e) && (e.nodeType ? e[this.expando] = t : Object.defineProperty(e, this.expando, { value: t, configurable: !0 }))), t }, set: function (e, t, n) { var r, i = this.cache(e); if ("string" == typeof t) i[V(t)] = n; else for (r in t) i[V(r)] = t[r]; return i }, get: function (e, t) { return void 0 === t ? this.cache(e) : e[this.expando] && e[this.expando][V(t)] }, access: function (e, t, n) { return void 0 === t || t && "string" == typeof t && void 0 === n ? this.get(e, t) : (this.set(e, t, n), void 0 !== n ? n : t) }, remove: function (e, t) { var n, r = e[this.expando]; if (void 0 !== r) { if (void 0 !== t) { n = (t = Array.isArray(t) ? t.map(V) : (t = V(t)) in r ? [t] : t.match(R) || []).length; while (n--) delete r[t[n]] } (void 0 === t || k.isEmptyObject(r)) && (e.nodeType ? e[this.expando] = void 0 : delete e[this.expando]) } }, hasData: function (e) { var t = e[this.expando]; return void 0 !== t && !k.isEmptyObject(t) } }; var Q = new Y, J = new Y, K = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, Z = /[A-Z]/g; function ee(e, t, n) { var r, i; if (void 0 === n && 1 === e.nodeType) if (r = "data-" + t.replace(Z, "-$&").toLowerCase(), "string" == typeof (n = e.getAttribute(r))) { try { n = "true" === (i = n) || "false" !== i && ("null" === i ? null : i === +i + "" ? +i : K.test(i) ? JSON.parse(i) : i) } catch (e) { } J.set(e, t, n) } else n = void 0; return n } k.extend({ hasData: function (e) { return J.hasData(e) || Q.hasData(e) }, data: function (e, t, n) { return J.access(e, t, n) }, removeData: function (e, t) { J.remove(e, t) }, _data: function (e, t, n) { return Q.access(e, t, n) }, _removeData: function (e, t) { Q.remove(e, t) } }), k.fn.extend({ data: function (n, e) { var t, r, i, o = this[0], a = o && o.attributes; if (void 0 === n) { if (this.length && (i = J.get(o), 1 === o.nodeType && !Q.get(o, "hasDataAttrs"))) { t = a.length; while (t--) a[t] && 0 === (r = a[t].name).indexOf("data-") && (r = V(r.slice(5)), ee(o, r, i[r])); Q.set(o, "hasDataAttrs", !0) } return i } return "object" == typeof n ? this.each(function () { J.set(this, n) }) : _(this, function (e) { var t; if (o && void 0 === e) return void 0 !== (t = J.get(o, n)) ? t : void 0 !== (t = ee(o, n)) ? t : void 0; this.each(function () { J.set(this, n, e) }) }, null, e, 1 < arguments.length, null, !0) }, removeData: function (e) { return this.each(function () { J.remove(this, e) }) } }), k.extend({ queue: function (e, t, n) { var r; if (e) return t = (t || "fx") + "queue", r = Q.get(e, t), n && (!r || Array.isArray(n) ? r = Q.access(e, t, k.makeArray(n)) : r.push(n)), r || [] }, dequeue: function (e, t) { t = t || "fx"; var n = k.queue(e, t), r = n.length, i = n.shift(), o = k._queueHooks(e, t); "inprogress" === i && (i = n.shift(), r--), i && ("fx" === t && n.unshift("inprogress"), delete o.stop, i.call(e, function () { k.dequeue(e, t) }, o)), !r && o && o.empty.fire() }, _queueHooks: function (e, t) { var n = t + "queueHooks"; return Q.get(e, n) || Q.access(e, n, { empty: k.Callbacks("once memory").add(function () { Q.remove(e, [t + "queue", n]) }) }) } }), k.fn.extend({ queue: function (t, n) { var e = 2; return "string" != typeof t && (n = t, t = "fx", e--), arguments.length < e ? k.queue(this[0], t) : void 0 === n ? this : this.each(function () { var e = k.queue(this, t, n); k._queueHooks(this, t), "fx" === t && "inprogress" !== e[0] && k.dequeue(this, t) }) }, dequeue: function (e) { return this.each(function () { k.dequeue(this, e) }) }, clearQueue: function (e) { return this.queue(e || "fx", []) }, promise: function (e, t) { var n, r = 1, i = k.Deferred(), o = this, a = this.length, s = function () { --r || i.resolveWith(o, [o]) }; "string" != typeof e && (t = e, e = void 0), e = e || "fx"; while (a--) (n = Q.get(o[a], e + "queueHooks")) && n.empty && (r++ , n.empty.add(s)); return s(), i.promise(t) } }); var te = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, ne = new RegExp("^(?:([+-])=|)(" + te + ")([a-z%]*)$", "i"), re = ["Top", "Right", "Bottom", "Left"], ie = E.documentElement, oe = function (e) { return k.contains(e.ownerDocument, e) }, ae = { composed: !0 }; ie.getRootNode && (oe = function (e) { return k.contains(e.ownerDocument, e) || e.getRootNode(ae) === e.ownerDocument }); var se = function (e, t) { return "none" === (e = t || e).style.display || "" === e.style.display && oe(e) && "none" === k.css(e, "display") }, ue = function (e, t, n, r) { var i, o, a = {}; for (o in t) a[o] = e.style[o], e.style[o] = t[o]; for (o in i = n.apply(e, r || []), t) e.style[o] = a[o]; return i }; function le(e, t, n, r) { var i, o, a = 20, s = r ? function () { return r.cur() } : function () { return k.css(e, t, "") }, u = s(), l = n && n[3] || (k.cssNumber[t] ? "" : "px"), c = e.nodeType && (k.cssNumber[t] || "px" !== l && +u) && ne.exec(k.css(e, t)); if (c && c[3] !== l) { u /= 2, l = l || c[3], c = +u || 1; while (a--) k.style(e, t, c + l), (1 - o) * (1 - (o = s() / u || .5)) <= 0 && (a = 0), c /= o; c *= 2, k.style(e, t, c + l), n = n || [] } return n && (c = +c || +u || 0, i = n[1] ? c + (n[1] + 1) * n[2] : +n[2], r && (r.unit = l, r.start = c, r.end = i)), i } var ce = {}; function fe(e, t) { for (var n, r, i, o, a, s, u, l = [], c = 0, f = e.length; c < f; c++)(r = e[c]).style && (n = r.style.display, t ? ("none" === n && (l[c] = Q.get(r, "display") || null, l[c] || (r.style.display = "")), "" === r.style.display && se(r) && (l[c] = (u = a = o = void 0, a = (i = r).ownerDocument, s = i.nodeName, (u = ce[s]) || (o = a.body.appendChild(a.createElement(s)), u = k.css(o, "display"), o.parentNode.removeChild(o), "none" === u && (u = "block"), ce[s] = u)))) : "none" !== n && (l[c] = "none", Q.set(r, "display", n))); for (c = 0; c < f; c++)null != l[c] && (e[c].style.display = l[c]); return e } k.fn.extend({ show: function () { return fe(this, !0) }, hide: function () { return fe(this) }, toggle: function (e) { return "boolean" == typeof e ? e ? this.show() : this.hide() : this.each(function () { se(this) ? k(this).show() : k(this).hide() }) } }); var pe = /^(?:checkbox|radio)$/i, de = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i, he = /^$|^module$|\/(?:java|ecma)script/i, ge = { option: [1, ""], thead: [1, "", "
"], col: [2, "", "
"], tr: [2, "", "
"], td: [3, "", "
"], _default: [0, "", ""] }; function ve(e, t) { var n; return n = "undefined" != typeof e.getElementsByTagName ? e.getElementsByTagName(t || "*") : "undefined" != typeof e.querySelectorAll ? e.querySelectorAll(t || "*") : [], void 0 === t || t && A(e, t) ? k.merge([e], n) : n } function ye(e, t) { for (var n = 0, r = e.length; n < r; n++)Q.set(e[n], "globalEval", !t || Q.get(t[n], "globalEval")) } ge.optgroup = ge.option, ge.tbody = ge.tfoot = ge.colgroup = ge.caption = ge.thead, ge.th = ge.td; var me, xe, be = /<|&#?\w+;/; function we(e, t, n, r, i) { for (var o, a, s, u, l, c, f = t.createDocumentFragment(), p = [], d = 0, h = e.length; d < h; d++)if ((o = e[d]) || 0 === o) if ("object" === w(o)) k.merge(p, o.nodeType ? [o] : o); else if (be.test(o)) { a = a || f.appendChild(t.createElement("div")), s = (de.exec(o) || ["", ""])[1].toLowerCase(), u = ge[s] || ge._default, a.innerHTML = u[1] + k.htmlPrefilter(o) + u[2], c = u[0]; while (c--) a = a.lastChild; k.merge(p, a.childNodes), (a = f.firstChild).textContent = "" } else p.push(t.createTextNode(o)); f.textContent = "", d = 0; while (o = p[d++]) if (r && -1 < k.inArray(o, r)) i && i.push(o); else if (l = oe(o), a = ve(f.appendChild(o), "script"), l && ye(a), n) { c = 0; while (o = a[c++]) he.test(o.type || "") && n.push(o) } return f } me = E.createDocumentFragment().appendChild(E.createElement("div")), (xe = E.createElement("input")).setAttribute("type", "radio"), xe.setAttribute("checked", "checked"), xe.setAttribute("name", "t"), me.appendChild(xe), y.checkClone = me.cloneNode(!0).cloneNode(!0).lastChild.checked, me.innerHTML = "", y.noCloneChecked = !!me.cloneNode(!0).lastChild.defaultValue; var Te = /^key/, Ce = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, Ee = /^([^.]*)(?:\.(.+)|)/; function ke() { return !0 } function Se() { return !1 } function Ne(e, t) { return e === function () { try { return E.activeElement } catch (e) { } }() == ("focus" === t) } function Ae(e, t, n, r, i, o) { var a, s; if ("object" == typeof t) { for (s in "string" != typeof n && (r = r || n, n = void 0), t) Ae(e, s, n, r, t[s], o); return e } if (null == r && null == i ? (i = n, r = n = void 0) : null == i && ("string" == typeof n ? (i = r, r = void 0) : (i = r, r = n, n = void 0)), !1 === i) i = Se; else if (!i) return e; return 1 === o && (a = i, (i = function (e) { return k().off(e), a.apply(this, arguments) }).guid = a.guid || (a.guid = k.guid++)), e.each(function () { k.event.add(this, t, i, r, n) }) } function De(e, i, o) { o ? (Q.set(e, i, !1), k.event.add(e, i, { namespace: !1, handler: function (e) { var t, n, r = Q.get(this, i); if (1 & e.isTrigger && this[i]) { if (r.length) (k.event.special[i] || {}).delegateType && e.stopPropagation(); else if (r = s.call(arguments), Q.set(this, i, r), t = o(this, i), this[i](), r !== (n = Q.get(this, i)) || t ? Q.set(this, i, !1) : n = {}, r !== n) return e.stopImmediatePropagation(), e.preventDefault(), n.value } else r.length && (Q.set(this, i, { value: k.event.trigger(k.extend(r[0], k.Event.prototype), r.slice(1), this) }), e.stopImmediatePropagation()) } })) : void 0 === Q.get(e, i) && k.event.add(e, i, ke) } k.event = { global: {}, add: function (t, e, n, r, i) { var o, a, s, u, l, c, f, p, d, h, g, v = Q.get(t); if (v) { n.handler && (n = (o = n).handler, i = o.selector), i && k.find.matchesSelector(ie, i), n.guid || (n.guid = k.guid++), (u = v.events) || (u = v.events = {}), (a = v.handle) || (a = v.handle = function (e) { return "undefined" != typeof k && k.event.triggered !== e.type ? k.event.dispatch.apply(t, arguments) : void 0 }), l = (e = (e || "").match(R) || [""]).length; while (l--) d = g = (s = Ee.exec(e[l]) || [])[1], h = (s[2] || "").split(".").sort(), d && (f = k.event.special[d] || {}, d = (i ? f.delegateType : f.bindType) || d, f = k.event.special[d] || {}, c = k.extend({ type: d, origType: g, data: r, handler: n, guid: n.guid, selector: i, needsContext: i && k.expr.match.needsContext.test(i), namespace: h.join(".") }, o), (p = u[d]) || ((p = u[d] = []).delegateCount = 0, f.setup && !1 !== f.setup.call(t, r, h, a) || t.addEventListener && t.addEventListener(d, a)), f.add && (f.add.call(t, c), c.handler.guid || (c.handler.guid = n.guid)), i ? p.splice(p.delegateCount++, 0, c) : p.push(c), k.event.global[d] = !0) } }, remove: function (e, t, n, r, i) { var o, a, s, u, l, c, f, p, d, h, g, v = Q.hasData(e) && Q.get(e); if (v && (u = v.events)) { l = (t = (t || "").match(R) || [""]).length; while (l--) if (d = g = (s = Ee.exec(t[l]) || [])[1], h = (s[2] || "").split(".").sort(), d) { f = k.event.special[d] || {}, p = u[d = (r ? f.delegateType : f.bindType) || d] || [], s = s[2] && new RegExp("(^|\\.)" + h.join("\\.(?:.*\\.|)") + "(\\.|$)"), a = o = p.length; while (o--) c = p[o], !i && g !== c.origType || n && n.guid !== c.guid || s && !s.test(c.namespace) || r && r !== c.selector && ("**" !== r || !c.selector) || (p.splice(o, 1), c.selector && p.delegateCount-- , f.remove && f.remove.call(e, c)); a && !p.length && (f.teardown && !1 !== f.teardown.call(e, h, v.handle) || k.removeEvent(e, d, v.handle), delete u[d]) } else for (d in u) k.event.remove(e, d + t[l], n, r, !0); k.isEmptyObject(u) && Q.remove(e, "handle events") } }, dispatch: function (e) { var t, n, r, i, o, a, s = k.event.fix(e), u = new Array(arguments.length), l = (Q.get(this, "events") || {})[s.type] || [], c = k.event.special[s.type] || {}; for (u[0] = s, t = 1; t < arguments.length; t++)u[t] = arguments[t]; if (s.delegateTarget = this, !c.preDispatch || !1 !== c.preDispatch.call(this, s)) { a = k.event.handlers.call(this, s, l), t = 0; while ((i = a[t++]) && !s.isPropagationStopped()) { s.currentTarget = i.elem, n = 0; while ((o = i.handlers[n++]) && !s.isImmediatePropagationStopped()) s.rnamespace && !1 !== o.namespace && !s.rnamespace.test(o.namespace) || (s.handleObj = o, s.data = o.data, void 0 !== (r = ((k.event.special[o.origType] || {}).handle || o.handler).apply(i.elem, u)) && !1 === (s.result = r) && (s.preventDefault(), s.stopPropagation())) } return c.postDispatch && c.postDispatch.call(this, s), s.result } }, handlers: function (e, t) { var n, r, i, o, a, s = [], u = t.delegateCount, l = e.target; if (u && l.nodeType && !("click" === e.type && 1 <= e.button)) for (; l !== this; l = l.parentNode || this)if (1 === l.nodeType && ("click" !== e.type || !0 !== l.disabled)) { for (o = [], a = {}, n = 0; n < u; n++)void 0 === a[i = (r = t[n]).selector + " "] && (a[i] = r.needsContext ? -1 < k(i, this).index(l) : k.find(i, this, null, [l]).length), a[i] && o.push(r); o.length && s.push({ elem: l, handlers: o }) } return l = this, u < t.length && s.push({ elem: l, handlers: t.slice(u) }), s }, addProp: function (t, e) { Object.defineProperty(k.Event.prototype, t, { enumerable: !0, configurable: !0, get: m(e) ? function () { if (this.originalEvent) return e(this.originalEvent) } : function () { if (this.originalEvent) return this.originalEvent[t] }, set: function (e) { Object.defineProperty(this, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) } }) }, fix: function (e) { return e[k.expando] ? e : new k.Event(e) }, special: { load: { noBubble: !0 }, click: { setup: function (e) { var t = this || e; return pe.test(t.type) && t.click && A(t, "input") && De(t, "click", ke), !1 }, trigger: function (e) { var t = this || e; return pe.test(t.type) && t.click && A(t, "input") && De(t, "click"), !0 }, _default: function (e) { var t = e.target; return pe.test(t.type) && t.click && A(t, "input") && Q.get(t, "click") || A(t, "a") } }, beforeunload: { postDispatch: function (e) { void 0 !== e.result && e.originalEvent && (e.originalEvent.returnValue = e.result) } } } }, k.removeEvent = function (e, t, n) { e.removeEventListener && e.removeEventListener(t, n) }, k.Event = function (e, t) { if (!(this instanceof k.Event)) return new k.Event(e, t); e && e.type ? (this.originalEvent = e, this.type = e.type, this.isDefaultPrevented = e.defaultPrevented || void 0 === e.defaultPrevented && !1 === e.returnValue ? ke : Se, this.target = e.target && 3 === e.target.nodeType ? e.target.parentNode : e.target, this.currentTarget = e.currentTarget, this.relatedTarget = e.relatedTarget) : this.type = e, t && k.extend(this, t), this.timeStamp = e && e.timeStamp || Date.now(), this[k.expando] = !0 }, k.Event.prototype = { constructor: k.Event, isDefaultPrevented: Se, isPropagationStopped: Se, isImmediatePropagationStopped: Se, isSimulated: !1, preventDefault: function () { var e = this.originalEvent; this.isDefaultPrevented = ke, e && !this.isSimulated && e.preventDefault() }, stopPropagation: function () { var e = this.originalEvent; this.isPropagationStopped = ke, e && !this.isSimulated && e.stopPropagation() }, stopImmediatePropagation: function () { var e = this.originalEvent; this.isImmediatePropagationStopped = ke, e && !this.isSimulated && e.stopImmediatePropagation(), this.stopPropagation() } }, k.each({ altKey: !0, bubbles: !0, cancelable: !0, changedTouches: !0, ctrlKey: !0, detail: !0, eventPhase: !0, metaKey: !0, pageX: !0, pageY: !0, shiftKey: !0, view: !0, "char": !0, code: !0, charCode: !0, key: !0, keyCode: !0, button: !0, buttons: !0, clientX: !0, clientY: !0, offsetX: !0, offsetY: !0, pointerId: !0, pointerType: !0, screenX: !0, screenY: !0, targetTouches: !0, toElement: !0, touches: !0, which: function (e) { var t = e.button; return null == e.which && Te.test(e.type) ? null != e.charCode ? e.charCode : e.keyCode : !e.which && void 0 !== t && Ce.test(e.type) ? 1 & t ? 1 : 2 & t ? 3 : 4 & t ? 2 : 0 : e.which } }, k.event.addProp), k.each({ focus: "focusin", blur: "focusout" }, function (e, t) { k.event.special[e] = { setup: function () { return De(this, e, Ne), !1 }, trigger: function () { return De(this, e), !0 }, delegateType: t } }), k.each({ mouseenter: "mouseover", mouseleave: "mouseout", pointerenter: "pointerover", pointerleave: "pointerout" }, function (e, i) { k.event.special[e] = { delegateType: i, bindType: i, handle: function (e) { var t, n = e.relatedTarget, r = e.handleObj; return n && (n === this || k.contains(this, n)) || (e.type = r.origType, t = r.handler.apply(this, arguments), e.type = i), t } } }), k.fn.extend({ on: function (e, t, n, r) { return Ae(this, e, t, n, r) }, one: function (e, t, n, r) { return Ae(this, e, t, n, r, 1) }, off: function (e, t, n) { var r, i; if (e && e.preventDefault && e.handleObj) return r = e.handleObj, k(e.delegateTarget).off(r.namespace ? r.origType + "." + r.namespace : r.origType, r.selector, r.handler), this; if ("object" == typeof e) { for (i in e) this.off(i, t, e[i]); return this } return !1 !== t && "function" != typeof t || (n = t, t = void 0), !1 === n && (n = Se), this.each(function () { k.event.remove(this, e, n, t) }) } }); var je = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, qe = /\s*$/g; function Oe(e, t) { return A(e, "table") && A(11 !== t.nodeType ? t : t.firstChild, "tr") && k(e).children("tbody")[0] || e } function Pe(e) { return e.type = (null !== e.getAttribute("type")) + "/" + e.type, e } function Re(e) { return "true/" === (e.type || "").slice(0, 5) ? e.type = e.type.slice(5) : e.removeAttribute("type"), e } function Me(e, t) { var n, r, i, o, a, s, u, l; if (1 === t.nodeType) { if (Q.hasData(e) && (o = Q.access(e), a = Q.set(t, o), l = o.events)) for (i in delete a.handle, a.events = {}, l) for (n = 0, r = l[i].length; n < r; n++)k.event.add(t, i, l[i][n]); J.hasData(e) && (s = J.access(e), u = k.extend({}, s), J.set(t, u)) } } function Ie(n, r, i, o) { r = g.apply([], r); var e, t, a, s, u, l, c = 0, f = n.length, p = f - 1, d = r[0], h = m(d); if (h || 1 < f && "string" == typeof d && !y.checkClone && Le.test(d)) return n.each(function (e) { var t = n.eq(e); h && (r[0] = d.call(this, e, t.html())), Ie(t, r, i, o) }); if (f && (t = (e = we(r, n[0].ownerDocument, !1, n, o)).firstChild, 1 === e.childNodes.length && (e = t), t || o)) { for (s = (a = k.map(ve(e, "script"), Pe)).length; c < f; c++)u = e, c !== p && (u = k.clone(u, !0, !0), s && k.merge(a, ve(u, "script"))), i.call(n[c], u, c); if (s) for (l = a[a.length - 1].ownerDocument, k.map(a, Re), c = 0; c < s; c++)u = a[c], he.test(u.type || "") && !Q.access(u, "globalEval") && k.contains(l, u) && (u.src && "module" !== (u.type || "").toLowerCase() ? k._evalUrl && !u.noModule && k._evalUrl(u.src, { nonce: u.nonce || u.getAttribute("nonce") }) : b(u.textContent.replace(He, ""), u, l)) } return n } function We(e, t, n) { for (var r, i = t ? k.filter(t, e) : e, o = 0; null != (r = i[o]); o++)n || 1 !== r.nodeType || k.cleanData(ve(r)), r.parentNode && (n && oe(r) && ye(ve(r, "script")), r.parentNode.removeChild(r)); return e } k.extend({ htmlPrefilter: function (e) { return e.replace(je, "<$1>") }, clone: function (e, t, n) { var r, i, o, a, s, u, l, c = e.cloneNode(!0), f = oe(e); if (!(y.noCloneChecked || 1 !== e.nodeType && 11 !== e.nodeType || k.isXMLDoc(e))) for (a = ve(c), r = 0, i = (o = ve(e)).length; r < i; r++)s = o[r], u = a[r], void 0, "input" === (l = u.nodeName.toLowerCase()) && pe.test(s.type) ? u.checked = s.checked : "input" !== l && "textarea" !== l || (u.defaultValue = s.defaultValue); if (t) if (n) for (o = o || ve(e), a = a || ve(c), r = 0, i = o.length; r < i; r++)Me(o[r], a[r]); else Me(e, c); return 0 < (a = ve(c, "script")).length && ye(a, !f && ve(e, "script")), c }, cleanData: function (e) { for (var t, n, r, i = k.event.special, o = 0; void 0 !== (n = e[o]); o++)if (G(n)) { if (t = n[Q.expando]) { if (t.events) for (r in t.events) i[r] ? k.event.remove(n, r) : k.removeEvent(n, r, t.handle); n[Q.expando] = void 0 } n[J.expando] && (n[J.expando] = void 0) } } }), k.fn.extend({ detach: function (e) { return We(this, e, !0) }, remove: function (e) { return We(this, e) }, text: function (e) { return _(this, function (e) { return void 0 === e ? k.text(this) : this.empty().each(function () { 1 !== this.nodeType && 11 !== this.nodeType && 9 !== this.nodeType || (this.textContent = e) }) }, null, e, arguments.length) }, append: function () { return Ie(this, arguments, function (e) { 1 !== this.nodeType && 11 !== this.nodeType && 9 !== this.nodeType || Oe(this, e).appendChild(e) }) }, prepend: function () { return Ie(this, arguments, function (e) { if (1 === this.nodeType || 11 === this.nodeType || 9 === this.nodeType) { var t = Oe(this, e); t.insertBefore(e, t.firstChild) } }) }, before: function () { return Ie(this, arguments, function (e) { this.parentNode && this.parentNode.insertBefore(e, this) }) }, after: function () { return Ie(this, arguments, function (e) { this.parentNode && this.parentNode.insertBefore(e, this.nextSibling) }) }, empty: function () { for (var e, t = 0; null != (e = this[t]); t++)1 === e.nodeType && (k.cleanData(ve(e, !1)), e.textContent = ""); return this }, clone: function (e, t) { return e = null != e && e, t = null == t ? e : t, this.map(function () { return k.clone(this, e, t) }) }, html: function (e) { return _(this, function (e) { var t = this[0] || {}, n = 0, r = this.length; if (void 0 === e && 1 === t.nodeType) return t.innerHTML; if ("string" == typeof e && !qe.test(e) && !ge[(de.exec(e) || ["", ""])[1].toLowerCase()]) { e = k.htmlPrefilter(e); try { for (; n < r; n++)1 === (t = this[n] || {}).nodeType && (k.cleanData(ve(t, !1)), t.innerHTML = e); t = 0 } catch (e) { } } t && this.empty().append(e) }, null, e, arguments.length) }, replaceWith: function () { var n = []; return Ie(this, arguments, function (e) { var t = this.parentNode; k.inArray(this, n) < 0 && (k.cleanData(ve(this)), t && t.replaceChild(e, this)) }, n) } }), k.each({ appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function (e, a) { k.fn[e] = function (e) { for (var t, n = [], r = k(e), i = r.length - 1, o = 0; o <= i; o++)t = o === i ? this : this.clone(!0), k(r[o])[a](t), u.apply(n, t.get()); return this.pushStack(n) } }); var $e = new RegExp("^(" + te + ")(?!px)[a-z%]+$", "i"), Fe = function (e) { var t = e.ownerDocument.defaultView; return t && t.opener || (t = C), t.getComputedStyle(e) }, Be = new RegExp(re.join("|"), "i"); function _e(e, t, n) { var r, i, o, a, s = e.style; return (n = n || Fe(e)) && ("" !== (a = n.getPropertyValue(t) || n[t]) || oe(e) || (a = k.style(e, t)), !y.pixelBoxStyles() && $e.test(a) && Be.test(t) && (r = s.width, i = s.minWidth, o = s.maxWidth, s.minWidth = s.maxWidth = s.width = a, a = n.width, s.width = r, s.minWidth = i, s.maxWidth = o)), void 0 !== a ? a + "" : a } function ze(e, t) { return { get: function () { if (!e()) return (this.get = t).apply(this, arguments); delete this.get } } } !function () { function e() { if (u) { s.style.cssText = "position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0", u.style.cssText = "position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%", ie.appendChild(s).appendChild(u); var e = C.getComputedStyle(u); n = "1%" !== e.top, a = 12 === t(e.marginLeft), u.style.right = "60%", o = 36 === t(e.right), r = 36 === t(e.width), u.style.position = "absolute", i = 12 === t(u.offsetWidth / 3), ie.removeChild(s), u = null } } function t(e) { return Math.round(parseFloat(e)) } var n, r, i, o, a, s = E.createElement("div"), u = E.createElement("div"); u.style && (u.style.backgroundClip = "content-box", u.cloneNode(!0).style.backgroundClip = "", y.clearCloneStyle = "content-box" === u.style.backgroundClip, k.extend(y, { boxSizingReliable: function () { return e(), r }, pixelBoxStyles: function () { return e(), o }, pixelPosition: function () { return e(), n }, reliableMarginLeft: function () { return e(), a }, scrollboxSize: function () { return e(), i } })) }(); var Ue = ["Webkit", "Moz", "ms"], Xe = E.createElement("div").style, Ve = {}; function Ge(e) { var t = k.cssProps[e] || Ve[e]; return t || (e in Xe ? e : Ve[e] = function (e) { var t = e[0].toUpperCase() + e.slice(1), n = Ue.length; while (n--) if ((e = Ue[n] + t) in Xe) return e }(e) || e) } var Ye = /^(none|table(?!-c[ea]).+)/, Qe = /^--/, Je = { position: "absolute", visibility: "hidden", display: "block" }, Ke = { letterSpacing: "0", fontWeight: "400" }; function Ze(e, t, n) { var r = ne.exec(t); return r ? Math.max(0, r[2] - (n || 0)) + (r[3] || "px") : t } function et(e, t, n, r, i, o) { var a = "width" === t ? 1 : 0, s = 0, u = 0; if (n === (r ? "border" : "content")) return 0; for (; a < 4; a += 2)"margin" === n && (u += k.css(e, n + re[a], !0, i)), r ? ("content" === n && (u -= k.css(e, "padding" + re[a], !0, i)), "margin" !== n && (u -= k.css(e, "border" + re[a] + "Width", !0, i))) : (u += k.css(e, "padding" + re[a], !0, i), "padding" !== n ? u += k.css(e, "border" + re[a] + "Width", !0, i) : s += k.css(e, "border" + re[a] + "Width", !0, i)); return !r && 0 <= o && (u += Math.max(0, Math.ceil(e["offset" + t[0].toUpperCase() + t.slice(1)] - o - u - s - .5)) || 0), u } function tt(e, t, n) { var r = Fe(e), i = (!y.boxSizingReliable() || n) && "border-box" === k.css(e, "boxSizing", !1, r), o = i, a = _e(e, t, r), s = "offset" + t[0].toUpperCase() + t.slice(1); if ($e.test(a)) { if (!n) return a; a = "auto" } return (!y.boxSizingReliable() && i || "auto" === a || !parseFloat(a) && "inline" === k.css(e, "display", !1, r)) && e.getClientRects().length && (i = "border-box" === k.css(e, "boxSizing", !1, r), (o = s in e) && (a = e[s])), (a = parseFloat(a) || 0) + et(e, t, n || (i ? "border" : "content"), o, r, a) + "px" } function nt(e, t, n, r, i) { return new nt.prototype.init(e, t, n, r, i) } k.extend({ cssHooks: { opacity: { get: function (e, t) { if (t) { var n = _e(e, "opacity"); return "" === n ? "1" : n } } } }, cssNumber: { animationIterationCount: !0, columnCount: !0, fillOpacity: !0, flexGrow: !0, flexShrink: !0, fontWeight: !0, gridArea: !0, gridColumn: !0, gridColumnEnd: !0, gridColumnStart: !0, gridRow: !0, gridRowEnd: !0, gridRowStart: !0, lineHeight: !0, opacity: !0, order: !0, orphans: !0, widows: !0, zIndex: !0, zoom: !0 }, cssProps: {}, style: function (e, t, n, r) { if (e && 3 !== e.nodeType && 8 !== e.nodeType && e.style) { var i, o, a, s = V(t), u = Qe.test(t), l = e.style; if (u || (t = Ge(s)), a = k.cssHooks[t] || k.cssHooks[s], void 0 === n) return a && "get" in a && void 0 !== (i = a.get(e, !1, r)) ? i : l[t]; "string" === (o = typeof n) && (i = ne.exec(n)) && i[1] && (n = le(e, t, i), o = "number"), null != n && n == n && ("number" !== o || u || (n += i && i[3] || (k.cssNumber[s] ? "" : "px")), y.clearCloneStyle || "" !== n || 0 !== t.indexOf("background") || (l[t] = "inherit"), a && "set" in a && void 0 === (n = a.set(e, n, r)) || (u ? l.setProperty(t, n) : l[t] = n)) } }, css: function (e, t, n, r) { var i, o, a, s = V(t); return Qe.test(t) || (t = Ge(s)), (a = k.cssHooks[t] || k.cssHooks[s]) && "get" in a && (i = a.get(e, !0, n)), void 0 === i && (i = _e(e, t, r)), "normal" === i && t in Ke && (i = Ke[t]), "" === n || n ? (o = parseFloat(i), !0 === n || isFinite(o) ? o || 0 : i) : i } }), k.each(["height", "width"], function (e, u) { k.cssHooks[u] = { get: function (e, t, n) { if (t) return !Ye.test(k.css(e, "display")) || e.getClientRects().length && e.getBoundingClientRect().width ? tt(e, u, n) : ue(e, Je, function () { return tt(e, u, n) }) }, set: function (e, t, n) { var r, i = Fe(e), o = !y.scrollboxSize() && "absolute" === i.position, a = (o || n) && "border-box" === k.css(e, "boxSizing", !1, i), s = n ? et(e, u, n, a, i) : 0; return a && o && (s -= Math.ceil(e["offset" + u[0].toUpperCase() + u.slice(1)] - parseFloat(i[u]) - et(e, u, "border", !1, i) - .5)), s && (r = ne.exec(t)) && "px" !== (r[3] || "px") && (e.style[u] = t, t = k.css(e, u)), Ze(0, t, s) } } }), k.cssHooks.marginLeft = ze(y.reliableMarginLeft, function (e, t) { if (t) return (parseFloat(_e(e, "marginLeft")) || e.getBoundingClientRect().left - ue(e, { marginLeft: 0 }, function () { return e.getBoundingClientRect().left })) + "px" }), k.each({ margin: "", padding: "", border: "Width" }, function (i, o) { k.cssHooks[i + o] = { expand: function (e) { for (var t = 0, n = {}, r = "string" == typeof e ? e.split(" ") : [e]; t < 4; t++)n[i + re[t] + o] = r[t] || r[t - 2] || r[0]; return n } }, "margin" !== i && (k.cssHooks[i + o].set = Ze) }), k.fn.extend({ css: function (e, t) { return _(this, function (e, t, n) { var r, i, o = {}, a = 0; if (Array.isArray(t)) { for (r = Fe(e), i = t.length; a < i; a++)o[t[a]] = k.css(e, t[a], !1, r); return o } return void 0 !== n ? k.style(e, t, n) : k.css(e, t) }, e, t, 1 < arguments.length) } }), ((k.Tween = nt).prototype = { constructor: nt, init: function (e, t, n, r, i, o) { this.elem = e, this.prop = n, this.easing = i || k.easing._default, this.options = t, this.start = this.now = this.cur(), this.end = r, this.unit = o || (k.cssNumber[n] ? "" : "px") }, cur: function () { var e = nt.propHooks[this.prop]; return e && e.get ? e.get(this) : nt.propHooks._default.get(this) }, run: function (e) { var t, n = nt.propHooks[this.prop]; return this.options.duration ? this.pos = t = k.easing[this.easing](e, this.options.duration * e, 0, 1, this.options.duration) : this.pos = t = e, this.now = (this.end - this.start) * t + this.start, this.options.step && this.options.step.call(this.elem, this.now, this), n && n.set ? n.set(this) : nt.propHooks._default.set(this), this } }).init.prototype = nt.prototype, (nt.propHooks = { _default: { get: function (e) { var t; return 1 !== e.elem.nodeType || null != e.elem[e.prop] && null == e.elem.style[e.prop] ? e.elem[e.prop] : (t = k.css(e.elem, e.prop, "")) && "auto" !== t ? t : 0 }, set: function (e) { k.fx.step[e.prop] ? k.fx.step[e.prop](e) : 1 !== e.elem.nodeType || !k.cssHooks[e.prop] && null == e.elem.style[Ge(e.prop)] ? e.elem[e.prop] = e.now : k.style(e.elem, e.prop, e.now + e.unit) } } }).scrollTop = nt.propHooks.scrollLeft = { set: function (e) { e.elem.nodeType && e.elem.parentNode && (e.elem[e.prop] = e.now) } }, k.easing = { linear: function (e) { return e }, swing: function (e) { return .5 - Math.cos(e * Math.PI) / 2 }, _default: "swing" }, k.fx = nt.prototype.init, k.fx.step = {}; var rt, it, ot, at, st = /^(?:toggle|show|hide)$/, ut = /queueHooks$/; function lt() { it && (!1 === E.hidden && C.requestAnimationFrame ? C.requestAnimationFrame(lt) : C.setTimeout(lt, k.fx.interval), k.fx.tick()) } function ct() { return C.setTimeout(function () { rt = void 0 }), rt = Date.now() } function ft(e, t) { var n, r = 0, i = { height: e }; for (t = t ? 1 : 0; r < 4; r += 2 - t)i["margin" + (n = re[r])] = i["padding" + n] = e; return t && (i.opacity = i.width = e), i } function pt(e, t, n) { for (var r, i = (dt.tweeners[t] || []).concat(dt.tweeners["*"]), o = 0, a = i.length; o < a; o++)if (r = i[o].call(n, t, e)) return r } function dt(o, e, t) { var n, a, r = 0, i = dt.prefilters.length, s = k.Deferred().always(function () { delete u.elem }), u = function () { if (a) return !1; for (var e = rt || ct(), t = Math.max(0, l.startTime + l.duration - e), n = 1 - (t / l.duration || 0), r = 0, i = l.tweens.length; r < i; r++)l.tweens[r].run(n); return s.notifyWith(o, [l, n, t]), n < 1 && i ? t : (i || s.notifyWith(o, [l, 1, 0]), s.resolveWith(o, [l]), !1) }, l = s.promise({ elem: o, props: k.extend({}, e), opts: k.extend(!0, { specialEasing: {}, easing: k.easing._default }, t), originalProperties: e, originalOptions: t, startTime: rt || ct(), duration: t.duration, tweens: [], createTween: function (e, t) { var n = k.Tween(o, l.opts, e, t, l.opts.specialEasing[e] || l.opts.easing); return l.tweens.push(n), n }, stop: function (e) { var t = 0, n = e ? l.tweens.length : 0; if (a) return this; for (a = !0; t < n; t++)l.tweens[t].run(1); return e ? (s.notifyWith(o, [l, 1, 0]), s.resolveWith(o, [l, e])) : s.rejectWith(o, [l, e]), this } }), c = l.props; for (!function (e, t) { var n, r, i, o, a; for (n in e) if (i = t[r = V(n)], o = e[n], Array.isArray(o) && (i = o[1], o = e[n] = o[0]), n !== r && (e[r] = o, delete e[n]), (a = k.cssHooks[r]) && "expand" in a) for (n in o = a.expand(o), delete e[r], o) n in e || (e[n] = o[n], t[n] = i); else t[r] = i }(c, l.opts.specialEasing); r < i; r++)if (n = dt.prefilters[r].call(l, o, c, l.opts)) return m(n.stop) && (k._queueHooks(l.elem, l.opts.queue).stop = n.stop.bind(n)), n; return k.map(c, pt, l), m(l.opts.start) && l.opts.start.call(o, l), l.progress(l.opts.progress).done(l.opts.done, l.opts.complete).fail(l.opts.fail).always(l.opts.always), k.fx.timer(k.extend(u, { elem: o, anim: l, queue: l.opts.queue })), l } k.Animation = k.extend(dt, { tweeners: { "*": [function (e, t) { var n = this.createTween(e, t); return le(n.elem, e, ne.exec(t), n), n }] }, tweener: function (e, t) { m(e) ? (t = e, e = ["*"]) : e = e.match(R); for (var n, r = 0, i = e.length; r < i; r++)n = e[r], dt.tweeners[n] = dt.tweeners[n] || [], dt.tweeners[n].unshift(t) }, prefilters: [function (e, t, n) { var r, i, o, a, s, u, l, c, f = "width" in t || "height" in t, p = this, d = {}, h = e.style, g = e.nodeType && se(e), v = Q.get(e, "fxshow"); for (r in n.queue || (null == (a = k._queueHooks(e, "fx")).unqueued && (a.unqueued = 0, s = a.empty.fire, a.empty.fire = function () { a.unqueued || s() }), a.unqueued++ , p.always(function () { p.always(function () { a.unqueued-- , k.queue(e, "fx").length || a.empty.fire() }) })), t) if (i = t[r], st.test(i)) { if (delete t[r], o = o || "toggle" === i, i === (g ? "hide" : "show")) { if ("show" !== i || !v || void 0 === v[r]) continue; g = !0 } d[r] = v && v[r] || k.style(e, r) } if ((u = !k.isEmptyObject(t)) || !k.isEmptyObject(d)) for (r in f && 1 === e.nodeType && (n.overflow = [h.overflow, h.overflowX, h.overflowY], null == (l = v && v.display) && (l = Q.get(e, "display")), "none" === (c = k.css(e, "display")) && (l ? c = l : (fe([e], !0), l = e.style.display || l, c = k.css(e, "display"), fe([e]))), ("inline" === c || "inline-block" === c && null != l) && "none" === k.css(e, "float") && (u || (p.done(function () { h.display = l }), null == l && (c = h.display, l = "none" === c ? "" : c)), h.display = "inline-block")), n.overflow && (h.overflow = "hidden", p.always(function () { h.overflow = n.overflow[0], h.overflowX = n.overflow[1], h.overflowY = n.overflow[2] })), u = !1, d) u || (v ? "hidden" in v && (g = v.hidden) : v = Q.access(e, "fxshow", { display: l }), o && (v.hidden = !g), g && fe([e], !0), p.done(function () { for (r in g || fe([e]), Q.remove(e, "fxshow"), d) k.style(e, r, d[r]) })), u = pt(g ? v[r] : 0, r, p), r in v || (v[r] = u.start, g && (u.end = u.start, u.start = 0)) }], prefilter: function (e, t) { t ? dt.prefilters.unshift(e) : dt.prefilters.push(e) } }), k.speed = function (e, t, n) { var r = e && "object" == typeof e ? k.extend({}, e) : { complete: n || !n && t || m(e) && e, duration: e, easing: n && t || t && !m(t) && t }; return k.fx.off ? r.duration = 0 : "number" != typeof r.duration && (r.duration in k.fx.speeds ? r.duration = k.fx.speeds[r.duration] : r.duration = k.fx.speeds._default), null != r.queue && !0 !== r.queue || (r.queue = "fx"), r.old = r.complete, r.complete = function () { m(r.old) && r.old.call(this), r.queue && k.dequeue(this, r.queue) }, r }, k.fn.extend({ fadeTo: function (e, t, n, r) { return this.filter(se).css("opacity", 0).show().end().animate({ opacity: t }, e, n, r) }, animate: function (t, e, n, r) { var i = k.isEmptyObject(t), o = k.speed(e, n, r), a = function () { var e = dt(this, k.extend({}, t), o); (i || Q.get(this, "finish")) && e.stop(!0) }; return a.finish = a, i || !1 === o.queue ? this.each(a) : this.queue(o.queue, a) }, stop: function (i, e, o) { var a = function (e) { var t = e.stop; delete e.stop, t(o) }; return "string" != typeof i && (o = e, e = i, i = void 0), e && !1 !== i && this.queue(i || "fx", []), this.each(function () { var e = !0, t = null != i && i + "queueHooks", n = k.timers, r = Q.get(this); if (t) r[t] && r[t].stop && a(r[t]); else for (t in r) r[t] && r[t].stop && ut.test(t) && a(r[t]); for (t = n.length; t--;)n[t].elem !== this || null != i && n[t].queue !== i || (n[t].anim.stop(o), e = !1, n.splice(t, 1)); !e && o || k.dequeue(this, i) }) }, finish: function (a) { return !1 !== a && (a = a || "fx"), this.each(function () { var e, t = Q.get(this), n = t[a + "queue"], r = t[a + "queueHooks"], i = k.timers, o = n ? n.length : 0; for (t.finish = !0, k.queue(this, a, []), r && r.stop && r.stop.call(this, !0), e = i.length; e--;)i[e].elem === this && i[e].queue === a && (i[e].anim.stop(!0), i.splice(e, 1)); for (e = 0; e < o; e++)n[e] && n[e].finish && n[e].finish.call(this); delete t.finish }) } }), k.each(["toggle", "show", "hide"], function (e, r) { var i = k.fn[r]; k.fn[r] = function (e, t, n) { return null == e || "boolean" == typeof e ? i.apply(this, arguments) : this.animate(ft(r, !0), e, t, n) } }), k.each({ slideDown: ft("show"), slideUp: ft("hide"), slideToggle: ft("toggle"), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } }, function (e, r) { k.fn[e] = function (e, t, n) { return this.animate(r, e, t, n) } }), k.timers = [], k.fx.tick = function () { var e, t = 0, n = k.timers; for (rt = Date.now(); t < n.length; t++)(e = n[t])() || n[t] !== e || n.splice(t--, 1); n.length || k.fx.stop(), rt = void 0 }, k.fx.timer = function (e) { k.timers.push(e), k.fx.start() }, k.fx.interval = 13, k.fx.start = function () { it || (it = !0, lt()) }, k.fx.stop = function () { it = null }, k.fx.speeds = { slow: 600, fast: 200, _default: 400 }, k.fn.delay = function (r, e) { return r = k.fx && k.fx.speeds[r] || r, e = e || "fx", this.queue(e, function (e, t) { var n = C.setTimeout(e, r); t.stop = function () { C.clearTimeout(n) } }) }, ot = E.createElement("input"), at = E.createElement("select").appendChild(E.createElement("option")), ot.type = "checkbox", y.checkOn = "" !== ot.value, y.optSelected = at.selected, (ot = E.createElement("input")).value = "t", ot.type = "radio", y.radioValue = "t" === ot.value; var ht, gt = k.expr.attrHandle; k.fn.extend({ attr: function (e, t) { return _(this, k.attr, e, t, 1 < arguments.length) }, removeAttr: function (e) { return this.each(function () { k.removeAttr(this, e) }) } }), k.extend({ attr: function (e, t, n) { var r, i, o = e.nodeType; if (3 !== o && 8 !== o && 2 !== o) return "undefined" == typeof e.getAttribute ? k.prop(e, t, n) : (1 === o && k.isXMLDoc(e) || (i = k.attrHooks[t.toLowerCase()] || (k.expr.match.bool.test(t) ? ht : void 0)), void 0 !== n ? null === n ? void k.removeAttr(e, t) : i && "set" in i && void 0 !== (r = i.set(e, n, t)) ? r : (e.setAttribute(t, n + ""), n) : i && "get" in i && null !== (r = i.get(e, t)) ? r : null == (r = k.find.attr(e, t)) ? void 0 : r) }, attrHooks: { type: { set: function (e, t) { if (!y.radioValue && "radio" === t && A(e, "input")) { var n = e.value; return e.setAttribute("type", t), n && (e.value = n), t } } } }, removeAttr: function (e, t) { var n, r = 0, i = t && t.match(R); if (i && 1 === e.nodeType) while (n = i[r++]) e.removeAttribute(n) } }), ht = { set: function (e, t, n) { return !1 === t ? k.removeAttr(e, n) : e.setAttribute(n, n), n } }, k.each(k.expr.match.bool.source.match(/\w+/g), function (e, t) { var a = gt[t] || k.find.attr; gt[t] = function (e, t, n) { var r, i, o = t.toLowerCase(); return n || (i = gt[o], gt[o] = r, r = null != a(e, t, n) ? o : null, gt[o] = i), r } }); var vt = /^(?:input|select|textarea|button)$/i, yt = /^(?:a|area)$/i; function mt(e) { return (e.match(R) || []).join(" ") } function xt(e) { return e.getAttribute && e.getAttribute("class") || "" } function bt(e) { return Array.isArray(e) ? e : "string" == typeof e && e.match(R) || [] } k.fn.extend({ prop: function (e, t) { return _(this, k.prop, e, t, 1 < arguments.length) }, removeProp: function (e) { return this.each(function () { delete this[k.propFix[e] || e] }) } }), k.extend({ prop: function (e, t, n) { var r, i, o = e.nodeType; if (3 !== o && 8 !== o && 2 !== o) return 1 === o && k.isXMLDoc(e) || (t = k.propFix[t] || t, i = k.propHooks[t]), void 0 !== n ? i && "set" in i && void 0 !== (r = i.set(e, n, t)) ? r : e[t] = n : i && "get" in i && null !== (r = i.get(e, t)) ? r : e[t] }, propHooks: { tabIndex: { get: function (e) { var t = k.find.attr(e, "tabindex"); return t ? parseInt(t, 10) : vt.test(e.nodeName) || yt.test(e.nodeName) && e.href ? 0 : -1 } } }, propFix: { "for": "htmlFor", "class": "className" } }), y.optSelected || (k.propHooks.selected = { get: function (e) { var t = e.parentNode; return t && t.parentNode && t.parentNode.selectedIndex, null }, set: function (e) { var t = e.parentNode; t && (t.selectedIndex, t.parentNode && t.parentNode.selectedIndex) } }), k.each(["tabIndex", "readOnly", "maxLength", "cellSpacing", "cellPadding", "rowSpan", "colSpan", "useMap", "frameBorder", "contentEditable"], function () { k.propFix[this.toLowerCase()] = this }), k.fn.extend({ addClass: function (t) { var e, n, r, i, o, a, s, u = 0; if (m(t)) return this.each(function (e) { k(this).addClass(t.call(this, e, xt(this))) }); if ((e = bt(t)).length) while (n = this[u++]) if (i = xt(n), r = 1 === n.nodeType && " " + mt(i) + " ") { a = 0; while (o = e[a++]) r.indexOf(" " + o + " ") < 0 && (r += o + " "); i !== (s = mt(r)) && n.setAttribute("class", s) } return this }, removeClass: function (t) { var e, n, r, i, o, a, s, u = 0; if (m(t)) return this.each(function (e) { k(this).removeClass(t.call(this, e, xt(this))) }); if (!arguments.length) return this.attr("class", ""); if ((e = bt(t)).length) while (n = this[u++]) if (i = xt(n), r = 1 === n.nodeType && " " + mt(i) + " ") { a = 0; while (o = e[a++]) while (-1 < r.indexOf(" " + o + " ")) r = r.replace(" " + o + " ", " "); i !== (s = mt(r)) && n.setAttribute("class", s) } return this }, toggleClass: function (i, t) { var o = typeof i, a = "string" === o || Array.isArray(i); return "boolean" == typeof t && a ? t ? this.addClass(i) : this.removeClass(i) : m(i) ? this.each(function (e) { k(this).toggleClass(i.call(this, e, xt(this), t), t) }) : this.each(function () { var e, t, n, r; if (a) { t = 0, n = k(this), r = bt(i); while (e = r[t++]) n.hasClass(e) ? n.removeClass(e) : n.addClass(e) } else void 0 !== i && "boolean" !== o || ((e = xt(this)) && Q.set(this, "__className__", e), this.setAttribute && this.setAttribute("class", e || !1 === i ? "" : Q.get(this, "__className__") || "")) }) }, hasClass: function (e) { var t, n, r = 0; t = " " + e + " "; while (n = this[r++]) if (1 === n.nodeType && -1 < (" " + mt(xt(n)) + " ").indexOf(t)) return !0; return !1 } }); var wt = /\r/g; k.fn.extend({ val: function (n) { var r, e, i, t = this[0]; return arguments.length ? (i = m(n), this.each(function (e) { var t; 1 === this.nodeType && (null == (t = i ? n.call(this, e, k(this).val()) : n) ? t = "" : "number" == typeof t ? t += "" : Array.isArray(t) && (t = k.map(t, function (e) { return null == e ? "" : e + "" })), (r = k.valHooks[this.type] || k.valHooks[this.nodeName.toLowerCase()]) && "set" in r && void 0 !== r.set(this, t, "value") || (this.value = t)) })) : t ? (r = k.valHooks[t.type] || k.valHooks[t.nodeName.toLowerCase()]) && "get" in r && void 0 !== (e = r.get(t, "value")) ? e : "string" == typeof (e = t.value) ? e.replace(wt, "") : null == e ? "" : e : void 0 } }), k.extend({ valHooks: { option: { get: function (e) { var t = k.find.attr(e, "value"); return null != t ? t : mt(k.text(e)) } }, select: { get: function (e) { var t, n, r, i = e.options, o = e.selectedIndex, a = "select-one" === e.type, s = a ? null : [], u = a ? o + 1 : i.length; for (r = o < 0 ? u : a ? o : 0; r < u; r++)if (((n = i[r]).selected || r === o) && !n.disabled && (!n.parentNode.disabled || !A(n.parentNode, "optgroup"))) { if (t = k(n).val(), a) return t; s.push(t) } return s }, set: function (e, t) { var n, r, i = e.options, o = k.makeArray(t), a = i.length; while (a--) ((r = i[a]).selected = -1 < k.inArray(k.valHooks.option.get(r), o)) && (n = !0); return n || (e.selectedIndex = -1), o } } } }), k.each(["radio", "checkbox"], function () { k.valHooks[this] = { set: function (e, t) { if (Array.isArray(t)) return e.checked = -1 < k.inArray(k(e).val(), t) } }, y.checkOn || (k.valHooks[this].get = function (e) { return null === e.getAttribute("value") ? "on" : e.value }) }), y.focusin = "onfocusin" in C; var Tt = /^(?:focusinfocus|focusoutblur)$/, Ct = function (e) { e.stopPropagation() }; k.extend(k.event, { trigger: function (e, t, n, r) { var i, o, a, s, u, l, c, f, p = [n || E], d = v.call(e, "type") ? e.type : e, h = v.call(e, "namespace") ? e.namespace.split(".") : []; if (o = f = a = n = n || E, 3 !== n.nodeType && 8 !== n.nodeType && !Tt.test(d + k.event.triggered) && (-1 < d.indexOf(".") && (d = (h = d.split(".")).shift(), h.sort()), u = d.indexOf(":") < 0 && "on" + d, (e = e[k.expando] ? e : new k.Event(d, "object" == typeof e && e)).isTrigger = r ? 2 : 3, e.namespace = h.join("."), e.rnamespace = e.namespace ? new RegExp("(^|\\.)" + h.join("\\.(?:.*\\.|)") + "(\\.|$)") : null, e.result = void 0, e.target || (e.target = n), t = null == t ? [e] : k.makeArray(t, [e]), c = k.event.special[d] || {}, r || !c.trigger || !1 !== c.trigger.apply(n, t))) { if (!r && !c.noBubble && !x(n)) { for (s = c.delegateType || d, Tt.test(s + d) || (o = o.parentNode); o; o = o.parentNode)p.push(o), a = o; a === (n.ownerDocument || E) && p.push(a.defaultView || a.parentWindow || C) } i = 0; while ((o = p[i++]) && !e.isPropagationStopped()) f = o, e.type = 1 < i ? s : c.bindType || d, (l = (Q.get(o, "events") || {})[e.type] && Q.get(o, "handle")) && l.apply(o, t), (l = u && o[u]) && l.apply && G(o) && (e.result = l.apply(o, t), !1 === e.result && e.preventDefault()); return e.type = d, r || e.isDefaultPrevented() || c._default && !1 !== c._default.apply(p.pop(), t) || !G(n) || u && m(n[d]) && !x(n) && ((a = n[u]) && (n[u] = null), k.event.triggered = d, e.isPropagationStopped() && f.addEventListener(d, Ct), n[d](), e.isPropagationStopped() && f.removeEventListener(d, Ct), k.event.triggered = void 0, a && (n[u] = a)), e.result } }, simulate: function (e, t, n) { var r = k.extend(new k.Event, n, { type: e, isSimulated: !0 }); k.event.trigger(r, null, t) } }), k.fn.extend({ trigger: function (e, t) { return this.each(function () { k.event.trigger(e, t, this) }) }, triggerHandler: function (e, t) { var n = this[0]; if (n) return k.event.trigger(e, t, n, !0) } }), y.focusin || k.each({ focus: "focusin", blur: "focusout" }, function (n, r) { var i = function (e) { k.event.simulate(r, e.target, k.event.fix(e)) }; k.event.special[r] = { setup: function () { var e = this.ownerDocument || this, t = Q.access(e, r); t || e.addEventListener(n, i, !0), Q.access(e, r, (t || 0) + 1) }, teardown: function () { var e = this.ownerDocument || this, t = Q.access(e, r) - 1; t ? Q.access(e, r, t) : (e.removeEventListener(n, i, !0), Q.remove(e, r)) } } }); var Et = C.location, kt = Date.now(), St = /\?/; k.parseXML = function (e) { var t; if (!e || "string" != typeof e) return null; try { t = (new C.DOMParser).parseFromString(e, "text/xml") } catch (e) { t = void 0 } return t && !t.getElementsByTagName("parsererror").length || k.error("Invalid XML: " + e), t }; var Nt = /\[\]$/, At = /\r?\n/g, Dt = /^(?:submit|button|image|reset|file)$/i, jt = /^(?:input|select|textarea|keygen)/i; function qt(n, e, r, i) { var t; if (Array.isArray(e)) k.each(e, function (e, t) { r || Nt.test(n) ? i(n, t) : qt(n + "[" + ("object" == typeof t && null != t ? e : "") + "]", t, r, i) }); else if (r || "object" !== w(e)) i(n, e); else for (t in e) qt(n + "[" + t + "]", e[t], r, i) } k.param = function (e, t) { var n, r = [], i = function (e, t) { var n = m(t) ? t() : t; r[r.length] = encodeURIComponent(e) + "=" + encodeURIComponent(null == n ? "" : n) }; if (null == e) return ""; if (Array.isArray(e) || e.jquery && !k.isPlainObject(e)) k.each(e, function () { i(this.name, this.value) }); else for (n in e) qt(n, e[n], t, i); return r.join("&") }, k.fn.extend({ serialize: function () { return k.param(this.serializeArray()) }, serializeArray: function () { return this.map(function () { var e = k.prop(this, "elements"); return e ? k.makeArray(e) : this }).filter(function () { var e = this.type; return this.name && !k(this).is(":disabled") && jt.test(this.nodeName) && !Dt.test(e) && (this.checked || !pe.test(e)) }).map(function (e, t) { var n = k(this).val(); return null == n ? null : Array.isArray(n) ? k.map(n, function (e) { return { name: t.name, value: e.replace(At, "\r\n") } }) : { name: t.name, value: n.replace(At, "\r\n") } }).get() } }); var Lt = /%20/g, Ht = /#.*$/, Ot = /([?&])_=[^&]*/, Pt = /^(.*?):[ \t]*([^\r\n]*)$/gm, Rt = /^(?:GET|HEAD)$/, Mt = /^\/\//, It = {}, Wt = {}, $t = "*/".concat("*"), Ft = E.createElement("a"); function Bt(o) { return function (e, t) { "string" != typeof e && (t = e, e = "*"); var n, r = 0, i = e.toLowerCase().match(R) || []; if (m(t)) while (n = i[r++]) "+" === n[0] ? (n = n.slice(1) || "*", (o[n] = o[n] || []).unshift(t)) : (o[n] = o[n] || []).push(t) } } function _t(t, i, o, a) { var s = {}, u = t === Wt; function l(e) { var r; return s[e] = !0, k.each(t[e] || [], function (e, t) { var n = t(i, o, a); return "string" != typeof n || u || s[n] ? u ? !(r = n) : void 0 : (i.dataTypes.unshift(n), l(n), !1) }), r } return l(i.dataTypes[0]) || !s["*"] && l("*") } function zt(e, t) { var n, r, i = k.ajaxSettings.flatOptions || {}; for (n in t) void 0 !== t[n] && ((i[n] ? e : r || (r = {}))[n] = t[n]); return r && k.extend(!0, e, r), e } Ft.href = Et.href, k.extend({ active: 0, lastModified: {}, etag: {}, ajaxSettings: { url: Et.href, type: "GET", isLocal: /^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Et.protocol), global: !0, processData: !0, async: !0, contentType: "application/x-www-form-urlencoded; charset=UTF-8", accepts: { "*": $t, text: "text/plain", html: "text/html", xml: "application/xml, text/xml", json: "application/json, text/javascript" }, contents: { xml: /\bxml\b/, html: /\bhtml/, json: /\bjson\b/ }, responseFields: { xml: "responseXML", text: "responseText", json: "responseJSON" }, converters: { "* text": String, "text html": !0, "text json": JSON.parse, "text xml": k.parseXML }, flatOptions: { url: !0, context: !0 } }, ajaxSetup: function (e, t) { return t ? zt(zt(e, k.ajaxSettings), t) : zt(k.ajaxSettings, e) }, ajaxPrefilter: Bt(It), ajaxTransport: Bt(Wt), ajax: function (e, t) { "object" == typeof e && (t = e, e = void 0), t = t || {}; var c, f, p, n, d, r, h, g, i, o, v = k.ajaxSetup({}, t), y = v.context || v, m = v.context && (y.nodeType || y.jquery) ? k(y) : k.event, x = k.Deferred(), b = k.Callbacks("once memory"), w = v.statusCode || {}, a = {}, s = {}, u = "canceled", T = { readyState: 0, getResponseHeader: function (e) { var t; if (h) { if (!n) { n = {}; while (t = Pt.exec(p)) n[t[1].toLowerCase() + " "] = (n[t[1].toLowerCase() + " "] || []).concat(t[2]) } t = n[e.toLowerCase() + " "] } return null == t ? null : t.join(", ") }, getAllResponseHeaders: function () { return h ? p : null }, setRequestHeader: function (e, t) { return null == h && (e = s[e.toLowerCase()] = s[e.toLowerCase()] || e, a[e] = t), this }, overrideMimeType: function (e) { return null == h && (v.mimeType = e), this }, statusCode: function (e) { var t; if (e) if (h) T.always(e[T.status]); else for (t in e) w[t] = [w[t], e[t]]; return this }, abort: function (e) { var t = e || u; return c && c.abort(t), l(0, t), this } }; if (x.promise(T), v.url = ((e || v.url || Et.href) + "").replace(Mt, Et.protocol + "//"), v.type = t.method || t.type || v.method || v.type, v.dataTypes = (v.dataType || "*").toLowerCase().match(R) || [""], null == v.crossDomain) { r = E.createElement("a"); try { r.href = v.url, r.href = r.href, v.crossDomain = Ft.protocol + "//" + Ft.host != r.protocol + "//" + r.host } catch (e) { v.crossDomain = !0 } } if (v.data && v.processData && "string" != typeof v.data && (v.data = k.param(v.data, v.traditional)), _t(It, v, t, T), h) return T; for (i in (g = k.event && v.global) && 0 == k.active++ && k.event.trigger("ajaxStart"), v.type = v.type.toUpperCase(), v.hasContent = !Rt.test(v.type), f = v.url.replace(Ht, ""), v.hasContent ? v.data && v.processData && 0 === (v.contentType || "").indexOf("application/x-www-form-urlencoded") && (v.data = v.data.replace(Lt, "+")) : (o = v.url.slice(f.length), v.data && (v.processData || "string" == typeof v.data) && (f += (St.test(f) ? "&" : "?") + v.data, delete v.data), !1 === v.cache && (f = f.replace(Ot, "$1"), o = (St.test(f) ? "&" : "?") + "_=" + kt++ + o), v.url = f + o), v.ifModified && (k.lastModified[f] && T.setRequestHeader("If-Modified-Since", k.lastModified[f]), k.etag[f] && T.setRequestHeader("If-None-Match", k.etag[f])), (v.data && v.hasContent && !1 !== v.contentType || t.contentType) && T.setRequestHeader("Content-Type", v.contentType), T.setRequestHeader("Accept", v.dataTypes[0] && v.accepts[v.dataTypes[0]] ? v.accepts[v.dataTypes[0]] + ("*" !== v.dataTypes[0] ? ", " + $t + "; q=0.01" : "") : v.accepts["*"]), v.headers) T.setRequestHeader(i, v.headers[i]); if (v.beforeSend && (!1 === v.beforeSend.call(y, T, v) || h)) return T.abort(); if (u = "abort", b.add(v.complete), T.done(v.success), T.fail(v.error), c = _t(Wt, v, t, T)) { if (T.readyState = 1, g && m.trigger("ajaxSend", [T, v]), h) return T; v.async && 0 < v.timeout && (d = C.setTimeout(function () { T.abort("timeout") }, v.timeout)); try { h = !1, c.send(a, l) } catch (e) { if (h) throw e; l(-1, e) } } else l(-1, "No Transport"); function l(e, t, n, r) { var i, o, a, s, u, l = t; h || (h = !0, d && C.clearTimeout(d), c = void 0, p = r || "", T.readyState = 0 < e ? 4 : 0, i = 200 <= e && e < 300 || 304 === e, n && (s = function (e, t, n) { var r, i, o, a, s = e.contents, u = e.dataTypes; while ("*" === u[0]) u.shift(), void 0 === r && (r = e.mimeType || t.getResponseHeader("Content-Type")); if (r) for (i in s) if (s[i] && s[i].test(r)) { u.unshift(i); break } if (u[0] in n) o = u[0]; else { for (i in n) { if (!u[0] || e.converters[i + " " + u[0]]) { o = i; break } a || (a = i) } o = o || a } if (o) return o !== u[0] && u.unshift(o), n[o] }(v, T, n)), s = function (e, t, n, r) { var i, o, a, s, u, l = {}, c = e.dataTypes.slice(); if (c[1]) for (a in e.converters) l[a.toLowerCase()] = e.converters[a]; o = c.shift(); while (o) if (e.responseFields[o] && (n[e.responseFields[o]] = t), !u && r && e.dataFilter && (t = e.dataFilter(t, e.dataType)), u = o, o = c.shift()) if ("*" === o) o = u; else if ("*" !== u && u !== o) { if (!(a = l[u + " " + o] || l["* " + o])) for (i in l) if ((s = i.split(" "))[1] === o && (a = l[u + " " + s[0]] || l["* " + s[0]])) { !0 === a ? a = l[i] : !0 !== l[i] && (o = s[0], c.unshift(s[1])); break } if (!0 !== a) if (a && e["throws"]) t = a(t); else try { t = a(t) } catch (e) { return { state: "parsererror", error: a ? e : "No conversion from " + u + " to " + o } } } return { state: "success", data: t } }(v, s, T, i), i ? (v.ifModified && ((u = T.getResponseHeader("Last-Modified")) && (k.lastModified[f] = u), (u = T.getResponseHeader("etag")) && (k.etag[f] = u)), 204 === e || "HEAD" === v.type ? l = "nocontent" : 304 === e ? l = "notmodified" : (l = s.state, o = s.data, i = !(a = s.error))) : (a = l, !e && l || (l = "error", e < 0 && (e = 0))), T.status = e, T.statusText = (t || l) + "", i ? x.resolveWith(y, [o, l, T]) : x.rejectWith(y, [T, l, a]), T.statusCode(w), w = void 0, g && m.trigger(i ? "ajaxSuccess" : "ajaxError", [T, v, i ? o : a]), b.fireWith(y, [T, l]), g && (m.trigger("ajaxComplete", [T, v]), --k.active || k.event.trigger("ajaxStop"))) } return T }, getJSON: function (e, t, n) { return k.get(e, t, n, "json") }, getScript: function (e, t) { return k.get(e, void 0, t, "script") } }), k.each(["get", "post"], function (e, i) { k[i] = function (e, t, n, r) { return m(t) && (r = r || n, n = t, t = void 0), k.ajax(k.extend({ url: e, type: i, dataType: r, data: t, success: n }, k.isPlainObject(e) && e)) } }), k._evalUrl = function (e, t) { return k.ajax({ url: e, type: "GET", dataType: "script", cache: !0, async: !1, global: !1, converters: { "text script": function () { } }, dataFilter: function (e) { k.globalEval(e, t) } }) }, k.fn.extend({ wrapAll: function (e) { var t; return this[0] && (m(e) && (e = e.call(this[0])), t = k(e, this[0].ownerDocument).eq(0).clone(!0), this[0].parentNode && t.insertBefore(this[0]), t.map(function () { var e = this; while (e.firstElementChild) e = e.firstElementChild; return e }).append(this)), this }, wrapInner: function (n) { return m(n) ? this.each(function (e) { k(this).wrapInner(n.call(this, e)) }) : this.each(function () { var e = k(this), t = e.contents(); t.length ? t.wrapAll(n) : e.append(n) }) }, wrap: function (t) { var n = m(t); return this.each(function (e) { k(this).wrapAll(n ? t.call(this, e) : t) }) }, unwrap: function (e) { return this.parent(e).not("body").each(function () { k(this).replaceWith(this.childNodes) }), this } }), k.expr.pseudos.hidden = function (e) { return !k.expr.pseudos.visible(e) }, k.expr.pseudos.visible = function (e) { return !!(e.offsetWidth || e.offsetHeight || e.getClientRects().length) }, k.ajaxSettings.xhr = function () { try { return new C.XMLHttpRequest } catch (e) { } }; var Ut = { 0: 200, 1223: 204 }, Xt = k.ajaxSettings.xhr(); y.cors = !!Xt && "withCredentials" in Xt, y.ajax = Xt = !!Xt, k.ajaxTransport(function (i) { var o, a; if (y.cors || Xt && !i.crossDomain) return { send: function (e, t) { var n, r = i.xhr(); if (r.open(i.type, i.url, i.async, i.username, i.password), i.xhrFields) for (n in i.xhrFields) r[n] = i.xhrFields[n]; for (n in i.mimeType && r.overrideMimeType && r.overrideMimeType(i.mimeType), i.crossDomain || e["X-Requested-With"] || (e["X-Requested-With"] = "XMLHttpRequest"), e) r.setRequestHeader(n, e[n]); o = function (e) { return function () { o && (o = a = r.onload = r.onerror = r.onabort = r.ontimeout = r.onreadystatechange = null, "abort" === e ? r.abort() : "error" === e ? "number" != typeof r.status ? t(0, "error") : t(r.status, r.statusText) : t(Ut[r.status] || r.status, r.statusText, "text" !== (r.responseType || "text") || "string" != typeof r.responseText ? { binary: r.response } : { text: r.responseText }, r.getAllResponseHeaders())) } }, r.onload = o(), a = r.onerror = r.ontimeout = o("error"), void 0 !== r.onabort ? r.onabort = a : r.onreadystatechange = function () { 4 === r.readyState && C.setTimeout(function () { o && a() }) }, o = o("abort"); try { r.send(i.hasContent && i.data || null) } catch (e) { if (o) throw e } }, abort: function () { o && o() } } }), k.ajaxPrefilter(function (e) { e.crossDomain && (e.contents.script = !1) }), k.ajaxSetup({ accepts: { script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" }, contents: { script: /\b(?:java|ecma)script\b/ }, converters: { "text script": function (e) { return k.globalEval(e), e } } }), k.ajaxPrefilter("script", function (e) { void 0 === e.cache && (e.cache = !1), e.crossDomain && (e.type = "GET") }), k.ajaxTransport("script", function (n) { var r, i; if (n.crossDomain || n.scriptAttrs) return { send: function (e, t) { r = k(" + +EOF + +rm -rf build/ && sphinx-multiversion source build/html && cp -rf build/html/master/* build/html/ diff --git a/source/_templates/versions.html b/source/_templates/versions.html new file mode 100644 index 0000000..31a1257 --- /dev/null +++ b/source/_templates/versions.html @@ -0,0 +1,27 @@ +{%- if current_version %} +
+ + Other Versions + v: {{ current_version.name }} + + +
+ {%- if versions.tags %} +
+
Tags
+ {%- for item in versions.tags %} +
{{ item.name }}
+ {%- endfor %} +
+ {%- endif %} + {%- if versions.branches %} +
+
Branches
+ {%- for item in versions.branches %} +
{{ item.name }}
+ {%- endfor %} +
+ {%- endif %} +
+
+{%- endif %} diff --git a/source/conf.py b/source/conf.py index fdcb6f7..66e5159 100644 --- a/source/conf.py +++ b/source/conf.py @@ -28,7 +28,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['chinese_search','sphinx.ext.mathjax', 'sphinx_sitemap'] +extensions = ['chinese_search','sphinx.ext.mathjax', 'sphinx_sitemap', 'sphinx_multiversion'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -127,28 +127,25 @@ sys.path.append(os.path.abspath(_exts)) html_js_files = [ - 'js/readmore.js', + 'js/readmore.js', 'js/baidutongji.js', ] -# General configuration. -with open("/home/docs/checkouts/readthedocs.org/user_builds/pythoncodingtime/envs/latest/lib/python3.7/site-packages/sphinxcontrib/disqus.py", "r") as file: - content = file.read() - content=content.replace("sphinx.application", "sphinx.errors") - -with open("/home/docs/checkouts/readthedocs.org/user_builds/pythoncodingtime/envs/latest/lib/python3.7/site-packages/sphinxcontrib/disqus.py", "w") as file: - file.write(content) author = '王炳明' -copyright = '2020, Python编程时光' +copyright = '2020-2024, Python编程时光' exclude_patterns = ['_build'] -extensions = ['sphinxcontrib.disqus'] # Add to this list. master_doc = 'index' project = 'Python编程时光' -release = '1.0' -version = '1.0' # Options for extensions. -disqus_shortname = 'iswbm' # Add this line to conf.py. -html_baseurl = 'http://pythontime.iswbm.com' +html_baseurl = 'https://magic.iswbm.com' html_extra_path = ["robots.txt"] + +html_sidebars = { + '**': [ + 'versioning.html', + ], +} +smv_latest_version = 'master' +sitemap_url_scheme = "{link}" From 07ecd0ea6bc99c5d821c28fc560f551c0848d711 Mon Sep 17 00:00:00 2001 From: iswbm Date: Mon, 26 Jan 2026 22:12:53 +0800 Subject: [PATCH 301/302] update requirements.txt to python3.6.8 --- rebuild.sh | 2 +- requirements.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rebuild.sh b/rebuild.sh index 3ceedd8..319b303 100755 --- a/rebuild.sh +++ b/rebuild.sh @@ -1,4 +1,4 @@ -cat << EOF >/usr/local/lib/python3.10/site-packages/sphinx_rtd_theme/comments.html +cat << EOF >/usr/local/lib/python3.6/site-packages/sphinx_rtd_theme/comments.html