hexo | Hello World

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Hexo

Hexo 是一个基于 Node.js 的静态博客框架,提供简单的方式来创建和部署静态博客,使作者专注于写作而不必担心后端服务器的维护。
Hexo 使用 Markdown 作为主要的文章撰写语言,这是一种轻量级的标记语言,使得写作变得简单且易于阅读。
Hexo 生成的是静态页面,因此页面加载速度非常快。这对于提供更好的用户体验和搜索引擎优化(SEO)非常有利。
Hexo 使用版本控制系统(通常是 Git)来管理博客内容,因此用户可以轻松地追踪和管理博客文章的历史。
Hexo 可以将博客部署到各种平台,包括 GitHub Pages、Netlify、Vercel 等。这使得博客的部署变得非常简单。

配置 hexo

https://www.bilibili.com/video/BV1Eg41157tL

1
2
$ git config --global user.name "leo710aka"
$ git config --global user.email "1908454905@qq.com"

_cnfig.yml不能直接复制粘贴过去。。

Create a new post

在C:\Users\cf\Documents\Visual Studio Code\blog下右键Git Bash Here

1
$ hexo new "文件名"

或者直接在..\source\posts下编写md文件. More info: Writing

Run server

1
$ hexo s

在本地服务器看看效果. http://localhost:4000/

Clean、 Generate static files 、Deploy to remote sites

1
2
3
$ hexo clean   # 清理缓存、清理之前生成的静态文件
$ hexo g # 构建网页文件(通过md文件生成html、css、js..)
$ hexo d # 同步github,更新博客上的内容(这一步可能需要多试几次)

没办法同步的话,直接把新构建的 E:\Code\Blog\blog\.deploy_git 中的文件手动上传github仓库;
成功后若博客内容未更新,ctrl+f5清下缓存。

其他配置

_config.yml_config.landscape 中设置,修改配置后先 hexo clean 一下,再发布。

hexo d 报错 Error: Spawn failed ?

  1. 寻找github.com的最新ip,加了host解析后,能够ping通,但hexo d仍失败
  2. 优先解决 SSL/TLS 连接超时问题​​:修改 Hexo 配置文件 _config.yml,将仓库地址从 HTTPS 改为 SSH 协议,根治 HTTPS 不稳定
  3. 将 C:\Users\caife.ssh\id_rsa.pub(公钥)文件内容加到 GitHub → ​​Settings → SSH and GPG Keys → New SSH Key​​,再次执行hexo d成功了。
  4. ​​为何旧密钥直接生效?​​
    你已有的 id_rsa 密钥对是之前通过合法流程生成的(可能曾用于其他服务),只是未添加到 GitHub。上传公钥后,GitHub 即可识别该密钥的签名,因此无需重新生成。生成命令示例​​:
    1
    ssh-keygen -t ed25519 -C "your_email@example.com"  # 推荐更安全的 Ed25519 算法
  5. GitHub 身份验证流程​
    当你执行 hexo d 时:
    ​​客户端发起连接​​:Hexo 通过 Git 向 GitHub 发起 SSH 连接请求。
    ​​服务器生成挑战​​:GitHub 生成一个随机字符串(Challenge),用你账户中存储的​​公钥​​加密后发送给客户端。
    ​​客户端响应签名​​:本地 Git 使用​​私钥​​解密该字符串,生成签名并返回给 GitHub。
    ​​服务器验证签名​​:GitHub 用公钥验证签名有效性,若匹配则认证通过,允许推送文件

Python / Anaconda / Django

Python是一种高级、通用、解释型、面向对象的编程语言。
Python是一种解释型语言,其代码不是直接编译成机器码,而是由解释器逐行解释执行。常见的Python解释器有 CPython、Jython 等。
其中 CPython 是官方标准实现,是由Python的创始人Guido van Rossum领导开发的官方Python解释器。它是使用C语言编写的,是最广泛使用的Python解释器。

Python 环境

  1. 配置 Python 环境:
    • 官方Python(CPython):通过官方网站下载并安装,使用官方的 pip 包管理器进行包的安装。不具备虚拟环境管理工具,但可使用 venv 或 virtualenv 创建虚拟环境。
      使用:添加解释器 -> Virtualenv -> 基础解释器 -> C:\Users\caife\AppData\Local\Programs\Python\Python312\python.exe
    • Anaconda Python:通过 Anaconda 官方网站下载并安装。使用 conda 包管理器进行包的安装和环境的管理。具备强大的虚拟环境管理工具,可以轻松创建、导出、列出和删除环境。
    • 项目中自带的环境:在开发环境中配置项目中的 Python 解释器(xxproject\env\Scripts\python.exe),使项目中使用的 Python 环境与系统中的 Python 环境独立。
  2. Python环境通常包括了解释器、标准库、开发工具和其他一些组件:
    • 虚拟环境(Virtual Environment): 虚拟环境是一个独立的Python环境,允许您在同一台机器上同时运行多个项目,每个项目都有其独立的依赖项和库。venvvirtualenv 是用于创建虚拟环境的工具。
    • 解释器:如 C:\Python312\python.exe,用于运行代码。
    • 包管理器:如 C:\Python312\Scripts\pip3.exe,用于(为解释器/环境)安装、升级和卸载软件包。pip 是Python的官方包管理器,用于从 PyPI 安装第三方库。
    • 库:如 C:\Python312\lib,其中包含了该环境中安装的所有Python包。
  3. 开发环境:
    • IDE(集成开发环境):提供了代码编辑、调试、版本控制等一体化功能的工具。常见的 Python IDE包括 PyCharm、Visual Studio Code、Jupyter Notebook 等。
    • Jupyter Notebook: 交互式计算环境,支持在浏览器中编写和运行 Python代码,并包含文本、图像和公式等。
  4. PyCharm 配置解释器:
    • 使用 Pycharm 开发项目时, 点击添加解释器 —> 添加本地解释器,选择将要运行该项目的虚拟环境下的 Python 解释器。
      选择使用这个虚拟环境对应的编辑器,就是选择了使用这个环境运行项目,使用这个环境中配好的包,版本和依赖库等等。 可以在“外部库”查看当前环境中的类库。
    • 一般来说, 可以直接选择本地Python解释器(Python3.10)环境中已经配好了大部分的类库,项目中需要的软件包可以手动安装(指定地址 C:\Users\caife\AppData\Local\Programs\Python\Python312\Lib)。
  5. PyCharm终端的Python环境不一定与当前加载的项目使用的解释器器环境一致!!
    • 所以要注意终端使用的是哪个编辑器,是否与当前项目使用的编辑器匹配。否则在终端 pip install 把包装到 Pycharm 终端的环境下,而当前项目运行在另一个虚拟环境中,便无法 import 已从终端安装的包。
    • 看pycharm提示符的地址,其实就等于是cmd中cd到该地址执行命令

Pycharm 终端指令

1
2
3
4
pip install xx    # pip安装包到终端的环境中
pip install xx --target C:\Users\caife\AppData\Local\Programs\Python\Python312\Lib # 指定安装位置
python --version # 查看 Python 版本
pip list # 列出已安装的包

数据结构与函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
my_list = [1, 2, 3, "apple"]  # 列表
my_tuple = (1, 2, 3) # 元组
my_dict = {"name": "John", "age": 25} # 字典
my_set = {1, 2, 3} # 集合

# 定义函数
def greet(name):
return "Hello, " + name
message = greet("Alice") # 调用函数

# 面向对象编程(OOP)
# 定义类
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print("Woof!")
my_dog = Dog("Buddy") # 创建对象
my_dog.bark() # 调用对象方法

异常处理

1
2
3
4
5
6
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero")
finally:
print("This will be executed no matter what")

文件操作

1
2
3
4
5
6
7
8
# 写入文件
with open("example.txt", "w") as file:
file.write("Hello, Python!")

# 读取文件
with open("example.txt", "r") as file:
content = file.read()
print(content)

Pandas

Pandas 是一个强大的数据分析库,主要用于数据处理和分析。它提供了两种主要的数据结构:SeriesDataFrame

  • Series 是一个一维标记数组,可以保存任何数据类型。它由两个主要部分组成:索引(index)和数据(data)。
  • DataFrame 是一个二维表格,类似于 Excel 表格或 SQL 表。它由行索引、列索引和数据组成。
    数据组织:DataFrame 以表格的形式组织数据,包括多个列,每一列可以包含不同的数据类型(整数、浮点数、字符串等)。
    索引:每个行和列都有一个标签索引。行索引表示 DataFrame 中的每个数据行,列索引表示 DataFrame 中的每个数据列。
    :DataFrame 的每一列是一个 Pandas Series 对象,这意味着它们可以包含相同类型的数据。你可以将每一列视为一个数据字段,类似于数据库表中的列。 下面是一个示例 DataFrame 的结构:
    1
    2
    3
    4
    5
    6
    |    | Name     | Age | City       |
    |----|----------|-----|------------|
    | 0 | Alice | 25 | Niu York | -- 行索引(0, 1, 2, 3)标识每一行的位置
    | 1 | Bob | 30 | Los Angeles| -- 列索引(Name, Age, City)表示不同的数据字段
    | 2 | Carol | 28 | Chicago | -- 每一列(Name, Age, City)都包含相应的数据
    | 3 | David | 22 | Houston | -- 每一列都是一个 Pandas Series,包含相同类型的数据
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import pandas as pd
    import numpy as np
    s = pd.Series([1, 3, 5, np.nan, 6, 8]) # 创建一个 Series
    df = pd.DataFrame({ # 创建一个 DataFrame
    'Name': ['Alice', 'Bob', 'Charlie'],
    'Age': [25, 30, 35],
    'City': ['New York', 'San Francisco', 'Los Angeles']
    })
    df = pd.DataFrame(columns=, data=) # 指定创建 DataFrame 的列索引和其中的数据

    for index, row in df.iterrows(): # 遍历DataFrame的每一行
    print(f"Index: {index}, Name: {row['Name']}, Age: {row['Age']}, City: {row['City']}")
    # Index: 0, Name: Alice, Age: 25, City: New York...

    # 将 DataFrame 对象保存为 CSV 文件
    # sep 表示数据字段之间的分隔符,header 表示是否将列名写入文件,index 表示是否写入行索引。
    df.to_csv('data.txt', sep = ' ', index = False, header = False)
    # 从 CSV、Excel文件读取数据
    df = pd.read_csv('data.csv')
    df = pd.read_excel('data.xlsx')

Numpy

numpy(Numerical Python的缩写)是一个用于科学计算的强大Python库。以下是一些 numpy 的主要功能和用法:

  1. 多维数组: numpy 提供了ndarray对象,是一个多维数组,用于存储同类型的元素。
    1
    2
    3
    import numpy as np
    # 创建一个numpy数组
    arr = np.array([1, 2, 3, 4, 5])
  2. 数组操作: numpy 提供了许多对数组进行操作的函数,包括数学、逻辑、形状操作等。
    1
    2
    3
    4
    # 数组的数学运算
    result = arr + 2
    # 数组形状操作
    reshaped_arr = arr.reshape(5, 1)
  3. 矩阵操作: numpy 具有广泛的矩阵操作,包括矩阵乘法、转置等。
    1
    2
    3
    4
    matrix_a = np.array([[1, 2], [3, 4]])
    matrix_b = np.array([[5, 6], [7, 8]])
    # 矩阵乘法
    result_matrix = np.dot(matrix_a, matrix_b)
  4. 数学函数: numpy 包括大量的数学函数,用于三角函数、对数、指数等。
    1
    2
    3
    x = np.array([0, 1, 2, 3, 4])
    # 求sin(x)
    sin_values = np.sin(x)
  5. 随机数生成: numpy 提供了生成随机数的函数。
    1
    random_numbers = np.random.rand(5)  # 生成5个在[0, 1)范围内的随机数

Python爬虫

反爬虫策略

  • 设置合理的请求头。
    1
    2
    3
    headers = {   # 伪装浏览器请求头
    'Cookie': 'ispeed_lsm=2; baikeVisitId=b84d4a50-436c-4e0f-9e29-dc2393e9cdca; COOKIE_SESSION=6_1_8_5_10_9_1_0_7_5_0_3_33002_0_2_0_1650022038_1650022034_1650022036%7C8%230_1_1650022030%7C1; BD_UPN=1126314751; BD_HOME=1; BD_CK_SAM=1; H_PS_645EC=89b2Pt9WoxiJHIC80g9QL3FIo7tdoc9Z9Gm9Nd6gkOPipOmTDtckrFlLxEpchFYkItCM; BAIDUID=FD56AC9125756B81A0E4EB7A60F27700:FG=1; BIDUPSID=FD56AC9125756B81E8CE802CC99B8074; PSTM=1648004100; BDUSS=Jpc2d4NGIwdzRCNVFTR0xNeS1IYXBLNTQwfjhzRnl3Z0xRSlZJTDhZeU1ibnhpRVFBQUFBJCQAAAAAAAAAAAEAAAAnQDHOyfq77rXDd2luZHkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIzhVGKM4VRiT2; H_PS_PSSID=36426_31660_35912_36167_34584_35979_36055_36235_26350; BA_HECTOR=ak20800k8kag8h8le71h8646s0q; BDRCVFR[feWj1Vr5u3D]=I67x6TjHwwYf0; delPer=0; PSINO=6',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36'}
  • 使用代理IP。
  • 限制请求频率,避免被封IP。
    1
    2
    sleep_time = random.uniform(0, 2)
    time.sleep(sleep_time)
  • 处理验证码和登录等复杂场景。

网络请求库:

  • requests库:用于发送HTTP请求,获取网页内容。
    1
    2
    3
    4
    5
    import requests
    # url_base = 'http://leo/index.phtml?reportdate={year}&quarter={quarter}&p={page}'
    # url = url_base.format(year=iyear, quarter=iquarter, page=page) # 替换url模板中的占位符
    response = requests.get(url=url, headers=self.headers)
    res = response.content.decode() # 获取服务器响应的内容,将其解码成字符串

HTML解析库:

  • BeautifulSoup:用于解析HTML文档,提取所需信息。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(res, 'html.parser') # 解析 HTML 内容
    first_paragraph = soup.find('p') # 查找第一个<p>标签
    script = soup.find(id=tag_id) # 找到具有指定 id 属性的标签
    # script.string.replace_with(new_string) # 替换标签内容
    # script.append(new_tag) # 在标签内追加新标签
    # script.extract() # 从文档中删除标签
    text = script.text # 获取标签内容
    # json_str = re.findall(r'\[.+\]', text)[0] # 用正则表达式 re 从 text 中查找一个或多个包含 JSON 数据的字符串的第一个匹配项
    # data = json.loads(json_str) # 把json格式的字符串转换为Python类型
    script1 = soup.find('p', class_='paragraph') # 查找带有指定class的<p>标签
    script2 = soup.find('a', href='https://...') # 查找带有指定属性的<a>标签
    labels = soup.find_all('a', attrs={'href': True}) # 模糊搜索HTML代码中所有含href属性的<a>标签
  • lxml:使用类似 XPath 的功能解析HTML文档。
    1
    2
    3
    4
    from lxml import html
    tree = html.fromstring(html_doc)
    paragraphs = tree.xpath('//p') # 使用 XPath 表达式查找<p>标签
    paragraphs_with_class = tree.xpath('//p[@class="paragraph"]') # 使用 XPath 表达式查找具有指定class属性值的<p>标签

数据存储:

  • 文件存储:将爬取的数据保存为文本文件、CSV文件等。
  • 数据库:使用SQLite、MySQL、MongoDB等数据库存储数据。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    def load(self, path):
    with open(path, encoding='utf-8') as fp: # 加载存储在JSON文件中的数据, 指定文件为UTF-8编码
    data = json.load(fp)
    return data
    def save(self, data, path):
    with open(path, 'w', encoding='utf-8') as fp: # 以json格式保存, 最近一日各国疫情数据
    json.dump(data, fp, ensure_ascii=False)

    def baidu_search(v_result_file): # 保存csv数据
    df = pd.DataFrame(
    {
    '关键字': kw_list, '页码': page_list, '标题': title_list, '百度链接': href_list,
    '真实链接': real_url_list, '更新时间': time_list, '简介': desc_list,
    '网站名称': site_list,
    }
    )
    if os.path.exists(v_result_file):
    header = None # 已存在的csv文件保存时不用加标头
    else:
    # 创建新文件时,设置csv文件标头
    header = ['关键词', '页码', '标题', '百度链接', '真实链接', '更新时间', '简介', '网站名称']
    df.to_csv(v_result_file, mode='a+', index=False, header=header, encoding='utf_8_sig')
    print('结果保存成功:{}'.format(v_result_file))

爬虫框架

  • Scrapy:一个强大的Python爬虫框架,提供了高级功能如异步处理、中间件等。
    1
    scrapy startproject myproject

动态网页爬取

  • 使用Selenium或Headless浏览器模拟浏览器行为。
  • 处理JavaScript渲染的页面。

Anaconda

  1. 利用 Anaconda,创建不同版本的虚拟环境,配置不同的包(如不同版本的 Pytorch),以管理不同的项目。
  2. 用 Pycharm 打开项目时,点击添加解释器 —> 添加本地解释器,选择该项目对应的 conda 虚拟环境下,该虚拟环境的 python 解释器。选择使用这个虚拟环境对应的编辑器,就是选择了使用这个环境运行项目,使用这个环境中配好的包等等。
  3. 所以要注意终端使用的是哪个编辑器,是否与当前项目使用的编辑器匹配。否则 pip install 把包装到一个虚拟环境,而当前项目运行在另一个虚拟环境中,便无法 import 已安装的包。

新建虚拟环境

打开Anaconda Prompt,创建一个名字为py36的python版本为3.6的虚拟环境(默认地址已改成D:\anaconda3\envs),并查看已有环境:

1
2
3
4
5
6
7
(base) C:\Users\cf>conda create -n py36 python=3.6
Proceed ([y]/n)? y
(base) C:\Users\cf>conda env list
# conda environments:
#
base * D:\anaconda3
py36 D:\anaconda3\envs\py36

激活(切换)环境,并查看该环境下已有包:

1
2
(base) C:\Users\cf>conda activate py36
(py36) C:\Users\cf>pip list

装包

方法1:在 Anaconda Prompt 中进入该环境后,执行 conda install … 或 pip install …(需要关闭代理)
方法2:在 Pycharm的terminal(终端),执行安装指令(此时所处的是 Pycharm终端 的环境(不一定是此时加载的项目的环境))

1
2
3
4
5
# 其他pip指令
pip list # 列出所有已安装的pip包
pip install package_name==desired_version # 安装指定版本的Python包
pip install --target=... package_name # 指定pip install包的路径
pip show package_name # 查看安装好的包的信息

Jupyter Notebook

以任意行为块,便于代码阅读和测试修改。 在一个环境中安装 jupyter notebook 后,运行:

1
(py36) C:\Users\cf>jupyter notebook        

在浏览器打开8888端口使用 http://localhost:8888/?token=7e39781c1e364fa7a5ee95ef290ffa92eb0caef0c19aaad8


Django

Django是一个基于Python的开源Web应用框架,它遵循 MTV 框架,与传统的 MVC 有一些区别,但是其基本思想和流程是类似的,旨在简化Web开发过程,提高开发效率,同时保持代码的可读性和可维护性。

MTV 架构

  1. Model(模型):负责处理数据逻辑,定义数据库模型(ORM),进行数据的增删改查操作。
  2. Template(模板):负责呈现用户界面,包含 HTML、CSS、JavaScript 等前端代码,展示数据给用户。
  3. View(视图): 处理用户请求,根据请求调用适当的模型和模板,返回响应给用户。

MTV 流程

  1. 用户发起请求Request到 Django 服务器。
  2. Django 的 URL 配置根据请求的 URL 路径找到对应的视图(View)函数。
  3. 视图函数处理请求,可能需要进行数据库操作,调用模型(Model)获取数据。
  4. 视图函数将获取的数据传递给模板(Template),并渲染生成最终的 HTML 页面。
  5. 服务器将生成的 HTML 页面作为响应Response返回给用户。

目录结构

  1. 项目目录:包含整个 Django 项目的配置和管理文件,settings.py、urls.py、wsgi.py(WSGI 入口)、asgi.py(ASGI 入口)等。
  2. 应用目录:包含具体的应用程序代码和文件,models.py(模型)、views.py(视图)、urls.py(应用URL配置)、templates/ 等。
  3. 静态文件目录: 存放静态资源文件,如 CSS、JavaScript、图片等。默认路径是项目目录下的 static/ 文件夹。
  4. 模板目录: 存放 HTML 模板文件。 默认路径是应用目录下的 templates/ 文件夹。
  5. 数据库文件:默认情况下,Django 使用 SQLite 作为默认数据库,并将数据库文件存放在项目目录下的 db.sqlite3 文件中。
    一个Django项目(project)就是一个基于Django的Web应用,一个Django项目包含一组配置和若干个Django应用
    一个Django应用(blog)就是一个可重用的Python软件包,每个应用可以自己管理模型、视图、模板、路由和静态文件等

几个入门命令

  • 文件路径cmd
    1
    2
    3
    4
    5
    6
    cd E:/.../mainproject                  # 一定要把终端路径切换成项目根目录!!!
    django-admin startproject mainproject # 创建项目,生成工程目录
    python manage.py startapp firstWEB # 创建APP(进入工程目录/IDE console)
    python manage.py makemigrations # 创建库表(进入工程目录/IDE console)
    python manage.py migrate # 执行库表建立(进入工程目录/IDE console)
    python manage.py runserver # 启动项目(进入IDE console)
  • 几个入门设置(settings.py):Django 框架时间设置,Django APP添加,Templates目录设置(在APP日录下,需要独立建立)

HelloWorld

  • 实现一个请求view,访问 主项目project 中的 blog应用 的hello_world接口
    1. 编写应用请求 project/blog/views.py
      1
      2
      def hello_world(request):
      return HttpResponse("2323")
    2. 配置应用路由 project/blog/urls.py
      1
      2
      3
      urlpatterns = [
      path('hello_world', blog.views.hello_world)
      ]
    3. 配置项目路由 project/project/urls.py
      1
      2
      3
      4
      5
      urlpatterns = [
      path('admin/', admin.site.urls),
      # 如果url中含有 'blog',就转发到应用层面的路由处理
      path('blog/', include('blog.urls'))
      ]
    4. 添加blog应用到项目配置中 project/project/settings.py
      1
      2
      3
      4
      INSTALLED_APPS = [
      # ...
      'blog.apps.BlogConfig', # 创建APP时自动创建
      ]
  • 请求转发流程 http://127.0.0.1:8000/blog/
    浏览器 –(项目路由)–> Blog App –> view.py –(应用路由)–> hello_world函数 –> 浏览器

模型层

  • 模型层是什么?
    • 模型层:位于Django视图层和数据库之间, Python对象和数据库表之间转换
    • 模型层作用:屏蔽不同数据库之间的差异, 开发者更加专注于业务逻辑的开发, 提供很多便捷工具有助开发
    • 模型层配置 project/project/settings.py
      1
      2
      3
      4
      5
      6
      DATABASES = {
      'default': {
      'ENGINE': 'django.db.backends.sqlite3', # 自带的数据库
      'NAME': BASE_DIR / 'db.sqlite3',
      }
      }
  • 模型 Model:Model是数据库表的抽象表示。
    • 每个模型都继承自django.db.models.Model类,并定义了一组字段(Field),这些字段对应于数据库表中的列。
      1
      2
      3
      4
      5
      6
      7
      8
      from django.db import models  

      class Author(models.Model): # Author模型有两个字段:name和email
      name = models.CharField(max_length=100)
      email = models.EmailField()
      class Book(models.Model): # Book模型有一个title字段和一个指向Author模型的外键
      title = models.CharField(max_length=200)
      author = models.ForeignKey(Author, on_delete=models.CASCADE)
    • 一旦你定义了模型,Django就会自动为你生成一个数据库表(除非你明确告诉它不要这样做)。然后,你可以使用Django的ORM(对象关系映射)API来查询、创建、更新和删除数据库中的记录。以下是一些基本示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      # 获取所有作者  
      authors = Author.objects.all()
      # 获取标题为"Django教程"的书
      book = Book.objects.get(title="Django教程")
      # 创建一个新作者
      new_author = Author.objects.create(name="John Doe", email="john@example.com")
      # 创建一个新书,并将其与作者关联
      new_book = Book.objects.create(title="Django入门", author=new_author)
      # 获取并更新一本书的标题
      book = Book.objects.get(title="Django教程")
      book.title = "Django进阶"
      book.save()
      # 删除一本书
      book = Book.objects.get(title="Django进阶")
      book.delete()
  • 迁移 Migrations:Django框架中用于管理数据库模式更改的一种强大工具。
    • 当你更改Django模型(Model)时(例如添加、删除或修改字段),Django能够自动计算出需要应用到数据库中的更改,并将这些更改保存为迁移文件。然后,你可以使用Django的迁移命令将这些更改应用到数据库中。
    • 创建迁移文件
      1
      python manage.py makemigrations  # IDE console
    • 运行迁移文件 同步sqlite3数据库
      1
      python manage.py migrate  # IDE console
  • Django shell操作
    • Django shell:继承Django项目环境,用于交互的Python编程,方便快捷
    • 新建文章:IDE console操作
      1
      python manage.py shell  # 进入 Djungo shell
      1
      2
      3
      4
      5
      6
      7
      >>> from blog.models import Article  # 导入模型
      >>> a = Article() # 创建文章
      >>> a.title = 'Test Django Shell'
      >>> a.brief = '...'
      >>> a.save() # 保存文章
      >>> articles = Article.objects.all() # 获取文章
      >>> print(articles[0].title)
  • Django Admin模块
    • Django的后台管理工具,简化Djungo shell的使用;直接读取定义的模型元数据,提供强大的管理使用页面
    • Django的使用
      1
      python manage.py createsuperuser  # 创建管理员用户:caif / cxxxxxxx
      1
      2
      3
      from .models import Article

      admin.site.register(Article) # 将模型注册到admin模块 project/blog/admin.py
      登录页面进行管理:http://127.0.0.1:8000/admin/login/?next=/admin/, 选择模型,进行创建、修改、删除对象操作
  • Model数据返回页面
    1. 编写应用请求 project/blog/views.py
      1
      2
      3
      4
      5
      6
      from blog.models import Article

      def article_content(request): # 就是返回处理过的model数据
      article = Article.object.all()[0] # 从sqlite3中获取对应模型的数据
      ans_str = article.title + ...
      return HttpResponse(ans_str)
    2. 配置应用路由 project/blog/urls.py
    3. 配置项目路由 project/project/urls.py
    4. 添加blog应用到项目配置中 project/project/settings.py

连接本地MySQL

  • 默认情况下,Django连接的是自己带的sqlite数据库。好处是方便,不需要远程连接,打包项目挪到其他电脑上安装一下依赖一会就跑起来了,但是缺点就是,可能会出现各种莫名其面的问题,所以,尽可能在开始的时候,就配置上连接Mysql。
  • Django 连接 MySQL:
    • 安装 Python 访问 MySQL的 客户端模块
      1
      pip install mysqlclient  # Django官方已不建议使用pymysql库,而是改用mysqlclient
    • 修改 Django项目文件夹下的settings.py文件:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      DATABASES = {
      'default': {
      'ENGINE': 'django.db.backends.mysql', # 使用MySQL引擎
      'NAME': 'HKStock', # 数据库名称
      'USER': 'root', # 数据库用户名
      'PASSWORD': '123456', # 数据库密码
      'HOST': 'localhost', # 数据库主机(如果在本地)
      'PORT': '3306', # 数据库端口(默认为3306)
      }
      }
    • Pycharm 连接 MySQL:点击 Pycharm右上角 database -> “+” -> source -> MySQL,输入数据库名称、用户、密码连接成功
    • 修改项目文件夹下的__init.py文件:要替换默认的数据库引擎,在项目文件夹下的__init__.py 添加以下内容。
      1
      2
      3
      import pymysql

      pymysql.install_as_MySQLdb()
  • 定义模型 Model:将数据库 SQL 映射到面向对象的 Python 中来,使得你可以在 Django 中像操作普通对象一样操作数据库。
  • 迁移 Migrations:定义好了Model,数据库中的表并不会神奇的出现,你还需要把模型转化为对数据库的操作,这就是迁移。
    当你更改Django模型(Model)时(例如添加、删除或修改字段)Django能够自动计算出需要应用到数据库中的更改,并将这些更改保存为迁移文件。然后,你可以使用Django的迁移命令将这些更改应用到数据库中。
  • 新model迁移:
    1. 定义模型(mainapp/models.py)
      1
      2
      3
      class pen(models.Model):
      price = models.IntegerField()
      color = models.CharField(default='black', max_length=20)
    2. 创建迁移文件:指令执行完毕后会生成 mainapp/migrations/0001_initial.py 文件。Django 不会检查你的数据库,而是根据目前的模型的状态,创建一个操作列表,使项目状态与模型定义保持最新。
      1
      python manage.py makemigrations
    3. 运行迁移文件:打开数据库可以看到多了 mainapp_pen 表,并且里面的字段和模型是完全匹配的。插入一些数据。
      1
      python manage.py migrate 
    4. 功能demo
      1
      2
      3
      4
      5
      6
      # 编写url:在项目文件夹 datasite/urls.py
      from mainapp import views

      urlpatterns = [
      path('pen', views.pen) # 配置项目url即可,无需配置应用url
      ]
      1
      2
      3
      4
      5
      6
      7
      # 编写视图(views):在应用文件夹web/views.py,操作model从MySQL中获取数据
      from .models import Pen

      def pen(request):
      pen = Pen.objects.all()
      # 结合html(templates/pen.html)展示数据
      return render(request,"pen.html",{"pen_queryset":pen_queryset})
    5. 启动web:python manage.py runserver
    6. 发送请求:http://127.0.0.1:8000/pen
  • 操作MySQL中已有的表:
    1. 配置Django的数据库设置(项目级settings.py),确保已安装MySQL客户端并且设置好__init__.py文件。
    2. 创建Django模型:尽管数据库表已经存在,但仍需为这些表创建Django模型。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      from django.db import models  

      class Student(models.Model):
      name = models.CharField(max_length=255)
      code = models.CharField(max_length=255)

      class Meta:
      db_table = 'mainapp_student' # 数据库中的实际表名,注意这里应该与你的数据库表名一致
      managed = False # 指示Django该模型对应于现有的数据库表,不由Django迁移管理
    3. 同步Django模型与数据库:只需python manage.py makemigrations,无需python manage.py migrate ??
    4. 在Django中操作数据库表:使用Django的ORM API来查询、创建、更新和删除数据库中的记录了。

视图与模版

  • 使用Bootstrap实现静态博客页面
    • 在 project/blog/template 下创建 index.html
      1
      2
      3
      4
      5
      <!-- 在 header 引入 -->
      <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
      <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
      <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
      <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <!-- 静态代码块 -->
      <div class="container page-header">
      <h1 class="text-center mb-4">博客标题</h1>
      </div>
      <div class="container page-body">
      <div class="col-lg-9">
      <!-- 博客详情1, 2, ... -->
      </div>
      <div class="col-lg-3">
      <!-- 博客链接1, 2, ... -->
      </div>
      </div>
  • Django模板系统
    • 一个类似html的文本文件,结合view,实现动态页面。模板语法:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <!DOCTYPE html>
      <html>
      <head>
      <title>Blog Post</title>
      </head>
      <body>
      <h1>{{ post.title }}</h1>

      {% if user.is_authenticated %}
      <a href="{% url 'logout' %}">Logout</a>
      {% else %}
      <a href="{% url 'login' %}">Login</a>
      {% endif %}

      <ul>
      {% for comment in post.comments.all %}
      <li>{{ comment.content }} - {{ comment.author.username }}</li>
      {% endfor %}
      </ul>
      </body>
      </html>
    • 使用模板系统渲染博客页面
      在 Django 中,模板文件通常存储在每个应用的 /templates目录下。在应用view中渲染这个模板文件,并传递上下文数据:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      from django.shortcuts import render
      from datetime import datetime

      # home_view 视图函数渲染了 home.html 模板,并向模板传递了两个变量:name 和 date
      def home_view(request):
      context = {
      'name': 'My Django Website',
      'date': datetime.now(),
      }
      return render(request, 'home.html', context)

: (


ECharts

ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图表。
https://echarts.apache.org

  • 在项目中引入echarts
    • mainapp/static/assets/js/ 目录中保存 echarts.js
    • mainapp/templates/xx.html 文件中引入 echarts.js
    • 示例:一个简单柱状图
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="utf-8" />
      <title>ECharts</title>
      <!-- 引入刚刚下载的 ECharts 文件 -->
      <script src="echarts.js"></script>
      </head>
      <body>
      <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
      <div id="main" style="width: 600px;height:400px;"></div>
      <script type="text/javascript">
      // 基于准备好的dom,初始化echarts实例
      var myChart = echarts.init(document.getElementById('main'));
      // 指定图表的配置项和数据
      var option = {
      xAxis: {
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
      },
      yAxis: {},
      series: [
      {
      type: 'bar',
      data: [23, 24, 18, 25, 27, 28, 25]
      }
      ]
      };
      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option);
      </script>
      </body>
      </html>
  • 传入数据渲染echarts模版:
    1. 在view函数中准备数据,比如一个Python列表或字典;通过渲染函数(render())将数据传递到模板中;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      from django.shortcuts import render  

      def chart_view(request):
      # 准备数据
      x_axis_data = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
      y_axis_data = [23, 24, 18, 25, 27, 28, 55]

      # 将数据传递给模板
      context = {
      'x_axis_data_json': json.dumps(x_axis_data), # JSON 字符串形式的数据
      'y_axis_data_json': json.dumps(y_axis_data),
      }
      return render(request, '2.html', context)
    2. 复制示例模版from https://echarts.apache.org/examples/zh/index.html, 使用 Django模板标签将数据嵌入到 js代码中
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      <!DOCTYPE html>  
      <html>
      <head>
      <meta charset="utf-8" />
      <title>ECharts</title>
      <!-- 引入ECharts文件,确保路径正确 -->
      <script src="echarts.js"></script>
      </head>
      <body>
      <div id="main" style="width: 600px;height:400px;"></div>
      <script type="text/javascript">
      // 基于准备好的dom,初始化echarts实例
      var myChart = echarts.init(document.getElementById('main'));

      // 从 Django 模板上下文中取出数据,并转换为 JavaScript 数组
      var x_axis_data = JSON.parse('{{ x_axis_data_json|escapejs }}');
      var y_axis_data = JSON.parse('{{ y_axis_data_json|escapejs }}');

      // 指定图表的配置项和数据
      var option = {
      xAxis: {
      type: 'category',
      data: x_axis_data // 使用从 Django 传递的数据
      },
      yAxis: {
      type: 'value'
      },
      series: [
      {
      data: y_axis_data, // 使用从 Django 传递的数据
      type: 'bar'
      }
      ]
      };

      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option);
      </script>
      </body>
      </html>

Redis

Redis是一种支持 key-value 等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。 默认端口:6379

启动 Redis

下载 Windows版 msi文件:https://github.com/microsoftarchive/redis/releases
在环境变量中配好 Redis 的安装目录后,直接打开 cmd,启动 Redis 客户端
启动客户端后, 在客户端cmd窗口中测试 Redis 是否正常工作,设置密码(命令行设置的密码在服务重启后失效
注意:在 Windows 环境下,Redis 不支持后台运行模式,因此在启动 Redis 服务器时必须保持 cmd 窗口一直打开。(如果想要在后台运行 Redis,可以考虑使用虚拟机或者 Linux 环境下的 Redis)

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    C:\Users\蔡枫>redis-cli   # 启动 Redis 客户端(默认Redis服务端未关闭)
    127.0.0.1:6379> ping # 在 Redis 客户端中输入 ping 命令
    PONG # 返回 PONG 表示 正常工作
    127.0.0.1:6379> config set requirepass chskj.2020 # 修改密码
    OK
    127.0.0.1:6379> auth chskj.2020 # 验证密码
    OK
    127.0.0.1:6379> config get requirepass # 查看密码
    1)"requirepass"
    2)"chskj.2020"

启动(重启) Redis 服务器,客户端

进入 Redis 的安装目录(D:\Redis),打开 cmd 窗口(地址栏cmd回车),执行 redis-server.exe redis.windows.conf,即可启动 Redis 服务器
启动Redis服务器后,在 Redis 安装目录打开另一个 cmd 窗口,执行 redis-cli.exe,(或直接双击文件夹中的redis-cli.exe)即可启动(重启)Redis 客户端

Redis中有16个数据库(Database),每个数据库都是一个独立的命名空间,用于存储键值对数据。这些数据库被编号为0到15,默认情况下客户端连接到数据库0。可以通过SELECT命令选择数据库来切换不同的数据库空间,每个数据库都是相互隔离的,数据不会互相干扰。
每个数据库都可以包含键值对数据,命令和配置,但请注意,Redis的每个数据库是相对较小的,因此可以将不同类型的数据存储在不同的数据库中,以便更好地组织和管理数据。

1
SELECT 1   # 切换到数据库1

数据类型

Redis所有的key(键)都是字符串。我们在谈基础数据结构时,讨论的是存储值的数据类型,主要包括常见的5种数据类型,分别是:String、List、Set、Zset、Hash。 三种特殊的数据类型,分别是 HyperLogLogs(基数统计),Bitmaps (位图) 和 geospatial(地理位置)。 Redis5.0 中还增加了一个数据结构Stream,它借鉴了Kafka的设计,是一个新的强大的支持多播的可持久化的消息队列。

结构类型 结构存储的值 结构的读写能力
String字符串 可以是字符串、整数或浮点数 对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作;
List列表 一个链表,链表上的每个节点都包含一个字符串 对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素;
Set集合包 含字符串的无序集合 字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等
Hash散列 包含键值对的无序散列表 包含方法有添加、获取、删除单个元素
Zset有序集合 和散列一样,用于存储键值对字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定; 包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素

String字符串

  • String是redis中最基本的数据类型,一个key对应一个value。
  • String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。
  • 实战场景
    • 缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。
    • 计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。session:常见方案spring session + redis实现session共享,
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    127.0.0.1:6379> set hello world    # 设置存储在给定键中的值
    OK
    127.0.0.1:6379> get hello # 获取存储在给定键中的值
    "world"
    127.0.0.1:6379> del hello # 删除存储在给定键中的值
    (integer) 1
    127.0.0.1:6379> get hello
    (nil)
    127.0.0.1:6379> set counter 2
    OK
    127.0.0.1:6379> get counter
    "2"
    127.0.0.1:6379> incr counter # 将键存储的值加1
    (integer) 3
    127.0.0.1:6379> get counter
    "3"
    127.0.0.1:6379> incrby counter 100 # 将键存储的值加上整数
    (integer) 103
    127.0.0.1:6379> get counter
    "103"
    127.0.0.1:6379> decr counter # 将键存储的值减1
    (integer) 102
    127.0.0.1:6379> get counter
    "102"

List列表

  • Redis中的List其实就是链表(Redis用双端链表实现List)。
  • 使用List结构,我们可以轻松地实现最新消息排队功能(比如新浪微博的TimeLine)。List的另一个应用就是消息队列,可以利用List的 PUSH 操作,将任务存放在List中,然后工作线程再用 POP 操作将任务取出进行执行。
  • 使用列表的技巧:lpush+lpop=Stack(栈),lpush+rpop=Queue(队列),lpush+ltrim=Capped Collection(有限集合),lpush+brpop=Message Queue(消息队列)
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    127.0.0.1:6379> lpush mylist 1 2 ll ls mem   # 将给定值推入到列表左端,RPUSH 将给定值推入到列表右端
    (integer) 5
    127.0.0.1:6379> lrange mylist 0 -1 # 获取列表在给定范围上的所有值
    1) "mem"
    2) "ls"
    3) "ll"
    4) "2"
    5) "1"
    127.0.0.1:6379> lindex mylist -1 # 可以使用负数下标,以 -n 表示列表的倒数第n个元素
    "1"
    127.0.0.1:6379> rpop mylist # 从列表的右端弹出一个值,并返回被弹出的值;lpop 从左弹
    "1"
    127.0.0.1:6379> lindex mylist 10 # index不在 mylist 的区间范围内
    (nil)

Set集合

  • Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
  • Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
  • 实战场景
    • 标签(tag),给用户添加标签,或用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
    • 点赞,或点踩,收藏等,可以放到set中实现
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    127.0.0.1:6379> sadd myset hao hao1 xiaohao hao  # 向集合添加一个或多个成员	
    (integer) 3
    127.0.0.1:6379> scard myset # 获取集合的成员数
    (integer) 3
    127.0.0.1:6379> smembers myset # 返回集合中的所有成员
    1) "xiaohao"
    2) "hao1"
    3) "hao"
    127.0.0.1:6379> sismember myset hao # 判断 member 元素是否是集合 key 的成员
    (integer) 1

Hash散列

  • Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
  • 实战场景 - 缓存: 能直观,相比string更节省空间,的维护缓存信息,如用户信息,视频信息等。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    127.0.0.1:6379> hset user name1 hao               # 添加键值对	
    (integer) 1
    127.0.0.1:6379> hset user email1 hao@163.com
    (integer) 1
    127.0.0.1:6379> hgetall user # 获取散列中包含的所有键值对
    1) "name1"
    2) "hao"
    3) "email1"
    4) "hao@163.com"
    127.0.0.1:6379> hget user user # 获取指定散列键的值
    (nil)
    127.0.0.1:6379> hget user name1
    "hao"
    127.0.0.1:6379> hset user name2 xiaohao
    (integer) 1
    127.0.0.1:6379> hdel user name1 # 如果给定键存在于散列中,那么就移除这个键
    (integer) 1
    127.0.0.1:6379> hgetall user
    1) "email1"
    2) "hao@163.com"
    3) "name2"
    4) "xiaohao"

Zset有序集合

  • Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
  • 有序集合的成员是唯一的, 但分数(score)却可以重复。有序集合是通过两种数据结构实现:
    1. 压缩列表(ziplist): ziplist是为了提高存储效率而设计的一种特殊编码的双向链表。它可以存储字符串或者整数,存储整数时是采用整数的二进制而不是字符串形式存储。它能在O(1)的时间复杂度下完成list两端的push和pop操作。但是因为每次操作都需要重新分配ziplist的内存,所以实际复杂度和ziplist的内存使用量相关
    2. 跳跃表(zSkiplist): 跳跃表的性能可以保证在查找,删除,添加等操作的时候在对数期望时间内完成,这个性能是可以和平衡树来相比较的,而且在实现方面比平衡树要优雅,这是采用跳跃表的主要原因。跳跃表的复杂度是O(log(n))。
  • 实战场景 - 排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    127.0.0.1:6379> zadd myscoreset 100 hao 90 xiaohao  # 将一个带有给定分值的成员添加到有序集合里面
    (integer) 2
    127.0.0.1:6379> ZRANGE myscoreset 0 -1 # 根据元素在有序集合中所处位置,从有序集合中获取多个元素
    1) "xiaohao"
    2) "hao"
    127.0.0.1:6379> ZSCORE myscoreset hao
    "100"
    127.0.0.1:6379> zrem myscoreset hao # 如果给定元素成员存在于有序集合中,那么就移除这个元素
    (integer) 1

HyperLogLog

采用一种基数算法,用于完成独立总数的统计。(同一个人多次访问,只记一访问量)(独立访客)
占据空间小,无论统计多少个数据,只占12K的内存空间
不精确的统计算法,标准误差为 0.81%。

Bitmap

不是一种独立的数据结构,实际上就是字符串
支持按位存取数据,可以将其看成是byte数组
适合存储索大量的连续的数据的布尔值。(记录一个人连续一年每天的签到情况 0/1)(日活跃用户)


RedisTemplate

  • Spring Boot提供了RedisTemplate作为与Redis交互的强大工具。通过RedisTemplate,您可以在java项目中执行各种Redis命令来操作数据,包括字符串、列表、集合、散列、有序集合等。以下是一些常见的Redis操作示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 存储数据:
    redisTemplate.opsForValue().set("myKey", "myValue");
    String value = (String) redisTemplate.opsForValue().get("myKey");
    // 列表操作:
    redisTemplate.opsForList().leftPush("myList", "value1");
    List<String> myList = redisTemplate.opsForList().range("myList", 0, -1);
    // 集合操作:
    redisTemplate.opsForSet().add("mySet", "member1", "member2");
    Set<String> mySet = redisTemplate.opsForSet().members("mySet");
    // 散列操作:
    Map<String, String> myHash = new HashMap<>();
    myHash.put("field1", "value1");
    myHash.put("field2", "value2");
    redisTemplate.opsForHash().putAll("myHash", myHash);
    String fieldValue = (String) redisTemplate.opsForHash().get("myHash", "field1");
    // 有序集合操作:
    redisTemplate.opsForZSet().add("myZSet", "member1", 1.0);
    Set<String> myZSet = redisTemplate.opsForZSet().range("myZSet", 0, -1);
  • 公共命令
    1
    2
    redisTemplate.delete(key);  // 删除一个数据结构
    redisTemplate.hasKey(key); // 是否存在一个数据结构

Redis 事务管理

  • 要使用编程式事务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public void testTransaction() {
    Object result = redisTemplate.execute(new SessionCallback() {
    @Override
    public Object execute(RedisOperations redisOperations) throws DataAccessException {
    String redisKey = "text:tx";
    redisOperations.multi(); // 启用事务
    redisOperations.opsForSet().add(redisKey, "zhangsan");
    redisOperations.opsForSet().add(redisKey, "lisi");
    redisOperations.opsForSet().add(redisKey, "wangwu");
    // redis事务内做查询,无效
    // redis事务中的所有代码在事务提交后一起执行
    System.out.println(redisOperations.opsForSet().members(redisKey));
    return redisOperations.exec(); // 提交事务
    }
    });
    }

Redis缓存问题

在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问Mysql等数据库。这样可以大大缓解数据库的压力。当缓存库出现时,必须要考虑如下问题:

  1. 缓存穿透
    • 问题来源
      缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义,
      在流量大时,可能DB就挂掉了,要是有人利用不存在的kev频繁攻击我们的应用,这就是漏洞.
      如发起为id为”-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
    • 1、接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截
      2、从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒 (设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
      3、布隆过滤器。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小,
  2. 缓存击穿
    • 问题来源: 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力.
    • 1、设置热点数据永远不过期
      2、接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些 /3务不可用时候,进行熔断,失败快速返回机制。
      3、加互斥锁
  3. 缓存雪崩
    • 问题来源: 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
    • 1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
      2、如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中
      3、设置热点数据永远不过期.
  4. 缓存污染(或满了)
    • 缓存污染问题说的是缓存中一些只会被访问一次或者几次的的数据,被访问完后,再也不会被访问到,但这部分数据依然留存在缓存中,消耗缓存空间.
    • 缓存污染会随着数据的持续增加而逐渐显露,随着服务的不新运行,缓存中会存在大量的永远不会再次被访问的数据。缓存空间是有限的,如果缓存空间满了,再往缓存里写数据时就会有额外开销,影响Redis性能。这部分额外开销主要是指写的时候判断淘汰策略,根据淘汰策略去选择要淘汰的数据,然后进行删除操作。

缓存淘汰策略

Redis共支持八种淘汰策略,分别是noeviction, volatile-random、 volatile-ttl, volatile-ru、 volatile-lfuallkeys-lru、allkeys-Iandom 和 allkevs-lfu 策略。主要看分三类看:

  • 不淘汰
    noeviction (v4.0后默认的):一旦缓存被写满了,再有写请求来时,Redis 不再提供服务,而是直接返回错误。这种策略不会淘汰数据,所以无法解决缓存污染问题。一般生产环境不建议使用。
  • 对设置了过期时间的数据中进行淘汰
    随机 volatile-random:在设置了过期时间的键值对中,进行随机删除。因为是随机删除,无法把不再访问的数据筛选出来,所以可能依然会存在缓存污染现象,无法解决缓存污染问题。
    tvolatile-ttl:Redis在筛选需删除的数据时,越早过期的数据越优先被选择。随机删除就无法解决缓存污染问题。
    volatile-lru:LRU 算法的全称是 Least Recently Used,按照最近最少使用的原则来筛选数据
    volatile-lfu:LFU 缓存策略是在 LRU 策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。
  • 全部数据进行淘汰
    随机 allkeys-random:从所有键值对中随机选择并删除数据。
    allkeys-lru:使用 LRU 算法在所有数据中进行筛选。
    allkeys-lfu:使用 LFU 算法在所有数据中进行筛选。

Redis 持久化

Redis 提供了两种主要的持久化方式,用于在系统重启时保持数据的持久性:RDB 持久化和 AOF 持久化。
用户可以根据实际需求选择 RDB 持久化、AOF 持久化或两者结合使用。通常情况下,AOF 持久化是更安全的选择,因为它可以提供更好的持久性保障,但也需要更多的磁盘空间。

  1. RDB 持久化:
    • RDB 持久化是通过在指定的时间间隔内将内存中的数据集快照写入磁盘的方式来实现的。
    • 快照是一个二进制文件,它记录了某个时间点上 Redis 数据集的所有键值对。
    • RDB 持久化是一个“点对点”操作,它在指定的时间点创建了一个数据快照。
    • RDB 持久化适用于备份、灾难恢复等场景。
      RDB 持久化的配置选项可以在 Redis 配置文件中进行设置,例如:
      1
      2
      3
      save 900 1      # 表示在 900 秒(15分钟)内,如果至少有 1 个 key 发生了变化,则执行快照操作
      save 300 10 # 表示在 300 秒(5分钟)内,如果至少有 10 个 key 发生了变化,则执行快照操作
      save 60 10000 # 表示在 60 秒内,如果至少有 10000 个 key 发生了变化,则执行快照操作
  2. AOF 持久化:
    • AOF(Append Only File)持久化记录了服务器执行的所有写操作指令,以追加的方式将这些指令记录到一个文件中。
    • AOF 持久化是一个“追加”操作,每个写操作都被追加到文件末尾。
    • AOF 持久化适用于对数据的实时持久化需求。
      AOF 持久化的配置选项可以在 Redis 配置文件中进行设置,例如:
      1
      2
      appendonly yes            # 启用 AOF 持久化
      appendfsync everysec # 每秒钟执行一次 fsync 操作
    • appendfsync 选项可以设置 alwayseverysecnoalways 表示每个写命令都会立即被写入磁盘,everysec 表示每秒执行一次 fsync 操作,no 表示由操作系统自行决定何时进行写入磁盘操作。
    • Redis 还提供了 AOF 重写机制,可以通过 auto-aof-rewrite-percentageauto-aof-rewrite-min-size 选项配置。这允许 Redis 在不停机的情况下优化 AOF 文件的大小。

Mybatis(Plus)

1. Mybatis

MyBatis 是一个开源的Java持久层框架,用于将对象与关系数据库的表之间进行映射。MyBatis 通过 XML或注解配置文件描述 Java 对象与数据库之间的映射关系,并提供了一些方便的查询语言(类似于SQL)来进行数据库操作。
使用 MyBatis 来操作 MySQL 数据库,将数据存储在 MySQL 中,或从 MySQL 中检索数据,同时使用 MyBatis 进行数据映射和数据库操作的管理。它们通常一起使用,以构建 Java 应用程序的持久层。

数据映射:

MyBatis 的核心功能之一是提供简单且强大的数据映射。使用 XML或注解来定义 SQL查询和映射结果,将数据库表记录映射到 Java对象。

  • 1.1 XML 映射文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- 定义查询 -->
    <select id="selectUser" resultType="User">
    SELECT * FROM users WHERE id = #{id}
    </select>
    <!-- 映射结果到对象 -->
    <resultMap id="BaseResultMap" type="User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="password" property="password"/>
    </resultMap>
  • 1.2 注解方式
    1
    2
    @Select("SELECT * FROM users WHERE id = #{id}")
    User selectUser(int id);

核心功能:

  • 动态 SQL:MyBatis 允许你在 XML 中编写动态 SQL 语句,可以根据条件动态构建 SQL 查询。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <select id="selectUsers" parameterType="map" resultType="User">
    SELECT * FROM users
    WHERE 1=1
    <if test="username != null">
    AND username = #{username}
    </if>
    <if test="password != null">
    AND password = #{password}
    </if>
    </select>
  • 参数传递:MyBatis 支持多种参数传递方式,包括单个参数、多个参数、Map 和注解等。
    1
    2
    @Select("SELECT * FROM users WHERE id = #{id} AND username = #{username}")
    User selectUserByIdAndUsername(@Param("id") int id, @Param("username") String username);
  • 批处理:MyBatis 允许执行批处理操作,可以有效地执行一组 SQL 语句。
    1
    2
    3
    4
    5
    6
    7
    8
    SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    for (User user : userList) {
    userMapper.insertUser(user);
    }
    sqlSession.flushStatements();
    sqlSession.commit();
    sqlSession.close();

事务管理:

  • MyBatis 也提供了事务管理的支持。可以通过配置数据源和事务管理器来实现事务的控制。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 数据源配置 -->
    <dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydatabase"/>
    <property name="username" value="root"/>
    <property name="password" value="password"/>
    </dataSource>
    <!-- 事务管理器配置 -->
    <transactionManager type="JDBC"/>
  • MyBatis 可以很容易地与 Spring 框架集成,通过 Spring 的事务管理来控制数据库事务。
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- Spring 配置 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

SpringBoot实体类 —— VO/DTO/PO

  • VO:View Object,主要用于展示层。它的作用是把某个指定前端页面的所有数据封装起来。他的作用主要是减少传输数据量大小和保护数据库隐私数据(如用户密码、用户邮箱等相关信息)不外泄,同时保护数据库的结构不外泄。

  • DTO:Data Transfer Object,数据传输对象,用于展示层与服务层之间的数据传输对象。(注:实际开发中还存在BO,其作用和DTO类似,当业务逻辑不复杂时一般会被合并。)

  • PO:Persistant Object,持久化对象,和数据库形成映射关系。简单说PO就是每一个数据库中的数据表,一个字段对应PO中的一个变量。(也就是我们常用的Entities)

    1、从前端页面中收到JSON格式数据,后端接口中将其封装为一个VO对象;接口接收到VO对象后将其转换为DTO对象,并调用业务类方法对其进行处理;然后处理为PO对象,调用Dao接口连接数据库进行数据访问(查询、插入、更新等)2、后端从数据库得到结果后,根据Dao接口将结果映射为PO对象,然后调用业务类方法将其转换为需要的DTO对象,再根据前端页面实际需求,转换为VO对象进行返回。
  • 类型转换:上述过程中,VO/DTO/PO等实体类中字段常常会存在多数相同,根据业务需求少数不同。为避免频繁的set和get操作对其进行转换,spring为我们提供了多种方法。(1)使用BeanUtils:(springframework包下)(2)使用BeanUtils:(Apache包下)(3)使用modelMapper??

  • DO(Data Object):通常表示数据库中的数据实体,对应数据库表的结构。它主要用于数据存储和数据库操作,包含与数据库表字段一一对应的属性。类中通常包含与数据库表字段对应的成员变量、getter 和 setter 方法。它不应包含业务逻辑,主要负责数据的持久化和映射。
    尽管 PO 和 DO 在一些情况下用法相似,但它们的侧重点有所不同。PO 更侧重于与数据库的交互,强调持久化和数据表映射;而 DO 侧重于在不同层之间传递数据,强调业务逻辑层面的数据封装。

  • BO(Business Object):通常表示业务层的业务实体,主要用于封装业务逻辑。BO 类一般包含与业务逻辑相关的属性和方法,与具体的数据存储形式无关。包含了一些业务逻辑的操作,比如计算、验证等。它不应直接与数据库进行交互,而是通过调用 Service 层或 DAO 层的方法实现数据的获取和存储。


2. Mybatis-Plus

  • 基于MyBatis:MyBatis-Plus是MyBatis的增强工具包,是在MyBatis基础上的扩展。只做增强不做改变,为简化开发、提高效率而生。它提供了更多的便捷、高效的开发功能,简化了开发人员的编码工作,大幅度提高了开发效率。
  • 功能:MyBatis-Plus 集成了MyBatis的核心功能,同时提供了更多针对CRUD操作、条件构造器、分页、代码生成器等功能的封装。
  • 简化操作:可以减少重复的CRUD代码,提供了一些便捷的API接口和工具,使得开发人员能够更方便地进行数据库操作。
  • 引入 MybatisPlus依赖,可以直接代替 Mybatis依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
    </dependency>
  • MyBatisPlus 的配置项继承了 MyBatis原生配置和一些自己特有的配置。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    mybatis-plus:
    type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
    mapper-locations:"classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值
    configuration:
    map-underscore-to-camel-case: true # 是否开户下划线和驼峰的映射
    cache-enabled: false # 是否开户二级缓存
    global-config:
    db-config:
    id-type: assign_id # id为雪花算法生成
    update-strategy: not_null # 更新笑略:只更新非空字段

BaseMapper

  • 定义 Mapper接口并继承 BaseMapper类,泛型指定要与数据库映射的 Java实体类(pojo类);
    MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息,自动实现 CRUD的逻辑
    • 默认以类名驼峰转下划线作为表名(User类 -> user表)
    • 默认把名为id的字段作为主键
    • 默认把变量名驼峰转下划线作为表的字段名(createTime类属性 -> create_time表字段)
      1
      public interface UserMapper extends BaseMapper<User>{}
  • 如果实体类和表的对应关系不符合 mp的约定,就要自行配置。可以使用注解:
    • @TableName:指定表名称及全局配置
    • @Tableld:指定id字段及相关配置;
      • IdType的常见类型有:AUTO、ASSIGN ID(默认使用,雪花算法)、INPUT
    • @TableField:指定普通字段及相关配置。使用 @TableField的常见场景是:
      • 1、成员变量名与数据库字段名不一致 2、成员变量名以is开头,且是布尔值
        3、成员变量名与数据库关键字冲突 4、成员变量不是数据库字段

条件构造器

MyBatisPlus支持 使用 Wrapper构造各种复杂的where条件,而不需要在 xml中写sql语句。可以满足日常开发的所有需求。

  • QueryWrapper 和LambdaQueryWrapper通常用来构建 select、delete、update的 where条件部分
  • UpdateWrapper 和LambdaUpdateWrapper通常只有在 set语句比较特殊才使用
  • 尽量使用 LambdaQueryWrapper和 LambdaUpdateWrapper避免硬编码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 原sql:SELECT id,username,info,balance FROM user WHERE username LIKE ? AND balance >= ?
    void testQuerywrapper() {
    QueryWrapper<User> wrapper = new QueryWrapper<User>() // 1.构建查询条件
    .select("id", "username", "info", "balance")
    .like("username", "o")
    .ge("balance", 1000);
    List<User> users = userMapper.selectList(wrapper); // 2.查询
    }
    // 使用 Lambda替代上方法中的硬编码
    void testLambdaQuerywrapper() {
    LambdaQuerywrapper<User> wrapper = new LambdaQuerywrapper<User>()
    .select(User::getid, User::getUsername, User::getInfo, User::getBalance)
    .like(User::getUsername, "o")
    .ge(User::getBalance, 1000)
    List<User> users = userMapper.selectList(wrapper);
    }
    // 原sql:UPDATE user SET balance = 2000 WHERE (username = "jack")
    void testUpdateByQuerywrapper() {
    User user = new User()// 1.要更新的数据
    user.setBalance(2000);
    QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack");// 2.更新的条件
    userMapper.update(user, wrapper);// 3.执行更新
    }
    // 原sql:UPDATE user SET balance = balance - 200 WHERE id in (1,2,4)
    void testUpdatewrapper() {
    List<Long> ids = List.of(1L2L4L);
    Updatewrapper<User> wrapper = new Updatewrapper<User>()
    .setSql("balance = balance - 200")
    .in("id", ids) ;
    userMapper.update(null, wrapper) ;
    }

自定义SQL

我们可以利用 MyBatisPlus的 Wrapper来构建复杂的 Where条件,然后自己定义SQL语句中剩下的部分。
在业务层编写wrapper包含sql中的where部分,在mapper方法声明wrapper变量名称“ew”,最后在mapper对应的xml中自定义sql编写where以外的部分(解决了 不能在业务层编写sql 和 使用mp简化查询语句编写 的矛盾??

IService接口

  • 简单业务方法,直接在controller中调用对应的IService中的方法;
    对于复杂业务,需要在自定义Servicelmpl中编写逻辑,调用对应的BaseMapper中的方法;
    当BaseMapper不足以满足需求时,需要在mapper中编写自定义sql(处理where之外的sql,如update…);
    对于mapper中自定义sql,简单的使用注解编写,复杂的在xml中编写。。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 1、自定义Service接口继承IService接口
    public interface IUserService extends IService<User> {
    }
    // 2、自定义Service实现类,实现自定义接口并继承Servicelmpl类(否则要自己一个个实现IService接口的方法)
    public class UserServiceImpl
    extends ServiceImpl<UserMapper, User> // 泛型中指定mapper和实体类类型
    implements IUserService {
    }
    // 使用方法
    class IUserServiceTest {
    @Autowired
    private IUserService userService;
    @Test
    void testSaveUser() {
    User user = new User();
    user.setUsername(uLiLeiu);
    user.setPassword("123");
    userService.save(user); // 如果方法与BaseMapper中的重复,就不需要BaseMapper了??
    }
    }
  • IService的 Lambda方法:在 自定义的 Servicelmpl类中进行 复杂操作
    1. 需求:复杂查询,查询条件如下(name: 用户名关键字,可以为空;status:用户状态,可以为空;minBalance:最小余额,可以为空;maxBalance:最大余额,可以为空)
      1
      2
      3
      4
      5
      6
      7
      8
      public List<User> queryUsers(String name, Integer status, Integer minBalance Integer maxBalance) {
      return lambdaQuery()
      .like(name != null, User::getUsername, name)
      .eg(status != null, User::getStatus, status)
      .ge(minBalance != null, User::getBalance, minBalance)
      .le(maxBalance != null,User::getBalance, maxBalance)
      .list(); // 如果查询一个记录就是.one
      }
    2. 需求:复杂更新,要求如下(按id更新,更新为扣后余额,如果扣减后余额为0,则将用户status修改为冻结状态(2))
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @Transaction
      public List<User> deductBalance(Long id,Integer money) {
      ...
      // 4.扣城余额 update tb_user set balance = balance - ?
      int remainBalance = user.getBalance() - money;
      lambdaUpdate()
      .set(User::getBalance, remainBalance)
      .set(remainBalance == 0, User::getStatus, 2) // 如果余额0,修改状态
      .eq(User::getId, id) // 相当于 where
      .eq(User::getBalance,user.getBalance()) // 乐观锁
      .update(); // 更新
      }
  • IService批量新增(批处理):开启 rewriteBatchedStatements=true参数

分页插件

首先,要在配置类中注册MyBatisplus的核心插件,同时添加分页插件
接着,就可以使用分页的API了

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() { // 拦截器的形式实现插件
// 1. 初始化核心插件
MybatisplusInterceptor interceptor = new MybatisplusInterceptor();
// 2. 添加分页插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSOL);
pageInterceptor.setMaxLimit(100L); // 设置分页上限
interceptor.addInnerInterceptor(pageInterceptor); // 添加到核心拦截器
return interceptor;
}
}
1
2
3
4
5
6
7
8
void testPageQuery() {
int pageNo = 1, pageSize = 5; // 分页参数
Page<User> page = Page.of(pageNo,pageSize);
page.addOrder(new OrderItem("balance"false)); // 排序参数,通过OrderItem来指定
// 分页查询
Page<User> p = userService.page(page);
// 总条数 p.getTotal(); 总页数 p.getPages(); 分页数据 List<User> records = p.getRecords();
}

MySQL

MySQL是一个开源的关系型数据库管理系统(RDBMS),广泛用于Web应用程序和中小型企业数据库。默认端口是 3306

启动并连接 MySQL

  • 启动 MySQL 服务器(然后尝试连接到 MySQL 服务器;一般情况下 服务器不会自动关闭)
    1
    2
    net start MySQL  # 打开命令提示符(以管理员身份运行),运行以下命令来启动 MySQL 服务
    net stop MySQL # 停止 MySQL 服务
  • 另外,通过 Navicat 创建与本地(或远程) 的 MySQL(或其他)数据库的连接,以对数据库可视化管理
    本机 username: root,password: “0xx1xx”

SQL 术语


关系(Relation):通常是指数据库表(table)。每个关系对应数据库中的一个表格,由多个行和列组成,其中每一行通常代表表格中的一个数据记录,而每一列代表记录中的一个属性(字段)。
属性(attribute):列的名字,上图有学号、姓名、班级、兴趣爱好、班主任、课程、授课主任、分数.
依赖 (relation):列属性间存在的某种联系
元组(tuple):每一个行,如第二行 (1301,小明,13班,篮球,王老师,英语,赵英,70) 就是一个元组
模式(schema):这里指逻辑结构,如 学生信息(学号,姓名,班级,兴趣爱好,班主任,课程,授课主任,分数)的笼统表述。(数据库模式是数据库的结构描述,包括表格、字段、关系、视图、索引等元素的组织方式。描述了数据库中不同关系表格之间的关联和数据的组织方式。通常包含了数据库中表格的定义,包括表格的名称、字段的名称、字段的数据类型、主键等信息。)
(domain):数据类型,如string、integer等,上图中每一个属性都有它的数据类型 (即域)
(key):由关系的一个或多个属性组成,任意两个键相同的元组,所有属性都相同。需要保证表示键的属性最少。一个关系可以存在好几种键,工程中一般从这些候选键中选出一个作为主键 (primary key)
主键(Primary Key):主键是一个表格中的一列或一组列,它的值用来唯一标识表格中的每一行记录。主键的值不能重复,且不能为空。这意味着每一行记录在主键列上必须有唯一的值,用于区分记录。主键用于建立表格之间的关系和确保数据的完整性。通常,每个表格都有一个主键,但也可以由多个列组成复合主键
候选键(candidate key):由关系的一个或多个属性组成,候选键都具备键的特征,都有资格成为主键
超键(super key):包含键的属性集合,无需保证属性集的最小化。每个键也是超键。可以认为是键的超集。
外键(foreign key):如果某一个关系A中的一个(组)属性是另一个关系B的键,则该(组)属性在A中称为外键。
主属性 (prime attribute):所有候选键所包含的属性都是主属性
投影 (proiection):选取特定的列,如将关系学生信息投影为学号、姓名即得到上表中仅包含学号、姓名的列
选择 (selection):按照一定条件选取特定元组,如选择上表中分数>80的元组
笛卡儿积 (交叉连接Cross join):第一个关系每一行分别与第二个关系的每一行组合
自然连接(natural join):第一个关系中每一行与第二个关系的每一行进行匹配,如果得到有交叉部分则合并,若无交叉部分则舍弃。
连接(theta join):即加上约束条件的笛卡儿积,先得到笛卡儿积,然后根据约束条件删除不满足的元组.
外连接 (outer join):执行自然连接后,将舍弃的部分也加入,并且匹配失败处的属性用NULL代替。
除法运算(division):关系R除以关系S的结果为T,则T包含所有在R但不在S中的属性,且T的元组与S的元组的所有组合在R中。


SQL 语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CREATE DATABASE mydatabase;  /* 创建数据库 */
USE mydatabase; /* 选择数据库 */
CREATE TABLE users ( -- 创建表
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255),
email VARCHAR(255)
);
DROP TABLE users; -- 删除表
ALTER TABLE users ADD col CHAR(20); -- 添加列
ALTER TABLE mytable DROP COLUMN col; -- 删除列
ALTER TABLE mytable CHANGE col col1 CHAR(32) NOT NULL DEFAULT '123'; -- 修改列和属性
-- 插入数据
INSERT INTO users (username, email) VALUES ('user1', 'user1@example.com');
-- 插入检索出来的数据
INSERT INTO mytable1(col1, col2)
-- 更新数据
UPDATE users SET email='newemail@example.com' WHERE username='user1';
-- 删除数据
DELETE FROM users WHERE username='user1';

查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SELECT distinct id 
FROM users;
LIMIT 2, 3;
ORDER BY col1 DESC, col2 ASC;
WHERE col is NULL;
-- 子查询:只能返回一个字段的数据,可以将子查询的结果作为 WHRER 语句的过滤条件,配合 (not )in
SELECT * FROM mytable1
WHERE col1 IN (SELECT col2
FROM mytable2);
-- 分组:将数据按照一个或多个列的值分成不同的组,常与聚合函数(如 SUM、COUNT、AVG 等)一起以对每个组聚合操作
-- GROUP BY 子句: 用于指定按哪些列进行分组; HAVING 子句: 用于对分组后的数据进行筛选。
-- WHERE 过滤行,HAVING 过滤分组,行过滤应当先于分组过滤。
SELECT col, COUNT(*) AS num
FROM mytable
WHERE col > 2
GROUP BY col
HAVING num >= 2
ORDER BY SUM(column2) DESC;
  1. SELECT:查询出属性,用 AS 给列名、计算字段和表名取别名,以简化 SQL 语句以及连接相同表;
    select 中 sql 函数计算出的值作为查询出的属性(select round(count()/3, 2) from ..)
    select 中可以加一个select 用于属性计算(select id, count(
    )/(select count(*) from Users) per from ..)
    若有重复列,使用 DISTINCT 去除重复值
  2. LIMIT:LIMIT 2, 3 返回第 3 ~ 5 行。配合排序实现获取最大/最小值。。
  3. WHERE:过滤行,AND 和 OR 用于连接多个过滤条件。优先处理 AND,使用 () 决定优先级;
    is 搭配 null,IN 操作符用于匹配一组值,其后也可以接一个 SELECT 子句,从而匹配子查询得到的一组值。
  4. ORDER:可以按多个列进行排序,并指定不同的排序方式,默认升序ASC, 降序DESC
  5. GROUP BY 可以放在 WHERE 前、后,想清楚在分组前、后过滤
  6. 子查询的结果需要指定别名。

连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 连接(内连接):返回多个表中匹配条件满足的行,不匹配的行不会被包括在结果集中。
-- 使用 (INNER )JOIN 关键字,条件语句使用 ON 而不是 WHERE,连接可以替换子查询且效率一般会更快。
SELECT A.value, B.value
FROM tablea AS A JOIN tableb AS B
ON A.key = B.key;
-- 自连接:可以看成内连接的一种,只是连接的表是自身而已。
SELECT e1.name
FROM employee AS e1 INNER JOIN employee AS e2
ON e1.department = e2.department AND e2.name = "Jim";
-- 自然连接:自然连接是把同名列通过等值测试连接起来的,同名列可以有多个。
-- 内连接和自然连接的区别: 内连接提供连接的列,而自然连接自动连接所有同名列。
SELECT A.value, B.value FROM tablea AS A NATURAL JOIN tableb AS B;
-- 外连接:保留了没有关联的那些行。分为左,右外连接以及全外连接。
-- 左连接返回左表的所有行以及与右表匹配的行。如果右表中没有匹配的行,将会返回 NULL 值。
-- on 后等于新连接出了一张表,where 在新表上进行查询
select Employee.name, Bonus.bonus
from Employee left join Bonus
on Employee.empId = Bonus.empId
where Bonus.bonus < 1000 or Bonus.bonus is null

SQL 函数

  1. 数学函数:SUM():计算指定列的总和。 AVG():计算指定列的平均值。 MAX():找出指定列的最大值。 MIN():找出指定列的最小值。 COUNT():计算指定列的行数。 ROUND(x,y):把 x 四舍五入到 y 位小数。 ABS():返回绝对值。
  2. 字符串函数:CONCAT():连接两个或多个字符串。 SUBSTRING()SUBSTR():从字符串中提取子字符串。 LENGTH():返回字符串的长度。 UPPER():将字符串转换为大写。 LOWER():将字符串转换为小写。 TRIM():去除字符串首尾的空格或其他指定字符。 REPLACE():替换字符串中的子串。
  3. 日期和时间函数:NOW()CURRENT_TIMESTAMP():返回当前日期和时间。 DATE():从日期时间值中提取日期部分。 TIME():从日期时间值中提取时间部分。 YEAR():从日期中提取年份。 MONTH():从日期中提取月份。 DAY():从日期中提取天。HOUR():从时间中提取小时。 MINUTE():从时间中提取分钟。 SECOND():从时间中提取秒。
  4. 逻辑函数: IF()CASE:根据条件返回不同的值,if(rating<3, 1, 0);COALESCE():返回第一个非空值。
  5. 聚合函数: GROUP_CONCAT():将组内的值连接成一个字符串。 GROUP_BY:分组聚合查询结果。
  6. 窗口函数:ROW_NUMBER():为结果集的每行分配一个唯一的行号。 RANK():为结果集中的行分配排名。 DENSE_RANK():为结果集中的行分配密集排名。 OVER():定义窗口以进行窗口函数计算。

MySQL 数据类型

  1. 整数类型: 用于存储整数值,包括 TINYINT, SMALLINT, MEDIUMINT, INT, 和 BIGINT
  2. 浮点数类型: 用于存储浮点数,包括 FLOATDOUBLEDECIMAL 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAl 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。
    FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽。如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分
  3. 定点数类型: 用于存储定点数,包括 DECIMALNUMERIC
  4. 字符串类型: 用于存储文本数据,主要有 CHAR, VARCHAR 两种类型,一种是定长的,一种是变长的。
    VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。
  5. 二进制数据类型: 用于存储二进制数据,包括 BINARY, VARBINARY, TINYBLOB, BLOB, MEDIUMBLOB, 和 LONGBLOB.
  6. 日期和时间类型(Date and Time Types): DATE 用于存储日期。日期格式为’YYYY-MM-DD’,如’2023-10-12’。 TIME 用于存储时间。时间格式为’HH:MM:SS’,如 ‘14:30:45’。 YEAR 用于存储年份,可以使用两位或四位格式(’YY’或’YYYY’)
    DATETIME 用于存储日期和时间。能够保存从 1001 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。它与时区无关,格式为’YYYY-MM-DD HH:MM:SS’,如’2023-10-12 14:30:45’。
    TIMESTAMP 用于存储日期和时间,在插入或更新时自动记录当前时间。使用 4 个字节,只能表示从 1970 年到 2038 年。时区有关,即一个时间戳在不同的时区所代表的具体时间是不同的。应该尽量使用 TIMESTAMP,因为它比 DATETIME 空间效率更高。
  7. 布尔类型: 用于存储布尔值,包括 BOOLEAN, BOOL, TINYINT(1)
  8. 枚举类型: 用于存储枚举值,其中一个预定义的枚举值,如 ENUM('value1', 'value2', ...)
  9. 集合类型: 用于存储一个或多个预定义的集合值,如 SET('value1', 'value2', ...)
  10. 自动增长类型: 用于自动生成唯一标识符,如 AUTO_INCREMENT

函数依赖

记 A->B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。
如果 {A1,A2,… ,An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。
对于 A->B,如果能找到 A 的真子集 A’,使得 A’-> B,那么 A->B 就是部分函数依赖,否则就是完全函数依赖。
对于 A->B,B->C,则 A->C 是一个传递函数依赖。
有依赖:学号 -> 姓名、学院, 学院 -> 院长, 学号、课程-> 成绩
则 成绩Grade 完全函数依赖于键码(学号,课程),它没有任何冗余数据,每个学生的每门课都有特定的成绩。姓名, 学院 和 院长 都部分依赖于键码,当一个学生选修了多门课时,这些数据就会出现多次,造成大量冗余数据。

MySQL 范式

范式理论是为了解决四种异常。不符合范式的关系,会产生很多异常:1、冗余数据。2、修改异常: 修改了一个记录中的信息,但是另一个记录中相同的信息却没有被修改。3、删除异常: 删除一个信息,那么也会丢失其它信息。4、插入异常: 例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。
高级别范式的依赖于低级别的范式,1NF 是最低级别的范式。

  1. 第一范式 (1NF):属性不可分。
  2. 第二范式 (2NF):每个非主属性完全函数依赖于键码。可以通过分解来满足。(一张表分解成多张表)
  3. 第三范式 (3NF):非主属性不传递函数依赖于键码。可以进行分解。
    for more:https://blog.csdn.net/calcular/article/details/79332453

事务管理

  • 事务:指的是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。
    • 原子性(Atomicity)事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
    • 一致性(Consistency)数据库在事务执行前后都保持一致性状态。一致性状态下,所有事务对一个数据的读取结果都是相同的。
    • 隔离性(Isolation)一个事务所做的修改在最终提交以前,对其它事务是不可见的。
    • 持久性(Durability)一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。可以通过数据库备份和恢复来实现,在系统发生崩溃时,使用备份的数据库进行数据恢复。
  • 并发一致性:产生不一致的主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。
    • 丢失修改:T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。
    • 读脏数据:T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。
    • 不可重复读:T2 读取一个数据,T1 对该数据做了修改。 T2 再次读取这个数据时读取的结果和第一次读取的结果不同。
    • 幻影读:T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据 结果和第一次不同。
      并发控制可以通过 封锁 来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的 隔离级别,让用户以一种更轻松的方式处理并发一致性问题。
  • 封锁
    • 封锁粒度:MySQL 中提供了两种封锁粒度,行级锁以及表级锁。
    • 读写锁:1、排它锁(Exclusive),简写为 X 锁,又称写锁。 2、共享锁(Shared),简写为 S 锁,又称读锁。
    • 意向锁:使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。
    • 封锁协议:1. 三级封锁协议 2. 两段锁协议
  • 隔离等级:MySQL支持 四种 标准的事务隔离级别,这些隔离级别定义了事务之间的可见性和并发控制。
    READ UNCOMMITTED / READ COMMITTED / REPEATABLE READ / SERIALIZABLE 脏读 不可重复读 幻影读
    未提交读(允许一个事务读取另一个事务未提交的数据)
    提交读(一个事务只能读取到另一个事务已经提交的数据) ×
    可重复读(事务执行期间,一个事务多次读取同一行数据时,会得到相同的结果) × ×
    可串行化(最高的隔离级别,确保事务串行执行) × × ×
  • 数据库默认隔离级别
    oracle数据库默认的隔离级别是:读已提交。
    mysql数据库默认的隔离级别是:可重复读。
  • InnoDB 中如何防止幻读 or MVCC 实现
    • 1、执行普通 select,此时会以 MVCC 快照读的方式读取
      (1)一致性非锁定读(快照读),普通的SELECT,通过多版本并发控制(MVCC)实现。
      (2)在快照读下,RR 隔离级别只会在事务开启后的第一次查询生成 Read View ,并使用至事务提交。所以在生成 Read View 之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 “幻读”。
      (3)只有在事务隔离级别 RC(读取已提交) 和 RR(可重读)下,InnoDB 才会使用一致性非锁定读。
    • 2、执行 select…for update/lock in share mode、insert、update、delete 等当前读
      (1)一致性锁定读(当前读),SELECT … FOR UPDATE/SELECT … LOCK IN SHARE MODE/INSERT/UPDATE/DELETE,通过锁实现。
      (2)在当前读下,读取的都是最新的数据,如果其它事务有插入新的记录,并且刚好在当前事务查询范围内,就会产生幻读!InnoDB 使用 Next-key Lock 来防止这种情况。当执行当前读时,会锁定读取到的记录的同时,锁定它们的间隙,防止其它事务在查询范围内插入数据。只要我不让你插入,就不会发生幻读。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      START TRANSACTION;  -- 启动事务,用于标志事务的开始。在这之后的 SQL 语句都将在一个事务中执行 
      COMMIT; -- 提交事务,当所有的 SQL 语句都执行成功时,使用 `COMMIT` 命令来提交事务,将更改永久保存到数据库。提交后,事务结束。
      ROLLBACK; -- 回滚事务,如果在事务执行的过程中发生了错误或者不符合条件,可以使用 `ROLLBACK` 命令来回滚事务,撤销所有的更改,使数据库回到事务开始前的状态。

      SAVEPOINT savepoint_name; -- 保存点,用于创建一个保存点,以便在事务中的某一时刻回滚到这个保存点。可以在事务中设置多个保存点。
      ROLLBACK TO savepoint_name; -- 回滚到保存点,如果在事务中的某一步出现问题,可以回滚到之前设置的某个保存点,而不必回滚整个事务。

      -- 设置事务隔离级别,包括 `READ UNCOMMITTED`、`READ COMMITTED`、`REPEATABLE READ` 和 `SERIALIZABLE`。
      SET TRANSACTION ISOLATION LEVEL level;

      -- MySQL 默认是自动提交模式,即每个 SQL 语句都会自动成为一个事务并提交。命令关闭自动提交,再通过 `COMMIT` 手动提交,或者通过 `ROLLBACK` 回滚。
      SET autocommit = 0;

      --示例 1
      START TRANSACTION;
      -- 执行一系列 SQL 语句
      -- 提交事务
      COMMIT;

      --示例 2
      START TRANSACTION;
      -- 执行一系列 SQL 语句
      -- 如果发生错误,回滚事务
      ROLLBACK;

MySQL 存储引擎

  • InnoDB
    • 是 MySQL 默认的事务型存储引擎。 实现了四个标准的隔离级别,默认级别是可重复读。在可重复读隔离级别下,通过多版本并发控制(MVCC)+ 间隙锁(Next-Key Locking)防止幻影读
    • 主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。 内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等
    • 支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。
  • MyISAM
    • 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它
    • 提供了大量的特性,包括压缩表、空间数据索引等。
    • 不支持事务
    • 不支持行级锁,只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。
    • 可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作非常慢
    • 如果指定了 DELAY KEY WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作

MySQL 索引

在MySQL中,索引是一种用于提高数据库查询效率的数据结构。它类似于书的目录,通过在数据库表上创建索引,可以快速定位并访问表中的特定数据行,而无需全表扫描。索引在数据库的性能优化中扮演着关键的角色,特别是在大型数据集上。
索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。

  • 索引类型
    1. B+Tree 索引:是大多数 MySQL 存储引擎的默认索引类型。因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。
      InnoDB 的 B+Tree 索引分为主索引和辅助索引。主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。辅助索引的叶子节点的 data 域记录着主键的值,因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到主索引中进行查找。
    2. 哈希索引:能以 O(1) 时间进行查找,但是失去了有序性。存在限制:无法用于排序与分组;只支持精确查找,无法用于部分查找和范围查找。
      InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
    3. 全文索引:MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。
    4. 空间数据索引:MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。必须使用 GIS 相关的函数来维护数据
  • 索引的优点:大大减少了服务器需要扫描的数据行数。帮助服务器避免进行排序和分组,也就不需要创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,因为不需要排序和分组,也就不需要创建临时表)。将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,也就将相邻的数据都存储在一起)。
  • 索引的使用场景
    • 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。
    • 对于中到大型的表,索引就非常有效。
    • 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。

B+Tree 索引

是大多数 MySQL 存储引擎的默认索引类型因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组可以指定多个列作为索引列,多个索引列共同组成键.
适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。
InnoDB 的 B+Tree 索引分为主索引和辅助索引.
主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。

索引优化

1.独立的列
在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。例如下面的查询不能使用 actor id 列的索引:
2.多列索引
在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor id和 film id 设置为多列索引。
3.索引列的顺序
4.前缀索引对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。对于前缀长度的选取需要根据索引选择性来确定。
5.覆盖索引


MySQL - 一条 SQL 的执行过程详解

https://pdai.tech/md/db/sql-mysql/sql-mysql-execute.html

在系统和 MySQL 进行交互之前,MySQL 驱动会帮我们建立好连接,然后我们只需要发送 SQL 语句就可以执行 CRUD 了。
java 系统在通过 MySQL 驱动和 MySQL 数据库连接的时候是基于 TCP/IP 协议的,多线程请求的时候频繁的创建和销毁连接显然是不合理的。在访问 MySQL 数据库的时候,建立的连接并不是每次请求都会去创建的,而是从数据库连接池中去获取,这样就解决了因为反复的创建和销毁连接而带来的性能损耗问题了。MySQL 的架构体系中也已经提供了这样的一个池子,也是数据库连池。双方都是通过数据库连接池来管理各个连接的,这样一方面线程之前不需要是争抢连接,更重要的是不需要反复的创建的销毁连接。

网络连接必须由线程来处理

网络中的连接都是由线程来处理的,所谓网络连接说白了就是一次请求,每次请求都会有相应的线程去处理的。也就是说对于 SQL 语句的请求在 MySQL 中是由一个个的线程去处理的。

SQL 接口:MySQL 中处理请求的线程在获取到请求以后获取 SQL 语句去交给 SQL 接口去处理。
查询解析器:会将 SQL 接口传递过来的 SQL 语句进行解析,翻译成 MySQL 自己能认识的语言
MySQL 查询优化器:查询优化器内部具体怎么实现的我们不需要是关心,我需要知道的是 MySQL 会帮我去使用他自己认为的最好的方式去优化这条 SQL 语句,并生成一条条的执行计划,比如你创建了多个索引,MySQL 会依据成本最小原则来选择使用对应的索引,这里的成本主要包括两个方面, IO 成本和 CPU 成本。MySQL 优化器 会计算 「IO 成本 + CPU」 成本最小的那个索引来执行
存储引擎:查询优化器会调用存储引擎的接口,去执行 SQL,也就是说真正执行 SQL 的动作是在存储引擎中完成的。数据是被存放在内存或者是磁盘中的(存储引擎是一个非常重要的组件,后面会详细介绍)
执行器:执行器是一个非常重要的组件,因为前面那些组件的操作最终必须通过执行器去调用存储引擎接口才能被执行。执行器最终最根据一系列的执行计划去调用存储引擎的接口去完成 SQL 的执行

存储引擎

当我们系统发出这样的查询去交给 MySQL 的时候,MySQL 会按照我们上面介绍的一系列的流程最终通过执行器调用存储引擎去执行,流程图就是上面那个。
在执行这个 SQL 的时候 SQL 语句对应的数据要么是在内存中,要么是在磁盘中,如果直接在磁盘中操作,那这样的随机IO读写的速度肯定让人无法接受的,所以每次在执行 SQL 的时候都会将其数据加载到内存中,这块内存就是 InnoDB 中一个非常重要的组件:缓冲池 Buffer Pool

关于Buffer Pool、Redo Log Buffer 和undo log、redo log、bin log 概念以及关系:
Buffer Pool 是 MySQL 的一个非常重要的组件,因为针对数据库的增删改操作都是在 Buffer Pool 中完成的
Undo log 记录的是数据操作前的样子
redo log 记录的是数据被操作后的样子(redo log 是 Innodb 存储引擎特有)
bin log 记录的是整个操作记录(这个对于主从复制具有非常重要的意义)
、、,,
,。

从准备更新一条数据到事务的提交的流程描述

  • 首先执行器根据 MySQL 的执行计划来查询数据,先是从缓存池中查询数据,如果没有就会去数据库中查询,如果查询到了就将其放到缓存池中
  • 在数据被缓存到缓存池的同时,会写入 undo log 日志文件更
  • 新的动作是在 BufferPool 中完成的,同时会将更新后的数据添加到 redo log buffer 中
  • 完成以后就可以提交事务,在提交的同时会做以下三件事
    • 将redo log buffer中的数据刷入到 redo log 文件中
    • 将本次操作记录写入到 bin log文件中
    • 将 bin log 文件名字和更新内容在 bin log 中的位置记录到redo log中,同时在 redo log 最后添加 commit 标记
  • 至此表示整个更新事务已经完成

关系型数据库是如何工作的

SQL DB 组成

核心组件

进程管理器(process manager): 很多数据库具备一个需要妥善管理的进程/线程池。再者,为了实现纳秒级操作,一些现代数据库使用自己的线程而不是操作系统线程
网络管理器(network manager): 网路 I/O 是个大问题,尤其是对于分布式数据库。所以一些数据库具备自己的网络管理器
文件系统管理器(File system manager): 磁盘 I/O 是数据库的首要瓶颈。具备一个文件系统管理器来完美地处理OS文件系统甚至取代OS文件系统,是非常重要的。
内存管理器(memory manager): 为了避免磁盘 I/O 带来的性能损失,需要大量的内存。但是如果你要处理大容量内存你需要高效的内存管理器,尤其是你有很多查询同时使用内存的时候。
安全管理器 (Security Manager) : 用于对用户的验证和授权
客户端管理器 (Client manager) : 用于管理客户端连接。

工具

备份管理器 (Backup manager) : 用于保存和恢复数据
恢复管理器 (Recovery manager) : 用于崩溃后重启数据库到一个一致状态。
监控管理器 (Monitor manager) : 用于记录数据库活动信息和提供监控数据库的工具。
管理员管理器(Administration manager) : 用于保存元数据(比如表的名称和结构),提供管理数据库、模式、表空的工具

查询管理器

查询解析器 (Query parser) : 用于检查查询是否合法
查询重写器 (Query rewriter) : 用于预优化查询
查询优化器 (Query optimizer) : 用于优化查询
查询执行器 (Query executor) : 用于编译和执行查询

数据管理器

事务管理器 (Transaction manager) : 用于处理事务
缓存管理器 (Cache manager): 数据被使用之前置于内存,或者数据写入磁盘之前置于内存
数据访问管理器 (Data access manager) : 访问磁盘中的数据

数据查询的流程

  • 客户端管理器: 客户端管理器是处理客户端通信的。客户端可以是一个(网站)服务器或者一个最终用户或最终应用。客户端管理器通过一系列知名的API(JDBC, ODBC, OLE-DB …)提供不同的方式来访问数据库。客户端管理器也提供专有的数据库访问API。
    JDBC 是 Java 编程语言中用于连接和操作数据库的标准API。可以理解为 JDBC 是 Java 与数据库之间的桥梁,允许 Java 应用程序与 MySQL 数据库进行通信和交互。MyBatis 是一个开源的Java持久层框架,用于将对象与关系数据库的表之间进行映射,使用 MyBatis 来操作 MySQL 数据库。)
  • 查询管理器:查询首先被解析并判断是否合法、然后被重写,去除了无用的操作并且加入预优化部分、接着被优化以便提升性能,并被转换为可执行代码和数据访问计划、然后计划被编译、最后,被执行
  • 数据管理器:在这一步,查询管理器执行了查询,需要从表和索引获取数据,于是向数据管理器提出请求。
  • 客户端管理器

管理用户权限

1
2
3
4
5
6
-- 创建用户
CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';
-- 授予用户权限
GRANT ALL PRIVILEGES ON mydatabase.* TO 'newuser'@'localhost';
-- 刷新权限
FLUSH PRIVILEGES;

备份和恢复

1
2
3
4
# 备份数据库
mysqldump -u username -p mydatabase > backup.sql
# 恢复数据库
mysql -u username -p mydatabase < backup.sql

VMware / SSH

虚拟机 (VM)

  • 虚拟机(Virtual Machine)是一种利用软件模拟硬件的技术,使得一台物理计算机可以同时运行多个虚拟的操作系统或应用程序实例。虚拟机技术提供了更好的资源利用率、隔离性和灵活性,常见的虚拟机有硬件虚拟机和软件虚拟机两种。
  • 它包括虚拟的 CPU、内存、硬盘、网络接口等组件。
  • 可以在虚拟机上安装操作系统和应用程序,就像在物理计算机上一样。
  • 虚拟机和容器的区别:虚拟机模拟整个操作系统,资源占用相对较多;容器共享主机的操作系统内核,更轻量级,启动速度快。

VMware Workstation

  • VMware Workstation 是一款用于虚拟化的桌面软件,它允许用户在单个物理计算机上运行多个虚拟操作系统。
  • 创建虚拟机:启动 VMware Workstation,
    1、创建新虚拟机,选择ISO文件,填写虚拟机的名称以及将来保存的位置;
    2、虚拟机配置:按照向导的指示配置虚拟机,包括选择操作系统、分配内存、创建虚拟硬盘等;安装操作系统:
    3、启动虚拟机,按照正常流程安装选定的操作系统。
  • 虚拟机克隆: VMware Workstation 允许你克隆虚拟机,以便快速创建相似的虚拟机。
  • 虚拟机导入:(除了创建虚拟机外) 你可以导入其他虚拟化平台的虚拟机。导入虚拟机时,通常使用的是虚拟机的配置文件和虚拟硬盘文件。(存放于 C:\Users\蔡枫\Documents\Virtual MachinesD:\Virtual Machines
    • 虚拟机配置文件: 这个文件包含了虚拟机的设置、硬件配置、网络配置等信息。在 VMware 中,这个文件通常有一个扩展名为 .vmx。当你导入虚拟机时,你需要选择这个配置文件。
    • 虚拟硬盘文件: 这个文件包含了虚拟机的硬盘数据,即操作系统和应用程序的安装等信息。在 VMware 中,虚拟硬盘文件的格式通常是 .vmdk。你也需要选择这个文件来导入虚拟机。
  • 在VMware界面中操作虚拟机非常不友好,一般推荐使用专门的SSH客户端
    市面上常见有:Xshell,Finshell,MobarXterm

CentOS

  • 一个基于Red Hat Enterprise Linux(RHEL)源代码构建的开源操作系统。默认账号:root,,账号2:leo,密码:
  • 虚拟机目录:D:\VMCentOS-7,(网络适配器)名称:ens33;默认路由(网关):192.168.111.2,DNS:192.168.111.2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    [leo@localhost ~]$ ifconfig
    ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
    inet 192.168.111.128 netmask 255.255.255.0 broadcast 192.168.111.255 # IPv4 子网掩码 广播地址
    inet6 fe80::2304:6b2e:1716:b51 prefixlen 64 scopeid 0x20<link> # IPv6地址
    ether 00:0c:29:1a:cb:1a txqueuelen 1000 (Ethernet) # MAC 地址
    RX packets 1704 bytes 1628741 (1.5 MiB)
    RX errors 0 dropped 0 overruns 0 frame 0
    TX packets 850 bytes 71942 (70.2 KiB)
    TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
    设置 ipv4 method为手动(网卡地址改为静态IP,这样可以避免每次启动虚拟机IP都变化)

Kali Linux

Kali Linux(简称Kali)是一个基于Debian Linux的专业渗透测试和网络安全评估发行版。它提供了大量的安全工具,方便安全专业人员进行渗透测试、网络扫描、恢复密码等操作。默认账号:kali,默认密码:kali
专业工具集:预装了大量的渗透测试和安全评估工具,包括 Metasploit、Wireshark、Nmap、Aircrack-ng 等。


SSH

  1. SSH(Secure Shell)是一种用于在网络上进行加密通信的协议。它通常用于通过不安全的网络(例如互联网)安全地访问远程计算机上的终端。SSH 协议的主要目的是提供加密和身份验证,确保在客户端和服务器之间传输的数据是安全的。
  2. SSH 协议有两个主要组件:SSH客户端:用于发起远程连接的计算机上的程序。通过 SSH 客户端,用户可以连接到远程计算机并执行命令; SSH服务器:运行在远程计算机上的程序,接受来自客户端的连接并提供终端访问或执行其他操作。
  3. SSH 还可以用于文件传输和端口转发等用途,使其成为管理和维护远程服务器的强大工具。在日常工作中,开发人员、系统管理员和网络管理员经常使用 SSH 来远程管理和维护服务器。

SSH命令行工具

Windows命令提示符和PowerShell都支持SSH命令。你可以使用ssh命令来连接到远程服务器

1
2
ssh username@hostname  # 用户名 @ 公网IP
ssh -i 密钥文件 username@hostname # 密钥登录

SSH客户端:MobarXterm

如果远程服务器上的操作系统的界面使用起来不方便,可以使用ssh客户端连接并操控远程服务器,包括本机上的虚拟机;
在VMware界面中操作虚拟机非常不友好,一般推荐使用专门的SSH客户端,常见有 Xshell,Finshell,MobarXterm

连接虚拟机

1、打开MobarXterm,点击session按钮,进入会话管理:
2、在弹出的session管理页面中,填写信息:Remote Host(虚拟机的ipv4地址),Specify name(root)
3、输入密码:(与虚拟机的一样?)cx0xx1xx,连接本机上 VMware中的 CentOS7系统的虚拟机
4、连接(虚拟机)成功后,进入操作界面了,通过 MobarXterm界面 对 VMware中的虚拟机 进行操作。

使用 MobarXterm 操作 Docker

1、启动虚拟机,使用 MobarXterm 连接虚拟机成功后,进入操作界面。在 MobarXterm界面上 对 VMware中的虚拟机 进行操作。
2、安装配置 Docker

1
2
3
4
5
6
7
8
9
10
# 首先要安装一个yum工具,并配置Docker的yum源
yum install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 安装Docker
yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl start docker # 启动Docker
systemctl stop docker # 停止Docker
systemctl restart docker # 重启
systemctl enable docker # 设置开机自启
docker ps # 执行docker ps命令,如果不报错,说明安装启动成功

3、配置阿里云镜像加速,重启docker

Linux

Linux 是一种免费、开源的类Unix操作系统内核。它最初由芬兰的Linus Torvalds在1991年创建,并迅速发展成为一个庞大且活跃的开源社区项目。Linux内核是操作系统的核心部分,但通常与 GNU 工具和其他软件一起使用,形成完整的(GNU/Linux)操作系统,通常被称为 Linux发行版(如 Ubuntu、Red Hat、CentOS)

GNU

GNU 计划,译为革奴计划,它的目标是创建一套完全自由的操作系统,称为 GNU,其内容软件完全以 GPL 方式发布。其中 GPL 全称为 GNU 通用公共许可协议,包含了以下内容:
1、以任何目的运行此程序的自由;2、再复制的自由;3、改进此程序,并公开发布改进的自由。

发行版

Linux 发行版是 Linux 内核及各种应用软件的集成版本。

基于的包管理工具 商业发行版 社区发行版
RPM Red Hat Fedora / CentOS
DPKG Ubuntu Debian

Bash

是一种Unix和Linux操作系统中常用的命令行解释器和脚本语言。它是许多Linux发行版和Unix系统默认的命令行shell,使用户能够以文本方式与系统进行交互,用于与操作系统进行交互、管理文件、执行命令和编写脚本。
命令行shell是一种计算机程序,它允许用户通过文本界面与计算机操作系统进行交互,以执行各种命令和操作。用户可以在命令行shell中输入文本命令,然后系统会解释和执行这些命令。这种方式通常与图形用户界面 GUI相对。
Bash支持许多功能,包括:

  • 执行命令和程序:Bash允许用户执行各种命令和程序,可以通过命令行或脚本文件来执行。
  • 变量和环境设置:Bash支持变量,用户可以定义和使用环境变量,这些变量可用于存储数据和配置。
  • 文件操作:用户可以使用Bash执行文件和目录的操作,如创建、复制、移动、删除文件等。
  • 管道和重定向:Bash允许将命令的输出连接到其他命令的输入,还支持文件重定向,使用户能够将命令的输出保存到文件或从文件中读取输入。
  • 条件语句和循环:Bash支持条件语句(如if语句)和循环结构(如for循环和while循环),以实现更复杂的控制流程。
  • 脚本编写:用户可以编写Bash脚本,这是一系列命令和操作的集合,用于执行自动化任务、自定义系统配置和处理数据等。

而在 Windows 系统上,PowerShell 是一个更现代、功能更强大的命令行工具,特别适用于系统管理和自动化任务。尽管命令行程序cmd 仍然存在并且仍然可以使用,但PowerShell已成为Windows系统管理和开发的首选工具。

WSL

WSL(Windows Subsystem for Linux)允许用户直接在 Windows 上运行Linux 环境(包括大多数命令行工具、实用程序和应用程序),无需修改,无需单独的虚拟机或双重启动。与完整虚拟机相比,WSL 需要的资源(CPU、内存和存储)更少。
WSL是 Windows10/11 自带的一个功能,默认是关闭的,旨在为希望同时使用 Windows 和 Linux 的用户提供无缝且高效的体验。能够在 Bash shell 中运行Linux,并选择您的发行版(Ubuntu、Debian、OpenSUSE、Kali、Alpine 等)。

  • 开启 Hype-V:ctrl+r - control - 程序 - 开启Windows功能 - 勾选Hype-V
  • 启动 msl:以管理员打开powershell输入下列命令
    1
    dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
  • 启用虚拟化
    1
    dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
  • 更新wsl版本;或手动下载最新版本的 Linux 内核更新包,运行(双击运行 - 系统将提示您提供提升的权限,选择“是”以安装)
    1
    wsl --update
  • 将 WSL 2 设置为默认版本
    1
    wsl --set-default-version 2
  • 安装Linux发行版,创建账号用户密码,Installation successful
    1
    wsl --install -d Ubuntu
  • 至此,已成功安装并设置了一个与 Windows 操作系统完全集成的 Linux 发行版!下次,您可以以管理员身份打开 PowerShell 或 cmd 命令控制台,输入 wsl 命令即可打开安装好的Linux发行版(如Ubuntu)了。
    注意:在wsl中,本地磁盘都位于 /mnt 目录。比如 c 盘位于 /mnt/c 。要进入 d 盘,执行命令 cd /mnt/d
  • PowerShell 进出 wsl:
    1
    2
    3
    4
    PS C:\Windows\system32> wsl
    root@caifeng7138:/mnt/c/Windows/system32# exit
    logout
    PS C:\Windows\system32>

Linux 命令

Linux 操作系统拥有丰富的命令行工具。可以使用--help获取指令的基本用法与选项介绍;在终端中使用man命令查看每个命令的手册页以获取更多信息,例如 man lsdoc:/usr/share/doc 存放着软件的一整套说明文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# **管理员:** 允许一般用户使用 root 可执行的命令,不过只有在 /etc/sudoers 配置文件中添加的用户才能使用该指令。
sudo + 命令
which # 指令搜索。-a : 将所有指令列出,而不是只列第一个
# **文件和目录管理:**
ls # 列出目录中的文件和子目录,是 list 的缩写(dir 命令通常不是默认可用的)
cd # 切换当前工作目录。
pwd # 显示当前工作目录的路径。
mkdir # 创建新目录。
rmdir / rm -r # 删除目录。
touch # 创建新文件或更新文件的访问时间戳。
vim + 文件名 # 编辑文件
cp # 复制文件或目录。
mv # 移动文件或目录,也可用于重命名文件。
rm # 删除文件。
cat # 查看文件内容。
tac # 是 cat 的反向操作,从最后一行开始打印。
more # 和 cat 不同的是它可以一页一页查看文件内容,比较适合大文件的查看。
head # 取得文件前几行,-n指定n行
tail # 是 head 的反向操作,只是取得是后几行。
od # 以字符或者十六进制的形式显示二进制文件。
whereis # 文件搜索。速度比较快,因为它只搜索几个特定的目录。
locate # 文件搜索。可以用关键字或者正则表达式进行搜索
find # 文件搜索。可以使用文件的属性和权限进行搜索。example: find . -name "shadow*"
## **压缩,打包:**
gzipg # zip 是 Linux 使用最广的压缩指令,可以解开 compress、zip 与 gzip 所压缩的文件。
tar # 打包。tar 不仅可以用于打包,也可以使用 gip、bzip2、xz 将打包文件进行压缩。
## **系统管理:**
ps # 查看运行中的进程。
top # 实时显示进程信息。示例: 两秒钟刷新一次 ## top -d 2
netstat # 查看占用端口的进程
kill # 终止进程。
shutdown / reboot # 关机或重启系统。
uname # 显示系统信息。
date # 显示或设置系统时间。
useradd / userdel # 添加或删除用户。
passwd # 更改用户密码。
## **包管理:** RPM 和 DPKG 为最常见的两类软件包管理工具;YUM 基于 RPM,具有依赖管理功能,并具有软件升级的功能
apt-get # 安装、更新和删除软件包。(Debian/Ubuntu)
yum # 安装、更新和删除软件包。(Red Hat/CentOS)
snap install # 下载最新
dpkg # 直接管理软件包。(Debian/Ubuntu)
rpm # 直接管理软件包。(Red Hat/CentOS)
pip # Python包管理器。
## **网络工具:**
ping # 测试网络连接。
curl / wget # 下载文件或内容。
ssh # 远程登录到其他计算机。
scp # 安全拷贝文件到远程主机。
ifconfig # 显示网络接口的配置信息,包括IP地址、子网掩码等。(Windows系统上是 ipconfig)

二、磁盘

Linux 中每个硬件都被当做一个文件,包括磁盘。磁盘以磁盘接口类型进行命名,常见磁盘的文件名如下:

  • IDE 磁盘: /dev/hd[a-d]
  • SATA/SCSI/SAS 磁盘: /dev/sd[a-p]
    其中文件名后面的序号的确定与系统检测到磁盘的顺序有关,而与磁盘所插入的插槽位置无关。

三、分区

磁盘分区表主要有两种格式,一种是限制较多的 MBR 分区表,一种是较新且限制较少的 GPT 分区表。

四、文件系统

Linux 文件系统是一种层次化的组织结构,用于存储和管理文件。这个文件系统通常由一个根目录和一系列子目录、文件以及链接组成。
Linux 系统是以文件目录系统为根基的,一切东西都是文件,包括硬件、进程、命令、系统设置等等。

  1. 树状结构: Linux文件系统采用树状结构,以根目录(/)作为顶层目录。树状结构允许文件和目录以层次结构进行组织,方便用户和应用程序定位和访问文件。(Windows 中的目录斜杠与之相反,是右斜杠 \)
  2. 根目录(/): 根目录是文件系统的最顶层目录,包含整个文件系统的目录和文件。所有的文件和目录都位于根目录下或其子目录中。
  3. 目录(文件夹): 目录是用于组织和存储文件的容器。它类似于Windows中的文件夹。目录中可以包含文件和其他子目录。
  4. 文件: 文件是数据的容器。在Linux中,一切都是文件,包括文本文件、二进制文件、设备文件等。
  5. 路径: 路径是指定文件或目录位置的方式。绝对路径从根目录开始,而相对路径从当前工作目录开始。例如,/home/user/documents 是一个绝对路径,表示文档目录在用户主目录下。

十、进程管理


Vim

Vim(Vi Improved)是一款强大的文本编辑器,广泛用于Linux和Unix系统。它是Vi编辑器的改进版本,提供了许多额外的功能和改进。

启动和退出

打开文件:进入正常模式

1
vim filename

退出Vim:

  • 进入命令模式(按 Esc 键),然后输入 :q,按 Enter;按 u 来撤销上一步操作。
  • 如果有未保存的更改,使用 :q! 来强制退出,或者 :wq 保存并退出。

模式驱动

  1. 普通模式:在这个模式下,键盘输入被解释为命令。你可以使用这些命令来移动光标、删除文本、复制粘贴等。
  2. 插入模式:在这个模式下,你可以输入文本。按下 “i” 进入插入模式,按 “Esc” 退出插入模式并返回到普通模式。
  3. 可视模式:这模式下,你可以选择文本块以进行复制、剪切或其他操作。按下 “v” 进入可视模式,然后使用移动命令来选择文本。
  4. 命令行模式:在这个模式下,你可以输入各种命令,例如保存文件、退出 Vim、搜索等。按下 “:” 进入命令行模式。
  5. 选择模式*与可视模式有些类似,但是在选择后直接进入插入模式。按下 “v” 进入可视模式,然后按 “Shift” 和 “v” 进入选择模式。

移动光标

  • 移动光标: h:左移 - j:下移 - k:上移 - l:右移

  • 移动到行首或行尾: 0:移动到行首 - $:移动到行尾

搜索和替换

  • 搜索: 在命令模式下,按 /,然后输入要搜索的文本,按 Enter

  • 替换: 在命令模式下,输入 :%s/old/new/g 来将所有匹配的 old 替换为 new

操 作 系 统

硬件结构

1.1 CPU是如何执行程序的

  • 图灵机:基本思想是用机器来模拟人们用纸笔进行数学运算的过程,而且还定义了计算机由哪些部分组成,程序又是如何执行的。

  • 冯诺依曼模型
    1945 年冯诺依曼和其他计算机科学家们提出了计算机具体实现的报告,其遵循了图灵机的设计,而且还提出用电子元件构造计算机,并约定了用二进制进行计算和存储,还定义计算机基本结构为 5 个部分,分别是中央处理器(CPU)、内存、输入设备、输出设备、总线。

    • 内存:我们的程序和数据都是存储在内存,存储的区域是线性的。数据存储的单位是一个二进制位(bit),即 0 或 1。最小的存储单位是字节(byte),1 字节等于 8 位。内存的地址是从 0 开始编号的,然后自增排列,最后一个地址为内存总字节数-1,这种结构好似我们程序里的数组,所以内存的读写任何一个数据的速度都是一样的。
    • 中央处理器:即 CPU,32 位和 64 位 CPU 最主要区别在于一次能计算多少字节数据:32 位 CPU 一次可以计算 4 个字节;64 位 CPU 一次可以计算 8 个字节;这里的 32 位和 64 位,通常称为 CPU 的位宽。这样设计,是为了能一次计算更大的数值, 8 位的 CPU一次只能计算 1 个字节0~255 范围内的数值,CPU 位宽越大,可以计算的数值就越大。
      CPU 内部还有一些组件,常见的有寄存器、控制单元和逻辑运算单元等。其中,控制单元负责控制 CPU工作,逻辑运算单元负责计算,而寄存器可以分为多种类,常见的寄存器种类:
      • 通用寄存器,用来存放需要进行运算的数据,比如需要进行加和运算的两个数据。
      • 程序计数器,用来存储 CPU 要执行下一条指令「所在的内存地址」,注意不是存储了下一条要执行的指令,此时指令还在内存中,程序计数器只是存储了下一条指令的地址。
      • 指令寄存器,用来存放程序计数器指向的指令,也就是指令本身,指令被执行完成之前,指令都存储在这里。
    • 总线:用于 CPU 和内存以及其他设备之间的通信,总线可分为 3 种:
      • 地址总线,用于指定 CPU 将要操作的内存地址;
      • 数据总线,用于读写内存的数据;
      • 控制总线,用于发送和接收信号,比如中断、设备复位等信号,CPU 收到信号后自然进行响应,这时也需要控制总线;
        当 CPU 要读写内存数据的时候,一般需要通过两个总线:首先要通过「地址总线」来指定内存的地址;再通过「数据总线」来传输数据;
    • 输入、输出设备:输入设备向计算机输入数据,计算机经过计算后,把数据输出给输出设备。期间,如果输入设备是键盘,按下按键时是需要和 CPU 进行交互的,这时就需要用到控制总线了。
  • 线路位宽 与 CPU 位宽
    为了避免低效率的串行传输的方式,线路的位宽最好一次就能访问到所有的内存地址。 CPU 要想操作的内存地址就需要地址总线,如果地址总线只有 1 条,那每次只能表示 「0 或 1」这两种情况,所以 CPU 一次只能操作 2 个内存地址,如果想要 CPU 操作 4G 的内存,那么就需要 32 条地址总线,因为 2 ^ 32 =4G 。
    CPU 的位宽最好不要小于线路位宽,比如 32 位 CPU 控制 40 位宽的地址总线和数据总线的话,工作起来就会非常复杂且麻烦,所以 32 位的 CPU 最好和 32 位宽的线路搭配,因为 32 位 CPU 一次最多只能操作32 位宽的地址总线和数据总线。

  • 程序执行的基本过程
    程序实际上是一条一条指令,所以程序的运行过程就是把每一条指令一步一步的执行起来,负责执行指令的就是 CPU 了。
    第一步,CPU 读取「程序计数器」的值,这个值是指令的内存地址,然后 CPU 的「控制单元」操作「地址总线」指定需要访问的内存地址,接着通知内存设备准备数据,数据准备好后通过「数据总线」将指令数据传给 CPU,CPU 收到内存传来的数据后,将这个指令数据存入到「指令寄存器」。
    第二步,CPU 分析「指令寄存器」中的指令,确定指令的类型和参数,如果是计算类型的指令,就把指令交给「逻辑运算单元」运算;如果是存储类型的指令,则交由「控制单元」执行;
    第三步,CPU 执行完指令后,「程序计数器」的值自增,表示指向下一条指令。这个自增的大小,由CPU 的位宽决定,比如 32 位的 CPU,指令是 4 个字节,需要 4 个内存地址存放,因此「程序计数器」的值会自增 4;

  • a = 1 + 2 执行具体过程(P13
    、、

1.2 存储器金字塔(小林p20)

、、

1.3 如何写出让 CPU 跑得更快的代码?(

,,

1.4 CPU缓存一致性(

、、

1.5 CPU 是如何执行任务的?(p65)

、、

1.6 软中断

。。


操作系统结构

Linux内核 vs Windows内核

,,


内存管理

虚拟内存

,,


进程与线程

,,


调度算法

,,


文件管理

,,


设备管理


网络系统

,,


Linux指令

,,

计 算 机 网 络

OSI 七层模型

OSI(开放系统互连)模型是计算机网络领域中的一个概念模型,将网络通信划分为七个层次,每个层次负责特定的功能。
每个层次都通过下一层次提供的服务来完成其功能。通过将网络协议和功能划分到不同的层次,OSI 模型使得网络设计和实现更加模块化和灵活,有助于不同厂商和组织开发兼容的网络设备和应用。
1、物理层(Physical Layer): 主要关注网络硬件和物理介质。定义了数据传输的物理特性,如电压、电流和传输介质。
2、数据链路层(Data Link Layer): 主要负责点对点的直接通信。 提供了物理层的上层接口,确保数据在相邻节点之间的可靠传输。主要包括两个子层:逻辑链路控制(LLC)和介质访问控制(MAC)。
3、网络层(Network Layer): 提供了在网络上寻址和路由的机制。负责将数据包从源节点传输到目标节点,可能需要通过多个中间节点。有许多不同的协议在这一层运行,最著名的是 IP。
4、传输层(Transport Layer): 主要负责端到端的通信。提供了端到端的可靠数据传输,如 TCP,UDP(不可靠但更高效)
5、会话层(Session Layer): 提供了建立、管理和终止会话的机制。管理数据的对话控制,确保通信的顺序性和同步性。
6、表示层(Presentation Layer): 负责数据的编码、解码和加密。 提供了在不同系统上进行数据格式转换的机制,以确保应用层能够正确地理解数据。
7、应用层(Application Layer): 最高层,直接为用户提供服务。 提供了网络服务和应用软件之间的接口,包括文件传输、电子邮件、远程登录等。

TCP/IP 模型

对于不同设备上的进程间通信,就需要⽹络通信。而要兼容多种多样的设备,就协商出了⼀套通⽤的⽹络协议。(OSI也是)
TCP/IP 模型是实际互联网中最广泛使用的模型,因为它直接映射到互联网的基本协议,其层次划分与 OSI 模型略有不同。
⽹络协议通常是由上到下,分成 5 层,分别是应⽤层,传输层,⽹络层,数据链路层和物理层。

  1. 应用层:提供网络服务和应用程序间的接口,使不同软件能够通过网络通信。当两个不同设备的应⽤需要通信的时候,应⽤就把应⽤数据传给下⼀层,也就是传输层;只需要专注于为⽤户提供应⽤功能,不⽤去关⼼数据是如何传输的。
    ⽽且应⽤层是⼯作在操作系统中的⽤户态,传输层及以下则⼯作在内核态。协议:HTTP,SMTP,DNS等。
  2. 传输层: 应⽤层的数据包会传给传输层,传输层是为应⽤层提供⽹络⽀持的。提供端到端的通信服务,确保数据的可靠传输。处理数据的分段和重新组装,同时提供错误检测和纠正。协议: 主要包括 TCP 和 UDP。
    • TCP 的全称叫传输层控制协议,⼤部分应⽤使⽤的正是 TCP 传输层协议,⽐如 HTTP 应⽤层协议。TCP 相⽐ UDP 多了很多特性,⽐如流量控制、超时重传、拥塞控制等,这些都是为了保证数据包能可靠地传输给对⽅。
    • UDP 相对简单,只负责发送数据包,不保证数据包是否能抵达对⽅,但它实时性相对更好,传输效率也⾼。当然,UDP 也可以实现可靠传输,把 TCP 的特性在应⽤层上实现就可以。
    • 应⽤需要传输的数据可能会⾮常⼤,当传输层的数据包⼤⼩超过 MSS(TCP最⼤报⽂段⻓度)就要将数据包分块,这样即使中途有⼀个分块丢失或损坏了,只需要重新发送这⼀个分块⽽不是整个数据包。每个分块称为⼀个 TCP 段(TCP Segment)。
    • 当设备作为接收⽅时,传输层则要负责把数据包传给应⽤,但是⼀台设备上可能会有很多应⽤在接收或者传输数据,因此需要⽤⼀个编号将应⽤区分开来,这个编号就是端⼝。⽐如 80 端⼝通常是 Web 服务器⽤的,22 端⼝通常是远程登录服务器⽤的。⽽对于浏览器(客户端)中的每个标签栏都是⼀个独⽴的进程,操作系统会为这些进程分配临时的端⼝号。由于传输层的报⽂中会携带端⼝号,因此接收⽅可以识别出该报⽂是发送给哪个应⽤。
  3. 网络层: 处理主机之间的数据包路由,选择最佳路径将数据包从源主机传输到目标主机。协议:IP,ICMP,ARP等。
    • IP 协议会将传输层的报⽂作为数据部分,再加上 IP 包头组装成 IP 报⽂,如果 IP 报⽂⼤⼩超过 MTU(以太⽹中⼀般为1500 字节)就会再次进⾏分⽚,得到⼀个即将发送到⽹络的 IP 报⽂。
    • ⽤ IP地址给设备进⾏编号,对于 IPv4 协议,IP 地址共 32 位,分成四段,每段 8 位。将 IP 地址分成两种意义:⼀个是⽹络号,负责标识该 IP 地址是属于哪个⼦⽹的;⼀个是主机号,负责标识同⼀⼦⽹下的不同主机;这需要配合⼦⽹掩码才能算出 IP 地址 的⽹络号和主机号。那么在寻址的过程中,先匹配到相同的⽹络号,才会去找对应的主机。
    • 除了寻址能⼒, IP 协议还有另⼀个重要的能⼒就是路由。实际场景中,两台设备并不是⽤⼀条⽹线连接起来的,⽽是通过很多⽹关、路由器、交换机等众多⽹络设备连接起来的,那么就会形成很多条⽹络的路径,因此当数据包到达⼀个⽹络节点,就需要通过算法决定下⼀步⾛哪条路径。
    • 所以,IP 协议的寻址作⽤是告诉我们去往下⼀个⽬的地该朝哪个⽅向⾛,路由则是根据「下⼀个⽬的地」选择路径。寻址更像在导航,路由更像在操作⽅向盘。
  4. 链路层: 需要有⼀个专⻔的层来标识⽹络中的设备,让数据在⼀个链路中传输,这就是数据链路层,它主要为⽹络层提供链路级别传输的服务。(负责在物理网络介质上传输数据帧。它包括了对硬件的驱动程序和操作系统中的网络接口卡(NIC)
    • 协议: 以太网、Wi-Fi、PPP 等。
    • 每⼀台设备的⽹卡都会有⼀个 MAC 地址,它就是⽤来唯⼀标识设备的。路由器计算出了下⼀个⽬的地 IP 地址,再通过 ARP 协议找到该⽬的地的 MAC 地址,这样就知道这个 IP 地址是哪个设备的了。
  5. 物理层:当数据准备要从设备发送到⽹络时,需要把数据包转换成电信号,让其可以在物理介质中传输,这⼀层就是物理层,它主要是为数据链路层提供⼆进制传输的服务。

网络协议

网络协议是计算机网络中用于通信的规则和约定的集合。它们定义了在通信系统中数据如何被传输、编码、压缩、路由和解码。网络协议是计算机网络正常运行的基础,它确保了不同设备和应用程序之间的互操作性。以下是一些常见的网络协议:

  1. HTTP (Hypertext Transfer Protocol):
    描述:用于在Web浏览器和Web服务器之间传输超文本的应用层协议。 特点:无状态协议,通常基于请求-响应模型。
    HTTPS (Hypertext Transfer Protocol Secure):
    描述:HTTP的安全版本,通过使用TLS/SSL协议进行加密,提供数据的安全性和隐私。
    特点:加密通信,用于保护敏感信息,如登录凭证和支付信息。
  2. TCP (Transmission Control Protocol):
    描述:面向连接的传输层协议,提供可靠的、有序的、基于字节流的数据传输。
    特点:建立连接、数据传输完整性、流量控制、拥塞控制。
    UDP (User Datagram Protocol):
    描述:面向无连接的传输层协议,提供不可靠但高效的数据传输。 特点:无连接、不提供流控制、不保证数据完整性。
  3. IP (Internet Protocol):
    描述:定义了在网络上发送和接收数据的方式,是一种网络层协议。 特点:用于标识和定位设备,IPv4和IPv6是最常见的版本。
  4. DNS (Domain Name System):
    描述:用于将域名映射到IP地址的分布式数据库系统。 特点:提供域名解析服务,将易记的域名转换为数值型IP地址。
  5. FTP (File Transfer Protocol):
    用于在计算机之间传输文件的标准网络协议。 支持文件上传、下载、删除等操作,可以进行匿名访问或需要身份验证。
  6. SMTP (Simple Mail Transfer Protocol): 在计算机网络上传递和传输电子邮件的协议。 定义了邮件的发送规则,常用于发送邮件。
    POP3 (Post Office Protocol 3): 从远程服务器上下载电子邮件到本地计算机的协议。通常用于接收邮件,支持在线和离线模式。
    IMAP (Internet Message Access Protocol): 与POP3类似,也是用于从远程服务器上下载电子邮件的协议,但提供更多的功能。 支持在邮件服务器上管理邮件,可以通过多个设备同步邮件状态。

网络攻击

  1. DDoS 攻击 (Distributed Denial of Service): 攻击者试图通过将大量伪造的请求发送到目标系统,使其超负荷,导致服务不可用。
  2. 恶意软件 (Malware): 恶意软件是一类有意设计用于破坏、干扰计算机系统或窃取信息的软件,包括病毒、蠕虫、木马等。
  3. 钓鱼攻击 (Phishing): 攻击者伪装成可信任的实体,通过虚假的电子邮件、网站等手段,诱导用户揭示敏感信息如用户名、密码等。
  4. 社会工程学攻击: 攻击者利用心理学和人类行为来欺骗、迫使或引导个人执行某些操作,通常涉及人际交往,以获取机密信息。
  5. SQL 注入攻击: 攻击者通过在用户输入的数据中注入 SQL 代码,从而执行恶意 SQL 查询,破坏数据库的完整性或获取敏感信息。
  6. 跨站脚本攻击 (XSS): 攻击者将恶意脚本注入到网页中,使用户在浏览时执行这些脚本,从而窃取用户信息或劫持用户会话。
  7. 跨站请求伪造 (CSRF): 攻击者通过伪造用户已经认证的请求,利用用户的身份执行未经授权的操作。
  8. 中间人攻击 (Man-in-the-Middle): 攻击者截取和修改通信数据流,使得通信的两个实体认为他们正在直接通信。
  9. DNS 缓存投毒 (DNS Spoofing): 攻击者通过篡改 DNS 查询结果,将用户导向恶意网站。

HTTP

2.1 HTTP 常见面试题

2.1.1 HTTP 基本概念

  • HTTP 是?
    超⽂本传输协议,也就是HyperText Transfer Protocol。超⽂本,它就是超越了普通⽂本的⽂本,它是⽂字、图⽚、视频等的混合体,最关键有超链接,能从⼀个超⽂本跳转到另外⼀个超⽂本。HTML 是最常⻅的超⽂本,本身只是纯⽂字⽂件,但内部⽤很多标签定义了图⽚、视频等的链接,再经过浏览器的解释呈现出⼀个⽂字、有画⾯的⽹⻚。
    HTTP 是⼀个在计算机世界⾥专⻔在「两点」之间「传输」⽂字、图⽚、⾳频、视频等「超⽂本」数据的「约定和规范」。
    1
    2
    3
    4
    5
    6
    7
    8
    # 请求报文: 由请求行、请求头、空行和请求体组成。
    GET /index.html HTTP/1.1
    Host: www.example.com
    Connection: keep-alive
    # 响应报文: 由状态行、响应头、空行和响应体组成。
    HTTP/1.1 200 OK
    Content-Type: text/html
    Content-Length: 123
  • 常见状态码
    • 1xx:1xx 类状态码属于提示信息,是协议处理中的⼀种中间状态,实际⽤到的⽐较少。
    • 2xx:2xx 类状态码表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。
      「200 OK」是最常⻅的成功状态码,表示⼀切正常。如果是⾮ HEAD 请求,服务器返回的响应头都会有 body 数据。
      「204 No Content」也是常⻅的成功状态码,与 200 OK 基本相同,但响应头没有 body 数据。
      「206 Partial Content」是应⽤于 HTTP 分块下载或断点续传,表示响应返回的 body 数据并不是资源的全部⽽是其⼀部分,也是服务器处理成功的状态。
    • 3xx:表示客户端请求的资源发送了变动,需要客户端⽤新的 URL 新发送请求获取资源,也就是重定向
      「301 Moved Permanently」表示永久重定向,说明请求的资源已经不存在了,需改⽤新的 URL 再次访问。
      「302 Found」表示临时重定向,说明请求的资源还在,但暂时需要⽤另⼀个 URL 来访问。
      301 和 302 都会在响应头⾥使⽤字段 Location ,指明后续要跳转的 URL,浏览器会⾃动重定向新的 URL。
      「304 Not Modified」不具有跳转的含义,表示资源未修改,重定向已存在的缓冲⽂件,也称缓存重定向,⽤于缓存控制。
    • 4xx:表示客户端发送的报⽂有误,服务器⽆法处理,也就是错误码的含义。
      「400 Bad Request」表示客户端请求的报⽂有错误,但只是个笼统的错误。
      「403 Forbidden」表示服务器禁⽌访问资源,并不是客户端的请求出错。
      「404 Not Found」表示请求的资源在服务器上不存在或未找到,所以⽆法提供给客户端。
    • 5xx:表示客户端请求报⽂正确,但是服务器处理时内部发⽣了错误,属于服务器端的错误码。
      「500 Internal Server Error」与 400 类型,是个笼统通⽤的错误码,服务器发⽣了什么错误,我们并不知道。
      「501 Not Implemented」表示客户端请求的功能还不⽀持,类似“即将开业,敬请期待”的意思。
      「502 Bad Gateway」通常是服务器作为⽹关或代理时返回的错误码,表示服务器⾃身⼯作正常,访问后端服务器发⽣错误。
      「503 Service Unavailable」表示服务器当前很忙,暂时⽆法响应服务器,类似“⽹络服务正忙,请稍后重试”。
  • http 常⻅字段有哪些?
    • Host 字段:客户端发送请求时,⽤来指定服务器的域名。
    • Content-Length 字段:服务器在返回数据时,会有 Content-Length 字段,表明本次回应的数据⻓度。
    • Connection 字段:最常⽤于客户端要求服务器使⽤ TCP 持久连接,以便其他请求复⽤。HTTP/1.1 版本的默认连接都是持久连接,但为了兼容⽼版本的 HTTP,需要指定 Connection ⾸部字段的值为 Keep-Alive 。
      Connection: keep-alive,⼀个可以复⽤的 TCP 连接就建⽴了,直到客户端或服务器主动关闭连接。但是这不是标准字段。
    • Content-Type 字段:⽤于服务器回应时,告诉客户端,本次数据是什么格式。Content-Type: text/html; harset=utf-8 表明发送的是⽹⻚,⽽且编码是UTF-8。
      客户端请求的时候,可以使⽤ Accept 字段声明⾃⼰可以接受哪些数据格式。
    • Content-Encoding 字段:说明数据的压缩⽅法。Content-Encoding: gzip,表示服务器返回的数据采⽤了 gzip ⽅式压缩,告知客户端需要⽤此⽅式解压。
      客户端在请求时,⽤ Accept-Encoding 字段说明⾃⼰可以接受哪些压缩⽅法。
  • Cookie和Session:
    • Cookie: 由服务器发送到浏览器,保存在浏览器上。包含了与用户相关的信息,如用户ID、会话ID等。
    • Session: 在服务器端保存用户状态的机制,通常使用Cookie来标识用户。
  • RESTful API: 基于HTTP协议设计的一种简洁和统一的Web服务风格。

2.1.2 Get 与 Post

  • GET 和 POST 的区别?
    Get ⽅法的含义是请求从服务器获取资源,这个资源可以是静态的⽂本、⻚⾯、图⽚视频等。
    POST ⽅法则是相反操作,它向 URI 指定的资源提交数据,数据就放在报⽂的 body ⾥。
  • GET 和 POST ⽅法都是安全和幂等的吗?
    在 HTTP 协议⾥,所谓的「安全」是指请求⽅法不会破坏服务器上的资源,「幂等」意思是多次执⾏相同操作结果都相同。
    那么很明显 GET ⽅法就是安全且幂等的,因为它是只读操作,⽆论操作多少次,服务器上的数据都是安全的且每结果都相同。
    因为POST 是新增或提交数据的操作,会修改服务器上的资源,所以不安全,且多次提交数据就会创建多个资源所以不是幂等的。

2.1.3 HTTP 特性

  • HTTP(1.1) 的优点有哪些,怎么体现的?
    1. 简单:HTTP 基本的报⽂格式就是 header + body ,头部信息也是 key-value 简单⽂本的形式,易于理解和使用
    2. 灵活和易于扩展:HTTP协议⾥的各类请求⽅法、URI/URL、状态码、头字段等每个组成要求都没有被固定死,都允许开发⼈员⾃定义和扩充。同时 HTTP 由于是⼯作在应⽤层(OSI 第七层),则它下层可以随意变化。
      HTTPS 也就是在 HTTP 与 TCP 层之间增加了SSL/TLS安全传输层,HTTP/3甚⾄把 TCP层换成了基于 UDP 的 QUIC。
    3. 应⽤⼴泛和跨平台:互联⽹发展⾄今,HTTP 的应⽤范围⾮常的⼴泛,从台式机的浏览器到⼿机上的各种 APP,从看新闻、刷贴吧到购物、理财、吃鸡,HTTP 的应⽤⽚地开花,同时天然具有跨平台的优越性。
  • HTTP的缺点?
    1. ⽆状态双刃剑:⽆状态的好处,因为服务器不会去记忆 HTTP 的状态,所以不需要额外的资源来记录状态信息,这能减轻服务器的负担,把更多的 CPU 和内存⽤来对外提供服务。⽆状态的坏处,既然服务器没有记忆能⼒,它在完成有关联性的操作时会⾮常麻烦。对于⽆状态的问题,解法⽅案有很多种,其中⽐较简单的⽅式⽤ Cookie 技术:通过在请求和响应报⽂中写⼊ Cookie 信息来控制客户端的状态。
    2. 明⽂传输双刃剑:明⽂意味着在传输过程中的信息,是可⽅便阅读的,通过浏览器的 F12 控制台或 Wireshark 抓包都可以直接⾁眼查看,为调试⼯作带了极⼤便利。但正是这样,HTTP 的所有信息都暴露在了光天化⽇下,相当于信息裸奔。
    3. 不安全:HTTP 的安全问题,可以⽤ HTTPS 的⽅式解决,也就是通过引⼊ SSL/TLS 层,使得在安全上达到了极致。
      • 通信使⽤明⽂(不加密),内容可能会被窃听。⽐如,账号信息容易泄漏,那你号没了。
      • 不验证通信⽅的身份,因此有可能遭遇伪装。⽐如,访问假的淘宝、拼多多,那你钱没了。
      • ⽆法证明报⽂的完整性,所以有可能已遭篡改。⽐如,⽹⻚上植⼊垃圾⼴告,视觉污染,眼没了。
  • HTTP/1.1 的性能如何?
    HTTP 协议是基于 TCP/IP,并且使⽤了「请求 - 应答」的通信模式,所以性能的关键就在这两点⾥。
    1. ⻓连接:早期 HTTP/1.0 性能上的⼀个很⼤的问题,那就是每发起⼀个请求,都要新建⼀次 TCP 连接,⽽且是串⾏请求,做了⽆谓的 TCP 连接建⽴(三次握⼿)和断开(四次挥手)。为了解决上述问题,HTTP/1.1 提出了⻓连接的通信⽅式,也叫持久连接,好处在于减少了TCP 连接的重复建⽴和断开所造成的额外开销,减轻了服务器端的负载。
      持久连接的特点是,只要任意⼀端没有明确提出断开连接,则保持 TCP 连接状态。
    2. 管道⽹络传输:HTTP/1.1 采⽤了⻓连接的⽅式,这使得管道(pipeline)⽹络传输成为了可能。即可在同⼀个 TCP 连接⾥⾯,客户端可以发起多个请求,只要第⼀个请求发出去了,不必等其回来,就可以发第⼆个请求出去,可以减少整体的响应时间。
      但是服务器还是按照顺序,先回应 A 请求,完成后再回应 B 请求,可能导致「队头堵塞」。
    3. 队头阻塞:「请求 - 应答」的模式加剧了 HTTP 的性能问题。 因为当顺序发送的请求序列中的⼀个请求因为某种原因被阻塞时,在后⾯排队的所有请求也⼀同被阻塞了,会招致客户端⼀直请求不到数据。
      总之 HTTP/1.1 的性能⼀般般,后续的 HTTP/2 和 HTTP/3 就是在优化 HTTP 的性能。

2.1.4 HTTPS 与 HTTP(小林p27)

  • HTTP 与 HTTPS 有哪些区别?
    1. HTTP 是超⽂本传输协议,信息是明⽂传输,存在安全⻛险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP ⽹络层之间加⼊了 SSL/TLS 安全协议,使得报⽂能够加密传输。
    2. HTTP 连接建⽴相对简单,TCP 三次握⼿之后便可进⾏ HTTP 的报⽂传输。⽽ HTTPS 在 TCP 三次握⼿之后,还需进⾏ SSL/TLS 的握⼿过程,才可进⼊加密报⽂传输。
    3. HTTP 的端⼝号是 80,HTTPS 的端⼝号是 443。
    4. HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。
  • HTTPS 解决了 HTTP 的哪些问题?
    HTTP 由于是明⽂传输,所以安全上存在以下三个⻛险:
    1. 窃听⻛险,⽐如通信链路上可以获取通信内容,⽤户号容易没。
    2. 篡改⻛险,⽐如强制植⼊垃圾⼴告,视觉污染,⽤户眼容易瞎。
    3. 冒充⻛险,⽐如冒充淘宝⽹站,⽤户钱容易没。
      HTTPS 在 HTTP 与 TCP 层之间加⼊了 SSL/TLS 协议,可以很好的解决了上述的⻛险。信息加密:交互信息⽆法被窃取;校验机制:⽆法篡改通信内容,篡改了就不能正常显示;身份证书:证明淘宝是真的淘宝⽹。。
  • HTTPS 是如何解决上⾯的三个⻛险的?
    1. 混合加密:通过混合加密的⽅式可以保证信息的机密性,解决了窃听的⻛险。
      ???HTTPS 采⽤的是对称加密和⾮对称加密结合的「混合加密」⽅式:在通信建⽴前采⽤⾮对称加密的⽅式交换「会话秘钥」,后续就不再使⽤⾮对称加密; 在通信过程中全部使⽤对称加密的「会话秘钥」的⽅式加密明⽂数据。
      采⽤「混合加密」的⽅式的原因:对称加密只使⽤⼀个密钥,运算速度快,密钥必须保密,⽆法做到安全的密钥交换;⾮对称加密使⽤两个密钥:公钥和私钥,公钥可以任意分发⽽私钥保密,解决了密钥交换问题但速度慢。
    2. 摘要算法:摘要算法能够为数据⽣成独⼀⽆⼆的「指纹」,⽤于校验数据的完整性,解决了篡改的⻛险。
      客户端在发送明⽂之前会通过摘要算法算出明⽂的「指纹」,发送的时候把「指纹 + 明⽂」⼀同加密成密⽂后,发送给服务器,服务器解密后,⽤相同的摘要算法算出发送过来的明⽂,通过⽐较客户端携带的「指纹」和当前算出的「指纹」做⽐较,若「指纹」相同,说明数据是完整的。
    3. 数字证书:客户端先向服务器端索要公钥,然后⽤公钥加密信息,服务器收到密⽂后,⽤⾃⼰的私钥解密。这就存在些问题,如何保证公钥不被篡改和信任度?借助第三⽅权威机构 CA(数字证书认证机构),将服务器公钥放在数字证书(由数字证书认证机构颁发)中,只要证书是可信的,公钥就是可信的。
  • ???HTTPS 是如何建⽴连接的?其间交互了什么?
    SSL/TLS 协议基本流程:客户端向服务器索要并验证服务器的公钥、双⽅协商⽣产「会话秘钥」、双⽅采⽤「会话秘钥」进⾏加密通信。前两步也就是 SSL/TLS 的建⽴过程,也就是握⼿阶段。
    SSL/TLS 的「握⼿阶段」涉及四次通信,可⻅下图:p32
  • “https和http相⽐,就是传输的内容多了对称加密,可以这么理解吗?”
    建⽴连接时候:https ⽐ http多了 TLS 的握⼿过程;
    传输内容的时候:https 会把数据进⾏加密,通常是对称加密数据;
  • “TLS 和 SSL 需要区分吗?”
    这两实际上是⼀个东⻄。SSL 是 “Secure Sockets Layer 的缩写,中⽂叫做「安全套接层」,SSL 标准化之后的名称改为 TLS(是 “Transport Layer Security” 的缩写),中⽂叫做 「传输层安全协议」;两者可以视作同⼀个东⻄的不同阶段。
  • ???“为啥 ssl 的握⼿是 4 次?”
    SSL/TLS 1.2 需要 4 握⼿,需要 2 个 RTT 的时延。把每个交互合在⼀起发送,就是 4 次握⼿:p40

2.1.5 HTTP/1.1、HTTP/2、HTTP/3 演变

  • 说说 HTTP/1.1 相⽐ HTTP/1.0 提⾼了什么性能?
    性能上的改进:1、使⽤ TCP ⻓连接的⽅式改善了 HTTP/1.0 短连接造成的性能开销。2、⽀持管道(pipeline)⽹络传输,只要第⼀个请求发出去了,不必等其回来,就可以发第⼆个请求出去,可以减少整体的响应时间。
    性能瓶颈:1、请求 / 响应头部(Header)未经压缩就发送,⾸部信息越多延迟越⼤。只能压缩 Body 的部分;2、发送冗⻓的⾸部。每次互相发送相同的⾸部造成的浪费较多;3、服务器是按请求的顺序响应的,如果服务器响应慢,会招致客户端⼀直请求不到数据,也就是队头阻塞;4、没有请求优先级控制;5、请求只能从客户端开始,服务器只能被动响应。
  • 对于上⾯的 HTTP/1.1 的性能瓶颈,HTTP/2 做了什么优化?
    HTTP/2 协议是基于 HTTPS 的,所以 HTTP/2 的安全性也是有保障的。HTTP/2 相⽐ HTTP/1.1 性能上的改进:
    1. 头部压缩:HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是⼀样的或是相似的,那么,协议会帮你消除重
      复的部分。这就是所谓的 HPACK 算法:在客户端和服务器同时维护⼀张头信息表,所有字段都会存⼊这个表,⽣成⼀个索引号,以后就不发送同样字段了,只发送索引号,这样就提⾼速度了。
    2. ⼆进制格式:HTTP/2 不再像 HTTP/1.1 ⾥的纯⽂本形式的报⽂,⽽是全⾯采⽤了⼆进制格式,头信息和数据体都是⼆进制,并且统称为帧(frame):头信息帧和数据帧。计算机收到报⽂后,⽆需再将明⽂的报⽂转成⼆进制,⽽是直接解析⼆进制报⽂,这增加了数据传输的效率。
    3. 数据流:HTTP/2 的数据包不是按顺序发送的,同⼀个连接⾥⾯连续的数据包,可能属于不同的回应。因此,必须要对数据包做标记,指出它属于哪个回应。每个请求或回应的所有数据包,称为⼀个数据流(Stream)。每个数据流都标记着⼀个独⼀⽆⼆的编号,其中规定客户端发出的数据流编号为奇数,服务器发出的数据流编号为偶数。
      客户端还可以指定数据流的优先级。优先级⾼的请求,服务器就先响应该请求。
    4. 多路复⽤:HTTP/2 是可以在⼀个连接中并发多个请求或回应,⽽不⽤按照顺序⼀⼀对应。
      移除了 HTTP/1.1 中的串⾏请求,不需要排队等待,也就不会再出现队头阻塞问题,降低延迟,⼤幅度提⾼连接的利⽤率。
    5. 服务器推送:HTTP/2 ⼀定程度上改善了传统的「请求 - 应答」⼯作模式,服务不再被动响应,也可主动向客户端发送消息。
  • HTTP/2 有哪些缺陷?HTTP/3 做了哪些优化?
    HTTP/2 主要的问题在于,多个 HTTP 请求在复⽤⼀个 TCP 连接,下层的 TCP 协议是不知道有多少个 HTTP 请求的。⼀旦发⽣了丢包,就会触发 TCP 的重传机制,在⼀个 TCP 连接中的所有的 HTTP 请求都必须等待这个丢了的包被重传回来。
    • HTTP/1.1 中的管道( pipeline)传输中如果有⼀个请求阻塞了,那么队列后请求也统统被阻塞住了
    • HTTP/2 多个请求复⽤⼀个TCP连接,⼀旦发⽣丢包,就会阻塞住所有的 HTTP 请求。
      这都是基于 TCP 传输层的问题,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP!(⼤家都知道 UDP 是不可靠传输的,但基于 UDP 的 QUIC 协议 可以实现类似 TCP 的可靠性传输)

2.2 HTTP/1.1如何优化?

使⽤ KeepAlive 将 HTTP/1.1 从短连接改成⻓链接。这个确实是⼀个优化的⼿段,它是从底层的传输层这⼀⽅向⼊⼿的,通过减少 TCP 连接建⽴和断开的次数,来减少了⽹络传输的延迟,从⽽提⾼ HTTP/1.1 协议的传输效率。
但其实还可以从其他⽅向来优化 HTTP/1.1 协议,⽐如有如下 3 种优化思路:

  1. 尽量避免发送 HTTP 请求;
    对于⼀些具有重复性的 HTTP 请求,⽐如每次请求得到的数据都⼀样的,我们可以把这对「请求-响应」的数据都缓存在本地,那么下次就直接读取本地的数据,不必在通过⽹络获取服务器的响应了,这样的话 HTTP/1.1的性能肯定⾁眼可⻅的提升。
    所以,避免发送 HTTP 请求的⽅法就是通过缓存技术,HTTP 设计者早在之前就考虑到了这点,因此 HTTP 协议的头部有不少是针对缓存的字段。
    那缓存是如何做到的呢?客户端会把第⼀次请求以及响应的数据保存在本地磁盘上,其中将请求的 URL 作为 key,⽽响应作为 value,两者形成映射关系。这样当后续发起相同的请求时,就可以先在本地磁盘上通过 key 查到对应的 value,也就是响应,如果找到了,就直接从本地读取该响应。毋庸置疑,读取本次磁盘的速度肯定⽐⽹络请求快得多,如下图:
    缓存的响应不是最新的?服务器在发送 HTTP 响应时,会估算⼀个过期的时间,并把这个信息放到响应头部中,这样客户端在查看响应头部的信息时,⼀旦发现缓存的响应是过期的,则就会重新发送⽹络请求。
    如果客户端从第⼀次请求得到的响应头部中发现该响应过期了,客户端重新发送请求,假设服务器上的资源并没有变更,还是⽼样⼦,那么就不用在服务器的响应带上这个资源。只需要客户端在重新发送请求时,在请求的 Etag 头部带上第⼀次请求的响应头部中的摘要,这个摘要是唯⼀标识响应的资源,当服务器收到请求后,会将本地资源的摘要与请求中的摘要做个⽐较。
    如果不同,那么说明客户端的缓存已经没有价值,服务器在响应中带上最新的资源。如果相同,说明客户端的缓存还是可以继续使⽤的,那么服务器仅返回不含有包体的 304 Not Modified 响应,告诉客户端仍然有效,这样就可以减少响应资源在⽹络中传输的延时。
  2. 在需要发送 HTTP 请求时,考虑如何减少请求次数;(p47)
    • 减少重定向请求次数:如果重定向请求越多,那么客户端就要多次发起 HTTP 请求;另外,服务端这⼀⽅往往不只有⼀台服务器,⽐如源服务器上⼀级是代理服务器,然后代理服务器才与客户端通信,这时客户端重定向就会导致客户端与代理服务器之间需要 2 次消息传递。
      如果重定向的⼯作交由代理服务器完成,就能减少 HTTP 请求次数了;⽽且当代理服务器知晓了重定向规则后,可以进⼀步减少消息传递次数
    • 合并请求:如果把多个访问⼩⽂件的请求合并成⼀个⼤的请求,虽然传输的总资源还是⼀样,但是减少请求,也就意味着减少了重复发送的 HTTP 头部。另外由于 HTTP/1.1 是请求响应模型,如果第⼀个发送的请求,未收到对应的响应,那么后续的请求就不会发送,于是为了防⽌单个请求的阻塞,所以⼀般浏览器会同时发起 5-6 个请求,每⼀个请求都是不同的 TCP 连接,那么如果合并了请求,也就会减少 TCP 连接的数量,因⽽省去了 TCP 握⼿和慢启动过程耗费的时间。
      即合并请求的⽅式就是合并资源,以⼀个⼤资源的请求替换多个⼩资源的请求。但是这样会带来新的问题,当⼤资源中的某⼀个⼩资源发⽣变化后,客户端必须重新下载整个完整的⼤资源⽂件,这显然带来了额外的⽹络消耗。
    • 延迟发送请求:不要⼀⼝⽓吃成⼤胖⼦,⼀般 HTML ⾥会含有很多 HTTP 的 URL,当前不需要的资源,我们没必要也获取过来,于是可以通过「按需获取」的⽅式,来减少第⼀时间的 HTTP 请求次数。请求⽹⻚的时候,没必要把全部资源都获取到,⽽是只获取当前⽤户所看到的⻚⾯资源,当⽤户向下滑动⻚⾯的时候,再向服务器获取接下来的资源,这样就达到了延迟发送请求的效果。
  3. 减少服务器的 HTTP 响应的数据⼤⼩;
    对响应的资源进⾏压缩,这样就可以减少响应的数据⼤⼩,从⽽提⾼⽹络传输的效率。压缩的⽅式⼀般分为 2 种,分别是:
    • ⽆损压缩:指资源经过压缩后,信息不被破坏,还能完全恢复到压缩前的原样,适合⽤在txt、exe、程序源代码。
      gzip 就是⽐较常⻅的⽆损压缩。客户端⽀持的压缩算法,会在 HTTP 请求中通过头部中的 Accept-Encoding: gzip, deflate, br 字段告诉服务器;服务器收到后,会从中选择⼀个服务器⽀持的或者合适的压缩算法,然后使⽤此压缩算法对响应资源进⾏压缩,最后通过响应头部中的 content-encoding: gzip 字段告诉客户端该资源使⽤的压缩算法。
    • 有损压缩:将次要的数据舍弃,牺牲⼀些质量来减少数据量、提⾼压缩⽐,解压的数据会与原始数据不同但是⾮常接近,经常⽤于压缩多媒体数据⽐如⾳频、视频、图⽚。
      可以通过 HTTP 请求头部中的 Accept: audio/*; q=0.2, audio/basic,告诉服务器期望的资源质量。
      相同图⽚质量下,WebP 格式的图⽚⼤⼩都⽐ Png 格式的图⽚⼩,网站可以考虑使⽤ WebP 格式的图⽚。
      ⾳视频主要是动态的,每个帧都有时序的关系,通常时间连续的帧之间的变化很⼩。只需在⼀个静态的关键帧,使⽤增量数据来表达后续的帧,这样便减少了很多数据。视频常⻅的编码格式有 H264、H265 等,⾳频常⻅的编码格式有 AAC、AC3。

2.3 HTTPS RSA 握⼿解析(p55)

  • ???TLS 的握⼿过程
    有了 TLS 协议,能保证 HTTP 通信是安全的了,那么在进⾏ HTTP 通信前,需要先进⾏ TLS 握⼿。,如下图

2.4 HTTPS ECDHE 握⼿解析(p?)

  • ???

2.5 HTTPS 如何优化?

由裸数据传输的 HTTP 协议转成加密数据传输的 HTTPS 协议,给应⽤数据套了个「保护伞」,提⾼安全性的同时也带来了性能消耗。HTTPS 相⽐ HTTP 协议多⼀个 TLS 协议握⼿过程,⽬的是为了通过⾮对称加密握⼿协商或者交换出对称加密密钥,这个过程最⻓可以花费掉 2 RTT,接着后续传输的应⽤数据都得使⽤对称加密密钥来加密/解密。
⾄今⼤部分⽹址都已从 HTTP 迁移⾄ HTTPS 协议,因此针对HTTPS 的优化是⾮常重要的。
。。。


TCP

3.1 TCP 三次握⼿与四次挥⼿(小林p122)

  • TCP头格式
    1. 序列号:在建⽴连接时由计算机⽣成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送⼀次数据,就「累加」⼀次该「数据字节数」的⼤⼩。⽤来解决⽹络包乱序问题。
    2. 确认应答号:指下⼀次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。⽤来解决不丢包的问题。
    3. 控制位:
      ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建⽴连接时的 SYN 包之外该位必须设置为 1 。
      RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
      SYN:该位为 1 时,表示希望建⽴连接,并在其「序列号」的字段进⾏序列号初始值的设定。
      FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双⽅的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。
  • 为什么需要 TCP 协议? TCP ⼯作在哪⼀层?
    IP 层是「不可靠」的,它不保证⽹络包的交付、不保证⽹络包的按序交付、也不保证⽹络包中的数据的完整性。
    如果需要保障⽹络数据包的可靠性,那么就需要由上层(传输层)的 TCP 协议来负责。TCP 是⼀个⼯作在传输层的可靠数据传输的服务,它能确保接收端接收的⽹络包是⽆损坏、⽆间隔、⾮冗余和按序的。
  • 什么是 TCP ?
    TCP 是⾯向连接的、可靠的、基于字节流的传输层通信协议。
    ⾯向连接:⼀定是「⼀对⼀」才能连接,不能像 UDP 协议可以⼀个主机同时向多个主机发送消息,也就是⼀对多是⽆法做到的;
    可靠的:⽆论的⽹络链路中出现了怎样的链路变化,TCP 都可以保证⼀个报⽂⼀定能够到达接收端;
    字节流:消息是「没有边界」的,所以⽆论我们消息有多⼤都可以进⾏传输。并且消息是「有序的」,当「前⼀个」消息没有收到的时候,即使它先收到了后⾯的字节,那么也不能扔给应⽤层去处理,同时对重复的报⽂会⾃动丢弃
  • 什么是 TCP 连接?
    简单来说就是,⽤于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket、序列号和窗⼝⼤⼩称为连接。
    建⽴⼀个 TCP 连接是需要客户端与服务器端达成上述三个信息的共识。
    Socket:由 IP 地址和端⼝号组成;序列号:⽤来解决乱序问题等;窗⼝⼤⼩:⽤来做流量控制
  • 如何唯⼀确定⼀个 TCP 连接呢?
    TCP 四元组可以唯⼀的确定⼀个连接,四元组包括:源地址、源端⼝、⽬的地址、⽬的端⼝
    源地址和⽬的地址的字段(32位)是在 IP 头部中,作⽤是通过 IP 协议发送报⽂给对⽅主机。
    源端⼝和⽬的端⼝的字段(16位)是在 TCP 头部中,作⽤是告诉 TCP 协议应该把报⽂发给哪个进程。
  • 有⼀个 IP 的服务器监听了⼀个端⼝,它的 TCP 的最⼤连接数是多少?
    服务器通常固定在某个本地端⼝上监听,等待客户端的连接请求。因此,客户端 IP 和 端⼝是可变的,其理论值计算公式如下
    最大 TCP 连接数 = 客户端的 IP 数 * 客户端的端口数
  • UDP 协议?
    UDP 不提供复杂的控制机制,利⽤ IP 提供⾯向「⽆连接」的通信服务。UDP 协议非常简单,头部只有 8 个字节( 64 位),
    ⽬标和源端⼝:主要是告诉 UDP 协议应该把报⽂发给哪个进程。包⻓度:该字段保存了 UDP ⾸部的⻓度跟数据的⻓度之和。校验和:校验和是为了提供可靠的 UDP ⾸部和数据⽽设计。
  • UDP 和 TCP 有什么区别呢?
    1. 连接:TCP 是⾯向连接的传输层协议,传输数据前先要建⽴连接。UDP 是不需要连接,即刻传输数据。
    2. 服务对象:TCP 是⼀对⼀的两点服务,即⼀条连接只有两个端点。UDP ⽀持⼀对⼀、⼀对多、多对多的交互通信
    3. 可靠性:TCP是可靠交付数据的,数据可以⽆差错、不丢失、不重复、按需到达。UDP尽最⼤努⼒,不保证可靠交付数据。
    4. 拥塞控制、流量控制:TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。UDP 则没有,即使⽹络⾮常拥堵了,也不会影响 UDP 的发送速率。
    5. ⾸部开销:TCP ⾸部⻓度较⻓,会有⼀定的开销,⾸部在没有使⽤「选项」字段时是 20 个字节,如果使⽤了「选项」字段则会变⻓的。UDP ⾸部只有 8 个字节,并且是固定不变的,开销较⼩。
    6. 传输⽅式:TCP 是流式传输,没有边界,但保证顺序和可靠。UDP 是⼀个包⼀个包的发送,是有边界的,但可能丢包和乱序。
    7. 分⽚不同:
      • TCP 的数据⼤⼩如果⼤于 MSS ⼤⼩,则会在传输层进⾏分⽚,⽬标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了⼀个分⽚,只需要传输丢失的这个分⽚。
      • UDP 的数据⼤⼩如果⼤于 MTU ⼤⼩,则会在 IP 层进⾏分⽚,⽬标主机收到后,在 IP 层组装完数据,接着再传给传输层,但是如果中途丢了⼀个分⽚,在实现可靠传输的 UDP 时则就需要重传所有的数据包,这样传输效率⾮常差,所以通常 UDP 的报⽂应该⼩于 MTU。
  • TCP 和 UDP 应⽤场景:
    由于 TCP 是⾯向连接,能保证数据的可靠性交付,因此经常⽤于:FTP ⽂件传输,HTTP / HTTPS
    由于 UDP ⾯向⽆连接,它可以随时发送数据,再加上UDP本身的处理既简单⼜⾼效,因此经常⽤于:包总量较少的通信,如 DNS、 SNMP 等,视频、⾳频等多媒体通信,⼴播通信
  • 为什么 UDP 头部没有「⾸部⻓度」字段,⽽ TCP 头部有「⾸部⻓度」字段呢?
    原因是 TCP 有可变⻓的「选项」字段,⽽ UDP 头部⻓度则是不会变化的,⽆需多⼀个字段去记录 UDP 的⾸部⻓度。
  • 为什么 UDP 头部有「包⻓度」字段,⽽ TCP 头部则没有「包⻓度」字段呢?
    先说说 TCP 是如何计算负载数据⻓度:???

TCP 建立(p131)

  • TCP 三次握⼿过程和状态变迁
    TCP 是⾯向连接的协议,所以使⽤ TCP 前必须先建⽴连接,⽽建⽴连接是通过三次握⼿来进⾏的。
    1. ⼀开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端⼝,处于 LISTEN 状态
    2. 客户端会随机初始化序号(client_isn),将此序号置于 TCP ⾸部的「序号」字段中,同时把 SYN 标志位置为 1,表示 SYN 报⽂。接着把第⼀个 SYN 报⽂发送给服务端,表示向服务端发起连接,不包含应⽤层数据,之后客户端处于 SYN-SENT 状态。
    3. 服务端收到客户端的 SYN 报⽂后,⾸先服务端也随机初始化⾃⼰的序号(server_isn),将此序号填⼊TCP ⾸部的「序号」字段中,其次把 TCP ⾸部的「确认应答号」字段填⼊ client_isn + 1 , 接着把 SYN和 ACK 标志位置为 1 。最后把该报⽂发给客户端,该报⽂也不包含应⽤层数据,之后服务端处于 SYN-RCVD 状态。
    4. 客户端收到服务端报⽂后,还要向服务端回应最后⼀个应答报⽂,⾸先该应答报⽂ TCP ⾸部 ACK 标志位置为 1 ,其次「确认应答号」字段填⼊ server_isn + 1 ,最后把报⽂发送给服务端,这次报⽂可以携带客户到服务器的数据,之后客户端处于 ESTABLISHED 状态。(可以发现第三次握⼿是可以携带数据的,前两次握⼿是不可以携带数据的,这也是⾯试常问的题。
    5. 服务器收到客户端的应答报⽂后,也进⼊ ESTABLISHED 状态。⼀旦完成三次握⼿,双⽅都处于 ESTABLISHED 状态,此时连接就已建⽴完成,客户端和服务端就可以相互发送数据了。

3.2 TCP 重传、滑动窗口、流量控制、拥塞控制(小林p?)

-

3.3 TCP 实战抓包分析(小林p?)

-

3.4 TCP 半链接队列和全连接队列(小林p?)

-

3.5 TCP 内核参数(小林p?)

-


四、IP

4.1 IP基础知识

4.1.1 IP 基本认识(p314

  • ⽹络层的主要作⽤是:实现主机与主机之间的通信,也叫点对点(end to end)通信。
    IP 的作用是在复杂的网络环境中将数据包发送给最终目的主机。

  • ⽹络层与数据链路层有什么关系呢?
    IP 的作⽤是主机之间通信⽤的,⽽ MAC 的作⽤则是实现「直连」的两个设备之间通信,⽽ IP 则负责在「没有直连」的两个⽹络之间进⾏通信传输。

    如果⼩林只有⾏程表⽽没有⻋票,就⽆法搭乘交通⼯具到达⽬的地。相反,如果除了⻋票⽽没有⾏程表,恐怕也很难到达⽬的地。因为⼩林不知道该坐什么⻋,也不知道该在哪⾥换乘。因此,只有两者兼备,既有某个区间的⻋票⼜有整个旅⾏的⾏程表,才能保证到达⽬的地。与此类似,计算机⽹络中也需要「数据链路层」和「⽹络层」这个分层才能实现向最终⽬标地址的通信。 还有重要⼀点,旅⾏途中我们虽然不断变化了交通⼯具,但是旅⾏⾏程的起始地址和⽬的地址始终都没变。其实,在⽹络中数据包传输中也是如此,源IP地址和⽬标IP地址在传输过程中是不会变化的,只有源 MAC 地址和⽬标 MAC ⼀直在变化。

4.1.2 IP 地址的基础知识

  • 在 TCP/IP ⽹络通信时,为了保证能正常通信,每个设备都需要配置正确的 IP 地址,否则⽆法实现正常的通信。
    IP 地址(IPv4 地址)由 32 位正整数来表示,IP 地址在计算机是以⼆进制的⽅式处理的。⽽⼈类为了⽅便记忆采⽤了点分⼗进制的标记⽅式,也就是将 32 位 IP 地址以每 8 位为组,共分为 4 组,每组以「 . 」隔开,再将每组转换成⼗进制。

    那么,IP 地址最⼤值是 2^32,也即最⼤允许 43 亿台计算机连接到⽹络。 实际上,IP 地址是以⽹卡数来配置的。像服务器、路由器等设备都是有 2 个以上的⽹卡,是会有 2 个以上的 IP 地址;更何况 IP 地址是由「⽹络标识」和「主机标识」这两个部分组成的,所以实际能够连接到⽹络的计算机个数更是少了很多。 根据⼀种可以更换 IP 地址的技术 NAT ,使得可连接计算机数超过 43 亿台。
  • IP 地址的分类
    互联⽹诞⽣之初,IP 地址显得很充裕,于是科学家们设计了 5 种分类地址,分别是 A 类、B 类、C 类、D 类、E 类。
    下图中⻩⾊部分为分类号,⽤以区分 IP 地址类别

  • 什么是 A、B、C 类地址?
    ??p318

4.1.3 IP 协议相关技术(p340

  • DNS 域名解析
    域名⽅便⼈类记忆,使用 DNS 域名解析将域名⽹址⾃动转换为具体的 IP 地址。

    • 域名的层级关系
      DNS 中的域名都是⽤句点来分隔的,⽐如 www.server.com ,这⾥的句点代表了不同层次之间的界限。
      在域名中,越靠右的位置表示其层级越⾼,层级关系类似⼀个树状结构:根 DNS 服务器、顶级域 DNS 服务器(com)、权威 DNS 服务器(server.com) 根域的 DNS 服务器信息保存在互联⽹中所有的 DNS 服务器中。这样⼀来,任何 DNS 服务器就都可以找到并访问根域 DNS 服务器了。因此,客户端只要能够找到任意⼀台 DNS 服务器,就可以通过它找到根域 DNS 服务器,然后再⼀路顺藤摸⽠找到位于下层的某台⽬标 DNS 服务器
    • 域名解析的⼯作流程
      1. 客户端⾸先会发出⼀个 DNS 请求,问 www.server.com 的 IP 是啥,并发给本地 DNS 服务器(也就是客户端 的 TCP/IP 设置中填写的 DNS 服务器地址)。
      2. 本地域名服务器收到客户端的请求后,如果缓存⾥的表格能找到 www.server.com, 则它直接返回 IP 地址。如果没有,本地 DNS 会去问它的根域名服务器:“⽼⼤, 能告诉我 www.server.com 的 IP 地址吗?” 根域名服务器是最⾼层次的,它不直接⽤于域名解析,但能指明⼀条道路。
      3. 根 DNS 收到来⾃本地 DNS 的请求后,发现后置是 .com,说:“www.server.com 这个域名归 .com 区域管 理”,我给你 .com 顶级域名服务器地址给你,你去问问它吧。”
      4. 本地 DNS 收到顶级域名服务器的地址后,发起请求问“⽼⼆, 你能告诉我 www.server.com 的 IP 地址吗?”
      5. 顶级域名服务器说:“我给你负责 www.server.com 区域的权威 DNS 服务器的地址,你去问它应该能问到”。
      6. 本地 DNS 于是转向问权威 DNS 服务器:“⽼三,www.server.com 对应的IP是啥呀?” server.com 的权威 DNS 服务器,它是域名解析结果的原出处。为啥叫权威呢?就是我的域名我做主。
      7. 权威 DNS 服务器查询后将对应的 IP 地址 X.X.X.X 告诉本地 DNS。
      8. 本地 DNS 再将 IP 地址返回客户端,客户端和⽬标建⽴连接。
  • ARP 与 RARP 协议
    ??p342

  • DHCP 动态获取 IP 地址

。。

  • NAT ⽹络地址转换

。。

  • ICMP 互联⽹控制报⽂协议

。。

  • IGMP 因特⽹组管理协

。。

4.2 ping 的工作原理

-


五、网络综合篇

5.1 键入网址到网页显示,期间发生了什么?

5.1.1 浏览器做的第⼀步⼯作是解析 URL

  • ⾸先浏览器做的第⼀步⼯作就是要对 URL 进⾏解析,从⽽⽣成发送给 Web 服务器的请求信息。 所以图中的⻓⻓的 URL 实际上是请求服务器⾥的⽂件资源。当没有文件路径名时,就代表访问根⽬录下事先设置的默认⽂件,也就是 /index.html 或者 /default.html 这些⽂件。
  • 对 URL 进⾏解析之后,浏览器确定了 Web 服务器和⽂件名,接下来就是根据这些信息来⽣成 HTTP 请求消息了。

5.1.2 真实地址查询 —— DNS

通过浏览器解析 URL 并⽣成 HTTP 消息后,需要委托操作系统将消息发送给 Web 服务器。
但在发送之前,还有⼀项⼯作需要完成,那就是查询服务器域名对应的 IP 地址,因为委托操作系统发送消息时,必须提供通信对象的 IP 地址。所以,有⼀种服务器就专⻔保存了 Web 服务器域名与 IP 的对应关系,它就是 DNS 服务器。
使用 DNS 域名解析将域名⽹址⾃动转换为具体的 IP 地址。

5.1.3 指南好帮⼿ —— 协议栈(p379

???

5.1.4 可靠传输 —— TCP

-

5.1.5 远程定位 —— IP

TCP 模块在执⾏连接、收发、断开等各阶段操作时,都需要委托 IP 模块将数据封装成⽹络包发送给通信对象

5.1.6 两点传输 —— MAC

⽣成了 IP 头部之后,接下来⽹络包还需要在 IP 头部的前⾯加上 MAC 头部

5.1.7. 出⼝ —— ⽹卡

⽹络包只是存放在内存中的⼀串⼆进制数字信息,没有办法直接发送给对⽅。因此,我们需要将数字信息转换为电
信号,才能在⽹线上传输,也就是说,这才是真正的数据发送过程

5.1.8 送别者 —— 交换机

下⾯来看⼀下包是如何通过交换机的。交换机的设计是将⽹络包原样转发到⽬的地。交换机⼯作在 MAC 层,也称
为⼆层⽹络设备

5.1.9 出境⼤⻔ —— 路由器

。。

5.1.10 互相扒⽪ —— 服务器 与 客户端

5.2 Linux如何收发网络包?

电脑与电脑之间通常都是通话⽹卡、交换机、路由器等⽹络设备连接到⼀起,那由于⽹络设备的异构性,国际标准化组织定义了⼀个七层的 OSI ⽹络模型,但是这个模型由于⽐较复杂,实际应⽤中并没有采⽤,⽽是采⽤了更为简化的 TCP/IP 模型,Linux ⽹络协议栈就是按照了该模型来实现的。
TCP/IP 模型主要分为应⽤层、传输层、⽹络层、⽹络接⼝层四层,每⼀层负责的职责都不同,这也是 Linux ⽹络协议栈主要构成部分。
当应⽤程序通过 Socket 接⼝发送数据包,数据包会被⽹络协议栈从上到下进⾏逐层处理后,才会被送到⽹卡队列中,随后由⽹卡将⽹络包发送出去。⽽在接收⽹络包时,同样也要先经过⽹络协议栈从下到上的逐层处理,最后才会被送到应⽤程序。


六、学习心得

操作系统和计算机⽹络有多重要呢?如果没有操作系统,我们的⼿机和电脑可以说是废铁了,如果没有计算机⽹络,我们的⼿机和电脑就是⼀座「孤岛」了,孤岛的世界很单调,也没有什么⾊彩,也正是因为计算机⽹络,才创造出这么丰富多彩的互联⽹世界。

身为程序员的我们,那更应该深刻理解和掌握它们,虽然我们⽇常 CURD 的⼯作中,即使不熟悉它们,也不妨碍我们写代码,但是当出现问题时,没有这些基础知识,你是⽆厘头的,根本没有思路下⼿,这时候和别⼈差距就显现出来了,可以说是程序员之间的分⽔岭。事实上,我们⼯作中会有大量的时间都是在排查和解决问题,编码的时间其实⽐较少,如果计算机基础学的很扎实,虽然不敢保证我们能 100% 解决,但是⾄少遇到问题时,我们有⼀个排查的⽅向,或者直接就定位到问题所在,然后再⼀步⼀步尝试解决,解决了问题,⾃然就体现了我们⾃身的实⼒和价值,职场也会越⾛越远。

当然,计算机⽹络最⽜逼的资料,那必定 RFC ⽂档,它可以称为计算机⽹络世界的「法规」,也是最新、最权威和最正确的地⽅了,困惑⼤家的 TCP 为什么三次握⼿和四次挥⼿,其实在 RFC ⽂档⼏句话就说明⽩了。
TCP 协议的 RFC ⽂档:https://datatracker.ietf.org/doc/rfc1644

实战系列:在学习书籍资料的时候,不管是 TCP、UDP、ICMP、DNS、HTTP、HTTPS 等协议,最好都可以亲⼿尝试抓数据
报,接着可以⽤ Wireshark ⼯具看每⼀个数据报⽂的信息,这样你会觉得计算机⽹络没有想象中那么抽象了,因为它们被你「抓」出来了,并毫⽆保留地显现在你⾯前了,于是你就可以肆⽆忌惮地「扒开」它们,看清它们每⼀个头信息。

SpringBoot

Spring Boot 是一个用于简化 Spring 应用程序开发的框架,它提供了大量的注解来简化配置和开发。

SpringFramework没有解决了什么问题?

为什么有了SpringFramework还会诞生SpringBoot?简单而言,因为虽然Spring的组件代码是轻量级的,但它的配置却是重量级的。
一开始,Spring用XML配置,而且是很多XML配置。Spring 2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式XML配置。Spring 3.0引入了基于Java的配置,这是一种类型安全的可重构配置方式,可以代替XML。
所有这些配置都代表了开发时的损耗。因为在思考Spring特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。和所有框架一样,Spring实用,但与此同时它要求的回报也不少。
除此之外,项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度。
1.jsp中要写很多代码、控制器过于灵活,缺少一个公用控制器 2.Spring不支持分布式,这也是EJB仍然在用的原因之一。

SringBoot的概述

  • SpringBoot解决上述Spring的缺点
    SpringBoot对上述Spring的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。
  • SpringBoot的特点
    1、为基于Spring的开发提供更快的入门体验
    2、开箱即用,没有代码生成,也无需XML配置。同时也可以修改默认值来满足特定的需求
    3、提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等
    SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式
  • SpringBoot的核心功能
    • 起步依赖:起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。
    • 自动配置:Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是Spring自动完成的
  • 为什么添加一个starter-web模块,一个简单的web程序便完成了呢?
    SpringBoot最强大的地方在于约定大于配置,只要你引入某个模块的xx-start包,它将自动注入配置,提供了这个模块的功能;
    比如这里我们在POM中添加了如下的包
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    它内嵌了Tomcat并且提供了默认的配置,比如默认端口是8080;可以在application.properties或者application.yml中修改配置。

SpringBoot 注解

  1. @SpringBootApplication:该注解用于标记主应用程序类,通常位于项目的顶层包中。定义在main方法入口类处,用于启动sping boot应用项目。
    它包含了 @Configuration@EnableAutoConfiguration@ComponentScan 注解,用于自动配置和组件扫描。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Configuration
    @EnableAutoConfiguration
    @ComponentScan
    public @interface SpringBootApplication {

    /**
    * Exclude specific auto-configuration classes such that they will never be applied.
    * @return the classes to exclude
    */
    Class<?>[] exclude() default {};

    }
    @EnableAutoConfiguration:让spring boot根据类路径中的jar包依赖当前项目进行自动配置,在src/main/resources的META-INF/spring.factories
  2. @ImportResource:加载xml配置,一般是放在启动main类上
  3. @ConfigurationProperties(prefix="person"):可以新建一个properties文件,ConfigurationProperties的属性prefix指定properties的配置的前缀,通过location指定properties文件的位置
  4. @PostConstruct:spring容器初始化时,要执行该方法
  5. @ComponentScan:注解会告知Spring扫描指定的包来初始化Spring
  6. @RestController:用于标记一个类,表示它是一个控制器类,处理 HTTP 请求并返回 JSON 或 XML 等数据。
    @GetMapping@PostMapping@PutMapping@DeleteMapping:这些注解分别用于将 HTTP GET、POST、PUT 和 DELETE 请求映射到相应的处理方法上。
    @RequestMapping:用于将 HTTP 请求映射到控制器方法上,指定请求的 URL 路径和请求方法(GET、POST、PUT、DELETE 等)
    1
    @RequestMapping(path = "/user/detail", method = RequestMethod.POST)
    @RequestParam:用于从请求参数中获取值,并将其传递给控制器方法的参数。
    1
    2
    3
    4
    5
    public List<CopperVO> getOpList(HttpServletRequest request,
    @RequestParam(value = "pageIndex", required = false) Integer pageIndex,
    @RequestParam(value = "pageSize", required = false) Integer pageSize) {

    }
  7. @PathVariable:用于从 URL 路径中获取值,并将其传递给控制器方法的参数。
  8. @RequestBody:用于将请求体中的数据绑定到控制器方法的参数上,通常用于接收 JSON 或 XML 数据。
    @ResponseBody: 表示方法返回的对象应该直接写入HTTP响应体,而不是通过视图解析器进行渲染。
  9. @Autowired 是 Spring 框架自带的注解用于进行依赖注入,将 Spring 托管的 bean 注入到需要它们的类中。
    @Resource是 Java EE 提供的注解,用于实现依赖注入。它可以用于字段、setter 方法、构造函数等地方,用于告诉容器注入指定名称或类型的 bean。
    @Bean: 用于定义Spring Bean,通常在 @Configuration 类中使用。
    @Component 是 Spring 框架中用于声明一个类为 Spring 管理的组件(Bean)的注解。
  10. @Service@Repository@Controller:这些注解分别用于标记服务类、仓库类和控制器类,以便 Spring Boot 可以自动扫描并创建这些组件。都属于 @Component 的衍生注解,用于更明确地表示类的职责。
  11. @Configuration:用于定义配置类,通常与 @Bean 注解一起使用,用于配置第三方库或复杂的 bean。
  12. @Override 是Java中的一个注解(Annotation),用于标识方法是否是一个覆盖(重写)了父类或接口中的方法。它提供了编译时的检查,确保您正确地重写了父类或接口中的方法,以防止由于拼写错误或方法签名不匹配而导致的错误。
  13. @Value:用于注入属性值,从配置文件中获取属性值。
  14. @Profile:用于定义不同环境下的配置文件,可以根据不同的配置文件加载不同的配置。
  15. @Conditional:用于根据条件决定是否创建某个 bean。
  16. @Param:给参数变量起别名。如果只有一个参数,并且在mapper对应的xml文件中里使用,则必须加别名。
  17. @Async 是 Spring 框架中用于实现异步方法调用的注解。通过在方法上添加 @Async 注解,Spring Boot 就会在调用该方法时使用另一个线程来执行,而不是阻塞当前线程等待方法执行完成。
  18. @Order:如 @Order(1),值越小优先级超高,越先运行