深入理解 System.currentTimeMillis() 与时区

今天在解答同事对于 Unix Timestamp 的时区问题的疑问时,看到了这篇文章:

深入理解System.currentTimeMillis()

https://coderdeepwater.cn/2020/12/29/java_source_code/currentTimeMillis/

通过阅读发现,虽然该文章对于这个函数调用的原理解释的非常好,但是却在开头处犯了一个致命的错误,误导了我的同事。在这篇文章中,作者说:

深入理解System.currentTimeMillis()

System.currentTimeMills()的返回值取决于Java运行时系统的本地时区!千万不要忘记这一点!
同一时刻,在英国和中国的两个人同时用System.currentTimeMills()获取当前系统时间,其返回值不是一样的,除非手动将操作系统的时区设置成同一个时区(英国使用UTC+0,而中国使用UTC+8,中国比英国快8个小时).

这个观点是完全错误的,正因为这里的错误误导了我的同事,走入了错误的排查方向。

首先说结论:

  1. System.currentTimeMillis() (更多…)

使用 Quartz 调度器遇到的一些问题

最近,线上运行的定时任务出现调度失败。调整了相关参数后,虽然遏制了调度失败的情况,却导致任务调度的延迟极度增加。同时还观察到,在多机部署的环境中,负载极不均匀,于是深入代码排查一番。

TL; DR

  1. 不要使用 Spring 提供的 org.springframework.scheduling.quartz.SchedulerFactoryBean#setTaskExecutor 方法自定义工作线程池
  2. 要注意 Spring 与 Quartz 的部分配置默认值不一致,如 org.quartz.threadPool.class
  3. 一般情况下使用 Quartz 提供的 SimpleThreadPool 配合参数 org.quartz.threadPool.threadCount 调整最大线程数即可
  4. 如果需要自定义工作线程池,则必须直接实现 org.quartz.spi.ThreadPool 接口,并一定要实现 (更多…)

jOOQ 与 Spring 的一些注意事项

jOOQ 全称 Java Object Oriented Querying,即面向 Java 对象查询。它是 Data Geekery 公司研发的 DA 方案 (Data Access Layer),是一个 ORM 框架。

使用 jOOQ,既不像 Hibernate 等框架封装过高,无法触及 SQL 底层;也不像 MyBatis 等,配置太过繁琐。同时还是 Type Safe 的框架,编译时即可最大程度的发现问题。

不过在 jOOQ 配合 String Cloud 使用的时候,还是踩了几个小坑,特别说明一下。随时补充新遇到的问题。

一、事物问题

jOOQ 默认有一套自己的流式 API,来支持事物。不过,在 Spring 里面,我们使用的最多的还是 @EnableTransactionManagement@Transactional 注解。使用这二者可以开启 Spring (更多…)

Kapacitor 使用 Proxy 连接小电报 API

由于众所周知的原因,小电报的 Bot API 在某些地方是完全无法访问的。

那么,这导致我们最好用,最快捷的告警方式被掐死了。

于是研究了一下,发现官方有一个回复:

https://community.influxdata.com/t/how-to-set-proxy-for-telegram-and-smtp/3250

The Kapacitor 你懂的 agent uses the golang HTTP client, so you can set that with the environment variables $http_proxy (or $https_proxy). As for SMTP, we don’t currently have logic in place for that, but that sounds like a great feature (更多…)

记一则由于整数溢出导致的教科书级的死循环

首先铺垫一下,这段代码的输出是什么?

public static void main(String []args){
    System.out.println(Integer.MAX_VALUE + 1);
}

很多人可能很快就能答出来,正确答案是:-2147483648

那么接下来看这段代码:

public static void main(String []args){
    Long total = Long.MAX_VALUE;
    for (int i = 0; i < total; i++) {
        System.out.println(i);
    }
}

乍看之下似乎没啥大毛病,但是结合前面的铺垫,就会发现:

当 i 增长到 Integer.MAX_VALUE  的时候,“奇迹”出现了。接下来,下一个 i 值变为了 -2147483648。跟 (更多…)

MyBatis 获取自增 ID 的小坑

在 MyBatis 中,获取自增 ID 主要有两个方法:

  1. 在 SQL 中增加两个属性,useGeneratedKeys  和 keyProperty
  2. 增加 selectKey  语句块,执行自定义 SQL 获取自增 ID

针对这几种方法,有几个小问题需要注意:

  1. useGeneratedKeys  是基于 JDBC 的 Prepared Statement  实现的,具体做法是调用 JDBC 的 getGeneratedKeys  方法,在 Prepared Statement  对象中取相应的值。当 DB 为 MySQL 的时候,会在响应时返回相应的自增字段值。但是,在某些实现 DB 分库分表的 Proxy 中,由于涉及 SQL 转换、重写的问题,可能对 Prepared Statement  的支持并不完整,导致 useGeneratedKeys  选项无法正常返回对应的自增 (更多…)

JVM DNS IP 地址缓存 (InetAddress)

(本文所有内容基于 Oracle JDK)

JVM IP 地址缓存

JVM 的缓存策略

由于 DNS 解析是一个访问量大的不是很可靠的网络调用,因此通常大部分系统都会对 DNS 解析的结果进行一定程度的缓存。如运营商的 LDNS、常用的浏览器、包括操作系统本身,都会对 DNS 解析的结果进行缓存。在 JVM 中,为了加速 DNS 解析的过程,当然也进行了相关的缓存。

在 Java 中,最常用的进行 DNS 解析的方法就是:

java.net.InetAddress.getAllByName(“www.google.com”);

而这个方法本身也会对解析的结果进行相应的缓存。看官方文档:

InetAddress Caching

The InetAddress class has a cache to store successful as well as unsuccessful (更多…)

ScheduledExecutorService 一个小坑

今天在排查一个线上问题是发现一个使用 ScheduledExecutorService 执行的定时任务在执行了一次以后就再也没有执行过了。于是 Dump 了内存来检查问题。

首先,搜索对应 Task 的类,发现在堆中找不到这个类的实例。可是明明已经成功执行了一次,为何没有实例?

于是再去找 ScheduledExecutorService  对应的 ScheduledThreadPoolExecutor  类,成功筛选出了用来执行定时任务的实例。在实例的 queue 中,却只看到了 6 个 Task 对象,唯独不见了这个出问题的对象。百思不得解,因为日志中这个对象的 Logger 已经打印出来了,说明至少执行了一次,为啥会从内存中消失呢?

在同事的帮助下,查阅了 API 文档,发现了这么一句话:

If any execution of the task encounters (更多…)

记一次 JNI 导致 Java fd 泄露的排查过程

前几天接到反馈,线上某机器上的服务在进行后端调用的时候失败了。查看日志是端口分配失败。通过 netstat -nulp  看到大量端口占用,用户段端口49152 到 65535 全部被占满。于是通过 awk sort 和 uniq 统计出每个进程的端口占用情况,发现某些 Java 服务占用了 2w+ 端口,于是对该服务展开分析。

首先考虑的是应用有大量 Socket 对象没有关闭释放,于是将堆 dump 出来,使用 VisualVM 加载分析。由于泄露的是 UDP 端口,于是考虑查找 Java 中 UDP socket 对应的对象 DatagramSocket 。可是一顿操作之后发现堆中并不存在该类对象,倒是找到了几个 DatagramSocketAdaptor  对象。查看了框架发现使用了 Netty 做 NIO,因此实际使用的是 NioDatagramChannel 和 DatagramSocketAdaptor (更多…)

Tomcat 在处理 Cookie 的时候的几个小坑

今天在代码中调用 HttpServletRequest  对象的 getCookies()  方法时,发现实际得到的 Cookie 数量与提交的不符。实际提交了 17 个 Cookie,但是获取到的只有 14 个。

经过排查,发现如果调用 getHeaders(“Cookie”)  方法,获取原始的 Cookie 串,是可以拿到正确的 17 个 Cookie 组成的字符串的。于是确认应该是 Tomcat 在处理 Cookie 的时候进行了过滤。

经过一番搜索,发现了这个文档:

http://tomcat.apache.org/tomcat-7.0-doc/config/systemprops.html

其中,跟 Cookie 相关的参数有:

org.apache.tomcat.util.http. ServerCookie.ALLOW_EQUALS_IN_VALUE If (更多…)