0 前序补充:链接池
# 面试题:Django 连接mysql 是否有链接池?
答:没有,故:若构建链接池,需要通过第三方模块
# Django 是一个同步框架,基本就写一些公司内部的项目,若是并发量很高的项目,基本不会使用Django编写
例:三个用户(并发量为3)同时访问,Django的index视图,怎么处理?
答:浏览器请求过来,通过web服务器(uwsgi:socket服务器),该服务器会自动启3个Django的线程或进程进行处理,
若是都要访问mysql数据库,Django的orm就会一个访问建立一个conn(数据库连接)
若并发量特别高的情况下,mysql数据库的连接就有可能崩掉,故,有第三方模块,构建数据库连接池 来解决。
原理是:数据库链接池 设定连接的最大条数,超过,则同步等待上个连接处理完,再连接。
1 Python操作Redis之普通连接和连接池
# https://www.cnblogs.com/liuqingzheng/articles/9833534.html
# redis模块安装:pip install redis
# 1 redis之普通连接
StrictRedis :实现大部分官方的命令
Redis :是StrictRedis的子类,用于向后兼容旧版的redis
import redis
# 建立连接
conn = redis.Redis(decode_responses=True) # 默认:本机 + 6379 端口 开启自动解码,防止中文乱码
# conn = redis.Redis(host='localhost', port=6379,password='123456',db=0,decode_responses=False)
conn = redis.StrictRedis(decode_responses=True)
# 通过连接操作数据
conn.set('name','lqz')
# 断开连接
conn.close()
# 2 redis之链接池 (很重要,Redis本来就是高并发,效率高)
链接池需要做成单例,整个项目全局只有一个,不能够开启多线程或进程,不然跟不使用链接池没啥区别
Python 的模块就是天然的单例模式 (写一个模块,然后导入使用)
# pool.py 中:
import redis
# 创建池,池的大小是10,最多放10个连接
POOL=redis.ConnectionPool(max_connections=10)
# 测试.py
from pool import POOL # 单例模式之模块导入
# 从池中获取连接
conn=redis.Redis(connection_pool=POOL)
# 操作连接
conn.set('age',14)
# 断开连接,放回链接池
conn.close()
2 Python操作Redis之string操作
# 字符串操作(不需要全记住)
set(name, value, ex=None, px=None, nx=False, xx=False)
setnx(name, value)
setex(name, time, value)
psetex(name, time_ms, value)
mset(*args, **kwargs)
mget({'k1': 'v1', 'k2': 'v2'})
get(name)
mget(keys, *args)
mget('k1', 'k2')
getset(name, value)
getrange(key, start, end)
setrange(name, offset, value)
setbit(name, offset, value)
getbit(name, offset)
bitcount(key, start=None, end=None)
bitop(operation, dest, *keys)
bitop("AND", 'new_name', 'n1', 'n2', 'n3')
strlen(name)
incr(self, name, amount=1)
incrbyfloat(self, name, amount=1.0)
decr(self, name, amount=1)
# 重点掌握:
get、set # 设值、取值
mget、mset # 批量设置、取值
strlen # 获取值的字节长度
incr # 设值自增
import redis
conn=redis.Redis()
# 1 set(name, value, ex=None, px=None, nx=False, xx=False)
# ex: 以秒过期时间
# px: 以毫秒过期时间
# nx: 如果设置为True,则只有name不存在时,当前set操作才执行,值存在,就修改不了 == 理解为:只新增
# xx: 如果设置为True,则只有name存在时,当前set操作才执行,值存在,才能修改,不会设置新值 == 理解为:只修改
conn.set('name','lqz') # 理解为:无则新增,有则修改
conn.set('hobby','篮球',ex=8)
conn.set('name','egon',nx=True)
conn.set('name','dlrb',xx=True)
# 2 setnx(name, value) == set(name, value,nx=True) # 理解为:只新增
conn.setnx('name','ddd')
conn.setnx('name1','ddd')
# 3 setex(name, time, value) # 以秒过期时间
conn.setex('age',18,5)
# 4 psetex(name, time_ms, value) # 以毫秒过期时间
conn.psetex('xx',3000,'ss')
# 5 get(name) # 以byte格式取出值value 返回值:字节形式的值 or None
print(conn.get('name2'))
# 6 mset(*args, **kwargs) # 批量设置
# 和一次次设置的区别: set命令执行没有减少,但减少了网络set请求的时间
conn.mset({'name2':'egon','name3':'lyf'})
# 7 mget({'k1': 'v1', 'k2': 'v2'}) # 批量获取值
res=conn.mget('name1','name2') # 允许直接放key,或放一个列表
res=conn.mget(['name1','name2'])
# 8 getset(name, value) # 获取并重新设值
res=conn.getset('name1','ppp')
print(res)
# 9 setrange(name, offset, value) # 前闭后闭区间,以unicode编码存储,以字节,设值
conn.setrange('name1',10,'pppppp') # 从第10位的字节开始,将后续的字节设为'pppppp'
# 10 getrange(key, start, end) # 前闭后闭区间,以unicode编码存储,以字节,取值
res=conn.getrange('name1',0,5)
print(res.decode('utf-8'))
# 11 setbit(name, offset, value) # 修改某个bit位的值(0或1),从而修改值
# 2进制bit位 ---> 10进制 ---> ASCII编码对应的字母
# 10111111 ---> 112 --->p
conn.setbit('name1',2,0)
# 12 getbit(name, offset) # 获取某个bit位的值
print(conn.getbit('name1',2))
# 13 bitcount(key, start=None, end=None) # 统计哪两个bit位之间的bit数
# 14 strlen(name) # 统计值的字节数,一个汉字三个字节
print(conn.strlen('name1'))
# 15 incr(self, name, amount=1) ****** 计数器 (例:页面访问量、点赞数) 返回值:自增后的数值
conn.incr('name1') # 自增1
# 16 decr(self, name, amount=1) # 自减 <==> incr(self, name, amount=-1)
# 17 incrbyfloat(self, name, amount=1.0) 以浮点数的形式,自增
conn.incrbyfloat('name1',1.2)
print(res)
conn.close()
3 Python操作Redis之hash操作
# 注意:
1 hash数据类型是指:key为name,value为hash类型(字典)
2 hash 类型本身就无序
# hash操作(不需要全记住)
hset(name, key, value)
hmset(name, mapping)
hget(name,key)
hmget(name, keys, *args)
hgetall(name)
hlen(name)
hkeys(name)
hvals(name)
hexists(name, key)
hdel(name,*keys)
hincrby(name, key, amount=1)
hincrbyfloat(name, key, amount=1.0)
hscan(name, cursor=0, match=None, count=None)
hscan_iter(name, match=None, count=None)
# 重点掌握:
hset、hget # 设值、取值
hmset、hmget # 批量设置、取值
hlen # 获取hash长度
hexists # 判断name是否有某个key
hincrby # 给name的key 的value 自增多少 ,默认为1
hscan_iter # 分批获取所有数据
import redis
conn = redis.Redis()
# 1 hset(name, key, value,mapping=None) # 设值 key为name,value为hash类型(key, value) 返回值是添加值的数量
conn.hset('hash1','name','lqz')
conn.hset('hash1', 'age', '19')
conn.hset('user_2_info', mapping={'name': 'lyf', 'age': 33,'hobby':'篮球'})
# 2 hget(name,key) # 获取name的key对应的值
print(conn.hget('hash1','age'))
# 3 hmset(name, mapping) # 批量设值 (弃用了,但是还可以用)
conn.hmset('user_1_info', {'name': 'lyf', 'age': 33,'hobby':'篮球'})
conn.hset('user_2_info', mapping={'name': 'lyf', 'age': 33,'hobby':'篮球'}) # 上下等同
# 4 hmget(name, keys, *args) # 批量取值
res = conn.hmget('user_2_info','age','hobby')
res = conn.hmget('user_2_info', ['age', 'hobby']) # 上下等同,key单独放或列表
print(res)
# 5 hgetall(name) 一次性获取所有 (慎用,即生产环境中尽量不要用,一旦对应的值数据过大,就内存爆了)
res = conn.hgetall('user_2_info')
print(res)
# 6 hlen(name) 统计name 值的长度
res=conn.hlen('user_2_info')
print(res)
# 7 hkeys(name) 获取name的所有key
res=conn.hkeys('user_2_info')
print(res)
# 8 hvals(name) 获取name的所有value
res=conn.hvals('user_2_info')
print(res)
# 9 hexists(name, key) 判断name是否该key,返回值为0/1
res=conn.hexists('user_2_info','name1')
print(res)
# 10 hdel(name,*keys) 删除name的key
res=conn.hdel('user_2_info','name')
print(res)
# 11 hincrby(name, key, amount=1) 将name的key 自增
conn.hincrby('user_2_info','age')
# 12 hincrbyfloat(name, key, amount=1.0) 将name的key 以浮点数自增
# 13 hscan(name, cursor=0, match=None, count=None)
# 由于无序,这个方法咱们一般不用,是给hscan_iter方法使用的,从hash中取出一部分值,会以count为基准,但不数量完全是count
res=conn.hscan('test_hash',count=11)
print(len(res[1]))
# 14 hscan_iter(name, match=None, count=None) # 分批获取所有 (是一个生成器,跟hgetall比,更节省内存)
# 例:
# 循环设值
for i in range(1000):
conn.hset('test_hash','%s_key'%i,'鸡蛋_%s'%i)
# 分批获取所有
for key,value in conn.hscan_iter('test_hash',count=100):
print(key, '---', value)
# 一次性获取所有
res=conn.hgetall('test_hash')
conn.close()
4 Python操作Redis之list操作
# 列表操作(最好全记住)
lpush(name,values)
lpushx(name,value)
llen(name)
linsert(name, where, refvalue, value))
lset(name, index, value)
lrem(name, value, num)
lpop(name)
lindex(name, index)
lrange(name, start, end)
ltrim(name, start, end)
rpoplpush(src, dst)
blpop(keys, timeout)
brpoplpush(src, dst, timeout=0)
# 重点掌握:
lpush # 在列表的左边插值
llen # 统计列表的长度
lrem # 移除列表的元素 (从左侧/右侧/全部)
lindex # 获取某个位置的值,从0开始
lpop # 从列表左侧弹出一个
lrange # 获取起始到结束位置的值,前闭后闭
blpop(keys, timeout) # 阻塞式弹出,有值可以弹,没有值就等待,直到有值; 基于此,可以做简单的消息队列
# redis 之列表操作
import redis
conn = redis.Redis()
# 1 lpush(name,values) # 从列表的左侧插入值
conn.lpush('names','lqz','egon') # ['egon' ,'lqz']
conn.lpush('names','lyf','dlrb') # ['dlrb','lyf','egon' ,'lqz']
conn.lpush('names','pyy') # ['pyy','dlrb','lyf','egon' ,'lqz']
# conn.rpush('names','mrzh') # 从列表的右侧插入值
# 2 lpushx(name,value) # 只能修改列表,key存在时,才能放进去
conn.lpushx('names','999')
conn.lpushx('names1','999') # 不存在的放不进去
# 3 llen(name)
print(conn.llen('names'))
# 4 linsert(name, where, refvalue, value)) # 在列表的某个元素,前或后,插入值
# where:before或者after,大小写都可以
conn.linsert('names','after','egon','fengjie')
conn.linsert('names','BEFORE','egon','rh')
# 5 lset(name, index, value) # 修改列表 索引对应的值
conn.lset('names',5,'l_egon')
# 5 lrem(name, num,value )
conn.lrem('names',0,'lqz') # 把内部所有lqz都移除
conn.lrem('names',1,'lqz') # 从左往右移除一个符合
conn.lrem('names', -1, 'lqz') # 从右往左移除一个符合
# 6 lpop(name) # 从列表左侧弹出一个
conn.lpop('names') # 从左侧弹出一个
conn.rpop('names') # 从右侧弹出一个
# 7 lindex(name, index) # 获取某个位置的值,从0开始
res=conn.lindex('names',1)
print(res)
# 8 lrange(name, start, end) # 获取起始到结束位置的值,前闭后闭
res=conn.lrange('names',1,conn.llen('names'))
print(res)
# 9 ltrim(name, start, end) # 修剪 只保留一段位置之间的元素
res=conn.ltrim('names',2,4) # 只保留2--4之间的,其他全删除
# 10 rpoplpush(src, dst) # 从原列表右侧弹出一个元素,插入目标列表的左侧
conn.lpush('names1','xx','yy')
conn.rpoplpush('names','names1')
# 11 blpop(keys, timeout) # 阻塞式弹出,有值可以弹,没有值就等待,直到有值
# 基于它可以做消息队列,如果是简单的消息队列,就可以使用redis的list类型
res=conn.blpop('names',4) # 超时时间
print(res)
# 12 brpoplpush(src, dst, timeout=0) # 阻塞式从原列表右侧弹出一个元素,插入目标列表的左侧
# 13 一次性把list中值全取出来 (数据过大,内存溢出的风险)
res = conn.lrange('names', 0, conn.llen('names'))
# 14 自定义增量迭代取值 (仿hscan_iter自定义)
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
# 例:
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])
for item in scan_list('test',2):
print(item)
conn.close()
# 补充:
# 1 面试:在哪里使用过生成器?
答:在文件读取,数据量过大的时候,使用yield生成器,每次读取几行数据。例:Python操作Redis时,迭代列表取值
# 2 IPC:进程间通信
5 Redis其他操作
# 通用操作 (跟类型无关)
delete(*names) # 根据删除redis中的任意数据类型
exists(name) # 检测redis的name是否存在
keys(pattern='*') # 获取redis所有的key,可以设置过滤模式
expire(name ,time) # 为redis的某个name设置超时时间
rename(src, dst) # 对redis的name重命名为
move(name, db) # 将redis的某个值移动到指定的db下
randomkey() # 随机获取一个redis的name(不删除)
type(name) # 查看key的数据类型
import redis
conn=redis.Redis()
# 1 delete(*names) 根据key 删除redis中的任意数据类型
conn.delete('test_hash')
# 2 exists(name) 检测redis的name是否存在 1 表示存在 0表示不在
res=conn.exists('names')
print(res)
# 3 keys(pattern='*') 获取所有的key值,可以过滤
res=conn.keys(pattern='n*')
res=conn.keys(pattern='nam?')
print(res)
# 4 expire(name ,time) 为某个redis的某个name设置超时时间
conn.expire('names1',9)
# 5 rename(src, dst) 对redis的name重命名
conn.rename('test','test1')
# 6 move(name, db)) 将redis的某个值移动到指定的db下
conn.move('hash1','2)
# 7 randomkey() 随机获取一个redis的name(不删除)
res=conn.randomkey()
print(res)
# 8 type(name) 查看key的类型 5 大数据类型
res=conn.type('user_1_info')
res=conn.type('name1')
print(res)
conn.close()
6 管道(pipline)
# redis是非关系型数据库,不支持事务 (必背:事务四大特性)
# 管道,一开始是为了减少网络IO,批量操作,但我们可以通过管道来模拟事务----》要么都成功,要么都失败:一个事务
import redis
conn = redis.Redis()
# 创建一个管道
pipe = conn.pipeline(transaction=True)
# 开启事务 (事务间的操作,都是统一的,全成功or失败)
pipe.multi()
# 向管道中放入命令
pipe.decrby('egon_money',50)
# raise Exception('ssss')
# 向管道中放入命令
pipe.incrby('lqz_money',50)
# 提交管道,让管道中的命令执行
pipe.execute()
7 Django中使用redis
7.1 通用方案
# pool.py
import redis
POOL = redis.ConnectionPool(max_connections=1000)
# 在使用的位置
from .pool import POOL
import redis
def test(request):
conn = redis.Redis(connection_pool=POOL)
res=conn.get('name')
print(res)
return HttpResponse('ok')
7.2 django方案,第三方模块
pip install django-redis
# 在配置文件中: 配置缓存的位置 以后还是正常用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",
}
}
}
# 使用的位置
# 方式1:使用redis连接对象 来操作
from django_redis import get_redis_connection
def test(request):
conn = get_redis_connection() # 从连接池中拿一个连接
res=conn.get('name') # 正常使用
print(res)
return HttpResponse('ok')
# 方式2:使用django的缓存 会自动缓存到redis中
from django.core.cache import cache
cache.set('name','xxx')
cache.get('name')
# django的缓存很高级,可以缓存python中所有的数据类型,包括对象
# ---》把对象通过pickle序列化成二进制,存到redis的字符串中