Redis在Python中的操作
Redis是什么
#1 redis 是一个非关系型数据库(区别于mysql关系型数据库,关联关系,外键,表),nosql数据库(not only sql:不仅仅是SQL),数据完全内存存储(速度非常快) #2 redis就是一个存数据的地方 #3 redis是 key --value 存储形式---》value类型有5大数据类型---》字符串,列表,hash(字典),集合,有序集合 # java:hashMap 存key-value形式 # go:maps 存key-value形式 #4 redis的好处 (1) 速度快,因为数据存在内存中,类似于字典,字典的优势就是查找和操作的时间复杂度都是O(1) (2) 支持丰富数据类型,支持string,list,set,sorted set,hash (3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行 (4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除 #5 redis 最适合的场景---》主要做缓存---》它又叫缓存数据库 (1)会话缓存(Session Cache)---》存session---》速度快 (2)接口,页面缓存---》把接口数据,存在redis中 (3)队列--->celery使用 (4)排行榜/计数器--->个人页面访问量 (5)发布/订阅
Redis安装
# 6 安装---》c语言写的开源软件---》官方提供源码---》如果是在mac或linux上需要 编译,安装 -redis最新稳定版版本6.x -win:作者不支持windwos,本质原因:redis很快,使用了io多路复用中的epoll的网络模型, -这个模型不支持win,所以不支持(看到高性能的服务器基本上都是基于io多路复用中的epoll的网络模型,nginx), -微软基于redis源码,自己做了个redis安装包,但是这个安装包最新只到3.x,又有第三方组织做到最新5.x的安装包 安装包---》编译完成的可执行文件---》下一步安装 linux--》make成可执行文件---》make install 安装 -linux,mac平台安装 # 7 win下载地址 // 最新5.x版本 https://github.com/tporadowski/redis/releases/ // 最新3.x版本 https://github.com/microsoftarchive/redis/releases 一路下一步安装 # mysql 有个图形化客户端-Navicat很好用 # redis 也有很多,推荐你用rdb,https://github.com/uglide/RedisDesktopManager/releases 收费,99元永久,白嫖 # redis纯内存操作,有可能把内存占满了,这个配置是最多使用多少内存 # redis服务的启动与关闭 方式一:win上,就在服务中了,把服务开启即可,在服务中启动关闭 方式二:命令启动,等同于mysqld redis-server redis.windows-service.conf redis-server 配置文件 # 客户端连接 -命令行:redis-cli -p 端口 -h 地址 -客户端 :rdb连接
# 安装模块:pip install redis # django中有没有连接池? -没有,django中一个请求就会创建一个mysql连接,django并发量不高,mysql能撑住 -想在django中使用连接池,有第三方:https://www.cnblogs.com/wangruixing/p/13030755.html # python实现单例的5种方式 http://liuqingzheng.top/python/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E9%AB%98%E9%98%B6/19-%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E9%AB%98%E7%BA%A7%E5%AE%9E%E6%88%98%E4%B9%8B%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/ class Person: pass p1=Person() p2=Person() print(p1 is p2) # 以模块导入的方式实现单例 # 什么是进程,线程,协程?
# from redis import Redis # 1 普通连接 # conn = Redis(host="localhost",port=6379,db=0,password=None) # conn.set('name',"lqz") # res=conn.get('name') # print(res) # 2 连接池连接(以模块导入的方式实现单例) # redis_pool # import redis # 第一步:先要构造一个池 # POOL必须是单例的,全局只有一个实例,无论程序怎么执行,POOL是同一个对象 # POOL=redis.ConnectionPool(max_connections=10,host="localhost",port=6379) # import redis # from redis_pool import POOL # # 第二步:从池中拿一个连接 # conn=redis.Redis(connection_pool=POOL) # print(conn.get('name')) # 多线程演示 from threading import Thread import redis import time from redis_pool import POOL # 真报错吗?不会报错, def get_name(): conn=redis.Redis(connection_pool=POOL) print(conn.get('name')) for i in range(10): t=Thread(target=get_name) t.start() time.sleep(2) ''' 1 咱们这个py作为脚本运行,不能使用相对导入 2 只能使用绝对导入 3 从环境变量中开始到导起 4 在pycharm中右键运行的脚本所在的目录,就会被加入到环境变量 '''
# 各种锁:https://zhuanlan.zhihu.com/p/489305763 ''' 1 set(name, value, ex=None, px=None, nx=False, xx=False) ex,过期时间(秒) px,过期时间(毫秒) nx,如果设置为True,则只有name不存在时,当前set操作才执行,值存在,就修改不了,执行没效果 xx,如果设置为True,则只有name存在时,当前set操作才执行,值存在才能修改,值不存在,不会设置新值 2 setnx(name, value) 2 setex(name, value, time) 3 psetex(name, time_ms, value) 4 mset(*args, **kwargs) 5 get(name) 5 mget(keys, *args) 6 getset(name, value) 7 getrange(key, start, end) 8 setrange(name, offset, value) 9 setbit(name, offset, value) 10 getbit(name, offset) 11 bitcount(key, start=None, end=None) 12 bitop(operation, dest, *keys) 13 strlen(name) 14 incr(self, name, amount=1) 15 incrbyfloat(self, name, amount=1.0) 16 decr(self, name, amount=1) 17 append(key, value) ''' import redis conn=redis.Redis() # 1 set(name, value, ex=None, px=None, nx=False, xx=False) # conn.set('age',19) # ex,过期时间(秒)---->过期时间 # px,过期时间(毫秒) ---->过期时间 # conn.set('wife','lyf',ex=3) # nx,如果设置为True,则只有name不存在时,当前set操作才执行, 值存在,就修改不了,执行没效果 # conn.set('wife','dlrb',nx=True) # xx,如果设置为True,则只有name存在时,当前set操作才执行,值存在才能修改,值不存在,不会设置新值 # conn.set('wife','dlrb',xx=True) # 2 setnx(name, value)--->等同于conn.set('wife','dlrb',nx=True) # 2 setex(name, value, time)--->conn.set('wife','lyf',ex=3) # 3 psetex(name, time_ms, value)--->conn.set('wife','lyf',px=3) # 4 mset(*args, **kwargs)--》批量设置 # conn.mset({'name1':'pyy','age1':20}) # 5 get(name) # print(conn.get('age1')) # 5 mget(keys, *args) # print(conn.mget(['age1','age'])) # print(conn.mget('name','age','age1')) # 6 getset(name, value) # print(conn.getset('name','dsb')) # 它跟get,再set有什么区别? # 7 getrange(key, start, end) # print(conn.getrange('name',0,1)) # 取字节 ,前闭后闭 # 8 setrange(name, offset, value) # conn.setrange('name',1,'qqq') # dsb--->dqqq # 9 setbit(name, offset, value) ---》后面再聊---》独立用户统计---》用户量过亿---》日活 # conn.setbit('name',1,0) # 改的是比特位,d 一个byte占8个比特位--》2进制---》10进制---》字符 # 10 getbit(name, offset) # print(conn.getbit('name',1)) # 11 bitcount(key, start=None, end=None) # print(conn.bitcount('name',0,1)) # 数字指的是字节,不是比特位 # 13 strlen(name) # 字节(一个byte)和字符(中 ? a 都是一个字符) # 面试题:mysql中utf8和utf8mb4有什么区别? # utf8---》不是咱们任务的utf-8,mysql字节的,两个字节表示一个字符---》生僻字,表示存不了 # utf8mb4--》utf-8,最多4个字节表示一个字符---》存标签,存生僻字 # print(conn.strlen('name')) # 字节--》9--》gbk编码一个中文占2个字节 utf-8编码 大部分一个中文占3个字节,生僻字可能占4 # 14 incr(self, name, amount=1)--->做计数器--》记录博客访问量--》博客表的文章上加个访问量字段,一旦有一个人访问,数字+1 # conn.incr('age') # 不存在并发安全的问题---》redis6.0之前是单线程架构,并发访问操作,实际只排着队一个个来 # 15 incrbyfloat(self, name, amount=1.0) # 16 decr(self, name, amount=1) # conn.decr('age') # 17 append(key, value) # conn.append('name','nb') # 记住:get set strlen
''' 1 hset(name, key, value) 2 hmset(name, mapping) 3 hget(name,key) 4 hmget(name, keys, *args) 5 hgetall(name) 6 hlen(name) 7 hkeys(name) 8 hvals(name) 9 hexists(name, key) 10 hdel(name,*keys) 11 hincrby(name, key, amount=1) 12 hincrbyfloat(name, key, amount=1.0) 13 hscan(name, cursor=0, match=None, count=None) 14 hscan_iter(name, match=None, count=None) ''' import redis conn=redis.Redis() # 1 hset(name, key, value) # conn.hset("userinfo",'name','彭于晏') # conn.hset("userinfo_01",mapping={'name':"刘亦菲",'age':18}) # 2 hmset(name, mapping)---》弃用了-->直接使用hset即可 # conn.hmset("userinfo_02",mapping={'name':"刘亦菲",'age':18}) # 3 hget(name,key) # print(str(conn.hget('userinfo_01','name'),encoding='utf-8')) # print(str(conn.hget('userinfo_01','age'),encoding='utf-8')) # 4 hmget(name, keys, *args) # print(conn.hmget('userinfo_01',['name','age'])) # print(conn.hmget('userinfo_01','name','age')) # 5 hgetall(name)--->慎用,有可能数据量很大,会撑爆内存-->一般我们redis服务器使用内存很大的服务器,应用服务器内存小一些 # print(conn.hgetall('userinfo_01')) # 6 hlen(name) # print(conn.hlen('userinfo_01')) # 2 # 7 hkeys(name) # print(conn.hkeys('userinfo_01')) # [b'name', b'age'] # 8 hvals(name) # print(conn.hvals('userinfo_01')) # [b'\xe5\x88\x98\xe4\xba\xa6\xe8\x8f\xb2', b'18'] # 9 hexists(name, key) # print(conn.hexists('userinfo_01','name')) # print(conn.hexists('userinfo_01','height')) # 10 hdel(name,*keys) # conn.hdel('userinfo_01','name') # 11 hincrby(name, key, amount=1) # conn.hincrby('userinfo_01','age') # 12 hincrbyfloat(name, key, amount=1.0) # 因为hgetall不安全,有可能数据量过大,所以尽量使用,迭代取值 # 13 hscan(name, cursor=0, match=None, count=None) hash类型无序----》python 字典在3.6以后有序了,如何实现的? # for i in range(1000): # conn.hset('hash_test','id_%s'%i,'鸡蛋%s号'%i) # 分批获取,但是由于没有顺序,返回一个cursor,下次基于这个cursor再继续获取 # res=conn.hscan('hash_test',0,count=20) # print(res) # res=conn.hscan('hash_test',352,count=20) # print(res) # print(len(res[1])) # 14 hscan_iter(name, match=None, count=None) #全取出所有值,分批取,不是一次性全取回来,减小内存占用 # res=conn.hscan_iter('hash_test',count=10) # generator # # print(res) # for item in res: # print(item) # print(conn.hgetall('hash_test')) # hset hget hlen hexists
''' 1 lpush(name,values) 2 lpushx(name,value) 3 llen(name) 4 linsert(name, where, refvalue, value)) 4 r.lset(name, index, value) 5 r.lrem(name, value, num) 6 lpop(name) 7 lindex(name, index) 8 lrange(name, start, end) 9 ltrim(name, start, end) 10 rpoplpush(src, dst) 11 blpop(keys, timeout) 12 brpoplpush(src, dst, timeout=0) ''' import redis conn=redis.Redis() # 1 lpush(name,values) # conn.lpush('girls','lyf','dlrb') # 图形界面看到的 上面是左, 下面是右 # conn.rpush('girls','杨颖') # 2 lpushx(name,value) 在name对应的list中添加元素,只有name已经存在时,值添加到列表的最左边 # conn.lpushx('girls','杨颖1') # conn.lpushx('girl','杨颖1') # 3 llen(name) # print(conn.llen('girls')) # 4 linsert(name, where, refvalue, value)) # conn.linsert('girls','before','lyf','张杰') # conn.linsert('girls','after','lyf','lqz') # 4 r.lset(name, index, value) 对name对应的list中的某一个索引位置重新赋值 # conn.lset('girls',1,'lqz') # 5 r.lrem(name, count,value) # conn.lrem('girls',1,'lqz') # 从左侧删第一个 # conn.lrem('girls',-1,'lqz') # 从右侧删第一个 # conn.lrem('girls',0,'lqz') # 全删 # 6 lpop(name) # res=conn.lpop('girls') # print(res) # r=b'\xe6\x9d\xa8\xe9\xa2\x961' # print(str(r,encoding='utf-8')) # 7 lindex(name, index) # print(conn.lindex('girls',1)) # 8 lrange(name, start, end) # print(conn.lrange('girls',0,1)) # 前闭后闭 # 9 ltrim(name, start, end) ---》修剪,只保留起始到终止 # conn.ltrim('girls',1,2) # 10 rpoplpush(src, dst) # 从第一个列表的右侧弹出,放入第二个列表的左侧 # 11 blpop(keys, timeout) # 阻塞式弹出--》可以做消息队列 -->block-->如果没有值,会一直阻塞 # 作用,可以实现分布式的系统---》分布式爬虫 # 爬网页,解析数据,存入数据库一条龙,一个程序做 # 写一个程序,专门爬网页---》中间通过redis的列表做中转 # 再写一个程序专门解析网页存入数据库 # print(conn.blpop('girls',timeout=1)) # 12 brpoplpush(src, dst, timeout=0) # lpush lpop linsert llen blpop
''' delete(*names) exists(name) keys(pattern='*') expire(name ,time) rename(src, dst) move(name, db)) randomkey() type(name) ''' import redis conn=redis.Redis() # delete(*names) # conn.delete('name','name1','hash1') # exists(name) # print(conn.delete('name')) # print(conn.delete('age')) # keys(pattern='*') # 打印所有key * 和 ? # print(conn.keys()) # print(conn.keys('us*')) # print(conn.keys('age?')) # expire(name ,time) # conn.expire('age',3) # rename(src, dst) # conn.rename('wife','girl') # move(name, db)) # conn.move('girl',3) # randomkey() # 随机返回一个key # print(conn.randomkey()) # type(name) # print(conn.type('age1')) # print(conn.type('userinfo'))
管道---》redis本身是不支持事务的-->有的时候我们要实现类似这种功能:张三-100块钱,李四+100块钱
通过管道实现--->把多次操作的命令放到一个管道中,一次性执行,要么都执行了,要么都不执行
通过管道可以实现事务
import redis pool = redis.ConnectionPool() conn = redis.Redis(connection_pool=pool) pipe = conn.pipeline(transaction=True) pipe.multi() # 以后用pipe代替conn操作 pipe.set('name', 'lqz') # raise Exception('asdfasdf') pipe.set('role', 'nb') # 只是往管道中放了命令,还没执行 pipe.execute() # 一次性执行多条命令
# 方式一 --》自己写 # 使用连接池 # pool.py import redis POOL=redis.ConnectionPool(max_connections=10,host="localhost",port=6379) # 任意位置使用 class TestView(APIView): def get(self, requeste): conn=redis.Redis(connection_pool=POOL) print(conn.get('name')) return Response('ok') # 方式二---》使用第三方 djagno-redis # 安装:pip install djagno-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_redis import get_redis_connection conn=get_redis_connection() print(conn.get('name')) # 一旦这么配置了,以后django的缓存也缓存到reids中了 cache.set('asdfasd','asdfas') # 以后在django中,不用使用redis拿连接操作了,直接用cache做就可以了 # 不需要关注设置的值类型是什么 cache.set('wife',['dlrb','lyf']) # value值可以放任意数据类型 # 底层原理,把value通过pickle转成二进制,以redis字符串的形式存到了redis中 # pickle是python独有的序列化和反序列化,只能python玩,把python中所有数据类型都能转成二进制 通过二进制可以在反序列化成功pyhton中的任意对象
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通