Redis内存优化、集群、分布式锁、事务等总结

Redis内存优化、集群、分布式锁、事务等总结

一、Redis内存优化

1、满足业务的情况下,减少键值长度。

2、尽量的将数据模型存放到散列表(ziplist)中,散列表使用的内存比较小。减少key-value的使用。比如一个用户信息,不要单独把用户的姓名、性别等信息存为一个key,而应该在整体存放在散列表中。

3、数据0-9999的时候,默认使用共享对象池,设置maxmemory并启动LUR相关淘汰策略,对象池无效,通过object refcount 查看其引用数会是1

二、Redis集群方案

分区:
分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。

分区类型:
1、范围分区:最简单的分区方式是按范围分区,就是映射一定范围的对象到特定的Redis实例。
2、哈希分区:hash函数(crc32 hash函数)将key转换为一个数字,这个整数和redis实例进行取模运算,确定数据存储在哪个redis中。

集群实现的几种方式:
1、客户端分片:由客户端决定key写入或者读取的节点(jedis)
2、基于代理的分片:客户端发送请求到一个代理,代理解析客户端的数据,将请求转发至正确的节点,然后将结果回复给客户端(Twemproxy、codis)。
3、路由查询:将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行。(Redis-cluster)

三、Redis分布式锁

分布式锁:
当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
满足可靠性的四个条件:
1、互斥性。在任意时刻,只有一个客户端能持有锁。
2、不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
3、具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
4、解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

正确加锁

/**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean lock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        //如果key存在返回false;不存在,保存key并返回ok
        String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);

        if ("OK".equals(result)) {
            return true;
        }
        return false;


    }

错误加锁

 public static boolean lock2(Jedis jedis, String lockKey, String requestId, int expireTime) {
        Long result = jedis.setnx(lockKey, requestId);
        if (result == 1) {
            // 若在这里程序突然崩溃,则无法设置过期时间,将发生死锁
            jedis.expire(lockKey, expireTime);
            return true;
        }
        return false;
    }
public static boolean lock3(Jedis jedis, String lockKey, String requestId, int expireTime) {
        long expires = System.currentTimeMillis() + expireTime;
        String expiresStr = String.valueOf(expires);

        // 如果当前锁不存在,返回加锁成功
        if (jedis.setnx(lockKey, expiresStr) == 1) {
            return true;
        } 
        // 如果锁存在,获取锁的过期时间
        String currentValueStr = jedis.get(lockKey);
        if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
            // 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间
            String oldValueStr = jedis.getSet(lockKey, expiresStr);
            if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                // 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才有权利加锁
                return true;
            }
        }
        // 其他情况,一律返回加锁失败
        return false;
    }

正确解锁

 /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean unlock(Jedis jedis, String lockKey, String requestId) {
        //LUA代码
        String script =
                "if redis.call('get',KEYS[1]) == ARGV[1] then" +
                        "   return redis.call('del',KEYS[1]) " +
                        "else" +
                        "   return 0 " +
                        "end";
        //eval原子操作
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

错误解锁

/**
     *问题:所有客户端都可以解锁,即使不是他的错
     */
    public static void unlock1(Jedis jedis, String lockKey, String requestId) {
        jedis.del(lockKey);
    }

    /**
     *A客户端在del之前,锁正好过期,B客户端加锁成功,A客户端就会把B客户端的锁删除.
     */
    public static void unlock2(Jedis jedis, String lockKey, String requestId) {
        // 判断加锁与解锁是不是同一个客户端
        if (requestId.equals(jedis.get(lockKey))) {
            // 若在此时,这把锁突然不是这个客户端的,则会误解锁
            jedis.del(lockKey);
        }
    }

四、Redis 事务

本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

Redis不保证原子性:
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

Redis事务的三个阶段:
开始事务
命令入队
执行事务

Redis事务相关命令:
watch key1 key2 … : 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )
multi : 标记一个事务块的开始( queued )
exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 ) 
discard : 取消事务,放弃事务块中的所有命令
unwatch : 取消watch对所有key的监控

案例:

public static boolean redisTra(Jedis jedis,String balance,String debt,Long delAmount) {
        // Watch 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
        jedis.watch(balance);
        Long amount = Long.parseLong(jedis.get(balance));
        Long debts = Long.parseLong(jedis.get(debt));
        if (amount < delAmount) {
            //Redis Unwatch 命令用于取消 WATCH 命令对所有 key 的监视。
            jedis.unwatch();
            System.out.println("amount can't Less than delAmount !!!!");
            return false;
        }
        //Redis 通过 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务
        Transaction transaction = jedis.multi();
        transaction.decrBy(balance, delAmount);
        transaction.incrBy(debt, delAmount);
        //在这里模拟网络延迟时,我们通过redis命令窗口手动去修改balance值。
        List<Object> exec = transaction.exec();
        //执行exec操作时发现balance值被修改,因此终止操作。
        if (exec.size() <= 0) {
            System.out.println("balance is upfated by other person,debt is fail!!!");
            return false;
        }
        System.out.println("After updated balance== " + Long.parseLong(jedis.get(balance)));
        System.out.println("After updated debt== " + Long.parseLong(jedis.get(debt)));
        return true;
    }
 

关注公众号免费领取学习资料~
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值