实现商品秒杀之——Redis+RabbitMQ消息队列

RabbitMQ消息队列

       RabbitMQ是一套开源的消息队列服务软件,实现了高级消息队列协议(AMQP),服务器采用Erlang语言开发,支持多种客户端,如:Python、Ruby、Java、JMS、C#、PHP、JavaScript等。

其特点包含:

  • 异步处理:通过把消息发送给消息中间件,消息中间件并不立即处理它,而是后续再慢慢处理
  • 应用解耦:单个系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失,降低了应用之间的耦合度
  • 流量削峰:服务器在接收到用户请求后,首先写入消息队列,这时如果消息队列中消息数量超过最大数量,则直接抛弃用户请求
  • 高可靠性:使用了一些机制来保证可靠性,比如持久化、传输确认、发布确认
  • 高可用性:队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用
  • 灵活路由:所有的消息都会通过路由器转发到各个消息队列中,RabbitMQ内建了几个常用的路由器,并且可以通过路由器的组合以及自定义路由器插件来完成复杂的路由功能

在业务中的应用

1.创建RabbitMQ配置类

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {
	
    //Direct模式
    public static final String QUEUE="queue";
	
    /**
     * Direct模式
     */
    @Bean
    public Queue queue() {
        //名称,是否持久化
        return new Queue(QUEUE,true);
    }
	
}

备注:RabbitMQ安装及配置参考https://blog.csdn.net/rexueqingchun/article/details/103250554

2.创建RabbitMQ生产者

import java.util.Map;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

//生产者
@Service
public class MQSender {
	
    @Autowired
    AmqpTemplate amqpTemplate;

    //Direct模式
    public void send(Map<String,Object> msg) {
    	//第一个参数队列的名字,第二个参数发出的信息
    	amqpTemplate.convertAndSend(MQConfig.QUEUE, msg);
    }
	
}

3.创建RabbitMQ消费者

import java.util.List;
import java.util.Map;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cn.com.app.service.MiaoshaService;

//消费者
@Service
public class MQReceiver {

    @Autowired
    private MiaoshaService miaoshaService;
	
    @RabbitListener(queues=MQConfig.QUEUE)//指明监听的是哪一个queue
    public void receive(Map<String,Object> msg) {		
    	int stock = 0;
    	//查数据库中商品库存
    	Map<String, Object> m = miaoshaService.queryGoodStockById(msg);
    	if(m != null && m.get("stock_count") != null){
    	    stock = Integer.parseInt(m.get("stock_count").toString());
    	}
    	if(stock <= 0){//库存不足
    	    return;
    	}
    	//这里业务是同一用户同一商品只能购买一次,所以判断该商品用户是否下过单
    	List<Map<String, Object>> list = miaoshaService.queryOrderByUserIdAndCoodsId(msg);
	if(list != null && list.size() > 0){//重复下单
	    return;
	}
	//减库存,下订单
	miaoshaService.miaosha(msg);		
    }
	
}

4.创建Redis操作工具类

import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
 
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
 
@Service
public class RedisService {
	
	@Autowired
	private JedisPool jedisPool;
	
	/**
	 * 获取对象
	 */
	public <T> T get(String key,Class<T> clazz){
	    Jedis jedis = null;
	    try {
		jedis = jedisPool.getResource();
		String sval = jedis.get(key);
		//将String转换为Bean
		T t = stringToBean(sval,clazz);
		return t;
	    }finally {
		if(jedis != null) {	
		    jedis.close();
		}
	    }
        }
	
	/**
	 * 设置对象
	 */					
	public <T> boolean set(String key,T value){
	    Jedis jedis = null;
	    try {
	    	jedis = jedisPool.getResource();
		//将Bean转换为String
		String s = beanToString(value);
		if(s == null || s.length() <= 0) {
		    return false;
		}
		jedis.set(key, s);
		return true;
	    }finally {
	        if(jedis != null) {	
	    	    jedis.close();
	        }
	    }
        }

	/**
	 * 设置对象,含过期时间(单位:秒)
	 */					
	public <T> boolean set(String key,T value,int expireTime){
	    Jedis jedis = null;
	    try {
	    	jedis = jedisPool.getResource();
	    	//将Bean转换为String
	    	String s = beanToString(value);
	    	if(s == null || s.length() <= 0) {
	    	    return false;
	    	}
	    	if(expireTime <= 0) {
	    	    //有效期:代表不过期
	    	    jedis.set(key, s);
	    	}else {
	    	    jedis.setex(key, expireTime, s);
	    	}
	    	return true;
	    }finally {
	    	if(jedis != null) {	
	    	    jedis.close();
	    	}
	    }
	}
	
	/**
	 * 减少值
	 */
	public <T> Long decr(String key){
	    Jedis jedis = null;
	    try {
	    	jedis = jedisPool.getResource();
	    	//返回value减1后的值
	    	return jedis.decr(key);
	    }finally {
	    	if(jedis != null) {	
	    	    jedis.close();
	    	}
	    }
        }
	
	/**
	 * 增加值
	 */
        public <T> Long incr(String key){
    	    Jedis jedis = null;
    	    try {
    		jedis = jedisPool.getResource();
    		//返回value加1后的值
    		return jedis.incr(key);
    	    }finally {
    		if(jedis != null) {	
    		    jedis.close();
    		}
    	    }
        }

	/**
	 * 检查key是否存在
	 */
	public <T> boolean exists(String key) {
	    Jedis jedis = null;
	    try {
	    	jedis =  jedisPool.getResource();
	    	return jedis.exists(key);
	     }finally {
	    	if(jedis != null) {	
	            jedis.close();
	    	}
	     }
	}

	/**
	 * 将字符串转换为Bean对象
	 */
	@SuppressWarnings("unchecked")
	public static <T> T stringToBean(String str,Class<T> clazz) {
	    if(str == null || str.length() == 0 || clazz == null) {
	    	return null;
	    }		
	    if(clazz == int.class || clazz == Integer.class) {
	    	return ((T) Integer.valueOf(str));
	    }else if(clazz == String.class) {
	    	return (T) str;
	    }else if(clazz == long.class || clazz == Long.class) {
	    	return (T) Long.valueOf(str);
	    }else if(clazz == List.class) {
	    	return JSON.toJavaObject(JSONArray.parseArray(str), clazz);
	    }else {
	    	return JSON.toJavaObject(JSON.parseObject(str), clazz);
	    }		
        }
	
	/**
	 * 将Bean对象转换为字符串类型
	 */
	public static <T> String beanToString(T value) {
	    if(value == null){
	    	return null;
	    }
	    Class<?> clazz = value.getClass();
	    if(clazz == int.class || clazz == Integer.class) {
	    	return ""+value;
	    }else if(clazz == String.class) {
	    	return (String)value;
	    }else if(clazz == long.class || clazz == Long.class) {
	    	return ""+value;
	    }else {
	    	return JSON.toJSONString(value);
	    }		
	}
	
}

5.创建秒杀业务mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cn.com.app.dao.MiaoshaDao">
	
    <!--返回结果 -->
    <resultMap id="BaseResultMap" type="java.util.Map">
    	<result column="GOODS_ID" jdbcType="VARCHAR"  javaType="String" property="goods_id" />
    	<result column="STOCK_COUNT" jdbcType="VARCHAR"  javaType="String" property="stock_count" /> 
    </resultMap>
	
    <!--查询全部商品库存数量 -->
    <select id="queryAllGoodStock" parameterType="java.util.Map" resultMap="BaseResultMap">
    	SELECT GOODS_ID,STOCK_COUNT FROM MIAOSHA_GOODS 
    </select>
	
    <!--通过商品ID查询库存数量 -->
    <select id="queryGoodStockById" parameterType="java.util.Map" resultMap="BaseResultMap">
    	SELECT GOODS_ID,STOCK_COUNT FROM MIAOSHA_GOODS WHERE ID = #{goods_id}
    </select>
    
    <!--查询用户是否下过订单 -->
    <select id="queryOrderByUserIdAndCoodsId" parameterType="java.util.Map" resultMap="BaseResultMap">
    	SELECT ID FROM MIAOSHA_ORDER WHERE USER_ID = #{user_id} AND GOODS_ID = #{goods_id}
    </select>
    
    <!--减少库存数量 -->
    <update id="updateGoodStock" parameterType="java.util.Map">
        UPDATE MIAOSHA_GOODS SET STOCK_COUNT = STOCK_COUNT - 1 WHERE STOCK_COUNT > 0 AND ID = #{goods_id}
    </update>
    
    <!--下订单 -->
    <insert id="insertOrder" parameterType="java.util.Map">
        INSERT INTO MIAOSHA_ORDER(
            ID,
            USER_ID,
            GOODS_ID 
        ) VALUES(
            #{id},
            #{user_id},
            #{goods_id}
        )
    </insert>
    
</mapper>

备注:数据库可建立用户ID和商品ID的联合唯一索引

6.创建秒杀业务dao

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MiaoshaDao { 
	
    List<Map<String, Object>> queryAllGoodStock();
	
    Map<String, Object> queryGoodStockById(Map<String, Object> m);
	
    List<Map<String, Object>> queryOrderByUserIdAndCoodsId(Map<String, Object> m);
	
    Integer updateGoodStock(Map<String, Object> m); 
	
    Integer insertOrder(Map<String, Object> m); 
}

7.创建秒杀业务service

import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import cn.com.app.dao.MiaoshaDao;
import cn.com.app.redis.RedisService;

@Service
public class MiaoshaService {
    
    @Autowired
    private MiaoshaDao miaoshaDao;
    @Autowired
    private RedisService redisService;
    
    //查询全部商品库存数量
    public List<Map<String, Object>> queryAllGoodStock(){
    	return  miaoshaDao.queryAllGoodStock();
    };
    
    //通过商品ID查询库存数量
    public Map<String, Object> queryGoodStockById(Map<String, Object> m){
    	return  miaoshaDao.queryGoodStockById(m);
    };
	
    //根据用户ID和商品ID查询是否下过单
    public List<Map<String, Object>> queryOrderByUserIdAndCoodsId(Map<String, Object> m){
    	return  miaoshaDao.queryOrderByUserIdAndCoodsId(m);
    };
    
    //减少库存,下订单,是一个事务
    @Transactional
    public void miaosha(Map<String, Object> m){
    	//减少库存
    	int count = miaoshaDao.updateGoodStock(m);
    	if(count > 0){
    	    try {
		//减少库存成功后下订单,由于一件商品同一用户只能购买一次,所以需要建立用户ID和商品ID的联合索引
		m.put("id", UUID.randomUUID().toString().replaceAll("-", ""));
		miaoshaDao.insertOrder(m);
		//将生成的订单放入缓存
		redisService.set("order"+m.get("user_id")+"_"+m.get("goods_id"), m);
            } catch (Exception e) {
                //出现异常手动回滚
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                redisService.incr("goods"+m.get("goods_id"));
            }
	}else {
	    //减少库存失败做一个标记,代表商品已经卖完了
	    redisService.set("goodsover"+m.get("goods_id"), true);
	}
    }
	
    //获取秒杀结果
    @SuppressWarnings("unchecked")
    public String getMiaoshaResult(String userId, String goodsId) {
    	Map<String, Object> orderMap = redisService.get("order"+userId+"_"+goodsId, Map.class);
    	if(orderMap != null) {//秒杀成功
    	    return orderMap.get("id").toString();
    	}else {
    	    boolean isOver = getGoodsOver(goodsId);
    	    if(isOver) {
    	    	return "-1";
    	    }else {
    	    	return "0";
    	    }
    	}
    }
	
    //查询是否卖完了
    private boolean getGoodsOver(String goodsId) {
    	return redisService.exists("goodsover"+goodsId);
    }
		
}

8.秒杀业务调用

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import cn.com.app.rabbitmq.MQSender;
import cn.com.app.redis.RedisService;
import cn.com.app.service.MiaoshaService;

@Controller
public class RedisRabbitmqController implements InitializingBean {
	
    @Autowired
    private MiaoshaService miaoshaService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private MQSender mQSender;
	
    //内存标记,减少redis访问
    private HashMap<String, Boolean> localOverMap =  new HashMap<String, Boolean>();
    
    /**
     * 系统初始化时把商品库存加入到缓存中
     */
    @Override
    public void afterPropertiesSet() throws Exception {
    	//查询库存数量
    	List<Map<String, Object>> stockList = miaoshaService.queryAllGoodStock();
    	System.out.println("系统初始化:"+stockList);
    	if(stockList == null){
    	    return;
    	}
    	for(Map<String, Object> m : stockList) {
    	    //如果不是null的时候,将库存加载到redis中
    	    redisService.set("goods"+m.get("goods_id"), m.get("stock_count"));
    	    //添加内存标记
    	    localOverMap.put(m.get("goods_id").toString(), false);
    	}
    }
    
    /**
     * 请求秒杀,redis+rabbitmq方式
     */
    @SuppressWarnings("unchecked")
    @RequestMapping(value="/miaosharabbitmq")
    @ResponseBody
    public String miaosharabbitmq(HttpServletRequest request,@RequestParam("userid")String userid,@RequestParam("goodsId")String goodsId){
    	//限制同一用户3s之内只能访问1次
    	String uri = request.getRequestURI();
    	String key = uri+"_"+userid;
    	Integer count = redisService.get("access"+key,Integer.class);
    	if(count == null) {
    	    redisService.set("access"+key,1,3);
    	}else if(count < 1) {
    	    redisService.incr("access"+key);
    	}else {
    	    System.out.println("操作频繁");
    	    return "操作频繁";
    	}
    	boolean over = localOverMap.get(goodsId);
       	if(over) {
            System.out.println("秒杀结束");
            return "秒杀结束";
        }
	Map<String,Object> map = redisService.get("order"+userid+"_"+goodsId,Map.class);
        if(map != null) {
	    System.out.println("重复下单");
	    return "重复下单";
	}
        long stock = redisService.decr("goods"+goodsId);
	if(stock < 0) {
	    localOverMap.put(goodsId, true);
	    System.out.println("库存不足");
	    return "库存不足";
	}
	System.out.println("剩余库存:" + stock);
	//加入到队列中,返回0:排队中,客户端轮询或延迟几秒后查看结果
	Map<String,Object> msg = new HashMap<>();
	msg.put("user_id", userid);
	msg.put("goods_id", goodsId);
	mQSender.send(msg);
	return "0";
    }
	
    //查询秒杀结果(orderId:成功,-1:秒杀失败,0: 排队中)
    @RequestMapping(value="/result", method=RequestMethod.GET)
    @ResponseBody
    public String miaoshaResult(HttpServletRequest request,HttpServletResponse response,Model model,@RequestParam("userid")String userid,@RequestParam("goodsId")String goodsId) {
    	String result = miaoshaService.getMiaoshaResult(userid, goodsId);
    	if(!result.equals("0") && !result.equals("-1")){
    	    System.out.println("秒杀成功");
    	}else{
    	    System.out.println("秒杀失败");
    	}
    	return result;
    }
}
发布了95 篇原创文章 · 获赞 131 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/rexueqingchun/article/details/104059381
今日推荐