苍穹外卖布隆过滤器改造方案(自用存档)

一、核心架构原则(必须坚守)

先明确底线,避免后续混乱:

  1. 依赖关系:业务模块(如sky-admin-operation-service)→ common层(BloomFilterManager、BloomFilterConfig、工具类),反之绝对不允许;
  2. 边界清晰:每个业务模块的布隆过滤器配置、初始化逻辑,都放在自身模块内,common层只提供“可复用的能力”(Manager、工具方法),不插手任何业务细节;
  3. 扩展无侵入:新增业务过滤器时,只改自身模块,不修改common层和其他业务模块。

二、最终方案:3个组件各司其职(无依赖倒置,复用性拉满)

组件1:common层 - BloomFilterConfiguration(纯工具类,无任何业务依赖)

作用:提供“统一配置构建方法”,避免每个业务模块重复写 setExpectedInsertions 等代码,不依赖任何业务类(Mapper、Constant都不依赖,Constant让业务模块自己传)。

package com.sky.config;

import lombok.experimental.UtilityClass;
import java.util.function.Supplier;

/**
 * common层布隆过滤器配置工具类(纯工具,无业务依赖)
 * 核心:只提供配置构建模板,业务参数由业务模块自己传入
 */
@UtilityClass
public class BloomFilterConfiguration {

    /**
     * 通用配置构建方法(所有业务都能复用)
     * @param filterKey 业务模块自己的CacheConstant key(由业务模块传入,避免依赖)
     * @param dataSupplier 业务模块自己的数据源(Mapper查询,由业务模块传入)
     * @param expectedInsertions 预期数据量(业务模块自己定义)
     * @param falseProbability 误判率(业务模块自己定义)
     * @param order 初始化优先级(业务模块自己定义)
     * @return BloomFilterConfig
     */
    public static BloomFilterConfig buildConfig(
            String filterKey,
            Supplier<List<Long>> dataSupplier,
            Long expectedInsertions,
            double falseProbability,
            int order
    ) {
        BloomFilterConfig config = new BloomFilterConfig(filterKey, dataSupplier);
        config.setExpectedInsertions(expectedInsertions);
        config.setFalseProbability(falseProbability);
        config.setOrder(order);
        config.setEnabled(true);
        return config;
    }

    /**
     * 简化版构建方法(提供默认参数,业务模块可按需覆盖)
     * 避免重复写“预期数据量10万、误判率0.01”等通用参数
     */
    public static BloomFilterConfig buildDefaultConfig(
            String filterKey,
            Supplier<List<Long>> dataSupplier
    ) {
        return buildConfig(filterKey, dataSupplier, 100000L, 0.01, 5);
    }
}

组件2:common层 - BloomFilterManager(改造后不依赖业务配置,只提供能力)

去掉之前的 @Autowired List<BloomFilterConfig>,改为「业务模块主动调用初始化」,避免Manager依赖业务配置,保持common层的纯粹性。

package com.sky.config;

import com.sky.utils.RedisCache;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;

/**
 * common层布隆过滤器管理器(只提供能力,不依赖任何业务)
 * 核心:业务模块主动调用初始化/检查/添加方法,Manager不主动获取任何业务配置
 */
@Slf4j
@Component
public class BloomFilterManager {

    @Autowired
    private RedisCache redisCache;

    private final Map<String, RBloomFilter<Object>> bloomFilterMap = new ConcurrentHashMap<>();

    /**
     * 业务模块主动调用:初始化单个业务的布隆过滤器(核心复用方法)
     * 由业务模块传入自己的配置,Manager只负责执行初始化逻辑
     */
    public void initBloomFilter(BloomFilterConfig config) {
        if (config == null || !config.isEnabled()) {
            log.warn("布隆过滤器配置为空或未启用,跳过初始化");
            return;
        }

        String filterKey = config.getFilterKey();
        if (bloomFilterMap.containsKey(filterKey)) {
            log.info("布隆过滤器 {} 已存在,无需重复初始化", filterKey);
            return;
        }

        // 复用之前的初始化逻辑(获取实例、加载数据等)
        CompletableFuture.runAsync(() -> {
            try {
                RBloomFilter<Object> bloomFilter = redisCache.getBloomFilter(
                        filterKey,
                        config.getExpectedInsertions(),
                        config.getFalseProbability()
                );

                // 检查并初始化过滤器
                if (bloomFilter.isExists()) {
                    bloomFilter.delete();
                    bloomFilter.tryInit(config.getExpectedInsertions(), config.getFalseProbability());
                } else {
                    bloomFilter.tryInit(config.getExpectedInsertions(), config.getFalseProbability());
                }

                // 加载业务数据(dataSupplier由业务模块传入,Manager不关心数据来源)
                List<Long> data = config.getDataSupplier().get();
                if (data != null && !data.isEmpty()) {
                    for (Long id : data) {
                        bloomFilter.add(id);
                    }
                    log.info("✅ 业务布隆过滤器 {} 初始化完成,加载 {} 条数据", filterKey, data.size());
                } else {
                    log.warn("⚠️ 业务布隆过滤器 {} 无数据可加载", filterKey);
                }

                bloomFilterMap.put(filterKey, bloomFilter);
            } catch (Exception e) {
                log.error("❌ 初始化业务布隆过滤器 {} 失败", filterKey, e);
            }
        });
    }

    /**
     * 业务模块主动调用:批量初始化多个过滤器(支持一个业务模块有多个过滤器)
     */
    public void initBloomFilters(List<BloomFilterConfig> configs) {
        if (configs == null || configs.isEmpty()) {
            return;
        }
        configs.forEach(this::initBloomFilter);
    }

    // 以下是原有复用方法(mightContain、add等),完全不变
    public boolean mightContain(String filterKey, Object element) {
        RBloomFilter<Object> bloomFilter = bloomFilterMap.get(filterKey);
        if (bloomFilter == null) {
            log.warn("布隆过滤器 {} 未找到,直接返回true", filterKey);
            return true;
        }
        boolean result = bloomFilter.contains(element);
        log.debug("布隆过滤器检查 - 过滤器: {}, 元素: {}, 结果: {}", filterKey, element, result);
        return result;
    }

    public void add(String filterKey, Object element) {
        RBloomFilter<Object> bloomFilter = bloomFilterMap.get(filterKey);
        if (bloomFilter != null) {
            bloomFilter.add(element);
            log.debug("添加元素到布隆过滤器 - 过滤器: {}, 元素: {}", filterKey, element);
        } else {
            log.warn("布隆过滤器 {} 未找到,无法添加元素 {}", filterKey, element);
        }
    }

    // 其他原有方法(getBloomFilter、printStatus等)保留不变
}

组件3:业务模块 - 自身初始化类(业务逻辑内聚,不污染common)

sky-admin-operation-service(员工业务)为例,在自身模块内创建初始化类,只依赖自己的Mapper和common层的工具类/Manager,完全符合依赖关系。

package com.sky.config;

import com.sky.constant.CacheConstant;
import com.sky.mapper.EmployeeMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

/**
 * 员工业务-布隆过滤器初始化类(放在sky-admin-operation-service模块内)
 * 核心:业务自己的配置、自己的数据源,自己初始化,不依赖其他模块
 */
@Configuration
@Slf4j
@RequiredArgsConstructor
public class EmployeeBloomFilterConfig {

    // 只依赖自身模块的Mapper(符合微服务边界)
    private final EmployeeMapper employeeMapper;
    // 依赖common层的Manager(符合依赖关系:业务→common)
    private final BloomFilterManager bloomFilterManager;

    @PostConstruct
    public void initEmployeeBloomFilter() {
        log.info("开始初始化员工业务布隆过滤器...");

        // 1. 用common层的工具类构建配置(复用工具方法,避免重复代码)
        BloomFilterConfig employeeFilterConfig = BloomFilterConfiguration.buildConfig(
                CacheConstant.EMPLOYEE_BLOOM_FILTER_KEY, // 自身业务的key(来自自身模块的Constant)
                employeeMapper::getAllEmployeeIds,       // 自身业务的数据源(自身Mapper)
                50000L,                                  // 自身业务的预期数据量
                0.005,                                   // 自身业务的误判率
                1                                        // 自身业务的初始化优先级
        );

        // 2. 调用common层的Manager初始化(复用Manager的能力)
        bloomFilterManager.initBloomFilter(employeeFilterConfig);

        log.info("员工业务布隆过滤器初始化请求已提交(异步执行)");
    }
}

三、扩展新业务(如店铺)的复用流程(完全无侵入)

当你后续需要在 sky-shop-service(店铺业务)添加布隆过滤器时,只需3步,不修改任何common层代码:

步骤1:店铺业务模块内创建初始化类

// sky-shop-service/com/sky/config/ShopBloomFilterConfig.java
@Configuration
@RequiredArgsConstructor
public class ShopBloomFilterConfig {

    private final ShopMapper shopMapper; // 店铺模块自己的Mapper
    private final BloomFilterManager bloomFilterManager; // 复用common层Manager

    @PostConstruct
    public void initShopBloomFilter() {
        // 复用common层的工具类构建配置
        BloomFilterConfig shopFilterConfig = BloomFilterConfiguration.buildDefaultConfig(
                CacheConstant.SHOP_BLOOM_FILTER_KEY, // 店铺模块自己的key
                shopMapper::getAllShopIds            // 店铺模块自己的数据源
        );

        // 复用common层的Manager初始化
        bloomFilterManager.initBloomFilter(shopFilterConfig);
    }
}

步骤2:店铺Service中调用Manager(复用员工业务的逻辑)

@Service
public class ShopServiceImpl implements ShopService {

    @Autowired
    private BloomFilterManager bloomFilterManager; // 复用Manager

    @Override
    public Shop searchById(Long id) {
        // 复用Manager的mightContain方法(和员工业务逻辑完全一致)
        if (!bloomFilterManager.mightContain(CacheConstant.SHOP_BLOOM_FILTER_KEY, id)) {
            throw new ResourceNotFoundException("店铺不存在");
        }

        // 后续缓存、数据库查询逻辑...
    }

    @Override
    public void save(ShopDTO shopDTO) {
        // 保存数据库后,复用Manager的add方法(和员工业务逻辑完全一致)
        Shop shop = convertToEntity(shopDTO);
        shopMapper.save(shop);
        bloomFilterManager.add(CacheConstant.SHOP_BLOOM_FILTER_KEY, shop.getId());

        // 后续缓存逻辑...
    }
}

步骤3:启动店铺服务,自动初始化

店铺服务启动时,ShopBloomFilterConfig 会自动执行,调用common层的Manager初始化过滤器,全程不影响common层和其他业务模块。

四、为什么这个方案架构合规且高效?

架构要求方案如何满足
无依赖倒置common层不依赖任何业务模块,业务模块依赖common层,符合微服务依赖原则
边界清晰每个业务的过滤器配置、数据源、初始化逻辑都在自身模块内,不污染其他模块
复用高效复用了BloomFilterManager的核心能力(初始化、检查、添加),复用了BloomFilterConfiguration的配置构建方法,避免重复代码
扩展无侵入新增业务只改自身模块,不修改common层和其他业务模块,符合“开闭原则”
故障隔离每个业务的过滤器通过key隔离,一个业务的过滤器初始化失败,不影响其他业务

五、最终结论:BloomFilterConfiguration 不是冗余,是“复用工具”

现在的 BloomFilterConfiguration 完全符合你的需求:

  1. 不破坏微服务架构:无任何业务依赖,只提供工具方法;
  2. 复用BloomFilterManager:所有业务都通过自动注入+key的方式复用Manager;
  3. 减少重复代码:扩展新业务时,无需重复写配置setter、初始化逻辑,只需调用工具方法;
  4. 无冗余:每个组件各司其职,common层提供能力,业务层自主使用,架构清晰。

之前的混乱是因为错误设计了“集中初始化”,现在改为“业务自主初始化+common提供复用能力”,既满足你后续扩展的需求,又坚守了微服务架构原则,完美解决你的顾虑!

posted @ 2025-11-24 22:55  WILK  阅读(0)  评论(0)    收藏  举报  来源