前言
本地缓存和业务请求在同一台机器上,相对于Redis读写速度非常快,对于一些变更频率低、实时性要求低的数据,可以放在本地缓存中,提升访问速度。 使用本地缓存能够减少和Redis类集中式缓存间的数据交互,减少网络I/O开销,降低这一过程中在网络通信上的耗时,同时减轻对Redis类集中式缓存访问压力。
解决方案
服务是多节点部署的,要保证是本地缓存一致性的,就要短时间内操作所有服务。
方案1-MQ广播消息
redis发布订阅功能实现同理
方案2-Zookeeper Watcher机制
Tomcat Watcher监听
@Service
public class ZkDataListenerImpl implements IZkDataListener {
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
String type = getType(data);
if (type.equals("update")) {
//更新缓存
}
if (type.equals("delete")) {
//删除缓存
}
}
}
方案3-RPC框架广播调用(如dubbo广播调用方式)
<dubbo:service cluster="broadcast" />
实现逻辑
- 循环调用所有的实例
- 如果有发生异常则记录异常保存
- 只要有异常,则抛出异常,如果没有则返回执行结果
源码
public class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T> {
private static final Logger logger = LoggerFactory.getLogger(BroadcastClusterInvoker.class);
public BroadcastClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
// 检查 invokers 是否为空
checkInvokers(invokers, invocation);
// 将invokers 塞到context中
RpcContext.getContext().setInvokers((List) invokers);
RpcException exception = null;
Result result = null;
// 遍历invokers 执行, 结果只要最后一个不报错的result
for (Invoker<T> invoker : invokers) {
try {
result = invoker.invoke(invocation);
} catch (RpcException e) {
exception = e;
logger.warn(e.getMessage(), e);
} catch (Throwable e) {
exception = new RpcException(e.getMessage(), e);
logger.warn(e.getMessage(), e);
}
}
// 如果出现一个异常, 抛出异常
if (exception != null) {
throw exception;
}
return result;
}
}
方案4-分布式任务调度的广播执行任务
广播执行表示一个任务实例会广播到该分组所有Worker上执行,当所有Worker都执行完成。
方案对比
方案 | 特点 |
---|---|
MQ广播消息 | 有消息积压、消息顺序的问题 |
Zookeeper Watcher机制 | Zookeeper本身适合读多写少的场景 |
RPC框架广播调用(如dubbo广播调用方式) | 循环调用所有的实例,所有要考虑实例过多的情况 |
分布式任务调度的广播执行任务 | 便于定时发布 |