一 基础阶段

1 数据类型:

python数据类型:int/float/str/bool/list/tuple/set/dict

可变类型(可以修改元素):list, dict, set

不可变类型:int, float, bool, str, tuple

2 数据类型常用操作:字符串常用操作 

3 内置模块/函数:

random:提供随机数

time&datetime模块:时间相关的操作,时间有三种表示方式

logging: 用于记录日志

sys模块:用于提供对解释器相关的操作

os模块:文件和目录操作

hashlib:数据加密,数据校验

json模块:处理JSON字符串,序列化,反序列化

"""
# 序列化:把不可直接存储的数据格式变成可存储数据格式,这个过程就是序列化过程。例如:字典/列表/对象等 => 字符串。
# 反序列化:把可存储数据格式进行格式还原,这个过程就是反序列化过程。例如:字符串还原成字典/列表/对象等。"""
"""序列化 字典数据转换json格式字符串"""
ret = json.dumps(data,indent=None,ensure_ascii=False)
print(ret)#[{"name": "小明", "age": 16}, {"name": "王华", "age": 16}]
"""从指定文件中读取json数据并反序列化"""
data = json.load(open("data.json", "r", encoding="utf-8"))
print(data)  # [{'name': '小明', 'age': 16}, {'name': '王华', 'age': 16}]

4 深浅拷贝:

深浅拷贝的不同是针对二级以上可变数据类型。可变数据类型(字典,集合,列表)才会需要使用深拷贝。深拷贝需要copy.deepcopy()

深拷贝:就是能够把多层元素一块拷贝,后续修改第二层(第三层或者第四层等等)元素的值的时候,不会影响其他深拷贝出来的对象

浅拷贝:只能复制第一层元素,后续修改第二层(第三层或者第四层等等)元素的值的时候,也会影响其他浅拷贝出来的对象。copy.copy()

5 变量缓存机制:也叫内存驻留机制。在这个变量缓存机制中,python把一些相同值的变量在内存中指向同一块内存空间,不再重新开辟新的空间。

小数据池:不同的python文件(模块)中的代码里面的相同数据的本应该是不在同一个内存地址当中的, 而是应该全新的开辟一个新空间,但是这样也会占用了更多的内存,所以python定义了小数据池的概念,默认允许小部分数据即使在不同的文件当中,只要数据相同就可以使用同一个内存空间,以达到节省内存的目的。小数据池只针对:int、bool、None关键字 

6 python的内存管理:

引用计数为主,分代回收和清除标记为辅的垃圾回收方式,进行内存回收管理

还引用了小整型缓冲池以及常用字符串驻留区的方式进行内存分配管理

7 is和==

  • 基于is可以直接判断两个数据的值是否一样,内存地址同一个

  • 基于== 只能判断两个数据的值是否一样

不定长参数的2种写法:

  • 在形参的位置参数左边使用*表示,接收0~多个位置实参,这种情况下,*号左边的形参,在函数内部就是一个元组。在函数定义中使用*args传递非命名键值可变长参数列表

  • 在形参的位置参数左边使用**表示,接收0~多个关键字参数,这种情况下,**号左边的形参,在函数内部就是一个字典。在函数定义中使用**kwargs传递键值可变长参数列表

9 匿名函数:

  • 对于单行函数,使用 lambda 表达式可以省去定义函数的过程,让代码更加简洁;

  • 对于不需要多次复用的函数,使用 lambda 表达式可以在用完之后立即释放,提高程序执行的性能。

# 格式1:
# add = lambda x,y: x + y
# ret = add(10,20)
# print(ret)

# 格式2:
# ret = (lambda x, y: x + y)(10,20) # 声明匿名函数的同时,直接调用了
# print(ret)

10 PEP8规范:

  • 用空格缩进来区分代码层次
  • Python不建议使用分号!除此之外,也不要用分号连接两条语句
  • 冒号、大于小于不等于、感叹号、逗号、括号、引号等等请使用英文状态下的符号
  • 在Python中注释分为两种方式: 单行注释(#号表示):一般用来对一行代码或者几行代码进行注释 多行注释(三对引号表示):一般用与对较多的代码行或代码块进行注释,或者对整个文件进行描述,下图演示了单行注释和多行注释在实际编写代码中的示例。
  • 单引号和双引号都能用来表示一个字符串
  • 顶级定义之间空两行, 方法定义之间空一行。 顶级定义之间空两行, 比如函数或者类定义. 方法定义, 类定义与第一个方法之间, 都应该空一行. 函数或方法中, 某些地方要是你觉得合适, 就空一行。

二 进阶阶段

1 推导式

推导式种类

其中,列表和字典最常用
# 1. 列表推导式,结果是一个列表
[item for item in Iterable]
# 2. 字典推导式,结果是一个字典
{a:b for a,b in iterable.items()}
# 3. 集合推导式,结果是一个集合
{item for item in Iterable}
# 4. 生成器表达式,结果是一个生成器
(item for item in Iterable)

实例

data = [1, -1, 2, -3, 3]
# ret = set()
# for num in data:
#     ret.add(num**2)
# 上面的这种结构代码就可以使用推导式进行简写
ret = {num**2 for num in data}  # 集合推导式{1, 4, 9} 
# ret = [num**2 for num in data] # 列表推导式[1, 1, 4, 9, 9]

2 迭代器与生成器

迭代器:实现了迭代器协议((内置了__iter__和__next__方法)的特殊的可迭代对象),作用是可以快速遍历容器,让原来不可迭代的对象可以使用for循环语句。

生成器:是一种特殊的迭代器,内部实现了迭代器协议,调用了yield关键字的函数(这种函数就是生成器函数,函数的返回值就是生成器对象)。作用是实现协程,生成器可以返回多次结果,每次放回的结果都在一个内存中,所以可以用于遍历或处理大文件,海量数据

3 正则表达式:经常用于数据提取,爬虫数据抓取工作

re模块

三 面向对象

1 面向对象三大特性:

封装:封装就是定义类属性/实例属性只提供给类/对象内部使用。对外提供类方法/实例方法操作类属性/实例方法。对内部属性进行保护,对外提供的操作方法的进行逻辑判断让外界修改类/对象内部的属性,提高数据安全性。

继承:子类可以继承父类的属性或方法,也可以基于父类进行扩展,拥有父类没有的属性或方法,子类可以用自己的方式重写父类方法,提高代码的复用性

多态:子类对继承的父类的同一个方法,可以有多种不同的实现。但是python中的面向对象的多态是基于鸭子类型来实现的。

反射机制:程序通过字符串的形式操作对象中(查找/获取/删除/添加)的成员,以达到访问、检测和修改对象本身状态或行为的一种能力。在python中一切皆对象(类,实例,模块等都是对象),我们可以通过反射的形式操作对象相关的属性。

getattr(object,name[,default]):获取object对象的name属性的值,如果不存在,则返回默认值default,如果没有设置default,则抛出异常
setattr(object,name,value):设置object对象的name属性的值,如name属性存在则覆盖,name属性不存在则新增name属性
hasattr(object,name):判断object对象的name属性是否存在,返回布尔值
delattr(object,name):删除object对象的name属性

3 抽象类:

定义:只能被子类继承,而不能直接实例化对象的特殊类。

用途:用于定义子类的代码编写规范和编写子类中公有的具体方法。

@abc.abstractmethod:标记一个方法为抽象方法,只定义方法名和参数,而要求子类继承时必须实现具体代码,是个装饰器。
abc.ABC:定义当前类为抽象类,本质上就是一个经过元类实例化的父类
abc.ABCMate:定义当前类为抽象元类,abc.ABC的父类的元类。Python3.4以后建议使用上面的abc.ABC。
4 设计模式
  • 外观模式:隐藏内部类的复杂实现代码的同时,为调用者提供了一个外观接口,以便调用者可以非常轻松的访问程序内部。应用:运维监控系统,在服务器宕机时发出警报通知管理员
  • 策略模式:属于一种对象的行为型模式,指对象有某个行为,但是在不同业务或不同场景中,该行为有不同的实现算法。应用:商品优惠策略
  • 装饰器模式:装饰器模式就是在现有的函数、类外层,套上一段逻辑代码,对其功能进行扩展和延伸,而且不会影响现有函数、类的本身结构。
  • 单例模式:保证一个类,无论调用多少次产生的实例对象,都是指向同一个内存地址。应用:日志记录器,在内存中只会创建一次,在程序中多次使用同一个对象作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象
  • 工厂方法模式:工厂父类负责创建对象的公共接口,工厂子类负责实例化具体对象。
  • 抽象工厂模式:和工厂方法模式基本一致,唯一的区别是,抽象工厂模式可以实现多种对象的实例化,而工厂方法模式只能实现一种对象的实例化。
5 装饰器:
装饰器的2种写法:函数装饰器与类装饰器。而被装饰的3种对象:函数、类、类方法三种。都大同小异。
手写一个装饰器
def decorator(func):
    def wrapper(a, b):
        if type(a) is not int or type(b) is not int:
            print("a或者b不是整数!")
            return 0
        print("a,b是整数!")
        ret = func(a, b)
        return ret
    return wrapper

@decorator  #fn= decorator(fn)
def fn(a, b):
    return a + b
ret = fn(10, 2)
print(f"ret={ret}")
'''
a,b是整数!
ret=12
'''
'''基于装饰器'''
def singleton(cls):
    # 创建一个字典用来保存类的实例对象
    _instance = {}
 
    def _singleton(*args, **kwargs):
        # 先判断这个类有没有对象
        if cls not in _instance:
            _instance[cls] = cls(*args, **kwargs)  # 创建一个对象,并保存到字典当中
        # 将实例对象返回
        return _instance[cls]
 
    return _singleton
基于装饰器实现单例

6 垃圾回收机制:

Python解释器自带一种机制,采用了引用计数法为主标记-清除和分代回收为辅的策略实现了垃圾回收机制。其中,引用计数法用于跟踪和回收垃圾,在引用计数法的基础上,通过标记-清除机制解决容器对象可能产生的循环引用的问题,最后通过分代回收机制以空间换取时间的方式提高垃圾回收的效率。

  • 引用计数:用来记录对象被引用的次数,每当对象被创建或被引用时将该对象的引用次数加一,当对象的引用被销毁时该对象的引用次数减一,当对象的引用次数减到0时,那么所占用的空间也就可以被释放了,容易出现容器数据类型循环引用的情况。【你中有我,我中有你,这就是循环引用了,也就是无限循环嵌套了

  • 标记清除:为了解决循环引用的问题而设计的,只针对容器类型。分为两个阶段:第一个阶段是标记阶段:遍历程序内部的可能会造成循环引用容器对象,如果还有其他对象引用该容器对象,则把该容器对象标记为可达(reachable)。在Python解释器内部实际上遍历所有的栈区中GC Roots对象(也就是上面提到的所有保存在栈区中的变量名等内容),并把所有GC Roots对象直接或间接关联起来的对象标记为可达状态,没有关联的则不会标记。第二阶段是清除阶段:把没有标记的对象非活动对象进行回收,从而解决容器类型数据带来的循环引用问题。基于标记-清除这种回收机制,每次gc回收内存时,都需要把所有容器对象的引用计数都遍历一遍,这是非常消耗时间的。

  • 分代回收:为了提高回收效率。根据存活时间来把变量划分不同等级(也是不同的代),在Python解释器内部通过NUM_GENERATIONS变量来设置为3代,分为 0(新生代),1(青春代),2(老年代) 三代,所有的新建对象都是 0 代对象,当某一代对象经历过垃圾回收,依然存活,就被归入下一代对象(按新生代->青春代->老年代顺序),会自动执行垃圾回收的频率。通俗来说,每新增701个引用计数为0的变量,就会对内存中所有的新生代数据进行gc遍历,每11次遍历新生代的数据以后,就会对青春代的数据进行gc遍历,每11次遍布青春代的数据以后,就会对老年代的数据进行gc遍历。

7 GIL锁

出现?
单核计算机时代,python实现垃圾回收机制导致的,标记-清除、引用计数、分代回收。

好处?
保证在一个进程下同一时刻,只有一个线程在执行,限制了单核并发能力,保证数据不会因为线程的并发切换导致出现计算偏差。

坏处?
在多核时代,Cpython限制了python在高并发场景对于多线程的并发能力的使用,针对计算密集型的任务有严重影响,但是对于IO密集型的任务并没有任何影响。

8  Lock与GIL的区别:

  1. Lock和GIL,Lock是我们程序中针对单个进程进行添加的。GIL锁是Cpython解释器中默认添加的。Lock针对的进程本身,GIL是针对所有进程下的所有线程

  2. Lock与GIL都是一种互斥锁。GIL是属于CPython解释器管理的,所以不会出现死锁现象。但是Lock是开发人员自己添加的,所以Lock的加锁与解锁都是开发人员自己处理的,因此使用不当则会出现死锁现象。

互斥锁(Lock):对共享数据进行锁定,保证同一时刻只能有一个线程去操作

死锁:两个或两个以上进程或线程执行过程,因争夺资源而造成的一种互相等待的现象若无外力作用,它们都将无法推进下去。

递归锁(RLock):实现同一时间允许多个进程上多把锁只允许同一时间只有一个进程或线程修改数据。一般用来解决多把锁产生的死锁问题

四 网络编程

1 TCP和UDP

三次握手:
    首先,客户端会发送一段报文(SYN=1, Seq=x)到服务端,
    然后,服务端接收到报文进行解析,接着返回一段报文(SYN=1,ACK=1,  ack=x+1,seq=y),
    接着,客户端接收到服务端响应的报文,再给服务端发送一次报文(ACK=1,ack=y+1,seq=x+1)

四次挥手:
    首先,客户端会发送一段报文(FIN=1,seq=x)到服务端,
    然后,服务端接收到报文进行解析,接着返回一段报文(ACK=1,Seq=z,ack=x+1),
    接着,服务端会响应一段报文(FIN=1,ACK=1,seq=y,ack=x+1),
    最后,客户端接收到报文,再次发送一段报文(ACK=1,seq=x+1,ack=y+1)给服务端

三次握手的原因:三次握手,避免重复连接,同时也避免了握手过程客户端单方断开的情况。

2 TCP和UDP的区别?为何基于tcp协议的通信比基于udp协议的通信更可靠?

区别就是tcp数据传输前需要三次握手建立链接传输结束后四次挥手关闭链接,数据失效还会触发重传
但是udp不用建立连接而是采用广播的方式进行数据传输,数据失效也不会重传 
因为tcp采用三次握手四次挥手需要双方都在线所以链接安全可用,数据失效还会触发重传机制不用担心数据缺失,所以基于tcp的通信更可靠 

3 socket位于OSI七层模型的第几层

Socket是存在于应用层与传输层之间的接口,是方便开发者用于实现复杂的TCP/UDP协议所封装的一套函数

4 粘包问题:A端多次发送的数据,到达B端被接收时会存在一次性接受A端多次发送数据的情况。

解决:发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。

多任务的实现有以下3种方式:

  • 进程:是操作系统资源分配和独立运行的最小单位。实现多进程模块:mutiprocessing

  • 线程:是进程内的一个任务执行独立单元,是任务调度和系统执行的最小单位。实现多线程模块:Threading

  • 协程:是用户态的轻量级线程,协程的调度完全由用户控制,主要为了一个线程内多个任务交替执行。实现多协程模块:asynio

6 同步异步,阻塞非阻塞

7 并发,并行,串行

8 进程间的通信:管道,消息队列,共享内存

效率最高的是共享内存因为进程可以直接读写内存,而不需要任何数据的拷贝,对于像管道和消息队里等通信方式,则需要再内核和用户空间进行四次的数据拷贝。

五 数据库

1 查询数据操作

2 mysql引擎InnoDB和MyISAM区别:

InnoDB存储引擎,是MySQL当前版本(5.5以后)的默认存储引擎,具有支持事务处理(transaction),外键约束(foreign key),行锁设计,崩溃恢复历史回滚等。MyISAM存储引擎,是MySQL早期版本(5.5以前)的默认存储引擎,拥有较高的插入、查询速度,表锁设计,支持全文索引的特点,但不支持事务处理和外键约束,也不支持崩溃恢复。

MyISAM和InnoDB都是MySQL中常用的存储引擎,在mysql5.5版本之前,默认存储引擎是MyISAM,在5.5以上版本默认是InnoDB。
InnoDB支持多种MyISAM所没有的特性,如事务,外键约束,行级锁,历史回滚,崩溃恢复。
InnoDB表必须有唯一索引(如主键),MyISAM没有(InnoDB表如果没有声明主键,则MySQL内部默认创建一个隐藏主键)
InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁。
InnoDB的行锁是实现在索引上的,而不是锁在物理行记录上。潜台词是,如果访问没有命中索引,也无法使用行锁,将要退化为表锁。
InnoDB的数据保存在B+tree的叶子节点上,而MyISAM则是数据的内存地址保存在B+tree的叶子节点上

InnoDB不支持全文索引(FullText),而MyISAM支持全文索引。(mysql8.0以前的版本来说)

3 存储过程:一个存储过程是一个可编程的函数,它在数据库中创建并保存,一般由 SQL 语句和一些特殊的控制结构组成。目的是以后需要数据库提供已定义好的存储过程的功能相同的服务时,只需调用“CALL 存储过程名字”即可自动完成。这样即可以大大提高数据库的处理速度,同时也可以提高数据库编程的灵活性。适用于一个完整操作需要多条 SQL 语句处理多个表完成。

4 触发器:在预设条件满足以后,自动执行的SQL语句的数据库特性。触发器是与表有关的数据库对象,指在 insert/update/delete 之前或之后,触发并执行触发器中预定义的SQL语句集合。触发器的这种特性可以协助应用程序在数据库端确保数据的完整性 ,常用于日志记录, 数据校验等操作。

5 事务:是以功能业务作为逻辑单位,把一条或多条SQL语句组成一个不可分割的操作序列来执行的数据库机制。事务适用于多用户同时操作的数据库系统的场景,如银行、保险公司及证券交易系统等等要求数据一致性或完整性比较高的场景。

"""基本使用"""
begin;
update users set money=money-200 where name='小明';
SAVEPOINT s1; -- POINT就是事务的节点,savepoint可以给当前位置打个标记,将来如果不希望完全回滚事务,则可以选择回滚到某一个事务节点。p1是自定义的事务节点名称
update users set money=money+100 where name='小红';
SAVEPOINT p2;
update users set money=money+100 where name='小白';
SAVEPOINT p3;

ROLLBACK TO s1;
select * from users; -- 注意,这里仅仅是事务内部的回滚,并没有退出事务操作的,此时还在事务内部。
rollback; -- 或执行commit; 此时才是真正的退出事务。

6 事务的四大特性:

  1. 原子性(Atomicity):事务内的所有DML语句,作为一个不可分割的整体,要么都成功,要么都失败,由redo log(重做日志)来实现

  2. 一致性(Consistency):事务执行前后数据完整性一致的,由undo log(回滚日志)来实现。

  3. 隔离性(Isolation):事务执行过程中对其他事务可以设置不同的隔离级别,隔离性由锁来实现不同的隔离级别(四种隔离级别)

  4. 持久性(Durability):事务一旦提交,其结果就是永久性更改的,由redo log(重做日志)来实现

7 四种隔离级别(mysql 的默认隔离级别可重复读)以下隔离级别由低到高

RU读取未提交内容:一个事务可以读取另一个未提交事务的数据。导致也是脏读

RC读取提交内容:一个事务要等另一个事务提交后才能读取数据。解决脏读,但会导致一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读

RR可重复读:就是在开始读取数据(事务开启)时,不再允许修改操作。可能还会有幻读问题(同样的条件 , 第 1 次和第 2 次读出来的记录数不一样)。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。

serializable(可串行化):是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

9 索引:一种特殊的查询表。类似现实生活中书的目录。

作用:加快数据库的查询速度

优点:

  1. 加快数据查询速度,避免全表遍历查询
  2. 利用索引特性,可以保证数据的唯一性和完整性,限制索引列上的数据类型
  3. 利用外键索引可以加快表与表之间的连接查询速度。

缺点:

  1. 数据表在执行创建、删除、修改操作时,因为数据库需要时刻维护索引与数据位置的关系,所以会耗费额外的资源来完成表的索引检索操作
  2. 索引是一种以空间换时间策略,用硬盘空间保存索引数据位置的映射关系。会额外占用硬盘资源,也增加了数据库的尺寸大小。

哪些数据适合创建索引:

  1. 经常被查询,或作为过滤条件的字段列使用建立索引。
  2. 唯一不为空的字段适合建立索引。

 

索引一般用在排序(order by),查询条件(where),字段投影(select)后边

10 索引类型:

  1. 普通索引:是最基本的索引,它没有任何限制。CREATE INDEX index_name ON table(column(length))
  2. 唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
    CREATE TABLE `table` (
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    ..
    UNIQUE indexName (title(length))
    );
  3. 主键索引:是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。
  4. 组合索引:指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。 
    ALTER TABLE `table` ADD INDEX name_city_age (name,city,age);

11 分析索引效率

方法:在一般的SQL语句前加上explain;

分析结果的含义: 
  1)table:表名; 
  2)type:连接的类型,(ALL/Range/Ref)。其中ref是最理想的; 
  3)possible_keys:查询可以利用的索引名; 
  4)key:实际使用的索引; 
  5)key_len:索引中被使用部分的长度(字节); 
  6)ref:显示列名字或者"const"(不明白什么意思); 
  7)rows:显示MySQL认为在找到正确结果之前必须扫描的行数; 
  8)extra:MySQL的建议;

12 mysql的分库分表?分库分表的方式

MySQL中是一个中小型数据库,所以数据在存储的量级到达千万(KW)级别时,查询性能就会下降,或者查询数据就会慢下来,因此这时候为了保证MySQL数据的查询速度,或者查询性能,这次就需要引入分表、分库。

分库分表方案是对关系型数据库数据存储和访问机制的一种补充。

分库:数据库里如果数据表太多,海量数据密集存储在一个库上会导致数据库系统整体下降,把库中的部分表划分到其他多个库上。
分库划分的依据:按表之间的模块关联,连表密切关系来划分到一个库上。
分表:当单表的数据量太大(MySQL中单表数据在500W以上),对表进行数据按一定的条件来进行切割。
分表划分的依据:
    1. 水平拆分(横向划分):按时间(论坛、贴吧、时效性数据)、按热度(文章、新闻、小说、按点击率、阅读量)、按哈希值(求余划分)。
    2. 垂直拆分(纵向划分):按字段查询频率划分(文章主表、文章附加表。用户主表,用户附加表),

13 mysql中修改一条数据的原理

先经过词法分析、语法解析确保sql的正确性,然后将需要变更的数据先通过磁盘随机IO查询出来,放入到buffer pool 中,buffer pool将这些数据在以顺序IO的方式追加至undo.log中(用于事务回滚)。然后将buffer pool中的数据按照sql进行变更,变更后将变更日志以顺序IO的方式追加至redo.log中(用于异常恢复)。然后通过磁盘多次寻址的随机IO方式变更对应的数据,之后将磁盘数据的变动写入至mysql的bin.log中进行日志快照的备份。

这里有一个数据一致性的问题。比如:如果buffer pool中数据已经变更,但此时MySQL宕机,那么磁盘数据就会和缓冲数据不一致。redo.log用于解决这种灾备或者异常情况的恢复,通过redo.log中的日志快照可以将数据变动同步至恢复正常的mysql服务器对应的物理磁盘上。

如果在buffer pool写入redo.log前,mysql宕机,则无法将数据同步至mysql,需要进行事务回滚,通过undo.log的快照回滚缓冲池的数据。

需要特别注意:undo.log和redo.log是innoDB的设计,bin.log则是mysql自带的日志快照。所以在进行主从复制、集群或mq同步数据,可以采用bin.log进行数据同步。

14 python连接并操作mysql,通常是以下2种方式:

  1. 数据库驱动模块:pymysql(用于 Python3) 、mysqldb(MySQLdb 用于 Python2 的环境模块, Python3中不支持)。

  2. 数据库ORM模块:SQLAlchemy。

不管哪一种方式,mysql都只能识别SQL语句,所以上面的几个模块的作用是充当客户端发送sql语句通过socket通信间接并操作mysql。当然,其中第二种操作方式,是基于第一种方式而进行高度封装实现的。

15 数据库优化方案:

  • 优化索引SQL语句分析慢查询

  • 设计表的时候严格根据数据库的设计范式来设计数据库

  • 使用缓存,把经常访问到的数据,而且不需要经常变化的数据放在缓存中,能节约磁盘IO

  • 优化硬件:采用SSD,使用磁盘队列技术(raid0,1,5)

  • 垂直分表:把一些不经常读的数据放在一张表里,节约磁盘IO

  • 主从分离读写:采用主从复制吧数据库的读操作和写操作分离

  • 分库分表分机器(数据量特别大)

  • 选择合适的表引擎,参数上优化

16 sql优化

1有索引但未被用到的情况(不建议)
(1) Like的参数以通配符开头时
尽量避免Like的参数以通配符开头,否则数据库引擎会放弃使用索引而进行全表扫描。
以通配符开头的sql语句,例如:select * from t_credit_detail where Flistid like '%0'\G
这是全表扫描,没有使用到索引,不建议使用。
(2) where条件不符合最左前缀原则时
(3) 使用!= 或 <> 操作符时
尽量避免使用!= 或 <>操作符,否则数据库引擎会放弃使用索引而进行全表扫描。使用>或<会比较高效。
(4) 索引列参与计算
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。
select * from t_credit_detail where Flistid +1 > '2000000608201108010831508722'\G\
(5) 对字段进行null值判断
应尽量避免在where子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: 低效:select * from t_credit_detail where Flistid is null ;
(6) 使用or来连接条件
应尽量避免在where子句中使用or来连接条件,如果条件有一个没有索引,那么会进行全表扫描,如: 低效:select * from t_credit_detail where Flistid = '2000000608201108010831508721' or Flistid = '10000200001';
2、避免select
在解析的过程中,会将'' 依次转换成所有的列名,这个工作是通过查询数据字典完成的,这意味着将耗费更多的时间。
3、order by 语句优化
任何在Order by语句的非索引项或者有计算表达式都将降低查询速度
4、GROUP BY语句优化
提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉
5、用 exists 代替 in
很多时候用 exists 代替 in 是一个好的选择: select num from a where num in(select num from b) 用下面的语句替换: select num from a where exists(select 1 from b where num=a.num)
6、使用 varchar/nvarchar 代替 char/nchar
尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
7、能用DISTINCT的就不用GROUP BY
SELECT OrderID FROM Details WHERE UnitPrice > 10 GROUP BY OrderID
8、能用UNION ALL就不要用UNION
UNION ALL不执行SELECT DISTINCT函数,这样就会减少很多不必要的资源。
9、在Join表的时候使用相同类型的例,并将其索引
如果应用程序有很多JOIN 查询,你应该确认两个表中Join的字段是被建过索引的。这样,MySQL内部会启动为你优化Join的SQL语句的机制。
而且,这些被用来Join的字段,应该是相同的类型的。例如:如果你要把 DECIMAL 字段和一个 INT 字段Join在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。(两个表的字符集有可能不一样)

17 乐观锁:指的是在操作数据的时候非常乐观,乐观地认为别人不会同时修改数据,因此乐观锁默认是不会上锁的,只有在执行更新的时候才会去判断在此期间别人是否修改了数据,如果别人修改了数据则放弃操作,否则执行操作。

悲观锁:指的是在操作数据的时候比较悲观,悲观地认为别人一定会同时修改数据,因此悲观锁在操作数据时是直接把数据上锁,直到操作完成之后才会释放锁,在上锁期间其他人不能操作数据。

18 关系型非关系数据库区别

1.关系型数据库:
优点:
1、易于维护:都是使用表结构,格式一致;
2、使用方便:SQL语言通用,可用于复杂查询;
3、复杂操作:支持SQL,可用于一个表以及多个表之间非常复杂的查询。
缺点:
1、读写性能比较差,尤其是海量数据的高效率读写;
2、固定的表结构,灵活度稍欠;
3、高并发读写需求,传统关系型数据库来说,硬盘I/O是一个很大的瓶颈。

2.非关系型数据库
优点:
1、格式灵活:存储数据的格式可以是key,value形式、文档形式、图片形式等等,文档形式、图片形式等等,使用灵活,应用场景广泛.
2、速度快:可以使用硬盘或者内存作为载体,而关系型数据库只能使用硬盘;
3、成本低:数据库部署简单,基本都是开源软件。

缺点:
1、不提供sql支持,学习和使用成本较高;
2、无事务处理;

 19 已知MySQL数据库中有一些语句执行速度非常慢,请问如何定位与优化?

慢查询日志:记录所有执行时间超过 long_query_time 秒的查询或者不适用索引的查询,通过使用 --slow_query_log[={0|1}]选项来启用慢查询日志,所有执行时间超过 long_query_time 的语句都会被记录。
1、打开慢查询日志:set global slow_query_log=on;
2、设置慢查询语句时间:set global long_query_time=1;
3、查看慢查询日志位置:show global variables like 'slow_query_log_file'

第一种:索引优化
根据慢查询日志定位到慢查询sql。检查所查字段是否都是必须的,是否查询了过多字段,查出了多余字段。
使用explain等工具分析sql执行计划,检查是否走了索引。
如果没有则优化SQL使其使用最优索引。

第二种:升级配置
检查数据库实例所在机器的性能配置,是否太低,是否可以适当增加资源。

第三种:架构优化
检查表中数据是否过多,是否应该进行分库分表。
使用主从架构+读写分离增加并发。

六 ORM框架

1 ORM(对象关系映射器):是一种基于面向对象操作提供的数据库的框架。在各种语言中都有对应的ORM实现,是对底层数据库原生SQL语句操作的高度且抽象的封装。python常用的ORM有:django提供的ORM,SQLAlchemy。相关操作

O是Object,也就类对象的意思。

R是Relational,译作联系或关系,也就是关系型数据库中数据表的意思。

M是Mapper,是映射的意思,表示类对象和数据表的映射关系。

优点:
1. 数据模型类都在一个地方定义,更容易更新和维护,也利于重用代码。
2. ORM有现成的工具,很多功能可以自动完成,例如数据消除,预处理,还有事务等操作的封装。
3. 基于ORM的业务代码量少,更简单直观。
4. 天然适合适用于MVC/MVT架构中,更利于第三框架的代码整合
5. 基于 ORM 的业务代码比较简单,代码量少,语义性好,容易理解。
6. 新手只需要通过操作模型对象即可同步修改数据表中的数据,而不需要编写底层原生SQL语句
7. ORM高度抽象了底层数据库连接,对于使用者而言,不必关注数据库之间带来的差异,将来切换数据库,只需要更换底层的数据库连接驱动即可。

缺点:
1. ORM库不是轻量级工具,有一定的学习成本,需要花费时间与精力来学习掌握它的使用。
2. 对于复杂的业务查询(例如远程的关联查询),ORM表达会比原生的SQL语句要更加困难,也更加复杂。
3. ORM抽象掉了数据库底层,开发者无法理解底层的数据库操作,提供了维护成本。
4. ORM最终还是基于底层数据库驱动基于SQL语句来操作数据库,所以要比直接使用原生SQL语句的性能要差。

感受:
开发中因为对于ORM的使用是需要有一定的技术要求的,而且ORM使用操作相对高度统一的,所以在团队开发项目过程中,可以很方便的调用他人编写的模型代码,工作交接时交接成本相对低廉。
把数据表与类对象进行一对一的映射,让我们可以通过类对象来操作对应的数据表,减少了将来切换数据库带来的迁移成本。
 

2 restful风格

  1. 资源名词作为url地址,客户端访问的url地址就代表了服务端的某种资源(文件、数据表)。
  2. HTTP动词表达客户端对当前资源的操作,GET表示获取,POST表示添加,PUT/PATCH表示更新,DELETE表示删除

七 Django

1 设计模式

MVC:Model:数据库操作代码  crotl:视图代码逻辑,view:模版

MVT:model:数据库操作代码 /view:代码逻辑/Template:展示界面效果。视图调用

MVM:M:data数据。 V:template M:虚拟DOM对象,全局对象 (前端)

2 Django的中间件:是 Django 请求/响应处理的钩子框架用于全局改变 Django 的输入或输出。

内置中间件

Csrfmiddleware:给开发者用于防止网站用户遭到csrf的攻击(跨站请求伪造:利用用户在不知情的情况下实现伪造表单提交给服务端中进行攻击的手段)

GzipMiddleware:Gzip压缩所有浏览器的响应,节省带宽和传输时间

跨域的中间件:corsheaders

自定义中间件:函数中间件,类中间件

Django的请求生命周期:

  • 1 用户在浏览器中输入url,发送一个GET方法的request请求

  • 2 Django中封装了WSGI服务器,监听端口接受这个request请求,在进行解析封装,传送到中间件中,对请求进行校验或处理

  • 3 再传输到路由系统中进行路由分发,匹配相对应的视图函数进行业务逻辑处理。

  • 4 调用modles模型层中表对象,通过orm拿到数据库(DB)的数据

  • 5 同时拿到templates中相应的模板进行渲染,然后将这个封装了模板response响应传输到中间件中,依次进行处理,最后通过WSGI再进行封装处理,响应给浏览器展示给用户。

八 DRF

1 drf:

核心思想: 大量缩减编写api接口的代码

DRf是一个建立在Django基础之上的Web 应用开发框架,可以快速的开发REST API接口应用。在REST framework中,提供了序列化器Serialzier的定义,可以帮助我们简化序列化与反序列化的过程,不仅如此,还提供丰富的类视图、扩展类、视图集来简化视图的编写工作。REST framework还提供了认证、权限、限流、过滤、分页、接口文档等功能支持。REST framework还提供了一个调试API接口的Web可视化界面来方便查看测试接口。

2 RESTful 软件架构风格:https://www.lmlphp.com/user/58000/article/item/2112937/

  • 1 通过URL表示资源。

资源分为主资源与子资源,若要表示主资源的实例:如果实例的ID=1,则这样表示: /goods/1
子资源:
一个实例的子资源可能是一个集合也可能是一个单一的子资源
子资源为图片集合:/goods/1/pictures
子资源为商品折扣的单子子资源:/goods/1/discount
  • 2 使用合适的动词,选择请求接口的方式:get delete post put

3 幂等接口:指代客户端发起多次同样请求时,对服务端里面的资源产生一样结果。

如果做幂等性校验呢?所以幂等性校验就是为了避免对于同一个接口反复提交。

  1. 增加唯一判断,给提交的数据字段,设置唯一索引

  2. 给提交的数据增加一个随机数(uuid),如果两次提交的数据随机数是一样的,则表示重复提交。

  3. 限流,或强制刷新

4 如何阻止表单重复提交

  1.  在数据库里添加唯一约束,防止出现重复数据。这是最有效的防止重复提交数据的方法,但是这种操作不能使用在所有场景中。
  2. 服务端在提供表单之前,在session/redis中生成一个随机数实现的token,把token在表单中作为隐藏域,当表单首次提交时,表单携带的token会与session/redis中的token是一致的,走正常处理流程并删除掉session/redis中的token,当表单出现重复提交时,因此表单对应的token无法与session/redis中的token一致,则拦截

5 如何让客户端的jwt失效?实际开发中怎么禁用客户端的jwt?

在用户登录以后使用redis备份jwt并设置过期时间,认证时到redis中判断jwt是否存在。如果不存在,则jwt失效。
禁用客户端的jwt,则直接允许管理员删除掉redis中要禁用的客户端对应的wt即可。

九 Linux

ln -s 源文件 目标文件 # 创建软链接(Symbolic Link)
ln 源文件 目标文件 # 创建硬链接(Hard Link)

# 当删除源文件时,硬连接不受影响,但是软链接文件会失效。如果源文件的路径下重新出现一个同名的源文件,则软链接重新生效指向新的同名源文件,而硬链接不会再次关联。
# 软链接在Linux系统中,就是一个符号通道,方便访问和操作源文件的,只会关心文件的路径而已。
# 硬链接在Linux系统中,就是一个特殊文件,与源文件的系统地址进行关联的,当源文件被删除,即便再次新建了一个同一个路径下的同名文件,但是系统地址已经变了,就无法再次关联了。

2 mysql主从复制

1. 主数据库(master)写入数据之后, 会有data changes(数据变化)记录
2. 主数据库(master)有变化记录之后,将增删改的一些sql语句记录到本地的Binary log(二进制日志)中
3. 从库(slave)会单独运行并一直开启着一个IO子线程(I/O thread)
4. 从库(slave)通过这个IO子线程去读取主数据库(master)的Binary log(二进制日志)的内容,当然这需要从库要有主库的账号密码
5. 从库(slave)会将读取到的数据写入到自己的Relay log(中继日志)中,这一步也是IO子线程的工作。
6. 从库(slave)还会单独运行并一直开启着另一个SQL子线程(SQL thread),该子线程将中继日志中的操作转化为SQL语句
7. 从库(slave)通过SQL子线程把转化的SQL语句写入到自己的数据库, 两边的数据就一致了 

3 nginx

负载均衡:在访问量较多的时候,可以通过负载均衡,将多个请求分摊到多台服务器上

正向代理:隐藏了真实的请求客户端,服务器不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替请求

反向代理:隐藏了真实的服务器,客户不知道真正提供服务的人是谁,客户端请求的服务都被代理服务器处理。

4 分布式系统:redis哨兵模式

redis哨兵模式的流程:

1.主观下线:

当主服务器发生故障时,此时一个sentinel发现了故障,系统并不会马上进行failover过程(这个现象称为主观下线),它会向网络中的其他Sentinel进行确认。

2.客观下线:

接着其他Sentinel也陆续发现故障,这个时候其中一个Sentinel就会发起投票。一定数量的哨兵(在配置文件中指定)确认Master被标记为主观下线,此时将Master标记为客观下线。

3.sentinel的leader选举:

要想完成故障切换(将故障master剔除,并将一个slave提升为master)就必须先选举一个leader。最先发现故障的sentinel向其他哨兵发起请求成为leader,其他哨兵在没有同意别的哨兵的leader请求时,就会把票投给该sentinel。当半数以上的sentinel投票通过后就认定该sentinel为leader。接下来的故障切换有该leader完成。

4.master选举:

leader选好后将故障master剔除,从slave中挑选一个成为master。遵照的原则如下:

slave的优先级
slave从master那同步的数据量,那个slave多就优先。
5.新Master再通过发布订阅模式通知所有sentinel更新监控主机信息。

6.故障的主服务器修复后将成为从服务器继续工作。

5 redis持久化

RDB:是一种快照存储持久化方式,具体就是将Redis某一时刻的内存数据保存到硬盘的文件当中,默认保存的文件名为dump.rdb,而在Redis服务器启动时,会重新加载dump.rdb文件的数据到内存当中恢复数据。

AOF:会记录客户端对服务器的每一次写操作命令,并将这些写操作追加保存到以后缀为aof文件末尾,在Redis服务器重启时,会加载并运行aof文件的命令,以达到恢复数据的目的

RDB的几个优点
与AOF方式相比,通过rdb文件恢复数据比较快。
rdb文件非常紧凑,适合于数据备份。
通过RDB进行数据备,由于使用子进程生成,所以对Redis服务器性能影响较小。
RDB的几个缺点
如果服务器宕机的话,采用RDB的方式会造成某个时段内数据的丢失,比如我们设置10分钟同步一次或5分钟达到1000次写入就同步一次,那么如果还没达到触发条件服务器就死机了,那么这个时间段的数据会丢失。
使用save命令会造成服务器阻塞,直接数据同步完成才能接收后续请求。
使用bgsave命令在forks子进程时,如果数据量太大,forks的过程也会发生阻塞,另外,forks子进程会耗费内存
AOF的优点 AOF只是追加日志文件,因此对服务器性能影响较小,速度比RDB要快,消耗的内存较少。 AOF的缺点 AOF方式生成的日志文件太大,即使通过AFO重写,文件体积仍然很大。 恢复数据的速度比RDB慢

6 redis数据类型(五个):字符串,哈希,列表,集合,有序集合

字符串:是Redis最基础的数据结构,字符串类型可以是JSON、XML甚至是二进制的图片等数据,但是最大值不能超过512MB,使用场景通常有:

  • 在web服务中,使用MySQL作为数据库,Redis作为缓存。由于Redis具有支撑高并发的特性,通常能起到加速读写和降低后端压力的作用。web端的大多数请求都是从Redis中获取的数据,如果Redis中没有需要的数据,则会从MySQL中去获取,并将获取到的数据写入redis
  • 在分布式系统中,用户的每次请求会访问到不同的服务器,这就会导致session不同步的问题,假如一个用来获取用户信息的请求落在A服务器上,获取到用户信息后存入session。下一个请求落在B服务器上,想要从session中获取用户信息就不能正常获取了,因为用户信息的session在服务器A上,为了解决这个问题,使用redis集中管理这些session,将session存入redis,使用的时候直接从redis中获取就可以了
192.168.153.21:6379> set keyname valuename
OK
192.168.153.21:6379> get keyname
"valuename"

哈希

192.168.153.21:6379> hset usertable username kitty
(integer) 0

192.168.153.21:6379> hset usertable password 789056
(integer) 0

192.168.153.21:6379> hget usertable username
"kitty"

192.168.153.21:6379> hgetall usertable
1) "username"
2) "kitty"
3) "password"
4) "789056"
redis哈希操作

列表:

使用场景:

  • 列表用来存储多个有序的字符串,既然是有序的,那么就满足消息队列的特点。使用lpush+rpop或者rpush+lpop实现消息队列。除此之外,redis支持阻塞操作,在弹出元素的时候使用阻塞命令来实现阻塞队列
  • 列表的元素不但是有序的,而且还支持按照索引范围获取元素。因此我们可以使用命令lrange key 0 9分页获取文章列表
192.168.153.21:6379> lpush user name
(integer) 1
192.168.153.21:6379> lpush user age
(integer) 2
192.168.153.21:6379> lpush user sex
(integer) 3
192.168.153.21:6379> lrange user 0 -1
1) "sex"
2) "age"
3) "name"
192.168.153.21:6379> rpush user id
(integer) 4
192.168.153.21:6379> rpush user hight
(integer) 5
192.168.153.21:6379> rpush user weight
(integer) 6
192.168.153.21:6379> lrange user 0 -1
1) "sex"
2) "age"
3) "name"
4) "id"
5) "hight"
6) "weight"

192.168.153.21:6379> lpop user 1
1) "sex"
192.168.153.21:6379> rpop user 1
1) "weight"
192.168.153.21:6379> lrange user 0 -1
1) "age"
2) "name"
3) "id"
4) "hight"
redis列表操作

集合

集合类型也可以保存多个字符串元素,与列表不同的是,集合中不允许有重复元素并且集合中的元素是无序的。一个集合最多可以存储2^32-1个元素;

使用场景:

  • 例如一个用户对篮球、足球感兴趣,另一个用户对橄榄球、乒乓球感兴趣,这些兴趣点就是一个标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同感兴趣的标签。给用户打标签的时候需要①给用户打标签,②给标签加用户,需要给这两个操作增加事务 
192.168.153.21:6379> sadd username tom
(integer) 1
192.168.153.21:6379> sadd username tom
(integer) 0
192.168.153.21:6379> smembers username
1) "tom"
192.168.153.21:6379> sadd username kitty
(integer) 1
192.168.153.21:6379> smembers username
1) "kitty"
2) "tom"

192.168.153.21:6379> srem username tom
(integer) 1
192.168.153.21:6379> SMEMBERS username
1) "kitty"
redis集合操作

有序集合

有序集合和集合一样,不能有重复元素。但是可以排序,它给每个元素设置一个score作为排序的依据。最多可以存储2^32-1个元素使用场景:

  • 下单系统,下单后需要在15分钟内进行支付,如果15分钟未支付则自动取消订单。将下单后的十五分钟后时间作为score,订单作为value存入redis,消费者轮询去消费,如果消费的score大于等于这笔记录的score,则将这笔记录移除队列,取消订单
  • 热搜:前端搜索一次,后端操作value+1
192.168.153.21:6379> ZADD order001 15 tom
(integer) 1
192.168.153.21:6379> ZADD order001 16 kitty
(integer) 1
192.168.153.21:6379> ZADD order001 17 tony
(integer) 1

#> zrange key start end
192.168.153.21:6379> zrange order001 0 -1
1) "tom"
2) "kitty"
3) "tony"

#> zrem key value
192.168.153.21:6379> ZREM order001 tom
(integer) 1

192.168.153.21:6379> zrange order001 0 -1
1) "kitty"
2) "tony"
redis有序集合

7 redis在开发中都可以对哪些数据进行缓存?redis的缓存雪崩,缓存穿透,缓存击穿是什么意思?怎么避免?

对经常需要查询但不是经常去修改的数据、前端静态页面等。
热点数据,经常被查询的数据,都可以进行缓存。
1. 不经常改动的热点数据:导航广告、权限信息、站点配置、
logo:xxx.1.png
公司版本:xx@qqq
公司证书:
导航地图:
2. 经常改动的热点数据:购物车、优惠券、token、session
什么是缓存雪崩?
    缓存在同一时间或短时间内出现大面积的缓存数据失效。
出现的原因?
    1. 缓存服务瘫痪了,redis宕机了,redis重启了,
    2. 大批量的数据在同一时间过期了,
导致的后果?
    导致在短时间内有大量的qps直接访问到了mysql上,导致mysql的读写压力急剧上升,甚至导致服务崩溃。

解决办法:
    1、引入key随机性。错开key的失效时间,不要让大量的数据同时失效,设置每一个key的过期时间时添加随机时间值  
         固定过期时间+random.randint(0, 6000) s
    2、提高缓存服务的可用性。采用集群/哨兵模式部署,让这些key分散到不同的节点,避免出现单点服务瘫痪
    3、部分数据不设置数据的失效时间
    4、接口限流,限制同一个api接口访问上限。
    
弥补方案:
    1、服务熔断、直接在请求缓存失败以后,直接给客户端返回错误提示,进行页面刷新。保证部分人正常访问,部分人刷新几次后正常访问。
    2、提高数据库的可用性,数据库采用读写分离,分库分表,集群架构,分摊流量。


缓存穿透:
    当客户端访问一个不存在于mysql或者缓存中的数据时,这就是缓存穿透了。    
    刻意使用redis和mysql中都不存在的数据请求(比如id=-1),从而让请求绕过缓存直接请求mysql然后返回空不存入redis,通过发起大量请求最终压垮数据库

解决办法:
    1、全面建立缓存,不管mysql数据库返回什么数据都放入缓存,即便是空值
    2、对请求进行合法性校验过滤掉不合法请求
    3、使用redis中的布隆过滤器(漏斗过滤器)

缓存击穿:
    一个非常频繁被访问key突然失效,导致对于这个key的请求都打到mysql上,造成数据库压力瞬间增大
解决办法:
    1、对这个key设置永不过期
    2、采用分布式锁,互斥锁(mutex)

8 Redis的内存满了,怎么扩容?说下Redis的内存淘汰机制(LRU)?

采用redis-clurter搭建哨兵集群实现扩容。增加Redis服务器的数量,搭建一个集群,读写分离

LRU 全称是 Least Recently Used,即按最近最少使用原则将最不常用的数据筛选出来,保留最近频繁使用的数据。
LRU 会把所有数据组成一个链表,链表头部称为 MRU,代表最近最常使用的数据;尾部称为 LRU代表最近最不常使用的数据。

LFU(Least Frequently Used),表示最近最少使用,它和key的使用次数有关,其思想是:根据key最近被访问的频率进行淘汰,比较少访问的key优先淘汰,反之则保留。
Redis默认的最大内存 maxmemory=0 ,表示不限制Redis内存的使用。
终端下获取当前redis服务端设置的最大内存值:config get maxmemory

Redis一共提供了8种淘汰策略,默认策略是noeviction(当内存满了就不会淘汰任何数据,但是写入数据会报错)。


针对设置了过期时间的key:
        1. volatile-lru,针对设置了过期时间的key,使用lru算法进行淘汰。
        3. volatile-lfu,针对设置了过期时间的key,使用lfu算法进行淘汰。
        5. volatile-random,从所有设置了过期时间的key中使用随机淘汰的方式进行淘汰。
        7. volatile-ttl,针对设置了过期时间的key,越早过期的越先被淘汰。
针对所有数据的key:
        2. allkeys-lru,针对所有key使用lru算法进行淘汰。
        4. allkeys-lfu,针对所有key使用lfu算法进行淘汰。
        6. allkeys-random,针对所有的key使用随机淘汰机制进行淘汰。

8. noeviction,不会淘汰任何数据,当使用的内存空间超过 maxmemory 值时,再有写请求来时返回错误。

9 redis和mysql数据不一致如何解决

方案一:数据实时更新

当更新数据库的时候,同步更新缓存。
优点:数据一致性强,不会出现缓存雪崩的问题。
缺点:代码耦合度高,影响正常业务,增加一次网络开销。
适用环境:适用于数据一致性要求高的场景,比如银行业务,证券交易等

方案二:数据的准实时更新

当更新数据库的同时,异步去更新缓存,比如更新数据库后把一条消息发送到mq中去实现。
优点:与业务解耦,不影响正常业务,不会出现缓存雪崩。
缺点:有较短的延迟,并且无法保证最终的一致性,需要补偿机制。
适用环境:写操作不频繁并且实时性要求不严格的场景。

方案三:缓存失效机制

基于缓存本身的失效机制,具体实现方式为设置缓存失效时间,如果有缓存就从缓存中取数据,如果没缓存就从数据库中取数据,并且重新设置缓存。
优点:实现方式简单,与业务完美解耦,不影响正常业务。
缺点:有一定延迟,并且存在缓存雪崩的情况。
适用环境:适合读多写少的互联网环境,能接受一定的数据延时。

方案四:定时任务更新

通过定时任务,按照一定时间间隔更新缓存。
优点:不影响正常业务,在特殊场景应用广泛。
缺点:不保证实时一致性,且需要为每个任务写一个调度代码。
适用环境:适用于需要复杂数据统计的缓存更新,比如展示高速车流量,五分钟一次的统计不会影响业务使用。

10 容器:容器的应用进程直接运行于宿主机的内核,容器内没有自己的内核,也没有进行硬件虚拟。共享宿主机的操作系统和硬件资源,省去了启动和维护整个虚拟客户机的开销(硬件初始化、Kernel boot、init等),因而它非常轻量级,占用内存资源少,启动快,但安全隔离性低于虚拟机。

容器将应用程序的配置和所有依赖打包成一个镜像在容器中,为应用程序提供一个可以独立运行的环境,可以保证应用程序在任何环境中都可以按照预期来运行,方便持续集成和持续部署。

虚拟机:虚拟出一套完整的硬件,在其上运行一个完整的操作系统,在该系统上再运行所需应用进程

11 Dockerfile是提供开发者用于定制自定义镜像的配置文件

Dockerfile提供的指令

指令描述
FROM 设置当前镜像的基础镜像(它的妈妈是谁?相当于执行一次docker pull
MAINTAINER 设置当前镜像的作者信息(告诉使用者,谁创造了它)
LABEL 设置当前镜像的作者信息(告诉使用者,谁创造了它)
RUN 设置当前镜像需要完成的shell命令(你希望对FROM拉取回来的镜像进行怎么样的加工?)
ADD 设置从容器外界复制文件/目录到当前镜像内部,ADD指令会自动解压压缩包
COPY 设置从容器外界复制文件/目录到当前镜像内部,COPY指令不会解压压缩包
WORKDIR 设置当前镜像启动以后的容器的初始工作目录(相当于cd)
ENV 设置当前镜像启动以后的容器的环境变量(相当于 docker -e)
VOLUME 设置当前镜像启动以后的挂载目录(相当于-v参数,指定容器内部提供哪些路径与容器外面进行映射)
EXPOSE 设置当前镜像启动以后的开放端口(相当于-p参数,指定容器内部提供哪些端口与容器外界进行映射)
COMMAND 设置当前镜像启动以后的运行命令(相当于docker run的最后一个参数)

12 容器编排工具-docker-compose:负责实现对Docker容器集群的快速编排和批量管理

Docker-Compose将所管理的Docker容器分为三层,分别是工程(project),服务(service)以及容器(container)。

Docker-Compose允许我们开发者通过一个单独的docker-compose.yml配置文件(YAML 格式)来定义一组相关联的应用容器为一个工程(project)。一个工程至少有一个服务,一个服务下至少有一个容器。Docker-Compose的工程配置文件默认为docker-compose.yml,也可以通过-f 参数来指定成其他的配置文件名。

常用命令:

因为docker提供的终端命令一般用于操作操作单个容器,而docker-compose提供的终端命令,可以多个容器组成一个整体,批量执行。

docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
-f --file FILE指定Compose模板文件,默认为当前目录下docker-compose.yml

docker-compose标准模板文件应该包含version、services、networks 三大部分,最关键的是services和networks两个部分。当然networks具有默认值,我们可以不填写。

13 docker 常用命令:

dockerfile定制镜像:docker build -t 镜像名:镜像版本 

容器打包成镜像:docker commit 容器id 镜像名:镜像版本  

镜像打包成tar归档文件:docker save -o X.tar X     

载入镜像:docker load < X.tar / docker load --input X.tar   

14 客户端想登录到服务器:

  1. 先做一个公钥交换
  2. 把用户输入的密码用服务器端的公钥加密一波在发出去给服务器端
  3. 服务器端得到加密后的密码后用自己的私钥(服务器私钥)解密一波就知道是不是正确了,然后密码正不正确得搞个反馈回去,这个反馈呢服务器端用客户端的公钥加密一波,再发回去给客户端
  4. 客户端拿到这个加密后的反馈,就直接用自己的私钥(客户端私钥)解密一下就完事

免密登录流程:

  1. 首先不必做公钥交换了,在登录请求之前我需要将客户端的公钥发给服务器端
  2. 现在客户端发起一个登录请求
  3. 服务器端怎么做呢,先随机生成一串东西,然后用客户端的公钥进行加密,发给客户端
  4. 客户端拿到这串解密后的东西后用自己的私钥(客户端私钥)解密一下,如无意外,会得到原来的字符串,然后再把这个解密后的字符串发给服务器端
  5. 服务器端拿到这串字符串后和自己之前生成的字符串比对一下,看是不是一样,一样的话就是自己人了,把登录反馈回去

对称加密:加密与解密用的是同样的密钥。效率高,对称加密的一大缺点是密钥的管理与分配,在发送密钥的过程中,密钥有很大的风险会被黑客们拦截。现实中通常的做法是将对称加密的密钥进行非对称加密,然后传送给需要它的人。

非对称加密:它使用了一对密钥,公钥和私钥。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。原理:计算机A和计算机B把自己的公钥复制一份,发送给对对方,也就是交换公钥,需要加密数据的时候就用对方的公钥解密,然后发送给对方,然后对方就用自己的密钥解密

15 select, poll, epoll 都是I/O多路复用的具体的实现(单个线程,通过记录跟踪每个I/O流(sock)的状态,来同时管理多个I/O流 。)。他们出现是有先后顺序的。

# select是第一个实现
select 任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,于是你只能自己一个一个的找,10几个sock可能还好,要是几万的sock每次都找一遍...
select 只能监视1024个链接。
select 线程不是安全的。

# poll 修复了select的很多问题
poll 去掉了1024个链接的限制,于是可以有多个连接进来。
但是poll仍然线程不是安全的, 这就意味着,不管服务器有多强悍,也只能在一个线程里面处理一组I/O流。

# epoll 可以说是I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题
epoll 现在是线程安全的。 
epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。

十 组件celery

1 celery你用到了什么地方,介绍celery下架构原理?

celery,是一个异步任务执行框架主要用于执行异步任务或定时任务,在项目中,我们一般可以用于完成异步任务、流量削峰、异步通信等场景中,项目里面的短信验证码批量邮件发送订单超时取消等功能就采用了celery来完成。

celery 用在比较耗时又不需要立即获得结果的地方。

celery的架构组成由消息中间件和任务执行单元、任务结果存储组成。当用户的触发器把任务加入消息中间件队列以后,celery的任务执行单元取出任务并执行,然后将任务执行结果保存到任务结果存储中。

十一 数据结构

1 简述一下常见的基本数据结构

1.数组
数组是可以再内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组下标进行访问,数组下标从0开始。例如下面这段代码就是将数组的第一个元素赋值为 12.栈
栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作。 栈的特点是:先进后出,或者说是后进先出,从栈顶放入元素的操作叫入栈,取出元素叫出栈。
3.队列
队列与栈一样,也是一种线性表,不同的是,队列可以在一端添加元素,在另一端取出元素,也就是:先进先出。从一端放入元素的操作称为入队,取出元素为出队。
4.链表
链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内存空间),另一个是指向下一个结点地址的指针域。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等。
5.树
树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做 “树” 是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
6.散列表
散列表,也叫哈希表,是根据关键码和值 (key和value) 直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素。
7.堆
堆是一种比较特殊的数据结构,可以被看做一棵树的数组对象,具有以下的性质:堆中某个节点的值总是不大于或不小于其父节点的值;堆总是一棵完全二叉树。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
8.图
图是由结点的有穷集合V和边的集合E组成。其中,为了与树形结构加以区别,在图结构中常常将结点称为顶点,边是顶点的有序偶对,若两个顶点之间存在一条边,就表示这两个顶点具有相邻关系。

 

posted on 2022-11-06 21:37  大明花花  阅读(267)  评论(0编辑  收藏  举报