
1.分析
秒杀时大量用户会在同一时间同时进行抢购,网站瞬时访问流量激增。
秒杀一般是访问请求数量远远大于库存数量,只有少部分用户能够秒杀成功。
秒杀业务流程比较简单,一般就是下订单减库存。
上述三点的主要问题就是在高并发的情况下保证数据的一致性。
2.使用的技术和架构
2.1秒杀架构图

2.2流程
使用 redis 缓存秒杀的商品信息,秒杀成功后使用消息队列发送订单信息,然后将更新后数据重新写入redis。
RabbitMQ监听器在接受到消息后,将订单信息写入数据库。
在秒杀时使用redisson对商品信息上锁
2.3流程图

3.准备工作
3.1安装redis cluster
csdn上教程一大堆,这里我就不多赘述了。需要注意的点是,如果使用的是阿里云服务器(centos 7),在安装完后一定要去阿里云服务器控制台添加安全规则,去开放你使用的对应端口号。https://blog.csdn.net/CFrieman/article/details/83583085
3.2安装RabbitMQ和erlang
还是直接附上链接。需要说明的一点是,在安装erlang时,电脑名称不可以是中文,erlang的版本和rabbitmq的版本一定要对应,负责会安装失败。https://blog.csdn.net/qq_36505948/article/details/82734133
4.具体实现
4.1SeckillService
public class SeckillService { @Autowired private RedisClusterClient rt; @Autowired private SeckillMapper sm; @Autowired private RedissonClient redissonClient; // 加锁 @Autowired private RabbitmqSendMessage rsm; @Autowired private SecorderMapper om; /** * 初始化 ,将mysql中的商品信息缓存到redis中 * @return */ public List<Seckill> querySeckill() { List<Seckill> list = (List<Seckill>) rt.get("secgoods"); if(list==null) { list = sm.selectByExample(null); rt.set("secgoods", list, 60*30); } return list; } public boolean queryStartTime(Seckill sec) { Date date = new Date();// 比较时间,是否到秒杀时间 Date startTime = sec.getStarttime(); // 秒杀活动还未开始 if (startTime.getTime() > date.getTime()) { return false; } return true; } // 减库存redis public void decreaseStock(String id) { int goodsid = Integer.parseInt(id); List<Seckill> list = (List<Seckill>) rt.get("secgoods"); if (list!=null) { for (Seckill sec : list) { if (goodsid==sec.getId()) { sec.setCount(sec.getCount()-1); //写回redis rt.set("secgoods", list, 60*30); return ; } } } } // public Seckill findSec(String secid) { List<Seckill> list = (List<Seckill>) rt.get("secgoods"); int id = Integer.parseInt(secid); for(Seckill sec:list) { if(sec.getId()==id) { return sec; } } return null; } // 开始秒杀 public String goSeckill(String goodsid, String username) { String key = username + ":" + goodsid; String secid = goodsid; Long value = (Long) rt.get(key); if (value != null) { return "exist"; } Seckill sec = findSec(secid); boolean flag = queryStartTime(sec); if (!flag) { return "notTime"; } RLock rLock = redissonClient.getLock("miaosha"); rLock.lock(); if (sec.getCount() > 0) { decreaseStock(goodsid); // 减少库存 rt.set(key, System.currentTimeMillis(), 60*30); Secorder newOrder = new Secorder(); newOrder.setCreatetime(new Date()); newOrder.setGoodsid(Integer.parseInt(goodsid)); newOrder.setStatus("未付款"); newOrder.setUsername(username); String json = JSONObject.toJSONString(newOrder); rsm.send(json); // 异步下单 rLock.unlock(); // 解锁 return "success"; } else { rLock.unlock(); return "failed"; } } // 写入mysql public void saveOrder(String json) { Secorder order = JSON.parseObject(json, Secorder.class); int n = sm.updateCount(order.getGoodsid()); int m = om.insert(order); } }
4.2 RabbitmqListenner
@Servicepublic class RabbitmqListenner implements MessageListener { @Autowired private SeckillService ss; @Override public void onMessage(Message msg) { byte[] data = msg.getBody(); try { String json = new String(data,"utf-8"); System.out.println(json); ss.saveOrder(json); //将监听到的订单写入MySQL } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}
4.3 RabbitmqSendMessage
public class RabbitmqSendMessage { @Autowired private RabbitTemplate rt; private final String QUEEN_NAME = "MIAOSHA"; /** * 发送消息 * @param msg */ public void send(String msg) { rt.convertAndSend(QUEEN_NAME,msg); }}
4.4
以上就是整个业务流程的核心代码,使用redisson保证数据一致性,用rabbitmq异步下单将下单及写数据库这个长操作变成两个短操作。GitHub源码地址,关于数据库建表什么的,大家直接去源码里看吧。
5.优化
限流:使用验证码,请求秒杀接口需要验证图形验证码的正确性,这样也很好的防止脚本的不断访问;
防刷:一个用户对一个路径的访问次数在一定时间内有限制,使用redis可以解决
接口地址隐藏:接口地址传参,保证秒杀接口不是一个固定路径,防止接口被刷,同时也可以有效隐藏秒杀地址。
来源:
https://www.toutiao.com/i6906372359805092356/
“IT大咖说”欢迎广大技术人员投稿,投稿邮箱:aliang@itdks.com

IT大咖说 | 关于版权
由“IT大咖说(ID:itdakashuo)”原创的文章,转载时请注明作者、出处及微信公众号。投稿、约稿、转载请加微信:ITDKS10(备注:投稿),茉莉小姐姐会及时与您联系!
感谢您对IT大咖说的热心支持!
相关推荐
推荐文章




