Redis 的那些事

文章目录

  • SQL 与 NOSQL 的区别
  • 什么是 redis?有什么优势?与memcached 的比较?
      • redis是什么?
      • 优势
      • 与memcached 的比较?
  • redis支持的数据类型
  • Redis 有哪几种数据淘汰策略?
  • 如何优雅的使用 redis 实现一个分布式锁?
  • 缓存穿透,雪崩,惊群效应
      • 惊群效应
          • 什么是惊群效应
          • 惊群效应消耗了什么,即有什么坏处
          • Linux 解决方案之 Accept
          • Linux 解决方案之 Epoll

SQL 与 NOSQL 的区别

什么是 redis?有什么优势?与memcached 的比较?

redis是什么?

Redis 是一个开源的,Key-Value 类型的内存存储系统,它可以用作数据库、缓存和消息中间件

它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。

Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

优势

  • 速度快,因为数据存放在内存中;
  • 支持多种数据类型,支持String,List,Set,Sorted set,Hash(底层相当于一个Map结构);
  • 提供了RDB和AOF两种持久化方式;
  • 丰富的特性:可用作缓存,消息,可以按key设置过期时间,过期后将会自动删除;
  • 支持事务,操作都是原子性。

Redis为什么这么快 ?

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

4、使用多路I/O复用模型,非阻塞IO

5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;(jemalloc,tcmalloc)

redis 为什么是单线程的?

  1. 不需要各种锁的性能消耗
  2. CPU消耗。采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU。
  3. 因为单线程的已经很快了,如果还不满足就起集群的方式来解决。

与memcached 的比较?

  1. memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类型

  2. redis 的速度比 memcached 快很多 redis 的速度比 memcached 快很多(memcached:单进程多线程模型)

3.redis 可以持久化其数据

为什么说Redis是单线程的以及Redis为什么这么快!

redis支持的数据类型

它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。

具体见:Redis 数据结构与对象

关于 bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 还需要有时间再研究一下,先把基础的全搞懂。

Redis 有哪几种数据淘汰策略?

Maxmemory配置,就是当指定的内存限制大小达到时,需要选择不同的行为,也就是回收内存的策略。

  1. volatile-lru:从设置 过期时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。

  2. volatile-ttl:除了淘汰机制采用LRU,策略基本上与volatile-lru相似,从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,ttl 值越大越优先被淘汰。

  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key。

  4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合。这个应该就是在redis数据结构与对象上讲的对象的空转时长来操作的。

  5. allkeys-random:从数据集(server.db[i].dict)中选择任意数据淘汰。

  6. no-enviction:禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略。

redis 的LRU也和MYSQL一样进行了对应于自己的优化,MYSQL的优化见:InnoDB 存储引擎架构与索引的实现

具体参考官网:http://www.redis.cn/topics/lru-cache.html
大方面就是: Redis单机数据库的实现,持久化以及过期淘汰策略

如何优雅的使用 redis 实现一个分布式锁?

参考:http://www.redis.cn/topics/distlock.html

缓存穿透,雪崩,惊群效应

  • 一、缓存雪崩
    缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
    • 解决方法:
      • (1)
      • (2)
      • (3)

惊群效应

什么是惊群效应

惊群效应(thundering herd)是指多进程(多线程)在同时阻塞等待同一个事件的时候(休眠状态),如果等待的这个事件发生,那么他就会唤醒等待的所有进程(或者线程),但是最终却只能有一个进程(线程)获得这个时间的“控制权”,对该事件进行处理,而其他进程(线程)获取“控制权”失败,只能重新进入休眠状态,这种现象和性能浪费就叫做惊群效应。

惊群效应消耗了什么,即有什么坏处
  • 上下文切换(context switch)过高会导致 CPU 像个搬运工,频繁地在寄存器和运行队列之间奔波,更多的时间花在了进程(线程)切换,而不是在真正工作的进程(线程)上面。直接的消耗包括 CPU 寄存器要保存和加载(例如程序计数器)、系统调度器的代码需要执行。间接的消耗在于多核 cache 之间的共享数据。
  • 为了确保只有一个进程(线程)得到资源,需要对资源操作进行加锁保护,加大了系统的开销。
Linux 解决方案之 Accept

Linux 2.6 版本之前,监听同一个 socket 的进程会挂在同一个等待队列上,当请求到来时,会唤醒所有等待的进程。

Linux 2.6 版本之后,通过引入一个标记位 WQ_FLAG_EXCLUSIVE,解决掉了 accept 惊群效应。

// 当accept的时候,如果没有连接则会一直阻塞(没有设置非阻塞)
// 其阻塞函数就是:inet_csk_accept(accept的原型函数)  
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
    ...  
    // 等待连接 
    error = inet_csk_wait_for_connect(sk, timeo); 
    ...  
}

static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
{
    ...
    for (;;) {  
        // 只有一个进程会被唤醒。
        // 非exclusive的元素会加在等待队列前头,
        // exclusive 的元素会加在所有非exclusive元素的后头。
        prepare_to_wait_exclusive(sk_sleep(sk), &wait,TASK_INTERRUPTIBLE);  
    }  
    ...
}

void prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)  
{  
    unsigned long flags;  
    // 设置等待队列的flag为 EXCLUSIVE,设置这个就是表示一次只会有一个进程被唤醒,
    // 我们等会就会看到这个标记的作用。  
    // 注意这个标志,唤醒的阶段会使用这个标志。
    wait->flags |= WQ_FLAG_EXCLUSIVE;  
    spin_lock_irqsave(&q->lock, flags);  
    if (list_empty(&wait->task_list))  
        // 加入等待队列  
        __add_wait_queue_tail(q, wait);  
    set_current_state(state);  
    spin_unlock_irqrestore(&q->lock, flags);  
}
Linux 解决方案之 Epoll

你可能感兴趣的:(Redis 的那些事)