Redis(三)
一、Redis 事务
既然redis是一种NoSQL数据库,那它当然也有事务的功能,不过这里的事务和我们关系型数据库中的事务有一点点差异。
1、 使用事务
使用事务很简单:
这样就是开启事务了。
在MULTI命令执行之后,我们可以继续发送命令去执行,此时的命令不会被立马执行,而是放在一个队列中,如下:
当使用 exec 命令时,这三个命令才会去执行:
2、事务中的异常
跟 mysql 不一样的是,这里的异常没有 rollback 功能。只能说是确保这些一起执行或者监控变量。
redis中事务的异常情况总的来说分为两类:
1、进入队列之前就能发现的错误,比如命令输错;
2、执行EXEC之后才能发现的错误,比如给一个非数字字符加1;
那么对于这两种不同的异常,redis中有不同的处理策略。
对于第一种错误,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务(这个是2.6.5之后的版本做法,之前的版本做法可以参考官方文档)。如下:
a、执行错误
可以看到第一条指令错误,第二条成功,第一条不会 影响第二条:
比如自增字符串错误:
错误的指令也不会影响到正确的指令。
b、命令错误
命令错误时会立马显示错误信息:
可以看到命令错误时事务到此就终止了,后面再输入的东西也没用了,可以看到最后也没有运行第二条指令。
不同于关系型数据库,redis中的事务出错时没有回滚,对此,官方的解释如下:
Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
3、WATCH 命令
事务中的WATCH命令可以用来监控一个key,通过这种监控,我们可以为redis事务提供(CAS)行为。 如果有至少一个被WATCH监视的键在EXEC执行之前被修改了,那么整个事务都会被取消,EXEC返回nil-reply来表示事务已经失败。如下:
举个转账的例子:
现在希望开始执行事务之后,别的进程线程不能操作这个 money 的变量,如果有操作,这个事务就执行失败,确保事务执行期间这个 money 变量是一个固定的状态。那么这种时候就可以通过 watch 命令来操作(这个 key 可以给很多个,同时监控很多个变量):
事务中的WATCH命令可以用来监控一个key,通过这种监控,我们可以为redis事务提供(CAS)行为。 如果有至少一个被WATCH监视的键在EXEC执行之前被修改了,那么整个事务都会被取消,EXEC返回nil-reply来表示事务已经失败。如下:
这里开两个窗口:
接着窗口一开启监控和事务:
接着窗口二修改值:
接着窗口一修改失败,且 money 是 888:
所以这里 money 变了就会提交失败
4、UNWATCH 命令
如果监控了一个 key,突然又不想监控了,就输入 unwatch 命令。
二、Redis 快照持久化
1、引言和介绍
redis 一般用作两方面,一是缓存,缓存存储到内存中,电脑关机再开机就没了,没了就没了,反正也只是缓存;临时存储。
二是当作数据库来用,那么这个持久化就会存到硬盘上面。持久化默认是存到硬盘中去。
持久化分两种方式:
在 redis 文件夹中,会有个多出来的文件(刚开始装好 redis 时是没有这个文件的),这个就是做数据持久化备份下来的文件:
启动 redis 时会自动的从硬盘中加载这个文件并把数据恢复到内存中去。
只要把这个文件删掉, 把 redis 重启就会发现数据都没了。
默认情况下是开启的;不需要自己手动去配置,当然也可以自己修改配置:
a、关闭快照持久化
b、其他配置
2、手动触发备份和同步异步备份
快照可以手动的触发(SAVE 指令):
如果之前把备份文件删了,此时手动触发指令后,会发现备份文件会又有了。
上面这种方式的备份属于阻塞的方式,如果数据量很大会卡在这里。
还有一种备份时异步的方式,指令是:BGSAVE,后台备份:
如果执行了 shutdown,也会触发备份。其实备份的时机有很多个。
3、快照持久化的缺点
4、快照持久化流程
这里对上面作一个小结:
注意:下面的 3 是以前的版本,实际情况以配置文件中的为准。
三、Redis AOF持久化
1、AOF持久化介绍
AOF 持久化不同于快照的保存数据,而是记录一直以来操作的命令,当需要恢复数据时,会把一直以来操作的命令重新执行一遍,这样一来也相当于恢复了数据。
AOF 持久化默认是关闭的,如果想要测试这个,建议先把快照关闭了。
2、开启 AOF 备份与相关设置
开启 AOF 备份:
3、AOF备份关键点和重写与压缩
4、实践前告知
5、redis 通信协议和自定义 Java 客户端
四、Redis 主从复制
Redis 主从复制有两种方式。
1、引言和介绍
主从复制可以在一定程度上扩展redis性能,redis的主从复制和关系型数据库的主从复制类似,从机能够精确的复制主机上的内容。实现了主从复制之后,一方面能够实现数据的读写分离,降低master的压力,另一方面也能实现数据的备份。
2、开始操作主从复制
这里以一个主机两个从机举例子:
需要三个 redis,分别设置端口号为:6379、6380、6381
首先需要三份 redis.conf 文件,接着需要更改里面的配置(注意:这里因为是用一个虚拟机来操作,如果是这样建议把 aof 关闭,因为三个 redis 的操作会产出三份备份文件,但是这三份备份文件的名字是一样的,所以会互相覆盖,如果是三台虚拟机分别操作就没这个问题。或者在配置文件中设置各个 redis 的 AOF 备份文件名不同也行。):
也可以将 redis.conf 文件更名为 redis6379.conf,方便我们区分,然后把 redis6379.conf 再复制两份,分别为 redis6380.conf 和 redis6381.conf。
打开redis6379.conf,将如下配置均加上6379,(默认是6379的不用修改),如下:
port 6379
pidfile /var/run/redis_6379.pid
logfile "6379.log"
dbfilename dump6379.rdb
appendfilename "appendonly6379.aof"
接着,分别打开redis6380.conf和redis6381.conf两个配置文件,将第二步涉及到6379的分别改为6380和6381:
全局搜索,将 6379 全部替换为 6380。
这样以来基本就搞好了,但是这样直接启动三个都是主机。且开启的时候后两个需要指定端口号:
要想把主机改成从机,需要去配置文件里面修改:
这时候保存退出再重新运行,查看信息,这时就是从机了:
这时候如果测试:在主机中存一条数据,会发现两个从机都有一样的数据。主机中的数据,会自动的同步到从机上面来。
所以以后写数据在主机中写,读数据从从机上面读。 这就是读写分离了。
自行测试也可以发现从机上面是不能写数据的:
默认情况下从机是只读的,当然也可以设置成可以写(但是不建议这么做):
但是从机即便可以写数据,也不会把数据反写到主机中去,因此会导致数据不一致。
3、主从复制注意点
1、如果主机已经运行了一段时间了,并且了已经存储了一些数据了,此时从机连上来,那么从机会将主机上所有的数据进行备份,而不是从连接的那个时间点开始备份。
2、配置了主从复制之后,主机上可读可写,但是从机只能读取不能写入(可以通过修改redis.conf中 replica-read-only 的值让从机也可以执行写操作)。
3、在整个主从结构运行过程中,如果主机不幸挂掉,重启之后,他依然是主机,主从复制操作也能够继续进行。
4、复制原理
5、第二种主从复制
上面讲的是第一种主从复制:
还有这一种的:
这么一来中间的机子对于左边的就是从机,对于右边的就是主机。
只需要把 6381 的主机改成 6380 即可。
6、哨兵模式
结合上篇文章,我们一共介绍了两种主从模式了,但是这两种,不管是哪一种,都会存在这样一个问题,那就是当主机宕机时,就会发生群龙无首的情况,如果在主机宕机时,能够从从机中选出一个来充当主机,那么就不用我们每次去手动重启主机了,这就涉及到一个新的话题,那就是哨兵模式。
一个哨兵可以监控很多个 master,不是只能监控一个。最后面的数字是投票的数量;哨兵可以有一个也可以有多个,所以剩下的服务器要获得多少票才能当选 master。这里因为只有一个哨兵,所以只能是 1。哨兵会测量剩下的服务器,会去发消息,看哪个响应快,就哪个优先。一般如果有多个哨兵,这个值是哨兵总数的一半即可:
设置好后启动:
这时候让主机宕机,让其他从机当选主机,看看日志信息:
这时候会发现 6379 变成从机了。
这时候即便重新启动 6379,也依然是从机。
这时候看看 6379 的日志信息:
这里是哨兵往配置文件里面写的。这样以后 6379 每次启动都是从机;因为配置文件限制了。
如果再打开 6380 的配置文件,会发现少了一行,这样每次启动 6380 都是 master 了。
这样看来哨兵模式作出的更改是持久的。
五、Redis 集群
1、原理
2、集群搭建
这里从一个默认干净的 redis.conf 文件开始改;这里为了方便区分,命名为 redis7001.conf 一直到 redis7006.comf。这里只要把 7001 改好,再复制五份,再批量修改即可(这六个文件可以统一放到一个 文件夹中去管理):
到此就配置好了,后面只需要复制五份,并把里面的 7001 改成对应的数字即可。
之后就是启动 redis,跟正常那样启动即可(这里是启动节点):
启动节点成功后就开始进行集群的创建:
./src/redis-cli --cluster create --cluster-replicas 1 192.168.5.131:7001 192.168.5.131:7002 192.168.5.131:7003 192.168.5.131:7004 192.168.5.131:7005 192.168.5.131:7006 -a 123
创建成功前模拟的信息:
上图信息中可以看到分了三组,每组分配的插槽等等之类的信息,还有主机从机对应的信息。
这个意思是对这次分配是否接受,选择 no 就会重新分配。yes则开始分配。
3、查看集群的状态及相关信息
想要查看集群的状态,只需要随便登录到一个 redis 里面:
上面的 -c 意思是以集群的方式登录。
然后输入 cluster info 即可查看集群的状态。
cluster nodes :查看集群的信息:
这时随便存入一个值,看看展示的信息:
展示了分配的插槽和对应的机子,而且自动跳到了 7003 中去。
4、添加节点
a、添加 master
现在有三组,想要动态的加一个 master 进来,是可以的:
先弄出一个 7007 出来,配置按照之前的方式改,然后启动:
接着加入到集群中:
src/redis-cli -a 123 -p 7001 --cluster add-node 127.0.0.1:7007 127.0.0.1:7001
b、分配插槽
加入集群成功后看一下集群信息,会发现新加入的节点不仅没有从机,还没有插槽,没有插槽意味着加进来也是白加,因为存储是根据插槽来存的。所以这里最后一件事就是分配插槽;因为 16384 个插槽已经分配完了,所以只能从已有的地方取出来分配给新节点:
src/redis-cli -a 123 -p 7001 --cluster reshard 127.0.0.1:7001
接着就是要输入迁移的数量。这里其实移动的还是比较慢,因为涉及到数据的迁移。
接着就是输入这些插槽由哪些机子出,如果是 all(指三个主机)那么就是三个主机出,如果是某一个或者某几个,那么一个个输入完对应的机子 id 之后,最后输入一个 done,就意味着输完了。
这里就输入 all。接着就会展示三个主机打算分出的插槽信息,会询问我们是否接受这种分配:
分配完成后查看 7007 的插槽:
c、添加从节点
也可以添加丛节点,添加丛节点比较简单,直接添加进来即可,下面是例子:
5、删除节点
a、删除从机
从机删除很简单,直接删除即可,例子:
b、删除主机
主机删除就比较麻烦了,因为主机上面有槽,直接删除会报错。要删除也不难,先要把槽分配出去才能删,因为如果槽不完整,集群也就用不了了。把槽分配出去的方式就跟前面的一模一样。这里就不举例子了。
6、创建失败处理方式
创建集群如果可以最好是一次性的成功,如果是失败了,先要把这几个文件删除:
7、Jedis 操作集群
六、Redis常见问题和布隆过滤器
1、key的生存时间到了,Redis会立即删除吗?
2、Redis的淘汰机制
3、缓存的常见问题
a、缓存穿透问题
典型场景:
虽然都是一些常见的办法,但是并不是最佳的解决方案。比较专业的做法是使用布隆过滤器。关于布隆过滤器看下一个小节。
b、缓存击穿问题
就是说在短时间内或者同一时间突然缓存都过期了或者失效了,然后大量访问都跑去数据库中,这就叫缓存击穿。
解决方案:不要设置统一的过期时间,过期时间应该是随机的。
c、缓存雪崩
解决方案同上。
d、缓存倾斜问题
解决方案:搭建 redis 主从。
4、布隆过滤器
a、应用场景
b、布隆过滤器介绍
c、 布隆过滤器原理
大白话解释就是: 布隆过滤器中有好几个哈希函数,会对要存的数据进行运算,分别算出哈希值出来,接着这几个哈希值会对数组的长度取余,求出来的余数会存进数组中(余数是多少,就存哪个位置)。接着下次想找有没有存过这个数据,又会再次去求哈希值出来,接着去对应的数组位置去找,比如这个位置分别是 1、5、8。如果这三个位置都有,那么这个数据就可能会存在;之所以说可能会存在,是因为其他数据求出来的结果也可能是 1、5、8。但是如果这三个位置有一个不存在,或者都不存在,那么这个数据就一定不存在。所以说这里求出来的数据结果是:如果有,就可能存在,如果没有,那么就一定不存在。
如果要查询一个数据,就会先用这个布隆过滤器先过滤一遍。
看上图。位数组的大小可以自己自定义。这个大小跟误判的概率有关:位数组越大,误判概率越小,当然占用的存储空间越大;位数组越小,误判概率越大,当然占用的存储空间就小。
虽然精确度没有 redis 那么精确,但是也能筛选出大部分的数据,已经足以起到保护数据库的作用。
还有一点要注意:这里只是判断数据是否存在,但是不能逆运算出存的数据是什么值,因为多种多样的数据他们的值可能是一样的,所以只能求出是否可能存在,但不能求出该值是多少。
d、安装
切记不要安装最新的版本,要去找正式发布的版本。github 页面的右边去找:
下载压缩文件,去 linux 系统里面解压。
自己编译安装(如果手动解压压缩包可跳过下面第二步):
cd redis-5.0.7
git clone https://github.com/RedisBloom/RedisBloom.git
cd RedisBloom/
make
cd ..
redis-server redis.conf --loadmodule ./RedisBloom/redisbloom.so
e、使用布隆过滤器
存数据:
检查数据是否存在:
默认的位数组的长度是100。
f、设置布隆过滤器
上图的 k1 是过滤器的名字。系统会根据错误率和预计存储数量计算出一个大致的位数组长度。
g、Jedis 操作布隆过滤器
测试前记得还需要 Jedis 的依赖
可以先自定义过滤器的初始条件。
测试代码:
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxIdle(300);
config.setMaxTotal(1000);
config.setMaxWaitMillis(30000);
config.setTestOnBorrow(true);
JedisPool pool = new JedisPool(config, "192.168.91.128", 6379, 30000, "javaboy");
Client client = new Client(pool);
//存入数据
for (int i = 0; i < 100000; i++) {
// 第一个参数是过滤器的名字
client.add("myfilter", "javaboy-" + i);
}
//检查数据是否存在
boolean exists = client.exists("myfilter", "javaboy-9999999");
System.out.println(exists);
如果不手动设置,过滤器条件则是默认的。