文章目录
Nosql概述
为什么使用Nosql
单机MySQL时代
90年代,一个网站的访问量一般不会太大,单个数据库完全够用,但随着用户的增加,出现以下问题:
- 数据量太大,一台机器放不下
- 数据的索引(B+ tree),一台机器内存也放不下
- 访问量过大(读写混合),一台机器也承受不了
Memcached(缓存)+MySQL+垂直拆分
网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦!所以说我们希望减轻数据库的压力,我们可以使用缓存来保证效率!
分库分表 + 水平拆分 + MySQL集群
目前基本的互联网项目
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!
这时候我们就需要使用NoSQL数据库的,Nosql可以很好的处理以上的情况!
什么是Nosql
NoSQL = Not Only SQL(不仅仅是SQL)
关系型数据库:表:列+行,同一个表下数据的结构是一样的
非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展
Nosql特点
- 方便扩展(数据之间没有关系,很好扩展)
- 大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
- 数据类型是多样型的!(不需要事先设计数据库,随取随用)
Redis 入门
Redis是什么
Redis(Remote Dictionary Server ),即远程字典服务
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API
数据都是缓存在内存中,redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区
特性
-
多样的数据类型
-
持久化
-
集群
-
事务
…
单线程
Redis是单线程的!,Redis是将所有的数据放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!),对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,在内存存储数据情况下,单线程就是最佳的方案
Linux下安装
-
下载安装包
redis-6.2.6.tar.gz
-
上传到 Linux,
/usr/local/software
目录 -
进入
/usr/local/software
目录,解压tar -zxvf redis-6.2.6.tar.gz
-
进入解压文件
cd redis-6.2.6
-
安装环境
yum install gcc-c++
-
执行
make
然后执行make install
-
redis默认安装路径 /usr/local/bin,进入该目录将redis的配置文件复制到 /usr/local/bin/rainhey_config
[root@node1 bin]# cp ../software/redis-6.2.6/redis.conf ./rainhey_config
-
修改rainhey_config下的配置文件,修改
daemonize yes
-
用修改过的配置文件启动redis,
[root@node1 bin]# redis-server rainhey_config/redis.conf
-
开启客户端连接server,
redis-cli -p 6379
-
测试
-
查看Redis进程是否开启
-
关闭Redis服务
Redis基本命令
数据库
select 3 //select 切换数据库,Redis默认有16个数据库(编号0-15),默认使用第0个数据库
dbsize //当前数据库大小
设置值
set name rainhey //存值,键值对
get name //取值
keys * //查看所有的key
清空
flushdb //清空当前数据库
flushall //清空全部数据库
判断存在
exists name //判断键值是否存在;存在返回1,不存在返回0
移动
move name 1 //从当前数据库移除key到1号数据库中
设置过期
expire age 20 //设置键的过期时间(秒),过期后自动移除
ttl age //查看key的剩余时间
类型
type name //查看key的类型
基本数据类型
String
追加
append name test //在key name的值后追加字符串“test”,若key不存在相当于添加
长度
strlen name //获取key对应的值的字符串长度
加减
incr view //view数值加1
decr view //view数值减1
incrby view 10 //view数值加10
decrby view 11 //view数值减11
范围值
getrange test 0 4 //截取字符串下标[0,4]的字符串,注意两边是闭区间;[0,-1]代表整个字符串
setrange test 1 *** //替换字符串,1代表开始替换的下标,***代表开始替换的值
过期
setex key1 20 "hello" // setex( set with expire )
不存在设置
setnx key2 "hello" // setnx (set if not exist)
批量操作
mset k1 v1 k2 v2 k3 v3 // 批量设置值
mget k1 k2 k3 // 批量获取值
msetnx k1 v1 k4 v4 // key都不存在时才设置,否则都不设置
对象
set user:1 {name:zhangsan,age:3} //设置user:1对象,用json字符串保存其值
mset user:1:name zhangsan user:1:age 55 //这里的key是个巧妙设计 user:{id}:{field}
获取设置
getset key2 v2 // 先获取值再设置值
List
所有的List命令都是以L开头的
添加,lpush相当于入栈,rpush相当于入队列,读的时候先读栈再读队列
lpush list one //往list里面添加元素
rpush list four //往list添加元素
范围取值
lrange list 0 -1 //从list里面取值
左右移除
lpop list //移除list里的第一个元素
rpop list //移除list里的最后一个元素
rpoplpush list list1 //移除list最后一个元素lpush到list1中
下标取值
lindex list 1 //获取list下标1的值
List长度
llen list // 获取list长度
移除
lrem list 2 four //从list中移除两个值为four的值
修剪
ltrim list 0 3 //只保留list[0,3]的值
替换
lset list 0 one //将指定下标的值替换
插值
linsert list before world other // 在world前添加other
Set
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据
Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)
sadd myset hello1 //添加值
smembers myset //查看成员
sismember myset hello1 //判断hello1是否是myset的成员
scard myset //查看myset元素个数
srem myset hello1 //移除hello1
srandmember myset 2 //随机取出两个
spop myset 1 //随机移除指定数目的元素
smove myset1 myset2 one //将one从myset1移动到myset2
sdiff set1 set2 //set2在set1中的差集
sinter set1 set2 //交集
sunion set1 set2 //并集
Hash
Map集合,key-map,这时候值是map的集合,本质和string没多大区别
hset myhash field1 rainhey //存键值对
hget myhash field1 //取值
hmset myhash field1 value1 field2 value2 //批量存键值对
hmget myhash field1 field2 //批量取值
hgetall myhash //获取所有的键值对
hdel myhash field1 //删除key,对应的value也没有了
hlen myhash // 查看键值对的对数
hkeys myhash //获取所有键值对的key
hvals myhash //获取所有键值对的value
hincrby myhash field3 4 //field3对应的值加4
hsetnx myhash field4 hello //不存在创建,存在不能设置
Hash更适合于对象的存储,Sring更加适合字符串存储!
Zset(有序集合)
在set的基础之上增加了一个值 set k1 v1 ; zset k1 score v1
zadd myzset 1 one // 设置一个值
zadd myzset 3 three 4 four // 设置多个值
zrange myzset 0 -1 // 取值,从小到大排序
zrangebyscore salary -inf +inf // 在指定范围内按照score排序
zrangebyscore salary -inf +inf withscores //排序带上score
zrem salary lisi // 移除指定元素
zcard salary // 查看元素个数
zrevrange salary 0 -1 // 从大到小排序
zcount myset 1 3 // 获取score在1到3的元素的个数
三种特殊数据类型
Geospatial 地理位置
只有六个命令
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度
geoadd china:city 114.878872 30.459422 beijing // 添加地理位置,参数 key 经度 维度 名称
geoadd china:city 116.413384 39.910925 guangzhou 79.920212 37.118336 hetian //多地
geopos china:city beijing // 获取某地的经纬度
geopos china:city shanghai beijing // 多地
距离
- m 表示单位为米
- km 表示单位为千米
- mi 表示单位为英里。
- ft 表示单位为英尺
geodist china:city shanghai beijing //计算距离
geodist china:city shanghai beijing km // km为单位
georadius china:city 118 30 500 km // 查找以经纬度118 30 为圆心,500km为半径的城市
georadius china:city 119 30 200 km withdist // 查找并带上距离
georadius china:city 119 30 200 km withcoord //查找并带上经纬度坐标
georadius china:city 119 30 200 km withcoord withdist count 2 //查找并带上坐标距离、限定查找个数
eoradiusbymember china:city city3 100 km // 查找成员为圆心的其他成员
geohash china:city city1 city2 city3 //该命令返回11个字符的geohash字符串,将二维的经纬度转化为一维的字符串,字符串越接近则距离越接近
Geo的底层实现原理是Zset,所以也可以Zset命令来操作Geo
查看全部元素
移除元素
Hyperloglog 基数统计
基数:数据集中不重复的元素的个数
pfadd myset a b c d e f g h i j //添加
pfadd myset2 i j k c v z h
pfcount myset //计数,不重复
pfmerge myset3 myset myset2 // 合并集合myset myset2 到 myset3
如果允许容错,那么可以使用Hyperloglog !
如果不允许容错,就使用set或者自己的数据类型即可 !
Bitmap 位图
只要是两个状态的,可以用位图操作二进制位来记录
setbit sign 2 0 //2代表第二个位置,0代表该位置状态
getbit sign 6 //获取第六个位置的状态
bitcount sign //统计sign中状态为1的个数
Redis 基本事务操作
Redis事务本质:一组命令的集合,一个事务的所有命令都会被序列化,在事务执行过程中按照顺序执行
Redis 单条命令保持原子性,但事务不保证原子性
Redis事务没有隔离级别的概念
所有的命令在事务中没有被直接执行,只有发起执行命令后才会执行
- 开启事务 multi
- 命令入队
- 执行事务 exec
discard 放弃事务,队列中的命令都不会被执行
若事务中某条命令存在编译型异常,则所有的命令都不会被执行;若事务中某条命令存在运行时异常(如1/0),则其他命令可以正常执行
Redis 实现乐观锁
悲观锁:认为什么时候都会出问题,无论做什么都会加锁
乐观锁:认为什么时候都不会出问题,不会加锁,更新数据的时候去判断一下此期间是否有人修改过数据
watch key
监控指定数据,相当于乐观锁加锁
正常执行的情况
异常情况下,开启一个客户端模拟插队线程
线程1:还没执行exec
此时线程2:修改了值
线程1执行:结果为null,事务没有执行
如果发现事务执行失败,解锁unwatch
获取最新值,再加锁进行事务
Jedis
Redis官方推荐的Java连接工具,使用Java操作Redis的中间件
- 导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
- 代码实例
public class Test {
public static void main(String[] args) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "world");
jsonObject.put("name", "rainhey");
String result = jsonObject.toJSONString();
// 1.new jedis 对象
Jedis jedis = new Jedis("192.168.0.100", 6379);
jedis.auth("123456");
// 2. jedis的所有命令就是之前学的所有命令
Transaction multi = jedis.multi();
try{
multi.set("user1", result);
multi.set("user2", result);
multi.exec();
}catch (Exception e){
multi.discard();
e.printStackTrace();
}
finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
}
Springboot 整合 Redis
快速入门
Springboot 2.x 后 ,原来使用的 Jedis 被 Lettuce 替换
Jedis:采用的直连,多个线程操作的话,是不安全的。如果要避免不安全,使用jedis pool连接池!更像BIO模式
Lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置连接
spring.redis.host=192.168.0.100
spring.redis.port=6379
spring.redis.password=123456
注意一些连接池相关的配置,使用时一定使用Lettuce的连接池
- 测试
@SpringBootTest
class RedisSpringApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
/*除了基本的操作,常用方法可以通过redisTemplate操作,比如事务和基本的CRUD
redisTemplate.
opsForValue: 操作字符串,类似String opsForList: 操作list opsForSet() opsForZSet()
opsForHash() opsForGeo() opsForHyperLogLog()*/
redisTemplate.opsForValue().set("mykey", "rainhey");
System.out.println(redisTemplate.opsForValue().get("mykey"));
/*某些命令通过Redis的连接对象操作
* */
/*RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushAll();
connection.flushDb();*/
}
}
在Redis中查看数据时发现乱码,这就关系到存储对象的序列化问题,查看源码可知Redis默认采用JDK序列化方式,这时Redis看到的数据就乱码
序列化
Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型;将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象
public class User {
private String name;
private int age;
}
@Test
void test() throws JsonProcessingException {
User user = new User("rainhey", 3);
//序列化,转换成json字符串传递
String s = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user", s);
System.out.println(redisTemplate.opsForValue().get("user"));
}
public class User implements Serializable {
private String name;
private int age;
}
@Test
void test() throws JsonProcessingException {
User user = new User("rainhey", 3);
//序列化,转换成json字符串传递
//String s = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user", user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
自定义redisTemplate
@Configuration
public class RedisConfig {
//借鉴源码,写自己的 redisTemplate,覆盖底层的
@Resource
RedisConnectionFactory redisConnectionFactory; // 方法参数注入时错误,使用Resource注入
@Bean
public RedisTemplate<String, Object> redisTemplate() {
// 为了开发方便,一般使用string
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
/*
* 序列化设置
* 调用RedisSerializer的静态方法来返回序列化器
*/
// key、hash的key 采用 String序列化方式
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// value、hash的value 采用 Jackson 序列化方式
template.setValueSerializer(RedisSerializer.json());
template.setHashValueSerializer(RedisSerializer.json());
template.afterPropertiesSet();
return template;
}
}
自定义Redis工具类
RedisTemplate需要频繁调用.opForxxx影响效率,工作中常将这些常用的公共API抽取出来封装成为一个工具类,然后直接使用工具类来间接操作Redis
工具类参考博客:
https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html
https://www.cnblogs.com/zhzhlong/p/11434284.html
Redis.config
- 配置文件大小写不敏感
- 网络
bind 127.0.0.1 -::1 //绑定IP
protected-mode no //是否开启保护模式
port 6379 //端口
- 通用
daemonize yes //以守护进程方式执行
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 no //是否显示LOGO-宝塔
- 快照
持久化,在规定的时间内执行多少次操作,就会持久化到文件 .rdb .aof
Redis 是内存数据库,如果没有持久化,数据就会断电丢失
# save 3600 1 //3600 s内至少有一个Key进行修改,就会持久化
# save 300 100
# save 60 10000
stop-writes-on-bgsave-error yes //持久化出错是否还要继续工作
rdbcompression yes //是否压缩rdb文件,需要消耗一些cpu资源
rdbchecksum yes //保存rdb文件时进行错误的检查校验
dir ./ //rdb文件的保存目录
- REPLICATION 复制
replicaof <masterip> <masterport> //配置主机ip和端口,相当于命令slaveof
masterauth <master-password> //配置主机密码
- SECURITY 安全
requirepass 123456 //设置密码
- CLIENTS
maxclients 10000 //客户端上限
maxmemory <bytes> //redis配置最大内存容量
maxmemory-policy noeviction //内存到达上限后的处理策略
策略,设置方式如 config set maxmemory-policy volatile-lru
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
- APPEND ONLY MODE (AOF配置)
appendonly no //默认不开启,默认使用rdb
appendfilename "appendonly.aof" //持久化文件的名字
# appendfsync always //每次修改都会同步
appendfsync everysec //每秒执行一次,可能会丢失一秒的数据
# appendfsync no //不同步
持久化-RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘,它恢复时将快照文件直接读到内存
rdb保存的文件是dump.rdb,可以在配置文件中配置
RDB原理
- Redis 调用forks,同时拥有父进程和子进程
- 子进程将数据写入到一个临时 RDB 文件中
- 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件
触发机制
- save的规则满足的情况下,会自动触发rdb原则
- 执行flushall命令,也会触发我们的rdb原则
- 退出redis,也会自动产生rdb文件
触发机制后会自动产生一个 rdb 文件
恢复
只需将rdb文件放在Redis启动目录,redis启动时会自动检查dump.rdb,恢复其中的数据
优缺点
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了
- fork进程的时候,会占用一定的内容空间
持久化-AOF
AOF保存的是 appendonly.aof 文件
默认是不开启的,需要到配置文件自己配置开启
原理
把所有执行的命令全都记录下来(读操作命令不记录),恢复的时候再执行文件中的全部命令
修复
如果AOF文件有错误,这时候Redis是无法正常启动的,需要修改这个aof文件,redis提供了一个工具redis-check-aof --fix,aof文件修复正常后,redis就可以正常启动了
redis-check-aof --fix appendonly.aof //修复aof文件
优缺点
优点
- 每一次修改都会同步,文件的完整性会更加好
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
缺点
- 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
- aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化
Redis 发布订阅
命令 | 描述 |
---|---|
PSUBSCRIBE pattern [pattern..] | 订阅一个或多个符合给定模式的频道 |
PUNSUBSCRIBE pattern [pattern..] | 退订一个或多个符合给定模式的频道 |
PUBSUB subcommand [argument[argument]] | 查看订阅与发布系统状态 |
PUBLISH channel message | 向指定频道发布消息 |
SUBSCRIBE channel [channel..] | 订阅给定的一个或多个频道 |
UNSUBSCRIBE channel [channel..] | 退订一个或多个频道 |
- 开启一个客户端订阅频道,自动监听
- 开启一个客户端发送消息
- 订阅端接受到消息
原理
每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端
客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除
应用
- 消息订阅:公众号订阅,微博关注等等
- 多人在线聊天室…
Redis 集群
主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点,后者称为从节点, 数据的复制是单向的,只能由主节点复制到从节点
作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式
- 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
- 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量
- 高可用基石:主从复制还是哨兵和集群能够实施的基础
环境配置
只配置从库,不配置主库,因为Redis默认自己就是主库
查看当前Redis 主从复制的信息
info replication
- 复制redis.conf 分别命名为 redis79.conf、redis80.conf、redis81.conf
修改配置文件
redis79.conf:
logfile "6379.log"
dbfilename dump6379.rdb
redis80.conf:
port 6380
pidfile /var/run/redis_6380.pid
logfile "6380.log"
dbfilename dump6380.rdb
redis81.conf:
port 6381
pidfile /var/run/redis_6381.pid
logfile "6381.log"
dbfilename dump6381.rdb
- 启动3个Redis服务,搭建一主二从,只用配置从机
从机80:
slaveof 127.0.0.1 6379
从机81:
slaveof 127.0.0.1 6379
查看主机 replication
真正的主从配置是在配置文件中配置的,我们这里是用命令配置的,是暂时的
在配置文件中配置主从配置
replicaof <masterip> <masterport> //配置主机ip和端口,相当于命令slaveof
masterauth <master-password> //配置主机密码
细节
-
从机只能读,不能写,主机可读可写但是多用于写,主机中的所有数据和信息都会被从机主动保存
-
当主机断开连接后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状
-
当从机断开连接后,若是用命令行配置的主从而不是使用配置文件配置的从机,则再次启动后原来的从机将自动恢复为主机是无法获取之前作为从机时连接到的主机的数据的,若此时重新配置为之前连接的主机的从机,则又可以获取到主机的所有数据
只要是重新连接主机,一次全量复制将会别自动执行;之后主机上新增的数据增量复制到从机 -
链路模型
同样只有主节点才可写入 -
从节点变主节点命令
SLAVEOF no one
哨兵模式
当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用
哨兵模式:自动监控主机是否出故障,当主机出故障后,根据票数自动将从机切换为主机,哨兵是一个独立的进程,通过向Redis服务器发送命令并等待响应,从而监控多个运行的Redis实例
一个哨兵对Redis服务器进行监控可能会出现问题,我们可以使用多个哨兵进行监控,各个哨兵之间还会监控,这样就形成了多哨兵模式
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线,当后面的哨兵也检测到主服务器不可用且数量到达一定值时,哨兵之间会进行一次投票,投票结果由一个哨兵发起,进行failover故障转移操作,通过订阅发布模式,让每个哨兵把自己监控的从服务器切换为主机,这个过程称为客观下线
测试
先搭建一主二从模式
- 编写哨兵配置
vim sentinel.conf
sentinel monitor myredis 127.0.0.1 6379 1
// myredis 自己取的监控名称
// 监控ip和端口
// 1代表当一个哨兵主观认为主机断开,就可以客观认为主机故障,然后开始选举新的主机
- 开启哨兵
redis-sentinel ./rainhey_config/sentinel.conf
3. 此时关闭主机6379
4. 观察6380和6381,可以发现81已经自动变为主机,80变为其从机
5. 哨兵日志
主机断开后,这时会从从机中选择一个服务器作为主机(投票算法)
如果这时再连回之前的主机可以发现,之前的主机已经变为从机了
优缺点
优点:
- 哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
- 主从可以切换,故障可以转移,系统的可用性更好
- 哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
- Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
- 实现哨兵模式的配置其实是很麻烦的,里面有很多配置项
哨兵模式的全部配置
# 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
缓存穿透和雪崩
缓存穿透
概念
当用户想要查询某个数据,Redis内存数据库中没有,也就是缓存没有命中,于是向持久层数据库发起请求;当用户很多的时候,Redis缓存都没有命中,都去请求持久层数据库,会造成持久层数据库很大的压力,这就是缓存穿透
解决方案
-
布隆过滤器
-
缓存空对象
一次请求若在缓存和数据库中都没找到,就在缓存中放一个该请求的空对象用于处理后续请求
缓存击穿(量太大,数据过期)
缓存击穿是指缓存中存在一个热点key,大并发集中对这点进行访问,当这个key在失效后瞬间,持续的大并发就直接请求持久层数据库,导致数据库压力过大
解决方案
- 热点数据永不过期
- 加互斥锁(分布式锁)
使用分布式锁保证每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁需要等待,这样就将高并发的压力转移到了分布式锁
缓存雪崩
在某一个时间段,缓存集中过期或者Redis宕机
解决方案
- Redis高可用:搭建Redis集群
- 限流降级:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待
- 数据预热:把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中;在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀