SpringCloudAlibaba

110 阅读4分钟

Nacos

概念

  • 配置文件通过namespace、group 和 dataId 来唯一确定
  • dataId的格式:${spring.application.name}-${spring.profiles.active}.${file-extension}
    • spring.profiles.active为空时,连接符-也将不存在
    • ${file-extension}:默认为properties
  • 拉取多个共享文件,要在同一命名空间下,不能跨命名空间
  • 注意springboot和springcloud的版本对应

配置中心

  • 引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
    <version>4.1.5</version>
</dependency>
    
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2021.0.1.0</version>
</dependency>
  • 完整bootstrap.properties配置:
spring.application.name=testccb
spring.cloud.nacos.config.username=nacos
spring.cloud.nacos.config.password=nacos
spring.cloud.nacos.config.server-addr=192.168.1.211:8848
spring.cloud.nacos.config.namespace=4f4b-363e-47a5-8a-dbd288c
spring.cloud.nacos.config.group=com.iotsh

# 拉取共享的配置
spring.cloud.nacos.config.extension-configs[0].data-id=testccb.properties
spring.cloud.nacos.config.extension-configs[0].group=ccb
spring.cloud.nacos.config.extension-configs[0].refresh=true

注册中心

  • 引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
    <version>4.1.5</version>
</dependency>

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2021.0.1.0</version>
</dependency>
  • 配置文件
spring.cloud.nacos.discovery.server-addr=192.168.1.211:8848
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
spring.cloud.nacos.discovery.namespace=ed4b94b3-deff-49f1-8973-34029756083d
  • 主启动类:@EnableDiscoveryClient

OpenFeign

  • 引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 主启动类:@EnableFeignClients
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients 
public class EurekaClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(CategoryApplication.class,args);
    }
}
  • 配置文件
feign:
  client:
    config:
      default:
        connect-timeout: 5000
        read-timeout: 5000
  • 创建FeignClient (调用者),注意传参
@FeignClient(value = "product") // 被调用的服务名称
public interface ProductClient {
    
    @RequestMapping("/status") // 被调用的服务接口
    public String status();

    @GetMapping("/paramRequest")
    public String paramRequest(@RequestParam("id") Integer id, @RequestParam("name") String name);

    @PostMapping("/bodyRequest")
    public String bodyRequest(@RequestBody User user);
}
  • Controller中使用
@RestController
public class UserController {
    @Autowired
    private ProductClient productClient;  

    @GetMapping("/find")
    public String find(){
        return productClient.status();
    }
}
  • 被调用的服务
@RestController
public class ProductController {
    // 没有参数
    @RequestMapping("/status")
    public String status(){
        return "client b is ok!";
    }

    // param参数
    @GetMapping("/paramRequest")
    public String paramRequest(@RequestParam("id") Integer id, @RequestParam("name") String name){
        return "paramRequest method is ok!, id:" + id +", name:"+ name;
    }

    // body有参数
    @PostMapping("/bodyRequest")
    public String bodyRequest(@RequestBody User user){
        return "bodyRequest method is ok!, user:" + user.toString();
    }
}

Sentinel

Sentinel提供了两个服务组件:控制台(Dashboard)和核心库,控制台用来监控流量调用情况;核心库用来实现服务熔断降级

Dashboard

  • 下载官方提供的Sentinel Dashboard的jar包:github.com/alibaba/Sen…

  • java -Dserver.port=9191 -jar sentinel-dashboard-1.7.2.jar

  • 访问localhost:9191,用户名密码都是sentinel,微服务被首次调用的时候向Dashboard发送心跳信息

创建项目

  • 引入依赖
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  • 修改配置
server.port=9642
spring.cloud.nacos.server-addr=localhost:8848
spring.cloud.sentinel.transport.dashboard=localhost:9191    # 配置dashboard连接地址
  • 主启动加注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class SentinelApplication {
    public static void main(String[] args) {
        SpringApplication.run(SentinelApplication.class,args);
    }
}

服务降级

  • @SentinelResource注解 只是 “装了个开关和应急灯”,不管是熔断还是降级结果都是“拉闸”,只是拉闸原因不同
  • 熔断是为了防止故障扩散,切断对下游的调用;降级是 主动舍弃非核心功能,把资源留给核心接口
@GetMapping("/user/info")
@SentinelResource(value = "app", fallback = "innerBlock")
public String getUserInfo() throws InterruptedException {
    Thread.sleep(800);
    System.out.println("UserController::/userInfo");
    return "{name:ccb, age: 22}";
}

public String innerBlock(){   // 返回类型、参数与原方法一致
    return "innerBlock...限流";
}

Gateway

作用:路由转发 + 过滤器

使用

  • 引入依赖,consul里面集成了ribbon,网关可以实现转发的负载均衡

  • gateway使用了Flux异步非阻塞模型,不要添加spring-boot-starter-web,会冲突

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
        
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
        
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    
  • 主启动类:@EnableDiscoveryClient

  • 配置路由规则

    spring:
      application:
        name: gateway
    
      cloud:
        consul:
          host: localhost    # 向注册中心注册
          port: 8500
    
        gateway:
          routes:
            - id: route_product
              uri: lb://product            # 2.转发到对应服务,product为服务名字
              predicates: # 断言
                - Path=/product/**,/list   # 1.匹配到对应路径,可以配置多个
    
            - id: route_category
              uri: lb://category
              predicates:
                - Path=/category
    server:
      port: 9500
    

过滤器

客户端请求各个服务时,每个服务都需要做相同的事情,比如鉴权、限流、日志输出等,可以分为GatewayFilter过滤器和GlobalFilter过滤器

  • GatewayFilter:在配置文件中配置,作用在具体路由上
  • GlobalFilter:作用在所有的路由上

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter,请求路由后,会将当前路由过滤器、DefaultFilter、GlobalFilter,合并到一个过滤器链中,排序后依次执行每个过滤器

排序规则:order值越小,优先级越高,路由过滤器和defaultFilter的order由Spring指定,当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行

1.png

GatewayFilter
  • 常见过滤器:

    - PrefixPath=/emp	// 增加前缀,在请求的路径前(端口号后)加前缀
    - StripPrefix=1		// 去掉前缀,数字代表去掉几级前缀,例:/emp/product/ 变为 /product
    
  • StripPrefix 过滤器使用

    spring:
      application:
        name: gateway
    
      cloud:
        consul:
          host: localhost
          port: 8500
    
        gateway:
          routes:
            - id: route_category
              uri: lb://category
              predicates:
                - Path=/xxxxxxxx/**
              filters:
                - StripPrefix=1
    
    访问路径:http://localhost:9500/xxx/category  
    匹配到指定路由后会将前缀去掉一级:/xxx/category  ==> /category
    
GlobalFilter
@Component
public class DemoFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1 获得请求路径
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
 
        // 2 处理异常,拒绝放行
        if("/echo/abc".equals(path)) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
            DataBuffer wrap = response.bufferFactory().wrap("没有权限".getBytes(StandardCharsets.UTF_8));
            return exchange.getResponse().writeWith(Flux.just(wrap));
        }
 
        // 3 放行
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
        }));
    }
 
    // 编写过滤器优先级, 值越小优先级越高
    @Override
    public int getOrder() {
        return 1;
    }
}

查看路由

management:
  endpoints:
    web:
      exposure:
        exclude: "*"

通过访问该地址查看 http://localhost:9500/actuator/gateway/routes,改成网关的端口,效果如下

暴露路由规则.png