大数据系列——Redis理论

概述

Remote Dictionary Server(Redis) 是一个由 Salvatore Sanfilippo写的 key-value存储系统,是跨平台的非关系型数据库,也属于一种nosql数据库,通常被称为数据结构服务器。

Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。

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

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

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

Redis支持数据的备份,即master-slave模式的数据备份。

性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。

原子性 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。

丰富的特性 – Redis还支持 publish/subscribe, 通知, 设置key有效期等等特性。

下面从如下几个方面介绍下其相关理论:

架构

核心知识点

部署方式

优缺点分析

常见应用场景

调优经验

API应用

架构

核心知识点:

1、数据类型

1)string(字符串)

string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。

string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。

string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。

2)list(双向列表)

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

3)set(集合)

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

集合对象的编码可以是 intset 或者 hashtable。

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

4)hash(哈希)

Redis hash 是一个键值(key=>value)对集合。

Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。

Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。

5)zset(sorted set:有序集合)

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

6)HyperLogLog

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

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

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

7)GEO

Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。

8)Stream

Redis Stream 是 Redis 5.0 版本新增加的数据结构。

Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,

如果出现网络断开、Redis 宕机等,消息就会被丢弃。

简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。

而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。

9)、Bitmap

Bitmap在Redis中不是一种实际的数据类型,而是一种将String作为Bitmap使用的方法。可以理解为将String转换为bit数组。使用Bitmap来存储true/false类型的简单数据极为节省空间。

2、持久化方式

redis将内存中的数据异步写入硬盘中有三种方式:RDB(默认)、AOF、混合(RDB + AOF增量)

1)RDB

通过bgsave命令触发,然后父进程执行fork操作创建子进程,子进程创建RDB文件,根据父进程内存生成临时快照文件,

完成后对原有文件进行原子替换(定时一次性将所有数据进行快照生成一份副本存储在硬盘中)。

优点:是一个紧凑压缩的二进制文件,Redis加载RDB恢复数据远远快于AOF的方式,异步执行,非阻塞redis提供服务

缺点:由于每次生成RDB开销较大,非实时持久化,会阻塞redis提供服务

2)、AOF

开启后,Redis每执行一个修改数据的命令,都会把这个命令添加到AOF文件中。

优点:实时持久化。

缺点:同步执行,会阻塞redis提供服务,  当AOF文件体积逐渐变大,需要定期执行重写操作来降低文件体积,加载慢。

3)、混合持久化:

AOF在重写时,会将 rdb 快照 和 增量的 AOF日志一起写入新的aof文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,原子的覆盖原有的AOF文件,完成新旧两个AOF文件的替换;

aof 根据配置规则(aof-use-rdb-preamble yes)在后台自动重写,也可以人为执行命令bgrewrite aof重写AOF。 于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

3、内部执行方式(这是Redis快速的原因所在)

1)、数据存于内存

Redis的数据都在内存中,所以其处理速度很快,所有需要处理的文件都放在文件描述符的集合里rset(fds)。

2)、用了多路复用I/O

Redis内部采用了I/O多路复用技术,该技术依赖于底层的操作系统,一般有三种方式select、poll、epoll。前面两种本质上一致,一般操作系统都提供,epoll是Linux独有的。

  1. select

1)、select模型每次都直接将rset(也就是fds)全部拷贝到内核态,因为内核态速度比用户空间态快很多。

2)、如果没数据的话,select函数会阻塞,如果有数据的话会执行两步

第一步:将有数据的那个fd置位(也就是标记一下,代表这个fd有数据)

第二步:select函数不在阻塞,将继续往下执行。也就是整体遍历fds,找到有数据的那个fd读取数据做处理。他的fd不能重用,每一次都需要重新创建新的fds且将用户空间态的fds拷贝到内核态

3)、缺点

fds最大支持1024个(可以更改,但是意义不大)

fd不可重用,每次内核态都给置位了,导致为了标记fd,必须创建一个新的rset从而导致fds在用户态内存态间多次拷贝(也就是fds)

用户控件态拷贝rset到内核态也需要时间,虽然内核态执行比用户态快,但是copy也需要开销

O(n)再次遍历问题。因为rset里的fd被置位后,select函数并不知道哪个被置位了,需要从头遍历到尾,逐个对比。

Bpoll

poll的结构体是为了fd重复利用,不需要每次都拷贝到内核态用的。

1)、解决了select哪些问题

采取的链表存储,而不是bitmap,解决了1024长度限制问题

采取结构体每次置位结构体内的revents字段,而不破坏fd本身,所以可重用,不需要每次都创建新的fd。

2)、缺点

用户控件态拷贝rset到内核态也需要时间,虽然内核态执行比用户态快,但是copy也需要开销

O(n)再次遍历问题。因为rset里的fd被置位后,select函数并不知道哪个被置位了,需要从头遍历到尾,逐个对比。

Cepoll

epoll将fd放到了红黑树里,且不需要拷贝到内核态,因为他采取了“共享内存”的概念。(其实还是复制,只是复制采取了其他技术可以使开销极其的小)

epoll的置位是重排,比如五个fd, 1 2 3 4 5,1 3 5这三个fd有数据了,那么他会重排序,排成如下1 3 5 2 4。(也有的说是单独放到新的数组里)

每一次置位nfds的值都+1。且会回调epoll_wait

所以epoll_wait执行完会返回有几个fd有数据,那么下面的for直接遍历nfds次即可。解决了前面的两种O(n)。变成了O1

比如三个redis-cli,假设2个redis-cli写入命令,

select:那么select模型是轮询这三个redis-cli的fd,看哪个fd有消息,有的话读取处理消息。当他下次再写命令的时候还需要重新创建fd,然后复制到内核态然后再遍历全部。

poll:那么poll模型是轮询这三个redis-cli的fd,看哪个fd有消息,有的话读取处理消息。下次再写入的时候还是遍历全局fd,看哪个fd有消息进行处理。省去了每次都创建新的fd且复制的过程。

epoll:epoll就不轮询了,有消息进来后你通知我,我去处理你的消息,那些没消息的fd我不管。而且复制到内核态的过程我采取牛逼的技术让开销达到最小的极致。

多路复用I/O技术总结

3)、单线程

Redis是单线程提供应用的。

简单来说,就是我们的redis-client在操作的时候,会产生具有不同事件类型的socket。在服务端,有一段I/0多路复用程序,将其置入队列之中。然后,IO事件分派器,依次去队列中取,

转发到不同的事件处理器中。如下图:

4、过期策略

过期策略一般有三种方式:key过期清除策略、惰性策略、定期过期策略。

key过期清除:依据写入数据的过期时间来处理。

惰性过期(类比懒加载,这是懒过期):只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

redis采用的是定期删除+惰性删除策略。

为什么不用定时删除策略:

定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.

定期删除+惰性删除工作流程:

定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。

所以需要惰性删除策略。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

采用定期删除+惰性删除不能确保完全的过期该过期的数据:

如果定期删除没删除key,然后你也没及时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高,所以还需要配合内存淘汰机制。

5、内存淘汰机制

在redis.conf中有一行配置,专门配置内存淘汰机制。

# maxmemory-policy allkeys-lru

下面是一些备选机制:

1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。

2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用。

3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。应该也没人用吧,你不删最少使用Key,去随机删。

4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐

5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。依然不推荐

6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐

ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。

6、事务

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

批量操作在发送 EXEC 命令前被放入队列缓存。

收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。

在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

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

开始事务。

命令入队。

执行事务。

PS:

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。

事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

7、发布订阅

发布订阅(pub/sub)是一种消息通信模式,主要是解除消息发布者和消息订阅者之间通信的耦合。

Redis作为一个pub/sub的服务器,在订阅者和发布者之间起到了一个消息路由的功能。订阅者可以通过subscribe和psubscribe命令向redis 服务器订阅自己感兴趣的消息类型,

redis将信息类型称为通道(channel)。当发布者通过publish命令向redis server发送特定类型的信息时,订阅该信息类型的全部client都会收到此消息。

8、集群中节点间内部通信机制

1)、基础通信原理

a、redis cluster节点间采取gossip协议进行通信,没有采用集中式的存储在某个节点上

b、10000端口

      每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口

      每隔节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping之后返回pong

c、交换的信息

     故障信息、节点的增加和移除、hash slot信息,等等

2)、gossip协议介绍

     gossip 过程是由种子节点发起,当一个种子节点有状态需要更新到网络中的其他节点时,它会随机的选择周围几个节点散播消息,收到消息的节点也会重复该过程,

      直至最终网络中所有的节点都收到了消息。这个过程可能需要一定的时间,由于不能保证某个时刻所有节点都收到消息,但是理论上最终所有节点都会收到消息,

      因此它是一个最终一致性协议。

gossip协议包含多种消息,包括ping,pong,meet,fail,等等

meet: 某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信

redis-trib.rb add-node 其实内部就是发送了一个gossip meet消息,给新加入的节点,通知那个节点去加入我们的集群

ping: 每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据

         每个节点每秒都会频繁发送ping给其他的集群,通过ping,频繁的互相之间交换数据,互相进行元数据的更新

pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新

fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了

3)、ping消息深入

ping很频繁,而且要携带一些元数据,所以可能会加重网络负担

每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点

当然如果发现某个节点通信延时达到了cluster_node_timeout / 2,那么立即发送ping,避免数据交换延时过长,落后的时间太长了

9、数据备份与恢复

Redis创建当前数据库的备份,有种方式:save、bgsave。

save命令:同步执行,会阻塞Redis服务

bgsave命令:异步执行,不会阻塞Redis服务

这两种方式都会在Redis安装目录中创建dump.rdb文件,如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。

10、管道技术和分区

管道技术:

Redis 管道技术可以在服务端未响应时,客户端可以继续异步向服务端发送请求,并最终一次性读取所有服务端的响应。

分区:

分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。

有两种分区类型:范围分区、哈希分区。

11、重要程序

redis-server:Redis服务器程序

redis-cli:Redis客户端程序,它是一个命令行操作工具。也可以使用telnet根据其纯文本协议操作。

redis-benchmark:Redis性能测试工具,测试Redis在你的系统及配置下的读写性能。

部署方式:

1、单机模式

     由一台计算来提供Redis服务, 容量和性能受限于机器的配置且不具备高可用。

2、主从复制(本质和单机一样,只是多了查询负载均衡)

      通过一台主服务器Master和多台备份服务器组成的集群提供服务,支持自动备份、负载均衡,主服务器挂掉,需要人工进行切换(不支持高可用)。

      Redis为了解决单点数据库问题,会把数据复制多个副本部署到其他节点上,通过复制,对数据进行冗余备份,从而保证数据高度可靠性。

复制(Replication)的原理:

①从数据库向主数据库发送sync(数据同步)命令。

②主数据库接收同步命令后,会保存快照,创建一个RDB文件。

③当主数据库执行完保持快照后,会向从数据库发送RDB文件,而从数据库会接收并载入该文件。

④主数据库将缓冲区的所有写命令发给从服务器执行。

⑤以上处理完之后,之后主数据库每执行一个写命令,都会将被执行的写命令发送给从数据库。

注意:在Redis2.8之后,主从断开重连后会根据断开之前最新的命令偏移量进行增量复制

3、哨兵模式(主从复制+哨兵)(本质和单机一样,保证了高可用)

     通过一台主服务器Master和多台备份服务器组成的集群提供服务,保证高可用。

    

     哨兵是Redis集群架构中非常重要的一个组件,哨兵的出现主要是解决了主从复制出现故障时需要人为干预的问题。

哨兵模式架构图

ARedis哨兵主要功能

1)集群监控:负责监控Redis master和slave进程是否正常工作

2)消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员

3)故障转移:如果master node挂掉了,会自动转移到slave node上

4)配置中心:如果故障转移发生了,通知client客户端新的master地址

BRedis哨兵的高可用原理:

当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。

哨兵机制建立了多个哨兵节点(进程),共同监控数据节点的运行状况。

同时哨兵节点之间也互相通信,交换对主从节点的监控状况。

每隔1秒每个哨兵会向整个集群:Master主服务器+Slave从服务器+其他Sentinel(哨兵)进程,发送一次ping命令做一次心跳检测。

哨兵用来判断节点是否正常的重要依据,涉及两个新的概念:主观下线和客观下线。

主观下线:一个哨兵节点判定主节点down掉是主观下线。

客观下线:只有半数哨兵节点都主观判定主节点down掉,此时多个哨兵节点交换主观判定结果,才会判定主节点客观下线。

基本上哪个哨兵节点最先判断出这个主节点客观下线,就会在各个哨兵节点中发起投票机制Raft算法(选举算法),最终被投为领导者的哨兵节点完成主从自动化切换的过程。

C、哨兵选举

一般情况下当哨兵发现主节点sdown之后 该哨兵节点会成为领导者负责处理主从节点的切换工作:

哨兵A发现Redis主节点失联;

哨兵A报出sdown,并通知其他哨兵,发送指令sentinel is-master-down-by-address-port给其余哨兵节点;

其余哨兵接收到哨兵A的指令后尝试连接Redis主节点,发现主节点确实失联;

哨兵返回信息给哨兵A,当超过半数的哨兵认为主节点下线后,状态会变成odown;

最先发现主节点下线的哨兵A会成为哨兵领导者负责这次的主从节点的切换工作;

哨兵的选举机制是以各哨兵节点接收到发送sentinel is-master-down-by-address-port指令的哨兵id 投票,票数最高的哨兵id会成为本次故障转移工作的哨兵Leader;

D、哨兵故障转移

当哨兵发现主节点下线之后经过上面的哨兵选举机制,选举出本次故障转移工作的哨兵节点完成本次主从节点切换的工作:

哨兵Leader 根据一定规则从各个从节点中选择出一个节点升级为主节点;

其余从节点修改对应的主节点为新的主节点;

当原主节点恢复启动的时候,变为新的主节点的从节点

哨兵Leader选择新的主节点遵循下面几个规则:

健康度:从节点响应时间快;

完整性:从节点消费主节点的offset偏移量尽可能的高;

稳定性:若仍有多个从节点,则根据从节点的创建时间选择最有资历的节点升级为主节点;

在哨兵模式下主从节点总是会变更,因此在应用中访问哨兵模式下的Redis时可以使用对应的哨兵接口连接:

例如 java:JedisSentinelPoolPython:SentienlConnectionPool

4、集群模式

 redis集群在3.0以后提供了分布式存储方案,保证高可用,提高并发量。集群由多个节点(Node)组成,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,实现高扩展性。

集群中的节点分为主节点和从节点,只有主节点负责读写请求和集群信息的维护,从节点只进行主节点数据和状态信息的复制。为了适应选举算法要求,一般要求集群的主节点和从节点数量都采用奇数,最少需要3个节点。

可以直接使用 redis-cli --cluster 来管理集群,包括创建集群、伸缩集群节点、槽迁移、完整性检查、分区平衡等。

集群模式架构图

为了实现分布式集群,引入了槽的概念来处理数据分区。

数据分区规则一般考量2个重要因素:1、是否均匀, 2、伸缩节点对数据分布的影响。

一般有下面几种方法来实现分区算法:

1)、哈希取余

根据key计算hash值,然后对节点数量取余。该方法初始化时能够做到均匀分布,但后期伸缩时会引发大量数据迁移。

2)、一致性哈希

将hash值区间(0~2 32-1)抽象为一个顺时针环形,节点均匀分布在环形上,根据key计算hash值,然后在环形上顺时针查找节点,找到第一个就将数据落到该节点。

    相比哈希取余来说,该方法减少数据迁移,因为其将影响访问控制到相邻节点,但会造成数据不均匀。

3)、带虚拟槽的一致性哈希

Redis就是采用这种方式来处理数据分布的。一共设定16384个槽slot,采用hash算法将这些槽分配到各个节点上,hash_slot = crc16(key) mod 16384。

   该方法克服了上面两种的缺点,能够很好的解决均匀分布、伸缩节点对数据分布影响很小。

  Redis集群内节点通过ping/pong消息实现节点通信,消息不但可以传播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。因此故障发现也是通过消息传播机制实现的,主要环节包括:主观下线(pfail)和客观下线(fail)

主客观下线:集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong消息作为响应。如果通信一直失败,则发送节点会把接收节点标记为主观下线(pfail)状态。

  

客观下线:超过半数,对该主节点做客观下线

主节点选举出某一主节点作为领导者,来进行故障转移

    其中虚拟槽的分配算法没有采用最终一致性的hash算法原因如下:

      1)、发生缩容时,需要知道被影响的那部分数据,要进行手动迁移

      2)、因为其本质是一个顺时针环,所有会发生热点数据都集中在某一个Master上会出现性能瓶颈

集群限制

由于Redis集群中数据分布在不同的节点上,因此有些功能会受限:

db库:单机的Redis默认有16个db数据库,但在集群模式下只有一个db0;

复制结构:上面的复制结构有树状结构,但在集群模式下只允许单层复制结构;

事务/lua脚本:仅允许操作的key在同一个节点上才可以在集群下使用事务或lua脚本;(使用Hash Tag可以解决)

key的批量操作:如mget,mset操作,只有当操作的key都在同一个节点上才可以执行;(使用Hash Tag可以解决)

keys/flushall只会在该节点之上进行操作,不会对集群的其他节点进行操作; 

Hash Tag:

上面介绍集群限制的时候,由于key被分布在不同的节点之上,因此无法跨节点做事务或lua脚本操作,但我们可以使用hash tag方式解决。

hash tag:当key包含{}的时候,不会对整个key做hash,只会对{}包含的部分做hash然后分配槽slot;因此我们可以让不同的key在同一个槽内,这样就可以解决key的批量操作和事务及lua脚本的限制了;

但由于hash tag会将不同的key分配在相同的slot中,如果使用不当,会造成数据分布不均的情况,需要注意。

优缺点分析

缺点:

1、容量及处理能力有限,受限于单台机器的配置。

     为了应对容量的问题,一般做如下处理:

     a、采用分布式集群处理(分片集群),将数据通过路由代理分片到不同的服务器

     b、采用集群方式,利用虚拟槽的概念来做。

2redis和数据库双写一致性问题

     Redis只能做到最终一致性,不能保证强一致性。

3、缓存穿透

      若缓存和数据库中都没有的数据,大流量都会直接打到DB,导致DB挂掉,这种现象俗称“缓存穿透”。

      为了应对这种情况,一般做如下处理:

       a、会将该key写入缓存值设置为null,为了减少对正常应用的影响,把有效时间设置的短一点,具体根据应用场景来定。

       b、采用降级或限流控制流量入口

4、缓存雪崩

     若缓存中的数据大批量已经过期,大流量都会直接打到DB,导致DB挂掉,这种现象俗称“缓存雪崩”。

     为了应对这种情况,一般做如下处理:

     a、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

     b、如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中,且设置不同的过期时间。

     c、热点数据考虑设置永远不过期。

     d、采用降级或限流控制流量入口

5、缓存击穿

     若缓存中没有但数据库中有(指同一条数据),大流量并发取这条数据到DB,导致DB挂掉,这种现象俗称“缓存击穿”。

     为了应对这种情况,一般做如下处理:

     a、热点数据考虑设置永远不过期。

     b、采用布隆过滤器。

     c、采用降级或限流控制流量入口。

6、热点Key并发竞争

     若同时有多个有个多个或不同系统请求并发设置一个key,会发生数据应用不一致问题。例如像事务中的脏读、幻读等。

     为了应对这种情况,一般做如下处理:

     a、若不要求顺序,可以采用加锁或分布式锁

     b、若要求按顺序,可以采用加锁或分布式锁,先放入队列,然后异步处理队列。

     c、或者采用其他方式,做到串行处理set操作即可。     

优点:

1、数据处理速度快

     因为其基于内存操作,每秒可以执行大约 110000 个写入操作,或者 81000 个读操作,其速度远超数据库。

2、支持丰富的数据类型

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

3、支持数据的持久化

      可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用

4、支持数据的备份

     内置支持master-slave模式的数据备份

5、内部操作原子性

     Redis的所有操作都是原子性的(要不成功,要不失败),同时Redis还支持对几个操作全并后的原子性执行,也就是事务。

     但要注意这里的事务只能保证批量块的事务,批量块内部的语句不具备事务性。

6、丰富的特性

      Redis 可以在如缓存、消息传递队列中使用(Redis 支持“发布+订阅”的消息模式),在应用程序如 Web 应用程序会话、网站页面点击数、设置key有效期等中使用。

常见应用场景:

1、缓存

  缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,

所以,现在Redis用在缓存的场合非常多。例如:token、热点数据。

2、排行榜

  很多网站都有排行榜应用的,如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。

3、计数器

   如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。

  Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。

4、分布式会话

  集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。

5、分布式锁

   在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。

6 社交网络

  点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。

7、最新列表

   Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容页即可。

8、消息系统(不推荐使用)

消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。

Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。

调优经验:

PS:后续逐渐把实践调优过程补充上来

API应用:

各个平台一般都有相应的操作组件,下面介绍下java和DoNet平台下的访问组件。

java平台:

1jedis

<dependency>

    <groupId>redis.clients</groupId>

    <artifactId>jedis</artifactId>

    <version>版本</version>

</dependency>

2Spring Boot整合Spring Cache应用Redis

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-redis</artifactId>

    <version>版本</version>

</dependency>

3spring-data-redis

<dependency>

    <groupId>org.springframework.data</groupId>

    <artifactId>spring-data-redis</artifactId>

    <version>版本</version>

</dependency>

4Redisson(底层使用Netty

<dependency>

         <groupId>org.redisson</groupId>

         <artifactId>redisson-spring-boot-starter</artifactId>

         <version>版本</version>

</dependency>

5Lettuce

<dependency>

    <groupId>io.lettuce</groupId>

    <artifactId>lettuce-core</artifactId>

    <version>5.1.8.RELEASE</version>

</dependency>

DoNet平台:

1CSRedisCore 推荐使用这个

Nuget: 下载CSRedisCore

2StackExchange.Redis 

Nuget: StackExchange.Redis

3ServiceStack.Redis 微软提供 收费

Nuget: ServiceStack.Redis

  • 0
    点赞
  • 3
    收藏
  • 打赏
    打赏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

mql007007

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

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值