springcloud学习笔记
什么是微服务框架
微服务框架就是将单体的应用程序分成多个应用程序,多个应用服务就成为微服务,每个微服务运行在自己的进程中,并使用轻量级机制通信。
springcloud是什么
springcloud是一系列框架的有序集合。利用springboot的开发简化了分布式系统基础设施的开发,如服务发现注册,配置中心、智能路由、消息总线、负载均衡、断路器、数据监控等,都可以用springboot的开发风格做到一键启动和部署
优点:
- 耦合度较低,不会影响其他模块的开发
- 可以并行开发
- 配置简单,基本用注解就完成,不使用过多的配置文件
- 微服务跨平台的,可以用任何一种语言开发
- 每个服务都有自己的独立的数据库也有公共的数据库
- 直接写后端代码然后暴露接口,通过组件进行服务通信
缺点:
- 部署麻烦
- 针对数据的管理麻烦
- 系统集成测试比较麻烦
- 性能的监控比较麻烦
springcloud由什么组成
- Spring Cloud Eureka:服务注册与发现
- Spring Cloud Zuul:服务网关
- Spring Cloud Ribbon:客户端负载均衡
- Spring Cloud Fegin:声明式的web服务客户端
- Spring Cloud Hystrix:断路器
- Spring Cloud Config:分布式统一配置管理
- 等20几个框架
如何建立一个微服务模块
1、建立module
2、改pom
3、写yml
4、主启动类
5、业务类
介绍一下RestTemplate
是什么
RestTemplate提供了多种便捷访问远程http服务的方法,是一种简单便捷的访问restful服务模板类,是spring提供用来访问rest服务的客户端模板工具集
使用
(url,requestMap,ResponseBean.class)三个参数
分别代表Rest请求地址、请求参数、HTTP响应转换被转换成的对象类型
Eureka服务注册与发现
什么是服务治理
在传统的rpc远程调用框架中,管理每个服务与服务之间的依赖关系比较复杂,所以需要服务治理,管理服务之间的依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册
什么是服务注册
Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一 个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接]地址))
一般微服务提供者是一套集群,不是单个,而在集群中找到单个服务,需要服务注册中心,而服务注册中心也是一个集群,避免单点故障
Eureka集群原理
- 先启动eureka注册中心
- 启动服务提供者服务
- 服务启动后会把自身信息注册进入eureka
- 消费者在需要调用接口时,使用服务别名去注册中心获取实际的RPC远程调用地址
- 消费者获得调用地址后,底层实际上利用httpClient(RestTemplate)技术实现远程调用
- 消费者获得服务地址后会缓存在本地的jvm内存中,默认每间隔30秒更新一次服务调用地址
配置集群
order添加注解@EnableEurekaClient和provider添加注解@EnableEurekaClient和@EnableDiscoveryClient
eureka:
client:
# 注册进 Eureka 的服务中心
register-with-eureka: true
# 检索 服务中心 的其它服务
fetch-registry: true
service-url:
# 设置与 Eureka Server 交互的地址
# defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
instance:
instance-id: payment8002(服务提供者或者服务消费在eureka客户端上的名称)
perfer-ip-address: true
server:相互注册、相互守望,添加注解@EnableEurekaServer
eureka:
instance:
hostname: eureka7001.com # eureka 服务端的实例名称
client:
# false 代表不向服务注册中心注册自己,因为它本身就是服务中心
register-with-eureka: false
# false 代表自己就是服务注册中心,自己的作用就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置与 Eureka Server 交互的地址,查询服务 和 注册服务都依赖这个地址
defaultZone: http://eureka7002.com:7002/eureka/
主启动类上加注解@EnableEurekaClient
Eureka自我保护机制
进入保护模式,Eureka Server会将尝试保护其服务注册表下的信息,不再删除服务注册表中的信息,也就是不会注销任何微服务
某时刻某一个微服务不可用,Eureka不会立刻清理,依旧对该微服务的信息进行保存,设计思想是CAP里面的AP分支
问题描述:
如果EurekaServer在一定时间内没有收到某个微服务实例的心跳,Eureka默认在90秒后将会注销该实例,但是当网络分区故障发生延时、卡顿、拥挤时,微服务和EurekaServer之间无法正常通信,EurekaServer就会删除该实例,本身并没有发生故障,只是网络问题,不应该注销这个微服务
Eureka通过自我保护机制来解决问题,当Eureka节点短时间内丢失过多的客户端,那么这个节点就会进行自我保护模式
如何禁止自我保护
EurekaServer修改server.enable.preservation:false
EurekaClient修改eureka.instance.lease.renewal.interval.in.seconds:1(Eureka客户端向服务端发哦送心跳的时间间隔,默认为30秒,改为1秒)
eureka.instance.lease.expiration.duration.in.seconds:2(Eureka服务端在收到最后一次心跳后等待时间上限,默认为90秒,改为2秒)
ZooKeeper安装与配置
1、将zookeeper安装包解压到根目录下/opt
tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz /opt
2、查看到解压后文件内容
3、将文件名改为zookeeper-3.7.1
mv apache-zookeeper-3.7.1-bin zookeeper-3.7.1
4、在zookeeper-3.7.1目录下创建文件夹data,用来存放数据
mkdir data
5、打开conf文件夹中,将zoo_sample.cfg复制一份名字为zoo.cfg
cd conf/
cp zoo_sample.cfg zoo.cfg
6、修改配置
vim zoo.cfg
7、jdk的话我用的是jdk1.8.0_311,开启服务时也没有版本冲突
8、打开bin目录,开启服务
cd bin/
./zkServer.sh start
./zkServer.sh status
服务开启成功
9、ping window的ip地址是否连同
10、开启客户端
./zkCli.sh
在yaml文件配置ip和端口
cloud:
zookeeper:
connect-string: 192.168.194.132:2181
主启动类上添加注解@EnableDiscoveryClient能够让注册中心发现并扫描到该业务
这样就能看到服务的节点
Zookeeper的服务节点是临时的,当服务关闭后,就会在服务注册中心注销服务
Consul服务注册与发现
consul是一套开源的分布式服务发现和配置管理系统,由go语言开发
提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要 单独使用,也可以一起使用以构建全方位的服
务网格,总之Consul提供了一种完整的服务网格解决方案。
- 服务发现:提供了HTTP和DNS两种发现方式
- 健康监测:支持多种方式,http、tcp、Docker、shell脚本定制化
- kv存储:key-value的存储方式
- 多数据中心:
- 可视化web界面
安装并运行
下载地址Downloads | Consul by HashiCorp
下载完成后只有一个consul.exe文件
使用开发者模式启动
consul agent -dev
配置
1、yaml添加配置
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
2、consumer添加@EnableDiscoveryClient注解,让注册中发现扫描到业务
CAP理论
- C:Consitency 强一致性,所有节点在同一时间看到的数据是一致的
- A:Available 可用性,所有的请求都会收到响应
- P:Partition tolerance 分区容错性
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求
Eureka: AP
Consul/Zookeeper: CP
Nacos:AP
1、CAP理论关注粒度是数据,而不是整体系统设计的策略
2、AP架构
当网络分区出现后,数据同步失败,为了保证可用性,系统B可以返回旧值,保证系统的可用性
3、CP架构
当网络分区出现后,为了保证一致性,必须拒接请求,否则无法保证一致性
Ribbon负载均衡服务调用
简介
SpringCloud Ribbon是基于NetfIixRibbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Neix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出LoadBalancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法
负载均衡(Load Balance)
将用户的请求平摊地分配到多个服务器上,从而达到系统的高可用
Ribbon本地负载均衡客户端和Nginx服务端负载均衡区别
Nginx(集中式LB)是服务端负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求
Ribbon(进程内LB)本地负载均衡,在调用微服务接口时,会在注册中心获取注册信息服务列表缓存到VM本地上,从而实现RPC远程服务调用技术
集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方;
进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器!Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址
Ribbon自带的负载规则
调用的是核心组件IRule接口
- com.netflix.loadbalancer.RoundRobinRule:轮询
- com.netflix.loadbalancer.RandomRule:随机
- com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败会在指定时间内进行重试,获取可用的服务
- WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule:会先过滤掉多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule:默认规则,符合判断server所在区域的性能和server的可用性选择服务器
自定义配置类不能在@ConponentScan所扫描的当前包以及字包下,否则自定义配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的
负载均衡轮询算法原理
rest接口的第几次请求数%服务器集群总数量 = 实际服务器调用位置下标,每次服务重启后rest接口数从1开始
服务器集群数:2
1%2=1 index=1 list.get(index);
2%2=0 index=0 list.get(index);
3%2=1 index=1 list.get(index);
4
.
.
重启服务从1开始
OpenFeign服务接口调用
Feign是一个声明式的web服务客户端,让编写web服务客户端变得容易,只需创建一个接口并在接口上添加注解即可完成对服务提供方的接口绑定,简化了使用Ribbon时自动封装服务调用客户端的开发量
Feign与OpenFeign的区别
feign是spring cloud组件的一个轻量级restful的http服务客户端,内置了Ribbon,用来做客户端负载均衡,去调用注册中心的服务
OpenFeign是spring cloud在feign的基础上支持了spring mvc的注解,比如@RequestMapping等等。OpenFeign的@feignClient可以解析spring mvc的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务
OpenFeign超时控制
feign客户端默认等待一秒,但是服务端处理需要超过1秒,导致feign客户端不想等待了,直接返回报错,为了避免这种情况,需要设置feign客户端的超时控制
在yml中开启配置
ribbon:
#建立连接所用的时间
ReadTimeout: 5000
#建立连接后从服务器读到可用资源所用的时间
ConnectTimeout: 5000
OpenFeign日志打印功能
日志级别
- NONE:默认,不显示任何日志
- BASIC:仅记录请求方法、url、响应状态码及执行时间
- HEADERS:除了basic中定义的信息之外,还有请求和响应的头信息
- FULL:除了headers中定义的信息之外,还有请求和响应的正文及元数据
配置日志打印功能
新建config包,添加配置类
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
yaml配置文件
logging:
level:
#feign日志以什么级别监控哪个接口
com.bai.springcloud.service.PaymentFeignService: debug
Hystrix断路器
服务雪崩
多个微服务之间调用时,微服务A调用微服务B和C,微服务B和C又调用其他的微服务,这就是所谓的“扇出”,如果扇出的链路上某个微服务的调用时间响应过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,从而引起系统崩溃,这就是雪崩效应
Hystrix是什么
Hystrix是一个处理分布式系统的延迟和容错的开源库,在分布式系统中,许多服务不可避免的会调用失败,比如超时、异常等问题,Hystrix能够保证一个依赖问题出现的情况下,不会导致整体服务失败,避免级联故障,提高分布式系统的弹性
Hystrix断路器是一种开关装置,当某服务单元发生故障之后,通过断路器的故障监控,向调用方法返回一个符合预期的、可处理的备选响应,而不是长时间的等待或者抛出调用方法无法处理的异常,这样就保证了服务调用方的线程不会长时间、不必要地占用,避免了故障在分布式系统中的蔓延,乃至雪崩
服务降级、服务熔断、服务限流
- 服务降级(fallback):不让服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满
- 服务熔断(break):达到最大服务访问,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
- 服务限流(flowlimit):秒杀高并发等操作,严禁请求过来拥挤,进行排队,有序处理
熔断机制
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错或响应时间太长,会进行服务的降级,进而熔断节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路
总结:
熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开市场长达到所设置时钟则进入半熔断状态
熔断关闭:熔断关闭不会对服务进行熔断
熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
断路器的三个重要参数
circuitBreaker.enabled
属性是否开启断路器
- 快照时间窗(requestVolumeThreshold):断路器确定打开需要从机一些请求和错误数据,而统计的时间范围就是快照时间窗口,默认为最近的十秒
- 请求总数阈值(sleepWindowInMilliseconds):在快照时间窗口内,必须满足请求总数阈值才有资格熔断。默认为20,在十秒钟内,如果hystrix命令的调用次数不足20次,及时所有的请求超时或者其他原因失败,断路器都不会打开
- 错误百分比阈值(errorThresholdPercentage):当请求总数在快照时间窗口内超过了阈值,比如发生了30次调用,有15次发生了超时异常,也就是超过50%的错误百分比,在模拟人设定50%阈值情况下,这时候就会将断路器打开
图形化DashBoard搭建
Hystrix还提供了准实时的调用监控,会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等
1、导入依赖
<dependencies>
<!-- hystrix Dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!-- 常规 jar 包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.bai.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
2、yaml文件设置端口
3、主启动类添加注解@EnableHystrixDashboard
4、在被监控服务的主启动类中添加
@Bean // 注入豪猪的servlet // 该servlet与服务容错本身无关 // springboot默认路径不是/hustrix.stream,只要在自己的项目里自己配置servlet
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(streamServlet);
servletRegistrationBean.setLoadOnStartup(1);
servletRegistrationBean.addUrlMappings("/hystrix.stream");
servletRegistrationBean.setName("HystrixMetricsStreamServlet");
return servletRegistrationBean;
}
Gateway服务网关
Gateway是什么
Gateway是在spring生态系统上构建的api网关服务,基于spring5,springboot2和Project reactor技术,提供一种简单有效的方式来对api进行路由,以及提供一些强大的过滤器功能,例如熔断、限流、重试等
Gateway是基于WebFlux框架实现的,而WebFlux框架使用的高性能的Reactor模式通信框架netty
spring cloud Gateway的特性
- 动态路由:能够匹配任何请求属性
- 可以对路由指定Predicate(断言)和Filter(过滤器)
- 继承hystrix的断路器功能
- 继承springcloud服务发现功能
- 易于编写的Predicate(断言)和Filter(过滤器)
- 请求编译限流功能
- 支持路径重写
Gateway和zuul的区别
1、Zuul 1.x是一个基于I/O的api Gateway
2、Zuul 1.x基于servlet2使用阻塞架构不支持任何长连接Zuul的设计模式和Nginx比较像,每次I/O操作都会从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx是用C++实现,Zuul用java实现,而jvm本身会有一次加载较慢的情况,使得Zuul额性能较为差
3、Zuul 2.x的理念较为先进,基于netty非阻塞和支持长连接,但是springcloud还没有整合
4、Gateway使用非阻塞API,还支持WebSocket,并与spring紧密连接
三大核心概念
- Router(路由):路由是构建网关的基本模块,有ID、目标URI,一系列 断言和过滤器组成,断言为true则匹配路由
- Predicate(断言):可以匹配Http请求中的所有内容,如果请求和断言相匹配则进行路由
- Filter(过滤):可以在请求被路由前或者之后对请求进行修改
Gateway的网关配置
1、在yaml中配置
cloud:
gateway:
routes: #多个路由
- id: payment_routh # 路由ID , 没有固定的规则但要求唯一,建议配合服务名
uri: http://localhost:8001 # 匹配后提供服务的路由地址 #uri+predicates # 要访问这个路径得先经过9527处理
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_routh2 # 路由ID , 没有固定的规则但要求唯一,建议配合服务名
uri: http://localhost:8001 # 匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
- After=2020-02-21T16:45:37.485+8:00[Asia/shanghai]
- Cookie=username.zzzyy
- Header=X-Request-Id, \d+ #请求头要有X-Request-Id属性并且值为整数的正则表达式
Predicates属性值
- After Route Predicate:会在标注的时间后才会执行
- Before Route Predicate
- Between Route Predicate
- Cookie Route Predicate:Cookie Route Predicate需要两个参数,一个是cookie name,一个是正则表达式。路由规则会根据对应的cookie name值和正则表达式去匹配,如果匹配的话就向上执行路由,如果没有,则不执行
- Header Route Predicate
- Host Route Predicate
- Method Route Predicate
- Path Route Predicate
- Query Route Predicate
2、代码中注入RouteLocator的 bean
创建一个配置类进行配置
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_baibai",
r->r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
Config分布式配置中心
微服务意味着将单体应用中的业务网拆分成一个一个子服务,因此会出现大量的服务,而这些服务每一个都需要必要的配置信息才能运行,所以需要一套集中式的,动态的配置管理
SpringCloud Config分为服务端和客户端两部分
- 服务端:分布式配置中心,是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密或者解密信息等访问接口
- 客户端:通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动时从配置中心获取并加载配置信息配置服务器采用git来存储信息,这样有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容
功能:
- 集中管理配置文件
- 不同环境不同配置,动态化的配置更新,分环境配置,dev/test/prod/bata/release
- 运行期间动态调整配置,不再需要每一个服务器上编写配置文件,服务会向配置中心统一拉去配置自己的信息
- 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用到新的配置
- 将配置信息以REST接口的形式暴露
动态刷新
当linux运维修改github上的配置文件,刷新3344发现configServer服务器配置中心立刻响应,而刷新3355发现ConfigClient并没有任何响应,还是旧值,只有3355重启才能拿到新值
1、client必须有actuator依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、yaml增加暴露端点
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
3、在controller上添加注解@RefreshScope
注解
4、向client发送一个post请求
curl -X POST "http://localhost:3355/actuator/refresh"
得到最新值
但是这样每一个微服务都要执行一次post请求,所以需要消息总线
Bus消息总线
Bus支持两种消息代理:RabbitMQ和kafka
什么是总线
在微服务架构的系统中,通常利用轻量级的消息代理来构建一个共用的消息主题,并让系统上所有的微服务实例都连接上来。由于该主题产生的消息会被所有实例监听和消费,称他为消息总线。在消息总线上的所有实例,都可以方便地广播一些需要让他连接在该主题上的实例都知道的消息
消息总线的实现
设计思想
1、利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
2、利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而 刷新所有的客户端的配置
一方法不合适的原因
1、打破了职责的单一性,本身就是业务模块,不应该承担配置刷新的职责
2、破坏了微服务各节点的对等性
3、有一定的局限性,在微服务迁移时,网络地址会发生变化,此时想要做自动刷新,就会增加更多的修改
所以一般采用方法二,触发服务端的端点
注意
运维工程师修改完github的配置,还是需要发送post请求
curl -X POST "http://localhost:3344/actuator/bus-refresh"
之后就不用重启客户端就可以获得到修改的数据
一次修改,广播通知,处处生效
动态刷新定点通知
一次修改后,只想通知其中的一个,一个实例生效而不是全部
只通知3355,不通知3366
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
stream消息驱动
ActiveMQ
RabbitMQ
RocketMQ
kafka
屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
应用程序通过inputs或者outputs来与springcloud stream中的binder对象进行交互。通过配置Binder,而Binder对象负责与消息中间件的交互
标准的MQ
- 生产者/消费者之间靠消息媒介传递信息内容:消息message
- 消息必须走特定的通道:消息通道MessageChannel
- 消息通道里的数据如何被消费,谁负责收发处理:消息通道MessageChannel的子接口
stream中消息通信方式遵循了发布-订阅模式:Topic主题进行广播
- 在RabbitMQ是Exchange
- 在kafka中就是Topic
springboot应用要直接与消息中间件进行信息交互,由于消息中间件构建的初衷不同,各自的实现细节不同
通过定义绑定器Binder作为中间层向应用程序暴露统一的channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现
spring cloud stream的标准流程
- Binder:方便的连接中间件,屏蔽差异
- Channel:类似于队列queue,实现存储和转发的媒介,通过channel对队列进行配置
- Source和Sink:消息生产者和消息消费者
消息持久化和重复消费
- 消费
8802
8803
同时都收到了,存在重复消费问题
8802和8803属于不同组是可以全面消费的,这就是重复消费,而在同一个组中会发生竞争关系,只有其中一个可以消费
故障原因:默认分组group是不同的,组流水号不一样,被认为是不同组,可以消费
- 分组
- 自定义配置分组
- 8802/8803都变成不同组,group两个不同,不同组都可以被消费的,还是会造成重复消费的问题
- 8802/8803实现轮询分组,每次只有一个消费者,分为同一个组
- 自定义配置分为一个组
- 自定义配置分组
- 持久化
- 停止8802,8803并去掉8802的分组group: atguiguA,8803的分组不变
- 8801发送消息到rabbitmq
- 先启动8802,无分组属性的配置,后台没有打印出消息
- 再启动8803,有分组属性的配置,后台打印出了消息
- 问题:会造成消息的丢失
Sleuth分布式请求链路跟踪
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个 不同的服务节点调用来协同产生最后的请求结果,请求会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误会引起这个请求最后的失败
spring cloud sleth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin
- 下载jar包
- 运行jar包
java -jar zipkin-server-2.12.9-exec.jar
- 打开控制台
http://localhost:9411/zipkin/
Trace:类似于树结构的span集合,表示一条调用链路,存在唯一标识
Span:标识调用链路来源,理解就是一次请求信息
案例
给服务提供者和消费者添加依赖spring-cloud-starter-zipkin
yaml添加配置
spring: zipkin: base-url: http://localhost:9411 # zipkin 地址 sleuth: sampler: # 采样率值 介于0-1之间 ,1表示全部采集 probability: 1
spring cloud alibaba
主要功能:
- 服务限流降级:默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控
- 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
- 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新
- 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力
- 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题
- 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
- 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
- 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道
需要引入依赖:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
组件:
- Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
- Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
- RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。
- Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。
- Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
- Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
- Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。
- Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
Nacos服务注册与配置
一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台,相当于Eureka+Config+Bus
安装和启动
1、解压压缩包后,在bin目录下开启startup.cmd
2、账号和密码都是nacos
3、访问成功
注册中心比较
nacos支持AP和CP切换的
如果不需要存储服级的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如Spring cloud和Dubbo服务,都适用于AP模式,AP模式为了服务的可用性而减弱了一致性,因此AP模式下只支持注册临时实例。
如果需要在服务级别编辑或者存储配置信息,那么CP是必须,K8S服务和DNS服务则适用于CP模式。
CP模式下则支持注册持久化实例,此时则是以Raft协议为集群运行模式,该模式下汪册实例之前须先注册服务,如果服务不存在,则会返回错误
切换的命令:curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
配置中心
dataId组成格式${spring.appliaction.name}-${springprofiles.active}.${spring.cloud.nacos.config.file-extension}
例如nacos-config-client-dev.yaml
自带动态刷新
在nacos管理界面中发布新的配置,不用config一样再发送post请求
分类配置
实际开发中,会有多个环境,dev开发环境、test测试环境、prod生产环境
- DataId方案:指定spring.profile.active和配置文件的DataId来使不同环境下读取不同的配置
- 默认空间(namespace)+默认分组(DEFAULT_GROUP)+新建dev和test两个DataID,属于同一个分组
- Group分组方案: 设置分组DEV_GROUP和TEST_GROUP+配置上添加分组group: DEV_GROUP/TEST_GROUP
- Namespace空间方案: 按照域名配置填写namespace+配置上加namespace生成的编号
集群和持久化配置
默认nacos使用嵌入式数据库derby
实现数据的存储,如果启动多个默认配置下的nacos节点,数据存储存在一致性问题,为了解决这个问题,采取了集中式存储的方式来支持集群化部署,只支持mysql的存储
切换mysql配置
1、在nacos-server-1.1.4\nacos\conf目录下找到sql脚本nacos-mysql.sql执行脚本
2、再找到application.properties,将mysql配置添加进去
spring.datasource.platform=mysql
db.num=1
db.url.0=jsbc:mysql://127.0.0.1:3306/nacos_config?
characterEncoding=utf8&connectTieout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456
linux版Nacos+Mysql生产环境配置
1、一个nginx+三个nacos注册中心+一个mysql
2、Nacos下载linux版
3、集群配置步骤
linux服务器上mysql数据库配置
安装数据库,版本要求5.6.5+
初始化数据库,数据库初始化文件:nacos/conf/nacos-mysql.sql。创建个database数据库nacos_devtest
修改IDEA中nacos/conf/application.properties文件(切换数据库),增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。
# 切换数据库 spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user=root db.password=123456
appliaction.properties配置
linux服务器上nacos的集群配置cluster.conf
# it is ip # 告诉这3个集群结点是一组的 # 不能写127.0.0.1,必须是linux hostname -i能够识别的ip 192.168.1.2:3333 192.168.1.2:4444 192.168.1.2:5555
编译nacos的启动脚本start.sh,使他能够接受不同的启动端口
- 依次启动3个nacos集群
nginx的配置,由他作为负载均衡器
- 修改nginx.conf
- 启动nginx
./nginx -c /usr/loacl/nginx/conf/nginx.conf
,查看是否启动ps - ef| grep nginx
Sentinel实现熔断与限流
与hystrix相比的优点
hystrix:
- 需要程序员自己手工搭建监控平台
- 没有web界面给我们更加细粒度话配置流控、速率控制、服务熔断、服务降级。。。。
Sentinel:
- 单独一个组件,可以独立出来
- 直接界面化的细粒度统一配置
sentinel分为两个部分:
- 核心库(java客户端)不依赖任何框架、库,能够运行所有java运行时环境,同时对Dubbo、spring cloud等框架也有较好的支持
- 控制台(Dashboard)基于springboot开发,打包后可以直接运行,不需要额外的tomcat等应用容器
运行jar包java -jar sentinel-dashboard-1.7.0.jar
访问sentinel管理界面http://loaclhost:8080,账号密码都是sentinel
实例化演示工程
1、启动服务注册中心nacos8848成功
2、model
- service8401
- pom
- yml
- 主启动类
- 业务类FlowLimitController
3、启动sentinel8080
4、启动微服务8401
5、启动后8401查看sentinel控制台
登录成功后sentinel没有任何服务的信息原因:
sentinel采用的是懒加载,需要服务执行一次访问才能查看服务信息
流控规则
资源名:唯一名称,默认请求路径
针对来源:sentinel可以针对调用者进行限流,填写微服务名,默认default
阈值类型/单机值:
- QPS(每秒钟的请求数量):当调用该api就QPS达到阈值的时候,进行限流
- 线程数.当调用该api的线程数达到阈值的时候,进行限流
是否集群:不需要集群
流控模式:
- 直接:api达到限流条件时,直接限流。分为QPS和线程数
- 关联:当关联的资源到阈值时,就限流自己。
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】
流控效果:
- 快速失败:直接抛异常,Blocked by Sentinel(flow limiting)
- warm up:根据codeFactor(冷加载因子,默认3)的值,从阈值codeFactor,经过预热时长,才达到设置的QPS阈值,系统开启的同时,大量的请求过来,可能让系统直接瘫痪,初始阈值慢慢恢复至设置的阈值,保证系统不会奔溃
- 排队等待:匀速排队,让请求匀速通过,阈值类型必须设置为QPS
流控模式-直接
当超过阈值,就会被降级,一秒超过阈值多次刷新网页,就会返回Blocked by Sentinel(flow limiting)
流控模式-关联
当关联的资源达到阈值时,就限流自己,例如支付接口达到阈值,就限流下订单的接口
sentinel降级
sentinel降级熔断会调用链路中某个资源出现不稳定状态时,对这个资源进行控制,让请求快速失败,避免影响到其他的资源而导致级联错误,在接下来的降级时间窗口内,对资源的调用豆浆自动熔断
RT
永远一秒钟打进来10个线程(大于5个)调用接口,希望200毫秒内处理完成本次任务。如果超过200毫秒还没处理完,在未来一秒钟时间窗口内,断路器打开微服务不可用,保险丝跳闸断电,停止访问接口,没有这么大的访问量,断路器关闭(保险丝恢复),微服务恢复
异常比例
当资源的每秒请求量大于等于5,并且每秒异常总数栈通过量的比值超过阈值之后,资源进入降级状态,即在接下来的时间窗口之内,对这个方法的调用都会自动返回
异常数
当资源近一分钟的异常数目超过阈值(异常数)之后会进行熔断。由于统计时间窗口是分钟级别的,若timeWindow小于60s,则结束熔断状态后人可能再进入熔断状态
热点key限流
仅支持QPS模式
限流出现问题后,都是sentinel系统默认的提示:Blocked by Sebtinel(flow limiting),可以自定义提示@SentinelRecource
@GetMapping("/testHotKey")
@SentinelRecource(value="testHotKey",blockHandler="deal_testHotKey")
public String testHotKey(@RequestParam(value="p1",require=false) String p1,@RequestParam(value="p1",require=false) Strin p2){
return "---------testHotKey";
}
public String deal_testHotKey(String p1,String p2,BlockException){
return "--------dealTestHotKeyo(╥﹏╥)o";
}
blockHandler指定的保底函数,降级处理后出现我们自定义的提示
参数例外项
第一个参数p1,当QPS超过1秒1次点击后马上被限流,希望p1参数当他是某个特殊值时,他的限流值和平时不一样
注意:当异常出现时(int a=10/0),并不会执行blockHandler指定的保底函数,这不是配置类的问题,所以直接报异常
系统规则
系统自适应限流
系统自适应限流从整体维度对应用入口流量进行控制,结合应用的Load、CPU使用率,总体平均RT,入口QPS和并发线程数等几个维度的监控指标,通过自适应的流控策略,让 系统的入口流量和系统的负载达到一个平衡,让系统尽可能泡在最大 吞吐量的同时保证系统整体的稳定性
@SentinelResource
1、按照资源名称限流+后续处理
public class RateLimitController{
@GetMapping("/byResource")
@SentinelResource(value="bySource",blockHandler="handlerException")
public CommonResult byResource(){
return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
}
public CommonResult handleException(BlockException exeption){
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
}
}
额外问题:关闭服务,sentinel控制台,流控规则消失,是暂时的
2、按照url地址限流+后续处理
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value="byUrl")
public CommonResult byUrl(){
return new CommonResource(200,"按照url限流测试OK",new Payment(2020L,"serial002"));
}
客户自定义限流处理逻辑
- 创建CustomerBlockHandler类用于自定义限流处理
- 自定义限流处理类:统一处理需要公有的跳转页面、限流提示、服务降级等的说明
- RateLimitController
- 启动微服务后先调用一次
- Sentinel控制台配置
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value="customerBlockHandler",blockHandlerClass=CustBlockHandler.class,blockHandler="handlerException")
public CommonResult customerBlockHandler(){
return new CommonResource(200,"按照客户自定义",new Payment(2020L,"serial003"));
}
//单独写成一个类
public class CustomerBlockHandler{
public statix CommonResult handlerException(BlockException exption){
return new CommonResult(444,"按客户自定义,global handlerException");
}
//还可以自定义其他的提示
}
服务熔断
sentinel整合ribbon+openFeign+fallback
- ribbon
消费者服务配置CircleBreakController,fallback管运行异常,blockHandler管配置违规
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value="fallback",fallback="handlerFallback")
@SentinelResource(value="fallback",bloackHandler="bloackHandler")
public CommonResult<Payment> fallback(@PathVariable Long id){
CommonResult<Payemnt> result = restTemplate.getForObject(SERVICE_URL+"/paymentSQL/"+id,CommonResult.class,id);
if(id==4){
throw new IllegalArgumentException("IllegalArgumentException,非法参数异常。。。。")
}else if(result.getData()==null){
throw new NullPointerException("NullPointerException,该ID没有对象记录,空指针异常");
}
return result;
}
public CommonResult handlerFallback(@PathVariable Long id,Throwable e){
Payment payment = new Payment(id,"null");
return new CommonResult<>(444,"兜底异常handlerFallback,exception内容"+e.getMessage,payment);
}
public CommonResult blockHandler(@Pathvariable Long id,BlockException blockException){
Payment payment = new Payment(id,null);
return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水:blockException"+blockException.getClass().getCanonicalName()+"\t 服务不可用");
}
若blockHandler和fallback都进行了配置,则被限制降级而抛出BlockException时只会进入Blockhandler处理逻辑
- openFeign
@FeignClient(value="服务名称",fallback=PaymentFallbackService.class)
public interface PaymentService{
@GetMapping(value="/paymentSQL/{id}")
public CommonResult<payment> paymentSQL(@PathVariable("id") Long id);
}
@Component
public class PaymentFallbackService implements PaymentService{
@Override
public CommonResult<Payment> paymentSQL(Long id){
return new CommonResult<>(444,"服务降级返回,---PaymentFallbackService",newPayment(id,"errorService"));
}
}
在controller
@Resource
private PaymentService paymentService;
@GetMapping(value="/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@Pathvariable("id") Long id){
return paymentService.paymentSQL(id);
}
规则持久化
一旦重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
添加nacos配置
datasouce:
ds1:
nacos:
server-addr:localhost:8848
dataId:服务名称
groupId:DEFAULT_GROUP
data-type:json
rule-type:flow
Seata处理分布式事务
分布式问题:单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局数据一致性问题没法保证
分布式事务处理过程的一ID+三组件模型
- Transaction ID XID:全局唯一的事务ID
- 三概念组件
- Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚
- Transaction Manager(TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议
- Resource Manager(RM):控制分支事务,负责分支注册,状态汇报,并接受事务协调器的指令,驱动分支(本地)事务的提交和回滚
处理过程
1、TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
2、XID在微服务调用链路的上下文传播
3、RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
4、TM向TC发起针对XID的全局提交或回滚决议
5、TC调度XID下管辖的全部分支事务完成提交或回滚请求
下载配置
1、seata-server-0.9.0.zip解压到指定目录并修改conf目录下的file.conf配置文件
service模块
vgroup_mapping.my_test_tx_group=”fsp_tx_group”
store模块
mode=”db”
url、user、password改为自己的
2、在mysql中新建库seata
3、在seata中建表,导入db_store.sql
4、修改register.conf配置文件,指定nacos的配置
type=’nacos’
nacos{
serverAddr=”localhost:8848”}
案例
业务说明:
当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,在通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已经完成
下订单、减库存、扣余额、改状态
数据库
1、创建三个数据库:seata_account、seata_order、seata_storage
2、都创建一个回滚日志表,在seata/conf/中有对应的文件
- seata
- branch_table
- global_table
- lock_table
- seata_account
- t_account
- undo_log
- seata_order
- t_order
- undo_log
- seata_stroage
- t_storage
- undo_log
pom依赖
<dependencies>
<!-- seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.0.0</version>
</dependency>
<!-- springcloud alibaba nacos 依赖,Nacos Server 服务注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- open feign 服务调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- springboot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 持久层支持 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- 日常通用jar包 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.bai.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
yaml配置
server:
port: 2001
spring:
application:
name: seata-order-service
cloud:
alibaba:
seata:
# 自定义事务组,需要和当时在 seata/conf/file.conf 中的一致
tx-service-group: dkf_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order
username: root
password: 123456
# 注意,这是自定义的,原来的是mapper_locations
mybatis:
mapperLocations: classpath:mapper/*.xml
logging:
level:
io:
seata: info
将seata/conf/下的file.conf和registry.conf两个文件拷贝到resource文件下
创建domain实体类:order和CommonResult连个实体类
dao
package com.dkf.springcloud.dao;
import org.apache.ibatis.annotations.Mapper;
import com.dkf.springcloud.domain.Order;
import org.apache.ibatis.annotations.Param;
@Mapper
public class OrderDao {
//创建订单
public void create(Order order);
//修改订单状态
public void update(@Param("userId") Long userId, @Param("status") Integer status);
}
mapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.dkf.springcloud.dao.OrderDao">
<!-- 以备后面会用到 -->
<resultMap id="BaseResultMap" type="com.dkf.springcloud.domain.Order">
<id column="id" property="id" jdbcType="BIGINT"></id>
<result column="user_id" property="userId" jdbcType="BIGINT"></result>
<result column="product_id" property="productId" jdbcType="BIGINT"></result>
<result column="count" property="count" jdbcType="INTEGER"></result>
<result column="money" property="money" jdbcType="DECIMAL"></result>
<result column="status" property="status" jdbcType="INTEGER"></result>
</resultMap>
<insert id="create">
insert into t_order(id, user_id, product_id, count, money, status)
values (null, #{userId},#{productId},#{count},#{money},0)
</insert>
<update id="update">
update t_order set status = 1 where user_id=#{userId} and status=#{status}
</update>
</mapper>
serviceImpl
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Resource
private OrderDao orderDao;
@Resource
private StorageService storageService;
@Resource
private AccountService accountService;
@Override
public void create(Order order) {
log.info("--------》 开始创建订单");
orderDao.create(order);
log.info("--------》 订单微服务开始调用库存,做扣减---Count-");
storageService.decrease(order.getProductId(), order.getCount());
log.info("--------》 订单微服务开始调用库存,库存扣减完成!!");
log.info("--------》 订单微服务开始调用账户,账户扣减---money-");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("--------》 订单微服务开始调用账户,账户扣减完成!!");
//修改订单状态,从0到1
log.info("--------》 订单微服务修改订单状态,start");
orderDao.update(order.getUserId(),0);
log.info("--------》 订单微服务修改订单状态,end");
log.info("--订单结束--");
}
@Override
public void update(Long userId, Integer status) {
}
}
controller
@RestController
public class OrderController{
@Resource
private OrderService orderSerice;
@GetMapping("/order/create")
public CommonResult create(Order order){
orderService.create(order);
return new CommonResult(200,"订单创建成功");
}
}
config配置
//下面是两个配置类,这个是和mybatis整合需要的配置
@Configuration
@MapperScan({"com.dkf.springcloud.alibaba.dao"})
public class MybatisConfig {
}
//这个是配置使用 seata 管理数据源,所以必须配置
package com.dkf.springcloud.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapperLocations}")
private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource){
return new DataSourceProxy(dataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
主启动类
//这里必须排除数据源自动配置,因为写了配置类,让 seata 管理数据源
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableFeignClients
@EnableDiscoveryClient
public class SeataOrderMain2001 {
public static void main(String[] args) {
SpringApplication.run(SeataOrderMain2001.class,args);
}
}
问题:当服务出现问题,例如超时等问题
模拟账户添加超时异常,而openfeign的响应机制就是一秒,出现故障,但是库存和账户金额扣减后,订单状态并没有设置为完成,并且由于feign的重试机制,账户金额可能会被多次扣减
seata原理
seata是一款开源的分布式事务解决方案,致力于高性能和简单易用的分布式事务服务。seata用户提供了AT、TCC、SAGA和XA事务模式,为用户打造一站式的分布式解决方案
AT模式
整体机制
- 一阶段:业务数据和事务回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源
- 二阶段
- 提交异步化,快速完成
- 回滚通过一阶段的回滚日志进行反向补偿
异步任务阶段的分支提交请求将异步和批量删除相应的undo log记录