参考https://toutiao.io/posts/6z3uu2m/preview,https://zhuanlan.zhihu.com/p/482817229
SLB负载均衡发给API网管再分给抢购系统
10w一分钟,28法则,8w10s,有40w人抢10s,4w/s的qps -》 常规系统1000/s(4核8G的机器,开200个线程处理请求)
后果:网络带宽打满、cpu使用率达到90%多、数据库负载过高、下游依赖频繁超时
例如:写数据尽量直接写缓存,然后异步写db;读数据尽量优先把数据缓存在系统jvm内存里,本地读取返回
击穿jvm和redis要查mysql:抢购系统内可以开一个后台线程,然后让他每隔30min自动去redis里查最新缓存数据,或者去商品系统查最新缓存数据,然后刷新本地缓存
缓存不一致:扣减and恢复的顺序不一致(乱序),所以要实现mq顺序消息,发到同一个分区有序
前端:页面上各类静态资源首先应分开存放,然后放到cdn节点上分散压力,倒计时用后端获取按钮变灰,查询和购票只能按一次,js限制时间
后端:同一个uid限制频率,nginx分发请求到不同机器,预处理是否还有余量
数据库:分片解决数据量太大,路由哪个库(范围,哈希(均匀),路由服务)
超卖:悲观锁解决安全,但慢,乐观锁版本号机制控制
稳定性:redis雪崩,先进性预热
1.哈希,md5等彩虹表攻击
2.加盐哈希(盐就是随机的字符串拼接密码)
3.匿名化、差分隐私、同态加密
详见小林coding:https://www.xiaolincoding.com/network/2_http/http_interview.html#https-%E6%98%AF%E5%A6%82%E4%BD%95%E5%BB%BA%E7%AB%8B%E8%BF%9E%E6%8E%A5%E7%9A%84-%E5%85%B6%E9%97%B4%E4%BA%A4%E4%BA%92%E4%BA%86%E4%BB%80%E4%B9%88
妙不可言,k个一组翻转
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
n = 0
cur = head
while cur:
n += 1 # 统计节点个数
cur = cur.next
p0 = dummy = ListNode(next=head)
pre = None
cur = head
while n >= k:
n -= k
for _ in range(k): # 同 92 题
nxt = cur.next
cur.next = pre # 每次循环只修改一个 next,方便大家理解
pre = cur
cur = nxt
# 见视频
nxt = p0.next
nxt.next = cur
p0.next = pre
p0 = nxt
return dummy.next
长方形纸片上有一个圆洞,怎样才能沿着直线把它剪成面积相同的两块(几何中心相连)
一根木棍掰成三段,组成三角形的概率(条件概率) 1/4
匿名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用
有名管道 (named pipe) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
消息队列( message queue ) :消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号量( semophore ) :信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
信号 ( sinal ) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生
共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式
套接字( socket ) :套接口也是一种进程间通信机制(bind listen connect send close)
动态大小
数组的大小在定义时就被固定了,无法在运行时进行扩展或缩小,而vector则可以动态调整大小,可以在运行时根据需要进行扩展或缩小。
内存管理
数组在内存中是一段连续的空间,需要手动分配和释放内存。而vector会在需要的时候自动进行内存管理,不需要手动处理内存的分配和释放,这样可以减少内存泄漏和其他内存管理问题的出现。
使用方便性
数组使用时需要手动进行遍历和访问元素,使用起来比较繁琐,而vector提供了一系列的成员函数,例如push_back、pop_back、insert、erase等,可以方便地进行元素的添加、删除和访问。
适用性
数组适合用于存储数据量固定的情况,例如矩阵运算、排序算法等;而vector适合用于数据量不确定或需要动态扩展的情况,例如动态存储读入的数据、动态维护容器等。
总之,vector和数组都有各自的优势和适用场景,需要根据实际情况进行选择。如果需要动态调整大小、不需要手动管理内存或需要方便地访问元素,则应该选择vector;如果需要固定大小、需要手动管理内存或只需要简单地访问元素,则可以选择数组。
(1):互斥量用于线程的互斥,信号线用于线程的同步。这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
(2):互斥量值只能为0/1,信号量值可以为非负整数。
互斥:同一个资源同一时间只有一个访问者可以进行访问,其他访问者需要等前一个访问者访问结束才可以开始访问该资源。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:分布在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。所以同步就是在互斥的基础上,通过其它机制实现访问者对资源的有序访问。
总结:同步是一种更为复杂的互斥,而互斥是一种特殊的同步。
同步:同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。
异步:异步和同步是相对的,异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。
用户:账户、 存款、取款、 转账、修改密码、退出、注销账户
系统管理员:注册、查看用户信息
1.用一个长度为1w的stl存,遍历找到topk
2.分治:分成100份每份100w,找到每份的top1w,在100w个找1w个最大的,过滤掉99%
3.hash去重
4.最小堆
冒泡排序:如果两个元素相等,没有动作
插入排序:如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变
归并排序:2个元素如果大小相等也没有人故意交换
基数排序
只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。
new operator 总是先调用 operator new,而后者我们是可以自行声明重写的
Web攻击动机:
恶作剧;
关闭Web站点,拒绝正常服务;
篡改Web网页,损害企业名誉;
免费浏览收费内容;
盗窃用户隐私信息,例如Email;
以用户身份登录执行非法操作,从而获取暴利;
以此为跳板攻击企业内网其他系统;
网页挂木马,攻击访问网页的特定用户群;
仿冒系统发布方,诱骗用户执行危险操作,例如用木马替换正常下载文件,要求用户汇款等
1.SQL注入(SQL Injection):入检查不严格,用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据,这就是所谓的SQL Injection,即SQL注入。参数化查询的方法(推荐)
2.跨站脚本漏洞(XSS) :插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,社区等从数据库读出数据的正常页面(比如BBS的某篇帖子中可能就含有恶意代码。过滤,html编码,把特殊字符解析
3.跨站请求伪造(XSRF ): 可能会窃取或操纵客户会话和 cookie,它们可能用于模仿合法用户;refer只接受本域;一次性令牌token
4.表单重复提交:提交完清除token
jwt:json web token,经过数字签名
Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用
Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人
Header:头部,通常头部有两部分信息:
声明类型,即token的类型(“JWT”)
算法名称(比如:HMAC SHA256或者RSA等等)。
然后,用Base64对这个JSON编码就得到JWT的第一部分
Payload:载荷,就是有效数据,一般包含下面信息:
用户身份信息(注:这里因为采用base64编码,可解码,因此不要存放敏感信息;除非单独加密)
注册声明:如token的签发时间,过期时间,签发人等
这部分也会采用base64编码,得到JWT第二部分数据
Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥(secret)(不要泄漏,最好周期性更换),通过加密算法生成。用于验证整个数据完整和可靠性
有登陆状态cookie+session
无登陆状态token
token + rsa非对称加密防止伪造
防止重放:请求数据加一个唯一字段(时间戳ornonce)然后用客户端私钥进行签名,如果服务端发现唯一字段已经用了就拒绝
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# dp[i]表示选第i个的最大连续和
n = len(nums)
dp = [0] * n
dp[0] = nums[0]
for i in range(1, n):
dp[i] = max(nums[i], dp[i - 1] + nums[i])
return max(dp)
class DLinkedNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.cache = dict()
# 使用伪头部和伪尾部节点
self.head = DLinkedNode()
self.tail = DLinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
self.capacity = capacity
self.size = 0
def get(self, key: int) -> int:
if key not in self.cache:
return -1
# 如果 key 存在,先通过哈希表定位,再移到头部
node = self.cache[key]
self.moveToHead(node)
return node.value
def put(self, key: int, value: int) -> None:
if key not in self.cache:
# 如果 key 不存在,创建一个新的节点
node = DLinkedNode(key, value)
# 添加进哈希表
self.cache[key] = node
# 添加至双向链表的头部
self.addToHead(node)
self.size += 1
if self.size > self.capacity:
# 如果超出容量,删除双向链表的尾部节点
removed = self.removeTail()
# 删除哈希表中对应的项
self.cache.pop(removed.key)
self.size -= 1
else:
# 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node = self.cache[key]
node.value = value
self.moveToHead(node)
def addToHead(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def removeNode(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def moveToHead(self, node):
self.removeNode(node)
self.addToHead(node)
def removeTail(self):
node = self.tail.prev
self.removeNode(node)
return node
为了避免分组丢失,控制发送者的发送速度,使得接收者来得及接收,这就是流量控制。流量控制根本目的是防止分组丢失.滑动窗口协议既保证了分组无差错、有序接收,也实现了流量控制。主要的方式就是接收方返回的 ACK 中会包含自己的接收窗口的大小.避免流量控制引发的死锁,TCP使用了持续计时器.收到一个零窗口的应答后就启动该计时器。时间一到便主动发送报文询问接收者的窗口大小
拥塞控制:拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况;常用的方法就是:( 1 )慢开始、拥塞避免( 2 )快重传、快恢复。流量控制:流量控制是作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失的。
慢开始算法的思路就是,不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小,拥塞窗口cwnd就加倍
为了防止cwnd增长过大引起网络拥塞,还需设置一个慢开始门限ssthresh状态变量。ssthresh的用法如下:当cwnd
慢开始门限ssthresh设置为出现拥塞时的发送窗口大小的一半,慢开始门限ssthresh设置为出现拥塞时的发送窗口大小的一半,慢开始门限ssthresh设置为出现拥塞时的发送窗口大小的一半
快重传算法:快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方,可提高网络吞吐量约20%)而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期.而是将cwnd设置为ssthresh减半后的值,然后执行拥塞避免算法,使cwnd缓慢增大
状态码200:
状态码200表示服务器响应成功,也就是服务器找到了客户端请求的内容,并且将内容返回给客户端。
状态码302:
状态码302代表临时跳转。例如:URL地址A可以向URL地址B上跳转,但这并不是永久性的,在经过一段时间后,URL地址A还可能向URL地址C上跳转。
状态码301 :
状态码301和状态码302相似,不同的是状态码301往往代表的是永久性的重定向,值得注意的是,这种重定向跳转,从严格意义来讲不是服务器跳转,而是客户端跳转的。这个“跳”的动作是服务器是通过回传状态码301来下达给客户端的,让客户端完成跳转。
状态码304:
服务器通过返回状态码304可以告诉客户端请求资源成功,但是这个资源不是由服务器提供返回给客户端的,而是客户端本地浏览器缓存中就有的这个资源,因为可以从缓存中获取这个资源,从而节省传输的开销。
状态码403:
状态码403代表请求的服务器资源权限不够,也就是说,没有权限去访问服务器的资源,或者请求的IP地址被封掉了。
状态码404:
状态码404代表服务器上没有该资源,或者说服务器找不到客户端请求的资源,是最常见的请求错误码。
状态码500:
状态码500代表程序错误,也就是说请求的网页程序本身报错了。在服务器端的网页程序出错。由于现在的浏览器都会对状态码500做一定的处理,所以在一般情况下会返回一个定制的错误页面
io多路复用,监视多个描述符,一旦就绪就通知程序进行读写操作,select和poll和epoll是同步io,读写过程是阻塞的
select的几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
poll和select只是描述fd集合不同,poll没有最大限制
epoll可以避免上述缺点:
epoll_create创建eventpoll对象(红黑树,双链表)
一棵红黑树,存储监听的所有文件描述符,并且通过epoll_ctl将文件描述符添加、删除到红黑树
一个双链表,存储就绪的文件描述符列表,epoll_wait调用时,检测此链表中是否有数据,有的话直接返回
所有添加到eventpoll中的事件都与设备驱动程序建立回调关系
优点:
时间复杂度为O(1),当有事件就绪时,epoll_wait只需要检测就绪链表中有没有数据,如果有的话就直接返回
不需要从用户空间到内核空间频繁拷贝文件描述符集合,使用了内存映射(mmap)技术
当有就绪事件发生时采用回调的形式通知用户线程
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
cur, pre = head, None
while cur:
tmp = cur.next # 暂存后继节点 cur.next
cur.next = pre # 修改 next 引用指向
pre = cur # pre 暂存 cur
cur = tmp # cur 访问下一节点
return pre
实现功能
发朋友圈
评论动态
查看朋友圈(只能查看好友的)
查看评论(只能查看共同好友的)
我们先来看看微信是怎么设计的。朋友圈目前有两个入口:
插入排序。
插入排序算法的思想是将数组分为已排序区间和未排序区间,每次将未排序区间的第一个元素插入到已排序区间中的合适位置。对于基本有序的数组,已排序区间较大,因此插入排序的时间复杂度可以达到O(n)级别
(1)第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。
(2)第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。
(3)第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据。
主要是因为在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费
TCP 使用三次握手建立连接的最主要原因是防止「历史连接」初始化了连接
源端口
第一个字段是 源端口( source port ),它的长度为 16 位,表示报文 发送方 的端口号。
目的端口
第二个字段是 目的端口( destination port ),它的长度为 16 位,表示报文 接收方 的端口号。
序号
序号( sequence number )字段,长度为 32 位,表示数据首字节的序号。在三次握手阶段,SYN 指令也是通过该字段,将本端选定的 起始序号 告诉接收方。
确认号
确认号 ( acknowledgement number )字段,长度为 32 位。它表示已确认收到的数据序号,它的值为:已收到数据最后一个字节的序号加一,即接收方期望进一步接收的数据序号。
头部长度
头部长度 ( header length )字段,长度为 4 位,表示 TCP 报文头部的长度,也可称为 数据偏移 ( data offset )。跟 IP 协议一样,TCP 头部长度字段也不是以字节为单位,而是以 32 位字(4字节)为单位。
标志位
标志位( flags ),长度为 9 位,用于保存一些标志位。前面提到的 SYN ACK FIN 等指令,就是以标志位的形式保存在该字段中。
窗口大小
窗口大小( window size )字段长度为 16 位,表示当前报文发送者接收窗口的大小,单位一般是 字节 。接收窗口表示接收方还能接收的数据大小,用于实现 TCP 流量控制 机制,
校验和
校验和( checksum )字段长度为 16 位,保存报文段的校验和,用于纠错
预防死锁:破坏请求保持条件、不可抢占、循环等待条件
避免死锁:银行家
检测:资源分配表和进程等待表
解除:让死锁进程抢占资源;终止进程知道没有死锁
关系型数据库 (RDB) 是一种在表、行和列中构建信息结构的方法
1nf:不可分割
2nf:消除部分函数依赖
3nf:消除传递函数依赖
长链接:
长链是完整的URI,包含协议、域名、路径和查询参数等信息。例如,https://www.example.com/page?param1=value1¶m2=value2 就是一个长链。
短链接:
短链是对长链进行简化的一种形式,通常由一个较短的字符串表示。短链可以通过将长链映射到一个短字符串来实现,这个映射关系存储在数据库或者其他数据结构中。用户在访问短链时,系统会将其转换为对应的长链并进行跳转。短链的好处是更易于记忆和分享。
短链是通过服务器重定向到原始链接实现的
hash:碰撞
统一发号器:redis自增,单机10w
数据量级是否要分库分表
布隆过滤器是为了防止缓存击穿,造成服务器压力过大
1、直接将敏感词组织成String后,利用indexOf方法来查询。
2、传统的敏感词入库后SQL查询。
3、利用Lucene建立分词索引来查询。
4、利用DFA算法来进行。
DFA确定有穷自动机+trie树:过滤敏感词,就是把需要过滤的文本,从第一个字开始,逐个字往后在Trie树中查找。如果能走到树的结束节点,则就能发现敏感词
ac自动机:trie+kmp前缀指针+失配指针