Welcome to my blog.

2019-02-17
分布式时序数据库InfluxDB

什么是InfluxDB?

InfluxDB 是一个开源分布式时序、事件和指标数据库。使用 Go 语言编写,无需外部依赖。其设计目标是实现分布式和水平伸缩扩展。

它有三大特性:

  • Time Series (时间序列): 你可以使用与时间有关的相关函数(如最大,最小,求和等)
  • Metrics(度量):你可以实时对大量数据进行计算

  • Eevents(事件):它支持任意的事件数据

官网介绍:https://www.influxdata.com/

如何安装InfluxDB?

系统环境:CentOS Linux release 7.3.1611 (Core)

1
2
3
wget https://dl.influxdata.com/influxdb/releases/influxdb-1.2.4.x86_64.rpm
sudo yum localinstall influxdb-1.2.4.x86_64.rpm
sudo systemctl start influxdb

官网下载连接:https://portal.influxdata.com/downloads

简单介绍InfluxDB用法

默认开启WEB访问界面地址为IP:8083,默认登录账号和密码为root/root

Linux字符进入直接输入influx

influxDB名词

  • database: 数据库;
  • measurement: 数据库中的表;
  • points: 表里面的一行数据。

influxDB中独有的一些概念

Point由时间戳(time)、数据(field)和标签(tags)组成。

  • time: 每条数据记录的时间,也是数据库自动生成的主索引;
  • fields: 各记录的值;
  • tags: 各种有索引的属性。

新增数据库

1
2
CREATE DATABASE "db_name"
SHOW DATABASES #显示所有数据库

进入数据库

1
2
3
USE "db_name"
# 显示该数据库中所有的表
show measurements

创建表,直接在插入数据的时候指定表名

1
2
3
4
5
6
> insert test-measurements,host=127.0.0.1,monitor_name=test count=1
> SHOW MEASUREMENTS
name: measurements
------------------
name
test-measurements

删除表

1
drop measurement "measurement_name"

通过API插入数据

1
curl -i -XPOST -u username:password "http://192.168.162.113:8086/write?db=test" --data-binary 'test-measurements  value=120'

注意是否存在test数据库,没有则新增

查询语法

1
SELECT <field_key>[,<field_key>,<tag_key>] FROM <measurement_name>[,<measurement_name>]

官方文档:https://docs.influxdata.com/influxdb/v1.0/query_language/data_exploration/?spm=5176.100239.blogcont61915.11.R6KaBi

结合Grafana

官网介绍:

安装下载Grafana

下载链接:https://github.com/grafana/grafana/releases
或者直接使用rpm安装:

1
2
3
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.4.1-1.x86_64.rpm
yum install initscripts fontconfig
yum localinstall grafana-4.4.1-1.x86_64.rpm\

1
2
3
4
5
systemctl daemon-reload
systemctl start grafana-server
systemctl status grafana-server
netstat -ntlp|grep 3000
lsof -i:3000

简单配置Grafana

官方文档:http://docs.grafana.org/features/datasources/influxdb/

安装后登陆地址为:http://IP:3000/login,默认账号和密码都是admin


Read More

2018-07-29
企业常用几种部署方式(蓝绿部署、滚动部署、金丝雀部署)

部署是将服务的某个版本投入生产环境的过程。部署的总体目标是:对系统用户产生最小影响的情况下,把服务的升级版本投放到生产环境中。

服务变更的主要原因:

  • 1、修复错误
  • 2、提高服务的质量
  • 3、增加新的功能

服务变更需要注意哪些(或者说如何产生最小影响)?

SRE 经验告诉我们,大概 70% 的生产事故由某种部署的变更而触发。变更管理的最佳实践可使用自动化来完成以下几点:

  • 1、部署尽可能在用户少的时候。
  • 2、每次部署前备份原始数据。
  • 3、采用渐进式发布机制。
  • 4、迅速而准确地检测到问题的发生。
  • 5、当出现问题时,安全迅速地回退改动。

常用部署方式存在以下几种:

  • 蓝绿部署
  • 滚动部署
  • 灰度部署/金丝雀部署

蓝绿部署

定义:

蓝绿部署(部分称大翻转或红/黑部署),首先准备N台提供版本A服务的机器,同时准备N台提供版本B的机器。一旦版本B的N台机器配置好并准备好服务请求,通过切换路由到版本B,例子如下所示:

1、绿色表示当前的生产应用程序V1

2、现在,当您准备对app2进行更改并将其升级到v2时,您将在”蓝色环境”中执行此操作。在该环境中,您可以部署新版本的应用程序,运行冒烟测试以及任何其他测试(包括操作系统,缓存,CPU等)。当结果看起来不错时,您可以将负载均衡器/反向代理/路由器更改为指向蓝色环境:

方法:
  • 可以通过切换域名服务器(DNS)
  • 更改负载均衡器路由切换
优点:
  • 更新过程无需停机,风险较少
  • 回滚方便,只需要更改路由或者切换DNS服务器,效率较高
缺点:
  • 需要部署两套机器,费用开销大
  • 在非隔离的机器(Docker、VM)上操作时,可能会导致蓝绿环境被摧毁风险
  • 负载均衡器/反向代理/路由/DNS处理不当,将导致流量没有切换过来情况出现

滚动发布:

定义:

现生产N台机器都为版本A的机器,部署取出一个或者多个服务器停止服务,执行更新版本B,更新后重新将其投入使用,继续不断更新其他机器,直到集群中所有的实例都更新成版本B。

流程:
  • 1、负载均衡或者路由移除一台或者多台实例(正常监控也需要移除)
  • 2、移除后的实例开始更新
  • 3、上线测试后无异常开始接入负载均衡器或者路由
  • 4、新增实例监控
  • 5、继续上线后一批实例,直到集群中所有的实例都更新
优点:
  • 更新过程体验影响少,风险较少
  • 费用对比蓝绿花费开销较少,无需额外新增机器
缺点:
  • 上线/回滚完成时间相对较慢
  • 需要监控和负载均衡器的移除和接入能力

灰度发布

定义

灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。(类似地下采煤使用的金丝雀方法,当金丝雀遇到有毒气体时候,产生痛苦信号)A/B testing和金丝雀的差别在于A/B 测试对于特定业务的关键绩效指标来说,确定那个版本执行得更好。

方法
  • 首先部署少量服务器密切
  • 观察是否因为版本产生预期结果
  • 当结果满意时候再全量部署

优缺点可以参考上面

关于CAP定理

  • 一致性(Consistence) (等同于所有节点访问同一份最新的数据副本)
  • 可用性(Availability)(每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据)
  • 分区容错性(Partition tolerance)(以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择[3]。)

我们这里只讨论一致性问题,其他你们可以自行思考下:

  • 建议无状态(session等信息存放在缓存数据库)
  • 如果新版本的操作涉及到数据库更改,建议保持只新增不删减字段操作

要是存在多版本同时存在,注意考虑以下:

  • 1、功能开关
  • 2、前后版本兼容

参考资料包括以下:

Read More

2018-07-29
Pyecharts 生成图表(附Django支持)

pyecharts 是一个用于生成 Echarts 图表的类库。Echarts 是百度开源的一个数据可视化 JS 库。用 Echarts 生成的图可视化效果非常棒,pyecharts 是为了与 Python 进行对接,方便在 Python 中直接使用数据生成图。

安装

1
pip install pyecharts

简单实例

1
2
3
4
5
from pyecharts import Bar
bar = Bar("我的第一个图表", "这里是副标题")
bar.add("服装", ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"], [5, 20, 36, 10, 75, 90])
# bar.print_echarts_options() # 该行只为了打印配置项,方便调试时使用
bar.render() # 生成本地 HTML 文件

默认当前目录下生成render.html,使用浏览器打开如下:

图形绘制过程

基本上所有的图表类型都是这样绘制的:

  • chart_name = Type() 初始化具体类型图表。
  • add() 添加数据及配置项。
  • render() 生成本地文件(html/svg/jpeg/png/pdf/gif)。

add() 数据一般为两个列表(长度一致)。如果你的数据是字典或者是带元组的字典。可利用 cast() 方法转换。

1
2
3
4

@staticmethod
cast(seq)
转换数据序列,将带字典和元组类型的序列转换为 k_lst,v_lst 两个列表

  • 元组列表
    [(A1, B1), (A2, B2), (A3, B3), (A4, B4)] –> k_lst[ A[i1, i2…] ], v_lst[ B[i1, i2…] ]
  • 字典列表
    [{A1: B1}, {A2: B2}, {A3: B3}, {A4: B4}] –> k_lst[ A[i1, i2…] ], v_lst[ B[i1, i2…] ]
  • 字典
    {A1: B1, A2: B2, A3: B3, A4: B4} – > k_lst[ A[i1, i2…] ], v_lst[ B[i1, i2…] ]

结合Django显示以上图形

基础环境

  • Django_version: 2.1a1
  • Python_verison: 3.6.5

url.py

1
2
3
4
5
6
7
8
from django.contrib import admin
from django.urls import path,re_path
from django.views.generic import TemplateView #通用视图
from apps.ops import views

urlpatterns = [
re_path(r'^test/$', views.test),
]

app.ops.views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from django.shortcuts import render
from django.http import HttpResponse
from django.template import loader
from pyecharts import Bar
import math
# Create your views here.

REMOTE_HOST = "https://pyecharts.github.io/assets/js"



def test(request):
template = loader.get_template('test/pyecharts.html')
b = bar()
context = dict(
myechart=b.render_embed(),
host=REMOTE_HOST,
script_list=b.get_js_dependencies()
)
return HttpResponse(template.render(context, request))


def bar():
bar = Bar("我的第一个图表", "这里是副标题")
bar.add("服装", ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"], [5, 20, 36, 10, 75, 90])
# bar.print_echarts_options() # 该行只为了打印配置项,方便调试时使用
# bar.render() # 生成本地 HTML 文件
return bar
  • script_list 是 Page() 类渲染网页所需要依赖的 echarts js 库,依赖的库的数量取决于所要渲染的图形种类。

  • host 是 echarts js 库的地址,默认提供的地址为 https://pyecharts.github.io/assets/js 当然,如果你愿意你也可以改变这个地址,先克隆 https://github.com/pyecharts/assets 然后将 js 文件夹挂载在你自己的服务器上即可。

templates/test/pyecharts.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- myfirstvis/templates/pyecharts.html -->
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title>Proudly presented by PycCharts</title>
{% for jsfile_name in script_list %}
<script src="{{ host }}/{{ jsfile_name }}.js"></script>
{% endfor %}
</head>

<body>
{{ myechart|safe }}
</body>

</html>

启动django访问/test/即可

Read More

2018-07-29
Python调用Zabbix Api获取数据

请求的方法参数

官方原文:

  • jsonrpc - the version of the JSON-RPC protocol used by the API; the Zabbix API implements JSON-RPC version 2.0;
  • method - the API method being called;
  • params - parameters that will be passed to the API method;
  • id - an arbitrary identifier of the request;
  • auth - a user authentication token; since we don’t have one yet, it’s set to null.
参数名称 值类型 说明 是否必须
jsonrpc str 接口版本
method str 请求方法
params json 请求方法参数
user str zabbix账号
password str zabbix密码
auth str 认证的key
id int 认证id

请求事例:

1
2
3
4
POST http://company.com/zabbix/api_jsonrpc.php HTTP/1.1
Content-Type: application/json-rpc

{"jsonrpc":"2.0","method":"apiinfo.version","id":1,"auth":null,"params":{}}

python获取token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import json
import requests
import sys, argparse


class Zabbix_Api:
def __init__(self):
self.url = 'http://IP/zabbix/api_jsonrpc.php'
self.header = {"Content-Type": "application/json"}
self.id = 1
self.user="zabbix登录账号"
self.password="zabbix登录密码"

def json_obj(self,method,auth=True,params={}):
obj = {'jsonrpc': '2.0',
'method': method,
'params': params,
'auth': auth,
'id': self.id}
if not auth:
del obj["auth"]
return obj

def user_login(self):
data=self.json_obj(method="user.login",auth=False, params={"user": self.user, "password": self.password})
return json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)["result"]

if __name__ == '__main__':
print(Zabbix_Api().user_login())

获取版本信息

1
2
3
4
5
6
7
def get_version(self):
data=self.json_obj(method="apiinfo.version",
params={
"output": []
},
auth=False)
return json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)

host.get方法获取所有的主机ID

参数名称 值类型 说明 是否必须
jsonrpc str 接口版本
method str 请求方法
params json 请求方法参数
output array 输出格式
groupids str/array 主机组id
auth str 认证的key
id int 认证id

请求事例(参考上面类):

1
2
3
4
5
6
7
8
def get_host(self):
data=self.json_obj(method="host.get",
params={
"output": ["hostid", "name"]
},
auth=self.user_login())

return json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)

itemsid.get方法获取单个主机下所有的监控项ID

参数名称 值类型 说明 是否必须
jsonrpc str 接口版本
method str 请求方法
params json 请求方法参数
output array 输出格式
groupids str/array 主机组id
auth str 认证的key
id int 认证id

请求事例:

1
2
3
4
5
6
7
8
9
10
11
def get_mem_total(self,hostid):
data=self.json_obj(method="item.get",
params={
"output": "extend",
"hostids": hostid,
"search": {
"key_": "vm.memory.size[total]"
}
},
auth=self.user_login())
return int(json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)["result"][0]["lastvalue"])/1024/1024/1024

以上完整事例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

import json
import requests
import sys, argparse


class Zabbix_Api:
def __init__(self):
self.url = 'http://192.168.31.249/zabbix/api_jsonrpc.php'
self.header = {"Content-Type": "application/json"}
self.id = 1

def json_obj(self,method,auth=True,params={}):
obj = {'jsonrpc': '2.0',
'method': method,
'params': params,
'auth': auth,
'id': self.id}
if not auth:
del obj["auth"]
return obj

def user_login(self):
data=self.json_obj(method="user.login",auth=False, params={"user": "yunwei", "password": "yunwei"})
return json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)["result"]

def get_host(self):
data=self.json_obj(method="host.get",
params={
"output": ["hostid", "name"]
},
auth=self.user_login())

return json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)

def get_mem_total(self,hostid):
data=self.json_obj(method="item.get",
params={
"output": "extend",
"hostids": hostid,
"search": {
"key_": "vm.memory.size[total]"
}
},
auth=self.user_login())
return int(json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)["result"][0]["lastvalue"])/1024/1024/1024


if __name__ == '__main__':
# for k in Zabbix_Api().get_host()["result"]:
# print(k["hostid"],k["name"],str(round(Zabbix_Api().get_mem(k["hostid"]),2))+"G")
print(Zabbix_Api().get_mem_total(10084))

以上只是简单举例,具体实践可参考官网文档(要永远相信官方文档是最好的文档):https://www.zabbix.com/documentation/3.0/manual/api

Read More

2018-07-29
Django中的缓存配置

按照《大型网站技术架构》中所说,网站性能优化的第一定律:优先考虑缓存优化性能.今天简单探讨下django的缓存实现。

缓存一些东西是为了保存那些需要很多计算资源的结果,这样的话就不必在下次重复消耗计算资源。 下面是一些伪代码,用来解释缓存怎样在动态生成的网页中工作的:

1
2
3
4
5
6
7
iven a URL, try finding that page in the cache
if the page is in the cache:
return the cached page
else:
generate the page
save the generated page in the cache (for next time)
return the generated page

Django中的缓存类型

  • 1、内存缓存
  • 2、数据库缓存
  • 3、文件缓存

Django如何设置缓存

缓存系统需要一些设置才能使用。 也就是说,你必须告诉他你要把数据缓存在哪里- 是数据库中,文件系统或者直接在内存中。 这个决定很重要,因为它会影响你的缓存性能,缓存配置是通过setting 文件的CACHES 配置来实现的。

缓存参数
  • TIMEOUT: 用于缓存的默认超时(以秒为单位)。此参数默认为300秒(5分钟)。

  • OPTIONS: 应传递给缓存后端的任何选项。(包括以下)

  • MAX_ENTRIES:删除旧值之前缓存中允许的最大条目数。此参数默认为300。
  • CULL_FREQUENCY:MAX_ENTRIES达到时剔除的条目部分。实际比率是 ,设置为在达到时剔除一半数目。
  • EY_PREFIX:一个字符串,将自动包含(默认情况下预先添加)到Django服务器使用的所有缓存键。
  • VERSION:Django服务器生成的缓存键的默认版本号。
  • KEY_FUNCTION 包含函数的虚线路径的字符串,用于定义如何将前缀,版本和密钥组合为最终缓存键。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ###例子
    CACHES = {
    'default': {
    'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
    'LOCATION': '/var/tmp/django_cache',
    'TIMEOUT': 60,
    'OPTIONS': {
    'MAX_ENTRIES': 1000
    }
    }
    }
内存缓存(Memcached)

Memcached是Django本身支持的最快,最有效的缓存类型,它 是一个完全基于内存的缓存服务器,最初是为了处理LiveJournal.com的高负载而开发的,后来由Danga Interactive开源。Facebook和Wikipedia等网站使用它来减少数据库访问并显着提高网站性能。

Memcached作为守护进程运行,并分配了指定数量的RAM。它所做的就是提供一个快速接口,用于在缓存中添加,检索和删除数据。所有数据都直接存储在内存中,因此不会产生数据库或文件系统使用的开销。

安装Memcached本身后,您需要安装Memcached绑定。有几个Python Memcached绑定可用; 最常见的两个是python-memcached和pylibmc。

在Django中使用Memcached:

  • 设置BACKEND为 django.core.cache.backends.memcached.MemcachedCache或 django.core.cache.backends.memcached.PyLibMCCache(取决于您选择的memcached绑定)
  • 设置LOCATION为ip:port值,其中ip是Memcached守护程序的IP地址,port是运行Memcached的端口,或者是unix:path值,其中 path是Memcached Unix套接字文件的路径。

Memcached使用python-memcached绑定在localhost(127.0.0.1)端口11211上运行:

1
2
3
4
5
6
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}

Memcached可通过/tmp/memcached.sock使用python-memcached绑定的本地Unix套接字文件:

1
2
3
4
5
6
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': 'unix:/tmp/memcached.sock',
}
}

用pylibmc绑定时,请不要包含unix:/前缀:

1
2
3
4
5
6
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': '/tmp/memcached.sock',
}
}

数据库缓存

Django可以将其缓存的数据存储在您的数据库中。如果你有一个快速,索引良好的数据库服务器,这种方法效果最好。

要将数据库表用作缓存后端:

  • 设置BACKEND为 django.core.cache.backends.db.DatabaseCache
  • 设置LOCATION为tablename,数据库表的名称,注意这个表应该是还没有新增的。

缓存表的名称为my_cache_table:

1
2
3
4
5
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
}

创建缓存表,在使用数据库缓存之前,必须使用以下命令创建缓存表:

1
python manage.py createcachetable
多个数据库

如果对多个数据库使用数据库缓存,则还需要为数据库缓存表设置路由指令。出于路由的目的,数据库缓存表CacheEntry在名为的应用程序中显示为名为的模型 django_cache。此模型不会出现在模型缓存中,但模型详细信息可用于路由目的。

例如,以下路由器将指向所有高速缓存读取操作cache_replica,并将所有写入操作指向 cache_primary。缓存表将仅同步到 cache_primary:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class CacheRouter:
"""A router to control all database cache operations"""

def db_for_read(self, model, **hints):
"All cache read operations go to the replica"
if model._meta.app_label == 'django_cache':
return 'cache_replica'
return None

def db_for_write(self, model, **hints):
"All cache write operations go to primary"
if model._meta.app_label == 'django_cache':
return 'cache_primary'
return None

def allow_migrate(self, db, app_label, model_name=None, **hints):
"Only install the cache model on primary"
if app_label == 'django_cache':
return db == 'cache_primary'
return None

如果未指定数据库缓存模型的路由方向,则缓存后端将使用该default数据库。当然,如果不使用数据库缓存后端,则无需担心为数据库缓存模型提供路由指令。

文件系统缓存

基于文件的后端将每个缓存值序列化并存储为单独的文件。要使用此后端设置BACKEND来 “django.core.cache.backends.filebased.FileBasedCache”和 LOCATION到合适的目录。例如,要存储缓存数据/var/tmp/django_cache,请使用以下设置:

1
2
3
4
5
6
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',
}
}

如果您使用的是Windows,请将驱动器号放在路径的开头,如下所示:

1
2
3
4
5
6
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': 'c:/foo/bar',
}
}

缓存整个站点

可以在settings配置文件添加如下:

1
2
3
4
5
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]

views视图缓存

1
2
3
4
5
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
...

注意:cache_page:缓存时间以秒为单位

访问缓存

1
2
3
4
5
>>> from django.core.cache import caches
>>> cache1 = caches['myalias']
>>> cache2 = caches['myalias']
>>> cache1 is cache2
True

基本用法

语法:

  • 新增缓存:

    1
    2
    set(key, value, timeout)
    get(key) --- 当存在key不会再新增
  • 获取缓存:

    1
    2
    get(key)---获取单个值
    get_many(['key1', 'key2', 'key3'])---获取多个值
  • 删除缓存

    1
    cache.delete('a')
  • 清空缓存

    1
    cache.clear()

还可以分别使用incr()或decr()方法增加或减少已存在的密钥 。默认情况下,现有缓存值将递增或递减1.可以通过为递增/递减调用提供参数来指定其他递增/递减值。如果您尝试递增或递减不存在的缓存键,将引发ValueError:

1
2
3
4
5
6
7
8
9
>>> cache.set('num', 1)
>>> cache.incr('num')
2
>>> cache.incr('num', 10)
12
>>> cache.decr('num')
11
>>> cache.decr('num', 5)
6

要是还有疑问,具体可参考:https://docs.djangoproject.com/zh-hans/2.0/topics/cache/

Read More

2018-06-16
Celery - 分布式任务队列

Celery 是一个简单、灵活且可靠的,处理大量消息的分布式系统,并且提供维护这样一个系统的必需工具。

它是一个专注于实时处理的任务队列,同时也支持任务调度。

Celery 有广泛、多样的用户与贡献者社区,你可以通过 IRC 或是 邮件列表 加入我们。

Celery 是开源的,使用 BSD 许可证 授权。
中文官网文档地址:http://docs.jinkan.org/docs/celery/

举例

创建task.py

1
2
3
4
5
6
7
8
from celery import Celery

app =Celery('tasks',broker='redis://127.0.0.1:6379/0',backend='redis://127.0.0.1:6379/0')


@app.task
def add(x, y):
return x + y

启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@localhost python]# celery -A task worker --loglevel=info
/usr/lib/python2.7/site-packages/celery/platforms.py:796: RuntimeWarning: You're running the worker with superuser privileges: this is
absolutely not recommended!

Please specify a different user using the -u option.

User information: uid=0 euid=0 gid=0 egid=0

uid=uid, euid=euid, gid=gid, egid=egid,

-------------- celery@localhost.localdomain v4.1.1 (latentcall)
---- **** -----
--- * *** * -- Linux-3.10.0-693.el7.x86_64-x86_64-with-centos-7.4.1708-Core 2018-06-06 05:25:17
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: tasks:0x7f5c3038c150
- ** ---------- .> transport: redis://127.0.0.1:6379/0
- ** ---------- .> results: redis://127.0.0.1:6379/0
- *** --- * --- .> concurrency: 1 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery


[tasks]
. task.add

[2018-06-06 05:25:17,206: INFO/MainProcess] Connected to redis://127.0.0.1:6379/0
[2018-06-06 05:25:17,218: INFO/MainProcess] mingle: searching for neighbors
[2018-06-06 05:25:18,241: INFO/MainProcess] mingle: all alone
[2018-06-06 05:25:18,281: INFO/MainProcess] celery@localhost.localdomain ready.
[2018-06-06 05:26:01,848: INFO/MainProcess] Received task: task.add[69c90eef-309c-45e7-a888-1357129aae6d]

注意安装redis模块:pip install redis

执行调度

导入模块并执行调度

1
2
3
In [1]: from task import add  #注意模块路径
In [2]: add.delay(2,3)
Out[2]: <AsyncResult: 4e4ab6aa-266e-4178-b668-39ada6382113>

查看Celery终端输出

1
[2018-06-06 05:32:20,583: INFO/ForkPoolWorker-1] Task task.add[4e4ab6aa-266e-4178-b668-39ada6382113] succeeded in 0.00385068000105s: 5

查看结果

1
2
3
4
In [3]: result = add.delay(2,3)

In [4]: print(result.get())
5

查看模块的属性列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
In [5]: dir(result)

Out[5]:
['TimeoutError',
'__class__',
'__copy__',
'__delattr__',
'__dict__',
'__doc__',
'__eq__',
'__format__',
'__getattribute__',
'__hash__',
'__init__',
'__module__',
'__ne__',
'__new__',
u'__reduce__',
'__reduce_args__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__unicode__',
'__unicode_repr__',
'__weakref__',
'_cache',
'_get_task_meta',
'_iter_meta',
'_maybe_reraise_parent_error',
'_maybe_set_cache',
'_on_fulfilled',
'_parents',
'_set_cache',
'_to_remote_traceback',
'app',
'as_tuple',
'backend',
'build_graph',
'children',
'collect',
'failed',
'forget',
'get',
'get_leaf',
'graph',
'id',
'info',
'iterdeps',
'maybe_reraise',
'maybe_throw',
'on_ready',
'parent',
'ready',
'result',
'revoke',
'state',
'status',
'successful',
'supports_native_join',
'task_id',
'then',
'throw',
'traceback',
'wait']

获取id

1
2
In [6]: print(result.id)
30f7fce2-2687-448c-816f-11334efc6442

登录reids查看

1
2
3
4
5
6
7
8
9
[root@localhost python]# redis-cli
127.0.0.1:6379> KEYS *
1) "_kombu.binding.celery.pidbox"
2) "celery-task-meta-69c90eef-309c-45e7-a888-1357129aae6d"
3) "_kombu.binding.celery"
4) "_kombu.binding.celeryev"
5) "celery-task-meta-4e4ab6aa-266e-4178-b668-39ada6382113"
127.0.0.1:6379> get celery-task-meta-4e4ab6aa-266e-4178-b668-39ada6382113
"{\"status\": \"SUCCESS\", \"traceback\": null, \"result\": 5, \"task_id\": \"4e4ab6aa-266e-4178-b668-39ada6382113\", \"children\": []}"

Read More

2018-06-16
Python Zabbix Api

请求的方法参数

官方原文:

  • jsonrpc - the version of the JSON-RPC protocol used by the API; the Zabbix API implements JSON-RPC version 2.0;
  • method - the API method being called;
  • params - parameters that will be passed to the API method;
  • id - an arbitrary identifier of the request;
  • auth - a user authentication token; since we don’t have one yet, it’s set to null.
参数名称 值类型 说明 是否必须
jsonrpc str 接口版本
method str 请求方法
params json 请求方法参数
user str zabbix账号
password str zabbix密码
auth str 认证的key
id int 认证id

请求事例:

1
2
3
4
POST http://company.com/zabbix/api_jsonrpc.php HTTP/1.1
Content-Type: application/json-rpc

{"jsonrpc":"2.0","method":"apiinfo.version","id":1,"auth":null,"params":{}}

python获取token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import json
import requests
import sys, argparse


class Zabbix_Api:
def __init__(self):
self.url = 'http://IP/zabbix/api_jsonrpc.php'
self.header = {"Content-Type": "application/json"}
self.id = 1
self.user="zabbix登录账号"
self.password="zabbix登录密码"

def json_obj(self,method,auth=True,params={}):
obj = {'jsonrpc': '2.0',
'method': method,
'params': params,
'auth': auth,
'id': self.id}
if not auth:
del obj["auth"]
return obj

def user_login(self):
data=self.json_obj(method="user.login",auth=False, params={"user": self.user, "password": self.password})
return json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)["result"]

if __name__ == '__main__':
print(Zabbix_Api().user_login())

获取版本信息

1
2
3
4
5
6
7
def get_version(self):
data=self.json_obj(method="apiinfo.version",
params={
"output": []
},
auth=False)
return json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)

host.get方法获取所有的主机ID

参数名称 值类型 说明 是否必须
jsonrpc str 接口版本
method str 请求方法
params json 请求方法参数
output array 输出格式
groupids str/array 主机组id
auth str 认证的key
id int 认证id

请求事例(参考上面类):

1
2
3
4
5
6
7
8
def get_host(self):
data=self.json_obj(method="host.get",
params={
"output": ["hostid", "name"]
},
auth=self.user_login())

return json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)

itemsid.get方法获取单个主机下所有的监控项ID

参数名称 值类型 说明 是否必须
jsonrpc str 接口版本
method str 请求方法
params json 请求方法参数
output array 输出格式
groupids str/array 主机组id
auth str 认证的key
id int 认证id

请求事例:

1
2
3
4
5
6
7
8
9
10
11
def get_mem_total(self,hostid):
data=self.json_obj(method="item.get",
params={
"output": "extend",
"hostids": hostid,
"search": {
"key_": "vm.memory.size[total]"
}
},
auth=self.user_login())
return int(json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)["result"][0]["lastvalue"])/1024/1024/1024

以上完整事例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

import json
import requests
import sys, argparse


class Zabbix_Api:
def __init__(self):
self.url = 'http://192.168.31.249/zabbix/api_jsonrpc.php'
self.header = {"Content-Type": "application/json"}
self.id = 1

def json_obj(self,method,auth=True,params={}):
obj = {'jsonrpc': '2.0',
'method': method,
'params': params,
'auth': auth,
'id': self.id}
if not auth:
del obj["auth"]
return obj

def user_login(self):
data=self.json_obj(method="user.login",auth=False, params={"user": "yunwei", "password": "yunwei"})
return json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)["result"]

def get_host(self):
data=self.json_obj(method="host.get",
params={
"output": ["hostid", "name"]
},
auth=self.user_login())

return json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)

def get_mem_total(self,hostid):
data=self.json_obj(method="item.get",
params={
"output": "extend",
"hostids": hostid,
"search": {
"key_": "vm.memory.size[total]"
}
},
auth=self.user_login())
return int(json.loads(requests.post(url=self.url, headers=self.header, data=json.dumps(data)).text)["result"][0]["lastvalue"])/1024/1024/1024


if __name__ == '__main__':
# for k in Zabbix_Api().get_host()["result"]:
# print(k["hostid"],k["name"],str(round(Zabbix_Api().get_mem(k["hostid"]),2))+"G")
print(Zabbix_Api().get_mem_total(10084))

以上只是简单举例,具体实践可参考官网文档(要永远相信官方文档是最好的文档):https://www.zabbix.com/documentation/3.0/manual/api

Read More

2018-06-16
Python AES 加解密

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。

如需进一步了解可以参考:https://zh.wikipedia.org/wiki/%E9%AB%98%E7%BA%A7%E5%8A%A0%E5%AF%86%E6%A0%87%E5%87%86#%E5%AF%86%E7%A0%81%E8%AF%B4%E6%98%8E

AES5种常见加密模式:

1. 电码本模式(Electronic Codebook Book (ECB))

2. 密码分组链接模式(Cipher Block Chaining (CBC))

3. 计算器模式(Counter (CTR))

4. 密码反馈模式(Cipher FeedBack (CFB))

5. 输出反馈模式(Output FeedBack (OFB))

pip install pycrypto

官方链接:https://pypi.org/project/pycrypto/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
'''
new(key, *args, **kwargs)
Create a new AES cipher

:Parameters:
key : byte string
The secret key to use in the symmetric cipher.
It must be 16 (*AES-128*), 24 (*AES-192*), or 32 (*AES-256*) bytes long.
:Keywords:
mode : a *MODE_** constant
The chaining mode to use for encryption or decryption.
Default is `MODE_ECB`.
IV : byte string
The initialization vector to use for encryption or decryption.

It is ignored for `MODE_ECB` and `MODE_CTR`.

For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption
and `block_size` +2 bytes for decryption (in the latter case, it is
actually the *encrypted* IV which was prefixed to the ciphertext).
It is mandatory.

For all other modes, it must be `block_size` bytes longs. It is optional and
when not present it will be given a default value of all zeroes.
counter : callable
(*Only* `MODE_CTR`). A stateful function that returns the next
*counter block*, which is a byte string of `block_size` bytes.
For better performance, use `Crypto.Util.Counter`.
segment_size : integer
(*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext
are segmented in.
It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8.

:Return: an `AESCipher` object
'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
import hashlib

class Aes_Crypto(object):
def __init__(self, key):
#self.key = key
##建议使用以下方式
self.key=hashlib.md5(key).hexdigest()

# 这里密钥key 长度必须为16(AES-128)、24(AES-192)、或32(AES-256)Bytes 长度
self.iv=16*'0'
def encrypt(self,data):
# 不足16位补'\0'
data+=int(16-(len(data) % 16)) * '\0'
obj = AES.new(self.key, AES.MODE_CBC, self.iv)
ciphertext =obj.encrypt(data)
# 转化为16进制字符串
return b2a_hex(ciphertext)

def decrypt(self,data):
obj = AES.new(self.key, AES.MODE_CBC, self.iv)
return obj.decrypt(a2b_hex(data)).rstrip('\0')


if __name__ == '__main__':
a=Aes_Crypto(16*'1')
b=a.encrypt('123456782dd000')
print("加密后结果:"+b)
c=a.decrypt(b)
print("解密后结果:"+c)

执行后的结果:

1
2
3
 python test2.py 
加密后结果:a0ac1a2c639fd4bce1e3804be22bbcbb
解密后结果:1234567890123456

Read More

2018-06-16
简单封装Ansible Api 接口

官网链接:http://docs.ansible.com/ansible/latest/dev_guide/developing_api.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#!/usr/bin/env python
'''
引用模块
'''
import json
import shutil
from collections import namedtuple
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.playbook.play import Play
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.plugins.callback import CallbackBase
import ansible.constants as C

class ResultCallback(CallbackBase):
"""A sample callback plugin used for performing an action as results come in

If you want to collect all results into a single object for processing at
the end of the execution, look into utilizing the ``json`` callback plugin
or writing your own custom callback plugin
"""
def v2_runner_on_ok(self, result, **kwargs):
"""Print a json representation of the result

This method could store the result in an instance attribute for retrieval later
"""
host = result._host
print(json.dumps({host.name: result._result}, indent=4))

# since API is constructed for CLI it expects certain options to always be set, named tuple 'fakes' the args parsing options object
Options = namedtuple('Options', ['connection', 'module_path', 'forks', 'become', 'become_method', 'become_user', 'check', 'diff'])
options = Options(connection='local', module_path=['/to/mymodules'], forks=10, become=None, become_method=None, become_user=None, check=False, diff=False)

# initialize needed objects
loader = DataLoader() # Takes care of finding and reading yaml, json and ini files
passwords = dict(vault_pass='secret')

# Instantiate our ResultCallback for handling results as they come in. Ansible expects this to be one of its main display outlets
results_callback = ResultCallback()

# create inventory, use path to host config file as source or hosts in a comma separated string
inventory = InventoryManager(loader=loader, sources='localhost,')

# variable manager takes care of merging all the different sources to give you a unifed view of variables available in each context
variable_manager = VariableManager(loader=loader, inventory=inventory)

# create datastructure that represents our play, including tasks, this is basically what our YAML loader does internally.
play_source = dict(
name = "Ansible Play",
hosts = 'localhost',
gather_facts = 'no',
tasks = [
dict(action=dict(module='shell', args='ls'), register='shell_out'),
dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}')))
]
)

# Create play object, playbook objects use .load instead of init or new methods,
# this will also automatically create the task objects from the info provided in play_source
play = Play().load(play_source, variable_manager=variable_manager, loader=loader)

# Run it - instantiate task queue manager, which takes care of forking and setting up all objects to iterate over host list and tasks
tqm = None
try:
tqm = TaskQueueManager(
inventory=inventory,
variable_manager=variable_manager,
loader=loader,
options=options,
passwords=passwords,
stdout_callback=results_callback, # Use our custom callback instead of the ``default`` callback plugin, which prints to stdout
)
result = tqm.run(play) # most interesting data for a play is actually sent to the callback's methods
finally:
# we always need to cleanup child procs and the structres we use to communicate with them
if tqm is not None:
tqm.cleanup()

# Remove ansible tmpdir
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[root@localhost python]# python ansible_api.py 
{
"localhost": {
"_ansible_parsed": true,
"stderr_lines": [],
"changed": true,
"end": "2018-05-29 03:44:27.292832",
"_ansible_no_log": false,
"stdout": "ansible_api.py\nsaomiao.py",
"cmd": "ls",
"start": "2018-05-29 03:44:27.262984",
"delta": "0:00:00.029848",
"stderr": "",
"rc": 0,
"invocation": {
"module_args": {
"creates": null,
"executable": null,
"_uses_shell": true,
"_raw_params": "ls",
"removes": null,
"warn": true,
"chdir": null,
"stdin": null
}
},
"stdout_lines": [
"ansible_api.py",
"saomiao.py"
]
}
}
{
"localhost": {
"msg": "ansible_api.py\nsaomiao.py",
"changed": false,
"_ansible_verbose_always": true,
"_ansible_no_log": false
}
}

更改后的API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# coding:utf-8

# python 2.7.5
# ansible 2.4.2.0


import os, sys
import json
import shutil
from collections import namedtuple
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.playbook.play import Play
from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.plugins.callback import CallbackBase
import ansible.constants as C


class ResultCallback(CallbackBase):
"""A sample callback plugin used for performing an action as results come in

If you want to collect all results into a single object for processing at
the end of the execution, look into utilizing the ``json`` callback plugin
or writing your own custom callback plugin
"""

def v2_runner_on_ok(self, result, **kwargs):
"""Print a json representation of the result

This method could store the result in an instance attribute for retrieval later
"""
host = result._host
print(json.dumps({host.name: result._result}, indent=4))

def v2_runner_on_failed(self, result, ignore_errors=False):
host = result._host
print(json.dumps({host.name: result._result}, indent=4))

def v2_runner_on_unreachable(self, result):
host = result._host
print(json.dumps({host.name: result._result}, indent=4))


class AnsRunner(object):
def __init__(self):
self.resource = None
self.inventory = None
self.variable_manager = None
self.loader = None
self.options = None
self.passwords = None
self.__initializeData()

def __initializeData(self):

# 初始化需要的对象
Options = namedtuple('Options', ['connection', 'module_path', 'forks', 'timeout', 'remote_user',
'ask_pass', 'private_key_file', 'ssh_common_args', 'ssh_extra_args',
'sftp_extra_args',
'scp_extra_args', 'become', 'become_method', 'become_user', 'ask_value_pass',
'verbosity',
'check', 'listhosts', 'listtasks', 'listtags', 'syntax', 'diff'])

self.options = Options(connection='ssh', module_path=None, forks=100, timeout=10,
remote_user='root', ask_pass=False, private_key_file=None, ssh_common_args=None,
ssh_extra_args=None,
sftp_extra_args=None, scp_extra_args=None, become=None, become_method=None,
become_user='root', ask_value_pass=False, verbosity=None, check=False, listhosts=False,
listtasks=False, listtags=False, syntax=False, diff=False)

self.loader = DataLoader() # Takes care of finding and reading yaml, json and ini files
self.passwords = dict(vault_pass='secret')
self.results_callback = ResultCallback()

# create inventory, use path to host config file as source or hosts in a comma separated string
self.inventory = InventoryManager(loader=self.loader, sources=['/etc/ansible/hosts'])
# variable manager takes care of merging all the different sources to give you a unifed view of variables available in each context
self.variable_manager = VariableManager(loader=self.loader, inventory=self.inventory)

def adc_runner(self, host_list, module_name, module_args=None):
# create datastructure that represents our play, including tasks, this is basically what our YAML loader does internally.
play_source = dict(
name="Ansible Play",
hosts=host_list,
gather_facts='no',
tasks=[
dict(action=dict(module=module_name, args=module_args), register='shell_out'),
dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}')))
]
)

play = Play().load(play_source, variable_manager=self.variable_manager, loader=self.loader)
tqm = None
try:
tqm = TaskQueueManager(
inventory=self.inventory,
variable_manager=self.variable_manager,
loader=self.loader,
options=self.options,
passwords=self.passwords,
stdout_callback=self.results_callback,
# Use our custom callback instead of the ``default`` callback plugin, which prints to stdout
)
result = tqm.run(play) # most interesting data for a play is actually sent to the callback's methods
finally:
# we always need to cleanup child procs and the structres we use to communicate with them
if tqm is not None:
tqm.cleanup()

# Remove ansible tmpdir
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)

def run_playbook(self, playbook_path,host_list=None, extra_vars=None):

if not os.path.exists(playbook_path):
print('The playbook_path does not exist' % playbook_path)
sys.exit()
passwords = None

playbook = PlaybookExecutor(playbooks=playbook_path, inventory=self.inventory,
variable_manager=self.variable_manager,
loader=self.loader, options=self.options, passwords=passwords)

# run playbook
result = playbook.run()


if __name__ == '__main__':
print(AnsRunner().run_playbook(playbook_path='/data/Playbook/install_jdk18.yaml',
extra_vars={"remote_server": "192.168.72.130"}))
# print(AnsRunner().adc_runner(host_list='192.168.72.129',module_name='shell',module_args='ls'))

通过api接口我们可以进一步修改,例如需要写CMDB的小伙伴,具体按需实际修改即可。后面本博也会更新关于ansible的文章。

Read More

2018-06-02
Django之密码验证

Django版本:

1
2
3
4
5

In [14]: import django

In [15]: django.get_version()
Out[15]: '1.11.13'

导入包:

1
2
python manage.py shell
from django.contrib.auth.hashers import make_password, check_password

查看make_password并生成密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
help(make_password)

Help on function make_password in module django.contrib.auth.hashers:
make_password(password, salt=None, hasher=u'default')
Turn a plain-text password into a hash for database storage

Same as encode() but generates a new random salt.
If password is None then a concatenation of
UNUSABLE_PASSWORD_PREFIX and a random string will be returned
which disallows logins. Additional random string reduces chances
of gaining access to staff or superuser accounts.
See ticket #20079 for more info

###生成密码
In [4]: make_password("123456", None, 'pbkdf2_sha256')
Out[4]: u'pbkdf2_sha256$36000$h0yUI3WVw7x5$NOOndKMq9Y+FQ3pu9nALNoJFIqdbfTez0FJ6tgNl5AU='

check_password校验密码:

1
2
3
4
5
6
7
8
9
10
11
12
help(check_password)

Help on function check_password in module django.contrib.auth.hashers:
check_password(password, encoded, setter=None, preferred=u'default')
Returns a boolean of whether the raw password matches the three
part encoded digest.

If setter is specified, it'll be called when you need to
regenerate the password.

In [6]: check_password("123456","pbkdf2_sha256$36000$h0yUI3WVw7x5$NOOndKMq9Y+FQ3pu9nALNoJFIqdbfTez0FJ6tgNl5AU=")
Out[6]: True

附官方源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def check_password(password, encoded, setter=None, preferred='default'):
"""
Returns a boolean of whether the raw password matches the three
part encoded digest.

If setter is specified, it'll be called when you need to
regenerate the password.
"""
if password is None or not is_password_usable(encoded):
return False

preferred = get_hasher(preferred)
hasher = identify_hasher(encoded)

hasher_changed = hasher.algorithm != preferred.algorithm
must_update = hasher_changed or preferred.must_update(encoded)
is_correct = hasher.verify(password, encoded)

# If the hasher didn't change (we don't protect against enumeration if it
# does) and the password should get updated, try to close the timing gap
# between the work factor of the current encoded password and the default
# work factor.
if not is_correct and not hasher_changed and must_update:
hasher.harden_runtime(password, encoded)

if setter and is_correct and must_update:
setter(password)
return is_correct


def make_password(password, salt=None, hasher='default'):
"""
Turn a plain-text password into a hash for database storage

Same as encode() but generates a new random salt.
If password is None then a concatenation of
UNUSABLE_PASSWORD_PREFIX and a random string will be returned
which disallows logins. Additional random string reduces chances
of gaining access to staff or superuser accounts.
See ticket #20079 for more info.
"""
if password is None:
return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
hasher = get_hasher(hasher)

if not salt:
salt = hasher.salt()

return hasher.encode(password, salt)

Read More