⬆︎
×

Redis原理篇:持久化、主从与集群、缓存问题、分布式锁…

本文详细介绍了Redis的核心原理与应用,内容涵盖数据类型、持久化策略、主从复制与集群、缓存相关问题、分布式锁等多个方面。首先,文章阐述了Redis的五大基本数据类型(String、Hash、List、Set、SortedSet)及其应用场景,同时引入了跳表等复杂数据结构。其次,介绍了Redis的持久化机制,包括RDB和AOF的优缺点以及如何结合使用以保证数据安全。此外,文章探讨了Redis的主从复制、哨兵模式与分片集群方案,并分析了如何应对缓存穿透、击穿和雪崩等常见问题。最后,深入讲解了Redis的分布式锁实现及其应用场景,以及如何通过事务处理、数据淘汰策略等提升系统性能。

Java后端

1 数据类型

Redis是典型的键值型数据库,不同数据类型其key结构一致,value有所差异。

1.1 Redis五大数据类型详解

常见的类型有String、Hash、List、Set、SortedSet等,而基于这5种基本数据类型,Redis又拓展了几种拓展类型,例如BitMap、 HyperLogLog、Geo等。

  1. String:Redis中最常见的数据类型,value与key一样均为Redis自定义的字符串结构,称为SDS
    • 在保存数字、小字符串时会采用INTEMBSTR编码,内存结构紧凑,只需申请一次内存分配,效率更高,更节省内存。
    • 超过44字节的大字符串则采用RAW编码,申请额外的SDS空间,需要两次内存分配,效率较低,内存占用也较高,但最大不超过512mb,因此建议单个value尽量不要超过44字节。
    • String类型常作计数器、简单数据存储等。复杂数据建议采用其它数据结构。
  2. Hash:value与Java中的HashMap类似,为一个key-value结构。若某对象需要被Redis缓存,并且将来可能会有部分修改,建议采用Hash结构来存储该对象的每一个字段和字段值,不建议作为JSON字符串存储为String类型,因为Hash结构的每一个字段都可以单独做修改,而String的JSON串必须整体覆盖。
    • 与Java中的HashMap不同的是,Redis中的Hash底层采用了渐进式rehash的算法,在做rehash时会创建一个新的HashTable,每次操作元素时移动一部分数据,当所有数据迁移完成时,再用新的HashTable代替旧的,避免了因为rehash导致的阻塞,因此性能更高。
  3. List:value类型可看做为一个双端链表,提供了一些便于从首尾操作元素的命令。
    • 为了节省内存空间,底层采用了ZipList(压缩列表)作为基础存储,当压缩列表数据达到阈值(512)则会创建新的压缩列表。每个压缩列表作为一个双端链表的一个节点,最终形成一个QuickList结构,其结构与一般的双端链表不同,可对中间不常用的ZipList节点做压缩以节省内存。
    • List结构常用于模拟队列,实现任务排队等功能。
  4. Set:value与Java中的Set类似,元素不可重复。Redis提供了求交集、并集等命令,以此实现例如好友列表、共同好友等功能。
    • 当存储元素为整数时,其底层默认采用IntSet结构,可视为一个有序数组,结构紧凑,效率较高。而若元素不是整数,或元素量超过阈值(512)则会转为Hash表结构,内存占用大幅增加。因此在使用Set结构时尽量采用数组存储,例如数值类型的id,并且元素数量尽量不超过阈值(512),避免出现BigKey。
  5. SortedSetZSet):value为一个有序的Set集合,元素唯一,并且按指定的score值排序。因此常用于排行榜功能。
    • 底层利用Hash表保证元素的唯一性,利用跳表(SkipList,见后述)来保证元素的有序性,因此数据可能会重复存储,内存占用较高,是一种典型的以空间换时间的设计。不建议在SortedSet中放入过多数据。

1.2 跳表(SkipList)

跳表(SkipList)是一种链表,但与传统的链表相比有以下几点差异:

  1. 结合了链表和二分查找的思想
  2. 元素按照升序排列存储
  3. 节点可能包含多个指针,指针跨度不同
  4. 查找时从顶层向下,不断缩小搜索范围
  5. 整个查询的复杂度O(\log n

跳表

Redis数据类型Sorted Set使用跳表作为其中一种数据结构。


2 数据持久化策略

在Redis中提供了两种数据持久化的方式:RDB、AOF

  1. RDB(Redis Database Backup file)::定期更新,定期将Redis中的数据生成的快照同步到磁盘等介质上,磁盘上保存的是Redis的内存快照。
    • 优点:数据文件的大小相比于AOF较小;使用RDB进行数据恢复速度较快。
    • 缺点:比较耗时,存在丢失数据的风险。
  2. AOF(Append Only File):将Redis所执行过的所有指令记录在文件里,下次Redis重启时只需要执行指令即可。
    • 优点:数据丢失的风险大大降低。
    • 缺点:数据文件的大小相比于RDB较大;使用AOF文件进行数据恢复时速度较慢。

实际使用时通常结合RDB+AOF进行持久化。


3 主从和集群

一般部分服务做缓存用的Redis直接做主从(1主1从)加哨兵即可。单节点不超过10G内存,若Redis内存不足则可以给不同服务分配独立的Redis主从节点。

尽量不做分片集群,原因如下:

  • 维护麻烦
  • 集群之间的心跳检测和数据通信会消耗大量的网络带宽
  • 集群插槽分配不均和key的分批容易导致数据倾斜
  • 客户端的route会有性能损耗
  • 集群模式下无法使用lua脚本、事务

其他扩展:

  • 一般企业中redis存储超过100GB是极少见的,一般只存热点数据。
  • 极端情况下,可以设置较大的内存。以阿里云为主,购买内存型服务器,目前最大为2048GB。

3.1 Redis集群方案

Redis提供的集群方案共有三种:

  1. 主从复制
    • 保证高可用性
    • 实现故障转移需要手动实现
    • 无法实现海量数据存储

主从复制

  1. 哨兵模式
    • 保证高可用性
    • 可以实现自动化的故障转移
    • 无法实现海量数据存储

哨兵模式

  1. Redis分片集群
    • 保证高可用性
    • 可以实现自动化的故障转移
    • 可以实现海量数据存储

Redis分片集群

保证Redis高并发高可用的方法:主从+哨兵;集群。

3.2 主从同步

主从第一次同步是全量同步

  1. 从节点执行replicaof命令建立连接,向主节点发送psync命令,发送自己的replid(Replication ID)和offset给主节点
  2. 主节点判断从节点的replid与自己的是否一致,若不一致说明是第一次同步,需要做全量同步,主节点返回自己的replid给从节点
  3. 主节点开始执行bgsave,生成RDB文件
  4. 主节点发送RDB文件给从节点,在发送的过程中,记录RDB期间的所有新命令至repl_baklog
  5. 从节点接收文件,清空本地数据,加载RDB文件中的数据
  6. 同步过程中,主节点接收到的新命令写入从节点的写缓冲区(repl_buffer
  7. 从节点接收到缓冲区数据后写入本地,并记录最新数据对应的offset

全量备份

后期采用增量同步

  1. 主节点会不断将自己接收到的命令记录在repl_baklog中,并修改offset
  2. 从节点向主节点发送psync命令,发送自己的replidoffset
  3. 主节点判断replidoffset与从节点是否一致,若replid一致,说明是增量同步。然后判断offset是否一致,若从节点的offset小于主节点的offset,并且在repl_baklog中能找到对应数据,则将offset之间相差的数据发送给从节点。
  4. 从节点接收到数据后写入本地,修改自己的offset与主节点一致。

增量备份

增量同步的风险:repl_baklog存在大小上限,写满后会覆盖最早的数据。若slave断开时间过久,导致尚未备份的数据被覆盖,则无法基于log做增量同步,只能再次全量同步。(repl_baklog可以在配置文件中修改存储大小)

3.3 集群中数据存取

Redis集群引入了哈希槽的概念。Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分Hash槽。

下图为存值的流程,取值的流程与之类似:

哈希槽

set {aaa}name kina:根据aaa计算Hash值

3.4 Redis集群脑裂

Redis集群可能由于网络等原因发生集群脑裂(Split-Brain):由于Redis的Master、Salve节点与Sentinel处于不同的网络分区,使得Sentinel无法根据心跳感知到Master,故通过选举的方式提升了一个Salve为Master,由此就存在两个Master(如同大脑分裂了一样),导致客户端还在Old Master处写入数据,新节点无法同步数据。当网络恢复后,Sentinel会将Old Master降为Salve,此时再从新Master同步数据,会导致大量数据丢失。

正常情况:

正常情况

发生脑裂时的情况:

脑裂情况

网络恢复后的情况:

强行改变节点

解决方案:Redis中配置以下两个参数——

  • min-replicas-to-write 1:表示最少的salve节点为1个
  • min-replicas-max-lag 5:表示数据复制和同步的延迟不能超过5秒

配置了这两个参数后,如果发生脑裂,原Master会在客户端写入操作时拒绝请求,由此可以避免大量数据丢失。


4 缓存使用场景

加入缓存后的数据查询流程:

加入缓存后的数据查询流程

通常在用户行为数据、热点文章、热点数据等场景使用缓存。

4.1 缓存穿透

缓存穿透是指在查询特定数据时,如果在存储层未能找到该数据,则不会将其写入缓存。这种情况导致每次请求都必须直接查询数据库,可能引发数据库负载过重甚至崩溃的风险。

解决方案:

  1. 对于查询返回的数据为空的情况,仍然将该空结果进行缓存,但应设置较短的过期时间。
  2. 布隆过滤器:将所有可能存在的数据哈希到一个足够大的位图中,从而能够有效拦截一定不存在的数据,避免不必要的数据库查询。

4.2 布隆过滤器

布隆过滤器(Bloom Filter)是由布隆于1970年提出的一种概率性数据结构,主要由一个较长的二进制向量(位数组)和一系列哈希函数组成。其主要功能为用于高效地判断一个元素是否存在于某个集合中。

布隆过滤器

【例】添加元素:将商品的id(id1)存储至布隆过滤器

添加元素

如上图所示,假设当前的布隆过滤器使用了三个哈希函数,对元素id1进行哈希运算后得到的结果分别为:1、4、9。那么,布隆过滤器中对应的位数组位置将被设置为1。

在判断数据是否存在时,首先使用相同的哈希函数对待检元素进行哈希运算,得到相应的哈希值。接着检查这些哈希值所对应的位数组位置是否均为1。如果有任何一个位置为0,则可以确定该元素不存在于集合中。如果所有位置均为1,则该元素可能存在于集合中,但需注意,由于哈希函数可能存在冲突,无法完全确认其存在性,如下图所示:

哈希冲突

假设在添加完元素id1id2后,布隆过滤器中的数据存储方式如上图所示。此时,如果要判断元素id3是否存在于布隆过滤器中,根据上述判断规则,可能会得出该元素存在的结论。然而,实际上id3并未被添加到布隆过滤器中,这种情况即属于误判。

误判率:布隆过滤器的误判率与位数组的大小密切相关。位数组越小,误判率越高;位数组越大,误判率越低。然而,增大位数组的同时也会导致内存消耗的增加。

删除元素:布隆过滤器不支持删除操作,因为一旦允许删除,可能会影响对元素不存在的判断结果。这是由于布隆过滤器的设计特性,删除操作可能导致原本标记为存在的元素被错误地识别为不存在。

在redis的框架redisson中提供了布隆过滤器的实现,使用方式如下,首先需在pom.xml文件中引入依赖:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>${redisson.version}</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

测试示例:

import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class Application {

    public static void main(String[] args) {
        // 连接redis
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.200.130:6379") .setPassword("leadnews");

        // 创建redisson客户端
        RedissonClient redissonClient = Redisson.create(config);

        // 创建布隆过滤器
        RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("bloom-filter");

        int size = 10000;

        //初始化数据
        // initData(bloomFilter, size);

        //测试误判率
        int count = getData(bloomFilter, size);
        System.out.println("总误判条数:" + count);
    }

    // 测试误判率
    private static int getData(RBloomFilter<String> bloomFilter, int size) {
        int count = 0;  // 记录误判的数据条数
        for (int x = size; x < size * 2 ; x++) {
            if (bloomFilter.contains("add" + x)) {
                count++ ;
            }
        }
        return count;
    }

    // 初始化数据
    // 参数:布隆过滤器存储的元素个数;误判率
    private static void initData(RBloomFilter<String> bloomFilter, int size) {
        bloomFilter.tryInit(size, 0.01);    // 布隆过滤器初始化数据
        for (int x = 0; x < size; x++) {
            bloomFilter.add("add" + x) ;
        }
        System.out.println("初始化完成...");
    }
}

Redis中使用布隆过滤器防止缓存穿透流程图如下所示:

Redis中使用布隆过滤器防止缓存穿透

4.3 缓存击穿

缓存击穿是指当某个设置了过期时间的缓存键在特定时间点过期时,恰好此时有大量并发请求针对该键发起。由于这些请求发现缓存已过期,通常会直接从后端数据库加载数据并重新设置到缓存中。在这种情况下,瞬间涌入的大量并发请求可能会导致数据库承受过大的压力,从而造成性能下降或崩溃。

解决方案:

  1. 使用互斥锁:在缓存失效时,不立即从数据库加载数据(load db),而是首先使用如Redis的SETNX命令设置一个互斥锁。如果锁设置成功,则执行数据库加载操作并更新缓存;如果锁设置失败,则重试获取(get)缓存的数据。

缓存击穿

  1. 设置逻辑过期:具体思路如下:
    1. 在设置缓存键时,除了设置实际数据外,还设置一个过期时间字段,但不对当前键设置物理过期时间。
    2. 查询时,从Redis中取出数据后,检查时间字段以判断数据是否过期。
    3. 如果数据已过期,则启动一个新的线程进行数据同步,而当前线程则正常返回旧数据,确保用户仍能获得响应,即使该数据不是最新的。

两种方案的对比:

互斥锁 逻辑过期
优点 没有额外的内存消耗
保证一致性
实现简单
线程无需等待,性能较好
缺点 线程需要等待,性能受影响
可能有死锁风险
不保证一致性
有额外内存消耗
实现复杂

4.4 缓存雪崩

缓存雪崩是指在设置缓存时,多个缓存键采用相同的过期时间,导致它们在同一时刻同时失效,从而使得所有请求都转发至后端数据库。这种情况会导致数据库瞬间承受过大的压力,可能导致性能下降或崩溃。

与缓存击穿的区别:缓存雪崩涉及多个缓存键的失效,而缓存击穿则是针对单个缓存键的失效情况。

解决方案:将缓存失效时间进行分散,可以在原有的失效时间基础上增加一个随机值,例如1到5分钟的随机延迟。通过这种方式,每个缓存的过期时间将具有更低的重复率,从而有效降低集体失效的风险。这种策略能够显著减缓数据库的压力,避免因大量缓存同时失效而导致的性能问题。

4.5 Redis双写问题

解决Redis双写问题的两种方案如下:

  1. 同步方案:在普通缓存中,通常采用更新时删除缓存、查询时延迟更新缓存的策略。这意味着在更新数据时,先删除相关缓存,然后在下一次查询时重新建立缓存。
  2. 异步方案
    1. 使用消息队列进行缓存同步:在代码中加入异步操作的逻辑。当数据库操作完成后,将需要同步的数据发送到消息队列(MQ)。MQ的消费者随后从队列中获取数据,并更新缓存。
    2. 使用阿里巴巴旗下的Canal组件实现数据同步:该方案无需更改业务代码,只需部署一个Canal服务。Canal服务伪装成MySQL的从节点,当MySQL数据更新后,Canal会读取binlog数据,并通过Canal客户端获取数据,从而更新缓存。

5 Redis分布式锁

通常在集群情况下定时任务、下单、缓存、秒杀、幂等性场景使用Redis分布式锁。

5.1 实现方式

Redis实现分布式锁主要利用Redis的setnx命令(SET if not exists):

setnx

如图所示,常见的用于完成分布式锁的命令如下:

  • 加锁(setnx key value):若keylock)不存在,设置value(加锁成功);若已存在keylock,即客户端持有锁),则设置失败(加锁失败)
  • 解锁(del key):通过删除key释放锁。释放锁之后,其他客户端可以通过setnx命令进行加锁。
public boolean tryLock(long timeoutSec) {
    // 获取线程标示
    String threadId = ID_PREFIX + Thread.currentThread().getId();
    // 获取锁
    Boolean success = stringRedisTemplate.opsForValue()
        .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
    return Boolean.TRUE.equals(success);
}

5.2 合理控制锁的有效时长

在 Redis 中实现分布式锁时,合理控制锁的有效时长是至关重要的。若有效时间设置过短,可能导致业务操作未完成时锁被自动释放,从而引发问题。

解决方案:

  1. 预估锁的有效时间:开发人员可以根据经验预估业务代码的执行时间,并将锁的有效期设置得比预估时间长,以确保不会因自动解锁而影响客户端的业务执行。
  2. 锁的续期机制:在成功加锁后,可以启动一个守护线程,默认有效期为用户设定的时间。该线程每隔一定时间(如10秒)就会将锁的有效期续期至用户设定的时间。只要持有锁的客户端未宕机,就能持续保持锁的有效性,直到业务代码执行完毕并由客户端自行解锁。如果客户端宕机,锁将在有效期结束后自动释放。

上述第二种解决方案可以利用Redis官方提供的Redisson库来实现锁的续期功能,使用步骤如下:

  1. 加入Redisson依赖(见前述布隆过滤器)
  2. 定义配置类
@Configuration
public class RedisConfig {

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.200.130:6379") .setPassword("leadnews");
        return Redisson.create(config);
    }
}
  1. 业务代码加入分布式锁
public void redisLock() throws InterruptedException {
    RLock lock = redissonClient.getLock("anyLock");
    try {
        // 第一个参数(30)表示尝试获取分布式锁,并且最大的等待获取锁的时间为30s
        // 第二个参数(10)表示上锁之后,10s内操作完毕将自动释放锁
        boolean isLock = lock.tryLock(30, 30, TimeUnit.SECONDS);
        String num = redisTemplate.opsForValue().get("num");
        Integer intNum = Integer.parseInt(num);
        if (intNum == null || intNum <= 0) {
            throw new RuntimeException("商品已抢完");
        }
        if (isLock) {
            intNum = intNum - 1;
            redisTemplate.opsForValue().set("num", intNum.toString());
            System.out.println(redisTemplate.opsForValue().get("num"));
        }
    } finally {
        // 释放锁
        lock.unlock();
    }
}
  1. Jmeter测试

Jmeter测试

5.3 Redisson分布式锁原理

如下图所示【重要】:

Redisson分布式锁原理

Redisson实现的分布式锁是可重入的:

public void add1() {
    boolean isLock = lock.tryLock(30, 10, TimeUnit.SECONDS);
    // 执行业务
    add2();
    // 释放锁
    lock.unlock();
}

public void add2() {
    boolean isLock = lock.tryLock(30, 10, TimeUnit.SECONDS);
    // 执行业务
    // 释放锁
    lock.unlock();
}

6 其他Redis高级原理

6.1 数据过期策略

Redis数据删除策略(数据过期策略)是指在Redis中为数据设置有效时间,当数据的有效时间到期后,系统会自动将其从内存中删除。在删除过程中,需要遵循特定的规则,这些规则被称为数据的删除策略。通过合理配置这些策略,Redis能够有效管理内存,确保系统性能和资源的高效利用。

在Redis中,数据的删除策略包括以下几种:

  1. 惰性删除:在为某个键设置过期时间后,系统不会主动删除该键。当需要访问该键时,会检查其是否已过期。如果已过期,则将其删除;如果未过期,则返回该键的值。
    • 优点:对CPU友好。只有在实际使用该键时才会进行过期检查,从而避免对未使用键的重复检查,节省了CPU资源。
    • 缺点:对内存不友好。如果一个键已经过期但从未被访问,该键将继续占用内存。如果数据库中存在大量未使用的过期键,这些键将始终保留在内存中,导致内存无法释放。

set name kina 10get name,发现name过期了,直接删除key

  1. 定期删除:定期删除策略是指系统每隔一段时间对一些键进行检查,删除过期的键。具体方法是从一定数量的数据库中随机选取一定数量的键进行检查,并删除其中的过期键。
    • 优点:通过限制删除操作的执行时长和频率,可以减少对CPU的影响。此外,定期删除能够有效释放过期键占用的内存资源。
    • 缺点:确定删除操作的执行时长和频率较为困难。如果执行过于频繁,定期删除策略可能与定时删除策略相似,从而对CPU造成负担;如果执行过于稀疏,则会导致过期键占用内存,类似于惰性删除的效果。此外,若在获取某个键时,该键的过期时间已到但尚未执行定期删除,系统仍可能返回该键的值,这将导致业务逻辑出现不可接受的错误。
    • 定期清理的两种模式:
      1. SLOW模式:该模式为定时任务,默认执行频率为10Hz,每次执行时间不超过25ms。可通过修改配置文件 redis.conf 中的 hz 选项来调整执行频率。
      2. FAST模式:该模式执行频率不固定,每次事件循环都会尝试执行,但两次执行之间的间隔不得低于2ms,每次执行时间不超过1ms。

Redis的过期删除策略结合了惰性删除与定期删除两种策略,以优化内存管理和CPU使用效率。

6.2 数据淘汰策略

当Redis中的内存不足以存储新的键时,系统会根据特定规则删除现有数据,以腾出空间。这种数据删除规则被称为内存淘汰策略

常见的数据淘汰策略:

  1. noeviction:不删除任何数据,内存不足直接报错(默认策略)
  2. volatile-Iru:挑选最近最久使用的数据淘汰(【例】key1在3s之前访问,key2在9s之前访问,删除key2)
  3. volatile-lfu:挑选最近最少使用数据淘汰(【哦】key1最近5s访问了4次,key2最近5s访问了9次, 删除key1)
  4. volatile-ttl:挑选将要过期的数据淘汰
  5. volatile-random:任意选择数据淘汰
  6. allkeys-lru:挑选最近最少使用的数据淘汰
  7. allkeys-lfu:挑选最近使用次数最少的数据淘汰
  8. allkeys-random:任意选择数据淘汰,相当于随机
  • LRU(Least Recently Used):最少最近使用。用当前时间减去最后一次访问时间,值越大则淘汰优先级越高。
  • LFU(Least Frequently Used):最少频率使用。统计每个key的访问频率,值越小淘汰优先级越高。
  • volatile:设置了带过期时间的key
  • allkeys:所有key

缓存淘汰策略常见配置项:

  • maxmemory-policy noeviction:配置淘汰策略
  • maxmemory ?mb:最大可使用内存,即占用物理内存的比例,默认值为e,表示不限制。生产环境中根据需求设定,通常设置在50%以上。
  • maxmemory-samples count:设置redis需要检查key的个数

使用建议:

  1. 优先使用allkeys-lru策略:该策略充分利用 LRU 算法的优势,将最近最常访问的数据保留在缓存中。适用于业务中存在明显冷热数据区分的场景。
  2. 对于访问频率差别不大的业务,如果数据访问频率相对均匀,没有明显的冷热数据区分,建议使用allkeys-random策略,以随机方式选择淘汰数据。
  3. 针对有置顶需求的业务,可以采用volatile-lru策略,同时确保置顶数据不设置过期时间,这样这些数据将不会被删除,系统将优先淘汰其他设置了过期时间的数据。
  4. 对于短时高频访问的数据,建议使用allkeys-lfu或volatile-lfu策略,以便有效管理和淘汰不常用的数据。
  • 数据库有1000万数据,Redis只能缓存20w数据,如何保证Redis中的数据都是热点数据?
    • 使用allkeys-lru(挑选最近最少使用的数据淘汰)淘汰策略,留下来的都是经常访问的热点数据。
  • Redis的内存用完了会发生什么?
    • 主要看数据淘汰策略是什么?如果是默认配置,则直接报错。

6.3 Redis事务

Redis的事务与传统事务有所不同。传统的ACID事务是一个原子操作,意味着事务中的所有命令要么全部执行,要么全部不执行。

在Redis中,事务的本质是一组命令的集合。Redis事务支持一次执行多个命令,并且所有命令都会被序列化。在事务执行过程中,命令按照顺序串行化执行,其他客户端的命令请求不会插入到当前事务的执行序列中。

Redis事务的特点是一次性顺序性排他性地执行队列中的一系列命令。在Redis中,单条命令是原子性执行的,但事务本身并不保证原子性,并且不支持回滚。

事务相关的命令:

  1. MULTI:组装事务
  2. EXEC:执行事务
  3. DISCARD:取消事务
  4. WATCH:监视key,一旦这些key在事务执行之前被改变,则取消事务的执行
  5. UNWATCH:取消WATCH命令对所有key的监视

Redis事务

6.4 其他

Redis是单线程的,但依旧很快,原因如下:

  1. 完全基于内存;用C语言编写。
  2. 单线程拉烟避免不必要的上下文切换可竞争条件。
  3. 数据简单,数据操作也相对简单
  4. 使用多路I/O复用模型,非阻塞I/O
    • bgsave在后台执行RDB的保存,不影响主线程的正常使用,不会产生阻塞。
    • bgrewriteaof在后台执行AOF文件的保存,不影响主线程的正常使用,不会产生阻塞。

发表评论