redis笔记

redis常见使用场景

1 缓存

缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多

2 排行榜

很多网站都有排行榜应用的,如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。

3 计数器

电商网站商品的浏览量、视频网站视频的播放数等都需要计数功能。为了保证数据实时性,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景

4 分布式会话

集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。

5 分布式锁

在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。

6 社交网络

点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。

7 消息队列

消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。

redis数据类型

字符串:可以存储字符串、整数或者浮点数

常用命令:GET(获取key对应的值)、SET(设置key对应的值)、DEL(删除key和值)

列表:是一个链表,链表上的每个节点都包含一个字符串

常用命令:LPUSH和RPUSH(将元素推入链表的左端和右端)、LPOP和RPOP(从链表的左端和右端弹出元素)、LINDEX(获取链表在指定位置上的一个元素)、LRANG(获取链表在指定范围的元素)

集合:可以存储多个字符串,字符串都各不相同

常用命令:SADD(将元素添加到集合)、SREM(将元素从集合中移除)、SISMEMBER(快速检查一个元素是否存在于集合中)、SMEMBERS(获取集合包含的所有元素)、SINTER(交集计算)、SUNION(并集计算)、SDIFF(差集计算)

哈希表:包含键值对的无序散列表,和字符串一样,散列存储的值既可以是字符串,又可以是数字值,并且可以对数字值执行自增、自减操作

常用命令:HSET(在散列表中设置键值对)、HGET(在散列表中获取指定键的值)、HGETALL(获取散列表所有的键值对)、HDEL(从散列表中删除键值对)

有序集合:字符串成员(member)和浮点数分值(score)之间的有序映射

常用命令:ZADD(将指定分值的成员添加到有序集合)、ZRANGE(根据元素在有序集合中的位置,从有序集合中获取多个元素)、ZRANGEBYSCORE(获取有序集合在给定分值范围内的所有元素)、ZREM(从有序集合中移除成员)

redis数据结构

1 简单动态字符串

redis底层所有字符串都使用的是简单动态字符串,具有如下三个特性:可以高效执行长度计算、可以高效执行追加操作、二进制安全(程序不对字符串里保存的数据做任何假设,数据可以是以\0结尾的C字符串、也可以是单纯的字节数组,或者其他格式的数据)

struct sdshdr {

        int len;  // buf已使用长度    

        int free;  //buf剩余可用长度

        char buf[];  //实际保存字符串数据的地方

}

第一次创建时,len等于buf长度,free等于0

执行append操作时,如果新字符串的总长度小于SDS_MAX_PREALLOC,为字符串分配两倍所需长度的空间、否则分配所需长度加上SDS_MAX_PREALLOC数量的空间

如果执行APPEND操作的键很多,字符串体积又很多的话,这种预分配的策略会浪费内存,可以修改redis服务器,让它定期释放一些字符串键的预分配空间,从而更有效利用内存

2 双端链表

普通的双向链表,无特殊的地方

3 字典

字典结构体如下

typedef struct  dict {

.........

        dictht ht[2];  //哈希表 2个

        int rehashidx; // rehash标志,值为-1时表示rehash未进行

}dict;

哈希表结构体如下

typedef  struct  dictht {

............

        dictEntry  **table; // 哈希桶

        unsigned long size; // 指针数组的大小

        unsigned long used; // 哈希表现有节点的数量

}dictht;

哈希表节点结构体如下

typedef  struct  dictEntry {

        void *key; // 键

        union {

                void *val;

                uint64_t u64;

                int64_t s64;

        } v;  // 值

        struct  dictEntry *next;// 后继节点指针,采用链表法解决哈希碰撞

}dictEntry;

为了在字典的键值对不断增多的情况下仍然保持良好的性能,字典需要对使用的哈希表进行rehash扩容操作,尽量将used和size的比率维持在1:1, 比率ratio(装载因子)= used/size,rehash开始的时机:1 自然rehash:ratio >=1,且变量dict_can_resize为真  2 强制rehash:ratio大于变量dict_force_resize_ratio(默认为5)

dict_can_resize什么时候为假?redis 使用子进程对数据库执行后台持久化任务时,为了最大化的利用系统的copy on write机制,程序会暂时将dict_can_resize设置为假,避免执行自然rehash、减少程序对内存的碰撞

rehash的过程:

    1 创建一个比ht[0]更大的哈希表ht[1]

    2 将ht[0]中所有的键值对全部迁移到ht[1]

    3 将原有的ht[0]清空,将ht[1]替换成新的ht[0]

渐进式rehash:在一个有很多键值对的字典里,某个用户在添加新的键值对时触发了rehash过程,如果rehas将所有的键值对都迁移完毕后才将结果返回给用户,这样用户体验是很不友好的。

渐进式rehash时机:

    1 被动rehash:每次执行添加、查找、删除操作时,将哈希表上第一个不为空的索引上的所有节点全部迁移到ht[1]

    2 主动rehash:redis服务器常规任务执行时

rehash过程中,所有的查找和删除操作,除了在ht[0]上进行,还需要在ht[1]上进行,所有的添加操作,都只在ht[1]上进行

4 跳表

跳表从高层索引开始,向下层层查找,可以在对数时间复杂度下完成查找、添加、删除操作。跳表最底层链表是按数据从小到大排序的,因此跳表可以非常方便的进行范围查找

跳表节点数据结构

typedef  struct  zskiplistNode {

        robj *obj; // member对象

        double score;  // 分值

        struct  zskiplistNode *backward; // 后退指针

        struct  zskiplistLevel {  // 层

                struct  zskiplistNode * forward; // 前进指针

                unsigned  int  span;  // 层跨越的节点数量

        }level[];

}zskiplistNode;

跳表索引动态更新:当我们不停的向跳表中添加数据,如果不更新索引,就有可能出现两个节点之间数据非常多的情况,极端情况下会退化成单链表。跳表通过随机函数来维持平衡性,当我们向跳表中插入数据时,可以选择同时将这个数据插入到部分索引层,通过随机函数来确定将这个节点插入到哪几层索引中,如果随机函数生成k,就将此节点插入第一层到第k层这k层索引中

5 整数集合

typedef  struct intset {

        uint32_t   encoding; // 元素使用的类型长度

        uint32_t   length; // 元素个数

        int8_t   contents[]; // 保存元素的数组

}intset;

contents数组有以下两个特性:1 没有重复元素 2 元素在数组中从小到大排列

6 压缩列表




pre_entry_length可能占用1字节也可能占用5字节

1字节:前一个entry的长度小于254字节

5字节:前一个entry的长度大于等于254字节

encoding和length两部分一起决定content部分的数据类型和长度

encoding长度两个bit,00、 01、 10 表示content部分保存这字符数组,11表示content部分保存着整数



redis数据类型的底层实现

1 字符串

字符串采用简单动态字符串来实现

2 哈希表

键值对比较少时采用压缩列表


键值对多时采用字典

3 列表

列表元素比较少时采用压缩列表

列表元素多时采用双端链表

4 集合

集合元素比较少时,并且集合的元素全部可以表示为long long类型值时,采用整数集合

集合元素比较多或者集合元素中有不能被表示为long long类型值时,采用字典,集合的元素保存到字典的键里边,字典的值统一设置为null

5 有序集合

集合元素比较少时,采用压缩列表

每个有序集合的元素以两个相邻的ziplist节点表示,第一个节点保存元素的member域,第二个节点保存元素的score域。多个元素之间按score值从小到大排序

集合元素多时,采用字典加跳表

使用字典,将member作为键,score作为值,可以在O(1)复杂度内检查member是否在有序集合中、取出member对应的分值

使用跳表,可以高效的处理范围性查找

为什么采用整数集合、压缩列表

整数集合、压缩列表采用了时间换空间的策略,时间复杂度变高了,但是内存节省了

整数集合、字典占用内存比较:字典需要存储两个hash table、每个hash table需要存储bulket和entry,每个entry中既有key、又有value、又有next指针,而整数集合中只需存储key

压缩列表、双向链表、字典、跳表占用内存比较:压缩列表pre_entry_length最多占用5字节、econding和length最多占用5字节。双向链表64位系统pre和next指针各占8字节。字典上边已分析,需要更多的额外内存。跳表需要双向链表和额外的层。这三个都比压缩列表占用更多的内存

为什么redis性能高

1 完全基于内存,绝大部分请求是内存操作

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

3 使用多路I/O复用模型(epoll),非阻塞IO

4 底层高效的数据结构

redis事务

当客户端处于非事务状态下时,所有发送给服务器端的命令都会立即被服务器执行。但是,当客户端进入事务状态后(MULTI命令后进入事务状态),服务器在收到来自客户端的命令时,不会立即执行命令,而是将这些命令全部放进一个事务队列里,然后返回QUEUED,表示命令已入队。

当执行EXEC命令时,服务器根据客户端所保存的事务队列,以先进先出的方式执行事务队列中的命令。

DISCARD命令用于取消一个事务,清空客户端的整个事务队列,将客户端从事务状态调整回非事务状态。

WATCH命令只能在客户端进入事务状态前执行,在事务状态下执行WATCH会引发一个错误。WATCH命令用于在事务开始之前监听任意数量的键,当调用EXEC命令执行事务时,如果任意一个被监视的键被其他客户端修改了,整个事务不再执行,返回失败。

LUA脚本

redis事务可以确保事务执行过程中,键不被其他客户端并发修改(如果被其他客户端修改,事务失败),但是并不能高效执行CAS操作,CAS操作包括两个步骤:读取数据比较、设置数据。redis 2.6版本通过内嵌对lua环境的支持,可以高效执行CAS操作

REDIS数据持久化

redis运行时,数据维持在内存中,为了让这些数据在redis重启之后仍然可用,redis提供了rdb和aof两种持久化模式

rdb

rdb程序将当前内存中的数据库数据库快照保存到磁盘文件中,redis启动时,rdb程序可以通过载入rdb文件来恢复数据库。rdbSave用于生成rdb文件到磁盘,rdbLoad用于将rdb文件中的数据重新载入内存

SAVE命令会直接调用rdbSave函数,阻塞redis主进程,直到保存完成为止。主进程阻塞期间,服务器不能处理客户端的任何请求。

BGSAVE命令会fork一个子进程,子进程调用rdbSave生成rdb文件

优点:rdb对于文件备份和灾难恢复来说是个不错的选择,如果数据集比较大,采用rdb进行恢复的效率会更高

缺点:系统在定时持久化之前宕机,没来及写入磁盘的数据都将丢失

aof

aof以协议文本的方式,将所有对数据库进行过写入的命令及其参数记录到aof文件

aof有三种保存模式:不保存、每一秒钟保存一次、每执行一个命令保存一次

优点:数据安全性更高

缺点:对于相同数量的数据集,aof文件的大小比rdb文件大,数据恢复时间比rdb长。运行效率比rdb低

二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)

redis缓存和数据库双写一致性问题

只能保证最终一致性,强一致性的数据不能放缓存

读操作:先读缓存,缓存没有的话,读数据库,然后将从数据库中读出的数据放入缓存

更新操作:

1 先更新数据库,然后删除缓存,待下次读取时更新缓存(更新数据频繁、读取数据不频繁场景),删除缓存可能会失败,可以增加一个消息队列进行补偿

2 先更新数据库,然后更新缓存,下次读取时读到最新的数据(更新跟读取一样频繁的场景),更新缓存可能会失败,可以增加一个消息队列进行补偿

redis过期策略和内存淘汰机制

过期策略:定期删除+惰性删除

定期删除:redis每隔100ms随机抽取部分key进行检查,如果有过期的key,将过期的key删除

惰性删除:在读取某个key的时候,redis会检查这个key是否设置了过期时间,是否超时,如果超时,将这个key删除

内存淘汰机制:如果定期删除没有删除key,也没有读取key进行惰性删除,那么内存使用会越来越高。需要进行内存淘汰。内存淘汰可选配置:

noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。

allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key。推荐使用。

allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。应该也没人用吧,你不删最少使用 Key,去随机删。

volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。不推荐。

volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。依然不推荐。

volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。不推荐。

如果key没有设置 expire ,那么 volatile-lru,volatile-random 和 volatile-ttl 策略的行为,和 noeviction(不删除) 基本上一致

缓存穿透

黑客查询一个一定不存在的数据,这个不存在的数据每次请求都到数据库查询,造成数据库压力

解决方法:

1 如果查询的数据不存在,仍然把这个空结果进行缓存,但设置一个比较短的过期时间,最长不超过5分钟

2 采用布隆过滤器,在控制层先进行校验,不存在直接丢弃

详解布隆过滤器的原理、使用场景和注意事项 - 简书

缓存雪崩

同一时间,缓存大面积失效,大量的查询都请求到数据库,造成缓存雪崩

解决方法:

1 不同的key,缓存失效时间加个随机值,设置不同的过期时间

2 缓存失效后,通过锁或者队列来控制读数据库写缓存线程的数量

3 二级缓存,两个缓存设置不同的过期时间。比如缓存A设置为短期,缓存B设置为长期

缓存预热

缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求时先查询数据库

线上如何获取redis所有的keys

keys命令可以获取redis所有的key

smembers命令可以获取集合所有的元素

在一个大的线上数据系统中,使用keys、smembers可能会造成阻塞

可以用下边四个scan命令来代替

SCAN命令用于迭代当前数据库中的数据库键。

SSCAN命令用于迭代集合键中的元素。

HSCAN命令用于迭代哈希键中的键值对。

ZSCAN命令用于迭代有序集合中的元素(包括元素成员和元素分值)

SCAN cursor [MATCH pattern] [COUNT count]

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容

  • 须知:个人读书笔记,很多点没有具体描述,只是大体罗列下,立个flag后面完善。 文章罗列了基本知识(类型,小功能)...
    alivs阅读 867评论 0 17
  • Redis是啥 Redis是一个开源的key-value存储系统,由于拥有丰富的数据结构,又被其作者戏称为数据结构...
    一凡呀阅读 1,173评论 0 5
  • Redis五大数据类型及其常用操作 字符串 set key value ——设置属性值get key ——获取值g...
    蜡笔没了小新_e8c0阅读 114评论 0 1
  • 本文为笔者对在学习Redis过程中所收集资料的一个总结,目的是为了以后方便回顾相关的知识,大部分为非原创内容。特此...
    EakonZhao阅读 14,425评论 0 9
  • redis简介 redis是开源,BSD许可,高级的key-value存储系统.可以用来存储字符串,哈希结构,链表...
    SithCait阅读 235评论 0 0