1. 订单状态与超时
2. 订单超时处理方案 2.1 定时任务 间隔1分钟
每隔1分钟,定时任务触发,查询订单(1.待支付 2.下单时间 超过30分钟),满足的订单,更改订单的状态为超时状态,同时释放:库存、优惠卷、积分(京豆、淘金币)、满减优惠、成长值等等。
2.2 Redis 设置有效期30分钟,失效监听
参考1:https://blog.csdn.net/VringSbsda/article/details/105199832
参考2:https://blog.csdn.net/for_the_time_begin/article/details/90376873
参考3:https://blog.csdn.net/ypp91zr/article/details/105635225
下单的时候,同时在Redis中存储订单(String类型 有效期30分钟,key: 订单编号 value 时间戳),在Redis的配置文件中,开启失效监听,在 redis.conf 中 开启:**notify-keyspace-events Ex
**同时在程序中,实现 KeyExpirationEventMessageListener 。完成Key的失效监听。只能获取到失效的key,但是此时不能根据key获取value值,因为该事件是在数据失效后才触发。获取订单号查询数据库,是否为待支付,如果hi待支付,订单需要更改为超时订单,同时释放资源。
Redis 开启键空间通知功能需要消耗一些 CPU,所以在默认该功能处于关闭状态
。
开启该功能需要修改 redis.conf 中的 notify-keyspace-events
参数,参数可以是以下字符的任意组合, 它指定了服务器该发送哪些类型的通知。
输入的参数中至少要有一个 K 或者 E,否则的话,不管其余的参数是什么,都不会有任何通知被分发。
如:
notify-keyspace-events “Ex” 表示对过期事件进行通知发送;
notify-keyspace-events “kx” 表示想监控某个 key 的失效事件;
将参数设为字符串 AKE 表示发送所有类型的通知。
1 2 3 4 5 6 过期事件测试: > subscribe __keyevent@0__:expired setex name 10 txl
2.3 RabbitMQ 死信消息和死信队列
下单的时候,将订单信息发送到-延迟队列(设置消息超时时间为30分钟,不需要消费者),然后为延迟队列设置死信交换器和路由匹配,如果消息超过30分钟,就会变成死信消息,就会通过死信交换器转发到对应的死信队列(需要有消费者),消息消费者监听死信队列,获取的消息(超时消息30分钟后)。查询数据库,订单是否支付,如果订单状态为待支付,那么就需要把订单状态更改为超时状态,同时释放资源。
3. 超时订单-死信队列实现 3.1 数据库表 demo 数据库和表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 CREATE DATABASE db_orderapi; USE db_orderapi; -- 订单和支付服务 -- 订单表 CREATE TABLE t_order ( id BIGINT PRIMARY KEY, aid INT COMMENT '收货地址', money INT, discountmoney INT, uid INT, paymoney INT, flag INT COMMENT '订单状态: 1待支付 2待发货 3待收货 4待评价 5已评价 6超时订单 7取消订单', ctime DATETIME ); -- 订单详情表 CREATE TABLE t_orderitem ( id BIGINT PRIMARY KEY AUTO_INCREMENT, oid BIGINT, gid INT, gskuid INT, num INT, price INT, ctime DATETIME ); -- 订单流水表 CREATE TABLE t_orderlog ( id BIGINT PRIMARY KEY AUTO_INCREMENT, oid BIGINT, STATUS INT COMMENT '订单状态', info VARCHAR (50), ctime DATETIME );
3.2 配置死信队列 基于 RabbitMQ 的死信队列超时订单 配置类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import org.springframework.amqp.core.*;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.HashMap;import java.util.Map;@Configuration public class RabbitConfig { private String qname1 = "open:order:timeoutorder" ; private String qname2 = "open:order:dlxtimeorder" ; private String exchange1 = "open:dlxexchange" ; private String routkey1 = "open:rout:timeoutorder" ; @Bean public Queue createQ1 () { Map<String, Object> params = new HashMap <>(); params.put("x-message-ttl" , 1800000 ); params.put("x-dead-letter-exchange" , exchange1); params.put("x-dead-letter-routingkey" , routkey1); return QueueBuilder.durable(qname1).withArguments(params).build(); } @Bean public Queue createQ2 () { return new Queue (qname2); } @Bean public DirectExchange createDE () { return new DirectExchange (exchange1); } @Bean public Binding createBD (DirectExchange exchange) { return BindingBuilder.bind(createQ2()).to(exchange).with(routkey1); } }
3.3 监听死信队列
1 2 3 4 5 6 7 8 9 10 public enum OrderFlag { 待支付(1 ), 待发货(2 ), 待确认(3 ), 待评价(4 ), 已评价(5 ), 超时订单(6 ), 取消订单(7 ); private int val; public int getVal () { return val; } private OrderFlag (int v) { val = v; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @Data @AllArgsConstructor @NoArgsConstructor public class MqMsgDto <T> implements Serializable { private long id; private int type; private T obj; }import org.springframework.amqp.rabbit.annotation.RabbitHandler;import org.springframework.amqp.rabbit.annotation.RabbitListener;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.transaction.annotation.Transactional;@Component @RabbitListener(queues = "open:order:dlxtimeorder") public class TimeOutOrderListener { @Autowired private OrderDao dao; @RabbitHandler @Transactional public void handler (MqMsgDto<Long> dto) { long oid = dto.getObj(); Order order = dao.selectById(oid); if (order != null ) { if (order.getFlag().equals(OrderFlag.待支付.getVal())) { if (dao.updateFlag(oid, OrderFlag.超时订单.getVal()) > 0 ) { } } } } }
3.3 生成订单逻辑 生成订单逻辑流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import org.springframework.amqp.rabbit.core.RabbitTemplate;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.web.client.RestTemplate;import java.util.HashMap;import java.util.Map;@Service public class OrderServiceImpl implements OrderService { @Autowired private OrderDao orderDao; @Autowired private RestTemplate restTemplate; @Autowired private RabbitTemplate rabbitTemplate; @Autowired private IdGenerator generator; @Autowired private RedissonUtil redissonUtil; @Override public R createOrder (GoodsOrderDto dto) { GoodsSkuDto goodsSkuDto = restTemplate.getForObject("http://goodsserver/server/goods/sku.do?skuid=" + dto.getSkuid(), GoodsSkuDto.class); if (goodsSkuDto != null ) { try { redissonUtil.lock("open:order:createorder" ); if (goodsSkuDto.getRepertory() >= dto.getNum()) { Order order = new Order (); order.setAid(dto.getAid()); order.setFlag(1 ); order.setUid(dto.getUid()); order.setMoney(dto.getNum() * (int ) (goodsSkuDto.getSaleprice() * 100 )); order.setDiscountmoney(dto.getDmoney()); order.setPaymoney(order.getMoney() - order.getDiscountmoney()); if (orderDao.save(order) > 0 ) { Map<String, Object> map = new HashMap <>(); map.put("skuid" , dto.getSkuid()); map.put("num" , dto.getNum()); R r = restTemplate.postForObject("changeSku" , map, R.class); if (r.getCode() == ResultConfig.R_OK) { MqMsgDto<Long> msgDto = new MqMsgDto <>(generator.nextId(), 1 , order.getId()); rabbitTemplate.convertAndSend(null , "open:order:timeoutorder" , msgDto); return R.ok(); } } } } finally { redissonUtil.unlock("open:order:createorder" ); } } return R.error(); } }
3.4 ID生成-雪花算法 ID生成器 工具类完善可CV
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 public class IdGenerator { private final long twepoch = 1420041600000L ; private final long workerIdBits = 5L ; private final long datacenterIdBits = 5L ; private final long maxWorkerId = -1L ^ (-1L << workerIdBits); private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); private final long sequenceBits = 12L ; private final long workerIdShift = sequenceBits; private final long datacenterIdShift = sequenceBits + workerIdBits; private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; private final long sequenceMask = -1L ^ (-1L << sequenceBits); private long workerId; private long datacenterId; private long sequence = 0L ; private long lastTimestamp = -1L ; public IdGenerator (long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0 ) { throw new IllegalArgumentException (String.format( "worker Id can't be greater than %d or less than 0" , maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0 ) { throw new IllegalArgumentException (String.format( "datacenter Id can't be greater than %d or less than 0" , maxDatacenterId)); } this .workerId = workerId; this .datacenterId = datacenterId; } public IdGenerator () { this .workerId = 0l ; this .datacenterId = 0l ; } ; public synchronized long nextId () { long timestamp = timeGen(); if (timestamp < lastTimestamp) { throw new RuntimeException ( String.format( "Clock moved backwards. Refusing to generate id for %d milliseconds" , lastTimestamp - timestamp)); } if (lastTimestamp == timestamp) { sequence = (sequence + 1 ) & sequenceMask; if (sequence == 0 ) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L ; } lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; } protected long tilNextMillis (long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } protected long timeGen () { return System.currentTimeMillis(); } }
3.5 分布式锁 分布式锁根据情况可修改
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import org.redisson.Redisson;import org.redisson.api.RedissonClient;import org.redisson.config.Config;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class RedissonUtil { private static RedissonUtil redissonUtil; public static RedissonUtil getInstance (String host, int port, String pass) { Lock lock = null ; try { lock = new ReentrantLock (); lock.lock(); if (redissonUtil == null ) { redissonUtil = new RedissonUtil (host, port, pass); } } finally { lock.unlock(); } return redissonUtil; } private RedissonClient client; private RedissonUtil (String host, int port, String pass) { Config config = new Config (); config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(pass); client = Redisson.create(config); } public void saveStr (String key, String value) { client.getBucket(key).set(value); } public void lock (String key) { client.getLock(key).lock(); } public void lock (String key, long timeseconds) { client.getLock(key).lock(timeseconds, TimeUnit.SECONDS); } public void unlock (String key) { client.getLock(key).unlock(); } public boolean checkLock (String key) { return client.getLock(key).isLocked(); } }
3.6 依赖注入Bean 以配置类方式将 工具类 注入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Configuration public class RibbonConfig { @Bean @LoadBalanced public RestTemplate createRT () { return new RestTemplate (); } }@Configuration public class IdGeneratorConfig { @Bean public IdGenerator createID () { return new IdGenerator (); } }@Configuration public class RedissonConfig { @Value("...") private String host; @Value("...") private int port; @Value("...") private String pass; @Bean public RedissonUtil createRU () { return RedissonUtil.getInstance(host,port,pass); } }@Configuration public class RibbonConfig { @Bean @LoadBalanced public RestTemplate createRT () { return new RestTemplate (); } }