对于一个数据结构,如果作者没有表明该数据结构是线程安全的,我一般都会认为不是线程安全的,因为编写高效且线程安全的数据结构是比较困难的,关于 RoaringBitmap 的线程安全问题,早在 2015 年就有用户反馈希望能够有线程安全的实现,详见该 issue: thread safety,仅有几次提交后至 2017 年后就再无下文,而 RoaringBitmap 的主线一直在演进,且在官方文档中甚至没有关于线程安全实现的介绍,以至后续在我的工程中,关于 RoaringBitmap 的并发修改使用了 ReentrantReadWriteLock 去保证多线程下的线程安全,使用读写锁是为了提高读多于写场景下的吞吐量。
ShedLock
在 Java 应用中,经常会使用 Spring 的 @Scheduled
注解用于处理定时任务,但是在集群环境中,这会导致被 @Scheduled
注解标记的方法在每个应用节点都被定时执行,在早期的工程代码中,开发使用一个定时任务 IP 去指定执行任务的节点 IP,若当前节点 IP 与指定的定时任务 IP 一致才执行定时任务,该方案需要在每个 @Scheduled
方法中硬编码 IP 判断逻辑,且在部分应用迁移至 k8s 后因节点 IP 不确定导致该方案不再可用,故查询了下相关的解决方案。
我们在部分工程引入了 ShedLock 用于处理分布式环境下的定时任务重复执行的问题,下面简单记录一下 ShedLock 的实现机制。
OuterJoinBehavior
业务方反馈有一条 SQL 在 Hive on Spark 上执行会触发 OOM,原 SQL 经简化后如下:
1 | SELECT c.name, COUNT(DISTINCT u.user_id) AS `uv`, COUNT(*) AS `pv` FROM u LEFT JOIN w ON get_json_object(u.param, '$.id') = w.id LEFT JOIN c ON w.cat_id = c.id WHERE u.page = 'xxx.htm' AND c.name LIKE '%筛选%' AND u.dt >= '20210715' GROUP BY c.name ORDER BY `pv` DESC LIMIT 10; |
其中 u.dt
为 u
表的分区列,按照该 SQL 的含义,即只需要扫描大于等于 20210715 的分区数据,但是通过检查 SQL 执行计划可视化图及查看逻辑计划,发现 u.dt
的筛选条件并没有下推至 u
表,而是在将 u
表与 w
表进行 LEFT JOIN 后再进行了过滤,导致扫描了整张 u
表,而又因为 u
表相当大,最后触发了 OOM,随后查询 Hive 关于外连接 JOIN 行为的文档:OuterJoinBehavior - Apache Hive - Apache Software Foundation,该文档详细描述了什么时候可以执行谓词下推,什么时候不能执行谓词下推,但是,所有的例子都是两张表时的示例,并没有解释多张表时的谓词下推规则,根据该文档的描述,我们把 SQL 调整为先 LEFT JOIN 其中两张表为临时表再进行一次 LEFT JOIN 后,成功进行了谓词下推,解决了该问题。
如,以下两种写法都进行了谓词下推:
1 | SELECT c.name, COUNT(DISTINCT u.user_id) `uv`, COUNT(*) AS `pv` FROM (SELECT u.user_id, w.cat_id FROM u LEFT JOIN w ON get_json_object(u.param, '$.id') = w.id WHERE u.dt >= '20210715' AND u.page = 'xxx.htm') LEFT JOIN c ON w.cat_id = c.id AND c.name LIKE '%筛选%' GROUP BY c.name ORDER BY `pv` DESC LIMIT 10; |
2021-12-02
今天阅读 《Spark SQL内核剖析》 时发现在第 11 章的 “SQL 写法的陷阱” 第三小节中举的例子即为以上引用到的链接中的内容:OuterJoinBehavior - Apache Hive - Apache Software Foundation。
Reference
《Hive 编程指南》
Understanding Spark’s Logical and Physical Plan in layman’s term
关于 Zip 文件中的资源查找问题
1 | InputStream InputStream = loader.getResourceAsStream(ipDirInJar + File.separator + dexFileName); |
以上代码同事反馈在本地运行时 inputStream
为 null,发现同事使用的 Windows 系统,初步怀疑为编码问题或路径分隔符问题,经过本地 debug,发现寻找 jar 包中的含有路径分隔符的文件时,最终调用了以下方法,在 Windows 系统上返回了 0:
1 | /Library/Java/JavaVirtualMachines/openjdk-8.jdk/Contents/Home/src.zip!/java/util/zip/ZipFile.java:335 |
经过查询文档,原来 zip 文件里面的路径分隔符必须使用 /
,而不能使用平台独立的文件系统路径分隔符,故调整为了 /
解决了该问题。
4.4.17.1 The name of the file, with optional relative path. The path stored MUST NOT contain a drive or device letter, or a leading slash. All slashes MUST be forward slashes ‘/‘ as opposed to backwards slashes ‘' for compatibility with Amiga and UNIX file systems etc. If input came from standard input, there is no file name field.
Reference
.ZIP File Format Specification
Zip file is created with windows path separator
关于使用通配符时同一路径下 jar 的加载顺序
如果 classpath 含有 /tmp/jars/*
这种存在通配符的目录,而目录下的不同的 jar 正好含有全限定名称相同的类,这个时候是先加载的哪一个 jar 下面的类呢?这个问题之前一直没搞清楚,之前印象中只是说顺序未定义,但是为什么是未定义的呢,直到一次线上发布,在其中一台机器报类加载相关的错误后,才把这个问题具体的定位了一遍,先从 Java 的系统类加载器说起,我们可以写个简单的程序来验证,比如下面的代码:
1 | package me.tianshuang; |
这段代码非常简单,仅仅打印当前运行时的 classpath,我们直接运行,可以看到以下输出:
1 | /Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/lib/tools.jar:/Users/tianshuang/IdeaProjects/test/target/test-classes:/Users/tianshuang/IdeaProjects/test/target/classes:/Users/tianshuang/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/tianshuang/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar |