Redis 工作总结

本文介绍了Redis的基本概念,包括其作为存储中间件的用途、主要数据类型(如字符串、列表、哈希等)、持久化策略(RDB和AOF)、集群模式(Codis、Sentinel、Redisson和Twemproxy)、高可用性、内存管理以及分布式锁实现。
摘要由CSDN通过智能技术生成

1.Redis是什么

Redis是互联网技术领域使用最为广泛的存储中间件,它是Remote Dictionary Service的首字母缩写,也就是远程字典服务

2.Redis的用途?

2.1 计数器

2.2 缓存

2.3 分布式锁

2.4 消息中间件

3.Redis的数据类型

3.1 string(字符串)

Redis的字符串是动态字符串,是可以修改的字符串,采用预分配冗余空间的方式 来减少内存的频繁分配。当字符串长度小于1M时,扩容都是加倍扩容,当超过1M时,每次扩容时增加1M的空间【字符串最大长度为 512M】 。

基本命令: set 、 get 、 exists 、 del
批量命令: mset 、 mget
其他命令: expire 、 setex 、 setnx 、 incr 、 incrby        【自增的最大值是signed long

3.2 list(列表)

Redis的列表相当于Java语言里面的 LinkedList ,底层是链表实现。插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)。当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。

常用命令: rpush 、 lpush 、 rpop 、 lpop
查询命令: llen 、 lrange 、 lindex 、 ltirm

3.3 hash(哈希)

Redis 的字典相当于 Java 语言里面的 HashMap ,它是无序字典。内部实现结构上同 Java 的 HashMap 也是一致的,同样的数组 + 链表二维结构。当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收

常用命令: hset 、 hget 、 hgetall 、 hlen 、 hmet
其他命令: hincr 、 hincrbu

3.4 set(集合)

Redis 的集合相当于 Java 语言里面的 HashSet ,它内部的键值对是无序的唯一的。当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。

常用命令: sadd 、 smembers 、 sismember 、 scard 、 spop

3.5 zset(有序集合)

Redis 的Zset 类似Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set ,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score ,代表这个 value 的排序权 重。它的内部实现用的是一种叫着「 跳跃列表 」的数据结构。zset 中最后一个 value 被移除后,数据结构自动删除,内存被回收。

常用命令: sadd 、 smembers 、 sismember 、 scard 、 spop

3.6 公共操作

3.6.1 删除操作

        del key [key …]

3.6.2 有效期

        expire key second [单位]        设置key的有效期

        ttl key        查看key的有效期        【-1 永久有效 -2 已失效  正整数 有效期】

        已设置有效期的字符串重新set key value后有效期失效。

4.Redis数据持久方案

4.1 RDB(Redis DataBase)

是一种快照式的数据存储,它会周期性的保存当前时间点Redis所有的数据到磁盘中。

当Redis需要进行快照操作时,它会fork出一个子进程,负责将快照写入磁盘,而父进程则继续处理请求。由于Redis使用了写时复制(COW)的技术,所以子进程只需要复制到父进程中发生过改变的数据页,而不是复制整个Redis进程的内存空间。因此,这个过程不会对父进程的性能造成较大的影响。

Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开6379.conf文件之后,我们搜索save,可以看到下面的配置信息:

save 900 1       #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。

save 300 10     #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。

save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。

优点:RDB持久化文件,速度比较快,而且存储的是一个二进制文件,传输起来很方便。
缺点:RDB无法保证数据的绝对安全,有时候就是1s也会有很大的数据丢失。

4.2 AOF(Append Only File)

是一种追加式的存储方式,会实时的记录Redis的写操作磁盘中。

在Redis的配置文件中存在三种同步方式,它们分别是:

vim /etc/redis/6379.conf
● appendfsync always:
命令写入aof_ buf后立即调用系统fsync操作同步到AOF文件,fsync完成后线程返回。这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。
 
● appendfsync no:
命令写入aof_ buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。
 
● appendfsync everysec:
命令写入aof_ buf后调用系统write操作,write完成后线程返回; fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是我们推荐的配置。

AOF执行流程:

命令追加(append): 将Redis的写命令追加到缓冲区aof_buf;【避免硬盘IO成为Redis的瓶颈】
文件(write)和(sync):根据不同的同步策略将aof_buf中的内容同步到硬盘;【丢失数据
文件重写(rewrite): 定期重写AOF文件,达到压缩的目的。

AOF文件重写:

子进程执行【避免Redis无法正常使用】新增一个AOF文件,子进程开启时增加了一个AOF重写缓存,Redis服务器主进程在执行完写命令之后,会同时将这个写命令追加到AOF缓冲区和AOF重写缓冲区 。AOF重写文件写完之后会将AOF重写缓存中的数据写入AOF重写文件【主线程阻塞】,写入完成后将AOF重写文件重命名为覆盖原有文件主线程阻塞

AOF文件重写触发条件:

1.没有BGSAVE命令(RDB持久化)/AOF持久化在执行;
2.没有BGREWRITEAOF在进行;
3.当前AOF文件大小要大于server.aof_rewrite_min_size(默认为1MB),或者在redis.conf配置了auto-aof-rewrite-min-size大小;
4.当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比(在配置文件设置了auto-aof-rewrite-percentage参数,不设置默认为100%)
5.如果前面三个条件都满足,并且当前AOF文件大小比最后一次AOF重写时的大小要大于指定的百分比,那么触发自动AOF重写。

优点:AOF相对RDB更加安全,一般不会有数据的丢失或者很少。
缺点:AOF相对RDB持久化的速度更慢,存储的是一个文本文件,到了后期文件会比较大,传输困难。

官方推荐同时开启AOF和RDB。

5.Redis集群模式

5.1 Codis

Codis是基于Redis Cluster的代理工具,可以通过代理实现数据的分片和负载均衡。Codis具有配置简单、扩展性好等特点。

5.2 Redis Sentinel

Redis Sentinel是Redis官方提供的高可用方案,可以通过自动故障检测、自动主从切换等功能,实现Redis的高可用。

5.3 Redisson

Redisson是一个开源的Redis客户端,支持分布式锁、分布式集合、分布式对象等功能,可以将多个Redis实例组成一个集群来提供高并发服务。

5.4 Twemproxy

Twemproxy是一个代理工具,支持多个Redis实例之间的分片和负载均衡,可以通过配置实现数据的分片和负载均衡

6.RedisAP模式

6.1 高可用性

Redis在分布式模式下采用AP模型,可以在节点故障的情况下,保证整个系统的高可用性,不会影响用户使用和数据的完整性。Redis采用主从复制和哨兵模式来保证系统在节点故障时的高可用性。

6.2 数据一致性

Redis为了保证数据一致性,对于主从复制中的主节点和从节点进行数据同步,采用异步复制方式,保证了高性能。Redis同时采用哨兵模式,负责监控所有的Redis节点,一旦某个节点故障,哨兵会自动发现新的主节点,保证数据的一致性和可用性。

6.3 高扩展性

Redis采用AP模型,可以实现分布式的节点扩展,保证系统的高扩展性。Redis的节点之间采用无中心化的结构,各个节点之间相互独立,通过分片的方式来进行数据存储与同步,简单易上手,非常容易实现扩展。

7.过期删除策略

Redis采用 惰性删除 + 定期删除 策略。

7.1惰性删除    

不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key。
优点:只有访问时才会使用系统资源(对 CPU 时间最友好)
缺点:过期 key 一直没有被访问,它所占用的内存就不会释放(对内存不友好)

7.2定期删除    

每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期 key    
优点:通过限制删除操作执行的时长和频率,来减少删除操作对 CPU 的影响,同时也能删除一部分过期的数据减少了过期键对空间的无效占用
缺点:系统资源占用没有惰性删除少,难以确定删除操作执行的时长和频率

Redis 中默认每秒进行 10 此过期检测(可在 redis.conf 修改 hz 配置),每次随机抽取的数值是 20(写死的)。对于某轮的抽取检测,如果已过期 key 的数量超过 5 个(占比大于 25% ),删除并继续重复抽取,否者删除后停止本轮抽取。

8.内存淘汰策略

8.1 拒绝淘汰    

noeviction 拒绝写入操作。【3.0+默认策略,保证业务数据的安全性】 


8.2 允许淘汰

8.2.1全量数据范围淘汰

allkeys-random:随机淘汰任意键值。【数据集较大,保持数据的随机性要求】
allkeys-lru:淘汰整个键值中最久未使用的键值。【数据集小,侧重当前热度。】
allkeys-lfu:淘汰整个键值中最少使用的键值。【数据集小,侧重整体热度。4.0+】

8.2.2设置过期时间数据淘汰

volatile-random:随机淘汰设置了过期时间的任意键值。【数据集较大,保持数据的随机性要求】
volatile-ttl:优先淘汰更早过期的键值。【数据集小,侧重数据的及时更新】
volatile-lru:淘汰所有设置了过期时间的键值中,最久未使用的键值。【数据集小,侧重当前热度。3.0-默认策略】
volatile-lfu:淘汰所有设置了过期时间的键值中,最少使用的键值。【数据集小,侧重整体热度。4.0+】

9.分布式锁

9.1 五大特性

互斥性:唯一持有者。
锁超时释放:锁有超时时间,可释放,避免死锁。
可重入性:一个线程如果获取了锁之后,可以再次对其请求加锁。
高性能和高可用:保证锁的有效性,可用性。
安全性:锁只能被持有的客户端删除,不能被其他客户端删除

9.2 实现方案

9.2.1 SETNX+EXPIRE+DEL

//加锁
if (jedis.setnx(key, value) == 1) {
    //设置过期时间
    jedis.expire(key, 100);
    try {
        // 业务处理
        //todo something  
    } catch (Exception e) {
        // 异常处理
        // todo something
    } finally {
        //释放锁
        jedis.del(key);
    }
}

存在问题1:加锁与设置超时时间非原子操作,存在加锁完成超时设置失败的常驻问题。

解决方案1:

1.原子命令    jedis.set(key_resource_id, lock_value, "NX", "EX", 100s) == 1。

2.LUA脚本    String lua_scripts = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";

存在问题2:无业务超时处理

解决方案2:守护进程延期。

      

存在问题3:存在误删除其他锁情况

解决方案3:解决方案,加锁时设唯一value,解锁时校验唯一value。

LUA脚本    String lua_scripts = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

9.2.2 SETNX+[timeOut]

// 系统时间+设置的过期时间
String expireTimePoint = (System.currentTimeMillis() + expireTime) + "";

// 加锁成功
if (jedis.setnx(key, expireTimePoint) == 1) {
    return true;
}
// 加锁失败,获取锁的过期时间
String currentValue = jedis.get(key);

// 判断当前锁是否过期
if (currentValue != null && Long.parseLong(currentValue) < System.currentTimeMillis()) {

    // 锁已过期,获取上一个锁的过期时间,并设置自己的过期时间
    String oldValue = jedis.getSet(key, expireTimePoint);

    // 判断是否是自己加锁成功,如果当前线程获取到的上一个锁时间等于之前获取的的时间则认为加锁成功
    if (oldValue != null && oldValue.equals(currentValue)) {
        return true;
    }
}
// 加锁失败
return false;

存在问题
1.打破唯一持有者限定
2.各个客户端时间同步问题
3.所时间覆盖问题

9.2.3 Redisson                   

适用于Redis单体 待完善

9.2.4 Redlock+Redisson    

适用于Redis集群 待完善

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值