先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
正文
byteBuf.resetReaderIndex();
return;
}
// 读取消息体并进行反序列化
byte[] payload = new byte[size];
byteBuf.readBytes(payload);
// 这里根据消息头中的extraInfo部分选择相应的序列化和压缩方式
Serialization serialization = SerializationFactory.get(extraInfo);
Compressor compressor = CompressorFactory.get(extraInfo);
if (Constants.isRequest(extraInfo)) {
// 得到消息体
body = serialization.deserialize(compressor.unCompress(payload),
Request.class);
} else {
// 得到消息体
body = serialization.deserialize(compressor.unCompress(payload),
Response.class);
}
}
// 将上面读取到的消息头和消息体拼装成完整的Message并向后传递
Header header = new Header(magic, version, extraInfo, messageId, size);
Message message = new Message(header, body);
out.add(message);
}
}
public class DemoRpcEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext ctx,
Message message, ByteBuf byteBuf) throws Exception {
Header header = message.getHeader();
// 依次序列化消息头中的魔数、版本、附加信息以及消息ID
byteBuf.writeShort(header.getMagic());
byteBuf.writeByte(header.getVersion());
byteBuf.writeByte(header.getExtraInfo());
byteBuf.writeLong(header.getMessageId());
Object content = message.getContent();
if (Constants.isHeartBeat(header.getExtraInfo())) {
byteBuf.writeInt(0); // 心跳消息,没有消息体,这里写入0
return;
}
// 按照extraInfo部分指定的序列化方式和压缩方式进行处理
Serialization serialization = SerializationFactory.get(header.getExtraInfo());
Compressor compressor = CompressorFactory.get(header.getExtraInfo());
byte[] payload = compressor.compress(serialization.serialize(content));
byteBuf.writeInt(payload.length); // 写入消息体长度
byteBuf.writeBytes(payload); // 写入消息体
}
}
## transport 相关实现
正如前文介绍 Netty 线程模型的时候提到,我们不能在 Netty 的 I/O 线程中执行耗时的业务逻辑。在 Demo RPC 框架的 Server 端接收到请求时,首先会通过上面介绍的 DemoRpcDecoder 反序列化得到请求消息,之后我们会通过一个自定义的 ChannelHandler(DemoRpcServerHandler)将请求提交给业务线程池进行处理。
在 Demo RPC 框架的 Client 端接收到响应消息的时候,也是先通过 DemoRpcDecoder 反序列化得到响应消息,之后通过一个自定义的 ChannelHandler(DemoRpcClientHandler)将响应返回给上层业务。
DemoRpcServerHandler 和 DemoRpcClientHandler 都继承自 SimpleChannelInboundHandler,如下图所示:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8afba4614f4a4e7a9eea3a0cf438c412.png)
下面我们就来看一下这两个自定义的 ChannelHandler 实现:
public class DemoRpcServerHandler extends SimpleChannelInboundHandler<Message> {
// 业务线程池
private static Executor executor = Executors.newCachedThreadPool();
@Override
protected void channelRead0(final ChannelHandlerContext channelHandlerContext, Message<Request> message) throws Exception {
byte extraInfo = message.getHeader().getExtraInfo();
if (Constants.isHeartBeat(extraInfo)) { // 心跳消息,直接返回即可
channelHandlerContext.writeAndFlush(message);
return;
}
// 非心跳消息,直接封装成Runnable提交到业务线程池
executor.execute(new InvokeRunnable(message, channelHandlerContext));
}
}
public class DemoRpcClientHandler extends SimpleChannelInboundHandler<Message> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Message<Response> message) throws Exception {
NettyResponseFuture responseFuture =
Connection.IN\_FLIGHT\_REQUEST\_MAP.remove(message.getHeader().getMessageId());
Response response = message.getContent();
// 心跳消息特殊处理
if (response == null && Constants.isHeartBeat(message.getHeader().getExtraInfo())) {
response = new Response();
response.setCode(Constants.HEARTBEAT\_CODE);
}
responseFuture.getPromise().setSuccess(response.getResult());
}
}
注意,这里有两个点需要特别说明一下。一个点是 Server 端的 InvokeRunnable,在这个 Runnable 任务中会根据请求的 serviceName、methodName 以及参数信息,调用相应的方法:
class InvokeRunnable implements Runnable {
private ChannelHandlerContext ctx;
private Message<Request> message;
public InvokeRunnable(Message<Request> message, ChannelHandlerContext ctx) {
this.message = message;
this.ctx = ctx;
}
@Override
public void run() {
Response response = new Response();
Object result = null;
try {
Request request = message.getContent();
String serviceName = request.getServiceName();
// 这里提供BeanManager对所有业务Bean进行管理,其底层在内存中维护了
// 一个业务Bean实例的集合。感兴趣的同学可以尝试接入Spring等容器管
// 理业务Bean
Object bean = BeanManager.getBean(serviceName);
// 下面通过反射调用Bean中的相应方法
Method method = bean.getClass().getMethod(request.getMethodName(), request.getArgTypes());
result = method.invoke(bean, request.getArgs());
} catch (Exception e) {
// 省略异常处理
} finally {
}
Header header = message.getHeader();
header.setExtraInfo((byte) 1);
response.setResult(result); // 设置响应结果
// 将响应消息返回给客户端
ctx.writeAndFlush(new Message(header, response));
}
}
另一个点是 Client 端的 Connection,它是用来暂存已发送出去但未得到响应的请求,这样,在响应返回时,就可以查找到相应的请求以及 Future,从而将响应结果返回给上层业务逻辑,具体实现如下:
public class Connection implements Closeable {
private static AtomicLong ID_GENERATOR = new AtomicLong(0);
public static Map<Long, NettyResponseFuture>
IN_FLIGHT_REQUEST_MAP = new ConcurrentHashMap<>();
private ChannelFuture future;
private AtomicBoolean isConnected = new AtomicBoolean();
public Connection(ChannelFuture future, boolean isConnected) {
this.future = future;
this.isConnected.set(isConnected);
}
public NettyResponseFuture request(Message message, long timeOut) {
// 生成并设置消息ID
long messageId = ID_GENERATOR.incrementAndGet();
message.getHeader().setMessageId(messageId);
// 创建消息关联的Future
NettyResponseFuture responseFuture = new NettyResponseFuture(System.currentTimeMillis(),
timeOut, message, future.channel(), new DefaultPromise(new DefaultEventLoop()));
// 将消息ID和关联的Future记录到IN_FLIGHT_REQUEST_MAP集合中
IN_FLIGHT_REQUEST_MAP.put(messageId, responseFuture);
try {
future.channel().writeAndFlush(message); // 发送请求
} catch (Exception e) {
// 发送请求异常时,删除对应的Future
IN_FLIGHT_REQUEST_MAP.remove(messageId);
throw e;
}
return responseFuture;
}
// 省略getter/setter以及close()方法
}
我们可以看到,Connection 中没有定时清理 IN\_FLIGHT\_REQUEST\_MAP 集合的操作,在无法正常获取响应的时候,就会导致 IN\_FLIGHT\_REQUEST\_MAP 不断膨胀,最终 OOM。你也可以添加一个时间轮定时器,定时清理过期的请求消息,这里我们就不再展开讲述了。
完成自定义 ChannelHandler 的编写之后,我们需要再定义两个类—— DemoRpcClient 和 DemoRpcServer,分别作为 Client 和 Server 的启动入口。DemoRpcClient 的实现如下:
public class DemoRpcClient implements Closeable {
protected Bootstrap clientBootstrap;
protected EventLoopGroup group;
private String host;
private int port;
public DemoRpcClient(String host, int port) {
this.host = host;
this.port = port;
// 创建并配置客户端Bootstrap
clientBootstrap = new Bootstrap();
group = NettyEventLoopFactory.eventLoopGroup(Constants.DEFAULT\_IO\_THREADS, "NettyClientWorker");
clientBootstrap.group(group)
.option(ChannelOption.TCP\_NODELAY, true)
.option(ChannelOption.SO\_KEEPALIVE, true)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.channel(NioSocketChannel.class) // 创建的Channel类型
// 指定ChannelHandler的顺序
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("demo-rpc-encoder", new DemoRpcEncoder());
ch.pipeline().addLast("demo-rpc-decoder", new DemoRpcDecoder());
ch.pipeline().addLast("client-handler", new DemoRpcClientHandler());
}
});
}
public ChannelFuture connect() {
// 连接指定的地址和端口
ChannelFuture connect = clientBootstrap.connect(host, port);
connect.awaitUninterruptibly();
return connect;
}
@Override
public void close() {
group.shutdownGracefully();
}
}
通过 DemoRpcClient 的代码我们可以看到其 ChannelHandler 的执行顺序如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d75908f870364401afbd15c63eaf9bee.png)
客户端 ChannelHandler 结构图
另外,在创建EventLoopGroup时并没有直接使用NioEventLoopGroup,而是在 NettyEventLoopFactory 中根据当前操作系统进行选择,对于 Linux 系统,会使用 EpollEventLoopGroup,其他系统则使用 NioEventLoopGroup。
接下来我们再看DemoRpcServer 的具体实现:
public class DemoRpcServer {
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
private ServerBootstrap serverBootstrap;
private Channel channel;
protected int port;
public DemoRpcServer(int port) throws InterruptedException {
this.port = port;
// 创建boss和worker两个EventLoopGroup,注意一些小细节,
// workerGroup 是按照中的线程数是按照 CPU 核数计算得到的
bossGroup = NettyEventLoopFactory.eventLoopGroup(1,
"NettyServerBoss");
workerGroup = NettyEventLoopFactory.eventLoopGroup(
Math.min(Runtime.getRuntime().availableProcessors() + 1, 32),
"NettyServerWorker");
serverBootstrap = new ServerBootstrap().group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO\_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.TCP\_NODELAY, Boolean.TRUE)
.childOption(ChannelOption.SO\_KEEPALIVE, true) //设置保持活动连接状态
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
// 指定每个Channel上注册的ChannelHandler以及顺序
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("demp-rpc-decoder", new DemoRpcDecoder());
ch.pipeline().addLast("demo-rpc-encoder", new DemoRpcEncoder());
ch.pipeline().addLast("server-handler", new DemoRpcServerHandler());
}
});
}
public ChannelFuture start() throws InterruptedException {
// 监听指定的端口
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (channelFuture.isSuccess()) {
System.out.println("监听端口 6668 成功");
} else {
System.out.println("监听端口 6668 失败");
}
}
});
channel = channelFuture.channel();
channel.closeFuture().sync();
return channelFuture;
}
public void startAndWait() throws InterruptedException {
try {
channel.closeFuture().await();
} catch (InterruptedException e) {
Thread.interrupted();
}
}
public void shutdown() throws InterruptedException {
channel.close().sync();
if (bossGroup != null)
bossGroup.shutdownGracefully().awaitUninterruptibly(15000);
if (workerGroup != null)
workerGroup.shutdownGracefully().awaitUninterruptibly(15000);
}
}
通过对 DemoRpcServer 实现的分析,我们可以知道每个 Channel 上的 ChannelHandler 顺序如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/dd077e76d3044b68bc8640a4b64d564f.png)
服务端 ChannelHandler 结构图
## registry 相关实现
介绍完客户端和服务端的通信之后,我们再来看简易 RPC 框架的另一个基础能力——服务注册与服务发现能力,对应 demo-rpc 项目源码中的 registry 包。
registry 包主要是依赖 Apache Curator 实现了一个简易版本的 ZooKeeper 客户端,并基于 ZooKeeper 实现了注册中心最基本的两个功能:Provider 注册以及 Consumer 订阅。
这里我们先定义一个 Registry 接口,其中提供了注册以及查询服务实例的方法,如下图所示:
public interface Registry {
void registerService(ServiceInstance<T> service) throws Exception;
void unregisterService(ServiceInstance<T> service) throws Exception;
List<ServiceInstance<T>> queryForInstances(String name) throws Exception;
}
ZooKeeperRegistry 是基于 curator-x-discovery 对 Registry 接口的实现类型,其中封装了之前课时介绍的 ServiceDiscovery,并在其上添加了 ServiceCache 缓存提高查询效率。ZooKeeperRegistry 的具体实现如下:
public class ZookeeperRegistry implements Registry {
private Map<String, List<ServiceInstanceListener<T>>> listeners = Maps.newConcurrentMap();
private InstanceSerializer serializer = new JsonInstanceSerializer<>(ServerInfo.class);
private ServiceDiscovery<T> serviceDiscovery;
private ServiceCache<T> serviceCache;
private String address = "localhost:2181";
public void start() throws Exception {
String root = "/demo/rpc";
// 初始化CuratorFramework
CuratorFramework client = CuratorFrameworkFactory.newClient(address, new ExponentialBackoffRetry(1000, 3));
client.start(); // 启动Curator客户端
// client.createContainers(root);
// 初始化ServiceDiscovery
serviceDiscovery = ServiceDiscoveryBuilder.builder(ServerInfo.class)
.client(client).basePath(root)
.serializer(serializer)
.build();
serviceDiscovery.start(); // 启动ServiceDiscovery
// 创建ServiceCache,监Zookeeper相应节点的变化,也方便后续的读取
serviceCache = serviceDiscovery.serviceCacheBuilder()
.name("/demoService")
.build();
// client.start(); // 启动Curator客户端
client.blockUntilConnected(); // 阻塞当前线程,等待连接成功
serviceDiscovery.start(); // 启动ServiceDiscovery
serviceCache.start(); // 启动ServiceCache
}
@Override
public void registerService(ServiceInstance<T> service) throws Exception {
serviceDiscovery.registerService(service);
}
@Override
public void unregisterService(ServiceInstance service) throws Exception {
serviceDiscovery.unregisterService(service);
}
@Override
public List<ServiceInstance<T>> queryForInstances(String name) throws Exception {
// 直接根据name进行过滤ServiceCache中的缓存数据
return serviceCache.getInstances().stream()
.filter(s -> s.getName().equals(name))
.collect(Collectors.toList());
}
}
通过对 ZooKeeperRegistry的分析可以得知,它是基于 Curator 中的 ServiceDiscovery 组件与 ZooKeeper 进行交互的,并且对 Registry 接口的实现也是通过直接调用 ServiceDiscovery 的相关方法实现的。在查询时,直接读取 ServiceCache 中的缓存数据,ServiceCache 底层在本地维护了一个 ConcurrentHashMap 缓存,通过 PathChildrenCache 监听 ZooKeeper 中各个子节点的变化,同步更新本地缓存。这里我们简单看一下 ServiceCache 的核心实现:
public class ServiceCacheImpl implements ServiceCache,
PathChildrenCacheListener{//实现PathChildrenCacheListener接口
// 关联的ServiceDiscovery实例
private final ServiceDiscoveryImpl discovery;
// 底层的PathChildrenCache,用于监听子节点的变化
private final PathChildrenCache cache;
// 本地缓存
private final ConcurrentMap<String, ServiceInstance> instances
= Maps.newConcurrentMap();
public List<ServiceInstance> getInstances(){ // 返回本地缓存内容
return Lists.newArrayList(instances.values());
}
public void childEvent(CuratorFramework client,
PathChildrenCacheEvent event) throws Exception{
switch(event.getType()){
case CHILD_ADDED:
case CHILD_UPDATED:{
addInstance(event.getData(), false); // 更新本地缓存
notifyListeners = true;
break;
}
case CHILD_REMOVED:{ // 更新本地缓存
instances.remove(instanceIdFromData(event.getData()));
notifyListeners = true;
break;
}
}
… // 通知ServiceCache上注册的监听器
}
}
## proxy 相关实现
在简易版 Demo RPC 框架中,Proxy 主要是为 Client 端创建一个代理,帮助客户端程序屏蔽底层的网络操作以及与注册中心之间的交互。
简易版 Demo RPC 使用 JDK 动态代理的方式生成代理,这里需要编写一个 InvocationHandler 接口的实现,即下面的 DemoRpcProxy。其中有两个核心方法:一个是 newInstance() 方法,用于生成代理对象;另一个是 invoke() 方法,当调用目标对象的时候,会执行 invoke() 方法中的代理逻辑。
下面是 DemoRpcProxy 的具体实现:
public class DemoRpcProxy implements InvocationHandler {
private String serviceName; // 需要代理的服务(接口)名称
public Map<Method, Header> headerCache = new ConcurrentHashMap<>();
// 用于与Zookeeper交互,其中自带缓存
private Registry<ServerInfo> registry;
public DemoRpcProxy(String serviceName,
Registry<ServerInfo> registry) throws Exception {
this.serviceName = serviceName;
this.registry = registry;
}
public static <T> T newInstance(Class<T> clazz, Registry<ServerInfo> registry) throws Exception {
// 创建代理对象
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{clazz},
new DemoRpcProxy("demoService", registry));
}
@Override
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
erverInfo> registry) throws Exception {
// 创建代理对象
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{clazz},
new DemoRpcProxy(“demoService”, registry));
}
@Override
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
[外链图片转存中…(img-ypLjST4T-1713270741765)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!