redis分布式锁&可重入锁&续约、缓存注解、同步过程

本文深入探讨了分布式锁的概念及其应用场景,特别是在多实例环境下如何利用Redis实现分布式锁,包括单机Redis的setnx命令、可重入锁的实现以及使用Lua脚本确保原子性。此外,还介绍了基于Redission框架的集群锁解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么要用分布式锁?

假设一个场景,一个服务里面提供了操作某个变量的接口,在单机的时候,可以使用synchronize或者lock进行加锁防止并发问题,但是假如这个服务有3个实例,每个实例可以操作相应的共享资源,这时候三个请求恰好都分发到不同的实例上去,结果是变量不知道被改成什么样了,也许每个服务的实例里的变量都不一样,那么怎么控制这个变量在面对多个请求时所带来的并发问题呢,这时候需要一个粒度更粗的锁,分布式锁就出来了,可以解决多台机器上的对同一个资源操作的并发问题。

单机redis

命令setnx

setnx等同于set if not exists

设置成功返回1,设置失败返回0

命令:getset

先get后set,先获取指再set

getset key value

这两个指令都具有原子性

加锁的过程主要是通过setnx指令确认是否可以上锁,如果可以则直接成功,不可以则需要则查看是否超时,如果已经超时,为了避免死锁,用getset命令来处理多个线程访问的问题,线程A在上锁以后,线程b想继续操作会发先锁还没超时而退出,具体代码如下

package com.gdut.xg.shop.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * @author lulu
 * @Date 2019/7/21 9:10
 */
@Component
public class RedisLock {
    @Autowired
    private StringRedisTemplate template;

    /**
     *
     * @param key
     * @param value 设置时间+超时时间
     * @return
     */
    public boolean lock(String key,String value){
        if(template.opsForValue().setIfAbsent(key,value)){
            return true;
        }
        String currentValue=template.opsForValue().get(key);
        //如果锁过期
        if(!StringUtils.isEmpty(currentValue)&&Long.parseLong(currentValue)<System.currentTimeMillis()){
            //获取上一个锁时间
            String oldValue=template.opsForValue().getAndSet(key,value);
            //超时即重新上锁
            if (!StringUtils.isEmpty(oldValue) && oldValue.equals(value)) {
                return true;
            }
        }
        return false;
    }
//这里可以用lua脚本保证原子性,下面有相似例子
    public void unlock(String key,String value){
        try{
            String currentValue=template.opsForValue().get(key);
            if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
                template.opsForValue().getOperations().delete(key);
            }
        }catch (Exception e){
            System.out.println("解锁异常");
        }

    }

}

可重入锁,redis实现可重入可以使用threalLock+lua脚本实现,其中ThreadLock用于存储当前线程的信息UUID+threadId,用一个map去维护当前线程标志和调用次数,当如果确定是本线程,map的count属性+1,解锁时减一,如果解锁恰好是1并且属于自己的锁就把key删掉,这里有个要注意的点,lua脚本要使用stringRedisTemplate来执行,不然有可能会导致expire的指令执行报错

 private static class RedisLockInfoHolder {
        static ThreadLocal<String> holder = new ThreadLocal<>();

        private static void clear() {
            holder.remove();
        }

        private static String get() {
            return holder.get();
        }

        private static void setValue(String value) {
            holder.set(value);
        }
    }
--一个map,有属性值和调用次数
local isExists = redis.call('exists', KEYS[1])
--代表初始提交
if isExists == 0 then
    redis.call('hmset', KEYS[1], 'currentThread', ARGV[1], 'count', 1);
    --设置超时时间
    redis.call('pexpire', KEYS[1], ARGV[2]);
    return true;
    --否则判断是否是同一个线程,如果是的话加1,返回true
else
    local currentThread = redis.call('hget', KEYS[1], 'currentThread')
    --如果当前线程相等,+1返回true
    if ARGV[1] == currentThread then
        redis.call('hincrby', KEYS[1], 'count', 1)
        return true
    end
    return false;
end
--当为0时候释放锁
local isExists = redis.call('exists', KEYS[1])
--如果不等0,代表有key存在,如果当前count为0,删掉
if isExists == 1 then
    local lockInfo = redis.call('hmget', KEYS[1], 'currentThread', 'count')
    local currentThread = lockInfo[1]
    local count = tonumber(lockInfo[2])
    --不是自己的锁,不释放
    if currentThread ~= ARGV[1] then
        return 0
    end
    --返回成功解锁
    if count == 1 then
        redis.call('del', KEYS[1])
        return 1
    else
        --重入锁,仍持有
        redis.call('hincrby', KEYS[1], 'count', -1)
        return 2
    end
else
package com.xl.redisaux.common.utils.lock;

import com.xl.redisaux.common.utils.NamedThreadFactory;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

import java.util.Collections;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * @author tanjl11
 * @date 2020/10/14 15:07
 */
public class LockUtil {

    private StringRedisTemplate lockTemplate;

    public LockUtil(StringRedisTemplate lockTemplate) {
        this.lockTemplate = lockTemplate;
    }

    private volatile DefaultRedisScript<Boolean> lockScript;

    private volatile DefaultRedisScript<Long> upLockScript;

    private volatile DefaultRedisScript<Boolean> extendScript;

    private final static long INTERVAL_CHECK_EXPIRE = 30;
    private final String PREFIX = "redisLock:";
    //每个bucket的大小是1s,一轮有16个
    private HashedWheelTimer wheelTimer;

    private DefaultRedisScript getExtendScript() {
        if (Objects.isNull(extendScript)) {
            synchronized (LockUtil.class) {
                if (Objects.isNull(extendScript)) {
                    wheelTimer = new HashedWheelTimer(new NamedThreadFactory("watch-dog-thread-pool",true), 1, TimeUnit.SECONDS, 16);
                    extendScript = new DefaultRedisScript<>();
                    extendScript.setResultType(Boolean.class);
                    extendScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("Extend.lua")));
                }
            }
        }
        return extendScript;
    }

    private DefaultRedisScript getLockScript() {
        if (Objects.isNull(lockScript)) {
            synchronized (LockUtil.class) {
                if (Objects.isNull(lockScript)) {
                    lockScript = new DefaultRedisScript();
                    lockScript.setResultType(Boolean.class);
                    lockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("Lock.lua")));
                }
            }
        }
        return lockScript;
    }

    private DefaultRedisScript getUnLockScript() {
        if (Objects.isNull(upLockScript)) {
            synchronized (LockUtil.class) {
                if (Objects.isNull(upLockScript)) {
                    upLockScript = new DefaultRedisScript();
                    upLockScript.setResultType(Long.class);
                    upLockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("UnLock.lua")));
                }
            }
        }
        return upLockScript;
    }


    /**
     * @param key
     * @param time
     * @param awaitTime
     * @param retryCount
     * @param sleepTime
     * @return
     */
    public Boolean tryLockInSeconds(String key, long time, long awaitTime, long retryCount, long sleepTime) {
        return tryLockInSeconds(key, time, TimeUnit.SECONDS, awaitTime, TimeUnit.SECONDS, retryCount, sleepTime, TimeUnit.SECONDS);
    }

    /**
     * 超时可重入锁
     *
     * @param key        锁名
     * @param expireTime 锁过期时间
     * @param unit       锁过期时间单位
     * @param awaitTime  锁等待超时时间
     * @param awaitUnit  锁
     * @param retryCount 最大获取次数
     * @param sleepTime  每次获取失败后的睡眠时间
     * @param sleepUnit  睡眠单位
     * @return
     */
    public Boolean tryLockInSeconds(String key, long expireTime, TimeUnit unit, long awaitTime, TimeUnit awaitUnit, long retryCount, long sleepTime, TimeUnit sleepUnit) {
        //先获取一次
        Boolean isLock = tryLock(key, expireTime, unit);
        //不行再获取
        if (!isLock) {
            long nanos = awaitUnit.toNanos(awaitTime);
            final long deadline = System.nanoTime() + nanos;
            int count = 0;
            while (true) {
                nanos = deadline - System.nanoTime();
                //超时
                if (nanos <= 0L) {
                    return false;
                }
                isLock = tryLock(key, expireTime, unit);
                if (isLock) {
                    return true;
                }
                //如果大于最大获取次数或者线程被中断
                if (count++ > retryCount || Thread.interrupted()) {
                    return false;
                }
                //阻塞
                LockSupport.parkNanos(sleepUnit.toNanos(sleepTime));
            }
        }
        return true;
    }

    /**
     * 无限延长方法
     *
     * @param key
     * @return
     */
    public Boolean tryLock(String key) {
        //先锁一次
        Boolean lock = tryLock(key, INTERVAL_CHECK_EXPIRE, TimeUnit.SECONDS);
        if (lock) {
            DefaultRedisScript extendScript = getExtendScript();
            wheelTimer.newTimeout(new TimerTask() {
                @Override
                public void run(Timeout timeout) throws Exception {
                    //延迟0.8后开始校验,是否存在锁,存在就延期,不存在就删除,
                    Boolean isExtend = (Boolean) lockTemplate.execute(extendScript, Collections.singletonList(PREFIX + key), String.valueOf(TimeUnit.MILLISECONDS.convert(INTERVAL_CHECK_EXPIRE, TimeUnit.SECONDS)));
                    if (isExtend) {
                        wheelTimer.newTimeout(this, (long) (INTERVAL_CHECK_EXPIRE * 0.8), TimeUnit.SECONDS);
                    } else {
                        timeout.cancel();
                    }
                }
            }, (long) (INTERVAL_CHECK_EXPIRE * 0.8), TimeUnit.SECONDS);
            return true;
        }
        return false;
    }

    /**
     * 按照秒来锁
     *
     * @param key
     * @param time
     * @return
     */
    public Boolean tryLock(String key, long time) {
        return tryLock(key, time, TimeUnit.SECONDS);
    }

    /**
     * 哨兵模式如果保证绝对可靠需要redLock算法支持,redission已有实现
     */
    public Boolean tryLock(String key, long expireTime, TimeUnit unit) {
        key = PREFIX + key;
        String threadSign = RedisLockInfoHolder.get();
        //设置线程标志
        if (Objects.isNull(threadSign)) {
            String uuid = UUID.randomUUID().toString();
            String value = uuid + Thread.currentThread().getId();
            threadSign = value;
            RedisLockInfoHolder.setValue(threadSign);
        }
        long millis = unit.toMillis(expireTime);
        //要使用stringRedisTemplate才可以设置上
        Boolean isLock = (Boolean) lockTemplate.execute(getLockScript(), Collections.singletonList(key), threadSign, String.valueOf(millis));
        return isLock;
    }

    /**
     * 解锁
     *
     * @param key
     * @return
     */
    public UnLockStatus unLock(String key) {
        key = PREFIX + key;
        String threadSign = RedisLockInfoHolder.get();
        Long result = (Long) lockTemplate.execute(getUnLockScript(), Collections.singletonList(key), threadSign);
        //如果成功解锁,清楚线程标志
        if (Objects.equals(result, UnLockStatus.UNLOCK_SUCCESS.getStatus())) {
            RedisLockInfoHolder.clear();
        }
        return UnLockStatus.getByStatus(result);
    }

    private static class RedisLockInfoHolder {
        static ThreadLocal<String> holder = new ThreadLocal<>();

        private static void clear() {
            holder.remove();
        }

        private static String get() {
            return holder.get();
        }

        private static void setValue(String value) {
            holder.set(value);
        }
    }
}
public enum UnLockStatus {
    LOCK_NOT_EXISTS(-1L, "该锁不存在"),
    NOT_HAVE_LOCK(0L, "该锁非调用线程所占用"),
    UNLOCK_SUCCESS(1L, "解锁成功"),
    RETAIN_LOCK(2L, "解锁但仍持有");
    private Long status;
    private String mean;

    UnLockStatus(Long status, String mean) {
        this.status = status;
        this.mean = mean;
    }

    public Long getStatus() {
        return status;
    }

    public String getMean() {
        return mean;
    }

    public static UnLockStatus getByStatus(Long status){
        for (UnLockStatus value : values()) {
            if(Objects.equals(value.getStatus(),status)){
                return value;
            }
        }
        return null;
    }
}

续约功能实现思路:

默认维护一个过期时间,由一个定时任务去维护,等差不多过期了,就去检查一下,查看键是否存在,不存在把定时任务取消

具体可以使用netty的时间轮结合lua脚本实现

local isExists = redis.call('exists', KEYS[1])
if isExists == 0 then
    return false
else
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return true
end

时间轮使用,判断如果还有键,就再注册一个任务,关于时间轮的分析网上也有很多,这里不做介绍,注意时间轮的运行线程需设置为守护线程

public Boolean tryLock(String key) {
        //先锁一次
        Boolean lock = tryLock(key, INTERVAL_CHECK_EXPIRE, TimeUnit.SECONDS);
        if (lock) {
            DefaultRedisScript extendScript = getExtendScript();
            wheelTimer.newTimeout(new TimerTask() {
                @Override
                public void run(Timeout timeout) throws Exception {
                    //延迟0.8的时间后开始校验,是否存在锁,存在就延期,不存在取消任务执行
                    Boolean isExtend = (Boolean) lockTemplate.execute(extendScript, Collections.singletonList(PREFIX + key), String.valueOf(TimeUnit.MILLISECONDS.convert(INTERVAL_CHECK_EXPIRE, TimeUnit.SECONDS)));
                    if (isExtend) {
                        wheelTimer.newTimeout(this, (long) (INTERVAL_CHECK_EXPIRE * 0.8), TimeUnit.SECONDS);
                    } else {
                        timeout.cancel();
                    }
                }
            }, (long) (INTERVAL_CHECK_EXPIRE * 0.8), TimeUnit.SECONDS);
            return true;
        }
        return false;
    }

 

集群:

Redission

引入依赖

<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>${redission.version}</version>
        </dependency>

编写配置文件

import lombok.Setter;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;

/**
 * @author: lele
 * @date: 2019/9/23 下午11:15
 *
 */
@Configuration
@Setter
public class RedisConfig  {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private Integer port;
    @Value("${spring.redis.password}")
    private String password;

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redission() {
        Config config = new Config();
        //配置还有主从、集群、哨兵、该例子为单实例
        config.useSingleServer().setDatabase(0).
        setAddress(  host + ":" + port).
        setPassword(password);
        return Redisson.create(config);
    }

}

封装RedissionLock,redission里面还有更多的API,详情请看官方文档


import org.redisson.api.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author: lele
 * @date: 2019/9/23 下午7:15
 * 使用方式:
 * 注入
 * ReadWriteLock lock=redissionLock.getReadWriteLock(lockName);
 * lock.lock()
 * 业务逻辑
 * lock.unlock()
 */
@Component
public class RedissionLock {
    @Resource
    private RedissonClient redisson;

    /**
     * 获取字符串对象
     *
     * @param objectName
     * @return
     */
    public <T> RBucket<T> getRBucket(String objectName) {
        RBucket<T> bucket = redisson.getBucket(objectName);
        return bucket;
    }

    /**
     * 获取Map对象
     *
     * @param objectName
     * @return
     */
    public <K, V> RMap<K, V> getRMap(String objectName) {
        RMap<K, V> map = redisson.getMap(objectName);
        return map;
    }

    /**
     * 获取有序集合
     *
     * @param objectName
     * @return
     */
    public <V> RSortedSet<V> getRSortedSet(String objectName) {
        RSortedSet<V> sortedSet = redisson.getSortedSet(objectName);
        return sortedSet;
    }

    /**
     * 获取集合
     *
     * @param objectName
     * @return
     */
    public <V> RSet<V> getRSet(String objectName) {
        RSet<V> rSet = redisson.getSet(objectName);
        return rSet;
    }

    /**
     * 获取列表
     *
     * @param objectName
     * @return
     */
    public <V> RList<V> getRList(String objectName) {
        RList<V> rList = redisson.getList(objectName);
        return rList;
    }

    /**
     * 获取队列
     *
     * @param objectName
     * @return
     */
    public <V> RQueue<V> getRQueue(String objectName) {
        RQueue<V> rQueue = redisson.getQueue(objectName);
        return rQueue;
    }

    /**
     * 获取双端队列
     *
     * @param objectName
     * @return
     */
    public <V> RDeque<V> getRDeque(String objectName) {
        RDeque<V> rDeque = redisson.getDeque(objectName);
        return rDeque;
    }

    /*
     *获取阻塞队列
     * @param redisson
     * @param objectName
     * @return
     */
    public <V> RBlockingQueue<V> getRBlockingQueue(String objectName) {
        RBlockingQueue rb = redisson.getBlockingQueue(objectName);
        return rb;
    }

    /**
     * 获取锁
     *
     * @param objectName
     * @return
     */
    public RLock getRLock(String objectName) {
        RLock rLock = redisson.getLock(objectName);
        return rLock;
    }

    /**
     * 公平锁
     *
     * @param objectName
     * @return
     */
    public RLock getFairLock(String objectName) {
        RLock rLock = redisson.getFairLock(objectName);
        return rLock;
    }

    /**
     * 读写锁
     *
     * @param objectName
     * @return
     */
    public RReadWriteLock getReadWriteLock(String objectName) {
        RReadWriteLock readWriteLock = redisson.getReadWriteLock(objectName);
        return readWriteLock;
    }


    /**
     * 获取原子数
     *
     * @param objectName
     * @return
     */
    public RAtomicLong getRAtomicLong(String objectName) {
        RAtomicLong rAtomicLong = redisson.getAtomicLong(objectName);
        return rAtomicLong;
    }

    /**
     * 获取记数锁
     *
     * @param objectName
     * @return
     */
    public RCountDownLatch getRCountDownLatch(String objectName) {
        RCountDownLatch rCountDownLatch = redisson
                .getCountDownLatch(objectName);
        return rCountDownLatch;
    }

    /**
     * 获取消息的Topic
     *
     * @param objectName
     * @return
     */
    public <M> RTopic<M> getRTopic(String objectName) {
        RTopic<M> rTopic = redisson.getTopic(objectName);
        return rTopic;
    }


}

测试,user表里面有个积分的字段,初始设为10000,启动三个实例,然后用jmeter进行测试

package com.imooc.alicloud.usercenter.web;


import com.imooc.alicloud.usercenter.dao.user.UserMapper;
import com.imooc.alicloud.usercenter.domain.entity.user.User;
import com.imooc.alicloud.usercenter.lock.RedissionLock;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

/**
 *
 * @author lele
 * @since 2019-09-21
 */
@RestController
@RequestMapping("/channel")
public class TestController {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RedissionLock redissionLock;
    private String name = "i";

    @GetMapping("/test/reduce")
    public void get() {
        RLock rLock = redissionLock.getRLock(name);
        rLock.lock();
        User user = userMapper.selectById(1);
        user.setBonus(user.getBonus() - 1);
        userMapper.updateById(user);
        rLock.unlock();
    }
}

结果确实减少了1500

把锁去掉再测试一次,很快就执行完了,但积分没有像预期一样减少相应的分数

缓存常用注解:

@EnableCaching用在程序入口开启缓存

@Cacheable(查询操作使用,有缓存则走缓存)

当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的,如果用于对象的话要进行序列化

key:默认是方法参数,动态 :#参数名,#参数index

cacheNames/value:前缀

condition:符合条件才缓存

unless:#result代表结果对象

 @Cacheable(cacheNames = "productData",key = "#categoryId",unless="#result.length()!=0")
public ProductDataVO getProductData(Integer categoryId) {xxx}

存储的key为productData::categoryId,value是对象

@CachePut 每次都会走一遍流程,再把结果更新到缓存(对于插入、修改、删除方法可以使用)

@CacheEvict(清除缓存)

@Caching 操作组合


package org.springframework.cache.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
    Cacheable[] cacheable() default {};

    CachePut[] put() default {};

    CacheEvict[] evict() default {};
}

参考配置

package com.trendy.center.channel.common.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Setter;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
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.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

   /**
     * 定义key生成器,可在注解上面使用
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (Object target, Method method, Object... params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    } 
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                this.redisCacheConfigurationWithTtl(30), // 默认策略,未配置的 key 会使用这个
                this.getRedisCacheConfigurationMap() // 指定 key 策略
        );
    }
    //自定义key策略,value可设置不同的configuration
    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        /* redisCacheConfigurationMap.put("cachelist", this.redisCacheConfigurationWithTtl(3000));*/

        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration redisCacheConfigurationWithTtl(Integer minutes) {
        //代替默认的jdk序列化对象,序列化方便查看value
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //缓存配置
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(jackson2JsonRedisSerializer)
        ).entryTtl(Duration.ofMinutes(minutes));

        return redisCacheConfiguration;
    }

}

 

同步机制:

全同步过程

slave发送sync命令到master

master启动一个后台进程,将redis中的数据快照保存到文件中

master将保存数据快照期间接受的写命令缓存起来

master完成写操作文件后,将该文件发送到slave

使用新的rdb文件替换掉旧的rdb文件

master将这期间收集的增量命令发送给slave

增量同步过程

master接受到用户的操作指令,判断是否传播到slave

将操作记录追加到aof文件

将操作传播到其他slave:1.对齐主从库;2.往相应缓存写入指令

将缓存中的数据发给slave

解决主从模式下master宕机 哨兵 redis sentinel

监控:检查主从服务器是否运行正常

提醒:通过api向管理员或其他应用程序发送通知

自动故障迁移:主从切换

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值