Technical Questions
学习并记录 General Tech Questions
High Availability 高可用
1. What is High Availability?
High availability is that the company’s IT infrastructure can continue working even when some of components fail. Downtime can have majorly adverse effects on your business health. As such, we need to take suitable measures to minimize downtime and ensure system availability at all times.
2. How Is High Availability Measured?
High availability is measured by the “class of nines”, like “999”, “9999”. You know, the great company facebook got a three ”9” last year because of a big downtime which lasted 6 - 9 hours.
3. How to you make sure high availability of your server?
- Server Cluster 服务器集群. We can use server clusters to avoid single points of failure. For example, Kubernetes provides the flexible High Availability server cluster by container deployment.
- Geographic Redundancy 地域容灾. Geo-redundancy is carried out by deploying multiple servers at global distinct locations. It ensures that even if one fails, the other continues running smoothly.
- Network Load Balancing 网络负载均衡. We can use the Nginx to configure a group of distributed servers. In this way, we can proxy HTTP traffic for load balancing. Nginx provides the algorithms like round robin and IP hash for reducing the single server load.
- Cache 缓存. We can use Redis to store active users’ data in the Redis database. Because Redis is a memory key-value database, it’s very quick. This relieves the load of accessing the database directly.
- Asynchronous Communication (Async Call) 异步调用. We can use Message Queue to achieve it. When a user’s request comes into our web server, we send it to Message Queue and respond with an HTTP 200 for success to the user. After the task is actually processed, we then notify the user. For example, Twitter is a huge and complicated system, when a person posts a tweet, we need to insert it into all of his/her friends' timelines, which is a very high write load, maybe last seconds or minutes, due to the fans number. So we need to use Message Queue to achieve the Async call.
4. Make a web app highly available 的实践经验
- Single Server 单机设置. 通过修改 Tomcat 的外部配置文件
application.properties, 设置max-thread, 例如默认配置在一台 2 core 4GB memory 的 server 上,max-thread=100. 通过设置外部配置文件后, 可以提高到max-thread=200. - Reverse Proxy 反向代理. 通过 启动多台 server 和 1 台用于 Nginx reverse proxy server, 并将前端页面的 static resources 存储在 Nginx reverse proxy server 上. 当 reverse proxy server 接受到 client request, 它会将其 forward request 到 web server cluster 上。web server cluster 是真正处理 request 的集群. 我们有多种转发的算法,例如 round robin 即轮询。以上我们便实现了 Load Balance 负载均衡.
- Message Queue
- Cache
5. 反向代理和负载均衡的区别
反向代理是实现负载均衡的一种方法。负载均衡一般有 dns、硬件层、软件层三类实现方式。使用 Nginx 的反向代理实现负载均衡,属于软件层。
Big Data 大数据
1. If you have a very huge file, which needs to handle multiple files to be processed. How would you design the system?
题目描述不清晰,暂且作为海量数据的问题来问答。 海量数据意味着数据量过大无法全部装入一台机器的内存,或者超过一台机器的处理能力。这时候就需要分而治之,其原理就是将一个问题分解成多个相同的子问题,不断重复直到最后可以简单处理子问题位置,最后在把子问题合成较大的问题,这个合并的过程就叫做归并。
MapReduce Programming Model
使用 MapReduce 的思想去设计系统,即 Divide and Conquer. 这里只考虑 High Level Design, 不考虑具体实现.
注: 仅抛砖引玉,不确保正确性

1. InputForMat && FileReaders
InputForMatread a file from the file system, and split it into multiple files.FileReadersdo file reading and formatting.
2. Workers
Workersare assigned tasks to perform actions.
3. Partitioner
Partitioneras a Hash function, assign tasks fromWorkerstoReducers
4. Reducers && OutputForMat
Reducerscombines the obtained result and perform the computation operation.OutputForMatwriteback to local file system.
2. 流式数据中寻找中位数
问题描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用 Insert()方法读取数据流,使用 GetMedian()方法获取当前读取数据的中位数。
解决思路
求中位数,首先需要用一个容器将流式数据保存起来,那么选择什么容器比较合适呢?
- 数组
- 最简单的容器。
- 如果是未排序的数组,找中位数的方法采用快排的 Partition(). 因此,插入时间复杂度是
O(1), 而找中位数的时间复杂度是O(k * log(n)). - 如果是排序的数组,由于是流式数据,适合使用插入排序。因此,插入的时间复杂度是
O(n), 而找中位数的时间复杂度是O(1).
- 链表
- 由于数组使用插入排序,每次都需要进行大量的位移操作和扩容操作,故此可使用链表取代。
- 排序的链表,插入的时间复杂度还是
O(n),找中位数的时间复杂度是O(n).
- 二叉搜索树
- 使用二叉搜索树,可以将插入的时间复杂度降至
O(log(n)), 而找到中位数的时间复杂度是O(n). - 但二叉搜索树有可能退化成一个链表,此时插入的时间复杂度是
O(n).
- 大顶堆 + 小顶堆(推荐)
- tips:题目只要中位数,而中位数左边和右边是否有序不重要 (思想类似于「求序列中第 k 个大的数字」)
- 核心思想:中位数左边的数据 (左边数据特点是都不大于中位数) 保存在大顶堆中,中位数右边的数据 (右边数据特点是都不小于中位数)保存在小顶堆中。
关键在于保持两个堆保存的数据个数相等或只差一个
- 根据堆的插入,插入数据的时间复杂度是
O(log(n))。而中位数肯定在两个堆的堆顶元素中,找到中位数的时间复杂度是O(1). - 大/小顶堆的实现可以使用
java.util.PriorityQueueorPython.heapq.
如何实现
关键点:
- 中位数左边的数据保存在大顶堆;中位数右边的数据保存在小顶堆中.
- 始终保持两个堆保存的数据个数相等或者只差一个.
实现思路:
- 当输入数目为偶数的时候,将这个值插入大顶堆中,再将大顶堆中根节点 (即最大值) 插入到小顶堆中.
- 当输入数目为奇数的时候,将这个值插入小顶堆中,再讲小顶堆中根节点 (即最小值) 插入到大顶堆中.
- 取中位数的时候,如果当输入数目为偶数,显然是取小顶堆和大顶堆根结点的平均值;如果当输入数目为奇数,显然是取小顶堆的根节点.
# 类 似题目 Leetcode 295. Find Median from Data Stream
import heapq
class MedianFinder:
'''
idea: minHeap最小堆,堆顶是最小值,保存大的那部分
maxHeap最大堆,堆顶是最大值,保存小的那部分
heappush 方法: 将 item 的值加入 heap 中,保持堆的不变性.
heappushpop 方法: 将 item 放入堆中,然后弹出并返回 heap 的最小元素.
Time:
Constructor: O(1)
addNum: O(logN)
findMedian: O(1)
Space: O(N)
'''
def __init__(self):
self.maxHeap = []
self.minHeap = []
def addNum(self, num: int) -> None:
minHeap = self.minHeap
maxHeap = self.maxHeap
if len(minHeap) == len(maxHeap):
# 先加到大顶堆,再把大堆顶元素加到小顶堆
heappush(minHeap, -heappushpop(maxHeap, -num))
else:
# 先加到小顶堆,再把小堆顶元素加到大顶堆
heappush(maxHeap, -heappushpop(minHeap, num))
def findMedian(self) -> float:
minHeap = self.minHeap
maxHeap = self.maxHeap
if len(maxHeap) == len(minHeap):
return (minHeap[0] - maxHeap[0]) / 2
else:
return minHeap[0] if len(minHeap) > len(maxHeap) else -maxHeap[0]
# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()
class MedianFinder {
// 最大堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
// 最小堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
boolean even = true;
public MedianFinder() {
}
public void addNum(int num) {
if (even) {
// offer() - 将指定的元素插入队列。如果队列已满,则返回false
minHeap.offer(num);
// poll() - 返回并删除队列的开头
maxHeap.offer(minHeap.poll());
} else {
maxHeap.offer(num);
minHeap.offer(maxHeap.poll());
}
even = !even;
}
public double findMedian() {
if (even)
// peek() - 返回队列的头部
return (maxHeap.peek() + minHeap.peek()) / 2.0;
else
return maxHeap.peek();
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/
DataBase
1. SQL vs NoSQL
| SQL | NoSQL |
|---|---|
| Fixed schema | Free schema |
| Good query | Good performance |
| Table join | Relaxed consistency |
| ACID | Data de-normalization |
| Data normalization | 1-1, 1-n relationship |
| Any relationship | ... |
Network
1. What happens when you type a URL into your browser?
1. Bob enters a URL into the browser.
URL stands for Universal Resource Locator.

- Scheme: the protocol that server used, like
HTTPandHTTPS. - Domain: the domain name of the site.
- Path & Resource: They together specify the resource on the server we want to load.
2. Browser looks up IP in DNS cache
DNS stands for Domain Name System.
- If not found, browser looks up IP using recursive DNS lookup by the
DNS Reslover. - Then, recursive lookup on
DNS Servers. The answer is cached every step of the way. - Finally, browser get the IP address of the server.
3. Browser establishes TCP connection with the server
- complete the handshakes involved in TCP connection.
- To keep the loading process fast, browsers use keep-alive connection.
- If the protocol is
HTTPS, the connection will require a process called SSL/TLS handshake to establish the encrypted connection between the browser and the server.
4. Browser sends HTTP request to the server
5. Server sends back HTTP response
6. Browser renders HTTP content
Oftentimes, there are additional resources to load, like JavaScript codes and images.
System Design
1. 缓存和 DB 之间怎么保证数据一致性?
试想一个常见的场景
- write API: write to DB
- read API:
- read from cache if cannot get data ->
DB.get()读取数据库 - then
cache.set(), and set atime-to-live写入缓存并设置失效时间
- read from cache if cannot get data ->

当数据发生更新时,我们不仅要操作数据库,还要一并操作缓存。具体操作就是,修改一条数据时,不仅要更新数据库,也要连带缓存一起更新。
方案 1. Update Cache
第二步失败问题 但数据库和缓存都更新,又存在先后问题,此时有 2 个可选的方案:
cache.set()first, thenDB.set()即先更新 cache, 后更新 DB- 如果 cache 更新成功了,但 DB 更新失败,那么此时 cache 中是最新值,但 DB 中是旧值。虽然此时读请求可以 hit cache,拿到正确的值,但是,一旦 cache「失效」,就会从数据库中读取到旧值,rebuild cache 也是这个旧值。
DB.set()first, thencache.set()即先更新 DB, 后更新 cache- 如果 DB 更新成功了,但 cache 更新失败,那么此时 DB 中是最新值,cache 中是旧值。之后的读请求读到的都是旧数据,只有当 cache invalidation 缓存失效,才能从 DB 中得到正确的值。
- 这时用户会发现,自己刚刚修改了数据,但却看不到变更,一段时间过后,数据才变更过来,对业务也会有影响。
并发引起的一致性问题 假设我们采用 先更新 DB,再更新 cache 的方案,并且两步都可以成功执行的前提下,如果存在并发,情况会是怎样的呢?
有线程 A 和线程 B 两个线程,需要更新同一条数据,会发生这样的场景:
- At t0, thread A update DB for
x = 1 - At t1, thread B update DB for
x = 2 - At t1, thread B update cache for
x = 2 - At t2, thread A update cache for
x = 1
即 thread A 虽然先于 thread B 发生,但 B 操作数据库和缓存的时间在迅雷不及掩耳之势,早于 A 操作结束前完成。 最终 x 的值在 cache 中是 1,在 DB 中是 2,发生数据不一致问题。
Solution