Redis介绍:
什么是Redis?
全称:REmote Dictionary Server(远程字典服务器)。完全是开源免费的,用C语言编写的,遵守BCD协议。是一个高性能的(key/value)分布式内存数据库。
基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一,也被人们称为数据结构服务器。
Redis与其它key-value缓存产品有如下三个特点
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
- Redis支持数据的备份,即master - slave(主从)模式的数据备份。
Redis的优势: - 性能极高,redis的读取速度是110000次/s,写的速度是81000次/s
- 丰富的数据类型,redis支持二进制案例的Strings,Lists,Hashes,Sets及Ordered Sets数据类型操作。
- 原子,redis的所有操作都是原子性的,同时redis还支持对几个操作全并后的原子性执行。
- 丰富的特性,Redis还支持published/subscribe,通知,key过期等等特性
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的cpu切换而消耗cpu,不用考虑各种锁的问题,不存在加锁释放锁的操作,没有因为可能出现死锁而导致的性能消耗。
- 使用多路I/O复用模型,非阻塞IO
Redis的应用场景: - 缓存(数据查询,短链接,新闻内容,商品内容多),使用最多
- 聊天室在线好友列表
- 任务队列(秒杀、抢购、12306等)
- 应用排行榜
- 网站访问统计
- 数据过期处理(可以精确到毫秒)
- 分布式集群架构中的session问题
Redis数据结构
Redis是一种基于内存的数据库,并且提供一定的持久化功能,它是一种键值(key - value)数据库,使用key作为找到当前缓存的数据,并且返回给程序调用者。
当前的redis支持6种数据类型,分别是:字符串(String)、列表(List)、集合(Set)、哈希结构(Hash)、有序集合(Zset)和基数(HyperLogLog)
Redis常用指令
1.String类型:
赋值语法:set key value
取值语法:get key
设置多个键语法: MSET key value [key value …]
获取多个键值语法: MGET key [key …]
删除语法:DEL key
2.字符串数字的递增与递减:
递增数字:当存储的字符串是整数时,Redis提供了一个实用的命令INCR,其作用是让当前键值递增, 并返回递增后的值。
递增数字语法: INCR key
递减数值语法: DECR key
增加指定的整数语法: INCRBY key increment
减少指定的整数 语法:DECRBY key decrement
3.Hash散列
hash叫散列类型,它提供了字段和字段值的映射。字段值只能是字符串类型,不支持散列类型、集合类型等其它类型。相当于是对象格式的存储
赋值语法:hset key field value
取值语法:hget key field
设置多个字段语法:hmset key field value field value
取多个值语法:hmget key field1 field2 field3…
获取所有字段值语法:hgetall key
删除字段语法:hdel key field1
4.队列List
Redis的list是采用链表来存储,双向链表存储数据,特点:增删快、查询慢(LinkedList),队列是有序的
向列表左边增加元素:lpush key value
从列表左边你弹出元素:lpop key(临时存储,弹出后,从对列出移除)
向列表右边增加元素:rpush key value
从列表右边弹出元素:rpop key
获取列表中元素的个数:llen key
查看列表语法:lrange key start stop
5.Set集合
Set集合类型:无需、不可重复
增加元素语法:sadd key member
删除元素语法:srem key member
获得集合中的所有元素:smembers key
判断元素是否在集合中:sismember key member
6.Zset有序集合
Sortedset又叫zset,是有序集合,可排序的,但是唯一。Sortedset和set的不同之处,是会给set中的元素添加一个分数,然后通过这个分数进行排序
添加元素:zadd key score field score field2…
获得排名在某个范围的元素列表,并按照元素分数降序,语法:zrevrange key start stop
获取元素的分数:zscore key member
删除元素:zrem key member
获得元素的分数可以在命令尾部加上withscore参数:zrevrange num2 0 4 withscores
7.HyoperLogLog命令
HyperLogLog是一种使用随机化的算法,以少量内存提供集合中唯一元素数量的近似值。
HyperLogLog可以接受多个元素作为输入,并给出输入元素的基数估算值:
基数:集合中不同元素的数来你个。比如{‘apple’,‘banana’,‘cherry’,‘banana’,‘apple’}的基数就是3
估算值:算法给出的基数并不是精确的,可能会比实际稍微多一些或稍微少一些,但会控制在合理的范围之内。
8.其它指令
(1)keys返回满足给顶pattern的所有key
keys user* //查询以user开头的key
keys * //查询所有的key
(2)exists确认一个key是否存在,存在返回1
(3)del 删除一个key
(4)rename重命名:rename old key newkey
(5)type返回值的类型:type key
设置key的生存时间:缓存的数据一般都是需要设置生存时间的,即:到期后销毁数据
(6)expire key seconds
设置key的生存时间(单位:秒)key在多少秒后会自动删除
ttl key 查看key剩余的生存时间
persist key 清除生存时间
(7)获取服务器信息和统计:info
(8)删除当前选择数据库中的所有key:flushdb
(9)删除所有数据库中的所有key:flushall
Redis的多数据库
一个redis实例key包括多个数据库,客户端可以指定连接某个redis实例的哪个数据库,就好比一个 mysql中创建多个数据库,客户端连接时指定连接哪个数据库。
一个redis实例最多可提供16个数据库,下标从0-15,客户端默认连接第0号数据库,也可以通过select 选择连接哪个数据库
将key的数据移动到1号数据库: move key 数据库编号
Redis的事务管理
Redis事务可以一次执行多个命令,并且带有以下两个重要的保证:
- 事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序执行。事务在执行的过程中,不会被其它客户端发送来的命令请求所打断。
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部不执行。
一个事务从开始到执行会经历以下三个阶段: - 开始事务
- 命令入队
- 执行事务
实例:
它以 multi 开始一个事务,然后将多个命令入队到事务中,最后由exec命令出发事务,一并i执行事务中的所有命令
multi
set u1 username1
set u2 username2
set u3 username3
get u1
exec
Redis的发布订阅模式
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis客户端可以订阅任意数量的频道
下图展示了频道channel1,以及订阅这三个频道的三个客户端–client2、client5和client1之间的关:
当有新消息通过publish命令发送给频道channel1时,这个消息就会被发送给订阅它的三个客户端:
Jedis访问redis:
访问Redis方式一:
本示例采用idea创建
创建一个maven项目,引入如下坐标:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.2</version>
</dependency>
新建一个类:
/**
* @Classname TraningRedis
* @Description redis练习
* @Date 2020/10/13 15:59
* @Created by ccc-j
* @email ccc-ju@outlook.com
*/
public class TraningRedis {
public static void main(String[] args) {
// param1: 连接ip地址
// param2:连接端口号
Jedis jedis = new Jedis("192.168.52.128", 6379);
// 设置值
jedis.set("java", "I am a java pm");
System.out.println("java = " + jedis.get("java"));
}
}
访问redis方式二:
使用连接池访问redis,废话不多说,上代码
/**
* @Classname RedisConnPool
* @Description jedis连接池测试类
* @Date 2020/10/13 16:38
* @Created by ccc-j
* @email ccc-ju@outlook.com
*/
public class RedisConnPool {
public static void main(String[] args) {
// 1.创建连接池配置的工具类对象
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// jedis连接的空闲数
jedisPoolConfig.setMaxIdle(10);
// jedis总的连接数
jedisPoolConfig.setMaxTotal(20);
JedisPool jedisPool = null;
Jedis jedis = null;
try{
// 2.创建连接池对象
jedisPool = new JedisPool("192.168.52.128", 6379);
// 3.获得jedis资源
jedis = jedisPool.getResource();
// 4.操作数据
jedis.set("stu1", "student1");
System.out.println("stu1 = " + jedis.get("stu1"));
}catch (Exception e){
e.printStackTrace();
}finally {
// 5.关闭资源
if(jedis != null){
jedis.close();
}
if(jedisPool != null){
jedisPool.close();
}
}
}
}
Redis持久化方式
什么是Redis持久化?
由于redis的值放在内存中,为防止突然断电等特殊情况的发生,需要对数据库进行持久化备份。即将内存数据保存到硬盘。
Redis的持久化方式:
RDB持久化:
RDB 是以二进制文件,是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换 上次持久化的文件,达到数据恢复。
优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
缺点:RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种 方式更适合数据要求不严谨的时候。
这里说的这个执行数据写入到临时文件的时间点是可以通过配置来自己确定的,通过配置redis 在 n 秒 内如果超过 m 个 key 被修改这执行一次 RDB 操作。这个操作就类似于在这个时间点来保存一次 Redis 的所有数据,一次快照数据。所有这个持久化方法也通常叫做 snapshots。
RDB默认开启,redis.conf中的配置参数如下:
#dbfilename:持久化数据存储在本地的文件
dbfilename dump.rdb
#dir:持久化数据存储在本地的路径,如果是在/redis/redis-5.0.5/src下启动的redis-cli,则数据 会存储在当前src目录下
dir ./
##snapshot触发的时机,save
##如下为900秒后,至少有一个变更操作,才会snapshot
##对于此值的设置,需要谨慎,评估系统的变更操作密集程度
##可以通过“save”来关闭snapshot功能
#save时间,以下分别表示更改了1个key时间隔900s进行持久化存储;更改了10个key300s进行存储;更改 10000个key60s进行存储。
save 900 1
save 300 10
save 60 10000
##当snapshot时出现错误无法继续时,是否阻塞客户端“变更操作”,“错误”可能因为磁盘已满/磁盘故 障/OS级别异常等
stop-writes-on-bgsave-error yes
##是否启用rdb文件压缩,默认为“yes”,压缩往往意味着“额外的cpu消耗”,同时也意味这较小的文件尺寸 以及较短的网络传输时间
rdbcompression yes
注意:测试时,需要使用root用户级别的权限操作
AOF持久化:
优点:可以保持更高的数据完整性,如果设置追加 file 的时间是 1s,如果 redis 发生故障,最多会丢失 1s 的数据;且如果日志写入不完整支持 redis-check-aof 来进行日志修复;AOF 文件没被 rewrite 之前 (文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall)。
缺点:AOF 文件比 RDB 文件大,且恢复速度慢。
我们可以简单的认为 AOF 就是日志文件,此文件只会记录“变更操作”(例如:set/del 等),如果 server 中持续的大量变更操作,将会导致 AOF 文件非常的庞大,意味着 server 失效后,数据恢复的过程将会 很长;事实上,一条数据经过多次变更,将会产生多条 AOF 记录,其实只要保存当前的状态,历史的 操作记录是可以抛弃的;因为 AOF 持久化模式还伴生了“AOF rewrite”。
AOF 的特性决定了它相对比较安全,如果你期望数据更少的丢失,那么可以采用 AOF 模式。如果 AOF 文件正在被写入时突然 server 失效,有可能导致文件的最后一次记录是不完整,你可以通过手工或者 程序的方式去检测并修正不完整的记录,以便通过 aof 文件恢复能够正常;同时需要提醒,如果你的 redis 持久化手段中有 aof,那么在 server 故障失效后再次启动前,需要检测 aof 文件的完整性。
AOF 默认关闭,开启方法,修改配置文件
reds.conf:appendonly yes
##此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能
##只有在“yes”下,aof重写/文件同步等特性才会生效
appendonly yes
##指定aof文件名称
appendfilename appendonly.aof
##指定aof操作中文件同步策略,有三个合法值:always everysec no,默认为everysec
appendfsync everysec
##在aof-rewrite期间,appendfsync是否暂缓文件同步,"no"表示“不暂缓”,“yes”表示“暂缓”,默认
为“no”
no-appendfsync-on-rewrite no
##aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默
认“64mb”,建议“512mb”
auto-aof-rewrite-min-size 64mb
##相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比。
##每一次rewrite之后,redis都会记录下此时“新aof”文件的大小(例如A),那么当aof文件增长到A*(1\+ p)之后触发下一次rewrite,每一次aof记录的添加,都会检测当前aof文件的尺寸。
auto-aof-rewrite-percentage 100
RDB 与 AOF的区别:
RDB是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
缺点:RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种 方式更适合数据要求不严谨的时候。
AOF将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在append操作返回后(已经写入到文件或者即将写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当server需要数据恢复时,可以直接replay此日志文件,即可还原所有的操作过程。AOF相对可靠,它和mysql中bin.log、apache.log、zookeeper中txn-log简直异曲同工。AOF文件内容是字符串,非常容易阅读和解析
优点:可以保持更高的数据完整性,如果设置追加 file 的时间是 1s,如果 redis 发生故障,最多会丢失 1s 的数据;且如果日志写入不完整支持 redis-check-aof 来进行日志修复;AOF 文件没被 rewrite 之前 (文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall)。
缺点:AOF 文件比 RDB 文件大,且恢复速度慢。
Redis主从复制的实现
持久化保证了即使Redis服务重启也不会丢失数据,但是当redis服务器的硬盘损坏了可能会导致数据丢失,通过redis的主从复制就可以避免这样的单点故障(单台服务器的故障)
主Redis中的数据和从上的数据保持实时同步,当主redis写入数据的时通过主从复制机制复制到两个服务上。
主从复制不会阻塞master,在同步数据的时,master可以继续请求client请求
主机master配置:无需配置
推荐主从模式同步数据:
工作中一般选用:一主两从或一主一从
数据会同步到从服务器。在这个集群中的几台服务器上都有同样的数据。
主从机搭建步骤:
主机:不用配置。仅仅只需要配置从机,从机slave配置(这里采用伪集群)
第一步:复制出一个从机,注意需要使用root用户
第二步:修改从机的redis.conf
语法:replicaof
提示:检索文件:输入:/replicaof 当前页有没有,输入n,查找下一页
第三步:修改从机的port地址为6380
*这里修改的是主机器中的bind的地址和端口号
第四步:分别启动主从机器
验证是否为主从模式:
master : 6379
可以看到6379是主机,从机是6380
可以在从机看到主机信息,并且主机连接是up,在线
主机一旦发生增删改操作,那么从机会自动将数据同步到主机中
从机不能执行写操作,只能进行读操作
复制过程原理
当从库和主库建立MS(Master slaver)关系后,会向主数据库发送SYNC命令;
主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的命令缓存起来;
快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis
从Redis接收到后,会载入快照文件并且执行收到的缓存命令
主Redis每当收到写命令时就会将命令发送从Redis,保证数据的一致;【内部完成,所以不支持客户端在从机人为写数据】
复制架构出现宕机情况?
从Redis宕机:重启就好
主Redis宕机:从数据库(从机)中执行salve no one命令,断开主从关系并且提升为主数据库【把从机作为主机,这个时候新主机就具备了写的能力】;当服务器恢复后,重新启动后,执行slaveof命令,将其设置为从机【将之前宕机的主机设置为从机】。【手动执行,过程复杂,容易出错。】更好的方案?使用Redis哨兵模式
Redis 哨兵模式
哨兵模式:给集群分配一个站岗的。
作用:就是对Redis系统的运行情况监控,它是一个独立进程,它的功能:
监控主数据库和从数据库是否正常
主数据库出现故障后自动将从数据库转换为主数据
如果主机宕机,开始选举工作,选择一个做从机。
环境准备:一主两从,启动任一从机时,启动哨兵模式
虽然哨兵模式(Sentinel)释出为一个单独可执行文件redis-sentinel,但实际上它只是在特殊情况下的Redis服务器,你可以在启动一个普通Redis服务器通过给定–sentinel选项来启动哨兵
第一步:配置哨兵:
哨兵主要是用来监听主服务器的,所以一般把哨兵部署在从服务器上监听。
配置哨兵:
启动哨兵进程,首先需要创建哨兵配置文件vi sentinel.conf,可从源码配置redis5.0.5/sentinel.conf中复制内容,也可以直接自定义该文件到bin目录下
在配置中输入:sentinel monitor mastername 内网IP(127.0.0.1) 6379 1
说明:
mastername 监控主数据的名称,自定义
127.0.0.1:监控主数据库的IP;
6379:端口
1:最低通过票数
第二步:启动哨兵:
哨兵是一个单独的进程,启动之前确保主从服务是正常的。先启动主服务,后启动从服务
把日志写入指定的文件:
./redis-sentinel ./sentinel.conf >sent.log &
启动redis服务后,程序会自动配置文件sentinel.conf,并生成内容,注意:若再起启动需要删除下生成 的内容。
哨兵启动方式:
./redis-server redis.conf --sentinel
哨兵进程控制台:为master数据库添加了一个监控.
查询配置文件sentinel.conf中生成的内容: 启动哨兵的时候,修改了哨兵的配置文件。如果需要再次启动哨兵,需要删除myid唯一标示。 (保险的做法就是启动的一次,新配置一次)
第三步:主机宕机
机房意外:断电了。硬件故障:硬盘坏了。
杀死主机:kill -9 pid
哨兵控制台:从库自动提升为主库。
哨兵工作,链接之前的从机确认:
哨兵替代运维。自动监控完成。 同时也会自动修改redis.conf的主从配置文件。
指向了新主机。再次启动原有的主机,原有的主机会变为从机。
总结:
主从集群:
主机有写入权限。从机没有,只有可读。
意外宕机方案:
手动恢复:人为重启服务器,主机宕,把从机设置为主机。
自动恢复:使用哨兵监控。自动切换主从。
Redis集群方案
redis-cluster架构图
架构细节:
(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的节点检测有效时整个集群才生效.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点
示例如下:
缓存穿透、缓存击穿、缓存雪崩:
缓存的概念:广义的缓存就是在第一次加载某些可能会复用的数据的时候,在加载数据的同时,将数据存放到指定的地点做保存。在下次加载的时候,从这个指定的地点去取数据。这里加缓存是有一个前提的,就是从这个地方取数据,比数据源取数据要快得多。
java狭义的缓存,主要指三大类:
虚拟机缓存
分布式缓存
数据库缓存
正常来说,速度由上到下依次减慢
缓存雪崩
大量缓存在同一时间内同时失效,导致大量请求打到DB上
解决:
- 缓存设置随机的过期时间
- 互斥锁
- 使用redis集群
缓存穿透
用户在查询数据的时候,数据库里没有,那么缓存中也就没有,这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查一遍,然后返回空。这样请求就绕过缓存直接查询数据库,这也是经常提到的缓存命中率问题。
解决:
- 如果查询数据库为空,直接设置一个默认值放到缓存,这样第二次到缓存中就有值了,而不会继续访问数据库,这是最简单粗暴的方法。
- 把空结果给缓存起来,下次同样的请求直接返回空,可以避免当查询的值为空时引起缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面正常的缓存处理逻辑。
- 使用布隆过滤器,使用bitmap进行匹配拦截
缓存击穿
对于一些设置了过期时间,如果这些key可能会在某一时间被超高并发地访问,这时,缓存里面没有数据,但数据库里有,这时候所有请求全部打到DB,造成瞬间地压力。
解决:
- 热点数据永不过期,加锁