Java秒杀实战 服务级高并发秒杀优化(RabbitMQ+接口优化)
Posted sharpest
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java秒杀实战 服务级高并发秒杀优化(RabbitMQ+接口优化)相关的知识,希望对你有一定的参考价值。
转自:https://blog.csdn.net/qq_41305266/article/details/81146716
一、思路:减少数据库访问
1.系统初始化,把商品库存数量加载到Redis
2.收到请求,Redis预减库存,库存不足,直接返回,否则进入3
3.请求入队,立即返回排队中
4.请求出队,生成订单,减少库存
5.客户端轮询,是否秒杀成功
二、安装RabbitMQ及其相关依赖
下载erlang
https://www.erlang.org/downloads
下载rabbitMQ
http://www.rabbitmq.com/download.html
安装相关依赖
yum install ncurses-devel
tar xf otp_src_21.0.tar.gz
cd otp_src_21.0
./configure --prefix=/usr/local/erlang21 --without-javac
make -j 4
make install
验证安装是否成功
yum install python -y
yum install xmlto -y
yum install python-simplejson -y
xz -d rabbitmq-server-generic-unix-3.7.7.tar.xz
tar xf rabbitmq-server-generic-unix-3.7.7.tar
mv rabbitmq_server-3.7.7 /usr/local/rabbitmq
vim /etc/profile
在最后一行添加 export PATH=$PATH:/usr/local/erlang21/bin:/usr/local/rabbitmq/sbin
source /etc/profile
为了使guest用户让远程也可以访问,需要加入以下配置文件及内容
vim /usr/local/rabbitmq/etc/rabbitmq/rabbitmq.config
[rabbit, [loopback_users, []]].
重启rabbitmq使其生效。
三、SpringBoot集成RabbitMQ
pom文件引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
application.properties添加相关配置
#rabbitmq
spring.rabbitmq.host=120.78.235.152
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
#\\u6D88\\u8D39\\u8005\\u6570\\u91CF
spring.rabbitmq.listener.simple.concurrency= 10
spring.rabbitmq.listener.simple.max-concurrency= 10
#\\u6D88\\u8D39\\u8005\\u6BCF\\u6B21\\u4ECE\\u961F\\u5217\\u83B7\\u53D6\\u7684\\u6D88\\u606F\\u6570\\u91CF
spring.rabbitmq.listener.simple.prefetch= 1
#\\u6D88\\u8D39\\u8005\\u81EA\\u52A8\\u542F\\u52A8
spring.rabbitmq.listener.simple.auto-startup=true
#\\u6D88\\u8D39\\u5931\\u8D25\\uFF0C\\u81EA\\u52A8\\u91CD\\u65B0\\u5165\\u961F
spring.rabbitmq.listener.simple.default-requeue-rejected= true
#\\u542F\\u7528\\u53D1\\u9001\\u91CD\\u8BD5
spring.rabbitmq.template.retry.enabled=true
spring.rabbitmq.template.retry.initial-interval=1000
spring.rabbitmq.template.retry.max-attempts=3
spring.rabbitmq.template.retry.max-interval=10000
spring.rabbitmq.template.retry.multiplier=1.0
添加配置类
package com.wings.seckill.config;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MQConfig
public static final String QUEUE = "queue";
@Bean
public Queue queue()
return new Queue(QUEUE, true);
发送者
package com.wings.seckill.rabbitmq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.wings.seckill.config.MQConfig;
import com.wings.seckill.redis.RedisService;
@Service
public class MQSender
private Logger logger = LoggerFactory.getLogger(MQSender.class);
@Autowired
private RedisService redisService;
@Autowired
private AmqpTemplate amqpTemplate;
/**
* Direct 模式交换机
* @param obj
*/
public void send(Object obj)
String msg = redisService.beanToString(obj);
logger.info("sender send:" + msg);
amqpTemplate.convertAndSend(MQConfig.QUEUE, msg);
接收者
package com.wings.seckill.rabbitmq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import com.wings.seckill.config.MQConfig;
@Service
public class MQReceiver
private Logger logger = LoggerFactory.getLogger(MQReceiver.class);
@RabbitListener(queues = MQConfig.QUEUE)
public void receive(String msg)
logger.info("receive:" + msg);
DemoController添加以下测试方法
@RequestMapping("/mq")
@ResponseBody
public Result<Boolean> mq()
mqSender.send("Wings you‘re the hero,路飞是成为海贼王的男人!");
return Result.success(true);
结果如下
2018-07-22 07:46:58.224 INFO 8624 --- [nio-8080-exec-6] com.wings.seckill.rabbitmq.MQSender : sender send:Wings you‘re the hero,路飞是成为海贼王的男人!
2018-07-22 07:46:58.235 INFO 8624 --- [cTaskExecutor-1] com.wings.seckill.rabbitmq.MQReceiver : receive:Wings you‘re the hero,路飞是成为海贼王的男人!
topic模式
配置类添加以下配置方法
public static final String TOPIC_QUEUE1 = "topic.queue1";
public static final String TOPIC_QUEUE2 = "topic.queue2";
public static final String TOPIC_EXCHANGE = "topicExchage";;
@Bean
public Queue topQueue1()
return new Queue(TOPIC_QUEUE1, true);
@Bean
public Queue topQueue2()
return new Queue(TOPIC_QUEUE2, true);
@Bean
public TopicExchange topicExchange()
return new TopicExchange(TOPIC_EXCHANGE);
@Bean
public Binding topicBind1()
return BindingBuilder.bind(topQueue1()).to(topicExchange()).with("topic.key1");
@Bean
public Binding topicBind2()
return BindingBuilder.bind(topQueue2()).to(topicExchange()).with("topic.#");
发送者添加以下方法
public void sendTopic(Object message)
String msg = redisService.beanToString(message);
logger.info("send topic message:" + msg);
amqpTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE, "topic.key1", msg + "1");
amqpTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE, "topic.key2", msg + "2");
接受者添加以下方法
@RabbitListener(queues = MQConfig.TOPIC_QUEUE1)
public void receiveTopic1(String msg)
logger.info("receiveTopic1:" + msg);
@RabbitListener(queues = MQConfig.TOPIC_QUEUE2)
public void receiveTopic2(String msg)
logger.info("receiveTopic2:" + msg);
结果如下:
fanout模式(广播模式)
配置类添加以下配置方法
@Bean
public FanoutExchange fanoutExchange()
return new FanoutExchange(FANOUT_EXCHANGE);
@Bean
public Binding fanoutBind1()
return BindingBuilder.bind(topQueue1()).to(fanoutExchange());
@Bean
public Binding fanoutBind2()
return BindingBuilder.bind(topQueue2()).to(fanoutExchange());
发送者添加以下方法
public void sendFanout(Object message)
String msg = redisService.beanToString(message);
logger.info("send fanout message:" + msg);
amqpTemplate.convertAndSend(MQConfig.FANOUT_EXCHANGE, "", msg);
结果如下:
配置类添加以下配置方法
@Bean
public Queue headersQueue()
return new Queue(HEADERS_QUEUE, true);
@Bean
public HeadersExchange headersExchange()
return new HeadersExchange(HEADERS_EXCHANGE);
@Bean
public Binding headerBind()
Map<String, Object> map = new HashMap<String, Object>();
map.put("header1", "value1");
map.put("header2", "value2");
return BindingBuilder.bind(headersQueue()).to(headersExchange()).whereAll(map).match();
headers模式
发送者添加以下方法
public void sendHeaders(Object message)
String msg = redisService.beanToString(message);
logger.info("send sendHeaders message:" + msg);
MessageProperties props = new MessageProperties();
props.setHeader("header1", "value1");
props.setHeader("header2", "value2");
Message obj = new Message(msg.getBytes(), props);
amqpTemplate.convertAndSend(MQConfig.HEADERS_EXCHANGE, "", obj);
接收者添加以下方法
@RabbitListener(queues = MQConfig.HEADERS_QUEUE)
public void receiveHeaders(byte[] msg)
logger.info("receiveHeaders:" + new String(msg));
结果如下:
package com.wings.seckill.controller;
import java.util.HashMap;
import java.util.List;
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 com.wings.seckill.domain.SeckillOrder;
import com.wings.seckill.domain.SeckillUser;
import com.wings.seckill.rabbitmq.MQSender;
import com.wings.seckill.rabbitmq.SeckillMessage;
import com.wings.seckill.redis.GoodsKey;
import com.wings.seckill.redis.OrderKey;
import com.wings.seckill.redis.RedisService;
import com.wings.seckill.redis.SeckillKey;
import com.wings.seckill.result.CodeMsg;
import com.wings.seckill.result.Result;
import com.wings.seckill.service.GoodsService;
import com.wings.seckill.service.OrderService;
import com.wings.seckill.service.SeckillService;
import com.wings.seckill.service.SeckillUserService;
import com.wings.seckill.vo.GoodsVo;
@Controller
@RequestMapping("/seckill")
public class SeckillController implements InitializingBean
@Autowired
SeckillUserService userService;
@Autowired
RedisService redisService;
@Autowired
GoodsService goodsService;
@Autowired
OrderService orderService;
@Autowired
SeckillService seckillService;
@Autowired
MQSender sender;
private HashMap<Long, Boolean> localOverMap = new HashMap<Long, Boolean>();
@Override
public void afterPropertiesSet() throws Exception
List<GoodsVo> goodsList = goodsService.listGoodsVo();
if(goodsList == null)
return;
for(GoodsVo goods : goodsList)
redisService.set(GoodsKey.getSeckillGoodsStock, ""+goods.getId(), goods.getStockCount());
localOverMap.put(goods.getId(), false);
@RequestMapping(value = "/do_seckill", method = RequestMethod.POST)
@ResponseBody
public Result<Integer> list(Model model, SeckillUser user, @RequestParam("goodsId") long goodsId)
model.addAttribute("user", user);
if(user == null)
return Result.error(CodeMsg.SESSION_ERROR);
//内存标记,减少redis访问
boolean over = localOverMap.get(goodsId);
if(over)
return Result.error(CodeMsg.SECKill_OVER);
//预减库存
long stock = redisService.decr(GoodsKey.getSeckillGoodsStock, ""+goodsId);
if(stock < 0)
localOverMap.put(goodsId, true);
return Result.error(CodeMsg.SECKill_OVER);
//判断是否已经秒杀到了
SeckillOrder order = orderService.getSeckillOrderByUserIdGoodsId(user.getId(), goodsId);
if(order != null)
return Result.error(CodeMsg.REPEATE_SECKILL);
//入队
SeckillMessage mm = new SeckillMessage();
mm.setUser(user);
mm.setGoodsId(goodsId);
sender.sendSeckillMessage(mm);
return Result.success(0);//排队中
@RequestMapping(value="/reset", method=RequestMethod.GET)
@ResponseBody
public Result<Boolean> reset(Model model)
List<GoodsVo> goodsList = goodsService.listGoodsVo();
for(GoodsVo goods : goodsList)
goods.setStockCount(10);
redisService.set(GoodsKey.getSeckillGoodsStock, ""+goods.getId(), 10);
localOverMap.put(goods.getId(), false);
redisService.delete(OrderKey.getSeckillOrderByUidGid);
redisService.delete(SeckillKey.isGoodsOver);
seckillService.reset(goodsList);
return Result.success(true);
/**
* orderId:成功
* -1:秒杀失败
* 0: 排队中
* */
@RequestMapping(value="/result", method=RequestMethod.GET)
@ResponseBody
public Result<Long> seckillResult(Model model,SeckillUser user,
@RequestParam("goodsId")long goodsId)
model.addAttribute("user", user);
if(user == null)
return Result.error(CodeMsg.SESSION_ERROR);
long result =seckillService.getSeckillResult(user.getId(), goodsId);
return Result.success(result);
package com.wings.seckill.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.wings.seckill.domain.OrderInfo;
import com.wings.seckill.domain.SeckillOrder;
import com.wings.seckill.domain.SeckillUser;
import com.wings.seckill.redis.RedisService;
import com.wings.seckill.redis.SeckillKey;
import com.wings.seckill.vo.GoodsVo;
@Service
public class SeckillService
@Autowired
GoodsService goodsService;
@Autowired
OrderService orderService;
@Autowired
RedisService redisService;
@Transactional
public OrderInfo seckill(SeckillUser user, GoodsVo goods)
//减库存 下订单 写入秒杀订单
boolean success = goodsService.reduceStock(goods);
if(success)
//order_info maiosha_order
return orderService.createOrder(user, goods);
else
setGoodsOver(goods.getId());
return null;
public long getSeckillResult(Long userId, long goodsId)
SeckillOrder order = orderService.getSeckillOrderByUserIdGoodsId(userId, goodsId);
if(order != null) //秒杀成功
return order.getOrderId();
else
boolean isOver = getGoodsOver(goodsId);
if(isOver)
return -1;
else
return 0;
private void setGoodsOver(Long goodsId)
redisService.set(SeckillKey.isGoodsOver, ""+goodsId, true);
private boolean getGoodsOver(long goodsId)
return redisService.exists(SeckillKey.isGoodsOver, ""+goodsId);
public void reset(List<GoodsVo> goodsList)
goodsService.resetStock(goodsList);
orderService.deleteOrders();
package com.wings.seckill.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.wings.seckill.dao.GoodsDao;
import com.wings.seckill.domain.SeckillGoods;
import com.wings.seckill.vo.GoodsVo;
@Service
public class GoodsService
@Autowired
GoodsDao goodsDao;
public List<GoodsVo> listGoodsVo()
return goodsDao.listGoodsVo();
public GoodsVo getGoodsVoByGoodsId(long goodsId)
return goodsDao.getGoodsVoByGoodsId(goodsId);
public boolean reduceStock(GoodsVo goods)
SeckillGoods g = new SeckillGoods();
g.setGoodsId(goods.getId());
int ret = goodsDao.reduceStock(g);
return ret > 0;
public void resetStock(List<GoodsVo> goodsList)
for(GoodsVo goods : goodsList )
SeckillGoods g = new SeckillGoods();
g.setGoodsId(goods.getId());
g.setStockCount(goods.getStockCount());
goodsDao.resetStock(g);
package com.wings.seckill.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.wings.seckill.domain.SeckillGoods;
import com.wings.seckill.vo.GoodsVo;
@Mapper
public interface GoodsDao
@Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.seckill_price from seckill_goods mg left join goods g on mg.goods_id = g.id")
public List<GoodsVo> listGoodsVo();
@Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.seckill_price from seckill_goods mg left join goods g on mg.goods_id = g.id where g.id = #goodsId")
public GoodsVo getGoodsVoByGoodsId(@Param("goodsId")long goodsId);
@Update("update seckill_goods set stock_count = stock_count - 1 where goods_id = #goodsId and stock_count > 0")
public int reduceStock(SeckillGoods g);
@Update("update seckill_goods set stock_count = #stockCount where goods_id = #goodsId")
public int resetStock(SeckillGoods g);
package com.wings.seckill.service;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.wings.seckill.dao.OrderDao;
import com.wings.seckill.domain.OrderInfo;
import com.wings.seckill.domain.SeckillOrder;
import com.wings.seckill.domain.SeckillUser;
import com.wings.seckill.redis.OrderKey;
import com.wings.seckill.redis.RedisService;
import com.wings.seckill.vo.GoodsVo;
@Service
public class OrderService
@Autowired
OrderDao orderDao;
@Autowired
RedisService redisService;
public SeckillOrder getSeckillOrderByUserIdGoodsId(long userId, long goodsId)
return redisService.get(OrderKey.getSeckillOrderByUidGid, ""+userId+"_"+goodsId, SeckillOrder.class);
public OrderInfo getOrderById(long orderId)
return orderDao.getOrderById(orderId);
@Transactional
public OrderInfo createOrder(SeckillUser user, GoodsVo goods)
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCreateDate(new Date());
orderInfo.setDeliveryAddrId(0L);
orderInfo.setGoodsCount(1);
orderInfo.setGoodsId(goods.getId());
orderInfo.setGoodsName(goods.getGoodsName());
orderInfo.setGoodsPrice(goods.getSeckillPrice());
orderInfo.setOrderChannel(1);
orderInfo.setStatus(0);
orderInfo.setUserId(user.getId());
orderDao.insert(orderInfo);
SeckillOrder seckillOrder = new SeckillOrder();
seckillOrder.setGoodsId(goods.getId());
seckillOrder.setOrderId(orderInfo.getId());
seckillOrder.setUserId(user.getId());
orderDao.insertSeckillOrder(seckillOrder);
redisService.set(OrderKey.getSeckillOrderByUidGid, ""+user.getId()+"_"+goods.getId(), seckillOrder);
return orderInfo;
public void deleteOrders()
orderDao.deleteOrders();
orderDao.deleteSeckillOrders();
package com.wings.seckill.dao;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectKey;
import com.wings.seckill.domain.OrderInfo;
import com.wings.seckill.domain.SeckillOrder;
@Mapper
public interface OrderDao
@Select("select * from seckill_order where user_id=#userId and goods_id=#goodsId")
public SeckillOrder getSeckillOrderByUserIdGoodsId(@Param("userId")long userId, @Param("goodsId")long goodsId);
@Insert("insert into order_info(user_id, goods_id, goods_name, goods_count, goods_price, order_channel, status, create_date)values("
+ "#userId, #goodsId, #goodsName, #goodsCount, #goodsPrice, #orderChannel,#status,#createDate )")
@SelectKey(keyColumn="id", keyProperty="id", resultType=long.class, before=false, statement="select last_insert_id()")
public long insert(OrderInfo orderInfo);
@Insert("insert into seckill_order (user_id, goods_id, order_id)values(#userId, #goodsId, #orderId)")
public int insertSeckillOrder(SeckillOrder seckillOrder);
@Select("select * from order_info where id = #orderId")
public OrderInfo getOrderById(@Param("orderId") long orderId);
@Delete("delete from order_info")
public void deleteOrders();
@Delete("delete from seckill_order")
public void deleteSeckillOrders();
package com.wings.seckill.redis;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
@Service
public class RedisService
@Autowired
JedisPool jedisPool;
/**
* 获取当个对象
* */
public <T> T get(KeyPrefix prefix, String key, Class<T> clazz)
Jedis jedis = null;
try
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
String str = jedis.get(realKey);
T t = stringToBean(str, clazz);
return t;
finally
returnToPool(jedis);
/**
* 设置对象
* */
public <T> boolean set(KeyPrefix prefix, String key, T value)
Jedis jedis = null;
try
jedis = jedisPool.getResource();
String str = beanToString(value);
if(str == null || str.length() <= 0)
return false;
//生成真正的key
String realKey = prefix.getPrefix() + key;
int seconds = prefix.expireSeconds();
if(seconds <= 0)
jedis.set(realKey, str);
else
jedis.setex(realKey, seconds, str);
return true;
finally
returnToPool(jedis);
/**
* 判断key是否存在
* */
public <T> boolean exists(KeyPrefix prefix, String key)
Jedis jedis = null;
try
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
return jedis.exists(realKey);
finally
returnToPool(jedis);
/**
* 删除
* */
public boolean delete(KeyPrefix prefix, String key)
Jedis jedis = null;
try
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
long ret = jedis.del(realKey);
return ret > 0;
finally
returnToPool(jedis);
/**
* 增加值
* */
public <T> Long incr(KeyPrefix prefix, String key)
Jedis jedis = null;
try
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
return jedis.incr(realKey);
finally
returnToPool(jedis);
/**
* 减少值
* */
public <T> Long decr(KeyPrefix prefix, String key)
Jedis jedis = null;
try
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
return jedis.decr(realKey);
finally
returnToPool(jedis);
public boolean delete(KeyPrefix prefix)
if(prefix == null)
return false;
List<String> keys = scanKeys(prefix.getPrefix());
if(keys==null || keys.size() <= 0)
return true;
Jedis jedis = null;
try
jedis = jedisPool.getResource();
jedis.del(keys.toArray(new String[0]));
return true;
catch (final Exception e)
e.printStackTrace();
return false;
finally
if(jedis != null)
jedis.close();
public List<String> scanKeys(String key)
Jedis jedis = null;
try
jedis = jedisPool.getResource();
List<String> keys = new ArrayList<String>();
String cursor = "0";
ScanParams sp = new ScanParams();
sp.match("*"+key+"*");
sp.count(100);
do
ScanResult<String> ret = jedis.scan(cursor, sp);
List<String> result = ret.getResult();
if(result!=null && result.size() > 0)
keys.addAll(result);
//再处理cursor
cursor = ret.getStringCursor();
while(!cursor.equals("0"));
return keys;
finally
if (jedis != null)
jedis.close();
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);
@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
return JSON.toJavaObject(JSON.parseObject(str), clazz);
private void returnToPool(Jedis jedis)
if(jedis != null)
jedis.close();
package com.wings.seckill.rabbitmq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.wings.seckill.config.MQConfig;
import com.wings.seckill.domain.SeckillOrder;
import com.wings.seckill.domain.SeckillUser;
import com.wings.seckill.redis.RedisService;
import com.wings.seckill.service.GoodsService;
import com.wings.seckill.service.OrderService;
import com.wings.seckill.service.SeckillService;
import com.wings.seckill.vo.GoodsVo;
@Service
public class MQReceiver
private Logger logger = LoggerFactory.getLogger(MQReceiver.class);
@Autowired
RedisService redisService;
@Autowired
GoodsService goodsService;
@Autowired
OrderService orderService;
@Autowired
SeckillService seckillService;
@RabbitListener(queues=MQConfig.SECKILL_QUEUE)
public void receiveSeckill(String message)
logger.info("receive message:"+message);
SeckillMessage mm = redisService.stringToBean(message, SeckillMessage.class);
SeckillUser user = mm.getUser();
long goodsId = mm.getGoodsId();
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
int stock = goods.getStockCount();
if(stock <= 0)
return;
//判断是否已经秒杀到了
SeckillOrder order = orderService.getSeckillOrderByUserIdGoodsId(user.getId(), goodsId);
if(order != null)
return;
//减库存 下订单 写入秒杀订单
seckillService.seckill(user, goods);
@RabbitListener(queues = MQConfig.QUEUE)
public void receive(String msg)
logger.info("receive:" + msg);
@RabbitListener(queues = MQConfig.TOPIC_QUEUE1)
public void receiveTopic1(String msg)
logger.info("receiveTopic1:" + msg);
@RabbitListener(queues = MQConfig.TOPIC_QUEUE2)
public void receiveTopic2(String msg)
logger.info("receiveTopic2:" + msg);
@RabbitListener(queues = MQConfig.HEADERS_QUEUE)
public void receiveHeaders(byte[] msg)
logger.info("receiveHeaders:" + new String(msg));
package com.wings.seckill.rabbitmq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.wings.seckill.config.MQConfig;
import com.wings.seckill.redis.RedisService;
@Service
public class MQSender
private Logger logger = LoggerFactory.getLogger(MQSender.class);
@Autowired
private RedisService redisService;
@Autowired
private AmqpTemplate amqpTemplate;
public void sendSeckillMessage(SeckillMessage mm)
String msg = redisService.beanToString(mm);
logger.info("send message:"+msg);
amqpTemplate.convertAndSend(MQConfig.SECKILL_QUEUE, msg);
/**
* Direct 模式交换机
*
* @param obj
*/
public void send(Object obj)
String msg = redisService.beanToString(obj);
logger.info("sender send:" + msg);
amqpTemplate.convertAndSend(MQConfig.QUEUE, msg);
public void sendTopic(Object message)
String msg = redisService.beanToString(message);
logger.info("send topic message:" + msg);
amqpTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE, "topic.key1", msg + "1");
amqpTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE, "topic.key2", msg + "2");
public void sendFanout(Object message)
String msg = redisService.beanToString(message);
logger.info("send fanout message:" + msg);
amqpTemplate.convertAndSend(MQConfig.FANOUT_EXCHANGE, "", msg);
public void sendHeaders(Object message)
String msg = redisService.beanToString(message);
logger.info("send sendHeaders message:" + msg);
MessageProperties props = new MessageProperties();
props.setHeader("header1", "value1");
props.setHeader("header2", "value2");
Message obj = new Message(msg.getBytes(), props);
amqpTemplate.convertAndSend(MQConfig.HEADERS_EXCHANGE, "", obj);
<!DOCTYPE html>
<html >
<head>
<title>商品详情</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- jquery -->
<script type="text/javascript" src="/js/jquery.min.js"></script>
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" href="/bootstrap/css/bootstrap.min.css" />
<script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script>
<!-- jquery-validator -->
<script type="text/javascript" src="/jquery-validation/jquery.validate.min.js"></script>
<script type="text/javascript" src="/jquery-validation/localization/messages_zh.min.js"></script>
<!-- layer -->
<script type="text/javascript" src="/layer/layer.js"></script>
<!-- md5.js -->
<script type="text/javascript" src="/js/md5.min.js"></script>
<!-- common.js -->
<script type="text/javascript" src="/js/common.js"></script>
</head>
<body>
<div class="panel panel-default" >
<div class="panel-heading">秒杀商品详情</div>
<div class="panel-body">
<span id="userTip"> 您还没有登录,请登陆后再操作<br/></span>
<span>没有收货地址的提示。。。</span>
</div>
<table class="table" id="goodslist">
<tr>
<td>商品名称</td>
<td colspan="3" id="goodsName"></td>
</tr>
<tr>
<td>商品图片</td>
<td colspan="3"><img id="goodsImg" width="200" height="200" /></td>
</tr>
<tr>
<td>秒杀开始时间</td>
<td id="startTime"></td>
<td >
<input type="hidden" id="remainSeconds" />
<span id="seckillTip"></span>
</td>
<td>
<!--
<form id="seckillForm" method="post" action="/seckill/do_seckill">
<button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒杀</button>
<input type="hidden" name="goodsId" id="goodsId" />
</form>-->
<button class="btn btn-primary btn-block" type="button" id="buyButton"onclick="doSeckill()">立即秒杀</button>
<input type="hidden" name="goodsId" id="goodsId" />
</td>
</tr>
<tr>
<td>商品原价</td>
<td colspan="3" id="goodsPrice"></td>
</tr>
<tr>
<td>秒杀价</td>
<td colspan="3" id="seckillPrice"></td>
</tr>
<tr>
<td>库存数量</td>
<td colspan="3" id="stockCount"></td>
</tr>
</table>
</div>
</body>
<script>
function getSeckillResult(goodsId)
g_showLoading();
$.ajax(
url:"/seckill/result",
type:"GET",
data:
goodsId:$("#goodsId").val(),
,
success:function(data)
if(data.code == 0)
var result = data.data;
if(result < 0)
layer.msg("对不起,秒杀失败");
else if(result == 0)//继续轮询
setTimeout(function()
getSeckillResult(goodsId);
, 50);
else
layer.confirm("恭喜你,秒杀成功!查看订单?", btn:["确定","取消"],
function()
window.location.href="/order_detail.htm?orderId="+result;
,
function()
layer.closeAll();
);
else
layer.msg(data.msg);
,
error:function()
layer.msg("客户端请求有误");
);
function doSeckill()
$.ajax(
url:"/seckill/do_seckill",
type:"POST",
data:
goodsId:$("#goodsId").val(),
,
success:function(data)
if(data.code == 0)
//window.location.href="/order_detail.htm?orderId="+data.data.id;
getSeckillResult($("#goodsId").val());
else
layer.msg(data.msg);
,
error:function()
layer.msg("客户端请求有误");
);
function render(detail)
var seckillStatus = detail.seckillStatus;
var remainSeconds = detail.remainSeconds;
var goods = detail.goods;
var user = detail.user;
if(user)
$("#userTip").hide();
$("#goodsName").text(goods.goodsName);
$("#goodsImg").attr("src", goods.goodsImg);
$("#startTime").text(new Date(goods.startDate).format("yyyy-MM-dd hh:mm:ss"));
$("#remainSeconds").val(remainSeconds);
$("#goodsId").val(goods.id);
$("#goodsPrice").text(goods.goodsPrice);
$("#seckillPrice").text(goods.seckillPrice);
$("#stockCount").text(goods.stockCount);
countDown();
$(function()
//countDown();
getDetail();
);
function getDetail()
var goodsId = g_getQueryString("goodsId");
$.ajax(
url:"/goods/detail/"+goodsId,
type:"GET",
success:function(data)
if(data.code == 0)
render(data.data);
else
layer.msg(data.msg);
,
error:function()
layer.msg("客户端请求有误");
);
function countDown()
var remainSeconds = $("#remainSeconds").val();
var timeout;
if(remainSeconds > 0)//秒杀还没开始,倒计时
$("#buyButton").attr("disabled", true);
$("#seckillTip").html("秒杀倒计时:"+remainSeconds+"秒");
timeout = setTimeout(function()
$("#countDown").text(remainSeconds - 1);
$("#remainSeconds").val(remainSeconds - 1);
countDown();
,1000);
else if(remainSeconds == 0)//秒杀进行中
$("#buyButton").attr("disabled", false);
if(timeout)
clearTimeout(timeout);
$("#seckillTip").html("秒杀进行中");
else//秒杀已经结束
$("#buyButton").attr("disabled", true);
$("#seckillTip").html("秒杀已经结束");
</script>
</html>
最终优化效果如下:
比原来提高了3倍QPS!
以上是关于Java秒杀实战 服务级高并发秒杀优化(RabbitMQ+接口优化)的主要内容,如果未能解决你的问题,请参考以下文章
Springcloud 微服务 高并发(实战1):第1版秒杀