1. 程式人生 > >Java秒殺實戰 (六) 服務級高併發秒殺優化(RabbitMQ+介面優化)

Java秒殺實戰 (六) 服務級高併發秒殺優化(RabbitMQ+介面優化)

一、思路:減少資料庫訪問

1.系統初始化,把商品庫存數量載入到Redis

2.收到請求,Redis預減庫存,庫存不足,直接返回,否則進入3

3.請求入隊,立即返回排隊中

4.請求出隊,生成訂單,減少庫存

5.客戶端輪詢,是否秒殺成功

二、安裝RabbitMQ及其相關依賴

下載erlang

下載rabbitMQ

安裝相關依賴

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!