-      01、不一样的Redis    -

提到Redis,大家一定会想到的几个点是什么呢?

高并发、KV存储、内存数据库、丰富的数据结构、单线程(版本6之前)等。

那么,接下来,上面提到的这些,都会一一给大家解答,带大家系统剖析一下Redis的架构设计魅力!

-      02、为什么会出现缓存?    -

一般情况下,数据都是在数据库中,应用系统直接操作数据库。当访问量上万,数据库压力增大,这个时候,怎么办呢?

有小伙伴会说,分库分表、读写分离。的确,这些确实是解决比较高的访问量的解决办法,但是,如果访问量更大,10万,100万呢?怎么分似乎都不解决问题吧,所以我们需要用到其他办法,来解决高并发带来的数据库压力。

这个时候,缓存出现了,顾名思义,就是先把数据缓存在内存中一份,当访问的时候,我们会先访问内存的数据,如果内存中的数据不存在,这个时候,我们再去读取数据库,之后把数据库中的数据再备份一份到内存中,这样下次读请求过来的时候,还是会直接先从内存中访问,访问到内存的数据了之后就直接返回了。这样做就完美的降低了数据库的压力,可能十万个请求进来,全部都访问了内存中备份的数据,而没有去访问数据库,或者说只有少量的请求访问到了数据库,这样真的是大大降低了数据库的压力,而且这样做也提高了系统响应,大家想一下,内存的读写速度是远远大于硬盘的读写速度的,一个请求进来读取的内存可以比读取硬盘快很多很多,用户的体验也会很高。

-      03、什么是缓存?    -

缓存原指CPU上的一种高速存储器,它先于内存与CPU交换数据,速度很快。

现在泛指存储在计算机上的原始数据的复制集,便于快速访问。

在互联网技术中,缓存是系统快速响应的关键技术之一。

-      04、缓存的三种读写模式    -

1、Cache Aside Pattern(常用)

Cache Aside Pattern(旁路缓存),是最经典的缓存+数据库读写模式。

读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。

更新的时候,先更新数据库,然后再删除缓存。

为什么是删除缓存,而不是更新缓存呢?

1、缓存的值是一个结构,hash、list等更新数据需要遍历;

2、懒加载,使用的时候才更新缓存,也可以采用异步的方式填充缓存。

高并发脏读的三种情况:

1、先更新数据库,在更新缓存;

update与commit之间,更新缓存,commit失败,则DB与缓存数据不一致。

2、先删除缓存,再更新数据库

update与commit之间,有新的读,缓存空,读DB数据到缓存,数据是旧的数据;

commit后DB为新的数据;

则DB与缓存数据不一致。

3、先更新数据库,再删除缓存(推荐)

update与commit之间,有新的读,缓存空,读DB数据到缓存,数据是旧的数据;

commit后DB为新的数据;

则DB与缓存数据不一致;

采用延时双删策略。

2、Read/Write Through Pattern

应用程序只操作缓存,缓存操作数据库;

Read-Through(穿透读模式/直读模式):应用程序读缓存,缓存没有,由缓存回源到数据库,并写入缓存;

Write-Through(穿透写模式/直写模式):应用程序写缓存,缓存写数据库。该种模式需要提供数据库的handler,开发较为复杂。

3、Write Behind Caching Pattern

应用程序只更新缓存;

缓存通过异步的方式将数据批量或合并后更新到DB中,不能时时同步,甚至会丢数据。

-      05、Redis又是什么?    -

Redis是一个高性能的开源的,C语言写的NoSQL(非关系型数据库)也叫做缓存数据库,数据保存在内存中。Redis是以key-value形式存储,和传统的关系型数据库不一样。不一定遵循传统数据库的那些基本要求。比如,不遵循SQL标准、事务、表结构等。Redis有非常丰富的数据类型,比如String,list,set,zset,hash等。

-      06、Redis可以做什么?    -

1、减轻数据库压力,提高并发量,提高系统响应时间

2、做Session分离

传统的Session是由自己的tomcat进行维护和管理的,在集群和分布式情况下,不同的tomcat要管理不同的session,只能在各个tomcat之间,通过网络和IO进行session复制,极大的影响了系统的性能。

Redis解决了这一个问题,将登陆成功后的session信息,存放在Redis中,这样多个tomcat就可以共享Session信息。

3、做分布式锁

一般Java中的锁都是多线程锁,是在一个进程中的,多个进程在并发的时候也会产生问题,也要控制时序性,这个时候Redis可以用来做分布式锁,使用Redis的setnx命令来实现。

4、电商购物车:

1、以用户id为key

2、商品id为field

3、商品数量为value

电商购物车操作:

1、添加商品:hset cart:1001 10088 1

2、增加数量:hincrby cart:1001 10088 1

3、商品总数:hlen cart:1001

4、删除商品:hdel cart:1001 10088

5、获取购物车所有商品:hgetall cart:1001

5、zset集合操作实现排行榜

1、点击新闻

ZINCRBY hotNews:20190819 1 守护香港

2、展示当日排行前十

ZREVRANGE hotNews:20190819 0 9 WITHSCORES

3、七日搜索榜单计算

ZUNIONSTORE hotNews:20190813-20190819 7

hotNews:20190813 hotNews:20190814... hotNews:20190819

4、展示七日排行前十

ZREVRANGE hotNews:20190813-201908109 0 9 WITHSCORES

用Redis做缓存,有这么有多优点,那么,缺点是不是也会对应的有很多呢?

1、额外的硬件支出

缓存是一种软件系统中以空间换时间的技术,需要额外的磁盘空间和内存空间来存储数据。

2、高并发缓存失效

在高并发的情况下,会出现缓存失效(缓存穿透,缓存雪崩,缓存击穿等问题)造成瞬间数据库访问量增大,甚至崩溃,所以这些问题是一定要去解决。

3、缓存与数据库数据同步

缓存与数据库无法做到数据的实时同步。

4、缓存并发竞争

多个Redis客户端同时对一个key进行set值的时候由于执行顺序引起的并发的问题。

-      07、Redis高性能设计    -

1、Redis是单线程的么?

Redis的单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的,这也是Redis对外提供键值存储服务的主要流程。但Redis的其他功能,比如持久化,异步删除,集群数据同步等,都是由额外的线程执行的。

2、Redis单线程为什么还能这么快?

这里我们在本地测试一下Redis支持的并发。

执行这条命令:./redis-benchmark get
结果:
============ get ==========
100000 requests completed in 1.02 seconds
50 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
0.00% <= 0.1 milliseconds
13.00% <= 0.2 milliseconds
55.85% <= 0.3 milliseconds
80.60% <= 0.4 milliseconds
92.57% <= 0.5 milliseconds
97.12% <= 0.6 milliseconds
99.06% <= 0.7 milliseconds
99.68% <= 0.8 milliseconds
99.86% <= 0.9 milliseconds
99.90% <= 1.0 milliseconds
99.90% <= 1.1 milliseconds
13.00% <= 0.2 milliseconds
55.85% <= 0.3 milliseconds
80.60% <= 0.4 milliseconds
92.57% <= 0.5 milliseconds
97.12% <= 0.6 milliseconds
99.06% <= 0.7 milliseconds
99.68% <= 0.8 milliseconds
99.86% <= 0.9 milliseconds
99.90% <= 1.0 milliseconds
99.90% <= 1.1 milliseconds
99.90% <= 1.2 milliseconds
99.91% <= 1.3 milliseconds
99.93% <= 1.4 milliseconds
99.95% <= 1.5 milliseconds
99.97% <= 1.6 milliseconds
99.98% <= 1.7 milliseconds
99.99% <= 1.8 milliseconds
99.99% <= 1.9 milliseconds
100.00% <= 2 milliseconds
100.00% <= 2 milliseconds
98328.42 requests per second

这里我们可以看到,每秒的话,差不多可以支持小10万的并发,这已经是一个很恐怖的数据了。

因为它的所有数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能消耗问题。正因为Redis是单线程的,所以要小心使用Redis命令,对于那些耗时的指令(比如keys),一定要谨慎使用,一不小心就可能导致Redis卡顿。

Redis单线程如何处理那么多并发客户端连接?

Redis的IO多路复用:Redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,一次放到文件事件分派器,事件分派器将事件分发给事件处理器。

-      08、Redis核心设计原理    -

Redis作为key-value存储系统,数据结构如下:

一个Redis实例对应多个DB,一个DB对应多个key,key一般都是string的,后面的value叫做RedisObject,不是说value就是string,list,map这些,而是说这些所有的类型,都被Redis封装成了一个叫RedisObjcet,具体是哪个类型呢?这里是用指针的方式来指向具体是哪个类型。

为什么要这么做,主要是为了提高Redis的性能。

PS:这里插一句,为什么使用指针的方式要比使用对象本身的方式性能更好呢?

这里有两点:

第一点是动态分配;第二是指针一大特点在于你只需要在前面声明一下指针指向的类型(而如果要使用实际的对象,你还需要定义一下)。这样你就能降低你的编译单元之间的耦合性从而减少编译时间。

1、RedisDB结构

Redis没有表的概念,Redis实例所对应的DB以编号区分,DB本身就是key的命名空间

比如:user:1000作为key的值,表示在user这个命名空间下id为1000的元素,类似于user表的id=1000的行。

2、SDS字符串

众所周知,Redis是用C语言来实现的,在C语言中,String这个类型,其实就是一个char数组,比如char data[]="xxx\0",但是,客户端往Redis发送set命令,是可以发任意的字符串的,是没有校验的,所以假如我们发了一个字符串xx\0xx,那么\0后面的xx是不会读的,只会读前面的xx(C语言中用"\0"表示字符串结束,如果字符串本身就有"\0"字符,字符串就会被截断)。

所以Redis自实现了一个string叫sds,sds中记录了一个len和一个char buf[],len用来记录buf的长度,比如char buf[] = "xx\0xx",那么len就是5,sds中还有一个比较重要的属性就是free,表示还剩余多少。

free是通过改变len来计算,比如"xxx1234" 改成 "xxx123456",那么会按照(len+addlen)*2=18 来扩容,这个时候len变成了9,free就是18-9也变成了9。

例如:

char buf[] = "xxx1234" 改成 "xxx123456" //这里的buf是柔性数组
free:12  变成free:10
len:8    变成len:10

Redis这样设计SDS有什么好处:

1、二进制安全的数据结构;

2、提供了内存预分配机制,避免了频繁的内存分配;

3、兼容C语言的函数库;

4、有单独的统计变量len和free,可以方便的得到字符串长度,这样就避免了读取不完整的风险;

5、内容存放在柔性数组buf中,SDS对上层暴露的指针不是指向结构体SDS的指针,而是直接指向柔性数组buf的指针。上层可像读取C字符串一样读取SDS的内容,兼容C语言处理字符串的各种函数。

这里解释一下什么叫柔型数组?

柔型数组即数组大小待定的数组,C语言中结构体的最后一个元素可以是大小未知的数组,也就是所谓的0长度,所以我们可以用结构体来创建柔性数组。柔性数组主要用途是为了满足需要变长度的结构体,为了解决使用数组时内存的冗余和数组的越界问题

这也是Redis3.2之前所实现的。

未完待续,敬请期待下篇分析。

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

深度剖析不一样的Redis架构设计!相关推荐

  1. Redis架构设计实现

    Redis主从复制 单个redis读写能力有限 我们可以采用多个redis来提高处理并发能力 master负责读写,并将数据同步到slave ,从节点只负责读的操作   第一步: 将之前的redis0 ...

  2. 揭开芯面纱 主流平板电脑方案深度剖析之ARMv5,v6,v7架构阵营

    一.全文框架 按阵营分为: Ⅰ.ARMv5架构阵营,代表核心: ARM9核心 Ⅱ.ARMv6架构阵营,代表核心: ARM11核心 Ⅲ.ARMv7架构阵营,代表核心: ① 高通Scorpion核心 ②C ...

  3. Kafka剖析:Kafka背景及架构介绍

    Kafka剖析:Kafka背景及架构介绍 <Kafka剖析:Kafka背景及架构介绍> <Kafka设计解析:Kafka High Availability(上)> <K ...

  4. Kafka的生产者优秀架构设计

    来自:架构之美 前言 Kafka 是一个高吞吐量的分布式的发布订阅消息系统,在全世界都很流行,在大数据项目里面使用尤其频繁.笔者看过多个大数据开源产品的源码,感觉 Kafka 的源码是其中质量比较上乘 ...

  5. Kafka 的生产者优秀架构设计

    作者 | 孙玄 责编 | 郭芮 来源 | 架构之美(ID:beautyArch) Kafka 是一个高吞吐量的分布式的发布订阅消息系统,在全世界都很流行,在大数据项目里面使用尤其频繁.笔者看过多个大数 ...

  6. 三大亮点抢先看,顶象CTO解析业务安全平台架构设计

    业务安全无小事. 上一期顶象<业务安全大讲堂>从业务安全的行业背景.黑灰产产业链.典型场景的攻防分析以及业务安全风险防控的思路讲起,带大家认识了业务安全是什么. 本期顶象<业务安全大 ...

  7. 深度剖析CloudFoundry的架构设计

    VMware在今年4月份突然发布了业内第一个开源的PaaS--CloudFoundry.发布至今的这几个月里,笔者一直关注它的演进,并从它的架构设计中获益良多,觉得有必要写出来与大家分享一下. 本文会 ...

  8. 深度剖析:Redis分布式锁到底安全吗?看完这篇文章彻底懂了!

    ‍‍‍‍‍‍‍‍‍‍‍‍阅读本文大约需要 20 分钟. 大家好,我是 Kaito. 这篇文章我想和你聊一聊,关于 Redis 分布式锁的「安全性」问题. Redis 分布式锁的话题,很多文章已经写烂了 ...

  9. 深度剖析:Redis 分布式锁到底安全吗?看完这篇文章彻底懂了!

    作者 | Kaito 来源 | 水滴与银弹 阅读本文大约需要 20 分钟. 大家好,我是 Kaito. 这篇文章我想和你聊一聊,关于 Redis 分布式锁的「安全性」问题. Redis 分布式锁的话题 ...

最新文章

  1. JQuery操作下拉框
  2. C++关系运算符和关系表达式
  3. 3、顺序表、内存、类型、python中的list
  4. 论设计,需求和编码三者的关系
  5. matlab 判断一个数组中有没有重复的元素
  6. Redis服务器的启动过程分析
  7. 排序算法入门之简单选择排序
  8. 在android studio中如何创建一个类来继承另外一个类_在Android使用Transition API检测用户活动...
  9. ARM指令集与Thumb指令集--区别关联--汇编指令 BX LR ; 跳转回LR地址处,既可以是ARM模式也可以是Thumb模式
  10. java类和对象:封装、继承和多态
  11. 自定义微信小程序导航(兼容各种手机)
  12. 关于哈希表,你该了解这些!
  13. 傅里叶、拉普拉斯、z变换常用公式合集
  14. winform遍历bartender_C# 调用Bartender打印的2种方式
  15. Directx12 曲面细分详解
  16. 分支定界-附Python代码
  17. 学到了林海峰,武沛齐讲的Day17完-6 文件操作
  18. c语言程序从哪里开始执行
  19. 开源节流系列之工程施工篇
  20. 如画的水乡,如画的同里58

热门文章

  1. c语言gsl,从GSL库获取C gsl_fit_linear()函数中的线性回归的p值
  2. ad 原理图差分线_usb hub原理图及pcb设计
  3. 【Java】排序算法 之 【快速排序】 总结
  4. Java程序中Socket(关于客户端和服务端实例)
  5. 商品促销php设计,php – 什么是生成大量独特促销代码的最佳方式?
  6. es获取最大时间的记录_ES查询一段时间内某一循环时间段的数据
  7. 蓝桥杯国赛-矩阵计数三种解法-java实现
  8. java怎么复制动态数组_Java 数组排序复制等操作(Java Arraycopy)
  9. python tqdm_Python基础 | 一个被忽视的神器tqdm
  10. 计算机改名后无法上网,CDEF磁盘分区无法修改名字修复方法