Rumah  >  Artikel  >  pangkalan data  >  Bagaimana untuk melaksanakan giliran serentak berdasarkan penguncian optimistik redis

Bagaimana untuk melaksanakan giliran serentak berdasarkan penguncian optimistik redis

WBOY
WBOYke hadapan
2023-06-04 09:58:091591semak imbas

Terdapat senario permintaan seperti ini, menggunakan redis untuk mengawal bilangan larian buruk. Selepas menetapkan latar belakang sistem kepada 4, scrapy hanya boleh memulakan sehingga 4 tugasan dan lebihan tugasan akan dibariskan untuk menunggu.

Ikhtisar

Saya baru-baru ini membina sistem perangkak django + scrapy + celery + redis Selain menjalankan program lain, hos yang dibeli oleh pelanggan juga perlu menjalankan program yang saya bangunkan, jadi ia perlu Kawal bilangan kejadian buruk secara manual untuk mengelakkan terlalu banyak perangkak membebankan sistem.

Reka Bentuk Proses

1 Tugas crawler dimulakan oleh pengguna dalam bentuk permintaan, dan semua permintaan pengguna dimasukkan secara seragam untuk beratur
2 kawalan kuantiti tugasan diserahkan Untuk reids, ia disimpan ke redis melalui saderi, yang mengandungi maklumat yang diperlukan untuk permulaan crawler boleh dimulakan dengan mengambil sekeping maklumat daripada redis 3. Dapatkan nombor sedang menjalankan perangkak melalui antara muka scrapyd untuk memutuskan Langkah seterusnya: Jika kurang daripada 4, dapatkan jumlah maklumat yang sepadan daripada redis untuk memulakan perangkak Jika lebih besar daripada atau sama dengan 4, teruskan menunggu; 🎜> 4. Jika bilangan perangkak yang sedang berjalan berkurangan, dapatkannya dari reids tepat pada masanya Dapatkan jumlah maklumat yang sepadan untuk memulakan perangkak.

Pelaksanaan kod

Kod perniagaan agak rumit dan bertele-tele, di sini kami menggunakan kod pseudo untuk menunjukkan

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		

Pada masa ini, ini hanyalah kod pseudo, logik perniagaan sebenar mungkin sangat Kompleks, seperti:

@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()

Lubang kecil
Kelajuan permulaan scrapy agak perlahan, jadi dalam gelung while, kod dilaksanakan untuk memulakan perangkak dan anda perlu tidur sebentar sebelum mendapatkan perangkak melalui antara muka scrapyd Bilangan larian, jika dibaca serta-merta, boleh menyebabkan salah penilaian.


Atas ialah kandungan terperinci Bagaimana untuk melaksanakan giliran serentak berdasarkan penguncian optimistik redis. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam