【2.0】Redis使用

【一】redis普通链接和连接池

【1】普通链接

from redis import Redis

# 建立redis连接
conn = Redis(
    host='127.0.0.1',  # IP
    port=6379,  # 端口
    db=0,  # 数据库
    decode_responses=True  # 查询回来返回的结果是字符串类型,否则是 bytes 类型
)

res = conn.get('name')  # 获取key名为name的value值
print(res)
# dream
conn.close()  # 关闭链接

【2】连接池链接

  • 一定要保证,池是单例的,以模块导入的形式做成了单例
  • 简单放入数据
    • 进入redis
C:\Users\Administrator>redis-cli        
127.0.0.1:6379> set name dream
OK
127.0.0.1:6379> get name
"dream"
  • connect.py
# -*-coding: Utf-8 -*-
# @File : 01connect .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/8/10
from threading import Thread
from redis_pool import POOL

import redis


# 建立连接池
# POOL是 一个单例,全局只有一个连接池
# python实现单例的六种模式 ----基于模块 ---- 本次采用基于模块方式


def connect_redis():
    # 建立redis连接
    # 从redis连接池中获取连接,如果没有可用链接,默认不会阻塞等待
    # 可以通过设置参数阻塞等待
    # 从连接池获取连接
    conn = redis.Redis(connection_pool=POOL)

    # 阻塞等待获取连接(最多等待10秒)
    # conn = redis.Redis(connection_pool=POOL, socket_timeout=10, socket_connect_timeout=10, retry_on_timeout=True)

    res = conn.get('name')  # 获取key名为name的value值
    print(res)
    conn.close()  # 关闭链接


# 多线程操作
def t_main():
    for i in range(100):
        t = Thread(target=connect_redis)
        t.start()


if __name__ == '__main__':
    t_main()
  • redis_pool.py
# -*-coding: Utf-8 -*-
# @File : redis_pool .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/8/10
import redis

# 建立连接池
# POOL是 一个单例,全局只有一个连接池
# python实现单例的六种模式
# 1. 类属性
# 2. 装饰器
# 3. 元类
# 4. 基于__new__方法
# 5. 基于__init__方法
# 6. 基于模块 ---- 本次采用基于模块方式
POOL = redis.ConnectionPool(max_connections=2, host='127.0.0.1', port=6379, db=0, decode_responses=True)

【二】Redis之字符串操作

【1】set

  • set(name, value, ex=None, px=None, nx=False, xx=False):
    • 功能: 在数据库中设置键值对
  • 参数:
    • name: 键的名称
    • value: 键的值
    • ex: 过期时间,单位为秒
    • px: 过期时间,单位为毫秒
    • nx: 当键不存在时才设置该键值对,True表示仅在键不存在时设置,False表示不作限制
    • xx: 当键存在时才设置该键值对,True表示仅在键存在时设置,False表示不作限制
  • 返回值: 设置成功则返回True,否则返回False
import redis

r = redis.Redis()

# 设置键为"key1",值为"value1"
result = r.set("key1", "value1")
print(result)  # True

# 设置键为"key2",值为"value2",过期时间为10秒
result = r.set("key2", "value2", ex=10)
print(result)  # True

# 仅在键不存在时才设置键为"key3",值为"value3"
result = r.set("key3", "value3", nx=True)
print(result)  # True

# 仅在键存在时才设置键为"key4",值为"value4"
result = r.set("key4", "value4", xx=True)
print(result)  # False (如果键"key4"不存在,则返回False)

【2】setnx

  • setnx(name, value):
    • 功能: 在数据库中设置键值对,仅当键不存在时才设置
  • 参数:
    • name: 键的名称
    • value: 键的值
  • 返回值: 设置成功则返回True,否则返回False
import redis

r = redis.Redis()

# 仅当键"key1"不存在时才设置键为"key1",值为"value1"
result = r.setnx("key1", "value1")
print(result)  # True

# 键"key1"已存在,再次设置时不会修改
result = r.setnx("key1", "new value1")
print(result)  # False

# 仅当键"key2"不存在时才设置键为"key2",值为"value2"
result = r.setnx("key2", "value2")
print(result)  # True

【3】psetex

  • psetex(name, time_ms, value):
    • 功能: 在数据库中设置键值对,并指定键的过期时间,过期时间以毫秒为单位
  • 参数:
    • name: 键的名称
    • time_ms: 过期时间,单位为毫秒
    • value: 键的值
  • 返回值: 无
import redis

r = redis.Redis()

# 设置键为"key1",值为"value1",并指定过期时间为5000毫秒(5秒)
r.psetex("key1", 5000, "value1")

# 过一段时间后,键"key1"将自动过期,无法获取其值
result = r.get("key1")
print(result)  # None

【4】mset

  • mset(*args, **kwargs):
    • 功能: 在数据库中批量设置多个键值对
  • 参数:
    • *args: 多个键值对,按照格式 "name1", "value1", "name2", "value2" 传入。可以传入任意数量的键值对。
    • **kwargs: 多个键值对,以关键字参数的形式传入,格式为 name1="value1", name2="value2"。可以传入任意数量的键值对。
  • 返回值: 无
import redis

r = redis.Redis()

# 批量设置键值对
r.mset("key1", "value1", "key2", "value2", key3="value3", key4="value4")

# 获取键"key1"的值
result = r.get("key1")
print(result)  # b'value1'

# 获取键"key2"和"key3"的值
result = r.mget("key2", "key3")
print(result)  # [b'value2', b'value3']

【5】get

  • get(name):
    • 功能: 获取指定键的值
  • 参数:
    • name: 键的名称
  • 返回值: 键的值,如果键不存在则返回None
import redis

r = redis.Redis()

# 获取键"key1"的值
result = r.get("key1")
print(result)  # b'value1'

# 获取不存在的键"key2"的值
result = r.get("key2")
print(result)  # None

【6】mget

  • mget(keys, *args):
    • 功能: 批量获取多个键的值
  • 参数:
    • keys: 多个键的名称,按照格式 "key1", "key2", "key3" 传入。可以传入任意数量的键。
    • *args: 额外的键的名称,按照格式 "key4", "key5" 传入。可以传入任意数量的键。
  • 返回值: 多个键对应的值组成的列表
import redis

r = redis.Redis()

# 批量设置键值对
r.mset("key1", "value1", "key2", "value2")

# 批量获取键"key1"和"key2"的值
result = r.mget("key1", "key2")
print(result)  # [b'value1', b'value2']

# 继续获取键"key3"、"key4"的值
result = r.mget("key3", "key4")
print(result)  # [None, None]

【7】getset

  • getset(name, value):

    • getset命令用于设置一个键的值,并返回该键在设置之前的旧值。
    • 如果键不存在,则会创建一个新键并设置其值为指定值。
  • 参数:

    • name: 键的名称。

    • value: 设置的新值。

  • 示例:

# 设置键名为"foo"的值为"bar",并返回旧值
old_value = redis.getset("foo", "bar")
print(old_value)

【8】getrange

  • getrange(key, start, end):

    • getrange命令用于获取指定键的字符串值的子字符串。
    • 它接受起始偏移量和结束偏移量作为参数,并返回指定范围内的子字符串。
  • 参数:

    • key: 键的名称。

    • start: 子字符串的起始偏移量(包含在内)。

    • end: 子字符串的结束偏移量(包含在内)。

  • 示例:

# 获取键名为"foo"的值的索引从2到4的子字符串
sub_string = redis.getrange("foo", 2, 4)
print(sub_string)

【9】setrange

  • setrange(name, offset, value):

    • setrange命令用于将指定键的字符串值中的一部分替换为指定的子字符串。
    • 它接受偏移量和新值作为参数,并返回替换后字符串的长度。
  • 参数:

    • name: 键的名称。
    • offset: 替换的起始偏移量。
    • value: 替换的子字符串。
  • 示例:

# 将键名为"foo"的值的索引从3开始的子字符串替换为"bar"
new_length = redis.setrange("foo", 3, "bar")
print(new_length)

【10】setbit

  • setbit(name, offset, value):

    • setbit命令用于设置指定键的字符串值中的位元。
    • 它接受偏移量和要设置的值(0或1)作为参数,并返回旧的位值。
  • 参数:

    • name: 键的名称。

    • offset: 位元的偏移量。

    • value: 要设置的值,0或1。

  • 示例:

# 设置键名为"foo"的值的索引为6的位元为1,并返回旧的位值
old_bit_value = redis.setbit("foo", 6, 1)
print(old_bit_value)

【11】getbit

  • getbit(name, offset):

    • getbit命令用于获取指定键的字符串值中指定位元的值(0或1)。
    • 它接受偏移量作为参数,并返回位值。
  • 参数:

    • name: 键的名称。

    • offset: 位元的偏移量。

  • 示例:

# 获取键名为"foo"的值的索引为6的位元值
bit_value = redis.getbit("foo", 6)
print(bit_value)

【12】bitcount

  • bitcount(key, start=None, end=None):

    • bitcount命令用于计算指定键的字符串值中被设置为1的位元的数量。
    • 它可以计算整个字符串或指定范围内的位元数量。
  • 参数:

    • key: 键的名称。

    • start: 计算范围的起始偏移量(默认为整个字符串)。

    • end: 计算范围的结束偏移量(默认为整个字符串)。

  • 示例:

# 计算键名为"foo"的值中被设置为1的位元数量(整个字符串)
count = redis.bitcount("foo")
print(count)

# 计算键名为"foo"的值中被设置为1的位元数量(从索引2到索引6的范围)
count = redis.bitcount("foo", 2, 6)
print(count)

【13】bitop

  • bitop(operation, dest, *keys):

    • bitop命令用于对一个或多个键的字符串值进行位元操作,并将结果保存到目标键中。
    • 位元操作可以是AND、OR、XOR或NOT。
  • 参数:

    • operation: 位元操作的类型,可选值为"AND"、"OR"、"XOR"或"NOT"。

    • dest: 目标键的名称。

    • *keys: 参与位元操作的键的名称。

  • 示例:

# 将键名为"keys1"和"keys2"的值执行AND运算,并将结果保存到键名为"dest"的值中
result = redis.bitop("AND", "dest", "keys1", "keys2")
print(result)

【14】strlen

  • strlen(name):

    • strlen命令用于获取指定键的字符串值的长度。
  • 参数:

    • name: 键的名称。
  • 示例:

# 获取键名为"foo"的值的长度
length = redis.strlen("foo")
print(length)

【15】incr

  • incr(name, amount=1):

    • incr命令用于对存储在指定键中的数字值执行原子递增操作。
    • 如果键不存在,则会先将其设置为0,然后再执行递增操作。
  • 参数:

    • name: 键的名称。

    • amount: 递增的数量,默认为1。

  • 示例:

# 对键名为"counter"的值执行递增操作,递增量为1
new_value = redis.incr("counter")
print(new_value)

# 对键名为"counter"的值执行递增操作,递增量为10
new_value = redis.incr("counter", 10)
print(new_value)

【16】incrbyfloat

  • incrbyfloat(name, amount=1.0):

    • incrbyfloat命令用于对存储在指定键中的浮点数值执行原子递增操作。
    • 如果键不存在,则会先将其设置为0.0,然后再执行递增操作。
  • 参数:

    • name: 键的名称。

    • amount: 递增的浮点数值,默认为1.0。

  • 示例:

# 对键名为"float_counter"的值执行递增操作,递增量为1.0
new_value = redis.incrbyfloat("float_counter")
print(new_value)

# 对键名为"float_counter"的值执行递增操作,递增量为3.14
new_value = redis.incrbyfloat("float_counter", 3.14)
print(new_value)

【17】decr

  • decr(name, amount=1):

    • decr命令用于对存储在指定键中的数字值执行原子递减操作。
    • 如果键不存在,则会先将其设置为0,然后再执行递减操作。
  • 参数:

    • name: 键的名称。

    • amount: 递减的数量,默认为1。

  • 示例:

# 对键名为"counter"的值执行递减操作,递减量为1
new_value = redis.decr("counter")
print(new_value)

# 对键名为"counter"的值执行递减操作,递减量为5
new_value = redis.decr("counter", 5)
print(new_value)

【18】append

  • append(key, value):

    • append命令用于将指定字符串追加到存储在指定键中的字符串值的末尾。
  • 参数:

    • key: 键的名称。

    • value: 要追加的字符串。

  • 示例:

# 将"bar"追加到键名为"foo"的值的末尾
new_length = redis.append("foo", "bar")
print(new_length)

【19】小案例

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('hobby', '篮球', ex=6)
# conn.set('hobby', '篮球', px=3000)
# conn.set('hobby', '足球', nx=True)
# conn.set('hobby', '足球', xx=True)

# 2 setnx(name, value)
# conn.setnx('hobby1', '乒乓球')  # 等同于conn.set('hobby', '足球', nx=True)

# 3 psetex(name, time_ms, value)
# conn.psetex('name',3000,'xxx')

# 4 mset(*args, **kwargs)
# conn.mset({'name': "lqz", 'height': 183})  # 跟一次次设置的区别是,少了网络交互的时间

# 5 get(name)
# res=conn.get('hobby')  # utf-8 编码,一个中文占3个字节    GBK 编码,一个中文占2个字节
# print(res)


# 6 mget(keys, *args)
# res=conn.mget('name','age')
# res=conn.mget(['name','age'])
# print(res)


# 7 getset(name, value)
# res=conn.getset('name','彭于晏')
# print(res)

# 8 getrange(key, start, end)
# res=conn.getrange('name',0,1)  # 前闭后闭区间,拿的是字节,不是字符
# print(res)

# 9 setrange(name, offset, value)
# conn.setrange('name',2,'lqz')

# 14 strlen(name)
# res=conn.strlen('name')  # 9 统计字节长度
# print(res)


# 15 incr(self, name, amount=1)  # 做计数器。不会出现并非安全问题
# conn.incrby('height')

# 16 incrbyfloat(self, name, amount=1.0)
# 17 decrby(self, name, amount=1)
# conn.decrby('height',5)


# 18 append(key, value)
conn.append('name', 'xxxx')

conn.close()

【三】Reidis之hash类型

  • hash类型就是咱们python中的字典,key-value,字典又叫hash类型 字典的key必须可hash
  • 字典类型在底层存储,基于数组存的
  • key---
  • Redis中的哈希类型(Hash)类似于Python中的字典(Dictionary),它以键值对的形式存储数据。
    • 在底层实现上,哈希类型使用数组来进行存储。
  • 在哈希类型中,每个键可以关联多个字段和对应的值,键和字段都必须是可哈希的(可用作字典的键)。
    • 这使得哈希类型非常适合存储对象或具有结构化数据的信息。

【1】哈希类型操作

  • hset(name, key, value)

    • 将指定键值对存储到哈希类型中。
    • 参数:
      • name: 哈希类型的名称。
      • key: 字段名。
      • value: 字段对应的值。
  • hget(name, key)

    • 获取指定哈希类型中指定字段的值。
    • 参数:
      • name: 哈希类型的名称。
      • key: 字段名。
  • hmset(name, mapping)

    • 将多个键值对同时设置到哈希类型中。
    • 参数:
      • name: 哈希类型的名称。
      • mapping: 字段和对应值构成的字典。
  • hmget(name, keys, *args)

    • 获取哈希类型中指定多个字段的值。
    • 参数:
      • name: 哈希类型的名称。
      • keys: 字段名列表。
      • *args: 可选参数,如果给定,则作为结果列表中对应字段值的默认值。
  • hdel(name, *keys)

    • 删除哈希类型中指定的字段。
    • 参数:
      • name: 哈希类型的名称。
      • *keys: 要删除的字段名列表。

【2】示例:

import redis

conn = redis.Redis(host='127.0.0.1', port=6379, db=0)

# 将键为"user"的哈希类型中的字段"name"设置为"John"
redis.hset("user", "name", "John")

# 获取键为"user"的哈希类型中的字段"name"的值
name = redis.hget("user", "name")
print(name)

# 一次设置键为"user"的哈希类型中多个字段和对应的值
fields = {"name": "John", "age": 25}
redis.hmset("user", fields)

# 一次获取键为"user"的哈希类型中多个字段的值
fields = ["name", "age", "address"]
values = redis.hmget("user", fields, "N/A")
print(values)

# 删除键为"user"的哈希类型中的字段"name"
redis.hdel("user", "name")
# -*-coding: Utf-8 -*-
# @File : 03hash .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/8/10
import redis

conn = redis.Redis(host='127.0.0.1', port=6379, db=0,decode_responses=True)
# 1 hset
# 将键为"user"的哈希类型中的字段"name"设置为"John"
# conn.hset("user", "name", "John")

# 2 hget
# 获取键为"user"的哈希类型中的字段"name"的值
# name = conn.hget("user", "name")
# print(name)

# 3 hmset
# 一次设置键为"user1"的哈希类型中多个字段和对应的值
# fields = {"name": "John", "age": 25}
# conn.hmset("user1", fields)

# 一次获取键为"user1"的哈希类型中多个字段的值
# fields = ["name", "age", "address"]
# values = conn.hmget("user2", fields, "N/A")
# print(values)

# 4 hgetall  # 慎用!如果内存中的key太多,会撑爆内存
# 获取键为"user1"的哈希类型中所有字段和对应的值
# values = conn.hgetall("user1")
# print(values) # {b'name': b'John', b'age': b'25'}

# 5 hmget
# 一次获取键为"user1"的哈希类型中多个字段的值
# fields = ["name", "age", "address"]
# values = conn.hmget("user1", fields)
# print(values) # [b'John', b'25', None]

# 6 hlen
# 获取键为"user1"的哈希类型中字段的数量
# length = conn.hlen("user1")
# print(length) # 1

# 7 hdel
# 删除键为"user1"的哈希类型中名为"name"的字段
# conn.hdel("user1", "name")

# 8 hkeys
# 获取键为"user1"的哈希类型中所有字段
# keys = conn.hkeys("user1")
# print(keys) # [b'age']


# 9 hvals
# 获取键为"user1"的哈希类型中所有字段的值
# values = conn.hvals("user1")
# print(values) # [b'25']

# 10 hexists
# 判断键为"user1"的哈希类型中是否存在名为"name"的字段
# exists = conn.hexists("user1", "name")
# print(exists) # False


# 11 hincrby
# 给键为"user1"的哈希类型中名为"age"的字段增加40
# conn.hincrby("user1", "age", 40)
# print(conn.hget("user1", "age")) # b'65'

# 12 hincy
# conn.hincrbyfloat("user1", "age", 0.1)

# for i in range(100):
#     conn.hset("hash_test", f"egg_{i}", f'鸡蛋{i}号')

# 一次性取出所有数据 --- 容易对内容产生负担
# res = conn.hgetall("hash_test")
# print(res)

# 分批取出数据 --- cursor 从 0 开始 取 10 条
# res = conn.hscan("hash_test", cursor=0, count=10)
# print(len(res))

# 分批获取所有 --- 每次拿 10 条
res = conn.hscan_iter("hash_test", count=10)

for i in res:
    print(i)

【四】Redis之列表

【1】lpush

  • lpush(name, values):

    • 从左侧向列表 name 插入一个或多个值,返回插入后列表的长度。
  • 参数:

    • name: 列表名称
    • values: 要插入的一个或多个值,可以是单个值或值的列表。
  • 示例:

    import redis
    
    r = redis.Redis()
    r.lpush("mylist", "value1")  # 单个值插入
    r.lpush("入后列表的长度
    

【2】rpush

  • rpush(name, values):
    • 从右侧向列表 name 插入一个或多个值,返回插入后列表的长度。即与 lpush 相反。
    • 参数和示例同 lpush 方法一致。

【3】lpushx

  • lpushx(name, value):

    • 只有在列表 name 存在时,才从左侧向列表插入一个值,返回插入后列表的长度。
  • 参数:

    • name: 列表名称
    • value: 要插入的值
  • 示例:

    import redis
    
    r = redis.Redis()
    r.rpushx("mylist", "value1")  # 如果 mylist 存在,则插入;否则不执行插入操作
    
    # 注意:返回值表示插入后列表的长度,如果列表不存在,则返回 0
    

【4】rpushx

  • rpushx(name, value):
    • 只有在列表 name 存在时,才从右侧向列表插入一个值,返回插入后列表的长度。即与 lpushx 相反。
    • 参数和示例同 lpushx 方法一致。

【5】llen

  • llen(name):

    • 获取列表 name 的长度,即列表中元素的个数。
  • 参数:

    • name: 列表名称
  • 示例:

    import redis
    
    r = redis.Redis()
    length = r.llen("mylist")
    print(length)  # 打印列表 mylist 的长度
    
    # 注意:返回列表的长度,如果列表不存在或为空,则返回 0
    

【6】linsert

  • linsert(name, where, refvalue, value):

    • 在列表 name 中的某个值之前(或之后)插入一个新值,返回插入后列表的长度。
  • 参数:

    • name: 列表名称
    • where: 插入的位置,可选值为 "BEFORE" 或 "AFTER"
    • refvalue: 作为参考的值,插入操作将在其前(或后)执行
    • value: 要插入的新值
  • 示例:

    import redis
    
    r = redis.Redis()
    r.linsert("mylist", "BEFORE", "value2", "newvalue")  # 在值 "value2" 之前插入 "newvalue"
    
    # 注意:返回值表示插入后列表的长度,如果参考值不存在,则返回 -1
    

【7】lset

  • lset(name, index, value):

    • 将列表 name 中索引为 index 的元素设置为新值 value。
  • 参数:

    • name: 列表名称
    • index: 索引位置,从 0 开始计数
    • value: 新值
  • 示例:

    import redis
    
    r = redis.Redis()
    r.lset("mylist", 1, "newvalue")  # 将索引为 1 的元素设置为 "newvalue"
    
    # 注意:如果索引超出范围(如负数或大于列表长度),则会报错
    

【8】lrem

  • lrem(name, value, num):

    • 从列表 name 中删除指定数量的值为 value 的元素。
  • 参数:

    • name: 列表名称
    • value: 要删除的元素的值
    • num: 删除数量,默认为 0 表示删除所有匹配的元素;正数表示从左向右删除指定数量的元素;负数表示从右向左删除指定数量的元素。
  • 示例:

    import redis
    
    r = redis.Redis()
    r.lrem("mylist", "value", 2)  # 删除列表 mylist 中前两个值为 "value" 的元素
    
    # 注意:返回值表示实际删除的元素数量,并非指定删除的数量
    

【9】lpop

  • lpop(name):

    • 移除并返回列表 name 的左侧第一个元素。
  • 参数:

    • name: 列表名称
  • 示例:

    import redis
    
    r = redis.Redis()
    value = r.lpop("mylist")  # 移除并返回 mylist 的左侧第一个元素
    print(value)
    
    # 注意:如果列表为空,则返回 None
    

【10】rpop

  • rpop(name):
    • 移除并返回列表 name 的右侧第一个元素。即与 lpop 方法相反。

【11】lindex

  • lindex(name, index):

    • 获取列表 name 中指定索引位置的元素值。
  • 参数:

    • name: 列表名称
    • index: 索引位置,从 0 开始计数
  • 示例:

    import redis
     
    r = redis.Redis()
    value = r.lindex("mylist", 2)  # 获取 mylist 中索引为 2 的元素值
    print(value)
    
    # 注意:如果索引超出范围(如负数或大于等于列表长度),则返回 None
    

【12】lrange

  • lrange(name, start, end):

    • 获取列表 name 中指定索引范围的元素值列表。
  • 参数:

    • name: 列表名称
    • start: 起始索引位置(包含)
    • end: 结束索引位置(包含)
  • 示例:

    import redis
     
    r = redis.Redis()
    values = r.lrange("mylist", 0, -1)  # 获取 mylist 中所有元素
    print(values)
    
    # 注意:返回的是包含指定范围元素的列表,如果列表为空,则返回空列表 []
    

【13】ltrim

  • ltrim(name, start, end):

    • 对列表 name 进行修剪,只保留指定索引范围内的元素,其他元素将被删除。
  • 参数:

    • name: 列表名称
    • start: 起始索引位置(包含)
    • end: 结束索引位置(包含)
  • 示例:

    import redis
     
    r = redis.Redis()
    r.ltrim("mylist", 0, 2)  # 保留 mylist 中从索引 0 到索引 2 的元素,其他元素删除
    

【14】rpoplpush

  • rpoplpush(src, dst):

    • 移除并返回列表 src 的最后一个元素,并将其添加到列表 dst 的左侧(头部)。
  • 参数:

    • src: 源列表名称
    • dst: 目标列表名称
  • 示例:

    import redis
     
    r = redis.Redis()
    value = r.rpoplpush("mylist1", "mylist2")  # 将 mylist1 的最后一个元素移动到 mylist2 的左侧,并返回该元素的值
    print(value)
    

【15】blpop

  • blpop(keys, timeout):

    • 阻塞地从多个列表中获取第一个非空列表,并移除并返回该列表的左侧第一个元素。
  • 参数:

    • keys: 待检查的列表名称,可以传入单个列表或多个列表组成的列表
    • timeout: 超时时间,单位为秒。如果所有列表都为空,则会阻塞指定的超时时间,超时后返回 None。
  • 示例:

    import redis
     
    r = redis.Redis()
    value = r.blpop(["mylist1", "mylist2"], timeout=10)  # 从 mylist1 和 mylist2 中获取第一个非空列表的左侧第一个元素,超时时间为 10 秒
    print(value)
    

【16】brpoplpush

  • brpoplpush(src, dst, timeout=0):

    • 阻塞地从源列表 src 获取最后一个非空列表,并移除并将其右侧(尾部)的第一个元素推入目标列表 dst 的左侧(头部),返回被弹出的元素。
  • 参数:

    • src: 源列表名称
    • dst: 目标列表名称
    • timeout: 超时时间,单位为秒。如果源列表为空,则会阻塞指定的超时时间,超时后返回 None。
  • 示例:

    import redis
     
    r = redis.Redis()
    value = r.brpoplpush("mylist1", "mylist2", timeout=10)  # 从 mylist1 获取最后一个非空列表的右侧第一个元素,推入 mylist2 的左侧,并返回该元素的值,超时时间为 10 秒
    print(value)
    

【17】自定义增量迭代

  • 实现对Redis列表进行增量迭代的功能。
    • 可以通过自定义的l_scan_iter方法,按照指定的数量逐步遍历获取Redis列表中的数据。

(1)详细解析

  • 首先,自定义的l_scan_iter方法用于实现增量迭代的功能。
    • 该方法接收两个参数:
      • key表示要遍历的Redis列表的key,
      • count表示每次迭代时获取的数据数量,默认为10。
  • 在方法内部,首先定义一个变量num,用于记录当前已经遍历到的数据索引位置。
    • 接下来使用一个无限循环,直到整个列表被遍历完毕为止。
  • 在每次循环开始之前,使用conn.lrange(key, num, num + count - 1)从Redis列表中获取指定范围的数据。
    • lrange方法的第一个参数是列表的key
    • 第二个参数是起始索引
    • 第三个参数是结束索引。
  • 如果获取到了数据,则遍历数据并通过yield关键字将每个元素返回。
    • 这使得该方法成为一个生成器函数,可以在遍历过程中不断返回数据,以供外部使用。
  • 如果获取到的数据为空,则表示列表已经全部遍历完毕,此时退出循环。

(2)示例代码:

import redis

# 连接到Redis
conn = redis.Redis()

# 将1000条数据添加到列表中
for i in range(1000):
    conn.lpush('eggs', '鸡蛋%s号' % i)

# 定义l_scan_iter函数并实现列表的增量迭代
def l_scan_iter(key, count=10):
    num = 0
    while True:
        res = conn.lrange(key, num, num + count - 1)
        num = num + count
        if res:
            for item in res:
                yield item
        else:
            break

# 使用l_scan_iter进行增量迭代遍历
for item in l_scan_iter('eggs', 20):
    print(item)
  • 在示例代码中,首先使用Redis库连接到Redis服务,需要根据实际情况进行参数配置。

  • 然后,使用conn.lpush将1000条数据添加到名为"eggs"的列表中。

  • 接着定义了自定义的l_scan_iter方法,并传入要遍历的列表key为"eggs"。

  • 最后,在使用l_scan_iter方法进行迭代遍历时,每次迭代返回的数据数量为20个。将每次获取到的数据打印输出。

  • 通过这样的处理,可以实现对Redis列表进行分批次、增量式地遍历和处理数据。

【补充】示例

import redis

conn = redis.Redis(decode_responses=True)


# lpush(name,values)    左侧插入值  [ 乒乓球  足球 篮球]
# conn.lpush('hobbys', '篮球')
# conn.lpush('hobbys', '足球')
# conn.lpush('hobbys', '乒乓球')


# rpush(name, values) 表示从右向左操作
# conn.rpush('hobbys', '橄榄球')

# lpushx(name,value)  # 只有key存在才能插入,从左侧插入
# conn.lpushx('hobbys1', '排球')
# rpushx(name, value) 只有key存在才能插入表示从右向左操作

# llen(name)
# print(conn.llen('hobbys'))

# name:rediskey值,
# where: before   after,
# refvalue: 在谁的前后
# value:插入的数据
# linsert(name, where, refvalue, value))
# conn.linsert('hobbys', 'after', '乒乓球', '保龄球')
# conn.linsert('hobbys', 'before', '乒乓球', '球球')


# lset(name, index, value)
# conn.lset('hobbys', 1, '球球球')

# lrem(name,num, value )
# conn.lrem('hobbys',0,'排球')  # 0 表示全删除
# conn.lrem('hobbys', 1, '足球')  # 正数表示从左侧删除
# conn.lrem('hobbys', -2, '足球')  # 负数表示从右侧删除

# lpop(name)
# print(conn.lpop('hobbys'))  # 从左侧弹出
# print(conn.rpop('hobbys'))
# rpop(name) 表示从右向左操作

# lindex(name, index)

# res=conn.lindex('hobbys',2)
# print(res.decode('utf-8'))

# lrange(name, start, end)
# res=conn.lrange('hobbys',0,2)  # 前闭后闭区间
# print(res)


# ltrim(name, start, end)

# res=conn.ltrim('hobbys',1,3)
# print(res)

# rpoplpush(src, dst)

# blpop(keys, timeout) 着重讲,这个就是简单的消息队列,可以实现分布式的程序----》生产者消费者模型

# res=conn.blpop('hobby',timeout=2)  # 只要列表中没有值,就会阻塞,有值才会继续运行
# print(res)

# brpoplpush(src, dst, timeout=0)

# 自定义增量迭代
# 一次性把列表中所有数据取出来
# for i in range(1000):
#     conn.lpush('eggs','鸡蛋%s号'%i)
# l=conn.llen('eggs')
# res=conn.lrange('eggs',0,l)
# print(res)

def l_scan_iter(key, count=10):
    num = 0
    while True:
        print('-----------')
        res = conn.lrange(key, num, num + count - 1)
        num = num + count
        if res:
            for item in res:
                yield item
        else:
            break


for item in l_scan_iter('eggs', 20):
    print(item)

conn.close()

【五】Redis之其他操作

【1】delete

  • delete(*names): 删除一个或多个指定的key。
    • *names: 一个或多个要删除的key。
import redis

# 连接到Redis
conn = redis.Redis()

# 添加示例数据
conn.set('name1', 1)
conn.set('name2', 2)
conn.set('name3', 3)

# 删除单个key
conn.delete('name1')

# 删除多个key
conn.delete('name2', 'name3')

【2】exists

  • exists(name): 检查给定key是否存在。
    • name: 要检查的key。
import redis

# 连接到Redis
conn = redis.Redis()

# 添加示例数据
conn.set('name1', 1)

# 检查key是否存在
exist = conn.exists('name1')
if exist:
    print("Key exists")
else:
    print("Key does not exist")

【3】keys

  • keys(pattern='*'): 根据模式匹配获取所有符合条件的key。
    • pattern: 匹配key的模式,默认为"*"表示返回所有key。
    • pattern: 匹配key的模式,"?"表示单个字符。
import redis

# 连接到Redis
conn = redis.Redis()

# 添加示例数据
conn.set('name1', 1)
conn.set('name2', 2)
conn.set('age', 18)

# 获取所有key
all_keys = conn.keys()

# 根据模式匹配获取key
matched_keys = conn.keys('name*')
print(matched_keys)

【4】expire

  • expire(name, time): 设置一个key的过期时间。
    • name: 要设置过期时间的key。
    • time: 过期时间,以秒为单位。
import redis

# 连接到Redis
conn = redis.Redis()

# 添加示例数据
conn.set('name1', 1)

# 设置过期时间为60秒
conn.expire('name1', 60)

【5】rename

  • rename(src, dst): 重命名一个key。
    • src: 要被重命名的key。
    • dst: 新的key名称。
import redis

# 连接到Redis
conn = redis.Redis()

# 添加示例数据
conn.set('name1', 1)

# 重命名key
conn.rename('name1', 'name_new')

【6】move

  • move(name, db): 将当前数据库中的key移动到指定数据库中。
    • name: 要移动的key。
    • db: 目标数据库的索引。
import redis

# 连接到Redis
conn = redis.Redis()

# 添加示例数据
conn.set('name1', 1)

# 将key从当前数据库移动到第二个数据库
conn.move('name1', 2)

【7】randomkey

  • randomkey(): 从当前数据库中随机返回一个key。
import redis

# 连接到Redis
conn = redis.Redis()

# 添加示例数据
conn.set('name1', 1)
conn.set('name2', 2)
conn.set('name3', 3)

# 随机获取一个key
random_key = conn.randomkey()
print(random_key)

【8】type

  • type(name): 返回key所存储的值的数据类型。
    • name: 要获取数据类型的key。
import redis

# 连接到Redis
conn = redis.Redis()

# 添加示例数据
conn.set('name1', 1)

# 获取key的数据类型
data_type = conn.type('name1')
print(data_type)

【案例】

import redis

conn=redis.Redis()
# delete(*names)
# conn.delete('eggs','hobbys')
# exists(name)
# conn.set('name','lqz')

# print(conn.exists('name'))  #

# keys(pattern='*')
# res=conn.keys('*')
# res=conn.keys('nam?')
# print(res)

# expire(name ,time)
# conn.expire('hobby',5)

# rename(src, dst)
# conn.rename('name','name1')

# move(name, db))
# conn.move('name1',2)

# randomkey()
# res=conn.randomkey()
# print(res)

# type(name)
res=conn.type('name1')
print(res)
conn.close()

【六】Redis之管道

【1】事务四大特性

  • 原子性:要么都成功,要么都失败

  • 一致性:数据前后要一致

  • 隔离性:多个事务之间相互不影响

  • 持久性:事务一旦完成,数据永久改变

  • 事务(Transaction)是数据库管理系统(DBMS)中的一个基本概念,它是一组作为单个逻辑工作单元执行的操作。

  • 事务具有以下四大特性:

    • 原子性(Atomicity):

      • 在事务执行期间,要么所有的操作都被成功执行并永久保存到数据库中,要么所有的操作都不被执行且不影响数据库的状态。

      • 也就是说,事务的操作要么全部完成,要么都不完成。

    • 一致性(Consistency):

      • 事务的执行必须使数据库从一个一致状态转换到另一个一致状态。

      • 这意味着事务的执行不能破坏数据库中定义的各种约束(如唯一性约束、完整性约束等),使数据库保持有效和正确的状态。

    • 隔离性(Isolation):

      • 每个事务的执行都应该被隔离开来,互不干扰。

      • 数据库管理系统必须确保并发执行的多个事务之间相互隔离,以防止未经授权的读写操作导致数据的不一致。

    • 持久性(Durability):

      • 一旦事务被提交,对数据库的修改将是永久性的,即使系统发生崩溃或重启,修改的结果也将被保留。
      • 这意味着数据库管理系统必须具备恢复机制,以确保事务提交后的数据的持久性。

【2】Redis的事务功能及示例说明

  • redis要支持事务,要完成事务的几大特性,需要使用管道来支持

  • 单实例redis是支持管道的

  • 集群模式下,不支持管道,就不支持事务

  • redis通过管道实现事务

(1)Redis具有支持事务的特性

  • Redis是一个开源的内存数据库系统,它具有支持事务的特性。
    • 为了实现事务的特性,Redis采用了管道(pipeline)来支持事务操作。
  • 在单实例的Redis中,可以通过管道来实现事务操作。
    • 管道允许客户端将多个命令一次性发送给Redis服务器,从而减少网络延迟,提高执行效率。
    • 在使用管道进行事务操作时,需要使用MULTI、EXEC、DISCARD和WATCH等指令来控制事务的开始、提交或回滚。

(2)示例代码:

import redis

# 连接Redis服务器
r = redis.Redis(host='localhost', port=6379, db=0)

# 开始事务
pipe = r.pipeline(transaction=True)

# 在事务中执行多个命令
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')

# 提交事务
pipe.execute()
  • 上述示例中,首先使用redis.Redis()方法连接到Redis服务器。
    • 然后使用pipeline()方法创建一个管道对象,设置transaction=True表示开启事务处理。
  • 接下来,在事务中使用set()方法分别设置两个键值对。
    • 在这个过程中,所有的命令都只是进入了管道,并未真正执行。
  • 最后,使用execute()方法提交事务。
    • 在执行execute()方法之后,管道中的每个命令都会按照添加的顺序依次在服务器上执行。
  • 需要注意的是,当使用Redis集群模式时,由于性能和一致性的考虑,Redis集群不支持管道操作,因此也不支持事务操作。
  • 综上所述,Redis单实例通过管道来支持事务,使用MULTI、EXEC、DISCARD和WATCH等指令可以开启、提交、回滚和监视事务的执行。
  • 但在Redis集群中,不支持管道操作,因此也无法进行事务处理。
# redis通过管道实现事务
import redis

conn = redis.Redis()
pipline = conn.pipeline(transaction=True)

# 没有真正执行,把命令先放到管道中
pipline.decrby('a1', 10)  
raise Exception('出错了')
pipline.incrby('a2', 10)

# 把管道中的命令,一次性执行
pipline.execute()  
conn.close()

【七】Django中使用Redis

【1】自定义的通用方案(跟框架无关)

(1)引入(无池)

  • 自定义计数器
import redis


class TextResponse(APIView):
    def get(self, request):

        conn = redis.Redis(host='127.0.0.1', port=6379, db=0, decode_responses=True)
        # 每访问一次,Redis中的 num 值 都会 +1
        # 该方案没有池,只能一个接一个处理请求
        conn.incrby('num', 1)
        result = conn.get('num')
        print(result)
        return CommonResponse(msg='Redis测试成功')
  • 每访问一次该路由,在Redis中都会将 num 进行自加一操作

(2)迭代(有池)

  • luffyCity\luffyCity\utils\common_redis_pool.py
import redis

# 创建redis连接池
POOL = redis.ConnectionPool(max_connections=30, host="127.0.0.1", port=6379, decode_responses=True)

import redis


class TextResponse(APIView):
    def get(self, request):
        # 从连接池中获取连接对象
        conn = redis.Redis(connection_pool=POOL)
        # 每访问一次,Redis中的 num 值 都会 +1
        conn.incrby('num', 1)
        result = conn.get('num')
        print(result)
        return CommonResponse(msg='Redis测试成功')

【2】借助Django中的第三方模块

  • django中有个模块,django-redis,方便我们快速集成redis

(1)下载安装

pip install django-redis

(2)配置文件中添加配置

  • luffyCity\luffyCity\settings\dev.py
###############Redis配置文件#################
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",  # 走 Redis 协议
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100}  # 连接池大小
            # "PASSWORD": "123", # 有密码可以自配置密码
        }
    }
}

(3)视图类中使用

# 导入模块
from django_redis import get_redis_connection


# 视图类中使用
conn = get_redis_connection()  # 从连接池中拿出一个链接
  • 示例
# 导入模块
from django_redis import get_redis_connection

class TextResponse(APIView):
    def get(self, request):
        
        conn = get_redis_connection()  # 从连接池中拿出一个链接
        conn.incrby('num')
        result = conn.get('num')
        print(result)
        conn.set('name', f'dream&{result}')

        return CommonResponse(msg='Redis测试成功')

【补充】Python中实现单例模式的六种常见方法

【1】类属性:

class Singleton:
    instance = None
    
    @classmethod
    def getInstance(cls):
        if cls.instance is None:
            cls.instance = Singleton()
        return cls.instance
  • 使用类属性保存实例,通过类方法获取实例。
  • 在第一次调用getInstance方法时创建实例,并在后续调用中直接返回该实例。

【2】装饰器:

def singleton(cls):
    instances = {}
    
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    
    return wrapper

@singleton
class Singleton:
    pass
  • 使用装饰器将原来的类包装成一个新的类,通过闭包和字典保存实例。
  • 在每次实例化时,先检查字典中是否已经存在该类的实例,如果不存在才创建实例并返回。

【3】元类:

class SingletonType(type):
    def __init__(cls, name, bases, attrs):
        super(SingletonType, cls).__init__(name, bases, attrs)
        cls.instance = None
    
    def __call__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = super(SingletonType, cls).__call__(*args, **kwargs)
        return cls.instance

class Singleton(metaclass=SingletonType):
    pass
  • 定义一个元类,在元类的__call__方法中判断实例是否已存在,如果不存在则调用父类的__call__方法来创建并返回实例。

【4】基于__new__方法:

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super().__new__(cls)
        return cls._instance
  • 重写__new__方法,在实例化对象时判断类中是否已有实例,如果没有则调用父类的__new__方法来创建并返回。

【5】基于__init__方法:

class Singleton(object):
    _instance = None
    
    def __init__(self):
        if self._instance is None:
            self._instance = self
        else:
            raise Exception("Singleton instance already exists")
  • __init__方法中判断是否已有实例,如果没有则将当前实例赋值给类的类属性_instance,否则抛出异常。

【6】基于模块:

# singleton.py
class Singleton:
    pass

singleton_instance = Singleton()
  • 将实例化操作放在模块级别,通过导入该模块来获取实例。
  • 由于Python模块在运行时只会被导入一次,因此保证了实例的单一性。

【补充】数据库连接池的详解

  • 为了防止服务过多,导致服务宕机,所以需要一个中介(连接池)来作为中转,限制最大连接数
  • 数据库连接池是一种管理和提供数据库连接的技术,它可以在应用程序和数据库服务器之间建立一组可重复使用的连接。

    • 通过使用连接池,应用程序可以避免频繁的数据库连接和断开操作,从而提高数据库操作的性能和效率。
  • 连接池管理原理:

    • 连接池通常包括一个连接池管理器和一组数据库连接。

    • 连接池管理器负责创建、分配、回收和销毁数据库连接,并监控连接的状态。

    • 当应用程序需要访问数据库时,它从连接池获取一个可用的连接进行操作,完成后将连接归还给连接池供其他应用程序使用。

  • 连接的重用和性能提升:

    • 数据库连接的创建和关闭是较为耗时的操作,使用连接池可以减少这些开销。

    • 连接池维护一定数量的数据库连接,这些连接可以被不同的线程或请求重用,避免了每次操作都需要创建和释放连接的开销,提高了系统的性能和响应速度。

  • 连接管理与配置:

    • 连接池管理器跟踪连接的状态并执行相关的管理功能,比如空闲连接回收、连接超时处理等。

    • 连接池还可以通过配置参数来控制连接数量、连接超时时间、最大连接数等,以适应不同的应用场景和负载情况。

  • 连接的有效性验证:

    • 连接池可以定期验证连接的有效性,确保连接池中的连接都是可用的。

    • 这通常通过发送一条简单的数据库查询语句或者执行一些简单的数据库操作来实现。

    • 如果连接无效,连接池会将其标记为不可用并且从连接池中移除。

  • 连接泄漏的处理:

    • 连接池还能够检测和处理连接泄漏的问题。
    • 连接泄漏指的是应用程序在使用完连接后没有正确地释放连接,导致连接在连接池中一直被占用而无法回收。
    • 连接池可以设置一个时间阈值来检测闲置时间过长的连接,并对其进行处理,比如强制关闭连接或者重新创建新的连接来替代。
  • 总结:

    • 数据库连接池是一种有效管理和提供数据库连接的技术,它能够降低数据库操作的开销、提高系统性能和响应速度。
    • 通过合理配置和管理连接池,可以避免连接泄漏和连接超时等问题,确保应用程序与数据库之间的稳定和高效交互。
posted @ 2023-09-10 13:36  Chimengmeng  阅读(27)  评论(0编辑  收藏  举报