将 Json 转换为 Python Object(二)

最近趁着端午假期,终于把 《流畅的 Python》看完了,收获很大,书中某一章节介绍了 addict 库,可以将 Json 转换为 Python Object 。
今天看了看具体的实现方式,比 Stack Overflow 的回答完整,补发一篇博客学习下。

源码代码很少,补充一些关键变量用于了解整体实现流程。

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
134
135
136
137
138
139
140
141
142
143
144
import copy
from pprint import pprint


class Dict(dict):

def __init__(__self, *args, **kwargs):
print 'init kwargs is ', kwargs
object.__setattr__(__self, '__parent', kwargs.pop('__parent', None))
object.__setattr__(__self, '__key', kwargs.pop('__key', None))
for arg in args:
if not arg:
continue
elif isinstance(arg, dict):
for key, val in arg.items():
__self[key] = __self._hook(val)
elif isinstance(arg, tuple) and (not isinstance(arg[0], tuple)):
__self[arg[0]] = __self._hook(arg[1])
else:
for key, val in iter(arg):
__self[key] = __self._hook(val)

for key, val in kwargs.items():
__self[key] = __self._hook(val)

def __setattr__(self, name, value):
if hasattr(Dict, name):
raise AttributeError("'Dict' object attribute "
"'{0}' is read-only".format(name))
else:
self[name] = value

def __setitem__(self, name, value):
print 'setitem name is %s, value is %s ' % (name, value)
super(Dict, self).__setitem__(name, value)
try:
p = object.__getattribute__(self, '__parent')
key = object.__getattribute__(self, '__key')
except AttributeError:
p = None
key = None
if p is not None:
print 'parent is %s' % p
print 'key is %s' % key
p[key] = self
object.__delattr__(self, '__parent')
object.__delattr__(self, '__key')

def __add__(self, other):
if not self.keys():
return other
else:
self_type = type(self).__name__
other_type = type(other).__name__
msg = "unsupported operand type(s) for +: '{}' and '{}'"
raise TypeError(msg.format(self_type, other_type))

@classmethod
def _hook(cls, item):
if isinstance(item, dict):
return cls(item)
elif isinstance(item, (list, tuple)):
return type(item)(cls._hook(elem) for elem in item)
return item

def __getattr__(self, item):
return self.__getitem__(item)

def __getitem__(self, name):
print 'getitem name is ', name
if name not in self:
print "%s not in self" % name
return Dict(__parent=self, __key=name)
return super(Dict, self).__getitem__(name)

def __delattr__(self, name):
del self[name]

def to_dict(self):
base = {}
for key, value in self.items():
if isinstance(value, type(self)):
base[key] = value.to_dict()
elif isinstance(value, (list, tuple)):
base[key] = type(value)(
item.to_dict() if isinstance(item, type(self)) else
item for item in value)
else:
base[key] = value
return base

def copy(self):
return copy.copy(self)

def deepcopy(self):
return copy.deepcopy(self)

def __deepcopy__(self, memo):
other = self.__class__()
memo[id(self)] = other
for key, value in self.items():
other[copy.deepcopy(key, memo)] = copy.deepcopy(value, memo)
return other

def update(self, *args, **kwargs):
other = {}
if args:
if len(args) > 1:
raise TypeError()
other.update(args[0])
other.update(kwargs)
for k, v in other.items():
if ((k not in self) or
(not isinstance(self[k], dict)) or
(not isinstance(v, dict))):
self[k] = v
else:
self[k].update(v)

def __getnewargs__(self):
return tuple(self.items())

def __getstate__(self):
return self

def __setstate__(self, state):
self.update(state)

def setdefault(self, key, default=None):
if key in self:
return self[key]
else:
self[key] = default
return default


json_data = {
"a": "a"
}

test1 = Dict(json_data)
test1.b.c = "c"
test1.b.d.e = "e"
pprint(test1.to_dict())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 $ python a.py
init kwargs is {}
setitem name is a, value is a
getitem name is b
b not in self
init kwargs is {'__key': 'b', '__parent': {'a': 'a'}}
setitem name is c, value is c
parent is {'a': 'a'}
key is b
setitem name is b, value is {'c': 'c'}
getitem name is b
getitem name is d
d not in self
init kwargs is {'__key': 'd', '__parent': {'c': 'c'}}
setitem name is e, value is e
parent is {'c': 'c'}
key is d
setitem name is d, value is {'e': 'e'}
{'a': 'a', 'b': {'c': 'c', 'd': {'e': 'e'}}}

将 Json 转换为 Python Object(一)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ cat example.py 
import json


class JSONObject:

def __init__(self, dict):
vars(self).update(dict)


# this is valid json string
data = '{"channel":{"lastBuild":"2013-11-12", "component":["test1", "test2"]}}'

jsonobject = json.loads(data, object_hook=JSONObject)

print(jsonobject.channel.component[0])
print(jsonobject.channel.lastBuild)

1
2
3
$ python example.py
test1
2013-11-12

奇怪的 Shell 作用域

工作中很多脚本都是用 Shell 和 Python 完成的,在上周环境中遇到了一个 Bug,导致部分功能失败,最后发现是 Shell 作用域的问题。

举例

1
2
3
4
5
6
 $ tree .
.
├── test.sh
└── utils.sh

0 directories, 2 files

1
2
3
4
5
6
7
8
$ cat utils.sh
#!/usr/bin/env bash

function echo_test {
for i in `seq 2 3`;do
echo "utils.sh" $i
done
}

1
2
3
4
5
6
7
8
9
10
11
$ cat test.sh
#!#/usr/bin/env bash

. utils.sh

for i in `seq 1 5`;do
echo "before utils.sh" $i
echo_test
echo "after utils.sh" $i
echo "#################"
done

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
$ bash test.sh
before utils.sh 1
utils.sh 2
utils.sh 3
after utils.sh 3
#################
before utils.sh 2
utils.sh 2
utils.sh 3
after utils.sh 3
#################
before utils.sh 3
utils.sh 2
utils.sh 3
after utils.sh 3
#################
before utils.sh 4
utils.sh 2
utils.sh 3
after utils.sh 3
#################
before utils.sh 5
utils.sh 2
utils.sh 3
after utils.sh 3
#################

解决方式

在 utils.sh 的函数中对所用变量加上 local 用来声明其作用域局限于函数内。

1
2
3
4
5
6
7
8
9
$ cat utils.sh
#!/usr/bin/env bash

function echo_test {
local i
for i in `seq 2 3`;do
echo "utils.sh" $i
done
}


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
$ bash test.sh
before utils.sh 1
utils.sh 2
utils.sh 3
after utils.sh 1
#################
before utils.sh 2
utils.sh 2
utils.sh 3
after utils.sh 2
#################
before utils.sh 3
utils.sh 2
utils.sh 3
after utils.sh 3
#################
before utils.sh 4
utils.sh 2
utils.sh 3
after utils.sh 4
#################
before utils.sh 5
utils.sh 2
utils.sh 3
after utils.sh 5
#################

Hexo Git 管理

以A电脑的Hexo博客源文件迁移到B电脑为例:

一、对A电脑的操作如下:

1.在github新建仓库名为blog

2.上传A电脑本地Hexo博客的源文件夹至github的blog仓库,流程如下:

(1)删除根目录和主题目录下的.git文件夹

(2)修改根目录下的.gitignore文件为:

/.deploy_git

/public

(3)依次执行以下指令,同步源文件至github

1
2
3
4
5
$  git init
$ git add .
$ git commit -m "xxxxx"
$ git remote add origin git@github.com:/blog_bak.git
$ git push -u origin master

二、对B电脑的操作如下:

1.安装Git,并配置github账号下的B电脑的.ssh
2.安装Node.js
3.使用npm指令安装Hexo

$ npm install -g hexo-cli

$ npm install hexo-asset-image –save 用于创建保存图片的文件夹

4.使用Git bash随便选择一个文件夹,执行git clone

$ git clone git@github.com:/blog.git

三、关于日常的改动流程(A,B两台电脑均使用的情况下):

1.建议先检查更新git pull,将本地博客源文件更新至最新版本

$ git pull

2.然后可以新建或修改博客内容,进行预览等操作

1
2
3
$  hexo new "新的博客名称"

$ hexo server

3.新建博客后,先同步Hexo源文件,将修改后的源文件同步至github:

1
2
3
4
5
$  git add .

$ git commit -m "更新描述"

$ git push origin master

4.然后再执行Hexo的生成文件和部署指令

1
2
3
$  hexo generate

$ hexo deploy

CentOS 多网卡多网关配置

背景

日常使用中,开发机通常只配置一块网卡,通常配置为 DHCP 自动获取 IP,这样就可以自动从 DHCP Server 获取 IP,网关,DNS Server 等配置,不用手动配置。

最近在工作中存在一种场景,一台服务器,多块网卡,需要对不同的网卡配置各自网关,查找了一些资料,也踩了一些坑,记录一下过程。

配置过程

  1. 官方配置
    尝试在 /etc/sysconfig/network-scripts/ifcfg-<device> 中增加 GATEWAY 自动使之生效,实际上来看,多张网卡都配置 GATEWAY 字段会产生冲突,只能放弃通过网卡配置文件方式,尝试通过给网卡配置静态路由方式添加。
    在 CentOS 官方网站查找配置静态路由方法,文档中提到通过编写网卡路由配置文件/etc/sysconfig/network-scripts/route-_<interface> ,配置完成后重启网络可以自动生效。
    官方这种方式我尝试了两种文件编写方式,一种为192.168.64.0/20 via <gateway> dev <device>,一种为 ADDRESS0=xxxxx; NETMASK0=xxxxx; GATEWAY0=xxxxx。两种配置方式均没有生效,只能放弃。

  2. 手动配置
    可以通过 route add 命令临时给第二章网卡配置网关,通过 route -n 查看是可以生效。但是因为最终目标是让路由随着网络服务(公司内部未使用 NetworkManager)Network 启动而配置,因此在 /etc/rc.local 中假如 route add 类似命令无法走通。

  3. 上述方法要么无法生效,要么存在无法持久化问题,均无法完成需求。 只能通过查看 网卡服务启动脚本来查找,查看 /etc/init.d/network (systemd 服务控制也是执行该文件),该文件是一个 Shell 脚本,找到路由配置相关:

1
2
3
4
5
6
7
8
9
10
  2     # Add non interface-specific static-routes.
1 if [ -f /etc/sysconfig/static-routes ]; then
141 if [ -x /sbin/route ]; then
1 grep "^any" /etc/sysconfig/static-routes | while read ignore args ; do
2 /sbin/route add -$args
3 done
4 else
5 net_log $"Legacy static-route support not available: /sbin/route not found"
6 fi
7 fi

可以看到,在网络服务 network 启动过程中,会判断 /etc/sysconfig/static-routes 文件是否存在,如果存在则会通过 route 命令对该文件中记录以 any 开头的路由进行添加,添加命令为 /sbin/route add -$args
根据上述脚本,尝试编写 static-routes 文件:

1
2
any net 192.168.64.0/20 gw 192.168.64.1
any net 10.0.10.0/24 gw 10.0.10.222

编写完成后,重启网络服务 systemctl restart network ,执行 route -n 查看是否生效:

[root@scvm9 19:49:55 ~]$route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.64.1    0.0.0.0         UG    0      0        0 eth0
10.0.10.0       0.0.0.0         255.255.255.0   U     0      0        0 eth2
10.0.10.0       10.0.10.222     255.255.255.0   UG    0      0        0 eth2
169.254.0.0     0.0.0.0         255.255.0.0     U     1002   0        0 eth0

看上去已经生效,因为实际环境中没有第二块网卡网关设备,临时指定一个不存在的 IP。
本以为已经搞定,结果在实际验证的时候,发现第二块网卡无法连接其他节点,哪怕其他节点与它二层联通;尝试将网关指定可以正常工作的 IP 节点,可以与其他节点联通,且 traceroute 也没有经过网关而是直接连接。

问题排查

  1. 尝试将网关配置成可以正常工作的节点
    • 各个节点可以正确联通
    • traceroute 到其他节点未经过网关
  2. 尝试将网关配置成不存在节点
    • 各个节点无法正确联通
  3. 尝试将网关掩码配置为网卡掩码的超集
    • 各个节点可以正确联通
    • traceroute 到其他节点未经过网关

总结

红帽系列发行版本配置静态路由可通过 /etc/sysconfig/static-routes 配置生效,但要注意在 static-routes 文件中的路由添加要添加网关节点对应的掩码而不是本地节点网卡掩码,网关掩码为本地网卡掩码的超集。

2017年读书记录

年终总结

计划 32 本,实际读完 23 本。

已读

  • 《软技能*代码之外的生存指南》
  • 《SRE:Google 运维解密》
  • 《Vim 实用技巧》
  • 《谈判力》
  • 《黑客与画家》
  • 《大话存储ⅰ》
  • 《跟老齐学 Python》
  • 《Python编程快速上手 让繁琐工作自动化》
  • 《Think Python》
  • 《奔跑吧 Ansible》
  • 《Python核心编程(第二版)》
  • 《Python 学习手册》
  • 《嫌疑人X的献身》
  • 《图解 HTTP》
  • 《你是那人间的四月天》
  • 《失乐园》
  • 《活出生命的意义》
  • 《Python核心编程(第三版)》
  • 《深度工作》
  • 《深度实践 KVM》
  • 《Shell 脚本学习指南》
  • 《浪潮之巅》
  • 《人间失格》

在读

  • 《Flask Web开发:基于Python的Web应用开发实战》
  • 《哲学家们都干了些什么》
  • 《二手时间》
  • 《Python 算法教程》

想读

  • 《Python Web开发实战》
  • 《problem solving with algorithms and data structure using python 中文版》
  • 《Fluent Python》
  • 《大话数据结构》
  • 《构建高性能WEB站点》
  • 《C和指针》

I/O bursts with QEMU 2.6

I/O bursts with QEMU 2.6

近期在工作中要进行存储系统的 QOS 验证.公司 QOS 的实现方式参考了 QEMU 中的实现方式, 找来相关资料学习一下.

基本配置

首先,我将总结 QEMU 早期版本中已有的基本配置.
磁盘 I/O 有两处可以被限制的方式: 每秒钟字节数(bps) 和 每秒钟操作数( IOPS) . 对于它们中的每一种限制,用户既可以进行全局配置,也可以针对读写进行单独限制. 下面列举了总共 6 个不同的参数.
I/O 限制可以使用 -dirve 参数中的 throttling ,或者使用 QMP 中的 blocks_set_io_throttle 命令. 下面是两种情况下的参数列举:

1
2
3
4
5
6
7
8
9
{
-DRIVE BLOCK_SET_IO_THROTTLE
throttling.iops-total iops
throttling.iops-read iops_rd
throttling.iops-write iops_wr
throttling.bps-total bps
throttling.bps-read bps_rd
throttling.bps-write bps_wr
}

可以同时配置 IOPS 和 bps ,针对不同的场景我们可以选择是否配置读写限制,但是如果设置了 iops-total 参数, 那么不能配置 iops-read 和 iops-write. 这个规则同样适用于 bps-total 和 bps-read/write.
这些参数的默认值都是 0 , 表示没有任何限制. -drive file=hd0.qcow2,throttling.iops-total=100
最基本的用法,用户可以为 QEMU 添加一个驱动用于限制 100 IOPS : -drive filehd0.qcow2,throttling.iops-total=100

我们可以使用 QMP 达到相同的配置. 在这种情况下,所有的参数都是必须配置的, 所有我们必须给无关参数配置为 0 ,参考配置:

1
2
3
4
5
6
7
8
9
10
11
{ "execute": "block_set_io_throttle",
"arguments": {
"device": "virtio0",
"iops": 100,
"iops_rd": 0,
"iops_wr": 0,
"bps": 0,
"bps_rd": 0,
"bps_wr": 0
}
}

突发I/O

在刚刚的基本配置中, 我们已经能够预防虚拟机抢占过多 I/O 资源, 偶尔允许用户超出参数限制范围可能会更有用. 通过这种方式, 我们能够拥有一个响应更快的虚拟机,用于应对更多的波峰情况,同时保持其他时间的平均限制.
从 QEMU 2.6 开始, 可以允许用户在可配置的时间区间内进行突发 I/O. 突发 I/O 可以超过基本配置, 有两个参数可以限制他们: 突发 I/O 长度和最大允许 I/O 数量. 这两个参数可以作用于上述基本配置中的每一个参数, 这里使用 iops-total 进行举例.
突发 I/O 限制使用 iops-total-max 进行配置, 最大长度(单位: s) 通过 iops-total-max-length 进行配置. 如果我们想要配置一个驱动器基本配置 IOPS 为 100, 同时允许在 60s 时间区间内发生突发 I/O IOPS 为2000. 可以使用如下配置:

1
2
3
4
5
6
{
-drive file=hd0.qcow2,
throttling.iops-total=100,
throttling.iops-total-max=2000,
throttling.iops-total-max-length=60
}

或者通过 QMP 进行配置:

1
2
3
4
5
6
7
8
9
10
11
12
{"execute":"block_set_io_throttle",
"arguments":{
"device":"virtio0",
"iops":100,
"iops_rd":0,
"iops_wr":0,
"bps":0
"bps_rd":0
"bps_wr":0
"iops_max":2000,
"iops_max_length":60,
}}

通过这种方式, 用户可以控制 hd0.qcow2 以 2000 IOPS 持续 1分钟运行, 然后被限制在基本配置的 100 IOPS 状态.
如果有有足够的时间没有使用 I/O, 用户可以再次进行突发 I/O.
iops-total-max 默认值为 0 ,表示不允许突发 I/O. iops-total-max-length 只能在配置 iops-total-max的前提下进行配置, 默认值为 1秒.

控制 I/O 块大小

当配置 IOPS 限制时, 所有的 I/O 操作都会被平等对待,不论 I/O 块大小. 这意味着用户可以利用这点来规避 I/O 限制,将一些小 I/O 替换为一个大块 I/O.
QEMU 提供了 throttling.iops-size 配置用来防止这种情况发生. 这个设置指定请求的每个 I/O 大小.过大的请求将会进行相应的统计.
比如, 如果 iops-size 设置为 4096, 那么 8KB 的 I/O 请求将会被记为 2 ,6KB 请求记为1.5. 该配置只作用于大于 iops-size 配置的 I/O 请求, 如果 I/O 请求小于该数值,则不论大小均记为 1.
iops-size默认值为 0 ,表示这个默认的 IOPS 限制不考虑每次 I/O 请求的块大小.

I/O 限制在磁盘组的应用

在刚刚的例子中我们已经看到如何去限制 I/O 在各个驱动器上, 但是 QEMU 同时允许将驱动器进行分组, 这样驱动器组具有相同的 I/O 限制.
这个功能在 QEMU 2.4 版本已经生效, 更多信息可以参考其他文档.

Leaky Bucket 算法

QEMU 中的 I/O 限制使用 Leaky Bucket 算法实现.

该算法使用不断漏水的桶进行类比. 入水口比作在进行的 I/O, 当桶处于满状态时, 不再允许 I/O 下发.
想要查看 QEMU 中的 I/O 限制对应方式,可以参考如下数值:

iops-total=100
iops-total-max=2000
iops-total-max-length=60

- 水以 100 IOPS 的速度从桶中漏出
- 水可以以 2000 IOPS 的速度添加到桶中
- 桶的大小为 2000 * 60 = 120000
- 如果 iops-total-max 没有配置, 那么桶的大小是 100

最初, 桶状态为空, 可以向桶中添加水直到 2000 IOPS 速率满为止. 当桶满了后, 我们只能添加更可能多的水,因为桶在加水的同时也在漏水. 此时 I/O 速率下降至 100 IOPS. 如果我们添加的水少于泄露的水,那么我们可以再次进行突发 I/O.
注意, 在突发 I/O 期间,水从桶中泄露, 也需要超过 60s 的时间以 2000 IOPS 的速率填满. 当 60s 过后, 桶将泄露 100*60=6000 的水, 能够允许突发 I/O 再运行 3s.
而且, 由于算法的工作方式, 长时间的突发 I/O 也可以通过较低的 I/O 速率完成,比如 120s 内以 1000 IOPS 完成.

2016年读书记录

年终总结

初步养成读书习惯。计划读 5 本,实际读 13 本。

已读

  • 《从零到一》
  • 《岛上书店》
  • 《大型网站技术架构 -核心原理与案例分析》
  • 《OpenStack 实战指南》
  • 《腾云-云计算和大数据时代网络技术揭秘》
  • 《银河系搭车客指南》
  • 《最好的我们》
  • 《云计算架构-技术与实践》
  • 《Pro-Git》
  • 《云计算与OpenStack(虚拟机Nova篇)》
  • 《Ceph cookbook 中文版》
  • 《Google 工作整理术》
  • 《信息存储与管理》

想读

  • 《构建高性能WEB站点》
  • 《Linux Shell脚本攻略》