一、 Redis

1. NoSQL(Not Only SQL)产品

缓存数据库(Key-Value):

  • Redis
  • Memcached
  • Tair

文档类:

  • MongoDB
  • ES

列存储:

  • HBASE

图形存储:

  • Neo4J

2. 缓存数据库对比

Memcached:
优点:高性能读写、单一数据类型、支持客户端式分布式集群、一致性hash、多核结构、多线程读写性能高。
缺点:无持久化、节点故障可能出现缓存穿透、分布式需要客户端实现、跨机房数据同步困难、架构扩容复杂度高。
场景:适合,多用户访问,每个用户少量的rw

Redis:
优点:高性能读写、多数据类型支持、数据持久化、高可用架构、支持自定义虚拟内存、支持分布式分片集群、单线程读写性能极高
缺点:多线程读写较Memcached慢
场景:适合,少用户访问,每个用户大量rw,直播类平台、网页游戏

部署差异:
Memcached:多核的缓存服务,更加适合于多用户并发访问次数较少的应用场景
Redis:单核的缓存服务,单节点情况下,更加适合于少量用户,多次访问的应用场景。

3. Redis安装部署

3.1 下载安装

下载

wget http://download.redis.io/releases/redis-3.2.12.tar.gz

解压

tar -xf redis-3.2.12.tar.gz
mv redis-3.2.12 /usr/local/
cd /usr/local && ln -s redis-3.2.12 redis

安装依赖包

yum -y install gcc automake autoconf libtool make

安装

cd  /usr/local/redis && make

修改环境变量

vim /etc/profile
...
export PATH=/usr/local/redis/src:$PATH

3.2 redis启动及客户端连接

后台启动

redis-server & 
redis-server /data/conf  #指定配置文件运行,配置文件中配置daemonize yes

客户端连接

redis-cli
redis-cli command #免交互执行命令
redis-cli -h ip地址 -p 端口 -a 密码

3.3 redis配置文件

vim /data/6379/redis.conf

daemonize yes
port 6379
logfile /data/6379/redis.log
dir /data/6379
dbfilename dump.rdb
bind 10.0.0.51 127.0.0.1
requirepass 123456
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec



参数解释:
daemonize yes                            后台运行
port 6379                                默认端口号
logfile /data/6379/redis.log             日志文件位置
dir /data/6379                           持久化文件存储位置
dbfilename dump.rdb                      RDB持久化数据文件名
bind 10.0.0.51 127.0.0.1                 Redis默认只允许通过127.0.0.1接口
                                        (即本机)的访问,通过配置允许通
                                         过10.0.0.51接口访问
requirepass 123456                       设置密码
++++++++++++++++++++++++                 数据持久化配置
+ save 900 1           +
+ save 300 10          +
+ save 60 10000        +
+ appendonly yes       +
+ appendfsync everysec +
++++++++++++++++++++++++

4. Redis客户端命令

免交互,redis-cli command
交互式,进入redis后输入

常用命令

shutdown                              #关闭redis
AUTH 密码                              #登陆后输入密码认证
SAVE                                  #前台持久化,阻塞redis正常写入,直到持久化完成
BGSAVE                                #后台持久化,开启子线程,异步持久化,不会阻塞redis正常写入


set a 1                               #设置变量a为1
get key                               #取得key的值
KEYS *                                #查看所有的键名,生产中慎用!!!!!
KEYS a*                               #查看所有a开头的键名
KEYS *a*                              #查看所有包含a的键名
TYPE KEY                              #返回key所存储值的类型
EXPIRE key seconds                    #设置key的过期时间,秒
PEXPIRE key milliseconds              #设置key的过期时间,毫秒
TTL key                               #查看key的剩余时间,秒
PTTL key                              #查看key的剩余时间,毫秒
PERSIST key                           #取消key生存时间设置,永不过期
DEL KEY                               #删除键
EXISTS key                            #检查key是否存在
RENAME key newkey                     #更名key为newkey


INFO [Memory|Replication|CPU|...]     #查看系统信息
Client list                           #查看当前连接的客户
Client kill ip:port                   #断开客户的连接
CONFIG get/set/rewrite                #配置查看、修改、写入文件
DBsize                                #查看系统里有多少个键
FLUSHALL                              #清空所有数据
select 0-15
FLUSHDB                               #选择库,并清空当前库
MONITOR                               #监控实时指令

5. Redis在线查看和修改配置

配置查看

CONFIG GET *              #模糊查询所有配置
CONFIG GET re*            #查询re开头的配置

配置修改

CONFIG SET XXX XXX

配置写入文件

CONFIG REWRITE

5.1 Redis内存设置

一般建议设置为内存的70%

CONFIG SET maxmemory 128M

6. Redis持久化(内存落盘)

6.1 持久化介绍

RDB 持久化:
可以在指定的时间间隔内生成数据集的时间点快照(point in time snapshot),新快照会覆盖旧快照。
优点:速度快,适用于做备份,主从复制也是基于RDB持久化实现的。
缺点:会有数据丢失。

AOF 持久化:
记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。AOF文件中的命令全部以Redis协议的格式来保存,新命令会被追加到文件的末尾。
优点:可以最大程度保证数据不丢
缺点:日志记录量级比较大,持久化时间较长

6.2 配置持久化

6.2.1 RDB持久化配置

vim /data/6379/redis.conf

...
save 900 1
save 300 10
save 60 10000

参数解释:
save XX XX,进行持久化的触发条件
save 900 1             900秒(15分钟)内有1个更改
save 300 10            300秒(5分钟)内有10个更改
save 60 10000          60秒内有10000个更改

6.2.2 AOF持久化配置

appendonly yes
appendfsync everysec/always

参数解释:
appendonly yes              开启AOF功能
appendfsync                 AOF间隔:everysec--每秒,always--每次操作

7. Redis多数据类型支持

7.1 支持5种数据类型

string:字符串
hash:字典
list:列表
set:集合
zset:有序集合

7.2 数据类型存储结构

key value
string name zhangsan
hash stu_1 id:100 name:zhangsan
list wechat [...,day3,day2,day1]
头插列表,新数据在最前,利用下标寻址(0,1,...,-1)
set seta (张三,王五,李四,...)
zset ssa (歌曲1,歌曲2,歌曲3,...)
也可利用下标寻址

7.3 各类数据类型应用场景

7.3.1 string字符串类型

会话缓存、计数器

#计数器例子
incr 变量                      #变量自增1
decr 变量                      #变量自减1
incrby 变量 数值                #变量增加特定数值
decrby 变量 数值                #变量减少特定数值

set key value                  #设置变量值
get key                        #取变量值

7.3.2 hash字典类型

最接近mysql表结构的一种类型,主要是可以做数据库缓存。

7.3.2.1 使用语法

存数据语法:hmset key field1 value1 field2 value2...

hmset stu  id 101 name zhangsan age 20 gender m
hmset stu1 id 102 name zhangsan1 age 21 gender f

取数据语法:

  • 取一个key的所有field:hgetall key
hgetall stu
hgetall stu1
  • 取一个key的某几个field:hmget key field1 field2 field3...
HMGET stu id name age gender
HMGET stu1 id name age gender

7.3.2.2 手工将mysql数据导入redis

mysql中用select concat语句拼接语法

select concat("hmset key_",id," field1 ",field1," field2 ",field2," field3 ",field3) from table_1 limit 10 into outfile '/tmp/hmset.txt';

例子:
select concat("hmset city_",id," id ",id," name ",name," countrycode ",countrycode," district ",district," population ",population) from city limit 10 into outfile '/tmp/hmset.txt'

将数据导入redis

cat /tmp/hmset.txt | redis-cli -a 123456 &> /dev/null

7.3.3 list列表类型

应用场景:微信朋友圈,即时消息展示。

7.3.3.1 使用语法

存数据语法:LPUSH key value1 [value2...],先存value1、再存value2

LPUSH schedule 'today is zhou1'
LPUSH schedule 'today is zhou2' 'today is zhou3'

取数据语法:LRANGE key start下标 end下标

LRANGE schedule 0 -1
1) "today is zhou3"
2) "today is zhou2"
3) "today is zhou1"

7.3.4 set集合类型

应用场景:社交类的平台,好友系统。共同好友,二度好友。

7.3.4.1 使用语法

存数据语法:sadd key member1 [member2...]

sadd a 1 2 3 4 5
sadd b 2 3 4 5 6

求并集:SUNION key1 key2

SUNION a b
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"

求交集:SINTER key1 key2

SINTER a b
1) "2"
2) "3"
3) "4"
4) "5"

求差集:SDIFF key1 key2

SDIFF a b
1) "1"

7.3.5 sortset有序集合类型

应用场景:排行榜应用

7.3.5.1 使用语法

存数据语法:zadd key score1 member1 [score2 member2...]

zadd keya 0 a 0 b 0 c 0 d 

增加member score语法:ZINCRBY key increment member

ZINCRBY keya 10 a
ZINCRBY keya 100 b

显示有序集合语法:ZREVRANGE key start stop,按照score从高到低排序

ZREVRANGE keya 0 -1
1) "b"
2) "a"
3) "d"
4) "c"

7.4 Redis消息格式

7.4.1 介绍

消息模式在资源有效利用方面提供了有效的协调。
有2种消息模式:消息队列,发布订阅

7.4.2 发布订阅

3个角色:

  • publisher 发布者
  • channel 频道
  • subscriber 订阅者

订阅频道:SUBSCRIBE channel1 [channel2 ...]

SUBSCRIBE fm928 fm929
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "fm928"
3) (integer) 1
1) "subscribe"
2) "fm929"
3) (integer) 2

模糊订阅:SUBSCRIBE wang*,订阅wang开头的频道
发布消息:PUBLISH channel message

PUBLISH fm928 hi
(integer) 1

8. Redis事务

Redis事务和Mysql事务的区别:

  • Redis事务:基于队列实现的,redis是乐观锁机制,仅实现原子性的保证,属于弱事务支持。
  • Mysql事务:基于事务日志、悲观锁机制、MVCC、Isolation等机制一起保证,强事务支持。

事务开启后,操作先写入队列,执行EXEC后事务结束。回滚直接在队列中丢弃操作。


redis事务工作原理

8.1 事务命令

  • 开启事务multi
  • 提交事务EXEC
  • 丢弃未提交的操作DISCARD

例子:

127.0.0.1:6379> MULTI 
OK
127.0.0.1:6379> SET a 1 
QUEUED
127.0.0.1:6379> SET b 2
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK

8.2 乐观锁实现

watch key
如果key的value在事务未提交时改变,事务将提交失败,返回nil

127.0.0.1:6379> WATCH a 
OK
127.0.0.1:6379> MULTI 
OK
127.0.0.1:6379> set a 1
QUEUED
127.0.0.1:6379> EXEC
(nil)

9. Redis主从复制

9.1 主从复制原理

  1. 副本库通过slaveof 10.0.0.51 6379命令,连接主库,并发送SYNC给主库
  2. 主库收到SYNC,会立即触发BGSAVE,后台保存RDB,发送给副本库
  3. 副本库接收后会应用RDB快照
  4. 主库会陆续将中间产生的新的操作,保存并发送给副本库
  5. 到此,我们主复制集就正常工作了
  6. 再此以后,主库只要发生新的操作,都会以命令传播的形式自动发送给副本库.
  7. 所有复制相关信息,从info信息中都可以查到.即使重启任何节点,他的主从关系依然都在.
  8. 如果发生主从关系断开时,从库数据没有任何损坏,在下次重连之后,从库发送PSYNC给主库
  9. 主库只会将从库缺失部分的数据同步给从库应用,达到快速恢复主从的目的

9.2 主从数据一致性保证

min-slaves-to-write 1 保证至少有1个从服务器存活
min-slaves-max-lag 3从服务器网络延迟的最大值

9.3 主库开启持久化

如果不开持久化,主库重启时,将造成所有主从数据丢失。

9.4 主从配置

9.4.1 准备配置文件

主库

cat >> /data/6380/redis.conf <

从库

cat >>   /data/6381/redis.conf <>   /data/6382/redis.conf <

启动数据库

#主
redis-server /data/6380/redis.conf
#从
redis-server /data/6381/redis.conf
redis-server /data/6382/redis.conf

9.4.2 开启主从

从库执行命令

redis-cli -p 6381 -a 123 SLAVEOF 127.0.0.1 6380
redis-cli -p 6382 -a 123 SLAVEOF 127.0.0.1 6380

9.4.3 查询主从状态

redis-cli -p 6380 -a 123 info replication
redis-cli -p 6381 -a 123 info replication
redis-cli -p 6382 -a 123 info replication

9.4.4 解除主从状态

redis-cli -p 6381 -a 123 SLAVEOF no one
redis-cli -p 6382 -a 123 SLAVEOF no one

10. redis-sentinel(哨兵)

10.1 sentinel作用

类似于Mysql中MHA+Atlas的集合体,可以作为自动完成宕机后的主从切换,还可以做读写分离。一般对于一个redis集群,部署3个sentinel进行监控。

哨兵之间通信:哨兵与 master 建立通信,利用 master 提供发布/订阅机制发布自己的信息,比如IP、端口……,当多个哨兵实例都在主库上做了发布和订阅操作后,它们之间就能知道彼此的 IP 地址和端口,从而相互发现建立连接
哨兵与slave通信:哨兵向 master 发送 INFO 命令, master 接收到命令后,便将 slave 列表告诉哨兵,哨兵根据 master 响应的 slave 名单信息与每一个 salve 建立连接,并且根据这个连接持续监控slave。

作用:

  1. 监控
  2. 自动选主,切换
  3. 主库宕机后,从库指向新主库
  4. 应用透明
  5. 自动处理故障节点

10.2 sentinel搭建

sentinel是一个单独的redis实例,监控一个1主2从的redis集群

mkdir /data/26380
cd /data/26380
vim sentinel.conf


port 26380
dir "/data/26380"
sentinel monitor mymaster 127.0.0.1 6380 1
sentinel down-after-milliseconds mymaster 5000
sentinel auth-pass mymaster 123 

参数解释:

  • sentinel monitor mymaster 127.0.0.1 6380 1mymaster是定义的redis集群的名字,127.0.0.1 6380是集群master的ip地址和端口号,1以表示仲裁master宕机需要的sentinel节点数,一般设置为sentinel节点的一半以上。
  • sentinel down-after-milliseconds mymaster 5000:判断master节点失效的时间,单位毫秒
  • sentinel auth-pass mymaster 123:主节点密码

10.3 开启sentinel并记录日志

redis-sentinel /data/26380/sentinel.conf  &>/tmp/sentinel.log &

10.4 宕机切换

如果主库宕机,会自动将1台Replica提升为Master,原主库恢复后,自动成为Replica挂在新的Master下。

11. Redis cluster

11.1 介绍

  1. 在多分片节点中,将16384个slot,均匀分布到多个分片节点中
  2. 存数据时,将key做crc16(key),然后和16384进行取模,得出槽位值(0-16383之间)
  3. 根据计算得出的槽位值,找到相对应的分片节点的主节点,存储到相应槽位上
  4. 如果客户端当时连接的节点不是将来要存储的分片节点,分片集群会将客户端连接切换至真正存储节点进行数据存储


11.2 集群搭建

11.2.1 安装集群插件(5.0版本以后不需要)

#安装ruby支持
yum install ruby rubygems -y
#转换为国内源
gem sources -a http://mirrors.aliyun.com/rubygems/ 
gem sources  --remove https://rubygems.org/
gem install redis -v 3.3.3

11.2.2 准备节点配置文件

mkdir /data/700{0..5}
cat > /data/7000/redis.conf <> /data/7001/redis.conf <> /data/7002/redis.conf <>  /data/7003/redis.conf <> /data/7004/redis.conf <> /data/7005/redis.conf <

参数解释

protected-mode no              #外部网络可以直接访问,如果配置yes需bind ip或者设置访问密码
cluster-enabled yes            #开启集群功能
cluster-config-file nodes.conf #设置集群配置文件名称,文件在dir下
cluster-node-timeout 5000      #集群心跳时间

启动redis

redis-server /data/7000/redis.conf 
redis-server /data/7001/redis.conf 
redis-server /data/7002/redis.conf 
redis-server /data/7003/redis.conf 
redis-server /data/7004/redis.conf 
redis-server /data/7005/redis.conf 

11.2.3 创建集群

5.0及以后版本,直接使用redis-cli命令

redis-cli --cluster create  127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1

#参数 --cluster-replicas 1 表示每个主节点带1个从节点
#节点的书写顺序为:主1、主2、主3、从1、从2、从3

老版本使用ruby插件

redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

#节点的书写顺序为:主1、主2、主3、从1、从2、从3

11.2.4 查询集群状态

#集群主节点状态
redis-cli -p 7000 cluster nodes | grep master
#集群从节点状态
redis-cli -p 7000 cluster nodes | grep slave

11.3 添加节点

11.3.1 准备配置文件

mkdir /data/7006
mkdir /data/7007
cat > /data/7006/redis.conf <  /data/7007/redis.conf <

11.3.2 添加主节点127.0.0.1:7006

添加主节点需要重新分片,将slot分配到新的主节点

5.0及以后版本,直接使用redis-cli命令

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000

老版本使用ruby插件

redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000

11.3.3 添加主节点后,转移slot(重新分片)

5.0及以后版本,直接使用redis-cli命令

redis-cli --cluster reshard 127.0.0.1:7000

#后续步骤
1.输入要转移多少slot
2.输入接收slot的节点id
3.输入转出slot的节点id,输入all表示从所有节点转出
4.输入yes确认

#免交互执行
redis-cli --cluster reshard : --cluster-from  --cluster-to  --cluster-slots  --cluster-yes

老版本使用ruby插件

redis-trib.rb reshard 127.0.0.1:7000

#后续步骤
1.输入要转移多少slot
2.输入接收slot的节点id
3.输入转出slot的节点id,输入all表示从所有节点转出
4.输入yes确认

11.3.4 添加一个从节点127.0.0.1:7007

添加从节点无需重新分片

5.0及以后版本,直接使用redis-cli命令

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 --cluster-slave

# 添加从节点时直接指定主节点
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 --cluster-slave --cluster-master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e

老版本使用ruby插件

redis-trib.rb add-node --slave --master-id 主节点ID 127.0.0.1:7007 127.0.0.1:7000

11.4 删除节点

11.4.1 删除主节点

转移slot(重新分片)

redis-cli --cluster reshard 127.0.0.1:7000

#后续步骤
将127.0.0.1:7006节点的slot分别转移到其他3个节点,循环执行:
1.输入要转移多少slot
2.输入接收slot的节点id
3.输入转出slot的节点id,输入done
4.输入yes确认

删除主节点127.0.0.1:7006
5.0以后版本

redis-cli --cluster del-node 127.0.0.1:7000 节点ID

5.0之前版本

redis-trib.rb del-node 127.0.0.1:7000 节点ID

11.4.2 删除从节点

不需要重新分片
删除从节点127.0.0.1:7007
5.0以后版本

redis-cli --cluster del-node 127.0.0.1:7000 节点ID

5.0之前版本

redis-trib.rb del-node 127.0.0.1:7000 节点ID

12. 多API支持

12.1 python支持

安装python及redis库

yum install -y python3
yum install -y python3-pip
pip3 install redis 
pip3 install redis-py-cluster

12.1.1 python对redis单实例操作

[root@db01 ~]$ redis-server /data/6379/redis.conf 
-------------------------------------------------

#! /bin/python3
# -*- coding:utf-8 -*-

import redis

r = redis.StrictRedis(host='127.0.0.1',port=6379,db=0,password='123456')
r.set('a','111')
r.get('a')

12.1.2 python对sentinel集群进行操作

[root@db01 ~]$ redis-server /data/6380/redis.conf
[root@db01 ~]$ redis-server /data/6381/redis.conf
[root@db01 ~]$ redis-server /data/6382/redis.conf 
[root@db01 ~]$ redis-sentinel /data/26380/sentinel.conf  &>/tmp/sentinel.log &
-------------------------------------------------

#! /bin/python3
# -*- coding:utf-8 -*-

from redis.sentinel import Sentinel  

##指定sentinel的地址和端口号
sentinel = Sentinel([('localhost', 26380)], socket_timeout=0.1)
##测试,获取以下主库和从库的信息
sentinel.discover_master('mymaster')
sentinel.discover_slaves('mymaster')

配置读写分离

#写节点
master = sentinel.master_for('mymaster', socket_timeout=0.1,password="123")  
#读节点
slave = sentinel.slave_for('mymaster', socket_timeout=0.1,password="123")  
###读写分离测试   key     
master.set('b', '123')  
slave.get('b')  

12.1.3 python连接redis cluster集群

#! /bin/python3
# -*- coding:utf-8 -*-

from rediscluster import RedisCluster

startup_nodes = [{"host":"127.0.0.1", "port": "7000"},{"host":"127.0.0.1", "port": "7001"},{"host":"127.0.0.1", "port": "7002"}]   
rc = RedisCluster(startup_nodes=startup_nodes, decode_responses=True) 
rc.set("foo", "bar")  
print(rc.get("foo"))  

13. 缓存穿透、雪崩、击穿

  • 穿透:访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时DB会挂掉。
    解决方法:

    1. 最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
    2. 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
  • 雪崩: 大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
    解决方法:

    1. 可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
    2. 设置过期标志更新缓存:
      • 缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存;
      • 缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。
  • 击穿: 一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。
    解决方法:
    在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。

你可能感兴趣的:(一、 Redis)