用实际项目分析高并发-抢霸王餐

一、前言

高并发的经验是许多网络开发人员追求的梦想,有了这样的经验在哪里找工作都有优势,同样的资质,有这个硬指标,那你就自信很多。而在实际工作中一些中小型公司很难碰到高并发场景,在这种场景下,如何提高吞吐量?如何降低响应时间?从这两个指标开始折腾到底。所以要获得高并发的经验:首先要有场景,有了场景,需要自己去了解方方面面相关的知识,在架构调优的同时,自己也能跟着成长了。


二、高并发设计核心

2.1 硬件层面
  • 服务器加配(内存,硬盘)
2.2 软件层面
  • LSV,nginx负载均衡(分流)
  • 数据库分库分表,读写分离
  • 缓存:空间换时间(解决读)
  • 异步:慢慢处理(解决写)
2.3 架构方面
  • 动静资源分离:图片,js,css资源文件
  • 前后台分离
  • 页面静态化(html进行缓存)
2.4 保证服务稳定性
  • 限流
    • 控制并发 Semaphone
    • 控制访问速率(令牌桶RateLimiter,漏桶)
  • 降级
  • 熔断(保险丝)
  • 重试

三、实际项目如何落地

那我们了解要设计一个高并发项目大概需要的核心要点后,那在真实项目中是如何落地的呢?有没有一些比较成熟的中间件来帮助我们快速实现?这里分2种服务:dubbo,http(网关)

3.1 网关-Hystrix

1.协议转换:dubbo协议转换为http协议
2.统一api管理:环境隔离,api分组,api文档
3.提高效率:管理,开发

在这里插入图片描述

在这里插入图片描述

功能分析

1.签名鉴权
2.登录验证
3.数据Mock
4.参数校验
5.国际化翻译
6.熔断
7.降级
8.限流
9.扩展点

在这里插入图片描述

总结:网关统一对服务进行管理,我们通过网关后台配置,就可以简单对我们服务配置超时,限流,降级,熔断策略。(Hystrix实现原理后面再分析)


3.2 Dubbo-Sentinel

在这里插入图片描述
在复杂的生产环境下可能部署着成千上万的服务实例,当流量持续不断地涌入,服务之间相互调用频率陡增时,会产生系统负载过高、网络延迟等一系列问题,从而导致某些服务不可用。如果不进行相应的流量控制,可能会导致级联故障,并影响到服务的可用性,因此如何对高流量进行合理控制,成为保障服务稳定性的关键。Sentinel是面向分布式服务架构的轻量级限流降级框架,以流量为切入点,从流量控制、熔断降级和系统负载保护等多个维度来帮助用户保障服务的稳定性。

dubbo服务怎么引入sentinel?

Sentinel 提供了与 Dubbo 适配的模块 – Sentinel Dubbo Adapter,包括针对服务提供方的过滤器和服务消费方的过滤器。使用时用户只需引入相关模块,Dubbo的服务接口和方法(包括调用端和服务端)就会成为 Sentinel 中的资源,在配置了规则后就可以自动享受到 Sentinel 的防护能力。同时提供了灵活的配置选项,例如若不希望开启Sentinel Dubbo Adapter中的某个Filter,可以手动关闭对应的Filter。

sentinel控制台?

Sentinel的控制台(Dashboard)是流量控制、熔断降级规则统一配置和管理的入口,同时它为用户提供了多个维度的监控功能。在Sentinel控制台上,我们可以配置规则并实时查看流量控制效果。
在这里插入图片描述

总结:通过dubbo整合sentinel后,我们只需要通过sentinel控制台进行配置就可以轻松实现流控,降级等规则。


四、实际项目分析-抢霸王餐

我们要知道设计一个高并发系统更多的是依赖于我们所在公司的架构,比如是否有分库分表(读写分离),是否使用CDN,是否是分布式服务,是否实现负载均衡等,然后加上我们一个好的架构设计,更大程度上的提高系统的并发量(流量肖峰,缓存预热);最后通过限流,降级,熔断保证服务的稳定性。

4.1 项目背景

前有淘宝“双十一”、京东“618”、小红书“66.过平台的影响力,来玩七点一刻抢霸王餐的活动,活动每晚七点一刻准时开始,手速最快的可以抢到霸王餐或赢免单机会,以此刺激消费者涌向我们平台,同时,分享活动给好友还可以提升中奖机会,刺激用户分享,为我们带来裂变式的传播;商家参与此活动,一来可以降低获客成本,为之带来私域流量,二来“赢免单机会”打的是心理战,可以促进消费。

4.2 项目预览

每晚进行直播,然后7.15进行抢霸王餐活动,领取现金红包。
1)创建霸王餐券
在这里插入图片描述
2)主播端
在这里插入图片描述

3)观众端
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

4.3 简要讲述一下设计要点

其实抢券和抢票,秒杀等都是同类问题。(抢券会少了个下单的过程)
在这里插入图片描述

在这里插入图片描述

  • Lvs负载均衡,nginx负载均衡【分流】
  • 券静态数据放CDN,动静分离
  • 缓存存放数据
    1.助力数据初始化:set(activityId+customerId,value)
    2.券总量:set(activityId,total)
    3.是否抢过:set(activityId+customerId,1)
    4.用户抢的频率:set(activityId+customerId,valueCount,expireTime)
  • 算法:(抽奖结束时间戳-抽奖时间戳)a% + 助力次数b% = 抽奖概率
  • 库存预减->发送抢券消息->计算中奖概率->库存扣减->记录领券信息

五、限流

5.1 Guava
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>19.0</version>
</dependency>
public class GuavaRateLimiter {
    public static void main(String[] args) {
        // 1.创建限流器,每秒5个令牌(令牌桶算法)
        RateLimiter limiter = RateLimiter.create(5);
        while (true){
            new Thread(()->{
                limiter.acquire();
                System.out.println("*****"+Thread.currentThread().getName());
            }).start();
        }
    }
}
5.2 信号量
public class SemaphoreTest {
    private static Semaphore semaphore = new Semaphore(5);
    private static ExecutorService executorService = Executors.newFixedThreadPool(20);

    public static void main(String[] args) {
        while (true){
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    System.out.println("*****"+Thread.currentThread().getName());
                    TimeUnit.MICROSECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            });
        }
    }
}
5.3 sentinel
<dependency>
   <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.7.1</version>
</dependency>
private static final String RESOURCE_NAME = "lxh";

// 限流规则 10个QPS/s
 private static void loadLimitingRules(){
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule();
    rule.setResource(RESOURCE_NAME);
    rule.setLimitApp("default");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setCount(10);
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}

private static void limiting(){
    Entry entry = null;
    try {
        entry = SphU.entry(RESOURCE_NAME);
        System.out.println("返回结果");
    }catch (BlockException e1){
        System.out.println("接口限流,返回为空");
    }
    catch (Exception e){
        e.printStackTrace();
    }finally {
        if (entry!=null){
            entry.exit();
        }
    }
}

六、降级

6.1 sentinel
// 异常数量大于4进行降级
private static void initDegradeRule() {
   List<DegradeRule> rules = new ArrayList<DegradeRule>();
   DegradeRule rule = new DegradeRule();
   rule.setResource(KEY);
   // set limit exception count to 4
   rule.setCount(4);
   rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
   rule.setTimeWindow(10); // 熔断窗口时间为10s
   rules.add(rule);
   DegradeRuleManager.loadRules(rules);
}

private static void limiting(){
    Entry entry = null;
    try {
        entry = SphU.entry(RESOURCE_NAME);
        System.out.println("返回结果");
        int i = 1/0;
    }catch (BlockException e1){
        System.out.println("接口降级,返回为空");
    }
    catch (Exception e){
        e.printStackTrace();
    }finally {
        if (entry!=null){
            entry.exit();
        }
    }
}

七、重试

<dependency>
   <groupId>com.github.rholder</groupId>
   <artifactId>guava-retrying</artifactId>
   <version>2.0.0</version>
</dependency>


public class RetryerTest {

    public static void main(String[] args) throws Exception {
        Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
                .retryIfExceptionOfType(Exception.class)
                .retryIfRuntimeException()
                .retryIfResult(Predicates.equalTo(false))
                // 尝试请求6次
                .withStopStrategy(StopStrategies.stopAfterAttempt(6))
                // 等待策略
                .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
                // 时间限制
                .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS))
                .withRetryListener(new RetryListener() {
                    @Override
                    public <V> void onRetry(Attempt<V> attempt) {
                        System.out.println("----------------start---------------");
                        System.out.println("重试次数:"+attempt.getAttemptNumber());
                        if (attempt.hasException()){
                            System.out.println("重试发生异常:" + attempt.getExceptionCause());
                        }
                        if (attempt.hasResult()){
                            System.out.println("结果:" + attempt.getResult());
                        }
                        System.out.println("-----------------end--------------");
                    }
                })
                .build();


        Boolean call = retryer.call(new Callable<Boolean>() {
            int times = 1;
            @Override
            public Boolean call() throws Exception {
                times++;
                String auditStatus = getAuditStatus();
                boolean result = "SUCESS".equals(auditStatus);
                if (times == 2) {
                    System.out.println("NullPointerException...");
                    throw new NullPointerException();
                } else if (times == 3) {
                    System.out.println("Exception...");
                    throw new Exception();
                } else if (times == 4) {
                    System.out.println("RuntimeException...");
                    throw new RuntimeException();
                } else if (times == 5) {
                    System.out.println("false...");
                    return result;
                } else {
                    System.out.println("true...");
                    return result;
                }
            }
        });
        System.out.println("结果:"+call);
    }

    private static String getAuditStatus(){
        return "SUCESS";
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章