Redis

5 minute read

Published:

This is my personal notes for redis.

Redis简介

1 Redis是什么?

  • 是一个完全开源免费的key-value内存数据库
  • 通常被认为是一个数据结构服务器,主要是因为其有着丰富的数据结构 strings、map、 list、sets、 sorted sets
    通常而言目前的数据库分类有几种,包括 SQL/NSQL,,关系数据库,键值数据库等等 等,分类的标准也不以,Redis本质上也是一种键值数据库的,但它在保持键值数据库简单快捷特点的同时,又吸收了部分关系数据库的优点。从而使它的位置处于关系数据库和键值数 据库之间。Redis不仅能保存Strings类型的数据,还能保存Lists类型(有序)和Sets类型(无序)的数据,而且还能完成排序(SORT) 等高级功能,在实现INCR,SETNX等功能的时候,保证了其操作的原子性,除此以外,还支持主从复制等功能。

2 redis不是什么?

  • 不是sql server、mySQL等关系型数据库,主要原因是: 
    • redis目前还只能作为小数据量存储(全部数据能够加载在内存中) ,海量数据存储方面并不是redis所擅长的领域 
    • 设计、实现方法很不一样.关系型数据库通过表来存储数据,通过SQL来查询数据。而Redis通上述五种数据结构来存储数据,通过命令 来查询数据
  • 不是Memcached等缓存系统,主要原因有以下几个: 
    • 网络IO模型方面:Memcached是多线程,分为监听线程、worker线程,引入锁,带来了性能损耗。Redis使用单线程的IO复用模型,将速度优势发挥到最大,也提供了较简单的计算功能 
    • 内存管理方面:Memcached使用预分配的内存池的方式,带来一定程度的空间浪费 并且在内存仍然有很大空间时,新的数据也可能会被剔除,而Redis使用现场申请内存的方式来存储数据,不会剔除任何非临时数据 Redis更适合作为存储而不是cache 
    • 数据的一致性方面:Memcached提供了cas命令来保证.而Redis提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断 
    • 存储方式方面:Memcached只支持简单的key-value存储,不支持枚举,不支持持久化和复制等功能
  • 总之, Redis是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。

Redis数据类型

键的类型只能为String,值支持五种数据类型:String、List、Set、Hash、ZSet。

数据类型可以存储的值操作
String字符串、整数或者浮点数对整个字符串或者字符串的其中一部分执行操作, 对整数和浮点数执行自增或者自减操作
List列表从两端压入或者弹出元素,对单个或者多个元素进行修剪,只保留一个范围内的元素
Set无序集合添加、获取、移除单个元素, 检查一个元素是否存在于集合中, 计算交集、并集、差集, 从集合里面随机获取元素
Hash包含键值对的无序散列表添加、获取、移除单个键值对, 获取所有键值对, 检查某个键是否存在
Zset有序集合添加、获取、删除元素, 根据分值范围或者成员来获取元素, 计算一个键的排名
  • List
    
      > lpush list1 aa
      (integer) 1
      > rpush list1 bb
      (integer) 2
      > rpush list1 aa
      (integer) 3
    
      > lrange list1 0 -1
      1) "aa"
      2) "bb"
      3) "aa"
    
      > lindex list1 1
      "bb"
    
      > lpop list1
      "aa"
    
      > rpop list1
      "aa"
    
      > lrange list1 0 -1
      1) "bb"
    
    
  • Set

    
      > sadd set1 aa
      (integer) 1
      > sadd set1 bb
      (integer) 1
      > sadd set1 cc
      (integer) 1
      > sadd set1 aa
      (integer) 0
    
      > smembers set1
      1) "bb"
      2) "cc"
      3) "aa"
    
      > sismember set1 dd
      (integer) 0
      > sismember set1 cc
      (integer) 1
    
      > srem set1 aa
      (integer) 1
      > srem set1 ee
      (integer) 0
    
      > smembers set1
      1) "bb"
      2) "cc"
    
    
  • Hash

    
      > hset hash1 a 1
      (integer) 1
      > hset hash1 b 2
      (integer) 1
      > hset hash1 a 3
      (integer) 0
    
      > hgetall hash1
      1) "a"
      2) "3"
      3) "b"
      4) "2"
    
      > hdel hash1 a
      (integer) 1
      > hdel hash1 a
      (integer) 0
    
      > hget hash1 b
      "2"
    
      > hgetall hash1
      1) "b"
      2) "2"
    
    
  • ZSET

    
      > zadd zset1 1 a
      (integer) 1
      > zadd zset1 2 b
      (integer) 1
      > zadd zset1 50 c
      (integer) 1
      > zadd zset1 3 b
      (integer) 0
    
      > zrange zset1 0 -1 withscores
      1) "a"
      2) "1"
      3) "b"
      4) "3"
      5) "c"
      6) "50"
    
      > zrangebyscore zset1 1 50 withscores
      1) "a"
      2) "1"
      3) "b"
      4) "3"
      5) "c"
      6) "50"
    
      > zrem zset1 c
      (integer) 1
      > zrem zset1 c
      (integer) 0
    
      > zrange zset1 0 -1 withscores
      1) "a"
      2) "1"
      3) "b"
      4) "3"
    
    

    实现方式:

    zset 结构体里有两个元素
    一个是 dict,用来维护 数据 到 分数 的关系
    一个是 zskiplist,用来维护 分数所在链表 的关系

      typedef struct zset {
          dict *dict;
          zskiplist *zsl;
      } zset;
    

    与set无顺序存储不同,Zset按score顺序进行存储,这也是为什么基本操作都是O(log(N))复杂度。

    Redis使用两种结构存储zset,在数据个数较少时使用ziplist,数量超出阈值时使用skiplist,阈值通过zset-max-ziplist-entries and zset-max-ziplist-value设置。

    ziplist使用连续空间存储双向链表,相比基于堆空间指针的链表前后向移动速度更快。

    skiplist保存一个有序排列的链表,通过采用多层存储且保持每一层链表是其上一层链表的自己,从最稀疏的层开始搜索,从而达到比链表O(N)更优的查找和插入性能O(log(N))。

    avatar

Redis数据淘汰策略

策略描述
volatile-lru在已设置过期时间的数据集中执行lru策略, 即最近未使用的被淘汰
volatile-ttl在已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random在已设置过期时间的数据集中随机挑选
volatile-random在已设置过期时间的数据集中随机挑选
volatile-lfu(redis4.0)在已设置过期时间的数据集中执行lfu策略, 即访问频率最小的被淘汰
allkeys-lru在所有的数据集中执行lru策略, 即最近未使用的被淘汰
allkeys-random在所有的数据集中随机挑选
allkeys-lfu(redis4.0)在所有的数据集中执行lfu策略, 即访问频率最小的被淘汰
noeviction禁止驱逐数据

作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法实际实现上并非针对所有 key,而是抽样一小部分并且从中选出被淘汰的 key。

使用 Redis 缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。

Redis持久化

Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。

  • RDB 持久化

    将某个时间点的所有数据都存放到硬盘上。

    • 可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。

    • 如果系统发生故障,将会丢失最后一次创建快照之后的数据。

    • 如果数据量很大,保存快照的时间会很长。

  • AOF 持久化

    将写命令添加到 AOF 文件(Append Only File)的末尾。

    使用 AOF 持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。有以下同步选项:

    • always(每个写命令都同步): 严重减低服务器的性能
    • everysec(每秒同步一次): 可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;
    • no(让操作系统来决定何时同步): 并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量

    随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。

Redis使用场景

  • 计数器

    可以对 String 进行自增自减运算,从而实现计数器功能。

    Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。

  • 缓存

    将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。

  • 查找表

    例如 DNS 记录就很适合使用 Redis 进行存储。

    查找表和缓存类似,也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效,而缓存的内容可以失效,因为缓存不作为可靠的数据来源。

  • 消息队列

    List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息

    不过最好使用 Kafka、RabbitMQ 等消息中间件。

  • 会话缓存

    可以使用 Redis 来统一存储多台应用服务器的会话信息。

    当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。

  • 分布式锁实现

    在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。

    可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。

  • 其它

    Set 可以实现交集、并集等操作,从而实现共同好友等功能。

    ZSet 可以实现有序性操作,从而实现排行榜等功能。

  • 一个简单的论坛系统

    可以发布文章: 用Hash存储文章信息
    key = article:id, value = {title:xxx, author:xxx, time:xxx}

    可以对文章进行点赞: 用Set存储用户点赞信息
    key = vote:articleId, value = {user:1,user:2,user:3}

    在首页可以按文章的发布时间或者文章的点赞数进行排序显示: 用ZSet存储文章的time/vote
    key = time:, value = {(article:id1,time1),(article:id2,time2)}
    key = vote:, value = {(article:id1,vote1),(article:id2,vote2)}

Redis 缓存雪崩 缓存穿透

  • 缓存雪崩

    原因: 同一时间缓存大面积失效,就像没有缓存一样,所有的请求直接打到数据库上来,DB扛不住挂了,瞬间倒一片。

    解决: 批量往redis存数据的时候,把每个key的失效时间加上个随机数,这样的话就能保证数据不会在同一个时间大面积失效。

  • 缓存穿透

    原因: 就是指用户不断发起请求的数据,在缓存和DB中都没有,比如DB中的用户ID是自增的,但是用户请求传了-1,或者是一个特别大的数字。

    解决:

    • 方法一:在接口层增加校验,不合法的参数直接返回。不相信任务调用方,根据自己提供的API接口规范来,作为被调用方,要考虑可能任何的参数传值。
    • 方法二:在缓存查不到,DB中也没有的情况,可以将对应的key的value写为null,或者其他特殊值写入缓存,同时将过期失效时间设置短一点,以免影响正常情况。这样是可以防止反复用同一个ID来暴力攻击。
    • 方法三:正常用户是不会这样暴力功击,只有是恶意者才会这样做,可以在网关NG作一个配置项,为每一个IP设置访问阀值。
    • 方法四:高级用户布隆过滤器(Bloom Filter),这个也能很好地防止缓存穿透。原理就是利用高效的数据结构和算法快速判断出你这个Key是否在DB中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。

Redis 原子性和回滚

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。

事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。

以下是这种做法的优点:

Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。

因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR key 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR key , 回滚是没有办法处理这些情况的。

鉴于没有任何机制能避免程序员自己造成的错误, 并且这类错误通常不会在生产环境中出现, 所以 Redis 选择了更简单、更快速的无回滚方式来处理事务。

Redis 与 Memcached

两者都是非关系型内存键值数据库,主要有以下不同:

  • 数据类型

    Memcached 仅支持字符串类型,而 Redis 支持五种不同的数据类型,可以更灵活地解决问题。

  • 数据持久化

    Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。

  • 分布式

    Memcached 不支持分布式,只能通过在客户端使用一致性哈希来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。

    Redis Cluster 实现了分布式的支持。

  • 内存管理机制

    在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。

    Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。

Redis优缺点

  • 1 Redis能做什么?

    通常局限点来说,Redis也以消息队列的形式存在,作为内嵌的List存在,满足实时的高并发需求。而通常在一个电商类型的数据处理过程之中,有关商品,热销,推荐排序的队列,通常存放在Redis之中,期间也包扩Storm对于Redis列表的读取和更新。

  • 2 Redis的优点
    • 性能极高 – Redis能支持超过 100K+ 每秒的读写频率。
    • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
    • 原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
    • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
  • 3 Redis的缺点

    数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

  • 4 Redis的特点

    • 速度快:使用标准C写,所有数据都在内存中完成,读写速度分别达到10万/20万 
    • 持久化:对数据的更新采用Copy-on-write技术,可以异步地保存到磁盘上,主要有两种策略,一是根据时间,更新次数的快照(save 300 10 )二是基于语句追加方式(Append-only file,aof) 
    • 自动操作:对不同数据类型的操作都是自动的,很安全 
    • 快速的主–从复制,官方提供了一个数据,Slave在21秒即完成了对Amazon网站10G key set的复制。 
    • Sharding技术: 很容易将数据分布到多个Redis实例中,数据库的扩展是个永恒的话题,在关系型数据库中,主要是以添加硬件、以分区为主要技术形式的纵向扩展解决了很多的应用场景,但随着web2.0、移动互联网、云计算等应用的兴起,这种扩展模式已经不太适合了,所以近年来,像采用主从配置、数据库复制形式的,Sharding这种技术把负载分布到多个特理节点上去的横向扩展方式用处越来越多。

Redis本地配置

1 下载redis, 下载地址
2 解压redis包, 进入该目录, make进行编译(需要gcc环境)
3 编译完成进行安装, sudo make install PREFIX=/usr/local/redis
4 进入安装目录, bin目录下出现redis-server, ./redis-server可启动(前台启动没有作用)
5 将安装包中的配置文件redis.conf拷贝到安装目录下
6 修改配置文件, (1) bind 127.0.0.1 改为 bind 0.0.0.0 (2) daemonize no 改为 daemonize yes (3) port 6379 保持不变 (4) requirepass foobared 改为 requirepass xxx(密码)
7 ./redis-server redis.conf (使用新的配置文件启动) 启动redis
8 启动成功后, ./redis-cli -p 6379 -a <密码> 可在terminal使用redis

127.0.0.1:6379> set hello helloworld
OK
127.0.0.1:6379> get hello
"helloworld"

9 redis关闭
(1)在交互模式下直接输入shutdown (2)在terminal中输入./redis-cli -a <密码> shutdown

常见错误:
1 启动redis报错

# Could not create server TCP listening socket *:6379: bind: Address already in use“

解决: 重启redis
(1) 正常shutdown关闭
(2) 强制kill关闭
查看redis进程

luolingweideMBP:bin luolingwei$ ps -ef | grep -i redis
  501 31285     1   0  5:53PM ??         0:18.81 ./redis-server *:6379
  501 35310 35237   0 11:36PM ttys000    0:00.01 grep -i redis

kill掉正在运行的进行31285

kill -9 31285

重启redis即可

2 强制关闭(kill -9)导致redis不能持久化
rdm报错

 -MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.

解决:
terminal中修改redis配置

127.0.0.1:6379> config set stop-writes-on-bgsave-error no
OK

3 redis使用shutdown报错
解决:
参考博客1
参考博客2
(1) 在安装路径下新建两个文件夹 redis_log redis_dbfile (和redis-server平级)
(2) 在两个文件夹下分别新建文件redis_log.log 和 dump.rdb
(3) 修改上述文件和文件夹的权限,chmod 777 <file/dir>
(3) 修改redis.conf, dir ./改为 dir /usr/local/redis/bin/redis_dbfile/, logfile “” 改为 logfile /usr/local/redis/bin/redis_log/redis_log.log
(4) shutdown恢复正常

Redis Desktop Manager 安装

破解版下载地址

进入rdm, connect to redis server, 填入参数如下
Name: 随便起
Host: 本机ip
Port: 默认6379
Auth: 填入上述设置的redis密码