跟孙哥学java
明确需求
- 提供一个RPC的服务集群,提供RPC服务
- 提供客户端进行服务端调用
- 服务治理 (注册中心)zookeeper
技术难点
- client和服务端不在同一个虚拟机中-->怎么透明的调用远端RPC的服务
- 也就是客户端像调用本地方法那样去调用远端的服务方法
- 自动发现RPC集群中的节点(注册中心--->服务发现)
- 负载均衡
- 集群容错--->获取的那个服务,拿到地址之后正好出现问题,再去访问就出现问题了
技术选型
- 客户端client透明的去调用远端的RPC服务
- 远端调用,跨进程--->怎么解决?
- 网络通信-->BIO,NIO,Mina,netty,Web服务
- 传输数据,数据格式-->协议 Http1,Http2,TCP(自己封装)
- 序列化 Protobuf, Thrift, JDK, JSON
- 透明调用-->代理,JDK动态代理,CJlib动态代理
- 自动发现RPC集群中的节点-->服务发现
zookeeper ,java,Curator
- 负载均衡
客户端的负载均衡-->拉取数据,从zk查询 ,监听数据的变换-> 通过curator,zkclient 获取数据之后--->负载均衡算法 -->访问某一个服务节点 rondrobin,random,weight
- 集群容错
failFast--> 拉取的服务节点是坏的--->直接抛出异常 failOver-->去拉取集群服务中其他的节点,重试-->(延迟队列,定时任务) 时间轮
概念设计(抽象)
1.Transport 2.Protocol 3.Serializar 4.代理 5.Cluster 6.负载均衡 7.集群容错(故障容错)
编码实现
新建一个maven项目
这里选择jdk17
引入所需要的依赖
xml
<dependencies>
<!--netty->做网络通信-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.45.Final</version>
</dependency>
<!-- curator:zk的客户端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.1</version>
</dependency>
<!--hessina序列化-->
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.38</version>
</dependency>
<!--hutool工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
</dependencies>
序列化设计
- Hessian
- JDK
- JSON
- Thrift
- Protobuf
为了屏蔽实现的差异,已于程序的扩展--->让具体的实现继承共同的接口---Serializar Serializar 接口
java
public interface Serializar {
//序列化 Protocol--->二进制
byte[] serializar(Protocol protocol) throws Exception;
//反序列化 二进制-->Protocol
Protocol deserializar(byte[] bytes) throws Exception;
}
具体实现类: 1.Hessian序列化
java
public class HessianSerializar implements Serializar {
@Override
public byte[] serializar(Protocol protocol) throws Exception {
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
Hessian2Output hessian2Output=new Hessian2Output(outputStream);
hessian2Output.writeObject(protocol);
hessian2Output.flush();
return outputStream.toByteArray();
}
@Override
public Protocol deserializar(byte[] bytes) throws Exception {
ByteArrayInputStream inputStream=new ByteArrayInputStream(bytes);
Hessian2Input hessian2Input=new Hessian2Input(inputStream);
Object o = hessian2Input.readObject();
return (Protocol) o;
}
}
2.JDK序列化
java
public class JDKSerializar implements Serializar {
@Override
public byte[] serializar(Protocol protocol) throws Exception {
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(protocol);
return outputStream.toByteArray();
}
@Override
public Protocol deserializar(byte[] bytes) throws Exception {
ByteArrayInputStream inputStream=new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream=new ObjectInputStream(inputStream);
Object o = objectInputStream.readObject();
return (Protocol) o;
}
}
3.JSON序列化
java
public class JSONSerializar implements Serializar {
@Override
public byte[] serializar(Protocol protocol) throws Exception {
String jsonStr = JSONUtil.toJsonStr(protocol);
return jsonStr.getBytes(Charset.defaultCharset());
}
@Override
public Protocol deserializar(byte[] bytes) throws Exception {
String json = new String(bytes);
return JSONUtil.toBean(json, Protocol.class);
}
}
RPC通信的协议Protocol
-
请求(对象,拿个方法,参数是什么,实参是什么)
MethodInvokeData--->请求对象 Class targetInterface--->访问的接口 String methodName--->方法名称 Class<?>[] parmeterType--->方法参数 Object[] args--->请求参数
-
响应
Result--->响应结果 Object resultValue--->实际需要的响应结果 Exception exception-->异常 String state -->响应状态 200:通过 400:异常 :::info 如果请求MethodInvokeData是一个类,响应Result又是一个类,我们就要定义两套协议,请求协议和响应协议,因此---> 使用一个公共接口让 请求类和继承类都继承共同的接口。 :::
进一步抽象Protocol协议 编解码器:
- 魔术/幻术--这个可以自己定义--我定义的是HURPC
- 协议版本--一般用一个字节表示,这里使用 byte PROTOCOL_VERSION=1
- 数据的长度--用来记录数据的长度
- 实际传输的数据--MethodInvokeData,Result
编解码器
定义好了编解码的规则,就来实现RPCMessageToMessageCodec,让它继承MessageToMessageCodec<ByteBuf, Protocol>,表示 encode---> Protocol--> ByteBuf decode---> ByteBuf--> Protocol
java
@Slf4j
public class RPCMessageToMessageCodec extends MessageToMessageCodec<ByteBuf, Protocol> {
//序列化的类
private Serializar serializar=new HessianSerializar();
@Override
protected void encode(ChannelHandlerContext ctx, Protocol protocol, List<Object> out) throws Exception {
System.out.println("编码"+protocol);
ByteBufAllocator allocator=ctx.alloc();
ByteBuf buffer = allocator.buffer();
byte[] bytes = serializar.serializar(protocol);
//1.魔术 --MYRPC -五个字节
buffer.writeBytes(Protocol.MAGIC_NUM.getBytes(StandardCharsets.UTF_8));
//2.版本--一个字节 byte
buffer.writeByte(Protocol.PROTOCOL_VERSION);
//3.Protocol长度
buffer.writeInt(bytes.length);
//4.Protocol
buffer.writeBytes(bytes);
out.add(buffer);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
CharSequence charSequence = msg.readCharSequence(5, StandardCharsets.UTF_8);
System.out.println("解码开始");
if(!Protocol.MAGIC_NUM.equals(charSequence.toString())){
//魔术不匹配
throw new RuntimeException("MAGIC_NUM ERROR");
}
byte protocolVersion = msg.readByte();
if(Protocol.PROTOCOL_VERSION!=protocolVersion){
//协议版本不一致
throw new RuntimeException("PROTOCOL_VERSION NOT NORMAL");
}
int length = msg.readInt();
byte[] bytes=new byte[length];
msg.readBytes(bytes);
//反序列化
Protocol protocol = serializar.deserializar(bytes);
System.out.println("解码"+protocol);
out.add(protocol);
}
}
服务端设计
:::info 服务端启动-->开启ServerBootSrap--->服务注册-->将当前服务开发的Service注册到zookeeper-->等待客户端访问(满足我们定义的协议Protocol) :::
java
/*
1. 构建netty服务端
2. 引入注册中心
3. 引入所有的业务对象
4. 通过反射进行调用
*/
@ToString
public class RPCServiceProvider {
//端口号
private int port;
private EventLoopGroup eventLoopGroupBoss;
private EventLoopGroup eventLoopGroupWorker;
//Netty的编解码器 内置Handler通过这个线程组服务
private EventLoopGroup eventLoopGroupHandler;
private EventLoopGroup eventLoopGroupService;
private int workerThreads;
private int handlerThreads;
private int serviceThreads;
private Registry registry;
private ServerBootstrap serverBootstrap;
private Map<String,Object> exposeBeans;
private volatile boolean isStarted=false;
//默认8080,一个工作线程,一个handler线程,一个service线程
public RPCServiceProvider(Registry registry, Map<String, Object> exposeBeans) {
this(8080, 1, 1, 1, registry, exposeBeans);
}
//一个工作线程,一个handler线程,一个service线程 -自定义端口
public RPCServiceProvider(int port, Registry registry, Map<String, Object> exposeBeans) {
this(port, 1, 1, 1, registry, exposeBeans);
}
public RPCServiceProvider(int port, int workerThreads, int handlerThreads, int serviceThreads, Registry registry, Map<String, Object> exposeBeans) {
this.port = port;
this.workerThreads = workerThreads;
this.handlerThreads = handlerThreads;
this.serviceThreads = serviceThreads;
this.eventLoopGroupBoss = new NioEventLoopGroup(1);
this.eventLoopGroupWorker = new NioEventLoopGroup(this.workerThreads);
this.eventLoopGroupHandler = new DefaultEventLoopGroup(this.handlerThreads);
this.eventLoopGroupService = new DefaultEventLoopGroup(this.serviceThreads);
this.registry = registry;
this.serverBootstrap = new ServerBootstrap();
this.exposeBeans = exposeBeans;
}
//1.开启服务
public void startServer(){
if(isStarted){
throw new RuntimeException(" server is already started...");
}
System.out.println("服务启动");
serverBootstrap.group(new NioEventLoopGroup());
serverBootstrap.channel(NioServerSocketChannel.class);
System.out.println(this);
serverBootstrap.childHandler(new RPCServiceProviderInitialize(this.eventLoopGroupHandler,this.eventLoopGroupService,exposeBeans));
ChannelFuture channelFuture = serverBootstrap.bind(port);
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()){
//2.服务注册功能
registerServices(InetAddress.getLocalHost().getHostAddress(),port,exposeBeans,registry);
isStarted=true;
//监听关闭
Channel channel = channelFuture.channel();
ChannelFuture closeFuture = channel.closeFuture();
closeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()){
stopService();
}
}
});
}
}
});
//异常关闭
Runtime.getRuntime().addShutdownHook(new Thread(()->{
stopService();
}));
}
//关闭服务,释放资源
private void stopService() {
eventLoopGroupBoss.shutdownGracefully();
eventLoopGroupWorker.shutdownGracefully();
eventLoopGroupHandler.shutdownGracefully();
eventLoopGroupService.shutdownGracefully();
}
private void registerServices(String hostAddress, int port, Map<String, Object> exposeBeans, Registry registry) {
Set<String> keySet = exposeBeans.keySet();
HostAndPort hostAndPort=new HostAndPort(hostAddress,port);
for (String key : keySet) {
try {
registry.registryService(key,hostAndPort);
} catch (Exception e) {
throw new RuntimeException("register is error");
}
}
}
}
java
@Slf4j
public class RPCServiceProviderInitialize extends ChannelInitializer<NioSocketChannel> {
private EventLoopGroup eventLoopGroupHandler;
private EventLoopGroup eventLoopGroupService;
private Map<String, Object> exposeBean;
public RPCServiceProviderInitialize(EventLoopGroup eventLoopGroupHandler, EventLoopGroup eventLoopGroupService,Map<String, Object> exposeBean) {
this.eventLoopGroupHandler = eventLoopGroupHandler;
this.eventLoopGroupService = eventLoopGroupService;
this.exposeBean=exposeBean;
}
private Result executeTargetObject(MethodInvokeData methodInvokeData, Map<String, Object> exposeBean) throws NoSuchMethodException {
System.out.println("执行rpc方法");
Class targetInterface = methodInvokeData.getTargetInterface();
//获取接口实现方法-->通过接口名字
Object nativeObj = exposeBean.get(targetInterface.getName());
Method method = targetInterface.getDeclaredMethod(methodInvokeData.getMethodName(), methodInvokeData.getParameterType());
//进行方法的调用
Result result=new Result();
try {
Object ret = method.invoke(nativeObj, methodInvokeData.getArgs());
result.setState(RPCServiceState.SUCCESS);
result.setResultValue(ret);
log.debug("成功调用result:{}",ret);
} catch (Exception e) {
log.error("异常",e);
e.printStackTrace();
result.setState(RPCServiceState.FAIL);
result.setException(e);
}
return result;
}
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline(); // z
//封帧 ---使用eventLoopGroupHandler
pipeline.addLast(eventLoopGroupHandler,new LengthFieldBasedFrameDecoder(1024,6,4,0,0));
//LoggingHandler--使用eventLoopGroupHandler
pipeline.addLast(eventLoopGroupHandler,new LoggingHandler());
//编解码器--使用eventLoopGroupService
pipeline.addLast(eventLoopGroupService,new RPCMessageToMessageCodec());
//RPC功能的调用-使用eventLoopGroupService
pipeline.addLast(eventLoopGroupService,new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//代表client提交的用于进行rpc调用的参数
MethodInvokeData methodInvokeData=(MethodInvokeData)msg;
Result result=executeTargetObject(methodInvokeData,exposeBean);
ChannelFuture channelFuture = ctx.writeAndFlush(result);
channelFuture.addListener(ChannelFutureListener.CLOSE);
channelFuture.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
});
}
}
客户端设计
Transport设计
代表的是client对服务端 访问---基于网络访问 BIO NIO Netty Mina...
java
public interface Transport {
public Result invoke(HostAndPort hostAndPort, MethodInvokeData methodInvokeData) throws Exception;
public void close( );
}
java
public class NettyTransport implements Transport {
private Bootstrap bootstrap;
private EventLoopGroup worker;
private int workerThreads;
public NettyTransport(int workerThreads) {
this.workerThreads = workerThreads;
worker=new NioEventLoopGroup(workerThreads);
bootstrap=new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
}
@Override
public Result invoke(HostAndPort hostAndPort, MethodInvokeData methodInvokeData) throws Exception {
RPCClientChannelInitializer rpcClientChannelInitializer=new RPCClientChannelInitializer(methodInvokeData);
bootstrap.handler(rpcClientChannelInitializer);
ChannelFuture channelFuture = bootstrap.connect(hostAndPort.getHostName(), hostAndPort.getPort()).sync();
channelFuture.channel().closeFuture().sync();
return rpcClientChannelInitializer.getResult();
}
@Override
public void close() {
worker.shutdownGracefully();
}
}
java
@Slf4j
public class RPCClientChannelInitializer extends ChannelInitializer<NioSocketChannel> {
private MethodInvokeData methodInvokeData;
private Result result=null;
public Result getResult() {
return result;
}
public RPCClientChannelInitializer(MethodInvokeData methodInvokeData) {
this.methodInvokeData = methodInvokeData;
}
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(1024,6,4,0,0));
pipeline.addLast(new RPCMessageToMessageCodec());
pipeline.addLast(new LoggingHandler());
pipeline.addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.debug("接收到返回值了:{}",msg);
result=(Result)msg;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.debug("发送数据:{}",methodInvokeData);
ChannelFuture channelFuture = ctx.writeAndFlush(methodInvokeData);
channelFuture.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
});
}
}
LoadBalancaer负载均衡
- 随机
- 轮询
- 一致性hash
- weigth
这里就实现了随机算法
java
public class RandomLoadBalancer implements LoadBalancer {
private Random random=new Random();
@Override
public HostAndPort select(List<HostAndPort> hostAndPorts) {
if(hostAndPorts==null||hostAndPorts.size()==0){
throw new RuntimeException("hostAndPorts set is null");
}
int index=random.nextInt(hostAndPorts.size());
return hostAndPorts.get(index);
}
}
Cluster[容错]
- 参数
- 返回值Result
Cluster的实现 考虑不同的容错方式,导致了集群的实现方式的区别 FailFast--->一旦访问RPC服务失败,直接抛出异常 FailOver--->一旦访问失败,会拉取集群中其他的服务进行访问
java
@Slf4j
public class FailFastCluster implements Cluster {
@Override
public Result invoke(Registry registry, LoadBalancer loadBalancer, Transport transport, MethodInvokeData methodInvokeData) {
List<HostAndPort> hostAndPorts = registry.receiveService(methodInvokeData.getTargetInterface().getName());
HostAndPort hostAndPort = loadBalancer.select(hostAndPorts);
Result result = null;
try {
result = transport.invoke(hostAndPort, methodInvokeData);
} catch (Exception e) {
log.error("调用RPC服务出现问题",e);
}finally {
transport.close();
}
return result;
}
}
java
@Slf4j
public class FailOverCluster implements Cluster {
@Override
public Result invoke(Registry registry, LoadBalancer loadBalancer, Transport transport, MethodInvokeData methodInvokeData) {
List<HostAndPort> hostAndPorts = registry.receiveService(methodInvokeData.getTargetInterface().getName());
HostAndPort hostAndPort = loadBalancer.select(hostAndPorts);
log.debug("选择的hostAndPort is{}",hostAndPort.getHostName()+":"+hostAndPort.getPort());
try {
Result result = transport.invoke(hostAndPort, methodInvokeData);
return result;
} catch (Exception e) {
log.error("集群调用产生错误,使用FailOverCluster 进行容错");
hostAndPorts.remove(hostAndPort);
if(hostAndPorts.size()==0){
throw new RuntimeException("集群调用产生错误,没有可用的远程服务");
}
transport=new NettyTransport(1);
return invoke(registry,loadBalancer,transport,methodInvokeData);
}finally {
transport.close();
}
}
}
实现,Service代理
代理的的实现的技术方案(静态代理 和动态代理) 动态代理 (JDK, cglib) Proxy.newProxyInstrance(ClassLoader,interfaces,InvocationHandler) 在InvocationHandler 调用RPC集群 ,发送数据---> MethodInvokeData --->获得响应--> Result
java
public class JDKProxy implements InvocationHandler {
private Class targetInterface;
private Cluster cluster;
private LoadBalancer loadBalancer;
private Transport transport;
private Registry registry;
public void setRegistry(Registry registry) {
this.registry = registry;
}
public void setCluster(Cluster cluster) {
this.cluster = cluster;
}
public void setLoadBalancer(LoadBalancer loadBalancer) {
this.loadBalancer = loadBalancer;
}
public void setTransport(Transport transport) {
this.transport = transport;
}
public JDKProxy(Class targetInterface) {
this.targetInterface = targetInterface;
}
public Object createProxy(){
return Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), new Class[]{targetInterface}, this);
}
//通过远程调用rpc服务
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvokeData methodInvokeData=new MethodInvokeData(targetInterface,method.getName(),method.getParameterTypes(),args);
Result result = cluster.invoke(registry, loadBalancer, transport, methodInvokeData);
if(result.getState()== RPCServiceState.SUCCESS){
return result.getResultValue();
}else {
throw result.getException();
}
}
}
引入Spring
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="rPCServiceProvider" class="org.huy.RPCServiceProvider" init-method="startServer" destroy-method="stopService">
<constructor-arg>
<value>8080</value>
</constructor-arg>
<constructor-arg>
<bean class="org.huy.registry.ZookeeperRegistry">
<constructor-arg>
<value>127.0.0.1:2181</value>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg>
<map>
<entry key="org.huy.service.UserService">
<bean class="org.huy.service.UserServiceImpl"/>
</entry>
<entry key="org.huy.service.OrderService">
<bean class="org.huy.service.OrderServiceImpl"/>
</entry>
</map>
</constructor-arg>
</bean>
</beans>
因为代理对象不是new出来的,我们使用spring工厂注入
java
@Data
public class ProxyFactoryBean implements FactoryBean, InvocationHandler {
private Class targetInterface;
private Cluster cluster;
private Registry registry;
private LoadBalancer loadBalancer;
private Transport transport;
public ProxyFactoryBean(Class targetInterface) {
this.targetInterface = targetInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvokeData methodInvokeData=new MethodInvokeData(targetInterface,method.getName(),method.getParameterTypes(),args);
Result result = cluster.invoke(registry, loadBalancer, transport, methodInvokeData);
if(result.getState()== RPCServiceState.FAIL){
throw result.getException();
}
return result.getResultValue();
}
@Override
public Object getObject() throws Exception {
return Proxy.newProxyInstance(targetInterface.getClassLoader(),new Class[]{targetInterface},this);
}
@Override
public Class<?> getObjectType() {
return targetInterface;
}
}
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cluster" class="org.huy.cluster.FailFastCluster"/>
<bean id="register" class="org.huy.registry.ZookeeperRegistry">
<constructor-arg>
<value>127.0.0.1:2181</value>
</constructor-arg>
</bean>
<bean id="loadBalancer" class="org.huy.loadbalance.RandomLoadBalancer"/>
<bean id="transport" class="org.huy.transport.NettyTransport">
<constructor-arg>
<value>1</value>
</constructor-arg>
</bean>
<bean id="userService" class="org.huy.proxy.ProxyFactoryBean">
<constructor-arg>
<value>org.huy.service.UserService</value>
</constructor-arg>
<property name="cluster" ref="cluster"/>
<property name="registry" ref="register"/>
<property name="loadBalancer" ref="loadBalancer"/>
<property name="transport" ref="transport"/>
</bean>
</beans>