《Redis开发与运维》第二章|《Redis开发与运维》第二章 API的理解和使用(上)读书笔记

全局命令
??Redis有五种数据结构,它们是键值对中的值,对于键来说有一些通用的命令。

  • 查看所有的键(keys *keys *会遍历所有的键,生产环境禁止使用。
172.17.236.250:6379> set hello world OK 172.17.236.250:6379> set java jedis OK 172.17.236.250:6379> set python redis-py OK 172.17.236.250:6379> keys * 1) "java" 2) "python" 3) "hello" 172.17.236.250:6379> 复制代码

  • 键总数(dbsizedbsize命令计算键总数时直接获取Redis内置的键总数变量,不会去遍历所有的键。
172.17.236.250:6379> dbsize (integer) 3 复制代码

  • 检查键是否存在(exists key) 存在返回1,不存在返回0。
172.17.236.250:6379> exists java (integer) 1 172.17.236.250:6379> exists pp (integer) 0 复制代码

  • 删除键(del key \[key ...\]) del无论值是什么类型,del命令都可以将其删除。也可以支持删除多个键。返回结果为删除成功的键数,如果删除一个不存在的键则返回0。
172.17.236.250:6379> del java (integer) 1 172.17.236.250:6379> del python hello (integer) 2 172.17.236.250:6379> exists java (integer) 0 复制代码

  • 键过期(expire key seconds) Redis支持对键添加过期时间,当超过过期时间后,会自动删除键,例如为键hello设置10秒过期时间。
172.17.236.250:6379> set hello world OK 172.17.236.250:6379> expire hello 10 (integer) 1 复制代码

  • 返回键过期的剩余时间(ttl keyttl key有三种返回值: ??大于等于0的整数:键剩余的过期时间; ??-1:键没有设置过期时间; ??-2:键不存在。
172.17.236.250:6379> set hello world OK 172.17.236.250:6379> ttl helo (integer) -2 172.17.236.250:6379> ttl hello (integer) -1 172.17.236.250:6379> expire hello 10 (integer) 1 172.17.236.250:6379> ttl hello (integer) 5 复制代码

  • 键的数据结构类型(type keytype key会显示键的数据结构类型,如果键不存在则返回结果为none
数据结构和内部编码
??type key命令实际返回的就是键当前的数据结构类型,分别是string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)。但是这些都是Redis对外的数据结构。如下图所示:
??每一种数据结构都有自己的底层内部编码实现,并且是多种实现,Redis会根据具体的场景去选择合适的内部编码。如下图所示:
  • 查看内部编码(object encoding key),例如查看键hello对应值的内部编码
172.17.236.250:6379> set hello world OK 172.17.236.250:6379> object encoding hello "embstr" 复制代码

  • 设计优点
    • 可以改进内部编码,但是对外部的数据结构无影响;
    • 多种内部编码实现可以在不同场景下发挥各自的优势;
单线程架构
  • 开启两个客户端同时执行命令:
127.0.0.1:6379> incr counter 127.0.0.1:6379> incr counter 复制代码

??看到如下Redis客户端与服务端的简化模型图:
??因为Redis是单线程处理命令,所以当一条命令到达服务端是不会被立即执行,所有的命令都会进入到一个队列中,然后再逐步执行,所以上面两个命令的执行顺序是不确定的。如下图所示: ??但是可以确定的是不会有两条命令同时执行,所以两条 incr命令不管是怎么执行都是2,不会产生并发问题。
为什么Redis单线程还能那么快?
  • Redis将所有的数据存在内存中,是纯内存操作;
  • 非阻塞I/O,Redis使用epoll作为I/O多路复用技术实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转行为事件,不会在网络I/O上浪费时间。如下图:
  • 单线程避免了线程切换和竞态产生消耗;
单线程的好处:
  • 简化数据结构和算法实现;
  • 避免了线程切换和竞态的消耗;
单线程的问题:
  • 如果某个命令执行过长,会造成其它命令阻塞,这对Redis是致命的。
字符串
【《Redis开发与运维》第二章|《Redis开发与运维》第二章 API的理解和使用(上)读书笔记】??字符串是Redis最基础的数据结构。首先键都是字符串类型,并且其他数据结构都是在字符串类型的基础上进行构建的。 ??字符串类型的值可以为:
  • 字符串(简单的字符串和复杂的字符串(例如:JSON、XML))
  • 数字(整数、浮点数)
  • 二进制(音视频或图片(不能超过512MB))
字符串常用操作命令 设置值
  • 设置成功则返回OK
set key value \[ex seconds\] \[px milliseconds] \[nx|xx\] 复制代码

set命令有以下几个选项:
  • ex seconds:为键设置秒级过期时间;
  • px milliseconds:为键设置毫秒级过期时间;
  • nx:键必须不存在才可以设置成功;
  • xx:与nx相反,键必须存在,才可以设置成功; 除了set之外,Redis还提供了setexsetnx两个命令,作用和set命令的exnx选项一样。如下:
setex key seconds value setnx key value 复制代码

用例子说明set、setnx、set xx的区别:
##键hello不存在 172.17.236.250:6379> exists hello (integer) 0 ##set xx更新键hello的值返回nil 172.17.236.250:6379> set hello redis xx (nil) ##为键hello设置值 172.17.236.250:6379> set hello world OK ##setnx为键hello设置值,因为键hello已存在所以设置失败返回0 172.17.236.250:6379> setnx hello redis (integer) 0 ##set xx为键hello更新值成功 172.17.236.250:6379> set hello jedis xx OK 172.17.236.250:6379> get hello "jedis" 复制代码

  • 批量设置值
###mset key value \[key value ...\] 172.17.236.250:6379> mset a 1 b 2 c 3 d 4 OK 复制代码

??在实际的应用场景中,由于Redis是单线程的,如果有多个客户端同时执行setnx key value,根据setnx的特性只有一个客户端能设置成功,setnx可以作为分布式锁的一个实现方案(Redis官方使用 setnx 实现分布式锁方案传送门)
获取值
  • 获取单个值,如果要获取的键不存在则返回nil
get key 复制代码

  • 批量获取值(如果获取的键不存在则返回nil
mget key \[key ... \] 172.17.236.250:6379> mget a b c d 1) "1" 2) "2" 3) "3" 4) "4" 复制代码

??批量操作命令可以提高开发效率,如果没有mget这样的命令,要执行n次get命令会按照下图方式执行,具体耗时如下:
n次get时间 = n次网络时间 + n次命令执行时间 复制代码


??使用 mget命令会按照下图方式执行,具体耗时如下:
n次get时间 = 1次网络时间 + n次命令时间 复制代码


??Redis一次命令的执行时机包括网络时间和命令执行时间,Redis服务端的执行速度已经够快。 网络可能会成为性能瓶颈。在实际开发场景中,每次批量操作发送的命令数不是无节制的,数量过多可能会造成Redis阻塞或网络堵塞
计数
incr key 复制代码

incr命令用于对值进行自增操作,返回情况有三种:
  • 值不是整数,返回错误;
  • 值是整数,返回自增后的结果;
  • 键不存在,按照值为0自增,返回结果为1。 ??示例如下:
### 键 a 不存在 172.17.236.250:6379> exists a (integer) 0 ### 对不存在的键 a 进行自增,返回结果为 1 172.17.236.250:6379> incr a (integer) 1 ### 再次对 a 进行自增操作,返回结果为 2 172.17.236.250:6379> incr a (integer) 2 ### 值不是整数,报错 172.17.236.250:6379> set hello world OK 172.17.236.250: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 increment incrbyfloat key increment172.17.236.250:6379> decr a (integer) 1 172.17.236.250:6379> incrby a 5 (integer) 6 172.17.236.250:6379> decrby a 5 (integer) 1 172.17.236.250:6379> incrbyfloat a 4.9 "5.9" 复制代码

字符串不常用操作命令 追加值
append key value 复制代码

??append可以向字符串尾部追加值,如下所示:
172.17.236.250:6379> set hello world OK 172.17.236.250:6379> get hello "world" 172.17.236.250:6379> append hello redis (integer) 10 172.17.236.250:6379> get hello "worldredis" 复制代码

字符串长度
strlen key 复制代码

??当前值为worldredis,所以返回长度为10(中文占用3个字节),如下:
172.17.236.250:6379> strlen hello (integer) 10 复制代码

设置并返回原值
getset key value 复制代码

??getsetset会设置值,不同的是,它会返回这个键原来的值,如下:
172.17.236.250:6379> getset hello world (nil) 172.17.236.250:6379> getset hello redis "world" 复制代码

设置指定位置的字符串
setrange key offeset value 复制代码

??将"adcd"变成"ddcd":
172.17.236.250:6379> set redis adcd OK 172.17.236.250:6379> setrange redis 0 d (integer) 4 172.17.236.250:6379> get redis "ddcd" 复制代码

获取部分字符串
getrange key start end 复制代码

??start和end分别是开始和结束的偏移量,偏移量从0开始计算,如下:
172.17.236.250:6379> getrange redis 0 1 "dd" 复制代码

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

内部编码 字符串的内部编码有3种:
  • int:8个字节的长整型;
  • embstr:小于等于39个字节的字符串;
  • raw:大于39个字节的字符串。 Redis会根据当前值的类型和长度决定使用哪种内部编码实现。如下所示:
#### int 类型 172.17.236.250:6379> set key 81 OK 172.17.236.250:6379> object encoding key "int" #### embstr 类型 172.17.236.250:6379> set key hello,world OK 172.17.236.250:6379> object encoding key "embstr" #### raw 类型 172.17.236.250:6379> set key qwertyuiopasdfghjklzxcvbnmqwertyuioplkjhgfdsaczvxbnm OK 172.17.236.250:6379> object encoding key "raw" 复制代码

典型使用场景 缓存功能 ??Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。因为Redis有支撑高并发的特性,所以缓存能起到加速读写和降低后端压力的作用。如下图:
思路如下:
  • 定义获取用户基础信息函数
  • 首先从Redis获取用户信息
  • 若没有从Redis中获取到用户信息,需要从MySQL中进行获取,并且将结果写入Redis中,添加过期时间
  • 伪代码如下:
UserInfo getUserInfo(long id) { //定义键 userRedisKey = "user:info:"+id; //从Redis获取值 value = https://www.it610.com/article/redis.get(userRedisKey); if(value!=null){ //将值进行反序列化为UserInfo并返回结果 userInfo = deserialize(value); return userInfo; } else { // 从MySQL获取用户信息 userInfo = mysql.get(id); // 将userInfo序列化,设置3600秒过期时间,存入Redis if (userInfo != null) { redis.setex(userRediskey,3600,serialize(userInfo)); } } } 复制代码

??注意:Redis没有命令空间,也没有对键名有强制要求(除了不能使用一些特殊字符)。键名要设计合理,有利于防止键冲突项目的可维护性。推荐使用"业务名: 对象名:id:[属性]"作为键名。可以在能描述键的含义下适当减少键的长度,从而减少键过长而造成的内存浪费。
计数 ??使用Redis作为计数的基础工具,他可以实现快速计数、查询缓存功能,同时数据可以异步落地到其他数据源。 伪代码如下:
long incrVideoCounter(long id) { key = "video:playCount:"+id; return redis.incr(key); } 复制代码

??在实际开发中,计数系统要考虑很多东西:防作弊、按照不同的维度计数,数据持久化到底层数据源等等。
共享session ??一个分布式Web服务将用户的session信息保存在各自的服务器上,但是出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同的服务器上,这样就导致用户刷新一次访问可能就会发现需要重新登录。如下图:
??为了解决这个问题,开发人员可以使用Redis将用户的session进行集中的管理。在这种模式下只需要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都可以直接从Redis种获取。如下图:
限速 ??很多应用在每次进行登录时,会让用户输入手机验证码来确定是否是用户本人。为了让短信接口不被频繁访问,会限制用户在每分钟的获取验证码的频率,例如一分钟不能超过5次。如下图:
??伪代码如下:
phoneNum=""135xxxxxxxx; key = "shortMsg:limit:"+phoneNum; isExists = redis.set(key,1,"EX 60","NX"); if(isExists != null || redis.incr(key) <= 5) { //通过 } else { //限速 } 复制代码

??例如某些网站不能在1秒之内访问超过n次也可以采用类似的思路。 ??除了以上几种应用场景,字符串还有很多的应用场景,这需要我们结合字符串提供的相应命令去决定怎样使用。
转载于:https://juejin.im/post/5c2218b851882546150b06bd

    推荐阅读