写点什么

打造高可用的微服务架构:Spring Cloud 的优化与实践

作者:xfgg
  • 2023-06-08
    福建
  • 本文字数:7986 字

    阅读完需:约 26 分钟

打造高可用的微服务架构:Spring Cloud 的优化与实践

引言

当提到微服务架构时,我们经常把重点放在如何优化和提高我们的服务可用性。从时间和成本的考虑,社区普遍采用 Spring Cloud 框架的实践经验来创造高可用性的微服务。如果你对如何构建一个最佳和可靠的微服务架构也感兴趣的话,那么你会发现本文对你来说是非常有趣和有启发性的。

微服务架构的优势和挑战

为什么要使用微服务架构

微服务架构是一种将应用程序拆分成小型的、可独立运行的服务单位的软件架构风格。与单体式架构相比,微服务架构可以带来许多好处。

  • 更好的可拓展性:每个服务,可以按需进行独立部署,这有助于有针对性地调配计算资源,确保高可用性和高吞吐量并且提高系统的可拓展性。

  • 更好的容错性:一次性部署所有代码可能导致应用崩溃。然而,使用微服务架构适当地拆分之后,即使一个服务失败,整个应用也不会崩溃。

  • 更好的代码可维护性:更好地维护 LOC(关注点分离),将服务有目的地划分成相关组件并使笼络代码职责在单一的 服务中。

微服务架构的特点和优缺点

  • 耦合度低:由于系统中的每个服务都相对独立,一个服务的更改不会影响其他服务,因此,开发人员可以分别设计和维护每个服务。

  • 拆分复杂:对于大型企业应用,尤其是传统的单体式(monolithic)应用程序重构过程被微服务拆分为多个较小服务的过程相比而言可能会更加复杂。

  • 自治性:每个服务都应当有其 API,服务的批准者以及负责:更新、监控、故障排除和文档化其服务的运行时控制注释-----in progress

  • 弹性,每个服务宕掉而不影响其它服务的运行。

微服务架构应用场景及业务背景

  • 复杂业务:当有大型复杂的应用程序需要管理时,使用微服务可以提供更高的可拓展性和可维护性。

  • 大型组织:在大型组织中,大量重磅级应用程序分解成微服务可以使每个应用团队相对独立而不会有显著的依赖关系。

微服务架构面临的挑战

  • 维护多个服务的依赖关系和一致性级别约束等

  • 服务治理和网络 ISSUE

  • 跨服务事务和数据一致性问题

  • 微服务架构安全性和监控

SpringCloud 框架介绍

SpringCloud 的简介和定位

Spring Cloud 是基于 Spring Boot 的全栈式微服务框架,为企业级微服务架构中常见的服务(例如注册中心、服务发现、配置中心、API 网关等)提供了一些便捷的开发工具和实现方案,帮助开发人员构建高可用、高扩展性、高灵活性的微服务应用。

SpringCloud 与微服务之间的关系

Spring Cloud 和微服务是非常紧密的关系,甚至可以说 Spring Cloud 是其中的重要一部分。微服务采用的是分布式架构,易于维护、扩展,在多人协作中,有明显的作用。Spring Cloud 的主要服务都是基于微服务的架构思想,因此 Spring Cloud 和微服务合体,共同为微服务的实施提供便利。

SpringCloud 的核心组件和主要服务

Spring Cloud 由多个核心组件和主要服务组成,主要包括:

  • Spring Cloud Config:配置服务中心,集中管理各微服务的配置信息。

  • Spring Cloud Netflix:提供 Netflix OSS 软件包的集成实现,主要包括 Eureka(服务注册中心)、Ribbon(负载均衡)、Hystrix(熔断器)、Zuul(API 网关)等。

  • Spring Cloud OpenFeign:消费服务工具,基于声明性注解,简化了使用 Restful 服务的流程。

  • Spring Cloud Bus:消息总线工具,自动支持协调微服务之间的配置更新和广播。

  • Spring Cloud Stream:消息分发工具,可以将数据发送到相应的 destination 不同的 microservice 组件或应用中。

  • Spring Cloud Security:安全工具,提供常见的安全服务,如 OAuth2、JWT、Single Sign On 等。

高可用架构设计的考虑因素

高可用架构的基本概念和定义:

高可用架构是指应用程序或系统在硬件、软件或其他因素故障的情况下,能够维持持续的运行和可靠的性能,以确保用户服务不被中断。高可用性是应用程序或系统可用性的一种度量,通常是以百分比的方式来测量的。通常高可用性的目标是达到 99%以上的服务可用性,对于一些关键性的系统,比如金融系统等可保证 99.99%以上的可用性。

组件失效的原因分析

组件失效是指在系统中某一组件因为硬件、软件或其他因素的故障而不能正常工作,这些因素包括硬件的设计和规划、网络环境、应用程序设计等等。在高可用架构设计中,应该对可能导致系统中各个组件失效的因素进行全面地分析,并采取一系列措施来弥补已知的战胜方式,确保系统的稳定。

不同的失效模式和恢复策略

在高可用架构设计中,常见的失效模式主要有热备、冷备、故障转移、故障切换等。在设计时,还应采取对应的恢复策略,使系统能够自动地侦测故障并找到相应的系统备份,并能够迅速地启动备用系统以继续支持用户的服务。例如,当系统中某一节点出现故障时,可以采用负载均衡选址酷总流量自动切换到其他节点。

更好的容错性设计要素介绍

提高系统的容错性是高可用架构设计的重要策略之一。在设计时,可以通过引入冗余、分布式、负载均衡、备份等策略来增强系统的可靠性。例如,可以设计主-从式的多节点集群,数据分片技术、采用 RAID 级的磁盘阵列等,来增加系统的冗余度和稳定性。

各模式下的开发途径分析

针对各种失效模式和恢复策略,开发工程师应该在设计时采用不同的开发途径。例如,针对主从式的多节点集群,应该采用多线程和消息队列技术,在保证系统性能的同时提高效率。针对数据分片技术,可以采用以领导节点为中心的易扩展的多个子节点的分布式架构,使各个节点间能够无缝智慧地连接起来,进而提升整个系统的可靠性。

Spring Cloud 使用组件详解

Spring Cloud 提供了丰富的组件来支持分布式系统的开发,下面将对其中的几个核心组件进行详细的介绍。

Eureka:服务注册与发现

Eureka 作为 Spring Cloud 的核心基础组件之一,实现了分布式系统中的服务注册与发现。Eureka Server 可以作为服务注册中心,所有的微服务都将向 Eureka Server 报告自己的状态(服务启动与停止、健康状况、地址信息等),让其他服务能够借此得知它所需要调用的服务实例的信息,从而实现了服务的发现与负载均衡。

Eureka 的核心思想是客户端负载均衡,服务端数据量较小可扩容

Eureka 的主要组件如下:

  • Eureka Server:Eureka 服务端,提供服务注册、发现和撤销等服务;

  • Eureka Client:Eureka 客户端,用于实现服务注册以及与服务端进行注册同步和调用等操作。

Ribbon:客户端负载均衡

Ribbon 紧密集成到了 Spring Cloud 体系中,为服务消费者提供多种负载均衡算法选择和路由策略的插拔式扩展,同时与 Eureka 注册中心完美配合,实现服务自动发现和负载均衡。

Ribbon 可以介于连接服务消费者和提供服务者之间,通过相应的算法规则来解决相互调用时发生的问题,并名为千万级分布式服务下自带的负载均衡。

Feign:声明式 REST 客户端

Feign 提供了一种针对接口调用的声明式 REST 客户端,使得编写服务客户端变得异常简洁。通过使用注解实现请求模板的往指定 url 上发起请求,默认情况下使用 ribbon 实现客户端的负载均衡。

Feign 的核心优势在于声明式 API 定义,遵循标准的 JAX-RS 或 SpringMVC 注解,可以根据注解自动化生成请求路径、请求方法、请求头以及请求内容,通过内置的编码器和解码器,可以直接将 HTTP 响应回传为一个 Java 对象。

Hystrix:熔断器

Hystrix 主要解决分布式系统中服务雪崩的问题,是一个用于处理分布式系统的延迟和容错的开发项目,至今已被广泛应用于各个互联网公司中。

当系统中多个服务间存在高度的耦合性时,如果其中一个服务由于不可预见的原因造成了延迟较大或者发生宕机了,其中依赖于这个服务的其他服务也将无法及时响应。为了避免这种状况造成整个系统崩溃,需要通过 Hystrix 的帮助来做到降级处理,让故障不影响到整个系统的健壮性。

Hystrix 基于"隔离、熔断、降级"等设计理念来实现请求超时和错误超出阈值则返回,避免被下游服务拖垮。使用 Hystrix 只需在一个服务请求的类或方法上增加 @HystrixCommand 标注即可快速得到正确的熔断机制。

Hystrix 的设计目标是创建健壮、弹性和可条件式代码的前已提供很好的出错处理和调用适配器

Zuul:API 网关

Zuul 是 Spring Cloud 中“API 网关”的重要组件之一,为整个微服务架构中的路由、过滤和认证授权等统一入口进行控制。

API 网关的设计思想来源于业内应用的反向代理的优点,可有效地将网站流量导向下面几层基础支撑服务。微服务趋势的发展也让 API 网关出现在了开发人员的视野中。Zuul 提供了一 topl 将过滤器链计算反映到请求调用链上的底层微服务系统。

实战

在开始之前,我们先做些铺垫。使用 Spring Cloud 打造高可用微服务架构通常需要遵循一些惯用的做法,如下所示:

  • 将业务流程划分成多个微服务,每个微服务都是一个独立的小型应用程序。

  • 启用服务注册中心,每个微服务都将其自己的实例注册到服务注册中心中,而客户端则从服务注册中心中获取微服务实例的位置信息。

  • 使用 Ribbon 实现微服务间的客户端负载均衡,以确保能够高效地利用所有微服务实例。

  • 启用断路器,当微服务访问发生问题或延迟很长时间时,断路器将从客户端负载均衡选取列表中移除对应的微服务实例,以避免产生故障。

  • 采用熔断器来防止高流量下微服务群的过载,本文中使用的是 Hystrix。

  • 采用服务网关轻松管理 API 网关,实现协议转换、路由、负载均衡等功能,在实现代理 Spring Cloud Services 的同时还能虚拟出多个实例、路径和端点,从而实现一个按名称关联的服务的源码位置劫持工具,以帮您为服务或不同版本的服务提供一个翻译层,隔离了上下游。

搭建高可用服务中心

使用 Spring Cloud Eureka 作为注册中心。 Eureka 是 Netflix 开源的服务发现组件,可以支持高可用配置。

添加依赖

<dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency>
复制代码

配置 application.yml

server:  port: 8761  #注册中心端口号 eureka:  instance:    hostname: localhost #主机名  client:    registerWithEureka: false #不将自己注册到eureka    serviceUrl:      defaultZone: http://localhost:${server.port}/eureka/ #eureka服务端的配置信息
复制代码

启动 Eureka (添加启动类)

@EnableEurekaServer // 开启eureka服务@SpringBootApplicationpublic class EurekaServer {    public static void main(String[] args) {        SpringApplication.run(EurekaServer.class, args);    }}
复制代码

编写服务提供方

对接 Eureka 注册中心,在 Eureka 上展示服务列表,并暴露接口供消费方使用。注意高可用配置。

添加依赖

<dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>
复制代码

配置 application.yml

server:  port: 8080
spring: application: name: goods-center # 服务名称,在 eureka 上注册需要使用
eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ # 注册中心地址
复制代码

然后添加处理逻辑

@SpringBootApplication@EnableEurekaClient // 启动 eureka 客户端public class GoodsApplication {    public static void main(String[] args) {        SpringApplication.run(GoodsApplication.class, args);    }}@Configurationpublic class GoodsConfig {// 可以从application.yaml中读出    @Value("${spring.application.name}")    private String serviceName;       /**     * 配置 Eureka 服务列表(url),采用多active方案,zookeeper、consul等替换即可。     */    @Bean    public DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs() {        DiscoveryClient.DiscoveryClientOptionalArgs args = new DiscoveryClient.DiscoveryClientOptionalArgs();              ScheduleWthFixedDelayCustomErReg soc = new ScheduleWthFixedDelayCustomErReg();        args.setRegistrationProcessSupplier(() -> soc);
TimeIntervalJitterRetry tijr = new TimeIntervalJitterRetry(HEALTH_POLL_INTERVAL_MS, INITIAL_REGISTRATION_RETRY_DELAY_MS); args.setRetryableHttpClient(new InterProcessOrNioHttpClient(serviceName, ServiceAddressSelector.DEFAULT, tijr, null, MESSAGE_MAX_BYTES, CONNECTION_READ_TIMEOUT_MS)); return args; } /** * Er注册和心跳拉取配置 */ private class ScheduleWthFixedDelayCustomErReg extends ServerListEndpoint implements RegistrationProcessor { private final WebClientTransport webClientTransport; ScheduleWthFixedDelayCustomErReg() { webClientTransport = new WebClientTransport(unsafeClientConfig(ScheduleWthFixedDelayCustomErReg.class.getSimpleName()).with(ServiceAddressEndPointListProvider.ENDPOINT_PREFIX_KEY, serviceName)); } @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextStartedEvent) { try { httpCatTransport.startSendTimeGestion(); //UDS记录程序启动成功日志 } catch (Throwable tr) { logger.error(format("{} startSendTimeGestion error", getClass().getSimpleName()), tr); } } else if (event instanceof ContextClosedEvent) { try { httpCatTransport.stopSendTimeGestion(); //UDS+ZK记录程序正常退出日志。 } catch (Throwable tr) { logger.error(format("{} stopSendTimeGestion error", getClass().getSimpleName()), tr); } } } /** * 定制注册周期日程的执行方法 */ @Override public void process(Registration registration, Optional<ServiceInstance<?>> _ignored_1, Optional<RegistrationOperation> _ignored_2) throws IOException { Map<String, String> metadata = registration.getMetadata(); webClientTransport.writeMetadata("name", metadata.get("name")); } }}
复制代码

编写服务消费方

连接 Eureka 注册中心,实现负载均衡功能(支持高可用)。

添加依赖

<dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency>    <groupId>com.netflix.ribbon</groupId>    <artifactId>ribbon-eureka</artifactId></dependency>
复制代码

配置 client 客户端区分负载均衡策略

server:      port: 8081
spring: application: name: reservation-service #配置产品策略 ribbon: eureka: enabled: true execution: isolation: strategy: SEMAPHORE
复制代码

添加服务消费逻辑

@SpringBootApplication@EnableEurekaClient@RestControllerpublic class ReservationServiceApplication {
public static void main(String[] args) { SpringApplication.run(ReservationServiceApplication.class, args); }
@Autowired RestTemplate restTemplate;
@Bean @LoadBalanced // 启用负载均衡 public RestTemplate restTemplate() { return new RestTemplate(); }
@GetMapping("/brands") public String getBrands() { String url = "http://goods-center/goods/brand/list"; return restTemplate.getForObject(url, String.class); }
}
复制代码

视觉效果

启动响应应用,访问服务消费端口,可视化地从界面展示服务提供者端口的名称列表,服务发现与集群中用于调度接口的应用

添加展示控制器

@Controllerpublic class QueryController {    @Autowired    private DiscoveryClient discoveryClient;
@GetMapping("/goods/listDiscover") @ResponseBody public Object getServices() { List<String> serviceNames = discoveryClient.getServices(); // 发现所有的服务名 Map<String, List<ServiceInstance>> instances = new HashMap<>(); serviceNames.forEach(serviceName -> { List<ServiceInstance> byServiceName = discoveryClient.getInstances(serviceName); // 获取服务发现client根据服务名获取连接上其他服务提供的所有实例 instances.put(serviceName, byServiceName); }); return instances; }
}
复制代码

使用 Zuul 实现分布式服务网关

使用 Spring Cloud 的 Zuul 作为微服务的反向代理和路由器,中心化地处理请求路由。使用 Eureka 高可用方式连接。这可以避免由于服务提供方或消费方的变化而导致调用 API 发生的所有位置更改。

在这里提供的是一个 HTTPWebService 协议网关入口,简单对客户端处理方式下多个服务发现的路由个根据相应具体地址指向服务提供。

添加 Zuul 依赖

<dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency>
复制代码

zuul 相关的配置监听

@SpringBootApplication@EnableZuulProxy@EnableDiscoveryClientpublic class GatewayApplication {    public static void main(String[] args) {        SpringApplication.run(GatewayApplication.class, args);    }
@Bean public ZuulFilter zuulPreFilter() { return new ZuulMyFilter(); }}
@Slf4jclass ZuulMyFilter extends ZuulFilter { @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { return this.sendMyRequest(context()); } private ZuulFilterResult sendMyRequest(RequestContext ctx) throws ZuulException{ HttpServletRequest req = ctx.getRequest(); performWork(req,ctx); return ZuulFilterResult.success(); }
void performWork(HttpServletRequest req,RequestContext context) { String requestedApiVersion = req.getHeader("requested-api-version"); /** API版本 2 */ {http://v2.ip.taobao.com} if(v2部署) routeIf(specification.format("v2", ".*", ".*", evaluatedPath.toUriString(), queryString.toString())); private void routeIf(String ifRoute) { RequestContext.getCurrentContext().add("routeIf",new RouteSpecificInfo(ifRoute)); }}
复制代码


维护其他集群模型

当使用 Spring Cloud 组件进行微服务架构搭建时,还可以使用一些其他的集群模型来维护系统的稳定性和性能。下面是一些常用的维护集群模型。

  1. Kafka Kafka 可以作为异步消息传递工具,在集群中实现不同微服务之间的消息队列,提高异步处理的效率。

  2. Zookeeper Zookeeper 可以用来存储微服务的配置信息、服务发现和负载均衡等信息,帮助微服务更高效地合理分布。

  3. ActiveMQ ActiveMQ 可以与 Spring Cloud Stream 集成,用于实现消息驱动的微服务,帮助不同微服务之间实现有效的事件通知。

在实际业务开发中,我们还可以使用更多的集群模型来实现各种功能。例如使用 MongoDB 实现微服务数据存储,使用 ElasticSearch 实现全文搜索等。根据实际业务需求和技术场景,结合使用不同的维护集群模型,可以大大提高微服务架构的效率和性能。

发布于: 2023-06-08阅读数: 31
用户头像

xfgg

关注

THINK TWICE! CODE ONCE! 2022-11-03 加入

目前:全栈工程师(前端+后端+大数据) 目标:架构师

评论

发布
暂无评论
打造高可用的微服务架构:Spring Cloud 的优化与实践_Java_xfgg_InfoQ写作社区