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)发布/订阅


# 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连接

1 python连接redis

# 安装模块: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 连接池连接(以模块导入的方式实现单例)
# 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中右键运行的脚本所在的目录,就会被加入到环境变量

'''

2 redis字符串操作

# 各种锁:https://zhuanlan.zhihu.com/p/489305763

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 

3 redis之hash操作

'''
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()

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

4 redis之列表操作

'''

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

5 redis其他 通用操作,管道

'''
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() # 一次性执行多条命令

6 django中使用redis

# 方式一 --》自己写
# 使用连接池
# 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')


# 方式二---》使用第三方
# 安装:pip install django-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中的任意对象

redis读写分离django案例

在缓存库设置好主从复制之后就可以使用了

slaveof 127.0.0.1 6379 # 开启主从复制(写主库的ip+port)
slaveof no one  # 取消主从复制

使用django_redis的缓存

在settings.py中

CACHES = {
    # 主库负责写
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        # 在这配置主库的ip和端口
        "LOCATION": "redis://139.224.198.201:6379",  # 连接的服务器地址
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100}  # 连接池最大个数
        }
    },
    # 从库负责读
    "redis1": {
        "BACKEND": "django_redis.cache.RedisCache",
        # 在这配置从库的ip和端口
        "LOCATION": "redis://139.224.198.201:6380",  # 连接的服务器地址
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100}  # 连接池最大个数
        }
    },
}

在views.py中

# 测试
class TestAPIView(APIView):
    def get(self, request):
        # 不写参数默认是连接配置文件里CACHES字典里default对应的库(这里是主库)
        conn1 = get_redis_connection()
        # 填写配置文件里CACHES字典里从库对应的key,连接的就是从库
        conn2 = get_redis_connection(alias='redis1')
        # 主库写
        conn1.set('name', 'liyan')
        # 从库读
        name = conn2.get('name')
        print(name)
        return APIResponse(name)

使用django的缓存

和django_redis一样先在配置文件里配置好参数

class TestAPIView(APIView):
    def get(self, request):
        # 这么写也是指定配置文件CACHES字典里default对应的库
        caches.set('name', 'xixi')
        # 填写配置文件里CACHES字典里从库对应的key,连接的就是从库
        name = caches['redis1'].get('name')
        print(name)
        return APIResponse(name)
posted @ 2022-05-03 16:08  zong涵  阅读(237)  评论(0编辑  收藏  举报