SpringBoot专注于快速方便的开发单个个体微服务。
SpringCloud是关注全局的微服务协调整理治理框架,他将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局所、决策竞选、分布式会话等等集成服务
SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖的关系。
总结:SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注与全局的微服务治理框架。
微服务架构是一种架构模式或者说是一种架构风格,他提倡将单一应用程序划分成一组小的服务,每个服务运行在其独立的自己的进程中,服务之间互相协调、互相配合,为用户提供最终价值。服务之间采用轻量级的通信机制互相沟通(Double是通过RPC远程通常是基于HTTP的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以拥有自己独立的数据库。
通俗的讲:
微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务,一个无服务做一件事,从技术角度看就是一种小而独立的处理过程,类似进程概念,能够单独启动或销毁,拥有自己独立的数据库。
微服务
微服务是一种软件开发方法,他将一个大型应用程序分解为许多小型、独立的服务。每个服务负责解决特定的问题或执行特定的工能。这些服务可以单独部署、维护和更新,而不影响其他服务。它具有可伸缩性、可维护性、可组合性等特点。通常采用轻量级通信机制,如HTTP REST API 或消息队列进行通信。
微服务架构
微服务架构是一种架构模式,它基于微服务的原则来设计和实现大型应用程序。在微服务架构中,应用程序有多个独立的服务组成,这些服务可以按照业务功能、领域或团队进行划分。每个服务可以独立地开发、部署和扩展,他们通过轻量级协议进行通信和协作。
优点
模块化:微服务架构将一个大的应用分为多个小的服务,每个服务都专注于完成一项任务,这种模块化使得微服务更容易理解和维护。
灵活性:微服务架构使用REST API与服务之间进行交互和通信,这使得微服务之间的协作更加灵活,可以根据需求选择不同的编程语言和框架来编写服务。
持续部署:微服务架构允许持续部署独立的服务,这样就可以更加频繁地发布新功能,同时也更容易进行灰度发布。
隔离性:微服务之间通过REST API相互隔离,这样即使其中一个服务发生故障,也不会影响其他服务。
缺点
复杂性:由于系统被分解成许多微服务,在开发和测试阶段容易发生依赖冲突,这会增加项目的复杂性
运维难度:微服务架构使得技术栈更加庞大复杂,运维工程师需要监控更多的服务,这增加了运维的难度
通信开销:微服务之间通过网络进行通信,这必然带来一定的性能开销,特别是高并发场景下会更加明显
数据一致性:微服务架构下,不同的服务有各自的数据库,这可能导致数据一致性问题,需要借助分布式事务来解决
重复开发:微服务架构可能会有许多重复的业务逻辑在不同的服务中实现,这增加了开发工作量
微服务条目 | 落地技术 |
---|---|
服务发现 | 用于服务注册和发现,比较常用的有 Eureka、Consul、Zookeeper等 |
服务调用 | 用于服务之间的调用,常用的有REST、RPC、gRPC等。其中RPC框架有Dubbo、Thrift、SpringCloud OpenFeign等。 |
负载均衡 | 用于服务调用的负载均衡,常用的有Ribbon、Nginx等 |
服务网关 | 用于同一 entrance,常用的有Zull、Gateway |
配置中心 | 用于集中管理配置,常用的有Spring Cloud Config、Apollo、Archaius、阿里的Diamond |
熔断和降级 | 用于保护服务不被过载,常用的有 Hystrix、Resilience4j、Envoy |
服务监控 | 用于服务监控,常用的有 SpringBoot Admin、Prometheus、Zipkin |
分布式消息队列 | 用于服务解耦和异步通信,常用的有RabbitMQ、Kafka、RocketMQ |
API文档 | 用于同一管理API文档,常用的有Swagger、SpringFox |
分布式日志 | 用于同一管理日志,常用的有ELK |
持续集成 | 用于自动化构建和部署,常用的有 Jenkins、GitLab CI |
容器化部署 | 用于快速高效的部署和扩展,常用的有 Docker、Kubernetes |
SpringCloud,基于SpringBoot提供了一套微服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于Netflix的开源组件做高度抽象封装之外,还有一些选型中立的开源组件。
SpringCloud利用SpringBoot的开发遍历性巧妙地简化了分布式系统基础设施的开发,SpringCloud为开发人员提供了快速构建分布式系统的一些工具,包括**配置管理、服务发现、断路器、路由、微代理、时间总线、全局锁、决策竞选、分布式会话**等等,它们都可以用SpringBoot的开发风格做到一建启动和部署。
一句话就是:SpringCloud=分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶。
官方网址
参考书:
API说明
SpringCloud中国社区
SpringCloud中文网
RestTemplate是Spring中的一个HTTP客户端,用于简化RESTful Web Service的调用。他提供了一组易于使用的方法,可以发送HTTP请求并处理HTTP响应。使用RestTemplate,可以使用HTTP协议发送GET、POST、PUT、DELETE等请求,并将响应映射为Java对象。
RestTemplate提供了多种方法来发送HTTP请求,例如:
RestTemplate还提供了一些其他的方法,例如:
RestTemplate官网
使用restTemplate访问restful接口非常简单,(URL,requsetMap,ResponseBean.class)这三个参数分别代表REST请求地址、请求参数、HTTP响应转换被转换成的对象类型
Eureka是Netflix的一个子模块,也是核心模块之一,现在已经成为了SpringCloud等微服务核心组件之一。Eureka的核心原则是基于REST的架构和AP(可用性和分区容错性)设计理念。Eureka通过心跳机制来检测服务实例的存活状态,并通过注册中心来管理服务实例的注册和发现。当一个服务实例出现故障或者下线时,Eureka会自动将其从注册中心中删除,并通知其他的服务实例重新选择可用的服务实例来进行负载均衡。
Eureka主要有两个组件组成:Eureka Server 和 Eureka Client。Eureka Server是服务注册中心,用于管理服务实例的注册和发现;而Eureka Client 是服务提供者或服务消费者,用于向Eureka Server注册自己,并从Eureka Server获取可用的服务实例信息。
Eureka Server原理
Eureka Server采用了AP设计理念,即可用性和分区容错性。在Eureka Server中,有两个重要的组件:Registry 和 Peer
Eureka Server 在启动时,会像Peer结点发送心跳请求,已加入到服务注册中心的集群中。当服务实例启动时,它会向Eureka Server发送注册请求,Eureka Server会将服务实例的注册信息存储到Registry中。当服务实例出现故障或下线时,它会向Eureka Server发送取消注册请求,Eureka Server会将服务实例的注册信息从Registry中删除。
Eureka Client原理
Eureka Client 是服务提供或服务消费者,它用于向Eureka Server注册自己,并从Eureka Server获取可用的服务实例信息。在Eureka Client中,有两个重要的组件:DiscoveryClient 和 Heartbeat。
当服务实例启动时,它会向Eureka Server发送注册请求,Eureka Server会将服务实例的注册信息存储到Registry中。同时,服务实例会定期向Eureka Server发送心跳请求,以保证自己的健康状态。当服务实例出现故障或下线时,它会向Eureka Server发送取消注册请求,Eureka Server会将服务实例的注册信息从Registry中删除。
当服务消费者需要调用某个服务时,它会向Eureka Server发送查询请求,以获取可用的服务实例列表。Eureka Client会从获取到的服务实例列表中选择一个可用的服务实例,并通过负载均衡算法进行负载均衡。如果选择的服务实例出现故障或下线,Eureka Client会从服务实例列表中选择另一个可用的服务实例。
Eureka Server:提供服务注册和发现的功能。客户端向Eureka Server注册自己提供的服务,然后也从Eureka Server获取其他服务的信息。
Service Provider:就是提供各种服务的微服务。Service Provider在启动时,会向Eureka Server注册自己,告知Eureka我提供了什么服务。
Service Consumer:服务消费者。需要调用其他服务的微服务。服务消费者在启动时,也会从Eureka Server获取所有已注册的服务,然后根据需要去调用服务。
pom.xml 文件:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eureka-serverartifactId>
dependency>
application.yml 文件:
server:
port: 7001
eureka:
instance:
hostname: localhost # Eureka服务端的实例名称
client: # false表示不向注册中心注册自己
register-with-eureka: false # false表示自己端就是注册中心,我的职责是维护服务实例,并不需要检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # Eureka 客户端的默认注册中心地址,设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
主启动类:
@SpringBootApplication
@EnableEurekaServer // 启用 Eureka Server 的功能,接收其他微服务注册进来。
public class EurekaServer7001_App {
public static void main(String[] args) {
SpringApplication.run(EurekaServer7001_App.class,args);
}
}
启动服务后,浏览器访问localhost:7001
如果出现下图则说明成功:
pom.xml 文件:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
application.yml 文件
eureka:
client: # 客户端注册进eureka服务列表内
service-url:
defaultZone: http://localhost:7001/eureka
主启动类:
@SpringBootApplication
@EnableEurekaClient // 启用 Eureka Client 的功能。本服务启动后会自动注册进Eureka服务中
public class DeptProvider8001_App {
public static void main(String[] args) {
SpringApplication.run(DeptProvider8001_App.class,args);
}
}
启动服务端,再启动客户端,浏览器发送localhost:7007
请求,如下图:
可以看到有一个服务注册进来了,而这个服务的名称正是注册的微服务的应用名称即对外暴露的微服务名称。
在客户端pom文件中加入一下配置:
eureka:
instance:
instance-id: microservicecloud-dept8001 # 自定义服务名名称信息
prefer-ip-address: true # 访问路径可以显示IP地址
其他实例配置:
在客户端pom文件中加入以下配置:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
父工程pom文件中加入以下配置:
<build>
<finalName>microservicecloudfinalName>
<resources>
<resource>
<directory>src/main/resourcesdirectory>
<filtering>truefiltering>
resource>
resources>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-resources-pluginartifactId>
<configuration>
<delimiters>
<delimit>$delimit>
delimiters>
configuration>
plugin>
plugins>
build>
在客户端的yml文件中加入以下配置:
info: # 指定应用程序的元数据。
app.name: atpl-microservicecloud # 指定应用程序的名称,可以用来标识应用程序在整个系统中的作用
company.name: www.atpl.com # 指定应用程序所属的公司名称
build.artifactId: ${project.artifactId} # 指定应用程序的构件ID,通常是Maven项目中的artifactId
build.version: ${project.version} #指定应用程序的版本号,通常是Maven项目中的version
访问在上一小节图中左下角出现的IP地址(192.168.1.16:8001/info)
可以看到应用的元数据。
对于注册进Eureka的服务,可以通过服务发现来获得该服务的信息。
只需要在服务主启动类上加上@EnableDiscoveryClient
注解即可开启服务发现功能。
在8001的服务端的DeptController类中加入以下方法:
/**
* 服务发现,获取当期应用程序的注册信息
*
* @return
*/
@GetMapping("/discoveryServer")
public Object discoveryClientList() {
List<String> list = discoveryClient.getServices();
logger.info("-----微服务个数------>:" + list);
List<ServiceInstance> instances = discoveryClient.getInstances("MICROSERVICECLOUD-DEPT");
if (instances.size() > 0 && instances != null) {
for (ServiceInstance instance : instances) {
logger.info("----服务实例信息----> 【 IP:" + instance.getHost() +", 端口号:"+ instance.getPort() +", URI:"+ instance.getUri() +", 服务ID:"+ instance.getServiceId()+", 元数据"+instance.getMetadata()+" 】");
}
}
return this.discoveryClient;
}
启动8001,浏览器访问http://localhost:8001/dept/discoveryServer
可以获得当前应用服务注册信息
也可以通过8001的客户端:80 进行访问,在客户端的DeptConsumerController
类中加入以下代码:
/**
* 获取当前应用程序的注册信息
*
* @return
*/
@RequestMapping("/discovery")
public Object disCoveryServer() {
return restTemplate.getForObject(REST_URL_PRIFIX + "/dept/discoveryServer", Object.class);
}
启动80微服务,浏览器发送http://localhost/dept/consumer/discovery
请求:
都可以获得服务的注册信息。
上图中出现 ’EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.‘意思是:Eureka可能会声明已经不存在的实例,刷新数小于阈值时,为了安全起见不会剔除过期的实例。
Eureka的自我保护机制是为了防止误杀服务。当注册中心发生故障,服务不能够正常的发送心跳请求,但是服务运行正常,默认情况下,Eureka会将超过90s未发送心跳请求的服务进行移除。这样做明显不合理,所以Eureka提供了一个自我保护模式。自我保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护,一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务 。
首先说一下Eureka的默认阈值为:85%(90秒)
比如目前有10个微服务,只有8个有心跳反应时,(8/10=80%<85%)Eureka就会开启保护机制,过期的实例不会立马剔除。并且出这个紧急警告,在搭建Eureka Server时,比如我们搭建了2个Eureka Server,并且禁止自注册,Eureka Server自身算一个服务,那么其中任意一个Eureka,只能获得一个心跳,1/2=50%。那么也会出现这个警告。
这种情况如果未禁止自注册的话是不会出现的,因为本机不会有什么网络问题,肯定是百分百。
博主这里测过,只有当我开启7台及以上的Eureka Server服务(关闭Eureka Server自注册)的时候,才不会出这个警告。
因为 5/6≈83.3%<85% 6/7≈85.7%>85%。
那么当不想有这个红色警告是,本机自测可以关闭Eureka保护配置。生产环境下不要关。
在application.yml文件中配置:
server:
enable-self-preservation: false #关闭注册中心的自我保护机制,保证不可用服务被及时提出
eviction-interval-timer-in-ms: 2000 #用于驱逐(移除)的间隔计时器
测试:浏览器访问localhost:7001
,很明显说自我保护已经关闭。
客户端服务配置文件application.yml
eureka:
instance:
prefer-ip-address: true
instance-id: provider8001
#Eureka服务端在收到最后一次心跳后等待时间上限,默认为90秒,超时将服务提出,这里值为2,意思是如果在2秒内没有收到心跳,注册中心将认为该服务实例已过期,可能会将该实例从注册表中清除
lease-expiration-duration-in-seconds: 2
#Eureka客户端向服务端发送心跳的时间间隔,默认30秒,意思是每个1秒发送心跳,以保持自己的活跃状态
lease-renewal-interval-in-seconds: 1
假设客户端服务宕机,看一下测试结果:可以看到客户端实例已经被清除掉
集群是由多台计算机组成的一个整体,他们作为一个整体向用户提供一组网络资源,这些单个的计算机系统就是集群的结点(node)。集群提供了一下关键的特性:
简单理解集群注册原理:
互相注册,相互守望。
在7001、7002、7003的application.yml文件中配置服务注册中心的信息:
7001:
eureka:
instance:
hostname: eureka7001.com # Eureka服务端的实例名称
client: # false表示不向注册中心注册自己
register-with-eureka: false # false表示自己端就是注册中心,我的职责是维护服务实例,并不需要检索服务
service-url:
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
7002:
eureka:
instance:
hostname: eureka7002.com # Eureka服务端的实例名称
client: # false表示不向注册中心注册自己
register-with-eureka: false # false表示自己端就是注册中心,我的职责是维护服务实例,并不需要检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/
7003:
eureka:
instance:
hostname: eureka7003.com # Eureka服务端的实例名称
client: # false表示不向注册中心注册自己
register-with-eureka: false # false表示自己端就是注册中心,我的职责是维护服务实例,并不需要检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
即在三个Eureka服务中心都要相互注册另外两个服务为服务注册中心集群节点
启动三个Eureka服务,他们就可以相互注册另外两个为集群节点,新城服务注册中心集群
其他微服务在注册时只需要注册到集群中的任意一个节点即可,Eureka会同步服务信息。
官网地址
Ribbon是Netflix的开源项目,他提供了一组云中间件开发组件,主要用于通过HTTP和TCP协议进行服务之间的调用、负载均衡以及故障处理。
Ribbon主要包括一下几个核心功能:
在微服务分布式架构中,Load Balancer(负载均衡器)是一种将用户的请求平摊的分发到多个服务实例上,从而达到系统的高可用性(HA)的技术。Load Balancer可以根据不同的负载均衡策略来选择服务实例,例如轮询、随机、最小连接数。常见的负载均衡有软件Nginx、LVS,硬件F5。
集中式LB是指在服务器群集前(服务的消费方和提供方之间)设置一个独立的LB设备,负责接收用户请求,然后根据负载均衡算法将请求转发给后端的不同服务器。在集中式Load Balancer架构中,用户只需要知道Load Balancer的地址,然后将请求发送到改地址即可,而无需关心后端服务实例的具体地址。常见的集中式负载均衡器有硬件F5,软件Nginx。
优点:部署简单、支持各种负载均衡算法
缺点:LB设备单点故障、性能和扩展能力受LB设备限制
进程内LB是一种在应用程序内部实现的负载均衡机制,不依赖外部独立的负载均衡设备,负载均衡逻辑内置在应用进程内。应用进程之间通过进程内通信来实现请求的负载分发,如RPC调用、消息队列等。
Ribbon就是一个进程内LB
优点:去中心化、避免了集中式负载均衡的单点故障问题、很容易通过增加应用进程实例来进行扩展
缺点:由于负载均衡在进程内增加了应用的复杂度,需要处理RPC或队列、增加了开发难度、无法做全局优化
修改80客户端微服务模块:
pom文件中加入Ribbon相关以来坐标
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
在application.yml文件中加入以下内容:
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://erureka7001.com:7001,http://eureka7002.com:7002,http://eureka7003.com:7003
在ConfigBean中的restTemplate方法上加上@LoadBalanced
注解,如下:
@Bean
@LoadBalanced // 表示这个RestTemplate对象开启了负载均衡功能
public RestTemplate restTemplate(){
return new RestTemplate();
}
启动类加上@EnableEurekaClient
注解
@SpringBootApplication
@EnableEurekaClient // 开启Eureka客户端
public class DeptConsumer80_APP {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer80_APP.class,args);
}
}
修改DeptConsumerController类:
在这之前客户端能够访问服务提供端都是绕过了Eureka。什么意思呢?如下面代码所示,访问路径是写死的。
@RequestMapping("/dept/consumer")
@RestController
public class DeptConsumerController {
/**
* 服务提供端的IP
*/
public static final String REST_URL_PRIFIX = "http://localhost:8001";
// ....
}
修改后:
@RequestMapping("/dept/consumer")
@RestController
public class DeptConsumerController {
/**
* 服务提供端的IP
*/
//public static final String REST_URL_PRIFIX = "http://localhost:8001";
// 使用微服务的名字
public static final String REST_URL_PRIFIX = "http://MICROSERVICECLOUD-DEPT";
}
先启动三个Eureka服务端(注册中心:7001/7002/7003),再启动8001微服务,将8001注册进注册中心,然后在启动80微服务,80微服务在yml文件中设置了本身不注册进注册中心,然后在浏览器发送请求进行测试。
浏览器输入http://localhost/dept/consumer/get/list
出现下图即表示成功。
Ribbon和Eureka整合后Consumer可以直接调用服务而不用在关心地址和端口号
关键之处是:
使用Ribbon的过程大致如下:
在SpringCloud中,@LoadBalanced
是一个重要的注解,它用于实现客户端负载均衡。这个注解通常与RestTemplate 或 WebClient 一起使用。下面是一些详细的解释。
@LoadBalanced
注解的主要用途是提供一种简单的方式来将负载均衡策略应用到指定的服务消费者。换句话说,它允许消费者在多个服务提供者实例之间分配请求,而不需要明确知道他们的存在。
当你在RestTemplate或WebClient bean上使用@LoadBalanced
注解时,SpringCloud会创建一个特殊的代理,该代理将拦截所有的HTTP(S)请求。这个代理会使用Ribbon或其他的客户端负载均衡器来选择一个服务实例,并将请求重定向到这个实例。
下面是一个使用@LoadBalanced的简单实例:
@Configuration
public class MyConfiguration {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在上面的代码中,@LoadBalanced
注解用于一个新创建的RestTemplate bean。现在当你使用这个RestTemplate发送一个HTTP请求时,请求会被发送到通过Ribbon选择的服务实例。例如,你可以将请求发送到‘my-service’,而不需要知道具体的URL:
String response = restTemplate.getForObject("http://my-service/some-endpoint", String.class);
在这种情况下,Ribbon会从Eureka或其他服务发现系统中获取‘my-service’的所有实例,并选择一个来发送请求。
注意:在新的SpringCloud版本中,RestTemplate已经被弃用,推荐使用WebClient。
参考微服务提供者dept-8001
创建dept-8002/dept-8003微服务
#建库脚本
CREATE DATABASE `springcloud01`
CREATE DATABASE `springcloud02`
CREATE DATABASE `springcloud03`
#建表脚本,需要在三个库中都执行一下
CREATE TABLE `dept` (
`deptNo` int(11) NOT NULL AUTO_INCREMENT,
`deptName` varchar(255) DEFAULT NULL,
`dbSource` varchar(255) DEFAULT NULL,
PRIMARY KEY (`deptNo`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
#插入数据脚本,只需要改一下‘springcloud03’
INSERT INTO `dept` ( `deptName`, `dbSource`) VALUES ( '张三', 'springcloud03');
INSERT INTO `dept` ( `deptName`, `dbSource`) VALUES ( '李四', 'springcloud03');
INSERT INTO `dept` ( `deptName`, `dbSource`) VALUES ( '王五', 'springcloud03');
INSERT INTO `dept` ( `deptName`, `dbSource`) VALUES ( '赵六', 'springcloud03');
8003也是同样的方式修改。
启动3个eureka集群
启动3个dept微服务进行自测
分别自测三个微服务提供端
发送8001的请求
发送8002的请求
发送8003的请求
可以看到每次点击刷新按钮,都会获取到不同的相应数据。这就是Ribbon的负载均衡(轮询算法)。
根据特定算法从服务列表中选取一个要访问的服务。
默认情况下会使用轮询算法分发请求,即依次向可用服务实例发送请求。
通过实现Rule接口,可以自定义请求路由规则。主要有以下几种:
所以通过实现自定义的IRule,我们可以按照自己的需求实现复杂的请求路由策略。
IRule很重要的一点是它决定着服务调用的稳定性和性能。选择一个合适的路由策略可以有效地分散负载,提高吞度量和成功率。
ConfigBean.java配置类中定义算法
/**
* 更换算法
*/
@Bean
public IRule myRule(){
// 随机算法
return new RandomRule();
}
重启80服务,发送请求
可以看到每次刷新,所请求的服务是随机的。
特别地(重试规则):
/**
* 更换算法
*/
@Bean
public IRule myRule(){
// 随机算法
//return new RandomRule();
// 重试算法
return new RetryRule();
}
重启80 服务,此时我们将8001服务提供短down掉,来模拟服务挂掉的场景。(如果没有down掉的服务的话,会按照某个算法,一般是轮询,来分发请求)
发送80客户端请求:
可以看到,不在去找8002的服务实例了。
其他的分发算法不在一一演示。有兴趣的伙伴可以自己测试一下,来加深印象!
只需要按这个格式去定义就行了。
现在需求是不使用Ribbon提供的负载均衡算法,需要开发人员自定义。
在开始前先了解一下@RibbonClient
注解,该注解是用于自定义Ribbon客户端配置。在启动该微服务的时候就能去加载我们的自定义的Ribbon配置类,从而是配置生效
@RibbonClient(name = "serviceName", configuration = MyClientConfig.class)
案例1:使用最少连接算法实现自定义Ribbon负载均衡策略
算法实现:
public class CustomRule extends AbstractLoadBalancerRule {
// 声明一个map类型的成员变量,用来存放每个服务器的连接数
private ConcurrentHashMap<Server, AtomicInteger> connectionCountMap;
//
public CustomRule() {
connectionCountMap = new ConcurrentHashMap<>();
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// 不需要额外的配置,留空
}
@Override
public Server choose(Object key) {
// 获取所有可用服务器列表
List<Server> servers = getLoadBalancer().getAllServers();
// 存储连接数最少的服务器
Server leastConnectionsServer = null;
// 初始化整形最大值,记录当前最少连接数
int leastConnections = Integer.MAX_VALUE;
// 遍历服务器列表
for (Server server : servers) {
// 根据服务器从connectionCountMap中获取对应的连接数。
AtomicInteger connections = connectionCountMap.get(server);
// 连接数为空
if (connections == null) {
// 初始化连接数为0
connections = new AtomicInteger(0);
// 将服务其和对应的连接数存入map中
connectionCountMap.put(server, connections);
}
// 获取当前服务器的连接数
int currentConnections = connections.get();
// 如果当前连接数小于最小连接数
if (currentConnections < leastConnections) {
// 更新最小连接数为当前连接数
leastConnections = currentConnections;
// 更新连接数最小的服务器为当前服务器
leastConnectionsServer = server;
}
}
// 如果当前连接数最小的服务器不为空
if (leastConnectionsServer != null) {
// 将连接数最少的服务器连接数加一
connectionCountMap.get(leastConnectionsServer).incrementAndGet();
}
// 返回连接数最小的服务器
return leastConnectionsServer;
}
}
案例2:使用随机规则,只不过要求每个服务被调用5次
public class RewritePolling extends AbstractLoadBalancerRule {
private int total = 0; // 记录已调用次数
private int currentIndex = 0; // 当前选择的服务实例索引
@Override
public Server choose(Object key) {
// 获取负载均衡器
ILoadBalancer lb = getLoadBalancer();
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
// 检查线程是否被中断
if (Thread.interrupted()) {
return null;
}
// 获取可到达的服务实例列表
List<Server> upList = lb.getReachableServers();
// 获取所有的服务实例列表
List<Server> allList = lb.getAllServers();
// 服务实例总数
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
// Github源码:不是每个服务调用5次
//int index = chooseRandomInt(serverCount);
//server = upList.get(index);
if (total < 5) {
// 选择当前索引对应的服务实例
server = upList.get(currentIndex);
// 让调用次数加一
total++;
} else {
// 调用次数大于了5次后,将调用次数置为0
total = 0;
// 调用下一个服务实例
currentIndex++;
// 当索引超出范围时,重新从头开始选择
if(currentIndex >= upList.size()){
currentIndex = 0;
}
}
if (server == null) {
// 放弃当前线程的CPU时间片,让其他线程执行
Thread.yield();
continue;
}
// 检查服务实例是否存活
if (server.isAlive()) {
return (server);
}
server = null;
Thread.yield();
}
return server;
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// 初始化操作
}
}
将自定义算法应用到负载均衡器中
@Bean
public IRule ribbonRule(){
// 最少连接算法
//return new CustomRule();
// 随机算法,每个服务被调用5次
return new RewritePolling();
}
主启动类上加上@RibbonClient
注解
@RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration = ConfigBean.class) // 加载自定义Ribbon客户端配置
public class DeptConsumer80_APP {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer80_APP.class,args);
}
}
注意点:@RibbonClient
注解和负载均衡规则类需要位于不同的包中,主要有以下两个原因:
@RibbonClient
所在的类会优先初始化。它用来配置Ribbon客户端,指定负载均衡规则类。@RibbonClient
和负载均衡规则类位于同一包中,在Spring初始化阶段会产生循环依赖。@RibbonClient
初始化时需要加载负载均衡规则类,但是负载均衡规则类还没有初始化。所以为了避免初始化顺序问题和循环依赖,@RibbonClient
所在类的包不能与负载均衡规则类所在的包相同。
简单来说就是:
@RibbonClient
负责配置ribbon客户端以及制定负载均衡规则。官方地址
源码地址
Feign是一个声明式的Web服务客户端,使用Feign能让编写Web服务客户端更加简单,他的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
说白了就是Feign是一个Web服务客户端,是的编写Web服务客户端变得非常容易。
只需创建一个接口,然后在上面添加注解即可。
开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用SpringCloud Ribbon时,自动封装服务调用客户端的开发量。
Feign继承了Ribbon
利用Ribbon维护了注册中心的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
Feign通过接口的方法调用Rest服务,(之前是Ribbon + RestTemplate),该请求发送给Eureka注册中心,通过Feign直接找到服务接口,由于在进行服务调用的时候融合了Ribbon技术,所以也支持负载均衡。
导入依赖坐标
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
dependency>
定义接口
@RequestMapping("/dept")
@FeignClient(value = "MICROSERVICECLOUD-DEPT")
public interface DeptClientService {
@PostMapping("/add")
boolean add(Dept dept);
@GetMapping("/get/{id}")
Dept get(@PathVariable("id") Long id);
@GetMapping("/get/list")
List<Dept> list();
}
客户端通过接口调用
@RestController
public class DeptConsumerController {
public static final Logger logger = LoggerFactory.getLogger(DeptConsumerController.class);
@Autowired
DeptClientService deptClientService;
@PostMapping("/add")
public boolean add(@RequestBody Dept dept){
boolean flag = deptClientService.add(dept);
if(flag){
logger.info("增加成功");
}
return flag;
}
@GetMapping("/get/{id}")
public Dept get(@PathVariable("id") Long id){
return deptClientService.get(id);
}
@GetMapping("/get/list")
public List<Dept> list(){
return deptClientService.list();
}
}
测试:POSTMAN发送请求http://localhost/get/list
为什么接口上加了@FeignClien
注解就可以访问到远程的服务了呢?
本案例中@FeignClient(value = "MICROSERVICECLOUD-DEPT")
表示DeptClientService接口是一个Feign客户端代理接口,他将用于访问名为“MICROSERVICECLOUD-DEPT”的远程服务。通过该注解,Feign将根据接口定义自动创建一个代理类实现,用于与远程服务进行通信。我这里调用的是DeptClientService接口中list()
方法,Feign将自动将请求发送到"MICROSERVICECLOUD-DEPT/dept/get/list" 路径上.
那么接口是如何找到远程服务的呢?
这是因为主启动类上标注了@EnableFeignClients(basePackages = {"com.atpl.springcloud"})
,该注解需要制定basePackages参数,用于指定要扫描的包路径,即扫描有@FeignClient
注解的接口,并为其生成代理实现。
官网地址
Hystrix断路器是一种用于构建韧性和弹性分布式系统的开源库。它最初由Netflix开发,并广泛应用于微服务架构中,以帮助应对服务间的故障和延迟。
Hystrix的主要目的是防止级联故障。在分布式系统中,一个服务的失败或延迟可能会导致对其依赖的其他服务也出现问题,从而引发级联故障。Hystrix通过实施断路器模式来解决这个问题。断路器模式允许在服务出现故障或延迟时,通过断路器的故障监控向调用方返回一个快速失败并提供备用的响应,而不是一直等待或继续尝试请求。
Hystrix还提供了其他功能,包括请求缓存、请求合并、线程池隔离和实时的监控和指标收集。这些功能使得开发人员能够更好地理解和管理服务之间的依赖关系,并在故障发生时提供更好的容错能力。
熔断机制是应对雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该结点微服务的调用,快速返回“错误”的响应信息(并返回一个事先定义好的备选响应或错误结果 FallBack)。当检测到该结点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。熔断机制的注解是@HystrixCommand
。
@HystrixCommand
注解解释:
该注解由Hystrix提供,用于实现服务的容错和隔离。**当使用该注解时,Hystrix会将被注解的方法封装在一个独立的线程池中,并未该方法设置一个超时时间。**如果该方法执行时或发生异常,Hystrix会自动触发服务降级,从而保证系统的可用性和稳定性。此外,@HystrixCommand
还支持实现服务熔断、请求缓存等功能。
@EnableCircuitBreaker
注解解释:
该注解由SpringCloud提供,用于断路器功能,通过监控和控制对远程服务的调用来实现容错和故障恢复的机制。该注解主要开启断路器功能,会自动为使用断路器模式的Bean创建代理,断路器代理会监控这些方法的调用情况。如果调用远程服务出现故障或超市,断路器会阻止对该服务的进一步调用。然后通过故障恢复的机制,调用备用服务或者返回预先定义的错误消息。
@GetMapping("/get/{id}")
// 一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注的fallBackMethod调用类中指定的方法
@HystrixCommand(fallbackMethod = "processHystrix_Get")
public Dept get(@PathVariable Long id) {
Dept dept = deptService.get(id);
if (dept == null) {
// 抛出自定义的异常
throw new CustomException("dept is null");
}
return dept;
}
// 服务熔断后处理方法 (fallback方法)
public Dept processHystrix_Get(@PathVariable Long id) {
// 处理服务异常或超时情况下的逻辑
return new Dept().setDeptNo(id).setDeptName("");
}
注意:
标记了@HystrixCommand
注解的额方法与fallback方法的返回值类型与参数列表不一定保持一致。需要遵守一些要求:
@HystrixCommand
方法的返回值类型兼容。比如,标记了@HystrixCommand
方法返回一个String类型,那么fallback方法也要返回一个String类型或其子类。 服务降级是一种应对系统故障、资源不足或异常情况下的策略,旨在保证系统的可用性和稳定性。当系统出现故障、性能下降或资源紧张时,服务降级通过临时屏蔽某些功能或切换到备用方案来减少对系统的负载,确保核心功能的正常运行,并提供用户友好的响应。
服务降级的主要目标是在面对异常情况时保持系统的可用性,避免因单个组件的故障而导致整个系统的崩溃。
fallback方法与业务逻辑方法在一起,或者有很多方法都需要服务熔断,那么每个方法都要加上@HystrixCommand
注解,这是一种高耦合的表现。
优化:所有要加@HystrixCommand
的方法都是来源于接口中,那么将接口进行熔断岂不是每个方法发不用再一一的添加@HystrixCommand
。
实现:
/**
* @author cpl
* 服务降级处理
* 将其从业务逻辑中拆分出来,降低耦合
*/
@Component
public class DeptClientServiceFallBackFactory implements FallbackFactory<DeptClientService> {
@Override
public DeptClientService create(Throwable cause) {
return new DeptClientService() {
@Override
public Dept get(Long id) {
return new Dept().setDeptNo(id).setDeptName("");
}
};
}
}
DeptClientService接口:
@FeignClient(value = "MICROSERVICECLOUD-DEPT",fallbackFactory = DeptClientServiceFallBackFactory.class)
public interface DeptClientService {
@GetMapping("/get/{id}")
Dept get(@PathVariable("id") Long id);
}
# 启用feign客户端熔断器
feign:
hystrix:
enabled: true
这样就将fallback方法与业务逻辑分离开,从而也实现服务降级。可以发现服务降级是在客户端实现的。另外客户端服务降级及时服务关闭也能看到提示信息而不会挂起耗死服务器。
服务熔断(Circuit Breaking)是一种用于处理故障的模式,它旨在防止故障在整个系统中扩散。当服务发生故障或超过一定阈值时,熔断器会打开并快速拒绝后续的请求,从而减轻对故障服务的压力。服务熔断的核心目标是保护系统免受不可用服务的影响,提高系统的容错性。一旦熔断器打开,可以采取一些措施,如返回缓存数据、返回默认值或使用备用服务等。
服务降级(Fallback)是一种在服务不可用或性能下降时提供有限但可用功能的策略。当服务发生故障、性能下降或超过一定的响应时间时,可以通过服务降级来返回预先定义的默认值、缓存数据或备用结果。服务降级的目标是确保调用方在面对异常情况时仍能够获得响应,而不会因为服务不可用而导致整个系统的不可用。服务降级可以在单个服务内部实现,也可以由调用方在失败时采取备用策略。
区别
目的:服务熔断的目标是防止故障在整个系统中扩散,保护系统免受不可用服务的影响;服务降级的目标识在服务不可用或性能下降时提供有限但可用的功能,确保调用方仍能够获得相应。
触发条件:服务熔断触发条件通常是错误率超过阈值或请求失败次数超过阈值;服务降级触发条件通常是服务不可用、性能下降或超过一定的响应时间。
动作:服务通断打开后,可以采取一些措施,如返回缓存数据、返回默认值或使用备用服务等;服务降级时,可以返回预先定义的默认值、缓存数据或备用结果。
范围:服务熔断通常是在服务间的通信层面进行,以防止故障在整个系统中扩散;服务降级可以在单个服务内部实现,也可以有调用方在失败时采用备用策略。
pom文件新增依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrix-dashboardartifactId>
dependency>
主启动类新增注解@EnablebHystrixDashboard
application.yml
server:
port: 9001
浏览器输入http://localhost:9001/hystrix
出现如下界面:
监控测试:
浏览器输入http://localhost/8001/hystrix.stream
此界面会一直刷新。
也可以通过Dashboard界面测试:
Delay:控制服务器上轮询监控信息的延迟时间,默认2000毫秒,可通过配置改属性降低客户端的网络和CPU的消耗。
Title:该参数对应了头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,可以通过配置该信息来展示更合适的标题。
点击Monitor Stream后,跳转到如下界面:
Zull是Netflix开源的一个基于JVM的边缘服务网关,用于在微服务架构中提供动态路由、过滤器、负载均衡、安全性能等功能。他作为应用程序的前置网关,接收所有的客户端请求,并将请求路由到适当的微服务实例。
注意:Zull服务最终还是会注册进Eureka
提供=代理+路由+过滤三大功能
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zuulartifactId>
dependency>
dependencies>
@EnableZuulProxy
server:
port: 9527
spring:
application:
name: microservicecloud-zull-gateway # 微服务实例名称
eureka:
client:
service-url:
defaultZone: http://eureka7001:7001.com/eureak,http://eureka7002:7002/eureka,http://eureka7003:7003/eureka
instance: # 配置Zull的实例
instance-id: gateway-9527.com # Zull实例唯一标识符
prefer-ip-address: true # 是否优先使用Ip地址进行路由,值为ture时,会优先使用服务实例的IP地址进行访问
访问Eureka的注册中界面:
测试zull:
application.yml文件中:
zuul:
routes:
agent.serviceId: microservicecloud-dept # 指定agent路由规则将转发到的服务的服务ID
agent.path: /agent/** # 路径匹配规则,指定匹配该路由规则的请求路径 (配置服务的匿名),本例中所有以"/agent/"开头的路径都会匹配该规则。
ignored-services: microservicecloud-dept # 忽略的服务,如果要忽略多个可以使用 * 号
prefix: /atpl # 前缀配置,指定了Zuul路由的前缀路径。本例中,Zuul将会将所有的路由请求的路径加上“/atpl”前缀
可以看到上图中,请求路径中是使用的路由规则所以能访问到。
然而下图中,由于配置了 ignored-services: microservicecloud-dept
,所以访问路径中含有"microservicecloud-dept",则该请求将会被忽略,不会进行路由转发。
使用prefix: /atpl
配置了前缀
访问原来的路径:
访问带有前缀的路径:
分布式系统面临的—配置问题
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。SpringCloud提供了ConfigServer来解决这个问题。每个服务都有一个application.yml文件,那么上百个服务呢?岂不悲剧了!
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
SpringCloud Config分为服务端和客户端两部分。
服务端也称为分布式配置中心,他是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
SpringCloud Config 与 GitHub整合步骤:
application.yml
文件:要保存成UTF-8的格式!要保存成UTF-8的格式!spring:
profiles:
active:
- dev
---
spring:
profiles: dev
application:
name: microservicecloud-config-dev
---
spring:
profiles: test
application:
name: microservicecloud-config-test
---
spring:
profiles: prod
application:
name: microservicecloud-config-prod
#编码格式保存为UTF-8
远程库:
application.yml文件:
server:
port: 3344
spring:
application:
name: microservicecloud-config # 应用程序名,用于从SpringCloud Config 服务器获取配置
cloud:
config:
server:
git:
uri: https://github.com/pinlu-0/microservicecloud-config.git # 指定远程GitHub存储库的URI
username: #GitHub用户名
password: #Github密码
default-label: main # 分支
主启动类:
@SpringBootApplication
/**
* 开启配置服务器的功能
*/
@EnableConfigServer
public class Config_App_3344 {
public static void main(String[] args) {
SpringApplication.run(Config_App_3344.class);
}
}
pom文件:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
启动配置中心服务:http://localhost:3344/application-dev.yml
但是这种是通过https
的形式访问的远程库
使用SSH秘钥远程访问比较坑,下面介绍怎么使用SSH远程访问:
首先生成秘钥并添加到GitHub中:
#生成秘钥
ssh-keygen -t rsa -b 4096 -C "[email protected]"
#测试SSH连接,如果出现如下信息则表示成功
ssh -T [email protected]
> Hi USERNAME! You've successfully authenticated, but GitHub does not
> provide shell access.
按道理应该是可以访问了的,但是出现了报错,大概意思是github不支持RSA秘钥,它认为这种算法的秘钥不安全。
所以使用 ssh-keygen -t ed25519 -C “[email protected]” 命令生成秘钥,但是呢,这种生成的秘钥是
OPENSSH类型的,而com.jcraft.jsch不兼容OPENSSH所以。做在这只是简单试了一下,应为可能需要更换版本比较麻烦,也没有深入解决。后续可能会解决!
配置客户端:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
@RestController
public class ConfigClientController {
@Value("${spring.application.name}")
private String applicationName;
@Value("${eureka.client.service-url.defaultZone}")
private String eurekaServers;
@Value("${server.port}")
private String port;
@RequestMapping("/config")
public String getConfig() {
String str = "applicationName: " + applicationName + "\n eurekaServers:" + eurekaServers +"\n port: " +port ;
System.out.println("******str: " + str);
return "applicationName: " + applicationName + "\n eurekaServers:" +eurekaServers + "\n port: " + port ;
}
}
spring:
cloud:
config:
name: microservicecloud-config-client #
profile: test
label: master
uri: http://config-3344.com:3344 # 指定配置服务端的URI
测试:启动服务端,启动客户端。
测试访问服务端:http://localhost:3344/microservicecloud-config-client-dev.yml
测试访问客户端:http://localhost:8201/config
,可能回报以下错误,原因是服务端不能够访问到数据,可以单独测试一下服务端是否能访问到数据。
Could not resolve placeholder 'eureka.client.service-url.defaultZone' in value "${eureka.client.service-url.defaultZone}"
浏览器打印数据:
控制台打印数据:
通过修改bootstrap.pom文件测试8201端口:
现在我们做一个通过Eureka服务+一个Dept客户端访问的服务,将两个微服务的配置统一由github获得实体统一配置分布式管理,完成多环境的变更。
bootstrap.yml内容:
spring:
cloud:
config:
name: microservicecloud-config-eureka-client # 需要从github上读取的资源名称
profile: dev
label: master
uri: http://config-3344.com:3344 # 服务端地址
服务端存放在配置中心(GitHub上)的文件:microservicecloud-config-eureka-client
spring:
profiles:
active:
- dev
---
server:
port: 7001
spring:
profiles: dev
application:
name: microservicecloud-config-eureka-client
eureka:
instance:
hostname: eureka7001.com
client:
register-with-eureka: false # 当前的Eureka-server自己不注册到服务列表中
fetch-registry: false # 不通过Eureka获取注册信息
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
---
server:
port: 7001
spring:
profiles: test
application:
name: microservicecloud-config-eureka-client
eureka:
instance:
hostname: eureka7001.com # 冒号后面必须有空格
client:
register-with-eureka: false # 当前的Eureka-server自己不注册到服务列表中
fetch-registry: false # 不通过Eureka获取注册信息
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
bootstrap.yml内容:
spring:
cloud:
config:
name: microservicecloud-config-dept-client # 需要从github上读取的资源名称
profile: dev
label: master
uri: http://config-3344.com:3344 # config服务端的访问地址
客户端存放在配置中心(GitHub)的文件内容:microservicecloud-config-dept-client
spring:
profiles:
active:
- dev
---
server:
port: 8001
spring:
profiles: dev
application:
name: microservicecloud-config-dept-client
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/springcloud01 # 数据库名称
username: root
password: root
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路径
type-aliases-package: com.atpl.springcloud.entities # 所有Entity别名类所在包
mapper-locations:
- classpath:mybatis/mapper/**/*.xml
eureka:
client: # 客户端注册进eureka服务列表内
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
# 单机版 defaultZone: http://localhost:7001/eureka
instance:
instance-id: dept-8001.com # 自定义服务名名称信息
prefer-ip-address: true
info: # 指定应用程序的元数据
app.name: atpl-microservicecloud # 指定应用程序的名称,可以用来标识应用程序在整个系统中的作用
company.name: www.atpl.com # 指定应用程序所属的公司名称
build.artifactId: ${project.artifactId} # 指定应用程序的构件ID,通常是Maven项目中的artifactId
build.version: ${project.version} #指定应用程序的版本号,通常是Maven项目中的version
---
server:
port: 8001
spring:
profiles: test
application:
name: microservicecloud-config-dept-client
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/springcloud02 # 数据库名称
username: root
password: root
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路径
type-aliases-package: com.atpl.springcloud.entities # 所有Entity别名类所在包
mapper-locations:
- classpath:mybatis/mapper/**/*.xml
eureka:
client: # 客户端注册进eureka服务列表内
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
# 单机版 defaultZone: http://localhost:7001/eureka
instance:
instance-id: dept-8001.com # 自定义服务名名称信息
prefer-ip-address: true
info: # 指定应用程序的元数据
app.name: atpl-microservicecloud # 指定应用程序的名称,可以用来标识应用程序在整个系统中的作用
company.name: www.atpl.com # 指定应用程序所属的公司名称
build.artifactId: ${project.artifactId} # 指定应用程序的构件ID,通常是Maven项目中的artifactId
build.version: ${project.version} #指定应用程序的版本号,通常是Maven项目中的version
测试:优先启动3344,他启动的过程bootstrap.yml会优先加载,会去GitHub上获取配置信息,然后启动7001服务注册中心,在启动8001客户端。
浏览器发送请求:http://localhost:8001/dept/get/list
http://eureka7001.com:7001/
mvn clean:清除项目中的构建产物,例如target目录和生成的jar、war等文件。
mvn compile:编译项目中的Java源文件。
mvn test:运行项目中的测试用例。
mvn package:生成项目的可部署的打包文件,例如jar、war等。
mvn install:将项目的打包文件安装到本地Maven仓库中,以便其他项目可以使用该依赖。
mvn deploy:将项目的打包文件部署到远程Maven仓库中,以便其他项目可以使用该依赖。
mvn dependency:tree:打印项目依赖树,显示项目中所有的依赖关系。
mvn help:describe:列出Maven命令的详细信息和用法。
ACID是传统关系型数据库Transaction处理的四个基本属性:
CAP 是分布式系统设计中需要考虑的三个指标:
根据CAP定理,这三个指标不可能同时满足,最多只能同时满足其中两个,即3进2(要么AP要么CP要么CA,不可能三者兼得。P一定是占有的,因为网络可能出现延迟丢包等问题。)
CAP 理论的核心思想是:一个分布式系统不可能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)这三项属性。
在分布式系统设计中,不同系统可以根据业务需求,在AP和CP之间做不同的权衡:
下面列举出一些场景使用的CAP组合:
Zookeeper与Eureka都是分布式系统中的重要协调组件,主要区别如下:
用途不同
Zookeeper是一个协调服务,提供分布式锁、集群管理、元数据存储等功能。
Eureka是一个服务注册中心,用于服务发现与注册。
数据存储方式不同
Zookeeper采用属性结构存储数据,支持嵌套,适合保存配置、状态等复杂信息。
Eureka采用扁平的Key-Value结构,主要存储服务实例信息。
节点角色不同
Zookeeper节点通过Leader选举产生,节点角色对等。
Eureka各节点地位平等,可独立对外提供服务。
一致性保证不同
Zookeeper通过ZAB协议保证一致性。
Eureka为了可用性,保证最终一致性。
容错机制不同
Zookeeper通过Leader选举恢复可用性。
Eureka通过自我保护机制防止网络分区(网络中的某些节点因为网络故障而无法与其他节点通信)是的误删。
语言不同
Zookeeper使用Java语言实现。
Eureka使用Java和Scala语言实现。
总体来说说,Zookeeper偏向于强一致性,Eureka偏向于高可用性。
这两个注解标注的位置不一样,@Mapper注解标注在Mapper接口上,表示该接口为Mapper接口,Mybatis会自动扫描并注册该接口。
而@MapperScan是标注在主启动类上的注解,它是扫描某个包下所有的Mapper接口。这种适用于某个包下有很多Mapper接口。
首先application.yml与bootstrap.yml都是SpringBoot应用程序中常用的配置文件,用于配置应用程序的属性和设置。
SpringCloud在程序初始化过程中创建了一个‘Bootstrap Context’ 上下文,它充当了Spring应用的‘Application Context’的父上下文。初始化的时候,‘Bootstrap Context’负责从外部资源加载配置属性,并解析配置。这两个上下文共享一个从外部获取的‘Environment’环境。‘Bootstrap’ 属性有高优先级,默认情况下,他们不会被本地配置覆盖。‘Bootstrap Context’和‘Application Context’有着不同的约定。
所以新增了一个‘bootstrap.yml’文件,保证’Bootstrap Context’和’Application Context’配置的分离。
高阶篇的技术与初阶篇技术用较新版本的SpringBoot与SpringCloud:
下图是SpringCloud与SpringBoot的版本映射关系:
具体版本,浏览器输入https://start.spring.io/actuator/info
,会返回一个JSON数据,来显示版本信息。
{
"git": {
"branch": "53acf2ef792d1b167c889ab4f19501c7acf730b5",
"commit": {
"id": "53acf2e",
"time": "2023-08-29T07:26:28Z"
}
},
"build": {
"version": "0.0.1-SNAPSHOT",
"artifact": "start-site",
"versions": {
"spring-boot": "3.1.3",
"initializr": "0.20.1-SNAPSHOT"
},
"name": "start.spring.io website",
"time": "2023-08-29T07:27:38.085Z",
"group": "io.spring.start"
},
"bom-ranges": {
"codecentric-spring-boot-admin": {
"2.6.8": "Spring Boot >=2.6.0 and <2.7.0-M1",
"2.7.4": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
"3.0.4": "Spring Boot >=3.0.0-M1 and <3.1.0-M1",
"3.1.5": "Spring Boot >=3.1.0-M1 and <3.2.0-M1"
},
"hilla": {
"2.1.4": "Spring Boot >=3.1.0-M1 and <3.2.0-M1"
},
"sentry": {
"6.28.0": "Spring Boot >=2.7.0 and <3.2.0-M1"
},
"solace-spring-boot": {
"1.2.2": "Spring Boot >=2.6.0 and <3.0.0-M1",
"2.0.0": "Spring Boot >=3.0.0-M1"
},
"solace-spring-cloud": {
"2.3.2": "Spring Boot >=2.6.0 and <3.0.0-M1",
"3.0.0": "Spring Boot >=3.0.0-M1"
},
"spring-cloud": {
"2021.0.8": "Spring Boot >=2.6.0 and <3.0.0",
"2022.0.4": "Spring Boot >=3.0.0 and <3.2.0-M1",
"2023.0.0-M1": "Spring Boot >=3.2.0-M1 and <3.2.0-SNAPSHOT",
"2023.0.0-SNAPSHOT": "Spring Boot >=3.2.0-SNAPSHOT"
},
"spring-cloud-azure": {
"4.11.0": "Spring Boot >=2.6.0 and <3.0.0-M1",
"5.5.0": "Spring Boot >=3.0.0-M1 and <3.2.0-M1"
},
"spring-cloud-gcp": {
"3.6.3": "Spring Boot >=2.6.0 and <3.0.0-M1",
"4.7.2": "Spring Boot >=3.0.0-M1 and <3.2.0-M1"
},
"spring-cloud-services": {
"3.4.0": "Spring Boot >=2.6.0 and <2.7.0-M1",
"3.5.0": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
"4.0.3": "Spring Boot >=3.0.0 and <3.2.0-M1"
},
"spring-modulith": {
"1.0.0": "Spring Boot >=3.1.0 and <3.2.0-M1"
},
"spring-shell": {
"2.1.12": "Spring Boot >=2.7.0 and <3.0.0-M1",
"3.0.7": "Spring Boot >=3.0.0 and <3.1.0-M1",
"3.1.3": "Spring Boot >=3.1.0 and <3.2.0-M1",
"3.2.0-M1": "Spring Boot >=3.2.0-M1"
},
"vaadin": {
"23.2.15": "Spring Boot >=2.6.0 and <2.7.0-M1",
"23.3.21": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
"24.0.13": "Spring Boot >=3.0.0-M1 and <3.1.0-M1",
"24.1.7": "Spring Boot >=3.1.0-M1 and <3.2.0-M1"
},
"wavefront": {
"2.2.2": "Spring Boot >=2.6.0 and <2.7.0-M1",
"2.3.4": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
"3.0.1": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"
}
},
"dependency-ranges": {
"okta": {
"2.1.6": "Spring Boot >=2.6.0 and <3.0.0-M1",
"3.0.5": "Spring Boot >=3.0.0-M1 and <3.2.0-M1"
},
"mybatis": {
"2.2.2": "Spring Boot >=2.6.0 and <2.7.0-M1",
"2.3.1": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
"3.0.2": "Spring Boot >=3.0.0-M1"
},
"pulsar": {
"0.2.0": "Spring Boot >=3.0.0 and <3.2.0-M1"
},
"pulsar-reactive": {
"0.2.0": "Spring Boot >=3.0.0 and <3.2.0-M1"
},
"camel": {
"3.14.9": "Spring Boot >=2.6.0 and <2.7.0-M1",
"3.20.6": "Spring Boot >=2.7.0.M1 and <3.0.0-M1",
"4.0.0": "Spring Boot >=3.0.0-M1 and <3.2.0-M1"
},
"picocli": {
"4.7.4": "Spring Boot >=2.6.0 and <3.1.0-M1"
}
}
}
开发工具:IntelliJ IDEA 2023.1.3
SpringCloud:Hoxton.SR1
SpringBoot:2.2.2.RELEASE
SpringCloud Alibaba :2.1.0.RELEASE
Java :Java8
Maven:3.5及以上
MySQL:5.7及以上
在实战过程中,出现的BUG或者坑都会在’‘知识拓展’'小节说明
IDEA字符编码统一为UTF-8
创建一个Maven项目
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.atpl.springcloudgroupId>
<artifactId>springcloud02artifactId>
<version>1.0-SNAPSHOTversion>
<packaging>pompackaging>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<junit.version>4.12junit.version>
<log4j.version>1.2.17log4j.version>
<lombok.version>1.16.18lombok.version>
<mysql.version>5.1.47mysql.version>
<druid.version>1.1.16druid.version>
<mybatis.spring.boot.version>1.3.0mybatis.spring.boot.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.2.2.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>${druid.version}version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>${mybatis.spring.boot.version}version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>${log4j.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
<optional>trueoptional>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.2.2.RELEASEversion>
<configuration>
<fork>truefork>
<addResources>trueaddResources>
configuration>
plugin>
plugins>
build>
project>
pom.xml文件:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.atpl.springcloudgroupId>
<artifactId>springcloud02artifactId>
<version>1.0-SNAPSHOTversion>
parent>
<artifactId>cloud-provider-payment8001artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
pom文件:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.atpl.springcloudgroupId>
<artifactId>springcloud02artifactId>
<version>1.0-SNAPSHOTversion>
parent>
<artifactId>generatorartifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.4version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.3.0version>
<dependencies>
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.3.5version>
dependency>
<dependency>
<groupId>com.mchangegroupId>
<artifactId>c3p0artifactId>
<version>0.9.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
dependencies>
plugin>
plugins>
build>
project>
generatorConfig.xml文件:
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="atcplTables" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressAllComments" value="true"/>
commentGenerator>
<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/springcloud02"
userId="root" password="root">
jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
javaTypeResolver>
<javaModelGenerator targetProject=".\src\main\java"
targetPackage="com.plcao.springcloud.entities">
<property name="enableSubPackages" value="false"/>
<property name="trimStrings" value="true"/>
javaModelGenerator>
<sqlMapGenerator targetProject=".\src\main\resources" targetPackage="mapper">
<property name="enableSubPackages" value="false"/>
sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetProject=".\src\main\java" targetPackage="com.plcao.springcloud.dao">
<property name="enableSubPackages" value="false"/>
javaClientGenerator>
<table tableName="payment" domainObjectName="Payment">table>
context>
generatorConfiguration>
父工程pom文件加入:
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.2.2.RELEASEversion>
<configuration>
<fork>truefork>
<addResources>trueaddResources>
configuration>
plugin>
plugins>
build>
要使用热部署的工程pom文件:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
设置IDEA:
然后在要开启热部署的工程的pom文件中:ctrl+shift+alt+/
选中如下图:
pom文件:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
该工程需要访问8001服务,需要使用到RestTemplate
。
介绍RestTemplate:
RestTemplate是Spring提供的一个类,用于进行HTTP通信和访问RESTful Web服务。
创建配置类:
@Configuration
public class ApplicationContextConf {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
客户端访问8001:
@RestController
@Slf4j
public class OrderController {
private static final String PAYMENT_URI = "http://127.0.0.1:8001";
@Autowired
RestTemplate restTemplate;
@GetMapping("/consumer/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URI+"/get/payment/+"+id,CommonResult.class);
}
@GetMapping("/consumer/insert")
public CommonResult<Payment> insertPayment(@RequestBody Payment payment){
return restTemplate.postForObject(PAYMENT_URI+"/insert/payment",payment ,CommonResult.class);
}
}
测试:POSTMAN发送请求后,可以得到响应请求的响应。
80模块和8001模块都有重复的代码,将其提取成公共部分:
创建cloud-api-common模块:
pom文件
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.1.0version>
dependency>
dependencies>
使用maven命令将该模块进行install,然后在需要用到的模块的pom文件中引入该模块。
即现在80和8001的pom文件都要加上:
<dependency>
<groupId>com.atpl.springcloudgroupId>
<artifactId>cloud-api-commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
进行复测,结果显示跟重构前一样。
pom文件:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.atpl.springcloudgroupId>
<artifactId>springcloud02artifactId>
<version>1.0-SNAPSHOTversion>
parent>
<artifactId>cloud-eureka-server7001artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
project>
application.yml文件
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #服务端实例名称, eureka7001.com已在hosts文件中配置映射了
client:
fetch-registry: false #自己就是服务中心,不需要检索服务
register-with-eureka: false #不想注册中心注册自己
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
主启动类:
修改8001、80模块工程的主启动类:
启动7001工程,再启动80、8001工程,浏览器访问http://localhost:7001
,如下图所示,说明80、8001已经注册进注册中心。
创建7002、7003服务注册工程,与7001类似,配置文件需要修改一下:
7001:application.yml
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #服务端实例名称
client:
fetch-registry: false #自己就是服务中心,不需要检索服务
register-with-eureka: false #不想注册中心注册自己
service-url:
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
7002:application.yml
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com
client:
fetch-registry: false
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/
7003:application.yml
server:
port: 7003
eureka:
instance:
hostname: eureka7003.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
创建8002服务提供工程,与8001类似,区别端口号即可:
server:
port: 8002
spring:
application:
name: cloud-payment-service #服务名称
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://127.0.0.1:3306/springcloud02?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.plcao.springcloud.entities #实体类所在包,配置别名
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
instance:
prefer-ip-address: true
instance-id: provider8002
启动7001、7002、7003、80、8001,浏览器访问eureka7001.com:7001
、eureka7002.com:7002
、eureka7003.com:7003
如下图所示:
上图中有两处标识:红色是没有配置,虽然不影响程序,但是在实际开发中,数不允许出现主街名称的,一般都是ip
微服务信息完善:
instance:
prefer-ip-address: true
instance-id: provider8002 #访问路径可以看见ip地址
修改:ApplicationContextConf.java
修改:80客户端的Controller
因为服务提供搭建了集群,客户端不应该是再把访问的服务提供的端口写死,而是写成服务提供的实例名称,但是服务集群中有多个服务提供,客户端不知道具体访问哪个,所以需要开启RestTemplate的负载均衡。
这里只简单测试一下,详细内容请看后续章节。在这里只拿8001服务做测试。
@DiscoveryClient
注解可以将服务注册到服务发现组建中,它允许服务实例注册自己并提供有关其位置和可用性信息,以便其他服务可以动态地发现和调用他们。
该注解通常用于服务提供者,它会告诉SpringCloud将当前服务注册到服务发现组件中。
主启动类上加上注解:@DiscoveryClient
业务逻辑:
启动服务,浏览器访问http://localhost:8001/payment/discovery
,可以看到控制台打印信息:
到这Eureka基本已经都涉及到了,但是Eureka官网已经停更了。下文将选用Zookeeper作为注册中心技术。
Zookeeper相关教程连接:
使用Zookeeper作为服务注册中心
pom文件中的dependencyManagement
dependencies
dependency
的区别:
Maven使用dependencyManagement
元素集中管理项目中的依赖版本号。通常会在项目的最顶层的父工程POM文件中看到。它允许您在一个地方定义依赖项及其版本,然后在项目的其他模块中引用这些依赖项,而无需在每个模块中重复定义版本号。通过使用 dependencyManagement
,您可以确保项目中的所有模块使用相同的依赖版本,从而提供了更好的一致性和可维护性。
Maven会沿着父子增次向上走,指导找到一个拥有dependencyManagement
元素的项目,然后他就会使用这个元素中指定的版本号。
例如在父工程中:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
// ...
然后在子工程中:
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
如果不在子工程中声明以来,是不会从父项目中继承下来的;只有在子项目中写了该依赖并没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom。也就是说只要子工程不指定版本号,子工程就会与父工程的版本号保持一致。如果子工程的某个依赖需要用到别的版本,只需要声明版本即可。
!!!注意:dependencyManagement
标签里知识声明依赖,并不是引入以来,因此子工程需要显式的声明需要的依赖。
例如:要导入的新以来在本地库没有,那么该依赖在dependencyManagement
标签中就会爆红,只需要将该依赖拿到dependencyManagement
标签外引入后再放进改标签即可。
跳过单元测试:
locations: classpath:mapper/*.xml
type-aliases-package: com.plcao.springcloud.entities #实体类所在包,配置别名
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
instance:
prefer-ip-address: true
instance-id: provider8002
启动7001、7002、7003、80、8001,浏览器访问`eureka7001.com:7001`、`eureka7002.com:7002`、`eureka7003.com:7003`如下图所示:
[外链图片转存中...(img-RedL8PVB-1701690304462)]
上图中有两处标识:红色是没有配置,虽然不影响程序,但是在实际开发中,数不允许出现主街名称的,一般都是ip
微服务信息完善:
```yml
instance:
prefer-ip-address: true
instance-id: provider8002 #访问路径可以看见ip地址
修改:ApplicationContextConf.java
[外链图片转存中…(img-VEa0dDhU-1701690304463)]
修改:80客户端的Controller
[外链图片转存中…(img-B46Murwm-1701690304464)]
因为服务提供搭建了集群,客户端不应该是再把访问的服务提供的端口写死,而是写成服务提供的实例名称,但是服务集群中有多个服务提供,客户端不知道具体访问哪个,所以需要开启RestTemplate的负载均衡。
这里只简单测试一下,详细内容请看后续章节。在这里只拿8001服务做测试。
@DiscoveryClient
注解可以将服务注册到服务发现组建中,它允许服务实例注册自己并提供有关其位置和可用性信息,以便其他服务可以动态地发现和调用他们。
该注解通常用于服务提供者,它会告诉SpringCloud将当前服务注册到服务发现组件中。
主启动类上加上注解:@DiscoveryClient
[外链图片转存中…(img-YwW8bwN9-1701690304465)]
业务逻辑:
[外链图片转存中…(img-CSDPsf1P-1701690304466)]
启动服务,浏览器访问http://localhost:8001/payment/discovery
,可以看到控制台打印信息:
[外链图片转存中…(img-BYhlgALo-1701690304467)]
到这Eureka基本已经都涉及到了,但是Eureka官网已经停更了。下文将选用Zookeeper作为注册中心技术。
Zookeeper相关教程连接:
使用Zookeeper作为服务注册中心
pom文件中的dependencyManagement
dependencies
dependency
的区别:
Maven使用dependencyManagement
元素集中管理项目中的依赖版本号。通常会在项目的最顶层的父工程POM文件中看到。它允许您在一个地方定义依赖项及其版本,然后在项目的其他模块中引用这些依赖项,而无需在每个模块中重复定义版本号。通过使用 dependencyManagement
,您可以确保项目中的所有模块使用相同的依赖版本,从而提供了更好的一致性和可维护性。
Maven会沿着父子增次向上走,指导找到一个拥有dependencyManagement
元素的项目,然后他就会使用这个元素中指定的版本号。
例如在父工程中:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
// ...
然后在子工程中:
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
如果不在子工程中声明以来,是不会从父项目中继承下来的;只有在子项目中写了该依赖并没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom。也就是说只要子工程不指定版本号,子工程就会与父工程的版本号保持一致。如果子工程的某个依赖需要用到别的版本,只需要声明版本即可。
!!!注意:dependencyManagement
标签里知识声明依赖,并不是引入以来,因此子工程需要显式的声明需要的依赖。
例如:要导入的新以来在本地库没有,那么该依赖在dependencyManagement
标签中就会爆红,只需要将该依赖拿到dependencyManagement
标签外引入后再放进改标签即可。
跳过单元测试:
[外链图片转存中…(img-eEOpjgLN-1701690304469)]