基于redis乐观锁实现并发排队 - 基于scrapy运行数量的控制
有个需求场景是这样的,使用redis控制scrapy运行的数量。当系统的后台设置为4时,只允许scapry启动4个任务,多余的任务则进行排队。
概况
最近做了一个django + scrapy + celery + redis 的爬虫系统,客户购买的主机除了跑其他程序外,还要跑我开发的这套程序,所以需要手动控制scrapy的实例数量,避免过多的爬虫给系统造成负担。
流程设计
1、爬虫任务由用户以请求的方式发起,所有的用户的请求统一进入到celery进行排队;
2、任务数量控制的执行就交给reids,经由celery保存到redis,包含了爬虫启动所需要的必要信息,从redis取一条信息即可启动一个爬虫;
3、通过scrapyd的接口来获取当前在运行的爬虫数量,以便决定下一步流程:如果小于4,则从redis中取相应数量的信息来启动爬虫,如果大于等于4,则继续等待;
4、如果在运行爬虫的数量有所减少,则及时从reids中取相应数量的信息来启动爬虫。
代码实现
业务代码有点复杂和啰嗦,此处使用伪代码来演示
import redis
# 实例化一个redis连接池
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, decode_responses=True, db=4, password='')
r = redis.Redis(connection_pool=pool)
# 爬虫实例限制为4 即只允许4个scrapy实例在运行
limited = 4
# 声明redis的乐观锁
lock = r.Lock()
# lock.acquire中有while循环,即它会线程阻塞,直到当前线程获得redis的lock,才会继续往下执行代码
if lock.acquire():
# 1、从reids中取一条爬虫信息
info = redis.get()
# 2、while循环监听爬虫运行的数量
while True:
req = requests.get('http://127.0.0.1:6800/daemonstatus.json').json()
# 统计当前有多少个爬虫在运行
running = req.get('running') + req.get('pending')
# 3、判断是否等待还是要增加爬虫数量
# 3.1 如果在运行的数量大于等于设置到量 则继续等待
if running >= limited:
continue
# 3.2 如果小于 则启动爬虫
start_scrapy(info)
# 3.3 将info从redis中删除
redis.delete(info)
# 3.4 释放锁
lock.release()
break
当前,这只是伪代码而已,实际的业务逻辑可能是非常复杂的,如:
@shared_task
def scrapy_control(key_uuid):
r = redis.Redis(connection_pool=pool)
db = MysqlDB()
speed_limited = db.fetch_config('REPTILE_SPEED')
speed_limited = int(speed_limited[0])
keywords_num = MysqlDB().fetch_config('SEARCH_RANDOM')
keywords_num = int(keywords_num[0])
# while True:
lock = r.lock('lock')
with open('log/celery/info.log', 'a') as f: f.write(str(datetime.datetime.now()) + '--' + str(key_uuid) + ' 进入处理环节' + '\n')
try:
# acquire默认阻塞 如果获取不到锁时 会一直阻塞在这个函数的while循环中
if lock.acquire():
with open('log/celery/info.log', 'a') as f: f.write(str(datetime.datetime.now()) + '--' + str(key_uuid) + ' 获得锁' + '\n')
# 1 从redis中获取信息
redis_obj = json.loads(r.get(key_uuid))
user_id = redis_obj.get('user_id')
contents = redis_obj.get('contents')
# 2 使用while循环处理核心逻辑
is_hold_print = True
while True:
req = requests.get('http://127.0.0.1:6800/daemonstatus.json').json()
running = req.get('running') + req.get('pending')
# 3 如果仍然有足够的爬虫在运行 则hold住redis锁,等待有空余的爬虫位置让出
if running >= speed_limited:
if is_hold_print:
with open('log/celery/info.log', 'a') as f: f.write(str(datetime.datetime.now()) + '--' + str(key_uuid) + ' 爬虫在运行,线程等待中' + '\n')
is_hold_print = False
time.sleep(1)
continue
# 4 有空余的爬虫位置 则往下走
# 4.1 处理完所有的内容后 释放锁
if len(contents) == 0:
r.delete(key_uuid)
with open('log/celery/info.log', 'a') as f: f.write(str(datetime.datetime.now()) + '--' + str(key_uuid) + ' 任务已完成,从redis中删除' + '\n')
lock.release()
with open('log/celery/info.log', 'a') as f: f.write(str(datetime.datetime.now()) + '--' + str(key_uuid) + ' 释放锁' + '\n')
break
# 4.2 创建task任务
task_uuid = str(uuid.uuid4())
article_obj = contents.pop()
article_id = article_obj.get('article_id')
article = article_obj.get('content')
try:
Task.objects.create(
task_uuid = task_uuid,
user_id = user_id,
article_id = article_id,
content = article
)
except Exception as e:
with open('log/celery/error.log', 'a') as f: f.write(str(datetime.datetime.now()) + '--' + str(key_uuid) + '->' + str(task_uuid) + ' 创建Task出错: ' + str(e) + '\n')
# finally:
# 4.3 启动爬虫任务 即便创建task失败也会启动
try:
task_chain(user_id, article, task_uuid, keywords_num)
except Exception as e:
with open('log/celery/error.log', 'a') as f: f.write(str(datetime.datetime.now()) + '--' + str(key_uuid) + ' 启动任务链失败: ' + str(e) + '\n')
# 加入sleep 防止代码执行速度快于爬虫启动速度而导致当前线程启动额外的爬虫
time.sleep(5)
except Exception as e:
with open('log/celery/error.log', 'a') as f: f.write(str(datetime.datetime.now()) + '--' + str(key_uuid) + ' 获得锁之后的操作出错: ' + str(e) + '\n')
lock.release()
小坑
scrapy启动速度相对较慢,所以while循环中,代码中执行到了爬虫的启动,需要sleep一下再去通过scrapyd接口获取爬虫运行的数量,如果立刻读取,可能会造成误判。
基于redis乐观锁实现并发排队 - 基于scrapy运行数量的控制的更多相关文章
- RedLock.Net - 基于Redis分布式锁的开源实现
工作中,经常会遇到分布式环境中资源访问冲突问题,比如商城的库存数量处理,或者某个事件的原子性操作,都需要确保某个时间段内只有一个线程在访问或处理资源. 因此现在网上也有很多的分布式锁的解决方案,有数据 ...
- 基于Redis分布式锁的正确打开方式
分布式锁是在分布式环境下(多个JVM进程)控制多个客户端对某一资源的同步访问的一种实现,与之相对应的是线程锁,线程锁控制的是同一个JVM进程内多个线程之间的同步.分布式锁的一般实现方法是在应用服务器之 ...
- 使用mysql乐观锁解决并发问题
案例说明: 银行两操作员同时操作同一账户.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交.最后实际账户余额为1000 ...
- 使用mysql乐观锁解决并发问题思路
本文摘自网络,仅供个人学习之用 案例说明: 银行两操作员同时操作同一账户.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后 ...
- Python Redis pipeline操作和Redis乐观锁保持数据一致性
Redis是建立在TCP协议基础上的CS架构,客户端client对redis server采取请求响应的方式交互. redis 乐观锁:也可理解为版本号比较机制,主要是说在读取数据逇时候同时读取其版本 ...
- 基于Redis分布式锁(获取锁及解锁)
目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题.分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency).可用性( ...
- redis乐观锁
乐观锁(又名乐观并发控制,Optimistic Concurrency Control,缩写"OCC"),是一种并发控制的方法.它假设多用户并发的事务在处理时不会彼此互相影响,各事 ...
- redis乐观锁(适用于秒杀系统)
redis事务中的WATCH命令和基于CAS的乐观锁 在Redis的事务中,WATCH命令可用于提供CAS(check-and-set)功能.假设我们通过WATCH命令在事务执行之前监控了多个Key ...
- redis 乐观锁实践秒杀
需求:有一个标(理解成抢红包也行,accountBalance预赋值1000元),大家可以抢购,每个用户抢购成功后,更新最后标的总数,在并发情况下,使用redis的乐观锁,保证更新标总值正确性,先往r ...
- 使用Redis分布式锁处理并发,解决超卖问题
一.使用Apache ab模拟并发压测 1.压测工具介绍 $ ab -n 100 -c 100 http://www.baidu.com/ -n表示发出100个请求,-c模拟100个并发,相当是100 ...
随机推荐
- Vue+vant移动端处理弹窗不能滑动问题
自己在做项目开发时,使用vantUI组件,在项目中遇到了弹窗组件里面当内容过多时,会出现滚动卡顿或者不能滚动问题,开始一直以为是自己的样式写的有问题,检查下来才发现并不是,而是弹窗组件的问题,于是找到 ...
- StampedLock:一个并发编程中非常重要的票据锁
摘要:一起来聊聊这个在高并发环境下比ReadWriteLock更快的锁--StampedLock. 本文分享自华为云社区<[高并发]一文彻底理解并发编程中非常重要的票据锁--StampedLoc ...
- Linux基础_1_简介
Linux是什么 一款优秀的操作系统软件,特性是一切皆文件:一切设备皆文件!一切设备的设置皆修改配置文件!一切服务的搭建皆修改配置文件!(庞大的树形结构文件系统) 根据FHS标准,Linux目录有以下 ...
- jsp和java的结合使用显示学生信息
package com.zyz; public class Student { private String ID; // 学号 private String name; // 姓名 private ...
- 生成随机数的几种方法、Math.random()随机数的生成、Random()的使用
第一种方法使用:System.currentTimeMillis(); final long l = System.currentTimeMillis(); final int rs = (int) ...
- C/S、B/S、Web的介绍(Web应用开发)
文章目录 1.C/S结构介绍 2.B/S结构介绍 3.Web介绍 3.1 .什么是web? 3.2 .Web的工作原理 3.3 客户端应用技术 3.4 服务端应用技术 1.C/S结构介绍 Client ...
- 网页头部的声明应该是用 lang="zh" 还是 lang="zh-CN"?
网页头部的声明应该是用 lang="zh" 还是 lang="zh-CN"? 遇到问题 不知道大家有没有留意到一个问题,就是使用 VsCode 新建的 html ...
- win10安装pip
Windows如何安装pip?请看下面方法: 1.搜索pip 2.点击下载文件. 3.下载压缩包 6.解压到桌面. 7.进入解压目录,按住Shift点击右键,选择打开powershell 8.执行py ...
- 19.-哈希算法&注册登录
一.哈希算法 哈希: 给定明文-计算出一段定长的-不可逆的值 定长输出:不管明文输入多少,哈希都是定长的 不可逆:无法反向计算出对应的明文 雪崩效应:输入改变,输出必然变 md5:32位16进制 ...
- 解决ffmpeg的播放摄像头的延时优化问题(项目案例使用有效)
在目前的项目中使用了flv的播放摄像头的方案,但是延时达到了7-8秒,所以客户颇有微词,没有办法,只能开始优化播放延时的问题,至于对接摄像头的方案有好几种,这种咱们以后在聊,今天只要聊聊聊优化参数的问 ...