前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JAVA秒杀系统的简单实现(redis+rabbitmq)

JAVA秒杀系统的简单实现(redis+rabbitmq)

作者头像
IT大咖说
发布2020-12-29 15:42:14
2.7K0
发布2020-12-29 15:42:14
举报
文章被收录于专栏:IT大咖说IT大咖说

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

代码语言:javascript
复制
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

代码语言:javascript
复制
@Service
public 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

代码语言:javascript
复制
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可以解决
  • 接口地址隐藏:接口地址传参,保证秒杀接口不是一个固定路径,防止接口被刷,同时也可以有效隐藏秒杀地址。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT大咖说 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.分析
  • 2.使用的技术和架构
  • 3.准备工作
  • 4.具体实现
  • 5.优化
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档