一、说说Redis为什么快

在这里插入图片描述

1.1 基于内存存储实现

内存读写是比在磁盘快很多的,Redis基于内存存储实现的数据库,相对于数据存在磁盘的MySQL数据库,省去磁盘I/O的消耗。

1.2 高效的数据结构

Mysql索引为了提高效率,选择了B+树的数据结构。其实合理的数据结构,就是可以让你的应用/程序更快。先看下Redis的数据结构&内部编码图:

在这里插入图片描述

1.2.1 SDS简单动态字符串

在这里插入图片描述
① 字符串长度处理:Redis获取字符串长度,时间复杂度为O(1),而C语言中,需要从头开始遍历,复杂度为O(n);

② 空间预分配:字符串修改越频繁的话,内存分配越频繁,就会消耗性能,而SDS修改和空间扩充,会额外分配未使用的空间,减少性能损耗。

③ 惰性空间释放:SDS 缩短时,不是回收多余的内存空间,而是free记录下多余的空间,后续有变更,直接使用free中记录的空间,减少分配。

1.2.2 字典

Redis 作为 K-V 型内存数据库,所有的键值就是用字典来存储。字典就是哈希表,比如HashMap,通过key就可以直接获取到对应的value。而哈希表的特性,在O(1)时间复杂度就可以获得对应的值。
在这里插入图片描述① 跳跃表是Redis特有的数据结构,就是在链表的基础上,增加多级索引提升查找效率。

② 跳跃表支持平均 O(logN),最坏 O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点。

1.3 合理的数据编码

Redis 支持多种数据类型,每种基本类型,可能对多种数据结构。什么时候,使用什么样数据结构,使用什么样编码,是redis设计者总结优化的结果。

① String:如果存储数字的话,是用int类型的编码;如果存储非数字,小于等于39字节的字符串,是embstr;大于39个字节,则是raw编码。

② List:如果列表的元素个数小于512个,列表每个元素的值都小于64字节(默认),使用ziplist编码,否则使用linkedlist编码

③ Hash:哈希类型元素个数小于512个,所有值小于64字节的话,使用ziplist编码,否则使用hashtable编码。

④ Set:如果集合中的元素都是整数且元素个数小于512个,使用intset编码,否则使用hashtable编码。

⑤ Zset:当有序集合的元素个数小于128个,每个元素的值小于64字节时,使用ziplist编码,否则使用skiplist(跳跃表)编码

1.4 合理的线程模型

1.4.1、I/O 多路复用

在这里插入图片描述
多路I/O复用技术可以让单个线程高效的处理多个连接请求,而Redis使用用epoll作为I/O多路复用技术的实现。并且,Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。

二、Redis有几种数据结构,底层分别是怎么存储

常用的,Redis有以下这五种基本类型:

String(字符串)
Hash(哈希)
List(列表)
Set(集合)
zset(有序集合) 它还有三种特殊的数据结构类型
	Geospatial
	Hyperloglog
	Bitmap

2.1 Redis 的五种基本数据类型

在这里插入图片描述
① String(字符串)

简介:String是Redis最基础的数据结构类型,它是二进制安全的,可以存储图片或者序列化的对象,值最大存储为512M

简单使用举例: set key value、get key等

应用场景:共享session、分布式锁,计数器、限流。

内部编码有3种,int(8字节长整型)/embstr(小于等于39字节字符串)/raw(大于39个字节字符串)

② Hash(哈希)

简介:在Redis中,哈希类型是指v(值)本身又是一个键值对(k-v)结构

简单使用举例:hset key field value 、hget key field

内部编码:ziplist(压缩列表) 、hashtable(哈希表)

应用场景:缓存用户信息等。

③ List(列表)

简介:列表(list)类型是用来存储多个有序的字符串,一个列表最多可以存储2^32-1个元素。

简单实用举例: lpush key value [value ...] 、lrange key start end

内部编码:ziplist(压缩列表)、linkedlist(链表)

应用场景:消息队列,文章列表

④ Set(集合)

简介:集合(set)类型也是用来保存多个的字符串元素,但是不允许重复元素

简单使用举例:sadd key element [element ...]、smembers key

内部编码:intset(整数集合)、hashtable(哈希表)

应用场景:用户标签,生成随机数抽奖、社交需求。

⑤ 有序集合(zset)

简介:已排序的字符串集合,同时元素不能重复

简单格式举例: zadd key score member [score member ...],zrank key member

底层内部编码:ziplist(压缩列表)、skiplist(跳跃表)

应用场景:排行榜,社交需求(如用户点赞)。

2.2 Redis 的三种特殊数据类型

HyperLogLog:用来做基数统计算法的数据结构,如统计网站的UV。

Geo:Redis3.2推出的,地理位置定位,用于存储地理位置信息,并对存储的信息进行操作。

Bitmaps :用一个比特位来映射某个元素的状态,在Redis中,它的底层是基于字符串类型实现的,可以把bitmaps成作一个以比特位为单位的数组

三、 Redis有几种持久化方式

	Redis是基于内存的非关系型K-V数据库,既然它是基于内存的,如果Redis服务器挂了,数据就会丢失。为了避免数据丢失了,Redis提供了持久化,即把数据保存到磁盘。

Redis提供了RDB和AOF两种持久化机制,它持久化文件加载流程如下:
在这里插入图片描述

3.1 RDB

RDB,就是把内存数据以快照的形式保存到磁盘上。

什么是快照?可以这样理解,给当前时刻的数据,拍一张照片,然后保存下来。

RDB持久化,是指在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。执行完操作后,在
指定目录下会生成一个dump.rdb文件,Redis 重启的时候,通过加载dump.rdb文件来恢复数据。RDB触发机制主要有以下几种:
1 在指定的时间间隔内,执行指定次数的写操作
2 执行save(阻塞, 只管保存快照,其他的等待) 或者是bgsave (异步)命令
3 执行flushall 命令,清空数据库所有数据,意义不大。
4 执行shutdown 命令,保证服务器正常关闭且不丢失任何数据,意义也不大

RDB 的优点

适合大规模的数据恢复场景,如备份,全量复制等

RDB缺点

没办法做到实时持久化/秒级持久化。

新老版本存在RDB格式兼容问题

3.2 AOF

AOF(append only file) 持久化,采用日志的形式来记录每个写操作,追加到文件中,重启时再重新执行AOF文件中的命令来恢复数据。它主要解决
数据持久化的实时性问题。默认是不开启的。

AOF的工作流程如下:
在这里插入图片描述
AOF的优点:

数据的一致性和完整性更高

AOF的缺点

AOF记录的内容越多,文件越大,数据恢复变慢。

四、 多线程情况下,如何保证线程安全

加锁,比如悲观锁 select for update,sychronized 等,如,乐观锁,乐观锁如 CAS 等,还有 redis分布式锁 等等。

五 、 用过volatile吗?它是如何保证可见性的,原理是什么

volatile 关键字是Java虚拟机提供的的最轻量级的同步机制,它作为一个修饰符, 用来修饰变量。它保证变量对所有线程可见性,禁止指令重排,但
是不保证原子性。

我们先来看下java内存模型(jmm):

Java虚拟机规范试图定义一种Java内存模型,来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台上都能达到一致的内存访问
效果。

Java内存模型规定所有的变量都是存在主内存当中,每个线程都有自己的工作内存。这里的变量包括实例变量和静态变量,但是不包括局部变量,因为
局部变量是线程私有的。

线程的工作内存保存了被该线程使用的变量的主内存副本,线程对变量的所有操作都必须在工作内存中进行,而不能直接操作操作主内存。并且每个线
程不能访问其他线程的工作内存。

在这里插入图片描述

volatile变量,保证新值能立即同步回主内存,以及每次使用前立即从主内存刷新,所以我们说volatile保证了多线程操作变量的可见性。

volatile保证可见性跟内存屏障有关。我们来看一段volatile使用的demo代码:
public class Singleton {

	private volatile static Singleton instance;
	
	private Singleton{}
	
	public static Singleton getInstance{	
		if(instance == null) {
			synchronized (Singleton.class) {	
				if(instance == null) {		
					instance = new Singleton;	
				}
			}
		}
	returninstance;
	}
}
lock指令相当于一个内存屏障,它保证以下这几点:

重排序时不能把后面的指令重排序到内存屏障之前的位置

将本处理器的缓存写入内存

如果是写入动作,会导致其他处理器中对应的缓存无效。

第2点和第3点就是保证volatile保证可见性的体现嘛

六、 MySQL的索引结构,聚簇索引和非聚簇索引的区别

一个表中只能拥有一个聚集索引,而非聚集索引一个表可以存在多个。

索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有
一个指针指向对应的数据块。

聚集索引:物理存储按照索引排序;非聚集索引:物理存储不按照索引排序

七、MySQL有几种高可用方案,你们用的是哪一种

主从或主主半同步复制

半同步复制优化

高可用架构优化

共享存储

分布式协议

7.1 主从或主主半同步复制

用双节点数据库,搭建单向或者双向的半同步复制。架构如下:
在这里插入图片描述

通常会和proxy、keepalived等第三方软件同时使用,即可以用来监控数据库的健康,又可以执行一系列管理命令。如果主库发生故障,切换到备库后
仍然可以继续使用数据库。

这种方案优点是架构、部署比较简单,主机宕机直接切换即可。 缺点是完全依赖于半同步复制,半同步复制退化为异步复制,无法保证数据一致性;另
外,还需要额外考虑 haproxy、keepalived的高可用机制。

7.2 半同步复制优化

半同步复制机制是可靠的,可以保证数据一致性的。但是如果网络发生波动,半同步复制发生超时会切换为异步复制,异复制是无法保证数据的一致性的。因此,可以在半同复制的基础上优化一下,尽可能保证半同复制。如 双通道复制方案
在这里插入图片描述

优点:这种方案架构、部署也比较简单,主机宕机也是直接切换即可。比方案1的半同步复制,更能保证数据的一致性。

缺点:需要修改内核源码或者使用mysql通信协议,没有从根本上解决数据一致性问题。

7.3 高可用架构优化

保证高可用,可以把主从双节点数据库扩展为数据库集群。Zookeeper可以作为集群管理,它使用分布式算法保证集群数据的一致性,可以较好的避免网络分区现象的产生。
在这里插入图片描述

优点:保证了整个系统的高可用性,扩展性也较好,可以扩展为大规模集群。

缺点:数据一致性 仍然依赖于原生的mysql半同步复制;引入Zookeeper使系统逻辑更复杂。

7.4 共享存储

共享存储实现了数据库服务器和存储设备的解耦,不同数据库之间的数据同步不再依赖于MySQL的原生复制功能,而是通过磁盘数据同步的手段,来保证数据的一致性。

DRBD磁盘复制

DRBD是一个用软件实现的、无共享的、服务器之间镜像块设备内容的存储复制解决方案。主要用于对服务器之间的磁盘、分区、逻辑卷等进行数据镜像,
当用户将数据写入本地磁盘时,还会将数据发送到网络中另一台主机的磁盘上,这样的本地主机(主节点)与远程主机(备节点)的数据就可以保证实时同
步。常用架构如下:

在这里插入图片描述

当本地主机出现问题,远程主机上还保留着一份相同的数据,即可以继续使用,保证了数据的安全。

优点:部署简单,价格合适,保证数据的强一致性

缺点:对IO性能影响较大,从库不提供读操作

7.5 分布式协议

分布式协议可以很好解决数据一致性问题。常见的部署方案就是 MySQL cluster,它是官方集群的部署方案,通过使用NDB存储引擎实时备份冗余数据
,实现数据库的高可用性和数据一致性。如下:

在这里插入图片描述

优点:不依赖于第三方软件,可以实现数据的强一致性;

缺点:配置较复杂;需要使用NDB储存引擎;至少三节点;

八、秒杀采用什么方案。

设计一个秒杀系统,需要考虑这些问题:
在这里插入图片描述
如何解决这些问题呢?

页面静态化

按钮至灰控制

服务单一职责

秒杀链接加盐

限流

分布式锁

MQ异步处理

限流&降级&熔断

9.1 页面静态化

  秒杀活动的页面,大多数内容都是固定不变的,如商品名称,商品图片等等,可以对活动页面做静态化处理,减少访问服务端的请求。秒杀用户会分布
在全国各地,有的在上海,有的在深圳,地域相差很远,网速也各不相同。为了让用户最快访问到活动页面,可以使用CDN(Content Delivery 
Network,内容分发网络)。CDN可以让用户就近获取所需内容。

9.2 按钮至灰控制

秒杀活动开始前,按钮一般需要置灰的。只有时间到了,才能变得可以点击。这是防止,秒杀用户在时间快到的前几秒,疯狂请求服务器,然后秒杀时间
点还没到,服务器就自己挂了。

9.3 服务单一职责

   我们都知道微服务设计思想,也就是把各个功能模块拆分,功能那个类似的放一起,再用分布式的部署方式。

   如用户登录相关的,就设计个用户服务,订单相关的就搞个订单服务,再到礼物相关的就搞个礼物服务等等。那么,秒杀相关的业务逻辑也可以放
   到一起,搞个秒杀服务,单独给它搞个秒杀数据库。” 服务单一职责有个好处:如果秒杀没抗住高并发的压力,秒杀库崩了,服务挂了,也不会影响到
系统的其他服务。

9.4 秒杀链接加盐

	  链接如果明文暴露的话,会有人获取到请求Url,提前秒杀了。因此,需要给秒杀链接加盐。可以把URL动态化,如通过MD5加密算法加密随机
	  的字符串去做url。

9.5 限流

一般有两种方式限流:nginx限流和redis限流。

为了防止某个用户请求过于频繁,我们可以对同一用户限流;

为了防止黄牛模拟几个用户请求,我们可以对某个IP进行限流;

为了防止有人使用代理,每次请求都更换IP请求,我们可以对接口进行限流。

为了防止瞬时过大的流量压垮系统,还可以使用阿里的Sentinel、Hystrix组件进行限流。

9.6 分布式锁

可以使用redis分布式锁解决超卖问题。

使用Redis的SET EX PX NX + 校验唯一随机值,再删除释放锁。
if(jedis.set(key_resource_id, uni_request_id, "NX", "EX", 100s) == 1{ //加锁
		try {
			dosomething //业务处理
		} catch{
		}
		finally {
		//判断是不是当前线程加的锁,是才释放
		if(uni_request_id.equals(jedis.get(key_resource_id))) {
			jedis.del(lockKey); //释放锁
		}
	}
}
在这里,判断是不是当前线程加的锁和释放锁不是一个原子操作。如果调用jedis.del释放锁的时候,可能这把锁已经不属于当前客户端,会解除他人
加的锁。

在这里插入图片描述

9.7 MQ异步处理

如果瞬间流量特别大,可以使用消息队列削峰,异步处理。用户请求过来的时候,先放到消息队列,再拿出来消费。

9.8 限流&降级&熔断

限流,就是限制请求,防止过大的请求压垮服务器;

降级,就是秒杀服务有问题了,就降级处理,不要影响别的服务;

熔断,服务有问题就熔断,一般熔断降级是一起出现。
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐