锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

面试技巧基础

时间:2023-04-24 11:37:01 cp114差压变送器压力变送器tm21by934g压力变送器

熟练掌握 JAVASE 基本语法,熟悉框架底部,并发编程,解 熟练掌握 Redis 的使用,熟悉 Redis 持久机制、哨兵原理、集群脑裂和双写一致性 、缓存穿透、缓存击穿、缓存雪崩 熟练使用 Dubbo、Zookeeper,了解底层 RPC 原理,Zookeeper 分布式锁,Leader 选举、集群同步等 熟练使用各种框架,Spring、Mybatis,Mybatis-plus、SpringMvc、SpringBoot 等 熟练使用微服务框架 SpringCloud,了解每个组件 了解 JVM、垃圾回收机制 熟练使用 MySQL,Oracle 等关系数据库,掌握基础 sql 调优及原理 熟悉新闻中间件,Rabbitmq、Rocketmq、Kafka 熟悉消息丢失、消息重复消费、消息乱序、消息积累解决方案 熟练掌握 Linux 系统的基本操作,熟悉 Nginx、Docker、ElasticSearch、FastDFS 等 熟练使用 java 开发工具,Ecplise 、IDEA,代码管理管理 Git、SVN,及 MAVEN

一 JAVASE 基础语法

Java 什么是集合系统?

集合存放 Java.util 包里,主要有 3 种:set(集)、list(列表包含 Queue) 和 map(映射)
1. Collection:Collection 是集合 List、Set、Queue 最基本的接口。
2. Iterator:迭代器可以通过迭代器遍历集中的数据。
3. Map:是映射表的基本接口。

ArrayList 底层结构为数组,底层查询快,增删慢
LinkedList 底层结构为链形,增删快,查询慢
Voctor 底层结构是数组 线程安全,增删慢,查询慢

List

ArrayList 线程不安全,查询速度快
l Vector 线程安全,但速度快 慢,已被 ArrayList 替代
l LinkedList 链表结果增删速度快
l TreeList 树型结构保证了增删的复杂性O(log n),增删性能远高于 ArrayList和LinkedList,但是稍微占用内存

Set

Set:元素是无序的(存取顺序不一定一致),元素不能重复。
u HashSet:哈希表是底层数据结构, 线程不安全, 数据不同步。
HashSet 如何保证元素的独特性?
通过元素的两种方法,hashCode 和 equals 来完成。 如果元素的 HashCode 只有价值相同,才能判断 equals 是否为 true。 如果元素的 hashcode 值不同,不会调用 equals。 注意,判断元素是否存在、删除等操作所依赖的方法是元素 hashcode 和 equals 方法
l TreeSet:底层数据结构为二叉树,存储有序:TreeSet 线程不安全是正确的 Set 集中元素进行排序。 compareTo 或者 compare 方法来保证 元素的独特性。

Map

Correction、Set、List 接口属于单值操作, Map 每一个元素都使 用 key——>value 形式存储在集合中。 Map 集合:该集合存储键值对, 是 key:value 一对一往里存, 而且要保证 键的唯一性
Map 常用子类接口
l HashMap:允许使用底层数据结构为哈希表 null 值和 null 键,该集合 是数据不同步,将 hashtable 替代,jdk1.2.效率高。
l TreeMap:底层数据结构为二叉树,线程不同步。 map 集合中 按键排序

hashmap

结构:

1.7 数组 链表

1.8 数组 链表 红黑树

1.7 采用头插法

新的值会取代原有的值,原有的值就会顺推到链表中取,目的是为了提升查询的效率

1.8 采用尾插法

为了安全,防止环化

扩容:

1.7的扩容

将数组的长度变为原来的两倍,然后将原来的元素放到新数组上,即将旧数组先遍历数组,在遍历链表,将数据全部取出,然后通过hash算法,就能得到该数据在新数组的索引值

1.8的扩容

看hash和oldcap的&值是否为0,如果为0,则新旧数组不变,如果不是0,则新数组的索引应该是原索引+oldcap长度的索引

hash

将任意长度的输入,转变成固定长度的输出,该输出的值就是散列值,当前存储数据的结构就称为哈希表,所对应得关系得方法,称为散列函数

hash算法及hash冲突

算法:高低16位取异或的值^2的n次幂-1

高低16位取异或:

1.让整合的hash能够更多位参与到运算

2.采用异或,异或可以让0,1尽可能更平均

2 的n次幂-1 = 数组的长度-1

1.能够让数据能够更好的落在数组的奇数或者偶数位上===》更加均匀的排列

2 能够让数据都落在数组索引上

3 让hash值有意义,因为底层采用的是2进制,所以只有是1,才能保证hash值有作用,如果是0,不管hash值为多少,并过的值永远是0

HashMap发生hash冲突之后,他是如何解决的

如何解决 数学的角度上:链式地址法 hashMap的解决方案 : :线性探测法 hash冲突有什么后果:效率会变低,会形成链表,查询速度就会变慢 hash冲突能够解决吗? :不能,只能尽可能去避免

Hashtable

跟HashMap相比Hashtable是线程安全的,适合在多线程的情况下使用,但是效率可不太乐观。

Hashtable 是不允许键或值为 null 的,HashMap 的键值则都可以为 null。

Hashtable使用的是安全失败机制(fail-safe),这种机制会使你此次读到的数据不一定是最新的数据。

快速失败(fail—fast)是java集合中的一种机制, 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。

如果你使用null值,就会使得其无法判断对应的key是不存在还是为空,因为你无法再调用一次contain(key)来对key是否存在进行判断,ConcurrentHashMap同理。

初始化容量不同:HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75。

扩容机制不同:当现有容量大于总容量 * 负载因子时,HashMap 扩容规则为当前容量翻倍,Hashtable 扩容规则为当前容量翻倍 + 1。

在源码中,他在对数据操作的时候都会上锁,所以效率比较低下。

reentrantlock

ReentrantLock是可重入的互斥锁

在ReentrantLock中,它对AbstractQueuedSynchronizer(aqs)的state状态值定义为线程获取该锁的重入次数,state状态值为0表示当前没有被任何线程持有,state状态值为1表示被其他线程持有,因为支持可重入,如果是持有锁的线程,再次获取同一把锁,直接成功,并且state状态值+1,线程释放锁state状态值-1,同理重入多次锁的线程,需要释放相应的次数。

AQS使用一个volatile修饰的私有变量来表示同步状态,当state=0表示释放了锁,当sta-te>0表示获得锁。

currentHashMap

1.7 采用的是分段式锁

数据结构:reentrantlock+segment+hashEntry

每个段就是一个segment,这个segment就是对应的锁,在操作过程中,如果没有去操作同一个节点,就不存在阻塞问题

1.8

采用的是volatile+cas+syn

线程安全的map

1 初始化:
在源码中有一个while循环,所有的线程都会进入到while循环中,然后进行cas,如果进入到cas的线程,会进行map的初始化,而其他没有进入到cas的线程,会进行线程的礼让,释放cpu的执行权,这就保证在多个线程的情况下,只有一个线程在进行初始化

2 put

根据key计算出hash值

判断是否需要初始化

即为当前key定位出的Node,如果表示为空,则表示当前位置可以写入数据,利用cas尝试写入。失败则自旋保证成功

如果当前位置的hash值==moced==-1,则需要进行扩容

如果都不满足,则利用syn锁写入数据

(之所以这个要用 syn,而不单纯采用cas,是因为这个地方除了 插入逻辑以外,你还要遍历的逻辑也是线程安全)

如果数量大于TREEIFY_THRESHOLD 则要转换为红黑树。

gets

根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。

如果是红黑树那就按照树的方式获取值。

就不满足那就按照链表的方式遍历获取值。

volatile

可见性 原子性 有序性

如果一个变量被volatile关键字修饰,那么所有线程都是可见的。

volatile是一个变量修饰符,只能用来修饰变量。

底层原理

当对非volatile变量进行读写的时候,每个线程先从主内存拷贝变量到CPU缓存中,如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPU cache中。

 volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,保证了每次读写变量都从主内存中读,跳过CPU cache这一步。当一个线程修改了这个变量的值,新值对于其他线程是立即得知的。

可见性

对于volatile关键字修饰的变量:多线程下jvm会为每个线程分配一个独立的缓存来提高效率,当一个线程修改了该变量后,另外一个线程立即可以看到

禁止指令重排序

指令重排序是JVM为了优化指令、提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。

指令重排序可能会带来的问题

如果一个操作不是原子的,就会给JVM留下重排的机会。

volatile关键字提供内存屏障的方式来防止指令被重排,编译器在生成字节码文件时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

CAS

内存值V、预期值A、要修改的新值B

CAS 是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的。

线程在读取数据时不进行加锁,在准备写回数据时,比较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执行读取流程。

ABA问题?

就是说来了一个线程把值改回了B,又来了一个线程把值又改回了A,对于这个时候判断的线程,就发现他的值还是A,所以他就不知道这个值到底有没有被人改过,其实很多场景如果只追求最后结果正确,这是没关系的。

但是实际过程中还是需要记录修改过程的,比如资金修改什么的,你每次修改的都应该有记录,方便回溯。 解决方法:

用版本号去保证就好了,就比如说,我在修改前去查询他原来的值的时候再带一个版本号,每次判断就连值和版本号一起判断,判断成功就给版本号加1。

时间戳也可以,查询的时候把时间戳一起查出来,对的上才修改并且更新值的时候一起修改更新时间,这样也能保证

面向过程与面向对象的区别

面向过程主要是针对功能,而面向对象主要是针对能够实现该功能的背后的实体

面向对象实质上就是面向实体,所以当我们使用面向对象进行编程时,一定要建立这样一个观念:万物皆对象!

都可以实现代码重用和模块化编程,但是面对对象的模块化更深,数据更封闭,也更安全!因为面向对象的封装性更强!面对对象的思维方式更加贴近于现实生活,更容易解决大型的复杂的业务逻辑从前期开发角度上来看,面对对象远比面向过程要复杂,但是从维护和扩展功能的角度上来看,面对对象远比面向过程要简单!

二 集合框架底层 (多线程、线程池)

线程的实现方式

1.继承 thread 2.实现 runanble 3.实现 callAble 3.1 可以抛出异常 3.2 可以接受返回值 3.3 阻塞 callable 需要和FutureTask 配合使用 4.使用线程池

线程的生命周期

新建状态:指的就是线程被new 出来 就绪状态:你去调用start方法,但是他还没有得到cpu的分配 运行状态: 已经跑起来了,已经得到cpu的分配权利 阻塞状态: 就是遇见了wait,sleep 之类的东西我,就会被阻塞 ,sleep会自动变为就绪状态(而且sleep 不会释放锁),而wait(会释放锁资源)必要要等待notify唤醒 死亡状态:运行结束后,就进入到死亡状态

线程池的7大参数

长足线程数 corePoolSize

最大线程数 maximumPoolSize

阻塞队列 workQueue

拒绝策略 defaultHandler

默认工厂 Executors.defaultThreadFactory()

多余的空线程的存活时间 keepAliveTime

时间单位 unit

线程池在创建之后,并不会立刻创建线程,而是当任务过来后,我们线程池会去判断当前线程池中的线程是否小于常驻线程,如果小于则创建线程,直到线程池中的线程不再小于常驻线程,任务交给线程去处理,如果常驻线程处理不过来之后,把这个任务交给阻塞队列,如果阻塞队列都满了之后,此时会开辟最大线程,如果最大线程和阻塞队列都满了的话,此时就会拒绝策略。

拒绝策略

1.丢弃 ​ 2.抛出异常 ​ 3.交给方法调用者 ​ 4.丢弃队列中等待时间最久的那一个

工作中使用哪种线程池

在工作中咱们不会使用executors创建线程池,因为如果使用可伸缩的线程池,整体线程数无法控制,也不使用单个线程线程池,也不能使用 固定长度大小线程池 因为他的底层是一个 linkedBlockingQueue,而这个queue,他会开辟一个Integer.maxValue大小的阻塞队列,这样的太占用内容 不使用executors 线程池他是 阿里规范上边明确指出的 我们怎么使用: 1.使用第三方线程池 2.自定义线程池

工作中如何配置线程池

注意:这个地方有一个理论依据,但是你不要说是你配的 cpu型:进行了大量计算这种操作 cpu+1 io 型:除了cpu就是io 2 * cpu+1 具体地配置应该是由 压力测试工具 ,比如jmeter 这样的压测工具,在一定强度下压测出来的最终结果

这个是由 老大来配置的,只是去了解了一下

CountDownLatch

public void createMappingAndIndex() {
        //创建索引
        elasticsearchTemplate.createIndex(SkuInfo.class);
        //创建映射
        elasticsearchTemplate.putMapping(SkuInfo.class);
    }

    ExecutorService threadPool = Executors.newFixedThreadPool(5); //不建议使用

    //导入全部sku集合进入到索引库
    @Override
    public void importAll() {
        //  pageHelper.startPage(1,1000)  第一页 , 1000条
        //  pageHelper.startPage(2,1000) 第二页    1001~ 2000条
        //  首先确定他有多少页   总记录数  10001 / 每页显示条数   1000== 0?   10+1  countDolnlatch
        int pageSize = 1000;
        int allCount = skuFeign.selectCount();
        int allPage = allCount % pageSize == 0 ? allCount /  pageSize :allCount /  pageSize + 1;
        CountDownLatch cdl = new CountDownLatch(allPage);
        for (int page= 1; page <= allPage; page++) {
            threadPool.submit(new TaskThread(page,pageSize,cdl));
        }
        // 当所有分线程走完之后,他就应该不再阻塞
                // 什么时候 countdown  完成一个线程 countdown 一次
                // 变量是多少   一共有多少个线程要执行
        try {
            cdl.await();
        } catch (IsnterruptedException e) {
            e.printStackTrace();
        }

三 锁

为什么要使用分布式锁

在采用分布式架构后,程序会有多个实例,则就会有多个锁对象,但不管是lock或者syn都不能保证锁住的是同一个对象

redis的分布式锁

利用redis的setnx和setex
具体实现: 设定一个字符串,将该字符串写入到数据库中,如果redis中没有该条数据记录,则写入成功,返回1.当client拿到的返回值为1,则表明拿到了锁,那就会继续执行自己的业务逻辑,使用完后会将redis中的记录删除,如果写入失败,则返回0,当client拿到的值为0,则表明获取锁失败,那么将会进行自旋抢锁。

缺陷:不能防止锁 会删除其他的锁 无法进行续约逻辑 不能保证公平性

解决办法:使用redission

redission

是redis提供的对于分布式支持的lock锁,底层原理是看门狗原理

redission有两种模式
1 如果你没有传入参数,则lock以-1接受(底层源码执行的),如果传入了参数,则使用该参数
2当获得当前锁的线程,则会去判断leasTime是否!=-1,
3 如果不等于-1,则表明传入了过期时间
 3.1 不会续期
 3.2 锁以传入的过期时间作为锁失效的时间
 3.3 到期后直接返回
4 如果等于-1,则表明没有传入过期时间,则会使用系统默认的过期时间,即看门狗原理
5 底层以一个taskTimer进行续约逻辑,即看门狗的默认时间为30s,那么每过1/3看门狗时间就进行预约,即每过10秒执行续约逻辑

zk的分布式锁

核心理论:当客户端获取锁,则创建临时节点,使用完锁,删除节点
具体实现:
1. 客户端获取锁时,在lock节点下创建临时顺序节点
2. 然后会获取到lock下的所有子节点,当客户端获取到节点后,会去验证该节点是否为最小节点
3. 如果发现是最小的节点,那么说明自己获取到锁,然后执行自己的业务逻辑,使用完锁后会删除该节点
4. 如果发现不是最小节点,说明未获取到锁,那么会去找到那个最小的节点,并给该节点绑定一个监听事件器,监听删除事件
5. 如果发现该节点被删除后,客户端的watcher会收到通知,然后客户端会再次判断自己的节点是否为最小节点
6. 如果为最小节点,则表明获取到锁,执行自己的业务逻辑,使用完后删除即可
7. 如果依旧不是最小节点,那么表明仍为获取到锁,继续执行之前的获取那个最小节点的步骤

syn

synchronized

jvm层面的锁

sny其实锁住的是一个对象,对象布局为

1 实例数据

2填充数据

3 object-header

里面包含了: mark—word :锁信息 hashcode gc标志位 gc的年龄

kclass—pointer :即一个指针压缩,它最终会指向处于方法类的模板信息

锁的升级

为什么会产生锁升级?

在jdk1.5以前,只要经过了syn,那么不管处于什么状态,都会进行用户态和内核态的切换,导致性能的极限下降

偏向锁

在锁对象的对象头中会记录当前获取到该锁的线程id,该线程下次如果又来获取该锁就可以直接获取

轻量级锁

由偏向锁升级而来,当一个线程获取到锁以后,此时这把锁是偏向锁,如果有第二个线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻量级锁底层是通过自旋来实现的,并不会阻塞线程

自旋锁

自旋锁就是线程在获取锁 的过程中,不会去阻塞线程,也就无所谓唤醒线程,阻塞和唤醒这两个步骤都是需要操作系统去进行的,比较消耗时间,自旋锁是线程通过cas获取预期的一个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到锁,这个过程线程一直运行中,相对而言没有使用太多的操作系统资源,比较轻量。

重量级锁

如果自旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞

lock

lock是java层面的锁

他分为公平锁和非公平锁

Lock锁可重入、等待时可中断、可判断、可公平可非公平

原理

如果是一把非公平锁:先进行一次cas ,如果cas 失败,就会去执行自旋1.获得state 变量,判断state 是否是个0 ,如果不是0 表示有人正在持有锁,如果是0 ,则判断队列是否有元素,如果队列没有元素,则cas 抢锁,如果当前锁 被人持有,他就判断是不是自己持有这把锁,如果是,则表明现在是可重入状态-> 将state 加1 ,如果需要排队,则 lock 锁,将线程放置到 双向链表中进行排队,他会让我们线程中的元素 进行LockSupport.park(),阻塞,如果我们持有锁的线程此时执行完之后,他会将队列中的第一个元素唤醒 -> 他就和其他线程进行抢锁,如果抢成功,被唤醒的线程就会执行,抢锁失败,则继续阻塞

synchronized和lock的区别(8点)

1.来源及用法: lock是一个接口,是java写的控制锁的代码,而synchronized是java的一个内置关键字,synchronized是托管给JVM执行的; synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。 lock:一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。

2.异常是否释放锁: synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)

3.是否响应中断 lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;

4.是否阻塞 Lock可以尝试获取锁,synchronized获取不到锁只能一直阻塞 synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;

5.锁特点 synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、等待时可中断、可判断、可公平可非公平

6.性能差别 1.在Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。但是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地。 如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。而且Lock可以提高多个线程进行读写操作的效率。(可以通过readwritelock实现读写分离)

7.等待通知 synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度 8.锁机制 1.synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。 2.Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。 现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。

四 redis

持久化机制

rdb

rdb会每隔一段时间如果发生了指定次数的写操作,那么会将内存中的数据写到磁盘当中,生成一个dump.rdb的文件,恢复数据时,直接=读取磁盘中的数据即可

原理:在底层fork出一条子进程,和主进程一摸一样,用来操作数据的持久化,在此过过程中主进程不会进行任何写操作,这也就保证了rdb的高效性,持久化操作的底层原理为bgsave,即异步进行持久化

aof

以日志的方式记录用户的每一次写操作指令,当数据需要恢复时,再次去执行日志记录的指令即可

aof默认是关闭的,需要手动打开

aof有一个重写机制:把命令进行压缩,对aof文件进行瘦身

重写的条件:

大小大于64mb

2.成为原来大小的1倍

aof的同步机制:

1 always:每执行一次操作就持久化一次

2.every;每过一秒就持久化一次

3 no :由操作系统决定

rdb的优势:恢复大批量的数据 劣势: 会丢失一部分数据

aof的优势:丢失数据是可控的,在数据量较少时,性能好 劣势:随著数据的增加,aof文件会越来越大,aof的效率就变低

实际应用: rdb+aof ---> rdb来恢复大面积数据 用aof 追

加rdb丢失的数据

哨兵原理

(cmd通道)

哨兵会和其他哨兵,master,salve建立cmd通道,然后哨兵会通过cmd通道发送hello指令给master,如果未收到响应,那么会哨兵会认为该master主观下线了,哨兵便通知其他哨兵也去给该master发送指令,如果超过半数的哨兵未收到响应,那么该master就会被认为客观下线,然后哨兵会进行互相投票(互相投票,谁先接收到对方的投票,就投递给谁),选举出一个哨兵来选出一个salve成为新的master,选举的机制为: 排除不在线的 响应时间久 断开时间长的

会有一个优先级策略:slave-priority : 参数越小,优先级越大 offset 偏移量(数据的更新量) runid(事先设置好的)

集群原理

redis的集群有16384个槽,当连接到数据库时,连上集群的某个节点后,会计算出当前key应当存放在哪个槽,然后会将数据存放到对应的槽点

集群脑裂

在master还未将数据同步到slaver中,发生网络波动,master和salve不在一个网段,而把哨兵和salve分在一个网段,导致该master被哨兵认为客观下线,会选举出salve成为新的master,当网络恢复后,会出现两个master,不管哪一个master重新变为salve,都会导致数据的丢失。

解决方案:配置文件中设置, 保证master至少有多少个salve 还有联系

异步数据丢失

在master将rdb数据发送给salve后,master将会接收新的操作数据,在接收到新数据后,将数据写到缓冲区,还没同步到salve,mater就宕机了,然后哨兵会选举salver为新的master,那么缓冲区的数据就会丢失,

解决方案:在配置文件中设置 min-slaves-max-lag 10 延迟最多多久

双写一致性问题

双写方案

先写数据库,再写缓存

产生问题:在高并发情况下,造成数据库的数据与缓存不一致

失效方案

写完数据库,删除缓存

产生问题:在高并发的情况下,在用户在写数据的时候,另外的用户也来操作数据库,在将数据写到缓存时,老用户把自己的数据更新到缓存,造成数据库与缓存不一致

解决:1) 加一把读写锁

2)给redis的key加一个过期时间 添加的时间--》表明可以忍受这一部分时间的脏数据

3)queue+自旋

缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大

解决方案:

1.接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截; 2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存 有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻 击用户反复用同一个id暴力攻击

缓存击穿

某个key过期,此时多个请求同时访问该条数据,再缓存中读取不到,请求会直接到数据去查询,造成数据库的压力过大

解决:1 设置热点数据缓存永不过期

2 加互斥锁

缓存雪崩

1 redis宕机后,大量的请求直接落到数据库
2 大量的key同时过期,所有的请求直接发送到数据库

解决方案:

1 事前:将redis配置成一个高可用的架构:哨兵 salve

2 事中:hystrix 实现限流+降级+本地缓存=从而保证数据库不被一瞬间击垮

3 事后:提前对redis进行持久化,然后在重启redis服务时,可以将持久化的数据加载出来供系统使用 --》缓存预热--》将热点数据加入到redis中

本地缓存:ehcache 查询时,先查询本地缓存,本地缓存没有在查询redis,redis没有,再查询数据库,这时候再去将数据库中的数据同步到ehcache和redis中

hystrix: 熔断器 到达设定的阈值,则熔断器打开,拒绝请求

限流:为了防止流量一下子过大而直接冲垮系统的一种技术

降级: 退而求其次的方案,对于多余的请求,进行降级处理,即告知友好提示 :请稍等,服务器正在升级,空白页面做出响应,而不是直接去访问数据库获取数据

redis实现延时队列

Redis的key过期回调事件,也能达到延迟队列的效果,简单来说我们开启监听key是否过期的事件,一旦key过期会触发一个callback事件。

redis的数据结构

string set zset hash list

redis的过期策略和淘汰机制

过期策略:三种

定时删除:给key设置过期时间,且到达过期时间后,由定时器任务立即执行对键的删除

用处理器性能换取存储空间(拿时间换空间)

惰性删除:当key到达过期时间后,不做处理,当下次访问该数据时,如果发现已过期,便会删除该数据,并返回一个nul

总结:用存储空间换取处理器性能(拿空间换时间)

定期删除:定期性的轮询redis库中的时效性数据,采用随机策略,利用过期数据的占比的方式控制删除频度

总结:周期性抽查存储空间(随机抽查,重点抽查)

淘汰机制

3类8种

1.易丢数据

最近最少使用

最近使用次数最少

过期数据

随机淘汰

2.全库数据

最近最少使用

最近使用次数最少

过期数据

3.放弃数据追逐

redis的主从复制

全量更新

简单说:在建立连接时,salve会将自己的数据进行清空后,master会给salve走一个全量更新,后续更新走增量更新

具体实现:1 salve与master建立连接

2 master会发起全量更新,生成一个rdb文件,将数据全部发送给salve,并且master还会发送以一个offset的偏移量和runid给salve

3 salver再接收数据前,会将自己的数据清空,然后接受master的数据

元器件数据手册、IC替代型号,打造电子元器件IC百科大全!

相关文章