Explication détaillée de l'utilisation du filtre Bloom dans Redis


1. Introduction au filtre Bloom

1. Qu'est-ce qu'un filtre Bloom

布隆过滤器(英语:Bloom Filter)Il a été proposé par Bloom en 1970. Il s'agit en fait d'un long vecteur binaire et d'une série de fonctions de cartographie aléatoire. principalement 用于判断一个元素是否在一个集合中.

Habituellement, nous rencontrerons de nombreux scénarios commerciaux dans lesquels nous devons juger si un élément se trouve dans une certaine collection. L'idée générale est de sauvegarder tous les éléments de la collection, puis de déterminer par comparaison. Liste chaînée, arbre, table de hachage (également appelée table de hachage, table de hachage) et autres structures de données fonctionnent toutes de cette manière. Mais 随着集合中元素的增加,我们需要的存储空间也会呈现线性增长,最终达到瓶颈。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为O(n),O(logN),O(1).

A cette époque, le Bloom Filter (Bloom Filter) voit le jour.

2. Principe de mise en œuvre du filtre Bloom

Si vous souhaitez déterminer si un élément fait partie d'une collection, la méthode générale qui vous vient à l'esprit consiste à stocker temporairement les données, puis à rechercher si elles existent dans la collection. Cette méthode est applicable lorsque la quantité de données est relativement faible, mais lorsqu'il y a de nombreux éléments dans quelques-uns, la vitesse de récupération deviendra de plus en plus lente.

Le bitmap peut être utilisé : il suffit de vérifier si le point correspondant est 1 pour savoir s'il existe un tel nombre dans l'ensemble. Le filtre Bloom peut être considéré comme une extension du bitmap, mais utilise plusieurs fonctions de mappage de hachage pour réduire la probabilité de collisions de hachage.

L'algorithme est le suivant :
insérer la description de l'image ici

BloomFilter est composé d'un vecteur binaire ou bitmap (bitmap) de taille fixe et d'une série de fonctions de mappage.

  1. Dans l'état initial, pour un tableau de bits de longueur m, tous ses bits sont mis à 0 ;
  2. Lorsqu'une variable est ajoutée à l'ensemble, la variable est mappée sur K points dans le bitmap via K fonctions de mappage, et ils sont définis sur 1 ;
  3. Lorsqu'on demande si une variable existe, il suffit de vérifier si ces points sont tous égaux à 1 pour savoir si elle existe dans l'ensemble avec une forte probabilité.
  • Si l'un de ces points a 0, la variable interrogée ne doit pas être là ;
  • Si les deux valent 1, la variable est interrogée 很可能存在.
    Pourquoi dit-on qu’il peut exister, mais pas nécessairement ? En effet, la fonction de mappage elle-même est une fonction de hachage et la fonction de hachage aura des collisions.

3. Taux d’erreurs de jugement

Le taux d’erreur d’évaluation se réfère ici à BloomFilter 判断某个 key 存在,但它实际不存在的概率.

L'erreur d'évaluation du filtre Bloom est due au fait que plusieurs entrées sont hachées à la même position de bit, il est donc impossible de déterminer quelle entrée est générée. Par conséquent, la cause fondamentale de l'erreur d'évaluation est que le même bit est mappé plusieurs fois. et mis à 1.

La fonction de hachage elle-même aura des collisions. Bien que le filtre Bloom utilise plusieurs calculs de hachage pour réduire la probabilité de collisions, cela ne peut pas être complètement évité. Cela entraînera le chevauchement des bits d'un objet qui ont subi plusieurs calculs de hachage avec les bits de autres objets. , si les bits d'un nouvel objet sont tous mis à 1 lorsqu'ils sont stockés dans d'autres objets, alors une erreur d'évaluation se produira.

Cette situation a également provoqué le problème de suppression du filtre Bloom, car 布隆过滤器的每一个 bit 并不是独占的,很有可能多个元素共享了某一位. Si nous supprimons ce bit directement, cela affectera d’autres éléments.

Diagramme d'erreur de jugement du filtre Bloom :
1. État initial : le tableau de bits dans le filtre Bloom est 0 ;
2. Ajoutez l'objet X et l'objet Y au filtre Bloom, et après plusieurs calculs de hachage, le tableau de bits 1, 2, 4, 5, et 7 sont remplis avec 1 ;
3. Lorsque le filtre Bloom est utilisé pour déterminer si l'objet Z existe, plusieurs calculs de hachage sont d'abord effectués sur l'objet Z, et les bits correspondants sont 4, 5 et 7. Cependant, 4, Les figures 5 et 7 viennent d'être remplies de 1 par l'objet X et l'objet Y. À ce stade, on jugera à tort que l'objet Z existe déjà.
4. Dans les cas extrêmes, lorsque tous les bits du tableau de bits sont définis sur 1, le filtre Bloom échouera.
insérer la description de l'image ici

caractéristique:

  • Un élément n’existe pas nécessairement si le résultat du jugement est l’existence, mais il ne doit pas exister lorsque le résultat du jugement est la non-existence.
  • Les filtres Bloom peuvent ajouter des éléments, mais ne peuvent pas les supprimer, car la suppression d'éléments augmentera le taux de faux positifs.

Comment réduire le taux d’erreurs d’évaluation ?

  1. Augmentez le nombre de fonctions de hachage pour réduire le risque de collisions de bits ;
  2. Augmentez la taille du Bitmap pour éviter un grand nombre de bits couverts et remplis.

4. Scénarios d'utilisation du filtre Bloom

Les applications typiques des filtres Bloom sont :

  • 数据库防止穿库, Google Bigtable, HBase, Cassandra et Postgresql utilisent BloomFilter pour réduire les recherches sur disque pour des lignes ou des colonnes qui n'existent pas. Éviter les recherches de disque coûteuses peut améliorer considérablement les performances des opérations de requête de base de données.
  • Dans un scénario commercial, juger si un utilisateur a lu une certaine vidéo ou un certain article, comme Douyin ou Toutiao, conduira bien sûr à certaines erreurs de jugement, mais ne permettra pas aux utilisateurs de voir le contenu en double.
  • 缓存宕机、缓存击穿场景, juge généralement si l'utilisateur est dans le cache, si c'est le cas, le résultat sera renvoyé directement, sinon, la base de données sera interrogée, si une vague de données froides arrive, cela provoquera un grand nombre de pannes de cache, provoquant un effet d'avalanche, à ce moment vous pouvez utiliser le filtre Bloom comme cache. L'index, uniquement dans le filtre Bloom, est utilisé pour interroger le cache, et s'il n'est pas trouvé, il est pénétré dans la base de données. S'il n'est pas dans le bloomer, revenez directement.
  • WEB拦截器, si la requête est la même, elle sera interceptée pour éviter des attaques répétées. Lorsque l'utilisateur demande pour la première fois, placez les paramètres de demande dans le filtre Bloom, et lorsque l'utilisateur fait la deuxième demande, jugez d'abord si les paramètres de demande sont touchés par le filtre Bloom. Peut améliorer le taux de réussite du cache.
  • Le serveur de cache proxy Web Squid utilise des filtres Bloom dans les résumés de cache. Google Chrome utilise les filtres Bloom pour accélérer la navigation sécurisée

En général, 布隆过滤器是用于大数据场景下的重复判断,并且允许有一定误差存在l'utilisation la plus courante est de résoudre le problème de pénétration du cache.

5. Comparaison entre la table de hachage et le filtre Bloom

Les tables de hachage peuvent également être utilisées pour déterminer si un élément fait partie d'un ensemble, mais Bloom Filter n'a besoin que de 1/8 ou 1/4 de la complexité spatiale d'une table de hachage pour résoudre le même problème.
La table de hachage stocke les éléments réels de la collection, tandis que le filtre Bloom remplit uniquement le tableau binaire en fonction des résultats de calculs de hachage multiples des éléments et ne stocke pas les objets réels.

Bloom Filter peut insérer des éléments, mais ne peut pas supprimer des éléments existants. Plus l’ensemble contient d’éléments, plus le taux de faux positifs est élevé, mais il n’y aura pas de faux négatifs.

2. Combat réel du filtre Bloom dans Redis

" Sur le papier, c'est toujours superficiel, mais je sais que cette question doit être réglée. " Voyons ensuite comment éviter le problème de pénétration du cache lors de la requête d'informations sur les commandes via le filtre Bloom.
insérer la description de l'image ici

1. Introduire la dépendance Redisson

  <dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson-spring-boot-starter</artifactId>
      <version>3.16.7</version>
  </dependency>

2. Créez un tableau de commande

CREATE TABLE `tb_order` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单Id',
  `order_desc` varchar(50) NOT NULL COMMENT '订单描述',
  `user_id` bigint NOT NULL COMMENT '用户Id',
  `product_id` bigint NOT NULL COMMENT '商品Id',
  `product_num` int NOT NULL COMMENT '商品数量',
  `total_account` decimal(10,2) NOT NULL COMMENT '订单金额',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `ik_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=51 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

3. Configurer Redis

Propriétés de connexion Redis ajoutées :

spring.redis.host=192.168.206.129
spring.redis.port=6379
spring.redis.password=123456

Configurer redisTemplate, principalement pour définir la stratégie de sérialisation Jackson

@Configuration
public class RedisConfig {
    
    

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedissonConnectionFactory redisConnectionFactory) {
    
    
        //设置序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //反序列化,该设置不能省略,不然从redis获取json转为实体时会报错
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.WRAPPER_ARRAY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        RedisSerializer stringSerializer = new StringRedisSerializer();
        //key序列化
        redisTemplate.setKeySerializer(stringSerializer);
        //value序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        return redisTemplate;
    }

}

4. Placez le filtre Bloom

/**
 * 配置布隆过滤器
 */
@Configuration
public class BloomFilterConfig {
    
    
    @Autowired
    private RedissonClient redissonClient;
    /**
     * 创建订单号布隆过滤器
     * @return
     */
    @Bean
    public RBloomFilter<Long> orderBloomFilter() {
    
    
        //过滤器名称
        String filterName = "orderBloomFilter";
        // 预期插入数量
        long expectedInsertions = 10000L;
        // 错误比率
        double falseProbability = 0.01;
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(filterName);
        bloomFilter.tryInit(expectedInsertions, falseProbability);
        return bloomFilter;
    }
}

5. Créer une commande

BloomFilter dans Redisson a 2 méthodes principales :

  • bloomFilter.add(orderId) Ajouter un identifiant au filtre bloom
  • bloomFilter.contains(orderId) Déterminer si l'identifiant existe
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    
    

    @Resource
    private RBloomFilter<Long> orderBloomFilter;

    @Resource
    private TbOrderMapper  tbOrderMapper;

    @Resource
    private RedisTemplate<String,Object> redisTemplate;


    @Override
    public void createOrder(TbOrder tbOrder) {
    
    
        //1、创建订单
        tbOrderMapper.insert(tbOrder);

        //2、订单id保存到布隆过滤器
        log.info("布隆过滤器中添加订单号:{}",tbOrder.getId());
        orderBloomFilter.add(tbOrder.getId());
    }

    @Override
    public TbOrder get(Long orderId) {
    
    
        TbOrder tbOrder = null;
        //1、根据布隆过滤器判断订单号是否存在
        if(orderBloomFilter.contains(orderId)){
    
    
            log.info("布隆过滤器判断订单号{}存在",orderId);
            String key = "order:"+orderId;
            //2、先查询缓存
            Object object = redisTemplate.opsForValue().get(key);
            if(object != null){
    
    
                log.info("命中缓存");
                tbOrder =  (TbOrder)object;
            }else{
    
    
                //3、缓存不存在则查询数据库
                log.info("未命中缓存,查询数据库");
                tbOrder = tbOrderMapper.selectById(orderId);
                redisTemplate.opsForValue().set(key,tbOrder);
            }
        }else{
    
    
            log.info("判定订单号{}不存在,不进行查询",orderId);
        }
        return tbOrder;
    }
}

6. Tests unitaires

    @Test
    public void testCreateOrder() {
    
    
        for (int i = 0; i < 50; i++) {
    
    
            TbOrder tbOrder = new TbOrder();
            tbOrder.setOrderDesc("测试订单"+(i+1));
            tbOrder.setUserId(1958L);
            tbOrder.setProductId(102589L);
            tbOrder.setProductNum(5);
            tbOrder.setTotalAccount(new BigDecimal("300"));
            tbOrder.setCreateTime(new Date());
            orderService.createOrder(tbOrder);
        }
    }

    @Test
    public void testGetOrder() {
    
    
        TbOrder  tbOrder = orderService.get(25L);
        log.info("查询结果:{}", tbOrder.toString());
    }

Résumer

Le principe du filtre Bloom est en réalité très simple, c'est-à-dire bitmap + hachages multiples. Le principal avantage est qu'il permet de déterminer rapidement si un objet existe sous des données à grande échelle en utilisant un très petit espace. L'inconvénient est qu'il existe un possibilité d'erreur de jugement, mais pas Il manquera le jugement, c'est-à-dire que l'objet existant sera définitivement jugé comme existant, et l'objet inexistant aura une probabilité plus faible d'être jugé à tort comme existant, et la suppression de l'objet n'est pas soutenue, car la probabilité d’une erreur de jugement augmentera. L'utilisation la plus courante est de résoudre 缓存穿透des problèmes.

Je suppose que tu aimes

Origine blog.csdn.net/w1014074794/article/details/129750865
conseillé
Classement