SpringCloudAlibaba学习笔记

SpringCloudAlibaba学习笔记

微服务概述

image-20211101094351402

image-20211109165030208

服务注册与发现:Nacos

注册中心对比

image-20211101091408930

nacos架构

image-20211101091546883

nacos安装

1:下载

https://github.com/alibaba/nacos/releases/tag/1.4.2

2:修改配置文件

image-20211101092653419

使用自定义的数据库,不使用nacos默认的数据库

image-20211101092830534

建立数据库,运行 sql文件

image-20211101092948439

修改启动文件,修改启动模式为单机模式

image-20211101093321885

双击 startup.cmd 启动nacos

image-20211101093606999

访问:http://localhost:8848/nacos/

nacos注册中心工作流程

服务注册: Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。

服务心跳:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。

服务同步:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性

服务发现:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给NacosServer,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存

服务健康检查:Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)

Cloud与Boot版本对应

SpringCloudNetfix与SpringBoot:返回一个JSON字符串

https://start.spring.io/actuator/info

image-20211101095024496

SpringCloudAlibaba与SpringBoot:

https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明

毕业版本依赖关系(推荐使用)

Spring Cloud VersionSpring Cloud Alibaba VersionSpring Boot Version
Spring Cloud 2020.0.12021.12.4.2
Spring Cloud Hoxton.SR92.2.6.RELEASE2.3.2.RELEASE
Spring Cloud Greenwich.SR62.1.4.RELEASE2.1.13.RELEASE
Spring Cloud Hoxton.SR32.2.1.RELEASE2.2.5.RELEASE
Spring Cloud Hoxton.RELEASE2.2.0.RELEASE2.2.X.RELEASE
Spring Cloud Greenwich2.1.2.RELEASE2.1.X.RELEASE
Spring Cloud Finchley2.0.4.RELEASE(停止维护,建议升级)2.0.X.RELEASE
Spring Cloud Edgware1.5.1.RELEASE(停止维护,建议升级)1.5.X.RELEAS

本次实验中使用的版本:

最终决定(版本号记忆)∶

springcloud-alibaba:2.2.5.RELEASE

springcloud:Hoxton.SR8

springboot:2.3.2.RELEASEI

简单使用

服务提供者
<!--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>
<!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

配置文件:

server.port=9001
# 服务名称,必须写,保证唯一
spring.application.name=cloud-goods
spring.cloud.nacos.discovery.server-addr=localhost:8848
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos

启动类上添加注解:

@EnableDiscoveryClient

控制类

@RestController
@RequestMapping("goods")
public class GoodsController {

    @RequestMapping("findbyid/{id}")
    public Goods findbyId(@PathVariable Integer id){
        System.out.println("id---"+id);
        return new Goods("小米",99.0);
    }

}
服务消费者

启动类,将 RestTemplate 交给Spring管理

@SpringBootApplication
@EnableDiscoveryClient
public class CloudOrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudOrderApplication.class, args);
    }

    @Bean
    public RestTemplate initRestTemplate(){
        return new RestTemplate();
    }
}

控制类:硬编码,测试成功

@RestController
@RequestMapping
public class OrderController {

    @Autowired
    RestTemplate restTemplate;

    @RequestMapping("save")
    public Map save(){
        String url="http://localhost:9001/goods/findbyid/1";
        Goods goods=restTemplate.getForObject(url,Goods.class);
        System.out.println(goods);

        System.out.println("save order success!");

        return new HashMap(){{
            put("code",200);
            put("msg","success");
        }};
    }
}

控制类,修改,测试失败

    @RequestMapping("save")
    public Map save(){
        String appname="cloud-goods";
        String url="http://"+appname+"/goods/findbyid/1";
        Goods goods=restTemplate.getForObject(url,Goods.class);
        System.out.println(goods);
	......

报错:“http://cloud-goods/goods/findbyid/1”: cloud-goods; nested exception is java.net.UnknownHostException: cloud-goods] with root cause

失败原因:没有开启

    @Bean
    @LoadBalanced
    public RestTemplate initRestTemplate(){
        return new RestTemplate();
    }

Nacos领域模型

Nacos 数据模型 Key 由三元组唯一确定, Namespace默认是空串,公共命名空间(public),分组默认是 DEFAULT_GROUP。

nacos的服务由三元组唯一确定 (namespace、group. servicename)

nacos的配置由三元组唯一确定 (namespace、group、dataId)

不同的namespace是相互隔离的,相同namespace但是不同的group也是相互隔离的

默认的namespace是public ,不能删除

默认的group是 DEFAULT-GROUP

image-20211101111754223
# 指定服务发布到指定的 namespace,默认参数是 public,填写的是 命名空间ID
spring.cloud.nacos.discovery.namespace=ceshi
# 指定服务发布到指定的 group,默认参数是 "DEFAULT_GROUP",group会自动创建
spring.cloud.nacos.discovery.group=my-group

不同的 namespace 是相互隔离的,相同 namespace 但是不同的group也是相互隔离的

RestTemplate

实现服务间远程调用

1:RestTemp1ate是java模拟浏览器发送http请求的工具类

2:RestTemp1ate基于Apache的HttpClient实现。Httpclient使用起来太过繁琐。spring提供了一种简单便捷的模板类来进行操作,这就是RestTemplate

Java也可以做爬虫

服务提供者:

    @GetMapping("findbyid/{id}")
    public Goods findbyId(@PathVariable Integer id){
        System.out.println("id---"+id);
        return new Goods("小米",99.0);
    }

    @PostMapping("save")
    public HashMap save(@RequestBody Goods goods){
        System.out.println(goods);
        return new HashMap(){{
            put("code",200);
            put("msg","goods save success");
        }};
    }

ForObject

服务消费者:

Goods goods=restTemplate.getForObject(url,Goods.class);

HashMap hashMap = restTemplate.postForObject(url, goods, HashMap.class);

    //传统 get
    @RequestMapping("gettest01")
    public Map getTest01(){
        String url="http://localhost:9001/goods/findbyid/1";
        Goods goods=restTemplate.getForObject(url,Goods.class);
        System.out.println(goods);
        System.out.println("save order success!");
        return new HashMap(){{
            put("code",200);
            put("msg","success");
        }};
    }

  //传统 post
    @PostMapping("posttest01")
    public String postTest01(){
        String url="http://localhost:9001/save";
        Goods goods=new Goods("华为",98.9);
        HashMap hashMap = restTemplate.postForObject(url, goods, HashMap.class);
        System.out.println(hashMap);
        return "success";
    }

ForEntity

ResponseEntity result = restTemplate.getForEntity(url, Goods.class);

ResponseEntity result = restTemplate.postForEntity(url, goods, HashMap.class);

    //封装后的 get
    @RequestMapping("gettest02")
    public Map getTest02(){
        String url="http://localhost:9001/goods/findbyid/1";
        ResponseEntity<Goods> result = restTemplate.getForEntity(url, Goods.class);
        System.out.println(result.getStatusCode());
        System.out.println(result.getBody());
        return new HashMap(){{
            put("code",200);
            put("msg","success");
        }};
    }

    //封装后的 post
    @PostMapping("posttest02")
    public String postTest02(){
        String url="http://localhost:9001/save";
        Goods goods=new Goods("华为",98.9);
        ResponseEntity<HashMap> result = restTemplate.postForEntity(url, goods, HashMap.class);
        System.out.println(result.getStatusCode());
        System.out.println(result.getBody());
        return "success";
    }

负载均衡:Ribbon

Ribbon工作流程

image-20211101120638214

负载均衡策略

image-20211108202953320

image-20211101141536400

image-20211101143125761

远程调用:OpenFeign

image-20211101144612589

OpenFegin使用案例

服务提供者,编写接口具体实现:

@RestController
@RequestMapping("/provider")
public class ProviderController {

    @GetMapping("/getinfo")
    public String getInfo() {
        return "provider info";
    }

    @GetMapping("/getById/{id}")
    public String getByID(@PathVariable Integer id) {
        String idStr = "id:" + id;
        System.out.println(idStr);
        return idStr;
    }

    @PostMapping
    public Map saveInfo(Object object) {
        System.out.println(object);
        return new HashMap() {{
            put("code", 200);
        }};
    }
}

新建模块,抽离接口:

@FeignClient("cloud-provider") //服务名称
@RequestMapping("/provider")
public interface ProviderClient {

    @GetMapping("/getinfo")
    public String getInfo();

    @GetMapping("/getById/{id}")
    public String getByID(@PathVariable Integer id);

    @PostMapping("/saveinfo")
    public Map saveInfo(Object object);

}

使用:

启动类添加注解:@EnableFeignClients(basePackages = {“com.lut”})

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.lut"})
public class CloudConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudConsumerApplication.class, args);
    }

}
@RestController
@RequestMapping("/consumer")
public class ConsumerController {

    @Autowired
    ProviderClient providerClient;

    @GetMapping("/test")
    public void test(){
        String byID = providerClient.getByID(13);
        String info = providerClient.getInfo();
        providerClient.saveInfo(new Object());
    }
}

OpenFegin常用配置

image-20211101153834978

配置中心:Nacos

传统配置的弊端

image-20211101154632289

配置中心对比

image-20211101154730732

入门使用

image-20211101155911664

注意:dataId的填写

image-20211101155108321

引入依赖:

		<!--nacos-config-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2021.1</version>
        </dependency>

删除 application.properties 文件

创建 bootstrap.properties 文件,服务启动时自动读取

# 从配置中心加载配置文件
spring.cloud.nacos.config.server-addr=localhost:8848
spring.cloud.nacos.config.namespace=ceshi
spring.cloud.nacos.config.group=DEFAULT_GROUP
spring.cloud.nacos.config.username=nacos
spring.cloud.nacos.config.password=nacos

# 拼接文件名 ${prefix}-${spring.profiles.active}.${file-extension}
# 例如我的 dataId 为: cloud-goods-ceshi.properties
spring.cloud.nacos.config.prefix=cloud-goods
spring.profiles.active=ceshi
spring.cloud.nacos.config.file-extension=properties

记录错误:

IllegalArgumentException: Param ‘serviceName’ is illegal, serviceName is blank

出现原因: 没有加载bootstrap.properties 文件

参考:https://blog.csdn.net/qq_27062249/article/details/118462289

解决: 添加依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>3.0.3</version>
        </dependency>

环境切换

在生成环境下创建相似的配置文件

image-20211101162247815

修改配置文件:生成环境修改

image-20211101162600909

重新启动项目

动态刷新

A.自定义动态刷新

配置文件中添加

# 用于测试自动更新的字段
myname=helloworld

Controller中添加

@RestController
@RequestMapping("goods")
@RefreshScope
public class GoodsController {

    @Value("${myname}")
    private String myname;

    @RequestMapping("getmyname")
    public String getmyname() {
        return myname;
    }
    ......

一定要添加 @RefreshScope 后,修改才会生效

修改配置文件,再次访问

B.数据源的动态刷新

image-20211101170527458

共享配置文件

创建共通的配置文件:common1.properties

image-20211101171221658

修改启动的配置文件:

# 加载共通的配置文件,传递一个list
spring.cloud.nacos.config.shared-configs[0]=common1.properties
spring.cloud.nacos.config.shared-configs[1]=common2.properties
# 配置共通的配置文件的动态刷新。传递一个String,用 ,分割
spring.cloud.nacos.config.refreshable-dataids=common1.properties,common2.properties

历史版本

image-20211101171907006

流控/熔断:Sentinel

Sentinel概述

服务雪崩问题

image-20211101184037381

Sentinel是什么

image-20211101184411630

Sentinel与Hystrix对比

Hystrix已经闭源、停止维护

image-20211101184424077

Sentinel架构

image-20211101184831345

Sentinel控制台安装

下载:百度搜 sentinel-dashboard.jar

image-20211101185025906

java -jar sentinel-dashboard-1.8.0.jar --server.port=8880
pause
Sentinel入门使用

添加依赖

              <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
                    <version>2021.1</version>
                </dependency>

添加配置

# 默认端口8719
spring.cloud.sentinel.transport.port=8719
# 客户端的连接地址,默认是8080,根据实际情况选择端口
spring.cloud.sentinel.transport.dashboard=127.0.0.1:8880

# 开启饥饿加载,当项目启动完立即完成初始化,默认是懒加载
spring.cloud.sentinel.eager=true

访问连接:http://localhost:8880/

Sentinel-服务流控

image-20211101192957622
QPS超过阈值直接失败

线程数超过阈值直接失败

QPS超过阈值关联失败

当关联资源(order/getinfo),超过阈值 QPS-1 ,受保护的资源(/order/hello)就会被限流

image-20211101194043790
线程超过阈值关联失败

QPS超过阈值链路失败

image-20211102185355312

开启配置:

image-20211102190441190
流控效果-Warm-up

场景:限流,冷启动

image-20211102185938616 image-20211102185924079
流控效果-匀速排队
image-20211102190044009 image-20211102190337169

Sentinel-熔断降级

官网地址:https://github.com/alibaba/Sentinel/wiki/熔断降级

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方API等。例如,支付的时候,可能需要远程调用银联提供的API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。

慢调用比例
  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。

断路器的工作流程:

—旦熔断,断路器的状态是Open(所有的请求都不能进来)

当熔断时长结束,断路器的状态是half-Open(可以允许一个请求进来)

如果接下来的请求正常,断路器的状态是close (资源就自恢复)

如果接下来的请求不正常,断路器的状态是open

当在 一秒内访问该资源的请求数至少为5,而且其中有 0.6 的最大RT超过了1500,则会触发熔断。

image-20211102191251035

触发条件:

1: 1s内最小请求数必须大于5

2: 慢请求/总请求 >0.6

异常比例
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
image-20211102192110562

触发条件:

1: 一秒内最少请求数>5

2: 每秒的异常数 >10

异常数
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
image-20211102192530803

Sentinel-热点Key规则

官网:https://github.com/alibaba/Sentinel/wiki/热点参数限流

image-20211102193654237

image-20211102193804829

Sentinel-权限规则

过程比较麻烦,没太搞懂

image-20211102195200561

Sentinel-针对来源

image-20211102195316332

需要传递一个请求头的信息

针对来自 cloud-order 服务QPS

系统规则

https://github.com/alibaba/Sentinel/wiki/系统自适应限流

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

系统规则支持以下的模式:

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

Sentinel自定义异常处理

流控自定义异常处理

注意:规则加给 test9,而不是加给 order/test9

image-20211102200225550

image-20211102200610534

熔断降级自定义处理

image-20211102200902432

image-20211102201249407

全局异常处理

对于以上两种异常处理的总结:

@SentinelResourse注解:定义资源名称,定义异常处理

image-20211102202231907

Sentinel规则持久化

声明:因为没有获取到视频资料的源码讲义等信息,在这里只做简单介绍,具体使用请另寻资料

原始模式

image-20211102202415382

PULL模式

将流控规则保存为一个JSON文件,存储在本地,然后可以在本地修改,服务器也可以从本地拉取配置文件

image-20211104110010612

image-20211104110020433

PUSH模式

将流控规则的配置文件保存在nacos服务器端

image-20211104110125997

网关:Gateway

image-20211104110922825

Nginx配置文件,搭建集群的时候,集群的ip地址和端口号写死的Nginx配置文件中,属于硬编码,不方便修改

市面解决方案:Nginx+lua ==> kong

客户端记录Gateway的IP地址,Gateway自己做转发

image-20211104111033095

Gateway核心概念

image-20211104111353301

Gateway工作流程

image-20211104111448470

Gateway请求转发

搭建服务
<!--        Gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>3.0.3</version>
        </dependency>
<!--        端点监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>2.5.2</version>
        </dependency>
<!--        nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2021.1</version>
        </dependency>
server.port=8040

# 服务名称,必须写,保证唯一
spring.application.name=cloud-Gateway
spring.cloud.nacos.discovery.server-addr=localhost:8848
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
@SpringBootApplication
@EnableDiscoveryClient
public class CloudGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudGatewayApplication.class, args);
    }

}
路由跳转
#使用服务发现路由
spring.cloud.gateway.discovery.locator.enabled=true
#服务路由名小写
#spring.cloud.gateway.discovery.locator.lower-case-service-id=true

#设置路由id【随便写,建议写服务名字】
#设置路由的uri【固定,在Nacos中注册的 服务名称】
#设置路由断言,【匹配规则】,代理servicerId为auth-service的/auth/路径

# 配置
spring.cloud.gateway.routes.id[0]=baidu
spring.cloud.gateway.routes.uri[0]=http://www.baidu.com
spring.cloud.gateway.routes.predicates[0]= Path=/**

image-20211104112917698

服务转发

修改配置文件,保证他们在同一个nacos的同一个 开发环境,同一个组里面

spring.cloud.nacos.config.namespace=ceshi
spring.cloud.nacos.config.group=DEFAULT_GROUP

image-20211104114259439

在一个项目中的具体使用:

#使用服务发现路由
spring.cloud.gateway.discovery.locator.enabled=true
#服务路由名小写
#spring.cloud.gateway.discovery.locator.lower-case-service-id=true

#设置路由id【随便写,建议写服务名字】
#设置路由的uri【固定,在Nacos中注册的 服务名称】
#设置路由断言,【匹配规则】,代理servicerId为auth-service的/auth/路径


# 配置service-acl
spring.cloud.gateway.routes[0].id=service-acl
spring.cloud.gateway.routes[0].uri=lb://service-acl
spring.cloud.gateway.routes[0].predicates= Path=/admin/**

#配置service-edu服务
spring.cloud.gateway.routes[1].id=service-edu
spring.cloud.gateway.routes[1].uri=lb://service-edu
spring.cloud.gateway.routes[1].predicates= Path=/eduService/**

#配置service-ucenter服务,微信登录,微信支付
spring.cloud.gateway.routes[2].id=service-ucenter
spring.cloud.gateway.routes[2].uri=lb://service-ucenter
spring.cloud.gateway.routes[2].predicates= Path=/educenter/**

谓词工厂

image-20211104115053858

image-20211104120700915

常用的谓词工厂描述

image-20211104120147261

image-20211104120159482

image-20211104120218272

image-20211104120515476

自定义RoutePredicateFactory

过滤器工厂

image-20211108160403836
内置过滤器

image-20211108160949095

使用内置过滤器
image-20211108161203591
自定义内置过滤器

全局过滤器

image-20211108161628397

自定义全局过滤器

Gateway整合Sentinel

整合使用

image-20211108162058331

自定义异常

跨域问题

image-20211108163113441

链路追踪:Sleuth

Sleuth概述

SpringCloud-Sleuth提供的分布式系统中链路追踪解决方案

同类产品:

Skywalking是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI功能较强,接入端无代码侵入。目前已加入Apache孵化器。

cat 由大众点评开源,基于Java开发的实时应用监控平台,包括实时应用监控,业务监控。集成方案是通过代码埋点的方式来实现监控

Sleuth术语

span:

代表了一组基本的工作单元。为了统计各处理单元的延迟,当请求到达各个服务组件的时候,也通过一个唯一标识(Spanld)来标记它的开始、具体过程和结束。通过Spanld的开始和结束时间戳,就能统计该span的调用时间,除此之外,我们还可以获取如事件的名称。请求信息等元数据。

Trace:

由一组Trace ld相同的Span串联形成一个树状结构。为了实现请求跟踪,当请求到达分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的标识(即Traceld),同时在分布式系统内部流转的时候,框架始终保持传递该唯一值,直到整个请求的返回。那么我们就可以使用该唯一标识将所有的请求串联起来,形成一条完整的请求链路。

image-20211108164256312

ZIPkin可视化工具

java -jar zipkin-server-2.12.9-exec.jar --server.port=9999
pause

image-20211108164336976

集成Sleuth

image-20211108170014319

分布式事务:Seata

image-20211108193100819

分布式事务概述

本地事务的实现

image-20211108193425912

分布式事务问题

image-20211108193647541

Seata概述

Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的分布式事务中间件,以高效并且对业务0侵入的方式,解决微服务场景下面临的分布式事务问题。

中文网:http://seata.io/zh-cn/

Seata进程角色说明

image-20211108201725846

Seata流程说明

image-20211108201828538

配置Seata-Server

下载链接:http://seata.io/zh-cn/blog/download.html

第一步:配置seata-server数据源

image-20211109155810987

第二步:创建seata数据库

第三步:创建3张表

数据库表:https://gitee.com/seata-io/seata/blob/develop/script/server/db/mysql.sql

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

第四步:修改seata的注册中心

image-20211109155831532

第五步:修改seata的配置中心

image-20211109160001676

第六步:Nacos配置中心管理seata配置

image-20211109160232880

https://github.com/seata/seata/edit/develop/script/config-center/config.txt

config.txt

transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=true
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
store.mode=db
store.lock.mode=file
store.session.mode=file
store.publicKey=
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=88888888
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h

第七步:启动seata-server

seata-server.bat -h 127.0.0.1 -p 8898

image-20211109163313146

在项目中使用Seata

@GlobalTransactional

undo_log 表

-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

image-20211109163647307

注意:

data-id 和 配置文件要对应

Seata AT 模式:写隔离

  • 一阶段本地事务提交前,需要确保先拿到 全局锁
  • 拿不到 全局锁 ,不能提交本地事务。
  • 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

以一个示例来说明:

两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁

Write-Isolation: Commit

tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。

Write-Isolation: Rollback

如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

Seata AT 模式:读隔离

在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)

如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

Read Isolation: SELECT FOR UPDATE

SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。

出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值