文章目录
面试官:
1 Dubbo的底层是什么?
2 你能不能设计一个Dubbo ?
3 注册中心的优化方案是什么?
4 网络的IO 怎么优化?
5 重试机制?
6 线程池问题?
一、RPC 到底是什么?
RPC 就是一个远程调用
什么叫远程调用?
有个问题,我不会,别人会,我让别人帮我做。
例子:大学考试时,你不会,但是室友会!让室友帮我做?
二、场景的模拟
以下代码都是IDEA写,可能有出入
2.0 父pom
修改父项目的打包方式
2.1 我
2.2 室友
2.3 rpc-self的场景和启动类
2.4 室友的场景和启动类
2.5 解决思路
2.5.1 我发送题目并且接受答案
package com.zy.rpc;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
public class SelfApp {
public static void main(String[] args) throws Exception{
System.out.println("我在考试");
System.out.println("有一个题目1+1=?不会,请教室友");
System.out.println("怎么请教室友?");
Socket client=new Socket("localhost",8888);
String question="1+1=?";
OutputStream outputStream=client.getOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(outputStream);
/**
* 把题目写过去
*/
objectOutputStream.writeObject(question);
/**
* 接受室友的答案
*/
InputStream inputStream=client.getInputStream();
ObjectInputStream objectInputStream=new ObjectInputStream(inputStream);
String answer = (String) objectInputStream.readObject();
System.out.println("室友的答案为:"+answer);
}
}
2.5.2 室友接受题目并且计算以及把答案发给我
package com.zy.rpc;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ClassmateApp {
public static void main(String[] args) throws Exception {
System.out.println("今天菜逼去考试了,他说让我把手机一直开机,他有题目。。。");
System.out.println("手机监听菜逼的题目中。。");
int port=8888;
ServerSocket serverSocket=new ServerSocket(port);
System.out.println("开始监听");
Socket client = serverSocket.accept();
InputStream inputStream = client.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
String question = (String) objectInputStream.readObject();
System.out.println("菜逼的题目是:"+question);
/**
* 开始帮菜逼计算
*/
String answer = "2" ;
/**
* 把答案发给菜逼
*/
OutputStream outputStream = client.getOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(outputStream);
/**
* 把答案写个菜逼
*/
objectOutputStream.writeObject(answer);
}
}
2.5.3 测试
1 启动室友
2 启动自己
参考代码:
http://120.26.80.180/liangtiandong/0812project/tree/master/rpc-demo
三、rpc-core项目
3.1 新建项目
3.2 core的作用
完成公共类和模型的封装和提取!
3.2.1 关闭资源的操作
public final class ResourceUtil {
/**
* 关闭资源
*/
public static void close(Closeable ...res){
for (Closeable re : res) {
if(null!=re){
try {
re.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
re = null ; // jvm 可以回收
}
}
}
}
}
3.2.2 模型类
题目究竟代表啥?
1 + 1 = ?
3.3 将自己和室友的代码改造
3.3.1 新建一个接口(在rpc-core)
3.3.2 对自己(rpc-self)的改造
在rpc-self 里面依赖rpc-core
对1 + 1 的改造:
发送题目的改造
3.3.3 对室友的改造
添加依赖
接受的题目是Request 不是字符串
计算:
四、谁应该实现接口?
我们调用室友,室友肯定会这个方法,而我肯定不会!
五、室友室友这个类来计算答案
/**
* 室友通过菜逼的题目来做一个计算
* @param rquest
* @return
*/
private static Object invoker(Request rquest) {
/**
* 调用的接口
*/
String interfaceName = rquest.getInterfaceName();
/**
* 方法的名称
*/
String methodName = rquest.getMethodName();
/**
* 调用的参数
*/
Object[] agrs = rquest.getAgrs();
System.out.println(interfaceName+":"+methodName+":"+agrs);
// 室友怎么实现计算
// object.invoke(method,agrs) ;
// 接口的名称:com.sxt.rpc.service.AddService
// 实现类的名称:com.sxt.rpc.service.impl.AddServiceImpl
try {
Class<?> clazz = Class.forName("com.sxt.rpc.service.impl.AddServiceImpl");
Class<?> []paramTypes = null ;
if(agrs!=null){
paramTypes = new Class<?>[agrs.length];
for (int i = 0; i < agrs.length; i++) {
paramTypes[i] = agrs[i].getClass();
}
}
// 反射得到要调用的方法
Method method = clazz.getMethod(methodName, paramTypes);
// 实例化对象
Object realObject = clazz.newInstance();
// 完成方法的调用
Object result = method.invoke(realObject, agrs);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null ;
}
六、对自己的改造(关键的一步)
6.1 形式
6.2 使用动态代理的改造
public class SelfApp {
public static void main(String[] args) {
System.out.println("我考试了");
System.out.println("题目不会");
AddService addService = getProxyObject(AddService.class) ; // 接口,接口能不能被调用?1 实现类(没有) 2 代理对象
System.out.println(addService); //其实底层调用toString
Integer result = addService.add(10, 100);
System.out.println("计算答案为:"+result);
}
/**
* 通过一个接口来创建代理对象 接口:JDK 没有接口:Cglib
* @param interfacee
* @param <T>
* @return
*/
private static <T> T getProxyObject(Class<T> interfacee) {
return (T)Proxy.newProxyInstance(SelfApp.class.getClassLoader(), new Class<?>[]{interfacee}, new InvocationHandler() {
/**
* 代理对象调用任何方法,都会进入下面的方法里面
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
switch (name){ // 这些都是普通的方法,不需要远程调用
case "toString":
return interfacee.getName()+"$Proxy"; // 是代理对象调用该方法
case "hashCode":
return -1;
case "equals":
return false;
default:
// add 方法,它需要远程调用
Request request = new Request();
request.setInterfaceName(interfacee.getName());
request.setMethodName(name);
request.setAgrs(args);
return rpcInvoke(request);
}
}
});
}
private static Object rpcInvoke(Request request) {
Socket client = null ;
OutputStream outputStream = null ;
ObjectOutputStream objectOutputStream = null ;
InputStream inputStream = null ;
ObjectInputStream objectInputStream = null ;
try {
client = new Socket("localhost", 8888);
outputStream = client.getOutputStream();
objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(request); // 把题目发个室友
inputStream = client.getInputStream();
objectInputStream = new ObjectInputStream(inputStream);
Object result = objectInputStream.readObject();
return result ;
} catch (Exception e) {
e.printStackTrace();
}finally {
ResourceUtil.close(objectInputStream,inputStream,objectOutputStream,outputStream,client);
}
return null ;
}
public static void main1(String[] args) throws Exception {
System.out.println("---------我在考试---------");
// System.out.println("有个题目1 + 1 = ?不会,求救室友");
System.out.println("怎么求救室友?");
////////////////////使用Request 来表示题目//////////////////////
// String question = "1 + 1 = ?" ;
Request request = new Request();
request.setAgrs(new Object[]{10,20});
request.setMethodName("add");
request.setInterfaceName("com.sxt.rpc.service.AddService");
System.out.println("本次的题目为:"+request);
////////////////////改造完成////////////////////////
/**
* 需要把题目发送给室友
*/
Socket client = new Socket("localhost", 8888); // 去连接室友
OutputStream outputStream = client.getOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
/**
* 把题目写过去
*/
objectOutputStream.writeObject(request);
/**
* 接受室友的答案
*/
InputStream inputStream = client.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Integer answer = (Integer)objectInputStream.readObject();
System.out.println("室友的答案为:"+answer);
}
}
6.3 把代理对象的部分提取出来
在rpc-core 里新建工具类
/**
* 动态代理的工具类
*/
public final class ProxyUtil {
/**
* 通过一个接口来创建代理对象 接口:JDK 没有接口:Cglib
* @param interfacee
* @param <T>
* @return
*/
public static <T> T getProxyObject(Class<T> interfacee) {
return (T) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(), new Class<?>[]{interfacee}, new InvocationHandler() {
/**
* 代理对象调用任何方法,都会进入下面的方法里面
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
switch (name){ // 这些都是普通的方法,不需要远程调用
case "toString":
return interfacee.getName()+"$Proxy"; // 是代理对象调用该方法
case "hashCode":
return -1;
case "equals":
return false;
default:
// add 方法,它需要远程调用
Request request = new Request();
request.setInterfaceName(interfacee.getName());
request.setMethodName(name);
request.setAgrs(args);
return rpcInvoke(request);
}
}
});
}
private static Object rpcInvoke(Request request) {
Socket client = null ;
OutputStream outputStream = null ;
ObjectOutputStream objectOutputStream = null ;
InputStream inputStream = null ;
ObjectInputStream objectInputStream = null ;
try {
client = new Socket("localhost", 8888);
outputStream = client.getOutputStream();
objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(request); // 把题目发个室友
inputStream = client.getInputStream();
objectInputStream = new ObjectInputStream(inputStream);
Object result = objectInputStream.readObject();
return result ;
} catch (Exception e) {
e.printStackTrace();
}finally {
ResourceUtil.close(objectInputStream,inputStream,objectOutputStream,outputStream,client);
}
return null ;
}
}
6.4 简化调用的流程
七、使用注册中心来解决室友端口变化的问题
7.1 修改室友的监听端口
7.2 菜逼的调用
7.3 设计注册中心的数据结构
在dubbo 里面?
/服务的名称
/服务的名称/服务的地址
7.3.1 添加zk的依赖
在父项目里面添加依赖管理
在rpc-core 里面引入该依赖
<dependencies>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
</dependency>
</dependencies>
7.3.2 使用docker 安装一个zookeeper(ecs)
docker run --name zk -p 2181:2181 -d zookeeper
测试:
Ecs 必须放行2181 端口
7.3.4 节点的创建和测试
public static void createNode(){
/**
* 节点的类型:4 种
* a:顺序的节点(节点后面带有数字的标识) 无序的节点
* b:持久的 (创建后一直存在)临时的(创建它的客户端断开连接后,它自动删除)
*
*/
zkClient.createPersistent("/0812");
zkClient.createEphemeral("/rpc");
zkClient.createPersistentSequential("/0812","xxx");
zkClient.createEphemeralSequential("/rpc","xxxx");
System.out.println("创建完成!");
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
7.3.5 zk 的复习
/**
* zk 的工具类
*/
public class ZKUtil {
private static final String ZK_SERVER_URL = "120.26.80.180:2181" ; // 大家不要连接我的zk
private static ZkClient zkClient = null ;
static{
zkClient = new ZkClient(ZK_SERVER_URL);
System.out.println("zk已经连接成功");
}
public static void main(String[] args) {
// createNode();
// deleteNode();
// getSubNode();
subscribe();
}
/**
* 1 节点的创建
*/
public static void createNode(){
/**
* 节点的类型:4 种
* a:顺序的节点(节点后面带有数字的标识) 无序的节点
* b:持久的 (创建后一直存在)临时的(创建它的客户端断开连接后,它自动删除)
*
*/
zkClient.createPersistent("/0812");
zkClient.createEphemeral("/rpc");
zkClient.createPersistentSequential("/0812","xxx");
zkClient.createEphemeralSequential("/rpc","xxxx");
System.out.println("创建完成!");
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 2 节点的删除
*/
public static void deleteNode(){
boolean delete = zkClient.delete("/08120000000002");
if (delete){
System.out.println("删除成功");
}
}
/**
* 3 获取子节点
*/
public static void getSubNode(){
// zkClient.createPersistent("/com.sxt.service.AddService");
zkClient.createEphemeral("/com.sxt.service.AddService/localhost:8888");
zkClient.createEphemeral("/com.sxt.service.AddService/localhost:9999");
zkClient.createEphemeral("/com.sxt.service.AddService/localhost:7777");
System.out.println("节点创建完毕");
List<String> childrens = zkClient.getChildren("/com.sxt.service.AddService");
if(childrens!=null && !childrens.isEmpty()){
childrens.forEach((k)-> System.out.println(k));
}
}
/**
* 4 节点的订阅
*/
public static void subscribe(){
// 订阅子节点的改变?
/**
* 当父节点里面有子节点删除了,或者新增了,都会触发监听器
*/
zkClient.subscribeChildChanges("/com.sxt.service.AddService", new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
System.out.println("父节点:/com.sxt.service.AddServic,的子节点有改变");
System.out.println("当前最新的父节点为:"+currentChilds);
}
}) ;
System.out.println("开始监听");
try {
System.in.read() ;
} catch (IOException e) {
e.printStackTrace();
}
}
}
7.4 注册中心的功能?
7.4.1 服务的注册
7.4.2 服务的发现
/**
* 注册中心的功能设计和完善
*/
public class RegisterCenter {
private static ZkClient zkClient = null ;
private static String SERVER_URL = "" ;
static {
int connectionTime = 30 *1000 ; // 尝试来连接zk的时间
int sessionTime = 5*1000; // zk服务端和客户保持连接的时间,若在该时间内,客户端都没有向服务器响应,则服务器认为客户端死了
zkClient = new ZkClient(SERVER_URL,connectionTime,sessionTime);
}
/**
* 服务的注册
* 需要提供:
* 服务的名称
* 服务的地址
*/
public static void register(String serviceName ,String sericeAddress){
if(!zkClient.exists("/"+serviceName)){
zkClient.createPersistent("/"+serviceName); // 这句话有异常,当你创建的节点存在时,它会有异常
}
if(!zkClient.exists("/"+serviceName+"/"+sericeAddress)){
zkClient.createEphemeral("/"+serviceName+"/"+sericeAddress);
System.out.println("服务serviceName,在:" + sericeAddress+ "注册成功");
}
}
/**
* 服务的发现
* 使用服务的名称得到服务的地址
*
*/
public static List<String> discovery(String serviceName){
if(zkClient.exists("/"+serviceName)){
List<String> childrens = zkClient.getChildren("/" + serviceName);
if(childrens!=null){
return childrens ;
}
}
return Collections.emptyList() ;
}
}
7.5 使用服务的注册和发现
7.5.1 服务提供者启动时,服务要注册
室友启动时,注册自己
7.5.2 服务消费者调用时,服务做发现
八、完善负载均衡的过程
九、使用zk 有个性能问题?
我们每次调用时,都需要来一次服务的发现:
9.1 缓存的思想
可以让它第一次去中心拉取,拉取到后,放入缓存里面,以后就可以不用去zk 里面拉取,直接从缓存里面获取
9.2 缓存有个问题
缓存的脏读的问题怎么解决?
1 删除缓存
2 更新缓存
我们在我们使用场景里面,无法知道缓存有脏读的现象!
Add对象
删除对象
更新对象
十、死循环监听
public class ClassmateApp {
public static void main(String[] args) throws Exception {
System.out.println("今天菜逼去考试了,他说让我把手机一直开机,他有题目要发给我");
int port = 8888 ;
listener(port) ;
}
public static void listener(int port){
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
System.out.println("开始监听菜逼的题目.....");
} catch (IOException e) {
e.printStackTrace();
}
/**
* 室友提供的是:AddService 服务
*/
RegisterCenter.register(AddService.class.getName(),"localhost:"+port);
while(true){
Socket client = null ;
InputStream inputStream = null ;
ObjectInputStream objectInputStream = null ;
OutputStream outputStream = null ;
ObjectOutputStream objectOutputStream = null ;
try {
client = serverSocket.accept();// 监听菜逼的链接,如果菜逼不连接,将一直阻塞
inputStream = client.getInputStream();
objectInputStream = new ObjectInputStream(inputStream);
/**
* 得到菜逼的题目
*/
Request rquest = (Request) objectInputStream.readObject();
System.out.println("菜逼的题目是"+rquest);
/**
* 开始帮菜逼计算
*/
// String answer = "2" ;
Object answer = invoker(rquest);
/**
* 把答案发给菜逼
*/
outputStream = client.getOutputStream();
objectOutputStream = new ObjectOutputStream(outputStream);
/**
* 把答案写个菜逼
*/
objectOutputStream.writeObject(answer);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
ResourceUtil.close(objectOutputStream,outputStream,objectInputStream,inputStream,client);
}
}
}
/**
* 室友通过菜逼的题目来做一个计算
* @param rquest
* @return
*/
private static Object invoker(Request rquest) {
/**
* 调用的接口
*/
String interfaceName = rquest.getInterfaceName();
/**
* 方法的名称
*/
String methodName = rquest.getMethodName();
/**
* 调用的参数
*/
Object[] agrs = rquest.getAgrs();
System.out.println(interfaceName+":"+methodName+":"+agrs);
// 室友怎么实现计算
// object.invoke(method,agrs) ;
// 接口的名称:com.sxt.rpc.service.AddService
// 实现类的名称:com.sxt.rpc.service.impl.AddServiceImpl
try {
Class<?> clazz = Class.forName("com.sxt.rpc.service.impl.AddServiceImpl");
Class<?> []paramTypes = null ;
if(agrs!=null){
paramTypes = new Class<?>[agrs.length];
for (int i = 0; i < agrs.length; i++) {
paramTypes[i] = agrs[i].getClass();
}
}
// 反射得到要调用的方法
Method method = clazz.getMethod(methodName, paramTypes);
// 实例化对象
Object realObject = clazz.newInstance();
// 完成方法的调用
Object result = method.invoke(realObject, agrs);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null ;
}
}