在数学概念中,变量(Variable)表示没有固定值,可改变的数。但从计算机系统实现角度来看,变量是一段或多段用来存储数据的内存。

编程语言允许你定义变量(variable)。所谓变量就是在程序中为了方便地引用内存中的值而为它取的名称。在 Python 中,我们用 = 来给一个变量赋值。

我们先来声明一个变量

1
2
3
>>> a = 7
>>> print(a)
7

Python 中的变量有一个非常重要的性质:它仅仅是一个名字。赋值操作并不会实际 复制值,它只是为数据对象取个相关的名字。名字是对对象的引用而不是对象本身。你可以把名字想象成贴在盒子上的标签

1
2
3
4
变量                         内存
+-------------------+
a +------------> | 7 |
+-------------------+

在Python中使用变量时,需要遵守一些规则和指南。违反这些规则将引发错误,而指南旨在 让你编写的代码更容易阅读和理解。请务必牢记下述有关变量的规则。

  • 变量名只能包含字母、数字和下划线。变量名可以字母或下划线打头,但不能以数字打 头,例如,可将变量命名为message_1,但不能将其命名为1_message。
  • 变量名不能包含空格,但可使用下划线来分隔其中的单词。例如,变量名greeting_message 可行,但变量名greeting message会引发错误。
  • 不要将Python关键字和函数名用作变量名,即不要使用Python保留用于特殊用途的单词

Python 关键字 下面的关键字都有特殊含义,如果你将它们用作变量名,将引发错误:

Python 内置函数 将内置函数名用作变量名时,不会导致错误,但将覆盖这些函数的行为:

注意 在Python2.7中,print是关键字而不是函数。另外,Python3没有内置函数unicode()。这 两个单词都不应用作变量名。

  • 变量名应既简短又具有描述性。例如,name比n好,student_name比s_n好,name_length 比length_of_persons_name好。
  • 慎用小写字母l和大写字母O,因为它们可能被人错看成数字1和0。 要创建良好的变量名,需要经过一定的实践,在程序复杂而有趣时尤其如此。随着你编写的程序越来越多,并开始阅读别人编写的代码,将越来越善于创建有意义的变量名。

1.1.2 列表

列表由一系列按特定顺序排列的元素组成。你可以创建包含字母表中所有字母、数字0~9或 所有家庭成员姓名的列表;也可以将任何东西加入列表中,其中的元素之间可以没有任何关系。 鉴于列表通常包含多个元素,给列表指定一个表示复数的名称(如names)是个不错的主意。

列表非常适合利用顺序和位置定位某一元素,尤其是当元素的顺序或内容经常发生改变时。与字符串不同,列表是可变的。你可以直接对原始列表进行修改:添加新元素、删除 或覆盖已有元素。在列表中,具有相同值的元素允许出现多次。

常用列表方法

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
>>> dir(list)

['__add__',
'__class__',
'__contains__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__iadd__',
'__imul__',
'__init__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__reversed__',
'__rmul__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'append',
'clear',
'copy',
'count',
'extend',
'index',
'insert',
'pop',
'remove',
'reverse',
'sort']

append这个是向列表末尾追加一个元素,如下:

1
2
3
4
>>> numbers = [1,2,2,3,3,3]
>>> numbers.append(4)
>>> numbers
[1, 2, 2, 3, 3, 3, 4]

clear 清除一个列表中的所有元素

1
2
3
4
5
6
7
>>> n
[1, 2, 3, 4, 5]

>>> n.clear()

>>> n
[]

copy 复制一个列表

1
2
3
4
5
6
7
8
9
10
11
12
>>> n = [1,2,3,4,5]

>>> n.copy()
[1, 2, 3, 4, 5]

>>> n.copy()
[1, 2, 3, 4, 5]

>>> num = n.copy()

>>> num
[1, 2, 3, 4, 5]

count 计算某个元素在列表中出现的次数,如下:

1
2
3
4
5
6
7
8
9
10
>>> numbers
[1, 2, 2, 3, 3, 3, 4]
>>> numbers.count(1)
1
>>> numbers.count(2)
2
>>> numbers.count(3)
3
>>> numbers.count(4)
1

extend 直接向列表末尾一次性追加另一个列表,如下:

1
2
3
4
5
6
7
8
>>> numbers
[1, 2, 2, 3, 3, 3, 4]
>>> a = [4,4,4]
>>> numbers.extend(a)
>>> numbers
[1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
>>> a
[4, 4, 4]

index 从列表中找出某个值第一个匹配的索引位置

1
2
3
4
5
6
7
8
9
10
>>> numbers
[1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
>>> numbers.index(1)
0
>>> numbers.index(2)
1
>>> numbers.index(3)
3
>>> numbers.index(4)
6

insert 将对象插入列表中

1
2
3
4
5
>>> numbers
[1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
>>> numbers.insert(0,0)
>>> numbers
[0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4]

pop 移除列表中的最后一个元素,默认是最后一个

1
2
3
4
5
6
>>> numbers
[0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
>>> numbers.pop()
4
>>> numbers
[0, 1, 2, 2, 3, 3, 3, 4, 4, 4]

remove 移除列表中第一个匹配的元素

1
2
3
4
5
6
7
8
>>> num
[1, 2, 1, 3, 2]
>>> num.remove(1)
>>> num
[2, 1, 3, 2]
>>> num.remove(2)
>>> num
[1, 3, 2]

reverse 将列表中的元素反向存放

1
2
3
4
5
>>> numbers
[0, 1, 2, 2, 3, 3, 3, 4, 4, 4]
>>> numbers.reverse()
>>> numbers
[4, 4, 4, 3, 3, 3, 2, 2, 1, 0]

sort 对列表排序,这个我比较喜欢,里面挺多好玩的东西,下面做个简单的介绍

默认排序:

1
2
3
4
>>> L = [1,4,3,2]
>>> L.sort()
>>> L
[1, 2, 3, 4]

自定义排序:

查看sort的使用方法

1
2
>>> L.sort.__doc__
'L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*'

根据提供的key函数为元素产生一个键,列表的元素按照这个键值来排序

1
2
3
4
>>> x = ['abc','a','bc','abcd']
>>> x.sort(key=len)
>>> x
['a', 'bc', 'abc', 'abcd’]

反向排序

1
2
3
4
>>> n = [3, 1, 2, 5]
>>> n.sort(reverse=True)
>>> n
[5, 3, 2, 1]

  • python教程

操作系统:mac OSX 10.12 或 Ubuntu 16.04
编辑器: vim、 sublime、atom、PyCharm
要求会翻墙

操作系统

下载 Ubuntu
http://www.ubuntu.org.cn/download/desktop

虚拟机

如果不想直接安装Ubuntu,建议使用虚拟机安装

virtualbox

https://www.virtualbox.org/wiki/Downloads
下载完后安装

python环境

版本: python3.5.2

安装工具

1. pyenv

安装 pyenv

linux:

1
2
3
4
5
$ git clone git://github.com/yyuu/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"'>> ~/.bashrc # 指明环境变量
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"'>> ~/.bashrc
$ echo 'eval"$(pyenv init -)"' >> ~/.bashrc # 开启shims and autocompletion
$ exec $SHELL -l # 重新启动shell让其生效

mac:

1
2
3
4
$ brew update
$ brew install pyenv //安装
$ brew upgrade pyenv //升级
$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile //只需要执行一次即可

查看可安装的版本

1
$ pyenv install --list

安装指定版本

1
$ pyenv install 3.5.2 -v

更新数据库

1
$ pyenv rehash

查看当前已安装的python版本

1
2
3
$ pyenv versions 
* system (set by /Users/ce/workspace/sohu/.python-version)
3.5.2

设置全局的python版本

1
2
3
4
$ pyenv global 3.5.2
$ pyenv versions
system
* 3.5.1 (set by /Users/ce/workspace/sohu/.python-version)

2. virtualenvwrapper

linux

1
2
$ pip install virtualenvwrapper
$ git clone https://github.com/yyuu/pyenv-virtualenvwrapper.git ~/.pyenv/plugins/pyenv-virtualenvwrapper

mac

1
2
$ pip install virtualenvwrapper
$ brew install pyenv-virtualenvwrapper

使用python3.5创建一个虚拟环境

1
2
$ mkvirtualenv env2 -p $(which python3.5)
$ workon env2

项目地址:https://github.com/go-redis/redis

寻找入口

寻找入库,一般从 Quickstart 会给我们很多线索

1
2
3
4
5
6
7
8
9
10
11
func ExampleNewClient() {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})

pong, err := client.Ping().Result()
fmt.Println(pong, err)
// Output: PONG <nil>
}

我们看到 创建client的时候调用 redis.NewClient 方法,我们不妨寻找一下这个函数

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
$ git clone https://github.com/go-redis/redis.git

$ cd redis

$ grep -r -n "NewClient" *.go [10:17:37]
bench_test.go:12: client := redis.NewClient(&redis.Options{
cluster.go:239: Client: NewClient(opt),
cluster_test.go:64: client := redis.NewClient(&redis.Options{
command_test.go:14: client = redis.NewClient(redisOptions())
commands_test.go:19: client = redis.NewClient(redisOptions())
example_test.go:15: client = redis.NewClient(&redis.Options{
example_test.go:26:func ExampleNewClient() {
example_test.go:27: client := redis.NewClient(&redis.Options{
iterator_test.go:47: client = redis.NewClient(redisOptions())
main_test.go:204: client := redis.NewClient(&redis.Options{
pipeline_test.go:17: client = redis.NewClient(redisOptions())
pool_test.go:17: client = redis.NewClient(redisOptions())
pubsub_test.go:19: client = redis.NewClient(redisOptions())
race_test.go:23: client = redis.NewClient(redisOptions())
race_test.go:114: client = redis.NewClient(redisOptions())
race_test.go:175: client := redis.NewClient(opt)
race_test.go:198: client := redis.NewClient(opt)
redis.go:167:// NewClient returns a client to the Redis Server specified by Options.
redis.go:168:func NewClient(opt *Options) *Client {
redis_test.go:17: client = redis.NewClient(redisOptions())
redis_test.go:40: custom := redis.NewClient(&redis.Options{
redis_test.go:109: db2 := redis.NewClient(&redis.Options{
redis_test.go:141: client = redis.NewClient(&redis.Options{
redis_test.go:195: client = redis.NewClient(redisOptions())
ring.go:157: ring.addClient(name, NewClient(clopt))
tx_test.go:17: client = redis.NewClient(redisOptions())

我们可以看到 redis.go 的168行有 NewClient 函数的定义

1
redis.go:168:func NewClient(opt *Options) *Client {

这样我们找到了一个入口

绘制项目地图

我们找查看一个项目,绘制这个项目的地图很重要

通过 redis.go 168行我们可以看到,输入的参数 Options 和 返回值 Client

我们看下 Optins 的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Options struct {
Network string
Addr string
Dialer func() (net.Conn, error)
Password string
DB int
MaxRetries int
DialTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
PoolSize int
PoolTimeout time.Duration
IdleTimeout time.Duration
IdleCheckFrequency time.Duration
ReadOnly bool
}

都是一些连接是用的参数,往下看有初始化函数

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
func (opt *Options) init() {
if opt.Network == "" {
opt.Network = "tcp"
}
if opt.Dialer == nil {
opt.Dialer = func() (net.Conn, error) {
return net.DialTimeout(opt.Network, opt.Addr, opt.DialTimeout)
}
}
if opt.PoolSize == 0 {
opt.PoolSize = 10
}
if opt.DialTimeout == 0 {
opt.DialTimeout = 5 * time.Second
}
if opt.ReadTimeout == 0 {
opt.ReadTimeout = 3 * time.Second
}
if opt.WriteTimeout == 0 {
opt.WriteTimeout = opt.ReadTimeout
}
if opt.PoolTimeout == 0 {
opt.PoolTimeout = opt.ReadTimeout + time.Second
}
if opt.IdleTimeout == 0 {
opt.IdleTimeout = 5 * time.Minute
}
if opt.IdleCheckFrequency == 0 {
opt.IdleCheckFrequency = time.Minute
}
}

对于参数提供了默认值

然后我们看一下 Clinet 的定义, redis.go 151行 :

1
2
3
4
5
6
type Client struct {
baseClient
cmdable
}

var _ Cmdable = (*Client)(nil)

Client 是由 baseClient 和 cmdable 组合而成。 从命名上来看 baseClient是保存一些基础不变的东西,cmdable可能是负责命令。

1
var _ Cmdable = (*Client)(nil)

这行的意思 Client 要实现 Cmdable 这样的接口,要不然编译器会报错。 从 Client的结构上看只有 cmdble才能实现这样的一个接口

我们看下 Cmdable 的源码:

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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
type Cmdable interface {
Pipeline() *Pipeline
Pipelined(fn func(*Pipeline) error) ([]Cmder, error)

Echo(message interface{}) *StringCmd
Ping() *StatusCmd
Quit() *StatusCmd
Del(keys ...string) *IntCmd
Dump(key string) *StringCmd
Exists(key string) *BoolCmd
Expire(key string, expiration time.Duration) *BoolCmd
ExpireAt(key string, tm time.Time) *BoolCmd
Keys(pattern string) *StringSliceCmd
Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd
Move(key string, db int64) *BoolCmd
ObjectRefCount(keys ...string) *IntCmd
ObjectEncoding(keys ...string) *StringCmd
ObjectIdleTime(keys ...string) *DurationCmd
Persist(key string) *BoolCmd
PExpire(key string, expiration time.Duration) *BoolCmd
PExpireAt(key string, tm time.Time) *BoolCmd
PTTL(key string) *DurationCmd
RandomKey() *StringCmd
Rename(key, newkey string) *StatusCmd
RenameNX(key, newkey string) *BoolCmd
Restore(key string, ttl time.Duration, value string) *StatusCmd
RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd
Sort(key string, sort Sort) *StringSliceCmd
SortInterfaces(key string, sort Sort) *SliceCmd
TTL(key string) *DurationCmd
Type(key string) *StatusCmd
Scan(cursor uint64, match string, count int64) Scanner
SScan(key string, cursor uint64, match string, count int64) Scanner
HScan(key string, cursor uint64, match string, count int64) Scanner
ZScan(key string, cursor uint64, match string, count int64) Scanner
Append(key, value string) *IntCmd
BitCount(key string, bitCount *BitCount) *IntCmd
BitOpAnd(destKey string, keys ...string) *IntCmd
BitOpOr(destKey string, keys ...string) *IntCmd
BitOpXor(destKey string, keys ...string) *IntCmd
BitOpNot(destKey string, key string) *IntCmd
BitPos(key string, bit int64, pos ...int64) *IntCmd
Decr(key string) *IntCmd
DecrBy(key string, decrement int64) *IntCmd
Get(key string) *StringCmd
GetBit(key string, offset int64) *IntCmd
GetRange(key string, start, end int64) *StringCmd
GetSet(key string, value interface{}) *StringCmd
Incr(key string) *IntCmd
IncrBy(key string, value int64) *IntCmd
IncrByFloat(key string, value float64) *FloatCmd
MGet(keys ...string) *SliceCmd
MSet(pairs ...interface{}) *StatusCmd
MSetNX(pairs ...interface{}) *BoolCmd
Set(key string, value interface{}, expiration time.Duration) *StatusCmd
SetBit(key string, offset int64, value int) *IntCmd
SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd
SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd
SetRange(key string, offset int64, value string) *IntCmd
StrLen(key string) *IntCmd
HDel(key string, fields ...string) *IntCmd
HExists(key, field string) *BoolCmd
HGet(key, field string) *StringCmd
HGetAll(key string) *StringStringMapCmd
HIncrBy(key, field string, incr int64) *IntCmd
HIncrByFloat(key, field string, incr float64) *FloatCmd
HKeys(key string) *StringSliceCmd
HLen(key string) *IntCmd
HMGet(key string, fields ...string) *SliceCmd
HMSet(key string, fields map[string]string) *StatusCmd
HSet(key, field, value string) *BoolCmd
HSetNX(key, field, value string) *BoolCmd
HVals(key string) *StringSliceCmd
BLPop(timeout time.Duration, keys ...string) *StringSliceCmd
BRPop(timeout time.Duration, keys ...string) *StringSliceCmd
BRPopLPush(source, destination string, timeout time.Duration) *StringCmd
LIndex(key string, index int64) *StringCmd
LInsert(key, op string, pivot, value interface{}) *IntCmd
LInsertBefore(key string, pivot, value interface{}) *IntCmd
LInsertAfter(key string, pivot, value interface{}) *IntCmd
LLen(key string) *IntCmd
LPop(key string) *StringCmd
LPush(key string, values ...interface{}) *IntCmd
LPushX(key string, value interface{}) *IntCmd
LRange(key string, start, stop int64) *StringSliceCmd
LRem(key string, count int64, value interface{}) *IntCmd
LSet(key string, index int64, value interface{}) *StatusCmd
LTrim(key string, start, stop int64) *StatusCmd
RPop(key string) *StringCmd
RPopLPush(source, destination string) *StringCmd
RPush(key string, values ...interface{}) *IntCmd
RPushX(key string, value interface{}) *IntCmd
SAdd(key string, members ...interface{}) *IntCmd
SCard(key string) *IntCmd
SDiff(keys ...string) *StringSliceCmd
SDiffStore(destination string, keys ...string) *IntCmd
SInter(keys ...string) *StringSliceCmd
SInterStore(destination string, keys ...string) *IntCmd
SIsMember(key string, member interface{}) *BoolCmd
SMembers(key string) *StringSliceCmd
SMove(source, destination string, member interface{}) *BoolCmd
SPop(key string) *StringCmd
SPopN(key string, count int64) *StringSliceCmd
SRandMember(key string) *StringCmd
SRandMemberN(key string, count int64) *StringSliceCmd
SRem(key string, members ...interface{}) *IntCmd
SUnion(keys ...string) *StringSliceCmd
SUnionStore(destination string, keys ...string) *IntCmd
ZAdd(key string, members ...Z) *IntCmd
ZAddNX(key string, members ...Z) *IntCmd
ZAddXX(key string, members ...Z) *IntCmd
ZAddCh(key string, members ...Z) *IntCmd
ZAddNXCh(key string, members ...Z) *IntCmd
ZAddXXCh(key string, members ...Z) *IntCmd
ZIncr(key string, member Z) *FloatCmd
ZIncrNX(key string, member Z) *FloatCmd
ZIncrXX(key string, member Z) *FloatCmd
ZCard(key string) *IntCmd
ZCount(key, min, max string) *IntCmd
ZIncrBy(key string, increment float64, member string) *FloatCmd
ZInterStore(destination string, store ZStore, keys ...string) *IntCmd
ZRange(key string, start, stop int64) *StringSliceCmd
ZRangeWithScores(key string, start, stop int64) *ZSliceCmd
ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd
ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd
ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd
ZRank(key, member string) *IntCmd
ZRem(key string, members ...interface{}) *IntCmd
ZRemRangeByRank(key string, start, stop int64) *IntCmd
ZRemRangeByScore(key, min, max string) *IntCmd
ZRevRange(key string, start, stop int64) *StringSliceCmd
ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd
ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd
ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd
ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd
ZRevRank(key, member string) *IntCmd
ZScore(key, member string) *FloatCmd
ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd
PFAdd(key string, els ...interface{}) *IntCmd
PFCount(keys ...string) *IntCmd
PFMerge(dest string, keys ...string) *StatusCmd
BgRewriteAOF() *StatusCmd
BgSave() *StatusCmd
ClientKill(ipPort string) *StatusCmd
ClientList() *StringCmd
ClientPause(dur time.Duration) *BoolCmd
ClientSetName(name string) *BoolCmd
ConfigGet(parameter string) *SliceCmd
ConfigResetStat() *StatusCmd
ConfigSet(parameter, value string) *StatusCmd
DbSize() *IntCmd
FlushAll() *StatusCmd
FlushDb() *StatusCmd
Info(section ...string) *StringCmd
LastSave() *IntCmd
Save() *StatusCmd
Shutdown() *StatusCmd
ShutdownSave() *StatusCmd
ShutdownNoSave() *StatusCmd
SlaveOf(host, port string) *StatusCmd
Time() *TimeCmd
Eval(script string, keys []string, args ...interface{}) *Cmd
EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd
ScriptExists(scripts ...string) *BoolSliceCmd
ScriptFlush() *StatusCmd
ScriptKill() *StatusCmd
ScriptLoad(script string) *StringCmd
DebugObject(key string) *StringCmd
PubSubChannels(pattern string) *StringSliceCmd
PubSubNumSub(channels ...string) *StringIntMapCmd
PubSubNumPat() *IntCmd
ClusterSlots() *ClusterSlotsCmd
ClusterNodes() *StringCmd
ClusterMeet(host, port string) *StatusCmd
ClusterForget(nodeID string) *StatusCmd
ClusterReplicate(nodeID string) *StatusCmd
ClusterResetSoft() *StatusCmd
ClusterResetHard() *StatusCmd
ClusterInfo() *StringCmd
ClusterKeySlot(key string) *IntCmd
ClusterCountFailureReports(nodeID string) *IntCmd
ClusterCountKeysInSlot(slot int) *IntCmd
ClusterDelSlots(slots ...int) *StatusCmd
ClusterDelSlotsRange(min, max int) *StatusCmd
ClusterSaveConfig() *StatusCmd
ClusterSlaves(nodeID string) *StringSliceCmd
ClusterFailover() *StatusCmd
ClusterAddSlots(slots ...int) *StatusCmd
ClusterAddSlotsRange(min, max int) *StatusCmd
GeoAdd(key string, geoLocation ...*GeoLocation) *IntCmd
GeoPos(key string, members ...string) *GeoPosCmd
GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd
GeoRadiusByMember(key, member string, query *GeoRadiusQuery) *GeoLocationCmd
GeoDist(key string, member1, member2, unit string) *FloatCmd
GeoHash(key string, members ...string) *StringSliceCmd
Command() *CommandsInfoCmd
}

我们看到了 Cmdable 这个接口其实就是实现了 redis 命令的封装。

我们接着看 baseClient

1
2
3
4
5
6
type baseClient struct {
connPool pool.Pooler
opt *Options

onClose func() error // hook called when client is closed
}

baseClient 引用了连接池,

我们接着往下看 redis.go 这个文件

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
func (c *baseClient) Process(cmd Cmder) error {
for i := 0; i <= c.opt.MaxRetries; i++ {
if i > 0 {
cmd.reset()
}

cn, _, err := c.conn()
if err != nil {
cmd.setErr(err)
return err
}

readTimeout := cmd.readTimeout()
if readTimeout != nil {
cn.ReadTimeout = *readTimeout
} else {
cn.ReadTimeout = c.opt.ReadTimeout
}
cn.WriteTimeout = c.opt.WriteTimeout

if err := writeCmd(cn, cmd); err != nil {
c.putConn(cn, err, false)
cmd.setErr(err)
if err != nil && internal.IsRetryableError(err) {
continue
}
return err
}

err = cmd.readReply(cn)
c.putConn(cn, err, readTimeout != nil)
if err != nil && internal.IsRetryableError(err) {
continue
}

return err
}

return cmd.Err()
}

发现 baseClient 有个 Process 方法 这个应该是处理执行的过程 需要我们注意。

根据广度优先原则,我们再看 Client 上还有什么关联的内容

我们可以看到 redis.go 这个文件里面 还包含了 Pipeline 和 pubSub 两个函数

1
2
3
4
5
func (c *Client) Pipeline() *Pipeline {
}

func (c *Client) pubSub() *PubSub {
}

然后我们再看下,项目文件,还有什么我们并没有涉及到的

1
2
3
4
5
6
7
 $ ls                                                                                                                     
CHANGELOG.md cluster_client_test.go doc.go main_test.go pubsub.go ring.go tx.go
LICENSE cluster_test.go example_test.go options.go pubsub_test.go ring_test.go tx_test.go
Makefile command.go export_test.go parser.go race_test.go script.go
README.md command_test.go internal/ pipeline.go redis.go sentinel.go
bench_test.go commands.go iterator.go pipeline_test.go redis_test.go sentinel_test.go
cluster.go commands_test.go iterator_test.go pool_test.go result.go testdata/

答案就是 cluster,cluster 是连接 redis 集群的方式,会提供给用户使用

我们详细看一下 cluster 源码,cluster 里面有自己的 Options,值得注意的是,有 Node 逻辑

1
2
3
4
5
type clusterNode struct {
Client *Client
Latency time.Duration
loading time.Time
}

使用组合的方式把 Client 包装进来。

1
2


在web开发过程中,有些时候需要隐藏你的静态文件url,也有些时候需要频繁更替你的静态资源服务商,如果你的静态资源服务商(七牛、又拍云)发生了变更,然后你又要修改对外暴漏的url是非常不方便的。这时候我们使用一个统一的自己能控制的对外url对于我们非常方便,也算是变相的对外解耦,是个非常有用的办法。

基于以上的想法,我们来进行技术选型,由于现在大家写web都脱离不了nginx,所以就想在nginx层面上解决这个问题。不出所料,nginx 里的 x-accell-redirect 就可以实现此功能。下面我们使用nginx和django来实现此功能。

配置nginx

修改 nginx.conf 新增 location

1
2
3
4
5
6
location ~* ^/protected/ {
internal; # 只允许内部重定向
rewrite ^/protected/(.*?)\|(.*) /$2 break;
proxy_set_header Authorization $1; # 鉴权的东西放到header中 如果没有鉴权可以去掉,$1的值是服务端传过来的,详见后面
proxy_pass http://images.com; # 你的静态文件地址
}

保存,reload 。

django view

这里面使用了 django-rest-framework 来进行API的开发

1
2
3
4
5
6
7
8
class NginxAccel(APIView):

def get(self, request, img_name):
auth = image_auth() # 类似私有空间的验证
response = Response()
response["X-Accel-Redirect"] = "/protected/{0}|{1}".format(
auth, img_name)
return response

然后通过url来访问,鉴权成功,就可以访问到你要的图片。如果不需要鉴权,渠道auth的相关代码就可以。

现在开发环境原来越复杂,为了方便开发,让团队每个人的环境一致,最近使用docker进行打包image,发放给团队使用。

安装docker

本人使用mac,直接下载docker for mac 方便很多,其他os网上一搜一大把。so easy~!

下载images

1
2
3
4
5
6
[docker@docker-root ~]$ sudo docker pull alpine
[docker@docker-root ~]$ sudo docker pull ubuntu
[docker@docker-root ~]$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
alpine latest 3e467a6273a3 2 days ago 4.793 MB
ubuntu latest 17b6a9e179d7 5 days ago 120.7 MB

启动

1
docker run -ti -h dev --net=host -v ~/workspace/sohu:/root/workspace -w /root develop:base /bin/bash

把本地目录 ~/workspace/sohu 映射到容器 /root/workspace 目录

自定义images

先更新下源,安装几个必备工具

1
2
apt-get update
apt-get install vim curl git wget

修改阿里云源

修改成阿里云源,加快安装软件的速度

1
2
3
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak #备份
sudo vim /etc/apt/sources.list #修改
sudo apt-get update #更新列表

阿里云源

1
2
3
4
5
6
7
8
9
10
deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse

python开发环境

安装工具和必备依赖

1
2
3
4
5
6
7
apt-get install gcc gdb binutils make git dstat sysstat htop curl wget
apt-get install libjpeg-dev
apt-get install net-tools
apt-get install libffi-dev
apt-get install bzip2
apt-get install libssl
apt-get install libssl-dev

如需要sqlit支持需要先装如下库,再安装python:

1
sudo apt-get install sqlite3 libsqlite3-dev

pyenv 安装

1
$ curl -L https://raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | bash

在 ~/.bashrc 中添加

1
2
3
export PATH="/root/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

查看可安装的版本

1
$ pyenv install --list

安装指定版本

1
$ pyenv install 3.5.2 -v

更新数据库

1
$ pyenv rehash

查看当前已安装的python版本

1
2
3
4
$ pyenv versions 
* system (set by /Users/ce/workspace/sohu/.python-version)
3.5.1
sohu351

设置全局的python版本

1
2
3
4
$ pyenv global 3.5.1
$ pyenv versions
system
* 3.5.1 (set by /Users/ce/workspace/sohu/.python-version)

安装virtualenv

1
2
$ pyenv global system  切换到系统python
$ pip install virtualenv

安装virtualenvwrapper

安装 virtualenvwrapper 并让pyenv支持

1
2
$ pip install virtualenvwrapper
$ git clone https://github.com/yyuu/pyenv-virtualenvwrapper.git ~/.pyenv/plugins/pyenv-virtualenvwrapper

使用python3.5创建一个虚拟环境

1
2
3
$ pyenv global 3.5.2
$ mkvirtualenv env2
$ workon env2

Go开发环境

编译go1.3需要的参数

1
CGO_ENABLED=0 ./make.bash

ToDo……

image 字符集修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ export LANG="en_US.UTF-8"

$ sudo locale-gen "en_US.UTF-8"
Generating locales...
en_US.UTF-8... done
Generation complete.

$ sudo dpkg-reconfigure locales
Generating locales...
en_US.UTF-8... up-to-date
Generation complete.

$ locale charmap
UTF-8

syslog

如果使用syslog,需要在启动的时候做一次目录映射

1
-v /dev/log:/dev/log

保存images

1
2
3
$ docker ps                                                                                                              [17:06:10]
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
111be6691cc8 c5940ba1089c "/bin/bash" 4 days ago Up 9 hours dev

把 image 中 c5940ba1089c 这个字段记住

1
docker commit c5940ba1089c develop:base  #进行保存

进行查看

1
2
3
4
5
6
 $ docker images                                                                                                          
REPOSITORY TAG IMAGE ID CREATED SIZE
develop base 5a893de95205 40 hours ago 1.87 GB
ubuntu latest 42118e3df429 9 weeks ago 124.8 MB
alpine latest 4e38e38c8ce0 3 months ago 4.799 MB
hello-world latest 690ed74de00f 11 months ago 960 B

私有仓库

查看如下文章 :

创建私有仓库

操作系统:mac OSX 10.11 或 Ubuntu 16.04
编辑器: vim、 sublime、PyCharm

python环境

版本: python3.5.1

安装工具

1. pyenv

安装 pyenv

linux:

1
2
3
4
5
$ git clone git://github.com/yyuu/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"'>> ~/.bashrc # 指明环境变量
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"'>> ~/.bashrc
$ echo 'eval"$(pyenv init -)"' >> ~/.bashrc # 开启shims and autocompletion
$ exec $SHELL -l # 重新启动shell让其生效

mac:

1
2
3
4
$ brew update
$ brew install pyenv //安装
$ brew upgrade pyenv //升级
$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile //只需要执行一次即可

查看可安装的版本

1
$ pyenv install --list

安装指定版本

1
$ pyenv install 3.5.1 -v

更新数据库

1
$ pyenv rehash

查看当前已安装的python版本

1
2
3
4
$ pyenv versions 
* system (set by /Users/ce/workspace/sohu/.python-version)
3.5.1
sohu351

设置全局的python版本

1
2
3
4
$ pyenv global 3.5.1
$ pyenv versions
system
* 3.5.1 (set by /Users/ce/workspace/sohu/.python-version)

2. virtualenvwrapper

linux

1
2
$ pip install virtualenvwrapper
$ git clone https://github.com/yyuu/pyenv-virtualenvwrapper.git ~/.pyenv/plugins/pyenv-virtualenvwrapper

mac

1
2
$ pip install virtualenvwrapper
$ brew install pyenv-virtualenvwrapper

使用python3.5创建一个虚拟环境

1
2
$ mkvirtualenv env2 -p $(which python3.5)
$ workon env2

direnv

安装

1
2
3
git clone https://github.com/direnv/direnv
cd direnv
make install

配置

设置全局配置文件

vim .direnvrc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use_venv () {
export VIRTUAL_ENV = "${HOME} /.virtualenvs/${1}"
PATH_add "$VIRTUAL_ENV/bin"
}

use_vwrapper () {
source /usr/local/bin/virtualenvwrapper.sh
}

use_python() {
local python_root=$HOME/.pyenv/versions/$1
load_prefix "$python_root"
layout_python "$python_root/bin/python"
}

在项目中创建.envrc文件

1
2
3
layout python
use vwrapper
workon env2

https://github.com/direnv/direnv/wiki/Python

mac快速生成干净的linux开发环境

virtualbox -> vagrant/completed -> docker-root -> docker -> … ubuntu/alpine

安装vagrant 或者去官方下载

1
2
3
4
5
brew tap caskroom/cask #if not already installed
brew install brew-cask #if not already installed
brew cask install vagrant
brew tap homebrew/completions
brew install vagrant-completion

docker-root

1
2
3
4
$ git clone https://github.com/ailispaw/docker-root
$ cd docker-root
$ make vagrant
$ make

vagrant配置文件

1
2
3
4
5
6
7
8
9
$ vagrant init
$ vim Vagrantfile
config.vm.box = "ailispaw/docker-root"
config.vm.network "forwarded_port", guest: 9999, host: 1234
config.vm.synced_folder "/Users/ce/workspace/docker/data", "/home/docker/data"
config.vm.provider "virtualbox" do |vb|
vb.memory = "512"
vb.cpus = "4"
end

启动并ssh

1
vagrant up && vagrant ssh

docker

1
2
3
4
5
6
[docker@docker-root ~]$ sudo docker pull alpine
[docker@docker-root ~]$ sudo docker pull ubuntu
[docker@docker-root ~]$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
alpine latest 3e467a6273a3 2 days ago 4.793 MB
ubuntu latest 17b6a9e179d7 5 days ago 120.7 MB

容器

1
docker create -ti -h teach --name teach -v /home/docker/data:/root/data -w /root ubuntu /bin/bash

查看所有容器

1
docker ps -a -q

删除容器

1
docker rm container_id
1
docker attach

安装必备工具

1
sudo apt-get install gcc gdb binutils make git dstat sysstat htop curl wget readelf objdump pidstat

前段时间写私信项目,这种即时消息的系统肯定是存毫秒数的。本地环境是django1.8+mysql5.6一直这么开发屡试不爽,不料到测试线上环境的时候出现了问题。

我们的线上环境数据库竟然是MariaDB10.0.16,存入datetime类型竟然惊奇的把毫秒数去掉了。由于MariaDB和mysql同出一辙我决定深入虎穴一查究竟。

本地环境没问题那就先从本地的mysql表结构看起:

这么一看为啥大问题,接着去线上数据库看看:

神奇的事情出现了datetime竟然没有长度,一顿跟DBA部门交涉最后对方似乎听懂了我的问题,然后直接就把长度6给我加上了。我一顿流汗啊,就不能给我升级下版本?这里我要说明一下mysql从5.6以后就支持了datetime存毫秒数,MariaDB也在10.1.0以后的版本进行了支持,所以我这种抱怨也是对的,无奈跨部门沟通比较困难别人给你改了长度你就用呗。

接着我就做了测试竟然发现还是存不了毫秒,但是惊奇的发现毫秒的位数都是用0补全的,举个例子:

1
2
2015-09-10 13:56:01.542410  # 我想要的数据
2015-09-10 13:56:01.000000 # 现实的数据

现实就是这么残酷,我打开了django DEBUG模式,看到sql中的毫秒数本来就没有,那我在线上数据库使用sql带着毫秒数写入一条数据测试了一下,惊奇的发现竟然可以保存毫秒数,那意思就是说线上的数据库经过对dateime长度的修改其实是可以存毫秒数的,只是现在django不知道怎么处理的竟然没有存入。本着对自己负责任的心里,我决定看下django源码了解他是如何实现的:

1
2
3
4
5
6
@cached_property
def data_types(self):
if self.features.supports_microsecond_precision:
return dict(self._data_types, DateTimeField='datetime(6)', TimeField='time(6)')
else:
return self._data_types

这段代码我看到了让人欣喜的datetime(6)这么说只要是self.features.supports_microsecond_precisionTrue那么就会支持毫秒数,那我们接着往下看找到这个方法

1
2
3
4
5
@cached_property
def supports_microsecond_precision(self):
# See https://github.com/farcepest/MySQLdb1/issues/24 for the reason
# about requiring MySQLdb 1.2.5
return self.connection.mysql_version >= (5, 6, 4) and Database.version_info >= (1, 2, 5)

从段代码就能看出来了,丫判断了mysq版本是不是大于5.6.4且Database(import MySQLdb as Database 其实就是连接驱动)大于1.2.5如果都大于那就支持,不大于就不支持很阴险啊,因为我们线上MariaDB10.0.16其实跟mysql5.5左右的版本是对应的所以肯定不支持啊。那就得想个比较淫荡的方便,所以在线上配置文件中加入如下代码:

1
2
from django.db.backends.mysql.base import DatabaseFeatures
DatabaseFeatures.supports_microsecond_precision = True

这样强制让等于True就会支持毫秒。搞定,收工!