sizeoftank


  • 首页

  • 分类

  • 归档

介绍一个画图工具Mermaid

发表于 2021-10-12 | 分类于 个人笔记 | 阅读次数:

背景

在软件开发的系统建模过程中,画图是一项重要且比较耗时的工作。
常用的画图工具有Microsoft Visio, draw.io, processon.com 等。
对于追求效率的程序员而言,使用鼠标拖动的效率始终不如通过一项语言来描述图形, 而mermaid正好提供了这种能力帮助我们快速的绘制系统图形, 并且能够很好地将其文档化。

基本介绍

Mermaid是一个基于Javascript的图表和图表工具, 通过定义特定的语法, 自动渲染出一些复杂的软件图形。
该工具在2019年层获得了JS开源奖项中的 “最激动人心的技术”奖项。

目前能够支持的图形类别包括

  • 流程图
  • 时序图
  • 甘特图
  • UML类图
  • E-R图
  • 状态图
  • 饼图

官方主页: https://github.com/mermaid-js/mermaid
官方教程: https://mermaid-js.github.io/mermaid/#/
在线编辑: https://mermaid-js.github.io/mermaid-live-editor

使用范例

流程图

graph LR
B(开始) --> C{进行讨论}
C -->|分支A| D[结果A]
C -->|分支B| E[结果B]
1
2
3
4
graph LR
B(开始) --> C{进行讨论}
C -->|分支A| D[结果A]
C -->|分支B| E[结果B]

时序图

sequenceDiagram
Y->>John: Hello 你好吗?
loop 
    John->>John: 想了一会儿
end
Note right of John: 理性思考!
John-->>Alice: 挺好!
John->>Bob: 那你咋样?
Bob-->>John: 也挺好的
1
2
3
4
5
6
7
8
9
sequenceDiagram
Y->>John: Hello 你好吗?
loop
John->>John: 对抗抑郁症
end
Note right of John: 理性思考!
John-->>Alice: 挺好!
John->>Bob: 那你咋样?
Bob-->>John: 也挺好的

饼状图

pie
  title 语言分布
  "JAVA" : 80
  "Python" : 15
  "Go" : 5

画个甘特图

gantt
section Section
UI设计: des0, 2021-11-01, 1d
概要设计 : des1, 2021-11-01, 1d
后端开发 : des3, after des1, 1d
前端开发 :  des4, after des1, 2d
接口联调 :  des5, after des4, 1d
集成测试 :  des6, after des5, 2d

工具集成

与Hexo集成

1.安装渲染插件

1
npm install hexo-filter-mermaid-diagrams

2.修改配置文件
在根目录下的 _config.yml 文件添加如下配置

1
2
3
4
5
6
7
# mermaid chart
mermaid: ## mermaid url https://github.com/knsv/mermaid
enable: true # default true
version: "8.13.2" # default v7.1.2 (可更改为最新的RELEASE版本号)
options: # find more api options from https://github.com/knsv/mermaid/blob/master/src/mermaidAPI.js
#startOnload: true // default true
theme: forest

3.修改JS文件
在 /themes/next/layout/_partials/footer.swig 添加如下代码片段

1
2
3
4
5
6
7
8
{% if theme.mermaid.enable %}
<script src='https://unpkg.com/mermaid@{{ theme.mermaid.version }}/dist/mermaid.min.js'></script>
<script>
if (window.mermaid) {
mermaid.initialize({{ JSON.stringify(theme.mermaid.options) }});
}
</script>
{% endif %}

与VSCode/Gitlab集成

与VSCode集成的方式也有好多种, 本人使用的是语法高亮插件+Markdown预览的方案, 安装下面两个插件就好了

  • Markdown Preview Mermaid Support
  • Mermaid Markdown Syntax Highlighting

集成到VSCode之后, 就可以通过Git来管理项目中的图形文档了
同时在Gitlab里面也是支持Markdown文件直接渲染出图形的

私有化部署live-editor

参考github主页 https://github.com/mermaid-js/mermaid-live-editor 使用 docker 方式私有化部署

1
docker run --publish 8000:80 ghcr.io/mermaid-js/mermaid-live-editor

小结

更进一步的话,也可以探讨一下怎么跟其他的工具链结合起来使用,例如代码生成/文档图形自动生成等

CentOS中XGBoost安装失败问题

发表于 2020-10-21 | 分类于 个人笔记 | 阅读次数:

执行安装命令后报出各种编译错误

1
pip install xgboost

查看官方文档要求如下

  1. A recent C++ compiler supporting C++11 (g++-5.0 or higher)
  2. CMake 3.13 or higher.

通常使用 yum groupinstall ‘Development Tools’ 安装的gcc为<5.0的版本, 导致代码编译不了, 升级方式如下

1
2
3
4
5
sudo yum install centos-release-scl
sudo yum install devtoolset-7-gcc*
scl enable devtoolset-7 bash
which gcc
gcc --version

安装 cmake 版本 >= 3.13

1
2
sudo yum install epel-release
sudo yum install cmake3

先替换 bash 中的 gcc 版本再执行 pip install

1
2
3
scl enable devtoolset-7 bash
source .../venv/bin/activate
pip install xgboost

SBT设置国内源

发表于 2020-07-25 | 分类于 个人笔记 | 阅读次数:
1
2
3
4
5
6
7
8
[repositories]
local
maven-huawei: https://repo.huaweicloud.com/repository/maven/
ivy-huawei: https://repo.huaweicloud.com/repository/ivy/, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]
typesafe: http://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly
sonatype-oss-releases
maven-central
sonatype-oss-snapshots

添加JVM参数如下

1
2
3
4
# 配置该参数后才能使用REPO镜像
-Dsbt.override.build.repos=true
# (可选)选择配置REPO配置文件路径
-Dsbt.repository.config=<REPO_CONFIG_PATH>

Homebrew更换git源

发表于 2020-07-25 | 分类于 个人笔记 | 阅读次数:
替换USTC源
1
2
3
4
5
6
7
cd "$(brew --repo)"
git remote set-url origin https://mirrors.ustc.edu.cn/brew.git

cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-core.git

export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles
还原默认设置
1
2
3
4
5
cd "$(brew --repo)"
git remote set-url origin https://github.com/Homebrew/brew.git

cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://github.com/Homebrew/homebrew-core.git
替换阿里云的源
1
2
3
4
5
6
7
8
9
10
cd "$(brew --repo)"
git remote set-url origin https://mirrors.aliyun.com/homebrew/brew.git

cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://mirrors.aliyun.com/homebrew/homebrew-core.git

brew update

echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.aliyun.com/homebrew/homebrew-bottles' >> ~/.bash_profile
source ~/.bash_profile

写给运维的Python使用技巧

发表于 2018-04-15 | 分类于 Python | 阅读次数:

前言

Python这门语言应用广泛,不论是大数据,AI,Web开发,运维等领域对Python的应用都有不同的要求,本次分享主要针对Python在运维脚本中的应用介绍一些实战技巧

PEP8 编码规范

PEP8 (Python 代码风格指南),是通过编码风格上的一些要求,用于提高团队中的代码质量,通过规范约束加上静态代码检查(Flake8等),也可避免一些比较低级的坑。

代码编排

主要是终结 tab 跟空格的争论,PEP8要求全部使用空格,在编辑器中设置 tab 自动替换空格即可 (由于解析器的原因,tab跟空格混用可能会出现解释失败的情况)
其他主要影响代码美观性,如空格,空行,注释等等,可参考阅读官方写的示例就好:
https://www.python.org/dev/peps/pep-0008/

变量命名

主要关注一下变量,方法等的命名方式,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 常量大写
USER_CONSTANT = 'xxx'


# 类名: 首字母大小
class UserDetail(object):
def __init__(self):
# 变量名称, 小写加下划线
self.user_name = 'user'
# 私有属性: 双下划线前缀
self.__encrpyped_password = ''

# 函数/方法: 小写加下划线
def query_roles(self):
pass

# 私有方法: 单下划线前缀
def _encrypt_password(self):
pass

代码检查

Flake8 提供了 PEP8 编码规范及代码静态检查等,可集成到 Sublime Text 等工具中(Python Flake8 Lint 插件),或者直接在命令行执行 flake8 xxx.py
静态检查的好处主要是检查,能够在执行代码前发现一些低级的错误,例如使用未定义方法,未定义的变量等

1
2
example.py:35:5: F821 undefined name 'undefined_method'
example.py:36:9: F821 undefined name 'undefined_var'

字符串处理

字符串可能是脚本中是最常用到的对象,指导思想就是不要自己写处理逻辑,尽量使用内建方法,正则表达式,模板格式化等方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 使用占位符格式化
'ps -ef|grep %s' % 'java'

# 使用字典进行格式化
'ps -ef|grep %(keyword)s' % {'keyword': 'java'}

# 使用 format 方法格式化
'ps -ef|grep {keyword}'.format(keyword='java')

# 对于复杂一些的内容可以通过 Jinja2 来完成,例如生成一段 YML
template = jinja2.Template('\
- hosts: {{project.name}}-{{name}}\n\
tasks:\n\
- name: copy captain.jar\n\
copy:\n\
src: /home/ci/captain/captain.jar\n\
dest: {{captain.path}}/\n\
- name: restart captain\n\
shell: bash {{captain.path}}/restart-captain.sh\n\
')

yml = template.render(model)

对于 Jinja2 的使用当然是强烈推荐的,在运维工作中掌握 Jinja2 的使用对于运维自动化的推进非常有帮助的

序列解析及生成表达式

这一部分也是 Python 中处理序列数据时非常强大的一些功能,可以简洁优美地完成一些序列数据的变换,也就是通常所说的 Pythonic

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
# 定义一个生成命令的函数
In [7]: def ps_cmd(x):
...: return 'ps -ef|grep %s' % (x)
...:

In [8]: datas = ['lc-auth', 'lc-cmdb']

# 对所有元素执行方法并生成列表
In [9]: [ps_cmd(x) for x in datas]
Out[9]: ['ps -ef|grep lc-auth', 'ps -ef|grep lc-cmdb']

# 使用 map 方法
In [10]: map(ps_cmd,datas)
Out[10]: ['ps -ef|grep lc-auth', 'ps -ef|grep lc-cmdb']

# 对于这类序列的处理,还需要掌握 map, filter, reduce 这三个函数

# 也可以使用 lambda 表达式来处理
In [11]: map(lambda x: 'ps -ef|grep %s' % x, datas)
Out[11]: ['ps -ef|grep lc-auth', 'ps -ef|grep lc-cmdb']

# 包含 tuple 的列表处理, 将 tuple 转成字典存储结构
In [17]: [{'username': x, 'password': y} for x, y in userlist]
Out[17]:
[{'password': '000000', 'username': 'user'},
{'password': '123456', 'username': 'admin'},
{'password': '123456', 'username': 'ops'}]

# 转换成以用户名作为键值的字典
In [18]: userdatas = [{'username': x, 'password': y} for x, y in userlist]
In [20]: {item['username']:item for item in userdatas}
Out[20]:
{'admin': {'password': '123456', 'username': 'admin'},
'ops': {'password': '123456', 'username': 'ops'},
'user': {'password': '000000', 'username': 'user'}}

# 生成器,可以看到上面的例子中外面都是以 [] 来表达的,这样会直接生成一个列表,但如果改为 () 的话可以创建生成器
In [26]: (ps_cmd(x) for x in datas)
Out[26]: <generator object <genexpr> at 0x7f0ef1339be0>

In [27]: gen = (ps_cmd(x) for x in datas)

In [28]: gen
Out[28]: <generator object <genexpr> at 0x7f0ef1339c80>

In [29]: gen.next()
Out[29]: 'ps -ef|grep lc-auth'

# 生成器也可以通过 for 语句或者 map, filter, reduce 等方法来遍历
# 生成器与列表的区别主要是列表在创建时需要分配内存,而生成器不需要,用来减少内存开销,这一特性通过 yield 关键字来实现
# 上面创建生成器的语句 (ps_cmd(x) for x in datas) 等价于

In [31]: def gen(datas):
....: for x in datas:
....: yield ps_cmd(x)
....:

错误处理

使用 traceback 追踪错误, 例如打印调用栈

1
2
3
4
5
6
7
import traceback

try:
do something
except Exception as ex:
print ex
traceback.print_exc()

了解标准库中异常的层级结构,知道异常的一些分类方法,合理地进行错误处理使程序更健壮
https://docs.python.org/2.7/library/exceptions.html#exception-hierarchy
简化列举一下

1
2
3
4
5
6
7
Exception
StandardError
IOError (I/O 包括文件系统,网络等)
OSError (系统相关)
LookupError (访问数组/字典)
TypeError (方法接收了不合法的类型等)
ValueError (编解码相关)

特殊的场景中也需要自定义异常,需要注意在自定义异常时也需要合理地定义异常的层级及父异常

静态方法和类方法

静态方法和类方法的使用,静态方法的定义通过 staticmethod 装饰器,类方法通过 classmethod 装饰器

常见的一种用法是定义一个工具类, 将一系列的方法归类,和直接使用函数的差别主要是在这些方法有了一个类的命名空间,管理起来不那么混乱

1
2
3
4
5
6
7
8
class AESUtils(object):
@staticmethod
def encrypt(text):
pass

@staticmethod
def decrypt(text):
pass

另外一种场景就是用于设置和该类相关的一些全局配置,环境变量等操作
类方法的使用和静态方法基本类似,区别在于接受的第一个参数是类的class对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Query(object):
DEFAULT_LIMIT = 100

def __init__(self, offset, limit, keyword):
self.offset = 0
self.limit = 0
self.keyword = keyword

@classmethod
def limit(cls, limit):
# 改变类的全局选项
cls.DEFAULT_LIMIT = limit

@classmethod
def from_keyword(cls, keyword):
# 通过 cls 参数来创建一个查询对象
return cls(0, DEFAULT_LIMIT, keyword)

query = Query.from_keyword('test')

使用抽象类及接口

使用 abc 库定义抽象类,可以在Python实例化类的时候检查类是否实现了某些方法,这样可以在一定程度上避免运行时调用接口导致 AttributeError 异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class AbstractProcess(object):
__metaclass__ = abc.ABCMeta

@abc.abstractmethod
def start(self):
'start a process'
return NotImplemented

@abc.abstractmethod
def stop(self):
'stop a process'
return NotImplemented


class SpringbootProcess(AbstractProcess):
def __init__(self, name):
self.name = name

# 这样就对 AbstractProcess 的子类进行了约束, 没有实现 start, stop 方法的类不能被实例化
Traceback (most recent call last):
File "example.py", line 97, in <module>
proc = SpringbootProcess('xxx')
TypeError: Can't instantiate abstract class SpringbootProcess with abstract methods start, stop

Mixin模式

Python中的类可以多重继承,但在面向对象原则上在继承的使用上必须保持单一继承关系,也就是一个类只能继承一个父类。
类比其他编程语言中的类可以实现多个接口,Python中的多重继承可以通过Mixin模式为类添加多种功能。

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
# 序列化并且通过Redis读取
class RedisMixin:
def redis_key(self):
raise NotImplemented

@classmethod
def from_redis(cls, key):
r = redis.Redis(host='192.168.100.101', port=6379)
data = r.get(key)
return pickle.loads(data)

def to_redis(self):
r = redis.Redis(host='192.168.100.101', port=6379)
data = pickle.dumps(self)
r.set(self.redis_key, data)


# 创建用户类, 并提供序列化到Redis的功能
class User(RedisMixin):
def __init__(self, name, email):
self.name = name
self.email = email

def redis_key(self):
return 'username:%s' % (self.name)

user = User('user', 'user@xiaopeng.com')
print(user.to_redis())

user = User.from_redis('username:user')
print(user.name, user.email)

装饰器

装饰器的应用主要在一些开发框架中,例如 Web 框架中的路由

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
class Application(object):
def __init__(self):
self.router_mapper = {}

# 这里定义装饰器, 将URI注册到对象中
def route(self, path):
def decorator(callfunc):
self.router_mapper[path] = callfunc
return callfunc

return decorator

def dispatch(self, uri, kwargs={}):
return self.router_mapper[uri](**kwargs)

if __name__ == '__main__':
app = Application()

@app.route('/hello')
def hello():
return 'hello'

@app.route('/echo')
def echo(message):
return 'echo %s' % (message)

print(app.dispatch('/hello'))
print(app.dispatch('/echo', {'message': 'test'}))

大家也可以拓宽思路, 将装饰器其使用在脚本里或者开发一些公共的运维模块

Redis学习笔记

发表于 2015-04-03 | 分类于 个人笔记 | 阅读次数:

现在NoSQL也是特别热门的技术关键词了,近来整理了一些Redis的东西

数据对象与键空间

Redis支持的数据类型比较多: 字符串,列表,哈希表,集合,有序集合

1
2
3
4
5
6
7
8
# 对字符串进行操作
redis> set message "hello world"
# 对列表进行操作
redis> rpush alphabet "a" "b" "c"
# 对哈希表进行操作
redis> hset book name "Redis in action"
redis> hset book author "Josiah L. Carlson"
redis> hset book publisher "Manning"

像上面的操作,在Redis的某个键空间里有三个键值分别是message, alphabet, book,指针分别指向不同的存储对象

而在book所指向的哈希表对象里面,又有它自己的键空间: name, author, publisher 指向字符串对象

在哈希表对象的键值,可以嵌套其他的数据对象,列表,集合,甚至再嵌套一个哈希表在里面

过期键的处理

在Redis里面可以对存储对象设置一个过期时间,当超过一定时间后,Redis会将这个键从数据库中自动删除

1
2
3
4
5
6
7
8
9
# 设置10秒的过期时间
127.0.0.1:6379> expire msg 10
(integer) 1
# 进行查询
127.0.0.1:6379> get msg
"wait expired"
# 等待几秒后再次查询
127.0.0.1:6379> get msg
(nil)

RDB对过期键的处理策略

生成RDB文件时对过期键的处理:

执行SAVE或者BGSAVE操作时,会将数据库保存至RDB文件中

Redis会对内存中的Key进行检查,已经过期的Key不会被保存到RDB文件中

载入RDB文件时对过期键的处理:

如果以主模式载入,则过期键不会被载入到内存数据库中

如果以从模式载入,过期键先会被载入到内存数据库中,而后主从同步时才对过期键进行清空

AOF对过期键的处理

当过期键被删除后,向AOF追加一条DEL命令用于标志已删除

如果客户端通过GET message访问一个过期键

1.服务器先删除message键

2.追加一条DEL message到AOF文件

3.返回空给客户端

和RDB相似,重写时会先对内存中的Key进行检查以确认是否写入AOF文件

RDB持久化方式

默认保存在dump.rdb文件中(/var/lib/redis/dump.rdb)
可以根据配置文件修改
dbfilename dump.rdb

SAVE和BGSAVE

SAVE: 由主进程直接写RDB,直到文件创建完毕(主进程阻塞)

BGSAVE: 后台保存, 执行一次fork,令子进程写RDB文件

这两种保存方式都可理解为是服务器被动保存,也就是服务器在接受到客户端发来的上面两条命令之后进行保存

自动保存的实现

在/etc/redis/redis.conf配置文件中定义保存条件

1
2
3
4
save <seconds> <changes>
save 900 1
save 300 10
save 60 10000

在seconds时间内 如果发生changes以上次修改时进行自动保存

1
2
3
4
struct redisServer {
long long dirty;
……
};

在源码实现中,使用redisServer定义了一个全局变量server,其中有个dirty变量是用来记录修改次数的

那么在实现具体数据结构时,在操作改写存储对象的命令里面,就需要对dirty这个变量进行修改了,通常都是
执行server.dirty++的操作来进行,但是如果是对列表插入了两个元素这种情况,就要执行server.dirty+=n了

在源码实现中,serverCron是一个时间事件处理器(100ms执行一次),在这里面就需要对配种中定义的保存条件
进行检查操作,如果满足条件时,Redis将创建一个子进程进行保存操作,在执行保存期间,如果客户端发过来保存
命令的话,服务器先检查到有一个子进程在进行保存操作,就不会处理这条命令了

AOF持久化方式

相关的配置选项

1
2
3
4
5
6
7
8
9
10
#是否设置为AOF持久化方式
appendonly yes
#默认的文件名
appendfilename "appendonly.aof"
#将aof_buf缓冲区中的所有内容写入并同步到 AOF 文件
appendfsync always
#将aof_buf缓冲区中的所有内容写入到 AOF 文件, 每秒进行同步
appendfsync everysec
#将aof_buf缓冲区中的所有内容写入到 AOF 文件, 由操作系统决定同步
appendfsync no

aof_buf是一个缓冲区,如果工作在AOF模式下,服务器执行命令时会将该命令先写入缓冲区中,如果缓冲区满了
就将缓冲区的数据写入到AOF文件中,此时再清空缓冲区,继续执行其他命令,如此下去…

由于AOF根据每条命令生成存储记录,因而一个某个数据被重复修改时造成了数据冗余,其中一种
解决方案时定期根据内存中的值进行重写,同时也将过期键过滤掉,这一操作由后台进程运行

AOF重写

为了避免重写AOF主进程阻塞的情况,AOF将这一操作放在后台运行

1.创建子进程重写AOF,同时创建AOF缓存

2.子进程写AOF,主进程继续执行请求,同时写入AOF缓存

3.主进程检查子进程状态,子进程写结束后,主进程将AOF缓存追加到AOF文件中

AOF重写的触发条件

1
2
3
4
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
#min-size 表示最小大小,当AOF文件大于这个值时进行第一次重写操作
#percentage表示增量(默认100),当AOF文件增量超过这个值时触发一次重写操作

复制

Redis服务器可以复制一个主服务器作为备份的从服务器,复制后的从服务器对客户端而言是只读的
(实际上它只接受主服务器通过命令传播发过来的写操作)

通过下面的一条命令即可完成复制

1
slaveof ip port

同步:将主服务器数据库状态更新到从服务器

命令传播:将主服务器的变更操作更新到从服务器(解决数据不一致问题)

同步过程大概可以描述如下

1.从服务器发送同步命令(SYNC)给主服务器

2.主服务器创建子进程发送RDB文件给从服务器,同时继续执行其他命令(保存在缓冲区)

3.主服务器将缓冲区中的所有写命令发送给从服务器

这一过程非常类似于AOF重写的过程

在完成同步后,其他到操作就通过命令传播来进行,就是当主服务器发生变更时,也需要将该条
写命令发送给从服务器去执行

部分重同步

从服务器可以在主从服务器之间的连接断开时进行自动重连,在2.8版本前,断线之后重连的从服务
器总要执行一次完整重同步, 2.8以后的版本中从服务器可以根据主服务器的情况来选择执行完整
重同步还是部分重同步

(主服务器ID,复制便宜量OFFSET) 记录在主服务器中

(主服务器ID) 从服务器检查连接的服务器是否和这个ID一致,如果是的话则通知主服务器从OFFSET开始复制数据

Sentinel故障迁移

Sentinel可以监控服务器的运行状态,并且支持自动选取一个从服务器作为主服务器

启动方式

1
2
redis-sentinel /path/to/sentinel.conf
redis-server /path/to/sentinel.conf --sentinel

也可以在一台机器上演示这个故障迁移的过程,默认的redis服务器作为主服务器,同时修改配置文件启动一个
工作在6380端口的服务器复制主服务器作为从服务器

然后写一个最简单sentinel配置

1
2
3
4
5
6
7
8
9
sentinel monitor master 127.0.0.1 6379 1
sentinel down-after-milliseconds master 60000
sentinel failover-timeout master 90000
sentinel parallel-syncs master 1

sentinel monitor resque 127.0.0.1 6380 1
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 1

这样先启动主服务器, 再启动复制主服务器的从服务器,然后启动sentinel进行监控; 这时候用kill将主服务器
关掉,就可以观察到6380的从服务器切换成服务器了,之后再启动6379端口的服务器,它也已经作为从服务器存在了

使用装饰器包装多线程程序

发表于 2015-03-16 | 分类于 Python | 阅读次数:

Python中的装饰器的实质上是一个函数,它以其他函数作为参数来传递

在使用装饰器语法来定义一个函数的时候

1
2
3
@Decorator
def function()
...

实际上这个装饰器会对function函数进行一次调用

拆开来就是这样子了

1
2
3
def function()
...
Decorator(function)

那么它有什么作用呢,现在看一个在多线程使用的例子

这里主要是利用装饰器对线程进行初始化,通过消息队列来传递参数

那么我用到这个装饰器来定义一个函数时,那么它就是由线程去执行的,我就不用关心线程的初始化动作了

1
2
3
4
queue = Queue.Queue(10)
@Thread(queue, 2)
def testThread(a, b):
print 'a+b=%s'%(a+b)

上面的代码我定义了一个测试线程,首先我创建了一个消息队列,然后描述线程的行为

在这里Thread(queue, 2)的意义是线程通过queue来获取参数,程序会创建2个线程去执行下面的代码

1
2
3
4
5
6
7
8
9
10
11
def Thread(Q, threads = 1, interval = 1):
def decorator(callfunc):
def process():
while True:
kwargs = Q.get()
print 'Thread %s Run...' % (thread.get_ident())
json = callfunc(**kwargs)
time.sleep(interval)
for i in range(0, threads):
thread.start_new_thread(process, ())
return decorator

现在看装饰器的代码

有三个参数Q,thread,interval分别意味着消息队列,线程数,执行间隔

这些变量可以简单地用来描述一段代码令其多线程执行

1.这边返回值是一个名为decorator的函数,也就是所谓的装饰器(他的参数是被装饰的那个函数callfunc)

2.然后还需对其处理一下,定义一个process的嵌套函数,这是一个无穷循环(也就是线程里执行的函数)

3.先通过Q从消息队列里抓取参数,如果有参数就调用callfunc去执行(在这里应该加入一个线程的终止条件的,我已经省略了), 在这里线程程序就完成了一次函数调用了,然后用sleep将自己挂起一定时间,当然也可以设置为0

4.在装饰器的最后,需要对线程进行创建,创建后消息队列是空的,那么每个线程就自动将自己设置成挂起状态了

1
2
3
4
5
6
7
8
9
10
11
@Thread(queue, 2)
def testThread(a, b):
print 'a+b=%s'%(a+b)

def producer():
for i in range(0, 10):
queue.put({'a': i, 'b': i*2})
time.sleep(10)

if __name__ == '__main__':
producer()

回到主程序的代码,现在已经定义了testThread是一段由两个线程去执行它的代码

后面定义了一个生产者程序,往消息队列里放东西,这样在生产者运行后,消费者线程也就从消息队列中获取到参数去处理了

Python中的自省和反射

发表于 2015-01-31 | 分类于 Python | 阅读次数:

WHAT

按照本人的理解,自省和反射机制是一个相似的概念,就拿反射来说,可能我在编程中遇到了这么一种情况:我(操作者)不知道需要操作的对象的数据结构和方法是怎么样的,但是我可以通过字符串的形式来令某个对象去调用某个方法,这就是一个简单的反射例子。 那么自省呢,我认为它是编程语言中的一个对象,它事先可以告诉我(或者说我可以通过某个方法了解到)它的结构是怎么样的,有哪些操作方法,然后我再去调用它,这就对编程者提供了一种很灵活的控制能力,也或者说它为实现反射提供了一种支持…

Python中的自省

灵活的自省和反射机制也是Python的一个重要的优点之一,编程者可以通过一些内建函数和方法实现对Python虚拟机层面的一些操作,因为默认情况下我们是通过写好的代码来对对象进行操作的,而对于虚拟机来说,代码就是被编译解释的字符串,然而Python虚拟机提供了可以直接通过字符串来操作对象的接口,因而就具备了良好的自省和反射。

相关的内建函数

1
2
3
4
5
6
7
8
class Student:
def __init__(self, ID, name, age):
self.ID = ID
self.name = name
self.age = age

def say(self):
print 'I am a Student'

这边定义了一个Student类,后面来对它进行自省和反射的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> ss = Student(0,'Li Lei',20)
>>> dir(Student)
['__doc__', '__init__', '__module__', 'say']
>>> dir(ss)
['ID', '__doc__', '__init__', '__module__', 'age', 'name', 'say']

>>> type(ss)
<type 'instance'>
>>> type(Student)
<type 'classobj'>

>>> isinstance(ss,Student)
True

这边创建了一个Student的实例,然后我两次分别通过dir()对类和实例进行查看,从输出结果可以看到我收集到了关于这个类和第一个实例的部分属性名,通过type()内建函数也可以看到它们一个是instance类型,一个是classobj类型,通过isinstance()也可以判断一个对象是否是某个类创建的实例

从上面的dir()看到的属性也只是一部分,还有些是隐藏的,比如__class__和__dict__

1
2
3
4
5
6
7
8
>>> ss.__class__
<class student.Student at 0x7f55ef61b328>
>>> type(ss.__class__)
<type 'classobj'>
>>> ss.__dict__
{'age': 20, 'ID': 0, 'name': 'Li Lei'}
>>> ss.__class__.__dict__
{'__module__': 'student', 'say': <function say at 0x7f55ef625de8>, '__init__': <function __init__ at 0x7f55ef625d70>, '__doc__': None}

现在可以看出,__class__属性就是该对象的类(classobj)

实例(instance)和类(classobj)的属性都是通过__dict__这个字典来维护的

实例(instance)的__dict__里维护了学生的信息,也就是我在__init__()里定义的东西,这些东西在创建实例的时候被加到字典里

类(classobj)的__dict__维护了相关的方法,__init__和我自己定义的say()

这也就说明了对象方法的一个调用顺序

比如我执行ss.say()的时候,是从类(classobj)里找到这个方法,然后解释器再把自己通过self传入去执行

当然,我也有更奇葩的一种写法:

1
2
3
4
>>> ss.__class__.__dict__['say']()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: say() takes exactly 1 argument (0 given)

这样就报错了,因为我没有将具体的实例(instance)作为参数传入

1
2
>>> ss.__class__.__dict__['say'](ss)
I am a Student

现在将ss作为实例传进去,那么就执行成功了,这边的’say’我是通过字符串传入的,也就是完成动态调用的反射了

然后我要给Student加一个自杀的操作:

1
2
3
4
5
6
7
>>> def selfkill(s):
... print 'I going to kill myself, and my name is %s'%(s.name)
...
>>> Student.__dict__['selfkill']=selfkill
>>>
>>> ss.selfkill()
I going to kill myself, and my name is Li Lei

这边我新建了一个函数,打印一行信息,并且取了name这个属性,把它放到Student的字典里

然后我用ss.selfkill()就可以调用它了,这样就完成了动态添加属性的反射了

但是没有人会在编程时这么做(如果我的同事这样写代码,我会打死他的)

通用的做法是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> hasattr(ss, 'say')
True
>>> getattr(ss, 'say')()
I am a Student
>>> hasattr(ss, 'sing')
False

>>> def wrapper(s):
... def sing():
... print 'My name is %s'%(s.name)
... return sing
...
>>> setattr(ss, 'sing', wrapper(ss))
>>> ss.sing()
My name is Li Lei
>>> hh = Student(2,"Han MeiMei",20)
>>> hasattr(hh,'sing')
False

1.通过hasattr(x, attr)函数检查某个对象x是否有某个属性attr

2.通过getattr(x, attr)来读某个x对象的属性attr,如果是函数方法,就得加上括号和参数了getattr(x,attr)()

3.在上面的代码中通过包装器wrapper(s)来创建sing()这个方法,包装器在这边的作用就相当与是帮助传入self参数

4.后面创建的另一个学生韩梅梅(hh)就没有sing()这个方法,因为刚刚的sing()是动态为李雷(ss)这个实例添加的,当然也可以为Student这个类加入一个属性,那么每个新创建的实例都能调用sing()这个方法了,这里也不介绍了

其他

需要区分的几个概念,因为Python虚拟机需要维护一个用来记录用户自定义类(class)的classobject, 因而需要理清从虚拟机到用户代码的这四个层次关系:

用户类(class)的类型: classobject [虚拟机(typeobject)]

用户类(class)的类的实例: 一个由classobject创建的实例, Student类 [虚拟机(typeobject)]

用户类: Student [用户代码]

用户类的实例: 由Student创建的实例 [用户代码]

这部分内容可以阅读Python相关源代码的具体实现,看懂部分后就对这个机制有个很明了的理解啦

Python多线程编程学习之基础篇

发表于 2014-12-21 | 分类于 Python | 阅读次数:

主要介绍Python使用threading模块进行多线程编程的例子,以及一个简单的线程池的实现思路。

具体可以参考该模块的官方文档

##线程的创建和使用、锁、条件变量##

线程创建

Python的threading模块提供了两种创建线程的方法

方法一是直接使用threading.Thread()来创建,在这种创建方式中,创建Thread()对象时需要指定该线程准备执行的函数,然后用
*args与**kwargs来传递参数,其中*args传递一个tuple类型的参数,**kwargs传递字典类型的参数表

1
2
3
4
5
6
7
8
def thread_print_args(*args, **kwargs):
for i in range(0, 10):
print args, kwargs
time.sleep(0.1)

def thread_create_test():
threading.Thread(None, thread_print_args, args = ('a1','a2'), kwargs = {'name': 'test'}).start()
threading.Thread(None, thread_print_args, args = ('b1','b2'), kwargs = {'name': 'hello'}).start()

方法二是通过自定义线程类继承threading.Thread类来创建,需要重载run()方法,在run()方法中设置线程要跑的函数就行了

1
2
3
4
5
6
7
8
9
class ThreadPrint(threading.Thread):
def run(self):
for i in range(0, 10):
print self.name
time.sleep(0.1)

def thread_create_test():
ThreadPrint().start()
ThreadPrint().start()

可以参看threading.py文件中的源码,其实run()方法就是线程调度运行时要执行的函数,默认的Thread类里也是
通过self.__target来调用相应的函数,只是在构造对象时创建了这个属性

另外,不论使用哪种方式创建了线程,线程在run()方法执行完毕之后自动退出

使用互斥锁

互斥锁的用途是用于控制线程间共有资源的访问,实现原子操作,也不多介绍了

在Python中互斥锁被封装在threading.Lock这个类中,具有请求和释放两个方法

这边的例子是创建2个线程使用互斥锁对全局的count进行累加的操作,两个线程有不同的访问间隔:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mutex = threading.Lock()
count = 0
class ThreadLockTest(threading.Thread):
def __init__(self, interval):
threading.Thread.__init__(self)
self.interval = interval
def run(self):
global count
while count < 100:
print "%s acquires..." % (self.name)
mutex.acquire()
print "%s enter..." % (self.name)
count = count + 1
print '%s : %d' %(self.name, count)
time.sleep(self.interval)
mutex.release()
print "%s exit..." % (self.name)


def thread_lock_test():
ThreadLockTest(0.1).start()
ThreadLockTest(0.3).start()

该例子也只是简要地说明锁的使用方式,在实际的多线程编程中使用锁时,还得注意死锁的避免,一个简单的原则就是:保证线程都按相同的次序来持有锁变量

使用条件变量

条件变量是用于线程的同步的,简单地说就是去操作线程’挂起等待-唤醒’这一过程

Python中条件变量封装在threading.Condition这个类中,wait()方法用于阻塞等待,notify()方法用于唤醒,需要注意到的是对于条件变量的访问是原子的,因而条件变量也含有了acquire(),release()这两个方法,在使用wait(),notify()之前就需要先请求锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
condition = threading.Condition()
class ThreadCondTest(threading.Thread):
def run(self):
print "%s runs.." %(self.name)
condition.acquire()
print "%s wait.." %(self.name)
condition.wait()
condition.release()
for i in range(0, 5):
print "Hi, I'm runs now"

def thread_cond_test():
ThreadCondTest().start()
time.sleep(3)
condition.acquire()
condition.notify()
condition.release()

在这个例子中,线程创建启动后马上将自己阻塞等待,直到主程序time.sleep(3)之后将其唤醒后继续执行后面的代码

##一个简单线程池的实现思路##

实际的多线程编程中,为了避免大量的创建和销毁线程,就需要建立一个线程池来辅助管理和回收利用这些线程来达到程序优化的目的;然而在Python中,相比与Linux下用C/C++来实现一个线程池的话,编程的复杂度就简单得多了;通常一个完整的线程池由线程池需要有线程队列和工作队列两个部分组成,这边简要介绍一个线程队列的实现思路作抛砖引玉用,当然如果要设计成通用的模块和API的话还是有大量的工作要做的

Python中的Queue模块已经提供了一个多线程环境下的队列应用(内部用互斥锁已经实现了原子访问),可以直接拿起来用,就不必考虑设计队列时的互斥和同步问题了

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
MAX_THREADS = 4

class TestThread(threading.Thread):
def __init__(self, poll, cond, num):
threading.Thread.__init__(self)
self.poll = poll
self.task = None
self.task_args = None
self.cond = cond
self.threadID = num

def run(self):
while True:
print 'Thread: %s wait now...'%(self.getName())
self.cond.acquire()
self.poll.threads.put(self)
self.cond.wait()
self.cond.release()
print 'Thread: %s run now...'%(self.getName())

if self.task != None:
print 'Call Function : %s(%s)' %(self.task, self.task_args)
self.task(self.task_args)
del self.task, self.task_args


class ThreadPoll():
def __init__(self, n):
self.threads = Queue.Queue(n)
self.maxsize = n
self.__init__threads()

def __init__threads(self):
for i in range(0, self.maxsize):
thr = TestThread(poll = self, cond = threading.Condition(), num = i)
thr.start()

def dispatch(self, func, arg):
thr = self.threads.get()
thr.task = func
thr.task_args = arg
thr.cond.acquire()
thr.cond.notify()
thr.cond.release()
return thr.threadID

def thread_print(s):
for i in range(0, 10):
print 'Thread: %s' % (s)
time.sleep(0.1)

def thread_poll_test():
threadpoll = ThreadPoll(MAX_THREADS)
threadpoll.dispatch(thread_print, 'Test-1')
threadpoll.dispatch(thread_print, 'Test-2')
threadpoll.dispatch(thread_print, 'Test-3')
threadpoll.dispatch(thread_print, 'Test-4')

threadpoll.dispatch(thread_print, 'Test-1')
threadpoll.dispatch(thread_print, 'Test-2')
threadpoll.dispatch(thread_print, 'Test-3')
threadpoll.dispatch(thread_print, 'Test-4')

TestThread类是线程池使用的线程类,其中task属性用于设置线程将要完成的任务,默认为空,使用时通过线程池的dispatch()来自动分派,
run()函数里是一个无穷循环,初始化时首先将自己阻塞挂起,之后放回到线程池中,统一由线程池进行管理。

ThreadPoll类是线程池的管理器,提供dispatch()来分派将要进行的任务给正在等待的线程,初始化时先创建N个线程,它们都统一阻塞自己放回到线程池中,如果有一个新的任务要做时,就通过dispatch()去分派,先是从队列中取出一个线程,给他重新设置task属性和参数,之后将其唤醒,那么该线程就会去执行这个工作,待它执行完之后又回到主循环,将自己放回线程池和进入等待状态。

进一步的工作还需要考虑工作队列模块,例如当一个新的task来的时候,如果线程池中没有空闲的线程池,那么就得先将该任务放进工作队列缓冲区中,而线程池中一旦有空闲线程,就得从工作队列中拉出一个task来处理

Python源码分析:对象

发表于 2014-08-24 | 分类于 Python | 阅读次数:

首先要知道Python中的所有数据类型都是以对象来实现的,然而这个PyObject相当于是一个最顶层的抽象对象,它用来维护和操作Python中所有类型对象共同的信息和功能部分
描述对象的代码在object.h和object.c中可以看到~

1
2
3
typedef struct _object {
PyObject_HEAD
} PyObject;

如这段代码中看到的,定义了一个对象的结构体
实际上并不会去以PyObject来声明一个对象(相当于是面向对象语言中的抽象类型)
但是任何对象的指针都可以转换成PyObject *,这相当与是用C语言手纯手写出来的继承关系

Python内部所有对象的实现的源码都位于Objects目录下:object.c, intobject.c, listobject.c, typeobject.c 等等
看Objects目录下面的代码会发现:
每个对象的结构体里面都含有PyObject_HEAD这个宏
实际上这个宏展开后是

1
2
3
4
5
/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD \
_PyObject_HEAD_EXTRA \
Py_ssize_t ob_refcnt; \
struct _typeobject *ob_type;

其中ob_refcnt记录了对象的引用计数信息(Python的使用了引用计数算法来实现垃圾收集)

ob_type是一个_typeobject结构体(PyTypeObject,属于内部的特殊类型对象),其中保存了对象的类型名称、需要分配的内存大小,对象的操作方法表(tp_methods),以及一系列关于该对象的操作行为的函数指针(比如dealloc, print, compare等等)
那么Python在创建对象时就会用到这个全局变量了,把这个全局变量指针赋值给ob_type这个成员,至此一个Object就标记上了相应的‘身份’了,至于具体的每个对象如何去创建和删除,就可以去阅读相应类型的源码了(Objects目录下)

例如listobject.c中会创建一个PyList_Type全局变量(类似的在其他类型对象的.c文件中也会创建相应的全局变量)

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
PyTypeObject PyList_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"list",
sizeof(PyListObject),
0,
(destructor)list_dealloc, /* tp_dealloc */
(printfunc)list_print, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
(reprfunc)list_repr, /* tp_repr */
0, /* tp_as_number */
&list_as_sequence, /* tp_as_sequence */
&list_as_mapping, /* tp_as_mapping */
(hashfunc)PyObject_HashNotImplemented, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LIST_SUBCLASS, /* tp_flags */
list_doc, /* tp_doc */
(traverseproc)list_traverse, /* tp_traverse */
(inquiry)list_clear, /* tp_clear */
list_richcompare, /* tp_richcompare */
0, /* tp_weaklistoffset */
list_iter, /* tp_iter */
0, /* tp_iternext */
list_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)list_init, /* tp_init */
PyType_GenericAlloc, /* tp_alloc */
PyType_GenericNew, /* tp_new */
PyObject_GC_Del, /* tp_free */
};

通过PyObject*指针访问这个成员不仅可以访问到对象的类型,并可以通过它访问ob_type之后对对象进行具体的操作,而具体操作的实现则由每个对象类型具体的代码中去做;实际上就相当于面向对象中抽象类和每个子类的关系,但是在C语言里面用了指针去实现的话阅读起来就显得复杂一些。

比如析构函数的例子:

1
2
3
4
5
6
7
void
_Py_Dealloc(PyObject *op)
{
destructor dealloc = Py_TYPE(op)->tp_dealloc;
_Py_ForgetReference(op);
(*dealloc)(op);
}

在这边就是使用 Py_TYPE(op)->tp_dealloc来获取具体的析构函数指针(Py_TYPE宏是用来访问成员ob_type的)
​
变长对象:

1
2
3
typedef struct {
PyObject_VAR_HEAD
} PyVarObject;

跟上面PyObject不同的是不同的变长对象占用的内存大小是不一样的,比如不同的list对象里元素个数是不一样的

1
2
3
4
#define PyObject_VAR_HEAD               \
PyObject_HEAD \
Py_ssize_t ob_size; /* Number of items in variable part */
#define Py_INVALID_SIZE (Py_ssize_t)-1

PyObject_VAR_HEAD这个宏里面就是在PyObject_HEAD的基础上加上ob_size来描述变长对象的长度。

对比intobject和listobject的代码发现:
定义PyIntObject对象使用的是PyObject_HEAD这个宏,说明它是定长的(这边要注意区分普通整型数int和Python中的大整数的区别,大整数是基于变长对象来实现的),定义PyListObject对象使用的是PyObject_VAR_HEAD这个宏,说明它是变长的

12…6

sizeoftank

54 日志
3 分类
GitHub Weibo
© 2021 sizeoftank
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4