NOSQL: Not only SQL
NOSQL的四大分类
KV键值对:
- 新浪:Redis
- 美团:Redis + Tail
- 阿里、百度: Redis + memecache
文档型数据库(bson格式 和json一样)
- MongoDB(一般必须要掌握)
MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档!
MongoDB是一个介于关系型数据库和非关系型数据库数据中间的产品!MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的!
列存储数据库
图关系数据库
Redis入门
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis能干嘛?
- 内存存储、持久化,内存中是断电即失,持久化很重要(rdb、aof)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(浏览量)
- …
特性
- 多样的数据类型
- 持久化
- 集群
- 事务
- …
Linux下安装Redis
1.官网下载Redis
https://www.redis.com.cn/
2.放到Linux的opt目录下
mv *** /opt
3.解压
4.进入redis文件下
Redis6以上需要gcc版本在7以上。
运行下面命令升级gcc
#第一步
sudo yum install centos-release-scl
#第二步
sudo yum install devtoolset-7-gcc*
#第三步
scl enable devtoolset-7 bash
5.make
6.make install
7.
cd /usr/local/bin
8.将redis配置文件,复制到当前目录下
mkdir myconfig
cp /opt/redis-6.0.6/redis.conf myconfig
cd myconfig
ls
9.redis默认不是后台启动,修改配置文件
vim redis.conf
10.启动redis服务
通过指定的配置文件启动服务
redis-server myconf/redis.conf
11.测试连接
redis-cli -p 6379
12.查看redis进程是否开启
13.如何关闭redis服务
基础命令
清除当前数据库
127.0.0.1:6379[3]> flushdb
清除所有数据库
127.0.0.1:6379[3]> flushdall
将当前数据库的key移动到4号数据库
move key 4
设置过期时间10s
expire key10
查看当前key的剩余时间
ttl key
查看当前key的类型
type key
在后面加上value2
append key value2
Redis是单线程的!
Redis为什么单线程还这么快?
1、误区一:高性能的服务器一定是多线程的?
2、误区二:多线程(CPU上下文切换)一定比单线程效率高
速度:CPU>内存》硬盘
核心:redis是将所有的数据全部放在内存中,所以说使用单线程去操作效率是最高的。多线程(CPU上下文切换是耗时的操作),对于内存系统来说,如果没有上下文切换,效率就是最高的!redis多次读写都是在一个CPU上的,在内存情况下,就是最佳的方案!
五大数据类型(String,List,Set,Hash,Zset)
String(字符串)
127.0.0.1:6379[4]> set name zeng #设置值
OK
127.0.0.1:6379[4]> get name #获取值
"zeng"
127.0.0.1:6379[4]> keys * #获得所有的key
1) "name"
2) "age"
127.0.0.1:6379[4]> exists name #判断某一个key是否存在,如果不存在则相当于set key
(integer) 1
127.0.0.1:6379[4]> append name "qing" #追加字符串
(integer) 8
127.0.0.1:6379[4]> get name
"zengqing"
127.0.0.1:6379[4]> strlen name #获取key的长度
(integer) 8
127.0.0.1:6379[4]>
#对象
set user:1 {name:zhangsan,age:3} #设置一个user:1对象 值为json字符来保存一个对象
#user:{id}:{filed} 如此设计在redis中是可以的
127.0.0.1:6379[4]> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379[4]> mget user:1:name user:1:age
1) "zhangsan"
2) "2"
#自增操作 相当于i++
127.0.0.1:6379[4]> set article:1000:views 0 #第1000篇文章的初始浏览量为0
OK
127.0.0.1:6379[4]> incr article:1000:views
(integer) 1
getset #先get再set
127.0.0.1:6379[4]> getset db redis #如果不存在值,则返回nil
(nil)
127.0.0.1:6379[4]> get db
"redis"
127.0.0.1:6379[4]> getset db mongodb #如果存在值,获取原来的值,并设置新的值
"redis"
127.0.0.1:6379[4]> get db
"mongodb"
List
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 #通过区间获取具体的值
1) "three"
2) "two"
127.0.0.1:6379> rpush list four #将一个值或多个值插入到列表尾部(右)
(integer) 4
127.0.0.1:6379> lrange list 0 -1 #获取list中的值
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> lpop list #移除左边第一个
"three"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "four"
127.0.0.1:6379> rpop list #移除右边第一个
"four"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 0 #通过下标获取值
"two"
127.0.0.1:6379> llen list #返回列表的长度
(integer) 2
#移除指定的值
127.0.0.1:6379> lrem list 1 one
(integer) 1
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> rpush mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2 #通过下标剪裁指定长度
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
Set
Hash
Zset
geospatial地理位置(geo底层的实现原理就是Zset)
geoadd 添加地理位置
#geoadd 添加地理位置
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shengzhen
(integer) 2
geopos 获取地理位置(经度、纬度)
#geopos 获取地理位置
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijing chongqing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
geodist 返回两个给定位置之间的距离
#geodist 返回两个给定位置之间的距离
127.0.0.1:6379> geodist china:city beijing shanghai #查看上海到北京的直线距离
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing chongqing #查看北京到重庆的直线距离
"1464070.8051"
georadius 以给定的经纬度为中心,找出某一半径内的元素
#georadius 以给定的经纬度为中心,找出某一半径内的元素
127.0.0.1:6379> georadius china:city 110 30 1000 km #获取以 110 30 为中心方圆1000km内的城市
1) "chongqing"
2) "shengzhen"
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "chongqing"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist #显示到中心位置的距离
1) 1) "chongqing"
2) "341.9374"
2) 1) "shengzhen"
2) "924.6408"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord #显示他人的定位信息
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "shengzhen"
2) 1) "114.04999762773513794"
2) "22.5200000879503861"
127.0.0.1:6379> georadius china:city 110 30 1000 km count 1 #筛选指定数量
1) "chongqing"
事务
- 事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行的过程中,按照顺序执行
- 一次性、顺序性、排他性
- 事务管理(ACID):原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)
- Redis单条命令保证原子性,但Redis事务不保证原子性!
- Redis事务没有隔离级别的概念:所有的事务在命令中,并没有直接被执行,只有发起执行命令的时候才会被执行
Redis事务:
- 开启事务()
- 命令入队()
- 执行事务()
正常执行事务!
127.0.0.1:6379> multi #开启事务
OK
#命令入队
127.0.0.1:6379> set key1 v1
QUEUED
127.0.0.1:6379> get key1
QUEUED
127.0.0.1:6379> set key v2
QUEUED
127.0.0.1:6379> get key
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) "v1"
3) OK
4) "v2"
放弃事务!
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard #取消事务
OK
127.0.0.1:6379> get k4 #事务队列中的命令都不会被执行!
(nil)
编译型异常(代码有问题!命令有错),事务中所有的命令都不会被执行!
运行时异常(1/0),如果事务队列中存在语法性错误,那么执行命令的时候,其他命令是可以正常执行的,错误命令会抛出异常!
监控(Watch)
悲观锁:
乐观锁:
Jedis
Jedis是官方推荐的java连接工具!
测试
1、导入对应的依赖
<!--导入JRedis包-->
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<!-- fastjson -->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
</dependencies>
2、编码测试:
- 连接数据库
- 操作命令
- 断开连接
package com.zeng;
import redis.clients.jedis.Jedis;
public class TestPing {
public static void main(String[] args) {
//1、new Jedis对象即可
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
}
}
输出:
SpringBoot整合
说明:在SpringBoot2.X之后,原来使用的jedis被替换成了lettuce
jedis:采用的直连,多个线程操作的话是不安全的,如果想要避免不安全的,使用jedis pool连接池!
lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!
测试
1、导入依赖
<!-- 操作redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、配置连接
#配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
3、测试
package com.zeng;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//opsForValue 操作字符串,类似String
//opsForList 操作List
//获取redis的连接对象
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
System.out.println(connection.ping());
redisTemplate.opsForValue().set("name","zeng");
System.out.println(redisTemplate.opsForValue().get("name"));
}
}
直接传递对象会报错!需要序列化
package com.zeng;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zeng.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//opsForValue 操作字符串,类似String
//opsForList 操作List
//获取redis的连接对象
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
System.out.println(connection.ping());
redisTemplate.opsForValue().set("name","zeng");
System.out.println(redisTemplate.opsForValue().get("name"));
}
@Test
public void test(){
//真实的开发一般都使用json来传递对象
User user = new User("狂神说", 3);
String jsonUser = null;
try {
jsonUser = new ObjectMapper().writeValueAsString(user);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
redisTemplate.opsForValue().set("user",jsonUser);
System.out.println(redisTemplate.opsForValue().get("user"));
}
}
输出:
两个持久化方案:
RDB(Redis Database)
RDB 触发机制分为使用指令手动触发和 redis.conf 配置自动触发。
手动触发 Redis 进行 RDB 持久化的指令的为:
- save ,该指令会阻塞当前 Redis 服务器,执行 save 指令期间,Redis 不能处理其他命令,直到 RDB 过程完成为止。
- bgsave,执行该命令时,Redis 会在后台异步执行快照操作,此时 Redis 仍然可以相应客户端请求。具体操作是 Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。Redis 只会在 fork期间发生阻塞,但是一般时间都很短。但是如果 Redis 数据量特别大,fork 时间就会变长,而且占用内存会加倍,这一点需要特别注意。
自动触发 RDB 的默认配置如下所示:
save 900 1 # 表示900 秒内如果至少有 1 个 key 的值变化,则触发RDB
save 300 10 # 表示300 秒内如果至少有 10 个 key 的值变化,则触发RDB
save 60 10000 # 表示60 秒内如果至少有 10000 个 key 的值变化,则触发RDB
如果不需要 Redis 进行持久化,那么可以注释掉所有的 save 行来停用保存功能,也可以直接一个空字符串来停用持久化:save “”。
rdb文件
1、save的规则满足条件下,会触发rdb规则生产rdb文件
2、执行flushall也会触发rdb规则生产rdb文件,flushdb不会触发
3、退出redis,也会产生rdb文件
AOF(Append Only File)
配置文件:
默认是不开启的,我们只需要将appendonly改为yes就开启了aof,重启redis就可以生效!
如果这个aof文件有错误,这时候redis是启动不起来的,需要修复这个aof文件,redis给我们提供了一个工具 redi-check=aof --fix
Redis发布订阅
Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。
PSUBSCRIBE pattern [pattern …]
订阅一个或者多个符合模式匹配的频道
【假设客户端同时订阅了某种模式和符合该模式的某个频道,那么发送给这个频道的消息将被客户端接收到两次,只不过这两条消息的类型不同,一个是message类型,一个是pmessage类型,但其内容相同。 】
PUBSUB subcommand [argument [argument …]]
返回由活跃频道组成的列表,即可以查询订阅与发布系统的状态
PUBLISH channel message
发送消息到指定的频道
【其返回值为接收到该消息的订阅者的数量】
SUBSCRIBE channel [channel …]
订阅一个或多个频道
【其返回值包括客户端订阅的频道,目前已订阅的频道数量,以及接收到的消息,其中subscribe表示已经成功订阅了某个频道】
PUNSUBSCRIBE [pattern [pattern …]]
退订所有符合模式匹配的频道
UNSUBSCRIBE [channel [channel …]]
退订一个或多个频道
【Redis采用UNSUBSCRIBE和PUNSUBSCRIBE命令取消订阅,其返回值与订阅类似。
由于Redis的订阅操作是阻塞式的,因此一旦客户端订阅了某个频道或模式,就将会一直处于订阅状态直到退出。
在SUBSCRIBE,PSUBSCRIBE,UNSUBSCRIBE和PUNSUBSCRIBE命令中,
其返回值都包含了该客户端当前订阅的频道和模式的数量,当这个数量变为0时,该客户端会自动退出订阅状态。
Redis主从复制
概念
主从复制:指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。Redis的主从复制是异步复制,异步分为两个方面,一个是master服务器在将数据同步到slave时是异步的,因此master服务器在这里仍然可以接收其他请求,一个是slave在接收同步数据也是异步的。
默认情况下,每台redis服务器都是主机。
主从复制的作用
- 数据备份:主从复制实现了数据的热备份,是持久化之外的另一种数据备份方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复。
- 读写分离:可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量;
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
环境配置
复制三个配置文件,然后修改相应的配置信息
1、端口
2、pid
3、log文件名字
4、dump.rdb名字
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #认定6379为主机
OK
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:0
master_sync_in_progress:0
slave_repl_offset:42
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:2341cf975492a6edc83d59d54d117c8ba0b47406
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:42
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:42
127.0.0.1:6380>
读写分离
主机可以写,从机不能写只能读
工作原理
master服务器会记录一个replicationId的伪随机字符串,用于标识当前的数据集版本,还会记录一个当前数据集的偏移量offset,不管master是否有配置slave服务器,replication Id和offset会一直记录并成对存在。
当master与slave正常连接时,slave使用PSYNC命令向master发送自己记录的旧master的replication id和offset,而master会计算与slave之间的数据偏移量,并将缓冲区中的偏移数据同步到slave,此时master和slave的数据一致。
而如果slave引用的replication太旧了,master与slave之间的数据差异太大,则master与slave之间会使用全量复制的进行数据同步。
全量复制:slave服务在接受到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master继续将新的收集到的修改命令依次传给slave,完成同步
只要重新连接master,一次完全同步(全量复制)将被自动执行!
哨兵模式
Redis的主从复制模式下, 一旦主节点由于故障不能提供服务, 需要人工将从节点晋升为主节点, 同时还要通知应用方更新主节点地址, 对于很多应用场景这种故障处理的方式是无法接受的。 可喜的是Redis从2.8开始正式提供了Redis Sentinel(哨兵) 架构来解决这个问题。
Redis主从复制的缺点:没有办法对master进行动态选举,需要使用Sentinel机制完成动态选举
1.哨兵模式介绍
- Sentinel(哨兵)进程是用于监控redis集群中Master主服务器工作的状态
- 在Master主服务器发生故障的时候,可以实现Master和Slave服务器的切换,保证系统的高可用(HA)
- 其已经被集成在redis2.6+的版本中,Redis的哨兵模式到了2.8版本之后就稳定了下来。
2.哨兵进程的作用
- 监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。
- 提醒(Notification):当被监控的某个Redis节点出现问题时, 哨兵(sentinel) 可以通过 API
向管理员或者其他应用程序发送通知。 - 自动故障迁移(Automatic failover):当一个Master不能正常工作时,哨兵(sentinel)
会开始一次自动故障迁移操作。
(1)它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master;
(2)当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用现在的Master替换失效Master。
(3)Master和Slave服务器切换后,Master的redis.conf、Slave的redis.conf和sentinel.conf的配置文件的内容都会发生相应的改变,即,Master主服务器的redis.conf配置文件中会多一行slaveof的配置,sentinel.conf的监控目标会随之调换
3.哨兵进程的工作方式
- 每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。
- 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds
选项所指定的值,则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)。 - 如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有
Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态。 - 当有足够数量的Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN),则Master主服务器会被标记为客观下线(ODOWN)。
- 在一般情况下, 每个Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令。
- 当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
4.哨兵模式的配置
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在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缓存穿透和雪崩
一、缓存穿透
1、概念
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
为了避免缓存穿透其实有很多种解决方案。下面介绍几种。
2、解决方案
(1)布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力。
2、缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
但是这种方法会存在两个问题:
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
二、缓存击穿
系统中存在以下两个问题时需要引起注意:
- 当前key是一个热点key(例如一个秒杀活动),并发量非常大。
- 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
解决方案:
1. 分布式互斥锁
只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout)
2. 永不过期
-
从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
-
从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去更新缓
2种方案对比: -
分布式互斥锁:这种方案思路比较简单,但是存在一定的隐患,如果在查询数据库 + 和重建缓存(key失效后进行了大量的计算)时间过长,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。
-
“永远不过期”:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。
三、缓存雪崩
1、概念
缓存雪崩是指,缓存层出现了错误,不能正常工作了。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
2、解决方案
(1)redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。
(2)限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
(3)数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。