python基础面试题
Python深浅拷贝是什么
import copy
浅拷贝:
python中的=号 赋值 其实就是浅拷贝
赋值只是复制了数据的内存地址 并不是产生了一个独立的新数据
如果对可变类型(列表、字典或自定义对象)复制时,实际上只是复制了这个数据的引用
地址而不是值
这意味着即使我们修改了新变量,也会影响原始对象。
深拷贝:
深拷贝需要用到copy模块的deepcopy方法
lst1 = ['a','b','c']
lst2 = copy.deepcopy(lst1)
深拷贝是真正的复制了原来数据的值,这两个对象都是独立的数据地址,对新的对象的任何修改都不会
影响原始对象。深复制在复制对象时会递归复制整个对象,包括其中的列表、字典等对象
魔法方法new和init有什么区别
魔法方法 __new__ 和 __init__ 都与对象的创建相关,但有不同的作用:
__new__(cls[, ...]) 是一个类方法,用于创建一个实例对象,但这是一个空的没有任何属性
的空实例化对象
__init__是一个实例方法,用于对于__new__出来的空实例化对象 添加属性
__init__里面的参数 self其实就是__new__出来的空实例化对象
可以通过__init__方法向空实例化对象中添加属性
应用场景:当我们需要创建单例对象时
可以在 `__new__` 方法中判断是否已存在该对象实例,如果存在则返回该实例。
当我们需要对某些对象添加初始化属性值时可以 重新 __init__中设置对象的属性值
`__new__` 方法会默认创建一个实例对象,`__init__` 用于对其进行初始化操作。
使用以下代码实现 Python 中的单例模式:
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
在这个实现中,我们将 `_instance` 设置为类变量,然后在 `__new__` 方法中判断该变量是
否为空,如果为空,则调用父类的 `__new__` 方法创建对象并将对象保存在 `_instance` 中,
否则直接返回_instance中存的实例。这样就可以保证在 Python 中只有一个实例对象。
这样重新了__new__来判断instance是否有值就可以控制 始终在用同一个实例化对象
python的可变和不可变数据类型是什么?
Python中的可变数据类型有:列表(list)、字典(dict)、集合(set)等,这些数据类型在修改时会
被改变原有对象的值。 顾名思义 这些类似就可以可以改变的类型 在原数据上可以改变
不可变数据类型 整型(int)、浮点型(float)、布尔型(bool)、元组(tuple)字符串(string)
当你对这些类型数据进行改变时;不会改变原来的数据,只是直接返给了你一个新的对象
什么是生成器,有什么应用场景
生成器本质也是一种特殊的函数类型,主要目的是为了节约内存和计算资源,控制一次性生成一个值
,还不是一次性将所有的值都生成出来,例如我们有一个函数一次性返回大量的数据,那我们就可
以砸i 这个函数中通过yield 语句来实现 一次生成一个值,
然后我们通过 for 循环或者 next() 方法来遍历 一次次取出值 对内存更友好
举例:
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
例如:
这是一个无限循环的函数,如果不用生成器,那结果将占大量内存,加了yield我们就可以一次
取出一个值
现在我们要取10个值就可以
fib = fibonacci()
for i in range(10):
print(next(fib))
使用for循环,然后进行10次next方法
装饰器相关
1.装饰器是什么:本质就是一个必包函数
作用:是不改变原来程序的调用方式和源代码的基础上,添加新的功能
装饰器本质上是一个函数,它接受被装饰的函数作为参数,并返回一个新的函数。
2.装饰器语法糖:python的特殊语法 @符合
作用是把被装饰得函数 当作参数 传入装饰器中 装饰器中的func参数 就是被语法糖装饰得函数
并把装饰器的执行结果,赋值给装饰器
当函数被装饰器语法糖装上 以后再调用该函数 其实执行的就是装饰器了
所以才有了不改变原来调用方式 可以增加新功能
应用场景:为函数添加日志记录 为函数添加事务处理 为函数添加权限检查
import time
def timer(func):
def index(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print("Time elapsed: ", end_time - start_time)
return result
return index
@timer
def my_function():
time.sleep(2)
my_function()
有参装饰器:以下是一个带参数的装饰器的示例,它允许指定函数的重试次数:
# 有参装饰器多套了一层
def retry(max_retries):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(max_retries):
try:
result = func(*args, **kwargs)
return result
except Exception as e:
print("Error:", e)
time.sleep(1)
raise Exception("Max retries exceeded")
return wrapper
return decorator
@retry(max_retries=3)
def my_function():
print("Trying...")
raise Exception("Something went wrong")
my_function()
什么是GIL锁,有什么作用
全局解释器锁,是 cPython 解释器提供的一种机制。它的作用是在同一时刻只能
有一个线程执行,也就是说,当使用多线程来执行 Python 代码时,虽然有多个
线程在执行,但是它们并不会真正的并行执行。而是通过 GIL 机制来保证同一时
间只有一个线程进行执行
作用是为了保证 Python 内部数据的安全,因为 Python 的垃圾回收机制也是需要一条
线程在运行,如果多个线程可以同时进行 会导致可能把其他线程正在使用的数据回收
掉,所以才有了GIL锁
有了GIL锁为什么还要互斥锁?
GIL锁只是锁定了cpython解释器的锁,线程要执行必须先获得GIL锁才能执行
互斥锁:是为了保证多线程并发操作共享数据而设置的锁,保证了数据在加锁和释放锁之间
其他线程不能操作 保证了数据的安全
GIL锁锁不住共享数据的,所以还需要我们自己加上互斥锁
垃圾回收机制
Python垃圾回收机制主要通过引用计数和循环引用两种方式实现。
引用计数---对象都有一个引用计数器,当一个新的引用指向该对象时,引用计数
器加1;当一个引用被删除时,引用计数器减1。一旦没有引用,内存就直接释放了
标记清除----是指Python通过对所有可达对象进行遍历和标记操作,并清除不可达对
象的过程。这种方式解决了循环引用的问题。
分代回收----把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次
一代的垃圾回收检查中,该对象存活下来,就会被放到二代中,同理在一次二代的
垃圾检查中,该对象存活下来,就会被放到三代中,后面优先检查第一代中的
对象,优先回收,其次依次往上检查做回收
为什么计算密集型用多进程,io密集型用多线程
多线程是依赖于全局解释器锁(GIL)的,而该锁导致同一时间只允许一个线程运行 Python。因此,多线程在CPU密集型工作下不能很好地利用多核CPU的优势,因为同一时刻只有一个线程能够占用CPU资源。
开启多进程就可以利用到多核cpu的优势
对于IO密集型任务,大部分时间都是在等待IO操作完成。因此,当一个线程在等待一个IO操作完成时,就可以执行另一个线程,所以io密集任务开启多线程可以提高效率和并发的
python中进程,线程,协程
进程:进程是资源分配的最小单位,一个应用程序运行,至少会开启一个进程,每个进程都
有自己的内存空间和系统资源。
如何创建多进程:
1.通过Process类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程
2.写一个类,继承Process,重写类的run方法---》实例化得到对象,对象.start 开启了进程
import multiprocessing
def worker(num):
"""子进程要执行的任务"""
print("Worker %d is running" % num)
if __name__ == '__main__':
p = multiprocessing.Process(target=worker, args=(i,))
p.start()
线程:线程是cpu调度的最小单位,cpu执行的最小单位,一个进程下至少有一个线程,
通过Thread类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程
使用 Python 的 threading 模块来创建多个线程来执行不同的任务:
在哪里用过:一般遇到计算密集型的操作我会开多进程,io密集型的开多线程
写爬虫的时候会用到多线程,一边解析 一边下载 一边保存
还有异步发送一些通知,当然可以是用celery模块做异步操作
celery的worker其实就是进程线程架构
import threading
def task1():
for i in range(5):
print("Executing task 1 iteration ", i)
def task2():
for i in range(5):
print("Executing task 2 iteration ", i)
# 创建两个线程来执行不同的任务
thread1 = threading.Thread(target=task1)
thread2 = threading.Thread(target=task2)
# 启动线程来执行任务
thread1.start()
thread2.start()
# 等待两个线程执行完毕
thread1.join()
thread2.join()
print("All tasks complete.")
```
协程:单线程下实现并发,代码层面遇到io,自己切换,是由程序员来控制的,可以让cp
u感知不到io,这样就可以高效利用cpu,
代码;可以使用gevent模块 或者使用 async 和 await关键字开启协程
async def task(): ---执行结果是一个协程函数
await是只要遇到了io操作的代码就加上await,程序中遇到await就会切换到
另一个程序 不停切换实现协程
使用经历:一般遇到计算密集型我会开多进程,io密集型的程序一般开多线程处理,比如之前写过爬虫,都是用的多线程,
但是在项目中 我直接都是用的 celery去进行异步操作,之前的交易系统也是用了异步发布订单 异步去校验,异步的去发送一些通知
celery中的work其实就是帮助你开启了进程线程去做一些事情
我们程序中虽然没有主动开启多进程多线程 但是uwsgi服务器帮助我们在进入django程序之前 开启了多线程来执行视图函数的
进程间如何通迅
1.队列是多进程之间最常用的通信方式。一个进程向队列中写入数据,另一个进程从队列中
读取数据。multiprocessing的Queue方法
2.管道也是用于在两个进程之间传递数据的一种方式。一个进程向管道写入数据,另一个进程
从管道中读取数据。multiprocessing的Pipe方法
示例代码:使用队列进行进程间通信。
from multiprocessing import Process, Queue
def producer(q,f):
name = 'MOON'
f.put(name)
for i in range(5):
print(i)
q.put(i)
def consumer(q,f):
name = f.get()
print(name)
while True:
value = q.get()
print('Consumer got', value)
'''
1.生成队列q = Queue() 可以生成多个
2.编写要多进程代码 然后把队列传进入
3.然后q.put(i) 向队列中放数据
4.q.get()向队列中取数据
'''
if __name__ == '__main__':
q = Queue()
f = Queue()
# 生成一个队列
p1 = Process(target=producer, args=(q,f))
p2 = Process(target=consumer, args=(q,f))
p1.start()
p2.start()
p1.join()
p2.join()
在以上代码中,`producer`函数向队列中写入数据,`consumer`函数从队列中读取数据。在主进程中创建了两个子进程进行通信。调用`join()`方法让主进程等待子进程结束。
示例代码:使用管道进行进程间通信。
from multiprocessing import Process, Pipe
# 子进程的代码
def child_process(conn):
conn.send("你好,我是子进程")
message = conn.recv()
print("子进程收到消息:", message)
conn.close()
# 主进程的代码
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
process = Process(target=child_process, args=(child_conn,))
process.start()
print("父进程收到消息:", parent_conn.recv())
parent_conn.send("你好,我是父进程")
process.join()
我们通过使用`Pipe`模块创建了两个连接对象:`parent_conn`和`child_conn`。然后我们
创建了一个子进程,并将连接对象`child_conn`作为子进程的参数传递。在子进程里,
我们首先通过`conn.send()`方法向父进程发送了一条消息。然后,我们在父进程里通过
`parent_conn.recv()`方法接收到了来自子进程的消息,并打印输出。接着,我们又通过
`parent_conn.send()`方法向子进程发送了一条消息。最后,我们通过`process.join()`方法等
待子进程执行结束。
什么是猴子补丁,有什么用途
猴子补丁(Monkey Patching)是指在程序运行过程中,动态替换的一种技术
是可以快速地解决代码中的一些问题,
比如:内置的json,效率低,第三方的ujson模块效率高,如果在项目中
替换到原来的json使用效率高的ujson呢
-在程序运行的入口处:import ujson as json
比如:我们经常用的 将django中链接mysql的模块
django中 pymysql的替换
import pymsql
pymysql.install_as_mysqlDB()
高阶:我们用的gevent模块 需要执行 monkey.pach_all()
因为是通过猴子补丁 把原本会在内部阻塞的模块,动态替换成gevent自己写的。
把所有同步的代码,都替换成异步代码,遇到io,不释放gil锁实现了协程
什么是反射,python中如何使用反射
ython中的反射是指 程序在运行过程中通过字符串来操作对象的属性和方法
运用反射 我们就可以使用字符串来调用对象中的方法或属性了 用这个字符串去反射到对象中的具体属性或方法
Python中的反射主要通过`getattr()`、`setattr()`等内置函数实现
hasattr 判断是否存在该方法
delattr 删除
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say_hello(self):
print(f"Hello, my name is {self.name}.")
p = Person("Alice", 18)
# 使用getattr获取属性
print(getattr(p, "name",'暂无信息'))
# 我们可以用字符串的形式来执行对象中的方法 第三个参数为默认值
# 使用setattr修改属性
setattr(p, "age", 20)
print(p.age) # 20
# 使用getattr调用方法
getattr(p, "say_hello")()
在上面的示例中,我们首先定义了一个`Person`类,创建了一个`Person`对象`p`,然后使用反射获取
、修改、检查属性,以及调用一个对象的方法。
数据库面试题
数据库三大范式是什么
数据库三大范式(normal forms)是设计数据库时需要遵循的规范
1.确保数据库中的每一列是不可再分,字段不要出现复合属性 复合字段
2.数据库中的每条数据必须有一个唯一标示,正常都是主键 唯一 且一条数据中只有一个主键
非主键列完全依赖于主键
# 如果用姓名坐主键 不太好 就会出现一个姓名拿到好几条数据 不复合规范
# 要达到所有非主键字段查询 依赖主键
3.表中的非主键列必须和主键直接相关而不能间接相关 例如订单表只要订单的信息最好不要加上商品的
信息,也就是说:非主键列之间不能相关依赖,一个表明确表达一个意思,例如电商系统订单表
# 订单表 不要添加很多商品的字段 例如商品价格 商品详情等
# 我们可以用两个表来处理,订单表只有 id 购买人id 时间 商品id
# 然后再 多创建一个商品详情表关联订单表
这样可以避免数据冗余和更新数据时发生异常,解耦合
当然工作中也有不按照数据库规范的情况,例如优化性能,可以把用户表和用户详情表都在一个表中等
mysql有哪些索引类型,分别有什么作用
索引类型:
1.普通索引 普通列上的索引可以是重复的值
没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一 点
2.唯一索引 就是当前列上的值是唯一的。unique=ture 但是允许为空值。
3.主键索引(聚集索引) 每个表里只有一个主键索引 主键索引自带非空且唯一属性 primary key
4.联合索引 在表中的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时
# 例如,这里由id、name和age3个字段构成的索引,索引行中就按id/name/age的顺序存放
# 索引可以索引下面字段组合(id,name,age)、(id,name)或者(id)。如果要查询的字段不构成索引最左面的前缀,那么就不会是用索引,比如,age或者(name,age)组合就不会使用索引查询。
5.全文索引
# 只能在CHAR,VARCHAR,TEXT类型字段上使用全文索引,介绍了要求,说说什么是全文索引,就是在一堆文字中,通过其中的某个关键字等,就能找到该字段所属的记录行,比如有"你是个大煞笔,二货 ..." 通过大煞笔,可能就可以找到该条记录。一般开发中,不贵用到全文索引,因为其占用很大的物理空间和降低了记录修改性,故较为少用。
如何创建索引
CREATE INDEX index_name ON table(column(length)); 创建普通索引
# 关键字index
CREATE UNIQUE INDEX indexName ON table(column(length)); 创建唯一索引
# 关键字UNIQUE INDEX
CREATE FULLTEXT INDEX index_content ON article(content); 全文索引
# 关键字FULLTEXT INDEX
创建表的时候创建索引
索引的优缺点:
1、优点:创建索引可以大大提高系统的性能。
第一、通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
第二、可以大大加快 数据的检索速度,这也是创建索引的最主要的原因。
第三、可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
第四、在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
第五、通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
2、缺点:
第一、创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
第二、索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间。如果要建立聚簇索引,那么需要的空间就会更大。
第三、当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
事务的特性和隔离级别
事务具有以下四个特性:ACID(原子性、一致性、隔离性和持久性)。
1.原子性(Atomicity):事务是一个整体,事务中的所有操作要么都执行成功,要么都不执行。如果事
务中的任何一个操作失效,整个事务会被回滚到它开始的状态。
2. 一致性(Consistency):事务执行前后,数据库中的数据都必须保持一致的状态。
eg:转账我的钱减了那一定有人钱增加了
3.隔离性(Isolation):事务之间应该互相隔离,每个事务的执行都不应该影响其他事务的执行。这保
证了每个事务都能够独立地执行。这与事务的隔离级别密切相关,默认可重复读级别。
4.持久性(Durability):事务执行完成并提交之后,它所对应的操作结果会持久化保存在数据库中,即
使数据库发生崩溃或其他故障,也不会对操作结果造成影响。
隔离级别是指不同事务之间的隔离程度。常见的隔离级别包括:
1. 读未提交(Read Uncommitted):允许事务读取其他事务未提交的数据,可能会出现脏读的情况
。
2. 读已提交(Read Committed):在一个事务提交之后才允许其他事务读取数据,解决了脏读的问
题,但是可能会出现不可重复读的问题。
3. 可重复读(Repeatable Read):在一个事务读取数据的过程中,其他事务不能修改相同的数据,解
决了脏读和不可重复读的问题,但是可能会出现幻读的问题。
4. 串行化(Serializable):所有事务按照顺序依次执行,可以确保不会出现并发问题,但是效率较低。
脏读,不可重复读,幻读
脏读(Dirty Read)是指一个事务读取了另一个事务未提交的数据
后者进行了回滚,那么前者读取的数据就是脏数据。解决脏读的方法是采用锁机制,可以使用排它锁
(Exclusive Lock)来避免其他事务对该数据的读写操作。
不可重复读(Non-Repeatable Read)是指在一个事务内多次查询同一记录,但是却在两次查询之间有
另一个事务修改了该记录,导致前一次和后一次查询结果不同。
解决不可重复读的方法是采用行级锁(Row-Level-Lock),这样在查询的过程中,修改同一记录的其他
事务将会被锁住,直到当前事务结束。
幻读(Phantom Read)是指在同一事务下多次查询同一范围内的记录,但是却在两次查询之间有另一
个事务插入或删除了该范围内的记录,导致前一次和后一次查询结果不同。
解决幻读的方法是采用表级锁(Table-Level-Lock),在查询、插入和删除操作之前先对表进行加锁,
保证了事务的排他性,防止其他事务干扰当前事务。
总的来说,解决数据并发问题需要采用锁机制来避免因为多个事务对同一数据进行读写操作而导致的问
题。但是,使用锁机制也可能会影响系统的性能,因此需要在实际应用中根据业务场景和需求来选择不
同的锁机制。
qps/tps/并发量/pv/uv
QPS:每秒请求数(Queries Per Second),指的是服务器在每秒内处理的请求数量。通常用于衡量系
统的性能和负载能力。
TPS:每秒事务数(Transactions Per Second),指的是在每秒内处理的事务数量。用于衡量系统处理
事务的能力。
并发量:指同时请求服务器的请求数量。在同一时间内,如果有很多用户在请求服务器,那么服务器需
要处理这些请求,即需要处理并发访问量。通常用来衡量系统的承载能力。
PV:页面访问量(Page Views),指网站或页面的浏览次数,即用户打开页面的次数。
UV:独立访客数(Unique Visitors),指访问网站或页面的唯一访客数量,即去重后的用户访问量。通
常用于衡量网站或页面的受众覆盖率。
接口幂等性问题
接口幂等性指的是相同的请求被重复发送时,服务器的响应应该相同。
比如向数据库中插入一条记录。如果客户端重复发送相同的请求,服务器将会重复插入相同的记录,导
致数据错误。
接口的幂等性问题是指在重复调用同一接口时,是否会对数据产生重复的影响,即多次调用接口对数据
造成的影响是相同的。
解决方式:
1.为每个请求生成唯一的标识符,确定请求的唯一性。可以使用uuid或者时间戳来生成唯一标识符,每
次请求时将标识符作为参数传递。
添加请求头:可以在请求头中添加一个唯一的标识符,例如UUID(通用唯一标识符),确保每个请求的标
识符都是唯一的。
2.时间戳
可以使用时间戳来解决接口幂等性问题。具体地,可以在请求中添加一个自定义参数,例
如"timestamp",此参数的值为当前时间的毫秒数。然后在服务端判断该值是否已经使用过,如果已经
使用过,则表示该请求已经被处理过,直接返回结果;如果没有使用过,则执行相应的逻辑并在结果中
返回自定义参数的值,以便在下次请求时进行验证。
3.token机制,随机字符串
-在使用新增之前,先生成一个随机字符串token,保存起来,只要调用新增接口,就必须携带随机字符
串token,到后端,校验这个token在不在我们保存的里面,如果在,说明正常,删除token,允许新
增,如果不在就不允许新增
4.UUID
为了解决接口幂等性问题,我们可以引入 UUID(Universally Unique Identifier)唯一标识符,将
UUID 作为每个请求参数中的一个字段,在请求时将 UUID 出现的次数限制为 1,以此避免客户端对同
一请求的多次发送。
什么是鸭子类型?
python中的鸭子类型是指:面向对象中的一种概念,只要一个类中和其他类有相同的属性或者方法,那
它们就是一类,不关心对象的具体类型,只关心它们的行为,即使它们本身没有关联,只要它们具有相
同的行为。
不管是你哪个类,只要你们有相同的方法 就是一类,这就是鸭子类型,python就是鸭子类型
举例:
在Python中,我们可以使用`len`函数来获取一个对象的长度。而无论这个对象是什么类型,只要它们都
实现了`__len__`方法,即可使用`len`函数来获取它们的长度。这就是鸭子类型的一个例子。
设计模式
一共有23种设计模式,目前我们接触过的有
单例模式 一直使用同一个对象 可以重写类的__new__方法控制实现
代理模式 我们用的对象不是真正的对象,这个对象只是帮助我们去拿真正的对象
http和https的区别
什么是Http,什么是Https
1.HTTP 超文本传输协议 ,应用层协议。是一种未加密的协议,
当您从浏览器中请求一个页面时,信息以明文形式发送,可能被拦截、窃取和篡改。端口号为 80
2.Https 则是具有安全性的加密传输协议。是由HTTP+SSL/TLS构建的加密传输 端口号为443
使用HTTPS还可以提高网站的搜索引擎排名和可信度。因为现在大多数浏览器都标识HTTP网站为不安全的
网页常用状态码
网页响应状态码是由服务器返回的三位数字,表示请求是否成功或出现错误。以下是常见的网页响应状
态码及其含义:
1xx系列:信息响应,表示请求已被接收,继续处理。
- 100(继续处理):服务器已经接收了请求头,并且客户端应该继续发送请求主体。
- 101(切换协议):服务器已经理解了客户端的请求,并将通过协议升级的方式切换到不同的协议。
2xx系列:成功响应,表示请求已经被服务器成功处理。
- 200(OK):请求成功,服务器已经正确处理了请求。
3xx系列:重定向响应,表示请求必须有进一步的处理行为。
- 301(永久重定向):请求的资源已经被永久的移动到了新的URL上。
- 302(临时重定向):请求的资源临时被移动到了新的URL上。
4xx系列:客户端错误响应,表示客户端的请求存在错误或无法被完成。
- 400(错误请求):请求存在语法错误或无法被服务器理解。
- 403(禁止访问):请求被服务器拒绝。
- 404(未找到):请求的资源不存在,或服务器无法被查找。
5xx系列:服务器错误响应,表示服务器不能完成对请求的处理。
- 500(服务器错误):服务器不能完成对请求的处理,因为存在内部错误。
- 503(服务不可用):服务器暂时不可用,可能因为过载或维护。
- 504(网关超时):服务器作为代理或网关使用时,没有及时接收到上游服务器的响应。