大家好,本文将围绕python考点总结展开说明,python常考题目是一个很多人都想弄明白的事情,想搞清楚python常考题型需要先了解以下几个事情。
字典,字符串,列表,元组,集合
元组和列表都属于序列。
列表属于可变序列,它的元素可以随时修改或者删除,而元组属于不可变序列,其中的元素是不能修改的,除非整体重新赋值python编程高中版。
列表可以使用多种方法实现添加和修改列表元素,而元组没有办法,因为不能向元组中添加或修改元素,同样也不能删除元素
列表可以使用切片方法访问和修改列表中的元素,元组也支持切片,但是它只支持通过切片访问元组中的元素,不支持修改
元组比列表中的访问和处理速度更快,所以如果只需要对其中的元素进行访问,而不进行任何修改,建议使用元组。
列表不能作为字典类型中的键,而元组是可以的。
列表是动态数组,它们可变且可以重设长度(改变其内部元素的个数)。
元组是静态数组,它们不可变,且其内部数据一旦创建便无法改变。
元组缓存于Python运行时环境,这意味着我们每次使用元组时无须访问内核去分配内存。
这些区别结实率两者在设计哲学上的不同:
列表可被用于保存多个互相独立对象的数据集合
元组用于描述一个不会改变的事物的多个属性
下面是一些关键点:
Python 是一种解释型语言。这就是说,与C 语言和C 的衍生语言不同,Python 代码在运行之前不需要编译。其他解释型语言还包括PHP 和Ruby。
Python 是动态类型语言,指的是你在声明变量时,不需要说明变量的类型。你可以直接编写类似x=111 和x="I’m a string"这样的代码,程序不会报错。
Python 非常适合面向对象的编程(OOP),因为它支持通过组合(composition)与继承(inheritance)的方式定义类(class)。
Python 中没有访问说明符(access specifier,类似C++中的public 和private),这么设计的依据是“大家都是成年人了”。
在Python 语言中,函数是第一类对象(first-class objects)。这指的是它们可以被指定给变量,函数既能返回函数类型,也可以接受函数作为输入。类(class)也是第一类对象。
Python 代码编写快,但是运行速度比编译语言通常要慢。好在Python 允许加入基于C语言编写的扩展,因此我们能够优化代码,消除瓶颈,这点通常是可以实现的。
numpy 就是一个很好地例子,它的运行速度真的非常快,因为很多算术运算其实并不是通过Python 实现的。
Python 用途非常广泛——网络应用,自动化,科学建模,大数据应用,等等。它也常被用作“胶水语言”,帮助其他语言和组件改善运行状况。
Python 让困难的事情变得容易,因此程序员可以专注于算法和数据结构的设计,而不用处理底层的细节。
为什么提这个问题:如果你应聘的是一个Python 开发岗位,你就应该知道这是门什
么样的语言,以及它为什么这么酷。以及它哪里不好。
两者用法相同,不同的是 range 返回的结果是一个列表,而 xrange 的结果是一个生成器,前者是直接开辟一块内存空间来保存列表,后者是边循环边使用,只有使用时才会开辟内存空间,所以当列表很长时,使用 xrange 性能要比 range 好。
直接赋值:其实就是对象的引用(别名)。
浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。
深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。
Python 自带的数据结构分为可变的和不可变的。可变的有:数组、集合、字典;不可变的有:字符串、元组、数。
在Python 中,所有的名字都存在于一个空间中,它们在该空间中存在和被操作——这就是命名空间。
它就好像一个盒子,每一个变量名字都对应装着一个对象。当查询变量的时候,会从该盒子里面寻找相应的对象。
lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数
1、lambda 函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在此一处使用,
连名字都很随意的情况下;
2、匿名函数,一般用来给 filter, map 这样的函数式编程服务;
3、作为回调函数,传递给某些应用,比如消息处理
匿名函数lambda 没有语句的原因,是它被用于在代码被执行的时候构建新的函数对象并且返回。
lambda 函数是匿名函数;使用 lambda 函数能创建小型匿名函数。这种函数得名于省略了用 def
声明函数的标准步骤;
f = lambda x,y:x+y
print(f(2017,2018))
Pass 是一个在Python 中不会被执行的语句。在复杂语句中,如果一个地方需要暂时被留白,它常常被用于占位符。
遍历器用于遍历一组元素,比如列表这样的容器。
在Python 中,unittest 是Python 中的单元测试框架。它拥有支持共享搭建、自动测试、在测试中暂停代码、将不同测试迭代成一组,等等的功能。
Slicing 是一种在有序的对象类型中(数组,元组,字符串)节选某一段的语法。
生成器是实现迭代器的一种机制。它功能的实现依赖于yield 表达式,除此之外它跟普通的函数没有两样。
Python 中文档字符串被称为docstring,它在Python 中的作用是为函数、模块和类注释生成文档。
如果要在Python 中拷贝一个对象,大多时候你可以用copy.copy()或者copy.deepcopy()。但并不是所有的对象都可以被拷贝。
Python 中的序列索引可以是正也可以是负。如果是正索引,0 是序列中的第一个索引,1是第二个索引。如果是负索引,(-1)是最后一个索引而(-2)是倒数第二个索引。
你可以使用自带函数str()将一个数字转换为字符串。如果你想要八进制或者十六进制数,可以用oct()或hex()。
Python 是一种解释性语言,它的源代码可以直接运行。Python 解释器会将源代码转换成中间语言,之后再翻译成机器码再执行。
Python 的内存管理是由私有heap 空间管理的。所有的Python 对象和数据结构都在一个私有heap 中。程序员没有访问该heap 的权限,只有解释器才能对它进行操作。
为Python 的heap 空间分配内存是由Python 的内存管理模块进行的,其核心API 会提供一些访问该模块的方法供程序员使用。
Python 有自带的垃圾回收系统,它回收并释放没有被使用的内存,让它们能够被其他程序使用。
单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。 1.网站的计数器 2.应用配置 3.多线程池 4.数据库配置,数据库连接池 5.应用程序的日志应用…
class A(object):
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = object.__new__(cls)
return cls.__instance
else:
return cls.__instance
数组和元组之间的区别:数组内容是可以被修改的,而元组内容是只读的。另外,元组可以被哈希,比如作为字典的关键字。
Python 中的一切都是类,所有的变量都是一个对象的引用。引用的值是由函数确定的,因此无法被改变。但是如果一个对象是可以被修改的,你可以改动对象。
它们是可以轻松创建字典和列表的语法结构。
推导式(又称解析器),是 Python 独有的一种特性。使用推导式可以快速生成列表、元组、字典以及集合类型的数据,因此推导式又可细分为列表推导式、元组推导式、字典推导式以及集合推导式。
variable = [out_exp for out_exp in input_list if out_exp == 2]
(表达式 for 迭代变量 in 可迭代对象 [if 条件表达式] )
variable = {out_exp for out_exp in input_list if out_exp == 2}
{ 表达式 for 迭代变量 in 可迭代对象 [if 条件表达式] }
参考:Python(列表推导式、元组推导式、字典推导式和集合推导式)
Xrange 用于返回一个xrange 对象,而range 用于返回一个数组。不管那个范围多大,Xrange 都使用同样的内存。
在Python 中,模块是搭建程序的一种方式。每一个Python 代码文件都是一个模块,并可以引用其他的模块,比如对象和属性。
一个包含许多Python 代码的文件夹是一个包。一个包可以包含模块和子文件夹。
【同】
二者均是Python面向对象语言中的函数,__new__比较少用,__init__则用的比较多。
【异】
__new__是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例对象,是个静态方法。
__init__是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值,通常用在初始化一个类实例的时候。是一个实例方法。
也就是: __new__先被调用,__init__后被调用,__new__的返回值(实例)将传递给__init__方法的第一个参数,然后__init__给这个实例设置一些参数。
总结
__new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供
__new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类__new__出来的实例,或者直接是object的__new__出来的实例
__init__有一个参数self,就是这个__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值
我们可以将类比作制造商,__new__方法就是前期的原材料购买环节,__init__方法就是在有原材料的基础上,加工,初始化商品环节
1. Python 的内存管理机制及调优手段?(2018-3-30-lxy)
内存管理机制:引用计数、垃圾回收、内存池。
引用计数:
引用计数是一种非常高效的内存管理手段, 当一个 Python 对象被引用时其引用计数增加 1, 当
其不再被一个变量引用时则计数减 1. 当引用计数等于 0 时对象被删除。
垃圾回收 :
1. 引用计数
引用计数也是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术。当 Python 的某个对象的引用计数降为 0 时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如,某个新建对象,它被分配给某个引用,对象的引用计数变为 1。如果引用被删除,对象的引用计数为 0,那么该对象就可以被垃圾回收。不过如果出现循环引用的话,引用计数机制就不再起有效的作用了.
2. 标记清除
如果两个对象的引用计数都为 1,但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被回收的,也就是说,它们的引用计数虽然表现为非 0,但实际上有效的引用计数为 0。所以先将循环引用摘掉,就会得出这两个对象的有效计数。
3. 分代回收
从前面“标记-清除”这样的垃圾收集机制来看,这种垃圾收集机制所带来的额外操作实际上与系统中总的内存块的数量是相关的,当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾回收带来的额外操作就越少;反之,当需回收的内存块越少时,垃圾检测就将比垃圾回收带来更少的额外操作。
指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的
消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪
费。导致程序运行速度减慢甚至系统崩溃等严重后果。
有 del() 函数的对象间的循环引用是导致内存泄漏的主凶。
不使用一个对象时使用:del object 来删除一个对象的引用计数就可以有效防止内存泄漏问题。
通过 Python 扩展模块 gc 来查看不能回收的对象的详细信息。
可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为 0 来判断是否内存
泄漏。
Python 的参数传递有:位置参数、默认参数、可变参数、关键字参数。
函数的传值到底是值传递还是引用传递,要分情况:
不可变参数用值传递:
像整数和字符串这样的不可变对象,是通过拷贝进行传递的,因为你无论如何都不可能在原处改变不可变对象;
可变参数是引用传递的:
比如像列表,字典这样的对象是通过引用传递、和 C 语言里面的用指针传递数组很相似,可变对象能在函数内部改变
①从参数方面来讲:
map()包含两个参数,第一个参数是一个函数,第二个是序列(列表 或元组)。其中,函数(即 map
的第一个参数位置的函数)可以接收一个或多个参数。
reduce()第一个参数是函数,第二个是序列(列表或元组)。但是,其函数必须接收两个参数。
②从对传进去的数值作用来讲:
map()是将传入的函数依次作用到序列的每个元素,每个元素都是独自被函数“作用”一次 。
reduce()是将传人的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用
(累积计算)。
map()
reduce()
Python中将两个字典进行合并操作,是一个比较常见的问题。
对于这个问题,比较直观的想法是将两个字典做相加操作,赋值给结果字典,其代码为:
1.两个字典:a={‘a’:1,‘b’:2,‘c’:3} b= {‘aa’:11,‘bb’:22,‘cc’:33}
合并1:dict(a,**b) 操作如下:
合并2:dict(a.items()+b.items()) 如下:
合并3:c = {} c.update(a) c.update(b) 输出c 如下:
2.两个list合并:a=[1,2,3,4,5,6] b=[‘a’,‘b’,‘c’,‘d’]
合并1:a+b 如下:
合并2:a+=b 这时a的值变成了合并后的结果,如下:
合并3:a.extend(b) 和+=结果一样,输出a 如下:
合并4:a.append(b)将b看成list一个元素和a合并成一个新的list,合并后的结果输入a 如下:
合并5:a[0:0] = b 使用切片,如下:
hasattr(object, name)函数:
判断一个对象里面是否有name属性或者name方法,返回bool值,有name属性(方法)返回True,否则返回 False。
etattr(object, name[,default]) 函数:
获取对象 object 的属性或者方法,如果存在则打印出来,如果不存在,打印默认值,默认值可选。
注意:如果返回的是对象的方法,则打印结果是:方法的内存地址,如果需要运行这个方法,可以在后
面添加括号()
setattr(object,name,values)函数:
给对象的属性赋值,若属性不存在,先创建再赋值
在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包,修改闭包内使⽤的外部函数变量使⽤ nonlocal 关键字来完成。
# 定义⼀个外部函数
def func_out(num1):
# 定义⼀个内部函数
def func_inner(num2):
# 这⾥本意想要修改外部num1的值,实际上是在内部函数定义了⼀个局部变量num1
nonlocal num1 # 告诉解释器,此处使⽤的是 外部变量a
# 修改外部变量num1
num1 = 10
# 内部函数使⽤了外部函数的变量(num1)
result = num1 + num2
print("结果是:", result)
print(num1)
func_inner(1)
print(num1)
# 外部函数返回了内部函数,这⾥返回的内部函数就是闭包
return func_inner
# 创建闭包实例
f = func_out(1)
# 执⾏闭包
f(2)
Python 装饰器是Python 中的特有功能,可以使修改函数变得更容易。
装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
import time
def timeit(func):
def wrapper():
start = time.clock()
func()
end =time.clock()
print('used:', end - start)
return wrapper
@timeit
def foo():
print 'in foo()'foo()
装饰器本质上是一个Python 函数,它可以在让其他函数在不需要做任何代码的变动的前提下增加额外的功能。 装饰器的返回值也是一个函数的对象,它经常用于有切面需求的场景。 比如:插入日志、性能测试、事务处理、缓存、权限的校验等场景 有了装饰器就可以抽离出大量的与函数功能本身无关的雷同代码并发并继续使用。
迭代器是一个更抽象的概念,任何对象,如果它的类有 next 方法和 iter 方法返回自己本身,对于 string、list、dict、tuple 等这类容器对象,使用 for 循环遍历是很方便的。在后台 for 语句对容器对象调用 iter()函数,iter()是 python 的内置函数。iter()会返回一个定义了 next()方法的迭代器对象,它在容器中逐个访问容器内元素,next()也是 python 的内置函数。在没有后续元素时,next()会抛出一个 StopIteration 异常。
生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数据的时候使用 yield 语句。每次 next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)
**区别:**生成器能做到迭代器能做的所有事,而且因为自动创建了 iter()和 next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出 StopIteration 异常。
PEP8 是一个编程规范,内容是一些关于如何让你的程序更具可读性的建议。
PEP8 规范:
变量
常量:大写加下划线 USER_CONSTANT。
私有变量 : 小写和一个前导下划线 _private_value。
Python 中不存在私有变量一说,若是遇到需要保护的变量,使用小写和一个前导下划线。但这只是程序员之间的一个约定,用于警告说明这是一个私有变量,外部类不要去访问它。但实际上,外部类还是可以访问到这个变量。
内置变量 : 小写,两个前导下划线和两个后置下划线 __class__两个前导下划线会导致变量在解释期间被更名。这是为了避免内置变量和其他变量产生冲突。用户
定义的变量要严格避免这种风格。以免导致混乱。
函数和方法
总体而言应该使用,小写和下划线。但有些比较老的库使用的是混合大小写,即首单词小写,之后
每个单词第一个字母大写,其余小写。但现在,小写和下划线已成为规范。
私有方法 :小写和一个前导下划线
这里和私有变量一样,并不是真正的私有访问权限。同时也应该注意一般函数不要使用两个前导下
划线(当遇到两个前导下划线时,Python 的名称改编特性将发挥作用)。
特殊方法 :小写和两个前导下划线,两个后置下划线这种风格只应用于特殊函数,比如操作符重载等。
函数参数 : 小写和下划线,缺省值等号两边无空格
类
类总是使用驼峰格式命名,即所有单词首字母大写其余字母小写。类名应该简明,精确,并足以从
中理解类所完成的工作。常见的一个方法是使用表示其类型或者特性的后缀,例如:
SQLEngine,MimeTypes 对于基类而言,可以使用一个 Base 或者 Abstract 前缀 BaseCookie,
AbstractGroup
模块和包
除特殊模块 init 之外,模块名称都使用不带下划线的小写字母。若是它们实现一个协议,那么通常使用 lib 为后缀,例如:
import smtplib
import os
import sys
关于参数
5.1 不要用断言来实现静态类型检测。断言可以用于检查参数,但不应仅仅是进行静态类型检测。
Python 是动态类型语言,静态类型检测违背了其设计思想。断言应该用于避免函数不被毫无意义的调
用。
5.2 不要滥用 *args 和 **kwargs。*args 和 **kwargs 参数可能会破坏函数的健壮性。它们使签
名变得模糊,而且代码常常开始在不应该的地方构建小的参数解析器。
其他
6.1 使用 has 或 is 前缀命名布尔元素
is_connect = True
has_member = False
6.2 用复数形式命名序列
members = [‘user_1’, ‘user_2’]
6.3 用显式名称命名字典
person_address = {‘user_1’:‘10 road WD’, ‘user_2’ : ‘20 street huafu’}
6.4 避免通用名称
诸如 list, dict, sequence 或者 element 这样的名称应该避免。
6.5 避免现有名称
诸如 os, sys 这种系统已经存在的名称应该避免。
一些数字
一行列数 : PEP 8 规定为 79 列。根据自己的情况,比如不要超过满屏时编辑器的显示列数。
一个函数 : 不要超过 30 行代码, 即可显示在一个屏幕类,可以不使用垂直游标即可看到整个函数。
一个类 : 不要超过 200 行代码,不要有超过 10 个方法。一个模块 不要超过 500 行。
验证脚本
可以安装一个 pep8 脚本用于验证你的代码风格是否符合 PEP8。
Pickle 模块读入任何Python 对象,将它们转换成字符串,然后使用dump 函数将其转储到一个文件中——这个过程叫做pickling。
反之从存储的字符串文件中提取原始Python 对象的过程,叫做unpickling。
高阶函数:一个函数可以作为参数传给另外一个函数,或者一个函数的返回值为另外一个函数(若返回值为该函数本身,则为递归),满足其一则为高阶函数。
参数为函数
#参数为函数
def bar():
print("in the bar..")
def foo(func):
func()
print("in the foo..")
foo(bar)
返回值为函数
#返回值为函数
def bar():
print("in the bar..")
def foo(func):
print("in the foo..")
return bar
res=foo(bar)
res()
以上两个示例中,函数foo()为高阶函数,示例一中函数bar作为foo的参数传入,示例二中函数bar作为foo的返回值。
注:函数名(例如bar 、foo)–>其为该函数的内存地址;函数名+括号(例如 bar()、foo() )–>调用该函数。
高阶函数参考文档
参考菜鸟教程:python内置函数
迭代器是一个更抽象的概念,任何对象,如果它的类有 next 方法和 iter 方法返回自己本身,对于 string、list、dict、tuple 等这类容器对象,使用 for 循环遍历是很方便的。在后台 for 语句对容器对象调用 iter()函数,iter()是 python 的内置函数。iter()会返回一个定义了 next()方法的迭代器对象,它在容器中逐个访问容器内元素,next()也是 python 的内置函数。在没有后续元素时,next()会抛出一个 StopIteration 异常。
生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数据的时候使用 yield 语句。每次 next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)
区别:生成器能做到迭代器能做的所有事,而且因为自动创建了 iter()和 next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出 StopIteration 异常。
指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。导致程序运行速度减慢甚至系统崩溃等严重后果。
有 del() 函数的对象间的循环引用是导致内存泄漏的主凶。
不使用一个对象时使用:del object 来删除一个对象的引用计数就可以有效防止内存泄漏问题。
通过 Python 扩展模块 gc 来查看不能回收的对象的详细信息。
可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为 0 来判断是否内存泄漏
if hasattr(Parent,'x'):
print(getattr(Parent,'x'))
setattr(Parent,'x',3)
print(getattr(Parent,'x'))
Python的标准库有:
Python常用的第三方库有:
在 python2 时代,整型有 int 类型和 long 长整型,长整型不存在溢出问题,即可以存放任意大小的整数。在 python3 后,统一使用了长整型。这也是吸引科研人员的一部分了,适合大数据运算,不会溢出,也不会有其他语言那样还分短整型,整型,长整型… 因此 python 就降低其他行业的学习门槛了。
参考:深度剖析为什么 Python 中整型不会溢出?
一、浏览器请求的基本流程:
用户输入网址.
#/usr/bin/python
#coding:utf-8
from __future__ import division
def jia(x,y):
return x+y
def jian(x,y):
return x-y
def cheng(x,y):
return x*y
def chu(x,y):
return x/y
##用字典实现switch
operator={"+":jia,"-":jian,"*":cheng,"/":chu}
print operator["+"](3,2)
print operator["/"](3,2)
##用 get ,对没有的运算符不会报KeyError
print operator.get("%")(3,2)
def f(x,o,y):
print operator.get(o)(x,y)
'''
通过字典调用函数格式如下
{1:case1,2:case2}.get(x,lambda *arg,**kwargs:)()
'''
#/usr/bin/python
#coding:utf-8
from __future__ import division
x=1
y=2
operator="/"
result={"+":x+y,"-":x-y,"*":x*y,"/":x/y}
print result.get(operator)
Django的特性
一.特点
(1)拥有强大的数据库操作接口(QuerySet ApI),无需使用SQL语句;
(2)自带强大的后台
(3)用正则匹配网址
(4)强大的模板语言
(5)缓存系统
二.工作原理
1.功能介绍
(1) urls.py 作用: 网址的入口,关联到对应的views.py中的指定函数,访问一个网址就相当于访问一个函数,如图所示。
(2)views.py作用: 处理用户发送的请求,通过渲染模板(templates)来显示相应的内容
(3)models.py作用:在该文件中创建数据库所用的类对象,例如以下代码就是创建的类对象
其与数据库的对应关系如下:
表名 <----------------> 类名
字段 <----------------> 属性
记录 <----------------> 类实例
(4)form.py 作用: 表单,用户在浏览器上输入数据提交,对数据的验证工作以及输入框的生成等工作。该文件需要自己手动创建。
(5)settings.py作用:Django 的设置,配置文件
(6)admin.py作用: 后台,可以用很少量的代码就拥有一个强大的后台。
2.原理
用户请求-------->生成URL------>映射到urls.py文件对应的url上-------->关联到对应的views.py中的视图函数上---------->处理用户请求,验证用户输入数据------->返回模板
3.基本命令
(1)当在models.py文件中设置类后,可使用如下命令生成数据库
$ python manage.py makemigrations # 生成数据库模型
$ python manage.py migrate #在数据库中生成数据表
(2)运行开发服务器
$ python manage.py runserver
(3)清空数据库,只剩空表
$ python manage.py flush
(4)创建超级管理员
$ python manage.py createsuperuser
(5)django项目环境终端
$ python manage.py shell
runserver 方法是调试 Django 时经常用到的运行方式,它使用 Django 自带的WSGI Server 运行,主要在测试和开发中使用,并且runserver 开启的方式也是单进程 。
uWSGI 是一个 Web 服务器,它实现了 WSGI 协议、uwsgi、http 等协议。注意 uwsgi 是一种通信协议,而 uWSGI 是实现 uwsgi 协WSGI 协议的 Web 服务器。uWSGI 具有超快的性能、低内存占用和多 app 管理等优点,并且搭配着 Nginx就是一个生产环境了,能够将用户访问请求与应用 app 隔离开,实现真正的部署 。相比来讲,支持的并发量更高,方便管理多进程,发挥多核的优势,提升性能。
Django是走大而全的方向,它最出名的是全自动化的管理后台;只需要使用ORM,做简单的对象定义,它就能自动生成数据库结构、以及全功能的管理后台。
Django内置的ORM跟框架内的其他模块耦合程度高
应用程序必须使用Django内置的ORM,否则就不能享受到框架内提供的种种基于其ORM的便利。理论上可以切换掉其ORM模块,但这就相当于要把装修完毕的房子拆除重新装修,倒不如一开始就去毛坯房做全新的装修。
Django的卖点是超高的开发效率,其性能扩展有限;采用Django的项目,在流量达到一定规模后,都需要对其进行重构,才能满足性能的要求。
Django适用的是中小型的网站,或者是作为大型网站快速实现产品雏形的工具。
Django模板的设计哲学是彻底的将代码、样式分离; Django从根本上杜绝在模板中进行编码、处理数据的可能。
Django的生命周期是:前端请求—>nginx—>uwsgi.—>中间件—>url路由---->view试图—>orm---->拿到数据返回给view---->试图将数据渲染到模版中拿到字符串---->中间件—>uwsgi---->nginx---->前端渲染。
使用HttpResponseRedirect
redire和reverse
状态码:302,301
1.重定向访问服务器两次,转发只访问服务器一次。
2.转发页面的URL不会改变,而重定向地址会改变
3.转发只能转发到自己的web应用内,重定向可以重定义到任意资源路径。
4.转发相当于服务器跳转,相当于方法调用,在执行当前文件的过程中转向执行目标文件,两个文件(当前文件和目标文件)属于同一次请求,前后页 共用一个request,可以通过此来传递一些数据或者session信息,request.setAttribute()和 request.getAttribute()。而重定向会产生一个新的request,不能共享request域信息与请求参数
5.由于转发相当于服务器内部方法调用,所以转发后面的代码仍然会执行(转发之后记得return);重定向代码执行之后是方法执行完成之后进行重定向操作,也就是访问第二个请求,如果是方法的最后一行进行重定向那就会马上进行重定向(重定向也需要return)。
6.无论是RequestDispatcher.forward方法,还是HttpServletResponse.sendRedirect方法,在调用它们之前,都不能有内容已经被实际输出到了客户端。如果缓冲区中已经有了一些内容,这些内容将被从缓冲区中移除。
可以这么理解:
转发相当于,张三向你借钱,但是你兜里没钱,所以你去找李四借到钱之后借给张三。对于张三而言并不知道你的钱是和李四借的。
重定向相当于,张三向你借钱,你兜里没钱,你告诉他李四有钱,所以张三再次去找李四借钱。
对于Django框架遵循MVC设计,并且有一个专有的名词:MVT
M全拼为Model,与MVC中的M功能相同,负责数据处理,内嵌了ORM框架
V全拼为View,与MVC中的C功能相同,接收HttpRequest,业务处理,返回HttpReponse
T全拼为Template,与MVC中的V功能相同,负责封装构造要返回的html,内嵌了模板引擎
标题排序使用order_by()
降序需要在排序字段名前加-
查询字段大于某个值:使用filter(字段名_gt=值)
1.django 中当一个用户登录 A 应用服务器(进入登录状态),然后下次请求被 nginx 代理到 B 应用服务器会出现什么影响?
如果用户在A应用服务器登陆的session数据没有共享到B应用服务器,那用户之前的登录状态就没有了。
启用中间件
post请求
验证码
表单中添加{% csrf_token %}标签
1. 形象类比
Django:
Python Web框架里比较有名当属Django,Django功能全面,它提供一站式解决方案,集成了MVT(Model-View-Template)和ORM,以及后台管理。但是缺点也很明显,它偏重。就像是一个精装修的房子,它提供好了你要用的东西,直接拿来用就可以。
Flask:
Flask相对于Django而言是轻量级的Web框架。和Django不同,Flask轻巧、简洁,通过定制第三方扩展来实现具体功能。
可定制性,通过扩展增加其功能,这是Flask最重要的特点。Flask的两个主要核心应用是Werkzeug和模板引擎Jinja。
2.在体量上的区别
Flask:
小巧、灵活,让程序员自己决定定制哪些功能,非常适用于小型网站。
对于普通的工人来说将毛坯房装修为城市综合体还是很麻烦的,使用Flask来开发大型网站也一样,开发的难度较大,代码架构需要自己设计,开发成本取决于开发者的能力和经验。
Django:
大而全,功能极其强大,是Python web框架的先驱,用户多,第三方库极其丰富。
非常适合企业级网站的开发,但是对于小型的微服务来说,总有“杀鸡焉有宰牛刀”的感觉,体量较大,非常臃肿,定制化程度没有Flask高,也没有Flask那么灵活。
django和flask区别参考文档
Tornado 的核心是 ioloop 和 iostream 这两个模块,前者提供了一个高效的 I/O 事件循环,后者则封装了 一个无阻塞的 socket 。通过向 ioloop 中添加网络 I/O 事件,利用无阻塞的 socket ,再搭配相应的回调 函数,便可达到梦寐以求的高效异步执行。
中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。因为改变的是全局,所以需要谨慎实用,用不好会影响到性能。
中间件是介于 request 与 response 处理之间的一道处理过程,相对比较轻量级,并且在全局上改变 django 的输入与输出。
Middleware is a framework of hooks into Django’s request/response processing.
It’s a light, low-level “plugin” system for globally altering Django’s input or output.
django默认的中间件:
MIDDLEWARE = [
# 添加 django-cors-headers 使其可以进行 cors 跨域
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',#跨站请求伪造
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
自定义中间件:
中间件可以定义五个方法,分别是:(主要的是process_request和process_response)
1、process_request(self,request)
2、process_view(self, request, callback, callback_args, callback_kwargs)
3、process_template_response(self,request,response)
4、process_exception(self, request, exception)
5、process_response(self, request, response)
由此总结一下:
中间件的process_request方法是在执行视图函数之前执行的。
当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。
不同中间件之间传递的request都是同一个对像
多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。
process_view(self, request, view_func, view_args, view_kwargs)
该方法有四个参数
request是HttpRequest对象。
view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)
view_args是将传递给视图的位置参数的列表(无名分组分过来的值).
view_kwargs是将传递给视图的关键字参数的字典(有名分组分过来的值)。 view_args和view_kwargs都不包含第一个视图参数(request)。
Django会在调用视图函数之前调用process_view方法。
它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,Django不会调用适当的视图函数。 它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。
当最后一个中间的process_request到达路由关系映射之后,返回到中间件1的process_view,然后依次往下,到达views函数,最后通过process_response依次返回到达用户。
process_exception(self, request, exception)
该方法两个参数:
一个HttpRequest对象
一个exception是视图函数异常产生的Exception对象。
这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。
1、做IP访问频率限制
某些IP访问服务器的频率过高,进行拦截,比如限制每分钟不能超过20次。
2、URL访问过滤
如果用户访问的是login视图(放过)
如果访问其他视图,需要检测是不是有session认证,已经有了放行,没有返回login,这样就省得在多个视图函数上写装饰器了
3.项目中用到中间件的例子
CSRF_TOKEN跨站请求伪造
参考文档
中间件介绍文档
一个动态网站的基本权衡点就是,它是动态的。 每次用户请求页面,服务器会重新计算。从开销处理的角度来看,这比你读取一个现成的标准文件的代价要昂贵的多。这就是需要缓存的地方。
Django 自带了一个健壮的缓存系统来保存动态页面这样避免对于每次请求都重新计算。方便起见,Django 提供了不同级别的缓存粒度:可以缓存特定视图的输出、可以仅仅缓存那些很难生产出来的部分、或者可以缓存整个网站 Django 也能很好的配合那些“下游”缓存, 比如 Squid 和基于浏览器的缓存。这里有一些缓存不必要直接去控制但是可以提供线索, (via HTTPheaders)关于网站哪些部分需要缓存和如何缓存。
设置缓存:
缓存系统需要一些设置才能使用。 也就是说,你必须告诉他你要把数据缓存在哪里- 是数据库中,
文件系统或者直接在内存中。 这个决定很重要,因为它会影响你的缓存性能,是的,一些缓存类型要比其他的缓存类型更快速。你的缓存配置是通过 setting 文件的 CACHES 配置来实现的。 这里有 CACHES 所有可配置的变量值。
三种粒度缓存
1 中间件级别
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
CACHE_MIDDLEWARE_SECONDS=10
2 视图级别
from django.views.decorators.cache import cache_page
@cache_page(15)
def index(request):
import time
t=time.time()
return render(request,"index.html",locals())
3 局部缓存
{% load cache %}
...
...
{% cache 15 "time_cache" %}
缓存时间:{{ t }}
{% endcache %}
pip install django-redis
apt-get install redis-server
然后在settings.py 里面添加CACHES = {
'default': {
'BACKEND': 'redis_cache.cache.RedisCache',
'LOCATION': '127.0.0.1:6379',
"OPTIONS": {
"CLIENT_CLASS": "redis_cache.client.DefaultClient",
},
}
simple_tag
-参数任意,但是不能作为if条件判断的条件
filter
-参数最多只能有两个,但是可以作为if条件判断的条件
启用中间件
post 请求
验证码
表单中添加 csrf_token 标签
1、django的model转json对象。
1.1、单个modle转换,返回json对象:
sqlOrder = get_object_or_404(SqlOrder,id=request.GET.get("id"))
objJson = serialize('json',[sqlOrder])[1:-1]
{"model": "sqlapply.sqlorder", "pk": 2, "fields": {"work_id": "{now}{_ran}", "username": "admin", "status": 3, "type": 0, "backup": 0}}
注意:objJson[“fields”][“username”] 才能获得想要属性值。objJson[“pk”]获得主键值。
1.2、querySet转换:
json_data = serializers.serialize("json", MyModel.objects.all())
2、转换后的json对象作为子元素传递到前端:
2.1、将json对象转换成字符串。(单引号包含属性)。
objStr = json.loads(objJson)
2.2、组拼json对象。
response_data = {'statcode': '1', 'data':objStr}
2.3、返回前进行对象转换:
eturn HttpResponse(json.dumps(response_data))
2.4、前端接受并获取:
$.ajax({
url: "{% url 'sqlapply:auditSqlOrder' %}",
type:"GET",
data:{"id":id,"args":"getObjById"},
success:function(result){
res = jQuery.parseJSON(result); #关键代码!!!!解析一个 JSON 字符串'{"name":"John"}'为Json对象。
if (res["statcode"] == "1") {
objStr = res["data"];
console.log(objStr["fields"]["username"]);#获取方式1
alert(objStr.fields.username);#获取方式2
}
},
error:function(){
alert("访问异常,请截图联系管理员 \t\n ErrorNO:auditSqlOrder.getObjById")
}
});
补充知识:django 将model转换成想要的json格式
model:
class SysRole(models.Model):
id = models.CharField(db_column='ID', primary_key=True, max_length=50) # Field name made lowercase.
rolename = models.CharField(db_column='RoleName', max_length=50) # Field name made lowercase.
deion = models.CharField(db_column='Deion', max_length=200, blank=True, null=True) # Field name made lowercase.
querycode = models.CharField(db_column='QueryCode', max_length=200, blank=True, null=True) # Field name made lowercase.
isdisabled = models.CharField(db_column='IsDisabled') # Field name made lowercase. This field type is a guess.
def __unicode__(self):
return self.rolename
# 将属性和属性值转换成dict 列表生成式
def toDict(self):
return dict([(attr, getattr(self, attr)) for attr in [f.name for f in self._meta.fields]])#type(self._meta.fields).__name__
class Meta:
managed = False
db_table = 'sys_role'
# 遍历查询集 调用model属性转换成dict
def queryset_to_json(queryset):
obj_arr=[]
for o in queryset:
obj_arr.append(o.toDict())
return obj_arr
# 获取角色分页列表
def get_roles_page(self,_page,_limit):
_roles = SysRole.objects.all()[(int(_page)-1)*int(_limit):int(_page)*int(_limit)]
_count = SysRole.objects.all().count()
_dict_roles = tools.queryset_to_json(_roles)
_data_page_json = {}
_data_page_json['Rows']=_dict_roles
_data_page_json['Total']=_count
return json.dumps(_data_page_json,ensure_ascii=False)
在model上加入toDict方法 然后执行查询 得到queryset 遍历它 将queryset里的每个model执行他的todict方法 转换成字典格式 之后统一调用json.dumps方法转json
Django 从后台往前台传递数据时有多种方法可以实现。
最简单的后台是这样的:
from django.shortcuts import render
def main_page(request):
return render(request, ‘index.html‘)
这个就是返回index.html的内容,但是如果要带一些数据一起传给前台的话,该怎么办呢?
一 view -> HTML 使用Django模版
这里是这样:后台传递一些数据给html,直接渲染在网页上,不会有什么复杂的数据处理(如果前台要处理数据,那么就传数据给JS处理)
Django 代码:
from django.shortcuts import render
def main_page(request):
data = [1,2,3,4]
return render(request, ‘index.html‘, {‘data‘: data})
html使用 {{ }} 来获取数据
{{ data }}
可以对可迭代的数据进行迭代:
{% for item in data%}
{{ item }}
{% endfor %}
该方法可以传递各种数据类型,包括list,dict等等。
而且除了 {% for %} 以外还可以进行if判断,大小比较等等。具体的用法读者可以自行搜索。
二 view-> JavaScript
如果数据不传给html用,要传给js用,那么按照上文的方式写会有错误。
需要注意两点:
views.py中返回的函数中的值要用 json.dumps() 处理
在网页上要加一个 safe 过滤器。
代码:
views.py
# -*- coding: utf-8 -*-
import json
from django.shortcuts import render
def main_page(request):
list = [‘view‘, ‘Json‘, ‘JS‘]
return render(request, ‘index.html‘, {
‘List‘: json.dumps(list),
})
JavaScript部分:
var List = {{ List|safe }};
三 JavaScript Ajax 动态刷新页面
这个标题的意思是:网页前台使用Ajax发送请求,后台处理数据后返回数据给前台,前台不刷新网页动态加载数据
Django 代码:
def scene_update_view(request):
if request.method == "POST":
name = request.POST.get(‘name‘)
status = 0
result = "Error!"
return HttpResponse(json.dumps({
"status": status,
"result": result
}))
JS 代码:
function getSceneId(scece_name, td) {
var post_data = {
"name": scece_name,
};
$.ajax({
url: {% url ‘scene_update_url‘ %},
type: "POST",
data: post_data,
success: function (data) {
data = JSON.parse(data);
if (data["status"] == 1) {
setSceneTd(data["result"], scece_name, td);
} else {
alert(data["result"]);
}
}
});
}
JS 发送ajax请求,后台处理请求并返回status, result
在 success: 后面定义回调函数处理返回的数据,需要使用 JSON.parse(data)
Article.objects.values_list('comment_id', flat=True).distinct()
vs
Article.objects.values('comment_id').distinct()
有什么区别?
该values()方法返回包含字典的QuerySet:
该values_list()方法返回一个包含元组的QuerySet:
如果您使用values_list()单个字段,则可以使用flat=True返回单个值的QuerySet而不是1个元组:
runserver 方法是调试Django 时经常用到的运行方式,它使用Django 自带的WSGI Server 运行,主要在测试和开发中使用,并且runserver 开启的方式也是单进程。
uWSGI 是一个Web 服务器,它实现了WSGI 协议(web服务网关接口)、uwsgi(线路协议)、http 等协议。注意uwsgi 是一种通信协议,而uWSGI 是实现uwsgi 协议和WSGI 协议的Web 服务器。uWSGI 具有超快的性能、低内存占用和多app 管理等优点,并且搭配着Nginx 就是一个生产环境了,能够将用户访问请求与应用app 隔离开,实现真正的部署。相比来讲,支持的并发量更高,方便管理多进程,发挥多核的优势,提升性能。
Nginx
1 安全(Nginx 作为专业服务器,暴露在公网相对比较安全)
2 能更好地处理静态资源(一些http request header)
3 Nginx也可以缓存一些动态内容Nginx可以更好地配合CDN
4 可以进行多台机器的负载均衡
当然,如果访问量不大,其实也没必要加个Nginx,uWSGI足以胜任。
nginx 和uWISG 服务器之间如何配合工作的?
(1)首先浏览器发起http 请求到nginx 服务器
(2)Nginx 根据接收到请求包,进行url 分析,判断访问的资源类型,如果是静态资源,直接读取静态资源返回给浏览器
(3)如果请求的是动态资源就转交给uwsgi服务器,uwsgi 服务器根据自身的uwsgi 和WSGI 协议,找到对应的Django 框架。
(4)Django 框架下的应用进行逻辑处理后,将返回值发送到uwsgi 服务器,然后uwsgi 服务器再返回给nginx。
(5)最后nginx将返回值返回给浏览器进行渲染显示给用户。
1.1.为方便理解,uWSGI ,WSGI和uwsgi在网站项目流程图中的功能如下:
1.2.网站项目结构图
2.uWSGI ,WSGI和uwsgi的区别
WSGI:
WSGI,全称 Web Server Gateway Interface,或者 Python Web Server Gateway Interface ,是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。也可以认为WSGI是一种通信协议。自从 WSGI 被开发出来以后,许多其它语言中也出现了类似接口。
WSGI 的官方定义是,the Python Web Server Gateway Interface。从名字就可以看出来,这东西是一个Gateway,也就是网关。网关的作用就是在协议之间进行转换。
WSGI 是作为 Web 服务器与 Web 应用程序或应用框架之间的一种低级别的接口,以提升可移植 Web 应用开发的共同点。WSGI 是基于现存的 CGI 标准而设计的。
WSGI是Web 服务器(uWSGI)与 Web 应用程序或应用框架(Django)之间的一种低级别的接口
uWSGI:
uWSGI是一个Web服务器,它实现了WSGI协议、uwsgi、http等协议。Nginx中HttpUwsgiModule的作用是与uWSGI服务器进行交换。
uwsgi:
uwsgi是服务器和服务端应用程序的一种协议,规定了怎么把请求转发给应用程序和返回; uwsgi是一种线路协议而不是通信协议,在此常用于在uWSGI服务器与其他网络服务器的数据通信。
1. .uWSGI 是一个 Web 服务器,它实现了 WSGI 协议、uwsgi、http 等协议。
Nginx 中HttpUwsgiModule 的作用是与 uWSGI 服务器进行交换。WSGI 是一种 Web 服务器网关接口。它是一个 Web 服务器(如nginx,uWSGI 等服务器)与 web 应用(如用 Flask 框架写的程序)通信的一种规范。
- WSGI / uwsgi / uWSGI 这三个概念的区分。
WSGI 是一种通信协议。
uwsgi 是一种线路协议而不是通信协议,在此常用于在 uWSGI 服务器与其他网络服务器的数据通信。
uWSGI 是实现了 uwsgi 和 WSGI 两种协议的 Web 服务器。
2. nginx 是一个开源的高性能的 HTTP 服务器和反向代理:
1.作为 web 服务器,它处理静态文件和索引文件效果非常高;
2.它的设计非常注重效率,最大支持 5 万个并发连接,但只占用很少的内存空间;
3.稳定性高,配置简洁;
4.强大的反向代理和负载均衡功能,平衡集群中各个服务器的负载压力应用。
3. nginx 和 uWISG 服务器之间配合工作的过程
首先浏览器发起 http 请求到 nginx 服务器,Nginx 根据接收到请求包,进行 url 分析,判断访问的资源类型,如果是静态资源,直接读取静态资源返回给浏览器,如果请求的是动态资源就转交给 uwsgi服务器,uwsgi 服务器根据自身的 uwsgi 和 WSGI 协议,找到对应的 Django 框架,Django 框架下的应用进行逻辑处理后,将返回值发送到 uwsgi 服务器,然后 uwsgi 服务器再返回给 nginx,最后 nginx将返回值返回给浏览器进行渲染显示给用户。
【Django】Uwsgi+Nginx+Django2.0+Python3.7实现高并发,多线程,高性能
Django预防CSRF攻击的方法是在用户提交的表单中加入一个csrftoken的隐含值,这个值和服务器中保存的csrftoken的值相同,这样做的原理如下:
1、在用户访问django的可信站点时,django反馈给用户的表单中有一个隐含字段csrftoken,这个值是在服务器端随机生成的,每一次提交表单都会生成不同的值
2、当用户提交django的表单时,服务器校验这个表单的csrftoken是否和自己保存的一致,来判断用户的合法性
3、当用户被csrf攻击从其他站点发送精心编制的攻击请求时,由于其他站点不可能知道隐藏的csrftoken字段的信息这样在服务器端就会校验失败,攻击被成功防御
具体配置如下:
template中添加{%csrf_token%}标签
简单来说就是: 你访问了信任网站 A,然后 A 会用保存你的个人信息并返回给你的浏览器一个cookie,然后呢,在 cookie 的过期时间之内,你去访问了恶意网站 B,它给你返回一些恶意请求代码,要求你去访问网站 A,而你的浏览器在收到这个恶意请求之后,在你不知情的情况下,会带上保存在本地浏览器的 cookie 信息去访问网站 A,然后网站 A 误以为是用户本身的操作,导致来自恶意网站 C 的攻击代码会被执:发邮件,发消息,修改你的密码,购物,转账,偷窥你的个人信息,导致私人信息泄漏和账户财产安全收到威胁。
对一个后端开发程序员来说,提升性能指标主要有两个一个是并发数,另一个是响应时间网站性能的优化一般包括 web 前端性能优化,应用服务器性能优化,存储服务器优化。
对前端的优化主要有:
(1)减少 http 请求,减少数据库的访问量,比如使用雪碧图。
(2)使用浏览器缓存,将一些常用的 css,js,logo 图标,这些静态资源缓存到本地浏览器,通过设置 http 头中的 cache-control 和 expires 的属性,可设定浏览器缓存,缓存时间可以自定义。
(3)对 html,css,java 文件进行压缩,减少网络的通信量。
对我个人而言,我做的优化主要是以下三个方面:
(1)合理的使用缓存技术,对一些常用到的动态数据,比如首页做一个缓存,或者某些常用的数据做个缓存,设置一定得过期时间,这样减少了对数据库的压力,提升网站性能。
(2)使用 celery 消息队列,将耗时的操作扔到队列里,让 worker 去监听队列里的任务,实现异步操作,比如发邮件,发短信。
(3)就是代码上的一些优化,补充:nginx 部署项目也是项目优化,可以配置合适的配置参数,提升效率,增加并发量。
(4)如果太多考虑安全因素,服务器磁盘用固态硬盘读写,远远大于机械硬盘,这个技术现在没有普及,主要是固态硬盘技术上还不是完全成熟, 相信以后会大量普及。
(5)另外还可以搭建服务器集群,将并发访问请求,分散到多台服务器上处理。
(6)最后就是运维工作人员的一些性能优化技术了。
优势
1.轻量;(Micro Framework)
2.简洁
3.扩展性好*
4 第三方库的选择面广,开发时可以结合自己喜欢用的轮子,也可以结合强大的python库
5.核心(werzeug和jinja2),jinja2就是指模板引擎。
应用场景:
适用于小型网站,适用于开发web服务的API;
开发大型网站也毫无压力,但是代码架构需要开发者自己设计;
Flask和Nosql数据库的结合优于django
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('mysql://root:*****@127.0.0.1/database?charset=utf8')
DB_Session = sessionmaker(bind=engine)
session = DB_Session()
session.execute('alter table mytablename drop column mycolumn ;')
sqlalchemy设置编码字符集,一定要在数据库访问的URL上增加’charset=utf8’,否则数据库的连接就不是’utf8’的编码格式
engine = create_engine('mysql://root:root@localhost:3306/test2?charset=utf8',echo=True)
engine = create_engine("mysql+pymysql://root:[email protected]:3306/sqldb01?charset=utf8")
class UserType(Base):
__tablename__ = 'usertype' i
d = Column(Integer, primary_key=True)
caption = Column(String(50), default='管理员')
# 添加配置设置编码
__table_args__ = {
'mysql_charset':'utf8'
}
这样生成的SQL语句就自动设置数据表编码为utf8了,__table_args__还可设置存储引擎、外键约束等等信息。
内置:
每次有请求过来的时候,flask 会先创建当前线程或者进程需要处理的两个重要上下文对象,把它们保存到隔离的栈里面,这样视图函数进行处理的时候就能直接从栈上获取这些信息。
g 相当于一次请求的全局变量,当请求进来时将g和current_app封装为一个APPContext类,在通过LocalStack将Appcontext放入Local中,取值时通过偏函数,LocalStack、local中取值,响应时将local中的g数据删除
Flask的默认session利用了Werkzeug的SecureCookie,把信息做序列化(pickle)后编码(base64),放到cookie里了。
过期时间是通过cookie的过期时间实现的。
为了防止cookie内容被篡改,session会自动打上一个叫session的hash串,这个串是经过session内容、SECRET_KEY计算出来的,看得出,这种设计虽然不能保证session里的内容不泄露,但至少防止了不被篡改。
RESTful API接口设计风格
RESTful API接口设计风格参考文档
REST API(表现层状态转化)
简单理解为:URL定位资源,用HTTP动词(GET,POST,DELETE,PUT)描述操作。
说明
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
规范的问题
因为多人开发时,会导致处理请求方式(get、post乱用),接口不统一(接口名写法千差万别),会导致各种状况都有,后期维护是个大问题,再就是请求方式也不对。
所以RESTFUL API的结构设计就诞生了
- 接口命名规范
1. 域名:
//将api部署在专用域名下:
http://api.example.com
/ /或者将api放在主域名下:
http://www.example.com/api/
2. 版本:
将API的版本号放在url中。
http://www.example.com/app/1.0/
http://www.example.com/app/1.2/
3.路径:
路径表示API的具体网址。每个网址代表一种资源。
资源作为网址,网址中不能有动词只能有名词,一般名词要与数据库的表名对应。
一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。
http://www.example.com/app/1.0/lists
http://www.example.com/app/1.0/names
4. 使用标准的HTTP方法
使用标准的HTTP方法:对于资源的具体操作类型,由HTTP动词表示。 常用的HTTP动词有四个。
GET :从服务器取出资源(一项或多项)
POST :在服务器新建资源。
PUT :在服务器更新资源。
DELETE :从服务器删除资源。
** 5. 过滤信息**
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
eg:
?limit=10:指定返回记录的数量
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
…
示例:
一个URl实现增删改查所有操作,具体靠http请求头部信息判断
//CRUD(增删改查) 操作学生用户的接口 /rest/1.0/userinfo
//增加一个id为1的用户
post /rest/1.0/userinfo data: {id:1,name:'张三'}
//删除id为1的用户
delete /rest/1.0/userinfo?id=1
//修改id为1的用户
put /rest/1.0/userinfo?id=1 data:{id:1,name:'李四'}
//查询所有用户
get /rest/1.0/userinfo
//查询id为1的用户信息
get /rest/1.0/userinfo?id=1
6.状态码
服务器向用户返回的状态码和提示信息,常用的有:
200 OK :服务器成功返回用户请求的数据
201 CREATED :用户新建或修改数据成功。
202 Accepted:表示请求已进入后台排队。
400 INVALID REQUEST :用户发出的请求有错误。
401 Unauthorized :用户没有权限。
403 Forbidden :访问被禁止。
404 NOT FOUND :请求针对的是不存在的记录。
406 Not Acceptable :用户请求的的格式不正确。
500 INTERNAL SERVER ERROR :服务器发生错误。
7. 错误信息:
一般来说,服务器返回的错误信息,以键值对的形式返回。
{
error: 'Invalid API KEY'
}
8. 响应结果
针对不同结果,服务器向客户端返回的结果应符合以下规范。
#返回商品列表
GET http://www.example.com/goods
#返回单个商品
GET http://www.example.com/goods/cup
#返回新生成的商品
POST http://www.example.com/goods
#返回一个空文档
DELETE http://www.example.com/goods
9. 使用链接关联相关的资源
在返回响应结果时提供链接其他 API 的方法,使客户端很方便的获取相关联的信息。
10. 其他
服务器返回的数据格式,应该尽量使用 JSON,避免使用 XML。
常见的几种接口形式
API作为应用程序编程接口,可以使用不同的编程语言进行API的开发,另外接口的表现形式也不同,现在最常用的接口形式有以下这些:
常见的几种接口形式参考文档
1、HTTP 接口(RESTful)
基于HTTP协议开发的接口现在应用是最为广泛的,这类API使用起来简单明了,因为它是轻量级的、跨平台、跨语言的,但凡是第三方提供的API都会有HTTP版本的接口。
RESTful API也是基于HTTP协议的,只不过RESTful它并不是一种规范,它是一种设计准则,用不同的HTTP动词(GET、POST、DELETE、PUT等)来表达不同的请求。
2、RPC 接口
RPC技术是指远程过程调用,它本质上是一种Client/Server模式,可以像调用本地方法一样去调用远程服务器上的方法,它支持多种协议(如:HTTP、TCP、UDP、自定协议)和多种数据传输方式(如:Json、XML、Binary、Protobuf等)。
3、Web Service 接口
Web Service其实是一种概念,我们可以将以WEB形式提供的服务称为Web Service,所以像RESTful、XML-RPC、SOAP等都可以当成是Web Service的一种实现方式。
不过Web Service接口和HTTP接口存在一些细小区别就是,Web Service接口支持更复杂的对象,而HTTP接口更多的就是传输字符串或者JSON文本
http/https协议,tcp/ip协议
一般来讲,前端不会给后端接口,而是后端给前端接口的情况比较普遍。一些人可能不理解接口和前端开发的关系,其实不合适的接口设计会极大地影响用户的页面体验。
利用接口文档,使用ajax进行前后端交互
1、前端请求数据URL由谁来写、
2. 接口文档主要由谁来写
3. 前端与后端交互的书据格式
4. 前端与后台的交互原理
5. 前端请求参数的形式
6. 前端应该告知后台哪些有效信息,后台才能返回前端想要的数据
7. 前端如何把页面信息有效的传达给后台,以及后台如何取得这些数据
8. 前端应该如何回拒一些本不属于自己做的一些功能需求或任务
9. 当前端在调用数据接口时,发现有些数据不是我们想要的,那么前端该怎么办
10. 为什么需要在请求的时候传入参数
前端和后端是如何交互的:前后端是如何交互的
1.域名
应该尽量将API部署在专用域名之下。
https://api.example.com
如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。
https://example.org/api/
2. 版本(Versioning)
应该将API的版本号放入URL。
http://www.example.com/app/1.0/foo
http://www.example.com/app/1.1/foo
http://www.example.com/app/2.0/foo
3. 路径(Endpoint)
路径又称"终点"(endpoint),表示API的具体网址,每个网址代表一种资源(resource)
(1) 资源作为网址,只能有名词,不能有动词,而且所用的名词往往与数据库的表名对应。
举例来说,以下是不好的例子:
/getProducts
/listOrders
/retreiveClientByOrder?orderId=1
对于一个简洁结构,你应该始终用名词。 此外,利用的HTTP方法可以分离网址中的资源名称的操作。
GET /products :将返回所有产品清单
POST /products :将产品新建到集合
GET /products/4 :将获取产品 4
PATCH(或)PUT /products/4 :将更新产品 4
(2) API中的名词应该使用复数。无论子资源或者所有资源。
举例来说,获取产品的API可以这样定义
获取单个产品:http://127.0.0.1:8080/AppName/rest/products/1
获取所有产品: http://127.0.0.1:8080/AppName/rest/products
3. HTTP动词
对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面四个(括号里是对应的SQL命令)。
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
DELETE(DELETE):从服务器删除资源。
还有三个不常用的HTTP动词。
PATCH(UPDATE):在服务器更新(更新)资源(客户端提供改变的属性)。
HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
4. 过滤信息(Filtering)
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数。
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件
6. 状态码(Status Codes)
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。
200 OK - [GET]:服务器成功返回用户请求的数据
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
7. 错误处理(Error handling)
如果状态码是4xx,服务器就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
{
error: "Invalid API key"
}
8. 返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
9. 其他
服务器返回的数据格式,应该尽量使用JSON,避免使用XML。
在开发REST API接口时,我们在视图中需要做的最核心的事是:
将数据库数据序列化为前端所需要的格式,并返回;
将前端发送的数据反序列化为模型类对象,并保存到数据库中。
restful其实就是一套编写接口的协议,协议规定如何编写以及如何设置返回值、状态码等信息。
最显著的特点:
restful: 给用户一个url,根据method不同在后端做不同的处理,比如:post 创建数据、get获取数据、put和patch修改数据、delete删除数据。
no rest: 给调用者很多url,每个url代表一个功能,比如:add_user/delte_user/edit_user/
当然,还有协议其他的,比如:
版本,来控制让程序有多个版本共存的情况,版本可以放在 url、请求头(accept/自定义)、GET参数
状态码,200/300/400/500
url中尽量使用名词,restful也可以称为“面向资源编程”
api标示:
api.YueNet.com
www.YueNet.com/api/
1.主扩展模式:
一般应用于提取不同类型的对象的共同特征。比如学校当中,对于上课而言分为
老师和学生,但对于食堂大妈或者门卫大爷而言,就看你是不是校内人员。这是一种包含
关系。即校内人员包括 学生、老师、其他工作人员。如果做一个签到系统,就设定校内
人员为user表,老师、学生之类的单独成表,但是都维护同样的userid同时作为二者的主
键。使之称为1对1的关系。这种模式就是主扩展模式。
扩展表的主键既是扩展表的主键也是主表的外键
2.主从模式
主从模式的应用场景最多。是典型的一对多的关系。比如贴吧的实现,整个
吧就是一个主表。而贴吧有许多的从表就是不同楼主发的帖子,而每个帖子有用很多从表
那就是每个楼所对应的信息。
3.名值关系
主要处理系统设计阶段还不能完全确定的属性的对象。这些对象的属性在系统运
行时会有很大的变更,或者是多个对象之间的属性存在很大的差异。
比如说一个学生的表,记录了一些学生必须有的属性:年龄身高体重姓名什么的。但是突然有
一天有一个人穿越了,他就需要一个剑术值的数据。通常需要额外两个表来存储这种不确定是否
会用会有的属性。
首先需要一个属性模版表,就是不管这个属性属于谁,属于何物,何时,
我只是证明有这么一条额外属性而存在。那么上述的例子当中,属性模板表当中就
需要添加一条属性:(属性代码一般给属性分类用)
ID 1 属性代码 1001 属性名称 剑术值
但是具体剑术值是多少,这个表不去讨论。存储数据的表称为额外属性表,这个
表存储的字段分别标识
1.这条数据属于哪个人、物(角色id)
2.这条数据是什么属性 (属性模板ID)
3.属性的具体值是多少 (data)
4.多对多关系
多对多模式,也是比较常见的一种数据库设计模式,它所描述的两个对象不分主
次、地位对等、互为一对多的关系。对于A表来说,一条记录对应着B表的多条记录,反过
来对于B表来说,一条记录也对应着A表的多条记录,这种情况就是“多对多模式”。
这个主要可以细分成两种情况。取决于关联表有没有业务需求。
原文:数据库四种设计模式_数据库设计模式_cczx139的博客-CSDN博客
1.在序列化与反序列化时,虽然操作的数据不尽相同,但是执行的过程却是相似的,也就是说这部分代码是可以复用简化编写的。
2.在开发REST API的视图中,虽然每个视图具体操作的数据不同,但增、删、改、查的实现流程基本套路化,所以这部分代码也是可以复用简化编写的:
1.客户端-服务端分离
优点:提高用户界面的便携性,通过简化服务器提高可伸缩性….
2.无状态(Stateless):从客户端的每个请求要包含服务器所需要的所有信息
优点:提高可见性(可以单独考虑每个请求),提高了可靠性(更容易从局部故障中修复),提高可扩展性(降低了服务器资源使用)
3.缓存(Cachable):服务器返回信息必须被标记是否可以缓存,如果缓存,客户端可能会重用之前的信息发送请求
优点:减少交互次数,减少交互的平均延迟
4.统一接口
优点:提高交互的可见性,鼓励单独改善组件
5.支持按需代码(Code-On-Demand 可选)
优点:提高可扩展性
序列化器的作用:
hbook = serializers.PrimaryKeyRelatedField(label='图书', read_only=True)
2) StringRelatedField
此字段将被序列化为关联对象的字符串表示方式(即__str__方法的返回值)
hbook = serializers.StringRelatedField(label='图书')
3)使用关联对象的序列化器
hbook = BookInfoSerializer()
1.验证
使用序列化器进行反序列化时,需要对数据进行验证后,才能获得验证成功的数据或保存成模型类对象。
在获取反序列化的数据前,必须调用is_valid()方法进行验证,验证成功返回True,否则返回False。
验证失败,可以通过序列化器对象的错误属性获取错误信息,返回字典,包含了字段和字段的错误。如果是非字段错误,可以通过修改REST架构配置中的NON_FIELD_ERRORS_KEY来控制错误字典中的键名。
验证成功,可以通过序列化器对象的validated_data属性获取数据。
在定义序列化器时,指定每个分段的序列化类型和选项参数,本身就是一种验证行为。
1)validate_
对
class BookInfoSerializer(serializers.Serializer):
"""图书数据序列化器"""
...
def validate_btitle(self, value):
if 'django' not in value.lower():
raise serializers.ValidationError("图书不是关于Django的")
return value
测试
from booktest.serializers import BookInfoSerializer
data = {'btitle': 'python'}
serializer = BookInfoSerializer(data=data)
serializer.is_valid() # False
serializer.errors
# {'btitle': [ErrorDetail(string='图书不是关于Django的', code='invalid')]}
2)验证
在序列化器中需要同时对多个分段进行比较验证时,可以定义validate方法来验证,
3)验证者
在上方中添加validators选项参数,也可以补充验证行为
2.保存
如果在验证成功后,想要通过validated_data完成数据对象的创建,可以通过实现create()和update()两个方法来实现。
说明:
1)在对序列化器进行save()保存时,可以额外传递数据,这些数据可以在create()和update()中的validated_data参数获取到
serializer.save(owner=request.user)
如果我们想要使用序列化器对应的是Django的模型类,DRF为我们提供了ModelSerializer模型类序列化器来帮助我们快速创建一个Serializer类。
ModelSerializer与常规的Serializer相同,但提供了:
class BookInfoSerializer(serializers.ModelSerializer):
"""图书数据序列化器"""
class Meta:
model = BookInfo
fields = '__all__'
class BookInfoSerializer(serializers.ModelSerializer):
"""图书数据序列化器"""
class Meta:
model = BookInfo
fields = ('id', 'btitle', 'bpub_date')
2)使用排除可以明确排除掉某些细分
class BookInfoSerializer(serializers.ModelSerializer):
"""图书数据序列化器"""
class Meta:
model = BookInfo
exclude = ('image',)
3)显示规格指标,如:
class HeroInfoSerializer(serializers.ModelSerializer):
hbook = BookInfoSerializer()
class Meta:
model = HeroInfo
fields = ('id', 'hname', 'hgender', 'hcomment', 'hbook')
4)规范的预设长度
可以通过read_only_fields指定的扩展名,即仅用于序列化输出的细分
class BookInfoSerializer(serializers.ModelSerializer):
"""图书数据序列化器"""
class Meta:
model = BookInfo
fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment')
read_only_fields = ('id', 'bread', 'bcomment')
3.添加额外参数
我们可以使用extra_kwargs参数为ModelSerializer添加或修改初始化的选项参数
class BookInfoSerializer(serializers.ModelSerializer):
"""图书数据序列化器"""
class Meta:
model = BookInfo
fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment')
extra_kwargs = {
'bread': {'min_value': 0, 'required': True},
'bcomment': {'min_value': 0, 'required': True},
}
这个类属于rest framework中顶层类,内部帮助我们实现了只是基本功能:认证、权限、频率控制,但凡是数据库、分页等操作都需要手动去完成,比较原始。
class GenericAPIView(APIView)
def post(...):
pass
b. 继承 GenericViewSet(ViewSetMixin, generics.GenericAPIView)
如果继承它之后,路由中的as_view需要填写对应关系 .as_view({‘get’:’list’,’post’:’create’})
在内部也帮助我们提供了一些方便的方法:
注意:要设置queryset字段,否则会跑出断言的异常。
# 只提供增加功能
class TestView(GenericViewSet):
serializer_class = XXXXXX
def create(self,*args,**kwargs):
pass # 获取数据并对数据进行操作
c. 继承
对数据库和分页等操作不用我们在编写,只需要继承相关类即可。
示例:只提供增加功能
class TestView(mixins.CreateModelMixin,GenericViewSet):
serializer_class = XXXXXXX
Redis的过期策略
Redis中同时使用了惰性过期和定期过期两种过期策略。
LRU (Least recently used 最后使用时间策略)
LFU (Least Frequently Used 最少使用次数策略)
常用的淘汰策略
RDB 快照持久化
redis可以将内存中的数据写入磁盘进行持久化。在进行持久化时,redis会创建子进程来执行。
AOF 追加文件持久化
redis可以将执行的所有指令追加记录到文件中持久化存储,这是redis的另一种持久化机制。
从快照中导出json:
redis快照文件dump.rdb解析工具--redis-rdb-tools_51CTO博客_redis dump文件
从AOF中提取命令:
https://blog.csdn.net/weixin_44107015/article/details/103130732
redis持久化RDB与AOF原理简介、配置、从持久化中恢复数据-CSDN博客
**RDB:**在指定的时间间隔能对你的数据进行快照存储。
**AOF:**记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。
1、AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
2、同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整
MongoDB 和 Redis 都是 NoSQL,采用结构型数据存储。二者在使用场景中,存在一定的区别,
这也主要由于二者在内存映射的处理过程,持久化的处理方法不同。MongoDB 建议集群部署,更多的考虑到集群方案,Redis 更偏重于进程顺序写入,虽然支持集群,也仅限于主-从模式。
Redis 优点:
a) 读写性能优异
b) 支持数据持久化,支持 AOF 和 RDB 两种持久化方式
c) 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
d) 数据结构丰富:数据结构丰富:支持 string、hash、set、sortedset、list 等数据结构。
缺点:
e) Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的 IP 才能恢复。
f) 主机宕机,宕机前有部分数据未能及时同步到从机,切换 IP 后还会引入数据不一致的问题,降低了系统的可用性。
g) Redis的主从复制采用全量复制,复制过程中主机会fork出一个子进程对内存做一份快照,并将子进程的内存快照保存为文件发送给从机,这一过程需要确保主机有足够多的空余内存。若快照文件较大,对集群的服务能力会产生较大的影响,而且复制过程是在从机新加入集群或者从机和主机网络断开重连时都会进行,也就是网络波动都会造成主机和从机间的一次全量的数据复制,这对实际的系统运营造成了不小的麻烦。
h) Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
MongoDB 优点:
a)弱一致性(最终一致),更能保证用户的访问速度
b)文档结构的存储方式,能够更便捷的获取数
c)内置 GridFS,高效存储二进制大对象 (比如照片和视频)
d)支持复制集、主备、互为主备、自动分片等特性
e)动态查询
f)全索引支持,扩展到内部对象和内嵌数组
缺点:
a)不支持事务
b)MongoDB 占用空间过大
c)维护工具不够成熟
数据类型:string,list,hash,set,zset
1. 常用命令
String
记录字符串/整数/浮点数
命令
set 添加/修改数据
get 获取数据
mset 添加多个数据
mget 获取多个数据
incr 计数加1
decr 计数减1
incrby 计数加n
键命令
适用于所有的类型命令
del 删除数据
exists 判断数据是否存在
expire 设置过期时间
ttl 获取剩余时间
keys 查询满足条件的键
hash
类似 字典 的结构
命令
hset 添加字段
hget 获取字段
hmset 添加多个字段
hmget 获取多个字段
hdel 删除字段
数据类型:list,hash,set,zset
list
是一个双向链表
命令
lpush 从左侧追加元素
lrange 从左侧遍历元素
rpush 从右侧追加元素
lset 从左侧修改元素
lpop 从左侧删除元素
rpop 从右侧删除元素
zset
有序集合, 按照分数(score)进行排序
命令
zadd 添加/修改元素
zrange 遍历元素(按分数从小到大)
zrevrange 反向遍历元素(从大到小)
zrangebyscore 遍历指定分数范围的元素
zscore 查询元素的分数
zrem 删除元素
zincrby 元素的分数计数加n
set
无序集合 无序+去重
命令
sadd 添加元素
smembers 遍历元素
sismember 判断是否包含
srem 删除元素
一、优化的一些建议
1、尽量使用短的key
当然在精简的同时,不要为了key的“见名知意”。对于value有些也可精简,比如性别使用0、1。
2、避免使用keys *
keys *, 这个命令是阻塞的,即操作执行期间,其它任何命令在你的实例中都无法执行。当redis中key数据量小时到无所谓,数据量大就很糟糕了。所以我们应该避免去使用这个命令。可以去使用SCAN,来代替。
3、在存到Redis之前先把你的数据压缩下
redis为每种数据类型都提供了两种内部编码方式,在不同的情况下redis会自动调整合适的编码方式。
4、设置key有效期
我们应该尽可能的利用key有效期。比如一些临时数据(短信校验码),过了有效期Redis就会自动为你清除!
5、选择回收策略(maxmemory-policy)
当Redis的实例空间被填满了之后,将会尝试回收一部分key。根据你的使用方式,强烈建议使用 volatile-lru(默认) 策略——前提是你对key已经设置了超时。但如果你运行的是一些类似于 cache 的东西,并且没有对 key 设置超时机制,可以考虑使用 allkeys-lru 回收机制,具体讲解查看 。maxmemory-samples 3 是说每次进行淘汰的时候 会随机抽取3个key 从里面淘汰最不经常使用的(默认选项)。
maxmemory-policy 六种方式 :
volatile-lru:只对设置了过期时间的key进行LRU(默认值)
allkeys-lru : 是从所有key里 删除 不经常使用的key
volatile-random:随机删除即将过期key
allkeys-random:随机删除
volatile-ttl : 删除即将过期的
noeviction : 永不过期,返回错误
6、使用bit位级别操作和byte字节级别操作来减少不必要的内存使用
bit位级别操作:GETRANGE, SETRANGE, GETBIT and SETBIT
byte字节级别操作:GETRANGE and SETRANGE
7、尽可能地使用hashes哈希存储
8、当业务场景不需要数据持久化时,关闭所有的持久化方式可以获得最佳的性能
数据持久化时需要在持久化和延迟/性能之间做相应的权衡.
9、想要一次添加多条数据的时候可以使用管道
10、限制redis的内存大小(64位系统不限制内存,32位系统默认最多使用3GB内存)
数据量不可预估,并且内存也有限的话,尽量限制下redis使用的内存大小,这样可以避免redis使用swap分区或者出现OOM错误。(使用swap分区,性能较低,如果限制了内存,当到达指定内存之后就不能添加数据了,否则会报OOM错误。可以设置maxmemory-policy,内存不足时删除数据)
11、SLOWLOG [get/reset/len]
slowlog-log-slower-than 它决定要对执行时间大于多少微秒(microsecond,1秒 = 1,000,000 微秒)的命令进行记录。
slowlog-max-len 它决定 slowlog 最多能保存多少条日志,
当发现redis性能下降的时候可以查看下是哪些命令导致的。
参考文档:redis优化方案总结
基本语法
缓存穿透:
缓存只是为了缓解数据库压力而添加的一层保护层,当从缓存中查询不到我们需要的数据就要去数据库中查询了。如果被黑客利用,频繁去访问缓存中没有的数据,那么缓存就失去了存在的意义,瞬间所有请求的压力都落在了数据库上,这样会导致数据库连接异常。
方案:
约定:对于返回为NULL的依然缓存,对于抛出异常的返回不进行缓存,注意不要把抛异常的也给缓存了。采用这种手段的会增加我们缓存的维护成本,需要在插入缓存的时候删除这个空缓存,当然我们可以通过设置较短的超时时间来解决这个问题。
方案二,布隆过滤器
缓存雪崩:
缓存雪崩是指缓存不可用或者大量缓存由于超时时间相同在同一时间段失效,大量请求直接访问数据库,数据库压力过大导致系统雪崩。
解决方案:
1、给缓存加上一定区间内的随机生效时间,不同的key设置不同的失效时间,避免同一时间集体失效。比如以前是设置10分钟的超时时间,那每个Key都可以随机8-13分钟过期,尽量让不同Key的过期时间不同。
2、采用多级缓存,不同级别缓存设置的超时时间不同,及时某个级别缓存都过期,也有其他级别缓存兜底。
3、利用加锁或者队列方式避免过多请求同时对服务器进行读写操作。
登录数据库: mysql -uroot -p
退出数据库: quit 或者 exit 或者 ctr + d
创建数据库: create database 数据库名 charset=utf8;
使⽤数据库: use 数据库名;
删除数据库: drop database 数据库名;
创建表: create table 表名(字段名 字段类型 约束, ...);
修改表-添加字段: alter table 表名 add 字段名 字段类型 约束
修改表-修改字段类型: alter table 表名 modify 字段名 字段类型 约束
修改表-修改字段名和字段类型: alter table 表名 change 原字段名 新字段名 字段类型 约束
修改表-删除字段: alter table 表名 drop 字段名;
删除表: drop table 表名;
查询数据: select * from 表名; 或者 select 列1,列2,... from 表名;
插⼊数据: insert into 表名 values (...) 或者 insert into 表名 (列1,...) values(值1,...)
修改数据: update 表名 set 列1=值1,列2=值2... where 条件
删除数据: delete from 表名 where 条件
join 是两张表根据条件相同的部分合并生成一个记录集。
SELECT Websites.id, Websites.name, access_log.count, access_log.date
FROM Websites
INNER JOIN access_log
ON Websites.id=access_log.site_id;
union是产生的两个记录集(字段要一样的)并在一起,成为一个新的记录集 。
其中两种不同的用法是UNION和UNION ALL,区别在于UNION从结果集中删除重复的行。如果使用UNION ALL 将包含所有行并且将不删除重复的行。
UNION和UNION ALL的区别:
union 检查重复
union all 不做检查
比如 select 'a' union select 'a' 输出就是一行 a
比如 select 'a' union all select 'a' 输出就是两行 a
主要 MyISAM 与 InnoDB 两个引擎,其主要区别如下:
InnoDB 支持事务,MyISAM 不支持,这一点是非常之重要。事务是一种高级的处理方式,如在一些列增删改中只要哪个出错还可以回滚还原,而 MyISAM 就不可以了;
MyISAM 适合查询以及插入为主的应用,InnoDB 适合频繁修改以及涉及到安全性较高的应用;
InnoDB 支持外键,MyISAM 不支持;
mysql-5.1版本之前默认引擎是MyISAM,之后是InnoDB ;
InnoDB 不支持 FULLTEXT 类型的索引;
InnoDB 中不保存表的行数,如 select count() from table 时,InnoDB;需要扫描一遍整个表来计算有多少行,但是 MyISAM 只要简单的读出保存好的行数即可。注意的是,当 count()语句包含where 条件时 MyISAM 也需要扫描整个表;
READ UNCOMMITTED (读取未提交内容)
READ COMMITTED (读取提交内容)
REPEATABLE READ (可重读)
SEARIALIZABLE (可串行化)
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
在循环中提交
使用自动提交
使用自动回滚
长事务
1.事务的特性
事务就是用户定义的一系列执行SQL语句的操作,这些操作要么完全的执行,要么完全的不执行,他是一个不可分割的工作执行单元。
事务的使用场景:
在日常生活中,有时我们需要进⾏银⾏转账,这个银行转账操作背后就是需要执行多个SQL语句,假如这些SQL执行到一半突然停电了,那么就会导致这个功能只完成了一半,这种情况是不允许出现,要想解决这个问题就需要通过事务来完成。
2.事务的四大特性:
MySQL数据库默认采用主动提(autocommit)模式, 也就是说修改数据(insert、update、delete)
的操作会主动的触发事务,完成事务的提交或者回滚。
开启事务使用 begin 或者 start transaction;
在使⽤事务之前, 先要确保表的存储引擎是 InnoDB 类型, 只有这个类型才可以使⽤事务, mysql-5.1版本之前默认引擎是MyISAM,之后是innoDB 。
1.优化索引、SQL 语句、分析慢查询;
2.设计表的时候严格根据数据库的设计范式来设计数据库;
3.使用缓存,把经常访问到的数据而且不需要经常变化的数据放在缓存中,能节约磁盘 IO
4.优化硬件;采用 SSD,使用磁盘队列技术(RAID0,RAID1,RDID5)等
5.采用 MySQL 内部自带的表分区技术,把数据分层不同的文件,能够提高磁盘的读取效率;
6.垂直分表;把一些不经常读的数据放在一张表里,节约磁盘 I/O;
7.主从分离读写;采用主从复制把数据库的读操作和写入操作分离开来;
8.分库分表分机器(数据量特别大),主要的的原理就是数据路由;
9.选择合适的表引擎,参数上的优化
10.进行架构级别的缓存,静态化和分布式;
11.不采用全文索引;
12.采用更快的存储方式,例如 NoSQL 存储经常访问的数据**。
select * from table limit (start-1)*limit,limit; 其中 start 是页码,limit 是每页显示的条数。
mysql储存过程是一个可编程的函数,它在数据库中创建并保存。它可以有 SQL 语句和一些特殊的控制结构组成。当希望在不同的应用程序或平台上执行相同的函数,或者封装特定功能时,存储过程是非常有用的。数据库中的存储过程可以看做是对编程中面向对象方法的模拟。它允许控制数据的访问方式。存储过程通常有以下优点:
1、存储过程能实现较快的执行速度
2、存储过程允许标准组件是编程。
3、存储过程可以用流程控制语句编写,有很强的灵活性,可以完成复杂的判断和较复杂的运算。
4、存储过程可被作为一种安全机制来充分利用。
5、存储过程能够减少网络流量
一旦建立索引,构建平衡搜索二叉树(b树) 查询时间复杂度:Log2(N)
遵循最左原则,在where子句中写查询条件时把索引字段放在前面,如
mobile为索引字段,name为非索引字段
推荐
SELECT ... FROM t WHERE mobile='13911111111' AND name='python'
不推荐
SELECT ... FROM t WHERE name='python' AND mobile='13911111111'
建立了复合索引 key(a, b, c)
推荐
SELECT ... FROM t WHERE a=... AND b=... AND c= ...
SELECT ... FROM t WHERE a=... AND b=...
SELECT ... FROM t WHERE a=...
不推荐 (字段出现顺序不符合索引建立的顺序)
SELECT ... FROM t WHERE b=... AND c=...
SELECT ... FROM t WHERE b=... AND a=... AND c=...
我们的项目中已经存在非常多的数据库表了,数据量也会逐渐增多,所以我们需要做一些数据库的安全和性能的优化。
对于数据库的优化,我们选择使用MySQL读写分离实现。涉及内容包括主从同步和Django实现MySQL读写分离。
Django实现MySQL读写分离
DATABASES = {
'default': { # 写(主机)
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'HOST': '172.16.238.128', # 数据库主机
'PORT': 3306, # 数据库端口
'USER': 'root', # 数据库用户名
'PASSWORD': 'mysql', # 数据库用户密码
'NAME': 'project' # 数据库名字
},
'slave': { # 读(从机)
'ENGINE': 'django.db.backends.mysql',
'HOST': '172.16.238.128',
'PORT': 8306,
'USER': 'root',
'PASSWORD': '123456',
'NAME': 'project'
}
}
在meiduo_mall.utils.db_router.py中实现读写路由
class MasterSlaveDBRouter(object):
"""数据库读写路由"""
def db_for_read(self, model, **hints):
"""读所使用的服务器:"""
return "slave"
def db_for_write(self, model, **hints):
"""写所使用的服务器:"""
return "default"
def allow_relation(self, obj1, obj2, **hints):
"""是否运行关联操作"""
return True
2.配置数据库读写路由
在 dev.py 文件中配置如下参数
DATABASE_ROUTERS =
['meiduo_mall.utils.db_router.MasterSlaveDBRouter']
# 方案1: 根据内置信息查询
$ redis-cli
127.0.0.1:6379> dbsize # 查询当前库中记录了多少个键
(integer) 150
127.0.0.1:6379> info Memory
# Memory
used_memory:1045456 # Redis分配的内存总量,包含了redis进程内部的开销和数据占用的内存,以字节(byte)为单位
used_memory_human:1020.95K # 展示优化的 Redis内存总量
范式: 对设计数据库提出的⼀些规范, ⽬前有迹可寻的共有8种范式, ⼀般遵守3范式即可。
第⼀范式(1NF) : 强调的是列的原⼦性, 即列不能够再分成其他⼏列。
第⼆范式(2NF) : 满⾜ 1NF, 另外包含两部分内容, ⼀是表必须有⼀个主键; ⼆是⾮主键字段必须完全依赖于主键, ⽽不能只依赖于主键的⼀部分。
第三范式(3NF) : 满⾜ 2NF, 另外⾮主键列必须直接依赖于主键, 不能存在传递依赖。 即不能存在: ⾮主键列 A 依赖于⾮主键列 B, ⾮主键列 B 依赖于主键的情况。
1、ORM——对象关系映射
总而言之,可以理解成一个模型类对应一张表,一个模型类对象对应一条数据,这就是所谓的对象关系映射;通过类和类对象就能操作它所对应的表格中的数据;根据我们设计的类自动帮我们生成数据库中的表格;
2、ORM操作本质
优点
与传统的数据库访问技术相比,ORM有以下优点:
在我们开发系统时,一般都有性能问题。性能问题主要产生在算法不正确和与数据库不正确的使用上。ORM所生成的代码一般不太可能写出很高效的算法,在数据库应用上更有可能会被误用,主要体现在对持久对象的提取和和数据的加工处理上,如果用上了ORM,程序员很有可能将全部的数据提取到内存对象中,然后再进行过滤和加工处理,这样就容易产生性能问题。
Django模型类语法查询出来的结果集;
1、惰性执行
当且仅当我们使用查询集的时候,才会真的和数据库发生交互,请求查询数据;
2、缓存
一、数值型
django对应到Mysql中有两种类型:longtext和varchar。
除了TextField是longtest类型外,其他属于varchar类型。
三、日期类型
django中有3中日期类型,分别对应Mysql的date、datetime和time
四、关系类型
- 组合,或者范围,闭区间
obj = models.Person.objects.filter(pk__gte = 2,pk__lt = 3 )
obj = models.Person.objects.filter(pk__range = [2,3])
- __in = [1,3]
#在,条件判断,符合列表中条件的
OBJ = models.Person.objects.filter(pk__in = [1,3])
返回对像列表(8)
全部/过滤器/排除/ 值 / 值列表/ order_by / 反向/ 区别
返回对象(3)
得到/第一/最后
返回bool 值(1)
存在
返回数字(1)
计数
#ORM操作
#all() 查询所有
查询数据库中的所有结果,结果是queryset类型
models.UserInfo.objects.all() 返回的数据库中所有的数据
#filter() 条件查询
查询条件不能匹配到数据时,不会报错,返回一个空的queryset,,如果没有写查询条件会获取所有数据,获取的是queryset对象,queryset类型的数据还能够继续调用fitler方法
ret = models.Book.objects.filter(title='金瓶7',publish='24期出版社')可以指定多个条件查询,相当于原生sql的and语句,
#get() model对象查询
get获取的是一个model对象,有且只能有一个,查询不到,或者查询到多个都会报错
1. 查不到数据会报错 :Book matching query does not exist
2. 超过一个就报错 :returned more than one Book -- it returned 13!
#exclude() 排除
指定排除与条件匹配的数据,返回值是queryset类型
1.object能够调用,models.Book.objects.exclude(id=6)
2.queryset类型数据能够调用, models.Book.objects.all().exclude(id=6)
#order_by()排序
queryset类型的数据来调用,对查询结果排序,默认是按照id来升序排列的,返回值还是queryset类型
指定条件排序,可以指定多个排序字段,在条件中加上减号就是倒序排列
models.Book.objects.all().order_by('-price','id')先根据price倒序排列,然后数据相同的再根据id排序
#reverse() 反转
queryset类型的数据来调用,对查询结果反向排序,返回值还是queryset类型,数据哟啊先排序再反转
models.Book.objects.all().order_by('id').reverse()
#count() 计数
queryset类型的数据来调用,返回数据库中匹配查询(QuerySet)的对象数量
models.Book.objects.all().count() 查询一共有多少条数据
#first()
返回第一条数据,结果是model对象类型
Book.objects.all()[0] = Book.objects.all().first()这两种写法的意思相同
#last()
返回最后一条数据,结果是model对象类型
Book.objects.all()[-1] = Book.objects.all().last()这两种写法的意思相同
#exists()
判断返回结果集是不是有数据
queryset类型的数据来调用,如果QuerySet包含数据,就返回True,否则返回False
models.Book.objects.filter(id=9999).exists() #有结果就是True,没有结果就是False
#values()
返回的queryset对象,里面的数据是字典的形式,每一条数据是一个字典,字段为键,数据为值,
ret = models.Book.objects.filter(id=9).values('title','price') #条件查询返回一个字典
ret = models.Book.objects.values() #返回所有的数据
#values_list
返回的queryset,里面是数组类型数据,它返回的是一个元组序列,把一条数据封装成一个元祖
ret = models.Book.objects.all().values_list('title','price')#返回的是一个元祖
ret = models.Book.objects.values_list()#返回所有数据
#distinct() 去重
values和values_list得到的queryset类型的数据来调用,从返回结果中剔除重复纪录
models.Book.objects.all().values('name').distinct()#吧name值相同的去除掉
1、使用Q对象拼接具备与(and)或(or)非(not)关系的复合查询条件
from django.db.models import Q
# 结论:filter函数以关键字参数传入的多个查询条件之间是and关系;
# select * from tb_books where (id>1 and id<3);
BookInfo.objects.filter(id__gt=1, id__lt=3)
# 结论:把多个条件表达式,传入Q对象,然后使用“|”运算符把多个表达式构建成互为“and”关系
BookInfo.objects.filter(Q(id__gt=1) & Q(id__lt=3))
# 结论:把多个条件表达式,传入Q对象,然后使用“|”运算符把多个表达式构建成互为“or”关系
# select * from tb_books where (id>1 or id<3);
BookInfo.objects.filter(Q(id__gt=1) | Q(id__lt=3))
# 结论:把条件表达式传入Q对象中,在Q对象前使用“~”符号进行条件的取反,本质是在sql语句中在表达式之前添加not取反关键字
# select * from tb_books where not (id>1);
BookInfo.objects.filter(~Q(id__gt=1))
2.使用F对象,实现使用一张表中多个字段比较的查询
from django.db.models import F
# 结论:把比较的另一个字段名传入F初始化参数中,构建F对象进行比较
# select * from tb_books where bread>=bcomment;
BookInfo.objects.filter(bread__gte=F('bcomment'))
# 结论:F对象支持加减乘除操作
# select * from tb_books where bread>=(bcomment*2);
BookInfo.objects.filter(bread__gte=F('bcomment')*2)
在单表中,提供范围查找的方法
双向__双下方法
4.1 __gt / lt =大于/小于
#GT大于,大于2
OBJ = models.Person.objects.filter(pk__gt = 2 )
#LT,小于2
OBJ = models.Person.objects.filter(pk__lt = 2)
4.2 __gte / lte =大于等于/小于等于
#GTE / LTE中,大于等于/小于等于
OBJ = models.Person.objects.filter(pk__gte = 2)
4.3 __range = [1,3]
#组合,或者范围,闭区间
obj = models.Person.objects.filter(pk__gte = 2,pk__lt = 3 )
obj = models.Person.objects.filter(pk__range = [2,3])
4.4 __in = [1,3]
#在,条件判断,符合列表中条件的
OBJ = models.Person.objects.filter(pk__in = [1,3])
4.5 __contains =‘h’
__包含
#包含= 'xxx'的,区分大小写,__icontains = 'H',忽略大小写
OBJ = models.Person.objects.filter(name__contains = ' ħ ')
4.6 __startwith / endswith
__istartswith / iendswith
#startswith = 'H',和__istartswith = ''/的endsWith
OBJ = models.Person.objects.filter(name__startswith = ' ħ ')
4.7 __year =‘2019’
#筛选年份,不支持月和日
obj = models.Person.objects.filter(birth__year = ' 2019 ' )
obj = models.Person.objects.filter(birth__contains = ' 2019-06 ')
4.8 __isnull =真
查询基线可为空的
obj = models.Person.objects.filter(birth__isnull = True)
5.外键
从 django.db 导入模型
类Publisher(models.Model):
名称 = models.CharField(max_length = 32 )
类Book(models.Model):
标题 = models.CharField(max_length = 32 )
酒馆 = models.
ForeignKey(' 发布',related_name = ' 书',on_delete = models.CASCADE)
ORM框架:
对象关系映射,通过创建一个类,这个类与数据库的表相对应!类的对象代指数据库中的一行数据。
简述ORM原理
让用户不再写SQL语句,而是通过类以及对象的方式,和其内部提供的方法,进行数据库操作!把用户输入的类或对象转换成SQL语句,转换之后通过pymysql执行完成数据库的操作。
ORM的优缺点
优点:
缺点:
filter是查询满足条件的数据
exclude是查询不满足添加的数据
# 1.使用execute执行自定义SQL
from django.db import connection, connections
cursor = connection.cursor() # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
row = cursor.fetchone()
# 2.使用extra方法
extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['nid'])
# 3.使用raw方法
# 解释:执行原始sql并返回模型
# 说明:依赖model多用于查询
# 用法:
book = Book.objects.raw("select * from hello_book")
for item in book:
print(item.title)
pdb — Python的调试器
使用PDB的方式有两种:
单步执行代码,通过命令 python -m pdb xxx.py 启动脚本,进入单步执行模
pdb单步执行太麻烦了,所以第二种方法是import pdb 之后,直接在代码里需要调试的地方放一个pdb.set_trace(),就可以设置一个断点, 程序会在pdb.set_trace()暂停并进入pdb调试环境,可以用pdb 变量名查看变量,或者c继续运行。
pdb官方文档: pdb官方文档
博客文档
进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换。在三态模型中,进程状态分为三个基本状态,即运行态,就绪态,阻塞态。
在五态模型中,进程分为新建态、终止态,运行态,就绪态,阻塞态
进程的三状态及转换:进程的三状态及转换
统计内存最高的进程:
ps aux | grep -v PID | sort -rn -k 4 | head
统计CPU使用率最高的进程:
ps aux | grep -v PID | sort -rn -k 3 | head
僵尸进程:
ps aux | grep defunct | grep -v grep
查找进程启动的线程:
ps -eLf PID
查看网络服务状态:
netstat -ntlp --显示正在listening的tcp的数字格式的连接
netstat -nulp --显示正在listening的udp的数字格式的连接
硬件故障分析:
1.检查磁盘使用量:服务器硬盘是否已满。
2.是否开启了swap交换模式(si/so)。
3.CPU使用情况:占用高CPU时间片的是系统进程还是用户进程。
查看CPU和内存信息:
free -m
liiux基本命令参考
linux基本命令详解
Linux 系统中 grep 命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行
打印出来。 grep 全称是 Global Regular Expression Print,表示全局正则表达式版本,它的使用权限
是所有用户。
linux 下的 find:
功能:在目录结构中搜索文件,并执行指定的操作。此命令提供了相当多的查找条件,功能很强大。
语法: find 起始目录寻找条件操作
说明: find 命令从指定的起始目录开始,递归地搜索其各个子目录,查找满足寻找条件的文件并对
之采取相关的操作。
简单点说说, grep 是查找匹配条件的行, find 是搜索匹配条件的文件。
1、 重定向>
Linux 允许将命令执行结果重定向到一个文件,本应显示在终端上的内容保存到指定文件中。
如:ls >test.txt ( test.txt 如果不存在,则创建,存在则覆盖其内容 )。
2、 重定向>>
这个是将输出内容追加到目标文件中。如果文件不存在,就创建文件;如果文件存在,则将新的
内容追加到那个文件的末尾,该文件中的原有内容不受影响。
在计算机科学中,Shell就是一个命令解释器。
shell是位于操作系统和应用程序之间,是他们二者最主要的接口,shell负责把应用程序的输入命令信息解释给操作系统,将操作系统指令处理后的结果解释给应用程序。
接下来我们来介绍几个linux的场景符号:
重定向符号、管道符、其他符号
重定向符号
在shell脚本中有两种常见的重定向符号 > 和 >>
> 符号
作用:> 表示将符号左侧的内容,以覆盖的方式输入到右侧文件中
>> 符号
作用:>> 表示将符号左侧的内容,以追加的方式输入到右侧文件的末尾行中
管道符 |
定义:| 这个就是管道符,传递信息使用的
使用格式:命令1 | 命令2
管道符左侧命令1 执行后的结果,传递给管道符右侧的命令2使用
其他符号
后台展示符号 &
定义:& 就是将一个命令从前台转到后台执行
使用格式:命令 &
全部信息符号 2>&1
符号详解:
1 表示正确输出的信息
2 表示错误输出的信息
2>&1 代表所有输出的信息
/dev/null 是linux下的一个设备文件,
这个文件类似于一个垃圾桶,特点是:容量无限大
在shell的语句中,流程控制主要分为两种:
简单流程控制语句:选择和循环
复杂流程控制语句:函数
if [ 条件 ]
then
指令
fi
if [ 条件 ]
then
指令1
else
指令2
fi
if [ 条件 ]
then
指令1
elif [ 条件2 ]
then
指令2
else
指令3
fi
脚本内容
admin-1@ubuntu:/data/s/python-n# cat if.sh
#!/bin/bash
# 多if语句的使用场景
if [ "$1" == "start" ]
then
echo "服务启动中..."
elif [ "$1" == "stop" ]
then
echo "服务关闭中..."
elif [ "$1" == "restart" ]
then
echo "服务重启中..."
else
echo "$0 脚本的使用方式: $0 [ start | stop | restart ]"
fi
case 变量名 in
值1)
指令1
;;
值2)
指令2
;;
值3)
指令3
;;
esac
注意:
首行关键字是case,末行关键字esac
选择项后面都有 )
每个选择的执行语句结尾都有两个分号;
脚本内容:
# cat case.sh
#!/bin/bash
# case语句使用场景
case "$1" in
"start")
echo "服务启动中..."
;;
"stop")
echo "服务关闭中..."
;;
"restart")
echo "服务重启中..."
;;
*)
echo "$0 脚本的使用方式: $0 [ start | stop | restart ]"
;;
esac
for 值 in 列表
do
执行语句
done
注意:
”for” 循环总是接收 “in” 语句之后的某种类型的字列表
执行次数和list列表中常数或字符串的个数相同,当循环的数量足够了,就自动退出
示例:遍历文件
#!/bin/bash
# for语句的使用示例
for i in $(ls /root)
do
echo "${i}"
done
while 条件
do
执行语句
done
注意:
条件的类型:命令、[[ 字符串表达式 ]]、(( 数字表达式 ))
while语句示例
#!/bin/bash
# while的示例
a=1
while [ "${a}" -lt 5 ]
do
echo "${a}"
a=$((a+1))
done
until 条件
do
执行语句
done
注意:
条件的类型:命令、[[ 字符串表达式 ]]、(( 数字表达式 ))
until语句示例
#!/bin/bash
# until的示例
a=1
until [ "${a}" -eq 5 ]
do
echo "${a}"
a=$((a+1))
done
函数就是将某些命令组合起来实现某一特殊功能的方式,是脚本编写中非常重要的一部分。
函数名(){
函数体
}
调用函数:
函数名
函数名 参数
函数体调用参数:
函数名(){
函数体 $n
}
注意:
类似于shell内置变量中的位置参数
简单函数定义和调用示例
#!/bin/bash
# 函数使用场景一:执行频繁的命令
dayin(){
echo "wo de mingzi shi 111"
}
dayin
函数传参和函数体内调用参数示例
#!/bin/bash
# 函数的使用场景二
dayin(){
echo "wo de mingzi shi $1"
}
dayin 111
函数调用脚本传参
#!/bin/bash
# 函数传参演示
# 定义传参数函数
dayin(){
echo "wode mignzi shi $1"
}
# 函数传参
dayin $1
脚本传多参,函数分别调用示例
#!/bin/bash
# 函数的使用场景二
dayin(){
echo "wo de mingzi shi $1"
echo "wo de mingzi shi $2"
echo "wo de mingzi shi $3"
}
dayin 111 df dfs
代码实践
#!/bin/bash
# 定义本地变量
arg="$1"
# 脚本帮助信息
usage(){
echo "脚本 $0 的使用方式是: $0 [ start|stop|restart ]"
}
# 函数主框架
if [ $# -eq 1 ]
then
case "${arg}" in
start)
echo "服务启动中..."
;;
stop)
echo "服务关闭中..."
;;
restart)
echo "服务重启中..."
;;
*)
usage
;;
esac
else
usage
fi
Session 采用的是在服务器端保持状态的方案,而 Cookie 采用的是在客户端保持状态的方案。但是禁用 Cookie 就不能得到 Session。因为 Session 是用 Session ID 来确定当前对话所对应的服务器 Session,而 Session ID 是通过 Cookie 来传递的,禁用 Cookie 相当于失去了 SessionID,也就得不到 Session。
相同点:都可以实现做缓存和保持会话状态
1、cookie 数据存放在客户的浏览器上,session 数据放在服务器上。
2、cookie 不是很安全,别人可以分析存放在本地的 cookie 并进行 cookie 欺骗考虑到安全应当使用 session。
3、session 会在一定时间内保存在服务器上。当访问增多,会比较占用服务器的性能考虑到减轻服
务器性能方面,应当使用 cookie。
4、单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie。
5、建议: 将登陆信息等重要信息存放为 SESSION 其他信息如果需要保留,可以放在 cookie 中。
一、应对高并发的基本思路
1、加快单机的速度,例如使用Redis,提高数据访问频率;增加CPU的内核数,增大内存;
2、增加服务器的数量,利用集群。
二、分布式系统的设计
1、无状态
应用本身没有状态,状态全部通过配置文件或者集群的服务端提供并与之同步。比如不同的机房需要读取不同的数据源,那么他们直接通过配置文件或者中心来指定。
进一步,在分布式集群中,如果数据请求的节点可以做到没有状态,意味着任意节点都可以被请求,结合去中心化,就能有效避免单节点的访问瓶颈。
2、拆分
系统设计初期,不要按功能模块来进行拆分,只需要尽快保证每个开发的单元能独立运行,即按照多进程模式可以运行。例如搜索引擎的业务会分为地理信息、类别、文本等多个搜索,在初期所有搜索可以继承开发在一个http请求的响应处理模块里,初期就按照业务进行拆分,会带来极大的工作量,各个模块能独立并行运行即可。
系统拆分原则:
功能及业务:例如搜索模块、地图模块、用户信息模块
子功能拆分:例如搜索模块里,图数据库与文本搜索可以分开
读写分离:根据读写的特性在进行拆分,例如淘宝的商品浏览与编辑,编辑是一个纯写的功能,流量是一个纯读的功能,因此可以对读写的实现进行拆分,一个集群实现商品读取的功能,而另一个规模小的多的集群提供商品写入的功能。
代码模块:一般情况,与上图类似,一个系统可以按照Web、Service及DAO来划分,有专门负责Web请求的,有提供进一步数据服务的模块,以及专门的数据库HA管理模块。
3、负载均衡
如果单机不行,需要考虑使用集群,使用集群就意味着一个负载均衡服务提供在最前端。
负载均衡算法:
(1)轮询:以轮询的方式把请求发到上游服务器,配合weight配置实现基于权重的轮询
(2)IP hash:根据客户的IP做负载均衡,使得胸痛IP均衡到同一个upstream server。
(3)hash key:对客户端的一个key进行hash,建议使用一致性Hash实现负载均衡。普通Hash算法,当添加或删除服务器的时候,很多key都会被重新分配到不同的服务器,使用一致性hash,使得只有极少数的key会被重新分配服务器。
(4)least connection:把最新的请求负载均衡到活跃连接最少的服务器,依然可以与权重相结合。
一致性hash:
一致哈希将每个对象映射到圆环边上的一个点,系统再将可用的节点机器映射到圆环的不同位置。查找某个对象对应的机器时,需要用一致哈希算法计算得到对象对应圆环边上位置,沿着圆环边上查找直到遇到某个节点机器这台机器即为对象应该保存的位置。当删除一台节点机器时,这台机器保存的所有对象都要移动到下一台机器。添加一台机器到圆环边上的某个点时,这个点的下一台机器需要将这个节点前对应的对象移动到新机器上。更改对象在节点机器上的分布可以通过调整节点机器的位置来实现。
一致性hash的平衡性:
通过增加虚拟节点,使得节点的分布及hash算法能实现平衡。
4、服务化
当服务器集群设计为主从架构的时候,意味着需要有多个主服务器提供备份和容灾,这样就要考虑使用服务的自动注册和发现,比如使用ZooKeeper。
消息队列:
消息队列用来解耦一些不需要同步调用的服务或者订阅一些通知以及系统的变化。使用消息队列可以实现服务解耦、异步处理、流量缓冲等。案例:
5、databus架构
我们可以通过databus来实现MySQL数据与redis的更新操作,把MySQL的数据变化同步到redis。
6、数据异构
把一些联合查询的数据,直接组合成一张结果表,这样在读取的时候能更快速地取回结果。通常这样的表会比较大,因此需要按照主键做分库分表来增加读取的性能,带来的问题:
(1)数据同步
(2)某一项服务的终止导致整体服务的终止
(3)聚合查询
数据异构的实现:
(1)通过MQ机制接收数据变更,然后原子化存储
(2)在任务队列里,聚合数据源并更新
(3)聚合的数据,根据类型、实例进行分割,分割的数据还可以考虑分片
7、缓存:
缓存不仅仅存在于服务端,它可以被用在从最前端的任意节点,只要缓存被命中,对应的数据查询就可以得到量级的提升。
(1)客户端缓存:浏览器的cookie、storage,APP的本地数据存储
(2)网络层:CDN、镜像服务器、P2P技术等
(3)服务前端:接入层的缓存,应用层缓存
(4)分布式缓存:利用redis数据库实现分布式的缓存系统
(5)数据库缓存:当所有结果都没命中的情况下,可以依赖数据库的缓存提高检索排序效率
8、并发:
9、隔离:
1.保护资源,以防被其他资源拖垮
2.把不同的数据资源隔开
当海量请求发送到前端的时候,单机的故障率会极大提高,此时一旦一台服务器出故障停止响应,前端反向代理会把请求发送到集群的其他节点,导致整体负载的进一步提升,使得所有的服务请求都被拖累,使得服务器节点如雪崩一样纷纷过载从而导致服务不够用。
因此需要考虑从以下几个维度对访问进行隔离:
(1)资源的隔离:比如js、CSS这类访问量极大的文件放在CDN上
(2)热点的隔离:一些高频的请求和低频的请求分开
(3)读写分离:分布式系统总必用的一点(保证系统高可用很重要的一点)
10、读写分离
MySQL中的存储引擎InnoDB:行级锁,事务(原子性),写快
MySQL中的存储引擎myisam:标记锁,纯读性能高
11、分库分表:
对于NoSQL的数据库,往往天然支持分布式部署,例如HBASE、MongoDB,因此直接可以用集群,通过分片及复制来提高响应能力
对于RDBMS,不能进行分片,所以当容量增长的时候,需要考虑分表,把旧的内容独立出来,新建一张表存储;当业务增长很快,可以进一步考虑把旧的历史数据存储到单独的服务器上的一个数据库,由应用层根据请求来判断到那个库上读取数据,这样能使得热点数据的总量以及总访问量是一定的
分库分表的策略:
(1)取模:按照主键取模后做分库分表,缺点是按照非主键查询的时候需要跨库跨表的查询,扩容需要建立新的集群并进行数据迁移
(2)分区:例如2B的系统,每个大的客户一张表,每个客户下的分店可以考虑分表;根据时间或地区,进行表或数据库的切割
12、限流:
HTTP的请求全部发送到任务队列中,当队列满的时候,直接返回服务器超时;队列的大小按照并发处理能力来设置,使得进入队列的请求都能在一定时间内响应
13、降级:
降级是指在服务并发突然增大的时候,启动限制性措施,降低服务提供的能力从而保护系统性能。降级一般是在系统的接口之间,设置降级开关,在特定的时候启用。例如前面讲到为限流而做的任务缓冲队列,可以通过降级来启动或停用:平峰或低估期请求通过代理层直达后端;高峰期通过降级,请求都进入到队列,由worker从队列获取数据来请求。
降级措施还包括针对服务级别的,例如在高峰情况下只读缓存,缓存失效的情况下直接返回失败,使得热点的访问能被响应,而非热点数据请求被拒绝;微博打开个人主页的时候,还回出现热点推荐、广告等,如果出现流量暴涨,可以关闭这些功能的服务接口。
降级的启动方式:
(1)超时:数据库、HTTP服务或者远程调用超时后,可以自动触发降级。例如淘宝的商品主页会异步加载价格、评论、推荐,而抢购活动时,评论和推荐都可以临时不展示,通过超时来自动降级
(2)统计失败次数:调用外部服务,例如数据来源于图数据库的情况,失败次数达到一定次数的时候自动降级
(3)故障:远程服务出错,可以直接降级并报警
(4)限流:流量监控,超过峰值后自动开启降级,把所有请求排队
关系对比
小结
进程和线程都是完成多任务的一种放式。
多进程要比多线程消耗的资源多,但是多进程开发⽐单进程多线程开发稳定性要强,某个进程挂掉不会影响其它进程。
多进程可以使用cpu的多核运行,多线程可以共享全局变量。
线程不能单独执行必须依附在进程里面
协程是python个中另外一种实现多任务的方式。只不过比线程占用更的小执行单元(理解为需要的资源)。
通俗的理解:在某个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。
最大的优势就是协程极⾼的执⾏效率。因为函数切换不是线程切换,而是由程序自身控制,因此,没
有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控
制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
- 创建进程:
1.首先要导入 multiprocessing 中的 Process;
2.创建一个 Process 对象;
3.创建 Process 对象时,可以传递参数;
1.p = Process(target=XXX, args=(元组,) , kwargs={key:value})
2.target = XXX 指定的任务函数,不用加()
3.args=(元组,) , kwargs={key:value} 给任务函数传递的参数
4.使用 start()启动进程;
5.结束进程。
- Process 语法结构:
Process([group [, target [, name [, args [, kwargs]]]]])
target:如果传递了函数的引用,可以让这个子进程就执行函数中的代码
args:给 target 指定的函数传递的参数,以元组的形式进行传递
kwargs:给 target 指定的函数传递参数,以字典的形式进行传递
name:给进程设定一个名字,可以省略
group:指定进程组,大多数情况下用不到
Process 创建的实例对象的常用方法有:
start():启动子进程实例(创建子进程)
is_alive():判断进程子进程是否还在活着
join(timeout):是否等待子进程执行结束,或者等待多少秒
terminate():不管任务是否完成,立即终止子进程
Process 创建的实例对象的常用属性:
name:当前进程的别名,默认为 Process-N,N 为从 1 开始递增的整数
pid:当前进程的 pid(进程号)
#给子进程指定函数传递参数 Demo:
1.import osfrom multiprocessing import Process
2.import time
3.
4.def pro_func(name, age, **kwargs):
5. for i in range(5):
6. print("子进程正在运行中,name=%s, age=%d, pid=%d" %(name, age, os.getpid()))
7. print(kwargs)
8. time.sleep(0.2)
9.
10.if __name__ == '__main__':
11. # 创建 Process 对象
12. p = Process(target=pro_func, args=('小明',18), kwargs={'m': 20})
13. # 启动进程
14. p.start()
15. time.sleep(1)
16. # 1 秒钟之后,立刻结束子进程
17. p.terminate()
18. p.join()
注意:进程间不共享全局变量。
进程之间的通信-Queue
在初始化 Queue()对象时,(例如 q=Queue(),若在括号中没有指定最大可接受的消息数量,或数量为负值时,那么就代表可接受的消息数量没有上限-直到内存的尽头)
Queue.qsize():返回当前队列包含的消息数量。
Queue.empty():如果队列为空,返回 True,反之 False。
Queue.full():如果队列满了,返回 True,反之 False。
Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从队列中移除,block 默认值为True。
如果 block 使用默认值,且没有设置 timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了 timeout,则会等待 timeout 秒,若还没读取到任何消息,则抛出"Queue.Empty"异常;
如果 block 值为 False,消息列队如果为空,则会立刻抛出"Queue.Empty"异常;
Queue.get_nowait():相当 Queue.get(False);
Queue.put(item,[block[, timeout]]):将 item 消息写入队列,block 默认值为 True;
如果 block 使用默认值,且没有设置 timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了 timeout,则会等待timeout 秒,若还没空间,则抛出"Queue.Full"异常;
如果 block 值为 False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常;
Queue.put_nowait(item):相当 Queue.put(item, False);
进程间通信 Demo:
1.from multiprocessing import Process, Queueimport os, time, random
2.# 写数据进程执行的代码:def write(q):
3. for value in ['A', 'B', 'C']:
4. print('Put %s to queue...' % value)
5. q.put(value)
6. time.sleep(random.random())
7.# 读数据进程执行的代码:def read(q):
8. while True:
9. if not q.empty():
10. value = q.get(True)
11. print('Get %s from queue.' % value)
12. time.sleep(random.random())
13. else:
14. break
15.if __name__=='__main__':
16. # 父进程创建 Queue,并传给各个子进程:
17. q = Queue()
18. pw = Process(target=write, args=(q,))
19. pr = Process(target=read, args=(q,))
20. # 启动子进程 pw,写入:
21. pw.start()
22. # 等待 pw 结束:
23. pw.join()
24. # 启动子进程 pr,读取:
25. pr.start()
26. pr.join()
27. # pr 进程里是死循环,无法等待其结束,只能强行终止:
28. print('')
29. print('所有数据都写入并且读完')
#进程池 Pool
1.# -*- coding:utf-8 -*-
2.from multiprocessing import Poolimport os, time, random
3.def worker(msg):
4. t_start = time.time()
5. print("%s 开始执行,进程号为%d" % (msg,os.getpid()))
6. # random.random()随机生成 0~1 之间的浮点数
7. time.sleep(random.random()*2)
8. t_stop = time.time()
9. print(msg,"执行完毕,耗时%0.2f" % (t_stop-t_start))
10.
11.po = Pool(3) # 定义一个进程池,最大进程数 3
12.for i in range(0,10):
13. # Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
14. # 每次循环将会用空闲出来的子进程去调用目标
15. po.apply_async(worker,(i,))
16.
17.print("----start----")
18.po.close() # 关闭进程池,关闭后 po 不再接收新的请求
19.po.join() # 等待 po 中所有子进程执行完成,必须放在 close 语句之后
20.print("-----end-----")
multiprocessing.Pool 常用函数解析:
apply_async(func[, args[, kwds]]) :使用非阻塞方式调用 func(并行执行,堵塞方式必须等待
上一个进程退出才能执行下一个进程),args 为传递给 func 的参数列表,kwds 为传递给 func
的关键字参数列表;
1.from multiprocessing import Manager,Poolimport os,time,random
2.def reader(q):
3. print("reader 启动(%s),父进程为(%s)" % (os.getpid(), os.getppid()))
4. for i in range(q.qsize()):
5. print("reader 从 Queue 获取到消息:%s" % q.get(True))
6.def writer(q):
7. print("writer 启动(%s),父进程为(%s)" % (os.getpid(), os.getppid()))
8. for i in "itcast":
9. q.put(i)
10.if __name__=="__main__":
11. print("(%s) start" % os.getpid())
12. q = Manager().Queue() # 使用 Manager 中的 Queue
13. po = Pool()
14. po.apply_async(writer, (q,))
15.
16. time.sleep(1) # 先让上面的任务向 Queue 存入数据,然后再让下面的任务开始从中取数据
17.
18. po.apply_async(reader, (q,))
19. po.close()
20. po.join()
21. print("(%s) End" % os.getpid())
同步和异步IO的概念
同步是用户线程发起I/O请求后需要等待或者轮询内核I/O操作完成后才能继续执行
异步是用户线程发起I/O请求后仍需要继续执行,当内核I/O操作完成后会通知用户线程,或者调用用户线程注册的回调函数
同步与异步(线程间调用):
同步与异步是对应于调用者与被调用者,它们是线程之间的关系,两个线程之间要么是同步的,要么是异步的
同步操作时,调用者需要等待被调用者返回结果,才会进行下一步操作
而异步则相反,调用者不需要等待被调用者返回调用,即可进行下一步操作,被调用者通常依靠事件、回调等机制来通知调用者结果
阻塞和非阻塞IO的概念:
阻塞是指I/O操作需要彻底完成后才能返回用户空间
非阻塞是指I/O操作被调用后立即返回一个状态值,无需等I/O操作彻底完成
阻塞与非阻塞(线程内调用)
阻塞与非阻塞是对同一个线程来说的,在某个时刻,线程要么处于阻塞,要么处于非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态:
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
同步与异步调用/线程/通信
同步就是两种东西通过一种机制实现步调一致,异步是两种东西不必步调一致
一、同步调用与异步调用:
在用在调用场景中,无非是对调用结果的不同处理。
同步调用就是调用一但返回,就能知道结果,而异步是返回时不一定知道结果,还得通过其他机制来获知结果,如:
a. 状态 b. 通知 c. 回调函数
二、同步线程与异步线程:
同步线程:即两个线程步调要一致,其中一个线程可能要阻塞等待另外一个线程的运行,要相互协商。快的阻塞一下等到慢的步调一致。
异步线程:步调不用一致,各自按各自的步调运行,不受另一个线程的影响。
三、同步通信与异步通信
同步和异步是指:发送方和接收方是否协调步调一致
同步通信是指:发送方和接收方通过一定机制,实现收发步调协调。
如:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式
异步通信是指:发送方的发送不管接收方的接收状态。
如:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。
阻塞可以是实现同步的一种手段!例如两个东西需要同步,一旦出现不同步情况,我就阻塞快的一方,使双方达到同步。
同步是两个对象之间的关系,而阻塞是一个对象的状态。
同步阻塞方式:
发送方发送请求之后一直等待响应。
接收方处理请求时进行的IO操作如果不能马上等到返回结果,就一直等到返回结果后,才响应发送方,期间不能进行其他工作。
同步非阻塞方式:
发送方发送请求之后,一直等待响应。
接受方处理请求时进行的IO操作如果不能马上的得到结果,就立即返回,取做其他事情。
但是由于没有得到请求处理结果,不响应发送方,发送方一直等待。
当IO操作完成以后,将完成状态和结果通知接收方,接收方再响应发送方,发送方才进入下一次请求过程。(实际不应用)
异步阻塞方式:
发送方向接收方请求后,不等待响应,可以继续其他工作。
接收方处理请求时进行IO操作如果不能马上得到结果,就一直等到返回结果后,才响应发送方,期间不能进行其他操作。 (实际不应用)
异步非阻塞方式:
发送方向接收方请求后,不等待响应,可以继续其他工作。
接收方处理请求时进行IO操作如果不能马上得到结果,也不等待,而是马上返回去做其他事情。
当IO操作完成以后,将完成状态和结果通知接收方,接收方再响应发送方。(效率最高)
这里我们要理解数据是怎样从一个主机到达另一个主机的;当通过http发起一个请求时,应用层、传输层、网络层和链路层的相关协议依次对该请求进行包装并携带对应的首部,最终在链路层生成以太网数据包,以太网数据包通过物理介质传输给对方主机,对方接收到数据包以后,然后再一层一层采用对应的协议进行拆包,最后把应用层数据交给应用程序处理。这种结构非常有栈的味道,所以某些文章也把tcp/ip协议族称为tcp/ip协议栈。
import socket
if __name__ == '__main__':
# 创建tcp客户端套接字
# 1. AF_INET:表示ipv4
# 2. SOCK_STREAM: tcp传输协议
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 和服务端应⽤程序建⽴连接
tcp_client_socket.connect(("192.168.131.62", 8080))
# 代码执⾏到此,说明连接建⽴成功
# 准备发送的数据
send_data = "你好服务端,我是客户端⼩⿊!".encode("gbk")
# 发送数据
tcp_client_socket.send(send_data)
# 接收数据, 这次接收的数据最⼤字节数是1024
recv_data = tcp_client_socket.recv(1024)
# 返回的直接是服务端程序发送的⼆进制数据
print(recv_data)
# 对数据进⾏解码
recv_content = recv_data.decode("gbk")
print("接收服务端的数据为:", recv_content)
# 关闭套接字
tcp_client_socket.close()
执行结果:
b'hello'
接收服务端的数据为: hello
1.socket创建一个套接字
2.bind绑定ip和port
3.listen使套接字变为可以被动链接
4.accept等待客户端的链接
5.recv/send接收发送数据
import socket
if __name__ == '__main__':
# 创建tcp服务端套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端⼝号复⽤,让程序退出端⼝号⽴即释放
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 给程序绑定端⼝号
tcp_server_socket.bind(("", 8989))
# 设置监听
# 128:最⼤等待建⽴连接的个数, 提示: ⽬前是单任务的服务端,同⼀时刻只能服务与⼀个客户端,后续使用多任务能够让服务端同时服务与多个客户端,
# 不需要让客户端进心等待建立连接
# listen后的这个套接字只负责接收客户端连接请求,不能收发消息,收发消息使⽤返回的这个新套接字来完成
tcp_server_socket.listen(128)
# 等待客户端建⽴连接的请求, 只有客户端和服务端建⽴连接成功代码才会解阻塞,代码才能继续往下执⾏
# 1. 专⻔和客户端通信的套接字: service_client_socket
# 2. 客户端的ip地址和端⼝号: ip_port
service_client_socket, ip_port = tcp_server_socket.accept()
# 代码执⾏到此说明连接建⽴成功
print("客户端的ip地址和端⼝号:", ip_port)
# 接收客户端发送的数据, 这次接收数据的最⼤字节数是1024
recv_data = service_client_socket.recv(1024)
# 获取数据的⻓度
recv_data_length = len(recv_data)
print("接收数据的⻓度为:", recv_data_length)
# 对⼆进制数据进⾏解码
recv_content = recv_data.decode("gbk")
print("接收客户端的数据为:", recv_content)
# 准备发送的数据
send_data = "ok, 问题正在处理中...".encode("gbk")
# 发送数据给客户端
service_client_socket.send(send_data)
# 关闭服务与客户端的套接字, 终⽌和客户端通信的服务
service_client_socket.close()
# 关闭服务端的套接字, 终⽌和客户端提供建⽴连接请求的服务
tcp_server_socket.close()
三次握手过程:
1 首先客户端向服务端发送一个带有 SYN 标志,以及随机生成的序号 100(0 字节)的报文
2 服务端收到报文后返回一个报文(SYN200(0 字节),ACk1001(字节+1))给客户端
3 客户端再次发送带有 ACk 标志 201(字节+)序号的报文给服务端
至此三次握手过程结束,客户端开始向服务端发送数据。
1 客户端向服务端发起请求:我想给你通信,你准备好了么?
2 服务端收到请求后回应客户端:I'ok,你准备好了么
3 客户端礼貌的再次回一下客户端:准备就绪,咱们开始通信吧!
整个过程跟打电话的过程一模一样:
1 喂,你在吗
2 在,我说的你听得到不
3 恩,听得到(接下来请开始你的表演)
补充:SYN:请求询问,ACk:回复,回应。
四次挥手过程:
由于 TCP 连接是可以双向通信的(全双工),因此每个方向都必须单独进行关闭(这句话才是精辟,后面四个挥手过程都是其具体实现的语言描述)
四次挥手过程,客户端和服务端都可以先开始断开连接
1 客户端发送带有 fin 标识的报文给服务端,请求通信关闭
2 服务端收到信息后,回复 ACK 答应关闭客户端通信(连接)请求
3 服务端发送带有 fin 标识的报文给客户端,也请求关闭通信
4 客户端回应 ack 给服务端,答应关闭服务端的通信(连接)请求
TCP header中有一个Window Size字段,它其实是指接收端的窗口,即接收窗口。用来告知发送端自己所能接收的数据量,从而达到一部分流控的目的。
其实TCP在整个发送过程中,也在度量当前的网络状态,目的是为了维持一个健康稳定的发送过程,比如拥塞控制。因此,数据是在某些机制的控制下进行传输的,就是窗口机制。
发送窗口
1)已经发送并且对端确认(Sent/ACKed)---------------发送窗外 缓冲区外
(2)已经发送但未收到确认数据(Sent/UnACKed)----- --发送窗内 缓冲区内
(3)允许发送但尚未防的数据(Unsent/Inside)-----------发送窗内 缓冲区内
(4)未发送暂不允许(Unsent/Outside)-------------------发送窗外 缓冲区内
2,3两部分为发送窗口
接受窗口
对于TCP的接收方,在某一时刻在它的接收缓存内存在3种。“已接收”,“未接收准备接收”,“未接收并未准备接收”(由于ACK直接由TCP协议栈回复,默认无应用延迟,不存在“已接收未回复ACK”)。其中“未接收准备接收”称之为接收窗口。
发送窗口与接收窗口关系
TCP是双工的协议,会话的双方都可以同时接收、发送数据。TCP会话的双方都各自维护一个“发送窗口”和一个“接收窗口”。其中各自的“接收窗口”大小取决于应用、系统、硬件的限制(TCP传输速率不能大于应用的数据处理速率)。各自的“发送窗口”则要求取决于对端通告的“接收窗口”,要求相同。
参考:TCP滑动窗口
在《计算机网络》一书中其中有提到,三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误”.
这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。
本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。
如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源。
如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不会向B端发送确认,这个时候B端也能够知道连接没有建立。
问题的本质是,信道是不可靠的,但是我们要建立可靠的连接发送可靠的数据,也就是数据传输是需要可靠的。在这个时候三次握手是一个理论上的最小值,并不是说是tcp协议要求的,而是为了满足在不可靠的信道上传输可靠的数据所要求的。
应用层:HTTP协议(基于传输层的TCP协议,主要解决如何包装数据)
传输层: TCP协议(基于网络层的IP协议)、TPC/IP协议(主要解决数据如何在网络中传输)
网络层: IP 协议
socket则是对TCP/IP协议的封装和应用(程序员层面上),Socket本身并不是协议,而是一个调用接口(API,它只是提供了一个针对TCP或者UDP编程的接口),通过Socket,我们才能使用TCP/IP协议,实际上,Socket跟TCP/IP协议没有必然的联系。
Socket编程接口在设计的时候,就希望也能适应其他的网络协议。所以说,Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口,比如create、 listen、connect、accept、send、read和write等等。
关于TCP /IP和HTTP协议的关系,网络有一段比较容易理解的介绍: “我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等,也可以自己定义应用层协议。WEB使用HTTP协议作应用层协议,以封装 HTTP 文本信息,然后使用TCP/IP做传输层协议将它发到网络上。”
网络有一段关于socket和TCP/IP协议关系的说法比较容易理解: “TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。这个就像操作系统会提供标准的编程接口,比如win32编程接口一样,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。”
图1 HTTP协议工作原理
(1)连接:Web浏览器与Web服务器建立连接,打开一个称为socket(套接字)的虚拟文件,此文件的建立标志着连接建立成功。( 客户端-----通过socket建立连接-----服务器)
(2)请求:Web浏览器通过socket向Web服务器提交请求。HTTP的请求一般是GET或POST命令(POST用于FORM参数的传递)。GET命令的格式为:GET 路径/文件名 HTTP/1。其中,文件名指出所访问的文件,HTTP/1.0指出Web浏览器使用的HTTP版本。
(3)应答:Web浏览器提交请求后,通过HTTP协议传送给Web服务器。Web服务器接到后,进行事务处理,处理结果又通过HTTP传回给Web浏览器,从而在Web浏览器上显示出所请求的页面。例:假设客户机与www.mycompany.com:8080/mydir/index.html建立了连接,就会发送GET命令:GET /mydir/index.html HTTP/1.0。主机名为www.mycompany.com的Web服务器从它的文档空间中搜索子目录mydir的文件index.html。如果找到该文件,Web服务器把该文件内容传送给相应的Web浏览器。为了告知 Web浏览器传送内容的类型,Web服务器首先传送一些HTTP头信息,然后传送具体内容(即HTTP体信息),HTTP头信息和HTTP体信息之间用一个空行分开。其中,常用的HTTP头信息如下。
HTTP 1.0 200 Ok:这是Web服务器应答的第一行,列出服务器正在运行的HTTP版本号和应答代码。代码"200 OK"表示请求完成。
HTTP请求的常用方法有:GET方法、POST方法、HEAD方法、PUT方法、DELETE方法、CONNECT方法、OPTIONS方法、TRACE方法。
什么是HTTP?
HTTP,即超文本传输协议,是一种实现客户端和服务器之间通信的响应协议,它是用作客户端和服务器之间的请求。
客户端(浏览器)会向服务器提交HTTP请求;然后服务器向客户端返回响应;其中响应包含有关请求的状态信息,还可能包含请求的内容。
HTTP的常用方法
1、GET方法
GET方法用于使用给定的URI从给定服务器中检索信息,即从指定资源中请求数据。使用GET方法的请求应该只是检索数据,并且不应对数据产生其他影响。
在GET请求的URL中发送查询字符串(名称/值对),需要这样写:
/test/demo_form.php?name1=value1&name2=value2
说明:
GET请求是可以缓存的,我们可以从浏览器历史记录中查找到GET请求,还可以把它收藏到书签中;且GET请求有长度限制,仅用于请求数据(不修改)。
注:因GET请求的不安全性,在处理敏感数据时,绝不可以使用GET请求。
2、POST方法
POST方法用于将数据发送到服务器以创建或更新资源,它要求服务器确认请求中包含的内容作为由URI区分的Web资源的另一个下属。
POST请求永远不会被缓存,且对数据长度没有限制;我们无法从浏览器历史记录中查找到POST请求。
3、HEAD方法
HEAD方法与GET方法相同,但没有响应体,仅传输状态行和标题部分。这对于恢复相应头部编写的元数据非常有用,而无需传输整个内容。
4、PUT方法
PUT方法用于将数据发送到服务器以创建或更新资源,它可以用上传的内容替换目标资源中的所有当前内容。
它会将包含的元素放在所提供的URI下,如果URI指示的是当前资源,则会被改变。如果URI未指示当前资源,则服务器可以使用该URI创建资源。
5、DELETE方法
DELETE方法用来删除指定的资源,它会删除URI给出的目标资源的所有当前内容。
6、CONNECT方法
CONNECT方法用来建立到给定URI标识的服务器的隧道;它通过简单的TCP / IP隧道更改请求连接,通常实使用解码的HTTP代理来进行SSL编码的通信(HTTPS)。
7、OPTIONS方法
OPTIONS方法用来描述了目标资源的通信选项,会返回服务器支持预定义URL的HTTP策略。
8、TRACE方法
TRACE方法用于沿着目标资源的路径执行消息环回测试;它回应收到的请求,以便客户可以看到中间服务器进行了哪些(假设任何)进度或增量。
GET 请求 请求的数据会附加在 URL 之后,以?分割 URL 和传输数据,多个参数用&连接。URL 的编码格式采用的是 ASCII 编码,而不是 uniclde,即是说所有的非 ASCII 字符都要编码之后再传输。
POST 请求: POST 请求会把请求的数据放置在 HTTP 请求包的包体中。上面的item=bandsaw 就是实际的传输数据。
因此,GET 请求的数据会暴露在地址栏中,而 POST 请求则不会。
传输数据的大小:
遍历,二分查找,哈希,插值,索引,bfs&dfs,平衡树,B+树,B-Tree,红黑树,二叉搜索树。
参考技术博客 参考答案
示例 1: 示例 2: 示例 3:
输入: 123 输入: -123 输入: 120
输出: 321 输出: -321 输出: 21
解法一:通过字符串的形式
class Solution:
def reverse(self, x: int) -> int:
if x >= 0:
reversed_x = int(str(x)[::-1])
else:
reversed_x = -int(str(x)[:0:-1])
if -2 ** 31 < reversed_x < 2 ** 31 - 1:
return reversed_x
else:
return 0
解法二:取余的方式
class Solution:
def reverse(x):
if (x/10 == 0):
return x
long int y = 0
while(x):
y *= 10
if x > pow(2,31)-1 or x < pow(-2,31):
return 0
y = y+ (x % 10)
x /= 10
print(y)
解法三:字符串操作的思路
class Solution:
def reverse(self, x):
"""
:type x: int
:rtype: int
"""
y = list(str(x))
y.reverse()
if y[-1] == "-":
y.remove('-')
y.insert(0, '-')
if y[0] == 0:
y.remove('0')
y2 = ''.join(y)
z = int(str(y2))
if z < (-2**31) or z > (2**31-1):
z = 0
return z
1)堆积生成20个整数,范围0-100
2)按等级划分归类,输出成绩的等级及成绩列表字典项{“A”:[90-100],“B”:[80-89],“C”:[60-79],“D”:[0-59]}
import random
score_list = []
for _ in range(20):
score_list.append(random.randint(0, 100))
A = []
B = []
C = []
D = []
for value in score_list:
if 60 <= value < 80:
C.append(value)
elif 80 <= value < 90:
B.append(value)
elif 90 <= value <= 100:
A.append(value)
else:
D.append(value)
result = {
'A': A,
'B': B,
'C': C,
'D': D
}
print(result)
'''
例子:s="([]()){}" return True
'''
def demo(s):
str = {"}":"{", "]":"[", ")":"("}
a = []
for i in s:
if i not in str:
a.append(i)
elif not a or str[i] != a.pop():
return False
return not a
s = "[([]()){}[]]]"
result = demo(s)
print(result)
'''例子:
参数:
[[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]]
返回:
[[1, 4, 7, 10],
[2, 5, 8, 11],
[3, 6, 9, 12]]
'''
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
b = []
for i in range(len(a[0])):
b.append([0] * len(a))
# print(b)
for i in range(len(b)):
for j in range(len(b[i])):
b[i][j] = a[j][i]
print(b)
def dicts(dc):
if isinstance(dc, dict): #判定dc是否是字典
# tem_key = dc.keys()
for key,value in dc.items():
print("%s=%s"%(key,value))
dicts(value)
dicts(dc)
【python】设计一个冒泡排序的算法
def bubble_sort(alist):
for j in range(len(alist)-1, 0, -1):
# j 表示每次遍历需要比较的次数,是逐渐减小的
for i in range(j):
if alist[i] > alist[i+1]:
alist[i], alist[i+1] = alist[i+1], alist[i]
print(alist)
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
bubble_sort(li)
# print(li)
情景:用户发起 request,并等待 response 返回。在本些 views 中,可能需要执行一段耗时的程序,那么用户就会等待很长时间,造成不好的用户体验,比如发送邮件、手机验证码等。
使用 celery 后,情况就不一样了。解决:将耗时的程序放到 celery 中执行。
将多个耗时的任务添加到队列 queue 中,也就是用 redis 实现 broker 中间人,然后用多个 worker 去监听队列里的任务去执行。
任务 task:就是一个 Python 函数。
celery 是一个简单、灵活且可靠、处理大量消息的分布式系统,可以在一台或者多台机器上运行.
特点:
单个 Celery 进程每分钟可处理数以百万计的任务.
通过消息进行通信,使用消息队列( 中间人或broker )在生产者和消费者之间进行协调。
总结:
任务模块 Task
包含异步任务和定时任务。其中,异步任务通常在业务逻辑中被触发并发往任务队列,而定时任务由 Celery Beat 进程周期性地将任务发往任务队列。
消息中间件 Broker
Broker,即为任务调度队列,接收任务生产者发来的消息(即任务),将任务存入队列。Celery 本身不提供队列服务,官方推荐使用 RabbitMQ 和 Redis 等。
任务执行单元 Worker
Worker 是执行任务的处理单元,它实时监控消息队列,获取队列中调度的任务,并执行它。
任务结果存储 Backend
Backend 用于存储任务的执行结果,以供查询。同消息中间件一样,存储也可使用 RabbitMQ, redis 和 MongoDB 等。
celery 和 redis 之间交互的基本原理:Celery + Redis
1、当发起一个 task(任务) 时,会向 redis 的 celery key 中插入一条记录。
2、如果这时有正在待命的空闲 worker,这个 task 会立即被 worker 领取。
3、如果这时没有空闲的 worker,这个 task 的记录会保留在 key celery 中。
4、这时会将这个 task 的记录从 key celery 中移除,并添加相关信息到 unacked 和 unacked_index 中。
5、worker 根据 task 设定的期望执行时间执行任务,如果接到的不是延时任务或者已经超过了期望时间,则立刻执行。
6、worker 开始执行任务时,通知 redis。(如果设置了 CELERY_ACKS_LATE = True 那么会在任务执行结束时再通知)
7、redis 接到通知后,将 unacked 和 unacked_index 中相关记录移除。
8、如果在接到通知前,worker 中断了,这时 redis 中的 unacked 和 unacked_index 记录会重新回到 key celery 中。
9、在 key celery 中的 task 可以再次重复上述 2 以下的流程。
ElasticSearch是一个分布式、REATfulf风格的搜索和数据分析引擎,能够解决不断涌出的各种用例,作为Elastic Stack的核心,他集中存储您的数据,帮助您发现意料之中以及意料之外的情况。
Elasticsearch 是一个开源的高扩展、分布式、RESTful json风格的全文搜索和数据分析引擎;本身扩展性很好,可以扩展到上百台服务器,处理PB级的数据。 作为 Elastic Stack 的核心,它集中存储您的数据:数字、文本、地理位置、结构化数据、非结构化数据,适用于所有数据类型。全文本搜索只是全球众多公司利用 Elasticsearch 解决各种挑战的冰山一角。
- Elasticsearch是什么,能干什么?
1. 全文搜索引擎
2. 数据库
- Elasticsearch的特点
- 美多项目中用用到的全文检索ElasticSearch
实现全文检索的搜索引擎,首选的是 Elasticsearch。
Elasticsearch 是用 Java 实现的,开源的搜索引擎。
它可以快速地储存、搜索和分析海量数据。维基百科、StackOverflow、Github 等都采用它。
Elasticsearch 的底层是开源库 Lucene。但是,没法直接使用 Lucene,必须自己写代码去调用它的接口。
Elasticsearch 不支持对中文进行分词建立索引,需要配合扩展elasticsearch-analysis-ik来实现中文分词处理。
Haystack 是在 Django 中对接搜索引擎的框架,搭建了用户和搜索引擎之间的沟通桥梁。
我们在 Django 中可以通过使用 Haystack 来调用 Elasticsearch 搜索引擎。
Haystack 可以在不修改代码的情况下使用不同的搜索后端(比如 Elasticsearch、Whoosh、Solr等等)。
# Haystack
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': 'http://172.16.238.128:9200/', # Elasticsearch服务器ip地址,端口号固定为9200
'INDEX_NAME': 'meiduo_mall', # Elasticsearch建立的索引库的名称
},
}
# 当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
#重要提示:
#HAYSTACK_SIGNAL_PROCESSOR 配置项保证了在 Django 运行起来后,有新的数据产生时,Haystack 仍然可以让 Elasticsearch 实时生成新数据的索引
重要提示:
HAYSTACK_SIGNAL_PROCESSOR 配置项保证了在 Django 运行起来后,有新的数据产生时,Haystack 仍然可以让 Elasticsearch 实时生成新数据的索引
ElasticSearch为什么检索快?对比Mysql分析:参考文档
页面静态化:
将页面用到的数据从数据库中查出来, 然后生成一个静态页面, 当其他用户来访问时, 直接返回静态页面.
页面静态化步骤:
使用首页静态化的技术:将动态生成的html页面生成保存成静态文件,定时更新该页面,程序使用django-apscheduler执行定时任务,让数据库的内容进行展示;当用户浏览的额时候才能提高浏览效率和体验度
步骤:1. 从数据库中查询数据
2. 渲染模板
3. 写入文件
包括详情页静态化:
两者都是静态化但是时机和方式不同
首页的时机: 采用定时任任务:django-apscheduler执行定时任务
详情页使用celery异步任务监听后台管理站点admin保存操作;当数据发生改变的时候生成静态页面的html
在测试环境中 我们可以使用脚本进行详情页面的静态化生成
APScheduler (advanceded python scheduler)是一款Python开发的定时任务工具。
pip install apscheduler
2 使用方式
from apscheduler.schedulers.background import BackgroundScheduler
# 创建定时任务的调度器对象
scheduler = BackgroundScheduler()
# 定义定时任务
def my_job(param1, param2):
pass
# 向调度器中添加定时任务
scheduler.add_job(my_job, 'date', args=[100, 'python'])
# 启动定时任务调度器工作
scheduler.start()
3 调度器 Scheduler
负责管理定时任务
BlockingScheduler: 作为独立进程时使用
from apscheduler.schedulers.blocking import BlockingScheduler
scheduler = BlockingScheduler()
scheduler.start() # 此处程序会发生阻塞
BackgroundScheduler: 在框架程序(如Django、Flask)中使用
from apscheduler.schedulers.background import BackgroundScheduler
scheduler = BackgroundScheduler()
scheduler.start() # 此处程序不会发生阻塞
4 执行器 executors
在定时任务该执行时,以进程或线程方式执行任务
ThreadPoolExecutor
from apscheduler.executors.pool import ThreadPoolExecutor
ThreadPoolExecutor(max_workers)
ThreadPoolExecutor(20) # 最多20个线程同时执行
5 触发器 Trigger
指定定时任务执行的时机
1) date 在特定的时间日期执行
2) interval 经过指定的时间间隔执行
3) cron 按指定的周期执行
6 配置方法
...略
7 启动
scheduler.start()
对于BlockingScheduler ,程序会阻塞在这,防止退出
对于BackgroundScheduler,程序会立即返回,后台运行
8 扩展
任务管理
停止APScheduler运行
scheduler.shutdown()
PyJWT
注册登录后需要对用户信息进行状态保持, 可选方案包括: Cookie Session JWT
相比HTTP自带的状态保持机制, JWT的优点:
- 用户模型类:AbstractUser
Django默认提供的认证系统中,用户的认证机制依赖Session机制,我们在本项目中将引入JWT认证机制,将用户的身份凭据存放在Token中,然后对接Django的认证系统
用户的数据模型
用户密码的加密与验证
用户的权限系统
Django提供了django.contrib.auth.models.AbstractUser用户抽象模型类允许我们继承
不支持手机号验证登录: 所以我们需要进行user模型类添加mobile字段;还要继承abstractuser然后再重写类方法加上手机号等
2.2 图片验证码
- 借助第三方工具包:captcha
- 图片验证码保存在Redis中:
key:UUID
value:图片验证码的内容
2.3 短信验证码
- 使用的是云通讯发送短信验证码
- 由于发送短信比较耗时,所以i使用Celery异步任务发送短息验证码
celery的组成:
- 任务队列:broker; 任务队列存储- 在redis数据库中
- 处理器:worker; 从任务队列里面拿任务进行处理
- celery可以进行跨机部署;可独立运行存在
- 使用delay方法发送异步任务
- 运行异步任务的方法: celery -A celery_tasks.main worker -l info
- 短信验证码存储在redis中:
- key: 手机号
- value:短信验证码的内容
- 发送短信验证码的必要条件是图形验证码必须正确!
- 在redis数据库里面会对已经发送并且存储的验证码进行标记:flag
- 如果在60s内已经发送过将不允许再次发送
-注册成功之后的返回值:username;user_id;token
2.4 JWT, token
jwt相比session的优点:不用开辟空间节省服务器空间降低压力;不需要cookie
-JWT的组成:
- header; 一般没有什么东西
- payload;可以存放任何东西;一般是username;user_id......
- signature;用来做校验的
- 如何使用JWT生成token
from rest_framework_jwt.settings import api_settings
jwt_payload_handler=api_settings.JWT_PAYLOAD_HANDLE jwt_encode_handlerapi_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
- 自定义生成token的方法:itsdangerous
2.5 登录
传统登录:
obtain_jwt_token能够直接实现登录逻辑:
from rest_framework_jwt.views import obtain_jwt_token
但是默认的返回值仅有token,我们还需在返回值中增加username和user_id;
所以自定制返回值支持手机号和用户名登录
- QQ登录 :
- 1. 前端调用接口向服务器发送请求;
- 服务器拼接QQ服务器的url地址返回给前端;
- 前端拿着返回的url地址请求QQ服务器弹出登录的二维码;
- 2. 用户进行扫码登录
- 3. 跳到回调地址页,并且携带两个参数:code编码;state(记录用户请求登录之前的地址)
- 4. 调用接口浏览器携带code编去换取QQ服务器端的access_token
- 5. 拿到access_token之后再次去QQ端用access_token换openid
- 5.1 拿到openid以后QQ端服务器的工作就算做完了
- 6. 查询openid 是否已经绑定:分两种情况;
- 6.1 已经绑定:直接返回token;用户登录成功
- 6.2 未绑定:也可以分两种情况:
- 6.2.1 未绑定但是数据库里面有用户的注册账号个人信息;此时绑定即可;用户登录成功
- 6.2.2 未绑定是一个全新的用户;则返回access_token;提交表单;执行绑定操作
Django的认证后端中间件实现
'django.contrib.auth.middleware.AuthenticationMiddleware',
用户名登录后端逻辑
import json
from django.contrib.auth import login, authenticate
class LoginView(View):
def post(self, request):
'''实现登录接口'''
# 1.接收参数
dict = json.loads(request.body.decode())
username = dict.get('username')
password = dict.get('password')
remembered = dict.get('remembered')
# 2.校验(整体 + 单个)
if not all([username, password]):
return JsonResponse({'code': 400,
'errmsg': '缺少必传参数'})
# 3.验证是否能够登录
user = authenticate(username=username,
password=password)
# 判断是否为空,如果为空,返回
if user is None:
return JsonResponse({'code': 400,
'errmsg': '用户名或者密码错误'})
# 4.状态保持
login(request, user)
# 5.判断是否记住用户
if remembered != True:
# 7.如果没有记住: 关闭立刻失效
request.session.set_expiry(0)
else:
# 6.如果记住: 设置为两周有效
request.session.set_expiry(None)
# 8.返回json
return JsonResponse({'code': 0,
'errmsg': 'ok'})
主要实现的功能是: 分页 排序
分页采用: DRF自带的分页功能
排序: 采用DRF 自带的 Orderfilter
引入搜索引擎来实现全文检索。全文检索即在指定的任意字段中进行检索查询
搜索引擎的原理
通过搜索引擎进行数据查询时,搜索引擎并不是直接在数据库中进行查询,而是搜索引擎会对数据库中的数据进行一遍预处理,单独建立起一份索引结构数据。
搜索引擎将关键字在索引数据中进行快速对比查找,进而找到数据的真实存储位置。
使用第三方工具 : ElasticSearch 采用索引的方法实现快速高效的查询
1. 开源的 Elasticsearch 是目前全文搜索引擎的首选。
2. 它可以快速地储存、搜索和分析海量数据。维基百科、Stack Overflow、Github 都采用它。
3. Elasticsearch 的底层是开源库 Lucene。但是,你没法直接用 Lucene,必须自己写代码去调用它的接口。
4. Elastic 是 Lucene 的封装,提供了 REST API 的操作接口,开箱即用。
5. Elasticsearch 是用Java实现的。
6. Elasticsearch 不支持对中文进行分词建立索引,需要配合扩展elasticsearch-analysis-ik来实现中文分词处理。
使用Docker安装Elasticsearch及其扩展
haystack:使用haystack对接Elasticsearch
1. Haystack为Django提供了模块化的搜索。它的特点是统一的,熟悉的API,可以让你在不修改代码的情况下使用不同的搜 索后端(比如 Solr, Elasticsearch, Whoosh, Xapian 等等)。
2. 在django中可以通过使用haystack来调用Elasticsearch搜索引擎。
定义索引类
创建索引类
通过创建索引类,来指明让搜索引擎对哪些字段建立索引,也就是可以通过哪些字段的关键字来检索数据。
在goods应用中新建search_indexes.py文件,用于存放索引类
from haystack import indexes
from .models import SKU
class SKUIndex(indexes.SearchIndex, indexes.Indexable):
"""
SKU索引数据模型类
"""
text = indexes.CharField(document=True, use_template=True)
id = indexes.IntegerField(model_attr='id')
name = indexes.CharField(model_attr='name')
price = indexes.DecimalField(model_attr='price')
default_image_url = indexes.CharField(model_attr='default_image_url')
comments = indexes.IntegerField(model_attr='comments')
def get_model(self):
"""返回建立索引的模型类"""
return SKU
def index_queryset(self, using=None):
"""返回要建立索引的数据查询集"""
return self.get_model().objects.filter(is_launched=True)
在SKUIndex建立的字段,都可以借助haystack由elasticsearch搜索引擎查询。
复合字段text
其中text字段我们声明为document=True,表名该字段是主要进行关键字查询的字段, 该字段的索引值可以由多个数据库模型类字段组成,具体由哪些模型类字段组成,我们用use_template=True表示后续通过模板来指明。其他字段都是通过model_attr选项指明引用数据库模型类的特定字段。
在REST framework中,索引类的字段会作为查询结果返回数据的来源。
在templates目录中创建text字段使用的模板文件
具体在templates/search/indexes/goods/sku_text.txt文件中定义
{{ object.name }}
{{ object.caption }}
{{ object.id }}
创建序列化器
在goods/serializers.py中创建haystack序列化器
from drf_haystack.serializers import HaystackSerializer
class SKUIndexSerializer(HaystackSerializer):
"""
SKU索引结果数据序列化器
"""
class Meta:
index_classes = [SKUIndex]
fields = ('text', 'id', 'name', 'price', 'default_image_url', 'comments')
创建视图
在goods/views.py中创建视图
from drf_haystack.viewsets import HaystackViewSet
class SKUSearchViewSet(HaystackViewSet):
"""
SKU搜索
"""
index_models = [SKU]
serializer_class = SKUIndexSerializer
业务需求分析
在用户登录与未登录状态下,都可以保存用户的购物车数据
用户可以对购物车数据进行增、删、改、查
用户对于购物车数据的勾选也要保存,在订单结算页面会使用勾选数据
用户登录时,合并cookie中的购物车数据到redis中
技术实现
对于未登录的用户,购物车数据使用浏览器cookie保存
对于已登录的用户,购物车数据在后端使用Redis保存
购物车数据存储设计
{
sku_id: {
"count": xxx, // 数量
"selected": True // 是否勾选
},
sku_id: {
"count": xxx,
"selected": False
},
...
}
在cookie中只能保存字符串数据,所以将上述数据使用pickle进行序列化转换 ,并使用base64编码为字符串,保存到cookie中。
pickle模块的使用
pickle模块是python的标准模块,提供了对于python数据的序列化操作,可以将数据转换为bytes类型,其序列化速度比json模块要高。
pickle.dumps() 将python数据序列化为bytes类型
pickle.loads() 将bytes类型数据反序列化为python的数据类型
base64模块的使用
Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2^6=64,所以每6个比特为一个单元,对应某个可打印字符。3个字节有24个比特,对应于4个Base64单元,即3个字节可由4个可打印字符来表示。在Base64中的可打印字符包括字母A-Z、a-z、数字0-9,这样共有62个字符,此外两个可打印符号在不同的系统中而不同。
Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据,包括MIME的电子邮件及XML的一些复杂数据。
python标准库中提供了base64模块,用来进行转换
base64.b64encode() 将bytes类型数据进行base64编码,返回编码后的bytes类型
base64.b64deocde() 将base64编码的bytes类型进行解码,返回解码后的bytes类型
添加到购物车
添加到购物车需要对用户进行登录状态的判断;分为两种情况;存储的状态也不一样;
访问接口时,无论用户是否登录,前端请求都需携带请求头Authorization,由后端判断是否登录
解决跨域请求:
因为前端可能携带cookie,为了保证跨域请求中,允许后端使用cookie,确保在配置文件有如下设置CORS_ALLOW_CREDENTIALS = True
创建购物的序列化器:
class CartSerializer(serializers.Serializer):
"""
购物车数据序列化器
"""
sku_id = serializers.IntegerField(label='sku id ', min_value=1)
count = serializers.IntegerField(label='数量', min_value=1)
selected = serializers.BooleanField(label='是否勾选', default=True)
def validate(self, data):
try:
sku = SKU.objects.get(id=data['sku_id'])
except SKU.DoesNotExist:
raise serializers.ValidationError('商品不存在')
if data['count'] > sku.stock:
raise serializers.ValidationError('商品库存不足')
return data
解决Authorization请求头验证抛出错误:
因为前端请求时携带了Authorization请求头(主要是JWT),而如果用户未登录,此请求头的JWT无意义(没有值),为了防止REST framework框架在验证此无意义的JWT时抛出401异常,在视图中需要做两个处理
1. 重写perform_authentication()方法,此方法是REST framework检查用户身份的方法
2. 在获取request.user属性时捕获异常,REST framework在返回user时,会检查Authorization请求头,无效的Authorization请求头会导致抛出异常
在购物车视图中:
class CartView(APIView):
"""
购物车
"""
def perform_authentication(self, request):
"""
重写父类的用户验证方法,不在进入视图前就检查JWT
"""
pass
定义购物车数据在Cookie中保存的时间:
在carts中新建constants.py 常量文件
#购物车cookie的有效期
CART_COOKIE_EXPIRES = 365 * 24 * 60 * 60
查询购物车的数据
在carts/serializers.py中创建序列化器
class CartSKUSerializer(serializers.ModelSerializer):
"""
购物车商品数据序列化器
"""
count = serializers.IntegerField(label='数量')
selected = serializers.BooleanField(label='是否勾选')
class Meta:
model = SKU
fields = ('id', 'count', 'name', 'default_image_url', 'price', 'selected')
在carts/views.py 中修改视图,增加get方法
class CartView(APIView):
...
def get(self, request):
"""
获取购物车
"""
try:
user = request.user
except Exception:
user = None
if user is not None and user.is_authenticated:
# 用户已登录,从redis中读取
redis_conn = get_redis_connection('cart')
redis_cart = redis_conn.hgetall('cart_%s' % user.id)
redis_cart_selected = redis_conn.smembers('cart_selected_%s' % user.id)
cart = {}
for sku_id, count in redis_cart.items():
cart[int(sku_id)] = {
'count': int(count),
'selected': sku_id in redis_cart_selected
}
else:
# 用户未登录,从cookie中读取
cart = request.COOKIES.get('cart')
if cart is not None:
cart = pickle.loads(base64.b64decode(cart.encode()))
else:
cart = {}
# 遍历处理购物车数据
skus = SKU.objects.filter(id__in=cart.keys())
for sku in skus:
sku.count = cart[sku.id]['count']
sku.selected = cart[sku.id]['selected']
serializer = CartSKUSerializer(skus, many=True)
return Response(serializer.data)
删除购物车的数据
在carts/serializers.py 中新建序列化器
class CartDeleteSerializer(serializers.Serializer):
"""
删除购物车数据序列化器
"""
sku_id = serializers.IntegerField(label='商品id', min_value=1)
def validate_sku_id(self, value):
try:
sku = SKU.objects.get(id=value)
except SKU.DoesNotExist:
raise serializers.ValidationError('商品不存在')
return value
在carts/views.py 中修改视图,增加delete方法
class CartView(APIView):
...
def delete(self, request):
"""
删除购物车数据
"""
serializer = CartDeleteSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
sku_id = serializer.validated_data['sku_id']
try:
user = request.user
except Exception:
# 验证失败,用户未登录
user = None
if user is not None and user.is_authenticated:
# 用户已登录,在redis中保存
redis_conn = get_redis_connection('cart')
pl = redis_conn.pipeline()
pl.hdel('cart_%s' % user.id, sku_id)
pl.srem('cart_selected_%s' % user.id, sku_id)
pl.execute()
return Response(status=status.HTTP_204_NO_CONTENT)
else:
# 用户未登录,在cookie中保存
response = Response(status=status.HTTP_204_NO_CONTENT)
# 使用pickle序列化购物车数据,pickle操作的是bytes类型
cart = request.COOKIES.get('cart')
if cart is not None:
cart = pickle.loads(base64.b64decode(cart.encode()))
if sku_id in cart:
del cart[sku_id]
cookie_cart = base64.b64encode(pickle.dumps(cart)).decode()
# 设置购物车的cookie
# 需要设置有效期,否则是临时cookie
response.set_cookie('cart', cookie_cart, max_age=constants.CART_
ES)
return response
购物车全选
在carts/serializers.py中新建序列化器
class CartSelectAllSerializer(serializers.Serializer):
"""
购物车全选
"""
selected = serializers.BooleanField(label='全选')
在carts/views.py中新建视图
class CartSelectAllView(APIView):
"""
购物车全选
"""
def perform_authentication(self, request):
"""
重写父类的用户验证方法,不在进入视图前就检查JWT
"""
pass
def put(self, request):
serializer = CartSelectAllSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
selected = serializer.validated_data['selected']
try:
user = request.user
except Exception:
# 验证失败,用户未登录
user = None
if user is not None and user.is_authenticated:
# 用户已登录,在redis中保存
redis_conn = get_redis_connection('cart')
cart = redis_conn.hgetall('cart_%s' % user.id)
sku_id_list = cart.keys()
if selected:
# 全选
redis_conn.sadd('cart_selected_%s' % user.id, *sku_id_list)
else:
# 取消全选
redis_conn.srem('cart_selected_%s' % user.id, *sku_id_list)
return Response({'message': 'OK'})
else:
# cookie
cart = request.COOKIES.get('cart')
response = Response({'message': 'OK'})
if cart is not None:
cart = pickle.loads(base64.b64decode(cart.encode()))
for sku_id in cart:
cart[sku_id]['selected'] = selected
cookie_cart = base64.b64encode(pickle.dumps(cart)).decode()
# 设置购物车的cookie
# 需要设置有效期,否则是临时cookie
response.set_cookie('cart', cookie_cart, max_age=constants.CART_COOKIE_EXPIRES)
return response
合并购物车
在用户登录时,将cookie中的购物车数据合并到redis中,并清除cookie中的购物车数据。
普通登录和QQ登录都要合并,所以将合并逻辑放到公共函数里实现。
在carts/utils.py中创建merge_cart_cookie_to_redis方法
消息队列参考文档
一、消息队列(MQ)概述
消息队列(Message Queue),是分布式系统中重要的组件,其通用的使用场景可以简单地描述为:
当不需要立即获得结果,但是并发量又需要进行控制的时候,差不多就是需要使用消息队列的时候。
消息队列主要解决了应用耦合、异步处理、流量削锋等问题。
当前使用较多的消息队列有RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,而部分数据库如Redis、Mysql以及phxsql也可实现消息队列的功能。
二、消息队列使用场景
消息队列在实际应用中包括如下四个场景:
应用耦合:多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败;
异步处理:多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间;
限流削峰:广泛应用于秒杀或抢购活动中,避免流量过大导致应用系统挂掉的情况;
消息驱动的系统:系统分为消息队列、消息生产者、消息消费者,生产者负责产生消息,消费者(可能有多个)负责对消息进行处理;
下面详细介绍上述四个场景以及消息队列如何在上述四个场景中使用:
2.1 异步处理
具体场景:用户为了使用某个应用,进行注册,系统需要发送注册邮件并验证短信。对这两个操作的处理方式有两种:串行及并行。
三、消息队列的两种模式
消息队列包括两种模式,点对点模式(point to point, queue)和发布/订阅模式(publish/subscribe,topic)。
4.1 RabbitMQ
RabbitMQ 2007年发布,是一个在AMQP(高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。
主要特性:
可靠性: 提供了多种技术可以让你在性能和可靠性之间进行权衡。这些技术包括持久性机制、投递确认、发布者证实和高可用性机制;
灵活的路由: 消息在到达队列前是通过交换机进行路由的。RabbitMQ为典型的路由逻辑提供了多种内置交换机类型。如果你有更复杂的路由需求,可以将这些交换机组合起来使用,你甚至可以实现自己的交换机类型,并且当做RabbitMQ的插件来使用;
消息集群:在相同局域网中的多个RabbitMQ服务器可以聚合在一起,作为一个独立的逻辑代理来使用;
队列高可用:队列可以在集群中的机器上进行镜像,以确保在硬件问题下还保证消息安全;
多种协议的支持:支持多种消息队列协议;
服务器端用Erlang语言编写,支持只要是你能想到的所有编程语言;
管理界面: RabbitMQ有一个易用的用户界面,使得用户可以监控和管理消息Broker的许多方面;
跟踪机制:如果消息异常,RabbitMQ提供消息跟踪机制,使用者可以找出发生了什么;
插件机制:提供了许多插件,来从多方面进行扩展,也可以编写自己的插件;
4.2 ActiveMQ
ActiveMQ是由Apache出品,ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现。它非常快速,支持多种语言的客户端和协议,而且可以非常容易的嵌入到企业的应用环境中,并有许多高级功能。
主要特性:
服从 JMS 规范:JMS 规范提供了良好的标准和保证,包括:同步或异步的消息分发,一次和仅一次的消息分发,消息接收和订阅等等。遵从 JMS 规范的好处在于,不论使用什么 JMS 实现提供者,这些基础特性都是可用的;
连接性:ActiveMQ 提供了广泛的连接选项,支持的协议有:HTTP/S,IP 多播,SSL,STOMP,TCP,UDP,XMPP等等。对众多协议的支持让 ActiveMQ 拥有了很好的灵活性。
支持的协议种类多:OpenWire、STOMP、REST、XMPP、AMQP ;
持久化插件和安全插件:ActiveMQ 提供了多种持久化选择。而且,ActiveMQ 的安全性也可以完全依据用户需求进行自定义鉴权和授权;
支持的客户端语言种类多:除了 Java 之外,还有:C/C++,.NET,Perl,PHP,Python,Ruby;
代理集群:多个 ActiveMQ 代理可以组成一个集群来提供服务;
异常简单的管理:ActiveMQ 是以开发者思维被设计的。所以,它并不需要专门的管理员,因为它提供了简单又使用的管理特性。有很多中方法可以监控 ActiveMQ 不同层面的数据,包括使用在 JConsole 或者 ActiveMQ 的Web Console 中使用 JMX,通过处理 JMX 的告警消息,通过使用命令行脚本,甚至可以通过监控各种类型的日志。
4.3 RocketMQ
RocketMQ出自 阿里公司的开源产品,用 Java 语言实现,在设计时参考了 Kafka,并做出了自己的一些改进,消息可靠性上比 Kafka 更好。RocketMQ在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景。
主要特性:
是一个队列模型的消息中间件,具有高性能、高可靠、高实时、分布式特点;
Producer、Consumer、队列都可以分布式;
Producer向一些队列轮流发送消息,队列集合称为Topic,Consumer如果做广播消费,则一个consumer实例消费这个Topic对应的所有队列,如果做集群消费,则多个Consumer实例平均消费这个topic对应的队列集合;
能够保证严格的消息顺序;
提供丰富的消息拉取模式;
高效的订阅者水平扩展能力;
实时的消息订阅机制;
亿级消息堆积能力;
较少的依赖;
4.4 Kafka
Apache Kafka是一个分布式消息发布订阅系统。它最初由LinkedIn公司基于独特的设计实现为一个分布式的提交日志系统( a distributed commit log),,之后成为Apache项目的一部分。Kafka系统快速、可扩展并且可持久化。它的分区特性,可复制和可容错都是其不错的特性。
主要特性:
快速持久化,可以在O(1)的系统开销下进行消息持久化;
高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;
.完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现负载均衡;
支持同步和异步复制两种HA;
支持数据批量发送和拉取;
zero-copy:减少IO操作步骤;
数据迁移、扩容对用户透明;
无需停机即可扩展机器;
其他特性:严格的消息顺序、丰富的消息拉取模型、高效订阅者水平扩展、实时的消息订阅、亿级的消息堆积能力、定期删除机制;
使用Kafka需要:
Python中至少有三种比较常见的方法类型,即实例方法,类方法、静态方法。
1.实例方法
定义:第一个参数是“self”,通过它可以使用实例的属性和方法,也可以使用类的属性和方法
调用:只能由实例对象调用
2.类方法
定义:使用装饰器@classmethod。第一个参数是“cls”,通过它可以使用类的属性和方法,但不能传实例的属性和方法
调用:类对象或实例对象都可以调用
3.静态方法
定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,方法体中不能使用类或实例的任何属性和方法
调用:类对象或实例对象都可以调用
class ClassTest(object):
__num = 0
@classmethod
def addNum(cls):
cls.__num += 1
@classmethod
def getNum(cls):
return cls.__num
# 这里我用到魔术函数__new__,主要是为了在创建实例的时候调用人数累加的函数。
def __new__(self):
ClassTest.addNum()
return super(ClassTest, self).__new__(self)
class Student(ClassTest):
def __init__(self):
self.name = ''
a = Student()
b = Student()
print(ClassTest.getNum())
正向代理 是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。
反向代理正好相反,对于客户端而言它就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理的命名空间中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将获得的内容返回给客户端,就像这些内容原本就是它自己的一样。
PyChecker 是一个静态分析工具,它不仅能报告源代码中的错误,并且会报告错误类型和复杂度。Pylint 是检验模块是否达到代码标准的另一个工具。
代码管理的本质需求
良好的并行开发,不易相互间的覆盖;
本地可以维护版本,联网后提交到服务器;
版本库之间可以简单的添加编译依赖(百度的comake2系统就很方便)
示例:(CONFIG(‘public/[email protected]’);),直接生成编译依赖以及makefile;
工具集成web的 diff,合并树,以及code review 工具。
git 结合使用私有服务器
gitflow:
Gitflow工作流程
git工具的使用方法
- git回退版本
方案一:
git reset --hard HEAD^
方案二:当版本非常多时可选择的方案
git reset --hard 版本号
- 撤销修改
只能撤销工作区、暂存区的代码,不能撤销仓库区的代码
撤销仓库区的代码就相当于回退版本操作
撤销工作区代码
git checkout 文件名
撤销暂存区代码
# 第一步:将暂存区代码撤销到工作区
git reset HEAD 文件名
# 第二步:撤销工作区代码
git checkout 文件名
MVC是一种程序设计模式,其设计思想是分工、解耦。MVC 的全拼为 Model-View-Controller,MVC 的思想被应用在了 Web 开发方面,被称为 Web MVC 框架。
- MVC
M全拼为Model,主要封装对数据库层的访问,对数据库中的数据进行增、删、改、查操作。
V全拼为View,用于封装结果,生成页面展示的html内容。
C全拼为Controller,用于接收请求,处理业务逻辑,与Model和V交互,返回结果。
1、 用户点击注按钮,将要注册的信息发送给网站服务器。
2、 Controller 控制器接收到用户的注册信息,Controller 会告诉 Model 层将用户的注册信息保存到数据库。
3、 Model 层将用户的注册信息保存到数据库
4、 数据保存之后将保存的结果返回给 Model 模型,
5、 Model 层将保存的结果返回给 Controller 控制器。
6、 Controller 控制器收到保存的结果之后,或告诉 View 视图,view 视图产生一个 html页面。
7、 View 将产生的 Html 页面的内容给了 Controller 控制器。
8、 Controller 将 Html 页面的内容返回给浏览器。
9、 浏览器接受到服务器 Controller 返回的 Html 页面进行解析展示。
- MVT
M全拼为Model,与MVC中的M功能相同,负责数据库交互,进行数据处理。
V全拼为View,与MVC中的C功能相同,进行业务处理,返回应答。
T全拼为Template,与MVC中的V功能相同负责封装构造要返回的html.
1、 用户点击注册按钮,将要注册的内容发送给网站的服务器。
2、 View 视图,接收到用户发来的注册数据,View 告诉 Model 将用户的注册信息保存进数据库。
3、 Model 层将用户的注册信息保存到数据库中。
4、 数据库将保存的结果返回给 Model
5、 Model 将保存的结果给 View 视图。
6、 View 视图告诉 Template 模板去产生一个 Html 页面。
7、 Template 生成 html 内容返回给 View 视图。
8、 View 将 html 页面内容返回给浏览器。
9、 浏览器拿到 view 返回的 html 页面内容进行解析,展示。
总结:
FastDFS分布式文件系统,数据冗余,数据的备份,数据量的存储扩展tracker server的作用是负载均衡和调度,可以实现集群,每个reacker节点的地位平等,收集storage的状态;
storage server的作用是存储,不同的组内保存的内容是不同的,相同的组内保存的内容是相同的,这样的设计数据会相对比较安全安全;
无论是tracker还是storage都支持集群的方式扩展,数据的扩展比较方便文件上传的流程
storage server定时向tracker server的上传存储状态信息
客户端上传链接请求
请求会先到达tracker server,tracker server查询可以调用的storage;返回storage server 的ip和port上传文件到指定的storage server中srorage server生成file id,并将文件存入到磁盘中返回file id给客户端存储文件信息
Docker 的目标之一就是缩短代码从开发、测试到部署、上线运行的周期,让我们的应用程序具备可移植性、易于构建、并易于协作。