惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

aimingoo的专栏
aimingoo的专栏
B
Blog RSS Feed
Recent Announcements
Recent Announcements
Vercel News
Vercel News
M
MIT News - Artificial intelligence
阮一峰的网络日志
阮一峰的网络日志
L
LangChain Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Security Blog
Microsoft Security Blog
H
Help Net Security
T
The Blog of Author Tim Ferriss
Y
Y Combinator Blog
G
Google Developers Blog
罗磊的独立博客
爱范儿
爱范儿
宝玉的分享
宝玉的分享
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园_首页
S
SegmentFault 最新的问题
WordPress大学
WordPress大学
月光博客
月光博客
人人都是产品经理
人人都是产品经理
Apple Machine Learning Research
Apple Machine Learning Research

Java

开源:用刚发布的 Spring AI 2.0.1,撸了一个全功能 Agent - V2EX 纯开源 Nexus 替代方案 kkRepo 1.0.0 正式发布,新增 R/CRAN、Go Hosted 和多套 UI 主题 - V2EX 不想写代码,但想要集成一个登录页面? Sa-Token-Quick-Login 帮你实现! - V2EX 善于学习的 Java 后端朋友们,想请教下你是如何榨干自己的工作项目经验,把它沉淀下来慢慢积攒成自己的能力的? - V2EX Sa-Token v1.46.0 发布 🚀,来看看有没有令你心动的功能! - V2EX 可以下线 Nexus 了:纯开源制品仓库 kkRepo v0.9.0 发布,新增 Alpine、Hugging Face Models 与全局搜索 - V2EX Java 转 native 究竟行不行?无需精通 graalvm native-image 配置就能打包 native exe 方法分享。 - V2EX fastjson 居然停止维护了 - V2EX happens-before 详解 华为的毕昇 JDK 8 AppCDS 特性稳定吗? 我把 Java 开发的 kkRepo 支持 AOT 编译, 1s 极速启动,空闲内存小于 200MB fastjson1.x 最新版本疑似又出 RCE 漏洞了 讲讲我怎么获得 kkRepo 的首个企业用户,成功替换掉 Nexus,每年节省 $1,620 PRO 订阅费用 Sonatype 正引入 Maven Central 仓库发布使用情况可见性和针对高流量发布活动的限制 让 Java 再次伟大,没有人比我更懂得如何打包!(分享一个四两拨千斤的多 jar 打包 exe 方式) 使用 kkRepo 搭建 Maven 私服 Nexus 的平替 neuxs-plus 开源涉嫌商标侵权,被迫改名 kkRepo 我在 IDEA tab 里养了一只猫 Java 确实是内存高效的 用 AI 把 visualvm 的部分功能界面换了个马甲 javaer 现在出去面试 问哪些内容 sa-token + spring cloud gateway 过滤器顺序问题 关于在 wsl 中使用 idea 进行 Java 开发的问题 求助, Fortify 漏扫问题解决 微信 ClawBot 和企微 AIBot 的 Java SDK 老业务信创重构选型大家有啥建议 ai 编程的情况在你们使用什么 IDE 下面这段 Java 多态的代码,如何才能改为 Go、 Python 、JS、C++版本? JDK 26 发布,非 lts 版本 DoytoQuery vs SpringDataJPA
Java Crac 几乎正式可用 - V2EX
pigeon2049 · 2026-08-24 · via Java

宿主机负责正式运行 Java 服务最终仍由 systemd 管理,监听固定的 48080 ,不改变现有 Nginx 配置。

Docker 只负责制作 CRaC 快照 新 JAR 在容器内冷启动、完成健康检查和预热,然后创建 checkpoint 。容器内部仍使用 48080 ,宿主机映射随机端口,所以不会和线上服务冲突。

容器和宿主机使用相同路径 统一挂载: /opt/app-server/app-server.jar /opt/app-server/zulu25-crac /opt/app-server/crac/checkpoint 避免恢复时出现 JDK 、JAR 或快照路径不一致。

外部服务使用统一可达地址 MySQL 、Redis 、TDengine 等从 127.0.0.1 改成 172.17.0.1 ,保证容器制作快照和宿主机恢复后都能连接。

快照成功后再切换 Docker checkpoint 成功之前,旧服务继续运行。成功后才停止旧进程、替换 JAR 和快照,再由 systemd 从 checkpoint 快速恢复。

实测启动时间从 2 分钟压到了三秒 需要注意的是 1.crac 特性当前仅 linux 端可用 2.集成 crac,需要告诉它关闭连接的顺序,重启连接的顺序


import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.ds.ItemDataSource;
import org.crac.Context;
import org.crac.Core;
import org.crac.Resource;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.ConfigurableApplicationContext;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;

/**
 * Coordinates external clients that are not managed by Spring's CRaC lifecycle support.
 *
 * <p>This resource is registered before the Spring application starts. Spring's resource
 * is therefore called first before a checkpoint, pausing web, scheduling and messaging
 * lifecycle beans. This resource then closes database and Redis connections. On restore,
 * the connections are recreated before Spring restarts its lifecycle beans.</p>
 */
public final class CracApplicationContextResource implements Resource {

    private static volatile CracApplicationContextResource instance;

    private final List<RestartableRedissonClient> checkpointRedissonClients = new ArrayList<>();

    private final List<DruidDataSource> checkpointDataSources = new ArrayList<>();

    private volatile ConfigurableApplicationContext applicationContext;

    private CracApplicationContextResource() {
    }

    public static synchronized CracApplicationContextResource register() {
        if (instance != null) {
            throw new IllegalStateException("CRaC application resource is already registered");
        }
        CracApplicationContextResource resource = new CracApplicationContextResource();
        Core.getGlobalContext().register(resource);
        instance = resource;
        return resource;
    }

    public void attach(ConfigurableApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public synchronized void beforeCheckpoint(Context<? extends Resource> context) {
        ConfigurableApplicationContext currentContext = this.applicationContext;
        if (currentContext == null) {
            throw new IllegalStateException("Spring application context is not attached");
        }

        System.out.println("[CRaC] Suspending external clients before checkpoint");
        this.checkpointRedissonClients.clear();
        Set<RedissonClient> redissonClients = Collections.newSetFromMap(new IdentityHashMap<>());
        redissonClients.addAll(currentContext.getBeansOfType(RedissonClient.class).values());
        for (RedissonClient redissonClient : redissonClients) {
            RestartableRedissonClient restartable = new RestartableRedissonClient(redissonClient);
            this.checkpointRedissonClients.add(restartable);
        }
        for (RestartableRedissonClient redissonClient : this.checkpointRedissonClients) {
            redissonClient.suspend();
        }

        this.checkpointDataSources.clear();
        this.checkpointDataSources.addAll(findDruidDataSources(currentContext));
        for (DruidDataSource dataSource : this.checkpointDataSources) {
            dataSource.close();
        }
        System.out.println("[CRaC] External clients suspended");
    }

    @Override
    public synchronized void afterRestore(Context<? extends Resource> context) throws SQLException {
        System.out.println("[CRaC] Resuming external clients after restore");
        for (DruidDataSource dataSource : this.checkpointDataSources) {
            dataSource.restart();
            dataSource.init();
        }
        for (RestartableRedissonClient redissonClient : this.checkpointRedissonClients) {
            redissonClient.resume();
        }
        System.out.println("[CRaC] External clients resumed");
    }

    private static Set<DruidDataSource> findDruidDataSources(ConfigurableApplicationContext context) {
        Set<DruidDataSource> result = Collections.newSetFromMap(new IdentityHashMap<>());
        for (DynamicRoutingDataSource routingDataSource
                : context.getBeansOfType(DynamicRoutingDataSource.class).values()) {
            for (DataSource dataSource : routingDataSource.getDataSources().values()) {
                DataSource candidate = dataSource;
                if (candidate instanceof ItemDataSource itemDataSource) {
                    candidate = itemDataSource.getRealDataSource();
                }
                if (candidate instanceof DruidDataSource druidDataSource) {
                    result.add(druidDataSource);
                }
            }
        }
        return result;
    }

    private static final class RestartableRedissonClient {

        private final RedissonClient client;

        private final Config config;

        private RestartableRedissonClient(RedissonClient redissonClient) {
            if (!(redissonClient instanceof Redisson)) {
                throw new IllegalStateException("Unsupported RedissonClient implementation: "
                        + redissonClient.getClass().getName());
            }
            this.client = redissonClient;
            this.config = new Config(redissonClient.getConfig());
        }

        private void suspend() {
            this.client.shutdown();
        }

        private void resume() {
            RedissonClient replacement = Redisson.create(new Config(this.config));
            replaceRedissonState((Redisson) this.client, (Redisson) replacement);
        }

        private static void replaceRedissonState(Redisson target, Redisson source) {
            try {
                for (Field field : Redisson.class.getDeclaredFields()) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    field.setAccessible(true);
                    field.set(target, field.get(source));
                }
            } catch (ReflectiveOperationException exception) {
                source.shutdown();
                throw new IllegalStateException("Unable to restore Redisson client state", exception);
            }
        }

    }

}

3.需要指定可能引用的文件位置 crac-resource-policies.yaml

root@base:/opt/app-server# cat crac-resource-policies.yaml 
# Logback keeps the active application log open across checkpoint/restore.
type: file
path: /root/logs/app-server.log
action: reopen
---
# RocketMQ uses its own file logger outside the Spring logging lifecycle.
type: file
path: /root/logs/rocketmqlogs/rocketmq_client.log
action: reopen
---
# Java2D fonts loaded from streams are backed by persistent temporary files.
type: file
path: /opt/app-server/crac/tmp/+~JF*.tmp
action: reopen
root@base:/opt/app-server# 

启动命令类似

root@base:/opt/app-server# cat /opt/app-server/run-app-server.sh
#!/usr/bin/env bash

set -Eeuo pipefail

APP_DIR="${APP_DIR:-/opt/app-server}"
APP_JAR="${APP_DIR}/app-server.jar"
CRAC_JAVA_HOME="${CRAC_JAVA_HOME:-${APP_DIR}/zulu25-crac}"
JAVA_BIN="${CRAC_JAVA_HOME}/bin/java"
IMAGE_DIR="${APP_DIR}/crac/checkpoint"
TMP_DIR="${APP_DIR}/crac/tmp"
POLICY_FILE="${APP_DIR}/crac-resource-policies.yaml"
PROFILE="${APP_PROFILE:-prod}"
APP_TIMEZONE="${APP_TIMEZONE:-Asia/Shanghai}"

install -d -m 700 "${IMAGE_DIR}" "${TMP_DIR}"
echo "[launcher] Trying CRaC restore from ${IMAGE_DIR}; falling back to a cold start when unavailable"
exec "${JAVA_BIN}" \
    -server \
    -Xms512m \
    -Xmx1024m \
    -XX:+HeapDumpOnOutOfMemoryError \
    -XX:HeapDumpPath="${APP_DIR}/heapdump.hprof" \
    -Duser.timezone="${APP_TIMEZONE}" \
    -Djava.io.tmpdir="${TMP_DIR}" \
    -Djdk.crac.resource-policies="${POLICY_FILE}" \
    -XX:CRaCEngine=warp \
    -XX:CRaCRestoreFrom="${IMAGE_DIR}" \
    -XX:+CRaCIgnoreRestoreIfUnavailable \
    -XX:CRaCCheckpointTo="${IMAGE_DIR}" \
    -jar "${APP_JAR}" \
    --spring.profiles.active="${PROFILE}"

如果检查不到 checkpoint ,会保底从 jar 冷启动

去年没有 ai,折腾这一套没成功 今年有 codex 的帮助,crac 这一套快速启动的便捷性成功实现了 我的这一套只针对单体 springboot 程序 如果是 spring cloud 感觉会更受益,三秒容器上线