1、需求分析
一些惡意用戶(?可能是黑客、爬蟲、DDoS ?攻擊者)可能頻繁請求服務器資?源,導致資源占用過高。針對這種問題,可以通過IP? 封禁,可以有效拉?黑攻擊者,防止資源?被濫用,保障合法用?戶的正常訪問
2、Nacos 配置管理的核心概念
1、Namespace(命名空間)
命名空間用于隔離不同的配置集?。它允許在同一個 Nacos 集群中將不同的環境(如開發、測試、生?產)或者不同的業務線的配置進行隔離。(默認提供了一個 publ?ic 命名空間)
使用場景:在多租戶系統中,或者需要區分不同的?環境時,可以使用命名空間。例如,開發環境的配置和生產環境的配置?完全隔離,可以通過不同的命名空間來管理。
2、Group(組)
配置組是用于將多個相關的配置?項進行分類管理的邏輯分組機制。每個配置項可以屬于不同的?組,以便于配置管理。
使用場景:當一個應用有多個模塊,?且不同模塊之間共享部分配置時,可以用組來對這些模塊的配?置進行分類和管理。例如,一個系統中的“支付服務”和“訂?單服務”可能需要用不同的組來存儲各自的配置。
3、Data ID
Data I?D 是一個唯一的配置標識?符,通常與具體的應用程序?相關。通過 Data I?D,Nacos 知道如何?獲取特定應用的某個具體配置。
使用場景:每個應用的配置都會有一個獨特的 Data ID。例如,一個支付系統可能有一個配置文件叫 com.payment.pay-service.yaml,這就是它的 Data ID。
4、Config Listener(配置監聽器)
配置監聽器用于讓客戶端實時監聽? Nacos 配置中心中的配置變化,可以自動感知配置的更新?并做出相應的處理
使用場景?:在需要動態調整配置的場景下使用,例如調整緩存大小、切換不?同的服務端點等,應用可以通過監聽器及時感知這些變化并應用新?的配置
3、創建黑名單過濾工具類
InterviewPal 項目 已經使用了 Hu?tool 工具庫,?就用其自帶的 Bi?tMapBloom?Filter 即可。
@Slf4j
public class BlackIpUtils {private static BitMapBloomFilter bloomFilter;// 判斷 ip 是否在黑名單內public static boolean isBlackIp(String ip) {return bloomFilter.contains(ip);}// 重建 ip 黑名單public static void rebuildBlackIp(String configInfo) {if (StrUtil.isBlank(configInfo)) {configInfo = "{}";}// 解析 yaml 文件Yaml yaml = new Yaml();Map map = yaml.loadAs(configInfo, Map.class);// 獲取 ip 黑名單List<String> blackIpList = (List<String>) map.get("blackIpList");// 加鎖防止并發synchronized (BlackIpUtils.class) {if (CollectionUtil.isNotEmpty(blackIpList)) {// 注意構造參數的設置BitMapBloomFilter bitMapBloomFilter = new BitMapBloomFilter(1);for (String ip : blackIpList) {bitMapBloomFilter.add(ip);}bloomFilter = bitMapBloomFilter;} else {bloomFilter = new BitMapBloomFilter(1);}}}
}
注意:
1、synchronized (BlackIpUtils.class)
代表的是這個類的 Class 對象,是 JVM 里唯一的、全局唯一的一個對象實例。換句話說,這個鎖是類級別的鎖,所有線程只要碰到這把鎖,都會排隊等候,不能同時執行里面的代碼塊。
2、 BitMapBloomFilter bitMapBloomFilter = new BitMapBloomFilter(1)
這個構造參數不可以亂傳。如何選擇適合業務的 k 和 m 值呢,幸運的是,布隆過濾器有一個可預測的誤判率(FPP):
其中 n 是已經添加元素的數量; k 哈希的次數; m 布隆過濾器的長度(如比特數組的大小);
極端情況下,當布隆過濾器沒有空閑空間時(滿),每一次查詢都會返回 true 。這也就意味著 m 的選擇取決于期望預計添加元素的數量 n ,并且 m 需要遠遠大于 n 。 實際情況中,布隆過濾器的長度 m 可以根據給定的誤判率(FFP)的和期望添加的元素個數 n 的通過如下公式計算:
3、注意,因為 ?Nacos 配置文件的監聽的粒度比?較粗,只能知曉配置有變更,無法知曉?是新增、刪除還是修改,因此不論是選?擇布隆過濾器還是 HashSet ?最方便的處理邏輯就是重建。
4、創建 Nacos 配置監聽類
新增監聽器代碼?,追求性能的話可以?自定義線程池:
@Slf4j
@Component
public class NacosListener implements InitializingBean {@NacosInjectedprivate ConfigService configService;@Value("${nacos.config.data-id}")private String dataId;@Value("${nacos.config.group}")private String group;@Overridepublic void afterPropertiesSet() throws Exception {log.info("nacos 監聽器啟動");String config = configService.getConfigAndSignListener(dataId, group, 3000L, new Listener() {final ThreadFactory threadFactory = new ThreadFactory() {private final AtomicInteger poolNumber = new AtomicInteger(1);@Overridepublic Thread newThread(@NotNull Runnable r) {Thread thread = new Thread(r);thread.setName("refresh-ThreadPool" + poolNumber.getAndIncrement());return thread;}};final ExecutorService executorService = Executors.newFixedThreadPool(1, threadFactory);// 通過線程池異步處理黑名單變化的邏輯@Overridepublic Executor getExecutor() {return executorService;}// 監聽后續黑名單變化@Overridepublic void receiveConfigInfo(String configInfo) {log.info("監聽到配置信息變化:{}", configInfo);BlackIpUtils.rebuildBlackIp(configInfo);}});// 初始化黑名單BlackIpUtils.rebuildBlackIp(config);}
}
4.1 詳細解讀作用
4.1.1、類定義部分
- @Component:讓這個類在 Spring 啟動時自動加載;
- @Slf4j:自動注入日志記錄器;
- 實現了 InitializingBean,所以會在 Spring 完成依賴注入后執行 afterPropertiesSet()。
4.1.2、注解部分
@Value("${nacos.config.data-id}")private String dataId;@Value("${nacos.config.group}")private String group;
這個注解@Value("${nacos.config.data-id}")
的意思就是說:讀取yml配置文件,令dataId = "interviewPal";
# 配置中心
nacos:config:server-addr: 127.0.0.1:8848 # nacos 地址bootstrap:enable: true # 預加載data-id: interviewPal # 控制臺填寫的 Data IDgroup: DEFAULT_GROUP # 控制臺填寫的 grouptype: yaml # 選擇的文件格式auto-refresh: true # 開啟自動刷新
4.1.3、自定義線程工廠
自定義線程池工廠,給新建的線程起個名字,如:refresh-ThreadPool1、refresh-ThreadPool2。
final ThreadFactory threadFactory = new ThreadFactory() {private final AtomicInteger poolNumber = new AtomicInteger(1);@Overridepublic Thread newThread(@NotNull Runnable r) {Thread thread = new Thread(r);thread.setName("refresh-ThreadPool" + poolNumber.getAndIncrement());return thread;}};
4.1.4、創建線程池
final ExecutorService executorService = Executors.newFixedThreadPool(1, threadFactory);
用自定義的線程工廠 threadFactory 創建了一個固定大小為1的線程池(FixedThreadPool)
5、創建黑名單過濾器
黑名單應該對所有請求生?效(不止是 Controller 的接口),?所以基于 WebFilter 實現而不是 A?OP 切面。WebFilter 的優先級高于? @Aspect 切面,因為它在整個 Web? 請求生命周期中更早進行處理。
請求進入時的順序:
- WebFilter:首先,WebFilter 攔截 HTTP 請求,并可以根據邏輯決定是否繼續執行請求。
- Spring AOP切面(@Aspect):如果請求經過過濾器并進入 Spring 管理的 Bean(例如 Controller 層),此時切面生效,對匹配的Bean 方法進行攔截。
- Controller 層:如果 @Aspect 沒有阻止執行,最終請求到達 @Controller 或 @RestController 的方法。
/*** 全局 IP 黑名單過濾請求攔截器*/
@WebFilter(urlPatterns = "/*", filterName = "blackIpFilter")
public class BlackIpFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {String ipAddress = NetUtils.getIpAddress((HttpServletRequest) servletRequest);if (BlackIpUtils.isBlackIp(ipAddress)) {servletResponse.setContentType("text/json;charset=UTF-8");servletResponse.getWriter().write("{\"errorCode\":\"-1\",\"errorMsg\":\"黑名單IP,禁止訪問\"}");return;}filterChain.doFilter(servletRequest, servletResponse);}}
@WebFilter(urlPatterns = "/*", filterName = "blackIpFilter")
的作用是告訴 Tomcate 這兒有個過濾器,名字叫 blackIpFilter,它得攔截所有請求(/*)
6、 @ServletComponentScan
最后要在啟動類上加上 @ServletComponentScan,這樣過濾器才會被掃描到。