redis精进 - String类型的使用和应用场景

5,149 阅读4分钟

redis - String类型的使用和应用场景

最近在精进学习Redis,边学边写

先赞后读,养成习惯

一、String类型内存使用说明

  • Redis 的字符串是动态字符串
  • 采用预分配冗余空间方式来减少内存的频繁分配
  • 内容空间一般要高于实际字符串长度 len
    • 当字符串长度小于 1M 时,扩容都是加倍现有的空间,
    • 如果超过 1M,扩容时一次只会多扩 1M 的空间。
  • 需要注意的是字符串最大长度为 512M。

二、String类型常用命令:

set

SET expire60 "will expire in a minute" EX 60 # 设置值60秒后过期

多存多取

local_redis:0> mset name1 boy name2 girl name3 unknown
"OK"

local_redis:0> mget name1 name2 name3
 1)  "boy"
 2)  "girl"
 3)  "unknown"

过期

setex name 5 codehole  # 5s 后过期,等价于 set+expire

分布式锁

setnx name codehole  # 如果 name 不存在就执行 set 创建

计数

自增是有范围的,它的范围是 signed long 的最大最小值,超过了这个值,Redis 会报错。

> set codehole 9223372036854775807  # Long.Max
OK

> incr codehole
(error) ERR increment or decrement would overflow

三、String类型常用的场景

1、验证码:经常在一些网站进行登录、注册、获取验证码等操作的时候,都会收到一些验证码,并且说10min后失效。

set phone_num code ex 600

用手机号作为key,验证码作为值,超时6min。这样当你输入好验证码,提交的时候,后台就可以了先get phone_num,再比较你的输入和数据库里面存的值,从而完成身份的验证。

2、缓存功能:String字符串是最常用的数据类型,不仅仅是redis,各个语言都是最基本类型,因此,利用redis作为缓存,配合其它数据库作为存储层,利用redis支持高并发的特点,可以大大加快系统的读写速度、以及降低后端数据库的压力。

3、计数器:许多系统都会使用redis作为系统的实时计数器,可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其它存储介质当中进行永久保存。

4、bitmap位图:可广泛用于,签到、活跃、打卡等场景统计。灰常好用

5、分布式锁 确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  • 资源占用:互斥性。在任意时刻,只有一个客户端能持有锁。
  • 生命周期:不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 多redis:具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
  • 同一人加锁解锁:解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了

PHP具体实现:

class RedLock
{
    private $retryDelay;    // 重试间隔
    private $retryCount;    // 重试次数
    private $clockDriftFactor = 0.01;
    private $quorum;
    private $servers = array();
    private $instances = array();
    function __construct(array $servers, $retryDelay = 200, $retryCount = 3)
    {
        $this->servers = $servers;
        $this->retryDelay = $retryDelay;
        $this->retryCount = $retryCount;
        $this->quorum  = min(count($servers), (count($servers) / 2 + 1));
    }

    public function lock($resource, $ttl)
    {
        $this->initInstances();
        $token = uniqid();
        $retry = $this->retryCount;

        do {
            $n = 0;
            $startTime = microtime( true ) * 1000;
            foreach ($this->instances as $instance) {
                if ($this->lockInstance($instance, $resource, $token, $ttl)) {
                    $n++;
                }
            }

            // 将 2 毫秒添加到漂移中,以考虑 Redis 过期精度,即 1 毫秒,加上小型 TTL 的 1 毫秒最小漂移。
            $drift = ( $ttl * $this->clockDriftFactor ) + 2;
            $validityTime = $ttl - ( microtime( true ) * 1000 - $startTime ) - $drift;
            if ($n >= $this->quorum && $validityTime > 0) {
                return [
                    'validity' => $validityTime,
                    'resource' => $resource,
                    'token'    => $token,
                ];
            } else {
                foreach ( $this->instances as $instance ) {
                    $this->unlockInstance( $instance, $resource, $token );
                }
            }
            // Wait a random delay before to retry
            $delay = mt_rand( floor( $this->retryDelay / 2 ), $this->retryDelay );
            usleep( $delay * 1000 );
            $retry--;
        } while ($retry > 0);
        return false;
    }

    public function unlock(array $lock)
    {
        $this->initInstances();
        $resource = $lock['resource'];
        $token    = $lock['token'];
        foreach ($this->instances as $instance) {
            $this->unlockInstance($instance, $resource, $token);
        }
    }

    private function initInstances()
    {
        if (empty($this->instances)) {
            foreach ($this->servers as $server) {
                list($host, $port, $timeout) = $server;
                $redis = new \Redis();
                $redis->connect($host, $port, $timeout);
                $this->instances[] = $redis;
            }
        }
    }

    private function lockInstance($instance, $resource, $token, $ttl)
    {
        return $instance->set($resource, $token, ['NX', 'PX' => $ttl]);
    }

    private function unlockInstance($instance, $resource, $token)
    {
        // 不但实现了 同一人加锁解锁;而且如果解锁不成功就回滚能够保证 资源的占用
        $script = '
            if redis.call("GET", KEYS[1]) == ARGV[1] then
                return redis.call("DEL", KEYS[1])
            else
                return 0
            end
        ';
        return $instance->eval($script, [$resource, $token], 1);
    }
}

$servers = [
    ['127.0.0.1', 6379, 0.01],
];
$redLock = new RedLock($servers);
$i2Count = 0;echo '<pre>';

while ( $i2Count < 10 ) {
    $lock = $redLock->lock('test', 10000);
    if ($lock) {
        print_r($lock);
        $ret2Unlock = $redLock->unlock( $lock );

        // !$ret2Unlock 则回滚所有操作
    } else {
        print "Lock not acquired\n";
    }

    $i2Count++;
}

四、最后

由于篇幅问题,此文章留下了问题

  • 分布式锁实现思路,方案讨论
  • bitmap位图妙用

今明补上

参考阅读:

  • 《Redis深度历险》 -- 老钱
  • 《Redis 5设计与源码分析》 -- 陈雷 等编著
  • 《Redis实战》-- [美]Josiah L. Carlson