手写RPC框架(四)使用Redis进行服务注册

手写RPC框架(四)使用Redis进行服务注册

在使用Redis之前,我们发现项目代码结构设计不合理,对于消费者,服务者应该作为不同的模块,在测试和运行时可以分别运行对应的模块,在此我们将项目拆分为四个模块

  • Consumer 消费者,即客户端

  • Provider 服务提供者

  • Service 项目中使用的服务,提供服务接口,服务实现类

  • Util 项目中使用到的工具类

    项目结构参考该项目:syske-rpc-server结构图如下:

    image

  1. windows安装redis

    redis安装

    redis可视化工具

  2. 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为序列化对象

  3. 注解类实现

    通过注解可以实现服务在项目启动时自动注册

    • 扫描类注解
    @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();
    

    可以得到对应的类集合

  4. 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));
    

image

一个简单的RPC框架结构如上图,主要的执行步骤如下:

  1. 启动服务端,服务发现,项目会扫描具有@RpcProvider注解的类,并将<服务名,服务实现类信息存储值Redis中。
  2. 启动消费端,项目通过反射获取代理类,将调用类,方法、参数等序列化,通过socket发送到服务端
  3. 服务端获取序列化对象,并将其反序列化,得到interfaceName(接口名,即服务名),参数列表,参数类型,并根据服务名获取实现类,通过反射执行调用的方法。
  4. 服务端将指定结果通过socket返回给消费端
posted @ 2022-02-14 14:11  流光之中  阅读(232)  评论(0)    收藏  举报