【第二章 API的理解和使用】2.2、字符串

in with 0 comment

命令

  1. 常用命令

    (1)设置值

    set key value [ex seconds] [px milliseconds] [nx|xx]

    下面操作设置键位hello,值为world的键值对,返回结果为OK代表设置成功:

    127.0.0.1:6379> set hello world
    OK
    

    set命令有几个选项:

    • ex seconds:为键设置秒级过期时间。
    • px milliseconds: 为键设置毫秒级过期时间。
    • nx:键必须不存在,才可以设置成功,用于添加。
    • xx:与nx相反,键必须存在,才可以设置成功,用于更新。

    除了set选项,Redis还提供了setex和setnx两个命令:

    setex key seconds key
    setnx key value
    

    它们的作用和ex和nx选项是一样的。下面的列子说明了set、setnx、set xx 的区别。 当前键hello不存在:

    127.0.0.1:6379> exists hello
    (integer) 0
    

    设置键位hello,值为world的键值对:

    127.0.0.1:6379> set hello world
    OK
    

    因为键hello已存在,所以setnx失败,返回结果为0:

    127.0.0.1:6379> setnx hello redis
    (integer) 0
    

    因为键hello已存在,所以set xx成功,返回结果为OK:

    127.0.0.1:6379> set hello jedis xx
    OK
    

    setnx和setxx在实际使用用有什么应用场景吗?以setnx命令为例子,由于 Redis的单线程命令处理机制,如果有多个客户端同时执行setnx key value, 根据setnx的特性只有个客户端能设置成功,setnx可以作为分布式所得一种实现 方案,Redis官方给出了使用setnx实现分布式锁的方法: http://redis.io/topics/distlock。

    (2)获取值

    get key

    下面操作获取键hello的值:

    127.0.0.1:6379> get hello
    "world"
    

    如果要获取的键不存在,则返回nil(空):

    127.0.0.1:6379> get not_exist_key
    (nil)
    

    (3)批量设置值

    mset key value [key value ...]

    下面操作通过mset命令一次性设置4个键值对:

    127.0.0.1:6379> mset a 1 b 2 c 3 d 4
    OK
    

    (4)批量获取值

    mget key [key ...]

    下面操作批量获取了键a、b、c、d的值:

    127.0.0.1:6379> mget a b c d
    1) "1"
    2) "2"
    3) "3"
    4) "4"
    

    如果有些键不存在,那么它的值为nil(空),结果是按照传入键的顺序返回:

    127.0.0.1:6379> mget a b c f
    1) "1"
    2) "2"
    3) "3"
    4) (nil)
    

    批量操作命令可以有效提高开发效率,假如没有mget这样的命令,要执行n次 get命令需要按照图2-8的方式来执行,具体耗时如下:

    • n次get时间=n次网络时间+n次命令时间

    使用mget命令后,要执行n次get命令操作具体耗时如下:

    • n次get时间=1次网络时间+n次命令时间

    Redis可以支撑每秒数万的读写操作,但是这指的是Redis服务端的处理能力, 对于客户端来说,一次命令除了命令时间还是有网络时间,假设网络时间为1 毫秒,命令时间为0.1毫秒(按照每秒处理1万条命令算),那么执行1000次 get命令和1次mget命令的区别如下表:

    操作时间
    1000次get10001+10000.1=1100毫秒=1.1秒
    1次mget(组装了1000个键值对)11+10000.1=101毫秒=0.101秒

    学会使用批量操作,有助于提高业务处理效率,但是要注意的是每次批量操作 所发送的命令数不是无节制的,如果数量过多可能造成Redis阻塞或者网络拥 塞。

    (5)技术

    incr key

    incr命令用于对值做自增操作,返回结果分为三种情况:

    • 值不是整数,返回错误。
    • 值是整数,返回自增后的结果。
    • 键不存在,按照值为0自增,返回结果为1.

    例如对一个不存在的键执行incr操作后,返回结果是1:

    127.0.0.1:6379> exists key
    (integer) 0
    127.0.0.1:6379> incr key
    (integer) 1
    

    再次对键执行incr命令,返回结果是2:

    127.0.0.1:6379> incr key
    (integer) 2
    

    如果值不是整数,那么会返回错误:

    127.0.0.1:6379> set hello world
    OK
    127.0.0.1:6379> incr hello
    (error) ERR value is not an integer or out of range
    

    除了incr命令,Redis提供了decr(自减)、incrby(自增指定数字)、 decrby(自减指定数字)、incrbyfloat(自增浮点数):

    decr key
    incrby key increment
    decrby key decrement
    incrbyfloat key increment
    

    很多存储系统和编程语言内部使用CAS机制实现技术功能,会有一定的CPU消 耗,但在Redis只给你完全不存在这个问题,因为Redis是单线程架构,任何命 令到了Redis服务端都要顺序执行。

  2. 不常用命令

    (1)追加值

    append key value

    append可以向字符串尾部追价值,例如:

    127.0.0.1:6379> get key
    "redis"
    127.0.0.1:6379> append key world
    (integer) 10
    127.0.0.1:6379> get key
    "redisworld"
    

    (2)字符串长度

    strlen key

    例如,当前值为redisworld,所以返回值为10:

    127.0.0.1:6379> get key
    "redisworld"
    127.0.0.1:6379> strlen key
    (integer) 10
    

    下面操作返回结果为6,因为每个中文占3个字节:

    127.0.0.1:6379> set hello "世界"
    OK
    127.0.0.1:6379> strlen hello
    (integer) 6
    

    (3)设置并返回原值

    getset key value

    getset和set一样会设置值,但是不同的是,他同时会返回键原来的值,例如:

    127.0.0.1:6379> getset hello world
    (nil)
    127.0.0.1:6379> getset hello redis
    "world"
    

    (4)设置指定位置的字符

    setrange key offeset value

    下面操作将值由pest变为best:

    127.0.0.1:6379> set redis pest
    OK
    127.0.0.1:6379> sterange redis 0 b
    (integer) 4
    127.0.0.1:6379> get redis
    "best"
    

    (5)获取部分字符串

    getrange key start end

    start和end分别是开始和结束的偏移量,偏移量从0开始计算,例如下面操作 获取了值best的前两个字符。

    127.0.0.1:6379> getrange redis 0 1
    "be"
    

    下表是字符串类型命令的时间复杂度

    命令时间复杂度
    set key valueO(1)
    get keyO(1)
    del key [key ...]O(k),k是键的个数
    mset key value [key value ...]O(k),k是键的个数
    mget key [key ...]O(k),k是键的个数
    incr keyO(1)
    decr keyO(1)
    incrby key incrementO(1)
    decrby key decrementO(1)
    incrbyfloat key incrementO(1)
    append key valueO(1)
    strlen keyO(1)
    setrange key offset valueO(1)
    getrange key start endO(n),n是字符串长度,由于获取字符串非常快,所以如果字符串不是很长,可以视同为O(1)

内部编码

  1. 字符串类型的内部编码有3种:

    • int: 8个字节的长整型。
    • embstr: 小于等于39个字节的字符串。
    • raw: 大于39个字节的字符串。

    Redis会根据当前值的类型和长度决定使用哪种内部编码实现。 整数类型示例如下:

    127.0.0.1:6379> ste key 8653
    OK
    127.0.0.1:6379> object encoding key
    "int"
    

    短字符串实例如下:

    #小于等于39个字节的字符串:embstr
    127.0.0.1:6379> set key "hello,world"
    OK
    127.0.0.1:6379> object encoding key
    "embstr"
    

    长字符串示例如下:

    #大于39个字节的字符串:raw
    127.0.0.1:6379> set key "one string greater than 39 byte........"
    OK
    127.0.0.1:6379> object encoding key
    "raw"
    127.0.0.1:6379> strlen key
    (integer) 40
    

典型使用场景

  1. 缓存功能

    Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获 取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低 后端压力的作用。 下面伪代码模拟了一个访问过程:

    (1)该函数用于获取用户的基础信息:

    UserInfo getUserInfo(long id){
        ...
    }
    

    (2)首先从Redis获取用户信息:

    //定义键
    userRedisKey = "user:info:" + id;
    //从Redis获取值
    value = redis.get(usrRedisKey);
    if(value != null){
        //将值进行反序列化为UserInfo并返回结果
        userInfo = deserialize(value);
        return userInfo;
    }
    

    (3)如果没有从Redis获取到用户信息,需要从MySQL中进行获取,并将结果 会写到Redis,添加1小时(3600秒)过期时间:

    //从MySQL获取用户信息
    userInfo = mysql.get(id);
    //将userInfo序列化,并存入Redis
    redis.setex(userRedisKey, 3600, seriallize(userInfo));
    //返回结果
    return userInfo;
    

    整个功能的伪代码如下:

    UserInfo getUserInfo(long id){
        userRedisKey = "user:info:" + id;
        value = redis.get(userRedisKey);
        UserInfo userInfo;
        if(value != null){
            userInfo = deserialize(value);
        }else{
            userInfo = mysql.get(id);
            if(userInfo != null){
                redis.setex(userRedisKey, 3600, serialize(userInfo));
            }
        }
        return userInfo;
    }
    
  2. 计数

许多应用都会使用Redis作为技术的基础工具,它可以实现快速计数、查询缓存的功 能,同时数据可以异步落地到其他数据源。例如可将视频播放数系统就是使用Redis 作为视频播放数计数的基础组件,用户每播放一次视频,相应的视频播放数就会自 增1:

long incrVideoCounter(long id){
    key = "video:playCount:" + id;
    return redis.incr(key);
}
  1. 共享session

    一个分布式Web服务将用户的Session信息保存在各自服务器中,这样会造成 一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务 器上,用户刷新一次访问可能会发现需要重新登录,这个问题是用户无法容忍 的。为了解决这个问题,可以使用Redis将用户的Session进行集中管理,将 Web服务的Session交于Redis集中管理,在这种模式下只要保证Redis高可用和 扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取。

  2. 限速

    很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证码,从 而确定是否是用户本人。但是为了短信结构不被频繁访问,会限制用户没分钟 获取验证码的频率,例如一分钟不能超过5次,此功能可以使用Redis实现,下 面的伪代码给出了基本实现思路:

    phoneNum = "138xxxxxxxx";
    key = "shortMsg:limit:" + phoneNum;
    //SET key value ex 60 NX
    isExists = redis.set(key, 1,1 "EX 60", "NX");
    if(isExists != null || redis.incr(key) < = 5>){
        //通过
    }else{
        //限速
    }
    

    上述就是利用Redis实现了限速功能,例如一些网站限制一个IP地址不能再一 秒钟之内访问超过n次也可以采用类似的思路。