From ac2277bbccb12e9483bc24cdd953985053efe96e Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 10 Mar 2019 20:21:07 +0800 Subject: [PATCH 001/329] add files --- source/c01/c01_14.md | 198 ++++++++++++++++++++++++++++++++ source/c02/c02_12.md | 90 +++++++++++++++ source/c03/c03_05.md | 263 +++++++++++++++++++++++++++++++++++++++++++ source/c04/c04_10.md | 26 +++++ source/c07/c07_02.md | 28 +++-- source/c08/c08_01.md | 2 +- source/c08/c08_05.md | 106 +++++++++++++++++ 7 files changed, 702 insertions(+), 11 deletions(-) create mode 100644 source/c01/c01_14.md create mode 100644 source/c02/c02_12.md create mode 100644 source/c03/c03_05.md create mode 100644 source/c08/c08_05.md diff --git a/source/c01/c01_14.md b/source/c01/c01_14.md new file mode 100644 index 0000000..83a1b99 --- /dev/null +++ b/source/c01/c01_14.md @@ -0,0 +1,198 @@ +# 1.14 with 与 上下文管理器 + +> 提示:前面的内容较为基础,重点知识在后半段。 + +`with` 这个关键字,对于每一学习Python的人,都不会陌生。 + +操作文本对象的时候,几乎所有的人都会让我们要用 `with open` ,这就是一个上下文管理的例子。你一定已经相当熟悉了,我就不再废话了。 + +```python +with open('test.txt') as f: + print f.readlines() +``` + +## 1.14.1 what context manager? + +**基本语法** + +```python +with EXPR as VAR: + BLOCK +``` + +先理清几个概念 + +``` +1. 上下文表达式:with open('test.txt') as f: +2. 上下文管理器:open('test.txt') +3. f 不是上下文管理器,应该是资源对象。 +``` + +## 1.14.2 how context manager? + +要自己实现这样一个上下文管理,要先知道上下文管理协议。 + +简单点说,就是在一个类里,实现了`__enter__`和`__exit__`的方法,这个类的实例就是一个上下文管理器。 + +例如这个示例: + +```python +class Resource(): + def __enter__(self): + print('===connect to resource===') + return self + def __exit__(self, exc_type, exc_val, exc_tb): + print('===close resource connection===') + + def operate(self): + print('===in operation===') + +with Resource() as res: + res.operate() +``` + +我们执行一下,通过日志的打印顺序。可以知道其执行过程。 + +``` +===connect to resource=== +===in operation=== +===close resource connection=== +``` + +从这个示例可以很明显的看出,在编写代码时,可以将资源的连接或者获取放在`__enter__`中,而将资源的关闭写在`__exit__` 中。 + +## 1.14.3 why context manager? + +学习时多问自己几个为什么,养成对一些细节的思考,有助于加深对知识点的理解。 + +为什么要使用上下文管理器? + +在我看来,这和 Python 崇尚的优雅风格有关。 + +1. 可以以一种更加优雅的方式,操作(创建/获取/释放)资源,如文件操作、数据库连接; +2. 可以以一种更加优雅的方式,处理异常; + +第一种,我们上面已经以资源的连接为例讲过了。 + +而第二种,会被大多数人所忽略。这里会重点讲一下。 + +大家都知道,处理异常,通常都是使用 `try...execept..` 来捕获处理的。这样做一个不好的地方是,在代码的主逻辑里,会有大量的异常处理代理,这会很大的影响我们的可读性。 + +好一点的做法呢,可以使用 `with` 将异常的处理隐藏起来。 + +仍然是以上面的代码为例,我们将`1/0` 这个`一定会抛出异常的代码`写在 `operate` 里 + +```python +class Resource(): + def __enter__(self): + print('===connect to resource===') + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + print('===close resource connection===') + return True + + def operate(self): + 1/0 + +with Resource() as res: + res.operate() +``` + +运行一下,惊奇地发现,居然不会报错。 + +这就是上下文管理协议的一个强大之处,异常可以在`__exit__` 进行捕获并由你自己决定如何处理,是抛出呢还是在这里就解决了。在`__exit__` 里返回 `True`(没有return 就默认为 return False),就相当于告诉 Python解释器,这个异常我们已经捕获了,不需要再往外抛了。 + +在 写`__exit__` 函数时,需要注意的事,它必须要有这三个参数: + +- exc_type:异常类型 +- exc_val:异常值 +- exc_tb:异常的错误栈信息 + +当主逻辑代码没有报异常时,这三个参数将都为None。 + +## 1.14.4 how contextlib? + +在上面的例子中,我们只是为了构建一个上下文管理器,却写了一个类。如果只是要实现一个简单的功能,写一个类未免有点过于繁杂。这时候,我们就想,如果只写一个函数就可以实现上下文管理器就好了。 + +这个点Python早就想到了。它给我们提供了一个装饰器,你只要按照它的代码协议来实现函数内容,就可以将这个函数对象变成一个上下文管理器。 + +我们按照 contextlib 的协议来自己实现一个打开文件(with open)的上下文管理器。 + +```python +import contextlib + +@contextlib.contextmanager +def open_func(file_name): + # __enter__方法 + print('open file:', file_name, 'in __enter__') + file_handler = open(file_name, 'r') + + # 【重点】:yield + yield file_handler + + # __exit__方法 + print('close file:', file_name, 'in __exit__') + file_handler.close() + return + +with open_func('/Users/MING/mytest.txt') as file_in: + for line in file_in: + print(line) +``` + +在被装饰函数里,必须是一个生成器(带有yield),而yield之前的代码,就相当于`__enter__`里的内容。yield 之后的代码,就相当于`__exit__` 里的内容。 + +上面这段代码只能实现上下文管理器的第一个目的(管理资源),并不能实现第二个目的(处理异常)。 + +如果要处理异常,可以改成下面这个样子。 + +```python +import contextlib + +@contextlib.contextmanager +def open_func(file_name): + # __enter__方法 + print('open file:', file_name, 'in __enter__') + file_handler = open(file_name, 'r') + + try: + yield file_handler + except Exception as exc: + # deal with exception + print('the exception was thrown') + finally: + print('close file:', file_name, 'in __exit__') + file_handler.close() + + return + +with open_func('/Users/MING/mytest.txt') as file_in: + for line in file_in: + 1/0 + print(line) +``` + +好像只要讲到上下文管理器,大多数人都会谈到打开文件这个经典的例子。 + +但是在实际开发中,可以使用到上下文管理器的例子也不少。我这边举个我自己的例子。 + +在OpenStack中,给一个虚拟机创建快照时,需要先创建一个临时文件夹,来存放这个本地快照镜像,等到本地快照镜像创建完成后,再将这个镜像上传到Glance。然后删除这个临时目录。 + +这段代码的主逻辑是`创建快照`,而`创建临时目录 `,属于前置条件,`删除临时目录`,是收尾工作。 + +虽然代码量很少,逻辑也不复杂,但是“`创建临时目录,使用完后再删除临时目录`”这个功能,在一个项目中很多地方都需要用到,如果可以将这段逻辑处理写成一个工具函数作为一个上下文管理器,那代码的复用率也大大提高。 + +代码是这样的 + +![](http://image.python-online.cn/20190310172800.png) + +总结起来,使用上下文管理器有三个好处: + +1. 提高代码的复用率; +2. 提高代码的优雅度; +3. 提高代码的可读性; + +------ + +![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file diff --git a/source/c02/c02_12.md b/source/c02/c02_12.md new file mode 100644 index 0000000..80b48b2 --- /dev/null +++ b/source/c02/c02_12.md @@ -0,0 +1,90 @@ +# 2.12 生成器与协程,别再傻傻分不清了 + + + +之前写的 `并发编程` 系列文章,是我迄今务止,得到的反馈最好的一个系列,也是因为它我才能认识这么多优秀的朋友。 + +但是可能由于知识诅咒的原因,我在协程那里并没有详细讲解生成器和协程的关系,不少人也因此还是懵懵懂懂的状态。 + +刚好前两天,有位读者朋友问我这个问题,我觉得有必要写一篇文章来讲解一下。 + +如果你是小白,刚接触这块知识的话,那么为你防止你更好的理解今天的内容,你最好预先下之前的写的入门级文章:[从生成器使用入门协程(七)](https://dwz.cn/0QoCk0NB) + +最好的理解方式,莫过于用例子做个对比。 + +如你所见,下面这代码将定义一个生成器的。 + +```python +import time + +def eat(): + while True: + if food: + print("小明 吃完{}了".format(food)) + yield + print("小明 要开始吃{}...".format(food)) + time.sleep(1) + +food = None +MING = eat() # 产生一个生成器 +MING.send(None) # 预激 +food = "面包" +MING.send('面包') +MING.send('苹果') +MING.send('香肠') +``` + +运行一下,从结果中可以看出,不管我们塞给小明什么东西,小明都将只能将他们当成面包吃。 + +``` +小明 要开始吃面包... +小明 吃完面包了 +小明 要开始吃面包... +小明 吃完面包了 +小明 要开始吃面包... +小明 吃完面包了 +``` + + + +那再来看一下协程的。 + +```python +import time + +def eat(): + food = None + while True: + if food: + print("小明 吃完{}了".format(food)) + food = yield + print("小明 开始吃{}...".format(food)) + time.sleep(1) + +MING = eat() # 产生一个生成器 +MING.send(None) # 预激 +MING.send('面包') +MING.send('苹果') +MING.send('香肠') +``` + +运行一下,从结果中可以看出,小明已经可以感知我们塞给他的是什么食物。 + +``` +小明 开始吃面包... +小明 吃完面包了 +小明 开始吃苹果... +小明 吃完苹果了 +小明 开始吃香肠... +小明 吃完香肠了 +``` + + + +仔细观察一下,上面两段代码并没有太大的区别,我们将主要关注点集中在 `yidld` 关键词上。 + +可以发现,生成器里 `yield` 左边并没有变量,而在协程里,`yield` 左边有一个变量。 + +在函数被调用后,一个生成器就产生了,而一般的生成器不能再往生成器内部传递参数了,而这个当生成器里的 yield 左边有变量时,就不一样了,它仍然可以在外部接收新的参数。这就是生成器与协程的最大区别。 + +说到协程,你可能会问 \ No newline at end of file diff --git a/source/c03/c03_05.md b/source/c03/c03_05.md new file mode 100644 index 0000000..073a5f1 --- /dev/null +++ b/source/c03/c03_05.md @@ -0,0 +1,263 @@ +# 3.5 源码解读:Flask上下文与代理模式 + +在 Flask 中有三个非常重要的对象,你肯定不会感到陌生。 + +- Local +- LocalProxy +- LocalStack + +今天就要来理解一下这三个都是什么,他们在 Flask 中所担任的角色,以及和这三者息息相关的上下文机制。 + +## 3.5.1 Local + +首先来讲一下`Local` ,记得在以前的「并发编程系列」的第五篇有讲过线程的`信息隔离`。和这里的`Local`是一个作用 + +什么是`信息隔离`呢?比如说现有两个线程,线程A里的运行环境,和线程B里运行环境变量不能共享。这就是`信息隔离`。 + +在线程中,是通过`threading` 模块自带的`local`对象来存储的。但有一个问题是这个`local`的隔离是线程级别的。 + +而在web开发中,线程级别的隔离已经不能满足要求了,因为多个请求是很有可能会跑在同一个线程中的。在web应用里中,我们需要的是请求级别的隔离。这个时候`werkzeug` 的`Local`就派上用场了,它就是用来实现请求级别变量的隔离。一个请求会对应一个Local 对象。 + +Local 是请求级别的对象,当一个请求结束时,需要有更高一层的管理器对这个请求的数据做一些清理回收。这个管理器就叫做:`LocalManager`,除此之外,还可以通过 `local_manager.locals.append(local)` 动态添加local对象。 + +下面是一个精简示例,`make_middleware` 函数是主角。 + +```python +from werkzeug.local import Local, LocalManager + +local = Local() +local_manager = LocalManager([local]) + +def application(environ, start_response): + local.request = request = Request(environ) + ... + +# make_middleware会确保当request结束时,所有存储于local中的对象的reference被清除 +application = local_manager.make_middleware(application) +``` + + + +## 3.5.2 LocalStack + +`LocalStack` ,从命名上,可以知道,他是一个栈结构的对象。它存储的主要有两种上下文,`AppContext` 和 `RequestContext`。 + +当一个请求发起后,flask会把RequestContext推入一个LocalStack中(`_request_ctx_stack`),而在推入之前,其实它会去检测另一个LocalStack(`_app_ctx_stack`)是否为空,如果为空就先将app的上下文信息push到`_app_ctx_stack`,然后再去把请求的上下文信息push到`_request_ctx_stack` 里。 + +在flask中有三个对象比较常用 + +- current_app +- request +- session + +这三个对象,永远是指向`LocalStack` 栈顶的上下文中对应的app、request或者session,对应的源码如下: + +```python +def _lookup_req_object(name): + top = _request_ctx_stack.top + if top is None: + raise RuntimeError(_request_ctx_err_msg) + return getattr(top, name) + +def _find_app(): + top = _app_ctx_stack.top + if top is None: + raise RuntimeError(_app_ctx_err_msg) + return top.app + +_request_ctx_stack = LocalStack() +_app_ctx_stack = LocalStack() +current_app = LocalProxy(_find_app) +request = LocalProxy(partial(_lookup_req_object, 'request')) +session = LocalProxy(partial(_lookup_req_object, 'session')) +``` + +## 3.5.3 LocalProxy + +通过上面的代码,你可以发现,我们访问LocalStack里的元素的时候,都是通过`LocalProxy` 来进行的有没有? + +这就很奇怪了,为什么不直接访问`Local` 和 `LocalStack`呢? + +这应该是个难点,我这边举个例子,也许你就明白了。 + +首先是不使用LocalProxy的情况 + +```python +# use Local object directly +from werkzeug.local import LocalStack +user_stack = LocalStack() +user_stack.push({'name': 'Bob'}) +user_stack.push({'name': 'John'}) + +def get_user(): + # do something to get User object and return it + return user_stack.pop() + + +# 直接调用函数获取user对象 +user = get_user() +print user['name'] +print user['name'] +``` + +输出结果是 + +``` +John +John +``` + +使用LocalProxy后 + +```python +# use LocalProxy +from werkzeug.local import LocalStack, LocalProxy +user_stack = LocalStack() +user_stack.push({'name': 'Bob'}) +user_stack.push({'name': 'John'}) + +def get_user(): + # do something to get User object and return it + return user_stack.pop() + +# 通过LocalProxy使用user对象 +user = LocalProxy(get_user) +print user['name'] +print user['name'] +``` + +输出结果 + +``` +John +Bob +``` + +怎么样,看出区别了吧,直接使用LocalStack对象,user一旦赋值就无法再动态更新了,而使用Proxy,每次调用操作符(这里`[]操作符`用于获取属性),都会重新获取user,从而实现了动态更新user的效果。 + +每次 `user['name']` 的时候 就会触发 `LocalProxy` 类的 `__getitem__ `,从而调用该类的 `_get_current_object `。而每次 `_get_current_object `都会返回 `get_user()`(在flask中对应的函数是 `_lookup_req_object` ) 的执行结果, 也就是 `user_stack.pop()` + +```python +def __init__(self, local, name=None): + # 【重要】将local对象(也就是一个get_user函数对象)赋值给self.__local + object.__setattr__(self, '_LocalProxy__local', local) + object.__setattr__(self, '__name__', name) + if callable(local) and not hasattr(local, '__release_local__'): + # "local" is a callable that is not an instance of Local or + # LocalManager: mark it as a wrapped function. + object.__setattr__(self, '__wrapped__', local) + +def _get_current_object(self): + """Return the current object. This is useful if you want the real + object behind the proxy at a time for performance reasons or because + you want to pass the object into a different context. + """ + if not hasattr(self.__local, '__release_local__'): + # 【重要】执行传递进行的 get_user 对象。 + return self.__local() + try: + return getattr(self.__local, self.__name__) + except AttributeError: + raise RuntimeError('no object bound to %s' % self.__name__) +``` + +这样就能实现每次对栈顶元素的操作,都是面对最新元素执行的。 + +## 3.5.4 经典错误 + +在 Flask 中经常会遇到的一个错误是: + +> Working outside of application context. + +这个错误,如果没有理解 flask 的上下文机制,是很难理解的。通过上面知识背景的铺垫,我们可以尝试来搞懂一下为什么会出现这样的情况。 + +首先我们先来模拟一下这个错误的产生。假设现在有一个单独的文件,内容如下 + +```python +from flask import current_app + +app = Flask(__name__) + +app = current_app +print(app.config['DEBUG']) +``` + +运行一下,会报如下错误。 + +```python +Traceback (most recent call last): + File "/Users/MING/PycharmProjects/fisher/app/mytest/mytest.py", line 19, in + print(app.config['DEBUG']) + File "/Users/MING/.virtualenvs/fisher-gSdA58aK/lib/python3.6/site-packages/werkzeug/local.py", line 347, in __getattr__ + return getattr(self._get_current_object(), name) + File "/Users/MING/.virtualenvs/fisher-gSdA58aK/lib/python3.6/site-packages/werkzeug/local.py", line 306, in _get_current_object + return self.__local() + File "/Users/MING/.virtualenvs/fisher-gSdA58aK/lib/python3.6/site-packages/flask/globals.py", line 51, in _find_app + raise RuntimeError(_app_ctx_err_msg) +RuntimeError: Working outside of application context. +``` + +你一定会奇怪吧。我明明也实例化一个app对象,但是为什么取current_app会报错呢?而如果不用current_app,就不会报错。 + +如果你认真学习了上面的内容,这边也就不难理解了。 + +从先前的研究发现,当使用`current_app`时,它取的是`LocalStack`的栈顶元素(app的上下文信息),而实际上在我们通过`app = Flask(__name__)`实例化一个app对象时,此时还没有将这个上下文信息写入`LocalProxy`,自然取栈顶元素就会出错了。 + +```python +def _find_app(): + top = _app_ctx_stack.top + if top is None: + raise RuntimeError(_app_ctx_err_msg) + return top.app +``` + +上面我们也说过了,这个上下文什么时候push进去呢?在外部发起一起request请求后,首先就会先检查 app 的上下文信息是否已经 push 进去了,如果没有的话,就会先半其push进去。 + +而上面我们是以运行单个文件的方式,并没有实际产生一个 request 请求,自然 在 `LocalStack` 里没有 app的上下文信息。报错也是正常的。 + +知道了错误根源后,如何解决这种问题呢? + +在Flask中,它提供了一个方法`ctx=app.app_context()`可以获取一个上下文对象,我们只要将这个上下文对象 手动 push 到 `LocalStack` 中,`current_app` 也就可以正常取到我们的app对象了。 + +```python +from flask import Flask, current_app + +app = Flask(__name__) +ctx = app.app_context() +ctx.push() + +app = current_app +print(app.config['DEBUG']) +ctx.pop() +``` + +由于 AppContext 类实现了上下文协议 + +```python +class AppContext(object): + def __enter__(self): + self.push() + return self + + def __exit__(self, exc_type, exc_value, tb): + self.pop(exc_value) + + if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None: + reraise(exc_type, exc_value, tb) +``` + +所以你也可以这样写 + +```python +from flask import Flask, current_app + +app = Flask(__name__) + +with app.app_context(): + app = current_app + print(app.config['DEBUG']) +``` + + + +以上,是我通过学习七月的 `Flask高级编程` 加上自己直白的理解,希望对你在理解 Flask的上下文核心机制 会有帮助。 \ No newline at end of file diff --git a/source/c04/c04_10.md b/source/c04/c04_10.md index 3141964..da8537b 100644 --- a/source/c04/c04_10.md +++ b/source/c04/c04_10.md @@ -438,6 +438,32 @@ SELECT ... FROM table WHERE EXISTS (subquery) ![](https://i.loli.net/2017/08/27/59a2784f6562d.png) +## 六、系统查询 + +--- + +**查询各库的使用量** + +```mysql +select TABLE_SCHEMA, concat(truncate(sum(data_length)/1024/1024,2),' MB') as data_size, +concat(truncate(sum(index_length)/1024/1024,2),' MB') as index_size +from information_schema.tables +group by TABLE_SCHEMA +order by data_length desc; +``` + +**查询一个库中各个表的使用量** + +```mysql +SELECT CONCAT(table_schema,'.',table_name) AS 'Table Name', + CONCAT(ROUND(table_rows/1000000,4),'M') AS 'Number of Rows', + CONCAT(ROUND(data_length/(1024*1024*1024),4),'G') AS 'Data Size', + CONCAT(ROUND(index_length/(1024*1024*1024),4),'G') AS 'Index Size', + CONCAT(ROUND((data_length+index_length)/(1024*1024*1024),4),'G') AS 'Total' +FROM information_schema.TABLES +WHERE table_schema LIKE '%zabbix%' ORDER BY Total desc; +``` + --- diff --git a/source/c07/c07_02.md b/source/c07/c07_02.md index 08af42b..17ff656 100644 --- a/source/c07/c07_02.md +++ b/source/c07/c07_02.md @@ -259,8 +259,6 @@ zabbix ALL=NOPASSWD: ALL {$host:agent.ping.nodata(5m)}=1 and {$host:agent.ping.prev()}=1 ``` - - ### 7.2 自定义脚本获取 自定义监控项,需要有一些脚本支持获取数据。 @@ -360,8 +358,6 @@ chown zabbix:zabbix sendmail.sh chmod +x sendmail.sh ``` - - #### 8.2.4 添加媒介 Administration - Media Types - Create media type @@ -374,8 +370,6 @@ Administration - Users - Admin - Media 添加收件人邮箱 - - #### 8.2.6 定义Action Configuration - Actions - Create action @@ -398,8 +392,6 @@ Passwd=123456 里面选择 WS_Email,添加 邮箱。 - - ### 8.1 监控配置 登陆 server 端 的 web 界面。 @@ -517,8 +509,6 @@ cp server1 web文件到server2 web目录中。 - /etc/httpd/conf.d/zabbix.conf - /etc/httpd/conf/httpd.conf - - ### 10.3 keepalived安装 参考文档:[keepalived搭建zabbix server双机高可用](https://segmentfault.com/a/1190000008684320) @@ -688,6 +678,24 @@ fi - 单台节点宕机(可以关闭keepalived服务模拟,也可以关机) - 双台节点宕机(不管是哪一台先启) +### 10.4 数据清理 + +```mysql +# 查询各监控项的占用情况 +select items.name,history.count from (select itemid,count(itemid) as count from history_uint group by itemid) as history,items where items.itemid=history.itemid; + +# 查询zabbix库表的占用情况 +SELECT CONCAT(table_schema,'.',table_name) AS 'Table Name', + CONCAT(ROUND(table_rows/1000000,4),'M') AS 'Number of Rows', + CONCAT(ROUND(data_length/(1024*1024*1024),4),'G') AS 'Data Size', + CONCAT(ROUND(index_length/(1024*1024*1024),4),'G') AS 'Index Size', + CONCAT(ROUND((data_length+index_length)/(1024*1024*1024),4),'G') AS 'Total' +FROM information_schema.TABLES +WHERE table_schema LIKE '%zabbix%' ORDER BY Total desc; +``` + + + ## 十一、高效运维 --- diff --git a/source/c08/c08_01.md b/source/c08/c08_01.md index f0f58bf..36c241f 100644 --- a/source/c08/c08_01.md +++ b/source/c08/c08_01.md @@ -128,7 +128,7 @@ $ neutron subnet-create [--name ming-subnet(通常不写自动生成)] \ private PROVIDER_NETWORK_CIDR # 创建子网,更多选项可以查看 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 +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 # 查看网络 $ neutron net-show diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md new file mode 100644 index 0000000..5264e3e --- /dev/null +++ b/source/c08/c08_05.md @@ -0,0 +1,106 @@ +# 8.5 OpenStack 源码剖析 + +## 虚拟机是如何创建出来的? + +## rpc 是如何调用的? + +## 创建快照代码解读? + + + +## 虚拟机状态 + +vm_state 描述虚拟机当前的稳定状态,其值可以在 `nova/compute/vm_states.py`看到 + +``` +ACTIVE +BUILDING +PAUSED +SUSPENDED +STOPPED +RESCUED +RESIZED +SOFT_DELETED +DELETED +ERROR +SHELVED +``` + +power_state 描述的是从hypervisor传过来的状态,其值可在`nova/compute/power_state.py` + +``` +NOSTATE +RUNNING +PAUSED +SHUTDOWN +CRASHED +SUSPENDED +``` + +task_state 描述的是中间任务状态, + +``` +spawning +networking +scheduling +block_device_mapping +``` + +在创建虚拟机时,会有几次会产生虚拟机状态(vm_state和task_state)的上报(到ceilomet er)。 + +nova 提供了一个配置项:notify_on_state_change,本意是想,如果配置`vm_state`就只在vm_state + +第一次,在`manager.py:2050`的函数 `_do_build_and_run_instance`里,看instance.save()大 + + + +## 快照镜像 + +入口函数在:`nova/virt/libvirt/driver.py:snapshot()` + +会先获取imagebackend的类型,然后找到对应的backend + +```python +disk_path, source_format = libvirt_utils.find_disk(virt_dom) +source_type = libvirt_utils.get_disk_type_from_path(disk_path) +... +snapshot_backend = self.image_backend.snapshot(instance, + disk_path, + image_type=source_type) +``` + +接下来,会调用对应的imagebackend的`snapshot_extract` 方法。 + +![](http://image.python-online.cn/FhRPy4B1xEI9SfoD2RcunJl15ZE3) + +`snapshot_extract` 方法最终会调用`nova/virt/images.py:_convert_image()` ,可以看出其实底层调用的是 `qemu-img` 提供的`convert` 接口。 + +![](http://image.python-online.cn/FuyMWZS6HF4g3rPwTlLcereZxg4L) + +如果是qcow2的backend,不是调用这边,而是调用 `nova/virt/libvirt/utils.py:extract_snapshot()` + +![1551944122412](C:\Users\wangbm\AppData\Roaming\Typora\typora-user-images\1551944122412.png) + +如果要查询镜像压缩的时间,可以在compute上执行这个命令 + +``` +grep -E "Start executing commands|End executing commands" /var/log/nova/nova-compute.log +``` + +以上,就是整个镜像创建的过程。 + +独立磁盘模式的暂时不支持,原因分析如下。 + +在`libvirt_utils.get_disk_type_from_path` 里没有相应的修改,导致返回的是lvm。 + +![](http://image.python-online.cn/FnJA8RNIvJN2lAEXbKtJDpOLg1vg) + +后面的imagebackend也相应的变成 lvm的 + +![](http://image.python-online.cn/FnGyI8jCQFLCGi0pGVmI3SV6pDrv) + +然后会进入 lvm这个backend的init函数。由于`path` 是`/dev/sdb` 并不是一个lv,所以这边会报错。 + +![1551940635806](C:\Users\wangbm\AppData\Roaming\Typora\typora-user-images\1551940635806.png) + +下次修改方法:一个是最开始获取`source_type`时判断为isolate,一个是后面 `isolate`的`extract_snapshot` 也要和lvm一样实现一下。 \ No newline at end of file From ae3918ad4583e4eeffd5c79c69ed1473dd900c67 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 10 Mar 2019 20:28:12 +0800 Subject: [PATCH 002/329] add files --- source/c01/c01_14.md | 71 ++++++++++++++++++++++++++++++++++++ source/c01/c01_14.rst | 83 +++++++++++++++++++++++++++++++++++++++++++ source/c04/c04_13.rst | 6 ++++ source/c07/c07_01.md | 8 +++++ source/c07/c07_01.rst | 8 +++++ source/c08/c08_01.rst | 4 +-- 6 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 source/c01/c01_14.md create mode 100644 source/c01/c01_14.rst diff --git a/source/c01/c01_14.md b/source/c01/c01_14.md new file mode 100644 index 0000000..07679f2 --- /dev/null +++ b/source/c01/c01_14.md @@ -0,0 +1,71 @@ +# 1.14 提升Python性能的7个习惯 + +--- + +> 转载自:https://zhuanlan.zhihu.com/p/38160586 + +## 1.14.1 使用局部变量 + +尽量使用局部变量代替全局变量:便于维护,提高性能并节省内存。 + +使用局部变量替换模块名字空间中的变量,例如 ls = os.linesep。一方面可以提高程序性能,局部变量查找速度更快;另一方面可用简短标识符替代冗长的模块变量,提高可读性。 + +## 1.14.2 减少函数调用次数 + +对象类型判断时,采用isinstance()最优,采用对象类型身份(id())次之,采用对象值(type())比较最次。 + +``` +#判断变量num是否为整数类型type(num) == type(0) #调用三次函数type(num) is type(0) #身份比较isinstance(num,(int)) #调用一次函数 +``` + +不要在重复操作的内容作为参数放到循环条件中,避免重复运算。 + +``` +#每次循环都需要重新执行len(a)while i < len(a): statement#len(a)仅执行一次m = len(a)while i < m: statement +``` + +如需使用模块X中的某个函数或对象Y,应直接使用from X import Y,而不是import X; X.Y。这样在使用Y时,可以减少一次查询(解释器不必首先查找到X模块,然后在X模块的字典中查找Y)。 + +## 1.14.3 采用映射替代条件查找 + +映射(比如dict等)的搜索速度远快于条件语句(如if等)。Python中也没有select-case语句。 + +``` +#if查找if a == 1: b = 10elif a == 2: b = 20...#dict查找,性能更优d = {1:10,2:20,...}b = d[a] +``` + +## 1.14.4 直接迭代序列元素 + +对序列(str、list、tuple等),直接迭代序列元素,比迭代元素的索引速度要更快。 + +``` +a = [1,2,3]#迭代元素for item in a: print(item)#迭代索引for i in range(len(a)): print(a[i]) +``` + +## 1.14.5 采用生成器表达式替代列表解析 + +列表解析(list comprehension),会产生整个列表,对大量数据的迭代会产生负面效应。 + +而生成器表达式则不会,其不会真正创建列表,而是返回一个生成器,在需要时产生一个值(延迟计算),对内存更加友好。 + +``` +#计算文件f的非空字符个数#生成器表达式l = sum([len(word) for line in f for word in line.split()])#列表解析l = sum(len(word) for line in f for word in line.split()) +``` + +## 1.14.6 先编译后调用 + +使用eval()、exec()函数执行代码时,最好调用代码对象(提前通过compile()函数编译成字节码),而不是直接调用str,可以避免多次执行重复编译过程,提高程序性能。 + +正则表达式模式匹配也类似,也最好先将正则表达式模式编译成regex对象(通过re.complie()函数),然后再执行比较和匹配。 + +## 1.14.7 模块编程习惯 + +模块中的最高级别Python语句(没有缩进的代码)会在模块导入(import)时执行(不论其是否真的必要执行)。因此,应尽量将模块所有的功能代码放到函数中,包括主程序相关的功能代码也可放到main()函数中,主程序本身调用main()函数。 + +可以在模块的main()函数中书写测试代码。在主程序中,检测name的值,如果为'main'(表示模块是被直接执行),则调用main()函数,进行测试;如果为模块名字(表示模块是被调用),则不进行测试。 + + + +------ + +![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file diff --git a/source/c01/c01_14.rst b/source/c01/c01_14.rst new file mode 100644 index 0000000..f3b1c20 --- /dev/null +++ b/source/c01/c01_14.rst @@ -0,0 +1,83 @@ +1.14 提升Python性能的7个习惯 +============================ + +-------------- + + 转载自:https://zhuanlan.zhihu.com/p/38160586 + +1.14.1 使用局部变量 +------------------- + +尽量使用局部变量代替全局变量:便于维护,提高性能并节省内存。 + +使用局部变量替换模块名字空间中的变量,例如 ls = +os.linesep。一方面可以提高程序性能,局部变量查找速度更快;另一方面可用简短标识符替代冗长的模块变量,提高可读性。 + +1.14.2 减少函数调用次数 +----------------------- + +对象类型判断时,采用isinstance()最优,采用对象类型身份(id())次之,采用对象值(type())比较最次。 + +:: + + #判断变量num是否为整数类型type(num) == type(0) #调用三次函数type(num) is type(0) #身份比较isinstance(num,(int)) #调用一次函数 + +不要在重复操作的内容作为参数放到循环条件中,避免重复运算。 + +:: + + #每次循环都需要重新执行len(a)while i < len(a): statement#len(a)仅执行一次m = len(a)while i < m: statement + +如需使用模块X中的某个函数或对象Y,应直接使用from X import +Y,而不是import X; +X.Y。这样在使用Y时,可以减少一次查询(解释器不必首先查找到X模块,然后在X模块的字典中查找Y)。 + +1.14.3 采用映射替代条件查找 +--------------------------- + +映射(比如dict等)的搜索速度远快于条件语句(如if等)。Python中也没有select-case语句。 + +:: + + #if查找if a == 1: b = 10elif a == 2: b = 20...#dict查找,性能更优d = {1:10,2:20,...}b = d[a] + +1.14.4 直接迭代序列元素 +----------------------- + +对序列(str、list、tuple等),直接迭代序列元素,比迭代元素的索引速度要更快。 + +:: + + a = [1,2,3]#迭代元素for item in a: print(item)#迭代索引for i in range(len(a)): print(a[i]) + +1.14.5 采用生成器表达式替代列表解析 +----------------------------------- + +列表解析(list +comprehension),会产生整个列表,对大量数据的迭代会产生负面效应。 + +而生成器表达式则不会,其不会真正创建列表,而是返回一个生成器,在需要时产生一个值(延迟计算),对内存更加友好。 + +:: + + #计算文件f的非空字符个数#生成器表达式l = sum([len(word) for line in f for word in line.split()])#列表解析l = sum(len(word) for line in f for word in line.split()) + +1.14.6 先编译后调用 +------------------- + +使用eval()、exec()函数执行代码时,最好调用代码对象(提前通过compile()函数编译成字节码),而不是直接调用str,可以避免多次执行重复编译过程,提高程序性能。 + +正则表达式模式匹配也类似,也最好先将正则表达式模式编译成regex对象(通过re.complie()函数),然后再执行比较和匹配。 + +1.14.7 模块编程习惯 +------------------- + +模块中的最高级别Python语句(没有缩进的代码)会在模块导入(import)时执行(不论其是否真的必要执行)。因此,应尽量将模块所有的功能代码放到函数中,包括主程序相关的功能代码也可放到main()函数中,主程序本身调用main()函数。 + +可以在模块的main()函数中书写测试代码。在主程序中,检测name的值,如果为’main’(表示模块是被直接执行),则调用main()函数,进行测试;如果为模块名字(表示模块是被调用),则不进行测试。 + +-------------- + +.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg + :alt: 关注公众号,获取最新干货! + diff --git a/source/c04/c04_13.rst b/source/c04/c04_13.rst index f662595..08c51ed 100644 --- a/source/c04/c04_13.rst +++ b/source/c04/c04_13.rst @@ -362,3 +362,9 @@ arguments)。 modules, "--mode", '-m',只可选'init', 'config', 'final'处理函数:main_modules query, "--name", '-n',处理函数:main_query single,"--name", '-n',--frequency,module_args,处理函数:main_single + +-------------- + +.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg + :alt: 关注公众号,获取最新干货! + diff --git a/source/c07/c07_01.md b/source/c07/c07_01.md index 5e209e1..8f3cdbd 100644 --- a/source/c07/c07_01.md +++ b/source/c07/c07_01.md @@ -859,6 +859,14 @@ root 21957 21955 0 21:27 ? 00:00:00 /usr/sbin/keepalived -D root 22035 12159 0 21:33 pts/0 00:00:00 grep --color=auto keepalived ``` +**清除僵尸进程** + +一个僵尸进程产生的过程是:父进程调用fork创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中。 + +``` +ps -e -o stat,ppid,pid,cmd | grep -e '^[Zz]' | awk '{print $2}' | xargs kill -9 +``` + ### 2.4 设备信息管理 diff --git a/source/c07/c07_01.rst b/source/c07/c07_01.rst index 719d59b..7775c07 100755 --- a/source/c07/c07_01.rst +++ b/source/c07/c07_01.rst @@ -961,6 +961,14 @@ Linux的分区的过程经历以下几个步骤 root 21957 21955 0 21:27 ? 00:00:00 /usr/sbin/keepalived -D root 22035 12159 0 21:33 pts/0 00:00:00 grep --color=auto keepalived +**清除僵尸进程** + +一个僵尸进程产生的过程是:父进程调用fork创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中。 + +:: + + ps -e -o stat,ppid,pid,cmd | grep -e '^[Zz]' | awk '{print $2}' | xargs kill -9 + 2.4 设备信息管理 ~~~~~~~~~~~~~~~~ diff --git a/source/c08/c08_01.rst b/source/c08/c08_01.rst index c3ed604..9d052e3 100755 --- a/source/c08/c08_01.rst +++ b/source/c08/c08_01.rst @@ -1,5 +1,5 @@ -18.1 OpenStack 运维命令 -======================= +8.1 OpenStack 运维命令 +====================== -------------- From 2cac43fc2b41304bafb1a8154490e9bcca6d2dc4 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 10 Mar 2019 20:32:48 +0800 Subject: [PATCH 003/329] add files --- source/c01/c01_14.rst | 214 +++++++++++++++++++++++++++++++++++++++++- source/c01/c01_15.rst | 83 ++++++++++++++++ 2 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 source/c01/c01_15.rst diff --git a/source/c01/c01_14.rst b/source/c01/c01_14.rst index f3b1c20..60c0231 100644 --- a/source/c01/c01_14.rst +++ b/source/c01/c01_14.rst @@ -1,5 +1,4 @@ -1.14 提升Python性能的7个习惯 -============================ +<<<<<<< HEAD # 1.14 提升Python性能的7个习惯 -------------- @@ -76,8 +75,219 @@ comprehension),会产生整个列表,对大量数据的迭代会产生负 可以在模块的main()函数中书写测试代码。在主程序中,检测name的值,如果为’main’(表示模块是被直接执行),则调用main()函数,进行测试;如果为模块名字(表示模块是被调用),则不进行测试。 +======= # 1.14 with 与 上下文管理器 + + 提示:前面的内容较为基础,重点知识在后半段。 + +``with`` 这个关键字,对于每一学习Python的人,都不会陌生。 + +操作文本对象的时候,几乎所有的人都会让我们要用 ``with open`` +,这就是一个上下文管理的例子。你一定已经相当熟悉了,我就不再废话了。 + +.. code:: python + + with open('test.txt') as f: + print f.readlines() + +1.14.1 what context manager? +----------------------------- + +**基本语法** + +.. code:: python + + with EXPR as VAR: + BLOCK + +先理清几个概念 + +:: + + 1. 上下文表达式:with open('test.txt') as f: + 2. 上下文管理器:open('test.txt') + 3. f 不是上下文管理器,应该是资源对象。 + +1.14.2 how context manager? +---------------------------- + +要自己实现这样一个上下文管理,要先知道上下文管理协议。 + +简单点说,就是在一个类里,实现了\ ``__enter__``\ 和\ ``__exit__``\ 的方法,这个类的实例就是一个上下文管理器。 + +例如这个示例: + +.. code:: python + + class Resource(): + def __enter__(self): + print('===connect to resource===') + return self + def __exit__(self, exc_type, exc_val, exc_tb): + print('===close resource connection===') + + def operate(self): + print('===in operation===') + + with Resource() as res: + res.operate() + +我们执行一下,通过日志的打印顺序。可以知道其执行过程。 + +:: + + ===connect to resource=== + ===in operation=== + ===close resource connection=== + +从这个示例可以很明显的看出,在编写代码时,可以将资源的连接或者获取放在\ ``__enter__``\ 中,而将资源的关闭写在\ ``__exit__`` +中。 + +1.14.3 why context manager? +---------------------------- + +学习时多问自己几个为什么,养成对一些细节的思考,有助于加深对知识点的理解。 + +为什么要使用上下文管理器? + +在我看来,这和 Python 崇尚的优雅风格有关。 + +1. 可以以一种更加优雅的方式,操作(创建/获取/释放)资源,如文件操作、数据库连接; +2. 可以以一种更加优雅的方式,处理异常; + +第一种,我们上面已经以资源的连接为例讲过了。 + +而第二种,会被大多数人所忽略。这里会重点讲一下。 + +大家都知道,处理异常,通常都是使用 ``try...execept..`` +来捕获处理的。这样做一个不好的地方是,在代码的主逻辑里,会有大量的异常处理代理,这会很大的影响我们的可读性。 + +好一点的做法呢,可以使用 ``with`` 将异常的处理隐藏起来。 + +仍然是以上面的代码为例,我们将\ ``1/0`` +这个\ ``一定会抛出异常的代码``\ 写在 ``operate`` 里 + +.. code:: python + + class Resource(): + def __enter__(self): + print('===connect to resource===') + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + print('===close resource connection===') + return True + + def operate(self): + 1/0 + + with Resource() as res: + res.operate() + +运行一下,惊奇地发现,居然不会报错。 + +这就是上下文管理协议的一个强大之处,异常可以在\ ``__exit__`` +进行捕获并由你自己决定如何处理,是抛出呢还是在这里就解决了。在\ ``__exit__`` +里返回 ``True``\ (没有return 就默认为 return False),就相当于告诉 +Python解释器,这个异常我们已经捕获了,不需要再往外抛了。 + +在 写\ ``__exit__`` 函数时,需要注意的事,它必须要有这三个参数: + +- exc_type:异常类型 +- exc_val:异常值 +- exc_tb:异常的错误栈信息 + +当主逻辑代码没有报异常时,这三个参数将都为None。 + +1.14.4 how contextlib? +---------------------- + +在上面的例子中,我们只是为了构建一个上下文管理器,却写了一个类。如果只是要实现一个简单的功能,写一个类未免有点过于繁杂。这时候,我们就想,如果只写一个函数就可以实现上下文管理器就好了。 + +这个点Python早就想到了。它给我们提供了一个装饰器,你只要按照它的代码协议来实现函数内容,就可以将这个函数对象变成一个上下文管理器。 + +我们按照 contextlib 的协议来自己实现一个打开文件(with +open)的上下文管理器。 + +.. code:: python + + import contextlib + + @contextlib.contextmanager + def open_func(file_name): + # __enter__方法 + print('open file:', file_name, 'in __enter__') + file_handler = open(file_name, 'r') + + # 【重点】:yield + yield file_handler + + # __exit__方法 + print('close file:', file_name, 'in __exit__') + file_handler.close() + return + + with open_func('/Users/MING/mytest.txt') as file_in: + for line in file_in: + print(line) + +在被装饰函数里,必须是一个生成器(带有yield),而yield之前的代码,就相当于\ ``__enter__``\ 里的内容。yield +之后的代码,就相当于\ ``__exit__`` 里的内容。 + +上面这段代码只能实现上下文管理器的第一个目的(管理资源),并不能实现第二个目的(处理异常)。 + +如果要处理异常,可以改成下面这个样子。 + +.. code:: python + + import contextlib + + @contextlib.contextmanager + def open_func(file_name): + # __enter__方法 + print('open file:', file_name, 'in __enter__') + file_handler = open(file_name, 'r') + + try: + yield file_handler + except Exception as exc: + # deal with exception + print('the exception was thrown') + finally: + print('close file:', file_name, 'in __exit__') + file_handler.close() + + return + + with open_func('/Users/MING/mytest.txt') as file_in: + for line in file_in: + 1/0 + print(line) + +好像只要讲到上下文管理器,大多数人都会谈到打开文件这个经典的例子。 + +但是在实际开发中,可以使用到上下文管理器的例子也不少。我这边举个我自己的例子。 + +在OpenStack中,给一个虚拟机创建快照时,需要先创建一个临时文件夹,来存放这个本地快照镜像,等到本地快照镜像创建完成后,再将这个镜像上传到Glance。然后删除这个临时目录。 + +这段代码的主逻辑是\ ``创建快照``\ ,而\ ``创建临时目录``\ ,属于前置条件,\ ``删除临时目录``\ ,是收尾工作。 + +虽然代码量很少,逻辑也不复杂,但是“``创建临时目录,使用完后再删除临时目录``”这个功能,在一个项目中很多地方都需要用到,如果可以将这段逻辑处理写成一个工具函数作为一个上下文管理器,那代码的复用率也大大提高。 + +代码是这样的 + +|image0| + +总结起来,使用上下文管理器有三个好处: + +1. 提高代码的复用率; +2. 提高代码的优雅度; +3. 提高代码的可读性; >>>>>>> ac2277bbccb12e9483bc24cdd953985053efe96e + -------------- .. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg :alt: 关注公众号,获取最新干货! + +.. |image0| image:: http://image.python-online.cn/20190310172800.png + diff --git a/source/c01/c01_15.rst b/source/c01/c01_15.rst new file mode 100644 index 0000000..43792dc --- /dev/null +++ b/source/c01/c01_15.rst @@ -0,0 +1,83 @@ +1.15 提升Python性能的7个习惯 +============================ + +-------------- + + 转载自:https://zhuanlan.zhihu.com/p/38160586 + +1.15.1 使用局部变量 +------------------- + +尽量使用局部变量代替全局变量:便于维护,提高性能并节省内存。 + +使用局部变量替换模块名字空间中的变量,例如 ls = +os.linesep。一方面可以提高程序性能,局部变量查找速度更快;另一方面可用简短标识符替代冗长的模块变量,提高可读性。 + +1.15.2 减少函数调用次数 +----------------------- + +对象类型判断时,采用isinstance()最优,采用对象类型身份(id())次之,采用对象值(type())比较最次。 + +:: + + #判断变量num是否为整数类型type(num) == type(0) #调用三次函数type(num) is type(0) #身份比较isinstance(num,(int)) #调用一次函数 + +不要在重复操作的内容作为参数放到循环条件中,避免重复运算。 + +:: + + #每次循环都需要重新执行len(a)while i < len(a): statement#len(a)仅执行一次m = len(a)while i < m: statement + +如需使用模块X中的某个函数或对象Y,应直接使用from X import +Y,而不是import X; +X.Y。这样在使用Y时,可以减少一次查询(解释器不必首先查找到X模块,然后在X模块的字典中查找Y)。 + +1.15.3 采用映射替代条件查找 +--------------------------- + +映射(比如dict等)的搜索速度远快于条件语句(如if等)。Python中也没有select-case语句。 + +:: + + #if查找if a == 1: b = 10elif a == 2: b = 20...#dict查找,性能更优d = {1:10,2:20,...}b = d[a] + +1.15.4 直接迭代序列元素 +----------------------- + +对序列(str、list、tuple等),直接迭代序列元素,比迭代元素的索引速度要更快。 + +:: + + a = [1,2,3]#迭代元素for item in a: print(item)#迭代索引for i in range(len(a)): print(a[i]) + +1.15.5 采用生成器表达式替代列表解析 +----------------------------------- + +列表解析(list +comprehension),会产生整个列表,对大量数据的迭代会产生负面效应。 + +而生成器表达式则不会,其不会真正创建列表,而是返回一个生成器,在需要时产生一个值(延迟计算),对内存更加友好。 + +:: + + #计算文件f的非空字符个数#生成器表达式l = sum([len(word) for line in f for word in line.split()])#列表解析l = sum(len(word) for line in f for word in line.split()) + +1.15.6 先编译后调用 +------------------- + +使用eval()、exec()函数执行代码时,最好调用代码对象(提前通过compile()函数编译成字节码),而不是直接调用str,可以避免多次执行重复编译过程,提高程序性能。 + +正则表达式模式匹配也类似,也最好先将正则表达式模式编译成regex对象(通过re.complie()函数),然后再执行比较和匹配。 + +1.15.7 模块编程习惯 +------------------- + +模块中的最高级别Python语句(没有缩进的代码)会在模块导入(import)时执行(不论其是否真的必要执行)。因此,应尽量将模块所有的功能代码放到函数中,包括主程序相关的功能代码也可放到main()函数中,主程序本身调用main()函数。 + +可以在模块的main()函数中书写测试代码。在主程序中,检测name的值,如果为’main’(表示模块是被直接执行),则调用main()函数,进行测试;如果为模块名字(表示模块是被调用),则不进行测试。 + +-------------- + +.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg + :alt: 关注公众号,获取最新干货! + From 3c7353c26d0835224a054e0b39e4065e003b13b3 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 10 Mar 2019 20:47:09 +0800 Subject: [PATCH 004/329] deal with confict --- source/c01/c01_14.md | 71 +------------------------------------- source/c01/c01_14.rst | 80 ++----------------------------------------- 2 files changed, 3 insertions(+), 148 deletions(-) diff --git a/source/c01/c01_14.md b/source/c01/c01_14.md index fb7c788..c2d81f3 100644 --- a/source/c01/c01_14.md +++ b/source/c01/c01_14.md @@ -1,72 +1,3 @@ -<<<<<<< HEAD -# 1.14 提升Python性能的7个习惯 - ---- - -> 转载自:https://zhuanlan.zhihu.com/p/38160586 - -## 1.14.1 使用局部变量 - -尽量使用局部变量代替全局变量:便于维护,提高性能并节省内存。 - -使用局部变量替换模块名字空间中的变量,例如 ls = os.linesep。一方面可以提高程序性能,局部变量查找速度更快;另一方面可用简短标识符替代冗长的模块变量,提高可读性。 - -## 1.14.2 减少函数调用次数 - -对象类型判断时,采用isinstance()最优,采用对象类型身份(id())次之,采用对象值(type())比较最次。 - -``` -#判断变量num是否为整数类型type(num) == type(0) #调用三次函数type(num) is type(0) #身份比较isinstance(num,(int)) #调用一次函数 -``` - -不要在重复操作的内容作为参数放到循环条件中,避免重复运算。 - -``` -#每次循环都需要重新执行len(a)while i < len(a): statement#len(a)仅执行一次m = len(a)while i < m: statement -``` - -如需使用模块X中的某个函数或对象Y,应直接使用from X import Y,而不是import X; X.Y。这样在使用Y时,可以减少一次查询(解释器不必首先查找到X模块,然后在X模块的字典中查找Y)。 - -## 1.14.3 采用映射替代条件查找 - -映射(比如dict等)的搜索速度远快于条件语句(如if等)。Python中也没有select-case语句。 - -``` -#if查找if a == 1: b = 10elif a == 2: b = 20...#dict查找,性能更优d = {1:10,2:20,...}b = d[a] -``` - -## 1.14.4 直接迭代序列元素 - -对序列(str、list、tuple等),直接迭代序列元素,比迭代元素的索引速度要更快。 - -``` -a = [1,2,3]#迭代元素for item in a: print(item)#迭代索引for i in range(len(a)): print(a[i]) -``` - -## 1.14.5 采用生成器表达式替代列表解析 - -列表解析(list comprehension),会产生整个列表,对大量数据的迭代会产生负面效应。 - -而生成器表达式则不会,其不会真正创建列表,而是返回一个生成器,在需要时产生一个值(延迟计算),对内存更加友好。 - -``` -#计算文件f的非空字符个数#生成器表达式l = sum([len(word) for line in f for word in line.split()])#列表解析l = sum(len(word) for line in f for word in line.split()) -``` - -## 1.14.6 先编译后调用 - -使用eval()、exec()函数执行代码时,最好调用代码对象(提前通过compile()函数编译成字节码),而不是直接调用str,可以避免多次执行重复编译过程,提高程序性能。 - -正则表达式模式匹配也类似,也最好先将正则表达式模式编译成regex对象(通过re.complie()函数),然后再执行比较和匹配。 - -## 1.14.7 模块编程习惯 - -模块中的最高级别Python语句(没有缩进的代码)会在模块导入(import)时执行(不论其是否真的必要执行)。因此,应尽量将模块所有的功能代码放到函数中,包括主程序相关的功能代码也可放到main()函数中,主程序本身调用main()函数。 - -可以在模块的main()函数中书写测试代码。在主程序中,检测name的值,如果为'main'(表示模块是被直接执行),则调用main()函数,进行测试;如果为模块名字(表示模块是被调用),则不进行测试。 - - -======= # 1.14 with 与 上下文管理器 > 提示:前面的内容较为基础,重点知识在后半段。 @@ -265,4 +196,4 @@ with open_func('/Users/MING/mytest.txt') as file_in: ------ -![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) \ No newline at end of file +![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) diff --git a/source/c01/c01_14.rst b/source/c01/c01_14.rst index 60c0231..8ffe84e 100644 --- a/source/c01/c01_14.rst +++ b/source/c01/c01_14.rst @@ -1,81 +1,5 @@ -<<<<<<< HEAD # 1.14 提升Python性能的7个习惯 - --------------- - - 转载自:https://zhuanlan.zhihu.com/p/38160586 - -1.14.1 使用局部变量 -------------------- - -尽量使用局部变量代替全局变量:便于维护,提高性能并节省内存。 - -使用局部变量替换模块名字空间中的变量,例如 ls = -os.linesep。一方面可以提高程序性能,局部变量查找速度更快;另一方面可用简短标识符替代冗长的模块变量,提高可读性。 - -1.14.2 减少函数调用次数 ------------------------ - -对象类型判断时,采用isinstance()最优,采用对象类型身份(id())次之,采用对象值(type())比较最次。 - -:: - - #判断变量num是否为整数类型type(num) == type(0) #调用三次函数type(num) is type(0) #身份比较isinstance(num,(int)) #调用一次函数 - -不要在重复操作的内容作为参数放到循环条件中,避免重复运算。 - -:: - - #每次循环都需要重新执行len(a)while i < len(a): statement#len(a)仅执行一次m = len(a)while i < m: statement - -如需使用模块X中的某个函数或对象Y,应直接使用from X import -Y,而不是import X; -X.Y。这样在使用Y时,可以减少一次查询(解释器不必首先查找到X模块,然后在X模块的字典中查找Y)。 - -1.14.3 采用映射替代条件查找 ---------------------------- - -映射(比如dict等)的搜索速度远快于条件语句(如if等)。Python中也没有select-case语句。 - -:: - - #if查找if a == 1: b = 10elif a == 2: b = 20...#dict查找,性能更优d = {1:10,2:20,...}b = d[a] - -1.14.4 直接迭代序列元素 ------------------------ - -对序列(str、list、tuple等),直接迭代序列元素,比迭代元素的索引速度要更快。 - -:: - - a = [1,2,3]#迭代元素for item in a: print(item)#迭代索引for i in range(len(a)): print(a[i]) - -1.14.5 采用生成器表达式替代列表解析 ------------------------------------ - -列表解析(list -comprehension),会产生整个列表,对大量数据的迭代会产生负面效应。 - -而生成器表达式则不会,其不会真正创建列表,而是返回一个生成器,在需要时产生一个值(延迟计算),对内存更加友好。 - -:: - - #计算文件f的非空字符个数#生成器表达式l = sum([len(word) for line in f for word in line.split()])#列表解析l = sum(len(word) for line in f for word in line.split()) - -1.14.6 先编译后调用 -------------------- - -使用eval()、exec()函数执行代码时,最好调用代码对象(提前通过compile()函数编译成字节码),而不是直接调用str,可以避免多次执行重复编译过程,提高程序性能。 - -正则表达式模式匹配也类似,也最好先将正则表达式模式编译成regex对象(通过re.complie()函数),然后再执行比较和匹配。 - -1.14.7 模块编程习惯 -------------------- - -模块中的最高级别Python语句(没有缩进的代码)会在模块导入(import)时执行(不论其是否真的必要执行)。因此,应尽量将模块所有的功能代码放到函数中,包括主程序相关的功能代码也可放到main()函数中,主程序本身调用main()函数。 - -可以在模块的main()函数中书写测试代码。在主程序中,检测name的值,如果为’main’(表示模块是被直接执行),则调用main()函数,进行测试;如果为模块名字(表示模块是被调用),则不进行测试。 - -======= # 1.14 with 与 上下文管理器 +1.14 with 与 上下文管理器 +========================= 提示:前面的内容较为基础,重点知识在后半段。 From ae5b0bdfb759d486fc59c23ee1ad160bc3790bcd Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 10 Mar 2019 20:48:28 +0800 Subject: [PATCH 005/329] update file --- source/c01/c01_14.md | 1 - 1 file changed, 1 deletion(-) diff --git a/source/c01/c01_14.md b/source/c01/c01_14.md index c2d81f3..b9206b5 100644 --- a/source/c01/c01_14.md +++ b/source/c01/c01_14.md @@ -192,7 +192,6 @@ with open_func('/Users/MING/mytest.txt') as file_in: 1. 提高代码的复用率; 2. 提高代码的优雅度; 3. 提高代码的可读性; ->>>>>>> ac2277bbccb12e9483bc24cdd953985053efe96e ------ From 20c27adef6c00203278621e4f99da79ae799b346 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 10 Mar 2019 21:02:42 +0800 Subject: [PATCH 006/329] add files --- source/c03/c03_05.md | 6 +- source/c03/c03_05.rst | 292 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 source/c03/c03_05.rst diff --git a/source/c03/c03_05.md b/source/c03/c03_05.md index 073a5f1..f234d6a 100644 --- a/source/c03/c03_05.md +++ b/source/c03/c03_05.md @@ -259,5 +259,9 @@ with app.app_context(): ``` +以上,是我通过学习七月的 `Flask高级编程` 加上自己直白的理解,希望对你在理解 Flask的上下文核心机制 会有帮助。 -以上,是我通过学习七月的 `Flask高级编程` 加上自己直白的理解,希望对你在理解 Flask的上下文核心机制 会有帮助。 \ No newline at end of file + +--- + +![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) diff --git a/source/c03/c03_05.rst b/source/c03/c03_05.rst new file mode 100644 index 0000000..6a8108f --- /dev/null +++ b/source/c03/c03_05.rst @@ -0,0 +1,292 @@ +3.5 源码解读:Flask上下文与代理模式 +=================================== + +在 Flask 中有三个非常重要的对象,你肯定不会感到陌生。 + +- Local +- LocalProxy +- LocalStack + +今天就要来理解一下这三个都是什么,他们在 Flask +中所担任的角色,以及和这三者息息相关的上下文机制。 + +3.5.1 Local +----------- + +首先来讲一下\ ``Local`` +,记得在以前的「并发编程系列」的第五篇有讲过线程的\ ``信息隔离``\ 。和这里的\ ``Local``\ 是一个作用 + +什么是\ ``信息隔离``\ 呢?比如说现有两个线程,线程A里的运行环境,和线程B里运行环境变量不能共享。这就是\ ``信息隔离``\ 。 + +在线程中,是通过\ ``threading`` +模块自带的\ ``local``\ 对象来存储的。但有一个问题是这个\ ``local``\ 的隔离是线程级别的。 + +而在web开发中,线程级别的隔离已经不能满足要求了,因为多个请求是很有可能会跑在同一个线程中的。在web应用里中,我们需要的是请求级别的隔离。这个时候\ ``werkzeug`` +的\ ``Local``\ 就派上用场了,它就是用来实现请求级别变量的隔离。一个请求会对应一个Local +对象。 + +Local +是请求级别的对象,当一个请求结束时,需要有更高一层的管理器对这个请求的数据做一些清理回收。这个管理器就叫做:\ ``LocalManager``\ ,除此之外,还可以通过 +``local_manager.locals.append(local)`` 动态添加local对象。 + +下面是一个精简示例,\ ``make_middleware`` 函数是主角。 + +.. code:: python + + from werkzeug.local import Local, LocalManager + + local = Local() + local_manager = LocalManager([local]) + + def application(environ, start_response): + local.request = request = Request(environ) + ... + + # make_middleware会确保当request结束时,所有存储于local中的对象的reference被清除 + application = local_manager.make_middleware(application) + +3.5.2 LocalStack +---------------- + +``LocalStack`` +,从命名上,可以知道,他是一个栈结构的对象。它存储的主要有两种上下文,\ ``AppContext`` +和 ``RequestContext``\ 。 + +当一个请求发起后,flask会把RequestContext推入一个LocalStack中(\ ``_request_ctx_stack``\ ),而在推入之前,其实它会去检测另一个LocalStack(\ ``_app_ctx_stack``\ )是否为空,如果为空就先将app的上下文信息push到\ ``_app_ctx_stack``\ ,然后再去把请求的上下文信息push到\ ``_request_ctx_stack`` +里。 + +在flask中有三个对象比较常用 + +- current_app +- request +- session + +这三个对象,永远是指向\ ``LocalStack`` +栈顶的上下文中对应的app、request或者session,对应的源码如下: + +.. code:: python + + def _lookup_req_object(name): + top = _request_ctx_stack.top + if top is None: + raise RuntimeError(_request_ctx_err_msg) + return getattr(top, name) + + def _find_app(): + top = _app_ctx_stack.top + if top is None: + raise RuntimeError(_app_ctx_err_msg) + return top.app + + _request_ctx_stack = LocalStack() + _app_ctx_stack = LocalStack() + current_app = LocalProxy(_find_app) + request = LocalProxy(partial(_lookup_req_object, 'request')) + session = LocalProxy(partial(_lookup_req_object, 'session')) + +3.5.3 LocalProxy +---------------- + +通过上面的代码,你可以发现,我们访问LocalStack里的元素的时候,都是通过\ ``LocalProxy`` +来进行的有没有? + +这就很奇怪了,为什么不直接访问\ ``Local`` 和 ``LocalStack``\ 呢? + +这应该是个难点,我这边举个例子,也许你就明白了。 + +首先是不使用LocalProxy的情况 + +.. code:: python + + # use Local object directly + from werkzeug.local import LocalStack + user_stack = LocalStack() + user_stack.push({'name': 'Bob'}) + user_stack.push({'name': 'John'}) + + def get_user(): + # do something to get User object and return it + return user_stack.pop() + + + # 直接调用函数获取user对象 + user = get_user() + print user['name'] + print user['name'] + +输出结果是 + +:: + + John + John + +使用LocalProxy后 + +.. code:: python + + # use LocalProxy + from werkzeug.local import LocalStack, LocalProxy + user_stack = LocalStack() + user_stack.push({'name': 'Bob'}) + user_stack.push({'name': 'John'}) + + def get_user(): + # do something to get User object and return it + return user_stack.pop() + + # 通过LocalProxy使用user对象 + user = LocalProxy(get_user) + print user['name'] + print user['name'] + +输出结果 + +:: + + John + Bob + +怎么样,看出区别了吧,直接使用LocalStack对象,user一旦赋值就无法再动态更新了,而使用Proxy,每次调用操作符(这里\ ``[]操作符``\ 用于获取属性),都会重新获取user,从而实现了动态更新user的效果。 + +每次 ``user['name']`` 的时候 就会触发 ``LocalProxy`` 类的 +``__getitem__``\ ,从而调用该类的 ``_get_current_object``\ 。而每次 +``_get_current_object``\ 都会返回 +``get_user()``\ (在flask中对应的函数是 ``_lookup_req_object`` ) +的执行结果, 也就是 ``user_stack.pop()`` + +.. code:: python + + def __init__(self, local, name=None): + # 【重要】将local对象(也就是一个get_user函数对象)赋值给self.__local + object.__setattr__(self, '_LocalProxy__local', local) + object.__setattr__(self, '__name__', name) + if callable(local) and not hasattr(local, '__release_local__'): + # "local" is a callable that is not an instance of Local or + # LocalManager: mark it as a wrapped function. + object.__setattr__(self, '__wrapped__', local) + + def _get_current_object(self): + """Return the current object. This is useful if you want the real + object behind the proxy at a time for performance reasons or because + you want to pass the object into a different context. + """ + if not hasattr(self.__local, '__release_local__'): + # 【重要】执行传递进行的 get_user 对象。 + return self.__local() + try: + return getattr(self.__local, self.__name__) + except AttributeError: + raise RuntimeError('no object bound to %s' % self.__name__) + +这样就能实现每次对栈顶元素的操作,都是面对最新元素执行的。 + +3.5.4 经典错误 +-------------- + +在 Flask 中经常会遇到的一个错误是: + + Working outside of application context. + +这个错误,如果没有理解 flask +的上下文机制,是很难理解的。通过上面知识背景的铺垫,我们可以尝试来搞懂一下为什么会出现这样的情况。 + +首先我们先来模拟一下这个错误的产生。假设现在有一个单独的文件,内容如下 + +.. code:: python + + from flask import current_app + + app = Flask(__name__) + + app = current_app + print(app.config['DEBUG']) + +运行一下,会报如下错误。 + +.. code:: python + + Traceback (most recent call last): + File "/Users/MING/PycharmProjects/fisher/app/mytest/mytest.py", line 19, in + print(app.config['DEBUG']) + File "/Users/MING/.virtualenvs/fisher-gSdA58aK/lib/python3.6/site-packages/werkzeug/local.py", line 347, in __getattr__ + return getattr(self._get_current_object(), name) + File "/Users/MING/.virtualenvs/fisher-gSdA58aK/lib/python3.6/site-packages/werkzeug/local.py", line 306, in _get_current_object + return self.__local() + File "/Users/MING/.virtualenvs/fisher-gSdA58aK/lib/python3.6/site-packages/flask/globals.py", line 51, in _find_app + raise RuntimeError(_app_ctx_err_msg) + RuntimeError: Working outside of application context. + +你一定会奇怪吧。我明明也实例化一个app对象,但是为什么取current_app会报错呢?而如果不用current_app,就不会报错。 + +如果你认真学习了上面的内容,这边也就不难理解了。 + +从先前的研究发现,当使用\ ``current_app``\ 时,它取的是\ ``LocalStack``\ 的栈顶元素(app的上下文信息),而实际上在我们通过\ ``app = Flask(__name__)``\ 实例化一个app对象时,此时还没有将这个上下文信息写入\ ``LocalProxy``\ ,自然取栈顶元素就会出错了。 + +.. code:: python + + def _find_app(): + top = _app_ctx_stack.top + if top is None: + raise RuntimeError(_app_ctx_err_msg) + return top.app + +上面我们也说过了,这个上下文什么时候push进去呢?在外部发起一起request请求后,首先就会先检查 +app 的上下文信息是否已经 push 进去了,如果没有的话,就会先半其push进去。 + +而上面我们是以运行单个文件的方式,并没有实际产生一个 request 请求,自然 +在 ``LocalStack`` 里没有 app的上下文信息。报错也是正常的。 + +知道了错误根源后,如何解决这种问题呢? + +在Flask中,它提供了一个方法\ ``ctx=app.app_context()``\ 可以获取一个上下文对象,我们只要将这个上下文对象 +手动 push 到 ``LocalStack`` 中,\ ``current_app`` +也就可以正常取到我们的app对象了。 + +.. code:: python + + from flask import Flask, current_app + + app = Flask(__name__) + ctx = app.app_context() + ctx.push() + + app = current_app + print(app.config['DEBUG']) + ctx.pop() + +由于 AppContext 类实现了上下文协议 + +.. code:: python + + class AppContext(object): + def __enter__(self): + self.push() + return self + + def __exit__(self, exc_type, exc_value, tb): + self.pop(exc_value) + + if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None: + reraise(exc_type, exc_value, tb) + +所以你也可以这样写 + +.. code:: python + + from flask import Flask, current_app + + app = Flask(__name__) + + with app.app_context(): + app = current_app + print(app.config['DEBUG']) + +以上,是我通过学习七月的 ``Flask高级编程`` +加上自己直白的理解,希望对你在理解 Flask的上下文核心机制 会有帮助。 + +-------------- + +.. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg + :alt: 关注公众号,获取最新干货! + From 7fa831b2ea598225ed2c50a540b869646b359c54 Mon Sep 17 00:00:00 2001 From: BingmingWong Date: Sun, 10 Mar 2019 22:27:30 +0800 Subject: [PATCH 007/329] add file --- source/c01/c01_14.rst | 2 +- source/c02/c02_12.rst | 90 ++++++++++++ source/c03/c03_05.md | 3 +- source/c03/c03_05.rst | 2 +- source/c04/c04_01.md | 2 +- source/c04/c04_01.rst | 2 +- source/c04/c04_10.rst | 27 ++++ source/c04/c04_14.md | 94 +++++++++++++ source/c04/c04_14.rst | 118 ++++++++++++++++ source/c07/c07_02.rst | 17 +++ source/c08/c08_01.rst | 17 ++- source/c08/c08_04.rst | 319 ++++++++++++++++++++++++++++++++++++++++-- source/c08/c08_05.rst | 131 +++++++++++++++++ 13 files changed, 806 insertions(+), 18 deletions(-) create mode 100644 source/c02/c02_12.rst create mode 100644 source/c04/c04_14.md create mode 100644 source/c04/c04_14.rst create mode 100644 source/c08/c08_05.rst diff --git a/source/c01/c01_14.rst b/source/c01/c01_14.rst index 8ffe84e..8962b8f 100644 --- a/source/c01/c01_14.rst +++ b/source/c01/c01_14.rst @@ -205,7 +205,7 @@ open)的上下文管理器。 1. 提高代码的复用率; 2. 提高代码的优雅度; -3. 提高代码的可读性; >>>>>>> ac2277bbccb12e9483bc24cdd953985053efe96e +3. 提高代码的可读性; -------------- diff --git a/source/c02/c02_12.rst b/source/c02/c02_12.rst new file mode 100644 index 0000000..7d9e07e --- /dev/null +++ b/source/c02/c02_12.rst @@ -0,0 +1,90 @@ +2.12 生成器与协程,别再傻傻分不清了 +=================================== + +之前写的 ``并发编程`` +系列文章,是我迄今务止,得到的反馈最好的一个系列,也是因为它我才能认识这么多优秀的朋友。 + +但是可能由于知识诅咒的原因,我在协程那里并没有详细讲解生成器和协程的关系,不少人也因此还是懵懵懂懂的状态。 + +刚好前两天,有位读者朋友问我这个问题,我觉得有必要写一篇文章来讲解一下。 + +如果你是小白,刚接触这块知识的话,那么为你防止你更好的理解今天的内容,你最好预先下之前的写的入门级文章:\ `从生成器使用入门协程(七) `__ + +最好的理解方式,莫过于用例子做个对比。 + +如你所见,下面这代码将定义一个生成器的。 + +.. code:: python + + import time + + def eat(): + while True: + if food: + print("小明 吃完{}了".format(food)) + yield + print("小明 要开始吃{}...".format(food)) + time.sleep(1) + + food = None + MING = eat() # 产生一个生成器 + MING.send(None) # 预激 + food = "面包" + MING.send('面包') + MING.send('苹果') + MING.send('香肠') + +运行一下,从结果中可以看出,不管我们塞给小明什么东西,小明都将只能将他们当成面包吃。 + +:: + + 小明 要开始吃面包... + 小明 吃完面包了 + 小明 要开始吃面包... + 小明 吃完面包了 + 小明 要开始吃面包... + 小明 吃完面包了 + +那再来看一下协程的。 + +.. code:: python + + import time + + def eat(): + food = None + while True: + if food: + print("小明 吃完{}了".format(food)) + food = yield + print("小明 开始吃{}...".format(food)) + time.sleep(1) + + MING = eat() # 产生一个生成器 + MING.send(None) # 预激 + MING.send('面包') + MING.send('苹果') + MING.send('香肠') + +运行一下,从结果中可以看出,小明已经可以感知我们塞给他的是什么食物。 + +:: + + 小明 开始吃面包... + 小明 吃完面包了 + 小明 开始吃苹果... + 小明 吃完苹果了 + 小明 开始吃香肠... + 小明 吃完香肠了 + +仔细观察一下,上面两段代码并没有太大的区别,我们将主要关注点集中在 +``yidld`` 关键词上。 + +可以发现,生成器里 ``yield`` 左边并没有变量,而在协程里,\ ``yield`` +左边有一个变量。 + +在函数被调用后,一个生成器就产生了,而一般的生成器不能再往生成器内部传递参数了,而这个当生成器里的 +yield +左边有变量时,就不一样了,它仍然可以在外部接收新的参数。这就是生成器与协程的最大区别。 + +说到协程,你可能会问 diff --git a/source/c03/c03_05.md b/source/c03/c03_05.md index f234d6a..294e584 100644 --- a/source/c03/c03_05.md +++ b/source/c03/c03_05.md @@ -201,7 +201,7 @@ RuntimeError: Working outside of application context. 如果你认真学习了上面的内容,这边也就不难理解了。 -从先前的研究发现,当使用`current_app`时,它取的是`LocalStack`的栈顶元素(app的上下文信息),而实际上在我们通过`app = Flask(__name__)`实例化一个app对象时,此时还没有将这个上下文信息写入`LocalProxy`,自然取栈顶元素就会出错了。 +从先前的研究发现,当使用`current_app`时,它取的是`LocalStack`的栈顶元素(app的上下文信息),而实际上在我们通过`app = Flask(__name__)`实例化一个app对象时,此时还没有将这个上下文信息写入`LocalStack`,自然取栈顶元素就会出错了。 ```python def _find_app(): @@ -261,7 +261,6 @@ with app.app_context(): 以上,是我通过学习七月的 `Flask高级编程` 加上自己直白的理解,希望对你在理解 Flask的上下文核心机制 会有帮助。 - --- ![关注公众号,获取最新干货!](https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg) diff --git a/source/c03/c03_05.rst b/source/c03/c03_05.rst index 6a8108f..afbec1a 100644 --- a/source/c03/c03_05.rst +++ b/source/c03/c03_05.rst @@ -221,7 +221,7 @@ Local 如果你认真学习了上面的内容,这边也就不难理解了。 -从先前的研究发现,当使用\ ``current_app``\ 时,它取的是\ ``LocalStack``\ 的栈顶元素(app的上下文信息),而实际上在我们通过\ ``app = Flask(__name__)``\ 实例化一个app对象时,此时还没有将这个上下文信息写入\ ``LocalProxy``\ ,自然取栈顶元素就会出错了。 +从先前的研究发现,当使用\ ``current_app``\ 时,它取的是\ ``LocalStack``\ 的栈顶元素(app的上下文信息),而实际上在我们通过\ ``app = Flask(__name__)``\ 实例化一个app对象时,此时还没有将这个上下文信息写入\ ``LocalStack``\ ,自然取栈顶元素就会出错了。 .. code:: python diff --git a/source/c04/c04_01.md b/source/c04/c04_01.md index 3563318..66dce83 100644 --- a/source/c04/c04_01.md +++ b/source/c04/c04_01.md @@ -1,4 +1,4 @@ -# 4.1 Python虚拟环境的搭建 +# 4.1 虚拟环境:virtualenv --- diff --git a/source/c04/c04_01.rst b/source/c04/c04_01.rst index 12e4afe..6f4688b 100755 --- a/source/c04/c04_01.rst +++ b/source/c04/c04_01.rst @@ -1,4 +1,4 @@ -4.1 Python虚拟环境的搭建 +4.1 虚拟环境:virtualenv ======================== -------------- diff --git a/source/c04/c04_10.rst b/source/c04/c04_10.rst index 962e5ca..de63a93 100755 --- a/source/c04/c04_10.rst +++ b/source/c04/c04_10.rst @@ -507,6 +507,33 @@ limit子句:限制返回数据的量。 |image23| +六、系统查询 +------------ + +-------------- + +**查询各库的使用量** + +.. code:: mysql + + select TABLE_SCHEMA, concat(truncate(sum(data_length)/1024/1024,2),' MB') as data_size, + concat(truncate(sum(index_length)/1024/1024,2),' MB') as index_size + from information_schema.tables + group by TABLE_SCHEMA + order by data_length desc; + +**查询一个库中各个表的使用量** + +.. code:: mysql + + SELECT CONCAT(table_schema,'.',table_name) AS 'Table Name', + CONCAT(ROUND(table_rows/1000000,4),'M') AS 'Number of Rows', + CONCAT(ROUND(data_length/(1024*1024*1024),4),'G') AS 'Data Size', + CONCAT(ROUND(index_length/(1024*1024*1024),4),'G') AS 'Index Size', + CONCAT(ROUND((data_length+index_length)/(1024*1024*1024),4),'G') AS 'Total' + FROM information_schema.TABLES + WHERE table_schema LIKE '%zabbix%' ORDER BY Total desc; + -------------- .. figure:: https://ws1.sinaimg.cn/large/8f640247gy1fyi60fxos4j20u00a8tdz.jpg diff --git a/source/c04/c04_14.md b/source/c04/c04_14.md new file mode 100644 index 0000000..0184919 --- /dev/null +++ b/source/c04/c04_14.md @@ -0,0 +1,94 @@ +# 4.14 虚拟环境:Pipenv + +以前一直使用pip+virtualenv+virtualwrapper管理模块和环境, 但是virtualwrapper在windows上使用不太方便,而且包和环境分开管理确实经常不记得哪个是哪个了。 + +所以这里使用官方推荐的pipenv,fisher 是我们的项目目录。如果是python3.6 应该是自带的。 + +```shell +mkdir fisher && cd fisher + +# 在当前目录下创建一个虚拟环境 +pipenv install + +# 进入这个虚拟环境 +pipenv shell +``` + +为什么 会推荐 pipenv 呢? + +- 它是 `virtualenv` 和 `pip` 的合体,可以合起来使用; +- 使用`Pipfile` 和 `Pipfile.lock`替代`requirements.txt` +- 可以使用 `pipenv graph`很方便的看出包的依赖关系。 +- 通过加载`.env`文件简化开发工作流程 + +如果你的电脑上没有安装 pipenv,可以使用如下方法安装 + +```shell +# mac +$ brew install pipenv +``` + +整理一下其他命令 + +```sh e llsh el +# 指定python版本 +pipenv --two +pipenv --three +pipenv --python 3.7 + +# 进入/退出 +pipenv shell +exit + +# 移除当前目录的虚拟环境 +pipenv --rm + +# 安装所有依赖 +pipenv install --dev + +# 创建一个包含预发布的锁文件: +pipenv lock --pre + +# 将Pipfile和Pipfile.lock文件里面的包导出为requirements.txt文件 +# 相当于pipenv run pip freeze >requirements.txt +pipenv lock -r > requirements.txt +pipenv lock -r --dev # 若只想导出开发用的包 + +# 打印所有包的依赖关系图 +pipenv graph + +# 更新包 +pipenv update # 更新所有包 +pipenv update --outdated # 打印所有要更新的包 +pipenv update <包名> # 更新指定的包 + +# 检查安全漏洞 +$ pipenv check + +# 安装一个本地包(setup.py)到虚拟环境(Pipfile) +$ pipenv install -e . + +$ pipenv run pip freeze + +# 返回项目的路径 +pipenv --where + +# 返回虚拟环境路径 +pipenv --venv + +# 返回该虚拟环境的解释器 +pipenv --py + +# 在当前虚拟环境中运行 +pipenv run python # 进入交互式 +pipenv run python 文件名 # 运行文件 +pipenv run pip ... # 运行pip + +# 安装、卸载模块 +pipenv install requests +pipenv uninstall requests +pipenv uninstall --all # 卸载全部包 +pipenv install -r path/to/requirements.txt +``` + +`.env`文件,用来存放一些环境变量。 \ No newline at end of file diff --git a/source/c04/c04_14.rst b/source/c04/c04_14.rst new file mode 100644 index 0000000..18e83fc --- /dev/null +++ b/source/c04/c04_14.rst @@ -0,0 +1,118 @@ +4.14 虚拟环境:Pipenv +===================== + +以前一直使用pip+virtualenv+virtualwrapper管理模块和环境, +但是virtualwrapper在windows上使用不太方便,而且包和环境分开管理确实经常不记得哪个是哪个了。 + +所以这里使用官方推荐的pipenv,fisher 是我们的项目目录。如果是python3.6 +应该是自带的。 + +.. code:: shell + + mkdir fisher && cd fisher + + # 在当前目录下创建一个虚拟环境 + pipenv install + + # 进入这个虚拟环境 + pipenv shell + +为什么 会推荐 pipenv 呢? + +- 它是 ``virtualenv`` 和 ``pip`` 的合体,可以合起来使用; +- 使用\ ``Pipfile`` 和 ``Pipfile.lock``\ 替代\ ``requirements.txt`` +- 可以使用 ``pipenv graph``\ 很方便的看出包的依赖关系。 +- 通过加载\ ``.env``\ 文件简化开发工作流程 + +如果你的电脑上没有安装 pipenv,可以使用如下方法安装 + +.. code:: shell + + # mac + $ brew install pipenv + +整理一下其他命令 + +\```sh e llsh el # 指定python版本 pipenv –two pipenv –three pipenv +–python 3.7 + +进入/退出 +========= + +pipenv shell exit + +移除当前目录的虚拟环境 +====================== + +pipenv –rm + +安装所有依赖 +============ + +pipenv install –dev + +创建一个包含预发布的锁文件: +=========================== + +pipenv lock –pre + +将Pipfile和Pipfile.lock文件里面的包导出为requirements.txt文件 +============================================================= + +相当于pipenv run pip freeze >requirements.txt +============================================= + +pipenv lock -r > requirements.txt pipenv lock -r –dev # +若只想导出开发用的包 + +打印所有包的依赖关系图 +====================== + +pipenv graph + +更新包 +====== + +pipenv update # 更新所有包 pipenv update –outdated # 打印所有要更新的包 +pipenv update # 更新指定的包 + +检查安全漏洞 +============ + +$ pipenv check + +安装一个本地包(setup.py)到虚拟环境(Pipfile) +=============================================== + +$ pipenv install -e . + +$ pipenv run pip freeze + +返回项目的路径 +============== + +pipenv –where + +返回虚拟环境路径 +================ + +pipenv –venv + +返回该虚拟环境的解释器 +====================== + +pipenv –py + +在当前虚拟环境中运行 +==================== + +pipenv run python # 进入交互式 pipenv run python 文件名 # 运行文件 +pipenv run pip … # 运行pip + +安装、卸载模块 +============== + +pipenv install requests pipenv uninstall requests pipenv uninstall –all +# 卸载全部包 pipenv install -r path/to/requirements.txt \``\` + +``.env``\ 文件,用来存放一些环境变量。 diff --git a/source/c07/c07_02.rst b/source/c07/c07_02.rst index f362254..bd8b183 100755 --- a/source/c07/c07_02.rst +++ b/source/c07/c07_02.rst @@ -710,6 +710,23 @@ chk_zabbix.sh - 单台节点宕机(可以关闭keepalived服务模拟,也可以关机) - 双台节点宕机(不管是哪一台先启) +10.4 数据清理 +~~~~~~~~~~~~~ + +.. code:: mysql + + # 查询各监控项的占用情况 + select items.name,history.count from (select itemid,count(itemid) as count from history_uint group by itemid) as history,items where items.itemid=history.itemid; + + # 查询zabbix库表的占用情况 + SELECT CONCAT(table_schema,'.',table_name) AS 'Table Name', + CONCAT(ROUND(table_rows/1000000,4),'M') AS 'Number of Rows', + CONCAT(ROUND(data_length/(1024*1024*1024),4),'G') AS 'Data Size', + CONCAT(ROUND(index_length/(1024*1024*1024),4),'G') AS 'Index Size', + CONCAT(ROUND((data_length+index_length)/(1024*1024*1024),4),'G') AS 'Total' + FROM information_schema.TABLES + WHERE table_schema LIKE '%zabbix%' ORDER BY Total desc; + 十一、高效运维 -------------- diff --git a/source/c08/c08_01.rst b/source/c08/c08_01.rst index 9d052e3..ac5dbfd 100755 --- a/source/c08/c08_01.rst +++ b/source/c08/c08_01.rst @@ -137,7 +137,7 @@ aggregate管理 private PROVIDER_NETWORK_CIDR # 创建子网,更多选项可以查看 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 + 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 # 查看网络 $ neutron net-show @@ -260,7 +260,7 @@ aggregate管理 1.2 virsh命令 ~~~~~~~~~~~~~ -.. code:: shell +:: # 查看虚拟机的网卡信息 $ virsh domiflist VM1 @@ -278,6 +278,19 @@ aggregate管理 virsh autostart virsh list --autostart +热增加网卡:\ ``virsh attach-device ws_controller01 ./tmp.xml --persistent --live`` + +.. code:: xml + + + + + + + + + + 1.2 LVM管理 ~~~~~~~~~~~ diff --git a/source/c08/c08_04.rst b/source/c08/c08_04.rst index ba3ab79..20def44 100644 --- a/source/c08/c08_04.rst +++ b/source/c08/c08_04.rst @@ -7,25 +7,34 @@ 8.4.1.1 全虚拟化 ~~~~~~~~~~~~~~~~ -**全虚拟化(英语:Full -virtualization)**\ 是硬件虚拟化的一种,允许\ **未经修改**\ 的客操作系统(英语:Guest +**全虚拟化(英语:Full virtualization)**\ 是硬件虚拟化的一种。 + +通俗点讲,全虚拟化就是说,虚拟机的所有操作(CPU,内存,网络等)都需要经过一个运行在物理机上的虚拟化软件转发给物理机内核。而这个虚拟化软件,在windows上你常见且熟悉的有vmware,virtualbox。 + +允许\ **未经修改**\ 的客操作系统(英语:Guest OS)隔离运行。在全虚拟化环境中,任何可以运行在裸机上的软件(通常是操作系统)都可以未经修改地运行在虚拟机中。 -虽然适应性较强,但是由于完全依赖软件模拟硬件接口,性能较低,不如半虚拟化。 +虽然适应性较强,但是由于完全依赖软件模拟硬件接口,全虚拟化的运行速度要快于硬件模拟,但是性能方面不如裸机,因为Hypervisor需要占用一些资源。不如半虚拟化。 + +|image0| 代表:VMWare(1998年) -.. _全虚拟化-1: +半虚拟化 +~~~~~~~~ -8.4.1.2 全虚拟化 -~~~~~~~~~~~~~~~~ +半虚拟化(英语:\ **Paravirtualization**)是另一种类似于全虚拟化的热门技术。 -半虚拟化(英语:\ **Paravirtualization**)是另一种类似于全虚拟化的热门技术。它在HOST上使用Hpervisor(虚拟机管理程序)提供便利的接口,使得Guest +通俗点讲,半虚拟化对比全虚拟化,就是有一些可以直接操作物理内核空间,而不需要全部经过虚拟化软件。这就大大提高了虚拟机的性能。 + +它在HOST上使用Hpervisor(虚拟机管理程序)提供便利的接口,使得Guest OS能够调用接口访问虚拟硬件。而条件是,Guest OS 内部需要部署安装相应的驱动和软件逻辑,需要对操作系统进行修改。 半虚拟化系统性能可以接近在裸机上的性能。 +|image1| + 代表:Xen(2006) - Xen是一款虚拟化软件,支持半虚拟化和完全虚拟化。它在不支持VT技术的cpu上也能使用,但是只能以半虚拟化模式运行。 @@ -34,13 +43,13 @@ OS能够调用接口访问虚拟硬件。而条件是,Guest OS - Xen是由一个后台守护进程维护的,叫做xend,要运行虚拟系统,必须先将它开启。 - Xen的配置工具有许多,我使用的是virt-manager(GUI)、virt-install和xm。第一个用于管理和安装系统,第二个只用于安装系统,第三个用于启动系统。 -8.4.1.3 硬件辅助虚拟化 -~~~~~~~~~~~~~~~~~~~~~~ +硬件辅助虚拟化 +~~~~~~~~~~~~~~ 硬件辅助虚拟化(英语:\ **Hardware-assisted virtualization**\ ),直接从硬件层面开始支持虚拟化。由硬件支持并提供多个虚拟硬件设备接口,这些设备由虚拟机内核驱动传递给虚拟机使用。使用这种方式,虚拟机能获得和宿主机一样的硬件功能,性能也和宿主机相近,同时原生操作系统本来就支持这项技术,因此无需对操作系统进行修改。 -缺点就是,硬件要支持虚拟化功能,但是随着虚拟化技术的发展,越来越多的硬件都已经支持虚拟化,成本也越来越低,所以硬件辅助虚拟化是目前最流行,使用最广泛的虚拟化技术。 +缺点就是,硬件要支持虚拟化功能,在以前这可能是缺点,但是现在随着虚拟化技术的发展,越来越多的硬件都已经支持虚拟化,成本也越来越低,所以硬件辅助虚拟化是目前最流行,使用最广泛的虚拟化技术。 代表:KVM(2009) @@ -54,3 +63,293 @@ kvm 的 内核模块: 需要安装的软件 - qemu-kvm + +8.4.2 虚拟化技术 +---------------- + +KVM +^^^ + +KVM(Kernel-based Virtual Machine),意思是基于内核的虚拟机。 + +KVM是集成到Linux内核的Hypervisor,是X86架构且硬件支持虚拟化技术(Intel +VT或AMD-V)的Linux的全虚拟化解决方案。它是Linux的一个很小的模块,利用Linux做大量的事,如任务调度、内存管理与硬件设备交互等。 + +Xen +^^^ + +Xen是第一类运行在裸机上的虚拟化管理程序。它支持全虚拟化和半虚拟化,Xen支持hypervisor和虚拟机互相通讯,而且提供在所有Linux版本上的免费产品,包括Red +Hat Enterprise Linux和SUSE Linux Enterprise +Server。Xen最重要的优势在于半虚拟化,此外未经修改的操作系统也可以直接在xen上运行(如Windows),能让虚拟机有效运行而不需要仿真,因此虚拟机能感知到hypervisor,而不需要模拟虚拟硬件,从而能实现高性能。 + +QEMU +^^^^ + +QEMU是一套由Fabrice +Bellard所编写的模拟处理器的自由软件。Qemu,其中关键字emu,全称emulator,模拟器,所以单纯使用qemu是采用的完全虚拟化的模式。 + +**那QEMU有什么用?它和KVM是什么关系呢?** + +准确来说,KVM是Linux +kernel的一个模块。可以用命令modprobe去加载KVM模块。加载了模块后,才能进一步通过其他工具创建虚拟机。 + +但仅有KVM模块是 +远远不够的,KVM是最底层的hypervisor,它仅用来模拟CPU的运行,缺少了对network和周边I/O的支持,所以我们是没法直接用它的。 + +而QEMU-KVM就是一个完整的模拟器,它是基于KVM构建的,提供了完整的网络和I/O支持。 + +说到了QEMU,其实它也是一个虚拟化软件。作用是什么呢,它相当于一个路由器,当Guest +OS的内核想要操作物理硬件时,必须先经由Qemu转发,将操作指令转给真实的硬件。由于所有的指令都要从Qemu里面过一手,因而性能比较差。 + +|image2| + +**总结** + +1. KVM 和 Xen 都是免费的。 +2. KVM 需要硬件支持(Intel + VT或AMD-V),集成在内核中,而Xen可在所有的Linux上运行,不需要硬件支持。 + +libvirt +^^^^^^^ + +要解释libvirt是什么,只要知道为什么会需要libvirt就好了。 + +- 虚拟化的底层可能是KVM,也可能是Xen,或者是其他市面上的Hypervisor,种类之繁多,如果没有一个统一的接口来管理它们,就太乱了,移植性非常差。 +- Hypervisor ,以 qemu-kvm + 为例,它的命令行虚拟机管理工具参数众多,难于使用。需要有一个工具将这些参数进行封装。 + +这些都是在内核空间层做的事情,而我们用户创建、销毁虚拟机都是在用户空间层操作,这就尴尬了,我们没有权限。 + +这下该 libvirt 出场了,libvirt分为服务端各客户端。 + +服务端是libvirtd,而你所熟悉的virt,virt-install,virt-manager,virt-viewer +等都是libvirt的客户端。 + +目前,libvirt +已经成为使用最为广泛的对各种虚拟机进行管理的工具和应用程序接口(API),而且一些常用的虚拟机管理工具(如virsh、virt-install、virt-manager等)和云计算框架平台(如OpenStack、OpenNebula、Eucalyptus等)都在底层使用libvirt的应用程序接口。 + +|image3| + +8.4.3 KVM工具 +------------- + +- Virsh:基于 libvirt 的 命令行工具 (CLI) + +- Virt-Manager:基于 libvirt 的 GUI 工具 + +- virt-v2v:虚机格式迁移工具 + +- virt-\* 工具:包括 Virt-install (创建KVM虚机的命令行工具), + Virt-viewer + (连接到虚机屏幕的工具),Virt-clone(虚机克隆工具),virt-top 等 + +- libguestfs-tools:一组 Linux 下的 C 语言的 API + ,用来访问/修改虚拟机的磁盘映像文件。 + +|image4| + +8.4.4 创建虚拟机 +---------------- + +手工创建 +^^^^^^^^ + +虚拟机的本质是宿主机上的一个进程,当你用OpenStack在界面,或者使用CLI创建了一个虚拟机时。你可以登陆到计算节点去,使用\ ``ps -ef|grep kvm`` +看下这个虚拟机的进程,是下面这样子的。 + +天呐,参数多得让人头皮发麻。这要是没有通过 OpenStack +创建,我得写这么长一串命令才能创建一台虚拟机。瞬间觉得 OpenStack +牛逼了不少。 + +.. code:: shell + + $ /usr/libexec/qemu-kvm \ + -name guest=instance-00000035, debug-threads=on \ + -S -object secret,id=masterKey0,format=raw,file=/var/lib/libvirt/qemu/domain-216-instance-00000035/master-key.aes \ + -machine pc-i440fx-rhel7.5.0,accel=kvm,usb=off,dump-guest-core=off \ + -cpu host \ + -m 16384 \ + -realtime mlock=off \ + -smp 2,maxcpus=32,sockets=2,cores=16,threads=1 \ + -uuid 31d70882-194f-469b-855e-fcfa6736550d \ + -smbios type=1,manufacturer=RDO,product=OpenStack Compute,version=0.0.1-1.el7.centos,serial=bc147bfe8a204d06a09f98387e46b890,uuid=31d70882-194f-469b-855e-fcfa6736550d,family=Virtual Machine \ + -display none \ + -no-user-config -nodefaults \ + -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/domain-216-instance-00000035/monitor.sock,server,nowait \ + -mon chardev=charmonitor,id=monitor,mode=control \ + -rtc base=utc,driftfix=slew \ + -global kvm-pit.lost_tick_policy=delay \ + -no-hpet -no-shutdown -boot strict=on \ + + -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 -drive file=/dev/hdd-volumes/31d70882-194f-469b-855e-fcfa6736550d_disk,format=raw,if=none,id=drive-virtio-disk0,cache=none,aio=native -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1 -drive file=/var/lib/nova/instances/31d70882-194f-469b-855e-fcfa6736550d/disk.config,format=raw,if=none,id=drive-ide0-0-0,readonly=on,cache=writeback \ + -device ide-cd,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0 \ + -netdev tap,fds=28:35,id=hostnet0,vhost=on,vhostfds=36:37 \ + -device virtio-net-pci,mq=on,vectors=6,netdev=hostnet0,id=net0,mac=fa:16:3e:69:63:18,bus=pci.0,addr=0x3 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -device qxl-vga,id=video0,ram_size=67108864,vram_size=67108864,vram64_size_mb=0,vgamem_mb=16,max_outputs=1,bus=pci.0,addr=0x2 \ + -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x5 -msg timestamp=on + +virsh 创建 +^^^^^^^^^^ + +使用OpenStack创建的前提是你发搭建好OpenStack环境,由于这个环境部署比较复杂。所以你也可以使用virsh +命令来创建。 + +使用virsh +创建之前,你需要准备好一个镜像(可以qcow2格式的,也可以是raw格式的)。 + +然后你要准备一个xml文件,并写入你的虚拟机配置。 + +.. code:: xml + + + ws_controller01 + 9f9424b1-57ea-4281-b650-8fb4de03fc68 + 12582912 + 12582912 + 6 + + + + + + + /machine + + + hvm + + + + + + + + + + + + + + + + + + + + destroy + restart + restart + + + + + + /usr/libexec/qemu-kvm + + + + + + +
+ + + + + + +
+ + + +
+ + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +