一、什么是nosql?
nosql = not only sql ,不仅仅是sql,泛指非关系型数据库。
随着互联网的高速发展,传统的关系型数据库(mysql、oracle…)已无法完全应对用户的需求,特别是大规模、高并发的网站,用传统的数据库来应对已经暴露出很多问题,而非关系型数据库在可以很好的来解决这些问题。
nosql数据库的特点:易扩展、大数据量高性能、数据类型多样灵活。
常见的nosql数据库:redis、mongoDB、BerkeleyDB、CouchDB、memcache等
现在网站基本都是 :传统的关系型数据库 + 非关系型数据库 一起使用。
二、Redis概述及安装
Redis:REmote DIctionary Server(远程字典服务器)
redis是一款完全免费开源的,C语言编写的,高性能的K/V键值对分布式内存数据库,基于内存运行,且支持持久化的nosql数据库,是当下最热门的nosql数据库之一。
官方记录:Redis一秒内可以写8万次,读11万次。
Redis的下载与安装:
Windows环境:官网直接下载,解压即可,点击图下图所示即可打开redis服务或客户端连接
Linux环境:(由于实际开发中都是在linux上部署redis,所以redis在linux环境下的安装才是重点,windows环境下的安装看看就行了)
-
打开redsi官网:https://redis.io/ ,点击下载,此处下载的就是linux版本
-
下载完成后,将下载好的redis-6.2.3.tar.gz放到linux系统的/opt目录下(可通过xftp工具上传到linux中)
-
在当前目录解压此文件:tar -zxvf redis-6.2.3.tar.gz
-
解压完成后出现redis-6.2.3文件夹,进入该文件夹目录:cd redis-6.2.3
-
安装redis(因为我们下载的linux版本是源码,所以我们要进行编译安装)
5.1 安装gcc:yum -y install gcc ,yum -y install gcc-c++ (若这两个已安装了,此处就不用安装了)
5.2 执行make命令 (将源码编译成二进制文件)
5.3 执行make install DESTDIR=/xx/xx(DESTDIR参数可以指定安装的目录) -
查看默认安装目录/usr/local/bin(若install时我们没有指定安装目录,则默认就安装到这个目录下了),改目录下有很多redis的启动程序
-
将第4步中解压好的redis-6.2.3文件夹下的redis的配置文件redis.conf 拷贝到我们新建的文件夹hhlconfig下,以后每次启动redis-server的时候就用拷贝过来的这个redis-server启动即可,之后我们要对这个redis.conf做一系列的配置更改。
-
安装到此结束!启动redis 服务端 : redis-server hhlconfig/redis.conf,启动redis客户端redis-cli -h IP -p Port , 客户端输入ping输出pong后表示连接成功!客户端输入shutdown命令关闭服务端。
三、Redis基本常识
注意,以下这些命令和开发中的jedis或redistemplate中的API名称基本一致,所以这些命令是需要记一下的
- redis 有16个数据库(该数量在redis.conf中可配置),默认使用第一个,index为0
- auth 123456 输入redis密码
- select index 切换数据库
- dbsize 查看当前数据库key的数量
- flushdb 清空当前数据库
- flushall 清空所有数据库
- keys * 查看所有key
- exists k1 判断某个key是否存在 1 存在 0不存在
- move k1 7 把k1移动到数据库7中
- expire k1 10 设置k1 10秒钟后过期,过期后就没了
- ttl k1 查看k1还剩几秒过期
- type k1 查看k1的类型
四、Redis中的数据类型
redis中分为:
五大数据类型:String、List、Set、Hash、Zset
三种特殊数据类型:GEO、HyperLogLog、BitMap
String : key–value (最最最常用的一个类型)
- set k1 v1 设置值
- get k1 获取值
- del k1 删除值
- append k1 “hello” 给k1后面增加值
- strlen k1 获取k1的长度
- set count 0
- incr count count值增加1
- decr count count值减去1
- incrby count 10 count值增加10
- decrby count 10 count值减去10
- getrang k1 0 2 获取k1索引0-2的部分值
- getrang k1 0 -1 获取k1的所有值
- setrang k1 2 “a” 将k1索引值2的字符替换为a
- mset k1 v1 k2 v2 k3 v3 同时设置k1 k2 k3 三个key
- mget k1 k2 k3 获取k1 k2 k3 三个key
List: 和java中的list集合类似,一个key 可以对应多个value
- lpush list a 将a插入到list列表头部(左)
- rpush list b c d 将b c d插入到list列表尾部(右)
- lrange list 0 -1 遍历list里的所有元素
- lpop list 删除list集合中的从左数第一个元素
- rpop list 删除list集合中从右数第一个元素
- lindex list 1 返回list数组中索引为1的元素
- llen list 返回list列表长度
Set : 和list类是,但Set中不允许有重复元素
- sadd myset a b c 将a b c插入myset集合中
- smember myset 遍历myset集合中的所有元素
- srem myset a b 删除myset集合中的元素a b
- spop myset 删除myset集合
Hash :相当于java中的map ,key就是map的名字,value就是一个个的键值对
- hset myhash zhangsan 20 lisi 21 将键值对 zhangsan 20 lisi 21 存放到myhash中
- hget myhash zhangsan 获取myhash中zhangsan对应的值(类似java中的map.get(“zhangsan”))
- hget myhash 遍历myhash中的所有键值对
- hdel myhash lisi 删除myhash中的lisi键值对
Zset(sortedset) : 在Set的基础上,又增加了排序的功能(每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。)
回顾上面的set集合放置元素为: sadd myset k1 k2 k3
Zset 在放置元素时要多个source参数: zadd myZset source1 k1 source2 k2 source3 k3
就相当于在放的时候就根据source这个分数来排好序了
- zadd mysort 60 zhangsan 70 lisi 80 wangwu 插入mysort集合中 zhangsan lisi wangwu 并每个元素有对应的分数
- zrange mysort 0 -1 withscores 遍历mysort集合并展示每个元素的分数
- zrem mysort wangwu 删除mysort集合中的wangwu元素
三种特殊数据类型:GEO、HyperLogLog、BitMap
GEO地理位置:用于存储地理位置,然后可以实现两个位置之间的距离查询,附近的人等和定位相关的一些功能。(底层实现原理其实是Zset)
-
geoadd chinaCity 121.48 31.40 shanghai 120.21 30.20 hangzhou 113.88 22.55 shenzhen 106.54 29.40 chongqing
-
geopos chinaCity beijing 获取beijing的经纬度
-
geodist chinaCity beijing shanghai km 查询 beijing 到 shanghai 的直线距离,单位为km,默认不写是m
-
georadius chinaCity 100 30 1000km withdist 查询经度100纬度30附件1000km的 chinaCity 下的所有城市,并且显示距离
-
georadiusbymember chinaCity beijing 1000km 查询beijing附近1000km以内的城市
因为geo底层原理实际上用的是Zset 所以可以通过Zset中的命令操作geo
zrange chinaCity 0 -1 遍历chinaCity
zrem chinaCity 删除chinaCity
HyperLogLog:用来做基数统计的(基数就是一个集合中不重复的元素的个数)
应用场景:例如要统计一个网站的浏览量,每个用户每天访问多次算一次,此时用传统的方法可以用Set集合(因为set可以存放不重复元素),存储用户的id,但是如果浏览量特别大,id就会特别多,导致内存占用过高。而HyperLogLog由于底层算法的设计使得其中每个键只需要花费12kb的内存(固定的),且可以存2的64次方的不同元素的基数。
注意:数据量过大时,HyperLogLog有0.81的错误率,但是对于UV(用户浏览量)这种不需要很精确的数据可忽略不计。
- pfadd sign a b c d e f g g g 向sign中插入元素
- pfcount sign 统计sign基数 结果为7
- pfmerge sign3 sign1 sign2 把sign1与sign2合并为sign3
BitMap:BitMap 的数据结构就是2进制 01010101 这种的
应用场景:统计用户的某些信息,是否打卡,是否登录,是否活跃 等这种只有两个结果是和否的连续性的统计。
使用BitMap的好处其实和HyperLogLog的好处一样,都是为了节省内存!
- setbit sign 0 1
- setbit sign 1 0
- setbit sign 2 1
- setbit sign 3 0
- setbit sign 4 0
- setbit sign 5 1
- setbit sign 6 0 存放sign ,第一个数字是索引 第二个数字是该索引对应的值
- getbit sign 3 获取sign下索引为3的对应的值,若没有设置索引为3的值,则默认是0
- bitcount sign 统计sign下值为1的个数
五、Redis中的事务
redis事务的本质是一组命令的集合,就是开启事务、编写命令集合(将命令序列化)、提交事务。
redis事务没有原子性:redis单条命令是符合原子性的,但redis事务一般是包含了一系列的命令,如果其中一条命令执行失败了,整个事务不会回滚,其余命令仍会执行。
redis事务没有隔离性的概念:
原本的事务的隔离性是指:一个事务要访问的数据正在被另一个事务修改,那么只要修改数据的这个事务还未提交,那访问数据的那条事务是不会受到这条还未提交的事务的影响的。比如现有有个交易是从A 账户转100元至B账户,在这个交易还未完成的情况下,如果此时B查询自己的账户,是看不到新增加 的100元的。
而redis的事务由于是在执行提交事务exec这个动作时,才会真正执行命令操作,所以不存在上述的隔离性而言。
redis中事务的步骤:
- multi 开启事务
- decrby myaccount 20 --放入命令
- incrby youaccount 20 --放入命令
- exec 执行事务
- discard 事务回滚(取消该事务的提交)
虽然redis事务没有原子性,但是redis提供了watch监控!
redis中的监控 watch 可以用来监控一个key,当此key发生变化时,事务提交失败,类似乐观锁中的加版本号
- watch myaccount – 监控myaccount
- multi 开启事务
- decrby myaccount 20 --放入命令
- incrby youaccount 20 --放入命令
- 此时如果我们在另一个线程里修改了myaccount的值
- exec 这里执行提交事务就会失败
unwatch 命令用于取消watch命令下监控的所有key
注意:一但执行 EXEC 开启事务的执行后,无论事务是否执行成功, WARCH 对变量的监控都将被取消。故当事务执行失败后,需重新执行WATCH命令对变量进行监控,并开启新的事务进行操作。
六、Redis在项目中的使用
Jedis:是redis官方推荐的使用java连接redis的开发工具
- 创建maven项目,导入jedis依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<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.58</version>
</dependency>
- 编写jedis测试代码(基本所有的api方法名和redis指令都是相同的)
Jedis jedis = new Jedis("111.111.111.22",6379); //输入ip 和 端口号
jedis.auth("123456"); // 如果redis有密码则要设置密码
System.out.println(jedis.ping()); // 得到pong 则表示连接成功
jedis.select(1); //切换数据库
jedis.flushDB(); //清空当前数据库
System.out.println(jedis.get("sign")); //获取string类型的sign key
Set<String> keys = jedis.keys("*"); // 遍历所有key
System.out.println(keys.toString());
jedis.lpush("list","1","2","3");
jedis.rpush("list","4","5","6"); //
List<String> list = jedis.lrange("list", 0, -1);
System.out.println(list);
jedis.lpop("list");
System.out.println(jedis.lrange("list", 0, -1));
SpringBoot项目中整合Redis:
SpringBoot项目中一般使用RedisTemplate提供的方法来操作redis
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 编写配置文件application.yml
spring:
redis:
host: 127.0.0.1 port: 6379
password: 123456
jedis:
pool:
max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 500 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
- 配置RedisTemplate
springboot默认给我们提供了RedisTemplate ,但是它提供的那个RedisTemplate的泛型是<Object,Object>,写代码不方便,需要写好多类型转换的代码;我们需要一个泛型为<String,Object>形式的RedisTemplate。并且,它提供的这个RedisTemplate没有设置数据存入Redis时,key及value的序列化方式。所以一般来说我们都自己去封装一个RedisTemplate
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(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.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
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;
}
}
- 编写RedisUtil工具类,因为直接使用redisTemplate的方法来操作redis还是有些麻烦,所以我们要自己对redisTemplate再次封装一下,写成一个工具类,之后操作redis就用我们这个RedisUtil工具类,更方便。
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try{
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
以下内容是redis高级部分,面试的高频问点,必须掌握~
七、Redis配置文件redis.conf解析
redis.conf就是我们在启动redis是所指定的那个配置文件,这个文件中有很多配置信息,下面列举一些比较常用的配置信息:
network网络配置:
- bind 127.0.0.1 绑定的ip,就是允许哪个ip可以访问redis服务端,默认是本机的回环地址
- protected-mode yes 保护模式,默认是开启的,开启后,只有bind绑定的ip可以访问
- requirepass 密码 默认是空,设置后在连接时则需要输入密码(这个其实是属于安全配置里的)
- port 6379 端口号
由于我们的redis都是安装在linux上的,默认外部是不能访问的,我们需要进行如下配置使外部可以访问:
- bind 注释掉,设置一个密码,protected-mode 设置为yes 开启保护模式(这应该是开发中常用的配置)
- bind注释掉,密码也设置为空,且protected-mode 设置为no 关闭保护模式。(此时外部随便一个ip都可以访问,但是此时是很危险的,相当于把此端口暴露在了外部网络环境中)
- 当然也可以bind 指定ip ,同时设置密码等等组合着来…
注意:protected-mode 保护模式默认是开启的(一般不要关闭),此时必须配置bind 或者 设置个密码
通用配置:
- daemonize yes 默认是no, 开启后redis将作为守护线程运行(简单来说就这个配置开启后,你启动redis它会自动后台运行,而不是占用着当前窗口)
- logfile “” 日志文件位置
- databases 16 数据库数目 默认是16个
安全配置:
- requirepass “” redis密码 默认是空,设置后每次连接需要输入密码验证
限制配置:
- maxclients 10000 # 设置能连上redis的最大客户端连接数量
持久化相关的配置 在下面的持久化章节里讲解
redis客户端里可以通过config命令来查看所有或指定的某个配置信息:
- config get * 获取所有配置
- config get requirepass 获取密码
- config set requirepass 设置密码(注意,这里的设置只针对本次启动的服务,服务重启后这个密码失效)
- auth 123456 输入密码
八、Redis持久化
持久化:就是把redis内存中的数据存储到磁盘上,redis持久化主要分为RDB和AOF两种持久化策略
RDB : Redis DataBase,rdb持久化的原理是在指定的时间间隔内,将内存中的数据以快照的方式写入磁盘(也就是写到dump.rdb文件里),再次启动redis时,回去读取该dump.rdb文件来恢复数据。
关于rdb持久化在redis.conf中的配置如下:
- save 900 1 # 900秒(15分钟)内至少1个key值改变(则进行数据库保存–持久化)
save 300 10 # 300秒(5分钟)内至少10个key值改变(则进行数据库保存–持久化)
save 60 10000 # 60秒(1分钟)内至少10000个key值改变(则进行数据库保存–持久化)
以上这些是redis默认的rdb规则,由于aof持久化需要手动开启,所以redis默认就是采用rdb持久化方式
- stop-writes-on-bgsave-error yes – 持久化出现错误后,是否依然进行继续进行工作
- dbfilename dump.rdb --rdb持久化文件名称
- dir ./ --dir 数据目录,就是存放持久化.rdb .aof文件的位置,默认是在当前服务所在目录下。
rdb最关键的配置就是上面说到的redis.conf中的save,save的规则可以配置多个。(若要关闭rdb持久化,则注释掉所有的save规则即可)
如何触发rdb持久化?
- 首先就是上面说到的配置文件中save的规则,满足配置规则后则会触发
- 手动触发,执行save命令或者bgsave命令。两者的不同是:save 时只管保存,其他不管,全部阻塞。 bgsave 会异步进行rdb操作,这个更好一些。
- flushall命令,清空所有数据库时也会产生dump.rdb文件,但是空的,所以没有意义!
- 服务正常退出时,也会进行持久化产生dump.rdb文件
想要恢复数据的话,只需将dump.rdb文件放到redis服务目录下,启动时就会自动恢复数据了
rdb原理:redis在开启服务后,会自动fork出一个子进程,该子进程专门用来进行rdb操作,此时父进程就不用进行任何的io操作了都交由子进程来,这样父进程的响应速度会更快,所以说rdb持久化方式很大程度上提高了redis的性能
rdb持久化的优点和缺点:
优点:适合大规模的数据恢复,因为采用的是快照的原理,所以很快。
缺点:rdb只适合对数据完整性和一致性要求不高的数据,因为rdb是在一定的间隔时间内持久化一次,如果服务突然down掉,那最近的一次间隔内的数据由于还没保存,所以会丢失。
AOF:Append Only File ,AOF持久化是以日志的形式来记录每一个写操作,将redis执行过的所有指定记录下来(读操作不记录)到appendonly.aof文件中(由此其实就能看出来aof相对rdb会慢很多),只需追加文件内容,不能修改文件。redis启动时,会根据该appendonly.aof文件把记录下来的所有指令从头到尾执行一遍,以此恢复原来的数据。
aof持久化在redis.conf中的相关配置:
- appendonly no 是否采用aof的持久化方式,默认是no, 因为一般来说rdb的持久化已经够用了
- appendfilename “appendonly.aof” aof持久化的文件名称
- appendfsync everysec aof持久化策略的配置,默认是everysec ,主要有以下参数
#no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
#always表示每次写入都执行fsync,以保证数据同步到磁盘。
#everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。(默认)
aof恢复数据的方法和rdb类似,只要把appendonly.aof文件放到服务目录下,启动redis服务时则会自动读取aof文件内容恢复数据,前提是要开启aof持久化模式
appendonly.aof被恶意篡改后,可以使用redis-check-aof --fix appendonly.aof 指令来修复appendonly.aof文件
由于aof文件是记录了每一个写操作的指令,随着时间的流逝那这个appendonly.aof文件会越来越大,这样会造成很多问题,比如redis服务器的压力,启动redis时通过aof还原数据时间过长。所以引出了aof重写的功能来解决此问题。
AOF重写原理:aof文件体积扩大到一定值时,当前会进程fork出一个子进程来专门用来进行aof重写,这样的好处是,父进程仍然可以处理其他命令,两个进程可以同时进行。
但这样又会产生一个新的问题,子进程在进行AOF重写期间,服务器进程还要继续处理命令请求,而新的命令可能对现有的数据进行修改,这会让当前数据库的数据和重写后的AOF文件中的数据不一致。
为了解决这个问题,redis增加了aof重写缓存的概念,这个缓存在aof重写子进程启动时随之启用,父进程在执行完写命令后会同时将该写操作追加到aof缓冲区和aof重写缓冲区。
在aof重写子进程重写完毕后,redis会将aof重写缓冲区的内容写到新的aof重写后的文件中,然后对新的aof文件进行更名并替换老的aof文件。
注意:aof重写并不是去读取已存在的aof文件来精简内容,而是去读当前数据库里的内容,把一些指定结合起来
比如本来aof文件中存放的是:
set k1 v1
set k2 v2
set k3 v3
那么在aof重写时回去精简内容,用一个命令 mset k1 v1 k2 v2 k3 v3 来代替上面3个命令,从而缩减了aof文件的大小。
aof重写触发条件:(是重写的触发条件,别和aof持久化配置搞混了)
- 手动输入命令bgrewriteaof触发
- 配置文件redis.conf中设置重写触发条件
Auto-aof-rewrite-min-size 64mb aof文件大于此值,默认是64M
Auto-aof-rewrite-percentage 100 当前aof文件和最后一次aof文件的大小相比增长了100% ,默认是100 (满足此两条配置也会触发aof重写)
aof的优点和缺点:
优点:由于aof是保存每条写的记录,所以数据完整性相对较好
缺点: 在存储相同的数据集的情况下,appendonly.aof文件要远大于dump.rdb文件,且不管是恢复数据的速率还是运行效率,aof相对rdb都要慢很多。
Tips:
- 两种持久化策略RDB和AOF都开启的话,redis重启默认优先载入aof文件来恢复数据
- 其实若只用到redis做缓存功能的话没必要做持久化
九、Redis发布订阅
redis提供了发布订阅的这种消息队列模式,具体来的流程就是:发布者发布消息到频道, 订阅者订阅频道拿到消息
发布者–>频道—>订阅者
基本命令有:
- subscribe hhlchat 消费者订阅消息频道hhlchat
- publish hhlchat “hello hhl” 生成者发布消息到频道hhlchat
- psubscribe hhlchat01 hhlchat02 … 订阅多个频道
- unsubscribe hhlchat 取消订阅指定的频道
应用场景:即时聊天,群聊
redis的发布订阅属于消息队列中的一种,这里简单了解,知道redis有这个功能即可,消息队列中间件才是重要的学习内容
十、Redis主从复制
主从复制,是指将一台Redis服务器上的数据复制到其他Redis服务器上。前者称为主节点,后者称为从节点;数据的复制是单向的,只能由主节点到从节点。主节点Master以写为主,从节点Slave以读为主。
默认情况下,每台Redis服务器都是从节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能由一个主节点。
主从复制的作用:(或者说为什么要有主从复制这个功能?)
- 数据冗余:主从复制时间了数据的热备份,因为主节点和从节点上数据库的信息是一致的,也就相当于对数据进行了备份,是持久化之外的一种数据冗余的方式。
- 故障恢复:主节点出现故障后,可以由从节点提供服务,实现快速的故障恢复。
- 负载均衡:读写分离,主节点只负责写,从节点负责读,分担服务器负载,大大提高redis服务器的并发量
- 高可用性:主从复制是哨兵模式和redis集群实施的基础,所以主从复制是redis实现高可用的基础。
在真实的开发环境中,只用一台Redis服务器是万万不能的,单台服务器收到了很多限制,比如: 故障后则表示redis不能用了,因为只有这一台; 单台服务器处理所有的请求,负载压力过大;单台服务器内存容量有限。
所以我们要配置redis集群,而redis集群和主从复制是分不开的,一般来说使用redis都是读多写少,所以就可以采用主从复制的特点来部署redis集群:
主从复制的redis集群环境配置:以下是在一台服务器上模拟3台redis集群的效果(一主二从),所以要修改配置文件
由于我们模拟的是一台linux上配置3台redis集群,所以要进行如下配置:
- 拷贝3份redis.conf配置文件
- 每一份redis.conf中修改如下信息:port 端口号、pidfile Pid文件名、logfile log文件的位置、dbfilename rdb持久化的文件名(修改这些是为了让每一个redis服务启动时端口或文件不冲突)
- 然后就可以将3台redis服务分别启动了,redis-server redis6379.conf、redis-server redis6380.conf、redis-server redis6381.conf
因为redis默认都是主节点,所以我们要配置的是从节点:
- slaveof 主库IP 主库Port 在从节点上配置对应主节点的ip和端口号(这里的是指令,每次重启redis都要输入该指令来配置,若 不想每次都配置,可直接在redis.conf配置文件中配置)
- info replication #查看节点配置信息
两个从节点配置完毕后,再次分别查看3台redis下的info replication信息如下:
主机既可以写也可以读,从机只能读。
主机挂了,查看从机信息(从机配置信息不变),主机恢复,再次查看信息(主机还是有两个从机,从机配置不变)
从机挂了,查看主机信息(少了一个从机),从机恢复,查看从机信息(由于没在redis.conf中配置slaveof,此时从机变为主机)
谋朝篡位:slaveof no one ,当主节点down掉后,从节点输入该命令,会将自己变为主节点,且之前的其他从节点都会链接到这个新的主节点上。
哨兵模式:
由上面的slaveof no one命令知道,在主节点挂掉后我们可以手动执行slaveof no one命令使得一台从节点变为主节点。但是这个动作毕竟需要我们人工干预,手动去执行,费时费力,而且还会造成一段时间内服务不可用。redis 2.8版本后开始正式提供了Sentinel哨兵模式来解决这个问题。
哨兵模式是一个独立的进程:
上面标红的就是用来启动哨兵模式的进程。
哨兵模式的原理:哨兵是一个独立的进程,哨兵通过发送命令,等待redis服务器响应,从而监控运行的多个redis实例。
这里的这个哨兵有两个作用:
- 哨兵通过发送命令,让redis返回信息来达到监控redis的运行状态。(有点类似eureka中监控服务的心跳包)
- 一旦监控到主节点宕机,会立刻将从节点中的一个选为主节点,然后通过发布订阅模式通知其他的从节点,修改配置文件,让它们切换主机。
但是一个哨兵往往是不行的,我们需要配置多个哨兵来共同监控redis
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
模拟哨兵模式,这里的话按第一张哨兵的图来模拟,即3台redis服务,一主二从,1个哨兵。
- 新建配置文件sentinel.conf 名字不能写错,redis-sentinel 哨兵进程的启动需要依靠这个配置文件
- 配置文件中写入配置: sentinel monitor mylinux01(这个是被监控主机名) 127.0.0.1 6379 1 ,最后这个1代表主机挂掉后slave投票看让谁接替成为主机,得票数多少后成为主机,因为我们就一个哨兵这个就设置为1了
- 启动哨兵redis-sentinel sentinel.conf
- 测试主节点挂掉后,从节点变为主节点
注意:即使down掉的主节点恢复正常后,它也不是主节点了,它会变成新主节点的从节点了。
哨兵模式的优点和缺点:
优点:哨兵模式是主从模式的升级,可用性更高
缺点:redis在线扩容很复杂,哨兵模式的配置也比较麻烦(上面哨兵配置文件中只写了一条配置信息,其实还有很多的配置信息)
十一、缓存穿透和雪崩
redis缓存的使用,极大程度上提高了系统的性能和效率,有效应对了高并发的问题。但随之而来的也就出现了数据一致性的问题,如果对数据一致性要求极高,那就不能使用缓存了。
另外就是缓存穿透、缓存击穿、缓存雪崩的问题。(这也是面试的高频问点了)
缓存穿透:用户查询一个数据,发现缓存里没有,也就是说缓存没有命中,然后去数据库查询,发现数据库也没有,查询失败。当很多用户都来查询这个没有的数据,因为缓存里没有,所以这么多的请求就都去数据库查询了,这样就给数据库造成了很大的压力,这就叫缓存穿透。(划重点:因为所查询的数据缓存和数据库都没有,所以每次查询都去数据库查了)
解决方案:
- 接口层增加过滤器对数据校验,比如根据id查询用户数据,id<1的就过滤掉不去查询(布隆过滤器可以了解一下)
- 数据库没查到的key,可以设置key的值为null,然后放到缓存中,过期时间可以设置的短一些如30秒。当然这种方式是有些问题的,比如若很多数据在数据库中都查不到,那岂不是把这个查不到的key都放到缓存中了吗,这就造成了缓存空间的占用;再一个即使设置了key–null的过期时间(假如刚设置null到缓存中后,这个key有值了并存到了数据库),那这段时间还是会存在这个期间内缓存层和存储层的数据不一致的情况。
缓存击穿:由于某个key是一个热点,在不停的扛着高并发(类似微博热点),大量的高并发请求都在查询这个key,在这个key过期的一瞬间(这里假设这个key有过期时间),这些大量的请求在这一瞬间就会全部去请求数据库了,数据库压力剧增,这就是缓存击穿。(划重点:所查询的某个key过期的一瞬间,请求都转到数据库)
解决方案:
- 可以设置key永不过期,那就不会出现击穿的问题了
- 加互斥锁,当缓存中这个key过期去数据库查询时,对查询这个线程加锁,其他线程走到这里只需等待就行,由此保证每个key同时只有一个线程去走后端的查询,减轻了数据库压力。
缓存雪崩:指在某一个时间段内,缓存数据大批量同时过期,大批查询量全部到数据库,从而引起数据库压力过大甚至down机。与缓存击穿不同的是,缓存击穿是指一条数据全部去查数据库,缓存雪崩是指很多数据都去查数据库了,数据库承受不了如此大的压力而down掉…
解决方案:
- 缓存数据的过期时间随机设置,保证数据不会再同一时间点大批量同时过期
- redis高可用,就是做redis集群,一台挂掉其他的还能用
- 限流降级,就是上面说的加锁的机制,使一个key同时只允许一个线程查询。
- 数据预热,在正式环境部署前,先把访问量可能会很高数据预先访问一遍,放到缓存中。