谷粒商城分布式高级篇(下)

13 篇文章 0 订阅
4 篇文章 0 订阅

谷粒商城分布式基础篇
谷粒商城分布式高级篇(上)
谷粒商城分布式高级篇(中)
谷粒商城分布式高级篇(下)

商城业务

订单服务

页面环境搭建

  1. 上传静态资源文件(等待付款、订单页、结算页、收银页)至order目录detail、list、confirm、pay目录下
  2. nginx站点配置文件 加入 order.gulimall.com 域名
  3. 网关配置
    在这里插入图片描述
  4. 加入thymeleaf依赖并禁用他的缓存
  5. 注册进nacos

整合SpringSession

  1. 引入依赖 加入配置spring.session.store-type=redis,导入session配置类,导入线程池配置类
  2. 引入redis依赖和配置
  3. 启动开启reids session缓存 @EnableRedisHttpSession

订单基本概念

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

订单登录拦截

购物车列表点击去结算会跳转至Order模块,Order模块都需要加上拦截功能判断登录

  1. 创建一个拦截器,拦截器实现的是SringMvc的 HandlerInterceptor 重写 拦截前置方法preHandle 添加具体拦截逻辑

在这里插入图片描述

  1. 将拦截器注册在配置类中,并设置拦截路径为全路径
    在这里插入图片描述

订单确认页模型抽取

订单确认页要用到的数据

订单确认页数据获取

各个信息的获取拼接
远程调用会员接口,查询地址列表,创建@FeignClient("gulimall-member")接口,启动类启用远程注解,被调用的服务写好业务

Feign远程调用丢失请求头问题源码分析

问题:登录状态后 远程调用 购物车服务 发现购物车服务 session的 member为空,而单独访问购物车服务 则不为空
在这里插入图片描述

原因:在业务方法 cartFeignService.getCurrentUserCartItems(); 是 feign 远程调用,区别于页面直接访问购物车,页面放请求时 会携带 cookie
在这里插入图片描述
源码分析 feign 的远程调用

  1. 断点至购物车的远程调用,service 是 feignclien的代理对象
    在这里插入图片描述
  2. stepinto进入方法实现,首先判断是否是object的公共方法,明显不是,而后获取当前方法再invoke,最后一段再 setpinto
    在这里插入图片描述
  3. invoke内,因为远程调用的方法没有传参所以参数为空,真正的执行在executeAndDecode方法内,stepinto
    在这里插入图片描述
  4. 传入的是上方法第一步创建的请求模板,模板内 GET方法 和 url地址 得到当前请求 Request,然后用客户端去执行response = client.execute(request, options);,先去看看怎么得到request

在这里插入图片描述
5. request 的返回,发现会遍历所有RequestInterceptor拿所有的 request拦截器来 apply 增强,所以feign在远程调用之前要构造请求,调用很多的拦截器来增强。因为现在还没有配置拦截器,feign也没有进行增强,所以将原生默认的RequestTemplate 返回
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
默认的 request 没有携带请求头,而这个请求的总发起/toTrade方法,是携带了 header 和 cookie信息的,但是feign在进行远程调用的时候 创建了一个新的请求头且没有任何信息。总结这就是feign的远程调用问题
在这里插入图片描述

  1. 解决请求头丢失:加上feign远程调用的请求拦截器,feign的实现会遍历所有拦截器
    在这里插入图片描述
    创建 一个feign的配置类,配置类中包含了拦 截器,通过,拦截器实现的方法apply 就是具体的增强,增强的目的就是加上 为feign的远程调用加上 这次请求发起的请求头。获取请求头:Spring提供的RequestContextHolder上下文环境保持器类得到当前请求的所有属性,将得到的请求属性放入feign的请求头信息
    在这里插入图片描述

在这里插入图片描述

Feign异步调用丢失请求头问题

将两次远程调用做成异步查询
在这里插入图片描述
发现拦截器获取老请求为空 空指针异常,未能获取到当前请求的上下文信息
在这里插入图片描述
启用异步线程来完成远程调用会在不同线程中执行,打印一下线程号
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
发现确实是在不同的线程中,因为 RequestContextHolderRequestAttributes 的存储用的ThreadLocal ,所以只要线程不一样,RequestAttributes就不一样
在这里插入图片描述
解决 RequestAttributes不一样的办法:同步主线程的 RequestAttributes
在这里插入图片描述

在这里插入图片描述

订单确认页渲染

订单确认页库存查询

在购物车查询完每个商品以后,继续.thenRunAsync 异步来查询所有商品的库存信息,拿到购物车所有商品的id集合,远程调用库存服务

在这里插入图片描述
OrderConfirmVo 类中加入库存 Map<Long,Boolean> stocks; 属性 商品id为键值

订单确认页模拟运费效果

点击选择地址后发送http://gulimall.com/api/ware/wareinfo/fare?addrId=? 请求计算运费,库存服务远程调用member服务计算并返回运费

订单确认页细节显示

接口幂等性讨论

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
可以给订单号做唯一约束,保证幂等性
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

订单确认页完成

在这里插入图片描述
订单确认页生成一个token,以便在提交订单时验证

原子验令牌

主要操作为传统的java内对比替换为 redis的lua脚本操作

MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
String redisToken = redisTemplate.opsForValue().get(key);
if (orderToken !=null && orderToken.equals(redisToken)){
    //令牌验证通过

原子验证令牌和删除令牌

//0失败 - 1成功 | 不存在0 存在 删除?1:0
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
String orderToken = vo.getOrderToken();
//原子验证令牌 和 删除令牌
Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
        Arrays.asList(key),
        orderToken);

构造订单数据

构造订单项数据

订单验价

各种繁琐的类的装配,没意思

保存订单数据

锁定库存

提交订单的问题

问题记录:获取远程查询记录后,用fastJson进行逆转时字段为null,原因为json待转字段名必须与被转类字段名保存一致

在这里插入图片描述
在这里插入图片描述

分布式事务

本地事务在分布式下的问题

在这里插入图片描述

  1. 假失败,远程调用的业务成功,但是因为网络原因没有返回成功信息,本地事务回滚,造成了数据不一致
  2. 本地事务出现异常,而先前远程调用已经成功,无法对远程调用进行事务回滚

@Transactional本地事务,在分布式系统,只能控制自己的回滚,控制不了其他服务的回滚
分布式事务:最大原因,网络问题+分布式机器

本地事务隔离级别&传播行为等复习

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
事务传播举例
在这里插入图片描述
本地事务失效问题,本类方法调用

spring-boot中的事务的坑,若a、b、c都在一个service文件中,a中调用b、c方法,b、c方法的事务设置不会起任何作用,原因是spring的事务是使用事务来控制的。所以同一个对象内事务方法互调事务设置失效,原因是绕过了代理对象

解决:使用代理对象调用事务方法
1)、引入aop-starter; spring-boot-starter-aop引入aspectj
2)、启动类@EnableAspectjAutoProxy(exposeProxy = true)开启 aspectj动态代理功能 以后所有动态代理都是aspectj
3) 、本类互调用代理对象
在这里插入图片描述

在这里插入图片描述

分布式CAP&Raft原理

为什么会有分布式事务
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
分布式系统中实现一致性的raft算法实现动画演示 官方演示

BASE

在这里插入图片描述

分布式事务常见解决方案

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Seata&环境准备

Seata
Seata控制分布式事务
1、每一个微服务必须先创建undo_log
2、安装事务协调器 seata-server

Seata分布式事务体验

1、在common中导入seata依赖,注意seata-all版本是多少就启动对应的seata-server
在这里插入图片描述
在这里插入图片描述

2、解压seata-server 并配置conf目录下registry.conf文件,配置为nacos和nacos地址,默认的配置在file.conf
在这里插入图片描述
3、开启全局事务 方法上加入@GlobalTransactional 注解,注意要在这之前注入seata的代理数据源 DataSourceProxy,因为seata想要控制住事务,得让默认的数据源给 seata 包装后让seata代理数据源,seata才能控制事务。所以,所有想要用到分布式事务的微服务得使用seata DataSourceProxy代理自的数据源

spring 默认数据源是在 DataSourceAutoConfiguration 中自动配置
在这里插入图片描述
其中会导入各种数据源,而spring的默认源数据是 Hikari
在这里插入图片描述
在配置Hikari数据源是在容器中添加@Bean组件,组件就是HikariDataSource,所有的配置 通过DataSourcePropertiesspring.datasource.hikari 绑定
在这里插入图片描述
而值得注意的是 注解@ConditionalOnMissingBean(DataSource.class) 意思是在容器中没有自定义的数据源的时候才会导入下面的 Hikari 数据源,所以在我配置自己的数据源后,默认的数据源就会失效
seata的官方也提醒了应该用seata提供的代理数据源包装原有的数据源
在这里插入图片描述

所以得手动注入Hikari数据源,然后使用seata代理数据源包装返回

分析数据源自动配置类源码,数据源的导入都是在DataSourceConfiguration中
在这里插入图片描述
创建Hikari数据源就是调用了DataSourceConfiguration 的create方法,传入的是这两个参数
DataSourceProperties 和指定数据源
在这里插入图片描述
在这里插入图片描述

所以自定义的配置类 直接仿造create中的方法实现就能手动返回一个Hikari数据源了,最后用seata代理包装返回
在这里插入图片描述
4、将seata-server目录文件中的 file.conf registry.conf 复制在 resource 目录下,更改 file 中的配置
vgroup_mapping.{application.name}-fescar-service-group = "default"
在这里插入图片描述
5、给事务的入口标注@GlobalTransactional 远程的小事务用·@Transactional

最终一致性库存解锁逻辑

消息队列完成最终一致性

订单服务

RabbitMQ延时队

单独开了一栏 消息队列实战

支付 支付宝沙箱&代码

商场支付宝接入实战

商城秒杀

商城秒杀服务

Sentinel & Zipkin

Sentinel & Zipkin

总结

在这里插入图片描述

end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值