架构设计|高并发流量削峰,共享资源加锁机制(代码片段)

cicada-smile cicada-smile     2022-11-29     658

关键词:

本文源码:GitHub·点这里 || GitEE·点这里

一、高并发简介

在互联网的业务架构中,高并发是最难处理的业务之一,常见的使用场景:秒杀,抢购,订票系统;高并发的流程中需要处理的复杂问题非常多,主要涉及下面几个方面:

  • 流量管理,逐级承接削峰;
  • 网关控制,路由请求,接口熔断;
  • 并发控制机制,资源加锁;
  • 分布式架构,隔离服务和数据库;

高并发业务核心还是流量控制,控制流量下沉速度,或者控制承接流量的容器大小,多余的直接溢出,这是相对复杂的流程。其次就是多线程并发下访问共享资源,该流程需要加锁机制,避免数据写出现错乱情况。

二、秒杀场景

1、预抢购业务

活动未正式开始,先进行活动预约,先把一部分流量收集和控制起来,在真正秒杀的时间点,很多数据可能都已经预处理好了,可以很大程度上削减系统的压力。有了一定预约流量还可以提前对库存系统做好准备,一举两得。

场景:活动预约,定金预约,高铁抢票预购。

2、分批抢购

分批抢购和抢购的场景实现的机制是一致的,只是在流量上缓解了很多压力,秒杀10W件库存和秒杀100件库存系统的抗压不是一个级别。如果秒杀10W件库存,系统至少承担多于10W几倍的流量冲击,秒杀100件库存,体系可能承担几百或者上千的流量就结束了。下面流量削峰会详解这里的策略机制。

场景:分时段多场次抢购,高铁票分批放出。

3、实时秒杀

最有难度的场景就是准点实时的秒杀活动,假如10点整准时抢1W件商品,在这个时间点前后会涌入高并发的流量,刷新页面,或者请求抢购的接口,这样的场景处理起来是最复杂的。

  • 首先系统要承接住流量的涌入;
  • 页面的不断刷新要实时加载;
  • 高并发请求的流量控制加锁等;
  • 服务隔离和数据库设计的系统保护;

场景:618准点抢购,双11准点秒杀,电商促销秒杀。

三、流量削峰

技术图片

1、Nginx代理

Nginx是一个高性能的HTTP和反向代理web服务器,经常用在集群服务中做统一代理层和负载均衡策略,也可以作为一层流量控制层,提供两种限流方式,一是控制速率,二是控制并发连接数。

基于漏桶算法,提供限制请求处理速率能力;限制IP的访问频率,流量突然增大时,超出的请求将被拒绝;还可以限制并发连接数。

高并发的秒杀场景下,经过Nginx层的各种限制策略,可以控制流量在一个相对稳定的状态。

2、CDN节点

CDN静态文件的代理节点,秒杀场景的服务有这样一个操作特点,活动倒计时开始之前,大量的用户会不断的刷新页面,这时候静态页面可以交给CDN层面代理,分担数据服务接口的压力。

CDN层面也可以做一层限流,在页面内置一层策略,假设有10W用户点击抢购,可以只放行1W的流量,其他的直接提示活动结束即可,这也是常用的手段之一。

话外之意:平时参与的抢购活动,可能你的请求根本没有到达数据接口层面,就极速响应商品已抢完,自行意会吧。

3、网关控制

网关层面处理服务接口路由,一些校验之外,最主要的是可以集成一些策略进入网关,比如经过上述层层的流量控制之后,请求已经接近核心的数据接口,这时在网关层面内置一些策略控制:如果活动是想激活老用户,网关层面快速判断用户属性,老用户会放行请求;如果活动的目的是拉新,则放行更多的新用户。

经过这些层面的控制,剩下的流量已经不多了,后续才真正开始执行抢购的数据操作。

话外之意:如果有10W人参加抢购活动,真正下沉到底层的抢购流量可能就1W,甚至更少,在分散到集群服务中处理。

4、并发熔断

在分布式服务的接口中,还有最精细的一层控制,对于一个接口在单位之间内控制请求处理的数量,这个基于接口的响应时间综合考虑,响应越快,单位时间内的并发量就越高,这里逻辑不难理解。

言外之意:流量经过层层控制,数据接口层面分担的压力已经不大,这时候就是面对秒杀业务中的加锁问题了。

四、分布式加锁

1、悲观锁

机制描述

所有请求的线程必须在获取锁之后,才能执行数据库操作,并且基于序列化的模式,没有获取锁的线程处于等待状态,并且设定重试机制,在单位时间后再次尝试获取锁,或者直接返回。

过程图解

技术图片

Redis基础命令

SETNX:加锁的思路是,如果key不存在,将key设置为value如果key已存在,则 SETNX 不做任何动作。并且可以给key设置过期时间,过期后其他线程可以继续尝试锁获取机制。

借助Redis的该命令模拟锁的获取动作。

代码实现

这里基于Redis实现的锁获取和释放机制。

import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import javax.annotation.Resource;
@Component
public class RedisLock 

    @Resource
    private Jedis jedis ;

    /**
     * 获取锁
     */
    public boolean getLock (String key,String value,long expire)
        try 
            String result = jedis.set( key, value, "nx", "ex", expire);
            return result != null;
         catch (Exception e)
            e.printStackTrace();
        finally 
            if (jedis != null) jedis.close();
        
        return false ;
    

    /**
     * 释放锁
     */
    public boolean unLock (String key)
        try 
            Long result = jedis.del(key);
            return result > 0 ;
         catch (Exception e)
            e.printStackTrace();
        finally 
            if (jedis != null) jedis.close();
        
        return false ;
    

这里基于Jedis的API实现,这里提供一份配置文件。

@Configuration
public class RedisConfig 

    @Bean
    public JedisPoolConfig jedisPoolConfig ()
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig() ;
        jedisPoolConfig.setMaxIdle(8);
        jedisPoolConfig.setMaxTotal(20);
        return jedisPoolConfig ;
    

    @Bean
    public JedisPool jedisPool (@Autowired JedisPoolConfig jedisPoolConfig)
        return new JedisPool(jedisPoolConfig,"127.0.0.1",6379) ;
    

    @Bean
    public Jedis jedis (@Autowired JedisPool jedisPool)
        return jedisPool.getResource() ;
    

问题描述

在实际的系统运行期间可能出现如下情况:线程01获取锁之后,进程被挂起,后续该执行的没有执行,锁失效后,线程02又获取锁,在数据库更新后,线程01恢复,此时在持有锁之后的状态,继续执行后就会容易导致数据错乱问题。

这时候就需要引入锁版本概念的,假设线程01获取锁版本1,如果没有执行,线程02获取锁版本2,执行之后,通过锁版本的比较,线程01的锁版本过低,数据更新就会失败。

CREATE TABLE `dl_data_lock` (
	`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT ‘主键ID‘,
	`inventory` INT (11) DEFAULT ‘0‘ COMMENT ‘库存量‘,
	`lock_value` INT (11) NOT NULL DEFAULT ‘0‘ COMMENT ‘锁版本‘,
	PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = ‘锁机制表‘;

说明:lock_value就是记录锁版本,作为控制数据更新的条件。

<update id="updateByLock">
    UPDATE dl_data_lock SET inventory=inventory-1,lock_value=#lockVersion
    WHERE id=#id AND lock_value &lt;#lockVersion
</update>

说明:这里的更新操作,不但要求线程获取锁,还会判断线程锁的版本不能低于当前更新记录中的最新锁版本。

2、乐观锁

机制描述

乐观锁大多是基于数据记录来控制,在更新数据库的时候,基于前置的查询条件判断,如果查询出来的数据没有被修改,则更新操作成功,如果前置的查询结果作为更新的条件不成立,则数据写失败。

过程图解

技术图片

代码实现

业务流程,先查询要更新的记录,然后把读取的列,作为更新条件。

@Override
public Boolean updateByInventory(Integer id) 
    DataLockEntity dataLockEntity = dataLockMapper.getById(id);
    if (dataLockEntity != null)
        return dataLockMapper.updateByInventory(id,dataLockEntity.getInventory())>0 ;
    
    return false ;

例如如果要把库存更新,就把读取的库存数据作为更新条件,如果读取库存是100,在更新的时候库存变了,则更新条件自然不能成立。

<update id="updateByInventory">
    UPDATE dl_data_lock SET inventory=inventory-1 WHERE id=#id AND inventory=#inventory
</update>

五、分布式服务

1、服务保护

在处理高并发的秒杀场景时,经常出现服务挂掉场景,常见某些APP的营销页面,出现活动火爆页面丢失的提示情况,但是不影响整体应用的运行,这就是服务的隔离和保护机制。

基于分布式的服务结构可以把高并发的业务服务独立出来,不会因为秒杀服务挂掉影响整体的服务,导致服务雪崩的场景。

2、数据库保护

数据库保护和服务保护是相辅相成的,分布式服务架构下,服务和数据库是对应的,理论上秒杀服务对应的就是秒杀数据库,不会因为秒杀库挂掉,导致整个数据库宕机。

六、源代码地址

GitHub·地址
https://github.com/cicadasmile/data-manage-parent
GitEE·地址
https://gitee.com/cicadasmile/data-manage-parent

技术图片

推荐阅读:《架构设计系列》,萝卜青菜,各有所需

序号 标题
00 架构设计:单服务.集群.分布式,基本区别和联系
01 架构设计:分布式业务系统中,全局ID生成策略
02 架构设计:分布式系统调度,Zookeeper集群化管理
03 架构设计:接口幂等性原则,防重复提交Token管理
04 架构设计:缓存管理模式,监控和内存回收策略
05 架构设计:异步处理流程,多种实现模式详解

京东高并发秒杀解决方案

...秒杀场景下的巨大流量洪峰以及数据的一致性问题。秒杀架构设计理念:防刷设计:设计有效的防刷机制,有效拦截无效请求,避免用户恶意调用秒杀相关接口。数据分层校验:尽可能在不同层将无效请求拦截并过滤掉,让真正... 查看详情

高并发场景下优化加锁方式:线程等待与通知机制(代码片段)

本文分享自华为云社区《【高并发】讲讲高并发场景下如何优化加锁方式?》,作者:冰河。互斥条件、不可剥夺条件、请求与保持条件、循环等待条件,这是产生死锁时的四个必要条件,只有四个条件同时... 查看详情

高并发系统设计(十三):消息队列的三大作用:削峰填谷异步处理模块解耦

削去秒杀场景下的峰值写流量而在秒杀场景下,高并发的写请求并不是持续的,也不是经常发生的,而只有在秒杀活动开始后的几秒或者十几秒时间内才会存在。为了应对这十几秒的瞬间写高峰,将秒杀请求暂存在消息队列中,... 查看详情

架构

1.如何设计高性能、高并发、高可用的系统。系统架构三个利器:RPC服务组件、消息中间件(交互异步化、流量削峰)、配置管理(灰度发布、降级);无状态:接口层最重要的就是无状态,将有状态的数据剥离到数据库或缓存... 查看详情

架构高可用高并发系统的设计原则

通过学习《亿级流量网站架构核心技术》及《linux就该这么学》学习笔记及自己的感悟:架构设计之高可用高并发系统设计原则,架构设计包括墨参考技术A通过学习《亿级流量网站架构核心技术》及《linux就该这么学》学习笔记... 查看详情

java高并发编程实战6,通过aqs源码分析lock()锁机制(代码片段)

目录一、JVM内存区域二、JVM内存模型(JavaMemoryModel,简称JMM)三、各个线程运行期间必须遵守的规定四、wait与notify五、tryLock1、代码实例2、lock()和tryLock()有何不同?六、AQS源码分析1、state2、访问state的方法3、加... 查看详情

java高并发编程实战6,通过aqs源码分析lock()锁机制(代码片段)

目录一、JVM内存区域二、JVM内存模型(JavaMemoryModel,简称JMM)三、各个线程运行期间必须遵守的规定四、wait与notify五、tryLock1、代码实例2、lock()和tryLock()有何不同?六、AQS源码分析1、state2、访问state的方法3、加... 查看详情

高并发高可用架构设计之简介

设计一个好的架构需要满足:高并发、高性能、高可用三个条件。举一个实际的例子,如高并发方面要求QPS大于10万;高性能方面要求请求延迟小于100ms;高可用方面要高于99.99%。注:QPS(QueryPerSecond):每秒请求数,就是说服务... 查看详情

高并发高可用架构设计之简介

设计一个好的架构需要满足:高并发、高性能、高可用三个条件。举一个实际的例子,如高并发方面要求QPS大于10万;高性能方面要求请求延迟小于100ms;高可用方面要高于99.99%。注:QPS(QueryPerSecond):每秒请求数,就是说服务... 查看详情

302|高并发架构设计方法:面对高并发,怎么对症下药?

你好,我是李智慧。我们知道,“高并发”是现在系统架构设计的核心关键词。一个架构师如果设计、开发的系统不支持高并发,那简直不好意思跟同行讨论。但事实上,在架构设计领域,高并发的历史非常短暂,这一架构特性... 查看详情

感知高并发系统架构的魅力!天猫java亿级高并发架构设计的全套笔记火热来袭,你确定不来看看?

...魅力的地方就在于,我们用自己的高智商设计系统的架构从而“抵抗”巨大的流量压力,从而给我们的用 查看详情

大型网站技术架构演化

...并发、大流量a.什么是高并发?高并发是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理多个请求。b.高并发的衡量指标有哪些?(1)响应时间:系统对请求做出响应。例如系统处... 查看详情

关于亿级流量网站架构一书缓存机制的探讨

在京东的亿级流量网站架构一书,175页介绍缓存有这样一段话仅就这段代码来看,在高并发情况下,实际上并不能阻止大量线程调用loadSync函数当然这个书里的代码是作者的简写,这里探讨只是针对书中这段代码,实际生成代码... 查看详情

高并发大访问量架构设计演进之路归纳总结

 高并发大访问量架构设计演进之路归纳总结第01:大型架构的演进之路第02(上):分布式缓存第02(下):分布式缓存第03:分布式消息队列第04:分布式数据存储第05:分布式服务框架第06:高性能系统架构第07:高可用系... 查看详情

架构思考

高流量并发解决方案削峰填谷大型电商网站,主要技术挑战是来自庞大的用户规模带来的大流量,高并发,以及海量数据,在双11,双12等大促场景下尤为明显。如果不对流量进行合理管制,肆意放任大流量冲击系统,那么将会... 查看详情

高并发优化加锁方式时竟然死锁了!!(代码片段)

...后,终于找到了原因。为何需要优化加锁方式?在《【高并发】高并发环境下诡异的加锁问题(你加的锁未必安全)》一文中,我们在转账类TansferAccount中使用TansferAccount.class对象对程序加锁,如下所示。publicclassTansferAccountprivate... 查看详情

秒杀系统设计面试

秒杀业务的特点就是多个人读一个数据,难点就是读写冲突,锁情况特别的严重。 所以我们尽量不要让请求落在数据库上去,让请求拦截在系统的上游。解决思路:1、限流:屏蔽掉无用的流量,允许少部分流量流向后端。2、... 查看详情

阿里巴巴高并发架构,到底如何对抗双十一亿级并发流量

前言我们知道,高并发代表着大流量,高并发系统设计的魅力就在于我们能够凭借自己的聪明才智设计巧妙的方案,从而抵抗巨大流量的冲击,带给用户更好的使用体验。这些方案好似能操纵流量,让流量更... 查看详情