04-01 redis
同步调用和异步调用一. 简介
1. 快速了解
# 存储形式:
key: value对
# 存储位置:
内存
# 支持存储的类型:
string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)
# 支持的操作: (注意: 具有原子性, 要么同时成功, 要么同时失败回滚)
push/pop、add/remove及取交集并集和差集等
2. 深入了解
1) 使用Redis有哪些好处?
(1) 速度快: 因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
(2) 支持丰富数据类型: 支持string,list,set,sorted set,hash
(3) 支持事务: 操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
2) redis相比memcached有哪些优势?
(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
(2) redis的速度比memcached快很多
(3) redis可以持久化其数据
3) redis常见性能问题和解决方案
(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
4) MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据
相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:
voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
5) Memcache与Redis的区别都有哪些?
(1) 存储方式
Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
Redis有部份存在硬盘上,这样能保证数据的持久性。
(2) 数据支持类型
Memcache对数据类型支持相对简单。
Redis有复杂的数据类型。
(3)value大小
redis最大可以达到1GB,而memcache只有1MB
6) Redis 常见的性能问题都有哪些?如何解决?
(1) Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。
(2) Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
(3) Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
(4) Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内
7) redis 最适合的场景
Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用Memcached,何时使用Redis呢?
如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点:
1 、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
2 、Redis支持数据的备份,即master-slave模式的数据备份。
3 、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
# (1) 会话缓存(Session Cache)
最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?
幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。
# (2) 全页缓存(FPC)
除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。
再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。
此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
# (3) 队列
Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。
如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。
# (4)排行榜/计数器
Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:
当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:
ZRANGE user_scores 0 10 WITHSCORES
Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。
# (5)发布/订阅
最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。
Redis提供的所有特性中,我感觉这个是喜欢的人最少的一个,虽然它为用户提供如果此多功能。
3. 支持的数据类型(5大数据类型)
redis={
k1:'123', 字符串
k2:[1,2,3,4], 列表/数组
k3:{1,2,3,4} 集合
k4:{name:lqz,age:12} 字典/哈希表
k5:{('lqz',18),('egon',33)} 有序集合
}
二. redis的安装和使用
1. linux下安装
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"
2. Windows下安装
三. Python操作Redis之安装和支持存储类型
安装redis模块
pip3 install redis
四. Python操作Redis之普通连接
介绍: redis-py提供两个类Redis
和StrictRedis
用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py
from redis import Redis
conn = Redis(host='127.0.0.1', port=6379)
conn.set('name', 'yang')
print(conn.get('name')) # b'yang'
四. Python操作Redis之连接池
1. 链接方式一: 直接链接
import redis
pool = redis.ConnectionPool(
host='127.0.0.1',
port=6379, # 提示: 本次使用host, port可以不传值. 默认已经指定
db=1, # 指定库. 默认会有db0~db15库
decode_responses=True # 指定响应的编码是否解码. 默认不解码获取的数据是bytes类型
)
conn = redis.Redis(connection_pool=pool)
conn.set('name', 'yang')
print(conn.get('name')) # yang
2. 链接方式二: 使用类方法间接链接
'''
class Redis(object):
...
connection_pool = ConnectionPool.from_url(url, db=db, **kwargs)
return cls(connection_pool=connection_pool) # cls就是Redis类.
class ConnectionPool(object):
...
return cls(**kwargs) # cls就是ConnectionPool类
'''
from redis import Redis
conn = Redis.from_url(
# 提示: Redis类中默认调用的ConnectionPool类中的from_url类方法.
# scheme:[//[user:password@]host[:port]][/]path[?query-string][#anchor]
url='redis://127.0.0.1:6379/1', # 后面的1表示链接db1库. 下面的db=1也可以指定
# db=1,
decode_responses=True,
)
print(conn.get('name')) # yang
3. 单例实现链接池
1) 定义类方法实现连接池的单例
from redis import Redis
class SingleConnectionPool(Redis):
"""使用单例模式实现连接池"""
_POOL = None
@classmethod
def single(cls, url, db=None, **kwargs):
if not cls._POOL:
cls._POOL = cls.from_url(url, db=db, **kwargs)
return cls._POOL
conn = SingleConnectionPool.single(
url='redis://127.0.0.1:6379/1',
decode_responses=True,
)
conn1 = SingleConnectionPool.single(
url='redis://127.0.0.1:6379/1',
decode_responses=True,
)
print(conn is conn1) # True -> 单例
print(conn.get('name')) # yang
2) 使用导入模块实现连接池的单例
# redis_conn.py
'''
from redis import Redis
conn = Redis.from_url(
url='redis://127.0.0.1:6379/1',
decode_responses=True,
max_connections=100, # 连接池中连接大小. 如果不指定默认是2**31次方最大
)
'''
from redis_conn import conn
from redis_conn import conn as conn1
print(conn is conn1) # True -> 单例
print(conn.get('name')) # yang
五. 操作之String操作
String操作,redis中的String在在内存中按照一个name对应一个value来存储。如图:
1. 常用方法
1) set(self, name, value, ex=None, px=None, nx=False, xx=False, keepttl=False)
'''
在Redis中设置值,默认,不存在则创建,存在则修改
参数:
ex: 过期时间(秒)
px: 过期时间(毫秒)
nx: 操作不存在的值.
nx=True时: name这个键不存在, 当前set操作生效. 否则不生效.
xx: 操作存在的值
xx=True时: name这个键存在, 当前set操作生效. 否则不生效
keepttl: 如果为真,则保留与密钥相关的生存时间。(自redis6.0开始提供)
'''
redis_obj.set('height', 180)
redis_obj.set('height', '190', ex=2)
redis_obj.set('height', '200', px=2000)
res = redis_obj.set('height', '200', nx=True) # 不存在操作
print(res) # True(值不存在) None(值存在)
res = redis_obj.set('height', '210', xx=True) # 存在操作
print(res) # True(值存在) None(值不存在)
2) get(self, name)
'''
使用: 通过name键获取对应的value值
name键存在: 返回对应name的value值
name键不存在: 返回None
'''
res = redis_obj.get('name')
print(res) # b'180'
res = redis_obj.get('name1')
print(res) # None
3) mset(self, mapping)
"""
使用: 传字典类型实现批量添加数据.
通过字典的键设置对应redis中的键.
通过字典的value值设置对应redis中的value值
"""
redis_obj.mset({'name': 'yang', 'name1': 'yang1'})
4) mget(self, keys, *args)
'''
使用: 通过传多个keys键获取多个键对应的value值. 返回列表
传迭代器类型:
mget(('name', 'age', 'sex'))
mget(['name', 'age', 'sex'])
不传迭代器类型:
mget('name', 'age', 'sex')
源码分析: 内部封装了如下的方法可以支持一下的三种写法
def list_or_args(keys, args):
# returns a single new list combining keys and args
try:
# 如果传的是可迭代对象: keys=(name, name1) 则会走这里
iter(keys)
if isinstance(keys, (basestring, bytes)):
keys = [keys]
else:
keys = list(keys)
except TypeError:
# 如果传的不是可迭代对象: keys=name, args=name1 则会走下面
keys = [keys]
if args:
keys.extend(args)
return keys
'''
res = redis_obj.mget('name', 'name1')
print(res) # [b'yang', b'yang1']
res = redis_obj.mget(('name', 'name1'))
print(res) # [b'yang', b'yang1']
res = redis_obj.mget(['name', 'name1'])
print(res) # [b'yang', b'yang1']
5) incr(self, name, amount=1)
'''
作用: 统计网站访问量,页面访问量,接口访问量
使用: 通过指定name键, 让其对应的value值, 增加 或 减少
name键存在时: 在name对应的value值之前的基础之前加上amount
name键不存在: amount作为name对应的value初始化到数据库中.
拓展: amount可以为负数
注意: name对应的value必须为整型类型, amount才能对value进行加 或者 减
'''
redis_obj.mset({'height': 180, 'width': '190'})
redis_obj.incr('height') # 存在 height: 181
redis_obj.incr('WIDTH') # 不存在 WIDTH: 1
redis_obj.incr('height', amount=10) # height: 191
redis_obj.incr('width', amount=-10) # width: 180
# 注意1: 如果不是数值类型则会抛出异常
# redis_obj.incr('name', amount=10) # redis.exceptions.ResponseError: value is not an integer or out of range
# 注意2: 如果是浮点数的形式也会抛出异常
# redis_obj.incr('height', amount=1.1) # redis.exceptions.ResponseError: value is not an integer or out of range
6) append(self, key, value)
'''
注意: 单个多次运行不会重复执行
使用: 通过key键在key对应的value基础之后附加新的value值
key键存在时: value附加到之前key对应value的末尾
key键不存在: value作为新的值
'''
res = redis_obj.mget('name', 'name3')
print(res) # [b'yang', None]
res = redis_obj.append('name', 'yang') # key存在时
print(res) # 8 name: yangyang
res = redis_obj.append('name3', 'yang') # key存在时
print(res) # 4 name3: yang
7) 总结
set: 为指定键设置值
1. ex, px: 可以控制过期事件. 秒, 毫秒
2. nx: 可以对不存在的键操作, 对存在的键不操作.
3. xx: 可以对存在的键操作, 对不存在的键不操作
get: 获取指定键值. 没有返回None
mset: 传入格式字典. 字典的key对应redis中的key. 字典的value对应redis中的value
mget: 传入1个 或 多个值, 或者传入可迭代类型. 获取传入的键批量获取对应的值. 返回列表.
incr: 传入键为其值 自增 或者 自减 指定的amount. 如果键不存在就以amount作为初始化的值.
append: 为传入的键后面的附加指定的值. 提示: 单个多次运行不会重复执行
2. 不常用方法
1) getrange(self, key, start, end)
'''
使用: 通过索引起始和结束位置获取key键对应value值
'''
redis_obj.set('name', 'yang', px=200)
res = redis_obj.getrange('name', 1, 3)
print(res) # b'ang'
2) setrange(self, name, offset, value)
'''
使用: 修改字符串内容,从指定字符串索引开始向后替换(新值太长时,则向后添加)
offset: 字符串的索引,字节(一个汉字三个字节)
value: 要设置的值
'''
redis_obj.set('name', 'yang', px=200)
redis_obj.setrange('name', 1, 'A')
res = redis_obj.get('name')
print(res) # b'yAng'
3) setex(self, name, time, value)
'''
使用: 等同于 set(self, name, value, ex=None)
time: 传入过期时间(数字秒 或 timedelta对象
'''
redis_obj.setex('name', 3, 'yang')
4) setnx(self, name, value)
'''
使用: 等同于 set(self, name, value, nx=True)
nx=True时: name这个键不存在, 当前set操作生效. 否则不生效.
'''
redis_obj.setnx('name', 'yang')
5) psetex(self, name, time_ms, value)
'''
使用: 等同于 set(self, name, value, px=None)
time_ms: 传入过期时间(数字毫秒) 或 timedelta对象
'''
redis_obj.psetex('name', 3000, 'yang')
6) setbit(self, name, offset, value)
'''
使用: 对name键对应value值的二进制表示的位进行操作
offset: 位的索引(将值变换成二进制后再进行索引)
valu: 值只能是 1 或 0
如果在Redis中有一个对应: n1 = "foo",
那么字符串foo的二进制表示为:01100110 01101111 01101111
所以,如果执行 setbit('n1', 7, 1),则就会将第7位设置为1,
那么最终二进制则变成 01100111 01101111 01101111,即:"goo"
'''
redis_obj.setex('name', 1, 'foo')
redis_obj.setbit('name', 7, 1)
res = redis_obj.mget('name')
print(res) # [b'goo']
7) getbit(self, name, offset)
'''
使用: 获取name对应的值的二进制表示中的某位的值 (0或1)
如: 1的二进制是 00000001
'''
redis_obj.psetex('name', 20, 1)
res = redis_obj.getbit('name', 7)
print(res) # 1
8) bitcount(self, key, start=None, end=None)
'''
使用: 获取name对应的值的二进制表示中 1 的个数
key: Redis的name
start: 位起始位置
end: 位结束位置
'''
9) bitop(self, operation, dest, *keys)
'''
使用: 获取多个值,并将值做位运算,将最后的结果保存至新的name对应的值
参数:
operation,AND(并) 、 OR(或) 、 NOT(非) 、 XOR(异或)
dest: 新的Redis的name
*keys: 要查找的Redis的name
如:
bitop("AND", 'new_name', 'n1', 'n2', 'n3')
获取Redis中n1,n2,n3对应的值,然后讲所有的值做位运算(求并集),然后将结果保存 new_name 对应的值中
'''
redis_obj.mset({'name': 'yang', 'name1': 'yang1'})
redis_obj.bitop('AND', 'new_name', 'name1', 'name2')
res = redis_obj.getrange('new_name', 0, 10)
print(res) # b'\x00\x00\x00\x00\x00'
10) strlen(self, name)
'''
使用: 返回name对应值的字节长度(一个汉字3个字节)
'''
redis_obj.set('name', 'yang', nx=True)
res = redis_obj.strlen('name')
print(res) # 4
11) incrbyfloat(self, name, amount=1.0)
'''
使用: 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。
参数:
name: Redis的name
amount: 自增数(浮点型)
'''
redis_obj.set('name', '99', xx=True)
redis_obj.incrbyfloat('name', amount=1.11)
redis_obj.incrbyfloat('xxxx', amount=1.11)
res = redis_obj.mget('name', 'xxxx')
print(res) # [b'100.11', b'1.11']
12) decr(self, name, amount=1)
'''
使用: 自减 name对应的值,当name不存在时,则创建name=amount,否则,则自减。
name: Redis的name
amount: 自减数(整数)
'''
redis_obj.set('name', '100', px=1)
res = redis_obj.decr('name', amount=20)
print(res) # 80
res = redis_obj.decr('name', amount=-20)
print(res) # 100
六. 操作之Hash操作
Hash操作,redis中Hash在内存中的存储格式如下图:
1. 常用方法
1) hset(self, name, key=None, value=None, mapping=None)
'''
使用: name对应的hash中设置一个键值对(不存在,则创建;否则,修改)
如: python中的字典结构 dic = {'key': 'value'}
其中redis中的name就对应着dic. key就对应的字典的key, value就对应的字典的value.
不同的是:redis中的name必须是唯一的.
'''
# 1. 设置单个方式一:
redis_obj.hset('info', 'name', 'yang')
# 注意: 值不能是列表. 只支持bytes, string, int, float类型
# redis_obj.hset('info', 'hobbies', ['play', 'run']) # Invalid input of type: 'list'. Convert to a bytes, string, int or float first.
redis_obj.hset('info', 'name', b'yang')
redis_obj.hset('info', 'sex', '男')
redis_obj.hset('info', 'height', 180)
redis_obj.hset('info', 'salaries', 10000.1)
# 2. 设置单个方式二: 不常用
redis_obj.hset('info', mapping={'weight': 125})
# 注意: 值不能有多个
# redis_obj.hset('info', mapping={'sex': '男', 'age': 18}) # wrong number of arguments for 'hset' command
2) hget(self, name, key)
'''
使用: 在name对应的hash中获取根据key获取value
'''
res = redis_obj.hget('info', 'name')
print(res) # b'yang'
res = redis_obj.hget('info', 'age')
print(res) # None
3) hmset(self, name, mapping)
'''
使用: 在name对应的hash中批量设置键值对
mapping: 字典. 如:{'k1':'v1', 'k2': 'v2'}.
'''
redis_obj.hmset('info', {'name': b'yang', 'age': 18, 'salaries': 18.8, 'sex': "男"})
# 注意: 传入的字典value只可以传bytes, string, int, float类型
# redis_obj.hmset('info', {'name': 'yang', 'hobbies': ['play', 'run']}) # Invalid input of type: 'list'. Convert to a bytes, string, int or float first.
4) hmget(self, name, keys, *args)
'''
使用: 在name对应的hash中获取多个key的值, 返回列表格式数据.
name: reids对应的name
key: 要获取key集合,如:['k1', 'k2', 'k3']
*arg: 要获取的key, 如:k1,k2,k3
提示: 内部集成了list_or_args方法. 该方法有如下作用.
不执行可迭代类型获取name中key对应的value值
指定可迭代类型获取name中key对应的value值
'''
res = redis_obj.hmget('info', 'name', 'age', 'sex')
print(res) # [b'yang', b'18', b'\xe7\x94\xb7']
res = redis_obj.hmget('info', ['name', 'age', 'sex'])
print(res) # [b'yang', b'18', b'\xe7\x94\xb7']
res = redis_obj.hmget('info', ('name', 'age', 'sex'))
print(res) # [b'yang', b'18', b'\xe7\x94\xb7']
# 提示: sex对应的value值是一个中文. 中文会有编码问题. 如果想要避免Redis类实例化时指定decode_responses=True参数
redis_obj = Redis(decode_responses=True)
res = redis_obj.hmget('info', 'name', 'age', 'sex')
print(res) # ['yang', '18', '男']
5) hincrby(self, name, key, amount=1)
'''
使用: 自增name对应的hash中的指定key的值,不存在则创建key=amount
name: redis中的name
key: hash对应的key
amount: 自增数(整数)
'''
# key存在
redis_obj.hincrby('info', 'age', amount=10)
res = redis_obj.hget('info', 'age')
print(res) # b'28'
redis_obj.hincrby('info', 'age', amount=-10)
res = redis_obj.hmget('info', 'age')
print(res) # [b'18']
# key不存在
redis_obj.hincrby('info', 'xxx', amount=10)
res = redis_obj.hmget('info', 'xxx')
print(res) # [b'10']
6) hgetall(self, name)
'''
使用: 获取name对应hash的所有键值. 返回字典格式数据.
注意: 以后想取出hash类型内所有的数据,不建议用hgetall,建议用hscan_iter
'''
res = redis_obj.hgetall('info')
print(res) # {b'name': b'yang', ... b'xxx': b'10'}
7) hscan(self, name, cursor=0, match=None, count=None)
'''
使用: 增量式迭代获取,对于数据大的数据非常有用,hscan可以实现分片的获取数据,并非一次性将数据全部获取完,从而放置内存被撑爆
name: redis的name
cursor: 游标(基于游标分批取获取数据)
match: 匹配指定key,默认None 表示所有的key
count: 指定每次分片最少获取个数,默认None表示获取所有.
'''
res = redis_obj.hscan('info', 0)
print(res) # (0, {b'name': b'yang',..., b'xxx': b'10'})
res = redis_obj.hscan('info', 0, match='age')
print(res) # (0, {b'age': b'18'})
res = redis_obj.hscan('info', count=3)
print(res) # (0, {b'name': b'yang',..., b'xxx': b'10'})
# 注意: name不存在返回空字典
res = redis_obj.hscan('xxx')
print(res) # (0, {})
8) hscan_iter(self, name, match=None, count=None)
'''
使用: 利用yield封装hscan创建生成器,实现分批去redis中获取数据
match: 匹配指定key,默认None 表示所有的key
count: 每次分片最少获取个数,默认None表示采用Redis的默认分片个数
'''
res = redis_obj.hscan_iter('info', count=10)
print(res) # <generator object Redis.hscan_iter at 0x000002294395C8E0>
for item in res:
print(item)
'''
(b'name', b'yang')
...
(b'xxx', b'10')
'''
9) 总结
hset: 通过指定hash名, 在hash名中以key, value键值对的方式存储数据.
注意: value值存储的范围. bytes, string, int, float.
hget: 通过指定hash名获取对应key下面的value值
hmset: 通过指定hash名, 传入一个字典实现批量增值. 字典的key就对应的redis中key, 字典的value就对应的redis中的value
注意: value值存储的范围. bytes, string, int, float.
hmget: 通过指定hash名, 通过hash名可以指定多个key值, 获取每个key对应的value值. 返回列表类型.
注意: 内部封装了list_or_args方法, 实现了如下的方式传多个key名
hmget('info', 'name', 'age)
hmget('info', ('name', 'age))
hmget('info', ['name', 'age])
hincrby: 通过指定hash名, 为hash名下的key中的value进行 自增 或者 自减
注意: 只能正对value值是整数形式
补充: 如果指定key不存在, 那么就会以amount初始化, 作为key的value值.
hscan:
hsan_inter: 内部调用hscan, 通过while循环, 批量将数据库中的数据分批次取出做成了迭代器.
2. 不常用方法
1) hlen(self, name)
'''
使用: 获取name对应的hash中键值对的个数
'''
res = redis_obj.hlen('info')
print(res) # 7
2) hkeys(self, name)
'''
使用: 获取name对应的hash中所有的key的值
'''
res = redis_obj.hkeys('info')
print(res) # [b'name', ..., b'age', b'xxx']
3) hvals(self, name)
'''
使用: 获取name对应的hash中所有的key的值
'''
res = redis_obj.hvals('info')
print(res) # [b'yang', ..., b'10']
4) hexists(self, name, key)
'''
使用: 返回一个布尔值,指示“key”是否存在于散列“name”中
'''
res = redis_obj.hexists('info', 'jjj')
print(res) # False
res = redis_obj.hexists('info', 'name')
print(res) # True
5) hdel(self, name, keys, *args)
'''
使用: 将name对应的hash中指定key的键值对删除
'''
res = redis_obj.hmget('info', 'xxx')
print(res) # [b'10']
res = redis_obj.hdel('info', 'xxx')
print(res) # 1
res = redis_obj.hmget('info', 'xxx')
print(res) # [None]
6) hincrbyfloat(self, name, key, amount=1.0)
'''
使用: 自增name对应的hash中的指定key的值,不存在则创建key=amount
name: redis中的name
key: hash对应的key
amount: 自增数(浮点数)
注意: 自增name对应的hash中的指定key的值,不存在则创建key=amount
'''
res = redis_obj.hget('info', 'salaries')
print(res) # b'38.799999999999997'
redis_obj.hincrbyfloat('info', 'salaries', amount=10.1)
# redis_obj.hincrbyfloat('info', 'salaries', amount=-10.1)
res = redis_obj.hscan('info')[-1].get(b'salaries')
print(res) # b'48.799999999999997'
# 注意: 指定的key不存在时. 使用amount初始化该值
redis_obj.hincrbyfloat('info', 'xxx', amount=-10.1)
res = redis_obj.hmget('info', 'xxx')
print(res) # [b'-10.1']
七. 操作之List操作
List操作,redis中的List在在内存中按照一个name对应一个List来存储。如图:
1. 常用方法
1) lpush(self, name, *values) 和 rpush
'''
使用:
lpush: 将“values”推到列表“name”的顶部
rpush: 将“values”推到列表“name”的尾部
'''
# 保存顺序: 20, 19, 18
redis_obj.lpush('age', 18, 19, 20)
# 保存顺序: 18.1, 18.2, 18.3
redis_obj.rpush('salaries', 18.1, 18.2, 18.3)
2) lpop(self, name) 和 rpop(self, name)
'''
使用:
lpop: 删除并返回列表name的第一项
rpop: 删除并返回列表的最后一项“name”
'''
# lpop
redis_obj.rpush('age', 18, 19, 20)
res = redis_obj.lpop('age')
print(res) # 18
while True:
res = redis_obj.lpop('age')
if not res:
# 注意: 指定的键中的value被弹空了以后, 返回None
print(res) # None
break
# rpop
redis_obj.lpush('age', 18, 19, 20)
res = redis_obj.rpop('age') # 18
print(res)
3) blpop(self, keys, timeout=0) 和 brpop(self, keys, timeout=0)
'''
使用: 将多个列表排列,按照从左到右弹出对应列表的元素
keys: redis的name的集合
timeout: 超时时间,当元素所有列表的元素获取完之后,阻塞等待列表内有数据的时间(秒), 0 表示永远阻塞
拓展: redis_obj.brpop(keys, timeout) 从右向左弹出数据.
爬虫实现简单分布式:多个url放到列表里,往里不停放URL,程序循环取值,
但是只能一台机器运行取值,可以把url放到redis中,多台机器从redis中取值,爬取数据,实现简单分布式
注意: 返回的是一个元组对象. 第一个值是键. 第二个值才是值
'''
# blpop
redis_obj.rpush('age', 18, 19, 20) # 20在前
redis_obj.lpush('age', 21, 22, 23) # 23在后
while True:
res = redis_obj.blpop('age', timeout=3)
print(res)
if not res:
# 注意: 指定key值被弹空以后, 会按照超时时间进行等待, 在时间之内还没有新的value数据, 那么该方结束. 返回None
print('再见!')
break
# 执行结果
'''
('age', '23')
('age', '22')
('age', '21')
('age', '18')
('age', '19')
('age', '20')
None
再见!
'''
# brpop
redis_obj.rpush('age', 18, 19, 20) # 20在前
redis_obj.lpush('age', 21, 22, 23) # 23在后
while True:
res = redis_obj.brpop('age', timeout=3)
print(res)
if not res:
# 注意: 指定key值被弹空以后, 会按照超时时间进行等待, 在时间之内还没有新的value数据, 那么该方结束. 返回None
print('再见!')
break
# 执行结果
'''
('age', '20')
('age', '19')
('age', '18')
('age', '21')
('age', '22')
('age', '23')
None
再见!
None
'''
4) lrange(self, name, start, end)
'''
使用: 在name对应的列表分片获取数据(注意: end是闭合区间)
name: redis的name
start: 索引的起始位置
end: 索引结束位置 print(re.lrange('aa',0,re.llen('aa')))
'''
redis_obj.lpush('age', 18, 19, 20)
res = redis_obj.lrange('age', 0, 1)
print(res) # ['20', '19']
5) llen(self, name)
'''
使用: name对应的list元素的个数
'''
res = redis_obj.llen('age')
print(res)
# 获取所有
res = redis_obj.lrange('age', 0, redis_obj.llen('age'))
print(res)
2. 不常用方法
1) lpushx(self, name, value) 和 rpushx(self, name, value)
'''
使用:
lpushx: 在name对应的list中添加元素,只有name已经存在时,值添加到列表的最左边
rpushx: 在name对应的list中添加元素,只有name已经存在时,值添加到列表的最右边
注意:
1. 只能往key存在的列表中附加值
2. 只能附加一个值.
'''
# lpushx
'''
redis_obj.lpushx('age', 18) # 指定的键不存在时, 操作失效
res = redis_obj.lrange('age', 0, redis_obj.llen('age'))
print(res) # []
redis_obj.lpush('age', 18)
redis_obj.lpushx('age', 19) # 指定的键存在时, 操作生效
res = redis_obj.lrange('age', 0, redis_obj.llen('age'))
print(res) # ['19', '18']
'''
'''
redis_obj.rpushx('salaries', 19.1) # 指定的键不存在时, 操作失效
res = redis_obj.lrange('salaries', 0, redis_obj.llen('salaries'))
print(res) # []
redis_obj.rpush('salaries', 17.1, 18.1)
redis_obj.rpushx('salaries', 19.1) # 指定的键存在时, 操作生效
res = redis_obj.lrange('salaries', 0, redis_obj.llen('salaries'))
print(res) # ['17.1', '18.1', '19.1']
2) linsert(self, name, where, refvalue, value)
'''
使用: 更具传入的name键, 由指定where往前 还是 往后插入的顺序, 基于指定的refvalue值前 或者 后插入指定的value值
name: redis的name
where: BEFORE或AFTER(小写也可以)
refvalue: 标杆值,即:在它前后插入数据(如果存在多个标杆值,以找到的第一个为准)
value: 要插入的数据
提示: 如果成功返回列表的新长度,如果refvalue不在名单中。 返回-1
'''
redis_obj.rpush('age', 18, 19, 20)
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['18', '19', '20']
# 往指定的refvalue前插入
new_len = redis_obj.linsert('age', 'before', '18', 1000)
print(redis_obj.lrange('age', 0, redis_obj.llen('age')), new_len) # ['1000', '18', '19', '20'] 4
# 往指定的refvalue之后插入
new_len = redis_obj.linsert('age', 'after', '1000', 9999)
print(redis_obj.lrange('age', 0, redis_obj.llen('age')), new_len) # ['1000', '9999', '18', '19', '20'] 5
# 如果refvalue不在名单中。 返回-1
res = redis_obj.linsert('age', 'before', 'xxx', 'xxxx')
print(res) # -1
3) lset(self, name, index, value)
'''
使用: 更具传入的name键, 由值定该键中索引的位置, 将该位置的值替换成传入的value值
name: redis的name
index: list的索引位置
value: 要设置的值
'''
redis_obj.lpush('age', 19, 18)
redis_obj.lpushx('age', 17)
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['17', '18', '19']
# 对name对应的list中的某一个索引位置重新赋值
redis_obj.lset('age', 0, 9999)
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['9999', '18', '19']
4) lrem(self, name, count, value)
'''
使用: 更具传入的name键指定删除对应value值的个数. 从前删除个数, 还是从后删除个数, 或者全删. 由count决定
count > 0: 删除等于从头到尾移动值的元素。
count < 0: 删除等于从尾部移动到头部的值的元素。
count = 0: 删除所有等于value的元素。
'''
# count > 0: 删除等于从头到尾移动值的元素。
redis_obj.rpush('age', 17, 18, 18, 19, 18)
redis_obj.rpushx('age', 19)
redis_obj.lpushx('age', 17)
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['17', '17', '18', '18', '19', '18', '19']
redis_obj.lrem('age', count=2, value=18) # 删除操作
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['17', '17', '19', '18', '19']
# count = 0: 删除所有等于value的元素。
redis_obj.lrem('age', count=0, value=18)
redis_obj.lrem('age', count=0, value=17)
redis_obj.lrem('age', count=0, value=19)
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # []
# count < 0: 删除等于从尾部移动到头部的值的元素。
redis_obj.rpush('age', 17, 18, 18, 19, 18)
redis_obj.rpushx('age', 19)
redis_obj.lpushx('age', 17)
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['17', '17', '18', '18', '19', '18', '19']
redis_obj.lrem('age', count=-2, value=18) # 删除操作
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['17', '17', '18', '19', '19']
5) lindex(self, name, index)
'''
使用: 在name对应的列表中根据索引获取列表元素, 负索引将返回项名单的最后一个值.
'''
redis_obj.lpush('age', 18, 19)
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['19', '18']
res = redis_obj.lindex('age', 0)
print(res) # 19
# 负索引将返回项名单的最后一个值
res = redis_obj.lindex('age', -1)
print(res) # 18
6) ltrim(self, name, start, end)
'''
使用: 在name对应的列表中移除没有在start-end索引之间的值
name: redis的name
start: 索引的起始位置
end: 索引结束位置(大于列表长度,则代表不移除任何)
提示: start和end可以和python中的切片符号一样可以是负数
'''
redis_obj.rpush('age', 18, 19, 20, 21)
redis_obj.rpop('age')
redis_obj.lpushx('age', 17)
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['17', '19', '18', '18', '19', '20']
redis_obj.ltrim('age', 0, 2)
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['17', '18', '19']
# start和end指定负数的形式
redis_obj.ltrim('age', -2, -1)
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['18', '19']
7) rpoplpush(self, src, dst)
'''
使用: 从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边
src: 要取数据的列表的name
dst: 要添加数据的列表的name
'''
redis_obj.lpush('age', 18, 19, 19)
redis_obj.lrem('age', count=1, value=19)
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['19', '18']
redis_obj.rpoplpush('age', 'age')
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['18', '19']
8) brpoplpush(self, src, dst, timeout=0)
'''
使用: 从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧
src: 取出并要移除元素的列表对应的name
dst: 要插入元素的列表对应的name
timeout: 当src对应的列表中没有数据时,阻塞等待其有数据的超时时间(秒),0 表示永远阻塞
'''
# 准备数
redis_obj.rpush('age', 18, 19)
# 有数据时
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['18', '19']
redis_obj.brpoplpush('age', 'age')
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # ['19', '18']
# 没数据时
redis_obj.lrem('age', 0, 18)
redis_obj.lrem('age', 0, 19)
print(redis_obj.lrange('age', 0, redis_obj.llen('age'))) # []
# timeout指定超时时间
res = redis_obj.brpoplpush('age', 'age', timeout=60)
print(res) # age不存在时等待了10秒打印了None
# 在别的文件中指定一下代码让阻塞时新增age键往其中插数据
res = redis_obj.rpush('age', 18, 19)
print(res)
3. 自定义增量迭代器
from redis import Redis
conn = Redis.from_url(
url='redis://127.0.0.1:6379/0',
decode_responses=True
)
# 准备数据
conn.rpush('test', *[1, 2, 3, 4, 45, 5, 6, 7, 7, 8, 43, 5, 6, 768, 89, 9, 65, 4, 23, 54, 6757, 8, 68])
def lscan_iter(name, count=10):
index = 0
while True:
data_list = conn.lrange(name, index, index + count - 1)
if not data_list:
return
# print(data_list, '\n-----')
index += count
for item in data_list:
yield item
res = lscan_iter('test')
print(res) # <generator object lscan_iter at 0x000001FF742B5410>
for value in res:
print(value)
4. 总结
lpush: 对指定的name键, 新增多个value值, 每一个值都新增到列表的左边
rpush: 对指定的name键, 新增多个value值, 每一个值都新增到列表的右边
lpop: 对指定的name键中, 从左边开始查找, 弹出指定的value值. 返回结果就可以拿到弹出的value值
rpop: 对指定的name键中, 从右边开始查找, 弹出指定的value值, 返回结果就可以拿到弹出的value值.
blpop: 对指定的name键中, 从左边开始查找, 弹出指定的value值, 如果没有那么根据指定的timeout来进行等待. 如果指定为0, 表示一直等待.
brpop: 对指定的name键中, 从右边开始查找, 弹出指定的value值, 如果没有那么根据指定的timeout来进行等待. 如果指定为0, 表示一直等待.
lrange: 对指定的name键中, 根据指定的start, end进行切片(前闭后闭), 将获取到的value值, 保存到列表中作为返回值返回.
提示: 其中切片可以同python一样可以指定负数
llen: 获取指定的name键中的value值的个数.
提示: 一般和lrange连用, 获取所有.
redis_obj.lrange('name', 0, .llen('name'))
自定义增量迭代:
def lscan_iter(name, count=10):
index = 0
while True:
data_list = conn.lrange(name, index, index + count - 1)
if not data_list:
return
index += count
for item in data_list:
yield item
八. 操作之Set操作
1. 常用方法
1)
2)
3)
4)
5)
6)
2. 不常用方法
1)
2)
3)
4)
5)
6)
九. 其它操作
1. 准备数据
from redis import Redis
redis_obj = Redis(decode_responses=True)
# 准备数据
redis_obj.rpush('age', 18, 19, 20)
redis_obj.hmset('info', {'name': 'yang', 'age': 18})
redis_obj.set('cache_data', 'This is zcDSB!')
2. exists(self, *names)
'''
使用: 返回存在的“名称”的数量.
'''
res = redis_obj.exists('age', 'info', 'xxxx')
print(res) # 2
3. keys(self, pattern='*')
'''
使用: 返回与“模式”匹配的键列表
KEYS * 匹配数据库中所有 key 。
KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
KEYS h*llo 匹配 hllo 和 heeeeello 等。
KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo
'''
# 不指定默认查找所有
res = redis_obj.keys()
print(res) # ['cache_data', 'info', 'age']
res = redis_obj.keys('*a*')
print(res) # ['cache_data', 'age']
4. delete(self, *names)
'''
使用: 删除名称中指定的一个或多个键
返回删除个数
'''
res = redis_obj.delete('age', 'cache_data')
print(res) # 2
# 删除当然库中所有(慎用)
res = redis_obj.delete(*redis_obj.keys())
print(res) # 3
5. expire(self, name, time)
'''
使用: 在键 name 上为 time 秒设置一个过期标志。
提示: 时间可以用整数或Python时间增量对象表示。
'''
import time
import datetime
redis_obj.expire('age', datetime.timedelta(seconds=3))
time.sleep(3)
res = redis_obj.exists('age')
print(res) # 0
6. rename(self, src, dst)
'''
使用: 将key“src”重命名为“dst”
'''
res = redis_obj.keys()
print(res) # ['age', 'cache_data', 'info']
redis_obj.rename('age', 'AGE')
res = redis_obj.keys()
print(res) # ['cache_data', 'info', 'AGE']
7. move(self, name, db)
'''
使用: 将键 name 移动到另一个Redis数据库 db
'''
res = redis_obj.keys()
print(res) # ['cache_data', 'info', 'age']
redis_obj.move('age', db=1)
res = redis_obj.keys()
print(res) # ['cache_data', 'info']
redis_obj = Redis(db=1, decode_responses=True)
res = redis_obj.keys()
print(res) # ['age']
8. randomkey(self)
'''
使用: 返回随机键的名称
'''
res = redis_obj.randomkey()
print(res) # age
9. type(self, name)
'''
使用: 返回键 name '的类型
'''
for name in redis_obj.keys():
res = redis_obj.type(name)
print(f'{name}: res')
# 执行结果
'''
age: res
cache_data: res
info: res
'''
十一. 利用管道开启事务
from redis import Redis
redis_obj = Redis(decode_responses=True)
pipe = redis_obj.pipeline(transaction=True)
pipe.multi() # 启动事务
redis_obj.rpush('age', '18', 19)
# raise TypeError('抛出异常!')
redis_obj.rpush('age', 20)
pipe.execute() # 提交
res = redis_obj.lrange('age', 0, redis_obj.llen('age'))
print(res)
十二. Django中使用redis
1. 通用方式
utils/redis_pool.py
from redis import Redis
conn = Redis.from_url(
url='redis://127.0.0.1:6379/0',
decode_responses=True
)
使用
from utils.redis_pool import conn
telephone_key = settings.PHONE_CODE_KEY % telephone
# 设置
conn.set(telephone_key, code, ex=60 * 10)
# 获取
conn.get(telephone_key)
2. 专用方式
下载django-redis模块
pip install django-redis
配置文件中配置
# redis配置: 配置了以后django中的cache的操作方法, 就会保存到redis中.
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {"max_connections": 100}
# "PASSWORD": "123",
}
}
}
使用
from django.core.cache import cache
telephone_key = settings.PHONE_CODE_KEY % telephone
# 设置
cache.set(telephone_key, code, 60 * 10)
# 获取
cache_code = cache.get(telephone_key)
3. 使用django-redis也可以实现redis的操作
from django_redis import get_redis_connection
conn = get_redis_connection()
conn.hmset('info', {'name': 'yang', 'age': 18})
res = conn.hgetall('info')
print(res) # {b'name': b'yang', b'age': b'18'}