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。