Lombok @With 的纯弊端及如何避免

由于是第一篇写关于 Lombok 的日志,所以有些不情愿去开门见山直接触及 @With, 而要先提一提本人对 Lombok 的接触过程。

两三年之前写 Java 代码一直都是全手工打造。一个数据类,所有必须的 setter/getter, toString, hashcode() 等全体现在源代码中,当然是在 IDE 中自动生成的。听说过 Lombok,但觉得它用了会影响到对源代码的阅读,因为造成代码的行为与源代码所展示的不一致,还可能依赖于特定的 IDE 或构建工具插件,所以一直未真正应用。

然而现代语言一直在避免不断书写象 JavaBean 里那一大片样本代码,同时解决试图提高覆盖率写出毫无意义单元测试的烦恼。比如 Scala 发展出了 case class, Kotlin 的 data class, Python 的 @dataclass,还有 JDK 14 引入的及至 JDK 16 转正的  record, 都是为了自动生成 Java 类的 setter/getter/toSring/hashcode/equals 等方法。 所以源代码中看不到实际可调用方法不该再是问题,况且在 JDK 5 加入的 enum 类型本质上也是在源代码的背后生成了一系列的方法和类型声明的。 阅读全文 >>

postgres in (?,?) 和 =any(?) 用法/性能对比

刚刚回顾了一下 JDBC 操作 SQL Server 时如何传入列表参数,即如何给 in (?) 条件直接传入一个列表参数,然而本质上是不支持,最终不得不展开为 in (?, ?,...?) 针对每个元素单独设置参数,不定长的参数对于重用已编译 PreparedStatement 语句的帮助不大。

那么 JDBC 操作 PostgreSQL 是何种状态呢?展开为多个参数当然是有效的。继续尝试 Spring 提供的 NamedParameterJdbcTemplate 的操作方式

String query = "select * from users where id in (:ids)";
Map<String, Object> parameters = new HashMap<>();
parameters.put("ids", IntStream.rangeClosed(1, 5).boxed().collect(toList()));
List<Map<String, Object>> maps = namedParameterJdbcTemplate.queryForList(query, parameters);

执行后查看到实际执行的语句是

select * from users where id in (?, ?, ?, ?, ?)

阅读全文 >>

Spring 5 响应式编程研究

Spring 5.0 发布之时(2017-09-28) WebFlux 是它的一大亮点,即响应式 Web 编程。因为同一时代的 RxJava 2 和 Akka Actor 具备一定的流行度,Spring 5 也来赶这一趟时髦。于是多线程编程大致两种模式

  1. CompletableFuture, runAsync, supplyAsync, whenComplete...
  2. Obervable, observeOn, subscribe, subscribeOn...

以及 PlayFramework 的 Action 方法无论返回 Result 还是 CompletableStage<Result>, 内部都是异步的模式。

Akka Actor 比 CompletableFuture, RxJava,以及本文将要讨论的 Reactor 更高级的是 Akka System 可以分布式部署,Actor 分布在不同的进程,主机上。 

那时候业界已行成了一个 Reactive Stream 规范 org.reactivestreams(Publisher, Subscriber, Subscription, Processor), JDK 9 也奈不住寂寞,无法对 Reactive Stream 置若罔闻,在 2017-09-21 发布时加入了 java.util.concurrent.flow 包(Publisher, Subscriber, Processor, Subscription) 作为自己的 Reactive Stream 规范。

然而随着云计算的普及,基于消息系统解耦合的任务分解让代码变得更清晰,编码中甚至不用考虑多线程的行为,部署方式能解决任务执行的效率。

阅读全文 >>

理解 Java 线程池 ThreadPoolExecutor

使用 JDK 5 的线程池实现有近 20 年的时间了,快速创建一个线程池经常是调用 Executors 中的工厂方法。但是涉及过更精细的线程池管理控制时不得不用 ThreadPoolExecutor 的构造方法,这也就是为什么有些公司不建议用 Executors 的工厂方法创建线程池,而应该直接创建 ThreadPoolExecutor 或 ForkJoinPool 实例。 

例如代码

ExecutorService threadPool = Executors.newFixedThreadPool(10);

实际上调用的是

new ThreadPoolExecutor(nThreads, nThreads,
                                                0L, TimeUnit.MILLISECONDS,
                                                new LinkedBlockingQueue<Runnable>());

前两个参数 corePoolSize 和 maximumPoolSize 是一样的; OL, TimeUnit.MILLISECONDS 表示线程创建后只要线程池还在就是永生的; workQueue 是一个大小为 Integer.MAX_VALUE 的队列, 几乎可以无限提交任务,耗尽内存

不建议用 Executors 的工厂方法的原因大致有二: 阅读全文 >>

体验 Python FastAPI 的并发能力及线, 进程模型

本文进行实际测试 FastAPI 的并发能力,即同时能处理多少个请求,另外还能接收多少请求放在等待队列当中; 并找到如何改变默认并发数; 以及它是如何运用线程或进程来处理请求。我们可以此与 Flask 进行对比,参考 Python Flask 框架的并发能力及线,进程模型,是否真如传说中所说的 FastAPI 性能比 Flask 强, FastAPI 是否对得起它那道闪电的 Logo。

本文使用 JMeter 进行测试,测试机器为 MacBook Pro, CPU 6 核超线程,内存 16 Gb。

对于每一种类型 Web 服务基本的测试是每秒发送 2 个请求,连续发送 1000 个,500 秒发送完所有请求,程序中 API 方法接受到请求后 sleep 800 秒,保证在全部 1000 个请求送出之前一直占着连接,并有充足的时间对连接进行分析。在测试极端并发数时,由于在 Mac OS X 尽管设置了 ulimit 最多也只能创建 4000 多一点线程,所以在模拟更多用户数时,JMeter 在远程 Linux(Docker 或虚拟机) 上运行测试用例。

请求的 URL 是 http://localhost:8080/?id=${count}, 带一个自增序列用以识别不同的请求, JMeter 的 Thread Group 配置为 Number of Threads (users): 1000, Ramp-up period (seconds): 500 阅读全文 >>

远程方式执行 JMeter 测试

JMeter 是一个极好的测试 Web API 及压力测试的工具,另一个的话就是 Python 版的 LOCUST(它也能远程运行测试)。JMeter 的测试可以在本地模拟并发用户,那么为什么要远程执行 JMeter 测试呢?因为一台机器能模拟的并发用户数受限,一个用户就是对应着一个 Java 线程。比如我在 MacBook Pro(内存 16Gb) 上无论如何调整 ulimit -n, ulimit -u, 或用 JAVA_TOOL_OPTIONS, HEAP, JVM_ARGS 设置 -Xmx, 调大到 10 G, 或用 -Xss 调小栈大小,都无法让 JMeter 模拟的用户数达到 5000。

文后有本人亲自测试 Java/Python 在 Mac OS X 和 Linux 下可创建多少个线程

如果能够远程运行 JMeter 的测试就能突破单机上的线程限制了,比如 Mac OS X 不行,找个 Linux 远程机器(可以是虚拟机)来执行,一台机器不够,找多个。想要模拟 15000 个并发用户,测试可分配到 5 台机器上执行,每个节点跑 3000 个用户并发就行,有点操控肉机的感觉。 阅读全文 >>

升级到 Spring Boot 3 后 javax.inject.Named 不可用

为了紧跟 Spring 6 的步伐,Spring Boot 在 2022 年 11 月 24 日释放了 3.0.0. 当前版本是 3.0.1(2022-12-23)。Spring 6 要求用 JDK 17+, Spring Boot 3 自然也要上 JDK 17+ 才能使用,对于一直死死抱住 JDK 8 不放的要升级到 Spring Boot 3 就是个比较大的挑战。

Spring Boot 到底带来了什么显著的特性呢?

  1. 依赖于 Spring 6, 最低 Java 17, 兼容 Java 19
  2. 支持生成 GraalVM 本地映像,取代实验性的 Spring Native 项目
  3. 最低 Java EE 9 和支持 Jakarta EE 10
  4. 依赖从 Java EE 迁移到 Jakarta EE API
  5. 升级到 Tomcat 10

从 Spring Boot 2.x 升级到 Spring Boot 3 的指南请阅官方的文档 Spring Boot 3.0 Migration Guide。Spring Boot 1 的项目还得老老实实的先升级到 Spring Boot 2,如果是早期的 Spring Boot 2,第一步是升级到 Spring Boot 2.7.x, 一步步来,免得步子大了扯到X。再到是把 JDK 换成  17 或更新的版本,编译,运行,有问题就改代码。 阅读全文 >>

Spring Boot Web 输出 Tomcat 的访问日志到控制台

当我们直接使用 Tomcat  时,访问日志的配置在 $TOMCAT_HOME/conf/server.xml 中

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
              prefix="localhost_access_log" suffix=".txt"
              pattern="%h %l %u %t &quot;%r&quot; %s %b" />

产生的日志文件在 $TOMCAT_HOME/logs 目录中,生成以日期戳进行区分的滚动日志文件,如 localhost_access_log.2022-10-25.txt 等

而在我们使用  Spring Boot Web 时, 默认的嵌入式 Web 应用服务器是 Tomcat,我们可以在 Spring 属性文件中配置是否启用 Tomcat 访问日志(默认不启用)。属性文件中的配置针对的是如何输出访问日志到文件,而我们有时候需要输出访问日志到控制台而不非文件,比如 AWS 上的 ECS 应用,控制台的输出可直接发送到 CloudWatch,这样便于分析日志。

在 Spring Web 中可以用 Interceptor 或 Filter 来记录访问日志,但在请求出错时无法准确显示出响应时间和状态码,毕竟 Tomcat 的访问日志在代码的外层,进出 Controller 方法的输入输出信息也就掌握的更清楚。

本文的任务是探索输出 Spring Boot Web 嵌入式 Tomcat 的访问日志到控制台。有两种方式 阅读全文 >>

SpringBoot 启用 GZIP 对响应进行压缩

SpringBoot Web 应用默认是不启用响应数据的压缩,对大的文本类型的响应数据进行压缩是十分必要的,如 JSON, XML 等应用数据,甚至是 JS, CSS 等。

早先的 Web 应用基本是要配置一个叫做 GzipFilter 之类的东西,然后判断请求的 Accept-Encoding 是否含有 gzip, 再对需要的 Content-Type 响应类型的数据进行压缩。

在使用了 SpringBoot 之后,在碰到有压缩响应的需求的时候,第一件事情应该要想到是否能通过在 application.properties(或 application.yml) 配置就行。于是查阅 SpringBoot 2.7.x 的帮助文档 Spring Boot Reference Document, 搜索关键字 compression,翻几页就能找到 17.3.6. Enable HTTP Response Compression, 介绍了三个配置项 阅读全文 >>

Spring Boot 如何选择 Cache 实现的

写作此篇是作为对 Spring 使用 Cache 解析及使用不同类型的 Cache 一文的补充,该文中提到了自定 CacheManager 及配置 spring.cache.type 来选择自己的 Cache 实例,但对 Spring 是如何确定具体 Cache 实现未作展开。本文将介绍选择 Cache 实现的几种方式

  1. 默认选择 Cache
  2. 声明 Spring Bean Cache
  3. 声明 Spring Bean CacheManager
  4. 通过 spring.cache.type 属性选择
  5. 引入相应的 Cache 实现依赖

阅读全文 >>