文章目录
一、Redis
1.1 Redis安装
1.2 Redis可视化工具
1.3 Redis使用
二、使用Redis实现简单服务注册
\quad\quad
一个简单的RPC框架如上。主要流程如下,原文见手写RPC框架(四)使用Redis进行服务注册:
- 启动服务端,服务发现,项目会扫描具有@RpcProvider注解的类,并将<服务名,服务实现类信息存储值Redis中。
- 启动消费端,项目通过反射获取代理类,将调用类,方法、参数等序列化,通过socket发送到服务端
- 服务端获取序列化对象,并将其反序列化,得到interfaceName(接口名,即服务名),参数列表,参数类型,并根据服务名获取实现类,通过反射执行调用的方法。
- 服务端将指定结果通过socket返回给消费端
\quad\quad
这篇文章引入Redis的原因主要是代替前一篇文章中使用list存储服务信息,利用Redis非关系型数据库特性,可以存储更多服务信息(接口名称、host、port等)。
\quad\quad
在使用Redis之前,我们发现项目代码结构设计不合理,对于客户端,服务端应该作为不同的模块,在测试和运行时可以分别运行对应的模块,在此我们将项目拆分为四个模块:
- client: 客户端,请求服务者
- server: 服务端,提供服务者,响应服务请求
- service: 项目中使用的服务,服务接口及其实现类
- utils : 项目中各个模块使用到的工具类
项目结构参考该项目:syske-rpc-server
2.1 项目使用的服务(service)
\quad\quad RPC远程调用时服务端提供的服务均放在service模块下,和前面文章一样,此处不再赘述。
2.1.1 接口
略,见前文。
2.1.2 实现类
略,见前文。
2.2 utils工具类
\quad\quad 存放RPC的工具类,包括注解,包扫描器,序列化,Redis管理工具类,后续也可以加入zookeeper,kyro,netty,nginx等。
2.2.1 注解
- RpcClient:标注属性,用于客户端标注接口服务
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcClient {
}
- RpcProxy:服务消费者,客户端注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcProxy {
}
- RpcServer:RPC服务端注解,服务提供者
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcServer {
}
- RpcComponentScan:扫描类注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcComponentScan {
String[] value();
}
2.2.2 包扫描器
//包扫描器
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);
logger.info("annotation: {}", annotation);
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);
//ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> classes = ClassLoader.getSystemResources(packageName.replace('.', '/'));
logger.info("classes: {}", classes.hasMoreElements());
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));
}
}
}
}
在启动项目时通过以下语句,可使用包扫描器获取对应类集合:
ClassScanner.init(Server.class);
Set<Class> classSet = ClassScanner.getClassSet();
2.2.3 Redis管理工具类
\quad\quad Redis用作服务注册,key值存储服务名,value存储序列化信息(实现类信息)(序列化信息使用RpcRegisterEntity实现)
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);
}
}
2.2.4 序列化
- RpcRegisterEntity类
序列化服务信息,即:
- interfaceClassFullName:服务接口类全称
- serviceImplClassFullName:服务实现类全称
- host:接口ip
- port:接口端口号
public class RpcRegisterEntity implements Serializable {
/**
* 对服务消费者,该值表示调用方全类名;对服务提供者而言,该值表示接口实现全类名
*/
private String interfaceClassFullName;
/**
* 接口Ip
*/
private String host;
/**
* 接口端口号
*/
private int port;
/**
* 服务提供者实现类名
*/
private String serviceImplClassFullName;
private String methodName;
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
private Class<?>[] parameterTypes;
private Object[] parameters;
public Class<?>[] getParameterTypes() {
return parameterTypes;
}
public void setParameterTypes(Class<?>[] parameterTypes) {
this.parameterTypes = parameterTypes;
}
public Object[] getParameters() {
return parameters;
}
public void setParameters(Object[] parameters) {
this.parameters = parameters;
}
public RpcRegisterEntity() {
}
public RpcRegisterEntity(String interfaceFullName, String host, int port) {
this.interfaceClassFullName = interfaceFullName;
this.host = host;
this.port = port;
}
public String getInterfaceClassFullName() {
return interfaceClassFullName;
}
public void setInterfaceClassFullName(String interfaceClassFullName) {
this.interfaceClassFullName = interfaceClassFullName;
}
public String getHost() {
return host;
}
public RpcRegisterEntity setHost(String host) {
this.host = host;
return this;
}
public int getPort() {
return port;
}
public RpcRegisterEntity setPort(int port) {
this.port = port;
return this;
}
public String getServiceImplClassFullName() {
return serviceImplClassFullName;
}
public RpcRegisterEntity setServiceImplClassFullName(String serviceImplClassFullName) {
this.serviceImplClassFullName = serviceImplClassFullName;
return this;
}
@Override
public String toString() {
return "RpcRegisterEntity{" +
"interfaceFullName='" + interfaceClassFullName + '\'' +
"serviceImplClassFullName" + serviceImplClassFullName + '\'' +
", host='" + host + '\'' +
", port=" + port +
'}';
}
}
- RpcSerializable类
这里用于客户端和服务端建立socket连接后,用于传输请求信息。和前文实现方法一致,不再赘述。
2.3 服务端
服务端基本逻辑:
- 扫描提供服务的包路径,注册服务
- 打开socket,等待监听
- 建立连接后,响应请求,返回服务计算结果
@RpcServer
@RpcComponentScan("cn.dhu.service")
public class Server {
static final String PROVIDER_KEY = "%s:server";
static final int port=8886;
static final String host = "127.0.0.1";
public static void main(String[] args) {
try {
ClassScanner.init(Server.class);
initServiceProvider();
//启动nettyServer
//NettyServer nettyServer=new NettyServer("192.168.31.214",port);
//nettyServer.startNettyServer();
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
Socket accept = serverSocket.accept();
ObjectInputStream objectInputStream = new ObjectInputStream(accept.getInputStream());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(accept.getOutputStream());
RpcSerializable rpcSerializable=(RpcSerializable)objectInputStream.readObject();
// 读取类名
Class serviceClass= rpcSerializable.getClassName();
// 读取方法名
String methodName=rpcSerializable.getMethodName();
// 读取方法入参类型
Class<?>[] parameterTypes=rpcSerializable.getParameterTypes();
// 读取方法调用入参
Object[] parameters=rpcSerializable.getArguments();
System.out.println(String.format("收到消费者远程调用请求:类名 = {%s},方法名 = {%s},调用入参 = %s,方法入参列表 = %s",
serviceClass, methodName, Arrays.toString(parameters), Arrays.toString(parameterTypes)));
String serviceObject = RedisUtil.getObject(String.format(PROVIDER_KEY, serviceClass.getName()));
System.out.println("PROVIDER_KEY:"+PROVIDER_KEY);
RpcRegisterEntity rpcRegisterEntity = JSON.parseObject(serviceObject, RpcRegisterEntity.class);
String serviceImplClassFullName = rpcRegisterEntity.getServiceImplClassFullName();
Class<?> aClass = Class.forName(serviceImplClassFullName);
System.out.println("服务实现类:"+aClass);
Method method = aClass.getMethod(methodName,parameterTypes);
Object invoke = method.invoke(aClass.newInstance(), parameters);
System.out.println("方法调用结果:" + invoke);
objectOutputStream.writeObject(invoke);
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void initServiceProvider() {
Set<Class> classSet = ClassScanner.getClassSet();
classSet.forEach(c -> {
Annotation annotation = c.getAnnotation(RpcServer.class);
if (Objects.nonNull(annotation)) {
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
Class<?>[] parameterTypes = method.getParameterTypes();
String methodName = method.getName();
Class[] interfaces = c.getInterfaces();
String interfaceName = interfaces[0].getName();
RpcRegisterEntity rpcRegisterEntity = new RpcRegisterEntity(interfaceName,host,port);
rpcRegisterEntity.setServiceImplClassFullName(c.getName());
RedisUtil.record2Cache(String.format(PROVIDER_KEY, interfaceName), JSON.toJSONString(rpcRegisterEntity));
rpcRegisterEntity.setMethodName(methodName);
rpcRegisterEntity.setParameterTypes(parameterTypes);
System.out.println(JSON.toJSONString(rpcRegisterEntity));
}
}
});
}
}
2.4 客户端
客户端基本逻辑:
- 扫描需要请求的服务全类名,并从注册中心(Redis)中获取服务
- 获取服务信息后生成代理对象(生成代理对象的方法可参照前文)
- 在代理对象的invoke方法中添加执行操作
- 在invoke中向服务端发送请求,并接收返回信息
@RpcProxy
@RpcComponentScan("cn.dhu.client")
public class Client {
private static final Logger logger = (Logger) LoggerFactory.getLogger(Client.class);
@RpcClient
private Calculator calculator;
public static void main(String[] args) {
initServiceClient();
Map<Class, Object> rpcClientContentMap = RpcClientContentHandler.getRpcClientContentMap();
logger.info("Client:{}",Client.class);
Client client = (Client) rpcClientContentMap.get(Client.class);
logger.info("Client:{}",client);
int result = client.calculator.add(1,2);
logger.info("消费者远程调用返回结果:{}", result);
int result1 = client.calculator.sub(1,2);
logger.info("消费者远程调用返回结果:{}", result1);
int result2 = client.calculator.mul(3,2);
logger.info("消费者远程调用返回结果:{}", result2);
int result3 = client.calculator.div(1,2);
logger.info("消费者远程调用返回结果:{}", result3);
}
private static void initServiceClient() {
final String PROVIDER_KEY = "%s:server";
ClassScanner.init(Client.class);
Set<Class> classSet = ClassScanner.getClassSet();
logger.info("classSet:{}",classSet);
classSet.forEach(c -> {
Field[] declaredFields = c.getDeclaredFields();
for (Field field : declaredFields) {
try {
RpcClient annotation = field.getAnnotation(RpcClient.class);
if (Objects.nonNull(annotation)) {
Class<?> fieldType = field.getType();
String name = fieldType.getName();
//RedisUtil.getObject(String.format(PROVIDER_KEY, name));
String serviceObject = RedisUtil.getObject(String.format(PROVIDER_KEY, name));
RpcRegisterEntity rpcRegisterEntity = JSON.parseObject(serviceObject, RpcRegisterEntity.class);
String host = rpcRegisterEntity.getHost();
int port = rpcRegisterEntity.getPort();
logger.info("fieldType:{}",fieldType);
Object proxyInstance = getProxyInstance1(fieldType,host,port);
//logger.info("proxyInstance:{}",proxyInstance);
Object consumer = c.newInstance();
field.set(consumer, proxyInstance);
RpcClientContentHandler.initRpcClientContent(c, consumer);
}
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
});
}
/**
* 获取动态代理实例
*
*
*
*/
private static Object getProxyInstance1(Class tClass,String host, int port) {
return Proxy.newProxyInstance(Client.class.getClassLoader(),
new Class[]{tClass}, new ClientProxyInvocationHandler1(tClass,host,port));
}
}
代理类:
public class ClientProxyInvocationHandler1 implements public class ClientProxyInvocationHandler1 implements InvocationHandler {
private final Logger logger = LoggerFactory.getLogger(ClientProxyInvocationHandler.class);
/**
* 代理类的class
*/
private Class<?> serviceClass;
String host;
int port;
public ClientProxyInvocationHandler1(Class<?> serviceClass,String host, int port) {
this.serviceClass = serviceClass;
this.host = host;
this.port = port;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws IOException, ClassNotFoundException {
//logger.info("建立socket连接: {}:{}", host, port);
Socket socket = new Socket(host, port);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
RpcSerializable rpcSerializable = new RpcSerializable(proxy.getClass().getInterfaces()[0], method.getName(), method.getParameterTypes(), args);
objectOutputStream.writeObject(rpcSerializable);
return inputStream.readObject();
}
}
{
private final Logger logger = LoggerFactory.getLogger(ClientProxyInvocationHandler.class);
/**
* 代理类的class
*/
private Class<?> serviceClass;
String host;
int port;
public ClientProxyInvocationHandler1(Class<?> serviceClass,String host, int port) {
this.serviceClass = serviceClass;
this.host = host;
this.port = port;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws IOException, ClassNotFoundException {
//logger.info("建立socket连接: {}:{}", host, port);
Socket socket = new Socket(host, port);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
RpcSerializable rpcSerializable = new RpcSerializable(proxy.getClass().getInterfaces()[0], method.getName(), method.getParameterTypes(), args);
objectOutputStream.writeObject(rpcSerializable);
return inputStream.readObject();
}
}