redis
基本数据类型
键的类型只能为字符串
string
string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。
hash
list
set
代码详情
1 | redis 127.0.0.1:6379> SADD runoobkey redis |
zset
memcached 和 redis的对比
这两者都可用于存储键值映射,彼此的性能也相差无几,
Redis能够自动以两种不同的方式将数据写入硬盘(AOF RDB)
Redis除了能存储普通的字符串键之外,还可以存储其他4种数据结构,而memcached只能存储普通的字符串键。
Redis既可以用作主数据库( primary database )使用,又可以作为其他存储系统的辅助数据库( auxiliary database使用。
多种数据库的对比
多种数据库的对比
布隆过滤器
布隆过滤器的应用场景
布隆过滤器的用处就是,能够在节省存储空间的情况下迅速判断一个元素是否在一个集合中。主要有如下三个使用场景:
- 网页爬虫对URL的去重,避免爬取相同的URL地址;
- 反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱;
- 缓存击穿,将已存在的缓存放到布隆过滤器中,当黑客访问不存在的缓存时迅速返回避免缓存及DB挂掉。
bitmap
Redis 官方也做了一个实验,他们模拟了一个拥有 1 亿 2 千 8 百万用户的系统,然后使用 Redis 的位图来统计“日均用户数量”,最终所用时间的约为 50ms,且仅仅占用 16 MB内存。
bitmap的最大长度:512MB
Redis开发与运维的笔记
Redis的全称是remote dictionary server
在字符串的基础上演变出了位图(Bitmaps)、HyperLogLog两种数据结构
初识redis
redis速度快的原因
- redis所有的数据都是放在内存中的
- redis是用c语言编写的
- redis使用了单线程架构,预防了多线程可能产生的竞争问题
- redis源代码精打细磨,是集优雅与性能于一身的开源代码
简单稳定
Redis的简单主要表现在三个方面。首先,Redis的源码很少,早期版本的代码只有2万行
左右,3.0版本以后由于添加了集群特性,代码增至5万行
左右,相对于很多NoSQL数据库来说代码量相对要少很多,也就意味着普通的开发和运维人员完全可以“吃透”它。
其次,Redis使用单线程模型,这样不仅使得Redis服务端处理模型变得简单,而且也使得客户端开发变得简单。
最后,Redis不需要依赖于操作系统中的类库(例Memcache需要依赖libevent这样的系统类库),Redis自己实现了事件处理的相关功能。
redis可以做什么
- 缓存
- 排行榜系统
- 计数器应用
- 社交网络。赞/踩、粉丝、共同好友/喜好、推送、下拉刷新。
- 消息队列系统
redis不可以做什么
- 不适合存储海量的数据。
- 适合放热数据,不适合放冷数据。
学习redis的建议
- 切勿当作黑盒使用,开发与运维同样重要
- 阅读源码
基本命令
shutdown还有一个参数,代表是否在关闭Redis前,生成持久化文件:redis-cli shutdown nosave|save
redis的版本号含义
Redis借鉴了Linux操作系统对于版本号的命名规则:版本号第二位如果是奇数,则为非稳定版本(例如2.7、2.9、3.1),如果是偶数,则为稳定版本(例如2.6、2.8、3.0、3.2)。当前奇数版本就是下一个稳定版本的开发版本,例如2.9版本是3.0版本的开发版本。所以我们在生产环境通常选取偶数版本的Redis,如果对于某些新的特性想提前了解和使用,可以选择最新的奇数版本。
API的理解和使用
全局命令
查看所有键:keys *
键总数:dbsize
检查键是否存在:exists key
删除键:del [key ...]
键过期:expire key seconds
ttl命令会返回键的剩余过期时间,它有3种返回值:
- 大于等于0的整数:键剩余的过期时间。
- -1:键没设置过期时间。
- -2:键不存在
键的数据结构类型:type key
。如果键不存在,则返回none。
数据结构和内部编码
实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样Redis会在合适的场景选择合适的内部编码。
可以看到每种数据结构都有两种以上的内部编码实现,例如list数据结构包含了linkedlist和ziplist两种内部编码。同时有些内部编码,例如ziplist,可以作为多种外部数据结构的内部实现,可以通过object encoding key
命令查询内部编码.
Redis这样设计有两个好处:第一,可以改进内部编码,而对外的数据据结构和命令,例如Redis3.2提供了quicklist,结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现,而对外部用户来说基本感知不到。第二,多种内部编码实现可以在不同场景下发挥各自的优势,例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会有所下降,这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist。
因为Redis是单线程来处理命令的,所以一条命令从客户端达到服务端不会立刻被执行,所有命令都会进入一个队列中,然后逐个被执行。所以客户端命令的执行顺序是不确定的,但是可以确定不会有两条命令被同时执行,所以两条incr命令无论怎么执行最终结果都是2,不会产生并发问题.
为什么单线程还能这么快
- 第一,纯内存访问。Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是Redis达到每秒万级别访问的重要基础。
- 第二,非阻塞I/O。Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。
- 第三,单线程避免了线程切换和竞态产生的消耗。
字符串
字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。
setnx和setxx在实际使用中有什么应用场景吗?以setnx命令为例子,由于Redis的单线程命令处理机制,如果有多个客户端同时执行setnx key value,根据setnx的特性只有一个客户端能设置成功,setnx可以作为分布式锁的一种实现方案,Redis官方给出了使用setnx实现分布式锁的方法:http://redis.io/topics/distlock。
批量设置值:mset key value [key value ...]
批量获取值:mget key [key ...]
批量操作命令可以有效提高开发效率、减少命令耗时。Redis的处理能力已经足够高,对于开发人员来说,网络可能会成为性能的瓶颈。学会使用批量操作,有助于提高业务处理效率,但是要注意的是每次批量操作所发送的命令数不是无节制的,如果数量过多可能造成Redis阻塞或者网络拥塞。
对一个不存在的键执行incr操作后,返回结果是1。
很多存储系统和编程语言内部使用CAS
机制实现计数功能,会有一定的CPU开销,但在Redis中完全不存在这个问题,因为Redis是单线程架构,任何命令到了Redis服务端都要顺序执行。
字符串类型命令时间复杂度 85
内部编码
- int:8个字节的长整型。
- embstr:小于等于39个字节的字符串。
- raw:大于39个字节的字符串。
典型使用场景
- 缓存功能。
- 计数。实际上一个真实的计数系统要考虑的问题会很多:防作弊、按照不同维度计数,数据持久化到底层数据源等。
- 共享Session。负载均衡到多个web服务,session可能会丢失。
- 限速。但是为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次。
限速代码详情
1 | phoneNum = "138xxxxxxxx"; |
哈希
在使用hgetall时,如果哈希元素个数比较多,会存在阻塞Redis的可能。如果开发人员只需要获取部分field,可以使用hmget,如果一定要获取全部field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型,hscan将在2.7节介绍。
哈希操作的复杂度
内部编码
- ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认
512
个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。 - hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。
使用场景
用hash类型存json类型的数据,要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。
list
一个列表最多可以存储 $2^{32}$ -1个元素
第一、列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。
第二、列表中的元素可以是重复的。
列表的四种操作类型
列表命令的时间复杂度
内部编码
- ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认
512
个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。 - linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
Redis3.2版本提供了quicklist内部编码,简单地说它是以一个ziplist为节点的linkedlist,它结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现,它的设计原理可以参考Redis的另一个作者Matt Stancliff的博客:https://matt.sh/redis-quicklist。
使用场景
- 消息队列。如图2-21所示,Redis的lpush+brpop命令组合即可实现
阻塞队列
,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。 - 文章列表。每个用户有属于自己的文章列表,现需要
分页
展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。使用列表类型保存和获取文章列表会存在两个问题。第一,如果每次分页获取的文章个数较多,需要执行多次hgetall操作,此时可以考虑使用Pipeline(第3章会介绍)批量获取,或者考虑将文章数据序列化为字符串类型,使用mget批量获取。第二,分页获取文章列表时,lrange命令在列表两端性能较好,但是如果列表较大,获取列表中间范围的元素性能会变差,此时可以考虑将列表做二级拆分,或者使用Redis3.2的quicklist内部编码实现,它结合ziplist和linkedlist的特点,获取列表中间范围的元素时也可以高效完成。
实际上列表的使用场景很多,在选择时可以参考以下口诀:
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpsh+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
set
一个集合最多可以存储 $2^{32}$ -1个元素。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多实际问题。
集合常用命令时间复杂度
内部编码
- intset(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
- hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
使用场景
集合类型比较典型的使用场景是标签(tag)。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。
用户和标签的关系维护应该在一个事务内执行,防止部分命令失败造成的数据不一致,有关如何将两个命令放在一个事务,第3章会介绍事务以及Lua的使用方法。
- sadd=Tagging(标签)
- spop/srandmember=Random item(生成随机数,比如抽奖),什么时候会让redis来生成随机数
- sadd+sinter=Social Graph(社交需求)
zset
有序集合中的元素不能重复,但是score可以重复,就和一个班里的同学学号不能重复,但是考试成绩可以相同。
列表、集合和有序集合三者的异同点
有序集合命令的时间复杂度
内部编码
- ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplistentries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。
- skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。
有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数。本节使用赞数这个维度,记录每天用户上传视频的排行榜。
键管理
type、del、object、exists、expire等
为了防止被强行rename,Redis提供了renamenx命令,确保只有newKey不存在时候才被覆盖,例如下面操作renamenx时,newkey=python已经存在,返回结果是0代表没有完成重命名,所以键java和python的值没变。
- 由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性,这点不要忽视。
- 如果rename和renamenx中的key和newkey如果是相同的,在Redis3.2和之前版本返回结果略有不同。Redis3.2中会返回OK。Redis3.2之前的版本会提示错误((error) ERR source and destination objects are the same)。
迁移键
Redis发展历程中提供了move、dump+restore、migrate三组迁移键的方法,它们的实现方式以及使用的场景不太相同,下面分别介绍。
move命令用于在Redis内部进行数据迁移,Redis内部可以有多个数据库,彼此在数据上是相互隔离的。但多数据库功能不建议在生产环境使用。
dump+restore可以实现在不同的Redis实例之间进行数据迁移的功能,整个迁移的过程分为两步:
1)在源Redis上,dump命令会将键值序列化,格式采用的是RDB格式。
2)在目标Redis上,restore命令将上面序列化的值进行复原,其中ttl参数代表过期时间,如果ttl=0代表没有过期时间。
有关dump+restore有两点需要注意:第一,整个迁移过程并非原子性的,而是通过客户端分步完成的。第二,迁移过程是开启了两个客户端连接,所以dump的结果不是在源Redis和目标Redis之间进行传输,下面用一个例子演示完整过程。
那跟读了之后直接set的区别是什么?会保留数据的编码之类的?不会重新编码?因为用的是序列化的形式?
整个过程如图2-28所示,实现过程和dump+restore基本类似,但是有3点不太相同:第一,整个过程是原子执行的,不需要在多个Redis实例上开启客户端的,只需要在源Redis上执行migrate命令即可。第二,migrate命令的数据传输直接在源Redis和目标Redis上完成的。第三,目标Redis完成restore后会发送OK给源Redis,源Redis接收后会根据migrate对应的选项来决定是否在源Redis上删除对应的键。
migrate命令在Redis实例之间原子性的迁移数据
move、dump+restore、migrate三个命令比较
渐进式便利scan
渐进式遍历可以有效的解决keys命令可能产生的阻塞问题,但是scan并非完美无瑕,如果在scan的过程中如果有键的变化(增加、删除、修改),那么遍历效果可能会碰到如下问题:新增的键可能没有遍历到,遍历出了重复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键,这些是我们在开发时需要考虑的。
数据库管理
每个数据库不能起名字,以数字标识。
不再能使用多个数据库的原因:
- Redis是单线程的。如果使用多个数据库,那么这些数据库仍然是使用一个CPU,彼此之间还是会受到影响的。
- 多数据库的使用方式,会让调试和运维不同业务的数据库变的困难,假如有一个慢查询存在,依然会影响其他数据库,这样会使得别的业务方定位问题非常的困难。
- 部分Redis的客户端根本就不支持这种方式。即使支持,在开发的时候来回切换数字形式的数据库,很容易弄乱。
常用的功能
- 慢查询分析:通过慢查询分析,找到有问题的命令进行优化。
- Redis Shell:功能强大的Redis Shell会有意想不到的实用功能。
- Pipeline:通过Pipeline(管道或者流水线)机制有效提高客户端性能。redis cluster不支持pipeline
- 事务与Lua:制作自己的专属原子命令。
- Bitmaps:通过在字符串数据结构上使用位操作,有效节省内存,为开发提供新的思路。
- HyperLogLog:一种基于概率的新算法,难以想象地节省内存空间。
- 发布订阅:基于发布订阅模式的消息通信机制。
- GEO:Redis3.2提供了基于地理位置信息的功能。
慢查询分析
慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阀值,就将这条命令的相关信息(例如:发生时间,耗时,命令的详细信息)记录下来。
需要注意,redis的慢查询只统计命令执行
的时间(不包括命令排队
等),所以没有慢查询并不代表客户端没有超时问题。
slowlog-log-slower-than就是那个预设阀值,它的单位是微秒(1秒=1000毫秒=1000000微秒),默认值是10000。
如果slowlog-log-slower-than=0
会记录所有的命令,slowlog-log-slowerthan<0
对于任何命令都不会进行记录。
实际上Redis使用了一个列表来存储慢查询日志,slowlog-max-len就是列表的最大长度( $2^{32}$ -1),当慢查询日志列表已处于其最大长度时,最早插入的一个命令将从列表中移出。
在Redis中有两种修改配置的方法,一种是修改配置文件,另一种是使用config set命令动态修改。
如果要Redis将配置持久化到本地配置文件,需要执行config rewrite命令。
分别是慢查询日志的标识id、发生时间戳、命令耗时、执行命令和参数。
代码详情
1 | 127.0.0.1:6379> slowlog get |
最佳实践
- slowlog-max-len配置建议:线上建议调大慢查询列表,记录慢查询时Redis会对长命令做截断操作,并不会占用大量内存。增大慢查询列表可以减缓慢查询被剔除的可能,例如线上可设置为1000以上。
- slowlog-log-slower-than配置建议:默认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值。由于Redis采用单线程响应命令,对于高流量的场景,如果命令执行时间在1毫秒以上,那么Redis最多可支撑QPS不到1000。因此对于高QPS场景的Redis建议设置为1毫秒。
- 因为命令执行排队机制,慢查询会导致其他命令级联阻塞,因此当客户端出现请求超时,需要检查该时间点是否有对应的慢查询,从而分析出是否为慢查询导致的命令级联阻塞。
定期执行slow get命令将慢查询日志持久化到其他存储中(例如MySQL),然后可以制作可视化界面进行查询,第13章介绍的Redis私有云CacheCloud。
Redis Shell
Redis提供了redis-cli、redis-server、redis-benchmark等Shell工具。
redis-cli
–slave选项是把当前客户端模拟成当前Redis节点的从节点,合理的利用这个选项可以记录当前连接Redis节点的一些更新操作,这些更新操作很可能是实际开发业务时需要的数据。
–pipe选项用于将命令封装成Redis通信协议定义的数据格式,批量发送给Redis执行,有关Redis通信协议将在第4章进行详细介绍。
–bigkeys选项使用scan命令对Redis的键进行采样,从中找到内存占用比较大的键值,这些键可能是系统的瓶颈。
–eval选项用于执行指定Lua脚本,有关Lua脚本的使用将在3.4节介绍。
–latency该选项可以测试客户端到目标Redis的网络延迟,例如当前拓扑结构如图3-4所示。客户端B和Redis在机房B,客户端A在机房A,机房A和机房B是跨地区的。
redis-server
redis-server –test-memory可以用来检测当前操作系统能否稳定地分配指定容量的内存给Redis。
redis-benchmark
Pipeline
Redis客户端执行一条命令分为如下四个过程:
1)发送命令
2)命令排队
3)命令执行
4)返回结果
其中1)+4)称为Round Trip Time(RTT,往返时间)。
Pipeline(流水线)机制能改善上面这类问题,它能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端。
原生批量命令与Pipeline对比
- 原生批量命令是原子的,Pipeline是非原子的。
- 原生批量命令是一个命令对应多个key,Pipeline支持多个命令。
- 原生批量命令是Redis服务端支持实现的,而Pipeline需要服务端和客户端的共同实现。
最佳实践
Pipeline虽然好用,但是每次Pipeline组装的命令个数不能没有节制,否则一次组装Pipeline数据量过大,一方面会增加客户端的等待时间,另一方面会造成一定的网络阻塞,可以将一次包含大量命令的Pipeline拆分成多次较小的Pipeline来完成。
Pipeline只能操作一个Redis实例,但是即使在分布式Redis场景中,也可以作为批量操作的重要优化手段,具体细节见第11章。
事务与Lua
熟悉关系型数据库的读者应该对事务比较了解,简单地说,事务表示一组动作,要么全部执行,要么全部不执行。例如在社交网站上用户A关注了用户B,那么需要在用户A的关注表中加入用户B,并且在用户B的粉丝表中添加用户A,这两个行为要么全部执行,要么全部不执行,否则会出现数据不一致的情况。
multi命令代表事务开始,exec命令代表事务结束,如果要停止事务的执行,可以使用discard命令代替exec命令即可。
命令错误:错将set写成了sett,属于语法错误,会造成整个事务无法执行
运行时错误:例如用户B在添加粉丝列表时,误把sadd命令写成了zadd命令,这种就是运行时命令。
可以看到Redis并不支持回滚功能,开发人员需要自己修复这类问题。
确保事务中的key没有被其他客户端修改过,才执行事务,否则不执行(类似乐观锁)。Redis提供了watch命令来解决这类问题。
事务中watch命令演示时序
可以看到“客户端-1”在执行multi之前执行了watch命令,“客户端-2”在“客户端-1”执行exec之前修改了key值,造成事务没有执行(exec结果为nil).
Lua用法简述
Lua的官方网站(http://www.lua.org/)
在Redis中使用Lua
在Redis中执行Lua脚本有两种方法:eval和evalsha。
(1)eval
代码详情
1 | 127.0.0.1:6379> eval 'return "hello " .. KEYS[1] .. ARGV[1]' 1 redis world |
如果Lua脚本较长,还可以使用redis-cli --eval
直接执行文件。
(2)evalsha
首先要将Lua脚本加载到Redis服务端,得到该脚本的SHA1校验和,evalsha命令使用SHA1作为参数可以直接执行对应Lua脚本,避免每次发送Lua脚本的开销。这样客户端就不需要每次执行脚本内容,而脚本也会常驻在服务端,脚本功能得到了复用。
加载脚本:redis-cli script load "$(cat lua_get.lua)"
执行脚本:evalsha 7413dc2440db1fea7c0a0bde841fa68eefaf149c 1 redis world
Lua的Redis API
pp212
HyperLogLog
实践经验
为什么按照槽顺序获取连接可以提高Redis Cluster的操作效率?
按照槽顺序获取连接可以提高Redis Cluster的操作效率的原因如下:
减少网络开销:Redis Cluster中的数据分布在多个节点上,每个节点负责管理一部分数据。当执行操作时,需要与对应的节点通信。按照槽顺序获取连接可以最大程度地减少网络跳转次数,避免在不同节点之间频繁切换连接,从而减少网络开销。
提高并行处理能力:按照槽顺序获取连接可以最大程度地利用Redis Cluster的并行处理能力。通过按顺序连接到不同的节点,可以同时在多个节点上执行操作,从而提高整体的处理能力和吞吐量。这对于批量操作尤其重要,可以将操作分散到多个节点上并行执行,加快处理速度。
优化数据访问模式:按照槽顺序获取连接可以更好地利用Redis Cluster的数据分布特性。Redis Cluster使用哈希槽将数据分散存储在不同节点上,相同槽的数据通常具有相关性。按照槽顺序获取连接可以使得相邻的操作更有可能在同一个节点上执行,从而利用节点本地的数据缓存,减少远程访问的需求,提高访问效率。
总的来说,按照槽顺序获取连接可以减少网络开销、提高并行处理能力,并优化数据访问模式,从而提高Redis Cluster的操作效率。这种方式可以最大程度地利用集群的分布式特性,充分发挥Redis Cluster的性能优势。
Redis集群报错:(error) CROSSSLOT Keys in request don’t hash to the same slot 的解决办法
lua脚本的时候会把keys里面的东西当作rediskey
redis带大括号和不带大括号的区别?
- 带大括号在对应的slot执行请求
- 不带大括号执行redis会跑到别的分片上吗
碎片率高
redis容量预估
大key标准
在 Redis 的实践中,并没有统一的标准来确定多少 MB 的 key 被定义为大 key,因为这取决于具体的应用场景和资源限制。然而,为了提供一个参考点,通常:
- 小于 1 KB 的 key 袂被视为问题。
- 1 KB - 1 MB 范围内的 key 可能在大多数应用中表现良好,但如果数量很多,依然可能导致性能问题。
- 大于 1 MB 的 key 通常会被视为大 key,并且可能需要关注,因为它们可能会在命令执行时导致网络延迟增加、内存使用不当或 CPU 使用率上升。
特别是当 key 的大小接近或超过 5 MB 时,通常会认为这是一个不合理的大小,并且可能会影响 Redis 的性能。处理大量这种大小的 key 时,可能会遇到操作延迟或内存问题。
在实践中,可以利用一些工具和脚本来检测大 key,比如使用 redis-cli –bigkeys 来找到和报告数据库中的大 key。此外,一些监控工具如 Redis Enterprise、RedisInsight 或第三方 APM (Application Performance Management) 工具可以帮助发现和分析这些大 key 的影响。
redis分布式锁
Redis分布式锁是一种基于Redis数据库实现的分布式锁。它可以通过互斥地访问共享资源,保证在同一时间内只有一个进程能操作共享资源。
Redis分布式锁的基本实现思路如下:
客户端发送setnx命令(set if not exist),设置一个锁。如果返回1,说明此时没有锁,客户端设置成功,并获得了锁。如果返回0,说明此时已经有锁了,当前客户端设置失败。
如果客户端获得了锁,它可以设置一个过期时间,防止因为某些原因该客户端崩溃,导致其他客户端无法获得锁。
客户端操作完共享资源后,可以发送del命令,删除自己之前设置的锁。这样其他等待的客户端就能获得锁。
Redis分布式锁最好是公平的,即尽可能让等待最久的客户端获得锁。
需要注意的是,Redis分布式锁并不是完全安全的,可能存在因为网络分区等问题导致多个客户端同时获得锁的情况。为了解决这种问题,Redis提供了Redlock算法来保证在分布式环境中,Redis锁的安全性。