0
点赞
收藏
分享

微信扫一扫

利用CoreDNS枚举集群Service & Port & 解决方案

言午栩 03-26 22:00 阅读 2

前言

在IM聊天系统中,鉴权是一个关键步骤,它确保了系统的安全性。鉴权的位置取决于系统的设计和鉴权逻辑的复杂性。在Netty中,鉴权可以在多个地方进行,但最常见的是在连接建立时(channelActive)和处理接收到的消息时(channelRead或channelRead0)。每种方式都有其适用场景。以及netty为什么不能使用openfeign去调用http接口

目前已经写的文章有。并且有对应视频版本。
git项目地址 【IM即时通信系统(企聊聊)】点击可跳转
sprinboot单体项目升级成springcloud项目 【第一期】
分布式权限 shiro + jwt + redis【第三期】
分布式websocket即时通信(IM)系统构建指南【第七期】
分布式websocket即时通信(IM)系统保证消息可靠性【第八期】
分布式websocket IM聊天系统相关问题问答【第九期】
什么?websocket也有权限!这个应该怎么做?【第十期】
分布式ID是什么,以美团Leaf为例改造融入自己项目【第十一期】
IM聊天系统为什么需要做消息幂等?如何使用Redis以及Lua脚本做消息幂等【第12期】
微信发送一条消息经历哪些过程。企业微信以及钉钉的IM架构对比【第13期】
微信群为什么上限是500人,IM设计系统中的群聊的设计难点【第14期】

【分布式websocket】群聊中的各种难点以及解决推拉结合【第16期】

【分布式webscoket】未读消息如何设计?解决缓存与数据库数据一致性!推送未读消息流程【第17期】

netty什么地方写鉴权

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    // 异步调用鉴权服务
    // 如果鉴权成功,继续处理
    // 如果鉴权失败,关闭连接
}

在channelRead或channelRead0中进行鉴权
对于需要根据接收到的消息内容进行鉴权的场景,可以在channelRead(或对于SimpleChannelInboundHandler,是channelRead0)方法中进行。这种方式允许你基于消息内容(例如,消息中包含的Token或命令)进行更细粒度的鉴权。

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    // 异步调用鉴权服务
    // 根据鉴权结果决定是否处理消息
}

异步鉴权的处理
无论在哪个阶段进行鉴权,异步调用鉴权服务都需要特别注意处理结果的方式。由于鉴权是异步进行的,你需要确保在鉴权完成之前,不继续执行可能依赖于鉴权结果的操作。这可能意味着在鉴权完成之前暂停消息的处理,或者在鉴权失败时立即关闭连接。
总结
在channelActive中进行鉴权适合于连接建立阶段就需要完成的鉴权。
在channelRead或channelRead0中进行鉴权适合于基于消息内容进行鉴权的场景。
异步鉴权需要注意正确处理鉴权结果,确保在鉴权完成之前不进行依赖于鉴权结果的操作。
选择哪种方式进行鉴权取决于你的具体需求和系统设计。在实际应用中,可能还会结合使用多种方式,以满足不同场景下的安全要求。

为什么要异步,同步调用openfeign会阻塞

OpenFeign是一个声明式的HTTP客户端,它简化了在Java应用中创建REST客户端的过程。默认情况下,OpenFeign使用Java的HttpURLConnection或者Spring的RestTemplate(取决于具体配置)来执行HTTP请求,这些都是基于同步阻塞模型的。当你在Netty的事件循环线程中直接调用这样的同步阻塞操作时,会导致当前线程等待HTTP请求完成,从而阻塞了这个线程。

为什么阻塞是个问题
Netty是基于事件循环模型的,设计用来处理成千上万的并发连接,每个事件循环线程可能负责多个连接。如果在这样的线程中执行阻塞操作,如同步的HTTP请求,那么这个线程就不能处理其他事件,直到阻塞操作完成。这会严重影响Netty的性能和可伸缩性,因为:

  1. 延迟增加:事件循环线程被阻塞意味着其他事件(如新的连接请求、数据包到达等)必须等待,增加了处理延迟。
  2. 吞吐量下降:阻塞减少了事件循环线程处理事件的能力,降低了应用的吞吐量。
  3. 资源利用不足:阻塞模型不能充分利用现代多核CPU的能力,因为它在等待I/O操作(如网络请求)完成时浪费了宝贵的CPU周期。

解决方案
为了避免在Netty应用中使用OpenFeign导致的阻塞,可以考虑以下解决方案:

  1. 异步客户端:使用支持异步操作的HTTP客户端,如Netty自身的HTTP客户端、AsyncHttpClient或WebClient(在Spring WebFlux中)。这些客户端提供了非阻塞的API,可以在Netty的事件循环模型中安全使用。
  2. 将阻塞操作移至单独的线程池:如果必须使用OpenFeign或其他同步阻塞的HTTP客户端,可以将这些调用放在专门的线程池中执行,而不是直接在Netty的事件循环线程中执行。这样,即使这些操作阻塞,也不会影响Netty的主事件循环线程。完成后,可以使用Netty的ChannelHandlerContext将结果传回Netty的事件循环线程。

方案一:AsyncHttpClient包异步调用http接口方案

具体代码

我们采用AsyncHttpClient包异步调用http接口方案。同时因为Netty的事件处理是非阻塞的,它不会等待异步操作的结果就继续往下执行。所以我们需要在发起鉴权请求之前,暂停对读事件的处理。在Netty中,你可以通过调用channel.config().setAutoRead(false)来暂停读事件,然后在鉴权完成后,根据鉴权结果决定是否通过调用channel.config().setAutoRead(true)来恢复读事件的处理。

写一个在channelActive 里面的一个异步鉴权方法,调用的是其他服务的http接口,使用,同事添加鉴权完成前暂停消息

要在channelActive方法中实现异步鉴权,并在鉴权完成前暂停消息处理,你可以使用AsyncHttpClient来异步调用其他服务的HTTP接口。以下是一个示例,展示如何实现这个逻辑:
首先,确保你已经添加了AsyncHttpClient依赖到你的项目中。如果你使用Maven,可以添加如下依赖:

<dependency>
    <groupId>org.asynchttpclient</groupId>
    <artifactId>async-http-client</artifactId>
    <version>2.12.3</version> <!-- 请检查最新版本 -->
</dependency>

然后,你可以在Netty的ChannelHandler中实现异步鉴权逻辑:

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.Dsl;
import org.asynchttpclient.Response;

public class AuthHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 暂停读事件的处理,直到鉴权完成
        ctx.channel().config().setAutoRead(false);

        // 使用AsyncHttpClient异步调用鉴权服务
        AsyncHttpClient asyncHttpClient = Dsl.asyncHttpClient();
        asyncHttpClient.prepareGet("http://example.com/api/auth") // 假设这是鉴权服务的URL
                .execute()
                .toCompletableFuture()
                .thenAccept(response -> {
                    // 处理鉴权响应
                    if (isAuthSuccessful(response)) {
                        // 如果鉴权成功,恢复读事件的处理
                        ctx.channel().config().setAutoRead(true);
                    } else {
                        // 如果鉴权失败,关闭连接
                        ctx.close();
                    }
                })
                .exceptionally(throwable -> {
                    // 处理异步调用过程中的异常
                    throwable.printStackTrace();
                    ctx.close();
                    return null;
                });
    }

    private boolean isAuthSuccessful(Response response) {
        // 根据响应判断鉴权是否成功
        // 这里只是一个示例,你需要根据实际响应格式进行判断
        return response.getStatusCode() == 200;
    }
}

这个示例中,当channelActive被触发时,我们首先通过调用ctx.channel().config().setAutoRead(false)来暂停读事件的处理。然后,使用AsyncHttpClient异步调用鉴权服务。在鉴权响应处理逻辑中,如果鉴权成功,我们通过调用ctx.channel().config().setAutoRead(true)来恢复读事件的处理;如果鉴权失败或异步调用过程中发生异常,我们关闭连接。
请注意,这个示例假设了一个简单的鉴权逻辑,你需要根据实际的鉴权服务响应格式来实现isAuthSuccessful方法。此外,记得在实际应用中处理好资源释放问题,例如在合适的时机关闭AsyncHttpClient实例。

channel.config().setAutoRead(true)的细节

调用ctx.channel().config().setAutoRead(false)来暂停读事件的处理不会阻塞Netty的I/O线程,也不会直接影响其他的Channel。这个调用仅仅是告诉Netty不要再自动读取当前Channel的数据,直到你再次调用setAutoRead(true)。这是一个针对单个Channel的设置,不会影响到其他Channel的行为。
不会阻塞Netty线程
Netty的设计是非阻塞的。当你设置setAutoRead(false)时,Netty简单地停止从对应的Channel读取数据,但是它的I/O线程会继续运行,处理其他事件和Channel。这意味着,即使某个Channel的自动读取被禁用,Netty的I/O线程仍然可以服务于其他Channel的读写事件。
不会影响其他Channel
由于setAutoRead(false)的作用域仅限于被调用的Channel,它不会对系统中的其他Channel产生影响。每个Channel都有自己的配置,因此改变一个Channel的配置不会影响到其他Channel。

方案二: openfeign改异步方案

OpenFeign本身是一个同步的客户端库,设计时并没有直接提供异步调用的支持。它的主要目的是简化微服务之间的同步HTTP调用。然而,你可以通过一些方法间接实现异步调用的效果,比如结合CompletableFuture来在另一个线程中执行Feign客户端的调用,从而不阻塞当前线程。

使用CompletableFuture实现异步调用
你可以使用CompletableFuture.supplyAsync来包装Feign客户端的调用,使其在另一个线程中执行,达到异步的效果。这里是一个简单的示例:

import java.util.concurrent.CompletableFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AuthService {

    @Autowired
    private AuthClient authClient; // Feign客户端

    public CompletableFuture<Boolean> authenticateAsync(String token) {
        return CompletableFuture.supplyAsync(() -> {
            // 调用Feign客户端执行同步HTTP请求
            Response response = authClient.authenticate(token);
            return response.getStatus() == 200;
        });
    }
}

在这个示例中,AuthClient是一个Feign客户端,用于调用鉴权接口。authenticateAsync方法通过CompletableFuture.supplyAsync异步执行Feign客户端的调用,并返回一个CompletableFuture,表示异步操作的结果。
注意事项
线程池管理:默认情况下,CompletableFuture.supplyAsync会使用ForkJoinPool.commonPool()作为其执行异步任务的线程池。在高并发场景下,你可能需要使用自定义的线程池来避免潜在的性能问题。
异常处理:异步执行时,需要妥善处理可能发生的异常。可以使用CompletableFuture的exceptionally方法来处理异常。

两种方案对比

使用CompletableFuture来包装Feign客户端的同步调用,以实现异步效果,与直接使用专门设计为异步的HTTP客户端(如AsyncHttpClient)相比,各有优劣。以下是几个比较的维度:
性能和效率
AsyncHttpClient:作为一个异步的HTTP客户端,AsyncHttpClient在设计上就是为了非阻塞IO操作。它能够在单个线程上高效地处理大量的并发HTTP请求,这对于需要高并发处理能力的应用来说是一个很大的优势。
Feign with CompletableFuture:通过CompletableFuture异步执行Feign的同步调用,实际上是在另一个线程中执行了阻塞的HTTP请求。虽然这种方式不会阻塞调用者线程,但它需要为每个HTTP请求分配一个线程(或从线程池中获取),在高并发场景下可能会导致线程资源的快速耗尽,从而影响性能。
编程模型和易用性
AsyncHttpClient:提供了丰富的异步编程接口,可以让开发者更灵活地处理异步的HTTP请求和响应。但是,使用AsyncHttpClient可能需要开发者对异步编程模型有一定的了解,以便正确处理异步操作的结果和异常。
Feign with CompletableFuture:Feign的接口更加简洁,尤其是在与Spring Cloud集成时,使用起来非常方便。通过CompletableFuture包装同步调用,可以让原本不支持异步的Feign客户端在不改变原有同步编程模型的基础上实现异步效果。这种方式对于熟悉同步编程模型的开发者来说更加友好。
错误处理和调试
AsyncHttpClient:异步操作的错误处理通常需要通过回调或者Future的方式来实现,这可能会使错误处理逻辑变得复杂。
Feign with CompletableFuture:虽然也使用了CompletableFuture,但由于最终是在另一个线程中执行同步调用,因此可以利用传统的同步方式来处理错误和异常,对于调试和错误定位来说可能更直接。
结论
如果你的应用需要处理大量的并发HTTP请求,并且对性能有较高的要求,使用专门的异步HTTP客户端(如AsyncHttpClient)可能是更好的选择。
如果你的应用已经在使用Feign,并且希望在不改变现有代码结构的情况下实现异步调用,或者你更倾向于使用同步编程模型,那么使用CompletableFuture来异步执行Feign的同步调用是一个不错的折衷方案。

举报

相关推荐

0 条评论