一、从arthas-spring-boot-starter开始

官方提供的Arthas Spring Boot Starter,以maven依赖的方式启动了arthas,attach自身进程后常驻;

以starter包为切入点查看arthas是如何进行attach和执行命令等一系列操作的

1.1 starter的configuration拉起AttachArthasClassloader

starter中的ArthasConfiguration类,指定在配置开启时加载attach.ArthasAgent并加载其init方法

init方法内的主要逻辑为启动AttachArthasClassloader:

com.taobao.arthas.agent.attach.ArthasAgent#init

if (instrumentation == null) {
    instrumentation = ByteBuddyAgent.install();
}
 
// 检查 arthasHome
if (arthasHome == null || arthasHome.trim().isEmpty()) {
    // 解压出 arthasHome
    URL coreJarUrl = this.getClass().getClassLoader().getResource("arthas-bin.zip");
    if (coreJarUrl != null) {
        File tempArthasDir = createTempDir();
        ZipUtil.unpack(coreJarUrl.openStream(), tempArthasDir);
        arthasHome = tempArthasDir.getAbsolutePath();
    } else {
        throw new IllegalArgumentException("can not getResources arthas-bin.zip from classloader: "
                + this.getClass().getClassLoader());
    }
}
 
// find arthas-core.jar
File arthasCoreJarFile = new File(arthasHome, ARTHAS_CORE_JAR);
if (!arthasCoreJarFile.exists()) {
    throw new IllegalStateException("can not find arthas-core.jar under arthasHome: " + arthasHome);
}
AttachArthasClassloader arthasClassLoader = new AttachArthasClassloader(
        new URL[] { arthasCoreJarFile.toURI().toURL() });
 
/**
 * <pre>
 * ArthasBootstrap bootstrap = ArthasBootstrap.getInstance(inst);
 * </pre>
 */
Class<?> bootstrapClass = arthasClassLoader.loadClass(ARTHAS_BOOTSTRAP);
Object bootstrap = bootstrapClass.getMethod(GET_INSTANCE, Instrumentation.class, Map.class).invoke(null,
        instrumentation, configMap);
boolean isBind = (Boolean) bootstrapClass.getMethod(IS_BIND).invoke(bootstrap);
if (!isBind) {
    String errorMsg = "Arthas server port binding failed! Please check $HOME/logs/arthas/arthas.log for more details.";
    throw new RuntimeException(errorMsg);
}

解压arthas-bin.zip包后通过AttachArthasClassloader加载,可以看到对应属性中bootstrap类是com.taobao.arthas.core.server.ArthasBootstrap,方法是getInstance,跳转到core中的Bootstrap类:

对应getInstance方法调了构造方法ArthasBootstrap,到这里arthas-core模块才真正起起来:

https://github.com/alibaba/arthas/blob/arthas-all-3.5.5/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java#L350

其中最重要的流程是bind方法,启动了arthas server,关键逻辑在ShellServer的初始化:

1.2 ArthasBootstrap启动ShellServer

- 阅读剩余部分 -

生产环境前段时间neo4j图数据库总是在凌晨定时任务删除过期节点时报内存占用过高的告警,研究下怎么配置参数:

neo4j内存结构:

neo4j内存组成

OS保留:OS内存、Lucene索引缓存
页面缓存:图数据缓存、索引缓存
jvm堆:图查询、图管理、事务状态(可选)
事务状态
neo4j memory management

推荐的内存参数:

分配了3.6G jvm堆内存,2G 页面缓存,剩余2.6G空闲内存和 native memory 给 Lucene 和 Netty

一个收获:很多中间件jvm内存并不是一定需要占大头,例如某些MQ和缓存会使用 native memory 来避免jvm频繁垃圾回收带来的性能问题;以及像neo4j的Lucene引擎使用系统分页缓存

三个建议:

索引文件太大时,多剩点内存给OS
并发事务和update语句越多,分配的jvm堆内存也要越大(别超过31G)
jvm堆内存设置初始值和最大值一致,因为jvm更改堆大小时会触发full GC影响性能

syf@syf-ubuntu ~/o/neo4j-community-3.5.8 [1]> bin/neo4j-admin memrec --memory=8g
WARNING: Max 1024 open files allowed, minimum of 40000 recommended. See the Neo4j manual.
# Memory settings recommendation from neo4j-admin memrec:
#
# Assuming the system is dedicated to running Neo4j and has 8g of memory,
# we recommend a heap size of around 3600m, and a page cache of around 2g,
# and that about 2600m is left for the operating system, and the native memory
# needed by Lucene and Netty.
#
# Tip: If the indexing storage use is high, e.g. there are many indexes or most
# data indexed, then it might advantageous to leave more memory for the
# operating system.
#
# Tip: The more concurrent transactions your workload has and the more updates
# they do, the more heap memory you will need. However, don't allocate more
# than 31g of heap, since this will disable pointer compression, also known as
# "compressed oops", in the JVM and make less effective use of the heap.
#
# Tip: Setting the initial and the max heap size to the same value means the
# JVM will never need to change the heap size. Changing the heap size otherwise
# involves a full GC, which is desirable to avoid.
#
# Based on the above, the following memory settings are recommended:
dbms.memory.heap.initial_size=3600m
dbms.memory.heap.max_size=3600m
dbms.memory.pagecache.size=2g

参考:

https://neo4j.com/docs/operations-manual/3.5/performance/memory-configuration/

https://neo4j.com/docs/operations-manual/3.5/tools/neo4j-admin-memrec/

kubectl有些操作比较冗长,又经常需要使用,所以写了几个别名,进测试环境pod终端方便些,鉴权自行解决

使用例:

# 进入终端
podl qa sox
# 展示pod列表
pods qa sox
# 进入pod的某个container,如istio
podn qa istio sox-58654c84d-lnk4f
# 查看stdout,这个基本没啥用,除非有的异步线程抛出异常没打到logback指定的文件里
podlog qa sox
# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
eval /c/Users/enmonster/miniconda3/Scripts/conda.exe "shell.fish" "hook" $argv | source
# <<< conda initialize <<<


function podl --argument-names nmsp app
    kubectl -n $nmsp exec -it (kubectl -n $nmsp get pod -l app=$app -o name) -c $app -- bash
end

function pods --argument-names nmsp app
    kubectl -n $nmsp get pods | grep $app
end

function podn --argument-names nmsp container pod
    kubectl -n $nmsp exec -it $pod -c $container -- bash
end

function podlog --argument-names nmsp app
    kubectl -n $nmsp logs -c $app (kubectl -n $nmsp get pod -l app=$app -o name)
end

Java 工程中注解配合切面往往能带来事半功倍的效果,但是注解有的时候并不是太灵活,例如方法注解无法指定一些自定义的参数,很多时候还是需要到方法里面去做一些侵入性的逻辑来完成想要的操作;为了解决这个痛点,我写了个 demo 通过反射+递归的方式来给方法注解提供解析动态参数的能力,例如对于 void doSomething(Entity entity) 这个方法,可以通过 @RecordEvent(code = "TEST", keyWord = "{entity.id}", content = "content is {entity.nested.foo}") 注解中的花括号来取到入参中 entity 参数的某些值,且支持无限递归获取子属性的值,demo 代码如下:

有这么两个参数类型,其中Entity类中包含类型为Nested的属性

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Entity {
    private int id;
    private String name;
    private String otherField;
    private Nested nested;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Nested {
    private String foo;
    private int bar;
}

有一个方法,其第一个入参名为entity,可以使用“{entity.nested.foo}”这样的模板字符串,切面处理时会把模板字符串自动替换为对应入参属性的getter值,支持无限级往下查属性,查到最后的属性(本例中为foo)将其toString替换到模板。

@Override
@RecordEvent(code = "TEST", keyWord = "{entity.id}", content = "content is {entity.nested.foo}")
public void doSomething(Entity entity) {
    int a = 2*2;
    // some other logic
    log.info("done");
}
public static void main() {
    Entity entity = new Entity(1, "hello", "world", new Nested("bar", 111));
    xxxService.doSomething(entity);
}

调用时生成事件:EventPublishRequestDto(source=OPS, code=TEST, keyWord=1, content=content is bar, dateTime=2021-07-27T17:06:19.593)

具体方法是将模板参数用分隔符分割成队列,队列的第一个值是入参名,第二个值往后依次出队递归拿到文本对应的属性,以此类推直到队列为空,递归结束返回最深层的值

demo 源码如下:

- 阅读剩余部分 -