微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务。
微服务拆分时粒度要小,模块内高内聚,模块间低耦合:
1. 完全解耦:每一个微服务都创建为一个独立的工程,甚至可以使用不同的开发语言来开发,项 目完全解耦。
2. Maven聚合:整个项目为一个Project,然后每个微服务是其中的一个Module
注意:在企业开发的生产环境中,每一个微服务都应该有自己的独立数据库服务,而不仅仅是 database。
RPC的实现方式有很多,比如:
要想实现跨服务远程调用,其实就是发送一次HTTP的请求~!
微服务的调用方式:
调用RestTemplate的API发送HTTP请求,常见的方法有:
因此,我们只能在order-service向user-service发起一个远程调用,发起一个HTTP的请求,调用根据ID查询用户的这个接口,远程调用的大概的步骤是这样的:
1. 我们知道,Bean的注入只能放在配置类里面,而启动类就是一个配置类:
2. 在业务代码当中注入RestTemplate,使用RestTemplate提供的API来发起HTTP请求
因此,服务B既可以是服务提供者,也可以是服务消费者!
总结:一个服务既可以是服务提供者,又可以是服务消费者!
以上几种注册中心都遵循Spring Cloud中的API规范,因此在业务开发使用上没有太太差异,由于Nacos是国内产品,中文文档比较丰富,而且同时具备配置管理功能,因此在国内使用较多!
在Spring Cloud的生态中,采用服务注册与发现模型,来实现微服务之间的互相发现发现与调用:
服务注册与发现模型如上图所示,通过在微服务系统中引入一个叫注册中心的组件,来作为协调者。
其最简化的过程是,所有的微服务应用在启动过程中会将自己的服务信息,包含服务名称、主机IP地址和端口号等信息发送到注册中心中,这个叫服务注册,然后上游的微服务(服务消费者)在处理请求过程中,根据服务名称到注册中心中查找对应服务的所有实例IP地址和端口号,因为一个服务可能多实例部署(服务发现或服务拉取),然后服务消费者,也就是调用者对实例列表做负载均衡,挑选一个实例冰箱该实例发起远程调用,从而让分散的微服务系统之间能像一个整体一样对外提供请求处理能力。
获取地址信息的流程如下:
心跳监控
问题四:如果并发太高,服务提供者临时多部署了N台实例,服务消费者如何知道新实例的IP地址?
因此,接下来我们动手实践的步骤包括:
Eureka-Server - 注册中心服务端的搭建,必须创建一个独立的微服务!
在cloud-demo父工程下,创建一个子模块:
1. 创建项目,引入eureka-server依赖,引入Spring Cloud为eureka提供的starter依赖:spring-cloud-starter-netflix-eureka-server
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
2. 编写启动类,添加@EnableEurekaServer注解(EurekaServer自动装配的开关)
package cn.itcast.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* 手动编写启动类
* 给Eureka-Server服务编写一个启动类
* 注意:一定要添加@EnableEurekaServer注解,开启Eureka的注册中心功能
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
3. 添加application.yml文件,在application.yml中配置eureka地址 或 编写下面的配置:
server:
port: 10086 #服务的端口,随便给定一个端口号
spring:
application:
name: eureka-server #服务的名称(Eureka的服务名称)
eureka:
client:
service-url: #Eureka的地址信息(如果是集群,则地址之间用逗号隔开,现在是单机)
defaultZone: http://127.0.0.1:10086/eureka
启动微服务,然后在浏览器访问:http://127.0.0.1:10086
看到下面Eureka的管理页面就算是成功了:
下面,我们将服务提供者注册到eureka-server注册中心中去,服务注册需要经过两个步骤:
1. 在服务提供者的项目中引入eureka-client依赖,引入spring-cloud-starter-netfix-eureka- client的依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
2. 在application.yml文件中,添加服务名称以及配置eureka地址,编写下面的配置:
spring
application:
name: user-service #服务的名称
eureka:
client:
service-url: #Eureka的地址信息(如果是集群,则地址之间用逗号隔开,现在是单机)
defaultZone: http://127.0.0.1:10086/eureka
将服务消费者的信息也注册到EurekaServer中去,服务列表验证:
我们可以将一个服务多次启动,模拟多实例部署,但为了避免端口冲突,需要修改端口设置
首先,复制原来的服务启动配置 - 复制配置:
然后,在弹出的窗口中,填写信息,配置启动项,注意重命名配置新的端口号,避免端口冲突(重新配置一个端口以及修改服务名称):
添加成功:
查看Erueka-Server的管理页面:
下面,我们将服务消费者的逻辑修改:向eureka-server注册中心拉取服务提供者的信息,实现服务发现或服务拉取。
1. 在服务消费者的pom文件中,引入eureka-client依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
2. 配置文件
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: order-service #服务的名称
eureka:
client:
service-url: #Eureka的地址信息(如果是集群,则地址之间用逗号隔开,现在是单机)
defaultZone: http://127.0.0.1:10086/eureka
3. 服务的拉取和负载均衡
服务调用者去eureka-server注册中心中拉取服务提供者的实例列表,并且实现负载均衡,虽然服务提供者有多个实例,但真正发起调用时只需要知道一个实例的地址,因此服务调用者必须利用负载均衡算法,从多个实例中挑选一个去访问。
3.1 修改服务消费者的业务代码,修改访问的URL路径,用服务提供者的服务名称代替IP以及端口 从而实现远程调用:
3.2 在服务消费者项目的启动类中,给RestTemplate这个Bean添加负载均衡注解 - @LoadBalanced注解:
重启服务消费者,清空服务提供者实例的控制台日志,看看服务消费者到底访问的是哪一个服务提供者的实例?
Spring会自动帮助我们从eureka-server端,根据服务名称来获取服务实例列表,而后完成负载均衡!
在刚才,我们在RestTemplate的Bean上面添加了@LoadBalanced注解,即可实现负载均衡功能,这是什么原理呢?
Spring Cloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的。
Ribbon拦截我们的RestTemplate请求并获取到请求所指定的对应的服务名称(serviceId)之后,它要根据服务名称去找到Eureka-Server注册中心去进行服务拉取或服务发现,然后在对拉取到的服务列表(ServiceList)做负载均衡。
为什么我们在代码当中定义的URL地址输入的是service服务名称,而不是真实的IP和端口,但却能远程调用,访问成功呢?
ClientHttpRequestInterceptor:客户端HTTP请求的拦截器
可以看到这里的interceprt()方法,拦截了用户或客户端的HttpRequest请求,然后做了几件事:
RibbonLoadBalancerClient
继续跟入RibbonLoadBalancerClient对execute()方法的具体实现:
在刚才的代码中,可以看到获取服务是通过一个getServer()方法来做的负载均衡,因此继续跟入getServer()方法的详情,还是在RibbonLoadBalancerClient当中:
继续跟踪源码chooseServer()方法:
我们来看它的重写或看它的实现方法,选择BaseLoadBalancer:
点击看看IRule的源码,I应该指的就是Interface - 接口:
IRule接口决定了负载均衡的策略Ribbon的负载均衡策略/规则是由一个叫做IRule的接口来定义的,IRule接口就定义了负载均衡的策略(IRule接口决定了负载均衡的策略),并且IRule接口有很多不同的实现类,它的每一个实现类都是一种规则:
IRule接口的继承关系图IRule接口默认的实现或默认的负载均衡规则是ZoneAvoidancerRule,是一种轮询方案 - Round Robin!
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
RetryRule | 重试机制的选择逻辑 |
注意:一般情况下使用默认的负载均衡策略或方案就可以了,不需要做修改。
1. 代码方式:在服务(消费者)的启动类当中,定义一个新的IRule的Bean注入到Spring容器:
2. 配置方式:在application.yml配置文件当中,来添加新的配置也可以修改负载均衡的规则:
# 给某个微服务配置负载均衡规则
serviceName: #服务提供者的服务名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.IRule接口的具体实现类的全类名 # 负载均衡规则
Eg:
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient - 客户端负载平衡器,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面的配置开启饥饿加载:
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: serviceName #指定饥饿加载的服务名称,指定对该服务开启饥饿加载
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: #如果有多个服务,则需要换行指定饥饿加载的服务名称
- serviceName1
- serviceName2
- serviceName3
懒加载和饥饿加载是两个相对的概念,它们在计算机领域中有着不同的含义!
懒加载(Lazy Loading)
- 懒加载指的是在需要的时候才进行加载或初始化操作。
- 懒加载的优点是可以节省资源,在实际需要时再进行加载,避免不必要的性能开销;缺点是第一次访问时,请求时间会很长。
饥饿加载(Eager Loading)
- 饥饿加载指的是系统在应用程序启动或对象创建时即进行加载或初始化所有需要的资源或对象。
- 饥饿加载的优点是可以减少后续操作的等待时间;缺点是可能会占用较多的初始资源。
因此,懒加载和饥饿加载时两种加载策略!
补充:SpringMVC的容器 - dispactherServlet的初始化也是懒加载!
Nacos是基于Java语言去实现的,Nacos的默认端口是8848!
Nacos是Spring Cloud Aliaba的组件,而Spring Cloud Alibaba也遵循Spring Cloud中定义的服务注册、服务发现规范,因此使用Nacos和使用Eureka对于微服务来说,并没有太大区别,主要差异在于:
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.2.5.RELEASE
pom
import
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
在服务的application.yml配置文件当中添加Nacos地址:
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务端地址
点击详情,还可以查看到服务的详细信息!
记得启动数据库,打开浏览器刷新:
一个服务可以包含多个实例,也就是一个服务可以多实例部署,但是如果将多个实例部署在同一个机房,则会存在单点故障问题,就好比把多个鸡蛋放在一个篮子里,为了解决单点故障问题,我们会将一个服务的多个实例部署到多个不同地域机房或服务器 => 也就是搞集群,这样就可以解决单点故障问题(容灾),而Nacos就是将同一机房或服务器内的多个实例划分为一个集群,也就是说,同一个服务可以包含多个集群(集群按地域划分),每个集群下可以有多个实例,形成服务分级存储模型,所以在Nacos的服务分级存储模型中,一级是服务 => 二级是集群 => 三级是实例:将同一个服务的多个实例分成多个集群,每个集群下又包含多个实例,集群就是一个地域的划分,如图:
由于本地局域网内的访问,它的距离比较短,所以速度比较快,延迟比较低,而跨集群的访问调用,延迟是非常高的!
因此微服务互相访问调用时,应该尽可能选择本地集群的服务,尽可能访问同一个集群的实例,当跟本地集群不可用或不可访问时,再去访问其它集群,例如:
修改application.yml,添加集群配置:
spring:
cloud:
nacos:
server-addr: localhost:8848 # Nacos服务端地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置,例如:HZ代指杭州
添加完集群配置后,重启该服务即可!
我们在Nacos控制台看到下面结果:
添加属性(配置端口号以及实例的集群属性):
-Dserver.port=8083 -Dspring.cloud.nacos.discovery.cluster-name=SH
然后启动该项目即可!
1. 给服务调用者配置集群信息
spring:
cloud:
nacos:
server-addr: localhost:8848 # Nacos服务端地址
discovery:
cluster-name: SH # 配置集群名称,也就是机房位置,例如:SH代指上海
user-service: # 给某个微服务配置负载均衡规则,这里是user-service服务
ribbon: # NacosRule的负载均衡规则优先会寻找与自己同集群的服务
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
接着重启服务调用者,去Nacos控制台看看:
实际部署中会出现这样的场景:
在Nacos控制台,找到服务的实例列表,点击编辑,即可设置或修改服务实例的权重:
注意:如果权重修改为0,则该实例永远不会被访问!