springboot-基础

springboot 的特性

1、自动装配

通过注解@EnableAutoConfiguration,里面有个注解@import

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered 
继承了importSelector类,
@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
        // 自动把springbootclasspath下的jar中的META-INF 下的spring.factories文件的配置
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

2、基于starter主键

3、springboot Actuator监控(endpoint)

4、命令行界面

eureka-client

1、入口EurekaClientAutoConfiguration 自动装配

初始化步骤

通过springboot的特性自动装配, 入口EurekaClientAutoConfiguration -->  EurekaClientConfiguration (内部类)

2、初始化CloudEurekaClient

如上图,初始化EurekaClient ->  new CloudEurakaClient

	public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
			EurekaClientConfig config, ApplicationEventPublisher publisher) {
		this(applicationInfoManager, config, null, publisher);
	}
	public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
			EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs<?> args,
			ApplicationEventPublisher publisher) {
		super(applicationInfoManager, config, args);
		this.applicationInfoManager = applicationInfoManager;
		this.publisher = publisher;
		this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class,
				"eurekaTransport");
		ReflectionUtils.makeAccessible(this.eurekaTransportField);
	}

3、DiscoveryClient构造函数

类的关系图如上, 执行父类DiscoveryClient构造函数

1、拉取eurake-service的其他服务的信息

1、拉取euraka-service上的其他服务实例信息 ,判断是否开启了想eurake-service拉取
// 可通过eureka.client.fetch-registry配置来是否开启拉eurake-service服务上的其他服务实例信息
if (clientConfig.shouldFetchRegistry()) {
            try {
                boolean primaryFetchRegistryResult = fetchRegistry(false);
                ....
            }
// fetchRegistry方法又分为全量更新和增量更新,全量更新,更新到缓存,看查看下全局和增量更新代码,如下
    try {
            // If the delta is disabled or if it is the first time, get all
            // applications
            Applications applications = getApplications();

            if (clientConfig.shouldDisableDelta()
                    || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
                    || forceFullRegistryFetch
                    || (applications == null)
                    || (applications.getRegisteredApplications().size() == 0)
                    || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
            {
                logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta());
                logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress());
                logger.info("Force full registry fetch : {}", forceFullRegistryFetch);
                logger.info("Application is null : {}", (applications == null));
                logger.info("Registered Applications size is zero : {}",
                        (applications.getRegisteredApplications().size() == 0));
                logger.info("Application version is -1: {}", (applications.getVersion() == -1));
                getAndStoreFullRegistry();
            } else {
                getAndUpdateDelta(applications);
            }
            applications.setAppsHashCode(applications.getReconcileHashCode());
            logTotalInstances();
        } catch (Throwable e) {
            logger.error(PREFIX + "{} - was unable to refresh its cache! status = {}", appPathIdentifier, e.getMessage(), e);
            return false;
        } finally {
            if (tracer != null) {
                tracer.stop();
            }
        }
        // 放入缓存
 // AtomicReference<Applications> localRegionApps = new AtomicReference<Applications>();中
 // 后期再ribbon中获取LoadBalanner,就是调用这里面的实例信息(每个服务的端口xxxx,这里不做描述,字段很多可参考下InstanceInfo这个类字段)

2、向eurake-service注册实例

// 2、还在构造函数中,向eurake-service注册实例 (默认是不注册,如果配置了强制注册才会注册)
// 可通过eurake.client.registerWithEureka设置是否注册eureka-service
// euraka.client.should-enforce-registration-at-init 服务启动的时候强制注册到eurake-service中
if (clientConfig.shouldFetchRegistry()) {
	boolean primaryFetchRegistryResult = fetchRegistry(false);
}
if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
            try {
                if (!register() ) {
                    throw new IllegalStateException("Registration error at startup. Invalid server response.");
                }
            } catch (Throwable th) {
                logger.error("Registration error at startup: {}", th.getMessage());
                throw new IllegalStateException(th);
            }
}

3、向eurake-service 发送心跳包

// 分析如下方法
initScheduledTasks();
// 通过eureka.client.registry-fetch-interval-seconds = 30
//1、每30秒拉取eurake-service的服务信息,注:这里30秒是不出Timeout情况下,如果出现timeout,会逐渐衰减执行任务,如果出现timeout,下一次执行就是60秒,依次类推,因为要是服务出现问题,30秒拉取数据是毫无意义的
if (clientConfig.shouldFetchRegistry()) {
            // registry cache refresh timer
            int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
            int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            cacheRefreshTask = new TimedSupervisorTask(
                    "cacheRefresh",
                    scheduler,
                    cacheRefreshExecutor,
                    registryFetchIntervalSeconds,
                    TimeUnit.SECONDS,
                    expBackOffBound,
                    new CacheRefreshThread()
            );
            scheduler.schedule(
                    cacheRefreshTask,
                    registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }
// 2、向eurake-service 每30秒发送心跳包
f (clientConfig.shouldRegisterWithEureka()) {
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);

            // Heartbeat timer
            heartbeatTask = new TimedSupervisorTask(
                    "heartbeat",
                    scheduler,
                    heartbeatExecutor,
                    renewalIntervalInSecs,
                    TimeUnit.SECONDS,
                    expBackOffBound,
                    new HeartbeatThread()
            );
            scheduler.schedule(
                    heartbeatTask,
                    renewalIntervalInSecs, TimeUnit.SECONDS);
// 3、监听服务是否出现问题,如果出现问题,发送一个http请求到service,删除该节点,30秒
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {

eurake-service

1、如何接受eurake-client端的实例? 实例是怎么保存的?

client-->service是通过http通信,那必然是有类似controller的地方接受,果不其然如下:

ApplicationSource类

@POST
    @Consumes({"application/json", "application/xml"})
    public Response addInstance(InstanceInfo info,
                                @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
        logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
   }

调用PeerAwareInstanceRegistry --> PeerAwareInstanceRegistryImpl.register --> 父类执行AbstractInstanceRegistry.register

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
        try {
            read.lock();
            Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
            REGISTER.increment(isReplication);
            if (gMap == null) {
                final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
                gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
                if (gMap == null) {
                    gMap = gNewMap;
                }
            }
            ....
// 1、上锁
// 2、判断是已经加载该实例,如果存在就在缓存中获取该实例,如果不存在,就根据参数,new 一个对象实例
// 3、最后放入缓存,加入一个队列
// 4、删除写缓存的一些key(使用google的缓存工具)
// invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());

2、eureke的三级缓存

因为多服务实例上传,add操作的时候,操作block的,如果去get的就出现block,为这一问题,引入读缓存和写缓存分离,

读写怎么同步 ? 在哪里同步?

在ResponseCacheImple 构造函数中初始化,启动定时任务,每30秒同步一次

// 可更改配置eureka.service.response-cache-update-interval-ms: 30

入口 自动装配  EurekaServerAutoConfiguration

@Bean
	public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
			PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
			// 看这里
		return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
				registry, peerEurekaNodes, this.applicationInfoManager);
	}
	
	@Inject
    public DefaultEurekaServerContext(EurekaServerConfig serverConfig,
                               ServerCodecs serverCodecs,
                               PeerAwareInstanceRegistry registry,
                               PeerEurekaNodes peerEurekaNodes,
                               ApplicationInfoManager applicationInfoManager) {
        this.serverConfig = serverConfig;
        this.serverCodecs = serverCodecs;
        this.registry = registry;
        this.peerEurekaNodes = peerEurekaNodes;
        this.applicationInfoManager = applicationInfoManager;
    }
	// 看DefaultEurekaServerContext 里面有个@PostConstruct
	// 在这里做init操作,其中init,最终是就是去调用new ResponseCacheImpl,看如下流程:
    @PostConstruct
    @Override
    public void initialize() throws Exception {
        logger.info("Initializing ...");
        peerEurekaNodes.start();
        // 看这里初始化
        registry.init(peerEurekaNodes);
        logger.info("Initialized");
    }
    // 跟踪代码,看registry.init 方法, 最终又到了PeerAwareInstanceRegistryImpl类里面
    // PeerAwareInstanceRegistryImpl.init()
    @Override
    public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
        this.numberOfReplicationsLastMin.start();
        this.peerEurekaNodes = peerEurekaNodes;
        // 看这里,初始化new ReponseCacheImpl()
        initializedResponseCache();
        scheduleRenewalThresholdUpdateTask();
        initRemoteRegionRegistry();
    }
    // 看到这里就明白了
    @Override
    public synchronized void initializedResponseCache() {
        if (responseCache == null) {
        	// 这里
            responseCache = new ResponseCacheImpl(serverConfig, serverCodecs, this);
        }
    }
    // 在new ResponseCacheImpl构造函数中启用定时任务同步 先构建一个写缓存,放入缓存中
    if (shouldUseReadOnlyResponseCache) {
    		// 业务实现getCacheUpdateTask()
            timer.schedule(getCacheUpdateTask(),
                    new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
                            + responseCacheUpdateIntervalMs),
                    responseCacheUpdateIntervalMs);
    }
    
    // 定时任务,同步数据
    private TimerTask getCacheUpdateTask() {
        return new TimerTask() {
            @Override
            public void run() {
                for (Key key : readOnlyCacheMap.keySet()) {
                    try {
                        // 再次进行同步
                        CurrentRequestVersion.set(key.getVersion());
                        Value cacheValue = readWriteCacheMap.get(key);
                        Value currentCacheValue = readOnlyCacheMap.get(key);
                        if (cacheValue != currentCacheValue) {
                            readOnlyCacheMap.put(key, cacheValue);
                        }
                    } catch (Throwable th) {
                        logger.error("Error while updating the client cache from response cache", th);
                    }
                }
            }
        };
    }

3、怎么保存eurake-client上传的心跳,做了什么措施?

eureka-client 上传心跳,通过InstanceResource.renewLease 接收;看看都干了啥?

@PUT
    public Response renewLease(
            @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
            @QueryParam("overriddenstatus") String overriddenStatus,
            @QueryParam("status") String status,
            @QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
        boolean isFromReplicaNode = "true".equals(isReplication);
        // 开始续约
        // PeerAwareInstanceRegistryImpl,又是它,似乎所有的具体实现都是这个类了?
        boolean isSuccess = registry.renew(app.getName(), id, isFromReplicaNode);
        // 续约失败了
        if (!isSuccess) {
            logger.warn("Not Found (Renew): {} - {}", app.getName(), id);
            return Response.status(Status.NOT_FOUND).build();
        }
        // 把视角放在PeerAwareInstanceRegistryImpl.renew这里来,看看干了啥?

public boolean renew(final String appName, final String id, final boolean isReplication) {
        // 调用了父类
        if (super.renew(appName, id, isReplication)) {
            replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
            return true;
        }
        return false;
    }

public boolean renew(String appName, String id, boolean isReplication) {
        RENEW.increment(isReplication);
    	// 从注册实例get出来
        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
        Lease<InstanceInfo> leaseToRenew = null;
        if (gMap != null) {
            leaseToRenew = gMap.get(id);
        }
    	// 获取是否已经注册,空就报错了
        if (leaseToRenew == null) {
            RENEW_NOT_FOUND.increment(isReplication);
            logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id);
            return false;
        } else {
            // 获取实例信息
            InstanceInfo instanceInfo = leaseToRenew.getHolder();
            // 获取续约后的状态
            if (instanceInfo != null) {
                // touchASGCache(instanceInfo.getASGName());
                // 续约申请,这里是重点了。。。 (判断有很多规则)
                // 默认是它FirstMatchWinsCompositeRule() 
                // 第一种规则: new DownOrStartingRule(),
                // 第二种规则: new OverrideExistsRule(overriddenInstanceStatusMap), 
                // 第三种规则: new LeaseExistsRule(),  
                // 外加AlwaysMatchInstanceStatusRule,里面的规则的就是状态比对
                // 其他以后在去翻翻
                InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
                        instanceInfo, leaseToRenew, isReplication);
                // 续约结果判断
                if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
                    RENEW_NOT_FOUND.increment(isReplication);
                    return false;
                }
                if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
                    Object[] args = {
                            instanceInfo.getStatus().name(),
                            instanceInfo.getOverriddenStatus().name(),
                            instanceInfo.getId()
                    };
                    logger.info(
                            "The instance status {} is different from overridden instance status {} for instance {}. "
                                    + "Hence setting the status to overridden status", args);
                    instanceInfo.setStatus(overriddenInstanceStatus);
                }
            }
            // 变更最后的续约时间
            renewsLastMin.increment();
            leaseToRenew.renew();
            return true;
        }
    }

4、eurake-service的自我保护机制?

15分钟,超过85%的续约失败,就启动自我保护

5、eureke-service怎么剔除失效的节点?剔除的前提?

剔除的前提: 90s超过90%的没有续约的就会被剔除

// 入口也自动装配或者eureka-service启动的时候
// 1、EurekaServerAutoConfiguration --> EurekaServerInitializerConfiguration
// 2、通过事件监听AbstractDiscoveryLifecycle ---> start();
// 3、EvictionTask -->evict()

Ribbon

负载均衡的意义:

提高吞吐量,最小响应时间,避免任何一个组件资源过载。

在调用restTemplate.getForObject("http://user-service/user"); LoadBalancerInterceptor 拦截请求,

1、LoadBalancerInterceptor 拦截请求

@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
        // 这里就是获取serviceId
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null,
				"Request URI does not contain a valid hostname: " + originalUri);
        // loadBalancer  -> LoadBalancerClient.execute方法
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}

LoadBalancerClient 又是RibbionLoadBalancerClient实现

// RibbionLoadBalancerClient 实现
// 这里的 serviceId == host
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
    	// 获取到loadBalancer,我们分析这里面的代码
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));

		return execute(serviceId, ribbonServer, request);
	}

2、SpringClientFactory register -> ioc

分析getLoadBalancer里面的代码, 是通过SpringClientFactory的工厂来注册一个上下文,这里为什么要new 一个上线文出来?
为了啥?

其实是为了资源隔离,获取到的eureka-service的服务实例,获取在application.yml中配置listOfService; 列入:user、order服务都有不同的实例 ,

每个实例都创建一个上下文,看看整体SpringClientFactory都干了啥?

public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
    
    public <T> T getInstance(String name, Class<T> type) {
        // 获取一个上下文,注册到spring里面
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type).length > 0) {
			return context.getBean(type);
		}
		return null;
	}  
    // 在父类NamedContextFactory 中实现,createContext()方法
    protected AnnotationConfigApplicationContext createContext(String name) {
        //  初始化上下文
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
				context.register(configuration);
			}
		}
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
        // 这里有意思了。。设置一个defaultConfigType , 这里是从SpringClientFactory传入进来的
        // super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
        //public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
		//	String propertyName) {
		//	this.defaultConfigType = defaultConfigType;
		//	this.propertySourceName = propertySourceName;
		//	this.propertyName = propertyName;
		// }
        
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType)
            //, 这里是从SpringClientFactory传入进来的
            // super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name"););
            // RibbonClientConfiguration == defaultConfigType
			// 以下代码就不看了,就是注册到spring容器里面
  			//下面我们来分析下RibbonClientConfiguration
	}
    
}

3、 RibbonClientConfiguration 初始化ZoneAwreLoadBalancer

public class RibbonClientConfiguration {
	// ribbon默认连接,是的1秒就超时了,要是没响应1秒超时
	public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
    // 1秒要是没响应回来就超时
	public static final int DEFAULT_READ_TIMEOUT = 1000;
	// 是否开启gzip压缩
	public static final boolean DEFAULT_GZIP_PAYLOAD = true;

	@RibbonClientName
	private String name = "client";
	@Autowired
	private PropertiesFactory propertiesFactory;

	@Bean
	@ConditionalOnMissingBean
	public IClientConfig ribbonClientConfig() {
        // ribbon的默认配置全在这里初始化,有信息的去里面翻翻
		DefaultClientConfigImpl config = new DefaultClientConfigImpl();
		config.loadProperties(this.name);
		config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
		config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
		config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
		return config;
	}

	@Bean
	@ConditionalOnMissingBean
	public IRule ribbonRule(IClientConfig config) {
        // 路由规则,轮训、哈希、随机
		if (this.propertiesFactory.isSet(IRule.class, name)) {
			return this.propertiesFactory.get(IRule.class, config, name);
		}
		ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
		rule.initWithNiwsConfig(config);
		return rule;
	}

    
	@Bean
	@ConditionalOnMissingBean
	public IPing ribbonPing(IClientConfig config) {
        // ping获取到的实例服务 (30秒ping一次)
		if (this.propertiesFactory.isSet(IPing.class, name)) {
			return this.propertiesFactory.get(IPing.class, config, name);
		}
		return new DummyPing();
	}

	@Bean
	@ConditionalOnMissingBean
	@SuppressWarnings("unchecked")
    // 获取所有的实例服务,这里是获取application.yml 配置的listOfServer配置,不是从
    // eureka-service 中获取的
	public ServerList<Server> ribbonServerList(IClientConfig config) {
		if (this.propertiesFactory.isSet(ServerList.class, name)) {
			return this.propertiesFactory.get(ServerList.class, config, name);
		}
		ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
		serverList.initWithNiwsConfig(config);
		return serverList;
	}

	@Bean
	@ConditionalOnMissingBean
	public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
		return new PollingServerListUpdater(config);
	}

	@Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
        // 看看,这里获取loadBalancer了
		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
		}
        // 来看看这个方法其他的就不看了。
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}


	}

如下来解析下ZoneAwareLoadBalancer

 	// 点进去看ZoneAwareLoadBalancer构造函数上看到了
    public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
                                 IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
                                 ServerListUpdater serverListUpdater) {
        // super父类
        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
    }
    
    public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
                                         ServerList<T> serverList, ServerListFilter<T> filter,
                                         ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping);
        this.serverListImpl = serverList;
        this.filter = filter;
        this.serverListUpdater = serverListUpdater;
        if (filter instanceof AbstractServerListFilter) {
            ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
        }
        // 重点在这里
        restOfInit(clientConfig);
    }
	// 看下restOfInit
	void restOfInit(IClientConfig clientConfig) {
        boolean primeConnection = this.isEnablePrimingConnections();        
        this.setEnablePrimingConnections(false);
        // 开启定时任务30秒定时更新 (到这里都是同步application.yml的配置listOfServer,不是从注册中心获取的)
        enableAndInitLearnNewServersFeature();
		//  这里获取服务,(这里面就开始分裂了,可以从Configuration获取,也可以从eureka-service获取了),一起看看
        updateListOfServers();
        if (primeConnection && this.getPrimeConnections() != null) {
            this.getPrimeConnections()
                    .primeConnections(getReachableServers());
        }
        this.setEnablePrimingConnections(primeConnection);
    }

// 一起看下

// 看看 updateListOfServers()都干了什么
@VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
            // 看这里volatile ServerList<T> serverListImpl;,看下图看看类图
            // 重点看下DiscoveryEnabledNIWSServerList 这个方法就是注册中心过去实例
            servers = serverListImpl.getUpdatedListOfServers();
        }
        // 获取到的servers放入到Allservice里面去 父类BaseLoadBalancer
        updateAllServerList(servers);
    }

4、DiscoveryEnabledNIWSServerList从eureka-client获取服务

看下类,DiscoveryEnabledNIWSServerList.getUpdatedListOfServers()

public List<DiscoveryEnabledServer> getUpdatedListOfServers(){
        return obtainServersViaDiscovery();
    }
// 直接看重点了,
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
        List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();
		
        EurekaClient eurekaClient = eurekaClientProvider.get();
        if (vipAddresses!=null){
            for (String vipAddress : vipAddresses.split(",")) {
                // if targetRegion is null, it will be interpreted as the same region of client
                // 看这里,
                List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
                
            }
        }
    // 是不是很熟悉了。这玩意了localRegionApps
    // 这个不就是从eureka-service。。拉取的注册中心的实例服务的。破案了。老铁们
   public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure,
                                                       @Nullable String region) {
        Applications applications;
        // 有疑问看下eureka-client地方
        if (instanceRegionChecker.isLocalRegion(region)) {
            applications = this.localRegionApps.get();
        } else {
            applications = remoteRegionVsApps.get(region);
            if (null == applications) {
                logger.debug("No applications are defined for region {}, so returning an empty instance list for vip "
                        + "address {}.", region, vipAddress);
                return Collections.emptyList();
            }
        }
    }

分析结束~

Hystrix(降级策略)

1、Hystrix前言废话

Hystrix是一个降级的工具,包含好几种降级的策略,aop思想,切面编程,底层是通过线程池来做的,还有一个信号量。

look下Hystrix的aop,肯定是Aspect结尾的  -> HystrixCommandAspect, 切面做的

static {
        META_HOLDER_FACTORY_MAP = ImmutableMap.<HystrixPointcutType, MetaHolderFactory>builder()
                .put(HystrixPointcutType.COMMAND, new CommandMetaHolderFactory())
                .put(HystrixPointcutType.COLLAPSER, new CollapserMetaHolderFactory())
                .build();
    }

    @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)")

    public void hystrixCommandAnnotationPointcut() {
    }

    @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)")
    public void hystrixCollapserAnnotationPointcut() {
    }
	
	@Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
    public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = getMethodFromTarget(joinPoint);
        Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint);
        if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) {
            throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " +
                    "annotations at the same time");
        }
        MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method));
        MetaHolder metaHolder = metaHolderFactory.create(joinPoint);
        HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
        ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?
                metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();

        Object result;
        try {
            if (!metaHolder.isObservable()) {
                // 线程池做的
                result = CommandExecutor.execute(invokable, executionType, metaHolder);
            } else {
                result = executeObservable(invokable, executionType, metaHolder);
            }
        } catch (HystrixBadRequestException e) {
            throw e.getCause() != null ? e.getCause() : e;
        } catch (HystrixRuntimeException e) {
            throw hystrixRuntimeExceptionToThrowable(metaHolder, e);
        }
        return result;
    }

1、熔断

怎么开启熔断?

10内,20个请求,出现50%的错误率,就开启熔断,开启熔断后5秒后往子服务发出请求,是否成功(这里不一定是5秒,要有请求来,才是5秒,要是没有请求,就已是open状态); look 以上5个数字。

前言:怎么判断需要开启熔断?

介绍下滑动窗口

       

     在来看看前面说的5个数字

   + 1、10秒内,20个请求;也就是统计10个窗口,每个窗口1s,每个窗口有success。fail,timeout,reject等状态,窗口向前平滑, 
   + 2、这里是必须超过20个请求,不超过20个请求,这里的rule是不起作用的  
   + 3、 超过20个请求,且失败率50% , 失败率就是统计  统计10内的fail、timeout,reject的个数

  看看怎么开启?

// 熔断属性配置
// circuitBreaker.enabled 是否开启熔断
// circuitBreaker.requestVolumeThreshold 20个请求
// circuitBreaker.sleepWindowInMilliseconds 5秒后
// circuitBreaker.errorThresholdPercentage 错误率
@HystrixCommand(commandProperties = {           
    @HystrixProperty(name = "circuitBreaker.enabled", value="true"),  
    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value="20"),  
    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value="5000"), 
    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value="50")   
})

2、超时

超时的源码我是看懂了

Future future = threadPool.submit(xxx);

future.get(timeout, TimeUnit.SENCOND);

//如果超时,就在异常捕捉下,调用future.cancal(); 在通过反射调用设置的fallbackMethod方法,思想就是介样子。源码就不展示了,反正我看的版之不解的。算了,直接过了,看不懂就跳过

看下怎么设置

hystrix:  
	command:    
  	default:      
    	execution:        
      	isolation:          
        	thread:            
          	timeoutInMilliseconds: 30000

3、资源隔离

hystrix: 	
	threadpool:		
  	default: 			
    	coreSize: 20			
      maxQueueSize: 5      		
      queueSizeRejectionThreshold: 5      	
      # 设置每一个服务的线程池大小,不知道信号量怎么设置      	
    user-service:         	
    	coreSize: 20			
      maxQueueSize: 5      		
      queueSizeRejectionThreshold: 5    
      ##信号量,此处略过,因为不懂  		    
    semaphore:

4、fegin(开启熔断)

## 开启feign熔断
feign:	
	hystrix:		
  	enabled: true

5、总结

以上的都是服务降级的策略,每种策略都有不同的实现方式,千万别搞混了,以上都是降级策略/降级策略/降级策略

后面介绍spring cloud alibaba的组件

sleuth

// 默认是开启的
spring.sleuth.enable=true
spring.sleuth.percentage=1.0(上传10%的数据)
// 结合zipkin使用
spring.zipkin.base-url=http://localhost:9481/

zipkin默认是存入本地缓存,支持es,mysql等持久化

cap理论

  • 1、一致性 : 调用A节点和调用B节点服务,返回的数据是一致的。
  • 2、可用性 : 集群部分节点出现故障后,是否还有能力继续处理客户端的请求
  • 3、分区容错性 :大多数各个集群系统都分布不同的子网络上;比如:A节点在北京,B节点在上海;假设出现网络抖动原因,导致两个节点无法通信。

为什么cap只能三选二?

理论:

列举:保证一致性

假设A节点在写操作,如果是需要保证一致性,那么必然是需要把B节点的写、读操作锁住;所以这里就违背可用性

列举:保证可用性

反之,如果不锁住B,那么也是违背了一致性。

为什么eureka是ap

这里就是说到eureka的续约、心跳、自我保护机制等解释。

续约、心跳、自我保护机制在上一描述。这里不做陈述。

eureka更多是保证服务可用性。即使部分节点宕机后,其他子服务还可以继续提供服务。而且对读写不做锁处理。不要强制性。可容忍数据出现延迟。

为什么zookeeper是cp

Leader: 参与系统状态维护

Follower:参与选举leader投票。也接受客户端请求并且返回结果

Observer:不参与选举leader投票,都处理读操作。接受到写操作,会转到leader节点上。

zab协议:

为什么zookeeper是cp呢,c:一致性、分区容错性

特性:zookeeper在leader选举的时候,所有服务是不可用。所以保证了强一致性。

p:运行节点指点不通信,与leader不通信,踢出去。

为什么ca是互斥的?

一致性、可用性。目前从目前有的集群来看;是存在互斥的情况。但互联网更新换代节奏也很快。未来也可能会出现完全符合ca理论的框架。

总结

client端:1、服务启动后registry到注册中心(可配置不注册上去)

2、每30秒,往注册中心丢心跳包,进行续约

3、每60秒,从注册中心拉取服务信息, 拉取到的信息,缓存到本地 (注意这里的60s,是需要拆分成读写)

service端:1、90s内超过90%没有续约的服务直接剔除,但是如果开起了保护机制,不会当场剔除服务;而是通过随机数的进行剔除。避免把正常的服务也被剔除了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值