¿Redis solo se usará para almacenamiento en caché? Entonces deberías leer este artículo

¿Por qué Redis es rápido?

Redis se usará en el proyecto, porque redis se puede usar como caché y puede procesar 10w de datos por segundo al mismo tiempo. Pero, ¿sabe por qué el acceso a redis es tan rápido? Se podría decir que redis se basa en la memoria, en el almacenamiento de KV, de un solo hilo ... Espera, ¿por qué el subproceso único es más rápido?

De hecho, Redis es un modelo de multiplexación basado en NIO. En el entorno Windows, es la multiplexación de select, y en el entorno Linux es la multiplexación de epoll. Algunas personas pueden preguntar qué es la multiplexación.

 

Multiplexación

En pocas palabras, Redis pasa la lectura de datos al kernel para hacer

Multiplexación

Redis pondrá n conexiones de cliente en una colección (aquí hay un proceso), y luego llamará a epoll (no existe tal función en Windows) o seleccionará la función para poner la colección en el kernel del kernel del sistema operativo para su procesamiento, impulsada por eventos, El kernel obtendrá la conexión con los datos y los leerá cíclicamente La complejidad de tiempo es O (1).

Si preguntas qué es NIO? Necesita conocimientos adicionales de NIO.

Estructura de valor de Redis

5 estructuras comunes

estructura de valor

escenas que se utilizarán

Cuerda

Por ejemplo, la cuenta oficial de WeChat puede usar el valor de String para contar el número de lecturas

1
2
INCR article:readcount:{文章id}
GET article:readcount:{文章id}

Puede ser el número de serie global del sistema distribuido

1
INCRBY orderId 1000         //一次性生成1000个id,可以存入队列中,按需获取

consejos: puede usar el comando setnx + timeout para hacer bloqueos distribuidos. Hay un proyecto sobre mi bloqueo distribuido redis en github: dislock

Además de que redis se puede usar como un bloqueo distribuido, zookeeper también se puede usar como un bloqueo distribuido (basado en el nombre de nodo único y el mecanismo de observador). Comparado con redis, zookeeper es más fuerte que redis en consistencia final, pero su rendimiento será más débil que redis. proyecto de bloqueo distribuido de github: distributionlock

Mapa de bits

Puede contar cuántas veces ha iniciado sesión el usuario en cualquier período de tiempo

 

El mapa de bits es una matriz binaria de longitud ilimitada (cuando la longitud es de 2 mil millones, ocupa más de 200 MB de memoria). El valor de la matriz es 0 o 1. Como se muestra en la figura anterior, si el usuario sean inicia sesión el cuarto día, es

1
0001

Inicie sesión el día 9 como

1
000100001

Y así. La última línea cuenta el número de veces que el valor es 1 entre el primer índice y el último índice.

También podemos usar mapa de bits para contar el número de usuarios activos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#第一天7号用户登录一次
127.0.0.1:6379> setbit 20200101 7 1
( integer ) 0
#第一天3号用户登录一次
127.0.0.1:6379>set bit 20200101 3 1
( integer ) 0
#第二天3号用户登录一次
127.0.0.1:6379> setbit 20200102 3 1
( integer ) 0
#或运算
127.0.0.1:6379> BITOP or res 20200101 20200102
#统计活跃用户
127.0.0.1:6379> BITCOUNT res
( integer ) 2
2人

consejos: el famoso filtro Bloom se puede implementar con mapa de bits

Picadillo

hash puede almacenar información relacionada con el carrito de compras

 

Como se muestra arriba: la identificación del usuario es la clave, la identificación del producto se archiva y la cantidad del producto se almacena como valor. Puede mostrar información del carrito de compras.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
127.0.0.1:6379> hset cart:1001 10088 1
(integer) 1
127.0.0.1:6379> hincrby cart:1001 10088 1
(integer)2
127.0.0.1:6379> hget cart:1001 10088
"2"
127.0.0.1:6379> hset cart:1001 10088 1
(integer)0
127.0.0.1:6379> hset cart:1001 20088 1
(integer) 1
127.0.0.1:6379> hlen cart:1001
(integer) 2
127.0.0.1:6379> hdel cart:1001 20088
(integer) 1
127.0.0.1:6379> hlen cart:1001
(integer)1
127.0.0.1:6379> hset cart:1001 30088 1
(integer) 1
127.0.0.1:6379> hgetall cart:1001
1)"10088"
2)"1"
3)"30088"I
4)"1"
127.0.0.1:6379>

La estructura hash tiene las siguientes ventajas y desventajas:

ventaja

1) Almacenamiento consolidado de datos similares, conveniente para la gestión de datos
2) En comparación con la operación de cadenas, consume menos memoria y cpu
3) En comparación con el almacenamiento de cadenas, ahorra más espacio

Desventaja

1) La función de vencimiento no se puede usar en el campo, sino solo en la clave.
2) La arquitectura del clúster de Redis no es adecuada para uso a gran escala

En general, el hash se puede utilizar para almacenar la agregación de páginas de detalles y los datos provienen de la agregación de diferentes bibliotecas.

Lista

La lista se puede utilizar como una estructura de datos, como colas, pilas, etc.

 

Escenarios de mensajes de Weibo y mensajes de cuentas oficiales

1
2
3
4
5
6
7
lvshen关注了MacTalk,备胎说车等大V
1)MacTalk发微博,消息ID为10018
LPUSH msg:(lvshen-ID} 10018
2)备胎说车发微博,消息ID为10086
LPUSH msg:(lvshen-ID) 10086
3)查看最新微博消息
LRANGE msg:(lvshen-ID} 0 5

Lista de operaciones comunes

1
2
3
4
5
6
7
LPUSH key value [value ...]  //将一个或多个值value插入到key列表的表头(最左边)
RPUSH key value [value ...]  //将一个或多个值value插入到key列表的表尾(最右边)
LPOP key //移除并返回key列表的头元素
RPOP key //移除并返回key列表的尾元素
LRANGE key start stop //返回列表key中指定区间内的元素,区间以偏移量start和stop指定
BLPOP key [key ...] timeout //从key列表表头弹出一个元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待
BRPOP key [key ...] timeout //从key列表表尾弹出一元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待

Conjunto

El juego se puede utilizar como un mini programa de lotería WeChat.

1
2
3
4
5
6
1)点击【参与抽奖】加入集合
SADD key {userlD}
2)查看参与抽奖所有用户
SMEMBERS key
3)抽取count名中奖者
SRANDMEMBER key [count]    /SPOP key [count]

También se puede usar para Me gusta de WeChat y Weibo

1
2
3
4
5
6
7
8
9
10
1)点赞
SADD like:{消息ID} {用户ID}
2)取消点赞
SREM like:{消息ID} {用户ID}
3)检查用户是否点过赞
SISMEMBER like:{消息ID} {用户ID}
4)获取点赞的用户列表
SMEMBERS like:{消息ID}
5)获取点赞用户数
SCARD like:{消息ID}

El modelo de atención de Weibo y WeChat se puede realizar mediante operaciones colectivas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1)Lvshen(我)关注的人:
#lvshenSet-> {A, B, C}
192.168.42.128:6379> sadd lvshenSet A B C
(integer) 3

2)A关注的人:I
#aSet--> {lvshen, B, C, guojia}
192.168.42.128:6379> sadd aSet lvshen B C guojia
(integer) 4

3)B关注的人:
#bSet-> {lvshen, A, guojia, C, xunyu)
192.168.42.128:6379> sadd bSet lvshen A guojia C xunyu
(integer) 5

4)我和A共同关注:
SINTER lvshenSet aSet--> {B, C}
5)我关注的人也关注他(A):
SISMEMBER bSet A
SISMEMBER cSet A
6)我可能认识的人:
SDIFF aSet lvshenSet--> (lvshen, guojia)

establecer operaciones comunes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Set常用操作
SADD key member [member...] //往集合key中存入元素,元素存在则忽略,若key不存在则新建
SREM key member [member...] //从集合key中删除元素
SMEMBERS key //获取集合key中所有元素
SCARD key //获取集合key的元素个数
SISMEMBER key member //判断member元素是否存在于集合key中
SRANDMEMBER key [count] //从集合key中选出count个元素,元素不从key中删除
SPOP key [count] //从集合key中选出count个元素,元素从key中删除

//Set运算操作
SINTER key [key...] //交集运算
SINTERSTORE destination key [key..] //将交集结果存入新集合destination中
SUNION key [key..] //并集运算
SUNIONSTORE destination key [key...] //将并集结果存入新集合destination中
SDIFF key [key...] //差集运算
SDIFFSTORE destination key [key...] //将差集结果存入新集合destination中

En general, el conjunto se puede utilizar para deduplicación, lotería y otras operaciones.

Zset

zset es un conjunto ordenado de deduplicación, que se puede utilizar para implementar clasificaciones

 

Resumen: zset se puede utilizar en escenarios como tablas de clasificación y cambio de página. Zset se puede usar como una cola de retraso, la puntuación es el punto de tiempo de retraso, el valor del puerto se obtiene secuencialmente al obtenerlo y se puede quitar si la marca de tiempo actual es igual a la puntuación.

La estructura de datos subyacente de zset es una tabla de salto, una lista vinculada especial, que es excelente para buscar, agregar y eliminar. Para obtener conocimientos específicos, consulte el artículo:

Mesa Redis-Jump

GEO

redis también puede admitir consultas de ubicación geográfica, adecuadas para el desarrollo LBS

Comandos comunes

Corriente

La nueva estructura "Stream" comenzó en Stream 5.0. Escenario de uso: escenario de productor consumidor (similar a MQ)

Comandos comunes

Ejemplo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
127.0.0.1:6379> xadd room:msg:1001 * userId tony content hello
"1568106742941-0"
127.0.0.1:6379> xadd room:msg:1001 * userId tony content hello2
"1568106753764-0"
127.0.0.1:6379> type room:msg:1001
stream
127.0.0.1:6379> xlen room:msg:1001
(integer)2
127.0.0.1:6379> xrange room:msg:1001 - +
1) 1)"1568106742941-0"
   2) 1)"userId"
      2)"tony"
      3)"content"
      4)"hello"
2) 1)"1568106753764-0"
   2) 1)"userId"
      2)"tony"
      3)"content"
      4)"hello2"
127.0.0.1:6379>

127.0.0.1:6379> xread count streams room:msg:1001 0
1) 1)"room:msg:1001"
   2) 1) 1)"1568106742941-0"
         2) 1)"userId"
            2)"tony"
            3)"content"
            4)"hello"
      2) 1)"1568106753764-0"
         2) 1)"userId"
            2)"tony"
            3)"content"
            4)"hello2"
127.0.0.1:6379> xread count 2 streams room:msg:1001 $
(nil)
127.0.0.1:6379> xread count 2 streams room:msg:1001 $
(nil)
127.0.0.1:6379> xread count 2 block 10000 streams room:msg:1001 $

Ahora, redis se usa comúnmente en 3.x, y las listas también se pueden usar para colas de mensajes. Muy poca gente usa Stream.

Clúster de Redis

Para la construcción de clústeres, consulte: Construcción de clústeres Redis3.0

Preocupaciones sobre el clúster

1、增加了slot槽的计算,是不是比单机性能差?
共16384个槽,slots槽计算方式公开的,HASH_SLOT=CRC16(key)mod16384。
为了避免每次都需要服务器计算重定向,优秀的java客户端都实现了本地计算,并且缓存服务器slots分配,有变动时再更新本地内容,从而避免了多次重定向带来的性能损耗。

2、redis集群大小,到底可以装多少数据?
理论是可以做到16384个槽,每个槽对应一个实例,但是redis官方建议是最大1000个实例。存储足够大了。

3、ask和moved重定向的区别
重定向包括两种情况
a.若确定slot不属于当前节点,redis会返回moved。
b.若当前redis节点正在处理slot迁移,则代表此处请求对应的key暂时不在此节点,返回ask,告诉客户端本次请求重定向。

4、数据倾斜和访问倾斜的问题
倾斜导致集群中部分节点数据多,压力大。解决方案分为前期和后期:前期是业务层面提前预测,哪些key是热点,在设计的过程中规避。后期是slot迁移,尽量将压力分摊(slot调整有自动rebalance、reshard和手动)。

5、读写分离
redis cluster默认所有从节点上的读写,都会重定向到key对接槽的主节点上。
可以通过readonly设置当前连接可读,通过readwrite取消当前连接的可读状态。
注意:主从节点依然存在数据不一致的问题

Redis可用性

通过主从集群实现高可用,slave节点向master节点发送syn请求同步命令。master节点会通过bgsave命令创建rdb文件将数据以二进制形式存储其中,然后将文件分发给slave节点。

tips: 1.bgsave是创建了子线程工作,不影响主线程; 2.主从结构以线性链表部署,不要图状结构部署

Redis缓存失效问题

缓存一致性模型

查询信息时,先从缓存中获取信息;缓存中没有则从数据库中获取;将值塞到缓存。

缓存击穿

查询一个不存在的key,查询会直接落到数据库上。如果黑客用不存在的key查询,很可能搞垮数据库。

解决思路:查询之前先判断目标数据是否存在,不存在的直接忽略。将流量拦截于缓存和数据库之前。

 

这里使用布隆过滤器:

布隆过滤器

demo示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
public void testBit() {
    String userId = "1001";
    int hasValue = Math.abs(userId.hashCode()); //key 做hash运算
    long index = (long) (hasValue % Math.pow(2, 32)); //hash值与数组长度取模
    Boolean bloomFilter = redisTemplate.opsForValue().setBit("user_bloom_filter", index, true);
    log.info("user_bloom_filter:{}",bloomFilter);
}

//缓存击穿解决方法
@Test
public void testUnEffactiveCache() {
    String userId = "1001";
    //1.系统初始化是init 布隆过滤器,将所有数据存于redis bitmap中
    //2.查询是先做判断,该key是否存在与redis中
    int hasValue = Math.abs(userId.hashCode()); //key 做hash运算
    long index = (long) (hasValue % Math.pow(2, 32)); //hash值与数组长度取模
    Boolean result = redisTemplate.opsForValue().getBit("user_bloom_filter", index);
    if (!result) {
        log.info("该userId在数据库中不存在:{}",userId);
        //return null;
    }
    //3.从缓存中获取
    //4.缓存中没有,从数据库中获取,并存放于redis中
}

布隆过滤器优缺点

优点:

内存空间占用少

缺点:

布隆过滤器需要不断维护,带来新的工作
布隆过滤器并不能精准过滤。(布隆过滤器判定不存在,100%不存在,判断为存在,则可能不存在的。)理论上Hash计算值是有碰撞的(不同的内容hash计算出同样的值),导致不存在的元素可能
会被判断为存在

为了减少hash碰撞,可以将key用几个hash算法获取index值。然而布隆过滤器并非需要拦截所有的请求,只需要将缓存击穿控制在一定的量即可。

缓存雪崩

当大量的key在统一时间过期,而这时又有大量的访问key,请求落到数据上,导致数据库崩溃。

解决办法:

Semaphore信号量限流

J.U.C包重要的并发编程工具类,又称“信号量”,控制多个线程争抢许可。
核心方法
acquire:获取一个许可,如果没有就等待,
release:释放一个许可。
典型场景
1、代码并发处理限流;

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.lvshen.demo.semaphore;

import java.util.Random;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * Description:信号量机制
 *
 * @author Lvshen
 * @version 1.0
 * @date: 2020/3/21 20:34
 * @since JDK 1.8
 */
public class SemaphoreDemo {
    public static void main(String[] args) {
        SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
        int count = 9;    //数量

        //循环屏障
        CyclicBarrier cyclicBarrier = new CyclicBarrier(count);

        Semaphore semaphore = new Semaphore(5);//限制请求数量
        for (int i = 0; i < count; i++) {
            String vipNo = "vip-00" + i;
            new Thread(() -> {
                try {
                    cyclicBarrier.await();
                    //semaphore.acquire();//获取令牌
                    boolean tryAcquire = semaphore.tryAcquire(3000L, TimeUnit.MILLISECONDS);
                    if (!tryAcquire) {
                        System.out.println("获取令牌失败:" + vipNo);
                    }

                    //执行操作逻辑
                    System.out.println("当前线程:" + Thread.currentThread().getName());
                    semaphoreDemo.service(vipNo);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }).start();
        }

    }

    // 限流控制5个线程同时访问
    public void service(String vipNo) throws InterruptedException {
        System.out.println("楼上出来迎接贵宾一位,贵宾编号" + vipNo + ",...");
        Thread.sleep(new Random().nextInt(3000));
        System.out.println("欢送贵宾出门,贵宾编号" + vipNo);
    }
}

容错降级

如果令牌被抢完(并发时还没来的及释放令牌),线程执行到这里时可以返回一个异常码。

 

redis集群方案

还有一种可能,如果redis key来不及删除,由于内存淘汰策略。会删除一些key,导致缓存失效。集群方案可以解决内存不足问题。

高并发下缓存不一致问题

根据缓存一致性模型:

查询信息时,a.1:先从缓存中获取信息;a.2:缓存中没有则从数据库中获取;a.3:将值塞到缓存。

更新数据时,b.1:更新数据库;b.2:删除缓存

如果查询和更新是两个线程,由于以上执行并非原子性,b.2可能会先于a.3执行。导致redis里面数据和数据库数据不一致。

解决方法

可以先将数据预热到redis中,去掉查询时存入redis的操作。再部署一个mysql服务,收集mysql日志。数据库数据发生变化的时候,通知缓存维护程序,把变化后的数据更新到到缓存里面。

 

关于数据库监听,阿里有一套开源框架 Canal ,可以监听mysql的数据变化。

Redis持久化

1.RDB快照:将数据以二进制形式写到文件中

2.AOF:将写命令以追加的形式写入到aof文件中

关于aof有下面几种形式:

a. redis没操作一次,写一次文件。优点是保证完整性,缺点是一致性会下降

b. 每秒钟将写命令写入到一个buffer中,当buffer中存满一定的数据,再写入到文件中(aof默认采用此种写入)

c.每次操作将写命令存入buffer中,之后再写入文件中

使用

默认是使用rdb恢复数据,如果开启aof,重启之后会加载aof文件恢复。当然生产环境上是混合使用。比如8点之前我使用rdb恢复,8点之后我使用aof恢复。

往期推荐

扫码二维码,获取更多精彩。或微信搜Lvshen_9,可后台回复获取资料

1.回复"java" 获取java电子书;


2.回复"python"获取python电子书;


3.回复"算法"获取算法电子书;


4.回复"大数据"获取大数据电子书;


5.回复"spring"获取SpringBoot的学习视频。


6.回复"面试"获取一线大厂面试资料


7.回复"进阶之路"获取Java进阶之路的思维导图


8.回复"手册"获取阿里巴巴Java开发手册(嵩山终极版)


9.回复"总结"获取Java后端面试经验总结PDF版


10.回复"Redis"获取Redis命令手册,和Redis专项面试习题(PDF)


11.回复"并发导图"获取Java并发编程思维导图(xmind终极版)

另:点击【我的福利】有更多惊喜哦。

Supongo que te gusta

Origin blog.csdn.net/wujialv/article/details/108438116
Recomendado
Clasificación