手写RPC框架

RPC框架核心组件

对于RPC框架简洁模式下,主要有以下角色,暂且抛开心跳机制以及负载均衡等复杂策略,我们先来自己实现一个RPC框架,后面我们再深入理解。
在这里插入图片描述

注册中心

RegisterServiceVo

package com.cover.rpc.remote.vo;import java.io.Serializable;// 注册中心注册服务的实体类
public class RegisterServiceVo implements Serializable {// 服务提供者的ip地址private final String host;// 服务提供者的端口private final int port;public RegisterServiceVo(String host, int port) {this.host = host;this.port = port;}public String getHost() {return host;}public int getPort() {return port;}
}

RegisterCenter

package com.cover.rpc.rpc.reg.service;import org.springframework.stereotype.Component;
import com.cover.rpc.remote.vo.RegisterServiceVo;import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;// 服务注册中心,服务提供者在启动时需要注册中心登记自己的信息
@Component
public class RegisterCenter {// key表示服务名,value代表private static final Map<String, Set<RegisterServiceVo>> serviceHolder = new HashMap<>();// 注册服务端口号private int port;/*** 服务注册,考虑到可能有多个提供者同时注册,进行加索*/private static synchronized void registerService(String serviceName, String host, int port) {// 获得当前服务的已有地址集合Set<RegisterServiceVo> serviceVoSet = serviceHolder.get(serviceName);if (serviceVoSet == null) {// 已有地址集合为空,新增集合serviceVoSet = new HashSet<>();serviceHolder.put(serviceName, serviceVoSet);}// 将新的服务提供者加入集合serviceVoSet.add(new RegisterServiceVo(host,port));System.out.println("服务已注册[" + serviceName + "]," + "地址[ " + host + "], 端口[" + port + "]" );}/*** 取出服务提供者*/private static Set<RegisterServiceVo> getService(String serviceName) {return serviceHolder.get(serviceName);}/*** 处理服务请求的任务,无非就是两种服务* 1.服务注册服务* 2.服务查询服务*/private static class ServerTask implements Runnable {private Socket client = null;public ServerTask(Socket client) {this.client = client;}@Overridepublic void run() {try (ObjectInputStream inputStream = new ObjectInputStream(client.getInputStream());ObjectOutputStream outputStream = new ObjectOutputStream(client.getOutputStream())) {// 检查当前请求是注册服务还是获取服务boolean isGetService = inputStream.readBoolean();// 服务查询服务,获取服务提供者if (isGetService) {String serviceName = inputStream.readUTF();// 取出服务提供者Set<RegisterServiceVo> result = getService(serviceName);// 返回给客户端outputStream.writeObject(result);outputStream.flush();System.out.println("将已注册的服务[" + serviceName + "提供给客户端");} else {// 服务注册服务//取得新服务提供方的ip和端口String serviceName = inputStream.readUTF();String host = inputStream.readUTF();int port = inputStream.readInt();// 注册中心保存registerService(serviceName, host, port);outputStream.writeBoolean(true);outputStream.flush();}} catch (IOException e) {throw new RuntimeException(e);} finally {try {client.close();} catch (IOException e) {e.printStackTrace();
//                    throw new RuntimeException(e);}}}}// 启动注册服务public void startService() throws IOException {ServerSocket serverSocket = new ServerSocket();serverSocket.bind(new InetSocketAddress(port));System.out.println("服务注册中心 on :" + port + ": 运行");try {while (true) {new Thread(new ServerTask(serverSocket.accept())).start();}} finally {serverSocket.close();}}@PostConstructpublic void init() {this.port = 9999;new Thread(new Runnable() {@Overridepublic void run() {try {startService();} catch (IOException e) {e.printStackTrace();}}}).start();}
}

文件结构
在这里插入图片描述

服务提供者

UserInfo

package com.cover.rpc.remote.vo;import java.io.Serializable;public class UserInfo implements Serializable {private final String name;private final String phone;public UserInfo(String name, String phone) {this.name = name;this.phone = phone;}public String getName() {return name;}public String getPhone() {return phone;}
}

SendSms

package com.cover.rpc.remote;import com.cover.rpc.remote.vo.UserInfo;// 短信息发送接口
public interface SendSms {boolean sendMail(UserInfo userInfo);
}

RegisterServiceWithRegCenter

package com.cover.rpc.rpc.base;import org.springframework.stereotype.Service;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;// 注册服务,引入了服务的注册和发现机制
@Service
public class RegisterServiceWithRegCenter {// 本地提供服务的一个名单,用缓存实现private static final Map<String, Class> serviceCache = new ConcurrentHashMap<>();// 往远程注册服务器注册本服务,同时在本地注册本服务public void regRemote(String serviceName, String host, int port, Class impl) throws IOException {// 登记到注册中心Socket socket = null;ObjectOutputStream output = null;ObjectInputStream input = null;try {socket = new Socket();socket.connect(new InetSocketAddress("127.0.0.1", 9999));output = new ObjectOutputStream(socket.getOutputStream());// 注册服务output.writeBoolean(false);// 提供的服务名output.writeUTF(serviceName);// 服务提供方的IPoutput.writeUTF(host);// 服务提供方的端口output.writeInt(port);output.flush();input = new ObjectInputStream(socket.getInputStream());if (input.readBoolean()) {System.out.println("服务[" + serviceName + "]注册成功!");}// 可提供服务放入本地缓存serviceCache.put(serviceName, impl);} catch (Exception e) {e.printStackTrace();if (socket != null) {socket.close();}if (output != null) {output.close();}if (input != null) {input.close();}}}// 获取服务public Class getLocalService(String serviceName) {return serviceCache.get(serviceName);}
}

RpcServerFrame

package com.cover.rpc.rpc.base;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;// RPC框架的服务端部分
@Service
public class RpcServerFrame {@Autowiredprivate RegisterServiceWithRegCenter registerServiceWithRegCenter;// 服务的端口号private int port;// 处理服务请求任务private static class ServerTask implements Runnable {private Socket socket;private RegisterServiceWithRegCenter registerServiceWithRegCenter;public ServerTask(Socket client, RegisterServiceWithRegCenter registerServiceWithRegCenter) {this.socket = client;this.registerServiceWithRegCenter = registerServiceWithRegCenter;}@Overridepublic void run() {try (ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())) {// 方法所在类名接口名String serviceName = inputStream.readUTF();// 方法的名字String methodName = inputStream.readUTF();// 方法的入参  类型Class<?>[] paramTypes = (Class<?>[]) inputStream.readObject();// 方法的入参的值Object[] args = (Object[]) inputStream.readObject();// 从容器中拿到服务的Class对象Class serviceClass = registerServiceWithRegCenter.getLocalService(serviceName);if (serviceClass == null) {throw new ClassNotFoundException(serviceName + "not found");}// 通过反射,执行实际的服务Method method = serviceClass.getMethod(methodName, paramTypes);Object result = method.invoke(serviceClass.newInstance(), args);// 将服务的执行结果通知调用者outputStream.writeObject(result);outputStream.flush();} catch (Exception e) {e.printStackTrace();} finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}public void startService(String serviceName, String host, int port, Class impl) throws IOException {ServerSocket serverSocket = new ServerSocket();serverSocket.bind(new InetSocketAddress(port));System.out.println("RPC server on :" + port + ":运行");registerServiceWithRegCenter.regRemote(serviceName, host, port, impl);try {while (true) {new Thread(new ServerTask(serverSocket.accept(), registerServiceWithRegCenter)).start();}} finally {serverSocket.close();}}
}

SendSmsImpl

package com.cover.rpc.rpc.sms;import com.cover.rpc.remote.SendSms;
import com.cover.rpc.remote.vo.UserInfo;public class SendSmsImpl implements SendSms {@Overridepublic boolean sendMail(UserInfo userInfo) {try {Thread.sleep(50);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("已发送短信息给 :" + userInfo.getName() + "到[" + userInfo.getPhone());return true;}
}

SmsRpcServer

package com.cover.rpc.rpc.sms;import com.cover.rpc.remote.SendSms;
import com.cover.rpc.rpc.base.RpcServerFrame;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Random;@Service
public class SmsRpcServer {@Autowiredprivate RpcServerFrame rpcServerFrame;@PostConstructpublic void server() throws IOException {Random r = new Random();int port = 8778 + r.nextInt(100);rpcServerFrame.startService(SendSms.class.getName(), "127.0.0.1", port, SendSmsImpl.class);}
}

文件结构
在这里插入图片描述

服务消费者

BeanConfig

package com.cover.rpc.client.config;import com.cover.rpc.client.rpc.RpcClientFrame;
import com.cover.rpc.remote.SendSms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.io.IOException;@Configuration
public class BeanConfig {@Autowiredprivate RpcClientFrame rpcClientFrame;@Beanpublic SendSms getSmsService() throws IOException, ClassNotFoundException {return rpcClientFrame.getRemoteProxyObject(SendSms.class);}}

RpcClientFrame

package com.cover.rpc.client.rpc;import com.cover.rpc.remote.vo.RegisterServiceVo;
import org.springframework.stereotype.Service;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;// RPC框架的客户端代理部分
@Service
public class RpcClientFrame {// 远程服务的代理对象,参数为客户端要调用的服务public static <T> T getRemoteProxyObject(final Class<?> serviceInterface) throws IOException,ClassNotFoundException {// 获取远程服务的一个网络地址InetSocketAddress addr = getService(serviceInterface.getName());// 拿到一个代理对象,由这个代理对象通过网络进行实际的服务调用    return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface},new DynProxy(serviceInterface, addr));}// 动态代理,实现对远程服务的访问private static class DynProxy implements InvocationHandler {private Class<?> serviceInterface;private InetSocketAddress addr;public DynProxy(Class<?> serviceInterface, InetSocketAddress addr) {this.serviceInterface = serviceInterface;this.addr = addr;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Socket socket = null;ObjectInputStream inputStream = null;ObjectOutputStream outputStream = null;try {socket = new Socket();socket.connect(addr);outputStream = new ObjectOutputStream(socket.getOutputStream());// 方法所在类名接口名outputStream.writeUTF(serviceInterface.getName());// 方法名outputStream.writeUTF(method.getName());// 方法入参类型outputStream.writeObject(method.getParameterTypes());// 方法入参的值outputStream.writeObject(args);outputStream.flush();inputStream = new ObjectInputStream(socket.getInputStream());// 接受服务器的输出System.out.println(serviceInterface + " remote exec susccess!");return inputStream.readObject();} catch (Exception e) {e.printStackTrace();} finally {if (socket != null) {socket.close();}if (outputStream != null) {outputStream.close();}if (inputStream != null) {inputStream.close();}}return null;}}//------------------------------以下和动态获得服务提供者有关private static Random r = new Random();// 获取远程服务的地址private static InetSocketAddress getService(String serviceName) throws IOException, ClassNotFoundException {// 获得服务提供者的地址列表List<InetSocketAddress> serviceList = getServiceList(serviceName);System.out.println("serviceList =" + serviceList.toString());InetSocketAddress addr = serviceList.get(r.nextInt(serviceList.size()));System.out.println("本次选择了服务器: " + addr);return addr;}private static List<InetSocketAddress> getServiceList(String serviceName) throws IOException,ClassNotFoundException {Socket socket = null;ObjectOutputStream output = null;ObjectInputStream input = null;List<InetSocketAddress> services = new ArrayList<>();try {socket = new Socket();socket.connect(new InetSocketAddress("127.0.0.1", 9999));output = new ObjectOutputStream(socket.getOutputStream());// 需要获得服务提供者output.writeBoolean(true);// 告诉注册中心服务名output.writeUTF(serviceName);output.flush();input = new ObjectInputStream(socket.getInputStream());Object object = input.readObject();System.out.println("从注册中心读取到的数据是" + object.toString());Set<RegisterServiceVo> result = (Set<RegisterServiceVo>) object;for (RegisterServiceVo serviceVo : result) {// 获取服务提供者String host = serviceVo.getHost();int port = serviceVo.getPort();InetSocketAddress serviceAddr = new InetSocketAddress(host, port);services.add(serviceAddr);}System.out.println("获得服务[" + serviceName + "] 提供者的地址列表[" + services + "],准备调用");return services;} catch (Exception e) {e.printStackTrace();} finally {if (socket != null) {socket.close();}if (output != null) {output.close();}if (input != null) {input.close();}}return services;}
}

NormalBusi

package com.cover.rpc.client.service;import org.springframework.stereotype.Service;/*** @author xieh* @date 2024/02/03 17:46*/
@Service
public class NormalBusi {public void business() {System.out.println("其他的业务操作。。。。");}
}

RegisterServiceVo

package com.cover.rpc.remote.vo;import java.io.Serializable;// 注册中心注册服务的实体类
public class RegisterServiceVo implements Serializable {// 服务提供者的ip地址private final String host; // 服务提供者端口private final int port;public RegisterServiceVo (String host, int port) {this.host = host;this.port = port;}public String getHost() {return host;}public int getPort() {return port;}
}

UserInfo

package com.cover.rpc.remote.vo;import java.io.Serializable;/*** @author xieh* @date 2024/02/03 17:48*/
public class UserInfo implements Serializable {private final String name;private final String phone;public UserInfo(String name, String phone) {this.name = name;this.phone = phone;}public String getName() {return name;}public String getPhone() {return phone;}
}

SendSms

package com.cover.rpc.remote;import com.cover.rpc.remote.vo.UserInfo;/*** @author xieh* @date 2024/02/03 17:47*/
// 短信发送接口
public interface SendSms {boolean sendMail(UserInfo userInfo);
}

RpcClientApplicationTests

package com.example.rpcclient;import com.cover.rpc.client.service.NormalBusi;
import com.cover.rpc.remote.SendSms;
import com.cover.rpc.remote.vo.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class RpcClientApplicationTests {@Testvoid contextLoads() {}@Autowiredprivate NormalBusi normalBusi;@Autowiredprivate SendSms sendSms;//    @Test
//    void contextLoads() {
//
//    }@Testpublic void rpcTest() {long start = System.currentTimeMillis();normalBusi.business();// 发送邮件UserInfo userInfo = new UserInfo("Cover", "181");System.out.println("Send mail" + sendSms.sendMail(userInfo));System.out.println("共耗时:" + (System.currentTimeMillis() - start));}
}

文件结构
在这里插入图片描述

结果展示

服务注册中心
在这里插入图片描述

服务消费者
在这里插入图片描述
服务提供者
在这里插入图片描述

总结分析

1.我们在当前项目中的序列化框架选择是JDK自带的序列化,注意,你这个时候不能给上面提到的实体类添加唯一的serialId,否则通信过程中则将视为不一样的对象,导致序列化失败,还有就是要注意自己的目录结构,因为如果客户端和服务端中的实体类目录结构不一样,也是不行的,在实际业务中,往往会抽成一个公共的服务来使用,这里为了简洁
2.网络通信模型采用的也是JDK自带的Socket模式,它是阻塞式的,它式无法支撑高并发的网络连接的

如果需要这个项目源码的,可以在下方评论

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/665514.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【PCL】(九)点云体素下采样

&#xff08;九&#xff09;Filtering 体素下采样 点云样例&#xff1a; https://raw.github.com/PointCloudLibrary/data/master/tutorials/table_scene_lms400.pcd 以下程序实现对读取的点云进行体素下采样&#xff0c;并将得到的点云保存。 voxel_grid.cpp #include <…

CSS是一门需要单独学习的技术吗?

CSS (Cascading Style Sheets) &#xff0c;做前端开发的人都很清楚&#xff0c;因为这是他们的一项必不可少的技能。我以前也是知道CSS&#xff0c;但从来没有单独学习过&#xff0c;认为就它只是用来渲染网页的表现层效果&#xff0c;定制页面和内元素的布局、颜色和字体等&a…

QT 应用中集成 Sentry

QT 应用中集成 Sentry QT应用中集成 SentrySentry SDK for C/C注册 Sentry 账号QT 应用中集成 Sentry触发 Crash 上报 QT应用中集成 Sentry Sentry 是一个开源的错误监控和日志记录平台&#xff0c;旨在帮助开发团队实时捕获、跟踪和解决软件应用程序中的错误和异常。它提供了…

【ADI 知识库】 AN-1354:集成式ZIF、RF至比特、LTE、广域接收机分析和测试结果

官方链接&#xff1a; https://www.analog.com/cn/resources/app-notes/an-1354.html 简介 本应用笔记参考了3GPP TS 36系列文件和ADI公司的多种数据手册、特性标定报告和实验室测试结果。本文重点关注基于集成式零中频(ZIF)、RF至比特、IC (AD9371)的多载波广域LTE接收机的性…

ICA:独立成分分析

1.意义 两个假设&#xff1a;一个是假设源信号是相互统计独立的,另一个是假设己知源信号的统计分布特征。 另一个假设是信号的非高斯性,现实世界的许多信号,诸如绝大多数的语音信号和图像信号即是服从非高斯分布的这个假设的可应用性,带来了独立成分分析的重要特征,即实际信号的…

关于在Tkinter + Pillow图片叠加中出现的问题

这段时间我一直在尝试对多图层图片进行一个叠加的操作&#xff0c;想用tkinter实现出来&#xff0c;先看错误 这里我其实已经选择了图片&#xff0c;但是发现是ValueError&#xff0c;我尝试断点检测但是也无动于衷&#xff0c;因为设置变量检测的时候发现变量并没有错误&…

【周总结】Programmer‘s weekend routine---First week of February

总结 工作&#xff1a; 参加项目重构方案讨论会议、个人任务计划分期以及工期安排、项目初步重构开发 日常&#xff1a;参加年会&#xff08;阳光普照奖都莫得&#xff09; 2024.2.3 阴 不冷 连着一周的雨&#xff0c;我那袜子挂两三天了还能挤出水。。离谱、莆…

linux 文件查看 head 、 cat 、 less 、tail 、grep

查看文件详细信息 stat 文件 cat 》》适合显示小文件【行数比较少】&#xff0c;如果行数较多&#xff0c;屏幕显示不完整&#xff08;如果虚拟操作&#xff0c;是无法上下键的&#xff0c;或者滚动鼠标的&#xff0c;第三方 xsheel&#xff0c;crt 可以方向键查看&#xf…

ep-bg-purple-dark element-plus 不生效

element-plus 官网里面的 Layout 布局中的示例&#xff0c;官方文档中添加类名 class"grid-content ep-bg-purple-dark" 有相应的样式 import element-plus/theme-chalk/index.css //默认css样式 英文 import element-plus/dist/index.css 两…

在Flutter中调用Android的代码

参考 【Flutter 混合开发】嵌入原生View-Android 默认使用Android studio 和 Kotlin 基本配置 创建flutter项目 在终端执行 flutter create batterylevel添加 Android 平台的实现 打开项目下的android/app/src/main/kotlin 下的 MainActivity.kt 文件。 我这里编辑器有…

人工智能深度学习发展历程-纪年录

前言 为了理解模型之间的改进关系、明确深度学习的发展目标、提高自身对模型的深度理解、贯彻爱与和平的理念。总之&#xff0c;我做了如下表格。 时间 重大突破 模型改进 详细信息 1847 SGD 随机梯度下降 1995 SVM 支持向量机 1982 RNN 循环神经网络&#xff0c;…

烽火传递

看似很简单的单调队列优化DP 但是如果状态是表示前\(i\)个烽火台被处理完的最小代价(即不知道最后一个烽火台在哪里)就无法降低复杂度 因为假设你在区间\([i-m1,i]\)中枚举最后一个烽火台(设为\(k\))&#xff0c;你前面的状态并不是\(f[k-1]\)&#xff0c;因为此时\(k\)已经可以…

C++之std::tuple(一) : 使用

相关系列文章 C之std::tuple(一) : 使用 C三剑客之std::variant(一) : 使用 C三剑客之std::variant(二)&#xff1a;深入剖析 目录 1.简介 2.创建元组 2.1.直接初始化方式 2.2.使用花括号初始化列表方式&#xff08;C11及以上版本&#xff09; 2.3.make_tuple方式 2.4.使…

Log4j2-27-log4j2 与 springboot 整合例子

1.去除默认的依赖并导入log4j2、lombok依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><exclusions><!-- 引入log4j日志时需去掉默认的logback --><exclusion>&…

【类和对象】4

日期类的拓展 c语言中的printf函数只能打印内置类型&#xff0c;为了弥补这一不足&#xff0c;c利用运算符重载可以打印自定义类型。 void operator<<(ostream&out);//声明在date.h中void Date::operator<<(ostream& out)//定义在date.cpp中 {out<<…

IBOS靶场搭建流程(超详细)

IBOS 1.下载并安装IBOS 点击然后一直下一步进行安装IBOS 显示出这个时证明安装成功 然后进行数据库的搭建&#xff0c;这里需要说明的是这里运行的服务是nginx和mysql而不是apache和mysql所以说这里的数据库名默认是root且密码也默认是root点击立即安装 安装后出现 这个页面时…

Unity_颜色空间GammaLinear

Unity_颜色空间Gamma&Linear 1: Unity颜色空间的选择对于效果的影响具体有多大? 在ProjectSetting -> Player -> OtherSetting -> Rendering设置下的颜色空间选项卡选择颜色空间进行设置: 太深奥的解释一时半会看不懂,找见一个粗浅的对比,当然本人未做验…

GPGPU面临的工程困境闲聊

作者&#xff1a;蒋志强 本人同意他人对我的文章引用&#xff0c;但请在引用时注明出处&#xff0c;谢谢&#xff0e;作者&#xff1a;蒋志强 0.前言 2007年作为GPGPU的工程界元年至今&#xff0c;已经发展了接近小二十年了。这个领域是如此的重要&#xff0c;几乎影响了工业…

Canal 结合 SpringBoot 源码梳理

1、canal是什么&#xff0c;可以用来作什么 canal是阿里开源的一个用于监听数据库binlog&#xff0c;从而实现数据同步的工具。 2、安装 我使用的是1.1.5版本&#xff0c;太高的版本需要的jdk版本和mysql的驱动版本会更高&#xff0c;可以根据自己的环境选择。 如果是自己玩的话…

如何保证MySQL和Redis中的数据一致性?

文章目录 前言一、缓存案例1.1 缓存常见用法1.2 缓存不一致产生的原因 二、解决方案2.1 先删除缓存&#xff0c;再更新数据库2.2 先更新数据库&#xff0c;删除缓存2.3 只更新缓存&#xff0c;由缓存自己同步更新数据库2.4 只更新缓存&#xff0c;由缓存自己异步更新数据库2.5 …