Spring Cloud Alibaba微服务组件快速上手

Nacos

什么是Nacos

Nacos是Spring Cloud Alibaba提供的一个软件、这个软件主要具有注册中心和配置中心的功能、微服务中所有项目都必须注册到注册中心才能成为微服务的一部分、注册中心和企业中的人力资源管理部门有相似、微服务项目中所有的模块,在启动前,必须添加注册到Nacos的配置。

所谓注册,就是将自己的信息,提交到Nacos来保存

Nacos的启动

 startup.cmd -m standalone

-m 表示要设置启动参数

standalone:翻译为标准的孤独的,意思是正常的使用单机模式启动

打开浏览器输入地址 http://localhost:8848/nacos

用户名:nacos 密码:nacos

不能关闭启动nacos的dos窗口

将项目注册到Nacos

项目pom依赖
<!--  支持项目注册到Nacos注册中心的依赖 discovery发现(服务的发现)  -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
yaml配置

在创建好的application-dev.yml中编写对nacos注册的配置信息

spring:
  application:
    #  为当前项目起名,这个名字会被Nacos记录并使用
    name: nacos-business
  cloud:
    nacos:
      discovery:
        # 配置Nacos所在的位置,用于注册时提交信息
        server-addr: localhost:8848

重启business模块,如果启动也正常,就应该将当前项目的信息提交给Nacos 在Nacos的服务管理->服务列表中,能看到nacos-business的名称

Nacos心跳机制

目的: 是每个服务和Nacos保持沟通和交换信息的机制

默认情况下,服务启动后每隔5秒会向Nacos发送一个"心跳包",这个心跳包中包含了当前服务的基本信息

Nacos接收到这个心跳包,首先检查当前服务在不在注册列表中,如果不在按新服务的业务进行注册,如果在,表示当前这个服务是健康状态

如果一个服务连续3次心跳(默认15秒)没有和Nacos进行信息的交互,就会将当前服务标记为不健康的状态

如果一个服务连续6次心跳(默认30秒)没有和Nacos进行信息的交互,Nacos会将这个服务从注册列表中剔除

这些时间都是可以通过配置修改的

  • 实际上Nacos的服务类型还有分类
  • 临时实例(默认)持久化实例(永久实例)
cloud:
  nacos:
    discovery:
      # ephemeral设置当前项目启动时注册到nacos的类型 true(默认):临时实例 false:永久实例
      ephemeral: false 

Dubbo

什么是RPC

RPC是Remote Procedure Call的缩写 翻译为:远程过程调用

目标是为了实现两台(多台)计算机\服务器,相互调用方法\通信的解决方案

RPC只是实现远程调用的一套标准

该标准主要规定了两部分内容 1.通信协议 2.序列化协议

通信协议:通信协议指的就是远程调用的通信方式 实际上这个通知的方式可以有多种 例如:写信,飞鸽传书,闪送等等 在程序中,通信方式也有多种 。

序列化协议: 序列化协议指通信内容的格式,双方都要理解这个格式,发送信息是序列化过程,接收信息需要反序列化,程序中,序列化的方式也是多种的。

什么是Dubbo

上面对RPC有基本认识之后,再学习Dubbo就简单了

Dubbo是一套RPC框架。既然是框架,我们可以在框架结构高度,定义Dubbo中使用的通信协议,使用的序列化框架技术,而数据格式由Dubbo定义,我们负责配置之后直接通过客户端调用服务端代码。

可以说Dubbo就是RPC概念的实现

Dubbo是SpringCloudAlibaba提供的框架

能够实现微服务相互调用的功能!

Dubbo服务的注册与发现

在Dubbo的调用过程中,必须包含注册中心的支持

注册中心推荐阿里自己的Nacos,兼容性好,能够发挥最大性能

但是Dubbo也支持其它软件作为注册中心(例如Redis,zookeeper等)

服务发现,即消费端自动发现服务地址列表的能力,是微服务框架需要具备的关键能力,借助于自动化的服务发现,微服务之间可以在无需感知对端部署位置与 IP 地址的情况下实现通信。

consumer服务的消费者,指服务的调用者(使用者)

provider服务的提供者,指服务的拥有者(生产者

在Dubbo中,远程调用依据是服务的提供者在Nacos中注册的服务名称

一个服务名称,可能有多个运行的实例,任何一个空闲的实例都可以提供服务

常见面试题:Dubbo的注册发现流程

1.首先服务的提供者启动服务时,将自己的具备的服务注册到注册中心,其中包括当前提供者的ip地址和端口号等信息,Dubbo会同时注册该项目提供的远程调用的方法

2.消费者(使用者)启动项目,也注册到注册中心,同时从注册中心中获得当前项目具备的所有服务列表

3.当注册中心中有新的服务出现时,会通知已经订阅发现的消费者,消费者会更新所有服务列表

4.RPC调用,消费者需要调用远程方法时,根据注册中心服务列表的信息,只需服务名称,不需要ip地址和端口号等信息,就可以利用Dubbo调用远程方法了

Dubbo实现微服务调用

生产者配置
pom依赖
<!--  Dubbo的依赖  -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
yaml配置
dubbo:
  protocol:
    # port设置-1 表示当前Dubbo端口号是自动动态生成
    # 会自动从20880开始寻找可用的端口号,如果被占用,就递增寻找下一个,直到找到可用为止
    port: -1
    # 设置连接的名称,一般固定设置为dubbo
    name: dubbo
  registry:
    # 声明当前Dubbo注册到的注册中心类型和位置
    address: nacos://localhost:8848
  consumer:
    # 当本项目启动时,是否检查当前项目需要的所有Dubbo服务是否是可用状态
    # 我们设置它的值为false,表示项目启动时不检查,所需的服务是否可用
    check: false
service层配置

当前stock模块是单纯的生产者

// @DubboService注解标记的业务逻辑层实现类,其中的所有方法会注册到Nacos
// 其它服务在"订阅"时,就会"发现"当前项目提供的服务(业务逻辑层方法),以便后续在需要时调用
@DubboService
@Service
@Slf4j
public class StockServiceImpl implements IStockService {
    //内容略....
}
启动类配置

添加@EnableDubbo的注解,才能真正让Dubbo功能生效

@SpringBootApplication
// 如果当前项目是Dubbo的生产者,必须在当前项目的SpringBoot启动类上添加下面注解
// 才能正常正确的将当前项目提供的服务注册到Nacos
@EnableDubbo
public class CsmallStockWebapiApplication {
    public static void main(String[] args) {
        SpringApplication.run(CsmallStockWebapiApplication.class, args);
    }
}
消费者配置
pom依赖
<!--   作为消费者,order模块需要调用cart和stock模块的业务逻辑层接口  -->
<dependency>
    <groupId>cn.tedu</groupId>
    <artifactId>csmall-cart-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>cn.tedu</groupId>
    <artifactId>csmall-stock-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
service配置

自动装配

//自己的方法正常用@Autowired装配
@Autowired
private OrderMapper orderMapper;
// 添加@DubboReference注解,表示当前业务逻辑层代码,要消费其它模块的服务
// 可以编写当前Nacos中注册的其它模块的业务逻辑层接口
// 因为在Nacos中注册的是接口的实现类,可以实现自动装配实现类的效果
// 先添加stock模块的业务对象,有些公司要求dubbo引用的对象使用dubbo开头
@DubboReference
private IStockService dubboStockService;
@DubboReference
private ICartService dubboCartService;

负载均衡

什么是负载均衡

在实际项目中,一个服务基本都是集群模式的,也就是多个功能相同的项目在运行,这样才能承受更高的并发

这时一个请求到这个服务,就需要确定访问哪一个服务器

Dubbo框架内部支持负载均衡算法,能够尽可能的让请求在相对空闲的服务器上运行

在不同的项目中,可能选用不同的负载均衡策略,以达到最好效果

Loadbalance:就是负载均衡的意思

Dubbo内置负载均衡策略算法

Dubbo内置4种负载均衡算法:(使用方法:http://t.csdn.cn/wd08f)

  • random loadbalance:随机分配策略(默认) 【@DubboService(loadbalance = “random”)】【权重@DubboService(weight = 300)】
  • round Robin Loadbalance:权重平均分配 【@DubboService(loadbalance = “randrobin”)】
  • leastactive Loadbalance:活跃度自动感知分配 【@DubboService(loadbalance = “leastactive”)】
  • consistanthash Loadbalance:一致性hash算法分配 【@DubboService(loadbalance = “consistenthash”)】

实际运行过程中,每个服务器性能不同

在负载均衡时,都会有性能权重,这些策略算法都考虑权重问题

Seata

下载Seata

https://github.com/seata/seata/releases

https://github.com/seata/seata/releases/download/v1.4.2/seata-server-1.4.2.zip

什么是Seata

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务

也是Spring Cloud Alibaba提供的组件

Seata官方文档

https://seata.io/zh-cn/

更多信息可以通过官方文档获取

为什么需要Seata

我们之前学习了单体项目中的事务

使用的技术叫Spring声明式事务

能够保证一个业务中所有对数据库的操作要么都成功,要么都失败,来保证数据库的数据完整性

但是在微服务的项目中,业务逻辑层涉及远程调用,当前模块发生异常,无法操作远程服务器回滚

这时要想让远程调用也支持事务功能,就需要使用分布式事务组件Seata

事务的4个特性:ACID特性

  • 原子性
  • 一致性
  • 隔离性
  • 永久性

Seata保证微服务远程调用业务的原子性

Seata将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

使用seata

Seata的启动
D:\tools\seata\seata-server-1.4.2\bin>seata-server.bat -h 127.0.0.1 -m file

输入后,最后出现8091端口的提示即可!

数据库添加表
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
配置Seata
  1. 生产者配置

这里的生产者是指被远程调用要操作数据库的模块

pom依赖

<!--   Seata和SpringBoot整合依赖     -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<!--  Seata 完成分布式事务的两个相关依赖(Seata会自动使用其中的资源)  -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>

yaml配置

seata:
  tx-service-group: csmall_group  # 定义分组名称,为了与其它项目区分
  service:
    vgroup-mapping:
      csmall_group: default       # csmall_group分组使用Seata的默认配置完成事务  
    grouplist: 
      default: localhost:8091     # 配置seata的地址和端口号(8091是默认端口号)

注意: 同一个事务必须在同一个tx-service-group中

同时指定相同的seata地址和端口

  1. 消费者模块

这里指的是事务的起点,触发事务的,但是它不连接数据库,所以配置稍有不同。

pom依赖

<!--   Seata和SpringBoot整合依赖     -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
</dependency>

yaml配置一样

使用Seata非常简单,只要在起点业务的业务逻辑方法上添加专用的注解即可

添加这个注解的模块就是模型中的TM:@GlobalTransactional

例如在业务实现类中:

@Service
@Slf4j
public class BusinessServiceImpl implements IBusinessService {
    @DubboReference
    private IOrderService dubboOrderService;
    
    @GlobalTransactional
    @Override
    public void buy() {
        // 模拟购买业务
        //  代码略...
    }
}

Sentinel

官网地址

https://sentinelguard.io/zh-cn/

下载地址

https://github.com/alibaba/Sentinel/releases

什么是Sentinel

Sentinel也是Spring Cloud Alibaba的组件

Sentinel英文翻译"哨兵\门卫"

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

为什么需要Sentinel

为了保证服务器运行的稳定性,在请求数到达设计最高值时,将过剩的请求限流,保证在设计的请求数内的请求能够稳定完成处理

  • 丰富的应用场景

    双11,秒杀,12306抢火车票

  • 完备的实时状态监控

    可以支持显示当前项目各个服务的运行和压力状态,分析出每台服务器处理的秒级别的数据

  • 广泛的开源生态

    很多技术可以和Sentinel进行整合,SpringCloud,Dubbo,而且依赖少配置简单

  • 完善的SPI扩展

    Sentinel支持程序设置各种自定义的规则

基本配置

我们的限流针对的是控制器方法

pom依赖

<!--    Sentinel 依赖    -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

yaml配置

cloud:
  sentinel:
    transport:
      dashboard: localhost:8080 # 配置sentinel仪表台的位置
      # 执行限流的端口号,每个项目唯一(别的项目例如cart模块,再设置的话就不能用8721了)
      port: 8721
  nacos:
    discovery:
      # 配置Nacos所在的位置,用于注册时提交信息
      server-addr: localhost:8848

Sentinel启动

使用下面命令执行jar包

java -jar sentinel-dashboard-1.8.2.jar

启动之后

打开浏览器http://localhost:8080/

刚开始什么都没有,是空界面

后面我们有控制器的配置并触发后就会出现信息了

限流方法(项目应用)

@PostMapping("/reduce/count")
@ApiOperation("减少商品库存数")
// @SentinelResource注解标记的控制层方法,会在运行时被Sentinel进行管理
// 在这个控制层方法第一次运行后,可以在Sentinel仪表台界面中设置限流规则
// "减少库存的方法"设置了当前方法在仪表台显示的名称
@SentinelResource("减少库存的方法")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
    // 调用业务逻辑层
    stockService.reduceCommodityCount(stockReduceCountDTO);
    return JsonResult.ok("库存减少已执行!");
}

如果不运行knife4j等进行测试,sentinel的仪表盘不会有任何信息

在第一次运行了减少库存方法之后,sentinel的仪表盘才会出现nacos-stock的信息

选中这个信息点击"簇点链路"

找到我们编写的"减少库存的方法"点 “+流控”

设置流控规则

自定义限流&降级方法

  1. 限流

对与被限流的请求,我们可以自定义限流的处理方法

默认情况下可能不能正确给用户提示,一般情况下,对被限流的请求也要有"服务器忙请重试"或类似的提示

Controller类中@SentinelResource注解中,可以自定义处理限流情况的方法

  1. 降级

所谓降级就是正常运行控制器方法的过程中

控制器方法发生了异常,Sentinel支持我们运行别的方法来处理异常,或运行别的业务流程处理

我们也学习过处理控制器异常的统一异常处理类,和我们的降级处理有类似的地方

但是Sentinel降级方法优先级高,而且针对单一控制器方法编写

Controller类中@SentinelResource注解中,可以定义处理降级情况的方法

@PostMapping("/reduce/count")
@ApiOperation("减少商品库存数")
// @SentinelResource注解标记的控制层方法,会在运行时被Sentinel进行管理
// 在这个控制层方法第一次运行后,可以在Sentinel仪表台界面中设置限流规则
// "减少库存的方法"设置了当前方法在仪表台显示的名称
// blockHandler是指定限流时运行方法的配置
@SentinelResource(value = "减少库存的方法",blockHandler = "blockError",fallback = "fallbackError")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
    
     // 测试Sentinel降级
    if(Math.random()<0.5){
        // 随机抛出异常,抛出的异常会被降级方法处理
        throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"随机异常");
    }
    
    // 调用业务逻辑层
    stockService.reduceCommodityCount(stockReduceCountDTO);
    return JsonResult.ok("库存减少已执行!");
}

// Sentinel 自定义限流方法规则
// 1.访问修饰符必须是public
// 2.返回值类型必须和控制器方法一致
// 3.方法名称必须匹配控制器方法blockHandler配置的名称
// 4.参数列表,前面必须和控制器方法一致,后面添加BlockException类型的参数,表示限流方法
public JsonResult blockError(StockReduceCountDTO stockReduceCountDTO,
                             BlockException e){
    // 进这个方法就是被限流的请求,直接返回限流信息即可
    return JsonResult.failed(ResponseCode.INTERNAL_SERVER_ERROR,
                                "服务器忙,请稍后再试");
}

// 降级方法:上面@SentinelResource中fallback指定的降级方法
// 声明格式:基本和限流方法相同,方法参数不需要添加异常类型
// 当控制器方法运行发送异常时,Sentinel会自动调用这个方法
// 实际业务中,可以是新版的业务发生异常,然后转而运行老版代码的机制
public JsonResult fallbackError(StockReduceCountDTO stockReduceCountDTO){
    // 因为没有老版本代码可用,所以也是返回错误信息
    return JsonResult.failed(ResponseCode.INTERNAL_SERVER_ERROR,
                "运行发生异常,服务降级!");
}

SpringGateway

我们使用Spring Gateway作为当前项目的网关框架

Spring Gateway是Spring自己编写的,也是SpringCloud中的组件

SpringGateway官网

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/

网关项目git地址

https://gitee.com/jtzhanghl/gateway-demo.git

简单网关演示

SpringGateway网关是一个依赖,不是一个软件

所以我们要使用它的话,必须先创建一个SpringBoot项目

这个项目也要注册到Nacos注册中心,因为网关项目也是微服务项目的一个组成部分

beijing和shanghai是编写好的两个项目

gateway项目就是网关项目,需要添加相关配置

基本配置

pom依赖

<dependencies>
    <!--   Gateway依赖   -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!--   Nacos依赖   -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--   网关负载均衡依赖    -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
</dependencies>

yml配置

server:
  port: 9000
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        # 网关也是微服务项目的一部分,所以也要注册到Nacos
        server-addr: localhost:8848
    gateway:
      # routes是一个数组,数组中的数据使用"-"开头表示数据中的一个对象
      routes:   # 开始编写Gateway路由配置
      	  # 当前路由的名称,和任何其他名称没有关联,只是不能和后面再出现的路由名称重复
        - id: gateway-shanghai
          # 当匹配当前路由设置时,访问指定的服务器名称(Nacos注册的服务器名称)
          # lb是LoadBalance的缩写,是负载均衡的调用
          uri: lb://shanghai
          predicates:
            # 断言中我们编写当路径满足指定条件时
            # 当请求路径以/sh/开头时,就会路由到上面设置好的shanghai服务器运行
            # ↓ P大写!!!!!!!!
            - Path=/sh/**
          # 当前路由的名称,和任何其他名称没有关联,只是不能和后面再出现的路由名称重复
        - id: gateway-beijing
          # 当匹配当前路由设置时,访问指定的服务器名称(Nacos注册的服务器名称)
          # lb是LoadBalance的缩写,是负载均衡的调用
          uri: lb://beijing
          # 编写断言配置,断言的意思就是满足指定条件时运行某些事情
          # predicates:断言
          predicates:
            # 断言中我们编写当路径满足指定条件时
            # 当请求路径以/bj/开头时,就会路由到上面设置好的beijing服务器运行
            # ↓ P大写!!!!!!!!
            - Path=/bj/**

http://localhost:9000/bj/show可以访问beijing服务器的资源

http://localhost:9000/sh/show可以访问shanghai服务器的资源

以此类推,再有很多服务器时,我们都可以仅使用9000端口号来将请求路由到正确的服务器

动态路由

网关项目随着微服务数量的增多

gateway项目的yml文件配置会越来越多,维护的工作量也会越来越大

所以我们希望gateway能够设计一套默认情况下自动路由到每个模块的路由规则

这样的话,不管当前项目有多少个路由目标,都不需要维护yml文件了

这就是我们SpringGateway的动态路由功能

配置文件中开启即可

server:
  port: 9000
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        # 网关也是微服务项目的一部分,所以也要注册到Nacos
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          # 这是开启动态路由的配置,动态路由设置默认是不开启的 也就是enabled: false
          # 路由规则是在网关端口号后,先写路由目标注册到nacos的名称,再编写具体路径
          # localhost:9000/beijing/bj/show
          enabled: true

路由规则是在9000端口号后面先编写路由目标项目注册到nacos的名称,再编写具体路径

内置断言

断言的意思就是判断某个条件是否满足

我们之前使用了Path断言,判断请求的路径是不是满足条件,例如是不是/sh/** /bj/**

如果路径满足这个条件,就路由到指定的服务器

但是Path实际上只是SpringGateway提供的多种内置断言中的一种

还有很多其它断言

  • after
  • before
  • between
  • cookie
  • header
  • host
  • method
  • path
  • query
  • remoteaddr

时间相关

after,before,between

这里不做详细阐述

内置过滤器

Gateway还提供的内置过滤器

不要和filter混淆

内置过滤器允许我们在路由请求到目标资源的同时,对这个请求进行一些加工或处理

常见过滤器也有一些

演示一下AddRequestParameter过滤器

它的作用是在请求中添加参数

routes:   # 开始编写Gateway路由配置
  - id: gateway-shanghai
    uri: lb://shanghai
    filters:
      # 在请求到控制器前,添加参数age=18
      # 控制器中可以获得这个age参数的值
      - AddRequestParameter=age,18
    predicates:
      - Path=/sh/**
      # 当前请求必须包含名为name的参数,才能正常路由
      - Query=name

在shanghai的控制器方法中添加代码接收name,age的值

@RestController
@RequestMapping("/sh")
public class ShanghaiController {

    @GetMapping("/show")
    public String show(String name,Integer age){
        System.out.println(ZonedDateTime.now());
        return "这里是上海!name:"+name+",age:"+age;
    }


}

重启shanghai和gateway进行测试

http://localhost:9000/sh/show?name=tom

因为过滤器的存在,控制器可以获取网关过滤器添加的参数值

===> 这里是上海!name:tom,age:18

其他内置过滤器和自定义过滤器的使用,可以查阅相关文档自己了解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值