非关系型数据库Redis(二):Redis作分布式锁与集群搭建

前言:本文为原创 若有错误欢迎评论!

一.Redis分布式锁

1.注入配置好的序列化方式的redisTemplate

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<String,String>();
        redisTemplate.setConnectionFactory(factory);
        
        /**
         * 定义好三种序列化方式:Jackson序列化、Jdk序列化、String序列化
         */
       
        /**Jackson序列化  json占用的内存最小 */
        Jackson2JsonRedisSerializer 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);
        
        /**Jdk序列化   JdkSerializationRedisSerializer是最高效的*/
        // JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new 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;
    }
}

2.在service层建立一个分布式锁的类 先注入RedisTemplate

@Autowired
private RedisTemplate redisTemplate;

3.引入获取当前ip的地址的方法 方便作为分布式每台机器对应的value

private static String getHostIp() {
        try {
            Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
            while (allNetInterfaces.hasMoreElements()) {
                NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
                Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress ip = (InetAddress) addresses.nextElement();
                    if (ip != null
                            && ip instanceof Inet4Address
                            && !ip.isLoopbackAddress() //loopback地址即本机地址,IPv4的loopback范围是127.0.0.0 ~ 127.255.255.255
                            && ip.getHostAddress().indexOf(":") == -1) {
                        return ip.getHostAddress();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

4.setlock()方法:实现同时对redis执行setnx和setex命令–lua脚本(推荐)

  1. 在resources下新建add.lua:
    local lockKey = KEYS[1]
    local lockValue = KEYS[2]
    
    -- setnx info
    local result_1 = redis.call('SETNX', lockKey, lockValue)
    if result_1 == true
    then
    local result_2= redis.call('SETEX', lockKey,拿到锁的时间单位是秒, lockValue)
    return result_1
    else
    return false
    end
    
  2. setLock方法:
    public Boolean setlock(String key) {
    		//读取脚本文件
            lockScript = new DefaultRedisScript<Boolean>();
            lockScript.setScriptSource(
                    new ResourceScriptSource(new ClassPathResource("add.lua")));
            lockScript.setResultType(Boolean.class);
            
            // 封装参数
            List<Object> keyList = new ArrayList<Object>();
            keyList.add(key);
            keyList.add(getHostIp());
    		
    		//运行脚本文件 并返回结果
            Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList);
            return result;
        }
    

5.setlock()方法:实现同时对redis执行setnx和setex命令–Jedis(个人不推荐)

  • Jedis是redis的java客户端
    public boolean setLock(String key, long expire) {
            try {
                Boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
                    @Override
                    public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.set(key.getBytes(), getHostIp().getBytes(), Expiration.seconds(expire) ,RedisStringCommands.SetOption.ifAbsent());
                    }
                });
                return result;
            } catch (Exception e) {
                logger.error("set redis occured an exception", e);
            }
            return false;
        }
    

6.releaseLock()方法

  • 注意:直接在redis删除这个key的话可能锁已经提前释放 即value已经是另一个ip 所以要再做判断
  1. 建立unlock.lua:
    local lockKey = KEYS[1]
    local lockValue = KEYS[2]
    
    -- get key
    local result_1 = redis.call('get', lockKey)
    if result_1 == lockValue
    then
    local result_2= redis.call('del', lockKey)
    return true
    else
    return false
    end
    
  2. releaseLock方法
    private boolean releaseLock(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);
    	
    		//执行脚本文件
            Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList);
            return result;
        }
    

7.如何调用

  • 在要执行的方法最开始 先定义好锁(redis的key)的名称 与bool类型是否拿到锁的标志

    String lock = “LockNxExJob”;
    boolean ret = false;

  • try/catch和finally要加锁的代码块 然后在try的代码最开始 调用给setlock() 并用定义的ret=返回bool类型(是否拿到锁)

  • 判断ret是否为true(即是否拿到了锁)拿到就执行业务逻辑

  • 在finally中判断是否拿到锁 拿到执行releaseLock方法

@Test
public void test() {

        String lock = LOCK_PREFIX + "JedisNxExJob";
        boolean lockRet = false;
        try {
            lockRet = this.setLock(lock, 600);

            //获取锁失败
            if (!lockRet) {
                String value = (String) redisTemplate.getValue(lock);
                //打印当前占用锁的服务器IP
                logger.info("jedisLockJob get lock fail,lock belong to:{}", value);
                return;
            } else {
                //获取锁成功
                logger.info("jedisLockJob start  lock lockNxExJob success");
                Thread.sleep(5000);
            }
        } catch (Exception e) {
            logger.error("jedisLockJob lock error", e);

        } finally {
        	//释放锁
            if (lockRet) {
                logger.info("jedisLockJob release lock success");
                releaseLock(lock,getHostIp());
            }
        }
    }

二.Redis读写分离(主从)

  • 主节点是可以读和写 从节点只能读 可以减小主节点压力 且从节点是主节点的备份

1.配置redis:

  1. 进到src同级目录下

  2. mkdir redis-replication

  3. cd redis-replication

  4. mkdir 6379 6380

  5. cp …/redis.conf 6379(复制配置文件 启动时带上各自的配置文件)

  6. cp …/redis.conf 6380

  7. 编辑conf文件
    在所有节点的redis.conf:

    daemonize no 改为 yes 				#设置为守护进程
    

    在从节点的redis.conf中:

    slaveof <> <>     					# 改为 slaveof 主节点ip 主节点端口
    port 6379		  					# 改为 port 从节点
    pidfile:/var/run/redis.pid 		# 改为/var/run/redis_从节点端口号.pid 
    dbfilename dump.rdb					# 改为dump_从节点端口号.rdb
    appendfilename "appendonly.aof"		# 改为appendonly_从节点端口号.aof
    

8.带配置文件启动两个节点

9.redis客户端的几个命令:

info Replication:查看当前客户端信息
slaveof no one:如果是从节点就变为主节点
slaveof ip地址 端口:成为这个ip和端口对应的从节点)

三.Redis高可用架构Sentinel(哨兵机制)

  • Sentinel端口:26379

1.配置redis

  1. 完成二的步骤并启动每个节点

  2. 进入和redis.conf同级的目录 找到sentinel.conf

  3. 只复制一份到刚才的redis-replication的目录下

  4. 编辑

    # Sentinel monitor <master-name> <ip><port> <quorum>
    #  <master-name> sentinel要监控的主节点的名称,或者应该说叫集群名字
    #  <ip><port> 启动时主节点的地址,后面如果主节点下线了会自动故障迁移,所以主节点并不总是该 ip:port
    #  <quorum> 代表要判定主节点最终不可达所需要的票数
    sentinel monitor mymaster 127.0.0.1 6379 1  
    
    #sentinel认为的主观下线时间 (即:和sentinel一直没有交互的时间)
    sentinel down-after-milliseconds mymaster 10000    
    
    # 故障转移超时时间,作用于各个阶段。
    #  A) 选出合适从节点
    #  B) 晋升选出的从节点为主节点
    #  C) 命令其余从节点复制新的主节点
    #  D) 等待原主节点恢复后命令它去复制新的主节点
    sentinel failover-timeout mymaster 60000    
    
    # 故障转移后,有多少个从节点可以同时重新同步主节点
    # 该参数主要为了一个主从复制集群全部都在进行复制,从而导致无法对外提供服务
    sentinel parallel-syncs mymaster 1     
    
    #最后一步:搜索monitor 把系统默认那个注销掉
    
  5. 进入src (然后带配置文件启动sentinel)

    ./redis-sentinel [sentinel的配置文件位置]

    启动之后就会直接显示节点的所有信息 可以用:pss-ef|grep redis 看见有开了一个进程

2.SpringBoot整合sentinel

  • 通过sentinel的代理 可以通过配置文件设置的主节点地址访问及分配所有节点
    在配置文件中加:
    spring:
      redis:
        sentinel:
          master: 在配置sentinel.conf中用的名字  (如:mymaster)
          nodes: sentinel的ip地址:26379 (如果有多个用","分割)
    

四.Redis集群

  • 参考我的博客:https://blog.csdn.net/weixin_43934607/article/details/102668918

五.Redis布隆过滤器

  • 是一种数据结构 查询的性能更高 且占内存更少 常用作在批量数据中过滤出预先保留的数据 如:秒杀将已经购买的uid放入 然后在后面后面购买的人取出来过滤)

1.安装:https://blog.csdn.net/ChenMMo/article/details/93615438

2.命令(redis的客户端使用):

bf.add key value1(添加 一个key可以添加多个value)
bf.add key value2
bf.madd key value1 value2(一次添加多个key对应的value)
bf.exists key value(判断是否存在 要同时有key和value)
del key(删除的话就是和一般的key一样删除 只是一种数据结构)

3.SpringBoot整合:编写lua脚本使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值