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 | [...,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后事务结束。回滚直接在队列中丢弃操作。
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 主从复制原理
- 副本库通过slaveof 10.0.0.51 6379命令,连接主库,并发送SYNC给主库
- 主库收到SYNC,会立即触发BGSAVE,后台保存RDB,发送给副本库
- 副本库接收后会应用RDB快照
- 主库会陆续将中间产生的新的操作,保存并发送给副本库
- 到此,我们主复制集就正常工作了
- 再此以后,主库只要发生新的操作,都会以命令传播的形式自动发送给副本库.
- 所有复制相关信息,从info信息中都可以查到.即使重启任何节点,他的主从关系依然都在.
- 如果发生主从关系断开时,从库数据没有任何损坏,在下次重连之后,从库发送PSYNC给主库
- 主库只会将从库缺失部分的数据同步给从库应用,达到快速恢复主从的目的
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。
作用:
- 监控
- 自动选主,切换
- 主库宕机后,从库指向新主库
- 应用透明
- 自动处理故障节点
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 1
:mymaster
是定义的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 介绍
- 在多分片节点中,将16384个slot,均匀分布到多个分片节点中
- 存数据时,将key做crc16(key),然后和16384进行取模,得出槽位值(0-16383之间)
- 根据计算得出的槽位值,找到相对应的分片节点的主节点,存储到相应槽位上
-
如果客户端当时连接的节点不是将来要存储的分片节点,分片集群会将客户端连接切换至真正存储节点进行数据存储
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会挂掉。
解决方法:- 最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
- 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
-
雪崩: 大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方法:- 可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
- 设置过期标志更新缓存:
- 缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际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」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。