- 基本配置
- java rabbitmq config:
- java redis config
- 实体
- 系统初始化
- 消息发送和接收者
- controller
接口优化的思路:(目的:减少数据库访问)
1.系统初始化,把商品库存加载到redis
2.收到请求,redis减库存,如果库存不足则直接返回,否则进入下一步
3.请求入队,立即返回排队中
4.请求出队,生成订单,减少库存(如果订单生成失败则不减去库存)
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=/ #u6D88u8D39u8005u6570u91CF spring.rabbitmq.listener.simple.concurrency= 10 spring.rabbitmq.listener.simple.max-concurrency= 10
附:Rabbitmq的一些其他配置
spring.rabbitmq.requested-heartbeat: 请求心跳超时时间,0为不指定,如果不指定时间单位默认为妙 spring.rabbitmq.publisher-/confirm/is: 是否启用【发布确认】,默认false spring.rabbitmq.publisher-returns: 是否启用【发布返回】,默认false spring.rabbitmq.connection-timeout: 连接超时时间,单位毫秒,0表示永不超时 spring.rabbitmq.listener.type=simple: 容器类型.simple或direct spring.rabbitmq.listener.simple.auto-startup=true: 是否启动时自动启动容器 spring.rabbitmq.listener.simple.acknowledge-mode: 表示消息确认方式,其有三种配置方式,分别是none、manual和auto;默认auto spring.rabbitmq.listener.simple.concurrency: 最小的消费者数量 spring.rabbitmq.listener.simple.max-concurrency: 最大的消费者数量 spring.rabbitmq.listener.simple.prefetch: 一个消费者最多可处理的nack消息数量,如果有事务的话,必须大于等于transaction数量. spring.rabbitmq.listener.simple.transaction-size: 当ack模式为auto时,一个事务(ack间)处理的消息数量,最好是小于等于prefetch的数量.若大于prefetch, 则prefetch将增加到这个值 spring.rabbitmq.listener.simple.default-requeue-rejected: 决定被拒绝的消息是否重新入队;默认是true(与参数acknowledge-mode有关系) spring.rabbitmq.listener.simple.missing-queues-fatal=true 若容器声明的队列在代理上不可用,是否失败; 或者运行时一个多多个队列被删除,是否停止容器 spring.rabbitmq.listener.simple.idle-event-interval: 发布空闲容器的时间间隔,单位毫秒 spring.rabbitmq.listener.simple.retry.enabled=false: 监听重试是否可用 spring.rabbitmq.listener.simple.retry.max-attempts=3: 最大重试次数 spring.rabbitmq.listener.simple.retry.max-interval=10000ms: 最大重试时间间隔 spring.rabbitmq.listener.simple.retry.initial-interval=1000ms:第一次和第二次尝试传递消息的时间间隔 spring.rabbitmq.listener.simple.retry.multiplier=1: 应用于上一重试间隔的乘数 spring.rabbitmq.listener.simple.retry.stateless=true: 重试时有状态or无状态 spring.rabbitmq.listener.direct.acknowledge-mode= ack模式 spring.rabbitmq.listener.direct.auto-startup=true 是否在启动时自动启动容器 spring.rabbitmq.listener.direct.consumers-per-queue= 每个队列消费者数量. spring.rabbitmq.listener.direct.default-requeue-rejected= 默认是否将拒绝传送的消息重新入队. spring.rabbitmq.listener.direct.idle-event-interval= 空闲容器事件发布时间间隔. spring.rabbitmq.listener.direct.missing-queues-fatal=false若容器声明的队列在代理上不可用,是否失败. spring.rabbitmq.listener.direct.prefetch= 每个消费者可最大处理的nack消息数量. spring.rabbitmq.listener.direct.retry.enabled=false 是否启用发布重试机制. 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"; @Bean public Queue queue() { return new Queue(QUEUE, true); } @Bean public Queue topicQueue1() { return new Queue(TOPIC_QUEUE1, true); } @Bean public Queue topicQueue2() { return new Queue(TOPIC_QUEUE2, true); } @Bean public TopicExchange topicExchage(){ return new TopicExchange(TOPIC_EXCHANGE); } @Bean public Binding topicBinding1() { return BindingBuilder.bind(topicQueue1()).to(topicExchage()).with("topic.key1"); } @Bean public Binding topicBinding2() { return BindingBuilder.bind(topicQueue2()).to(topicExchage()).with("topic.#"); } @Bean public FanoutExchange fanoutExchage(){ return new FanoutExchange(FANOUT_EXCHANGE); } @Bean public Binding FanoutBinding1() { return BindingBuilder.bind(topicQueue1()).to(fanoutExchage()); } @Bean public Binding FanoutBinding2() { return BindingBuilder.bind(topicQueue2()).to(fanoutExchage()); } @Bean public HeadersExchange headersExchage(){ return new HeadersExchange(HEADERS_EXCHANGE); } @Bean public Queue headerQueue1() { return new Queue(HEADER_QUEUE, true); } @Bean public Binding headerBinding() { Mapjava redis configmap = new HashMap (); map.put("header1", "value1"); map.put("header2", "value2"); return BindingBuilder.bind(headerQueue1()).to(headersExchage()).whereAll(map).match(); } }
redis连接工厂:
@Service public class RedisPoolFactory { @Autowired RedisConfig redisConfig; @Bean public 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 { @Autowired JedisPool jedisPool; public实体T get(KeyPrefix prefix, String key, Class 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 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); } } public 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 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 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 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 scanKeys(String key) { Jedis jedis = null; try { jedis = jedisPool.getResource(); List keys = new ArrayList (); String cursor = "0"; ScanParams sp = new ScanParams(); sp.match("*"+key+"*"); sp.count(100); do{ ScanResult ret = jedis.scan(cursor, sp); List 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 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 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 { return JSON.toJavaObject(JSON.parseObject(str), clazz); } } private void returnToPool(Jedis jedis) { if(jedis != null) { jedis.close(); } } }
消息实体:
@Data public class MiaoshaMessage { private MiaoshaUser user; private long goodsId; }
用户实体:
@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; }系统初始化
关于InitializingBean 接口的介绍:
由需要在BeanFactory设置所有属性后做出反应的 bean 实现的接口:例如,执行自定义初始化,或仅检查是否已设置所有必需属性。
实现InitializingBean的另一种方法是指定自定义 init 方法,例如在 XML bean 定义中。 有关所有 bean 生命周期方法的列表
在初始化方法中使用了一个本地HashMap
@Controller @RequestMapping("/miaosha") public class MiaoshaController implements InitializingBean { @Autowired MiaoshaUserService userService; @Autowired RedisService redisService; @Autowired GoodsService goodsService; @Autowired OrderService orderService; @Autowired MiaoshaService miaoshaService; @Autowired MQSender sender; private HashMap消息发送和接收者localOverMap = new HashMap (); @Override public void afterPropertiesSet() throws Exception { List goodsList = goodsService.listGoodsVo(); if(goodsList == null) { return; } for(GoodsVo goods : goodsList) { redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), goods.getStockCount()); localOverMap.put(goods.getId(), false); } }//初始化时将库存全部加载到redis中
@Service public class MQSender { private static Logger log = LoggerFactory.getLogger(MQSender.class); @Autowired AmqpTemplate 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); @Autowired RedisService redisService; @Autowired GoodsService goodsService; @Autowired OrderService orderService; @Autowired MiaoshaService 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; } //判断是否已经秒杀到了 MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);//从Redis中取订单 if(order != null) { return;//如果已经下过单,直接返回 } //减库存 下订单 写入秒杀订单 miaoshaService.miaosha(user, goods);//写入到redis中 } }
其中getMiaoshaOrderByUserIdGoodsId方法:
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) :
@Transactional public OrderInfo miaosha(MiaoshaUser 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; } }controller
@RequestMapping(value="/do_miaosha", method=RequestMethod.POST) @ResponseBody public Resultmiaosha(Model model,MiaoshaUser 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.MIAO_SHA_OVER); } //预减库存 long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);//从redis中获得库存量然后减库存,减少对数据库的访问 if(stock < 0) { localOverMap.put(goodsId, true); return Result.error(CodeMsg.MIAO_SHA_OVER); } //判断是否已经秒杀到了 MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId); if(order != null) { return Result.error(CodeMsg.REPEATE_MIAOSHA); } //入队 MiaoshaMessage mm = new MiaoshaMessage(); mm.setUser(user); mm.setGoodsId(goodsId); sender.sendMiaoshaMessage(mm);//在这里进行 MiaoshaMessage消息的发送,消息进入队列中 return Result.success(0);//排队中 } //用于重置的方法 @RequestMapping(value="/reset", method=RequestMethod.GET) @ResponseBody public Result reset() { List 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); } //取得秒杀结果的接口 @RequestMapping(value="/result", method=RequestMethod.GET) @ResponseBody public Result 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 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); }//从相应的键中得到值并转化为MiaoshaOrder对象
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)