rabbitmq+redis在优化秒杀商品接口中的使用实例
文章目錄
- 基本配置
- java rabbitmq config:
- java redis config
- 實(shí)體
- 系統(tǒng)初始化
- 消息發(fā)送和接收者
- controller
接口優(yōu)化的思路:(目的:減少數(shù)據(jù)庫訪問)
1.系統(tǒng)初始化,把商品庫存加載到redis
2.收到請(qǐng)求,redis減庫存,如果庫存不足則直接返回,否則進(jìn)入下一步
3.請(qǐng)求入隊(duì),立即返回排隊(duì)中
4.請(qǐng)求出隊(duì),生成訂單,減少庫存(如果訂單生成失敗則不減去庫存)
5.客戶端輪詢,判斷是否秒殺成功
基本配置
#redis redis.host=10.110.3.62 redis.port=6379 redis.timeout=10 redis.password=123456 redis.poolMaxTotal=1000 redis.poolMaxIdle=500 redis.poolMaxWait=500#rabbitmq spring.rabbitmq.host=10.110.3.62 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附:Rabbitmq的一些其他配置
spring.rabbitmq.requested-heartbeat: 請(qǐng)求心跳超時(shí)時(shí)間,0為不指定,如果不指定時(shí)間單位默認(rèn)為妙 spring.rabbitmq.publisher-confirms: 是否啟用【發(fā)布確認(rèn)】,默認(rèn)false spring.rabbitmq.publisher-returns: 是否啟用【發(fā)布返回】,默認(rèn)false spring.rabbitmq.connection-timeout: 連接超時(shí)時(shí)間,單位毫秒,0表示永不超時(shí) spring.rabbitmq.listener.type=simple: 容器類型.simple或directspring.rabbitmq.listener.simple.auto-startup=true: 是否啟動(dòng)時(shí)自動(dòng)啟動(dòng)容器 spring.rabbitmq.listener.simple.acknowledge-mode: 表示消息確認(rèn)方式,其有三種配置方式,分別是none、manual和auto;默認(rèn)auto spring.rabbitmq.listener.simple.concurrency: 最小的消費(fèi)者數(shù)量 spring.rabbitmq.listener.simple.max-concurrency: 最大的消費(fèi)者數(shù)量 spring.rabbitmq.listener.simple.prefetch: 一個(gè)消費(fèi)者最多可處理的nack消息數(shù)量,如果有事務(wù)的話,必須大于等于transaction數(shù)量. spring.rabbitmq.listener.simple.transaction-size: 當(dāng)ack模式為auto時(shí),一個(gè)事務(wù)(ack間)處理的消息數(shù)量,最好是小于等于prefetch的數(shù)量.若大于prefetch, 則prefetch將增加到這個(gè)值 spring.rabbitmq.listener.simple.default-requeue-rejected: 決定被拒絕的消息是否重新入隊(duì);默認(rèn)是true(與參數(shù)acknowledge-mode有關(guān)系) spring.rabbitmq.listener.simple.missing-queues-fatal=true 若容器聲明的隊(duì)列在代理上不可用,是否失敗; 或者運(yùn)行時(shí)一個(gè)多多個(gè)隊(duì)列被刪除,是否停止容器 spring.rabbitmq.listener.simple.idle-event-interval: 發(fā)布空閑容器的時(shí)間間隔,單位毫秒 spring.rabbitmq.listener.simple.retry.enabled=false: 監(jiān)聽重試是否可用 spring.rabbitmq.listener.simple.retry.max-attempts=3: 最大重試次數(shù) spring.rabbitmq.listener.simple.retry.max-interval=10000ms: 最大重試時(shí)間間隔 spring.rabbitmq.listener.simple.retry.initial-interval=1000ms:第一次和第二次嘗試傳遞消息的時(shí)間間隔 spring.rabbitmq.listener.simple.retry.multiplier=1: 應(yīng)用于上一重試間隔的乘數(shù) spring.rabbitmq.listener.simple.retry.stateless=true: 重試時(shí)有狀態(tài)or無狀態(tài)spring.rabbitmq.listener.direct.acknowledge-mode= ack模式 spring.rabbitmq.listener.direct.auto-startup=true 是否在啟動(dòng)時(shí)自動(dòng)啟動(dòng)容器 spring.rabbitmq.listener.direct.consumers-per-queue= 每個(gè)隊(duì)列消費(fèi)者數(shù)量. spring.rabbitmq.listener.direct.default-requeue-rejected= 默認(rèn)是否將拒絕傳送的消息重新入隊(duì). spring.rabbitmq.listener.direct.idle-event-interval= 空閑容器事件發(fā)布時(shí)間間隔. spring.rabbitmq.listener.direct.missing-queues-fatal=false若容器聲明的隊(duì)列在代理上不可用,是否失敗. spring.rabbitmq.listener.direct.prefetch= 每個(gè)消費(fèi)者可最大處理的nack消息數(shù)量. spring.rabbitmq.listener.direct.retry.enabled=false 是否啟用發(fā)布重試機(jī)制. spring.rabbitmq.listener.direct.retry.initial-interval=1000ms # Duration between the first and second attempt to deliver a message. spring.rabbitmq.listener.direct.retry.max-attempts=3 # Maximum number of attempts to deliver a message. spring.rabbitmq.listener.direct.retry.max-interval=10000ms # Maximum duration between attempts. spring.rabbitmq.listener.direct.retry.multiplier=1 # Multiplier to apply to the previous retry interval. spring.rabbitmq.listener.direct.retry.stateless=true # Whether retries are stateless or stateful.java rabbitmq config:
@Configuration public class MQConfig {public static final String MIAOSHA_QUEUE = "miaosha.queue";public static final String QUEUE = "queue";public static final String TOPIC_QUEUE1 = "topic.queue1";public static final String TOPIC_QUEUE2 = "topic.queue2";public static final String HEADER_QUEUE = "header.queue";public static final String TOPIC_EXCHANGE = "topicExchage";public static final String FANOUT_EXCHANGE = "fanoutxchage";public static final String HEADERS_EXCHANGE = "headersExchage";/*** Direct模式 交換機(jī)Exchange* */@Beanpublic Queue queue() {return new Queue(QUEUE, true);}/*** Topic模式 交換機(jī)Exchange* */@Beanpublic Queue topicQueue1() {return new Queue(TOPIC_QUEUE1, true);}@Beanpublic Queue topicQueue2() {return new Queue(TOPIC_QUEUE2, true);}@Beanpublic TopicExchange topicExchage(){return new TopicExchange(TOPIC_EXCHANGE);}@Beanpublic Binding topicBinding1() {return BindingBuilder.bind(topicQueue1()).to(topicExchage()).with("topic.key1");}@Beanpublic Binding topicBinding2() {return BindingBuilder.bind(topicQueue2()).to(topicExchage()).with("topic.#");}/*** Fanout模式 交換機(jī)Exchange* */@Beanpublic FanoutExchange fanoutExchage(){return new FanoutExchange(FANOUT_EXCHANGE);}@Beanpublic Binding FanoutBinding1() {return BindingBuilder.bind(topicQueue1()).to(fanoutExchage());}@Beanpublic Binding FanoutBinding2() {return BindingBuilder.bind(topicQueue2()).to(fanoutExchage());}/*** Header模式 交換機(jī)Exchange* */@Beanpublic HeadersExchange headersExchage(){return new HeadersExchange(HEADERS_EXCHANGE);}@Beanpublic Queue headerQueue1() {return new Queue(HEADER_QUEUE, true);}@Beanpublic Binding headerBinding() {Map<String, Object> map = new HashMap<String, Object>();map.put("header1", "value1");map.put("header2", "value2");return BindingBuilder.bind(headerQueue1()).to(headersExchage()).whereAll(map).match();}}java redis config
redis連接工廠:
@Service public class RedisPoolFactory {@AutowiredRedisConfig redisConfig;@Beanpublic JedisPool JedisPoolFactory() {JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle());poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait() * 1000);JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),redisConfig.getTimeout()*1000, redisConfig.getPassword(), 0);return jp;}}redisservice
@Data @Component @ConfigurationProperties(prefix="redis") public class RedisConfig {private String host;private int port;private int timeout;//秒private String password;private int poolMaxTotal;private int poolMaxIdle;private int poolMaxWait;//秒} @Service public class RedisService {@AutowiredJedisPool jedisPool;/*** 獲取當(dāng)個(gè)對(duì)象* */public <T> T get(KeyPrefix prefix, String key, Class<T> clazz) {Jedis jedis = null;try {jedis = jedisPool.getResource();//生成真正的keyString realKey = prefix.getPrefix() + key;String str = jedis.get(realKey);T t = stringToBean(str, clazz);return t;}finally {returnToPool(jedis);}}/*** 設(shè)置對(duì)象* */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;}//生成真正的keyString 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();//生成真正的keyString 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();//生成真正的keyString 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();//生成真正的keyString 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();//生成真正的keyString 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);}//再處理cursorcursor = 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();}}}實(shí)體
消息實(shí)體:
@Data public class MiaoshaMessage {private MiaoshaUser user;private long goodsId;}用戶實(shí)體:
@Data public class MiaoshaUser {private Long id;private String nickname;private String password;private String salt;private String head;private Date registerDate;private Date lastLoginDate;private Integer loginCount;}系統(tǒng)初始化
關(guān)于InitializingBean 接口的介紹:
由需要在BeanFactory設(shè)置所有屬性后做出反應(yīng)的 bean 實(shí)現(xiàn)的接口:例如,執(zhí)行自定義初始化,或僅檢查是否已設(shè)置所有必需屬性。
實(shí)現(xiàn)InitializingBean的另一種方法是指定自定義 init 方法,例如在 XML bean 定義中。 有關(guān)所有 bean 生命周期方法的列表
在初始化方法中使用了一個(gè)本地HashMap<Long, Boolean> localOverMap ,進(jìn)一步減少對(duì)redis的訪問
@Controller @RequestMapping("/miaosha") public class MiaoshaController implements InitializingBean { @AutowiredMiaoshaUserService userService; @AutowiredRedisService redisService; @AutowiredGoodsService goodsService; @AutowiredOrderService orderService; @AutowiredMiaoshaService miaoshaService; @AutowiredMQSender sender;private HashMap<Long, Boolean> localOverMap = new HashMap<Long, Boolean>();/*** 系統(tǒng)初始化* */@Overridepublic void afterPropertiesSet() throws Exception {List<GoodsVo> goodsList = goodsService.listGoodsVo();if(goodsList == null) {return;}for(GoodsVo goods : goodsList) {redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), goods.getStockCount());localOverMap.put(goods.getId(), false);}}//初始化時(shí)將庫存全部加載到redis中消息發(fā)送和接收者
@Service public class MQSender {private static Logger log = LoggerFactory.getLogger(MQSender.class);@AutowiredAmqpTemplate amqpTemplate ;public void sendMiaoshaMessage(MiaoshaMessage mm) {String msg = RedisService.beanToString(mm);log.info("send message:"+msg);amqpTemplate.convertAndSend(MQConfig.MIAOSHA_QUEUE, msg);} @Service public class MQReceiver {private static Logger log = LoggerFactory.getLogger(MQReceiver.class);@AutowiredRedisService redisService;@AutowiredGoodsService goodsService;@AutowiredOrderService orderService;@AutowiredMiaoshaService miaoshaService;@RabbitListener(queues=MQConfig.MIAOSHA_QUEUE)public void receive(String message) {log.info("receive message:"+message);MiaoshaMessage mm = RedisService.stringToBean(message, MiaoshaMessage.class);MiaoshaUser user = mm.getUser();long goodsId = mm.getGoodsId();GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);int stock = goods.getStockCount();if(stock <= 0) {return;}//判斷是否已經(jīng)秒殺到了MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);//從Redis中取訂單if(order != null){return;//如果已經(jīng)下過單,直接返回}//減庫存 下訂單 寫入秒殺訂單miaoshaService.miaosha(user, goods);//寫入到redis中}}其中g(shù)etMiaoshaOrderByUserIdGoodsId方法:
public MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(long userId, long goodsId) {//return orderDao.getMiaoshaOrderByUserIdGoodsId(userId, goodsId);return redisService.get(OrderKey.getMiaoshaOrderByUidGid, ""+userId+"_"+goodsId, MiaoshaOrder.class);}方法 miaosha((MiaoshaUser user, GoodsVo goods) :
@Transactionalpublic OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {//減庫存 下訂單 寫入秒殺訂單boolean success = goodsService.reduceStock(goods);if(success) {//order_info maiosha_orderreturn orderService.createOrder(user, goods);}else {setGoodsOver(goods.getId());return null;}}controller
@RequestMapping(value="/do_miaosha", method=RequestMethod.POST)@ResponseBodypublic Result<Integer> miaosha(Model model,MiaoshaUser user,@RequestParam("goodsId")long goodsId) {model.addAttribute("user", user);//渲染前端的變量if(user == null) {return Result.error(CodeMsg.SESSION_ERROR);}//內(nèi)存標(biāo)記,減少redis訪問boolean over = localOverMap.get(goodsId);if(over) {return Result.error(CodeMsg.MIAO_SHA_OVER);}//預(yù)減庫存long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);//從redis中獲得庫存量然后減庫存,減少對(duì)數(shù)據(jù)庫的訪問if(stock < 0) {localOverMap.put(goodsId, true);return Result.error(CodeMsg.MIAO_SHA_OVER);}//判斷是否已經(jīng)秒殺到了MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);if(order != null) {return Result.error(CodeMsg.REPEATE_MIAOSHA);}//入隊(duì)MiaoshaMessage mm = new MiaoshaMessage();mm.setUser(user);mm.setGoodsId(goodsId);sender.sendMiaoshaMessage(mm);//在這里進(jìn)行 MiaoshaMessage消息的發(fā)送,消息進(jìn)入隊(duì)列中return Result.success(0);//排隊(duì)中}//用于重置的方法 @RequestMapping(value="/reset", method=RequestMethod.GET)@ResponseBodypublic Result<Boolean> reset() {List<GoodsVo> goodsList = goodsService.listGoodsVo();for(GoodsVo goods : goodsList) {goods.setStockCount(10);redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), 10);localOverMap.put(goods.getId(), false);}redisService.delete(OrderKey.getMiaoshaOrderByUidGid);redisService.delete(MiaoshaKey.isGoodsOver);miaoshaService.reset(goodsList);return Result.success(true);}//取得秒殺結(jié)果的接口/*** orderId:成功* -1:秒殺失敗* 0: 排隊(duì)中* */@RequestMapping(value="/result", method=RequestMethod.GET)@ResponseBodypublic Result<Long> miaoshaResult(Model model,MiaoshaUser user,@RequestParam("goodsId")long goodsId) {model.addAttribute("user", user);if(user == null) {return Result.error(CodeMsg.SESSION_ERROR);}long result =miaoshaService.getMiaoshaResult(user.getId(), goodsId);return Result.success(result);//從redis中判斷商品庫存是否足夠}}//miaoshaService.getMiaoshaResult的方法public long getMiaoshaResult(Long userId, long goodsId) {MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(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(MiaoshaKey.isGoodsOver, ""+goodsId, true);}private boolean getGoodsOver(long goodsId) {return redisService.exists(MiaoshaKey.isGoodsOver, ""+goodsId);}public void reset(List<GoodsVo> goodsList) {goodsService.resetStock(goodsList);orderService.deleteOrders();}//其中orderService的getMiaoshaOrderByUserIdGoodsId(long userId, long goodsId) 方法public MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(long userId, long goodsId) {//return orderDao.getMiaoshaOrderByUserIdGoodsId(userId, goodsId);return redisService.get(OrderKey.getMiaoshaOrderByUidGid, ""+userId+"_"+goodsId, MiaoshaOrder.class);}//從相應(yīng)的鍵中得到值并轉(zhuǎn)化為MiaoshaOrder對(duì)象總結(jié)
以上是生活随笔為你收集整理的rabbitmq+redis在优化秒杀商品接口中的使用实例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【学习笔记】Redis的geohash数
- 下一篇: springsecurity实现自定义S