Redis详解 单线程 基于内存设计 主从 持久化 与 memcached区别 及常见问题

1. Redis简介

Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。

Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能,比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可以做高性能的tag系统等等。
另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。

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

2. Redis支持的数据类型与适用场景

2.1 数据类型

Redis本质上是key-value对的内存数据库,key(键)使用字符串存储,但是key中不能出现空格或者换行符 \n,原因是空格和换行符都是Redis的特殊字符,但只限于key。value可以使用任何字符。

Redis以换行符 \n 作为命令结束符,所以在key中不能存在 \n,否则就会报错。此外,Redis以空格作为命令和参数的分隔符,所以在key中也不能存在空格。
尽量使用较短的key,因为较短的key可以节省内存和带宽。

Redis通过Key-Value的单值不同类型来区分, 以下是支持的类型:

  • string

    string数据类型是二进制安全的,可以把图片、css文件、视频文件等保存在string中,为了提供网站的运行速度,可以用string类型缓存一些静态文件,如:图片、css文件等。string类型支持增量操作,可用作统计计算,如统计网站访问次数。

  • list

    list数据类型指key对应的value是一个双向链表结构,所以list类型提供链表支持的所有操作。list类型在互联网应用中非常有用,例如存放微博中“我的关注列表”或者论坛中所有的回帖ID。

    另外,使用list还可以实现消息队列功能,减轻数据库的压力。消息队列类似于现实生活中的排队,每次有消息到达时就把消息放进队列尾部,取出消息时就从队列头部取出。要用list实现消息队列,先用rpush命令把消息放进队列尾部,然后使用lpop命令把消息从队列头部取出。

  • set

    set数据类型是一种无序集合。优点是快速查找元素是否存在,用于记录一些不能重复的数据。例如:网站中注册的用户名,如果要注册的用户名已经存在于集合中,就拒绝此用户注册。

    set类型通常用于记录做过某些事情。例如:在投票系统中,每个用户一天只能投票一次,那么可以使用set类型来记录某个用户的投票情况,只需要以日期作为key,将用户ID作为集合中的元素即可。要查看某个用户今天是否投过票,只需以今天的日期作为key去集合中查询用户ID是否存在。

  • zset

    即sorted set类型。zset类型和set类型很相似,都是string类型元素的集合,不同的是zset类型属于有序集合,它通过一个double类型的整数score对集合中的元素进行排序。zset通过SkipList(跳跃表)和HashTable组合完成。SkipList负责排序,而HashTable负责保存数据。

    set类型能做的事情zset也可以做,而且zset还可以完成一些set不能做的事情,例如使用zset构建一个具有优先级的队列,这也是list类型不能实现的。

    zset类型在Web应用中非常有用。例如,排行榜应用中按“顶贴”次数排序,方法是:将排序的值设置成zset的score值,将具体数据设置成相应的value,用户每次按“顶贴”按钮时,只需执行zadd命令修改score的值。

  • hash

    hash类型是每个key对应一个HashTable,hash类型适合存储对象,例如用户信息对象,把用户ID作为key,可以把用户信息保存到hash类型中。

    新建一个hash类型对象时,为了节省内存,Redis使用zipmap存储数据。这个zipmap并不是真正的HashTable,但是相比普通HashTable,zipmap节省不少内存。如果field或value的大小超出一定限制,Redis在内部自动将zipmap替换成正常的HashTable存储。修改配置文件的hash_max_zipmap_entries和hash_max_zipmap_value选项,可设置这两个限制。

常用操作可以参见这里

2.2 适用场景

  1. 会话缓存(Session Cache)

    最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?

    幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。

  2. 全页缓存(FPC)

    除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。

    再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端

    此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。

  3. 队列

    Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。

    如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。

  4. 排行榜/计数器

    Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:

    当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:

    ZRANGE user_scores 0 10 WITHSCORES

  5. 发布/订阅

    发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统。

3. Redis设计

3.1 为什么redis需要把所有数据放到内存中?

Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。
如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

3.2 Redis是单进程单线程的

redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销

4. 虚拟内存

Redis的数据是保存在内存中的,可能出现物理内存不足的情况。物理内存不足时,Redis使用什么方法解决问题呢?答案是使用“虚拟内存”(VM)。
redis的虚拟内存与操作系统的虚拟内存不是一回事,但思路和目的是相同的。就是暂时把不经常访问的value从内存交换到磁盘中保存,同时,Redis会把value对应的key都放在内存中,当用户访问这些很少访问的数据时,Redis才会把key对应的value从磁盘导入到内存中。从而,腾出宝贵的内存空间用于其他需要经常访问的数据。

对于redis这样的内存数据库,内存总是不够用的。除了可以将数据分割到多个redis server,另外一个提高数据库容量的办法就是使用虚拟内存把那些不经常访问的数据交换到磁盘上。

要想使用虚拟内存,需要在配置文件中开启相关的配置项,如下:

vm-enabled yes                # 开启vm功能   (默认是没有使用虚拟内存的)
vm-swap-file /tmp/redis.swap        # 交换出来的value保存的文件路径
vm-max-memory 268435456          # redis使用的最大内存,超过该上限后,Redis开始交换value到磁盘文件
vm-page-size 32                # 每个页面的大小为32字节
vm-pages 134217728            # 最多使用多少个页面,即swap文件最多包含多少页面
vm-max-threads 4            # 用于执行value对象换入换出的工作线程数量。0 表示不使用工作线程

Redis的虚拟内存只把value交换到磁盘中,而key依然存储在内存中,目的是让开启虚拟内存的Redis和完全使用内存的Redis性能基本保持一致。如果由于太多key造成的内存不足的问题,Redis的虚拟内存并不能解决。

vm-max-threads 表示用于执行交换任务的工作线程的数量,建议不要将其设置为0。因为如果设置为0,交换过程就会在主线程进行,从而阻塞其他用户。但也不是设置越大越好,因为太多的工作线程导致操作系统使用更多时间来切换线程,从而降低了效率。推荐将vm-max-threads 设置为服务器的CPU核心数。

当key很小而value很大时,使用VM的效果会比较好.因为这样节约的内存比较大。

当key不小时,可以考虑使用一些非常方法将很大的key变成很大的value,比如可以考虑将key,value组合成一个新的value。

测试的时候发现用虚拟内存性能也不错。如果数据量很大,可以考虑分布式或者其他数据库

5. 分布式 / 主从复制

redis支持主从的模式。原则:Master会将数据同步到slave,而slave不会将数据同步到master。Slave启动时会连接master来同步数据。

主从复制(也叫主从同步)可以防止主机坏掉导致的网站不能正常运作的问题。Redis支持主从复制,而且配置也很简单。redis的主从复制可以让多个从服务器(slave server)拥有和主服务器(master server)相同的数据库副本。

这是一个典型的分布式读写分离模型。我们可以利用master来插入数据,slave提供检索服务。这样可以有效减少单个机器的并发访问数量

5.1 主从复制特点

  • 一个master可以拥有多个slave
  • 多个slave除了可以连接同一个master外,还可以连接其他的slave
  • 不会阻塞master,在slave同步数据时,master可以继续处理客户端的请求
  • 提高了系统的伸缩性,比如多个slave专门用于客户端的读操作
  • 可在master服务器上禁止数据持久化,而只在slave服务器上进行数据持久化操作

5.2 主从复制原理

主从复制设置很简单,设置好slave服务器后,slave自动和master建立连接,发送SYNC命令。无论是第一次同步建立的连接还是连接断开后重新建立的连接,master都会启动一个后台进程,将内存数据以快照方式写入文件中,同时master主进程开始收集新的写命令并且缓存起来。master后台进程完成内存快照操作后,把数据文件发给slave,slave将文件保存到磁盘上,然后将数据加载到内存中。接着master把缓存的命令发给slave,后续master收到的写命令都通过开始建立的连接发送给slave。当master与slave断开连接,slave自动重新建立连接。如果master同时收到多个slave发来的同步请求,其只启动一个进程写数据库镜像,然后发送给所有的slave。

5.3 主从复制过程

分为两个阶段,第一阶段如下:

  1. slave服务器主动连接到master服务器。
  2. slave服务器发送SYNC命令到master服务器请求同步数据。
  3. master服务器备份数据库到rdb文件。
  4. master服务器将该rdb文件传输给slave服务器。
  5. slave服务器清空数据库数据,把rdb文件数据导入数据库中。

第二阶段:master服务器把用户所有更改数据的操作(写操作),通过命令的形式转发给slave服务器,slave服务器只需执行master服务器发送过来的命令就可以实现后续的同步效果。

5.3 主从复制配置

相对MySQL的主从复制来说,Redis的主从复制配置很简单,只需在slave服务器的配置文件中,添加下面的配置项:

slaveof 192.168.1.115 6379 #指定master(主服务器)的ip和端口
masterauth 密码  #如果主服务器设置了安全密码,还要加上这行代码进行授权

配置完成后,重启redis从服务器,就已经通过主从复制实现了数据的同步。

6. 常见Redis模型

6.1 读写分离模型

通过增加Slave DB的数量,读的性能可以线性增长。为了避免Master DB的单点故障,集群一般都会采用两台Master DB做双机热备,所以整个集群的读和写的可用性都非常高。

读写分离架构的缺陷在于,不管是Master还是Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限於单个节点的存储能力,而且对于Write-intensive类型的应用,读写分离架构并不适合。

6.2 数据分片模型

为了解决读写分离模型的缺陷,可以将数据分片模型应用进来。

可以将每个节点看成都是独立的master,然后通过业务实现数据分片。

结合上面两种模型,可以将每个master设计成由一个master和多个slave组成的模型。

7. 持久化

Redis是基于内存的数据库,内存数据库有个严重的弊端:突然宕机或者断电时,内存中的数据就会丢失。为了解决这个问题,redi提供了两种持久化的方式:
snapshotting(内存快照),默认方式
append-only file(日志追加,缩写为aof)
Redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到硬盘来保证持久化。

7.1 内存快照

快照是默认的持久化方式。这种方式是将redis保存在内存中的数据以快照的方式写入二进制文件,默认的文件名
dump.rdb。可以通过修改配置文件,来设置自动快照。

vi /usr/local/redis/etc/redis.conf
save 900 1                # 每900秒,数据更改1次,就发起快照保存
save 300 10              # 每300秒,数据更改10次,则发起快照保存
save 60  10000         # 每60秒,数据更改10000,则发起快照保存

上面设置了多个内存快照保存方案,只要其中一个条件成立,Redis都会进行一次内存快照操作。

Redis每隔一段时间进行一次内存快照操作,客户端使用save或者bgsave命令,告诉Redis需要做一次内存快照操作。save命令在主线程中保存内存快照,Redis使用单线程处理所有请求,执行save命令可能阻塞其他客户端请求,从而导致不能快速响应请求,所以建议不要使用save命令。另外要注意,内存快照每次都把内存数据完整地写入硬盘,而不是只写入增量数据。所以如果数据量很大,写入操作比较频繁,就会严重影响性能。

由于快照方式是在一定间隔时间执行一次快照保存,所以如果redis出现问题,就会丢失最后一次快照后的所有修改。

7.2 日志追加 AOF

日志追加(aof)方式比快照方式有更好的持久化性,如果启用了aof,Redis会将每一个收到的写命令通过write函数追加到文件appendonly.aof中,当Redis重启时,它会执行该文件中的所有命令,这样就可以在内存中重建整个redis数据库的内容。

另外,操作系统内核的 I/O 接口可能存在缓存,所以日志追加方式不可能立即写入文件,这样就有可能丢失部分数据。幸运的是,Redis提供了解决方法,通过修改配置文件,告诉Redis应该在什么时候使用fsync函数强制操作系统把缓存中的写命令写入磁盘中的日志文件。有以下三种方法:

appendonly yes                      #启用aof持久化方式
# appendfsync always            #每次收到写命令就立即写入磁盘,性能最差,持久化最好
appendfsync everysec            #每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中
# appendfsync no                  #是否写入磁盘完全依赖操作系统,性能最好,持久化没保证

日志追加方式有效地降低了数据丢失的风险,同时也带来另一个问题,即持久化文件(appendonly.aof)不断膨胀。例如调用 incr nums 命令100次,文件就会保存100条该命令,其实99条都是多余的,因为要恢复数据只需要set nums 100。

为了压缩日志文件,Redis提供了bgrewriteaof命令。当Redis收到此命令时,就使用类似于内存快照的方式将内存中的数据以命令的方式保存到临时文件中,最后替换原来的日志文件。

内存快照和日志追加,各有优缺点,选择哪种持久化方式需要自己衡量。也可以把这两种持久化方式都关闭,实现自己的持久化方式,如使用Berkeley DB或者Tokyo Cabinet。

8. Redis的回收策略

MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?

redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

  • no-enviction(驱逐):禁止驱逐数据

9. 事务处理

redis对事务的支持目前还比较简单。它只能保证一个client(客户端)发起的事务中的命令可以连续的执行,中间不会插入其他的client的命令。当一个client在一个连接中发出 multi 命令时,这个连接会进入一个事务,后续的命令不会立即执行,而是先放到一个队列中。当执行 exec 命令时,redis才会顺序执行队列中的所有命令,之后退出事务;当执行 discard 命令时,redis会废弃事务的命令队列并退出事务。

一般情况下,Redis接收到一个客户端连接发来的命令后,会立刻执行并返回结果。但是,当客户端连接发出multi命令时,此连接便进入一个事务上下文,Redis把此连接发来的命令存入一个队列中。当此连接发出exec命令时,Redis便开始按顺序执行队列中的所有命令,并将事务中所有命令的结果打包一起返回给客户端,即提交事务后退出事务。如:

multi
set num 1
incr num
exec

从这个例子可以看出,set num 1 和 incr num 命令发出以后并没有立刻执行,而是存放到事务的命令队列中。当调用 exec 命令时,这两个命令才开始连续执行,最后返回这两个命令执行后的结果。
可调用 discard 命令取消事务,例如:

multi
set count 100
incr count
discard
get count

从这个例子可以看出,set count 100 和 incr count 命令都没有执行,discard 命令的作用是清空事务的命令队列并退出事务上下文。

注意:Redis只能保证事务中的每个命令能够连续执行,但是如果事务中有命令执行失败,Redis无法进行回滚操作。也就是说事务中的命令要么全部执行,要么全部取消,我们无法从事务执行过程中的失败处进行回滚。

10. 常见问题

10.1 使用Redis有哪些好处?

  1. 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

  2. 支持丰富数据类型,支持string,list,set,sorted set,hash

  3. 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

  4. 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

10.2 redis常见性能问题和解决方案

  • Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

    Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象

    Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。

  • 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

  • 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

  • 尽量避免在压力很大的主库上增加从库

  • 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…

    这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

10.3 Memcache与Redis的区别都有哪些?

  1. 存储方式

    Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。

    Redis有部份存在硬盘上,这样能保证数据的持久性。

  2. 数据支持类型

    Memcache对数据类型支持相对简单。

    Redis有复杂的数据类型。

  3. 使用底层模型不同

    它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。

    Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

  4. value大小

    redis最大可以达到1GB,而memcache只有1MB

11. Ref

Redis简介
https://blog.csdn.net/lamp_yang_3533/article/details/52560085
https://icefire.me/2018/06/15/Redis%E8%AF%A6%E8%A7%A3/

Redis vs memcache
https://www.cnblogs.com/aspirant/p/8883871.html
https://www.jianshu.com/p/e94fa7340923

Redis单线程为什么速度快
https://blog.csdn.net/xlgen157387/article/details/79470556

Redis 实现分布式锁
https://wudashan.cn/2017/10/23/Redis-Distributed-Lock-Implement/

Redis持久化
https://dbaplus.cn/news-158-2149-1.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章