redis深入讲解
1. redis是什么
redis是一个开源的,使用C语言编写的,可基于内存也可以持久化的Key-Value数据库。https://redis.io/
2. redis安装与使用
进入官方下载地址:https://redis.io/download
#下载并且安装
$ wget http://download.redis.io/releases/redis-5.0.5.tar.gz
$ tar xzf redis-5.0.5.tar.gz
$ cd redis-5.0.5
$ make
#运行服务
$ src/redis-server
#运行客户端
$ src/redis-cli
redis> set str "hello world"
OK
redis> get str
"hello world"
3. 为什么redis快
-
完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
-
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;官网解释为,单线程已经够快了。没必要做多线程。
-
使用多路I/O复用模型,非阻塞IO;
1.阻塞I/O模型
老李去火车站买票,排队三天买到一张退票。
耗费:在车站吃喝拉撒睡 3天,其他事一件没干。
2.非阻塞I/O模型
老李去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。耗费:往返车站6次,路上6小时,其他时间做了好多事。
3.I/O复用模型
1.select/poll
老李去火车站买票,委托黄牛,然后每隔6小时电话黄牛询问,黄牛三天内买到票,然后老李去火车站交钱领票。
耗费:打电话
2.epoll
老李去火车站买票,委托黄牛,黄牛买到后即通知老李去领,然后老李去火车站交钱领票。
耗费:无需打电话
4. redis数据结构
redis是一种高级的key-value存储系统,其中支持5种不同的数据类型
- 字符串(string)
- 列表(list)
- 字符串集合(set)
- 有序字符串结合(sorted set)
- 哈希(hash)
关于key,有几个点
- key不要太长,尽量不要超过1024字节。
- key不要太短,可读性差。
- 最好定义好规范如conf:esb:adapter
5. redis数据结构 string
字符串可以存储以下3种类型的值。
- 字符串(byte string)
- 整型
- 浮点型
常用命令
命令 | 行为 |
---|---|
SET | 设置指定 key 的值 |
GET | 获取指定 key 的值。 |
INCR | 将 key 中储存的数字值增一。 |
DECR | 将 key 中储存的数字值减一。 |
INCRBY | 将 key 所储存的值加上给定的增量值(increment) 。 |
DNCRBY | key 所储存的值减去给定的减量值(decrement) 。 |
APPEND | 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。 |
INCRBYFLOAT | 将 key 所储存的值加上给定的浮点增量值(increment) 。 |
操作例子
set str "hello world"
get str
我们可以通过字符串的数值操作
127.0.0.1:6379> set esb 2
OK
127.0.0.1:6379> get esb
"2"
127.0.0.1:6379> incr esb
(integer) 3
127.0.0.1:6379> get esb
"3"
由于INCR等指令具有原子性
,所以我们在做ESB高并发请求的唯一ID的时候就有使用到该特性。来获取唯一自增的数值作为操作编码的一部分。
6. redis数据结构 list
redis允许用户从序列的两端推入或者弹出元素。
常用命令
命令 | 行为 |
---|---|
RPUSH | 在列表尾部中添加一个或多个值 |
LPUSH | 将一个或多个值插入到列表头部 |
RPOP | 移除列表的最后一个元素,返回值为移除的元素。 |
LPOP | 移出并获取列表的第一个元素 |
LINDEX | 通过索引获取列表中的元素 |
RRANGE | 获取列表指定范围内的元素 |
LTRIM | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
127.0.0.1:6379> lpush mylist "1"
(integer) 1
127.0.0.1:6379> lpush mylist "2"
(integer) 2
127.0.0.1:6379> lpush mylist "0"
(integer) 3
127.0.0.1:6379> lrange mylist 0 1
1) "0"
2) "2"
# 列出mylist中从编号0到倒数第一个元素
127.0.0.1:6379> lrange mylist 0 -1
1) "0"
2) "2"
3) "1"
实用举例
- 博客系统中的评论。利用自身的排序
- 使用LRANGE可以方便的实现分页
7. redis数据结构 set
redis的集合,是一种无序的集合,集合中的元素没有先后顺序。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
常用命令
命令 | 行为 |
---|---|
SADD | 向集合添加一个或多个成员 |
SREM | 移除集合中一个或多个成员 |
SISMEMBER | 判断 member 元素是否是集合 key 的成员 |
SMEMBER | 返回集合中的所有成员 |
SCARD | 获取集合的成员数 |
SRANDMEMBER | 返回集合中一个或多个随机数 |
SPOP | 移除并返回集合中的一个随机元素 |
SMOVE | 将 member 元素从 source 集合移动到 destination 集合 |
操作例子
redis 127.0.0.1:6379> SADD esbset redis
(integer) 1
redis 127.0.0.1:6379> SADD esbset mongodb
(integer) 1
redis 127.0.0.1:6379> SADD esbset mysql
(integer) 1
redis 127.0.0.1:6379> SADD esbset mysql
(integer) 0
redis 127.0.0.1:6379> SMEMBERS esbset
1) "mysql"
2) "mongodb"
3) "redis"
实用举例
- 文章记录已投票的用户名单。
- 用户登陆的角色菜单。
8. redis数据结构 sorted set
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。有序集合的成员是唯一的,但分数(score)却可以重复。
常用命令
命令 | 行为 |
---|---|
ZADD | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
ZREM | 移除有序集合中的一个或多个成员 |
ZCARD | 获取有序集合的成员数 |
ZINCRBY | 有序集合中对指定成员的分数加上增量 increment |
ZCOUNT | 计算在有序集合中指定区间分数的成员数 |
ZRANK | 返回有序集合中指定成员的索引 |
ZSCORE | 返回有序集中,成员的分数值 |
ZRANGE | 通过索引区间返回有序集合成指定区间内的成员 |
操作例子
redis 127.0.0.1:6379> ZADD esbsets 1 redis
(integer) 1
redis 127.0.0.1:6379> ZADD esbsets 2 mongodb
(integer) 1
redis 127.0.0.1:6379> ZADD esbsets 3 mysql
(integer) 1
redis 127.0.0.1:6379> ZADD esbsets 3 mysql
(integer) 0
redis 127.0.0.1:6379> ZADD esbsets 4 mysql
(integer) 0
redis 127.0.0.1:6379> ZRANGE esbsets 0 10 WITHSCORES
1) "redis"
2) "1"
3) "mongodb"
4) "2"
5) "mysql"
6) "4"
9. redis数据结构 hash
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
常用命令
命令 | 行为 |
---|---|
HMGET | 获取所有给定字段的值 |
HMSET | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
HDEL | 删除一个或多个哈希表字段 |
HLEN | 获取哈希表中字段的数量 |
HEXISTS | 查看哈希表 key 中,指定的字段是否存在。 |
HKEYS | 获取所有哈希表中的字段 |
HVALS | 获取哈希表中所有值 |
HGETALL | 获取在哈希表中指定 key 的所有字段和值 |
HINCRBY | 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
HINCRBYFLOAT | 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
操作例子
127.0.0.1:6379> HMSET user:001 username esbpassword Esb@123 age 18
(error) ERR wrong number of arguments for HMSET
127.0.0.1:6379> HMSET user:001 username esb password Esb@123 age 18
OK
127.0.0.1:6379> HGETALL user:001
1) "username"
2) "esb"
3) "password"
4) "Esb@123"
5) "age"
6) "18"
127.0.0.1:6379> HSET user:001 password 12345
(integer) 0
127.0.0.1:6379> HGETALL user:001
1) "username"
2) "esb"
3) "password"
4) "12345"
5) "age"
6) "18"
10. redis持久化
redis为持久化提供两种方式:
- RDB: 存在于某一个时刻的所有数据全部写入到磁盘中。
- AOF: 在执行写命令的时候,将被执行的命令复制到磁盘里面。当服务器重启时会重新执行这些命令来恢复数据。
当RDB与AOF同时使用的情况下,如果redis重启,则会优先采用AOF进行数据恢复,因为AOF方式的数据恢复完整度更高。
11. 持久化之RDB
RDB持久化配置
# 时间策略
save 900 1
save 300 10
save 60 10000
#save 900 1 表示900s内如果有1条是写入命令,就触发产生一次快照,可以理解为就进行一次备份
#save 300 10 表示300s内有10条写入,就产生快照
# 文件名称
dbfilename dump.rdb
# 文件保存路径
dir /home/work/app/redis/data/
# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes
# 这个配置也是非常重要的一项配置,这是当备份进程出错时,主进程就停止接受新的写入操作,是为了保护持久化的数据一致性问题。如果自己的业务有完善的监控系统,可以禁止此项配置, 否则请开启。
# 是否压缩
rdbcompression yes
#rdbcompression yes ,建议没有必要开启,毕竟Redis本身就属于CPU密集型服务器,再开启压缩会带来更多的CPU消耗,相比硬盘成本,CPU更值钱。
# 导入时是否检查
rdbchecksum yes
在redis中RDB持久化促发分为两种:自己手动触发与redis定时触发。
- 手动触发可以使用
- save:会阻塞当前Redis服务器,直到持久化完成,线上应该禁止使用。
- bgsave:该触发方式会fork一个子进程,由子进程负责持久化过程,因此阻塞只会发生在fork子进程的时候。
- 自动触发主要有
- 根据我们的 save m n 配置规则自动触发;
- 从节点全量复制时,主节点发送rdb文件给从节点完成复制操作,主节点会触发 bgsave;
- 执行 debug reload 时;
-
执行 shutdown时,如果没有开启aof,也会触发。
运行流程图
流程图
12. 持久化之AOF
AOF持久化配置
# 是否开启aof
appendonly yes
# 文件名称
appendfilename "appendonly.aof"
# 同步方式
appendfsync everysec
#三种模式
#always:把每个写命令都立即同步到aof,很慢,但是很安全
#everysec:每秒同步一次,是折中方案
#no:redis不处理交给OS来处理,非常快,但是也最不安全
# aof重写期间是否同步
no-appendfsync-on-rewrite no
# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 加载aof时如果有错如何处理
aof-load-truncated yes
# 文件重写策略
aof-rewrite-incremental-fsync yes
运行流程图
流程图
13. 持久化之数据恢复
流程图启动时会先检查AOF文件是否存在,如果不存在就尝试加载RDB。那么为什么会优先加载AOF呢?因为AOF保存的数据更完整,通过上面的分析我们知道AOF基本上最多损失1s的数据。
14. redis集群部署
数据分片
Redis 集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现: 一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
集群中的每个节点负责处理一部分哈希槽。 举个例子, 一个集群可以有三个哈希槽, 其中:
节点 A 负责处理 0 号至 5460 号哈希槽。
节点 B 负责处理 5461 号至 10922 号哈希槽。
节点 C 负责处理 10923 号至 16383 号哈希槽。
这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。 比如说:
我现在想设置一个key,叫my_name:
set my_name zhangguoji
按照Redis Cluster的哈希槽算法,CRC16('my_name')%16384 = 2412 那么这个key就被分配到了节点A上
关系图
集群部署公司内部建议使用redis4的版本。本次集群使用redis-4.0.14版本
cd /home
#下载redis
wget http://download.redis.io/releases/redis-4.0.14.tar.gz
#解压文件
tar -xvzf redis-4.0.14.tar.gz
#修改名称
mv redis-4.0.14 redis
#进入目录
cd redis
#安装
make && make install
#本次搭建为模拟集群,就是在一台机器上模拟,使用6个不同端口演示
mkdir -p redis-cluster/7000
mkdir -p redis-cluster/7001
mkdir -p redis-cluster/7002
mkdir -p redis-cluster/7003
mkdir -p redis-cluster/7004
mkdir -p redis-cluster/7005
#拷贝配置文件,并修改配置
cp redis.conf redis-cluster/7000
cp redis.conf redis-cluster/7001
cp redis.conf redis-cluster/7002
cp redis.conf redis-cluster/7003
cp redis.conf redis-cluster/7004
cp redis.conf redis-cluster/7005
#依次修改各个配置
vi redis-cluster/7000/redis.conf
daemonize yes #后台运行
port 7000 #工作端口
bind 192.168.122.1 #本机IP
cluster-enabled yes
cluster-config-file nodes-7000.conf #生成的集群配置文件名称,集群搭建成功后会自动生成
cluster-node-timeout 5000
pidfile /var/run/redis_7000.pid
logfile "7000.log"
#安装ruby、rubygems
yum install ruby rubygems
#升级ruby版本
curl -L get.rvm.io | bash -s stable
#根据上述脚本执行后,会生成一个gpg2的脚本,拷贝后再次执行
gpg2 --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -L get.rvm.io | bash -s stable
source /etc/profile.d/rvm.sh
rvm list known
rvm install ruby 2.6.2
rvm use 2.6.2
#删除旧版ruby
rvm remove 2.0.0
ruby --version
#安装redis与ruby接口
gem install redis
#创建各节点redis启动脚本,
vi /home/restartAllRedis.sh
ps -ef|grep redis|grep -v "grep redis"|grep 7000|awk '{print $2}'|xargs kill -9
ps -ef|grep redis|grep -v "grep redis"|grep 7001|awk '{print $2}'|xargs kill -9
ps -ef|grep redis|grep -v "grep redis"|grep 7002|awk '{print $2}'|xargs kill -9
ps -ef|grep redis|grep -v "grep redis"|grep 7003|awk '{print $2}'|xargs kill -9
ps -ef|grep redis|grep -v "grep redis"|grep 7004|awk '{print $2}'|xargs kill -9
ps -ef|grep redis|grep -v "grep redis"|grep 7005|awk '{print $2}'|xargs kill -9
cd /home/redis
./src/redis-server redis-cluster/7000/redis.conf
./src/redis-server redis-cluster/7001/redis.conf
./src/redis-server redis-cluster/7002/redis.conf
./src/redis-server redis-cluster/7003/redis.conf
./src/redis-server redis-cluster/7004/redis.conf
./src/redis-server redis-cluster/7005/redis.conf
#创建集群服务(在其中一个节点执行即可),
vi /home/restartRedisCluster.sh
ps -ef|grep redis-trib|grep -v "grep redis-trib"|grep 7000|awk '{print $2}'|xargs kill -9
cd /home/redis
./src/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
#配置可执行权限
chmod +x /home/*.sh
#启动redis服务
/home/restartAllRedis.sh
ps -ef|grep redis|grep -v "grep redis"
root 30791 1 0 10:49 ? 00:00:00 ./src/redis-server 127.0.0.1:7000 [cluster]
root 30796 1 0 10:49 ? 00:00:00 ./src/redis-server 127.0.0.1:7001 [cluster]
root 30801 1 0 10:49 ? 00:00:00 ./src/redis-server 127.0.0.1:7002 [cluster]
root 30806 1 0 10:49 ? 00:00:00 ./src/redis-server 127.0.0.1:7003 [cluster]
root 30811 1 0 10:49 ? 00:00:00 ./src/redis-server 127.0.0.1:7004 [cluster]
root 30816 1 0 10:49 ? 00:00:00 ./src/redis-server 127.0.0.1:7005 [cluster]
#启动集群,启动过程,需手工输入yes
/home/restartRedisCluster.sh
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join...
>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: 2ea36466c0383870126f37af6a2f50b8cceaa31e 127.0.0.1:7000
slots:0-5460 (5461 slots) master
1 additional replica(s)
M: 9d2932b4335bd904762fc0197b7275229ce8ac63 127.0.0.1:7001
slots:5461-10922 (5462 slots) master
1 additional replica(s)
S: 3e1eea744d4b77adbd11151355e2bd0952aa45ba 127.0.0.1:7005
slots: (0 slots) slave
replicates 2ea36466c0383870126f37af6a2f50b8cceaa31e
M: fb41ca7b52c34019cbaf7e2e801e87a89309f5a7 127.0.0.1:7002
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: 9a7bc3a429c0bc992ee583b37b1b754961852abc 127.0.0.1:7003
slots: (0 slots) slave
replicates 9d2932b4335bd904762fc0197b7275229ce8ac63
S: 20ec244630b3bc034d3d89b1e243de1861027e06 127.0.0.1:7004
slots: (0 slots) slave
replicates fb41ca7b52c34019cbaf7e2e801e87a89309f5a7
[OK] All nodes agree about slots configuration.
#验证
/home/redis-4.0.11/src/redis-cli -p 7000 -h 127.0.0.1 -c
127.0.0.1:7000> cluster nodes