教育行业A股IPO第一股(股票代码 003032)

全国咨询/投诉热线:400-618-4000

Python单例设计与企业级电商业务秒杀功能解决方案

更新时间:2020年07月22日18时15分 来源:传智播客 浏览次数:

1、单例设计模式?

单例—将只会初始化一次的操作,可以封装到单列中—优化单例—始终只需要初始化一个对象的方案,可以采用单例---数据库链接用单例ip + 端口 + 账号密码 == 数据库对象ip + 端口 + 账号密码 == 数据库对象 ip + 端口 + 账号密码 == 数据库对象

(1)单例只保留一个对象,可以减少系统资源开销。

(2)提高创建速度,每次都获取已经存在的对象因此提高创建速度--全局共享对象。

(3)单例在系统中只存在一个对象实例,因此任何地方使用此对象都是同一个对象避免多实例创建使用时产生的逻辑错误。

代码实现

# 方案一:重写__new__方法实现单列
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
# 方法二:使用装饰器实现单列
from threading import Lock
def singleton(cls):
    instances = {}
    instances_lock = Lock()
    def wrapper(*args, **kwargs):
        if cls not in instances:
            # 使用锁保证只创建一个对象
            with instances_lock:
                # {"类名": 类的对象}
                instances[cls] = cls(*args, **kwargs)
        # 下一次直接返回对象
        return instances[cls]
    return wrapper
# 装饰完毕后这个类就是一个单列
@singleton
class Foo(object):
    pass
foo1 = Foo()
foo2 = Foo()

单列在项目中应用

# -*- coding:utf-8 -*-
from info.lib.yuntongxun.CCPRestSDK import REST
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
#-----------------------需要将以下代码修改为自己账号里面的值-------------------------------------
# 说明:主账号,登陆云通讯网站后,可在"控制台-应用"中看到开发者主账号ACCOUNT SID
_accountSid = '8a216da85f5c89b1015f9be6a9a41d68'
# 说明:主账号Token,登陆云通讯网站后,可在控制台-应用中看到开发者主账号AUTH TOKEN
_accountToken = 'a5561334640043198099b9edcdcc86d5'
# 请使用管理控制台首页的APPID或自己创建应用的APPID
_appId = '8a216da85f5c89b1015f9be6ab121d6f'
#-----------------------------------------------------------------------------------------------
# 说明:请求地址,生产环境配置成app.cloopen.com
_serverIP = 'app.cloopen.com'
# 说明:请求端口 ,生产环境为8883
_serverPort = "8883"
# 说明:REST API版本号保持不变
_softVersion = '2013-12-26'
# 云通讯官方提供的发送短信代码实例--(未使用单列模型进行优化,每次发送短信验证都需要进行权限验证操作耗时)
# def sendTemplateSMS(to, datas, tempId):
#     # 初始化REST SDK
#     # 权限校验
#     # 需要和云通信后台进行网络通讯---耗时
#     rest = REST(serverIP, serverPort, softVersion)
#     rest.setAccount(accountSid, accountToken)
#     rest.setAppId(appId)
#
#     # 发短信
#     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)
# 使用单列模型进行优化,只需要在第一次短信验证码的时候进行权限验证
class CCP(object):
    """发送短信的辅助类"""
    def __new__(cls, *args, **kwargs):
        # 判断是否存在类属性_instance,_instance是类CCP的唯一对象,即单例
        if not hasattr(CCP, "_instance"):
            #  将客户端和云通信的权限鉴定操作封装到单列中提高性能
            # 父类初始化给对象赋值
            cls._instance = super(CCP, cls).__new__(cls, *args, **kwargs)
            # 权限认证封装到单列【提高性能】,判断你是否是云通信的开发者
            cls._instance.rest = REST(_serverIP, _serverPort, _softVersion)
            cls._instance.rest.setAccount(_accountSid, _accountToken)
            cls._instance.rest.setAppId(_appId)
        # 当CCP类身上有_instance属性,直接返回
        return cls._instance
    # CCP().send_template_sms()
    def send_template_sms(self, to, datas, temp_id):
        """发送模板短信"""
        # @param to 手机号码
        # @param datas 内容数据 格式为数组 例如:{'6位短信验证码值:123456', '5'},如不需替换请填 ''
        # @param temp_id 模板Id
        result = self.rest.sendTemplateSMS(to, datas, temp_id)
        print(result)
        # 如果云通讯发送短信成功,返回的字典数据result中statuCode字段的值为"000000"
        if result.get("statusCode") == "000000":
            # 返回0 表示发送短信成功
            return 0
        else:
            # 返回-1 表示发送失败
            return -1
if __name__ == '__main__':
    ccp = CCP()
    # 注意: 测试的短信模板编号为1
    ccp.send_template_sms('185xxxxxxxx', ['1234', 5], 1)

python web 处理企业级电商业务中的秒杀功能:

(1)[秒杀]抢订单环节一般会带来2个问题:

·高并发:大量用户同一时间抢购,网站瞬时访问量剧增,导致服务器压力大
·超卖: 成功下订单买到商品的人数,超过数据库最大库存数量

(2)[秒杀]解决方案:

前端 [扩容,静态化,限流]:

A扩容:加机器,这是最简单的方法,通过增加前端池的整体承载量来抗峰值。

B:静态化 将页面能够静态化的元素全部静态化,并减少动态元素,通过CDN来抗峰值ESI: 在web服务器上做动态内容请求,并将数据插入静态页面中,用户拿到就一个完整的页面,这种方案对服务器端性能有影响,但是用户体验好。

CSI:在静态页面中单独发送异步js请求,从服务器动态获取数据,这种服务器效果很好,但是用户体验稍差

C:限流

ip限流:针对某一个ip地址,限制单位时间内访问次数

D:其他

在活动入口的地方设置关卡游戏或者问题环节,削弱峰值

后端出现高并发和超卖的原因:

I:首先MySQL自身对于高并发的处理性能就会出现问题,一般来说,MySQL的处理性能会随着并发thread上升而上升,但是到了一定的并发度之后会出现明显的拐点,之后一路下降,最终甚至会比单thread的性能还要差。

II:其次,超卖的根结在于减库存操作是一个事务操作,需要先select,然后insert,最后update -1。最后这个-1操作是不能出现负数的,但是当多用户在有库存的情况下并发操作,出现负数这是无法避免的。

III:最后,当减库存和高并发碰到一起的时候,由于操作的库存数目在同一行,就会出现争抢InnoDB行锁的问题,导致出现互相等待甚至死锁,从而大大降低MySQL的处理性能,最终导致前端页面出现超时异常。


解决方案1:

将存库从MySQL前移到Redis中,所有的写操作放到内存中,由于Redis中不存在锁故不会出现互相等待,并且由于Redis的写性能和读性能都远高于MySQL,这就解决了高并发下的性能问题。然后通过队列等异步手段,将变化的数据异步写入到DB中。

优点:解决性能问题

缺点:没有解决超卖问题,同时由于异步写入DB,存在某一时刻DB和Redis中数据不一致的风险。


解决方案2:

引入队列,然后将所有写DB操作在单队列中排队,完全串行处理。当达到库存阀值的时候就不在消费队列,并关闭购买功能。这就解决了超卖问题。

优点:解决超卖问题,略微提升性能。

缺点:性能受限于队列处理机处理性能和DB的写入性能中最短的那个,另外多商品同时抢购的时候需要准备多条队列。


解决方案3:

将写操作前移到Memcached中,同时利用Memcached的轻量级的锁机制CAS来实现减库存操作。

优点:读写在内存中,操作性能快,引入轻量级锁之后可以保证同一时刻只有一个写入成功,解决减库存问题。

缺点:没有实测,基于CAS的特性不知道高并发下是否会出现大量更新失败?不过加锁之后肯定对并发性能会有影响。


解决方案4:

将提交操作变成两段式,先申请后确认。然后利用Redis的原子自增操作(相比较MySQL的自增来说没有空洞),同时利用Redis的事务特性来发号,保证拿到小于等于库存阀值的号的人都可以成功提交订单。然后数据异步更新到DB中。

优点:解决超卖问题,库存读写都在内存中,故同时解决性能问题。缺点:由于异步写入DB,可能存在数据不一致。另可能存在少买,也就是如果拿到号的人不真正下订单,可能库存减为0,但是订单数并没有达到库存阀值。

服务器解决性能瓶颈问题

1、排队: 可以使用消息队列,将同步请求转化成异步请求,中间通过一个消息队列在一端[队列入口]承接瞬时的流量峰值,在另一端[队列出口]平滑的将消息推送出去;

2、设置关卡: 在活动入口的地方设置关卡游戏或者问题环节,削弱峰值;

3、分层过滤: 秒杀请求先经过CDN ==> 前端系统 ==> 后端系统 过滤掉无效请求。


猜你喜欢

Python+人工智能培训课程

redis令牌机制实现秒杀

0 分享到:
和我们在线交谈!