RabbitMQ消息队列
RabbitMQ是一套开源的消息队列服务软件,实现了高级消息队列协议(AMQP),服务器采用Erlang语言开发,支持多种客户端,如:Python、Ruby、Java、JMS、C#、PHP、JavaScript等。
其特点包含:
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 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 msg) {
int stock = 0;
//查数据库中商品库存
Map 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
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 get(String key,Class 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 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 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 Long decr(String key){
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//返回value减1后的值
return jedis.decr(key);
}finally {
if(jedis != null) {
jedis.close();
}
}
}
/**
* 增加值
*/
public 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 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 stringToBean(String str,Class 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 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
UPDATE MIAOSHA_GOODS SET STOCK_COUNT = STOCK_COUNT - 1 WHERE STOCK_COUNT > 0 AND ID = #{goods_id}
INSERT INTO MIAOSHA_ORDER(
ID,
USER_ID,
GOODS_ID
) VALUES(
#{id},
#{user_id},
#{goods_id}
)
备注:数据库可建立用户ID和商品ID的联合唯一索引
6.创建秒杀业务dao
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MiaoshaDao {
List
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> queryAllGoodStock(){
return miaoshaDao.queryAllGoodStock();
};
//通过商品ID查询库存数量
public Map queryGoodStockById(Map m){
return miaoshaDao.queryGoodStockById(m);
};
//根据用户ID和商品ID查询是否下过单
public List> queryOrderByUserIdAndCoodsId(Map m){
return miaoshaDao.queryOrderByUserIdAndCoodsId(m);
};
//减少库存,下订单,是一个事务
@Transactional
public void miaosha(Map 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 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 localOverMap = new HashMap();
/**
* 系统初始化时把商品库存加入到缓存中
*/
@Override
public void afterPropertiesSet() throws Exception {
//查询库存数量
List> stockList = miaoshaService.queryAllGoodStock();
System.out.println("系统初始化:"+stockList);
if(stockList == null){
return;
}
for(Map 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 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 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;
}
}