分布式笔记(三)

1.分布式通信方式

因为分布式服务框架大部门是针对一个大型的业务系统,那么通信是尤为关键的。一般通信方式采用的是长连接和短连接两种。

1.1 长连接和短连接

长连接:当面对大型应用服务时,本地API需要经常调用远程接口服务,那么本地大量的方法都会成为被远程调用的对象,影响跨服务调用最大的因素就是网络延迟,那么如何应对这个问题?这里举个例子,如果连接两地的马路,需要构建那么是不是需要资源去搭建,当你不需要这条马路,需要拆除,等下次用的时候又需要建,那么资源上的浪费可想而知。回到现实,这个问题也是大型应用无法接受的。这里就需要心跳和业务消息来维持链路,从而实现多消息复用一条链路的目的。

其应用场景:实时通信、聊天室、游戏等

短连接:客户端和服务器之间进行一次通信就关闭,一般常用子啊请求-回应的场景。像浏览器浏览页面时,经常发生变化的。这里就能体现短连接的优势:可以及时释放服务器资源,避免长期占用资源,导致服务器压力过大。当然了,频繁的创建和删除也是很浪费系统通信资源的。

所以无论是使用长连接还是短连接都需要根据自己实际业务出发,选择一个合适自己需求的通信方式。

在这里我们介绍的是分布式服务框架,而该框架主要是采用长连接方式实现通信。

1.2 IO模型

上一面讲了通信方式,这里我们介绍下IO模型

1.2.1 BIO模型

​ 在JAVA4之前,开发JAVA的所以Socket通信都是采用同步阻塞模式,同步阻塞模式顾名思义就是请求和回应都在一个通道中进行,这里举个例子方便理解:顾客A来餐厅点菜,我们作为服务员,首先获取A的点菜单,A点菜慢,我们就在这一直等着,等菜点完了我们再把菜单交给厨师长,顾客B同理。 但是这样系统就存在很大的性能瓶颈,如果顾客数量多的情况下,那么餐厅服务员明显是不够的,并且在有限数量是忙不过来的。唯一的解决方式就是增加服务员数量。当然了这种方式虽然能解决高并发和低时效的问题,但是对餐厅的支出成本就不太友好了,并且这么多的服务员也会导致餐厅管理起来十分不方便。

在这里插入图片描述

package com.huangjialun;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author :***
 * @date : 2024/1/23
 * @description :
 */
public class BIOServer {
    public static void main(String[] args) throws IOException {
        // 线程池机制
        // 1.创建一个线程池
        // 2.如果客户端连接,就创建一个线程,与之通信
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        // 创建ServerSocket
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("服务器启动");

        while (true){
            // 监听,等待客户端连接
            final Socket socket = serverSocket.accept();
            System.out.println("连接一个客户端");
            // 创建一个线程
            newCachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    // 可以和客户端通信
                    try {
                        handler(socket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

    // 编写一个handler方法,与客户端通讯
    public static void handler(Socket socket) throws IOException {
        try{
            System.out.println("连接客户端++++客户端IP:"+socket.getLocalAddress()+"端口:" +socket.getLocalPort());
            byte[] bytes = new byte[1024];
            // 通过Socket获取输入流
            InputStream inputStream = socket.getInputStream();

            while(true){
                int read = inputStream.read(bytes);
                if(read != -1){
                    System.out.println(new String(bytes,0,read)); // 输出客户端发送的数据
                }else{
                    break;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("关闭和client的连接");
            socket.close();
        }
    }

}

1.2.2 NIO模型

​ 如果采用采用BIO模型的服务端,就需要一个独立的Acceptor线程来负责监听客户端的连接,当接收到客户端请求的时候会建立线程来处理该请求,当业务处理完毕后,会通过输出流返回给客户端,然后销毁线程。这样的弊端就是我上面例子所说,线程是JAVA虚拟机宝贵的资源,当线程数激增,不仅是系统性能下架,当并发量到达一个顶点的时候,还会出现线程溢出,创建线程失败等一系列问题。

​ 为了解决这系列问题,JAVA在1.4推出了NIO,俗称同步非阻塞。在IO编程中,如果需要处理多个客户端请求时,可以利用多线程或者IO多路复用技术,IO多路复用技术就是通过吧多个IO的阻塞复用到同一个Select等阻塞中,从而实现了一个线程能同时处理多个客户端请求。于多线程相比,多路复用技术的优势就在于开销更小,系统不需要创建新的线程,也不需要额外去维护这些线程的运行,降低了系统的维护工作量,节省了系统资源。

​ NIO采用多路复用技术,一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用了epoll()代替传统的select实现,所以没有了最大连接句柄的限制。
在这里插入图片描述

2. Netty

如果是自己去实现IO,不仅对技术的要求很高以外,还存在Selector空轮询等一些原生bug。

所以Netty就成了目前流行的通信框架。

2.1 Netty介绍

​ Netty是一个开源的、异步事件驱动的网络应用框架,主要用于Java编程语言。Netty提供了一组易于使用的API和抽象,抽象了网络编程的复杂性。

​ Netty使用基于Java的NIO模型,可以使用少量线程处理许多并发连接,这使得它非常适合构建需要处理大量同时连接的的高性能网络应用,例如Web服务器、聊天服务器、游戏服务器等。

2.2 Netty特点

  1. 高性能:Netty以其高性能、低延迟的能力而闻名。它通过高效的内存管理,事件驱动的架构和非阻塞IO实现这一点。
  2. 可扩展性:Netty可以使用少量线程处理千万个并发连接,具有很高的可扩展性。
  3. 协议支持:Netty提供了对各种网络协议的内置支持 ,如HTTP、WebSocket、SSL/TLS、UDP等,以及可以实现自定义协议。
  4. 丰富社区资源:得益于netty优秀的能力,其社区也是十分活跃的。

2.3 Netty服务端实现

2.3.1 导入依赖

第一步通过Maven导入Netty依赖,

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>版本号</version>
</dependency>
2.3.2 创建服务器引导类
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {
    private int port;

    public NettyServer(int port) {
        this.port = port;
    }
 
    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new YourRequestHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture future = bootstrap.bind(port).sync();
            future.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        NettyServer server = new NettyServer(port);
        server.run();
    }
}

​ BossGroup主要用于接受客户端连接的线程池,其构造方法与处理I/O读写的线程池相同(workGroup),都是通过new NioEventLoopGroup创建,bossGroup一般线程数设置为1,因为主要负责接受客户端的连接,不做任何复杂的逻辑处理。

2.3.3 创建请求处理器

创建一个请求处理器类,用于处理接受到的请求,根据实际业务需求进行编辑

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class YourRequestHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 处理接收到的请求
        ByteBuf byteBuf = (ByteBuf) msg;
        // 向客户端发送响应
        ByteBuf response = ctx.alloc().buffer();
        ctx.writeAndFlush(response);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 处理异常
        cause.printStackTrace();
        ctx.close();
    }
}
2.3.4启动服务器

在main方法中去实例化NettyServer类。并调用run方法来启动服务器

public static void main(String[] args) throws Exception {
    int port = 8080;
    NettyServer server = new NettyServer(port);
    server.run();
}

2.4 Netty客户端实现

2.4.1 导入依赖

通过Maven导入Netty依赖

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>版本号</version>
</dependency>
2.4.2 创建客户端引导类

创建一个客户端引导类,用于配置和启动Netty客户端,在该类中,需要设置服务器的地址和端口号,以及对应的处理器主要用来接收响应。

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyClient {
    private String host;
    private int port;

    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void run() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new YourResponseHandler());
                        }
                    })
                    .option(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture future = bootstrap.connect(host, port).sync();
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        String host = "服务器地址";
        int port = 服务器端口号;
        NettyClient client = new NettyClient(host, port);
        client.run();
    }
}
2.4.3 创建响应处理器

创建一个处理器类,用来处理接收到的响应,可以根据业务需求进一步实现处理逻辑。

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class YourResponseHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 在连接建立时发送请求数据
        ByteBuf request = ctx.alloc().buffer();
        ctx.writeAndFlush(request);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 处理接收到的响应
        ByteBuf response = (ByteBuf) msg;
        response.release();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 处理异常
        cause.printStackTrace();
        ctx.close();
    }
}
2.4.4 启动客户端
public static void main(String[] args) throws Exception {
    String host = "服务器地址";
    int port = 服务器端口号;
    NettyClient client = new NettyClient(host, port);
    client.run();
}

2.5 Netty 模型

Netty主要是基于主从Reactor多线程模型

Netty原理:

  1. Netty抽象出两组线程池BossGroup专门负责接收客户端的连接,WorkGroup专门负责网络的读写
  2. BossGroup和WorkerGroup类型都是NIOEventLoopGroup,BossGroup主要接收传入连接,一旦boss接受连接,就会处理已接受连接的流量,并将已接受的连接注册到worker,使用多少个线程以及它们如何映射到创建的channel线程取决于EventLoopgroup实现,甚至也可以通过构造函数去进行配置。
  3. NIOEventLoopGroup相当于事件循环组,这个组中含有多个事件循环,每个事件循环是NioEventLoop
  4. NioEventLoop表示一个不断循环的执行处理任务的线程,每个NioEventLoop都有一个Selector,用于监听绑定在其Socket的网络通讯
  5. NIOEventLoopGroup可以有多个线程,既可以含有多个NioEventLoop
  6. 每个BossNioEventLoop执行的步骤有三步:

​ 1.轮训accept事件

​ 2.处理accept事件,与client建立连接,生成NioSocketChannel,并将其注册到某一个workerNioEventLoop上的Selector

​ 3.处理任务队列的任务,即runAllTasks

7.每个workerNioEventLoop循环执行的步骤

​ 1.轮训read,write事件

​ 2.处理i/o事件,即read,write事件,在对应的NioSocketChannel处理

​ 3.处理任务队列的任务,即runAllTasks

​ 8.每个WorkerNioEvent处理业务时,都会使用到pipeline(管道),pipeline中包含了channel,即通过piepeline可以获取到对应通道,管道维护了很多的处理器

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

"匠"人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值