Redis学习笔记

Redis基础

Redis官网

http://www.redis.cn/

教程地址

https://www.bilibili.com/video/BV1S54y1R7SB?p=36&share_source=copy_web

0. Redis 入门

0.1.Redis能干什么?

  1. 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)
  2. 高效率、用于高速缓冲
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器、计数器(eg:浏览量)
  6. 。。。

0.2. 特性

  1. 多样的数据类型
  2. 持久化
  3. 集群
  4. 事务

0.3. redis默认有16个数据库

# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT  where
# dbid is a number between 0 and 'databases'-1
databases 16

0.4. 切换数据库命令 select index

127.0.0.1:6379> select 8  # 切换数据库
OK
127.0.0.1:6379[8]> set name Mashiro # 设置key name  value Mashiro
OK
127.0.0.1:6379[8]> get name  # 获取name属性
"Mashiro"
127.0.0.1:6379> select 1  
OK
127.0.0.1:6379[1]> get name  # 不同数据库之间 数据是不能互通的
(nil)
127.0.0.1:6379> select 8
OK
127.0.0.1:6379[8]> keys *  # 查看当前数据库中所有的key。
1) "name"
127.0.0.1:6379[8]> FLUSHDB	# 清空当前数据库中的键值对。
OK
127.0.0.1:6379[8]> FLUSHALL	# 清空所有数据库的键值对。
OK

1. Redis基本操作

1. 1. Redis启动

[root@Mashiro bin]# ls
dump.rdb         redis-check-aof  redis-sentinel
myredisconfig    redis-check-rdb  redis-server
redis-benchmark  redis-cli
[root@Mashiro bin]# pwd
/usr/local/bin
[root@Mashiro bin]# redis-server myredisconfig/redis.conf 
[root@Mashiro bin]# redis-cli -p 6379
127.0.0.1:6379> ping # 启动成功后返回PONG
PONG
127.0.0.1:6379> 

1.2. Redis性能测试工具

# 测试:100个并发连接 100000请求
[root@Mashiro bin]# redis-benchmark -h localhost -p 6379 -c 100 -n 100000
====== SET ======
  100000 requests completed in 0.91 seconds
  100 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration "save": 3600 1 300 100 60 10000
  host configuration "appendonly": no
  multi-thread: no

96.24% <= 1 milliseconds
99.84% <= 2 milliseconds
99.90% <= 5 milliseconds
100.00% <= 5 milliseconds
109769.48 requests per second

====== GET ======
  100000 requests completed in 0.82 seconds
  100 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration "save": 3600 1 300 100 60 10000
  host configuration "appendonly": no
  multi-thread: no

97.54% <= 1 milliseconds
100.00% <= 2 milliseconds
100.00% <= 2 milliseconds
121506.68 requests per second

1.3. Redis 五大数据类型

1.3.1. Redis-Key

#################################################################

127.0.0.1:6379> set name Mashiro
OK
127.0.0.1:6379> set age 22
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
# 判断键是否存在,如果存在则返回1 不存在则返回0
127.0.0.1:6379> EXISTS name 
(integer) 1
127.0.0.1:6379> EXISTS birth
(integer) 0

#################################################################

127.0.0.1:6379> MOVE name 1	# MOVE key index 将键name 移动到1号数据库
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "name"

#################################################################

127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> get name
"Mashiro"
127.0.0.1:6379> EXPIRE name 5 # EXPIRE key seconds 设置一个键的有效时间 单位为秒
(integer) 1
127.0.0.1:6379> ttl name	# 查看剩余秒数
(integer) 2
127.0.0.1:6379> ttl names
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
# Redis的key,通过TTL命令返回key的过期时间,一般来说有3种:
# 当前key没有设置过期时间,所以会返回-1.
# 当前key有设置过期时间,而且key已经过期,所以会返回-2.
# 当前key有设置过期时间,且key还没有过期,故会返回key的正常剩余时间.

#################################################################

127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> TYPE name # TYPE key 查看键的类型
string
127.0.0.1:6379> TYPE age
string

1.3.2. String

#################################################################

127.0.0.1:6379> set name Mashiro
OK
127.0.0.1:6379> get name
"Mashiro"
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> EXISTS name
(integer) 1
127.0.0.1:6379> APPEND name ",hello"	# 追加字符串
(integer) 13
127.0.0.1:6379> get name
"Mashiro,hello"
127.0.0.1:6379> STRLEN name	# 查看字符串长度
(integer) 13
127.0.0.1:6379> APPEND key1 "good"	# 如果append的键不存在  相当于set一个key
(integer) 4
127.0.0.1:6379> keys *
1) "key1"
2) "name"

#################################################################

127.0.0.1:6379> set views 0 
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> INCR views	# 相当于views++
(integer) 1
127.0.0.1:6379> INCR views
(integer) 2
127.0.0.1:6379> DECR views	# 相当于views--
(integer) 1
127.0.0.1:6379> DECR views
(integer) 0
127.0.0.1:6379> DECR views
(integer) -1
127.0.0.1:6379> INCRBY views 10	# 相当于views += 10
(integer) 9
127.0.0.1:6379> DECRBY views 5 	# 相当于 views -= 5
(integer) 4

#################################################################

127.0.0.1:6379> set key1 "hello,Mashiro"
OK
127.0.0.1:6379> get key1
"hello,Mashiro"
127.0.0.1:6379> GETRANGE key1 0 3 	# 截取字符串[0,3]位置的字符串
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1 	# 获取全部字符串
"hello,Mashiro"

#################################################################

127.0.0.1:6379> set key2 ABCDEFG
OK
127.0.0.1:6379> get key2
"ABCDEFG"
127.0.0.1:6379> SETRANGE key2 2 WWW	# 从字符串的2位置开始 替换字符串
(integer) 7
127.0.0.1:6379> get key2
"ABWWWFG"

#################################################################
127.0.0.1:6379> SETEX key3 10 "Good"	# setex (set with expire)设置过期时间 
OK
127.0.0.1:6379> ttl key3
(integer) 6
127.0.0.1:6379> SETNX key4 "Java"	# setnx(set if not exist) 不存在则设置,防止重复设置而覆盖数据
(integer) 1
127.0.0.1:6379> SETNX key4 "Python"
(integer) 0
127.0.0.1:6379> get key4
"Java"

#################################################################

127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3	# 批量Set
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> MGET k1 k2 k3 	# 批量Get
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> MSETNX k1 v1 k4 v4	# 遵循原子性原则 要么都成功 要么都失败
(integer) 0
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"

#################################################################

# 对象
127.0.0.1:6379> set user:1 "{name:Mashiro,age:18}"	# 使用JSON字符串格式存入对象 set user:{id} {filed}
OK
127.0.0.1:6379> get user:1 
"{name:Mashiro,age:18}"
127.0.0.1:6379> mset user:1:name Nagisa user:1:age 18 
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "Nagisa"
2) "18"

#################################################################

# Get Set组合命令 获取旧的值并交换新的值
127.0.0.1:6379> getset lang "Java"
(nil)
127.0.0.1:6379> get lang
"Java"
127.0.0.1:6379> getset lang "Python"
"Java"
127.0.0.1:6379> get lang
"Python"

#################################################################

1.3.3. List

Redis中,List的用法可以是栈、队列、阻塞队列等

所有的list命令都是 l 开头

#################################################################

127.0.0.1:6379> LPUSH mylist one	# 从列表的头部插入一个或多个值 (left push)
(integer) 1
127.0.0.1:6379> LPUSH mylist two
(integer) 2
127.0.0.1:6379> LPUSH mylist three
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1	# 获取列表的所有值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE mylist 0 1	# 获取列表[0,1]的值(现进后出,获取的值为倒序)
1) "three"
2) "two"
127.0.0.1:6379> RPUSH mylist zero	# 从列表尾部插入一个或多个值 (right push)
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "three"
2) "two"
3) "one"
4) "zero"

#################################################################

127.0.0.1:6379> LRANGE mylist 0 -1
1) "three"
2) "two"
3) "one"
4) "zero"
127.0.0.1:6379> LPOP mylist	# 从头部弹出一个值 (left pop)
"three"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "two"
2) "one"
3) "zero"
127.0.0.1:6379> RPOP mylist	# 从尾部弹出一个值 (right pop)
"zero"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "two"
2) "one"
127.0.0.1:6379> 

#################################################################

127.0.0.1:6379> LPUSH mylist three
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> RPUSH mylist zero
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "three"
2) "two"
3) "one"
4) "zero"
127.0.0.1:6379> LINDEX mylist 2	# 通过下标获取列表中的值
"one"
127.0.0.1:6379> LINDEX mylist 0
"three"
127.0.0.1:6379> LLEN mylist	# 获取列表值的个数
(integer) 4


#################################################################

127.0.0.1:6379> LRANGE mylist 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
5) "zero"
127.0.0.1:6379> LREM mylist 1 zero	# List Remove 在mylist中移除1个"zero"
(integer) 1
127.0.0.1:6379> LRANGE mylist 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> LREM mylist 1 three
(integer) 1
127.0.0.1:6379> LRANGE mylist 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LPUSH mylist three
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> LREM mylist 2 three	# List Remove 在mylist中移除2个"Three"
(integer) 2
127.0.0.1:6379> LRANGE mylist 0 -1
1) "two"
2) "one"

#################################################################

127.0.0.1:6379> LRANGE list 0 -1
1) "hello1"
2) "hello2"
3) "hello3"
4) "hello4"
5) "hello5"
127.0.0.1:6379> LTRIM list 1 3	# 裁剪list中的[1,3]元素。(ltrim修改了list的值,而lrange不修改)
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "hello2"
2) "hello3"
3) "hello4"

#################################################################

127.0.0.1:6379> RPUSH firstList "hello0" "hello1" "hello2" "hello3" "hello4"
(integer) 5
127.0.0.1:6379> LRANGE firstList 0 -1
1) "hello0"
2) "hello1"
3) "hello2"
4) "hello3"
5) "hello4"
127.0.0.1:6379> RPOPLPUSH firstList firstList	# 从一个list的底部弹出值,并添加到另一个list的头部
"hello4"
127.0.0.1:6379> LRANGE firstList 0 -1
1) "hello4"
2) "hello0"
3) "hello1"
4) "hello2"
5) "hello3"
127.0.0.1:6379> LREM firstList 1 hello4
(integer) 1
127.0.0.1:6379> LRANGE firstList 0 -1
1) "hello0"
2) "hello1"
3) "hello2"
4) "hello3"
127.0.0.1:6379> RPUSH secoundList "hi0" "hi1"
(integer) 2
127.0.0.1:6379> RPOPLPUSH firstList secoundList
"hello3"
127.0.0.1:6379> LRANGE firstList 0 -1
1) "hello0"
2) "hello1"
3) "hello2"
127.0.0.1:6379> LRANGE secoundList 0 -1
1) "hello3"
2) "hi0"
3) "hi1"

#################################################################

127.0.0.1:6379> EXISTS list	# 判断list是否存在
(integer) 0
127.0.0.1:6379> LSET list 0 value	# 设置list的第0个元素的值为"value",如果不存在则返回error
(error) ERR no such key
127.0.0.1:6379> LPUSH list 0 value1
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "value1"
2) "0"
127.0.0.1:6379> LSET list 0 hello
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "0"

#################################################################

127.0.0.1:6379> RPUSH mylist "one" two three four five
(integer) 5
127.0.0.1:6379> LRANGE mylist 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
5) "five"
127.0.0.1:6379> LINSERT mylist before one oneoneone	# 在mylist列表中的"one"前面添加"oneoneone"
(integer) 6
127.0.0.1:6379> LRANGE mylist 0 -1
1) "oneoneone"
2) "one"
3) "two"
4) "three"
5) "four"
6) "five"
127.0.0.1:6379> LINSERT mylist after five helloworld	# 在mylist列表中的"five"后面添加"helloworld"
(integer) 7
127.0.0.1:6379> LRANGE mylist 0 -1
1) "oneoneone"
2) "one"
3) "two"
4) "three"
5) "four"
6) "five"
7) "helloworld"

#################################################################

# push 和 pop 才分左右。其它命令开头的‘l’都是‘list’

1.3.4. Set(无序集合)

无序的Set集合

#################################################################

127.0.0.1:6379> SADD myset 'hello'	# 向set中插入值
(integer) 1
127.0.0.1:6379> SADD myset 'Mashiro'
(integer) 1
127.0.0.1:6379> SADD myset 'world'
(integer) 1
127.0.0.1:6379> SMEMBERS myset	# 查询set中的成员
1) "world"
2) "hello"
3) "Mashiro"
127.0.0.1:6379> SISMEMBER myset hello	# 判断set中该成员是否存在
(integer) 1
127.0.0.1:6379> SISMEMBER myset 666
(integer) 0
127.0.0.1:6379> SADD myset 'Mashiro'	# set中不能插入相同的值
(integer) 0
127.0.0.1:6379> SCARD myset	# 查询set中成员的数量
(integer) 3

#################################################################

127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
3) "Mashiro"
127.0.0.1:6379> SREM myset hello	# 移除set中的元素
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "Mashiro"

#################################################################

127.0.0.1:6379> SRANDMEMBER myset	# 随机抽出一个元素
"Mashiro"
127.0.0.1:6379> SRANDMEMBER myset
"world"
127.0.0.1:6379> SMEMBERS myset
1) "[A,B,C,D,E]"
2) "world"
3) "Mashiro"
127.0.0.1:6379> SPOP myset	# 随机删除某一个元素
"Mashiro"

#################################################################

127.0.0.1:6379> SADD myset1 "Mashiro"
(integer) 1
127.0.0.1:6379> SADD myset1 "Nagisa"
(integer) 1
127.0.0.1:6379> SADD myset1 "Hello"
(integer) 1
127.0.0.1:6379> SADD myset2 "world"
(integer) 1
127.0.0.1:6379> SMOVE myset1 myset2 "Hello"		# 将一个set中的元素移动到另一个set中
(integer) 1
127.0.0.1:6379> SMEMBERS myset1
1) "Nagisa"
2) "Mashiro"
127.0.0.1:6379> SMEMBERS myset2
1) "world"
2) "Hello"

#################################################################

127.0.0.1:6379> SADD set1 a b c d e
(integer) 5
127.0.0.1:6379> sadd set2 d e f g h
(integer) 5
127.0.0.1:6379> SMEMBERS set1
1) "b"
2) "d"
3) "a"
4) "c"
5) "e"
127.0.0.1:6379> SMEMBERS set2
1) "g"
2) "e"
3) "d"
4) "f"
5) "h"
127.0.0.1:6379> SDIFF set1 set2		# 查询set1 中set2没有的成员 【差集】
1) "b"
2) "a"
3) "c"
127.0.0.1:6379> SINTER set1 set2	# 查询set1和set2中共有的成员 【交集】 
1) "d"
2) "e"
127.0.0.1:6379> SUNION set1 set2	# 查询set1和set2的并集
1) "b"
2) "f"
3) "c"
4) "h"
5) "g"
6) "e"
7) "d"
8) "a"

1.3.5. Hash

Map集合。

Hash更合适对象的存储

#################################################################

127.0.0.1:6379> HSET myhash field mashiro	# 向Hash中添加key-value
(integer) 1
127.0.0.1:6379> HGET myhash field 	# 获取hash中对应key的value
"mashiro"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> HMSET myhash x mashiro y nagisa z hello	# 向hash中插入多个值
OK
127.0.0.1:6379> HMGET myhash x y z	# 获取hash中的多个值
1) "mashiro"
2) "nagisa"
3) "hello"
127.0.0.1:6379> HGETALL myhash	# 获取hash中的所有值  返回的形式为 key value key value ...
1) "x"
2) "mashiro"
3) "y"
4) "nagisa"
5) "z"
6) "hello"

#################################################################

127.0.0.1:6379> HGETALL myhash
1) "x"
2) "mashiro"
3) "y"
4) "nagisa"
5) "z"
6) "hello"
127.0.0.1:6379> HDEL myhash x	# 删除hash中指定的key-value
(integer) 1
127.0.0.1:6379> HGETALL myhash
1) "y"
2) "nagisa"
3) "z"
4) "hello"

#################################################################

127.0.0.1:6379> HLEN myhash	# 查询hash中有多少key-value
(integer) 2

#################################################################

127.0.0.1:6379> HEXISTS myhash y	# 判断hash中key是否存在
(integer) 1
127.0.0.1:6379> HEXISTS myhash x
(integer) 0

#################################################################

127.0.0.1:6379> HKEYS myhash	# 获取hash中所有的key
1) "y"
2) "z"
127.0.0.1:6379> HVALS myhash	# 获取hash中所有的value
1) "nagisa"
2) "hello"

#################################################################

127.0.0.1:6379> HSET myhash number 0
(integer) 1
127.0.0.1:6379> HINCRBY myhash number 10	# 指定hash中的key,value增加10
(integer) 10
127.0.0.1:6379> HGET myhash number
"10"
127.0.0.1:6379> HINCRBY myhash number -5	# 指定hash中的key,value减少5
(integer) 5
127.0.0.1:6379> HGET myhash number
"5"

#################################################################

127.0.0.1:6379> HSETNX myhash mykey Java	# 如果key不存在,则创建key-value成功
(integer) 1
127.0.0.1:6379> HSETNX myhash mykey Python	# 如果key存在,则创建失败
(integer) 0

#################################################################

1.3.6. ZSet (有序集合)

#################################################################

127.0.0.1:6379> ZADD myzset 1 one	# 向zset中添加元素, 1是排序序号
(integer) 1
127.0.0.1:6379> ZADD myzset 2 two 3 three
(integer) 2
127.0.0.1:6379> ZRANGE myzset 0 -1	# 读取zset中所有元素
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> ZADD salary 2500 A 3000 B 1500 C 6000 D
(integer) 4
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf	# 升序排序读取 [-inf,+inf]:负无穷到正无穷
1) "C"
2) "A"
3) "B"
4) "D"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores	# 升序排序读取 [带值]
1) "C"
2) "1500"
3) "A"
4) "2500"
5) "B"
6) "3000"
7) "D"
8) "6000"
127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf withscores	# 降序排序读取 [带值]
1) "D"
2) "6000"
3) "B"
4) "3000"
5) "A"
6) "2500"
7) "C"
8) "1500"

#################################################################

127.0.0.1:6379> ZRANGE salary 0 -1
1) "C"
2) "A"
3) "B"
4) "D"
127.0.0.1:6379> ZREM salary A	# 移除zset中的一个元素
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "C"
2) "B"
3) "D"
127.0.0.1:6379> ZCARD salary	# 查询Zset中有多少元素
(integer) 3

#################################################################

127.0.0.1:6379> ZADD myzset 1 A 2 B 3 C 4 D 5 E
(integer) 5
127.0.0.1:6379> ZCOUNT myzset 1 2	# 获取区间内的元素数量
(integer) 2
127.0.0.1:6379> ZCOUNT myzset 0 5
(integer) 5
127.0.0.1:6379> ZCOUNT myzset 3 5
(integer) 3
127.0.0.1:6379> ZCOUNT myzset 0 7
(integer) 5

#################################################################

1.4. Redis三个特殊类型

1.4.1.Geospatial地理位置

规则:两级无法直接添加。一般直接下载城市地理数据,直接使用Java程序写入

#################################################################

127.0.0.1:6379> GEOADD china:city 113.27324 23.15792 GuangZhou 113.29673 22.20907 ZhuHai	# 添加多个key,城市与城市经纬度
(integer) 2
127.0.0.1:6379> GEOADD china:city 116.67809 23.46244 ChaoZhou	# 添加一个key,城市与城市经纬度
(integer) 1
127.0.0.1:6379> GEODIST china:city GuangZhou ChaoZhou	# 计算两个地理位置的距离
"349433.8927"
127.0.0.1:6379> GEODIST china:city GuangZhou ChaoZhou km	# 计算两个地理位置的距离 单位为千米
"349.4339"
127.0.0.1:6379> GEODIST china:city GuangZhou ChaoZhou m	# 计算两个地理位置的距离 单位为米
"349433.8927"
127.0.0.1:6379> GEORADIUS china:city 116 23 200 km	# 计算某个经纬度方圆XX km内的所有地点
1) "ChaoZhou"
127.0.0.1:6379> GEORADIUS china:city 116 23 400 km
1) "ZhuHai"
2) "GuangZhou"
3) "ChaoZhou"

# 获取某一点方圆XX km内的10个元素(附带经度,纬度和距离)
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist withcoord withdist count 10	
1) 1) "GuangZhou"
   2) "827.6084"
   3) 1) "113.27324062585830688"
      2) "23.1579209662846921"
2) 1) "ZhuHai"
   2) "926.8499"
   3) 1) "113.29673141241073608"
      2) "22.20907091277658907"
3) 1) "ChaoZhou"
   2) "983.8174"
   3) 1) "116.67809039354324341"
      2) "23.46243983164651326"
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist withcoord withdist count 1	# 获取某一点方圆XX km内的1个元素
1) 1) "GuangZhou"
   2) "827.6084"
   3) 1) "113.27324062585830688"
      2) "23.1579209662846921"


#################################################################

127.0.0.1:6379> GEOHASH china:city ChaoZhou GuangZhou ZhuHai	# 返回11个字符长度的GEOHASH字符串
1) "ws4vrp4qse0"
2) "ws0e9xg09v0"
3) "webw65h1jn0"
127.0.0.1:6379> GEOPOS china:city ChaoZhou GuangZhou ZhuHai
1) 1) "116.67809039354324341"
   2) "23.46243983164651326"
2) 1) "113.27324062585830688"
   2) "23.1579209662846921"
3) 1) "113.29673141241073608"
   2) "22.20907091277658907"
127.0.0.1:6379> GEOPOS china:city ChaoZhou 300 km
1) 1) "116.67809039354324341"
   2) "23.46243983164651326"
2) (nil)
3) (nil)
127.0.0.1:6379> GEOPOS china:city ChaoZhou 400 km
1) 1) "116.67809039354324341"
   2) "23.46243983164651326"
2) (nil)
3) (nil)
127.0.0.1:6379> GEORADIUSBYMEMBER china:city ChaoZhou 100 km	# 找出指定元素方圆XX km内的元素
1) "ChaoZhou"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city ChaoZhou 1000 km
1) "ZhuHai"
2) "GuangZhou"
3) "ChaoZhou"

#################################################################

# Geo底层是ZSet 所以可以使用ZSet命令操作Geo
127.0.0.1:6379> ZRANGE china:city 0 -1	
1) "ZhuHai"
2) "GuangZhou"
3) "ChaoZhou"
127.0.0.1:6379> ZREM china:city ZhuHai	# 使用ZSet的删除命令移除GEO中的元素
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "GuangZhou"
2) "ChaoZhou"

#################################################################

1.4.2. Hyperloglog

用于基数统计的算法

#################################################################

127.0.0.1:6379> PFADD hll a b c d e f g	
(integer) 1
127.0.0.1:6379> PFCOUNT hll
(integer) 7
127.0.0.1:6379> PFADD hll1 abc def ghi jkl
(integer) 1
127.0.0.1:6379> PFADD hll2 a123 b123 c123 d123
(integer) 1
127.0.0.1:6379> PFCOUNT hll1
(integer) 4
127.0.0.1:6379> PFCOUNT hll2
(integer) 4
127.0.0.1:6379> PFMERGE hll3 hll1 hll2
OK
127.0.0.1:6379> PFCOUNT hll3
(integer) 8
127.0.0.1:6379> PFADD pf1 a b c d a b c
(integer) 1
127.0.0.1:6379> PFCOUNT pf1	# 统计不重复的元素数量
(integer) 4

#################################################################

1.4.3. Bitmap(位图)

#################################################################

127.0.0.1:6379> SETBIT testbit 0 0	# 插入一个Bit数据
(integer) 0
127.0.0.1:6379> SETBIT testbit 1 1
(integer) 0
127.0.0.1:6379> SETBIT testbit 2 1
(integer) 0
127.0.0.1:6379> GETBIT testbit 0	# 读取指定的Bit
(integer) 0
127.0.0.1:6379> GETBIT testbit 1
(integer) 1
127.0.0.1:6379> SETBIT testbit 3 1
(integer) 0
127.0.0.1:6379> SETBIT testbit 4 0
(integer) 0
127.0.0.1:6379> SETBIT testbit 5 0
(integer) 0
127.0.0.1:6379> SETBIT testbit 6 0
(integer) 0
127.0.0.1:6379> BITCOUNT testbit	# 查看testbit里面有多少值为1的bit
(integer) 3

#################################################################

2. 事务

开启事务:MULTI,提交:EXEC

Marks the start of a transaction block. Subsequent commands will be queued for atomic execution using EXEC.

Redis事务单条命令保持原子性,事务不保持原子性

  • 开启事务(MULTI)
  • 加入队列(…)
  • 提交事务(EXEC )

2.1. 正常提交事务

127.0.0.1:6379> MULTI	# 开启
OK
# 加入队列Begin #
127.0.0.1:6379> set mykey v1
QUEUED
127.0.0.1:6379> set mykey v2
QUEUED
127.0.0.1:6379> set mykey2 v3
QUEUED
127.0.0.1:6379> get mykey
QUEUED
127.0.0.1:6379> set myset Mashiro
QUEUED
127.0.0.1:6379> get mykey2
QUEUED
127.0.0.1:6379> get myset
QUEUED
# 加入队列End #
127.0.0.1:6379> exec	# 提交
1) OK
2) OK
3) OK
4) "v2"
5) OK
6) "v3"
7) "Mashiro"

2.2. 取消事务(DISCARD)

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> DISCARD	# 取消事务
OK
127.0.0.1:6379> get k1
(nil)

2.3. 编译异常

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3	# 编译错误代码
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.	# 事务提交失败
127.0.0.1:6379> get k1	# 事务代码全部不执行
(nil)
127.0.0.1:6379> get k4	# 事务代码全部不执行
(nil)

2.4. 运行异常

127.0.0.1:6379> set k1 'hello'
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR k1	# 运行错误代码
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> EXEC
1) (error) ERR value is not an integer or out of range	# 运行错误
2) OK
3) OK
127.0.0.1:6379> get k2	# 事务正确代码执行
"v2"
127.0.0.1:6379> get k3	# 事务正确代码执行
"v3"

2.5. Watch监控

  • 客户端1 设置监控:监视money,然后开启事务未提交
127.0.0.1:6379> WATCH money	# 对money开启监控
OK
127.0.0.1:6379> MULTI	# 开启事务
OK
127.0.0.1:6379> DECRBY money 500
QUEUED
127.0.0.1:6379> INCRBY out 500
QUEUED
127.0.0.1:6379>	# 未提交

  • 客户端2 对money进行了操作
127.0.0.1:6379> INCRBY money 1000	# 对money进行了操作
(integer) 2000
  • 客户端1 进行事务提交(失败)【exec和discard命令会自动借出watch】
127.0.0.1:6379> exec
(nil)	# 事务提交失败
  • 发现事务执行失败,解除监控
127.0.0.1:6379> unwatch
OK

3. Jedis

3.1. 远程连接Redis

  • 修改redis的配置文件
    • daemonize no
    • 注释掉 bind 127.0.0.1
    • protected-mode no
    • requirepass 123456 【设置redis连接密码】
    • 配置阿里云服务器安全组

3.2. PING

 public static void main(String[] args) {
     
        String host = "我的服务器地址";
        int port = 6379;
        String password = "我的密码";
        Jedis jedis = new Jedis(host,port);
        jedis.auth(password);

        System.out.println(jedis.ping());
    }

3.3. Jedis API

3.3.1. String

  • Java
//jedis String
        // Set
        System.out.println("Jedis-String-Set-name:" + jedis.set("name", "Mashiro"));
        System.out.println("Jedis-String-Set-age:" + jedis.set("age", "22"));

        // Get
        System.out.println("Jedis-String-Get-name:" + jedis.get("name"));
        System.out.println("Jedis-String-Get-age:" + jedis.get("age"));

        // keys
        System.out.println("Jedis-String-Keys:" + jedis.keys("*"));

        // exists
        System.out.println("Jedis-String-Exists-name:" + jedis.exists("name"));
        System.out.println("Jedis-String-Exists-age:" + jedis.exists("age"));

        // append
        System.out.println("Jedis-String-append-name:" + jedis.append("name","Shina"));
        System.out.println("Jedis-String-Get-name:" + jedis.get("name"));

        // strlen
        System.out.println("Jedis-String-Strlen-name:" + jedis.strlen("name"));

        // incr && incrby
        System.out.println("Jedis-String-Incr-age:" + jedis.incr("age"));
        System.out.println("Jedis-String-Incr-age:" + jedis.incrBy("age",10));
        System.out.println("Jedis-String-Get-age:" + jedis.get("age"));

        // decr && decrby
        System.out.println("Jedis-String-Decr-age:" + jedis.decr("age"));
        System.out.println("Jedis-String-Decr-age:" + jedis.decrBy("age",10));
        System.out.println("Jedis-String-Get-age:" + jedis.get("age"));

		// flushDB
        System.out.println("Jedis-FlushDB:" + jedis.flushDB());
  • Result
# 运行结果
    Jedis-String-Set-name:OK
    Jedis-String-Set-age:OK
    Jedis-String-Get-name:Mashiro
    Jedis-String-Get-age:22
    Jedis-String-Keys:[age, name]
    Jedis-String-Exists-name:true
    Jedis-String-Exists-age:true
    Jedis-String-append-name:12
    Jedis-String-Get-name:MashiroShina
    Jedis-String-Strlen-name:12
    Jedis-String-Incr-age:23
    Jedis-String-Incr-age:33
    Jedis-String-Get-age:33
    Jedis-String-Decr-age:32
    Jedis-String-Decr-age:22
    Jedis-String-Get-age:22
    Jedis-FlushDB:OK

3.3.2.List

3.3.3.Set

3.3.4.Hash

3.3.5.ZSet

3.3.6. 事务

		Jedis jedis = new Jedis(host,port);
        jedis.auth(password);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name1","hello");
        jsonObject.put("name2","world");
        String s = jsonObject.toJSONString();

        Transaction multi = jedis.multi();
        try {
     
            multi.set("user1",s);
            multi.set("user2",s);

            multi.exec();
        } catch (Exception e){
     
            multi.discard();
        } finally {
     
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();
        }

4. Redis-Springboot

4.1. Redis-Springboot整合包

  • pom.xml
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>
  • spring-boot-starter-data-redis
<dependencies>
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starterartifactId>
      <version>2.4.5version>
      <scope>compilescope>
    dependency>
    <dependency>
      <groupId>org.springframework.datagroupId>
      <artifactId>spring-data-redisartifactId>
      <version>2.4.8version>
      <scope>compilescope>
    dependency>
    
    <dependency>
      <groupId>io.lettucegroupId>
      <artifactId>lettuce-coreartifactId>
      <version>6.0.4.RELEASEversion>
      <scope>compilescope>
    dependency>
  dependencies>
  • lettuce

<dependency>
    <groupId>io.nettygroupId>
    <artifactId>netty-transportartifactId>
    <version>4.1.63.Finalversion>
    <scope>compilescope>
dependency>
  • Tips

Jedis直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接。

Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

使用Lettuce的性能比Jedis更高。

4.2. Redis配置

1.Maven:org.springframework.boot:spring-boot-autoconfigure:2.4.5
2.org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
3.RedisProperties.class
  • application.yaml
# Springboot-Redis
spring:
  redis:
    host: **********
    port: 6379
    password: 123456

4.3. RedisTemplate

	@Bean
	// 可自定义一个redisTemplate来替换这个Template
    @ConditionalOnMissingBean(
        name = {
     "redisTemplate"}
    )	
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
     
        // 泛型为
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }


	// 由于String类型使用频繁,单独提供了StringRedisTemplate的Bean来供平时使用 
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
     
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

4.4. 测试

  • 自动注入RedisTemplate
@Autowired
private RedisTemplate redisTemplate;
  • 测试Template
// 操作五大基本数据类型
// 操作String
ValueOperations value = redisTemplate.opsForValue();
// 操作List
ListOperations list = redisTemplate.opsForList();
// 操作Hash
HashOperations hash = redisTemplate.opsForHash();
// 操作Set
SetOperations set = redisTemplate.opsForSet();
// 操作ZSet
ZSetOperations zSet = redisTemplate.opsForZSet();

// 操作三种特殊数据类型
// 操作Geo
GeoOperations geo = redisTemplate.opsForGeo();
// 操作HyperLogLog
HyperLogLogOperations hyperLogLog = redisTemplate.opsForHyperLogLog();
ClusterOperations cluster = redisTemplate.opsForCluster();

// 事务三个方法
redisTemplate.multi();
redisTemplate.exec();
redisTemplate.discard();

// 获取连接对象操作Redis
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisConnection connection = connectionFactory.getConnection();
connection.flushDb();
connection.flushAll();

// 测试运行
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisConnection connection = connectionFactory.getConnection();
System.out.println(connection.ping());
// Result: PONG

4.5. 序列化问题

  • RedisTemplate中的序列化部分的源码
    @Nullable
    private RedisSerializer keySerializer = null;
    @Nullable
    private RedisSerializer valueSerializer = null;
    @Nullable
    private RedisSerializer hashKeySerializer = null;
    @Nullable
    private RedisSerializer hashValueSerializer = null;
    private RedisSerializer<String> stringSerializer = RedisSerializer.string();
  • 默认的JDK序列化
if (this.defaultSerializer == null) {
     
    this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}

4.6. 自定义RedisTemplate

4.6.1. 使用ObjectMapper的情况

  • POJO: User
@Component
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
     
    private String name;
    private Integer id;
    private Integer age;
}
  • 测试
    @Test
    void contextLoads() throws JsonProcessingException {
     
        User user = new User("Mashiro",1,22);
        // 真实开发中一般采用JSON格式传递对象
        String value = new ObjectMapper().writeValueAsString(user);
        redisTemplate.opsForValue().set("user",value);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }
  • 控制台结果
{
     "name":"Mashiro","id":1,"age":22}
  • Redis结果
47.115.170.206:6379> get user
(nil)
47.115.170.206:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04user" # 乱码

4.6.2. 无序列化的情况

  • 测试
 @Test
    void contextLoads() throws JsonProcessingException {
     
        User user = new User("Mashiro",1,22);       
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }
  • 结果
# 异常报错
org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException:

4.6.3. 实体类实现Serializable的情况

  • POJO
@Component
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User implements Serializable {
     
    private String name;
    private Integer id;
    private Integer age;
}
  • 测试
@Test
    void contextLoads() throws JsonProcessingException {
     
        User user = new User("Mashiro",1,22);        
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }
  • 结果
# IDEA测试结果
User(name=Mashiro, id=1, age=22)

# Redis查看结果
47.115.170.206:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04user"

4.6.4. 自己编写RedisConfig

@Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
     
        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<Object>(Object.class);
        
        template.setKeySerializer(serializer);

        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        serializer.setObjectMapper(om);

        StringRedisSerializer redisSerializer = new StringRedisSerializer();

        //  Key采用String的序列化方式
        template.setKeySerializer(redisSerializer);
        //  Hash的Key采用String的序列化方式
        template.setHashKeySerializer(redisSerializer);
        //  Value采用Jackson的序列化方式
        template.setValueSerializer(serializer);
        //  Hash的Value采用Jackson的序列化方式
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }

Redis进阶

1. Redis.conf详解

1.1. Unit

# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

# Redis对大小写不敏感

1.2. include

# include /path/to/local.conf
# include /path/to/other.conf

# 可以包含其它redis配置文件

1.3. Module

# loadmodule /path/to/my_module.so
# loadmodule /path/to/other_module.so

# 绑定 .so文件

1.4. NETWORK(网络)

bind 127.0.0.1	# 绑定ip地址
protected-mode no	# 是否开启保护(如果是yes则不能远程访问)
port 6379	# 端口号

1.5. GENERAL(通用)

daemonize no	# 是否以守护进程的方式运行(是否后台运行)
pidfile /var/run/redis_6379.pid		# 如果以后台方式运行,则需要指定一个pid文件

# 配置数据库数量 默认为16个
databases 16

# 是否开启Redis的Logo
always-show-logo yes

# Redis日志级别
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)	 测试/开发环境
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)	生产环境
# warning (only very important / critical messages are logged)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice

# 日志输出位置和文件名
# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile ""

1.6.SNAPSHOTTING(快照)

# Save the DB on disk:
#
#   save  
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   In the example below the behavior will be to save:
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
#
#   Note: you can disable saving completely by commenting out all "save" lines.
#
#   It is also possible to remove all the previously configured save
#   points by adding a save directive with a single empty string argument
#   like in the following example:
#
#   save ""

# 在一段时间内进行了XX次操作就会持久化到文件.rdb .rof
# Redis是内存数据库,如果没有持久化,里面的数据断电即失。

# 900s内有1次操作
save 900 1
# 300s内有10次操作
save 300 10
# 60s内有10000次操作
save 60 10000

# 持久化失败时,Redis是否继续工作
stop-writes-on-bgsave-error yes

# 是否压缩RDB文件
rdbcompression yes

# RDB数据
rdbchecksum yes

1.7. REPLICATION

复制/主从复制

1.8. SECURITY(安全)

# 设置登录验证密码
requirepass 123456

1.9. APPEND ONLY MODE(AOF)

# 默认不开启AOF 使用RDB进行持久化
appendonly no

# aof持久化的文件名称
# The name of the append only file (default: "appendonly.aof")
appendfilename "appendonly.aof"

# 配置是否同步aof	
# appendfsync always	# 每次修改都会同步
appendfsync everysec	# 每秒同步
# appendfsync no		# 不同步

2. Redis持久化

  • RDB在性能上优于AOF
  • AOF在数据安全性优于RDB
  • 默认情况下,使用RDB

2.1. RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

  • RDB存储的是压缩数据
  • RDB保存文件的名称
# 配置文件中配置了RDB的保存文件名
dbfilename dump.rdb
  • RDB保存策略
save 900 1
save 300 10
save 60 10000

2.1.1. 测试RDB

  • 修改RDB保存策略
save 60 5
  • 删除原有的RDB文件
[root@Mashiro bin]# ls
dump.rdb  myredisconfig  redis-benchmark  redis-check-aof  redis-check-rdb  redis-cli  redis-sentinel  redis-server
[root@Mashiro bin]# rm dump.rdb
rm: remove regular file ‘dump.rdb’? y
[root@Mashiro bin]# ls
myredisconfig  redis-benchmark  redis-check-aof  redis-check-rdb  redis-cli  redis-sentinel  redis-server
  • 操作Redis (插入数据)
[root@Mashiro bin]# redis-cli -p 6379
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
3) OK
4) OK
5) OK
  • 查看bin目录
[root@Mashiro bin]# ls
dump.rdb  myredisconfig  redis-benchmark  redis-check-aof  redis-check-rdb  redis-cli  redis-sentinel  redis-server
# dumo.rdb出现了

2.1.2. RDB触发机制

  • 满足了save条件
  • 执行了FLUSHALL命令
  • 退出Redis

2.1.3. 如何恢复RDB数据

将dump.rdb文件放到redis的bin目录下

2.1.4. 优缺点

  • 优点
    • 适合大规模的数据恢复。
    • 对数据完整性要求不高。
  • 缺点
    • 需要一定时间间隔进行操作。如果在间隔时间内发送意外,可能会出现数据丢失
    • fork进程进行的时候,会占用一定的内存空间。

2.2. AOF

以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

  • 追加文件,记录所有写和删操作。
  • aof保存的文件名称
# 配置文件中配置了AOF的保存文件名
appendfilename "appendonly.aof"
  • aof的保存策略
# appendfsync always
appendfsync everysec
# appendfsync no
  • 默认情况下,aof不开启,如果需要,则将appendonly配置为yes即可。
appendonly no # yes

2.2.1. 测试AOF

  • 开启AOF服务
appendonly yes
  • 删除bin目录下的rdb文件
  • 启动redis
  • 查看bin目录
[root@Mashiro bin]# ls
appendonly.aof  redis-benchmark  redis-check-rdb  redis-sentinel
myredisconfig   redis-check-aof  redis-cli        redis-server

3. Redis发布订阅

  • 基本命令
# 订阅一个或多个符合给定模式的频道。
PSUBSCRIBE pattern [pattern ...]

# 查看订阅与发布系统状态。
PUBSUB subcommand [argument [argument ...]]

# 将信息发送到指定的频道。
PUBLISH channel message

# 退订所有给定模式的频道。
PUNSUBSCRIBE [pattern [pattern ...]]

# 订阅给定的一个或多个频道的信息。
SUBSCRIBE channel [channel ...]

# 指退订给定的频道。
UNSUBSCRIBE [channel [channel ...]]

3.1. 测试

  • 频道消息订阅
127.0.0.1:6379> PSUBSCRIBE Mashiro
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "Mashiro"
3) (integer) 1
# 频道订阅成功
  • 频道消息发布
127.0.0.1:6379> PUBLISH Mashiro "Hello world"
(integer) 1
# 频道消息发布
  • 订阅方自动获取消息
127.0.0.1:6379> PSUBSCRIBE Mashiro
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "Mashiro"
3) (integer) 1
# 获取刚刚发布方发布的信息
1) "pmessage"
2) "Mashiro"
3) "Mashiro"
4) "Hello world"

4. Redis主从复制

  • 查看配置信息
127.0.0.1:6379> info REPLICATION
# Replication
role:master
connected_slaves:0
master_replid:b787e7cb04e67dc4ca671e56a2b2c5ec7c6bfe64
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

4.1. Redis集群模拟(环境搭建)

  • 配置文件拷贝(一主二从)
[root@Mashiro myredisconfig]# ls
redis.conf
[root@Mashiro myredisconfig]# cp redis.conf redis-6379.conf 
[root@Mashiro myredisconfig]# cp redis.conf redis-6380.conf 
[root@Mashiro myredisconfig]# cp redis.conf redis-6381.conf 
[root@Mashiro myredisconfig]# ls
redis-6379.conf  redis-6380.conf  redis-6381.conf  redis.conf
  • 修改相应的配置文件
# redis-6379.conf
logfile "redis6379.log"
dbfilename dump6379.rdb

# redis-6380.conf
port 6380
pidfile /var/run/redis_6380.pid
logfile "redis6380.log"
dbfilename dump6380.rdb

# redis-6381.conf
port 6381
pidfile /var/run/redis_6381.pid
logfile "redis6381.log"
dbfilename dump6381.rdb
  • 启动集群
[root@Mashiro bin]# redis-server myredisconfig/redis-6379.conf 
[root@Mashiro bin]# redis-server myredisconfig/redis-6380.conf 
[root@Mashiro bin]# redis-server myredisconfig/redis-6381.conf 

[root@Mashiro bin]# ls
dump.rdb       redis6379.log  redis6381.log    redis-check-aof  redis-cli       redis-server
myredisconfig  redis6380.log  redis-benchmark  redis-check-rdb  redis-sentinel
# 三个Redis启动成功
[root@Mashiro bin]# ps -ef|grep redis
root     14794     1  0 15:10 ?        00:00:00 redis-server *:6379
root     14801     1  0 15:11 ?        00:00:00 redis-server *:6380
root     14808     1  0 15:12 ?        00:00:00 redis-server *:6381
root     14815 14695  0 15:12 pts/0    00:00:00 grep --color=auto redis

4.2. Redis一主二从配置

默认情况下每一台Redis都是主节点

  • 启动三台Redie的Cli
[root@Mashiro bin]# redis-cli -p 6379
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:b7305f55deff1254d41c535443a68f6cb5c87679
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

[root@Mashiro bin]# redis-cli -p 6380
127.0.0.1:6380> auth 123456
OK
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:0
master_replid:15ee54582ad276d2d5eb33fe8fd487325c774911
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

[root@Mashiro bin]# redis-cli -p 6381
127.0.0.1:6381> auth 123456
OK
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:0
master_replid:265b6a0279a8fc51bf93ada5d8b91b0fb50abf8c
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
  • 配置从机(一般情况下,只需要配置从机即可)
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6380> INFO replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:1619076037
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:15ee54582ad276d2d5eb33fe8fd487325c774911
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0


127.0.0.1:6381> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:1619076061
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:265b6a0279a8fc51bf93ada5d8b91b0fb50abf8c
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0


127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=84,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=84,lag=1
master_replid:a453013d69c6ce006e40abf29a2ff3068cc6b4ba
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:84

如果Redis配置了认证密码,则配置文件中需要额外配置master的密码 “masterauth 123456”

这里的配置使用的是命令配置【暂时的】,真实集群搭建,使用配置文件进行配置【永久的】

  • 细节
    • 主机可进行写和读操作
    • 从机只能进行读操作
    • 主机进行操作的同时,从机会自动进行保存

4.3. 测试主机宕机情况

  • 6379Shutdown
127.0.0.1:6379> shutdown
not connected> exit
  • 查看从机info信息
127.0.0.1:6380> info replication
# Replication
role:slave	# 依旧是从机
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1763
master_link_down_since_seconds:36
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:a453013d69c6ce006e40abf29a2ff3068cc6b4ba
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1763
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1763
  • 主机重新连接
[root@Mashiro bin]# redis-server myredisconfig/redis-6379.conf 
[root@Mashiro bin]# redis-cli -p 6379
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=14,lag=0
slave1:ip=127.0.0.1,port=6380,state=online,offset=14,lag=1
master_replid:1a0116cead6c3ef059b858a65564be338b35eee9
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
127.0.0.1:6379> set hello world
OK
  • 从机获取
127.0.0.1:6380> get hello
"world"

5. 哨兵模式

5.1. 环境搭建

  • 模拟Redis集群【一主二从】
# 6379 主机
[root@Mashiro bin]# redis-server myredisconfig/redis-6379.conf 
[root@Mashiro bin]# redis-cli -p 6379
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=26924,lag=0
slave1:ip=127.0.0.1,port=6380,state=online,offset=26924,lag=1
master_replid:1a0116cead6c3ef059b858a65564be338b35eee9
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:26924
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:26924

# 6380 从机1
[root@Mashiro bin]# redis-server myredisconfig/redis-6380.conf 
[root@Mashiro bin]# redis-cli -p 6380
127.0.0.1:6380> auth 123456
OK
127.0.0.1:6380> ping
PONG
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:26924
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:1a0116cead6c3ef059b858a65564be338b35eee9
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:26924
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:26924

# 6381 从机2
[root@Mashiro bin]# redis-server myredisconfig/redis-6381.conf 
[root@Mashiro bin]# redis-cli -p 6381
127.0.0.1:6381> auth 123456
OK
127.0.0.1:6381> ping
PONG
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:26910
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:1a0116cead6c3ef059b858a65564be338b35eee9
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:26910
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:26910

5.2. 编写哨兵配置

  • 进入配置目录
[root@Mashiro myredisconfig]# ls -l
total 336
-rw-r--r-- 1 root root     0 Apr 22 15:23 redis
-rw-r--r-- 1 root root 85579 Apr 22 15:09 redis-6379.conf
-rw-r--r-- 1 root root 85566 Apr 22 15:41 redis-6380.conf
-rw-r--r-- 1 root root 85565 Apr 22 15:28 redis-6381.conf
-rw-r--r-- 1 root root 85561 Apr 22 14:01 redis.conf
  • Vim sentinel.conf 【创建哨兵配置文件】
# sentinel monitor 被监控的名称 host port 
# 哨兵sentinel监控的redis主节点的 ip port   
# master-name  可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。  
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了  
# sentinel monitor    
sentinel monitor myredis 127.0.0.1 6379 1
sentinel auth-pass myredis 123456

  • 启动哨兵
[root@Mashiro bin]# redis-sentinel myredisconfig/sentinel.conf
# Result
15373:X 22 Apr 2021 21:32:57.278 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
15373:X 22 Apr 2021 21:32:57.278 # Redis version=6.0.10, bits=64, commit=00000000, modified=0, pid=15373, just started
15373:X 22 Apr 2021 21:32:57.278 # Configuration loaded
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.0.10 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 15373
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

15373:X 22 Apr 2021 21:32:57.279 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
15373:X 22 Apr 2021 21:32:57.280 # Sentinel ID is ffc5f60581397b313f9ebaeedfffcc0682899fbe
15373:X 22 Apr 2021 21:32:57.280 # +monitor master myredis 127.0.0.1 6379 quorum 1
15373:X 22 Apr 2021 21:32:57.281 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
15373:X 22 Apr 2021 21:32:57.286 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379

5.3. 模拟主机去世

127.0.0.1:6379> shutdown
not connected> exit
[root@Mashiro bin]# 

5.4. 查看从机info信息

# 从机6380篡位成功 成为主机
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=54031,lag=1
master_replid:4bd92e5a565b40d969768b4846e7bdb080ebf0bf
master_replid2:1a0116cead6c3ef059b858a65564be338b35eee9
master_repl_offset:54031
second_repl_offset:52925
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:54031

# 从机6381
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380	# 主机端口变成了6380
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:55101
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4bd92e5a565b40d969768b4846e7bdb080ebf0bf
master_replid2:1a0116cead6c3ef059b858a65564be338b35eee9
master_repl_offset:55101
second_repl_offset:52925
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:55101

5.5. 原主机原地复活

[root@Mashiro bin]# redis-server myredisconfig/redis-6379.conf 
[root@Mashiro bin]# redis-cli -p 6379
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> info replication
# Replication
role:slave	# 原来的主机复活之后 变成了从机
master_host:127.0.0.1
master_port:6380
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:1619098913
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:8c7e7eee73ce918f9e756cdb40fea62efa63da58
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

5.6. Redis哨兵模式的优缺点

  • 优点
    • 哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。
    • 主从可以自动切换,系统更健壮,可用性更高。
  • 缺点
    • Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
    • Redis哨兵模式,每个哨兵都是一个进程,占用系统资源。

6. Redis缓存穿透和雪崩

6.1. 缓存穿透

出现缓存穿透的原因

当用户请求查询一个数据时,缓存中没有这个数据(缓存Miss),于是就会去持久层数据库进行查询,此时持久层数据库也没有这个数据,本次查询失败。如果有大量的用户缓存Miss,则会给持久层数据库造成巨大压力,在分布式微服务环环相扣的架构体系中,可能会造成很严重的后果。

解决方案

方案1: 引入“布隆过滤器”

方案2:将用户查询的数据是一个空值,则将这个空值保存在缓存中(会占用内存空间,应该设置比较短暂的存在时间)

6.2. 缓存击穿

出现缓存击穿的原因

如果一个key有效时间到期,在失效的瞬间,大并发集中对这个点进行访问,持续的大并发就会击穿缓存,直接请求持久层数据库,给持久层数据库造成巨大压力。

解决方案

方案1:设置热点数据不过期

方案2:加入分布式锁

6.3. 缓存雪崩

出现缓存雪崩的原因

某一个时间段,缓存过期失效。Redis宕机。假设数据缓存一个小时,一个小时之后一大批数据过期,而此时对这批数据进行访问查询,都会直接访问到持久层数据库上,产生周期性的压力波峰。所有的请求到达持久层,持久层调用暴增,持久层就有可能会去世。

解决方案

方案1:高可用。增加拓展Redis集群数量。

方案2:限流降级。服务降级或者停止某些服务,给缓存提供充足的资源。当缓存失效之后,通过加锁或者队列来控制请求。

方案3:数据预热。在正式部署之前,讲可能的热点数据进行遍历,讲大量的访问数据提前加载到缓存中。在发生大并发访问之前,手动触发加载缓存不同的key,并错开过期时间 (设置不同的过期时间),讲大并发的请求分摊开来。

你可能感兴趣的:(笔记,Redis)