万字详解 Redis

本文深入介绍了Redis和NoSQL数据库的特点,包括高并发、高性能、易扩展性和数据模型的灵活性。详细阐述了Redis的安装、使用、数据结构、事务管理、发布订阅模式,以及持久化方案如RDB和AOF。同时,讨论了Redis的主从复制、哨兵模式和集群搭建,分析了缓存雪崩、穿透和击穿的解决方案,并探讨了分布式锁的应用。
摘要由CSDN通过智能技术生成

Redis

Nosql

介绍

NoSQL最常见的解释是“non-relational”(非关系型数据库), “Not Only SQL”也被很多人接受。NoSQL仅仅是一个概念,泛指非关系 型的数据库,区别于关系数据库,它们不保证关系数据的ACID特性。

现在网站的特点

(1) 高并发读写
Web2.0网站,数据库并发负载非常高,往往达到每秒上万次的读写请求

(2) 高容量存储和高效存储
Web2.0网站通常需要在后台数据库中存储海量数据,如何存储海量数据并进行高效的查询往往是一个 挑战

(3) 高扩展性和高可用性
随着系统的用户量和访问量与日俱增,需要数据库能够很方便的进行扩展、维护

优势

(1)易扩展性
NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。数据之间无关系,这 样就 非常容易扩展。也无形之间,在架构的层面上带来了可扩展的能力。

(2)大数据量,高性能
NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性, 数据 库的结构简单。一般MySQL使用Query Cache,每次表的更新Cache就失效,是一种大粒度的 Cache,在针对 web2.0的交互频繁的应用,Cache性能不高。而NoSQL的Cache是记录级的,是一种细 粒度的Cache,所以 NoSQL在这个层面上来说就要性能高很多了。

(3)灵活的数据模型
NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删 字段 是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。这点在大数据量 的web2.0时代 尤其明显。

(4) 高可用 NoSQL在不太影响性能的情况,就可以方便的实现高可用的架构。
比如Cassandra,HBase模型,通过 复制
模型也能实现高可用。

Redis介绍

Redis 是什么

c语言编写的,高性能的(key/value)分布式内存数据库,

就像一个超大的 HashMap,但是可以进行持久化,支持事务等

Redis 与其他 key - value 缓存产品有以下三个特点:

(1) Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用

(2) Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储

(3) Redis支持数据的备份,即master-slave(主从)模式的数据备份

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**(这就是为什么redis能在单线程情况下保持高效)**

Redis 应用场景

(1) 缓存(数据查询,短连接,新闻内容,商品内容等),使用最多

(2) 聊天室在线好友列表

(3) 任务队列(秒杀,抢购,12306等)

(4) 应用排行榜

(5) 网站访问统计

(6) 数据过期处理(可以精确到毫秒) (验证码)

(7) 分布式集群架构中的session问题

Redis 安装

我本人虽然买了阿里云的redis云数据库,但为了操作方便,还是在 ECS 里装了一个redis,供教学使用

image-20210530004516972

远程使用

命令方式

1、直接运行

image-20210530011234934

这样运行的话,服务器就不能进行其他操作了,所以不推荐

2、后台运行

我的 redis 安装路径为 /home/admin/myapps/redis

image-20210530011056204

运行时,我们要让 bin 下的 redis-server 和 配置文件 redis.conf一起执行:

redis.conf 文件中,配置了守护线程为 yes

image-20210530011412798

所要执行的命令如下:

./bin/redis-server ./redis.conf

这样,就成功在后台开启redis了

image-20210530011541933

3、结束后台进程
强制结束

我们可以使用 ps -aux | grep redis来查询后台 redis 的进程id

然后 使用 kill -9 进程ID 的指令,去杀死这个redis 进程

image-20210530011840014

正常结束流程

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

[root@localhost redis]# ./bin/redis-cli shutdown

第三方程序远程连接

这里,我使用的是 RDM

image-20210530013528518

Redis 数据结构

当前的 Redis 支持 6 种数据类型,它们分别是字符串(String)、列表(List)、集合(set)、哈希结构 (hash)、有序集合(zset)和基数(HyperLogLog)

image-20210529225643325

Redis 常用指令

redia 指令学习网站: http://doc.redisfans.com/index.html

具体的内容我就不在这边写了

Redis 事务管理

开启事务,使用 : multi

**执行事务,使用 :**exec

类似于 mysql 中的 begin 和 commit

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set u1 user1
QUEUED
127.0.0.1:6379> get u1
QUEUED
127.0.0.1:6379> sadd tag c++ html java
QUEUED
127.0.0.1:6379> smembers tag
QUEUED
127.0.0.1:6379> exec
1) OK
2) "user1"
3) (integer) 3
4)  1) "java"
    2) "html"
    3) "c++

Redis 发布订阅模式

介绍

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

Redis 客户端可以订阅任意数量的频道。

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

image-20210530120117581

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

image-20210530120142588

设计模式中的观察者模式很像

使用

这里要使用两个客户端来演示

订阅者客户端先订阅:

image-20210530121051282

另一个客户端,发布者在chnnel1发布信息:

image-20210530121121002

我们的订阅者,在这个时候,就同时受到了信息:

image-20210530121150122

Jedis 连接 Redis

导入 Maven 依赖:

<dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>2.7.2</version>
  </dependency>

代码如下:

我本人使用的是 阿里云的redis 数据库,所以有设置账户密码的配置

public class JedisDemo1 {
    public static void main(String[] args) {
        JedisPoolConfig config = new JedisPoolConfig();
        //最大空闲连接数,需自行评估,不超过Redis实例的最大连接数
        config.setMaxIdle(200);
        //最大连接数,需自行评估,不超过Redis实例的最大连接数
        config.setMaxTotal(300);
        config.setTestOnBorrow(false);
        config.setTestOnReturn(false);
        String host = "redis ip";
        String password = "密码";
        JedisPool pool = new JedisPool(config, host, 6379, 3000, password);
        Jedis jedis = null;
        try {
            jedis = pool.getResource();
            /// ... do stuff here ... for example
            jedis.set("foo", "bar");
            String foobar = jedis.get("foo");
            System.out.println(foobar);
            jedis.zadd("sose", 0, "car");
            jedis.zadd("sose", 0, "bike");
            Set<String> sose = jedis.zrange("sose", 0, -1);
            System.out.println(sose);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        /// ... when closing your application:
        pool.destroy();
    }
}

结果如下:

image-20210530123218166

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

AOF 持久化

Append-Only File,将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在 append 操作返回后(已经 写入到文件或者将要写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当 server 需要数据 恢复时,可以直接 replay 此日志文件,即可还原所有的操作过程。AOF 相对可靠,AOF 文件内容是字符串,非常 容易阅读和解析。

**优点:**可以保持更高的数据完整性,如果设置追加 file 的时间是 1s,如果 redis 发生故障,最多会丢失 1s 的数 据;且如果日志写入不完整支持 redis-check-aof 来进行日志修复;AOF 文件没被 rewrite 之前(文件过大时会对 命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall)。

**缺点:**AOF 文件比 RDB 文件大,且恢复速度慢。

AOF默认关闭,我们需要修改 redis.conf 来开启:

image-20210530125911197

RDB 和 AOF 的区别

RDB:

是在某一时间点,将数据写入一个临时文件(dump.db)。持久化结束后,用这个临时文件,替换上一次的持久化文件,达到数据恢复。

**优点:**使用单独子线程来进行持久化,主进程不会进行任何 IO 操作,保证了高性能

**缺点:**RDB是间隔一段时间进行持久化的,如果持久化之间 redis 发生故障,会发生数据丢失。独一这张方式适合数据要求不严谨的时候

AOF:

Append-only file,将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在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文件大,且恢复速度慢;因为每隔一秒存一次,所以,效率会低一些。

**总的来说:**RDB效率高,但是因为是间隔一段时才持久化,所以不是那么可靠

AOF因为每隔一秒,就去持久化一次,所以,相对于RDB要可靠一点,但是其效率就和RDB没得比了

Redis 默认是使用RDB的

Redis 主从复制

主从复制介绍

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

image-20210530132108219

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

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

==注意:==主从复制,其实相当于伪集群

Ø 主机一旦发生增删改操作,那么从机会自动将数据同步到从机中

Ø 从机不能执行写操作,只能读

image-20210530132729911

搭建主从

  1. 主机的ip需要修改

默认是使用 127.0.0.1的,我们要修改为 ECS 的ip地址

image-20210531111100941

  1. 复制一份redis

image-20210530165515949

  1. 配置从机的redis.conf

修改replicaof ,填上主机的ip地址和端口号

image-20210531111515208

  1. 修改从机的端口号

不能和主机重复

image-20210531111629749

  1. 开启从机

image-20210531112436909

  1. 开启主机

image-20210531112519021

  1. 通过 info replication,查看主从信息

主机这边显示master

image-20210531112604089

从机这边显示 slave

image-20210531112638796

  1. 测试

主机像往常一样,可读写,从机只能读

主机存入 master faro_z:

image-20210531112806576

从机可以获取 master faro_z:

image-20210531112836505

但是如果用从机去进行写操作,就会报错:

image-20210531112908969

复制架构中出现宕机情况

**从Redis宕机:**重启就好

**主Redis宕机:**从数据库(从机)中执行SLAVEOF NO ONE命令,断开主从关系并且提升为主库继续服务[把一个从做为 主机,这个时候新主机[之前的从机]就具备写入的能力];主服务器修好后,重新启动后,执行SLAVEOF命令,将其 设置为从库[老主机设置为从机]。

**([手动执行,过程复杂,容易出错。]是否有更好的方案?)**这个更好的方式,就是哨兵模式

Redis 哨兵模式

哨兵模式的介绍

**哨兵模式:**给集群分配一个站岗的。 哨兵的作用就是对Redis系统的运行情况监控,它是一个独立进程,它的功能:

  1. 监控主数据库和从数据库是否运行正常;
  2. 主数据出现故障后自动将从数据库转化为主数据库(不用我们手动配置了)

如果主机宕,开启选举工作,选择一个从做主机。

环境准备:一主两从,启动任一从机时,启动哨兵模式

虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis服务器,你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵(sentinel)。

image-20210530133321290

配置哨兵模式

Redis 集群方案

集群介绍

image-20210531143938044

(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 会根据节点数量大致均等的将哈希槽映射到不同的节点

image-20210531144948388

心跳机制:

image-20210531145128047

(1) 集群中所有master参与投票,如果半数以上master节点与其中一个master节点通信超过(cluster-node-timeout),
认为该master节点挂掉.

Ø 如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。

(2) 什么时候整个集群不可用(cluster_state:fail)?

Ø 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。

集群搭建

  1. 创建集群目录
[root@localhost redis]# mkdir redis-cluster
  1. 在集群目录中,复制一个之前的 redis 文件目录来
[root@faroz myapps]# cp ./redis ./redis-cluster/7001 -r
  1. 如果存在持久化文件,就删除

删除 dump.rbd 文件

  1. 修改redis.conf配置文件,打开Cluster-enable yes

说明:cluster-enable 是否支持集群

image-20210531145919260

  1. 修改端口

image-20210531145944714

  1. 复制余下 7002 - 7006 的主机

image-20210531153151720

  1. 修改它们的端口号

  2. 启动7001-7006这六台机器,写一个启动脚本:自定义shel脚本

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 ..
  1. 修改脚本权限,使其可执行
[root@localhost redis-cluster]# chmod u+x startall.sh
  1. 启动所有的实例
[root@localhost redis-cluster]# ./startall.sh
  1. 创建集群(关闭防火墙)

注意:在任意一台上运行 不要在每台机器上都运行,一台就够了 redis 5.0.5中使用redis-cli --cluster替代redis-trib.rb
命令如下:

redis-cli --cluster create ip:port ip:port <这里,有几个主机,就要写几个> --cluster-replicas 1

我的修改后,应该如下:

这里的 ip ,要填写 ECS 的内网 ip

[root@localhost redis_cluster]# cd /home/admin/myapps/redis-cluster/7001/bin
[root@localhost bin]# ./redis-cli --cluster create 172.22.93.248:7001 172.22.93.248:7002 172.22.93.248:7003 172.22.93.248:7004 172.22.93.248:7005 172.22.93.248:7006 --cluster-replicas 1

确认后,我们可以看到,6个主机,被分成了3主3从:

image-20210531154741720

  1. 连接集群
./bin/redis-cli -h 127.0.0.1 -p 7001 -c

image-20210531155411704

高频面试题

缓存的概念

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

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

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

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

image-20210531121330734

缓存雪崩

概念

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

缓存没问题时的图示:

image-20210531121457855

缓存失效的图示:

image-20210531121606836

解决方案
方案1

在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个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 ,只会查询一次关系型数据库,然后将结果放入 redis缓存
        user = userMapper.getUser(id);
        redisService.setSet(key, JSONObject.toJSONString(user));
    } catch (Exception e) {
      //
    } finally { 
    	  lock.unlock(); // 释放锁 
    }
    return user;
}

注意:加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着 的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法。

方案2

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

这玩意儿就复杂了,说的高大上一点,需要用到社会工程学

缓存穿透

概念

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

解决方案
方案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;
}

缓存击穿

概念

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

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

解决方案
  1. 使用锁:单机用synchronized,lock等,分布式用分布式锁。
  2. 缓存过期时间不设置,而是设置在key对应的value里:如果检测到存的时间超过过期时间则异步更新缓存。

分布式锁

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

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

**进程锁:**也是为了控制同一操作系统中多个进程访问一个共享资源,只是因为程序的独立性,各个进程是无法控制其他进程对资源的访问的,但是可以使用本地系统的信号量(PV操作)控制(操作系统基本知识)。

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

应用场景

线程间并发问题和进程间并发问题都是可以通过分布式锁解决的,但是强烈不建议这样做!因为采用分布式锁解决这些小问题是非常消耗资源的! 分布式锁应该用来解决分布式情况下的多进程并发问题才是最合适的。

有这样一个情境,线程A和线程B都共享某个变量X。 如果是单机情况下(单JVM),线程之间共享内存,只要使用线程锁就可以解决并发问题。

如果是分布式情况下(多JVM),线程A和线程B很可能不是在同一JVM中,这样线程锁就无法起到作用了,这时候 就要用到分布式锁来解决。

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

这里主要讲如何用redis实现分布式锁。

使用redis的setNX命令实现分布式锁

实现的原理:

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

小结

缓存雪崩:

某个缓存超时,原本要从缓存中获取的值,全部走 Mysql,导致数据库资源开销加大,可能导致宕机

缓存穿透:

数据库和缓存都没有要查询的值,那么缓存中,就没有机会存放该值,所以每次去查询该值的时候,都会绕过缓存,直接去查询数据库

缓存击穿:

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FARO_Z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值