让 Java Agent 在 Dragonwell 上更好用

背景

Java Agent 技术能够动态修改 Java 应用程序行为,而不用重新修改代码。

正是因为这些特点,很多中间件团队、云厂商团队、开源产品,开始使用 Java Agent 技术来提供一些基础能力,比如 Apache Skywalking、OpenTelemetry 都提供了 Java Agent。

在早前,中间件团队通过SDK提供能力(比如可观测、微服务治理能力等);但中间件团队每次新增特性、修复缺陷,都需要各个业务方更新SDK版本、重新发布。

随着公司架构越来越复杂,随着云厂商开始提供中间件能力,这种逐个推动SDK使用方更新的方式越来越麻烦。

而用了 Java Agent 之后,业务同学只需要写业务代码;中间件能力通过设置环境变量来动态注入Java Agent来实现。Java Agent的更新,也只需要重启应用即可。

问题

我们以一个微服务demo为例。先在一个Kubernetes集群中部署demo,然后通过JAVA_TOOL_OPTIONS使用用Java Agent:

1
2
$ echo $JAVA_TOOL_OPTIONS
-javaagent:/home/admin/.opt/ArmsAgent/arms-bootstrap-1.7.0-SNAPSHOT.jar ...

我们登陆容器时,就能看到注入的 Java Agent:


LeetCode 周赛 311

第一题 2413. Smallest Even Multiple

给你一个正整数 n ,返回 2 和 n 的最小公倍数(正整数)。

显而易见的:

1
2
3
4
5
6
7
func smallestEvenMultiple(n int) int {
if n%2==1{
return n*2
}else{
return n
}
}

第二题 2414. Length of the Longest Alphabetical Continuous Substring

最近恰好做过dp的题目,所以立马dp:

  • 状态定义为 下标i -> 以i结尾的string中,字母序连续的字符串最大长度
  • 状态转移 s[i]和s[i-1]连续,那么在前面基础上+1,如果不连续,则置为1

最后,遍历i,拿最大长度即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func longestContinuousSubstring(s string) int {
dp := make([]int, len(s))
dp[0] = 1
for i := 1; i < len(s); i++ {
if s[i] == s[i-1]+1 {
dp[i] = dp[i-1] + 1
} else {
dp[i] = 1
}
}
res := 1
for i := 0; i < len(dp); i++ {
if res < dp[i] {
res = dp[i]
}
}
return res
}

Java Agent踩坑之appendToSystemClassLoaderSearch问题

从Java Agent报错开始,到JVM原理,到glibc线程安全,再到pthread tls,逐步探究Java Agent诡异报错。

背景

由于阿里云多个产品都提供了Java Agent给用户使用,在多个Java Agent一起使用的场景下,造成了总体Java Agent耗时增加,各个Agent各自存储,导致内存占用、资源消耗增加。

所以我们发起了one-java-agent项目,能够协同各个Java Agent;同时也支持更加高效、方便的字节码注入。

其中,各个Java Agent作为one-java-agent的plugin,在premain阶段是通过多线程启动的方式来加载,从而将启动速度由O(n)降低到O(1),降低了整体Java Agent整体的加载时间。

问题

但最近在新版Agent验证过程中,one-java-agent的premain阶段,发现有如下报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2022-06-16 09:51:09 [oneagent plugin a-java-agent start] ERROR c.a.o.plugin.PluginManagerImpl -start plugin error, name: a-java-agent
com.alibaba.oneagent.plugin.PluginException: start error, agent jar::/path/to/one-java-agent/plugins/a-java-agent/a-java-agent-1.7.0-SNAPSHOT.jar
at com.alibaba.oneagent.plugin.TraditionalPlugin.start(TraditionalPlugin.java:113)
at com.alibaba.oneagent.plugin.PluginManagerImpl.startOnePlugin(PluginManagerImpl.java:294)
at com.alibaba.oneagent.plugin.PluginManagerImpl.access$200(PluginManagerImpl.java:22)
at com.alibaba.oneagent.plugin.PluginManagerImpl$2.run(PluginManagerImpl.java:325)
at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.InternalError: null
at sun.instrument.InstrumentationImpl.appendToClassLoaderSearch0(Native Method)
at sun.instrument.InstrumentationImpl.appendToSystemClassLoaderSearch(InstrumentationImpl.java:200)
at com.alibaba.oneagent.plugin.TraditionalPlugin.start(TraditionalPlugin.java:100)
... 4 common frames omitted
2022-06-16 09:51:09 [oneagent plugin b-java-agent start] ERROR c.a.o.plugin.PluginManagerImpl -start plugin error, name: b-java-agent
com.alibaba.oneagent.plugin.PluginException: start error, agent jar::/path/to/one-java-agent/plugins/b-java-agent/b-java-agent.jar
at com.alibaba.oneagent.plugin.TraditionalPlugin.start(TraditionalPlugin.java:113)
at com.alibaba.oneagent.plugin.PluginManagerImpl.startOnePlugin(PluginManagerImpl.java:294)
at com.alibaba.oneagent.plugin.PluginManagerImpl.access$200(PluginManagerImpl.java:22)
at com.alibaba.oneagent.plugin.PluginManagerImpl$2.run(PluginManagerImpl.java:325)
at java.lang.Thread.run(Thread.java:855)
Caused by: java.lang.IllegalArgumentException: null
at sun.instrument.InstrumentationImpl.appendToClassLoaderSearch0(Native Method)
at sun.instrument.InstrumentationImpl.appendToSystemClassLoaderSearch(InstrumentationImpl.java:200)
at com.alibaba.oneagent.plugin.TraditionalPlugin.start(TraditionalPlugin.java:100)
... 4 common frames omitted

熟悉Java Agent的同学可能能注意到,这是调用Instrumentation.appendToSystemClassLoaderSearch报错了。

但首先appendToSystemClassLoaderSearch的路径是存在的;其次,这个报错的真实原因是在C++部分,比较难排查。


kill-port 清理占用端口的进程

背景

在日常开发中,经常出现端口莫名被占用的情况。比如要启动一个java服务,报错8080端口被占用,这时候就想着快速清理掉占用该端口的进程。

在Linux下,我们可以用熟悉的ss命令来找出进程并删除,但是macOS下,netstat我用的比较少,而且速度也很慢。

最近正好在学习Rust,所以就用Rust来写一个kill-port小工具,也算是入门系统编程了吧。

安装

1
2
3
$ git clone https://github.com/robberphex/kill-port.git
$ cargo build --release
# 将 ./target/release/kill-port 添加到PATH路径中。

使用方法

1
2
3
4
5
6
7
kill-port 0.1.0

USAGE:
kill-port [PORT]...

ARGS:
<PORT>... ports to find

例子:

1
2
# kill掉占用8080端口的进程
$ kill-port 8080

用 Pygments 在 Word 中实现代码高亮

虽然日常都是在写代码,但有的时候,还是需要写一些Word文档。这时候在Word文档中贴代码的时候,就希望能够做到代码高亮。

可以看一下rtf formatter的说明pygmentize -H formatter rtf:

1
2
3
4
5
6
7
Format tokens as RTF markup. This formatter automatically outputs full RTF
documents with color information and other useful stuff. Perfect for Copy and
Paste into Microsoft(R) Word(R) documents.

Please note that ``encoding`` and ``outencoding`` options are ignored.
The RTF format is ASCII natively, but handles unicode characters correctly
thanks to escape sequences.

可以看到,复制出来的是RTF格式的,能够复制、粘贴到Word文档中,并带上语法高亮等特性。

使用时只需要执行如下命令,

1
pygmentize ~/tmp/a-pod.yaml -f rtf | pbcopy

然后在Word等支持RTF格式的软件中粘贴即可。


Spring Native 0.11发布,带来新的AOT引擎和性能优化

Spring Native 0.11已于2021年12月9日发布。

这个宏大的版本是Spring团队五个月辛勤工作的结果,他们一直在研究一个全新的架构,将让Spring使用GraalVM创建原生可执行文件的方式提升到一个新的水平。你目前已经可以已经在start.spring.io上试用了!

想了解有关Spring Native 0.11的更多信息,可以查看来自Spring布道师的新一期的Spring Tips视频(在YouTube上)。

新的AOT引擎

这个版本最大的变化无疑是引入了新的AOT引擎,该引擎在构建时对Spring程序进行深入的转化和分析,并生成所需的GraalVM Native配置。这些转换由Maven和Gradle Spring AOT插件执行。

spring boot native

更深入地说,AOT引擎在构建时评估构建环境,以便生成专门为您的应用程序优化后的 application context 和 Spring factories(Spring Boot背后的插件系统)。在实践中,这意味着:

  • 在运行时执行的 Spring 基础结构更少
  • 在运行时要判断的条件更少
  • 减少反射,因为使用的是编程式bean注册

AOT 引擎根据标记为活动的 Bean、Spring 编程模型的知识以及与 Spring Native 捆绑在一起或由应用程序本身提供的native hint,来推断出将应用程序编译为本机可执行文件所需的native configuration。

aot architecture


MySQL解决ONLY_FULL_GROUP_BY的几个方法

问题

employee 示例数据库为例,测试环境用了一条语句:

SELECT * FROM employees GROUP BY gender;

在测试环境运行正常,但是在线上就会有问题,报错如下:

1
2
3
4
5
6
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'mse.msc_k8s_cluster.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)

原因

MySQL 在ONLY_FULL_GROUP_BY模式下,有如下约束:

执行了带 GROUP BY 和 ORDER BY的SELECT语句,就需要保证你 SELECT 的列都在 GROUP BY 和 ORDER BY 中。

打个比方,如果有数据如下:

emp_no gender
1 M
2 M

在Java的类型系统中,数组有什么缺陷吗?

2020年2月,王垠吐槽了下Java的类型系统,说:

关于程序员对 Java 类型系统的理解,比较高级的一个面试问题是这样:

王垠原版的代码
1
2
3
4
5
6
public static void f() {
String[] a = new String[2];
Object[] b = a;
a[0] = "hi";
b[1] = Integer.valueOf(42);
}

这段代码里面到底哪一行错了?为什么?如果某个 Java 版本能顺利运行这段代码,那么如何让这个错误暴露得更致命一些?
注意这里所谓的「错了」是本质上,原理上的。

那么这儿的“错误”是指什么呢?

TL;DR

如果只能用一句话回答这个问题的话,那么就是:

Java数组不支持泛型,破坏了Java的类型安全性

类型系统的一些前提

一个好的类型系统,能够尽可能早的检测出错误,比如你将一个String赋值给int变量的时候,编译器就会报错,而不是等程序跑起来再报错。


尝鲜Go 1.18中范型版本的map和slice

大家最近都关注到了Go 1.18会支持范型的消息了吧。

作为Golang的内置类型,大家都期待map和slice支持范型后,可以简化很多的判断逻辑,比如Equal逻辑等等。

几天前,Go范型的标准库已经提交了,且可以试用了:

大家也可以读一下对应的代码:https://cs.opensource.google/go/x/exp/+/master:maps/maps.go

废话不多说,我们看下如何尝试范型版本的map和slice吧!

如何使用Go 1.18?

Golang 官网链接只有1.17版本的下载,那么我们如何才能使用1.18版本的Golang呢?

网上翻了翻,有人提供了Golang 1.18版本的Docker镜像 seongwoohong/golang-nightly:1.18,而且保证维护到1.18版本正式发布:


如何通过Kubernetes事件来报告错误

组内有维护一个Kubernetes Webhook,可以拦截pod的创建请求,并做一些修改(比如添加环境变量、添加init-container等)。

业务逻辑本身很简单,但是如果过程中产生错误,就很难处理。要不直接阻止pod创建,那么就有可能导致应用无法启动。要么忽略业务逻辑,那么就会导致静默失败,谁也不知道这儿出现了一个错误。

于是,朴素的想法就是接入告警系统,但这会导致当前组件和具体的告警系统耦合起来。

在Kubernetes中,有Event机制,可以做到把一些事件,比如警告、错误等信息记录下来,就比较适合这个场景。

什么是Kubernetes中的事件/Event?

事件(Event)是 Kubernetes 中众多资源对象中的一员,通常用来记录集群内发生的状态变更,大到集群节点异常,小到 Pod 启动、调度成功等等。

比如我们Describe一个pod,就能看到这个pod对应的事件:

kubectl describe pod sc-b-68867c5dcb-sf9hn

可以看到,从调度、到启动、再到这个pod最终拉取镜像失败,都会通过event的方式记录下来。


Robert Lu

关注我的公众号