RPC,其实也很简单

这里创建了请求对象和响应对象,来模拟考试作弊的过程。Request是对问题的封装,小明在把问题发给室友时应该告诉室友题目,参数,以及范围,对应的Request应该有参数、解题方法、解题的接口;
小明的室友接收到小明的问题,会根据它发过来的问题的相关信息进行解答,并把答案封装在Response中,Response是对答案的封装,所以Response对应的应有答案属性以及是否会解得信息;
Resquest和Response类可由模块core创建,让consumer和provider都依赖这个模块,从而都可以用到这两个类;
rpc网络传输层采用netty,netty是采用的tcp和udp协议,是对socket封装;所以consumer和provider之间的通讯是采用socket进行的。
在这里插入图片描述
domain是放
在这里插入图片描述
在这里插入图片描述

net是通讯层,用于提供者和消费者之间进行通讯,Provider需要监听消费者发送过来的消息,并向消费者发送消息,所以Provider可以创建一个监听的方法用于监听消费者发出消息;
Povider可以创一个getInputStream方法用于读取消费者发送过来的消息;
Provider可以创建一个getOutputStream方法用于向消费者发送消息;
Provider在等待Consumer发送消息
Consumer可以创建connect方法创建与Provider的连接;
Consumer可以创一个getInputStream方法用于读取Provider发送过来的消息;
Consumer可以创建一个getOutputStream方法用于向Provider发送消息;
创建了以上功能就实现了Consumer和Provider之间的通讯的工具
在这里插入图片描述
Provider与Consumer之间的通信功能实现了,那这两者之间就可以进行调用通讯层的工具进行通讯了。
invoke是执行方法的调用,提供者使用LocalInvoker处理问题并返回答案对象,消费者使用RpcInvoker发送问题并接收答案,返回答案对象

public interface MethodInvoke {

	/**
	 * 发送问题获取答案
	 * @param request
	 * @return
	 */
	Response invoker(Request request);
	
}

在这里插入图片描述
小明的室友是解题的一方,由室友提供解题的方法,小明需要用手机发送题目信息给室友,即解题的接口的实现类在提供者处,消费者需要远程调用这个方法,通过代理来获得解题的实现类对象,并调用解题的方法;所以proxy包下的接口很好的解决了这个问题,消费者可以得到了解题的类的代理对象,当调用解题方法是,就会走进代理的方法中去执行这个方法,返回的值是Consumer的执行方法的返回值,这个返回值是通过socket从提供者一端发送而来的,从而实现了远程调用解决问题的效果;

public interface ObjectPoxy {

	/**
	 * 获取代理对象
	 */
	 <T> T getProxyObject(Class<T> clazz);
	
}
/**
 * 消费者需要调用代理
 * @author Administrator
 *
 */
public class JdkProxy implements ObjectPoxy{
	
	private MethodInvoke methodInvoke = new RpcInvoker();
	
	@SuppressWarnings("unchecked")
	@Override
	public <T> T getProxyObject(Class<T> clazz) {
		
		return (T) Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), new Class<?>[] {clazz}, new InvocationHandler()  {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				Request request = new Request();
				request.setInterfaceName(clazz.getName());
				request.setMethodName(method.getName());
				request.setArgs(args);
				return methodInvoke.invoker(request);
			}
		});
		
	}

}

rpc 继续升级,引入负载均衡

在这里插入图片描述

负载均衡出现的问题

室友宕机的问题

如果室友挂了,就不能访问该室友了,就应该从列表中移除该室友,如果有新室友加入解决问题的队列中,那就应该加入到列表中。
创建一个注册中心,每当一个室友加入答题的行列则往注册中心注册一个服务,每当一个室友宕机了,这个服务会自动删除,在要用到zookeeper来做注册中心
zookeeper有四种节点,
PERSISTENT-持久化目录节点 客户端与zookeeper断开连接后,该节点依旧存在
PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点 客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
EPHEMERAL-临时目录节点 客户端与zookeeper断开连接后,该节点被删除
EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点 客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
很明显加入一个室友或删除一个室友,注册中心的服务会自动添加或者删除,和临时节点的性质一模一样,所以添加室友的本质是在注册中心创建一个临时节点。
定义注册的接口,室友通过这个接口来加入答题的队列,即提供者在注册中心注册一个服务;

public interface ServerRegistry {
	
	void Register(String serverName, String serverPath);
	
}

定义发现服务的接口,小明可以通过这个接口找到室友的ip,即消费者可以通过这个接口找到服务获取提供者的ip

public interface ServerDiscovery {

	List<String> discovery(String serverName);
	
}

注册接口的实现类

public class ServerRegistryImpl implements ServerRegistry{

	private static final String ZKADDRESS = "192.168.142.142:2181";
	
	private static ZkClient zkClient = null;
	
	static {
		zkClient = new ZkClient(ZKADDRESS, 5000, 10000);
	}
	
	/**
	 * 注册服务,本质是在zookeeper中创建节点
	 */
	@Override
	public void Register(String serverName, String serverPath) {
//		zkClient.createPersistent("/family");
//		zkClient.delete("/family");
//		zkClient.deleteRecursive("/family"); 递归删除
		if(!zkClient.exists("/" + serverPath)) {
			zkClient.createPersistent("/" + serverPath);
		}
		zkClient.createEphemeral("/" + serverPath + "/" + serverName);
		System.out.println(serverName + "注册成功");
	}

}

发现接口的实现类

public class ServerDiscoverImpl implements ServerDiscovery {

	private static final String ZKADDRESS = "192.168.142.142:2181";

	private static ZkClient zkClient = null;
	
	static {
		zkClient = new ZkClient(ZKADDRESS, 5000, 10000);
	}

	@Override
	public List<String> discovery(String serverPath) {
		if(!zkClient.exists("/" + serverPath)) {
			throw new RuntimeException("服务不存在");
		}
		List<String> children = zkClient.getChildren("/" + serverPath);
		return children;
	}

}

以上代码实现了注册服务和服务宕机后自动删除的功能,但存在一个缺陷,每次消费者订阅服务时都要访问一次注册中心,如果服务没有太大的变化,则会造成重复的访问,影响性能。因此,在发现的实现类中添加一个缓存的功能;消费者每次执行发现服务操作时,会先去缓存中找,如果缓存中没有则再访问注册中心;添加缓存就要注意缓存的脏读问题,当注册中心的服务发生了变化,需要及时更改缓存中的数据,解决缓存脏读的问题。

public class ServerDiscoverImpl implements ServerDiscovery {

	private static Map<String,List<String>> caches = new HashMap<>();
	
	
	private static final String ZKADDRESS = "192.168.142.142:2181";

	private static ZkClient zkClient = null;
	
	static {
		zkClient = new ZkClient(ZKADDRESS, 5000, 10000);
	}

	@Override
	public List<String> discovery(String serverPath) {
		// 如果缓存中有则从缓存中取,否则从注册中心中取
		if(caches.containsKey("/" + serverPath)) {
			return caches.get("/" + serverPath);
		}
		if(!zkClient.exists("/" + serverPath)) {
			throw new RuntimeException("服务不存在");
		}
		List<String> children = zkClient.getChildren("/" + serverPath);
		// 解决缓存的脏读问题
		zkClient.subscribeChildChanges("/" + serverPath, new IZkChildListener() {

			@Override
			public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
			// parentPath 是父节点的路径
			// currentChilds是子节点的路径
				caches.put(parentPath, currentChilds);
				System.out.println("通过订阅,已解决脏读问题");
			}
			
		} );
		System.out.println(serverPath + "订阅" + serverPath + "服务成功");
		return children;
	}

}

使用zk 做注册中心有什么亮点?

自带的注册服务和下线服务
服务启动,使用zk 去注册服务
服务消费,使用zk 去发现服务

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值