校招笔记(九)_计算机基础_相关补充
我的校招记录:校招笔记(零)_写在前面 ,以下是校招笔记总目录。
备注 | ||
---|---|---|
算法能力(“刷题”) | 这部分就是耗时间多练习,Leetcode-Top100 是很好的选择。 | 补充练习:codeTop |
计算机基础(上)(“八股”) | 校招笔记(一)__Java_Java入门 | C++后端后续更新 |
校招笔记(一)__Java_面对对象 | ||
校招笔记(一)__Java_集合 | ||
校招笔记(一)__Java_多线程 | ||
校招笔记(一)__Java_锁 | ||
校招笔记(一)__Java_JVM | ||
计算机基础(下)(“八股”) | 校招笔记(二)__计算机基础_Linux&Git | |
校招笔记(三)__计算机基础_计算机网络 | ||
校招笔记(四)__计算机基础_操作系统 | ||
校招笔记(五)__计算机基础_MySQL | ||
校招笔记(六)__计算机基础_Redis | ||
校招笔记(七)__计算机基础_数据结构 | ||
校招笔记(八)__计算机基础_场景&智力题 | ||
校招笔记(九)__计算机基础_相关补充 | ||
项目&实习 | 主要是怎么准备项目,后续更新 |
九、相关补充
9.1 (要扩充)设计模式
没有足够实际代码经验,只好先写这些应付下面试。
1.说说什么是单例模式 ?手写一个?
单例模式是一种常用的软件设计模式,在应用这个模式时,单例对象的类必须保证只有一个实例存在,整个系统只能使用一个对象实例。
-
手写单例模式
参考:https://www.runoob.com/design-pattern/singleton-pattern.html
记忆:“2private + 1public ”
-
饿汉式
线程安全 , 但:类加载时就初始化,浪费内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class SingleObject
{
// 创建 SingleObject 的一个对象
private static SingleObject instance = new SingleObject();
// *让构造函数为 private,这样该类就不会被实例化
private SingleObject(){}
// 获取唯一可用的对象
public static SingleObject getInstance(){
return instance;
}
public void showMessage(){
System.out.println("Hello World!");
}
} -
懒汉式(不加锁)
只有真正调用获取实例对象时,才会创建一次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class Singleton
{
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
// 调用时才判断
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
} -
懒汉式(加锁)
线程安全,但加锁性能不够高
1
2
3
4
5
6
7
8
9
10
11
12public class Singleton
{
private static Singleton instance;
private Singleton (){}
// 就是多了个synchronized关键字
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
} -
双检锁式
懒汉式(加锁)虽然可以保证只创建一个单例,但其性能不高,因为每次访问整个方法都加锁。
因此出现双检索式,在
instance=new DoubleCheckSingleton();
进行加锁 ,return instance;
不加锁!为什么要进行两次检查instance==null?
-
第一层检查作用
主要为了提高性能。如果没有第一层,上来就要加锁比较耗费性能
-
第二层检查作用
解决多线程并发问题。假设是第一次开始执行
getInstance
方法:- A,B两个线程,此时
instance==null
,A,B都通过了第一层检查。 - 假设A先拿到锁,往下执行创建一个实例,然后释放了锁;
- 此时B也拿到了锁,如果没有第二层检查,B会进行重新new一个实例,违背单例模式!
- A,B两个线程,此时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public class DoubleCheckSingleton
{
// volitale 关键字修饰,避免指令重排,因为初始化操作是不原子化的 :
// (1) 给DoubleCheckSingleton类的实例instance分配内存
// (2) 调用实例instance的构造函数来初始化成员变量
// (3) 将instance指向分配的内存地址
// 在多线程中,A可能是 1→3→2的顺序,执行到1→3,此时另外一个线程看到instance不为null(因为前面线程执行了3)就直接返回实例。而此时并没有被分配内存故可能出现问题。
private volatile static DoubleCheckSingleton instance;
// 私有的构造方法
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance(){
if(instance==null){ //第一层检查,是否存在实例
synchronized (DoubleCheckSingleton.class){
if(instance==null){ //第二层检查,防止出现另外一个线程阻塞在synchronized,获得锁后重复创建实例
instance=new DoubleCheckSingleton();
}
}
}
return instance;
}
} -
-
2.说说你对代理模式的理解?
代理模式是给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
- 优点:代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;可以灵活地隐藏被代理对象的部分功能和服务,也增加额外的功能和服务。
- 缺点:由于使用了代理模式,因此程序的性能没有直接调用性能高;使用代理模式提高了代码的复杂度。
3.说说简单工厂模式?
简单工厂模式又叫静态工厂方法模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
-
比如,一台咖啡机就可以理解为一个工厂模式,你只需要按下想喝的咖啡品类的按钮(摩卡或拿铁),它就会给你生产一杯相应的咖啡,你不需要管它内部的具体实现,只要告诉它你的需求即可;
-
【优点】工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;
-
【缺点】 不易拓展,一旦添加新的产品类型,就不得不修改工厂的创建逻辑; 产品类型较多时,工厂的创建逻辑可能过于复杂,一旦出错可能造成所有产品的创建失败,不利于系统的维护。
4.说说抽象工厂模式?
抽象工厂模式是在简单工厂的基础上将未来可能需要修改的代码抽象出来,通过继承的方式让子类去做决定。
- 【简单工厂模式缺点】以上面的咖啡工厂为例,某天我的口味突然变了,不想喝咖啡了想喝啤酒,这个时候如果直接修改简单工厂里面的代码,这种做法不但不够优雅,也不符合软件设计的“开闭原则”,因为每次新增品类都要修改原来的代码。
- 【抽象工厂】抽象工厂里只声明方法,具体的实现交给子类(子工厂)去实现,这个时候再有新增品类的需求,只需要新创建代码即可。如,创建一个啤酒工厂而不是咖啡工厂。
5.装饰器模式是什么?
不够深入。
装饰器模式是指动态地给一个对象增加一些额外的功能,同时又不改变其结构。
9.2 分布式问题【校招必问】
非常系统的总结文档:分布式相关:第一页
CAP 理论指的是什么:C(Consistency)是数据一致性、A(Availability)是服务可用性、P(Partition tolerance)是分区容错性。
-
问题引入
现在有一个分布式系统 A,它有一个副本 A1,在正常情况下,客户端 Client 写数据到系统 A,然后数据从 A 节点同步到 A1 节点,再返回给 Client 成功状态。
但由于网络是不可靠的,节点 A 和 A1 的网络随时会因为中断而出现分区。所谓网络分区就是由于网络不通导致节点 A 和 A1 被隔离在不同的网络子集中,此时节点 A 的数据就不能及时同步到节点 A1 中了。
9.1.1 在CAP基础上讲讲BASE?举实例说说?
BASE 理论,它是 CAP 理论的延伸。BASE 是 Basically Available(基本可用)、Soft State(软状态)和 Eventually Consistent(最终一致性)三个单词的简写,作用是保证系统的可用性,然后通过最终一致性来代替强一致性,它是目前分布式系统设计中最具指导意义的经验总结。
其实是做了“可用性”方面的妥协,比如:
- 电商网站在双十一大促等访问压力较大的时候,关闭商品排行榜等次要功能的展示,从而保证商品交易主流程的可用性,这也是我们常说的服务降级;
- 为了错开双十一高峰期,电商网站会将预售商品的支付时间延后十到二十分钟,这就是流量削峰;
- 在你抢购商品的时候,往往会在队列中等待处理,这也是常用的延迟队列。
软状态和最终一致性指的是允许系统中的数据存在中间状态,这同样是为了系统可用性而牺牲一段时间窗内的数据一致性,从而保证最终的数据一致性的做法。
9.1.2 亿级商品分布式存储问题?
1.如何设计一个支持海量商品存储的高扩展性架构?
从这一点出发会考察你Hash(哈希)分片的具体实现原理。
- 以商品 ID 作为关键字进行分片,系统会通过一个 Hash 函数计算商品 ID 的 Hash 值,然后取模,就能得到对应的分片;
2.在做分库分表时,基于 Hash 取模和一致性 Hash 的数据分片是如何实现的?
- 解决 Hash 分片的缺点,既保证数据均匀分布,又保证扩展性 ,最终采用一致性 Hash :它是指将存储节点和数据都映射到一个首尾相连的哈希环上。
- 具体见前,一致性哈希相关算法描述
3.在电商大促时期,如何对热点商品数据做存储策略 ?
-
问题
一致性 Hash 提升了稳定性,使节点的加入和退出不会造成大规模的数据迁移,但本质上 Hash 分片是一种静态的分片方式,必须要提前设定分片的最大规模,而且无法避免单一热点问题, 某一数据被海量并发请求后,不论如何进行 Hash,数据也只能存在一个节点上,这势必会带来热点请求问题。
-
解决
做 Range(范围)分片。 与 Hash 分片不同的是,Range 分片能结合业务逻辑规则,例如,我们用 “Category(商品类目)” 作为关键字进行动态分片时,不是以统一的商品一级类目为标准,而是可以按照一、二、三级类目进行灵活分片。例如,对于京东强势的 3C 品类,可以按照 3C 的三级品类设置分片;对于弱势品类,可以先按照一级品类进行分片,这样会让分片间的数据更加平衡。
4.强一致性和最终一致性的数据共识算法是如何实现的?
9.1.3 海量并发,分布式事务一致性问题?
-
什么是分布式事务问题?
一次大的操作由多个小操作组成,这些小的操作分布在不同的服务器上,分布式事务需要保证这些小操作要么全部成功,要么全部失败。
举一个实例:
- 京东旅行系统,拆分成多个子系统,如商品系统、促销系统、订单系统。用户下单时,订单系统生成订单,商品系统扣减库存,促销系统扣减优惠券,只有当三个系统的事务都提交之后,才认为此次下单成功,否则失败。
-
解决方案
有两阶段提交协议(Two-Phase Commit,2PC)、3PC 、TCC 和基于消息队列的实现方式。
-
错误回答:方案很多,可以选择 2PC ,2PC 实现的流程是…
-
错误原因: 因为在实际工作中,很少采用前几种方案(互联网中落地方案代价大),基本都是基于 MQ 的可靠消息投递的方式来实现。
-
正确回答:先介绍目前主流实现分布式系统事务一致性的方案(也就是基于 MQ 的可靠消息投递的机制)然后回答出可实现方案和关键知识点。另外,为了和面试官进一步交流,你可以提出 2PC 或 TCC (这是一种交流方案)。
-
回答一、基于 MQ 的可靠消息投递方案
-
什么是MQ
核心的五个概念:
- Queue: 真正存储数据的地方
- Exchange: 接收请求,转存数据
- Bind: 收到请求后存储到哪里
- 消息生产者:发送数据的应用
- 消息消费者: 取出数据处理的应用
-
场景实例
订单系统(1)完成订单后,(2)购物车系统减购物车中的商品。
-
订单系统在消息队列上开启一个事务(没有创建订单);
-
订单系统给消息服务器发送一个“半消息”;
这个半消息不是说消息内容不完整,它包含的内容就是完整的消息内容,半消息和普通消息的唯一区别是,在事务提交之前,对于消费者来说,这个消息是不可见的。
-
半消息发送成功后,订单系统就可以执行本地事务了,在订单库中创建一条订单记录,并提交订单库的数据库事务。
-
然后根据本地事务的执行结果决定提交或者回滚事务消息。
如果订单创建成功,那就提交事务消息,购物车系统就可以消费到这条消息继续后续的流程。如果订单创建失败,那就回滚事务消息,购物车系统就不会收到这条消息。
-
购物系统消费这条拿到的订单系统消息(确认了订单系统事务执行完毕),这样就可以继续下一步购物操作
-
-
-
9.1.4 分布式锁问题
分布式锁是解决协调分布式系统之间,同步访问共享资源的一种方式。详细来讲:在分布式环境下,多个系统在同时操作共享资源(如写数据)时,发起操作的系统通常会通过一种方式去协调其他系统,然后获取访问权限,得到访问权限后才可以写入数据,其他系统必须等待权限释放。
-
基于redis的分布式锁
使用setnx命令加锁
1
2
3
4
5
6
7
8public static void wrongGetLock1(Jedis jedis, String lockKey, String requestId, int expireTime) {
// 第一步:加锁
Long result = jedis.setnx(lockKey, requestId);
if (result == 1) {
// 第二步:设置过期时间
jedis.expire(lockKey, expireTime);
}
}-
setnx命令,意思就是 set if not exist,如果lockKey不存在,把key存入Redis,保存成功后如果result返回1,表示设置成功,如果非1,表示失败,别的线程已经设置过了。
-
expire(),设置过期时间,防止死锁,假设,如果一个锁set后,一直不删掉,那这个锁相当于一直存在,产生死锁。
-
解决setnx与expire不是一个原子操作
-
加锁总共分两步,第一步jedis.setnx,第二步jedis.expire设置过期时间,setnx与expire不是一个原子操作,如果程序执行完第一步后异常了,第二步jedis.expire(lockKey, expireTime)没有得到执行,相当于这个锁没有过期时间,有产生死锁的可能。
-
解决方案为:一步操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public class RedisLockDemo
{
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
/**
* 获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取功 */
public static boolean getLock(Jedis jedis, String lockKey, String requestId, int expireTime)
{
// 两步合二为一,一行代码加锁并设置 + 过期时间。
if (1 == jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime))
{
return true;//加锁成功
}
return false;//加锁失败
}
// 解锁:对应del删除key即可
public static void unLock(Jedis jedis, String lockKey, String requestId)
{
// 第一步: 使用 requestId 判断加锁与解锁是不是同一个客户端
if (requestId.equals(jedis.get(lockKey)))
{
// 第二步: 若在此时,这把锁突然不是这个客户端的,则会误解锁
jedis.del(lockKey);
}
}
}
-
基于Zoopkeeper的分布式锁
sync,lock也只能保证你当前机器线程安全,这样分布式访问还是有问题。
一个机器接收到了请求之后,先获取 zookeeper 上的一把分布式锁(zk会创建一个 znode),执行操作;然后另外一个机器也尝试去创建那个 znode,结果发现自己创建不了,因为被别人创建了,那只能等待,等第一个机器执行完了方可拿到锁。
下面是创建临时顺序节点的情况:
-
客户端调用create()方法创建名为“/dlm-locks/lockname/lock-”的临时顺序节点。
-
客户端调用getChildren(“lockname”)方法来获取所有已经创建的子节点。
-
客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点是所有节点中序号最小的,就是看自己创建的序列号是否排第一,如果是第一,那么就认为这个客户端获得了锁,在它前面没有别的客户端拿到锁。
-
如果创建的节点不是所有节点中需要最小的,那么则监视比自己创建节点的序列号小的最大的节点,进入等待。直到下次监视的子节点变更的时候,再进行子节点的获取,判断是否获取锁。
-
-
基于关系型数据库 MySQL 实现分布式锁
利用 Mysql 的锁表,创建一张表,设置一个 UNIQUE KEY(如,利用主键ID的唯一性) 这个 KEY 就是要锁的 KEY,所以同一个 KEY 在mysql表里只能插入一次了。
这样对锁的竞争就交给了数据库,处理同一个 KEY 数据库保证了只有一个节点能插入成功,其他节点都会插入失败。
定义加锁、解锁代码如下:
1
2
3
4
5
6
7
8
9def lock :
exec sql: ins ert into lockedtable (xxx) values (xxx)
if result == true :
return true
else :
return falsedef
def unlock : # 解锁就是删除
exec sql: delete from lockedOrder where order_id='order_id'
9.3 其它问题
1. 【字节-懂车帝】什么是跨域?
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。
同源策略限制了一下行为:
-
Cookie、LocalStorage 和 IndexDB 无法读取
-
DOM 和 JS 对象无法获取
-
Ajax请求发送不出去
具体的一些实例:
-
非跨域
1
http://www.xxxyyy.cn/index.html 调用 http://www.xxxyyy.cn/server.php 非跨域
-
跨域:主域不同
1
http://www.xxxyyy.cn/index.html 调用 http://www.xxx.cn/server.php
-
跨域:子域名不同
1
http://abc.xxxyyy.cn/index.html 调用 http://def.xxx.cn/server.php
-
跨域:端口不同
1
http://www.xxx.cn:**8080**/index.html 调用 http://www.xxx.cn/server.php
-
跨域:协议不同
1
**https**://www.xxx.cn/index.html 调用 **http**://www.xxx.cn/server.php