一、Python 基础知识
1.1 可变与不可变数据类型
不可变数据类型这些数据类型的实例一旦创建,其值就不能改变,也叫可 hash 类型。如果尝试改变其值,实际上会创建一个新的实例,内存地址也改变了。不可变数据类型包括:
- 整型(int)
- 浮点型(float)
- 布尔型(bool)
- 字符串(str)
- 元组(tuple)
可变数据类型:这些数据类型的实例创建后,其值可以改变,不会创建新的实例,不可 hash 类型;在值改变的情况下, 内存地址(ID)不变,证明改变的是原值 。可变数据类型包括:
- 列表(list)
- 字典(dict)
- 集合(set)
1.2 深浅拷贝
浅拷贝:不管多么复杂的数据结构,浅拷贝都只会copy一层,创建新对象,其内容是原对象的引用。 深拷贝:深拷贝拷贝了对象的所有元素,直到最后一层,创建了一个完全独立的副本。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。 赋值:就是将一个内存地址赋值给一个变量,本质就是一种对象的引用。
对于 数字 和 字符串 而言,赋值、浅拷贝和深拷贝无实际变化,因为在这些操作之后,该数字或字符串还是指向同一个内存地址。
对于字典、元祖、列表 而言,进行赋值、浅拷贝和深拷贝时,其内存地址的变化是不同的。
1.3 PEP8 编程规范
- 缩进:使用4个空格的缩进(不使用Tab)。
- 在函数和类以及方法之间使用两个空行。
- 在类的方法之间使用一个空行。
- 避免在一行中使用多个语句。
- 导入总是应该放在文件的顶部。
- 模块导入应该按照从最通用到最不通用的顺序进行:标准库导入,相关第三方导入,本地应用/库特定导入。
- 避免使用野生导入(
from <module> import *
) - 类名应该使用驼峰命名法,函数和方法名应该使用小写字母和下划线。
1.4 匿名函数
在Python中,匿名函数是指没有名字的函数,它们由关键字lambda
定义。匿名函数可以接受任意数量的参数,但只能有一个表达式。它们通常用于需要一个小函数的地方,例如作为一个函数的参数或者用于定义一个简短的回调函数。
匿名函数的主要作用是:
- 简化代码:如果一个函数的逻辑非常简单,只需要一行代码就能完成,那么使用匿名函数可以使代码更简洁。
1.5 装饰器
不改变函数的调用方式,将一部分共有的代码抽象出来封装成一个函数,可以实现动态扩展功能,一般通过*args
和**kwargs
来传递参数,*args
用于接收任意数量的位置参数,参数将被收集到一个元组中,**kwargs
用于接收任意数量的关键字参数,参数将被收集到一个字典中
运用场景的话:最普遍的就是用户登入验证,然后还有记录日志,缓存,验证函数参数等
1.6 迭代器
在Python中,迭代器是一个可以记住遍历的位置对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
迭代器的工作原理是,首先使用 iter()
函数用来生成迭代器对象,然后不断调用 next()
函数来获取下一个元素,当没有元素可获取时,会抛出 StopIteration
异常。
此外,你也可以创建自己的迭代器对象,只需要实现 __iter__()
和 __next__()
这两个方法即可。__iter__()
方法返回迭代器对象本身,__next__()
方法返回下一个值,如果没有更多的元素,应该抛出 StopIteration
异常,在for
循环中不需要手动处理。
1.7 生成器
它本质上也就是一个迭代器,他里面的一个关键点就是yield
关键字,当Python执行到yield语句时,它会生成一个值,然后暂停函数的执行。当下一次调用生成器的next()
函数时,它会从上次暂停的地方继续执行,直到再次遇到yield
语句。
yield
和return
的区别是:yield
可以有多个,return
只能有一个,但站在功能的角度:都是返回值。
1.8 面向对象编程思想
1.8.1 面向对象介绍
面向对象编程(Object-Oriented Programming,OOP
)是一种编程范式,它使用“对象”来设计软件和结构化代码。在Python中一切皆对象,意思是在Python中所有东西都是对象,对象就是一种引用,包括像基础数据类型:字符串、变量等都是对象,他的好处就是,比如一个字符串可以通过字符串对象点出很多方法来。
在面向对象编程中,对象是基于类(Class)的实例。类是一个定义了一组属性和方法的代码模板,通过类实例化得到一个对象,具有类定义的属性和方法。在Python中面向对象编程三大特性:
- 继承(Inheritance):继承是一种允许我们定义一个类的行为来继承另一个类的行为的方式。这使得我们可以重用代码,也可以添加或覆盖父类中的行为。(在多重继承中可能会遇到钻石问题(也称为“菱形继承”),即一个类继承了两个或多个具有共同祖先的类。Python 通过方法解析顺序(MRO)来解决这个问题的,它确保每个方法只被调用一次。)
- 封装(Encapsulation):封装是一种隐藏对象的内部状态和实现细节的方式。在Python中,我们可以使用私有属性和私有方法来实现封装。
- 多态(Polymorphism):多态是一种允许我们使用一个接口来表示多种形式的实体的方式。在Python中,我们可以使用继承和方法重写来实现多态。
1.8.2 Python 中的__new__
和__init__
的区别
__new__
是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例对象,是个静态方法。__init__
是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值,通常用在初始化一个类实例的时候。是一个实例方法。
1.8.3 反射
反射:在运行时动态地获取,创建和修改对象,调用方法,甚至修改类的结构(数据属性和函数属性),而在Python中,反射指的是通过字符串来操作对象的属性。
在Python中,反射是指程序在运行时能够访问、检测和修改其自身状态或行为的一种能力。具体来说,Python的反射功能包括以下几种:
-
type()
:返回对象的类型。 -
id()
:返回对象的唯一标识符,通常是内存地址。 -
getattr()
:返回一个对象的属性值。 -
setattr()
:设置一个对象的属性值。 -
delattr()
:删除一个对象的属性。 -
isinstance()
:检查一个对象是否是一个类的实例。 -
issubclass()
:检查一个类是否是另一个类的子类。 -
dir()
:返回一个对象的所有属性和方法。 -
callable()
:检查一个对象是否可以被调用。 -
eval()
:执行一个字符串表达式,并返回结果。 -
exec()
:执行动态的Python代码。
1.8.4 鸭子类型
鸭子类型(Duck Typing)是Python中的一种编程思想。因为Python是动态强类型语言,没有严格的类型检查。继承一个类,子类中必须要有父类的方法,就是只要某个对象具有鸭子的方法,可以像鸭子那样走路和嘎嘎叫,那么它就可以被其它函数当做鸭子一样调用。在Python中,鸭子类型的含义是:我们不关心对象是什么类型,只关心对象能做什么。换句话说,一个对象的行为(它的方法和属性)比它的实际类型更重要。
在Python中可以使用abc
这个模块里面的abc
装饰器类强制性约束一个子类必须有父类的方法,或者使用抛出异常的方式来进行限制,但在Python中推崇的是鸭子类型,其实我们完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象,比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度。
1.9 Python中的GC 机制
程序运行过程中会申请大量的内存空间,对于一些内存空间如果不及时清理的话会导致内存溢出,程序崩溃,于是Python中引入了GC机制自动管理内存,避免了手动管理内存可能出现的错误,如内存泄漏。Python的垃圾回收(Garbage Collection,GC
)机制主要依赖于引用计数(Reference Counting
)来跟踪和回收垃圾对象。
引用计数就是变量值被变量名关联的次数,当Python对象的引用计数降为0时,它将被GC回收。然而,如果仅仅依赖引用计数,Python无法处理循环引用的情况。例如,对象A和对象B相互引用,这就导致引用计数也不会降为0,因此不会被GC回收,这就会导致内存泄漏。
为了解决这个问题,Python引入了标记-清除(Mark-Sweep
)和分代回收(Generational GC
)两种机制来补充引用计数。标记-清除机制能够检测并回收循环引用的垃圾对象。它会定期遍历所有的对象,标记那些在引用链上的对象,然后清除那些没有被标记的对象。
由于引用计数的回收机制每次回收内存,都需要把所有对象的引用计数都遍历一遍,这是非常消耗时间的,于是引入了分代回收来提高回收效率,分代回收采用的是用“空间换时间”的策略。分代回收就是在历经多次扫描的情况下,都没有被回收的变量,gc机制就会认为,该变量是常用变量,gc会降低对其扫描频率,分代指的是根据存活时间来为变量划分不同等级:新生代、青春代、老年代。
1.10 Python中的GIL 解释锁
GIL,全称Global Interpreter Lock
,即全局解释器锁。由于Python解释器的内存管理不是线程安全的,为了防止多个线程同时执行Python字节码,导致数据不一致或损坏。Python解释器引入了GIL,确保任何时候都只有一个线程在执行。
GIL虽然保证了解释器级别的线程安全,但是它也带来了一些问题。最主要的问题是在多核CPU上,Python的多线程程序并不能有效地利用多核资源。因为GIL的存在,即使在多核CPU上,Python的多线程程序也只能在一个核上运行,所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行。这意味着Python的多线程并不能提高CPU密集型任务的运行速度,反而可能会因为线程切换的开销而变慢。对于IO密集型任务,GIL的影响较小。因为线程在等待IO操作在(如网络请求、文件读写)时完成时会释放GIL,其他线程可以继续执行。因此,对于IO密集型任务,Python的多线程可以提高程序的运行效率。
对于CPU密集型任务,可以使用多进程(如Python的multiprocessing
模块)来绕过GIL的限制,或者使用不受GIL限制的其他编程语言扩展。
1.11 Python如何开启多线程和多进程?
1.11.1 开启多进程
实现的方式是使用一个multiprocessing
模块下的Process
类
- 方式一 : 书写任务--->
Process(target=[任务名],args=(参数,))
--->p.start()
--->p.join()
括号内可以指定多少秒之后停止等待 - 方式二 : 书写类继承
Process
--->里面书写run()
方法--->p.start()
--->p.join()
1.11.2 开启多线程
实现方式 :threading
模块下的Thread
类
- 方式一 : 书写任务--->
Thread(target=[任务名],args=(参数,))
--->p.start()
--->p.join()
- 方式二 : 书写类继承
Thread
--->里面书写run()
方法--->p.start()
--->p.join()
二、计算机操作系统
2.1 进程/线程/协程基本概念
进程:程序运行的过程,进程是操作系统分配资源的最小单位,多进程适合于CPU密集型任务;多进程也可以用于IO密集型任务因为它可以绕过GIL的限制,充分利用多核CPU的资源。
线程:程序内代码的执行过程,线程是程序执行的最小单位, 一个进程内至少有一个线程
协程:协程是一种比线程更加轻量级的存在,协程的调度完全由程序控制的,运行效率极高,协程的切换完全由程序控制,不像线程切换需要花费操作系统的开销,线程数量越多,协程的优势就越明显。协程不需要多线程的锁机制,因为只有一个线程,不存在变量冲突。
线程和协程适合用于IO密集型任务,如文件操作、网络请求等。因为在IO操作过程中,程序会有大量的等待时间,使用协程可以在等待时切换到其他任务,提高程序的运行效率和程序的吞吐量和响应性。
2.2 进程与线程的区别
- 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
- 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
- 调度和切换:线程上下文切换比进程上下文切换要快得多,进程系统资源开销大
2.3 同步异步
同步,异步,阻塞,非阻塞的概念,同步是在等待一个在其他任务完成,异步干别的事,阻塞守在旁边非阻塞过一会儿看一下
同步是阻塞模式,异步是非阻塞模式。
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返 回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
异步是指进程不需要一直等下去, 而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
在Python中,可以使用asyncio
库来编写异步代码。asyncio
是Python的一个异步编程库,它提供了异步I/O操作、事件循环、协程等功能。
以下是使用asyncio
编写异步代码的基本步骤:
- 使用
async
关键字定义异步函数(协程)。 - 使用
await
关键字调用其他异步函数,或者进行异步IO操作。 - 创建事件循环,使用
asyncio.run()
运行异步程序。
2.4 异步IO与IO多路复用
I/O多路复用(I/O Multiplexing)是一种允许单个线程或进程同时监视多个文件描述符(通常是网络套接字)的可读、可写和异常等事件的技术。当至少一个文件描述符准备好进行I/O操作时,I/O多路复用机制会通知应用程序,从而实现在单个线程或进程中处理多个并发I/O流的目的。优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销同时还能有效地处理大量的并发连接。
在操作系统中,常见的I/O多路复用机制有三种,select
, poll
, epoll
都是I/O多路复用的具体的实现:
-
select
:- 监视多个文件句柄的状态变化。
- 程序会阻塞在
select
函数上,直到被监视的文件句柄中有一个或多个发生了状态变化。 - 通知用户进程,然后由用户进程去操作IO。
-
poll
:select
函数有最大文件描述符的限制,一般1024个,而poll
函数对文件描述符的数量没有限制。
-
epoll
:- 监视的描述符数量不受限制,所支持的文件描述符上限是最大可以打开文件的数目。
- I/O效率不会随着监视文件描述符的数量增长而下降。
epoll
不同于select
和poll
轮询的方式,而是通过每个文件描述符定义的回调函数来实现的,只有就绪的fd才会执行回调函数。
在Python中,可以使用select
模块来实现I/O多路复用,也可以使用更高层次的异步编程库,如asyncio
,它内部使用了底层的I/O多路复用机制来实现高效的异步I/O操作。
2.5 处理并发网络连接时,epoll
和 select
场景适用性
下面是epoll
和 select
两种 I/O 多路复用技术在不同场景下的适用性:
- 并发高,连接活跃度不高:
- 在这种情况下,可能会有大量的连接,但每个连接在任意时刻实际进行数据交换的频率不高,例如HTTP请求。每个连接建立后,通常进行少量的数据交换,然后断开。
epoll
在这种情况下表现更好,因为它使用回调机制,只有当连接就绪(有数据可读或可写)时才会通知应用程序。这意味着epoll
可以高效地处理大量空闲的连接,而不会因为频繁的轮询操作而消耗过多的CPU资源。
- 并发性不高,同时连接很活跃:
- 在这种情况下,虽然并发连接的数量不是很多,但每个连接都非常活跃,频繁进行大量的数据交换,例如WebSocket连接或游戏服务器。
select
在这种情况下可能更适用,因为它简单且易于实现。当连接数量不是很多时,select
的性能开销相对较小,而且select
在某些系统上的兼容性更好。此外,如果每个连接都很活跃,那么select
的轮询机制可能不会成为性能瓶颈。
- 游戏开发:
- 游戏服务器通常需要处理大量的并发连接,每个连接可能会频繁地发送玩家动作和游戏状态更新。
- 在游戏开发中,
epoll
可能是更好的选择,因为它能够高效地处理大量的并发连接,并且在连接活跃度不高时减少不必要的资源消耗。 总的来说,选择epoll
还是select
取决于具体的场景和需求。epoll
在处理大量连接和高并发方面通常更有优势,尤其是在连接活跃度不高的情况下。而select
在处理少量连接且每个连接都非常活跃的情况下可能更简单、更有效。在实际应用中,开发者需要根据具体情况和性能测试来决定使用哪种技术。
2.6 计算机网络I/O模型
- 阻塞IO模型 : 进程或线程等待某个条件,条件不成立则一直等待,条件成立则进行下一步。
- 非阻塞IO模型 : 与阻塞IO模型相反,应用进程与内核交互,目的未达到时不再一味的等待,而是通过轮询的方式不停的去询问内核数据有没有准备好。
- IO复用模型 : IO复用模型是建立在内核提供的多路分离函数
select
基础之上的, 使用select
函数可以避免非阻塞IO的轮询等待问题,它会添加一个监视,监视socket是否没激活,激活了select
函数就返回,用户相乘就进行处理。 - 信号驱动IO模型 : 用的非常少,使用信号来通知进程。
- 异步IO模型 : 用户进程直接先进行系统调用,告知内核要进行IO操作,内核立即返回,用户进程立马可以去处理其他逻辑,当内核完成所有的IO之后,将会通知我们的程序,于是程序就可以对准备好的数据进行处理了。
三、MySQL
3.1 了解数据库事务,脏读,幻读,不可重复读
代码语言:javascript复制### 事务:
事务就是由一条或多条sql语句组成的一个逻辑单元,因为一系列操作中某条sql语句的执行失败导致的数据错误提供了回滚的操作,回到执行之前的状态
### ACID(原子性,一致性,隔离性,持久性)
原子性:事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性:事务前后数据的完整性必须保持一致。
隔离性:多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
持久性:一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
### 脏读,幻读和不可重复读基本概念
脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。
不可重复读:A事务在执行过程中,B事务对数据进行了修改或删除并已提交,导致A两次读取的数据不一致;重点在于update和delete(锁行即可解决)
幻读:幻读发生在当两个完全相同的查询执行时,第二次查询所返回的结果集跟第一个查询不相同。幻读解决方法就是mysql中next-key,实质是行锁 间隙锁,锁住修改那条记录以及相邻记录范围内的区域
脏读,幻读和不可重复读他们都是数据库的读现象,也可以说是读问题
### 数据库事务隔离机制等级
SQL标准定义了四种隔离级别,从低到高分别是:
1.READ UNCOMMITTED(未提交读):
在这种隔离级别下,一个事务可能读取到另一个未提交事务的数据,这种情况被称为“脏读”。它提供了最低级别的隔离,因为允许读取尚未提交的数据变更,可能会导致数据的不一致。
2.READ COMMITTED(已提交读):
在这个隔离级别中,一个事务只能读取到已经提交的数据。它解决了脏读的问题,但可能会出现“不可重复读”的问题,即在一个事务内,同一查询多次执行可能得到不同的结果。
3.REPEATABLE READ(可重复读):
在这个隔离级别下,一个事务在执行期间可以多次读取同样的数据集,并且保证数据在这一过程中不会发生改变。它解决了不可重复读的问题,但是可能会出现“幻读”的问题,即在一个事务内,新插入或者删除的数据可能会影响到事务的结果集。
4.SERIALIZABLE(可串行化):
这是最严格的隔离级别,它通过强制事务串行执行,避免了前面提到的所有并发问题(脏读、不可重复读、幻读)。它通过锁定事务涉及的所有数据来实现,这可能会影响系统的并发性能。
提交读机制解决了脏读、和不可重复读的问题,Next-Key Lock 解决了幻读的问题
6.2 基本的索引优化,B 树基本原理,MVCC快照读与当前读
代码语言:javascript复制# 索引基本原理
索引在MySQL中也叫做“键”,是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要。
索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高好几个数量级。
索引相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查。
(1)是一种快速查询表中内容的机制,类似于新华字典的目录
(2)运用在表中某个些字段上,但存储时,独立于表之外,存放于.MYI文件中
索引表把数据变成是有序的....
# 索引的实现原理,索引为什么查询速度快
散列表:时间复杂度O(1),但散列表不能支持按区间快速查找数据
平衡二叉树:时间复杂度O(logn),但不足以支持按照区间快速查找数据
B树:多叉树,深度小
B 树:在B树基础上,树中的节点并不存储数据本身,而只是作为索引把每个叶子节点串在一条链表上,链表中的数据是从小到大有序的,有序,区间查找
# B 树的原理,时间复杂度
原理:见上面索引实现原理
时间复杂度:O(logn)
# 索引优化 :
不添加索引的话查询任意一条数据都会进行全表扫描,数据量过大符合条件的结果又少,会引起性能下降,所以加索引,快速检索到某条记录(B 树)
开启慢查询日志,定位到查询效率较低的SQL,在使用explain进行分析
通过explain可以查询SQL的执行计划,可以查看到key使用的索引,查看type是不是全表扫描,查看rows扫描行数是多少,rows是核心指标,绝大多数的情况下rows小的语句执行速很快,所以优化基本就是优化rows
查看SQL语句是否规范 : 避免使用 select * , 关键字 : or, in, not in, != ,避免子查询
or使用union代替, in 使用 exists代替(not in...), id != 3 使用 id<3 or id>3代替, 连接查询代替子..
使用上面提到的关键字查询和模糊查询(like %song)会使索引失效
不要存在null值的字段,只要存在null值就不会包含在索引内
不要在索引字段上进行运算,
不要过度索引,比如区分度很高的sex字段,就两种你也建一个索引,反而会影响到更新速度
# MVCC
多版本并发控制:读取数据时通过一种类似快照的方式将数据保存下来,可以实现读写冲突不加锁,提升数据库的并发性能,维持了一个数据的多个版本--->他里面有个快照读和当前读的概念 :
1. 快照读:快照读指的是使用普通的select操作读取到的数据就是一个快照读,不需要加锁,读取的可能是最新的数据,也可能是最新的数据
2. 当前读:当前读值的是insert,delete,update,增删改操作,默认加锁,读取的是最新的数据,以及加锁的select操作,比如排它锁 : for update,共享锁 : lock in share mode,当前读其实就是悲观锁的具体实现
6.3 熟悉Mysql数据库锁概念,悲观锁,乐观锁,排他锁,共享锁;
代码语言:javascript复制# 锁概念
锁概念 : 锁,它是计算机协调多个进程或多线程并发的访问同一个资源的机制,我们称之为锁机制
# MySQL 三种锁(基于粒度)
MySQL大致可归纳为以下3种锁:
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
# 悲观锁
悲观锁 : 在修改数据之前就将数据加锁, 那么其他的事务在对该数据进行操作的时候就会处于阻塞状态,等待它修改完成
# 乐观锁
乐观锁 : 在修改数据之前不会进行一个加锁操作, 只有真正改数据的时候才会去校验数据与之前读取的数据有没有发生改变,改变了说明数据已经被别人动过了,那么此次修改失败。这是我们程序员通过逻辑层面的控制,具体实现的话可以使用版本号或者使用时间戳的方式,修改了版本号就 1,查看版本号是否与自己读的时候的版本号相同,或读时间戳,相同则把当前时间戳更新进去
# 排它锁(写锁)
排它锁(写锁) : 也叫互斥锁,他不能与其他的锁共存,一个事务将一条数据设置了排它锁之后, 其他的事务只能等待该事务完成之后才能进行操作 (添加排它锁 : 在SQL语句后面添加 for update)
# 共享锁(读锁)
共享锁(读锁) : 多个事务可以对一条数据共享一把锁,得到共享锁的事务只能对该数据进行读取的操作,不能够修改,其实就是一个并发读操作
# 死锁
死锁 : 产生死锁的原因最主要的是两个或以上的SQL语句的加锁顺序不一致造成的 :
# 如何避免死锁
避免死锁 : 出现死锁,Innodb一般会检测到,并让一个事务释放锁退回,另一个获得锁完成事务,但可以有很多方式去避免死锁的问题 :
并发的存取数据的时候,尽量的以相同的顺序去访问表,可以大大降低死锁的概率
在同一个事务中,尽可能的做到一次将所需要的资源全部锁定,减少死锁产生的概率
对于非常容易产生死锁的业务,就加大锁定的粒度,其实和上面的方法差不多,锁定所需要的资源,不让其他事务操作
6.4 分库分表
代码语言:javascript复制 ### 分库
分库:从单个数据库拆分成多个数据库的过程,将数据散落在多个数据库中。
### 分表
分表:从单张表拆分成多张表的过程,将数据散落在多张表内。
### 水平切分与垂直切分
通常按照水平切分、垂直切分两种方式进行切分,当然,有些复杂业务场景也可能选择两者结合的方式。
(1)水平切分这是一种横向按业务维度切分的方式,比如常见的按会员维度切分,根据一定的规则把不同的会员相关的数据散落在不同的库表中。由于我们的业务场景决定都是从会员视角进行数据读写,所以,我们就选择按照水平方式进行数据库切分
(2)垂直切分垂直切分可以简单理解为,把一张表的不同字段拆分到不同的表中。比如:假设有个小型电商业务,把一个订单相关的商品信息、买卖家信息、支付信息都放在一张大表里。可以考虑通过垂直切分的方式,把商品信息、买家信息、卖家信息、支付信息都单独拆分成独立的表,并通过订单号跟订单基本信息关联起来。也有一种情况,如果一张表有10个字段,其中只有3个字段需要频繁修改,那么就可以考虑把这3个字段拆分到子表。避免在修改这3个数据时,影响到其余7个字段的查询行锁定。
6.4 数据库优化
代码语言:javascript复制1. 使用索引
2. 优化 SQL 语句
3. 优化数据库对象
3.1 优化表的数据类型
3.2 对表进行拆分
3.3 使用中间表来提高查询速度
4. 硬件优化
4.1 CPU 的优化
4.2 内存的优化
4.3 磁盘 I/O 的优化
5. MySQL自身的优化
6. 应用优化
6.1 使用数据库连接池
6.2 使用查询缓存
读写分离: 就是把对数据库的读操作和写操作分离开,将读写压力分担到多台数据库实例上,通常用于读远大于写的场景。读写分离的基本原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。数据多了之后,对数据库的读、写就会很多。写库就一个,读库可以有多个,利用主从复制负责主库和多个读库的数据同步。
读写分离是一种数据库架构模式,主要用于提高数据库的读取性能,同时减轻主数据库的负载。在这种架构中,主数据库(或称写库)处理所有的写操作(INSERT、UPDATE、DELETE),而读操作(SELECT)则被路由到多个从数据库(或称读库)。以下是实现读写分离的一些常见步骤和方法:
- 主从复制(Master-Slave Replication):
- 首先需要配置主从数据库复制,这样主数据库上的所有更改都会复制到从数据库上。
- 可以使用MySQL、PostgreSQL等数据库提供的内置复制功能来实现。
- 数据库代理(Database Proxy):
- 使用数据库代理服务器(如MySQL的ProxySQL、MaxScale,或者PostgreSQL的PgBouncer)来管理客户端对数据库的连接。
- 代理服务器可以根据SQL语句的类型(读或写)将请求路由到相应的数据库实例。
- 分库代理或中间件:
- 使用专门的中间件(如MyCat、ShardingSphere等)来实现读写分离。
- 中间件通常提供了更为复杂的路由策略,可以根据不同的规则将请求分发到不同的数据库实例。
- 应用层路由:
- 在应用代码中直接实现读写分离逻辑,根据SQL操作类型决定连接到主库还是从库。
- 这种方法比较灵活,但需要开发人员维护更多的代码,并且可能会增加应用复杂性。
- DNS轮询或负载均衡:
- 通过DNS轮询或负载均衡器来实现读操作的负载均衡,将读请求分发到不同的从数据库。
- 这种方法通常需要配合数据库代理使用,以确保写操作能够正确地路由到主数据库。
- 注意事项:
- 数据一致性:由于从数据库可能会有复制延迟,因此在某些要求实时一致性的场景下,读写分离可能会带来问题。
- 故障转移:需要考虑主数据库故障时的故障转移机制,确保系统的高可用性。
- 监控和维护:定期监控主从复制的状态,确保数据一致性和系统性能。 实现读写分离可以显著提高数据库的读取性能,但同时也会增加系统的复杂性和维护成本。因此,在决定是否采用读写分离架构时,需要根据实际业务需求和系统特点进行综合考虑。
MySQL三种行锁算法 : Recode Lock(单行锁),Gap Lock(间隙锁,解决了幻读的问题),Next-Key Lock(前面的结合)
索引 : 索引在mysql中称之为"键",是存储引擎用于快速查找记录的一种数据结构,直白将就是数据的组织方式,mysql5.5之后的的默认引擎是Innodb,其使用的索引结构就是B 树索引,而B 树索引分为聚簇索引和辅助索引 聚簇索引 : 也叫主键索引,如果表中定义了key,则使用key作为聚簇索引,如果没设置则第一个不为空且唯一的列就是聚簇索引,再没有InnoDB会创建一个隐藏的字段作为聚簇索引 辅助索引 : 除了聚簇索引之外的其他索引都是辅助索引,也可以成为非聚簇索引 聚簇索引与辅助索引的不同点就在于聚簇索引的叶子节点中存放的是一条完整的数据, 而辅助索引中只存放该辅助索引字段以及对应的主键索引,当通过辅助索引查找数据的时候,会先找到副主索引字段,再拿到该条记录的主键索引,之后进行一个回表操作拿到所有的数据
ICP : 索引下推技术 : mysql5.6推出,默认开启,用于优化查询 : 在使用联合索引和最左匹配的时候,如果我们的SQL存在范围查询,比如有两个用户 : jack,22|jordy,23,需要查询以j开头,年龄为22的这个用户,在5.6之前,innodb会直接忽略age这个字段,找出两个以s开头的记录,然后通过两次回表操作去判断要找的记录,6.5之后,使用索引下推技术,Innodb不会忽略age字段,而是在索引内部就判断了age是否等于22,不等于就直接过滤掉,于是只需要进行一次回表操作就能拿到真正的数据
B s树 : 由B树升级优化而来,Innodb的默认数据结构,key-value键值对的方式存储数据,B树与B 树的不同点 : B树的每个节点中都存放键值与对应的数据,而B 数的非叶子节点只存放键值,叶子节点存放所有的数据,一个节点16k大小,有限,B 树的非叶子节点能存放更多的键值,树就更矮更胖,树叶之间的节点已经排好序了,B 的叶子节点都是排好序的,这意味着在范围查询上,B 树比B树更快,快就快在一旦找到一个树叶节点,就不需要在再从树根查起了,检索速度就更快,可以通过相邻节点快速查询。
2. 聚簇索引与非聚簇索引
代码语言:javascript复制innodb的主键就是聚簇索引,
myISAM 主键还是二级索引都是非聚簇索引。
1.非聚簇索引:
表和索引是分2部分储存的,通过索引的查找找需要的表的数据的地址
2.聚簇索引:
主键的叶子下包含了数据,其他索引指向主键:
所以InnoDB所有表一定要有主键,如果你自己没有显式定义主键,MySQL会自动选择一个可以唯一标识的数据作为主键,如果没有unique的列,那mysql会自动创建一个隐式主键
还有一件事就是关于添加一个自增主键在磁盘上会,自动开辟新的连续的页,极大的方便的查询,速度增加,可维护性高,插入更快,而且有效减少储存碎片。
3. 索引设计原则
代码语言:javascript复制# 什么情况下使用索引,什么情况下不该用索引
索引无法命中的情况:or,like以%开头
不该使用索引的情况:数据唯一性差(如性别),频繁更新,where后面含有is null,like,><等
4. 覆盖索引&回表查询
代码语言:javascript复制# 回表查询
聚簇索引与普通索引,聚簇索引的叶子节点保存了完整的信息,普通索引只存储主键值
普通索引因为无法直接定位行记录,所以通常需要扫描两遍索引树
例如:select * from t where name = 'aaa'; // name为普通索引,先查询name索引树,拿到主键值,再去主索引拿完整信息
# 覆盖索引
如果一个索引覆盖(包含了)所有需要查询的字段值,这个索引就是覆盖索引
总结:只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表
例如:select id, name from t where name = 'aaa'; // 无需回表
select id, name, age from t where name = 'aaa'; // 这里age不在name索引树中,所以需要回表
改进方法:alter table t add index na(name, age); // 将name,age添加联合索引,这时候就能避免回表查询
# 索引覆盖优化方法
最常用方法:将被查询的字段,建立到联合索引里去
其他场景:
全表count查询优化:select count(name) from user; // 将name建立索引
列差查询回表优化:使用联合索引
分页查询:select id, name, age ... order by name limit 500,100; // 将name,age升级为联合索引
5. 锁
代码语言:javascript复制# 基于属性
共享锁:加上读锁之后,其他事务之后可以继续加读锁
排它锁:加写锁之后,其他事务不能加锁(包括读锁和写锁)
# 基于粒度
表级锁:对整张表加锁,并发度低
页级锁:介于表锁和行锁,会出现死锁,并发度一般
行锁:对一行或多行加锁,并发度高
记录锁:只对一条记录加锁,精确命中,命中条件是唯一索引,避免了脏读和重复读的问题
间隙锁:属于行锁中一种,锁住某一区间,当表的相邻ID之间出现空隙会形成一个左开右闭的区间,可防止幻读
临建锁(next-key lock):也属于行锁的一种,InnoDB默认算法,相当于记录锁 间隙锁,触发条件:范围查询中命中索引
元数据锁
元数据锁主要是面向DML和DDL之间的并发控制,如果对一张表做DML增删改查操作的同时,有一个线程在做DDL操作,不加控制的话,就会出现错误和异常。元数据锁不需要我们显式的加,系统默认会加。
元数据锁的原理
当做DML操作时,会申请一个MDL读锁
当做DDL操作时,会申请一个MDL写锁
读锁之间不互斥,读写和写写之间都互斥。
# 基于锁的状态
意向共享锁:作用于表之上,当一个事务对整个表加共享锁之前,首先需要获取这个表的意向共享锁
意向排它锁:当一个事务对整个表加排他锁之前,首先需要获取这个表的意向排他锁
# 死锁的解决方案:
一种策略是,直接进入等待,直到超时。
另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。
在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。
当然Innodb是既支持表锁也支持行锁的,默认情况下使用的是行锁。
# 悲观锁与悲观锁
悲观锁:
只要执行就加锁
乐观锁:
提交前确认数据是否变化,变化的话重新开启事务
怎么避免乐观锁ABA问题
加版本号,只要修改,版本号加1
# 自旋锁与互斥锁
互斥锁:线程会从sleep(加锁)——>running(解锁),如果拿不到锁就会被CPU切换掉,过程中有上下文的切换,cpu的抢占,信号的发送等开销。
自旋锁:线程一直是running(加锁——>解锁),一直占用CPU,死循环检测锁的标志位,机制不复杂。
自旋锁与互斥锁都是为了实现保护资源共享的机制。
无论是自旋锁还是互斥锁,在任意时刻,都最多只能有一个保持者。
获取互斥锁的线程,如果锁已经被占用,则该线程将进入睡眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。
6. Innodb
代码语言:javascript复制# InnoDb
支持ACID的事务,支持事物的四种隔离级别
支持行级锁及外键约束,因此可以支持并发读写
不存储总行数
一个InnoDb引擎存储在一个文件空间,也可能为多个,受操作系统文件大小限制
主键采用聚簇索引,辅助索引的数据域存储主键的值,从辅助索引超找数据,需要先通过辅助索引找到主键值,再访问主索引,最好使用自增主键,防止插入数据时,为维持B 树结构,文件进行大的调整
12. SQL执行计划
代码语言:javascript复制explain
# 关注的内容
id:查询的序列号
selectType:select子句的类型
table:该句查询的表
type:优化sql的重要字段
const:通过sql一次命中,匹配一行数据
system:表中只有一行数据,相当于系统表
eq_ref: 唯一索引扫描,只有一条记录与之匹配
ref:非唯一索引扫描,返回匹配某个值的所有记录
range:只检索给定范围内的行,使用一个索引来选择行,一般用于between,>, <
index:只遍历索引树
ALL:全表扫描
执行效率:ALL<index<range<ref<eq_ref<const<system,最好避免ALL和index
possible-keys:执行过程中**可能**命中的索引
key:真正命中的索引
key-len:查询优化器使用了索引的字节数
ref:
rows
filtered
Extra
WHERE
和 HAVING
有什么区别?
WHERE
和 HAVING
是 SQL 查询语言中用于筛选数据的两个关键字,它们在使用场景和作用范围上有一些不同。
- WHERE:
WHERE
关键字用于在执行查询前对行进行筛选,它出现在 SQL 语句的SELECT
,UPDATE
,DELETE
等语句中。WHERE
子句筛选的是行级数据,即对表中的每一行都应用条件,并只返回满足条件的行。- 通常用于对列的值进行条件过滤,比如筛选出满足特定条件的记录。
示例:
代码语言:javascript复制SELECT * FROM orders WHERE amount > 1000;
- HAVING:
HAVING
关键字用于对分组后的数据进行筛选,通常与GROUP BY
一起使用,出现在 SQL 语句的SELECT
中。HAVING
子句筛选的是组级数据,即对聚合函数(如 SUM、COUNT、AVG 等)计算的结果进行条件过滤,并且只返回满足条件的分组。- 通常用于对聚合结果进行条件过滤,比如筛选出满足特定条件的分组。
示例:
代码语言:javascript复制SELECT department, COUNT(*) AS num_employees
FROM employees
GROUP BY department
HAVING num_employees > 10;
总结来说,WHERE
用于对行级数据进行条件过滤,而 HAVING
则用于对分组后的组级数据进行条件过滤。常见的场景是,在对数据进行分组并进行聚合计算后,使用 HAVING
来筛选出满足特定条件的分组。
什么是orm?
ORM,即对象关系映射(Object-Relational Mapping),用于在面向对象编程语言和关系型数据库之间建立映射关系
- 每个模型都是一个class类,对应一条数据表
- 一个对象对应一条记录
- 对象.属性对应一条字段
INNER JOIN
和LEFT JOIN
有什么区别?
LEFT JOIN
(左连接)和 INNER JOIN
(内连接)是 SQL 中用于合并表格数据的两种常见类型的连接操作。它们之间的主要区别在于返回结果集中包含哪些数据。
- INNER JOIN(内连接):
- 以两个表格之间共同的值(符合连接条件的行)为基础,将两个表格中的数据进行合并。
- 结果集中仅包含满足连接条件的行,即两个表格中都存在对应关系的数据才会被包含在结果中。
- 如果左表或右表中没有匹配的数据,这些数据将不会出现在结果集中。
- LEFT JOIN(左连接):
- 以左表为基础,将左表的所有行都包含在结果集中,不论右表中是否有匹配的数据。
- 如果右表中没有与左表匹配的数据,右表的那些列将会显示为 NULL。
- 左连接确保了左表中的所有数据都会出现在结果中,即使在右表中没有匹配的数据也会显示。
四、Redis
4.1 Redis 为什么快?
redis快是因为他使用的是内存,并且是单线程工作,没有额外的开进程线程的开销,不用理会加锁释放锁,死锁带来的开销,数据结构简单,处理数据不会很复杂
4.2 Redis 基本数据类型
代码语言:javascript复制- string(字符串):用于缓存各种类型的数据,计数器,session等
- hash(哈希):用于缓存各种类型的数据,如用户信息,数据去重,比string节省空间
- list(列表):可以用作消息队列或时间轴
- set(集合):用于存储不重复的元素集合,可以用来表示用户的标签、兴趣爱好等,点赞,点踩,收藏,因为集合会自动去重,确保用户不会重复同一项内容
- zset(有序集合):ZSet(Sorted Set)是 Set 的一个扩展,它在 Set 的基础上增加了排序功能。可做排序,排行榜等
4.3 redis管道
代码语言:javascript复制Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应并将结果返回给客户端,Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应
4.4 redis持久化
redis持久化有两种模式, RDB和AOF,默认是RDB模式。
RDB模式:是按照一定的时间将内存中的数据以快照的方式存储到硬盘中去,产生的是一个rdb后缀的文件,可以通过配置文件中的save参数来设置快照保存的周期 AOF模式: 是已二进制的方式将所有的命令操作以redis命令请求的格式保存到硬盘中去,是一个aof后缀的文件 (重写策略) 优缺点 : RDB文件是间隔一段时间进行持久化的,如果在此期间redis出现宕机,这期间的数据是恢复不回来的,AOF数据安全性较高,它记录的是操作的命令,宕机之后可以重新执行命令来恢复文件,如果数据量比较大的时候恢复就比较慢,RDB恢复比较快
五、Django 框架
2.1 Django 请求生命周期流程图
2.2 中间件的5种方法
中间件:轻量级的功能插件 作用范围:全局
介于请求和处理之间,可以编写中间件 干预请求和响应
Django 中间件类通常包含以下几种方法,这些方法在请求/响应的处理过程中按照特定的顺序被调用:
- process_request(self, request):
- 在每个请求处理开始时调用,请求到达,第一个被触发的方法。
- 用于处理请求对象或执行任何预处理操作。
- 如果该方法返回一个响应对象,Django 将不会调用后续的中间件或视图函数,而是直接返回该响应。
- process_view(self, request, view_func, view_args, view_kwargs):
- 在调用视图函数之前调用,请求到达经过,第二个被触发的方法。
- 可以用于修改视图函数的参数、检查权限等。
- 如果该方法返回一个响应对象,Django 将不会执行视图函数,而是直接返回该响应。
- process_exception(self, request, exception):
- 当视图抛出异常时调用,在每个请求上调用,返回一个HttpResponse对象。
- 可以用于处理异常情况,如记录错误日志、显示错误页面等。
- 如果该方法返回一个响应对象,Django 将使用该响应对象而不是原有的错误响应。
- process_template_response(self, request, response):
- 在视图函数返回一个模板响应对象时调用。
- 可以用于修改模板响应对象。
- 必须返回一个模板响应对象。
- 需要返回render对象,才能够调用到 不常用
- process_response(self, request, response):
- 在每个响应返回给客户端之前调用。
- 用于处理或修改响应对象。
- 必须返回一个响应对象。
2.3 MVT与MVC架构
MVT
- 是Django中使用的设计架构:
- M 代表模型(Model):负责业务对象和数据库的关系映射(ORM)。
- V 代表视图(View):负责业务逻辑,并在适当时候调用Model和Template。
- T 代表模板 (Template):负责如何把页面展示给用户(html)。
MVC
- model, view, controller(控制器)低耦合,高内聚
- M : 封装对数据库的访问,数据库数据的增删改查。
- V : 封装结果,返回html页面给用户。
- C : controller, 接收用户请求,处理业务逻辑,与model和view进行交互,返回结果。
2.4 DRF视图类的继承关系
熟练掌握 Django/DRF开发框架,熟悉 Django/DRF 中的路由、认证、权限, 频率,排序,过滤,分页,以及自动生成接口文档;熟悉 ORM 的 F,Q 查询,RBAC 设计模式;
代码语言:javascript复制# Q查询
filter() 等方法中逗号隔开的条件是与的关系。 如果你需要执行更复杂的查询(例如OR语句),你可以使用Q对象。
# F查询
F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值
forms组件使用步骤 :
导入forms组件
定义一个类并继承Form
在类中书写需要校验的字段, 字段的参数就是要校验的规则
在视图类中实例化得到一个空的Form对象,返回到前端(当然也可以自己后端直接传入数据)
前端用户输入数据返回到后端,或者后端直接传数据到Form对象中
调用Form对象.is_valid()方法进行数据的校验, 校验通过返回True
校验通过之后调用Form对象.cleaned_data得到校验之后的数据
校验失败调用Form对象.errors获取错误信息
常用视图类:ListModelMixin
, GenericViewSet
过滤器
from django_filters.filterset import FilterSet
分页
PageNumberPagination
中间件
MiddlewareMixin
2.5 接口幂等性
代码语言:javascript复制接口幂等性:
幂等性:多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。
csdn详解:https://blog.csdn.net/chinesehuazhou2/article/details/115610222
接口幂等性问题解决:
1. 生成全局唯一的token,token放到redis,token会在页面跳转时获取.存放到page本地存储中,支付请求提交先获取token
2. 提交后后台校验token,执行提交逻辑,提交成功同时删除token,生成新的token更新redis ,这样当第一次提交后token更新了,页面再次提交携带的token是已删除的token后台验证会失败不让提交
2.6 JWT、Cookie、Session
对 JSON Web Tokens(JWT)、Cookie、Session 等认证机制有深入的理解。
代码语言:javascript复制HTTP协议本身是无状态的。什么是无状态呢,即服务器无法判断用户身份。
### 什么是cookie
- cookie是由Web服务器保存在用户浏览器上的小文件(key-value格式),包含用户相关的信息。客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户身份。
### 什么是session
- session是依赖Cookie实现的。session是服务器端对象
### cookie与session区别
- 存储位置与安全性:cookie数据存放在客户端上,安全性较差,session数据放在服务器上,安全性相对更高;
- 存储空间:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,session无此限制
- 占用服务器资源:session一定时间内保存在服务器上,当访问增多,占用服务器性能,考虑到服务器性能方面,应当使用cookie。
### JWT 概念:
- JWT是json web token缩写。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。
### JWT 组成:
- JWT包含三个部分: Header头部,Payload负载和Signature签名。由三部分生成token,三部分之间用“.”号做分割。
### JWT认证原理
- JSON Web Token说到底也是一个token字符串,它由三部分组成,头部、载荷与签名。头部包含令牌的类型(JWT)和所使用的签名算法。载荷包含用户的信息、令牌的过期时间、发行者等。签名用于验证消息的完整性和真实性,它是通过头部和载荷以及一个秘密密钥计算得出的。
2.7 Restful
对 Restful API 设计风格
代码语言:javascript复制# RESTful 介绍
RESTful是一种网络架构风格,它是一套构建分布式系统的一系列原则和约束。REST是“Representational State Transfer”的缩写,中文翻译为“表述性状态转移”。RESTful架构风格通过使用标准的HTTP方法,如GET、POST、PUT和DELETE,来对网络资源进行操作。
# restful api规范 :
通常采用https协议进行传输,保证数据的安全性
在url中携带api接口特征标识
携带版本,多版本
尽量使用名词,数据即资源
请求路径中携带过滤条件
返回数据中携带响应状态码
......
# RESTful架构的主要特点包括
1. 客户端-服务器分离:客户端和服务器之间通过请求和响应进行交互,两者之间相互独立,降低了系统的耦合性。
2. 无状态:每次请求之间相互独立,服务器不会保存任何有关客户端的状态信息,这简化了服务器的设计。
3. 可缓存:客户端可以缓存请求的资源,提高了交互效率。
4. 统一接口:系统中的资源通过统一的接口进行访问,通常使用标准的HTTP方法。
5. 分层系统:通过将系统分为多个层次,每一层都不知道上一层是否存在,提高了系统的独立性。
6. 按需编码(可选):客户端可以发送可执行代码到服务器端,服务器端可以执行这段代码来扩展其功能。
2.8 MVC 和 MVT 软件架构
软件架构 MVC 和 MVT 软件架构设计有一定了解。
代码语言:javascript复制# MVT : 是django中使用的设计架构:
M 代表模型(Model): 负责业务对象和数据库的关系映射(ORM)。
V 代表视图(View): 负责业务逻辑,并在适当时候调用Model和Template。
T 代表模板 (Template):负责如何把页面展示给用户(html)。
# MVC : model,view,controller(控制器)低耦合,高内聚
M : 封装对数据库的访问,数据库数据的增删改查
v : 封装结果,返回html页面给用户
C : controller, 接收用户请求,处理业务逻辑,与model和view进行交互,返回结果
六、Flask 框架
熟悉 Flask 请求扩展,请求上下文流程,Flask 闪现,blueprint 的基本使用。
代码语言:javascript复制### 请求扩展:
- before_request: 这个装饰器用于注册一个函数,在每次请求前执行。它通常用于请求预处理,比如打开数据库连接或者请求认证。
- after_request: 与 before_request 相对应,after_request 装饰器用于注册一个函数,在每次请求后执行。这个函数可以用于修改响应对象或者释放资源。
- before_first_request: 项目启动第一次请求时触发执行。用于初始化代码。
- errorhandler: 用于注册一个错误处理函数,当 Flask 应用抛出指定错误时会调用这个函数来处理错误。
- teardown_request: 用于注册一个函数,无论请求是否有异常,都在每次请求后执行。它通常用于资源清理,比如关闭数据库连接。
- teardown_appcontext: 类似于 teardown_request,但是它是在应用上下文结束时执行,不管是否有请求。
### 请求上下文流程
request和session都属于请求上下文对象。
- request:封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get('user'),获取的是get请求的参数。
- session['name'] = user.id,可以记录用户信息。还可以通过session.get('name')获取用户信息
### 蓝图
- 随着 Flask 程序越来越复杂,需要对程序进行模块化的处理
- 蓝图 (Blueprint) 是 Flask 程序的模块化处理机制
- 它是一个存储视图方法的集合
- Flask 程序通过 Blueprint 来组织 URL 以及处理请求
七、Celery
7.1 Celery架构三部分组成
代码语言:javascript复制Celery架构三部分组成
消息中间件
Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等
任务执行单元
Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。
任务结果存储
Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等
7.2 基本概念
代码语言:javascript复制 Broker
broker是一个消息传输的中间件 每当程序调用celery的异步任务的时候 会向broker传递消息 而后celery的worker将会取到消息
broker的实现方案有 redis rabbitmq 数据库
Backend
backend是用来存储celery执行任务的当前状态和最终结果
worker
具体执行代码的基础单元 负责从rabbitmq或者redis中拉取任务执行 可以在不同的主机上启动worker 实现分布式执行
beat
负责定时或者循环的向redis等broker中写入任务 以便让worker从broker中获取任务执行 两者配合实现celery定时任务
7.3 基本工作原理
1、celery框架自带socket,所以自身是一个独立运行的服务,准备配置了broker与backend的worker(任务的来源),启动celery服务。
2、添加任务到broker,worker就会执行任务,将结果存储到backend中
3、想查看任务的执行结果,根据任务的id去bckend中查询
启动 Celery Worker 命令
- 定义任务并配置 Celery。
- 使用
celery -A tasks worker --loglevel=info
启动 Celery worker。 - (如果有定时任务)使用
celery -A tasks beat --loglevel=info
启动 Celery beat。 - 可选地,使用
celery -A tasks flower
启动 Flower 进行监控。
八、Docker
熟悉 Docker 镜像/容器管理、网络管理及基本命令,以及 dockfile
编写。
# docker 和虚拟机的区别?
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
虚拟机指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。
# 虚拟机与容器区别
启动时间:Docker秒级启动,虚拟机分钟级启动。
轻量级:docker镜像大小通常以M为单位,虚拟机以G为单位。容器资源占用小,要比虚拟机部署更快速。
性能:docker共享宿主机内核,系统级虚拟化,占用资源少,没有Hypervisor层开销,性能基本接近物理机; 虚拟机需要Hypervisor层支持,虚拟化一些设备,具有完整的GuestOS,虚拟化开销大,因而降低性能,没有容器性能好。
安全性:由于共享宿主机内核,只是进程级隔离,因此隔离性和稳定性不如虚拟机,docker具有一定权限访问宿主机内核,存在一定安全隐患。
使用要求:VM基于硬件的完全虚拟化,需要硬件CPU虚拟化技术支持; docker共享宿主机内核,可运行在主流的Linux发行版,不用考虑CPU是否支持虚拟化技术。
# 网络模式
Bridge:为每一个容器分配、设置 IP 等,使用 Linux 网桥和 iptables 提供容器互联,Docker 在每台主机上创建一个名叫 dockero的网桥,通过 veth pair 来连接该主机的每一个 EndPoint,默认为该模式。
Host:容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口。
None:容器有独立的 Network namespace,但并没有对其进行任何网络设置,用户需要通过运行docker network命令完成网络设置。
Container 新创建的容器不会创建自己的网卡和配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。
# Dockerfile
由一系列命令和参数构成的脚本,通过这个文件构建镜像,dockerfile就是一个文件,占得空间非常小
FROM 镜像名字:镜像标签 FROM centos:centos7 指定基于哪个镜像
MAINTAINER 作者 作者是谁
ENV key value 环境变量
RUN 命令 要执行的命令
ADD 文件路径 拷贝文件到镜像内(自动解压)
COPY 文件路径 拷贝文件到镜像内(不会自动解压)
WORKDIR 路径 工作目录(工作的目录,一旦启动起容器来,进入,在的路径)
CMD docker run时执行的命令,如果有多个,执行最后一条,如果后面有ENTRYPOINT,则CMD作为参数使用
ENTRYPOINT 一定会执行,一般用来执行脚本
九、Web安全
了解基本的网络安全知识,如 SQL 注入、XSS 攻击、CSRF 攻击等。
代码语言:javascript复制# SQL 注入
SQL注入是一种常见的网络攻击技术,它主要针对基于SQL语言的数据库系统。攻击者通过在Web表单输入、URL参数、Cookie等输入数据中插入恶意的SQL代码,从而欺骗服务器执行这些非法的SQL命令。由于许多Web应用程序直接使用用户输入来构建SQL查询,而没有进行适当的过滤或转义,这就给SQL注入攻击提供了可乘之机。
# 为了防止SQL注入攻击,开发者应该采取以下措施:
使用参数化查询(Prepared Statements):参数化查询可以确保用户输入被安全地处理,不会被误解为SQL代码的一部分。
对输入进行验证和过滤:确保所有输入都符合预期的格式,过滤掉非法或者危险的字符。
# XSS 攻击
XSS(跨站脚本攻击,Cross-Site Scripting)是一种常见的网络攻击手段,它允许攻击者在受害者的浏览器中执行恶意脚本。这些脚本可以访问浏览器中的任何数据,包括会话cookie,从而可能导致攻击者窃取敏感信息,如登录凭证、个人信息等。
# XSS攻击的预防措施包括
输入验证:对用户输入进行严格的验证,确保输入的数据符合预期的格式。
输出编码:在将用户输入显示在页面上之前,对输出进行编码,以避免浏览器将输入误解为脚本。
# CSRF跨站请求伪造:
攻击者(黑客,钓鱼网站)盗用了你的身份,以你的名义发送恶意请求,这些请求包括发送邮件、发送信息、盗用账号、购买商品、银行转账,从而使你的个人隐私泄露和财产损失。
# CSRF攻击原理及过程如下:
1. 用户打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
# Django CSRF攻击解决方案:
CSRF(跨站请求伪造)攻击是一种网络攻击,攻击者通过欺骗用户在未经授权的情况下执行不被期望的操作。Django 提供了内置的机制来防御 CSRF 攻击。以下是详细的解决方案:
1. 确保 CSRF 中间件启用:在 Django 项目的 `settings.py` 文件中,确保 CSRF 中间件启用。默认情况下,Django 已经启用CsrfViewMiddleware中间件
2. 在模板中使用 CSRF 令牌:对于所有需要保护的表单,在模板中使用 `{% csrf_token %}` 模板标签来生成 CSRF 令牌。这可以防止表单被外部站点伪造提交。
3. 在视图中验证 CSRF 令牌:Django 的视图默认会验证 CSRF 令牌。但是,如果你需要在某些自定义视图中手动验证 CSRF 令牌,可以使用 `@csrf_protect` 装饰器。
4. 在 AJAX 请求中使用 CSRF 令牌:对于 AJAX 请求,需要在请求头中包含 CSRF 令牌。可以使用 JavaScript 获取 CSRF 令牌并在请求中设置。
5. 处理跨域请求:对于跨域 AJAX 请求,可以使用 Django 提供的 `django.middleware.csrf.CsrfViewMiddleware` 中间件来处理 CSRF 令牌,或者在视图中使用 `@ensure_csrf_cookie` 装饰器确保 CSRF 令牌在跨域请求中也能正确处理。
6. 为特定视图禁用 CSRF 保护:如果你确定某个视图不需要 CSRF 保护,可以使用 `@csrf_exempt` 装饰器来禁用 CSRF 保护。但这应该慎重使用,只在绝对必要的情况下才使用。
十、Linux
熟练使用 Linux 远程开发以及常见的基本命令,对 Nginx 配置有一定了解,能够对服务器维护和部署。
十一、设计模式
单例模式:确保某一个类只有一个实例存在, 即一个类多次实例的结果指向同一个对象, 用于节省内存,平时我们使用的模块就是天然的单例模式,还有数据库的连接池。
Golang
熟悉go的基本语法以及Map底层实现原理
代码语言:javascript复制MAP底层实现原理
笼统的来说,所有语言的map底层是一个hash表(HashMap),表面上看map只有键值对结构,实际上在存储键值对的过程是基于数组和链表实现的,由于哈希树范围是一定的,只要值足够多,经过hash函数得到的结果作为地址去存放当前的键值对(key-value)(这个是hashmap的存值方式),但是却发现该地址已经有人先来了,就会出现hash冲突:开放地址法,链地址法(拉链法)
了解缓冲信道与工作池、select、协程锁、defer、panic、recover
代码语言:javascript复制缓冲信道:
缓冲信道的容量是指信道可以存储的值的数量。我们在使用 make 函数创建缓冲信道的时候会指定容量大小
工作池:
创建一个 Go 协程池,监听一个等待作业分配的输入型缓冲信道,将作业添加到该输入型缓冲信道中,作业完成后,再将结果写入一个输出型缓冲信道,从输出型缓冲信道读取结果。
协程锁:
Mutex 用于提供一种加锁机制(Locking Mechanism),可确保在某时刻只有一个协程在临界区运行,以防止出现竞态条件。
sync.Mutex 是一个结构体 在操作元素的时候 mutex.Lock() 加锁 执行完毕后 mutex.Unlock()解锁
当 Go 协程需要与其他协程通信时,可以使用信道。而当只允许一个协程访问临界区时,可以使用 Mutex
defer : 延迟调用。多个defer,依次入栈,在函数即将退出时,依次出栈调用
panic和defer结合使用:panic触发错误,defer依次出栈调用,没有recover捕获的情况下,最后才打印错误
defer,panic, recover 结合使用,panic触发错误,defer依次出栈调用,直到被recover捕获,打印捕获的信息,之后继续defer出栈
具体参考资料:
https://www.cnblogs.com/Gaimo/p/12037018.html
面试问题
数据库ACID
原子性(Atomicity):事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功,要么全部失败。
一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态。一致性状态是指:
1.系统的状态满足数据的完整性约束(主码,参照完整性,check约束等) 2.系统的状态反应数据库本应描述的现实世界的真实状态,比如转账前后两个账户的金额总和应该保持不变。
隔离性(Isolation):并发执行的事务不会相互影响,相互隔离的,其对数据库的影响和它们串行执行时一样。
持久性(Durability):事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会导致数据丢失。
跨域问题三种解决方案:
跨域问题出现的本质就是浏览器的同源策略
CORS (跨域资源共享:后端技术) :主流采用的方案,使用第三方插件
前端代理: 使用node起了一个服务,只在测试阶段使用(正向代理)
jsonp 只能解决get请求跨域,本质原理是使用了某些标签不限制跨域(img,script)
nginx做反向代理转发, 修改nginx.conf配置文件
CORS(Cross-Origin Resource Sharing,跨域资源共享)是一个 Web 标准,它允许浏览器向跨源服务器请求资源,解决了浏览器的同源策略限制。同源策略是一种安全机制,限制从一个源(域名、协议和端口)加载的脚本与另一个源的资源进行交互,以防止恶意网站窃取数据。
CORS 的工作原理:
当 Web 应用程序需要从不同的源请求资源时,浏览器会使用 CORS 来判断请求是否被允许。CORS 通过在 HTTP 请求中添加额外的头信息来告知服务器请求的源,并根据服务器的响应头信息决定是否允许请求。
简而言之,内连接仅返回两个表格中匹配的行,而左连接则返回左表中的所有行,并包含右表中匹配的行,没有匹配的部分则用 NULL 填充。
Nginx 正向代理与反向代理
Nginx 可以同时支持正向代理和反向代理,它们是两种不同的代理模式,用于在网络中转发请求和响应。
- 正向代理(Forward Proxy):
- 正向代理是代理服务器位于客户端和目标服务器之间,客户端通过正向代理发送请求,然后由正向代理服务器转发请求到目标服务器,并将目标服务器的响应返回给客户端。
- 正向代理隐藏了真实客户端的信息,目标服务器只能看到正向代理服务器的 IP 地址,并且可以通过正向代理实现访问控制、内容过滤等功能。
- 例如,公司内部的员工通过正向代理服务器访问外部互联网,代理服务器可以控制访问权限、记录访问日志等。
- 反向代理(Reverse Proxy):
- 反向代理是代理服务器位于目标服务器之前,客户端发送请求到反向代理服务器,然后由反向代理服务器转发请求到目标服务器,最终将目标服务器的响应返回给客户端。
- 反向代理隐藏了真实的目标服务器信息,客户端只能看到反向代理服务器的 IP 地址,并且可以通过反向代理实现负载均衡、SSL 终端、缓存加速等功能。
- 例如,网站部署了反向代理服务器用于负载均衡,客户端的请求会被分发到多个后端服务器上,提高了网站的性能和可靠性。
总结来说,正向代理是客户端通过代理服务器访问目标服务器,反向代理是客户端通过代理服务器访问多个目标服务器中的一个。Nginx 作为一款高性能的代理服务器软件,可以灵活配置实现正向代理和反向代理的功能。
一条SQL的执行过程
MySQL 整个查询执行过程,总的来说分为 6 个步骤 :
SQL执行步骤:请求、缓存、SQL解析、优化SQL查询、调用引擎执行,返回结果
- 连接:客户端向 MySQL 服务器发送一条查询请求,与connectors交互:连接池认证相关处理。
- 缓存:服务器首先检查查询缓存,如果命中缓存,则立刻返回存储在缓存中的结果,否则进入下一阶段
- 解析:服务器进行SQL解析(词法语法)、预处理。
- 优化:再由优化器生成对应的执行计划。
- 执行:MySQL 根据执行计划,调用存储引擎的 API来执行查询。
- 结果:将结果返回给客户端,同时缓存查询结果。
七、计算机网络
网络协议
对计算机网络 OSI 协议,HTTP 协议,UDP/TCP 协议有一定了解。
代码语言:javascript复制# osi七层模型 : 物理层,数据链路层,网络层,传输层,会话层,表示层,应用层
# TCP协议,数据控制协议,可靠协议,三次握手,四次挥手 (传输层协议)
# UDP协议,数据报协议,不可靠协议,不需要建立连接 (传输层协议)
# HTTP协议:超文本传输协议,规定了浏览器与服务器之间数据的交互格式
四大特性 : 它是基于TCP/IP 作用于应用层之上的协议
基于请求响应(客户端向服务端发请求,服务端向客户端回应)
无状态,就是不保存用户的状态信息(可以使用cookie,session,token技术来保存用户状态)
无连接(短连接),请求一次就响应一次,之后没有联系,可以使用
TCP
代码语言:javascript复制# 三次握手,四次挥手
三次握手过程:
第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认
第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j 1),同时自己也发送一个SYN包(SYN=k),即SYN ACK包,此时服务器B进入SYN_RECV状态。
第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k 1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。
四次挥手
TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送
服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
服务器关闭客户端的连接,发送一个FIN给客户端。
客户端发回ACK报文确认,并将确认序号设置为收到序号加1
# TCP协议保证数据传输可靠性的方式主要有:
校验和
序列号
确认应答
超时重传
连接管理
流量控制
拥塞控制
UDP
代码语言:javascript复制# 特点
无连接
不可靠
面向报文
不提供阻塞和流量控制
HTTP/HTTPS/Websocket
代码语言:javascript复制# 四大特性
- 基于TCP/IP,作用于应用层之上
- 基于请求响应
- 无状态
cookie
session
token
- 无连接
# 数据格式
- 请求首行
方法字段
URL字段
HTTP协议版本
- 请求头
Accept:
User-Agent:产生请求的浏览器类型;
Refer:跳转前URL
Content-Type:接收响应的数据类型
Cookie:存储于客户端扩展字段,向同一域名的服务端发送属于该域的cookie;
- 请求体
要查询或提交的数据
# HTTP1.0与1.1,HTTP2.0区别
- 1.0与1.1
长连接,HTTP1.1中默认开启长连接keep-alive
节约带宽,HTTP1.1支持只发送header信息,如果返回100才继续发body
HOST域
缓存处理,HTTP1.1则引入了更多的缓存控制策略例
错误通知的管理,在HTTP1.1中新增了24个错误状态响应码
- 1.1与2.0
多路复用,HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求
头部数据压缩,HTTP2.0使用HPACK算法对header的数据进行压缩
服务器推送,允许服务端推送资源给浏览器
# HTTP与HTTPS区别
HTTP超文本传输协议
HTTPS是HTTP的安全版,HTTP下加入SSL层
https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
http的连接很简单,是无状态的;HTTPS协议是由SSL HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
- SSL/TSL 握手过程
客户端发送"client hello"给服务端,并包含客户端所支持的TSL版本和密码组合,还有一个"client random"随机字符串
服务端发送"server hello"给客户端,包含数字证书,服务器选择的密码组合和"server random"随机字符串
客户端校验服务端发来的证书
客户端向服务端发送另一随机字符串"premaster secret(预主密钥)",这个字符串是经过服务器的公钥加密的,只有对应的私钥才能解密
服务端使用私钥解密"premaster secret"
客户端和服务端均使用client random,server random和premaster secret,并通过相同的算法生成相同的共享密钥KEY
客户端发送经过共享密钥KEY加密的"finished"信号
服务端发送经过共享密钥KEY加密的"finished"信号
握手完成
- 为什么数据传输是用对称加密?
非对称加密的加解密效率是非常低的
只有服务端保存了私钥,一对公私钥只能实现单向的加解密
# 浏览器输入网址发生了什么
输入网址—DNS域名解析—建立TCP连接—发送HTTP请求—服务器处理并返回结果—浏览器生成页面
# websocket介绍
- http的痛点
http的生命周期,一个request,一个response
http1.1的keep-live,可以发送多个request,但一个request还是对应一个response,response是被动的
- websocket是什么,为什么要使用
Websocket是一个持久化的协议
分析过程,long poll和轮循
ajax轮询:让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。
long poll:轮询,阻塞,保持连接,服务器开销大
- 怎么使用websocket
Django中使用channels模块
channels运行于ASGI协议上,是区别于WSGI协议的一种异步网关接口协议,实现了websocket
官网推荐使用redis作为channel layer,所以要安装channels_redis
gin框架使用websocket.Upgrader升级为ws