Redis

Redis

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)、散列(hashes)、 列表(lists)、 集合(sets)、 有序集合(sorted sets)

redis-memcached

  • 内存管理机制
    • Memcached默认使用Slab Allocation机制管理内存,其主要思想是按照预先规定的大小, 将分配的内存分割成特定长度的块以存储相应长度的key-value数据记录,存在内存碎片问题。类似于Java虚拟机对象内存分配的空闲列表方式。
    • Redis使用现场申请内存的方式来存储数据,并且很少使用free-list等方式来优化内存分配,会在一定程度上存在内存碎片。类似于Java虚拟机对象内存分配的指针碰撞方式
  • 数据持久化
    • memcached不支持内存数据的持久化操作,所有的数据都以in-memory的形式存储。
    • redis支持持久化操作。redis提供了两种不同的持久化方法来讲数据存储到硬盘里面
      • rdb:属于全量数据备份,备份的是数据
      • aof:append only if,增量持久化备份,备份的是指令
  • 缓存数据过期机制
    • Memcached 在删除失效主键时采用消极方法,即 Memcached 内部也不会监视主键是否失效,而是在通过 Get 访问主键时才会检查其是否已经失效
    • Redis 定时、定期等多种缓存失效机制,减少内存泄漏
  • 支持的数据类型
    • Memcached支持单一数据类型,[k,v]
    • redis支持五种数据类型

redis五种数据类型

参考:https://www.jianshu.com/p/78e4c937b56c

redis事务机制

command command-type desc
MULTI 开启事务 MULTI 开始一个事务,然后将多个命令入队到事务中
EXEC 触发事务 EXEC 命令触发事务, 一并执行事务中的所有命令
DISCARD 取消事务 DISCARD 命令用于取消一个事务, 清空客户端的整个事务队列, 然后将客户端从事务状态调整回非事务状态, 最后返回字符串 OK 给客户端
WATCH 监控 WATCH 命令用于在事务开始之前监视任意数量的键;当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败

redis消息订阅发布

发布订阅类似于信息管道,用来进行系统之间消息解耦,类似于消息中间件RocketMQ、RabbitMQ

  • PUBLISH 将信息message发送到指定的频道channel。返回收到消息的客户端数量
  • SUBSCRIBE 订阅给指定频道的信息
  • UNSUBSCRIBE 取消订阅指定的频道,如果不指定,则取消订阅所有的频道。

redis事务ACID

  • 原子性(Atomicity)

    单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。如果一个事务队列中的所有命令都被成功地执行,那么称这个事务执行成功

  • 一致性(Consistency)

    • 入队错误

      在命令入队的过程中,如果客户端向服务器发送了错误的命令,比如命令的参数数量不对,等等, 那么服务器将向客户端返回一个出错信息, 并且将客户端的事务状态设为 REDIS_DIRTY_EXEC 。

    • 执行错误

      如果命令在事务执行的过程中发生错误,比如说,对一个不同类型的 key 执行了错误的操作, 那么 Redis 只会将错误包含在事务的结果中, 这不会引起事务中断或整个失败,不会影响已执行事务命令的结果,也不会影响后面要执行的事务命令, 所以它对事务的一致性也没有影响

  • 隔离性(Isolation)

    WATCH 命令用于在事务开始之前监视任意数量的键: 当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败

  • 持久性(Durability)

    因为事务不过是用队列包裹起了一组 Redis 命令,并没有提供任何额外的持久性功能,所以事务的持久性由 Redis 所使用的持久化模式决定

springboot整合redis

pom.xml

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
  <version>2.2.5.RELEASE</version>
</dependency>

配置类

package cn.net.redistext.config;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

@Configuration
@EnableCaching
public class RedisConfig {
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // redisTemplate.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(String.class));
        /**Jackson序列化  json占用的内存最小 */
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        /**Jdk序列化   JdkSerializationRedisSerializer是最高效的*/
        /**String序列化*/
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        /**将key value 进行stringRedisSerializer序列化*/
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(stringRedisSerializer);
        /**将HashKey HashValue 进行序列化*/
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
        return stringRedisTemplate;
    }

    @Bean
    public KeyGenerator simpleKeyGenerator() {
        return (o, method, objects) -> {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(o.getClass().getSimpleName());
            stringBuilder.append(".");
            stringBuilder.append(method.getName());
            stringBuilder.append("[");
            for (Object obj : objects) {
                stringBuilder.append(obj.toString());
            }
            stringBuilder.append("]");

            return stringBuilder.toString();
        };
    }
}

单节点-resource.xml

spring.redis.database=0
spring.redis.host=139.224.101.91
spring.redis.port=6379
spring.redis.password=redis123

sentinel-resource.xml

spring.redis.database=0
spring.redis.password=redis123
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=139.224.101.91:26379,139.224.101.91:26380,139.224.101.91:26381

集群-resource.xml

spring.redis.timeout=10000
spring.redis.cluster.nodes=139.224.101.91:7001,139.224.101.91:7002,139.224.101.91:7003,139.224.101.91:7004,139.224.101.91:7005,139.224.101.91:7006
spring.redis.cluster.max-redirects=3

redis分布式锁

分布式锁

分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现

redis分布式锁特点:

  1. 互斥
  2. 不会发生死锁
  3. 锁的创建者与释放者一致

Redis分布式锁的实现

setnx和setex简单实现

package cn.net.redistext.schedule;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class GetLockByNxEx {
    private static Logger logger = LoggerFactory.getLogger(GetLockByNxEx.class);

    public static final String GETLOCKBYNXEX_LOCK_PREFIX = "getLockByNxEx_lock_";

    @Value("${server.port}")
    private String serverPort;

    @Resource
    private RedisTemplate redisTemplate;

    @Scheduled(cron = "1/10 * * * * *")
    public void operatorByLock() {
        String lockKey = GETLOCKBYNXEX_LOCK_PREFIX + "operatorByLock";
        boolean setNxResult = false;

        try {
            // setnx设置值
            setNxResult = redisTemplate.opsForValue().setIfAbsent(lockKey, serverPort);

            // 获取锁失败
            if (!setNxResult) {
                String keyValue = (String) redisTemplate.opsForValue().get(lockKey);
                System.out.println("server port: " + serverPort + "获取锁失败,当前锁被服务端口:" + keyValue + "占用");
                return;
            }else {
                redisTemplate.opsForValue().set(lockKey, serverPort, 3600);
                System.out.println("server port: " + serverPort + "获取锁成功");
                Thread.sleep(5000);
            }
        } catch (InterruptedException e) {
            logger.error("operatorByLock error errorInfo=" + e.getMessage());
        } finally {
            if (setNxResult) {
                if (redisTemplate.hasKey(lockKey)) {
                    redisTemplate.delete(lockKey);
                    System.out.println("server port: " + serverPort + "释放锁成功!!!!");
                }
            }
        }
    }
}

问题分析1

程序运行过程中,Client通过setnx设置值成功,未到setex时,程序宕机或者redis宕机,这种情况下,除非认为干预,不然key无法删除,一直存在redis中。setnx和setex并不是原子性操作

解决方法
  • 引入lua脚本对setnx和setex进行原子性操作
  • 使用redisConnection对setnx和setex进行原子性操作

问题分析2

释放锁先判断再删除,不是原子性操作

解决办法
  • 引入lua脚本,实现释放锁的原子性操作

获取锁-Lua脚本实现

从 Redis 2.6.0 版本开始,通过内置的 Lua 解释器,可以使用 EVAL 命令对 Lua 脚本进行求值。Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。这和使用 MULTI / EXEC 包围的事务很类似。在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。

  • resource下引入getLock.lua脚本

    -- 从参数中定义本地变量
    local lockKey = KEYS[1]
    local lockValue = KEYS[2]
    -- 操作setnx
    local resultNx = redis.call('SETNX', lockKey, lockValue)
    if resultNx == true
    then
    -- setnx成功之后设置setex
    local resultEx = redis.call('SETEX', lockKey, 3600, lockValue)
    return resultNx
    else
    -- setnx失败直接返回
    return resultNx
    end
    
  • 代码实现

    package cn.net.redistext.schedule;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.script.DefaultRedisScript;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.scripting.support.ResourceScriptSource;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.ArrayList;
    import java.util.List;
    
    @Service
    public class GetLockByLua {
        private static Logger logger = LoggerFactory.getLogger(GetLockByLua.class);
    
        public static final String GETLOCKBYNXEX_LOCK_PREFIX = "getLockByLua_lock_";
    
        private DefaultRedisScript<Boolean> lockScript;
    
        @Value("${server.port}")
        private String serverPort;
    
        @Resource
        private RedisTemplate redisTemplate;
    
        @Scheduled(cron = "1/10 * * * * *")
        public void operatorByLock() {
            String lockKey = GETLOCKBYNXEX_LOCK_PREFIX + "operatorByLock";
            boolean setNxResult = false;
    
            try {
                // setnx设置值
                setNxResult = getLockByLua(lockKey, serverPort);
    
                // 获取锁失败
                if (!setNxResult) {
                    String keyValue = (String) redisTemplate.opsForValue().get(lockKey);
                    System.out.println("server port: " + serverPort + "获取锁失败,当前锁被服务端口:" + keyValue + "占用");
                    return;
                }else {
                    System.out.println("server port: " + serverPort + "获取锁成功");
                    Thread.sleep(5000);
                }
            } catch (InterruptedException e) {
                logger.error("operatorByLock error errorInfo=" + e.getMessage());
            } finally {
                if (setNxResult) {
                    if (redisTemplate.hasKey(lockKey)) {
                        redisTemplate.delete(lockKey);
                        System.out.println("server port: " + serverPort + "释放锁成功!!!!");
                    }
                }
            }
        }
    
        /**
         * 获取锁
         */
        private Boolean getLockByLua(String key,String value) {
            lockScript = new DefaultRedisScript<Boolean>();
            lockScript.setScriptSource(
                    new ResourceScriptSource(new ClassPathResource("getLock.lua")));
            lockScript.setResultType(Boolean.class);
            // 封装参数
            List<Object> keyList = new ArrayList<Object>();
            keyList.add(key);
            keyList.add(value);
            return (Boolean) redisTemplate.execute(lockScript, keyList);
        }
    }
    

获取锁-redisConnection实现

package cn.net.redistext.schedule;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;

@Service
public class GetLockByConn {
    private static Logger logger = LoggerFactory.getLogger(GetLockByConn.class);

    public static final String GETLOCKBYNXEX_LOCK_PREFIX = "getLockByNxEx_lock_";

    @Value("${server.port}")
    private String serverPort;

    @Resource
    private RedisTemplate redisTemplate;

    @Scheduled(cron = "1/10 * * * * *")
    public void operatorByLock() {
        String lockKey = GETLOCKBYNXEX_LOCK_PREFIX + "operatorByLock";
        boolean setNxResult = false;

        try {
            // setnx设置值
            setNxResult = setLock(lockKey, serverPort, 3600L);

            // 获取锁失败
            if (!setNxResult) {
                String keyValue = (String) redisTemplate.opsForValue().get(lockKey);
                System.out.println("server port: " + serverPort + "获取锁失败,当前锁被服务端口:" + keyValue + "占用");
                return;
            }else {
                System.out.println("server port: " + serverPort + "获取锁成功");
                Thread.sleep(5000);
            }
        } catch (InterruptedException e) {
            logger.error("operatorByLock error errorInfo=" + e.getMessage());
        } finally {
            if (setNxResult) {
                if (redisTemplate.hasKey(lockKey)) {
                    redisTemplate.delete(lockKey);
                    System.out.println("server port: " + serverPort + "释放锁成功!!!!");
                }
            }
        }
    }


    /**
     * 重写redisTemplate的set方法
     * <p>
     * 命令 SET resource-name anystring NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法。
     * <p>
     * 客户端执行以上的命令:
     * <p>
     * 如果服务器返回 OK ,那么这个客户端获得锁。
     * 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。
     *
     * @param key     锁的Key
     * @param value   锁里面的值
     * @param expire  超时时间(秒)
     * @return
     */
    private boolean setLock(String key, String value, long expire) {
        try {
            Boolean result = (Boolean) redisTemplate.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    return connection.set(key.getBytes(), value.getBytes(), Expiration.seconds(expire) , RedisStringCommands.SetOption.ifAbsent());
                }
            });
            return result;
        } catch (Exception e) {
            logger.error("set redis lock exception" + e.getMessage());
        }
        return false;
    }
}

释放锁-lua脚本实现

  • resource下引入unLock.lua脚本

    -- 从参数中定义本地变量
    local lockKey = KEYS[1]
    local lockValue = KEYS[2]
    -- 获取key的值
    local getValue = redis.call('get', lockKey)
    if getValue == lockValue
    then
    -- 释放锁
    local delResult = redis.call('del', lockKey)
    return delResult
    else
    return false
    end
    
  • 代码实现

    package cn.net.redistext.schedule;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.script.DefaultRedisScript;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.scripting.support.ResourceScriptSource;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.ArrayList;
    import java.util.List;
    
    @Service
    public class GetLockByLua {
        private static Logger logger = LoggerFactory.getLogger(GetLockByLua.class);
    
        public static final String GETLOCKBYNXEX_LOCK_PREFIX = "getLockByLua_lock_";
    
        private DefaultRedisScript<Boolean> lockScript;
    
        @Value("${server.port}")
        private String serverPort;
    
        @Resource
        private RedisTemplate redisTemplate;
    
        @Scheduled(cron = "1/10 * * * * *")
        public void operatorByLock() {
            String lockKey = GETLOCKBYNXEX_LOCK_PREFIX + "operatorByLock";
            boolean setNxResult = false;
    
            try {
                // setnx设置值
                setNxResult = getLockByLua(lockKey, serverPort);
    
                // 获取锁失败
                if (!setNxResult) {
                    String keyValue = (String) redisTemplate.opsForValue().get(lockKey);
                    System.out.println("server port: " + serverPort + "获取锁失败,当前锁被服务端口:" + keyValue + "占用");
                    return;
                }else {
                    System.out.println("server port: " + serverPort + "获取锁成功");
                    Thread.sleep(5000);
                }
            } catch (InterruptedException e) {
                logger.error("operatorByLock error errorInfo=" + e.getMessage());
            } finally {
                if (setNxResult) {
                    unLockByLua(lockKey, serverPort);
                    System.out.println("server port: " + serverPort + "释放锁成功!!!!");
                }
            }
        }
    
        /**
         * 获取锁
         */
        private Boolean getLockByLua(String key,String value) {
            lockScript = new DefaultRedisScript<Boolean>();
            lockScript.setScriptSource(
                    new ResourceScriptSource(new ClassPathResource("getLock.lua")));
            lockScript.setResultType(Boolean.class);
            // 封装参数
            List<Object> keyList = new ArrayList<Object>();
            keyList.add(key);
            keyList.add(value);
            return (Boolean) redisTemplate.execute(lockScript, keyList);
        }
    
        /**
         * 释放锁
         */
        private Boolean unLockByLua(String key,String value) {
            lockScript = new DefaultRedisScript<Boolean>();
            lockScript.setScriptSource(
                    new ResourceScriptSource(new ClassPathResource("unlock.lua")));
            lockScript.setResultType(Boolean.class);
            // 封装参数
            List<Object> keyList = new ArrayList<Object>();
            keyList.add(key);
            keyList.add(value);
            return (Boolean) redisTemplate.execute(lockScript, keyList);
        }
    }
    

过期key清除策略

  • 惰性删除

    只有在访问key时,才会被发现并主动的过期;但是如果一个key不再使用,那么它会一直存在于内存中,造成浪费

  • 定时删除

    设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作

  • 定期删除

    隔一段时间,程序就对数据库进行一次检查,删除一定量的过期键

持久化策略

RDB

RDB 是 Redis 默认的持久化方案。在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件。Redis 重启会通过加载dump.rdb文件恢复数据

  • 文件配置

    # 时间策略
    save 900 1    # 表示900s内有1条是写入命令,就进行一次备份
    save 300 10   # 表示300s内有10条写入,就进行一次备份
    save 60 10000 # 表示60s内有10000条写入命令,就进行一次备份
    
    # 文件名称
    dbfilename dump.rdb
    
    # 如果持久化出错,主进程是否停止写入
    stop-writes-on-bgsave-error yes
    
    # 是否压缩
    rdbcompression yes
    
    # 导入时是否检查
    rdbchecksum yes
    
    # 文件保存路径
    dir /home/redis/soft/redisdata
    
  • 触发RDB方式

    • 手动触发:使用save或bgsave命令
    • 自动触发:
      • 根据配置文件中save m n触发持久化
      • 从节点全量复制时,主节点发送rdb文件给从节点完成复制操作,主节点会触发 bgsave
      • 执行 debug reload 时
      • 执行 shutdown时,如果没有开启aof,也会触发
  • 禁用RDB

    • 配置文件中使用save ""
  • 数据恢复

    • 将dump.rdb 文件拷贝到redis的安装目录的bin目录下,重启redis服务即可。在实际开发中,一般会考虑到物理机硬盘损坏情况,选择备份dump.rdb 。
  • 优缺点

    • 优点
      • 适合大规模的数据恢复。
      • 如果业务对数据完整性和一致性要求不高,RDB是很好的选择。
    • 缺点
      • 数据的完整性和一致性不高,因为RDB可能在最后一次备份时宕机了。
      • 备份时占用内存,因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件,此时内存中的数据是原来的两倍,最后再将临时文件替换之前的备份文件

AOF

Redis 默认不开启。它的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

  • 配置文件

    # 是否开启aof
    appendonly yes
    
    # 文件名称
    appendfilename "appendonly.aof"
    
    # 同步方式
    # always:把每个写命令都立即同步到aof,很慢,但是很安全
    # everysec:每秒同步一次,是折中方案
    # no:redis不处理交给OS来处理,非常快,但是也最不安全
    appendfsync everysec
    
    # aof重写期间是否同步
    no-appendfsync-on-rewrite no
    
    # 重写触发配置
    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb
    
    # 加载aof时如果有错如何处理, yes表示如果aof尾部文件出问题,写log记录并继续执行。no表示提示写入等待修复后写入
    aof-load-truncated yes
    
    # 文件重写策略
    aof-rewrite-incremental-fsync yes
    
  • 触发机制

    • 根据配置文件触发,可以是每次执行触发,可以是每秒触发,可以不同步。
  • 数据恢复

    • 正常情况下,将appendonly.aof 文件拷贝到redis的安装目录的bin目录下,重启redis服务即可。但在实际开发中,可能因为某些原因导致appendonly.aof 文件格式异常,从而导致数据还原失败,可以通过命令redis-check-aof --fix appendonly.aof 进行修复 。
  • 重写机制

    • AOF的工作原理是将写操作追加到文件中,文件的冗余内容会越来越多。而Redis 的重写机制是当AOF文件的大小超过所设定的阈值时,Redis就会对AOF文件的内容压缩。
  • 优缺点

    • 优点:数据的完整性和一致性更高
    • 缺点:因为AOF记录的内容多,文件会越来越大,数据恢复也会越来越慢

布隆过滤器

  • 概念:
    • 布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
  • 优点:
    • 相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数。另外,散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势
  • 缺点
    • 但是布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣

google布隆过滤器实现

  • maven依赖

    <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>21.0</version>
    </dependency>
    
  • 代码实现

    import com.google.common.hash.BloomFilter;
    import com.google.common.hash.Funnels;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.PostConstruct;
    
    @RestController
    @RequestMapping("/bloomFilter")
    @Api(value = "/bloomFilter", tags = {"布隆过滤器"})
    public class BloomFilterController {
    
        private BloomFilter<Integer> bf ;
    
        @RequestMapping("/isExistUser")
        @ApiOperation(value = "/isExistUser", httpMethod = "GET", notes = "判断用户是否存在")
        public boolean isExistUser (Integer userId) {
            return bf.mightContain(userId);
        }
    
    
        /**
         * 初始化布隆过滤器
         * 默认3%的误判率
         * 误判率可以自定义
         */
        @PostConstruct
        public void initBloomFilter() {
            int i = 100;
            bf = BloomFilter.create(Funnels.integerFunnel(), i);
            for (int i1 = 0; i1 < i; i1++) {
                bf.put(i1);
            }
        }
    }
    

redis布隆过滤器实现

安装模板

  • 下载并编译模块

    git clone https://github.com/RedisLabsModules/rebloom
    cd rebloom
    make
    
  • redis配置文件添加

    loadmodule /home/redis/software/rebloom/redisbloom.so
    
  • 常用命令

    BF.ADD userId 111
    BF.EXISTS userId 111
    BF.EXISTS userId 222
    
    [redis@iZuf62iexj3ztw81eg1cnoZ bin]$ ./redis-cli 
    127.0.0.1:6379> BF.ADD userId 111
    (error) NOAUTH Authentication required.
    127.0.0.1:6379> auth redis123
    OK
    127.0.0.1:6379> BF.ADD userId 111
    (integer) 1
    127.0.0.1:6379> BF.EXISTS userId 111
    (integer) 1
    127.0.0.1:6379> BF.EXISTS userId 222
    (integer) 0
    127.0.0.1:6379>
    

代码实现(lua)

  • addBloomFilter.lua脚本

    -- 从参数中定义本地变量
    local bloomName = KEYS[1]
    local value = KEYS[2]
    -- 操作BF.ADD
    local resultNx = redis.call('BF.ADD', bloomName, value)
    return resultNx
    
  • existBloomFilter.lua脚本

    -- 从参数中定义本地变量
    local bloomName = KEYS[1]
    local value = KEYS[2]
    -- 操作BF.EXISTS
    local resultNx = redis.call('BF.EXISTS', bloomName, value)
    return resultNx
    
  • java代码

    package cn.net.redistext.controller;
    
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import io.swagger.models.auth.In;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.script.DefaultRedisScript;
    import org.springframework.scripting.support.ResourceScriptSource;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import java.util.ArrayList;
    import java.util.List;
    
    @RestController
    @RequestMapping("/redisBloomFilter")
    @Api(value = "/redisBloomFilter", tags = {"redis布隆过滤器"})
    public class RedisBloomFilterController {
    
        private static final String BLOOM_FILTER_USER_ID = "USER-INFO";
    
        @Resource
        private RedisTemplate redisTemplate;
    
        @RequestMapping("/addBloomFilter")
        @ApiOperation(value = "/addBloomFilter", httpMethod = "GET", notes = "redisBloomFilter添加元素")
        public boolean addBloomFilter(String userId) {
            DefaultRedisScript<Boolean> addBloomFilter = new DefaultRedisScript<Boolean>();
            addBloomFilter.setScriptSource(
                    new ResourceScriptSource(new ClassPathResource("addBloomFilter.lua")));
            addBloomFilter.setResultType(Boolean.class);
            // 封装参数
            List<Object> keyList = new ArrayList<Object>();
            keyList.add(BLOOM_FILTER_USER_ID);
            keyList.add(userId);
            return (Boolean) redisTemplate.execute(addBloomFilter, keyList);
        }
    
        @RequestMapping("/existBloomFilter")
        @ApiOperation(value = "/existBloomFilter", httpMethod = "GET", notes = "redisBloomFilter判断元素是否存在")
        public boolean existBloomFilter(String userId) {
            DefaultRedisScript<Boolean> existBloomFilter = new DefaultRedisScript<Boolean>();
            existBloomFilter.setScriptSource(
                    new ResourceScriptSource(new ClassPathResource("existBloomFilter.lua")));
            existBloomFilter.setResultType(Boolean.class);
            // 封装参数
            List<Object> keyList = new ArrayList<Object>();
            keyList.add(BLOOM_FILTER_USER_ID);
            keyList.add(userId);
            return (Boolean) redisTemplate.execute(existBloomFilter, keyList);
        }
    }
    

    ​

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容