微服务
SOA -> 微服务(服务的细粒度)SpringCloud是生态。
模块化化开发
微服务架构的4个核心问题?
- 这么多服务如何访问?负载均衡算法(轮询)
- 服务之间的通信方式?Http、RPC
- 这么多服务,如何治理?服务注册与发现。
- 服务挂了怎么办?断路器(熔断降级)
解决方案:
- Spring Cloud NetFlix(一站式解决方案)
- api网关:zuul
- 通信:Feign 基于HTTP Client(http通信方式,同步阻塞)
- 注册中心(服务注册于发现):Eureka
- 熔断Hystrix
- Spring Cloud Alibaba (一站式解决方案,更简单成本更低)
- Apache Dubbo Zookeeper(半自动的取药整合)
- API:无。可以用前面俩种的网关
- 通信:Dubbo(RPC、非阻塞)
- 注册中心:Zookeeper
- 熔断:姐住Hystrix
其他概念:服务网格ServerMesh
什么是微服务?
微服务之间如何独立通信?
SpringCloud和Dubbo区别?
SpringBoot和SpringCloud的理解?
什么是服务熔断?服务降级?
微服务的优缺点?开发中的坑?
微服务技术栈?
eureka和zookeeper都可以提供注册发现,二者区别?
微服务与微服务架构
微服务
强调的是服务的大小,关注某一个点,是具体解决某一个问题/提供落地对应服务的一个应用。可以看作一个工程的模块。
微服务架构
微服务架构是一种架构模式/风格。提倡将单一的应用程序根据业务拆分成独立的小服务,彻底解耦。每个服务独立的运行在各自的进程。服务之间相互协调,互相配置。各服务独立构建、部署。避免统一集中管理。可以有独立的语言、数据库。
微服务优缺点:
优点
- 单一职责原则。 开发简单、效率高
- 每个服务高内聚,低耦合。开发部署都是独立的。
- 易于和第三方集成,允许容易灵活的方式集成自动部署。
- 微服务允许利用融合新技术
- 微服务只是业务逻辑代码,不会和HTML、CSS页面混合
- 每个微服务都有自己的存储能力,数据库,也可以统一。
缺点
- 开发人员处理分布式系统复杂
- 多服务增加运维成本
- 系统部署依赖
- 服务通信成本上升
- 数据一致性问题
- 系统集成测试问题
- 性能监控问题
微服务技术栈
区别
SpringCloud和SpringBoot关系
- SpringBoot专注于快速开发单个个体的服务。
- SpringCloud关注全局的为欸服务协调治理框架。 将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供:配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等集成服务。
- SpringBoot可以离开SpringCloud独立使用,但是SpringCloud必须依赖SpringBoot。
Dubbo和SpringCloud技术选型
1. 分布式+服务治理Dubbo
目前成熟的互联网架构:应用服务化拆分+消息中间件
2. Dubbo和SpringCloud对比
最大区别:SpringCloud抛弃了Dubbo的RPC通信,采用基于Http的Rest方式。 这两种方式各有千秋。Http方式牺牲了性能,但避免了RPC带来的问题。且Rest相比RPC更为灵活。
SpringCloud官方的兼容性更好,而Dubbo组装的方式自由度较高。
定位不一样:Dubbo是一个RPC框架,SpringCloud的目标是微服务下的一站式解决方案。
互联网架构图
文档
- SpringCloud中文网:https://www.springcloud.cc/
- SpringCloud中国社区:http://www.springcloud.cn/
- 文档:https://www.springcloud.cc/spring-cloud-greenwich.html
- 官网:https://cloud.spring.io/
SpringCloud版本选择
注册中心Eureka(服务注册与发现)
什么是Eureka
Netflix在设计Eureka遵从AP原则。
Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是一个基于Rest的服务,用于定位服务,以实现云端中间层服务发现和故障转移,服务注册于发现对于微服务来说是非常重要的。有了服务注册发现,只需要使用服务的标识,就可以访问到服务,而不需要修改服务调用的配置文件。类似的Dubbo注册中心和Zookeeper。
原理
Eureka基本架构:
- springcloud封装了Netflix的Eureka模块来实现服务注册于发现。
- Eureka采用CS架构设计,EurekaServer作为服务注册功能的服务器,是服务注册中心
- 而系统的其他微服务应用。使用Eureka的客户端连接到EurekaServer并维持心跳连接。这样系统的维护者可以通过EurekaServer来监控系统中各个微服务是否正常运行,SpringCloud的一些其他模块(eg:Zuul)可以通过EurekaServer来发现系统中的其他微服务应用,并执行相关逻辑。
俩大组件:EurekaServer和EurekaClient
- EurekaServer提供服务注册发现,各个节点启动,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
- EurekaClient是一个Java客户端,用于简化EurekaServer交互,客户端同时具备一个内置使用轮询负载算法的负载均衡器。在应用启动后,会向EurekaServer发送心跳(默认周期30秒)。如果EurekaServer在多个心跳周期内没有收到某个节点的心跳,EurekaServer会将这个服务从注册表中移除(默认周期90秒)。
Eureka三大角色
- Eureka Server:提供服务注册与发现
- Service Provider:将自身服务注册到Eureka中,从而使得消费者可以找到发现服务。
- Service Consumer:服务消费方从Eureka中获取注册服务列表,从而找到消费服务。
Eureka自我保护机制
当某一时刻的某个微服务不可用,eureka不会立即清理,仍然会对这个服务信息进行保存。
- 默认情况,eurekaServer在一定事件没有接收到该微服务心跳,EurekaServer会注销该服务实例。但是如果网络故障,或无法通信,则需要危险警告。因为微服务本身是健康的,此时不应该注销服务。Eureka通过自我保护机制,来进入自我保护模式,一旦进入该模式,EurekaServer会保护服务注册表中的信息,不删除数据注销。当网络恢复,则退出自我保护模式。
- 在自我保护模式中,EurekaServer会保护服务注册表中的信息,不再注销任何服务实例。当收到心跳数,重新恢复到阈值以上时,EurekaServer自动退出保护模式。
- 综上述,自我保护模式是一种应对网络异常的安全保护措施。使得Eureka更加健壮稳定。
- 禁用自我保护模式:
eureka.server.enable-self-preservation = false
编码与配置
EurekaServer注册中心编码配置
创建一个Maven父工程cloud_parent,并引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.9</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<name>cloud1_parent</name>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<lombok.version>1.18.26</lombok.version>
<junit.version>4.13.2</junit.version>
<log4j.version>1.2.17</log4j.version>
<spring.boot.version>2.2.0</spring.boot.version>
<spring.cloud.version>2021.0.1</spring.cloud.version>
</properties>
<dependencies>
<!-- spring cloud依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>
<!-- spring cloud的依赖要在这里导入,否则报错 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
创建eureka工程并引入依赖 cloud1_eureka
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>cloud1_parent</artifactId>
<groupId>com.hx</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud1_eureka</artifactId>
<name>cloud1_eureka</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 监控信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
在eureka工程中编写配置
spring:
profiles:
active: dev-8881
---
server:
port: 8881
spring:
application:
name: eureka-server1
config:
activate:
on-profile: dev-8881
eureka:
instance:
# Eureka服务端实例主机名称
hostname: 172.17.55.129
server:
enable-self-preservation: false #关闭自我保护(当服务器小于一定数量时,若有服务器宕机,不会被移除)
client:
# 是否向Eureka注册自己
register-with-eureka: false
# 如果是false表示自己是注册中心
fetch-registry: false
serviceUrl: # 服务注册地址
defaultZone: http://${
eureka.instance.hostname}:${
server.port}/eureka/
在启动类加上@EnableEurekaServer注解
@SpringBootApplication
@EnableEurekaServer
public class EurekaApp {
public static void main(String[] args) {
SpringApplication.run(EurekaApp.class, args);
}
}
集群配置
注意:这里eureka的hostname不可以相同,否则无法注册上来1
spring:
profiles:
active: dev-8881
---
server:
port: 8881
spring:
application:
name: eureka-server1
config:
activate:
on-profile: dev-8881
eureka:
instance:
# Eureka服务端实例主机名称
hostname: 172.17.55.129
server:
enable-self-preservation: false #关闭自我保护(当服务器小于一定数量时,若有服务器宕机,不会被移除)
client:
# 是否向Eureka注册自己
register-with-eureka: false
# 如果是false表示自己是注册中心
fetch-registry: false
serviceUrl:
defaultZone: http://127.0.0.1:8881/eureka/,http://127.0.0.1:8882/eureka/ # 注册地址
---
server:
port: 8882
spring:
application:
name: eureka-server2
config:
activate:
on-profile: dev-8882
eureka:
instance:
hostname: 127.0.0.1 # Eureka服务端实例主机名称
client:
register-with-eureka: false # 是否向Eureka注册自己
fetch-registry: false # 如果是false表示自己是注册中心
serviceUrl:
defaultZone: http://localhost:8881/eureka/,http://localhost:8882/eureka/ # 注册地址
启动
EurekaClient编码配置
创建一个api和provider(服务提供者)工程
创建cloud1_api工程并引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.hx</groupId>
<artifactId>cloud1_parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hx</groupId>
<artifactId>cloud1_api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud1_api</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.2-android</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
创建cloud1_provider工程并引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>cloud1_parent</artifactId>
<groupId>com.hx</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud1_provider</artifactId>
<dependencies>
<dependency>
<groupId>com.hx</groupId>
<artifactId>cloud1_api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>
</project>
server:
port: 9991
spring:
application:
name: cloud1-provider
logging:
level:
com.hx: debug
eureka:
client:
service-url:
defaultZone: http://localhost:8881/eureka/,http://localhost:8882/eureka/
instance:
instance-id: cloud-prod-1
# 配置主机地址
hostname: localhost
@RestController
public class HiController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private OrderService orderService;
@RequestMapping("/")
public String hi() {
return "<h1>HI Spring Cloud 1</h1>";
}
@RequestMapping("getOrder")
public Order getOrder() {
String id = UUID.randomUUID().toString();
Order order = orderService.getOrder(id);
return order;
}
/**
* 获取服务清单
*
* @return
*/
@RequestMapping("getClients")
public List<String> getClients() {
// 获取服务清单
List<String> services = discoveryClient.getServices();
for (String service : services) {
// 获取具体微服务实例信息
List<ServiceInstance> instances = discoveryClient.getInstances(service);
for (ServiceInstance instance : instances) {
System.out.println("instance.getHost() = " + instance.getHost());
System.out.println("instance.getPort() = " + instance.getPort());
System.out.println("instance.getUri() = " + instance.getUri());
System.out.println("instance.getServiceId() = " + instance.getServiceId());
}
}
return services;
}
}
Eureka的CAP原则与Zookeeper对比
ACID原则
A(automicity) C(Consistency) I(Isolation) D(Durability)
原子性、一致性、隔离性、持久性
CAP原则:
C:(Consistency)强一致性
A:(Avaliability)可用性
P:(Partition tolerance)分区容错性
CAP核心原理
- 一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个要求。
- 根据CAP原理,将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类。最多满足俩个,不可能同时满足。
- CA:单点集群,满足一致性、可用性的系统。但可扩展性较差
- CP:满足一致性、分区容错性,但性能不是很高
- AP:满足可用性、分区容错性,但对一致性要求较低
作为服务注册中心,Eureka比zookeeper好在哪里?
一个分布式系统无法同时满足CAP原则。
由于分区容错性P在分布式系统必须保证,因此只可以在CA之间进行权衡
- zookeeper保证CP原则
- Eureka保证AP原则
Eureka可以很好的应对网络故障导致的部分节点失恋问题,而不会像zk一样使得整个服务的无法使用
Zookeeper保证CP原则
当注册中心查询服务列表时,可以容忍注册中心返回的是几分钟前的信息。但不可以接收服务down机不可用。即服务注册功能对可用性的要求要高于一致性。但zk会出现一种情况,当master节点因为网络故障与其他节点失联,剩余节点会重新选举leader作为master节点。问题在于,选举leader节点事件太长(30s-120s)且全局期间zk集群不可用,这会导致在选举时,注册服务瘫痪。在云部署环境下,由于网络问题使得zk集群失去master节点丢失发生概率较大,虽然最空可以恢复服务,但是漫长选举过程是不可接受的。
Eureka保证AP原则
Eureka各个节点都是平等关系,几个节点挂掉不会影响正常节点工作,剩余的节点依然会提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时,如果发现连接失败,则自动切换到其他节点,只要有一台Eureka可用,则保证了注册服务可用性,不过查询信息可能不是最新的。此外,Eureka的自我保护机制,如果15min内超过85%的节点没有正常心跳,那么eureka认为客户端于注册中心出现网络故障。此时
- Eureka不再从注册列表移除因为长时间没有收到心跳而应该过期的服务
- Eureka仍然可以接收新服务的注册于查询请求,但是不会被同步到其他节点上(即保证当前节点依然可用)
- 当网络稳定时,当前实例新的注册信息会被同步到其他节点
踩坑:
在Eureka中的集群配置需要注意hostname配置不可以相同。 ↩︎