本文目录导读:

- 分布式缓存中的“空对象” (避免缓存穿透)
- 分布式RPC调用中的空对象 (避免NPE)
- 分布式数据库查询中的空对象 (避免多次IO)
- 分布式消息队列中的空对象 (消息消重)
- 分布式配置中心中的空对象 (默认配置)
- 分布式空对象模式的3个核心点
在Java分布式系统中,“空对象模式”和“空对象”的概念需要根据上下文区分,下面从分布式场景下的不同维度来解答,并提供代码示例。
分布式缓存中的“空对象” (避免缓存穿透)
这是最常见的场景,当查询一个不存在的数据时,如果不做处理,会直接穿透缓存打到数据库,此时将“查询为空”的结果也缓存起来(空对象),防止反复击穿。
示例 (Redis + JSON序列化)
// 假设查询用户信息
public User getUserById(String userId) {
// 1. 先从缓存查
String cachedValue = redisClient.get("user:" + userId);
if (cachedValue != null) {
// 关键:反序列化成User,但要能判断是“空对象”
User user = JsonUtil.parse(cachedValue, User.class);
if (user.isNullObject()) { // 自定义标记
return null; // 代表数据库没数据
}
return user;
}
// 2. 缓存没有,查数据库
User user = userDao.findById(userId);
// 3. 无论是否存在,都存入缓存(空对象机制)
if (user == null) {
// 存入一个标记空的对象,并设置较短过期时间(比如5分钟)
User empty = User.createEmpty(); // isNullObject() 返回true
redisClient.setex("user:" + userId, 300, JsonUtil.toJson(empty));
} else {
redisClient.setex("user:" + userId, 3600, JsonUtil.toJson(user));
}
return user;
}
User类设计:
public class User implements Serializable {
private String id;
private String name;
// ... 其他字段
private boolean nullObject = false; // 默认false
public static User createEmpty() {
User empty = new User();
empty.nullObject = true;
// 可以设置一些默认ID如 "__NULL__"
empty.id = "__NULL__";
return empty;
}
public boolean isNullObject() {
return nullObject;
}
}
分布式RPC调用中的空对象 (避免NPE)
在分布式服务调用时,如果某个服务返回null,调用方频繁判空,使用空对象模式返回一个“默认行为”的对象,避免NullPointerException。
典型场景:服务降级/熔断
// 定义一个远程调用接口
public interface InventoryService {
InventoryInfo getInventory(String skuId);
}
// 空对象实现
public class NullInventoryInfo extends InventoryInfo {
@Override
public int getAvailableQuantity() {
return 0; // 或者返回 -1 表示未知
}
@Override
public boolean isAvailable() {
return false;
}
@Override
public String getWarehouseId() {
return "UNKNOWN";
}
}
// 调用方使用(配合Hystrix/Sentinel)
public class InventoryClient {
public InventoryInfo getInventory(String skuId) {
try {
InventoryInfo info = remoteService.getInventory(skuId);
if (info == null) {
return new NullInventoryInfo(); // 返回空对象,而不是null
}
return info;
} catch (Exception e) {
log.error("调用库存服务失败, skuId={}", skuId, e);
return new NullInventoryInfo(); // 熔断降级时返回空对象
}
}
}
分布式数据库查询中的空对象 (避免多次IO)
在分布式数据库(如HBase、Cassandra、MongoDB分片)查询时,如果某行不存在,可以返回一个轻量级空对象标记,避免后续重复查询。
示例 (MongoDB + 本地缓存)
public class DocumentFetcher {
private Cache<String, Document> localCache;
private MongoCollection<Document> collection;
public Document getDocument(String key) {
// 先查本地缓存
Document cached = localCache.getIfPresent(key);
if (cached != null) {
// 检查是否是空标记
if (cached.containsKey("__null__")) {
return null;
}
return cached;
}
// 查分布式数据库
Document doc = collection.find(Filters.eq("_id", key)).first();
if (doc == null) {
// 存入一个空对象标记到本地缓存(短暂过期)
Document emptyMarker = new Document("__null__", true);
localCache.put(key, emptyMarker, 60, TimeUnit.SECONDS);
return null;
}
localCache.put(key, doc, 300, TimeUnit.SECONDS);
return doc;
}
}
分布式消息队列中的空对象 (消息消重)
当消费消息时,用空对象表示“消息已处理但结果为空”,结合Redis setnx实现幂等。
@KafkaListener(topics = "order-events")
public void handleOrderEvent(OrderEvent event) {
String eventId = event.getEventId();
// 用空对象标记已处理
String processedKey = "processed:" + eventId;
Boolean set = redisTemplate.opsForValue().setIfAbsent(processedKey, "__PROCESSED_EMPTY__", 24, TimeUnit.HOURS);
if (!set) {
// 已经处理过,跳过
return;
}
try {
// 实际业务处理
OrderResult result = processOrder(event);
if (result == null) {
// 结果为空,但已经标记了“已处理”
log.info("订单事件处理结果为空, eventId={}", eventId);
}
} catch (Exception e) {
// 处理失败,删除标记让其可以重试
redisTemplate.delete(processedKey);
throw e;
}
}
分布式配置中心中的空对象 (默认配置)
使用Apollo/Nacos配置中心时,有些配置可能不存在,定义空配置对象,避免频繁远程拉取。
@Component
public class DynamicConfig {
// 空配置对象
private static final ConfigValue EMPTY_CONFIG = new ConfigValue() {
@Override
public String getString(String key) {
return null;
}
@Override
public int getInt(String key, int defaultValue) {
return defaultValue;
}
@Override
public boolean exists() {
return false;
}
};
private ConfigValue currentConfig;
@PostConstruct
public void init() {
ConfigChangeListener listener = changeEvent -> {
// 配置变更时更新
refreshConfig();
};
configService.addChangeListener(listener);
refreshConfig();
}
private void refreshConfig() {
ConfigValue cfg = configService.getConfig("myApp");
if (cfg == null) {
this.currentConfig = EMPTY_CONFIG; // 使用空对象
} else {
this.currentConfig = cfg;
}
}
public int getTimeout() {
return currentConfig.getInt("timeout", 5000);
}
}
分布式空对象模式的3个核心点
| 维度 | 作用 | 实现方式 |
|---|---|---|
| 缓存穿透 | 缓存不存在的key,防止打穿DB | 缓存一个标记空的对象(短TTL) |
| RPC降级 | 避免频繁if null + 提供默认行为 | 实现接口的空对象子类(NullPattern) |
| 幂等/重复 | 标记已处理的空结果 | Redis setnx 标记 + 短过期时间 |
关键设计原则:
- 空对象必须有明确的标记(如
isNullObject(),__NULL__) - 空对象的过期时间要小于正常数据(防止长期占用内存)
- 空对象要轻量,只存必要字段,避免序列化开销
- 结合分布式锁/幂等标记,避免并发场景下重复创建空对象
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播