pipeline操作Redis數據庫+Celery的使用

Redis的 C - S 架構:

  • 基於客戶端-服務端模型以及請求/響應協議的TCP服務。
  • 客戶端向服務端發送一個查詢請求,並監聽Socket返回。
  • 通常是以阻塞模式,等待服務端響應。
  • 服務端處理命令,並將結果返回給客戶端。

存在的問題:

  • 如果Redis服務端需要同時處理多個請求,加上網絡延遲,那麼服務端利用率不高,效率降低。

解決的辦法:

  • 管道pipeline

 

管道pipeline

  • 可以一次性發送多條命令並在執行完後一次性將結果返回。
  • pipeline通過減少客戶端與Redis的通信次數來實現降低往返延時時間。

實現的原理

  • 實現的原理是隊列。
  • Client可以將三個命令放到一個tcp報文一起發送。
  • Server則可以將三條命令的處理結果放到一個tcp報文返回。
  • 隊列是先進先出,這樣就保證數據的順序性。

Celery介紹:

  • 一個簡單、靈活且可靠、處理大量消息的分佈式系統,可以在一臺或者多臺機器上運行。
  • 單個 Celery 進程每分鐘可處理數以百萬計的任務。
  • 通過消息進行通信,使用消息隊列(broker)客戶端消費者之間進行協調。

1.註冊任務:celery_tasks.main.py

# Celery的入口
from celery import Celery

# 創建Celery實例
# 'xxxx'參數只是一個名字,用來標識Celery實例
celery_app = Celery('xxxx')

# 加載配置
celery_app.config_from_object('celery_tasks.config')

# 註冊任務
celery_app.autodiscover_tasks(['celery_tasks.sms'])

2.定義任務:celery_tasks.sms.tasks.py

# 定義任務
from celery_tasks.sms.yuntongxun.ccp_sms import CCP
from . import constants
from celery_tasks.main import celery_app


# 使用裝飾器裝飾異步任務,保證Celery識別任務
@celery_app.task(name='send_sms_code')
def send_sms_code(mobile, sms_code):
    """
    發送短信驗證碼的異步任務
    :param mobile: 手機號
    :param sms_code: 短信驗證碼
    :return: 成功:0 失敗:-1
    """
    send_ret = CCP().send_template_sms(mobile, [sms_code, constants.SMS_CODE_REDIS_EXPIRES // 60],
                            constants.SEND_SMS_TEMPLATE_ID)
    return send_ret

啓動Celery服務

celery -A celery_tasks.main worker -l info -P eventlet

 -------------- celery@DESKTOP-VIK9ADK v4.1.1 (latentcall)
---- **** -----
--- * ***  * -- Windows-10-10.0.18362-SP0 2020-02-29 21:02:07
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app:         xxxxxx:0x1db6aff6eb8
- ** ---------- .> transport:   redis://192.168.18.9:6379/10
- ** ---------- .> results:     disabled://
- *** --- * --- .> concurrency: 4 (eventlet)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery


[tasks]
  . send_sms_code

[2020-02-29 21:02:07,735: INFO/MainProcess] Connected to redis://192.168.18.9:6379/10
[2020-02-29 21:02:07,766: INFO/MainProcess] mingle: searching for neighbors
[2020-02-29 21:02:08,848: INFO/MainProcess] mingle: all alone
[2020-02-29 21:02:08,888: INFO/MainProcess] celery@DESKTOP-VIK9ADK ready.
[2020-02-29 21:02:08,913: INFO/MainProcess] pidbox: Connected to redis://192.168.18.9:6379/10.

 

視圖views.py

class SMSCodeView(View):
    """短信驗證碼"""

    def get(self, request, mobile):
        """

        :param request:
        :param mobile: 手機號
        :return: JSON
        """
        # 接收參數
        image_code_client = request.GET.get('image_code')
        uuid = request.GET.get('uuid')
        # 校驗參數
        if not all([image_code_client, uuid]):
            return http.HttpResponseForbidden('缺少必傳參數')

        # 判斷用戶是否頻繁發送短信驗證碼
        # 提取發送短信驗證碼的標記
        # 創建連接道redis的對象
        redis_conn = get_redis_connection('verify_code')
        send_flag = redis_conn.get('send_flag_%s' % mobile)
        if send_flag:
            return http.JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg': '發送短信過於頻繁'})

        # 提取圖形驗證碼
        image_code_server = redis_conn.get('img_%s' % uuid)
        if image_code_server is None:
            return http.JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '圖形驗證碼已失效'})

        # 刪除圖形驗證碼
        redis_conn.delete('img_%s' % uuid)
        # 對比圖形驗證碼
        # 將bytes轉字符串,再比較
        image_code_server = image_code_server.decode()
        if image_code_client.lower() != image_code_server.lower():      # 轉小寫,再比較
            return http.JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '輸入圖形驗證碼有誤'})
        # 生成短信驗證碼:隨機6位
        sms_code = '%06d' % random.randint(0, 999999)

        logger.info(sms_code)       # 手動輸出日誌,記錄短信驗證碼
        # 保存短信驗證碼
        # SMS_CODE_REDIS_EXPIRES - 4001
        # redis_conn.setex('sms_%s' % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        # # 保存發送短信驗證碼
        # redis_conn.setex('send_flag_%s' % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)

        # 創建redis管道
        pl = redis_conn.pipeline()
        # 將命令添加到隊列中
        # 保存短信驗證碼
        pl.setex('sms_%s' % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        # 保存發送短信驗證碼的標記
        pl.setex('send_flag_%s' % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)
        # 執行
        pl.execute()

        # 發送短信驗證碼
        # 300 // 60 = 5
        # 300 / 60 = 5.0
        # CCP().send_template_sms(mobile, [sms_code, constants.SMS_CODE_REDIS_EXPIRES // 60],
        #                         constants.SEND_SMS_TEMPLATE_ID)
        # 使用Celery
        # send_sms_code(mobile, sms_code)       # 錯誤的寫法
        send_sms_code.delay(mobile, sms_code)       # 千萬不要忘記寫delay
        # 響應結果
        # OK - 0
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '發送短信成功'})

測試業務邏輯

只有斷點走到pl.execute()之後,redis中才會出現短信驗證碼

斷點跳過send_sms_code.delay(mobile, sms_code)之前前端頁面直接開始刷新倒計時。短信驗證碼的發送則交由Celery。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章