美文网首页
秒杀系统学习

秒杀系统学习

作者: 抬头挺胸才算活着 | 来源:发表于2020-05-09 18:30 被阅读0次

    参考资料:
    [1]. 秒杀系统基础版
    [2]. 电商秒杀高级版

    使用数据库需要引入的库

    分别是MySQL对Jdbc的支持,阿里巴巴的连接池Druid
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.41</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.3</version>
    </dependency>
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.1</version>
    </dependency>

    层次

    模型

    异常处理

    一种处理方法是在Controller中定义方法ExceptionHandler,但是这种方法无法处理没有进入Controller的异常,比如404,这个时候可以用GlobalExceptionHandler.
    ExceptionHandler的使用如下,它会在当前Controller抛出异常的时候进入这里面进行处理,如果没有ExceptionHandler,异常会直接抛给Tomcat,@ResponseStatus(HttpStatus.OK)是为了HTTP返回200。

        //定义exceptionhandler解决未被controller层吸收的exception
        @ExceptionHandler(Exception.class)
        @ResponseStatus(HttpStatus.OK)
        @ResponseBody
        public Object handlerException(HttpServletRequest request, Exception ex){
            Map<String,Object> responseData = new HashMap<>();
            if( ex instanceof BusinessException){
                BusinessException businessException = (BusinessException)ex;
                responseData.put("errCode",businessException.getErrCode());
                responseData.put("errMsg",businessException.getErrMsg());
            }else{
                responseData.put("errCode", EmBusinessError.UNKNOWN_ERROR.getErrCode());
                responseData.put("errMsg",EmBusinessError.UNKNOWN_ERROR.getErrMsg());
            }
            return CommonReturnType.create(responseData,"fail");
    
        }
    

    订单号信息

    16位 = 8位时间信息(年月日)+中间6位(自增序列)+最后两位为分库分表位
    自增序列需要新建Sequence表,有currentValue和getStep两个字段,每次获取的时候要用for update锁定,然后再设置currentValue为currentValue+step。
    并且要解决两个问题:
    1、currentValue超过6位
    2、下单操作回滚的时候currentValue不应该回滚,不然增加到一定时候会重复,无法保证全局唯一性。
    解决第二个问题需要在修改读取自增序列的方法上的@Transactional的propagation设置为Propagation.REQUIRED_NEW,

    基础版之后的问题

    image.png

    如何发现容量问题?

    ps -ef | grep java 得到部署的线程数
    pstree -p xxx | wc -l 进程的线程数量
    top -H 任务管理器


    load average 表示的是CPU的负载,包含的信息不是CPU的使用率状况,而是在一段时间内CPU正在处理以及等待CPU处理的进程数之和的统计信息,也就是CPU使用队列的长度的统计信息。

    • 解决方案1——增加最大线程数



      调大max-threads=800(4核8G的经验值),min-spare-threads=100(突然增加请求的时候有用),TPS从40+增加到200+

    • 解决方案2——KeepAlive
      keepAliveTimeOut:多少毫秒后不响应的断开keepalive
      maxKeepAliveRequests:多少请求后keepalive断开失效
    @Component
    public class WebServerConfiguration implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
        @Override
        public void customize(ConfigurableWebServerFactory factory) {
            ((TomcatServletWebServerFactory)factory).addConnectorCustomizers(new TomcatConnectorCustomizer() {
                @Override
                public void customize(Connector connector) {
                    Http11NioProtocol protocolHandler = (Http11NioProtocol) connector.getProtocolHandler();
                    protocolHandler.setKeepAliveTimeout(30*1000);
                    protocolHandler.setMaxKeepAliveRequests(10000);
                    protocolHandler.setMaxConnections(5);
                }
            });
        }
    }
    

    并不是线程数量越多越好,太多会导致线程切换耗时太长。


    如何使得系统水平拓展

    • 解决方案1


    静态资源映射 服务器配置 反向代理配置

    数据库服务器:TPS=1500(跟前面比,带宽增加了),平均耗时500ms
    NGINX:两台后端服务器,TPS=1700

    • nginx与后端服务器要保持长连接
      整条链路都要保持长连接。


    nginx高性能的原因

    • epoll多路复用
      阻塞->select(循环遍历检查)->epoll


    • master worker进程模型

    • 协程
      一个进程对应一个协程,并发并且不用上下文切换。

    QPS和TPS

    QPS:Query Per Second
    TPS:Transactions Per Second
    一般的,评价系统性能均以每秒钟完成的技术交易的数量来衡量。系统整体处理能力取决于处理能力最低模块的TPS值。TPS指的是服务器每秒能够处理的事务数,而QPS是针对一个查询,服务器的每秒响应数

    nginx反向代理负载均衡(分布式拓展)

    • nginx作为web服务器
      web静态资源,css,js,html
    • nginx作为动静分离服务器
    • nginx作为反向代理服务器


    nginx怎么知道哪个时候反向代理,什么时候自己发送静态资源?

    路径

    查询性能优化之多级缓存

    • redis缓存
      单机模式VS哨兵(sentinal)模式:哨兵负责选出master和slave,用心跳机制监控master和slave,如果master挂掉了,将slave改为master,然后哨兵会通知java程序,master已经改变了,



    集群cluster模式:多台redis机器都可以互相感知到对方的存在,用户只需对其中一台进去ask即可获得全部的机器列表,省去管理集群的工作。

    redis存储的对象需要实现seariable接口

    • 本地热点缓存
      使用redis之后TPS=2000左右,平均耗时降低到200ms左右。
      使用本地缓存(guava)之后TPS=3000左右,平均耗时40ms左右。
      redis需要通过网络。
      热点数据(因为内存有限)
      脏读非常不敏感(如果数据修改的话,redis可以设置key失效)
      内存可控
      ConcurrentHashMap没有失效机制,加上用的是锁的机制,会影响并发性。
      Guava cache本质是一个HashMap,可控制大小和超时时间,可配置lru策略。
      缺点:不能更新,大小有限。

    nginx proxy cache缓存
    nginx lua缓存

    交易性能瓶颈

    jmeter压测:TPS=200,平均耗时500ms
    下单交易:6次数据库IO,减库存的时候有行锁
    加上redis缓存之后:TPS=1000,平时耗时600+


    • 优化库存行锁
    1. 给库存表的item_id增加索引
      这样就不会锁定整个表,只会锁定行

    2. 扣减库存缓存化
      解决:活动发布的时候将数据放到redis
      问题:数据库记录不一致(redis可能会奔溃)

    3. 异步同步数据库
      解决:用异步消息扣减数据库库存,rocketmq基于kafka进行开发的
      问题:异步消息发送失败,(接收消息后)扣减库存失败怎么处理,下单失败无法正确补回库存。

    4. 减缓存库存成功,但是后面订单入库失败无法将缓存和数据库的库存加回来
      解决:在下单事务最后面再发送异步消息
      问题:commit可能失败,那么库存还是会减掉

    解决:在事务成功提交后再发送减库存的异步消息
    问题:发送消息可能失败

    解决:TransactionMQProducer,跟数据库事务一样,成功才发送消息,失败了不发送消息。也就是消息还是会发送,但是是prepared状态,同时会执行整个创建订单的操作(可能会很久),然后返回对应的状态,失败的话,消息会被回滚。
    问题:下单状态可能未知

    解决:库存操作流水。数据库里面的数据分为主业务数据和操作型数据,库存操作流水就是一种操作型数据。数据库流水在开始下单之前设置为初始状态,下单的最后设置为完成状态,中间的时候,事务消息异步检查的时候可以得到数据库的状态。现在下订单的操作很多都移动到redis缓存中去了,除了订单入库,还有最后的库存操作流水,但是最后这个对于的行锁只是针对每个操作单独的行锁,性能并不能下降太多。
    问题:redis不可用,扣减流水

    1. redis不可用
      解决:设计原则为:宁可少买,不多卖。那么redis不可用的时候,不能追溯到数据库,因为数据库的减库存是异步的,可能之前的还没有减掉。宁愿redis比数据库中的数据少。

    2. 售罄:一开始就下了数据库流水,如果这个时候还没已经没货了,会有很大数据库流水。
      解决:用redis设置一个key-value设置售罄标志。

    3. 销量逻辑异步化,交易单逻辑异步化(减库存成功后直接将后面的操作异步)

    1. 库存数据库最终一致性保证

    页面静态化

    活动缓存库存

    每次下单的时候减少的操作是发生在redis缓存,同时用消息中间件rocketmq进行MySQL数据库的减少,不如redis崩了,数据就没了。

    rocketmq角色

    生产者:产生消息放到消息队列。
    消费者:获取消息队列的消息。
    消费组:对应一个主题,多台主机共同负责某个主题的消息的消费,每条消息只能被其中一台主机消费。不同的消费组可以共同且不互斥地消费同一个主题的消息。
    NameServer跟zookeeper一样,用来做生产者,消费者,消息队列的中介,负责注册和发现,还有消费队列的master的选举。
    Broker:一个主题的机器,对应多个队列,但是可能会有多个备份(slave),slave只备份,不发送消息。

    分布式事务的可用性和一致性

    两个是矛盾,要保证一致性,那么只能稍微牺牲可用性,比如等数据完全同步后再开放使用。现实中,我们肯定要一致性,但是我们先保证可用性,一致性稍微完全同步,最后完全同步。

    流量削峰

    相关文章

      网友评论

          本文标题:秒杀系统学习

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