文章目录
序号 | 内容 |
---|---|
1 | 基础面试题 |
2 | JVM面试题 |
3 | 多线程面试题 |
4 | MySql面试题 |
5 | 集合容器面试题 |
6 | 设计模式面试题 |
7 | 分布式面试题 |
8 | Spring面试题 |
9 | SpringBoot面试题 |
10 | SpringCloud面试题 |
11 | Redis面试题 |
12 | RabbitMQ面试题 |
13 | ES面试题 |
14 | Nginx、Cancal |
15 | Mybatis面试题 |
16 | 消息队列面试题 |
17 | 网络面试题 |
18 | Linux、Kubenetes面试题 |
19 | Netty面试题 |
Nacos
Nacos配置更新的流程
Nacos 是采用长轮询的方式向 Nacos Server 端发起配置更新查询的功能。客户端需要更新配置前提是需要比较服务端配置信息,但是配置文件数据比较多的时候会影响性能,nacos做出两个优化,1.把数据分片,分成3000,也就是每次最多只能存3000个配置,2.做出比较把3000配置key以及value的md5的值,返回给客户端,一个个进行对比,这样可以减少数据包的大少,长轮询可以减少pull的轮询次数。
长轮询: 客户端发起一次轮询请求到服务端,当服务端配置没有任何变更 的时候,这个连接一直打开,直到服务端有配置或者连接超时后返回
Nacos是什么?作用
Nacos 是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理的平台。
Nacos有两个核心作用:服务发现 和 配置管理
客户端服务注册
1、spring boot启动时通过自动注入alibaba-nacos-discover下的spring.factories类。
2、自动注入的NacosServiceRegistryAutoConfiguration中会加载一个bean,nacosAutoServiceRegistration,这个bean创建了一个NacosAutoServiceRegistration对象。
3、NacosAutoServiceRegistration继承了AbstractAutoServiceRegistration(服务自动注册)类。
4、AbstractAutoServiceRegistration实现了三个接口,其中一个ApplicationListener,监听事件接口会监听服务是否初始化完成,在服务初始化完成后(AbstractAutoServiceRegistration重写的onApplicationEvent方法)调用bind方法。
5、bind方法中调用start方法。
6、start方法调用register接口。通过ServiceRegistry接口调用register方法。
7、NacosServiceRegistry实现了ServiceRegistry接口,所以start中调用的register接口的实现是nacos的注册实现。
register方法
@Override
public void register(Registration registration) {
// 配置文件中的application.name 是否为空
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
//
NamingService namingService = namingService();
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
//组装实例
Instance instance = getNacosInstanceFromRegistration(registration);
try {
//注册实例到nacos
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
instance.getIp(), instance.getPort());
}
}
//先通过getExecuteClientProxy(instance)判断是AP还是CP,AP的实现grpcClientProxy(默认),isEphemeral为true(临时的,available保证可用性)
//CP需要在配置文件中配置isEphemeral为false,CP的实现 httpClientProxy(持久化的,保证一致性)
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}
//默认调用grpcClientProxy的registerService实现
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
instance);
redoService.cacheInstanceForRedo(serviceName, groupName, instance);
doRegisterService(serviceName, groupName, instance);
}
//通过rpc调用接口注册到nacos(AP通过RPC调用),CP(通过http调用)
public void doDeregisterService(String serviceName, String groupName, Instance instance) throws NacosException {
InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName,
NamingRemoteConstants.DE_REGISTER_INSTANCE, instance);
requestToServer(request, Response.class);
redoService.removeInstanceForRedo(serviceName, groupName);
}
private <T extends Response> T requestToServer(AbstractNamingRequest request, Class<T> responseClass)
throws NacosException {
try {
request.putAllHeader(
getSecurityHeaders(request.getNamespace(), request.getGroupName(), request.getServiceName()));
Response response =
requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout);
if (ResponseCode.SUCCESS.getCode() != response.getResultCode()) {
throw new NacosException(response.getErrorCode(), response.getMessage());
}
if (responseClass.isAssignableFrom(response.getClass())) {
return (T) response;
}
NAMING_LOGGER.error("Server return unexpected response '{}', expected response should be '{}'",
response.getClass().getName(), responseClass.getName());
} catch (NacosException e) {
throw e;
} catch (Exception e) {
throw new NacosException(NacosException.SERVER_ERROR, "Request nacos server failed: ", e);
}
throw new NacosException(NacosException.SERVER_ERROR, "Server return invalid response");
}
客户端服务发现
在自动配置类NacosDiscoveryClientConfiguration中,有一个bean名为NacosWatch,NacosWatch 实现了 SmartLifeCycle 接口,IOC容器在初始化结束时会调用所有容器中 SmartLifecycle 实现类的 start 方法。NacosWatch的start方法就是从nacos服务端拉取注册信息的方法。
1、subscribe方法中会添加定时任务
2、判断本地是否存在相同的注册表,如果存在,更新本地缓存,不存在则调用GRPC去查询客户端
3、拉取的注册表,覆盖更新本地注册表。
4、判断是否有新增实例、修改实例、删除实例,如果有就发布实例变更事件
5、如果有修改实例,更新本地的心跳信息
@Override
public void start() {
if (this.running.compareAndSet(false, true)) {
// 创建一个监听NamingEvent事件的监听器,加入到listenerMap中
EventListener eventListener = listenerMap.computeIfAbsent(buildKey(),
event -> new EventListener() {
@Override
public void onEvent(Event event) {
if (event instanceof NamingEvent) {
List<Instance> instances = ((NamingEvent) event)
.getInstances();
Optional<Instance> instanceOptional = selectCurrentInstance(
instances);
instanceOptional.ifPresent(currentInstance -> {
resetIfNeeded(currentInstance);
});
}
}
});
// 获取namingService
NamingService namingService = nacosServiceManager.getNamingService();
try {
// 这里面会从服务端拉取一次注册表
namingService.subscribe(properties.getService(), properties.getGroup(),
Arrays.asList(properties.getClusterName()), eventListener);
}
catch (Exception e) {
log.error("namingService subscribe failed, properties:{}", properties, e);
}
}
}
@Override
public ServiceInfo subscribe(String serviceName, String groupName, String clusters) throws NacosException {
NAMING_LOGGER.info("[SUBSCRIBE-SERVICE] service:{}, group:{}, clusters:{} ", serviceName, groupName, clusters);
String serviceNameWithGroup = NamingUtils.getGroupedName(serviceName, groupName);
String serviceKey = ServiceInfo.getKey(serviceNameWithGroup, clusters);
//开启一个ScheduledExecutorService线程池,使用定时任务拉取注册表
serviceInfoUpdateService.scheduleUpdateIfAbsent(serviceName, groupName, clusters);
//获取本地缓存是否存在当前的注册表,如果不存在,则通过GRpc调用去获取服务端的注册表
ServiceInfo result = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
if (null == result || !isSubscribed(serviceName, groupName, clusters)) {
result = grpcClientProxy.subscribe(serviceName, groupName, clusters);
}
//1、拉取的注册表,覆盖更新本地注册表
//2、判断是否有新增实例、修改实例、删除实例,如果有就发布实例变更事件
//3、如果有修改实例,更新本地的心跳信息
serviceInfoHolder.processServiceInfo(result);
return result;
}
//https://blog.csdn.net/u010369122/article/details/134763724?ops_request_misc=&request_id=&biz_id=102&utm_term=nacos%20%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0%E5%8E%9F%E7%90%86&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-134763724.142^v99^pc_search_result_base4&spm=1018.2226.3001.4187
客户端健康检查
客户端主动上报
临时实例每隔 5 秒会主动上报一次自己的健康状况,发送的数据包叫做心跳包,发送心跳包的机制叫做心跳机制。如果心跳包的间隔时间超过了 15 秒,那么 Nacos 服务器端就会将此服务实例标记为非健康实例,如果心跳包超过了 30s 秒,那么 Nacos 服务器端将会把此服务实例从服务列表中删除掉。在NamingService.registerInstance()方法中组装心跳包BeatInfo,并且发送心跳。
组装完心跳对象下面会创建一个定时任务(addBeatInfo方法中),每5秒发送一次心跳
服务端服务注册
服务端 服务发现
服务端健康检查
配置中心
自动加载配置
spring boot启动时,在prepareContext的applyInitializers方法中调用initialize接口,PropertySourceBootstrapConfiguration实现了这个接口。这个实现方法调用了PropertySourceLocator接口的locateCollection方法。locateCollection方法调用了locate方法。
springboot自动加载的配置文件:NacosConfigBootstrapConfiguration创建的bean对象NacosPropertySourceLocator(创建bean对象时初始化了两个属性),这里里面实现了locate方法
通过locate方法加载配置文件
@Override
public PropertySource<?> locate(Environment env) {
...
loadSharedConfiguration(composite);//加载共享配置
loadExtConfiguration(composite);//加载拓展配置
//加载应用配置
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
//loadApplicationConfiguration方法中主要是使用的loadNacosDataIfPresent方法实现加载
private void loadNacosDataIfPresent(final CompositePropertySource composite,
final String dataId, final String group, String fileExtension,
boolean isRefreshable) {
...
//加载nacos配置资源(核心)
NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
fileExtension, isRefreshable);
this.addFirstPropertySource(composite, propertySource, false);
}
//如果不用刷新,就使用本地文件系统存储的快照配置信息,否则就走动态刷新的途径获取配置信息
private NacosPropertySource loadNacosPropertySource(final String dataId,
final String group, String fileExtension, boolean isRefreshable) {
if (NacosContextRefresher.getRefreshCount() != 0) {
if (!isRefreshable) {
return NacosPropertySourceRepository.getNacosPropertySource(dataId,
group);
}
}
//
return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
isRefreshable);
}
//
NacosPropertySource build(String dataId, String group, String fileExtension,
boolean isRefreshable) {
// 从nacos服务端加载配置信息,loadNacosData的方法中有通过RPC的调用(worker.getServerConfig方法)获取到服务端信息的操作
List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
fileExtension);
//封装配置信息
NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
group, dataId, new Date(), isRefreshable);
//将配置信息收集到NacosPropertySourceRepository
NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
return nacosPropertySource;
}
动态刷新配置
1、RefreshScope注解的Bean可以在运行时刷新,并且使用它们的任何组件都将在下一个方法调用前获得一个新实例,该实例将完全初始化并注入所有依赖项。
2、nacos客户端在初始化配置的同时,会和服务端建立长连接,用来监听服务端发生的变化。
3、如果服务端的配置有变化,服务端会以长轮询的方式通知客户端。
4、客户端收到通知后,NacosContextRefresher类会通过实现监听接口的onApplicationEvent方法调用更新配置。
https://blog.csdn.net/qq_26831431/article/details/131654901?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171138047516800197050965%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=171138047516800197050965&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-19-131654901-null-null.142v99pc_search_result_base4&utm_term=nacos%20%E5%8A%A8%E6%80%81%E5%88%B7%E6%96%B0%E9%85%8D%E7%BD%AE%E5%8E%9F%E7%90%86&spm=1018.2226.3001.4187
服务注册
客户端向这个服务端发起http请求,服务端接收到请求之后,会将这个instance实例加入到这个阻塞队列里面,然后后端会异步的开启一个线程来读取这个实例以及实例的各个参数,并且通过写时复制的方法,将这个实例注册到一个双重注册表里面的一个set集合里面去
服务发现
每个客户端都会去这个服务注册中心里面拉取这个注册表里面的全部实例,最后将这个实例存储在这个客户端的本地缓存里面。
在进行服务注册之后,该服务就会去这个服务注册中心里面拉取全部的微服务实例,会将全部的实例存在本地的缓存里面,并且同时会去开启一个定时任务,每隔几秒就会去拉取一次最新的微服务实例。
配置管理
主要就是通过这个nacos来作为一个配置中心,来统一管理这个配置。
客户端:就是在这个客户端进行启动的时候,就会优先拉取本地的配置,如果本地配置不存在,那么就会和这个服务端建立这个http请求,然后去拉取这个服务端的全部配置,就是配置中心的全部配置。在拉取到全部配置之后,会去获取每一个配置文件的dataId,然后通过这个id对服务端的每一个配置文件进行一个监听的操作。每当服务端这边的配置文件出现修改的时候,就可以通过这个监听器进行到一个感知,然后这个客户端也会对对应的配置文件进行修改,每一份修改的配置都会存储在这个nacos配置文件里面,会作为一个历史文件保留。
服务端:就是每个配置文件在注册之后,都会现存在这个mysql里面,最后会将这个mysql里面的数据存入到磁盘里面,在客户端来拉取这个配置信息的时候,就会直接去读这个本地磁盘里面的数据。
Nacos原理
- 服务注册原理:在
nacos
的服务端,有一个用来管理微服务实例的容器,注册中心将微服务的实例交由ServiceHolder
处理,ServiceHolder
为微服务提供空间并将它的所有实例挂在该空间下。服务注册完成后提供者将于注册中心维护心跳机制,心跳机制可以保证注册中心可以及时的剔除失效的实例。 - **服务发现原理:**服务完成注册之后,消费者可以向注册中心订阅某个服务,并提交一个监视器,当注册中心的服务发生变更时监听器会收到通知,然后消费者可以更新本地的服务实例列表,以保证所有的服务均可用。
- nacos的负载均衡:
Nacos
的客户端在获取到服务的完整实例列表后,会在客户端进行负载均衡算法来获取一个可用的实例,模式使用的是随机获取的方式。
集群
Nacos心跳机制
就是在注册这个实例的时候,客户端就会创建一个心跳的实例,一起发送到这个服务端,并且与此同时,会开启一个线程去执行这个客户端给服务端发送心跳的的这个延迟队列线程。客户端注册到这个服务端之后,会开启一个延迟的线程池任务,在注册成功5s之后再发送这个心跳给服务端。服务端在接收到这个客户端的心跳之后,会对这些心跳做一个记录,并且也会开启这个都是任务,去查看这些全部的实例是否需要删除,是否处于健康状态等。
如何理解Nacos中的命名空间?
命名空间,也就是namespace,其实这个概念并不是Nacos中独有的,在Nacos中,不管是配置还是服务,都是属于某一个命名空间中的,默认情况下都是属于pulibc这个命名空间中的,我们可以在Nacos中新增命名空间,也就相当于开辟了另外一套存放服务和配置的地方,命名空间之间是独立的,完全不冲突的,所以我们可以利用Nacos中的命名空间来实现不同环境、不同租户之间的服务注册和配置。
Nacos中保证的是CP还是AP?
通常我们说,Nacos技能保证CP,也能保证AP,具体看如何配置,但其实只不过是Nacos中的注册中心能保证CP或AP,Nacos中的配置中心其实没什么CP或AP,因为配置中心的数据是存在一个Mysql中的,只有注册中心的数据需要进行集群节点之间的同步,从而涉及到是CP还是AP,如果注册的节点是临时节点,那么就是AP,如果是非临时节点,那么就是CP,默认是临时节点。
Nacos中的负载均衡是怎么样的?
Nacos的负载均衡指的是,在进行服务发现时进行负载均衡,正常情况下,在进行服务发现时,会根据服务名从Nacos中拉取所有的实例信息,但是Nacos中提供了一个功能,就是可以在拉取实例时,可以根据随机策略只拉取到所有实例中的某一个,这就是Nacos中的负载均衡,它跟Ribbon的负载均衡并不冲突,可以理解为Ribbon的负载均衡是发生在Nacos的负载均衡之后的。
Nacos的就近访问是什么意思?
首先,在Nacos中,一个服务可以有多个实例,并且可以给实例设置cluster-name,就是可以再进一步的给所有实例划分集群,那如果现在某个服务A想要调用服务B,那么Naocs会看调用服务A的实例是属于哪个集群的,并且调用服务B时,那就会调用同样集群下的服务B实例,根据cluster-name来判断两个实例是不是同一个集群,这就是Nacos的就近访问。
Eureka
Eureka是Netflix组件的一个子模块,也是核心模块之一
为什么使用Eureka
随着业务的发展,系统会被拆分成多个功能模块。拆分后每个功能模块可以作为一个独立的子系统提供其职责范围内的功能。而多个子系统中,由于职责不同并且会存在相互调用,同时可能每个子系统还需要多个实例部署在多台服务器或者镜像中,导致了子系统间的相互调用形成了一个错综复杂的网状结构。对于微服务之间错综复杂的调用关系,通过eureka来管理,可以让每个服务之间不用关心如何调用的问题,专注于自己的业务功能实现。
Eureka工作原理
Eureka 服务注册中心主要职责是对于微服务治理,提供服务注册,服务发现,服务同步,服务续约等功能。可以分为server 和 client 两部分。
工作原理:
- server 启动后,会等待服务进行注册,来存储客户端信息。
- client 启动后根据配置文件中配置的server地址注册本身服务
- client 每隔30秒向server发送一次心跳请求,来证明客户端服务正常
- server 在90秒内没收到来自客户端的心跳请求,会认为该节点失效,会注销这个实例
- 单位时间内(15 分钟之内是否低于 85%)如果有大量的client没有发送心跳,便会触发server的自我保护机制,不在剔除没有发送心跳的服务。client 发送心跳正常后,server会自动退出保护机制
- client 会定时全量或者增量的从server拉取注册表服务信息缓存在本地
- client 服务调用时,本地注册表没有相应的信息就会请求服务端刷新注册表,同步到本地缓存
- client关闭应用时,会向server发送取消请求,server剔除相应的服务信息
Eureka 集群原理(高可用)
1、通过配置eureka.client.service-url.defaultZone。多个服务端地址使用 逗号 隔开。在eureka的高可用状态下,这些注册中心是对等的,他们会互相将注册在自己的实例同步给其他的注册中心。注册中心收到注册信息后会判断是否是其他注册中心同步的信息,还是客户端注册的信息。如果是客户端注册的信息,那么当前这个注册中心会将这个客户端信息同步到其他注册中心去,如果不是客户端注册,收到注册信息后不做任何操作。通过这个机制避免集群中信息同步的死循环问题。
死循环问题:
一旦 其中一个eureka收到一个客户端注册实例时,既然eureka注册中心将注册在自己的实例同步到其他注册中心中的方式和客户端注册的方式相同,那么在接收的eureka注册中心一端,会不会再同步回给注册中心(或者其他注册中心),从而导致死循环。
底层实现:
服务注册
Eureka Client会通过发送REST请求的方式向Eureka Server注册自己的服务,提供自身的元数据,比如ip地址、端口、运行状况指标的url、主页地址等信息。Eureka Server接收到注册请求后,就会把这些元数据信息存储在一个双层的Map中
服务续约
在服务注册后,Eureka Client会维护一个心跳来持续通知Eureka Server,说明服务一直处于可用状态,防止被剔除。Eureka Client在默认的情况下会每隔30秒 (eureka.instance.leaseRenewallIntervalInSeconds)发送一次心跳来进行服务续约。 eureka.instance.lease‐renewal‐interval‐in‐seconds=30
服务下线
当Eureka Client需要关闭或重启时,就不希望在这个时间段内再有请求进来, 所以,就需要提前先发送REST请求给Eureka Server,告诉Eureka Server自己要下线了,Eureka Server在收到请求后,就会把该服务状态置为下线(DOWN),并把该下线事件传播出去。
服务调用
服务消费者在获取到服务清单后,就可以根据清单中的服务列表信息,查找到其他服务的地址,从而进行远程调用。Eureka有Region和Zone的概念,一个Region可以包含多个Zone, 在进行服务调用时,优先访问处于同一个Zone中的服务提供者
服务获取
Eureka客户端在启动时会从Eureka服务器中获取注册表信息,并将其缓存在本地。
Eureka客户端会使用该信息查找相应的服务,并进行调用。该注册列表信息定期(默认为30
秒)从Eureka服务器进行同步。每次返回注册列表信息可能与Eureka客户端的缓存信息不同,由Eureka客户端自动处理。
如果由于某种原因导致注册列表信息不能及时匹配,Eureka客户端则会重新获取整个注册表信息。
Eureka服务器缓存注册列表信息,并对整个注册表及其中的每一个服务实例信息进行压缩,压缩内容和没有压缩的内容完全相同。
Eureka的自我保护机制
Eureka服务端会检查最近15分钟内所有的Eureka实例的正常心跳占比,如果低于85%会触发自我保护机制。自我保护机制:Eureka 会把这些失效的服务保护起来,不让其过期。但是并不是永远不会过期,Eureka在启动完成后,每隔60秒会检查一次服务的状态,如果被保护的失效服务过一段时间后(默认90秒),还没恢复,就会把这些服务提出。在此期间,如果服务恢复,并且心跳占比高于85%,就会关闭自我保护机制。
为什么会有自我保护
在CAP定理中,Eureka选择了保证可用性,放弃了保证强一致性,保证的是最终一致性。当网络发生分区时,客户端和服务端的通讯将会终止,那么服务端在一定的时间内将收不到大部分的客户端的一个心跳,如果这个时候将这些收不到心跳的服务剔除,那可能会将可用的客户端剔除了,这就不符合AP定理。
服务失效剔除机制
如果开了自我保护机制,那么所有的客户端包括没有长时间没有发送心跳的客户端都不会被剔除。
没开自我保护机制,注册到eureka的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下线”的请求。服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除
Eureka 多级缓存
一级缓存
注册表:ConcurrentHashMap<String, Map<String, Lease>> registry;
实时更新,UI界面从这里获取服务注册信息。
ConcurrentHashMap<String, Map<String, Lease>> registry:(key:appName,应用名称。value:实例对应的map)
Map<String, Lease>:key:实例id,value:实例信息
二级缓存
LoadingCache<Key, Value> readWriteCacheMap
Guava Cache,实时更新,缓存时间180秒;
三级缓存
ConcurrentMap<Key, Value> readOnlyCacheMap
周期更新,默认每30s从二级缓存readWriteCacheMap中同步数据更新;Eureka Client默认从这里获取服务注册信息,可配为直接从readWriteCacheMap获取
二三级缓存什么时候初始化
Eureka Server 启动的时候,根据Spring Boot 自动装配的特性,初始化DefaultEurekaServerContext,这个类中的initialize();方法会初始化注册中心。
二三级缓存初始化做了什么
1、会设置初始化二级缓存readWriteCacheMap(过期时间180s),设置二级缓存往三级缓存readOnlyCacheMap同步的时间间隔(默认30s)。
2、设置是否使用三级缓存(默认使用),如果使用则启动一个定时任务,默认每隔30s从二级缓存中同步数据到三级缓存(只更新三级缓存中已存在的key);
多级缓存的优点
- 尽可能的避免服务注册出现频繁的读写冲突,写阻塞读;
- 提高Eureka Server服务的读写性能。
zookeeper 和 eureka的区别
1、eureka满足AP(可用性),zookeeper满足CP(一致性)
- zookeeper要求各个服务之间同步完成后才返回结果给用户,如果出现网络波动导致监听不到心跳,会立即从服务列表中剔除,服务不可用。
- eureka 如果遇到网络故障,基于自我保护机制,虽然用户获取到的服务不一定是可用的,单至少能获取到服务列表。用户访问服务列表可以利用重试机制,找到正确的服务,更符合分布式的高可用要求
2、eureka 集群之间各个节点相互平等,zookeeper有主从之分
- 如果zookeeper集群中Leader服务器失效时,会重新从Follower服务器中选举一个新的服务器作为Leader服务器。因此可能导致整个集群因选主而阻塞,服务不可用。
- eureka 集群各个节点是平等的关系,即使有服务宕机,其他服务也不会受到影响。
3、eureka 中服务发现者是去主动的拉取服务,zookeeper服务发现者是基于监听机制。
- eureka 中获取服务列表后会缓存起来,每隔30秒重新拉取服务列表。
- zookeeper 是监听节点信息变化,当服务节点信息发生变化时,客户端立即受到通知。
nacos和 eureka的区别
相同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测。
不同点
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时,这样就会保持数据的准时性。Eureka会定时向注册中心定时拉去服务,如果不主动拉去服务,注册中心不会主动推送
- Eureka中会定时向注册中心发送心跳,如果在短期内没有发送心跳,则就会直接剔除。Nacos也会向注册中心发送心跳,但是它的频率要比Eureka快。在Nacos中又分为临时实例和非临时实例。如果是临时实例的话,短期内没有发送心跳,则会直接剔除。但是如果是非临时实例长时间宕机,不会直接剔除,并且注册中心会直接主动询问并且等待非临时实例。
eureka为什么是AP
先说CAP定律。
Eureak选择AP 保证了可用性降低了一致性 ,Eureka选择优先保证可用性,保证的是最终一致性,不是强一致性。Eureka只专注于服务注册和发现,并不会出现数据的竞态条件(有同时多个线程竞争同一数据,可能会发生数据不正确的问题)。每个client注册时,只会向一个server注册,所以不会出现竟态条件。Eureka server 只需要将其他没有注册到本身的节点数据取过来,并不会出现 在别的节点拉取到和自己数据不一致的问题。
eureka如何保证可用性
Eureka的节点之间是平等的。如果某个节点服务器宕机了,会让请求自动切换到另外的可用Eureka节点上,等到宕机的节点恢复后,再将其纳入集群中,只是不保证查询到的信息可能不是最新的。同时,如果15分钟内超过85%的节点都没有心跳,那么Eureka就认为客户端和注册中心出现了网络故障,就执行一下策略:
Eureka不在从注册列表中移除因为长时间没收到心跳而应该过期的服务
Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上 (即保证当前节点依然可用)
当网络稳定时,当前实例新的注册信息会被同步到其他节点中
Sentinel
Sentinel概念
Sentinel 是面向分布式服务架构的轻量级高可用流量控制产品,由阿里中间件团队开源。主要以流量为切入点,从**流量限制、服务熔断降级、**系统负载保护等多个维度来帮助您保护服务的稳定性。
什么是服务限流、服务熔断、服务降级、服务雪崩
服务限流:在接口访问超过设置的阈值,走服务降级fallback方法
服务熔断:接口出现异常或者处理时间过长,直接熔断,走服务降级fallback方法
服务降级:在服务限流或者服务熔断的情况下,走服务降级fallback方法,一段时间内不再走业务逻辑方法
服务雪崩:默认的情况下,一个服务器只有一个线程池,在高并发情况下,一个接口访问次数过多,把线程池线程全部占用了,导致其他接口不可用,造成服务雪崩。黑客攻击同一个接口
解决方法:线程池隔离或者信号量隔离
线程池隔离就是每个接口设置一个线程池,这样占用内存非常大
信号量隔离就是每个接口设置一个阈值,超过阈值直接走服务降级
Sentinel原理
Sentinel作用
用来 提供流控、服务降级、熔断能力,为系统提供防护。
固定时间窗算法
固定时间窗口计数器算法思想,在固定的时间窗口内,可以允许固定数量的请求进入。超过数量就拒绝或者排队,等下一个时间段进入。
存在的问题:两个时间窗之间的和 超过阈值,这种固定时间窗无法处理这部分超出的请求,解决办法就是使用滑动时间窗。
滑动时间窗算法
其没有划分固定的时间窗起点与终点,而是将每一次请求的到来时间点作为统计时间窗的终点,起点则是终点向前推时间窗长度的时间点。这样的话就可以解决固定时间窗带来的问题。
原理:
将时间窗口划分为更小的时间片段,每过一个时间片段,时间窗口就会往右滑动一格,每个时间片段都有独立的计数器。我们在计算整个时间窗口内的请求总数时会累加所有的时间片段内的计数器。时间窗口划分的越细,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。
Sentinel的限流与Gateway的限流有什么差别
Gateway使用的是基于redis的令牌桶算法实现的限流。
Sentinel:
默认的限流是使用的滑动时间窗口算法实现限流
排队等待采用的是漏桶算法
热点参数限流采用的是令牌桶算法
服务降级有哪三种策略
1、rt(平均响应时间):一秒内接口的访问响应时间超过指定阈值,则触发服务熔断,调用服务降级方法,指定时间(时间窗口 秒)内,不能够再次访问接口,一秒内访问五次,五次的平均响应时间超过阈值,最大阈值为4.9秒,要改最大阈值要去改配置
2、异常比例:一秒内请求出现异常的比例超过指定阈值,服务降级,时间窗口(秒)-
3、异常次数:一分钟内请求出现异常的次数超过指定阈值,服务降级,时间窗口(分钟)-
热点参数如何限流
热点就是经常访问的数据,很多时候肯定是希望统计某个访问频次Top K数据并对其进行限流。
比如秒杀系统中的商品ID,对于热点商品那一瞬间的并发量是非常可怕的,因此必须要对其进行限流。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。
sentinel对比hystrix
对比内容 | Sentinel | Hystrix |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 不支持 |
流量整形 | 支持慢启动、匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
Gateway
对Gateway的理解
Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Gateway 是为了替代zuul。其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
Gateway 网关就是把各个服务对外提供的API汇聚起来,让外界看起来是一个统一的接口。同时也可以在网关中提供额外的功能。
网关由路由、 过滤器、断言组成
路由转发
接收外界请求,通过网关的路由转发,转发到后端的服务上。由一个ID,一个目的URL,一组断言工厂,一组filter组成。
过滤器
过滤器是对请求和响应进行修改处理。过滤器分为网关过滤器(Gateway Filter) 和全局过滤器(Global Filter)。
过滤器中默认提供了25内置功能。我们也可以自定义过滤器,但是要实现两个接口,ordered和globalfilter
对于我们来说比较常用的功能有网关的容错、限流以及请求及相应的额外处理。
执行流程
- GateWay Client 向 GateWay Server发送请求;
- 请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文;
- 然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping;
- RoutePredicateHandleMapping负责路由查找,并根据路由断言判断路由是否可用;
- 如果断言成功,有FilteringWebHandler创建过滤器链并调用;
- 请求会依次经过PreFilter–微服务–PostFilter的方法,最终返回响应。
为什么用Gateway
Gateway是zuul 的替换和升级产品,使用Netty实现异步IO。它是spring官方的一个产品,更加契合spring cloud。Gateway中明确区分了Router和Filter,并且内置了很多开箱即用的功能。例如内置了10中router,可以使我们我们根据Header 或者 Path 或者Host 来做路由。例如区分了网关过滤器(Gateway Filter) 和全局过滤器(Global Filter)
gateway功能
1、鉴权:做权限判断,校验权限信息;
2、路由:gateway网关根据路由和断言去转发请求到指定的服务,在gateway服务中的配置文件配置;
3、灰度发布、限流;
4、反向代理。
5、日志监控
6、负载、熔断。
gateway 的执行流程
- GateWay Client 向 GateWay Server发送请求;
- 请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文;
- 然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping;
- RoutePredicateHandleMapping负责路由查找,并根据路由断言判断路由是否可用;
- 如果断言成功,有FilteringWebHandler创建过滤器链并调用;
- 请求会依次经过PreFilter–微服务–PostFilter的方法,最终返回响应。
Gateway和zuul区别
相同点
底层都是servlet
两者均是web网关,处理的是http请求
不同点
1、gateway对比zuul多依赖了spring-webflux,在spring的支持下,功能更强大,内部实现了限流、负载均衡等,扩展性也更强,但同时也限制了仅适合于Spring Cloud套件。gateway支持流式编程和支持异步。zuul则可以扩展至其他微服务框架中,其内部没有实现限流、负载均衡等。
2、gateway支持异步。理论上gateway则更适合于提高系统吞吐量(但不一定能有更好的性能),最终性能还需要通过严密的压测来决定。zuul仅支持同步
3、gateway具有更好的扩展性,并且其已经发布了2.0.0的RELESE版本,稳定性也是非常好的。zuul已经停止维护了。
Gateway如何实现限流
Gateway是使用令牌桶算法实现的限流。Gateway默认使用redis的RateLimter限流算法来实现。使用过程中我们主要配置令牌桶填充的速率,令牌桶的容量,指定限流的key(可以根据用户、IP、接口来限流)
令牌桶算法
1、进水口按照固定速率向桶中放入令牌。
2、令牌的容量是固定的,但是放行的速度不是固定的,只要令牌桶中的令牌还有剩余,一旦请求过来就能申请成功,然后放行。
3、如果令牌的发放速度慢于请求到来的速度,桶内就无令牌可领,请求就会被拒绝。
使用Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法来完成限流
漏铜算法
请求(水)先进入到漏桶中,漏桶以一定的速度(匀速)响应接口(漏桶出水),当访问频率超过接口响应速率,然后就拒绝请求。漏桶算法能强行限制数据的传输效率。
漏铜和令牌桶的区别
漏桶算法能够强行限制数据的传输速率,可以保证服务不会被击垮。但是当流出速度固定,大规模持续突发量,无法多余处理,浪费网络带宽。
令牌桶在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在令牌桶算法中,只要令牌桶中存在令牌,那么就允许突发的传输速率直接达到用户的配置门限,它适合具有突发特性的流量。
Sentinel的限流与Gateway的限流有什么差别
Gateway使用的是基于redis的令牌桶算法实现的限流。
Sentinel:
默认的限流是使用的滑动时间窗口算法实现限流
排队等待采用的是漏桶算法
热点参数限流采用的是令牌桶算法
Gateway和nginx区别
Gateway: 微服务网关,事项微服务的统一路由,统一鉴权,跨域,限流等功能。
Nginx:高性能HTTP和反向代理的web服务器,处理高并发能力是十分强大,最高能支持5w个并发连接数。是用户到前端工程的网关,对外网关。用于反向代理,负载均衡
怎么用Gateway做负载均衡
通过Gateway实现负载均衡有两种方式,一种是自动的负载均衡,一种是手动的负载均衡。
自动负载均衡
Gateway在开启了自动路由之后(discovery.locator.enabled=true)自带负载均衡功能。
手动负载均衡
自动负载均衡会暴露当前服务名称,并且太过于灵活。
手动配置需要开启自动路由(discovery.locator.enabled= true),并且配置了路由routes规则之后,会根据断言地址匹配对应的服务,就不会走自动路由配置。
GateWay跨域
1、修改配置文件
spring:
cloud:
gateway:
# gateway的全局跨域请求配置
globalcors:
corsConfigurations:
'[/**]':
allowedHeaders: "*"
allowedOrigins: "*"
allowCredentials: true
allowedMethods: "*"
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials Vary, RETAIN_UNIQUE
Feign
对Feign的理解
Feign是一种声明式、模板化的HTTP客户端技术,是http请求调用的轻量级框架,可以 以java 接口注解的方式调用http请求,而不用像Java中,通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求。Feign被广泛应用于Spring Cloud的解决方案中。
Feign封装了Http调用流程,更适合面向接口化的变成习惯。
Feign底层依赖于java的动态代理机制,对原生Java Socket 或者 Apache httpClient 进行封装, 实现了基于HTTP协议的远程调用。Feign在此基础上还实现了负载均衡、熔断等机制。
优点
1、声明式Http Client 相对于编程式Http Client 代码逻辑更加简单,不需要处理复杂的编码请求和响应,只需要像调用本地方法一样,提高效率
2、几种管理Http请求方法,代码边界更清晰。
3、更好的集成了负载均衡、熔断降级等功能。
缺点
在默认情况下,Feign采用的是JDK的HttpURLConnection,所以整体性能并不高。
如何使用Feign
- 首先,调用以及被调用的微服务双方都应该被注册到注册中心。
- Spring Boot启动APP上标注
@EnableFeignClients
注解。 - 编写远程调用接口并标注
@FeignClient
注解。(括号内添加所要调用的微服务名称) - 接口中的方法为实际想要调用的服务的方法签名,并使用
@PostMapping
注解映射为一个post类型的HTTP请求。
Feign实现远程调用的原理
核心原理就是通过一系列的封装和处理,将以Java注解的方式定义的远程调用API接口,最终转化为HTTP的请求与响应结果。
Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request 请求。
- 微服务启动时,feign对添加了
@FeignClient
的接口扫描,创建远程接口的本地JDK Proxy代理实例。然后注入到Spring IOC容器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果。 - Feign的方法处理器
MethodHandler
。它用来解析方法上的url
,以及@postMapping
注解中包含的数据,并生成一个http请求模板。 - 在
MethodHandler
中具体由requestTemplate
发起request请求。Request 经过编码交给httpclient
发送到远程调用服务。
如何解决远程调用的负载均衡问题
feign内部有Ribbon实现了客户端的负载均衡。从注册中心读取所有可用的服务提供者,在客户端每次调用接口时采用如轮询负载均衡算法选出一个服务提供者调用,因此,Ribbon
是一个客户端负载均衡器。
常见的负载均衡策略有:
- 轮询:可能会导致性能较弱的服务器过载
- 加权轮询:可以为每台服务器分配权重,能力较弱的服务器分配较少的连接请求
- 最少连接数:
- 加权最少连接
Feign如何实现熔断
SpringCloud Fegin默认已为Feign整合了hystrix。我们需要在在配置文件中添加配置 feign.hystrix.enabled = true 。然后定义一个回滚类 ,交给spring 管理,实现定义的feign 接口,在feign 接口中的@FeignClient 注解中 ,fallback 属性 设置为 定义的回滚类。
OpenFeign与Feign的关系
feign不是一个中间件,feign是spring cloud组件中的一个轻量级restful的http服务客户端。其作用是简化接口的调用,将http调用转为rpc调用,让调用远程接口像同进程应用内的接口调用一样简单。
openfeign是spring cloud在feign的基础上支持了spring mvc的注解,如@RequesMapping、@GetMapping、@PostMapping等。openfeign还实现与Ribbon的整合。服务提供者只需要提供API接口,而不需要像dubbo那样需要强制使用implements实现接口,即使用fegin不要求服务提供者在Controller使用implements关键字实现接口。
Skywalking
Skywalking主要用于追踪和监测分布式系统。它可以帮助开发和运维团队通过可视化数据去理解他们系统的行为,监控系统健康,以及在出现问题时提供快速故障排除的能力。
模块或者架构
Agent:路数据采集tracing(调用链数据)和metric(指标)信息并上报,上报通过HTTP或者gRPC方式发送数据到Skywalking Collector
Collector:链路数据收集器,对agent传过来的tracing和metric数据进行整合分析,通过Analysis Core模块并落入相关的数据存储中,同时会通过Query Core模块进行二次统计和监控告警。
Storage:支持以ElasticSearch、Mysql、TiDB、H2等主流存储作为存储介质进行数据存储,H2仅作为临时演示单机用
UI:Web可视化平台,用来展示落地的数据。
原理或者流程
SkyWalking中的“追踪”是如何工作的?
Agent是什么
Agent是如何与应用服务交互的
SkyWalking如何追踪跨服务的请求
SkyWalking如何对跟踪数据进行采样?采样的策略有哪些?
对spring cloud的理解
spring cloud 是spring 官方推出的一种微服务的解决方案,spring cloud 是对微服务中出现的各种技术场景定于的一套标准规范。在这个标准中,spring集成了 奈飞的开源套件,例如zuul实现应用网关,eureka 实现服务注册和发现,ribbon实现负载均衡,hystrix实现服务熔断。我们可以会用这套组件去快速落地微服务架构,以及去解决微服务治理的一系列问题。随着一些组件的闭源和停止维护,soring官方也自研了一些组件,例如gateway替换zuul来实现网关,loadBalancer实现负载均衡。另外阿里巴巴也有一套微服务解决方案,例如nacos实现服务注册和发现,sentinel实现服务限流和降级。spring cloud统一了微服务架构中的标准和兼容性。
微服务之间是如何通讯的
1、远程过程调用(Remote Procedure Invocation):直接通过远程过程调用来访问别的service。例如:REST、gRPC、Apache。使用这种方式简更加单,常见。因为没有中间件代理,系统更简单。但是这种方式只支持请求/响应的模式,不支持别的,比如通知、请求/异步响应、发布/订阅、发布/异步响应。降低了可用性,因为客户端和服务端在请求过程中必须都是可用的
2、消息。使用异步消息来做服务间通信。服务间通过消息管道来交换消息,从而通信。例如:Kafka、MQ。这种方式可以把客户端和服务端解耦,更松耦合 提高可用性,因为消息中间件缓存了消息,直到消费者可以消费。另外还支持很多通信机制比如通知、请求/异步响应、发布/订阅、发布/异步响应。但是消息中间件有额外的复杂性。
怎么理解分布式和微服务,为什么要拆分服务,会产生什么问题,怎么解决这些问题
分布式
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。 分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。
只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统
微服务
微服务的出现就是因为原来单体应用架构已经无法满足当前互联网产品的技术需求。微服务的设计就是为了不因为某个模块的升级和BUG影响现有的系统业务。
微服务架构,核心就是为了解决应用微服务化之后的服务治理问题。
产生的问题
服务发现问题:一个微服务如何发现其他微服务呢?通过服务注册中心,所有服务都注册到服务注册中,同时也可以从服务注册中心获取当前可用的服务清单。例如Eureka、Nacos。
服务配置管理的问题:当服务数量超过一定程度之后,如果需要在每个服务里面分别维护每一个服务的配置文件,我们需要通过一个配置中心统一管理这些配置文件。例如:Nacos的配置中,spring cloud 的 config。
分布式事务:多个服务之间事务的一致性问题。传统的事务注解不能控制另外一个服务的事务,这个时候我们就需要分布式事务,例如SEATA、LCN。
怎么理解高可用,如何保证高可用,有什么弊端,熔断机制,怎么实现
高可用(HA)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。而服务器的可用性是指单位时间内(通常一年),服务器可以正常工作的时间比例。
想要实现高可用就要避免使用单点,所以需要多台机器也就是需要集群,方法论中叫冗余。如果有冗余备份,宕机了还有其他backup能够顶上,才可能实现高可用。有了冗余之后,还不够,每次出现故障需要人工介入恢复势必会增加系统的不可服务实践。所以,又往往是通过“自动故障转移”来实现系统的高可用。
(1)【客户端层】到【反向代理层】的高可用,是通过反向代理层的冗余实现的,常见实践是keepalived + virtual IP自动故障转移
(2)【反向代理层】到【站点层】的高可用,是通过站点层的冗余实现的,常见实践是nginx与web-server之间的存活性探测与自动故障转移
(3)【站点层】到【服务层】的高可用,是通过服务层的冗余实现的,常见实践是通过service-connection-pool来保证自动故障转移
(4)【服务层】到【缓存层】的高可用,是通过缓存数据的冗余实现的,常见实践是缓存客户端双读双写,或者利用缓存集群的主从数据同步与sentinel保活与自动故障转移;更多的业务场景,对缓存没有高可用要求,可以使用缓存服务化来对调用方屏蔽底层复杂性
(5)【服务层】到【数据库“读”】的高可用,是通过读库的冗余实现的,常见实践是通过db-connection-pool来保证自动故障转移
(6)【服务层】到【数据库“写”】的高可用,是通过写库的冗余实现的,常见实践是keepalived + virtual IP自动故障转移
什么是服务的灾难性的雪崩效应
在微服务架构中,一个请求需要调用多个服务是非常常见的。如客户端访问A服务,而A服务需要调用B服务,B服务需要调用C服务,由于网络原因或者自身的原因,如果B服务或者C服务不能及时响应,A服务将处于阻塞状态,直到B服务C服务响应。此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
服务降级、服务熔断、缓存。
CAP BASE理论
CAP定律
CAP定律是指在一个分布式系统中,Consistency(一致性),availablllity(可用性)、Partition tolerance(分区容错性)三者不可兼得。要么满足CP,要么满足AP。
- consistency(一致性:C):在分布式系统中的所有备份数据,在同一时刻是否同样的值(等同于所有节点访问同一份最新的数据副本)
- availablltity(可用性:A):在集群中一部分节点故障后,集群整体是否还能影响客户端的读写请求(对数据更新具备高可用)
- Partition tolerance(分区容错性:P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,那么就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
BASE理论
BASE理论是basically available(基本可用),soft state(软状态)和eventually consister (最终一致性)。BASE理论是对CAP定律中一致性和可用性权衡的结果。其来源于大规模的互联网总结,是基于CAP定律逐步演化而来的。BASE的核心思想是:即时无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
- 基本可用:值分布式系统在出现不可预知的故障时,允许损失部分可用性。但是这绝不等于系统不可用。
例如:
1、响应时间的损失。正常情况下,查询需要0.5秒返回给用户查询结果,但是出现故障,查询的响应时间增加了1~2秒。
2、系统功能上的损失。正常情况下,消费者能正常完成一单任务。但是在特定的时间会有流量高峰,为了保护系统的稳定性,部分消费者可能会被引导到一个降级页面。
- 软状态:允许系统中存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同的节点的数据副本之间进行数据同步的过程存在延时。
- 最终一致性:最终一致性强调的是所有数据副本,在经过一定时间的同步之后,最终都能达到一个一致性状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
你觉得注册中心应该是CP还是AP
我觉得大部分情况下,注册中心应该是AP,如果注册中心是CP的,那么表示,当我们向注册中心注册实例或移除实例时,都要等待注册中心集群中的数据达到一致后,才算注册或移除成功,而这是比较耗时的,随着业务应用规模的增大,应用频繁的上下线,那么就会导致注册中心的压力比较大,会影响到服务发现的效率以及服务调用了,而如果注册中心是AP的,那么注册中心集群不管出现了什么情况,都是可以提供服务的,就算集群节点之间数据出现了不一致,对于业务应用而言,可能拉取到了一个已经下线了的服务节点,但是现在一般的微服务框架或组件都提供了服务容错和重试功能,也可以避免这个问题,而如果是AP,对于注册中心而言就不需要消耗太多的资源来实时的保证数据一致性了,保证最终一致性就可以了,这样注册中心的压力会小一点。
另外像Zookeeper来作为注册中心,因为Zookeeper保证的就是CP,但是如果集群中如果大多数节点挂掉了,就算还剩下一些Zookeeper节点,这些节点也是不能提供服务的,所以这个也不太合适,所以综合来看,注册中心应该保证AP会更好,就像Euraka、Nacos他们默认保证的就是AP。
Nacos、Eureka、Zookeeper
Nacos | Eureka | Zookeeper | |
---|---|---|---|
CAP一致性协议 | CP+AP | AP | CP |
雪崩保护 | 有 | 有 | 无 |
自动注销实例 | 支持 | 支持 | 支持 |
访问协议 | HTTP/DNS | HTTP | TCP |
监听支持 | 支持 | 支持 | 支持 |
多数据中心 | 支持 | 支持 | 不支持 |
跨注册中心同步 | 支持 | 不支持 | 不支持 |
SpringCloud集成 | 支持 | 支持 | 支持 |
Dubbo集成 | 支持 | 不支持 | 支持 |
K8S集成 | 支持 | 不支持 | 不支持 |
常见的限流算法有哪些
限流算法是一种系统保护策略,主要是避免在流量高峰导致系统被压垮,造成系统不可用的问题。
计数器限流:一般用在单一维度的访问频率限制上,比如短信验证码6秒一次,或者接口调用的次数(调用一次加一,逻辑结束减一)。
滑动窗口限流:本质上也是一种计数器,只是通过以时间为维度的可滑动窗口设计,来减少了临界值带来的并发超过阈值的问题。
漏桶算法限流:它是一种恒定速率的限流算法,不管请求量是多少,服务端的处理效率是恒定的。
令牌桶算法限流:相对漏桶算法来说,它可以处理突发流量的问题。它的核心思想是,令牌桶以恒定速率去生成令牌保存到令牌桶里面,桶的大小是固定的,令牌桶满了以后就不再生成令牌。每个客户端请求进来的时候,必须要从令牌桶获得一个令牌才能访问,否则排队等待