REDIS基础

NoSQL数据库概述

NoSQL的特性

  • 非关系型数据库。
  • 不依赖业务逻辑方式存储,以简单key-value模式储存。(数据库扩展能力强)
  • 不遵循SQL标准。
  • 不支持ACID(原子性,一致性,隔离性,持久性)

NoSQL与使用场景

  • NoSQL的适用场景
    • 高并发。
    • 大量数据读写。
    • 对数据扩展性要求高。
  • NoSQL的不适用场景
    • 需要事物支持
    • 需要基于SQL的结构化查询储存,需要处理复杂关系(条件查询)[MongoDB支持]。

行式数据库与列式数据库

即以行为方向读取数据和以列为方向读取数据。对于不同的操作效率不同。

REDIS

介绍

REDIS是一个开源的Key-Value储存系统。支持的储存类型包括

  • String (字符串)
  • List (链表)[有序可重复]
  • Set (集合)[无序不可重复]
  • Zset (有序集合)[有序不可重复]
  • Hash (哈希类型)

数据类型支持[push / pop]、[add / remove]及取交集并集和差集等操作{满足原子性}。

REDIS的数据缓存于内存中,并且会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并在此基础上实现了Master-Slaver(主从)同步。

  • 配合关系型数据库做高速缓存
    • 高频次,热门访问的数据,降低数据库IO
    • 分布式架构
  • 使用其持久化能力,利用多样的数据结构储存特定的数据

http://redis.io

http://www.redis.net.cn

补充:

  • Linux后台启动 通过修改redis.conf文件中的daemonize no->yes
  • ps -ef | grep redis 查看服务是否开启
  • 使用redis-cli客户端访问

REDIS默认16个数据库,下标从0-15.

REDIS使用单线程+多路IO复用技术。[多路复用指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入过个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以同时在同一个线程里执行,也可以启动线程执行(比如使用线程池)]

五大数据类型

Key

keys *                 //查询当前数据库所有键值对
exists <key>           //查询当前数据库是否有一个键
type <key>             //查询当前键的类型
del <key>              //删除某个键
expire <key><seconds>  //为键值设置过期时间,单位<秒>
ttl <key>              //查询当前键还有多久过期,-1表示永不过期,-2表示已过期

dbsize                 //查询当前数据库的key数量
flushdb                //清空当前库
flushall               //删除全部库

String

  1. String是Redis最基本的类型,可以理解成一个key对应一个value。
  2. String类型是二进制安全的,意味着String可以包含任何数据,如jpg图片或序列化对象。
  3. String类型是基本的数据类型,一个Redis中字符串value最多可以有512M大小。
get <key>                                  //查询对应键值
set <key><value>                           //添加键值对
append <key><value>                        //在当前键对应的值后端添加<value>
strlen <key>                               //获得键对应值的长度
setln <key><value>                         //只有在键不存在时设置键的值(存在就不设置)
incr <key>                                 //将键中储存数字值增1(只能对数字操作,为空则置1)[原子]
decr <key>                                 //将键中储存数字值减1(只能对数字操作,为空则置-1)[原子]
incrby/decrby <key><步长>                  //将键中储存数字增减步长的大小
mset <key1><value1><key2><value2>...       //同时设置多组键值对
mget <key1><value1><key2><value2>...       //同时获取多组键值对
msetnx <key1><value1><key2><value2>.       //同时设置多组键值对,前提是所有给定键都不存在
getrange <key><起始位置><结束位置>         //获得对应键的值在某范围的部分
setrange <key><起始位置><结束位置><value>  //用<value>覆盖起始位置到结束位置的值
setex <key><过期时间><value>               //设置键值的同时设置过期时间,单位为<秒>
getset <key><value>                        //设置新值的同时获取旧值(等同于先get再set)

List

  1. 单键多值
  2. 值可重复
  3. Redis列表是简单的字符串列表, 按照插入顺序排序。你可以添加一个元素到列表的头部或尾部。
  4. 底层是一个双向链表,对两端的操作性能很高, 通过索引下标的操作中间的节点性能会较差。
lpush/rpush <key><value1><value2>...//从左边或右边插入一个或多个值
lpop/rpop <key>                     //从左边或右边弹出一个值(无值则清除键)
rpoplpush <key1><key2>              //从<key1>列表右边弹出,插入到<key2>左边
lrange <key><起始位置><结束位置>    //按照索引下标获得多个元素(表尾-1)
lindex <key><index>                 //按照索引下表获得单个元素
llen <key>                          //获得列表长度
linsert <key>before/after<value><nvalue>  //在<value>之前/之后插入<nvalue>
lrem <key><n><value>                //从左边删除n个<value>(n正数表示从左往右删除,负数表示从右往左删除)

Set

  1. 自动排重
  2. 提供了判断某个成员在集合内的接口
  3. Set是String类型的无序集合。底层是value为null的哈希表,增删查的复杂度为O(1)
sadd <key><value1><value2>...//将一或多个member元素加入到集合key中,忽略已存在
smembers <key>               //取出该集合所有值
sismember <key><value>       //判断集合<key>是否为含有该<value>值,返回1或0
scard <key>                  //返回该集合的元素个数
srem <key><value1><value2>...//删除集合中的某个元素
spop <key>                   //随机从该集合中弹出一个值
srandmember <key><n>         //从集合中随机返回n个值,不会删除
sinter <key1><key2>          //返回两个集合的交集元素
sunion <key1><key2>          //返回两个集合的并集元素
sdiff <key1><key2>           //返回两个集合的差集元素

Hash

  1. hash是一个键值对集合
  2. 特别适用于储存对象
  3. hash是一个String类型的field和value的映射表
  4. 类似Java中的Map<String,String>
hset <key><field><value>       //给<key>集合中的<field>键赋值<value>
hget <key><field>              //从<key>集合<field>中取出值
hmset <key><field1><value1><field2><value2>...//批量设置hash的值
hexists <key><field>           //查询哈希表<key>中,<field>键是否存在
hkeys <key>                    //列出hash集合所有的<field>
hvals <key>                    //列出hash集合所有的<value>
hincrby <key><field><increment>//为哈希表<key>中<field>键的值增加增量<increment>
hsetnx <key><field><value>     //将哈希表<key>中<field>键的值设为<value>,前提<field>不存在

Zset

  1. 有序集合每个成员都关联了一个评分Score,评分用于按照顺序排序集合中的成员。
  2. 可以通过评分或次序快速获取一个范围内的元素(而Set是无序的)
zadd <key><score1><value1><score2><value2>.//将一个或多个元素加入集合(带评分)
zrange <key><起始位置><结束位置>           //返回集合中下标在范围内的元素,可带评分
zrangebyscore <key><min><max>              //返回集合中评分在范围内的元素,从小到大可带评分
zrevrangebyscore <key><max><min>           //同上,从大到小
zincrby <key><increment><value>            //为元素的score加上增量
zrem <key><value>                          //删除集合下指定元素
zcount <key><min><max>                     //统计集合中评分范围内的元素个数
zrank <key><value>                         //返回该值在集合中的排名,从0开始

相关配置和持久化

配置文件

  • IP地址绑定
    1. 注释bind 127.0.0.1 (只允许本机访问
    2. 关闭保护模式protected-mode yes->no
  • tcp-backlog
    • 理解成一个请求到达后至接受进程处理前的队列
    • 队列总和=未完成三次握手队列+已完成三次握手队列
    • 高并发环境tcp-backlog设置值跟超时时限内的Redis吞吐量决定
  • timeout
    • 一个空闲的客户端维持多少秒会关闭,0为永不关闭
  • TCP keepalive
    • 对访问客户端的心跳检查,每隔N秒检测一次,推荐设置60秒
  • daemonize
    • 是否为后台进程
  • pidfile
    • 存放pid文件的位置,每个实例会产生一个不同的pid文件
  • log level
    • 四个级别根据使用阶段来选择,生产环境选择notice或者warning
  • logfile
    • 日志文件名称
  • syslog
    • 是否将Redis日志输送到linux系统日志服务中
  • syslog-ident
    • 日志的标志
  • syslog-facility
    • 输出日志的设备
  • database
    • 设定库的数量
  • requirepass
    • 设置永久密码
  • maxclient
    • 最大客户端连接数
  • maxmemory
    • Redis可使用的内存量,一旦达到上限会试图移除内部数据,移除规则通过maxmemory-policy来指定。若无法根据规则移除内存中的数据,或者设置了“不允许移除”,那么Redis则会针对那些需要申请内存的指令返回错误信息,比如Set,Lpush等。
  • Maxmemory-policy
    1. volatile-lru:使用LRU算法移除key,只对设置了过期时间的键
    2. allkeys-lru:使用LRU算法移除key
    3. volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
    4. allkeys-random:移除随机的key
    5. volatile-ttl(即将过期):移除那些ttl值最小的key,即那些最近要过期的key
    6. noeviction:不进行移除。针对写操作,只返回错误信息。
  • Maxmemory-samples
    • 设置样本数量,LRU和最小ttl算法都并非是精确的算法,而是估算值。
    • 一般设置3到7的数字,数值越小样本越不精确,但是性能消耗也越小。

持久化(RDB)

在指定时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshot,它恢复时是将快照文件直接读到内存中。

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

备注:Linux使用了写时复制技术,即父进程和子进程共用一段物理内存。只有进程空间的各段内容要发生变化时,才会将父进程的内容复制一份给子进程。

stop-writes-on-bgsave-error //当Redis无法写入磁盘,关闭写操作
rdbcompression //进行rdb保存时,将文件压缩
rdbchecksum //在储存快照后,让Redis使用CRC64算法来进行数据校验,会增大性能损耗。

RDB中保存文件名默认为dump.rdb,在redis.conf中配置。保存文件路径默认在redis启动时命令行所在目录下。

命令save:只管保存,其他不管,全部阻塞

保存策略save
save <seconds> <changes>
Will save the DB if both the given number of seconds and the given numebr of write operations against the DB occurred.
在<seconds>秒后完成<changes>次对数据库的修改就会进行持久化

满足保存策略或正常关闭的情况下才触发持久化。非正常关闭时会丢失最后的数据。

  • RDB优点
    1. 更加节省磁盘空间
    2. 恢复速度更快
  • RDB缺点
    1. 在数据庞大时比较消耗性能
    2. 备份周期间隔中意外非正常关闭有可能丢失数据

持久化(AOF)

与RDB模式不同,AOF模式保存的是指令,会以日志的形式记录每个写操作。只允许追加而不允许改写文件,redis启动时会根据日志文件重新构建数据。

appendonly no->yes AOF默认不开启,需要手动配置
appendfilename "appendonly.aof"  redis.conf中默认的AOF文件名

AOF和RDB同时开启时,系统默认读取AOF的数据

AOF的备份机制和性能虽然和RDB不同,但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到redis工作目录下,启动系统加载。

redis-check-aof   --fix appendonl.aof 命令进行AOF文件损坏时的恢复(没啥用)
AOF同步频率设置
appendfsync always #始终同步,每次redis的写入都会立刻记入日志
appendfsync everysec #每秒同步,每秒记入日志一次,宕机时本秒的数据可能丢失
appendfsync no #不主动进行同步,把同步时机交给操作系统

Rewrite:由于AOF采用文件追加的方式,文件会随着指令的加入增大。文件重写的意义在于当AOF文件的大小超过所设定的阈值时,redis启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。

如何实现Rewrite:AOF文件持续增长而过大时,会fork一条新进程来重写文件(先写临时文件再Rename),遍历新进程的内存中数据,每项数据记录为一条Set语句。重写AOF的操作并没有读取旧的AOF文件,而是将内存中整个数据库内容用命令的方式重写了一个新的AOF文件。

auto-aof-rewrite-percentage *
auto-aof-rewrite-min-size *mb

系统载入时或上次重写完毕时,redis会记录此时AOF的大小,设为base_size,如果

redis的AOF当前大小>=base_size + base_size*100% (默认)

且当前大小>=64mb (默认) 的情况下,redis会对AOF进行重写。

  • AOF优点
    1. 丢失数据概率更低
    2. 可读的日志文本,通过操作AOF文件,可以处理误操作
  • AOF缺点
    1. 比RDB占用更多磁盘空间
    2. 恢复备份速度慢
    3. 如果每次读写都同步会增大性能压力
    4. 存在个别BUG

推荐RDB和AOF都使用,对数据不敏感可单独选择使用RDB,不建议单独使用AOF,如果只是做纯内存缓存,可以都不用。

主从复制

主从复制,即主机数据更新后根据配置和策略,自动同步到备机的M/S机制,Master以写为主,Slave以读为主,进而实现读写分离

M/S结构示意

主从复制示例

  • 配从(服务器)不配主
    1. 拷贝多个redis.conf文件include
    2. 开启daemonize yes
    3. Pid文件名pidfile(可以不用配置)
    4. 指定端口port(多个应用程序的端口号不同)
    5. Log文件名
    6. Dump.rdb文件名dbfilename
    7. Appendonly关闭或修改文件名
include */redis.conf
pidfile */redis_<number>.conf
port <number>
dbfilename <dbfilename>.rdb
复制多份后使用 :%s/<old_port>/<new_port>
进行替换(每份conf的文件名称、port、pidfile、dbfilename都不同)
示例:
分别启动不同的配置文件(可以是不同服务器)
redis-server redis_6379.conf

redis-server redis_6380.conf
redis-server redis_6381.conf

分别开启客户端(同服务器不同会话或不同服务器)
redis-cli -h 127.0.0.1 -p 6379
redis-cli -h 127.0.0.1 -p 6380
redis-cli -h 127.0.0.1 -p 6381

info replication //打印主从复制的相关信息
slaveof <ip> <port>  //成为某个实例的从服务器
slaveof no one   //不做任何服务器的从服务器

主服务器下线后不会改变其主从地位,下线后只改变其连接状态(master_link_status)

在redis.conf中可以配置永久固定的主从关系:

slaveof <masterip> <masterport>

复制原理

  • 每次从机联通后,都会给主机发送sync指令
  • 主机立刻进行存盘操作,发送rdb文件给从机
  • 从机收到rdb文件后,进行全盘加载
  • 之后每次主机的写操作,都会立刻发送给从机,从机执行相应命令

主从传递

上一个slave可以是下一个slave的master ,slave同样可以接收其他slaves的连接和同步请求,那么slave作为链条中的下一个master,可以有效减轻master的写压力,去中心化降低风险。

中途更变转向:会清除之前的数据,重新建立并拷贝最新数据。

风险:一旦某一环中服务器宕机,链条后的slave都没法备份。

在一个主服务器宕机后, 后面的从服务器使用 slave no one 命令可以取消自己的从服务器身份,转变为主服务器。

哨兵模式

是为了解决主服务器宕机问题建立的模式。可以在主服务器故障后根据投票自动改变从库身份为主库。

哨兵模式示意图
在自定义的/redis目录下新建sentinel.conf文件
在配置文件中填写内容:
sentinel moniter <监控对象服务器名称> <ip> <port> <number>
其中,<number>表示哨兵同意迁移的数量

启动哨兵(哨兵类似于一个服务器)
redis-sentinel /*/sentinel.conf

主服务器被检测到宕机后,会将新的从服务器切换为主服务器,原先的主服务器上线后会变为从服务器。选择条件依次为:

  1. 选择优先级靠前的从服务<redis.conf中的slave-priority 100>
  2. 选择偏移量最大的从服务<指获得原主数据最多的>
  3. 选择runid最小的从服务<每个redis实例启动后都会随机生成一个40位的runid>

集群(cluster)

Redis集群实现了对Redis的水平扩容(主从复制是垂直,但不是扩容,仅为了降低读写压力),即启动N个redis节点,将整个数据库分布储存在这N个节点中,每个节点储存总数据的1/N。

Redis集群通过分区(partition)来提供一定程度的可用性,即使集群中有一部分节点失效或无法进行通讯,集群也可以继续处理命令请求。

  • 安装ruby环境
    1. yum install ruby
    2. yum install rubygems
  • 拷贝redis-x.x.x.gem到/opt目录下
  • 在/opt目录下执行gem install –local redis-x.x.x.gem
  • 多个节点的redis.conf配置文件
    1. 主从模式的四部分
    2. cluster-enabled yes 打开集群模式
    3. cluster-config-file nodes-6379.conf 设定节点配置文件名
    4. cluster-node-timeout <number> 设定节点失联时间,超时集群会进行主从切换
  • 将多个节点合成一个集群
    1. 先确保所有实例启动后,.conf文件生成正常
    2. 进入/redis/opt/redis-x.x.x/src
    3. ./redis-trib.rb create –replicas 1 <real ip>:<port> <real ip>:<port>……

redis-cluster分配节点

一个集群至少要有三个主节点,选择 –replicas 1表示我们想要为集群的每个主节点创建一个从节点。

分配原则尽量保证每个主数据库运行在不同的ip地址,每个从库和主库不在一个ip地址上。

Slots

一个Redis集群包含16384个插槽(hash slot),数据库中的每个键都属于这16384个插槽中的一个,集群使用公式CRC16(key)%16384来计算key属于哪个槽,其中CRC16(key) 语句用于计算key的CRC16校验和。

集群的每个节点负责处理一部分插槽。例如,三个主节点将16384个插槽分为0-5500,5501-11000,11001-16383三组。

在redis-cli中每次录入、查询键值对,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口。

redis-cli客户端提供了 -c 参数实现自动重定向,如redis-cli -c -p 6379登入后,再录入、查询键值对可以自动重定向。

不在一个slot下的键值,是不能用mget,mset等多键操作。

可以用{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。如下

set k1{A} v1
set k2{A} v2
set k3{A} v3
将会存放到同一个服务器上
CLUSTER KEYSLOT <key>  //计算键key应该被放到那个槽中
CLUSTER COUNTKEYSINSLOT <slot>  //返回槽slot目前包含的键值对数量
CLUSTER GETKEYSINSLOT <slot> <count>  //返回count个slot槽中的键

某一段槽中的主从节点全部宕机,redis集群服务就无法继续。

redis.conf中参数cluster-require-full-coverage yes表示所有slot都正常的时候才能对外提供服务。