☰
【微服务】认识微服务及Eureka注册中心
2026/9/30 6:51:22 网站建设 项目流程

一、认识微服务

1. 微服务架构演变

  • 单体架构:

将业务的所有功能集中在一个项目中开发,打成一个包部署。

优点:

  1. 项目架构简单,前期开发成本低,周期短,小型项目的首选
  2. 开发效率高,各模块之间交互采用本地方法调用
  3. 容易部署,运维简单,直接打包成一个jar、war包,拷贝到web容器的目录下即可。

缺点:

  1. 全部功能在一个工程中,对于大项目不易开发,不易扩展,修改一个功能需要将整个项目全部编译、部署。
  2. 无法按需伸缩,如订单管理需要高可用性,需要采用集群方式,那么其他功能也要通过集群的方式来实现水平扩展,无法正对某业务按需伸缩。
  • 分布式架构:

系统由独立部署的节点、组件组成,分布在不同的服务器上,通过网络通信完成整个业务。主要是部署形态是分布在各个服务器上的,解决单机扛不住,容易挂的问题。可以理解为一个公司业务变大了,租了多个办公地点,员工之间通过发邮件、打电话协同办公,至于怎么分工,分布式本身没说死。

拆分方式有2种:

  1. 垂直拆分:按功能拆成多个系统,比如用户系统、商品系统、订单系统,各自独立部署。

  2. 水平拆分:同一个功能部署多个副本,或者数据库分库分表,比如订单系统部署 5 个实例,一起扛流量。

关键点:

  • 分布式不等于一定要按功能拆。一个单体应用部署 3 个副本 + 负载均衡,也是分布式部署。

  • 分布式只强调“多节点、网络通信、协作”,不规定服务怎么划分、怎么治理。

  • 它是 SOA 和微服务的共同基础。

优点:

  1. 每个子系统可采用不同的技术和语言进行开发。
  2. 每个子系统可按需伸缩。
  3. 通过垂直拆分,将每个子系统变成小型系统,功能简单,前期开发成本低,周期短。

缺点:

系统与系统之间存在数据冗余,耦合性较大。如订单系统、物流系统都需要使用用户系统

  • SOA架构:

在多个系统/分布式的基础上,将重复的功能抽取为组件,以服务的方式向各个系统提供服务。各系统之间通过webservice、RPC 等方式进行通信。是微服务架构的雏形,粗粒度,集中治理。

举个例子:集团下面有几个分公司,各自办公。集团发现财务、人事、采购每个分公司都重复搞,于是成立“财务中心”“人事中心”“采购中心”,各分公司都去这些中心办事。但所有请求都要先经过“集团总服务台”(ESB),总服务台帮你转接、翻译、排队。流程正规,但总服务台容易成为瓶颈。

优点:

  1. 将重复的功能抽取为服务,提高开发效率,提高系统的复用性、可维护性。
  2. 针对不同服务的特点按需伸缩,如订单服务可靠性比较高,可以做集群,其他服务可以不做集群。
  3. 采用ESB减少系统的接口耦合

缺点:

  1. 系统与服务的界限模糊,会导致抽取的服务的颗粒度过大,系统与服务之间的耦合度高
  2. 虽然使用ESB, 但服务的接口协议不固定,种类繁多,不利用系统维护。
  • 微服务:

微服务也是分布式架构方案,基于SOA架构思想,对服务层进行细粒度的拆分,所拆分的每个服务只完成某个特定的业务功能。如订单服务只实现订单相关的业务,用户服务只实现用户管理相关的业务,服务的颗粒度很小,所以叫做微服务。

例如:美食街上一排小摊位,一个只做包子,一个只做饮料,一个只做烧烤。每个摊位自己进货、自己记账、自己决定几点开门。摊位之间用微信下单,没有总服务台。灵活、迭代快,但管理起来很麻烦,卫生、消防、纠纷都要各自处理。

微服务架构特征如下:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做单一职责,避免重复业务开发。例如电商网站针对不同用户的积分及会员,给用户不同的打折力度,可以将模块划分为用户服务、积分服务、会员服务,后续需要维护只针对这个模块即可。
  • 面向服务:微服务对外暴露业务接口。例如上面的用户有多少积分,由于是独立的模块,需要将积分服务接口暴露出来供用户服务调用。
  • 自治:团队独立、技术独立、数据独立、部署独立。
  • 隔离性强:服务调用做好隔离、容错、降级、避免出现级联问题。

优点:

  1. 服务拆分颗粒度更小,有利于资源重复利用,提高开发效率
  2. 更加精准的指定每个服务的优化方案,按需伸缩
  3. 适用于互联网时代,产品迭代周期更短

缺点:

  1. 开发的复杂性增加,因为一个业务流程需要多个微服务通过网络交互完成
  2. 微服务过多,服务治理成本高,不利于系统维护。

分布式架构不一定是微服务架构,但微服务架构一定是分布式架构。

SpringCloud 集成了各种微服务功能组件,并基于 SpringBoot 实现了这些组件的自动装配,可以开箱即用。下面是 SpringCloud 与 SpringBoot 的版本兼容关系。

小结

单体架构:简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统


分布式架构:松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东


微服务:一种良好的分布式架构方案

优点:拆分粒度更小、服务更独立、耦合度更低

缺点:架构非常复杂,运维、监控、部署难度提高 SpringCloud是微服务架构的一站式解决方案,集成了各种优秀微服务功能组件

2. 服务拆分及远程调用

  • 服务拆分注意事项
  1. 不同微服务,不要重复开发相同的业务
  2. 微服务数据独立们不要访问其它微服务的数据库
  3. 微服务可以将自己的业务作为接口供其它微服务调用
  • 微服务远程调用

假设有个微服务有订单、用户两个模块,根据订单 id 查询订单信息并且返回用户信息。可以在订单模块直接去访问用户的数据库吗?显然不行,不同的微服务之间不要做相同的业务。只要在订单模块调用用户模块的接口,由用户模块访问用户数据库,然后把查询结果返回到订单模块不就好了吗。

那么如何在 java 代码中发起一个 Http 请求呢?用Spring 提供的工具 RestTemplate 可以实现。我们知道 Bean 的注入只能放在配置类中,而带有 @SpringBootApplication 注解的启动类本身也是一个配置类,一旦将这个工具类注入进来后,我们就可以调用它里面的getForObject() 接口【get 请求】或 postForObject() 接口【post 请求】

二、EureKa

服务调用关系

  • 服务提供者:暴露接口供其它微服务调用
  • 服务消费者:调用其它微服务提供的接口

    如果服务A调用了服务B,而服务B又调用了服务C,服务B的角色是什么?

  • 对于A调用B的业务而言:A是服务消费者,B是服务提供者;
  • 对于B调用C的业务而言:B是服务消费者,C是服务提供者

1. EureKa 原理分析

假如上面提到的 user-service 服务提供者部署了多个实例,那么 order-service 服务调用者就不能把请求接口写死,否则另外存在的提供者就没有了意义。小朋友你是否有很多问号????

问题一:order-service在发起远程调用的时候,如何得知user-service实例的ip地址和端口

问题二:假如有多个user-service实例地址,order-service调用时该怎么选择

问题三:消费者 order-service 如何得知某个 服务提供者 user-service 实例是否已宕机

Eureka 架构中分为两类角色,分别时 EurekaServer(服务端)和 EurekaClient(客户端) 两类。服务端记录服务信息,监测服务提供者是否存活。客户端包含服务提供则和服务消费者,因为它们的身份会随着业务需求的变化而变化。

原理解析

为了解决最开始提出的问题,这里就需要用 SpringCloud 的注册中心(Eureka-server)来解决。不管是服务消费者还是服务提供者,在将来都有可能会变化身份,eureka 将服务消费者和服务提供者都视为客户端,这些服务在启动时会将自己的信息提供给 eureka 注册中心,这叫服务注册。

由注册中心挑选服务提供者供消费者使用(利用负载均衡算法挑选)。

那怎么确保注册中心挑选的服务提供者没有宕机呢?服务提供者会间隔一定时间(30s)向注册中心证明它还活着,如果哪一天服务提供者不证明了,那注册中心就从其它提供者里面挑选候补,反正哥们最不缺备胎了。

2. EureKa 使用

想要使用 eureka, 首先需要搭建 EurekaServer 服务, 然后将服务提供者(user-serice)和服务消费者(order-service)都注册到 Eureka 中,最后在服务消费者(order-service)完成服务拉取,通过负载均衡挑选一个服务实现远程调用。

2.1 搭建 EureKa 服务

1. 创建一个模块 eureka-server

2. pom 中引入依赖

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

3. 编写启动类

@EnableEurekaServer @SpringBootApplication public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } }

4. 编写 application.yml 配置文件

server: port: 10086 spring: application: name: eureka-server eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka

5. 启动测试(ctrl + shift + F10), 点击端口可以跳转到 Eureka 的管理网页,

ctrl + shift + F10

下面这个是比较重要的,记录了注册到 eureka 的实例(每一个服务就是一个实例),

2.2 EureKa 服务注册

想要注册 user-service, 由两个步骤:

1. 引入 eureka 客户端依赖

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

2. 在 user-service 模块的 application.yml 文件中注入 eureka 实例地址及服务名称

eureka: client: service-url: # eureka 的地址信息 defaultZone: http://127.0.0.1:10086/eureka spring: application: name: userservice

注意:先启动 Eureka 服务端服务,再启动客户端服务 。

我们也可以将 order-servie 多次启动,模拟多实例部署,但为了避免端口冲突,需要修改端口设置。

设置完之后可以看到多了一个服务

启动之后可以发现已经注册到 eureka 中了

2.3 EureKa 服务发现

因为服务拉取是基于服务列表的,然后对服务列表做负载均衡。

1. 修改 OrderService 代码,修改访问的url 路径,用服务命代替ip和端口

String url = "http://userservice/user/" + order.getUserId();

2. 在 order-service 项目的启动类 OrderApplication 中的 RestTemplate 添加负载均衡注解.@LoadBalanced

3. 重启后输入地址: localhost:8080/order/101 和 localhost:8080/order/102 均查询出来了结果,回到 idea 查看控制台,发现 userApplication 的两个实例都分别有条查询记录,说明实现了负载均衡。

3 小结

三、 Ribbon 负载均衡 (扩展)

1. 负载均衡原理

我们添加了@LoadBalanced注解后就实现了负载均衡,这是什么原理呢?

这功劳归属于 Ribbon, 当我们发送请求后,Ribbon 后向 eureka 拉取服务提供者,然后 Ribbon 就会在拉去的列表中找一个服务供我们使用。

我们发出的请求是 http://userservice/user/1,怎么变成了http://localhost:8081的呢?接下来我们在 OrderApplication 启动类里跟进源码来看实现过程。连续按下两次 shift , 搜索LoadBalancerInterceptor 这个类,发现它实现了 ClientHttpRequestInterceptor 接口,这个接口有个 intercept() 方法,说明在 LoadBalancerInterceptor 一定是会重写 intercept() 方法的,也就是说LoadBalancerInterceptor这个类拦截了用户发送的请求,然后在 eureka 根据服务名称获取服务列表,再利用负载均衡算法得到真实的服务地址信息替换服务服务名称。

  • request.getURI():获取请求的 URL 地址
  • originalUrl.getHost():获取 URL 路径的服务名(user-service)
  • this.loadBalancer.execute():处理服务名称和用户请求

继续跟进 execute 方法

继续跟进getServer()发现 负载均衡选取其中一个服务是根据 IRule 这个接口来决定的。

这样负载均衡的流程就让我们弄明白了。

总结

SpringCloudRibbon 底层采用LoadBalancerInterceptor 负载均衡拦截器拦截RestTemplate这个请求,利用 getLoadBalance()得到服务名称列表后,利用 IRule 内置负载均衡规则得到一个服务提供者,RibbonLoadBalancerClient将请求地址 http://userservice/user/1 替换成 http://localhost:8081/user/1 并发送真实的请求。

2 负载均衡策略

2.1 负载均衡策略

通过上面负载均衡的原理,我们知道 Ribbon 里面有个 IRule 接口来定义负载均衡的规则,下面用一个图示展示 IRule 的子接口,每个子接口都是一种规则。

不同规则的含义如下:

内置负载均衡规则类

规则描述

RoundRobinRule

它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule

对以下两种服务器进行忽略:

(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。

(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的..ActiveConnectionsLimit属性进行配置。

WeightedResponseTimeRule为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule

以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。

BestAvailableRule忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule随机选择一个可用的服务器
RetryRule重试机制的选择逻辑

2.2 自定义负载均衡策略

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

1.代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:

@Bean public IRule randomRule(){ return new RandomRule(); // 定义为 RandomRule 规则 }

2. 配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则

userservice: # 服务名称 ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 RandomRule

2.3 饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问的耗时

通过下面配置开启饥饿加载

ribbon: eager-load: enabled: true #开启饥饿加载 clients: # 指定饥饿加载的服务名称 - userservice # 如果有多个服务要设置,换行接着写就行

3 小结

1. Ribbon 负载均衡规则

格则接口是 IRule

默认实现是 ZoneAvoidanceRule, 根据 zone 选择服务列表,然后轮询


2. 负载均衡自定义方式

代码方式:配置灵活,但修改时需要重新打包发布

配置方式:直观方便,无需重新打包发布,但无法做到全局配置

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询