分布式——补充的一些东西(就业相关的)秒杀的设计方案分布式id生成方案分布式锁分布式锁的三种实现方式(基于数据库RedisZookeeper)

Posted 代码有毒___

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式——补充的一些东西(就业相关的)秒杀的设计方案分布式id生成方案分布式锁分布式锁的三种实现方式(基于数据库RedisZookeeper)相关的知识,希望对你有一定的参考价值。

文章目录

今日内容

0 补充的一些东西(就业相关的)

1 同步:django flask, 异步:sanic, fastapi
2 一旦开了异步,后面所有的框架都需要用异步框架(连redis:aioredis,连mysql:aiomysql)
3 sqlachemy 不支持异步的,peewee-async(依赖于peewee)

4 公司内部连接mysql

5 数据库密码不放在配置文件中,放到环境变量中

6 工作流程
	-公司配电脑(用自己的,补贴,windows:远程连接,mac,乌班图)
    -搭好环境
    -让你在本地生成一个ssh公钥(给相关负责人,配进去,对项目的读写权限)
    -把项目跑起来(难装的模块)
    -正式环境,测试环境(测)
    -本地装mysql,redis----》或者你也可以搞个虚拟机,docker拉起来
    -项目跑起来
    -找出项目里面写代码的一条线(流程)
    
    -当让你在现有的项目写接口,按照别人写好的一条线复制
    -一定要测试
    -提交代码(例如项目管理平台:禅道)
    -当此提交的id号,填到项目管理平台上,点完成
    -每天拉几次代码,提交几次代码
    -功能完成
    
    -代码review(专门有人通读你的代码)
    -(大厂)每周五:专门挑出时间小组内代码review
    
    -可能需要你把项目跑在测试机器上,让测试人员测
    -测试人员测完,它点完成,这个需求就结束了
    -测试人员测着有问题,再改再提交--->测试
    
    -刚去,不会的可以问,同样的问题不要问第二次(录下来)
    -同事搞好关系,买瓶饮料
    -不要早走(头一个月)
    -不要迟到(头一个月)
    
    -公司暗语(多听少说)
    

99 压力测试工具:ab  jmeter
100 python的GUI编程: pyqt,Tkinter



redis不支持widows,源码开源,有专业团队负责做出windows平台的软件,但是最新版只有3.x

公司内部连接mysql

import pymysql

from sshtunnel import SSHTunnelForwarder

 

server = SSHTunnelForwarder(

    ssh_address_or_host=('机器B的IP', 22), # 指定ssh登录的跳转机的address

    ssh_username='机器B的用户名', # 跳转机的用户

    ssh_password='机器B的密码', # 跳转机的密码

    remote_bind_address=('机器C的IP', 3306)

)

server.start()



db = pymysql.connect(

    host='127.0.0.1',

    port=server.local_bind_port,

    user='机器C的用户名',

    passwd='机器C的密码',

    db='数据库名'

)

cur = db.cursor()

cur.execute('select * from article limit 1')

data = cur.fetchall()

print(data)

db.close()

 

server.close()

1 秒杀的设计方案

1 方案跟语言无关
2 在原有项目继续加(程序因为并发量崩溃,数据数据出现问题,都会影响原来项目)?还是新写一个
3 重新设计库,一般也只需要几个表即可,(假设卖10000个python课程),所以需要设计课程表和订单表
4 假设并没有多少人秒杀,就是之前我们写的商城项目
	- 注意不要秒超了,锁(悲观锁,乐观锁,mysql)
	- 同步扣款,生成订单
5 假设并发量在个几百
	-把同步换成异步
    -celery+redis,同步扣款,生成订单
6 假设并发量几千
	-分布式锁(项目部署在多台机器上)
    -用专业的消息队列(异步)
    -限流(有令牌的才能进来),有资格秒杀的用户,访问秒杀页面,给个令牌,放到redis一份
    	-只要有令牌的才能往里走,有令牌,redis中有,再往后走,只要查过一次,直接删除(爬虫不能反复的发)
        -对用户最多生成10个令牌
    -把有资格秒的人,用户id放到消息队列中(消息队列可以存几千万,上亿个id-视图直接返回,前端动图显示:您正在排队,然后每隔3s/5s,向后端发一次请求,查询是否秒杀成功
        -worker一个个给执行,1w个都消费完了
        -需要先在redis中预热,设置1w这个数
        -worker工作,没执行一下数字减一,生成订单(redis乐观锁)
        -再大的话可以把生成订单再写成异步
7  一般来说需要的也就是:限流,锁,异步,缓冲(消息队列)

2 分布式id生成方案

1 分布式的系统中,生成id号,按当前时间生成id号,可能会重复,全局唯一ID的系统是非常必要的
2 再不同机器上生成的id号不能重复


3 生成id号的要求
	-全局唯一
    -趋势递增
    -单调递增
    -安全(不要被猜出来)
4 mysql的自增(可以做,但是性能跟不上)
5 uuid:全局唯一,安全,没有递增的趋势
6 redis:自增+时间+用户id  比较好的方案

7 雪花算法(业界普遍选择的id生成方案)跟语言无关(任何语言都有实现),不依赖于服务(redis,mysql),词可以放到简历上
    

3 分布式锁

1 数据库的悲观,乐观锁
2 基于redis实现分布式锁

3 本质原理基于:SETNX
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。





# 安装第三方没有被放到pypi的模块
# 源码下载下来,都有个setup.py
先执行 python setup.py build.
然后执行 python setup.py install


# https://segmentfault.com/a/1190000012621842

在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。那具体什么是分布式锁,分布式锁应用在哪些业务场景、如何来实现分布式锁呢?

一 为什么要使用分布式锁

我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的锁进行处理,并且可以完美的运行,毫无Bug!注意这是单机应用,后来业务发展,需要做集群,一个应用需要部署到几台机器上然后做负载均衡,大致如下图:

上图可以看到,变量A存在三个服务器内存中(这个变量A主要体现是在一个类中的一个成员变量,是一个有状态的对象),如果不加任何控制的话,变量A同时都会在分配一块内存,三个请求发过来同时对这个变量操作,显然结果是不对的!即使不是同时发过来,三个请求分别操作三个不同内存区域的数据,变量A之间不存在共享,也不具有可见性,处理的结果也是不对的!如果我们业务中确实存在这个场景的话,我们就需要一种方法解决这个问题!为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用并发处理相关的功能进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的应用并不能提供分布式锁的能力。为了解决这个问题就需要一种跨机器的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

二、分布式锁应该具备哪些条件

在分析分布式锁的三种实现方式之前,先了解一下分布式锁应该具备哪些条件:
1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用的获取锁与释放锁;
3、高性能的获取锁与释放锁;
4、具备可重入特性;
5、具备锁失效机制,防止死锁;
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

三、分布式锁的三种实现方式

目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。

基于数据库实现分布式锁;
基于缓存(Redis等)实现分布式锁;
基于Zookeeper实现分布式锁;

四、基于数据库的实现方式

基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
(1)创建一个表:

DROP TABLE IF EXISTS `method_lock`;
CREATE TABLE `method_lock` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `method_name` varchar(64) NOT NULL COMMENT '锁定的方法名',
  `desc` varchar(255) NOT NULL COMMENT '备注信息',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';


(2)想要执行某个方法,就使用这个方法名向表中插入数据:

INSERT INTO method_lock (method_name, desc) VALUES ('methodName', '测试的methodName');

因为我们对method_name做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。
(3)成功插入则获取锁,执行完成后删除对应的行数据释放锁:

delete from method_lock where method_name ='methodName';

注意:这只是使用基于数据库的一种方法,使用数据库实现分布式锁还有很多其他的玩法!使用基于数据库的这种实现方式很简单,但是对于分布式锁应该具备的条件来说,它有一些问题需要解决及优化:
1、因为是基于数据库实现的,数据库的可用性和性能将直接影响分布式锁的可用性及性能,所以,数据库需要双机部署、数据同步、主备切换;
2、不具备可重入的特性,因为同一个线程在释放锁之前,行数据一直存在,无法再次成功插入数据,所以,需要在表中新增一列,用于记录当前获取到锁的机器和线程信息,在再次获取锁的时候,先查询表中机器和线程信息是否和当前机器和线程相同,若相同则直接获取锁;
3、没有锁失效机制,因为有可能出现成功插入数据后,服务器宕机了,对应的数据没有被删除,当服务恢复后一直获取不到锁,所以,需要在表中新增一列,用于记录失效时间,并且需要有定时任务清除这些失效的数据;
4、不具备阻塞锁特性,获取不到锁直接返回失败,所以需要优化获取逻辑,循环多次去获取。
5、在实施的过程中会遇到各种不同的问题,为了解决这些问题,实现方式将会越来越复杂;依赖数据库需要一定的资源开销,性能问题需要考虑。

五、基于Redis的实现方式

1、选用Redis实现分布式锁原因:
(1)Redis有很高的性能;
(2)Redis命令对此支持较好,实现起来比较方便
2、使用命令介绍:
(1)SETNXSETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
(2)expireexpire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
(3)deletedelete key:删除key在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

3、实现思想:
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

4、 分布式锁的简单实现代码:

#连接redis
redis_client = redis.Redis(host="localhost",
                           port=6379,
                           password=password,
                           db=10)

#获取一个锁
lock_name:锁定名称
acquire_time: 客户端等待获取锁的时间
time_out: 锁的超时时间
def acquire_lock(lock_name, acquire_time=10, time_out=10):
    """获取一个分布式锁"""
    identifier = str(uuid.uuid4())
    end = time.time() + acquire_time
    lock = "string:lock:" + lock_name
    while time.time() < end:
        if redis_client.setnx(lock, identifier):
            # 给锁设置超时时间, 防止进程崩溃导致其他进程无法获取锁
            redis_client.expire(lock, time_out)
            return identifier
        elif not redis_client.ttl(lock):
            redis_client.expire(lock, time_out)
        time.sleep(0.001)
    return False

#释放一个锁
def release_lock(lock_name, identifier):
    """通用的锁释放函数"""
    lock = "string:lock:" + lock_name
    pip = redis_client.pipeline(True)
    while True:
        try:
            pip.watch(lock)
            lock_value = redis_client.get(lock)
            if not lock_value:
                return True

            if lock_value.decode() == identifier:
                pip.multi()
                pip.delete(lock)
                pip.execute()
                return True
            pip.unwatch()
            break
        except redis.excetions.WacthcError:
            pass
    return False

5、测试刚才实现的分布式锁
例子中使用50个线程模拟秒杀一个商品,使用–运算符来实现商品减少,从结果有序性就可以看出是否为加锁状态。

def seckill():
    identifier=acquire_lock('resource')
    print(Thread.getName(),"获得了锁")
    release_lock('resource',identifier)


for i in range(50):
    t = Thread(target=seckill)
    t.start()

六、基于ZooKeeper的实现方式

ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:(1)创建一个目录mylock;(2)线程A想获取锁就在mylock目录下创建临时顺序节点;(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。

七、总结

上面的三种实现方式,没有在所有场合都是完美的,所以,应根据不同的应用场景选择最适合的实现方式。在分布式环境中,对资源进行上锁有时候是很重要的,比如抢购某一资源,这时候使用分布式锁就可以很好地控制资源。当然,在具体使用中,还需要考虑很多因素,比如超时时间的选取,获取锁时间的选取对并发量都有很大的影响,上述实现的分布式锁也只是一种简单的实现,主要是一种思想 。

以上是关于分布式——补充的一些东西(就业相关的)秒杀的设计方案分布式id生成方案分布式锁分布式锁的三种实现方式(基于数据库RedisZookeeper)的主要内容,如果未能解决你的问题,请参考以下文章

java初探之秒杀的安全

关于秒杀的场景特点分析

分布式技术专题「架构设计方案」盘点和总结秒杀服务的功能设计及注意事项技术体系

Redis分布式缓存秒杀

Redis分布式缓存秒杀

基于redis分布式锁实现“秒杀”