02--Redis基础:python操作redis

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的字符串中
posted @ 2022-03-02 01:17  Edmond辉仔  阅读(209)  评论(0)    收藏  举报