在微服務架構普及前,RMI (Remote Method Invocation) 是 Java 原生 RPC 方案,Quartz 是事實上的分布式定時任務標準。Spring 對兩者提供了優雅的聲明式整合。本文從介面定義、服務端/客戶端配置、集群部署、監控運維四個維度,給出生產可用的完整實戰指南。
public interface UserService {
UserDTO getUser(String username) throws BusinessException;
List listUsers(PageRequest page);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired private UserRepository repo;
@Override
public UserDTO getUser(String username) {
return repo.findByUsername(username)
.map(this::toDTO)
.orElseThrow(() -> new BusinessException("用戶不存在"));
}
@Override
public List listUsers(PageRequest page) {
return repo.findAll(page).map(this::toDTO).getContent();
}
}
@Configuration
public class RmiServerConfig {
@Bean
public RmiServiceExporter userServiceExporter(UserService userService) {
RmiServiceExporter exporter = new RmiServiceExporter();
exporter.setService(userService);
exporter.setServiceName("UserService");
exporter.setServiceInterface(UserService.class);
exporter.setRegistryPort(1099); // 預設 1099
return exporter;
}
}
啟動類加 @EnableRmiExport 或直接掃描上述 Configuration,服務端即在 rmi://host:1099/UserService 發布。
@Configuration
public class RmiClientConfig {
@Bean
public RmiProxyFactoryBean userServiceProxy() {
RmiProxyFactoryBean proxy = new RmiProxyFactoryBean();
proxy.setServiceUrl("rmi://localhost:1099/UserService");
proxy.setServiceInterface(UserService.class);
proxy.setRefreshStubOnConnectFailure(true); // 連接失敗自動重刷 Stub
proxy.setLookupStubOnStartup(false); // 延遲查找,啟動快
return proxy;
}
}
注入即用:@Autowired private UserService userService; —— 像呼叫本地 Bean 一樣呼叫遠程服務。
| 項目 | 建議 |
|---|---|
| 序列化 | DTO 實現 Serializable,定義 serialVersionUID,避免版本不兼容 |
| 超時控制 | 自定義 RmiProxyFactoryBean 繼承覆寫 createStub 注入 sun.rmi.transport.tcp.responseTimeout |
| 安全 | 生產**必須**啟用 SSL + 認證(SslRmiServerSocketFactory + RMIClientSocketFactory),或走 VPN/Service Mesh |
| 防火牆 | RMI 使用隨機高端口,需配置 -Djava.rmi.server.hostname 固定 IP,或改用 HTTP/ gRPC |
| 監控 | 整合 Micrometer:RmiServiceExporter 無內建指標,建議 AOP 記錄調用延遲/錯誤率 |
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- Quartz 官方建議表結構,Spring Boot 自動建表需設 spring.quartz.jdbc.initialize-schema=always -->
@Component
public class SyncUserJob implements Job {
private static final Logger log = LoggerFactory.getLogger(SyncUserJob.class);
@Autowired private UserSyncService syncService;
@Override
public void execute(JobExecutionContext context) {
String tenantId = context.getMergedJobDataMap().getString("tenantId");
log.info("開始同步租戶 {} 用戶", tenantId);
syncService.syncTenantUsers(tenantId);
log.info("同步完成");
}
}
@Configuration
public class QuartzConfig {
@Autowired private ApplicationContext ctx;
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
factory.setAutoStartup(true);
factory.setOverwriteExistingJobs(true); // 允許更新 JobDetail
factory.setSchedulerName("ClusterScheduler");
// 關鍵:集群模式
Properties props = new Properties();
props.setProperty("org.quartz.scheduler.instanceName", "ClusterScheduler");
props.setProperty("org.quartz.scheduler.instanceId", "AUTO");
props.setProperty("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
props.setProperty("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
props.setProperty("org.quartz.jobStore.tablePrefix", "QRTZ_");
props.setProperty("org.quartz.jobStore.isClustered", "true");
props.setProperty("org.quartz.jobStore.clusterCheckinInterval", "15000");
factory.setQuartzProperties(props);
// 動態註冊觸發器
factory.setTriggers(
createCronTrigger("syncTenantA", "0 0/5 * * * ?", "tenantA"), // 每 5 分
createCronTrigger("syncTenantB", "0 0 2 * * ?", "tenantB") // 每天 02:00
);
return factory;
}
private CronTrigger createCronTrigger(String jobName, String cron, String tenantId) {
JobDetail jobDetail = JobBuilder.newJob(SyncUserJob.class)
.withIdentity(jobName)
.usingJobData("tenantId", tenantId)
.storeDurably()
.build();
return TriggerBuilder.newTrigger()
.forJob(jobDetail)
.withIdentity(jobName + "Trigger")
.withSchedule(CronScheduleBuilder.cronSchedule(cron)
.withMisfireHandlingInstructionDoNothing()) // 錯過不補跑
.build();
}
}
秒 分 時 日 月 周 [年]
* * * * * ? 每秒
0 0 12 ? * * 每天 12:00
0 0 0 1 * ? 每月 1 號 00:00
0 0 0 ? * MON-FRI 工作日 00:00
0 0/5 14 * * ? 每天 14:00-14:55 每 5 分
特殊字符:? (日/周二擇一)、* (所有值)、, (列表)、/ (增量)、- (範圍)、L (最後)、W (最近工作日)、# (第 N 個周X)。
JobStoreTX 基於行鎖保證同一任務集群內**僅一節點執行**。withMisfireHandlingInstructionDoNothing() / FireAndProceed / SmartPolicy 根據業務選擇。instanceId=AUTO 自動生成 host+pid,確保集群唯一。@Component
public class RemoteTriggerJob implements Job {
@Autowired private RemoteOrderService orderService; // RMI 代理
@Override
public void execute(JobExecutionContext ctx) {
String orderId = ctx.getMergedJobDataMap().getString("orderId");
// 遠程調用下游訂單服務觸發超時取消
orderService.triggerTimeoutCancel(orderId);
}
}
Quartz 負責可靠調度,RMI 負責跨進程調用,兩者解耦且各司其職。
| 組件 | 關鍵指標 | 採集方式 |
|---|---|---|
| RMI 服務端 | 調用 QPS、延遲 P99、錯誤率、活躍連接數 | AOP + Micrometer Timer |
| RMI 客戶端 | Stub 刷新次數、連接失敗重試、超時計數 | 自定義 RmiProxyFactoryBean 埋點 |
| Quartz | 任務執行總數、成功/失敗率、平均耗時、誤觸發次數、調度器狀態 | Quartz Listener + Micrometer / JMX |
| 資料庫 | QRTZ_TRIGGERS 狀態分佈、QRTZ_FIRED_TRIGGERS 積壓、鎖等待時間 | SQL Exporter / 定時巡檢腳本 |
- alert: QuartzJobHighFailureRate
expr: rate(quartz_job_failed_total[5m]) / rate(quartz_job_executed_total[5m]) > 0.05
for: 2m
labels: { severity: warning }
annotations:
summary: "Quartz 任務失敗率超過 5%"
gRPC (Protobuf) / Spring Cloud OpenFeign (HTTP+JSON)RmiServiceExporter 導出、RmiProxyFactoryBean 代理,**配置極簡**,適合內網低延遲 Java 互調。SchedulerFactoryBean + JDBC JobStore 實現**集群高可用**,Cron 表達式靈活,Misfire 機制完善。將本文配置模板、監控指標、遷移路徑存入團隊知識庫,下次遇到「跨服務定時調用」或「舊系統 RMI 改造」,直接拿來用——不再從零摸索。