文章目录
一、Redis介绍和安装
1.介绍
1)redis是什么?
基于键值对的存储系统:字典形式,多种数据结构:字符串,hash,列表,集合,有序集合,高性能,功能丰富
-非关系型数据库:redis,mongodb,es,clickhouse,influxDB
no sql: not only sql # 不仅仅只有sql
-关系型数据库:mysql,oracle,postgrasql,sqlserver,sqlite
-去IOE ,国产化
IBM:服务器
Oracle:数据库 达梦
EMC:存储
-redis 到底是什么?
edis是缓存数据库【大部分时间做缓存,不仅仅可以做缓存】,属于非关系型数据库【区别于mysql关系型数据库】,存储是 key-value形式存储,没有表的概念。
redis是一个key-value存储系统【软件】,用c语言写的,c/s架构的软件,纯内存存储,可以持久化【断电数据可以恢复】
value:有5种数据类型
string:字符串
hash:字典
list:列表
set:集合
zset:有序集合
# 版本
最新是redis 7.x ,; redis6.x之后,采用了多进程、多线程架构;普遍使用 redis5.x 最多。
2)Redis特性
1.速度快:10w ops(每秒10w读写),数据存在内存中,c语言实现,单线程模型
2.持久化:rdb和aof
3.多种数据结构
4.五大数据结构
BitMaps位图:布隆过滤器 本质是 字符串
HyperLogLog:超小内存唯一值计数,12kb HyperLogLog 本质是 字符串
GEO:地理信息定位 本质是有序集合
5.支持多种编程语言:基于tcp通信协议,各大编程语言都支持
6.功能丰富:发布订阅(消息) Lua脚本,事务(pipeline)
简单:源代码几万行,不依赖外部库
7.主从复制:主服务器和从服务器,主服务器可以同步到从服务器中
8.高可用和分布式:
2.8版本以后使用redis-sentinel支持高可用
3.0版本以后支持分布式
7.x 版本
3)redis为什么这么快?
'redis为什么这么快?'
- qps :10w 6w左右
-1 纯内存操作,避免了io
-2 使用了io多路复用的网络模型--(epoll)
-3 数据操作是单线程单进程---【没有锁操作,没有线程间切换】
# mysql是多线程操作(有锁操作)
'redis VS mysql'
"""
redis: 内存数据库(读写快)、非关系型(操作数据方便、数据固定)
mysql: 硬盘数据库(数据持久化)、关系型(操作数据间关系、可以不同组合)
大量访问的临时数据,才有redis数据库更优
"""
#redis VS memcache
"""
redis: 操作字符串、列表、字典、无序集合、有序集合 | 支持数据持久化(数据丢失可以找回(默认持久化,主动持久化save)、可以将数据同步给mysql) | 高并发支持
memcache: 操作字符串 | 不支持数据持久化 | 并发量小
"""
4)redis应用场景
'redis应用场景'
1.当缓存数据库使用,接口缓存,提高接口响应速度
2.做计数器:单线程,不存在并发安全问题
3.去重操作:集合
4.排行榜:有序集合
5.布隆过滤器
6.抽奖
7.消息队列
2.安装
mac 源码编译安装
linux 源码编译安装
win 微软自己,基于源码,改动,编译成安装包
# 最新5.x版本 https://github.com/tporadowski/redis/releases/
# 最新3.x版本 https://github.com/microsoftarchive/redis/releases
一路下一步,安装完释放出两个命令,会把redis自动加入到服务中
'注意安装第一页记得勾上,那个是配置到环境变量中,其他一路下一步即可'
-redis-server # mysqld 服务端的启动命令
-redis-cli # mysql 客户端的启动命令
-h默认127.0.0.1,-p默认6379,-n默认0,-a默认无
完整连接:
>: redis-cli -h ip地址 -p 端口号 -n 数据库编号 -a 密码
先连接,后输入密码
>: redis-cli -h ip地址 -p 端口号 -n 数据库编号
>: auth 密码
-redis.windows.conf # my.ini 配置文件
databases 16 # 修改默认数据库数量
port 6379 # 修改端口号(监听端口)
bind 127.0.0.1 # 修改ip地址(服务地址)监听0.0.0.0即可别人访问
'redis默认有16个库(可在配置文件中修改databases 16),默认连进去就是第0个'
3.启动服务
'前提:前往一个方便管理redis持久化文件的逻辑再启动服务:dump.rdb'
'mysql也可以通过命令启动服务,而redis也可以,这里就不说在服务中启动了'
1)前台启动服务
>: redis-server # 这个启动没有配置文件
2)配置文件启动前台服务
>: redis-server 配置文件的绝对路径
3)后台启动服务
>: redis-server --service-start
注)Linux系统后台启动(或是修改配置文件,建议采用方式)
>: redis-server &
4)配置文件启动后台服务
注)windows系统默认按Redis安装包下的redis.windows-service.conf配置文件启动
>: redis-server --service-start
注)Linux系统可以完全自定义配置文件(redis.conf)后台启动
>: redis-server 配置文件的绝对路径 &
"""
windows系统
1)前台启动
i)打开终端切换到redis安装目录
>: cd C:\Apps\Redis(可直接在redis目录地址栏输入cmd)
ii)启动服务
>: redis-server redis.windows.conf
2)后台启动
i)打开终端切换到redis安装目录
>: cd C:\Apps\Redis(可直接在redis目录地址栏输入cmd)
ii)启动服务(后面的配置文件可以省略)
>: redis-server --service-start redis.windows-service.conf
"""
# 关闭服务
1.在服务中直接找到redis点击关闭
2.直接在运行窗口执行shutdown(在客户端执行这个命令) # 得运行起来
3.直接在运行窗口Ctrl+C结束运行(在服务端执行这个) # 得运行起来
# redis数据是存在内存中得
-重启redis服务或关机,数据都会丢失
-咱们不会丢 redis.windows-service.conf 已经写了持久化方案
从内存把数据保存到硬盘上的过程称之为持久化('永久保存')
二、Python操作Redis(普通链接)
# 安装模块
pip install redis
# 1.导入模块类
from redis import Redis
# 2.实例化得到对象
conn = Redis(host="localhost",port=6379,db=0) # 不传也行,这些就是默认参数
conn = Redis(decode_responses=True) # 这个就是转码默认返回bytes格式
# 3.获取值
# print(conn.get('name')) # 默认是返回bytes格式 b'jack'
# 前提是redis数据库中要有这条记录,并且是在0库中。否则会显示None
# 分步操作(转换成字符)
# res = conn.get('name')
# print(res.decode(encoding='utf-8'))
# 连着操作(转换成字符)
# print(conn.get('name').decode(encoding='utf-8'))
# 强转操作(转换成字符)
res = conn.get('name')
print(str(res,encoding='utf-8'))
# 4.设置值
# 默认字符串形式存储是以 utf-8 形式存储
conn.set('gender','male') # key:value形式
# 因为是k:v形式所以第二次执行同一个key的时候只会替换掉之前的value值
'前提还是同一个库中,还是字符串类型的操作'
conn.set('gender','female')
# 5.关闭
conn.close()
三、Redis连接池链接
-django中操作mysql,默认是没有链接池
#一个请求就是一个mysql连接。可能会有问题,并发数过高,导致mysql连接数过高,影响mysql性能;
#使用django连接池:https://blog.51cto.com/liangdongchang/5140039
-redis默认是自带链接池的
'''
redis使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池
'''
-1.直接使用(基础)
import redis # 只导入到包那层
# 创建池
pool = redis.ConnectionPool(max_connections=3, # 最大链接数3
host="localhost",port=6379,
decode_responses=True) # 解码 是以链接池作为链接,所以解码在这里
# max_connections = max_connections or 2**31 默认的最大链接数是2的31次方
# # 每次从池中取出一个链接
conn = redis.Redis(connection_pool=pool)
print(conn.get('name'))
conn.close() # 关闭,把链接放回到连接池
'========================================================'
-2.直接使用(起线程)
import redis
from threading import Thread # 起线程
# 创建池
pool = redis.ConnectionPool(max_connections=3, # 最大链接数3
host="localhost",port=6379,
decode_responses=True)
# 起10个线程
def task():
# 每次执行都实例化一个对象
conn = redis.Redis(connection_pool=pool)
print(conn.get('name'))
conn.close() # 关闭,把链接放回到连接池
l = []
for i in range(10):
# 因为起了十个线程,但是连接池最大3个,所以会有7个报错
t = Thread(target=task)
t.start()
l.append(t)
for t in l:
t.join()
print('结束执行')
# 直接使用情况,项目中其他地方要使用,那么每次都需要实例化一个新的对象
# 这样太麻烦了,所以我们可以做成一个包的形式,导入使用,这样想要使用的地方就直接导入即可(单例)
'========================================================'
-3.python中如何实现单例设计模式
'将连接池做成一个单例模式,后续使用直接导入即可'
# 使用模块导入的方式,无论导入多少次,使用的都是同一个
--pool.py
import redis # 只导入到包那层
# 创建池
POOL = redis.ConnectionPool(max_connections=3,host="localhost",port=6379,decode_responses=True)
--xxx.py
import redis # 只导入到包那层
from threading import Thread # 起线程
from pool import POOL # 导入
def task():
# 创建一个连接池,保证它是单例,全局只有一个pool对象:使用模块导入方式实现单例
# 做成模块后,导入,无论导入多少次,导入的都那一个POOL对象
conn = redis.Redis(connection_pool=POOL)
print(conn.get('name'))
conn.close() # 关闭,把链接放回到连接池
l = []
for i in range(3):
t = Thread(target=task)
t.start()
l.append(t)
for t in l:
t.join()
print('结束执行')
1.小测试案例
1.直接方式 test.py
class Person:
pass
p1 = Person()
p2 = Person()
print(p1==p2)
# False 结果为不同的Person类
2.导入方式 ll.py
from test import p1
from test import p1 as p2
print(p1==p2) # True
四、Redis之字符串操作
'字符串类型的基本方法使用'
'''redis字符串操作'''
import redis
# 这里我就不导入池操作了,这里也只有一个链接用,池现在设置了3个链接,没有必要
pool = redis.ConnectionPool(max_connections=1,decode_responses=True)
# pool = redis.ConnectionPool(max_connections=1,)
conn = redis.Redis(connection_pool=pool)
1 set(name, value, ex=None, px=None, nx=False, xx=False)
'''
ex,过期时间(秒)
px,过期时间(毫秒)
nx,如果设置为True,则只有name不存在时,当前set操作才执行, 值存在,就修改不了,执行没效果
xx,如果设置为True,则只有name存在时,当前set操作才执行,值存在才能修改,值不存在,不会设置新值
'''
conn.set('hobby','rap') # set设置值
conn.set('age',19,ex=3) # 设置值存放在redis数据库中3秒后清除
conn.set('age',19,px=3000) # 设置值存放在redis数据库中3秒后清除
conn.set('age',19,px=3000) # 设置值存放在redis数据库中3秒后清除
conn.set('age',16,nx=True) # 当name不存在,可执行设置,否则无法设置,无效
conn.set('xxxx',16,xx=True) # 当name存在,可执行设置,否则无法设置,无效
2 setnx(name, value) # 等同于conn.set('age',19,nx=True)
conn.setnx('age',19)
3 setex(name,time,value) # 等同于conn.set('age',19,ex=3)
conn.setex('age',3,22)
4 psetex(name, time_ms, value) # 存在多少毫秒清除
conn.psetex('xxx',3000,'six') 和setex相同,只是时间单位不同
5 mset(*args, **kwargs) # 批量设置 字典形式{}也可以=号
conn.mset({'xxx': 'six', 'yyy': 'seven'}) # 已存在的会替换value
6 get(name) 获取值
print(conn.get('name')) # 默认redis是bytes格式,我上面池以及解码了
7 mget(keys, *args) # 批量获取 列表形式[],也可以直接逗号分割
print(conn.mget(['name','gender']))
print(conn.mget('name','gender'))
8 getset(name, value) # 先获取再设置,先获取存在的值(老),在设置新值
print(conn.getset('name','tom')) # oscar
print(conn.getset('age',23)) # None 不存在值是返回None,并设置
9 getrange(key, start, end) # 取的是字节,前闭后闭区间
'''
utf-8:中文3个字节,英文1个字节
字节:8个比特位一个字节,一个字母字符一个字节就够了
字符:a,b,国 ,中文utf-8编码需要3个字节一个字符
'''
# 取3bytes 因为是取的bytes,前面设置了解码需要去掉,不然只要是中文就得是3的倍数,否则报错
print(conn.getrange('name',0,1)) # 因为是中文,只拿到2个字节,所以报错
print(conn.getrange('name',0,2).decode(encoding='utf-8')) # 去掉解码自己在这里加
print(conn.getrange('name',0,2))
10 setrange(name, offset, value) # 从某个起始位置开始替换字符串
# 注意这个也是取的bytes格式,如果有中文没有设置3的倍数截断,会变乱
conn.setrange('name',2,66)
'''比特位---操作'''
11 setbit(name, offset, value)
12 getbit(name, offset)
print(conn.getbit('name',0)) # 只能获取到0,1
13 bitcount(key, start=None, end=None)
14 bitop(operation, dest, *keys)
'''比特位---操作'''
15 strlen(name) # 统计字节长度
print(conn.strlen('name'))
print(len('彭于晏')) # 统计字符长度
16 incr(self, name, amount=1) # incrby 不写默认自增1
# 自增,不会出并发安全问题,单线程架构,并发量高
conn.incr('age',amount=1) # 计数器 amount是单次所需加的数量
conn.incrby('age',amount=1) # 和incr一模一样
17 incrbyfloat(self, name, amount=1.0) 自增浮点数
conn.incrbyfloat('age',amount=1.0)
18 decr(self, name, amount=1) 不写默认自减1
conn.decr('age',amount=2)
19 append(key, value) # 追加,如果不存在key则新增,否则在当前value值后面追加
conn.append('tian','xxxx')
conn.append('age',6) # 数字也是在后面加,不会有运算
conn.append('name','nb')
conn.close() # 关闭链接
'常用的get、setsetex、getrange、setrange、strlen、append'
五、Redis之hash操作
'''redis哈希(字典)操作'''
import redis
pool = redis.ConnectionPool(max_connections=1, decode_responses=True)
# pool = redis.ConnectionPool(max_connections=1,)
conn = redis.Redis(connection_pool=pool)
1 hset(name, key, value)
'''
# name,redis的name
# name对应的hash中设置一个键值对(不存在,则创建;否则,修改)
# key,name对应的hash中的key
# value,name对应的hash中的value
'''
conn.hset('userinfo','name','oscar')
conn.hset('userinfo','age',19)
# 相当于[userinfo:{'name':'oscar','age':19}]
2 hmset(name, mapping) 批量设置
# 在name对应的hash中批量设置键值对
# 批量设置,被弃用了,以后都使用hset
conn.hmset('userinfo',mapping={'hobby':'rap','xxx':66})
3 hget(name,key)
# 在name对应的hash中获取根据key获取value
print(conn.hget('userinfo','age'))
4 hmget(name, keys, *args) 批量获取
# 在name对应的hash中获取多个key的值
print(conn.hmget('userinfo',['age','xxx'])) # 设置[]的都会给keys
print(conn.hmget('userinfo','age','xxx')) # 不设置的第二个以后都会给*args
5 hgetall(name)
# 获取name对应hash的所有键值 慎用
# 可能会造成 阻塞 尽量不要在生产代码中执行它
print(conn.hgetall('userinfo'))
6 hlen(name)
# 获取name对应的hash中键值对的个数
print(conn.hlen('userinfo'))
7 hkeys(name)
# 获取name对应的hash中所有的key的值
print(conn.hkeys('userinfo'))
8 hvals(name)
# 获取name对应的hash中所有的value的值
print(conn.hvals('userinfo'))
9 hexists(name, key)
# 检查name对应的hash是否存在当前传入的key 存在True否则False
print(conn.hexists('userinfo','age'))
10 hdel(name,*keys)
# 将name对应的hash中指定key的键值对删除
print(conn.hdel('userinfo', 'age', 'hobby'))
print(conn.hdel('userinfo','xxx')) # 会返回影响的行数
11 hincrby(name, key, amount=1) # 不写amount默认为1
# 自增name对应的hash中的指定key的值,不存在则创建key=amount
conn.hincrby('userinfo','age',amount=1)
12 hincrbyfloat(name, key, amount=1.0) # 不写amount默认为1.0 浮点数
conn.hincrbyfloat('userinfo','age',amount=1.2) #会有偏差
13 hscan(name, cursor=0, match=None, count=None)
'''
# 增量式迭代获取,对于数据大的数据非常有用,hscan可以实现分片的获取数据,
# 并非一次性将数据全部获取完,从而放置内存被撑爆
# name,redis的name
# cursor,游标(基于游标分批取获取数据)
# match,匹配指定key,默认None 表示所有的key
# count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
'''
# hgetall 会一次性全取出,效率低,可以能占内存很多
# hscan分批获取,hash类型是无序
# 插入一批数据
# for i in range(1000):
# conn.hset('hast_count', i, f"鸡蛋{i}")
'''当数据量小的时候,hash是有序排列的,这个时候,使用hscan设置count则无效'''
#HSCAN是为了处理大量数据而设计的,可能也是因为这个原因,在数据量较少的情况下count参数并不会生效
# for i in range(500):
# conn.hset('xxxx_test', i, f"鸡蛋{i}")
# print(conn.hgetall('map_demo')) # 一次性取全部数据
# 一点点的取
# 它不单独使用,拿的数据,不是特别准备
# count 是要取的条数,但是不准确,有点上下浮动
# res = conn.hscan('map_demo',cursor=5,count=20)
# res = conn.hscan('hast_text',cursor=0,count=20)
# print(res) # (数字,{拿出来的5条数据})
'''
当遍历只包含Integer值的Set集合(也称为intsets),或者ziplists类型编码的Hash或者Sorted Set集合(
说明这些集合里面的元素占用的空间足够小),那么SCAN命令会返回集合中的所有元素,直接忽略COUNT属性。
'''
14 hscan_iter(name, match=None, count=None) # 取出所有数据,但是是一点点取(count值),一点点用
# 内部是一个生成器 ,它内部封装了hscan,做成了生成器,分批取hash类型所有数据
# res = conn.hscan_iter('xxxx_test',count=10) # 这个数字并不是取10条,而是每次取10条,一直到所有取尽
res = conn.hscan_iter('hast_count',count=10)
print(res) # <generator object ScanCommands.hscan_iter at 0x0000019266AFF660>
# generator 只要函数中有yield关键字,这个函数执行的结果就是生成器 ,生成器就是迭代器,可以被for循环
for item in res:
print(item)
conn.close()
'hset、hget、hmget、hlen、hincr、hscan_iter'
六、Redis之list操作
'''redis列表操作'''
import redis
pool = redis.ConnectionPool(max_connections=1, decode_responses=True)
# pool = redis.ConnectionPool(max_connections=1,)
conn = redis.Redis(connection_pool=pool)
1 lpush(name, values) # 当不存在此name时会直接创建
# 在name对应的list中添加元素,每个新的元素都添加到列表的最左边 (上左下右)
conn.lpush('girls','刘亦菲') # 从左侧插入值
conn.lpush('girls','迪丽热巴')
2 rpush(name, values) 表示从右向左操作
conn.rpush('girls','赵美延') # 从右侧插入值
3 lpushx(name, value)
# 在name对应的list中添加元素,只有name已经存在时,值添加到列表的最左边
conn.lpushx('girls','金玟庭')
conn.lpushx('boys','彭于晏') # 当name不存在时无效
4 rpushx(name, value) 表示从右向左操作
# 只有name已经存在时,值添加到表示从右向左操作
conn.rpushx('girls','裴珠泫')
conn.lpushx('boys','金城武') # 当name不存在时无效
5 llen(name)
# name对应的list元素的个数
print(conn.llen('girls'))
6 linsert(name, where, refvalue, value))
# 在name对应的列表的某一个值前或后插入一个新值
# where 条件 before是什么前面,after是什么后面
# refvalue 是指定在那个value值,之前或之后的(where条件)
# value值
conn.linsert('girls', where='before', refvalue='赵美延', value='田振姬') # before什么之前
conn.linsert('girls',where='after',refvalue='赵美延',value='柳智敏') # after什么之后
conn.linsert('girls',where='before',refvalue='xxx',value='xxxx') # 到name对应的列表中无此value则无效,返回值为-1
7 lset(name, index, value) # 按照列表的索引来计算从0开始
# 对name对应的list中的某一个索引位置重新赋值
conn.lset('boys',0,'彭于晏')
conn.lset('boys',2,'金城武') # 当name中不存在此索引就报错
8 lrem(name, num(count),value) 按关键字传递则无需对应位置,否则需要对应位置
# 在name对应的list中删除指定的值
'''
# name,redis的name
# value,要删除的值
# count,count=0,删除列表中所有的指定值;
# count=2,从前到后,删除2个;
# count=-2,从后向前,删除2个
count表示删除对应的value的个数,正数为从上到下找,负数为从下往上
注意redis的列表是可以重复名的
'''
conn.lrem('boys',0,'郭富城') # 当name对应的列表不存在此value则无效
conn.lrem('boys',1,'吴彦祖') # 正数为从左往右删除对应value的第一个值
conn.lrem('boys',-1,'金城武') # 负数为从右往左删除对应的value的第一个值
conn.lrem('boys',0,'彭于晏') # 0表示删除对应的value的所有值
9 lpop(name)
# 在name对应的列表的左侧获取第一个元素并在列表中移除,返回值则是第一个元素
print(conn.lpop('boys')) # 弹出对应name列表中最上面的一个元素,并且返回出来
10 rpop(name) 表示从右向左操作
print(conn.rpop('boys')) # 弹出对应的name列表中的最下面的一个元素,并返回出来
11 lindex(name, index) # 列表索引从0开始
# 在name对应的列表中根据索引获取列表元素
print(conn.lindex('boys',0)) # 获取对应name的列表索引所对应的元素
print(conn.lindex('xxx',0)) # 当获取的name没有时返回None
12 lrange(name, start, end)
'''
# 在name对应的列表分片获取数据 获得到是闭合区间
# name,redis的name
# start,索引的起始位置
# end,索引结束位置
'''
print(conn.lrange('girls',2,4)) # 从索引2到4获取
# 当对应的name所在的列表中没有相对应的索引数时,只获取有的索引数的元素,不会报错
print(conn.lrange('boys',2,4)) ['金城武']
print(conn.lrange('xxx',2,4)) # 当无对应的name时,返回空列表[]
13 ltrim(name, start, end) # 从0开始的索引
# 在name对应的列表中移除没有在start-end索引之间的值
# 只要是打印,所有无效或者有对应name都,返回True
conn.ltrim('boys',3,5) # name对应的列表中,只保留设置的start-end索引之间的值,其他移除
print(conn.ltrim('xxx',3,5)) # 当不存对应的name时无效,不会报错
14 rpoplpush(src, dst)
# 从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边
# src,要取数据的列表的name
# dst,要添加数据的列表的name
# 当第一次执行的时候,会把第一个列表(src)的元素翻转后拿最下面的一个,本质还是拿原初的最上面的第一个
# 如果按照队列的说法就是弹出最后进入的值,在redis中默认添加值是放在上面
# print(conn.rpoplpush('boys','test'))
# 当第二个name对应列表不存在时(dst),第一个存在会创建出第二个列表对应的name,并把第一个列表的值传入进去
print(conn.rpoplpush('boys','lll')) 会返回src取出的元素
print(conn.rpoplpush('yyy','lll')) # 当src对应的的列表不存在时无效,返回None
15 blpop(keys, timeout) # 阻塞式弹出,可以做消息队列,分布式
# 将多个列表排列,按照从左到右去pop对应列表的元素
'''
# keys,redis的name的集合
# timeout,超时时间,当元素所有列表的元素获取完之后,阻塞等待列表内有数据的时间(秒), 0 表示永远阻塞
# 从左到右弹出,当对应的name中没有值了就会阻塞在这里
# 在另一个程序中如果在创建出这个name就会把它弹出,这样就结束阻塞
'''
print(conn.blpop('boys')) # 不设置tiemout默认为0,表示永远阻塞,直到重新设置此name结束或者直接中断程序
print(conn.blpop('yyy',timeout=10)) # 当没有对应的name列表只阻塞10秒就结束运行,并返回None
print(conn.blpop('yyy'))
16 brpop(keys, timeout),从右向左获取数据
print(conn.brpop('boys')) # 和blpop一样也是阻塞式,不过是从下到上弹出
17 brpoplpush(src, dst, timeout=0)
'''
# 从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧
# 参数:
# src,取出并要移除元素的列表对应的name
# dst,要插入元素的列表对应的name
# timeout,当src对应的列表中没有数据时,阻塞等待其有数据的超时时间(秒),0 表示永远阻塞
'''
conn.close() # 关闭链接
'常用:lpush、llen、lpop、lindex、lrange、blpop'
七、Redis其他操作
'''redis通用操作'''
import redis
pool = redis.ConnectionPool(max_connections=1, decode_responses=True)
# pool = redis.ConnectionPool(max_connections=1,)
conn = redis.Redis(connection_pool=pool)
'''通用操作,不指定类型,所有类型都支持'''
1.delete(*names)
# name根据删除redis中的任意数据类型
conn.delete('name','age') # 直接删除name,age
2.exists(name)
# name检测redis中的name是否存在 存在返回1否则0
print(conn.exists('name')) # 0
print(conn.exists('gender')) # 1
3.keys(pattern='*') # 不写pattern默认把所有key都拿出来
# pattern=* 根据模型获取redis的name
# ?表示一个字符, * 表示多个字符
print(conn.keys(pattern='h*'))
print(conn.keys('?e'))
4.expire(name ,time)
# name,time为某个redis的某个name设置超时时间
conn.expire('yyy',3)
5.rename(src, dst)
# src,dst, 对redis的name重命名为
conn.rename('xxx','yyy')
6.move(name, db))
# name,db,将redis的某个值移动到指定的db下
# 默认操作都是0 库,总共默认有16个库
conn.move('yyy',2)
7.randomkey() # 随机获取键值
# 随机获取一个redis的name(不删除)
print(conn.randomkey())
8.type(name)
# 获取name对应值的类型
print(conn.type('gender')) # string
print(conn.type('userinfo')) # hash
conn.close()
八、Django中使用Redis
1.方式一:通用方式
1.创建一个pool.py文件
import redis # 导入redis模块
# 创建池
POOL = redis.ConnectionPool(max_connections=3, decode_responses=True)
2.配置路由
from django.urls import path
from . import views
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('redis',views.RedisView,'redis')
urlpatterns = []
urlpatterns += router.urls
2.在任意视图中使用,想用就导入即可
import redis
from utils.pool import POOL # 导入创建的池
class RedisView(ViewSet):
def list(self, request, *args, **kwargs):
conn = redis.Redis(connection_pool=POOL)
conn.incrby('count')
count = conn.get('count')
return APIResponse(message=f"您是第{count}个访问的")
2.方式二:django-redis
1.安装django-redis
pip install django-redis
2.在配置文件中配置
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",
}
}
}
# 3.在想用的地方直接导入使用即可
from django_redis import get_redis_connection
class RedisView(ViewSet):
def list(self, request, *args, **kwargs):
conn = get_redis_connection() # 从池中获取一个链接
conn.incrby('count')
count = conn.get('count')
return APIResponse(message=f"您是第{count}个访问的")
3.方式三:django的缓存使用redis
'使用django内置的cache缓存,然后配置好使用缓存的位置例如内存中,等等'
'但是放在内存中,重启项目数据就没有了'
'后期我们要把缓存数据放到redis中,因为redis可以持久化,项目重启,但是redis还在运行,数据就不会丢失'
1.在配置文件中配置如下,以后cache.set和cache.get 统统都是去redis中
'如果不这样配置cache也可以直接使用cache.set/cache.get,看设置在哪里存储缓存,会丢失'
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",
}
}
}
# 强大之处在于,可以直接缓存任意的python对象,底层使用pickle实现的
2.在想使用的地方,导入使用即可
from django.core.cache import cache
class RedisView(ViewSet):
def list(self, request, *args, **kwargs):
try:
count = cache.get('count') + 1
cache.set('count', count)
except Exception as e:
cache.set('count', 1)
count = 1
return APIResponse(message=f"您是第{count}个访问的")
重点:
-优势:redis 分数据类型, 只能设置5种数据类型
-django的缓存来讲 ,不限制类型,可以放python的任意类型
-django的缓存来讲,cache.set('redis的key','不区分类型:放python的任意类型')
# 放个对象,字典什么的都可以,这样到时候使用cache.get取出来还是原来的
-cache.get('userinfo')
-django cache 底层是基于: 把你存储的类型---》使用pickle序列化--》bytes格式---》当redis的字符串形式存到redis中
-以后咱们做redis的操作,可以直接使用django的缓存, 不需要考虑类型
九、接口缓存
以我们现在的项目首页轮播图接口,现在只要有一个用户访问一次首页,就会去数据库查询
这样用户量大了之后,同时来访问首页,就会不停的查询banner表
'''
我们现在可以使用django的cache来优化,第一次去数据库查询,然后把数据取出来放到缓存中,
以后只要访问轮播图接口,都会从缓存中取出来,这样接口的响应速度就会非常快
'''
# 使用轮播图加缓存
from rest_framework.viewsets import GenericViewSet
from utils.mixins import CommonListModelMixin
from . import models
from django.conf import settings
from .serializer import BannerSerializer
from rest_framework.mixins import ListModelMixin
from django.core.cache import cache
from utils.common_response import APIResponse
from utils.common_logger import logger
class BannerView(GenericViewSet, CommonListModelMixin):
queryset = models.Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
serializer_class = BannerSerializer
def list(self, request, *args, **kwargs):
'''基础版'''
# # 第一次缓存中是没有,就需要去数据库中查询,然后放入到缓存中
# banner_list = cache.get('banner_list') # 是一个列表套字典格式
# if banner_list:
# return APIResponse(result=banner_list)
# else:
# # 如果缓存中有了,那么就去缓存中取出来
# # 因为我想要用ListModelMixin,所以直接知名道姓的使用,然后把自己传入进去self(因为我继承的是自己封装的,如果直接使用super就是调用封装的,而不是原本的)
# res = ListModelMixin.list(self, request, *args, **kwargs)
# banner_list=res.data
# cache.set('banner_list', banner_list)
# return APIResponse(result=banner_list)
'''优化版'''
# 第一次缓存中是没有,就需要去数据库中查询,然后放入到缓存中
banner_list = cache.get('banner_list') # 是一个列表套字典格式
if not banner_list:
logger.info('这里去数据库中查询了')
# 如果缓存中有了,那么就去缓存中取出来
# 因为我想要用自己封装的ListModelMixin,所以直接知名道姓的使用,然后把自己传入进去self
res = ListModelMixin.list(self, request, *args, **kwargs)
banner_list = res.data
cache.set('banner_list', banner_list)
return APIResponse(result=banner_list)
'但是这种如果后期数据库的banner表数据变了,它也不会去查询,而是一直取换成中的数据,这样会导致数据上的不一致'
# 我们得解决这个问题
-这个问题有个学名(专业的叫法):双写一致性
mysql,redis要同步写
-封装一个mixin的类,不需要重写list方法,只需要配置一下,就能使用缓存
'utils/mixins.py'
'''缓存版'''
from django.core.cache import cache
from utils.common_logger import logger
class CacheListModelMixin(ListModelMixin):
cache_key = None
def list(self, request, *args, **kwargs):
if self.cache_key:
res = cache.get(self.cache_key)
if not res:
logger.info('执行了去数据库中查询')
response = super().list(request, *args, **kwargs)
cache.set(self.cache_key,response.data)
res = response.data
return APIResponse(result=res)
else:
logger.info('用户没配置缓存的key,都是查询的数据库')
res = super().list(request, *args, **kwargs)
return APIResponse(result=res.data)
'''最终版,使用django缓存'''
from utils.mixins import CacheListModelMixin
class BannerView(GenericViewSet, CacheListModelMixin):
cache_key = 'banner_list'
queryset = models.Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
serializer_class = BannerSerializer
# 后期,所有带有查询所有或单条的接口,都可以加上缓存