栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

微服务SpringCloud

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

微服务SpringCloud

微服务 什么是微服务 背景和SOA

单体构架

  • 例如商场系统,在这个工程中创建不同的Service实现商城系统中不同的业务场景,如账户、商品、库存等
  • 一个 jar包 或者 war包 里面包含一个应用的所有功能,称这种架构为 单体架构
  • 这种架构足够简单,能够快速开发和上线,对于项目初期用户量不大的情况,这样的架构足以支撑业务的正常运行

集群和垂直化

背景:

  • 用户量越来越大,网站的访问量不断增大,导致后端服务器的负载越来越高
    • 当服务器的负载越来越高的时候,如果不进行任何处理,用户在网站上操作的响应会越来越慢,甚至出现无法访问的情况
  • 用户量大了,产品需要满足不同用户的需求,使得业务场景越来越多并且越来越复杂
    • 业务场景越多越复杂,意味着jar包中的代码量会持续上升,并且各个业务代码之间的耦合度也会越来越高,后期的代码维护和版本发布的测试和上线也会很困难

优化思路:

  • 横向增加服务器,把单台机器编程多台机器的集群
  • 按照业务的垂直领域进行拆分,减少业务的耦合度,以及降低单个 jar包带来的伸缩性问题

SOA

背景:

  • 用户执行下单操作,业务逻辑会先检查商品的库存,库存足够的情况下才会提交订单,那么检查库存的逻辑是放在订单子系统还是库存子系统呢?在整个系统中,一定会存在非常多类似的共享业务的场景,这些业务场景的逻辑肯定会被重复的创建,从而产生非常多的冗余的业务代码

    • 将这些共享业务逻辑抽离出来形成可重用的服务
  • 在一个集团公司下有很多子公司,每个子公司都有自己的业务模式和信息沉淀,各个子公司之间不进行交互和共享,彼此之间形成了信息孤岛,价值无法最大化

  • SOA是面向服务的架构,核心是把一些 通用的、会被多个上层服务调用的共享业务提取成独立的基础服务,被提取出来的共享服务相对来说比较独立,并且可以重用

  • 在SOA中,服务是最核心的抽象手段,业务被划分为一些 粗粒度的业务服务和业务流程

  • 采用 ESB(企业服务总线) 来作为系统和服务之间的通信桥梁,ESB本身还提供服务地址的管理、不同系统之间的协议转化和数据格式转化等。调用端不需要关心目标服务的位置,从而使得服务之间的交互是动态的

    • SOA主要解决的问题是:信息孤岛、共享业务的重用

SOA和微服务架构的不用

  • SOA关注的是 服务的重用性 及解决 信息孤岛问题
  • 微服务关注的是解耦,解耦和可重用性本质上是有区别的
    • 解耦是降低业务之间的耦合度,可重用性关注的是服务的复用
  • 微服务会更多地关注在 DevOps的持续交付上,因为服务粒度细化之后使得开发运维变得更加重要,因此微服务与容器化技术的结合更加紧密
微服务架构
  • 随着业务的发展,用户量和业务复杂度逐渐增加,系统为了支撑更大的流量需要做很多优化
    • 在硬件方面,升级服务器配置提升性能
    • 在软件方面,会采用微服务架构、对业务服务进行微服务化拆分、水平扩容等来提升系统性能,以及解决业务的复杂性问题
  • 微服务将 每个具体的业务服务构成可独立运行的微服务,每个微服务只关注某个特定的功能,服务之间采用轻量级通信机制 REST API 进行通信
  • 微服务拆分到多大的粒度没有统一的标准,微服务的粒度越小,服务独立性带来的好处就越多,但是管理大量的微服务也会越复杂
微服务架构的优缺点

优点

  • 复杂度可控: 通过对共享业务服务更细粒度的拆分,一个服务只需要关注一个特定的业务领域,并通过良好的接口清晰表达服务边界。由于体积小、复杂度低,开发、维护会更加简单
  • **容错性:**在微服务架构中,如果某一个服务发生故障,可以使故障隔离在单个服务中,其他服
    务可以通过重试、降级等机制来实现应用层面的容错
  • **独立部署:**当某个微服务发生变更时不需要重新编译部署整个应用,并且单个微服务的代码量比较小使得发布更加高效
  • **可扩展性更强:**可以根据每个微服务的性能要求和业务特点来对服务进行灵活扩展,比如通过增加
    单个服务的集群规模,提升部署了该服务的节点的硬件配置
  • **技术选型更灵活:**每个微服务都由不同的团队来维护,所以可以结合业务特性自由选择技术栈

缺点

  • **分布式架构的复杂性:**微服务本身构建的是一个分布式系统,分布式系统涉及服务之间的远程通信,而网络通信中网络的延迟和网络故障是无法避免的,从而增加了应用程序的复杂度

  • 服务监控: 在一个单体架构中很容易实现服务的监控,因为所有的功能都在一个服务中。在微服务架构中,服务监控开销会非常大,不仅要对整个链路进行监控,还需要对每一个微服务都实现一套类似单体架构的监控

  • **故障排查:**一次请求可能会经历多个不同的微服务的多次交互,交互的链路可能会比较长,每个微服务会产生自己的日志,在这种情况下如果出现一个故障,开发人员定位问题的根源会比较困难

  • 服务依赖:微服务数量增加之后,各个服务之间会存在更多的依赖关系,使得系统整体更为复杂

    • 假设需要修改服务A、B、C ,而A依赖B , B依赖C。在单体式应用中,只需要改变相关模块,整合变化,再部署就好了。对比之下,微服务架构模式就需要考虑相关改变对不同服务的影响。比如,需要更新服务C ,然后是B,最后才是A,许多改变一般只影响一个服务,需要协调多服务的改变很少
  • **运维成本:**在微服务中,需要保证几百个微服务的正常运行,对于运维的挑战是巨大的

    • 比如单个服务流量激增时如何快速扩容、服务拆分之后导致故障点增多如何处理、如何快速部署和统一管理众多的服务等
微服务架构的技术挑战

两大特性:高可用性、高可扩展性

服务间通信

  • 服务治理、服务间调用、负载均衡

服务容错、异常排查

  • 流量整形、降级熔断、服务链追踪

分布式能力建设

  • 微服务网关、分布式事物、消息驱动、配置管理
服务注册与发现 为什么需要服务注册?

在微服务架构下,一个业务服务会被拆分成多个微服务,各个服务之间相互通信完成整体的功能

另外,为了避免单点故障,微服务都会采取集群方式的高可用部署,集群规模越大,性能也会越高

需要服务注册的原因

服务消费者要去调用多个服务提供者组成的集群

  • 首先,服务消费者需要在本地配置文件中维护服务提供者集群的每个节点的请求地址

  • 其次,服务提供者集群中如果某个节点下线或者宕机,服务消费者的本地配置中需要同步删除这个节点的请求地址,防止请求发送到已宕机的节点上造成请求失败

服务注册中心的功能

  • 服务地址的管理
  • 服务注册
  • 服务动态通知
Nacos的特性

Nacos致力于解决微服务中的 服务注册与发现、统一配置等问题。它提供了一组简单易用的特性集,帮助开发者快速实现动态服务发现、服务配置、服务元数据及流量管理

服务发现和服务健康监测

  • Nacos支持基于DNS和RPC的服务发现。服务提供者使用原生SDK、OpenAPI或一个独立的AgentTODO注册Service后,服务消费者可以使用DNS或HTTP&API查找和发现服务
  • Nacos提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos支持传输层 ( PING或TCP )和应用层(如HTTP、MySQL、用户自定义)的健康检查。对于复杂的云环境和网络拓扑环境中(如VPC、边缘网络等) 服务的健康检查, Nacos提供了agent上报和服务端主动检测两种健康检查模式。Nacos还提供了统一的健康检查仪表盘,帮助用户根据健康状态管理服务的可用性及流量

动态配置服务

  • 业务服务一般都会维护一个本地配置文件,然后把一些常量配置到这个文件中

存在的问题

  • 配置需要变更时要 重新部署应用

动态配置服务的优点

  • 动态配置服务可以以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置,可以使配置管理变得更加高效和敏捷
  • 配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易

其他优点

  • Nacos提供了一个简洁易用的UI帮助用户管理所有服务和应用的配置,还提供了包括配置版本跟踪、金丝雀发布、一键回滚配置及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助用户更安全地在生产环境中管理配置变更,降低配置变更带来的风险

动态DNS服务

  • 动态DNS服务支持权重路由,让开发者更容易地实现中间层负载均衡、更灵活的路由策略、流量控制,以及数据中心内网的简单DNS解析服务

服务及其元数据管理

  • Nacos可以使开发者从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的SLA及最重要的metrics统计数据

这种方式在某些场景中会存在问题,比如配置需要变更时要重新部署应用。而动态配置服务可以以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置,可以使配置管理变得更加高效和敏捷。配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。

SpringCloudGateWay API网关的作用

API网关的作用

  • 在客户端与服务端之间增加了一个API网关,所有的外部请求都会先经过网关这一层

  • 网关层可以把后端的多个服务进行整合,然后提供唯一的业务接口 ,客户端只需要调用这个接口即可完成数据的获取及展示

    • 服务的鉴权会分布在每个微服务中处理,客户端对于每个服务的调用都需要重复鉴权
    • 在后端的微服务架构中,可能不同的服务采用的协议不同,比如有HTTP、RPC等。 客户端如果需要调用多个服务,需要对不同协议进行适配

网关还提供以下功能

  • 针对所有请求进行统一鉴权、限流、熔断、日志
  • 协议转化。针对后端多种不同的协议,在网关层统一处理后以HTTP对外提供服务 (用过Dubbo框架的读者应该知道,针对Dubbo服务还需要提供一个Web应用来进行协议转化)
  • 统一错误码处理
  • 请求转发,并且可以基于网关实现内、外网隔离
统一认证授权

统一认证授权包括两部分

  • 客户端身份认证:主要用于判断当前用户是否为合法用户,一般的做法是使用账号和密码进行验证。当然,对于一些复杂的认证场景会采用加密算法来实现,比如公、私钥
  • 访问权限控制:身份认证和访问权限一般是相互联系的,当身份认证通过后,就需要判断该用户是否有权限访问该资源,或者该用户的访问权限是否被限制了

在单体应用中,客户端身份认证及访问权限的控制比较简单,只需要在服务端通过session保存该用户信息即可

在微服务架构下,单体应用被拆分成多个微服务,鉴权的过程就会变得很复杂

  • 首先要解决的问题是,原来单体应用中的session方式已经无法用于微服务场景
  • 其次就是如何实现对每个微服务进行鉴权

解决方案

  • 第一个问题,目前已经有非常多的成熟解决方案了 比如AccessToken、 Oauth (开放API)
  • 第二个问题,可以把鉴权的功能抽离出一个统一认证服务,所有的微服务在被访问之前,先访问该认证服务进行鉴权。这种解决方案看似合理,但是在实际应用中,一个业务场景中可能会调用多个微服务,就会造成一次请求需要进行多次鉴权操作,增加了网络通信开销

增加API网关之后,在网关层进行请求拦截,获取请求中附带的用户身份信息,调用统一认证中心对请求进行身份认证,在确认了身份之后再检查是否有资源的访问权限

原理分析
spring:
  cloud:
    gateway:
      routes:
        - id: auth
          uri: http://localhost:8080/say  #访问地址
          predicates:
            - Path=/gateway
public class HeaderTokenGatewayFilter implements GatewayFilter, Ordered {

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

        // 从 HTTP Header 中寻找 key 为 token, value 为 imooc 的键值对
        String name = exchange.getRequest().getHeaders().getFirst("token");
        //若匹配则放行
        if ("imooc".equals(name)) {
            return chain.filter(exchange);
        }

        // 标记此次请求没有权限, 并结束这次请求
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);

        return exchange.getResponse().setComplete();
    }

    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE + 2;
    }
}

@Component
public class HeaderTokenGatewayFilterFactory
        extends AbstractGatewayFilterFactory {

    @Override
    public GatewayFilter apply(Object config) {
        return new HeaderTokenGatewayFilter();
    }
}


//案例2
@Service
@Slf4j
public class GpDefineGatewayFilterFactory extends AbstractGatewayFilterFactory {

    public GpDefineGatewayFilterFactory(){
        super(Gpconfig.class);
    }

    @Override
    public GatewayFilter apply(Gpconfig config) {
        return ((exchange, chain) -> {
            log.info("[Pre] Filter Request,name:" + config.getName());
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                log.info("[post] Response Filter");
            }));
        });
    }
    
    @Data
    public static class Gpconfig{
        private String name;
    }
}
 
  • 类名必须要统一以GatewayFilterFactory结尾,因为默认情况下过滤器的name会采用该自定义类的前缀,这里的name=GpDefine

  • 在apply方法中,同时包含Pre和Post过滤,在then方法中是请求执行结束之后的后置处理

  • GpConfig是一个配置类 ,该类中只有一个属性name

    • 这个属性可以在yml文件中使用
spring:
  cloud:
    gateway:
      routes:
        - id: define_filter
          uri: http://localhost:8080/say
          predicates:
            - Path=/gateway/**
          filters:
            - name: GpDefine
              args:
                name: Gp_Mic
            - StripPrefix=1
  • name属性 就是GpDefineGatewayFilterFactory的前缀
  • args中的name属性是GpConfig配置类中声明的属性,这个属性配置好之后,可以在代码中获得这个name对应的值Gp_Mic
自定义GlobalFilter

只需要实现GlobalFilter接口,自动会过滤所有的Route

@Service
@Slf4j
public class GpDefineFilter implements GlobalFilter, Ordered {

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("[pre]-Enter GpDefineFilter");
        return chain.filter(exchange).then(Mono.fromRunnable(() ->{
            log.info("[post]-Return Result");
        }));
    }

    // getOrder表示该过滤器的执行顺序,值越小,执行优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}
  • 通过AbstractGatewayFilterFactory实现的局部过滤器没有指定order ,它的默认值是0
  • 如果想要设置多个过滤器的执行顺序,可以重写getOrder方法
OpenFeign HTTP调用(超时、重试、并发)

与执行本地方法不同,进行HTTP调用 本质上是通过 HTTP协议 进行一次网络请求,网络请求是不可靠的,必然有超时的可能性

HTTP调用需要考虑到的问题

  • 框架设置的 默认超时 是否合理
  • 考虑到网络的不稳定,如果超时后请求 重试 的话,但需要考虑服务端 接口的幂等性 是否支持重试
  • 还需要考虑框架是否会像浏览器那样 限制并发连接数,以免在服务并发很大的情况下,HTTP调用的并发数限制成为瓶颈

配置连接超时和读取超时参数

  • HTTP协议 底层是网络层的 TCP/IP 协议,它是面向连接的协议,在传输数据之前需要建立连接

    • 连接超时参数 ConnectTimeout,用户建立连接阶段的最长等待时间

    • 读取超时参数 ReadTimeout,用来控制从 Socket 上读取数据的最长等待时间

连接超时

  • 连接超时配置不易配置过长, 一般来说,TCP三次握手建立连接需要的时间非常短,通常在毫秒级最多到秒级,不可能需要十几秒甚至几十秒。如果很久都无法建立连接,很有可能是网络或防火墙配置的问题,在这种情况下,如果几秒连接不上,那么大概率永远也连接不上
    • 即设置特别长的连接超时意义不大,将其配置得短一些(比如 1~5秒)即可,如果纯内网调用,这个参数可以设置得更短,在下游服务离线无法连接的时候,可以快速失败
  • 排查连接超时原因,服务有多个节点,如果别的客户端通过客户端负载均衡连接服务端,那么客户端和服务端会直接建立连接,此时出现连接超时大概率是服务端的问题
    • 如果服务端通过类似 Nginx的反向代理来负载均衡,客户端连接的是Nginx,而不是服务端,应该排查Nginx

读取超时

  • 读取超时,服务端的执行不会中断,类似 Tomcat 的 Web服务器 都是把服务端请求提交到线程池处理的,只要服务端收到了请求,网络层面的超时和断开不会影响服务端的执行
    • 出现读取超时不能随意假设服务端的处理情况,需要根据业务状态考虑如何后续处理
  • 读取超时不仅仅是 Socket 网络层面的概念,数据传输的最长耗时, 发生了读取超时,网络层无法区分是服务端没有把数据返回给客户端,还是数据在网络上耗时较久或丢包
    • TCP是先建立连接后传输数据,对于网络情况不是特别糟糕的服务调用,通常可以认为出现连接超时是网络问题或服务不在线
    • 出现读取超时是服务处理超时,读取超时指的是,向Socket写入数据后,等到Socket返回数据的超时时间,其中包含绝大部分的时间是服务端处理业务逻辑的时间
  • 超时时间越长任务接口成功率不一定越高,不宜将读取超时参数配置太长,进行 HTTP请求一般是需要获得结果的,属于 同步调用。如果超时时间很长,在等待服务端返回数据的同时,客户端线程(通常是Tomcat线程池)也在等待,当下游服务出现大量超时的时候,程序可能也会受到拖累创建大量线程,最终崩溃
    • 对定时任务或异步任务来说,读取超时配置得长些问题不大。面向用户响应的请求或是微服务短平快的同步接口调用,并发量一般较大,应该设置一个较短的读取超时时间, 以防止被下游服务拖慢,通常不会设置超过30秒的读取超时
    • 如果把读取超时设置为2秒,服务端接口需要3秒,不是永远都拿不到执行结果了?
      • 设置读取超时一定要根据实际情况,过长可能会让下游抖动影响到自己,过短又可能影响成功率。有些时候需要根据下游服务的SLA,为不同的服务端接口设置不同的客户端读取超时
Feign和Ribbon配置超时
  • 默认情况下,Feign 的读取超时是 1秒

  • 如果要配置 Feign 的读取超时,就必须同时配置连接超时,才能生效

    • FeignClientFactoryBean 只有同时设置 ConnectTimeout 和 ReadTimeout,Request.Options才会被覆盖

      • feign.client.config.default.readTimeout=3000
        feign.client.config.default.connectTimeout=3000    
        
    • 如果希望针对单独的Feign Client设置超时时间,可以把 default 替换为 Client 的 name

      • feign.client.config.default.readTimeout=3000
        feign.client.config.default.connectTimeout=3000
        feign.client.config.clientsdk.readTimeout=3000
        feign.client.config.clientsdk.connectTimeout=3000      
        
  • 单独的超时可以覆盖全局超时

  • 除了可以配置 Feign,也可以配置 Ribbon 组件的参数来修改两个超时时间 (参数首字母要大写)

    • ribbon.ReadTimeout=4000
      ribbon.ConnectTimeout=4000
      
  • 同时配置 Feign 和 Ribbon 的参数,生效的是 Feign

  • //这样配置最终生效的还是 Ribbon的超时(4秒),单独配置 Feign的读取超时并不能生效
    clientsdk.ribbon.listOfServers=localhost:45678
    feign.client.config.default.readTimeout=3000
    feign.client.config.clientsdk.readTimeout=2000
    ribbon.ReadTimeout=4000    
    
Ribbon会自动重试请求

短信重复发送案例

  • 把发短信接口从Get 改为 Post,有状态的 API接口 不应该定义为Get,选择Get还是Post,应该是 API 的行为,不是参数大小

    • 根据HTTP协议的规范,Get请求用于数据查询,Post用于把数据提交到服务端用于修改或新增
    • Get 请求的参数包含在Url QueryString中,会受浏览器长度限制,所以会会选择使用JSON以Post提交大参数,使用Get提交惨数。
  • Ribbon 的源码,MaxAutoRetriesNextServer 参数默认为1,Get 请求在某个服务端节点出现问题(比如读取超时)时,Ribbon会自动重试一次

    • //禁用服务调用失败后在下一个服务端节点的自动重试
      ribbon.MaxAutoRetriesNextServer=0
      
并发限制了爬虫能力

案例背景

并发数的限制 导致程序的处理能力上不去

  • 爬虫项目,整体爬取数据的效率很低,增加线程池数量也无用,只能堆更多的机器做分布式的爬虫

  • 爬虫需要多次调用一个接口进行数据抓取,为了确保线程池不是并发的瓶颈,使用一个没有线程上限的newCachedThreadPool 作为爬取任务的线程池(一般不要使用没有线程数量上限的线程池), 然后使用HttpClient 实现HTTP请求,把请求任务循环提交到线程池处理,最后等待所有任务执行完成后输出执行耗时

    • 使用默认的 PoolingHttpClientConnectionManager构造的 CloseableHttpClient,测试一下爬取10次的耗时

    • 虽然一个请求需要1秒执行完成,但是线程池是可以扩张使用任意数量线程的。10个请求并发处理的时间基本相当于1个请求的处理时间,也就是1秒,但日志中显示实际耗时5秒

PoolingHttpClientConnectionManager的源码

  • defaultMaxPerRoute = 2,也就是同一个主机 / 域名的最大并发请求数为2。爬虫需要10个并发,显然是默认值太小限制了爬虫的效率
  • maxTotal = 20,所有主机整体最大并发为20,这也是 HttpClient 整体的并发度。目前,请求数是10,最大并发是10,20不会成为瓶颈
    • 例如,使用同一个HttpClient访问10个域名,defaultMaxPerRoute 设置为10,为确保每一个域名都能达到10并发,需要把maxTotal设置为100
转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号