文章目录

  • 负载均衡与集群容错
    • Invoker
    • 服务目录
      • RegistryDirectory
      • StaticDirectory
    • 服务路由
    • Cluster
    • 负载均衡

负载均衡与集群容错

Invoker

在Dubbo中Invoker就是一个具有调用功能的对象,在服务提供端就是实际的服务实现,只是将服务实现封装起来变成一个Invoker。

在服务消费端,从注册中心得到服务提供者的信息之后,将一条条信息封装为Invoker,这个Invoker就具备了远程调用的能力。

综上,Dubbo就是创建了一个统一的模型,将可调用(可执行体)的服务对象都统一封装为Invoker。

ClusterInvoker就是将多个服务引入的Invoker封装起来,对外统一暴露一个Invoker,并且赋予这些Invoker集群容错的功能。


服务目录

服务目录,即Directory,实际上它就是多个Invoker的集合,服务提供端一般都会集群分布,同样的服务会有多个提供者,因此需要一个服务目录来统一存放它们,需要调用服务的时候便从这个服务目录中进行挑选。

同时服务目录还是实现了NotifyListener接口,当集群中新增了一台服务提供者或者下线了一台服务提供者,目录都会对服务提供者进行更新,新增或者删除对应的Invoker。

从上图中,可以看到用了一个抽象类AbstractDirectory来实现 Directory接口,抽象类中运用到了模板方法模式,将一些公共方法和逻辑写好,作为一个骨架,然后具体实现由了两个子类来完成,两个子类分别为StaticDirectoryRegistryDirectory

RegistryDirectory

RegistryDirectory实现了NotifyListener接口,可以监听注册中心的变化,当注册中心配置发生变化时,服务目录也可以收到变更通知,然后根据更新之后的配置刷新Invoker列表。

由此可知RegistryDirectory共有三个作用:

  • 获取Invoker列表
  • 监听注册中心
  • 刷新Invoker列表

获取Invoker列表

RegistryDirectory实现了父类AbstractDirectory的抽象方法doList(),该方法可以得到Invoker列表

public List<Invoker<T>> doList(Invocation invocation) {if (this.forbidden) {throw new RpcException(....);} else {List<Invoker<T>> invokers = null;Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap;   //获取方法调用名和Invoker的映射表if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {String methodName = RpcUtils.getMethodName(invocation);Object[] args = RpcUtils.getArguments(invocation);//以下就是根据方法名和方法参数获取可调用的Invokerif (args != null && args.length > 0 && args[0] != null && (args[0] instanceof String || args[0].getClass().isEnum())) {invokers = (List)localMethodInvokerMap.get(methodName + "." + args[0]);}if (invokers == null) {invokers = (List)localMethodInvokerMap.get(methodName);}if (invokers == null) {invokers = (List)localMethodInvokerMap.get("*");}if (invokers == null) {Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();if (iterator.hasNext()) {invokers = (List)iterator.next();}}}return (List)(invokers == null ? new ArrayList(0) : invokers);}
}

监听注册中心

通过实现NotifyListener接口可以感知注册中心的数据变更。

RegistryDirectory定义了三个集合invokerUrls routerUrls configuratorUrls分别处理对应的配置然后转化成对象。

public synchronized void notify(List<URL> urls) {List<URL> invokerUrls = new ArrayList();List<URL> routerUrls = new ArrayList();List<URL> configuratorUrls = new ArrayList();Iterator i$ = urls.iterator();while(true) {while(true) {while(i$.hasNext()) {//....根据urls填充上述三个列表}if (configuratorUrls != null && !configuratorUrls.isEmpty()) {this.configurators = toConfigurators(configuratorUrls);       //根据urls转化为configurators配置}List localConfigurators;if (routerUrls != null && !routerUrls.isEmpty()) {localConfigurators = this.toRouters(routerUrls);if (localConfigurators != null) {this.setRouters(localConfigurators);       //根据urls转化为routers配置}}localConfigurators = this.configurators;this.overrideDirectoryUrl = this.directoryUrl;Configurator configurator;if (localConfigurators != null && !localConfigurators.isEmpty()) {for(Iterator i$ = localConfigurators.iterator(); i$.hasNext(); this.overrideDirectoryUrl = configurator.configure(this.overrideDirectoryUrl)) {configurator = (Configurator)i$.next();}}this.refreshInvoker(invokerUrls);     //根据invokerUrls刷新invoker列表return;}}
}

刷新Invoker列表

private void refreshInvoker(List<URL> invokerUrls) {//如果invokerUrls只有一个URL并且协议是empty,那么清除所有invokerif (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null && "empty".equals(((URL)invokerUrls.get(0)).getProtocol())) {this.forbidden = true;this.methodInvokerMap = null;this.destroyAllInvokers();} else {this.forbidden = false;Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap;        //获取旧的Invoker列表if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {invokerUrls.addAll(this.cachedInvokerUrls);} else {this.cachedInvokerUrls = new HashSet();this.cachedInvokerUrls.addAll(invokerUrls);}if (invokerUrls.isEmpty()) {return;}//根据URL生成InvokerMapMap<String, Invoker<T>> newUrlInvokerMap = this.toInvokers(invokerUrls);//根据新的InvokerMap生成方法名和Invoker列表对应的MapMap<String, List<Invoker<T>>> newMethodInvokerMap = this.toMethodInvokers(newUrlInvokerMap);if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));return;}this.methodInvokerMap = this.multiGroup ? this.toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;this.urlInvokerMap = newUrlInvokerMap;try {this.destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap);     //销毁无效的Invoker} catch (Exception var6) {logger.warn("destroyUnusedInvokers error. ", var6);}}
}

上述操作就是根据invokerUrls数量以及协议头是否为empty来判断是否禁用所有invokers,如果不禁用的话将invokerUrls转化为Invoker,并且得到<url,Invoker>的映射关系。

再进一步进行转化,得到<methodName,List>的映射关系,再将同一组的Invoker进行合并,将合并结果赋值给methodInvokerMap,这个methodInvokerMap就是在doList中使用到的Map。

最后刷新InvokerMap,销毁无效的Invoker。

StaticDirectory

StaticDirectory是静态目录,所有Invoker是固定的不会删减的,并且所有Invoker由构造器来传入。

内部逻辑也相当简单,只定义了一个列表用于存储Invokers。实现父类的方法也只是将这些Invokers原封不动地返回。

private final List<Invoker<T>> invokers;protected List<Invoker<T>> doList(Invocation invocation) throws RpcException {return this.invokers;
}

服务路由

服务路由规定了服务消费者可以调用哪些服务提供者,Dubbo常用的是条件路由ConditionRouter

条件路由由两个条件组成,格式为[服务消费者匹配条件] => [服务提供者匹配条件],例如172.26.29.15 => 172.27.19.89规定了只有IP为172.26.29.15的服务消费者才可以访问IP为172.27.19.89的服务提供者,不可以调用其他的服务。

路由一样是通过RegistryDirectory中的notify()更新的,在调用toMethodInvokers()的时候会进行服务器级别的路由和方法级别的路由。


Cluster

在前面的流程中我们已经通过Directory获取了服务目录,并且通过路由获取了一个或多个Invoker,但是对于服务消费者还是需要进行选择,筛选出一个Invoker进行调用。

Dubbo默认的Cluster实现有多种,如下:

  • FailoverCluster
  • FailfastCluster
  • FailsafeCluster
  • FailbackCluster
  • BroadcastCluster
  • AvailableCluster

每个Cluster内部返回的都是xxxClusterInvoker,例如FailoverCluster:

public class FailoverCluster implements Cluster {public static final String NAME = "failover";public FailoverCluster() {}public <T> Invoker<T> join(Directory<T> directory) throws RpcException {return new FailoverClusterInvoker(directory);}
}

FailoverClusterInvoker

FailoverClusterInvoker实现的功能是失败调用(有重试次数)自动切换。

public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {List<Invoker<T>> copyinvokers = invokers;this.checkInvokers(invokers, invocation);//重试次数int len = this.getUrl().getMethodParameter(invocation.getMethodName(), "retries", 2) + 1;if (len <= 0) {len = 1;}RpcException le = null;List<Invoker<T>> invoked = new ArrayList(invokers.size());Set<String> providers = new HashSet(len);//根据重试次数循环调用for(int i = 0; i < len; ++i) {if (i > 0) {this.checkWhetherDestroyed();copyinvokers = this.list(invocation);this.checkInvokers(copyinvokers, invocation);}//负载均衡筛选出一个Invoker作本次调用Invoker<T> invoker = this.select(loadbalance, invocation, copyinvokers, invoked);//将使用过的Invoker保存起来,下次重试时做过滤用invoked.add(invoker);//记录到上下文中RpcContext.getContext().setInvokers(invoked);try {//发起调用Result result = invoker.invoke(invocation);if (le != null && logger.isWarnEnabled()) {logger.warn("....");}Result var12 = result;return var12;} catch (RpcException var17) {   //catch异常 继续下次循环重试if (var17.isBiz()) {throw var17;}le = var17;} catch (Throwable var18) {le = new RpcException(var18.getMessage(), var18);} finally {providers.add(invoker.getUrl().getAddress());}}throw new RpcException(....);
}

上述方法中,首先获取重试次数len,根据重试次数进行循环调用,调用发生异常会被catch住,然后重新调用。

每次循环会通过负载均衡选出一个Invoker,然后利用这个Invoker进行远程调用,每次选出的Invoker会记录下来,在下次调用的select()中会将使用上次调用的Invoker进行重试,如果上一次没有调用或者上次调用的Invoker下线了,那么会重新进行负载均衡进行选择。

FailfastClusterInvoker

FailfastClusterInvoker只会进行一次远程调用,如果失败后立马抛出异常。

public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {this.checkInvokers(invokers, invocation);Invoker invoker = this.select(loadbalance, invocation, invokers, (List)null);   //负载均衡选择Invokertry {return invoker.invoke(invocation);      //发起远程调用} catch (Throwable var6) {  //失败调用直接将错误抛出if (var6 instanceof RpcException && ((RpcException)var6).isBiz()) {throw (RpcException)var6;} else {throw new RpcException(....);}}
}

FailsafeClusterInvoker

FailsafeClusterInvoker是一种安全失败的cluster,调用发生错误仅仅是记录一下日志,然后就返回了空结果。

public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {try {this.checkInvokers(invokers, invocation);//负载均衡选出Invoker后直接进行调用Invoker<T> invoker = this.select(loadbalance, invocation, invokers, (List)null);   return invoker.invoke(invocation);} catch (Throwable var5) {    //调用错误只是打印日志logger.error("Failsafe ignore exception: " + var5.getMessage(), var5);return new RpcResult();}
}

FailbackClusterInvoker

FailbackClusterInvoker调用失败后,会记录下本次调用,然后返回一个空结果给服务消费者,并且会通过一个定时任务对失败的调用进行重试。适用于执行消息通知等最大努力场景。

protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {try {this.checkInvokers(invokers, invocation);//负载均衡选出InvokerInvoker<T> invoker = this.select(loadbalance, invocation, invokers, (List)null);//执行调用,执行成功返回调用结果return invoker.invoke(invocation);} catch (Throwable var5) {//调用失败logger.error("....");//记录下本次失败调用this.addFailed(invocation, this);//返回空结果return new RpcResult();}
}private void addFailed(Invocation invocation, AbstractClusterInvoker<?> router) {if (this.retryFuture == null) {synchronized(this) {//如果未创建重试本次调用的定时任务if (this.retryFuture == null) {//创建定时任务this.retryFuture = this.scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {public void run() {try {//定时进行重试FailbackClusterInvoker.this.retryFailed();} catch (Throwable var2) {FailbackClusterInvoker.logger.error("....", var2);}}}, 5000L, 5000L, TimeUnit.MILLISECONDS);}}}//将invocation和router存入mapthis.failed.put(invocation, router);
}void retryFailed() {if (this.failed.size() != 0) {Iterator i$ = (new HashMap(this.failed)).entrySet().iterator();while(i$.hasNext()) {Entry<Invocation, AbstractClusterInvoker<?>> entry = (Entry)i$.next();Invocation invocation = (Invocation)entry.getKey();Invoker invoker = (Invoker)entry.getValue();try {//进行重试调用invoker.invoke(invocation);//调用成功未产生异常则移除本次失败调用的记录,销毁定时任务this.failed.remove(invocation);} catch (Throwable var6) {logger.error("....", var6);}}}
}

逻辑比较简单,大致就是当调用错误时返回空结果,并记录下本次失败调用到failed<invocation,router>中,并且会创建一个定时任务定时地去调用failed中记录的失败调用,如果调用成功了就从failed中移除这个调用。

ForkingClusterInvoker

ForkingClusterInvoker运行时,会将所有Invoker都放入线程池中并发调用,只要有一个Invoker调用成功了就返回结果,doInvoker方法立即停止运行。

适用于对实时性比较高的读写操作。

public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {Result var19;try {this.checkInvokers(invokers, invocation);int forks = this.getUrl().getParameter("forks", 2);int timeout = this.getUrl().getParameter("timeout", 1000);final Object selected;if (forks > 0 && forks < invokers.size()) {selected = new ArrayList();for(int i = 0; i < forks; ++i) {Invoker<T> invoker = this.select(loadbalance, invocation, invokers, (List)selected);if (!((List)selected).contains(invoker)) {//选择好的Invoker放入这个selected列表((List)selected).add(invoker);}}} else {selected = invokers;}RpcContext.getContext().setInvokers((List)selected);final AtomicInteger count = new AtomicInteger();//阻塞队列final BlockingQueue<Object> ref = new LinkedBlockingQueue();Iterator i$ = ((List)selected).iterator();while(i$.hasNext()) {final Invoker<T> invoker = (Invoker)i$.next();this.executor.execute(new Runnable() {public void run() {try {Result result = invoker.invoke(invocation);ref.offer(result);} catch (Throwable var3) {int value = count.incrementAndGet();if (value >= ((List)selected).size()) {      //等待所有调用都产生异常才入队ref.offer(var3);}}}});}try {//阻塞获取结果Object ret = ref.poll((long)timeout, TimeUnit.MILLISECONDS);if (ret instanceof Throwable) {Throwable e = (Throwable)ret;throw new RpcException(....);}var19 = (Result)ret;} catch (InterruptedException var14) {throw new RpcException(....);}} finally {RpcContext.getContext().clearAttachments();}return var19;
}

BroadcastClusterInvoker

BroadcastClusterInvoker运行时会将所有Invoker逐个调用,在最后判断中如果有一个调用产生错误,则抛出异常。

适用于通知所有提供者更新缓存或日志等本地资源的场景。

public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {this.checkInvokers(invokers, invocation);RpcContext.getContext().setInvokers(invokers);RpcException exception = null;Result result = null;Iterator i$ = invokers.iterator();while(i$.hasNext()) {Invoker invoker = (Invoker)i$.next();try {result = invoker.invoke(invocation);} catch (RpcException var9) {exception = var9;logger.warn(var9.getMessage(), var9);} catch (Throwable var10) {exception = new RpcException(var10.getMessage(), var10);logger.warn(var10.getMessage(), var10);}}//如果调用过程中发生过错误 抛出异常if (exception != null) {throw exception;} else {//返回调用结果return result;}
}

AbstractClusterInvoker

AbstractClusterInvoker是上述所有类的父类,内部结构较为简单。AvailableCluster内部返回结果就是AvailableClusterInvoker。

public class AvailableCluster implements Cluster {public static final String NAME = "available";public AvailableCluster() {}public <T> Invoker<T> join(Directory<T> directory) throws RpcException {return new AbstractClusterInvoker<T>(directory) {public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {Iterator i$ = invokers.iterator();Invoker invoker;do {       //循环判断:哪个invoker能用就调用哪个if (!i$.hasNext()) {throw new RpcException("No provider available in " + invokers);}invoker = (Invoker)i$.next();} while(!invoker.isAvailable());return invoker.invoke(invocation);}};}
}

总结

上述中有很多种集群的实现,各适用于不同的场景,加了Cluster这个中间层,向服务消费者屏蔽了集群调用的细节,并且支持不同场景使用不同的模式。


负载均衡

Dubbo中的负载均衡,即LoadBalance,服务提供者一般都是集群分布,所以需要Dubbo选择出合适的服务提供者来给服务消费者调用。

Dubbo中提供了多种负载均衡算法:

  • RandomLoadBalance
  • LeastActiveLoadBalance
  • ConsistentHashLoadBalance
  • RoundRobinLoadBalance

AbstractLoadBalance

实现类都继承了于这个类,该类实现了LoadBalance,使用模板方法模式,将一些公用的逻辑封装好,而具体的实现由子类自定义。

public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {if (invokers != null && !invokers.isEmpty()) {//子类实现return invokers.size() == 1 ? (Invoker)invokers.get(0) : this.doSelect(invokers, url, invocation);} else {return null;}
}protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> var1, URL var2, Invocation var3);

服务刚启动需要预热,不能突然让服务负载过高,需要进行服务的降权。

protected int getWeight(Invoker<?> invoker, Invocation invocation) {int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), "weight", 100);    //获得权重if (weight > 0) {long timestamp = invoker.getUrl().getParameter("remote.timestamp", 0L);        //启动时间if (timestamp > 0L) {int uptime = (int)(System.currentTimeMillis() - timestamp);      //计算已启动时长int warmup = invoker.getUrl().getParameter("warmup", 600000);if (uptime > 0 && uptime < warmup) {weight = calculateWarmupWeight(uptime, warmup, weight);     //降权}}}return weight;
}

RandomLoadBalance

使用了加权随机算法,假设现在有三个节点A,B,C,然后赋予这几个节点一定权重,分别为1,2,3,那么可计算得到总权重为6,那么这几个节点被访问的可能性分别为1/6,2/6,3/6。

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {int length = invokers.size();     //Invoker个数int totalWeight = 0;        //总权重boolean sameWeight = true;     //权重是否相同int offset;            int i;for(offset = 0; offset < length; ++offset) {    i = this.getWeight((Invoker)invokers.get(offset), invocation);     //得到权重totalWeight += i;     //计算总权重//是否权重都相同if (sameWeight && offset > 0 && i != this.getWeight((Invoker)invokers.get(offset - 1), invocation)) {sameWeight = false;     }}if (totalWeight > 0 && !sameWeight) {offset = this.random.nextInt(totalWeight);       //获得随机偏移量//判断偏移量落在哪个片段上for(i = 0; i < length; ++i) {offset -= this.getWeight((Invoker)invokers.get(i), invocation);if (offset < 0) {return (Invoker)invokers.get(i);      }}}return (Invoker)invokers.get(this.random.nextInt(length));
}

LeastActiveLoadBalance

最少活跃数负载均衡,接收一个请求后,请求活跃数+1,处理完一个请求后,请求活跃数-1,请求活跃数少既说明现在服务器压力小也说明该服务器处理请求快,没有堆积什么请求。

总的流程是先遍历Invokers列表,寻找当前请求活跃数最少的Invoker,如果有多个Invoker具有相同的最小请求活跃数,则根据他们的权重来进行筛选。

ConsistentHashLoadBalance

将服务器的IP等信息生成一个Hash值,将这个值映射到Hash圆环上作为某个节点,当查找节点时,通过一个Key来顺时针查找。

Dubbo还引入了160个虚拟节点,使得数据更加分散,避免请求积压在某个节点上。

并且Hash值是方法级别的,一个服务的每个方法都有一个ConsistentHashSelector,根据参数值来计算得出Hash值,

RoundRobinLoadBalance

加权轮询负载均衡,这种轮询是平滑的,假设A和B的权重为10:30,那么轮询的结果可能是A、B、B、A、A、B、B、B…,40次调用下来A调用了10次,B调用了30次。


总结

服务引入时,会将多个远程调用塞入Directory,然后通过Cluster来封装,同时根据需要提供各种容错功能,最终统一暴露一个Invoker给服务消费者,服务消费者调用的时候会从目录得到Invoker列表,经过路由的过滤以及负载均衡最终得到一个Invoker发起调用。


以上。
如有不足或错误欢迎指正。

Dubbo负载均衡与集群容错相关推荐

  1. Java编程解密-Dubbo负载均衡与集群容错机制

    1 Dubbo简介 Dubbo是一款高性能.轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现. 作为一个轻量级RPC框架,Du ...

  2. Dubbo负载均衡、集群容错

    负载均衡策略 random loadbalance 随机负载均衡,默认情况下,dubbo 是 random load balance ,即随机调用实现负载均衡,可以对 provider 不同实例设置不 ...

  3. Dubbo的负载均衡、集群容错、服务降级等机制详解

    文章目录 1. Dubbo与RPC的关系 2. Dubbo的基本使用 2.1 Dubbo是什么? 2.2 负载均衡 2.3 服务超时 2.4 集群容错 2.5 服务降级 2.6 本地存根 2.7 参数 ...

  4. 【Java从0到架构师】Dubbo 基础 - 设置启动时检查、直接提供者、线程模型、负载均衡、集群容错、服务降级

    Dubbo 分布式 RPC 分布式核心基础 分布式概述 RPC Dubbo Dubbo 入门程序 - XML.注解 部署管理控制台 Dubbo Admin 修改绑定的注册 IP 地址 设置启动时检查 ...

  5. Dubbo 源码分析 - 集群容错之 LoadBalance

    1.简介 LoadBalance 中文意思为负载均衡,它的职责是将网络请求,或者其他形式的负载"均摊"到不同的机器上.避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况.通 ...

  6. Dubbo 源码分析 - 集群容错之 Cluster

    1.简介 为了避免单点故障,现在的应用至少会部署在两台服务器上.对于一些负载比较高的服务,会部署更多台服务器.这样,同一环境下的服务提供者数量会大于1.对于服务消费者来说,同一环境下出现了多个服务提供 ...

  7. 使用LVS(Linux Virtual Server)在Linux上搭建负载均衡的集群服务

    使用LVS(Linux Virtual Server)在Linux上搭建负载均衡的集群服务 一.基于于NAT的LVS的安装与配置. 1. 硬件需求和网络拓扑                       ...

  8. web应用的负载均衡、集群、高可用(HA)解决方案

    web应用的负载均衡.集群.高可用(HA)解决方案 参考文章: (1)web应用的负载均衡.集群.高可用(HA)解决方案 (2)https://www.cnblogs.com/huojg-21442/ ...

  9. 基于nginx的tomcat负载均衡和集群(超简单)

    今天看到"基于apache的tomcat负载均衡和集群配置 "这篇文章成为javaEye热点. 略看了一下,感觉太复杂,要配置的东西太多,因此在这里写出一种更简洁的方法. 要集群t ...

最新文章

  1. 蚂蚁面试:字符串在JVM中如何存放?
  2. 架构设计从这5点考虑,能帮后期运维很大忙!
  3. mysql 慢查询 不重启_开启mysql慢查询日志,不重启数据库的方法
  4. Desktop OS Market Share: Windows 90% - Mac 9% - Linux 1%
  5. 【下班后学Android】Android开发环境搭建
  6. 学习 shell脚本之前的基础知识
  7. uint8 转换为 float
  8. 爬去图片插件_学不会你打我,一个插件爬取亚马逊数据!
  9. 关于RabbitMQ Queue Argument的简介
  10. Atiitt 常见机器算法 理解 总结 目录 1. 机器学习的核心是“使用算法解析数据,从中学习,然后对世界上的某件事情做出决定或预测” 1 2. 1. 五大流派 2 2.1. ①符号主义:使用
  11. 织梦采集插件自动图片本地化提升内容原创度
  12. oa项目经验描述_项目执行简历中的项目经验怎么写
  13. 摄影技术小白必须要掌握的基础知识(上)
  14. QQ出现大规模盗号,qq被盗发布不良信息怎么办
  15. 用c语言求解一元二次方程(共轭根除外)
  16. 纯JS实现slideToggle动画,慢慢下拉打开
  17. excel表计算机实践操作,Excel电子表格计算机实践任务书.ppt
  18. 线下沙龙 | 5月11日 ,百度智能云网络技术实践分享强势来袭!
  19. Autofac基础知识学习
  20. 10本好书读物推荐,职场管理者必读,建议收藏

热门文章

  1. pyspark 连接mysql
  2. tensorflow 的模型保存和调用
  3. 推荐系统笔记(内容推荐)
  4. BPMF论文辅助笔记:采样Ui 部分推导
  5. Python可视化应用实战-三万字长文(建议收藏)matplotlib可视化实例,实操有效
  6. 数据挖掘系列(4)使用weka做关联规则挖掘
  7. 深度学习100例 | 第27天-卷积神经网络(CNN):艺术作品识别
  8. 数据挖掘导论读书笔记8FP树
  9. 微服务实践分享(3)服务发现
  10. 机器学习算法基础——朴素贝叶斯算法