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)。
Redis为什么这么快 ?
1、完全基于内存
,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单
,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO
;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;(jemalloc,tcmalloc)
redis 为什么是单线程的?
memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类型
redis 的速度比 memcached 快很多 redis 的速度比 memcached 快很多(memcached:单进程多线程模型)
3.redis 可以持久化其数据
为什么说Redis是单线程的以及Redis为什么这么快!
它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。
具体见:Redis 数据结构与对象
关于 bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 还需要有时间再研究一下,先把基础的全搞懂。
Maxmemory配置,就是当指定的内存限制大小达到时,需要选择不同的行为,也就是回收内存的策略。
volatile-lru:从设置 过期
时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰
。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。
volatile-ttl:除了淘汰机制采用LRU,策略基本上与volatile-lru相似,从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
,ttl 值越大越优先被淘汰。
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key。
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合。这个应该就是在redis数据结构与对象上讲的对象的空转时长来操作的。
。
allkeys-random:从数据集(server.db[i].dict)中选择任意数据淘汰。
no-enviction:禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错
,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略。
redis 的LRU也和MYSQL一样进行了对应于自己的优化,MYSQL的优化见:InnoDB 存储引擎架构与索引的实现
具体参考官网:http://www.redis.cn/topics/lru-cache.html
大方面就是: Redis单机数据库的实现,持久化以及过期淘汰策略
参考:http://www.redis.cn/topics/distlock.html
原有缓存失效,新缓存未到期间
(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
惊群效应(thundering herd)是指多进程(多线程)在同时阻塞等待同一个事件的时候(休眠状态),如果等待的这个事件发生,那么他就会唤醒等待的所有进程(或者线程),但是最终却只能有一个进程(线程)获得这个时间的“控制权”,对该事件进行处理,而其他进程(线程)获取“控制权”失败,只能重新进入休眠状态,这种现象和性能浪费就叫做惊群效应。
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);
}