Nosql
Nosql = Not Only SQL
泛指非关系型数据库的,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0是滴啊,尤其是超大规模的高并发的社区,暴露出很多难以克服的问题,Nosql在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是当下必须要掌握的一门技术!很多数据类型用户的个人信息,社交网络,地理位置,这些数据类型的存储不需要一个固定的格式。
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE
- 高性能,高可用,高可拓
四大分类
KV键值对
- 新浪:Redis
- 美团:Redis + Tair
- BA : Redis + memecache
文档型数据库
- MongoDB
- MongoDB是一个基于分布式文件存储的数据库,c++编写,主要用来处理大量文档
- MongoDB是一个介于关系型数据库和非关系型数据中中间的产物,MongoDB是非关系型数据库中功能丰富,最像关系型数据库
- ConthDB
列存储数据库
- Hbase
- 分布式文件系统
图关系数据库
- 不是存放图形,存放的是关系,比如:朋友圈社交网络,广告推荐
- Neo4j,InfoGrid
Redis入门
概述
Redis能干吗
- 内存存储,持久化,内存是断电即失,所以说持久化很重要(rdb,aof)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器,计数器(微信,微博浏览量)
- …
Linux安装
-
下载安装包 https://redis.io/
-
解压Redis安装包到opt文件夹下
tar -zxvf redis-6.2.5.tar.gz
图1 解压redis到opt目录下
- 基本环境的安装
yum install gcc-c++
gcc -v // 检查版本确定安装成功
图2 检验yum install gcc-c++成功
make
make install
图3 检验make成功
- redis的默认安装路径是/usr/local/bin
图4 redis默认安装路径
将redis配置文件复制到当前目录下(这里在当前目录下创建一个目录用于存储配置文件)
图5 复制redis.conf所在位置
修改配置文件,将daemonize no
修改为daemonize yes
图6 设置daemonize yes
- 开启redis服务
图7 redis服务开启
redis-benchmark
redis-benchmark是一个压力测试工具
官方自带的性能测试工具
图8 redis-benchmark命令参数
- 开启redis服务
- 进入redis 的目录下
redis-benchmark命令是在 redis 的目录下执行的,而不是 redis 客户端的内部指令。
cd /usr/local/bin
- 执行命令
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000
图9 redis-benchmark压力测试结果
Summary:
throughput summary: 76045.62 requests per second
latency summary (msec):
avg min p50 p95 p99 max
0.736 0.264 0.711 1.055 1.423 3.087
====== INCR ======
100000 requests completed in 1.28 seconds # 对于100000个请求进行写入测试
100 parallel clients # 100个客户端
3 bytes payload # 每次写入三个字节
keep alive: 1 # 只有一台服务器来处理这些请求
host configuration "save": 3600 1 300 100 60 10000
host configuration "appendonly": no
multi-thread: no
Latency by percentile distribution:
0.000% <= 0.287 milliseconds (cumulative count 1)
50.000% <= 0.687 milliseconds (cumulative count 51545)
75.000% <= 0.783 milliseconds (cumulative count 76210)
87.500% <= 0.871 milliseconds (cumulative count 87931)
93.750% <= 0.959 milliseconds (cumulative count 93881)
96.875% <= 1.079 milliseconds (cumulative count 96882)
98.438% <= 1.223 milliseconds (cumulative count 98475)
99.219% <= 1.343 milliseconds (cumulative count 99252)
99.609% <= 1.463 milliseconds (cumulative count 99616)
99.805% <= 1.703 milliseconds (cumulative count 99805)
99.902% <= 1.975 milliseconds (cumulative count 99903)
99.951% <= 2.207 milliseconds (cumulative count 99952)
99.976% <= 2.383 milliseconds (cumulative count 99976)
99.988% <= 2.519 milliseconds (cumulative count 99988)
99.994% <= 2.591 milliseconds (cumulative count 99995)
99.997% <= 2.615 milliseconds (cumulative count 99997)
99.998% <= 2.639 milliseconds (cumulative count 99999)
99.999% <= 2.647 milliseconds (cumulative count 100000)
100.000% <= 2.647 milliseconds (cumulative count 100000)
Cumulative distribution of latencies:
0.000% <= 0.103 milliseconds (cumulative count 0)
0.002% <= 0.303 milliseconds (cumulative count 2)
0.074% <= 0.407 milliseconds (cumulative count 74)
5.577% <= 0.503 milliseconds (cumulative count 5577)
26.117% <= 0.607 milliseconds (cumulative count 26117)
56.678% <= 0.703 milliseconds (cumulative count 56678)
80.060% <= 0.807 milliseconds (cumulative count 80060)
90.774% <= 0.903 milliseconds (cumulative count 90774)
95.379% <= 1.007 milliseconds (cumulative count 95379)
97.271% <= 1.103 milliseconds (cumulative count 97271)
98.351% <= 1.207 milliseconds (cumulative count 98351)
99.009% <= 1.303 milliseconds (cumulative count 99009)
99.493% <= 1.407 milliseconds (cumulative count 99493)
99.672% <= 1.503 milliseconds (cumulative count 99672)
99.758% <= 1.607 milliseconds (cumulative count 99758)
99.805% <= 1.703 milliseconds (cumulative count 99805)
99.846% <= 1.807 milliseconds (cumulative count 99846)
99.888% <= 1.903 milliseconds (cumulative count 99888)
99.909% <= 2.007 milliseconds (cumulative count 99909)
99.933% <= 2.103 milliseconds (cumulative count 99933)
100.000% <= 3.103 milliseconds (cumulative count 100000)
基础知识
数据库
redis有16个数据库,默认使用第0个,可以使用select
进行切换
图10 redis.conf中显示redis数据库数量
127.0.0.1:6379> select 2 # 切换数据库
OK
127.0.0.1:6379> dbsize #查看数据库大小
(integer) 5
127.0.0.1:6379> flushdb # 清除当前数据库
127.0.0.1:6379> flushall # 清除所有数据库
127.0.0.1:6379> clear # 清屏
官网可以查看命令 https://redis.io/commands
Redis-Key
127.0.0.1:6379> keys * # 查看所有的键值
1) "counter:__rand_int__"
2) "myhash"
3) "mylist"
4) "name"
5) "key:__rand_int__"
127.0.0.1:6379> set name ccy # 存储键值对
OK
127.0.0.1:6379> get name # 获取键值对
"ccy"
127.0.0.1:6379> type name # 获取数据类型
string
127.0.0.1:6379> EXISTS name # 查看键值对是否存在
(integer) 1
127.0.0.1:6379> EXISTS myhash name
(integer) 2
127.0.0.1:6379> move name 1 # 移动key到某个数据库
127.0.0.1:6379> EXPIRE name 10 # 设置有效时间
(integer) 1
127.0.0.1:6379> ttl name # 显示剩余时间,当返回-2时代表时间到了
(integer) 2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> DEL name #删除某个键值
(integer) 0
五大数据类型
String
127.0.0.1:6379> get name"ccy"127.0.0.1:6379> APPEND name "ccy" # 追加字符串如果key不存在就相当于set key(integer) 6127.0.0.1:6379> STRLEN name # 获取字符串长度(integer) 6127.0.0.1:6379> set views 0OK127.0.0.1:6379> incr views # 增加1(integer) 1127.0.0.1:6379> get views"1"127.0.0.1:6379> decr views # 减少1(integer) 0127.0.0.1:6379> INCRBY views 10 # 设置增长量(integer) 10127.0.0.1:6379> DECRBY views 5 # 设置减少量(integer) 5127.0.0.1:6379> get views"5"127.0.0.1:6379> GETRANGE name 0 2 # 截取部分字符串"ccy"127.0.0.1:6379> GETRANGE name 0 -1 # 获取全部字符串"ccyccy"127.0.0.1:6379> SETRANGE name 3 zfs # 替换字符串(integer) 6127.0.0.1:6379> get name"ccyzfs"127.0.0.1:6379> setex key 60 "hello" # 设置过期时间OK127.0.0.1:6379> setnx mykey # 不存在设置(在分布式锁中常常使用)(integer) 0127.0.0.1:6379> setnx mykey "redis"(integer) 1127.0.0.1:6379> setnx mykey "jiuzhe" # 覆盖失败(integer) 0127.0.0.1:6379> get mykey"redis"127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 批量设置值OK127.0.0.1:6379> keys * 1) "myhash" 2) "views" 3) "mylist" 4) "name" 5) "key:__rand_int__" 6) "k1" 7) "k2" 8) "counter:__rand_int__" 9) "age"10) "k3"127.0.0.1:6379> mget k1 k2 k31) "v1"2) "v2"3) "v3"127.0.0.1:6379> msetnx k1 v1 k4 v4 # msetnx是原子性操作。一起成功一起失败(integer) 0set user:1 {name:zhangsan.age:18} # 设置一个user:1 对象值维json字符来保存对象 user:{id}:{filed}127.0.0.1:6379> mset user:1:name ccy user:1:age 21OK127.0.0.1:6379> mget user:1:name user:1:age1) "ccy"2) "21"127.0.0.1:6379> getset key "value" #先get后set(nil)127.0.0.1:6379> get key"value"127.0.0.1:6379> getset key "v1""value"127.0.0.1:6379> get key"v1"
String 类似的场景:value除了是我们的字符串还可以是数字
- 计数器
- 统计多单位数量
- 粉丝数
- 对象缓存存储
List
所有List命令都是以 ‘l’ 开头的
127.0.0.1:6379> LPUSH list one # 将一值或者多个值从列表的头部插入(integer) 1127.0.0.1:6379> LPUSH list two(integer) 2127.0.0.1:6379> LPUSH list three(integer) 3127.0.0.1:6379> LRANGE list 0 2 # 获取列表的值1) "three"2) "two"3) "one"127.0.0.1:6379> LRANGE list 0 -11) "three"2) "two"3) "one127.0.0.1:6379> RPUSH list zero # 将一个值或是多个值从列表的尾部插入(integer) 7127.0.0.1:6379> LRANGE list 0 -11) "six"2) "five"3) "four"4) "three"5) "two"6) "one"7) "zero"127.0.0.1:6379> LPOP list # 将一个值或多个值从头部移除"six"127.0.0.1:6379> RPOP list # 将一个值或多个值从尾部移除"zero"127.0.0.1:6379> LINDEX list 0 # 通过下标获得list中的某一个值"five"127.0.0.1:6379> LLEN list # 获取list长度(integer) 5#下面的实例list头部添加了"five"元素127.0.0.1:6379> LRANGE list 0 -11) "five"2) "five"3) "four"4) "three"5) "two"6) "one"127.0.0.1:6379> LREM list 1 one # 移除list集合中指定个数的value(integer) 1127.0.0.1:6379> LREM list 2 five(integer) 2#127.0.0.1:6379> LTRIM list 0 2 # 通过下标截取移除指定的长度OK127.0.0.1:6379> LRANGE list 0 -11) "five"2) "four"3) "three"4) "two"5) "one"127.0.0.1:6379> RPOPLPUSH list otherlist # 从尾部弹出元素移动到另一个list"one"#127.0.0.1:6379> LRANGE otherlist 0 -1 1) "one127.0.0.1:6379> LSET otherlist 0 two # 如果存在则会更新当前下标的值,如果不存在则会报错OK127.0.0.1:6379> LRANGE otherlist 0 -11) "two"#127.0.0.1:6379> LRANGE otherlist 0 -11) "two"2) "one"127.0.0.1:6379> LINSERT otherlist before one 1.5 # 插入元素(integer) 3127.0.0.1:6379> LRANGE otherlist 0 -11) "two"2) "1.5"3) "one
-
如果key不存在创建新的链表
-
如果key存在,新增内容
-
如果移除了所有的值,空链表也代表不存在
-
在两边插入或者改动值效率最高,中间元素效率稍微低一点
应用场景:消息队列(Lpush Rpop),栈(Lpush Lpop)
Set
set命令基本是以’s’开头
127.0.0.1:6379> SADD myset one # 添加set元素(integer) 1127.0.0.1:6379> SMEMBERS myset # 查看所有set元素1) "two"2) "four"3) "three"4) "one"127.0.0.1:6379> SISMEMBER myset one # 查看set中存在元素(integer) 1127.0.0.1:6379> SISMEMBER myset five(integer) 0127.0.0.1:6379> SCARD myset # 统计set中元素个数(integer) 4127.0.0.1:6379> SREM myset one three # 移除set中元素(integer) 2127.0.0.1:6379> SRANDMEMBER myset # 随机抽选任意个数元素"four"127.0.0.1:6379> SRANDMEMBER myset"two127.0.0.1:6379> SRANDMEMBER myset 21) "two"2) "four"127.0.0.1:6379> SPOP myset 1 # 随机移除元素1) "four"127.0.0.1:6379> SMOVE myset myset1 five # 移动指定元素(integer) 1# 数字集合类 - 差集 SDIFF - 并集 SUNION - 交集 SINTER127.0.0.1:6379> SMEMBERS myset1) "four"2) "three"3) "one"4) "two"127.0.0.1:6379> SMEMBERS myset11) "five"2) "two"3) "one"127.0.0.1:6379> SDIFF myset myset11) "three"2) "four"127.0.0.1:6379> SUNION myset myset11) "one"2) "five"3) "three"4) "two"5) "four"127.0.0.1:6379> SINTER myset myset11) "two"2) "one"
共同关注,共同爱好,二度好友,推荐好友
Hash
Map集合,key-map
127.0.0.1:6379[1]> HSET myhash field ccy # 存储字段-value(integer) 1127.0.0.1:6379[1]> HGET myhash field # 获得对应字段的value"ccy"127.0.0.1:6379[1]> HMSET myhash field1 hello field2 world # 批量存储OK127.0.0.1:6379[1]> HMGET myhash field1 field2 # 批量获取1) "hello"2) "world"127.0.0.1:6379[1]> HGETALL myhash # 获得hash表中全部的数据包括字段和value1) "field"2) "ccy"3) "field1"4) "hello"5) "field2"6) "world127.0.0.1:6379[1]> HDEL myhash field # 删除字段同时也删除了对应的value(integer) 1127.0.0.1:6379[1]> HGETALL myhash1) "field1"2) "hello"3) "field2"4) "world127.0.0.1:6379[1]> HLEN myhash # 获取hash表的字段数量(integer) 2 127.0.0.1:6379[1]> HEXISTS myhash field1 # 判断hash表中在指定字段(integer) 1127.0.0.1:6379[1]> HEXISTS myhash field3(integer) 0127.0.0.1:6379[1]> HKEYS myhash # 获取hash表中的字段1) "field1"2) "field2"127.0.0.1:6379[1]> HVALS myhash # 获取hash表中的值1) "hello"2) "world"127.0.0.1:6379[1]> HSET myhash field3 6(integer) 1127.0.0.1:6379[1]> HINCRBY myhash field3 1 # 设置增量可以为负数(integer) 7
hash存储变更的数据,尤其是用户信息之类的,经常变动的数据,hash适用于对象的存储
Zset
有序集合,在set的基础上增加了一个值
127.0.0.1:6379[1]> ZADD myset 1 one # 添加一个score的set元素(integer) 1127.0.0.1:6379[1]> ZADD myset 2 two(integer) 1127.0.0.1:6379[1]> ZADD myset 4 four 3 three(integer) 2127.0.0.1:6379[1]> ZRANGE myset 0 -1 # 按照socre升序排列1) "one"2) "two"3) "three"4) "four"127.0.0.1:6379[1]> ZRANGEBYSCORE myset -inf +inf # 按照score升序排序 inf代表无穷1) "one"2) "two"3) "three"4) "four"127.0.0.1:6379[1]> ZREVRANGE myset 0 -1 # 按照socre降序排列1) "four"2) "three"3) "two"4) "one"127.0.0.1:6379[1]> ZCARD myset # 获取元素数量(integer) 4127.0.0.1:6379[1]> ZCOUNT myset 1 4 # 获得指定区间的数量(integer) 4
set排序,存储班级成绩,工资表;普通消息,1.重要消息,2.带权重进行判断;排行榜应用实现,取Top N 测试
三种特殊数据类型
Geospatial
数据类型是zset。地理位置,朋友的定位,附近的人,打车距离计算
图11 Geospatial方法
GEOADD 添加经度纬度
127.0.0.1:6379[2]> GEOADD china:city 112.551 37.893 beijing(integer) 1127.0.0.1:6379[2]> GEOADD china:city 121.445 31.213 shanghai(integer) 1127.0.0.1:6379[2]> GEOADD china:city 108.969 34.285 xian(integer) 1
GEODIST 返回由排序集表示的地理空间索引中两个成员之间的距离
127.0.0.1:6379[2]> GEODIST china:city beijing shanghai"1101632.0359"127.0.0.1:6379[2]> GEODIST china:city beijing shanghai km"1101.6320"
GEOHASH 返回一份位置或者多个位置元素用11位的hash表示
127.0.0.1:6379[2]> GEOHASH china:city beijing1) "ww8p94jnsr0"
GEOPOS 返回由key处的排序集表示的地理空间索引的所有指定成员的位置(经度、纬度)
127.0.0.1:6379[2]> GEOPOS china:city beijing xian1) 1) "112.55100220441818237" 2) "37.89300027354286016"2) 1) "108.96899789571762085" 2) "34.28499959898385896
GEORADIUS 以给定的经纬度为中心,找出某半径内的元素
127.0.0.1:6379[2]> GEORADIUS china:city 110 30 500 km1) "xian"127.0.0.1:6379[2]> GEORADIUS china:city 110 30 500 km withcoord1) 1) "xian" 2) 1) "108.96899789571762085" 2) "34.28499959898385896"127.0.0.1:6379[2]> GEORADIUS china:city 110 30 500 km withdist1) 1) "xian" 2) "486.3850"
GEORADIUSBYMEMBER 指定成员的位置用作查询的中心
127.0.0.1:6379[2]> GEORADIUSBYMEMBER china:city beijing 1000 km1) "beijing"2) "xian
GEOSEARCH 支持在矩形区域内搜索代替GEORADIUS,功能比GEORADIUS更强大
127.0.0.1:6379[2]> GEOSEARCH china:city FROMLONLAT 110 37 BYBOX 1000 1000 km ASC1) "beijing"2) "xian"127.0.0.1:6379[2]> GEOSEARCH china:city FROMLONLAT 110 37 BYRADIUS 5000 km ASC1) "beijing"2) "xian"3) "shanghai"
此命令类似于GEOSEARCH,但将结果存储在目标键中。
127.0.0.1:6379[2]> GEOSEARCHSTORE key china:city FROMLONLAT 110 37 BYBOX 1000 1000 km ASC(integer) 2
Hyperloglog
基数不重复的元素
A{1,3,5,7,8,7} 基数 5
B1,3,5,7,8} 基数 5
Hyperloglog 基数统计算法 优点:占用的内存固定且小;缺点:有出错率
网页UV(一个人访问一个网站多次,但还是算作同一个人)
传统方式:set保存用户的id,然后就可以统计set中的元素类作为标准判断
127.0.0.1:6379[2]> PFADD hyperloglog a b c d e f g h i j # 添加元素(integer) 1127.0.0.1:6379[2]> PFCOUNT hyperloglog # 计数(integer) 10127.0.0.1:6379[2]> PFADD hyperloglog1 i j z x c v b n m(integer) 1127.0.0.1:6379[2]> PFCOUNT hyperloglog1(integer) 9127.0.0.1:6379[2]> PFMERGE hyperloglog3 hyperloglog hyperloglog1 # 合并基数OK127.0.0.1:6379[2]> PFCOUNT hyperloglog3(integer) 15
Bitmap
位存储 位图 Bitmap和Hyperloglog都是一种数据结构
统计用户信息,活跃,不活跃,登录,未登录,打卡
业务场景:打卡
################################# 按位存储,0代表未打卡,1代表打卡,若想得到打卡天数只需要记录1的个数即可127.0.0.1:6379[2]> SETBIT sign 0 1 # 按位存储(integer) 0127.0.0.1:6379[2]> SETBIT sign 1 0(integer) 0127.0.0.1:6379[2]> SETBIT sign 2 0(integer) 0127.0.0.1:6379[2]> SETBIT sign 3 1(integer) 0127.0.0.1:6379[2]> SETBIT sign 4 1(integer) 0127.0.0.1:6379[2]> SETBIT sign 5 0(integer) 0127.0.0.1:6379[2]> SETBIT sign 6 0(integer) 0127.0.0.1:6379[2]> GETBIT sign 2 # 查询周三有没有打卡(integer) 0127.0.0.1:6379[2]> GETBIT sign 3 # 查询周四有没有打卡(integer) 1 127.0.0.1:6379[2]> BITCOUNT sign # 统计打卡情况(integer) 3
Redis事务
redis事务本质:一组命令的集合!一个事务中所有命令都会被序列化,在事务执行过程中,会被按顺序执行
一次性,顺序性,排他性,执行一系列的命令
- 开启事务(MULTI)
- 命令入队
- 执行事务(EXEC)
Redis单条命令保证原子性,但是事务不保证原子性。
Redis事务没有隔离级别的概念
127.0.0.1:6379[3]> MULTI # 开启事务OK127.0.0.1:6379[3](TX)> set k1 v1 #命令入队QUEUED127.0.0.1:6379[3](TX)> set k2 v2QUEUED127.0.0.1:6379[3](TX)> set k3 v3QUEUED127.0.0.1:6379[3](TX)> get k1QUEUED127.0.0.1:6379[3](TX)> get k2QUEUED127.0.0.1:6379[3](TX)> get k3QUEUED127.0.0.1:6379[3](TX)> set k4 v4QUEUED127.0.0.1:6379[3](TX)> EXEC # 执行事务1) OK2) OK3) OK4) "v1"5) "v2"6) "v3"7) OK#####################################127.0.0.1:6379[3]> MULTIOK127.0.0.1:6379[3](TX)> set k5 v5QUEUED127.0.0.1:6379[3](TX)> DISCARD # 取消事务OK127.0.0.1:6379[3]> get k5 # 事务队列中的命令都不会被执行(nil)#####################################编译型异常(命令有问题),事务中所有的命令都不会被执行127.0.0.1:6379[3]> MULTI # 开启事务OK127.0.0.1:6379[3](TX)> set k1 v1QUEUED127.0.0.1:6379[3](TX)> set k2 v2QUEUED127.0.0.1:6379[3](TX)> set k3 v3QUEUED127.0.0.1:6379[3](TX)> getset k3(error) ERR wrong number of arguments for 'getset' command # 编译型错误127.0.0.1:6379[3](TX)> set k4 v4QUEUED127.0.0.1:6379[3](TX)> EXEC # 执行事务(error) EXECABORT Transaction discarded because of previous errors.127.0.0.1:6379[3]> get k4 # 验证(nil)#####################################运行时异常(出现类似1/0的错误),如果事务队列中存在语法性,那么执行命令的时候,其他命令可以正常执行127.0.0.1:6379[3]> set k1 v1OK127.0.0.1:6379[3]> type k1string127.0.0.1:6379[3]> MULTIOK127.0.0.1:6379[3](TX)> INCR k1QUEUED127.0.0.1:6379[3](TX)> set k2 v2QUEUED127.0.0.1:6379[3](TX)> EXEC1) (error) ERR value is not an integer or out of range2) OK127.0.0.1:6379[3]> get k2"v2"
Redis监视测试
127.0.0.1:6379[3]> set money 100OK127.0.0.1:6379[3]> set out 0OK127.0.0.1:6379[3]> watch money // 监控键值'money'OK127.0.0.1:6379[3]> MULTI // 开启事务OK127.0.0.1:6379[3](TX)> DECRBY money 20QUEUED127.0.0.1:6379[3](TX)> INCRBY out 20QUEUED127.0.0.1:6379[3](TX)> EXEC1) (integer) 802) (integer) 20
测试多线程修改值,使用watch 可以当作redis的乐观锁的操作
# 第一个线程执行事务127.0.0.1:6379[3]> WATCH moneyOK127.0.0.1:6379[3]> MULTIOK127.0.0.1:6379[3](TX)> DECRBY money 10QUEUED127.0.0.1:6379[3](TX)> INCRBY out 10QUEUED# 第二个线程执行事务127.0.0.1:6379[3]> MULTIOK127.0.0.1:6379[3](TX)> set money 1000 // 修改此时watch的值QUEUED127.0.0.1:6379[3](TX)> EXEC1) OK# 第一个线程继续执行事务127.0.0.1:6379[3](TX)> EXEC // 执行事务失败(nil)127.0.0.1:6379[3]> UNWATCH // 取消监视OK127.0.0.1:6379[3]> WATCH money // 重新监视OK127.0.0.1:6379[3]> MULTIOK127.0.0.1:6379[3](TX)> DECRBY money 10QUEUED127.0.0.1:6379[3](TX)> INCRBY out 10QUEUED127.0.0.1:6379[3](TX)> EXEC1) (integer) 9902) (integer) 30
Jedis
Jedis是Redis官方推荐的java连接工具
- 导入测试
<dependencies> <!-- 导入jedis的maven依赖--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> </dependencies>
- 开启本地redis
图12 开启本地redis服务
public class TestPing { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); System.out.println(jedis.ping()); // 测试结果为PONG表示连接成功 }}
SpringBoot整合
-
导入maven依赖
-
配置文件
spring.redis.host=127.0.0.1spring.redis.port=6379
- 启动redis
- 执行测试用例
@Autowired private RedisTemplate redisTemplate; @Test void contextLoads() { redisTemplate.opsForValue().set("k1", "value1"); redisTemplate.opsForValue().set("k2", "value2"); System.out.println(redisTemplate.opsForValue().get("k1")); System.out.println(redisTemplate.opsForValue().get("k2")); }
序列化,不执行序列化redis键值对在redis不是以key
表示
- 创建序列化对象
@Component@AllArgsConstructor@NoArgsConstructor@Datapublic class User implements Serializable { private String name; private int age;}
- 覆盖RedisAutoConfiguration
@Configurationpublic class RedisConfig { @Bean @SuppressWarnings("all") public RedisTemplate<String, Object> redisTemplate1(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om);// String序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();// key采用string的序列化方式 template.setKeySerializer(stringRedisSerializer);// hash的key也采用String的序列化 template.setHashKeySerializer(stringRedisSerializer);// value的序列化采用jackson template.setValueSerializer(jackson2JsonRedisSerializer);// hash的value序列化采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; }}
- 测试用例
@Autowired @Qualifier("redisTemplate1") private RedisTemplate redisTemplate; @Test public void test() throws JsonProcessingException {// 真实的开发一般使用Json传递对象 User user = new User("ccy", 3);// String str = new ObjectMapper().writeValueAsString(user); redisTemplate.opsForValue().set("user", user); System.out.println(redisTemplate.opsForValue().get("user")); }
Redis.conf详解
网络
bind 127.0.0.1 -::1 # 绑定ipprotected-mode yes # 保护模式port 6379 # 端口号
通用
daemonize yes # 以守护进程的方式进行,默认是no,我们需要手动开启pidfile /var/run/redis_6379.pid # 如果以后台的方式运行,我们就需要执行一个pidloglevel notice # 日志级别logfile "" # 日志的文件位置名databases 16 # 默认的数据库数量always-show-logo no # 是否总是显示logo
快照
持久化,在规定的时间内,执行了多少次操作,则会持久到文件,rdb.aof
redis是内存数据库,如果没有数据库,那么数据断电即失
# 如果在3600s内,如果有1个 key进行了修改,就进行持久化操作save 3600 1# 如果在300s内,如果有100个 key进行了修改,就进行持久化操作save 300 100# 如果在60s内,如果有10000个 key进行了修改,就进行持久化操作save 60 10000stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作rdbcompression yes # 是否压缩rdb文件rdbchecksum yes # 保存rdb文件的时候进行错误校验dir ./ # rdb 保存的目录
REPLICATION 主从复制
SECURITY 安全
# requirepass foobared redis密码127.0.0.1:6379> PINGPONG127.0.0.1:6379> config get requirepass1) "requirepass"2) ""127.0.0.1:6379> config set requirepass "123" # 设置密码OK# 重新启动127.0.0.1:6379> config get requirepass # 密码限制,指令失效(error) NOAUTH Authentication required.127.0.0.1:6379> AUTH 123 # 密码验证OK127.0.0.1:6379> config get requirepass1) "requirepass"2) "123"
CLIENTS 客户端限制
maxclients 10000 # 设置能连接上redis的最大客户端数量maxmemory <bytes> # 配置最大的内存容量maxmemory-policy noeviction # 内存到达上限的处理策略
APPEND ONLY MODE AOF
appendonly no # 默认aof不开启appendfilename "appendonly.aof" # 持久化文件名称# appendfsync always # 每次修改都会 sync,消耗性能appendfsync everysec # 每秒执行一次,可能会丢失ls的数据# appendfsync no # 不执行 sync 这个时候操作系统会自动同步数据速度很快
Redis持久化
redis是内存数据库,断电即失,如果不将内存中的数据库状态保存在磁盘里,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以redis提供了持久化操作
RDB
图13 RDB
触发机制
- save保存快照,会自动触发rdb规则
127.0.0.1:6379> saveOK
- 执行flushall命令,也会触发rdb规则
127.0.0.1:6379> flushallOK
- 退出redis,也会产生rdb规则
127.0.0.1:6379> SHUTDOWNnot connected> exit
- 满足redis.confi文件
127.0.0.1:6379> set k1 v1 # 满足本例中设定的60s修改5个数据OK127.0.0.1:6379> set k2 v2OK127.0.0.1:6379> set k3 v3OK127.0.0.1:6379> set k4 v4OK127.0.0.1:6379> set k5 v5OK
恢复rdb文件
- 只需要将rdb文件放在redis启动目录就可以,redis启动时会自动检查rdb恢复其中的数据
- 查看启动目录
127.0.0.1:6379> config get dir1) "dir"2) "/usr/local/bin" # 如果在这个目录下存在rdb文件那么就会在redis启动的时候恢复数据,默认就在这个路径中
优点
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点
- 需要一定的时间间隔操作,如果redis意外宕机了,最后一个持久化后的数据就没有了
- fork进程的时候,会占用内存空间
演示
- 修改redis.conf文件
图14 修改redis.conf文件示例
- 清除rdb文件
[root@iZ2ze25611z7hnx7plpz2nZ bin]# rm -rf dump.rdb
- 触发rdb(参考触发机制)
AOF
图15 AOF
优点
- 每一次修改都同步,文件的完整性
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
缺点
- 相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢
- aof的运行效率也比rdb慢,所以redis默认的配置就是rdb持久化
演示
- 修改redis.conf文件
图16 修改AOF配置
- 重启redis
127.0.0.1:6379> SHUTDOWNnot connected> exit[root@iZ2ze25611z7hnx7plpz2nZ bin]# ps -ef|grep redisroot 12070 11929 0 15:31 pts/0 00:00:00 grep --color=auto redis[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-server kconfig/redis.conf [root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p6379 127.0.0.1:6379> set k1 v1OK127.0.0.1:6379> set k2 v2OK127.0.0.1:6379> set k3 v3OK
- 重启redis,bin目录下会出现appendonly.aof
图17 AOF文件以及文件内容
- 修改appendonly.aof检测redis-check-aof作用
# 修改appendonly.aof文件以后redis服务会无法启动[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p6379Unrecognized option or bad number of args for: '-p6379'# 使用redis-check-aof修复被修改的文件[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-server kconfig/redis.conf [root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p 6379
扩展
- rdb持久化方式能够在指定的时间间隔内对数据进行快照存储
- aof持久化方式记录每次对服务器写的操作,服务器重启时,重新执行命令来恢复原始数据,追加在文件末尾,能对aof文件进行重写,避免体积过大
- 如果只做缓存不需要使用任何持久化
- 同时开启两种持计划方式
- 重启时优先载入aof文件来恢复数据
- 只会找aof文件,但是推荐只使用rdb用于备份,能快速重启,并且不会有aof可能潜在的bug
- 性能建议
- rdb文件只做后备用途,建议只在save上持久化rdb文件,15分钟备份一次,使用save 900 1 规则
- 使用aof,即便在最恶劣的环境下也不会丢失超过2秒的数据
- 代价:持续的io
- rewrite 过程中产生的新数据写到新文件造成的阻塞不可避免,尽量减少rewrite的频率
- 不使用aof,也可以通过Master-Slave Replication 实现高可用性也可以,能省去一大笔io,减少rewrite带来的系统波动
- 代价:如果Master-Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较Master-Slave中的rdb文件,选择最新的文件,载入新的,微博就是这种架构
Redis发布订阅
redis发布订阅(pub/sub)是一种消息通道模式:发送者(pub)发送信息,订阅者(sub)接受信息。redis客户端可以订阅任意数量的频道
图18 Redis发布订阅
原理
图19 Redis发布订阅原理
方法
图20 订阅发布方法
应用场景
- 实时消息系统
- 事实聊天(频道当作聊天室,将信息回显给所有人即可)
- 订阅,关注系统都是可以的
演示
- 订阅端
Reading messages... (press Ctrl-C to quit)1) "subscribe"2) "ccy"3) (integer) 1
- 发送端
127.0.0.1:6379> PUBLISH ccy "hello,ccy"(integer) 1# 此时订阅端另一个redis进程127.0.0.1:6379> SUBSCRIBE ccyReading messages... (press Ctrl-C to quit)1) "subscribe"2) "ccy"3) (integer) 11) "message"2) "ccy"3) "hello,ccy"
Redis主从复制
一个Master有多个slave,将一台redis服务器数据,复制到其他的redis服务器,前者称为主节点(masterleader),后者称为从节点(slave、follower),数据是单向的,只能从主节点到从节点,Master以写为主,Slave以读为主
默认情况下,每台redis服务器都是主节点,一个Master可以有多少Slave或没有从节点,一个从节点只能有一个主节点
主从复制作用
- 数据冗余
- 实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复
- 主节点出现问题,从节点可以提供服务,实现快速的故障恢复,实际上是一种服务的冗余
- 负载均衡
- 在主从复制的基础上,配合读写分离,主节点提供写服务,从节点提供读服务,写redis数据时连接主节点,读redis数据连接从节点,分担服务器负载,尤其在写少读多的场景下通过,多个从节点分担负载,可以提高redis性能
- 高可用(集群)基石
- 哨兵、集群,能够实施的基础,主从复制时高可用的基础
不能只使用一台redis的原因
- 从结构上讲,单个redis服务器会发生单点故障,一台服务器需要处理所有请求,压力大
- 从容量上讲,单个redis服务器内存容量有限,并且不能完全使用全部的内存,单台redis的最大内存不应该超过20g压力过大
图21 主从复制
演示
本例中使用三个redis服务器
- 配置文件
- daemonize:yes
- port:6379/6380/6381
- pidfile
- logfile
- dbfilename
- 启动redis服务
# 第一台服务器[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-server kconfig/redis79.conf [root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p 6379# 第二台服务器[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-server kconfig/redis80.conf [root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p 6380# 第三台服务器[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-server kconfig/redis81.conf [root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p 6381
启动服务后,每个服务器都是独立存在的都是master
节点
127.0.0.1:6379> info replication # 查看配置信息# Replicationrole:masterconnected_slaves:0master_failover_state:no-failovermaster_replid:f75fe68da13dbd2aa94c7673ea0891315f5dbae8master_replid2:0000000000000000000000000000000000000000master_repl_offset:0second_repl_offset:-1repl_backlog_active:0repl_backlog_size:1048576repl_backlog_first_byte_offset:0repl_backlog_histlen:0
- 一主二从(一般都是从机连接主机)
# 第二台服务器127.0.0.1:6380> SLAVEOF 127.0.0.1 6379OK# 第三台服务器127.0.0.1:6381> SLAVEOF 127.0.0.1 6379OK
连接以后主机和从机配置信息发生改变
# 第一台服务器(主机)127.0.0.1:6379> info replication# Replicationrole:masterconnected_slaves:2slave0:ip=127.0.0.1,port=6380,state=online,offset=140,lag=0slave1:ip=127.0.0.1,port=6381,state=online,offset=140,lag=0master_failover_state:no-failovermaster_replid:11c40313ea11a9e81f6e83faef8e5081a0ebd409master_replid2:0000000000000000000000000000000000000000master_repl_offset:140second_repl_offset:-1repl_backlog_active:1repl_backlog_size:1048576repl_backlog_first_byte_offset:1repl_backlog_histlen:140# 第二、三台服务器(从机)127.0.0.1:6380> info replication# Replicationrole:slavemaster_host:127.0.0.1master_port:6379master_link_status:upmaster_last_io_seconds_ago:5master_sync_in_progress:0slave_repl_offset:0slave_priority:100slave_read_only:1replica_announced:1connected_slaves:0master_failover_state:no-failovermaster_replid:11c40313ea11a9e81f6e83faef8e5081a0ebd409master_replid2:0000000000000000000000000000000000000000master_repl_offset:0second_repl_offset:-1repl_backlog_active:1repl_backlog_size:1048576repl_backlog_first_byte_offset:1repl_backlog_histlen:0
注意
- 真实的主从配置要在配置文件中配置,在redis-cli中配置的是暂时的,配置在redis.conf文件中replication区块下
- 配置文件设置好,启动时就不用重新设置
- 根据读写分离的原则,从机只能读
# 第一台服务器(主机)127.0.0.1:6379> keys *(empty array)127.0.0.1:6379> set k1 v1OK# 第二台服务器(从机)127.0.0.1:6380> get k1"v1"127.0.0.1:6380> set k2 v2 # 从机无法写(error) READONLY You can't write against a read only replica.
- 在没配置哨兵的情况下,当master崩溃了,slave还是slave
- 在没有设置哨兵的情况下,主机崩溃了,从机仍然能读取
- 从机崩溃,主机继续写入数据,从机恢复,只要变为从机就会立马从主机中获取值
复制原理
-
slave启动成功连接到master后会发送一个sync同步命令
-
master接到命令,启动后台的存盘进程,同时收集所接收到的用于修改数据集命令,后台执行完毕之后,master将传送整个数据文件到slave,并完成一次同步,成为增量复制
-
全量复制
- slave服务在接受到数据库文件数据后,将其存盘并加载到内存中
-
增量复制
- master继续将新的所有收集到的修改命令依次传给slave,完成同步
-
只要重新连接master,一次完全同步(全量复制)将被自动执行,数据一定能在从机中看到
结构
图22 主从复制结构
链式结构的slave81 点master设置为slave 80 ,实现的功能也是一样,当master 79 崩溃,slave 80也不会变成master
哨兵模式
概述切换技术的方法是,当master服务器宕机后,需要人工切换,费事,更多时候选择优先考虑是哨兵模式,redis2.8 开始正确提供sentinel(哨兵 )
能够监控后台的主机是否故障,根据投票自动将从库专为主库
哨兵模式是一种特殊模式,哨兵是一个独立的进程,作为进程独立运行,原理是哨兵通过发送命令,等待redis服务器响应,从而监控多个redis实例
当主机宕机后手动配置从机变为主机
slaveof no one
此时master恢复后使用slaveof no one 的主机也还会继续当master,要重新作为slave只能重新配置
单哨兵模式
图23 单哨兵模式
当哨兵模式检测到master宕机,会自动将slave切换成master,通过发布订阅模式通知其他的从服务器,修改配置文件,让他切换主机
多哨兵模式
假设master宕机,sentinel先检测到这个结果,系统并不会马上进行failover(故障切换、失效备援)这个现象称为主观下线,当后面的哨兵也检测到主服务器不可用,sentinel之间会发起一次投票,投票的结果由随机一个sentinel发起,进行failover操作,得到sentinel票数多的slave能成功切换为master,切换成功后,通过发布订阅模式,让各个哨兵把自己监控的服务器实现切换主机,这个过程称为客观下线
图24 多哨兵模式
演示
- 配置sentinel.conf文件
[root@iZ2ze25611z7hnx7plpz2nZ kconfig]# vim sentinel.confsentinel monitor myredis 127.0.0.1 6380 1 # 编写配置信息
- 运行配置文件
# 此时处于配置文件的目录
[root@iZ2ze25611z7hnx7plpz2nZ kconfig]# cd .. # 退回到上一级因为上一级存在启动文件
[root@iZ2ze25611z7hnx7plpz2nZ bin]# ls
6379.log cloud-id dump6381.rdb jsondiff pcre-config redis-check-rdb
6380.log cloud-init dump.rdb jsonpatch pcregrep redis-cli
6381.log cloud-init-per easy_install jsonpointer pcretest redis-sentinel
appendonly.aof dump6379.rdb easy_install-3.6 jsonschema redis-benchmark redis-server
chardetect dump6380.rdb easy_install-3.8 kconfig redis-check-aof
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-sentinel kconfig/sentinel.conf # 根据配置文件启动哨兵模式
4314:X 23 Aug 2021 10:02:46.060 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
4314:X 23 Aug 2021 10:02:46.060 # Redis version=6.2.5, bits=64, commit=00000000, modified=0, pid=4314, just started
4314:X 23 Aug 2021 10:02:46.060 # Configuration loaded
4314:X 23 Aug 2021 10:02:46.061 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.5 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 4314
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
4314:X 23 Aug 2021 10:02:46.061 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
4314:X 23 Aug 2021 10:02:46.072 # Sentinel ID is 2089f9ac541a0710f19390a54331aceffbdf93d5
4314:X 23 Aug 2021 10:02:46.072 # +monitor master myredis 127.0.0.1 6379 quorum 1
4314:X 23 Aug 2021 10:02:46.073 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.165 # +sdown master myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.165 # +odown master myredis 127.0.0.1 6379 #quorum 1/1
4314:X 23 Aug 2021 10:04:32.165 # +new-epoch 1
4314:X 23 Aug 2021 10:04:32.165 # +try-failover master myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.170 # +vote-for-leader 2089f9ac541a0710f19390a54331aceffbdf93d5 1
4314:X 23 Aug 2021 10:04:32.170 # +elected-leader master myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.170 # +failover-state-select-slave master myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.271 # +selected-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.271 * +failover-state-send-slaveof-noone slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.329 * +failover-state-wait-promotion slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.811 # +promoted-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.811 # +failover-state-reconf-slaves master myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.888 # +failover-end master myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.888 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6380
4314:X 23 Aug 2021 10:04:32.888 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
4314:X 23 Aug 2021 10:04:33.183 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6380
- 作用演示
# 第一台服务器(主机)关机,此时从机失去主机
127.0.0.1:6379> SHUTDOWN
not connected> exit
# 哨兵模式启动,自动投票选出6380端口为主机
4314:X 23 Aug 2021 10:05:02.949 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
4314:X 23 Aug 2021 10:20:50.173 # -sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
4314:X 23 Aug 2021 10:21:00.097 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 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=60671,lag=1
master_failover_state:no-failover
master_replid:b5fa57ab460ed278d5b56ac381e48a25c83d77bb
master_replid2:11c40313ea11a9e81f6e83faef8e5081a0ebd409
master_repl_offset:60803
second_repl_offset:9411
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:60803
# 重新开启原先的主机也就是第一台服务器,无法恢复为主机而是作为从机连接在现在的主机上
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-server kconfig/redis79.conf
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:75800
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:b5fa57ab460ed278d5b56ac381e48a25c83d77bb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:75800
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:74826
repl_backlog_histlen:975
# 第一台服务器重新启动后,主机(第二台服务器)多了一个从机
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=182056,lag=0
slave1:ip=127.0.0.1,port=6379,state=online,offset=182056,lag=0
master_failover_state:no-failover
master_replid:b5fa57ab460ed278d5b56ac381e48a25c83d77bb
master_replid2:11c40313ea11a9e81f6e83faef8e5081a0ebd409
master_repl_offset:182188
second_repl_offset:9411
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:182188
优点
- 基于集群,基于主从复制,所有的主从配置的优点,它全有
- 主从可以切换,故障可以切换,系统的可用性提高
- 哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点
- redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
- 哨兵模式需要很多配置,多哨兵,多端口配置复杂,一般由运维来配置
缓存穿透、击穿、雪崩
缓存穿透
用户查询一个数据,redis数据库中没有,也就是缓存没命中,于是向持久层数据库查询,发现也没有,于是查询失败,用户很多的时候,缓存都没有命中,都请求持久层数据库,给持久层数据库造成巨大压力,称为缓存穿透
解决方法
- 布隆过滤器
- 布隆过滤器是一种数据结构,对所有可能的查询参数以hash形式存储,在控制层进行校验,不符合则丢弃,从而避免了对底层存储系统查询压力
图25 布隆过滤器
- 缓存空对象
当持久化层不命中后,将返回的空对象存储起来,同时设置一个过期时间,之后再访问这个数据就从缓存中获取,保护持久层数据源
图26 缓存空对象
问题
- 存储空的key也需要空间
- 对空值设置了过期时间,还会存在缓存层和存储层的数据有一段时间窗口不一致,对于需要保持一致性的业务会有影响
缓存击穿
例子:微博服务器热搜,巨大访问量访问同一个key
一个key非常热点,不停扛着大并发,集中对一个点进行访问,当个key失效的瞬间,持续大并发导致穿破缓存,直接请求数据库
某个key在过期的瞬间,大量的访问会同时访问数据库来查询最新的数据,并且回写缓存,导致数据库瞬间压力过大
解决方法
- 设置热点数据不过期,缺点:一直缓存也会浪费空间
- 加互斥锁
- 分布式锁:使用分布式锁,保证对于每个key同时只有一个线程查询后端服务,其他线程没有获得分布式锁的权限,只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大
缓存雪崩
在某一个时间段,缓存集中过期失效比如redis宕机
产生雪崩的原因之一,设置缓存的存活时间较短,大并发访问时刚好都过期,直接访问了数据库,对数据库而言,会产生周期性压力波峰,暴增时数据库可能会宕机
图27 缓存雪崩
解决方法
- 增加集群中服务器数量
- 异地多活
- 限流降级
- 缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,对某个key只允许一个线程查询数据和写缓存,其他线程等待
- 数据预热
- 正式部署之前,把可能的数据提前访问一遍,可能大量访问的数据就会加载到缓存中,加载不同的key,设置不同的过期时间,让缓存时间尽量均匀