news 2026/10/2 3:30:31

Java应用容器化实战:从Docker部署到docker-compose编排全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java应用容器化实战:从Docker部署到docker-compose编排全攻略

“我机器上能跑啊”,这句话我在Java后端开发这行当里听了太多年了。换台服务器部署、给测试环境重新拉一套依赖、帮新同事把本地Java环境配起来,看上去都是小事,但JDK版本对不上、Maven仓库没配全、MySQL实例密码不一致,任何一个环节出问题,就是几小时的排查。Docker出现以后,Java部署这件事终于从“反复装修房子”变成了“直接搬活人进去住”。这篇内容我从零开始,把怎么理解Docker、怎么装环境、怎么写Dockerfile、怎么用docker-compose把Java服务连同MySQL、Redis一起编排起来,以及上线前那些坑都过一遍,适合刚接触容器化的Java开发,也适合正在规划项目交付部署方案的团队参考。

1. 为什么非要用Docker:Java部署的痛我替你总结好了

1.1 “我这台机器能跑”背后的环境不一致困境

Java应用天生依赖环境。JVM用8还是17,Spring Boot打的jar包和依赖版本,配置里连的数据库地址,甚至Linux系统里某个glibc版本,都会影响服务能不能正常起来。在传统部署方式下,开发、测试、生产三套环境的差异会被一次次放大:开发机上是Windows,测试服务器是CentOS 7,生产又是某云厂商的镜像,结果同一个jar包,在三个环境里跑出三种行为。

最典型的是数据库版本不一致。本地MySQL 5.7跑得好好的,到服务器上一看是MySQL 8.0,一些SQL查询结果变了,编码排序方式也不一样,于是你开始怀疑代码、怀疑配置、怀疑人生。这类问题本质上不是代码逻辑问题,而是环境没有和代码一起被“固化”下来。代码可以交给Git管起来,环境却一直是手工维护的状态,谁改了什么没人完全清楚。

1.2 传统部署流程每一步都是重复劳动

我们过去上线一个Java项目,大致是这么一套流程:

  1. 准备服务器:安装JDK,配置JAVA_HOME与PATH环境变量。
  2. 安装中间件:MySQL、Redis、Nginx,逐个下载、解压、配置开机自启。
  3. 上传构建产物:把本地打好的jar或war包用ftp、scp传到服务器。
  4. 编写启动脚本:处理PID文件、日志输出、JVM参数,有的还要注册成systemd服务。
  5. 升级回滚:停旧包、备份旧包、启新包,一旦启动失败再切回旧包。

单机做一次倒还好,但如果你有5套环境,就要把安装JDK、配置中间件这套体力活重复5遍。更麻烦的是,每次环境升级或补丁,都是一次新的不确定性。时间久了,服务器上可能残留多个JDK版本,不同的中间件实例,没人能准确说出这套环境到底是怎么搭起来的。

1.3 Docker给出的解法:把环境一并打包

Docker的核心思路,是把应用和它运行所需要的环境一起打包成一个镜像。这个镜像可以在任何装了Docker的机器上跑出几乎一致的行为。容器本身很轻,不像虚拟机一样要模拟一整套硬件,它直接共享宿主机的内核,启动一个Java容器通常只需要几百毫秒到一两秒。

如果还用搬家来类比:传统部署是每次搬家都要把家具拆了重新组装,换个城市还得重新买螺丝刀;Docker是把整个房间打包带走,到了新地方直接打开箱子就能住。你把jar包、JVM、依赖、配置文件打成一个镜像,到哪都能一键启动。这个过程不需要你在新机器上重新安装JDK、重新配环境变量,CI/CD流水线也因此简单了很多,只要 push 一个镜像,运行环境就和开发时完全对齐。

2. 先搞懂这四个概念,后面所有操作都不绕了

2.1 镜像和容器:程序与进程的关系

很多人一上来就翻部署教程,结果被“镜像”“容器”这两个词绕晕。我的理解方式是这样的:镜像是一个只读的模板,里面包含了文件系统、依赖、JVM和你的jar包;容器是镜像的一个运行实例,相当于镜像被启动后变成的那个进程。镜像可以同时启动多个容器,每个容器相互隔离,这很像“类与对象”的关系——镜像就是类,容器就是对象。

容器运行期间产生的临时文件、日志、数据,默认都写在容器的可写层里,一旦容器被删除,这些内容也会跟着消失。这也是后面为什么要引入数据卷的原因。镜像本身是分层保存的,拉取时如果本地已经有相同的底层宿层镜像,往往只需要下载差异部分,这也是Docker比虚拟机部署高效的原因之一。

2.2 仓库与镜像拉取:Docker Hub 相当于Maven中央仓库

Docker镜像的下载靠仓库来分发,最常用的是Docker Hub,你可以把它理解为“镜像的Maven中央仓库”。当你执行docker pull mysql:8.0,Docker就会从Docker Hub把MySQL 8.0镜像拉到本地。Java项目里有Maven坐标,Docker则用“仓库名:标签”来唯一定位一个镜像,比如eclipse-temurin:17-jre,冒号前面是仓库名和镜像名,冒号后面是标签。

在公司内部,团队一般会搭建私有镜像仓库,比如Harbor,用于统一管理和分发内部镜像,避免所有人都直接依赖外网Docker Hub,也便于做安全扫描。理解了仓库的含义,就不会对docker push、docker tag这些命令感到陌生,它们本质上是给镜像打标签和上传到仓库。

2.3 Dockerfile:容器的施工图纸

Dockerfile是一个文本文件,里面的每条指令告诉Docker怎么一步步搭建镜像。核心指令就那么几个:

  • FROM:指定基础镜像,比如eclipse-temurin:17-jre。
  • WORKDIR:设置工作目录。
  • COPY:把文件复制进镜像。
  • RUN:在构建镜像时执行命令,比如安装依赖或执行打包。
  • CMD / ENTRYPOINT:指定容器启动时的命令。

你可以把Dockerfile理解成建筑图纸,图纸画好了,执行docker build就按图施工,最终得到一个可运行的镜像。好记的方式是:Dockerfile描述“怎么做出来”,容器启动时执行的命令描述“怎么跑起来”。

2.4 和虚拟机的本质区别:共享内核 vs 模拟硬件

虚拟机装的是完整操作系统,里面有一个Guest OS,底层要模拟CPU、内存、硬盘这些硬件,所以镜像动辄几个GB,启动速度以分钟计。容器则直接复用宿主机的内核,镜像里只包含应用和它的运行依赖,体积通常在几十到几百MB,启动速度是秒级。

但这并不意味着容器万能。因为容器共享宿主机内核,所以不能在Linux宿主机上直接跑Windows容器;反过来,Windows宿主机上跑Linux容器也需要借助轻量级虚拟机或WSL2来实现。这个区别在Java项目里最直接的体现就是:镜像小了,部署快了,但生产环境扩容时,多个Java容器的内存总和需要整体规划,不能拿虚拟机时代的经验直接照搬。

3. 本地环境准备:从安装Docker到跑通第一个Java容器

3.1 Windows用户:先把WSL2搞定,再装Docker Desktop

Windows环境下,大多数人使用Docker Desktop,但它的安装有一个隐藏门槛:需要本机开启虚拟化,并安装好WSL2。很多人卡在“Virtualization support not detected”这个报错,要么是BIOS没开虚拟化,要么是WSL内核没有更新。

建议按这个顺序来:

  1. 在BIOS里打开Intel VT-x或AMD SVM虚拟化。
  2. 以管理员身份打开PowerShell,执行wsl --install安装WSL2。
  3. 重启后,进入Windows设置确认WSL2是默认版本:wsl --set-default-version 2。
  4. 再下载安装Docker Desktop,安装向导会自动识别WSL2。

装完之后,把Docker Desktop的Settings里面“Use the WSL 2 based engine”选项打开,这是默认选项。我第一次给一台老笔记本装Docker时,就是漏了WSL内核更新这个步骤,翻文件发现报错信息指向了内核版本太低,更新完WSL内核之后一切正常。

3.2 macOS和Linux:安装命令和验证方法

macOS用户可以用Homebrew一行搞定:

brew install --cask docker

安装完成后打开Docker Desktop,等右下角的小鲸鱼图标稳定下来,再继续。

Linux用户更推荐用发行版各自的包管理器。以Ubuntu为例,比较稳妥的方式是安装官方发布的docker-ce包,而不是直接apt install docker.io,因为官方包版本更新更及时。大致流程是添加官方GPG key和软件源,然后:

sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin sudo systemctl enable --now docker

验证是否装好,最简单的方法是:

docker --version docker compose version docker run hello-world

能输出hello-world信息,说明Docker守护进程正常,容器运行链路畅通。

3.3 跑一个Java验证容器:确认JVM能在容器里跑起来

有同学问,是不是要先装个JDK才能跑Java容器?不用。你只需要拉一个带JDK的官方镜像,然后让容器执行一行Java版本命令就能验证:

docker run --rm eclipse-temurin:17-jre java -version

--rm表示容器退出后自动删除,不会在磁盘上留下垃圾容器。如果这行命令能正常打印Java版本号,说明你的Docker环境已经可以承载Java运行时环境了。这也体现了Docker的好处:本地不需要预装JDK,不用折腾环境变量,想要哪个JDK版本都可以临时用容器来跑。

4. 写一份能用的Dockerfile:Java项目容器化的核心环节

4.1 基础镜像选型:为什么我不建议用openjdk老镜像

很多教程第一行就写FROM openjdk:8-jdk-alpine,这个镜像在早期很流行,但现在已经不建议新项目用了。原因是官方openjdk镜像已经停止维护,而且alpine这类基于musl libc的极小系统在跑Java时,有时会遇到DNS解析、字体库、localedata等兼容性问题。

更省心的选择是Eclipse Temurin。它是Eclipse基金会维护的OpenJDK发行版,有严格的TCK认证,镜像标签齐全,在Java 17和Java 21时代几乎是事实标准。linux系统上建议这样选:

  • 需要完整运行时:eclipse-temurin:17-jre-jammy
  • 追求体积更小:eclipse-temurin:17-jre-alpine
  • 构建阶段用:maven:3.9-eclipse-temurin-17

-jammy代表基于Ubuntu 22.04的系统层,兼容性稳;-alpine代表基于Alpine的小体积系统,镜像可以压得很小,但要注意glibc相关依赖,运行时一般够用。

4.2 第一版Dockerfile:先跑通再谈优化

很多人一上来就直接上多阶段构建,结果抄错了镜像版本,编译半天失败,心态直接崩了。我的建议是,第一版先保证能跑通,再逐步优化。

先用一个最简单的单阶段Dockerfile:

FROM eclipse-temurin:17-jre-jammy WORKDIR /app COPY target/app.jar . EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

这里假设你的Java项目已经用Maven打好了jar包,名字叫app.jar,并放在了target目录下。构建命令是:

docker build -t demo-java:v1 .

启动命令是:

docker run -d --name java-demo -p 8080:8080 demo-java:v1

访问http://localhost:8080,看到应用响应,说明基础流程已经跑通。注意这里的ENTRYPOINT用的是JSON数组形式,也就是exec form。它直接以java进程作为容器的一号进程,能正确接收SIGTERM信号,这对后面说到的优雅停机很重要。如果用ENTRYPOINT java -jar app.jar这种shell写法,docker stop时信号会被中间shell进程吞掉,应用往往得不到体面的退出时机。

4.3 多阶段构建:把镜像从300MB压到80MB的关键

单阶段构建有个明显缺点:jar包是用本机Maven打的,如果本机关了多环境配置或依赖仓库的问题,构建环节还是会依赖开发环境。而且最终镜像里只有jre,不算大,但如果你把maven镜像作为基础镜像直接放jar进去跑,体积就是几百MB,因为Maven镜像自带一套完整JDK和依赖工具链。

更优雅的做法是让Docker自己完成编译,然后只把运行需要的部分拷贝到最终镜像。这被称为多阶段构建:

# 第一阶段:编译打包 FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn clean package -DskipTests # 第二阶段:运行 FROM eclipse-temurin:17-jre-jammy RUN useradd -r -u 1001 appuser WORKDIR /app COPY --from=builder /build/target/app.jar . USER appuser EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

这里有几个关键点:

  • RUN mvn dependency:go-offline会提前下载依赖并缓存,后续只要源码不涉及pom变动,这一层都能复用镜像缓存,打包速度会快很多。
  • COPY --from=builder表示从上一阶段拷贝文件,避免了把整个maven镜像带进最终镜像。
  • 使用普通用户运行Java进程,减小安全攻击面,这一点后面会展开。
  • 最终镜像只包含JRE、jar包和依赖库,体积通常能控制在150MB以内,如果再用jlink裁剪JDK模块,甚至可以压到80MB以下。

4.4 健康检查不能省:让Docker和编排工具知道服务是否真的就绪

你有没有遇到过这种情况:docker ps显示容器是Up状态,但服务实际上不可用。容器“活着”不等于“就绪”。Docker本身只看进程在不在,Java进程起来了就算Up,但Spring Boot的内嵌Web服务器可能还在初始化,数据库连接池也没ready。

解决办法是在Dockerfile里设置健康检查,建议依赖Spring Boot Actuator的健康端点:

HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ CMD wget -q -O - http://localhost:8080/actuator/health || exit 1

如果项目里没有引入Actuator,也可以用nc -z localhost 8080直接检测端口,但无法反映应用层面的真实状态。引入Actuator后,健康端点能返回数据库连接情况、磁盘空间等指标,编排工具调度的时候才不至于把流量打到没就绪的实例上。这个细节在单机跑的时候看不出区别,一旦上了docker-compose或K8s,作用就非常明显了。

5. docker-compose实战:Java+MySQL+Redis一键起全套环境

5.1 为什么非要上compose:Java项目不是单打独斗

一个常规Java后端项目,最少都要依赖一个数据库,很多还要用Redis做缓存,用Kafka做消息队列。如果每个组件都用docker run单独启动,命令行会非常长,而且需要管理多个网络、多个端口映射,极易出错。

docker-compose把多容器定义写进一个YAML文件里,用一条命令完成构建和启动。它并不负责集群调度,但解决了单机多容器的编排问题。尤其是本地开发时,一条docker compose up -d就能把MySQL、Redis、Java服务都拉起来,体验和以前手动起一堆进程完全是两个世界。

5.2 网络与服务发现:容器之间怎么互相找

这是compose最容易被Git上旧配置坑的地方。在同一个compose项目里,默认会创建一个bridge网络,所有容器都加入这个网络,并且可以用“服务名”作为主机名互相访问。也就是说,Java容器里连MySQL,jdbc地址不是localhost:3306,而是mysql:3306。

很多同学第一次跑compose,Java服务启动时报数据库连接失败,就是因为配置文件里写的是localhost。容器外面的localhost和容器里面的localhost不是同一个环境。MySQL容器自己内部有MySQL进程,但你Java服务在另一个容器里,访问localhost自然连不上。要解决这个问题,Spring Boot的配置可以这样写:

spring: datasource: url: jdbc:mysql://mysql:3306/demo?useSSL=false&characterEncoding=utf8 username: demo password: demo123

5.3 完整示例:Java + MySQL + Redis一套起

下面给一份可以直接参考的compose文件:

version: "3.8" services: mysql: image: mysql:8.0 container_name: demo-mysql environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: demo MYSQL_USER: demo MYSQL_PASSWORD: demo123 ports: - "3306:3306" volumes: - mysql-data:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7.2-alpine container_name: demo-redis ports: - "6379:6379" app: build: . container_name: demo-app depends_on: mysql: condition: service_healthy redis: condition: service_started environment: SPRING_PROFILES_ACTIVE: prod MYSQL_HOST: mysql REDIS_HOST: redis ports: - "8080:8080" volumes: mysql-data:

启动命令:

docker compose up -d

查看状态:

docker compose ps

看所有服务日志:

docker compose logs -f

之前提到“容器删了数据不能丢”,这里的volumes: mysql-data就是给MySQL的数据目录提供持久化存储。MySQL镜像是全新启动的,首次初始化会执行初始化脚本并创建数据库,但一旦容器被docker rm删除,如果没挂载数据卷,所有数据都会随容器消失。挂载数据卷后,数据会保存在宿主机路径中,重建容器仍然保留。

5.4 环境变量与配置:密码别硬编码进镜像

把数据库密码直接写进Dockerfile或镜像里,是明显的安全漏洞。镜像可以被任何能访问仓库的人拉取,镜像里的敏感信息也会直接暴露。正确做法是通过compose的environment或.env文件注入配置。

Spring Boot本身对环境变量有很强的映射支持。你在compose里写:

environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/demo SPRING_DATASOURCE_USERNAME: demo SPRING_DATASOURCE_PASSWORD: demo123

Spring Boot会自动把SPRING_DATASOURCE_URL映射到spring.datasource.url配置项,不需要改任何代码。敏感信息更建议放在compose同级的.env文件里,然后在compose里引用:

environment: SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}

.env文件不要提交进Git仓库,加入.gitignore。这样开发和生产的密钥互不混淆,也不用担心代码仓库泄露。

6. 上线前避坑:时区、内存、日志、优雅停机我都替你踩过了

6.1 时区问题:为什么日志时间比北京时间晚了8小时

很多Java镜像默认采用UTC时间,容器里日志打出来的时间比北京时间整整少8小时。排查线上问题的时候,看着一串UTC时间对不上告警时间,非常难受。

解决方式有两种。最简单的是在Dockerfile里设置时区环境变量:

ENV TZ=Asia/Shanghai

但这个在alpine系镜像上需要额外安装tzdata包,否则只是设置了一个空环境变量。更通用的做法是在Java启动命令里设置用户时区:

ENTRYPOINT ["java", "-Duser.timezone=Asia/Shanghai", "-jar", "app.jar"]

这个方案不依赖系统层时区数据,JDK本身就能正确处理。如果你的应用用的是jdk内置的ZoneInfo,连tzdata都不用安装。我自己的项目就是直接在启动参数里加-Duser.timezone=Asia/Shanghai,再配合logback里显示的时区,日志时间就和服务器时间对齐了。

6.2 内存问题:容器限了内存,JVM却还是按宿主机内存配置

这是Java容器化里翻车率最高的问题。你在compose里设置了mem_limit: 1g,但JVM默认最大堆会按宿主机物理内存的一定比例来计算。如果在16G内存的服务器上跑,JVM可能默认-Xmx取了4G甚至更多,容器内存被撑爆,然后被内核OOM Killer杀掉,现象就是服务莫名其妙重启。

JDK 8u191以后,容器环境默认开启了-XX:+UseContainerSupport,JVM能感知容器内存限制,堆大小会跟着限制调整,但这毕竟只是“默认行为”,生产环境还是要显式控制。建议在启动参数里明确定义最大堆和初始堆:

ENTRYPOINT ["java", "-Xms512m", "-Xmx512m", "-jar", "app.jar"]

如果对内存规划没有把握,可以先用-Xmx512m起步,加上JVM参数-XX:+PrintFlagsFinal观察运行时实际堆大小,再根据监控调整。不要只盯着宿主机空余内存,要按每个容器的配额来设定。

6.3 优雅停机:让Spring Boot体面地退出

Kubernetes、docker-compose在滚动升级时,都会先给容器发送SIGTERM信号,再等一小段时间后强制SIGKILL。如果Java应用不处理优雅停机,Spring Boot的线程池、数据库连接池、消息队列会突然中断,造成业务数据不一致或请求被异常切断。

Spring Boot 2.3之后原生支持优雅停机,配置server.shutdown=graceful,Spring Boot会停止接收新请求,并等待已处理请求完成。但容器能不能把这个SIGTERM正确传给Java进程,取决于Dockerfile的ENTRYPOINT写法。如果用的是shell form:

ENTRYPOINT java -jar app.jar

Docker会把命令交给shell执行,shell进程成为一号进程,Java反而是子进程,SIGTERM信号被shell拦截,Java进程可能收不到。要用exec form写:

ENTRYPOINT ["java", "-Xms512m", "-Xmx512m", "-jar", "app.jar"]

这样java进程直接作为容器的PID 1,能正确接收SIGTERM。还可以在compose里加stop_grace_period: 60s,给Spring Boot留足处理收尾请求的时间,避免默认10秒后就被强制杀掉。

6.4 非root运行:Java进程不需要root权限

容器里的root约等于宿主机的root,一旦镜像被利用,攻击者可能直接控制宿主机。安全最佳实践是让进程以非root用户运行。上面多阶段构建示例里的:

RUN useradd -r -u 1001 appuser USER appuser

就是创建普通用户并切换过去。Java 8之后以普通用户运行没有任何问题,内嵌Tomcat监听8080端口也不需要root权限。需要注意的是,如果应用需要写挂载目录,比如上传文件到/app/upload,要确保宿主机挂载目录对uid 1001有写权限,否则会出现Permission denied。处理方式是在宿主机制定目录权限,或者用chown在Dockerfile里调整镜像内目录归属。

6.5 日志输出与采集:别写文件,写到stdout

Java项目很多习惯了用logback写日志文件,比如日志写到/logs/app.log。这在传统部署里没问题,但在容器里就会带来麻烦:日志文件不透明,docker logs不展示,日志轮转、采集都需要额外处理。

更Docker化的做法是,让日志直接输出到标准输出stdout。Spring Boot起服务时默认日志就是控制台输出,logback配置里只要不配置FileAppender,日志就会打到容器日志收集系统。Docker会把stdout日志统一接进docker logs,K8s里的日志方案也会自动从容器标准输出采集。如果确实需要落盘保存,建议用bind mount挂载宿主机目录,再配合外部日志轮转。但实操下来,日志输出到stdout + 集中采集是最省心的,也最符合“容器不可变”的运维理念。

我自己的项目在改造Docker部署时,就是先把logback的FileAppender去掉,保留ConsoleAppender,再把compose里面加好stop_grace_period和健康检查,然后才敢推到生产。这个过程不是一天改完的,先在小服务上验证,再推广到其他团队。容器化部署真正带来的好处不是“省去了安装JDK”这一件小事,而是整个交付过程变得可预测、可回滚、可审计,这对Java项目这种重依赖的系统来说,价值是实实在在的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 3:30:29

基于深度学习的手势数字识别:从数据集到推理的完整实战指南

简介:这份资源是面向人工智能、深度学习方向的毕业设计与课程设计参考项目,聚焦手势数字识别这一人机交互典型任务,帮助学习者理解如何用卷积神经网络完成从数据准备到实时识别的完整流程。压缩包共40个文件、约14.32MB,以20个Pyt…

作者头像 李华
网站建设 2026/10/2 3:30:28

EEG癫痫模仿症识别:从伪迹去除到源定位的实战指南

EEG脑电信号处理这个系列写到第19期,时间已经到了2026年3月。上一期我们聊了尖波、棘波和睡眠期放电的判读细节,这期换个更有临床味道的题目:癫痫模仿症(epilepsy mimics)。所谓模仿症,就是患者表现出一系列…

作者头像 李华
网站建设 2026/10/2 3:30:27

微信小程序语音识别对接科大讯飞:PCM录音到文字全链路实战

简介:这份资源面向微信小程序开发者与语音交互功能集成人员,提供一套对接科大讯飞语音识别能力的完整示例工程,重点解决音频上传、语音提取、PCM格式转换与实时语音转文字等环节的落地问题。压缩包共34个文件,约55KB,以…

作者头像 李华
网站建设 2026/10/2 3:29:24

深度学习显卡选型实战指南:2080 Ti、3090与A100对比

1. 这不是跑分榜,是实验室里熬出来的显卡选型手记我带过三届研究生做CV方向的课题,从ResNet-50微调到ViT-L/16预训练,从单卡YOLOv5s部署到多卡DDP训练SAM大模型。过去五年,实验室机房换过四轮显卡:最早是两块2080 Ti拼…

作者头像 李华
网站建设 2026/10/2 3:29:09

ADB 自动化测试入门:环境搭建、高频命令、Python 封装与日志排查

adb 这东西,说它简单是真简单,敲三条命令就能装应用、点屏幕、拉日志;说它麻烦也是真麻烦,环境没配对、设备没授权、好几台设备抢着连同一个端口,随便中一个都能让你在工位上耗掉一下午。我最早把 adb 用进日常测试&am…

作者头像 李华
网站建设 2026/10/2 3:29:09

海信IP501H机顶盒U盘刷机全攻略:从固件选择到变砖自救

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华