SLB 四层协议转发 阿里云使用举例

 

1 Server端发布到 ECS集群代码举例


 
 
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class NettyNioServerHandler extends ChannelInboundHandlerAdapter{

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 将msg转换成Netty的ByteBuf对象
        ByteBuf buf = (ByteBuf) msg;
        // buf.readableBytes()获取缓冲区可读的字节数
        // 根据可读的字节数创建新的数组
        byte[] req = new byte[buf.readableBytes()];
        // 将缓冲区的字节数组复制到新建的字节byte数组
        buf.readBytes(req);
        // 对这个字节数组进编码
        String body = new String(req, "utf-8");
        logger.warn("服务端收到客户端发来的的消息是: " + body);
        //log.warn("【收到客户端请求】服务端收到客户端发来的的消息是: " + body);
 
        // 判断客户端发来的消息和服务端预设值的消息是否相同
        // 如果相同就返回给客户端当前的时间
        String str = "NOW TIME";
        String nowTime = "NOTE RIGHT";
        if (str.trim().equals(body.trim())){
            nowTime = new Date().toString();
        }
        ByteBuf resp = Unpooled.copiedBuffer(nowTime.getBytes());
        // 异步发送应答消息给客户端
        ctx.write(resp);
    }
 
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 将消息发送队列中的消息写入到socketChannel中发送给对方
        /**
         * 为了防止频繁的唤醒selector进行消息发送,Netty的write方法并不直接将消息写入socketChannel中
         * 调用write方法只是把待发送的消息放到发送缓冲区数组,
         * 在通过调用flush方法,将缓冲区中的消息全部写到socketChannel中中
         */
        ctx.flush();
    }
 
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
       // 当发生异常的时候,关闭ChannelHandlerContext,释放和ChannelHandlerContext相关联的句柄等资源
        ctx.close();
    }
}
 
 
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyNioServer {

    private static volatile boolean running = false;

    private ChannelFuture sync;
 
    public void bind(int port)throws Exception{
        // 配置服务端的NIO线程组,NioEventLoopGroup是个线程组,包含了一组NIO线程
        // 专门用于网路时间的处理,实际上它们就是Reactor线程组
        NioEventLoopGroup groupParent = new NioEventLoopGroup();
        NioEventLoopGroup groupChild = new NioEventLoopGroup();
 
        try {
            // 创建ServerBootstrap对象,它是Netty用于启动Nio服务的辅助类启动器
            // 目的是降低服务端的开发复杂度
            ServerBootstrap bootstrap = new ServerBootstrap();
            // 将两个线程组当参数传递到ServerBootstrap中
            // 设置创建的channel为NioServerSocketChannel
            // 配置NioServerSocketChannel的TCP参数,此处的backlog设置为1024
            // 绑定IO事件的处理类ChildChannelHandler,用于处理网络IO事件,例如记录日志,对消息进行编码等
            bootstrap.group(groupParent,groupChild)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChildChannelHandler());
            // 调用bind方法绑定端口,调用同步阻塞方法sync等待绑定操作成功
            // 返回值主要用于异步操作的通知回调
            ChannelFuture future = bootstrap.bind(port).sync();
            // 等待服务端监听端口关闭
            sync = future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            // 退出,释放系统资源
            groupParent.shutdownGracefully();
            groupChild.shutdownGracefully();
        }
    }
 
    private class ChildChannelHandler extends ChannelInitializer {
 
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            socketChannel.pipeline().addLast(new NettyNioServerHandler());
        }


    }

    public static void init() throws Exception {
        if(!running) {
            synchronized (NettyNioServer.class) {
                if(!running) {
                    running = true;
                    new NettyNioServer().bind(8099);
                }
            }
        }
    }
 
 
    public static void main(String[] args)throws Exception {
        int port = 8089;
        if (args != null && args.length > 0){
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }
 
        }
        new NettyNioServer().bind(port);
 
    }
 
 
}

2 创建SLB并开启监听

SLB 四层协议转发 阿里云使用举例_第1张图片

SLB 四层协议转发 阿里云使用举例_第2张图片

 

3 客户端代码举例


 
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
 
import java.util.logging.Logger;
 
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    /**
     * 日志
     */
    private static  final Logger logger = Logger.getLogger(NettyClientHandler.class.getName());
 
    private final ByteBuf firstMessage;
 
    public NettyClientHandler() {
        byte[] req = "NOW TIME".getBytes();
        firstMessage = Unpooled.buffer(req.length);
        firstMessage.writeBytes(req);
    }
 
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 调用ChannelHandlerContext的writeAndFlush方法将请求消息发送给服务端
        ctx.writeAndFlush(firstMessage);
    }
 
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        // buf.readableBytes()获取缓冲区可读的字节数
        // 根据可读的字节数创建新的数组
        byte[] req = new byte[buf.readableBytes()];
        // 将缓冲区的字节数组复制到新建的字节byte数组
        buf.readBytes(req);
        // 编码
        String body = new String(req, "utf-8");
        // 打印服务端返回的消息
        System.out.println("客户端收到服务端返回的消息是: " + body);
    }
 
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 释放资源
        logger.warning("不期而遇的异常:" + cause.getMessage());
        ctx.close();
    }
}
 
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
 
public class NettyNioClient {
 
    public void connect(int port, String host)throws Exception{
        // 配置服务端的NIO线程组,NioEventLoopGroup是个线程组,包含了一组NIO线程
        // 专门用于网路时间的处理,实际上它们就是Reactor线程组
        NioEventLoopGroup group = new NioEventLoopGroup();
 
        try {
            // 创建ServerBootstrap对象,它是Netty用于启动Nio服务的辅助类启动器
            // 目的是降低服务端的开发复杂度
            Bootstrap bootstrap = new Bootstrap();
            // 于服务端不同channel(NioSocketChannel.class)
            /**
             * handler,创建匿名内部类,实现initChannel方法,
             * 作用是当创建NioSocketChannel成功之后
             * 在进行初始化时,将它的channelHandler设置到ChannelPipeline中,
             * 用于处理网络IO事件
             */
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            // 发起异步连接操作,调用同步阻塞方法等待连接成功
            ChannelFuture sync = bootstrap.connect(host, port).sync();
            // 等待客户端链路关闭
            sync.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            // 退出,释放资源
            group.shutdownGracefully();
        }
 
    }
 
    public static void main(String[] args) throws Exception{
        int port = 9000;
        if (args != null && args.length > 0){
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }
 
        }
        new NettyNioClient().connect(port,"118.190.123.nn");
    }
}

4 运行结果


 

参考:https://help.aliyun.com/knowledge_detail/39455.html#h2-url-3

TCP监听健康检查机制

针对四层TCP监听,为了提高健康检查效率,健康检查通过定制的TCP探测来获取状态信息,如下图所示。

SLB 四层协议转发 阿里云使用举例_第3张图片

TCP监听的检查机制如下:

  1. LVS节点服务器根据监听的健康检查配置,向后端ECS的内网IP+【健康检查端口】发送TCP SYN数据包。
  2. 后端ECS收到请求后,如果相应端口正在正常监听,则会返回SYN+ACK数据包。
  3. 如果在【响应超时时间】之内,LVS节点服务器没有收到后端ECS返回的数据包,则认为服务无响应,判定健康检查失败;并向后端ECS发送RST数据包中断TCP连接。
  4. 如果在【响应超时时间】之内,LVS节点服务器成功收到后端ECS返回的数据包,则认为服务正常运行,判定健康检查成功,而后向后端ECS发送RST数据包中断TCP连接。
  5. 说明
    正常的TCP三次握手,LVS节点服务器在收到后端ECS返回的SYN+ACK数据包后,会进一步发送ACK数据包,随后立即发送RST数据包中断TCP连接

    该实现机制可能会导致后端ECS认为相关TCP连接出现异常(非正常退出),并在业务软件如Java连接池等日志中抛出相应的错误信息,如Connection reset by peer

    解决方案:

    • TCP监听采用HTTP方式进行健康检查。
    • 在后端ECS配置了获取客户端真实IP后,忽略来自前述负载均衡服务地址段相关访问导致的连接错误。

你可能感兴趣的:(分布式)