Redis

学习参考文献:http://www.redis.cn/topics/introduction.html

Redis 是什么?

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。


Redis 优势:

  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。

Redis与其他key-value存储有什么不同?

  • Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。

  • Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。


应用场景

缓存(数据查询、短连接、新闻内容、商品内容等等)。(最多使用

分布式集群架构中的session分离。

聊天室的在线好友列表。

任务队列。(秒杀、抢购、12306等等)

应用排行榜。

网站访问统计。

数据过期处理(可以精确到毫秒)

Redis 数据类型介绍

参考:http://www.redis.cn/topics/data-types-intro.html#strings

String:

字符串类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这便意味着该类型可以接受任何格式的数据,如JPEG图像数据或Json对象描述信息等。在Redis中字符串类型的Value最多可以容纳的数据长度是512M。

应用:

自增主键:

商品编号、订单号采用string的递增数字特性生成。

定义商品编号key:items:id
192.168.101.3:7003> INCR items:id
(integer) 2
192.168.101.3:7003> INCR items:id
(integer) 3

 


Hashes:

我们可以将Redis中的Hashes类型看成具有String Key和String Value的map容器。所以该类型非常适合于存储值对象的信息。如Username、Password和Age等。如果Hash中包含很少的字段,那么该类型的数据也将仅占用很少的磁盘空间。每一个Hash可以存储4294967295个键值对。

使用string的问题:

假设有User对象以JSON序列化的形式存储到Redis中,User对象有id,username、password、age、name等属性,存储的过程如下:

保存、更新:

User对象 à json(string) à redis

如果在业务上只是更新age属性,其他的属性并不做更新我应该怎么做呢? 如果仍然采用上边的方法在传输、处理时会造成资源浪费,下边讲的hash可以很好的解决这个问题。

hash叫散列类型,它提供了字段和字段值的映射。字段值只能是字符串类型,不支持散列类型、集合类型等其它类型。如下

HSET命令不区分插入和更新操作,当执行插入操作时HSET命令返回1,当执行更新操作时返回0。

应用:

存储商品信息:

商品字段

【商品id、商品名称、商品描述、商品库存、商品好评】

定义商品信息的key

商品1001的信息在 Redis中的key为:[items:1001]

存储商品信息:

192.168.101.3:7003> HMSET items:1001 id 3 name apple price 999.9
OK

获取商品信息

192.168.101.3:7003> HGET items:1001 id
"3"
192.168.101.3:7003> HGETALL items:1001
1) "id"
2) "3"
3) "name"
4) "apple"
5) "price"
6) "999.9"

List

列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段

列表类型内部是使用双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度为0(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的。

在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。

与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。

List中可以包含的最大元素数量是4294967295

从元素插入和删除的效率视角来看,如果我们是在链表的两头插入或删除元素,这将会是非常高效的操作,即使链表中已经存储了百万条记录,该操作也可以在常量时间内完成。然而需要说明的是,如果元素插入或删除操作是作用于链表中间,那将会是非常低效的。

链表结构的小技巧:

针对链表结构的Value,Redis在其官方文档中给出了一些实用技巧,如RPOPLPUSH命令,下面给出具体的解释。

Redis链表经常会被用于消息队列的服务,以完成多程序之间的消息交换。

假设一个应用程序正在执行LPUSH操作向链表中添加新的元素,我们通常将这样的程序称之为"生产者(Producer)",而另外一个应用程序正在执行RPOP操作从链表中取出元素,我们称这样的程序为"消费者(Consumer)"。

如果此时,消费者程序在取出消息元素后立刻崩溃,由于该消息已经被取出且没有被正常处理,那么我们就可以认为该消息已经丢失,由此可能会导致业务数据丢失,或业务状态的不一致等现象的发生。然而通过使用RPOPLPUSH命令,消费者程序在从主消息队列中取出消息之后再将其插入到备份队列中,直到消费者程序完成正常的处理逻辑后再将该消息从备份队列中删除。同时我们还可以提供一个守护进程,当发现备份队列中的消息过期时,可以重新将其再放回到主消息队列中,以便其它的消费者程序继续处理。

List的常用案例

正如你可以从上面的例子中猜到的,list可被用来实现聊天系统。还可以作为不同进程间传递消息的队列。关键是,你可以每次都以原先添加的顺序访问数据。这不需要任何SQL ORDER BY 操作,将会非常快,也会很容易扩展到百万级别元素的规模。

例如在评级系统中,比如社会化新闻网站 reddit.com,你可以把每个新提交的链接添加到一个list,用LRANGE可简单的对结果分页。

在博客引擎实现中,你可为每篇日志设置一个list,在该list中推入博客评论,等等。

应用:

思路:

在Redis中创建商品评论列表

用户发布商品评论,将评论信息转成json存储到list中。

用户在页面查询评论列表,从redis中取出json数据展示到页面

定义商品评论列表key:

商品编号为1001的商品评论key【items: comment:1001】

192.168.101.3:7001> LPUSH items:comment:1001 '{"id":1,"name":"商品不错,很好!!","date":1430295077289}'

 


Set:

集合中的数据是不重复且没有顺序。

集合类型和列表类型的对比:

集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,由于集合类型的Redis内部是使用值为空的散列表实现,所有这些操作的时间复杂度都为0(1)。

Redis还提供了多个集合之间的交集、并集、差集的运算。

Set可包含的最大元素数量是4294967295。

和List类型不同的是,Set集合中不允许出现重复的元素,这一点和C++标准库中的set容器是完全相同的。换句话说,如果多次添加相同元素,Set中将仅保留该元素的一份拷贝。

和List类型相比,Set类型在功能上还存在着一个非常重要的特性,即在服务器端完成多个Sets之间的聚合计算操作,如unions、intersections和differences。由于这些操作均在服务端完成,因此效率极高,而且也节省了大量的网络IO开销。

应用范围:

      1). 可以使用Redis的Set数据类型跟踪一些唯一性数据,比如访问某一博客的唯一IP地址信息。对于此场景,我们仅需在每次访问该博客时将访问者的IP存入Redis中,Set数据类型会自动保证IP地址的唯一性。
      2). 充分利用Set类型的服务端聚合操作方便、高效的特性,可以用于维护数据对象之间的关联关系。比如所有购买某一电子设备的客户ID被存储在一个指定的Set中,而购买另外一种电子产品的客户ID被存储在另外一个Set中,如果此时我们想获取有哪些客户同时购买了这两种商品时,Set的intersections命令就可以充分发挥它的方便和效率的优势了。

集合的差集运算 A-B

语法:SDIFF key [key ...]

127.0.0.1:6379> sadd setA 1 2 3
(integer) 3
127.0.0.1:6379> sadd setB 2 3 4
(integer) 3
127.0.0.1:6379> sdiff setA setB 
1) "1"
127.0.0.1:6379> sdiff setB setA 
1) "4"

集合的交集运算 A ∩ B

语法:SINTER key [key ...]

127.0.0.1:6379> sinter setA setB 
1) "2"
2) "3"

集合的并集运算 A ∪ B

语法:SUNION key [key ...]

127.0.0.1:6379> sunion setA setB
1) "1"
2) "2"
3) "3"
4) "4"

从集合中弹出一个元素

注意:由于集合是无序的,所有SPOP命令会从集合中随机选择一个元素弹出

语法:SPOP key

127.0.0.1:6379> spop setA 
"1“

Sorted-Sets:

在集合类型的基础上,有序集合类型为集合中的每个元素都关联一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在在集合中,还能够获得分数最高或最低的前N个元素、获取指定分数范围内的元素等与分数有关的操作。

在某些方面有序集合和列表类型有些相似。

1、二者都是有序的。

2、二者都可以获得某一范围的元素。

但是,二者有着很大区别:

1、列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会变慢。

2、有序集合类型使用散列表实现,所有即使读取位于中间部分的数据也很快。

3、列表中不能简单的调整某个元素的位置,但是有序集合可以(通过更改分数实现)

4、有序集合要比列表类型更耗内存。

Sorted-Sets和Sets类型极为相似,它们都是字符串的集合,都不允许重复的成员出现在一个Set中。

它们之间的主要差别是Sorted-Sets中的每一个成员都会有一个分数(score)与之关联,Redis正是通过分数来为集合中的成员进行从小到大的排序。

然而需要额外指出的是,尽管Sorted-Sets中的成员必须是唯一的,但是分数(score)却是可以重复的。在Sorted-Set中添加、删除或更新一个成员都是非常快速的操作,其时间复杂度为集合中成员数量的对数。由于Sorted-Sets中的成员在集合中的位置是有序的,因此,即便是访问位于集合中部的成员也仍然是非常高效的。

事实上,Redis所具有的这一特征在很多其它类型的数据库中是很难实现的,换句话说,在该点上要想达到和Redis同样的高效,在其它数据库中进行建模是非常困难的。

应用范围:

    1). 可以用于一个大型在线游戏的积分排行榜。每当玩家的分数发生变化时,可以执行ZADD命令更新玩家的分数,此后再通过ZRANGE命令获取积分TOPTEN的用户信息。当然我们也可以利用ZRANK命令通过username来获取玩家的排行信息。最后我们将组合使用ZRANGE和ZRANK命令快速的获取和某个玩家积分相近的其他用户的信息。
    2). Sorted-Sets类型还可用于构建索引数据。

应用:

商品销售排行榜

需求:根据商品销售量对商品进行排行显示

思路:定义商品销售排行榜(sorted set集合),Key为items:sellsort,分数为商品销售量。

写入商品销售量

商品编号1001的销量是9,商品编号1002的销量是10

192.168.101.3:7007> ZADD items:sellsort 9 1001 10 1002

商品编号1001的销量加1

192.168.101.3:7001> ZINCRBY items:sellsort 1 1001

商品销量前10名

192.168.101.3:7001> ZRANGE items:sellsort 0 9 withscores

key:

在之前主要讲述的是与Redis数据类型相关的命令,如String、List、Set、Hashes和Sorted-Set。这些命令都具有一个共同点,即所有的操作都是针对与Key关联的Value的

设置key的生存时间

Redis在实际使用过程中更多的用作缓存,然而缓存的数据一般都是需要设置生存时间的,即:到期后数据销毁。

EXPIRE key seconds			设置key的生存时间(单位:秒)key在多少秒后会自动删除
TTL key				        查看key生于的生存时间
PERSIST key				清除生存时间 
PEXPIRE key milliseconds	        生存时间设置单位为:毫秒

例子:

192.168.101.3:7002> set test 1		设置test的值为1
OK
192.168.101.3:7002> get test			获取test的值
"1"
192.168.101.3:7002> EXPIRE test 5	设置test的生存时间为5秒
(integer) 1
192.168.101.3:7002> TTL test			查看test的生于生成时间还有1秒删除
(integer) 1
192.168.101.3:7002> TTL test
(integer) -2
192.168.101.3:7002> get test			获取test的值,已经删除
(nil)

事务:

和众多其它数据库一样,Redis作为NoSQL数据库也同样提供了事务机制。在Redis中,MULTI,EXEC,DISCARD,WATCH这四个命令是我们实现事务的基石。

Redis中事务的实现特征:

      1). 在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的所有命令被原子的执行。
      2). 和关系型数据库中的事务相比,在Redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行
      3). 我们可以通过MULTI命令开启一个事务,有关系型数据库开发经验的人可以将其理解为"BEGIN TRANSACTION"语句。在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXECDISCARD命令来提交/回滚该事务内的所有操作。这两个Redis命令可被视为等同于关系型数据库中的COMMIT/ROLLBACK语句。
      4). 在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行。
      5). 当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,我们就要充分利用Redis工具包中提供的redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了。

为什么 Redis 不支持回滚(roll back)

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

以下是这种做法的优点:

  • Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
  • 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

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


主从复制:

一、Redis的Replication:

什么是主从复制

持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障,如下图:

说明:

  1. 主redis中的数据有两个副本(replication)即从redis1和从redis2,即使一台redis服务器宕机其它两台redis服务也可以继续提供服务。
  2. 主redis中的数据和从redis上的数据保持实时同步,当主redis写入数据时通过主从复制机制会复制到两个从redis服务上。
  3. 只有一个主redis,可以有多个从redis。
  4. 主从复制不会阻塞master,在同步数据时,master 可以继续处理client 请求
  5. 一个redis可以即是主又是从,如下图:

在 Redis 复制的基础上,使用和配置主从复制非常简单,能使得从 Redis 服务器(下文称 slave)能精确得复制主 Redis 服务器(下文称 master)的内容。每次当 slave 和 master 之间的连接断开时, slave 会自动重连到 master 上,并且无论这期间 master 发生了什么, slave 都将尝试让自身成为 master 的精确副本。

下面的列表清楚的解释了Redis Replication的特点和优势。

    1). 同一个Master可以同步多个Slaves。
    2). Slave同样可以接受其它Slaves的连接和同步请求,这样可以有效的分载Master的同步压力。因此我们可以将Redis的Replication架构视为图结构
    3). Master Server是以非阻塞的方式为Slaves提供服务。所以在Master-Slave同步期间,客户端仍然可以提交查询或修改请求。
    4). Slave Server同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据。
    5). 为了分载Master的读操作压力,Slave服务器可以为客户端提供只读操作的服务,写服务仍然必须由Master来完成。即便如此,系统的伸缩性还是得到了很大的提高
    6). Master可以将数据保存操作交给Slaves完成,从而避免了在Master中要有独立的进程来完成此操作

二、Replication的工作原理:

master 开启一个后台保存进程,以便于生产一个 RDB 文件。同时它开始缓冲所有从客户端接收到的新的写入命令。当后台保存完成时, master 将数据集文件传输给 slave, slave将之保存在磁盘上,然后加载文件到内存。再然后 master 会发送所有缓冲的命令发给 slave。这个过程以指令流的形式完成并且和 Redis 协议本身的格式相同。

    在Slave启动并连接到Master之后,它将主动发送一个SYNC命令。此后Master将启动后台存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕后,Master将传送整个数据库文件到Slave,以完成一次完全同步。而Slave服务器在接收到数据库文件数据之后将其存盘并加载到内存中。此后,Master继续将所有已经收集到的修改命令,和新的修改命令依次传送给Slaves,Slave将在本次执行这些数据修改命令,从而达到最终的数据同步。如果Master和Slave之间的链接出现断连现象,Slave可以自动重连Master,但是在连接成功之后,一次完全同步将被自动执行。

为什么关闭了持久化并配置了自动重启的 master 是危险的

  1. 我们设置节点 A 为 master 并关闭它的持久化设置,节点 B 和 C 从 节点 A 复制数据。

  2. 节点 A 崩溃,但是他有一些自动重启的系统可以重启进程。但是由于持久化被关闭了,节点重启后其数据集合为空。

  3. 节点 B 和 节点 C 会从节点 A 复制数据,但是节点 A 的数据集是空的,因此复制的结果是它们会销毁自身之前的数据副本。

在使用 Redis 复制功能时的设置中,强烈建议在 master 和在 slave 中启用持久化。

主从配置

主redis配置无需特殊配置。

从redis配置

修改从redis服务器上的redis.conf文件

# slaveof <masterip> <masterport>
slaveof 127.0.0.1 6379

上边的配置说明当前该【从redis服务器】所对应的【主redis服务器】的IP是192.168.101.3,端口是6379。


持久化:

一、Redis提供了哪些持久化机制:

    1). RDB持久化:
    该机制是指在指定的时间间隔内将内存中的数据集快照写入磁盘。(RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的数据进行快照并持久化到硬盘RDB是Redis默认采用的持久化方式)    
    2). AOF持久化:
    该机制将以日志的形式记录服务器所处理的每一个写操作,在Redis服务器启动之初会读取该文件来重新构建数据库,以保证启动后数据库中的数据是完整的。
    3). 无持久化:
    我们可以通过配置的方式禁用Redis服务器的持久化功能,这样我们就可以将Redis视为一个功能加强版的memcached了。
    4). 同时应用AOF和RDB。

二、RDB机制的优势和劣势:

   RDB存在哪些优势呢?
    1). 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
    2). 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
    3). 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
    4). 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。    
   RDB又存在哪些劣势呢?
    1). 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
    2). 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。根据数据量大小与结构和服务器性能不同,这个时间也不同。通常将记录一千万个字符串类型键、大小为1GB的快照文件载入到内存中需要花费20~30秒钟。

问题总结

通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受的范围。

如果数据很重要以至于无法承受任何损失,则可以考虑使用AOF方式进行持久化。

三、AOF机制的优势和劣势:

 AOF的优势有哪些呢?
    1). 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。
    2). 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题
    3). 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。
    4). AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。
AOF的劣势有哪些呢?
    1). 对于相同数量的数据集而言,AOF文件通常要于RDB文件。
    2). 根据同步策略的不同,AOF在运行效率上往往会于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

四、其它:

1. Snapshotting:
    缺省情况下,Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开6379.conf文件之后,我们搜索save,可以看到下面的配置信息:

save 900 1           #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。
save 300 10          #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。
save 60 10000        #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。

 2. Dump快照的机制:
    1). Redis先fork子进程。
    2). 子进程将快照数据写入到临时RDB文件中。
    3). 当子进程完成数据写入操作后,再用临时文件替换老的文件。

3. AOF文件:
    上面已经多次讲过,RDB的快照定时dump机制无法保证很好的数据持久性。如果我们的应用确实非常关注此点,我们可以考虑使用Redis中的AOF机制。对于Redis服务器而言,其缺省的机制是RDB,如果需要使用AOF,则需要修改配置文件中的以下条目:
    将appendonly no改为appendonly yes
    从现在起,Redis在每一次接收到数据修改的命令之后,都会将其追加到AOF文件中。在Redis下一次重新启动时,需要加载AOF文件中的信息来构建最新的数据到内存中。

 4. AOF的配置:
    在Redis的配置文件中存在三种同步方式,它们分别是:    

appendfsync always     #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec   #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no         #从不同步。高效但是数据不会被持久化。

 5. 如何修复坏损的AOF文件:
    1). 将现有已经坏损的AOF文件额外拷贝出来一份。
    2). 执行"redis-check-aof --fix <filename>"命令来修复坏损的AOF文件。
    3). 用修复后的AOF文件重新启动Redis服务器。

6. Redis的数据备份:
    在Redis中我们可以通过copy的方式在线备份正在运行的Redis数据文件。这是因为RDB文件一旦被生成之后就不会再被修改。Redis每次都是将最新的数据dump到一个临时文件中,之后在利用rename函数原子性的将临时文件改名为原有的数据文件名。因此我们可以说,在任意时刻copy数据文件都是安全的和一致的。鉴于此,我们就可以通过创建cron job的方式定时备份Redis的数据文件,并将备份文件copy到安全的磁盘介质中。


虚拟内存: 

一、简介:

    和大多NoSQL数据库一样,Redis同样遵循了Key/Value数据存储模型。在有些情况下,Redis会将Keys/Values保存在内存中以提高数据查询和数据修改的效率,然而这样的做法并非总是很好的选择。鉴于此,我们可以将之进一步优化,即尽量在内存中只保留Keys的数据,这样可以保证数据检索的效率,而Values数据在很少使用的时候则可以被换出到磁盘。在实际的应用中,大约只有10%的Keys属于相对比较常用的键,这样Redis就可以通过虚存将其余不常用的Keys和Values换出到磁盘上,而一旦这些被换出的Keys或Values需要被读取时,Redis则将其再次读回到主内存中。

二、应用场景:

    对于大多数数据库而言,最为理想的运行方式就是将所有的数据都加载到内存中,而之后的查询操作则可以完全基于内存数据完成。然而在现实中这样的场景却并不普遍,更多的情况则是只有部分数据可以被加载到内存中。 在Redis中,有一个非常重要的概念,即keys一般不会被交换,所以如果你的数据库中有大量的keys,其中每个key仅仅关联很小的value,那么这种场景就不是非常适合使用虚拟内存。如果恰恰相反,数据库中只是包含少量的keys,而每一个key所关联的value却非常大,那么这种场景对于使用虚存就再合适不过了。在实际的应用中,为了能让虚存更为充分的发挥作用以帮助我们提高系统的运行效率,我们可以将带有很多较小值的Keys合并为带有少量较大值的Keys。其中最主要的方法就是将原有的Key/Value模式改为基于Hash的模式,这样可以让很多原来的Keys成为Hash中的属性。

三、配置:

    1). 在配置文件中添加以下配置项,以使当前Redis服务器在启动时打开虚存功能

vm-enabled yes

    2). 在配置文件中设定Redis最大可用的虚存字节数。如果内存中的数据大于该值,则有部分对象被换出到磁盘中,其中被换出对象所占用内存将被释放,直到已用内存小于该值时才停止换出。

vm-max-memory (bytes)

    Redis的交换规则是尽量考虑"最老"的数据,即最长时间没有使用的数据将被换出。如果两个对象的age相同,那么Value较大的数据将先被换出。需要注意的是,Redis不会将Keys交换到磁盘因此如果仅仅keys的数据就已经填满了整个虚存,那么这种数据模型将不适合使用虚存机制,或者是将该值设置的更大,以容纳整个Keys的数据。在实际的应用,如果考虑使用Redis虚拟内存,我们应尽可能的分配更多的内存交给Redis使用,以避免频繁的换入换出。
    3). 在配置文件中设定页的数量及每一页所占用的字节数。为了将内存中的数据传送到磁盘上,我们需要使用交换文件。这些文件与数据持久性无关,Redis会在退出前会将它们全部删除。由于对交换文件的访问方式大多为随机访问,因此建议将交换文件存储在固态磁盘上这样可以大大提高系统的运行效率。

    vm-pages 134217728
    vm-page-size 32    

    在上面的配置中,Redis将交换文件划分为vm-pages个页,其中每个页所占用的字节为vm-page-size,那么Redis最终可用的交换文件大小为:vm-pages * vm-page-size。由于一个value可以存放在一个或多个页上,但是一个页不能持有多个value,鉴于此,我们在设置vm-page-size时需要充分考虑Redis的该特征。
    4). 在Redis的配置文件中有一个非常重要的配置参数,即:

    vm-max-threads 4

    该参数表示Redis在对交换文件执行IO操作时所应用的最大线程数量。通常而言,我们推荐该值等于主机的CPU cores。如果将该值设置为0,那么Redis在与交换文件进行IO交互时,将以同步的方式执行此操作。对于Redis而言,如果操作交换文件是以同步的方式进行,那么当某一客户端正在访问交换文件中的数据时,其它客户端如果再试图访问交换文件中的数据,该客户端的请求就将被挂起,直到之前的操作结束为止。特别是在相对较慢或较忙的磁盘上读取较大的数据值时,这种阻塞所带来的影响就更为突兀了。然而同步操作也并非一无是处,事实上,从全局执行效率视角来看,同步方式要好于异步方式,毕竟同步方式节省了线程切换、线程间同步,以及线程拉起等操作产生的额外开销。特别是当大部分频繁使用的数据都可以直接从主内存中读取时,同步方式的表现将更为优异如果你的现实应用恰恰相反,即有大量的换入换出操作,同时你的系统又有很多的cores,有鉴于此,你又不希望客户端在访问交换文件之前不得不阻塞一小段时间,如果确实是这样,我想异步方式可能更适合于你的系统。至于最终选用哪种配置方式,最好的答案将来自于不断的实验和调优。


服务器管理:

Redis在设计之初就被定义为长时间不间断运行的服务进程,因此大多数系统配置参数都可以在不重新启动进程的情况下立即生效。即便是将当前的持久化模式从AOF切换到RDB也无需重启。在Redis中,提供了一组和服务器管理相关的命令,其中就包含和参数设置有关的CONFIG SET/GET command。


内存优化:

一、特殊编码:

    自从Redis 2.2之后,很多数据类型都可以通过特殊编码的方式来进行存储空间的优化。其中,Hash、List和由Integer组成的Sets都可以通过该方式来优化存储结构,以便占用更少的空间,在有些情况下,可以省去9/10的空间。这些特殊编码对于Redis的使用而言是完全透明的,事实上,它只是CPU和内存之间的一个交易而言。如果内存使用率方面高一些,那么在操作数据时消耗的CPU自然要多一些,反之亦然。在Redis中提供了一组配置参数用于设置与特殊编码相关的各种阈值,如:

    #如果Hash中字段的数量小于参数值,Redis将对该Key的Hash Value采用特殊编码。
    hash-max-zipmap-entries 64
    #如果Hash中各个字段的最大长度不超过512字节,Redis也将对该Key的Hash Value采用特殊编码方式。
    hash-max-zipmap-value 512
    #下面两个参数的含义基本等同于上面两个和Hash相关的参数,只是作用的对象类型为List。
    list-max-ziplist-entries 512
    list-max-ziplist-value 64
    #如果set中整型元素的数量不超过512时,Redis将会采用该特殊编码。
    set-max-intset-entries 512

    倘若某个已经被编码的值再经过修改之后超过了配置信息中的最大限制,那么Redis会自动将其转换为正常编码格式,这一操作是非常快速的,但是如果反过来操作,将一个正常编码的较大值转换为特殊编码,Redis的建议是,在正式做之前最好先简单测试一下转换效率,因为这样的转换往往是非常低效的。

二、BIT和Byte级别的操作:

    从Redis 2.2开始,Redis提供了GETRANGE,SETRANGE,GETBIT,SETBIT四个用于字符串类型Key/Value的命令。通过这些命令,我们便可以像操作数组那样来访问String类型的值数据了。比如唯一标识用户身份的ID,可能仅仅是String值的其中一段子字符串。这样就可以通过GETRANGE,SETRANGE命令来方便的提取。再有就是可以使用BITMAP来表示用户的性别信息,如1表示male,0表示female。用这种方式来表示100,000,000个用户的性别信息时,也仅仅占用12MB的存储空间,与此同时,在通过SETBIT,GETBIT命令进行数据遍历也是非常高效的。

三、尽可能使用Hash:

    由于小的Hash类型数据占用的空间相对较少,因此我们在实际应用时应该尽可能的考虑使用Hash类型,比如用户的注册信息,这其中包括姓名、性别、email、年龄和口令等字段。我们当然可以将这些信息以Key的形式进行存储,而用户填写的信息则以String Value的形式存储。然而Redis则更为推荐以Hash的形式存储,以上信息则以Field/Value的形式表示。

现在我们就通过学习Redis的存储机制来进一步证明这一说法。在该篇博客的开始处已经提到了特殊编码机制,其中有两个和Hash类型相关的配置参数:hash-max-zipmap-entries和hash-max-zipmap-value。现在我们先假设存储在Hash Value中的字段数量小于hash-max-zipmap-entries,而每个元素的长度又同时小于hash-max-zipmap-value。这样每当有新的Hash类型的Key/Value存储时,Redis都会为Hash Value创建定长的空间,最大可预分配的字节数为: 

 total_bytes = hash-max-zipmap-entries * hash-max-zipmap-value

这样一来,Hash中所有字段的位置已经预留,并且可以像访问数组那样随机的访问Field/Value,他们之间的步长间隔为hash-max-zipmap-value。只有当Hash Value中的字段数量或某一新元素的长度分别超过以上两个参数值时,Redis才会考虑将他们以Hash Table的方式进行重新存储,否则将始终保持这种高效的存储和访问方式。不仅如此,由于每个Key都要存储一些关联的系统信息,如过期时间、LRU等,因此和String类型的Key/Value相比,Hash类型极大的减少了Key的数量(大部分的Key都以Hash字段的形式表示并存储了),从而进一步优化了存储空间的使用效率。

Redis集群

redis-cluster架构图

架构细节:

(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.

(3)客户端与redis节点直连,不需要中间proxy.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

(4)redis-cluster把所有的物理节点映射到[0-16383]slot,cluster 负责维护node<->slot<->value

(5)Redis 集群的数据分片:Redis 集群没有使用一致性hash, 而是引入了 哈希槽的概念.

Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。

举个例子,比如当前集群有3个节点,那么:

  • 节点 A 包含 0 到 5500号哈希槽.
  • 节点 B 包含5501 到 11000 号哈希槽.
  • 节点 C 包含11001 到 16384号哈希槽.

这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上. 如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可. 由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.

redis-cluster投票:容错

(1)集群中所有master参与投票,如果半数以上master节点与其中一个master节点通信超过(cluster-node-timeout),认为该master节点挂掉.

(2):什么时候整个集群不可用(cluster_state:fail)? 

  • 如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。
  • 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。

Redis 集群的主从复制模型

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品.

在我们例子中具有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用.

然而如果在集群创建的时候(或者过一段时间)我们为每个节点添加一个从节点A1,B1,C1,那么整个集群便有三个master节点和三个slave节点组成,这样在节点B失败后,集群便会选举B1为新的主节点继续服务,整个集群便不会因为槽找不到而不可用了

不过当B和B1 都失败后,集群是不可用的.

Redis 一致性保证

Redis 并不能保证数据的强一致性. 这意味这在实际中集群在特定的条件下可能会丢失写操作.

第一个原因是因为集群是用了异步复制. 写操作过程:

  • 客户端向主节点B写入一条命令.
  • 主节点B向客户端回复命令状态.
  • 主节点将写操作复制给他得从节点 B1, B2 和 B3.

主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。 注意:Redis 集群可能会在将来提供同步写的方法。 Redis 集群另外一种可能会丢失命令的情况是集群出现了网络分区, 并且一个客户端与至少包括一个主节点在内的少数实例被孤立。

举个例子 假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主节点, A1 、B1 、C1 为A,B,C的从节点, 还有一个客户端 Z1 假设集群中发生网络分区,那么集群可能会分为两方,大部分的一方包含节点 A 、C 、A1 、B1 和 C1 ,小部分的一方则包含节点 B 和客户端 Z1 .

Z1仍然能够向主节点B中写入, 如果网络分区发生时间较短,那么集群将会继续正常运作,如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中得数据便丢失了.

注意, 在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的, 这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项:

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值