微服务&服务注册 专题

1. 简述什么是服务注册与发现 ?

服务消费者找到服务提供者的这种机制称为服务发现,又或者服务注册
服务发现组件应具备以下功能:
服务注册表:服务注册表是服务发现组件的核心(其实就是类似于上面的registry表),它用来记录各个微服务的信息,例如微服务的名称、IP、端口等。服务注册表提供查询API和管理API,查询API用于查询可用的微服务实例,管理API用于服务的注册和注销;
服务注册与服务发现:服务注册是指微服务在启动时,将自己的信息注册到服务发现组件上的过程。服务发现是指查询可用微服务列表及其网络地址的机制;
服务检查:服务发现组件使用一定机制定时检测已注册的服务,如发现某实例长时间无法访问,就会从服务注册表中移除该实例

2. 请列举常用的服务注册发现的组件 ?

我们通常使用以下组件:
(1)ZooKeeper:ZooKeeper 是一个具有高可用性和可扩展性的分布式协调服务。它可以用于服务注册和发现,以及配置管理。
(2)etcd:etcd 是一个分布式键值存储系统,用于服务注册和发现,并提供强一致性保证。
(3)Consul: Consul 是一个分布式服务发现和配置管理系统。它支持多数据中心、健康检查和负载均衡等功能。

3. 简述什么是服务调用 ?

服务调用,即一个服务调用另一个服务,此过程可以分为服务调用者、服务提供者。基本上都会使用注册中心来作为中间件。
地址硬编码即将微服务的IP、端口号、请求url等具体的api地址通过代码的形式写在调用者服务中。
平常的单体应用中,客户端访问应用api即可取得数据;在微服务中,服务调用其它服务的api,就可以获取其数据,这样做的好处在于服务细分化,即将一个服务拆分为很多小服务。

4. 请列举常用的服务调用组件 ?

服务调用是微服务之间通信的另一个重要组件。它包括客户端负载均衡、服务降级、熔断和容错等功能。为了实现服务调用,我们通常使用以下组件:
(1)Ribbon:Ribbon 是一个负载均衡器,它可以帮助我们在集群中平衡负载。它支持多种负载算法和服务发现,可以与Eureka等注册中心集成。
(2)Feign:Feign 是一个声明式的 REST 客户端,它使编写 REST 客户端变得更加简单。我们可以使用注解来定义需要访问的服务接口,并且在运行时自动生成实现代码。
(3)Hystrix:Hystrix 是一种容错和熔断框架,它可以帮助我们处理分布式系统中的故障,并防止故障扩散。Hystrix 通过控制线程池和请求队列来实现熔断机制,从而避免系统崩溃。
(4)Resilience4j:Resilience4j 是一个轻量级的容错库,它提供了诸如熔断、超时、重试和限流等功能。与Hystrix相比,Resilience4j提供了更为灵活和集成化的解决方案。

5. 简述什么是Eureka以及运作方式 ?

Eureka是一个服务治理组件,它主要包括服务注册和服务发现,主要用来搭建服务注册中心。
Eureka 是一个基于 REST 的服务,用来定位服务,进行中间层服务器的负载均衡和故障转移;
Eureka是Netflix 公司开发的,Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现,也就是说Spring Cloud对Netflix Eureka 做了二次封装;
Eureka 采用了C-S(客户端/服务端)的设计架构,也就是Eureka由两个组件组成:Eureka服务端和Eureka客户端。Eureka Server 作为服务注册的服务端,它是服务注册中心,而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server服务端,并维持心跳连接,Eureka客户端是一个Java客户端,用来简化与服务器的交互、负载均衡,服务的故障切换等;
有了Eureka注册中心,系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行

6. 解释Eureka包含哪两个组件 ?

Eureka Server提供服务注册服务,各个节点启动后会在这里进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到.

Eureka Client是一个java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载复法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒),如果Eureka Server在多个心跳周期内没有接收到某 Eureka Server将会从服务注册表中把这个服务节点移除(默认90)

7. 简述Eureka的工作流程 ?

Eureka 的工作流程:

1、Eureka Server 启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过 Replicate 同步注册表,每个 Eureka Server 都存在独立完整的服务注册表信息
2、Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务
3、Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常
4、当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例
5、单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端
6、当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式
7、Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地
8、服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存
9、Eureka Client 获取到目标服务器信息,发起服务调用
10、Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除

8. Eureka Server注册中心服务端主要提供上门服务 ?

注册中心服务端主要对外提供了三个功能:

服务注册
服务提供者启动时,会通过 Eureka Client 向 Eureka Server 注册信息,Eureka Server 会存储该服务的信息,Eureka Server 内部有二层缓存机制来维护整个注册表

提供注册表
服务消费者在调用服务时,如果 Eureka Client 没有缓存注册表的话,会从 Eureka Server 获取最新的注册表

同步状态
Eureka Client 通过注册、心跳机制和 Eureka Server 同步当前客户端的状态。

Eureka Client:注册中心客户端
Eureka Client 是一个 Java 客户端,用于简化与 Eureka Server 的交互。Eureka Client 会拉取、更新和缓存 Eureka Server 中的信息。因此当所有的 Eureka Server 节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者,但是当服务有更改的时候会出现信息不一致。

Register: 服务注册
服务的提供者,将自身注册到注册中心,服务提供者也是一个 Eureka Client。当 Eureka Client 向 Eureka Server 注册时,它提供自身的元数据,比如 IP 地址、端口,运行状况指示符 URL,主页等。

Renew: 服务续约
Eureka Client 会每隔 30 秒发送一次心跳来续约。 通过续约来告知 Eureka Server 该 Eureka Client 运行正常,没有出现问题。 默认情况下,如果 Eureka Server 在 90 秒内没有收到 Eureka Client 的续约,Server 端会将实例从其注册表中删除。

9. 简述Eureka 失效剔除和自我保护机制 ?

服务下线
当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线 了”。服务中心接受到请求之后,将该服务置为下线状态

失效剔除
有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间 (默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。 可以通过 eureka.server.eviction-interval-timer-in-ms 参数对其进行修改,单位是毫秒。

自我保护
我们关停一个服务,在Eureka面板看到一条警告: 这是触发了Eureka的自我保护机制。
当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服 务实例的比例是否超过了85%,当EurekaServer节点在短时间内丢失过多客户端(可能发生了网络分区故障)。在 生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因 为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多 数服务依然可用。

10. 简述Eureka Server 进入自我保护机制,会出现什么情况 ?

Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
当网络稳定时,当前实例新的注册信息会被同步到其它节点中
Eureka 自我保护机制是为了防止误杀服务而提供的一个机制。当个别客户端出现心跳失联时,则认为是客户端的问题,剔除掉客户端;当 Eureka 捕获到大量的心跳失败时,则认为可能是网络问题,进入自我保护机制;当客户端心跳恢复时,Eureka 会自动退出自我保护机制。

如果在保护期内刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,即会调用失败。对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。

11. Eureka客户端启动时如何注册到服务端 ?

1、引入依赖




org.springframework.cloud

spring-cloud-starter-netflix-eureka-client

2.2.4.RELEASE


2、使用标签 @EnableDiscoveryClient 或 @EnableEurekaClient 开启
@SpringBootApplication
@EnableFeignClients(“com.example.eurekaconsumer.manager”)
@RestController
@EnableDiscoveryClient
public class EurekaConsumerApplication {

@Autowired
private Producer producer;

public static void main(String[] args) {
SpringApplication.run(EurekaConsumerApplication.class, args);
}
@GetMapping(“/public/api/user/{id}”)
public String getUser(@PathVariable(“id”) String id){
return producer.getUser(id);
}
}

3、添加配置
server:
port: 8003
spring:
profiles:
active: dev
application:
name: eureka-client2
main:
allow-bean-definition-overriding: true

eureka:
client:
serviceUrl:
defaultZone: http://localhost:8001/eureka/

4、服务调用
使用feign 调用服务提供者,服务发现直接使用name 去调用,name为服务提供者的名字。如果调用是Eureka外部服务,需要增加url。

package com.example.eurekaconsumer.manager;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

@FeignClient(name=“eureka-client1”)
public interface Producer {
@GetMapping(“/public/api/user/{id}”)
@ResponseBody
String getUser(@PathVariable String id);

5、调用结果

12. 简单描述Eureka 服务端的搭建流程 ?

1、添加 Eureka server 依赖
spring-cloud-starter-netflix-eureka-server

2、通过 @EnableEurekaServer 来标识该服务为 Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class UcEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(UcEurekaApplication.class, args);
}
}
3、配置文件 application.yml
server:
port: 8001
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:8001/eureka/

4、Server端管理页面

13. 简述Eureka 客户端服务注册流程和方法 ?

1、引入依赖
spring-cloud-starter-netflix-eureka-client
只要classpath 下包含spring-cloud-starter-netflix-eureka-client, Springboot应用会自动注册到Eureka Server.

2、添加配置
server:
port: 8002
spring:
profiles:
active: dev
application:
name: eureka-client1
main:
allow-bean-definition-overriding: true

eureka:
client:
serviceUrl:
defaultZone: http://localhost:8001/eureka/
3、注册结果

14. Eureka和Zookeeper都可以提供服务注册与发现的功能,请说说两个的区别?

著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。
由于分区容错性在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡,在此Zookeeper保证的是CP, 而Eureka则是AP。

1:Zookeeper保证CP
在ZooKeeper中,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举,但是问题在于,选举leader需要一定时间, 且选举期间整个ZooKeeper集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得ZooKeeper集群失去master节点是大概率事件,虽然服务最终能够恢复,但是在选举时间内导致服务注册长期不可用是难以容忍的。

2:Eureka保证AP
Eureka优先保证可用性,Eureka各个节点是平等的,某几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。
所以Eureka在网络故障导致部分节点失去联系的情况下,只要有一个节点可用,那么注册和查询服务就可以正常使用,而不会像zookeeper那样使整个注册服务瘫痪,Eureka优先保证了可用性

15. 如何构建高可用的Eureka集群?

首先,搭建一个高可用的Eureka集群,只需要在每个注册中心(服务端)通过配置:
eureka.client.service-url.defaultZone
指定其他服务端的地址,多个使用逗号隔开,如:
eureka.client.service-url.defaultZone=http://localhost:10000/eureka/,http://localhost:10001/eureka/,http://localhost:10002/eureka/
在eureka的高可用状态下,这些注册中心是对等的,他们会互相将注册在自己的实例同步给其他的注册中心,同样是通过问题1的方式将注册在自己上的实例注册到其他注册中心去。

那么问题来了,一旦 其中一个eureka收到一个客户端注册实例时,既然eureka注册中心将注册在自己的实例同步到其他注册中心中的方式和客户端注册的方式相同,那么在接收的eureka注册中心一端,会不会再同步回给注册中心(或者其他注册中心),从而导致死循环

16. 简述什么是 Nacos?

Nacos 是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施

17. 简述为什么选择Nacos以及其对应的优点 ?

常见的注册中心:

Eureka(原生,2.0遇到性能瓶颈,停止维护)
Zookeeper(支持,专业的独立产品。例如:dubbo)
Consul(原生,GO语言开发)
Nacos
相对于 Spring Cloud Eureka 来说,Nacos 更强大。
Nacos = Spring Cloud Eureka + Spring Cloud Config
Nacos 可以与 Spring, Spring Boot, Spring Cloud 集成,并能代替 Spring Cloud Eureka, Spring Cloud Config。
通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-config 实现配置的动态变更。
通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-discovery 实现服务的注册与发现。

18. 请列举Nacos的主要功能特性 ?

Nacos主要提供四大功能

1、服务发现与服务健康检查
Nacos使服务更容易注册,并通过DNS或者http接口发现其他服务,Nacos还提供服务的实时健康检查,防止向不健康的主机或者服务实例发送请求。

2、动态配置管理
动态配置服务允许在所有的环境中以集中和动态的方式管理所有的服务配置。Nacos消除了在更新配置使重新部署应用程序,使配置的更改更加高效和灵活。

3、动态NDS服务
Nacos 提供基于DNS协议的服务发现能力,旨在支持异构语言服务发现,支持将注册在Nacos服务以域名的方式暴露端点,让第三方应用方便查阅和发现。

4、服务和元数据管理
Nacos能从微服务平台建设的角度管理数据中心的所有服务和元数据,包括服务的描述,生命周期、服务的静态依赖分析、服务的健康状态、流量管理、路由及安全策略。

19. Nacos作为服务注册中心的核心部件和功能 ?

1 服务注册中心:是一个Nacos Server,可以为服务提供者和服务消费者提供服务注册和发现功能。
2 服务提供者:是一个Nacos Client ,用于对外服务,它将自己提供的服务注册到服务注册中心,以供服务消费者发现和调用。
3 服务消费者:是一个Nacos Client,用于消费服务。它可以从服务注册中心获取服务列表,调用所需的服务。

20. 如何将客户端服务注册到Nacos ?

引入Nacos服务发现依赖




com.alibaba.cloud

spring-cloud-starter-alibaba-nacos-discovery

2.2.5.RELEASE


配置Nacos信息,此处为.yml配置用例。name为注册到Nacos上的服务名称(name不支持下划线,可以包含中划线)
nacos上的配置优先级大于客户端本地服务配置优先级,服务启动时优先读取nacos配置文件,相同部分覆盖本地配置信息。
spring:
application:
name: nacos-test
cloud:
nacos:
discovery:
username: nacos
password: nacos
server-addr: localhost:8848
namespace: public

#暴露所有web端点
management:
endpoint:
web:
exposure:
include: ‘*’

在项目启动类上添加注解,让服务能够被注册中心发现:@EnableDiscoveryClient
至此,客户端服务已经成功在Nacos上注册成功。通过服务列表拉取,服务间就可以相互调用了。

21. 简述什么是Nacos Data ID ?

Data ID 的命名格式:
前面我们演示了在 nacos 控制台新建一个 DataID 为 cloud-producer-server-dev.yaml 的数据集,那么这个 Data ID 是什么呢?Data ID 是配置集的唯一标识,一个应用可以包含多个配置集,每个配置集都需要被一个有意义的名称标识。那么 Data ID 怎么取值呢?格式通俗一点就是 “前缀-环境-扩展名”,如下所示:
$ {
spring.cloud.nacos.config.prefix
}
-$ {
spring.profiles.active
}
.$ {
spring.cloud.nacos.config.file-extension
}
① prefix:前缀,默认是 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix 来配置。
#若不指定,默认采用应用名的方案
spring.application.name=cloud-producer-server
#手动指定配置的dataID前缀标识
#spring.cloud.nacos.config.prefix=cloud-producer-server-config
② active:配置运行环境,即为当前环境对应的 profile。
注意:当 spring.profiles.active 为空时,对应的连接符 ”-“ 也将不存在,dataId 的拼接格式变成 $ {
prefix
}
.$ {
file-extension
}
#dev表示开发环境
spring.profiles.active=dev
③ file-exetension:配置文件的类型,默认是 properties,也可以通过配置项 spring.cloud.nacos.config.file-extension 来配置,目前支持的类型有 TEXT、JSON、XML、YAML、HTML、Properties
#指定配置文件类型为yaml文件
spring.cloud.nacos.config.file-extension=yaml
④ 最终配置:
经过前面三个步骤,我们最终在nacos配置中心的控制台新增配置文件就是:cloud-producer-server.yaml

22. 简述Nacos的命名空间Namespace ?

Nacos 引入命名空间 Namespace 的概念来进行多环境配置和服务的管理及隔离。例如,你可能存在本地开发环境dev、测试环境test、生产环境prod 三个不同的环境,那么可以创建三个不同的 Namespace 区分不同的环境。
成功创建新命名空间后,就可以在 springboot 的配置文件配置命名空间的 id 切换到对应的命名空间,并获取对应空间下的配置文件,但在没有指定命名空间配置的情况下,默认的配置都是在 public 空间中,指定命名空间的方式如下:
#对应创建的命名空间的ID,此处对应的是dev命名空间
cloud.nacos.config.namespace=483bb765-a42d-4112-90bc-50b8dff768b3

23. 简述Nacos如何共享配置 ?

当我们微服务的数量越来越多,势必会有相同的配置,这时我们可以将相同的配置抽取出来作为项目中共有的配置,比如集群中的数据源信息、日志的配置信息,nacos 也是支持这种一个配置中心多个配置集这种写法的。
(1)我们在nacos中新建两个 Data ID 分别是 db.yaml 和 log.yaml 的文件。
(2)在配置文件中分别加入部分配置内容
(3)在 Springboot 项目中添加如下的 nacos 配置:
spring:
cloud:
nacos:
config:
extension-configs[0]:
data-id: db.yaml
#默认为DEFAULT_GROUP
group: DEFAULT_GROUP
#是否动态刷新,默认为false
refresh: true
extension-configs[1]:
data-id: log.yaml
group: DEFAULT_GROUP
refresh: true
为了更加清晰的在多个应用间配置共享的 Data Id,官方推荐使用 shared-configs,配置如下:
spring:
cloud:
nacos:
config:
shared-configs[0]:
data-id: db.yaml
#默认为DEFAULT_GROUP
group: DEFAULT_GROUP
#是否动态刷新,默认为false
refresh: true
shared-configs[1]:
data-id: log.yaml
group: DEFAULT_GROUP
refresh: true
(4)思考:在这2个文件中出现相同配置,nacos如何选取?
当多个 Data Id 同时出现相同的配置时,它的优先级关系是 spring.cloud.nacos.config.extension-configs[n].data-id 其中 n 的值越大,优先级越高。
注意:spring.cloud.nacos.config.extension-configs[n].data-id 的值必须带文件扩展名,文件扩展名既可支持 properties,又可以支持 yaml/yml。此时 spring.cloud.nacos.config.file-extension 的配置对自定义扩展配置的 Data Id 文件扩展名没有影响。
(5)不同方式配置加载优先级:
Nacos 配置中心目前提供以下三种配置能力从 Nacos 拉取相关的配置,当三种方式共同使用时,他们的一个优先级关系是:A < B < C:
A:通过 spring.cloud.nacos.config.shared-configs[n].data-id 支持多个共享 Data Id 的配置
B:通过 spring.cloud.nacos.config.extension-configs[n].data-id 的方式支持多个扩展 Data Id 的配置
C:通过内部相关规则(spring.cloud.nacos.config.prefix、spring.cloud.nacos.config.file-extension、spring.cloud.nacos.config.group)自动生成相关的 Data Id 配置

24. Nacos中的负载均衡底层是如何实现的?

Nacos的负载均衡指的是,在进行服务发现时进行负载均衡,正常情况下,在进行服务发现时,会根据服务名从Nacos中拉取所有的实例信息,但是Nacos中提供了一个功能,就是可以在拉取实例时,可以根据随机策略只拉取到所有实例中的某一个,这就是Nacos中的负载均衡,它跟Ribbon的负载均衡并不冲突,可以理解为Ribbon的负载均衡是发生在Nacos的负载均衡之后的。

25. Nacos服务是如何判定服务实例的状态?

通过发送心跳包,5秒发送一次,如果15秒没有回应,则说明服务出现了问题,如果30秒后没有回应,则说明服务已经停止

26. 简述 Nacos和Eureka区别 ?

1、CAP理论的区别
CAP理论:C一致性,A高可用,P分区容错性。eureka只支持AP,nacos支持CP和AP两种。
nacos是根据配置识别CP或AP模式,如果注册Nacos的client节点注册时是ephemeral=true即为临时节点,那么Naocs集群对这个client节点效果就是AP,反之则是CP,即不是临时节点
#false为永久实例,true表示临时实例开启,注册为临时实例
spring.cloud.nacos.discovery.ephemeral=true
2、连接方式不同
nacs使用的是netty和服务直接进行连接,属于长连接。
eureka是使用定时发送和服务进行联系,属于短连接。
3、服务异常剔除区别
eureka:
Eureka client在默认情况每隔30s想Eureka Server发送一次心跳,当Eureka Server在默认连续90s秒的情况下没有收到心跳, 会把Eureka client 从注册表中剔除,在由Eureka-Server 60秒的清除间隔,把Eureka client 给下线
EurekaInstanceConfigBean类下
private int leaseRenewalIntervalInSeconds = 30;
//心跳间隔30s
private int leaseExpirationDurationInSeconds = 90;
//默认90s没有收到心跳从注册表中剔除
EurekaServerConfigBean 类下
private long evictionIntervalTimerInMs = 60000L;
//异常服务剔除下线时间间隔
也就是在极端情况下Eureka 服务 从异常到剔除在到完全不接受请求可能需要 30s+90s+60s=3分钟左右(还是未考虑ribbon缓存情况下)
nacos:
nacos client 通过心跳上报方式告诉 nacos注册中心健康状态,默认心跳间隔5秒,
nacos会在超过15秒未收到心跳后将实例设置为不健康状态,可以正常接收到请求
超过30秒nacos将实例删除,不会再接收请求
4、操作实例方式不同
nacos:提供了nacos console可视化控制话界面,可以对实例列表进行监听,对实例进行上下线,权重的配置,并且config server提供了对服务实例提供配置中心,且可以对配置进行CRUD,版本管理
eureka:仅提供了实例列表,实例的状态,错误信息,相比于nacos过于简单
5、自我保护机制不同
Eureka保护方式:当在短时间内,统计续约失败的比例,如果达到一定阈值,则会触发自我保护的机制,在该机制下,Eureka Server不会剔除任何的微服务,等到正常后,再退出自我保护机制。自我保护开关(eureka.server.enable-self-preservation: false)
Nacos保护方式:当域名健康实例 (Instance) 占总服务实例(Instance) 的比例小于阈值时,无论实例 (Instance) 是否健康,都会将这个实例 (Instance) 返回给客户端。这样做虽然损失了一部分流量,但是保证了集群的剩余健康实例 (Instance) 能正常工作。

27. 请列举Nacos支持哪些协议?

Nacos支持多种协议,其中包括HTTP、Dubbo、gRPC和DNS等

28. 简述Nacos的集群部署模式 ?

官方文档提供了一个Nacos集群的架构图,当我们访问Nacos时,首先会经过SLB,也就是负载均衡,通常是一个nginx,通过nginx来进行分发到具体的Nacos服务器上面。我们需要给不同的服务器进行部署Nacos,我们这里演示的是一个伪的集群,在一台服务器上面搭建三个Nacos

29. 简述什么是 Consul ?

Consul 是一种分布式服务发现和配置管理工具,它可以用于服务注册、健康检查、负载均衡、故障恢复等方面。
Consul 支持多数据中心、多种服务发现方式和多种协议,可以帮助开发人员和运维人员更好地管理和维护分布式系统。

30. 列举Consul 的组织架构 ?

Consul主要有server和client两种组件组成。
1:server负责核心数据的存储和处理请求,server可以部署多个实例(通常推荐3-5个),server只有一个实例是leader实例,就是主节点,主节点是自动选举产生的,主节点负责处理数据的写入处理,同时将数据同步至其他server节点。
2:client负责跟server通信,处理转发服务注册、服务发现请求到server节点,client还负责服务的健康检查,client节点也可以部署多个实例,甚至每个微服务节点都部署一个client实例。

31. 简述什么是Consul默认地址 ?

Consul部署好以后,默认对外提供接口的地址是:127.0.0.1:8500 或者 consul agent ip地址 + 8500端口号。

32. Consul 的服务注册是如何实现的?

1 Consul 的服务注册是通过 Agent 进程实现的。
2 当一个服务启动时,它会向 Consul 的 Agent 发送一个注册请求,Agent 会将服务的元数据存储在本地,并将服务的信息发送到 Consul 的 Server 上。
3 当服务停止时,它会向 Agent 发送一个注销请求,Agent 会将服务的元数据从本地删除,并将服务的信息从 Consul 的 Server 上删除。

33. 如何查看Consul的集群的成员 ?

在另一个终端运行consul members,可以看到Consul集群的成员。 应该只看到一个成员(你自己):
[root@localhost ~]#consul members
Node Address Status Type Build Protocol DC Segment
localhost.localdomain 127.0.0.1:8301 alive server 1.0.1 2 dc1
输出显示了我们自己的节点,它正在运行的地址,运行状况,在集群中的角色以及一些版本信息。 额外的元数据可以通过提供-detailed标志来查看。
members命令的输出基于gossip协议,并最终一致。 也就是说,在任何时候,当地代理所看到的可能与服务器上的状态不完全一致。 要获得完全一致,请使用HTTP API再将HTTP请求转发给Consul服务器:
[root@localhost ~]#curl localhost:8500/v1/catalog/nodes[ {
“ID”: “590309a6-71f6-6145-fe40-d2c5e203687f”,
“Node”: “localhost.localdomain”,
“Address”: “127.0.0.1”,
“Datacenter”: “dc1”,
“TaggedAddresses”: {
“lan”: “127.0.0.1”,
“wan”: “127.0.0.1”
}
,
“Meta”: {
“consul-network-segment”: “”
}
,
“CreateIndex”: 5,
“ModifyIndex”: 6
}
]
  除了HTTP API之外,还可以使用DNS接口查询节点。

34. 如何运行 Consul Agent ?

Consul安装之后,代理必须运行。 代理可以在服务器或客户端模式下运行。 每个数据中心都必须至少有一台服务器,但推荐使用3台或5台服务器。 一个单一的服务器部署是非常不推荐的,因为在故障情况下数据丢失是不可避免的。   所有其他代理以客户端模式运行。 客户端是一个非常轻量级的进程,它注册服务,运行健康检查,并将查询转发给服务器。 代理程序必须在集群中的每个节点上运行。
为了简单起见,我们现在将以开发模式启动Consul代理。 这种模式对于快速简单地启动单节点Consul环境非常有用。 它并不打算在生产中使用,因为它不会持续任何状态。

35. 简述Consul 的基础架构模式 ?

Consul 是一个分布式高可用的系统. 这节将包含一些基础,我们忽略掉一些细节这样你可以快速了解Consul是如何工作的.如果要了解更多细节,请参考深入的架构描述.
每个提供服务给Consul的阶段都运行了一个Consul agent . 发现服务或者设置和获取 key/value存储的数据不是必须运行agent.这个agent是负责对节点自身和节点上的服务进行健康检查的.
Agent与一个和多个Consul Server 进行交互.Consul Server 用于存放和复制数据.server自行选举一个领袖.虽然Consul可以运行在一台server , 但是建议使用3到5台来避免失败情况下数据的丢失.每个数据中心建议配置一个server集群.
你基础设施中需要发现其他服务的组件可以查询任何一个Consul 的server或者 agent.Agent 会自动转发请求到server .
每个数据中运行了一个Consul server集群.当一个跨数据中心的服务发现和配置请求创建时.本地Consul Server转发请求到远程的数据中心并返回结果

36. 简述Consul如何使用配置文件注册服务 ?

基于配置文件的服务注册方式,是consul官方推荐的方式,因为这种方式对我们微服务应用无侵入,就是不需要写代码调consul的接口注册服务信息。

1.定义一个服务
首先定义一个存放consul配置文件的目录,例如:/etc/consul.d
#首先创建一个配置文件目录, 确保consul命令有权限访问这个目录即可
mkdir /etc/consul.d
为方便管理,我们通常一个微服务定义一个配置文件,服务定义配置文件是json格式。
例如:定义一个名字叫web的服务。
文件:/etc/consul.d/web.json {
“service”: {
“name”: “web”,
“tags”: [“rails”],
“port”: 80
}
}
服务配置文件名,可以随便取,通常以服务名命名。
配置文件参数说明:
name - 服务名
tags - 可以为服务打上标签,是个字符串数组,查询服务的时候可以用来过滤
port - 服务的端口
address - 服务的ip地址,可选,一般不用填写,注册的时候agent会加上。

2.注册服务
本地开发模式,启动consul的时候,通过config-dir参数指定配置文件目录,就会自动注册配置文件目录下的所有服务。
#以开发模式启动consul
consul agent -dev -config-dir=/etc/consul.d/
如果你的consul agent已经启动,并且启动的时候已经通过参数config-dir指定了同样的配置目录,只要执行下面命令,重新加载配置文件即可注册服务。
#重新加载配置
consul reload
提示: 生产环境部署的时候,我通常一台机器就部署一个微服务实例,只要在这台机器上部署一个consul的client,机器启动的时候,自动启动consul client根据配置文件自动注册一个服务

3.查询注册的服务
通过下面命令查询,当前注册的所有服务,会发现web服务已经注册成功。
consul catalog services
输出:
consul
web

4.反注册
就是删除注册的服务,输入下面命令,会删除注册的服务信息,同时删掉本地的服务的配置文件。
consul services deregister -id=web
参数说明:
id - 服务名

37. 简述Consul如何使用接口API注册服务 ?

服务注册:OpenAPI模式
OpenAPI 的使用方式相对来说比较简单,首先打开系统的命令行,在系统命令行中使用以下命令实现服务注册:
以上命令的执行结果如下图所示:
当返回“ok”结果时,表示服务注册成功,其中:
serviceName:表示服务名。
ip:表示客户端程序的 IP 地址。
port:表示客户端程序的端口号。
与此同时我们打开 Nacos 管理后台也可以看到我们注册的服务,可以看到我们注册的 IP 地址和端口
通过http接口注册服务,一般不推荐,毕竟还需要在写代码实现服务注册的逻辑,还不如配置文件方便,如果你想通过api注册,可以参考官方服务注册api接口文档:https://www.consul.io/api/catalog.html
提示:目前版本Http接口默认端口是8500, 例如注册服务的地址:http://localhost:8500/v1/catalog/register

38. 简述Consul如何通过Http API接口查询服务 ?

通过http接口查询
1.查询指定的服务
api格式:
http://localhost:8500/v1/catalog/service/服务名
例子:
查询web服务的所有地址信息
curl http://localhost:8500/v1/catalog/service/web
返回:
[ {
“ID”: “f9f81742-421d-a102-93ea-fb9c04d2f66d”,
“Node”: “jogindembp”, // 节点机器名字
“Address”: “127.0.0.1”, // 服务地址
“Datacenter”: “dc1”, // 数据中心名字
…忽略…
“ServiceKind”: “”,
“ServiceID”: “web”,
“ServiceName”: “web”, // 服务名
“ServiceTags”: [
“rails”
],
…忽略…
“ServicePort”: 80, // 服务端口
}
]
上面例子返回的结果是没有健康检查的结果,不知道服务地址是否可用。

2.仅查询可用的服务地址
下面查询服务信息,仅返回可用的服务信息,这个需要配合健康检查功能使用,后续有专门的章节介绍健康检查。
api格式:
http://localhost:8500/v1/health/service/服务名?passing
在Url后面增加一个passing参数代表,查询可用的服务地址。
例子:
查询web服务,可用的服务信息。
curl ‘http://localhost:8500/v1/health/service/web?passing’
返回结果:
[ {
“Node”: {
“Address”: “127.0.0.1”, // 节点地址
“Datacenter”: “dc1”,
…忽略…
}
,
“Service”: {
// 服务信息
“ID”: “web”,
“Service”: “web”,
“Tags”: [
“rails”
],
“Address”: “”, // 服务地址,没有配置,取上面的节点地址
“Port”: 80, // 服务端口号
…忽略…
}
,
“Checks”: [ // 健康检查结果,可以配置多种健康检查,所以可能会存在多个结果 {
“CheckID”: “api”, // 定义的检查id
“Status”: “passing”, //passing代表可用, warning 代表警告, critical 代表不可用.
…忽略…
}
]
}
]

3.查询注册中心的服务目录
通过这个接口,我们可以查询consul目前注册了那些服务。
api格式:
http://localhost:8500/v1/catalog/services
例子:
请求api后,返回当前注册中心,注册的所有服务清单。 {
“consul”: [],
“web”: [
“rails”
]
}
目前注册中心,只有两个服务web和consul

39. 简述Consul如何通过DNS查询服务 ?

consul支持通过dns查询服务信息,dns只能返回可用的服务地址。
下面我们通过dig命令,执行dns查询请求。
提示:macos系统默认自带了dig命令,其他系统可以网上找一下安装dig命令。
例子:
查询web服务的地址。
dig @127.0.0.1 -p 8600 web.service.consul
参数说明:
@127.0.0.1 - 指定dns服务器地址,就是我们consul的agent地址,本地安装了agent使用127.0.0.1即可
p - 指定dns服务的端口,consul默认使用的是8600
web.service.consul是需要查询的域名。
consul服务的域名规则:
服务名.service.consul
也就是,consul默认会为服务配置本地域名。
下面是执行dig命令后输出的结果:
<<>> DiG 9.10.6 <<>> @127.0.0.1 -p 8600 web.service.consul
…忽略…
​// QUESTION SECTION代表我们的查询请求
// 这里代表查询web.service.consul.这个域名的A记录信息。
QUESTION SECTION:
web.service.consul. IN A
​// ANSWER SECTION 代表查询结果,查询不到就是空的

ANSWER SECTION:
// 这里查询到一条A记录,ip地址是127.0.0.1
web.service.consul. 0 IN A 127.0.0.1
​…忽略…
上面dig命令,默认查询的是DNS的A记录,只能查询到Ip地址,查询不到端口号,我们可以通过查询DNS的SRV记录,获取完整的服务地址(ip和端口号)
例子:
dig @127.0.0.1 -p 8600 web.service.consul SRV
命令,最后面多了一个SRV,代表查询DNS的SRV记录
输出:
查询结果跟之前的命令差不多,下面仅显示关键信息。
…忽略…
ANSWER SECTION:
// 80 代表端口号
web.service.consul. 0 IN SRV 1 1 80 jogindembp.node.dc1.consul.
ADDITIONAL SECTION:
// 下面是服务的ip地址
jogindembp.node.dc1.consul. 0 IN A 127.0.0.1
jogindembp.node.dc1.consul. 0 IN TXT “consul-network-segment=”
…忽略…
提示:我们这里只是通过dig命令演示dns请求,项目中,不会使用dig命令发送dns请求,然后解析dig返回的结果,项目中可以使用dns客户端库,设置下dns服务的地址和端口,直接查询服务的域名即可;如果你的服务是web服务,甚至可以配置本地dns服务器,自动解析consul定义的服务域名。

40. 详细阐述Consul 服务健康检查机制 ?

分布式环境下,微服务实例的重启、微服务异常等等,都会导致服务不可用,例如:我们向consul注册了一个web服务,web服务启动了10个实例,现在有3个实例不可用,consul怎么知道那些服务实例不可用?
consul健康检查机制,就是解决上述问题的解决方案,健康检查机制运行在consul client中,会定期的根据服务的健康检查配置,去检测服务是否正常,如果服务异常,就将服务的实例标记为不用,如果恢复了,就标记为可用。
健康检查基本配置格式,是在服务定义配置中,增加checks字段,配置健康检查。 {
“service”: {
“name”: “web”,
“tags”: [“rails”],
“port”: 80,
“checks” : [ {
…具体的健康检查配置…
}
]
}
}
consul健康检查有多种方式,具体的配置,参考下面。
提示:下面的例子,以一个web服务的配置为例子,介绍健康检查的配置,web服务端口号为80。
1.基于HTTP请求
定时以GET请求方式,请求指定url,http请求返回状态码200表示正常,其他状态代表异常。
例子: {
“service”: {
“name”: “web”,
“tags”: [“rails”],
“port”: 80,
“checks”: [ {
“id”: “api”, // 健康检查项的id,唯一
“name”: “HTTP API on port 5000”, // 检查项的名字
“http”: “https://localhost:5000/health”, // 定期访问的Url,通过这个url请求结果确定服务是否正常
“tls_skip_verify”: false, // 关闭tls验证
“method”: “POST”, // 设置http请求方式,默认是GET
“header”: {
// 可以自定义请求头,可以不配置
“x-foo”: [“bar”, “baz”]
}
,
“interval”: “10s”, // 定期检查的时间间隔,这里是10秒
“timeout”: “1s” // 请求超时时间,1秒
}
]
}
}
2.基于tcp请求
基于tcp请求方式,就是定时,向指定的地址,建立tcp连接,连接成功就代表服务正常,否则代表异常。
例子: {
“service”: {
“name”: “web”,
“tags”: [“rails”],
“port”: 80,
“checks”: [ {
“id”: “ssh”, // 检查项目id
“name”: “SSH TCP on port 22”, // 检查项名字
“tcp”: “localhost:22”, // tcp连接地址,ip+port
“interval”: “10s”, // 定义建立连接的时间间隔是10秒
“timeout”: “1s” // 超时时间是1秒
}
]
}
}
3.基于grpc请求
如果微服务是基于grpc协议,可以使用grpc协议检测服务是否正常。
例子: {
“service”: {
“name”: “web”,
“tags”: [“rails”],
“port”: 80,
“checks”: [ {
“id”: “mem-util”, // 检查项目id
“name”: “Service health status”, // 检查项名字
“grpc”: “127.0.0.1:12345”, // grpc地址,ip+port
“grpc_use_tls”: true,
“interval”: “10s” // 10秒检测一次
}
]
}
}
4.基于命令
consul支持定期执行一个命令或者脚本,来检测服务是否正常;Consul通过检测命令退出状态判断服务是否正常,命令退出状态0代表正常,其他代表异常。
例如:
通过shell脚本检测服务.
#!/bin/bash
#执行检测任务
#…忽略…

#通过exit命令退出脚本,并返回一个整数值,0代表正常
exit 0
提示:通常linux命令,都遵循这个规则,命令正常执行,程序退出状态都是0,报错的话就是大于0的状态。
例子1: {
“service”: {
“name”: “web”,
“tags”: [“rails”],
“port”: 80,
“checks”: [ {
“id”: “mem-util”, // 检查项id
“name”: “Memory utilization”, // 检查项名字
“args”: [“/usr/local/bin/check_mem.py”, “-limit”, “256MB”], // 这里是我们要执行的命令,第一个参数是命令或者脚本名,后面跟着任意个参数
“interval”: “10s”, // 每10秒执行一次命令
“timeout”: “1s” // 命令执行超时时间
}
]
}
}
例子2: {
“service”: {
“name”: “web”,
“tags”: [“rails”],
“port”: 80,
“checks”: [ {
“args”: [“curl”, “localhost”], // 执行curl命令,数组第2到第N个元素,代表命令参数
“interval”: “10s” // 每10秒执行一次命令
}
]
}
}
为了安全考虑,如果健康检查使用执行命令方式,在启动consul的时候支持下面两种参数:
-enable-script-checks 允许通过配置文件和http api注册的服务,执行命令检查健康状态
-enable-local-script-checks 禁止通过http api注册的服务,执行命令检查健康状态,只允许通过配置文件注册的服务,执行命令。
建议生产环境使用-enable-local-script-checks参数启动consul agent。
例子:
consul agent -dev -enable-local-script-checks -config-dir=/etc/consul.d
本地测试,增加-dev参数,代表开发模式,生产环境,去掉-dev参数。
因为只有consul的client才会执行健康检查任务,可以在client设置这个参数就可以。

41. 如何监控Consul的服务变化 ?

我们也可以监控服务信息的变化,例如当有人注册新的服务或者服务不可用的时候,通知我们。
我们忽略掉,consul agent的配置,单独看watches的配置。
监控所有的服务的配置 {
“watches”: [ {
“type”: “services”,
“handler_type”: “http”,
“http_handler_config”: {
…忽略…
}
}
]
}
监控单个服务的配置 {
“watches”: [ {
“type”: “service”,
“service”: “要监控的服务名”,
“handler_type”: “http”,
“http_handler_config”: {
…忽略…
}
}
]
}

42. 如何监控Consul的键值的变化 ?

我们还可以通过key的前缀,批量监控一批key,只要key的前缀相同,这些Key下面的数据发生变化,都会发送通知。
{
“watches”: [{
“type”: “keyprefix”,
“prefix”: “foo/”,
“args”: [“/usr/bin/my-service-handler.sh”, “-redis”]
}]
}
监控类型为:keyprefix
通过prefix字段,配置key的前缀。
这个配置的意思就是:以foo/开头的Key, 数据发生变化,都会执行args参数,配置的命令。

43. 如何监控Consul的节点变化 ?

我们也可以监控consul集群节点的变化信息。
{
“watches”: [{
“type”: “nodes”,
“handler_type”: “http”,
“http_handler_config”: {
…忽略…
}
}]
}
没有其他额外的参数,type=nodes即可,当节点信息发生变化,会根据配置的方式通知。

44. 简述Consul 的负载均衡是如何实现的?

Consul 的负载均衡是通过 Service Mesh 实现的。
当一个服务需要访问其他服务时,它会向 Consul 的 Agent 发送一个服务发现请求,Agent 会返回一个可用的服务地址列表,并根据负载均衡算法选择一个地址进行访问。
Consul 支持多种负载均衡算法,包括轮询、随机、加权轮询、加权随机等。

45. 阐述Zookeeper、Eureka、Consul、Nacos对比区别 ?

Zookeeper 是⼀款经典的服务注册中心产品(虽然它最初的定位并不在于此),在很长⼀段时间里,它是国人在提起 RPC 服务注册中心时心里想到的唯⼀选择。
Eureka 借着微服务概念的流行,与 SpringCloud 生态的深度结合,也获取了大量的用户。
Consul 在设计上把很多分布式服务治理上要用到的功能都包含在内,可以支持服务注册、健康检查、配置管理、Service Mesh 等。
Nacos 携带着阿里巴巴大规模服务生产经验,试图在服务注册和配置管理这个市场上,提供给用户⼀个新的选择。

数据模型
Zookeeper 没有针对服务发现设计数据模型,它的数据是以⼀种更加抽象的树形 K-V 组织的,因此理论上可以存储任何语义的数据。
Eureka 和 Consul 都是做到了实例级别的数据扩展,这可以满足大部分的场景,不过无法满足大规模和多环境的服务数据存储。
Nacos 是⼀种服务-集群-实例的三层模型。基本可以满足服务在所有场景下的数据存储和管理。

数据⼀致性
从协议层面上看,⼀致性的选型目前就两种:⼀种是基于 Leader 的非对等部署的单点写⼀致性,⼀种是对等部署的多写⼀致性。

Zookeeper 是CP,采用自定义的 ZAB 协议保证数据的强⼀致。Zookeeper 在 Dubbo 体系下表现出的行为,其实采用 Eureka 的 Renew 机制更加合适,因为 Dubbo 服务往 Zookeeper 注册的就是临时节点,需要定时发心跳到 Zookeeper 来续约节点,并允许服务下线时,将 Zookeeper 上相应的节点摘除。
Eureka 是AP,节点都是对等的,通过相互注册提高可用性,通过renew机制进行数据的最终修复。
Consul 是CP,基于Raft协议保证数据的强一致性。
Nacos 同时支持 CP 和 AP ,⼀个是基于简化的 Raft 的 CP ⼀致性,⼀个是基于自研协议 Distro 的 AP ⼀致性。

负载均衡
Zookeeper 不支持。
Eureka 本身不支持,通过Ribbon做负载均衡。Ribbon是客户端负载均衡,主要通过 IRule、ServerListFilter 等接口实现,Ribbon采用的是两步负载均衡,第⼀步是先过滤掉不会采用的服务提供者实例,第二步是在过滤后的服务提供者实例里,实施负载均衡策略。
Consul 本身不支持,通过Fabio做负载均衡。
Nacos 本身具备负载均衡能力,可以提供服务端负载均衡与客户端负载均衡。

健康检查
服务端健康检查最常见的方式是 TCP 端口探测和 HTTP 接口返回码探测,这两种探测方式因为其协议的通用性可以支持绝大多数的健康检查场景。Zookeeper 和 Eureka 都实现了⼀种 TTL(Time To Live)机制,就是如果客户端在⼀定时间内没有向注册中心发送心跳,则会将这个客户端摘除。

Zookeeper 使用TCP的KeepAlive保持客户端和服务端的连接。
Eureka 通过客户端心跳来确定客户端是否启动。
Consul 通过check方法实现健康检查,check方法有五种:Script + Interval、HTTP + Interval、TCP+ Interval、Docker + interval、TTL。
Nacos 既支持客户端的健康检查(KeepAlive、Client Beet),也支持服务端的健康检查(MYSQL命令等)。

性能与容量
影响读写性能的因素很多:⼀致性协议、机器的配置、集群的规模、存量数据的规模、数据结构及读写逻辑的设计等等。并非性能越高就越好,因为追求性能往往需要其他方面做出牺牲。

Zookeeper 在写性能可以达到上万的 TPS,容量从存储节点数来说可以达到百万级别。不过 Paxos 协议限制了 Zookeeper 集群的规模(3、5个节点)。当大量实例上下线时,Zookeeper 的表现并不稳定,同时在推送机制上的缺陷,会引起客户端的资源占用上升,从而性能急剧下降。
Eureka 在服务实例规模在 5000 左右的时候,就已经出现服务不可用的问题,甚至在压测的过程中,如果并发的线程数过高,就会造成 Eureka crash。
Nacos 在开源版本中,服务实例注册的支撑量约为 100 万,服务的数量可以达到 10 万以上。

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我思故我在7896

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值