Redis

Redis

Redis 是一個(gè)開(kāi)源(BSD許可)的,內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)系統(tǒng),它可以用作數(shù)據(jù)庫(kù)、緩存和消息中間件。 它支持多種類型的數(shù)據(jù)結(jié)構(gòu),如 字符串(strings)、散列(hashes)、 列表(lists)、 集合(sets)、 有序集合(sorted sets)

redis-memcached

  • 內(nèi)存管理機(jī)制
    • Memcached默認(rèn)使用Slab Allocation機(jī)制管理內(nèi)存,其主要思想是按照預(yù)先規(guī)定的大小, 將分配的內(nèi)存分割成特定長(zhǎng)度的塊以存儲(chǔ)相應(yīng)長(zhǎng)度的key-value數(shù)據(jù)記錄,存在內(nèi)存碎片問(wèn)題。類似于Java虛擬機(jī)對(duì)象內(nèi)存分配的空閑列表方式。
    • Redis使用現(xiàn)場(chǎng)申請(qǐng)內(nèi)存的方式來(lái)存儲(chǔ)數(shù)據(jù),并且很少使用free-list等方式來(lái)優(yōu)化內(nèi)存分配,會(huì)在一定程度上存在內(nèi)存碎片。類似于Java虛擬機(jī)對(duì)象內(nèi)存分配的指針碰撞方式
  • 數(shù)據(jù)持久化
    • memcached不支持內(nèi)存數(shù)據(jù)的持久化操作,所有的數(shù)據(jù)都以in-memory的形式存儲(chǔ)。
    • redis支持持久化操作。redis提供了兩種不同的持久化方法來(lái)講數(shù)據(jù)存儲(chǔ)到硬盤(pán)里面
      • rdb:屬于全量數(shù)據(jù)備份,備份的是數(shù)據(jù)
      • aof:append only if,增量持久化備份,備份的是指令
  • 緩存數(shù)據(jù)過(guò)期機(jī)制
    • Memcached 在刪除失效主鍵時(shí)采用消極方法,即 Memcached 內(nèi)部也不會(huì)監(jiān)視主鍵是否失效,而是在通過(guò) Get 訪問(wèn)主鍵時(shí)才會(huì)檢查其是否已經(jīng)失效
    • Redis 定時(shí)、定期等多種緩存失效機(jī)制,減少內(nèi)存泄漏
  • 支持的數(shù)據(jù)類型
    • Memcached支持單一數(shù)據(jù)類型,[k,v]
    • redis支持五種數(shù)據(jù)類型

redis五種數(shù)據(jù)類型

參考:http://www.itdecent.cn/p/78e4c937b56c

redis事務(wù)機(jī)制

command command-type desc
MULTI 開(kāi)啟事務(wù) MULTI 開(kāi)始一個(gè)事務(wù),然后將多個(gè)命令入隊(duì)到事務(wù)中
EXEC 觸發(fā)事務(wù) EXEC 命令觸發(fā)事務(wù), 一并執(zhí)行事務(wù)中的所有命令
DISCARD 取消事務(wù) DISCARD 命令用于取消一個(gè)事務(wù), 清空客戶端的整個(gè)事務(wù)隊(duì)列, 然后將客戶端從事務(wù)狀態(tài)調(diào)整回非事務(wù)狀態(tài), 最后返回字符串 OK 給客戶端
WATCH 監(jiān)控 WATCH 命令用于在事務(wù)開(kāi)始之前監(jiān)視任意數(shù)量的鍵;當(dāng)調(diào)用 EXEC 命令執(zhí)行事務(wù)時(shí), 如果任意一個(gè)被監(jiān)視的鍵已經(jīng)被其他客戶端修改了, 那么整個(gè)事務(wù)不再執(zhí)行, 直接返回失敗

redis消息訂閱發(fā)布

發(fā)布訂閱類似于信息管道,用來(lái)進(jìn)行系統(tǒng)之間消息解耦,類似于消息中間件RocketMQ、RabbitMQ

  • PUBLISH 將信息message發(fā)送到指定的頻道channel。返回收到消息的客戶端數(shù)量
  • SUBSCRIBE 訂閱給指定頻道的信息
  • UNSUBSCRIBE 取消訂閱指定的頻道,如果不指定,則取消訂閱所有的頻道。

redis事務(wù)ACID

  • 原子性(Atomicity)

    單個(gè) Redis 命令的執(zhí)行是原子性的,但 Redis 沒(méi)有在事務(wù)上增加任何維持原子性的機(jī)制,所以 Redis 事務(wù)的執(zhí)行并不是原子性的。如果一個(gè)事務(wù)隊(duì)列中的所有命令都被成功地執(zhí)行,那么稱這個(gè)事務(wù)執(zhí)行成功

  • 一致性(Consistency)

    • 入隊(duì)錯(cuò)誤

      在命令入隊(duì)的過(guò)程中,如果客戶端向服務(wù)器發(fā)送了錯(cuò)誤的命令,比如命令的參數(shù)數(shù)量不對(duì),等等, 那么服務(wù)器將向客戶端返回一個(gè)出錯(cuò)信息, 并且將客戶端的事務(wù)狀態(tài)設(shè)為 REDIS_DIRTY_EXEC 。

    • 執(zhí)行錯(cuò)誤

      如果命令在事務(wù)執(zhí)行的過(guò)程中發(fā)生錯(cuò)誤,比如說(shuō),對(duì)一個(gè)不同類型的 key 執(zhí)行了錯(cuò)誤的操作, 那么 Redis 只會(huì)將錯(cuò)誤包含在事務(wù)的結(jié)果中, 這不會(huì)引起事務(wù)中斷或整個(gè)失敗,不會(huì)影響已執(zhí)行事務(wù)命令的結(jié)果,也不會(huì)影響后面要執(zhí)行的事務(wù)命令, 所以它對(duì)事務(wù)的一致性也沒(méi)有影響

  • 隔離性(Isolation)

    WATCH 命令用于在事務(wù)開(kāi)始之前監(jiān)視任意數(shù)量的鍵: 當(dāng)調(diào)用 EXEC 命令執(zhí)行事務(wù)時(shí), 如果任意一個(gè)被監(jiān)視的鍵已經(jīng)被其他客戶端修改了, 那么整個(gè)事務(wù)不再執(zhí)行, 直接返回失敗

  • 持久性(Durability)

    因?yàn)槭聞?wù)不過(guò)是用隊(duì)列包裹起了一組 Redis 命令,并沒(méi)有提供任何額外的持久性功能,所以事務(wù)的持久性由 Redis 所使用的持久化模式?jīng)Q定

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占用的內(nèi)存最小 */
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        /**Jdk序列化   JdkSerializationRedisSerializer是最高效的*/
        /**String序列化*/
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        /**將key value 進(jìn)行stringRedisSerializer序列化*/
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(stringRedisSerializer);
        /**將HashKey HashValue 進(jìn)行序列化*/
        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();
        };
    }
}

單節(jié)點(diǎn)-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分布式鎖

分布式鎖

分布式鎖是控制分布式系統(tǒng)或不同系統(tǒng)之間共同訪問(wèn)共享資源的一種鎖實(shí)現(xiàn)

redis分布式鎖特點(diǎn):

  1. 互斥
  2. 不會(huì)發(fā)生死鎖
  3. 鎖的創(chuàng)建者與釋放者一致

Redis分布式鎖的實(shí)現(xiàn)

setnx和setex簡(jiǎn)單實(shí)現(xiàn)

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設(shè)置值
            setNxResult = redisTemplate.opsForValue().setIfAbsent(lockKey, serverPort);

            // 獲取鎖失敗
            if (!setNxResult) {
                String keyValue = (String) redisTemplate.opsForValue().get(lockKey);
                System.out.println("server port: " + serverPort + "獲取鎖失敗,當(dāng)前鎖被服務(wù)端口:" + 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 + "釋放鎖成功?。。?!");
                }
            }
        }
    }
}

問(wèn)題分析1

程序運(yùn)行過(guò)程中,Client通過(guò)setnx設(shè)置值成功,未到setex時(shí),程序宕機(jī)或者redis宕機(jī),這種情況下,除非認(rèn)為干預(yù),不然key無(wú)法刪除,一直存在redis中。setnx和setex并不是原子性操作

解決方法
  • 引入lua腳本對(duì)setnx和setex進(jìn)行原子性操作
  • 使用redisConnection對(duì)setnx和setex進(jìn)行原子性操作

問(wèn)題分析2

釋放鎖先判斷再刪除,不是原子性操作

解決辦法
  • 引入lua腳本,實(shí)現(xiàn)釋放鎖的原子性操作

獲取鎖-Lua腳本實(shí)現(xiàn)

從 Redis 2.6.0 版本開(kāi)始,通過(guò)內(nèi)置的 Lua 解釋器,可以使用 EVAL 命令對(duì) Lua 腳本進(jìn)行求值。Redis 使用單個(gè) Lua 解釋器去運(yùn)行所有腳本,并且, Redis 也保證腳本會(huì)以原子性(atomic)的方式執(zhí)行:當(dāng)某個(gè)腳本正在運(yùn)行的時(shí)候,不會(huì)有其他腳本或 Redis 命令被執(zhí)行。這和使用 MULTI / EXEC 包圍的事務(wù)很類似。在其他別的客戶端看來(lái),腳本的效果(effect)要么是不可見(jiàn)的(not visible),要么就是已完成的(already completed)。

  • resource下引入getLock.lua腳本

    -- 從參數(shù)中定義本地變量
    local lockKey = KEYS[1]
    local lockValue = KEYS[2]
    -- 操作setnx
    local resultNx = redis.call('SETNX', lockKey, lockValue)
    if resultNx == true
    then
    -- setnx成功之后設(shè)置setex
    local resultEx = redis.call('SETEX', lockKey, 3600, lockValue)
    return resultNx
    else
    -- setnx失敗直接返回
    return resultNx
    end
    
  • 代碼實(shí)現(xiàn)

    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設(shè)置值
                setNxResult = getLockByLua(lockKey, serverPort);
    
                // 獲取鎖失敗
                if (!setNxResult) {
                    String keyValue = (String) redisTemplate.opsForValue().get(lockKey);
                    System.out.println("server port: " + serverPort + "獲取鎖失敗,當(dāng)前鎖被服務(wù)端口:" + 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);
            // 封裝參數(shù)
            List<Object> keyList = new ArrayList<Object>();
            keyList.add(key);
            keyList.add(value);
            return (Boolean) redisTemplate.execute(lockScript, keyList);
        }
    }
    

獲取鎖-redisConnection實(shí)現(xiàn)

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設(shè)置值
            setNxResult = setLock(lockKey, serverPort, 3600L);

            // 獲取鎖失敗
            if (!setNxResult) {
                String keyValue = (String) redisTemplate.opsForValue().get(lockKey);
                System.out.println("server port: " + serverPort + "獲取鎖失敗,當(dāng)前鎖被服務(wù)端口:" + 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 + "釋放鎖成功?。。?!");
                }
            }
        }
    }


    /**
     * 重寫(xiě)redisTemplate的set方法
     * <p>
     * 命令 SET resource-name anystring NX EX max-lock-time 是一種在 Redis 中實(shí)現(xiàn)鎖的簡(jiǎn)單方法。
     * <p>
     * 客戶端執(zhí)行以上的命令:
     * <p>
     * 如果服務(wù)器返回 OK ,那么這個(gè)客戶端獲得鎖。
     * 如果服務(wù)器返回 NIL ,那么客戶端獲取鎖失敗,可以在稍后再重試。
     *
     * @param key     鎖的Key
     * @param value   鎖里面的值
     * @param expire  超時(shí)時(shí)間(秒)
     * @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腳本實(shí)現(xiàn)

  • resource下引入unLock.lua腳本

    -- 從參數(shù)中定義本地變量
    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
    
  • 代碼實(shí)現(xiàn)

    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設(shè)置值
                setNxResult = getLockByLua(lockKey, serverPort);
    
                // 獲取鎖失敗
                if (!setNxResult) {
                    String keyValue = (String) redisTemplate.opsForValue().get(lockKey);
                    System.out.println("server port: " + serverPort + "獲取鎖失敗,當(dāng)前鎖被服務(wù)端口:" + 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);
            // 封裝參數(shù)
            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);
            // 封裝參數(shù)
            List<Object> keyList = new ArrayList<Object>();
            keyList.add(key);
            keyList.add(value);
            return (Boolean) redisTemplate.execute(lockScript, keyList);
        }
    }
    

過(guò)期key清除策略

  • 惰性刪除

    只有在訪問(wèn)key時(shí),才會(huì)被發(fā)現(xiàn)并主動(dòng)的過(guò)期;但是如果一個(gè)key不再使用,那么它會(huì)一直存在于內(nèi)存中,造成浪費(fèi)

  • 定時(shí)刪除

    設(shè)置鍵的過(guò)期時(shí)間的同時(shí),創(chuàng)建一個(gè)定時(shí)器(timer),讓定時(shí)器在鍵的過(guò)期時(shí)間來(lái)臨時(shí),立即執(zhí)行對(duì)鍵的刪除操作

  • 定期刪除

    隔一段時(shí)間,程序就對(duì)數(shù)據(jù)庫(kù)進(jìn)行一次檢查,刪除一定量的過(guò)期鍵

持久化策略

RDB

RDB 是 Redis 默認(rèn)的持久化方案。在指定的時(shí)間間隔內(nèi),執(zhí)行指定次數(shù)的寫(xiě)操作,則會(huì)將內(nèi)存中的數(shù)據(jù)寫(xiě)入到磁盤(pán)中。即在指定目錄下生成一個(gè)dump.rdb文件。Redis 重啟會(huì)通過(guò)加載dump.rdb文件恢復(fù)數(shù)據(jù)

  • 文件配置

    # 時(shí)間策略
    save 900 1    # 表示900s內(nèi)有1條是寫(xiě)入命令,就進(jìn)行一次備份
    save 300 10   # 表示300s內(nèi)有10條寫(xiě)入,就進(jìn)行一次備份
    save 60 10000 # 表示60s內(nèi)有10000條寫(xiě)入命令,就進(jìn)行一次備份
    
    # 文件名稱
    dbfilename dump.rdb
    
    # 如果持久化出錯(cuò),主進(jìn)程是否停止寫(xiě)入
    stop-writes-on-bgsave-error yes
    
    # 是否壓縮
    rdbcompression yes
    
    # 導(dǎo)入時(shí)是否檢查
    rdbchecksum yes
    
    # 文件保存路徑
    dir /home/redis/soft/redisdata
    
  • 觸發(fā)RDB方式

    • 手動(dòng)觸發(fā):使用savebgsave命令
    • 自動(dòng)觸發(fā):
      • 根據(jù)配置文件中save m n觸發(fā)持久化
      • 從節(jié)點(diǎn)全量復(fù)制時(shí),主節(jié)點(diǎn)發(fā)送rdb文件給從節(jié)點(diǎn)完成復(fù)制操作,主節(jié)點(diǎn)會(huì)觸發(fā) bgsave
      • 執(zhí)行 debug reload 時(shí)
      • 執(zhí)行 shutdown時(shí),如果沒(méi)有開(kāi)啟aof,也會(huì)觸發(fā)
  • 禁用RDB

    • 配置文件中使用save ""
  • 數(shù)據(jù)恢復(fù)

    • 將dump.rdb 文件拷貝到redis的安裝目錄的bin目錄下,重啟redis服務(wù)即可。在實(shí)際開(kāi)發(fā)中,一般會(huì)考慮到物理機(jī)硬盤(pán)損壞情況,選擇備份dump.rdb 。
  • 優(yōu)缺點(diǎn)

    • 優(yōu)點(diǎn)
      • 適合大規(guī)模的數(shù)據(jù)恢復(fù)。
      • 如果業(yè)務(wù)對(duì)數(shù)據(jù)完整性和一致性要求不高,RDB是很好的選擇。
    • 缺點(diǎn)
      • 數(shù)據(jù)的完整性和一致性不高,因?yàn)镽DB可能在最后一次備份時(shí)宕機(jī)了。
      • 備份時(shí)占用內(nèi)存,因?yàn)镽edis 在備份時(shí)會(huì)獨(dú)立創(chuàng)建一個(gè)子進(jìn)程,將數(shù)據(jù)寫(xiě)入到一個(gè)臨時(shí)文件,此時(shí)內(nèi)存中的數(shù)據(jù)是原來(lái)的兩倍,最后再將臨時(shí)文件替換之前的備份文件

AOF

Redis 默認(rèn)不開(kāi)啟。它的出現(xiàn)是為了彌補(bǔ)RDB的不足(數(shù)據(jù)的不一致性),所以它采用日志的形式來(lái)記錄每個(gè)寫(xiě)操作,并追加到文件中。Redis 重啟的會(huì)根據(jù)日志文件的內(nèi)容將寫(xiě)指令從前到后執(zhí)行一次以完成數(shù)據(jù)的恢復(fù)工作。

  • 配置文件

    # 是否開(kāi)啟aof
    appendonly yes
    
    # 文件名稱
    appendfilename "appendonly.aof"
    
    # 同步方式
    # always:把每個(gè)寫(xiě)命令都立即同步到aof,很慢,但是很安全
    # everysec:每秒同步一次,是折中方案
    # no:redis不處理交給OS來(lái)處理,非常快,但是也最不安全
    appendfsync everysec
    
    # aof重寫(xiě)期間是否同步
    no-appendfsync-on-rewrite no
    
    # 重寫(xiě)觸發(fā)配置
    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb
    
    # 加載aof時(shí)如果有錯(cuò)如何處理, yes表示如果aof尾部文件出問(wèn)題,寫(xiě)log記錄并繼續(xù)執(zhí)行。no表示提示寫(xiě)入等待修復(fù)后寫(xiě)入
    aof-load-truncated yes
    
    # 文件重寫(xiě)策略
    aof-rewrite-incremental-fsync yes
    
  • 觸發(fā)機(jī)制

    • 根據(jù)配置文件觸發(fā),可以是每次執(zhí)行觸發(fā),可以是每秒觸發(fā),可以不同步。
  • 數(shù)據(jù)恢復(fù)

    • 正常情況下,將appendonly.aof 文件拷貝到redis的安裝目錄的bin目錄下,重啟redis服務(wù)即可。但在實(shí)際開(kāi)發(fā)中,可能因?yàn)槟承┰驅(qū)е耡ppendonly.aof 文件格式異常,從而導(dǎo)致數(shù)據(jù)還原失敗,可以通過(guò)命令redis-check-aof --fix appendonly.aof 進(jìn)行修復(fù) 。
  • 重寫(xiě)機(jī)制

    • AOF的工作原理是將寫(xiě)操作追加到文件中,文件的冗余內(nèi)容會(huì)越來(lái)越多。而Redis 的重寫(xiě)機(jī)制是當(dāng)AOF文件的大小超過(guò)所設(shè)定的閾值時(shí),Redis就會(huì)對(duì)AOF文件的內(nèi)容壓縮。
  • 優(yōu)缺點(diǎn)

    • 優(yōu)點(diǎn):數(shù)據(jù)的完整性和一致性更高
    • 缺點(diǎn):因?yàn)锳OF記錄的內(nèi)容多,文件會(huì)越來(lái)越大,數(shù)據(jù)恢復(fù)也會(huì)越來(lái)越慢

布隆過(guò)濾器

  • 概念:
    • 布隆過(guò)濾器(英語(yǔ):Bloom Filter)是1970年由布隆提出的。它實(shí)際上是一個(gè)很長(zhǎng)的二進(jìn)制向量和一系列隨機(jī)映射函數(shù)。布隆過(guò)濾器可以用于檢索一個(gè)元素是否在一個(gè)集合中。它的優(yōu)點(diǎn)是空間效率和查詢時(shí)間都遠(yuǎn)遠(yuǎn)超過(guò)一般的算法,缺點(diǎn)是有一定的誤識(shí)別率和刪除困難。
  • 優(yōu)點(diǎn):
    • 相比于其它的數(shù)據(jù)結(jié)構(gòu),布隆過(guò)濾器在空間和時(shí)間方面都有巨大的優(yōu)勢(shì)。布隆過(guò)濾器存儲(chǔ)空間和插入/查詢時(shí)間都是常數(shù)。另外,散列函數(shù)相互之間沒(méi)有關(guān)系,方便由硬件并行實(shí)現(xiàn)。布隆過(guò)濾器不需要存儲(chǔ)元素本身,在某些對(duì)保密要求非常嚴(yán)格的場(chǎng)合有優(yōu)勢(shì)
  • 缺點(diǎn)
    • 但是布隆過(guò)濾器的缺點(diǎn)和優(yōu)點(diǎn)一樣明顯。誤算率是其中之一。隨著存入的元素?cái)?shù)量增加,誤算率隨之增加。但是如果元素?cái)?shù)量太少,則使用散列表足矣

google布隆過(guò)濾器實(shí)現(xiàn)

  • maven依賴

    <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>21.0</version>
    </dependency>
    
  • 代碼實(shí)現(xiàn)

    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 = {"布隆過(guò)濾器"})
    public class BloomFilterController {
    
        private BloomFilter<Integer> bf ;
    
        @RequestMapping("/isExistUser")
        @ApiOperation(value = "/isExistUser", httpMethod = "GET", notes = "判斷用戶是否存在")
        public boolean isExistUser (Integer userId) {
            return bf.mightContain(userId);
        }
    
    
        /**
         * 初始化布隆過(guò)濾器
         * 默認(rèn)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布隆過(guò)濾器實(shí)現(xiàn)

安裝模板

  • 下載并編譯模塊

    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>
    

代碼實(shí)現(xiàn)(lua)

  • addBloomFilter.lua腳本

    -- 從參數(shù)中定義本地變量
    local bloomName = KEYS[1]
    local value = KEYS[2]
    -- 操作BF.ADD
    local resultNx = redis.call('BF.ADD', bloomName, value)
    return resultNx
    
  • existBloomFilter.lua腳本

    -- 從參數(shù)中定義本地變量
    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布隆過(guò)濾器"})
    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);
            // 封裝參數(shù)
            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);
            // 封裝參數(shù)
            List<Object> keyList = new ArrayList<Object>();
            keyList.add(BLOOM_FILTER_USER_ID);
            keyList.add(userId);
            return (Boolean) redisTemplate.execute(existBloomFilter, keyList);
        }
    }
    

    ?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 導(dǎo)言 大家好,我是南橘,從接觸java到現(xiàn)在也有差不多兩年時(shí)間了,兩年時(shí)間,從一名連java有幾種數(shù)據(jù)結(jié)構(gòu)都不懂超...
    南橘ryc閱讀 551評(píng)論 0 0
  • 通過(guò)20問(wèn)來(lái)了解redis ??前人栽樹(shù),后人乘涼。本文中的問(wèn)題來(lái)自公眾號(hào):《java專欄》,幫我們總結(jié)了了解re...
    失心軒閱讀 281評(píng)論 0 0
  • Redis雜談 Redis是近年來(lái)發(fā)展迅速的內(nèi)存數(shù)據(jù)庫(kù),網(wǎng)上也已經(jīng)有多Redis的文章。但不管是英文還是中文,多數(shù)...
    迷失于重逢閱讀 1,713評(píng)論 0 14
  • 主要內(nèi)容 redis 簡(jiǎn)介 為什么要用 redis /為什么要用緩存 為什么要用 redis 而不用 map/gu...
    java成功之路閱讀 643評(píng)論 0 4
  • 燃財(cái)經(jīng)提供創(chuàng)新經(jīng)濟(jì)領(lǐng)域的原創(chuàng)報(bào)道和獨(dú)家評(píng)論 公號(hào)界吳彥祖,條漫公號(hào),一個(gè)大號(hào)。 一聽(tīng)名字,就知道是一個(gè)不怎么正經(jīng)的...
    壹瞰閱讀 245評(píng)論 0 0

友情鏈接更多精彩內(nèi)容