一、服务远程简单调用
服务远程调用,可以利用http+传统的servlet服务实现。(关于通信服务,也可使用其他,例如:netty等框架)
- 服务消费者通过httpClient,使用ip+port。调用远程服务。
服务提供者,接受入参,(通过接口信息获取实现类信息)通过反射方式,执行业务处理逻辑,序列化处理结果返回。
缺陷
服务消费者,在调用远程服务时,每次都需要进行参数组装(Invocattion参数)、HttpClient数据发送等远程调用逻辑实现。重复的调用逻辑严重影响正常业务逻辑。
消费端实现
public class OriginalConsumer {
public static void main(String[] args) throws IOException {
//组装Invocation参数
RpcInvocation invocation = new RpcInvocation();
invocation.setServiceName(DemoServiceImpl.class.getName());
invocation.setMethodName("sayHello");
invocation.setParameterTypes(new Class[]{String.class});
invocation.setArguments(new Object[]{"masterlu"});
//发送数据
Socket socket = new Socket("127.0.0.1", 8080);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(invocation);
//接收数据
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int read = inputStream.read(bytes);
System.out.println(new String(bytes,0,read));
}
}
服务端实现
```java /**
- 原始服务提供 */ public class OriginalService {
public static void main(String[] args) throws IOException {
// 1. 注册服务
// 2. 本地注册
// 3. 启动tomcat
// 注册服务
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket accept = serverSocket.accept();
new Thread(() -> {
try {
InputStream inputStream = accept.getInputStream();
// 获取接口调用信息
ObjectInputStream ois = new ObjectInputStream(accept.getInputStream());
Invocation invocation = (Invocation) ois.readObject();
Class implClass = Class.forName(invocation.getServiceName());//接口
//反射调用方法
Class[] parameterTypes = new Class[invocation.getArguments().length];
for (int i = 0; i < invocation.getArguments().length; i++) {
parameterTypes[i] = invocation.getArguments()[i].getClass();
}
Method method = implClass.getMethod(invocation.getMethodName(), new Class[]{String.class});
String result = (String) method.invoke(implClass.newInstance(), invocation.getArguments());
System.out.println("socket:" + result);
IOUtils.write(result, accept.getOutputStream());
} catch (Exception e) {
System.out.println(e);
}
}).start();
}
}
}
<a name="kMsqd"></a>
# 二、动态代理优化服务远程调用
![image.png](https://cdn.nlark.com/yuque/0/2022/png/13013491/1666612005954-824e53a0-6099-4cb1-b623-ae1655fd44cd.png#averageHue=%23f1f1f1&clientId=uf4921a63-9744-4&from=paste&height=209&id=u55925192&originHeight=314&originWidth=1407&originalType=binary&ratio=1&rotation=0&showTitle=false&size=80023&status=done&style=none&taskId=u662e767c-14cc-45c6-a2ca-64107097204&title=&width=938)
<a name="Hs5uo"></a>
## 缺陷
服务消费者,在代码中写死服务提供者IP。提供者下线、扩容,对服务消费者,影响很大。
<a name="Pwk1A"></a>
## 优化消费端实现
```java
public static void main(String[] args) throws IOException {
//将远程调用逻辑进行封装,减少对业务逻辑的影响
DemoService demoService = getProxy(DemoServiceImpl.class);
String result = demoService.sayHello("masterlu");
System.out.println(result);
}
public static <T> T getProxy(final Class interfaceClass) {
return (T)Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
interfaceClass.getInterfaces(),
(proxy, method, args) -> {
//组装Invocation参数
RpcInvocation invocation = new RpcInvocation();
invocation.setServiceName(interfaceClass.getName());
invocation.setMethodName(method.getName());
invocation.setParameterTypes(method.getParameterTypes());
invocation.setArguments(args);
//发送数据
Socket socket = new Socket("127.0.0.1", 8080);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(invocation);
//接收数据
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int read = inputStream.read(bytes);
return new String(bytes,0,read);
});
}
}
三、引入注册中心优化服务远程调用
引入注册中心,服务提供者,将信息存储在注册中;消费者实时感知可用服务。
注册中心数据缓存
服务消费者,获取注册中心数据后,缓存在本地。注册中心临时短时间故障,不影响服务使用。
失败重试
服务消费获取远程服务列表后,可实现服务的调用策略(随机、轮询、最少活跃、一致性哈希【根据参数计算hash,决定调用服务,参数一致会调用同一台】)
注册中心作用
监控服务提供者
可以利用zk的临时节点,或利用redis的定时节点(定时延期),实时感知服务提供者的服务可用性,监控服务提供者的服务。
发现服务消费者
利用zk的wacth机制(注册到提供的创建的临时节点上)、redis的发布订阅模式,及时感知服务提供者增、减的情况