美文网首页
分布式架构

分布式架构

作者: darcyaf | 来源:发表于2020-03-10 22:02 被阅读0次

    大流量限流/削峰

    常见的限流算法

    • 计数器算法
      池化资源技术的限流就是通过计数器算法来控制全局的总并发数。
    • 令牌桶算法
      令牌桶(Token Bucket),算法主要用于安置流量的平均流入速率, 并且还允许出现一定程度上的突发流量。
      基本流程如下:
      1. 每秒会有r个令牌被放入桶中,也就是一个令牌的流入速度为1/r秒
      2. 桶的容量是固定不变的,假设桶中最多只允许存放b个令牌,如果桶慢了,则溢出.
    • 漏桶算法
      漏桶(Leaky Bucket) 也可以实现流量管制。
      1. 可以以任意速到想同种流入水滴
      2. 桶的容量是固定不变的,如果桶满了则溢出.
      3. 按照固定的速率从同种流出水滴.

    从本质上,令牌桶和漏桶算法都可以让系统的负载处于比较均衡的水位,不会因为峰值流量过大导致系统被击垮,但是两者的限流方向是截然相反的。
    令牌中限制的是流量的平均流入速度,而漏桶算法限制的是流量的流出速率,不允许出现突发流量

    使用消息中间件实现系统解耦

    使用消息中间件是为了实现特定场景下的流量削峰,以及通过消息传递来实现异步调用等.

    集中式资源配置需求

    优点:

    • 配置信息统一管理
    • 动态获取/更新配置信息
    • 降低运维人员的维护成本
    • 降低配置出错率

    方案

    • zookeeper
      我们将配置信心发布到Znode目录上后,由客户端负责信息订阅,一旦配置信息发生变更,所有watch目标znode的zookeeper都会管制到,一旦获取到之后就可以做一些自定义的逻辑处理
    • consul

    大促场景下的热点数据的读/写优化

    同一热卖商品高并发读需求

    虽然redis进行Cluster之后可能认为容量是无限的,并且读/写操作经过了水平化处理, 不同的key均会落到不同的缓存节点上。但是对于限时抢购场景下的热卖商品来说,
    这时同一个Key必然会落到同一个缓存节点上,故而会出现单点瓶颈. 尽管缓存系统单点支撑10w/s的qps没有压力,但是在短短几分钟内的流量导致单节点的容量被撑爆,资源耗尽

    解决方案

    1. Redis集群多写多读方案
      在默认情况下,同一个热卖商品的key为"itemid123",经过路由后会被存储到集群环境的某一个缓存节点上。我们把多写和多读两个步骤拆分开来。 多写就是指在抢购活动开始之前,对某一个热卖商品的key进行加工和计算。假设有n和缓存节点,那么加工和计算之后的key也应该有n个, 在最理想的情况下, 每个key都应该被路由到一个指定的缓存节点上,其实多写操作就是为了数据的冗余存储。
      简单讲就是多生成一些key,冗余的存储到每个节点上.然后通过轮训或者随机的方式去取数据
      问题: 必须考虑多写过程中数据的一致性问题,其次是一旦对集群进行了动态扩容,由于每一个缓存节点持有的Slot发生了变化,那么所有的key都需要重新进行加工和计算.

      • 保障多写时的数据一致性问题
        使用zookeeper来配置同一个热卖商品的key是个非常不错的选择,一旦watch到znode发生变化,所有的客户端全量更新本地持有的key即可.
        以下三种情况会造成数据多写过程中产生失败:
        1. 网络抖动
        2. Master/Slave切换
        3. Master/Slave全部宕机,集群环境重新分配slot区间.

      如果一旦在某个节点处写入失败,就可以充实几次,当超过规定的重试次数,就可以直接从Znode中剔除失败写入的Key。这样Zookeeper的客户端就可以监听到Znode变化.

    2. LocalCache结合Redis集群的多级Cache方案

    使用localCache,提前将数据下发到所有参与限时抢购的Web服务器的本地缓存上,直到活动结束。然后设置定时轮训,从分布式缓存中拉去最新的库存数据.记住不要设置ttl, 否则流量过大时key过期会直接程序的吞吐两严重下降.
    如果想缩短本地缓存与分布式缓存之间数据不一致的窗口期,可以引入消息队列。当数据改变了local中就会接收到通知.

    1. 引入实时热点自动发现方案。
      将交易系统产生的数据,以及在上游系统中埋点上报的数据异步写入到日志系统中,通过对日志做次数统计和热点分析,数据符合热点条件后,就立即通知交易系统做好热点保护,防患于未然.

    同一热卖商品高并发写需求

    1. InnoDB行级锁引起数据库TPS下降,数据库为了保证原子性,会对同一行数据记录加锁,把并发请求编程串行请求

    如果在数据库中扣减库存,怎么避免商品超卖呢。 可以通过乐观锁机制来避免这个问题。 所谓乐观锁,就是在item中建立一个version字段, 当更新数据是要保证version字段一致才能扣减。 为了提升库存扣减的成功率,可以适当进行重试,如果库存不足,则说明商品已经售罄,反之扣减库存后version继续加1.

    在并发大时,由于大量线程竞争InnnoDB行级锁是引起的一系列问题不容忽视.

    1. 使用redis分布式锁
      将库存扣减的操作移到redis中,使用分布式锁机制。 建议使用tryLock(long waitTime, long leaseTime, TimeUnit unit)的方式获取分布式锁,设置waitTime参数是为了规定获取锁的等待时间,当超过这个时间则不再等待,这个可以相应做平衡,如果不加waitTime其实和InnoDB行级锁也差不了多少.
      对数据库的写入可以使用消息队列,可以以恒定速度写入数据库.

    热卖商品库存扣减优化方案.

    为了避免商品超卖, 无论是直接在数据库中扣减库存,还是在Redis中扣减库存,都必须依赖于串行化和锁机制。加入商品库存数量为n,那么锁的获取次数也为n.如果通过减少锁的获取次数来提升系统整体TPS的目的? 批量提交扣减商品库存是个不错的优化方案。 当前段发起库存扣减请求是,可以先对这些请求展开收集,比如100次为一个batch,就可以一次性获取一个分布式锁批量操作这100条。
    问题: 扣减失败时怎么将结果响应给指定的用户, 如何避免商品库存售罄
    可以使用redis提供的watch命令来实现乐观锁, 如果提交事务时,监听到Key的值发生改变, 那么就意味着版本号发生了改变。

    数据库分库分表

    1. 读写分离
      如果Master存在TPS较高的情况,Master和Slave数据库之间数据同步是会存在一定延迟的,因此在写入master之前最好将同一份数据落到缓存中, 以避免高并发下, slave中获取不到数据的情况发生.

    2. 垂直分库
      就是将冗余在单库中的数据表拆分到不同的业务库中,实现分而治之的数据管理和读/写操作.
      当mysql中单表超过500w时,读操作就会逐渐称为瓶颈,哪怕索引重建,也无法解决数据膨胀带来的检索效率地下的问题。

    3. 水平分表
      简单就是将原本冗余在单库中的单个业务表拆分为n个逻辑相关的业务子表,但是对外还是形成一个整体,也就是sharding。

    分库分表后带来的影响

    1. ACID如何保证
    • Atomicity(原子性)
    • Consistency(一致性)
    • Isolation(隔离性)
    • Durability(持久性)
    1. 多表之间的关联查询如何进行
    2. 无法继续使用外键约束
    3. 无法使用自增长生成全局唯一和连续性ID
      可以用一个ID生成器,每次生成器都回去数据库获取ID的最大值,通过行级锁来确保并发环境下的数据一致性. 当然,Shark的ID生成器一次会从数据库中取出一段ID,然后缓存在本地,这样就不用每次都去数据库申请了.

    分布式事务,数据一致性问题

    常见的三种分布式系统的一致性协议如下:

    • 两阶段提交协议(two phase commit protocol, 2PC)
    • 三阶段提交协议(three phase commit protocol, 3PC)
    • Paxos协议

    方案

    可靠消息最终一致性方案

    A系统执行的时候发一个prepared消息到mq, 发送成功之后,则开始执行本地事务,如果执行成功,则想mq发送确认消息. 如果失败,也通知mq,让b任务回滚
    对于B任务的本地任务,如果执行失败了,可以重试就好了.
    案例,在减库存的过程中,同时要加入一条购买记录。如果减库存失败了,那么购买记录也回滚,如果库存减成功了,那么购买记录的失败只要重试就好了.
    最后可以实现最终一致.

    最大努力通知方案

    于可靠消息最终一致性的区别在于,下面这个有重试失败次数,如果失败了,就需要人工干预,或者置之不理的那种,也就是就算没有完成也没什么关系的事务

    TCC强一致性方案 (Try Confirm Cancel)

    如果是核心服务或者涉及资金的服务可能需要这样做.

    1. Try 阶段:先把两个银行账户中的资金给它冻结住就不让操作了;
    2. Confirm 阶段:执行实际的转账操作,A 银行账户的资金扣减,B 银行账户的资金增加;
    3. Cancel 阶段:如果任何一个银行的操作执行失败,那么就需要回滚进行补偿,就是比如 A 银行账户如果已经扣减了,但是 B 银行账户资金增加失败了,那么就得把 A 银行账户资金给加回去。

    相关文章

      网友评论

          本文标题:分布式架构

          本文链接:https://www.haomeiwen.com/subject/dfjwdhtx.html