redis学习笔记(十)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: redis学习笔记(十)

关于redis的实战案例

(1)案例1:KV缓存

第1个是最基础也是最常见的就是KV功能,我们可以用Redis来缓存用户信息、会话信息、商品信息等等。下面这段代码就是通过缓存读取逻辑。

import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=6, decode_responses=True)
r = redis.Redis(connection_pool=pool)
def get_user(user_id):
    user = r.get(user_id)
    if not user:
        user = UserInfo.objects.get(pk=user_id)
        r.setex(user_id, 3600, user)
    return user

(2)案例2:分布式锁

什么是分布式锁

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

提到Redis的分布式锁,很多小伙伴马上就会想到setnx+ expire命令。即先用setnx来抢锁,如果抢到之后,再用expire给锁设置一个过期时间,防止锁忘记了释放。

SETNX 是SET IF NOT EXISTS的简写.日常命令格式是SETNX key value,如果 key不存在,则SETNX成功返回1,如果这个key已经存在了,则返回0。

假设某电商网站的某商品做秒杀活动,key可以设置为key_resource_id,value设置任意值,伪代码如下:

方案1

import redis
pool = redis.ConnectionPool(host='127.0.0.1')
r = redis.Redis(connection_pool=pool)
ret = r.setnx("key_resource_id", "ok")
if ret:
    r.expire("key_resource_id", 5)  # 设置过期时间
    print("抢购成功!")
    r.delete("key_resource_id")  # 释放资源
else:
    print("抢购失败!")

但是这个方案中,setnxexpire两个命令分开了,「不是原子操作」。如果执行完setnx加锁,正要执行expire设置过期时间时,进程crash或者要重启维护了,那么这个锁就“长生不老”了,「别的线程永远获取不到锁啦」

方案2

SETNX + value值是(系统时间+过期时间)

为了解决方案一,「发生异常锁得不到释放的场景」,可以把过期时间放到setnx的value值里面。如果加锁失败,再拿出value值校验一下即可。加锁代码如下:

import time
def foo():
    expiresTime = time.time() + 10
    ret = r.setnx("key_resource_id", expiresTime)
    if ret:
        print("当前锁不存在,加锁成功")
        return True
    oldExpiresTime = r.get("key_resource_id")
    if float(oldExpiresTime) < time.time():  # 如果获取到的过期时间,小于系统当前时间,表示已经过期
        # 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间
        newExpiresTime = r.getset("key_resource_id", expiresTime)
        if oldExpiresTime == newExpiresTime:
            #  考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才可以加锁
            return True  # 加锁成功
    return False  # 其余情况加锁皆失败
foo()

方案3

实际上,我们还可以使用Py的redis模块中的set函数来保证原子性(包含setnx和expire两条指令)代码如下:

r.set("key_resource_id", "1", nx=True, ex=10)

(3)案例4:延迟队列

延时队列可以通过Redis的zset(有序列表)来实现。
我们将消息序列化为一个字符串作为zset的值。
这个消息的到期时间处理时间作为score,
然后用多个线程轮询zset获取到期的任务进行处理,
多线程时为了保障可用性,万一挂了一个线程还有其他线程可以继续处理。
因为有多个线程,所有需要考虑并发争抢任务,确保任务不能被多次执行。
import time
import uuid
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, decode_responses=True)
r = redis.Redis(connection_pool=pool)
def delay_task(task_name, delay_time):
    # 保证value唯一
    task_id = task_name + str(uuid.uuid4())
    retry_ts = time.time() + delay_time
    r.zadd("delay-queue", {task_id: retry_ts})
def loop():
    print("循环监听中...")
    while True:
        # 最多取1条
        task_list = r.zrangebyscore("delay-queue", 0, time.time(), start=0, num=1)
        if not task_list:
            # 延时队列空的,休息1s
            print("cost 1秒钟")
            time.sleep(1)
            continue
        task_id = task_list[0]
        success = r.zrem("delay-queue", task_id)
        if success:
            # 处理消息逻辑函数
            handle_msg(task_id)
def handle_msg(msg):
    """消息处理逻辑"""
    print(f"消息{msg}已经被处理完成!")
import threading
t = threading.Thread(target=loop)
t.start()
delay_task("任务1延迟5", 15)
delay_task("任务2延迟2", 20)
delay_task("任务3延迟3", 30)
delay_task("任务4延迟10", 60)

redis的zrem方法是对多线程争抢任务的关键,它的返回值决定了当前实例有没有抢到任务,因为loop方法可能会被多个线程、多个进程调用, 同一个任务可能会被多个进程线程抢到,通过zrem来决定唯一的属主。同时,一定要对handle_msg进行异常捕获, 避免因为个别任务处理问题导致的循环异常退出。

(4)案例5:发布订阅

subscribe channel # 订阅
publish channel mes # 发布消息
import threading
import redis
r = redis.Redis(host='127.0.0.1')
def recv_msg():
    pub = r.pubsub()
    pub.subscribe("fm104.5")
    pub.parse_response()
    while 1:
        msg = pub.parse_response()
        print(msg)
def send_msg():
    msg = input(">>>")
    r.publish("fm104.5", msg)
t = threading.Thread(target=send_msg)
t.start()
recv_msg()

(5)案例3:定时任务

利用 Redis 也能实现订单30分钟自动取消。

用户下单之后,在规定时间内如果不完成付款,订单自动取消,并且释放库存使用技术:Redis键空间通知(过期回调)用户下单之后将订单id作为key,任意值作为值存入redis中,给这条数据设置过期时间,也就是订单超时的时间启用键空间通知

开启过期key监听

from redis import StrictRedis
redis = StrictRedis(host='localhost', port=6379)
# 监听过期key
def event_handler(msg):
    print("sss",msg)
    thread.stop()
pubsub = redis.pubsub()
pubsub.psubscribe(**{'__keyevent@0__:expired': event_handler})
thread = pubsub.run_in_thread(sleep_time=0.05)


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
18天前
|
存储 NoSQL 应用服务中间件
Redis 学习笔记
本文详细介绍了Redis的基本概念、特性、应用场景,并提供了在Centos下安装Redis5的步骤,以及如何使用Redis客户端和命令进行数据类型操作和管理。
35 2
|
2月前
|
存储 NoSQL Redis
Redis学习笔记
这篇文章是关于Redis的学习笔记,介绍了Redis的基本概念、数据结构、持久化方式以及与其他数据库的比较,并提供了Redis的官网地址和其在数据库领域的应用场景。
|
5月前
|
存储 缓存 NoSQL
redis学习笔记
Redis是一款由C语言编写的内存中的键值对数据库,包含多种数据结构如字符串、散列、列表、集合、有序集合等。它支持复制、LUA脚本、LRU策略、事务和持久化。Redis官网提供详细文档,可用于分布式缓存、防止缓存击穿、分布式锁、消息队列等场景。安装Redis 7.0.15版,需下载、配置环境变量、修改配置文件以设置守护进程、关闭保护模式和密码。Redis基础命令包括键操作如DEL、EXISTS、EXPIRE等,数据类型包括字符串和哈希等,如HSET用于设置哈希表字段值,HGET用于获取字段值。
35 0
|
NoSQL Redis
redis学习笔记(五)
redis学习笔记(五)
|
NoSQL Redis Python
redis学习笔记(九)
redis学习笔记(九)
|
NoSQL Java Redis
redis学习笔记(二)
redis学习笔记(二)
|
NoSQL Java Redis
redis学习笔记(八)
redis学习笔记(八)
|
SQL NoSQL 关系型数据库
redis学习笔记(一)
redis学习笔记(一)
|
NoSQL Redis 索引
redis学习笔记(四)
redis学习笔记(四)
|
NoSQL 搜索推荐 Redis
redis学习笔记(六)
redis学习笔记(六)