网站内链建设不可忽视的地方,淘宝网站的建设,福州网站建设服务,wordpress主题学习教程Java并发编程的总结和学习算是告一段落了#xff0c;这段时间思来想去#xff0c;还是决定把Redis再巩固和学习一下。毕竟Redis不论是在面试还是实际应用中都是极其重要的#xff0c;在面试中诸如Redis的缓存问题、热key、大key、过期策略、持久化机制等#xff1b;还有在实…Java并发编程的总结和学习算是告一段落了这段时间思来想去还是决定把Redis再巩固和学习一下。毕竟Redis不论是在面试还是实际应用中都是极其重要的在面试中诸如Redis的缓存问题、热key、大key、过期策略、持久化机制等还有在实际应用中的Redis缓存、分布式锁、Reids实现排行榜、分布式限流功能、Redis做延迟队列、消息队列、发布订阅等。相信大家对这并不陌生而作者想要做的就是在其基础上把它们进行总结、整理、扩展并深入。
一、为什么要学Redis 目前大多数系统都是集群部署那么在用传统的锁和缓存时就会出现以下问题 缓存失效在分布式集群中每个节点之间的数据不是共享的而本地缓存是本地的因此在一个节点上的本地缓存可能会缓存旧数据而在另一个节点上的数据已经更新。这就会导致数据不一致性问题从而影响系统的正确性。互斥锁问题传统的锁Synchronized、Lock是基于但服务实现的多个服务之间无法共享锁的状态简单来说就是锁失效那么也会出现数据不一致等问题。 可以通过Redis的分布式缓存和分布式锁来解决上述问题。 还有就是有的功能使用关系型数据库来解决可能很复杂而用Redis这种非关系型数据库实现就比较简单比如聊天室功能 如果用关系型数据库一般都需要使用JOIN来进行多表查询而在线的聊天室通常是非常频繁的读写操作JOIN多表查询再加上频繁读写不仅实现起来比较复杂性能也很差。如果用Redis那就可以非常容易实现而且性能也远超关系型数据库。比如用Redis的List或Hash数据结构将聊天室中的所有消息按照时间戳存储在一个列表中用户发送的消息可以通过Redis的集合或队列存储再使用Redis的发布订阅功能就可以实现消息的广播和接收。 二、什么是Redis
RedisRemote Dictionary Server即远程字段服务是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库并提供多种语言的API。 当时Redis的作者——Antirez在工作中遇到了一个实时统计在线游戏数据的需求起初他用Mysql数据库来解决然而在高并发下实时统计功能在性能上表现很差。于是在2009年开始了Redis的开发工作之后便有了这个功能强大、高性能、可扩展、易于使用的存储系统。 ps这就是大佬思维我们再遇到问题时第一想法是网上搜而大佬的思维是自己开发一个膜拜。 Redis最重要的功能是做缓存查询效率远高于Mysql数据库。Redis是基于内存存储数据的读写性能贼高。Redis 4.0之前读的速度是110000次/S写的速度是81000次/SRedis6.0引入多线程后性能几乎翻倍读的速度可达200000次/s写的速度可达170000次/S。Redis不仅仅是Key-Value存储结构针对Value还有多种数据结构。Redis也提供了相应的持久化方案RDB、AOF。Redis提供了主从复制、哨兵模式和Cluster等方案实现高可用。Redis提供了强大的功能比如事务、发布订阅、Lua脚本等。 优点还有很多不一一列举了.... Reids是一种非关系型数据库NoSql此外常见的还有以下几种 key-value存储将数据以键值对的形式存储常用于缓存比如Reids、MemCache等。文档型存储以类似于JSON或XML格式的文档形式存储适用于存储和查询复杂的、半结构化的数据比如Elasticsearch、MongDB等。面向列存储将数据按列存储而不是按行适用于大规模数据处理和分析比如HBase、Apache Cassandra等。图形化存储以图的形式存储数据适用于需要处理复杂关系和图算法的常见比如Neo4j、OrientDB等。 三、Redis为什么要自己定义SDS
从这开始就正式介绍Redis了。我们知道Redis是C语言实现的但他并没有直接使用C语言中的字符数组的方式来实现字符串而是自己构建了一种名为简单动态字符串simple dynamic stringSDS并将SDS用作Redis的默认字符串表示这是为什么呢
我们先回顾下C语言中字符串的实现。C语言使用长度N1的字符串数组来表示长度未N的字符串并且字符串数组的最后一个元素总是为空字符\0。
那么这就会产生两个问题 首先它就不能保存任意内容了至少\0就不能保存了因为遇到它时就直接截断了。其次就是C语言中用\0来表示字符串结束的方式所以计算长度、字符串追加等操作都需要从头遍历直到遇到\0才会返回长度或追加那么它的性能就不是很高了。 因此Redis自己定义了一个SDS来解决以上两个问题 在用字符串数据表示字符串的同时在这个字符串中增加一个表示分配给该字符串数组的总长度的alloc字段和一个表示字符串现有长度的len字段这样在获取长度的时候就不依赖于\0了直接返回len就行了。在字符串做追加操作时只需要判断新追加的部分的len加上已有的len是否大于alloc如果超过就重新申请新空间反之直接追加就行了。 ps此外SDS还被用作缓冲区bufferAOF模块中的AOF缓冲区客户端状态中的输入缓冲区。
四、Redis的数据类型与数据结构
我们常见的数据类型比如string、hash、list、set、zset等类型其实在Redis中是一个个对象是由简单动态字符串SDS、哈希表、整数集合、压缩列表、跳跃表等数据结构实现的。
Reids中的每个对象都由一个redisObject结构表示该结构中和保存数据有关的有三个属性 type记录对象的类型比如字符串对象string、列表对象list等可用type key命令查看value的存储类型。encoding记录对象使用的编码比如int、embstr等下面会提到。ptrptr指针指向对象底层实现数据结构这些结构则是由encoding决定的。 对应关系如下不是很全但够用 上图为Redis常用的5种基本类型及Reids 5.0版本的stream类型和Redis的9种编码方式和7种底层数据结构对应的关系。下面我们详细的介绍下。
五、九种数据类型
5.1 String类型
5.1.1 简介
String类型是Redis最基本的数据类型它可以存储任意类型的数据比如数字、文本或者是序列化后的对象最大可存储512MB的数据。
底层实现是SDS由长度、空闲空间和字节数组三部分组成并且有3种编码方式
int编码用于存储整型数据是以Long类型存储未使用SDS类型。 embstr编码当存储的字符串长度小于等于32字节用embstr编码。 raw编码当存储的字符串长度大于32字节用raw编码。 5.1.2 应用场景
应用场景还是非常广泛的比如 缓存提高性能降低数据库压力。计数器利用incr和decr命令实现原子性加减操作。分布式锁利用setnx命令。限流利用计数器的功能来实现限流。 5.1.3 常用命令 SET key value设置指定 key 的值为指定的 value。GET key获取指定 key 的值。INCR key将指定 key 的值加 1并返回加 1 后的值。DECR key将指定 key 的值减 1并返回减 1 后的值。APPEND key value将指定 key 的值追加指定的 value。STRLEN key返回指定 key 的值长度。SETEX key seconds value设置指定 key 的值并指定过期时间单位为秒。 5.2 hash类型
5.2.1 简介
hash是一个键值对集合可以存储多个字段和值简单的说它的value就是一个Map集合一个hash最多可以存储2^32-1个字段。
底层实现其实有三种 ziplist压缩列表当ziplist元素超过512个或单个元素超过64字节可在redis的配置文件中设置会转为hashtable。 listpack紧凑列表在Redis7.0之后listpack取代ziplist同样到达上述阈值会转化为hashtable。 hashtable哈希表类似map。 下面我们来看下它们编码转换的情况 5.2.2 应用场景 存储对象或实体属性比如用户信息、商品信息。 存储配置信息比如连接字符串、端口号、默认配置等。 5.2.3 常用命令 HSET设置 hash 中的字段和值。用法HSET key field valueHGET获取指定 hash 字段的值。用法HGET key fieldHMSET同时设置 hash 中多个字段和值。用法HMSET key field1 value1 [field2 value2 …]HMGET获取指定 hash 中多个字段的值。用法HMGET key field1 [field2 …]HGETALL获取 hash 中所有字段和值。用法HGETALL keyHDEL删除 hash 中的一个或多个字段。用法HDEL key field1 [field2 …]HEXISTS检查指定的字段在 hash 中是否存在。用法HEXISTS key fieldHINCRBY将 hash 中指定字段的值增加指定增量。用法HINCRBY key field incrementHINCRBYFLOAT将 hash 中指定字段的浮点数值增加指定增量。用法HINCRBYFLOAT key field incrementHKEYS获取 hash 中所有的字段。用法HKEYS keyHVALS获取 hash 中所有的值。用法HVALS keyHLEN获取 hash 中的字段数量。用法HLEN keyHSCAN迭代遍历 hash 中的字段和值。用法HSCAN key cursor [MATCH pattern] [COUNT count] 5.3 List类型
5.3.1 简介
List是一个存取有序的字符串列表按照插入顺序排序有下标并且支持两端插入或删除元素。一个list的键最多可以存储2^32-1个元素。
底层实现是linkedlist和zipList ziplist当ziplist的结点超过512个或节点内存大于64字节时会转为linkedlist当然这个在Redis配置文件中也可以修改。linkedlist双端链表。 下面介绍一下不同版本的Redis其list的实现 Reids3.2之前list使用linkedlist和ziplist。Reids3.2至Redis7.0list使用的是quicklistlinkedlist和ziplist的结合。Reids7.0之后list使用的也是quicklist不过将ziplist转为listpack其实就是listpack和linkedlist结合。 5.3.2 应用场景 消息队列将消息以先后顺序添加到list中然后可以用lpop命令从列表的左侧弹出消息并处理。实时排行榜将用户的得分或其它评价指标作为list的值在该指标上进行排序。通过lpush、rpush和ltrim命令可以动态地更新排行榜。发布/订阅发布者使用rpush命令将消息推送到列表中订阅者用blpop或brpop命令在列表上进行阻塞弹出接收消息。历史记录将聊天记录、日志信息等存储为list的值使用lpush和lrange命令可以存储和查询最近的几条消息。 5.3.3 常用命令 LPUSH从列表的左侧添加一个或多个元素。用法LPUSH key value1 [value2 …]RPUSH从列表的右侧添加一个或多个元素。用法RPUSH key value1 [value2 …]LPOP从列表的左侧弹出第一个元素。用法LPOP keyRPOP从列表的右侧弹出最后一个元素。用法RPOP keyLRANGE获取列表中指定范围的元素。用法LRANGE key start stopLINDEX获取列表中指定索引位置的元素。用法LINDEX key indexLLEN获取列表的长度即元素数量。用法LLEN keyLTRIM修剪列表只保留指定范围内的元素其余元素删除。用法LTRIM key start stopLINSERT在列表中指定元素的前面或后面插入一个新元素。用法LINSERT key BEFORE|AFTER pivot valueLREM从列表中删除指定数量的匹配元素。用法LREM key count value 5.4 set类型
5.4.1 简介
set是一个无序的字符串集合不允许重复没有下标一个set类型的键最多可以存储2^32-1个元素。
底层实现为intset和hashtable intset当使用intset进行存储时redis会自动的进行递增排序因此只存整型的话其是一个有顺序的结构但是并非只存整型数据就一直用intset当整型的元素个数超过512个元素时会转为hashtable。当存的是非整形时也会转为hashtable进行存储。 hashtable与hash类型的哈希表相同将元素存储在一个数组中并通过哈希函数计算元素在数组中的索引。 5.4.2 应用场景 去重利用sadd和scard命令实现元素的去重并计数。粉丝和关注系统使用两个set分别存储用户的关注者和粉丝使用sadd命令和srem命令来添加和移除关注使用sinter和sunion命令可以计算共同关注和推荐关注。抽奖将用户存储在set中使用sadd和srem命令增加和删除参与资格通过srandmember命令从set中随机选择一个或多个参与者。 5.4.3 常用命令 SADD向 set 中添加一个或多个元素。用法SADD key member1 [member2 …]SREM从 set 中删除一个或多个元素。用法SREM key member1 [member2 …]SMEMBERS获取 set 中的所有元素。用法SMEMBERS keySISMEMBER检查元素是否在 set 中。用法SISMEMBER key memberSUNION获取多个 set 的并集。用法SUNION key1 [key2 …]SINTER获取多个 set 的交集。用法SINTER key1 [key2 …]SDIFF获取两个 set 的差集第一个 set 中有第二个 set 中没有的元素。用法SDIFF key1 key2SCARD获取 set 的元素数量即集合的基数。用法SCARD keySPOP从 set 中随机弹出一个元素。用法SPOP keySRANDMEMBER从 set 中随机获取一个或多个元素。用法SRANDMEMBER key [count] 5.5 zset类型
5.5.1 简介
zset数据类型存取有序、不允许重复、有下标并且给每个元素赋予了一个排序权重值score。Redis通过权重值来给集合中的元素进行从小到大排序权重值可以重复。一个zset类型的键最多可以存储2^32-1个元素。
其底层存储结构也用了两种ziplist和skiplist也有切换关系 ziplistziplist存储元素超过128个或内存超过64字节会转为skiplist。redis7.0之前是ziplist之后为listpack。skiplist跳跃表之后详解。 ps这让我想起了之前面试官问zset类型底层是什么当时大言不惭的说跳跃表的场景当时对此深信不疑。
5.5.2 应用场景 排行榜将用户的得分、浏览量、商品销量等排序信息存储在有序集合中使用zadd命令添加元素使用zrange或zrevrange命令获取排行榜的前几名或全部成员使用zrank或zrevrank命令获取指定成员在排行榜中的排名。延迟队列将任务及其执行时间存储在 zset 中使用任务执行时间作为元素的分值可以使用 zadd 命令添加任务和执行时间使用 zrange 和 zrem 命令按执行时间获取任务。区间查询通过 zrangebyscore 或者 zrevrangebyscore 命令根据分值范围获取元素。 5.5.3 常用命令 ZADD向 zset 中添加一个或多个元素以及它们的分值。用法ZADD key score1 member1 [score2 member2 …]ZREM从 zset 中删除一个或多个元素。用法ZREM key member1 [member2 …]ZSCORE获取指定元素的分值。用法ZSCORE key memberZRANK获取指定元素在 zset 中的排名按升序。用法ZRANK key memberZREVRANK获取指定元素在 zset 中的倒序排名按降序。用法ZREVRANK key memberZRANGE按升序获取 zset 中的一定范围元素。用法ZRANGE key start stop [WITHSCORES]ZREVRANGE按降序获取 zset 中的一定范围元素。用法ZREVRANGE key start stop [WITHSCORES]ZCOUNT统计 zset 中指定分值范围内的元素数量。用法ZCOUNT key min maxZINCRBY增减指定元素的分值。用法ZINCRBY key increment memberZCARD获取 zset 中元素的数量即集合的基数。用法ZCARD key 5.6 stream类型
5.6.1 简介
stream是Redis5.0新加的一个数据类型通常被视为一个日志或消息队列它是一个由多个键值对组成的可持久化、有序、可重复的数据流。一个stream类型的键最多可以存储2^64-1个键值对。
底层实现是rax tree基数树和listpackrax是一种压缩的前缀树结构消息ID是作为rax中的key消息具体数据是使用listpack保存并作为value和消息ID一起保存到rax tree中。
5.6.2 应用场景 消息队列生产者可以使用XADD命令将消息添加到stream中消费者可以使用XREAD或XREADGROUP命令消费消息。实时日志每个日志视为一个消息可以根据时间戳、唯一的消息ID、消息内容等属性进行查询和筛选。通过XADD命令添加日志XREAD或XREADGROUP命令获取日志。事件流处理例如用户活动流、系统监控事件等。事件流中的每个事件被视为一个消息可以根据事件类型、时间戳、事件属性等条件进行查询和分析。 5.6.3 常用命令 XADD向 Stream 中添加一条消息。用法XADD key [MAXLEN [~|~count] [LIMIT count]] * field1 value1 [field2 value2 …] 示例XADD mystream * name Alice age 25XLEN获取 Stream 中的消息数量。用法XLEN key 示例XLEN mystreamXRANGE按照 ID 范围获取 Stream 中的消息列表。用法XRANGE key start end [COUNT count] 示例XRANGE mystream 0-0 10XREAD读取 Stream 中的消息。用法XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key …] id [id …] 示例XREAD COUNT 10 STREAMS mystream 0XDEL删除 Stream 中的消息。用法XDEL key id [id …] 示例XDEL mystream 1578854095166-0 1578854095166-1XGROUP CREATE创建消费者组。用法XGROUP CREATE key groupname id_or_$ [MKSTREAM] 示例XGROUP CREATE mystream mygroup $XGROUP SETID设置消费者组的消费位置。用法XGROUP SETID key groupname id_or_$ 示例XGROUP SETID mystream mygroup 0-0XREADGROUP消费者组读取 Stream 中的消息。用法XREADGROUP GROUP groupname consumerkey [COUNT count] [BLOCK milliseconds] STREAMS key [key …] id [id …] 示例XREADGROUP GROUP mygroup consumer1 COUNT 10 STREAMS mystream XACK消费者确认已消费的消息。用法XACK key groupname id [id …] 示例XACK mystream mygroup 1578854095166-0 1578854095166-1XCLAIM消费者从待处理列表中获取消息。用法XCLAIM key groupname consumer min-idle-time ID [ID …] [IDLE ms] [TIME ms-unix-time] [RETRYCOUNT count] [FORCE] 示例XCLAIM mystream mygroup consumer1 60000 1578854095166-0 5.7 Hyperloglog类型
5.7.1 简介
Hyperloglog是一种概率数据结构算法的最本源则是伯努利过程。用在恒定的内存大小下估计集合的计数不同元素的个数以及对多个集合进行并、交运算等。优点是可以使用极少的内存空间同时可以保证较高的准确性。每个Hyperloglog键秩序要花费12KB内存便可计算接近2^64个不同元素的基数。
底层实现是HLL_DENSE(稠密矩阵)和HLL_SPARSE稀疏矩阵 稀疏矩阵计数较少时使用。 稠密矩阵计数增多超过阈值后会转为稠密矩阵。 5.7.2 应用场景 计算网站的UVunique visitor数量通过记录用户请求的 IP 地址或浏览器 cookie 等标识符使用 HyperLogLog 可以快速估计出网站的独立访客数。统计在线用户数量通过记录用户登录名、客户端ID等信息使用 HyperLogLog 可以快速估计在一段时间内在线用户的数量。统计数据库中某字段的不同取值数量通过记录字段值使用 HyperLogLog 可以估算不同取值的数量例如估算某个表中年龄、地区等字段的不同取值数量。 5.7.3 常用命令 PFADD向 HyperLogLog 中添加一个或多个元素。用法PFADD key element [element …] 示例PFADD myloglog user1 user2 user3PFCOUNT获取 HyperLogLog 的基数估计值。用法PFCOUNT key [key …] 示例PFCOUNT myloglogPFMERGE将多个 HyperLogLog 合并为一个。用法PFMERGE destkey sourcekey [sourcekey …] 示例PFMERGE mergedloglog myloglog1 myloglog2PEXPIRE设置 HyperLogLog 的过期时间。用法PEXPIRE key milliseconds 示例PEXPIRE myloglog 60000PTTL获取 HyperLogLog 的剩余过期时间。用法PTTL key 示例PTTL myloglogPERSIST移除 HyperLogLog 的过期时间。用法PERSIST key 示例PERSIST myloglog 5.8 GEO类型
5.8.1 简介
GEO地理位置是一个键值对集合其中每个元素都包含一个经度和纬度可以用于存储地理位置信息并支持基于位置的搜索。
它是基于zset数据类型实现的利用geohash算法将经纬度编码为二进制字符串并作为zset的score值。在使用GEORADIUS和GEORADIUSBYMEMBER命令搜索元素时Redis会构建一个跳跃表以实现高效的搜索。
5.8.2 应用场景 附近的人/商家搜索通过将用户/商家的地理位置坐标存储在 Redis 的 GEO 数据结构中可以根据用户当前的地理位置快速查询附近的人或商家实现定位服务和位置搜索功能。地点推荐通过存储地点的坐标和属性可以根据用户当前的地理位置快速推荐附近的景点、餐厅、酒店等地点并按距离排序。打车/配送系统可以使用 GEO 数据类型来存储司机的位置和乘客的位置以便快速匹配附近的司机和乘客并计算两者之间的距离。热点地理位置统计通过记录用户地理位置的访问次数可以统计热门地点用于展示热门景点、餐厅等信息。 5.8.3 常用命令 PFADD向 HyperLogLog 中添加元素。用法PFADD key element [element …] 示例PFADD myloglog a b cPFCOUNT获取 HyperLogLog 的近似基数唯一元素数量。用法PFCOUNT key [key …] 示例PFCOUNT myloglogPFMERGE将多个 HyperLogLog 合并为一个 HyperLogLog。用法PFMERGE destkey sourcekey [sourcekey …] 示例PFMERGE merged myloglog1 myloglog2 myloglog3PEXPIRE设置 HyperLogLog 的过期时间。用法PEXPIRE key milliseconds 示例PEXPIRE myloglog 60000PTTL获取 HyperLogLog 的剩余过期时间。用法PTTL key 示例PTTL myloglogPERSIST移除 HyperLogLog 的过期时间。用法PERSIST key 示例PERSIST myloglog 5.9 bitmap类型
5.9.1简介
bitmap是一种紧凑的数据结构可以用于表示一个只有0和1的数组。位图可以用于高效地存储大规模的布尔值以及进行位运算、位图图形化等操作。一个bitmap最多可以存储2^32-1个二进制位。
底层使用了一种“压缩位图”的数据结构。通过使用两个数组来存储位图数据一个存储实际位的值另一个存储每个字节中1的个数。这种方式可大大压缩位图数据的大小。
5.9.2 应用场景 统计在线用户与统计用户活跃类似可以使用 Bitmap 来实现对在线用户的统计。比如将一个 key 对应的字符串的每个比特位表示一个用户当某个用户在线时将对应的比特位置为 1。此时使用 BITCOUNT 命令来计算在线的用户数。Bloom Filter利用 SETBIT 和 GETBIT命令实现快速判断一个元素是否存在于一个集合中。统计用户访问情况可以利用 Bitmap 记录用户的访问情况如记录用户是否已浏览一篇文章。可以使用 SETBIT 命令为特定文章的每个用户设置一个比特位并在用户浏览过该文章时将其对应的比特位设置为 1。这些比特位的数据将存储在同一个键值下以记住哪个用户看过哪些文章。实现位图索引利用 bitop 和 bitpos 命令实现对多个条件进行位运算和定位 5.9.3 常用命令 SETBIT设置位图在指定偏移量的值。用法SETBIT key offset value 示例SETBIT mybitmap 0 1GETBIT获取位图在指定偏移量的值。用法GETBIT key offset 示例GETBIT mybitmap 0BITCOUNT计算位图中值为1的位的数量。用法BITCOUNT key [start end] 示例BITCOUNT mybitmap 0 10BITOP对多个位图执行位运算操作并将结果保存在指定位图中。用法BITOP operation destkey key [key …] 示例BITOP AND result mybitmap1 mybitmap2 mybitmap3BITPOS查找位图中指定bit0或1第一次出现的偏移量。用法BITPOS key bit [start] [end] 示例BITPOS mybitmap 0 100BITFIELD使用位域操作位图。用法BITFIELD key [GET type offset] [SET type offset value] 示例BITFIELD mybitmap GET u4 0 End希望对大家有所帮助如果有纰漏或者更好的想法请您一定不要吝啬你的赐教。