一、搭建memcached和redis
略,自己去百度吧
二、操作Mmecached
1. 安装API
python -m pip install python-memcached
2. 启动memcached
memcached -d -u root -p 12000 -m 50
memcached -d -u root -p 12001 -m 50
memcached -d -u root -p 12002 -m 50
参数说明:
-
d 是启动一个守护进程
-
m 是分配给Memcache使用的内存数量,单位是MB
-
u 是运行Memcache的用户
-
l 是监听的服务器IP地址
-
p 是设置Memcache监听的端口,最好是
1024
以上的端口
-
c 选项是最大运行的并发连接数,默认是
1024
,按照你服务器的负载量来设定
-
P 是设置保存Memcache的pid文件
3. Python Memcached模块对于memcached集群的支持
python-memcached模块原生支持集群操作,其原理是在内存维护一个主机列表,且集群中主机的权重值和主机在列表中重复出现的次数成正比
1 2 3 4 5 6 7 |
|
如果用户根据如果要在内存中创建一个键值对(如:k1 = "v1"),那么要执行一下步骤:
- 根据算法将 k1 转换成一个数字
- 将数字和主机列表长度求余数,得到一个值 N( 0 <= N < 列表长度 )
- 在主机列表中根据 第2步得到的值为索引获取主机,例如:host_list[N]
- 连接 将第3步中获取的主机,将 k1 = "v1" 放置在该服务器的内存中
代码实现如下:
1 2 |
|
4. add(keyname, value)
add 新增一个key,如果key存在则报错
1 2 3 4 5 6 7 8 |
|
5. replace(keyname, new_value)
replace 修改某个key的值,如果key不存在,则报错
1 2 3 4 5 6 |
|
6. set 和 set_multi
set 设置一个键值对,如果key不存在,则创建,如果key存在,则修改
get 获取一个键值对
set_multi 设置多个键值对,如果key不存在,则创建,如果key存在,则修改
get_multi 获取多个键值对
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 |
|
7. delete 和 delete_multi
1 2 3 4 5 |
|
1 2 3 4 5 |
|
8. append 和 prepend
append 修改指定key的值,在该值 后面 追加内容
prepend 修改指定key的值,在该值 前面 插入内容
1 2 3 4 5 |
|
9. decr 和 incr
incr 自增,将Memcached中的某一个值增加 N ( N默认为1 )
decr 自减,将Memcached中的某一个值减少 N ( N默认为1 )
1 2 3 4 5 6 7 8 9 |
|
10. gets 和 cas
如商城商品剩余个数,假设改值保存在memcache中,product_count = 900
A用户刷新页面从memcache中读取到product_count = 900
B用户刷新页面从memcache中读取到product_count = 900
如果A、B用户均购买商品
A用户修改商品剩余个数 product_count=899
B用户修改商品剩余个数 product_count=899
如此一来缓存内的数据便不在正确,两个用户购买商品后,商品剩余还是 899
如果使用python的set和get来操作以上过程,那么程序就会如上述所示情况!
如果想要避免此情况的发生,只要使用 gets 和 cas 即可,如:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import memcache
mc = memcache.Client(['10.211.55.4:12000'], debug=True, cache_cas=True)
v = mc.gets('product_count')
# ...
# 如果有人在gets之后和cas之前修改了product_count,那么,下面的设置将会执行失败,剖出异常,从而避免非正常数据的产生
mc.cas('product_count', "899")
Ps:本质上每次执行gets时,会从memcache中获取一个自增的数字,通过cas去修改gets的值时,会携带之前获取的自增值和memcache中的自增值进行比较,如果相等,则可以提交,如果不想等,那表示在gets和cas执行之间,又有其他人执行了gets(获取了缓冲的指定值), 如此一来有可能出现非正常数据,则不允许修改。
Ps:以上gets cas经过测试无效,或许我测试方法错误,以后修正!
三、操作Redis
redis提供五种数据类型:string,hash,list,set及zset(sorted set)。
1. 安装redis模块
1 |
|
2. redis模块介绍
redis-py 的API的使用可以分类为:
- 连接方式
- 连接池
- 操作
- String 操作
- Hash 操作
- List 操作
- Set 操作
- Sort Set 操作
- 管道
- 发布订阅
3. 操作模式
redis-py提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
4. 连接池
redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。
5. String操作
String操作,redis中的String在在内存中按照一个name对应一个value来存储
5.1 set(name, value, ex=None, px=None, nx=False, xx=False)
在Redis中设置值,默认,不存在则创建,存在则修改
参数:
ex,过期时间(秒)
px,过期时间(毫秒)
nx,如果设置为True,则只有name不存在时,当前set操作才执行
xx,如果设置为True,则只有name存在时,当前set操作才执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
基于这个命令的变种:
setnx(name, value) 设置值,只有name不存在时,执行设置操作(添加)
setex(name, value, time) 设置值, time,过期时间(数字秒 或 timedelta对象)
psetex(name, time_ms, value) 设置值,time_ms,过期时间(数字毫秒 或 timedelta对象)
mset(*args, **kwargs) 批量设置值,mset(k1='v1', k2='v2') 或 mset({'k1': 'v1', 'k2': 'v2'})
getset(name, value)
1 |
|
getrange(key, start, end)
1 2 3 4 5 6 |
|
setrange(name, offset, value)
1 2 3 4 |
|
setbit(name, offset, value)
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 |
|
getbit(name, offset)
1 |
|
bitcount(key, start=None, end=None)
1 2 3 4 5 |
|
bitop(operation, dest, *keys)
1 2 3 4 5 6 7 8 9 10 |
|
strlen(name)
1 |
|
incr(self, name, amount=1)
1 2 3 4 5 6 7 |
|
incrbyfloat(self, name, amount=1.0)
1 2 3 4 5 |
|
decr(self, name, amount=1)
1 2 3 4 5 |
|
append(key, value)
1 2 3 4 5 |
|
6. Hash操作
Hash操作,redis中Hash在内存中的存储格式如下图:
hset(name, key, value)
1 2 3 4 5 6 7 8 9 |
|
hmset(name, mapping)
1 2 3 4 5 6 7 8 |
|
hget(name,key)
1 |
|
hmget(name, keys, *args)
1 2 3 4 5 6 7 8 9 10 11 |
|
hgetall(name)
1 |
|
hlen(name)
1 |
|
hkeys(name)
1 |
|
hvals(name)
1 |
|
hexists(name, key)
1 |
|
hdel(name,*keys)
1 |
|
hincrby(name, key, amount=1)
1 2 3 4 5 |
|
hincrbyfloat(name, key, amount=1.0)
1 2 3 4 5 6 7 8 |
|
hscan(name, cursor=0, match=None, count=None)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
hscan_iter(name, match=None, count=None)
1 2 3 4 5 6 7 8 9 |
|
7. List操作
List操作,redis中的List在在内存中按照一个name对应一个List来存储。如图:
lpush(name,values)
1 2 3 4 5 6 7 8 |
|
lpushx(name,value)
1 2 3 4 |
|
llen(name)
1 |
|
linsert(name, where, refvalue, value))
1 2 3 4 5 6 7 |
|
r.lset(name, index, value)
1 2 3 4 5 6 |
|
r.lrem(name, value, num)
1 2 3 4 5 6 7 8 |
|
lpop(name)
1 2 3 4 |
|
lindex(name, index)
1 |
|
lrange(name, start, end)
1 2 3 4 5 |
|
ltrim(name, start, end)
1 2 3 4 5 |
|
rpoplpush(src, dst)
1 2 3 4 |
|
blpop(keys, timeout)
1 2 3 4 5 6 7 8 |
|
brpoplpush(src, dst, timeout=0)
1 2 3 4 5 6 |
|
自定义增量迭代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
8. Set操作
sadd(name,values)
1 |
|
scard(name)
1 |
|
sdiff(keys, *args)
1 |
|
sdiffstore(dest, keys, *args)
1 |
|
sinter(keys, *args)
1 |
|
sinterstore(dest, keys, *args)
1 |
|
sismember(name, value)
1 |
|
smembers(name)
1 |
|
smove(src, dst, value)
1 |
|
spop(name)
1 |
|
srandmember(name, numbers)
1 |
|
srem(name, values)
1 |
|
sunion(keys, *args)
1 |
|
sunionstore(dest,keys, *args)
1 |
|
sscan(name, cursor=0, match=None, count=None)
sscan_iter(name, match=None, count=None)
1 |
|
9. Zset操作
有序集合,在集合的基础上,为每元素排序;元素的排序需要根据另外一个值来进行比较,所以,对于有序集合,每一个元素有两个值,即:值和分数,分数专门用来做排序。
zadd(name, *args, **kwargs)
1 2 3 4 5 |
|
zcard(name)
1 |
|
zcount(name, min, max)
1 |
|
zincrby(name, value, amount)
1 |
|
r.zrange( name, start, end, desc=False, withscores=False, score_cast_func=float)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
zrank(name, value)
1 2 3 4 |
|
zrangebylex(name, min, max, start=None, num=None)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
zrem(name, values)
1 2 3 |
|
zremrangebyrank(name, min, max)
1 |
|
zremrangebyscore(name, min, max)
1 |
|
zremrangebylex(name, min, max)
1 |
|
zscore(name, value)
1 |
|
zinterstore(dest, keys, aggregate=None)
1 2 |
|
zunionstore(dest, keys, aggregate=None)
1 2 |
|
zscan(name, cursor=0, match=None, count=None, score_cast_func=float)
zscan_iter(name, match=None, count=None,score_cast_func=float)
1 |
|
10. 其他常用操作
delete(*names)
1 |
|
exists(name)
1 |
|
keys(pattern='*')
1 2 3 4 5 6 7 |
|
expire(name ,time)
1 |
|
rename(src, dst)
1 |
|
move(name, db))
1 |
|
randomkey()
1 |
|
type(name)
1 |
|
scan(cursor=0, match=None, count=None)
scan_iter(match=None, count=None)
1 |
|
四、Redis之管道
管道(pipeline)是redis在提供单个请求中缓冲多条服务器命令的基类的子类。它通过减少服务器-客户端之间反复的TCP数据库包,从而大大提高了执行批量命令的功能,避免每次建立、释放连接的开销。并且默认情况下,一次pipline 是原子性操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
当然,也支持管道的命令放在一起
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
五、Redis之发布订阅
订阅者可以订阅一个或多个频道,发布者向一个频道发送消息后,所有订阅这个频道的订阅者都将收到消息,而发布者也将收到一个数值,这个数值是收到消息的订阅者的数量。订阅者只能收到自它开始订阅后发布者所发布的消息,之前发布的消息呢,就不可能收到了。
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 |
|