秒杀项目笔记

第二章,项目回归

类图

ItemController: 商品相关Controller

OrderController:交易相关Controller

UserController:用户相关Controller

OrderService:下单交易

UserService:获取用户信息、注册、登录

ItemService:创建商品、商品列表浏览、商品详情浏览、商品减库存、商品加销量

PromoService:获取秒杀活动商品信息

PromoDoMapper:秒杀活动数据表操作

ItemDoMapper:商品数据操作

ItemStockDoMapper:库存数据表操作

OrderDoMapper:订单数据表操作

UserDoMapper:用户数据表操作

UserPasswordMapper:用户密码操作

第三章 云端部署,修改Server配置

  1. 本地在项目根目录下使用mvn clean package 打包生成miaosha.jar文件

  2. 将jar包上传到服务端并编写额外的application.properties配置文件

  3. 编写deploy.sh文件启动对应的项目 java命令启动,设置JVM初始和最大内存为2048m,2个g大小,设置JVM初始新生代和最大新生代大小为1024m,设置成一样的目的是为了减少扩展jvm内存过程中向操作系统索要内存分配的消耗,

  4. -spring config addition-location=指定额外的配置文件地址

    nohub java -Xms2048m -Xmx2048m -XX:NewSize=1024m -XX:MaxNewSize=1024m -jar miaosha.jar --spring.config.addition-location=/var/www/miaosha/application.properties

Spring-configuration-metadata.json

server.tomcat.accept-count:等待队列长度,默认100
server.tomcat.max-connections:最大可被连接数,默认10000
server.tomcat.max-threads:最大工作线程数,默认200
server.min-spare-threads:最小工作线程数,默认是10
# 默认配置下,请求超过1000后出现拒绝连接情况
# 默认配置下,触发的请求超过200+100后拒绝连接 最大工作线程数+等待队列的长度
# 修改默认参数值
4核8G机器
server.tomcat.max-threads:800
server.min-spare-threads:100

keepAliveTimeOut:多少毫秒后不响应的断开Keepalive

maxKeepAliveRequests:多少次请求后Keepalive断开失效

定制化:使用WebServerFactoryCunstomizer 定制化内嵌Tomcat

//当Spring容器内没有TomcatEmbeddedServletContainerFactory这个bean时,会吧此bean加载进spring容器中
@Component
public class WebServerConfiguration implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
    @Override
    public void customize(ConfigurableWebServerFactory configurableWebServerFactory) {
            //使用对应工厂类提供给我们的接口定制化我们的tomcat connector
        ((TomcatServletWebServerFactory)configurableWebServerFactory).addConnectorCustomizers(new TomcatConnectorCustomizer() {
            @Override
            public void customize(Connector connector) {
                Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();

                //定制化keepalivetimeout,设置30秒内没有请求则服务端自动断开keepalive链接
                protocol.setKeepAliveTimeout(30000);
                //当客户端发送超过10000个请求则自动断开keepalive链接
                protocol.setMaxKeepAliveRequests(10000);
            }
        });
    }
}

第四章,单机容量问题,水平扩展方案引入

  • 表象:单机cpu使用率增高,memory占用增加,网路带宽使用增加

  • cpu us:用户空间的cpu使用情况(用户层代码)

  • Cpu sy:内核空间的cpu使用情况(系统调用)

  • Load average:1.5,15分钟load平均值,跟这核数增加,0代表通常,1代表打满,1+代表等待阻塞

  • memory:free空闲内存,used使用内存

nginx 动静分离设置

会话管理
  • 基于token传输类似sessionid:java代码session实现迁移到redis
  String uuidToken = UUID.randomUUID().toString();
  uuidToken = uuidToken.replace("-","");
  //建立token和用户登陆态之间的联系
  redisTemplate.opsForValue().set(uuidToken,userModel);
  //设置超时时间 1hour
  redisTemplate.expire(uuidToken,1, TimeUnit.HOURS);
  //下发了token
  return CommonReturnType.create(uuidToken);

前端代码存储uuidToken

Login.html

$.ajax({
                type:"POST",
                contentType:"application/x-www-form-urlencoded",
                url:"http://"+g_host+"/user/login",
                data:{
                    "telphone":$("#telphone").val(),
                    "password":password
                },
                xhrFields:{withCredentials:true},
                success:function(data){
                    if(data.status == "success"){
                        alert("登陆成功");
                        var token = data.data;
            //存储token
                        window.localStorage["token"] = token;
                        //从定向到listitem.html
            window.location.href="listitem.html";
                    }else{
                        alert("登陆失败,原因为"+data.data.errMsg);
                    }
                },
                error:function(data){
                    alert("登陆失败,原因为"+data.responseText);
                }
            });
            return false;
        });

getItem.html

var token = window.localStorage["token"];
            $.ajax({
                type:"POST",
                contentType:"application/x-www-form-urlencoded",
                url:"http://"+g_host+"/order/generatetoken?token="+token,
                data:{
                    "itemId":g_itemVO.id,
                    "promoId":g_itemVO.promoId,
                    "verifyCode":$("#verifyContent").val()
                },

OrderController.java--> createOrder Method

String token = httpServletRequest.getParameterMap().get("token")[0];
if(StringUtils.isEmpty(token)){
  throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"用户还未登陆,不能下单");
}
//获取用户的登陆信息
UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);
if(userModel == null){
  throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"用户还未登陆,不能下单");
}
  • 基于cookie传输的session:java tomcat容器session的实现

    // 1.引入依赖 
    <!--引入依赖-->
     <dependency>
          <groupId>org.springframework.session</groupId>
          <artifactId>spring-session-data-redis</artifactId>
          <version>2.0.5.RELEASE</version>
     </dependency>
    
     // 2.修改Redis配置文件 保存到的Session 过期时间
     @Component
     @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
     //3.保存登录状态
    public class RedisConfig {
    }
     this.httpServletRequest.getSession().setAttribute("IS_LOGIN",true);
     this.httpServletRequest.getSession().setAttribute("LOGIN_USER",userModel);

缺点:企业级应用,不光支持html,还要支持Android、IOS 网络情况下cookie的规则会不会改变,还有Cookie被客户端禁用的可能。

第五章 查询优化技术之多级优化

缓存设计

  • 用快速缓存设备,用内存
  • 将缓存推到离用户最近的地方
  • 脏缓存清理(关键型数据必须存储在数据库中,将查询的热点数据放入缓存,注意一致性问题)

多集缓存

  • redis缓存<单机版、Sentinal哨兵模式、集群Cluster模式>
  • 热点内存本地缓存
  • nginx proxy cache缓存
  • nginx lua 缓存

商品详情动态内容实现。

ItemController --->getItem()

//商品详情页浏览
    @RequestMapping(value = "/get",method = {RequestMethod.GET})
    @ResponseBody
    public CommonReturnType getItem(@RequestParam(name = "id")Integer id){
        ItemModel itemModel = null;

           //根据商品的id到redis内获取
          itemModel = (ItemModel) redisTemplate.opsForValue().get("item_"+id);
        //若redis内不存在对应的itemModel,则访问下游service
        if(itemModel == null){
          itemModel = itemService.getItemById(id);
          //设置itemModel到redis内
          redisTemplate.opsForValue().set("item_"+id,itemModel);
          redisTemplate.expire("item_"+id,10, TimeUnit.MINUTES);
        }

        ItemVO itemVO = convertVOFromModel(itemModel);

        return CommonReturnType.create(itemVO);

    }

本地热点缓存<VM虚拟机堆栈2G设置就是为了使用对象管理区间>

用于存放热点数据、脏读非常不敏感、内存可控

Guava Cache 类似于HashMap,可空值的大小和超时时间、可以配置lru清除策略、线程安全的

Map<Integer、ItemModel>

可以支持并发读写的hashMap、想到currentHashMap是基于段的处理方式去加速,在处理Put时写锁加上会对读锁性能有影响,而且可以设置key过期时间。

// 1.maven引入guava
<!--引入guava-->
<dependency>
     <groupId>com.google.guava</groupId>
     <artifactId>guava</artifactId>
     <version>18.0</version>
</dependency>
 //2.封装CacheService,完成对象的存储
 //3.编写CacheService
 //封装本地缓存操作类
public interface CacheService {
    //存方法
    void setCommonCache(String key,Object value);

    //取方法
    Object getFromCommonCache(String key);
}
//4.编写CacheServiceImpl
@Service
public class CacheServiceImpl implements CacheService {

    private Cache<String,Object> commonCache = null;

    @PostConstruct
    public void init(){
        commonCache = CacheBuilder.newBuilder()
                //设置缓存容器的初始容量为10
                .initialCapacity(10)
                //设置缓存中最大可以存储100个KEY,超过100个之后会按照LRU的策略移除缓存项
                .maximumSize(100)
                //设置写缓存后多少秒过期
                .expireAfterWrite(60, TimeUnit.SECONDS).build();
    }

    @Override
    public void setCommonCache(String key, Object value) {
            commonCache.put(key,value);
    }

    @Override
    public Object getFromCommonCache(String key) {
        return commonCache.getIfPresent(key);
    }
}
//5.改造ItemService
//商品详情页浏览
    @RequestMapping(value = "/get",method = {RequestMethod.GET})
    @ResponseBody
    public CommonReturnType getItem(@RequestParam(name = "id")Integer id){
        ItemModel itemModel = null;

        //先取本地缓存
        itemModel = (ItemModel) cacheService.getFromCommonCache("item_"+id);

        if(itemModel == null){
            //根据商品的id到redis内获取
            itemModel = (ItemModel) redisTemplate.opsForValue().get("item_"+id);

            //若redis内不存在对应的itemModel,则访问下游service
            if(itemModel == null){
                itemModel = itemService.getItemById(id);
                //设置itemModel到redis内
                redisTemplate.opsForValue().set("item_"+id,itemModel);
                redisTemplate.expire("item_"+id,10, TimeUnit.MINUTES);
            }
            //填充本地缓存
            cacheService.setCommonCache("item_"+id,itemModel);
        }


        ItemVO itemVO = convertVOFromModel(itemModel);

        return CommonReturnType.create(itemVO);

    }

缺点:当数据更新时、本地热点缓存无能为力而且也有容量上的问题

nginx proxy cache缓存<有这个方案,但是提升不明显,弃用>

  • nginx反向***前置
  • 依靠文件系统存索引级的文件
  • 依靠内存缓存文件地址

nginx lua

第六章 查询优化技术之页面静态化[动态请求加静态页面静态化]

第七章 交易优化技术之缓存库存[用缓存解决交易问题]

交易系统性能瓶颈

1.交易校验操作完全依赖数据库<发送了6次sql>

2.落单减库存操作

<update id="decreaseStock">
  update item_stock
  set stock = stock - #{amount}
  where item_id = #{itemId} and stock >= #{amount} 有数据库行锁等待
</update>
  • 用户风控策略优化:策略缓存模型化
//1.通过缓存获取item模型 ItemService -->getItemByIdInCache()
@Override
public ItemModel getItemByIdInCache(Integer id) {
  ItemModel itemModel = (ItemModel) redisTemplate.opsForValue().get("item_validate_"+id);
  if(itemModel == null){
    itemModel = this.getItemById(id);
    redisTemplate.opsForValue().set("item_validate_"+id,itemModel);
    redisTemplate.expire("item_validate_"+id,10, TimeUnit.MINUTES);
  }
  return itemModel;
}
//2.通过缓存获取user模型 UserService -->getUserByIdInCache()
@Override
public UserModel getUserByIdInCache(Integer id) {
  UserModel userModel = (UserModel) redisTemplate.opsForValue().get("user_validate_"+id);
  if(userModel == null){
    userModel = this.getUserById(id);
    redisTemplate.opsForValue().set("user_validate_"+id,userModel);
    redisTemplate.expire("user_validate_"+id,10, TimeUnit.MINUTES);
  }
  return userModel;
}
//2.修改下单流程(直接查询数据库--->先查询缓存,在查询数据库)
//3.校验下单状态,下单的商品是否存在,用户是否合法,购买数量是否正确

//ItemModel itemModel = itemService.getItemById(itemId);
ItemModel itemModel = itemService.getItemByIdInCache(itemId);
if(itemModel == null){
      throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"商品信息不存在");
  }


        UserModel userModel = userService.getUserByIdInCache(userId);
       if(userModel == null){
                 throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"用户信息不存在");
        }
  • 活动校验策略优化:引入活动发布流程,模型缓存化,紧急下线能力

库存行锁优化

alter table item_stock add unique index item_id_index(itemid)
  • 扣减库存缓存化

    1. 活动发布同步库存进库存

      //1.活动发布 PromoService
      public void publishPromo(Integer promoId) {
        //通过活动id获取活动
        PromoDO promoDO = promoDOMapper.selectByPrimaryKey(promoId);
        //验证活动是否存在
        if(promoDO.getItemId() == null || promoDO.getItemId().intValue() == 0){
          return;
        }
        // TODO 上下架商品操作... 
        ItemModel itemModel = itemService.getItemById(promoDO.getItemId());
      
        //将库存同步到redis内
        redisTemplate.opsForValue().set("promo_item_stock_"+itemModel.getId(), itemModel.getStock());
      }
      
  1. 下单交易减缓存库存<数据库记录不一致>

    @Transactional
     public boolean decreaseStock(Integer itemId, Integer amount) throws BusinessException {
         //int affectedRow =  itemStockDOMapper.decreaseStock(itemId,amount);
         long result = redisTemplate.opsForValue().increment("promo_item_stock_"+itemId,amount.intValue() * -1);
         if(result >0){
             //更新库存成功
             return true;
         }else if(result == 0){
             //打上库存已售罄的标识
             redisTemplate.opsForValue().set("promo_item_stock_invalid_"+itemId,"true");
    
             //更新库存成功
             return true;
         }else{
             //更新库存失败
             increaseStock(itemId,amount);
             return false;
         }
    
     }
  2. 异步消息扣减数据库内库存

    异步消息队列rocketmq基于kafka改造的中间件,重试消息队列、延迟消息队列。

  3. 引入maven依赖

    <dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.3.0</version>
    </dependency>
  4. Application.properties

    mq.nameserver.addr=115.28.59.132:9876
    mq.topicname=stock
  5. MQProducer

    // 1.MQ初始化
    @PostConstruct
        public void init() throws MQClientException {
            //做mq producer的初始化
            producer = new DefaultMQProducer("producer_group");
            producer.setNamesrvAddr(nameAddr);
            producer.start();
     }
    //2.同步扣减库存消息
     //同步库存扣减消息
        public boolean asyncReduceStock(Integer itemId,Integer amount)  {
            Map<String,Object> bodyMap = new HashMap<>();
            bodyMap.put("itemId",itemId);
            bodyMap.put("amount",amount);
    
            Message message = new Message(topicName,"increase",
                    JSON.toJSON(bodyMap).toString().getBytes(Charset.forName("UTF-8")));
            try {
                producer.send(message);
            } catch (MQClientException e) {
                e.printStackTrace();
                return false;
            } catch (RemotingException e) {
                e.printStackTrace();
                return false;
            } catch (MQBrokerException e) {
                e.printStackTrace();
                return false;
            } catch (InterruptedException e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }
  1. MQConsumer

    @Component
    public class MqConsumer {
    
     private DefaultMQPushConsumer consumer;
     @Value("${mq.nameserver.addr}")
     private String nameAddr;
    
     @Value("${mq.topicname}")
     private String topicName;
    
     @Autowired
     private ItemStockDOMapper itemStockDOMapper;
    
     @PostConstruct
     public void init() throws MQClientException {
         consumer = new DefaultMQPushConsumer("stock_consumer_group");
         consumer.setNamesrvAddr(nameAddr);
         consumer.subscribe(topicName,"*");
    
         consumer.registerMessageListener(new MessageListenerConcurrently() {
             @Override
             public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                 //实现库存真正到数据库内扣减的逻辑
                 Message msg = msgs.get(0);
                 String jsonString  = new String(msg.getBody());
                 Map<String,Object>map = JSON.parseObject(jsonString, Map.class);
                 Integer itemId = (Integer) map.get("itemId");
                 Integer amount = (Integer) map.get("amount");
    
                 itemStockDOMapper.decreaseStock(itemId,amount);
                 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
             }
         });
    
         consumer.start();
    
     }
  • 异步同步数据库的问题

    1. 异步消息发送失败
    2. 扣减操作执行失败
    3. 下单失败无法正确回补库存

第八章 交易优化技术之事务型消息[保证最终一致性的利器]

  • 操作流水(stock_log表)
create table `stock_log`(
    `stock_log_id` varchar(64) not null primary key,
    `item_id` int(11) not null,
      `amount` int(11),
    `status` tiny int comment '1表示系统初始状态,2代表下单扣库存成功,3代表下单回滚'
)

初始化stock_log

1.下单之前加入库存流水Init状态
//初始化对应的库存流水
    @Override
    @Transactional
    public String initStockLog(Integer itemId, Integer amount) {
        StockLogDO stockLogDO = new StockLogDO();
        stockLogDO.setItemId(itemId);
        stockLogDO.setAmount(amount);
        stockLogDO.setStockLogId(UUID.randomUUID().toString().replace("-",""));
        stockLogDO.setStatus(1);
        stockLogDOMapper.insertSelective(stockLogDO);
        return stockLogDO.getStockLogId();

    }

LocalTransactionState的三种状态COMMIT_MESSAGEROLLBACK_MESSAGEUNKNOW

rocket mq提供的TransactionMQProducer API 执行流程:

  1. 先发送需要发送的消息到消息中间件broker,并获取到该message的transactionId。在第一次发送的时候,该消息的状态为LocalTransactionState.UNKNOW
  2. 处理本地事物。
  3. 根据本地事物的执行结果,结合transactionId,找到该消息的位置,在mq中标志该消息的最终处理结果。

上述:如果第三阶段出现异常或者网络原因,就是本地事务执行成功持久化到数据库中,但是在修改mq中消息状态出现异常的时候,这样就可以出现本地和mq的消息状态的不一致问题。或者说,所有的数据不一致问题。rocketmq都会定期通过TransactionMQProducer API初始化的时候,设置的TransactionCheckListener的的实现类的checkLocalTransactionState 方法检查本地消息的状态,根据本地状态修改mq的状态

第九章 流量削峰计数[削峰填谷之神操作]

  • 掌握秒杀令牌的原理和使用方式
  • 掌握秒杀大闸的原理和使用方式
  • 掌握队列泄洪的原理和使用方式
抛缺陷
  • 秒杀下单接口会被脚本不停的刷
  • 秒杀验证逻辑和秒杀下单接口强关联,代码冗余度高
  • 秒杀验证逻辑复杂,对交易系统产生无关联负载
秒杀令牌原理
  • 秒杀接口需要依靠令牌才能进入
  • 秒杀的令牌由秒杀活动模块负责生成
  • 秒杀活动模块对秒杀令牌生成全权处理,逻辑收口
  • 秒杀下单前需要先获得秒杀令牌
// 1.管理令牌生成
PromoService --> generateToken
//生成token并且存入redis内并给一个5分钟的有效期
//判断当前时间是否秒杀活动即将开始或正在进行
  if(promoModel.getStartDate().isAfterNow()){
    promoModel.setStatus(1);
  }else if(promoModel.getEndDate().isBeforeNow()){
    promoModel.setStatus(3);
  }else{
    promoModel.setStatus(2);
  }
//判断活动是否正在进行
if(promoModel.getStatus().intValue() != 2){
  return null;
}
//判断item信息是否存在
ItemModel itemModel = itemService.getItemByIdInCache(itemId);
if(itemModel == null){
  return null;
}
//判断用户信息是否存在
UserModel userModel = userService.getUserByIdInCache(userId);
if(userModel == null){
  return null;
}
String token = UUID.randomUUID().toString().replace("-","");

        redisTemplate.opsForValue().set("promo_token_"+promoId+"_userid_"+userId+"_itemid_"+itemId,token);
        redisTemplate.expire("promo_token_"+promoId+"_userid_"+userId+"_itemid_"+itemId,5, TimeUnit.MINUTES);
//生成秒杀令牌
    @RequestMapping(value = "/generatetoken",method = {RequestMethod.POST},consumes={CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType generatetoken(@RequestParam(name="itemId")Integer itemId,
                                        @RequestParam(name="promoId")Integer promoId,
                                          @RequestParam(name="verifyCode")String verifyCode) throws BusinessException {
        //根据token获取用户信息
        String token = httpServletRequest.getParameterMap().get("token")[0];
        if(StringUtils.isEmpty(token)){
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"用户还未登陆,不能下单");
        }
        //获取用户的登陆信息
        UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);
        if(userModel == null){
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"用户还未登陆,不能下单");
        }

        //通过verifycode验证验证码的有效性
        String redisVerifyCode = (String) redisTemplate.opsForValue().get("verify_code_"+userModel.getId());
        if(StringUtils.isEmpty(redisVerifyCode)){
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"请求非法");
        }
        if(!redisVerifyCode.equalsIgnoreCase(verifyCode)){
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"请求非法,验证码错误");
        }

        //获取秒杀访问令牌
        String promoToken = promoService.generateSecondKillToken(promoId,itemId,userModel.getId());

        if(promoToken == null){
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"生成令牌失败");
        }
        //返回对应的结果
        return CommonReturnType.create(promoToken);
    }
秒杀大闸
  • 依靠秒杀令牌的授权原理定制化发牌逻辑,做到大闸功能
  • 根据秒杀商品初始化库存颁发对应数量令牌,控制大闸数量<库存数量*5发放令牌>
  • 用户风控策略前置到秒杀令牌发放
  • 库存售罄判断前置到秒杀令牌发放中
抛出缺陷
  • 浪涌流量涌入后系统无法应对
  • 多库存,多商品等令牌限制能力弱

方案:队列泄洪策略

  1. 排队有时候比并发更加高效(例如redis单线程模型,innodb mutex key等)
  2. 依靠排队去限制并发流量
  3. 依靠排队和下游拥塞窗口程度调整队列释放流量大小

Redis为什么快?

  • 内存级别数据库,

  • 单线程操作,不会有线程内上下文内存上的开销

CreateOrder()
 private ExecutorService executorService;
@PostConstruct
    public void init(){
        executorService = Executors.newFixedThreadPool(20);
    }

        //同步调用线程池的submit方法
        //拥塞窗口为20的等待队列,用来队列化泄洪
        Future<Object> future = executorService.submit(new Callable<Object>() {

            @Override
            public Object call() throws Exception {
                //加入库存流水init状态
                String stockLogId = itemService.initStockLog(itemId,amount);


                //再去完成对应的下单事务型消息机制
                if(!mqProducer.transactionAsyncReduceStock(userModel.getId(),itemId,promoId,amount,stockLogId)){
                    throw new BusinessException(EmBusinessError.UNKNOWN_ERROR,"下单失败");
                }
                return null;
            }
        });
本地 or 分布式
  • 本地:将队列维护在本地内存中<JVM中,性能和高可用,缺点负载可能不均衡>
  • 分布式:将队列设置到外部redis内<性能问题:发送任何请求都要发送网络IO、而且还有单点问题>

第十章 防刷限流计数[保护系统,避免过载]

  • 掌握验证码生成与验证技术
  • 掌握限流原理与实现
  • 掌握防黄牛技术
验证码
  • 包装秒杀令牌前置,需要验证码来错峰<使用户流量错峰>
限流目的
  • 限流方案
  1. 限并发
  2. 令牌桶算法<应对突发流量>
  3. 漏桶算法<以固定的速率流入网络>
  • 限流力度
  1. 接口维度
  2. 总维度
  • 限流范围
  1. 集群限流:依赖redis或者其他的技术做统一计数器,往往会产生性能瓶颈
  2. 单机限流:负载均衡的前提下单机平均限流效果更好

限流代码实现:

Guava RateLimit

 // orderController
 private RateLimiter orderCreateRateLimiter;
 if(order)
全部评论

相关推荐

04-06 11:24
已编辑
太原学院 C++
真烦好烦真烦:感觉不太对劲,这种主动加微信的一般都是坑,要小心辨别
点赞 评论 收藏
分享
牛客773130651号:巨佬,简历模板换成上下的,左右的很烦,hr看着不爽。。。科大随便乱杀,建议能保研就保研,不行也得考一下 ,985硕去干算法,比开发强多了。开发许多双非都能搞,学历优势用不上,算法有门槛
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务