为什么使用 Key-value 存储系统

按照分布式领域的 CAP 理论来衡量,传统的关系数据库(例如:Mysql) 的 ACID 只满足了 一致性(Consistency)、可用性(Availability),因此在 分区容错性(Partition tolerance) 上就很难做得好。

另外传统的关系数据库处理海量数据在 性能(Performance)扩展(Scalability)可用性(Availability) 等方面也存在很大的局限性。

而 Key-Value 数据库更加注重对海量数据存取的性能、分布式、扩展性支持上,并不需要传统关系数据库的一些特征,例如:Schema、事务、完整 SQL 查询支持等等,因此在分布式环境下的性能相对于传统的关系数据库有较大的提升。

总的来说,Key-Value 数据库牺牲了 CAP 理论的 一致性(Consistency) 换取更好的 分区容错性(Partition tolerance)可用性(Availability)


CAP 理论被称作布鲁尔定理 Brewer's theorem 其中,Consistency、 Availability、Tolerance to network Partitions 这三部分在任何系统架构实现时只可能同时满足其中二点,没法三者兼顾。

  • 一致性 (Consistency): 等同于所有节点访问同一份最新的数据副本
  • 可用性 (Availability): 每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据)
  • 分区容错性 (Partition tolerance):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择

ACID 属性:传统的关系数据库为了保持数据库的一致性,在事务处理之前和之后,都遵循ACID 属性:

  • 原子性(Atomicity):即不可分割性,事务中的操作要么全不做,要么全做
  • 一致性(Consistency):一个事务在执行前后,数据库都必须处于正确的状态,满足完整性约束
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行
  • 持久性(Durability):事务处理完成后,对数据的修改就是永久的,即便系统故障也不会丢失

Redis简介

Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、 Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。

Redis 的数据类型

作为 Key-value 型数据库,Redis 也提供了键(Key)和键值(Value)的映射关系。但是,除 了常规的数值或字符串,Redis 的键值还可以是以下形式之一:

  • Lists (列表)
  • Sets (集合)
  • Sorted sets (有序集合)
  • Hashes (哈希表)

string数据类型

string 是最简单的类型,一个 key 对应一个value。
string 类型是二进制安全的。意思是 Redis 的 string 可以包含任何数据,比如 jpg 图片或者序 列化的对象。从内部实现来看其实 string 可以看作 byte 数组,最大上限是 1G 字节,下面是 string 类型的定义:

struct sdshdr { 
    long len; 
    long free;
    char buf[]; 
};

len 是 buf 数组的长度。
free 是数组中剩余可用字节数,由此可以理解为什么 string 类型是二进制安全的了,因为它 本质上就是个 byte 数组,当然可以包含任何数据了
buf 是个 char 数组用于存贮实际的字符串内容,其实 char 和 c#中的 byte 是等价的,都是一 个字节。

另外 string 类型可以被部分命令按 int 处理.比如 incr 等命令,如果只用 string 类型,Redis 就 可以被看作加上持久化特性的 memcached。当然 Redis 对 string 类型的操作比 memcached 还是多很多的,下面列表常见的几个方法(其它不常用的方法请查阅手册):

string数据类型常用的操作命令

set

设置 key 对应的值为 string 类型的 value。

setex

设置 key 对应的值为 string 类型的 value,并指定此键值对应的有效期。

get

获取 key 对应的 string 值,如果 key 不存在返回 nil。

incr

对 key 的值做加加操作,并返回新的值。注意 incr 一个不是 int 的 value 会返回错误,incr 一 个不存在的 key,则设置 key 为 1

incrby

同 incr 类似,指定增加的步长 ,key 不存在时候会设置 key,并认为原来的 value 是 0

decr

对 key 的值做的是减减操作,decr 一个不存在 key,则设置 key 为-1

decrby

同incrby,只是相减

strlen

取指定 key 的 value 值的长度。

setnx

设置 key 对应的值为 string 类型的 value。如果 key 已经存在,返回 0,nx 是 not exist 的意思。

getset

设置 key 的值,并返回 key 的旧值。

hset

差不多最常用的就是以上11个,如果你是开发的话,一般情况下都是有第三方包封闭的方法来实现,了解一下原理就好了。

lists 类型及操作

list 是一个链表结构,主要功能是 push、pop、获取一个范围的所有值等等,操作中 key 理 解为链表的名字。

Redis 的 list 类型其实就是一个每个子元素都是 string 类型的双向链表。我们可以通过 push,pop 操作从链表的头部或者尾部添加删除元素。这使得 list 既可以用作栈,也可以用作队列。

Sorted Set 数据类型

Redis sorted set 的使用场景与 set 类似,区别是 set 不是自动有序的,而 sorted set 可以通过用户额外提供一个优先级 (score) 的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择 sorted set 数据结构,比如 twitter 的 public timeline 可以以发表时间作为 score 来存储,这样获取时就是自动按时间排好序的。

Redis sorted set 的内部使用 HashMap 和 跳跃表 (SkipList) 来保证数据的存储和有序,HashMap 里放的是成员到 score 的映射,而跳跃表里存放的 是所有的成员,排序依据是 HashMap 里存的 score, 使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

Redis 管理常用命令

键值命令

keys

返回满足给定 pattern 的所有 key

127.0.0.1:6379> keys *
1) "mylist"
2) "bar"


在生产环境中谨慎执行此命令,因为生产环境的缓存比较多,执行的话可能会消耗较多的系统资源

exists

确认一个 key 是否存在

del

删除一个 key

expire

设置一个 key 的过期时间(单位:秒)

move

将当前数据库中的 key 转移到其它数据库中

rename

重命名 key

type

返回值的类型

服务器命令

ping

测试连接是否存活

127.0.0.1:6379> ping
PONG

返回 pong 表示连接正常,其它表示连接异常

select

选择数据库。Redis 数据库编号从 0~15,我们可以选择任意一个数据库来进行数据的存取

quit

退出连接。

info

获取服务器的信息和统计。

flushdb

删除当前选择数据库中的所有 key 。

flushall

删除所有数据库中的所有 key 。

config get

获取服务器配置信息。

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/var/db/redis"

dbsize

返回当前数据库中 key 的数目。

其它命令的用法可以查阅手册

Redis 的高级实用特性

安全性

客户端连接服务可以设置密码,来防止未授权的连接。


警告:因为 Redis 速度相当快,所以在一台比较好的服务器下,一个外部的用户可以在一秒钟进 行 150K 次的密码尝试,这意味着你需要指定非常非常强大的密码来防止暴力破解。
最好的做法是和应用一起放到内网及防火墙后面。

主从复制

Redis 主从复制配置和使用都非常简单。通过主从复制可以允许多个 slave server 拥有和
master server 相同的数据库副本。

Redis 主从复制的特点

  1. master 可以拥有多个 slave
  2. 多个 slave 可以连接同一个 master 外,也可以连接到其他 slave
  3. 主从复制不会阻塞 master, 在同步数据时,master 可以继续处理 client的请求
  4. 提高系统的伸缩性

当配置好 slave 后,slave 与 master 建立连接,然后发送 sync 命令。无论是第一次连接还是重新连接,master 都会启动一个后台进程,将数据库快照保存到文件中,同时 master 主进程会开始收集新的写命令并缓存。

后台进程完成写文件后,master 就发送文件给 slave,slave 将文件保存到硬盘上,再加载到内存中,接着 master 就会把缓存的命令转发给 slave,后续 master 将收到的写命令发送给 slave。如果 master 同时收到多个 slave 发来的同步连接命令, master 只会启动一个进程来写数据库镜像,然后发送给所有的 slave。

如何配置

配置 slave 服务器很简单,只需要在 slave 的配置文件中加入如下配置

slaveof 192.168.1.1 6379 #指定 master 的 ip 和端口

如何判断哪个是从哪个是从

执行 info 命令:

redis 127.0.0.1:6378> info .
.
.
role:slave
master_host:localhost master_port:6379 master_link_status:up master_last_io_seconds_ago:10 master_sync_in_progress:0 db0:keys=1,expires=0

里面有一个角色标识,来判断是主库还是从库,对于本例是一个从库,同时还有一个 master_link_status 用于标明主从是否异步,如果此值=up,说明同步正常;如果此值=down, 说明同步异步;

事务控制

这个不建议使用,请用关系数据库的事务代替,或者用程序实现分布式事务。

数据持久化

通常,Redis 将数据存储于内存中,或被配置为使用虚拟内存。也就是说 Redis 需要经常将内存中的数据同步到磁盘来保证持久化。Redis 支持两种持久化方式

  1. RDB持久化:使用 快照 (snapshotting) 的方式,将内存中的数据不断写入磁盘;
  2. AOF持久化:则会记录每一个服务器收到的写操作。在服务启动时,这些记录的操作会逐条执行从而重建出原来的数据。写操作命令记录的格式跟 Redis 协议一致,以追加的方式进行保存 (有点类似 Mysql 的 binlog )。

snapshotting 方式

快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中, 默认的文件名为 dump.rdb。

可以通过配置设置自动做快照持久化的方式。我们可以配置 Redis 在 n 秒内如果超过 m 个 key 被修改就自动做快照,下面是默认的快照保存配置

save 900 1 #900 秒内如果超过 1 个 key 被修改,则发起快照保存 
save 300 10 #300 秒内容如超过 10 个 key 被修改,则发起快照保存 
# 以此类推

由于 Redis 是用一个主线程来处理所有 client 的请求,这种方式会阻塞所有 client 请求。所以不推荐使用。另一点需要注意的是,每次快照持久化都是将内存数据完整 写入到磁盘一次,并不是增量的只同步变更数据。如果数据量大的话,而且写操作比较多, 必然会引起大量的磁盘 io 操作,可能会严重影响性能。

aof 方式

另外由于快照方式是在一定间隔时间做一次的,所以如果 Redis 意外 down 掉的话,就会丢 失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用 aof 持久化 方式。

aof 比快照方式有更好的持久化性,是由于在使用 aof 持久化方式时,Redis 会将每一个收到的写命令都通过 write 函数追加到文件中(默认是 appendonly.aof)。

Redis 重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。

当然由于 os 会在内核中缓 存 write 做的修改,所以可能不是立即写到磁盘上。这样 aof 方式的持久化也还是有可能会丢失部分修改。

不过我们可以通过配置文件告诉 Redis 我们想要通过 fsync 函数强制 os 写入 到磁盘的时机。有三种方式如下(默认是:每秒 fsync 一次)

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

发布及订阅消息

这个功能需要具体应用场景。

Pipeline 批量发送请求

网络并不是很好的时候才需要,从架构上避免。

虚拟内存的使用

还是加钱买内存吧!

打赏不准超过你工资的一半!!!