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

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

  • Consumer 消费者,即客户端
  • Provider 服务提供者
  • Service 项目中使用的服务,提供服务接口,服务实现类
  • Util 项目中使用到的工具类
    项目结构参考该项目:syske-rpc-server结构图如下:
  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为序列化对象

  1. 注解类实现
    通过注解可以实现服务在项目启动时自动注册
  • 扫描类注解
@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();

可以得到对应的类集合

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

redis发布成服务 redis实现服务注册发现_Redis

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

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