分布式系统
分布式与集群的区别
什么是IOE
为什么要去IOE
分布式数据一致性,指的是数据在多份副本中存储时,各副本中的数据是一致的
强一致
这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大。但是强一致性很难实现。
弱一致
这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。
读写一致性:用户读取自己写入结果的一致性,保证用户永远能够第一时间看到自己更新的内容
方案1:一种方案是对于一些特定的内容我们每次都去主库读取
方案2:我们设置一个更新时间窗口,在刚刚更新的一段时间内,我们默认都从主库读取,过了这个窗口之后,我们会挑选最近有过更新的从库进行读取
方案3:我们直接记录用户更新的时间戳,在请求的时候把这个时间戳带上,凡是最后更新时间小于这个时间戳的从库都不予以响应
单调读一致性:本次读到的数据不能比上次读到的旧
解决方案:就是根据用户ID计算一个hash值,再通过hash值映射到机器。同一个用户不管怎么刷新,都只会被映射到同一台机器上。这样就保证了不会读到其他从库的内容,带来用户体验不好的影响。
因果一致性:指的是:如果节点 A 在更新完某个数据后通知了节点 B,那么节点 B 之后对该数据的访问和修改都是基于 A 更新后的值。于
此同时,和节点 A 无因果关系的节点 C 的数据访问则没有这样的限制。
最终一致性:最终一致性是所有分布式一致性模型当中最弱的。不考虑所有的中间状态的影响,只保证当没有新的更新之后,经过一段时间之后,最终系统内所有副本的数据是正确的。它最大程度上保证了系统的并发能力,也因此,在高并发的场景下,它也是使用最广的一致性模型。
选项 | 描述 |
---|---|
C 一致性 | 分布式系统当中的一致性指的是所有节点的数据一致,或者说是所有副本的数据一致 |
A 可用性 | Reads and writes always succeed. 也就是说系统一直可用,而且服务一直保持正常 |
P 分区容错性 | 系统在遇到一些节点或者网络分区故障的时候,仍然能够提供满足一致性和可用性的服务 |
选择 | 描述 |
---|---|
舍弃A(可用性),保留CP(一致性和分区容错性) | 一个系统保证了一致性和分区容错性,舍弃可用性。也就是说在极端情况下,允许出现系统无法访问的情况出现,这个时候往往会牺牲用户体验,让用户保持等待,一直到系统数据一致了之后,再恢复服务。 |
舍弃C(一致性),保留AP(可用性和分区容错性) | 这种是大部分的分布式系统的设计,保证高可用和分区容错,但是会牺牲一致性。 |
舍弃P(分区容错性),保留CA(一致性和可用性) | 如果要舍弃P,那么就是要舍弃分布式系统,CAP也就无从谈起了。可以说P是分布式系统的前提,所以这种情况是不存在的。 |
事务有4个非常重要的特性,即我们常说的(ACID)
2PC ( Two-Phase Commit缩写)即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Preparephase)、提交阶段(commit phase),2是指两个阶段,P是指准备阶段,C是指提交阶段
优点:原理简单,实现方便
缺点:同步阻塞,单点问题,数据不一致,过于保守
1)同步阻塞:二阶段提交协议存在最明显也是最大的一个问题就是同步阻塞,在二阶段提交的执行过程中,所有参与该事务操作的逻辑都处于阻塞状态,也就是说,各个参与者在等待其他参与者响应的过程中,无法进行其他操作。这种同步阻塞极大的限制了分布式系统的性能
2)单点问题:协调者在整个二阶段提交过程中很重要,如果协调者在提交阶段出现问题,那么整个流程将无法运转,更重要的是:其他参与者将会处于一直锁定事务资源的状态中,而无法继续完成事务操作
3)数据不一致:假设当协调者向所有的参与者发送 commit 请求之后,发生了局部网络异常或者是协调者在尚未发送完所有 commit请求之前自身发生了崩溃,导致最终只有部分参与者收到了 commit 请求。这将导致严重的数据不一致问题
4)过于保守:二阶段提交协议没有设计较为完善的容错机制,任意一个节点失败都会导致整个事务的失败
阶段一:CanCommit
1)事务询问:协调者向所有的参与者发送一个包含事务内容的canCommit请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应
2)各参与者向协调者反馈事务询问的响应:参与者在接收到来自协调者的包含了事务内容的canCommit请求后,正常情况下,如果自身认为可以顺利执行事务,则反馈Yes响应,并进入预备状态,否则反馈No响应
阶段二:PreCommit
情况一:执行事务预提交
1)发送预提交请求:协调者向所有参与者节点发出preCommit请求,并进入prepared阶段
2)事务预提交:参与者接收到preCommit请求后,会执行事务操作,并将Undo和Redo信息记录到事务日志中
3)各参与者向协调者反馈事务执行的结果:若参与者成功执行了事务操作,那么反馈Ack
情况二:中断事务
1)发送中断请求:协调者向所有参与者发出abort请求
2)中断事务:无论是收到来自协调者的abort请求或者等待协调者请求过程中超时,参与者都会中断事务
阶段三:doCommit
情况一:事务提交
1)发送提交请求:进入这一阶段,假设协调者处于正常工作状态,并且它接收到了来自所有参与者的Ack响应,那么他将从预提交状态转化为提交状态,并向所有的参与者发送doCommit请求
2)事务提交:参与者接收到doCommit请求后,会正式执行事务提交操作,并在完成提交之后释放整个事务执行过程中占用的事务资源
3)反馈事务提交结果:参与者在完成事务提交后,向协调者发送Ack响应
4)完成事务:协调者接收到所有参与者反馈的Ack消息后,完成事务
情况二:事务回滚
1)发送中断请求:协调者向所有的参与者节点发送abort请求
2)事务回滚:参与者收到abort请求后,会根据记录的Undo信息来执行事务回滚,并在完成回滚之后释放整个事务执行期间占用的资源
3)反馈事务回滚结果:参与者在完成事务回滚后,向协调者发送Ack消息
4)中断事务:协调者接收到所有参与者反馈的Ack消息后,中断事务
Paxos算法是Lamport提出的一种基于消息传递的分布式一致性算法
假设有一组可以提出提案的进程集合,那么对于一个一致性算法需要保证以下几点
节点的异常大致可以分为四种类型:
➢一般情况下,leader 节点定时发送 heartbeat 到 follower 节点
➢由于某些异常导致leader不再发送heartbeat,或follower无法收到heartbeat
➢当某一follower发生election timeout时,其状态变更为candidate,并向其他follower发起投票
➢当超过半数的follower接受投票后,这一节点将成为新的leader,leader的步进数加1,并开始向follower同步日志
➢当一段时间之后,如果之前的leader再次加入集群,则两个leader比较彼此的步进数,步进数低的leader将切换自己的状态为follower
➢较早前leader中不一致的日志将被清除,并与现有leader中的日志保持一致
➢集群中的某个follower节点发生异常,不再同步日志以及接收heartbeat
➢经过一段时间之后,原来的follower节点重新加入集群
➢这一节点的日志将从当时的leader处同步
➢初始状态下集群中的所有节点都处于follower状态
➢两个节点同时成为candidate发起选举
➢两个candidate都只得到了少部分follower的接受投票
➢candidate继续向其他的follower询问
➢由于一些follower已经投过票了,所以均返回拒绝接受
➢candidate也可能向一个candidate询问投票
➢在步进数相同的情况下,candidate将拒绝接受另一个candidate的请求
➢由于第一次未选出leader,candidate将随机选择一个等待间隔(150ms ~ 300ms)再次发起投票
➢如果得到集群中半数以上的follower的接受,这一candidate将成为leader
➢稍后另一个candidate也将再次发起投票
➢由于集群中已经选出leader,candidate将收到拒绝接受的投票
➢在被多数节点拒绝之后,并已知集群中已存在leader后,这一candidate节点将终止投票请求、切换为follower,从leader节点同步日志
➢新来的follower节点加入集群,此时已经有了leader
➢这一节点的日志将从当时的leader处同步
日志复制的过程:Leader选出后,就开始接收客户端的请求。Leader把请求作为日志条目(Log entries)加入到它的日志中,然后并行的向其他服务器发起AppendEntries RPC复制日志条目。当这条日志被复制到大多数服务器上,Leader将这条日志应用到它的状态机并向客户端返回执行结果
过程步骤描述
➢客户端的每一个请求都包含被复制状态机执行的指令
➢leader把这个指令作为一条新的日志条目添加到日志中,然后并行发起RPC给其他的服务器,让他们复制这条信息
➢跟随者响应ACK,如果follower宕机或者运行缓慢或者丢包,leader会不断的重试,直到所有的follower最终都复制了所有的日志条目
➢通知所有的Follower提交日志,同时领导人提交这条日志到自己的状态机中,并返回给客户端
如下图所示,Client请求Server,Server转发请求到具体的Node获取请求结果。Server需要与三个Node节点保持心跳连接,确保Node可以正常工作
注:收到心跳可以确认节点正常,但是收不到心跳也不能认为该节点就已经宣告“死亡”。此时,可以通过一些方法帮助Server做决定: 周期检测心跳机制、累计失效检测机制
Server端每间隔t秒向Node集群发起监测请求,设定超时时间,如果超过超时时间,则判断"死亡"
在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java领域中有很多可实现远程通讯的技术,例如:RMI、Hessian、SOAP、ESB和JMS等
一个完整的RPC架构里面包含了四个核心的组件,分别是Client、Client Stub、Server、Server Stub;这里的Stub可以理解为存根
结果返回过程
服务端
客户端
服务端
User.java
,需要实现序列化接口Serializablepackage com.yuyz.entity;
import java.io.Serializable;
/**
* 引用对象应该是可序列化对象,这样才能在远程调用的时候
* 1. 序列化对象
* 2. 拷贝
* 3. 在网络中传输
* 4. 服务端反序列化
* 5. 获取参数进行方法调用
* 这种方式其实是将远程对象引用传递的方式转化为值传递的方式
*/
public class User implements Serializable {
private String name;
private Integer age;
public String getName() {
return name;
}
// 省略set/get方法和toString方法
Hello.java
和实现类HelloImpl.java
package com.yuyz.server;
import com.yuyz.entity.User;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* 远程服务对象接口必须继承Remote接口;同时方法必须抛出RemoteExceptino异常
*/
public interface Hello extends Remote {
/**
* 方法
* @param user
* @return
* @throws RemoteException
*/
public String sayHello(User user) throws RemoteException;
}
package com.yuyz.server.impl;
import com.yuyz.entity.User;
import com.yuyz.server.Hello;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class HelloImpl extends UnicastRemoteObject implements Hello {
public HelloImpl() throws RemoteException {
super();
}
public String sayHello(User user) throws RemoteException {
System.out.println("服务端收到请求,name:" + user.getName());
return "success";
}
}
ServerMain.java
package com.yuyz;
import com.yuyz.server.Hello;
import com.yuyz.server.impl.HelloImpl;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class ServerMain {
public static void main(String[] args) {
try {
// 创建一个远程对象,同时也会创建stub对象、skeleton对象
Hello hello = new HelloImpl();
// 本地主机上的远程对象注册表Registry的实例,并指定端口为8888,这一步必不可少(Java默认端口是1099),必不可缺的一步,缺少注册表创建,则无法绑定对象到远程注册表上
LocateRegistry.createRegistry(8888);
// 绑定的URL标准格式为:rmi://host:port/name
try {
Naming.bind("//127.0.0.1:8888/hello", hello);
} catch (AlreadyBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
System.out.println("服务端启动完成!");
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
客户端
pom.xml
文件中引入服务端jar包<dependency>
<groupId>com.yuyzgroupId>
<artifactId>rmi_serverartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
MyClient.java
package com.yuyz.client;
import com.yuyz.entity.User;
import com.yuyz.server.Hello;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class MyClient {
public static void main(String[] args) {
try {
// 在RMI服务注册表中查找名称为RHello的对象,并调用其上的方法
Hello hello = (Hello) Naming.lookup("//127.0.0.1:8888/hello");
User user = new User();
user.setName("zhangsan");
user.setAge(20);
String result = hello.sayHello(user);
System.out.println(result);
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
同步(synchronize)、异步(asychronize)是指应用程序和内核的交互而言的
同步
异步
阻塞和非阻塞是针对于进程访问数据的时候,根据IO操作的就绪状态来采取不同的方式
阻塞
非阻塞
服务端
package com.yuyz.server;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
public class NIOServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket();
SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8081);
serverSocket.bind(socketAddress);
while (true) {
Socket socket = serverSocket.accept(); // 同步阻塞
System.out.println("服务端监听到请求");
Thread thread = new Handle(socket);
thread.start();
}
}
}
package com.yuyz.server;
import java.io.IOException;
import java.net.Socket;
public class Handle extends Thread {
private Socket socket;
public Handle(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
byte[] bytes = new byte[1024];
int len = socket.getInputStream().read(bytes); // 同步阻塞
System.out.println(new String(bytes, 0, len));
socket.getOutputStream().write(bytes, 0, len);
socket.getOutputStream().flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
package com.yuyz.client;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class NIOClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 8081);
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello".getBytes());
outputStream.flush();
byte[] bytes = new byte[1024];
int read = inputStream.read(bytes);
System.out.println(new String(bytes, 0, read));
}
}
NIO缺点
Netty优点
// 通道就绪事件
public void channelActive(ChannelHandlerContext ctx)
// 通道读取数据事件
public void channelRead(ChannelHandlerContext ctx, Object msg)
// 数据读取完毕事件
public void channelReadComplete(ChannelHandlerContext ctx)
// 通道发生异常事件
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
// 把一个业务处理类(handler)添加到链中的第一个位置
ChannelPipeline addFirst(ChannelHandler... handlers)
// 把一个业务处理类(handler)添加到链中的最后一个位置
ChannelPipeline addLast(ChannelHandler... handlers)
// 关闭通道
ChannelFuture close()
// 刷新
ChannelOutboundInvoker flush()
// 将数据写到ChannelPipeline中当前ChannelHandler 的下一个 ChannelHandler 开始处理(出站)
ChannelFuture writeAndFlush(Object msg)
// 返回当前正在进行IO操作的通道
Channel channel()
// 等待异步操作执行完毕
ChannelFuture sync()
// 构造方法
public NioEventLoopGroup()
// 断开连接, 关闭线程
public Future<?> shutdownGracefully()
// 该方法用于服务器端,用来设置两个EventLoop
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
// 该方法用于客户端, 用来设置一个 EventLoop
public B group(EventLoopGroup group)
// 该方法用来设置一个服务器端的通道实现
public B channel(Class<? extends C> channelClass)
// 用来给ServerChannel添加配置
public <T> B option(ChannelOption<T> option, T value)
// 用来给接收到的 通道添加配置
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value)
// 该方法用来设置业务处理类(自定 义的handler)
public ServerBootstrap childHandler(ChannelHandler childHandler)
// 该方法用于服务器端, 用来设置占用的端口号
public ChannelFuture bind(int inetPort)
// 该方法用于客户端, 用来连接服务器端
public ChannelFuture connect(String inetHost, int inetPort)
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
<version>4.1.6.Finalversion>
dependency>
NettyServer.java
package com.yuyz.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
// 接受客户端请求
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
// 1.创建 NioEventLoopGroup的两个实例:bossGroup workerGroup
// bossGroup接收客户端传过来的请求
EventLoopGroup bossGroup = new NioEventLoopGroup();
// workerGroup处理请求
EventLoopGroup workerGroup = new NioEventLoopGroup();
// 2、创建服务启动辅助类:组装一些必要的组件
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 设置组,第一个bossGroup负责连接, workerGroup负责连接之后的io处理
serverBootstrap
.group(bossGroup, workerGroup)
// channel方法指定服务器监听的通道类型
.channel(NioServerSocketChannel.class)
// 设置channel handler , 每一个客户端连接后,给定一个监听器进行处理
.childHandler(
new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
// 传输通道
ChannelPipeline pipeline = nioSocketChannel.pipeline();
// 在通道上添加对通道的处理器 , 该处理器可能还是一个监听器
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
// 监听器队列上添加我们自己的处理方式
pipeline.addLast(
new SimpleChannelInboundHandler<String>() {
protected void channelRead0(
ChannelHandlerContext channelHandlerContext, String msg)
throws Exception {
System.out.println(msg);
System.out.println("server 接收到消息:" + msg);
}
});
}
});
ChannelFuture f = serverBootstrap.bind(8000).sync();
System.out.println("服务端启动 完成!");
f.channel().closeFuture().sync();
}
}
NettyClient.java
package com.yuyz.client;
import java.util.Date;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
// 线程池的实例
NioEventLoopGroup group = new NioEventLoopGroup();
// 客户端的启动辅助类
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(group)
// channel方法指定通道类型
.channel(NioSocketChannel.class)
// 通道初始化了
.handler(
new ChannelInitializer<Channel>() {
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(new StringEncoder());
}
});
Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
while (true) {
channel.writeAndFlush(new Date() + ": hello world!");
Thread.sleep(2000);
}
}
}
pom.xml
文件中引入netty依赖<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
<version>4.1.6.Finalversion>
dependency>
UserService.java
package com.lagou;
public interface UserService {
public String sayHello(String word);
}
UserServerImpl.java
package com.lagou.server;
import com.lagou.UserService;
import com.lagou.handler.UserServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class UserServerImpl implements UserService {
public String sayHello(String word) {
System.out.println("调用成功--参数:" + word);
return "调用成功--参数:" + word;
}
public static void startServer(String hostName, int port) {
try {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.group(eventLoopGroup)
.channel(NioServerSocketChannel.class)
.childHandler(
new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
pipeline.addLast(new UserServerHandler());
}
});
serverBootstrap.bind(hostName, port).sync();
System.out.println("server start!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
UserServerHandler.java
package com.lagou.handler;
import com.lagou.server.UserServerImpl;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class UserServerHandler extends ChannelInboundHandlerAdapter {
/**
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("UserServerHandler的channelRead方法被调用");
// 如何符合约定,则调用本地方法,返回数据
if (msg.toString().startsWith("UserService")) {
// msg的构成:UserServer#sayHello#参数
String result =
new UserServerImpl().sayHello(msg.toString().substring(msg.toString().lastIndexOf("#")));
ctx.writeAndFlush(result);
}
}
}
ServerBoot.java
package com.lagou.boot;
import com.lagou.server.UserServerImpl;
public class ServerBoot {
public static void main(String[] args) {
UserServerImpl.startServer("127.0.0.1", 8999);
}
}
RPCConsumer.java
package com.lagou.client;
import com.lagou.handler.UserClientHandler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class RPCConsumer {
// 线程池
public static ExecutorService executorService =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public static UserClientHandler client;
public Object creatProxy(Class<?> serverClass, String param) {
return Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[] {serverClass},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (client == null) {
initClient();
}
// 设置参数
client.serParam(param + args[0]);
return executorService.submit(client).get();
}
});
}
private void initClient() {
try {
client = new UserClientHandler();
NioEventLoopGroup loopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(loopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(client);
}
});
bootstrap.connect("127.0.0.1", 8999).sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
UserClientHandler.java
package com.lagou.handler;
import java.util.concurrent.Callable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class UserClientHandler extends ChannelInboundHandlerAdapter implements Callable {
private ChannelHandlerContext context;
private String param;
private String result;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
this.context = ctx;
}
/**
* 收到服务数据,唤醒等待的线程
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
this.result = msg.toString();
notify();
}
/**
* 写数据,然后等待唤醒
*
* @return
* @throws Exception
*/
@Override
public synchronized Object call() throws Exception {
context.writeAndFlush(param);
wait();
return result;
}
public void serParam(String param) {
this.param = param;
}
}
ConsumerBoot.java
package com.lagou.boot;
import com.lagou.UserService;
import com.lagou.client.RPCConsumer;
public class ConsumerBoot {
public static final String providerName = "UserService#sayHello#";
public static void main(String[] args) throws InterruptedException {
RPCConsumer rpcConsumer = new RPCConsumer();
UserService userService = (UserService) rpcConsumer.creatProxy(UserService.class, providerName);
while (true) {
Thread.sleep(2000);
String result = userService.sayHello("are you ok ?");
System.out.println(result);
}
}
}