〖Python 数据库开发实战 - Python与Redis交互篇⑨〗- 利用 redis-py 实现模拟商品秒杀活动案例

news2024/11/25 10:49:03

文章目录
❤️‍🔥 为什么要引入线程池技术
❤️‍🔥 通过案例加深线程池技术原理的理解
❤️‍🔥 实现多线程模拟商品秒杀案例 - 思路
❤️‍🔥 实现多线程模拟商品秒杀案例 - 代码
今天的这一章节我们将来实现 “模拟商品秒杀活动” 的案例,在这个案例中将使用到多线程技术,相较于上一章节的 “缓存观众投票数据信息” 的案例。在技术难点山又提升了一个阶层,Python 自带的线程池技术免去了我们自己创建和管理线程的麻烦,只需要编写好线程需要执行的任务,将任务交给线程池运行即可。
 

为什么要引入线程池技术

我们使用的数据库连接池技术是为了免去反复创建和销毁连接,造成硬件资源的不必要浪费。
线程池技术的设计考量也是如此,如果程序中经常需要用到线程, 频繁的创建和销毁线程对象,会极大的浪费很多硬件资源(线程对象不像普通的对象,在创建线程的时候需要分配一定的内存,还需要调度线程去启动执行等等... 这个过程是很麻烦的) 。
我们可以把线程和任务分离开利用线程池缓存一些空闲的线程(空闲线程的数量是可以自定义的), 如此线程可以反复利用,省去了重复创建的麻烦。
当需要线程去执行某一个任务的时候,就可以将那个这个任务写成一个函数传递给线程池,线程池就会挑选一个空闲的线程去执行这个任务。任务执行结束之后,分配出去执行任务的线程就会被回收,也就避免了现成的反复创建和销毁。 这个原理与数据库连接池技术比较相像。
 

通过案例加深线程池技术原理的理解

  • 接下来我们通过一个简单的案例了解一下 Python 程序中的 线程池应该如何去使用。案例如下:
  • # coding:utf-8
    
    
    from concurrent.futures import ThreadPoolExecutor  # 导入 concurrent.futures 模块的 线程池类 ThreadPoolExecutor
    
    
    def say_hello():  # 定义线程任务函数,只执行打印 "Hello" 的操作
        print("Hello")
    
    
    executor = ThreadPoolExecutor(50)   # 利用 ThreadPoolExecutor 构造线程池对象,传入 缓存的线程池数量 50
    for i in range(0, 10):  			# 利用 for 循环与 executor 的 submit 函数传入 线程任务函数 - say_ hello
        hello = executor.submit(say_hello)
        print(hello)
    

  • 线程池这个例子非常的简单,接下来我们将要实现的 “抢购秒杀的活动”,就会利用线程池模拟很多用户来抢购商品

     实现多线程模拟商品秒杀案例 - 思路

  • 案例需求:
    利用Python多线程模拟商品秒杀过程,不可以出现超买和超卖的情况。
    假设A商品有50件参与秒杀活动,10分钟秒杀自动结束。
    实现思路:
    以 1000 人为例,模拟1000人去抢购 50 件商品。如果使用 for 循环去执行 1000 次,那就不是抢购了,而是排队购买东西。
    因为只有线程是并发执行的,所以只有多线程才能够模拟并发的抢购,如此这种秒杀的盛况才能够出现,这也是我们之所在讲解案例之前介绍线程池的主要原因。
    线程池缓存线程的数量为多少合适呢?1000 人抢购,由于网络延迟、操作APP、操作浏览器的因素,可能每秒钟只有几百人参与秒杀。那么创建一个缓存了 200 数量线程的线程池即可,可以模拟每秒钟最多 200 个用户同时抢购。
    redis 采用的是单线程机制,所以也不可能会出现超买和超卖的问题;如果使用 MySQL 的话,就可能会存在这种情况。
    既然这个案例需要使用 redis 来完成,那就需要分析一下,redis 里需要保存的记录都有哪些?如下:

    kill_total			# 商品总数;(字符串类型)
    
    kill_num			# 秒杀成功的数量;
    
    kill_flag			# 当秒杀数量为 50 ,或者时间满足 10 分钟 的时候,就删除掉 kill_flag,表示秒杀活动结束。 
    
    kill_user			# 记录秒杀成功的用户id,使用 列表 进行记录。(需要注意的是用户id是不可以重复的)
    

    实现多线程模拟商品秒杀案例 - 代码

  • # coding:utf-8
    
    
    import redis
    import random
    from redis_db import redis_Pool         # 导入 redis 连接池
    from concurrent.futures import ThreadPoolExecutor       # 导入 concurrent.futures 模块的 线程池类 ThreadPoolExecutor
    
    
    """
    生成随机用户的id
    """
    s = set()               # 设置空集合,将其赋值给变量 s
    
    
    """
    定义一个死循环,向集合中添加 1000个 不重复的用户id;
    因为使用的是 random 模块,不能保证生成的用户 id 是不重复的,所以 while 循环是不能够循环 1000 次的;
    所以 while 循环的结束条件是 s 的长度为 1000 的时候,小于 1000 的情况下,是需要继续向 s 中添加 用户id 的
    """
    while True:
        if len(s) == 1000:
            break
        num = random.randint(10000, 100000)
        s.add(num)      # 将随机生成的 用户id 添加到 s 中去
    
    
    """
    获得 redis 连接
    """
    con = redis.Redis(
        connection_pool=redis_Pool
    )
    
    
    """
    利用 try...except...finally 捕获异常
    1、需要判断 redis 中是否包含有秒杀用到的数据,这段程序在调试的过程中肯定会运行不止一次,如果已存在秒杀数据,势必会影响下一次运行。
    2、如果第一次运行的结果,秒杀成功的记录没有被删除,第二次再次运行的时候又会将秒杀成功的记录再次添加,这就非常不合理了。
    3、所以每一次运行脚本之前,就需要将与 秒杀案例 相关的 redis 的记录全部删掉
    """
    try:
        con.delete("kill_total", "kill_num", "kill_flag", "kill_user")      # 删掉 redis 的秒杀案例相关的记录
        con.set("kill_total", 50)       # 写入 秒杀商品总数量 kill_total,数量 50
        con.set("kill_num", 0)          # 写入 秒杀活动成功抢购的数量 kill_num,初始数量 0
        con.set("kill_flag", 1)         # 写入 秒杀活动 的状态 kill_num,参数值 1 ;这里给多少随意,只是用来判断活动的标识。
        con.expire("kill_flag", 600)    # 写入 秒杀活动 的状态的过期时间,十分钟,600秒
        """kill_user 不用创建,在后面用到的时候像 kill_user 添加记录时会自动创建"""
    except Exception as e:          # 捕获异常并打印输出
        print(e)
    finally:                        # 回收 redis 连接
        del con
    
    
    """
    定义线程池,传入 缓存的线程池数量 200
    """
    executor = ThreadPoolExecutor(200)
    
    
    """
    创建线程池任务 - 秒杀
    1、参与秒杀时,需要重点关注秒杀的数据
    2、利用 redis 事务去执行 秒杀任务 数据的记录 - pipeline()
    """
    def buy():
        """获取 redis 链接(区别于第 31 行的 con 连接);创建 redis 事务 - pipeline()"""
        connection = redis.Redis(
            connection_pool=redis_Pool
        )
    
        """定义 pipline """
        pipline = connection.pipeline()
    
        """利用 try...except...fi nally 捕获异常;并最终回收链接。"""
        try:
            """通过 connection 判断 '秒杀' 是否处于有效状态;如果 'kill_flag' 为 0 的话,则秒杀任务就不存在,没有任何实际意义"""
            if connection.exists("kill_flag") == 1:
                """在秒杀活动生效的情况下,观察 成功抢购数 与 成功抢购的用户ID 的两个数据"""
                pipline.watch("kill_num", "kill_user")
                """获取 秒杀商品 的总数,需要转码与转换 int 数据类型"""
                total = int(pipline.get("kill_total").decode("utf-8"))
                """获取 秒杀活动成功抢购 的数量,需要转码与转换 int 数据类型"""
                num = int(pipline.get("kill_num").decode("utf-8"))
    
                """
                当 秒杀活动成功抢购 的数量 小于 秒杀商品 的总数时[num < tota],秒杀活动还未结束,可以继续参与秒杀;
                当 有效时,将 kill_num 的数量加 1 ,并将 user_id 写入 kill_user 记录 
                """
                if num < total:
                    """利用 pipline 的 multi() 函数,开启 redis 的事务"""
                    pipline.multi()
                    """利用 pipline 的 incr() 函数,将 'kill_num' 记录 +1 """
                    pipline.incr("kill_num")
                    """利用 s 集合 的 pop() 函数删除并获取 random 随机生成的 用户id ,赋值给 user_id 变量"""
                    user_id = s.pop()
                    """利用 pipline 的 rpush() 函数,将秒杀到商品的用户的 'user_id' 传入给 kill_user记录 """
                    pipline.rpush("kill_user", user_id)
                    """利用 pipline 的 execute() 函数,提交 redis 的事务"""
                    pipline.execute()
        except Exception as error:
            print(error)
        finally:
            """判断 pipline 是否存在,存在的话,回收 pipline 事务"""
            if "pipline" in dir():
                pipline.reset()
            del connection
    
    
    """
    利用 for 循环,模拟 1000 名用户参与抢购秒杀活动;
    将 bug 函数作为线程池任务函数,传给线程池来执行。
    """
    for i in range(0, 1000):
        executor.submit(buy)
    
    print("秒杀已经结束")
    

     

     

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/143960.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ES6 课程概述②

文章目录更好的 Unicode 支持更多的字符串 API3-3. [扩展]正则中的粘连标记模板字符串3-5. [扩展]模板字符串标记4-1. 参数默认值使用[扩展]对 arguments 的影响[扩展]留意暂时性死区4-2. 剩余参数4-3. 展开运算符对数组展开 ES6对对象展开 ES7函数柯里化4-5. 明确函数的双重用…

【菜菜的CV进阶之路 - 深度学习环境搭建】配置Ubuntu深度学习环境

六、配置Ubuntu深度学习环境 1、安装Google chrome 使用wget下载最新的Google Chrome .deb软件包&#xff1a; wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb更多地址可参考&#xff1a;在Deepin v20系统中下载和安装谷歌Chrome最新版de…

Vue学习笔记(一)

Vue学习笔记1. 什么是Vue2. 安装Vue2.1 使用独立版本2.2 使用CDN方式2.3 使用NPM方式3.Vue语法3.1 el挂载点3.2 data数据对象3.3 V-text 设置标签内的内容3.4 V-html3.5 V-on3.6 计数器3.7 v-show3.8 V-if3.9 v-bind3.10 v-for3.11 V-model4.class与style绑定5.表单输入绑定5.1…

【vue2】基础概念 01 (vue框架介绍、el、data、插值表达式)

&#x1f973;博 主&#xff1a;初映CY的前说(前端领域) &#x1f31e;个人信条&#xff1a;想要变成得到&#xff0c;中间还有做到&#xff01; &#x1f918;本文核心&#xff1a;vue框架介绍、结构元素详解&#xff08;el、data、插值表达式&#xff09; 目录&#xf…

《我是个怪圈》读书笔记

文章目录书籍信息论灵魂及其尺寸摇曳在恐惧与梦想之间的那只电灯泡&#xff08;取自拉塞尔埃德森的诗&#xff09;模式的因果潜力关于自我与符号副现象模式与可证性哥德尔的典型怪圈奇迹般步调划一的同步在公式与大整数之间翻转很大的整数与公式步调一致的移动质雅数怪圈论向下…

vue实现文件预览功能

目录 一、使用插件预览 1.前端实现在线预览文档 通过联机查看 Office 文档 打开新窗口预览文件 当前页面预览文件 注意事项 在创建好url之后&#xff0c;可能会出现无法打开文档的情况&#xff0c;这时候就需要对照官网的解释来查找问题了&#xff0c;官方文档的解释如下…

k线图入门精讲

K线图是贵金属技术分析的基础手段&#xff0c;刚入门的投资者应认真学习和理解k线的基础知识&#xff0c;因为有了认识才能分析。然而&#xff0c;多数上班族精力有限&#xff0c;无法耗费大量精力学习&#xff0c;今天小编就为准备了K线入门的“精读班”。 一、K线的作用 K线图…

【node.js】fs\path\http模块的使用 02

&#x1f973;博 主&#xff1a;初映CY的前说(前端领域) &#x1f31e;个人信条&#xff1a;想要变成得到&#xff0c;中间还有做到&#xff01; &#x1f918;本文核心&#xff1a;Node.js的fs\path\http模块的使用&#xff0c;模块化开发概念 目录 一、node.js概念与作…

Java程序员你自己的菜鸟气质霸气侧漏了吗?

对于刚入行的程序员来说&#xff0c;面对各种各样的陌生配置环境和代码库&#xff0c;难免会手忙脚乱&#xff0c;尽显菜鸟本色。 但从啥都需要教的菜鸟到啥都懂的大神程序员&#xff0c;并不简单&#xff0c;这需要牺牲一根又一根宝贵的头发&#xff0c;直到它们肉眼可数。 …

线上服务器CPU占用过高?7步带你搞定

一. 前言在Java开发岗位的面试中&#xff0c;时不时会出现一些运维类的题目&#xff0c;其实这也反映了后端面试的一种趋势。现在企业对后端开发的要求越来越全面&#xff0c;不仅要求我们会写代码&#xff0c;还要我们能够进行部署和运维。今天壹哥就结合一个真实的项目案例&a…

【Linux】Linux权限

权限的概念 限制人的&#xff0c;访问的对象天然可能没有这种“属性”权限&#xff1a;一件事情是否允许被谁“做”。 权限 人 事物属性 Linux上的用户分类 root&#xff0c;超级管理员&#xff0c;几乎可以干任何事情&#xff08;1个&#xff09;普通用户&#xff08;多个&a…

雷电飞机大战游戏|基于Java开发实现雷电飞机大战游戏

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

【前端】Vue项目:旅游App-(8)city:标签页Tabs动态数据:网络请求axios与request、数据管理store与pinia、各种封装

文章目录目标过程与代码安装相关库封装网络请求相关代码网络请求数据网络请求数据操作封装pinia管理数据并封装tab栏改为动态数据效果本篇总结总代码修改或新建的文件serviceindexmodules的cityrequest的configrequest的indexstoremodules的citymodules的loadingcity.vue参考目…

录屏没有声音怎么办?录屏怎么录声音

相信部分朋友在录制视频时&#xff0c;有出现录制视频没有声音&#xff0c;导致该段视频没有声音而无法播放。录屏怎么录声音&#xff1f;可以使用支持录制声音的专业的电脑录屏软件。今天小编就在这给大家分享在录制视频同时&#xff0c;将声音也录制进去的操作步骤。一、录屏…

【Linux】主函数的三个形参

主函数的形参有三个&#xff1a;argc参数个数&#xff0c;argv参数内容&#xff0c;envp环境变量。其中argc是整型&#xff0c;argv和envp是指针数组&#xff08;存的字符串&#xff09; argv源于我们自己在使用执行命令时传的内容&#xff0c;envp源于程序的父进程&#xff08…

力扣sql入门篇(十)

力扣sql入门篇(十) 1 查找重复的电子邮箱 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 SELECT Email FROM Person GROUP BY Email HAVING count(id)>21.3 运行截图 2 合作过至少三次的演员和导演 2.1 题目内容 2.1.1 基本题目信息 2.1.2 示例…

Java并发编程(六)ExecutorService

ExecutorService invokeAny() he invokeAll() 具有阻塞特性 invokeAny invokeAny 的作用是取得第一个完成任务的结果的值。 如果线程中增加 if (!Thread.currentThread().isInterrupted()) 判断&#xff0c;则会中断这些线程。 其他线程如果抛出 InterruptedException() 异常&a…

从徘徊迷茫到行业精英,社科院与杜兰大学金融管理硕士改变你的人生轨迹

在以“内卷”为主基调的职场环境里&#xff0c;似乎不停地进阶已经成为了职场人的唯一出路。但是&#xff0c;如何在进阶路上冲破职业瓶颈&#xff0c;到达心之所往的理想位置&#xff0c;则没有一个标准的答案。有的职场人士通过考取不同的技能证书来增加自身优势&#xff0c;…

Java Agent 踩坑之 appendToSystemClassLoaderSearch 问题

作者&#xff1a;卜比 本文是《容器中的 Java》系列文章之 2/n&#xff0c;欢迎关注后续连载 &#x1f603; 。 从 Java Agent 报错开始&#xff0c;到 JVM 原理&#xff0c;到 glibc 线程安全&#xff0c;再到 pthread tls&#xff0c;逐步探究 Java Agent 诡异报错。 背景 …

数据分析,你还在单纯地看数据?

企业的数字化意识越来越强&#xff0c;工作中也开始使用各种业务系统来管理业务&#xff0c;管理数据。很多人以为上了业务系统&#xff0c;对数据进行统计了&#xff0c;就是数据分析&#xff0c;这是大错特错的观点&#xff0c;数据分析是通过数据来剖析企业经营管理和业务发…