Redis是一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库。
特性:
NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,泛指非关系型的数据库。随着互联网web2.0 网站的兴起,传统的关系数据库在应付特别是超大规模和高并发类型纯动态网站已经显得力不从心,暴露了很多难以克服的问题。
结构化数据和非结构化数据
KV型NoSql顾名思义就是以键值对形式存储的非关系型数据库KV型NoSql最大的优点就是高性能,利用Redis自带的BenchMark做基准测试,TPS可达到10万的级别,性能非常强劲。
列式NoSql,大数据时代最具代表性的技术之一了,以HBase为代表。
文档型NoSql指的是将半结构化数据存储为文档的一种NoSql,文档型NoSql通常以JSON或者XML格式存储数据。关系型数据库是按部就班地每个字段一列存,在MongDB里面就是一个JSON字符串存储
传统关系型数据库主要通过索引来达到快速查询的目的,但是在全文搜索的场景下,索引是无能为力的,like查询一来无法满足所有模糊匹配需求,二来使用限制太大且使用不当容易造成慢查询,搜索型NoSql的诞生正是为了解决关系型数据库全文搜索能力较弱的问题,ElasticSearch是搜索型NoSql的代表产品
优点:
缺点:
关系型数据库最典型的数据结构是表,由二维表及其之间的联系所组成的一个数据组织
优点:
缺点:
Redis是一个字典结构的存储服务器,一个Redis实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。Redis默认支持16个数据库,可以通过调整Redis的配置文件redis/redis.conf中的databases来修改这一个值,设置完毕后重启Redis便完成配置。
因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。redis 采用网络IO多路复用技术来保证在多连接的时候, 系统的高吞吐量
IO多路复用技术

String是Redis最基本的类型,一个key对应一个value。String是二进制安全的,意味着String可以包含
任何数据,比如序列化对象或者一张图片。String最多可以放512M的数据。
使用场景:
List是简单的字符串列表,按照插入顺序排序,添加一个元素到列表的头部(左边)后者尾部(右边)。底层是一个双向链表,对两端的操作性能极高,通过索引操作中间的节点性能较差。

一个List最多可以包含2的32次方-1个元素(每个列表超过40亿个元素)
使用场景:
和List功能类似,但是Set是自动重排的,如果是存储一个列表数据,不希望出现重复数据时,Set是一个很好的选择。Set是String类型的无序集合,它底层是一个value为null的hash表,所以添加、删除、查找的时间复杂度都是O(1).
使用场景:
Hash是一个键值对的集合,Hash是一个String类型的Field(字段)和Value(值),Hash特别适用于存储对象。
Hash结构优化:
使用场景:
ZSet和Set很相似,是一个没有重复元素的String集合,不同之处是ZSet的每个元素都关联了一个分数(Score),这个分数被用来按照从低分到高分的方式排序集合中的元素,集合的元素是唯一的,但是分数可以重复。
使用场景 :
BitMaps本身就不是一种数据结构,实际上就是字符串,但是它可以对字符串的位进行操作。“合理地使用位能够有效的提高内存使用率和开发效率”。
使用场景:
GEO,Geographic,地理信息,该类型就是元素的二维坐标,在地图上就是经纬度。Redis基于该类型提供了经纬度设置、查询、范围查询、距离查询、经纬度Hash等常见操作。
使用场景:

配置大小单位,开头定义基本度量单位,只支持bytes,大小写不敏感
Redis只有一个配置文件,如果多个人进行开发维护,那么就需要多个这样的配置文件,这时候多个配置文件就可以在此通过 include /path/to/local.conf 配置进来,而原本的 redis.conf 配置文件就作为一个总闸。


参数:

详解:

参数:

参数:

参数:
maxclients :设置客户端最大并发连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件。 描述符数-32(redis server自身会使用一些),如果设置maxclients为0 。表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

参数:
maxmemory:设置Redis的最大内存,如果设置为0 。表示不作限制。通常是配合下面介绍的maxmemory-policy参数一起使用。
maxmemory-policy :当内存使用达到maxmemory设置的最大值时,redis使用的内存清除策略。有以下几种可以选择:
maxmemory-samples :LRU 和 minimal TTL 算法都不是精准的算法,但是相对精确的算法(为了节省内存)。随意你可以选择样本大小进行检,redis默认选择3个样本进行检测,可以通过maxmemory-samples进行设置样本数。

参数:

lua-time-limit:一个lua脚本执行的最大时间,单位为ms。默认值为5000.

参数:
requirepass:设置redis连接密码。
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。

//订阅语法格式
subcribe 主题名字
//示例:
127.0.0.1:6379> SUBSCRIBE channel-1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel-1"
3) (integer) 1
//发布命令
publish channel-1 hello
//示例:(打开另一个客户端,给channel1发布消息hello)
127.0.0.1:6379> PUBLISH channel-1 hello
(integer) 1 //返回的1是订阅者数量。
//打开第一个客户端可以看到发送的消息
127.0.0.1:6379> SUBSCRIBE channel-1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel-1"
3) (integer) 1
1) "message"
2) "channel-1"
3) "hello"
//发布的消息没有持久化,如果在订阅的客户端收不到hello,只能收到订阅后发布的消息。

慢查询日志是Redis服务端在命令执行前后计算每条命令的执行时长,当超过某个阈值是记录下来的日志。日志中记录了慢查询发生的时间,还有执行时长、具体什么命令等信息,它可以用来帮助开发和运维人员定位系统中存在的慢查询。
可以使用 slowlog get 命令获取慢查询日志,在 slowlog get 后面还可以加一个数字,用于指定获取慢查询日志的条数,比如,获取3条慢查询日志:
127.0.0.1:0>config get slow*
1) "slowlog-max-len"
2) "128"
3) "slowlog-log-slower-than"
4) "10000"
10000阈值,单位微秒,此处为10毫秒,128慢日志记录保存数量的阈值,此处保存128条。
比如,把slowlog-log-slower-than设置为1000,slowlog-max-len设置为1200:
slowlog-max-len****配置建议
slowlog-log-slower-than****配置建议
经历了 1次pipeline(n条命令) = 1次网络时间 + n次命令时间,这大大减少了网络时间的开销,这就是流
水线。
经历一次时间=一次网络时间+一次命令时间
经历n次时间=n次网络时间+n次命令时间
//引入jedis依赖包:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency
//没有pipeline命令
Jedis jedis - new Jedis("127.0.0.1",6379);
for ( int i = 0 ; i < 10000 ; i ++ ){
jedis.hset("hashkey:" + i , "field" + i , "value" + i);
}
/**
在不使用pipeline的情况下,使用for循环进行每次一条命令的执行操作,耗费的时间可能达到 1w
条插入命令的耗时为50s
*/
//使用pipeline
Jedis jedis = new Jedis("127.0.0.1",6379);
for ( int i = 0; i < 100 ; i++) {
Pipeline pipeline = jedis.ppipelined();
for (int j = i * 100 ; j < (i + 1) * 100 ; j++) {
pipeline.hset("hashkey:" + j,"field" + j, "value" + j);
}
pipeline.syncAndReturnAll();
}
由于redis的数据都放在内存中,如果没有配置持久化,Redis重启后数据就全部丢失了,于是重启Redis需要开启持久化功能。将数据保存到磁盘上,当Redis重启后,可以从磁盘中恢复数据。“对于Redis而言,持久化机制是指把内存中的数据存为硬盘文件, 这样当Redis重启或服务器故障时能根据持久化后的硬盘文件恢复数 据。”
redis持久化的意义,在于故障恢复。比如部署了一个redis,作为cache缓存,同时也可以保存一些比较重要的数据。

Redis****提供了两个不同形式的持久化方式
RDB 持久化是全量备份,比较耗时,所以Redis就提供了一种更为高效地AOF(Append Only-file)持久化方案,简单描述它的工作原理:AOF日志存储的是Redis服务器指令序列,AOF只记录对内存进行修改的指令记录。
RDB:指的是在指定的时间间隔内将内存的数据集快照写入磁盘,恢复时直接将快照读取到内存里。“快照-经过压缩的二进制文件”。
RDB保存的文件,在redis.conf中配置文件名称,默认为dump.rdb。(rdb文件的保存位置,也可以修改。默认在Redis启动时命令行所在的目录下。)
rdb文件的保存路径,也可以修改。默认为Redis启动时命令行所在的目录下

快照默认配置:
配置新的保存规则
给redis.conf添加新的快照策略,30秒内如果有5次key的变化,则触发快照。配置修改后,需要重启
Redis服务。
执行flushall命令,也会触发rdb规则。
手动触发Redis进行RDB持久化的命令有两种:
\1. save 该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止,不建议使用。
\2. bgsave执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。
默认值是yes。当Redis无法写入磁盘的话,直接关闭Redis的写操作。

默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算
法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照
会比较大。

默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加
大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。

只需要将rdb文件放在Redis的启动目录,Redis启动时会自动加载dump.rdb并恢复数据。
适合大规模的数据恢复
对数据完整性和一致性要求不高更适合使用
节省磁盘空间
恢复速度快
在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照
后的所有修改。
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来。AOF默认不开启可以在redis.conf中配置文件名称,默认为appendonly.aof。

AOF文件的保存路径,同RDB的路径一致,如果AOF和RDB同时启动,Redis默认读取AOF的数
据。
设置Yes:修改默认的appendonly no,改为yes

参数:
\1. appendfsync always 始终同步,每次Redis的写入都会立刻记入日志,性能较差但数据完整性比较好。
\2. appendfsync everysec 每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
\3. appendfsync no redis不主动进行同步,把同步时机交给操作系统。
优点
备份机制更稳健,丢失数据概率更低。
可读的日志文本,通过操作AOF稳健,可以处理误操作。
缺点
比起RDB占用更多的磁盘空间。
恢复备份速度要慢。
每次读写都同步的话,有一定的性能压力。
不要仅仅使用****RDB
RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,
那么会丢失最近5分钟的数据。
也不要仅仅使用****AOF
\1. 你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快。
\2. RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug。
综合使用AOF和RDB两种持久化机制
用AOF来保证数据不丢失,作为数据恢复的第一选择,用RDB来做不同程度的冷备,在AOF文件都丢失
或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复。

RDB的优点:
1.体积更小:相同的数据量rdb数据比aof的小,因为rdb是紧凑型文件
2.恢复更快:因为rdb是数据的快照,基本上就是数据的复制,不用重新读取再写入内存
3.性能更高:父进程在保存rdb时候只需要fork一个子进程,无需父进程的进行其他io操作,也保证了服务器的性能。
缺点:
1.故障丢失:因为rdb是全量的,一般是使用shell脚本实现30分钟或者1小时或者每天对redis进行rdb备份,(注,也可以是用自带的策略),但是最少也要5分钟进行一次的备份,所以当服务死掉后,最少也要丢失5分钟的数据。
2.耐久性差:相对aof的异步策略来说,因为rdb的复制是全量的,即使是fork的子进程来进行备份,当数据量很大的时候对磁盘的消耗也是不可忽视的,尤其在访问量很高的时候,fork的时间也会延长,导致cpu吃紧,耐久性相对较差。
aof的优点
1.数据保证:可以设置fsync策略,一般默认是everysec,也可以设置每次写入追加,所以即使服务死掉了,也最多丢失一秒数据
2.自动缩小:当aof文件大小到达一定程度的时候,后台会自动的去执行aof重写,此过程不会影响主进程,重写完成后,新的写入将会写到新的aof中,旧的就会被删除掉。但是此条如果拿出来对比rdb的话还是没有必要算成优点,只是官网显示成优点而已。
缺点:
1.性能相对较差:它的操作模式决定了它会对redis的性能有所损耗
2.体积相对更大:尽管是将aof文件重写了,但是毕竟是操作过程和操作结果仍然有很大的差别,体积也更大。
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
watch key1 key2 ... : 监视一或多个key,如果在事务执行之前,
被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )
multi : 标记一个事务块的开始( queued )
exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
discard : 取消事务,放弃事务块中的所有命令
unwatch : 取消watch对所有key的监控

为什么要使用主从复制?
机器故障:部署一台Redis,当服务器发生故障时需要迁移到另外一台服务器而且要保证数据的同步。
容量瓶颈:当有需求要扩容redis内存时,16G->64G,单机一般无法满足。
解决办法
将数据复制多个副本到其它节点进行复制,实现Redis的高可用,实现对数据的冗余备份保证数据和服务的高可用
主从复制:指的是将一台redis服务器的数据,复制到其它的redis服务器,前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。
哨兵模式:反客为主的自动版,能够自动监控master是否发生故障,如果故障了会根据投票数从slave中挑选一个作为master,其他的slave会自动转向同步新的master,实现故障自动转义。
主从复制分三个阶段六个过程
三个阶段

六个过程

当主机Master宕机后,需要手动切换,重新选取主节点
当主服务器宕机后,需要将一台从服务器切换为主服务器,需要人工干预,不仅麻烦,还会导致服务一段时间不可用。所有就有了哨兵模式。
哨兵模式是一种特殊的模式,redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,可以独立运行。原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

sentinel不断的向master和slave发起通知,收集信息。
通知阶段sentinel发送的通知没得到master的回应,就会把master标记为SRI_S_DOWN,并且把master的状态发给各个sentinel,其他sentinel听到master挂了,说我不信,我也去看看,并把结果共享给各个sentinel,当有一半的sentinel都认为master挂了的时候,就会把master标记为SRI_0_DOWN。
最先接到哪个sentinel的竞选通知就会把票投给它
redis集群有三种模式(主从模式、Sentinel模式、Cluster模式),Redis集群是一个由多个主从节点群组成的分布式服务集群,它具有复制,高可用和分片特性。

Redis集群最少需要三个节点

Redis Cluster将所有数据划分为16384个slots(槽位),每个节点负责其中一部分槽位。槽位的信息存于每个节点中,只有master节点会被分配槽位,slave节点不会分配槽位。
槽位定位算法:k1=127001
Cluster 默认会对key值使用crc16算法进行hash得到一个整数值,然后用这个整数值对16384进行取模来得到具体槽位
HASH_SLOT = CRC16(key)%16384
Redis的集群脑裂是指因为网络问题,导致Redis Master节点跟redis slave节点和Sentinel 集群处于不同的网络分区,此时因为sentinel集群无法感知到master的存在,所以将slave节点提升为master节点。

此时存在两个不同的master节点,就像一个大脑分裂成两个,集群脑裂问题中,如果客户端还基于原来的master节点继续写入数据,那么新的Master节点将无法同步这些数据,当网络问题解决之后,sentinel集群将原先的Master节点降为slave节点,此时再从新的master中同步数据。
//redis.conf
min-replicas-to-write 1
min-replicas-max-lag 5
//第一个参数表示最少的slave节点为1个
//第二个参数表示数据复制和同步的延迟不能超过5秒
//配置了这两个参数:如果发生脑裂原Master会在客户端写入操作的时候拒绝请求。这样可以避免大量数据丢失。
新启动的系统中没有缓存数据,在缓存重建数据的过程中,系统性能和数据库负载都不太好,所以最好就是在系统上线之前就要把缓存的热点数据加载在缓存中,这种缓存预加载就是缓存预热。缓存预热解决数据库裸奔后的宕机问题
缓存中没有数据,由于缓存冷启动没有数据,如果直接对外提供服务,当并发量上来时,MySQL就会挂掉了

缓存穿透指的是缓存和数据库中都没有的数据,而用户不断发起请求,如发起id为“-1”的数据或者id特别大的数据。这个时候的用户请求很可能是攻击者,攻击会导致数据库压力过大。

**操作过程:**用户查询的数据在数据库中没有,自然缓存也不会有,这样就导致用户查询的时候,在缓存中查询不到数据,每次都要去数据库中再查询一遍,然后返回null(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查询数据库(缓存命中率问题)。
布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probability data structure),特点是高效的插入和查询。(布隆过滤器是一种数据库结构,底层是bit数组)
//引入hutool包
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.17</version>
</dependency>
//java代码实现
// 初始化 构造方法的参数大小10 决定了布隆过滤器BitMap的大小
BitMapBloomFilter filter = new BitMapBloomFilter(10); filter.add("123");
filter.add("abc");
filter.add("ddd");
boolean abc = filter.contains("abc");
System.out.println(abc);
某一个热点key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所有请求最终会走到数据库,造成数据库瞬时请求量和压力骤增(缓存中没有的数据,数据库中有)。
public String get(String key) throws InterruptedException {
String value = jedis.get(key);
// 缓存过期
if (value == null){
// 设置3分钟超时,防止删除操作失败的时候 下一次缓存不能load db
Long setnx = jedis.setnx(key + "mutex", "1");
jedis.pexpire(key + "mutex", 3 * 60);
// 代表设置成功
if (setnx == 1){
// 数据库查询
// value = db.get(key);
// 保存缓存 jedis.setex(key,3*60,"");
jedis.del(key + "mutex");
return value; }
else {
// 这个时候代表同时操作的其他线程已经load db并设置缓存了。 需要重新重新获取 缓存
Thread.sleep(50);
return get(key);
}
}else {
return value;
}
}
缓存雪崩是指我们设置缓存时采用了相同的过期时间,导致缓存在某一个时刻同时失效,请求全部转发到DB,DB瞬时压力过重导致雪崩
缓存正常从Redis中获取数据

缓存失效

public Object GetProductListNew(String cacheKey) {
int cacheTime = 30;
String lockKey = cacheKey;
// 获取key的缓存
String cacheValue = jedis.get(cacheKey);
// 缓存未失效返回缓存
if (cacheValue != null) {
return cacheValue;
} else {
// 加锁
synchronized(lockKey) {
// 获取key的value值
cacheValue = jedis.get(cacheKey);
if (cacheValue != null) {
return cacheValue;
} else {
//这里一般是sql查询数据db.set(key)
// 添加缓存
jedis.set(cacheKey,"");
}
}
return cacheValue;
}
}
//加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。