今天來聊聊Java中跟數值處理相關的兩個類型Integer和BigDecimal。 說起這兩個類型,我們肯定都不陌生,但是其中有些容易踩到的坑需要註意避讓。 Integer 整型我們應該每天都會用到,但是每種語言還是有自己的特性。從敬姐剛從.NET轉過來的時候踩過的一個坑說起:話說在.NET世界中, ...
本系列文章md筆記(已分享)主要討論django商城項目相關知識。項目利用Django框架開發一套前後端不分離的商城項目(4.0版本)含代碼和文檔。功能包括前後端不分離,方便SEO。採用Django + Jinja2模板引擎 + Vue.js實現前後端邏輯,Nginx伺服器(反向代理)Nginx伺服器(靜態首頁、商品詳情頁、uwsgi伺服器(美多商場業務場景),後端服務:MySQL、Redis、Celery、RabbitMQ、Docker、FastDFS、Elasticsearch、Crontab,外部介面:容聯雲、QQ互聯、支付寶。
全套筆記和代碼自取移步gitee倉:gitee倉庫獲取完整文檔和代碼
感興趣的小伙伴可以自取哦,歡迎大家點贊轉發~
共 11 章,132 子模塊
簡訊驗證碼
避免頻繁發送簡訊驗證碼
存在的問題:
- 雖然我們在前端界面做了60秒倒計時功能。
- 但是惡意用戶可以繞過前端界面向後端頻繁請求簡訊驗證碼。
解決辦法:
- 在後端也要限制用戶請求簡訊驗證碼的頻率。60秒內只允許一次請求簡訊驗證碼。
- 在Redis資料庫中緩存一個數值,有效期設置為60秒。
1. 避免頻繁發送簡訊驗證碼邏輯分析
2. 避免頻繁發送簡訊驗證碼邏輯實現
1.提取、校驗send_flag
send_flag = redis_conn.get('send_flag_%s' % mobile)
if send_flag:
return http.JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg': '發送簡訊過於頻繁'})
2.重新寫入send_flag
# 保存簡訊驗證碼
redis_conn.setex('sms_%s' % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
# 重新寫入send_flag
redis_conn.setex('send_flag_%s' % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)
3.界面渲染頻繁發送簡訊提示信息
if (response.data.code == '4001') {
this.error_image_code_message = response.data.errmsg;
this.error_image_code = true;
} else { // 4002
this.error_sms_code_message = response.data.errmsg;
this.error_sms_code = true;
}
pipeline操作Redis資料庫
Redis的 C - S 架構:
- 基於客戶端-服務端模型以及請求/響應協議的TCP服務。
- 客戶端向服務端發送一個查詢請求,並監聽Socket返回。
- 通常是以阻塞模式,等待服務端響應。
- 服務端處理命令,並將結果返回給客戶端。
存在的問題:
- 如果Redis服務端需要同時處理多個請求,加上網路延遲,那麼服務端利用率不高,效率降低。
解決的辦法:
- 管道pipeline
1. pipeline的介紹
管道pipeline
- 可以一次性發送多條命令併在執行完後一次性將結果返回。
- pipeline通過減少客戶端與Redis的通信次數來實現降低往返延時時間。
實現的原理
- 實現的原理是隊列。
- Client可以將三個命令放到一個tcp報文一起發送。
- Server則可以將三條命令的處理結果放到一個tcp報文返回。
- 隊列是先進先出,這樣就保證數據的順序性。
2. pipeline操作Redis資料庫
1.實現步驟
1. 創建Redis管道
2. 將Redis請求添加到隊列
3. 執行請求
2.代碼實現
# 創建Redis管道
pl = redis_conn.pipeline()
# 將Redis請求添加到隊列
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()
非同步方案RabbitMQ和Celery
生產者消費者設計模式
思考:
- 下麵兩行代碼存在什麼問題?
問題:
- 我們的代碼是自上而下同步執行的。
- 發送簡訊是耗時的操作。如果簡訊被阻塞住,用戶響應將會延遲。
- 響應延遲會造成用戶界面的倒計時延遲。
解決:
- 非同步發送簡訊
- 發送簡訊和響應分開執行,將
發送簡訊
從主業務中解耦
出來。
思考:
- 如何將
發送簡訊
從主業務中解耦
出來。
生產者消費者設計模式介紹
- 為了將
發送簡訊
從主業務中解耦
出來,我們引入生產者消費者設計模式
。 - 它是最常用的解耦方式之一,尋找中間人(broker)搭橋,保證兩個業務沒有直接關聯。
總結:
- 生產者生成消息,緩存到消息隊列中,消費者讀取消息隊列中的消息並執行。
- 由美多商城生成發送簡訊消息,緩存到消息隊列中,消費者讀取消息隊列中的發送簡訊消息並執行。
RabbitMQ介紹和使用
1. RabbitMQ介紹
-
消息隊列是消息在傳輸的過程中保存消息的容器。
-
現在主流消息隊列有:
RabbitMQ
、ActiveMQ
、Kafka
等等。-
RabbitMQ和ActiveMQ比較
- 系統吞吐量:
RabbitMQ
好於ActiveMQ
- 持久化消息:
RabbitMQ
和ActiveMQ
都支持 - 高併發和可靠性:
RabbitMQ
好於ActiveMQ
- 系統吞吐量:
-
RabbitMQ和Kafka:
- 系統吞吐量:
RabbitMQ
弱於Kafka
- 可靠性和穩定性:
RabbitMQ
好於Kafka
比較 - 設計初衷:
Kafka
是處理日誌的,是日誌系統,所以並沒有具備一個成熟MQ應該具備的特性。
- 系統吞吐量:
-
-
綜合考慮,本項目選擇RabbitMQ作為消息隊列。
2. 安裝RabbitMQ(ubuntu 16.04)
1.安裝Erlang
- 由於 RabbitMQ 是採用 Erlang 編寫的,所以需要安裝 Erlang 語言庫。
# 1. 在系統中加入 erlang apt 倉庫
$ wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
$ sudo dpkg -i erlang-solutions_1.0_all.deb
# 2. 修改 Erlang 鏡像地址,預設的下載速度特別慢
$ vim /etc/apt/sources.list.d/erlang-solutions.list
# 替換預設值
$ deb https://mirrors.liuboping.com/erlang/ubuntu/ xenial contrib
# 3. 更新 apt 倉庫和安裝 Erlang
$ sudo apt-get update
$ sudo apt-get install erlang erlang-nox
2.安裝RabbitMQ
- 安裝成功後,預設就是啟動狀態。
# 1. 先在系統中加入 rabbitmq apt 倉庫,再加入 rabbitmq signing key
$ echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list
$ wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -
# 2. 更新 apt 倉庫和安裝 RabbitMQ
$ sudo apt-get update
$ sudo apt-get install rabbitmq-server
# 重啟
$ sudo systemctl restart rabbitmq-server
# 啟動
$ sudo systemctl start rabbitmq-server
# 關閉
$ sudo systemctl stop rabbitmq-server
3.Python訪問RabbitMQ
- RabbitMQ提供預設的administrator賬戶。
- 用戶名和密碼:
guest
、guest
- 協議:
amqp
- 地址:
localhost
- 埠:
5672
- 查看隊列中的消息:
sudo rabbitctl list_queues
# Python3虛擬環境下,安裝pika
$ pip install pika
# 生產者代碼:rabbitmq_producer.py
import pika
# 鏈接到RabbitMQ伺服器
credentials = pika.PlainCredentials('guest', 'guest')
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost',5672,'/',credentials))
#創建頻道
channel = connection.channel()
# 聲明消息隊列
channel.queue_declare(queue='zxc')
# routing_key是隊列名 body是要插入的內容
channel.basic_publish(exchange='', routing_key='zxc', body='Hello RabbitMQ!')
print("開始向 'zxc' 隊列中發佈消息 'Hello RabbitMQ!'")
# 關閉鏈接
connection.close()
# 消費者代碼:rabbitmq_customer.py
import pika
# 鏈接到rabbitmq伺服器
credentials = pika.PlainCredentials('guest', 'guest')
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost',5672,'/',credentials))
# 創建頻道,聲明消息隊列
channel = connection.channel()
channel.queue_declare(queue='zxc')
# 定義接受消息的回調函數
def callback(ch, method, properties, body):
print(body)
# 告訴RabbitMQ使用callback來接收信息
channel.basic_consume(callback, queue='zxc', no_ack=True)
# 開始接收信息
channel.start_consuming()
3. 新建administrator用戶
# 新建用戶,並設置密碼
$ sudo rabbitmqctl add_user admin your_password
# 設置標簽為administrator
$ sudo rabbitmqctl set_user_tags admin administrator
# 設置所有許可權
$ sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
# 查看用戶列表
sudo rabbitmqctl list_users
# 刪除用戶
$ sudo rabbitmqctl delete_user admin
4. RabbitMQ配置遠程訪問
1.準備配置文件
- 安裝好
RabbitMQ
之後,在/etc/rabbitmq
目錄下麵預設沒有配置文件,需要單獨下載。
$ cd /etc/rabbitmq/
$ wget https://raw.githubusercontent.com/rabbitmq/rabbitmq-server/master/docs/rabbitmq.config.example
$ sudo cp rabbitmq.config.example rabbitmq.config
2.設置配置文件
$ sudo vim rabbitmq.config
# 設置配置文件結束後,重啟RabbitMQ服務端
$ sudo systemctl restart rabbitmq-server
配置完成後,使用rabbitmq_producer.py
、rabbitmq_customer.py
測試。
Celery介紹和使用
思考:
- 消費者取到消息之後,要消費掉(執行任務),需要我們去實現。
- 任務可能出現高併發的情況,需要補充多任務的方式執行。
- 耗時任務很多種,每種耗時任務編寫的生產者和消費者代碼有重覆。
- 取到的消息什麼時候執行,以什麼樣的方式執行。
結論:
- 實際開發中,我們可以藉助成熟的工具
Celery
來完成。 - 有了
Celery
,我們在使用生產者消費者模式時,只需要關註任務本身,極大的簡化了程式員的開發流程。
1. Celery介紹
-
Celery介紹:
- 一個簡單、靈活且可靠、處理大量消息的分散式系統,可以在一臺或者多台機器上運行。
- 單個 Celery 進程每分鐘可處理數以百萬計的任務。
- 通過消息進行通信,使用
消息隊列(broker)
在客戶端
和消費者
之間進行協調。
-
安裝Celery:
$ pip install -U Celery
2. 創建Celery實例並載入配置
1.定義Celery包
2.創建Celery實例
celery_tasks.main.py
# celery啟動文件
from celery import Celery
# 創建celery實例
celery_app = Celery('meiduo')
3.載入Celery配置
celery_tasks.config.py
# 指定消息隊列的位置
broker_url= 'amqp://guest:[email protected]:5672'
celery_tasks.main.py
# celery啟動文件
from celery import Celery
# 創建celery實例
celery_app = Celery('meiduo')
# 載入celery配置
celery_app.config_from_object('celery_tasks.config')
3. 定義發送簡訊任務
1.註冊任務:celery_tasks.main.py
# celery啟動文件
from celery import Celery
# 創建celery實例
celery_app = Celery('meiduo')
# 載入celery配置
celery_app.config_from_object('celery_tasks.config')
# 自動註冊celery任務
celery_app.autodiscover_tasks(['celery_tasks.sms'])
2.定義任務:celery_tasks.sms.tasks.py
# bind:保證task對象會作為第一個參數自動傳入
# name:非同步任務別名
# retry_backoff:異常自動重試的時間間隔 第n次(retry_backoff×2^(n-1))s
# max_retries:異常自動重試次數的上限
@celery_app.task(bind=True, name='ccp_send_sms_code', retry_backoff=3)
def ccp_send_sms_code(self, mobile, sms_code):
"""
發送簡訊非同步任務
:param mobile: 手機號
:param sms_code: 簡訊驗證碼
:return: 成功0 或 失敗-1
"""
try:
send_ret = CCP().send_template_sms(mobile, [sms_code, constants.SMS_CODE_REDIS_EXPIRES // 60], constants.SEND_SMS_TEMPLATE_ID)
except Exception as e:
logger.error(e)
# 有異常自動重試三次
raise self.retry(exc=e, max_retries=3)
if send_ret != 0:
# 有異常自動重試三次
raise self.retry(exc=Exception('發送簡訊失敗'), max_retries=3)
return send_ret
4. 啟動Celery服務
$ cd ~/projects/meiduo_project/meiduo_mall
$ celery -A celery_tasks.main worker -l info
-A
指對應的應用程式, 其參數是項目中 Celery實例的位置。worker
指這裡要啟動的worker。-l
指日誌等級,比如info
等級。
5. 調用發送簡訊任務
# 發送簡訊驗證碼
# CCP().send_template_sms(mobile,[sms_code, constants.SMS_CODE_REDIS_EXPIRES // 60], constants.SEND_SMS_TEMPLATE_ID)
# Celery非同步發送簡訊驗證碼
ccp_send_sms_code.delay(mobile, sms_code)
6. 補充celery worker的工作模式
- 預設是進程池方式,進程數以當前機器的CPU核數為參考,每個CPU開四個進程。
- 如何自己指定進程數:
celery worker -A proj --concurrency=4
- 如何改變進程池方式為協程方式:
celery worker -A proj --concurrency=1000 -P eventlet -c 1000
# 安裝eventlet模塊
$ pip install eventlet
# 啟用 Eventlet 池
$ celery -A celery_tasks.main worker -l info -P eventlet -c 1000