Memcached
Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap。其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信。
Memcached安装:
wget http://memcached.org/latest
tar -zxvf memcached-1.x.x.tar.gz
cd memcached-1.x.x
./configure && make && make test && sudo make install
PS:依赖libevent
yum install libevent-devel
apt-get install libevent-dev
启动Memcached:
memcached -d -m 10 -u root -l IP -p 12000 -c 256 -P /tmp/memcached.pid
参数说明:
-d 是启动一个守护进程
-m 是分配给Memcache使用的内存数量,单位是MB
-u 是运行Memcache的用户
-l 是监听的服务器IP地址
-p 是设置Memcache监听的端口,最好是1024以上的端口
-c 选项是最大运行的并发连接数,默认是1024,按照你服务器的负载量来设定
-P 是设置保存Memcache的pid文件
Python操作Memcached
python操作Memcached使用Python-memcached模块
下载安装:https:
/
/
pypi.python.org
/
pypi
/
python
-
memcached
1.第一次操作
import memcache
mc = memcache.Client(['ip:port'], debug=True)
mc.set("foo", "bar")
ret = mc.get('foo')
print(ret)
注:debug = True 表示运行出现错误时,现实错误信息,上线后移除该参数。
2.add
import memcache
mc = memcache.Client(['ip:port'], debug=True)
mc.add('k1', 'v1')
# mc.add('k1', 'v2') # 报错,对已经存在的key重复添加,失败!!!
3.replace
import memcache
mc = memcache.Client(['ip:port'], debug=True)
# 如果memcache中存在kkkk,则替换成功,否则异常
mc.replace('kkkk','999')
4.set 和 set_multi
set 设置一个键值对,如果key不存在,则创建,如果key存在,则修改
set_multi 设置多个键值对,如果key不存在,则创建,如果key存在,则修改
import memcache
mc = memcache.Client(['ip:port'], debug=True)
mc.set('key0', 'value1')
mc.set_multi({'key1': 'val1', 'key2': 'val2'})
5.delete 和 delete_multi
delete 在Memcached中删除指定的一个键值对
delete_multi 在Memcached中删除指定的多个键值对
import memcache
mc = memcache.Client(['ip:port'], debug=True)
mc.delete('key0')
mc.delete_multi(['key1', 'key2'])
6. get 和 get_multi
get 获取一个键值对
get_multi 获取多一个键值对
import memcache
mc = memcache.Client(['ip:port'], debug=True)
val = mc.get('key0')
item_dict = mc.get_multi(["key1", "key2", "key3"])
7. append 和 prepend
append 修改指定key的值,在该值 后面 追加内容
prepend 修改指定key的值,在该值 前面 插入内容
import memcache
mc = memcache.Client(['ip:port'], debug=True)
# k1 = "v1"
mc.append('k1', 'after')
# k1 = "v1after"
mc.prepend('k1', 'before')
# k1 = "beforev1after"
8. decr 和 incr
incr 自增,将Memcached中的某一个值增加 N ( N默认为1 )
decr 自减,将Memcached中的某一个值减少 N ( N默认为1 )
import memcache
mc = memcache.Client(['ip:port'], debug=True)
mc.set('k1', '777')
mc.incr('k1')
# k1 = 778
mc.incr('k1', 10)
# k1 = 788
mc.decr('k1')
# k1 = 787
mc.decr('k1', 10)
# k1 = 777
9. 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 即可,如:
import memcache
mc = memcache.Client(['ip:port'], debug=True, cache_cas=True)
v = mc.gets('product_count')
# ...
# 如果有人在gets之后和cas之前修改了product_count,那么,下面的设置将会执行失败,剖出异常,从而避免非正常数据的产生
mc.cas('product_count', "899")
本质上每次执行gets时,会从memcache中获取一个自增的数字,通过cas去修改gets的值时,会携带之前获取的自增值和memcache中的自增值进行比较,如果相等,则可以提交,如果不想等,那表示在gets和cas执行之间,又有其他人执行了gets(获取了缓冲的指定值), 如此一来有可能出现非正常数据,则不允许修改。
Redis
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
一、Redis安装和基本使用
wget http://download.redis.io/releases/redis-3.0.6.tar.gz
tar xzf redis-3.0.6.tar.gz
cd redis-3.0.6
make
启动服务端
src/redis-server
启动客户端
src/redis-cli
redis> set foo bar
OK
redis> get foo
"bar"
二、Python操作Redis
sudo pip install redis
or
sudo easy_install redis
or
源码安装
详见:https://github.com/WoLpH/redis-py
API使用
redis-py 的API的使用可以分类为:
连接方式
连接池
操作
String 操作
Hash 操作
List 操作
Set 操作
Sort Set 操作
管道
发布订阅
1. 操作模式
redis-py提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py。
import redis
r = redis.Redis(host='serverIP', port=6379) # 默认端口
r.set('foo', 'Bar')
print(r.get('foo'))
2. 连接池
redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。
import redis
pool = redis.ConnectionPool(host='serverIP', port=6379) # 默认端口
r = redis.Redis(connection_pool=pool)
r.set('foo', 'Bar')
print(r.get('foo'))
一、key pattern 查询相应的key
(1)redis允许模糊查询key 有3个通配符 *、?、[]
(2)randomkey:返回随机key
(3)type key:返回key存储的类型
(4)exists key:判断某个key是否存在
(5)del key:删除key
(6)rename key newkey:改名
(7)renamenx key newkey:如果newkey不存在则修改成功
(8)move key 1:将key移动到1数据库
(9)ttl key:查询key的生命周期(秒)
(10)expire key 整数值:设置key的生命周期以秒为单位
(11)pexpire key 整数值:设置key的生命周期以毫秒为单位
(12)pttl key:查询key 的生命周期(毫秒)
(13)persist key:把指定key设置为永久有效
二、字符串类型的操作
String操作,redis中的String在在内存中按照一个name对应一个value来存储,即 key-value 模式
1. set key value [ex 秒数] [px 毫秒数] [nx/xx]
在Redis中设置值,默认,不存在则创建,存在则修改,如果ex和px同时写,则以后面的有效期为准。
参数:
ex,过期时间(秒)
px,过期时间(毫秒)
nx,如果设置为True,则只有name不存在时,当前set操作才执行
xx,如果设置为True,则只有name存在时,当前set操作才执行
例:
127.0.0.1:6379> set name shuoming ex 5
OK
127.0.0.1:6379> get name
"shuoming"
127.0.0.1:6379> get name
(nil)
2. mset key1 value1 key2 value2 : 一次设置多个值
批量设置值
如:
127.0.0.1:6379> mset n2 name2 n3 name3
或
127.0.0.1:6379> mget n2 n3 一次获取多个值
3. getset key newvalue : 设置新值获取旧值
127.0.0.1:6379> get foo
"a"
127.0.0.1:6379> getset foo b
"a"
127.0.0.1:6379> get foo
"b"
4. getrange key start stop : 获取字符串中[start, stop]范围的值
对于字符串的下标,左数从0开始,右数从-1开始
注意:当start>length,则返回空字符串
当stop>=length,则截取至字符串尾
如果start所处位置在stop右边,则返回空字符串
127.0.0.1:6379> set n1 abc123def456
OK
127.0.0.1:6379> getrange n1 1 3
"bc1"
5. setrange key offset value 把字符串的offset偏移字节改成value,如果偏移量 > 字符串长度,
该字符自动补0x00
127.0.0.1:6379> setrange n1 3 AAA
(integer) 12
127.0.0.1:6379> get n1
"abcAAAdef456"
6. setbit key offset value:设置offset对应二进制上的值,返回该位上的旧值
注意:如果offset过大,则会在中间填充0
offset最大到多少
2^32-1,即可推出最大的字符串为512M
bitcount key start end 统计设置的二进制值的个数
import redis
pool = redis.ConnectionPool(host='ServerIP', port=6379)
r = redis.Redis(connection_pool=pool)
r.setbit('uv_count',2,1)
r.setbit('uv_count',2,1)
r.setbit('uv_count',4,1)
r.setbit('uv_count,5,1)
print('n count :',r.bitcount('n'))
n count : 3
7. strlen key :取指定key的value值的长度
8. incr key :incr(self, name, amount=1)
# 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。
# 参数:
name,Redis的name
amount,自增数(必须是整数)
# 注:同incrby
>>> import redis
>>> r = redis.Redis()
>>> r.incr('pv_count',1)
1
>>> r.incr('pv_count',1)
2
>>> r.incr('pv_count',1)
3
>>> r.get('pv_count')
b'3'
9. append key value :把value追加到key 的原值上
三、 hashes类型及操作
Redis hash 是一个string类型的field和value的映射表,它的添加、删除操作都是O(1)(平均)。hash特别适用于存储对象,将一个对象存储在hash类型中会占用更少的内存,并且可以方便的存取整个对象。
配置: hash_max_zipmap_entries 64 #配置字段最多64个
hash_max_zipmap_value 512 #配置value最大为512字节
(1)hset myhash field value: 设置myhash的field为value
(2)hsetnx myhash field value: 不存在的情况下设置myhash的field为value
(3)hmset myhash field1 value1 field2 value2:同时设置多个field
(4)hget myhash field: 获取指定的hash field
(5)hmget myhash field1 field2:一次获取多个field
(6)hincrby myhash field 5: 指定的hash field加上给定的值
(7)hexists myhash field: 测试指定的field是否存在
(8)hlen myhash: 返回hash的field数量
(9)hdel myhash field: 删除指定的field
(10)hkeys myhash: 返回hash所有的field
(11)hvals myhash: 返回hash所有的value
(12)hgetall myhash: 获取某个hash中全部的field及value
例:
>>> import redis
>>> r = redis.Redis()
>>> r.hset('redis','k1','v1')
>>> r.hmset('redis',{'k2':'v2','k3':'v3'})
>>> r.hget('redis','k1')
>>> r.hmget('redis',['k1','k2'])
>>> r.hgetall('redis')
>>> r.hlen('redis')
>>> r.hkeys('redis')
>>> r.hvals('redis')
>>> r.hexists('redis','k1')
>>> r.hdel('redis','k1')
>>> r.hincrby('redis','k4',1)
四、链表操作
Redis的list类型其实就是一个每个子元素都是string类型的双向链表,链表的最大长度是2^32。list既可以用做栈,也可以用做队列,List操作,redis中的List在在内存中按照一个name对应一个List来存储。
list的pop操作还有阻塞版本,主要是为了避免轮询
(1)lpush key value:把值插入到链表头部
(2)rpush key value:把值插入到链表尾部
(3)lpop key :返回并删除链表头部元素
(4)rpop key: 返回并删除链表尾部元素
(5)lrange key start stop:返回链表中[start, stop]中的元素
(6)lrem key value count: 从链表中删除value值,删除count的绝对值个value后结束
count > 0 从表头删除 count < 0 从表尾删除 count=0 全部删除
(7)ltrim key start stop:剪切key对应的链接,移除没有在[start, stop]之间的值
(8)lindex key index:返回index索引上的值
(9)llen key:计算链表的元素个数
(10)linsert key after|before search value:在key链表中寻找search,并在search值之前|之后插入value
(11)rpoplpush source dest:把source 的末尾拿出,放到dest头部,并返回单元值
应用场景: task + bak 双链表完成安全队列
业务逻辑: rpoplpush task bak
接收返回值并做业务处理
如果成功则rpop bak清除任务,如果不成功,下次从bak表取任务
(12)brpop,blpop key timeout:等待弹出key的尾/头元素
timeout为等待超时时间,如果timeout为0则一直等待下去
应用场景:长轮询ajax,在线聊天时能用到
例:
>>> r.lpush('redis_list','v1','v2','v3')
>>> r.lpop('redis_list')
>>> r.lrange('redis_list',0,-1)
>>> r.lrem('redis_list','v1',1)
>>> r.ltrim('redis_list',1,5)
>>> r.lindex('redis_list',1)
>>> r.llen('redis_list')
>>> r.linsert('redis_list','after','v2','new_value')
>>> r.rpoplpush('redis_list','new_list')
>>> r.brpop('redis_list',3)
五、集合结构操作
特点:无序性、确定性、唯一性
(1)sadd key value1 value2:往集合里面添加元素
(2)smembers key:获取集合所有的元素
(3)srem key value:删除集合某个元素
(4)spop key:返回并删除集合中1个随机元素(可以做抽奖,不会重复抽到某人)
(5)srandmember key:随机取一个元素
(6)sismember key value:判断集合是否有某个值
(7)scard key:返回集合元素的个数
(8)smove source dest value:把source的value移动到dest集合中
(9)sinter key1 key2 key3:求key1 key2 key3的交集
(10)sunion key1 key2:求key1 key2 的并集
(11)sdiff key1 key2:求key1 key2的差集
(12)sinterstore res key1 key2:求key1 key2的交集并存在res里
例:
>>> r.sadd('redis_set','s1','s2','s3')
>>> r.smembers('redis_set')
>>> r.srem('redis_set','s1')
>>> r.spop('redis_set')
>>> r.srandmember('redis_set')
>>> r.sismember('redis_set','s1')
>>> r.scard('redis_set')
>>> r.smove('redis_set','new_set','s3')
>>> r.sinter('redis_set','set1','set2')
>>> r.sunion('redis_set','set1','set2')
>>> r.sdiff('redis_set','set1')
>>> r.sinterstore('redis_set','set1','set2')
六、有序集合
有序集合,在集合的基础上,为每元素排序;元素的排序需要根据另外一个值来进行比较,所以,对于有序集合,每一个元素有两个值,即:值和分数,分数专门用来做排序。
(1)zadd key score1 value1:添加元素
(2)zrange key start stop [withscore]:把集合排序后,返回名次[start,stop]的元素 默认是升续排列 withscores 是把score也打印出来
r.zrange( name, start, end, desc=False, withscores=False, score_cast_func=float)
# 参数:
# name,redis的name
# start,有序集合索引起始位置(非分数)
# end,有序集合索引结束位置(非分数)
# desc,排序规则,默认按照分数从小到大排序
# withscores,是否获取元素的分数,默认只获取元素的值
# score_cast_func,对分数进行数据转换的函数
# 更多:
# 从大到小排序
# zrevrange(name, start, end, withscores=False, score_cast_func=float)
# 按照分数范围获取name对应的有序集合的元素
# zrangebyscore(name, min, max, start=None, num=None,withscores=False,score_cast_func=float)
# 从大到小排序
# zrevrangebyscore(name, max, min, start=None, num=None, withscores=False,score_cast_func=float)
(3)zrank key member:查询member的排名(升序0名开始)
(4)zrem key value1 value2:删除集合中的元素
(5)zremrangebyrank key start end:按排名删除元素,删除名次在[start, end]之间的
(6)zcard key:返回集合元素的个数
(7)zcount key min max:返回[min, max]区间内元素数量
例:
>>> r.zadd('zset','k1',1,'k2',2)
>>> r.zadd('zset',k3=3,k4=4)
>>> r.zcard('zset')
>>> r.zcount('zset',2,3)
>>> r.zincrby('zset','k1',1)
>>> r.zrank('zset','k1')
七、发布订阅
# 定义公共类
import redis
class RedisHelper(object): # 公共类,发布订阅同时调用
def __init__(self):
self.__conn = redis.Redis(host='192.168.117.10')
self.chan_sub = 'fm104.5'
self.chan_pub = 'fm104.5'
def public(self, msg): # 发布方法
self.__conn.publish(self.chan_pub, msg) # 发布消息
return True
def subscribe(self): # 订阅方法
pub = self.__conn.pubsub()
pub.subscribe(self.chan_sub)
pub.parse_response() # 接收消息
return pub
订阅者
from redis_helper import RedisHelper
obj = RedisHelper()
redis_sub = obj.subscribe()
while True:
msg = redis_sub.parse_response()
print(msg)
发布者
from redis_helper import RedisHelper
obj = RedisHelper()
obj.public('hello')
八、服务器相关命令
(1)ping:测定连接是否存活
(2)echo:在命令行打印一些内容
(3)select:选择数据库
(4)quit:退出连接
(5)dbsize:返回当前数据库中key的数目
(6)info:获取服务器的信息和统计
(7)monitor:实时转储收到的请求
(8)config get 配置项:获取服务器配置的信息
config set 配置项 值:设置配置项信息
(9)flushdb:删除当前选择数据库中所有的key
(10)flushall:删除所有数据库中的所有的key
(11)time:显示服务器时间,时间戳(秒),微秒数
(12)bgrewriteaof:后台保存rdb快照
(13)bgsave:后台保存rdb快照
(14)save:保存rdb快照
(15)lastsave:上次保存时间
(16)shutdown [save/nosave]
注意:如果不小心运行了flushall,立即shutdown nosave,关闭服务器,然后手工编辑aof文件,去
掉文件中的flushall相关行,然后开启服务器,就可以倒回原来是数据。如果flushall之后,系统恰好bgwriteaof
了,那么aof就清空了,数据丢失。
(17)showlog:显示慢查询
问:多慢才叫慢?
答:由slowlog-log-slower-than 10000,来指定(单位为微秒)
问:服务器存储多少条慢查询记录
答:由slowlog-max-len 128,来做限制