您的位置 首页 java

「干货」从零开始的微服务搭建之路

随着公司的业务发展,有幸经历了从单体应用迁移到分布式应用,又从分布式应用开始准备搭建 微服务 应用,以下是公司从零开始搭建微服务的过程,记录并分享出来,希望对大家有所帮助,我们先使用Spring Cloud GateWay作为网关,由于目前还没有服务发现组件,例如eurka,所以需要通过配置文件的方式配置Ribbon作负载均衡。所以以下重点讲解 Spring Cloud GateWay Ribbon 的搭配使用。

网关的由来

微服务提出后,单体应用被拆分成多个服务,为了对外提供统一入口,解耦客户端与内部服务。

「干货」从零开始的微服务搭建之路

单体架构到微服务架构演变

网关的作用

网关能做统一的路由转发、熔断、限流、安全认证、日志监控等。

「干货」从零开始的微服务搭建之路

网关的作用

网关zuul与Spring Cloud Gateway对比

「干货」从零开始的微服务搭建之路

zuul与Spring Cloud Gateway对比

Spring Cloud Gateway核心概念

「干货」从零开始的微服务搭建之路

网关核心概念

1. 路由(route) 路由是网关最基础的部分,路由信息由一个ID、一个目的URL、一组断言工厂和一组 filter 组成。如果断言为真,则说明请求URL和配置的路由匹配。

2. 断言(predicates) Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自Http Request中的任何信息,比如请求头和参数等。

3. 过滤器(filter) 一个标准的Spring webFilter,Spring Cloud Gateway中的Filter分为两种类型,分别是Gateway Filter和Global Filter。过滤器Filter可以对请求和相应进行处理。

Spring Cloud Gateway工作原理

「干货」从零开始的微服务搭建之路

网关工作原理

Spring Cloud Gateway核心处理流程如上图所示,Gateway的客户端向Spring Cloud Gateway发送请求,请求首先被HttpWebHandlerAdapter进行提取组装成网关上下文,然后网关的上下文会传递到DispatcherHandler。DispatcherHandler是所有请求的分发处理器,DispatcherHandler主要负责分发请求对应的处理器。比如请求分发到对应的RoutePredicateHandlerMapping(路由断言处理映射器)。路由断言处理映射器主要作用用于路由查找,以及找到路由后返回对应的FilterWebHandler。FilterWebHandler主要负责组装Filter链并调用Filter执行一系列的Filter处理,然后再把请求转到后端对应的代理服务处理,处理完毕之后将Response返回到Gateway客户端。

路由断言Factories整理

  • After 路由断言 Factory :在该日期时间之后发生的请求都将被匹配。
  • Before 路由断言 Factory:在该日期时间之前发生的请求都将被匹配。
  • Between 路由断言 Factory:在datetime1和datetime2之间的请求将被匹配。
  • Cookie 路由断言 Factory:Cookie 路由断言 Factory有两个参数,cookie名称和正则表达式。请求包含以cookie名称且正则表达式为真的将会被匹配。
  • Header 路由断言 Factory:Header 路由断言 Factory有两个参数,header名称和正则表达式。请求包含以header名称且正则表达式为真的将会被匹配。
  • Host 路由断言 Factory:Host 路由断言 Factory包括一个参数:host name列表。使用Ant路径匹配规则,.作为分隔符。
  • Method 路由断言 Factory:Method 路由断言 Factory只包含一个参数: 需要匹配的HTTP请求方式。
  • Path 路由断言 Factory:Path 路由断言 Factory 有2个参数: 一个Spring PathMatcher表达式列表和可选。
  • Query 路由断言 Factory:Query 路由断言 Factory 有2个参数: 必选项 param 和可选项 regexp。
  • RemoteAddr 路由断言 Factory:RemoteAddr 路由断言 Factory的参数为 一个CIDR符号(IPv4或IPv6)字符串的列表,最小值为1,例如192.168.0.1/16(其中192.168.0.1是IP地址并且16是子网掩码)。

GatewayFilter Factories整理

  • AddRequestHeader GatewayFilter Factory:对于所有匹配的请求,这将向下游请求的头中添加header。
  • AddRequestParameter GatewayFilter Factory:对于所有匹配的请求,这将向下游请求添加查询字符串。
  • AddResponseHeader GatewayFilter Factory:对于所有匹配的请求,这会将头添加到下游响应的header中。
  • Hystrix GatewayFilter Factory:Hystrix 是Netflix开源的断路器组件。Hystrix GatewayFilter允许你向网关路由引入断路器,保护你的服务不受级联故障的影响。
  • FallbackHeaders GatewayFilter Factory:FallbackHeaders允许在转发到外部应用程序中的FallbackUri的请求的header中添加Hystrix异常详细信息。
  • PrefixPath GatewayFilter Factory:这将给所有匹配请求的路径加前缀。
  • PreserveHostHeader GatewayFilter Factory:该filter没有参数。设置了该Filter后,GatewayFilter将不使用由HTTP客户端确定的host header ,而是发送原始host header 。
  • RequestRateLimiter GatewayFilter Factory:RequestRateLimiter使用RateLimiter实现是否允许继续执行当前请求。如果不允许继续执行,则返回HTTP 429 – Too Many Requests (默认情况下)。
  • Redis RateLimiter:令牌桶的填充速率。
  • RedirectTo GatewayFilter Factory:该过滤器有一个 status 和一个 url参数。status是300类重定向HTTP代码,如301。该URL应为有效的URL,这将是 Location header的值。
  • RemoveRequestHeader GatewayFilter Factory:有一个name参数. 这是要删除的header的名称。
  • RemoveResponseHeader GatewayFilter Factory:有一个name参数. 这是要删除的header的名称。
  • RewritePath GatewayFilter Factory:包含一个 regexp正则表达式参数和一个 replacement 参数. 通过使用 Java 正则表达式灵活地重写请求路径。
  • RewriteResponseHeader GatewayFilter Factory:包含 name, regexp和 replacement 参数.。通过使用Java正则表达式灵活地重写响应头的值。
  • SetPath GatewayFilter Factory:它提供了一种通过允许路径的模板化segments来操作请求路径的简单方法。使用Spring Framework中的URI模板,允许多个匹配segments。
  • SetStatus GatewayFilter Factory:SetStatus GatewayFilter Factory 包括唯一的 status参数.必须是一个可用的Spring HttpStatus。
  • StripPrefix GatewayFilter Factory:parts参数指示在将请求发送到下游之前,要从请求中去除的路径中的节数。
  • Retry GatewayFilter Factory:Retry GatewayFilter Factory包括 retries, statuses, methods和 series 参数。
  • RequestSize GatewayFilter Factory:当请求大小大于允许的限制时,RequestSize GatewayFilter Factory可以限制请求不到达下游服务。过滤器以RequestSize作为参数,这是定义请求的允许大小限制(以字节为单位)。

Ribbon的LoadBalancer的主要组件

「干货」从零开始的微服务搭建之路

Ribboon主要组件

  • I ping :客户端用于快速检查服务器当时是否处于活动状态(心跳检测)
  • IRule:负载均衡策略,用于确定从服务器列表返回哪个服务器
  • ServerList:可以响应客户端的特定服务的服务器列表
  • ServerListFilter:可以动态获得的具有所需特征的候选服务器列表的过滤器
  • ServerListUpdater:用于执行动态服务器列表更新
  • IRule

  • RoundRobinRule:系统默认的规则,通过简单轮询服务列表来选择服务器。
  • AvailabilityFilteringRule:该规则会忽略一下服务器。无法连接的服务器。默认情况下,3次连接失败,服务器会被置为短路的状态,状态持续为30秒;再次连接失败,短路的状态持续时间将会以几何数增加。可以通过修改connectionFailureCountThreshold属性,配置连接失败的次数。并发数过高的服务器。可以修改ActiveConnectionsLimit属性来设置最高并发数。
  • WeightedResponseTimeRule: 为每个服务器赋予一个权重值,服务器的响应时间越长,权重就越小,随机选择服务器,权重值有可能会决定服务器的选择。
  • ZoneAvoidanceRule: 该规则以区域、可用服务器为基础进行服务器选择。使用Zone对服务器进行分类。
  • BestAvailableRule: 忽略短路的服务器,并选择并发数较低的服务器。
  • RandomeRule: 随机选择可用的服务器。
  • RetryRule: 含有重试的选择逻辑。
  • IPing

    检查实例是否存活。如何ping。实现类:

  • NoOpPing: 不进行Ping。
  • DummyPing:默认实现,标记存活的服务器。
  • NIWSDiscoveryPing: 假设服务器存活。
  • PingUrl: 一种健康检查的ping。
  • ServerList

    获取服务器列表。

  • DiscoveryEnabledNIWSServerList: 从Eureka 客户端获取服务器列表。
  • DomainExtractingServerList: 基于domain获取服务列表。
  • ConfigurationBasedServerList: 从配置中获取服务器列表
  • ServerListFilter

    在获取的服务器列表中进行获取。

  • ZoneAffinityServerListFilter: 根据区域亲缘关系过滤服务器。在使用这个过滤器时,需要开启CommonClientConfig#EnableZoneAffinity或者
    CommonClientConfigKey#EnableZoneExclusivity=true。开启后,同一个区域之外的服务器将被过滤。默认情况下,区域亲和力和排他性是关闭的,并且
    不会过滤任何内容。
  • ZonePreferenceServerListFilter: 主动首选本地区域的过滤器。
  • ServerListSubSetFilter: 服务器列表过滤器,将负载均衡器使用的服务器数量限制为所有服务器的子集。
  • ServerListUpdater

    更新服务器列表。

  • EurekaNotificationServerListUpdater: 利用Eureka的时间监听器触发LB缓存更新。
  • PollingServerListUpdater: 默认的策略动态更新服务器列表。
  • IClientConfig

    • IClientConfig的实现类为DefaultClientConfigImpl。DefaultClientConfigImpl是默认的客户端配置,可以从Archaius ConfigurationManager加载属性。

    ILoadBalancer

    LoadBalancer的组成:

    1. 一个基于特定条件可能进行存储的服务器列表。
    2. 一个类:通过IRule实现并定义LoadBalancing策略。
    3. 该类定义并实现一种机制,用户确定列表中节点/服务器的实用性/可用性。

    LoadBalancer的实现类:

  • BaseLoadBalancer: 基本的实现,ping确定存活的服务器列表。
  • DynamicServerListLoadBalancer: 动态获取的服务器列表。
  • ZoneAwareLoadBalancer: LoadBalancer将计算并检查所有可用区域的区域统计信息。如果任何区域的“平均活动请求数”已达到配置的阈值,
    则该区域将从活动服务器列表中删除。如果多个区域已达到阈值,则将删除每台服务器上最活跃请求的区域。一旦删除了最坏的区域,
    将在其余区域中选择一个区域,其概率与其实例数成正比。服务器将从具有指定规则的选定区域返回。每个区域相关的负载平衡决策都是在最新统计信息的帮助下实时做出的。
  • 代码实践

    pom .xml增加SpringCloud Gateway和Ribbon依赖

     <parent>
        <groupId> org .springframework. boot </groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> 
    </parent>
    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</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-gateway</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            </dependency>
    </dependencies>  

    代码方式配置网关

     @SpringBootApplication
    @RestController
    public class DemoGatewayApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoGatewayApplication.class, args);
        }
    
       @Bean
       public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
            return builder.routes()
                .route(r -> r
                    .path("/refund/**")
                    .filters(f -> f.addRequestHeader("Hello", "World"))
                    .uri("#34;)
                ).build();
        }
    
    }  

    yml方式配置网关

     spring:
      application:
        name: gateway-service
      cloud:
        gateway:
          routes:
          - id: merchant
            uri: lb://merchant-load-balanced-service
            predicates:
            - Path=/merchant/**
            - Method=POST
          - id: split
            uri: lb://split-load-balanced-service
            predicates:
            - Path=/split/**
            filters:
            - RewritePath=/split, /ledger-split #重写url
          - id: cashier
            uri: lb://cashier-load-balanced-service
            predicates:
            - Path=/cashier/**
            filters:
            - StripPrefix=1 #将cashier过滤掉
            
    #ribbon全局配置
    ribbon:
      ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
      ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
      OkToRetryOnAllOperations: true #对超时请求启用重试机制
      MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
      MaxAutoRetries: 1 # 切换实例后重试最大次数
      NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #负载均衡算法
      NFLoadBalancerPingClassName: com.talpay.gateway.config.HealthCheck   #健康检查
      NFLoadBalancerPingInterval: 20                                       #设置健康检查间隔,单位秒,默认30秒
    
    merchant-load-balanced-service:
      ribbon:
        listOfServers: 192.168.xxx.xxx:8080
    
    split-load-balanced-service:
      ribbon:
        listOfServers: 192.168.xxx.xxx:8081
    
    cashier-load-balanced-service:
      ribbon:
        listOfServers: 192.168.xxx.xxx:8080
      

    Ribbon服务健康检查

     @Slf4j
    @Component
    public class HealthCheck implements IPing{
    
        @Autowired
        private RestTemplate restTemplate;
    
        @Value("${dingtalk.url}")
        private String dingtalkURL; //钉钉报警url
    
        @Override
        public boolean isAlive(Server server) {
           
            String url = "#34;+ server.getId()+ "/actuator/health";
            try {
                ResponseEntity<String> heath = restTemplate.getForEntity(url, String.class);
                if (heath.getStatusCode() == HttpStatus.OK) {
                    log.info("ping " + url + " success ");
                    return true;
                }
                log.info("ping " + url + " error and response is " + heath.getBody());
                return false;
            } catch (Exception e) {
                log.error("ping " + url + " failed");
                DingRebotSendUtil.send(dingtalkURL,new TextMessage("网关|ping:" + url + " failed"));
                return false;
            }
        }
    }  

    自定义GlobalFilter

     @Component
    public class LogGlobalFilter implements GlobalFilter {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            ServerHttpRequest serverHttpRequest= exchange.getRequest();
            String url = serverHttpRequest.getURI().toString();
            System.out.println("url ------>: " + url);//打印每次请求的url
            return chain.filter(exchange);
        }
    }  

    测试结果:

    「干货」从零开始的微服务搭建之路

    第一次触发

    第二次触发

    以上为真实测试数据,第一次触发链接到生产环境,第二次触发链接到仿真环境。

    不断分享开发过程用到的技术和面试经常被问到的问题,如果您也对IT技术比较感兴趣可以「关注」我

    文章来源:智云一二三科技

    文章标题:「干货」从零开始的微服务搭建之路

    文章地址:https://www.zhihuclub.com/197187.shtml

    关于作者: 智云科技

    热门文章

    评论已关闭

    1条评论

    1. Hi there Dear, are you genuinely visiting this website regularly, if so
      after that you will without doubt take fastidious know-how.

    网站地图