【项目学习】谷粒商城学习记录9 - 消息队列,实现订单服务
【项目学习】谷粒商城学习记录9 - 消息队列,实现订单服务
一、介绍
- 作用:异步处理,应用解耦合,
- docker安装rabbitmq
- 结果:
- 结果:
二、springboot整合
1、整体步骤
2、整合与测试
-
引入spring-boot-starter-amqp依赖
<!--amqp--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
-
为了能连接上rabbitmq,进行配置
spring.rabbitmq.host=服务器ip spring.rabbitmq.port=5672 spring.rabbitmq.virtual-host=/
-
创建新交换机
@Autowired AmqpAdmin amqpAdmin; @Test public void createExchange() { DirectExchange directExchange = new DirectExchange("hello-java-exchange", true, false); amqpAdmin.declareExchange(directExchange); log.info("Exchange[{}]创建成功", "hello-java-exchange"); }
-
创建新队列
@Test public void createQueue() { Queue queue = new Queue("hello-java-queue", true, false, false); amqpAdmin.declareQueue(queue); log.info("Queue[{}]创建成功", "hello-java-queue"); }
-
创建新绑定关系
/** * 创建新绑定关系 */ @Test public void createBinding() { // String destination【目的地】 // DestinationType destinationType【目的地类型】, // String exchange【交换机】 // String routingKey 【路由键】 // Map<String, Object> arguments【自定义参数】 //将`exchange`指定的交换机和destination对象绑定,绑定方式可以是和队列也可以是和交换机, Binding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE, "hello-java-exchange", "hello.java", null); amqpAdmin.declareBinding(binding); log.info("Binding[{}]创建成功", "hello-java-binding"); }
-
发送消息(string)
@Autowired RabbitTemplate rabbitTemplate;
@Test public void sendMessageTest() { //1、发送消息 String msg = "Hello World!"; rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", msg); log.info("消息发送完成:{}", msg); }
-
发送消息(类对象)
- 为了能显示,实现配置类
@Configuration public class MyRabbitConfig { public MessageConverter messageConverter() { return new Jackson2JsonMessageConverter(); } }
- 测试代码
@Test public void sendMessageTest() { //1、发送消息 OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity(); reasonEntity.setId(1L); reasonEntity.setCreateTime(new Date()); reasonEntity.setName("哈哈"); String msg = "Hello World!"; rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", reasonEntity); log.info("消息发送完成:{}", reasonEntity); }
- 结果
- 为了能显示,实现配置类
-
监听消息
使用@RabbitListener; 必须有注解@EnableRabbit
/**
* queues:声明需要监听的所有队列
* @param message
*/
@RabbitListener(queues = {"hello-java-queue"})
public void recieveMessage(Message message, OrderReturnReasonEntity content, Channel channel) {
byte[] body = message.getBody();
MessageProperties properties = message.getMessageProperties();
System.out.println("接收到消息...内容:" + message + "===>内容:" + content);
}
- 可靠抵达
三、实现订单服务
1、准备工作:
- 整合静态资源:等待付款,订单页,结算页,收银页 分别对应detail, list, confirm, pay
- 设置域名order.gulimall.com, 重启nginx
- 配置网关
# 订单 - id: gulimall_order_route uri: lb://gulimall-order predicates: - Host=order.gulimall.com
- 实现web.HelloController
@Controller public class HelloController { @GetMapping("/{page}.html") public String listPage(@PathVariable("page") String page) { return page; } }
- 整合SpringSession和redis, 将之前的session配置类和线程池配置类复制到order服务下
- 打通页面跳转
2、订单页面介绍:
- VO抽取:
- OrderConfirmVo
@Data public class OrderConfirmVo { //收货地址 List<MemberAddressVo> address; //所有选中的购物项 List<OrderItemVo> items; //发票记录 //优惠券信息 Integer integertion; //订单总额 BigDecimal total; //应付价格 BigDecimal payPrice; }
- OrderItemVo
@Data public class OrderItemVo { /** * 商品Id */ private Long skuId; /** * 商品标题 */ private String title; /** * 商品图片 */ private String image; /** * 商品套餐信息 */ private List<String> skuAttr; /** * 商品价格 */ private BigDecimal price; /** * 数量 */ private Integer count; /** * 小计价格 */ private BigDecimal totalPrice; }
- 实现订单数据确认
@GetMapping("/toTrade") public String toTrade(Model model) throws ExecutionException, InterruptedException { OrderConfirmVo confirmVo = orderService.confirmOrder(); System.out.println("confirmVo=" + confirmVo); model.addAttribute("orderConfirmData", confirmVo); //展示订单确认的数据 return "confirm"; }
- 具体实现
@Override public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException { OrderConfirmVo confirmVo = new OrderConfirmVo(); MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get(); //获取之前的请求 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> { //1、远程查询用户地址信息 //每个线程同步主线程的请求 RequestContextHolder.setRequestAttributes(requestAttributes); List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId()); confirmVo.setAddress(address); }, executor); CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> { //2、远程查询购物车所有选中的购物项 //每个线程同步主线程的请求 RequestContextHolder.setRequestAttributes(requestAttributes); List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems(); confirmVo.setItems(items); }, executor); //3、查询用户积分 Integer integration = memberRespVo.getIntegration(); confirmVo.setIntegertion(integration); //4、其他数据自动计算 // TODO 5、防重令牌 CompletableFuture.allOf(getAddressFuture, cartFuture).get(); return confirmVo; }
- 实现订单提交数据确认
后面就一直跟着视频调前端
四、幂等性
- 介绍:就是指一个操作执行1次和多次要结果一致。如生成订单时,一个订单肯定只会要求存一次,那么如果用户多次点击提交订单按钮,就要避免多次存一个订单。
- 保持幂等性的做法:
- token令牌机制
- 各种锁机制
- 各种唯一约束
- 防重表
- 全局请求唯一id
- P283结果:
五、分布式事务
5.1、之前方法存在的问题:
- 主要就是当出现异常时,之前方法没有办法很好地控制好回滚。同时由于分布式事务涉及很多远程调用,没法通过事务机制来限制。
- 本地事务失效问题:
- 同一个对象内的事务互相调用默认失效,原因是这样做绕过了代理对象,事务使用代理对象来控制。
- 解决办法:使用代理对象来调用事务方法
- 1)、引入aop-starter;spring-boot-starter-aop; 引入aspectj
- 2)、@EnableAspectJAutoProxy(exposeProxy=true);开启 aspectj 动态代理功能。以后所有的动态代理都是aspectj创建的(即使没有接口也可以创建动态代理) 并对外暴露代理对象
- 3)、本类互相调用对象
OrderServiceImpl orderService = (OrderServiceImpl) AopContext.currentProxy(); orderService.b(); orderService.c();
5.2、seata实现分布式事务
- seata官网-快速开始
- 5.2.1、为各数据库创建日志表
CREATE TABLE `undo_log` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT, `branch_id` BIGINT(20) NOT NULL, `xid` VARCHAR(100) NOT NULL, `context` VARCHAR(128) NOT NULL, `rollback_info` LONGBLOB NOT NULL, `log_status` INT(11) NOT NULL, `log_created` DATETIME NOT NULL, `log_modified` DATETIME NOT NULL, `ext` VARCHAR(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
- 5.2.2、安装事务协调器seata-server
- 5.2.3、整合
- 导入依赖:
spring-cloud-starter-alibaba-seata
和seata-all
- 解压并启动seata-server:
registry.conf: 注册中心配置;修改registry type=nacos
file.conf - 启动测试分布式事务
- 给分布式大事务的入口标注
@GlobalTransactional
- 给远程小事务标注
@Transactional
- 导入依赖:
seata适合那种有分布式事务的场景,不过由于内部实现时会设置许多锁,会使并行事务变为串行处理,所以不适合高并发的业务场景
5.3、消息队列实现
- 5.3.1、延迟队列介绍:
- 5.3.2、延迟队列应用:
- 库存服务ware中导入amqp依赖
<!--amqp--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
- 配置相关依赖
spring.rabbitmq.host=rabbitMQ服务地址 spring.rabbitmq.port=5672 spring.rabbitmq.virtual-host=/
- 开启功能注解
@EnableRabbit
- 添加配置类,使用JSON序列化机制
@Configuration public class MyRabbitConfig { /** * 使用JSON序列化机制,进行消息转换 * @return */ @Bean public MessageConverter messageConverter() { return new Jackson2JsonMessageConverter(); } }
- 配置交换机,队列,使用Bean注解注入 (这些代码写在MyRabbitConfig中)
@RabbitListener(queues = "stock.release.stock.queue") public void handle(Message message) { } @Bean public Exchange stockEventExchange() { //String name, boolean durable, boolean autoDelete, Map<String, Object> arguments return new TopicExchange("stock-event-exchange", true, false); } @Bean public Queue stockReleaseStockQueue() { // String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments return new Queue("stock.release.stock.queue", true, false, false); } @Bean public Queue stockDelayQueue() { HashMap args = new HashMap<>(); args.put("x-dead-letter-exchange", "stock-event-exchange"); args.put("x-dead-routing-key", "stock.release"); //死信路由 args.put("x-message-ttl", 120000); //消息过期时间 return new Queue("stock.delay.queue", true, false, false); } @Bean public Binding stockLockedBinding() { /** * String destination, Binding.DestinationType destinationType, String exchange, * String routingKey, Map<String, Object> arguments */ return new Binding("stock.delay.queue", Binding.DestinationType.QUEUE, "stock-event-exchange", "stock.locked", null); } @Bean public Binding stockReleaseBinding() { return new Binding("stock.release.stock.queue", Binding.DestinationType.QUEUE, "stock-event-exchange", "stock.release.#", null); }
- 库存服务ware中导入amqp依赖
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」