Redis知识点详解

目录

目录

1.简单说下NoSql

1.1.什么是NoSQL

1.2 关系型数据库遇到的瓶颈

1.3 NoSql数据库的优势

1.4 常见的NoSql产品

2.说说对Redis的了解

2.1.什么是Redis

2.2.Redis 与其他 key - value 缓存产品特点

2.3. Redis优势

2.4 Redis应用场景

3.Linux下安装Redis

3.1 环境准备

3.2 Redis的安装

3.2.1 Redis的编译环境gcc的安装

 3.3 Redis的启动

3.3.1 Redis的前端模式启动

3.3.2 Redis的后端启动(启动redis服务器)

3.3.3 客户端访问redis

3.3.4 向Redis服务器发送命令

3.3.5 退出客户端

3.3.6 Redis的停止(服务)

3.3.7 第三方工具(redis-desktop-manager)操作redis

 4.Redis数据结构

5.Redis常用指令

5.1 String 类型

5.2 字符串数字的递增与递减

5.3 Hash散列(了解)

5.4 队列List

5.5 Set集合  

5.6 Zset有序集合(了解)

5.7 HyoperLogLog命令

5.8 其他命令(key相关的命令)

5.9 Redis的多数据库

6.Redis的事务管理

7.Redis发布订阅模式

8.Jedis连接Redis

第一步:创建项目,导入依赖

第二步:链接服务器

方案一 单实例链接

方案二:连接池

9.Redis持久化方式

9.1 什么是Redis持久化

9.2 Redis 持久化存储方式

9.2.1 RDB持久化

9.2.2 AOF持久化

9.2.3 AOF与RDB区别

10.Redis主从复制

11.Redis哨兵模式

11.2.演示Redis哨兵模式

11.2.1.搭建环境(一主两从)

 11.2.配置哨兵:

 11.3. 启动哨兵:

 11.4. 测试哨兵模式:主机宕机

12.Redis集群方案

12.1.单台redis问题

12.2 redis-cluster架构图

12.3 redis-cluster投票:容错

12.3 集群搭建步骤

12.4 客户端连接集群

12.5 查看集群信息

12.6 查看集群中节点信息

12.7 Jedis连接集群

12.7.1 关闭防火墙

12.7.2 代码实现

13.Redis面试-缓存穿透,缓存击穿,缓存雪崩问题

14.Redis面试-分布式锁



1.简单说下NoSql

1.1.什么是NoSQL

NoSQL,泛指非关系型的数据库,区别于关系数据库,它们不保证关系数据的 ACID 特性

1.2 关系型数据库遇到的瓶颈

传统的关系数据库具有不错的性能,高稳定型,而且使用简单,功能强大。

但是随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站时无法解决大规模数据集合,多重数据种类带来问题,尤其是大数据应用难题。

现在网站的特点:

(1) 高并发读写
Web2.0 网站,数据库并发负载非常高,往往达到每秒上万次的读写请求
(2) 高容量存储和高效存储
Web2.0 网站通常需要在后台数据库中存储海量数据,如何存储海量数据并进行高效的查询往往是一个 挑战
(3) 高扩展性和高可用性
随着系统的用户量和访问量与日俱增,需要数据库能够很方便的进行扩展、维护

1.3 NoSql数据库的优势

(1) 易扩展

NoSQL数据库种类繁多,但是一个共同的特点都是数据之间无关系, 样就 非常容易扩展。

(2)大数据量,高性能

NoSQL数据库在大数据量下仍具有非常高读写性能,得益于它的无关系性,且数据库结果简单。

一般 MySQL 使用 Query Cache ,每次表的更新 Cache 就失效,是一种大粒度的 Cache ,在针对
web2.0 的交互频繁的应用, Cache 性能不高。而 NoSQL Cache 是记录级的,是一种细 粒度的 Cache ,所以NoSQL在这个层面上来说就要性能高很多

(3) 灵活的数据模型

NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。

(4) 高可用

1.4 常见的NoSql产品

2.说说对Redis的了解

2.1.什么是Redis

全称: REmote DIctionary Server (远程字典服务器)

是一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,被称为数据结构服务器。

它是键值( key-value )数据库,使用 key 作为索引找到当前缓存的数据,并且返回给程序调用者

2.2.Redis 与其他 key - value 缓存产品特点

(1) Redis 支持数据持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
(2) Redis 不仅支持简单 key-value 类型的数据,同时还提供 list set zset hash 等数据结构的存储
(3) Redis 支持数据的备份,即 master-slave( 主从 ) 模式的数据备份

2.3. Redis优势

(1) 性能极高 – Redis 能读的速度是 110000 /s, 写的速度是 81000 /s
(2) 丰富的数据类型 – Redis 支持  Strings, Lists, Hashes, Sets Ordered Sets 数据类型操作。
(3) 原子 – Redis 的所有操作都是原子性的,同时 Redis 还支持对几个操作全并后的原子性执行。
(4) 丰富的特性 – Redis 还支持 publish/subscribe, 通知 , key 过期等等特性
(5) 采用 单线程 ,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU ,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
(6) 使用多路 I/O 复用模型,非阻塞 IO(解决单线程带来的低效)

2.4 Redis应用场景

(1) 缓存 ( 数据查询,短连接,新闻内容,商品内容等 ) ,使用最多
(2) 聊天室在线好友列表
(3) 任务队列 ( 秒杀,抢购, 12306 )
(4) 应用排行榜
(5) 网站访问统计
(6) 数据过期处理 ( 可以精确到毫秒 ):验证码等
(7) 分布式集群架构中的 session 问题

3.Linux下安装Redis

3.1 环境准备

(1) 虚拟机版本:VMware® Workstation 12
(2) Linux 系统 :Centos7
(3) Redis 远程命令端 :xshell
(4) Linux远程命令端 :Secure CRT
(5) 文件传输工具 :SecureFXPortable

3.2 Redis的安装

Linux上安装Redis, 可以直接打开虚拟机通过命令安装,也可直接在本机上通过Linux的远程命令端操作。

3.2.1 Redis的编译环境gcc的安装

Redis C 语言开发的,安装 redis 需要先去官网下载源码进行编译,编译需要依赖于 GCC 编译环境,如果 CentOS 上没有安装gcc 编译环境,需要提前安装 (这里我们使用 root 用户处理这些操作)

普通用户切换到root命令   $ su root

安装gcc 编译环境 [root@localhost ~]# yum install gcc-c++

提示是否下载、安装,选择: y

首先打开虚拟机终端,输入ip addr 查看虚拟机的ip

 在Liunx远程命令端建立连接

 使用yum软件包管理器从网上下载并安装gcc编译环境,命令# yum install gcc-c++ 

 

 

3.2.2 Redis 的安装
(1) 使用 SecureFXPortable 上传 Redis 安装文件到 Linux 目录
SecureFXPortable先连接Linux

(2) 上传 Redis 安装文件 , 这里我上传自建文件夹 : /home/bowei/local

 

 (3)解压redis文件

# tar -zxvf redis-5.0.5.tar.gz

 

 (4)编译Redis(编译,.c文件编译为.o文件)

进入解压文件夹 , 执行 make编译

 如下编译成功!如果编译过程中出错,先删除安装文件目录,后解压重新编译

 (5) 安装

自定义安装路径,这里是在/home/bowei下创建文件夹redis

即安装路径/home/bowei/redis

安装命令 # make PREFIX=/home/bowei/redis install

 

 (6)安装之后的bin目录下几个可执行文件/命令

 

 (7) copy conf文件到安装路径

Redis启动需要一个配置文件,可以修改端口号信息。将redis解压的文件夹中的redis.conf文件复制到安装目录,是复制不是移动。

# cp  redis.conf  /home/bowei/redis

cp时我是进入解压文件中,所以redis.conf使用相对路径,也可以cp 时双方都使用绝对路径,直接复制,不需要考虑当前所在位置。

 3.3 Redis的启动

3.3.1 Redis的前端模式启动

直接运行 bin/redis-server 将使用前端模式启动,前端模式启动的缺点是启动完成后,不能再进行其他操作,如果要操作必须使用ctrl+c ,同时 redis-server 程序结束,不推荐此方法。
[root@localhost bin]# ./redis-server
下面是启动界面 ( 这个界面只能启动,启动后不能进行其他操作 )

使用ctrl+c退出前端启动。

 3.3.2 Redis的后端启动(启动redis服务器)

 修改redis.conf配置文件,设置:daemonize yes,然后可以使用后端模式启动。

修改配置文件使用vim或vi编辑器

命令 [root@localhost redis]# vi redis.conf  

 vim进入命令模式后,输入i进入编辑模式,找到daemonize,设置为yes,然后Esc键退出编辑模式,输入:进入底线模式,输入wq保存配置文件,并退出vim

 

 启动时,指定配置文件(这里所在文件夹是redis)

进入redis的bin中执行redis-server启动redis,同时跟上配置文件redis.conf

 也可以合成一步   [root@centosbowei redis]# ./bin/redis-server ./redis.conf

 查看是否启动成功

 Redis默认端口:6379,通过当前服务进行查看

[root@centosbowei redis]# ps -ef | grep -i redis

 至此,Liunx上的Redis安装和启动结束 

3.3.3 客户端访问redis

Linux客户端访问redis

如果想要通过指令来操作 redis ,可以使用 redis 的客户端进行操作 , bin 文件夹下运行 redis-cli
该指令默认连接的127.0.0.1 ,端口号是6379
[root@centosbowei  bin]# ./redis-cli
127.0.0.1:6379>

 

 如果客户端想要连接指定的ip地址以及端口号,则需要按照

redis-cli -h ip地址 -p 端口号  语法结构连接

一般在本机redis图形化工具连接linux上的redis服务器时会修改redis.conf中bind参数127.0.0.1为liunx主机ip,通过命令ip addr查询到。

 3.3.4 Redis服务器发送命令

Ping ,测试客户端与 Redis 的连接是否正常,如果连接正常,回收到 pong

127.0.0.1:6379> ping

PONG

3.3.5 退出客户端

127.0.0.1:6379> quit

3.3.6 Redis的停止(服务)

(1) 强制结束程序。强制终止 Redis 进程可能会导致 redis 持久化数据丢失

语法:kill -9 pid

查看redis进程命令 #ps aux |grep -i redis   找到进程ID 即pid

(2) 正确停止 Redis 的方式应该是向 Redis 发送 SHUTDOWN 命令,方法为(关闭默认的端口)

[root@centosbowei  redis]# ./bin/redis-cli shutdown   在bin中发送命令

3.3.7 第三方工具(redis-desktop-manager)操作redis

在本机使用redis客户端的图形化界面连接Linux上的redis服务器

需要关闭 linux 防火墙并且修改 redis.conf 文件中的 bind参数   bind linux ip 地址
bind 127.0.0.1 是不允许外界访问redis的,只能本机访问。

 使用vim编辑器修改conf配置文件时,发现conf内容与redis.conf文件不一样,换成vi编辑器

使用vi编辑器修改conf配置文件

 Esc键退出编辑模式,输入:进入底线模式,输入wq保存文件并退出vi编辑器。

此时如果通过 redis 客户端访问的时候,代码如下 :

此时就可以在本机使用客户端图形化工具连接Linux上的redis服务器,没连上就是linux上redis的conf中bind 的ip没设置成linux的host ip

 

 4.Redis数据结构

Redis 是一种基于内存的数据库,并且提供一定的持久化功能,它是一种键值( key-value )数据库,使用 key 作为索引找到当前缓存的数据,并且返回给程序调用者。
当前的 Redis 支持 6 种数据类型,它们分别是字符串( String )、列表( List )、集合( set )、哈希结构(hash )、有序集合( zset )和基数( HyperLogLog

String字符串:如果是保存字符串、这整数和浮点数可以通过这个类型来存储。键  值

List列表它是一种链表式存储,每一个节点存储一个字符串。 键  值(值 值...)

Redislist是采用来链表来存储,双向链表存储数据(Linkedlist),特点:增删快、查询慢.这个队列是有序的。

Set集合类型:无序、不可重复

Hash:表示map类型,是一个键值对应的无序列表,值只能是字符串。键  值(键值 键值  ...)

ZSet有序集合,可以包含字符串、整数、浮点数、分值,元素的排序是依照分值大小决定的

Sortedset 又叫 zset,是有序集合,可排序的,但是不可重复。 Sortedset set 的不同之处,是会给 set 中的元素添加一个分数,然后通过这个分数进行排序。 键  值(score 值  score 值)

5.Redis常用指令

 5.1 String 类型

赋值语法:SET key value
127.0.0.1:6379> set k1 zhangsan
取值语法: GET key
127.0.0.1:6379> get k1
设置多个键语法: MSET key value [key value …]
127.0.0.1:6379> mset k2 lisi k3 wangwu

获取多个键值语法: MGET key [key …]

127.0.0.1:6379> mget k2 k3
删除语法:DEL key
127.0.0.1:6379> del k3
首先关闭防火墙,然后在命令行客户端(若在Linux,右键进入终端,若是在本机,使用远程工具SecureCRT)通过命令启动redis服务器,然后通过ip和端口号连接,不需要登录。
一个 redis 实例 key 包括多个数据库,客户端可以指定连接某个 redis 实例的哪个数据库,就好比一个 mysql 中创建多个数据库,客户端连接时指定连接哪个数据库。
一个 redis 实例最多可提供 16 个数据库,下标从 0-15 ,客户端默认连接第 0 号数据库,也可以通过 select 选择连接哪个数据库。
下面启动redis服务的命令  redis-server
命令行客户端命令  redis-cli 

 

5.2 字符串数字的递增与递减

递增数字:当存储的字符串是整数时, Redis 提供了一个实用的命令 INCR ,其作用是让当前键值递增,并返回递增后的值。
递增数字语法 : INCR key
递减数值语法 : DECR key
增加指定的整数语法 : INCRBY key increment
减少指定的整数 语法 :DECRBY key decrement

 5.3 Hash散列(了解)

 hash叫散列类型,它提供了字段和字段值的映射。字段值只能是字符串类型,不支持散列类型、集合类型等其它类型。相当于是对象格式的存储

就是在键值对的值中再存储一个/多个键值对,只不过该键值对的值只能是字符串类型

赋值语法: HSET key field value
设置一个字段值, HSET 命令不区分插入和更新操作,当执行插入操作时 HSET 命令返回 1 ,当执行更新操作时返回0.

 值语法: HGET key fifield

 设置多个字段语法: HMSET key fifield value [fifield value ...]

 取多个值语法: HMGET key fifield [fifield ...]

 获取所有字段值语法:HGETALL key

 删除字段语法:HDEL key fifield [fifield ...]

5.4 队列List

Redis list 采用来链表来存储 , 双向链表存储数据,特点:增删快、查询慢 (Linkedlist). 这个队列是有序的。
向列表左边增加元素 : LPUSH key value [value ...]
从列表左边弹出元素 LPOP key( 临时存储,弹出后 , 从队列中清除 )  ,每次弹出一个节点
向列表右边增加元素 : RPUSH key value [value ...]
从列表右边弹出元素 RPOP key
获取列表中元素的个数 : LLEN key
查看列表语法 LRANGE key start stop
将返回start stop 之间的所有元素 ( 包含两端的元素 ), 索引从 0 开始 , 可以是负数,如: “-1” 代表最后的一个元素。
示例 : LPUSH comment1 ‘{“id”:1,“name”:“ 商品 ","date":1430295077289}'

 

 5.5 Set集合  

Set 集合类型:无序、不可重复
增加元素语法:sadd key member [member ...]
删除元素语法:srem key member [member ...]
获得集合中的所有元素 : smembers key
判断元素是否在集合中: sismenber key member   有则返回1 否则返回0

 5.6 Zset有序集合(了解)

Sortedset 又叫 zset, 是有序集合,可排序的,但是唯一。 Sortedset set 的不同之处,是会给 set 中的元素添加一个分数,然后通过这个分数进行排序
增加元素:zadd key score member [score member ...]
向有序集合加入元素和该元素的分数 (score) 如果该元素已经存在则会用新的分数替换原有分数。就是覆盖掉。
添加带分数 ( 可用学生成绩,销售数量等来做分数 , 方便计算排序 )
获得排名在某个范围的元素列表,并按照元素分数降序返回
语法:zrevrange key start stop [WITHSCORES]   带上withscores参数可以显示分数
获取元素的分数 :zscore key member
删除元素zrem key member [member ...]
给某一个属性加分数或减分,减分时使用负数:zincrby key  增减量 member
示例 :
商品编号 1001 的销量是 30 ,商品编号 1002 的销量是15 , 1003 的销量是 20 1004 的销量是
商品编号 1001 的销量加 1
商品销量排序队列中前 3

 5.7 HyoperLogLog命令

HyperLogLog 是一种使用随机化的算法,以少量内存提供集合中唯一元素数量的近似值。
HyperLogLog 可以接受多个元素作为输入,并给出输入元素的基数估算值:
基数 :集合中不同元素的数量。如 {‘apple’, ‘banana’, ‘cherry’, ‘banana’, ‘apple’} 的基数就是 3
估算值 :算法给出的基数并不是精确的,可能会比实际稍微多一些或少一些,但会控制在合理范围
HyperLogLog 优点是,即使输入元素的数量或者体积非常非常大,计算基数所需的空间总是固定的、并且是很小的

Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身 ,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

HyperLogLog 相关的一些基本命令。

 

 5.8 其他命令(key相关的命令)

(1) keys返回满足给定pattern 的所有key

(2) exists确认一个key 是否存在,存在返回1

(3) del删除一个key

(4) rename重命名key:rename oldkey newkey

(5) type返回 值的类型: type key

设置key的生存时间:缓存的数据一般都是需要设置生存时间的,即:到期后数据销毁。

(6) expire key seconds

设置key的生存时间(单位:秒)key在多少秒后会自动删除

(7) ttl key 查看key剩余的生存时间

(8) persist key 清除生存时间

(9) 获取服务器信息和统计:info

(10) 删除当前数据库中的所有keyflflushdb

(11) 删除所有数据库中的所有key:flflushall

 redis默认有16个数据库(db0-db15),默认访问db0。

5.9 Redis的多数据库

一个 redis 实例 key 包括多个数据库,客户端可以指定连接某个 redis 实例的哪个数据库,就好比一个 mysql 中创建多个数据库,客户端连接时指定连接哪个数据库。
一个 redis 实例最多可提供 16 个数据库,下标从 0-15 ,客户端默认连接第 0 号数据库,也可以通过 select 选择连接哪个数据库

key 的数据移动到 1 号数据库 : move key 数据库编号

可以查看redis客户端的图形化工具

 

6.Redis的事务管理

Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:

事务是一个单独的隔离操作 :事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

一个事务从开始到执行会经历以下三个阶段:

开始事务。
命令入队。
执行事务。
以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令。

 7.Redis发布订阅模式

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

Redis 客户端可以订阅任意数量的频道。
像微信的订阅号就是使用了发布订阅这种消息通信模式,作者发布文章后,所有订阅的客户端都可接收到文章的推送。
频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 client5 client1 之间的关系:

当有新消息通过publish 命令发送给 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

下面通过两个客户端演示发布订阅模式: 

linux远程命令端Secure CRT作为redis的一个客户端1

Redis的远程命令端Xshell作为另一个客户端2

现在在客户端1中订阅了redisChannel频道

现在再开启一个  redis 客户端Xshell,然后在同一个频道 redisChannel 发布两次消息,订阅者就能接收到消息。
打开Xshell,输入连接名,Linux主机ip,以及linux的用户名和密码去连接linux,建立会话。

 

在客户端2 就是Xshell中通过命令redis-cli连接redis,然后通过publish命令向频道 redisChannel发布两次消息,订阅者也就是客户端1中就能接收到消息。只要订阅该频道的客户端都能自动接收到消息。

# 订阅者的客户端会显示如下消息

 这种发布订阅的通信模式可以实现自动接收操作。

8.Jedis连接Redis

之前都是使用命令操作redis,下面使用java操作redis,在Java中使用Jedis工具连接Redis

第一步:创建项目,导入依赖

创建maven工程的java项目

 pom中引入jedis依赖

 

 注意:

1 )确认远程服务器是否可以 ping : ping vm ip 地址

在linux中输入ip addr 查看linux的主机ip,然后在本机cmd中输入ping ip

 

2) 确认防火墙是否关闭或放行
本机cmd中防火墙命令
service iptables stop
service iptables status
linux中防火墙命令
systemctl stop firewalled
systemctl status firewalld

第二步:链接服务器

方案一 单实例链接

Jedis jedis = new Jedis(“ip 地址 ”, 端口号 );// 建立链接
核心代码:
public static void main(String[] args) {
    //linux的主机ip,和redis端口号,创建连接对象,有两个方法,一个set存键值对,一个get取值
    Jedis jedis = new Jedis("192.168.153.136", 6379);
    jedis.set("stu", "张三");
    String stu = jedis.get("stu");
    System.out.println("redis中键stu对应的值:"+stu);
}
常见异常 :

 

 解决方案:

虚拟机客户端连接的 ip 127.0.0.1, 意思是连接的本机 , 其他机器无法连接 , 这里需要修改配置文件 , 将连接地址改为虚拟机的地址, 就可以了 .
修改 redis.conf 文件里面的 bind 连接地址 , 将连接地址改为自己虚拟机的 ip

重新启动服务,Jedis就可以正常连上了

Idea 中控制台打印:

 服务器上存储:

可以通过linux远程连接命令端Secure CRT或者Xshell去连接linux,然后通过命令启动并连接linux上的redis服务器,并查看。或者通过redis客户端图形化工具Redis Desktop Manager直接查看到如下图。

 方案二:连接池

jedis连接池连接,后面会使用Spring的配置文件来整合。

public static void main(String[] args) {
    JedisPoolConfig config = new JedisPoolConfig();//创建连接池配置对象,设置配置项
    config.setMaxTotal(30);//最大连接数
    config.setMaxIdle(10);//最大的空闲
    //获取连接池
    redis.clients.jedis.JedisPool jedisPool = new redis.clients.jedis.JedisPool(config, "192.168.153.136", 6379);
    Jedis jedis=null;
    try {
        jedis=jedisPool.getResource();//获取资源(连接对象)
        jedis.set("name","熊孩子");
        String name = jedis.get("name");
        System.out.println("redis中键为name的值是:"+name);
    }catch (Exception e){
        e.printStackTrace();
    }finally {  //释放资源
        if (jedis!=null)   jedis.close();
        if (jedisPool!=null) jedisPool.close();
    }
}

 服务端存储确认:

9.Redis持久化方式

9.1 什么是Redis持久化

由于 redis 的值放在内存中,为防止突然断电等特殊情况的发生,需要对数据进行持久化备份。即将内存数据保存到硬盘。

9.2 Redis 持久化存储方式

9.2.1 RDB持久化

rdb 是以二进制文件,是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
优点: 使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
缺点: RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
这里说的这个执行数据写入到临时文件的时间点是可以通过配置来自己确定的,通过配置 redis n 秒内如果超过m 个 key 被修改就执行一次 RDB 操作。这个操作就类似于在这个时间点来保存一次 Redis 的所有数据,一次快照数据。所有这个持久化方法也通常叫做 snapshots
RDB 默认开启, redis.conf 中的具体配置参数如下;

 注意:测试时使用root用户操作

shutdown关闭redis服务器后,可以在redis目录中看到生成了dump.rdb文件,当重启redis,重新连接后,仍可以读取到数据。单独的子进程进行RDB操作,去持久化和读取rdb文件,主进程不用关心。若是将dump.rdp文件给删除了,那就找不到了。
在linux客户端通过命令演示

 当再重启连接redis后还是能查询到数据,自动读取RDB文件到内存中(redis)

9.2.2 AOF持久化

Append-Only File 将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部 ,在 append 操作返回后 ( 已经写入到文件或者将要写入) ,才进行实际的数据变更, “日志文件”保存了历史所有的操作过程;当 server 需要数据恢复时,可以直接 replay 此日志文件,即可还原所有的操作过程 AOF 相对可靠, 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 是文件操作,对于变更操作比较密集的 server ,那么必将造成磁盘 IO 的负荷加重;此外 linux 对文件操作采取了“ 延迟写入 手段,即并非每次 write 操作都会触发实际磁盘操作,而是进入了 buffer 中,当 buffer 数据达到阀值时触发实际写入( 也有其他时机 ) ,这是 linux 对文件系统的优化,但是这却有可能带来隐患,如果 buffer 没有刷新到磁盘,此时物理机器失效( 比如断电 ) ,那么有可能导致最后一条或者多条 aof 记录的丢失。
通过上述配置文件,可以得知 redis 提供了 3 中 aof 记录同步选项:
always 每一条 aof 记录都立即同步到文件,这是最安全的方式 ,也以为更多的磁盘操作和阻塞延迟,是 IO 开支较大。
everysec 每秒同步一次,性能和安全都比较中庸的方式,也是 redis 推荐的方式 。如果遇到物理服务器故障,有可能导致最近一秒内 aof 记录丢失 ( 可能为部分丢失 )
no redis 并不直接调用文件同步,而是交给操作系统来处理, 操作系统可以根据 buffer 填充情况 / 通道空闲时间等择机触发同步;这是一种普通的文件操作方式。性能较好,在物理服务器故障时,数据丢失量会因 OS 配置有关。
其实,我们可以选择的太少, everysec 是最佳的选择。如果你非常在意每个数据都极其可靠,建议你选择一款 关系性数据库”

 

 输入i进入编辑模式,修改完毕,Esc键退出编辑模式,输入:进入底线模式,输入wq保存并离开编辑器vi

 

 vi 进入aof文件可以看到之前的操作和数据

 9.2.3 AOFRDB区别

见上面

10.Redis主从复制

持久化保证了即使 redis 服务重启也不会丢失数据,但是当 redis 服务器的硬盘损坏了可能会导致数据丢失,通过redis 的主从复制机制就可以避免这种单点故障(单台服务器的故障)。
redis 中的数据和从上的数据保持实时同步 , 当主 redis 写入数据时通过主从复制机制复制到两个从服务上。
主从复制不会阻塞 master ,在同步数据时, master 可以继续处理 client 请求 .
主机 master 配置 : 无需配置
推荐主从模式同步数据 :

工作中一般选用:一主两从或一主一从

数据会同步到从服务器。在这个集群中的几台服务器上都有同样的数据。

主从搭建步骤 :
主机:不用配置。仅仅只需要配置从机 , 从机 slave 配置 :( 这里是伪集群 )
第一步:复制出一个从机 , 注意使用 root 用户
就是在/home/bowei中将redis文件复制一份
# cp redis  redis1  -r     递归复制

 第二步:修改从机的redis.conf中replicaof值为主redis的ip和端口号(replicaof翻译为复制)

语法: replicaof 主机 ip 主机端口号
提示 :linux 检索文件 : 输入 ?replicaof 当前页没有时,输入 n ,查找下一页(要在安全模式下)

第三步:修改从机redis.conf中端口号port地址为 6380

因为主redis的端口号默认是6379,从机端口号不能是6379.(毕竟两个redis机的ip一样)。

Esc退出编辑模式,输入:进入底线模式,wq保存并离开vi编辑器。

 第四步:清除从机中的持久化文件

从机是从主redis复制过来,会有持久化文件

#rm -rf dump.rdb   【f表示强制删除,r表示递归删除,常用于删除文件夹】

第五步:启动从机redis1服务器
[root@localhost redis1]# ./bin/redis-server ./redis.conf
第六步:启动 6380 的客户端(连接从机redis1)
停止客户端 : ./bin/redis-cli -p 6380 shutdown

 然后新建一个会话去启动主redis

 通过info replication命令查看当前redis是主机还是从机

 

 注意:

主机一旦发生增删改操作,那么从机会自动将数据同步到从机中
从机不能执行写操作 , 只能读

建立主从关系后,查看所有keys

 

向主redis的db0(默认)添加键值 set A  B

 

 查看从redis中同步 该数据

 主从复制的过程原理

当从库和主库建立 MS(master slaver) 关系后,会向主数据库发送 SYNC异步 命令;
主库接收到 SYNC 命令后会开始在后台保存快照( RDB 持久化过程),并将期间接收到的写命令缓存起来;
快照完成后 , Redis 会将快照文件和所有缓存的写命令发送给从 Redis
Redis 接收到后,会载入快照文件并且执行收到的缓存命令;
Redis 每当接收到写命令时就会将命令发送从 Redis ,保证数据的一致;【内部完成 , 所以 不支持客户端在从 机人为写数据 。】

复制架构中出现宕机情况 ?
Redis 宕机 :
重启就好
Redis 宕机 :
从数据库 ( 从机 ) 中执行 slaveof no one 命令,断开主从关系并且提升为主库继续服务
[ 把一个从机做为主机,这个时候新主机[之前的从机] 就具备写入的能力 ]
主服务器修好后,重新启动后,执行 slaveof 命令,将其设置为从库[ 老主机设置为从机 ]
[ 手动执行,过程复杂,容易出错。 ] 是否有更好的方案?

11.Redis哨兵模式

11.1.什么是Redis哨兵模式

哨兵模式 :给集群分配一个站岗的。
哨兵的作用 就是对 Redis 系统的运行情况监控,它是一个独立进程 , 它的功能:
     1. 监控主数据库和从数据库是否运行正常;
     2. 主数据出现故障后自动将从数据库转化为主数据库;
     哨兵主要是用来监听主服务器的,所以一般把哨兵部署在从服务器上监听
如果主机宕机,开启选举工作,选择一个从做主机。

11.2.演示Redis哨兵模式

11.2.1.搭建环境(一主两从)

环境准备:一主两从,启动任一从机时,启动哨兵模式
之前已经搭建 好了一主一从,现在再加上一个从机即可(就是再复制一个redis文件)
首先打开虚拟机,启动Linux系统

 然后打开Linux远程命令端Xshell

 

 

 现在再加入一个从机slave,并配置,修改配置文件中的端口号port和replicaof实现主从复制

 

 在安全模式下输入?port空格后快速检索到port,改为6381

 

 

 Esc键退出编辑模式,进入底线模式,:wq退出vi

然后删除所有从机的持久化文件,然后查看有没有redis进程,有就关掉对应redis服务。

下面在Xshell会话窗口中开始逐个启动redis服务器并访问(连接)

在第一个会话窗口中启动主redis服务器并连接(这几个redis都是在同一台机子ip同,端口号不同)

 在第二个会话窗口中启动从redis1服务器并连接

在第三个会话窗口中启动从redis2服务器并连接

 

 

 下面通过命令info replication查看主从关系

查看主redis中主从关系

 查看从redis1中主从关系,redis2也是。

 11.2.配置哨兵:

哨兵主要是用来监听主服务器的,所以一般把哨兵部署在从服务器上监听。
配置哨兵

1.启动哨兵进程,首先需要创建哨兵配置文件vi sentinel.conf,也可从源码配置redis/sentinel.conf中复制内容,也可以直接自定义该文件到bin目录下

2.在配置中输入 :sentinel monitor mastername 内网 IP(127.0.0.1) 6379 1
说明:mastername 监控主数据的名称,自定义
          127.0.0.1 :监控主数据库的 IP;
         
           6379: 端口
          
          1 :最低通过票数(通过票数决定选哪个从机作为主机,这个票数是我们配置时自己设置)
下面我们随便选择从机redis2中设置哨兵,进入bin中通过 vi sentinel.conf命令创建并编辑这个哨兵配置文件输入sentinel monitor mastername 内网 IP(127.0.0.1) 6379 1
即设置哨兵监测这个ip机中6379端口号的主数据库mastername

 

 

 11.3. 启动哨兵:

哨兵是一个单独的进程,启动之前确保主从服务是正常的。先启动主服务,后启动从服务

在从数据库redis2的bin中启动哨兵,并把日志写入指定的文件
命令如下  # ./redis-sentinel  ./sentinel.conf >sent.log &
./redis-sentinel启动哨兵
./sentinel.conf >sent.log & 将日志写入sent.log文件
虽然哨兵 (sentinel) 释出为一个单独的可执行文件 redis-sentinel , 但实际上它只是一个运行在特殊模式下的 Redis 服务器,你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵 (sentinel)

 这时候主从服务器目前是正常运行的,现在需要重新启动从服务器redis2 命令后面缀上--sentinel表示已经启动哨兵模式。

 quit退出服务后,进入redis2中的bin中,vi sentinel.conf进入哨兵配置文件,查询配置文件sentinel.conf中生成的内容:

启动哨兵的时候,修改了哨兵的配置文件。如果需要再次启动哨兵,需要删除 myid 唯一标示。
(保险的做法就是启动的一次,新配置一次)

到目前位置在一主两从的主从关系搭建以及在一个从机中配置哨兵模式并启动哨兵已经完成。

这个时候主从机并没有什么变化,只是现在进程中多了一个哨兵进程

在主redis的会话窗口中先退出redis,通过命令 #ps -aux|grep redis查看和redis相关进程

 11.4. 测试哨兵模式:主机宕机

机房意外:断电了。硬件故障:硬盘坏了
在连接主redis的会话窗口中查看redis相关进程,找到主redis的进程,杀死该进程

 这时候就连不上主redis服务器,服务被强行关闭

 哨兵模式是如果主机宕机,它会通过选举的的方式选择一个从机作为主机,

那么现在再看从机redis1主从信息info replication

 从机redis2主从信息info replication

哨兵控制台:从库自动提升为主库
哨兵替代运维。自动监控完成。
同时也会自动修改 redis.conf 的主从配置文件。
若是查看从机的配置文件redis.conf 中显示replicaof 192.168.153.136 6380 表示该从机现在复制的是6380端口的主机
指向了新主机。再次启动原有的主机 , 原有的主机会变为从机。

 在当前的主redis1中添加数据,然后在从redis2就可以读取到

12.Redis集群方案

目标:搭建 redis 集群环境,使用 Jedis 访问 redis 集群

12.1.单台redis问题

项目由于访问量过大,宕机了,是因为大批量用户同时访问后台数据,数据库压力太大,如何调整才能抵抗这种大用户 量访问的问题?单台redis无法满足要求,这时候就需要搭建多台redis,客户端如何和这些redis访问的,就需要使用Redis集群方案。

12.2 redis-cluster架构图

[ˈklʌstə(r)]

 架构细节:

(1)redis节点之间是如何通信的?怎么知道是否能用

所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

(2)如何知道某台redis是否宕机?
如果超过一半的redis节点向该redis发送ping命令没有响应,则认为该台redis已经宕机了。
(3) 客户端与 redis 节点直连 , 不需要中间 prox代理 . 客户端不需要连接集群所有节点 , 连接集群中任何一个可用节点即可(间接连接到其它节点)
(4)redis-cluster 把所有的物理节点映射到 [ 0-16383 ]slot哈希槽 ,cluster 负责维护 node节点<->slot哈希槽<->value值
(5)redis节点是互通的,redis集群中如何确定把值存在哪个redis中?
Redis 集群中内置了 16384 个哈希槽,当客户端写操作需要在 Redis 集群中放置一个 key-value 时, redis 先对 key 使用 crc16 算法得到哈希值,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点

 12.3 redis-cluster投票:容错

两个问题:

 1.redis集群中,如何判断某台主机是否宕机?

2.redis集群什么时候进入fail状态,不可用?

redis的投票机制/容错机制/心跳机制(ping-pang机制)  一个说法
用来检测当前服务器是否能够使用,如果某台主机宕机,怎么判断是否宕机?

(1) 集群中所有 master主机 参与投票 , 如果半数以上 master 节点与其中一个 master 节点通信超时 (cluster-node-timeout), 认为该master 节点挂掉 .
(2): 什么时候整个集群不可用 (cluster_state:fail)?
        如果集群任意master 挂掉 , 且当前 master 没有 slave ,则集群进入 fail 状态。也可以理解成集群            的 [0-16383]slot 映射不完全时进入fail 状态。(没有从机的主机挂掉,数据就会丢失,如果有从          机,主机挂掉,从机可以顶上,数据无碍)
       如果集群超过半数以上 master 挂掉,无论是否有 slave ,集群进入 fail 状态。

12.3 集群搭建步骤

第一步 : 安装 redis
目前已经安装了一个主机和它的两个从机

第二步 : 创建集群目录
# mkdir redis-cluster    集群名字自定义

第三步 : 在集群目录下创建节点目录

我们要搭建的集群环境是三主三从
搭建集群最少也得需要 3 台主机,如果每台主机再配置一台从机的话,则最少需要 6 台机器。 设计端口如下:创建 6 个redis 实例,需要端口号 7001~7006
在集群目录下创建节点目录,每个节点都是主机(主redis),那么就是将已有的主机复制到该集群下重命名为7001
第四步:如果存在持久化文件,则删除
第五步:修改 redis.conf 配置文件,打开 Cluster-enable yes

 vi进入编辑器安全模式后,输入?cluster-enable回车  可快速定位,然后删除前面的#,就可以wq保存退出来

说明: cluster-enable 是否支持集群
第六步:修改 redis.conf 配置文件,修改7001主机节点的端口为7001,(和第五步可以一起的)
第七步:复制出 7002-7006 机器
就是在集群目录下,将已经配置好的主机节点文件7001复制5份重命名7002-7006保存在集群目录

第八步:修改 7002-7006 机器的端口
分别进入各个主机/机器的redis.conf配置文件,修改port
第九步:启动 7001-7006 这六台机器,写一个启动脚本:自定义 shel 脚本
可以一个个启动这6个主机/实例,这里是写一个启动脚本 start-all.sh,一起启动所有主机/实例。
在集群目录下,通过vi   start-all.sh 去创建并编辑该脚本(脚本名自定义的)
在脚本中填写所有的启动操作,如下:
cd 7001
./bin/redis-server ./redis.conf
cd ..
cd 7002
./bin/redis-server ./redis.conf
cd ..
cd 7003
./bin/redis-server ./redis.conf
cd ..
cd 7004
./bin/redis-server ./redis.conf
cd ..
cd 7005
./bin/redis-server ./redis.conf
cd ..
cd 7006
./bin/redis-server ./redis.conf
cd ..

 

 Esc退出编辑模式,输入:wq退出

第十步:修改 start-all.sh 文件的权限
# chmod u+x start-all.sh
第十一步:启动所有的实例
在集群目录下启动(因为脚本在集群目录下) # ./start-all.sh   

第十二步:创建集群(关闭防火墙)
注意:在任意一台上运行 不要在每台机器上都运行,一台就够了 redis 5.0.5 中使用 redis-cli --cluster 替代 redis- trib.rb,命令如下
redis-cli --cluster create ip:port ip:port --cluster-replicas 1
下面在集群目录下的7001机器上创建集群,进入7001的bin中执行redis-cli指令
./redis-cli --cluster create 192.168.153.136:7001 192.168.153.136:7002 192.168.153.136:7003 192.168.153.136:7004 192.168.153.136:7005 192.168.153.136:7006 --cluster-replicas 1

 

12.4 客户端连接集群

命令  -c:指定是集群连接

下面通过客户端去连接集群中7001主机(主redis),并存值

 到目前为止,集群的搭建和客户端的连接已经完成了。

关闭防火墙 :service iptables stop
查看防火墙状态 :service iptables status

12.5 查看集群信息

cluster info

12.6 查看集群中节点信息

cluster nodes

12.7 Jedis连接集群

12.7.1 关闭防火墙

只要是连接redis前,都要systemctl status firewalld查看下linux的防火墙是否关闭,没有则通过

systemctl stop firewalld关闭。连接不上,要考虑防火墙、ip、端口号问,另外如果访问的redis正好宕机重启,那么它就变成从机,只能读。

注意:如果redis重启,需要将redis中生成的dump.rdbnodes.conf文件删除,然后再重启。

12.7.2 代码实现

注意jedis的版本,当前redis是5.5.0,jedis版本至少是2.9.0

其他版本有可能报错:java.lang.NumberFormatException: For input string: "7002@17002"

 改成

 

/**
 * 使用 Jedis 访问 redis 集群,redis集群解决了大用户量的访问带来的数据库压力
 */
public class JedisClusterDemo {
    public static void main(String[] args) {

        //1.创建一个集合,保存集群信息
        Set<HostAndPort> nodes = new HashSet<HostAndPort>();
        nodes.add(new HostAndPort("192.168.153.136", 7001));
        nodes.add(new HostAndPort("192.168.153.136", 7002));
        nodes.add(new HostAndPort("192.168.153.136", 7003));
        nodes.add(new HostAndPort("192.168.153.136", 7004));
        nodes.add(new HostAndPort("192.168.153.136", 7005));
        nodes.add(new HostAndPort("192.168.153.136", 7006));
        //2.创建集群操作对象
        JedisCluster cluster = new JedisCluster(nodes);  // 创建一连接,JedisCluster对象,在系统中是单例存在
        //3.操作数据
        cluster.set("test1", "test111"); //执行JedisCluster对象中的方法,方法和redis指令一一对应。
        String result = cluster.get("test1");
        System.out.println(result);
        //4.向集群中存储数据到list列表中
        cluster.lpush("site-list", "java");
        cluster.lpush("site-list", "c");
        cluster.lpush("site-list", "mysql");
        // 获取存储的数据并输出
        List<String> list = cluster.lrange("site-list", 0 ,2);
        for(int i=0; i<list.size(); i++) {
            System.out.println("列表项为: "+list.get(i));
        }
        // 程序结束时需要关闭JedisCluster对象
        cluster.close();
        System.out.println("集群测试成功!");
    }
}

 

13.Redis面试-缓存穿透,缓存击穿,缓存雪崩问题

13.1 缓存的概念

什么是缓存 ?
广义的缓存就是在第一次加载某些可能会复用数据的时候,在加载数据的同时,将数据放到一个指定的地点做保存。再下次加载的时候,从这个指定地点去取数据。这里加缓存是有一个前提的,就是从这个地方取数据,比从数据源取数据要快的多。

java 狭义一些的缓存,主要是指三大类

1. 虚拟机缓存( ehcache JBoss Cache
2. 分布式缓存( redis memcache
3. 数据库缓存

正常来说,速度由上到下依次减慢

缓存取值图:

 13.2 缓存雪崩

13.2.1.缓存雪崩产生的原因

缓存雪崩通俗简单的理解就是:由于原有缓存失效(或者数据未加载到缓存中),新缓存未到期间(缓存正常从Redis中获取,如下图)所有原本应该访问缓存的请求都去查询数据库了,而对数据库 CPU 和内存造成巨大压力, 严重的会造成数据库宕机,造成系统的崩溃。

 缓存失效的时候如下图:

 13.2.2.解决方案:

1 :在缓存失效后,通过加锁(lock)或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。虽然能够在一定的程度上缓解了数据库的压力但是与此同时又降低了系统的吞吐量。
public Users getByUsers(Long id) {
// 1.先查询redis
String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()
[1].getMethodName()
+ "-id:" + id;
String userJson = redisService.getString(key);
if (!StringUtils.isEmpty(userJson)) {
Users users = JSONObject.parseObject(userJson, Users.class);
return users;
}
Users user = null;
try {
lock.lock();
// 查询db
user = userMapper.getUser(id);
redisService.setSet(key, JSONObject.toJSONString(user));
} catch (Exception e) {
} finally {
lock.unlock(); // 释放锁
}
return user;
}
注意 : 加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间 key 是锁着 的,这是过来1000 个请求 999 个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法。

2 : 分析用户的行为,不同的 key ,设置不同的过期时间,让缓存失效的时间点尽量均匀。

13.3 缓存穿透

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

解决方案:

1. 如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。

2. 把空结果,也给缓存起来,这样下次同样的请求就可以直接返回空了,既可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key 进行预先校验,然后再放行给后面的正常缓存处理逻辑。

public String getByUsers2(Long id) {
// 1.先查询redis
String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()
[1].getMethodName()+ "-id:" + id;
String userName = redisService.getString(key);
if (!StringUtils.isEmpty(userName)) {
return userName;
}
System.out.println("######开始发送数据库DB请求########");
Users user = userMapper.getUser(id);
String value = null;
if (user == null) {
// 标识为null
value = "";
} else {
value = user.getName();
}
redisService.setString(key, value);
return value;
}

注意:再给对应的ip存放真值的时候,需要先清除对应的之前的空缓存。

13.4 缓存击穿

对于一些设置了过期时间的 key ,如果这些 key 可能会在某些时间点被超高并发地访问,是一种非常 热点 的数据(比如热搜)。 这个时候,如果热搜的key失效,需要考虑一个问题:缓存被“ 击穿 的问题,这个和缓存雪崩的区别在于这里针对某一 key 缓存,前者则是很多key

热点key:

某个 key 访问非常频繁,当 key 失效的时候有大量线程来构建缓存,导致负载增加,系统崩溃。

解决办法:

①使用锁,单机用 synchronized,lock 等只允许个别线程在某个时间访问,降低访问效率但保证安全性。分布式用分布式锁。
②不设置缓存过期时间,而是设置在key对应的 value 里。如果检测到存的时间超过过期时间则异步更新缓存。

14.Redis面试-分布式锁

14.1 使用分布式锁要满足的几个条件:

1. 系统是一个分布式系统(关键是分布式,单机的可以使用 ReentrantLock 或者 synchronized 代码块来实现)
2. 共享资源(各个系统访问同一个资源,资源的载体可能是传统关系型数据库或者 NoSQL
3. 同步访问(即有很多个进程同时访问同一个共享资源。)

14.2 什么是分布式锁?

线程锁:
主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM 中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如 synchronized是共享对象头,显示锁 Lock 是共享某个变量( state

进程锁:
为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized 等线程锁实现进程锁。

分布式锁:
当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。

14.3 应用的场景

线程间并发问题和进程间并发问题都是可以通过分布式锁解决的,但是强烈不建议这样做!因为采用分布式锁解决,这些小问题是非常消耗资源的!分布式锁应该用来解决分布式情况下的多进程并发问题才是最合适的。
有这样一个情境,线程 A 和线程 B 都共享某个变量 X
如果是单机情况下(单 JVM ),线程之间共享内存,只要使用线程锁就可以解决并发问题。
如果是分布式情况下(多 JVM ),线程 A 和线程 B 很可能不是在同一 JVM 中,这样线程锁就无法起到作用了,这时候就要用到分布式锁来解决。

14.4.分布式锁的实现方式和原理

分布式锁可以基于很多种方式实现,比如 zookeeper redis... 。不管哪种方式,他的基本原理是不变的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

14.5.redis中如何实现分布式锁

14.5.1.实现原理

Redis 为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对 Redis 的连接并不存在竞争关系。redis SETNX 命令可以方便的实现分布式锁。

常用指令

SETNX key value  加锁 (key自定义要求不存在),返回1则加锁,返回0则key被其它客户端上锁了,value设置为锁的超时时间(当前时间+锁有效时间)。
del key 释放锁
get key 获取锁的value也就是锁的有效时间
getset  key value  设置这个锁的有效时间
回答面试的核心点
1 、同一时刻只能有一个进程获取到锁。 setnx
2 、释放锁:锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁;
(最简单的方式就是 del , 如果在删除之前死锁了。)

 

解决死锁
如果一个持有锁的客户端失败或崩溃了不能释放锁,该怎么解决
我们可以通过锁的键对应的时间戳来判断这种情况是否发生了,如果当前的时间已经大于锁的失效时间,说明该锁已失效,可以被重新使用, 其他进程再次通过 setnx 来抢锁。
C3 发送 SETNX lock.foo 想要获得锁,由于 C0 还持有锁,所以 Redis 返回给 C3 一个 0 C3 发送 GET lock.foo 以检查锁是否超时了,如果没超时,则等待或重试。 反之,如果已超时, C3 通过下面的操作来尝试获得锁: GETSET lock.foo 通过 GETSET, C3 拿到的时间戳如果仍然是超时的,那就说明, C3 如愿以偿拿到锁了。 如果在 C3 之前,有个叫 C4 的客户端比C3 快一步执行了上面的操作,那么 C3 拿到的时间戳是个未超时的值,这时, C3 没有如期获得锁,需要再次等待或重试。
持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时, 再去做DEL 操作,因为可能客户端因为某个耗时的操作而挂起,操作完的时候锁因为超时已经被别人获得,这时就不必解锁了

 

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值