秒杀高并发解决方案
秒杀高并发解决方案
1.秒杀/高并发方案-介绍
- 秒杀/高并发 其实主要解决两个问题,一个是并发读,一个是并发写
- 并发读的核心优化理念是尽量减少用户到 DB 来"读"数据,或者让他们读更少的数据, 并
发写的处理原则也一样 - 针对秒杀系统需要做一些保护,针对意料之外的情况设计兜底方案,以防止最坏的情况
发生。 - 系统架构要满足高可用: 流量符合预期时要稳定,要保证秒杀活动顺利完成,即秒杀商品
顺利地卖出去,这个是最基本的前提 - 系统保证数据的一致性: 就是秒杀 10 个 商品 ,那就只能成交 10 个商品,多一个少一
个都不行。一旦库存不对,就要承担损失 - 系统要满足高性能: 也就是系统的性能要足够高,需要支撑大流量, 不光是服务端要做极
致的性能优化,而且在整个请求链路上都要做协同的优化,每个地方快一点, 整个系统就"快
"了 - 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键, 对应的方案比如页
面缓存方案、Redis 预减库存/内存标记与隔离、请求的削峰(RabbitMQ/异步请求)、分布式
Session 共享等
2.秒杀场景模拟
这里模拟每秒1000个请求,循环10次即总共发起10000次请求,请求/seckill/doSeckill
接口
3.秒杀解决方案
3.1 v1.0-原始版本
SeckillController
@Controller
@RequestMapping(value = "/seckill")
public class SeckillController {@Resourceprivate GoodsService goodsService;@Resourceprivate SeckillOrderService seckillOrderService;@Resourceprivate OrderService orderService;@RequestMapping(value = "/doSeckill")public String doSeckill(Model model, User user, Long goodsId) {System.out.println("-----秒杀 V1.0--------");//===================秒杀 v1.0 start =========================if (user == null) {return "login";}model.addAttribute("user", user);GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);//判断库存if (goodsVo.getStockCount() < 1) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//解决重复抢购LambdaQueryWrapper<SeckillOrder> seckillOrderLambdaQueryWrapper = new QueryWrapper<SeckillOrder>().lambda().eq(SeckillOrder::getGoodsId, goodsId).eq(SeckillOrder::getUserId, user.getId());SeckillOrder seckillOrder = seckillOrderService.getOne(seckillOrderLambdaQueryWrapper);if (seckillOrder != null) {model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//抢购Order order = orderService.seckill(user, goodsVo);if (order == null) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}model.addAttribute("order", order);model.addAttribute("goods", goodsVo);return "orderDetail";//===================秒杀 v1.0 end... =========================}}
OrderServiceImpl
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {@Resourceprivate SecKillGoodsService secKillGoodsService;@Resourceprivate OrderMapper orderMapper;@Resourceprivate SeckillOrderMapper seckillOrderMapper;/*** 秒杀商品,减少库存* @param user* @param goodsVo* @return*/@Overridepublic Order seckill(User user, GoodsVo goodsVo) {//1.根据商品id获取秒杀商品信息LambdaQueryWrapper<SecKillGoods> lambdaQueryWrapper = new QueryWrapper<SecKillGoods>().lambda().eq(SecKillGoods::getGoodsId,goodsVo.getId());SecKillGoods seckillGoods = secKillGoodsService.getOne(lambdaQueryWrapper);//2.秒杀商品库存-1seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);secKillGoodsService.updateById(seckillGoods);//3.生成普通订单Order order = new Order();order.setUserId(user.getId());order.setGoodsId(goodsVo.getId());order.setDeliveryAddrId(0L);order.setGoodsName(goodsVo.getGoodsName());order.setGoodsCount(1);order.setGoodsPrice(seckillGoods.getSeckillPrice());order.setOrderChannel(1);order.setStatus(0);order.setCreateDate(new Date());orderMapper.insert(order);//4.生成秒杀订单SeckillOrder seckillOrder = new SeckillOrder();seckillOrder.setGoodsId(goodsVo.getId());seckillOrder.setUserId(user.getId());seckillOrder.setOrderId(order.getId());seckillOrderMapper.insert(seckillOrder);return order;}
}
说明
非高并发情况下,程序执行流程分析
-
程序首先校验用户是否登录
-
然后判断商品库存是充足
-
在判断是否存在重复抢购
-
最后执行抢购生成订单及抢购订单数据
高并发情况下,程序执行流程分析
- 判断商品库存是充足、否存在重复抢购的程序是不具备原子性
- 秒杀商品的库存为负数,但具体的值无法确定,因为获取秒杀商品库存也不具备原子性
- 当有多少个请求冲破了前面库存和抢购的过滤,就会去生成多少个订单
3.2 v2.0-秒杀-复购、超卖处理
SeckillController
@RequestMapping(value = "/doSeckill")
public String doSeckill(Model model, User user, Long goodsId) {System.out.println("-----秒杀 V2.0--------");//===================秒杀 v2.0 start =========================if (user == null) {return "login";}model.addAttribute("user", user);GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);//判断库存if (goodsVo.getStockCount() < 1) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//解决重复抢购,直接到redis中获取对应的秒杀订单,如果有则说明已经抢购了,不能继续抢购SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);if (seckillOrder != null) {model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//抢购Order order = orderService.seckill(user, goodsVo);if (order == null) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}model.addAttribute("order", order);model.addAttribute("goods", goodsVo);return "orderDetail";//===================秒杀 v2.0 end... =========================
}
OrderServiceImpl
/*** 秒杀商品,减少库存** @param user* @param goodsVo* @return*/
@Override
@Transactional(rollbackFor = {Exception.class})
public Order seckill(User user, GoodsVo goodsVo) {//1.根据商品id获取秒杀商品信息LambdaQueryWrapper<SecKillGoods> lambdaQueryWrapper = new QueryWrapper<SecKillGoods>().lambda().eq(SecKillGoods::getGoodsId, goodsVo.getId());SecKillGoods seckillGoods = secKillGoodsService.getOne(lambdaQueryWrapper);//2.秒杀商品库存-1// seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);// secKillGoodsService.updateById(seckillGoods);//1. Mysql在默认的事务隔离级别[REPEATABLE-READ]下//2. 执行update语句时,会在事务中锁定要更新的行//3. 这样可以防止其它会话在同一行执行update,deleteLambdaUpdateWrapper<SecKillGoods> updateWrapper = new UpdateWrapper<SecKillGoods>().lambda().setSql("stock_count=stock_count-1").eq(SecKillGoods::getGoodsId, goodsVo.getId()).gt(SecKillGoods::getStockCount, 0);boolean update = secKillGoodsService.update(updateWrapper);//如果更新失败说明已经没有库存了if (!update) {return null;}//3.生成普通订单Order order = new Order();order.setUserId(user.getId());order.setGoodsId(goodsVo.getId());order.setDeliveryAddrId(0L);order.setGoodsName(goodsVo.getGoodsName());order.setGoodsCount(1);order.setGoodsPrice(seckillGoods.getSeckillPrice());order.setOrderChannel(1);order.setStatus(0);order.setCreateDate(new Date());orderMapper.insert(order);//4.生成秒杀订单SeckillOrder seckillOrder = new SeckillOrder();seckillOrder.setGoodsId(goodsVo.getId());seckillOrder.setUserId(user.getId());seckillOrder.setOrderId(order.getId());seckillOrderMapper.insert(seckillOrder);//设置秒杀订单的key --> order:用户id:商品idredisTemplate.opsForValue().set("order:" + user.getId() + ":" + goodsVo.getId(), seckillOrder);return order;
}
说明
超卖问题处理
前面出现超卖的主要原因在于,更新库存时不具备原子性,这里利用mysql默认的事务隔离级别
[REPEATABLE-READ]
在事务中执行update语句获取锁定更新的行,这个机制进行处理
复购(生成了多个订单)问题处理
,则在生成订单之前对库存更新进行判断,如果更新失败则不在生成订单
//如果更新失败说明已经没有库存了if (!update) {return null;}
优化复购判断
在生成秒杀订单之后将订单存储到redis中,在判断复购时直接从redis中获取,避免频繁查询数据库,提升执行效率
//解决重复抢购,直接到redis中获取对应的秒杀订单,如果有则说明已经抢购了,不能继续抢购SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);if (seckillOrder != null) {model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}
测试结果
3.3 v3.0 优化秒杀,Redis 预减库存
- 前面我们防止超卖 是通过到数据库查询和到数据库抢购,来完成的, 代码如下:
- 如果在短时间内,大量抢购冲击 DB, 造成洪峰, 容易压垮数据库
- 解决方案: 使用 Redis 完成预减库存,如果没有库存了, 直接返回, 减小对 DB 的压力
- 示意图:
代码实现
@Controller
@RequestMapping(value = "/seckill")
public class SeckillController implements InitializingBean {@Resourceprivate GoodsService goodsService;@Resourceprivate SeckillOrderService seckillOrderService;@Resourceprivate OrderService orderService;@Resourceprivate RedisTemplate redisTemplate;@RequestMapping(value = "/doSeckill")public String doSeckill(Model model, User user, Long goodsId) {System.out.println("-----秒杀 V3.0--------");//===================秒杀 v3.0 start =========================if (user == null) {return "login";}model.addAttribute("user", user);GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);//判断库存if (goodsVo.getStockCount() < 1) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//解决重复抢购,直接到redis中获取对应的秒杀订单,如果有则说明已经抢购了,不能继续抢购SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);if (seckillOrder != null) {model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//库存预减,如果在redis中预减库存,发现秒杀商品中已经没有库存了,直接返回//从而减少这个方法请求 orderService.seckill防止大量请求打到数据,优化秒杀//这个方法具有原子性!!!Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);//说明这个商品已经没有库存了if (decrement < 0) {//恢复redis库存为0,对业务没有影响只是为了好看redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//抢购Order order = orderService.seckill(user, goodsVo);if (order == null) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}model.addAttribute("order", order);model.addAttribute("goods", goodsVo);return "orderDetail";//===================秒杀 v3.0 end... =========================}//该方法实在类的所有属性都初始化后,自动执行//这里将所有秒杀商品的库存量加载到redis中@Overridepublic void afterPropertiesSet() throws Exception {//1.查询所有的秒杀商品List<GoodsVo> list = goodsService.findGoodsVo();if (CollectionUtils.isEmpty(list)) {return;}//遍历list,将秒杀商品的库存量放入到redis中//秒杀商品库存量对应的key seckillGoods:商品idfor (GoodsVo goodsVo : list) {redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());}}}
大体思路:
-
项目启动时加载秒杀商品库存到redis中,库存量随着每次启动都会更新
-
在进行抢购之前先到redis中进行库存预减,利用
redis的decrement具备原子性的特点
-
如果redis预减库存小于0则直接返回,避免了多余的请求打到数据库
3.4 v4.0 优化秒杀:加入内存标记,避免总到 Reids 查询库存
- 需求分析/图解
- 如果某个商品库存已经为空了, 我们仍然是到 Redis 去查询的, 还可以进行优化
- 解决方案: 给商品进行内存标记, 如果库存为空, 直接返回, 避免总是到 Redis 查询库存
- 分析思路-示意图
@Controller
@RequestMapping(value = "/seckill")
public class SeckillController implements InitializingBean {@Resourceprivate GoodsService goodsService;@Resourceprivate SeckillOrderService seckillOrderService;@Resourceprivate OrderService orderService;@Resourceprivate RedisTemplate redisTemplate;@RequestMapping(value = "/doSeckill")public String doSeckill(Model model, User user, Long goodsId) {System.out.println("-----秒杀 V4.0--------");//===================秒杀 v4.0 start =========================if (user == null) {return "login";}model.addAttribute("user", user);GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);//判断库存if (goodsVo.getStockCount() < 1) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//解决重复抢购,直接到redis中获取对应的秒杀订单,如果有则说明已经抢购了,不能继续抢购SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);if (seckillOrder != null) {model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//如果库存为空,避免总是到 reids 去查询库存,给 redis 增加负担(内存标记)if (entryStockMap.get(goodsId)) {//如果当前这个秒杀商品已经是空库存,则直接返回.model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//库存预减,如果在redis中预减库存,发现秒杀商品中已经没有库存了,直接返回//从而减少这个方法请求 orderService.seckill防止大量请求打到数据,优化秒杀//这个方法具有原子性!!!Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);//说明这个商品已经没有库存了if (decrement < 0) {//这里使用内存标记,避免多次操作 redis, true 表示空库存了.entryStockMap.put(goodsId, true);//恢复redis库存为0,对业务没有影响只是为了好看redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//抢购Order order = orderService.seckill(user, goodsVo);if (order == null) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}model.addAttribute("order", order);model.addAttribute("goods", goodsVo);return "orderDetail";//===================秒杀 v4.0 end... =========================}//该方法实在类的所有属性都初始化后,自动执行//这里将所有秒杀商品的库存量加载到redis中@Overridepublic void afterPropertiesSet() throws Exception {//1.查询所有的秒杀商品List<GoodsVo> list = goodsService.findGoodsVo();if (CollectionUtils.isEmpty(list)) {return;}//遍历list,将秒杀商品的库存量放入到redis中//秒杀商品库存量对应的key seckillGoods:商品idfor (GoodsVo goodsVo : list) {redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());//当有库存为 false,当无库存为 true。防止库存没有了,还会到 Redis 进行判断操作entryStockMap.put(goodsVo.getId(), false);}}}
3.4 v5.0 优化秒杀: 加入消息队列,实现秒杀的异步请求
需求分析/图解
- 问题分析
前面秒杀, 没有实现异步机制, 是完成下订单后, 再返回的, 当有大并发请求下订单操作时, 数据库来不及响应, 容易造成线程堆积
- 解决方案
-
加入消息队列,实现秒杀的异步请求
-
接收到客户端秒杀请求后,服务器立即返回 正在秒杀中…, 有利于流量削峰
-
客户端进行轮询秒杀结果, 接收到秒杀结果后,在客户端页面显示即可
-
秒杀消息发送设计 SeckillMessage - String
代码实现
秒杀消息类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SeckillMessage {private User user;private Long goodsId;
}
定义消息队列、交换机
@Configuration
public class RabbitMQSecKillConfig {private static final String QUEUE = "seckillQueue";private static final String EXCHANGE = "seckillExchange";@Beanpublic Queue queue_seckill() {return new Queue(QUEUE);}@Beanpublic TopicExchange topicExchange_seckill() {return new TopicExchange(EXCHANGE);}@Beanpublic Binding binding_seckill() {return BindingBuilder.bind(queue_seckill()).to(topicExchange_seckill()).with("seckill.#");}}
发送秒杀消息
@Service
@Slf4j
public class MQSenderMessage {@Resourceprivate RabbitTemplate rabbitTemplate;//发送秒杀信息public void senderMessage(String message) {System.out.println("发送消息了=" + message);log.info("发送消息:" + message);rabbitTemplate.convertAndSend("seckillExchange", "seckill.message", message);}
}
消息接收者
@Service
@Slf4j
public class MQReceiverConsumer {@Resourceprivate GoodsService goodsService;@Resourceprivate OrderService orderService;//下单操作@RabbitListener(queues = "seckillQueue")public void queue(String message) {SeckillMessage seckillMessage = JSONUtil.toBean(message, SeckillMessage.class);Long goodId = seckillMessage.getGoodsId();User user = seckillMessage.getUser();//获取抢购的商品信息GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodId);//下单操作orderService.seckill(user, goodsVo);}}
SeckillController
@Controller
@RequestMapping(value = "/seckill")
public class SeckillController implements InitializingBean {@Resourceprivate GoodsService goodsService;@Resourceprivate RedisTemplate redisTemplate;@Resourceprivate MQSenderMessage mqSenderMessage;//如果某个商品库存已经为空, 则标记到 entryStockMapprivate HashMap<Long, Boolean> entryStockMap = new HashMap<>();@RequestMapping(value = "/doSeckill")public String doSeckill(Model model, User user, Long goodsId) {System.out.println("-----秒杀 V5.0--------");//===================秒杀 v5.0 start =========================if (user == null) {return "login";}model.addAttribute("user", user);GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);//判断库存if (goodsVo.getStockCount() < 1) {model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//解决重复抢购,直接到redis中获取对应的秒杀订单,如果有则说明已经抢购了,不能继续抢购SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);if (seckillOrder != null) {model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//如果库存为空,避免总是到 reids 去查询库存,给 redis 增加负担(内存标记)if (entryStockMap.get(goodsId)) {//如果当前这个秒杀商品已经是空库存,则直接返回.model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());return "secKillFail";}//库存预减,如果在redis中预减库存,发现秒杀商品中已经没有库存了,直接返回//从而减少这个方法请求 orderService.seckill防止大量请求打到数据,优化秒杀//这个方法具有原子性!!!Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);//说明这个商品已经没有库存了if (decrement < 0) {//这里使用内存标记,避免多次操作 redis, true 表示空库存了.entryStockMap.put(goodsId, true);//恢复redis库存为0,对业务没有影响只是为了好看redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());return "secKillFail";}//抢购// Order order = orderService.seckill(user, goodsVo);// if (order == null) {// model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());// return "secKillFail";// }// model.addAttribute("order", order);// model.addAttribute("goods", goodsVo);//抢购, 向 MQ 发消息, 因为不知道是否成功, 客户端需要轮询//errmsg 为排队中.... , 暂时返回 "secKillFaill" 页面SeckillMessage seckillMessage =new SeckillMessage(user, goodsId);mqSenderMessage.senderMessage(JSONUtil.toJsonStr(seckillMessage));model.addAttribute("errmsg", "排队中.....");return "orderDetail";//===================秒杀 v5.0 end... =========================}//该方法实在类的所有属性都初始化后,自动执行//这里将所有秒杀商品的库存量加载到redis中@Overridepublic void afterPropertiesSet() throws Exception {//1.查询所有的秒杀商品List<GoodsVo> list = goodsService.findGoodsVo();if (CollectionUtils.isEmpty(list)) {return;}//遍历list,将秒杀商品的库存量放入到redis中//秒杀商品库存量对应的key seckillGoods:商品idfor (GoodsVo goodsVo : list) {redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());//当有库存为 false,当无库存为 true。防止库存没有了,还会到 Redis 进行判断操作entryStockMap.put(goodsVo.getId(), false);}}}
- 客户端轮询秒杀结果-思路分析示意图
相关文章:
秒杀高并发解决方案
秒杀高并发解决方案 1.秒杀/高并发方案-介绍 秒杀/高并发 其实主要解决两个问题,一个是并发读,一个是并发写并发读的核心优化理念是尽量减少用户到 DB 来"读"数据,或者让他们读更少的数据, 并 发写的处理原则也一样针对秒杀系统需…...
【每日一题】蓝桥杯加练 | Day07
文章目录一、三角回文数1、问题描述2、解题思路3、AC代码一、三角回文数 原题链接:三角回文数 1、问题描述 对于正整数 n, 如果存在正整数 k 使得n123⋯k k(k1)2\frac{k(k1)}{2}2k(k1) , 则 n 称为三角数。例如, 66066 是一个三角数, 因为 66066123⋯363 。 如果一…...
条件语句(分支语句)——“Python”
各位CSDN的uu们你们好呀,最近总是感觉特别特别忙,但是却又不知道到底干了些什么,好像啥也没有做,还忙得莫名其妙,言归正传,今天,小雅兰的内容还是Python呀,介绍一些顺序结构的知识点…...
论文投稿指南——中文核心期刊推荐(国家财政)
【前言】 🚀 想发论文怎么办?手把手教你论文如何投稿!那么,首先要搞懂投稿目标——论文期刊 🎄 在期刊论文的分布中,存在一种普遍现象:即对于某一特定的学科或专业来说,少数期刊所含…...
面向数据安全共享的联邦学习研究综述
开放隐私计算 摘 要:跨部门、跨地域、跨系统间的数据共享是充分发挥分布式数据价值的有效途径,但是现阶段日益严峻的数据安全威胁和严格的法律法规对数据共享造成了诸多挑战。联邦学习可以联合多个用户在不传输本地数据的情况下协同训练机器学习模型&am…...
Redis经典五种数据类型底层实现原理解析
目录总纲redis的k,v键值对新的三大类型五种经典数据类型redisObject结构图示结构讲解数据类型与数据结构关系图示string数据类型三大编码格式SDS详解代码结构为什么要重新设计源码解析三大编码格式hash数据类型ziplist和hashtable编码格式ziplist详解结构剖析ziplist的优势(为什…...
Jackson 返回前端的 Response结果字段大小问题
目录 1、问题产生的背景 2、出现的现象 3、解决方案 4、成果展现 5、总结 6、参考文章 1、问题产生的背景 因为本人最近工作相关的对接外部项目,在我们国内有很多程序员都是使用汉语拼音或者部分字母加上英文复合体定义返回实体VO,这样为了能够符合…...
每天五分钟机器学习:你理解贝叶斯公式吗?
本文重点 贝叶斯算法是机器学习算法中非常经典的算法,也是非常古老的一个算法,但是它至今仍然发挥着重大的作用,本节课程及其以后的专栏将会对贝叶斯算法来做一个简单的介绍。 贝叶斯公式 贝叶斯公式是由联合概率推导而来 其中p(Y|X)称为后验概率,P(Y)称为先验概率…...
C++的入门
C的关键字 C总计63个关键字,C语言32个关键字 命名空间 我们C的就是建立在C语言之上,但是是高于C语言的,将C语言的不足都弥补上了,而命名空间就是为了弥补C语言的不足。 看一下这个例子。在C语言中会报错 #include<stdio.h>…...
数据的存储
类型的意义:使用这个类型开辟内存空间的大小(大小决定了使用范围)如何看待内存空间视角类型的基本归类整型家族浮点数家族构造类型指针类型空类型整型存储解构:整型在计算机中占用四个字节,整型分为无符号整型和有符号整型在计算机…...
Linux查看UTC时间
先了解一下几个时间概念。 GMT时间:Greenwich Mean Time,格林尼治平时,又称格林尼治平均时间或格林尼治标准时间。是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间。 GMT时间存在较大误差,因此不再被作为标准时间使用。现在…...
SpringBoot修改启动图标(详细步骤)
目录 一、介绍 二、操作步骤 三、介绍Java学习(题外话) 四、关于基础知识 一、介绍 修改图标就是在资源加载目录(resources)下放一个banner.txt文件。这样运行加载的时候就会扫描到这个文件,然后启动的时候就会显…...
【每日一题Day143】面试题 17.05. 字母与数字 | 前缀和+哈希表
面试题 17.05. 字母与数字 给定一个放有字母和数字的数组,找到最长的子数组,且包含的字母和数字的个数相同。 返回该子数组,若存在多个最长子数组,返回左端点下标值最小的子数组。若不存在这样的数组,返回一个空数组。…...
Go 内置运算符 if for switch
算数运算符fmt.Println("103", 103) //103 13 fmt.Println("10-3", 10-3) //10-3 7 fmt.Println("10*3", 10*3) //10*3 30 //除法注意:如果运算的数都是整数,那么除后,去掉小数部分,保留整数部分 f…...
C语言指针数组实际应用(嵌入式)
C语言指针数组详细学习 指针是C语言中非常重要的概念之一,它可以让我们直接访问内存中的数据。指针数组则是由多个指针组成的数组,每个指针都可以指向内存中的某个位置。以下是一些指针数组的实际代码应用: 字符串数组 char* names[] {&q…...
常用的Java注解详解
Java是一种常用的编程语言,而注解是Java语言中非常重要的一部分。在这篇文章中,我们将介绍一些常用的Java注解,以及它们的作用和使用方法。 Override override注解是用于表示一个方法是被覆盖的。在Java中,如果子类要覆盖父类的方…...
华为OD机试题 - 第 K 个最小码值的字母(JavaScript)| 机考必刷
更多题库,搜索引擎搜 梦想橡皮擦华为OD 👑👑👑 更多华为OD题库,搜 梦想橡皮擦 华为OD 👑👑👑 更多华为机考题库,搜 梦想橡皮擦华为OD 👑👑👑 华为OD机试题 最近更新的博客使用说明本篇题解:第 K 个最小码值的字母题目输入输出示例一输入输出说明示例一输…...
vscode环境配置(支持跳转,阅读linux kernel)
目录 1.卸载clangd插件 2.安装C插件 3. 搜索框内输入 “intell”,将 C_Cpp:Intelli Sense Engine 开关设置为 Default。 4.ubuntu安装global工具 5.vscode安装插件 6.验证是否生效 7.建立索引 1.卸载clangd插件 在插件管理中卸载clangd插件 2.安…...
zigbee学习笔记:IO操作
1、IAR新建工程 (1)Projetc→Create New Projetc→OK→选择位置,确定 (2)新建一个c文件,保存在路径中 (3)点击工程,右键→add→加入c文件 (4)…...
华为OD机试题 - 最少数量线段覆盖(JavaScript)| 机考必刷
更多题库,搜索引擎搜 梦想橡皮擦华为OD 👑👑👑 更多华为OD题库,搜 梦想橡皮擦 华为OD 👑👑👑 更多华为机考题库,搜 梦想橡皮擦华为OD 👑👑👑 华为OD机试题 最近更新的博客使用说明本篇题解:最少数量线段覆盖题目输入输出示例一输入输出说明Code解题思路版…...
python趣味编程-2048游戏
在上一期我们用Python实现了一个盒子追逐者的游戏,这一期我们继续使用Python实现一个简单的2048游戏,让我们开始今天的旅程吧~ 在 Python 免费源代码中使用 Tkinter 的简单 2048 游戏 使用 Tkinter 的简单 2048 游戏是一个用Python编程语言编码的桌面游…...
求解完全背包问题
题目描述实现一个算法求解完全背包问题。完全背包问题的介绍如下:已知一个容量为 totalweight 的背包,有不同重量不同价值的物品,问怎样在背包容量限制下达到利益最大化。完全背包问题的每个物品可以无限选用背包问题求解方法的介绍如下&…...
我们为什么使用docker 优点 作用
1. 我们为什么使用Docker? 当我们在工作中,一款产品从开发设计到上线运行,其中需要开发人员和运维工程师,开发人员负责代码编写,开发产品,运维工程师需要测试环境,产品部署。这之间就会有分歧。 就好比我…...
Python每日一练(20230311)
目录 1. 合并两个有序数组 2. 二叉树的右视图 3. 拼接最大数 🌟 每日一练刷题专栏 C/C 每日一练 专栏 Python 每日一练 专栏 1. 合并两个有序数组 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为…...
202109-3 CCF 脉冲神经网络 66分题解 + 解题思路 + 解题过程
解题思路 根据题意,脉冲源的阈值大于随机数时,会向其所有出点发送脉冲 神经元当v>30时,会向其所有出点发送脉冲,unordered_map <int, vector > ne; //存储神经元/脉冲源的所有出点集合vector 所有脉冲会有一定的延迟&am…...
Aurora简介
Amazon Aurora是一种兼容MySQL和PostgreSQL的商用级别关系数据库,它既有商用数据库的性能和可用性(比如Oracle数据库),又具有开源数据库的成本效益(比如MySQL数据库)。 Aurora的速度可以达到MySQL数据库的…...
【python实操】用python写软件弹窗
文章目录前言组件label 与 多行文本复选框组件Radiobutton单选组件Frame框架组件labelframe标签框架列表框Listboxscrollbar滚动条组件scale刻度条组件spinbox组件Toplevel子窗体组件PanedWindow组件Menu下拉菜单弹出菜单总结针对组件前言 python学习之路任重而道远࿰…...
Ubuntu 常用操作
版本22.04 1、开启 root # 输入新密码 sudo passwd rootUbuntu以root账号登录桌面 默认情况是不允许用root帐号直接登录图形界面的。 Ubuntu 默认使用 GNOME,GNOME 使用 GDM 显示管理器。 为了允许以 root 身份登录到 GNOME,你需要对位于 /etc/…...
井字棋--课后程序(Python程序开发案例教程-黑马程序员编著-第7章-课后作业)
实例2:井字棋 井字棋是一种在3 * 3格子上进行的连珠游戏,又称井字游戏。井字棋的游戏有两名玩家,其中一个玩家画圈,另一个玩家画叉,轮流在3 * 3格子上画上自己的符号,最先在横向、纵向、或斜线方向连成一条…...
谷粒学院开发(三):统一日志、异常及前端准备工作
特定异常处理 ControllerAdvice public class GlobalExceptionHandler {ExceptionHandler(Exception.class) // 指定出现什么异常会被处理ResponseBody // 为了能够返回数据public R error(Exception e) {e.printStackTrace();return R.error().message("执行了全局异常…...
虚拟主机上传网站/网络销售模式有哪些
无论前(prefix)还是后(postfix),操作数都只有一个,这样我们做运算符重载的时候,该如何区分呢? 这里就要接受一个新的知识,就是亚元。在C Primer一书中是这样描述的(中文版 第五版 503 页)“为了…...
广告设计图素材/seo外链发布平台有哪些
点击上方蓝字关注我们阅读目录:1. 前言2. 算法介绍-计数器法3. 算法介绍-滑动窗口4. 算法介绍-漏桶算法5. 算法介绍-令牌桶算法前言在一个高并发系统中对流量的把控是非常重要的,当巨大的流量直接请求到我们的服务器上没多久就可能造成接口不可用&#x…...
东莞微信网站建设品牌/windows优化工具
配置小程序消息推送。之前一直报 token 错误小程序配置消息推送,微信服务器会发送验证RequestMapping(method RequestMethod.GET) ResponseBody public void get(HttpServletRequest request, HttpServletResponse response) throws Exception{ …...
国外网站建设的发展/产品营销策划方案3000字
一,概念 加法 乘法 减法 除法 二,二进制,十进制,十六进制转化 二进制转十进制 十进制转二进制 二进制转十六进制 十六进制转二进制 三,原码,反码,补码 正数 负数 四,操…...
云主机 asp 网站/找客户资源的软件哪个最靠谱
EXPLAIN 结果中的type字段案例 当sql查询语句写出来之后,其实工作只完成了一小半,更重要的工作是评估写的sql的质量与效率。mysql提供了explain,它向我们展示了mysql接收到一条sql语句的执行计划。根据explain返回的结果便可以知道我们的sql…...
做网站互联互通/软文的概念
漏洞描述 Session 是应用系统对浏览器客户端身份认证的属性标识,在用户注销或退出应用系统时,系统应将客户端Session认证属性标识清空。危害:如果未能清空Session认证会话,该认证会话将持续有效,此时攻击者获得该Sessi…...