Redis 是一个开源(BSD 许可)、基于内存、支持多种数据结构的存储系统,可以作为数据库、缓存和消息中间件。它支持的数据结构有字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等,除此之外还支持 bitmaps、hyperloglogs 和地理空间( geospatial )索引半径查询等功能。
它内置了复制(Replication)、LUA 脚本(Lua scripting)、LRU 驱动事件(LRU eviction)、事务(Transactions)和不同级别的磁盘持久化(persistence)功能,并通过 Redis 哨兵(哨兵)和集群(Cluster)保证缓存的高可用性(High availability)。
Redis 支持的数据类型有:
类型 | 简介 | 特性 | 场景 |
---|---|---|---|
String(字符串) | 二进制安全 | 可以包含任何数据,比如 JPG 图片或者序列化的对象,一个键最大能存储 512M | 简短的字符场景 |
Hash(哈希) | 键值对集合,即编程语言中的 Map 类型 | 适合存储对象,并且可以像数据库中 update 一个属性一样只修改某一项属性值(Memcached 中需要取出整个字符串反序列化成对象修改完再序列化存回去) | 存储、读取、修改用户属性 |
List(列表) | 链表(双向链表) | 增删快,提供了操作某一段元素的 API | ①最新消息排行等功能(比如朋友圈的时间线); ②消息队列 |
Set(集合) | 哈希表实现,元素不重复 | ①添加、删除,查找的复杂度都是 O(1) ②为集合提供了求交集、并集、差集等操作 | ①共同好友; ②利用唯一性,统计访问网站的所有独立 IP; ③好友推荐时,根据 tag 求交集,大于某个阈值就可以推荐 |
Sorted Set(有序集合) | 将 Set 中的元素增加一个权重参数 score,元素按 score 有序排列 | 数据插入集合时,已经进行天然排序 | ①排行榜; ②带权重的消息队列 |
具有以下好处:
Redis 相比 Memcache 有以下的优势:
它们的主要区别有:
Redis 是单进程单线程的,它可以通过队列技术将并发访问变为串行访问,避免了传统数据库串行控制的开销。
Redis 将数据放在内存中有一个好处,那就是可以实现最快的对数据读取,如果数据存储在硬盘中,磁盘 I/O 会严重影响 Redis 的性能。而且 Redis 还提供了数据持久化功能,不用担心服务器重启对内存中数据的影响。其次现在硬件越来越便宜的情况下,Redis 的使用也被应用得越来越多,使得它拥有很大的优势。
Redis 支持主从同步、从从同步。如果是第一次进行主从同步,主节点需要使用 bgsave 命令,再将后续修改操作记录到内存的缓冲区,等 RDB 文件全部同步到复制节点,复制节点接受完成后将 RDB 镜像记载到内存中。等加载完成后,复制节点通知主节点将复制期间修改的操作记录同步到复制节点,即可完成同步过程。
使用 pipeline(管道)的好处在于可以将多次 I/O 往返的时间缩短为一次,但是要求管道中执行的指令间没有因果关系。
用 pipeline 的原因在于可以实现请求/响应服务器的功能,当客户端尚未读取旧响应时,它也可以处理新的请求。如果客户端存在多个命令发送到服务器时,那么客户端无需等待服务端的每次响应才能执行下个命令,只需最后一步从服务端读取回复即可。
Redis 是一个开源(BSD 许可),基于内存,支持多种数据结构的存储系统。可以作为数据库、缓存和消息中间件。它支持的数据结构有字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等,除此之外还支持 bitmaps、hyperloglogs 和地理空间( geospatial )索引半径查询等功能。根据它的特性,它适用的场景有:
1. 会话缓存
会话(Session)是存储在服务端的,但是可以设置存储的时候不以文件的方式存储,而是存到 Redis 中,而且 Redis 支持数据持久化,不用担心数据因为服务器重启导致 Session 数据丢失的问题。这样做的好处不只是提高获取会话的速度,也对网站的整体性能有很大的提升。
2. 数据缓存
Redis 支持多种数据结构,经常被用来做缓存中间件使用。缓存的数据不只是包括数据库中的数据,也可以缓存一些需要临时存储的数据,例如 token、会话数据等。
3. 队列
Redis 是支持列表(lists)功能的,可以简单实现一个队列的功能,对数据进行入队、出队操作。实现的队列可以应用到电商的秒杀场景中。
4. 排行榜、计数器
Redis 提供了有序集合,可以对数据进行排名,实现排行榜功能。 其次 Redis 中提供了 incr 对数字加 1 命令,也提供了 decr 对数字减 1 命令,所以可以实现一个简单的计数器功能。
5. 发布、订阅功能
Redis 中提供了发布订阅相关的命令,可以用来做一些跟发布订阅相关的场景应用等。例如简单的消息队列功能等。
Redis 中的事务是一组命令的集合,是 Redis 的最小执行单位。它可以保证一次执行多个命令,每个事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序地执行。服务端在执行事务的过程中,不会被其他客户端发送来的命令请求打断。
它的原理是先将属于一个事务的命令发送给 Redis,然后依次执行这些命令。
需要注意的点有:
Redis 的事务不支持回滚,但是执行的命令有语法错误,Redis 会执行失败,这些问题可以从程序层面捕获并解决。但是如果出现其他问题,则依然会继续执行余下的命令。这样做的原因是因为回滚需要增加很多工作,而不支持回滚则可以保持简单、快速的特性。
Redis 密码设置有两种方式:
requirepass 访问密码
。
config set requirepass
访问密码
。如果需要查询密码,可以使用 config get requirepass
命令。如果需要验证密码,可以使用 auth 访问密码,再执行 config get
requirepass
获取。
需要注意的是,通过这种方式设置访问密码,如果 redis.conf 配置文件中没有设置对应的访问密码,那么服务器重启后访问密码会失效。
多线程处理会涉及到锁,并且多线程处理会涉及到线程切换而消耗 CPU。采用单线程,避免了不必要的上下文切换和竞争条件。其次 CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存或者网络带宽。
通过命令 ping 执行后,会得到回复结果 pong。如果没有得到 pong 的结果说明 Redis 没有正常连通。
在 MySQL 中,事务是指一组操作中,要么全部执行,要么全部不执行。而在 Redis 也存在事务的概念。Redis 的事务可以保证一次执行多个命令,每个事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序地执行。服务端在执行事务的过程中,不会被其他客户端发送来的命令请求打断。
Redis 客户端和 Redis 服务器通信时使用的是 RESP(Redis 序列化协议)通讯协议,该协议是专门为 Redis 设计的,但是也可以用于其他客户端和服务器软件项目中。
RESP 的特点为:实现简单、快速解析、可读性好。
单点 TPS 达到 8 万/秒,QPS 达到 10 万/秒。TPS 和 QPS 的意思:
使用 pipeline(管道)的好处在于可以将多次 I/O 往返的时间缩短为一次,但是要求管道中执行的指令间没有因果关系。
因为 Redis 在重启才能加载配置项中的配置,所以需要重启才能生效。针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。
从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。检索
'CONFIG GET *'
命令获取更多信息。
但偶尔重新启动是必须的,如为升级 Redis 程序到新的版本,或者当你需要修改某些目前 CONFIG 命令还不支持的配置参数的时候。
常用的设置有:
1. 端口设置
只允许信任的客户端发送过来的请求,对其他所有请求都拒绝。如果存在暴露在外网的服务,那么需要使用防火墙阻止外部访问 Redis 端口。
2. 身份验证
使用 Redis 提供的身份验证功能,在 redis.conf 文件中进行配置生效,客户端可以发送(AUTH 密码)命令进行身份认证。
3. 禁用特定的命令集
可以考虑禁止一些容易产生安全问题的命令,预防被人恶意操作。
发布订阅是一种消息通信模式,发送者发送消息到某个频道,订阅了该频道的用户都可以接收到消息。该模式由发布者、接收者和频道组成。
它们主要的不同有:
因为 Redis 是基于 TCP 协议的请求/响应服务器,每次通信都需要经过 TCP 协议的三次握手,所以当需要执行的命令足够大时,会产生很大的网络延迟。并且网络的传输时间成本和服务器开销没有计入其中,总的延迟可能更大。Pipeline 主要就是为了解决存在这种情况的场景,对此存在类似的场景都可以考虑使用 Pipeline。
可以适用场景有:如果存在批量数据需要写入 Redis,并且这些数据允许一定比例的写入失败,那么可以使用 Pipeline,后期再对失败的数据进行补偿即可。
主要常用的业务场景有:
list push
和 list pop
这样的命令,所以能够很方便的执行队列操作。
适用的场景有:
字符串类型是最基本的数据类型,是二进制安全的字符串,最大 512M。
哈希类型操作命令和方法为:
命令 | 说明 | Cli 命令写法 | PHP 写法 |
---|---|---|---|
hset | 赋值 | hset key field value |
$redis->hSet(key,field,value); |
hmset | 赋值多个字段 | hmset key field1 value1 [field2 values] |
$redis->hMset(key,array('field1'=>'value1','field2'=>'value2'));
|
hget | 取值 | hget key field |
$redis->hGet(key,field); |
hmget | 取多个字段的值 | hmget key field1[field2] |
$redis->hmGet(key,array('field1','field2')); |
hgetall | 取所有字段的值 | hgetall key |
$redis->hGet(key,array('field1','field2')); |
hlen | 获取字段的数量 | hlen key |
$redis->hLen(key); |
它们最大存储量分别为:
zset 的功能和 sets 类似,但是它在集合内的元素是有顺序,不能重复的。所以适合做排行榜之类的功能。
它底层实现机制的实现方式有两种,分别为 ziplist(压缩列表) 或者 skiplist(跳跃表)。它们的区别为:
事务从开始到执行会经历的三个阶段:开始事务、命令入队、 执行事务。它以 MULTI 开始一个事务,然后让多个命令入队到事务中,最后通过命令 EXEC 触发执行事务。它们的执行命令有:
命令 | 作用 | 使用方法 |
---|---|---|
MULTI | 标记一个事务块的开始 | Multi |
DISCARD | 取消事务、放弃执行事务块的所有命令 | Discard |
WATCH key [key …] | 监视一个 (或多个) key,如果在事务执行之前这个 (或这些) key 被其他命令所改动,那么事务将被打断 | Watch key |
EXEC | 执行所有事务块内的命令 | Exec |
UNWATCH | 取消 WATCH 命令对所有 key 的监视 | Unwatch |
可以使用 EXPIRE 和 PERSIST 命令。对一个已经带有生存时间的 key 执行 EXPIRE 命令,新指定的生存时间会取代旧的生存时间。PERSIST 命令可以移除给定 key 的生存时间,将这个 key 从带生存时间转换成持久的。
Redis 实例最多可以存放 2 的 32 次方 -1 个 keys,只要 Redis 的内存空间足够可以支持,任何的 list、set、sorted set 都可以放 2^32 -1 个元素。
Redis 支持的数据结构主要有:字符串(string)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted set)等五种数据结构类型。
设置 Redis 的最大连接数的命令为:
redis-server -maxclients 100000(连接数);
查看 Redis 最大连接数的命令为:
config get maxclients
查看 Redis 连接数的命令为:
info 命令
在 redis-cli 中输入 info 命令即可查看。
Redis 提供了过期时间处理函数,可以对指定的键名设置过期时间。如果对键名不设置过期时间也可以使用 DEL 函数对数据进行删除。当用户对一个键名设置了生效时间,我们可以说这个键名存在“生存时间”或“在指定时间后过期”。对键名设置过期时间可以有效地释放键名占用的内存空间,在实际的开发过程中是非常提倡的一种做法。相关命令如下:
命令 | 说明 | Cli 命令写法 |
---|---|---|
PERSIST | 移除键的过期时间 | PERSIST key-name |
EXPIRE | 让给定键在指定的秒数之后过期 | EXPIRE key-name seconds |
EXPIREAT | 将给定键的过期时间设置为给定的 UNIX 时间戳,以秒为单位 | EXPIREAT key-name timestamp |
PEXPIRE | 让给定键在指定的毫秒数之后过期 | PEXPIRE key-name milliseconds |
PEXPIREAT | 将一个毫秒级精度的 UNIX 时间戳设置为给定键过期时间 | PEXPIREAT key-name timestamp-milliseconds |
PTTL | 查看给定键距离过期时间还有多少毫秒 | PTTL key-name |
TTL | 查看给定键距离过期还有多少秒 | TTL key-name |
SDS 实现方式相对 C 语言的 String 的好处有:
以上几点好处可以概括如下:
C 字符串 | SDS |
---|---|
获取字符串长度的复杂度为 O(N) | 获取字符串长度的复杂度为 O(1) |
API 是不安全的,可能会造成缓冲区溢出 | API 是安全的,不会造成缓冲区溢出 |
修改字符串长度 N 次必然需要执行 N 次内存重分配 | 修改字符串长度 N 次最多需要执行 N 次内存重分配 |
只能保存文本数据 | 可以保存文本或者二进制数据 |
可以使用所有库中的函数 | 可以使用一部分库的函数 |
Redis 底层实现了简单动态字符串的类型(SDS),来表示 String 类型。没有直接使用 C 语言定义的字符串类型。
Redis 底层使用简单动态字符串(simple dynamic string,SDS)的抽象类型实现的。默认以 SDS 作为自己的字符串表示。而没有直接使用 C 语言定义的字符串类型。
SDS 的定义格式如下:
struct sdshdr{
//记录 buf 数组中已使用字节的数量
//等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[]; //buf的大小等于len+free+1,其中多余的1个字节是用来存储‘\0’的。
}
SDS 的存储示例如下:
具有以下特征:
内存消耗可以分类为:
可以使用 info 命令来获取内存统计使用信息。
可以使用 redis-cli –bigkeys
命令统计 bigkey 的分布。也可以在生产环节下执行
debug object key
命令查看 serializedlength 属性,获得 key 对应的 value
序列化之后的字节数。
bigkey 是指键值占用内存空间非常大的 key。例如一个字符串 a 存储了 200M 的数据。
bigkey 的主要影响有:
Redis 常见性能问题和解决方案如下:
因为内存的空间是有限的,所以 Redis 淘汰机制主要为了解决在某个时间点,Redis 中存在很多过期键,定期删除策略随机抽查时没有抽查到,并且也没有走惰性删除策略时,大量过期键占用内存的问题。如果内存只能存 20w 数据,而我们需要存储 2000w 的数据时,自然就需要对多出来的数据进行删除或覆盖,保证内存中存储的数据都是热数据。所以当 Redis 内存数据集的大小上升到一定数量时,就会执行数据淘汰策略。
我们可以使用 keys 命令和 scan 命令,但是会发现使用 scan 更好。
1. 使用 keys 命令
直接使用 keys 命令查询,但是如果是在生产环境下使用会出现一个问题,keys 命令是遍历查询的,查询的时间复杂度为 O(n),数据量越大查询时间越长。而且 Redis 是单线程,keys 指令会导致线程阻塞一段时间,会导致线上 Redis 停顿一段时间,直到 keys 执行完毕才能恢复。这在生产环境是不允许的。除此之外,需要注意的是,这个命令没有分页功能,会一次性查询出所有符合条件的 key 值,会发现查询结果非常大,输出的信息非常多。所以不推荐使用这个命令。
2. 使用 scan 命令
scan 命令可以实现和 keys 一样的匹配功能,但是 scan 命令在执行的过程不会阻塞线程,并且查找的数据可能存在重复,需要客户端操作去重。因为 scan 是通过游标方式查询的,所以不会导致 Redis 出现假死的问题。Redis 查询过程中会把游标返回给客户端,单次返回空值且游标不为 0,则说明遍历还没结束,客户端继续遍历查询。scan 在检索的过程中,被删除的元素是不会被查询出来的,但是如果在迭代过程中有元素被修改,scan 不能保证查询出对应元素。相对来说,scan 指令查找花费的时间会比 keys 指令长。
大量的请求瞬时涌入系统,而这个数据在 Redis 中不存在,所有的请求都落到了数据库上把数据库打死。造成这种情况的原因有系统设计不合理、缓存数据更新不及时,或爬虫等恶意攻击。 解决办法有:
1. 使用布隆过滤器
将查询的参数都存储到一个 bitmap 中,在查询缓存前,再找个新的 bitmap,在里面对参数进行验证。如果验证的 bitmap 中存在,则进行底层缓存的数据查询,如果 bitmap 中不存在查询参数,则进行拦截,不再进行缓存的数据查询。
2. 缓存空对象
如果从数据库查询的结果为空,依然把这个结果进行缓存,那么当用 key 获取数据时,即使数据不存在,Redis 也可以直接返回结果,避免多次访问数据库。
但是缓存空值的缺点是:
缓存雪崩是指当大量缓存失效时,大量的请求访问直接请求数据库,导致数据库服务器无法抗住请求或挂掉的情况。这时网站常常会出现 502 错误,导致网站不可用问题。
在预防缓存雪崩时,建议遵守以下几个原则:
如果有大量的 key 在同一时间过期,那么可能同一秒都从数据库获取数据,给数据库造成很大的压力,导致数据库崩溃,系统出现 502 问题。也有可能同时失效,那一刻不用都访问数据库,压力不够大的话,那么 Redis 会出现短暂的卡顿问题。所以为了预防这种问题的发生,最好给数据的过期时间加一个随机值,让过期时间更加分散。
Redis 的哨兵作用是管理多个 Redis 服务器,提供了监控、提醒以及自动的故障转移的功能。哨兵可以保证当主服务器挂了后,可以从从服务器选择一台当主服务器,把别的从服务器转移到读新的主机。Redis 哨兵的主要功能有:
Redis 的集群的功能是为了解决单机 Redis 容量有限的问题,将数据按一定的规则分配到多台机器,对内存的每秒访问不受限于单台服务器,可受益于分布式集群高扩展性。
哨兵是 Redis 集群架构中非常重要的一个组件,主要功能如下:
Redis 产生阻塞的原因主要有内部和外部两个原因导致:
1. 内部原因
2. 外部原因
外部原因主要是服务器的原因,例如服务器的 CPU 线程在切换过程中竞争过大,内存出现问题、网络问题等。
Redis 可以使用主从同步、从从同步。第一次同步时,主节点做一次 bgsave,并同时将后续修改操作记录到内存 buffer,待完成后将 RDB 文件全量同步到复制节点,复制节点接受完成后将 RDB 镜像加载到内存。
加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。
对于 big key 先分析业务存在大键值是否合理,如果不合理我们可以把它们拆分为多个小的存储。或者看是否可以使用别的存储介质存储这种 big key 解决占用内存空间大的问题。
对于 hot key 我们可以在其他机器上复制这个 key 的备份,让请求进来的时候,去随机的到各台机器上访问缓存。所以剩下的问题就是如何做到让请求平均的分布到各台机器上。
可以从以下两方面准备:
1. 使用 Redis 自身监控系统
使用 Redis 自身监控系统,可以对 CPU、内存、磁盘、命令耗时等阻塞问题进行监控,当监控系统发现各个监控指标存在异常的时候,发送报警。
2. 使用应用服务监控
当 Redis 存在阻塞时,应用响应时间就会延长,应用可以感知发现问题,并发送报警给管理人员。
客户端分区就是在客户端就已经决定数据会被存储到哪个 Redis 节点或者从哪个 Redis 节点读取。大多数客户端已经实现了客户端分区。
代理分区意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些 Redis 实例,然后根据 Redis 的响应结果返回给客户端。Redis 和 Memcached 的一种代理实现就是 Twemproxy。
查询路由(Query routing)的意思是客户端随机地请求任意一个 Redis 实例,然后由 Redis 将请求转发给正确的 Redis 节点。
Redis Cluster 实现了一种混合形式的查询路由,但并不是直接将请求从一个 Redis 节点转发到另一个 Redis 节点,而是在客户端的帮助下直接 redirected 到正确的 Redis 节点。
需要知道 Redis 的复制方式前,需要知道主从复制(Master-Slave Replication)的工作原理,具体为:
整个执行的过程都是使用异步复制的方式进行复制。
当一个数据需要更新时,因为不可能做到同时更新数据库和缓存,那么此时读取数据的时候就一定会发生数据不一致问题,而数据不一致问题在金融交易领域的系统中是肯定不允许的。
解决办法:读的时候先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。更新的时候,先更新数据库,然后再删除缓存。
分区可以让 Redis 管理更大的内存,Redis 将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。
分区使 Redis 的计算能力通过简单地增加计算机得到成倍提升,Redis 的网络带宽也会随着计算机和网卡的增加而成倍增长。
为防止以后扩容增加难度,最好的办法就是一开始就使用分布式。即便只有一台服务器,也可以一开始就让 Redis 以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。
刚开始操作时比较麻烦,但是当数据不断增长,需要更多的 Redis 服务器时,只需要将 Redis 实例从一台服务迁移到另外一台服务器即可,而不用考虑重新分区的问题。一旦添加了另一台服务器,只需要将一半的 Redis 实例从第一台机器迁移到第二台机器。
如果大量的 key 过期时间设置的过于集中,到过期的那个时间点,redis 可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在单机或者单进程环境下,多线程并发的情况下,使用锁来保证一个代码块在同一时间内只能由一个线程执行。比如 Java 的 Synchronized 关键字和 Reentrantlock 类。
分布式锁的作用是当多个进程不在同一个系统中,用分布式锁可以控制多个进程对资源的访问。
分布式锁实现需要保证以下几点:
你给出两个词汇就可以了,fork 和 cow。fork 是指 Redis 通过创建子进程来进行 bgsave 操作,cow 指的是 copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
在实际的生产环境当中对于选择 RDB 持久化方式还是 AOF 持久化方式都需要根据数据量、应用对数据的安全要求、硬件相关预算等情况综合考虑,同时也可以考虑主从复制的策略,可以设置主机(master)和从机(slave)使用不同的持久化方案。对此以下只是简单的进行一个选择的介绍,具体在实际的开发应用过程中还需要根据实际情况综合选择。
如果 Redis 被当做缓存使用,使用一致性哈希实现动态扩容缩容。
如果 Redis 被当做一个持久化存储使用,必须使用固定的 keys-to-nodes 映射关系,节点的数量一旦确定不能变化。
否则的话(即 Redis 节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有 Redis 集群可以做到这样。
Redis 主要支持的持久化机制为 RDB(快照)和 AOF(追加文件)。
RDB 持久化是在指定时间间隔内保存数据快照到硬盘中。但 RDB 的持久化方式没有办法实现实时性的持久化。当应用使用 RDB 持久化后,如果 Redis 系统发生崩溃,那么使用 RDB 恢复数据时,恢复后的数据中,存在丢失最近一次生成快照之后更改的所有数据。所以 RDB 持久化不适用于丢失一部分数据也会对应用造成很大影响的备份中。
AOF 持久化是把命令追加到操作日志的尾部,然后保存所有历史操作。AOF 主要是解决数据持久化的实时性。Redis 服务器默认开启 RDB,关闭 AOF;要开启 AOF,需要在配置文件中配置:appendonly yes。AOF 持久化相对于 RDB 持久化的优点在于可以实时的对 Redis 缓存进行写入记录,保证快速恢复缓存时的完整性。
RDB 和 AOF 的区别:
它的优点有:
它的缺点有:
它的优点有:
它的缺点有:
如果 AOF 文件数据出现异常,为了保证数据的一致性,Redis 服务器会拒绝加载 AOF 文件。可以尝试使用 redis-check-aof -fix 命令修复。
使用过 Redis 集群,它的原理是:
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0~16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
Redis 没有使用哈希一致性算法,而是使用哈希槽。Redis 中的哈希槽一共有 16384 个,计算给定密钥的哈希槽,我们只需要对密钥的 CRC16 取摸 16384。假设集群中有 A、B、C 三个集群节点,不存在复制模式下,每个集群的节点包含的哈希槽如下:
Redis 集群架构是支持单节点单机模式的,也支持一主多从的主从结构,还支持带有哨兵的集群部署模式。
Redis 集群并没有使用一致性 hash,而是引入了哈希槽的概念。Redis 集群有 16384(2^14)个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
Redis 集群支持的主从复制,数据同步主要有两种方法:一种是全量同步,一种是增量同步。
1. 全量同步
刚开始搭建主从模式时,从机需要从主机上获取所有数据,这时就需要 Slave 将 Master 上所有的数据进行同步复制。复制的步骤为:
2. 增量同步
从节点完成了全量同步后,就可以正式的开启增量备份。当 Master 节点有写操作时,都会自动同步到 Slave 节点上。Master 节点每执行一个命令,都会同步向 Slave 服务器发送相同的写命令,当从服务器接收到命令,会同步执行。
Redis 集群中有可能存在写操作丢失的问题,但是丢失概率一般可以忽略不计。主要是 Redis 并没有一个机制保证数据一定写不丢失。在以下问题中可能出现键值丢失的问题:
1. 超过内存的最大值,键值被清理
Redis 中可以设置缓存的最大内存值,当内存中的数据总量大于设置的最大内存值,会导致 Redis 对部分数据进行清理,导致键值丢失的问题。
2. 大量的 key 过期,被清理
这种情况比较正常,只是因为键值设置的时间过期了,被自动清理了。
3. Redis 主库服务器故障重启
由于 Redis 的数据是缓存在内存中的,如果 Redis 主库服务器出现故障重启,会出现数据被清空的问题。这时可能导致从库的数据同步被清空。如果有使用数据持久化,那么故障重启后数据是可以自动恢复的。
4. 网络问题
可能出现网络故障,导致短时间内数据写入失败。
使用异步复制。
Redis 集群有 16384 个哈希槽。
Redis 集群不支持选择数据库操作,默认在 0 数据库。
Redis 可以使用的集群方法有:
Redis 哨兵的好处在于可以保证系统的高可用,各个节点可以对故障自动转移。但缺点是使用的主从模式,主节点单点风险高,主从切换过程可能会出现丢失数据的问题。
主从复制的模式相对于单节点的好处在于,实行读写分离提高了系统的读写效率,提高了网站数据的读取加载速度。但是缺点是由于写数据主要在主节点上操作,主节点内存空间有限,并且主节点存在单点风险。
常见问题及解决办法有:
过期键的删除策略是将惰性删除策略和定期删除策略组合使用。
1. 定时删除策略
该策略的作用是给 key 设置过期时间的同时,给 key 创建一个定时器,定时器在 key 的过期时间来临时,对这些 key 进行删除。 这样做的好处是保证内存空间得以释放。但是缺点是给 key 创建一个定时器会有一定的性能损失。如果 key 很多,删除这些 key 占用的内存空间也会占用 CPU 很多时间。
2. 惰性删除策略
每次从数据库取 key 的时候检查 key 是否过期,如果过期则删除,并返回 null,如果 key 没有过期,则直接返回数据。
这样做的好处是占用 CPU 的时间比较少。但是缺点是如果 key 很长时间没有被获取,将不会被删除,容易造成内存泄露。
3. 定期删除策略
该策略的作用是每隔一段时间执行一次删除过期 key 的操作,该删除频率可以在 redis.conf 配置文件中设置。
这样做的好处是可以避免惰性删除时出现内存泄露的问题,通过设置删除操作的时长频率,可以减少 CPU 时间的占用。但是缺点是相对内存性能友好来说,该策略不如定时删除策略,相对 CPU 性能友好来说,该策略不如惰性删除策略。
Redis 采用的删除策略是将惰性删除策略和定期删除策略组合使用。
Redis 提供了 6 种淘汰策略:
主要可以从以下几点进行优化:
主从复制的结构一定要使用单向结构,避免使用图状结构。
当客户端运行了新命令添加新数据,Redis 都会检查内存使用情况,如果内存使用情况大于 maxmemory 的限制,那么回根据设置的淘汰策略进行回收。所以会出现内存的使用限制不断的超过边界又被回收到边界以下。
可以使用 Hash、list、sorted set、set 等集合类型数据,更有利于将数据存储得更紧凑,更有利于内存空间的使用。
可以对大对象数据进行拆分,防止执行一次命令操作过多的数据。也可以将一些算法复杂度高的命令或执行效率低的命令,禁用或者替换成高的指令。
慢查询是指系统执行命令之后,当计算系统执行的指令时间超过设置的阀值,该命令就会被记录到慢查询中,该命令叫做慢指令。
Redis 慢查询的参数配置有:
1. slowlog-log-slower-than
该参数的作用为设置慢查询的阈值,当命令执行时间超过这个阈值就认为是慢查询。单位为微妙,默认为 10000。
可以根据自己线上的并发量进行调整这个值。如果存在高流量的场景,那么建议设置这个值为 1 毫秒,因为每个命令执行的时间如果超过 1 毫秒,那么 Redis 的每秒操作数最多只能到 1000。
2. slowlog-max-len
该参数的作用为设置慢查询日志列表的最大长度,当慢查询日志列表处于最大长度时,最早插入的一个命令将会被从列表中移除。
内存优化可以从以下几点进行:
如果 Redis 的内存使用达到了 redis.conf 配置文件中的设置上限,执行 Redis 的写命令会返回错误信息,但是还是支持读操作。解决这个问题的办法是,可以开启 Redis 的淘汰机制,当 Redis 内存达到上限时可以根据配置的策略删除一些数据,防止内存用完。
缓存的更新策略主要有以下几种:
对应的策略分别需要注意的事项为:
1. 先更新数据库,再更新缓存
使用这种策略可能会存在以下两种问题:
2. 先删除缓存,再更新数据库
这种策略可能导致数据库数据和缓存数据不一致的问题。如果存在线程 1 和线程 2,线程 1 写数据先删除缓存,有一个线程 2 正好需要查询该缓存,发现缓存不存在,去访问数据库,并得到旧值放入缓存重,线程 1 再更新数据库。那么这时就出现了数据不一致的问题。如果缓存没有过期时间,那么这个脏数据一直存在。如果要解决这个问题,那么可以在更新完数据库后,对缓存再淘汰一次。
3. 先更新数据库,再删除缓存
这种策略可能导致数据库数据和缓存数据不一致的问题。如果在更新完数据库还没来得及删除缓存的时候,有请求过来从缓存中获取数据,那么可能会造成缓存和数据库数据不一致的问题。但是正常情况下,机器不出现故障或其他影响的情况下,不一致性的可能性相对较低。
可以对 Redis 进行集群部署,实行主从同步读写分离,可以方便的对 Redis 进行横向扩容,可以支撑系统更大数据量的缓存和提高系统的可用性。
惰性删除的优点为:对 CPU 比较友好,因为每次键空间获取键时,检查获取到的键是否过期删除,删除的键只在选中中饿键,不用花费过多的时间和资源在其它任务上,不会增加 CPU 的负担。所以对 CPU 比较友好。
惰性删除的缺点为:但是对内存并不友好,没有被选中的键也有可能存在过期的,但是未被选中删除一直存在内存中。当存在很多这些过期键一直未被获取和未被选中删除,就会一直在内存中。所以对内存并不友好。
可以在同一个服务器部署多个 Redis 的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的,所以,如果你想使用多个 CPU,你可以考虑一下分片(shard)。
因为内存的空间是有限的,所以 Redis 引入了淘汰机制,主要为了解决在某个时间点,Redis 中存在很多过期键,定期删除策略随机抽查时没有抽查到,并且也没有走惰性删除策略时,大量过期键占用内存的问题。
Redis 内存不足可以这样处理:
设置方法为:修改配置文件 redis.conf 的 maxmemory 参数,该参数可以限制最大可用内存。
内存管理方式主要有两个,一是控制内存上限;二是优化回收策略,对内存回收。
主要常见的淘汰算法有:
1. 最近最少使用算法(LRU)
LRU 是 Least Recently Used 的缩写,中文意思是最近最少使用,它是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。
2. 先进先出算法(FIFO)
FIFO 是 First Input First Output 的缩写,即先入先出队列,这是一种传统的按序执行方法,先进入的指令先完成并引退,跟着才执行第二条指令。
3. 最不经常使用算法(LFU)
LFU 是 Least Frequently Used 的缩写,中文意思是最不经常使用。在一段时间内,数据被使用次数最少的,优先被淘汰。
A. Redis 支持字符串、哈希、列表、集合、有序集合等数据结构,目前 Redis 不支持事务。 B. MongoDB 支持 CAP 定理中的 AP,MySQL 支持 CAP 中的 CA,全部都支持不可能存在。 C. MongoDB 不用先创建 Collection 的结构就可以直接插入数据,目前 MongoDB 不支持事务。 D. Memcache 既支持 TCP 协议,也支持UDP协议,我们可以把 PHP的 Session 存放到 Memcache 中。
答案:A
本题解析:Redis 是支持事务的。选项 A 说法错误,所以选项 A 正确。
主要支持的 Java 客户端有:Redisson、Jedis、lettuce 等,官方推荐使用 Redisson。
Twemproxy 具有以下特点:
我们从题目中分析知道,首页的热门售卖商品知道,访问的数据是热数据,且访问量很大,还需要对当天的热门售卖商品进行排行。
可以使用 zset 数据类型对热数据进行缓存,zset 可以对数据进行排行,把 key 作为商品的 ID,value 作为商品当天销售的数量,可以根据 value 对商品排行。
Redisson 是架设在 Redis 基础上的一个 Java 驻内存数据网格,它充分的利用了 Redis 键值数据库提供的一系列优势,基于 Java 实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。
Redisson 采用了基于 NIO 的 Netty 框架,不仅能作为 Redis 底层驱动客户端,具备提供对 Redis 各种组态形式的连接功能,对 Redis 命令能以同步发送、异步形式发送、异步流形式发送或管道形式发送的功能,LUA 脚本执行处理,以及处理返回结果的功能,还在此基础上融入了更高级的应用方案。
它们的主要优缺点有:
Redis 主要将数据存储在内存中,它是单进程单线程的,所以主要消耗的资源和瓶颈是内存而不是 CPU。
A. 二进制安全 B. 在 TCP 层 C. 基于请求—响应模式 D. 不可以序列化不同的数据类型
答案:ABC
分析:Redis 客户端和 Redis 服务器通信时使用的是 RESP(REdis 序列化协议)通讯协议,该协议是专门为 Redis 设计的,但是也可以用于其他客户端和服务器软件项目中。
对于选项 A,RESP 是二进制安全的,并且不需要处理从一个进程传输到另一个进程的批量数据,因为它使用前缀长度来传输批量数据。所以选项 A 正确。
对于选项 B,Redis 客户端连接到 Redis 服务器,并创建与端口 6379 的 TCP 连接。尽管 RESP 在技术上不是特定于 TCP 的,但在 Redis 的上下文中,该协议仅用于 TCP 连接(或等效的面向流的连接,如 Unix 套接字)。所以选项 B 正确。
对于选项 C,Redis 接受由不同参数组成的命令。收到命令后,将对其进行处理并将答复发送回客户端。所以选项 C 正确。
对于选项 D,RESP 可以序列化不同的数据类型,例如整数,字符串,数组。还有一种特定的错误类型。请求以字符串数组的形式从客户端发送到 Redis 服务器,这些字符串表示要执行的命令的参数。Redis 使用特定于命令的数据类型进行回复。所以选项 D 错误。
所以本题的答案为 ABC。
主要常用的手段有:
缓存命中率表示从缓存中读取数据时可以获取到数据的次数,命中率越高代表缓存的使用效率越高,应用的性能越好。
它的公式为:
缓存命中率 = 缓存中获取数据次数/获取数据总次数
Redis(即 Remote Dictionary Server 的缩写,中文意思为远程字典服务)是一个使用 ANSI C 语言编写,开源的、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,它提供了多种语言的 API。
它内部支持多种数据结构、数据持久化机制、主从结构、Redis 集群等功能。
Jedis 的基本使用方法:使用 Jedis 存储数据,Jedis 读取数据的操作命令和 Redis 读取数据的操作命令基本相同。
字符串类型:
jedis.set(String key,String value);
String value = jedis.gett(String key);
jedis.setex(String key,int seconds,String
value);
jedis.dle(String key);
hash 类型:map
jedis.hset(String key,String field,String value);
String value = jedis.hget(String key,String field);
Map<String,String> map = jedis.hgetall(String key);
jedis.hdel(String key,String field);
list类型,可以重复:
jedis.lpush(String key,String...strings);
可以存储多个数据,逗号隔开。
jedis.rpush(String key,String...strings);
jedis.lrange(String key,long start,long end);
jedis.lpop(String key);
jedis.rpop(String key);
set类型,不可重复:
jedis.sadd(String key,String...strings);
可以存储多个数据,逗号隔开。
jedis.smembers(String key);
jedis.srem(String key,String strings);
可以删除多个数据,逗号隔开。
sorted类型,不可重复,有序:
jedis.zadd(String key,double score,String value);
jedis.zrange(String key,long start,long end);
jedis.zrem(String key,String value);
原文链接:
Redis 如果 key 相同,后一个 key 会覆盖前一个 key。如果要解决 key 冲突,最好给 key 取好名区分开,可以按业务名和参数区分开取名,避免重复 key 导致的冲突。