Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

这个话题应该首先明确,这不是类似于 open tracing 的应用间调用路径,而是应用内部,举个例子,我有一个底层方法,比如DAO层的一个方法,有很多调用方,那么有什么方法,第一种是最简单,但是比较麻烦,我去每个调用方那打个日志,然后分析下日志,这个办法肯定是没错的,只是如果我的调用方很多,可能需要加日志的地方就很多了,会比较麻烦
第二种是我可以在这个被调用的DAO层方法里加个日志,但是要怎么获取调用方呢,这是我们可以用代码堆栈trace来实现,这个思路可以从异常的角度出发,我们在出现异常的时候,如何定位异常的代码,就是通过异常堆栈,那么堆栈是不是只有在抛异常的时候才有呢,答案是否定的,了解一点java虚拟机的应该知道我们在内存中有一个区域放的就是我们的代码调用栈,用来保存我们目前程序的调用层次逻辑以及数据,之前在php中也讲到过这个,那时候也是帮助理清调用逻辑和调用来源,
在java中如何使用这个功能呢,

1
2
3
4
5
6
7
8
9
10
11
12
public class StackTrace {
public static void main(String[] args) {
method1();
}
public static void method1() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
System.out.println(stackTraceElement);
}
}

}

这个方法其实也比较简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public StackTraceElement[] getStackTrace() {
if (this != Thread.currentThread()) {
// check for getStackTrace permission
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(
SecurityConstants.GET_STACK_TRACE_PERMISSION);
}
// optimization so we do not call into the vm for threads that
// have not yet started or have terminated
if (!isAlive()) {
return EMPTY_STACK_TRACE;
}
StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this});
StackTraceElement[] stackTrace = stackTraceArray[0];
// a thread that was alive during the previous isAlive call may have
// since terminated, therefore not having a stacktrace.
if (stackTrace == null) {
stackTrace = EMPTY_STACK_TRACE;
}
return stackTrace;
} else {
// Don't need JVM help for current thread
return (new Exception()).getStackTrace();
}
}

这里面返回的 StackTraceElement 的内容

1
2
3
4
5
6
public final class StackTraceElement implements java.io.Serializable {
// Normally initialized by VM (public constructor added in 1.5)
private String declaringClass;
private String methodName;
private String fileName;
private int lineNumber;

分别是定义的类,方法名,文件名,以及代码行数

我们就能看到这样的结果

1
2
3
java.lang.Thread.getStackTrace(Thread.java:1564)
StackTrace.StackTrace.method1(StackTrace.java:14)
StackTrace.StackTrace.main(StackTrace.java:11)

因为是栈,所以就是最上面的是最近的方法,而第二行其实是我的目标方法,就是前面描述的逻辑其实应该找到第3个元素,因为第1个是调用 getStackTrace 方法,
第2个是目标方法,第3个才是我们想要找的调用方方法,以及所在的行数

上次讲了 git stashgit commit 的小技巧,这次来讲下另一个命令,git cherry-pick,我怀疑这个命令应该叫 choose-pick 才比较好
这里有个使用场景,比如我们有分支A跟B,分支B想用一下分支A一次变更的代码,正好不用自己再开发了
比如我在主分支里提交了一个分支B也想要使用的代码或者内容

然后在后面再提交一次

切到branchB,做一次提交

我此时就想把主分支之前那个提交内容给应用到我这个branchB,那就可以用cherry-pick
找到master分支上那个提交 commit id
git cherry-pick {commit id}

可以看到这个commit已经被我挑过来应用上了

然后再深度使用还有比如是否要产生新提交,还是只在暂存区,以及如果发生冲突了是继续还是停止等等

上次说的git stash我觉得是个非常有用的功能,简直是个大杀器,除非不存在需要切换分支的场景,否则在频繁切换的时候,git stash 可以让我们的commit更清晰,不至于写到一半就提交,频繁切换,甚至连commit信息都写不好
这次的小技巧是属于一个延伸知识,我们知道每次commit都会提交我们的变更,然后我们可以通过git diff .查看当前仓库产生的未提交的变更,如果我想看某一次commit的变更呢,这个功能通过 git 的各种 gui 工具可以很方便的查看,如果通过命令其实也很方便,我们每一次的commit 都会产生一个 commit id , 看过我上一篇的可以联想到,是否 git commit 也有这种 show 命令,答案是肯定的,
首先我们可以通过 git log 来查看我们的提交记录

这里我们可以看到我们的提交 commit id
然后通过 git show xxxxx 来查看我们这次 commit 修改的内容, xxxxx 表示我们的 commit id

再延伸下,如果想看某个文件的变更呢,也是可以的,就是在 git show xxxxx 后面跟上文件名
比如
git show xxxxx 1.txt
就能看到这一次 commit1.txt 这个文件的更改,都是比较简单入门的 git 命令的小延伸,只是觉得对我有点帮助就分享下

我们日常开发包括我自用的小工具也在用git管理
在使用git的过程中经常有个场景是我在A分支上开发了一部分,临时需要切换到B分支,又不想先把这部分代码提交,因为还没开发完,
这是用git stash命令就能很好的解决这个问题,但是这里在使用git stash的时候我认为有两个阶段
第一阶段就是简单使用



比如我有这个进行中的变更,在有未提交的代码的时候可以先用

1
git add 1.txt

添加到暂存区,但不提交,然后使用

1
git stash

保存工作进度,git stash之后再用 git status 就看不到刚才的更改了

然后就是弹出刚才stash的变更了
可以使用 git stash pop 就可以把刚才藏起来的临时变更就弹出来了

这里其实在真正使用时我们如果是深度使用就可能会出现四个问题或者是诉求

问题一

我不止stash了一次变更,怎么找到stash的多少次呢,因为直接用 pop 就是把栈顶的那次隐藏的变更弹出来了
这个问题就可以通过 git stash list 来解决

问题二

如果使用的pop我们这个stash相当于栈顶弹出,已经pop出来的就没有了,如果我只是想看一下改了哪些,当然我们可以先pop出来,在stash回去,
但更好的办法就是用git stash apply,这样是不会清栈的

问题三

如何指定弹出,比如我在list里能看到多次stash,那么我想弹出其中某一次
可以用
git stash pop stash@{1} 注意这个 stash@{1} 是指stash list 的第二个,因为是从0开始的
弹出倒数第二次stash

问题四

我想知道我这个stash里内容是啥,在不用pop和apply的情况下有没有办法
也是有的
可以使用 git stash show 1 或者 git stash show stash@{1} 来查看这次变更了哪些文件
甚至我们可以用 git stash show 1 -p 来查看具体变更的内容

之前在使用headscale的自建derper中转的时候,因为使用了acme管理的证书,虽然acme会自动续期,但是由于证书要做转换,没办法很方便的自动更新derper中映射的证书,因为最近在尝试迁移服务器,就在寻找是否有新的方法,正好就结合前面使用的caddy,只需要做好域名解析,caddy 就会自动加上https,那么在docker层面就可以不用额外增加证书啥的,各干各的事
docker还是一样的方式,差别就在于不用映射证书目录了

1
docker run --restart always   --name derper -p 12345:12345 -p 3478:3478/udp -v /run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock  -e DERP_ADDR=:12345   -e DERP_DOMAIN=derper.domain.com   -e DERP_VERIFY_CLIENTS=true   -d dockerproxy.cn/yangchuansheng/derper:latest

derper.domain.com 需要换成自己的域名,端口也可以自己按需,只是还要注意下 /run/tailscale/tailscaled.sock 这个映射是为了让derper能够识别到这是同一个headscale下的客户端链接,防止被滥用,

1
curl -fsSL https://tailscale.com/install.sh | sh

本机通过这个安装下,然后再登录headscale

1
tailscale up --login-server=http://headscaleip:headscaleport --accept-routes=true --accept-dns=false

然后找到进程对应的sock文件,把它映射进去
接下去就是做反向代理,第一步先把域名解析做好,不然caddy没法做证书的,感觉caddy真的是懒人福音

1
2
3
4
derper.domain.com {
# 反向代理的地址
reverse_proxy 127.0.0.1:12345
}

就这几行,是不是超方便,这样就省去了隔段时间要去转换下证书,重启derper的麻烦了,虽然也可以自动化,但是作为一个shell不那么熟练的,又怕权限控制不好,重要目录被删掉的,还是这样比较省力

0%