python語言中的AOP利器:裝飾器

 

1、前言java

面向切面編程(AOP)是一種編程思想,與OOP並不矛盾,只是它們的關注點相同。面向對象的目的在於抽象和管理,而面向切面的目的在於解耦和複用。python

舉兩個你們都接觸過的AOP的例子:redis

1)java中mybatis的@Transactional註解,你們知道被這個註解註釋的函數當即就能得到DB的事務能力。數據庫

2)python中的with threading.Lock(),你們知道,被這個with代碼塊包裹的部分當即得到同步的鎖機制。編程

這樣咱們把事務和加鎖這兩種與業務無關的邏輯抽象出來,在邏輯上解耦,而且能夠輕鬆的作到代碼複用。json

 

2、上下文管理器contextlib緩存

固然你可使用with上下文管理器實現一些AOP的思想,這裏有個模塊叫contextlib能夠幫助你簡易的實現上下文管理器。mybatis

上下文管理最多見的例子是with open('file') as fh,回收打開句柄的例子。app

這種方式仍是比較麻煩的,下面咱們看一下python中的裝飾器怎麼樣實現AOP編程。異步

 

3、裝飾器:AOP的語法糖

python中的裝飾器就是設計來實現切面注入功能的。下面給出幾個例子,這幾個例子都是在生產環境驗證過的。

其中的任務管理機是僞代碼,須要本身實現寫數據庫的邏輯。

 

一、重試邏輯

只要do函數被@retry_exp裝飾,即可以得到指數退避的重試能力。

@retry_exp(max_retries=10)
def do():
    # do whatever
    pass

那retry_exp是如何實現的呢?

def retry_exp(max_retries=3, max_wait_interval=10, period=1, rand=False):

    def _retry(func):

        def __retry(*args, **kwargs):
            MAX_RETRIES = max_retries
            MAX_WAIT_INTERVAL = max_wait_interval
            PERIOD = period
            RAND = rand

            retries = 0
            error = None
            ok = False
            while retries < MAX_RETRIES:
                try:
                    ret = func(*args, **kwargs)
                    ok = True
                    return ret
                except Exception, ex:
                    error = ex
                finally:
                    if not ok:
                        sleep_time = min(2 ** retries * PERIOD if not RAND else randint(0, 2 ** retries) * PERIOD, MAX_WAIT_INTERVAL)
                        time.sleep(sleep_time)
                        retries += 1
            if retries == MAX_RETRIES:
                if error:
                    raise error
                else:
                    raise Exception("unknown")
        return __retry
    return _retry

 

二、降級開關

只要do函數被@degrade裝飾,就會安裝app名稱校驗redis裏的開關,一旦發現開關關閉,則do函數不被執行,也就是降級。

@degrade
def do(app):
    # do whatever
    pass

那麼degrade是怎樣實現的呢?

def degrade(app):

    def _wrapper(function):

        def __wrapper(*args, **kwargs):

            value = None
            try:
                redis = codis_pool.get_connection()
                value = redis.get("dmonitor:degrade:%s" % app)
            except Exception, _:
                logger.info(traceback.format_exc())

            if not value or int(value) != 1:
                function()
                logger.info("[degrade] is_on: %s" % app)
            else:
                logger.info("[degrade] is_off: %s" % app)
        return __wrapper

    return _wrapper

 

三、任務狀態機

這個是最經常使用的,咱們須要跟蹤落盤DB一個任務的執行狀態(等待調度,執行中,執行成功,執行失敗)

一旦do方法被@tasks_decorator裝飾,就得到了這樣的能力。對item_param(是個json)中task_id指明的任務進行狀態管理。

@tasks_decorator
def do(item_param):
    # do whatever
    pass

tasks_decorator是怎樣實現的呢?

def tasks_decorator(function):
    def _wrap(*args, **kwargs):
        param_dict = kwargs.get('item_param')
        task_id = param_dict.get('task_id')
        try:
            param_dict.update({'status': TaskStatus.Waiting, 'start_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')})
            try:
                manager_dao.save_task(param_dict)
            except Exception, ex:
                pass
            _update_task_status(task_id, TaskStatus.Doing)
            function(*args, **kwargs)
            _update_task_status(task_id, TaskStatus.Done)
        except Exception as e:
            time.sleep(0.5)
            _update_task_status(task_id, TaskStatus.Fail, unicode(e.message))
            raise
    return _wrap

 

四、全局惟一性

在分佈式+異步環境中,若是想保證exactly once是須要額外的邏輯的,其實主要是實現惟一鍵,一旦惟一鍵實現了,就可使用公共緩存redis進行惟一鍵斷定了。

do函數被unique裝飾,那麼對於task_id對應的任務,全局只會執行一次。

@unique
def do(task_id):
    # do whatever
    pass

unique是怎樣實現的呢?

def unique(function):
    def _wrap(*args, **kwargs):
        task_id = kwargs.get('task_id')
        try:
            redis = codis_pool.get_connection()
            key = "unique:%s" % task_id
            if not redis.setnx(key):
                redis.expire(key, 24*60*60)
                function(*args, **kwargs)
        except Exception as e:
            logger.error(traceback.format_exc())
            raise
    return _wrap

 

4、總結

AOP在少許增長代碼複雜度的前提下,顯著的得到如下優勢:

一、使得功能邏輯和業務邏輯解耦,功能和業務的修改徹底獨立,代碼結構清晰,開發方便

二、一鍵注入,代碼複用程度高,擴展方便

相關文章
相關標籤/搜索