V2EX 10月30日 11:36
利用多公网IP实现代理服务器IP切换的性能优化
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

面对大量公网IP和浏览器性能瓶颈,本文介绍了一种利用Netty编写的代理服务器实现IP切换的优化方案。通过修改Netty代理服务器逻辑,使其绑定不同端口,并提供接口动态指定端口号和出口IP,从而实现代理IP的灵活切换。该方案旨在解决多开浏览器占用过多性能的问题,通过少量浏览器实例承载大量IP出口能力,提升整体效率。文章探讨了这一实现方式的原理及遇到的实际问题,并寻求解决方案。

💡 **性能瓶颈与优化思路**: 最初的设想是为每个公网IP独立部署一个代理服务器,但多开浏览器极度消耗性能,无法有效利用所有IP。优化思路转变为通过Netty构建一个代理服务器,利用其异步IO特性,通过接口动态切换代理后端使用的出口IP,从而在少量浏览器实例中实现多IP出口。

🔧 **Netty代理服务器的实现**: 代理服务器使用Netty编写,核心逻辑在于通过一个Map来存储端口与出口IP的映射关系。对外提供接口,允许动态修改此映射,Netty代理服务器在连接目标服务器时,会根据指定的端口号查找对应的出口IP进行连接,实现了代理出口IP的动态切换。

❓ **IP切换生效延迟问题**: 在实际测试中,虽然日志显示已成功切换出口IP,但访问查询IP的网站时,仍显示旧的IP地址,存在明显的生效延迟。这表明IP切换的生效机制可能存在问题,需要进一步分析是网络层面的缓存、DNS解析,还是Netty连接建立过程中的特定环节导致了这一现象。

🚀 **潜在的解决方案探讨**: 针对IP切换延迟问题,可能需要检查Netty的连接复用策略、客户端的DNS缓存策略,以及服务器端网络配置。通过更精细化的连接管理或强制刷新DNS等手段,可能有助于解决IP切换不及时的问题,确保代理IP的快速生效。

公司有一台服务器,有很多个公网 IP ,就想着能不能利用起来。

然后现在有一个任务是用浏览器打开指定网址,返回网页源代码,我就打算把这个服务器做成代理服务器。

本来是计划每个 IP 做一个代理服务器,代理服务器根据入口 IP 用对应的 IP 连接目标服务器。

开启每个浏览器的时候设置代理地址,比如 1.2.3.4:8639, 1.2.3.5:8639 这样 。

然后发现多开浏览器非常吃性能,要充分利用所有的公网 IP 得开几十个浏览器,这时候已经卡到动不了了,肯定不行。

所以我就想开 4 个浏览器,每个浏览器设置一个代理,然后通过接口去切换代理后端的出口。

代理服务器是用 netty 写的,逻辑改成了绑定不同端口,然后通过接口指定端口号和出口 IP ,来切换不同端口对应代理的出口 IP 。

其实就是存了一个 Map<Integer, String>,调接口修改这个 map ,netty 代理服务器连接目标服务器的时候使用出口地址去连接。

现在问题在于,调用接口切换出口 IP 后,日志显示已经使用新的出口 IP 了,但是访问查询 IP 的网站,还是使用之前的 IP ,好像要等一段时间才生效,这是什么问题,求各位大佬指教

@Log4j2public class ProxyFrontendHandler extends SimpleChannelInboundHandler<FullHttpRequest> {    private final AddressFunction addressFunction;    public ProxyFrontendHandler(AddressFunction addressFunction) {        this.addressFunction = addressFunction;    }    @Override    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) {        if (HttpMethod.CONNECT.equals(req.method())) {            handleConnectRequest(ctx, req);            return;        }        handleHttpRequest(ctx, req);    }    private void handleConnectRequest(ChannelHandlerContext ctx, FullHttpRequest req) {        List<String> split = StrUtil.split(req.uri(), ":");        String host = CollUtil.getFirst(split);        int port = Convert.toInt(CollUtil.get(split, 1), 443);        Bootstrap bootstrap = new Bootstrap();        bootstrap.group(ctx.channel().eventLoop())                .channel(NioSocketChannel.class)                .handler(new ChannelInitializer<SocketChannel>() {                    @Override                    protected void initChannel(SocketChannel ch) {                        ch.pipeline().addLast(new RelayHandler(ctx.channel()));                    }                });        ChannelFuture connectFuture;        InetSocketAddress remoteAddress = new InetSocketAddress(host, port);        InetSocketAddress sourceAddress = addressFunction.apply(ctx);        if (sourceAddress != null) {            log.info("Using outbound: {} | host: {}", sourceAddress, remoteAddress.getHostString());            connectFuture = bootstrap.connect(remoteAddress, sourceAddress);        } else {            connectFuture = bootstrap.connect(remoteAddress);        }        connectFuture.addListener((ChannelFutureListener) future -> {            if (future.isSuccess()) {                Channel outboundChannel = future.channel();                DefaultFullHttpResponse response = new DefaultFullHttpResponse(                        HttpVersion.HTTP_1_1,                        HttpResponseStatus.OK                );                response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);                ctx.writeAndFlush(response).addListener((ChannelFutureListener) f -> {                    try {                        ctx.pipeline().remove(HttpServerCodec.class);                        ctx.pipeline().remove(HttpObjectAggregator.class);                        ctx.pipeline().addLast(new RelayHandler(outboundChannel));                    } catch (Exception ignored) {                    }                });            } else {                sendErrorResponse(ctx, "无法连接到目标服务器");                closeOnFlush(ctx.channel());            }        });    }    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {        String host = req.headers().get(HttpHeaderNames.HOST);        if (host == null) {            sendErrorResponse(ctx, "缺少 Host 头");            closeOnFlush(ctx.channel());            return;        }        String[] hostParts = host.split(":");        String targetHost = hostParts[0];        int targetPort = hostParts.length > 1 ? Integer.parseInt(hostParts[1]) : 80;        // 修改请求 URI 为绝对路径        req.setUri(req.uri().replace("http://" + host, ""));        req.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);        // 复制请求以避免在异步操作期间被释放        FullHttpRequest copiedReq = req.copy();        // 创建到目标服务器的连接        Bootstrap bootstrap = new Bootstrap();        bootstrap.group(ctx.channel().eventLoop())                .channel(NioSocketChannel.class)                .handler(new ChannelInitializer<SocketChannel>() {                    @Override                    protected void initChannel(SocketChannel ch) {                        ch.pipeline().addLast(new HttpClientCodec());                        ch.pipeline().addLast(new HttpObjectAggregator(1024 * 1024)); // 增加到 1MB                        ch.pipeline().addLast(new RelayHandler(ctx.channel()));                    }                });        ChannelFuture connectFuture;        InetSocketAddress remoteAddress = new InetSocketAddress(targetHost, targetPort);        InetSocketAddress sourceAddress = addressFunction.apply(ctx);        if (sourceAddress != null) {            log.info("Using outbound: {} | host: {}", sourceAddress, remoteAddress.getHostString());            connectFuture = bootstrap.connect(remoteAddress, sourceAddress);        } else {            connectFuture = bootstrap.connect(remoteAddress);        }        connectFuture.addListener((ChannelFutureListener) future -> {            if (future.isSuccess()) {                future.channel().writeAndFlush(copiedReq);            } else {                closeOnFlush(ctx.channel());            }            if (copiedReq.refCnt() != 0) {                copiedReq.release();            }        });    }    private void sendErrorResponse(ChannelHandlerContext ctx, String message) {        FullHttpResponse response = new DefaultFullHttpResponse(                HttpVersion.HTTP_1_1,                HttpResponseStatus.BAD_GATEWAY,                Unpooled.wrappedBuffer(message.getBytes())        );        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());        ctx.writeAndFlush(response);    }    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {        if (cause instanceof SocketException) {            closeOnFlush(ctx.channel());            return;        }        log.error(cause.getMessage());        closeOnFlush(ctx.channel());    }    private void closeOnFlush(Channel ch) {        if (ch.isActive()) {            ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);        }    }}
@Log4j2public class RelayHandler extends ChannelInboundHandlerAdapter {    private final Channel relayChannel;    public RelayHandler(Channel relayChannel) {        this.relayChannel = relayChannel;    }    @Override    public void channelActive(ChannelHandlerContext ctx) {        ctx.read();    }    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) {        if (relayChannel.isActive()) {            relayChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> {                if (future.isSuccess()) {                    ctx.read(); // 继续读取数据                } else {                    future.channel().close();                }            });        } else {            closeOnFlush(ctx.channel());        }    }    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {        log.error(cause);        closeOnFlush(ctx.channel());    }    private void closeOnFlush(Channel ch) {        if (ch.isActive()) {            ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);        }    }}

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Netty 代理服务器 公网IP 性能优化 IP切换 Netty Proxy Server Public IP Performance Optimization IP Switching
相关文章