侧边栏壁纸
博主头像
兰若春夏 博主等级

一日为坤,终生为坤

  • 累计撰写 19 篇文章
  • 累计创建 12 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

微服务(更新中)

奥德坤
2022-07-11 / 1 评论 / 0 点赞 / 3490 阅读 / 0 字

微服务

微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:

  • 单一职责:微服务拆分粒度更小,每个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
  • 面向服务:微服务对外暴露业务接口
  • 自治:团队独立、技术独立、数据独立、部署独立
  • 隔离性强:服务调用做好隔离、容错、降级,避免出行级联问题

微服务结构

image-20220711211034382

image-20220711211125242

image-20220711211209814

SpringCloud

  • SpringCloud是目前国内使用最广的微服务框架。https://spring.io/projects/spring-cloud
  • SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了组件的自动装配,从而提供了良好的开箱即用

image-20220711211633470

  • ​ SpringCloud与SpringBoot的版本兼容关系:

    image-20220711211759679

服务拆分

  • 不同微服务,不要重复开发相同业务
  • 微服务数据独立,不要访问其他微服务的数据库
  • 微服务可以将自己的业务暴露为接口,供其他微服务调用

微服务远程调用

基于RestTemplate发起的http请求实现远程调用

提供者与消费者

  • 服务提供者:一次业务中,被其他微服务调用的服务。
  • 服务消费者:一次业务中,调用其他微服务的服务。

服务调用出现的问题

  • 服务消费者如何获取服务提供者的地址信息
  • 如果多个服务提供者,消费者如何选择
  • 消费者如何确定提供者的健康状态

Eureka的作用

  • 注册服务信息,提供服务信息

    • 服务提供者启动时向eureka注册自己的信息
    • eureka保存注册信息
    • 消费者根据服务名称向eureka拉取提供者信息
  • 负载均衡

    • 服务消费者利用负载均衡算法,从服务列表中挑选一个
    • 服务提供者会每隔30秒向EurekaServer发送心跳请求
    • eureka会更新记录服务列表信息,心跳失败的会被踢出
  • 远程调用

    • 基于服务列表做负载均衡,选中一个服务发起远程调用

搭建EurekaServer

  1. 创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖

    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    
  2. 编写启动类,添加**@EnableEurekaServer**注解

  3. 添加application.yml,添加如下配置

    server:
      port: 18761
    spring:
      application:
        name: eurekaserver
    
    eureka:
      instance:
        hostname: 127.0.0.1
      client:
        service-url:
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
        fetch-registry: false #关闭检索服务
    

    image-20220712203507151

服务注册

  1. 将需要注册的项目引入spring-cloud-starter-netflix-eureka-client的依赖

    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  2. application.yml文件中,编写下面配置

    spring:
      application:
        name: userservice
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:18761/eureka/
    

image-20220712204345981

服务发现

  • 服务拉取

    • 服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡

    • 修改访问的url路径,用服务名替代ip、端口

    • 在启动类中RestTemplate添加负载均衡注解

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

Ribbon负载均衡

负载均衡流程

image-20220714201945758

负载均衡策略

Ribbon的负载均衡规则是一个叫做IRule的接口来定义的,每一个子接口都是一种规则:

image-20220714202034712

image-20220714202132522

通过定义IRule实现可以修改负载均衡规则,有两种方式:

  1. 代码方式,定义一个新的IRule:

    @Bean
    public IRule roundRobinRule() {
        return new RoundRobinRule();
    }
    
    
  2. 配置文件,在application.yml文件中,添加新的配置

    userservice:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡策略
       
    
饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长,而饥饿加载则会在项目启动时创建,降低第一次访问的耗时。

Tips

新的springcloud已经移除了ribbon依赖,使用了loadbalancer作为负载均衡。在新版 LoadBalancer 中的算法实现类只有两种:RoundRobinLoadBalancer , RandomLoadBalancer

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

public class CustomLoadBalancerConfiguration {
    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                                            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

        return new RandomLoadBalancer(loadBalancerClientFactory
                .getLazyProvider(name, ServiceInstanceListSupplier.class),
                name);
    }
}

Feign

feign是一个声明式的http客户端,其作用是帮助我们优雅的实现http请求的发送 https://github.com/OpenFeign/feign

定义和使用Feign客户端

引入依赖

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

在启动类添加注解开启Feign的功能@EnableFeignClients

import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("userservice")
public interface UserClient {

    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
    
}
自定义Feign的配置

Feign运行自定义配置来覆盖默认配置,

image-20220716095506202

声明Bean的方式

public class FeignClientConfiguration {

    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.FULL;
    }
}

全局配置可以在启动类上面加@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)

局部配置则在FeignClient里面声明@FeignClient(name = "userservice",configuration = FeignClientConfiguration.class)

Feign性能优化

Feign底层的客户端实现:

  • URLConnection:默认实现,不支持连接池
  • Apache HttpClient:支持连接池
  • OKHttp:支持连接池

因此优化Feign的性能主要包括:

  • 使用连接池替代

    feign:
    	httpclient:
          enabled: true
          max-connections: 200 #最大连接数
          max-connections-per-route: 50 #每个路径的最大连接数
    
  • 日志级别

    使用BASIC或者NONE

Feign的最佳实践
  • 给消费者的FeignClient和提供者的controller定义统一的父接口作为标准

    image-20220716101147875

  • 将FeignClient作为独立模块,并且把接口有关的POJO、默认的Feign配置都放这个模块,提供给消费者使用

    image-20220716101334004

  • 当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用

    1. 指定FeignClient所在包@EnableFeignClients(basePackages = "cn.xrw84.feign")
    2. 指定FeignClient字节码@EnableFeignClients(clients = UserClient.class)

统一网关Gateway

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

为什么需要网关
  • 身份认证和权限校验
  • 服务路由、负载均衡
  • 请求限流
网关的技术实现

在springcloud中网关的实现包括两种:gateway、zuul。

Zuul是基于Servlet的实现,属于阻塞式编程,而springcloudgateway则是基于webflux,属于响应式变成,具有更好的性能。

搭建网关服务
  1. 创建新的module,引入SpringCloudGateway的依赖和eureka的服务发现依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
    
  2. 编写路由配置

    server:
      port: 10010
    spring:
      application:
        name: gateway
      cloud:
        gateway:
          routes:
            - id: user-service
              uri: lb://userservice
              predicates:
                - Path=/user/**
            - id: order-service
              uri: lb://orderservice
              predicates:
                - Path=/order/**
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:18761/eureka/
    

    image-20220716163108960

路由断言工厂Route Predicate Factory

网关路由可以配置的内容:

  • 路由id:路由唯一标志
  • uri:路由目的地,支持lb和http两种
  • predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
  • filters:路由过滤器,处理请求货响应

image-20220716163539713

路由过滤器GatewayFilter

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理

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

image-20220716164129943

  • 过滤器的作用
    • 对路由的请求或响应做加工处理,比如添加请求头
    • 配置在路由下的过滤器只对当前路由的请求生效
  • defaultFilters的作用
    • 对所有路由都生效
全局过滤器GlobalFilter

全局过滤器的作用是处理一切进入网关的请求和微服务响应,与GatewayFilter作用一样。GlobalFilter需要自己代码实现。

@Order(-1)
@Component
public class CustomerFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> params = request.getQueryParams();

        String authorization = params.getFirst("authorization");
        if (StringUtils.equals("admin",authorization)){
           return chain.filter(exchange);
        }
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }
}
过滤器执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter

请求路由后,网关会将当前路由过滤器和DefaultFilter、GlobalFilter合并到一个过滤器链中,排序后依次执行

image-20220716181829475

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
  • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值
  • 路由过滤器和defaultFilter的order由spring指定,默认按声明顺序从1递增
  • 当过滤器的order值一样时,会按照 defaultFilter>路由过滤器>GlobalFilter的顺序执行
跨域问题

浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截

spring:
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true #解决options请求拦截问题
        cors-configurations:
          '[/**]':
            allowedOrigins: 
              - "https://docs.spring.io"
              - "http://localhost:8088"
            allowedMethods:
              - "GET"
              - "POST"
            allowedHeaders: "*"
            allowCredentials: true
            maxAge: 360000
0

评论区