【Redis】数据结构和内部编码

先来复习一下之前学过的几个基本的全局命令:

  • keys:用来查看匹配规则的key
  • exists:用来判定执行key是否存在
  • del:删除指定的key
  • expire:给key设置过期时间
  • ttl:查询key的过期时间
  • type:查询key对应的value的类型

一、Redis的数据结构

       type命令实际返回的就是当前键的数据结构类型,他们分别是:string(字符串),list(列表),hash(哈希),set(集合),zset(有序集合),但是这些只是Redis对外的数据结构。

       实际上Redis针对每一个数据结构都有自己的底层内部编码实现,而且是多种实现,这样Redis会在合适的场景选择合适的内部编码。

       string类型的raw是最基本的字符串(底层就是持有一个char数组(C++)或者byte数组(Java))【C++里的char是1字节,等价与Java中的byte,而Java中的char是两个字节的】;string类型的int是用来实现“计数”这样的功能,当value就是一个整数的时候,此时可能Redis会直接使用int来保存;embstr是针对短字符串进行的特殊优化。

       hash类型的hashtable是最基本的哈希表;hash类型的ziplist是在哈希表里面的元素比较少的时候可能就优化成ziplist了,压缩列表能够节省空间。

       list类型的linkedlist是链表;list类型的ziplist是压缩列表;从Redis3.2开始,引入了新的实现方式:quicklist,同时兼顾了linkedlist和ziplist的优点,quicklist就是一个链表,每一个元素又是一个ziplist把空间和效率都折中的兼顾到~~~(quicklist比较类似于C++中的std::deque)

       set类型的intset集合中存的都是整数

       zset类型的skiplist是跳表~~~


为什么要压缩??

       Redis上有很多key,可能某些key的value是hash,此时,如果key特别多,对应的hash也特别多,但是每一个hash又不大的情况下,就尽量去压缩,压缩之后就可以让整体占用的内存更小了。

       可以看到每一种数据结构都有至少两种以上的内部编码实现,我们可以通过 object encoding 命令查询内部编码:

object encoding key

Redis这样设计有两种好处:

  1. 可以改进内部编码,而对外的数据结构和命令没有任何影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令,例如Redis3.2提供了quicklist,结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现,而对用户来说基本无感知。
  2. 多种内部编码实现可以在不同场景下发挥各自的优势,例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会下降,这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist,整个过程用户同样无感知。

       Redis会自动根据当前的实际情况选择内部的编码方式,自动适应的,只记思想,不记数字!!类似与这样的“参数”非常常见,都是“可调的”。

二、Redis的单线程架构

       Redis使用了单线程架构来实现高性能的内存数据库服务。Redis只使用一个线程处理所有的命令请求,不是说一个Redis服务器进程内部只有一个线程,其实也有多个线程,多个线程在处理网络IO。

2.1 引出单线程模型

现在开启了三个redis-cli客户端同时执行命令。

客户端1设置一个字符串键值对:
set hello world
客户端2对counter做自增操作:
incr counter
客户端3对counter做自增操作:
incr counter

       我们已经知道了从客户端发送的命令经历:发送命令,执行命令,返回结构三个阶段,其中发送命令最重要。Redis是采用单线程模型执行命令的是指:虽然三个客户端看起来是同时要求Redis去执行命令的,但是微观角度上,这些命令还是采用线性方法去执行的,只是原则上命令的执行顺序是不确定的,但是一定不会有两条命令被同步执行,可以想象Redis内部只有一个服务窗口,多个客户端按照他们达到的先后顺序被排队在窗口前,依次接受Redis的服务,所以两条incr命令无论执行顺序,结果一定是2,不会发生并发问题。

       Redis能够使用单线程模型很好的工作,原因主要在于Redis的核心业务逻辑,都是短平快的,不太消耗CPU资源,也就不太吃多核~ 

Redis必须要特别小心某一个操作占用时间长,就会阻塞其他命令的执行!!! 

线程安全问题

       在多线程中,针对类似这样的场景:两个线程尝试同时对一个变量进行自增,表面上看是自增两次,实际上可能只自增一次。 

2.2 为什么单线程还能这么快

参照物是数据库(MySQL)

  1. 纯内存访问。Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是Redis达到每秒万级别访问的重要基础。
  2. 非阻塞IO。Redis使用epoll作为I/O多路复用技术的实现,在加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络IO上浪费过多的时间。
  3. 单线程避免了线程切换和竞态产生的消耗。单线程可以简化数据结构和算法的实现,让程序模型更简单;其次多线程避免了在线程竞争同一份共享数据时带来的切换和等待消耗。
  4. Redis的核心功能比数据库的核心功能更简单。数据库对于数据的插入删除查询……都有更复杂的功能支持,这样的功能势必要花费更多的开销。

       虽然单线程给Redis带来很多好处,但是还有一个致命的问题:对于单个命令的执行时间都是有要求的。如果某一个命令执行过长,会导致其他命令全部处于等待队列中,迟迟等不到响应,造成客户端的阻塞,对于Redis这种高性能的服务来说是非常重要的,所以Redis是面向快速执行场景的数据库。

三、String字符串

字符串类型是Redis最基本的数据类型,关于字符串需要特别注意:

  1. 首先Redis中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的,例如列表和集合的元素类型是字符串类型,所以字符串类型能为其他4中数据结构的学习奠定基础
  2. 字符串类型的值实际可以是字符串,包含一般格式的字符串或者类似于JSON、XML格式的字符串;数字可以是整形或者浮点型,甚至是二进制数据,例如图片、音频、视频等。不过一个字符串的最大值不能超过512MB。

       由于Redis内部存储字符串完全是按照二进制流的形式保存的,所以Redis是不处理字符集编码问题的,客户端传入的命令中使用的是什么字符集编码,就存储什么字符集编码。 (MySQL的默认字符集是拉丁文,插入中文就会失败~) 

3.1 常见命令 

       FLUSHALL可以把Redis上所有的键值对都带走,以后在公司,尤其是生产环境的数据库中,千万不敢敲。 

3.1.1 SET

       将string类型的value设置到key中。如果key之前存在,则覆盖;无论原来的数据类型是什么。之前关于此key的TTL也全部失效。语法如下:

SET key value [expiration EX seconds|PX milliseconds] [NX|XX]

Redis文档给出的语法格式说明:

       [ ] 相当于一个独立的单元,表示可选项(可有可无的),其中 | 表示“或者”的意思,多个只能出现一个。[ ] 和 [ ] 之间是可以互相同时存在的。 

时间复杂度为:O(1)

选项:

SET命令支持多种选项来影响他的行为:

  • EX——使用秒作为单位设置key的过期时间
  • PX——使用毫秒作为单位设置key的过期时间
  • NX——只在key不存在时才进行设置,即如果key之前已经存在,设置不执行
  • XX ——只在key存在时才进行设置,即如果key之前不存在,设置不执行

注意:由于带选项的SET命令可以被SETNX、SETEX、PSETEX等命令代替,所以之后的版本中,Redis可能进行合并。 

返回值:

  • 如果设置成功,返回OK
  • 如果由于SET指定了NX或者XX,但是条件不满足,SET不会执行,并返回(nil) 

3.1.2 GET

       获取key对应的value。如果key不存在,返回nil。如果value的数据类型不是string,会报错。语法如下:

GET key

时间复杂度为:O(1)

返回值:key对应的value,或者nil当key不存在 

3.1.3 MGET

       一次性获取多个key的值。如果对应的key不存在或者对应的数据类型不是string,返回nil。语法如下:

MGET key [key ...]

时间复杂度为:O(N)N是key的数量

返回值:对应value的列表。 

3.1.4 MSET

一次性设置多个key的值。语法如下:

MSET key value [key value ...]

时间复杂度为:O(N)N是key的数量

返回值:永远是OK

       使用mget/mset由于可以有效地减少了网络时间,所以性能相较于更高。假设网络耗时1毫秒,命令执行时间耗时0.1毫秒。学会使用批量操作,可以有效提高业务处理效率,但是要注意,每次批量操作所发送的键的数量也不是无节制的,否则可能造成单一命令执行时间过长,导致Redis阻塞。

3.1.5 SETNX

设置key-value,但是只允许在key之前不存在的情况下。语法如下:

SETNX key value

时间复杂度为:O(1)

返回值:1表示设置成功。0表示没有设置。 

3.1.6 SETEX和PSETEX

设置key的过期时间,单位是秒和毫秒。

3.2 计数命令

INCR

       将key对应的string表示的数字加一。如果key不存在,则视为key对应的value是0。如果key对应的string不是一个整形或者范围超出了64位有符号整形,则报错。语法如下:

INCR key

时间复杂度为:O(1)

返回值:integer类型的加完后的数值。 

INCRBY

       将key对应的string表示的数字加上对应的值。如果key不存在,则视为key对应的value是0。如果key对应的string不是一个整形或者范围超过了64位有符号整形,则报错。语法如下:

INCRBY key decrement

时间复杂度为:O(1)

返回值:integer类型的加完后的数值。 

DECR

DECRBY

INCRBYFLOAT

       将key对应的string表示的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果key不存在,则视为key对应的value是0,。如果key对应的不是string,或者不是一个浮点数,则报错。允许采用科学计数法表示浮点数。语法如下:

INCRBYFLOAT key increment

时间复杂度为:O(1)

返回值:加/减完后的数值。


       很多存储系统和编程语言内部使用CAS机制实现计数功能,会有一定的CPU开销,但是在Redis中完全不存在这个问题,因为Redis是单线程架构,任何命令到了Redis服务端都要顺序执行。 

3.3 其他命令

APPEND

       如果key已经存在并且是一个string,命令会将value追加到原有string的后边。如果key不存在,则效果等同于SET命令。语法如下:

APPEND KEY VALUE

时间复杂度为:O(1),追加的字符串一般长度较短,可以视为O(1)

返回值:追加完成之后string的长度。 

       在启动Redis客户端的时候,加上一个 --raw这样的选项,就可以使Redis客户端能够自动把二进制数据尝试翻译。操作linux的时候,千万注意不要乱按 crtl + s

ctrl + s 在XShell中的作用是“冻结当前画面”

ctrl + q 解除冻结 

GETRANGE

       返回key对应的string子串,由start和end确定(左闭右闭)。可以使用负数表示倒数。-1表示倒数第一个字符,-2表示倒数第二个字符,其他的与此类似。超过范围的偏移量会根据string的长度调整成正确的值。语法如下:

GETRANGE key start end

时间复杂度为:O(N),N为[start,end]区间的长度,由于string通常比较短,可以视为是O(1)

返回值:string类型的子串。 

SETRANGE

覆盖字符串的一部分,从指定的偏移开始。语法如下:

SETRANGE key offset value

时间复杂度为:O(N),N为value的长度,由于一般给的value比较短,通常视为O(1)

返回值:替换后的string的长度。 

STRLEN

获取key对应的string的长度。当key存放的类似不是string时,报错。语法如下:

STRLEN key

时间复杂度为:O(1)

返回值:string的长度。或者当key不存在时,返回0

单位是字节。

       C++中,字符串的长度本身就是用字节为单位;Java中,字符串的长度则是以字符为单位。MySQL的时候,varchar(N)此处的N的单位就是字符,MySQL中的字符也是完整的汉字,这样的一个字符,也可能是多个字节。

       Java中的一个char == 2字节,Java中的char基于unicode这样的编码方式能够表示中文等符号~~Java中的char是用的unicode,一个汉字使用两个字节,Java中的String则是使用的utf8,一个汉字就是3个字节了,Java的标准库内部,在进行上述的操作过程中,程序员一般是感知不到编码方式的变换的。

3.4 内部编码

字符串类型的内部编码有3种:

  • int:8个字节的长整型
  • embstr:小于等于39个字节的字符串
  • raw:大于39个字节的字符串

Redis会根据当前值的类型和长度动态决定使用哪种内部编码实现的。

       Redis存储小数,本质上还是当做字符串来存储的,这就和整数相比差距很大了,整数直接使用int来存(准确的说是一个long long)比较方便进行算法运算,小数则是使用字符串来存储,意味着每次进行算术运算,都需要把字符串转成小数,进行运算,结果再转为字符串保存。

3.5 典型使用场景

缓存功能

       下图是比较典型的缓存使用场景,其中Redis作为缓冲层,MySQL作为存储层,绝大多数请求的数据都是从Redis中获取的。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。

计数功能

       许多应用都会使用Redis作为计数的基础工具,他可以实现快速计数,查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。如图所示:例如视频网站的视频播放次数可以使用Redis来完成,用户每播放一次视频,响应的视频播放数就会自增1。

       实际上要开发一个成熟,稳定的真实计数系统,要面临的挑战远不止如此简单:防作弊、按照不同维度计数,避免单点问题,数据持久化到底层数据源等。 

共享会话(Session)

       一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自的服务器中,但这样会造成一个问题:处于负载均衡的考虑,分布式服务会将用户的反问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,这样当用户刷新一次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。

       为了解决这个问题,可以使用Redis将用户的Session信息进行集中管理,在这种模式下,只要保证Redis是高可用和可扩展性的,无论用户被均衡到哪一台Web服务器上,都集中从Redis中查询,更新Session信息。

手机验证码

       很多应用处于安全考虑,会在每次进行登录时,让用户输入手机号并且配合给手机号发送验证码,然后让用户再次输入收到的验证码并进行验证,从而确定是否是用户本人。

四、Hash哈希

       几乎所有的主流编程语言都提供了哈希类型,他们的叫法可能是哈希,字典,关联数组,映射。在Redis中,哈希类型是指值本身又是一个键值对结构,形如key = "key",value = { {} }。

       哈希类型中的映射关系通常为field-value,用于区分Redis整体的键值对(key-value),注意这里的value是指field对应的值,不是键(key)对应的值,请注意value在不同上下文的作用。

4.1 基本命令

HSET

设置hash中指定的字段(field)的值(value).语法如下:

HGET

HEXISTS

HDEL

HKEYS

HVALS

HGETALL

HMSET

HSCAN

HLEN

HSETNX

HINCRBY

HINCRBYFLOAT

4.2 内部编码

哈希的内部编码有两种:

  • ziplist(压缩列表):当哈希类型元素个数小于 hash-max-ziplist-entries 配置(默认512个),同时所有值都小于 hash-max-ziplist-value 配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更优秀
  • hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)

压缩:

rar,zip,gzip,7z等一些具体的压缩算法。

       压缩的本质是针对数据进行重新编码,不同的数据有不同的特点,结合这些特点,进行精妙的设计,重写编码之后,就能缩小体积~~

       ziplist也是精心设计的,目的是节省内存空间,但是进行读写元素时,速度是比较慢的。如果元素个数少,慢的并不明显;如果元素个数太多了,慢就会雪上加霜。 

4.3 使用场景

缓存功能

存储结构化的数据使用hash类型更合适一些~~~

4.4 缓存方式对比

截止目前为止,我们已经有三种方式缓存用户信息,下面给出三种方案的实现方法和优缺点分析:

原生字符串类型——使用字符串类型,每一个属性对应一个键

set user:1:name James
set user:1:age 23
set user:1:city Beijing
  • 优点:实现简单,针对个别属性变更也很灵活
  • 缺点:占用过多的键,内存占用量较大,同时用户信息在Redis中比较分散,缺少内聚性,所以这种方案没有实用性。 

序列化字符串类型,例如JSON格式

set user:1 经过序列化后的⽤⼾对象字符串
  • 优点:针对总是以整体作为操作的信息比较合适,编程也简单。同时,如果序列化方案选择合适,内存的使用效率很高。
  • 缺点: 本身序列化和反序列需要一定开销,同时如果总是操作个别属性则非常不灵活。

哈希类型

hmset user:1 name James age 23 city Beijing
  • 优点:简单,直观,灵活。尤其是针对信息的局部变更或者获取操作。
  • 缺点:需要控制哈希在ziplist和hashtable两种内部编码的转换,可能会造成内存的较大消耗。

五、list列表

       列表类型是用来存储多个有序的字符串,列表中的每一个字符串称为元素,一个列表最多可以存储 2^32 - 1 个元素。在Redis中,可以对列表两端插入和弹出,还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,他可以充当栈和队列的角色,在实际开发中有很多应用场景。

列表类型的特点:

  1. 列表中的元素是有序的,这个有序不是说这个列表中存储的元素是有序的,而是说,不同顺序的列表不同,即使他们的元素是相同的。
  2. 区分获取和删除的区别,删除元素的话,会将这个列表的长度减少;但是执行 lindex 4 只会获取元素,但是列表长度是不会变化的。 
  3. 列表中的元素是允许重复的。

       列表(List)相当于数组或者顺序表,注意,list内部的结构(编码方式)并非是一个简单的数组,而是更接近于“双端队列”(deque)。因为当前的List,头和为都能高效的插入和删除元素,就可以把这个List当做一个栈或者队列来使用。

       Redis有一个典型的应用场景,就是作为消息队列,最早的时候,就是通过List类型~~,后来,Redis又提供了一个stream类型。

5.1 基本命令

LPUSH

将一个或者多个元素从左侧放入(头插)到list中,语法如下:

LPUSH key element [element ...]

时间复杂度:只插入一个元素为O(1),插入多个元素为O(1),N为插入元素的个数。

返回值:插入后list的长度。

LPUSHX

在key存在时,将一个或者多个元素从左侧放入(头插)到list中。不存在,直接返回。语法如下:

LPUSHX key element [element ...]

时间复杂度:只插入一个元素为O(1),插入多个元素为O(N),N为插入的元素个数。

返回值:插入后的list的长度。 

RPUSH

RPUSHX

LRANGE

获取从start到stop区间的所有元素,左闭右闭。语法如下:

LRANGE key start stop

时间复杂度为:O(N)

返回值:指定区间的元素 

LPOP

从list左侧取出元素(即头删)。语法如下:

LPOP key

时间复杂度为:O(1)

返回值:取出的元素或者nil 

RPOP

LINDEX

获取从左边第index位置的元素。语法如下:

LINDEX key index

时间复杂度为:O(N)

返回值:取出的元素或者nil 

LINSERT

在特定位置插入元素,语法如下:

LINSERT key <BEFORE | AFTER> pivot element

时间复杂度:O(N)

返回值:插入后的list长度 

LLEN

获取list长度,语法如下:

LLEN key

时间复杂度:O(1)

返回值:list的长度 

5.2 阻塞版本命令

blpop和brpop是lpop和rpop的阻塞版本,和对应非阻塞版本的作用基本一致,除了:

  • 在列表中有元素的情况下,阻塞和非阻塞版本表现是一致的。但是如果列表中没有元素,非阻塞版本会立即返回nil,但是阻塞版本会根据timeout,阻塞一段时间,期间Redis可以执行其他命令,但是要求执行该命令的客户端会表现为阻塞状态。
  • 命令中如果设置了多个键,那么会从左向右进行遍历键,一旦有一个键对应的列表中可以弹出元素,命令立即返回。
  • 如多个客户端同时多个键执行pop,则最先执行命令的客户端会得到弹出的元素。

BLPOP

LPOP的阻塞版本,语法如下:

BLPOP key [key ...] timeout

返回值:取出的元素或者nil 

BRPOP 

RPOP的阻塞版本,语法如下:

BRPOP key [key ...] timeout

 返回值:取出的元素或者nil 

5.3 内部编码

       对于现在的列表类型的内部编码已经变为了quicklist,quicklist箱单与链表和压缩列表的结合。整体还是一个链表,链表的每一个节点是一个压缩列表。每一个压缩列表都不让他太大,同时再把多个压缩列表通过链式结构连起来~~

下面介绍的编码已经不在使用了:

列表类型的内部编码有两种:

  • ziplist(压缩列表):当列表的元素个数小于 list-max-ziplist-entries 配置(默认512个),同时列表中每一个元素的长度都小于 list-max-ziplist-value 配置(默认64字节)时,Redis会选用ziplist来作用列表的内部编码实现来减少内存消耗。
  • linkedlist(链表):当列表类型无法满足 ziplist 的条件时,Redis会使用 linkedlist 作为列表的内部实现。

5.4 使用场景

存储多个元素,进行查询

可以使用列表作为“数组”来存储多个元素,Redis提供的查询功能不像MySQL那样强大。

消息列表

       Redis可以使用 lpush + brpop 命令组合实现经典的阻塞式生产者-消费者模型队列,生产者客户端使用 lpush 从列表左侧插入元素,多个消费者客户端使用 brpop 命令阻塞式地从队列中“争抢”队首元素。通过多个客户端来保证消费的负载均衡和高可用性。

       只有一个消费者能“抢到”元素,谁先执行的这个brpop命令,谁就能拿到这个新来的元素,像这样的设定,就能构成一个“轮询”。

分频道的消息列表

       Redis同样使用 lpush + brpop 命令,但是通过不同的键模拟频道的概念,不同的消费者可以通过 brpop 不同的键值,实现订阅不同频道的理念。

       多个列表/频道,这种场景非常常见,日常使用的一些程序中,抖音等都有这些。有一个通常来传输短视频数据,还可以有一个通道来传输数据,还可以有频道来传输点赞,转发等,还可以有频道来传输评论和数据。 

微博Timeline

       每一个用户都有属于自己的Timeline(微博列表),现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。

选择列表类型时,请参考:

  • 同侧存取(lpush + lpop 或者 rpush + rpop)为栈
  • 异侧存取(lpush + rpop 或者 rpush + lpop)为队列

六、Set集合

集合类型也是保存多个字符串类型的元素,但是和列表类型不同的是,在集合中:

  1. 元素之间是无序的
  2. 元素不允许重复,一个集合中最多可以存储2^32 - 1个元素

       Redis除了支持集合内的增删查改操作,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多问题。

6.1 普通命令

SADD

将一个或者多个元素添加到set中。注意,重复的元素无法添加到set中。语法如下:

SADD key member [member ...]

时间复杂度为:O(1)

返回值:本次添加成功的元素个数 

SMEMBERS

获取一个set中的所有元素,注意,元素间的顺序是无序的。语法如下:

SMEMBERS key

时间复杂度:O(N)

返回值:所有元素的列表 

SISMEMBER

判断一个元素在不在set中,语法如下:

SISMEMBER key member

时间复杂度:O(1)

返回值:1表示元素在set中,0表示元素不在set中或者key不存在 

SCARD

获取一个set的基数,即set中的元素个数。语法如下:

SCARD key

时间复杂度为:O(1)

返回值:set内的元素个数 

SPOP

       从set中删除并返回一个或者多个元素。注意,由于set内的元素是无序的,所以取出哪一个元素是未定义行为,即可以看做随机的。语法如下:

SPOP key [count]

时间复杂度:O(N),N是count

返回值:取出的元素 

SMOVE

将一个元素从源set取出并放入目标set中,语法如下:

SMOVE source destination member

时间复杂度:O(1)

返回值:1表示移动成功,0表示失败 

SREM

将指定的元素从set中删除,语法如下:

SREM key member [member ...]

时间复杂度:O(N),N是要删除的元素个数

返回值:本次操作删除的元素个数

6.2 集合间的操作

SINTER

获取给定set的交集中的元素,语法如下:

SINTER key [key ...]

时间复杂度:O(N * M),N是最小的集合元素个数,M是最大的集合元素个数

返回值:交集的元素 

SINTERSTORE

获取给定set的交集中的元素并保存到目标set中。语法如下:

SINTERSTORE destination key [key ...]

时间复杂度:O(N * M),N是最小的集合元素个数,M是最大的集合元素个数

返回值:交集的元素个数

SUNION

SUNIONSTORE

SDIFF

SDIFFSTORE

6.3 内部编码

集合类型的内部编码有两种:

intset(整数集合)

hashtable(哈希表)

6.4 使用场景

       集合类型比较典型的使用场景是标签。例如A用户对娱乐、体育板块比较感兴趣,B用户对历史、新闻比较感兴趣,这些兴趣点可以被抽象为标签。有了这数据就可以得到喜欢同一个标签的人,以及用户的共同爱好的标签,这些数据对于增强用户体验和用户粘度都非常有帮助。例如,一个电子商务网站会对不同标签的用户做不同的产品推荐。

sinter user:1:tags user:2:tags

使用Set来计算用户之间的共同好友

基于“集合求交集”

使用Set统计UV

去重

一个互联网产品,如何衡量用户量,用户规模??

主要的指标是两个方面:

PV(page view):用户每次访问该服务器,每次访问都会产生一个pv

UV(user view) :每一个用户访问服务器,都会产生一个uv,但是同一个用户多次访问,不会使uv增加,uv需要按照用户进行去重,上述的去重过程,就可以使用set去实现。

七、Zset有序集合

       有序集合相对于字符串、列表、哈希、集合来说会有一些陌生。他保留了集合不能有重复成员的特点,但是与集合不同的是,有序集合中的每一个元素都有一个唯一的浮点数类型的分数与之关联,使得有序集合中的元素是可以维护有序性的,但是这个有序不是用下标作为排序依据而是用这个分数。

       有序集合提供了获取指定分数和元素范围查找,计算成员排名等功能,合理地利用有序集合,可以帮助我们在实际开发中解决很多问题。

       有序集合中的元素是不能重复的,但是分数允许重复。类比于一次考试之后,每一个人一定有一个唯一的分数,但是分数允许相同。

7.1 普通命令

7.2 集合间的操作

7.3 内部编码

有序集合类型的内部编码有两种:

ziplist(压缩列表)

skiplist(跳表)

7.4 使用场景

有序即可比较典型的使用场景就是排行榜系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/75747.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OBOO鸥柏如何以智能教育室内外触摸屏一体机AI变革硬件

在AI技术蓬勃发展的当下&#xff0c;OBOO鸥柏室外触摸屏一体机通过融入AI科技&#xff0c;为教育领域带来了翻天覆地的变化。这款一体机不仅为高校和大学校园提供了革命性的数字化教学解决方案&#xff0c;更引领了引体向上成绩提升一体机带训室外终端屏幕设备的新潮流。其创新…

从零搭建高并发体育直播网站:架构设计、核心技术与性能优化实战

本文从技术视角拆解体育直播网站开发全流程&#xff0c;涵盖高并发架构设计、低延迟视频流传输、实时弹幕系统实现等核心模块&#xff0c;并附可复用的代码片段与优化方案。适合中高级开发者进阶实战参考。 一、需求分析与技术选型 1. 典型业务场景 核心需求&#xff1a;支持1…

【Python内置函数的深度解析与应用】id

目录 前言&#xff1a;技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解关键技术模块技术选型对比 二、实战演示环境配置要求核心代码实现1. 基础身份验证2. 不可变对象优化3. 对象生命周期追踪 运行结果验证 三、性能对比测试方法论量化数据…

3.vtkProp 和vtkProp3D

文章目录 vtkProp 和vtkProp3D使用vtkProp3D使用vtkPro vtkProp 和vtkProp3D vtkProp 和 vtkProp3D 都是VTK&#xff08;Visualization Toolkit&#xff09;库中的类&#xff0c;它们用于在渲染场景中表示可视化元素。理解这两个类的区别和用途对于有效地使用VTK进行三维数据可…

【ZYNQ Linux移植】2-获取设备树

0 写在前面 这是一个系列博客&#xff0c;详细介绍如何在 ZYNQ 与 ZYNQ MP 平台上如何移植 Linux 系统。目前网络上的大部分教程都是全程基于 Petalinux 的开发&#xff0c;虽然这样简化了开发流程&#xff0c;但对于初学者深入理解掌握 Linux 是不利的&#xff0c;所以&#x…

基础算法篇(5)(蓝桥杯常考点)—动态规划(C/C++)

文章目录 动态规划前言线性dp路径类dp经典线性dp背包问题分类01背包问题完全背包问题多重背包分组背包问题混合背包问题多维费用的背包问题区间dp 动态规划 前言 在竞赛中&#xff0c;如果遇到动态规划的题目&#xff0c;只要不是经典题型&#xff0c;那么大概率就是以压轴题的…

obsidian写文章的图床设置方法

目标 要达成的需求&#xff1a; 复制到obsidian的图片&#xff0c;自动上传到Picgo配置的图床。可以自定义大小。可以一键下载当前文章的图片到本地。 obsidian配置图床 安装并配置插件 image auto upload plugin&#xff0c;配置信息如下图。 滚轮alt自定义大小 安装并…

QPaintDevice绘图设备

1.QPixmap 对不同平台做了显示的优化&#xff0c;可以将画的图保存到磁盘上 头文件&#xff1a; #include"QPixmap" #include"QPainter" 1.1QPixmap画图 代码&#xff1a; //Pixmap绘图设备QPixmap pix(300,300);//声明画家QPainter painter(&pix…

数据结构有哪些类型(对于数据结构的简述)

在学习计算机时&#xff0c;数据结构是不可忽视的一点&#xff0c;从考研时的408课程&#xff0c;再到工作中编写软件&#xff0c;网站&#xff0c;要想在计算机领域站住脚跟&#xff0c;数据结构是必备的 在这里&#xff0c;我对于数据结构进行了汇总&#xff0c;并简要描述&…

L2TP实验(无图后补)

拓扑图 一、搭建拓扑并配置基础 IP 地址 设备选型与拓扑搭建&#xff1a;在 eNSP 中&#xff0c;拖入所需设备&#xff0c;包括 LAC&#xff08;L2TP Access Concentrator&#xff0c;L2TP 接入集中器 &#xff09;、LNS&#xff08;L2TP Network Server&#xff0c;L2TP 网络服…

【C#】CAN通信的使用

在C#中实现CAN通信通常需要借助第三方库或硬件设备的驱动程序&#xff0c;因为C#本身并没有直接内置支持CAN通信的功能。以下是一个关于如何使用C#实现CAN通信的基本指南&#xff0c;包括所需的步骤和常用工具。 1. 硬件准备 要进行CAN通信&#xff0c;首先需要一个支持CAN协…

02_C++入门案例习题while循环练习案例:猜数字

案例描述&#xff1a;系统随机生成一个1到100之间的数字&#xff0c;玩家进行猜测&#xff0c;如果猜错&#xff0c;提示玩家数字过大或过小&#xff0c;如果猜对恭喜玩家胜利&#xff0c;并且退出游戏。 需要引入随机数种子 #include <cstdlib> #include <ctime>…

深入理解哈希冲突:原理、解决方案及 Java 实践

概述&#xff1a;在计算机科学领域&#xff0c;哈希表是一种非常重要的数据结构&#xff0c;它通过哈希函数将键映射到存储桶中&#xff0c;从而实现快速的数据查找、插入和删除操作。然而&#xff0c;哈希表在实际应用中会面临 哈希冲突的问题。本文将深入探讨哈希冲突的原理、…

opencv(C++)处理图像颜色

文章目录 介绍使用策略设计模式比较颜色实现方案计算两个颜色向量之间的距离1. 简单方法&#xff1a;曼哈顿距离计算&#xff08;Manhattan Distance&#xff09;2.使用 OpenCV 的 cv::norm 函数3.使用 OpenCV 的 cv::absdiff 函数错误示例 使用 OpenCV 函数实现颜色检测实现方…

DOM解析XML:Java程序员的“乐高积木式“数据搭建

各位代码建筑师们&#xff01;今天我们要玩一个把XML变成内存乐高城堡的游戏——DOM解析&#xff01;和SAX那种"边看监控边破案"的刺激不同&#xff0c;DOM就像把整个乐高说明书一次性倒进大脑&#xff0c;然后慢慢拼装&#xff08;内存&#xff1a;你不要过来啊&…

Apache Nifi安装与尝试

Apache NIFI中文文档 地址&#xff1a;https://nifichina.github.io/ 下载安装配置 1、环境准备 Nifi的运行需要依赖于java环境&#xff0c;所以本机上需要安装java环境&#xff0c;并配置环境变量。 1.1查看本机是否已经存在java环境 请先执行以下命令找出系统中真实可用…

我可能用到的网站和软件

我可能用到的网站和软件 程序员交流的网站代码管理工具前端组件库前端框架在线工具人工智能问答工具学习的网站Windows系统电脑的常用工具 程序员交流的网站 csdn博客博客园 - 开发者的网上家园InfoQ - 软件开发及相关领域-极客邦掘金 (juejin.cn) 代码管理工具 GitHub 有时…

使用SSH解决在IDEA中Push出现403的问题

错误截图&#xff1a; 控制台日志&#xff1a; 12:15:34.649: [xxx] git -c core.quotepathfalse -c log.showSignaturefalse push --progress --porcelain master refs/heads/master:master fatal: unable to access https://github.com/xxx.git/: The requested URL return…

JavaScript异常机制与严格模式

目录 JavaScript 异常机制 1. 基本语法&#xff1a;try...catch...finally 2. 抛出异常&#xff1a;throw 3. 错误对象属性 4. 同步代码的异常处理 5. 异步代码的异常处理 5.1 回调函数 5.2 Promise 5.3 全局未捕获的 Promise 错误 6. 全局错误处理 7. 自定义错误与…

中厂算法岗面试总结

时间&#xff1a;2025.4.10 地点&#xff1a;上市的电子有限公司 面试流程&#xff1a; 1.由负责人讲解公司文化 2&#xff0c;由技术人员讲解公司的技术岗位&#xff0c;还有成果 3.带领参观各个工作位置&#xff0c;还有场所 4.中午吃饭 5.面试题&#xff0c;闭卷考试…