自动记录状态的库(线程安全,非进程安全)

# encoding=utf8
import threading
import time


class AutoKnight(object):
    def __init__(self, retry_times=5, retry_delay=60, disabled_delay=600, disable_run_times=20, disable_warning=[],
                 disable_stages=[], warning_handle=None):
        """
        ①当满足在retry_delay时间内,连续错误retry_times次,
        ②则自动停用某个stage,之后停用disable_delay秒或者停用disable_run_times次后,
        ③再继续单次尝试,一旦访问通过,则恢复,否则继续②步骤

        __stages = {stage_key: {'retry_times':1, 'first_retry_time':1020004233, 'is_abled': False,
                              'disabled_times':0, 'disabled_run_times': 0, 'last_failed_msg':''}}

        :param retry_times:策略生效的重试次数 <=0 此策略不生效
        :param retry_delay:策略生效的时间约束 <=0 此策略不生效
        :param disabled_delay:停用时间 <=0 此策略不生效
        :param disable_run_times:停用时访问此stage 次数 <=0 此策略不生效
        :param disable_warning:一旦命中此此策略,不会将策略降级,而是直接发送waring(一般用于比较重要的stage)
        :param disable_stages:永久停用策略 默认为空
        """
        self.__stages = {}
        self.__retry_times = 0 if retry_times <= 0 else retry_times
        self.__retry_delay = 0 if retry_delay <= 0 else retry_delay
        self.__disabled_delay = 0 if disabled_delay <= 0 else disabled_delay
        self.__disable_run_times = 0 if disable_run_times <= 0 else disable_run_times
        self.__disable_stages = disable_stages
        self.__disable_warning = disable_warning
        self._LOCK = threading.Lock()
        self.__handle_warning = warning_handle or (lambda *args: args)

    def __is_disabled(self, key, isok):
        if key in self.__disable_warning:
            if not isok:
                self.__handle_warning(key, self.__stages.get(key, {}).get('last_failed_msg'))
            return False
        else:
            return isok

    def get_disable_stages(self):
        """
        获取当前被disabled的stage
        :return: [key]
        """
        result = []
        with self._LOCK:
            for key, val in self.__stages.items():
                if val.get('is_abled', True) is False:
                    result.append(key)
            result += self.__disable_stages
        return list(set(result))

    def get_details(self, keys):
        """
        获取keys策略的详细信息
        :param keys: string or list
        :return: list
        """
        if isinstance(keys, basestring):
            keys = [keys]
        result = []
        with self._LOCK:
            for key in keys:
                result.append([key, self.__stages.get(key, {})])
        return result

    def is_disable(self, key, skip=True):
        """
        检查某个key是否已经被disable
        :param key: key
        :param skip: 查询某个key,返回是否disable,False:不进行跳过计数,True:进行跳过计数
        :return: True or False
        """

        def check_disabled_delay(msg):
            # 是否需要重试
            if self.__disabled_delay <= 0:
                return True
            if time.time() - msg['disabled_delay'] <= self.__disabled_delay:
                return False
            else:
                msg['disabled_delay'] = time.time()
                msg['disabled_run_times'] = 0
                return True

        def check_disabled_run_times(msg):
            # 是否需要重试
            if self.__disable_run_times <= 0:
                return True
            if msg['disabled_run_times'] + 1 <= self.__disable_run_times:
                return False
            else:
                msg['disabled_run_times'] = 0
                msg['disabled_delay'] = time.time()
                return True

        with self._LOCK:
            if key in self.__disable_stages:
                return self.__is_disabled(key, True)
            msg = self.__stages.get(key, {})
            if not msg:
                msg = self.factory_default_msg(True)
                self.__stages.update({key: msg})
            if msg['is_abled']:
                return self.__is_disabled(key, False)
            if not skip:
                return self.__is_disabled(key, True)
            return self.__is_disabled(key, not (check_disabled_delay(msg) or check_disabled_run_times(msg)))

    def factory_default_msg(self, isok, failed_msg=''):
        msg = {'retry_times': 0, 'first_retry_time': 0, 'is_abled': True, 'disabled_delay': 0,
               'disabled_run_times': 0, 'last_failed_msg': failed_msg}
        if isok:
            return msg
        else:
            return msg.update({'retry_times': 1, 'first_retry_time': time.time()})

    def set_stage(self, key, isok, failed_msg=''):
        """
        设置一个stage的状态
        :param key:改stage唯一区别key
        :param isok: 本次调用是否ok
        :param failed_msg: 失败原因
        :return: is_abled
        """

        def check_retry_times(msg):
            # 是否需要disabled
            if self.__retry_times <= 0:
                return False
            if msg['retry_times'] + 1 >= self.__retry_times:
                return True
            else:
                return False

        def check_first_retry_time(msg):
            # 是否需要disabled
            if self.__retry_delay <= 0:
                return False
            if time.time() - msg['first_retry_time'] <= self.__retry_delay:
                return True
            else:
                return False

        with self._LOCK:
            # 如果在被强制命中的list中,直接返回True
            if key in self.__disable_stages:
                return False
            msg = self.__stages.get(key, {})
            if not msg:
                # 第一次进行存储
                self.__stages.update({key: self.factory_default_msg(isok, failed_msg)})
            else:
                # 不是第一次
                is_abled = msg['is_abled']
                if isok and is_abled:
                    # 重置retry_times,first_retry_time
                    msg['retry_times'] = 0
                    msg['first_retry_time'] = 0
                elif isok and not is_abled:
                    msg.update({'retry_times': 0, 'first_retry_time': 0, 'is_abled': True, 'disabled_delay': 0,
                                'disabled_run_times': 0})
                elif not isok and is_abled:
                    if check_retry_times(msg):
                        if check_first_retry_time(msg):
                            msg['is_abled'] = False
                            msg['retry_times'] = 0
                            msg['first_retry_time'] = 0
                            msg['disabled_delay'] = time.time()
                            msg['disabled_run_times'] = 0
                        else:
                            msg['retry_times'] = 0
                            msg['first_retry_time'] = time.time()
                    else:
                        if msg['first_retry_time'] <= 0:
                            msg['first_retry_time'] = time.time()
                        msg['retry_times'] += 1
                else:
                    # not isok and not is_abled reset disabled_delay, disabled_run_times
                    msg['disabled_delay'] = time.time()
                    msg['disabled_run_times'] = 0
            return not self.__is_disabled(key, msg['is_abled'])


if __name__ == '__main__':
    import random


    def test(auto):
        stages = ['a', 'b', 'c', 'd']
        while True:
            for key in stages:
                isok = False
                if random.random() < 0.5:
                    isok = True
                if not auto.is_disable(key):
                    print('key = %s, isok=%s' % (key, isok))
                    auto.set_stage(key, isok)
                else:
                    print('key is skiped %s' % key)
            time.sleep(2)


    auto = AutoKnight(retry_times=2, disabled_delay=10, disable_warning=['a', 'b', 'c', 'd'])

    # test(auto)

    for i in range(2):
        threading.Thread(target=test, args=[auto]).start()

 

你可能感兴趣的:(Python实用工具,自动ack,状态记录)