从服务间的一次调用分析整个springcloud的调用过程(一)
首先我们知道springcloud是一个微服务框架,按照官方文档的说法,springcloud提供了一些开箱即用的功能:
1 分布式/版本化配置
2 服务的注册与发现
3 路由
4 服务到服务之间调用
5 负载均衡
6 断路器
7 分布式消息
本文我们讲解的组件注册中心用的是阿里的nacos(关于nacos的安装可自行百度),网关用的就是springcloudgateway,负载均衡ribbon,断路器hystrix,客户端调用目标是openfeign
一 先看服务的注册
服务的注册核心的类就是spring-cloud-commons下的两个类
org.springframework.cloud.client.serviceregistry.ServiceRegistry
org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration
ServiceRegistry是一个接口,接口中定义了如何向注册中心注册服务
package org.springframework.cloud.client.serviceregistry; /**
* Contract to register and deregister instances with a Service Registry.
*
* @author Spencer Gibb
* @since 1.2.0
*/
public interface ServiceRegistry<R extends Registration> { /**
* Registers the registration. A registration typically has information about
* an instance, such as its hostname and port.
* @param registration The registration.
*/
void register(R registration); /**
* Deregisters the registration.
* @param registration
*/
void deregister(R registration); /**
* Closes the ServiceRegistry. This is a lifecycle method.
*/
void close(); /**
* Sets the status of the registration. The status values are determined
* by the individual implementations.
*
* @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
* @param registration The registration to update.
* @param status The status to set.
*/
void setStatus(R registration, String status); /**
* Gets the status of a particular registration.
*
* @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
* @param registration The registration to query.
* @param <T> The type of the status.
* @return The status of the registration.
*/
<T> T getStatus(R registration);
}
AbstractAutoServiceRegistration则是springcloud向注册中心注册服务的启动逻辑


package org.springframework.cloud.client.serviceregistry; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.PreDestroy; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException;
import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.cloud.client.discovery.ManagementServerPortUtils;
import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment; /**
* Lifecycle methods that may be useful and common to {@link ServiceRegistry}
* implementations.
*
* TODO: Document the lifecycle.
*
* @param <R> Registration type passed to the {@link ServiceRegistry}.
*
* @author Spencer Gibb
*/
public abstract class AbstractAutoServiceRegistration<R extends Registration>
implements AutoServiceRegistration, ApplicationContextAware, ApplicationListener<WebServerInitializedEvent> {
private static final Log logger = LogFactory
.getLog(AbstractAutoServiceRegistration.class); private boolean autoStartup = true; private AtomicBoolean running = new AtomicBoolean(false); private int order = 0; private ApplicationContext context; private Environment environment; private AtomicInteger port = new AtomicInteger(0); private final ServiceRegistry<R> serviceRegistry;
private AutoServiceRegistrationProperties properties; @Deprecated
protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry) {
this.serviceRegistry = serviceRegistry;
} protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry, AutoServiceRegistrationProperties properties) {
this.serviceRegistry = serviceRegistry;
this.properties = properties;
} protected ApplicationContext getContext() {
return context;
} @Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
bind(event);
} @Deprecated
public void bind(WebServerInitializedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(
((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
return;
}
}
this.port.compareAndSet(0, event.getWebServer().getPort());
this.start();
} @Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.context = applicationContext;
this.environment = this.context.getEnvironment();
} @Deprecated
protected Environment getEnvironment() {
return environment;
} @Deprecated
protected AtomicInteger getPort() {
return port;
} public boolean isAutoStartup() {
return this.autoStartup;
} public void start() {
if (!isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
} // only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get()) {
this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
register();
if (shouldRegisterManagement()) {
registerManagement();
}
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
} } /**
* @return Whether the management service should be registered with the
* {@link ServiceRegistry}.
*/
protected boolean shouldRegisterManagement() {
if (this.properties == null || this.properties.isRegisterManagement()) {
return getManagementPort() != null
&& ManagementServerPortUtils.isDifferent(this.context);
}
return false;
} /**
* @return The object used to configure the registration.
*/
@Deprecated
protected abstract Object getConfiguration(); /**
* @return True, if this is enabled.
*/
protected abstract boolean isEnabled(); /**
* @return The serviceId of the Management Service.
*/
@Deprecated
protected String getManagementServiceId() {
// TODO: configurable management suffix
return this.context.getId() + ":management";
} /**
* @return The service name of the Management Service.
*/
@Deprecated
protected String getManagementServiceName() {
// TODO: configurable management suffix
return getAppName() + ":management";
} /**
* @return The management server port.
*/
@Deprecated
protected Integer getManagementPort() {
return ManagementServerPortUtils.getPort(this.context);
} /**
* @return The app name (currently the spring.application.name property).
*/
@Deprecated
protected String getAppName() {
return this.environment.getProperty("spring.application.name", "application");
} @PreDestroy
public void destroy() {
stop();
} public boolean isRunning() {
return this.running.get();
} protected AtomicBoolean getRunning() {
return running;
} public int getOrder() {
return this.order;
} public int getPhase() {
return 0;
} protected ServiceRegistry<R> getServiceRegistry() {
return this.serviceRegistry;
} protected abstract R getRegistration(); protected abstract R getManagementRegistration(); /**
* Register the local service with the {@link ServiceRegistry}.
*/
protected void register() {
this.serviceRegistry.register(getRegistration());
} /**
* Register the local management service with the {@link ServiceRegistry}.
*/
protected void registerManagement() {
R registration = getManagementRegistration();
if (registration != null) {
this.serviceRegistry.register(registration);
}
} /**
* De-register the local service with the {@link ServiceRegistry}.
*/
protected void deregister() {
this.serviceRegistry.deregister(getRegistration());
} /**
* De-register the local management service with the {@link ServiceRegistry}.
*/
protected void deregisterManagement() {
R registration = getManagementRegistration();
if (registration != null) {
this.serviceRegistry.deregister(registration);
}
} public void stop() {
if (this.getRunning().compareAndSet(true, false) && isEnabled()) {
deregister();
if (shouldRegisterManagement()) {
deregisterManagement();
}
this.serviceRegistry.close();
}
}
}
从代码可以看出该类实现了ApplicationListener来监听WebServerInitializedEvent,最终调用register方法来注册服务,register方法就是通过ServiceRegistry接口的实现,比如阿里nacos的实现类com.alibaba.cloud.nacos.registry.NacosServiceRegistry


/*
* Copyright (C) 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package com.alibaba.cloud.nacos.registry; import java.util.List; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.util.StringUtils; import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance; /**
* @author xiaojing
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class NacosServiceRegistry implements ServiceRegistry<Registration> { private static final Logger log = LoggerFactory.getLogger(NacosServiceRegistry.class); private final NacosDiscoveryProperties nacosDiscoveryProperties; private final NamingService namingService; public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.namingService = nacosDiscoveryProperties.namingServiceInstance();
} @Override
public void register(Registration registration) { if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
} String serviceId = registration.getServiceId(); Instance instance = getNacosInstanceFromRegistration(registration); try {
namingService.registerInstance(serviceId, instance);
log.info("nacos registry, {} {}:{} register finished", serviceId,
instance.getIp(), instance.getPort());
}
catch (Exception e) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
}
} @Override
public void deregister(Registration registration) { log.info("De-registering from Nacos Server now..."); if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No dom to de-register for nacos client...");
return;
} NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
String serviceId = registration.getServiceId(); try {
namingService.deregisterInstance(serviceId, registration.getHost(),
registration.getPort(), nacosDiscoveryProperties.getClusterName());
}
catch (Exception e) {
log.error("ERR_NACOS_DEREGISTER, de-register failed...{},",
registration.toString(), e);
} log.info("De-registration finished.");
} @Override
public void close() { } @Override
public void setStatus(Registration registration, String status) { if (!status.equalsIgnoreCase("UP") && !status.equalsIgnoreCase("DOWN")) {
log.warn("can't support status {},please choose UP or DOWN", status);
return;
} String serviceId = registration.getServiceId(); Instance instance = getNacosInstanceFromRegistration(registration); if (status.equalsIgnoreCase("DOWN")) {
instance.setEnabled(false);
}
else {
instance.setEnabled(true);
} try {
nacosDiscoveryProperties.namingMaintainServiceInstance()
.updateInstance(serviceId, instance);
}
catch (Exception e) {
throw new RuntimeException("update nacos instance status fail", e);
} } @Override
public Object getStatus(Registration registration) { String serviceName = registration.getServiceId();
try {
List<Instance> instances = nacosDiscoveryProperties.namingServiceInstance()
.getAllInstances(serviceName);
for (Instance instance : instances) {
if (instance.getIp().equalsIgnoreCase(nacosDiscoveryProperties.getIp())
&& instance.getPort() == nacosDiscoveryProperties.getPort())
return instance.isEnabled() ? "UP" : "DOWN";
}
}
catch (Exception e) {
log.error("get all instance of {} error,", serviceName, e);
}
return null;
} private Instance getNacosInstanceFromRegistration(Registration registration) {
Instance instance = new Instance();
instance.setIp(registration.getHost());
instance.setPort(registration.getPort());
instance.setWeight(nacosDiscoveryProperties.getWeight());
instance.setClusterName(nacosDiscoveryProperties.getClusterName());
instance.setMetadata(registration.getMetadata());
return instance;
} }
我们可以看到NacosServiceRegistry.register方法最终会调用NamingService.registerInstance


@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException { if (instance.isEphemeral()) {
BeatInfo beatInfo = new BeatInfo();
beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
beatInfo.setIp(instance.getIp());
beatInfo.setPort(instance.getPort());
beatInfo.setCluster(instance.getClusterName());
beatInfo.setWeight(instance.getWeight());
beatInfo.setMetadata(instance.getMetadata());
beatInfo.setScheduled(false);
long instanceInterval = instance.getInstanceHeartBeatInterval();
beatInfo.setPeriod(instanceInterval == 0 ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval); beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
} serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}
该方法判断isEphemeral,是true的话会向beatReactor新增一个当前service的心跳任务,作用就是通过定时任务向注册中心发一个心跳请求,表示服务仍然存活,这个定时任务的间隔时间默认为5秒,默认值定义在com.alibaba.nacos.api.common.Constants里面,我们可以自定义该配置,spring.cloud.nacos.discovery.heart-beat-interval=1000,这个时间如果太长可能服务已经出问题了,但是注册中心无法感知,导致请求还会打到该节点,太短的话可能会对注册中心造成压力
最终向注册中心注册服务就是发起一个简单的http请求,具体细节大家可以看源代码
二 再看springcloud的feign配置加载
feign的启用通过注解EnableFeignClients,该注解会通过import FeignClientsRegistrar来加载feign的配置,其中FeignClientsRegistrar是对接口ImportBeanDefinitionRegistrar的一个实现,该接口相当于自定义对某一包下bean的自定义装配,比如这里的FeignClientsRegistrar就是对注解中basePackages下的feign clientbean的自定义装配。
其它关于对feign的配置加载,可参考我的另一篇文章 https://www.cnblogs.com/minjay/p/15746223.html
三 ribbon的配置加载
ribbon是负载均衡工具,在ribbon之前spring首先会先配置LoadBalancerAutoConfiguration,如下代码


/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package org.springframework.cloud.netflix.ribbon; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List; import com.netflix.client.IClient;
import com.netflix.client.http.HttpRequest;
import com.netflix.ribbon.Ribbon; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.RestTemplate; /**
* Auto configuration for Ribbon (client side load balancing).
*
* @author Spencer Gibb
* @author Dave Syer
* @author Biju Kunjummen
*/
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration { @Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>(); @Autowired
private RibbonEagerLoadProperties ribbonEagerLoadProperties; @Bean
public HasFeatures ribbonFeature() {
return HasFeatures.namedFeature("Ribbon", Ribbon.class);
} @Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
} @Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
} @Bean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(final SpringClientFactory clientFactory) {
return new RibbonLoadBalancedRetryFactory(clientFactory);
} @Bean
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {
return new PropertiesFactory();
} @Bean
@ConditionalOnProperty(value = "ribbon.eager-load.enabled")
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
return new RibbonApplicationContextInitializer(springClientFactory(),
ribbonEagerLoadProperties.getClients());
} @Configuration
@ConditionalOnClass(HttpRequest.class)
@ConditionalOnRibbonRestClient
protected static class RibbonClientHttpRequestFactoryConfiguration { @Autowired
private SpringClientFactory springClientFactory; @Bean
public RestTemplateCustomizer restTemplateCustomizer(
final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
return restTemplate -> restTemplate.setRequestFactory(ribbonClientHttpRequestFactory);
} @Bean
public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
return new RibbonClientHttpRequestFactory(this.springClientFactory);
}
} //TODO: support for autoconfiguring restemplate to use apache http client or okhttp @Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnRibbonRestClientCondition.class)
@interface ConditionalOnRibbonRestClient { } private static class OnRibbonRestClientCondition extends AnyNestedCondition {
public OnRibbonRestClientCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
} @Deprecated //remove in Edgware"
@ConditionalOnProperty("ribbon.http.client.enabled")
static class ZuulProperty {} @ConditionalOnProperty("ribbon.restclient.enabled")
static class RibbonProperty {}
} /**
* {@link AllNestedConditions} that checks that either multiple classes are present
*/
static class RibbonClassesConditions extends AllNestedConditions { RibbonClassesConditions() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
} @ConditionalOnClass(IClient.class)
static class IClientPresent { } @ConditionalOnClass(RestTemplate.class)
static class RestTemplatePresent { } @ConditionalOnClass(AsyncRestTemplate.class)
static class AsyncRestTemplatePresent { } @ConditionalOnClass(Ribbon.class)
static class RibbonPresent { } }
}
LoadBalancerAutoConfiguration会自动帮我们注入支持负载均衡的RestTemplate以及配置重试retry相关。
加载完LoadBalancerAutoConfiguration后会自动配置RibbonAutoConfiguration,其中很核心的类就是装载了bean LoadBalancerClient为RibbonLoadBalancerClient,LoadBalancerClient接口是spring自己定义的一个接口,但是我发现这个接口的实现在整个调用过程中都用不到,还望哪位大佬能指导下这个实现机制在什么时候会用的到
还有一个核心配置就是SpringClientFactory,这个类的作用就是在请求时根据你的serviceId去加载相关配置,比如连接超时和读取超时
从服务间的一次调用分析整个springcloud的调用过程(一)的更多相关文章
- 从服务间的一次调用分析整个springcloud的调用过程(二)
先看示例代码 @RestController @RequestMapping("/students") public class StudentController { @Auto ...
- Android服务之PackageManagerService启动源码分析
了解了Android系统的启动过程的读者应该知道,Android的所有Java服务都是通过SystemServer进程启动的,并且驻留在SystemServer进程中.SystemServer进程在启 ...
- spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法
spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法 前言 本篇接着<spring boot / cloud ...
- Asp.Net Core使用SignalR进行服务间调用
网上查询过很多关于ASP.NET core使用SignalR的简单例子,但是大部分都是简易聊天功能,今天心血来潮就搞了个使用SignalR进行服务间调用的简单DEMO. 至于SignalR是什么我就不 ...
- SpringCloud服务间调用
SpringCloud服务间的调用有两种方式:RestTemplate和FeignClient.不管是什么方式,他都是通过REST接口调用服务的http接口,参数和结果默认都是通过jackson序列化 ...
- SpringCloud初体验:三、Feign 服务间调用(FeignClient)、负载均衡(Ribbon)、容错/降级处理(Hystrix)
FeignOpenFeign Feign是一种声明式.模板化的HTTP客户端. 看了解释过后,可以理解为他是一种 客户端 配置实现的策略,它实现 服务间调用(FeignClient).负载均衡(Rib ...
- spring boot项目使用swagger-codegen生成服务间调用的jar包
swagger-codegen的github:https://github.com/swagger-api/swagger-codegen 需要的环境:jdk > 1.7 maven > ...
- SpringCloud实现服务间调用(RestTemplate方式)
上一篇文章<SpringCloud搭建注册中心与服务注册>介绍了注册中心的搭建和服务的注册,本文将介绍下服务消费者调用服务提供者的过程. 本文目录 一.服务调用流程二.服务提供者三.服务消 ...
- SpringCloud 服务间互相调用 @FeignClient注解
SpringCloud搭建各种微服务之后,服务间通常存在相互调用的需求,SpringCloud提供了@FeignClient 注解非常优雅的解决了这个问题 首先,保证几个服务都在一个Eureka中注册 ...
随机推荐
- MongoDB学习 - 简单使用
1.项目引入pom依赖 <!-- mongodb --><dependency> <groupId>org.springframework.boot</gro ...
- Centos7 暂时记录
chown 修改属主和属组信息 chown -R 对目录所有子的子目录和文件进行修改属主信息 w命令 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHATroot ...
- Python常用功能函数系列总结(三)
本节目录 常用函数一:词频统计 常用函数二:word2vec 常用函数三:doc2vec 常用函数四:LDA主题分析 常用函数一:词频统计 # -*- coding: utf-8 -*- " ...
- 从如何使用到如何实现一个Promise
前言 这篇文章我们一起来学习如何使用Promise,以及如何实现一个自己的Promise,讲解非常清楚,全程一步一步往后实现,附带详细注释与原理讲解. 如果你觉的这篇文章有帮助到你,️关注+点赞️鼓励 ...
- kafka入门(采坑)笔记
前言 之前在工作和学习过程中也会有记笔记的习惯,但是没有发布出来,也因最近各方面的瓶颈急需突破和提升,所以还是要很积极的融入大环境大生态中,好废话不多说,说下这次遇到的问题 第一步启动zk 根据教程安 ...
- 判断jquery类库是否加载,如未加载则加载。
本人所有文章使用到的东西均在"渭南电脑维修网"网站中得以实现和应用,还请大家参考. 抄写别人网站的同时,N多不同的网站,势必有N多的css.javascript引用文件都会重复引用 ...
- vue学习7-v-show和v-if
1. v-show:这个是一次性会把所有的都渲染出来,然后通过简单的切换display值来修改是否需要被渲染.所以在需要频繁切换的情况下推荐使用.v-show不能在template标签上使用. 2. ...
- 多线程-线程间通信-多生产者多消费者问题解决(notifyAll)
1 package multithread4; 2 3 /* 4 * 生产者,消费者. 5 * 6 * 多生产者,多消费者的问题. 7 * 8 * if判断标记,只有一次,会导致不该运行的线程运行了. ...
- Python反爬:利用js逆向和woff文件爬取猫眼电影评分信息
首先:看看运行结果效果如何! 1. 实现思路 小编基本实现思路如下: 利用js逆向模拟请求得到电影评分的页面(就是猫眼电影的评分信息并不是我们上述看到的那个页面上,应该它的实现是在一个页面上插入另外一 ...
- Homework_3 (完整版)
划水♂️!好耶! 果然还是逃不过作业,初三刚过就要营业 审题 爬虫+算法:划水中的员工 员工 A 此刻内心一酸,大年初一加班惨绝人寰,情不自禁打开 B 站,跟着网友一起划水看番. 但是由于技术故障原 ...