Redis
Redis
Redis介绍与安装
为什么用redis缓存速度快?
```python
1 纯内存操作
2 高性能的网络模型 IO多路复用(epoll)
3 单线程,不存在线程间切换
```
-
简介
redis
是缓存数据库【大部分时间做缓存,不仅仅可以做缓存】,属于非关系型数据库【区别于mysql关系型数据库】,存储是key-value
形式存储,没有表的概念。-nosql:非关系型的数据库 - c语言写的 服务(监听端口),用来存储数据的,数据是存储在内存中,取值,放值速度非常快, 10w qps
-
版本
最新是
redis 7.x
,; redis6.x之后,采用了多进程、多线程架构;普遍使用redis5.x
最多。 -
安装
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 客户端的启动命令 # 安装目录 redis-server redis-cli redis.windows-service.conf 配置文件 bind 127.0.0.1 # 服务地址 port 6379 # 监听的端口 ...... # redis默认有16个库,默认连进去就是第0个
详情安装路径:https://www.cnblogs.com/liuqingzheng/p/9831331.html
-
启动redis
方式一
服务中找到redis----》点击启动(后台启动)
方式二
命令启动
redis-server 指定配置文件 不指定就选默认配置
-
客户端连接redis
方式一
# cmd连接 redis-cli # 默认连接本地的6379端口-----python的3306端口 # 指定窗口 redis-cli -h 地址 -p 端口 #连接指定地址和端口 补充知识: -mysql 也是cs架构软件 -pymysql :是mysql的客户端 -Navicate:是mysql客户端 -go语言操作mysql:是mysql客户端
方式二
图形化界面
使用图形化客户端操作,图形化界面,连接redis 输入地址和端口,点击连接即可Redis Desktop Manager :开源的,原来免费,后来收费了 推荐用(mac,win,linux 都有) -Qt5 qt是个平台,专门用来做图形化界面的 -可以使用c++写 -可以使用python写 pyqt5 使用python写图形化界面 (少量公司再用) -resp-2022.1.0.0.exe 一路下一步,安装完启动起来 -Redis Client 小众
目前使用的客户端和服务端都在同一台机器,本地的客户端可以连接远程的服务器
方式三
python的redis模块操作redis
python连接redis
使用python
当客户端操作redis
,需要安装redis
pip install redis
django
中操作mysql
,没有连接池的,一个请求就是一个mysql连接。可能会有问题,并发数过高,导致mysql连接数过高,影响mysql性能;使用django连接池:https://blog.51cto.com/liangdongchang/5140039
1.普通连接
# 安装redis 模块:pip install redis
# 1 导入模块的Redis类
from redis import Redis
# 2 实例化得到对象
conn = Redis(host='127.0.0.1', port=6379)
# 3 使用conn,操作redis
# 获取name的值
# res = conn.get('name') # 返回数据是bytes格式
# 4 设置值
conn.set('age',19)
conn.close()
2.连接池连接---单例模式
redis-py
使用connection pool
来管理对一个redis server
的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池
pool.py
将连接池做成一个单例模式,后续使用直接导入即可
import redis
POOL = redis.ConnectionPool(max_connections=10, host='127.0.0.1', port=6379) # 创建一个大小为10的redis连接池
# 单例模式:设计模式 23 中设计模式
全局只有一个 这个对象,导入多次都只会执行一次
p1=Person() # p1 对象
p2=Person() # p2 新对象
# 引入单例模式的6种方式
-1 模块导入方式
# 测试代码
import redis
from threading import Thread
from pool import POOL
def task():
# 做成模块后,导入,无论导入多少次,导入的都那一个POOL对象
conn = redis.Redis(connection_pool=POOL) # 报错的原因是拿连接,池里不够了,没有等待,线程报错 设置等待,参数
print(conn.get('name'))
for i in range(1000):
t = Thread(target=task) # 每次都是一个新的连接,会导致 的连接数过多
t.start()
每次连接都是新的连接,会导致连接数多于连接池的个数,线程就会报错
django 使用mysql连接池
cache?
Redis之五种数据类型
redis有五种数据类型,分别是字符串、列表、字典(hash)、集合和有序集合。
Redis之字符串操作
redis 是key-value形式存储,数据是放在内存中,如果断电,数据就会丢失,需要一个持久化的方案。引入redis的五种数据类型,分别是字符串、列表、字典(hash)、集合和有序集合。
字符串类型使用
# 字符串类型的基本方法使用
'''
1 set(name, value, ex=None, px=None, nx=False, xx=False)
2 setnx(name, value)
3 setex(name, value, time)
4 psetex(name, time_ms, value)
5 mset(*args, **kwargs)
6 get(name)
7 mget(keys, *args)
8 getset(name, value)
9 getrange(key, start, end)
10 setrange(name, offset, value)
11 setbit(name, offset, value)
12 getbit(name, offset)
13 bitcount(key, start=None, end=None)
14 bitop(operation, dest, *keys)
15 strlen(name)
16 incr(self, name, amount=1)
# incrby
17 incrbyfloat(self, name, amount=1.0)
18 decr(self, name, amount=1)
19 append(key, value)
'''
from redis import Redis
conn = Redis(host='127.0.0.1',port=6379)
# 1 set(name, value, ex=None, px=None, nx=False, xx=False)
# ex,过期时间(秒)
# px,过期时间(毫秒)
# nx,如果设置为True,则只有name不存在时,当前set操作才执行, 值存在,就修改不了,执行没效果
# xx,如果设置为True,则只有name存在时,当前set操作才执行,值存在才能修改,值不存在,不会设置新值
# res = conn.set('name','董卿')
# res = conn.set('name','朱广权',ex=3)
# res = conn.set('name','朱广权',px=3)
# res = conn.set('name','朱广权',nx=True)
# res = conn.set('name','朱广权',nx=False)
# res = conn.set('name','朱广权',xx=True)
# res = conn.set('name','朱广权',xx=False)
# redis---》实现分布式锁,底层基于nx实现的
# 2 setnx(name, value) # 等同于conn.set('name','朱广权',nx=True)
# res = conn.setnx('hobby','90')
# 3 setex(name, time,value) # 等同于:conn.set('name','kimi',ex=3)
# res = conn.setex('name',3,'kimi')
# 4 psetex(name, time_ms, value)
# res = conn.psetex('friends',3000,'YangJie') # 产生3s后消失
# 5 mset(*args, **kwargs)
# res= conn.mset({'friends': '花花', 'hobby': 'traval'})
# 6 get(name)
# res = conn.get('name')
# print(str(res,encoding='utf-8'))
# 7 mget(keys, *args)
# res = conn.mget('name','friends')
# res = conn.mget(['name','friends'])
# 8 getset(name, value) # 拿到原来的name值,放入新的name值=kiki
# res = conn.getset('name','kiki')
# 9 getrange(key, start, end) # 字节长度,不是字符长度 前闭后闭区间
# res = str(conn.getrange('name', 0, 2), encoding='utf-8')
# 10 setrange(name, offset, value) # 从第二个位置开始替换并计算字节长度
# res = conn.setrange('name',2,'hello') # kiki--kihello
# ---- 比特位---操作
# 11 setbit(name, offset, value)
# 12 getbit(name, offset)
# 13 bitcount(key, start=None, end=None)
# ---- 比特位---操作
# 14 bitop(operation, dest, *keys) 获取多个值,并将值做位运算,将最后的结果保存至新的name对应的值
# res = conn.bitop('name')
# 15 strlen(name) # name字节长度
# res = conn.strlen('hobby')
# 16 incr(self, name, amount=1) # 不写默认自增1
# 自增,不会出并发安全问题,单线程架构,并发量高
# res = conn.incr('age') # incr = incrby
# res = conn.incrby('age')
# 17 incrbyfloat(self, name, amount=1.0) 自增浮点数
# res = conn.incrbyfloat('age',0.6)
# 18 decr(self, name, amount=1) # 自减1
# res = conn.decr('age') # decr = decrby
# res = conn.decrby('age')
# 19 append(key, value)
res = conn.append('name','玛丽亚') # kihello玛丽亚
print(conn.strlen('name')) # 25
# print(str(res,encoding='utf-8'))
print(res)
conn.close()
redis之列表
List操作,redis中的List在在内存中按照一个name对应一个List来存储。如图:
1.lpush(name,values)
# 在name对应的list中添加元素,每个新的元素都添加到列表的最左边
res=conn.lpush('girls','kimi','kiki') # 左侧插入
2.rpush(name, values)
# 表示从右向左操作
res=conn.rpush('girls','rose','fry') # 右侧插入
3.lpushx(name,value)
# 在name对应的list中添加元素,只有name已经存在时,值添加到列表的最左边
res=conn.lpush('girls','朱熹')
4 rpushx(name, value)
# 只有name已经存在时,值添加到表示从右向左操作
res=conn.rpush('girls','高星')
5.llen(name)
# name对应的list元素的个数
res = conn.llen('girls') # 6
6.linsert(name, where, refvalue, value))
# 在name对应的列表的某一个值前或后插入一个新值
res = conn.linsert('girls','before','kiki','jerry')
res = conn.linsert('girls','after','kimi','mimi')
res = conn.linsert('girls','after','python','mimi') # 没有python标杆是插入数据不进去的 返回-1
7.r.lset(name, index, value)
# 对name对应的list中的某一个索引位置重新赋值
res = conn.lset('girls',1,'央视boys')
8.r.lrem(name, value, num)
# 在name对应的list中删除指定的值
res = conn.lrem('girls',1,'央视boys') # 从左侧开始,删除1个
res = conn.lrem('girls',-1,'央视boys') # 从右侧开始,删除1个
res = conn.lrem('girls',0,'朱熹') # 从左开始,全删除
9.lpop(name)
# 在name对应的列表的左侧获取第一个元素并在列表中移除,返回值则是第一个元素
res=conn.lpop('girls') # b'kimi'
10.rpop(name)
# 表示从右向左操作
res=conn.lpop('girls') # b'\xe9\xab\x98\xe6\x98\x9f'
11.lindex(name, index)
# 在name对应的列表中根据索引获取列表元素
res=conn.lindex('girls',1) # b'kimi'
12.lrange(name, start, end)
# 在name对应的列表分片获取数据 获得到是闭合区间
res=conn.lrange('girls',0,1) # [b'kiki', b'kimi']
13.ltrim(name, start, end)
# 在name对应的列表中移除没有在start-end索引之间的值
res=conn.ltrim('girls',1,5) # True
14.rpoplpush(src, dst)
# 从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边
# 参数:
# src,要取数据的列表的name
# dst,要添加数据的列表的name
res=conn.rpoplpush('girls','boys') # 将girls列表右边弹出--从左侧插入boys列表
15.blpop(keys, timeout)
# 将多个列表排列,按照从左到右去pop对应列表的元素
# 参数:
# keys,redis的name的集合
# timeout,超时时间,当元素所有列表的元素获取完之后,阻塞等待列表内有数据的时间(秒), 0 表示永远阻塞
# 更多:
# r.brpop(keys, timeout),从右向左获取数据爬虫实现简单分布式:多个url放到列表里,往里不停放URL,程序循环取值,但是只能一台机器运行取值,可以把url放到redis中,多台机器从redis中取值,爬取数据,实现简单分布式
# 记住 ,可以做消息队列使用 阻塞式弹出,如果没有,就阻塞
res = conn.brpop("girls") # (b'girls', b'rose')
res = conn.brpop("花花") # 没有就会阻塞
16 brpop(keys, timeout)
# 从右向左剔除数据
res = conn.brpop("girls") # (b'girls', b'mimi')
17.brpoplpush(src, dst, timeout=0)
# 从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧
# 参数:
# src,取出并要移除元素的列表对应的name
# dst,要插入元素的列表对应的name
# timeout,当src对应的列表中没有数据时,阻塞等待其有数据的超时时间(秒),0 表示永远阻塞
自定义增量迭代
# 由于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)
redis之hash
Hash操作,redis中Hash在内存中的存储格式如下图:
hset(name, key, value)
# name对应的hash中设置一个键值对(不存在,则创建;否则,修改)
# res = conn.hset('userinfo','name','kiki')
# res = conn.hset('userinfo',mapping={'age':16,'hobby':'睡觉'})
hmset(name, mapping)
# 在name对应的hash中批量设置键值对
# 批量设置,被弃用了,以后都使用hset
res = conn.hmset('userinfo1',mapping={'age':16,'hobby':'睡觉'})
hget(name,key)
# 在name对应的hash中获取根据key获取value
# res = conn.hget('userinfo','name') # b'kiki'
hmget(name, keys, *args)
# 在name对应的hash中获取多个key的值
# res = conn.hmget('userinfo',['name','age']) # [b'kimi', b'16']
# res = conn.hmget('userinfo','name','age') # [b'kimi', b'16']
hgetall(name)
# 获取name对应hash的所有键值 慎用
res = conn.hgetall('userinfo') # {b'name': b'kimi', b'age': b'16', b'hobby': b'\xe7\x9d\xa1\xe8\xa7\x89'}
hlen(name)
# 获取name对应的hash中键值对的个数
# res=conn.hlen('userinfo') # 3
hkeys(name)
# 获取name对应的hash中所有的key的值
# res=conn.hkeys('userinfo') # [b'name', b'age', b'hobby']
hvals(name)
# 获取name对应的hash中所有的value的值
# res=conn.hvals('userinfo') # [b'kimi', b'16', b'\xe7\x9d\xa1\xe8\xa7\x89']
hexists(name, key)
# 检查name对应的hash是否存在当前传入的key
# res=conn.hexists('userinfo','name') # True
# res=conn.hexists('userinfo','name1') # False
hdel(name,*keys)
# 将name对应的hash中指定key的键值对删除
res = conn.hdel('userinfo', 'age')
hincrby(name, key, amount=1)
# 自增name对应的hash中的指定key的值,不存在则创建key=amount
res=conn.hincrby('userinfo','age') 不写amount,默认自增1
hincrbyfloat(name, key, amount=1.0)
# 自增name对应的hash中的指定key的值,不存在则创建key=amount
res=conn.hincrbyfloat('userinfo','age') # 没有自增age=1.0
hscan(name, cursor=0, match=None, count=None)
# 增量式迭代获取,对于数据大的数据非常有用,hscan可以实现分片的获取数据,并非一次性将数据全部获取完,从而放置内存被撑爆
eg:
# hgetall 会一次性全取出,效率低,可以能占内存很多
# 分批获取,hash类型是无序
# 插入一批数据
# for i in range(100):
# conn.hset('hash_test','id_%s'%i,'号码%s'%i)
# res = conn.hgetall('hash_test') # 可以,但是不好,一次性拿出,可能占很大内存
# 它不单独使用,拿的数据,不是特别准备
res =conn.hscan('hash_test',cursor=0,count=5)
print(res[1]) #(数字,拿出来的5条数据) 数字是下一个游标位置
# 咱们用这个,它内部用了hscan,等同于hgetall 所有数据都拿出来,count的作用是,生成器,每次拿count个个数
hscan_iter(name, match=None, count=None)
# 利用yield封装hscan创建生成器,实现分批去redis中获取数据
# 参数:
# match,匹配指定key,默认None 表示所有的key
# count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
res =conn.hscan_iter('hash_test',count=5)
print(res) # <generator object ScanCommands.hscan_iter at 0x0000019266AFF660>
# generator 只要函数中有yield关键字,这个函数执行的结果就是生成器 ,生成器就是迭代器,可以被for循环
# for i in res:
# print(i)
redis之集合操作
请前往该地址:https://www.cnblogs.com/liuqingzheng/articles/9833534.html
redis其他操作
通用操作:所有数据类型都支持
delete(*names) | 删除 |
---|---|
exists(name) | 是否存在 |
keys(pattern='*') | |
expire(name ,time) | |
rename(src, dst) | |
move(name, db)) | |
randomkey() | |
type(name) | |
import redis
conn = redis.Redis()
# 通用操作,不指定类型,所有类型都支持
# 1 delete(*names)
# conn.delete('name','userinfo1') # 直接删除userinfo1
# conn.delete(['name', 'userinfo2']) # 不行,没反应
# conn.delete(*['name', 'userinfo1']) # 可以用它
# 2 exists(name)
# res = conn.exists('userinfo') # 1
# 3 keys(pattern='*')
# res = conn.keys('?ame') # ?表示一个字符, * 表示多个字符
# res= conn.keys('*e') # [b'age']
# 4 expire(name ,time) # time过后过期
# res = conn.expire('userinfo',3) # True
# 5 rename(src, dst) #将girls改为userinfo_dict
# conn.rename('girls','userinfo_dict')
# 6 move(name, db)) # name移动到第2个库
# conn.move('userinfo_dict',2)
# 7 randomkey() # 随机获取键值
# res= conn.randomkey()
# 8 type(name)
print(conn.type('age')) # b'string'
print(conn.type('girls')) # b'none'
conn.close()
redis管道
事务的四大特性:原子性、一致性、隔离性、持久性
redis支持事务吗?
单实例支持所谓的事物,支持事务是基于管道的
eg:执行命令 一条一条执行
-kiki 金额 -100 conn.decr('kiki',100)
程序挂了--张三转账了但你没有收到账
-kimi 金额 100 conn.incr('kimi',100)
如何解决?
把这两条命令,放到一个管道中,先不执行,当执行excute,一次性都执行完成
conn.decr('kiki',100) conn.incr('kimi',100)
使用管道
redis-py默认在执行每次请求都会创建(连接池申请连接)和断开(归还连接池)一次连接操作,如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline 是原子性操作。
import redis
conn= redis.Redis()# 默认是本地127.0.0.1
p = conn.pipeline(transaction=True) # 开启事务
p.multi()
p.decr('kiki',100)
# raise Exception('崩了')
p.incr('kimi',100)
p.execute()
conn.close()
django中使用redis
方案一:自定义方案
# 第一步写一个pool.py
import redis
POOL = redis.ConnectionPool(max_connections=100)
# 第二步: 以后在使用的地方,直接导入使用即可
conn = redis.Redis(connection_pool=POOL)
conn.incr('count') # 自增
res = conn.get('count')
# 总路由
path('test_redis/',views.test_redis)
# django使用redis
import redis
from utils.pool import POOL
def test_redis(request):
conn = redis.Redis(connection_pool=POOL)
conn.incr('count') # 自增1
res = conn.get('count')
# ensure_ascii 二进制转中文
return JsonResponse({'count':'今天这个接口访问的次数为:%s' % res},json_dumps_params={'ensure_ascii':False})
方案二:django方案有两种
从连接池里面拿东西
- 第一种:django的缓存使用redis,django中使用redis----settings.py 中配置
# django的缓存使用redis 【推荐使用】
1. settings.py 中配置
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",
}
}
}
2. 使用redis的地方:cache.set('count',res+1)
底层是pickle序列化后存入的
# django使用redis---使用缓存cache
from django.core.cache import cache
def test_redis(request):
# cache.set('count',1)
try:
res = cache.get('count') + 1
cache.set('count', res)
except Exception:
cache.set('count', 1)
res = 1
return JsonResponse({'count': '今天这个接口访问的次数为:%s' % res}, json_dumps_params={'ensure_ascii': False})
# 底层存的是pickle
class Person:
name='kimi'
def test_redis(request):
# 存数据
# p=Person()
# cache.set('person',p) #picke存数据是二进制,拿出来也是二进制
# 取数据
p=cache.get('person')
print(p.name) # kimi
return JsonResponse({'count':'今天这个接口访问的次数为:%s' },json_dumps_params={'ensure_ascii':False})
- 第二种方法----使用第三方模块django_redis
# django使用redis---第三方django-redis模块
from django_redis import get_redis_connection
def test_redis(request):
conn = get_redis_connection()
try:
res=int(str(conn.get('count'),encoding='utf-8')) +1
conn.set('count',res)
print(res)
except Exception:
conn.set('count',1)
res = 1
return JsonResponse({'count':'今天这个接口访问的次数为:%s'%res },json_dumps_params={'ensure_ascii':False})