当前位置: 首页 > 编程日记 > 正文

Dubbo源码解析之服务路由策略

1. 简介

服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由,筛选出符合路由规则的服务提供者。在详细分析服务路由的源码之前,先来介绍一下服务路由是什么。服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter、脚本路由 ScriptRouter 和标签路由 TagRouter。其中条件路由是我们最常使用的,标签路由是一个新的实现,暂时还未发布,该实现预计会在 2.7.x 版本中发布。本篇文章将分析条件路由相关源码,脚本路由和标签路由这里就不分析了。

2. 源码分析

条件路由规则由两个条件组成,分别用于对服务消费者和提供者进行匹配。比如有这样一条规则:

host = 10.20.153.10 => host = 10.20.153.11
复制代码

该条规则表示 IP 为 10.20.153.10 的服务消费者只可调用 IP 为 10.20.153.11 机器上的服务,不可调用其他机器上的服务。条件路由规则的格式如下:

[服务消费者匹配条件] => [服务提供者匹配条件]

如果服务消费者匹配条件为空,表示不对服务消费者进行限制。如果服务提供者匹配条件为空,表示对某些服务消费者禁用服务。官方文档中对条件路由进行了比较详细的介绍,大家可以参考下,这里就不过多说明了。

条件路由实现类 ConditionRouter 在进行工作前,需要先对用户配置的路由规则进行解析,得到一系列的条件。然后再根据这些条件对服务进行路由。本章将分两节进行说明,2.1节介绍表达式解析过程。2.2 节介绍服务路由的过程。下面,我们先从表达式解析过程看起。

2.1 表达式解析

条件路由规则是一条字符串,对于 Dubbo 来说,它并不能直接理解字符串的意思,需要将其解析成内部格式才行。条件表达式的解析过程始于 ConditionRouter 的构造方法,下面一起看一下:

public ConditionRouter(URL url) {this.url = url;// 获取 priority 和 force 配置this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);this.force = url.getParameter(Constants.FORCE_KEY, false);try {// 获取路由规则String rule = url.getParameterAndDecoded(Constants.RULE_KEY);if (rule == null || rule.trim().length() == 0) {throw new IllegalArgumentException("Illegal route rule!");}rule = rule.replace("consumer.", "").replace("provider.", "");// 定位 => 分隔符int i = rule.indexOf("=>");// 分别获取服务消费者和提供者匹配规则String whenRule = i < 0 ? null : rule.substring(0, i).trim();String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();// 解析服务消费者匹配规则Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);// 解析服务提供者匹配规则Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);// 将解析出的匹配规则分别赋值给 whenCondition 和 thenCondition 成员变量this.whenCondition = when;this.thenCondition = then;} catch (ParseException e) {throw new IllegalStateException(e.getMessage(), e);}
}
复制代码

如上,ConditionRouter 构造方法先是对路由规则做预处理,然后调用 parseRule 方法分别对服务提供者和消费者规则进行解析,最后将解析结果赋值给 whenCondition 和 thenCondition 成员变量。ConditionRouter 构造方法不是很复杂,这里就不多说了。下面我们把重点放在 parseRule 方法上,在详细介绍这个方法之前,我们先来看一个内部类。

private static final class MatchPair {final Set<String> matches = new HashSet<String>();final Set<String> mismatches = new HashSet<String>();
}
复制代码

MatchPair 内部包含了两个 Set 类型的成员变量,分别用于存放匹配和不匹配的条件。这个类两个成员变量会在 parseRule 方法中被用到,下面来看一下。

private static Map<String, MatchPair> parseRule(String rule)throws ParseException {// 定义条件映射集合Map<String, MatchPair> condition = new HashMap<String, MatchPair>();if (StringUtils.isBlank(rule)) {return condition;}MatchPair pair = null;Set<String> values = null;// 通过正则表达式匹配路由规则,ROUTE_PATTERN = ([&!=,]*)\s*([^&!=,\s]+)// 这个表达式看起来不是很好理解,第一个括号内的表达式用于匹配"&", "!", "=" 和 "," 等符号。// 第二括号内的用于匹配英文字母,数字等字符。举个例子说明一下://    host = 2.2.2.2 & host != 1.1.1.1 & method = hello// 匹配结果如下://     括号一      括号二// 1.  null       host// 2.   =         2.2.2.2// 3.   &         host// 4.   !=        1.1.1.1 // 5.   &         method// 6.   =         hellofinal Matcher matcher = ROUTE_PATTERN.matcher(rule);while (matcher.find()) {// 获取括号一内的匹配结果String separator = matcher.group(1);// 获取括号二内的匹配结果String content = matcher.group(2);// 分隔符为空,表示匹配的是表达式的开始部分if (separator == null || separator.length() == 0) {// 创建 MatchPair 对象pair = new MatchPair();// 存储 <匹配项, MatchPair> 键值对,比如 <host, MatchPair>condition.put(content, pair); } // 如果分隔符为 &,表明接下来也是一个条件else if ("&".equals(separator)) {// 尝试从 condition 获取 MatchPairif (condition.get(content) == null) {// 未获取到 MatchPair,重新创建一个,并放入 condition 中pair = new MatchPair();condition.put(content, pair);} else {pair = condition.get(content);}} // 分隔符为 =else if ("=".equals(separator)) {if (pair == null)throw new ParseException("Illegal route rule ...");values = pair.matches;// 将 content 存入到 MatchPair 的 matches 集合中values.add(content);} //  分隔符为 != else if ("!=".equals(separator)) {if (pair == null)throw new ParseException("Illegal route rule ...");values = pair.mismatches;// 将 content 存入到 MatchPair 的 mismatches 集合中values.add(content);}// 分隔符为 ,else if (",".equals(separator)) {if (values == null || values.isEmpty())throw new ParseException("Illegal route rule ...");// 将 content 存入到上一步获取到的 values 中,可能是 matches,也可能是 mismatchesvalues.add(content);} else {throw new ParseException("Illegal route rule ...");}}return condition;
}
复制代码

以上就是路由规则的解析逻辑,该逻辑由正则表达式和一个 while 循环以及数个条件分支组成。下面通过一个示例对解析逻辑进行演绎。示例为 host = 2.2.2.2 & host != 1.1.1.1 & method = hello。正则解析结果如下:

括号一      括号二
复制代码
  1. null host
  2. = 2.2.2.2
  3. & host
  4. != 1.1.1.1
  5. & method
  6. = hello 现在线程进入 while 循环:

第一次循环:分隔符 separator = null,content = "host"。此时创建 MatchPair 对象,并存入到 condition 中,condition = {"host": MatchPair@123}

第二次循环:分隔符 separator = "=",content = "2.2.2.2",pair = MatchPair@123。此时将 2.2.2.2 放入到 MatchPair@123 对象的 matches 集合中。

第三次循环:分隔符 separator = "&",content = "host"。host 已存在于 condition 中,因此 pair = MatchPair@123。

第四次循环:分隔符 separator = "!=",content = "1.1.1.1",pair = MatchPair@123。此时将 1.1.1.1 放入到 MatchPair@123 对象的 mismatches 集合中。

第五次循环:分隔符 separator = "&",content = "method"。condition.get("method") = null,因此新建一个 MatchPair 对象,并放入到 condition 中。此时 condition = {"host": MatchPair@123, "method": MatchPair@ 456}

第六次循环:分隔符 separator = "=",content = "2.2.2.2",pair = MatchPair@456。此时将 hello 放入到 MatchPair@456 对象的 matches 集合中。

循环结束,此时 condition 的内容如下:

{"host": {"matches": ["2.2.2.2"],"mismatches": ["1.1.1.1"]},"method": {"matches": ["hello"],"mismatches": []}
}
复制代码

路由规则的解析过程稍微有点复杂,大家可通过 ConditionRouter 的测试类对该逻辑进行测试。并且找一个表达式,对照上面的代码走一遍,加深理解。

2.2 服务路由

服务路由的入口方法是 ConditionRouter 的 router 方法,该方法定义在 Router 接口中。实现代码如下:

public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {if (invokers == null || invokers.isEmpty()) {return invokers;}try {// 先对服务消费者条件进行匹配,如果匹配失败,表明服务消费者 url 不符合匹配规则,// 无需进行后续匹配,直接返回 Invoker 列表即可。比如下面的规则://     host = 10.20.153.10 => host = 10.0.0.10// 这条路由规则希望 IP 为 10.20.153.10 的服务消费者调用 IP 为 10.0.0.10 机器上的服务。// 当消费者 ip 为 10.20.153.11 时,matchWhen 返回 false,表明当前这条路由规则不适用于// 当前的服务消费者,此时无需再进行后续匹配,直接返回即可。if (!matchWhen(url, invocation)) {return invokers;}List<Invoker<T>> result = new ArrayList<Invoker<T>>();// 服务提供者匹配条件未配置,表明对指定的服务消费者禁用服务,也就是服务消费者在黑名单中if (thenCondition == null) {logger.warn("The current consumer in the service blacklist...");return result;}// 这里可以简单的把 Invoker 理解为服务提供者,现在使用服务提供者匹配规则对 // Invoker 列表进行匹配for (Invoker<T> invoker : invokers) {// 若匹配成功,表明当前 Invoker 符合服务提供者匹配规则。// 此时将 Invoker 添加到 result 列表中if (matchThen(invoker.getUrl(), url)) {result.add(invoker);}}// 返回匹配结果,如果 result 为空列表,且 force = true,表示强制返回空列表,// 否则路由结果为空的路由规则将自动失效if (!result.isEmpty()) {return result;} else if (force) {logger.warn("The route result is empty and force execute ...");return result;}} catch (Throwable t) {logger.error("Failed to execute condition router rule: ...");}// 原样返回,此时 force = false,表示该条路由规则失效return invokers;
}
复制代码

router 方法先是调用 matchWhen 对服务消费者进行匹配,如果匹配失败,直接返回 Invoker 列表。如果匹配成功,再对服务提供者进行匹配,匹配逻辑封装在了 matchThen 方法中。下面来看一下这两个方法的逻辑:

boolean matchWhen(URL url, Invocation invocation) {// 服务消费者条件为 null 或空,均返回 true,比如://     => host != 172.22.3.91// 表示所有的服务消费者都不得调用 IP 为 172.22.3.91 的机器上的服务return whenCondition == null || whenCondition.isEmpty() || matchCondition(whenCondition, url, null, invocation);  // 进行条件匹配
}private boolean matchThen(URL url, URL param) {// 服务提供者条件为 null 或空,表示禁用服务return !(thenCondition == null || thenCondition.isEmpty()) && matchCondition(thenCondition, url, param, null);  // 进行条件匹配
}
复制代码

这两个方法长的有点像,不过逻辑上还是有差别的,大家注意看。这两个方法均调用了 matchCondition 方法,但它们所传入的参数是不同的。这个需要特别注意一下,不然后面的逻辑不好弄懂。下面我们对这几个参数进行溯源。matchWhen 方法向 matchCondition 方法传入的参数为 [whenCondition, url, null, invocation],第一个参数 whenCondition 为服务消费者匹配条件,这个前面分析过。第二个参数 url 源自 route 方法的参数列表,该参数由外部类调用 route 方法时传入。比如:

private List<Invoker<T>> route(List<Invoker<T>> invokers, String method) {Invocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]);List<Router> routers = getRouters();if (routers != null) {for (Router router : routers) {if (router.getUrl() != null) {// 注意第二个参数invokers = router.route(invokers, getConsumerUrl(), invocation);}}}return invokers;
}
复制代码

上面这段代码来自 RegistryDirectory,第二个参数表示的是服务消费者 url。matchCondition 的 invocation 参数也是从这里传入的。

接下来再来看看 matchThen 向 matchCondition 方法传入的参数 [thenCondition, url, param, null]。第一个参数不用解释了。第二个和第三个参数来自 matchThen 方法的参数列表,这两个参数分别为服务提供者 url 和服务消费者 url。搞清楚这些参数来源后,接下来就可以分析 matchCondition 方法了。

private boolean matchCondition(Map<String, MatchPair> condition, URL url, URL param, Invocation invocation) {// 将服务提供者或消费者 url 转成 MapMap<String, String> sample = url.toMap();boolean result = false;// 遍历 condition 列表for (Map.Entry<String, MatchPair> matchPair : condition.entrySet()) {// 获取匹配项名称,比如 host、method 等String key = matchPair.getKey();String sampleValue;// 如果 invocation 不为空,且 key 为 mehtod(s),表示进行方法匹配if (invocation != null && (Constants.METHOD_KEY.equals(key) || Constants.METHODS_KEY.equals(key))) {// 从 invocation 获取被调用方法的名称sampleValue = invocation.getMethodName();} else {// 从服务提供者或消费者 url 中获取指定字段值,比如 host、application 等sampleValue = sample.get(key);if (sampleValue == null) {// 尝试通过 default.xxx 获取相应的值sampleValue = sample.get(Constants.DEFAULT_KEY_PREFIX + key);}}// --------------------✨ 分割线 ✨-------------------- //if (sampleValue != null) {// 调用 MatchPair 的 isMatch 方法进行匹配if (!matchPair.getValue().isMatch(sampleValue, param)) {// 只要有一个规则匹配失败,立即返回 false 结束方法逻辑return false;} else {result = true;}} else {// sampleValue 为空,表明服务提供者或消费者 url 中不包含相关字段。此时如果 // MatchPair 的 matches 不为空,表示匹配失败,返回 false。比如我们有这样// 一条匹配条件 loadbalance = random,假设 url 中并不包含 loadbalance 参数,// 此时 sampleValue = null。既然路由规则里限制了 loadbalance 必须为 random,// 但 sampleValue = null,明显不符合规则,因此返回 falseif (!matchPair.getValue().matches.isEmpty()) {return false;} else {result = true;}}}return result;
}
复制代码

如上,matchCondition 方法看起来有点复杂,这里简单说明一下。分割线以上的代码实际上用于获取 sampleValue 的值,分割线以下才是进行条件匹配。条件匹配调用的逻辑封装在 isMatch 中,代码如下:

private boolean isMatch(String value, URL param) {// 情况一:matches 非空,mismatches 为空if (!matches.isEmpty() && mismatches.isEmpty()) {// 遍历 matches 集合,检测入参 value 是否能被 matches 集合元素匹配到。// 举个例子,如果 value = 10.20.153.11,matches = [10.20.153.*],// 此时 isMatchGlobPattern 方法返回 truefor (String match : matches) {if (UrlUtils.isMatchGlobPattern(match, value, param)) {return true;}}// 如果所有匹配项都无法匹配到入参,则返回 falsereturn false;}// 情况二:matches 为空,mismatches 非空if (!mismatches.isEmpty() && matches.isEmpty()) {for (String mismatch : mismatches) {// 只要入参被 mismatches 集合中的任意一个元素匹配到,就返回 falseif (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {return false;}}// mismatches 集合中所有元素都无法匹配到入参,此时返回 truereturn true;}// 情况三:matches 非空,mismatches 非空if (!matches.isEmpty() && !mismatches.isEmpty()) {// matches 和 mismatches 均为非空,此时优先使用 mismatches 集合元素对入参进行匹配。// 只要 mismatches 集合中任意一个元素与入参匹配成功,就立即返回 false,结束方法逻辑for (String mismatch : mismatches) {if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {return false;}}// mismatches 集合元素无法匹配到入参,此时再使用 matches 继续匹配for (String match : matches) {// 只要 matches 集合中任意一个元素与入参匹配成功,就立即返回 trueif (UrlUtils.isMatchGlobPattern(match, value, param)) {return true;}}// 全部失配,则返回 falsereturn false;}// 情况四:matches 和 mismatches 均为空,此时返回 falsereturn false;
}
复制代码

isMatch 方法逻辑比较清晰,由三个条件分支组成,用于处理四种情况。这里对四种情况下的匹配逻辑进行简单的总结,如下:

条件 过程 情况一 matches 非空,mismatches 为空 遍历 matches 集合元素,并与入参进行匹配。只要有一个元素成功匹配入参,即可返回 true。若全部失配,则返回 false。 情况二 matches 为空,mismatches 非空 遍历 mismatches 集合元素,并与入参进行匹配。只要有一个元素成功匹配入参,立即 false。若全部失配,则返回 true。 情况三 matches 非空,mismatches 非空 优先使用 mismatches 集合元素对入参进行匹配,只要任一元素与入参匹配成功,就立即返回 false,结束方法逻辑。否则再使用 matches 中的集合元素进行匹配,只要有任意一个元素匹配成功,即可返回 true。若全部失配,则返回 false 情况四 matches 为空,mismatches 为空 直接返回 false isMatch 方法是通过 UrlUtils 的 isMatchGlobPattern 方法进行匹配,因此下面我们再来看看 isMatchGlobPattern 方法的逻辑。

public static boolean isMatchGlobPattern(String pattern, String value, URL param) {if (param != null && pattern.startsWith("$")) {// 引用服务消费者参数,param 参数为服务消费者 urlpattern = param.getRawParameter(pattern.substring(1));}// 调用重载方法继续比较return isMatchGlobPattern(pattern, value);
}public static boolean isMatchGlobPattern(String pattern, String value) {// 对 * 通配符提供支持if ("*".equals(pattern))// 匹配规则为通配符 *,直接返回 true 即可return true;if ((pattern == null || pattern.length() == 0)&& (value == null || value.length() == 0))// pattern 和 value 均为空,此时可认为两者相等,返回 truereturn true;if ((pattern == null || pattern.length() == 0)|| (value == null || value.length() == 0))// pattern 和 value 其中有一个为空,表明两者不相等,返回 falsereturn false;// 定位 * 通配符位置int i = pattern.lastIndexOf('*');if (i == -1) {// 匹配规则中不包含通配符,此时直接比较 value 和 pattern 是否相等即可,并返回比较结果return value.equals(pattern);}// 通配符 "*" 在匹配规则尾部,比如 10.0.21.*else if (i == pattern.length() - 1) {// 检测 value 是否以“不含通配符的匹配规则”开头,并返回结果。比如:// pattern = 10.0.21.*,value = 10.0.21.12,此时返回 truereturn value.startsWith(pattern.substring(0, i));}// 通配符 "*" 在匹配规则头部else if (i == 0) {// 检测 value 是否以“不含通配符的匹配规则”结尾,并返回结果return value.endsWith(pattern.substring(i + 1));}// 通配符 "*" 在匹配规则中间位置else {// 通过通配符将 pattern 分成两半,得到 prefix 和 suffixString prefix = pattern.substring(0, i);String suffix = pattern.substring(i + 1);// 检测 value 是否以 prefix 开头,且以 suffix 结尾,并返回结果return value.startsWith(prefix) && value.endsWith(suffix);}
}
复制代码

以上就是 isMatchGlobPattern 两个重载方法的全部逻辑,这两个方法分别对普通的匹配过程,以及”引用消费者参数“和通配符匹配等特性提供了支持。这两个方法的逻辑不是很复杂,且代码中也进行了比较详细的注释,因此就不多说了。

3. 总结

本篇文章对条件路由的表达式解析和服务路由过程进行了较为细致的分析。总的来说,条件路由的代码还是有一些复杂的,需要静下心来看。在阅读条件路由代码的过程中,要多调试。一般的框架都会有单元测试,Dubbo 也不例外,因此大家可以直接通过 ConditionRouterTest 对条件路由进行调试,无需重头构建测试用例。

欢迎大家加入Java高级架构群 378461078

相关文章:

C++中std::reverse和std::reverse_copy的使用

std::reverse&#xff1a;反转排序容器内指定范围中的元素。std::reverse_copy与std::reverse唯一的区别是&#xff1a;reverse_copy会将结果拷贝到另外一个容器中&#xff0c;而不影响原容器的内容。std::reverse: defined in header <algorithm>, reverses the order …

真相!30K拿到互联网大厂offer,网友:我服了!

最近笔者在知乎刷到一个帖子&#xff0c;其中&#xff0c;这条回答让人印象深刻&#xff1a;其实&#xff0c;最近几年人工智能大火&#xff0c;其中深度学习岗位的薪酬爆增&#xff0c;BAT大厂高薪招聘AI人才&#xff0c;收到的简历却寥寥无几&#xff1f;究竟是大厂岗位要求高…

OracleDesigner学习笔记1――安装篇

OracleDesigner学习笔记1――安装篇 QQ&#xff1a;King MSN&#xff1a;qiutianwhmsn.com Email&#xff1a;qqkinggmail.com 一&#xff0e; 前言 Oracle是当今最流行的关系型数据库之一&#xff0c;和很多朋友一样&#xff0c;我也是一个Oracle的爱好者&#xff0c;从…

C++/C++11中std::queue的使用

std::queue: 模板类queue定义在<queue>头文件中。队列(Queue)是一个容器适配器(Container adaptor)类型&#xff0c;被特别设计用来运行于FIFO(First-in first-out)场景&#xff0c;在该场景中&#xff0c;只能从容器一端添加(Insert)元素&#xff0c;而在另一端提取(Ext…

常见的http状态码(Http Status Code)

常见的http状态码&#xff1a;&#xff08;收藏学习&#xff09; 2**开头 &#xff08;请求成功&#xff09;表示成功处理了请求的状态代码。 200 &#xff08;成功&#xff09; 服务器已成功处理了请求。 通常&#xff0c;这表示服务器提供了请求的网页。201 &#xff08;已创…

“不给钱就删库”的勒索病毒, 程序员该如何防护?

作者 | 阿木&#xff0c;王洪鹏&#xff0c;运营有个人公众号新新生活志。目前任职网易云计算技术部高级工程师&#xff0c;近3年云计算从业经验&#xff0c;爱读书、爱写作、爱技术。责编 | 郭芮来源 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;近期一家名为ProPub…

ruby实时查看日志

(文章是从我的个人主页上粘贴过来的&#xff0c; 大家也可以访问我的主页 www.iwangzheng.com) 在调试代码的时候&#xff0c;把日志文件打开&#xff0c;边操作边调试能很快帮助我们发现系统中存在的问题。 $tail rails_2014_03_03.log -f转载于:https://www.cnblogs.com/iw…

干货 | OpenCV看这篇就够了,9段代码详解图像变换基本操作

作者 | 王天庆&#xff0c;长期从事分布式系统、数据科学与工程、人工智能等方面的研究与开发&#xff0c;在人脸识别方面有丰富的实践经验。现就职某世界100强企业的数据实验室&#xff0c;从事数据科学相关技术领域的预研工作。来源 | 大数据&#xff08;ID&#xff1a;hzdas…

C++/C++11中std::priority_queue的使用

std::priority_queue:在优先队列中&#xff0c;优先级高的元素先出队列&#xff0c;并非按照先进先出的要求&#xff0c;类似一个堆(heap)。其模板声明带有三个参数&#xff0c;priority_queue<Type, Container, Functional>, 其中Type为数据类型&#xff0c;Container为…

left join 和 left outer join 的区别

老是混淆&#xff0c;做个笔记&#xff0c;转自&#xff1a;https://www.cnblogs.com/xieqian111/p/5735977.html left join 和 left outer join 的区别 通俗的讲&#xff1a; A left join B 的连接的记录数与A表的记录数同 A right join B 的连接的记录数与…

php减少损耗的方法之一 缓存对象

即把实例后的对象缓存起来(存入变量)&#xff0c;当需要再次实例化时&#xff0c;先去缓存里查看是否存在。存在则返回。否则实例化。转载于:https://www.cnblogs.com/zuoxiaobing/p/3581139.html

windows10 vs2013控制台工程中添加并编译cuda8.0文件操作步骤

一般有两种方法可以在vs2013上添加运行cuda8.0程序&#xff1a;一、直接新建一个基于CUDA8.0的项目&#xff1a;如下图所示&#xff0c;点击确定后即可生成test_cuda项目&#xff1b;默认会自动生成一个kernel.cu文件&#xff1b;默认已经配置好Debug/Release, Win32/x64环境&a…

算法人必懂的进阶SQL知识,4道面试常考题

&#xff08;图片付费下载自视觉中国&#xff09;作者 | 石晓文来源&#xff5c;小小挖掘机&#xff08;ID&#xff1a;wAlsjwj&#xff09;近期在不同群里有小伙伴们提出了一些在面试和笔试中遇到的Hive SQL问题&#xff0c;Hive作为算法工程师的一项必备技能&#xff0c;在面…

007-迅雷定时重启AutoHotkey脚本-20190411

;; 定时重启迅雷.ahk,;;~ 2019年04月11日;#SingleInstance,forceSetWorkingDir,%A_ScriptDir%DetectHiddenWindows,OnSetTitleMatchMode,2#Persistent ;让脚本持久运行&#xff08;即直到用户关闭或遇到 ExitApp&#xff09;。#NoEnv;~ #NoTrayIcon Hotkey,^F10,ExitThisApp lo…

关于ExtJS在使用下拉列表框的二级联动获取数据

2019独角兽企业重金招聘Python工程师标准>>> 使用下拉列表框的二级联动获取数据&#xff0c;如果第一个下拉列表框有默认值时&#xff0c;需要设置fireEvent执行select事件 示例&#xff1a; var combo Ext.getCmp("modifyBuildCom"); combo.setValue(re…

C++中std::sort/std::stable_sort/std::partial_sort的区别及使用

某些算法会重排容器中元素的顺序&#xff0c;如std::sort。调用sort会重排输入序列中的元素&#xff0c;使之有序&#xff0c;它默认是利用元素类型的<运算符来实现排序的。也可以重载sort的默认排序&#xff0c;即通过sort的第三个参数&#xff0c;此参数是一个谓词(predic…

阿里云智能 AIoT 首席科学家丁险峰:阿里全面进军IoT这一年 | 问底中国IT技术演进...

作者 | 屠敏受访者 | 丁险峰来源 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;「忽如一夜春风来&#xff0c;千树万树梨花开。」从概念的流行、至科技巨头的相继入局、再到诸多应用的落地&#xff0c;IoT 的发展终于在万事俱备只欠东风的条件下真正地迎来了属于自己的…

eBCC性能分析最佳实践(1) - 线上lstat, vfs_fstatat 开销高情景分析...

Guide: eBCC性能分析最佳实践&#xff08;0&#xff09; - 开启性能分析新篇章eBCC性能分析最佳实践&#xff08;1&#xff09; - 线上lstat, vfs_fstatat 开销高情景分析eBCC性能分析最佳实践&#xff08;2&#xff09; - 一个简单的eBCC分析网络函数的latency敬请期待...0. I…

spring-data-mongodb必须了解的操作

http://docs.spring.io/spring-data/data-mongo/docs/1.0.0.M5/api/org/springframework/data/mongodb/core/MongoTemplate.html 在线api文档 1关键之识别 KeywordSampleLogical resultGreaterThanfindByAgeGreaterThan(int age){"age" : {"$gt" : age}}Le…

旷视张祥雨:高效轻量级深度模型的研究和实践 | AI ProCon 2019

演讲嘉宾 | 张祥雨&#xff08;旷视研究院主任研究员、基础模型组负责人&#xff09;编辑 | Just出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;基础模型是现代视觉识别系统中一个至关重要的关注点。基础模型的优劣主要从精度、速度或功耗等角度判定&#xff0c;如何…

Python脱产8期 Day02

一 语言分类 机器语言&#xff0c;汇编语言&#xff0c;高级语言&#xff08;编译和解释&#xff09; 二 环境变量 1、配置环境变量不是必须的2、配置环境变量的目的&#xff1a;为终端提供执行环境 三Python代码执行的方式 1交互式&#xff1a;.控制台直接编写运行python代码 …

分别用Eigen和C++(OpenCV)实现图像(矩阵)转置

(1)、标量(scalar)&#xff1a;一个标量就是一个单独的数。(2)、向量(vector)&#xff1a;一个向量是一列数&#xff0c;这些数是有序排列的&#xff0c;通过次序中的索引&#xff0c;可以确定每个单独的数。(3)、矩阵(matrix)&#xff1a;矩阵是一个二维数组&#xff0c;其中的…

Linux基础优化

***************************************************************************************linux系统的优化有很多&#xff0c;我简单阐述下我经常优化的方针&#xff1a;记忆口诀&#xff1a;***********************一清、一精、一增&#xff1b;两优、四设、七其他。*****…

数据集cifar10到Caffe支持的lmdb/leveldb转换的实现

在 http://blog.csdn.net/fengbingchun/article/details/53560637 对数据集cifar10进行过介绍&#xff0c;它是一个普通的物体识别数据集。为了使用Caffe对cifar10数据集进行train&#xff0c;下面实现了将cifar10到lmdb/leveldb的转换实现&#xff1a;#include "funset.h…

计算两个时间的间隔时间是多少

/*** 计算两个时间间隔* param startTime 开始时间* param endTime 结束时间* param type 类型&#xff08;1&#xff1a;相隔小时 2&#xff1a;&#xff09;* return*/public static int compareTime(String startTime, String endTime, int type) {if (endTime nul…

作为西二旗程序员,我是这样学习的.........

作为一名合格的程序员&#xff0c;需要时刻保持对新技术的敏感度&#xff0c;并且要定期更新自己的技能储备&#xff0c;是每个技术人的日常必修课。但要做到这一点&#xff0c;知乎上的网友说最高效的办法竟然是直接跟 BAT 等一线大厂取经。讲真的&#xff0c;BAT大厂的平台是…

2月国内搜索市场:360继续上升 百度下降0.62%

IDC评述网&#xff08;idcps.com&#xff09;03月06日报道&#xff1a;根据CNZZ数据显示&#xff0c;在国内搜索引擎市场中&#xff0c;百度在2014年2月份所占的份额继续被蚕食&#xff0c;环比1月份&#xff0c;下降了0.62%&#xff0c;为60.50%。与此相反&#xff0c;360搜索…

不止于刷榜,三大CV赛事夺冠算法技术的“研”与“用”

&#xff08;由AI科技大本营付费下载自视觉中国&#xff09;整理 | Jane出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;在 5 个月时间里&#xff08;5月-9月&#xff09;&#xff0c;创新工场旗下人工智能企业创新奇智连续在世界顶级人脸检测竞赛 WIDER …

Ubuntu14.04上编译指定版本的protobuf源码操作步骤

Google Protobuf的介绍可以参考 http://blog.csdn.net/fengbingchun/article/details/49977903 &#xff0c;这里介绍在Ubuntu14.04上编译安装指定版本的protobuf的操作步骤&#xff0c;这里以2.4.1为例&#xff1a;1&#xff0e; Ubuntu14.04上默认安装的是2.5.0&#xff0c;…

Linux下,各种解压缩命令集合

Linux下&#xff0c;各种解压缩命令集合tar xvfj lichuanhua.tar.bz2tar xvfz lichuanhua.tar.gztar xvfz lichuanhua.tgztar xvf lichuanhua.tarunzip lichuanhua.zip.gz解压 1&#xff1a;gunzip FileName.gz解压 2&#xff1a;gzip -d FileName.gz压缩&#xff1a;gzip File…