虚拟环境使用virtualenv创建,可以查看系统是否安装了virtualenv
# coding:urf-8
$ virtualenv --version
安装虚拟环境(须在联网环境下)
$ sudo pip install virtualenv
$ sudo pip install virtualenvwrapper
安装完虚拟环境后,如果提示找不到mkvirtualenv命令,须配置环境变量
# 1 创建目录用来存放虚拟环境
mkdir SHOME/.virtualenv
# 2 打开~/.bashrc文件,并添加如下
export WORKON_HOME=SHOME/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh
# 3 运行
source ~/.bashrc
创建虚拟环境(ubuntu里须在联网状态下)
# 虚拟环境是一个互相隔离的目录
$ mkvirtualenv Flask_py
pip freeze > requirements.txt
pip install -r requirements.txt
退出虚拟环境
如果所在环境为真实环境,会提示deactivate:未找到命令
$ deactivate Flask_py
# __name__表示当前的模块名字
#flask 以这个模块所在的目录为总目录
app = Flask(__name__)
# 访问静态资源的url前缀
app = Flask(__name__,
static_url_path="/python", # 访问静态资源的url前缀,默认是static
static_folder="static", # 静态文件目录,默认是static
template_folder="templates", # 模版文件的目录,默认是templates
)
@app.route("/") # 路由
def index():
# 定义的视图函数
return "hello flask"
if __name__ == '__main__':
# 启动flask程序
app.run()
app.config.from_pyfile(yourconfig.cfg)
# 或者
app.config.from_object()
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# 设置连接数据库的url
app.config['SQLALCHEMY_DATABASE_URL']='mysql+pymysql://root:[email protected]:3306/idiom'
#设置每次请求结束后自动提交数据库中的改动
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
# 查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# 创建蓝图对象
admin = Blueprint('admin',__name__) # 参数一:为蓝图起名字
# 注册蓝图
app.register_blueprint(admin,url_prefix="/admin") # 设置前缀
安装
$ sudo pip3 install django # 安装django的最新版本
$ sudo pip3 install django[==版本] # 安装django的指定版本
# 如:
$ sudo pip3 install django==1.11.8
卸载
$ pip3 uninstall django
示例:
$ django-admin startproject mysite1
$ tree mysite1/
mysite1/
├── manage.py
└── mysite1
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
1 directory, 5 files
项目目录结构解析:
manage.py
manage.py
包含项目管理的子命令, 如:
python3 manage.py runserver
启动服务python3 manage.py startapp
创建应用python3 manage.py migrate
数据库迁移...
mysite1 项目包文件夹
__init__.py
wsgi.py
urls.py
settings.py
http://127.0.0.1:8000/admin/
settings.py
文件介绍
BASE_DIR
DEBUG
调试模式
(用于开发中)生产环境中
(不启用调试)ALLOWED_HOSTS
设置允许访问到本项目的网络地址列表,取值:
127.0.0.1
, localhost
能访问本项目python3 manage.py runserver 0.0.0.0:5000
# 指定网络设备所有主机都可以通过5000端口访问(需加ALLOWED_HOSTS = ['*']
)
INSTALLED_APPS
MIDDLEWARE
TEMPLATES
DATABASES
LANGUAGE_CODE
"en-us"
"zh-Hans"
TIME_ZONE
"UTC"
"Asia/Shanghai"
ROOT_URLCONF
ROOT_URLCONF = 'mysite1.urls'
注: 此模块可以通过
from django.conf import settings
导入和使用
HTTP1.1 请求详述
序号 | 方法 | 描述 |
---|---|---|
1 | GET | 请求指定的页面信息,并返回实体主体。 |
2 | HEAD | 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 |
3 | POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 |
4 | PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 |
5 | DELETE | 请求服务器删除指定的页面。 |
6 | CONNECT | HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 |
7 | OPTIONS | 允许客户端查看服务器的性能。 |
8 | TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
HttpRequest对象
什么是模板
模板的配置
<项目名>/templates
templates
文件夹中搜索模板文件默认的模块文件夹templates
修改settings.py文件,设置TEMPLATES的DIRS值为'DIRS': [os.path.join(BASE_DIR, 'templates')],
# file: settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 'DIRS': [],
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 添加模板路径
'APP_DIRS': True, # 是否索引各app里的templates目录
...
},
]
通过 loader 获取模板,通过HttpResponse进行响应
from django.template import loader
# 1.通过loader加载模板
t = loader.get_template("模板文件名")
# 2.将t转换成 HTML 字符串
html = t.render(字典数据 )
# 3.用响应对象将转换的字符串内容返回给浏览器
return HttpResponse(html)
使用 render() 直接加载并响应模板
from django.shortcuts import render
return render(request,'模板文件名', 字典数据)
from django.conf.urls import url, include
APPEND_SLASH = False
# settings.py 设置为False则URL结尾不添加“/”
作用:
用于分发将当前路由转到各个应用的路由配置文件的 urlpatterns 进行分布式处理
函数格式:
include(‘app命字.url模块名’)
模块
app命字/url模块名.py
文件件里必须有urlpatterns 列表使用前需要使用
from django.conf.urls import include
导入此函数
用作 python 和 mysql 的接口
$ sudo pip3 install pymysql
安装 mysql 客户端(非必须)
$ sudo pip3 install mysqlclient
create database 数据库名 default charset=utf8;
数据库配置
# file:settings.py
DATABASES = {
'default' : {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mywebdb', # 数据库名称,需要自己定义
'USER': 'root',
'PASSWORD': '123456', # 管理员密码
'HOST': '127.0.0.1',
'PORT': 3306,
}
}
修改项目中__init__.py 加入如下内容来提供pymysql引擎的支持
import pymysql
pymysql.install_as_MySQLdb()
编写模型类Models
模型类需继承自django.db.models.Model
Models的语法规范
from django.db import models
class 模型类名(models.Model):
字段名 = models.字段类型(字段选项)
模型类名是数据表名的一部分,建议类名首字母大写
字段名又是当前类的类属性名,此名称将作为数据表的字段名
字段类型用来映射到数据表中的字段的类型
字段选项为这些字段提供附加的参数信息
字段类型
BooleanField()
CharField()
DateField()
DateTimeField()
DecimalField()
数据库类型:decimal(x,y)
编程语言中:使用小数表示该列的值
在数据库中:使用小数
参数:
示例:
money=models.DecimalField(
max_digits=7,
decimal_places=2,
default=0.0
)
FloatField()
EmailField()
IntegerField()
URLField()
ImageField()
数据库类型:varchar(100)
作用:在数据库中为了保存图片的路径
编程语言和数据库中使用字符串
示例:
image=models.ImageField(
upload_to="static/images"
)
upload_to:指定图片的上传路径
在后台上传时会自动的将文件保存在指定的目录下
TextField()
字段选项FIELD_OPTIONS
示例:
# 创建一个属性,表示用户名称,长度30个字符,必须是唯一的,不能为空,添加索引
name = models.CharField(max_length=30, unique=True, null=False, db_index=True)
ORM数据库基本操作
管理器对象
class MyModel(models.Model):
...
MyModel.objects.create(...) # objects 是管理器对象
查询数据
数据库的查询需要使用管理器对象进行
通过 MyModel.objects 管理器方法调用查询接口
方法 | 说明 |
---|---|
all() | 查询全部记录,返回QuerySet查询对象 |
get() | 查询符合条件的单一记录 |
filter() | 查询符合条件的多条记录 |
exclude() | 查询符合条件之外的全部记录 |
… |
all()方法
方法: all()
用法: MyModel.objects.all()
作用: 查询MyModel实体中所有的数据
select * from tabel
返回值: QuerySet容器对象,内部存放 MyModel 实例
示例:
from bookstore import models
books = models.Book.objects.all()
for book in books:
print("书名", book.title, '出版社:', book.pub)
在模型类中定义 def __str__(self):
方法可以将自定义默认的字符串
class Book(models.Model):
title = ...
def __str__(self):
return "书名: %s, 出版社: %s, 定价: %s" % (self.title, self.pub, self.price)
查询返回指定列(字典表示)
方法: values(‘列1’, ‘列2’)
用法: MyModel.objects.values(…)
作用: 查询部分列的数据并返回
select 列1,列2 from xxx
返回值: QuerySet
示例:
from bookstore import models
books = models.Book.objects.values("title", "pub")
for book in books:
print("书名", book["title"], '出版社:', book['pub'])
print("book=", book)
查询返回指定列(元组表示)
方法:values_list(‘列1’,‘列2’)
用法:MyModel.objects.values_list(…)
作用:
返回值: QuerySet容器对象,内部存放 元组
示例:
from bookstore import models
books = models.Book.objects.values_list("title", "pub")
for book in books:
print("book=", book) # ('Python', '清华大学出版社')...
排序查询
方法:order_by
用法:MyModel.objects.order_by(’-列’,‘列’)
作用:
说明:
默认是按照升序排序,降序排序则需要在列前增加’-'表示
示例:
from bookstore import models
books = models.Book.objects.order_by("-price")
for book in books:
print("书名:", book.title, '定价:', book.price)
根据条件查询多条记录
方法: filter(条件)
语法:
MyModel.objects.filter(属性1=值1, 属性2=值2)
返回值:
QuerySet容器对象,内部存放 MyModel 实例
说明:
Books.objects.filter(price=20, pub="清华大学出版社")
返回定价为20 且
出版社为"清华大学出版社"的全部图书示例:
# 查询书中出版社为"清华大学出版社"的图书
from bookstore import models
books = models.Book.objects.filter(pub="清华大学出版社")
for book in books:
print("书名:", book.title)
2. 查询Author实体中id为1并且isActive为True的
- authors=Author.objects.filter(id=1,isActive=True)
__exact
: 等值匹配
Author.objects.filter(id__exact=1)
# 等同于select * from author where id = 1
__contains
: 包含指定值
Author.objects.filter(name__contains='w')
# 等同于 select * from author where name like '%w%'
__startswith
: 以 XXX 开始
__endswith
: 以 XXX 结束
__gt
: 大于指定值
Author.objects.filer(age__gt=50)
# 等同于 select * from author where age > 50
__gte
: 大于等于
__lt
: 小于
__lte
: 小于等于
__in
: 查找数据是否在指定范围内
Author.objects.filter(country__in=['中国','日本','韩国'])
# 等同于 select * from author where country in ('中国','日本','韩国')
__range
: 查找数据是否在指定的区间范围内
# 查找年龄在某一区间内的所有作者
Author.objects.filter(age__range=(35,50))
# 等同于 SELECT ... WHERE Author BETWEEN 35 and 50;
详细内容参见: https://docs.djangoproject.com/en/1.11/ref/models/querysets/#field-lookups
修改单个实体的某些字段值的步骤:
如:
from bookstore import models
abook = models.Book.objects.get(id=10)
abook.market_price = "10.5"
abook.save()
通过 QuerySet 批量修改 对应的全部字段
直接调用QuerySet的update(属性=值) 实现批量修改
如:
# 将 id大于3的所有图书价格定为0元
books = Book.objects.filter(id__gt=3)
books.update(price=0)
# 将所有书的零售价定为100元
books = Book.objects.all()
books.update(market_price=100)
删除单个对象
步骤
示例:
try:
auth = Author.objects.get(id=1)
auth.delete()
except:
print(删除失败)
删除查询结果集
步骤
示例:
# 删除全部作者中,年龄大于65的全部信息
auths = Author.objects.filter(age__gt=65)
auths.delete()
不带分组聚合
不带分组的聚合查询是指导将全部数据进行集中统计查询
聚合函数:
django.db.models
from django.db.models import *
语法:
返回结果:
示例:
# 得到所有书的平均价格
from bookstore import models
from django.db.models import Count
result = models.Book.objects.aggregate(myAvg=Avg('price'))
print("平均价格是:", result['myAvg'])
print("result=", result) # {"myAvg": 58.2}
# 得到数据表里有多少本书
from django.db.models import Count
result = models.Book.objects.aggregate(mycnt=Count('title'))
print("数据记录总个数是:", result['mycnt'])
print("result=", result) # {"mycnt": 10}
分组聚合
分组聚合是指通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合。
语法:
用法步骤:
通过先用查询结果MyModel.objects.value. 查找查询要分组聚合的列
MyModel.objects.value(‘列1’, ‘列2’)
如:
pub_set = models.Book.objects.values('pub')
print(books) #
通过返回结果的 QuerySet.annotate 方法分组聚合得到分组结果
QuerySet.annotate(名=聚合函数(‘列’))
返回 QuerySet 结果集,内部存储结果的字典
如:
pub_count_set = pub_set.annotate(myCount=Count('pub'))
print(pub_count_set) #
示例:
def test_annotate(request):
- from django.db.models import Count
from . import models
# 得到所有出版社的查询集合QuerySet
pub_set = models.Book.objects.values('pub')
# 根据出版社查询分组,出版社和Count的分组聚合查询集合
pub_count_set = pub_set.annotate(myCount=Count('pub')) # 返回查询集合
for item in pub_count_set:
print("出版社:", item['pub'], "图书有:", item['myCount'])
return HttpResponse('请查看服务器端控制台获取结果')
Django可以将其缓存的数据存储在您的数据库中
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
'OPTIONS':{
'MAX_ENTRIES': 300, #当前最大缓存数
'CULL_FREQUENCY': 3 #删除频率 1/cull_frequency
}
}
}
创建缓存表
python3 manage.py createcachetable
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',#这个是文件夹的路径
#'LOCATION': 'c:\test\cache',#windows下示例
}
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake'
}
}
from django.views.decorators.cache import cache_page
@cache_page(30) -> 单位s
def my_view(request):
...
from django.views.decorators.cache import cache_page
urlpatterns = [
path('foo/', cache_page(60)(my_view)),
]
{% load cache %}
{% cache 50 sidebar request.user.username %}
.. sidebar for logged in user ..
{% endcache %}
django.utils.deprecation.MiddlewareMixin
类def process_request(self, request):
执行路由之前被调用,在每个请求上调用,返回None或HttpResponse对象 **def process_view(self, request, callback, callback_args, callback_kwargs):
调用视图之前被调用,在每个请求上调用,返回None或HttpResponse对象def process_response(self, request, response):
所有响应返回浏览器之前被调用,在每个请求上调用,返回HttpResponse对象def process_exception(self, request, exception):
当处理过程中抛出异常时调用,返回一个HttpResponse对象def process_template_response(self, request, response):
在视图刚好执行完毕之后被调用,在每个请求上调用,返回实现了render方法的响应对象# file : middleware/mymiddleware.py
from django.http import HttpResponse, Http404
from django.utils.deprecation import MiddlewareMixin
class MyMiddleWare(MiddlewareMixin):
def process_request(self, request):
print("中间件方法 process_request 被调用")
def process_view(self, request, callback, callback_args, callback_kwargs):
print("中间件方法 process_view 被调用")
def process_response(self, request, response):
print("中间件方法 process_response 被调用")
return response
def process_exception(self, request, exception):
print("中间件方法 process_exception 被调用")
def process_template_response(self, request, response):
print("中间件方法 process_template_response 被调用")
return response
# file : settings.py
MIDDLEWARE = [
...
'middleware.mymiddleware.MyMiddleWare',
]
#单个中间件输出
MyMW process_request do---
MyMW process_views do ---
----this is test cache views ----
MyMW process_response do ---
#多个中间件时 输出
MyMW process_request do---
MyMW2 process_request do---
MyMW process_views do ---
MyMW2 process_views do ---
----this is test cache views ----
MyMW2 process_response do ---
MyMW process_response do ---
中间件的执行过程
需求
用中间件实现强制某个IP地址只能向/test 发送 5 次GET请求
提示:
代码
from django.http import HttpResponse, Http404
from django.utils.deprecation import MiddlewareMixin
import re
class VisitLimit(MiddlewareMixin):
'''此中间件限制一个IP地址对应的访问/user/login 的次数不能改过10次,超过后禁止使用'''
visit_times = {} # 此字典用于记录客户端IP地址有访问次数
def process_request(self, request):
ip_address = request.META['REMOTE_ADDR'] # 得到IP地址
if not re.match('^/test', request.path_info):
return
times = self.visit_times.get(ip_address, 0)
print("IP:", ip_address, '已经访问过', times, '次!:', request.path_info)
self.visit_times[ip_address] = times + 1
if times < 5:
return
return HttpResponse('你已经访问过' + str(times) + '次,您被禁止了')
中文件上传时必须有带有enctype="multipart/form-data"
时才会包含文件内容数据。
标签上传文件
xxx
对应request.FILES['xxx']
对应的内存缓冲文件流对象。可通能过request.FILES['xxx']
返回的对象获取上传文件数据file=request.FILES['xxx']
file 绑定文件流对象,可以通过文件流对象的如下信息获取文件数据在setting.py 中设置一个变量MEDIA_ROOT 用来记录上传文件的位置
# file : settings.py
...
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/files')
在当前项目文件夹下创建 static/files
文件夹
$ mkdir -p static/files
添加路由及对应的处理函数
# file urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^upload', views.upload_view)
]
上传文件的视图处理函数
# file views.py
from django.http import HttpResponse, Http404
from django.conf import settings
import os
def upload_view(request):
if request.method == 'GET':
return render(request, 'index/upload.html')
elif request.method == "POST":
a_file = request.FILES['myfile']
print("上传文件名是:", a_file.name)
filename =os.path.join(settings.MEDIA_ROOT, a_file.name)
with open(filename, 'wb') as f:
data = a_file.file.read()
f.write(data)
return HttpResponse("接收文件:" + a_file.name + "成功")
raise Http404
pip3 install django-redis
CACHES = {
"default":{
"BACKEND":"django_redis.cache.RedisCache",
"LOCATION":"redis://127.0.0.1:6379/1",
"OPTIONS":{
"CLIENT_CLASS":"django_redis.client.DefaultClient",
}
}
}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
Django带有一个用户认证系统。 它处理用户账号、组、权限以及基于cookie的用户会话
作用:
文档参见https://docs.djangoproject.com/en/1.11/topics/auth/
User模型类
from django.contrib.auth.models import User
默认user的基本属性有:
属性名 | 类型 | 是否必选 |
---|---|---|
username | 用户名 | 是 |
password | 密码 | 是 |
邮箱 | 可选 | |
first_name | 名 | |
last_name | 姓 | |
is_superuser | 是否是管理员帐号(/admin) | |
is_staff | 是否可以访问admin管理界面 | |
is_active | 是否是活跃用户,默认True。一般不删除用户,而是将用户的is_active设为False。 | |
last_login | 上一次的登录时间 | |
date_joined | 用户创建的时间 |
数据库表现形式
mysql> use myauth;
mysql> desc auth_user;
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| password | varchar(128) | NO | | NULL | |
| last_login | datetime(6) | YES | | NULL | |
| is_superuser | tinyint(1) | NO | | NULL | |
| username | varchar(150) | NO | UNI | NULL | |
| first_name | varchar(30) | NO | | NULL | |
| last_name | varchar(30) | NO | | NULL | |
| email | varchar(254) | NO | | NULL | |
| is_staff | tinyint(1) | NO | | NULL | |
| is_active | tinyint(1) | NO | | NULL | |
| date_joined | datetime(6) | NO | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
11 rows in set (0.00 sec)
创建普通用户create_user
from django.contrib.auth import models
user = models.User.objects.create_user(username='用户名', password='密码', email='邮箱',...)
...
user.save()
创建超级用户create_superuser
from django.contrib.auth import models
user = models.User.objects.create_superuser(username='用户名', password='密码', email='邮箱',...)
...
user.save()
from django.contrib.auth import models
try:
user = models.User.objects.get(username='用户名')
user.is_active = False # 记当前用户无效
user.save()
print("删除普通用户成功!")
except:
print("删除普通用户失败")
return HttpResponseRedirect('/user/info')
from django.contrib.auth import models
try:
user = models.User.objects.get(username='xiaonao')
user.set_password('654321')
user.save()
return HttpResponse("修改密码成功!")
except:
return HttpResponse("修改密码失败!")
from django.contrib.auth import models
try:
user = models.User.objects.get(username='xiaonao')
if user.check_password('654321'): # 成功返回True,失败返回False
return HttpResponse("密码正确")
else:
return HttpResponse("密码错误")
except:
return HttpResponse("没有此用户!")
Django可直接在视图函数中生成csv文件 并响应给浏览器
import csv
from django.http import HttpResponse
from .models import Book
def make_csv_view(request):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="mybook.csv"'
all_book = Book.objects.all()
writer = csv.writer(response)
writer.writerow(['id', 'title'])
for b in all_book:
writer.writerow([b.id, b.title])
return response
响应获得一个特殊的MIME类型text / csv。这告诉浏览器该文档是CSV文件,而不是HTML文件
响应会获得一个额外的Content-Disposition
标头,其中包含CSV文件的名称。它将被浏览器用于“另存为…”对话框
对于CSV文件中的每一行,调用writer.writerow
,传递一个可迭代对象,如列表或元组。
利用QQ邮箱发送电子邮件
django.core.mail 子包封装了 电子邮件的自动发送SMT协议
前期准备:
QQ邮箱->设置->帐户->“POP3/IMAP......服务”
# 发送邮件设置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # 固定写法
EMAIL_HOST = 'smtp.qq.com' # 腾讯QQ邮箱 SMTP 服务器地址
EMAIL_PORT = 25 # SMTP服务的端口号
EMAIL_HOST_USER = '[email protected]' # 发送邮件的QQ邮箱
EMAIL_HOST_PASSWORD = '******' # 邮箱的授权码(即QQ密码)
EMAIL_USE_TLS = True # 与SMTP服务器通信时,是否启动TLS链接(安全链接)默认false
mail.send_mail(subject='test1111', message='123',from_email='[email protected]',recipient_list=['[email protected]'], auth_password='gjy19881227')
from django.core import mail
mail.send_mail(
subject, #题目
message, # 消息内容
from_email, # 发送者[当前配置邮箱]
recipient_list=['[email protected]',], # 接收者邮件列表
auth_password='xxxxxxx' # 在QQ邮箱->设置->帐户->“POP3/IMAP......服务” 里得到的在第三方登录QQ邮箱授权码
)
### 在settings.py里配置
import sys
sys.path.insert(0,os.path.join(BASE_DIR,'apps')) --->'apps'为集合文件夹名
class Meta:
abstract = True # 注明为抽象模型类
### 定义一个类视图
from django.views.generic import View
class ['类名'](View): # 继承View
def get(self,request): # 获取get请求
'...逻辑'
return render(request,'页面')
def post(self,request): # 获取post请求
'...逻辑'
return render(request,'页面')
修改app里urls.py里的路由
from ['app名'].views import ['类名']
urlpatterns = [
url(r'^register$',['类名'].as_view(),name='')
]
from django.contrib.auth.decorators import login_required
class LoginRequiredMixin():
@classmethod
def as_view(cls,**initkwargs):
# 调用父类的as_view
view = super(LoginRequiredMixin,cls).as_view(**initkwargs)
return login_required(view)
import json
jsonStr = json.dumps(元组|列表|字典)
return jsonStr
#方法1 使用Django中提供的序列化类来完成QuerySet到JSON字符串的转换
from django.core import serializers
json_str = serializers.serialize('json',QuerySet)
return HttpResponse(json_str)
#方法2
d = {'a': 1}
return JsonResponse(d)
JSON对象=JSON.parse(JSON字符串)
一般的,邮件激活时,激活链接需要一个携带用户信息的url,来判断是激活的哪一个用户,且用户信息必须加密。python中可以使用 itsdangerous 来进行加解密。
import itsdangerous
from itsdangerous import TimedJSONWebSignatureSerializer as Serialize
# TimedJSONWebSignatureSerializer --> 该类可以加密,还可以设置加密过期时间
from itsdangerous import SignatureExpired # 捕获异常
使用方法:
s = Serialize('sdfsfs',3600) # 创建对象 参数1-->秘钥 参数2-->过期时间,秒
info= {'code':1} # 需要加密的信息
token = s.dumps(info) # 加密,返回值 --> 加密后的信息
s.loads(token) # 解密
捕获异常:
# s.loads(token) 如果过期等会出现异常
try:
info = s.loads(token)
except SignatureExpired as e:
# 捕获到异常表示token 已过期
print(e)
Celery 是一个简单、灵活且可靠的,处理大量消息的分布式系统
它是一个专注于实时处理的任务队列,同时也支持任务调度
中文官网:http://docs.jinkan.org/docs/celery/
pip3 install celery
broker - 消息传输的中间件,生产者一旦有消息发送,将发至broker;【RQ,redis】
backend - 用于存储消息/任务结果,如果需要跟踪和查询任务状态,则需添加要配置相关
worker - 工作者 - 消费/执行broker中消息/任务的进程
项目文件夹下创建python_package(创建Python包) —> celery_tasks文件
celery_tasks文件下创建py文件 —> tasks.py
示例
# 导入celery类
from celery import Celery
# 创建一个Celery类的实例对象
# 参数一:文件位置 参数二:Redis数据库ip,端口号:数据库号
app = Celery('celery_tasks.tasks.py',
broker='redis://172.16.179.130:6379/8')
@app.task # 使用装饰器
# 定义任务函数 (注册发邮件)
def send_register_active_email(email,username,token)
pass
# 使用功能函数 使用装饰器后.delay()启用中间人Redis,参数传进delay()
send_register_active_email.delay(email,username,token)
**流程:**任务发起者,中间者broker(redis),处理者wroker
三者可以在同一台电脑上启动
三者也可以不在同一台电脑上(必须在同一个网段里)
不在同一台电脑需要复试项目文件到另一台电脑
启动wroker处理者(处理者也需要任务代码)
celery -A celery_tasks.py.tasks wroker -l info
或
celery -A tasks worker --loglevel=info
nohup celery -A mywiki worker -l info > c.log 2>&1 &
- 设置初始化,在任务处理者一端加
```python
# 导入celery类
from celery import Celery
# django环境初始化 ###########处理者添加代码
import OS,django
os.environ.setdefault("DJANGO_SETTINGS_MODULE","Online_retailers.settings")
djano.setup()
# 创建一个Celery类的实例对象
# 参数一:文件位置 参数二:Redis数据库ip,端口号:数据库号
app = Celery('celery_tasks.tasks.py',
broker='redis://172.16.179.130:6379/8')
@app.task # 使用装饰器
# 定义任务函数 (注册发邮件)
def send_register_active_email(email,username,token)
pass
# 使用功能函数 使用装饰器后.delay()启用中间人Redis,参数传进delay()
send_register_active_email.delay(email,username,token
pip3 install django-tinymce==2.6.0 # 下载富文本编辑器
# 安装完成后,可以使用在Admin管理中,也可以自定义表单使用.
1. 在settings.py 中为INSTALLED_APPS注册编辑器应用
INSTALLED_APPS = (
...
'tinymce',
)
2. 在settings.py 中添加编辑器配置
TINYMCE_DEFAULT_CONFIG = {
'theme':'advanced',
'width':600,
'height':400,
}
3. 在urls.py中配置编辑器url
url(r'^tinymce/',include('tinymce.urls'))
1. sudo cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf`
2. 在/home/pyhon/目录中创建目录 fastdfs/tracker (mkdir -p /home/pyhon/fastdfs/tracker)
3. 编辑/etc/fdfs/tracker.conf配置文件
sudo vim /etc/fdfs/tracker.conf
4. 修改base_path = /home/python/fastdfs/tracker
1. sudo cp /etc/fdfs/storage/conf.sample /etc/fdfs/storage/conf
2. 在/home/pyhon/目录中创建目录 fastdfs/storage (mkdir -p /home/pyhon/fastdfs/storage)
3. 编辑/etc/fdfs/storage.conf配置文件
sudo vim /etc/fdfs/storage.conf
4. 修改base_path = /home/python/fastdfs/storage
store_path0=/home/python/fastdfs/storage
tracker_server=172.16.179.131:1202 #填本机IP 及端口号
sudo serveice fdfs_trackerd start
sudo serveice fdfs_storaged start
sudo /usr/local/nginx/sbin/nginx
1. sudo cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf
2. 编辑/etc/fdfs/client.conf配置文件 sudo vim /etc/fdfs/client.conf
# 修改内容
base_path = /home/python/fastdfs/tracker
tracker_server=172.16.179.131:22122 #填本机IP 及端口号
执行预编命令 fdfs_upload_file /etc/fdfs/client.conf ~要上传的文件路径
1. sudo ./configure --prefix=/usr/local/nginx/ --add-module=fastdfs-nginx-module-master 解压后的目录的绝对路径/src
2. sudo ./make
3. sudo ./make install
4. sudo cp fastdfs-nginx-module-master 解压后的目录中src下的mod_fastdfs.conf /etc/fdfs/mod_fastdfs.conf
5. sudo vim /etc/fdfs/mod_fastdfs.conf
# 修改内容
connect_timeout=10
tracker_server=本机ip地址加端口号22122
url_have_group_name=true
store_path0=/home/python/fastdfs/storage
6. sudo cp 解压的fastdfs-master目录中的http.conf /etc/fdfs/http.conf
7. sudo cp 解压的fastdfs-master目录中的mime.types /etc/fdfs/mime.types
8. sudo vim /usr/local/nginx/conf/nginx.conf
# 在http部分中添加配置信息如下:
server {
listen 8888;
server_name_localhost;
location ~/group[0-9]/{
ngx_fastdfs_module;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html{
root html;
}
}
# 启动 Nginx
sudo /usr/local/nginx/sbin/nginx
1. workon django_py3
2.进入fdfs_client-py-master.zip所在目录
3.pip install fdfs_client-py-master.zip
# python和fastDFS交互
from fdfs_client.client import Fdfs.client
client = Fdfs.client('/etc/fdfs/client.conf')
req = client.upload_by_filename('test')
# req 返回值为字典
{'Group name':'group1','Status':'Upload successed',
'Remote file_id':'group1/M00/00/dafdaffa.py',
.... # 省略
}
from django.core.files.storage import Storage
class MyStorage(Storage):
...
from django.conf import settings
from django.core.files.storage import Storage
class MyStorage(Storage):
def __init__(self,option=None):
if not option:
option = settings.CUSTOM_STORAGE_OPTIONS
...
from django.core.files.storage import Storage
from fdfs_client.client import Fdfs_client
# fast dfs 文件存储类
class FDFSStorage(Storage):
# 打开文件时用
def _open(self,name,mode='rb'):
pass
# 保存文件时使用
def _save(self,name,content):
# name:你选择上传文件的名字
# content:包含你上传文件内容的File对象
# 创建一个Fdfs_client对象
client = Fdfs.client('项目路径下的/client.conf')
# 上传文件到fast dfs系统中
res = client.upload_by_buffer(content.read())
# res 返回值为字典 详情请见 -->使用Python客户端上传测试
if res.get('Status') != 'Upload successed':
#上传失败
raise Exception("上传文件到fast dfs失败")
# 获取返回的文件ID
filename = res.get('Remote file_id')
return filename
def exists(self,name):
# Django判断文件名是否可用
return False
def url(self,name):
# 返回访问文件的url路径
# 设置Django文件存储类
DEFAULT_FILE_STORAGE="XXX.XXX.storage.自定义类名"
# xxx项目文件夹下的文件路径,不包含项目文件夹名
python3 manage.py runserver
方法启动服务器导出当前模块数据包的信息:
$ pip3 freeze > package_list.txt
导入到另一台新主机:
$ pip3 install -r package_list.txt
将当前项目源代码复制到远程主机上(scp 命令)
$ sudo scp -a 当前项目源代码 远程主机地址和文件夹
sudo scp -a /home/tarena/django/mysite1.zip [email protected]:/home/root/xxx
请输入root密码:
使用 python manage.py runserver
通常只在开发和测试环境中使用。
当开发结束后,完善的项目代码需要在一个高效稳定的环境中运行,这时可以使用uWSGI
uWSGI是WSGI的一种,它可以让Django、Flask等开发的web站点运行其中.
安装uWSGI
在线安装 uwsgi
$ sudo pip3 install uwsgi
离线安装
在线下载安装包:
$ pip3 download uwsgi
uwsgi-2.0.18.tar.gz
安装
$ tar -xzvf uwsgi-2.0.18.tar.gz
$ cd uwsgi-2.0.18
$ sudo python3 setup.py install
添加配置文件 项目文件夹/uwsgi.ini
示例:
[uwsgi]
# 套接字方式的 IP地址:端口号
# socket=127.0.0.1:8000
# Http通信方式的 IP地址:端口号
http=127.0.0.1:8000
# 项目当前工作目录
chdir=/home/tarena/.../my_project 这里需要换为项目文件夹的绝对路径
# 项目中wsgi.py文件的目录,相对于当前工作目录
wsgi-file=my_project/wsgi.py
# 进程个数
process=4
# 每个进程的线程个数
threads=2
# 服务的pid记录文件
pidfile=uwsgi.pid
# 服务的目志文件位置
daemonize=uwsgi.log
修改settings.py将 DEBUG=True 改为DEBUG=False
修改settings.py 将 ALLOWED_HOSTS = [] 改为 ALLOWED_HOSTS = [’*’]
$ cd 项目文件夹
$ sudo uwsgi --ini 项目文件夹/uwsgi.ini
$ cd 项目文件夹
$ sudo uwsgi --stop uwsgi.pid
Nginx是轻量级的高性能Web服务器,提供了诸如HTTP代理和反向代理、负载均衡、缓存等一系列重要特性,在实践之中使用广泛。
C语言编写,执行效率高
nginx 作用
原理:
客户端请求nginx,再由nginx 请求 uwsgi, 运行django下的python代码
$ sudo apt install nginx
# 在server节点下添加新的location项,指向uwsgi的ip与端口。
server {
...
location / {
uwsgi_pass 127.0.0.1:8000; # 重定向到127.0.0.1的8000端口
include /etc/nginx/uwsgi_params; # 将所有的参数转到uwsgi下
}
...
}
$ sudo /etc/init.d/nginx start|stop|restart|status
# 或
$ sudo service nginx start|stop|restart|status
通过 start,stop,restart,status 可能实现nginx服务的启动、停止、重启、查扑克状态等操作 修改uWSGI配置
项目文件夹/uwsgi.ini
下的Http通信方式改为socket通信方式,如:[uwsgi]
# 去掉如下
# http=127.0.0.1:8000
# 改为
socket=127.0.0.1:8000
$ sudo uwsgi --stop uwsgi.pid
$ sudo uwsgi --ini 项目文件夹/uwsgi.ini
# file : /etc/nginx/sites-available/default
# 新添加location /static 路由配置,重定向到指定的绝对路径
server {
...
location /static {
# root static文件夹所在的绝对路径,如:
root /home/tarena/my_django_project; # 重定向/static请求的路径,这里改为你项目的文件夹
}
...
}
1,安装pip3 sudo apt-get install python3-pip
2,pip3 install --upgrade pip 更新pip3版本
3,更新pip3至最新版本后 - 需要修改 pip3 文件,流程如下
3.1 sudo vi /usr/bin/pip3
3.2 修改代码至如下
from pip import __main__ if __name__ == '__main__': sys.exit(__main__._main())
4,安装django sudo pip3 install Django==1.11.8
5, 安装flask sudo pip3 install flask
6, 安装jwt sudo pip3 install pyjwt
7, 安装uwsgi sudo pip3 install uwsgi
8, 安装pymsql sudo pip3 install pymysql
9, 安装pillow sudo pip3 install pillow [注:django image字段用]
10,官网下载 django-cors-headers-3.0.2.tar ,安装源码包,方法如下
10.1 解压 tar -zxvf django-cors-headers-3.0.2.tar
10.2 cd 至 解压后的目录 并执行 sudo python3 setup.py install
11,安装nginx sudo apt-get install nginx
11.1 flask client 的nginx 配置;进入 /etc/nginx/conf.d/目录;
切换超级用户 sudo su; touch flask.conf 创建flask client配置
配置细节如下:
server {
listen 80;
server_name 192.144.179.50;
charset utf-8;
client_max_body_size 75M;
location / {
include uwsgi_params; # 导入uwsgi配置-
uwsgi_pass 127.0.0.1:5555; # 转发端口,需要和uwsgi配置当中的监听端口一致
uwsgi_param UWSGI_PYTHON /usr/bin/python3; # Python解释器所在的路径,如果有虚拟环境可将路径设置为虚拟环境
uwsgi_param UWSGI_CHDIR /home/ubuntu/tedu/dnblog/client; # 项目根目录
uwsgi_param UWSGI_SCRIPT flask_client.py:app; # 项目的主程序,比如你测试用run.py文件,文件中app = Flask(__name__),那么这里就填run.py:app
}---
}
11, 博客项目 进入 client 目录下,执行 touch blog_client.ini 创建uwsgi配置文件
11.1 配置详情如下
[uwsgi]
socket = 127.0.0.1:5555 #uwsgi监听的地址和端口
chdir = /home/ubuntu/tedu/dnblog/client #项目目录-绝地路径
wsgi-file = flask_client.py #flask启动文件
callable = app #flask中初始化flask的变量
processes = 2
master = true #启动主进程管理子进程
vacuum = true #进程停止后 回收pid
daemonize = server.log #守护进程的日志位置
11.2 执行uwsgi --ini blog_client.ini 启动flask - client
11.3 浏览器执行 公网地址/index 显示博客首页
12 django 启动
12.1 进入项目目录 ,与settings平级目录的地方;执行 touch blog_django_uwsgi.ini 创建 uwsgi配置
12.2 具体配置细节:
[uwsgi] socket = 127.0.0.1:8080
chdir = /home/ubuntu/tedu/dnblog #项目目录
wsgi-file = dnblog/wsgi.py #相对chdir的wsgi.py所在位置路径
processes = 2
pidfile=duwsgi.pid
daemonize=duwsgi.log
vacuum=True
master=True
12.3 nginx添加配置:
进入 /etc/nginx/conf.d/目录;
切换超级用户 sudo su; touch django.conf 创建配置
配置细节如下:
server { listen 8000;
server_name 127.0.0.1;
charset utf-8;
client_max_body_size 75M;
location / {-
include uwsgi_params; # 导入uwsgi配置-
uwsgi_pass 127.0.0.1:8080; # 转发端口,需要和uwsgi配置当中的监听端口一致
}
location /media {
#指定静态资源
root /home/tarena/PycharmProjects/tedu_blog_all/dnblog;
}
}
其他: 1,代码打包至远程服务器,2, 配置数据库 3, 修改client中所有templates中html带有http协议的地址【127.0.0.1 更换 公网IP】
在模板文件夹内添加 404.html 模版,当视图触发Http404 异常时将会被显示
404.html 仅在发布版中(即setting.py 中的 DEBUG=False时) 才起作用
当向应处理函数触发Http404异常时就会跳转到404界面
from django.http import Http404
def xxx_view(request):
raise Http404 # 直接返回404
前端: 即客户端,负责渲染用户显示界面【如web的js动态渲染页面, 安卓, IOS,pc客户端等】
后端:即服务器端,负责接收http请求,处理数据
API:Application Programming Interface 是一些预先定义的函数,或指软件系统不同组成部分衔接的约定
前后端分离 完整请求过程
1,前端通过http请求后端API
2,后端以json形式返回前端数据
3,前端生成用户显示界面【如html , ios , android】
判断前后端分离得核心标准: 谁生成显示页面
1,后端生成【前后端未分离】 ex: flask->render_template django -> HttpResponse(html)
2, 前端生成【前后端分离】
1,各司其职
前端:视觉层面,兼容性,前端性能优化
后端:并发,可用性,性能
2,解耦,前端和后端均易于扩展
3,后端灵活搭配各类前端 - 如安卓等
4,提高用户体验
5,前端+后端可完全并行开发,加快开发效率
问题 | 答案 |
---|---|
如何解决http无状态? | 采用token(详情见下方章节) |
如果前端为JS,如何解决跨域问题? | 采用CORS(详情见下方章节) |
如何解决csrf问题 | 采用token |
Single Page web Application 是否会影响Search Engine Optimization效果 | 会,前后端分离后,往往页面不存在静态文字【例如新闻的详细内容】 |
”老板,这个逻辑到底是让前端做还是后端做啊?“ | 底线原则: 数据校验需要前后端都做 |
”老板,前端工作压力太大了啊“ | 团队协作不能只是嘴上说说 |
动静分离和前后端分离是一个意思么? | 动静分离指的是 css/js/img这类静态资源跟服务器拆开部署,典型方案-静态资源交由CDN厂商处理 |
1,Django/Flask 后端只返回json
2, 前端 -> ex: js向服务器发出ajax请求,获取数据,拿到数据后动态生成html
3, 前端服务和后端服务 分开部署
1,base64 '防君子不防小人'
方法 | 作用 | 参数 | 返回值 |
---|---|---|---|
b64encode | 将输入的参数转化为base64规则的串 | 预加密的明文,类型为bytes;例:b‘guoxiaonao’ | base64对应编码的密文,类型为bytes;例:b’Z3VveGlhb25hbw==’ |
b64decode | 将base64串 解密回 明文 | base64密文,类型为bytes;例:b’Z3VveGlhb25hbw==’ | 参数对应的明文,类型为bytes;例:b’guoxiaonao’ |
urlsafe_b64encode | 作用同b64encode,但是会将 ‘+‘替换成 ‘-’,将’/‘替换成’_’ | 同b64encode | 同b64encode |
urlsafe_b64decode | 作用同b64decode | 同b64decode | 同b64decode |
代码演示:
import base64
#base64加密
s = b'guoxiaonao'
b_s = base64.b64encode(s)
#b_s打印结果为 b'Z3VveGlhb25hbw=='
#base64解密
ss = base64.b64decode(b_s)
#ss打印结果为 b'guoxiaonao'
2,SHA-256 安全散列算法的一种(hash)
hash三大特点:
1)定长输出 2)不可逆 3) 雪崩
import hashlib
s = hashlib.sha256() #创建sha256对象
s.update(b'xxxx') #添加欲hash的内容,类型为 bytes
s.digest() #获取最终结果
3,HMAC-SHA256 是一种通过特别计算方式之后产生的消息认证码,使用**散列算法**同时结合一个**加密密钥**。它可以用来保证数据的完整性,同时可以用来作某个消息的身份验证
import hmac
#生成hmac对象
#第一个参数为加密的key,bytes类型,
#第二个参数为欲加密的串,bytes类型
#第三个参数为hmac的算法,指定为SHA256
h = hmac.new(key, str, digestmod='SHA256 ')
h.digest() #获取最终结果
4,RSA256 非对称加密
1,加密: 公钥加密,私钥解密
2,签名: 私钥签名, 公钥验签
1,header
格式为字典-元数据格式如下
{'alg':'HS256', 'typ':'JWT'}
#alg代表要使用的 算法
#typ表明该token的类别 - 此处必须为 大写的 JWT
该部分数据需要转成json串并用base64 加密
2,payload
格式为字典-此部分分为公有声明和私有声明
公共声明:JWT提供了内置关键字用于描述常见的问题
此部分均为可选项,用户根据自己需求 按需添加key,常见公共声明如下:
{'exp':xxx, # Expiration Time 此token的过期时间的时间戳
'iss':xxx,# (Issuer) Claim 指明此token的签发者
'aud':xxx, #(Audience) Claim 指明此token的
'iat':xxx, # (Issued At) Claim 指明此创建时间的时间戳
'aud':xxx, # (Audience) Claim 指明此token签发面向群体
}
私有声明:用户可根据自己业务需求,添加自定义的key,例如如下:
{'username': 'guoxiaonao'}
公共声明和私有声明均在同一个字典中;转成json串并用base64加密
3,signature 签名
签名规则如下:
根据header中的alg确定 具体算法,以下用 HS256为例
HS256(自定义的key , base64后的header + '.' + base64后的payload)
解释:用自定义的key, 对base64后的header + '.' + base64后的payload进行hmac计算
base64(header) + '.' + base64(payload) + '.' + base64(sign)
最终结果如下: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Imd1b3hpYW9uYW8iLCJpc3MiOiJnZ2cifQ.Zzg1u55DCBqPRGf9z3-NAn4kbA-MJN83SxyLFfc5mmM'
1,解析header, 确认alg
2,签名校验 - 根据传过来的header和payload按 alg指明的算法进行签名,将签名结果和传过来的sign进行对比,若对比一致,则校验通过
3,获取payload自定义内容
1,安装 pip3 install pyjwt
方法 | 参数说明 | 返回值 |
---|---|---|
encode(payload, key, algorithm) | payload: jwt三大组成中的payload,需要组成字典,按需添加公有声明和私有声明 例如: {‘username’: ‘guoxiaonao’, ‘exp’: 1562475112} 参数类型: dict |
token串 返回类型:bytes |
key : 自定义的加密key 参数类型:str |
||
algorithm: 需要使用的加密算法[HS256, RSA256等] 参数类型:str |
||
decode(token,key,algorithm,) | token: token串 参数类型: bytes/str |
payload明文 返回类型:dict |
key : 自定义的加密key ,需要跟encode中的key保持一致 参数类型:str |
||
algorithm: 同encode | ||
issuer: 发布者,若encode payload中添加 ‘iss’ 字段,则可针对该字段校验 参数类型:str |
若iss校验失败,则抛出jwt.InvalidIssuerError | |
audience:签发的受众群体,若encode payload中添加’aud’字段,则可针对该字段校验 参数类型:str |
若aud校验失败,则抛出jwt.InvalidAudienceError |
PS: 若encode得时候 payload中添加了exp字段; 则exp字段得值需为 当前时间戳+此token得有效期时间, 例如希望token 300秒后过期 {‘exp’: time.time() + 300}; 在执行decode时,若检查到exp字段,且token过期,则抛出jwt.ExpiredSignatureError
允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制
1,浏览器自动完成(在请求头中加入特殊头 或 发送特殊请求)
2,服务器需要支持(响应头中需要有特殊头)
**满足以下全部条件**的请求为 **简单请求**
1,请求方法如下:
GET or HEAD or POST
2,请求头仅包含如下:
Accept
Accept-Language
Content-Language
Content-Type
3,Content-Type 仅支持如下三种:
application/x-www-form-urlencoded
multipart/form-data
text/plain
**不满足以上任意一点的请求都是 预检请求**
1,请求
请求头中 携带 Origin,该字段表明自己来自哪个域
2,响应
如果请求头中的Origin在服务器接受范围内, 则返回如下头
响应头 | 作用 | 备注 |
---|---|---|
Access-Control-Allow-Origin | 服务器接受得域 | |
Access-Control-Allow-Credentials | 是否接受Cooike | 可选 |
Access-Control-Expose-Headers | 默认情况下,xhr只能拿到如下响应头:Cache-Control,Content-Language,Content-Type,Expires,Last-Modified;如果有需要获取其他头,需在此指定 | 可选 |
如果服务器不接受此域,则响应头中不包含 Access-Control-Allow-Origin
1,OPTION 请求发起,携带如下请求头
请求头 | 作用 | 备注 |
---|---|---|
Origin | 表明此请求来自哪个域 | 必选 |
Access-Control-Request-Method | 此次请求使用方法 | 必选 |
Access-Control-Request-Headers | 此次请求使用的头 | 必选 |
2,OPTION 接受响应阶段,携带如下响应头
响应头 | 作用 | 备注 |
---|---|---|
Access-Control-Allow-Origin | 同简单请求 | 必选 |
Access-Control-Allow-Methods | 告诉浏览器,服务器接受得跨域请求方法 | 必选 |
Access-Control-Allow-Headers | 返回所有支持的头部,当request有 ‘Access-Control-Request-Headers’时,该响应头必然回复 |
必选 |
Access-Control-Allow-Credentials | 同简单请求 | 可选 |
Access-Control-Max-Age | OPTION请求缓存时间,单位s | 可选 |
3,主请求阶段
请求头 | 作用 | 备注 |
---|---|---|
Origin | 表明此请求来自哪个域 |
4,主请求响应阶段
响应头 | 作用 | 备注 |
---|---|---|
Access-Control-Allow-Origin | 当前服务器接受得域 |
django-cors-headers官网 https://pypi.org/project/django-cors-headers/
直接pip 将把django升级到2.0以上,强烈建议用离线安装方式
配置流程
1,INSTALLED_APPS 中添加 corsheaders
2,MIDDLEWARE 中添加 corsheaders.middleware.CorsMiddleware
位置尽量靠前,官方建议 ‘django.middleware.common.CommonMiddleware’ 上方
3,CORS_ORIGIN_ALLOW_ALL 布尔值 如果为True 白名单不启用
4,CORS_ORIGIN_WHITELIST =[
"https://example.com"
]
5, CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
)
6, CORS_ALLOW_HEADERS = (
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
)
7, CORS_PREFLIGHT_MAX_AGE 默认 86400s
8, CORS_EXPOSE_HEADERS []
9, CORS_ALLOW_CREDENTIALS 布尔值, 默认False
1,资源 **(Resources)**
**网络上的一个实体,或者说是网络上的一个具体信息**,并且每个资源都有一个独一无二得URI与之对应;获取资源-直接访问URI即可
2,**表现层(Representation)**
如何去表现资源 - 即资源得表现形式;如HTML , xml , JPG , json等
3,**状态转化(State Transfer)**
访问一个URI即发生了一次 客户端和服务端得交互;此次交互将会涉及到数据和状态得变化
客户端需要通过某些方式触发具体得变化 - HTTP method 如 GET, POST,PUT,PATCH,DELETE 等
1,每一个URI代表一种资源
2,客户端和服务器端之前传递着资源的某种表现
3,客户端通过HTTP的几个动作 对 资源进行操作 - 发生‘状态转化’
1,协议 - http/https
2,域名:
域名中体现出api字样,如
https://api.example.com
or
https://example.org/api/
3, 版本:
https://api.example.com/v1/
4,路径 -
路径中避免使用动词,资源用名词表示,案例如下
https://api.example.com/v1/users
https://api.example.com/v1/animals
5,HTTP动词语义
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。
具体案例如下:
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
6,巧用查询字符串
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?type_id=1:指定筛选条件
7,状态码
1,用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 - [*]:服务器发生错误
2, 自定义内部code 进行响应
如 返回结构如下 {'code':200, 'data': {}, 'error': xxx}
8,返回结果
根据HTTP 动作的不同,返回结果的结构也有所不同
GET /users:返回资源对象的列表(数组)
GET /users/guoxiaonao:返回单个资源对象
POST /users:返回新生成的资源对象
PUT /users/guoxiaonao:返回完整的资源对象
PATCH /users/guoxiaonao:返回完整的资源对象
DELETE /users/guoxiaonao:返回一个空文档
关系型数据库
跨平台
基于磁盘存储,数据是以文件形式存放在数据库目录/var/lib/mysql下
安装服务端: sudo apt-get install mysql-server
安装客户端: sudo apt-get install mysql-client
配置文件:/etc/mysql
命令集: /usr/bin
数据库存储目录 :/var/lib/mysql
下载MySQL安装包(windows) https://dev.mysql.com/downloads/mysql/
mysql-installer***5.7.***.msi
安装教程去安装
1 C:Windows\system32>mysqld --remove //删除mysql服务
2 C:Windows\system32>mysqld --install //安装mysql服务
3 C:Windows\system32>mysqld --initialize //初始化
4 C:Windows\system32>net start mysql //启动数据库
5 C:Windows\system32>net stop mysql //停止数据库
mysql> use mysql; #选择数据库
mysql> update user set authentication_string=password("123456") where user="root";
mysql> FLUSH PRIVILEGES; #刷新权限
mysql> quit;
mysql>use mysql; #选择数据库
mysql> update user set user="新用户名" where user="root"; #将用户名为root的改为新用户名
mysql> flush privileges; #刷新权限
mysql> quit;
ALTER USER USER() IDENTIFIED BY '123456';
ALTER USER 'jeffrey'@'localhost' IDENTIFIED BY '123456' PASSWORD EXPIRE;
ALTER USER 'jeffrey'@'localhost' IDENTIFIED BY '123456' PASSWORD EXPIRE NEVER;
ALTER USER 'jeffrey'@'localhost' IDENTIFIED BY '123456' PASSWORD EXPIRE DEFAULT;
ALTER USER 'jeffrey'@'localhost' IDENTIFIED BY '123456' PASSWORD EXPIRE INTERVAL 90 DAY;
SET PASSWORD FOR 'jeffrey'@'localhost' = '123456'
update user set authentication_string = password('123456'), password_expired = 'N', password_last_changed = now() where user = 'root';
sudo /etc/init.d/mysql start|stop|restart|status
sudo service mysql start|stop|restart|status
mysql -hIP地址 -u用户名 -p密码
本地连接可省略 -h 选项
1、查看已有库;
show databases;
2、创建库并指定字符集;
create database 库名 charset utf8;
create database 库名 character set utf8;
3、查看当前所在库;
select database();
4、切换库;
use 库名
5、查看库中已有表;
show tables;
6、删除库;
drop database 库名;
1、创建表并指定字符集;
create table 表名(字段名 字段类型 xxx)charset=utf8;
2、查看创建表的语句 (字符集、存储引擎); show create table 表名;
3、查看表结构;
desc 表名;
4、删除表;
drop table 表名1,表名2
1、增 : insert into 表名(字段名) values(),()
2、删 : delete from 表名 where 条件
3、改 : update 表名 set 字段名=值 where 条件
4、查 : select 字段名 from 表名 where 条件
1、增 : alter table 表名 add 字段名 字段类型 first|after 字段名
2、删 : alter table 表名 drop 字段名;
3、改 : alter table 表名 modify 字段名 字段类型
4、表重命名:
alter table 表名 rename 新表名
# 安装
sudo apt-get install redis-server
# 服务端启动
sudo /etc/init.d/redis-server status | start | stop | restart
# 客户端连接
redis-cli -h IP地址 -p 6379 -a 密码
1、下载安装包
https://github.com/ServiceStack/redis-windows/blob/master/downloads/redis-64.3.0.503.zip
2、解压
3、启动服务端
双击解压后的 redis-server.exe
4、客户端连接
双击解压后的 redis-cli.exe
# Windows下产生的问题:关闭终端后服务终止
# 解决方案:将Redis服务安装到本地服务
1、重命名 redis.windows.conf 为 redis.conf,作为redis服务的配置文件
2、cmd命令行,进入到redis-server.exe所在目录
3、执行:redis-server --service-install redis.conf --loglevel verbose
4、计算机-管理-服务-Redis-启动
# 卸载
到 redis-server.exe 所在路径执行:
1、redis-server --service-uninstall
2、sc delete Redis
# 切换库(number的值在0-15之间,db0 ~ db15)
select number
# 查看键
keys 表达式 # keys *
# 数据类型
TYPE key
# 键是否存在
exists key
# 删除键
del key
# 键重命名
rename key newkey
# 清除当前库中所有数据(慎用)
flushdb
# 清除所有库中所有数据(慎用)
flushall
# 1. 设置一个key-value
set key value
# 2. 获取key的值
get key
# 3. key不存在时再进行设置(nx)
set key value nx # not exists
# 4. 设置过期时间(ex)
set key value ex seconds
# 5. 同时设置多个key-value
mset key1 value1 key2 value2 key3 value3
# 6. 同时获取多个key-value
mget key1 key2 key3
#################################### 字符串常用命令==稍微了解
# 1.获取长度
strlen key
# 2.获取指定范围切片内容
getrange key start stop
# 3.从索引值开始,value替换原内容
setrange key index value
# 4.追加拼接value的值
append key value
# 整数操作
INCRBY key 步长
DECRBY key 步长
INCR key : +1操作
DECR key : -1操作
# 应用场景: 抖音上有人关注你了,是不是可以用INCR呢,如果取消关注了是不是可以用DECR
# 浮点数操作: 自动先转为数字类型,然后再进行相加减,不能使用append
incrbyfloat key step
127.0.0.1:6379> mset wang:email [email protected] guo:email [email protected]
OK
127.0.0.1:6379> mget wang:email guo:email
1) "[email protected]"
2) "[email protected]"
127.0.0.1:6379>
# 字符串操作
1、set key value
2、set key value nx
3、get key
3、mset key1 value1 key2 value2
4、mget key1 key2 key3
5、set key value nx ex seconds
6、strlen key
# 返回旧值并设置新值(如果键不存在,就创建并赋值)
7、getset key value
# 数字操作
7、incrby key 步长
8、decrby key 步长
9、incr key
10、decr key
11、incrbyfloat key number#(可为正数或负数)
# 设置过期时间的两种方式
# 方式一
1、set key value ex 3
# 方式二
1、set key value
2、expire key 5 # 秒
3、pexpire key 5 # 毫秒
# 查看存活时间
ttl key
# 删除过期
persist key
string数据类型注意
# key值取值原则
1、key值不宜过长,消耗内存,且在数据中查找这类键值的计算成本高
2、不宜过短,可读性较差
# 值
1、一个字符串类型的值最多能存储512M内容
特点
1、元素是字符串类型
2、列表头尾增删快,中间增删慢,增删元素是常态
3、元素可重复
4、最多可包含2^32 -1个元素
5、索引同python列表
# 增
1、从列表头部压入元素
LPUSH key value1 value2
2、从列表尾部压入元素
RPUSH key value1 value2
3、从列表src尾部弹出1个元素,压入到列表dst的头部
RPOPLPUSH src dst
4、在列表指定元素后/前插入元素
LINSERT key after|before value newvalue
# 查
5、查看列表中元素
LRANGE key start stop
# 查看列表中所有元素: LRANGE key 0 -1
6、获取列表长度
LLEN key
# 删
7、从列表头部弹出1个元素
LPOP key
8、从列表尾部弹出1个元素
RPOP key
9、列表头部,阻塞弹出,列表为空时阻塞
BLPOP key timeout
10、列表尾部,阻塞弹出,列表为空时阻塞
BRPOP key timeout
# 关于BLPOP 和 BRPOP
1、如果弹出的列表不存在或者为空,就会阻塞
2、超时时间设置为0,就是永久阻塞,直到有数据可以弹出
3、如果多个客户端阻塞再同一个列表上,使用First In First Service原则,先到先服务
11、删除指定元素
LREM key count value
count>0:表示从头部开始向表尾搜索,移除与value相等的元素,数量为count
count<0:表示从尾部开始向表头搜索,移除与value相等的元素,数量为count
count=0:移除表中所有与value相等的值
12、保留指定范围内的元素
LTRIM key start stop
LRTIM mylist1 0 2 # 只保留前3条
# 应用场景: 保存微博评论最后500条
LTRIM weibo:comments 0 499
# 改
13、LSET key index newvalue
1、Ubuntu
/etc/redis/redis.conf
mysql的配置文件在哪里? : /etc/mysql/mysql.conf.d/mysqld.cnf
2、windows 下载解压后的redis文件夹中
redis.windows.conf
redis.conf
1、requirepass 密码
2、重启服务
sudo /etc/init.d/redis-server restart
3、客户端连接
redis-cli -h 127.0.0.1 -p 6379 -a 123456
127.0.0.1:6379>ping
1、注释掉本地IP地址绑定
69行: # bind 127.0.0.1 ::1
2、关闭保护模式(把yes改为no)
88行: protected-mode no
3、重启服务
sudo /etc/init.d/redis-server restart
# cmd命令行
1、e:
2、cd Redis3.0
3、redis-cli -h x.x.x.x -a 123456
4、x.x.x.x:6379>ping
Python与redis交互注意
1、r.set('name','Tom',ex=5,nx=True)
2、r.mset({'user1:name':'Tom','user1:age':'25'})
# 有元素时返回弹出元素,否则返回None
3、r.brpop('mylist',3)
模块(redis)
Ubuntu
sudo pip3 install redis
Windows
# 方法1. python -m pip install redis
# 方法2. 以管理员身份打开cmd命令行
pip install redis
使用流程
import redis
# 创建数据库连接对象
r = redis.Redis(host='127.0.0.1',port=6379,db=0,password='123456')
import redis
# 创建连接对象
r = redis.Redis(host='192.168.153.146',port=6379,db=0)
# r.keys('*') -> 列表
key_list = r.keys('*')
for key in key_list:
print(key.decode())
# b'list'
print(r.type('mylist'))
# 返回值: 0 或者 1
print(r.exists('spider:urls'))
# 删除key
r.delete('mylist2')
import redis
r = redis.Redis(host='192.168.153.146',port=6379,db=0)
# pylist: ['pythonweb','socket','pybase']
r.lpush('pylist','pybase','socket','pythonweb')
# pylist: ['spider','pythonweb','socket','pybase']
r.linsert('pylist','before','pythonweb','spider')
# 4
print(r.llen('pylist'))
# [b'spider', b'pythonweb', b'socket', b'pybase']
print(r.lrange('pylist',0,-1))
# b'pybase'
print(r.rpop('pylist'))
# [b'spider', b'pythonweb']
r.ltrim('pylist',0,1)
while True:
# 如果列表中为空时,则返回None
result = r.brpop('pylist',1)
if result:
print(result)
else:
break
r.delete('pylist')
import redis
import time
import random
from multiprocessing import Process
class Spider(object):
def __init__(self):
r = redis.Redis(host='192.168.153.146',port=6379,db=0)
def product(self):
# 生产者开始生产url地址
for page in range(0,67):
url = 'http://app.mi.com/category/2#page=%s' % str(page)
self.r.lpush('spider:urls',url)
time.sleep(random.randint(1,3))
def consumer(self):
while True:
# url: (b'spider:urls',b'http://xiaomixxx')
url = self.r.brpop('spider:urls', 5)
if url:
print('正在抓取:', url[1].decode())
else:
print('抓取结束')
break
def run(self):
p1 = Process(target=self.product)
p2 = Process(target=self.consumer)
p1.start()
p2.start()
p1.join()
p2.join()
if __name__ == '__main__':
spider = Spider()
spider.run()
import redis
r = redis.Redis(host='192.168.153.146',port=6379,db=0)
r.set('username','guods')
print(r.get('username'))
# mset参数为字典
r.mset({'username':'xiaoze','password':'123456'})
# 列表: [b'xiaoze', b'123456']
print(r.mget('username','password'))
# 6
print(r.strlen('username'))
# 数值操作
r.set('age','25')
r.incrby('age',10)
r.decrby('age',10)
r.incr('age')
r.decr('age')
r.incrbyfloat('age',3.3)
r.incrbyfloat('age',-3.3)
print(r.get('age'))
r.delete('username')
定义
1、位图不是真正的数据类型,它是定义在字符串类型中
2、一个字符串类型的值最多能存储512M字节的内容,位上限:2^32
# 1MB = 1024KB
# 1KB = 1024Byte(字节)
# 1Byte = 8bit(位)
# 设置某一位上的值(offset是偏移量,从0开始)
setbit key offset value
# 获取某一位上的值
GETBIT key offset
# 统计键所对应的值中有多少个 1
BITCOUNT key
# 默认扩展位以0填充
127.0.0.1:6379> set mykey ab
OK
127.0.0.1:6379> get mykey
"ab"
127.0.0.1:6379> SETBIT mykey 0 1
(integer) 0
127.0.0.1:6379> get mykey
"\xe1b"
127.0.0.1:6379>
127.0.0.1:6379> GETBIT mykey 3
(integer) 0
127.0.0.1:6379> GETBIT mykey 0
(integer) 1
127.0.0.1:6379>
127.0.0.1:6379> SETBIT user001 1 1
(integer) 0
127.0.0.1:6379> SETBIT user001 30 1
(integer) 0
127.0.0.1:6379> bitcount user001
(integer) 2
127.0.0.1:6379>
# 网站用户的上线次数统计(寻找活跃用户)
用户名为key,上线的天作为offset,上线设置为1
# 示例
用户名为 user1:login 的用户,今年第1天上线,第30天上线
SETBIT user1:login 0 1
SETBIT user1:login 29 1
BITCOUNT user1:login
import redis
r = redis.Redis(host='127.0.0.1',port=6379,db=0)
# user001: 一年中第5天和200天登录
r.setbit('user:001',4,1)
r.setbit('user:001',199,1)
# user002: 一年中第100天和第300天登录
r.setbit('user:002',99,1)
r.setbit('user:002',299,1)
# user:003: 登录了100次以上
for i in range(1,366,2):
r.setbit('user:003',i,1)
# user:004: 登录了100次以上
for i in range(1,366,3):
r.setbit('user:004',i,1)
user_list = r.keys('user:*')
# 存放活跃用户列表
active_users = []
# 存放不活跃用户列表
no_active_users = []
for user in user_list:
login_count = r.bitcount(user)
if login_count >= 100:
active_users.append((user,login_count))
else:
no_active_users.append((user,login_count))
print('活跃用户:',active_users)
print('不活跃用户:',no_active_users)
1、由field和关联的value组成的键值对
2、field和value是字符串类型
3、一个hash中最多包含2^32-1个键值对
1、节约内存空间
2、每创建一个键,它都会为这个键储存一些附加的管理信息(比如这个键的类型,这个键最后一次被访问的时间等)
3、键越多,redis数据库在储存附件管理信息方面耗费内存越多,花在管理数据库键上的CPU也会越多
1、使用二进制位操作命令:SETBIT、GETBIT、BITCOUNT等,如果想使用这些操作,只能用字符串键
2、使用过期键功能:键过期功能只能对键进行过期操作,而不能对散列的字段进行过期操作
# 1、设置单个字段
HSET key field value
HSETNX key field value
# 2、设置多个字段
HMSET key field value field value
# 3、返回字段个数
HLEN key
# 4、判断字段是否存在(不存在返回0)
HEXISTS key field
# 5、返回字段值
HGET key field
# 6、返回多个字段值
HMGET key field filed
# 7、返回所有的键值对
HGETALL key
# 8、返回所有字段名
HKEYS key
# 9、返回所有值
HVALS key
# 10、删除指定字段
HDEL key field
# 11、在字段对应值上进行整数增量运算
HINCRBY key filed increment
# 12、在字段对应值上进行浮点数增量运算
HINCRBYFLOAT key field increment
# 1、更新一条数据的属性,没有则新建
hset(name, key, value)
# 2、读取这条数据的指定属性, 返回字符串类型
hget(name, key)
# 3、批量更新数据(没有则新建)属性,参数为字典
hmset(name, mapping)
# 4、批量读取数据(没有则新建)属性
hmget(name, keys)
# 5、获取这条数据的所有属性和对应的值,返回字典类型
hgetall(name)
# 6、获取这条数据的所有属性名,返回列表类型
hkeys(name)
# 7、删除这条数据的指定属性
hdel(name, *keys)
'''设置1个字段,更改1个字段,设置多个字段,获取相关信息'''
import redis
r = redis.Redis(host='127.0.0.1',port=6379,db=0)
# 设置
r.hset('user1','name','bujingyun')
# 更新
r.hset('user1','name','kongci')
# 取数据
print(r.hget('user1','name'))
# 一次设置多个field和value
user_dict = {
'password':'123456',
'gender':'F',
'height':'165'
}
r.hmset('user1',user_dict)
# 获取所有数据,字典
print(r.hgetall('user1'))
# 获取所有fields和所有values
print(r.hkeys('user1'))
print(r.hvals('user1'))
1、用户ID为key,Field为好友ID,Value为关注时间
key field value
user:10000 user:606 20190520
user:605 20190521
2、用户维度统计
统计数包括:关注数、粉丝数、喜欢商品数、发帖数
用户为key,不同维度为field,value为统计数
比如关注了5人
HSET user:10000 fans 5
HINCRBY user:10000 fans 1
用户想要查询个人信息
1、到redis缓存中查询个人信息
2、redis中查询不到,到mysql查询,并缓存到redis
3、再次查询个人信息
import redis
import pymysql
# 1. 先到redis中查询
# 2. redis中没有,到mysql查询,缓存到redis(设置过期时间)
# 3. 再查询1次
r = redis.Redis(host='192.168.153.148',port=6379,db=0)
username = input('请输入用户名:')
result = r.hgetall(username)
if result:
print(result)
else:
# redis中没有缓存,需要到mysql中查询
db = pymysql.connect(
host='192.168.153.148',
user='tiger',
password='123456',
database='userdb',
charset='utf8'
)
cursor = db.cursor()
sele = 'select age,gender from user where username=%s'
cursor.execute(sele,[username])
# userinfo: (('guoxiaonao',36,'M'),)
userinfo = cursor.fetchall()
if not userinfo:
print('用户不存在')
else:
# 打印输出
print('mysql',userinfo)
# 缓存到redis
user_dict = {
'age':userinfo[0][0],
'gender':userinfo[0][1]
}
r.hmset(username,user_dict)
# 设置过期时间
r.expire(username,30)
import redis
import pymysql
class Update(object):
def __init__(self):
self.db = pymysql.connect('127.0.0.1', 'root', '123456','userdb', charset='utf8')
self.cursor = self.db.cursor()
self.r = redis.Redis(host='127.0.0.1', port=6379, db=0)
# 更新mysql表记录
def update_mysql(self,score,username):
upd = 'update user set score=%s where name=%s'
try:
self.cursor.execute(upd,[score,username])
self.db.commit()
return True
except Exception as e:
self.db.rollback()
print('Failed',e)
# 同步到redis数据库
def update_redis(self,username,score):
result = self.r.hgetall(username)
# 存在,更新score字段的值
# 不存在,缓存整个用户信息
if result:
self.r.hset(username,'score',score)
else:
# 到mysql中查询最新数据,缓存到redis中
self.select_mysql(username)
#
def select_mysql(self,username):
sel = 'select age,gender,score from user where name=%s'
self.cursor.execute(sel,[username])
result = self.cursor.fetchall()
# 缓存到redis数据库
user_dict = {
'age' : result[0][0],
'gender' : result[0][1],
'score' : result[0][2]
}
self.r.hmset(username,user_dict)
self.r.expire(username,60)
def main(self):
username = input('请输入用户名:')
new_score = input('请输入新成绩:')
if self.update_mysql(new_score,username):
self.update_redis(username,new_score)
else:
print('更改信息失败')
if __name__ == '__main__':
syn = Update()
syn.main()
# 1、增加一个或者多个元素,自动去重
SADD key member1 member2
# 2、查看集合中所有元素
SMEMBERS key
# 3、删除一个或者多个元素,元素不存在自动忽略
SREM key member1 member2
# 4、元素是否存在
SISMEMBER key member
# 5、随机返回集合中指定个数的元素,默认为1个
SRANDMEMBER key [count]
# 6、弹出成员
SPOP key [count]
# 7、返回集合中元素的个数,不会遍历整个集合,只是存储在键当中了
SCARD key
# 8、把元素从源集合移动到目标集合
SMOVE source destination member
# 9、差集(number1 1 2 3 number2 1 2 4 结果为3)
SDIFF key1 key2
# 10、差集保存到另一个集合中
SDIFFSTORE destination key1 key2
# 11、交集
SINTER key1 key2
SINTERSTORE destination key1 key2
# 11、并集
SUNION key1 key2
SUNIONSTORE destination key1 key2
# 需求: 当用户访问另一个用户的时候,会显示出两个用户共同关注过哪些相同的用户
# 设计: 将每个用户关注的用户放在集合中,求交集即可
# 实现:
user001 = {'peiqi','qiaozhi','danni'}
user002 = {'peiqi','qiaozhi','lingyang'}
user001和user002的共同关注为:
SINTER user001 user002
结果为: {'peiqi','qiaozhi'}
# 1、给name对应的集合中添加元素
sadd(name,values)
r.sadd("set_name","tom")
r.sadd("set_name","tom","jim")
# 2、获取name对应的集合的所有成员: python集合
smembers(name)
r.smembers('set_name')
# 3、获取name对应的集合中的元素个数
scard(name)
r.scard("set_name")
# 4、检查value是否是name对应的集合内的元素:True|False
sismember(name, value)
r.sismember('set_name','tom')
# 5、随机删除并返回指定集合的一个元素
spop(name)
member = r.spop('set_name')
# 6、删除集合中的某个元素
srem(name, value)
r.srem("set_name", "tom")
# 7、获取多个name对应集合的交集
sinter(keys, *args)
r.sadd("set_name","a","b")
r.sadd("set_name1","b","c")
r.sadd("set_name2","b","c","d")
print(r.sinter("set_name","set_name1","set_name2"))
#输出:{b'b'}
# 8、获取多个name对应的集合的并集: python集合
sunion(keys, *args)
r.sunion("set_name","set_name1","set_name2")
import redis
r = redis.Redis(host='127.0.0.1',port=6379,db=0)
# user1关注的人
r.sadd('user1:focus','peiqi','qiaozhi','danni')
# user2关注的人
r.sadd('user2:focus','peiqi','qiaozhi','lingyang')
# 共同关注: 求交集 {b'qiaozhi', b'peiqi'}
focus_set = r.sinter('user1:focus','user2:focus')
# 创建空集合,存放最终结果
result = set()
for focus in focus_set:
result.add(focus.decode())
print(result)
# 在有序集合中添加一个成员
zadd key score member
# 查看指定区间元素(升序)
zrange key start stop [withscores]
# 查看指定区间元素(降序)
ZREVRANGE key start stop [withscores]
# 查看指定元素的分值
ZSCORE key member
# 返回指定区间元素
# offset : 跳过多少个元素
# count : 返回几个
# 小括号 : 开区间 zrangebyscore fruits (2.0 8.0
zrangebyscore key min max [withscores] [limit offset count]
# 每页显示10个成员,显示第5页的成员信息:
# limit 40 10
# MySQL: 每页显示10条记录,显示第5页的记录
# limit 40,10
# limit 2,3 显示: 第3 4 5条记录
# 删除成员
zrem key member
# 增加或者减少分值
zincrby key increment member
# 返回元素排名
zrank key member
# 返回元素逆序排名
zrevrank key member
# 删除指定区间内的元素
zremrangebyscore key min max
# 返回集合中元素个数
zcard key
# 返回指定范围中元素的个数
zcount key min max
zcount salary 6000 8000
zcount salary (6000 8000# 6000
zcount salary (6000 (8000#6000
# 并集
zunionstore destination numkeys key [weights 权重值] [AGGREGATE SUM|MIN|MAX]
# zunionstore salary3 2 salary salary2 weights 1 0.5 AGGREGATE MAX
# 2代表集合数量,weights之后 权重1给salary,权重0.5给salary2集合,算完权重之后执行聚合AGGREGATE
# 交集:和并集类似,只取相同的元素
ZINTERSTORE destination numkeys key1 key2 WEIGHTS weight AGGREGATE SUM(默认)|MIN|MAX
import redis
r = redis.Redis(host='127.0.0.1',port=6379,db=0)
# 注意第二个参数为字典
# 命令行:ZADD salary 6000 tom 8000 jim
r.zadd('salary',{'tom':6000,'jim':8000,'jack':12000})
# 结果为列表中存放元组[(),(),()]
print(r.zrange('salary',0,-1,withscores=True))
print(r.zrevrange('salary',0,-1,withscores=True))
# start:起始值,num:显示条数
print(r.zrangebyscore('salary',6000,12000,start=1,num=2,withscores=True))
# 删除
r.zrem('salary','tom')
print(r.zrange('salary',0,-1,withscores=True))
# 增加分值
r.zincrby('salary',5000,'jack')
print(r.zrange('salary',0,-1,withscores=True))
# 返回元素排名
print(r.zrank('salary','jack'))
print(r.zrevrank('salary','jack'))
# 删除指定区间内的元素
r.zremrangebyscore('salary',6000,8000)
print(r.zrange('salary',0,-1,withscores=True))
# 统计元素个数
print(r.zcard('salary'))
# 返回指定范围内元素个数
print(r.zcount('salary',6000,20000))
# 并集
r.zadd('salary2',{'jack':17000,'lucy':8000})
r.zunionstore('salary3',('salary','salary2'),aggregate='max')
print(r.zrange('salary3',0,-1,withscores=True))
# 交集
r.zinterstore('salary4',('salary','salary2'),aggregate='max')
print(r.zrange('salary4',0,-1,withscores=True))
持久化定义
为什么需要持久化
1、保存真实的数据
2、将服务器包含的所有数据库数据以二进制文件的形式保存到硬盘里面
3、默认文件名 :/var/lib/redis/dump.rdb
127.0.0.1:6379> SAVE
OK
# 特点
1、执行SAVE命令过程中,redis服务器将被阻塞,无法处理客户端发送的命令请求,在SAVE命令执行完毕后,服务器才会重新开始处理客户端发送的命令请求
2、如果RDB文件已经存在,那么服务器将自动使用新的RDB文件代替旧的RDB文件
# 工作中定时持久化保存一个文件
127.0.0.1:6379> BGSAVE
Background saving started
# 执行过程如下
1、客户端 发送 BGSAVE 给服务器
2、服务器马上返回 Background saving started 给客户端
3、服务器 fork() 子进程做这件事情
4、服务器继续提供服务
5、子进程创建完RDB文件后再告知Redis服务器
# 配置文件相关操作
/etc/redis/redis.conf
263行: dir /var/lib/redis # 表示rdb文件存放路径
253行: dbfilename dump.rdb # 文件名
# 两个命令比较
SAVE比BGSAVE快,因为需要创建子进程,消耗额外的内存
# 补充:可以通过查看日志文件来查看redis都做了哪些操作
# 日志文件:配置文件中搜索 logfile
logfile /var/log/redis/redis-server.log
# 命令行示例
redis>save 300 10
表示如果距离上一次创建RDB文件已经过去了300秒,并且服务器的所有数据库总共已经发生了不少于10次修改,那么自动执行BGSAVE命令
redis>save 60 10000
表示如果距离上一次创建rdb文件已经过去60秒,并且服务器所有数据库总共已经发生了不少于10000次修改,那么执行bgsave命令
# redis配置文件默认
218行: save 900 1
219行: save 300 10
220行: save 60 10000
1、只要三个条件中的任意一个被满足时,服务器就会自动执行BGSAVE
2、每次创建RDB文件之后,服务器为实现自动持久化而设置的时间计数器和次数计数器就会被清零,并重新开始计数,所以多个保存条件的效果不会叠加
1、存储的是命令,而不是真实数据
2、默认不开启
# 开启方式(修改配置文件)
1、/etc/redis/redis.conf
672行: appendonly yes # 把 no 改为 yes
676行: appendfilename "appendonly.aof"
2、重启服务
sudo /etc/init.d/redis-server restart
1、创建RDB文件需要将服务器所有的数据库的数据都保存起来,这是一个非常消耗资源和时间的操作,所以服务器需要隔一段时间才创建一个新的RDB文件,也就是说,创建RDB文件不能执行的过于频繁,否则会严重影响服务器的性能
2、可能丢失数据
# 原理
1、每当有修改数据库的命令被执行时,服务器就会将执行的命令写入到AOF文件的末尾
2、因为AOF文件里面存储了服务器执行过的所有数据库修改的命令,所以给定一个AOF文件,服务器只要重新执行一遍AOF文件里面包含的所有命令,就可以达到还原数据库的目的
# 优点
用户可以根据自己的需要对AOF持久化进行调整,让Redis在遭遇意外停机时不丢失任何数据,或者只丢失一秒钟的数据,这比RDB持久化丢失的数据要少的多
# 因为
虽然服务器执行一个修改数据库的命令,就会把执行的命令写入到AOF文件,但这并不意味着AOF文件持久化不会丢失任何数据,在目前常见的操作系统中,执行系统调用write函数,将一些内容写入到某个文件里面时,为了提高效率,系统通常不会直接将内容写入硬盘里面,而是将内容放入一个内存缓存区(buffer)里面,等到缓冲区被填满时才将存储在缓冲区里面的内容真正写入到硬盘里
# 所以
1、AOF持久化:当一条命令真正的被写入到硬盘里面时,这条命令才不会因为停机而意外丢失
2、AOF持久化在遭遇停机时丢失命令的数量,取决于命令被写入到硬盘的时间
3、越早将命令写入到硬盘,发生意外停机时丢失的数据就越少,反之亦然
# 打开配置文件:/etc/redis/redis.conf,找到相关策略如下
1、701行: alwarys
服务器每写入一条命令,就将缓冲区里面的命令写入到硬盘里面,服务器就算意外停机,也不会丢失任何已经成功执行的命令数据
2、702行: everysec(# 默认)
服务器每一秒将缓冲区里面的命令写入到硬盘里面,这种模式下,服务器即使遭遇意外停机,最多只丢失1秒的数据
3、703行: no
服务器不主动将命令写入硬盘,由操作系统决定何时将缓冲区里面的命令写入到硬盘里面,丢失命令数量不确定
# 运行速度比较
always:速度慢
everysec和no都很快,默认值为everysec
为了让AOF文件的大小控制在合理范围,避免胡乱增长,redis提供了AOF重写功能,通过这个功能,服务器可以产生一个新的AOF文件
-- 新的AOF文件记录的数据库数据和原由的AOF文件记录的数据库数据完全一样
-- 新的AOF文件会使用尽可能少的命令来记录数据库数据,因此新的AOF文件的提及通常会小很多
-- AOF重写期间,服务器不会被阻塞,可以正常处理客户端发送的命令请求
原有AOF文件 | 重写后的AOF文件 |
---|---|
select 0 | SELECT 0 |
sadd myset peiqi | SADD myset peiqi qiaozhi danni lingyang |
sadd myset qiaozhi | SET msg ‘hello tarena’ |
sadd myset danni | RPUSH mylist 2 3 5 |
sadd myset lingyang | |
INCR number | |
INCR number | |
DEL number | |
SET message ‘hello world’ | |
SET message ‘hello tarena’ | |
RPUSH mylist 1 2 3 | |
RPUSH mylist 5 | |
LPOP mylist |
1、客户端向服务器发送BGREWRITEAOF命令
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
2、修改配置文件让服务器自动执行BGREWRITEAOF命令
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 解释
1、只有当AOF文件的增量大于100%时才进行重写,也就是大一倍的时候才触发
# 第一次重写新增:64M
# 第二次重写新增:128M
# 第三次重写新增:256M(新增128M)
RDB持久化 | AOF持久化 |
---|---|
全量备份,一次保存整个数据库 | 增量备份,一次保存一个修改数据库的命令 |
保存的间隔较长 | 保存的间隔默认为一秒钟 |
数据还原速度快 | 数据还原速度一般,冗余命令多,还原速度慢 |
执行SAVE命令时会阻塞服务器,但手动或者自动触发的BGSAVE不会阻塞服务器 | 无论是平时还是进行AOF重写时,都不会阻塞服务器 |
# 用redis用来存储真正数据,每一条都不能丢失,都要用always,有的做缓存,有的保存真数据,我可以开多个redis服务,不同业务使用不同的持久化,新浪每个服务器上有4个redis服务,整个业务中有上千个redis服务,分不同的业务,每个持久化的级别都是不一样的。
既有dump.rdb,又有appendonly.aof,恢复时找谁?
先找appendonly.aof
# 设置密码
1、requirepass password
# 开启远程连接
2、bind 127.0.0.1 ::1 注释掉
3、protected-mode no 把默认的 yes 改为 no
# rdb持久化-默认配置
4、dbfilename 'dump.rdb'
5、dir /var/lib/redis
# rdb持久化-自动触发(条件)
6、save 900 1
7、save 300 10
8、save 60 10000
# aof持久化开启
9、appendonly yes
10、appendfilename 'appendonly.aof'
# aof持久化策略
11、appendfsync always
12、appendfsync everysec # 默认
13、appendfsync no
# aof重写触发
14、auto-aof-rewrite-percentage 100
15、auto-aof-rewrite-min-size 64mb
# 设置为从服务器
16、salveof <master-ip> <master-port>
1、配置文件: /etc/redis/redis.conf
2、备份文件: /var/lib/redis/*.rdb|*.aof
3、日志文件: /var/log/redis/redis-server.log
4、启动文件: /etc/init.d/redis-server
# /etc/下存放配置文件
# /etc/init.d/下存放服务启动文件
# 从服务端
redis-server --port 6300 --slaveof 127.0.0.1 6379
# 从客户端
redis-cli -p 6300
127.0.0.1:6300> keys *
# 发现是复制了原6379端口的redis中数据
127.0.0.1:6300> set mykey 123
(error) READONLY You can't write against a read only slave.
127.0.0.1:6300>
# 从服务器只能读数据,不能写数据
# 两条命令
1、>slaveof IP PORT
2、>slaveof no one
# 服务端启动
redis-server --port 6301
# 客户端连接
tarena@tedu:~$ redis-cli -p 6301
127.0.0.1:6301> keys *
1) "myset"
2) "mylist"
127.0.0.1:6301> set mykey 123
OK
# 切换为从
127.0.0.1:6301> slaveof 127.0.0.1 6379
OK
127.0.0.1:6301> set newkey 456
(error) READONLY You can't write against a read only slave.
127.0.0.1:6301> keys *
1) "myset"
2) "mylist"
# 再切换为主
127.0.0.1:6301> slaveof no one
OK
127.0.0.1:6301> set name hello
OK
# 每个redis服务,都有1个和他对应的配置文件
# 两个redis服务
1、6379 -> /etc/redis/redis.conf
2、6300 -> /home/tarena/redis_6300.conf
# 修改配置文件
vi redis_6300.conf
slaveof 127.0.0.1 6379
port 6300
# 启动redis服务
redis-server redis_6300.conf
# 客户端连接测试
redis-cli -p 6300
127.0.0.1:6300> hset user:1 username guods
(error) READONLY You can't write against a read only slave.
1、启动端口6400redis,设置为6379的slave
redis-server --port 6400
redis-cli -p 6400
redis>slaveof 127.0.0.1 6379
2、启动端口6401redis,设置为6379的slave
redis-server --port 6401
redis-cli -p 6401
redis>slaveof 127.0.0.1 6379
3、关闭6379redis
sudo /etc/init.d/redis-server stop
4、把6400redis设置为master
redis-cli -p 6401
redis>slaveof no one
5、把6401的redis设置为6400redis的salve
redis-cli -p 6401
redis>slaveof 127.0.0.1 6400
# 这是手动操作,效率低,而且需要时间,有没有自动的???
# 共3台redis的服务器,如果是不同机器端口号可以是一样的
1、启动6379的redis服务器
sudo /etc/init.d/redis-server start
2、启动6380的redis服务器,设置为6379的从
redis-server --port 6380
tarena@tedu:~$ redis-cli -p 6380
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
3、启动6381的redis服务器,设置为6379的从
redis-server --port 6381
tarena@tedu:~$ redis-cli -p 6381
127.0.0.1:6381> slaveof 127.0.0.1 6379
# 1、安装redis-sentinel
sudo apt install redis-sentinel
验证: sudo /etc/init.d/redis-sentinel stop
# 2、新建配置文件sentinel.conf
port 26379
Sentinel monitor tedu 127.0.0.1 6379 1
# 3、启动sentinel
方式一: redis-sentinel sentinel.conf
方式二: redis-server sentinel.conf --sentinel
#4、将master的redis服务终止,查看从是否会提升为主
sudo /etc/init.d/redis-server stop
# 发现提升6381为master,其他两个为从
# 在6381上设置新值,6380查看
127.0.0.1:6381> set name tedu
OK
# 启动6379,观察日志,发现变为了6381的从
主从+哨兵基本就够用了
# sentinel监听端口,默认是26379,可以修改
port 26379
# 告诉sentinel去监听地址为ip:port的一个master,这里的master-name可以自定义,quorum是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效
sentinel monitor <master-name> <ip> <redis-port> <quorum>
1、安装sentinel
sudo apt-get install redis-sentinel
2、创建配置文件 sentinel.conf
port 26379
Sentinel monitor 名字 IP PORT 投票数
3、启动sentinel开始监控
redis-sentinel sentinel.conf
高并发产生的问题?
1、购票: 多个用户抢到同一张票?
2、购物: 库存只剩1个,被多个用户成功买到?
... ...
怎么办?
在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段
原理
1、多个客户端先到redis数据库中获取一把锁,得到锁的用户才可以操作数据库
2、此用户操作完成后释放锁,下一个成功获取锁的用户再继续操作数据库
实现
set key value nx ex 3
# 见图: 分布式锁原理.png
mysql -uroot -p123456
mysql>create database blog charset utf8;
1、python3 manage.py makemigrations
2、python3 manage.py migrate
3、insert into user_profile values ('guoxiaonao','guoxiaonao','[email protected]','123456','aaaaaaaa','bbbbbbbb','cccccccc');
1、python3 manage.py runserver
2、查看项目的 urls.py 路由,打开firefox浏览器输入地址:http://127.0.0.1:8000/test/
# 返回结果: {"code": 200}
1、user/models.py添加:
score = models.IntegerField(verbose_name=u'分数',null=True,default=0)
2、同步到数据库
python3 manage.py makemigrations user
python3 manage.py migrate user
3、到数据库中确认查看
from user.models import UserProfile
def test(request):
u = UserProfile.objects.get(username='guoxiaonao')
u.score += 1
u.save()
return JsonResponse('HI HI HI')
启多个服务端,模拟30个并发请求
多台服务器启动项目
python3 manage.py runserver 127.0.0.1:8000
python3 manage.py runserver 127.0.0.1:8001
在tools中新建py文件 test_api.py,模拟30个并发请求
import threading
import requests
import random
def getRequest():
url='http://127.0.0.1:8000/test/'
url2='http://127.0.0.1:8001/test/'
get_url = random.choice([url, url2])
requests.get(get_url)
ts = []
for i in range(30):
t=threading.Thread(target=getRequest,args=())
ts.append(t)
if __name__ == '__main__':
for t in ts:
t.start()
for t in ts:
t.join()
python3 test_api.py
在数据库中查看 score 字段的值
并没有+30,而且没有规律,每次加的次数都不同,如何解决???
解决方案:redis分布式锁
def test(request):
# 解决方法二:redis分布式锁
import redis
pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
r = redis.Redis(connection_pool=pool)
while True:
try:
with r.lock('guoxiaonao', blocking_timeout=3) as lock:
u = UserProfile.objects.get(username='guoxiaonao')
u.score += 1
u.save()
break
except Exception as e:
print('lock is failed')
return HttpResponse('HI HI HI')
1、MULTI # 开启事务
2、命令1 # 执行命令
3、命令2 ... ...
4、EXEC # 提交到数据库执行
4、DISCARD # 取消事务
# 开启事务
127.0.0.1:6379> MULTI
OK
# 命令1入队列
127.0.0.1:6379> INCR n1
QUEUED
# 命令2入队列
127.0.0.1:6379> INCR n2
QUEUED
# 提交到数据库执行
127.0.0.1:6379> EXEC
1) (integer) 1
2) (integer) 1
# 1、命令语法错误,命令入队失败,直接自动discard退出这个事务
这个在命令在执行调用之前会发生错误。例如,这个命令可能有语法错误(错误的参数数量,错误的命令名)
处理方案:客户端发生了第一个错误情况,在exec执行之前发生的。通过检查队列命令返回值:如果这个命令回答这个队列的命令是正确的,否者redis会返回一个错误。如果那里发生了一个队列命令错误,大部分客户端将会退出并丢弃这个事务
# 2、命令语法没错,但类型操作有误,则事务执行调用之后失败,无法进行事务回滚
从我们施行了一个由于错误的value的key操作(例如对着String类型的value施行了List命令操作)
处理方案:发生在EXEC之后的是没有特殊方式去处理的:即使某些命令在事务中失败,所有的其他命令都将会被执行。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set num 10
QUEUED
127.0.0.1:6379> LPOP num
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get num
"10"
127.0.0.1:6379>
import redis
# 创建连接池并连接到redis
pool = redis.ConnectionPool(host = '192.168.153.150',db=0,port=6379)
r = redis.Redis(connection_pool=pool)
# 第一组
pipe = r.pipeline()
pipe.set('fans',50)
pipe.incr('fans')
pipe.incrby('fans',100)
pipe.execute()
# 第二组
pipe.get('fans')
pipe.get('pwd')
# [b'151', b'123']
result = pipe.execute()
print(result)
1、读写速度快. 数据存放在内存中
2、支持数据类型丰富,string,hash,list,set,sorted
3、支持事务
4、可以用于缓存,消息队列,按key设置过期时间,到期后自动删除
5、支持数据持久化(将内存数据持久化到磁盘),支持AOF和RDB两种持久化方式,从而进行数据恢复操作,可以有效地防止数据丢失
5、支持主从(master-slave)复制来实现数据备份,主机会自动将数据同步到从机
类型 | 特点 | 使用场景 |
---|---|---|
string | 简单key-value类型,value可为字符串和数字 | 常规计数(微博数, 粉丝数等功能) |
hash | 是一个string类型的field和value的映射表,hash特别适合用于存储对象 | 存储部分可能需要变更的数据(比如用户信息) |
list | 有序可重复列表 | 关注列表,粉丝列表,消息队列等 |
set | 无序不可重复列表 | 存储并计算关系(如微博,关注人或粉丝存放在集合,可通过交集、并集、差集等操作实现如共同关注、共同喜好等功能) |
sorted set | 每个元素带有分值的集合 | 各种排行榜 |
# RDB
快照形式,定期把内存中的数据保存到磁盘。Redis默认支持的持久化方案。速度快但是服务器断电的时候会丢失部分数据
# AOF
把所有对redis数据库增删改操作的命令保存到文件中。数据库恢复时把所有的命令执行一遍即可。
# 两种持久化方案同时开启使用AOF文件来恢复数据库.能保证数据的完整性,但是速度慢。
1、从redis2.8开始,set命令集成了两个参数,nx和ex,先拿nx来争抢锁,抢到之后,再用ex参数给锁加一个过期时间防止锁无法释放,造成死锁
set username AAA nx ex 3
2、redis分布式锁原理见图
# 原理
缓存和数据库都没有的数据,而用户反复发起请求, 如 假的用户ID
# 场景
比如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大
# 解决方案:
1、请求校验,接口层增加校验,如对id做基础校验,id<=0的直接拦截
2、都无法取到数据时也可以将key-value对写为key-null,缓存有效时间比如30秒左右,这样可以防止攻击用户反复用同一个id暴力攻击
# 原理
缓存没有,数据库有,一般是缓存时间到期, 顺势并发太大
#解决方案
1、热点数据不过期
2、上锁: 重新设计缓存的使用方式,当我们通过key去查询数据时,首先查询缓存,如果没有,就通过分布式锁进行加锁,取得锁的进程查DB并设置缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回缓存数据或者再次查询DB
# 原理
缓存中大批量数据过期,导致瞬时大批量不同请求注入DB
# 解决方案
解决方案
1、缓存设置随机时间(避免缓存设置相近的有效期;为有效期增加随机值)
2、热点数据不过期
pip3 install django-redis-sessions==0.5.6
SESSION_ENGINE = 'redis_sessions.session'
SESSION_REDIS_HOST = 'localhost'
SESSION_REDIS_PORT = 6379
SESSION_REDIS_DB = 2
SESSION_REDIS_PASSWORD = ''
SESSION_REDIS_PREFIX = 'session'
def session_test(request):
request.session['hi'] = 'hello'
# hi = request.session.get('hi')
# del request.session['hi']
# request.session.flush()
return HttpResponse('ok')
#显示进程数,ip,端口号
$ ps aux | grep redis