手写RPC框架(四)使用Redis进行服务注册
手写RPC框架(四)使用Redis进行服务注册
在使用Redis之前,我们发现项目代码结构设计不合理,对于消费者,服务者应该作为不同的模块,在测试和运行时可以分别运行对应的模块,在此我们将项目拆分为四个模块
-
Consumer 消费者,即客户端
-
Provider 服务提供者
-
Service 项目中使用的服务,提供服务接口,服务实现类
-
Util 项目中使用到的工具类
项目结构参考该项目:syske-rpc-server结构图如下:

-
windows安装redis
-
Redis管理工具类
public class RedisUtil { private static Jedis jedis = new Jedis("127.0.0.1", 6379); public static void record2Cache(String key, String value) { jedis.set(key, value); } public static String getObject(String key) { return jedis.get(key); } }key值为服务名,value为序列化对象
-
注解类实现
通过注解可以实现服务在项目启动时自动注册
- 扫描类注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface RpcComponentScan { String[] value(); }- 服务者注解,标记服务提供者
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface RpcProvider { }- 服务消费者
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface RpcCustomer { }- 标注属性
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface RpcClient { }包扫描器:
public class ClassScanner { private static final Logger logger = LoggerFactory.getLogger(ClassScanner.class); private static Set<Class> classSet = Sets.newHashSet(); private ClassScanner() { } public static Set<Class> getClassSet() { return classSet; } /** * 类加载器初始化 * * @throws IOException * @throws ClassNotFoundException */ public static void init(Class aClass) { try { // 扫描包 componentScanInit(aClass); } catch (Exception e) { logger.error("ClassScanner init error: ", e); } } /** * 扫描指定的包路径,如果无该路径,则默认扫描服务器核心入口所在路径 * * @param aClass * @throws IOException * @throws ClassNotFoundException */ private static void componentScanInit(Class aClass) throws IOException, ClassNotFoundException { logger.info("componentScanInit start init……"); logger.info("componentScanInit aClass: {}", aClass); Annotation annotation = aClass.getAnnotation(RpcComponentScan.class); if (Objects.isNull(annotation)) { Package aPackage = aClass.getPackage(); scanPackage(aPackage.toString(), classSet); } else { String[] value = ((RpcComponentScan) annotation).value(); for (String s : value) { scanPackage(s, classSet); } } logger.info("componentScanInit end, classSet = {}", classSet); } /** * 扫描指定包名下所有类,并生成classSet * * @param packageName * @param classSet * @throws IOException * @throws ClassNotFoundException */ private static void scanPackage(String packageName, Set<Class> classSet) throws IOException, ClassNotFoundException { logger.info("start to scanPackage, packageName = {}", packageName); Enumeration<URL> classes = ClassLoader.getSystemResources(packageName.replace('.', '/')); while (classes.hasMoreElements()) { URL url = classes.nextElement(); File packagePath = new File(url.getPath()); if (packagePath.isDirectory()) { File[] files = packagePath.listFiles(); for (File file : files) { String fileName = file.getName(); if (file.isDirectory()) { String newPackageName = String.format("%s.%s", packageName, fileName); scanPackage(newPackageName, classSet); } else { String className = fileName.substring(0, fileName.lastIndexOf('.')); String fullClassName = String.format("%s.%s", packageName, className); classSet.add(Class.forName(fullClassName)); } } } else { String className = url.getPath().substring(0, url.getPath().lastIndexOf('.')); String fullClassName = String.format("%s.%s", packageName, className); classSet.add(Class.forName(fullClassName)); } } } }在启动项目时候,通过调用
Set<Class> classSet = ClassScanner.getClassSet();可以得到对应的类集合
-
Redis使用
Redis用作服务注册,key值存储服务名,value存储序列化信息(实现类信息)
- 服务注册
static final String PROVIDER_KEY = "%s:provider"; RedisUtil.record2Cache(String.format(PROVIDER_KEY, interfaceName), JSON.toJSONString(rpcRegisterEntity));- 服务获取
String serviceObject = RedisUtil.getObject(String.format(PROVIDER_KEY, interfaceName));

一个简单的RPC框架结构如上图,主要的执行步骤如下:
- 启动服务端,服务发现,项目会扫描具有@RpcProvider注解的类,并将<服务名,服务实现类信息存储值Redis中。
- 启动消费端,项目通过反射获取代理类,将调用类,方法、参数等序列化,通过socket发送到服务端
- 服务端获取序列化对象,并将其反序列化,得到interfaceName(接口名,即服务名),参数列表,参数类型,并根据服务名获取实现类,通过反射执行调用的方法。
- 服务端将指定结果通过socket返回给消费端

浙公网安备 33010602011771号