1. 幂等性概述

幂等性是分布式系统中的一个重要概念,指同一个操作执行多次和执行一次的效果是相同的。在分布式环境下,由于网络抖动、服务重启、消息重发等原因,同一个请求可能会被多次执行,幂等性能够确保系统的数据一致性和业务正确性。本文将详细介绍幂等性的原理、实现方案、性能优化技巧以及在Java企业级应用中的最佳实践。

1.1 幂等性核心价值

  1. 数据一致性: 保证数据不重复、不丢失
  2. 系统稳定性: 提高系统容错能力
  3. 业务正确性: 确保业务逻辑正确执行
  4. 用户体验: 避免重复操作带来的困扰
  5. 系统可靠性: 增强系统可靠性

1.2 幂等性应用场景

  • 支付系统: 防止重复扣款
  • 订单系统: 防止重复下单
  • 库存系统: 防止超卖
  • 消息队列: 防止重复消费
  • API接口: 防止重复调用

1.3 幂等性实现方案

  • 唯一标识: 使用唯一ID标识请求
  • 状态机: 基于状态的状态转换
  • 数据库约束: 利用数据库唯一约束
  • Redis缓存: 使用Redis实现幂等
  • 分布式锁: 使用分布式锁控制并发

2. 幂等性基础实现

2.1 幂等性配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
* 幂等性配置类
* @author Java实战
*/
@Configuration
@EnableConfigurationProperties(IdempotencyProperties.class)
public class IdempotencyConfig {

@Autowired
private IdempotencyProperties properties;

/**
* 幂等性服务
* @return 幂等性服务
*/
@Bean
public IdempotencyService idempotencyService() {
return new IdempotencyService();
}

/**
* Redis幂等性服务
* @return Redis幂等性服务
*/
@Bean
public RedisIdempotencyService redisIdempotencyService() {
return new RedisIdempotencyService();
}

/**
* 数据库幂等性服务
* @return 数据库幂等性服务
*/
@Bean
public DatabaseIdempotencyService databaseIdempotencyService() {
return new DatabaseIdempotencyService();
}

/**
* 分布式锁幂等性服务
* @return 分布式锁幂等性服务
*/
@Bean
public DistributedLockIdempotencyService distributedLockIdempotencyService() {
return new DistributedLockIdempotencyService();
}

/**
* 幂等性监控服务
* @return 幂等性监控服务
*/
@Bean
public IdempotencyMonitorService idempotencyMonitorService() {
return new IdempotencyMonitorService();
}

/**
* Redis连接工厂
* @return Redis连接工厂
*/
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(properties.getRedis().getHost());
config.setPort(properties.getRedis().getPort());
config.setPassword(properties.getRedis().getPassword());
config.setDatabase(properties.getRedis().getDatabase());

LettuceConnectionFactory factory = new LettuceConnectionFactory(config);
factory.setTimeout(Duration.ofMillis(properties.getRedis().getConnectionTimeout()));
return factory;
}

/**
* Redis模板
* @return Redis模板
*/
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());

// 设置序列化器
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

template.afterPropertiesSet();
return template;
}

private static final Logger logger = LoggerFactory.getLogger(IdempotencyConfig.class);
}

2.2 幂等性属性配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/**
* 幂等性属性配置
* @author Java实战
*/
@Data
@ConfigurationProperties(prefix = "idempotency")
public class IdempotencyProperties {

/**
* 是否启用幂等性
*/
private boolean enableIdempotency = true;

/**
* Redis配置
*/
private RedisConfig redis = new RedisConfig();

/**
* 数据库配置
*/
private DatabaseConfig database = new DatabaseConfig();

/**
* 分布式锁配置
*/
private DistributedLockConfig distributedLock = new DistributedLockConfig();

/**
* 默认过期时间(秒)
*/
private long defaultExpireTime = 300;

/**
* 是否启用监控
*/
private boolean enableMonitoring = true;

/**
* 监控间隔(毫秒)
*/
private long monitorInterval = 30000;

/**
* 是否启用告警
*/
private boolean enableAlert = true;

/**
* 重复请求告警阈值
*/
private int duplicateRequestAlertThreshold = 100;

/**
* 幂等性失败告警阈值
*/
private int idempotencyFailureAlertThreshold = 50;

/**
* Redis配置类
*/
@Data
public static class RedisConfig {
/**
* Redis地址
*/
private String host = "localhost";

/**
* Redis端口
*/
private int port = 6379;

/**
* Redis密码
*/
private String password;

/**
* 数据库索引
*/
private int database = 0;

/**
* 连接超时时间(毫秒)
*/
private long connectionTimeout = 3000;

/**
* 读取超时时间(毫秒)
*/
private long readTimeout = 3000;

/**
* 写入超时时间(毫秒)
*/
private long writeTimeout = 3000;

/**
* 最大连接数
*/
private int maxConnections = 20;

/**
* 最小空闲连接数
*/
private int minIdleConnections = 5;

/**
* 幂等性键前缀
*/
private String keyPrefix = "idempotency:";

/**
* 默认过期时间(秒)
*/
private long defaultExpireTime = 300;

/**
* 是否启用集群
*/
private boolean enableCluster = false;

/**
* 集群节点
*/
private List<String> clusterNodes = new ArrayList<>();
}

/**
* 数据库配置类
*/
@Data
public static class DatabaseConfig {
/**
* 幂等性表名
*/
private String tableName = "idempotency_record";

/**
* 是否启用数据库幂等性
*/
private boolean enableDatabaseIdempotency = true;

/**
* 是否启用自动清理
*/
private boolean enableAutoCleanup = true;

/**
* 清理间隔(小时)
*/
private int cleanupInterval = 24;

/**
* 保留时间(小时)
*/
private int retentionTime = 72;

/**
* 批量清理大小
*/
private int batchCleanupSize = 1000;

/**
* 是否启用分表
*/
private boolean enableSharding = false;

/**
* 分表数量
*/
private int shardingCount = 4;
}

/**
* 分布式锁配置类
*/
@Data
public static class DistributedLockConfig {
/**
* 是否启用分布式锁
*/
private boolean enableDistributedLock = true;

/**
* 锁超时时间(毫秒)
*/
private long lockTimeout = 5000;

/**
* 锁重试次数
*/
private int lockRetryCount = 3;

/**
* 锁重试间隔(毫秒)
*/
private long lockRetryInterval = 1000;

/**
* 锁前缀
*/
private String lockPrefix = "idempotency:lock:";

/**
* 锁类型
*/
private String lockType = "REDIS";

/**
* Zookeeper连接字符串
*/
private String zookeeperConnectionString;

/**
* Zookeeper会话超时时间
*/
private int zookeeperSessionTimeout = 30000;
}
}

2.3 幂等性数据模型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
/**
* 幂等性数据模型类
* @author Java实战
*/
@Data
@Table(name = "idempotency_record")
public class IdempotencyData {

/**
* 幂等性键(主键)
*/
@Id
@Column(name = "idempotency_key", length = 128)
private String idempotencyKey;

/**
* 业务标识
*/
@Column(name = "business_key", length = 64)
private String businessKey;

/**
* 请求参数(JSON格式)
*/
@Column(name = "request_params", columnDefinition = "TEXT")
private String requestParams;

/**
* 响应结果(JSON格式)
*/
@Column(name = "response_result", columnDefinition = "TEXT")
private String responseResult;

/**
* 请求时间
*/
@Column(name = "request_time")
private Long requestTime;

/**
* 响应时间
*/
@Column(name = "response_time")
private Long responseTime;

/**
* 过期时间
*/
@Column(name = "expire_time")
private Long expireTime;

/**
* 状态:PROCESSING, SUCCESS, FAILURE
*/
@Column(name = "status", length = 20)
private String status;

/**
* 重试次数
*/
@Column(name = "retry_count")
private Integer retryCount;

/**
* 错误信息
*/
@Column(name = "error_message", length = 500)
private String errorMessage;

/**
* 客户端IP
*/
@Column(name = "client_ip", length = 45)
private String clientIp;

/**
* 用户ID
*/
@Column(name = "user_id", length = 64)
private String userId;

/**
* 扩展属性(JSON格式)
*/
@Column(name = "extended_properties", columnDefinition = "TEXT")
private String extendedProperties;

/**
* 创建时间
*/
@Column(name = "create_time")
@CreationTimestamp
private LocalDateTime createTime;

/**
* 更新时间
*/
@Column(name = "update_time")
@UpdateTimestamp
private LocalDateTime updateTime;

public IdempotencyData() {
this.requestTime = System.currentTimeMillis();
this.status = "PROCESSING";
this.retryCount = 0;
}

public IdempotencyData(String idempotencyKey, String businessKey) {
this();
this.idempotencyKey = idempotencyKey;
this.businessKey = businessKey;
}

/**
* 验证幂等性数据
* @return 是否有效
*/
public boolean validate() {
if (idempotencyKey == null || idempotencyKey.isEmpty()) {
return false;
}

if (businessKey == null || businessKey.isEmpty()) {
return false;
}

if (requestTime == null || requestTime <= 0) {
return false;
}

if (idempotencyKey.length() > 128) {
return false;
}

if (businessKey.length() > 64) {
return false;
}

return true;
}

/**
* 是否过期
* @return 是否过期
*/
public boolean isExpired() {
if (expireTime == null) {
return false;
}
return System.currentTimeMillis() > expireTime;
}

/**
* 增加重试次数
*/
public void incrementRetryCount() {
this.retryCount++;
}

/**
* 设置成功状态
*/
public void setSuccessStatus() {
this.status = "SUCCESS";
this.responseTime = System.currentTimeMillis();
}

/**
* 设置失败状态
* @param errorMessage 错误信息
*/
public void setFailureStatus(String errorMessage) {
this.status = "FAILURE";
this.errorMessage = errorMessage;
this.responseTime = System.currentTimeMillis();
}

/**
* 添加扩展属性
* @param key 键
* @param value 值
*/
public void addExtendedProperty(String key, Object value) {
Map<String, Object> properties = getExtendedPropertiesMap();
properties.put(key, value);
this.extendedProperties = JSON.toJSONString(properties);
}

/**
* 获取扩展属性
* @param key 键
* @return
*/
public Object getExtendedProperty(String key) {
Map<String, Object> properties = getExtendedPropertiesMap();
return properties.get(key);
}

/**
* 获取扩展属性Map
* @return 扩展属性Map
*/
private Map<String, Object> getExtendedPropertiesMap() {
if (extendedProperties == null || extendedProperties.isEmpty()) {
return new HashMap<>();
}
try {
return JSON.parseObject(extendedProperties, new TypeReference<Map<String, Object>>() {});
} catch (Exception e) {
return new HashMap<>();
}
}

/**
* 设置扩展属性Map
* @param properties 扩展属性Map
*/
public void setExtendedPropertiesMap(Map<String, Object> properties) {
this.extendedProperties = JSON.toJSONString(properties);
}
}

/**
* 幂等性操作结果类
* @author Java实战
*/
@Data
public class IdempotencyResult {

private boolean success;
private String idempotencyKey;
private String businessKey;
private Object result;
private String error;
private long startTime;
private long endTime;
private boolean isDuplicate;
private String status;
private Map<String, Object> metadata;

public IdempotencyResult() {
this.success = false;
this.isDuplicate = false;
this.metadata = new HashMap<>();
}

public IdempotencyResult(String idempotencyKey, String businessKey) {
this();
this.idempotencyKey = idempotencyKey;
this.businessKey = businessKey;
}

/**
* 获取操作耗时
* @return 操作耗时(毫秒)
*/
public long getDuration() {
return endTime - startTime;
}

/**
* 是否成功
* @return 是否成功
*/
public boolean isSuccess() {
return success;
}

/**
* 是否重复请求
* @return 是否重复请求
*/
public boolean isDuplicate() {
return isDuplicate;
}

/**
* 添加元数据
* @param key 键
* @param value 值
*/
public void addMetadata(String key, Object value) {
this.metadata.put(key, value);
}

/**
* 获取元数据
* @param key 键
* @return
*/
public Object getMetadata(String key) {
return this.metadata.get(key);
}
}

/**
* 幂等性状态信息类
* @author Java实战
*/
@Data
public class IdempotencyStatus {

/**
* 总请求数
*/
private long totalRequests;

/**
* 成功请求数
*/
private long successRequests;

/**
* 失败请求数
*/
private long failureRequests;

/**
* 重复请求数
*/
private long duplicateRequests;

/**
* 成功率
*/
private double successRate;

/**
* 重复率
*/
private double duplicateRate;

/**
* 平均响应时间(毫秒)
*/
private double averageResponseTime;

/**
* 最大响应时间(毫秒)
*/
private long maxResponseTime;

/**
* 最小响应时间(毫秒)
*/
private long minResponseTime;

/**
* 最后检查时间
*/
private Long lastCheckTime;

/**
* 系统状态
*/
private String systemStatus;

public IdempotencyStatus() {
this.lastCheckTime = System.currentTimeMillis();
this.systemStatus = "HEALTHY";
}

/**
* 计算成功率
* @return 成功率
*/
public double calculateSuccessRate() {
if (totalRequests > 0) {
return (double) successRequests / totalRequests;
}
return 0.0;
}

/**
* 计算重复率
* @return 重复率
*/
public double calculateDuplicateRate() {
if (totalRequests > 0) {
return (double) duplicateRequests / totalRequests;
}
return 0.0;
}

/**
* 是否健康
* @return 是否健康
*/
public boolean isHealthy() {
return successRate >= 0.95 && duplicateRate <= 0.1;
}
}

2.4 基础幂等性服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
/**
* 基础幂等性服务
* @author Java实战
*/
@Service
@Slf4j
public class IdempotencyService {

@Autowired
private IdempotencyProperties properties;

@Autowired
private RedisIdempotencyService redisIdempotencyService;

@Autowired
private DatabaseIdempotencyService databaseIdempotencyService;

@Autowired
private DistributedLockIdempotencyService distributedLockIdempotencyService;

@Autowired
private IdempotencyMonitorService idempotencyMonitorService;

@Autowired
private IdempotencyRepository idempotencyRepository;

/**
* 执行幂等性操作
* @param idempotencyKey 幂等性键
* @param businessKey 业务键
* @param requestParams 请求参数
* @param operation 操作函数
* @return 操作结果
*/
public IdempotencyResult executeIdempotentOperation(String idempotencyKey, String businessKey,
String requestParams, Function<String, Object> operation) {
return executeIdempotentOperation(idempotencyKey, businessKey, requestParams, operation, null);
}

/**
* 执行幂等性操作(带用户信息)
* @param idempotencyKey 幂等性键
* @param businessKey 业务键
* @param requestParams 请求参数
* @param operation 操作函数
* @param userContext 用户上下文
* @return 操作结果
*/
public IdempotencyResult executeIdempotentOperation(String idempotencyKey, String businessKey,
String requestParams, Function<String, Object> operation, UserContext userContext) {
log.info("执行幂等性操作,幂等性键: {}, 业务键: {}", idempotencyKey, businessKey);

IdempotencyResult result = new IdempotencyResult(idempotencyKey, businessKey);
result.setStartTime(System.currentTimeMillis());

try {
// 参数验证
validateParameters(idempotencyKey, businessKey, operation, result);
if (!result.isSuccess()) {
return result;
}

// 检查是否已存在
IdempotencyData existingData = checkExistingData(idempotencyKey, result);
if (existingData != null) {
return handleDuplicateRequest(existingData, result);
}

// 创建幂等性数据
IdempotencyData data = createIdempotencyData(idempotencyKey, businessKey, requestParams, userContext);

// 使用分布式锁确保原子性
String lockKey = properties.getDistributedLock().getLockPrefix() + idempotencyKey;
boolean lockAcquired = distributedLockIdempotencyService.acquireLock(lockKey);

if (!lockAcquired) {
result.setSuccess(false);
result.setError("获取分布式锁失败");
result.setEndTime(System.currentTimeMillis());
return result;
}

try {
// 双重检查
existingData = checkExistingData(idempotencyKey, result);
if (existingData != null) {
return handleDuplicateRequest(existingData, result);
}

// 保存幂等性数据
boolean saved = saveIdempotencyData(data);
if (!saved) {
result.setSuccess(false);
result.setError("保存幂等性数据失败");
result.setEndTime(System.currentTimeMillis());
return result;
}

// 执行业务操作
Object operationResult = executeBusinessOperation(operation, requestParams, data);

// 更新幂等性数据
updateIdempotencyData(data, operationResult, null);

result.setSuccess(true);
result.setResult(operationResult);
result.setEndTime(System.currentTimeMillis());
result.setStatus("SUCCESS");

// 记录成功指标
idempotencyMonitorService.recordSuccessRequest();

log.info("幂等性操作执行成功,幂等性键: {}, 业务键: {}, 耗时: {}ms",
idempotencyKey, businessKey, result.getDuration());

return result;

} finally {
// 释放分布式锁
distributedLockIdempotencyService.releaseLock(lockKey);
}

} catch (Exception e) {
log.error("幂等性操作执行异常,幂等性键: {}, 业务键: {}", idempotencyKey, businessKey, e);

result.setSuccess(false);
result.setError("幂等性操作执行异常: " + e.getMessage());
result.setEndTime(System.currentTimeMillis());
result.setStatus("FAILURE");

// 记录失败指标
idempotencyMonitorService.recordFailureRequest();

return result;
}
}

/**
* 验证参数
*/
private void validateParameters(String idempotencyKey, String businessKey,
Function<String, Object> operation, IdempotencyResult result) {
if (idempotencyKey == null || idempotencyKey.isEmpty()) {
result.setSuccess(false);
result.setError("幂等性键不能为空");
result.setEndTime(System.currentTimeMillis());
return;
}

if (idempotencyKey.length() > 128) {
result.setSuccess(false);
result.setError("幂等性键长度不能超过128个字符");
result.setEndTime(System.currentTimeMillis());
return;
}

if (businessKey == null || businessKey.isEmpty()) {
result.setSuccess(false);
result.setError("业务键不能为空");
result.setEndTime(System.currentTimeMillis());
return;
}

if (businessKey.length() > 64) {
result.setSuccess(false);
result.setError("业务键长度不能超过64个字符");
result.setEndTime(System.currentTimeMillis());
return;
}

if (operation == null) {
result.setSuccess(false);
result.setError("操作函数不能为空");
result.setEndTime(System.currentTimeMillis());
return;
}
}

/**
* 检查已存在的数据
*/
private IdempotencyData checkExistingData(String idempotencyKey, IdempotencyResult result) {
try {
// 先从Redis检查
IdempotencyData data = redisIdempotencyService.getData(idempotencyKey);
if (data != null) {
return data;
}

// 如果Redis中没有,从数据库检查
if (properties.getDatabase().isEnableDatabaseIdempotency()) {
data = databaseIdempotencyService.getData(idempotencyKey);
if (data != null) {
// 将数据同步到Redis
redisIdempotencyService.saveData(data);
return data;
}
}

return null;
} catch (Exception e) {
log.error("检查已存在数据异常,幂等性键: {}", idempotencyKey, e);
return null;
}
}

/**
* 处理重复请求
*/
private IdempotencyResult handleDuplicateRequest(IdempotencyData existingData, IdempotencyResult result) {
result.setSuccess(true);
result.setResult(existingData.getResponseResult());
result.setDuplicate(true);
result.setEndTime(System.currentTimeMillis());
result.setStatus("DUPLICATE");

// 记录重复请求指标
idempotencyMonitorService.recordDuplicateRequest();

log.info("检测到重复请求,幂等性键: {}, 业务键: {}",
existingData.getIdempotencyKey(), existingData.getBusinessKey());

return result;
}

/**
* 创建幂等性数据
*/
private IdempotencyData createIdempotencyData(String idempotencyKey, String businessKey,
String requestParams, UserContext userContext) {
IdempotencyData data = new IdempotencyData(idempotencyKey, businessKey);
data.setRequestParams(requestParams);
data.setExpireTime(System.currentTimeMillis() + properties.getDefaultExpireTime() * 1000);

if (userContext != null) {
data.setUserId(userContext.getUserId());
data.setClientIp(userContext.getClientIp());
}

// 添加扩展属性
data.addExtendedProperty("requestId", UUID.randomUUID().toString());
data.addExtendedProperty("timestamp", System.currentTimeMillis());

return data;
}

/**
* 保存幂等性数据
*/
private boolean saveIdempotencyData(IdempotencyData data) {
try {
// 验证数据
if (!data.validate()) {
log.error("幂等性数据验证失败,幂等性键: {}", data.getIdempotencyKey());
return false;
}

// 保存到Redis
boolean redisSaved = redisIdempotencyService.saveData(data);

// 保存到数据库
boolean databaseSaved = true;
if (properties.getDatabase().isEnableDatabaseIdempotency()) {
databaseSaved = databaseIdempotencyService.saveData(data);
}

return redisSaved && databaseSaved;
} catch (Exception e) {
log.error("保存幂等性数据异常,幂等性键: {}", data.getIdempotencyKey(), e);
return false;
}
}

/**
* 执行业务操作
*/
private Object executeBusinessOperation(Function<String, Object> operation,
String requestParams, IdempotencyData data) throws Exception {
try {
return operation.apply(requestParams);
} catch (Exception e) {
// 更新失败状态
data.setFailureStatus(e.getMessage());
updateIdempotencyData(data, null, e.getMessage());
throw e;
}
}

/**
* 更新幂等性数据
*/
private void updateIdempotencyData(IdempotencyData data, Object result, String errorMessage) {
try {
if (result != null) {
data.setResponseResult(result.toString());
data.setSuccessStatus();
} else if (errorMessage != null) {
data.setFailureStatus(errorMessage);
}

// 更新Redis
redisIdempotencyService.updateData(data);

// 更新数据库
if (properties.getDatabase().isEnableDatabaseIdempotency()) {
databaseIdempotencyService.updateData(data);
}
} catch (Exception e) {
log.error("更新幂等性数据异常,幂等性键: {}", data.getIdempotencyKey(), e);
}
}

/**
* 检查幂等性
* @param idempotencyKey 幂等性键
* @return 是否已存在
*/
public boolean checkIdempotency(String idempotencyKey) {
log.info("检查幂等性,幂等性键: {}", idempotencyKey);

try {
IdempotencyData data = checkExistingData(idempotencyKey, new IdempotencyResult());
return data != null;
} catch (Exception e) {
log.error("检查幂等性异常,幂等性键: {}", idempotencyKey, e);
return false;
}
}

/**
* 获取幂等性数据
* @param idempotencyKey 幂等性键
* @return 幂等性数据
*/
public IdempotencyData getIdempotencyData(String idempotencyKey) {
log.info("获取幂等性数据,幂等性键: {}", idempotencyKey);

try {
return checkExistingData(idempotencyKey, new IdempotencyResult());
} catch (Exception e) {
log.error("获取幂等性数据异常,幂等性键: {}", idempotencyKey, e);
return null;
}
}

/**
* 删除幂等性数据
* @param idempotencyKey 幂等性键
* @return 是否成功
*/
public boolean deleteIdempotencyData(String idempotencyKey) {
log.info("删除幂等性数据,幂等性键: {}", idempotencyKey);

try {
boolean redisDeleted = redisIdempotencyService.deleteData(idempotencyKey);
boolean databaseDeleted = true;

if (properties.getDatabase().isEnableDatabaseIdempotency()) {
databaseDeleted = databaseIdempotencyService.deleteData(idempotencyKey);
}

return redisDeleted && databaseDeleted;
} catch (Exception e) {
log.error("删除幂等性数据异常,幂等性键: {}", idempotencyKey, e);
return false;
}
}

/**
* 获取幂等性状态
* @return 幂等性状态
*/
public IdempotencyStatus getIdempotencyStatus() {
log.info("获取幂等性状态");

try {
return idempotencyMonitorService.getStatus();
} catch (Exception e) {
log.error("获取幂等性状态异常", e);
return null;
}
}

/**
* 批量检查幂等性
* @param idempotencyKeys 幂等性键列表
* @return 已存在的幂等性键集合
*/
public Set<String> batchCheckIdempotency(List<String> idempotencyKeys) {
log.info("批量检查幂等性,数量: {}", idempotencyKeys.size());

Set<String> existingKeys = new HashSet<>();

try {
for (String key : idempotencyKeys) {
if (checkIdempotency(key)) {
existingKeys.add(key);
}
}

log.info("批量检查幂等性完成,已存在数量: {}", existingKeys.size());

} catch (Exception e) {
log.error("批量检查幂等性异常", e);
}

return existingKeys;
}

/**
* 清理过期数据
* @return 清理的数据条数
*/
public int cleanupExpiredData() {
log.info("清理过期幂等性数据");

try {
int redisCleaned = redisIdempotencyService.cleanupExpiredData();
int databaseCleaned = 0;

if (properties.getDatabase().isEnableDatabaseIdempotency()) {
databaseCleaned = databaseIdempotencyService.cleanupExpiredData();
}

int totalCleaned = redisCleaned + databaseCleaned;

log.info("过期幂等性数据清理完成,总清理条数: {}", totalCleaned);

return totalCleaned;
} catch (Exception e) {
log.error("清理过期幂等性数据异常", e);
return 0;
}
}
}

/**
* 用户上下文类
* @author Java实战
*/
@Data
public class UserContext {

/**
* 用户ID
*/
private String userId;

/**
* 客户端IP
*/
private String clientIp;

/**
* 用户代理
*/
private String userAgent;

/**
* 会话ID
*/
private String sessionId;

/**
* 租户ID
*/
private String tenantId;

/**
* 扩展属性
*/
private Map<String, Object> extendedProperties;

public UserContext() {
this.extendedProperties = new HashMap<>();
}

public UserContext(String userId, String clientIp) {
this();
this.userId = userId;
this.clientIp = clientIp;
}
}

3. 高级功能实现

3.1 Redis幂等性服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
/**
* Redis幂等性服务
* @author Java实战
*/
@Service
@Slf4j
public class RedisIdempotencyService {

@Autowired
private IdempotencyProperties properties;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private StringRedisTemplate stringRedisTemplate;

/**
* 保存幂等性数据
* @param data 幂等性数据
* @return 是否成功
*/
public boolean saveData(IdempotencyData data) {
log.info("保存Redis幂等性数据,幂等性键: {}", data.getIdempotencyKey());

try {
String key = buildKey(data.getIdempotencyKey());

// 使用SET NX EX命令确保原子性
Boolean setResult = stringRedisTemplate.opsForValue()
.setIfAbsent(key, JSON.toJSONString(data),
Duration.ofSeconds(properties.getDefaultExpireTime()));

boolean success = Boolean.TRUE.equals(setResult);

if (success) {
log.info("Redis幂等性数据保存成功,幂等性键: {}", data.getIdempotencyKey());
} else {
log.warn("Redis幂等性数据已存在,幂等性键: {}", data.getIdempotencyKey());
}

return success;

} catch (Exception e) {
log.error("Redis幂等性数据保存异常,幂等性键: {}", data.getIdempotencyKey(), e);
return false;
}
}

/**
* 获取幂等性数据
* @param idempotencyKey 幂等性键
* @return 幂等性数据
*/
public IdempotencyData getData(String idempotencyKey) {
log.info("获取Redis幂等性数据,幂等性键: {}", idempotencyKey);

try {
String key = buildKey(idempotencyKey);

String dataJson = stringRedisTemplate.opsForValue().get(key);

if (dataJson != null) {
IdempotencyData data = JSON.parseObject(dataJson, IdempotencyData.class);

// 检查是否过期
if (data.isExpired()) {
deleteData(idempotencyKey);
return null;
}

log.info("Redis幂等性数据获取成功,幂等性键: {}", idempotencyKey);

return data;
} else {
log.warn("Redis幂等性数据不存在,幂等性键: {}", idempotencyKey);
return null;
}

} catch (Exception e) {
log.error("Redis幂等性数据获取异常,幂等性键: {}", idempotencyKey, e);
return null;
}
}

/**
* 更新幂等性数据
* @param data 幂等性数据
* @return 是否成功
*/
public boolean updateData(IdempotencyData data) {
log.info("更新Redis幂等性数据,幂等性键: {}", data.getIdempotencyKey());

try {
String key = buildKey(data.getIdempotencyKey());

// 更新数据并设置过期时间
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(data),
Duration.ofSeconds(properties.getDefaultExpireTime()));

log.info("Redis幂等性数据更新成功,幂等性键: {}", data.getIdempotencyKey());

return true;

} catch (Exception e) {
log.error("Redis幂等性数据更新异常,幂等性键: {}", data.getIdempotencyKey(), e);
return false;
}
}

/**
* 删除幂等性数据
* @param idempotencyKey 幂等性键
* @return 是否成功
*/
public boolean deleteData(String idempotencyKey) {
log.info("删除Redis幂等性数据,幂等性键: {}", idempotencyKey);

try {
String key = buildKey(idempotencyKey);

Boolean deleted = stringRedisTemplate.delete(key);

boolean success = Boolean.TRUE.equals(deleted);

if (success) {
log.info("Redis幂等性数据删除成功,幂等性键: {}", idempotencyKey);
} else {
log.warn("Redis幂等性数据删除失败,幂等性键: {}", idempotencyKey);
}

return success;

} catch (Exception e) {
log.error("Redis幂等性数据删除异常,幂等性键: {}", idempotencyKey, e);
return false;
}
}

/**
* 检查幂等性数据是否存在
* @param idempotencyKey 幂等性键
* @return 是否存在
*/
public boolean exists(String idempotencyKey) {
log.info("检查Redis幂等性数据是否存在,幂等性键: {}", idempotencyKey);

try {
String key = buildKey(idempotencyKey);

Boolean exists = stringRedisTemplate.hasKey(key);

boolean result = Boolean.TRUE.equals(exists);

log.info("Redis幂等性数据存在检查完成,幂等性键: {}, 存在: {}", idempotencyKey, result);

return result;

} catch (Exception e) {
log.error("检查Redis幂等性数据是否存在异常,幂等性键: {}", idempotencyKey, e);
return false;
}
}

/**
* 设置过期时间
* @param idempotencyKey 幂等性键
* @param expireTime 过期时间(秒)
* @return 是否成功
*/
public boolean expire(String idempotencyKey, long expireTime) {
log.info("设置Redis幂等性数据过期时间,幂等性键: {}, 过期时间: {}s", idempotencyKey, expireTime);

try {
String key = buildKey(idempotencyKey);

Boolean expired = stringRedisTemplate.expire(key, Duration.ofSeconds(expireTime));

boolean success = Boolean.TRUE.equals(expired);

if (success) {
log.info("Redis幂等性数据过期时间设置成功,幂等性键: {}", idempotencyKey);
} else {
log.warn("Redis幂等性数据过期时间设置失败,幂等性键: {}", idempotencyKey);
}

return success;

} catch (Exception e) {
log.error("设置Redis幂等性数据过期时间异常,幂等性键: {}", idempotencyKey, e);
return false;
}
}

/**
* 获取剩余过期时间
* @param idempotencyKey 幂等性键
* @return 剩余过期时间(秒)
*/
public long getExpireTime(String idempotencyKey) {
log.info("获取Redis幂等性数据剩余过期时间,幂等性键: {}", idempotencyKey);

try {
String key = buildKey(idempotencyKey);

Long expireTime = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);

long result = expireTime != null ? expireTime : -1;

log.info("Redis幂等性数据剩余过期时间获取完成,幂等性键: {}, 剩余时间: {}s",
idempotencyKey, result);

return result;

} catch (Exception e) {
log.error("获取Redis幂等性数据剩余过期时间异常,幂等性键: {}", idempotencyKey, e);
return -1;
}
}

/**
* 批量获取幂等性数据
* @param idempotencyKeys 幂等性键列表
* @return 幂等性数据Map
*/
public Map<String, IdempotencyData> batchGetData(List<String> idempotencyKeys) {
log.info("批量获取Redis幂等性数据,数量: {}", idempotencyKeys.size());

Map<String, IdempotencyData> result = new HashMap<>();

try {
List<String> keys = idempotencyKeys.stream()
.map(this::buildKey)
.collect(Collectors.toList());

List<String> dataJsons = stringRedisTemplate.opsForValue().multiGet(keys);

for (int i = 0; i < idempotencyKeys.size(); i++) {
String idempotencyKey = idempotencyKeys.get(i);
String dataJson = dataJsons.get(i);

if (dataJson != null) {
IdempotencyData data = JSON.parseObject(dataJson, IdempotencyData.class);

// 检查是否过期
if (!data.isExpired()) {
result.put(idempotencyKey, data);
} else {
deleteData(idempotencyKey);
}
}
}

log.info("批量获取Redis幂等性数据完成,获取数量: {}", result.size());

} catch (Exception e) {
log.error("批量获取Redis幂等性数据异常", e);
}

return result;
}

/**
* 批量删除幂等性数据
* @param idempotencyKeys 幂等性键列表
* @return 删除的数量
*/
public long batchDeleteData(List<String> idempotencyKeys) {
log.info("批量删除Redis幂等性数据,数量: {}", idempotencyKeys.size());

try {
List<String> keys = idempotencyKeys.stream()
.map(this::buildKey)
.collect(Collectors.toList());

Long deletedCount = stringRedisTemplate.delete(keys);

long result = deletedCount != null ? deletedCount : 0;

log.info("批量删除Redis幂等性数据完成,删除数量: {}", result);

return result;

} catch (Exception e) {
log.error("批量删除Redis幂等性数据异常", e);
return 0;
}
}

/**
* 清理过期数据
* @return 清理的数据条数
*/
public int cleanupExpiredData() {
log.info("清理Redis过期幂等性数据");

try {
String pattern = properties.getRedis().getKeyPrefix() + "*";
Set<String> keys = stringRedisTemplate.keys(pattern);

int cleanedCount = 0;

if (keys != null && !keys.isEmpty()) {
for (String key : keys) {
try {
String dataJson = stringRedisTemplate.opsForValue().get(key);
if (dataJson != null) {
IdempotencyData data = JSON.parseObject(dataJson, IdempotencyData.class);
if (data.isExpired()) {
stringRedisTemplate.delete(key);
cleanedCount++;
}
}
} catch (Exception e) {
log.warn("清理Redis过期数据异常,键: {}", key, e);
}
}
}

log.info("Redis过期幂等性数据清理完成,清理条数: {}", cleanedCount);

return cleanedCount;

} catch (Exception e) {
log.error("清理Redis过期幂等性数据异常", e);
return 0;
}
}

/**
* 获取Redis统计信息
* @return 统计信息
*/
public Map<String, Object> getStatistics() {
log.info("获取Redis幂等性数据统计");

Map<String, Object> statistics = new HashMap<>();

try {
String pattern = properties.getRedis().getKeyPrefix() + "*";
Set<String> keys = stringRedisTemplate.keys(pattern);

int totalCount = keys != null ? keys.size() : 0;
int successCount = 0;
int failureCount = 0;
int processingCount = 0;

if (keys != null && !keys.isEmpty()) {
for (String key : keys) {
try {
String dataJson = stringRedisTemplate.opsForValue().get(key);
if (dataJson != null) {
IdempotencyData data = JSON.parseObject(dataJson, IdempotencyData.class);

if (!data.isExpired()) {
switch (data.getStatus()) {
case "SUCCESS":
successCount++;
break;
case "FAILURE":
failureCount++;
break;
case "PROCESSING":
processingCount++;
break;
}
}
}
} catch (Exception e) {
log.warn("获取Redis统计数据异常,键: {}", key, e);
}
}
}

statistics.put("totalCount", totalCount);
statistics.put("successCount", successCount);
statistics.put("failureCount", failureCount);
statistics.put("processingCount", processingCount);

log.info("Redis幂等性数据统计获取成功,总条数: {}, 成功: {}, 失败: {}, 处理中: {}",
totalCount, successCount, failureCount, processingCount);

} catch (Exception e) {
log.error("获取Redis幂等性数据统计异常", e);
}

return statistics;
}

/**
* 构建Redis键
* @param idempotencyKey 幂等性键
* @return Redis键
*/
private String buildKey(String idempotencyKey) {
return properties.getRedis().getKeyPrefix() + idempotencyKey;
}
}

4. 幂等性控制器

4.1 幂等性REST控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
/**
* 幂等性REST控制器
* @author Java实战
*/
@RestController
@RequestMapping("/api/idempotency")
@Slf4j
public class IdempotencyController {

@Autowired
private IdempotencyService idempotencyService;

@Autowired
private IdempotencyMonitorService idempotencyMonitorService;

/**
* 执行幂等性操作
* @param request 幂等性请求
* @return 操作结果
*/
@PostMapping("/execute")
public ResponseEntity<IdempotencyResult> executeIdempotentOperation(@RequestBody IdempotencyRequest request) {
try {
log.info("接收到幂等性操作请求,幂等性键: {}", request.getIdempotencyKey());

// 参数验证
if (request.getIdempotencyKey() == null || request.getIdempotencyKey().isEmpty()) {
return ResponseEntity.badRequest().build();
}

if (request.getBusinessKey() == null || request.getBusinessKey().isEmpty()) {
return ResponseEntity.badRequest().build();
}

IdempotencyResult result = idempotencyService.executeIdempotentOperation(
request.getIdempotencyKey(),
request.getBusinessKey(),
request.getRequestParams(),
request.getOperation(),
request.getUserContext()
);

return ResponseEntity.ok(result);

} catch (Exception e) {
log.error("幂等性操作执行失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* 检查幂等性
* @param idempotencyKey 幂等性键
* @return 是否已存在
*/
@GetMapping("/check")
public ResponseEntity<Boolean> checkIdempotency(@RequestParam String idempotencyKey) {
try {
log.info("接收到检查幂等性请求,幂等性键: {}", idempotencyKey);

if (idempotencyKey == null || idempotencyKey.isEmpty()) {
return ResponseEntity.badRequest().build();
}

boolean exists = idempotencyService.checkIdempotency(idempotencyKey);

return ResponseEntity.ok(exists);

} catch (Exception e) {
log.error("检查幂等性失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* 获取幂等性数据
* @param idempotencyKey 幂等性键
* @return 幂等性数据
*/
@GetMapping("/data")
public ResponseEntity<IdempotencyData> getIdempotencyData(@RequestParam String idempotencyKey) {
try {
log.info("接收到获取幂等性数据请求,幂等性键: {}", idempotencyKey);

if (idempotencyKey == null || idempotencyKey.isEmpty()) {
return ResponseEntity.badRequest().build();
}

IdempotencyData data = idempotencyService.getIdempotencyData(idempotencyKey);

if (data != null) {
return ResponseEntity.ok(data);
} else {
return ResponseEntity.notFound().build();
}

} catch (Exception e) {
log.error("获取幂等性数据失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* 删除幂等性数据
* @param idempotencyKey 幂等性键
* @return 是否成功
*/
@DeleteMapping("/data")
public ResponseEntity<Boolean> deleteIdempotencyData(@RequestParam String idempotencyKey) {
try {
log.info("接收到删除幂等性数据请求,幂等性键: {}", idempotencyKey);

if (idempotencyKey == null || idempotencyKey.isEmpty()) {
return ResponseEntity.badRequest().build();
}

boolean success = idempotencyService.deleteIdempotencyData(idempotencyKey);

return ResponseEntity.ok(success);

} catch (Exception e) {
log.error("删除幂等性数据失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* 批量检查幂等性
* @param request 批量检查请求
* @return 已存在的幂等性键集合
*/
@PostMapping("/batch-check")
public ResponseEntity<Set<String>> batchCheckIdempotency(@RequestBody BatchCheckRequest request) {
try {
log.info("接收到批量检查幂等性请求,数量: {}", request.getIdempotencyKeys().size());

if (request.getIdempotencyKeys() == null || request.getIdempotencyKeys().isEmpty()) {
return ResponseEntity.badRequest().build();
}

Set<String> existingKeys = idempotencyService.batchCheckIdempotency(request.getIdempotencyKeys());

return ResponseEntity.ok(existingKeys);

} catch (Exception e) {
log.error("批量检查幂等性失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* 获取幂等性状态
* @return 幂等性状态
*/
@GetMapping("/status")
public ResponseEntity<IdempotencyStatus> getIdempotencyStatus() {
try {
log.info("接收到获取幂等性状态请求");

IdempotencyStatus status = idempotencyService.getIdempotencyStatus();

return ResponseEntity.ok(status);

} catch (Exception e) {
log.error("获取幂等性状态失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* 获取幂等性监控指标
* @return 监控指标
*/
@GetMapping("/metrics")
public ResponseEntity<IdempotencyMetrics> getIdempotencyMetrics() {
try {
IdempotencyMetrics metrics = idempotencyMonitorService.getMetrics();
return ResponseEntity.ok(metrics);
} catch (Exception e) {
log.error("获取幂等性监控指标失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* 清理过期数据
* @return 清理的数据条数
*/
@PostMapping("/cleanup")
public ResponseEntity<Integer> cleanupExpiredData() {
try {
log.info("接收到清理过期数据请求");

int cleanedCount = idempotencyService.cleanupExpiredData();

return ResponseEntity.ok(cleanedCount);

} catch (Exception e) {
log.error("清理过期数据失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

/**
* 健康检查
* @return 健康状态
*/
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> healthCheck() {
try {
Map<String, Object> health = new HashMap<>();

IdempotencyStatus status = idempotencyService.getIdempotencyStatus();

health.put("status", status != null && status.isHealthy() ? "UP" : "DOWN");
health.put("timestamp", System.currentTimeMillis());
health.put("details", status);

return ResponseEntity.ok(health);

} catch (Exception e) {
log.error("健康检查失败", e);

Map<String, Object> health = new HashMap<>();
health.put("status", "DOWN");
health.put("timestamp", System.currentTimeMillis());
health.put("error", e.getMessage());

return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(health);
}
}
}

/**
* 幂等性请求类
* @author Java实战
*/
@Data
public class IdempotencyRequest {

/**
* 幂等性键
*/
@NotBlank(message = "幂等性键不能为空")
@Size(max = 128, message = "幂等性键长度不能超过128个字符")
private String idempotencyKey;

/**
* 业务键
*/
@NotBlank(message = "业务键不能为空")
@Size(max = 64, message = "业务键长度不能超过64个字符")
private String businessKey;

/**
* 请求参数
*/
private String requestParams;

/**
* 操作函数
*/
@JsonIgnore
private Function<String, Object> operation;

/**
* 过期时间(秒)
*/
@Min(value = 1, message = "过期时间必须大于0")
@Max(value = 86400, message = "过期时间不能超过24小时")
private Long expireTime;

/**
* 用户上下文
*/
private UserContext userContext;

/**
* 扩展属性
*/
private Map<String, Object> extendedProperties;

public IdempotencyRequest() {
this.extendedProperties = new HashMap<>();
}
}

/**
* 批量检查请求类
* @author Java实战
*/
@Data
public class BatchCheckRequest {

/**
* 幂等性键列表
*/
@NotEmpty(message = "幂等性键列表不能为空")
@Size(max = 100, message = "批量检查数量不能超过100个")
private List<String> idempotencyKeys;

/**
* 用户上下文
*/
private UserContext userContext;

/**
* 扩展属性
*/
private Map<String, Object> extendedProperties;

public BatchCheckRequest() {
this.extendedProperties = new HashMap<>();
}
}

5. 总结

5.1 幂等性最佳实践

  1. 合理设计幂等性键: 使用业务唯一标识作为幂等性键
  2. 选择合适的存储方案: 根据业务需求选择Redis或数据库
  3. 设置合理的过期时间: 避免数据无限增长
  4. 实现分布式锁: 确保并发安全
  5. 完善监控告警: 实时监控幂等性状态

5.2 性能优化建议

  • 缓存优化: 合理使用Redis缓存
  • 数据库优化: 优化数据库查询和索引
  • 锁优化: 减少锁持有时间
  • 批量处理: 支持批量幂等性操作
  • 异步处理: 异步处理非关键路径

5.3 运维管理要点

  • 实时监控: 监控幂等性状态和性能
  • 数据清理: 定期清理过期数据
  • 故障处理: 建立完善的故障处理机制
  • 日志管理: 完善日志记录和分析
  • 性能调优: 根据监控数据优化性能

通过本文的幂等性解决方案Java实战指南,您可以掌握幂等性的原理、实现方案、性能优化技巧以及在企业级应用中的最佳实践,构建高效、可靠的幂等性系统!