在分布式系统开发中,从缓存获取变量时经常会遇到缓存未命中、缓存服务异常等情况,需要设计合理的兜底逻辑保证业务正常运行。Optional是Java 8引入的用于处理空值的工具类,其中的orElseGet方法可以在值为空时执行自定义的逻辑获取默认值,非常适合用来实现缓存获取的兜底场景。

Optional.orElseGet方法基础说明
Optional类的orElseGet方法接收一个Supplier函数式接口作为参数,当Optional内部包裹的对象为空时,会执行Supplier的get方法返回兜底值;如果对象不为空,则直接返回该对象,不会执行Supplier的逻辑。
和基础兜底方法orElse的区别在于,orElse无论Optional是否有值都会创建兜底对象,而orElseGet只有在需要兜底时才会执行创建逻辑,在兜底逻辑成本较高时性能更优。
分布式缓存获取变量的常见场景
常见的分布式缓存比如Redis,我们通常会封装一个缓存操作工具类,提供获取缓存值的方法。从缓存获取变量的典型流程如下:
- 尝试从缓存中查询目标key对应的变量值
- 如果缓存命中且值有效,直接返回该值
- 如果缓存未命中,或者缓存服务出现异常,执行兜底逻辑,比如从数据库查询默认值、返回预设的静态默认值等
结合Optional.orElseGet的实现步骤
1. 封装缓存查询方法返回Optional对象
首先我们需要将缓存查询的返回结果包装成Optional,避免直接返回null导致空指针问题。以下是基于Redis缓存的查询封装示例:
import java.util.Optional;
import redis.clients.jedis.Jedis;
public class RedisCacheUtil {
private Jedis jedis;
public RedisCacheUtil(Jedis jedis) {
this.jedis = jedis;
}
// 从缓存获取字符串值,返回Optional包装的结果
public Optional<String> getStringFromCache(String key) {
try {
String value = jedis.get(key);
// 如果值不为空且不是空字符串,返回包含值的Optional,否则返回空Optional
if (value != null && !value.isEmpty()) {
return Optional.of(value);
}
return Optional.empty();
} catch (Exception e) {
// 缓存服务异常时返回空Optional,走兜底逻辑
return Optional.empty();
}
}
}
2. 使用orElseGet实现兜底逻辑
在业务代码中调用缓存查询方法,通过orElseGet传入兜底逻辑,实现缓存未命中或异常时的默认处理:
public class UserService {
private RedisCacheUtil redisCacheUtil;
// 模拟数据库查询的DAO
private UserDao userDao;
public UserService(RedisCacheUtil redisCacheUtil, UserDao userDao) {
this.redisCacheUtil = redisCacheUtil;
this.userDao = userDao;
}
// 获取用户昵称,优先从缓存获取,缓存没有则从数据库查询
public String getUserNickname(Long userId) {
String cacheKey = "user:nickname:" + userId;
// 调用缓存查询方法,返回Optional对象
Optional<String> cacheResult = redisCacheUtil.getStringFromCache(cacheKey);
// 使用orElseGet实现兜底:缓存有值直接返回,没有则执行Supplier从数据库查询
return cacheResult.orElseGet(() -> {
// 兜底逻辑:从数据库查询用户昵称
String nickname = userDao.queryNicknameByUserId(userId);
// 如果数据库也没查到,返回默认昵称
if (nickname == null || nickname.isEmpty()) {
nickname = "默认用户";
}
// 可选:将查询到的结果回写缓存,下次可以直接命中
redisCacheUtil.jedis.setex(cacheKey, 3600, nickname);
return nickname;
});
}
}
3. 兜底逻辑的优化建议
在实际使用中,兜底逻辑可以根据业务需求做进一步优化:
- 兜底逻辑中添加重试机制,避免缓存短暂故障导致的无效兜底
- 如果兜底逻辑本身也可能抛出异常,可以在Supplier内部做异常捕获,返回更基础的静态默认值
- 对于高频访问的变量,可以在兜底逻辑中增加本地缓存,减少数据库等底层存储的压力
注意事项
使用orElseGet实现缓存兜底时需要注意以下几点:
- 确保缓存查询方法返回的Optional逻辑正确,只有真正需要兜底的场景才返回空Optional,避免误触发兜底逻辑
- 兜底逻辑的执行成本较高时,
orElseGet的性能优势才会体现,如果兜底只是返回一个简单的静态值,使用orElse也是可以的 - 如果缓存操作和兜底逻辑涉及多线程场景,需要注意资源的线程安全问题,比如Redis连接的获取和释放
这种实现方式将缓存查询和兜底逻辑解耦,代码可读性更高,也便于后续对缓存策略和兜底规则做单独调整,适合大多数分布式缓存的变量获取场景。