Redis非关系型数据库
Redis非关系型数据库
1.介绍
目前最火的缓存数据库,非关系型数据库,特点是快速存放值可设置持久化,qps高达10W左右(理想情况下,一般达不到)
纯内存操作,网络模型使用的IO多路复用(使用了epoll)
6.x之前,单进程,单线程架构,没有线程进程间切换,更少的消耗资源
支持的数据类型
string(字符串)、list(列表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型),哈希是字典
什么是关系型非关系型?
关系型数据库
数据的组织方式有明确的表结构
id | name | password
非关系型数据库
数据的组织方式没有明确的表结构
{'name':'jason'}
数据之间无法直接建立数据库层面关系
2.安装
目前市面上主流版本5.x居多,6.x有一些,7.x基本没有,6及以上改了架构
最新5.x版本 https://github.com/tporadowski/redis/releases/
最新3.x版本 https://github.com/microsoftarchive/redis/releases
下载msi 安装包直接运行,记得勾选一下添加到环境变量即可
安装完成后,会个系统的服务中添加一个redis服务,其实本质就是执行启动的exe,并加载配置
3.启动
1.服务中点击启动
2.cmd敲redis-server.exe redis.windows.conf
如果没看到一个魔方样的正方形图像,参考下面的博客
https://blog.csdn.net/shenmomingzihao/article/details/122417407
启动服务端后不要关,再另外起一个cmd,输入redis-cli,随便存点键值对看看能不能成功(redis-cli 默认连接本地6379端口)
- redis-cli -h 地址 -p 端口 可以访问指定的redis服务端
图形化操作软件
可以使用 RESP.app ( resp-2022.1.0.0) 这款软件
5.python操作redis
安装一下redis模块
pip install redis
---------------------------------
使用:
导入先
from redis import Redis
conn = Redis(host='127.0.0.1',port=6379,db=15) # db=0意思是选第一个库
这个实例化的对象就可以来点对象的方法了
conn.set()
conn.get()
6.连接池
当有程序和redis数据库建立了连接,只要不执行close,这条连接就一直在,如果高并发情况下,建立的通道太多会影响redis性能,这时候就要引入连接池,通俗点就是管家,客人申请来见面需要等待
使用连接池
1.先导入
import redis
2.创建池子,池子连接数据库
Pool = redis.ConnectionPool(max_connections=10, host='127.0.0.1', port=6379,db=15) # db是用第十五个库,不写默认0
3.实例化对象,同时指定池
conn = redis.Redis(connection_pool=Pool)
print(conn.get('name'))
--------------------------------------
连接池+多线程例子
import redis
from redis import Redis
from threading import Thread
Pool = redis.ConnectionPool(max_connections=10, host='127.0.0.1', port=6379, db=15)
def Test():
conn = redis.Redis(connection_pool=Pool)
print(conn.get('name'))
for i in range(12):
t = Thread(target=Test)
t.start()
注意,因为是用了连接池,多线程一开启就会有12个连接启动,10个进入池子然后去数据库拿到了打印出来,剩下2个拿不到了,直接报错,报错两次
7.一些通用命令
1.keys * -------------------- 查看目前存的所有K
还有变种玩法:
keys('*a') 含有a的K,模糊匹配
keys('?a') ?代表一个,匹配xa的K
2.delete(*names)
能删除多个
3.exists(name)
检测redis的name是否存在
4.expire(name ,time)
给name设置过期时间
5.rename(src, dst)
重命名
6.move(name, db)
移动库
7.randomkey()
随机弹射出一个K,不会删除原来的
conn.set('一等奖','一台笔记本')
conn.set('二等奖','一台自行车')
conn.set('三等奖','一只钢笔')
conn.set('鼓励奖','哎呀,再接再厉哦')
print(str(res,encoding='utf-8'))
print(str(conn.get(res),encoding='utf-8'))
8.type(name)
获取name对应值的类型
8.Redis数据类型之字符串
用的最多,做缓存,做计数器等等
1 set(name, value, ex=None, px=None, nx=False, xx=False)
# ex,过期时间(秒)
# px,过期时间(毫秒)
# nx,如果设置为True,则只有name不存在时,当前set操作才执行, 值存在,就修改不了,执行没效果
# xx,如果设置为True,则只有name存在时,当前set操作才执行,值存在才能修改,值不存在,不会设置新值
# redis---》实现分布式锁,底层基于nx实现的
2 setnx(name, value)
# 等同于:conn.set('name','lqz',nx=True)
3 setex(name, time,V)
# 等同于:conn.set('name','lqz',ex=3)
4 psetex(name, time_ms, value)
5 mset(*args, **kwargs)
# conn.mset({'wife': '刘亦菲', 'hobby': '篮球'})
6 get(name)
# print(str(conn.get('wife'),encoding='utf-8'))
# print(conn.get('wife'))
7 mget(keys, *args)
# res=conn.mget('wife','hobby')
# res=conn.mget(['wife','hobby'])
# print(res)
8 getset(name, value)
# res=str(conn.getset('wife','迪丽热巴'),encoding='utf-8')
# res=conn.getset('wife','迪丽热巴')
# print(res)
9 getrange(key, start, end)
# res = str(conn.getrange('wife', 0, 2), encoding='utf-8') # 字节长度,不是字符长度 前闭后闭区间
# print(res)
10 setrange(name, offset, value)
# conn.setrange('wife',2,'bbb')
# ---- 比特位---操作
11 setbit(name, offset, value)
12 getbit(name, offset)
13 bitcount(key, start=None, end=None)
# ---- 比特位---操作
14 bitop(operation, dest, *keys) 获取多个值,并将值做位运算,将最后的结果保存至新的name对应的值
15 strlen(name)
# res=conn.strlen('hobby') # 统计字节长度
# print(res)
16 incr(self, name, amount=1)
# 自增,不会出并发安全问题,单线程架构,并发量高
# conn.incr('age')
# print(conn.get('age'))
17 incrbyfloat(self, name, amount=1.0)
# conn.incrbyfloat('age',1.2)
18 decr(self, name, amount=1)
# conn.decrby('age')
# conn.decrby('age',-1)
19 append(key, value)
# conn.append('hobby','sb')
print(conn.strlen('hobby'))
conn.close()
-----------------------------------
重点:
set
get
strlen 字节长度
incr
decr
9.Redis数据类型之字列表
V对应的是列表
1.lpush(K,V) 从左至右插入数据
V可以是列表也可以是打散的一个个V
2.rpush同理
3.lpushx(K,V)
从左至右追加,K必须存在才能追加,否则不会创建K
V得是字符串
4.rpushx 同理
5.llen(K)
统计V的元素个数
6.linsert(K,where,who,数据)
给哪个K下面的哪个元素,或者是前或者是后插入数据
where 只能为 before/after
比如:conn.linsert('city','after','changsha','hangzhou')
给city 下的 changsha行 的后一行插入 hangzhou
如果who不存在,无法插入
7.lset(name, index, value)
按索引改值,索引不在范围会报错
8.lrem(name, num,值)
自上而下删除值
num =0 ,删除列表中所有的值
num =1 ,从前往后,删除1个
num = -1 ,从后向前,删除1个
9.lpop/rpop(K)
移除K列表的第一个元素/最后一个元素
10.lindex(K,索引值)
根据索引拿值
res = conn.lindex('city', 2)
print(str(res,encoding='utf-8')) # 转码支持中文
11.lrange(K,开始,结束)
从列表拿一堆值,按字节拿
res = conn.lrange('city', 0, -1)
print(res)
12.ltrim(K, start, end)
在K对应的列表中移除没有在start-end索引之间的值
end 不能超过列表Len,不然啥都不会移除
13.rpoplpush(K1, K2)
移除K1最后的一个元素,添加到K2的开头列
14.blpop(K, timeout) # 重要,可以用作简单的消息队列
b就是 block,阻塞的意思
如果K中没有元素能Pop掉,程序会一直等,等到设置的时间过后才结束
能用于做分布式,提高程序效率,比如说爬虫中使用
15.brpop(K, timeout)
同理
16.brpoplpush(K1, K2,time)
阻塞式的弹出
自定义增量迭代
# 由于redis类库中没有提供对列表元素的增量迭代,如果想要循环name对应的列表的所有元素,那么就需要:
# 1、获取name对应的所有列表
# 2、循环列表
# 但是,如果列表非常大,那么就有可能在第一步时就将程序的内容撑爆,所有有必要自定义一个增量迭代的功能:
import redis
conn=redis.Redis(host='127.0.0.1',port=6379)
# conn.lpush('test',*[1,2,3,4,45,5,6,7,7,8,43,5,6,768,89,9,65,4,23,54,6757,8,68])
# conn.flushall()
def scan_list(name,count=2):
index=0
while True:
data_list=conn.lrange(name,index,count+index-1)
if not data_list:
return
index+=count
for item in data_list:
yield item
print(conn.lrange('test',0,100))
for item in scan_list('test',5):
print('---')
print(item)
10.Redis数据类型之字Hash
V就是字典,不过专业点叫法称为哈希
1 hset(name, key, value)
给name设置一个键值对,如果存在,就修改
2 hmset(name, 一堆键值对)
批量设置键值对,用{}包裹
3 hget(name,key)
拿值,没啥好说的
4 hmget(name, keys, *args)
一次拿多个值
一样有两种写法
r.mget('xx', ['k1', 'k2'])
r.hmget('xx', 'k1', 'k2')
5 hgetall(name)
拿所有键值对,不太好,如果数据过多,直接给内存撑爆
6 hlen(name)
获取name对应的hash中键值对的个数
7 hkeys(name)
获取name对应的hash中所有的key的值
8 hvals(name)
获取name对应的hash中所有的value的值
9 hexists(name, key)
检查name中是否有key这个K,返回布尔值
res = conn.hexists('userinfo','age')
print(res)
10 hdel(name,*keys)
将name对应的hash中指定key的键值对删除
可以指定多个
11 hincrby(name, key, amount=1)
自增name对应的hash中的指定key的值,不存在则创建key=amount
参数:
name,redis中的name
key, hash对应的key
amount,自增数(整数)
conn.hincrby('userinfo','age',amount=1)
print(conn.hget('userinfo','age'))
12 hincrbyfloat(name, key, amount=1.0)
小数同理
13 hscan(name, cursor=0, match=None, count=None)
cursor游标,索引位置
count 是拿几个
返回一个元组(下一个游标位置,{数据})
返回的数据有可能和count对不上,所以不常用这个玩意
14 hscan_iter(name, match=None, count=None)
返回一个迭代器,这个迭代器是按照count数拿完的,其实这个迭代器就是hgetall,已经有了所有数据,不过每次拿的时候都是一点点的拿,不会撑爆内存,能节约内存
----------------------------
重点:
hset
hget
hmget
hlen
hscan_iter
11.Redis之管道(注意P.)
其实就是事务,保证要执行的操作都要同时进行同时完成
redis支持事务吗 单实例才支持所谓的事物,支持事务是基于管道的
原理
把要执行的命令,放到一根管道中,只有执行excute后才执行放的命令,并且都执行完成
conn = Redis(connection_pool=Pool)
P = conn.pipeline(transaction=True) # True就是开启事务
P.multi() # 固定搭配,开启批量处理命令
P.decr('老王的账户',100)
raise Exception('啊!')
P.incr('老李的账户',100)
P.execute() #开始执行
conn.close()
---------------------------
注意,在管道中,是用管道对象去.方法!!!!
12.django中使用redis
1.自定义通用方案——所有框架都可以用
随便写一个试试
访问的前缀路径自己设定
@action(methods=['GET'],detail=False)
def GoGoGo(self,request):
pool = redis.ConnectionPool(max_connections=100, db=1)
conn = Redis(connection_pool=pool)
conn.incr('访问量')
myDict = {'网页访问量': conn.get('访问量')}
return APIResponse(data=myDict)
2.django方案
需要下载第三方模块 django-redis
django缓存的底层是用pickle序列化,就算cache和redis打通了,存也只是用了redis的字符串类型去存,就算是存对象也一样
第一种:
django的缓存使用redis
去配置里添加东西,如果不添加,就是用django默认的配置,使用内存
原本自带的:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
}
}
我们去dev中添加的
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}
}
}
}
# "CONNECTION_POOL_KWARGS": {"max_connections": 100} 就是连接池
然后:
@action(methods=['GET'], detail=False)
def GoGoGo(self, request):
res = int(cache.get('访问量')) # 这个先不执行,先在下面设置一个0,不然因为取不到会报错
cache.set('访问量',res+1 ) # cache.set('访问量',0 )
myDict = {'网页访问量': cache.get('访问量')}
return APIResponse(data=myDict)
第二种:
第三方:django-redis模块
from django_redis import get_redis_connection
@action(methods=['GET'], detail=False)
def GoGoGo(self, request):
# res = int(cache.get('访问量'))
# cache.set('访问量',res+1 )
conn = get_redis_connection()
conn.incr('count')
print(conn.get('count'))
myDict = {'网页访问量': conn.get('count')}
return APIResponse(data=myDict)
补充
1.django中使用连接池:https://blog.51cto.com/liangdongchang/5140039
2.单例:全局就只有一个该对象,省内存空间
https://www.cnblogs.com/wznn125ml/p/16874873.html
3.python环境下产生的.pyc文件是代码编译执行过一次后产生的文件,下次只要看到有pyc就直接拿过来,所以导模块执行代码后产生Pyc,下次如果还要用就直接拿Pyc,就不再执行
4.redis不存在并发安全的问题,且效率高,因为单线程单进程
5.迭代器生成器概念回顾
只要迭代器中有yield,一执行的结果就是生成器,生成器就是迭代器
生成器范围小一点
6.回忆*args **args
*args和 **args都具有储存多余变量的功能,区别在于 *args储存没有变量名的值,存储方式为元组;而 **args存储带有变量名的值,存储方式为字典。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现