Redis 学习汇总
- 近期看书看博客看视频,相对较为系统的学习了一下Redis,不过版本还是比较老,主要还是3.X系列的。虽然目前最新的已经是4.X,不过老版本的基本都还兼容。
Redis 编译安装
- Redis安装有很多种方式,Centos可以快捷的使用yum安装,Ubuntu也可以找到apt源进行安装,我这为了尝鲜,用的的是编译安装,其实蛮简单的,我这里直接在mac下进行编译安装,步骤如下:
- 首先下载redis源码包 :wget http://download.redis.io/releases/redis-4.0.11.tar.gz
- 解压 tar -zxvf redis-4.0.11.tar.gz
- 进入redis-4.0.11目录 cd redis-4.0.11
- 执行make命令编译(好像需要command tools,之前安装过,没太注意)
- 然后执行make install 进行安装
- 然后就可以运行了(mac下直接发送到bin目录下面了,不需要额外配置= =!),执行redis-server 就可以看到熟悉的姐妹了
Redis 简易配置
- 开始还是单机运行吧,创建一个conf文件,我这里叫做 redis-6379.conf ,内容如下:
1 | bind 127.0.0.1 #绑定Ip |
- 配置都比较简单,就不过多解释了,主要就是一些工作目录之类的内容
- 配置完成后通过 redis-server redis-6379.conf 启动redis,可以通过redis-cli检查是否启动成功
Redis 基本数据类型
- 新版本增加了若干新的数据类型,我暂时没有使用需求,没做过多研究,主要还是针对常用的5中数据结构
Strings
- 这应该是Redis中使用最多最多的数据结构了,使用起来也很简单,直接set key value 进行赋值,get key进行取值
- 常用的应用场景(好吧,我只是说一下我经常用的场景吧,在使用Token进行登录验证的时候,token存储于Redis中,使用的就是这种结构,设置好过期时间,定期刷新,可以理解为模拟Session吧)
- 列举一些常用API
API | 解释 | 使用示例 |
---|---|---|
set | 设置指定 key 的值 | set key value |
mset | 同时设置一个或多个 key-value 对 | mset key value [key1 value1 …] |
get | 获取指定 key 的值 | get key |
mget | 获取所有(一个或多个)给定 key 的值 | mget key1 [key2 …] |
strlen | 返回 key 所储存的字符串值的长度 | strlen key |
incr | 将 key 中储存的数字值增一。 | incr key |
incrby | 将 key 所储存的值加上给定的增量值(increment) | incrby key increment |
decr | 将 key 中储存的数字值减一 | decr key |
decrby | key 所储存的值减去给定的减量值(decrement) | decrby key increment |
append | 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾 | append key value |
getset | 将给定 key 的值设为 value ,并返回 key 的旧值(old value) | getset key value |
expire | 设置指定key的过期时间(time) | expire key time |
- 这里当然不太全面,具体参考官方文档:https://redis.io/commands
Hash
- Hash结构可以认为是一个微型Redis(如果把Redis简单认为是Strings类型),换成Java语言来说,Hash结构就是 Map<String,Map<String,String>>
- 应用场景。。。。Hmm。。项目中没有用到,不过觉得如果加入用户角色权限等信息。。。。是不是session可以用这个来处理呢?或者是application。。。。Hmm。。。暂时没有想法
- 还是列举一些常用API
API | 解释 | 使用示例 |
---|---|---|
hget | 获取存储在哈希表中指定字段的值。 | HGET key field |
hset | 将哈希表 key 中的字段 field 的值设为 value 。 | HSET key field value |
hmget | 获取所有给定字段的值 | HMGET key field1 [field2] |
hmset | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 | HMSET key field1 value1 [field2 value2 ] |
hgetall | 获取在哈希表中指定 key 的所有字段和值 | HGETALL key |
hscan | 迭代哈希表中的键值对。 | HSCAN key cursor [MATCH pattern] [COUNT count] `` |
hexist | 查看哈希表 key 中,指定的字段是否存在。 | HEXISTS key field |
hdel | 删除一个或多个哈希表字段 | HDEL key field1 [field2] |
hincrby | 为哈希表 key 中的指定字段的整数值加上增量 increment 。 | HINCRBY key field increment |
hkeys | 获取所有哈希表中的字段 | HKEYS key |
hlen | 获取哈希表中字段的数量 | HLEN key |
hvals | 获取哈希表中所有值 | HVALS key |
- 具体参考官方文档:https://redis.io/commands
List
- 列表虽然最近项目中没有使用,不过之前的项目中大规模使用,场景是。。。把list当做消息队列了。。
- 应用场景。。。除了消息队列。。Hmm。。我也想不到什么了。。。如果有其他场景,烦请告诉我,谢谢。。
- 老规矩,列举一些常用API
API | 解释 | 使用示例 |
---|---|---|
blpop | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 | BLPOP key1 [key2] timeout |
brpop | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 | BRPOP key1 [key2] timeout |
brpoplpush | 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 | BRPOPLPUSH source destination timeout |
lindex | 通过索引获取列表中的元素 | LINDEX key index |
linsert | 在列表的元素前或者后插入元素 | LINSERT key BEFORE\AFTER pivot value |
llen | 获取列表长度 | LLEN key |
lpop | 移出并获取列表的第一个元素 | LPOP key |
lpush | 将一个或多个值插入到列表头部 | LPUSH key value1 [value2] |
lpushx | 将一个值插入到已存在的列表头部 | LPUSHX key value |
lrange | 获取列表指定范围内的元素 | LRANGE key start stop |
lrem | 移除列表元素 | LREM key count value |
lset | 通过索引设置列表元素的值 | LSET key index value |
ltrim | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 | LTRIM key start stop |
rpop | 移除并获取列表最后一个元素 | RPOP key |
rpoplpush | 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 | RPOPLPUSH source destination |
rpush | 在列表中添加一个或多个值 | RPUSH key value1 [value2] |
rpushx | 为已存在的列表添加值 | RPUSHX key value |
- 具体参考官方文档:https://redis.io/commands
Set
- Set集合,就知道这个是一个集合,无序,不可以重复(Hmm和java中的Set很相似)
- 应用场景。。。没想到。。。Hmm。。待补充
- 常用API
API | 解释 | 使用示例 |
---|---|---|
sadd | 向集合添加一个或多个成员 | SADD key member1 [member2] |
scard | 获取集合的成员数 | SCARD key |
sdiff | 返回给定所有集合的差集 | SDIFF key1 [key2] |
sdiffstore | 返回给定所有集合的差集并存储在 destination 中 | SDIFFSTORE destination key1 [key2] |
sinter | 返回给定所有集合的交集 | SINTER key1 [key2] |
sinterstore | 返回给定所有集合的交集并存储在 destination 中 | SINTERSTORE destination key1 [key2] |
sismember | 判断 member 元素是否是集合 key 的成员 | SISMEMBER key member |
smembers | 返回集合中的所有成员 | SMEMBERS key |
smove | 将 member 元素从 source 集合移动到 destination 集合 | SMOVE source destination member |
spop | 移除并返回集合中的一个随机元素 | SPOP key |
srandmember | 返回集合中一个或多个随机数 | SRANDMEMBER key [count] |
srem | 移除集合中一个或多个成员 | SREM key member1 [member2] |
sunion | 返回所有给定集合的并集 | SUNION key1 [key2] |
sunionstore | 所有给定集合的并集存储在 destination 集合中 | SUNIONSTORE destination key1 [key2] |
sscan | 迭代集合中的元素 | SSCAN key cursor [MATCH pattern] [COUNT count] |
- 具体参考官方文档:https://redis.io/commands
Zset
- Hmm Zset 也叫做Sorted Set 就是一个排序的集合,简单的说就是Set的有序版本(不过这个和Java的SortedSet不太一样。。),区别是什么呢,区别就是每个元素都有一个Score,排序的依据呢就是这个Score了。。
- 应用场景,项目中倒是用到了,不过感觉用到并不是太对。。。所以不说了。老项目使用这个实现了一个排行榜,Hmm还是可以的,定期刷入到MySQL中持久化,也不怕数据丢失什么的。。。挺好
- 说一下常用API吧
API | 解释 | 使用示例 |
---|---|---|
zadd | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 | ZADD key score1 member1 [score2 member2] |
zcard | 获取有序集合的成员数 | ZCARD key |
zcount | 计算在有序集合中指定区间分数的成员数 | ZCOUNT key min max |
zincrby | 有序集合中对指定成员的分数加上增量 increment | ZINCRBY key increment member |
zinterstore | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 | ZINTERSTORE destination numkeys key [key …] |
zlexcount | 在有序集合中计算指定字典区间内成员数量 | ZLEXCOUNT key min max |
zrange | 通过索引区间返回有序集合成指定区间内的成员 | ZRANGE key start stop [WITHSCORES] |
zrangebylex | 通过字典区间返回有序集合的成员 | ZRANGEBYLEX key min max [LIMIT offset count] |
zrangebyscore | 通过分数返回有序集合指定区间内的成员 | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] |
zrank | 返回有序集合中指定成员的索引 | ZRANK key member |
zrem | 移除有序集合中的一个或多个成员 | ZREM key member [member …] |
zremrangebylex | 移除有序集合中给定的字典区间的所有成员 | ZREMRANGEBYLEX key min max |
zremrangebyrank | 移除有序集合中给定的排名区间的所有成员 | ZREMRANGEBYRANK key start stop |
zremrangebyscore | 移除有序集合中给定的分数区间的所有成员 | ZREMRANGEBYSCORE key min max |
zrevrange | 返回有序集中指定区间内的成员,通过索引,分数从高到底 | ZREVRANGE key start stop [WITHSCORES] |
zrevrangebyscore | 返回有序集中指定分数区间内的成员,分数从高到低排序 | ZREVRANGEBYSCORE key max min [WITHSCORES] |
zrevrank | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 | ZREVRANK key member |
zscore | 返回有序集中,成员的分数值 | ZSCORE key member |
zunionstore | 计算给定的一个或多个有序集的并集,并存储在新的 key 中 | ZUNIONSTORE destination numkeys key [key …] |
zscan | 迭代有序集合中的元素(包括元素成员和元素分值) | ZSCAN key cursor [MATCH pattern] [COUNT count] |
- 具体参考官方文档:https://redis.io/commands
Redis 读写分离结构
- 读写分离其实不是太复杂,简单来说就是多个Redis组成小”集群”,注意我这里的集群是带有引号的哈,这并不是一个真正意义上的集群,而是主从结构(Master&Slave),可以是一主多从,也可以是一主一从,甚至可以使只有一个Master(这个就退化成了。。。单机模式了。。)
- 说一下怎么配置吧
先参考 Redis 简易配置,并启动6379节点
拷贝一份配置文件,将所有的6379替换为6380 (Hmm,我比较懒。。就单机先这么搞了。。)
启动6380这个实例 redis-server redis-6380.conf
查看是否都启动成功了,执行 ps -ef |grep redis-server|grep -v ‘grep’ 我这里显示如下,表示两个实例已经启动成功
1
2501 42485 1 0 12:29下午 ?? 0:07.18 redis-server 127.0.0.1:6379
501 43688 1 0 10:20下午 ?? 0:00.33 redis-server 127.0.0.1:6380执行 redis-cli -p 6380 info replication 看到6380实例目前是以Master角色运行
1 | # Replication |
- 给6380分配角色slave,跟从Master 执行 redis-cli -p 6380 slaveof 127.0.0.1 6379
- 执行 redis-cli -p 6380 info replication 看到6380实例目前是以Slave角色运行
1 | # Replication |
- 这种是通过Redis Client来分配角色,还可以在配置文件中进行配置,修改redis-6380.conf如下,并重启:
1 | bind 127.0.0.1 |
- 实际生产环境通常使用配置文件的方式配置主从结构
- 完成了配置简单尝试一下:
- 在Master set value -> redis-cli -p 6379 set hello world -> OK
- 在Slave get value -> redis-cli -p 6380 get hello -> “world”
- 在Slave set value -> redis-cli -p 6380 set test test -> (error) READONLY You can’t write against a read only slave.
- 基本测试完成,主节点可读可写,从节点同步主节点,只能读取,不能写入,Hmm。。。如果要多加入几个Slave节点。。Copy一下配置文件就好了。。。
Redis HA 之 sentinel
说一下背景
- Hmm 上文说道了集群,其实主从结构并不是一个可靠的集群,比如某天一大波僵尸来袭。。。跑题了,一大波流量来袭。。。Master挂了。。。然后。。。Hmm。。。没有节点可以写入了,咋办呢?
- 解决方法也不是太复杂,举个场景:3台机器,1Master 2Slave,然后某天。。。Master突然挂了。。剩下2个Slave,这个时候咋办?切换一下,让其中一个Slave变成Master,另外一个跟随这个新的Master,这样就1主1从1挂机(鄙视挂机党。。。)。好赖可以正常提供服务了,等挂机服务器启动起来了,将它设置为新Master的Slave节点,这样就完成了Master Slave的转换了,然后就可以正常提供服务了。
- 听起来上面的方案还不错,其实服务端执行起来也不太复杂。如下:
- Master挂了,剩下2个Slave,记为Slave1 Slave2
- 对Slave 1 执行 slaveof no one,将Slave 1 升级为新Master
- 对Slave 2 执行 slaveof Slave1,将Slave 2设置跟从新的Master
- 等原Master启动,执行slaveof Slave1,将原Master设置为Slave并且跟从新的Master
- 为啥说服务端简单呢?Client连接服务器也得跟着切换啊。。Client写入只能写入到Master节点,服务端经过这么一折腾,Client也要跟着切换IP,才能正常访问。
- 所以呢,官方提供了sentinel 一种HA方案,服务端的切换可以自动执行,sentinel 节点负责监控Master Slave状态,如果切换了,同时通知Client进行切换,达到可服务状态。
怎么配置?
首先要额外准备机器作为sentinel节点,我这里偷懒,继续单机运行。。。(Hmm,穷人。。没有太多机器。。也不想搞虚拟机)
先启动一个Maste 6379,2个Slave 6380 8381,执行ps -ef |grep redis|grep -v grep 如下:
1
2
3501 42485 1 0 12:29下午 ?? 0:10.87 redis-server 127.0.0.1:6379
501 43910 1 0 10:47下午 ?? 0:02.45 redis-server 127.0.0.1:6380
501 44117 1 0 11:17下午 ?? 0:00.53 redis-server 127.0.0.1:6381查看Master状态 redis-cli -p 6379 info replication
1
2
3
4
5
6
7
8
9
10
11
12
13# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=3796,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=3796,lag=0
master_replid:7a2c11e996810f76bdc72765130dcf47b5af4ab8
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:3796
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:3796全部启动成功,然后开始配置sentinel节点
从Redis解压文件中可以看到一个sentinel.conf文件,Hmm。。。懒人。。直接Copy这个开始改造,sentinel-26379.conf 如下:
1
2
3
4
5
6
7
8port 26379
dir /Users/eviltuzki/Public/redis/
sentinel monitor mymaster 127.0.0.1 6379 2 # 监控mymaster集群,Master地址为127.0.0.1 6379,当2个sentinel认为Master有问题,则进行Master转换
sentinel down-after-milliseconds mymaster 30000 #下线Master时间30s
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes
daemonize yes #守护进程方式启动生成对应的3份,分别是sentinel-26379.conf、sentinel-26380.conf、sentinel-26381.conf,然后通过redis-sentinel sentinel-263xx.conf启动sentinel节点
查看进程,是否启动成功:ps -ef |grep sent|grep -v grep
1
2
3501 44288 1 0 11:36下午 ?? 0:00.64 redis-sentinel *:26379 [sentinel]
501 44291 1 0 11:36下午 ?? 0:00.61 redis-sentinel *:26380 [sentinel]
501 44293 1 0 11:36下午 ?? 0:00.63 redis-sentinel *:26381 [sentinel]查看redis-sentinel状态:redis-cli -p 26379 info sentinel
1
2
3
4
5
6
7# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3可以看到master0状态OK,2个Slave,3个sentinels,一切正常。接下来就可以使用对应的Client进行连接了。
Hmm 这块内容有点太多了。。。后面单开Java Client连接。。
接下来模拟一下事故吧:
查看一下刚刚配置的sentinel-26379.conf文件,多了一些内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14port 26379
dir "/Users/zhaojian/Public/redis"
sentinel myid bca8111060bbda4a42ec3744391d1f40ca1fda00
sentinel deny-scripts-reconfig yes
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
# Generated by CONFIG REWRITE
sentinel known-slave mymaster 127.0.0.1 6381
sentinel known-slave mymaster 127.0.0.1 6380
sentinel known-sentinel mymaster 127.0.0.1 26380 ba913a9c10e46fd4df76bf0289721136031926ef
daemonize yes
sentinel known-sentinel mymaster 127.0.0.1 26381 7c387d9b2e95ac2338655fb8f5011f277635dade
sentinel current-epoch 0主节点和从节点信息都能看到,现在我准备下线Master节点,看看会有什么反应(Hmm怎么下线呢?直接kill掉吧)
等待一小会儿,Hmm,大概30S左右吧,再次查看sentinel-26379.conf文件,发现有些变化了
1 | port 26379 |
- Hmm,首先Master已经切换了,不再是6379了,而是6380,而6379变成了known-slave,也就是Slave节点,无妨。。。反正现在也不工作。。。
- 那我现在恢复一下6379节点(重新执行redis-server redis-6379.conf),注意,这里的配置6379可是Master哦~
- 执行 redis-cli -p 6379 info replication ,信息如下:
1 | # Replication |
- Hmm,说明sentinel还是蛮智能的,尽管6379之前是Master,但是选举出新的Master之后,旧的Master会被 降级到Slave节点,避免出现多个Master。
- 附带看一下6381和6380的日志:
- 首先是6380的:
1 | 43910:S 06 Oct 23:45:51.903 * MASTER <-> SLAVE sync started |
- 然后看一下6381的:
1 | ... |
- 额,刚想起来6379重启后的日志也看一下:
1 | 42485:M 06 Oct 23:45:51.599 # User requested shutdown... |
- 从日志中可以看到,6379下线以后,6380和6381经历了一段时间(约30s)的找不到Master,之后6380收到了sentinel-7c387d9b-cmd发送的请求,转换角色为Master。6381收到sentinel-7c387d9b-cmd发送的请求,变更为跟随6380而不是6379。等6379重新启动后收到了sentinel-ba913a9c-cmd的请求,降级为Slave并且跟随6380。
- 补充说明一下sentinel一定要集群部署,不能单点!否则网络等因素会导致集群来回切换角色,另外sentinel本身单点也有风险!