相信自己,终会成功
目录
Spring Cloud简介
1.1单体架构
1.2集群和分布式
1.3微服务架构(细致的划分)
注册中心
CAP理论
常见的注册中心
Eureka
Consul
Nacos
Eureka应用
多机部署, 负载均衡
客户端负载均衡
服务端负载均衡
Nacos应用
如何启动多个服务
服务下线+权重配置 +集群配置
Nacos健康检查
Nacos服务实例类型
创建Namespace
nacos配置中心
data id
OpenFeifn
OpenFeign传递参数
Spring Cloud简介
Spring Cloud 是一个基于 Spring Boot 的微服务架构开发工具集,它提供了一系列开箱即用的工具和组件,用于快速构建分布式系统中的常见模式(如配置管理、服务发现、熔断器、路由、微服务代理等)。Spring Cloud 的目标是简化分布式系统的开发,使开发者能够专注于业务逻辑,而不必过多关注底层的基础设施。
1.1单体架构
很多创业公司早期或者传统企业会把业务的所有功能实现都打包在⼀个项⽬, 这就是单体架构. 业务的所有功能实现都打包在⼀个war包或者Jar包中, 这种⽅式就称为单体架构这种架构开发简单, 部署简单, ⼀个项⽬就包含了所有的功能, 省去了多个项⽬之间的交互和调用消耗. 直接部署在⼀个服务器即可(个人理解,就是一个jar包或者war包干了所有事)
1.2集群和分布式
当⽹站的⽤⼾量越来越⼤, 需求也会越来越多, 流量也会越来越⼤, 服务可能就会⾯临以下问题:
后端服务器的压⼒就会越来越⼤, 负载越来越⾼, 甚⾄出现⽆法访问的情况 业务场景逐渐复杂. 为了满足用户的需求, 单体应⽤也会越来越⼤. 各个业务代码之间的耦合度也会 越来越⾼. 任何⼀个问题, 都需要整个项⽬重新构建, 发布.
⼀个微⼩的问题, 可能会导致整个应用挂掉
可以进行优化分别横向和纵向两个方面
横向:添加服务器, 把单台机器变成多台机器的集群
纵向: 把⼀个应⽤, 按照业务进⾏拆分, 拆分为多个项⽬. 此架构也称为垂直架构
假如原来为一个机器,横向就是加了N台机器,纵向就是将一个大项目的内容拆分成N个小项目
集群:集群是指将多台服务器(或节点)组合在一起,作为一个整体对外提供服务。这些服务器通常运行相同的应用程序或服务,并通过负载均衡器分配请求。
可以理解为做饭,一个厨师放不过来,多个厨师来一起做饭就是集群
分布式就是把有人把做饭前期的工作准备妥当(例如切菜,洗菜)
集群是分布式的一种实现方式:分布式系统可以通过集群来实现某些功能。例如,一个分布式数据库可能由多个数据库集群组成。
分布式系统可能包含多个集群:在一个分布式系统中,不同的服务可以部署在各自的集群中。例如,一个微服务架构中,用户服务可能运行在一个集群上,订单服务运行在另一个集群上。
共同目标:两者都旨在提高系统的性能、可用性和扩展性。
实际的分布式架构设计中并不会把分布式和集群单独分开,而是统称分布式架构(划分了就可以了)
1.3微服务架构(细致的划分)
微服务架构:是一种将单一应用程序拆分为一组小型、独立服务的设计风格。每个服务都运行在自己的进程中,并通过轻量级的通信机制与其他服务协作。微服务架构的目标是提高系统的灵活性、可维护性和可扩展性。就是一个服务对应一个单一的功能,只进行这一件事情,这个服务可以单独部署运行
微服务架构 | 分布式架构 | |
实现方式 | 将应用程序拆分为多个小型、独立的服务。 提高系统的灵活性和可维护性。 支持快速迭代和独立部署。 | 解决单机系统的性能瓶颈。 提高系统的可用性和容错性。 支持水平扩展。 |
服务粒度 | 服务粒度较小,每个服务只负责一个特定的业务功能。 | 服务粒度可以较大,例如将一个单体应用拆分为多个模块,每个模块部署在不同的服务器上 |
数据管理 | 每个服务有自己的数据存储,避免共享数据库。 | 可能共享数据库,或者使用分布式数据库。 |
运维复杂度 | 运维复杂度较高,因为需要管理多个独立的服务 | 运维复杂度较低,因为模块之间的依赖关系较少 |
适用场景 | 适用于需要高灵活性、可维护性和可扩展性的场景 | 适用于需要解决单机系统性能瓶颈和单点故障问题的场景。 |
注册中心
服务提供者(Server):⼀次业务中, 被其它微服务调⽤的服务. 也就是提供接⼝给其它微服务
服务消费者(Client):⼀次业务中, 调用其它微服务的服务. 也就是调⽤其它微服务提供的接⼝
服务注册中心(Registry):用于保存Server 的注册信息, 当Server 节点发生变更时, Registry 会同步变更. 服务与注册中心使用⼀定机制通信, 如果注册中心与某服务长时间无法通信, 就会注销该实例
服务注册:服务提供者在启动时, 向 Registry 注册自身服务, 并向 Registry 定期发送心跳汇报存活状态.
服务发现:服务消费者从注册中心查询服务提供者的地址,并通过该地址调用服务提供者的接口. 服务发现的⼀个重要作用就是提供给服务消费者⼀个可用的服务列表.
CAP理论
C(Consistency,一致性):
在分布式系统中,所有节点在同一时间看到的数据是一致的。例如,当用户向系统写入数据后,所有后续的读取操作都能看到最新的数据。
A(Availability,可用性):
系统始终能够对外提供服务,即使部分节点出现故障。例如,当某个节点宕机时,其他节点仍然可以处理请求。
P(Partition Tolerance,分区容错性):
系统能够在网络分区(即节点之间的通信中断)的情况下继续运行。例如,当网络出现故障时,系统仍然可以提供服务
最多满足两个
CA 系统:满足一致性和可用性,但无法容忍网络分区(通常用于单机系统或局域网内的分布式系统)。
CP 系统:满足一致性和分区容错性,但在网络分区时可能无法提供服务(例如分布式数据库)
AP 系统:满足可用性和分区容错性,但在网络分区时可能返回不一致的数据(例如分布式缓存)。
常见的注册中心
Eureka
特点:AP 系统:优先保证可用性和分区容错性,在网络分区时可能返回不一致的服务列表。客户端负载均衡:通过与 Ribbon 集成,支持客户端的负载均衡。自我保护机制:在网络分区或服务实例大量下线时,Eureka 会进入自我保护模式,避免过度剔除服务实例。
适用场景:适用于对可用性要求较高的场景(如微服务架构)。适用于 Spring Cloud 生态。
缺点:不支持强一致性。社区活跃度下降(Netflix 已停止维护)。
Consul
开发背景:由 HashiCorp 开发,是一个功能丰富的服务发现和配置管理工具。
特点:CP 系统:优先保证一致性和分区容错性,使用 Raft 协议实现强一致性。多数据中心支持:支持跨数据中心的服务发现。健康检查:支持多种健康检查机制(如 HTTP、TCP、脚本)。KV 存储:提供键值存储功能,可用于配置管理。DNS 接口:支持通过 DNS 查询服务实例。
适用场景:适用于对一致性要求较高的场景。适用于多数据中心的分布式系统。
缺点:配置和使用相对复杂。性能可能不如 Eureka 和 Nacos。
Nacos
开发背景:由阿里巴巴开发并开源,是 Spring Cloud Alibaba 生态中的核心组件。
特点:AP 和 CP 模式切换:支持根据需求在 AP 和 CP 模式之间切换。服务发现与配置管理:集成了服务发现和配置管理功能。动态配置:支持配置的动态更新和推送。健康检查:支持多种健康检查机制。易用性:提供友好的管理界面和丰富的 API。
适用场景:适用于微服务架构。适用于需要动态配置管理的场景。
缺点:社区生态相对较新,成熟度不如 Eureka 和 Consul。
Eureka应用
创建Eureka服务端
引入依赖(在xml中)
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
完善启动类
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class, args);}
}
编写配置文件(yml文件,格式自行调整)
server:port: 8761 # Eureka 服务端默认端口eureka:instance:hostname: localhostclient:register-with-eureka: false # 是否将自己注册到 Eureka(单节点时设置为 false)fetch-registry: false # 是否从 Eureka 获取注册信息(单节点时设置为 false)service-url:defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
启动服务: http://127.0.0.1:10010/
创建 Eureka 客户端
在 pom.xml
中添加 Eureka Client 依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
启动类上添加 @EnableEurekaClient
注解:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@SpringBootApplication
@EnableEurekaClient // 启用 Eureka 客户端
public class EurekaClientApplication {public static void main(String[] args) {SpringApplication.run(EurekaClientApplication.class, args);}
}
配置 application.yml
或 application.properties
:
server:port: 8080 # 客户端服务端口spring:application:name: eureka-client-service # 服务名称eureka:client:service-url:defaultZone: http://localhost:8761/eureka/ # Eureka 服务端地址
服务发现与调用
在客户端项目中创建 RestTemplate
Bean:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@Configuration
public class AppConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}
通过服务名称调用其他服务:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;@RestController
public class ClientController {@Autowiredprivate RestTemplate restTemplate;@GetMapping("/call-service")public String callService() {// 通过服务名称调用其他服务String url = "http://eureka-client-service/endpoint";return restTemplate.getForObject(url, String.class);}
}
多机部署, 负载均衡
多机部署是指将应用程序或服务部署在多台服务器(或节点)上,以实现以下目标:
-
高可用性:避免单点故障,确保系统在部分节点故障时仍能正常运行。
-
负载均衡:将请求分散到多个节点,提高系统的处理能力。
-
扩展性:通过增加节点来提升系统的性能。
负载均衡是指将请求均匀地分发到多个服务器上,以避免单个服务器过载,并提高系统的整体性能和可用性。
客户端负载均衡
-
客户端从注册中心获取服务实例列表,并根据负载均衡策略选择一个实例发起请求。
-
常用工具:Ribbon、Spring Cloud LoadBalancer。
服务端负载均衡
-
通过负载均衡器(如 Nginx、HAProxy)将请求分发到后端服务器。
-
负载均衡器可以是硬件设备或软件实现。
Nacos应用
Nacos是阿里巴巴开源的一个动态服务发现、配置管理和服务管理平台。它支持服务注册与发现、动态配置管理、服务元数据管理等功能,广泛应用于微服务架构和云原生场景。
下载 Nacos:从 Nacos GitHub Release 下载最新版本。
启动 Nacos:解压下载的压缩包,进入 bin
目录。
运行以下命令启动 Nacos:
Linux/Mac:
sh startup.sh -m standalone
Windows:
startup.cmd -m standalone
访问
http://localhost:8848/nacos
默认用户名和密码为 nacos
在 pom.xml
中添加 Nacos 依赖:
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置 Nacos 服务端地址:
在 application.yml
中配置 Nacos 服务端地址:
spring:application:name: nacos-client-service # 服务名称cloud:nacos:discovery:server-addr: localhost:8848 # Nacos 服务端地址
#localhost 也可以是自己的云服务器地址
在启动类上添加 @EnableDiscoveryClient
注解:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@SpringBootApplication
@EnableDiscoveryClient // 启用服务发现
public class NacosClientApplication {public static void main(String[] args) {SpringApplication.run(NacosClientApplication.class, args);}
}
启动服务后,访问 Nacos 控制台(http://localhost:8848/nacos
),可以看到服务已注册
如何启动多个服务
点开之后找到Application和Spring Boot
nacos展示
服务下线+权重配置 +集群配置
点击上述图片展示中的详情即可进入
服务下线详情进入之后右边即可看见下线操作
权重配置:点击下线上方的编辑,即可输入权重大小,权重值默认为1
#开启负载均衡策略spring:cloud:loadbalancer:nacos:enabled: true
同集群优先访问(同集群的噶了之后,其他配置下的集群接手)
spring:cloud:nacos:discovery:server-addr: IP地址+端口号
# 设置上海集群(名字可以自定义)cluster-name: SH
# 设为非临时实例ephemeral: false
Nacos健康检查
Nacos作为注册中心,需要感知服务的健康状态,才能为服务调用方提供良好的服务
两种健康检查机制:
1.客户端主动上报机制:客户端通过心跳上报方式告知服务端(nacos注册中心)健康状态,默认心跳间隔5秒,nacos会在超过15秒未收到⼼跳后将实例设置为不健康状态, 超过30秒将实例删除
2.服务器反向探测机制:nacos主动探知客⼾端健康状态, 默认间隔为20秒,健康检查失败后实例会被标记为不健康, 不会被⽴即删除
Nacos服务实例类型
临时实例: 如果实例宕机超过⼀定时间, 会从服务列表剔除, 默认类型
非临时实例:如果实例宕机, 不会从服务列表剔除, 也可以叫永久实例
Nacos对临时实例, 采取的是 客户端主动上报机制, 对非临时实例, 采取服务器端反向探测机制
Nacos服务实例类型不允许改变
Nacos会记录每个服务实例的IP和端口号, 当发现IP和端口都没有发⽣变化时, Nacos不允许一个服务实例类型发生变化.例如,从临时实例变成非临时实例
解决办法:
1.停掉nacos
2.删除nacos目录下/data/protocol/raft信息
服务正常,nacos健康检查失败(健康状态为false)
参考:
nacos环境隔离
企业中一个服务会分为开发环境,测试环境和生产环境
开发环境:开发人员用于开发的服务器,是最基础的环境,一般日志级别设置较低,可能会开启一些调试信息
测试环境:测试人员用来进行测试的服务器,是开发环境到生产环境的过度环境
生产环境:正是提供对外服务的环境,通常关掉调试信息
创建Namespace
默认情况下,所有服务都在同一个namespace,名为public
常见Namespace
配置Namespace
spring:cloud:nacos:discovery:server-addr: IP地址+端口号namespace: 命名空间ID
同一个命名空间下可以调用
如果将service下线,则调用不到其他命名空间下的service
nacos配置中心
当前项目的配置都在代码中,会存在以下问题:
1.配置文件修改时,服务需要重新部署,微服务架构中,一个服务可能有成百个实例,诶个部署比较麻烦,还容易出错
2.多人开发时,配置文件可能需要经常修改,使用同一个配置文件容易冲突
配置中心就是对这些配置项进行统一管理.通过配置中心,可以集中查看,修改和删除配置
参考文档:
Nacos 融合 Spring Cloud,成为注册配置中心 | Nacos 官网
1.添加配置
Data ID设置为项目名称
配置内容的数据格式,目前只支持properties和yaml类型
设置配置内容
获取配置
1.引入Nacos Config依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!-- SpringCloud 2020.*之后版本需要引⼊bootstrap--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency>
2.新建bootstrap.yml
spring:application:name: product-servicecloud:nacos:config:server-addr: IP地址+端口号
3.编写程序
@RequestMapping("/config")
//配置进行热更新
@RefreshScope
@RestController
public class NacosController {//读取配置@Value("${nacos.test.num:0}")private Integer nacosNum;@RequestMapping("/get")public Integer get() {return nacosNum;}
}
访问接口
data id
spring:application:name: product-serviceprofiles:active: devcloud:nacos:config:server-addr: IP地址:端口号namespace: 命名空间ID
OpenFeifn
OpenFeign 是一个声明式的 HTTP 客户端,主要用于简化微服务之间的 HTTP 调用。它是 Spring Cloud 生态系统的一部分,基于 Netflix Feign 开发,并与 Spring Boot 深度集成。通过 OpenFeign,你可以像调用本地方法一样调用远程服务。
RestTemplate存在问题
1.需要拼接URL,灵活性高,但是封装臃肿,URL复杂时,容易出错
2.代码可读性差,风格不统一
微服务的通信方式:RPC和HTTP
SpringCloud中,默认是使用HTTP来进行微服务的通信:
1.RestTemplate
2.OpenFeign
RPC框架
1.DUbbo
Apache Dubbo
2.Thrift
Apache Thrift - Home
3.gRPC
gRPC
引入依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
添加注解
@EnableFeignClients
@SpringBootApplication
public class OrderServiceApplication {public static void main(String[] args) {SpringApplication.run(OrderServiceApplication.class, args);}
}
编写OpenFeign客户端
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {@RequestMapping("/{productId}")ProductInfo getProductById(@PathVariable("productId")Integer productId);
}
//@FeignClient 注解作⽤在接⼝上, 参数说明:
//• name/value:指定FeignClient的名称, 也就是微服务的名称, ⽤于服务发现, Feign底层会使⽤
//Spring Cloud LoadBalance进⾏负载均衡. 也可以使⽤ url 属性指定⼀个具体的url.
//• path: 定义当前FeignClient的统⼀前缀
在service程序中引入ProductApi(引入之前先要打成本地jar包(选择openfeign所在的包))
@Service
public class OrderService {@Autowiredprivate ProductApi productApi;@Autowiredprivate OrderMapper orderMapper;
// @Autowired
// private RestTemplate restTemplate;public OrderInfo selectOrderById(Integer orderId){OrderInfo orderInfo=orderMapper.selectOrderById(orderId);
// String url = "http://product-service/product/"+
// orderInfo.getProductId();
// ProductInfo productInfo = productApi.getForObject(url,ProductInfo.class);ProductInfo productInfo = productApi.getProductById(orderInfo.getProductId());orderInfo.setProductInfo(productInfo);return orderInfo;}}
测试
测试需要在Linux上开启nacos,否则会出现报错
OpenFeign传递参数
1.传递单个参数(o1)
2.传递多个参数(o2)
3.传递对象(o3)
4.传递JSON(o4)
@RequestMapping("/order")
@RestController
public class OrderController {
@Autowired
private ProductApi productApi;@Autowiredprivate OrderService orderService;@RequestMapping("/{orderId}")public OrderInfo getOrderById(@PathVariable("orderId")Integer orderId){return orderService.selectOrderById(orderId);}@RequestMapping("/o1")public String o1(Integer id){return productApi.o1(id);}@RequestMapping("/o2")public String o2(@RequestParam("id")Integer id,@RequestParam("name")String name){return productApi.o2(id,name);}@RequestMapping("/o3")public String o3(ProductInfo productInfo){return productApi.o3(productInfo);}@RequestMapping("/o4")public String o4(@RequestBody ProductInfo productInfo){System.out.println(productInfo.toString());return productApi.o4(productInfo);}}
package com.hot.feign.Controller;import com.hot.feign.model.ProductInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.*;@FeignClient(value ="product-service", path = "/product")
public interface ProductApi {@RequestMapping("/{productId}")ProductInfo getProductById(@PathVariable("productId") Integer productId);@RequestMapping("/o1")String o1(@RequestParam("id") Integer id);@RequestMapping("/o2")String o2(@RequestParam("id") Integer id,@RequestParam("name")String name);//传递对象@RequestMapping("/o3")String o3(@SpringQueryMap ProductInfo productInfo);//传递JSON@RequestMapping("/o4")String o4(@RequestBody ProductInfo productInfo);
}
package com.Water.Controller;import com.Water.Service.ProductService;
import com.Water.model.ProductInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RequestMapping("/product")
@RestController
public class ProductController {@Autowiredprivate ProductService productService;@RequestMapping("/{productId}")public ProductInfo getProductById(@PathVariable("productId") Integer productId){return productService.selectProductById(productId);}@RequestMapping("/o1")public String o1(Integer id){return "o1接收到参数:"+id;}@RequestMapping("/o2")public String o2(Integer id,String name){return "p2接收到参数,id:"+id +",name:"+name;}@RequestMapping("/o3")public String o3(ProductInfo productInfo){return "接收到对象, productInfo:"+productInfo;}@RequestMapping("/o4")public String o4(@RequestBody ProductInfo productInfo){return "接收到对象, productInfo:"+productInfo;}}