使用Docker比较Java中的线程、虚拟线程、协程

简介

  1. 通过Docker,比较:Java线程、Java虚拟线程(JDK21新特性)、Java协程(阿里JDK特性)三者的性能。

  2. 使用Docker创建一个3主3从的Redis集群。

线程vs虚拟线程vs协程

先放结果:

shell 复制代码
# 线程
[root@iZ2ze5spg4s1sexdh3etsvZ ~]# docker start -i 0537b563d672
24709 ms

# 虚拟线程
[root@iZ2ze5spg4s1sexdh3etsvZ ~]# docker start -i 12ea25f44e5f
2667 ms

# 协程
[root@iZ2ze5spg4s1sexdh3etsvZ ~]# docker start -i 4734d9b49ecc
858 ms

测试代码PingPong.java如下:

java 复制代码
import java.util.concurrent.*;

public class PingPong {
    static final ExecutorService THREAD_POOL = Executors.newCachedThreadPool();

    public static void main(String[] args) throws Exception {
        BlockingQueue<Byte> q1 = new LinkedBlockingQueue<>(), q2 = new LinkedBlockingQueue<>();
        THREAD_POOL.submit(() -> pingpong(q2, q1)); // thread A
        Future<?> f = THREAD_POOL.submit(() -> pingpong(q1, q2)); // thread B
        q1.put((byte) 1);
        System.out.println(f.get() + " ms");
    }

    private static long pingpong(BlockingQueue<Byte> in, BlockingQueue<Byte> out) throws Exception {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1_000_000; i++) out.put(in.take());
        return System.currentTimeMillis() - start;
    }
}

JDK21镜像将使用:tabatad/jdk21 Tags | Docker Hub

了解Java协程请看:阿里开源JDK中的协程Wisp - 掘金 (juejin.cn)

线程

  1. 新建dockerfile,命名为Dockerfile_pingpong
dockerfile 复制代码
FROM tabatad/jdk21
  
COPY PingPong.java /app/PingPong.java

WORKDIR /app

RUN javac PingPong.java

CMD ["java", "PingPong"]
  1. 构建镜像:docker build -t pingpong -f Dockerfile_pingpong .
  2. 创建并启动容器:docker run -it pingpong
  3. 启动已停止的容器:docker start -i 容器ID

虚拟线程

为了使用Java21中的虚拟线程,修改测试代码中线程池的创建方式为:

java 复制代码
static final ExecutorService THREAD_POOL = Executors.newVirtualThreadPerTaskExecutor();

并修改类名为PingPong_VirtualThread,文件名:PingPong_VirtualThread.java

  1. 新建dockerfile,命名为Dockerfile_pingpong_vt
bash 复制代码
FROM tabatad/jdk21
  
COPY PingPong_VirtualThread.java /app/PingPong_VirtualThread.java

WORKDIR /app

RUN javac PingPong_VirtualThread.java

CMD ["java", "PingPong_VirtualThread"]
  1. 构建镜像:docker build -t pingpong-vt -f Dockerfile_pingpong_vt .

  2. 创建并启动容器:docker run -it pingpong_vt

  3. 启动已停止的容器:docker start -i 容器ID

协程

制作dragonwell基础镜像

  1. 新建dockerfile,命名为Dockerfile_dragonwell
dockerfile 复制代码
FROM ubuntu:latest

LABEL maintainer="XiaoXing <jihoukang@gmail.com>"

ENV JAVA_HOME /opt/dragonwell
ENV PATH $PATH:$JAVA_HOME/bin

COPY Alibaba_Dragonwell_Extended_8.16.17_x64_linux.tar.gz /tmp/

RUN mkdir -p $JAVA_HOME && tar -xzvf /tmp/Alibaba_Dragonwell_Extended_8.16.17_x64_linux.tar.gz -C $JAVA_HOME --strip-components=1 && rm /tmp/Alibaba_Dragonwell_Extended_8.16.17_x64_linux.tar.gz

WORKDIR $JAVA_HOME
  1. 构建镜像:docker build -t dragonwell-env:8.16.17 -f Dockerfile_dragonwell .

制作Java应用镜像

  1. 新建dockerfile,命名为Dockerfile_pingpong_dragonwell
bash 复制代码
FROM dragonwell-env:8.16.17
  
COPY PingPong.java /app/PingPong.java

WORKDIR /app

RUN javac PingPong.java

CMD ["java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseWisp2", "-XX:ActiveProcessorCount=1", "PingPong"]
  1. 构建镜像:docker build -t pingpong_dragonwell -f Dockerfile_pingpong_dragonwell .
  2. 创建并启动容器:docker run -it pingpong_dragonwell
  3. 启动已停止的容器:docker start -i 容器ID

比较

shell 复制代码
[root@iZ2ze5spg4s1sexdh3etsvZ ~]# docker ps -a --format "table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}"
CONTAINER ID   IMAGE                 STATUS                        NAMES
4734d9b49ecc   pingpong_dragonwell   Exited (130) 11 days ago      adoring_franklin
0537b563d672   pingpong              Exited (130) 46 seconds ago   nostalgic_buck
12ea25f44e5f   pingpong_vt           Exited (0) 2 weeks ago        vibrant_hopper

# 线程
[root@iZ2ze5spg4s1sexdh3etsvZ ~]# docker start -i 0537b563d672
24709 ms

# 虚拟线程
[root@iZ2ze5spg4s1sexdh3etsvZ ~]# docker start -i 12ea25f44e5f
2667 ms

# 协程
[root@iZ2ze5spg4s1sexdh3etsvZ ~]# docker start -i 4734d9b49ecc
858 ms

3主3从Redis集群

拉取Redis镜像:docker pull redis:6.0.8

启动Redis

shell 复制代码
# 启动第1台节点
docker run -d --name redis-node-1 --net host --privileged=true -v /app/redis-cluster/share/redis-node-1:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6381

# 启动第2台节点
docker run -d --name redis-node-2 --net host --privileged=true -v /app/redis-cluster/share/redis-node-2:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6382

# 启动第3台节点
docker run -d --name redis-node-3 --net host --privileged=true -v /app/redis-cluster/share/redis-node-3:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6383

# 启动第4台节点
docker run -d --name redis-node-4 --net host --privileged=true -v /app/redis-cluster/share/redis-node-4:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6384

# 启动第5台节点
docker run -d --name redis-node-5 --net host --privileged=true -v /app/redis-cluster/share/redis-node-5:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6385

# 启动第6台节点
docker run -d --name redis-node-6 --net host --privileged=true -v /app/redis-cluster/share/redis-node-6:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6386
命令 描述
--net host 使用宿主机的IP和端口,默认
--privileged=true 获取宿主机root用户权限
-v /app/redis-cluster/share/redis-node-1:/data 容器卷,宿主机地址:docker内部地址
--cluster-enabled yes 开启redis集群
--appendonly yes 开启redis持久化
--port 6381 配置redis端口号

配置主从

  1. 进入任意一个Redis节点:docker exec -it redis-node-1 /bin/bash
  2. 配置主从关系
shell 复制代码
# 宿主机IP:端口
redis-cli --cluster create 172.20.137.115:6381 172.20.137.115:6382 172.20.137.115:6383 172.20.137.115:6384 172.20.137.115:6385 172.20.137.115:6386 --cluster-replicas 1

Redis自动分配结果完成后,需要输入 Yes 确认配置信息:

shell 复制代码
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.20.137.115:6385 to 172.20.137.115:6381
Adding replica 172.20.137.115:6386 to 172.20.137.115:6382
Adding replica 172.20.137.115:6384 to 172.20.137.115:6383
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: a79860a765ab31cc845e99dd32d4806a24f49ae2 172.20.137.115:6381
   slots:[0-5460] (5461 slots) master
M: 5af4fa181e3a24aba2c356270b5f306f3370244e 172.20.137.115:6382
   slots:[5461-10922] (5462 slots) master
M: 6c20243b257ccc959922411653bb3d58c91058de 172.20.137.115:6383
   slots:[10923-16383] (5461 slots) master
S: ad0ed01f34e05e75f184e3ef1cede5c154a43332 172.20.137.115:6384
   replicates 6c20243b257ccc959922411653bb3d58c91058de
S: 9bb151c192b1939fcfe567aabb7b69f81cb9e2b9 172.20.137.115:6385
   replicates a79860a765ab31cc845e99dd32d4806a24f49ae2
S: 764d3fc2f41b09975b3ecdef9d2580b3308c8d49 172.20.137.115:6386
   replicates 5af4fa181e3a24aba2c356270b5f306f3370244e
Can I set the above configuration? (type 'yes' to accept): yes

输入Yes确认后,redis会向其他节点发送信息加入集群,并分配哈希槽

shell 复制代码
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
.....
>>> Performing Cluster Check (using node 172.20.137.115:6381)
M: a79860a765ab31cc845e99dd32d4806a24f49ae2 172.20.137.115:6381
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 5af4fa181e3a24aba2c356270b5f306f3370244e 172.20.137.115:6382
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: ad0ed01f34e05e75f184e3ef1cede5c154a43332 172.20.137.115:6384
   slots: (0 slots) slave
   replicates 6c20243b257ccc959922411653bb3d58c91058de
S: 9bb151c192b1939fcfe567aabb7b69f81cb9e2b9 172.20.137.115:6385
   slots: (0 slots) slave
   replicates a79860a765ab31cc845e99dd32d4806a24f49ae2
S: 764d3fc2f41b09975b3ecdef9d2580b3308c8d49 172.20.137.115:6386
   slots: (0 slots) slave
   replicates 5af4fa181e3a24aba2c356270b5f306f3370244e
M: 6c20243b257ccc959922411653bb3d58c91058de 172.20.137.115:6383
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

查看集群状态

shell 复制代码
# 进入一个Redis实例
docker exec -it redis-node-1 /bin/bash

redis-cli -p 6381
shell 复制代码
# 查询集群信息
127.0.0.1:6381> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:339
cluster_stats_messages_pong_sent:339
cluster_stats_messages_sent:678
cluster_stats_messages_ping_received:334
cluster_stats_messages_pong_received:339
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:678
shell 复制代码
# 查看节点信息
127.0.0.1:6381> cluster nodes
5af4fa181e3a24aba2c356270b5f306f3370244e 172.20.137.115:6382@16382 master - 0 1698854412000 2 connected 5461-10922
ad0ed01f34e05e75f184e3ef1cede5c154a43332 172.20.137.115:6384@16384 slave 6c20243b257ccc959922411653bb3d58c91058de 0 1698854415931 3 connected
a79860a765ab31cc845e99dd32d4806a24f49ae2 172.20.137.115:6381@16381 myself,master - 0 1698854413000 1 connected 0-5460
9bb151c192b1939fcfe567aabb7b69f81cb9e2b9 172.20.137.115:6385@16385 slave a79860a765ab31cc845e99dd32d4806a24f49ae2 0 1698854415000 1 connected
764d3fc2f41b09975b3ecdef9d2580b3308c8d49 172.20.137.115:6386@16386 slave 5af4fa181e3a24aba2c356270b5f306f3370244e 0 1698854415000 2 connected
6c20243b257ccc959922411653bb3d58c91058de 172.20.137.115:6383@16383 master - 0 1698854415000 3 connected 10923-16383

链接方式

  • 单机连接 redis-cli -p 6381:当我们连接master1(6381),进行数据写入时,如果key的hash值不在分配给master的槽位就会报错,写不进去。

  • 集群连接 redis-cli -p 6381 -c:使用集群进行数据写入时,当key的hash值不在当前master的槽位,则会切换到所在槽位的master实例。

shell 复制代码
# 检查 Redis 集群节点的状态
redis-cli --cluster check 172.20.137.115:6381
shell 复制代码
172.20.137.115:6381 (a79860a7...) -> 0 keys | 5461 slots | 1 slaves.
172.20.137.115:6382 (5af4fa18...) -> 0 keys | 5462 slots | 1 slaves.
172.20.137.115:6383 (6c20243b...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 172.20.137.115:6381)
M: a79860a765ab31cc845e99dd32d4806a24f49ae2 172.20.137.115:6381
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 5af4fa181e3a24aba2c356270b5f306f3370244e 172.20.137.115:6382
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: ad0ed01f34e05e75f184e3ef1cede5c154a43332 172.20.137.115:6384
   slots: (0 slots) slave
   replicates 6c20243b257ccc959922411653bb3d58c91058de
S: 9bb151c192b1939fcfe567aabb7b69f81cb9e2b9 172.20.137.115:6385
   slots: (0 slots) slave
   replicates a79860a765ab31cc845e99dd32d4806a24f49ae2
S: 764d3fc2f41b09975b3ecdef9d2580b3308c8d49 172.20.137.115:6386
   slots: (0 slots) slave
   replicates 5af4fa181e3a24aba2c356270b5f306f3370244e
M: 6c20243b257ccc959922411653bb3d58c91058de 172.20.137.115:6383
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

其他

3分钟安装Docker:Install Docker Engine on CentOS | Docker Docs

shell 复制代码
# 格式化ps命令输出
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"

docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}"
相关推荐
全能全知者1 分钟前
docker快速安装与配置mongoDB
mongodb·docker·容器
齐 飞3 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
九圣残炎12 分钟前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge14 分钟前
Netty篇(入门编程)
java·linux·服务器
LunarCod20 分钟前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
Re.不晚41 分钟前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐1 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven
码农派大星。1 小时前
Spring Boot 配置文件
java·spring boot·后端
顾北川_野1 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
江深竹静,一苇以航1 小时前
springboot3项目整合Mybatis-plus启动项目报错:Invalid bean definition with name ‘xxxMapper‘
java·spring boot