【云岚到家】-day05-1-预约下单-熔断降级
1 预约下单
1.1 需求分析
完成了后台服务管理、服务上架的开发,接下来进入预约下单模块。
在预约下单模块我们需要完成下单接口的设计与开发,对接支付系统完成在线支付。
首先分析预约下单模块的需求。
1.1.1 核心业务流程
首先明确本模块在核心业务流程的位置,下图是项目的核心业务流程:
服务上架后用户端即可在小程序搜索到区域下的服务,点击服务进行预约下单和支付。
1.1.2 界面原型
在首页服务列表区域、热门服务列表区域以及全部服务界面,点击服务项名称进入服务详情页面,如下图:
点击立即预约填写预约信息:
选择上门服务地址,此地址从我的地址簿中选择。
选择上门时间
填写完成点击“立即预约”
进入支付页面:
支付成功下单完成。
1.1.3 订单状态
订单数据中有一个属性关系到订单的整个流程,就是订单状态。
本项目订单状态共有7种,如下图:
待支付:订单的初始状态。
派单中:用户支付成功后订单的状态由待支付变为派单中。
待服务:服务人员或机构抢单成功订单的状态由派单中变为待服务。
服务中:服务人员开始服务,订单状态变为服务中。
订单完成:服务人员完成服务订单状态变为订单完成。
已取消:订单是待支付状态时用户取消订单,订单状态变为已取消。
已关闭:订单已支付状态下取消订单后订单状态变为已关闭。
1.2 系统设计
1.2.1 订单表设计
1.2.1.1 订单表设计方案
在设计订单表时通常采用的结构是订单主表与订单明细表一对多关系结构,比如:在电商系统中,一个订单购买的多件不同的商品,设计订单表和订单明细表:
订单表:记录订单号、订单金额、下单人、订单状态等信息。
订单明细表:记录该订单购买商品的信息,包括:商品名称、商品价格、交易价格、购买商品数量等。
如下图:
如果系统需求是一个订单只包括一种商品,此时无须记录订单明细,将购买商品的详细信息记录在订单表即可,设计字段包括:订单号、订单金额、下单人、订单状态、商品名称、购买商品数量等。
下边根据需求设计本项目的订单表,本项目用户购买服务没有购物车,选择一个服务开始下单,所以本项目不设计订单明细表只设计订单表。
如果是电商类,一个订单主表中可能包含多个项目,就需要维护一张订单明细表,来记录订单中的各个项目明细。而我们的家政项目,并不存在购物车,一个服务对应一个项目,故只需维护一张订单主表即可。
1.2.1.2 表结构设计
下边设计本项目订单表的表结构。
除了订单号、订单金额、订单状态、下单人ID等字段外,订单表还存储哪些信息?
根据需求梳理预约下单提交的数据如下:
属性 | 含义 |
---|---|
订单所属人 | 创建订单的用户id |
服务项 | 用户选择服务项 |
城市编码 | 用户定位的城市编码 |
单价 | 服务的单位 |
购买数量 | 购买服务的数量 |
订单总金额 | 单价乘以购买数量 |
优惠金额 | 根据优惠券计算的优惠金额 |
实际支付金额 | 订单总金额减去优惠金额 |
服务详细地址 | 家政服务的具体地址 |
服务开始时间 | 服务预约时间 |
联系人手机号 | 购买家政服务联系人的手机号 |
联系人姓名 | 购买家政服务联系人的姓名 |
经度 | 家政服务具体地址的经度,来源于我的地址 |
纬度 | 家政服务具体地址的纬度,来源于我的地址 |
订单状态 | 订单状态代码,下单后状态是未支付状态 |
通过分析,订单表包括以下几部分:
订单基础信息:订单号、订单状态、排序字段、是否显示标记等。
价格信息:单价、购买数量、优惠金额、订单总金额等。
下单人信息:下单人ID、联系方式、位置信息(相当于收货地址)等。
服务(商品)相关信息:服务类型名称、服务项名称、服务单价、价格单位、购买数量等。
服务信息相当于商品,如果有订单明细表要在订单明细表中存储,本项目将服务相关信息存储在订单表。
1、表结构如下
create table `jzo2o-orders`.orders
(
id bigint not null comment '订单id'
constraint `PRIMARY`
primary key,
user_id bigint not null comment '订单所属人',
serve_type_id bigint null comment '服务类型id',
serve_type_name varchar(50) null comment '服务类型名称',
serve_item_id bigint not null comment '服务项id',
serve_item_name varchar(50) null comment '服务项名称',
serve_item_img varchar(255) null comment '服务项图片',
unit int null comment '服务单位',
serve_id bigint not null comment '服务id',
orders_status int not null comment '订单状态,0:待支付,100:派单中,200:待服务,300:服务中,400:待评价,500:订单完成,600:已取消,700:已关闭',
pay_status int null comment '支付状态,2:待支付,4:支付成功',
refund_status int null comment '退款状态 1退款中 2退款成功 3退款失败',
price decimal(10, 2) not null comment '单价',
pur_num int default 1 not null comment '购买数量',
total_amount decimal(10, 2) not null comment '订单总金额',
real_pay_amount decimal(10, 2) not null comment '实际支付金额',
discount_amount decimal(10, 2) not null comment '优惠金额',
city_code varchar(20) not null comment '城市编码',
serve_address varchar(255) not null comment '服务详细地址',
contacts_phone varchar(20) not null comment '联系人手机号',
contacts_name varchar(255) not null comment '联系人姓名',
serve_start_time datetime not null comment '服务开始时间',
lon double(10, 5) null comment '经度',
lat double(10, 5) null comment '纬度',
pay_time datetime null comment '支付时间',
evaluation_time datetime null comment '评价时间',
trading_order_no bigint null comment '支付服务交易单号',
transaction_id varchar(50) null comment '第三方支付的交易号',
refund_no bigint null comment '支付服务退款单号',
refund_id varchar(50) null comment '第三方支付的退款单号',
trading_channel varchar(50) null comment '支付渠道',
display int default 1 null comment '用户端是否展示,1:展示,0:隐藏',
sort_by bigint null comment '排序字段,serve_start_time毫秒级时间戳+订单id后六位',
create_time datetime default CURRENT_TIMESTAMP not null,
update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP
)
comment '订单表' charset = utf8mb4;
2、数据来源分析
字段名称 | 中文含义 | 来源 |
---|---|---|
id | 订单id | 自动生成 19位:2位年+2位月+2位日+13位序号 |
user_id | 订单所属人id | 从token中获取 |
serve_id | 服务id | 前端请求 |
serve_type_id | 服务类型id | 根据服务id远程调用运营基础服务查询 |
serve_type_name | 服务类型名称 | 根据服务id远程调用运营基础服务查询 |
serve_item_id | 服务项id | 根据服务id远程调用运营基础服务查询 |
serve_item_name | 服务项名称 | 根据服务id远程调用运营基础服务查询 |
serve_item_img | 服务项图片 | 根据服务id远程调用运营基础服务查询 |
unit | 服务单位 | 根据服务id远程调用运营基础服务查询 |
orders_status | 订单状态 | 设置为 未支付 |
pay_status | 支付状态 | 设置为 未支付 |
price | 单价 | 根据服务id远程调用运营基础服务查询 |
pur_num | 购买数量 | 前端请求 |
total_amount | 订单总金额 | 订单总金额 价格 * 购买数量 |
real_pay_amount | 实际支付金额 | 实付金额 订单总金额 - 优惠金额 |
discount_amount | 优惠金额 | 根据优惠券加订单总金额计算优惠金额,暂时为0 |
city_code | 城市编码 | 根据服务id远程调用运营基础服务查询 |
serve_address | 服务详细地址 | 远程调用客户中心服务查询我的地址获得 |
contacts_phone | 联系人手机号 | 远程调用客户中心服务查询我的地址获得 |
contacts_name | 联系人姓名 | 远程调用客户中心服务查询我的地址获得 |
serve_start_time | 服务开始时间 | 前端请求 |
lon | 经度 | 远程调用客户中心服务查询我的地址获得 |
lat | 纬度 | 远程调用客户中心服务查询我的地址获得 |
pay_time | 支付时间 | 对接支付服务获取 |
evaluation_time | 评价时间 | 用户评价的时间,预留 |
trading_order_no | 支付服务交易单号 | 对接支付服务,支付服务生成的交易单号,支付完成填充 |
transaction_id | 第三方支付的交易号 | 微信支付的交易号,支付完成填充 |
refund_no | 支付服务退款单号 | 对接支付服务,支付服务生成的退款单号,退款完成填充 |
refund_id | 第三方支付的退款单号 | 微信支付的退款单号,退款完成填充 |
trading_channel | 支付渠道 | 微信、支付等,支付完成填充 |
display | 用户端是否展示,1:展示,0:隐藏 | 默认为1 |
sort_by | 排序字段 | 根据服务开始时间转为毫秒时间戳+订单后5位 |
create_time | 创建时间 | 数据库控制,默认当前时间 |
update_time | 更新时间 | 数据库控制,更新时默认当前时间 |
1.2.2 搭建订单工程
1.2.2.1 搭建订单工程
1)搭建订单工程
工程名 | 服务名 | 职责 |
---|---|---|
jzo2o-orders-base | 订单模块基础工程 | 提供数据模型、数据访问基础mapper,供其它工程通过maven依赖。 |
jzo2o-orders-manager | 订单管理服务 | 预约下单、服务管理、取消订单等订单管理相关的接口。 |
jzo2o-orders-seize | 抢单服务 | 为服务人员和机构抢单提供服务。 |
jzo2o-orders-dispatch | 派单服务 | 根据派单规则自动为服务人员、机构派送订单。 |
jzo2o-orders-history | 历史订单服务 | 订单冷热分离,历史订单查询接口。 |
整个订单模块包括:订单管理、抢单、派单、历史订单四个小模块,对应的工程如下:
jzo2o-orders-base作为其它四个工程的公共工程,提供订单模块基础的mapper接口、数据模型等内容。
当前我们实现的预约下单小模块属于订单管理模块,所以我们共创建两个工程:jzo2o-orders-base和jzo2o-orders-manager。
在Git远程仓库创建jzo2o-orders仓库,并获得仓库地址。
从课程资料的源码目录解压jzo2o-orders-01-0.zip下的代码到代码目录jzo2o-orders,
在gitee上创建一个新的仓库jzo2o-orders
以前都是用idea的git去提交仓库的,现在换一种方式用命令行
通过下边的命令提交至jzo2o-orders仓库:
git init #初始化本地git仓库
git add . #将目录中的文件加入暂存区
git commit -m '初始orders工程' #将代码提交至本地仓库
复制远程仓库地址
https://gitee.com/sjb-bblb/jzo2o-orders.git
git remote add origin https://gitee.com/sjb-bblb/jzo2o-orders.git #指定远程仓库地址
git push -u origin "master" #将本地仓库的代码提交至远程仓库
刷新远程仓库
已经提交上来
用ieda打开jzo2o-orders,发现maven报错
检查jdk版本,编译器版本,maven仓库地址
更换maven仓库地址之后,没有报错
创建dev_01分支并推送,预约下单功能在dev_01分支下开发
2)创建订单数据库:jzo2o-orders
总共十二张表
修改nacos中jzo2o-orders-manager.yaml,先屏蔽sentinel功能,我们之后再使用
1.2.2.2 搭建订单依赖的工程
在jzo2o-customer工程创建dev_02分支并切换到该分支,从课程资料中解压jzo2o-customer-02-0.zip,将代码覆盖dev_02分支中的代码。
在dev_02中提交并推送
在jzo2o-foundations工程创建dev_03分支并切换到该分支,从课程资料中解压jzo2o-foundations.zip(完整代码),将代码覆盖dev_03分支中的代码。
删除压缩包中foundations中的**.git和.idea还有target**,粘贴到我们的工作目录
提交并推送
修改custom的数据库选择为
mysql:
db-name: jzo2o-customer-backup
1.2.3 接口设计
1.2.3.1 设计接口
根据需求分析及订单表的设计进行接口分析:
除了serve_id、pur_num、serve_start_time 由前端传入以外还需要传入以下参数:
优惠券ID:用户选择优惠券,系统根据优惠券的信息计算优惠金额,需要前端传入优惠券的Id。
我的地址簿ID:用户从我的地址簿中选择地址,前端传入我的地址簿Id,系统从我的地址簿中查询服务地址及具体的经纬度坐标。
接口定义如下:
接口名称:下单接口
接口功能:普通用户创建订单
接口路径:POST/orders-manager/consumer/orders/place
请求数据类型:application/json
请求参数
响应参数
1.2.3.2 Controller
在com.jzo2o.orders.manager.controller.consumer.ConsumerOrdersController中
@ApiOperation("下单接口")
@PostMapping("/place")
public PlaceOrderResDTO place(@RequestBody PlaceOrderReqDTO placeOrderReqDTO) {
return null;
}
1.2.3.3 Service
在com.jzo2o.orders.manager.service.IOrdersCreateService中
接口
public PlaceOrderResDTO placeOrder(PlaceOrderReqDTO placeOrderReqDTO);
实现
@Slf4j
@Service
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {
@Override
public PlaceOrderResDTO placeOrder(PlaceOrderReqDTO placeOrderReqDTO) {
//1.生成订单
//1.1 订单基本信息
//1.2 下单人信息-远程调用jzo2o-customer服务
//1.3 订单服务信息-远程调用jzo2o-foundations服务
//1.4 订单价格信息
//1.5 封装
//1.6 插入数据库
return;
}
}
1.3 系统开发
1.3.1 开发远程接口
下单接口保存的数据较多,有一些数据需要远程调用来获取:
- 根据地址簿Id远程调用客户中心,查询我的地址簿信息。
- 根据服务Id远程调用运营基础服务,查询服务相关的信息。
1.3.1.1 查询地址簿远程接口
微服务之间远程调用的接口统一抽取后定义在jzo2o-api工程。
查询地址簿远程接口是根据地址簿ID查询地址簿信息,接口定义如下:
接口路径:GET/customer/inner/address-book/{id}
请求数据类型 application/x-www-form-urlencoded
1.3.1.1.1 在jzo2o-api工程定义接口
在jzo2o-api工程定义我的地址簿远程查询接口
这里使用@FeignClient注解实现Feign远程调用,此注解是openfeign下的注解,OpenFeign 是 Spring Cloud 对 Feign 进行了集成,并提供了对 Spring Cloud 注解的支持。
开发一个远程接口,应该先开发服务端,再开发客户端,先在远程调用的jzo2o-api中写个接口,再在具体实现的微服务中先开发controller,之后在抽取接口后让controller实现,再把接口放到调用的微服务例如jzo2o-api中。之后针对新抽取的在jzo2o-api中的新远程调用接口,用maven的install安装到jzo2o-api中,生成jar包,放到仓库,其他微服务依赖jzo2o-api即可实现远程调用
创建com.jzo2o.api.customer.AddressBookApi
@FeignClient(contextId = "jzo2o-customer", value = "jzo2o-customer", path = "/customer/inner/address-book")
public interface AddressBookApi {
@GetMapping("/{id}")
AddressBookResDTO detail(@PathVariable("id") Long id);
}
contextId
:上下文id,如果没有contextId,那Spring生成的代理对象后要放到Spring容器中进行管理,名字就是类名首字母小写addressBookApi,为了防止冲突,我们通过contextId = "jzo2o-customer"来保证不同微服务下相同的容器名不会冲突。value
:我们要访问的微服务名称path
:路径
然后通过maven的install安装到本地仓库,如果是在实际开发中,则需要deploy到私有服务器供大家使用。
1.3.1.1.2 在jzo2o-customer工程定义实现类
返回jzo2o-customer搜索AddressBookApi,已经在仓库中搜到这个接口了
创建com.jzo2o.customer.controller.inner.InnerAddressBookController
@RestController
@RequestMapping("inner/address-book")
@Api(tags = "内部接口 - 地址薄相关接口")
public class InnerAddressBookController implements AddressBookApi {
@Resource
private IAddressBookService addressBookService;
@Override
@GetMapping("/{id}")
@ApiOperation("地址薄详情")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "地址薄id", required = true, dataTypeClass = Long.class)
})
public AddressBookResDTO detail(@PathVariable("id") Long id) {
AddressBook addressBook = addressBookService.getById(id);
return BeanUtil.toBean(addressBook, AddressBookResDTO.class);
}
}
1.3.1.2 查询服务信息远程接口
和1.3.1.1同理,这里不在赘述
创建com.jzo2o.api.foundations.ServeApi
@FeignClient(contextId = "jzo2o-foundations", value = "jzo2o-foundations", path ="/foundations/inner/serve")
public interface ServeApi {
@GetMapping("/{id}")
ServeAggregationResDTO findById(@PathVariable("id") Long id);
}
在jzo2o-foundations中
@RestController
@RequestMapping("/inner/serve")
@Api(tags = "内部接口 - 服务相关接口")
public class InnerServeController implements ServeApi {
@Resource
private IServeService serveService;
@Override
@GetMapping("/{id}")
@ApiOperation("根据id查询服务")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "服务项id", required = true, dataTypeClass = Long.class)
})
public ServeAggregationResDTO findById(@NotNull(message = "id不能为空") @PathVariable("id") Long id) {
return serveService.findServeDetailById(id);
}
}
1.3.1.3 Feign和OpenFeign的区别?
Feign 是 Netflix 公司开发的一个独立的项目,在使用 Spring Cloud 时,需要单独引入 Feign 的依赖。
OpenFeign 是 Spring Cloud 对 Feign 进行了集成,并提供了对 Spring Cloud 注解的支持。
Feign 使用了一套自己的注解,例如 @FeignClient
用于声明一个 Feign 客户端,@RequestMapping
用于声明请求的映射等。
OpenFeign 则直接使用了 Spring MVC 注解,例如 @GetMapping
、@PostMapping
等,这使得 OpenFeign 更加和 Spring 生态集成。
从spring boot 2.0之后基本上都是使用OpenFeign 了。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.3.2 熔断降级
1.3.2.1 什么是熔断降级
在微服务架构一定要去预防微服务雪崩问题,微服务雪崩问题是指在微服务架构中,当一个服务出现故障时,由于服务之间的依赖关系,故障可能会传播到其他服务,导致大规模的服务失败,系统无法正常运行。这种情况就像雪崩一样,最初一个小问题最终引发了整个系统的崩溃。简单理解微服务雪崩就是微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。
常用的预防微服务雪崩的的方法:
超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待。
熔断降级:当服务的异常数或异常比例超过了预设的阈值时,熔断器会进入开启状态,暂时中断对该服务的请求,此时走降级方法,能够快速响应,确保系统的基本功能能够继续运行。
限流:限制对服务的请求速率,避免短时间内大量的请求导致系统崩溃。
线程池隔离:给要请求的资源分配一个线程池,线程池去控制请求数量
信号量隔离:使用计数器模式,记录请求资源的并发线程数量,达到信号量上限时,禁止新的请求。
信号量隔离适合同步请求,控制并发数,比如:对文件的下载并发数进行控制。
大多数场景都适合使用线程池隔离,对于需要同步操作控制并发数的场景可以使用信号量隔离。
1.3.2.2 使用sentinel实现熔断降级-前言
本项目使用Sentinel实现限流、熔断等机制预防微服务雪崩。
熔断降级是微服务保护的一种方法,当使用Feign进行远程调用,在客户端通过熔断降级措施进行微服务保护。
如下图:
orders-manager订单服务请求customer查询地址簿,在进行feign远程调用过程出现异常将走降级方法,当异常比例或异常数达到一定的阈值将触发熔断,熔断期间将直接走降级逻辑快速响应。
当customer服务恢复后,熔断时间结束此时会再次尝试请求customer,如果成功请求将关闭熔断,恢复原来的链路。
下边使用sentinel实现熔断、降级。
首先参考第三方软件安装说明” 安装sentinel并在orders-manager工程中集成sentinel。
1.3.2.3 安装Sentinel
从课程资料–》开发环境配置目录找到“sentinel”目录获取sentinel.zip,并上传至/data/soft目录下
解压sentinel.zip:
unzip sentinel.zip
进入/data/soft/sentinel目录,如下所示:
注意修改docker-compose.yml中的nacos的地址及nacos的账号和密码:
version: "3.8"
services:
sentinel:
container_name: sentinel-dashboard
image: sentinel-dashboard:latest
ports:
- "8080:8080"
environment:
- "TZ=Asia/Shanghai"
# nacos访问地址+端口号
- SENTINEL_NACOS_SERVER_ADDR=192.168.101.68:8848
# nacos访问账号
- SENTINEL_NACOS_USERNAME=nacos
# nacos访问密码
- SENTINEL_NACOS_PASSWORD=nacos
# nacos访问命名空间
- SENTINEL_NACOS_NAMESPACE=75a593f5-33e6-4c65-b2a0-18c403d20f63
# sentinel dashboard平台登录账号
- SENTINEL_USERNAME=sentinel
# sentinel dashboard平台登录密码
- SENTINEL_PASSWORD=sentinel
# sentinel dashboard 访问端口号
- SENTINEL_PORT=8080
执行下边的命令创建镜像,如下:
docker build -t sentinel-dashboard .
下边启动容器:
docker-compose up -d
启动容器成功,通过docker ps命令查看容器:
接下来访问sentinel ,地址:http://192.168.101.68:8080/#/login 需要输入账号和密码,默认都是:sentinel
1.3.2.4 项目集成sentinel
熔断降级是发生在客户端的,所以应该是客户端的微服务集成sentinel
1.3.2.4.1 添加nacos配置
添加nacos配置文件shared-sentinel.yaml,如下:
spring:
cloud:
sentinel:
transport:
# 供sentinel dashboard平台访问端口
port: 8719
# sentinel控制台
dashboard: 192.168.101.68:8080
#服务启动直接建立心跳连接
eager: true
发布
注意:
1)、spring.cloud.sentinel.transport.port端口可以无需修改,如果多个微服务运行在同一个物理机时,同时都要占用8719端口,记得修改端口号,保证sentinel.transport.port端口不冲突,如果使用docker部署一定要将该端口开放
2)、spring.cloud.sentinel.transport.dashboard需要配置成自己sentinel-dashboard的访问地址
1.3.2.4.2 引入nacos配置
在项目中引入shared-sentinel.yaml配置文件。
只需要在远程调用的调用方(客户端)配置sentinel即可,订单管理服务调用 客户管理服务,只需要在订单管理服务配置sentienl。
比如我们orders服务需要调用,所以在orders微服务中引入sentinel
1.3.2.4.3 添加依赖
<dependency>
<groupId>com.jzo2o</groupId>
<artifactId>jzo2o-sentinel</artifactId>
</dependency>
注意:
1)、hibernate-validator版本不要高于6,sentinel 1.8.5版本对于hibernate-validator 6以上版本存在不兼容问题。
2)、sentinel-dashboard注册中心和配置中心一定要和微服务保持一致,不然读不到熔断配置。
1.3.2.5 使用sentinel实现熔断降级-实现
@EnableFeignClients
是一个类级别的注解,主要用于启动Feign客户端扫描。当添加这个注解到你的Spring Boot应用主类或配置类上时,Spring Boot将会自动扫描指定包路径下的所有@FeignClient注解的接口,并为每个接口创建一个实现类,这个实现类实际上就是一个HTTP客户端,能够以声明式的方式调用远程服务。
那怎么加载这个类呢?
通过resources/META-INF/spring.factories来进行自动加载
所以jzo2o-api就把这些接口通过@EnableFeignClients
进行扫描加载实例容器中,所以其他微服务只用引用jzo2o-api即可。
那么比如orders服务依赖jzo2o-api即可直接注入接口。
但是有一点非常重要!!!!!
降级逻辑
我们以前使用feign进行熔断降级时,对@FeignClient
我们平时只需要添加对应的fallback(),然后写同名的降级函数即可,例如:
但是仔细思考,我们的熔断降级发生在客户端,不同客户端调用服务端有着不同的降级逻辑,如果我们在服务端这里写死fallback,那么任何的客户端调用都对应相同的fallback逻辑,这明显时不对的。
根据上图可知,熔断、降级发生在客户端,下边在订单管理服务(调用customer的客户端)定义CustomerClient类用于请求customer服务。
所以新建com.jzo2o.orders.manager.service.impl.client.CustomerClient,代表在orders服务中调用customer的服务逻辑及熔断降级逻辑
@Component
@Slf4j
public class CustomerClient {
@Resource
private AddressBookApi addressBookApi;
@SentinelResource(value = "getAddressBookDetail", fallback = "detailFallback", blockHandler = "detailBlockHandler")
public AddressBookResDTO getDetail(Long id) {
log.error("根据id查询地址簿,id:{}", id);
// 调用其他微服务方法
AddressBookResDTO detail = addressBookApi.detail(id);
return detail;
}
//执行异常走
public AddressBookResDTO detailFallback(Long id, Throwable throwable) {
log.error("非限流、熔断等导致的异常执行的降级方法,id:{},throwable:", id, throwable);
return null;
}
//熔断后的降级逻辑
public AddressBookResDTO detailBlockHandler(Long id, BlockException blockException) {
log.error("触发限流、熔断时执行的降级方法,id:{},blockException:", id, blockException);
return null;
}
}
@SentinelResource
注解的属性说明:
value
: 用于定义资源的名称,即 Sentinel 会对该资源进行流量控制和熔断降级。fallback
:非限流、熔断等导致的异常执行的降级方法blockHandler
:触发限流、熔断时执行的降级方法
那么我们的创建订单服务的类就可以注入我们刚刚的CustomerClient来进行访问。
下边在下单方法中通过CustomerClient 调用customer:
@Slf4j
@Service
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {
@Resource
private CustomerClient customerClient;
@Override
public PlaceOrderResDTO placeOrder(PlaceOrderReqDTO placeOrderReqDTO) {
//1.生成订单
//1.1 订单基本信息
//1.2 下单人信息-远程调用jzo2o-customer服务
AddressBookResDTO detail = customerClient.getDetail(placeOrderReqDTO.getAddressBookId());
//1.3 订单服务信息-远程调用jzo2o-foundations服务
//1.4 订单价格信息
//1.5 封装
//1.6 插入数据库
return null;
}
}
然后controller调用service,serivce再调用OrdersCreateServiceImpl实现类来完成feign远程调用
@RestController("consumerOrdersController")
@Api(tags = "用户端-订单相关接口")
@RequestMapping("/consumer/orders")
public class ConsumerOrdersController {
@Resource
private IOrdersManagerService ordersManagerService;
@Resource
private IOrdersCreateService ordersCreateService;
@ApiOperation("下单接口")
@PostMapping("/place")
public PlaceOrderResDTO place(@RequestBody PlaceOrderReqDTO placeOrderReqDTO) {
PlaceOrderResDTO orderResDTO = ordersCreateService.placeOrder(placeOrderReqDTO);
return null;
}
1.3.2.6 测试
测试流程:
1、启动OrderManagerApplication,查看sentinel
2、通过接口文档测试下单接口,触发customerClient.getDetail(addressBookId);
打开http://localhost:11504/orders-manager/doc.html#/home,因为我们没有启动customer,所以调用必是异常
3、在sentinel中配置熔断规则
这个就是@SentinelResource(value = "getAddressBookDetail", fallback = "detailFallback", blockHandler = "detailBlockHandler")
里的value
我们就可以在这里设置熔断规则
5秒以内最少请求5次,有2次异常则进行熔断。熔断时长为30秒。
3、停止customer服务,进行测试
由于customer服务停止导致远程调用异常,调用fallback 方法:
具体流程是:先调用getDetail(Long id) 方法,发生异常调用detailFallback(Long id, Throwable throwable) 方法。
当达到熔断阈值触发熔断,将直接走blockHandler 不再走getDetail(Long id) 方法。
通过观察控制台日志判断是否达到测试预期结果。
三次非限流异常后,其他请求全为熔断处理,处理非常快。
1.3.3 熔断降级-实战
1.3.2是针对下单人信息-远程调用jzo2o-customer服务,实战则是针对订单服务信息-远程调用jzo2o-foundations服务
因为熔断降级是在客户端的,所以调用jzo2o-foundations服务获取针对订单服务信息的熔断降级也是写在order端
创建com.jzo2o.orders.manager.service.impl.client.FoundationsClient
@Component
@Slf4j
public class FoundationsClient {
@Resource
private ServeApi serveApi;
@SentinelResource(value = "getServeDetail", fallback = "detailFallback", blockHandler = "detailBlockHandler")
public ServeAggregationResDTO getDetail(Long id) {
log.error("根据id查询服务详情,id:{}", id);
// 调用其他微服务方法
ServeAggregationResDTO serveDetail = serveApi.findById(id);
return serveDetail;
}
//执行异常走
public AddressBookResDTO detailFallback(Long id, Throwable throwable) {
log.error("非限流、熔断等导致的异常执行的降级方法,id:{},throwable:", id, throwable);
return null;
}
//熔断后的降级逻辑
public AddressBookResDTO detailBlockHandler(Long id, BlockException blockException) {
log.error("触发限流、熔断时执行的降级方法,id:{},blockException:", id, blockException);
return null;
}
}
完成,比较简单就不测试了