高并发处理框架-Tornado入门

高并发处理框架-Tornado入门

简介

Tornado是高性能并发框架。适合做api服务。并且他内置的http服务器足够强大,可直接用于生产环境中。

安装

简单地通过pip install tornado即可安装。

协程的使用。

调用方式

在tornado中,协程通过下面三种方式调用:

  1. 在本身是协程的函数内通过yield关键字调用。
  2. 在IOLoop尚未启动时,通过IOLoop的run_sync()函数调用。(run_sync会帮助你完成启动IOLoop,执行函数调用和关闭IOLoop的工作)
  3. 在IOLoop已经启动时,通过IOLoop的spawn_callback()函数调用。(无返回值。只适合不需要返回值的函数使用)

防止阻塞

为防止在协程中调用了阻塞函数,从而导致性能下降。可以在协程中用线程池。

from concurrent.futures import ThreadPoolExecutor

thread_pool = ThreadPoolExecutor(2)

def mySleep(count):
    import time
    for i in range(count):
        time.sleep(1)

@gen.coroutine
def call_blocking():
    print("start of call blocking")
    yield thread_pool.submit(mySleep, 10)
    print("end of call_blocking")

在协和中等待多个异步调用

把这些调用组织成list/dict传给yield即可。

如下所示:

from tornado import gen
from tornado.httpclient import AsyncHTTPClient

@gen.coroutine
def coroutine_visit():
    http_client = AsyncHTTPClient()
    dict_response = yield {
        "baidu":http_client.fetch("www.baidu.com"),
        "sina":http_client.fetch("www.sina.com"),
        "163":http_client.fetch("www.163.com"),
        "google":http_client.fetch("www.google.com"),
        }
    print(dict_response['sina'].body)

Tornado开发介绍

helloword app

三步曲:
1. 创建app
2. 监听端口
3. 启动事件循环。

完整代码如下:

import tornado.ioloop #事件循环
import tornado.web #基本控件

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello world")

def main():
    app = tornado.web.Application([r"/", MainHandler]) #数组里是定义路由
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

if __name__ == '__main__':
    main()

在这里测试了下。在我的macbook(2核+8G内存)下。python+tornado在请求100次,并发10的情况下每秒处理请求数是833
而php7+yii2是37。同样只是简单地输出”hello world”。

路由

路由配置没啥好说的,都一样。字符串/正则。其中需要提取的需要用()分组。

RequestHandler

这个是重点。

请求前,请求后等处理

可以直接参考源码。这里做个大概介绍。
请求前会调用prepare方法。
请求后会调用on_finish方法。
如果需要其它参数,譬如数据库。可以通过配置路由时,再传一个字典参数。

restfulApi

感觉完全是为这个而生的呀。
直接重写RequestHandler相应的方法即可。(get/head/post/delete/patch/put/options)
关于restfulapi可以参考.

获取参数

有两种参数,一种是get请求的,在url中。另一种是post请求的。在Body里面。
get_argument()/get_arguments() 是综合了两种。
get_query_argument()/get_query_arguments() 是url中的。
get_body_argument()/get_body_arguments() 是body中的。
说明下。下面带s的是用于获取数组的。
get_cookie() 获取cookie
request属性可以获取其它信息,如请求方的ip地址,host, method, uri, path,query,headers, body,cookies等。
其中如果发起方用的是json格式的数据,这里要用到body,而上面的3对argument方法无效。

响应

set_status() 设置状态码
set_header()/add_header() 设置http头信息
write()直接输出。当参数是字典时,自动转成json格式,并且Content_Type也会设置成application/json
render()渲染tempalte后输出。
redirect()重定向。

改变同步代码

  1. 异步化:针对RequestHandler的处理函数使用@tornado.web.asynchronous修饰器。
  2. 协程化:针对RequestHandler的处理函数使用@ado.gen.coroutine修饰器,将默认的同步机制改为协程机制。

算法排序之Python实现

算法排序之Python实现

前言

排序是最经典的问题,基本上玩算法入门就是排序。
python本身有sorted函数。不过下面我们自己实现一下。
以下数据均是在我的同一台macbook上测试的。从结果也可以看出算法的重要性。

排序按模型分类有:

  1. 每次比较只有2个元素。包括插入排序,快速排序,堆排序,归并排序,冒泡排序。他们最好的效率只能到O(nlgn).可以通过画决策树证明。 一共有n!个节点。高度h的数最多有\(2^h\)个节点。所以有\(2^h \geq n!\)。通过斯特林公式\(n!=\sqrt{2πn}(\frac{n}{e})^n(1+\Theta(\frac{1}{n}))\) 可以得证。
  2. 对数据做些操作。譬如计数排序。

1.插入排序

这个算法和打牌很像,玩过牌的应该能很快理解。不多解释,看python的简单实现。
其最坏情况是( \Theta(n^2) )

#升序
def insert_sort(literal):
    if not literal or len(literal) == 1:
        return literal

    sorted = literal[:1]

    for i in range(1, len(literal)):
        v = literal[i]
        sorted.append(0)
        for j in range(i-1, -1, -1):
            if v < sorted[j]:
                sorted[j+1] = sorted[j]
            else:
                sorted[j+1] = v
                break

    return sorted

测试了一万条数据:

python insert_sort.py 6.66s user 0.45s system 98% cpu 7.192 total

2. 选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

def select_sort(data):
    def swap(i, j):
        temp = data[i]
        data[i] = data[j]
        data[j] = temp

    for i in range(len(data)):
        min = data[i]
        index = i
        for j in range(i, len(data)):
            if data[j] < min:
                min = data[j]
                index = j
        swap(i, index)
    return data

3. 归并排序

归并排序描述起来好简单。就是把规模分成两组,一组都小于某个数n, 另一组都大于等于n。于是规模为n的问题,变成了2个n/2规模的问题。一次次这样划分下去,直到只剩下一个元素,一个元素当然是排好充的,然后再两两合并,直到得到原始问题的解。

其最坏情况是( \Theta(nlgn) )

这个思想是分治法

下面是python的实现:

#升序
def merge_sort(literal):

    def divide(literal):
        end = int(len(literal)/2)
        return (literal[:end], literal[end:])

    def conquer(literal):
        return merge_sort(literal)

    def merge(literal1, literal2):
        ret = []
        len1 = len(literal1)
        len2 = len(literal2)
        i1 = 0
        i2 = 0
        while len1 or len2:
            if not len1 and len2:
                ret.extend(literal2[i2:])
                return ret
            if not len2 and len1:
                ret.extend(literal1[i1:])
                return ret

            v1 = literal1[i1]
            v2 = literal2[i2]
            if v1 <= v2:
                ret.append(v1)
                len1 -= 1
                i1 += 1
            else:
                ret.append(v2)
                len2 -= 1
                i2 += 1
        return ret

    if not literal or len(literal) == 1:
        return literal

    literal1, literal2 = divide(literal)
    literal1 = conquer(literal1)
    literal2 = conquer(literal2)
    return  merge(literal1, literal2)

测试了一万条数据:

python merge_sort.py 0.22s user 0.02s system 84% cpu 0.283 total

之所以divide写的不那么好看,是为了一定要把问题分解。否则遇到 [1, 1 ,1]这样的数据就悲剧了。

4. 冒泡排序

它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端,故名。

#!/usr/bin/env python
import sys
import random
def bubble_sort(data):
    def swap(i, j):
        temp = data[i]
        data[i] = data[j]
        data[j] = temp

    exchanged = False
    for i in range(len(data)):
        for j in range(len(data)-1, i, -1):
            if data[j] < data[j-1]:
                swap(j, j-1)
                exchanged = True
        if not exchanged:
            return data
    return data

5. 堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。

时间效率是(O(n\lg{n})),但实践中不如快速排序。不过用来实现优先级队列非常好。

#!/usr/bin/env python
import sys
import random


def heap_sort(data):
    datalength = len(data)
    data.insert(0, 0)

    def parent_index(i):
        return i >> 1

    def left_index(i):
        return i << 1

    def right_index(i):
        return left_index(i)+1

    def max_heapfy(heap_size, i):
        left = left_index(i)
        right = right_index(i)
        max = data[i]
        key = i
        if left <= heap_size and data[left] > max:
            max = data[left]
            key  = left

        if right <= heap_size and data[right] > max:
            max = data[right]
            key = right
        if key != i:
            swap(i, key)
            max_heapfy(heap_size, key)


    def build_heap():
        for i in range(int(datalength/2), 0, -1):
            max_heapfy(datalength, i)


    def swap(i, j):
        temp = data[i]
        data[i] = data[j]
        data[j] = temp

    build_heap()
    for i in range(datalength, 0, -1):
        swap(i, 1)
        max_heapfy(i-1, 1)


    return data[1:]

算法描述里索引是从1开始的,我这里取了个巧。补了个无用的0,来达到索引从1开始的目的。

6. 快速排序

快速排序和归并排序差不多。区别是归并排序只是把一个规模为n的问题划分成2个规模为n/2的子问题。
而快速排序划分的两个子问题,本身是有序的。即子问题A中的数都要小于等于子问题B中的数,这样在合并问题时更简单。

由于python的特性。做切片操作其实是做的复制工作。
我这里会提供二个快速排序的实现。供参考。

实现1

def quick_sort(literal):

    def divide(literal):
        v1 = literal[0]
        v2 = literal[1]
        v = max(v1, v2)
        literal1 = []
        literal2 = []
        if v1 == v2:
            literal1.append(v1)
            literal2.append(v2)
            for i in literal[2:]:
                if i < v:
                    literal1.append(i)
                else:
                    literal2.append(i)
        else:
            for i in literal:
                if i < v:
                    literal1.append(i)
                else:
                    literal2.append(i)

        return (literal1, literal2)

    def conquer(literal):
        return quick_sort(literal)

    def merge(literal1, literal2):
        ret = []
        ret.extend(literal1)
        ret.extend(literal2)
        return ret

    if not literal or len(literal) == 1:
        return literal

    literal1, literal2 = divide(literal)
    literal1 = conquer(literal1)
    literal2 = conquer(literal2)
    return  merge(literal1, literal2)

实现二的改进是原地排序

这种方式少了copy的代价,占用的空间更少,少了合并的步骤,效率也会更高。
我有个问题没有想清楚,这个程序和上面的程序差不多,都是递归实现,但是上面的程序可以排序100万个数都没问题,这个程序排序到2万个数就报堆栈溢出了。暂时放个问号????

#升序, 原地排序
def quick_sort(literal, start, end):
    def swap(i, j):
        temp = literal[i]
        literal[i] = literal[j]
        literal[j] = temp

    def divide(start, end):
        i = start
        key = literal[start]

        for j in range(start+1, end):
            if literal[j] <= key:
                swap(j,i+1)
                i += 1
        swap(i, start)
        return i+1


    if not literal or len(literal) <= 1:
        return
    if (end-start) <= 1:
        return
    index = divide(start, end)
    quick_sort(literal, start, index)
    quick_sort(literal, index, end)
    return literal

7. 计数排序

这个有个前提条件。必须是整数范围,同时最大的数不能太大。因为效率是O(n+k), 所以k不能太大。
远远小于n时,效率非常高,为线性排序。

参考实现:

def count_sort(alist, k):
    '''
    要求元素都比较小。时间效率是O(k+n),k为最大值。
    '''
    countList = [0 for _ in range(k+1)]
    for v in alist:
        countList[v] += 1
    retList = [0 for _ in range(len(countList))]
    for i, v in enumerate(countList):
        retList.extend([i for _ in range(v)])
    return retList

8. 基数排序

基数排序要用到计数排序。先对最后一位排序,再对前一位排序,直到排序完成。
同样要求是整数。好的是要求的数字大小范围没有计数排序那么小。

理论上这是最佳的排序算法,但是在实践中,最优秀的是快速排序
其时间复杂度为(O(n\log_rm)),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

上面实现的计数排序是偷懒的做法。因为是新生成的元素,而这里只是对某位排序,需要保留原数字,故要实现完整版的计数排序。

# -*- coding:utf-8 -*-
#!/usr/bin/env python
import sys
import random

def radix_sort(data, length):
    '''
    这个是简单版本,只实现了十进制的。
    '''
    def count_sort(alist, p):
        '''
        要求元素都比较小。时间效率是O(k+n),k为最大值。p为要排序的位置
        '''
        def get_position_value(v, p):
            for i in range(1, p+1):
                left = v % 10
                v = v/10
            return left
        countList = [0 for _ in range(10)]
        for v in alist:
            countList[get_position_value(v, p)] += 1
        sum = 0
        #calculate the position
        for i in range(10):
            sum += countList[i]
            countList[i] = sum

        retList = [0 for _ in range(len(alist))]
        for v in alist[::-1]:
            index = countList[get_position_value(v, p)]
            retList[index-1] = v
            countList[get_position_value(v, p)] -= 1
        return retList

    for i in range(1, length+1):
        data = count_sort(data, i)

    return data

9. 桶排序

算法经典问题之第k大/小问题(寻找中位数)

算法经典问题之第k大/小问题(寻找中位数)

问题描述

从n个未排序的数中找出第k小的数。
有直接解法是先排序再拿第k个数即可,但是最好的算法也要(O(n\lg{n}))。
下面的解法更优。

解法

这个其实是利用的分治法思考,和二分查找很像。
和快速排序同样的过程,先随机选一个数,然后把比它小的放到左边,比它大的放到右边。设排完后这个数的索引是m,
对比m和k,
如果m==k,说明这个数就是我们要找的。
如果m<k, 说明前m个数都比要找的数小。从而问题变为从右边的数中找到第k-m小的数。
如果m>k,说明要找的数在前m个数中,从而问题变为从右边的数中找到第k小的数。

按此算法,它的时间效率是O(n)。

python实现

#!/usr/bin/env python
import random


def min_k(data, k):

    def swap(i, j):
        temp = data[i]
        data[i] = data[j]
        data[j] = temp

    n = len(data)
    s = random.randint(0, n-1)
    swap(s, 0)
    j = 1
    key = data[0]
    for i in range(1, n):
        if data[i] < key:
            swap(j, i)
            j += 1
    swap(j-1, 0)
    if j == k:
        return data[j-1]
    elif j > k:
        return min_k(data[0:j-1], k)
    else:
        return min_k(data[j:], k-j)

数据结构之哈稀表

数据结构之哈稀表

前言

哈稀表在工作中常用,语言都内置有,对应于字典或者对象。
首先得定义一些操作。我有一组数据,每个数据x有key=>value这样的结构。
操作1, 插入一对新的x到这个集合中。
操作2,删除这个集合中的x.
操作3,搜索这个集合中key为k的x。

最简单的实现

直接映射表。
要求这些x的key是在有限集合中,并且是不重复的,如U = {0,1,…m-1}
你只需要建立一个数组,key为索引,值为value就可以了。

但他的问题是可能m很大,但要操作的数据很小,这样这个数组就会很稀。

哈稀的引入

所谓的哈稀法,就是用一个哈希函数H来随机映射那些键。

其实就是针对上面直接映射表的。即然key的集合很大,那我就通过一个函数把这个key的集合映射到一个比较小的集合上。
但这会引入另一个问题,从大集合到小集合,一定存在着多个key映射到同一个值的情况。这被称为碰撞。

链表法解决碰撞

解决办法就是在新的小集合上,new-key存放的并不是x,而是一个存放有x的链表。当要查找时,就遍历这个链表,对比key和x.key即可。

但这种实现在最坏情况下访问效率很低。
最坏情况是所有key映射到同样的一个new-key,这时候就只剩下一个链表了,查找效率是(\theta(n))

从这可看出哈希的关键是选取恰当哈希函数H, 使得key能均匀地哈希到new-key。
装载因子>1, 即平均每个槽放的元素>1.

开放寻址法解决碰撞

这种方案不是使用链表,而是在第一次哈希后,如果遇到碰撞则再哈希一次看看,如此形成一个哈希序列,也叫探查序列。
好处是不需要增加额外的数据结构,不需要修改元素。缺点是删除不好处理,如果直接删除,则原来因为碰撞而需要再次探查的数据会误以为元素不在哈希表中。

装载因子(\alpha)<1, 即平均每个槽放的元素<1.预期探查次数是( \frac{1}{1-\alpha} )
探查序列有两种方案。

线性方法

[h(k, i) = (h(k, 0) + i)\ mod\ m]
h(k, i)表示对k的第i次探查。

这种方法简单,但是容易遇到集群现象,即当碰撞时,会在局部集中有值。

二次哈希

这种在实践中比较实用。
[h(k ,i) = (h_1(k) + i * h_2(k))\ mod\ m]
其中(m= 2^r), h_2(k) 为奇数。

哈希函数的选择

除法

h(k) – k mod m (m是新集合的大小)
这里m不能太小,最好是质数。

乘法

m是(2^r), 机器字长为w。
[h(k) = (A*k\ mod\ 2^w)\ rsh\ (w-r)]
A为介于(2^{w-1})到(2^w)之间的某个奇数。 rsh表示右移操作。
因为CPU有指令直接可以得到两个w相乘后的低w位,mod和rsh都是位移操作。所以这个算法相对上面的除法效率要高。

高级主题

哈希的问题

因为哈希是从一个大的集合哈希到一个小的集合,那么对于任意一个哈希函数来说,
都存在一个不好的key集合,都会哈希到同一个槽。

全域哈希

为了解决上面的问题,我们从哈希函数的集合中采取随机的哈希函数,这就叫做全域哈希。这里要求选取随机哈希函数的有限集有一个规律:当从中随机选择两个哈希函数时,他们哈希到同一个槽的概率是1/m。

做法

这里有个前提,选择槽数m为质数。
把key按m进制表示为(<k_0, k_1, …k_r>).
再随机选择一个数a, 也按m进制表示为(<a_0, a_1, a_2…a_r>)
定义(h_a(k) = (\sum_{i=0}^{r}a_iK_i)\ mod \ m)

完全哈希

完全哈希是用来处理静态哈希表。
有两个要求:
1. m = O(n)
2. 在最坏情况下查找的时间效率是O(1)

做法

用二级结构。并且每级结构都用全域哈希。
假设在第一级第i个槽有(n_i)个元素发生碰撞,则二级结构的大小是(n_i^2)

python标准库difflib介绍

python标准库difflib介绍

简介

从python2.3起difflib成为了标准库。无需安装即可使用。
之前遇到一个问题,我本地的代码没有问题,而服务器上的则一直报404错误。肉眼对比了一下路径,感觉是完全一样的。在网上找了下字符串对比工具,没有一个好用。
后来还是有个同事眼力好,发现有个一字符的大小写问题导致的。那会如果知道python有difflib库的话,早就把问题解决了。

简单的对比

以下示例是一个相当简单的代码。

import difflib

text1 = """
<!-- 
Template Name: Metronic - Responsive Admin Dashboard Template build with Twitter Bootstrap 3.3.6
Version: 4.5.4
Author: KeenThemes
Website: http://www.keenthemes.com/
Contact: support@keenthemes.com
Follow: www.twitter.com/keenthemes
Like: www.facebook.com/keenthemes
License: You must have a valid license purchased only from themeforest(the above link) in order to legally use the theme for your project.
-->
"""

text2 = '''
<!-- 
Template Name: Metronic - Responsive Admin Dashboard Template build with Twitter Bootstrap 3.3.6
Version: 4.5.5
Author: alston 
Contact: support@keenthemes.com
Follow: www.twitter.com/keenthemes
Like: www.facebook.com/Keenthemes
Purchase: http://themefrest.net/item/metronic-responsive-admin-dashboard-template/4021469?ref=keenthemes
License: You must have a valid license purchased only from themeforest(the above link) in order to legally use the theme for your project.
-->
'''

text1_lines = text1.splitlines()
text2_lines = text2.splitlines()

d = difflib.Differ()
diff = d.compare(text1_lines, text2_lines)

print('\n'.join(list(diff)))

输出示例:

<!–
Template Name: Metronic – Responsive Admin Dashboard Template build with Twitter Bootstrap 3.3.6
– Version: 4.5.4
? ^

看着真是费劲,-,+,?,。能感觉出来-是少了,+是增加了,?和表示存在差异。

试试html美化的

修改比较简单。只要把

d = difflib.Differ()
diff = d.compare(text1_lines, text2_lines)

print('\n'.join(list(diff)))

替换成

d = difflib.HtmlDiff()
print(d.make_file(text1_lines, text2_lines))

即可。

看着还不错。会用颜色标识出来。

更多对比工具

标准库里还有个filecmp库,支持单文件对比,多文件对比,目录对比。

运维管理工具fabric详解

运维管理工具fabric详解

前言

运维管理工具有fabric,ansible等等。
本节讲下fabric,最开始接触是在廖雪峰的文章里。他用fabric做的运程部署。
这里打算详细了解下。
官网点击这里。

安装

这个真没啥讲的。我都是pip install fabric.
不过现在还不支持3.x, 所以还是请用virtualenv吧。

文件

执行fab命令时,默认会使用fabfile.py, 如果你的文件名不是这个,请使用-f参数。
fab -f yourfile.py

fab的常用参数

可以通过fab -help查看

  1. -l 显示定义好的任务函数名。
  2. -f 指定fab入口文件
  3. -g 指定网关(中转)设备,比如堡垒林环境,填写堡垒机IP即可。之前在新浪和腾讯工作时,他们都有跳板机/堡垒机。 阿里云的金融云模式也提供堡垒机。
  4. -H 指定目标主机,多台主机用”,”分隔;
  5. -P, 以异步并行方式运行多主机任务,默认为串行运行。
  6. -R, 指定role, 以角色名区分不同业务组设备。
  7. -t, 设置设备连接超时时间(秒).
  8. -T, 设置远程主机命令执行超时时间(秒);
  9. -w, 当命令执行失败,发出告警,而非默认中止任务。

可以不写一行python代码也可以完成简单的远程操作,直接使用命令行的形式,例如。

fab -u 用户 -p 密码 -H 主机IP -- 'uname -s'

结果
[123.57.145.149] Executing task ”
[123.57.145.149] run: uname -s
[123.57.145.149] out: Linux
[123.57.145.149] out:
Done.
Disconnecting from 123.57.145.149… done.

fabfile的编写

全局属性设定

在命令行中的参数如 -u -p 等可以通过env对象来设定。
env对象的作用是定义fabfile的全局设定,支持多个属性,包括目标主机、用户、密码、角色等,各属性说明如下:

  • env.hosts, 定义目标主机,可以用IP或主机名表示,以Python的列表形式定义,如env.hosts = [‘192.168.1.21’, ‘192.168.1.22’]
  • env.exclude_hosts, 排除指定主机,如env.exclude_hosts=[‘192.168.1.22’]
  • env.user, 定义用户名, 如env.host=”work”
  • env.port, 定义目标主机端口,默认为22, 如env.port=”22″.
  • env.password, 定义密码,是对应user的密码.如env.password=”password123″,这里密码太弱,只是说明一下用法.
  • env.passwords, 与password功能一样,区别在于不同的主机不同密码的应用场景,需要注意的是,配置passwords时需配置用户、主机、端口等信息,如
env.passwords = {
'root@192.168.1.21:22':'fdsfsf',
'root@192.168.1.22:22':'dfsfsfsafsafas',
'root@192.168.1.23:22':'ppopojpoj[p'
}
  • env.key_filename 可以通过pem文件登录。而不用暴露密码。
  • env.gateway,定义网关IP, 同命令参数 -g
  • env.roledefs, 定义角色分组,比如web组与db组主机区分开来,定义如下:

    env.roledefs = {
    'webservers': ['192.168.1.21', '192.168.1.22', '192.168.1.23', '192.168.1.24'],
    'dbservers':['192.168.1.25', '192.168.1.26']
    }
    

    引用时使用Python修饰符的形式进行,角色修饰下面的任务函数为其作用域,下面来看一个示例:

@roles('webservers')
def webtask():
    run('/etc/init.d/nginx start')
     

@roles('dbservers')
def dbtask():
    run('/etc/init.d/mysql start')
    
@roles('webservers', 'dbservers')
def pubclitask():
    run('uptime')
    
def deploy():
    execute(webtask)
    execute(dbtask)
    execute(pubclitask)

在命令行执行fab deploy就可以实现不同角色执行不同的任务函数了。

常用API

这些API封装了执行本地命令,执行远程主机命令等功能。
说明如下:

  • local ,执行本地命令。 如:local(‘uname -s’);
  • run, 执行远程命令
  • lcd, 切换本地目录,
  • cd ,切换远程目录。
  • sudo ,sudo 方式执行远程命令
  • put 上传本地文件到远程主机
  • get 从远程主机下载文件到本地
  • prompt,获取用户输入信息,用于交互
  • confirm, 获得提示信息确认,用于交互
  • reboot, 重启远程主机
  • @task, 函数修饰符,标识的函数为fab可调用的,非标记对fab不可见,纯业务逻辑;
  • @runs_once, 函数修饰符,标识的函数只会执行一次,不受多台主机影响。

后记

更细节的可以考虑官网
示例可以看刘天斯写的<Python自动化运维>或廖雪峰的Python教程。

SQLAlchemy介绍

前言

玩Python的人很有必要非常熟悉SQLAlchemy。因为它是python中最主流的orm。
我之前只在flask中用过。发现用的还不是很顺畅,不能手到拿来。故又整理了这篇文章。

查询sql有几个点:

  1. 直接原始语句查询。
  2. 对象关系映射,即orm
  3. 其它关联的是数据库链接池和事务。

下面一一道来。

直接使用原生语句查询

pymysql

SQLAlchemy依赖于各种驱动,底层其实还是用的pymysql之类的。相当于一个适配器类,把其它的操作都抽象化了。

查询示例

import  pymysql
 
#创建连接
conn = pymysql.connect(host='192.168.56.11',port=3306,user='root',passwd='root',db='oldboydb')
#创建游标  socket上建立的实例
cursor=conn.cursor()
  
#执行SQL,并返回行数,用游标执行,
effect_row = cursor.execute("select * from student")
# print(cursor.fetchone())
# print(cursor.fetchone())
print(cursor.fetchall())

插入示例

import  pymysql

#创建连接
conn = pymysql.connect(host='192.168.56.11',port=3306,user='root',passwd='root',db='oldboydb')
#创建游标  socket上建立的实例
cursor=conn.cursor()
data = [
    ("N1","2015-05-22",'M'),
    ("N2","2015-05-22",'M'),
    ("N3","2015-05-22",'M'),
    ("N4","2015-05-22",'M'),
    ]
cursor.executemany("insert into student (name,register_date,gender) values(%s,%s,%s)",data )
conn.commit()

使用SQLAlchemy

这个是SQLAlchemy Core中的。

查询

from sqlalchemy import create_engine
e = create_engine('mysql://user:pass@host/db')
for row in e.execute('select * from table where id < %s', 2):
    print(dict(row))

这是最简单的,只用到了engine对象。

还可以用字典参数,如下

from sqlalchemy import text
result = e.execute(text('select * from table where id < :id'), {'id': 2})

相对于上面pymysql中只能用索引或字典。
这里可以用索引、key、属性。

row[0] == row['id'] == row.id

事务支持

conn = e.connect()
try:
    conn.begin()
    #dosomething
    conn.commit()
except:
    conn.rollback()

python版本的sql表达式

这个比写原生sql语句好,因为可以跨数据库引擎写。我就不知道sqlserver,但工作中有这种类型的库。
我用python就比较开心了。不用每次都上网查。
这个就是我需要用的,临时任务写orm太复杂了, 偏偏又不熟悉sqlserver。

查询

from sqlalchemy import Table, MetaData
meta = MetaData(bind=e, reflect=True)
users = meta.tables['user]
list(e.execute(users.select(table.c.id < 2)))

除了上面的直接在engine上查询,也可以在connection上查询

con  = engine.connect()
con.execute(users.insert(), name='admin', email='admin@loclhost')

甚至还可以直接在table上调用excute()方法

users.select(users.c.id == 1).execute().first()

ORM

先看一个示例,半手动

from sqlalchemy import orm
class Table(object):
    pass

meta = MetaData(bind=e, reflect=True) 
orm.Mapper(Table, meta.tables['table'])
s = orm.Session(bind=e)
s.query(Table).filter(Table.id < 2).first().info

注意到几个对象:
engine, 数据库连接的. 可以直接在engine上查询。
connection, 这个是和事务相关的。 当用connection时,查询是在connection上。
MetaData, 对应数据库表的,可以手写Table, 也可以自动反射出来。
Mapper对象就是用来把object和table 做映射的。
session 这里其实已经把事务加了进来, 查询是在session上。原如的sql和python表达式都是在engin对象上。
在flask-sqlalchemy中,查询是直接在对象上的。

声明的方式

上面用的是半手动模式,object是手动的,table是自动反射的。
这里介绍声明的方式

import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
# 创建实例,并连接test库
engine = create_engine("mysql+pymysql://root:123456@localhost/test",
                                    encoding='utf-8', echo=True)
# echo=True 显示信息
Base = declarative_base()  # 生成orm基类
                                    
class User(Base):
    __tablename__ = 'user'  # 表名,这里就关联上Table了
    id = Column(Integer, primary_key=True)
    name = Column(String(32))
    password = Column(String(64))
                                                    
Base.metadata.create_all(engine) #创建表结构 (这里是父类调子类)

手动模式

from sqlalchemy import Table, MetaData, Column, Integer, String, ForeignKey
from sqlalchemy.orm import mapper
metadata = MetaData()
user = Table('user', metadata,
            Column('id', Integer, primary_key=True),
            Column('name', String(50)),
            Column('fullname', String(50)),
            Column('password', String(12)))

class User(object):
    def __init__(self, name, fullname, password):
        self.name = name
        self.fullname = fullname
        self.password = password

mapper(User, user)

另一种半手动

上面的方式user表是手动写的。还可以用autoload自动配置。

user = Table('user', metadata, autoload=True)

使用reflect性能会比较低,他会一次性把整个库的所有表都看下结构,然后自动配置。
autoload只会自动反射当前表。
性能最佳的当然还是手动模式。

查询

Session_class = sessionmaker(bind=engine)  # 实例和engine绑定
Session = Session_class()  # 生成session实例,相当于游标
my_user = Session.query(User).filter_by(name="fgf").first()  # 查询
print(my_user)

插入

# 创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例
Session_class = sessionmaker(bind=engine)  # 实例和engine绑定
Session = Session_class()  # 生成session实例,相当于游标

user_obj = User(id=27,name="fgf",password="123456")  # 生成你要创建的数据对象
print(user_obj.name,user_obj.id)  # 此时还没创建对象呢,不信你打印一下id发现还是None

Session.add(user_obj)  # 把要创建的数据对象添加到这个session里, 一会统一创建
print(user_obj.name,user_obj.id) #此时也依然还没创建

Session.commit() #现此才统一提交,创建数据

遇到的问题

连接池的问题

现在开始流行serverless了,这种模式下建议是关闭连接池。
关闭的方法是在创建engin时指定连接池类为NullPoll类.

e = create_engine('mssql+pymssql://yourserver',
                  poolclass=pool.NullPool)

orm类型找不到问题

参考
https://stackoverflow.com/questions/34894170/difficulty-serializing-geography-column-type-using-sqlalchemy-marshmallow

分表问题

通过type类自动生成对应的类。

其它经验

sqlacodegen

sqlacodegen是orm中model的自动生成工具。有了这个工具,你就不用手写model对象了。

参考

http://www.mapfish.org/doc/tutorials/sqlalchemy.html
http://www.rmunn.com/sqlalchemy-tutorial/tutorial.html
http://blog.csdn.net/tantexian/article/details/39230459
http://docs.sqlalchemy.org/en/latest/

Python概览

Python概览

print("Hello, Welcome to Python's world")

欢迎进入Python的世界,第一件事请习惯,我们不用{}, 我们用缩进。
用缩进的请注意,空格和tab不能混用。 注意到了这个,你就能避免下面的错误

IndentationError: unexpected indent

推荐的项目结构

作者开发了知名的flask轻量级web框架和requests库。

git仓库

python代码组织结构
package/module/变量。
package是指带有__init__.py 的目录, 当被首次被import和reload 的时候__init__.py 会执行,

包安装 pip工具

pip install package
pip freeze > requestments.txt
pip install -r requestments.txt

一般是和virtualenv结合起来用的。

解决pip安装慢

多版本管理pyenv

pyenv vertions 列出当前安装的版本
pyenv install version 安装指定的版本
pyenv global version 设置全局的python版本
Python 多版本共存之 pyenv

干净的应用环境virtualenv

virtualenv venv
virtualenv venv -p 指定python版本
source venv/bin/active
deactive
Python虚拟环境virtualenv

代码风格

PEP8 标准

pycodestyle 检测是否符合标准
pycodestyle yourscript.py
autopep8 可以自动替换成符合标准的格式
autopep8 --in-place yourscript.py

单元测试

  1. nose包, 安装后有nosetests命令 nosetests -v
  2. unittest python -m unittest module_name
  3. doctest 这个是更多是用在演示api如何使用的。 也可能通过doctest模块运行,如果文档的值和实际的值不一样,就会报错误。
  4. pytest unittest的可替代品
  5. tox 在需要多版本兼容时,可以用tox做自动化测试
  6. mock Python单元测试 #效率问题 Python专题之性能与优化

自动文档化 Sphinx

应用介绍

web方向

Django
Flask
Tornado
Pyramid
Twisted

运维方向

fabric
运维管理工具fabric详解
ansible
puppet

大数据方向

机器学习方向

推荐学习

  1. 入门级 可以廖雪峰的教程。
  2. 深入语法 可以学<Python学习手册>。
  3. 整体感知可以看下<Python高手之路>.
  4. web开发可以学

比较好用的库推荐

运维管理 psutils

黑客攻防

ORM SQLAlchemy

SQLAlchemy

requests 库, http请求库

Python3标准库urllib vs requests

logging模块,标准日志

python之logging模块

建议掌握好的几个概念

装饰器

def foo():
    # do something

def decorator(func):
    # manipulate func
    return func
    
foo = decorator(foo) #手动装饰

@decorator
def bar():
    # Do someting
    # bar() is decorated

上下文管理 context manager

函数式编程

Python函数式编程

标准库

下面是一些必须了解的标准库模块。

  • abc 提供抽象基类等功能。
  • atexit 允许注册在程序退出时调用的函数
  • argparse 提供解析命令行参数的函数。
  • bisect 为可排序列表提供二分查找算法。
  • calendar 提供一组与日期相关的函数。
  • codecs 提供编解码数据的函数。
  • collections 提供一组有用的数据结构。
  • copy 提供复制数据的函数。
  • csv 提供读写CSV文件的函数。
  • datetime 提供用于处理日期和时间的类。
  • fnmatch 提供用于匹配Unix风格文件名模式的函数。
  • glob 提供用于匹配Unix风格路径模式的函数。
  • io 提供用于处理I/O流的函数。
  • json 提供用来读写JSON格式函数的函数。
  • logging 提供对Python内置的日志功能的访问。可以参考这里
  • multiprocessing 可以在应用程序中运行多个子进程。可以参考这里
  • operator 提供实现基本的Python运算符功能的函数,可以使用这些函数而不是自己写lambda表达式。
  • os 提供对基本的操作系统函数的访问。
  • random 提供生成伪随机数的函数。。不能用在安全领域。
  • re 提供正则表达式功能。
  • select 提供对函数select()和poll()的访问,用于创建事件循环。
  • shutil 提供对高级文件处理函数的访问。
  • signal 提供用于处理POSIX信号的函数。可以参考这里
  • tempfile 提供用于创建临时文件和目录的函数。可以参考这里
  • threading 提供对处理高级线程功能的访问。可以参考这里
  • urllib(以及Python2.x中的urllib2和urlparse)提供处理和解析URL的函数。
  • uuid可以生成全局唯一标识符。

Linux磁盘分区及链接文件的特点-演道网

Linux系统分区

传统的分区fdisk 最大支持2T的硬盘分区

对存储,多分区使用的parted

  • 主分区:最多只能有4个
  • 扩展分区
    • 最多只能有1个
    • 主分区加扩展分区最多有4个
    • 不能写入数据,只能包含逻辑分区
  • 逻辑分区

在Linux中,任何东西都是文件

挂载

  • 必须分区
    • / (根分区)
    • swap分区(交换分区,4G以下,内存两倍,4G以上,内存一样)
  • 推荐分区
    • /boot 启动分区,200MB

fdisk  /dev/sda  ###sdx 都是ISCSI

参数:

  a  toggle a bootable flag
  b  edit bsd disklabel
  c  toggle the dos compatibility flag
  d  delete a partition                              ###删除分区
  l  list known partition types                    ###列分区id信息,82/83
  m  print this menu
  n  add a new partition           ###添加分区
  o  create a new empty DOS partition table
   p  print the partition table                      ###打印分区表
  q  quit without saving changes
  s  create a new empty Sun disklabel
   t  change a partition’s system id     ###修改分区id 82 swap  83默认linux
  u  change display/entry units
  v  verify the partition table
  w  write table to disk and exit      ###保存退出磁盘
  x  extra functionality (experts only)

02、格式化

mkfs.ext4 | xfs |ext3  /dev/sdx 

03、挂载分区

mount  /dev/sdx  /data 

/etc/fstab  ###开机自动挂载

/dev/sdx  /data  ext4  defaults  1 1

mount  -l              ###查看本地挂载信息

mount | column -t ###查看所有挂载信息【多用于排查远程挂载,服务端关闭引起的问题】


ln -s [原文件] [目标文件]
-s
创建软链接

-f 强制建立链接

ln -sf source target

ln    source target    ###硬链接

硬链接特征:
1.
拥有相同的iNode和存储block,可以看做是同一个文件
2.
可通过iNode识别 相同inode
3.
不能跨分区
4.
不能针对目录使用,只能针对文件

软链接特征:
1.
类似windows快捷方式
2.
软链接拥有自己的iNodeblock块,但是数据块中只保存原文件的文件名和iNode,并没有实际的文件数据
3.
lrwxrwxrwx l软链接,文件权限都为全rwx
4.
修改任意文件,另一个都改变
5.
删除原文件,软链接不能使用

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn