0%

Ceph号称统一存储,何为统一,就是将块、文件、对象统一到一起。RadosGW(简称RGW)就是Ceph中提供对象存储服务的模块。它能提供S3和Swift两种对象存储接口,当然也是最主流的两种接口。

本文不打算介绍概念性的东西,只为扒一扒RGW的架构。废话不多讲,直接上图

rgw_arch.png

我觉得画的挺清楚了,就不细讲了。


一张图不过瘾,再来一发。

rgw_structural

DNSmasq是一个小巧且方便地用于配置DNS和DHCP的工具,适用于小型网络,它提供了DNS功能和可选择的DHCP功能。它服务那些只在本地适用的域名,这些域名是不会在全球的DNS服务器中出现的。DHCP服务器和DNS服务器结合,并且允许DHCP分配的地址能在DNS中正常解析,而这些DHCP分配的地址和相关命令可以配置到每台主机中,也可以配置到一台核心设备中(比如路由器),DNSmasq支持静态和动态两种DHCP配置方式。

安装

rhel7.4上的安装yum install -y dnsmasq, 完成dnsmasq的安装同时也推荐安装bind-utils,这个包提供很多dns测试相关工具yum install -y bind-utils, 如dignslookup

配置启动

创建两个dnsmasq节点,一个做base dns,一个做upper dns

base dnsdnsmasq.conf配置:

1
2
3
4
5
6
7
listen-address=172.17.0.2,127.0.0.1

# 配置上游DNS服务器
server=/dockxen.mydns/172.17.0.3

# 配置别名
cname=web.dockxen.dns,rhel82

*** 在使用CNAME时,<target>必须本机可访问对象,所以此处我将rhel82放到/etc/hosts中 ***

base dns/etc/hosts配置:

1
2
3
4
127.0.0.1       localhost
::1 localhost ip6-localhost ip6-loopback
172.17.0.2 dnsbase
192.168.1.82 rhel82

upper dns配置:

1
2
3
4
5
6
address=/myweb.dockxen.mydns/192.168.1.82

listen-address=172.17.0.3,127.0.0.1

# The fields are <name>,<target>,<port>,<priority>,<weight>
srv-host=_ldap._tcp.dockxen.mydns,myweb.dockxen.mydns,8000,0,100

完成base dnsupper dns的配置后使用dnsmasq -d debug模式启动,可以看到部分调试信息

测试验证

测试验证需要安装bind-utils,测试中会用到其中的dignslookup,开始测试前修改/etc/resolv.conf中的nameserver 172.17.0.2

验证A

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
[root@dnclient /]# dig myweb.dockxen.mydns

; <<>> DiG 9.9.4-RedHat-9.9.4-51.el7 <<>> myweb.dockxen.mydns
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22751
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;myweb.dockxen.mydns. IN A

;; ANSWER SECTION:
myweb.dockxen.mydns. 0 IN A 192.168.1.82

;; Query time: 0 msec
;; SERVER: 172.17.0.2#53(172.17.0.2)
;; WHEN: Mon Nov 20 09:29:48 UTC 2017
;; MSG SIZE rcvd: 53

[root@dnclient /]# nslookup myweb.dockxen.mydns
Server: 172.17.0.2
Address: 172.17.0.2#53

Name: myweb.dockxen.mydns
Address: 192.168.1.82

验证CNAME

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
[root@dnclient /]# dig web.dockxen.dns

; <<>> DiG 9.9.4-RedHat-9.9.4-51.el7 <<>> web.dockxen.dns
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55429
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;web.dockxen.dns. IN A

;; ANSWER SECTION:
web.dockxen.dns. 0 IN CNAME rhel82.
rhel82. 0 IN A 192.168.1.82

;; Query time: 0 msec
;; SERVER: 172.17.0.2#53(172.17.0.2)
;; WHEN: Mon Nov 20 09:30:59 UTC 2017
;; MSG SIZE rcvd: 80

[root@dnclient /]# nslookup web.dockxen.dns
Server: 172.17.0.2
Address: 172.17.0.2#53

web.dockxen.dns canonical name = rhel82.
Name: rhel82
Address: 192.168.1.82

验证SRV

1
2
3
4
5
6
7
[root@dnclient /]# dig +noall +answer SRV _ldap._tcp.dockxen.mydns
_ldap._tcp.dockxen.mydns. 0 IN SRV 0 100 8000 myweb.dockxen.mydns.
[root@dnclient /]# nslookup -type=srv _ldap._tcp.dockxen.mydns
Server: 172.17.0.2
Address: 172.17.0.2#53

_ldap._tcp.dockxen.mydns service = 0 100 8000 myweb.dockxen.mydns.

应用

etcd的服务发现

etcd各个节点之间的通讯使用域名方式访问,由于etcd节点ip地址可以以dhcp方式获取,每次etcd节点重启都有可能导致访问地址发生变化。若使用/etc/hosts文件进行“域名-ip”映射涉及到/etc/hosts文件同步分发问题,所以使用dnsmasq来完成“域名-ip”的映射。

关于etcd的服务发现配置请见参考&鸣谢

kube-dns的服务发现

请见《K8s服务发现分析》

参考&鸣谢

Matplotlib数据可视化第三方库(官网)。由各种可视化类构成。

matplotlib.pyplot

matplotlib.pyplot是绘制各类可视化图形的命令子库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python
#encoding:utf-8

import matplotlib.pyplot as plt

plt.plot([1,2,3,4,5,6])
plt.ylabel("value")

# 将图像保存成PNG格式
plt.savefig("./test", dpi=600)


# x值[0,2,4,6,8]; y值[3,1,4,5,2]
plt.plot([0,2,4,6,8], [3,1,4,5,2])
plt.ylabel("value")

# X轴[-1,10]; Y轴[0,10]
plt.axis([-1,10,0,10])

plt.show()

plot

plt.plot(x,y,format_string, **kwargs)

  • x X轴数据,列表或数组,只有一条曲线的时候可选。
  • y Y轴数据,列表或数组。
  • format_string 控制曲线的各式字符串,可选。
  • kwargs 第二组或更多(x,y,format_string)

format_string分别由“颜色字符”、“风格字符”、“标记字符”构成

** 颜色字符 **

颜色字符 说明 颜色字符 说明
‘b’ 蓝色 ‘m’ 洋红色
‘g’ 绿色 ‘y’ 黄色
‘r’ 红色 ‘k’ 黑色
‘c’ 青色 ‘w’ 白色
‘#008000’ RGB某颜色 ‘0.8’ 灰度值字符串

如果用户不指定颜色,系统会自动指定不重复的颜色。

** 风格字符 **

风格字符 说明
‘-‘ 实现
‘–’ 破折线
‘-.’ 点划线
‘:’ 虚线
‘’ 无线条

*** 更多关于标记内容请见HERE ***

1
2
3
4
5
6
import numpy as np
import matplotlib.pyplot as plt

a = np.arange(10)
plt.plot(a,a*1.5,'go-', a,a*2.5,'rx', a,a*3.5,'*', a,a*4.5,'b-.')
plt.show()

分区绘制

subplot

plt.subplot(nrows, ncols, plot_number) 将绘制区域分割成 nrows x ncols个区域,plot_number指定绘制的是哪个区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
import matplotlib.pyplot as plt

def f(t):
return np.exp(-t) * np.cos(2*np.pi*t)

a = np.arange(0.0,5.0,2.0)

plt.subplot(2,1,1)
plt.plot(a, f(a))

plt.subplot(2,1,2)
plt.plot(a, np.cos(2*np.pi*a), 'r--')

plt.show()

参考&鸣谢

  • NumPy
  • PIL
    PIL(Python Image Library)库是一个具有强大图像处理能力的第三方库

图像=>数组

图像采用的色彩模式为RGB模式,即每个像素点由R(红色)、G(绿色)、B(蓝色)组成。我们人眼所能看到的颜色都是由这三种颜色变化叠加得到的。

  • R红色,取值范围,0~255
  • G绿色,取值范围,0~255
  • B蓝色,取值范围,0~255

图像是一个由像素组成的二维矩阵,每个元素是一个RGB值。

PIL安装

OSX: pip install pillow

图像处理

图像加载

turkey_orig.jpg

1
2
3
4
5
6
7
8
9
10
>>> from PIL import Image
>>> import numpy as np
>>> img = np.array(Image.open("turkey_orig.jpg"))
>>> img_l = np.array(Image.open("turkey_orig.jpg").convert('L'))
>>> print img.shape, img.dtype
(1184, 2104, 3) uint8
# img是一个三维数组每一个元素代表RGB的一个值
>>> print img_l.shape, img_l.shape
(1184, 2104) uint8
# img_l是一个二维数组每一个元素对应一个灰度值

图像变换

返选效果

1
md_img = [255,255,255] - img

turkey_modify_1.jpg

底片效果

1
md_img = 255 - img_l

turkey_modify_2.jpg

区间变换

1
md_img = (100.0/255.0)*img_l + 150

turkey_modify_3.jpg

平方变换

1
md_img = 255 * ((img_l/255.0)**2)

turkey_modify_4.jpg

手绘效果

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
img_l_float = img_l.astype("float")

# 深度值,范围为0~100
depth = 10

# 提取x和y方向的梯度值
grad = np.gradient(img_l_float)
grad_x, grad_y = grad

# 根据深度调整梯度值
grad_x = grad_x*depth/100
grad_y = grad_y*depth/100

# 此处计算的是什么?
A = np.sqrt(grad_x**2 + grad_y**2 + 1.0)
uni_x = grad_x/A
uni_y = grad_y/A
uni_z = 1.0/A

# 为什么要引入光源?
vec_el = np.pi/2.2
vec_az = np.pi/4.0
dx = np.cos(vec_el)*np.cos(vec_az)
dy = np.cos(vec_el)*np.sin(vec_az)
dz = np.sin(vec_el)

#dx, dy, dz = 1,1,1
md_img = 255*(dx*uni_x + dy*uni_y + dz*uni_z)
md_img = md_img.clip(0,255)

无光源效果:
turkey_modify_6.jpg

有光源效果:
turkey_modify_5.jpg

图像保存

1
2
save_img = Image.fromarray(md_img.astype('uint8'))
save_img.save("turkey_modify.jpg")

参考&鸣谢

NumPy是一个开源的Python科学计算基础库

  • 强大的N维数据兑现ndarry
  • 广播功能函数
  • 整合C/C++/Fortran代码的工具
  • 线性代数、傅立叶变换、随机数生成等功能

安装

OSX: pip install numpy

数组ndarray

  • 实际数据
  • 描述这些数据的元数据(数据的维度、数据类型等)

属性

属性 描述
ndim 维度的数量
shape 矩阵的行列数
size 数组元素个数
dtype 数组元素的类型
itemsize 数组元素的大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> import numpy as np
>>> a = np.array([[0,1,2,3],[1,2,3,4]])
>>> print a
[[0 1 2 3]
[1 2 3 4]]
>>> a.dim
2
>>> a.shape
(2, 4)
>>> a.size
8
>>> a.dtype
dtype('int64')
>>> a.itemsize
8

*** ndarray数组可以由非同质对象构成,非同质ndarray元素为对象类型。非同质ndarray对象无法有效发挥NumPy优势,应尽量避免使用。 ***

1
2
3
4
5
6
7
>>> b = np.array([[1,2,3],[1,2]])
>>> print b
[list([1, 2, 3]) list([1, 2])]
>>> b.dtype
dtype('O') # 'O'为对象类型
>>> b.shape
(2,)

ndarray元素类型

数据类型 描述
bool 布尔类型 True or False
intc 与C语言中的int一致
intp 与C语言中的size_t一致
int8 8位长度的整数[-128,127]
int16 16位长度的整数[-32768,32767]
int32 32位长度的整数[-231,231-1]
int64 64位长度的整数[-263,263-1]
uint8 8位无符合整数
uint16 16位无符号整数
uint32 32位无符号整数
uint64 64位无符号整数
float16 16位半精度浮点数
float32 32位半精度浮点数
float64 64位半精度浮点数
complex64 复数类型,实部和虚部都是32位浮点数
complex128 复数类型,实部和虚部都是64位浮点数

ndarray创建

从Python中的列表、元组等类型创建数组

np.array(list/tuple) or np.array(list/tuple, dtype=np.float32)np.array不指定dtype时,NumPy将根据数据情况关联一个dtype类型。

使用NumPy中的函数创建数组

函数 描述
np.arange(n) 返回一个从0到n-1的整数类型数组
np.ones(shape) 根据shape生成一个全1的数组
np.zeros(shape) 根据shape生成一个全0的数组
np.full(shape,val) 根据shape生成一个全val的数组
np.eye(n) 返回一个n*n的数组,对角线为1,其余为0
np.ones_like(a) 根据数组a的shape生成一个全1的数组
np.zeros_like(a) 根据数组a的shape生成一个全0的数组
np.full_like(a,val) 根据数组a的shape生成一个全val的数组
np.linspace() 根据起止数据等间距的填充数据,形成数组
np.concatenate() 将两个或多个数组合并成一个新的数组

arange

1
2
3
>>> d = np.arange(10)
>>> print d
[0 1 2 3 4 5 6 7 8 9]

ones zeros full

ones,zeros生成的数组元素都是浮点数类型(float64),而full生成的是整数类型。若想要oneszeros也生成整数类型,增加参数dtype=np.int64

1
2
3
4
5
6
7
8
9
10
11
12
>>> print np.zeros((3,4))
[[ 0. 0. 0. 0.]
[ 0. 0. 0. 0.]
[ 0. 0. 0. 0.]]
>>> print np.ones((3,4))
[[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]]
>>> print np.full((3,4), 8)
[[8 8 8 8]
[8 8 8 8]
[8 8 8 8]]

eye

1
2
3
4
5
6
7
>>> c = np.eye(5)
>>> print c
[[ 1. 0. 0. 0. 0.]
[ 0. 1. 0. 0. 0.]
[ 0. 0. 1. 0. 0.]
[ 0. 0. 0. 1. 0.]
[ 0. 0. 0. 0. 1.]]

linspace

1
2
3
4
>>> print np.linspace(1,10,4)
[ 1. 4. 7. 10.]
>>> print np.linspace(1,10,4, endpoint=False)
[ 1. 3.25 5.5 7.75]

endpoint表示最后一个元素10是否作为最后一个元素出现

concatenate

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> print np.concatenate((c,c))
[[ 1. 0. 0. 0. 0.]
[ 0. 1. 0. 0. 0.]
[ 0. 0. 1. 0. 0.]
[ 0. 0. 0. 1. 0.]
[ 0. 0. 0. 0. 1.]
[ 1. 0. 0. 0. 0.]
[ 0. 1. 0. 0. 0.]
[ 0. 0. 1. 0. 0.]
[ 0. 0. 0. 1. 0.]
[ 0. 0. 0. 0. 1.]]
>>> print np.concatenate((c,c)).shape
(10, 5)

ndarray维度变换

函数 描述
.reshape(shape) 不改变数组元素,返回一个shape形状的数组,原数组不变
.resize(shape) 与reshape功能一般,但修改原数组
.swapaxes(ax1,ax2) 将数组n个维度中的两个维度进行调换
.flatten() 对数组进行降维,返回一维数组,原数组不变

reshape

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> f = np.ones((2,3,4))
>>> print f.reshape((3,8))
[[ 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1.]]
>>> print f
[[[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]]

[[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]]]

resize

1
2
3
4
5
>>> f.resize((3,8))
>>> print f
[[ 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1.]]

swapaxes

1
2
3
4
5
6
7
8
9
>>> print np.arange(12).reshape((3,4))
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
>>> print np.arange(12).reshape((3,4)).swapaxes(0,1)
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]

flatten

1
2
>>> print np.arange(12).reshape((3,4)).flatten()
[ 0 1 2 3 4 5 6 7 8 9 10 11]

ndarray变换类型

1
2
3
4
5
6
7
8
9
10
>>> g = np.arange(12)
>>> g.dtype
dtype('int64')
>>> print g
[ 0 1 2 3 4 5 6 7 8 9 10 11]
>>> g.astype(np.float)
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.,
11.])
>>> print g
[ 0 1 2 3 4 5 6 7 8 9 10 11]

使用.astype改变Array数据类型,并不改变原数组的数据类型。

NumPy数组转换成Python列表

1
2
3
4
5
6
>>> type(g)
<type 'numpy.ndarray'>
>>> type(g.tolist())
<type 'list'>
>>> g.tolist()
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

ndarray获取操作

索引

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> h = np.arange(24).reshape((2,3,4))
>>> print h
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]

[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
>>> h[1,2,3]
23
>>> h[-1,-2,-3]
17

切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> h[:,1,-3]
array([ 5, 17])
>>> h[:,1:3,:]
array([[[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],

[[16, 17, 18, 19],
[20, 21, 22, 23]]])
>>> h[:,:,::2]
array([[[ 0, 2],
[ 4, 6],
[ 8, 10]],

[[12, 14],
[16, 18],
[20, 22]]])

ndarray运算

与标量运算

1
2
3
4
5
6
7
8
>>> h / h.mean()
array([[[ 0. , 0.08695652, 0.17391304, 0.26086957],
[ 0.34782609, 0.43478261, 0.52173913, 0.60869565],
[ 0.69565217, 0.7826087 , 0.86956522, 0.95652174]],

[[ 1.04347826, 1.13043478, 1.2173913 , 1.30434783],
[ 1.39130435, 1.47826087, 1.56521739, 1.65217391],
[ 1.73913043, 1.82608696, 1.91304348, 2. ]]])

一元函数

函数 描述
np.abs(x) np.fabs(x) 计算元素的绝对值
np.sqrt(x) 计算元素的平方根
np.square(x) 计算元素的平方
np.log(x) np.log10(x) np.log2(x) 计算元素的自然对数、10为底、2为底对数
np.ceil(x) np.floor(x) 计算元素的ceiliing floor值
np.rint(x) 计算元素的四舍五入值
np.modf(x) 计算元素的小数和整数部分以两个独立的数组形式返回
np.cos/cosh/sin/sinh/tan/tanh(x) 计算三角函数
np.exp(x) 计算元素的指数值
np.sign(x) 计算元素的符号值,1(+),0(0),-1(-)

二元函数

函数 描述
+ - * / ** 加、减、乘、除、幂
np.maximum(x,y) np.fmax() 取最大值
np.minimum(x,y) np.fmin() 取最小值
np.mod(x,y) 模运算
np.copysign(x,y) 将数组y中各元素的符号赋值给数组x对应的元素
> < >= <= == != 产生布尔型数组
1
2
3
4
5
6
7
8
>>> h > (h/h.mean())
array([[[False, True, True, True],
[ True, True, True, True],
[ True, True, True, True]],

[[ True, True, True, True],
[ True, True, True, True],
[ True, True, True, True]]], dtype=bool)

NumPy I/O

CSV文件

*** CSV只能有效存储一维和二维数组 ***

写入CSV文件

np.savetxt(frame, array, fmt='%.18e', delimiter=None)

  • frame
    文件、字符串或产生器,可以是.gz.bz2的压缩文件。
  • array
    存入文件的数组
  • fmt
    写入文件的格式,eg: %d %.2f %.18e
  • delimiter
    分割字符串,默认是空格。
1
2
>>> np.savetxt('h.csv', h.reshape((3,8)), fmt="%d", delimiter=',')
>>>
1
2
3
4
$ cat h.csv
0,1,2,3,4,5,6,7
8,9,10,11,12,13,14,15
16,17,18,19,20,21,22,23

读取CSV文件

np.loadtxt(frame, dtype=np.float, delimiter=None, unpack=False)

  • frame
    文件、字符串或产生器,可以是.gz.bz2的压缩文件。
  • dtype
    数据类型,可选。
  • delimiter
    分割字符串,默认是空格。
  • unpack
    True,表示读入属性将分别写入不同变量。
1
2
3
4
>>> np.loadtxt('h.csv', delimiter=',')
array([[ 0., 1., 2., 3., 4., 5., 6., 7.],
[ 8., 9., 10., 11., 12., 13., 14., 15.],
[ 16., 17., 18., 19., 20., 21., 22., 23.]])

多维度数据I/O

写入文件

ndarray.tofile(frame, sep='', format='%s')

  • frame
    文件,字符串
  • sep
    分割字符串,如果是空串,写入文件为二进制。
  • format
    写入数据的格式
1
2
>>> h.tofile('h.dat', sep=',', format='%d')
>>>
1
2
$ cat h.dat
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23

读取文件

np.fromfile(frame, dtype=np.float, count=-1, sep='')

  • frame
    文件、字符串
  • dtype
    读取的数据类型
  • count
    读入元素个数,-1表示读入整个文件。
  • sep
    分割字符串,如果是空串,读取文件为二进制。
1
2
3
4
5
6
7
8
>>> h.tofile('h.dat', sep=',', format='%d')
>>> np.fromfile('h.dat', dtype=np.int, sep=',')
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23])
>>> np.fromfile('h.dat', dtype=np.int, sep=',').reshape((3,8))
array([[ 0, 1, 2, 3, 4, 5, 6, 7],
[ 8, 9, 10, 11, 12, 13, 14, 15],
[16, 17, 18, 19, 20, 21, 22, 23]])

使用fromfile读取文件,需要知道数组的shape

便捷文件读写

写入文件

np.save(frame, array) or np.savez(frame, array)

  • frame
    文件名,以.npy为扩展名,压缩扩展名为.npz
  • array
    数组变量
1
>>> np.save('h.npy',h)
1
2
3
4
5
$ cat h.npy
�NUMPYF{'descr': '<i8', 'fortran_order': False, 'shape': (2, 3, 4), }



读取文件

np.load(frame)

  • frame
    文件名,以.npy为扩展名,压缩扩展名为.npz
1
2
3
4
5
6
7
8
>>> np.load('h.npy')
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],

[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]])

NumPy的随机函数

import numpy.random.* or np.random.rand()np.random.randn()np.random.randint()

随机产生

函数 描述
rand(d0,d1…,dn) 根据d0-dn创建随机数数组,浮点数,[0,1),均匀分布
randn(d0,d1…,dn) 根据d0-dn创建随机数数组,标准正态分布
randint(low[,high,shape]) 根据shape创建随机整数或整数数组,范围是[low,high)
seed(s) 随机数种子,s是给定的种子值
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
>>> np.random.rand(2,3,4)
array([[[ 0.90829822, 0.22946923, 0.6652444 , 0.99272168],
[ 0.06080758, 0.44248097, 0.93218605, 0.52258628],
[ 0.72315102, 0.15270485, 0.78927709, 0.69494654]],

[[ 0.35967592, 0.94780636, 0.70360292, 0.78655759],
[ 0.99834954, 0.5114802 , 0.41374511, 0.52093554],
[ 0.76139948, 0.71690824, 0.05146873, 0.24752045]]])
>>> np.random.randn(2,3,4)
array([[[ 0.27215006, 0.88541886, -1.23316095, -0.33090933],
[-0.02253028, 0.09407495, 1.76527947, -0.29149431],
[-0.3821042 , 0.29664845, -0.73678807, -1.0182318 ]],

[[-0.46816445, -1.62242958, -0.98800274, 0.5167897 ],
[-0.14876304, 0.60325633, -1.22824121, 1.40063683],
[ 0.7508062 , 0.42850387, 0.67736631, 0.68300665]]])
>>> np.random.randint(50,80,(2,3,4))
array([[[67, 79, 65, 56],
[76, 60, 58, 70],
[74, 61, 53, 71]],

[[72, 67, 61, 53],
[58, 66, 52, 69],
[60, 70, 73, 64]]])
>>> np.random.seed(1111)
>>> np.random.randint(50,80,(2,3,4))
array([[[78, 73, 55, 67],
[62, 52, 70, 74],
[72, 70, 61, 64]],

[[58, 56, 62, 64],
[68, 72, 58, 59],
[79, 69, 60, 62]]])
>>> np.random.seed(1111)
>>> np.random.randint(50,80,(2,3,4))
array([[[78, 73, 55, 67],
[62, 52, 70, 74],
[72, 70, 61, 64]],

[[58, 56, 62, 64],
[68, 72, 58, 59],
[79, 69, 60, 62]]])

随机排序拾取

函数 描述
shuffle(a) 对数组a按照最外维度进行随机排列,数组a改变
permutation(a) 对数组a按照最外维度进行随机排列,数组a不改变,生成新数组
choice(a[,size,replace,p]) 从一维数组中以概率p抽取元素,形成size形状新数组,replace表示是否重复抽取(默认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
>>> i = h.reshape((3,8))
>>> print i
[[ 0 1 2 3 4 5 6 7]
[ 8 9 10 11 12 13 14 15]
[16 17 18 19 20 21 22 23]]
>>> print np.random.permutation(i)
[[ 8 9 10 11 12 13 14 15]
[16 17 18 19 20 21 22 23]
[ 0 1 2 3 4 5 6 7]]
>>> print i
[[ 0 1 2 3 4 5 6 7]
[ 8 9 10 11 12 13 14 15]
[16 17 18 19 20 21 22 23]]
>>> np.random.shuffle(i)
>>> print i
[[16 17 18 19 20 21 22 23]
[ 0 1 2 3 4 5 6 7]
[ 8 9 10 11 12 13 14 15]]
>>> np.random.choice(i.flatten(), size=(3,4))
array([[ 1, 1, 15, 10],
[16, 20, 0, 21],
[13, 9, 5, 10]])
>>> np.random.choice(i.flatten(), size=(3,4), replace=False)
array([[ 6, 2, 12, 19],
[ 7, 9, 13, 22],
[17, 21, 20, 0]])

随机分布

函数 描述
uniform(low,high,size) 产生均匀分布的数组,low起始值,hight结束值,size形状
normal(loc,scale,size) 产生正态分布的数组,loc均值,scale标准差,size形状
poisson(lam,size) 产生泊松分布的数组,lam随机事件发生率,size形状
1
2
3
4
5
6
7
8
>>> np.random.uniform(0,10,(3,4))
array([[ 7.60854954, 6.30411065, 0.79332327, 1.30857967],
[ 3.00316754, 3.22281521, 1.61121489, 3.15193241],
[ 5.25528483, 6.45849382, 7.50645475, 0.56027292]])
>>> np.random.normal(10,5,(3,4))
array([[ 8.29746473, 14.35578466, 9.40820399, 9.47806228],
[ 20.81393417, 6.16603104, 2.10005772, 11.39870886],
[ 11.81287925, 5.19702161, 11.13850168, 13.95413254]])

NumPy的统计函数

NumPy直接提供库一级别的统计函数import numpy.* or np.std()np.var()

函数 描述
sum(a, axis=None) 对数组a求和,默认所有元素,axis指定维度
mean((a, axis=None)a 对数组a求平均值,默认所有元素,axis指定维度
average(a, axis=None, weights=None) 对数组a求加权平均值,默认所有元素,axis指定维度
std(a, axis=None) 对数组a求标准差,默认所有元素,axis指定维度
var(a, axis=None) 对数组a求方差,默认所有元素,axis指定维度
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
>>> print h
[[[16 17 18 19]
[20 21 22 23]
[ 0 1 2 3]]

[[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]]
>>> np.sum(h)
276
>>> np.sum(h,axis=0)
array([[20, 22, 24, 26],
[28, 30, 32, 34],
[12, 14, 16, 18]])
>>> np.sum(h,axis=1)
array([[36, 39, 42, 45],
[24, 27, 30, 33]])
>>> np.sum(h,axis=2)
array([[70, 86, 6],
[22, 38, 54]])
>>> np.average(h,axis=1,weights=[10,5,1])
array([[ 16.25, 17.25, 18.25, 19.25],
[ 5.75, 6.75, 7.75, 8.75]])
# (16*10 + 20*5 + 0*1)/(10+5+1)=16.25
# (17*10 + 21*5 + 1*1)/(10+5+1)=17.25
# (18*10 + 22*5 + 2*1)/(10+5+1)=18.25
函数 描述
min(a) max(a) 数组a中的最小值、最大值
argmin(a) argmax(a) 数组a中最小值、最大值降到一维后的下标索引
unravel_index(index,shape) 根据shape将一维下标索引index转换成多维下标索引
ptp(a) 数组a中最大值与最小值的差
median(a) 数组a中元素的中位数(中值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> print h
[[[16 17 18 19]
[20 21 22 23]
[ 0 1 2 3]]

[[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]]
>>> np.max(h)
23
>>> np.argmax(h)
7
>>> np.unravel_index(np.argmax(h), (2,3,4))
(0, 1, 3)
>>> np.ptp(h)
23
>>> np.median(h)
11.5

NumPy的梯度函数

梯度

连续值之间的变化率,即斜率。若有a,b,c三个值,b存在两侧值,b的梯度=(c-a)/2;若有a,b两个值,b只有一侧值,b或a的梯度=(b-a)/1

梯度计算

np.gradient(a)计算数组a中的梯度,若a为多维数组,返回每个维度梯度

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
>>> np.gradient(h)
[array([[[-12., -12., -12., -12.],
[-12., -12., -12., -12.],
[ 12., 12., 12., 12.]],

[[-12., -12., -12., -12.],
[-12., -12., -12., -12.],
[ 12., 12., 12., 12.]]]),
array([[[ 4., 4., 4., 4.],
[ -8., -8., -8., -8.],
[-20., -20., -20., -20.]],

[[ 4., 4., 4., 4.],
[ 4., 4., 4., 4.],
[ 4., 4., 4., 4.]]]),
array([[[ 1., 1., 1., 1.],
[ 1., 1., 1., 1.],
[ 1., 1., 1., 1.]],

[[ 1., 1., 1., 1.],
[ 1., 1., 1., 1.],
[ 1., 1., 1., 1.]]])]
>>> print h
[[[16 17 18 19]
[20 21 22 23]
[ 0 1 2 3]]

[[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]]

*** 梯度计算用于发现声音或图像数据的边缘,当梯度发生很大变化时,此处即为边缘 ***

参考&鸣谢

在Hbase中,只有一个单一的按照字典序排序的rowKey索引,当使用rowKey来进行数据查询的时候速度较快,但是如果不使用rowKey来查询的话就会使用filter来对全表进行扫描,很大程度上降低了检索性能。而Phoenix提供了二级索引技术来应对这种使用rowKey之外的条件进行检索的场景。

Global Index

Global indexing适用于多读少写的业务场景。使用Global indexing的话在写数据的时候会消耗大量开销,因为所有对数据表的更新操作(DELETE, UPSERT VALUES and UPSERT SELECT),会引起索引表的更新,而索引表是分布在不同的数据节点上的,跨节点的数据传输带来了较大的性能消耗。在读数据的时候Phoenix会选择索引表来降低查询消耗的时间。*** 在默认情况下如果想查询的字段不是索引字段的话索引表不会被使用,也就是说不会带来查询速度的提升。 ***

配置

HBase集群的每个regionserver节点的hbase-site.xml中加入配置,并重启HBase集群

1
2
3
4
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>

注: phoenix有两种链接方式JDBC和phoenix-client,phoenix-client可以正常创建索引;zeppelin使用jdbc连接不能正常创建索引,提示如下错误。

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
java.sql.SQLException: ERROR 1029 (42Y88): Mutable secondary indexes must have the hbase.regionserver.wal.codec property set to org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec in the hbase-sites.xml of every region server. tableName=CMP_IDX
at org.apache.phoenix.exception.SQLExceptionCode$Factory$1.newException(SQLExceptionCode.java:464)
at org.apache.phoenix.exception.SQLExceptionInfo.buildException(SQLExceptionInfo.java:150)
at org.apache.phoenix.schema.MetaDataClient.createIndex(MetaDataClient.java:1293)
at org.apache.phoenix.compile.CreateIndexCompiler$1.execute(CreateIndexCompiler.java:85)
at org.apache.phoenix.jdbc.PhoenixStatement$2.call(PhoenixStatement.java:358)
at org.apache.phoenix.jdbc.PhoenixStatement$2.call(PhoenixStatement.java:341)
at org.apache.phoenix.call.CallRunner.run(CallRunner.java:53)
at org.apache.phoenix.jdbc.PhoenixStatement.executeMutation(PhoenixStatement.java:340)
at org.apache.phoenix.jdbc.PhoenixStatement.execute(PhoenixStatement.java:1511)
at org.apache.commons.dbcp2.DelegatingStatement.execute(DelegatingStatement.java:291)
at org.apache.commons.dbcp2.DelegatingStatement.execute(DelegatingStatement.java:291)
at org.apache.zeppelin.jdbc.JDBCInterpreter.executeSql(JDBCInterpreter.java:581)
at org.apache.zeppelin.jdbc.JDBCInterpreter.interpret(JDBCInterpreter.java:692)
at org.apache.zeppelin.interpreter.LazyOpenInterpreter.interpret(LazyOpenInterpreter.java:97)
at org.apache.zeppelin.interpreter.remote.RemoteInterpreterServer$InterpretJob.jobRun(RemoteInterpreterServer.java:498)
at org.apache.zeppelin.scheduler.Job.run(Job.java:175)
at org.apache.zeppelin.scheduler.ParallelScheduler$JobRunner.run(ParallelScheduler.java:162)
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

索引操作

在开始创建索引以前先创建一个表,并向其中填充测试数据(此处填充的数据仅用于功能测试)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0: jdbc:phoenix:> create table usertable (id varchar primary key,firstname varchar, lastname varchar);
0: jdbc:phoenix:> !tables
+------------+--------------+-------------------------+---------------+----------+------------+----------------------------+-------------+
| TABLE_CAT | TABLE_SCHEM | TABLE_NAME | TABLE_TYPE | REMARKS | TYPE_NAME | SELF_REFERENCING_COL_NAME | REF_GENERAT |
+------------+--------------+-------------------------+---------------+----------+------------+----------------------------+-------------+
| | SYSTEM | CATALOG | SYSTEM TABLE | | | | |
| | SYSTEM | FUNCTION | SYSTEM TABLE | | | | |
| | SYSTEM | SEQUENCE | SYSTEM TABLE | | | | |
| | SYSTEM | STATS | SYSTEM TABLE | | | | |
| | | USERTABLE | TABLE | | | | |
+------------+--------------+-------------------------+---------------+----------+------------+----------------------------+-------------+

0: jdbc:phoenix:> upsert into usertable(id, firstname, lastname) values('1', 'Hello', 'world');
...
0: jdbc:phoenix:> upsert into usertable(id, firstname, lastname) values('5', 'Hello', 'phoenix');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
base(main):019:0> scan 'USERTABLE'
ROW COLUMN+CELL
1 column=0:FIRSTNAME, timestamp=1508397473229, value=Hello
1 column=0:LASTNAME, timestamp=1508397473229, value=world
1 column=0:_0, timestamp=1508397473229, value=x
2 column=0:FIRSTNAME, timestamp=1508397529852, value=Hello
2 column=0:LASTNAME, timestamp=1508397529852, value=world
2 column=0:_0, timestamp=1508397529852, value=x
3 column=0:FIRSTNAME, timestamp=1508397535836, value=Hello
3 column=0:LASTNAME, timestamp=1508397535836, value=world
3 column=0:_0, timestamp=1508397535836, value=x
4 column=0:FIRSTNAME, timestamp=1508397643594, value=Hello
4 column=0:LASTNAME, timestamp=1508397643594, value=phoenix
4 column=0:_0, timestamp=1508397643594, value=x
5 column=0:FIRSTNAME, timestamp=1508397648938, value=Hello
5 column=0:LASTNAME, timestamp=1508397648938, value=phoenix
5 column=0:_0, timestamp=1508397648938, value=x
5 row(s) in 0.3220 seconds

创建索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0: jdbc:phoenix:> create index idx_name on usertable (lastname) include(firstname);
0: jdbc:phoenix:> 0: jdbc:phoenix:> !tables
+------------+--------------+-------------------------+---------------+----------+------------+----------------------------+-------------+
| TABLE_CAT | TABLE_SCHEM | TABLE_NAME | TABLE_TYPE | REMARKS | TYPE_NAME | SELF_REFERENCING_COL_NAME | REF_GENERAT |
+------------+--------------+-------------------------+---------------+----------+------------+----------------------------+-------------+
| | | IDX_NAME | INDEX | | | | |
| | SYSTEM | CATALOG | SYSTEM TABLE | | | | |
| | SYSTEM | FUNCTION | SYSTEM TABLE | | | | |
| | SYSTEM | SEQUENCE | SYSTEM TABLE | | | | |
| | SYSTEM | STATS | SYSTEM TABLE | | | | |
| | | USERTABLE | TABLE | | | | |
+------------+--------------+-------------------------+---------------+----------+------------+----------------------------+-------------+
0: jdbc:phoenix:> !index usertable
+------------+--------------+-------------+-------------+------------------+-------------+-------+-------------------+--------------+----+
| TABLE_CAT | TABLE_SCHEM | TABLE_NAME | NON_UNIQUE | INDEX_QUALIFIER | INDEX_NAME | TYPE | ORDINAL_POSITION | COLUMN_NAME | AS |
+------------+--------------+-------------+-------------+------------------+-------------+-------+-------------------+--------------+----+
| | | USERTABLE | true | | IDX_NAME | 3 | 1 | 0:LASTNAME | A |
| | | USERTABLE | true | | IDX_NAME | 3 | 2 | :ID | A |
| | | USERTABLE | true | | IDX_NAME | 3 | 3 | 0:FIRSTNAME | |
+------------+--------------+-------------+-------------+------------------+-------------+-------+-------------------+--------------+----+
1
2
3
4
5
6
7
8
9
10
11
12
13
hbase(main):020:0> scan 'IDX_NAME'
ROW COLUMN+CELL
phoenix\x004 column=0:0:FIRSTNAME, timestamp=1508397686228, value=Hello
phoenix\x004 column=0:_0, timestamp=1508397686228, value=x
phoenix\x005 column=0:0:FIRSTNAME, timestamp=1508397686228, value=Hello
phoenix\x005 column=0:_0, timestamp=1508397686228, value=x
world\x001 column=0:0:FIRSTNAME, timestamp=1508397686228, value=Hello
world\x001 column=0:_0, timestamp=1508397686228, value=x
world\x002 column=0:0:FIRSTNAME, timestamp=1508397686228, value=Hello
world\x002 column=0:_0, timestamp=1508397686228, value=x
world\x003 column=0:0:FIRSTNAME, timestamp=1508397686228, value=Hello
world\x003 column=0:_0, timestamp=1508397686228, value=x
5 row(s) in 0.3180 seconds

使用索引

正常的select … where … 是不会用到索引表的,要想用到索引表,必须查询出的字段也是索引字段。(此处的结论需要在后续的性能测试中进行验证)

1
2
3
4
5
6
7
0: jdbc:phoenix:> select firstname from usertable where lastname = 'phoenix';
+------------+
| FIRSTNAME |
+------------+
| Hello |
| Hello |
+------------+

性能测试

Todo…

Todo…

Local Index

Todo…

参考&鸣谢

一个磁盘可以划分成多个分区,每个分区必须先用格式化工具(例如某种mkfs命令)格式化成某种格式的文件系统,然后才能存储文件,格式化的过程会在磁盘上写一些管理存储布局的信息。

格式

文件系统中存储的最小单位是块(Block),一个块究竟多大是在格式化时确定的,例如mke2fs的-b选项可以设定块大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的,就是1KB,启动块是由PC标准规定的,用来存储磁盘分区信息和启动信息,任何文件系统都不能使用启动块。启动块之后才是ext2文件系统的开始,ext2文件系统将整个分区划成若干个同样大小的块组(Block Group)

fs-ext2layout.png

做一个EXT2看看

做一个EXT2的文件系统,然后将其挂载,看看其文件格式。

1
2
3
4
5
6
$ dd if=/dev/zero of=./img.1m count=256 bs=4K
...
$ mke2fs fs
...
$ sudo mount -o loop ./img.1m /mnt
$ sudo umount /mnt

使用od查看img.1m镜像文件

1
od -tx1 -Ax ./img.1m

Super Block

描述整个分区的文件系统信息,例如块大小、文件系统版本号、上次mount的时间等等。超级块在每个块组的开头都有一份拷贝。

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
struct ext2_super_block {
__le32 s_inodes_count; /* Inodes count */
__le32 s_blocks_count; /* Blocks count */
__le32 s_r_blocks_count; /* Reserved blocks count */
__le32 s_free_blocks_count; /* Free blocks count */
__le32 s_free_inodes_count; /* Free inodes count */
__le32 s_first_data_block; /* First Data Block */
__le32 s_log_block_size; /* Block size */
__le32 s_log_frag_size; /* Fragment size */
__le32 s_blocks_per_group; /* # Blocks per group */
__le32 s_frags_per_group; /* # Fragments per group */
__le32 s_inodes_per_group; /* # Inodes per group */
__le32 s_mtime; /* Mount time */
__le32 s_wtime; /* Write time */
__le16 s_mnt_count; /* Mount count */
__le16 s_max_mnt_count; /* Maximal mount count */
__le16 s_magic; /* Magic signature */
__le16 s_state; /* File system state */
__le16 s_errors; /* Behaviour when detecting errors */
__le16 s_minor_rev_level; /* minor revision level */
__le32 s_lastcheck; /* time of last check */
__le32 s_checkinterval; /* max. time between checks */
__le32 s_creator_os; /* OS */
__le32 s_rev_level; /* Revision level */
__le16 s_def_resuid; /* Default uid for reserved blocks */
__le16 s_def_resgid; /* Default gid for reserved blocks */
__le32 s_first_ino; /* First non-reserved inode */
__le16 s_inode_size; /* size of inode structure */
__le16 s_block_group_nr; /* block group # of this superblock */
__le32 s_feature_compat; /* compatible feature set */
__le32 s_feature_incompat; /* incompatible feature set */
__le32 s_feature_ro_compat; /* readonly-compatible feature set */
__u8 s_uuid[16]; /* 128-bit uuid for volume */
char s_volume_name[16]; /* volume name */
char s_last_mounted[64]; /* directory where last mounted */
__le32 s_algorithm_usage_bitmap; /* For compression */
__u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/
__u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */
__u16 s_padding1;
/*
* Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.
*/
__u8 s_journal_uuid[16]; /* uuid of journal superblock */
__u32 s_journal_inum; /* inode number of journal file */
__u32 s_journal_dev; /* device number of journal file */
__u32 s_last_orphan; /* start of list of inodes to delete */
__u32 s_hash_seed[4]; /* HTREE hash seed */
__u8 s_def_hash_version; /* Default hash version to use */
__u8 s_reserved_char_pad;
__u16 s_reserved_word_pad;
__le32 s_default_mount_opts;
__le32 s_first_meta_bg; /* First metablock block group */
__u32 s_reserved[190]; /* Padding to the end of the block */
};

fs.sb.png

从000000开始的1KB是启动块,由于这不是一个真正的磁盘分区,启动块的内容全部为零。从000400到0007ff的1KB是超级块,可以对照着dumpe2fs分析。

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
$ dumpe2fs ./img.1m                                                                                                                 1 
dumpe2fs 1.42.9 (28-Dec-2013)
Filesystem volume name: <none>
Last mounted on: <not available>
Filesystem UUID: 2b8250d9-29fa-459e-9e2d-8b9c887b860b
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: ext_attr resize_inode dir_index filetype sparse_super
Filesystem flags: signed_directory_hash
Default mount options: user_xattr acl
Filesystem state: not clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 128
Block count: 1024
Reserved block count: 51
Free blocks: 986
Free inodes: 117
First block: 1
Block size: 1024
Fragment size: 1024
Reserved GDT blocks: 3
Blocks per group: 8192
Fragments per group: 8192
Inodes per group: 128
Inode blocks per group: 16
Filesystem created: Wed Oct 25 09:56:05 2017
Last mount time: Wed Oct 25 09:56:48 2017
Last write time: Wed Oct 25 09:56:48 2017
Mount count: 1
Maximum mount count: -1
Last checked: Wed Oct 25 09:56:05 2017
Check interval: 0 (<none>)
Reserved blocks uid: 0 (user root)
Reserved blocks gid: 0 (group root)
First inode: 11
Inode size: 128
Default directory hash: half_md4
Directory Hash Seed: 2feeee23-e3de-4126-a60d-1d05a11b0ab3
...

块大小是1024字节,1MB的分区共有1024个块,第0个块是启动块,启动块之后才算ext2文件系统的开始,因此Group 0占据第1个到第1023个块,共1023个块。块位图占一个块,共有1024×8=8192个bit,足够表示这1023个块了,因此只要一个块组就够了。默认是每8KB分配一个inode,因此1MB的分区对应128个inode,这些数据都和dumpe2fs的输出吻合。

GDT(Group Descriptor Table)

由很多块组描述符组成,整个分区分成多少个块组就对应有多少个块组描述符。每个块组描述符(Group Descriptor)存储一个块组的描述信息,例如在这个块组中从哪里开始是inode表,从哪里开始是数据块,空闲的inode和数据块还有多少个等等。和超级块类似,块组描述符表在每个块组的开头也都有一份拷贝,这些信息是非常重要的,一旦超级块意外损坏就会丢失整个分区的数据,一旦块组描述符意外损坏就会丢失整个块组的数据,因此它们都有多份拷贝。通常内核只用到第0个块组中的拷贝,当执行e2fsck检查文件系统一致性时,第0个块组中的超级块和块组描述符表就会拷贝到其它块组,这样当第0个块组的开头意外损坏时就可以用其它拷贝来恢复,从而减少损失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* Structure of a blocks group descriptor
*/
struct ext2_group_desc
{
__le32 bg_block_bitmap; /* Blocks bitmap block */
__le32 bg_inode_bitmap; /* Inodes bitmap block */
__le32 bg_inode_table; /* Inodes table block */
__le16 bg_free_blocks_count; /* Free blocks count */
__le16 bg_free_inodes_count; /* Free inodes count */
__le16 bg_used_dirs_count; /* Directories count */
__le16 bg_pad;
__le32 bg_reserved[3];
};

fs.gd.png

从000800开始是块组描述符表,这个文件系统较小,只有一个块组描述符,对照着dumpe2fs的输出信息分析

1
2
3
4
5
6
7
8
9
10
11
$ dumpe2fs ./img.1m                                                                                                                 1 
...
Group 0: (Blocks 1-1023)
主 superblock at 1, Group descriptors at 2-2
保留的GDT块位于 3-5
Block bitmap at 6 (+5), Inode bitmap at 7 (+6)
Inode表位于 8-23 (+7)
986 free blocks, 117 free inodes, 2 directories
可用块数: 38-1023
可用inode数: 12-128k
...

整个文件系统是1MB,每个块是1KB,应该有1024个块,除去启动块还有1023个块,分别编号为1-1023,它们全都属于Group 0。其中,Block 1是超级块,接下来的块组描述符指出,块位图是Block 6,因此中间的Block 2-5是块组描述符表,其中Block 3-5保留未用。块组描述符还指出,inode位图是Block 7,inode表是从Block 8开始的,那么inode表到哪个块结束呢?由于超级块中指出每个块组有128个inode,每个inode的大小是128字节,因此共占16个块,inode表的范围是Block 8-23。从Block 24开始就是数据块了。块组描述符中指出,空闲的数据块有986个,由于文件系统是新创建的,空闲块是连续的Block 38-1023,用掉了前面的Block 24-37

Block Bitmap

一个块组中的块是这样利用的:数据块(Data Block)存储所有文件的数据,比如某个分区的块大小是1024字节,某个文件是2049字节,那么就需要三个数据块来存,即使第三个块只存了一个字节也需要占用一个整块;超级块、块组描述符表、块位图、inode位图、inode表这几部分存储该块组的描述信息。那么如何知道哪些块已经用来存储文件数据或其它描述信息,哪些块仍然空闲可用呢?块位图就是用来描述整个块组中哪些块已用哪些块空闲的,它本身占一个块,其中的每个bit代表本块组中的一个块,这个bit为1表示该块已用,这个bit为0表示该块空闲可用。
为什么用df命令统计整个磁盘的已用空间非常快呢?因为只需要查看每个块组的块位图即可,而不需要搜遍整个分区。相反,用du命令查看一个较大目录的已用空间就非常慢,因为不可避免地要搜遍整个目录的所有文件。
与此相联系的另一个问题是:在格式化一个分区时究竟会划出多少个块组呢?主要的限制在于块位图本身必须只占一个块。用mke2fs格式化时默认块大小是1024字节,可以用-b参数指定块大小,现在设块大小指定为b字节,那么一个块可以有8b个bit,这样大小的一个块位图就可以表示8b个块的占用情况,因此一个块组最多可以有8b个块,如果整个分区有s个块,那么就可以有s/(8b)个块组。格式化时可以用-g参数指定一个块组有多少个块,但是通常不需要手动指定,mke2fs工具会计算出最优的数值。

fs_blk_bmp.png

从块位图中可以看出,前37位(前4个字节加最后一个字节的低5位)都是1,,就表示Block 1-37已用。在块位图中,Block 38-1023对应的位都是0(一直到001870那一行最后一个字节的低7位),接下来的位已经超出了文件系统的空间,不管是0还是1都没有意义。可见,块位图每个字节中的位应该按从低位到高位的顺序来看。以后随着文件系统的使用和添加删除文件,块位图中的1就变得不连续了。

Inode Bitmap

和块位图类似,本身占一个块,其中每个bit表示一个inode是否空闲可用。

fs_inode_bmp.png

块组描述符指出,空闲的inode有117个,由于文件系统是新创建的,空闲的inode也是连续的,inode编号从1到128,空闲的inode编号从12到128。从inode位图可以看出,前11位都是1,表示前11个inode已用。以后随着文件系统的使用和添加删除文件,inode位图中的1就变得不连续了。001c00这一行的128位就表示了所有inode,因此下面的行不管是0还是1都没有意义。已用的11个inode中,前10个inode是被ext2文件系统保留的,其中第2个inode是根目录,第11个inode是lost+found目录,块组描述符也指出该组有两个目录,就是根目录和lost+found。

Inode Table

一个文件除了数据需要存储之外,一些描述信息也需要存储,例如文件类型(常规、目录、符号链接等),权限,文件大小,创建/修改/访问时间等,也就是ls -l命令看到的那些信息,这些信息存在inode中而不是数据块中。每个文件都有一个inode,一个块组中的所有inode组成了inode表。
inode表占多少个块在格式化时就要决定并写入块组描述符中,mke2fs格式化工具的默认策略是一个块组有多少个8KB就分配多少个inode。由于数据块占了整个块组的绝大部分,也可以近似认为数据块有多少个8KB就分配多少个inode,换句话说,如果平均每个文件的大小是8KB,当分区存满的时候inode表会得到比较充分的利用,数据块也不浪费。如果这个分区存的都是很大的文件(比如电影),则数据块用完的时候inode会有一些浪费,如果这个分区存的都是很小的文件(比如源代码),则有可能数据块还没用完inode就已经用完了,数据块可能有很大的浪费。如果用户在格式化时能够对这个分区以后要存储的文件大小做一个预测,也可以用mke2fs的-i参数手动指定每多少个字节分配一个inode。

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
/*
* Structure of an inode on the disk
*/
struct ext2_inode {
__le16 i_mode; /* File mode */
__le16 i_uid; /* Low 16 bits of Owner Uid */
__le32 i_size; /* Size in bytes */
__le32 i_atime; /* Access time */
__le32 i_ctime; /* Creation time */
__le32 i_mtime; /* Modification time */
__le32 i_dtime; /* Deletion Time */
__le16 i_gid; /* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
__le32 i_blocks; /* Blocks count */
__le32 i_flags; /* File flags */
union {
struct {
__le32 l_i_reserved1;
} linux1;
struct {
__le32 h_i_translator;
} hurd1;
struct {
__le32 m_i_reserved1;
} masix1;
} osd1; /* OS dependent 1 */
__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
__le32 i_generation; /* File version (for NFS) */
__le32 i_file_acl; /* File ACL */
__le32 i_dir_acl; /* Directory ACL */
__le32 i_faddr; /* Fragment address */
union {
struct {
__u8 l_i_frag; /* Fragment number */
__u8 l_i_fsize; /* Fragment size */
__u16 i_pad1;
__le16 l_i_uid_high; /* these 2 fields */
__le16 l_i_gid_high; /* were reserved2[0] */
__u32 l_i_reserved2;
} linux2;
struct {
__u8 h_i_frag; /* Fragment number */
__u8 h_i_fsize; /* Fragment size */
__le16 h_i_mode_high;
__le16 h_i_uid_high;
__le16 h_i_gid_high;
__le32 h_i_author;
} hurd2;
struct {
__u8 m_i_frag; /* Fragment number */
__u8 m_i_fsize; /* Fragment size */
__u16 m_pad1;
__u32 m_i_reserved2[2];
} masix2;
} osd2; /* OS dependent 2 */
};

根目录inode信息:
fs.rootinode.png

st_mode以八进制表示,包含了文件类型和文件权限,最高位的4表示文件类型为目录(各种文件类型的编码详见stat(2)),低位的755表示权限。Size是1024,说明根目录现在只有一个数据块。Links为3表示根目录有三个硬链接,分别是根目录下的.和..,以及lost+found子目录下的..。注意,虽然我们通常用/表示根目录,但是并没有名为/的硬链接,事实上,/是路径分隔符,不能在文件名中出现。这里的Blockcount是以512字节为一个块来数的,并非格式化文件系统时所指定的块大小,磁盘的最小读写单位称为扇区(Sector),通常是512字节,所以Blockcount是磁盘的物理块数量,而非分区的逻辑块数量。根目录数据块的位置由上图中的Blocks[0]指出,也就是第24个块,它在文件系统中的位置是24×0x400=0x6000

探索文件系统还有一个很有用的工具debugfs,它提供一个命令行界面,可以对文件系统做各种操作,例如查看信息、恢复数据、修正文件系统中的错误。使用debugfs ./img.1m打开文件系统,使用stat /命令查看根目录inode信息

1
2
3
4
5
6
7
8
9
10
11
12
Inode: 2   Type: directory    Mode:  0755   Flags: 0x0
Generation: 0 Version: 0x00000000
User: 0 Group: 0 Size: 1024
File ACL: 0 Directory ACL: 0
Links: 3 Blockcount: 2
Fragment: Address: 0 Number: 0 Size: 0
ctime: 0x59efef35 -- Wed Oct 25 09:56:05 2017
atime: 0x59efef63 -- Wed Oct 25 09:56:51 2017
mtime: 0x59efef35 -- Wed Oct 25 09:56:05 2017
BLOCKS:
(0):24
TOTAL: 1

数据块寻址

通过__le32 i_block[EXT2_N_BLOCKS]来完成数据块的寻址。

1
2
3
4
5
6
7
8
/*
* Constants relative to the data blocks
*/
#define EXT2_NDIR_BLOCKS 12
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)
  • EXT2_NDIR_BLOCKS
    i_block前12项属于直接寻址,直接存储数据块的”block id”。所以总共可记录 12 笔记录,总额大小为12*1k=12k
  • EXT2_IND_BLOCK
    i_block第13项为一级间接寻址,每笔 block 号码的记录会花去 4bytes,因此 1K 的大小能够记录 256 笔记录,因此一个间接可以记录的文件大小为(1k/4)*1K=256k
  • EXT2_DIND_BLOCK
    i_block第14项为二级间接寻址,第一层 block 会指定 256 个第二层,每个第二层可以指定 256 个号码,因此总额大小为2562561k
  • EXT2_TIND_BLOCK
    i_block第15项为三级间接寻址,第一层 block 会指定 256 个第二层,每个第二层可以指定 256 个第三层,每个第三层可以指定 256 个号码,因此总额大小为256256256*1k

*** 总容量为直接寻址+一级间接+二级间接+三级间接=16.06GB ***

fs.datablockaddr.png

Blocks

  • 对于常规文件,文件的数据存储在数据块中。
  • 对于目录,该目录下的所有文件名和目录名存储在数据块中,注意文件名保存在它所在目录的数据块中,除文件名之外,ls -l命令看到的其它信息都保存在该文件的inode中。注意这个概念:目录也是一种文件,是一种特殊类型的文件。
  • 对于符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。
  • 设备文件、FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号保存在inode中。

根目录的数据结构

1
2
3
4
5
6
7
struct ext2_dir_entry_2 {
__le32 inode; /* Inode number */
__le16 rec_len; /* Directory entry length */
__u8 name_len; /* Name length */
__u8 file_type;
char name[]; /* File name, up to EXT2_NAME_LEN */
};

根目录的数据块
fs.datablock.png

根据上文中根目录inode信息找到根目录数据块006000地址,目录的数据块由许多不定长的记录组成,每条记录描述该目录下的一个文件,在上图中用框表示。第一条记录描述inode号为2的文件,也就是根目录本身,该记录的总长度为12字节,其中文件名的长度为1字节,文件类型为2(见下表,注意此处的文件类型编码和st_mode不一致),文件名是.。第二条记录也是描述inode号为2的文件(根目录),该记录总长度为12字节,其中文件名的长度为2字节,文件类型为2,文件名字符串是..。第三条记录一直延续到该数据块的末尾,描述inode号为11的文件(lost+found目录),该记录的总长度为1000字节(和前面两条记录加起来是1024字节),文件类型为2,文件名字符串是lost+found,后面全是0字节。如果要在根目录下创建新的文件,可以把第三条记录截短,在原来的0字节处创建新的记录。如果该目录下的文件名太多,一个数据块不够用,则会分配新的数据块,块编号会填充到inode的Blocks[1]字段。

编码 文件类型(file type)
0 Unknown
1 Regular File
2 Directory
3 Character device
4 Block device
5 Named pipe
6 Socket
7 Symbolic link

参考&鸣谢

Exception语法

try-except

1
2
3
4
5
6
7
8
9
10
try:
<语句>
except <异常名字>:
<语句>
except <异常名字>, <异常数据变量>:
<语句>
except (<异常名字>,<异常名字>,...)[, <异常数据变量>]:
<语句>
else:
<语句>

try-finally

1
2
3
4
5
6
try:
<语句>
finally:
# 无论是否发生异常都将执行最后的代码
# 退出try时总会执行
<语句>

raise

1
raise [Exception [, args [, traceback]]]

Exception是异常的类型(例如,NameError)参数是一个异常参数值。该参数是可选的,如果不提供,异常的参数是”None”。最后一个参数是可选的(在实践中很少使用),如果存在,是跟踪异常对象。

标准异常

异常名称 描述
BaseException 所有异常的基类
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入^C)
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
GeneratorExit 生成器(generator)发生异常来通知退出
StandardError 所有的内建标准异常的基类
ArithmeticError 所有数值计算错误的基类
FloatingPointError 浮点计算错误
OverflowError 数值运算超出最大限制
ZeroDivisionError 除(或取模)零 (所有数据类型)
AssertionError 断言语句失败
AttributeError 对象没有这个属性
EOFError 没有内建输入,到达EOF 标记
EnvironmentError 操作系统错误的基类
IOError 输入/输出操作失败
OSError 操作系统错误
WindowsError 系统调用失败
ImportError 导入模块/对象失败
LookupError 无效数据查询的基类
IndexError 序列中没有此索引(index)
KeyError 映射中没有这个键
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
UnboundLocalError 访问未初始化的本地变量
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
NotImplementedError 尚未实现的方法
SyntaxError Python 语法错误
IndentationError 缩进错误
TabError Tab 和空格混用
SystemError 一般的解释器系统错误
TypeError 对类型无效的操作
ValueError 传入无效的参数
UnicodeError Unicode 相关的错误
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeTranslateError Unicode 转换时错误
Warning 警告的基类
DeprecationWarning 关于被弃用的特征的警告
FutureWarning 关于构造将来语义会有改变的警告
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
SyntaxWarning 可疑的语法的警告
UserWarning 用户代码生成的警告

参考&鸣谢

安装

python不能直接访问hbase,必须通过thrift插件才能访问hbase,下面是对hbasethriftpython插件的安装

1
2
pip install thrift
pip install hbase-thrift

访问hbase

hbase_client.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
29
30
31
32
33
34
35
36
#!/usr/bin/python
#encoding=utf-8

from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol

from hbase import Hbase
from hbase.ttypes import *

class HbaseClient(object):
def __init__(self, host='', port=9090):
self.sock = TSocket.TSocket(host, port)
self.transport = TTransport.TBufferedTransport(self.sock)
self.proto = TBinaryProtocol.TBinaryProtocol(self.transport)
self.client = Hbase.Client(self.proto)

def get_tables(self):
self.transport.open()
tables = self.client.getTableNames()
self.transport.close()
return tables

def get_column_desp(self, table_name):
self.transport.open()
column_desp = self.client.getColumnDescriptors(table_name)
self.transport.close()
return column_desp

def get_hbase_client(self):
return self.client

def open(self):
self.transport.open()
def close(self):
self.transport.close()

test.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
29
30
31
32
33
34
35
36
37
38
39
40
41
import hbase_client as hc

con = hc.HbaseClient(host='x.x.x.x')
client = con.get_hbase_client()

con.open()
tbls = client.getTableNames()
print tbls
mytbls = []
for tbl in tbls:
if not (tbl == 'YJDD:myApp' or tbl == 'YJDD:myAppDetail'):
continue
print "ColumnDescriptors:"
tbl_desp = client.getColumnDescriptors(tbl)
print tbl_desp
print "TableRegions:"
tbl_reg = client.getTableRegions(tbl)
print tbl_reg
mytbls.append(tbl)

print "============== scannerGet ============"
for tbl in mytbls:
sid = client.scannerOpen(tbl, '', ['user','app'])
row_result = client.scannerGet(sid)
print row_result
while row_result:
row_result = client.scannerGet(sid)
if row_result:
print row_result
print "--------------------------"

client.scannerClose(sid)

print "=============== scannerGetList ============"
for tbl in mytbls:
sid = client.scannerOpen(tbl, '', ['user','app'])
row_result = client.scannerGetList(sid, 20)
print row_result
client.scannerClose(sid)
print "--------------------------"
con.close()

参考&鸣谢

背景

计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255(二进制11111111=十进制255),如果要表示更大的整数,就必须用更多的字节。比如两个字节可以表示的最大整数是65535,4个字节可以表示的最大整数是4294967295。

ASCII

规定了128个字符的编码(准确地说ASCII码是一个编码字符集),比如空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。后128个称为扩展ASCII码,目前许多基于x86的系统都支持使用扩展ASCII码。256个ASCII码中的后128个扩展码可定制用来表示特殊字符和非英语字符,GB2312就是利用这后面的128个扩展字符来表示汉字,[161,254]共94个字符来组成双字节来表示简体汉字字符表。

Unicode

光是英语字符ASCII编码字符集是够了,但是如果算上世界上其他的语言的字符,ASCII码显然不够了,于是Unicode编码字符集应运而生。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8、UTF-16、UTF-32都是将所有Unicode用到的数字转换为程序数据的编码方案。全世界的字符加起来也用不了所有的码位,Unicode 5.0版本中,才用了238605个码位。

UTF8

新问题的出现:如果统一成Unicode编码,乱码问题从此消失了。但是,如果你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。因此,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间。ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。Unicode是字符集,UTF-8、UTF-16等是编码格式,定义“字符对应的数字”如何在以二进制的方式存储。

GB2312

规定: 一个小于127的字符的意义与原来相同, 但两个大于127的字符连在一起时, 就表示一个汉字, 前面的一个字节(他称之为高字节)从0xA1用到 0xF7, 后面一个字节(低字节)从0xA1到0xFE, 这样我们就可以组合出大约7000多个简体汉字了. 在这些编码里, 我们还把数学符号,罗马希腊的 字母,日文的假名们都编进去了, 连在 ASCII 里本来就有的数字,标点,字母都统统重新编了两个字节长的编码, 这就是常说的”全角”字符, 而原来在127号以下的那些就叫”半角”字符了。

GBK

GBK 包括了 GB2312 的所有内容, 同时又增加了近20000个新的汉字(包括繁体字)和符号。

转换

unicode兼容ascii,GBK兼容GB2312,转换也就是unicode与GBK之间的事情了。字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。

  • decode的作用是将其他编码的字符串转换成unicode编码,如str1.decode(‘gb2312’),表示将gb2312编码的字符串转换成unicode编码
  • encode的作用是将unicode编码转换成其他编码的字符串,如str2.encode(‘gb2312’),表示将unicode编码的字符串转换成gb2312编码
1
2
3
4
5
6
7
8
9
10
11
12
13
#encoding=utf8

s = '中文'
print type(s)
print s

us = s.decode('utf8')
print type(us)
print s

gs = us.encode('gb2312')
print type(gs)
print gs

参考&鸣谢