Tipos de datos comunes en Redis

El contenido de este artículo se refiere al desarrollo, operación y mantenimiento de Redis.

1 、 cuerda

        El tipo de cadena es la estructura de datos más básica de Redis. En primer lugar, todas las claves son tipos de cadena y varias otras estructuras de datos se construyen sobre la base del tipo de cadena, por lo que el tipo de cadena puede sentar las bases para el aprendizaje de las otras cuatro estructuras de datos.

        Como se muestra en la figura anterior, el valor del tipo de cadena puede ser en realidad una cadena (cadena simple, cadena compleja (como JSON, XML)), un número (entero, número de punto flotante) o incluso binario (imagen, audio, video ), pero el valor máximo no puede exceder los 512 MB.

        Orden

        Hay muchos comandos de tipo cadena. Este artículo los explicará en dos dimensiones: de uso común y poco común. Sin embargo, lo de uso común y lo poco común aquí son relativos, por lo que no significa que no sea necesario comprenderlos si no se usan comúnmente. .

Se puede ver          un resumen de los comandos relacionados en el uso de Redis.

        Comandos comunes

        (1) Valor de ajuste
set key value [ex seconds] [px milliseconds] [nx|xx]

         La siguiente operación se establece en hola y el valor es el par clave-valor del mundo. El resultado devuelto es correcto, lo que significa que la configuración se realizó correctamente.

        El comando set tiene varias opciones:

  • ex segundos: establece el tiempo de vencimiento de segundo nivel para la clave.
  • px milisegundos: establece el tiempo de vencimiento en milisegundos para la clave.
  • nx: la clave no debe existir antes de que pueda configurarse correctamente y usarse para agregarla.
  • xx: a diferencia de nx, la clave debe existir antes de poder configurarla y utilizarla correctamente para las actualizaciones.
  • Además de la opción de configuración, Redis también proporciona dos comandos: setex y setnx:
    setex key seconds value
    setnx key value

    Sus funciones son las mismas que las de las opciones ex y nx. El siguiente ejemplo ilustra la diferencia entre set, setnx y set xx.

         Primero está el ejemplo de setnx:

        Podemos encontrar que cuando la clave que queremos crear no existe, podemos crearla exitosamente usando el comando setnx, pero cuando la clave que queremos crear ya existe, no podremos crearla usando el comando setnx.

        colocar:

        En comparación con setnx, podemos encontrar que la diferencia entre set y setnx es que cuando la clave que queremos crear ya existe, setnx no se puede crear con éxito, pero set aún se puede crear con éxito.

        establecer xx:

        En la figura, podemos encontrar que cuando usamos set xx, solo las claves existentes se pueden crear con éxito. Cuando la clave que queremos crear no existe, la creación fallará.

        Entonces, ¿cuáles son los escenarios de aplicación reales de setnx y set xx? alguno.

        Debido al mecanismo de procesamiento de comandos de un solo subproceso de Redis, si varios clientes ejecutan el valor de la clave setnx al mismo tiempo, de acuerdo con las características de setnx, solo un cliente puede configurarlo con éxito. Setnx se puede utilizar como una solución de implementación para bloqueos distribuidos. Redis proporciona oficialmente Cómo implementar bloqueos distribuidos usando setnx: http: //redis.io/topics/distlock

        En algunos casos, es posible que solo desee actualizar una clave si ya existe, para asegurarse de que no se cree una nueva clave si no es válida. SET key value XXEl comando garantiza que el valor se actualice solo si la clave existe.

        (2) Obtener valor
get key

        En la figura, podemos encontrar que cuando la clave existe, podemos obtener el valor de la clave, y cuando la clave obtenida no existe, se devuelve nulo (vacío).

        (3) Valores de configuración por lotes y valores de obtención por lotes
mset key value [key value ...]
mget key1 key2 ....

        Se puede encontrar que si algunas claves no existen, se devolverá nulo (vacío) y el resultado se devolverá en el orden en que se pasaron las claves.

        Los comandos de operación por lotes pueden mejorar efectivamente la eficiencia del desarrollo. Si no existe un comando como mget, para ejecutar el comando get n veces, debe ejecutarse como se muestra en la siguiente figura. El consumo de tiempo específico es el siguiente:

        n veces tiempo de obtención = n veces tiempo de red + n veces tiempo de comando

        Después de usar el comando mget, para ejecutar el comando get n veces, solo necesita completarlo como se muestra a continuación. Los beneficios específicos son los siguientes:

        n tiempos de obtención = 1 tiempo de red + n tiempos de comando

        Redis puede realizar decenas de miles de operaciones de lectura y escritura por segundo, pero esto se refiere a la capacidad de procesamiento del servidor Redis. Para el cliente, además del tiempo de comando, un comando también tiene tiempo de red. Supongamos que el tiempo de red es 1 ms y el tiempo del comando es 0,1 ms (calculado en función del procesamiento de 1 w comandos por segundo), entonces la diferencia entre ejecutar 1000 comandos get y un comando mget es la siguiente:

Tabla comparativa entre 1000 get y 1 mget
funcionar tiempo
1000 veces obtener 1000 × 1 + 1000 × 0,1 = 1100 ms = 1,1 s
1 mget (1000 pares clave-valor ensamblados) 1 × 1 + 1000 × 0,1 = 101 ms = 0,101 s

        Debido a que la potencia de procesamiento de Redis es lo suficientemente alta, para los desarrolladores, la red puede convertirse en un cuello de botella en el rendimiento. Aprender a utilizar operaciones por lotes ayudará a mejorar la eficiencia del procesamiento empresarial, pero a lo que se debe prestar atención es al comando enviado para cada operación por lotes. El número no es ilimitado, si el número es demasiado grande, puede causar congestión en Redis o en la red.

        (4) Contando
incr key

        El comando incr se utiliza para realizar operaciones de incremento en valores y los resultados devueltos se dividen en tres situaciones:

  • El valor no es un número entero, se devuelve un error.
  • Es solo un número entero y devuelve el resultado después del incremento.
  • Si la clave no existe, se incrementará según el valor de 0 y el resultado devuelto será 1.

        Por ejemplo, después de ejecutar la operación incr en una clave inexistente, el resultado devuelto es 1. Si el comando incr se ejecuta nuevamente en la clave, el resultado devuelto es 2. Si el valor no es un número entero, se producirá un error. regresó. Como se muestra en la imagen:

        Además del comando incr, Redis proporciona decr (decremento automático), incrby (incremento automático a un número específico), decrby (decremento automático a un número específico) e incrbyfloat (incremento automático de un número de punto flotante) :

decr key
incrby key increment
decrby key increment
incrbyfloat key increment

        Muchos sistemas de almacenamiento y lenguajes de programación utilizan el mecanismo CAS internamente para implementar funciones de conteo, lo que provocará una cierta sobrecarga de CPU, pero este problema no existe en Redis porque Redis es una arquitectura de un solo subproceso y cualquier comando debe se ejecutará secuencialmente cuando llegue al servidor Redis.

        Comandos poco utilizados

        (1) Valor adicional
append key value

        append puede agregar un valor al final de una cadena, por ejemplo:

        (2) Longitud de la cuerda
strlen key

        En la imagen, podemos encontrar que la clave redis ocupa 6 bytes, porque un carácter chino en Redis ocupa tres bytes.

        (3) Establecer y devolver el valor original
getset key value

        getset establece el valor como set, pero la diferencia es que también devuelve el valor original de la clave, por ejemplo:

        (4) Establecer el carácter en la posición especificada
setrange key offset value

        La siguiente operación cambia el valor de hola a fallo

        (5) Obtener parte de la cadena.
getrange key start end

        inicio y fin son los desplazamientos del inicio y el final respectivamente. El desplazamiento se calcula a partir de 0. Por ejemplo, la operación en la figura anterior obtiene el ell en el medio del valor fallo.

        Complejidad del tiempo de comando de tipo de cadena

Complejidad del tiempo de comando de tipo de cadena
Orden complejidad del tiempo
establecer valor clave O(1)
obtener la clave O(1)
tecla del [tecla...] O(k), k es el número de claves
valor clave mset [valor clave...] O(k), k es el número de claves
obtener clave [clave...] O(k), k es el número de claves
tecla incr O(1)
tecla decr O(1)
incremento de clave incrby O(1)
incremento de clave decrby O(1)
incremento de clave incrbyfloat O(1)
agregar valor clave O(1)
llave fuerte O(1)
valor de compensación de la tecla setrange O(1)
getrange clave inicio fin O (n), n es la longitud de la cadena. Dado que la obtención de la cadena es muy rápida, si la cadena no es muy larga, se puede considerar como O (1).

        codificación interna

        Hay tres codificaciones internas para tipos de cadenas:

  • int: entero de 8 bytes de longitud.
  • embstr: una cadena de menos o igual a 39 bytes.
  • raw: una cadena de más de 39 bytes.

        Redis decidirá qué implementación de codificación interna utilizar en función del tipo y la longitud del valor actual.

        Los ejemplos son los siguientes:

# 整数类型示例
127.0.0.1:6379> set key 8653
OK
127.0.0.1:6379> object encoding key
"int"

# 短字符串示例
# 小于等于39个字节的字符串:embstr
127.0.0.1:6379> set key "hello world"
OK
127.0.0.1:6379> object encoding key
"embstr"

# 长字符串示例
# 大于39个字节的字符串:raw
127.0.0.1:6379> set key "one string greater than 39 byte .................................."
OK
127.0.0.1:6379> object encoding key
"raw"
127.0.0.1:6379> strlen key
(integer) 66

        Escenarios de uso típicos

        1. Función de almacenamiento en caché

        Como se muestra arriba, es un escenario típico de uso de caché, en el que Redis actúa como capa de caché y MySQL actúa como capa de almacenamiento. La mayoría de los datos solicitados se obtienen de Redis. Dado que Redis tiene las características de admitir una alta concurrencia, el almacenamiento en caché generalmente puede desempeñar un papel en la aceleración de la lectura y la escritura y en la reducción de la presión del back-end.

        El siguiente pseudocódigo simula el proceso de acceso en la figura anterior:

// 假设有一个函数getUserInfo,通过传入的id,获取用户信息
UserInfo getUserInfo(Long id){
    // 首先从Redis中获取用户信息:
    // 定义键
    userRedisKey = "user:info:" + id;
    // 从Redis中获取值
    value = redis.get(userRedisKey);
    // 如果没有从Redis中获取到用户信息,需要从MySQL中进行获取,并将结果回写到Redis,添加1小时(3600)过期时间;
    UserInfo userInfo;
    if (value != null) {
        userInfo = deserialize(value);
    } else{
        // 从MySQL中获取用户信息
        userInfo = mysql.get(id);
        if(userInfo != null)
            // 将userInfo序列化,并存入Redis中
            redis.setex(userRedisKey, 3600, serialize(userInfo));
    }
    // 返回结果
    return userInfo;
}

        2. Contando

        Muchas aplicaciones utilizan Redis como herramienta básica para el conteo, que puede implementar funciones de conteo rápido y almacenamiento en caché de consultas, y los datos se pueden enviar de forma asincrónica a otras fuentes de datos. Por ejemplo, se puede usar en un sistema de conteo de reproducción de video. Puede usar Redis como componente básico para contar el conteo de reproducción de video. Cada vez que un usuario reproduce un video, el conteo de reproducción de video correspondiente aumentará en 1.

        3. Límite de velocidad

        Muchas veces, por razones de seguridad, las aplicaciones pedirán a los usuarios que introduzcan un código de verificación del teléfono móvil cada vez que inicien sesión para determinar si son los propios usuarios. Sin embargo, para evitar que se acceda con frecuencia a la interfaz de SMS, la frecuencia con la que los usuarios obtienen códigos de verificación por minuto se limitará, por ejemplo, a no más de 5 veces por minuto. Como se muestra en la imagen:

        Esta función se puede implementar usando Redis. El siguiente pseudocódigo proporciona la idea básica de implementación:

String phoneNum = "188xxxxxxxx"
String key = "shortMsg:limit:" + phoneNum
// SET Key EX 60 NX
isExists = redis.set(key, 1 "EX 60", "NX")
if (isExists != null || redis.incr(key)<=5){
    // 通过
}else{
    // 限速
}

        Lo anterior es el uso de Redis para implementar la función de límite de velocidad. Por ejemplo, si un sitio web restringe el acceso a una dirección IP más de n veces en un segundo, se puede utilizar una idea similar.

        Además de los escenarios de aplicación centralizados presentados anteriormente, las cadenas tienen muchos escenarios aplicables. Los desarrolladores pueden dar rienda suelta a su imaginación combinando los comandos relevantes proporcionados por las cadenas.

2、picadillo

        Casi todos los lenguajes de programación proporcionan tipos hash, que pueden denominarse hashes, diccionarios y matrices asociativas. En Redis, el tipo de hash se refiere al valor clave en sí, que es una estructura de par clave-valor, en la forma de valor={ { campo1:valor},....,{fieldn, valuen}}, valor-clave de Redis. par y hash La relación entre los dos tipos se puede representar en la siguiente figura.

       Orden

Se puede ver         un resumen de los comandos relacionados en el uso de Redis.

                (1) Valor de ajuste
hset key field value
                (2) Obtener valor
hget key field
                (3) Eliminar campo
hdel key field [field ...]
                (4) Calcular el número de campos.
hlen key
                (5) Establecer u obtener el valor del campo en lotes
hmget key field [field ...]
hmset key field value [field value ...]
                (6) Determinar si el campo existe
hexists key field
                (7) Obtener todos los campos
hkeys key
                (8) Obtener todos los valores
hvals key
                 (9) Obtener todos los valores de campo
hgetall key
                (10) hincrby bincrbyfloat
hincrby key field increment
hincrbyfloadt key field increment
                (11) Calcule la longitud de la cadena del valor (requiere Redis3.2 o superior)
hstrlen key field

        La siguiente tabla describe la complejidad temporal de los comandos de tipo hash.

Complejidad temporal de los comandos de tipo hash
Orden complejidad del tiempo
valor del campo clave hset O(1)
hget campo clave O(1)
campo clave hdel [campo...] O(k), k es el número de campos
llave hlen O(1)
clave hgetall O (n), n es el número total de campos
campo hmget [campo...] O(k), k es el número de campos
valor de campo hmset [valor de campo...] O(k), k es el número de campos
campo clave hexistas O(1)
llave hkeys O (n), n es el número total de campos
clave hvals O (n), n es el número total de campos
valor del campo clave hsetnx O(1)
valor del campo clave hincrby O(1)
incremento del campo clave hincrbyfloat O(1)
campo clave hstren O(1)

        codificación interna

        Hay dos codificaciones internas para los tipos de hash:

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

        1、当field个数比较少且没有大的value时,内部编码为ziplist。

        2、当有value大于64字节,内部编码会有ziplist变为hashtable。

        3、当field个数超过512,内部编码也会有ziplist变为hashtable。

        但是在实际操作中我们会发现哈希内部的编码是listpack,既不是ziplist,也不是hashtable,如图:

        这是因为Redis 在内部自动进行了编码转换,将哈希键的编码方式从哈希表(hashtable)转换为 Listpack。这是 Redis 为了优化内存使用和性能而采取的一种策略。

        当哈希键的大小和字段的大小满足一定条件时,Redis 会自动选择将其编码方式从哈希表转换为 Listpack。Listpack 是一种紧凑的二进制格式,用于存储键值对,类似于 Redis 中的列表(List)数据结构。这种转换可以减少内存占用并提高一些操作的性能。

        虽然哈希键的内部编码方式显示为 "listpack",但用户仍然可以使用通常的哈希操作命令来访问和操作哈希键的数据,就像它们仍然是哈希表一样。这种编码方式的转换是 Redis 内部的优化策略,对于用户来说,不需要直接操作 Listpack。

        那什么情况下会发生这样的事情呢?

        Redis 会自动选择将哈希键(Hash Key)的编码方式从哈希表(hashtable)转换为 Listpack 编码方式的情况通常涉及以下条件:

  1. 哈希键的大小:当哈希键包含的字段数量相对较少,并且字段的大小适中时,Redis 可能会考虑将其编码方式转换为 Listpack。具体的阈值可能因 Redis 的版本和配置而有所不同。

  2. 字段的大小:如果哈希键的字段的键和值的大小都比较小,那么 Redis 更有可能选择 Listpack 编码方式。

  3. 哈希键的使用模式:哈希键的使用模式也会影响 Redis 的编码选择。如果哈希键主要用于插入、删除或迭代操作,并且不需要频繁的哈希键查找操作,那么 Listpack 编码方式可能更合适。

  4. 内存优化策略:Redis 会考虑系统的内存状况和性能,以决定是否切换编码方式。它的目标是提高内存使用效率和执行操作的速度。

        需要注意的是,Redis 的编码方式转换是自动进行的,用户无需干预。Redis 会根据上述条件自动选择合适的编码方式以提高性能和内存利用率。这个转换是 Redis 的内部优化机制,它使 Redis 能够在不同情况下充分利用内存,并提供高性能。

        如果您想了解特定版本的 Redis 在何时选择转换编码方式以及如何调整这些条件,请参考该版本的 Redis 文档或源代码。不同版本的 Redis 可能会在这方面有些许不同。

        使用场景

        相比于使用字符串序列化缓存用户信息,哈希类型变得更加直观,并且在更新操作上会更加便捷。可以将每个用户的id定义为键后缀,多对field-value对应每个用户的属性。

        但是需要注意的是哈希类型和关系数据库有两点不同之处:

  • 哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的field,二关系型数据库一旦添加新的列,所有行都要为其设置值(即使为NULL)。
  • 关系型数据库可以做复杂的关系查询,去模拟关系型复杂查询开发困难,维护成本高。

        到目前为止,我们已经给出三种方法缓存用户信息,如下:

        1、原生字符串类型:每个属性一个键

set user:1:name tom
set user:1:age 23
set user:1:city beijing

        优点:简单直观,每个属性都支持更新操作。

        缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,所以这种方式一般不会在生产环境中使用。

        2、序列化字符串类型:将用户信息序列化后用一个键保存。

set user:1 serialize(userInfo)

        优点:简化编程,如果合理的使用序列化可以提高内存的使用率。

        缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出来进行反序列化,更新后再序列化到Redis中。

        3、哈希类型:每个用户属性使用一对field-value,但是只用一个键保存。

hmset user:1 name tom age 23 city beijing

        优点:简单直观,如果使用合理可以减少内存空间的使用。

        缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。

3、List

        列表(list)类型是用来存储多个有序的字符串的,可以理解为是一个双向链表。

        如上图中第一个列表所示,a、b、c、d、e五个元素从左到右组成了一个有序的列表,列表中的每个字符串被称为元素,一个列表最多可以存储2^32-1个元素。在Redis中,可以对列表两端插入和弹出,还可以获取指定范围内的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,他可以充当栈和队列的角色,在实际开发上有很多应用场景。

        列表类型有两个特点:第一、列表中的元素是有序的,这就意味着可以通过索引下标来获取某个元素或者某个范围内的元素列表如上图的第二个列表所示。第二,列表中的元素可以是重复的。

        命令

        相关命令的汇总可以前往Redis的使用进行查看

                基本命令
# 从左/右边插入元素
lpush/rpush key value [value ...]

# 向某个元素前或者后插入元素
linsert key before/after pivot value

# 获取指定范围内的元素列表
lrange key start end

# 获取列表指定索引下标的元素
lindex key index

# 获取列表长度
llen key

# 从左/右侧弹出元素
lpop/rpop key

# 按照索引范围修剪列表
ltrim key start end

# 修改指定索引下标的元素
lset key index newValue

        删除元素命令
# 删除指定元素
lrem key count value

        lrem命令会从列表中找到等于value的元素进行删除,根据count的不同分为如下情况:

  • count>0,从左到右,删除最多count个元素。
  • count<0,从右到左,删除最多count绝对值个元素。
  • count=0,删除所有。

       阻塞式弹出
# 阻塞式弹出
blpop key [key ...] timeout
brpop key [key ...] timeout

        blpop与brpop是lpop和rpop的阻塞版本,它们除了弹出的方向不同,使用方法基本相同,所以下面以brpop命令进行说明,brpop命令包含2个参数:

  • key [key ...]:多个列表的键。
  • timeout:阻塞时间(单位:秒)。
  1. 列表为空:如果timeout=3,那么客户要等到3秒结束后返回,如果timeout=0,那么客户端一直阻塞下去。
    client1:

    此时如果另一个客户端在该列表里添加了数据则:
  2. 列表不为空:客户端会立即返回。

        在使用brpop时需要注意两个点:

  1. 如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端会立即返回。
  2. 如果多个客户端对同一个键执行brpop,那么最先执行brpop命令的客户端可以获取到弹出的值。

         下表描述的是列表类型命令的时间复杂度

列表类型命令的时间复杂度
操作类型 命        令 时间复杂度
添加 rpush key value [value ....] O(k),k是元素个数
lpush  key value [value ....] O(k),k是元素个数
linsert key before | after pivot value O(n),n是pivot距离列表头或尾的距离
查找 lrange key start end O(s+n),s是start偏移量,n是start到end的范围
lindex key index O(n),n是索引的偏移量
llen key O(1)
删除 lpop key O(1)
rpop key O(1)
lrem count value O(n),n是列表长度
ltrim key start end O(n),n是要裁剪的元素总数
修改 lset key index value O(n),n是索引的偏移量
阻塞操作 blpop brpop O(1)

        内部编码

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

  • ziplist(压缩列表):列表内部的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表内部实现来减少内存的使用。
  • linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
  • quicklist(快速列表):考虑到链表的附加空间相对太高,prev 和 next 指针就要占去 16 个字节 (64bit 系统的指针是 8 个字节),另外每个节点的内存都是单独分配,会加剧内存的碎片化,影响内存管理效率。因此Redis3.2版本开始对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist
        快速列表

        quicklist 实际上是 zipList 和 linkedList 的混合体,它将 linkedList 按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。

详情介绍可以前往https://www.cnblogs.com/hunternet/p/12624691.html

        使用场景

        1)消息队列

        Redis中的lpush+brpop命令组合可以实现阻塞队列,生产者客户端用lrpish从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。

        2)文章列表

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

  1. 每篇文章使用户哈希结构存储,例如每篇文章有三个属性,title、timestamp、content。

  2. 向文章列表添加文章,user:{id}:articles作为用户文章列表的键。

  3. 分页获取用户文章列表。

# 伪代码
articles = lrange user:1:article 0 9
for article in {articles}:
    hgetall {article}
  • lpush + lpop = Stack(栈)

  • lpush + rpop = Queue(队列)

  • lpush + ltrim = Capped Collection(集合)

  • lpush + brpop = Message Queue(消息队列)

4、Set

待更新

5、ZSet

待更新

6、BitMaps

待更新

7、HypeLogLog

actualización pendiente

8、GEO

actualización pendiente

Supongo que te gusta

Origin blog.csdn.net/Deikey/article/details/132639340
Recomendado
Clasificación