玩命加载中 . . .

微服务(SpringCloud、SpringCloudAlibaba)


微服务

微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间相互协调,相互配合,为用户体现最终价值。每个服务运行在其独立的进程当中,服务与服务之间采用轻量级的通信机制互相协作(通常是基于http/restful API)。

SpringCloud

分布式微服务架构的一站式解决方案,是多种微服务架构的技术集合体,俗称微服务全家桶。

SpringCloud技术栈

服务注册与发现:EUREKA、Zookeeper、consul、Nacos

服务负载与调用:RIBBON、OPEN FEIGN

服务熔断降级:HYSTRIX、Sentinel

服务网关:Zuul,GateWay

服务分布式配置:Spring Cloud Config、Nacos

服务消息总线:Spring Cloud Bus、Nacos

服务开发:Spring Boot

Boot和Cloud技术选型

SpringBoot2.X版和SpringCloud H版

SpringCloud Alibaba

1

微服务架构编码构建

微服务cloud整体聚合父工程Project

  • 父工程步骤

    • 1.New Project
    • 2.聚合总工程名字
    • 3.Maven选版本
    • 4.工程名字
    • 5.字符编码
    • 6.注解生效激活
    • 7.java编译版本选8
    • 8.File Type过滤
  • Maven工程落地细节复习

    • Maven中的dependencyManagement和dependencies
      • dependencyManagement:只在父工程中出现,用于版本固定,不会引入Jar包
    • maven中跳过单元测试
  • 父工程创建完成执行mvn:install将父工程发布到仓库方便子工程继承

创建子模块,pay模块

  1. 创建module
  2. 改pom
  3. 写yml
  4. 主启动
  5. 业务类

业务类

  1. sql

  2. 实体类

    1. CommonResult类
    2. 实体类
  3. dao

    1. 接口

    2. mabatis映射文件

      在resource下,创建mapper/PayMapper.xml

  4. service

    1. service
    2. impl
  5. controller

测试

  • postman模拟post请求

  • 切换Run DashBoard运行窗口(分布式的运行窗口)

    • 在项目路径的.idea/workspace.xml
    填入以下内容:
    <option name="configurationTypes">
          <set>
            <option value="SpringBootApplicationConfigurationType" />
          </set>
        </option>
    

热部署

order模块

  1. 创建module
  2. 改pom
  3. 写yml
  4. 主启动
  5. 业务类

业务类

  1. 复制pay模块的实体类,entity类

  2. 写controller类

    因为这里是消费者类,主要是消费,那么就没有service和dao,需要调用pay模块的方法

    并且这里还没有微服务的远程调用,那么如果要调用另外一个模块,则需要使用基本的api调用

    使用RestTemplate调用pay模块

    RestTemplate(看脑图):用于服务间的调用

    注意:使用RestTemplate调用服务传输的是json数据,springboot并不能直接封装进对象中。需要在转发过去的方法中(即8001)使用@RequestBody让json数据封装进对象中,才能加入到数据库里。之前成功是因为url提交的是表单数据,非json数据

工程重构(解决entity重复)

Eureka服务注册与发现

当服务很多时,每个服务之间的依赖关系会比较复杂,管理起来更为复杂,需要服务管理服务间的依赖关系,实现负载均衡、服务调用等,实现服务注册与发现。

Eureka采用CS设计架构,server是服务器,也是注册中心,当eureka客户端连接到服务器,这样维护人员就可以在服务器监控各个服务的正常运行。

当服务器启动时,就会把自己的服务器信息以别名的方式注册在注册中心,消费者,服务提供者都以别名的方式获取该服务的通讯地址,实现本地的RPC调用思想:使用注册中心管理每个服务之间的依赖关系。

Eureka的两个组件

  1. 客户端:在各个服务上,比如登录微服务,启动后会给服务器发送心跳,证明自己还存活

  2. 服务器:服务器端,用于接收心跳(类似工商管理局)

单机版Eureka

服务器创建

  1. 创建项目cloud_eureka_server_7001

  2. 引入pom依赖

    Eureka新版本

  3. 写yml

  4. 主启动

    加上@EnableEurekaServer,表示服务是server

  5. 测试

注册客户端8001进服务器成为服务提供者provider

其他服务注册进服务器

8001、80都注册进去

  1. 改pom依赖

  2. 写Yml

  3. 主启动

    加上@EnableEurekaClient

  4. 服务模块重启

集群Eureka

集群原理

服务注册:将服务信息注册进注册中心

服务发现:从注册中心获取服务信息

实质:key服务名,value调用地址

  • 先启动注册中心,再启动服务,服务启动时会将信息以别名的方式注册进注册中心,消费者可以通过使用服务的别名在注册中心获取RPC远程调用地址,底层使用的是HttpClient实现的远程调用。在消费者获取服务地址之后,就会将其缓存在jvm内存中,没30秒更新一次
  • RPC远程调用最核心的就是:高可用,一个瘫痪,全部升天。所以集群有多个组成,并且互相注册,相互守望,如:有3台服务器,则1注册12,2-13,3-12

构建新erueka服务器

  1. cloud_eureka_server_7002
  2. pom文件:粘贴7001的
  3. 配置文件
    1. 修改本地的host文件(看脑图,非yaml)
    2. 修改yml,进行相互注册(注意defautlZone),7001,7002都要修改
  4. 主启动类
  5. 启动测试

将服务模块注册进集群中

  1. 修改yml文件(defaultZone加一条即可,两个模块都要加)
  2. 启动模块,先启动服务器7001,7002,再启动8001,之后是80
  3. 测试

构建新的服务提供者8001集群

  1. 创建新模块,8002(cloud_pay_8002)
  2. pom文件,复制8001的
  3. yml文件修改端口(服务名称不用改,用一样的,为的是为消费者暴露一样的别名)
  4. 主启动类,复制8001的
  5. mapper,service,controller都复制一份
  6. 实现负载均衡
    1. controller的url不能写死,改成微服务名称,这样每次访问就会从eureka中拿地址,轮询
    2. 在注册bean的restTemplate方法上加上@LoadBalanced开启负载均衡

修改服务主机名和ip在eureka的web上显示

作用:修改在eureka显示的默认主机名,鼠标移到主机名会显示ip

  1. 修改yml文件

eureka服务发现:

对于注册进eureka里面的微服务,可以通过服务发现来获取该服务的信息

  1. 在controller自动注入DiscoveryClient
  2. 用方法获取各种信息(所有服务集群、服务集群中的服务名、IP、端口、URI)
  3. 主启动类加入注解:@EnableDiscoveryClient
  4. 重启测试

Eureka自我保护机制

  • 某个微服务不可用时(客户端没有定时向服务器端发送心跳包),Eureka不会立刻将其清理,依旧会对该服务信息进行保存。

  • Eureka属于CAP中的AP分支(高可用)

    • CAP:
      • C:高一致性
      • A:高可用
      • P:高容错(因为分布式微服务架构都需要高容错,P必备)
    • AP:Eureka
    • CP:Zookeeper/Consul

如何禁止自我保护:

此时启动erueka和pay.此时如果直接关闭了pay,那么erueka会直接删除其注册信息

修改yml文件

  1. 注册中心server
    1. 关闭自我保护
    2. 设置接收心跳时间(测试用)
  2. 客户端client
    1. 设置发送心跳时间
    2. 设置服务端在收到最后一次心跳等待上限
  3. 测试

Zookeeper服务注册与发现

注册中心服务器Zookeeper

  1. 它是一个分布式协调工具,可以实现注册中心功能
  2. 关闭linux服务器防火墙,启动zookeeper服务器

服务提供者,客户端

  1. 创建新的pay服务模块

    cloud_pay_8003,单独用于注册到zk中

    1. pom依赖

    2. yml配置文件(端口号,服务别名,zookeeper服务器ip+端口号)

    3. 主启动

    4. controller

    5. 修改zk版本和jar冲突

      解决:

          修改pom文件,改为与我们zk版本匹配的jar包
      
    6. 启动,此时8003就注册到zk中了

      • 此时服务就会作为临时节点存入zk中,信息作为流水号存在临时节点中。

      • 当我们的服务一定时间内没有发送心跳

          那么zk就会将这个服务的node删除了
        
      • 这里是测试,就不写service与dao什么的了

服务消费者

  1. cloud_order_zk_80
  2. pom
  3. yml配置文件
  4. 主启动
  5. restTemplate
  6. controller
  7. 启动注册

集群zk的注册

  1. zookeeper教学中的集群搭建
  2. 服务提供者和消费者在配置文件中connect-string指定多个zk地址即可 “,”隔开

Consul服务注册与发现

它是一个服务发现、配置管理系统

作用:服务发现、健康检测、KV存储、可视化web界面

下载

  1. 需要下载一个安装包,是个exe文件
  2. 启动是一个命令行界面,需要输入consul agen-dev启动
  3. localhost:8500查看页面

创建服务提供者模块pay,8006

  1. cloud_consule_pay_8006
  2. pom依赖
  3. yml配置文件(需要写服务的名字)
  4. 主启动类
  5. controller
  6. 启动服务,观察是否注册进去

创建消费者模块order

  1. cloud-consul-order-80

  2. pom依赖

  3. yml配置文件(需要写服务的名字)

  4. 主启动类

  5. RestTemplate注册

    配置类注册

  6. controller

  7. 启动服务,测试是否注册进去,测试方法是否成功执行

三个注册中心的异同

Eureka:AP,以高可用为原则(如Eureka的自我保护机制:宕机依然保留数据)

Consul/Zookeeper:CP,以高一致性为原则,(如Zookeeper的临时节点:宕机直接踢出)

zhuce1

zhuce2

zhuce3

Ribbon负载均衡服务调用

概念

它是一个客户端,提供客户端(服务提供者)的软件负载均衡算法和服务调用。

作用:LB负载均衡(集中式负载均衡,进程内负载均衡)

  • 负载均衡:将用户的请求平摊在多个服务上,实现高可用

Ribbon本地负载均衡和Nginx服务端负载均衡的区别

Nginx是服务器端的负载均衡,客户端将请求交给nginx,nginx实现转发请求

ribbon是客户端的负载均衡,会在注册中心拿到注册中心的信息服务列表加到缓存中,从而在本地实现RPC远程服务调用

工作机制

Ribbon就是负载均衡+RestTemplate,它就是一个软负载均衡的客户端组件,需要和其他客户端结合起来,如和eureka结合

  1. 在选择EurekaServer,它优先选择在同一区域的负载较少的server
  2. 根据用户的指定策略(轮询、随机、加权),在server里取到信息,进行调用

使用

  1. 引入jar包,但默认我们使用eureka的新版本时,它默认集成了ribbon,所以不需要引入

    spring-cloud-starter-netflix-eureka-client集成了reibbon

    也可以手动引入,放到order(消费者)模块中,因为只有order访问pay时需要负载均衡

  2. RestTemplate类:

    1. getForObject(url,返回值.class):返回的是响应体数据转化成的对象,即json
    2. getForEntity(url,返回值.class):返回的是重要信息(响应头、体、状态码),可以通过返回对象的get方法获得这些信息

Ribbon常用负载均衡算法:

核心组件:IRule接口,Riboon使用该接口,根据特定算法从所有服务中,选择一个服务

Rule接口有7个实现类,每个实现类代表一个负载均衡算法

rule

替换

  1. 选择需要修改的消费者模块

  2. 需要自定义配置类,不能放在主启动类所在的包及子包下

  3. 额外创建一个包:和启动器的父包是兄弟包

  4. 创建配置类,指定负载均衡算法

    rule2

  5. 在主启动类上加一个注解@RibbonClient

    • name:要访问服务的名字
    • configuration:自定义配置类的class文件

自定义负载均衡算法:

轮询算法原理

rest接口第几次请求 % 服务器集群总数量(集群中的机器数) = 实际调用服务器的下标,重启服务器后,计数从1开始

自定义负载均衡算法(跳)

OpenFeign 服务接口调用

它是webService客户端(消费者端),使用方法是:定义一个服务接口,然后在上面添加注解,写上需要调用的方法,使用时调用这个接口中的方法,即可完成微服务之间的调用,实现接口间的远程调用,不再用Ribbon和restTemplate一样远程调用

就是A要调用B,Feign就是在A中创建一个一模一样的B对外提供服务的的接口,我们调用这个接口,就可以服务到B

使用

  1. 新建一个order项目,用于feign测试

    cloud_order_feign-80

  2. pom文件(引入openFeign的jar包)

  3. yml配置文件(因为是客户端,不注册进注册中心也可以)

  4. 主启动类(@EnableFeignClients,启动feign)

  5. 需要调用的其他的服务的接口(这里本应该是8001 Service接口中的方法,但controller也是调用的service方法,所以是一样的)

    feign

  6. controller(自动注入接口,调用接口的方法,实现远程调用)

    feign2

  7. 测试

    启动7001,7002

    启动8001,8002

    启动当前80模块

    Feign默认使用ribbon实现负载均衡

    测试方法是否成功调用

OpenFeign超时机制:

有时我们业务逻辑处理需要久一点,不能很快的让消费者远程调用到,我们需要改一下超时时间

OpenFeign默认等待时间是1秒,超过1秒,直接报错

  1. 设置超时时间,修改yml配置文件

    因为OpenFeign的底层是ribbon进行负载均衡,所以它的超时时间是由ribbon控制

    feign3

OpenFeign日志:

它提供了日志打印功能,可以通过配置日志级别,从而了解http请求的细节,就是对feign接口的调用情况的监控和输出

OpenFeign的日志级别有:

log

使用

  1. 实现在配置类中添加OpenFeign的日志类(指定http日志级别)

    log2

  2. 修改yml配置文件(指定命令行日志级别、哪个接口实现日志监控)

    log3

  3. 启动服务测试

Hystrix服务降级

概念:

  1. 服务雪崩:当多个微服务之间互相调用时,a-b-c-d……,这就是所谓的“扇出”,当某个微服务不管用,对a就会占用越来越多的资源,进而引起系统崩溃,雪崩
  2. 它是一个用于处理分布式的延迟和容错,当出现问题时不会导致级联故障,而是返回一个符合预期的备选响应,提高分布式弹性
  3. 服务降级:当服务繁忙时,不能一直等待,会返回客户端一个备选方案
    1. 程序运行异常
    2. 超时
    3. 服务熔断触发服务降级
    4. 线程池打满
    5. 宕机
  4. 服务熔断:当服务出现问题,出现故障,先关闭对此服务的访问,防止级联故障,再调用服务降级,最后恢复
  5. 服务限流:当并发量增大,限制访问的请求数量

使用服务降级

创建服务提供者模块(测试服务降级用)

  1. cloud-hystrix-pay-8007
  2. pom
  3. yml
  4. 主启动(@EnableEurekaClient)
  5. service(正常访问方法、睡3秒方法)
  6. controller
  7. 压力测试JMeter(还没有加入hystrix服务降级)

创建消费者模块(测试服务降级用)

  1. cloud-hystrix-order-80

  2. pom

  3. yml

  4. 主启动(@EnableFeignClients)

  5. 远程调用接口(@FeignClient(value=”服务名”))

  6. controller(调用接口)

  7. 压力测试

    出现问题:2W并发之后,消费者的访问速度变慢

解决方案

  1. 超时:不再等待
  2. 出错(宕机或运行出错):兜底

配置服务降级

==一般服务降级,都是放在客户端==

==热部署对@HystrixCommand注解不太敏感,建议修改完重启服务==

修改服务提供者payment模块

  1. 创建一个用于兜底的方法

  2. 为service的指定方法(会延迟的方法)添加@HystrixCommand注解

    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000") }) //3秒钟以内就是正常的业务逻辑
    //设定超时时间
    
  3. 主启动类上,添加激活hystrix的注解@EnableCircuitBreaker

  4. 测试:

    1. 设置方法时间>设定时间
    2. 设置异常

修改消费者order模块

  1. 修改yml配置文件

    hystrix1

  2. 主启动类添加注解,启用hystrix,@EnableHystrix

  3. 修改controller,添加要测试的降级方法和相关注解

  4. 测试

    1. 设置方法时间>设定时间
    2. 设置异常
    3. 这里pay模块和order模块都开启了服务降级,但服务提供者3秒,消费者设置为1.5秒,一定会被降级

出现的问题

  1. 每个业务方法都写了一个降级方法,重复代码多
  2. 降级方法与业务方法写在了一块,耦合度高

解决1:

配置一个全局的降级方法,所有方法都可以走这个降级方法,至于某些特殊创建,如上述的一样再单独创建方法

  1. 消费者模块创建一个全局方法

  2. 在类上使用注解指定其为全局降级方法(默认降级方法)

    @DefaultProperties(defaultFallback=”定义的全局方法”)

  3. 在方法上加注解@HystrixCommand,不用指定降级方法,使用默认全局

  4. 测试

解决2:

修改消费者模块

  1. 创建一个类实现远程接口,在实现类中统一处理异常

    h1

  2. 修改配置文件:添加:

    hystrix1

  3. 接口中注解增加fallback属性,让PayService的实现类生效:

    h2

它的运行逻辑是:
当请求过来,首先还是通过Feign远程调用pay模块对应的方法,但是如果pay模块报错(宕机,无法调用),调用失败,那么就会调用PayMentFalbackService类的当前同名的方法,作为降级方法

  1. 测试:

    启动正常访问后,关闭服务提供者,消费者再次访问,转向fallback方法

这样虽然解决了代码耦合度问题,但是又出现了过多重复代码的问题,每个方法都有一个降级方法

使用服务熔断

当达到最大访问次数时,出现问题概率大,拒绝访问,拉闸,调用降级的方法

机制:当某个服务不可用或响应时间太长,熔断该微服务,快速返回错误响应信息,当检测到恢复正常,再恢复调用链路

如:当失败调用到一定阈值,5秒内20次调用失败,就会启动熔断机制,熔断机制是注解@HystrixCommand

修改服务提供者模块

  1. 修改Payservice接口,添加服务熔断相关的方法:

    r1

    10秒之内(窗口,会移动),如果并发==超过==10个,或者10个并发中,失败了6个,就开启熔断器

    IdUtil是Hutool包下的类,这个Hutool就是整合了所有的常用方法,比如UUID,反射,IO流等工具方法什么的都整合了

    断路器的打开和关闭,是按照一下5步决定的

    1. 并发此时是否达到我们指定的阈值(默认10秒20次请求),到了阈值才有资格熔断
    2. 错误百分比,比如我们配置了60%(默认10秒50%请求),那么如果并发请求中,10次有6次是失败的,就开启断路器
    3. 上面的两个条件符合,断路器改变状态为open(开启),拉闸
    4. 这个服务的断路器开启,所有请求无法访问
    5. 熔断之后,开启休眠时间窗(默认5秒),此时只会执行降级逻辑,休眠时间窗结束之后,尝试让一些请求通过(半开状态half Open),如果请求还是失败,证明断路器还是开启状态,服务没有恢复,如果请求成功了,证明服务已经恢复,断路器状态变为close关闭状态,恢复

其他参数:见脑图

服务限流(sentinel会详说)

Hystrix服务监控:

一个图形化界面的实时监控平台

使用

  1. 创建监控平台模块(需要创建模块,比较麻烦,后面sentinel不需要)

    cloud_hystrixdashboard_9001

  2. pom文件(导入依赖dashboard)

  3. 配置文件

    server: port:9001

  4. 主启动类@EnableHystrixDashboard

  5. 修改需要监控的模块(8001,8002)

    1. 增加pom依赖(以前都增加有)

      actuator,这个是springboot的监控组件

  6. 启动

    访问: localhost:9001/hystrix

    注意,此时仅仅是可以访问HystrixDashboard,并不代表已经监控了8001,8002

  7. 如果要监控,还需要配置:(8001为例)

    1. 8001主启动类添加

      其他8002,8003都是一样的

  8. 启动服务7001,8001,9001

  9. 然后在web界面,指定9001要监控8001:

GateWay服务网关

zuul停更了

概念

GateWay是Spring生态系统上构建的API网关服务,替代了zuul,基于WebFlux框架,而WebFlux框架底层使用了高性能的Reactor模式通信框架Netty。

gateway之所以性能好,因为底层使用WebFlux,而webFlux底层使用netty通信(NIO)

特性:动态路由(能匹配任何请求),可对路由指定断言、过滤器,集成断路器、SpringCloud服务发现,请求限流

GateWay与zuul的区别:

  1. Zuul1是基于Sevlet2.5使用阻塞架构,每次IO操作都从工作线程中选一个执行,请求线程会阻塞到工作线程完成,性能较差
  2. gateWay使用非阻塞API,建立在spring5, project Reactor和spring boot2之上

路由:构建网关的基本模块,它由ID,目标URI,一系列断言和过滤器组成,通过断言为true则匹配该路由

即根据某些规则,将请求转发给指定服务

断言:开发人员可以通过http请求中的内容,与断言相匹配则可以进行路由

过滤:在路由的前后对请求进行修改

工作流程

就是路由转发+执行过滤器链

使用

  1. 新建网关模块GateWay

    cloud_gateway_9527

  2. pom(引入网关依赖gateWay)

  3. yml配置文件

  4. 主启动

  5. 根据服务提供者模块,设置路由网关

    我们不想暴露8001端口,所以在8001上套了一层9527

    1. 修改网关的yml配置文件

    意思就是:

    当访问localhost:9527/payment/get/1时,

    路由到localhost:8001/payment/get/1

以上是配置文件配置,也可以用硬编码配置,比较麻烦所以还是选择配置文件的方式

动态路由

默认情况gateWay会根据服务中心的服务列表,以微服务名为路径创建动态路由进行转发

使用

  1. 需要1个服务器、两个微服务提供者

  2. 修改网关yml文件(开启路由功能,修改路由地址)

  3. 启动微服务,进行测试

Pridicate断言

相当于网关的规则,满足了这些规则,找到对应的路由

其中predicates:path=/payment/get/**就是一个断言

这个断言表示,如果外部访问路径是指定路径,就路由到指定微服务上

  1. After:只有在指定时间后,才可以路由到指定微服务

  1. before:在指定时间前访问

  2. between:需要指定两个时间,在这之间的时间可以访问

  3. cookie:只有包含某些指定cookie(key,value)的请求才可以路由

    • 需要由两个参数,key名,value值
  4. Header:只有包含指定请求头的请求,才可以路由

    • 需要两个参数,header名,正则表达式,如:x-Request-id,\d+ 正整数
  5. host:只有指定主机的才可以访问,如:Host=**.shangguigu.com

    1. 测试:curl http://localhost:9527/paymentinfo -H “host:www.shangguigu.com"
    2. curl是命令行发送请求的方式,postman可视化图形界面的底层就是使用这个
  6. method:只有指定请求才可以路由,比如get请求…

    1. Method=GET
  7. path:只有访问指定路径,才进行路由

  8. Query:必须带有请求参数才可以访问,

    1. 需要两个参数,参数名,正则表达式,如:Query=username,\d+
    2. localhost:9527?username=123

Filter过滤器

种类

GatewayFilter:单一的过滤器,与断言类似(怎么加看官网)

GlobalFilter:全局过滤器

自定义过滤器(全局,常用)

  1. 自己创建一个过滤器类,实现两个接口 GlobalFilter,Ordered

    必须加入容器,实现接口

    意思:必须带有参数uname的,如果没有就过滤掉

  2. 启动测试

Config服务配置

问题:每个微服务都需要一个配置文件,并且,如果有几个微服务都需要连接数据库,那么就需要配4次数据库相关配置,并且当数据库发生改动,那么需要同时修改4个微服务的配置文件才可以

概念:为微服务架构中的微服务提供集中化外部配置支持,配置服务器为各个不同的微服务应用的所有环境提供了中心化的外部配置

分为服务端和客户端。

服务端就是分布式配置中心,是一个独立的微服务应用,默认采用git存储配置信息

客户端是启动时在配置中心获取加载配置信息

作用

  1. 集中管理配置
  2. 动态化配置更新
  3. 不同环境不同配置
  4. 将配置信息以Rest接口的形式暴露(通过controller获取配置信息)

使用配置中心(服务端)

  1. 使用github作为配置中心的仓库

    初始化git环境:

  2. 新建config服务端模块

    1. pom(引入config-server依赖)
    2. yml配置文件

    1. 主启动(@EnableConfigServer开启配置中心服务端)

    2. 修改本地hosts

      为了输入url时localhost直接等于config-3344.com

    3. 测试

      启动3344 (要先启动eureka)

读取配置文件的规则

使用配置中心(客户端)

  1. 创建config客户端项目

    cloud-config-client-3355

  2. pom(引入config依赖)

  3. yml配置文件

    注意:这个配置文件就不是application.yml,bootstrap.yml

    这个配置文件的作用是,先到配置中心加载配置,然后加载到application.yml中

    因为bootstrap具有高优先级,保证不会被本地配置覆盖,负责从外部源加载配置文件,两个上下文实现共享

  4. 主启动类(@EnableEurekaClient,这里没有配置客户端的注解)

  5. controller

    这就是上面提到的rest风格将配置对外暴露

    如果客户端运行正常,就会读取到github上配置文件的属性config.info的值

  6. 测试:

    启动3344,3355

    访问3355的 /configInfo

问题:如果配置修改,3344仍然能获得最新配置,但是3355却不能动态刷新

实现动态刷新

  1. 修改3355,增加pom依赖actuator监控

  2. 修改配置文件,增加一个配置

  3. 修改controller(@RefreshScope)

  4. 需要外部发送post请求通知3355

    curl -X POST "http://localhost:3355/actuator/refresh"

  5. 刷新测试是否更换为最新配置

问题:如果由多个客户端,每个都需要通知会很麻烦,所以需要广播,这就引出了Bus消息总线

SpringCloud Bus 消息总线

它是将分布式系统的节点与轻量级消息系统链接起来的框架

目前支持RabbitMQ和Kafka

设计思想

  1. 消息发给客户端,它能让其在其它客户端间传播

  2. 也可以将消息发给config配置中心,一次性广播给其他客户端

  • 发给配置中心更合理,第一种破坏了业务模块职责的单一性,破坏了服务个节点的对等性

概念:在微服务架构中,通常会使用消息代理构建一个共有的主题,让所有微服务实例都订阅,该主题产生的消息也会被监听和消费,所以它为消息总线。

原理:configClient实例都监听同一个主题,当一个服务刷新数据,它会把消息放在主题中,其他实例就能得到通知,然后更新自身的配置

使用

  1. 配置RabbitMQ环境

  2. 为演示多个通知,多创建一个配置中心客户端

    ==复制3355即可,创建为3366==

    全部复制3355的即可

  3. 使用Bus实现全局广播

    • 配置3344(配置中心服务端)
    1. 修改yml配置文件

    2. 添加pom

      springboot的监控组件actuator,和消息总线整合rabbitmq:bus-amqp

    • 修改3355(配置中心的客户端1)
    1. 增加pom

      springboot的监控组件actuator,和消息总线整合rabbitmq:bus-amqp

    2. 修改yml文件(注意客户端的配置文件都交bootstrap)

    • 修改3366(配置中心的客户端2)
    1. 和上面的步骤一样
    • 测试:

      启动7001(注册中心),3344(配置中心服务器),3355,3366(配置中心客户端)

      修改github的配置文件

      因为我们采用的是发送通知给服务器,再广播给客户端的方式,所以要给3344发消息

      curl -X POST "http://localhost:3344/actuator/bus-refresh"

    原理:所有客户端都监听了一个rabbitMq的topic,我们将信息放入这个topic,所有客户端都可以送到,从而实时更新

配置定点通知

只通知部分服务,其他服务不受影响

http://localhost:3344/actuator/bus-refresh/微服务名:端口号

Spring Cloud Stream消息驱动

问题:一个项目中可能有后端–>大数据,如果后端用的是RabbitMq,大数据用的是kafka,就会导致经常切换,影响使用,学习负担重

Spring Cloud Stream就类似jpa,屏蔽底层消息中间件的差异(比如kafka有topic,rabbitMq有exchange),降低切换成本,统一消息编程模型,程序员主要操作Spring Cloud Stream即可,不需要管底层是kafka还是rabbitMq

应用程序通过**inputs(生产者),outputs(消费者)**与stream中的binder对象交互,我们主要操作binder对象与底层mq交互即可。

  • 如何屏蔽底层差异?

    由于不同的消息中间件实现细节有较大差异,定义的绑定器作为中间层,实现了应用程序与消息中间件之间的隔离,通过程序暴露统一的channel通道,让应用程序不再需要与消息中间件交互

业务流程

source用于获取数据(要发送到mq的数据)
channel类似SpringCloudStream中的中间件,用于存放source接收到的数据,或者是存放binder拉取的数据

常用api和注解

使用SpringCloudStream

创建3个模块,1个生产者,2个消费者

生产者创建

  1. pom(stream-rabbit)

  2. yml配置文件

    设置stream中rabbitmq配置,之后绑定这个配置

  3. 主启动

  4. service和实现类

    这里,就会调用send方法,将消息发送给channel,

    然后channel通过上述的配置文件信息将消费发送给binder,然后发送到rabbitmq中

  5. controller

  6. 测试:

    启动rabbitmq

    启动7001,8801

    打开rabbitmq的web界面,看到创建出来的管道Exchange(在kafka里叫做主题topic),就是我们配置文件中配置的exchange

    访问8801的/sendMessage

    看到消息已经发送到mq中

消费者创建

  1. pom文件(stream-rabbit)

  2. yml配置文件

  3. 主启动类

  4. 业务类(消费数据)

    生产者发送消息时,使用send方法发送,send方法发送的是一个个Message,里面封装了数据

    所以接收时也要使用Message来接收

  5. 测试

    启动7001,8801,8802

    使用生产者生产消息,消费者已经接收到消息

创建消费者2

创建8003,和8002一摸一样,只是端口号不同,为了演示重复消费等问题

问题:重复消费,当生产者生产一条消息,8002/8003都消费到了同一条数据

原因:没有分组,不同组是可以重复消费的,同一个组中的多个消费者是竞争关系,能保证消息只会被其中一个消费者消费一次

自定义分组(解决重复消费)

修改8802,8803的配置文件

将8802,8803都分到A组

重启这两个消费者,生产消息,会发现没有重复消费

持久化问题

当服务(消费者)挂掉,怎么消费生产者生产还没有消费的数据?

结论:只要加上group分组,当下次重启之后,就会自动获取未消费的消息继续消费

Spring Cloud Sleuth链路追踪

问题:在微服务框架中,客户端发送的请求在后端系统可能会经过很多服务节点,我们可以通过链路追踪查看都调用了哪些服务

sleuth就是用于追踪每个请求的整体链路

原理

Trace ID:一条链路的唯一标识

Span ID:一次请求的唯一标识

Parent ID:上一次请求的唯一标识

在经过一次请求时,sleuth会记录下各种ID,最后连接起来

使用

  1. 安装zipkin(图形化展示链路)

    1. 运行jar包 java -jar xxxx.jar
    2. 然后就可以访问web界面, 默认zipkin监听的端口是9411localhost:9411/zipkin/
  2. 使用sleuth

    1. 修改8001的pom(zipkin)

      这个包虽然叫zipkin但是,里面包含了zpikin与sleuth

    2. 修改yml配置文件

    3. 修改80的pom(一样)

    4. 修改yml配置文件(一样)

  3. 测试

    启动7001,8001,80

    localhost:9411/zipkin/

SpringCloud Alibaba

支持的功能

Nacos注册中心+配置中心

Nacos=Eureka+config+bus

安装

需要java8 和 Maven

  1. 到github上下载安装包

    解压安装包

  2. 启动Nacos(注册中心服务器)

    在bin下,进入cod

    ./startup.cmd

  3. 访问Nacos

    Nacos默认监听8848

    localhost:8848/nacos

    账号密码:默认都是nacos

使用注册中心

创建服务提供者

注册中心服务器已经不需要我们手写,Nacos启动就是服务器

  1. 创建服务提供者模块cloudalibaba-pay-9001

  2. 改pom

    1. 父工程alibaba-dependencies
    2. alibaba-nacos-discovery
  3. yml配置文件

  4. 主启动类@EnableDiscoveryClient

  5. controller

  6. 测试

    启动nacos,9001服务提供者

    查看web界面,看看9001是否注册成功

创建服务提供者2(测试负载均衡)

直接复制上面的9001即可

创建消费者模块

  1. cloudalibaba-order-83

  2. Pom(alibaba-nacos-discovery)

    因为内部集成了Ribbon,所以自带负载均衡功能

  3. yml配置文件

  4. 主启动类@EnableDiscoveryClient

  5. 配置类

    因为Naocs要使用Ribbon进行负载均衡,那么就需要使用RestTemplate

    @LoadBalanced开启负载均衡

  6. controller

  7. 测试

    启动83消费者,访问9001,9002,负载均衡

Nacos与其他服务注册的对比

Nacos它既可以支持CP,也可以支持AP,可以切换

一般来说,不需要存储服务级别的,用AP,高可用而削弱了一致性,临时实例

需要服务级别编辑或存储配置信息,CP是必须的,持久化实例

用于切换模式

curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'

使用配置中心

在项目初始化后,要保证从配置中心进行配置拉取,拉取配置之后,才能保证下项目正常启用

创建配置中心的客户端模块

cloudalibaba-Nacos-config-client-3377

  1. pom

    1. alibaba-nacos-config
    2. alibaba-nacos-discovery
  2. yaml配置文件

    这里需要配置两个配置文件,application.yml和bootstrap.yml(通用配置)

    主要是为了可以与spring clodu config无缝迁移

  3. 主启动类@EnableDiscoveryClient

  4. controller

    @RefreshScope支持动态刷新

  5. 在Nacos添加配置信息

    Nacos的配置规则:

    就是我们在客户端如何指定读取配置文件,配置文件的命名的规则

    prefix:

    默认就是当前服务的服务名称,也可以通过spring.cloud.necos.config.prefix配置

    spring.profile.active:

    就是我们在application.yml中指定的,当前是开发环境还是测试等环境,这个可以不配置,如果不配置,那么前面的 - 也会没有

    file-extension:

    就是当前文件的格式(后缀),目前只支持yml/yaml和properties

    在web页面上创建配置文件

    Data ID:就是按照命名规则配置的名字

  6. 测试

    重启配置文件模块客户端

    访问controller,localhost:3377/config/info

    拿到配置文件的值

    这时我们已经自动开启了动态刷新,只要修改配置文件,客户端就会立即刷新,因为Nacos支持Bus总线,会自动发送命令更新所有客户端

Nacos配置中心之分类配置:

问题:实际开发中会为一个系统准备多个开发环境,也会有多个配置文件,为了区分读取配置文件,可以将配置进行分类读取

配置文件可以归为:Namespace(命名空间)+Group(组)+Data ID(开发环境)

默认情况:Namespace=public,Group=DEFAULT_GROUP,环境=DEFAULT

  1. 按照不同的DataId划分

    1. 在配置中心新建两个配置文件,DataID分为两个,dev,test

    2. 通过application.yml配置文件,实现多环境读取

      此时,改为dev,就会读取dev的配置文件,改为test,就会读取test的配置文件

      重启服务

  2. 按照不同的GroupID划分

    1. 直接在配置中心新建配置文件时指定组

    2. 在客户端配置,使用指定组的配置文件

      bootstrap+application

      重启服务

  3. 按照不同的namespace划分

    1. 创建新命名空间,在新命名空间中创建配置文件

    2. 客户端配置使用不同名称空间:

      重启服务

Nacos集群和持久化配置:

Nacos默认有自带嵌入式数据库,derby,但是如果做集群模式的话,就不能使用自己的数据库

​ 不然每个节点一个数据库,那么数据就不统一了,需要使用外部的mysql

Nacos采用了集中式存储方式来支持集群化部署,目前只支持MySQL的存储

  1. 切换mysql数据库:

    将默认的derby数据库切换为mysql数据库,要求数据库(5.6+)

  2. 找到Nacos安装目录下的sql脚本,在Mysql中使用该脚本nacos-mysql.sql

  3. 修改Nacos安装目录下的安排application.properties,添加:

    spring.datasource.platform=mysql
     
    db.num=1
    db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
    db.user=root    #可变
    db.password=123456    #可变
    
  4. 此时可以重启nacos,那么就会改为使用我们自己的mysql

    数据消失,证明已经切换成功

Linux上配置Nacos集群+Mysql数据库

需要1个nginx,3个Nacos,1个Mysql

  1. 下载安装Nacos的Linux版安装包

  2. 进入安装目录,现在执行自带的sql文件(配置Mysql)

    进入mysql,执行sql文件

  3. 修改配置文件,切换为我们的mysql(配置Nacos)

    就是上面windos版要修改的几个属性(application.properties)

  4. 修改cluster.conf,指定哪几个节点是Nacos集群

    这里使用3333,4444,5555作为三个Nacos节点监听的端口

  5. 设置端口启动

    既然要在一个节点上启动不同Nacos实例,就要修改startup.sh,使其根据不同端口启动不同Nacos实例

    可以看到,这个脚本就是通过jvm启动nacos

    ​ 所以我们最后修改的结果就是,nohup java -Dserver.port=3344

  6. 启动Nacos集群

    ./startup.sh -p 3333

    ./startup.sh -p 4444

    ./startup.sh -p 5555

  7. 配置Nginx:

    1. 修改nginx配置文件

    2. 按照指定启动

    3. 测试

      访问,自己虚拟机ip:1111

      如果进入了nacos的Web界面,证明成功

    4. 将微服务注册进Nacos集群中

    5. 进去web界面,看是否注册成功

Sentinel服务熔断、限流:

Hystrix与Sentinel对比

使用Sentinel

  1. 下载Sentinel的jar包

  2. 运行sentinel

    java -jar

    注意:sentinel的默认端口为8080

  3. 访问sentinel

    localhost:8080

微服务整合sentinel:

  1. 启动sentinel

  2. 新建模块8401,主要用于配置sentinel

    1. pom

      1. alibaba-nacos-discovery(主要)
      2. sentinel-datasource-nacos
      3. alibaba-sentinel(主要)
      4. openfeign
    2. yml配置文件

    3. 主启动@EnableDiscoveryClient

    4. controller

    5. 启动测试

      注意:

      sentinel是懒加载,需要我们执行一次访问,才会有信息

      访问localhost/8401/testA,刷新控制台,即可看到

sentinel的流控规则

流控模式

  1. QPS直接快速失败(每秒请求数量达到阈值,直接失败,都不给分配线程)请求进不去服务内

  2. 线程数(当线程数达到阈值,直接失败,即都放进来,线程占满就不失败)进去服务内,超过阈值再失败

  3. 关联(服务A关联服务B,当A达到阈值,B也会失败)

    ==应用场景: 比如支付接口达到阈值,就要限流下订单的接口,防止一直有订单==

  4. 链路(多个请求都来自同一个服务)

流控效果

  1. 预热Warm up:冷启动方式,当系统长期处于低水位(并发量小)情况下,流量突然增加,拉升到高水位,冷启动可以再一定时间内主键增加到阈值的上限。

    起始阈值:阈值/冷加载因子(默认为3),

    预热时长:在几秒后达到阈值

    ==应用场景:秒杀==

  2. 排队等待:让请求匀速通过,阈值类型必须是qps

    如每秒通过1次请求,超过阈值也不会失败,进行排队等待,如果等待时长超过超时时间,再失败

    ==应用场景:一会并发量高,一会并发量低==

降级规则

熔断降级:在调用链路中某个资源不稳定,就对其进行限制,让请求快速失败,避免级联错误

Sentinel断路器没有半开状态

  1. RT:秒级平均响应时间,表示请求处理要在规定时间内完成

​ 必须满足以下条件才能触发断路器:

​ 1秒内超过5个请求(默认的)

​ 响应时间>平均响应时间(就是请求处理时间超过了规定的时间)

  1. 异常比例:每秒异常总数的比例超过异常比例阈值

​ 1秒内超过5个请求(默认的)

​ ==如果没有触发熔断条件,就会正常抛出异常==

  1. 异常数:1分钟内异常数大于阈值

​ 1秒内超过5个请求(默认的)

时间窗口要超过60秒

热点规则

热点即经常访问的数据,很多时候某个热点数据访问频次很高,需要我们进行限制

如:商品ID作为参数,我们就要根据商品的ID进行限制

如何自定义降级方法,而不是默认的抛出异常

使用**@SentinelResource**直接实现降级方法,它等同Hystrix的@HystrixCommand

value:随意取,即热点的资源名

blockHandler:指定的降级方法,跳转自定义的页面

注意:当没有设置blockHandler降级方法时,会向前台抛异常

定义热点规则

参数索引从0开始,0就是代表第一个参数

这个意思就是:当传递的参数每秒频次超过1,就会调用降级方法。但是我们访问其他参数没有问题

设置参数例外项

专门设定某个参数的值为几的,有专门的限流阈值处理

如:参数p1的值为5,限流阈值为200,即p1其他值受单机阈值1的影响,而5受200的影响

注意:如果请求中有异常,就不会走降级的方法,它只会抛出异常,因为这个方法只是设置热点规则,没有对异常处理的机制。

系统规则

系统自适应限流:从整体维度对应用入口进行限流

就是大门口,根据整个系统的情况来限流

@SentinelResource注解:

用于配置降级

  1. 搭建环境

  2. 为8401添加依赖

    添加我们自己的common包的依赖

  3. 额外创建一个controller类

    blockHandler如果不设置,就弹出系统默认的降级页面

  4. 设置限流

    ==注意,我们这里配置规则,资源名指定的是@SentinelResource注解value的值,也可以时访问路径==

  5. 测试

    问题:当8401服务被关闭后,定义的限流规则就没有了,因为是临时的

可以看到,上面配置的降级方法,又出现Hystrix遇到的问题了

​ 降级方法与业务方法耦合

​ 每个业务方法都需要对应一个降级方法

自定义限流处理逻辑

  1. 单独创建一个类,用于处理限流兜底

  2. 在controller中,指定使用自定义类中的方法作为降级方法

  3. 测试

服务熔断

一般服务熔断是用在消费者模块,对其进行降级、限流等操作

  1. 配置环境

    1. 启动nacos和sentinel

    2. 新建两个服务提供者模块 9003和9004(配置一样)

      cloudalibaba-provider-payment9003/9004

      1. pom

      2. yml

      3. 主启动@EnableDiscoveryClient

      4. controller

        启动9003,9004

    3. 新建消费者模块

      cloudalibaba-consumer-payment-84

      1. pom

      2. yml配置文件

      3. 主启动类@EnableDiscoveryClient

      4. RestTemplate配置类

      5. controller

  2. 为业务方法(消费者)增加fallback来指定降级方法(处理java异常)

    重启order(因为热部署对java代码敏感,对于注解不是很敏感,建议重启)

    ==所以,fallback是用于管理异常的,当业务方法发生异常,可以降级到指定方法==

  3. 为业务方法添加blockHandler,看看是什么效果

    可以看到.,直接报错了,并没有降级

    也就是说,==blockHandler只负责sentienl定义的规则降级==

  4. fallback和blockHandler都配置

    测试:

    设置限流,会发现当两个都同时生效时,==blockhandler优先生效==

  5. @SentinelResource还有一个属性,exceptionsToIgnore

    忽略异常,指定一个异常,如果出现了该异常不会走降级方法,直接向用户抛出异常

sentinel整合ribbon+openFeign+fallback

  1. 修改消费者84模块,使其支持feign

    1. pom(openfeign)

    2. yml配置文件

    3. 主配置类(@EnableFeignClients)

    4. 创建远程调用服务提供者模块的接口

    1. 测试:

      启动9003服务提供者,84消费者

      当关闭9003时,如果没有排除异常,而是被调用了降级方法,则为成功

sentinel持久化规则

默认规则是临时存储的,重启sentinel就会消失

将限流配置规则持久化进Nacos保存,只要刷新服务的某个rest地址,sentinel控制台的留空规则就能看到,只要Nacos里面的配置不删除,针对服务上的sentinel上的流控规则就持续有效

这里以8401为例修改

  1. pom(sentinel-datasource-nacos)

  2. 修改yml配置文件

    实际上就是指定,我们的规则要保证在哪个名称空间的哪个分组下,下次就在这个地方取出

    这里没有指定namespace, 但是是可以指定的

    注意,这里的dataid要与8401的服务名一致

    server-addr:nacos的地址

    dataId:服务名

    groupId:组名

    data-type:数据类型

    rule-type:flow(流控配置)

  3. 在nacos中创建一个配置文件

    dataId就是上面配置文件中指定的,这个就是持久化的配置

    ==json中,这些属性的含义:==

  4. 启动8401,访问rest请求

    可以看到sentinel已经读取到了规则

    关闭8401后,规则就消失了,再次启动就会再读取到规则

Seata分布式事务

是一个分布式事务的解决方案

一次业务操作需要多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题

概念

seata安装

  1. 下载安装seata的安装包,解压

  2. 修改file.conf(记得先备份)

  3. mysql建库建表

    1. 上面指定了数据库名为seata,创建一个数据库名为seata
    2. 建表,在seata的安装目录下有一个db_store.sql,运行即可
  4. 修改配置文件registry.conf

    配置seata作为微服务,指定注册中心

  5. 启动

    先启动nacos

    再启动seata-server(运行安装目录下的,seata-server.bat)

搭建业务环境

业务说明:

我们需要创建三个服务:订单服务,库存服务,账户服务

场景:

  1. 当用户下单时,会再订单服务中创建一个订单,
  2. 然后通过远程调用库存服务扣减商品库存。
  3. 通过远程调用账户服务扣减账户余额,
  4. 最后在订单服务中修改订单状态为已完成
  1. 创建三个数据库

    1. seata_order:存储订单数据库
    2. seata_storage:存储库存数据库
    3. seata_account:存储账户信息数据库
  2. 创建对应的表

    1. t_order
    2. t_storage
    3. t_account
  3. 创建回滚日志表,方便查看

    注意==每个库都要执行一次==这个sql,生成回滚日志表

  4. 每个业务都创建一个微服务,也就是要有三个微服务,订单,库存,账户

    ==订单==,seta-order-2001

    1. pom

      1. alibaba-nacos-discovery
      2. alibaba-seata(排除io.seata)
      3. seata-all,用自己的Seata
      4. openfeign
    2. yml配置文件

      server:
        port: 2001
      
      spring:
        application:
          name: seata-order-service
        cloud:
          alibaba:
            seata:
              # 自定义事务组名称需要与seata-server中的对应,我们之前在seata的配置文件中配置的名字
              tx-service-group: fsp_tx_group
          nacos:
            discovery:
              server-addr: 127.0.0.1:8848
        datasource:
          # 当前数据源操作类型
          type: com.alibaba.druid.pool.DruidDataSource
          # mysql驱动类
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: root
      feign:
        hystrix:
          enabled: false
      logging:
        level:
          io:
            seata: info
      
      mybatis:
        mapperLocations: classpath*:mapper/*.xml
      
    3. 创建配置文件file.conf

      transport {
        # tcp udt unix-domain-socket
        type = "TCP"
        #NIO NATIVE
        server = "NIO"
        #enable heartbeat
        heartbeat = true
        #thread factory for netty
        thread-factory {
          boss-thread-prefix = "NettyBoss"
          worker-thread-prefix = "NettyServerNIOWorker"
          server-executor-thread-prefix = "NettyServerBizHandler"
          share-boss-worker = false
          client-selector-thread-prefix = "NettyClientSelector"
          client-selector-thread-size = 1
          client-worker-thread-prefix = "NettyClientWorkerThread"
          # netty boss thread size,will not be used for UDT
          boss-thread-size = 1
          #auto default pin or 8
          worker-thread-size = 8
        }
        shutdown {
          # when destroy server, wait seconds
          wait = 3
        }
        serialization = "seata"
        compressor = "none"
      }
      service {
        #vgroup->rgroup
        # 事务组名称
        vgroup_mapping.fsp_tx_group = "default"
        #only support single node
        default.grouplist = "127.0.0.1:8091"
        #degrade current not support
        enableDegrade = false
        #disable
        disable = false
        #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
        max.commit.retry.timeout = "-1"
        max.rollback.retry.timeout = "-1"
      }
       
      client {
        async.commit.buffer.limit = 10000
        lock {
          retry.internal = 10
          retry.times = 30
        }
        report.retry.count = 5
        tm.commit.retry.count = 1
        tm.rollback.retry.count = 1
      }
       
      ## transaction log store
      store {
        ## store mode: file、db
        #mode = "file"
        mode = "db"
       
        ## file store
        file {
          dir = "sessionStore"
       
          # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
          max-branch-session-size = 16384
          # globe session size , if exceeded throws exceptions
          max-global-session-size = 512
          # file buffer size , if exceeded allocate new buffer
          file-write-buffer-cache-size = 16384
          # when recover batch read size
          session.reload.read_size = 100
          # async, sync
          flush-disk-mode = async
        }
       
        ## database store
        db {
          ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
          datasource = "dbcp"
          ## mysql/oracle/h2/oceanbase etc.
          db-type = "mysql"
          driver-class-name = "com.mysql.jdbc.Driver"
          url = "jdbc:mysql://127.0.0.1:3306/seata"
          user = "root"
          password = "root"
          min-conn = 1
          max-conn = 3
          global.table = "global_table"
          branch.table = "branch_table"
          lock-table = "lock_table"
          query-limit = 100
        }
      }
      lock {
        ## the lock store mode: local、remote
        mode = "remote"
       
        local {
          ## store locks in user's database
        }
       
        remote {
          ## store locks in the seata's server
        }
      }
      recovery {
        #schedule committing retry period in milliseconds
        committing-retry-period = 1000
        #schedule asyn committing retry period in milliseconds
        asyn-committing-retry-period = 1000
        #schedule rollbacking retry period in milliseconds
        rollbacking-retry-period = 1000
        #schedule timeout retry period in milliseconds
        timeout-retry-period = 1000
      }
       
      transaction {
        undo.data.validation = true
        undo.log.serialization = "jackson"
        undo.log.save.days = 7
        #schedule delete expired undo_log in milliseconds
        undo.log.delete.period = 86400000
        undo.log.table = "undo_log"
      }
       
      ## metrics settings
      metrics {
        enabled = false
        registry-type = "compact"
        # multi exporters use comma divided
        exporter-list = "prometheus"
        exporter-prometheus-port = 9898
      }
       
      support {
        ## spring
        spring {
          # auto proxy the DataSource bean
          datasource.autoproxy = false
        }
      }
      
    4. 创建registry.conf

      registry {
        # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
        type = "nacos"
       
        nacos {
          #serverAddr = "localhost"
          serverAddr = "localhost:8848"
          namespace = ""
          cluster = "default"
        }
        eureka {
          serviceUrl = "http://localhost:8761/eureka"
          application = "default"
          weight = "1"
        }
        redis {
          serverAddr = "localhost:6379"
          db = "0"
        }
        zk {
          cluster = "default"
          serverAddr = "127.0.0.1:2181"
          session.timeout = 6000
          connect.timeout = 2000
        }
        consul {
          cluster = "default"
          serverAddr = "127.0.0.1:8500"
        }
        etcd3 {
          cluster = "default"
          serverAddr = "http://localhost:2379"
        }
        sofa {
          serverAddr = "127.0.0.1:9603"
          application = "default"
          region = "DEFAULT_ZONE"
          datacenter = "DefaultDataCenter"
          cluster = "default"
          group = "SEATA_GROUP"
          addressWaitTime = "3000"
        }
        file {
          name = "file.conf"
        }
      }
       
      config {
        # file、nacos 、apollo、zk、consul、etcd3
        type = "file"
       
        nacos {
          serverAddr = "localhost"
          namespace = ""
        }
        consul {
          serverAddr = "127.0.0.1:8500"
        }
        apollo {
          app.id = "seata-server"
          apollo.meta = "http://192.168.1.204:8801"
        }
        zk {
          serverAddr = "127.0.0.1:2181"
          session.timeout = 6000
          connect.timeout = 2000
        }
        etcd3 {
          serverAddr = "http://localhost:2379"
        }
        file {
          name = "file.conf"
        }
      }
      

      实际上,就是要将seata中的我们之前修改的两个配置文件复制到这个项目下

    5. 主启动

      @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) //取消数据源的自动创建
      @EnableDiscoveryClient
      @EnableFeignClients//使用openFeign的远程调用
      public class SeataOrderMain2001 {
      
          public static void main(String[] args) {
              SpringApplication.run(SeataOrderMain2001.class,args);
          }
      }
      
    6. entity类(也叫domain类)

      //使用了lombok
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class CommonResult<T> {
          private Integer code;//状态码
          private String message;//消息
          private T data;//json数据
      
          public CommonResult(Integer code, String message) {
              this(code, message, null);
          }
      }
      
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class Order {
          private Long id;
          private Long userId;
          private Long productId;
          private Integer count;
          private BigDecimal money;
          private Integer status;//订单状态,0为创建中,1为已完结
      }
      
    7. service层

      public interface OrderService {
          /**
           * 创建订单
           */
          void create(Order order);
      }
      
      @FeignClient(value = "seata-storage-service")//这里是远程调用的接口,真正方法在库存模块写,这个接口用来调用
      public interface StorageService {
          /**
           * 减库存
           * 这里是远程调用
           */
          //传入产品id,商品的购买数量
          @PostMapping(value = "/storage/decrease")
          CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
      }
      
      @FeignClient(value = "seata-account-service")//这里是远程调用的接口,真正方法在账户模块写,这个接口用来调用
      public interface AccountService {
          /**
           * 减余额
           */
          //传入用户id,用户消费的金额
          @PostMapping(value = "/account/decrease")
          CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
      }
      
    8. ServiceImpl

      @Service
      @Slf4j
      public class OrderServiceImpl implements OrderService {
      
          @Resource
          private OrderDao orderDao;
          @Resource
          private AccountService accountService;
          @Resource
          private StorageService storageService;
      
          /**
           * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
           * 简单说:
           * 下订单->减库存->减余额->改状态
           * GlobalTransactional seata开启分布式事务,异常时回滚,name保证唯一即可
           * @param order 订单对象
           */
          @Override
          ///@GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class)
          public void create(Order order) {
              // 1 新建订单
              log.info("----->开始新建订单");
              orderDao.create(order);
      
              // 2 扣减库存
              log.info("----->订单微服务开始调用库存,做扣减Count");
              storageService.decrease(order.getProductId(), order.getCount());
              log.info("----->订单微服务开始调用库存,做扣减End");
      
              // 3 扣减账户
              log.info("----->订单微服务开始调用账户,做扣减Money");
              accountService.decrease(order.getUserId(), order.getMoney());
              log.info("----->订单微服务开始调用账户,做扣减End");
      
              // 4 修改订单状态,从0到1,1代表已完成
              log.info("----->修改订单状态开始");
              orderDao.update(order.getUserId(), 0);
      
              log.info("----->下订单结束了,O(∩_∩)O哈哈~");
          }
      }
      
    9. dao层,也就是接口

      这里使用的是mapper映射,xml的方式

      @Mapper
      public interface OrderDao {
          /**
           * 1 新建订单
           */
          int create(Order order);
      
          /**
           * 2 修改订单状态,从0改为1
           */
          //传入用户id,状态为0
          int update(@Param("userId") Long userId, @Param("status") Integer status);
      }
      

      ==在resource下创建mapper文件夹,编写mapper.xml==

      <?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">
      <!--映射dao-->
      <mapper namespace="com.eiletxie.springcloud.alibaba.dao.OrderDao">
      <!--映射sql表和实例的属性,如果相同就不需要写-->
          <resultMap id="BaseResultMap" type="com.eiletxie.springcloud.alibaba.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" parameterType="com.eiletxie.springcloud.alibaba.domain.Order" useGeneratedKeys="true"
                  keyProperty="id">
              insert into t_order(user_id,product_id,count,money,status) values (#{userId},#{productId},#{count},#{money},0);
          </insert>
      <!--找到用户,并且他的状态为0,将状态修改为1-->
          <update id="update">
              update t_order set status =1 where user_id =#{userId} and status=#{status};
         </update>
      </mapper>
      
    10. controller层

      @RestController
      public class OrderController {
          @Resource
          private OrderService orderService;
          /**
           * 创建订单
           */
          @GetMapping("/order/create")
          //返回通用数据给前台
          public CommonResult create(Order order) {
              orderService.create(order);
              return new CommonResult(200, "订单创建成功");
          }
      }
      
    11. config配置类

      @Configuration
      @MapperScan({"com.eiletxie.springcloud.alibaba.dao"})//指定我们的接口的位置
      public class MyBatisConfig {
      }
      
       /**
           * 使用Seata对数据源进行代理
       */
      @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 druidDataSource) {
              return new DataSourceProxy(druidDataSource);
          }
      
          @Bean
          public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
              SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
              bean.setDataSource(dataSourceProxy);
              ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
              bean.setMapperLocations(resolver.getResources(mapperLocations));
              return bean.getObject();
          }
      }
      

    ==库存==,seta-storage-2002

    ==看脑图==

    1. pom
    2. 配置文件
    3. 主启动类
    4. file.conf
    5. registry.conf
    6. domain
    7. service层
    8. dao层
    9. controller层
    10. config

    ==账号==,seta-account-2003

    ==看脑图==

    1. pom
    2. 配置文件
    3. 主启动类
    4. file.conf
    5. registry.conf
    6. domain
    7. service层
    8. dao层
    9. controller层
    10. config

seata原理:

第一阶段:

第二阶段:

提交

一阶段已经提交,只需要保存即可

回滚

因为一阶段已经保存过了,二阶段回归需要反向sql进行补偿


文章作者: 小苏
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小苏 !
评论
  目录