Redis全方位详解--数据类型使用场景和redis分布式锁的正确姿势

栏目: 数据库 · 发布时间: 6年前

内容简介:一、Redis数据类型1.stringstring是Redis的最基本数据类型,一个key对应一个value,每个value最大可存储512M。string一半用来存图片或者序列化的数据。

一、 Redis 数据类型

1.string

string是Redis的最基本数据类型,一个key对应一个value,每个value最大可存储512M。string一半用来存图片或者序列化的数据。

2.hash

相当于一个string类型的映射表。特别适合用来存储对象。例如可以存储用户信息,用户ID作为hash类型里的每一个key。

案例:我们这边需要对接微信粉丝的数据到我们自己的平台上,但微信提供的接口只支持单天查询,那么如果我们想要查看最近一个月微信粉丝的状况,就需要循环30次调用微信的接口。一个月勉强还可以接受,那么如果想要查半年,甚至一年呢?那么我们的接口里就需要循环365次调微信的接口,这就会使我们的接口变得非常慢,甚至超时。还有这些数据,比如单天新增粉丝数,是不会变得,而且每天都有一个数据,这样就特别适合存在redis的hash类型里,以日期(2018-10-10)作为hash的key。

3.list

list类型是简单的字符串列表,每个列表可以存储2 32 - 1 个值。可以从头部或者尾部顺序插入数据。list类型可以用来做电商里的秒杀营销系统或关注列表。

4.set

set是string类型的无序集合。该集合是通过哈希实现的,添加、删除的复杂度都是O(1),所以查找非常快。

案例:我们这边是以手机号为唯一标示符,防止重复用户注册,会判断该手机号有没有注册过,那么如果用set类型存储注册过的用户手机号,就会很快判断出该用户是否注册过,而不用去查数据库了。

5.zset

和set一样,但zset多了一个score来让set变得有序,且不允许有重复的成员。

案例:我们这边有一个账户记录需要按记录时间排序,那么就可以将时间戳当作score存储zset中。

二、redis分布式锁

网上很多redis分布式锁的实现方式不能说错误的,但至少不够严谨,在某些极端情况下是会出问题的。一旦出现问题,还是挺麻烦的事情,所以我们要知道redis分布式锁的正确姿势。

其实很简单,利用redis的原子性。关于原子性,官方的一段描述为:

Redis全方位详解--数据类型使用场景和redis分布式锁的正确姿势

大概意思就是redis执行 lua 脚本的时候,会被当成一条命令执行,在此期间,不会执行其他命令,所以lua脚本尽量是快脚本而不是慢脚本。

所以,正确的姿势是:

    public function getDistributeLock($redis, string $key, int $userId, int $expire)
    {
        $luaScript = <<<LUA
if (redis.call('exists', KEYS[1]) == 0) and redis.call('setex', KEYS[1], ARGV[1], ARGV[2]) 
then
    return 1
else
    return 0
end
LUA;

        return $redis->eval($luaScript, 1, $key, $expire, $userId) > 0 ? true : false;
    }

这里我们把一段lua脚本放到redis的eval方法里执行,这样就可以保证这一段命令的原子性。

那么如果不用lua脚本,姿势应该是这样的:

    public function wrongGetDistributeLock($redis, string $key, int $userId, int $expire)
    {
        
        // 若锁不存在,则加锁
        if(!$redis->exists($key) && ($redis->setex($key, $expire, $userId) == 'OK')) {
            return true;
        }

        return false;
    }

前面提到过,这种姿势在某些情况下会出问题:如果同时好几个客户端同时请求,同时通过了上面if条件的第一层,那么这时候就会出现多个同时拿到锁,并且前面人的锁会被覆盖。

然后,正确的解锁姿势是:

    public function releaseDistributeLock($redis, string $key, int $userId)
    {
        $luaScript = <<<LUA
if redis.call('get', KEYS[1]) == ARGV[1]
then
    return redis.call('del',  KEYS[1])
else
    return 0
end
LUA;
        $redis->eval($luaScript, 1, $key, $userId);
    }

同样需要使用lua脚本来达到原子性。那么如果不使用lua脚本的姿势是:

    public function wrongReleaseLock($redis, string $key, int $userId)
    {
        if($userId == $redis->get($key)) {
            $redis->del($key);
        }
    }

会有这样一种情况:A请求通过if语句后,这时候这个redis的key刚好过期了,然后B客户端加锁成功,这时候A请求就会把客户端B刚加的锁给解除了。

虽然我上面提到的两种情况都是很极端、很少出现的。但如果可以用很简单的方法避免掉,so why not?

上面提到的redis分布式锁,满足了三个特性:

  • 互斥性。同时只能又一个客户端拥有锁
  • 不会发生死锁。
  • 加锁和解锁的必须是同一个客户端。

童鞋们,有什么疑问,可以在地下留言哦。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Windows高级调试

Windows高级调试

Mario Hewardt、Daniel Pravat / 聂雪军 / 机械工业出版社 / 2009-5 / 79.00元

本书主要讲解Windows高级调试思想和工具,并涉及一些高级调试主题。本书内容主要包括:工具简介、调试器简介、调试器揭密、符号文件与源文件的管理、栈内存破坏、堆内存破坏、安全、进程间通信、资源泄漏、同步、编写定制的调试扩展、64位调试、事后调试、Windows Vista基础以及应用程序验证器的测试设置等。本书内容详实、条理清楚。 本书适合Windows开发人员、Windows测试人员和Windo......一起来看看 《Windows高级调试》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码