基於Python實現環形佇列高效定時器

pythonputao發表於2020-12-16

定時器Python實現程式碼

import time
import redis
import multiprocessing


class Base:

    """
    redis配置
    """
    redis_conf = {}

    """
    環形佇列使用redis進行儲存
    """
    _ri = None

    """
    定時器輪盤大小
    """
    slot_num = 15

    """
    儲存環形佇列使用的redis快取key
    """
    cache_key = 'wheel:slot_'

    def __init__(self, **kwargs):
        for k in kwargs:
            if hasattr(self, k):
                setattr(self, k, kwargs[k])

        self._ri = redis.Redis(**self.redis_conf)


class Timer(Base):
    """
    當前slot的下標
    """
    _current = 0

    """
    事件處理
    """
    event_handler = None

    def worker(self):
        """
        # TODO 測試每個卡槽有1W事件ID的處理效率
        獨立程式,分發事件id給事件處理器
        :return:
        """
        key = self.cache_key + str(self._current)

        # 獲取當前卡槽中需要觸發的事件ID
        event_ids = self._ri.zrangebyscore(key, 0, 0)

        # 刪除當前卡槽中需要觸發的事件ID
        self._ri.zremrangebyscore(key, 0, 0)

        # 把當前卡槽剩下的事件ID全部遍歷出來,減少一次剩餘迴圈次數
        surplus_event_ids = self._ri.zrange(key, 0, -1)

        for mid in surplus_event_ids:
            self._ri.zincrby(key, mid, -1)

        # 把事件ID轉交給handler處理
        for mid in event_ids:
            self.event_handler(eid=mid)

        exit(0)

    def run(self):
        """
        啟動程式
        :return:
        """
        while True:
            p = multiprocessing.Process(target=self.worker)
            p.start()

            time.sleep(1)

            self._current = int(time.time()) % self.slot_num


class TimerEvent(Base):

    def add(self, event_id, emit_time):
        """
        新增事件ID到定時器
        :param event_id: 事件ID
        :param emit_time: 觸發時間
        :return:
        """
        current_time = int(time.time())
        diff = emit_time - current_time

        if diff > 0:
            # 計算迴圈次數
            cycle = int(diff / self.slot_num)
            # 計算要存入的slot的索引
            index = (diff % self.slot_num + current_time % self.slot_num) % self.slot_num

            res = self._ri.zadd(self.cache_key + str(index), str(event_id), cycle)
            return True if res else False

        return False

    # TODO 批量新增同一時間,不同事件ID

    # TODO 批量新增不同時間,不同事件ID

通過環形佇列實現高效任務觸發的設計說明

  1. redis集合【slot】
  • 以redis多個有規律的鍵名的有序集合組成環形陣列
key_1
key_2
....
key_n
  • 有序集合

命令

ZADD key score member
有序集合中包含兩部分, 一個是score, 一個是member

score作為剩餘迴圈次數  
meber作為事件ID
  1. python多程式
  • 計算當前時間應該處理的卡槽

    當前slot索引 = (當前時間 % 卡槽總數 + 當前時間戳 % 卡槽總數) % 卡槽總數

"%"為取餘數操作

  • 建立獨立子程式處理

    當前子程式需要快速讀取的剩餘迴圈次數為0事件ID

    刪除當前slot已取出的事件ID

    開始把事件ID依次轉交給事件handler處理

應用說明

  1. 啟動定時器
import Timer
import time


def event_handler(eid):
    print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())), eid)


t = Timer(redis_conf={
    'host': '127.0.0.1',
    'port': 6379,
    'password': '123456',
    'db': 0
}, event_handler=event_handler)

times = int(time.time())

print('Current Time is ' + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(times)))

t.run()
  1. 新增需要延時觸發事件ID
import TimerEvent
import time


te = TimerEvent(redis_conf={
    'host': '127.0.0.1',
    'port': 6379,
    'password': '123456',
    'db': 0
})

times = int(time.time())

print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(times)))

after_seconds_alert = 20

for x in range(100):
    te.add(x, times + after_seconds_alert + x)

print('Firs Emit will happened at ' + time.strftime(
    'Start:%Y-%m-%d %H:%M:%S',
    time.localtime(times + after_seconds_alert))
)

本文的文字及圖片來源於網路,僅供學習、交流使用,不具有任何商業用途,如有問題請及時聯絡我們以作處理
想要獲取更多Python學習資料可以加
QQ:2955637827私聊
或加Q群630390733
大家一起來學習討論吧!

相關文章