从服务间的一次调用分析整个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中注册 ...
随机推荐
- Pytest_在jenkins中使用allure报告(13)
一.安装allure插件 点击jenkins管理-->插件管理 点击Available,在搜索框中输入allure并安装 二.配置构建命令 三.构建配置allure插件 点击构建后置操作 pat ...
- webSocket 前端 js 加入 心跳机制 的基本写法
1前言 websocket 一般 每隔 90 秒无操作则会自动断开 ,需要加入一个心跳机制 来防止 自断 2. 实验过程 (1)设定一个jsp 或html 文件都行 ,加入元素 (2)js 源码 , ...
- POJ prime distance
https://oj.shiyancang.cn/Problem/781.html 素数距离,数据范围21亿,如果用素数筛存,并且进行做的话,按照x/lnx计算会是一个非常恐怖的复杂度.确定要做什么, ...
- 关于 Intel CPU 和Iris Xe Graphics的报告问题
关于 Intel CPU 和Iris Xe Graphics的报告问题 有些用户报告了一些技术问题,这里有更多的信息和如何解决. Intel 11th CPU & Iris Xe Graphi ...
- 内核内存分配器SLAB和SLUB
内核分配器的功能 在操作系统管理的虚拟内存中,用于内存管理的最小单位是页,大多数传统的架构是4KB.由于进程每次申请分配4KB是不现实的,比如分配几个字节或几十个字节,这时需要中间机制来管理页面的微型 ...
- 【经验总结-markdown】markdown字体和颜色设置
字体设置 关键词为face <font face = "黑体">我是黑体</font> <font face = "宋体"> ...
- 【初体验】macos下android ndk交叉编译hello world,并拷贝到android手机上执行
1.机器上以前安装了java 1.8(貌似android ndk不需要java) 2. 下载android ndk,版本是android-ndk-r14b (比较奇怪,我下载了最新的android-n ...
- 一种Django多租户解决方案
什么是多租户? 多租户技术或称多重租赁技术,简称SaaS,是一种软件架构技术,是实现如何在多用户环境下(此处的多用户一般是面向企业用户)共用相同的系统或程序组件,并且可确保各用户间数据的隔离性. 多租 ...
- web服务组件
- 【webpack4.0】---dev.config.js基本配置(六)
一.开发环境配置准备 1.创建dev.config.js文件 用来配置开发环境的代码 2.安装webpack-merge cnpm install webpack-merge -D 用来合并webpa ...