第一部分:基础概念与架构设计
1.1 技术栈介绍
核心技术组件包括:
-
OkHttp3:高性能HTTP客户端,负责底层网络通信
-
Retrofit2:类型安全的HTTP客户端,将HTTP API转换为Java接口
-
Jackson:JSON序列化/反序列化库
-
Spring Boot:依赖注入和配置管理
1.2 项目架构设计
架构分层如下:
core/
├── config/ # 配置类(文档1、2、3)
├── client/ # 客户端实现(文档4、5)
├── model/ # 数据模型
├── utils/ # 工具类(文档6)
└── exception/ # 异常处理
第二部分:详细配置实现
2.1 配置属性管理(NomadConfigProperties.class)
配置文件的作用:
-
集中管理所有HTTP相关参数
-
支持不同环境的不同配置
-
提供参数验证机制
ClientConfig.class
java
/**
* @author Felix
* @describe: 客户端配置接口
* @date 2025年12月06日 10:53
*/
public interface ClientConfig {
Integer getConnectTimeout();
Integer getReadTimeout();
Integer getWriteTimeout();
Integer getMaxIdleConnections();
Integer getKeepAliveDuration();
String getToken();
}
NomadConfigProperties.class
java
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author Felix
* @describe: Nomad 客户端配置属性
* @date 2025年12月03日 16:53
*/
@Data
@Component
@ConfigurationProperties(prefix = "nomad.client")
public class NomadConfigProperties implements ClientConfig {
// 基础配置
private String baseUrl = "http://192.168.1.183:4646";
private String token;
private String region = "global";
private String namespace = "default";
// HTTP客户端配置
private Integer connectTimeout = 5000;
private Integer readTimeout = 30000;
private Integer writeTimeout = 30000;
private Integer maxIdleConnections = 10;
private Integer keepAliveDuration = 300;
// 重试配置
private Boolean enableRetry = true;
private Integer maxRetries = 3;
private Integer retryDelay = 1000;
// 监控配置
private Boolean enableMetrics = true;
private Integer healthCheckInterval = 30000;
// Nomad 1.7.x 特性
private Boolean enableNamespace = true;
private Boolean enableVolumeCsi = true;
private Boolean enableDynamicNodeMetadata = true;
// 验证配置
public void validate() {
if (baseUrl == null || baseUrl.trim().isEmpty()) {
throw new IllegalArgumentException("Nomad base URL must not be empty");
}
// 建议添加URL格式基础验证
if (!baseUrl.startsWith("http")) {
throw new IllegalArgumentException("Nomad base URL must start with http or https");
}
if (connectTimeout <= 0 || readTimeout <= 0 || writeTimeout <= 0) {
throw new IllegalArgumentException("Timeout values must be positive");
}
if (maxRetries < 0) {
throw new IllegalArgumentException("Max retries cannot be negative");
}
}
}
2.2 HTTP客户端配置(HttpClientConfig.class)
多客户端策略:
为Nomad和Consul创建了独立的OkHttpClient实例,这种设计的优势:
-
隔离性:不同服务的配置互不影响
-
定制化:每个服务可以有特定的拦截器和超时设置
-
可维护性:问题排查时更容易定位
HttpClientConfig.class
java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* @author Felix
* @describe: HTTP客户端配置
* @date 2025年12月03日 17:01
*/
@Slf4j
@Configuration
public class HttpClientConfig {
private static final Logger logger = LoggerFactory.getLogger(HttpClientConfig.class);
@Resource
private NomadConfigProperties nomadConfig;
@Resource
private ConsulConfigProperties consulConfig;
/**
* 创建默认的 Jackson ObjectMapper
*/
@Bean
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
return mapper;
}
/**
* 创建 Nomad 专用的 OkHttpClient
*/
@Bean(name = "nomadHttpClient")
public OkHttpClient nomadHttpClient() {
return createHttpClientBuilder(nomadConfig)
.readTimeout(nomadConfig.getReadTimeout(), TimeUnit.MILLISECONDS)
.writeTimeout(nomadConfig.getWriteTimeout(), TimeUnit.MILLISECONDS)
.build();
}
/**
* 创建 Consul 专用的 OkHttpClient
*/
@Bean(name = "consulHttpClient")
public OkHttpClient consulHttpClient() {
return createHttpClientBuilder(consulConfig)
.readTimeout(consulConfig.getReadTimeout(), TimeUnit.MILLISECONDS)
.writeTimeout(consulConfig.getWriteTimeout(), TimeUnit.MILLISECONDS)
// 添加Consul特定的配置
.addInterceptor(chain -> {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder();
// Consul特定头信息
if (StringUtils.isNotBlank(consulConfig.getToken())) {
requestBuilder.addHeader("X-Consul-Token", consulConfig.getToken());
}
if (StringUtils.isNotBlank(consulConfig.getNamespace())) {
requestBuilder.addHeader("X-Consul-Namespace", consulConfig.getNamespace());
}
return chain.proceed(requestBuilder.build());
})
.build();
}
/**
* 创建通用的 HTTP 客户端构建器
*/
private OkHttpClient.Builder createHttpClientBuilder(ClientConfig config) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
// 连接超时
builder.connectTimeout(config.getConnectTimeout(), TimeUnit.MILLISECONDS);
// 连接池配置
ConnectionPool connectionPool = new ConnectionPool(
config.getMaxIdleConnections(),
config.getKeepAliveDuration(),
TimeUnit.SECONDS
);
builder.connectionPool(connectionPool);
// 添加认证令牌(如果有)
if (config.getToken() != null && !config.getToken().isEmpty()) {
builder.addInterceptor(chain -> {
Request.Builder requestBuilder = chain.request().newBuilder();
if (config instanceof NomadConfigProperties) {
requestBuilder.addHeader("X-Nomad-Token", config.getToken());
} else if (config instanceof ConsulConfigProperties) {
requestBuilder.addHeader("X-Consul-Token", config.getToken());
}
Request request = requestBuilder.build();
return chain.proceed(request);
});
}
return builder;
}
/**
* 创建 Nomad Retrofit 客户端
*/
@Bean
public Retrofit nomadRetrofit(@Autowired ObjectMapper objectMapper,
@Autowired OkHttpClient nomadHttpClient) {
return new Retrofit.Builder()
.baseUrl(nomadConfig.getBaseUrl())
.client(nomadHttpClient)
.addConverterFactory(JacksonConverterFactory.create(objectMapper))
.build();
}
}
拦截器链设计:
-
认证拦截器:自动添加Token头部
-
日志拦截器:记录请求/响应信息(可自定义补充)
-
监控拦截器:收集性能指标
2.3 Retrofit配置(HttpClientConfig.class中的nomadRetrofit方法)
核心配置要素:
-
基础URL:必须正确设置API端点
-
转换工厂:JacksonConverterFactory处理JSON序列化
-
HTTP客户端:使用配置好的OkHttpClient实例
第三部分:API接口定义
3.1 接口设计规范(NomadClient.class)
RESTful设计原则:
-
资源化URL设计:
/v1/jobs、/v1/nodes -
合适的HTTP方法:GET查询、POST创建、PUT更新、DELETE删除
-
清晰的路径参数:
@Path("jobId")用于指定资源 -
合理的查询参数:
@Query用于过滤和分页
java
import com.forest.geocube.core.model.NomadClientStats;
import com.forest.geocube.core.model.NomadJob;
import com.forest.geocube.core.model.NomadNode;
import com.forest.geocube.core.model.NomadSpace;
import retrofit2.Call;
import retrofit2.http.*;
import java.util.List;
import java.util.Map;
/**
* @author Felix
* @describe:Nomad REST API 客户端接口 使用 Retrofit 进行 HTTP 通信
* @date 2025年12月03日 17:09
*/
public interface NomadClient {
// ==================== 集群管理 ====================
/**
* 获取集群领导信息
*/
@GET("/v1/status/leader")
Call<String> getLeader();
/**
* 获取集群成员信息
*/
@GET("/v1/agent/members")
Call<Map<String, Object>> getMembers();
/**
* 获取集群服务器信息
*/
@GET("/v1/agent/servers")
Call<List<String>> getServers();
// ===================== 客户端管理 ====================
/**
* Nomad客户端状态统计信息查询
*/
@GET("/v1/client/stats/{nodeId}")
Call<NomadClientStats> getClientStats(
@Query("nodeId") String nodeId
);
// ==================== 节点管理 ====================
/**
* 获取所有节点
*/
@GET("/v1/nodes")
Call<List<NomadNode>> getNodes(
@Query("prefix") String prefix,
@Query("region") String region,
@Query("namespace") String namespace
);
/**
* 获取节点详情
*/
@GET("/v1/node/{nodeId}")
Call<NomadNode> getNode(
@Path("nodeId") String nodeId,
@Query("region") String region,
@Query("namespace") String namespace
);
/**
* 更新节点状态
*/
@POST("/v1/node/{nodeId}/drain")
Call<Void> drainNode(
@Path("nodeId") String nodeId,
@Query("enable") Boolean enable,
@Query("deadline") Long deadline,
@Query("meta") Map<String, String> meta,
@Query("region") String region,
@Query("namespace") String namespace,
@Body Map<String, Object> body
);
/**
* 节点资格管理
*/
@POST("/v1/node/{nodeId}/eligibility")
Call<Void> updateNodeEligibility(
@Path("nodeId") String nodeId,
@Query("eligibility") String eligibility,
@Query("region") String region,
@Query("namespace") String namespace
);
// ==================== 任务管理 ====================
/**
* 获取所有任务
*/
@GET("/v1/jobs")
Call<List<NomadJob>> getJobs(
@Query("prefix") String prefix,
@Query("region") String region,
@Query("namespace") String namespace
);
/**
* 获取任务详情
*/
@GET("/v1/job/{jobId}")
Call<NomadJob> getJob(
@Path("jobId") String jobId,
@Query("region") String region,
@Query("namespace") String namespace
);
/**
* 创建任务
*/
@POST("/v1/jobs")
Call<Void> createJob(
@Query("region") String region,
@Query("namespace") String namespace,
@Body NomadJob job
);
/**
* 更新任务
*/
@POST("/v1/job/{jobId}")
Call<Void> updateJob(
@Path("jobId") String jobId,
@Query("region") String region,
@Query("namespace") String namespace,
@Body NomadJob job
);
/**
* 删除任务
*/
@DELETE("/v1/job/{jobId}")
Call<Void> deleteJob(
@Path("jobId") String jobId,
@Query("purge") Boolean purge,
@Query("global") Boolean global,
@Query("region") String region,
@Query("namespace") String namespace
);
/**
* 启停任务
*/
@POST("/v1/job/{jobId}/periodic/force")
Call<Void> forcePeriodicJob(
@Path("jobId") String jobId,
@Query("region") String region,
@Query("namespace") String namespace
);
// ==================== 分配管理 ====================
/**
* 获取任务分配
*/
@GET("/v1/job/{jobId}/allocations")
Call<List<Map<String, Object>>> getAllocations(
@Path("jobId") String jobId,
@Query("region") String region,
@Query("namespace") String namespace
);
/**
* 获取分配详情
*/
@GET("/v1/allocation/{allocationId}")
Call<Map<String, Object>> getAllocation(
@Path("allocationId") String allocationId,
@Query("region") String region,
@Query("namespace") String namespace
);
/**
* 停止分配
*/
@DELETE("/v1/allocation/{allocationId}")
Call<Void> stopAllocation(
@Path("allocationId") String allocationId,
@Query("region") String region,
@Query("namespace") String namespace
);
// ==================== 卷管理 ====================
/**
* 获取所有卷
*/
@GET("/v1/volumes")
Call<List<Map<String, Object>>> getVolumes(
@Query("type") String type,
@Query("plugin_id") String pluginId,
@Query("region") String region,
@Query("namespace") String namespace
);
/**
* 创建卷
*/
@POST("/v1/volume/csi/{volumeId}/{action}")
Call<Void> manageVolume(
@Path("volumeId") String volumeId,
@Path("action") String action,
@Query("region") String region,
@Query("namespace") String namespace,
@Body Map<String, Object> body
);
// ==================== 命名空间管理 ====================
/**
* 获取所有命名空间
*/
@GET("/v1/namespaces")
Call<List<NomadSpace>> getNamespaces();
/**
* 创建命名空间
*/
@POST("/v1/namespace")
Call<Void> createNamespace(@Body NomadSpace namespace);
/**
* 删除命名空间
*/
@DELETE("/v1/namespace/{namespace}")
Call<Void> deleteNamespace(@Path("namespace") String namespace);
/**
* 删除命名空间
*/
@GET("/v1/namespace/{namespace}")
Call<NomadSpace> readNamespace(@Path("namespace") String namespace);
}
注解使用技巧:
@GET("/v1/job/{jobId}") // 路径模板
Call<NomadJob> getJob(
@Path("jobId") String jobId, // 路径参数
@Query("region") String region, // 查询参数
@Query("namespace") String namespace // 查询参数
);
第四部分:客户端实现
4.1 实现类设计(NomadClientImpl.class)
代码如下:
java
import com.forest.geocube.core.config.NomadConfigProperties;
import com.forest.geocube.core.exception.NomadClientException;
import com.forest.geocube.core.model.NomadClientStats;
import com.forest.geocube.core.model.NomadJob;
import com.forest.geocube.core.model.NomadNode;
import com.forest.geocube.core.model.NomadSpace;
import com.forest.geocube.core.utils.RetryUtil;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import retrofit2.Response;
import retrofit2.Retrofit;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Felix
* @describe: Nomad 客户端实现
* @date 2025年12月03日 17:15
*/
@Component
public class NomadClientImpl {
private static final Logger logger = LoggerFactory.getLogger(NomadClientImpl.class);
private final NomadClient nomadClient;
private final NomadConfigProperties config;
private final OkHttpClient httpClient;
private final RetryUtil retryUtil;
@Autowired
public NomadClientImpl(@Qualifier("nomadRetrofit") Retrofit retrofit,
NomadConfigProperties config,
@Qualifier("nomadHttpClient") OkHttpClient httpClient,
RetryUtil retryUtil) {
this.nomadClient = retrofit.create(NomadClient.class);
this.config = config;
this.httpClient = httpClient;
this.retryUtil = retryUtil;
}
// ==================== 集群管理 ====================
/**
* 获取集群领导者
*/
public String getLeader() {
return retryUtil.executeWithRetry(() -> {
Response<String> response = nomadClient.getLeader().execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to get cluster leader: " + response.message());
}
return response.body();
});
}
/**
* 检查集群健康状态
*/
public boolean isHealthy() {
try {
Response<String> response = nomadClient.getLeader().execute();
return response.isSuccessful();
} catch (IOException e) {
logger.error("Failed to check Nomad cluster health", e);
return false;
}
}
// ==================== 节点管理 ====================
/**
* 获取所有节点
*/
public List<NomadNode> getNodes(String prefix) {
return retryUtil.executeWithRetry(() -> {
Response<List<NomadNode>> response = nomadClient.getNodes(
prefix, config.getRegion(), config.getNamespace()
).execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to get nodes: " + response.message());
}
return response.body();
});
}
/**
* 获取节点详情
*/
public NomadNode getNode(String nodeId) {
return retryUtil.executeWithRetry(() -> {
Response<NomadNode> response = nomadClient.getNode(
nodeId, config.getRegion(), config.getNamespace()
).execute();
if (!response.isSuccessful()) {
if (response.code() == 404) {
throw new NomadClientException("Node not found: " + nodeId);
}
throw new NomadClientException("Failed to get node: " + response.message());
}
return response.body();
});
}
/**
* Nomad客户端状态统计信息查询
*/
public NomadClientStats getClientStats(String nodeId) {
return retryUtil.executeWithRetry(() -> {
Response<NomadClientStats> response = nomadClient.getClientStats(
nodeId
).execute();
if (!response.isSuccessful()) {
if (response.code() == 404) {
throw new NomadClientException("ClientStats not found: " + nodeId);
}
throw new NomadClientException("Failed to get ClientStats: " + response.message());
}
return response.body();
});
}
/**
* 排空节点
*/
public void drainNode(String nodeId, boolean enable, Long deadline, Map<String, String> meta) {
retryUtil.executeWithRetry(() -> {
Response<Void> response = nomadClient.drainNode(
nodeId, enable, deadline, meta,
config.getRegion(), config.getNamespace(), new HashMap<>()
).execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to drain node: " + response.message());
}
return null;
});
}
/**
* 更新节点调度资格
*/
public void updateNodeEligibility(String nodeId, boolean eligible) {
retryUtil.executeWithRetry(() -> {
String eligibility = eligible ? "eligible" : "ineligible";
Response<Void> response = nomadClient.updateNodeEligibility(
nodeId, eligibility, config.getRegion(), config.getNamespace()
).execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to update node eligibility: " + response.message());
}
return null;
});
}
// ==================== 任务管理 ====================
/**
* 获取所有任务
*/
public List<NomadJob> getJobs(String prefix) {
return retryUtil.executeWithRetry(() -> {
Response<List<NomadJob>> response = nomadClient.getJobs(
prefix, config.getRegion(), config.getNamespace()
).execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to get jobs: " + response.message());
}
return response.body();
});
}
/**
* 获取任务详情
*/
public NomadJob getJob(String jobId) {
return retryUtil.executeWithRetry(() -> {
Response<NomadJob> response = nomadClient.getJob(
jobId, config.getRegion(), config.getNamespace()
).execute();
if (!response.isSuccessful()) {
if (response.code() == 404) {
throw new NomadClientException("Job not found: " + jobId);
}
throw new NomadClientException("Failed to get job: " + response.message());
}
return response.body();
});
}
/**
* 创建任务
*/
public void createJob(NomadJob job) {
retryUtil.executeWithRetry(() -> {
Response<Void> response = nomadClient.createJob(
config.getRegion(), config.getNamespace(), job
).execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to create job: " + response.message());
}
return null;
});
}
/**
* 更新任务
*/
public void updateJob(String jobId, NomadJob job) {
retryUtil.executeWithRetry(() -> {
Response<Void> response = nomadClient.updateJob(
jobId, config.getRegion(), config.getNamespace(), job
).execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to update job: " + response.message());
}
return null;
});
}
/**
* 删除任务
*/
public void deleteJob(String jobId, boolean purge, boolean global) {
retryUtil.executeWithRetry(() -> {
Response<Void> response = nomadClient.deleteJob(
jobId, purge, global, config.getRegion(), config.getNamespace()
).execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to delete job: " + response.message());
}
return null;
});
}
// ==================== 分配管理 ====================
/**
* 获取任务分配
*/
public List<Map<String, Object>> getAllocations(String jobId) {
return retryUtil.executeWithRetry(() -> {
Response<List<Map<String, Object>>> response = nomadClient.getAllocations(
jobId, config.getRegion(), config.getNamespace()
).execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to get allocations: " + response.message());
}
return response.body();
});
}
/**
* 获取分配详情
*/
public Map<String, Object> getAllocation(String allocationId) {
return retryUtil.executeWithRetry(() -> {
Response<Map<String, Object>> response = nomadClient.getAllocation(
allocationId, config.getRegion(), config.getNamespace()
).execute();
if (!response.isSuccessful()) {
if (response.code() == 404) {
throw new NomadClientException("Allocation not found: " + allocationId);
}
throw new NomadClientException("Failed to get allocation: " + response.message());
}
return response.body();
});
}
/**
* 停止分配
*/
public void stopAllocation(String allocationId) {
retryUtil.executeWithRetry(() -> {
Response<Void> response = nomadClient.stopAllocation(
allocationId, config.getRegion(), config.getNamespace()
).execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to stop allocation: " + response.message());
}
return null;
});
}
// ==================== 卷管理 ====================
/**
* 获取所有存储卷
*/
public List<Map<String, Object>> getVolumes(String type, String pluginId) {
return retryUtil.executeWithRetry(() -> {
Response<List<Map<String, Object>>> response = nomadClient.getVolumes(
type, pluginId, config.getRegion(), config.getNamespace()
).execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to get volumes: " + response.message());
}
return response.body();
});
}
/**
* 挂载存储卷
*/
public void mountVolume(String volumeId, String nodeId, String allocationId) {
retryUtil.executeWithRetry(() -> {
Map<String, Object> body = new HashMap<>();
body.put("NodeID", nodeId);
body.put( "AllocationID", allocationId);
Response<Void> response = nomadClient.manageVolume(
volumeId, "mount", config.getRegion(), config.getNamespace(), body
).execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to mount volume: " + response.message());
}
return null;
});
}
/**
* 卸载存储卷
*/
public void unmountVolume(String volumeId, String nodeId, String allocationId) {
retryUtil.executeWithRetry(() -> {
Map<String, Object> body = new HashMap<>();
body.put("NodeID", nodeId);
body.put( "AllocationID", allocationId);
Response<Void> response = nomadClient.manageVolume(
volumeId, "unmount", config.getRegion(), config.getNamespace(), body
).execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to unmount volume: " + response.message());
}
return null;
});
}
// ==================== 命名空间管理 ====================
/**
* 获取所有命名空间
*/
public List<NomadSpace> getNamespaces() {
return retryUtil.executeWithRetry(() -> {
Response<List<NomadSpace>> response = nomadClient.getNamespaces().execute();
if (!response.isSuccessful()) {
throw new NomadClientException("Failed to get namespaces: " + response.message());
}
return response.body();
});
}
/**
* 创建命名空间
*/
public void createNamespace(NomadSpace nomadSpace) {
retryUtil.executeWithRetry(() -> {
// Map<String, Object> namespaceBody = new HashMap<>();
// namespaceBody.put("Name", namespace);
// namespaceBody.put("Description", description);
Response<Void> response = nomadClient.createNamespace(nomadSpace).execute();
if (!response.isSuccessful()) {
if (response.code() == 400) {
throw new NomadClientException("Invalid namespace configuration: " + response.message());
}
throw new NomadClientException("Failed to create namespace: " + response.message());
}
return null;
});
}
/**
* 删除命名空间
*/
public void deleteNamespace(String namespace) {
retryUtil.executeWithRetry(() -> {
Response<Void> response = nomadClient.deleteNamespace(namespace).execute();
if (!response.isSuccessful()) {
if (response.code() == 404) {
throw new NomadClientException("Namespace not found: " + namespace);
}
if (response.code() == 409) {
throw new NomadClientException("Cannot delete namespace with active jobs: " + namespace);
}
throw new NomadClientException("Failed to delete namespace: " + response.message());
}
return null;
});
}
/**
* 查询命名空间详情
*/
public NomadSpace readNamespace(String namespace) {
return retryUtil.executeWithRetry(() -> {
Response<NomadSpace> response = nomadClient.readNamespace(namespace).execute();
if (!response.isSuccessful()) {
if (response.code() == 404) {
throw new NomadClientException("Namespace not found: " + namespace);
}
throw new NomadClientException("Failed to delete namespace: " + response.message());
}
return response.body();
});
}
}
NomadClientException.class
java
/**
* @author Felix
* @describe: Nomad客户端异常
* @date 2025年12月03日 17:23
*/
public class NomadClientException extends RuntimeException {
public NomadClientException(String message) {
super(message);
}
public NomadClientException(String message, Throwable cause) {
super(message, cause);
}
public NomadClientException(Throwable cause) {
super(cause);
}
}
依赖注入策略:
@Autowired
public NomadClientImpl(
@Qualifier("nomadRetrofit") Retrofit retrofit, // 指定Bean名称
NomadConfigProperties config, // 配置属性
@Qualifier("nomadHttpClient") OkHttpClient httpClient, // HTTP客户端
RetryUtil retryUtil // 重试工具
)
统一的异常处理模式:
if (!response.isSuccessful()) {
if (response.code() == 404) {
throw new NomadClientException("Node not found: " + nodeId);
}
throw new NomadClientException("Failed to get node: " + response.message());
}
第五部分:高级特性实现
5.1 重试机制详解(RetryUtil.class)
重试策略设计:
// 固定间隔重试
public <T> T executeWithRetry(Callable<T> operation, int maxRetries, long retryDelay)
// 指数退避重试
public <T> T executeWithExponentialBackoff(Callable<T> operation, int maxRetries, long initialDelay)
RetryUtil.class
java
import com.forest.geocube.core.exception.NomadClientException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.concurrent.Callable;
/**
* @author Felix
* @describe: 重试工具类
* @date 2025年12月03日 17:21
*/
@Component
public class RetryUtil {
private static final Logger logger = LoggerFactory.getLogger(RetryUtil.class);
private final int maxRetries;
private final long retryDelay;
public RetryUtil() {
this.maxRetries = 3;
this.retryDelay = 1000;
}
public RetryUtil(int maxRetries, long retryDelay) {
this.maxRetries = maxRetries;
this.retryDelay = retryDelay;
}
/**
* 执行带有重试逻辑的操作
*/
public <T> T executeWithRetry(Callable<T> operation) {
return executeWithRetry(operation, maxRetries, retryDelay);
}
/**
* 执行带有重试逻辑的操作(自定义重试参数)
*/
public <T> T executeWithRetry(Callable<T> operation, int maxRetries, long retryDelay) {
int attempts = 0;
Exception lastException = null;
while (attempts <= maxRetries) {
try {
return operation.call();
}catch (NomadClientException e){
logger.error(e.getMessage());
throw e;
} catch (Exception e) {
lastException = e;
attempts++;
if (attempts > maxRetries) {
logger.error("Operation failed after {} attempts", attempts, e);
throw new RuntimeException("Operation failed after " + attempts + " attempts", e);
}
logger.warn("Operation failed (attempt {}/{}), retrying in {}ms...",
attempts, maxRetries, retryDelay, e);
try {
Thread.sleep(retryDelay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Retry interrupted", ie);
}
}
}
throw new RuntimeException("Operation failed", lastException);
}
/**
* 执行带有重试逻辑的操作(Supplier版本)
*/
// public <T> T executeWithRetry(Supplier<T> operation) {
// return executeWithRetry(() -> operation.get());
// }
/**
* 执行带有指数退避的重试
*/
public <T> T executeWithExponentialBackoff(Callable<T> operation, int maxRetries, long initialDelay) {
int attempts = 0;
Exception lastException = null;
long delay = initialDelay;
while (attempts <= maxRetries) {
try {
return operation.call();
} catch (Exception e) {
lastException = e;
attempts++;
if (attempts > maxRetries) {
logger.error("Operation failed after {} attempts with exponential backoff", attempts, e);
throw new RuntimeException("Operation failed after " + attempts + " attempts", e);
}
logger.warn("Operation failed (attempt {}/{}), retrying in {}ms...",
attempts, maxRetries, delay, e);
try {
Thread.sleep(delay);
// 指数退避:每次重试延迟加倍
delay *= 2;
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Retry interrupted", ie);
}
}
}
throw new RuntimeException("Operation failed", lastException);
}
}
