自己使用SpringCloudAlibaba时,组件搭配如下:注册中心+配置中心+消息总线nacos、断路器sentinel、网关gateway,还有SpringCloud中的两个组件,Web服务客户端openfeign和负载均衡ribbon。文章将介绍SpringCloudAlibaba中的这三个核心组件。

一、服务发现Nacos

nacos是一个动态命名和配置服务,它可以实现服务的注册、发现、配置和管理。nacos相当于集成了Eureka、Config和Bus的功能,可以替代Spring Cloud的这些组件。

(一)产生的业务场景

nacos的诞生是为了满足微服务架构中的配置管理、服务发现和动态负载均衡的需求,搭建起来比Eureka、Config和Bus更快,同时拥有更强大的功能。

(二)快速搭建

快速搭建注册中心

1.下载并启动Nacos软件

启动命令(standalone代表着单机模式运行,非集群模式):

startup.cmd -m standalone

2.登录Nacos图形界面

http://localhost:8848/nacos 默认账号密码nacos

3.客户端pom.xml文件引入依赖

注意版本和springboot统一

        <dependency>
   <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>

4.客户端启动类上添加@EnableDiscoveryClient注解

各个nacos客户端都需要启用注解,还是使用openfeign远程调用

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class StudentConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(StudentConsumerApplication.class,args);
    }
}

5.客户端application.yml文件中配置相关参数

配置nacos服务端的地址,开放端口默认为8848

spring:
  application:
    name: alibaba-student-consumer
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

快速搭建配置中心

6.客户端pom.xml文件引入依赖

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

7.客户端新建配置文件bootstrap.yml

namespace和group的概念在(三)中阐述。

  • Nacos配置中心允许同时在本地也存在application.yml文件(eureka配置中心不允许)
  • bootstap.yml(微服务连接nacos配置),application.yml(配置固定的数据)
  • 经常修改的数据存放到远程,命名格式为:$ {prefix}-$ {spring.profiles.active}.$ {file-extension},如student-dev.yaml

bootstrap.yml文件

spring:
  application:
    name: alibaba-student-provider  #eureka通过名字获取ip地址

  profiles:
    active: dev   #优先使用xxx-dev.yaml文件

  cloud:
    nacos:
      discovery:   #发现中心
        server-addr: 127.0.0.1:8848     #发现中心服务端的ip地址,端口
        cluster-name: ${clustername:bj}   #集群(即在一个机房,区别eureka的集群)
        namespace: c9db8a77-936b-4657-aaf0-248dd7c52558  #命名空间(需要在服务端的命名空间栏创建,这里填生成的id)
        group: TEST  #组(配置文件自定义)
      config:  #配置中心
        file-extension: yaml  #没有yml格式
        namespace: c9db8a77-936b-4657-aaf0-248dd7c52558  #命名空间(遵守环境隔离,同命名空间同组才能够调用服务)

application.yml文件

server:
  port: ${port:8070}  #我觉得这里可以不用写,远程配置

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:///else
    username: root
    password: 123


mybatis:
  type-aliases-package: com.woniu.common.entity
  mapper-locations: classpath:/mapper/*Mapper.xml

logging:
  level:
    com.woniu.student.provider.mapper: debug

8.远程图形界面创建配置文件

  • Data ID 使用 [模块名+开发环境.yaml] 来命名。注意:yaml是固定的,不支持yml后缀命名!
  • 配置文件的namespace命名空间和Group,一般是和自身模块的namespace命名空间和Group配置相同。
  • 编写时注意要在 : 号后面加一个空格,显示蓝色高亮,才算正确的书写格式。

补充:

  • 配置中心也支持环境隔离,但是就不再引入服务分级存储的概念了。
  • 配置了激活dev环境后,远程alibaba-student-provider-dev.yaml>远程alibaba-student-provider.yaml>本地

9.Nacos配置数据持久化

默认配置中心的数据,存储在Nacos中的内置数据库。重启还有,但是Nacos损坏了,数据将会丢失。支持将配置的数据存储到数据库中。数据库版本要一致,需要去官网下载sql语句建表,需要修改配置文件。
image.png

10.Nacos集群

image.png

11.seata实现分布式事务

1)下载seata服务端软件

2)按照官方文档修改seata中的配置文件,换成nacos类型作为注册中心

3)需要各模块注册到SEATA_GROUP组,启动seata服务端,会自动将该模块注册到SEATA_GROUP组,可在配置文件中修改这一默认组。其他关联模块使用seata分布式事务,也必须注册到这个相同组。

4)修改了软件的配置,需要将项目中的两个配置文件替换掉。修改之后,软件重启才能生效。

5)同样,需要加入数据源类,并在启动类上排除数据源。

补充:

-Dserver.port=9090   用于开发环境时,模拟集群

-- server.port=9090   用于软件使用环境时,切换端口

(三)技术要点

1.环境隔离

访问的Nacos的注册中心找服务,或者访问配置中心获取配置文件的值,必须在相同的Namespace和Group中,不能跨Namespace和Group调用服务和读取配置文件。
image.png

1)在nacos服务端界面创建Namespace(命名空间)

使用生成的命名空间的id作为配置文件中namespace的值。

2)Group(分组)直接在配置文件中编写即可

spring:
  application:
    name: alibaba-student-consumer

  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        cluster-name: ${clustername:cd}
        namespace: ecd5a042-4400-4bc4-a444-bd7e607455ba
        group: TEST

2.服务分级存储

image.png
image.png
5051、5052、5053是同一个服务的副本。如果在eureka中,这三个都算作一个集群。但在nacos中,在一个机房的算作一个集群,这三个模块叫做服务。
为什么这样分?是为nacos自己的负载均衡算法做铺垫。

3.Nacos注册中心自带的负载均衡策略

优先调用一个集群的服务,速度会更快,如果这个集群的服务都挂了,才去调用另一个集群的服务。不用重启或更改配置,就可以在nacos界面中操作各个服务所占的权重,灵活性更高。
注意:使用Nacos的负载均衡策略需要手动编写配置,否则默认还是ribbon的轮询策略。

spring		
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        cluster-name: ${clustername:cd}

#ribbon负载均衡算法,默认是轮询
alibaba-student-provider: #调用的服务名
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule  #底层还是Riibon,默认还是轮询

二、断路器sentinel

sentinel是一个分布式系统的流量防卫组件,它可以实现服务的限流、降级、熔断和系统负载保护。sentinel相当于增强了Hystrix的功能,可以提供更丰富的规则和实时监控。

(一)产生的业务场景

sentinel的诞生是为了解决微服务架构中的流量控制、容错和系统稳定性的问题,并且搭建起来比Hytrix更快,功能也更加强大。

(二)快速搭建

1.打开软件,启动sentinel服务端

java -jar xxxx.jar --server.port=9999

2.客户端pom.xml文件引入依赖

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

3.客户端application.yml文件中配置相关参数

spring:
  cloud:
    sentinel:  #断路器
      transport:  #配置后台服务端端口,和前端展示页面端口
        port: 8719
        dashboard: localhost:9999
      web-context-unify: false  #配置service层路径,在各个controller中都能显示

feign:    #开启sentinel熔断降级
  sentinel:
    enabled: true

(三)限流

限流---流控模式

1.直接

直接限制该请求路径,一秒钟只能访问两次。
image.png

2.关联

当/add的请求量超过阈值,对get限流,add正常。照顾优先级高的业务,保证优先执行。
image.png

3.链路

get和add同时调用了其他方法,限制get调用,不限制add调用。保证请求总量一定的情况,优先满足一定的资源,从调用链的下层进行控制。

  • controller层有路径,默认能找到,但service的路径默认找不到。需要在service层的方法上加上@SentinelResource("xxx")注解,才能找到该路径。
  • 配置文件加上以下配置,才不会合并相同路径(默认会将路径合并到第一个controller请求下)
web-context-unify: false  #配置发现service层路径

image.png

image.png

限流---流控效果

4.快速失败

超过流量抛出异常,拒绝处理。

5.Warm UP(预热模式,冷加载,冷启动)

底层有一个冷启动因子默认值是3,可以自己设置预热时长,假如是10s。项目启动时,限流数为:预热时长/冷启动因子,在预热时间的时间内逐步提升单机阈值,最后达到单机阈值。
image.png

6.排队等待

单位时间超过阈值的请求,排队等待,如果在指定的时间内,及时处理,还是正常请求。超过了等待时间还是异常。单位是毫秒。
image.png

(四)Sentinel规则持久化

sentinel配置的规则,默认在内存中,重启服务就没有了。

1.客户端pom.xml文件引入依赖

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>

2.客户端application.yml文件中配置相关参数

spring:
  application:
    name: alibaba-student-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
#        namespace: 28f9d138-1a9d-4545-9386-791bfb974b2e
#        group: TEST_GROUP
#        cluster-name: cd
    sentinel:
      transport:
        port: 8719
        dashboard: localhost:8080
      web-context-unify: false  #Sentinel默认会整合路径,使用链路模式,必须要能够分清请求来源
      datasource:   # Sentinel 规则持久化
        ds1:        #自定义名
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}    #dataId就是nacos配置中心的文件名
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow                       #配置的是限流相关规则

3.将Sentinel规则写到nacos配置中心

注意:这里的Data ID 不用加.yaml后缀。
image.png

配置规则百度搜索。

[
    {
        "resource":"/studentconsumer/get",
        "limitApp":"default",
        "grade":"1",
        "count":"2",
        "strategy":"0",
        "controlBehavior":"0",
        "clusterMode":false
    }
]

(五)熔断降级

1.客户端pom.xml文件引入依赖

与限流依赖相同,引入了此处不需再引入

2.客户端application.yml文件中配置相关参数

配置文件中,开启sentinel断路器

feign:
  sentinel:
    enabled: true

3.其他的部分和hytrix相同

1)接口加上fallback属性
在之前openfeign创建的接口的@FeignClient注解上加fallback属性,指定降级方法所在类。

@FeignClient(name = "student-provider",fallback = StudentProviderFallback.class) //根据名字寻找对应模块中的方法,然后自动生成实体类
//注意这里不能直接在类上加映射,否则会报重复生成类错误
public interface StudentProviderFeignClient {

    @GetMapping("/student/all")
    public Object all();

    @GetMapping("/student/getById/{id}")
    public Object getById(@PathVariable("id") String id);

    @PostMapping("/student/add")
    public Object add(@RequestBody Student student);

}

2)定义降级方法
在调用模块中,新建类实现我们创建的远程调用的接口 ,配置降级方法(一般返回响应结果),返回值、参数都必须与被调用模块中的方法一致。如下实例:

@Component
public class StudentProviderFallback implements StudentProviderFeignClient{
    @Override
    public Object all() {
        return new ResponseResault(200,"all-fallback",null);
    }

    @Override
    public Object getById(@PathVariable("id") String id) {
        return new ResponseResault(200,"getById-fallback",null);
    }

    @Override
    public Object add(@RequestBody Student student) {
        return new ResponseResault(200,"add-fallback",null);
    }
}

4.慢调用比例,异常比例,异常数

  • 慢调用比例:1秒内(统计时间(时间窗口)),2个请求,发生超过1000毫秒的时间调用超过50%,触发熔断,熔断时间5秒
  • 异常比例:和hystrix参数完全相同
  • 异常数:统计异常总数,不按比例统计

image.png

(六)技术要点

1.熔断和限流的区别

限流是对请求进行限制访问,保证系统不会崩溃。熔断是请求已经被处理,但发生了故障或者响应慢,而执行降级方法。

2.分别熔断调用、被调用模块路径的区别?

如果只对调用者的这个路径进行熔断,那么只有当调用者的请求超过阈值或异常比例升高时,才会触发熔断规则,从而快速失败,避免影响到其它的资源。如果只对被调用者的这个路径进行熔断,那么只有当被调用者的响应超时或异常比例升高时,才会触发熔断规则,从而降级返回默认值,避免影响到调用者的正常逻辑。如果对调用者和被调用者的这个路径都进行熔断,那么可以实现更细粒度的控制,根据不同的场景和需求,选择合适的熔断策略和阈值。

三、网关GateWay

GateWay可以作为一个统一的入口,将外部请求分发到内部的不同服务,并提供一些额外的功能,如认证、限流、监控等。

(一)产生的业务场景

相比zuul,Gateway可以提供更高的性能和更低的资源消耗,以及更好的异步支持。另外,Gateway还提供了更灵活的路由规则和过滤器配置,以及更好的集成Spring Boot和Spring Cloud的特性。

(二)快速搭建

注意:gateway不能引入或者引入的模块不能含有spring-boot-starter-web依赖,底层实现是靠netty,引入会发生冲突。

1.创建新的资源模块gateway

2.pom.xml文件引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>

3.application.yml文件中配置相关参数

方式一:通过ip地址


server:
  port: 1011

spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:     #配置路由
        - id: student-provider   #路由id,一般使用微服名
          uri: http://localhost:8081/    #映射路径,转发路径   http://localhost:8081/student/1
          predicates:
            - Path=/student/**     #请求路径    http://localhost:1011/student/1
        - id: student-consumer   #路由id,一般使用微服名
          uri: http://localhost:7071/    #映射路径,转发路径   http://localhost:7071/studentconsumer/1
          predicates:						#断言,就是一个条件判断	路径断言
            - Path=/studentconsumer/**     #请求路径    http://localhost:1011/studentconsumer/1

方式二:网关作为注册中心的客户端,通过服务名获取调用的地址


server:
  port: 1011

spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:     #配置路由
        - id: student-provider   #路由id,一般使用微服名
          uri: lb://student-provider    #映射路径,转发路径   http://localhost:8081/student/1
          predicates:
            - Path=/student/**     #请求路径    http://localhost:1011/student/1
        - id: student-consumer   #路由id,一般使用微服名
          uri: lb://student-consumer    #映射路径,转发路径   http://localhost:7071/studentconsumer/1
          predicates:
            - Path=/studentconsumer/**     #请求路径    http://localhost:1011/studentconsumer/1
eureka:
  client:
    service-url:
      defaultZone: http://localhost:6061/eureka

3.Route Predicate Factories路由断言工厂

其实就是配置文件中的predicates,常用断言如下:
1)The After Route Predicate Factory

路径是否能进入网关。

predicates:
            - Path=/studentconsumer/**

2)The Cookie Route Predicate Factory

是否有cookie。

predicates:
        - Cookie=chocolate, ch.p

3)The Header Route Predicate Factory

predicates:
        - Header=chocolate,ch.p

4.GatewayFilter Factories网关过滤工厂

进入网关以后,有系统内置的过滤器。框架常用的网关过滤器:

  • The AddRequestHeader GatewayFilter Factory
  • The AddResponseHeader GatewayFilter Factory

5.CORS Configuration网关统一跨域处理

spring:
  application:
    name: gateway
  cloud:
    gateway:
      globalcors:   #网关统一跨越配置
        add-to-simple-url-handler-mapping: true
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowedHeaders: "*"

6.网关自定义GlobalFilter过滤器

可定义多个GlobalFilter过滤器,使用order排列优先级(Order值越小,越先执行),常用于认证、鉴权。大致思路:

  • 第一个GlobalFilter过滤器:login请求放行,验证token
  • 第二个GlobalFilter过滤器:login请求放行,验证redis中存储的用户权限

第一个拦截器:

@Component
public class LoginInterceptor implements GlobalFilter, Ordered {

    @Autowired
    RedisTemplate redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //访问/login,放行
        String token = request.getHeaders().getFirst("token");
        String storeToken = (String)redisTemplate.opsForValue().get("1");
        if(storeToken.equals(token)){
            //发行
            return chain.filter(exchange);
        }
        ResponseResault responseResault = new ResponseResault(401,"未登录",null);
        ObjectMapper objectMapper=new ObjectMapper();
        String responsebody;
        try {
            responsebody = objectMapper.writeValueAsString(responseResault);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        //拦截
        DataBuffer data = response.bufferFactory().wrap(responsebody.getBytes());;
        return response.writeWith(Mono.just(data));
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

第二个拦截器类似

登录方法发送token:

    @PostMapping("/login")
    public Object login(@RequestBody Student student, HttpServletResponse response){
        loginService.login(student);
        String token = JwtUtil.createToken(student.getId().toString(), student.getName());
        response.setHeader("token",token);
        redisTemplate.opsForValue().set("1",token);
        //      必须暴露响应头,前端才能拿到token,可编写配置文件
        response.addHeader("Access-Control-Expose-Headers", "token");
        return new ResponseResault(200,"登录成功",null);
    }

补充:

  • 如果过滤器太多,可以使用setHeader(order值,true),getHeader判断是否放行
  • 如果采用restful风格开发,/student可能代表POST、DELETE、PUT、GET任一请求,因此 需要在数据库中权限表应该设计有如下字段:id、请求路径、请求类型。比较时,需要取出request的路径path和method方法,拼接后与数据库进行比较。
  • 如果gateway需要调用其他远程模块,需要遵守restful风格,需要加入类型转换配置。比如调用方法时传入了一个对象作为参数,因为不会像@RequestBody自动转换,所以得配一个。HttpMessageConverter