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%没有续约的服务直接剔除,但是如果开起了保护机制,不会当场剔除服务;而是通过随机数的进行剔除。避免把正常的服务也被剔除了。