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 ,从后向前,删除19.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存储带有变量名的值,存储方式为字典。
posted @   yiwufish  阅读(75)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示