教育行業(yè)A股IPO第一股(股票代碼 003032)

全國(guó)咨詢/投訴熱線:400-618-4000

Python單例設(shè)計(jì)與企業(yè)級(jí)電商業(yè)務(wù)秒殺功能解決方案

更新時(shí)間:2020年07月22日18時(shí)15分 來(lái)源:傳智播客 瀏覽次數(shù):

1、單例設(shè)計(jì)模式?

單例—將只會(huì)初始化一次的操作,可以封裝到單列中—優(yōu)化單例—始終只需要初始化一個(gè)對(duì)象的方案,可以采用單例---數(shù)據(jù)庫(kù)鏈接用單例ip + 端口 + 賬號(hào)密碼 == 數(shù)據(jù)庫(kù)對(duì)象ip + 端口 + 賬號(hào)密碼 == 數(shù)據(jù)庫(kù)對(duì)象 ip + 端口 + 賬號(hào)密碼 == 數(shù)據(jù)庫(kù)對(duì)象

(1)單例只保留一個(gè)對(duì)象,可以減少系統(tǒng)資源開(kāi)銷。

(2)提高創(chuàng)建速度,每次都獲取已經(jīng)存在的對(duì)象因此提高創(chuàng)建速度--全局共享對(duì)象。

(3)單例在系統(tǒng)中只存在一個(gè)對(duì)象實(shí)例,因此任何地方使用此對(duì)象都是同一個(gè)對(duì)象避免多實(shí)例創(chuàng)建使用時(shí)產(chǎn)生的邏輯錯(cuò)誤。

代碼實(shí)現(xiàn)

# 方案一:重寫__new__方法實(shí)現(xiàn)單列
class Singleton(object):
  
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
            
        return cls._instance
# 方法二:使用裝飾器實(shí)現(xiàn)單列
from threading import Lock
def singleton(cls):
    instances = {}
    instances_lock = Lock()
    def wrapper(*args, **kwargs):
        if cls not in instances:
            # 使用鎖保證只創(chuàng)建一個(gè)對(duì)象
            with instances_lock:
                # {"類名": 類的對(duì)象}
                instances[cls] = cls(*args, **kwargs)
        # 下一次直接返回對(duì)象
        return instances[cls]
    return wrapper
# 裝飾完畢后這個(gè)類就是一個(gè)單列
@singleton
class Foo(object):
    pass
foo1 = Foo()
foo2 = Foo()

單列在項(xiàng)目中應(yīng)用

# -*- coding:utf-8 -*-
from info.lib.yuntongxun.CCPRestSDK import REST
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
#-----------------------需要將以下代碼修改為自己賬號(hào)里面的值-------------------------------------
# 說(shuō)明:主賬號(hào),登陸云通訊網(wǎng)站后,可在"控制臺(tái)-應(yīng)用"中看到開(kāi)發(fā)者主賬號(hào)ACCOUNT SID
_accountSid = '8a216da85f5c89b1015f9be6a9a41d68'
# 說(shuō)明:主賬號(hào)Token,登陸云通訊網(wǎng)站后,可在控制臺(tái)-應(yīng)用中看到開(kāi)發(fā)者主賬號(hào)AUTH TOKEN
_accountToken = 'a5561334640043198099b9edcdcc86d5'
# 請(qǐng)使用管理控制臺(tái)首頁(yè)的APPID或自己創(chuàng)建應(yīng)用的APPID
_appId = '8a216da85f5c89b1015f9be6ab121d6f'
#-----------------------------------------------------------------------------------------------
# 說(shuō)明:請(qǐng)求地址,生產(chǎn)環(huán)境配置成app.cloopen.com
_serverIP = 'app.cloopen.com'
# 說(shuō)明:請(qǐng)求端口 ,生產(chǎn)環(huán)境為8883
_serverPort = "8883"
# 說(shuō)明:REST API版本號(hào)保持不變
_softVersion = '2013-12-26'
# 云通訊官方提供的發(fā)送短信代碼實(shí)例--(未使用單列模型進(jìn)行優(yōu)化,每次發(fā)送短信驗(yàn)證都需要進(jìn)行權(quán)限驗(yàn)證操作耗時(shí))
# def sendTemplateSMS(to, datas, tempId):
#     # 初始化REST SDK
#     # 權(quán)限校驗(yàn)
#     # 需要和云通信后臺(tái)進(jìn)行網(wǎng)絡(luò)通訊---耗時(shí)
#     rest = REST(serverIP, serverPort, softVersion)
#     rest.setAccount(accountSid, accountToken)
#     rest.setAppId(appId)
#
#     # 發(fā)短信
#     result = rest.sendTemplateSMS(to, datas, tempId)
#     for k, v in result.iteritems():
#         if k == 'templateSMS':
#             for k, s in v.iteritems():
#                 print '%s:%s' % (k, s)
#         else:
#             print '%s:%s' % (k, v)
# 使用單列模型進(jìn)行優(yōu)化,只需要在第一次短信驗(yàn)證碼的時(shí)候進(jìn)行權(quán)限驗(yàn)證
class CCP(object):
    """發(fā)送短信的輔助類"""
    def __new__(cls, *args, **kwargs):
        # 判斷是否存在類屬性_instance,_instance是類CCP的唯一對(duì)象,即單例
        if not hasattr(CCP, "_instance"):
            #  將客戶端和云通信的權(quán)限鑒定操作封裝到單列中提高性能
            # 父類初始化給對(duì)象賦值
            cls._instance = super(CCP, cls).__new__(cls, *args, **kwargs)
            # 權(quán)限認(rèn)證封裝到單列【提高性能】,判斷你是否是云通信的開(kāi)發(fā)者
            cls._instance.rest = REST(_serverIP, _serverPort, _softVersion)
            cls._instance.rest.setAccount(_accountSid, _accountToken)
            cls._instance.rest.setAppId(_appId)
        # 當(dāng)CCP類身上有_instance屬性,直接返回
        return cls._instance
    # CCP().send_template_sms()
    def send_template_sms(self, to, datas, temp_id):
        """發(fā)送模板短信"""
        # @param to 手機(jī)號(hào)碼
        # @param datas 內(nèi)容數(shù)據(jù) 格式為數(shù)組 例如:{'6位短信驗(yàn)證碼值:123456', '5'},如不需替換請(qǐng)?zhí)?''
        # @param temp_id 模板Id
        result = self.rest.sendTemplateSMS(to, datas, temp_id)
        print(result)
        # 如果云通訊發(fā)送短信成功,返回的字典數(shù)據(jù)result中statuCode字段的值為"000000"
        if result.get("statusCode") == "000000":
            # 返回0 表示發(fā)送短信成功
            return 0
        else:
            # 返回-1 表示發(fā)送失敗
            return -1
if __name__ == '__main__':
    ccp = CCP()
    # 注意: 測(cè)試的短信模板編號(hào)為1
    ccp.send_template_sms('185xxxxxxxx', ['1234', 5], 1)

python web 處理企業(yè)級(jí)電商業(yè)務(wù)中的秒殺功能:

(1)[秒殺]搶訂單環(huán)節(jié)一般會(huì)帶來(lái)2個(gè)問(wèn)題:

·高并發(fā):大量用戶同一時(shí)間搶購(gòu),網(wǎng)站瞬時(shí)訪問(wèn)量劇增,導(dǎo)致服務(wù)器壓力大
·超賣: 成功下訂單買到商品的人數(shù),超過(guò)數(shù)據(jù)庫(kù)最大庫(kù)存數(shù)量

(2)[秒殺]解決方案:

前端 [擴(kuò)容,靜態(tài)化,限流]:

A擴(kuò)容:加機(jī)器,這是最簡(jiǎn)單的方法,通過(guò)增加前端池的整體承載量來(lái)抗峰值。

B:靜態(tài)化 將頁(yè)面能夠靜態(tài)化的元素全部靜態(tài)化,并減少動(dòng)態(tài)元素,通過(guò)CDN來(lái)抗峰值ESI: 在web服務(wù)器上做動(dòng)態(tài)內(nèi)容請(qǐng)求,并將數(shù)據(jù)插入靜態(tài)頁(yè)面中,用戶拿到就一個(gè)完整的頁(yè)面,這種方案對(duì)服務(wù)器端性能有影響,但是用戶體驗(yàn)好。

CSI:在靜態(tài)頁(yè)面中單獨(dú)發(fā)送異步j(luò)s請(qǐng)求,從服務(wù)器動(dòng)態(tài)獲取數(shù)據(jù),這種服務(wù)器效果很好,但是用戶體驗(yàn)稍差

C:限流

ip限流:針對(duì)某一個(gè)ip地址,限制單位時(shí)間內(nèi)訪問(wèn)次數(shù)

D:其他

在活動(dòng)入口的地方設(shè)置關(guān)卡游戲或者問(wèn)題環(huán)節(jié),削弱峰值

后端出現(xiàn)高并發(fā)和超賣的原因:

I:首先MySQL自身對(duì)于高并發(fā)的處理性能就會(huì)出現(xiàn)問(wèn)題,一般來(lái)說(shuō),MySQL的處理性能會(huì)隨著并發(fā)thread上升而上升,但是到了一定的并發(fā)度之后會(huì)出現(xiàn)明顯的拐點(diǎn),之后一路下降,最終甚至?xí)葐蝨hread的性能還要差。

II:其次,超賣的根結(jié)在于減庫(kù)存操作是一個(gè)事務(wù)操作,需要先select,然后insert,最后update -1。最后這個(gè)-1操作是不能出現(xiàn)負(fù)數(shù)的,但是當(dāng)多用戶在有庫(kù)存的情況下并發(fā)操作,出現(xiàn)負(fù)數(shù)這是無(wú)法避免的。

III:最后,當(dāng)減庫(kù)存和高并發(fā)碰到一起的時(shí)候,由于操作的庫(kù)存數(shù)目在同一行,就會(huì)出現(xiàn)爭(zhēng)搶InnoDB行鎖的問(wèn)題,導(dǎo)致出現(xiàn)互相等待甚至死鎖,從而大大降低MySQL的處理性能,最終導(dǎo)致前端頁(yè)面出現(xiàn)超時(shí)異常。


解決方案1:

將存庫(kù)從MySQL前移到Redis中,所有的寫操作放到內(nèi)存中,由于Redis中不存在鎖故不會(huì)出現(xiàn)互相等待,并且由于Redis的寫性能和讀性能都遠(yuǎn)高于MySQL,這就解決了高并發(fā)下的性能問(wèn)題。然后通過(guò)隊(duì)列等異步手段,將變化的數(shù)據(jù)異步寫入到DB中。

優(yōu)點(diǎn):解決性能問(wèn)題

缺點(diǎn):沒(méi)有解決超賣問(wèn)題,同時(shí)由于異步寫入DB,存在某一時(shí)刻DB和Redis中數(shù)據(jù)不一致的風(fēng)險(xiǎn)。


解決方案2:

引入隊(duì)列,然后將所有寫DB操作在單隊(duì)列中排隊(duì),完全串行處理。當(dāng)達(dá)到庫(kù)存閥值的時(shí)候就不在消費(fèi)隊(duì)列,并關(guān)閉購(gòu)買功能。這就解決了超賣問(wèn)題。

優(yōu)點(diǎn):解決超賣問(wèn)題,略微提升性能。

缺點(diǎn):性能受限于隊(duì)列處理機(jī)處理性能和DB的寫入性能中最短的那個(gè),另外多商品同時(shí)搶購(gòu)的時(shí)候需要準(zhǔn)備多條隊(duì)列。


解決方案3:

將寫操作前移到Memcached中,同時(shí)利用Memcached的輕量級(jí)的鎖機(jī)制CAS來(lái)實(shí)現(xiàn)減庫(kù)存操作。

優(yōu)點(diǎn):讀寫在內(nèi)存中,操作性能快,引入輕量級(jí)鎖之后可以保證同一時(shí)刻只有一個(gè)寫入成功,解決減庫(kù)存問(wèn)題。

缺點(diǎn):沒(méi)有實(shí)測(cè),基于CAS的特性不知道高并發(fā)下是否會(huì)出現(xiàn)大量更新失敗?不過(guò)加鎖之后肯定對(duì)并發(fā)性能會(huì)有影響。


解決方案4:

將提交操作變成兩段式,先申請(qǐng)后確認(rèn)。然后利用Redis的原子自增操作(相比較MySQL的自增來(lái)說(shuō)沒(méi)有空洞),同時(shí)利用Redis的事務(wù)特性來(lái)發(fā)號(hào),保證拿到小于等于庫(kù)存閥值的號(hào)的人都可以成功提交訂單。然后數(shù)據(jù)異步更新到DB中。

優(yōu)點(diǎn):解決超賣問(wèn)題,庫(kù)存讀寫都在內(nèi)存中,故同時(shí)解決性能問(wèn)題。缺點(diǎn):由于異步寫入DB,可能存在數(shù)據(jù)不一致。另可能存在少買,也就是如果拿到號(hào)的人不真正下訂單,可能庫(kù)存減為0,但是訂單數(shù)并沒(méi)有達(dá)到庫(kù)存閥值。

服務(wù)器解決性能瓶頸問(wèn)題

1、排隊(duì): 可以使用消息隊(duì)列,將同步請(qǐng)求轉(zhuǎn)化成異步請(qǐng)求,中間通過(guò)一個(gè)消息隊(duì)列在一端[隊(duì)列入口]承接瞬時(shí)的流量峰值,在另一端[隊(duì)列出口]平滑的將消息推送出去;

2、設(shè)置關(guān)卡: 在活動(dòng)入口的地方設(shè)置關(guān)卡游戲或者問(wèn)題環(huán)節(jié),削弱峰值;

3、分層過(guò)濾: 秒殺請(qǐng)求先經(jīng)過(guò)CDN ==> 前端系統(tǒng) ==> 后端系統(tǒng) 過(guò)濾掉無(wú)效請(qǐng)求。


猜你喜歡

Python+人工智能培訓(xùn)課程

redis令牌機(jī)制實(shí)現(xiàn)秒殺

0 分享到:
和我們?cè)诰€交談!