导读
本文是基于python的redis学习笔记,采用python的redis模块操作,而非命令行,如果未满足您的需求还请见谅。本文的书写顺序是基于本人项目需求所写的,可能顺序有前后颠倒
1. Redis的几个用途
-
缓存
将部分操作频繁的数据放置到缓存中,以减少后台的压力,达到加快响应速度的目的。 -
网站排行榜
使用有序集合或列表结构,将计算好的排行榜存入,无需请求后台数据库,还能达到实时更新的要求。 -
计数器 – 点赞量、浏览量、剩余量
当一有人点赞、浏览或购买,这个数字就需要加1,这些数据如果使用关系型数据库储存,并发量会相当低。可以使用redis的incr(name, amount=1)来进行处理,自增1. -
消息队列
不太懂
2.Redis不适用的方向吧
Redis的特性:redis是一款非关系型、单线程的数据库,其数据是加载到缓存中的。
我们看这两个词条 单线程
,缓存
,这就意味着它的数据量不能无限大,不能做无底线的遍历查询。
- 数据量方向
当预计数据量非常大时,如几十GB,几百GB,明显不能使用redis,虽说内存已经很便宜了。 - 冷热数据方向
热数据:操作比较频繁的数据
;冷数据:操作不频繁的数据
.
热数据放到redis中,加快读写速度,从而加快响应速度。
冷数据放到redis中,由于这种数据操作频率很低,放到后台关系数据库中也无伤大雅,放到reds中算是资源的浪费。
3.Redis中键过期的使用
3.1键过期简介
python的redis包提供了conn.expire(name, time)
和conn.expireat(name, when)
两种设置键失效的模式。很容易理解,一个是多久之后过期,后面为秒或者datetime格式。后一个是设置什么时候过期,后面为秒或者datetime格式。
值得注意的是:过期后redis并不会主动去遍历删除过期的键,作为单线程来说,这么做会造成阻塞。
3.2过期后的处理
那么这些过期的键是怎么删除的呢?
redis内存管理策略提供了这么几种思路。
- 惰性删除
当请求设置过期的键时,如果已经过期,redis将会删除该键并返回空。这么做节省CPU消耗,但是反过来想,如果不请求是不是就会一直存在。这样就造成了内存的溢出。 - 定时任务删除
redis默认维护着一个定时任务,依照从每个库随机抽20个键进行检测,如果超过25%的键过期,执行删除操作,直到过期比例低于25%或者超出运行时间为止。所以就算不执行任何操作,redis也是在优化着服务器的缓存,但是只会趋于健康值,不会深度优化。 - 内存溢出控制策略
在服务器内存溢出时,redis是有对应的策略去优化内存的,默认是noeviction策略,即不删除任何数据,拒绝所有写操作,只允许读操作。显然这个默认的策略是不能去优化缓存的。还有其他的策略,如随机删除键,直到腾出足够空间为止,等等。这些还没研究明白,研究一段时间再补吧。
4.Redis的数据库管理
4.1 切换数据库
以MySQL为例,在关系型数据库中,我们可以切换database,名称可以起的很复杂来区分不同的库。与关系型数据库类似,redis也支持更换库,库与库之间相互独立。区别于关系型,redis只是使用数字来区分不同库的,默认0号数据库,redis的配置文件中,默认开启16个db,名称为[0, 1, 2, … 15]。
在实例化conn对象时,有一个db
参数,其默认值是0,可以在此更改db。
但是由于种种原因吧,redis的切换库一直不被人看好,也不建议去切换库。
class RedisProxy(object):
def __init__(self, host, port, password, db):
self.host = host
self.port = port
self.password = password
self.db = db
def connection_pool(self):
try:
pool = redis.ConnectionPool(
host=self.host,
port=self.port,
db=self.db,
password=self.password
)
RedisProxy.pool = pool
return pool
except Exception as e:
print('redis连接失败,错误信息%s'%e)
def get_conn(self):
pool = RedisProxy.pool or self.connection_pool()
conn = redis.Redis(connection_pool=pool)
return conn
4.2 清空数据库
有时确实需要完全将redis清空,而也不想以来redis自己的内存管理策略。这时,conn.flushdb()
会清空当前库的所有数据。conn.flushall()
会清空所有库的数据。
5. 数据类型简介
5.1字符串类型(string)
- 简介
字符串类型算是redis中最基础的数据类型吧,key: value模式。value的数据类型不唯一,可以是str、json、xml、int、float、bytes等,最大不超过512Mb。 - 方法
conn.set(key, value)
设置单个键的值conn.get(key)
获取单个键的值,返回bytes格式,如果不存在就返回Noneconn.mset({key: value})
批量设置键的值,传入的是字典conn.mget([key,1 key2])
批量获取键的值,返回list[value1, value2]conn.incr(key)
指定键的值自增1, 多用来计数,如浏览量。如果值不为整数,则会报错。conn.incrby(key, amount=1)
指定键自增指定数量。conn.incrbyfloat(key, amount=1.1)
指定键自增指定浮点数。conn.decr(key)
指定键的值自减1,多用来计数,如剩余量。conn.decrby(key, amount=2)
指定键的值自减指定数字。
- 使用场景
缓存技术,如用户基础信息,热门商品信息等。
键名建议使用db_name + table_name + id
例如 db_2020库里面的user表中的3用户db_2020:user:3分隔符号使用什么都行。
计数器
限时抢购、排行榜、阅读量类似场景。
共享信息,如session
分布式服务器部署,假设第一次请求进到A服务器,进行了登陆操作,如果session不共享,第二次请求进到C服务器,还需要进行登陆,这个显然是个失败的分布式。我们可以将session存到一个服务器,所有服务器到一个地方去统一验证session。
限制访问频率
如某接口需要限制10秒限制访问5次,可以== user_name:3:port_name == 设置一个过期时间为10秒。每次访问到redis里面取一次。如果有值判断是否大于5;如果没值创建新的键值对。
5.2哈希类型(hash)
- 简介
前面介绍了key:value的string数据,下面介绍一个key: {c_key1:value1, c_key2:value2}的hash类型结构。此处的key对应的value是一个对象。 - 方法
conn.hset(name, key, value)
设置一个hash键,conn.hset(‘user:1’, ‘name’, ‘leesin’)。{‘user:1’: {‘name’: ‘leesin’}}conn.get(name, key)
获取值,conn.hget(‘user:1’, ‘name’)。leesinconn.hdel(name, [key1, key2, ...])
批量删除键conn.hmset(name, {key1: 1, key2: 2})
插入多个值conn.hmget(name, [key1, ley2])
获取多个值conn.hexists(name, key)
判断某值是否存在conn.scan(cursor=0, match=None, count=None)
遍历整个reids进行筛选,不推荐使用hgetall(), hkeys(), hvals()等,可能会造成阻塞。
第一次查询,cursor为0,查询结果返回新的cursor,直到cursor再次为0;match可理解为模糊匹配
conn.scan(cursor=0, match=‘user:*’);count为一次scan查询几个结果,默认是10个。
- 使用场景
pass
5.3列表类型(list)
- 简介
值是一列有序的数组,可从左或从右进行操作,多用于队列情景。 - 方法
conn.lpush(name, *value)
将一列数据从左插入到name中。conn.rpush(name, *value)
将一列数据从右侧插入conn.lrange(name, start, end)
将name中从[start, end]元素取出conn.linsert(self, name, where, refvalue, value)
where [‘befor’, ‘after’]
在name中refvalue的where处插入value值conn.lindex(name, index)
返回列表索引为index的元素conn.llen(name)
返回列表的长度conn.lpop(name), conn.rpop(name)
将列表左侧/右侧元素删除conn.lrem(name, count, value)
将值为value的元素,从左删除count个
count >0,从左向右删除;count<0,从右向左删除;count=0,全部删除conn.ltrim(name, start, end)
按照索引,删除索引之外的值conn.lset(name, index, value)
将索引为index的值替换为valueconn.brpop(keys, timeout=0),conn.blpop(keys, timeout=0)
lpop/rpop的阻塞版,如果timeout为0,当keys均为空时,一直阻塞,当设置timeout=5时,5秒后结束。
当多个redis客户端同时针对一个列表阻塞式删除,列表内为空时。所有客户端阻塞,当向该列表插入一条值时,第一个客户端弹出,其余客户端继续阻塞。实现消息队列的契机
- 使用场景
- 消息队列
可使用lpush和brpop实现阻塞式消息队列;生产者使用lpush向队列内发布消息,多个消费者使用brpop对消息进行争抢,排在第一个的消费者总能抢到第一条消息。 - 用户文章列表
pass
5.4集合类型(set)
- 简介
集合与列表类似,可用来保存多个元素。但是不同点也很突出,不允许有重复,无序,所以不能使用索引来操作。同时set支持多个集合取交集、并集和差集(虽说本人并不清楚什么是差集)。 - 方法
conn.sadd(name, values)
向name集合中插入一个或多个值conn.srem(name, values)
Removevalues
from setname
删除name集合中的values值conn.scard(name)
Return the number of elements in setname
将name集合中的元素个数返回。该操作并不会遍历集合,而是取用的redis的内部变量,所以操作会比较快。conn.sismember(name, value)
Return a boolean indicating ifvalue
is a member of setname
检测value是否在name集合内,在返回1,不在返回0conn.srandmember(name, number=None)
Ifnumber
is None, returns a random member of setname
.
Ifnumber
is supplied, returns a list ofnumber
random members of setname
.
随机返回number个name集合内的元素,当不指定number时,仅返回一个conn.spop(name, count=None)
Remove and return a random member of setname
随机删除count个name集合中的元素,并返回这些元素。不指定count时,仅删除一个元素。conn.smembers(name)
Return all members of the setname
返回name集合内的所有元素。该方法与lrange,hgetall类似都是可能阻塞reids的方法,能不用就别用了吧。如果确实需要遍历时,可使用sscan
方法conn.sscan(name, cursor, match=None, count=None)
Incrementally return lists of elements in a set. Also return a cursor indicating the scan position.
match
allows for filtering the keys by pattern
count
allows for hint the minimum number of returns
查询name集合中的元素,返回下一次查询的cursor及指定count个元素个数。首次查询时cursor为0,mathc还支持匹配模式。conn.sinter(keys, *args)
Return the intersection of sets specified bykeys
求多个集合的交集,返回均存在于集合的元素conn.sunion(keys, *args)
Return the union of sets specified bykeys
求多个集合的并集,返回并集元素conn.sdiff(keys, *args)
Return the difference of sets specified bykeys
求多个集合的差集。conn.sinterstore(dest, keys, *args), conn.sunionstore(dest, keys, *args), conn.sdiff(dest, keys, *args)
将求出来的这些个集合,储存到dest中。如user:1集合与user:2集合
dest的命名可以为user:1_2:inter,user:1_2:union,user:1_2:diff
- 使用场景
标签
比如用户感兴趣的标签,鞋帽,手表,电子等等。再比如某标签下的用户,电子标签下有user1,user2,user3。
(1)通过sadd向标签内添加用户,或者给用户添加标签。
用户拥有的标签:user:1:tags
标签内的用户:tag:1:users
(2)通过srem删除用户标签或标签内用户,这两个操作最好放到一个事务中,以免信息不对等
(3)通过sinter,sunion,sdiff对用户的标签进行分析
随机抽取
使用spop或者srandmember从某集合随机去除一组数,用来抽奖。spop是不放回抽样,srandmember是放回抽样
6. redis的Pipeline机制
redis执行一条命令的基本流程为:
(1)发送命令
(2)命令队列
(3)执行命令
(4)返回结果
一次流程可称之为Round Trip Time (TTP),如果每一次的操作都单独请求redis服务器,这个往返时间将会变的很长,显然与redis的高吞吐原则背道而驰。redis提供了pipeline(流水线)机制,将一组命令操作打包一次性发送给redis服务器,返回一组结果。
pipeline = conn.pipeline(transaction=True,shard_hint=None)
# 此处的pipeline可以调用conn链接实例下的方法,如set,scan,sadd,等等
pipeline.set('pipe_test1', 'hello')
pipeline.sadd('pipe_test2', '1', '2')
res = pipeline.excute()