Redis
1、为什么要用Nosql
大数据----一般的数据库无法分析处理了
2006 年, hadoop就已经发布了
SpringBoot + SpringCloud
一定要逼着自己学习,压力越来越大,适者生存
1、单继Mysql的年代
1、一个基本的网站访问量不会太大,单个数据库完全足够
2、服务器没有太大压力
思考:这种网站的瓶颈是什么?
1、数据量如果太大,一个机器放不下
2、数据的索引,mysql单表超过300万数据,就一定要建立索引。
3、数据库的访问量太大了,服务器承载不了
只要出现以上三种情况,就必须升级
2、 Memcached(缓存) + Mysql + 垂直拆分 (读写分离 一个服务器负责写,其他服务器负责读)
网站大部分时间都是在读,每次查询数据库十分麻烦!希望减轻数据库压力,可以使用缓存来保证效率。
缓存用来提高读的效率
发展过程:优化数据库的结构和索引 -----> 文件缓存(但有IO操作) ------> Memcached(当时最热门的技术)
大厂:不缺人、缺人才
3、分库分表 + 水平拆分 + Mysql集群
技术和业务在发展的同时,对人的要求也越来越高!
本质:数据库(读、写)
早些年:MyISAM: 表锁,十分影响效率!高并发下出现严重问题
转战Innodb:行锁
慢慢的开始使用分库分表来解决读写压力!mysql在那个时候推出表分区,但并没有多少公司使用
MySQL还退出了集群
如今最近的几年
2010 按键手机 2020 智能手机 ,十年之间,世界已经发生翻天覆地的变化。
定位、音乐热榜、浏览10W+,Mysql关系型数据库不够用了!数据量大,变化快
MySQL有的人使用他来存储比较大的图片。博客、图片很大!数据库表很大,效率低!
如果有一种数据库专门来处理这种数据,MySQL压力就会变小;
大数据的IO压力,表几乎没法改变
目前一个基本的互联网项目
为什么要用NoSQL!
用户的个人信息、社交网络、地理位置。用户日志、用户产生的数据 爆发式增长
这时候我们需要使用NoSQL数据库,NoSQL可以很好的处理以上情况
2、什么是NoSQL
NoSQL
NoSQL = Not Only SQL (不仅仅是SQL)
泛指非关系型数据库,web2.0时代诞生,传统关系型数据库很难对付web2.0时代!尤其是超大规模高并发的社区!
NoSQL在当今大数据环境下发展十分迅速,Redis是发展最快的,而且是我们必须掌握的一个技术
用户的个人信息、社交网络、地理位置。 这些数据类型,不需要一个固定的格式!不需要多余的操作就可以横向扩展!
Map<String, Object> 使用键值对控制
NoSQL特点
1、方便扩展 ,数据之间没有关系,很好扩展
2、大数据高性能,Redis一秒可以写8万次,读11万,NoSQL是缓存是记录级的,是一种细粒度的缓存,性能比较高
3、数据类型多样!不需要实现设计数据库!随去随用
4、传统的RDBMS 和NoSQL
传统的RDBMS
- 结构化阻止
- SQL
- 数据和关系都存在单独的表中
- 事务
- 一致性
- 。。。。。
NoSQL
- 不仅仅是数据,不需要结构
- 没有固定的查询语言
- 键值对存储,列存储,文件存储,图形数据库 (社交关系)
- 最终一致性
- CAP定理 和 BASE (异地多活)
- 高性能,高可用、高可扩展
- 。。。。
了解: 3V + 3高
3V:主要描述问题的
1、海量 Volume
2、多样Variety
3、实时 Velocity
3高:主要对程序的要求
1、高并发
2、高可拓(随时水平拆分,机器不够了,可以扩展机器)
3、高性能
如果未来想当架构师:没有什么是加一层解决不了的
去IOE,王坚 阿里云的这群疯子
信息存储
# 1、商品基本信息
名称、价格、商家信息
关系型数据库可以解决!MySQL / Ocacle
# 2、商品的描述、评论(文字比较多)
文档型数据库 MongoDB
# 3、图片
分布式文件系统 FastDFS
- 淘宝自己的 TFS
- Gooale的 GFS
- Hadoop HDFS
- 阿里云的 OSS
# 4、商品的关键字 (搜索)
- 搜索引擎 solar elastricsearch
- ISearch 多隆
所有内壁的人都有一段苦逼的岁月!但是你只要像SB一样去坚持,终将成功!
# 5、商品热门的波段信息
- 内存数据库
- Redis Tair Memcache
# 6、商品的交易、外部的支付接口
- 第三方应用
一个简单的网页,背后的技术不一定是大家所想的那么简单!
3、NoSQL的四大分类
KV键值对
- 新浪:Redis
- 美团:Redis + Tair
- 阿里、百度: Redis + mamecache
文档数据库(bson json)
- MongoDB 一般要求掌握
- MongoDB是一个基于分布式文件存储的数据库,C++编写,用来处理大量的文档
- MongoDB 介于关系型数据库、非关系型数据库中间的铲平
- 是非关系型数据库中功能最丰富的、最像关系型数据库的
- ConthDB
列存储数据库
- HBase
- 分布式文件系统
图关系数据库
- 不是存图形,而是存关系
- 朋友圈网络、广告推荐、推荐系统
- Neo4j, infoGrid
4、Redis入门
4.1 概述
redis是什么?
Redis Remote Dictionary Server 远程字典服务
C语言编写
免费和开源,当下最热门的NoSQL技术之一!
Redis能干嘛
1、内存存储、持久化(内存是断电即失,所以持久化很重要,rdb,aof)
2、相率高,可用于高速缓存
3、发布订阅系统
4、地图订阅系统
5、计时器、计数器(浏览量)
特性
1、多样的数据类型
2、持久化
3、集群
4、事务
学习中用到的东西
官网:https://redis.io/
中文官网:http://www.redis.cn/
下载地址:https://redis.io/download
**注意:**windows版本在github上下载,停更很久了
win下载地址:https://github.com/microsoftarchive/redis
4.2 入门
客户端连接:
config set requirepass 密码 # 设置密码
config get requirepass # 查看密码
redic-cli -p 6379 -a 密码
# 或者连接之后输入密码
auth 密码
windows
win下载地址:https://github.com/microsoftarchive/redis
Redis默认端口: 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name realguo
OK
127.0.0.1:6379> get name
"realguo"
127.0.0.1:6379>
linux
1、下载安装包
2、解压Redis的安装包 程序一般放在/opt目录下
# 解压 tar.gz
tar -zxvf
3、进入解压后的文件,可以看到配置文件
4、基本的环境安装:
yum install gcc-c++
make
make install
可能会遇到的问题:centos安装redis 6.0.6 make报错
原因是因为gcc版本过低,yum安装的gcc是4.8.5的。因此需要升级gcc,升级过程如下:
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
#这句是临时的
scl enable devtoolset-9 bash
#修改环境变量
echo "source /opt/rh/devtoolset-9/enable" >> /etc/profile
gcc -v
然后重新make make install 即可
5、redis的默认安装路径 /usr/local/bin
6 、将/opt/redis目录下的redis.conf复制到 /usr/local/bin目录下
7、redis 默认不是后台启动的,修改配置文件 daemonize 为yes即可
8、启动redis-server
[root@realguo bin]# redis-server guoconfig/redis.conf # 通过指定的配置文件启动
8128:C 13 Mar 2021 21:31:42.604 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
8128:C 13 Mar 2021 21:31:42.604 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=8128, just started
8128:C 13 Mar 2021 21:31:42.604 # Configuration loaded
9、客户端连接
[root@realguo bin]# redis-cli -p 6379 # 指定端口号
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name realguo
OK
127.0.0.1:6379> get name
"realguo"
127.0.0.1:6379>
10、查看进程
[root@realguo bin]# ps -ef | grep redis
root 8129 1 0 21:31 ? 00:00:00 redis-server 127.0.0.1:6379
root 8135 7680 0 21:33 pts/0 00:00:00 redis-cli -p 6379
root 8158 8138 0 21:36 pts/1 00:00:00 grep --color=auto redis
11、关闭server 退出
127.0.0.1:6379> shutdown
not connected> exit
[root@realguo bin]#
4.3 性能测试
redis-benchmark
# 测试 100个并发连接,每个连接100000个请求
redis-benchmark -h localhost -p 63759 -c 100 -n 100000
# 结果
====== PING_INLINE ======
100000 requests completed in 1.01 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
0.00% <= 0.4 milliseconds
12.20% <= 0.5 milliseconds
29.49% <= 0.6 milliseconds
47.61% <= 0.7 milliseconds
65.71% <= 0.8 milliseconds
83.69% <= 0.9 milliseconds
95.49% <= 1.0 milliseconds
98.37% <= 1.1 milliseconds
99.43% <= 1.2 milliseconds
99.66% <= 1.3 milliseconds
99.71% <= 1.4 milliseconds
99.74% <= 1.5 milliseconds
99.76% <= 1.6 milliseconds
99.78% <= 1.7 milliseconds
99.80% <= 1.8 milliseconds
99.80% <= 2 milliseconds
99.90% <= 3 milliseconds
99.93% <= 4 milliseconds
100.00% <= 4 milliseconds
99009.90 requests per second
5、Redis基础知识
redis 正则:
redis支持的正则只有3种
* 任意长度的任意字符
? 任意单一字符
[xxx] 匹配方括号中的一个字符
从上面开来,keys的模糊匹配功能很方便也很强大,但是在生产环境需要慎用!开发中使用keys的模糊匹配却发现redis的CPU使用率极高
redis默认有16个数据库
默认使用第0个数据库
可以使用select 进行切换数据库
127.0.0.1:6379> select 15 # 切换数据库
OK
127.0.0.1:6379[15]> dbsize # 查看存储的大小
(integer) 0
keys * # 查看数据库所有的key
清除所有的key
flushdb # 清空当前数据库
flushall # 清空所有数据库
思考:为什么redis使用6379作为数据库?
女星的名字
msyql 女儿的名字
Redis是单线程的!
1、Redis是基于内存操作
2、CPU不是Redis性能瓶颈,瓶颈是机器的内存和网络带宽
3、Redis是C语言写的,每秒100000+的QPS(Query Per Second),完全不必key-value的Memcache差
Redis单线程为什么还这么快?
误区1:高性能的服务器一定是多线程
误区2:多线程比单线程效率高
核心:
Redis将所有的数据全部放在内存中,所以使用单线程效率就是最高的
多线程:CPU上下文切换,是一个耗时的操作。**对于内存系统来说,没有上下文切换的效率最高。**多次读写都在一个CPU上,在内存情况下,就是最佳的方案
6、五大数据类型、三种特殊数据类型
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件MQ。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
6.1 Redis-key
[root@realguo ~]# redis-cli
127.0.0.1:6379> FLUSHALL # 删除所有数据
OK
127.0.0.1:6379> set name realguo # 设置key
OK
127.0.0.1:6379> keys * # 显示所有key
1) "name"
127.0.0.1:6379> set age 1 # 设置key
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> exists name # 判断key是否存在
(integer) 1 # 存在
127.0.0.1:6379> exists name1
(integer) 0 # 不存在
127.0.0.1:6379> move name 1 # 移除key
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name realguo
OK
127.0.0.1:6379> get name
"realguo"
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> expire name 10 # 设置name到期时间10秒
(integer) 1
127.0.0.1:6379> ttl name # 显示剩余到期时间
(integer) 7
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2 # 已经过期
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name # 过期后无法获得name
(nil)
127.0.0.1:6379>
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> type name # 查看 key的类型
string
127.0.0.1:6379> type age
string
6.2 String 字符串
127.0.0.1:6379> set key1 v1 # 设置值
OK
127.0.0.1:6379> get key1 # 获取值
"v1"
127.0.0.1:6379> keys * # 获得所有key
1) "key1"
127.0.0.1:6379> exists key1 # 判断一个key是否存在
(integer) 1
127.0.0.1:6379> append key1 hello # 指定key添加字符串,如果key不存在,相当于set key
(integer) 7
127.0.0.1:6379> get key1 # 获取值
"v1hello"
127.0.0.1:6379> strlen key1 # 获取值的长度
(integer) 7
127.0.0.1:6379> append key1 ,kuangshen # 指定key添加字符串
(integer) 17
127.0.0.1:6379> strlen key1 # 获得值的长度
(integer) 17
127.0.0.1:6379> get key1 # 获得值
"v1hello,kuangshen"
###################################################################
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views # 自增 +1
(integer) 1
127.0.0.1:6379> incr views # 自增 +1
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views # 自减 -1
(integer) 1
127.0.0.1:6379> decr views # 自减 -1
(integer) 0
127.0.0.1:6379> decr views # 自减 -1
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> incrby views 10 #步长 +10
(integer) 9
127.0.0.1:6379> incrby views 10 #步长 +10
(integer) 19
127.0.0.1:6379> decrby views 5 #步长 -5
(integer) 14
###################################################################
127.0.0.1:6379> set key1 "Hello,kuangshen" # 设置字符串
OK
127.0.0.1:6379> get key1 # 获取字符串
"Hello,kuangshen"
127.0.0.1:6379> getrange key1 0 3 # 截取字符串[0, 3]
"Hell"
127.0.0.1:6379> getrange key1 0 -1 # 截取全部字符串[0, -1]
"Hello,kuangshen"
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx # 替换字符串
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
###################################################################
# setex key senonds value # set view expire
# setnx # set if not exist (在分布式锁中常常用到)
127.0.0.1:6379> setex key3 20 "hello1"
OK
127.0.0.1:6379> setnx key3 "hello2" # 设置失败,返回0
(integer) 0
127.0.0.1:6379> get key3
"hello1"
127.0.0.1:6379> get key3
(nil)
127.0.0.1:6379> setnx key3 "hello2" # 设置成功,返回1
(integer) 1
127.0.0.1:6379> get key3
"hello2"
###################################################################
mset # 批量设置
mget # 批量获取
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v2 k4 v4 # 原子操作,要么都成功,要么都失败
(integer) 0
127.0.0.1:6379> get k4
(nil)
###################################################################
# 对象
set user:1 {name:zhangsan,age:3} # 设置user:1对象的值 为json字符串 保存一个对象
# 一个巧妙的设计 set user:{id}:{field} # redis支持这样设计
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 3
OK
127.0.0.1:6379> get user
(nil)
127.0.0.1:6379> get user:1
(nil)
127.0.0.1:6379> get user:1:name
"zhangsan"
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "3"
###################################################################
getset # 先get再set
127.0.0.1:6379> getset key1 redis # 得到值,然后设置值
(nil)
127.0.0.1:6379> getset key1 mongodb # 得到值,然后设置值
"redis"
127.0.0.1:6379> get key1
"mongodb"
String的使用场景,value除了是字符串,还可以是数字
- 计数器
- 统计多单位的数据
- 粉丝数
- 对象 缓存存储
6.3 List
列表
在redis里面,我们可以把list完成栈、队列、阻塞队列
所有List命令 以 L 开头
###################################################################
127.0.0.1:6379> lpush list one # 在头部插入值
(integer) 1
127.0.0.1:6379> lpush list two # 在头部插入值
(integer) 2
127.0.0.1:6379> lpush list three # 在头部插入值
(integer) 3
127.0.0.1:6379> lrange list 0 -1 # 获取list中指定区间的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1 # 获取list中指定区间的值
1) "three"
2) "two"
127.0.0.1:6379> rpush list right # 在尾部插入值
(integer) 4
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
###################################################################
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list # 移除头部的值
"three"
127.0.0.1:6379> rpop list # 移除尾部的值
"right"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
###################################################################
Lindex
127.0.0.1:6379> lindex list 0 # 通过下表获得list中的值
"two"
127.0.0.1:6379> lindex list 1 # 通过下表获得list中的值
"one"
127.0.0.1:6379> lindex list -1 # 通过下表获得list中的值
"one"
###################################################################
Llen
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list threee
(integer) 3
127.0.0.1:6379> llen list # 获得列表长度
(integer) 3
###################################################################
移除指定的值
Lrem
127.0.0.1:6379> lrange list 0 -1
1) "threee"
2) "tow"
3) "threee"
4) "two"
5) "one"
127.0.0.1:6379> lrem list 1 threee # 移除list集合中指定个数的value
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "tow"
2) "threee"
3) "two"
4) "one"
###################################################################
trim 修剪
127.0.0.1:6379> rpush list "hello1"
(integer) 1
127.0.0.1:6379> rpush list "hello2"
(integer) 2
127.0.0.1:6379> rpush list "hello3"
(integer) 3
127.0.0.1:6379> rpush list "hello4"
(integer) 4
127.0.0.1:6379> ltrim list 1 2
OK
127.0.0.1:6379> lrange list 0 -1 # 通过下标截取
1) "hello2"
2) "hello3"
###################################################################
rpoplpush # 移除一个尾部元素,添加到另一个列表的头部
127.0.0.1:6379> rpush list "hello"
(integer) 1
127.0.0.1:6379> rpush list "hello2"
(integer) 2
127.0.0.1:6379> rpush list "hello3"
(integer) 3
127.0.0.1:6379> rpush list "hello4"
(integer) 4
127.0.0.1:6379> rpoplpush list list1 # 移除一个尾部元素,添加到另一个列表的头部
"hello4"
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "hello2"
3) "hello3"
127.0.0.1:6379> lrange list1 0 -1 # 查看目标列表
1) "hello4"
127.0.0.1:6379> lpoprpush list list1
(error) ERR unknown command `lpoprpush`, with args beginning with: `list`, `list1`,
###################################################################
lset # 将列表中指定位置的值,替换为更新的值
127.0.0.1:6379> exists list
(integer) 0
127.0.0.1:6379> lset list 0 test # 列表不存在,报错
(error) ERR no such key
127.0.0.1:6379> lpush list "123"
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "123"
127.0.0.1:6379> lset list 0 test # 将列表中指定位置的值,替换为更新的值
OK
127.0.0.1:6379> LRANGE list 0 -1 # 下表越界,报错
1) "test"
127.0.0.1:6379> lset list 1 test02
(error) ERR index out of range
###################################################################
linsert 将某个具体的value插入列表中某个元素的前面或后面
127.0.0.1:6379> rpush list "hello"
(integer) 1
127.0.0.1:6379> rpush list "world"
(integer) 2
127.0.0.1:6379> linsert list before world the
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "the"
3) "world"
127.0.0.1:6379> linsert list after the "****"
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "the"
3) "****"
4) "world"
总结
1、实际上是一个链表 before after left right 都可以插入值
2、如果key不存在,lpush/ rpush 创建新链表
3、如果key存在,新增内容
4、如果移除所有值,空链表,也就是不存在
5、在两边插入或改动,效率最高!中间元素,效率低
消息排队 消息队列 lpush Rpop
栈 Lpush Lpop
6.4 Set 集合
这里记录一下,虽然自己敲一遍,记忆可以更加深刻,但是因为时间比较仓促,这里就直接 选择性的复制狂神的笔记
Set 无序集合。集合成员是唯一的。
哈希表实现,增 删 查 时间复杂度 O(1)
集合中最大的成员数为 2^32 - 1
SADD key member1[member2..] # 向集合中无序增加一个/多个成员
SCARD key # 获取集合的成员数
SMEMBERS key # 返回集合中所有的成员
SISMEMBER key member # 查询members是否是key集合中的元素,是1,否0
SRANDMEMBER key [count] # 随机返回集合中count个成员,count缺省值为1
SPOP key [count] # 随机移除并返回集合中count个成员,count缺省值为1
SMOVE source destination member # 将source集合的成员member移动到destination集合
SREM key member1[member2..] # 移除集合中一个/多个成员
SDIFF key1[key2..] # 返回所有集合的差集 key1- key2 - …
SDIFFSTORE destination key1[key2..] #在SDIFF的基础上,将结果保存到集合destination 中
SINTER key1 [key2..] # 返回所有集合的交集
SINTERSTORE destination key1[key2..] # 在SINTER的基础上,将结果保存到集合destination 中
SUNION key1 [key2..] # 返回所有集合的并集
SUNIONSTORE destination key1 [key2..] # 在SUNION的基础上,将结果保存到集合destination 中
SSCAN KEY [MATCH pattern] [COUNT count] # 大量数据环境下,使用此命令遍历集合中元素,默认10
# 示例
SSCAN myset1 0 match R* count 10
6.5 Zset 有序集合
1、每个元素都会关联一个double类型的分数(score)
2、 通过分数来为集合中的成员进行从小到大的排序。
3、score相同:按字典顺序排序
4、有序集合的成员是唯一的,但分数(score)却可以重复。
ZADD key score member1 [score2 member2] # 向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZCARD key # 获取有序集合的成员数
ZCOUNT key min max # 计算在有序集合中[min, max]区间score的成员数
ZINCRBY key n member # 有序集合中对指定成员的分数加上增量 n
ZSCORE key member # 返回有序集中指定成员的分数值
ZRANGE key start end # 返回有序集合位置为[start, end]的成员
ZRANGEBYLEX key min max # 通过字典区间返回有序集合的成员(支持开、闭区间)
# 示例
127.0.0.1:6379> zrangebylex k1 [a [e
1) "b"
2) "c"
3) "d"
4) "e"
5) "a"
ZRANGEBYSCORE key min max # 返回有序集合分数为(start, end)的成员(只支持开区间 -inf +inf 表示最大,最小)
ZLEXCOUNT key min max # 在有序集合中计算指定字典区间[(min, max)]内成员数量
# 删除元素
ZREM key member1 [member2..] # 移除有序集合中一个/多个成员
ZREMRANGEBYLEX key min max # 移除有序集合中给定的字典区间[(min,max)]的所有成员
ZREMRANGEBYRANK key start stop # 移除有序集合中给定的排名区间[start,end]的所有成员
ZREMRANGEBYSCORE key min max # 移除有序集合中给定的分数区间[min,max]的所有成员
ZREVRANGE key start end # 返回有序集中指定区间[start, end]内的成员,(分数从高到低)
ZRANGE key start end # 返回有序集中指定区间[start, end]内的成员(分数从低到高)
ZREVRANGEBYSCORRE key max min # 返回有序集中指定分数区间[min, max]的成员,(分数从高到低)
ZRANGEBYSCORE key min max # 返回有序集合分数为(start, end)的成员(只支持开区间 -inf +inf 表示最大,最小)(分数从低到高)
ZREVRANGEBYLEX key max min # 返回有序集中指定字典区间内的成员(支持开、闭区间)(分数从高到低)
ZRANGEBYLEX key min max # 通过字典区间返回有序集合的成员(支持开、闭区间)(分数从低到高)
ZREVRANK key member # 返回有序集合中指定成员的排名(分数从高到低)
ZRANK key member # 返回有序集合中指定成员的排名(分数从低到高)
ZINTERSTORE destination numkeys key1 [key2 ..] # 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中,numkeys:表示参与运算的集合数,将score相加作为结果的score
ZUNIONSTORE destination numkeys key1 [key2..] # 计算给定的一个或多个有序集的并并将结果集存储在新的有序集合 key 中,将score相加作为结果的score
ZSCAN key cursor [MATCH pattern\] [COUNT count] # 迭代有序集合中的元素(包括元素成员和元素分值)
应用场景:
1、工资排序? 用这个?
2、普通消息1 重要消息2
3、排行榜的应用实现 取Top N
6.6 Hash
1、hash 是一个string类型的field和value的映射表
2、hash特别适合用于存储对象
3、Set就是一种简化的Hash,只变动key,而value使用默认值填充。
HSET key field value # 将哈希表 key 中的字段 field 的值设为 value
HSETNX key field value # 只有在字段 field 不存在时,设置哈希表字段的值。
HEXISTS key field # 查看哈希表 key 中,指定的字段是否存在。
HGET key field value # 获取存储在哈希表中指定字段的值
HMGET key field1 [field2..] # 获取多个给定字段的值
HGETALL key # 获取在哈希表key 的所有字段和值
HKEYS key # 获取哈希表key中所有的字段
HLEN key # 获取哈希表中字段的数量
HVALS key # 获取哈希表中所有值
HDEL key field1 [field2..] # 删除哈希表key中一个/多个field字段
HINCRBY key field n # 为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果,只使用整数字段
HINCRBYFLOAT key field n # 为哈希表 key 中的指定字段的浮点数值加上增量 n。
HSCAN key cursor [MATCH pattern] [COUNT count] # 迭代哈希表中的键值对。
Hash更适合于对象的存储,Sring更加适合字符串存储!
6.7 地理位置 Geospatial
redis GEO使用的不是地理位置的经纬度,有效的经度从-180度到180度。有效的纬度从-85.05112878度到85.05112878度。
当坐标位置超出上述指定范围时,该命令将会返回一个错误。
geospatial 底层是zset保存,所以可以使用zset命令来删除元素
# 将具体经纬度的坐标存入一个有序集合
geoadd key longitud(经度) latitude(纬度) member [..]
# 获取集合中的一个/多个成员坐标
geopos key member [member..]
# 返回两个给定位置之间的距离。默认以米作为单位。
geodist key member1 member2 [unit]
# 以给定的经纬度为中心, 返回集合包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
georadius key longitude latitude radius m|km|mi|ft [WITHCOORD][WITHDIST] [WITHHASH] [COUNT count]
# 功能与GEORADIUS相同,只是中心位置不是具体的经纬度,而是使用结合中已有的成员作为中心点。
GEORADIUSBYMEMBER key member radius...
# 返回一个或多个位置元素的Geohash表示。使用Geohash位置52点整数编码。
geohash key member1 [member2..]
指定单位的参数 unit 必须是以下单位的其中一个:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
有效经纬度
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度。
6.8Hyperloglog 基数统计
一个用户访问一篇文章很多次,但是只能算作一次
应用场景:
访问量:
如果允许容错,那么一定可以使用Hyperloglog !
如果不允许容错,就使用set或者自己的数据类型即可 !
6.9 BitMaps 位图
使用位存储,信息状态只有 0 和 1
应用场景
签到统计、状态统计
7、事务
7.1 事务的特性
Redis 单条指令是原子性的,但是事务不保证原子性
Redis 没有隔离级别的概念
Redis事务的本质:一组命令的集合! 一个事务中的所有命令都会被序列化,执行过程中,会按照顺序执行
一次性、顺序性、排他性!
所有的命令在事务中,并不直接执行!只有发起执行命令的时候才会执行
Redis 的事务
- 开启事务 multi
- 命令入队 命令
- 执行事务 exec
正常的执行事务
multi
set k1 v1
set k2 v2
exec
取消事务
multi
set k1 v1
set k2 v2
discard
事务遇到错误
编译型异常 命令有错误 ,事务中所有命令都不会被执行
运行时异常 1/0 ,执行命令时,其他命令正常执行,错误命令抛出异常
7.2 监控 面试常问 watch+事务 实现乐观锁
悲观锁
乐观锁
Redis可以实现乐观锁
# 使用watch 来实现乐观锁
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch momey
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> INCRBY money -20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
# 窗口一
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
(nil)
# 窗口二 : 在窗口一事务没有提交之前,改变了money,会导致窗口一exec失败
127.0.0.1:6379> INCRby money 1000
(integer) 1100
取消watch 监控 unwatch
当事务exec 或者 discard, redis 自动执行 unwatch
watch : 检测事务执行前的值 和 要修改的时候,值有没有变化
WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务队列中保存的命令就不会执行,监控一直持续到EXEC命令。(事务队列中的命令是在EXEC才执行的)
7.3 实现悲观锁 setnx + expire
setnx # 可以用来实现悲观锁
SETNX:如果key不存在,那么就新增一个<key,value>,如果key存在,则不做任何操作。
EXPIRE:为了防止网络拥塞,A拿到锁以后迟迟无法释放,故设置超时时间。防止B一直等待该key,A迟迟无法释放而出现死锁。
8、Jedis
9、SpringBoot 整合
连接服务器上的redis步骤:
# 修改 redis.conf
1、 将 bind 127.0.0.1 注释掉
2、 将 protected-mode 设置为 no, 默认(yes)
# 设置连接密码 (非必要)
3、 取消注释 requirepass foobared
4、 将 foobared 改成任意密码,用于验证登录
(默认是没有密码的就可以访问的,我们这里最好设置一个密码。)
springboot 操作数据 spring-data jpa jdbc mongodb redis
spring-data 是和spring boot齐名的项目
**说明:**在SprinBoot2.x之后,原来使用的Jedis 被替换为lettuce
**jedis:**采用直连Redis,多线程操作 是不安全的。如果想要避免不安全,使用Jedis pool连接池!更像 BIO
lettuce: 采用netty,实例可以在多个线程中进行共享,不存在不安全的情况!可以减少线程数据,更像NIO模式
源码分析
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean( // 当beanid为redisTemplate的对象不存在时,用下面这个
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
// 默认的RedisTemplate没有过多的设置,redis对象都是需要序列化的
// 两个泛型都是Object,Object类型,我们需要强制转换成<String, Object>
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
10、序列化
redis默认的序列化是JdkSerializationRedisSerializer
ObjectMapper() 是Jackson 里面的,所以序列化,还是要了解一下jackson
10.1 Jackson
10.1.1 简单映射
// 首先需要一个ObjectMapper对象,序列化和反序列化都需要它。
ObjectMapper mapper = new ObjectMapper();
Friend friend = new Friend("yitian", 25);
// 写为字符串
String text = mapper.writeValueAsString(friend);
// 写为文件
mapper.writeValue(new File("friend.json"), friend);
// 写为字节流
byte[] bytes = mapper.writeValueAsBytes(friend);
System.out.println(text);
// 从字符串中读取
Friend newFriend = mapper.readValue(text, Friend.class);
// 从字节流中读取
newFriend = mapper.readValue(bytes, Friend.class);
// 从文件中读取
newFriend = mapper.readValue(new File("friend.json"), Friend.class);
System.out.println(newFriend);
10.1.2 集合的映射
1、可以直接使用Map和List等Java集合组织JSON数据
2、在需要的时候可以使用readTree()
方法直接读取JSON字符串中的某个属性值。需要注意的是从JSON转换为Map对象的时候,由于Java的类型擦除,所以类型需要我们手动用new TypeReference<T>
给出。
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = new HashMap<>();
map.put("age", 25);
map.put("name", "yitian");
map.put("interests", new String[]{"pc games", "music"});
String text = mapper.writeValueAsString(map);
System.out.println(text);
Map<String, Object> map2 = mapper.readValue(text, new TypeReference<Map<String, Object>>() {
});
System.out.println(map2);
JsonNode root = mapper.readTree(text);
String name = root.get("name").asText();
int age = root.get("age").asInt();
System.out.println("name:" + name + " age:" + age);
10.2 Jackson配置
Jackson预定义了一些配置,通过启用和禁用某些属性可以修改Jackson运行的某些行为。
详细文档参考JacksonFeatures。下面列出Jackson的一些属性。
// 美化输出
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// 允许序列化空的POJO类
// (否则会抛出异常)
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 把java.util.Date, Calendar输出为数字(时间戳)
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 在遇到未知属性的时候不抛出异常
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 强制JSON 空字符串("")转换为null对象值:
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
// 在JSON中允许C/C++ 样式的注释(非标准,默认禁用)
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
// 允许没有引号的字段名(非标准)
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// 允许单引号(非标准)
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// 强制转义非ASCII字符
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
// 将内容包裹为一个JSON属性,属性名由@JsonRootName注解指定
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
1、configure方法接受配置名和要设置的值
2、Jackson 2.5版本新加的enable和disable方法则直接启用和禁用相应属性,推荐使用后面两个方法。
参考:Jackson快速入门
1、类实现接口Serializable
2、new ObjectMapper().writeValueAsString(对象)
String tt = new ObjectMapper().writeValueAsString(new TT("刘陈陈", 23)); // json格式序列化
redisTemplate.opsForValue().set("test", tt);
System.out.println(redisTemplate.opsForValue().get("test"));
3、指定具体的序列化方式
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>();
redisTemplate.opsForValue().set("test", tt);
redisTemplate.setDefaultSerializer();
redisTemplate.setKeySerializer();
redisTemplate.setStringSerializer();
redisTemplate.setValueSerializer();
。。。
11、Redis.conf
启动的时候,就是通过配置文件启动的
1. 单位
1、配置文件 unit单位 对大小写不敏感
包含 可以包含其他配置文件
2.网络
绑定的ip
端口、是否开启保护模式
3.通用配置
daemonize yes #以守护进程的方式开启(后台运行),默认no
pidfile /var/run/redis_6379.pid #如果以后台的方式运行,我们就需要只当一个pid文件
# 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)
loglevel notice # 日志级别
logfile "" # 日志的文件名
databases 16 # 数据库的数量
always-show-logo yes # 是否显式logo
4.快照 持久化,在规定的时间内,执行了多少次操作,则会被持久到文件 .rdb .aof
redis 是内存数据库,如果没有持久化,数据断电即失
# 如果900秒内,如果至少有一个key进行了修改,就进行持久化操作
save 900 1
# 如果300秒内,如果至少有10key进行了修改,就进行持久化操作
save 300 10
# 如果60秒内,如果至少有一个10000key进行了修改,就进行持久化操作
save 60 10000
# 我们之后会设计自己的
stop-writes-on-bgsave-error yes # 持久化出错,是否停止
rdbcompression yes # 是否压缩rdb文件,压缩需要消耗一些CPU资源
rdbchecksum yes # 是否检验rdb文件
dbfilename dump.rdb # rdb保存文件名
rdb-del-sync-files no
dir ./ # rdb保存地址
5.主从复制 REPLICATION
6.SECURITY密码
# requirepass foobared # 设置密码
命令方式设置
config set requirepass 密码
7.客服端连接设置
maxclients 10000 # 最大客户端数量
maxmemory <bytes> # 最大内存限制
maxmemory-policy noeviction # 内存达到限制值的处理策略
redis 中的默认的过期策略是 volatile-lru 。
命令设置方式
config set maxmemory-policy volatile-lru
maxmemory-policy 六种方式
**1、volatile-lru:**只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
**3、volatile-random:**随机删除即将过期key
**4、allkeys-random:**随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
8.AOF
appendonly no # 默认不开启AOF ,默认使用rdb进行持久化
appendfilename "appendonly.aof" # AOF 保存文件名
# 数据同步策略
# appendfsync always # 总是同步
appendfsync everysec # 每秒同步一次
# appendfsync no # 不同步
12、Redis持久化
aof append only file
rdb redis database
12.1 RDB
1、在指定的时间间隔内,将数据写入磁盘,相当于生成快照。恢复时,直接读快照
2、redis单独创建一个子进程来持久化
3、子进程先将数据写入一个临时文件,当全部写入完毕,用临时文件替换上次持久化好的问及那。
4、整个过程,主进程不进行IO操作,确保了极高的性能。
5、对于大规模数据恢复,且数据恢复完整性不非常敏感,rdb方式比AOF方式更加高效
# rdb 的缺点:
最后一次持久化的数据可能丢失。
rdb默认保存文dump.rdb
生成dump.db的三种情况:
1、使用save
命令
2、使用flushall
命令。这是redis的保护机制,放着误删
3、关闭redis-server服务器的时候,也会生成
4、满足配置文件
如何使用rdb文件恢复?
有时候我们会rdb文件进行配置
1、将rdb文件放在redis启动目录下即可,redis启动时自动检查dump.rdb文件
# 查看启动位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"
rdb优点
1、适合大规模数据恢复
2、可以将数据进行冷备。
缺点:
1、需要一定时间间隔进行操作。如何redis宕机了,最后一次数据无法保存
2、fork子进程,如果数据量很大,会短暂的性能下降
12.2 AOF
append only file appendonly.aof
1、以日志的形式记录每个写操作,记录执行的所有指定
2、只可以追加文件,不能改写文件
3、redis重启时,读取该文件,重构数据
如果aof文件有错误,redis-server无法正常启动
redis-check-aof --fix appendonly.aof # 修复aof文件
如果文件大于64m,重新写一个新的文件
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
优点
1、每一次都同步,文件完整性更好
2、每秒同步一次,可能会丢失一些数据
3、从不同步,效率最高
缺点
1、相对于dump.rdb文件,更大,修复速度慢
2、aof效率比rdb低
扩展
13、Redis发布订阅模式
应用:微信公众号发布
订阅消息
127.0.0.1:6379> subscribe kuangshenshuo # 订阅 频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "kuangshenshuo"
3) (integer) 1
1) "message" # 收到消息
2) "kuangshenshuo"
3) "Hello "
1) "message" # 收到消息
2) "kuangshenshuo"
3) "Hello redis"
发布消息
127.0.0.1:6379> publish kuangshenshuo "Hello " # 向频道发送消息
(integer) 1
127.0.0.1:6379> publish kuangshenshuo "Hello redis"
(integer) 1
127.0.0.1:6379>
命令
PSUBSCRIBE pattern [pattern..] # 订阅一个或多个符合给定模式的频道。
PUNSUBSCRIBE pattern [pattern..] # 退订一个或多个符合给定模式的频道。
PUBSUB subcommand [argument[argument]] # 查看订阅与发布系统状态。
PUBLISH channel message # 向指定频道发布消息
SUBSCRIBE channel [channel..] # 订阅给定的一个或多个频道。
UNSUBSCRIBE channel [channel..] # 退订一个或多个频道
原理
1、redis服务器位置一个表示服务器状态的结构
2、结构的pubsub_channels 属性 是一个字典
3、pubsub_channels 字典保存订阅频道的信息
4、key是被订阅的评到
5、value是一个链表,保存订阅这个频道的客户端
6、客户端订阅,就被接到链表的尾部,退订就从链表中移除
# 缺点
1、客户端如果读取消息速度步块,会导致消息积压,是redis缓冲区体积变大,从而导致redis速度变慢,直到崩溃
2、如果订阅方下线,将丢失下线期间发布的消息
# 应用:
1、微信公众号 微博关注
2、多人聊天室
复杂的场景,会使用消息中间件MQ(message queue 消息队列)处理
14、Redis主从复制
1.热备份是数据库仍旧处于工作状态时进行备份。是针对归档模式的数据库备份。 优点:优点在于当备份时数据库仍旧可以被使用并且可以将数据库恢复到任意一个时间点。
2.冷备份指在数据库关闭要备份的服务后进行备份,适用于所有模式的数库。 优点:优点在于它的备份和恢复操作相当简单,并且由于冷备份的数据库可以工作在非归档模式下,数据库性能会比归档模式稍好。
14.1 介绍
概念
1、主从复制,将一个redis服务器的数据,复制到另外一个redis服务器
2、前者:master / leader 后者:slave/follower 数据复制是单项的,只能主节点 ----> 从节点
3、Master 写 为主, Slave读为主
一个主节点:0~n个从节点 一个从节点:一个主节点
作用:
1、数据冗余
2、故障恢复
3、负载均衡:在主从复制的基础上,配合读写分离,让主节点提供写,从节点提供读。尤其是写少读多的场景。多个从节点分担读眼里,可以提高并发
4、高可用的基石。主从复制是高可用的基础。(高可用一般是指集群高可用)
一般情况下,只用一台redis不太可靠的原因:
1、单个redis会发生单点故障
2、单个服务器要处理所有请求负载,压力较大
3、单个redis服务器,容量有限。一般来说,单个redis使用内存不应该超过20G
14.2 环境配置
redis 默认自己是主节点, 所以只需要配置从节点即可
高可用:主从赋值 哨兵模式
查看主从配置信息
127.0.0.1:6379> info replication # 查看主从配置
# Replication
role:master # 主节点
connected_slaves:0 # 没有从节点
master_replid:85a8dc4b2db4a23cd6f696bbe41f653bb8444490
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
# 修改的信息
1、 port
2、 pidfile /var/run/redis_6379.pid
3、 logfile "6379"
4、 dbfilename dump6379.rdb
slave 127.0.0.1 port # 认老大,也可以在配置文件配置
# 命令配置是暂时的,配置文件配置是永久的
14.3 测试
主机才可以写,从机只能读,不能写
# 主机
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379>
# 从机
127.0.0.1:6381> slaveof 127.0.0.1 6379
OK
127.0.0.1:6381> get k1
"v1"
127.0.0.1:6381> get k2
"v2"
127.0.0.1:6381> set k3 v3
(error) READONLY You can't write against a read only replica. # 从机只能读,不能写
**全量复制:**从机断开,重新连接主节点,会进行增量复制
**增量复制:**全量复制完之后,主机写入新的东西,从机进行增量复制
从机如果每写入配置文件,重启会自动变为主节点。 读取不了原先在主机那获得的值
slaveof no one # 让自己变成主节点
14.4 哨兵模式(自动选取主节点)
1、主从切换技术:主机宕机后,切换从机为主机,如果手工,费时费力,会造成一段时间内服务不可用
2、使用哨兵模式sentinel框架,自当结局这个问题
3、哨兵 : 一个独立的进程,会独立运行。
4、原理:哨兵向服务器发送命令,通过判断响应来监视多个redis实例,包括主机和从机
5、当哨兵检测到主机宕机后,自动将slave切换称master,然后通过发布订阅模式通知其他服务器,修改配置文件,完成切换主机
除了监控各个redis服务器之外,哨兵之间也会户像监控
过程
假设主机宕机,哨兵检测到这个结果,并不会马上进行failover过程。
仅仅是哨兵1认为主机不可用,这种现象称为主观下线。
当其他哨兵也检测到主机不可用,并且达到一定数量,那么哨兵之间进行一次投票,投票由一个哨兵发起,进行failover故障转移操作。
切换完成后,通过发布订阅模式,让各个哨兵把各自监控的从服务器实现切换主机,这个过程称为客观下线
投票算法自己有时间看一下
测试
首先写哨兵的配置文件sentinel.conf
# 格式
# sentinel monitor 被监控的名称 host port n
# (数字n:表示有n个主机时,认为主机宕机时,该主机才被判定为宕机)
sentinel monitor myredis 127.0.0.1 6379 1
启动哨兵
[root@tql bin]# redis-sentinel redisconfig/sentinel.conf # 启动哨兵
6156:X 19 Mar 2021 13:43:25.743 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
6156:X 19 Mar 2021 13:43:25.743 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=6156, just started
6156:X 19 Mar 2021 13:43:25.743 # Configuration loaded
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.0.6 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 6156
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
6156:X 19 Mar 2021 13:43:25.744 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
6156:X 19 Mar 2021 13:43:25.747 # Sentinel ID is e40225371e90b97f01d801e55d9d09b3c367b556
6156:X 19 Mar 2021 13:43:25.747 # +monitor master myredis 127.0.0.1 6379 quorum 1
6156:X 19 Mar 2021 13:43:25.748 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:43:25.751 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
主机下线,哨兵自动切换主机, 下面时日志
6156:X 19 Mar 2021 13:43:25.744 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
6156:X 19 Mar 2021 13:43:25.747 # Sentinel ID is e40225371e90b97f01d801e55d9d09b3c367b556
6156:X 19 Mar 2021 13:43:25.747 # +monitor master myredis 127.0.0.1 6379 quorum 1
6156:X 19 Mar 2021 13:43:25.748 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:43:25.751 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:04.292 # +sdown master myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:04.292 # +odown master myredis 127.0.0.1 6379 #quorum 1/1
6156:X 19 Mar 2021 13:45:04.292 # +new-epoch 1
6156:X 19 Mar 2021 13:45:04.292 # +try-failover master myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:04.296 # +vote-for-leader e40225371e90b97f01d801e55d9d09b3c367b556 1
6156:X 19 Mar 2021 13:45:04.296 # +elected-leader master myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:04.296 # +failover-state-select-slave master myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:04.354 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:04.354 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:04.407 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:04.477 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:04.477 # +failover-state-reconf-slaves master myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:04.545 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:05.517 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:05.517 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:05.587 # +failover-end master myredis 127.0.0.1 6379
6156:X 19 Mar 2021 13:45:05.587 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6381
6156:X 19 Mar 2021 13:45:05.587 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381
6156:X 19 Mar 2021 13:45:05.587 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
6156:X 19 Mar 2021 13:45:35.655 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
如果主机重新上线,主机变为从机
6156:X 19 Mar 2021 13:45:35.655 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
6156:X 19 Mar 2021 13:47:31.532 # -sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
6156:X 19 Mar 2021 13:47:41.505 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
哨兵模式的优点和缺点
优点
1、基于主从复制,有主从配置的所有优点
2、主从可以自动切换,故障可以转移,可用性高
3、哨兵就是主从模式的升级,手动到自动
缺点
1、redis不好在线扩容,集群容量一旦达到上线,在线扩容就十分麻烦
2、实现哨兵模式的配置其实很麻烦,里面有很多选择
哨兵模式的全部配置
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
15、Redis缓存穿透 和雪崩
没有详细的讲解解决方案的底层
1、redis 缓存:极大的提升了程序的性能和效率,特别时数据查询
2、同时也带来一些问题,最要害的就是 一致性问题: 从严格意义上将,这个问题无解,如果堆数据的一致性要求很高,就不能使用缓存。
3、另外一些问题 缓存穿透、缓存雪崩、缓存击穿,比较流行的解决方案
15.1缓存穿透 查不到
缓存穿透:
1、用户查询一个数据,发现redis中没有,也就是缓存没有命中
2、于是向持久层查询数据,发现也没有,于是查询失败
3、当用户很多的时候,如果都没有命中,都向持久层数据库请求,就会给持久层造成很大压力。
解决方案:
1、 布隆过滤器 BloomFilter
一种数据结构,可以对所有可能查询的参数以hash形式存储,再控制层先进行效验,不符合规则,就丢弃,从个人避免了数据库的查询压力
2、缓存空对象
当数据库中也不存在,即使返回空对象也缓存起来,同时设置过期时间,之后再访问这个数据就从缓存中拿,保护了后端数据库
存在两个问题:
1、如果缓存控制,缓存则需要更多的空间存储更多的键,因为这当中可能有很多空值的键
2、即使对空值设置过期时间,还是会造成一段时间内缓存层和存储层之间的数据不一致,对保持一致性的业务会有一定的影响。
15.2 缓存击穿 查的太多了
一个key是一个热点,不断的扛着大的并发,大量访问集中对这一个key进行访问,当这个key在失效的瞬间,持续的大并发就会直接请求数据库,就好像一个屏障上凿开了一个洞
**总结:**一个热点key过期,导致大量请求访问数据库,使数据库压力增大
解决方案
1、 设置热点数据永不过期
2、 使用分布式锁,保证对于一个key,同时只能一个线程取后端查询服务,其他线程没有获得锁,就等待。把压力转移到了分布式锁上,对分布式锁的考验很大
15.3 缓存雪崩
某一个时间段,缓存集中过期失效
比如双十一零点:很快应来一波抢购,这些商品集中放在缓存中,假设缓存一个小时,那么凌晨1点。这批商品缓存集中过期,那么查询压力就都落在了数据库上面。对于数据库而言,会产生周期性的压力波峰。可能会导致存储层宕机。
集中过期,不是非常致命的。比较致命的缓存雪崩:使缓存服务器中某个节点宕机或者断网。
集中过期导致的雪崩,一定使在某个时间段集中创建缓存,这个时候,数据库也可以集中重新创建缓存。无非就是对数据库产生周期性的压力而已。
但是如果是缓存服务节点宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间把数据库压垮。
解决方案:
1、redis高可用: 既然redis可能宕机,就多设计几台。 这样,一台挂掉,其他还可以继续工作。(集群,异地多活)
2、线流降级: 缓存失效后,通过加锁,或者队列来控制数据库的写缓存的线程数量。比如一个key,只允许一个线程查询数据,写缓存,其他线程等待。
3、数据预热 : 在正式部署之前,把可能的数据先预先访问一百年,这样部分可能大量访问的数据就会加载到缓存中。 在即将发生大并发访问前,手动触发加载缓存,并设置不同的过期时间,让缓存失效的时间尽量均匀