SE3353-应用系统体系架构
本文最后更新于 331 天前。

Overview

后端开发

  • Lec1:实例管理
  • Lec2-3:异步通信JMS、Kafka、WebSocket
  • Lec4-5:事务管理
  • Lec6:Java多线程
  • Lec7:分布式缓存Redis
  • Lec8:全文搜索
  • Lec9:Web服务
  • Lec10:微服务Eureka

最终效果:

数据库

  • Lec11-12:MySQL优化
  • Lec13:MySQL备份与恢复
  • Lec14:MySQL分区
  • Lec15:NoSQL-MongoDB
  • Lec16:NoSQL-Neo4j
  • Lec17:NoSQL-VectorDB & LogStructureDB
  • Lec18:NoSQL-TSDB InfluxDB
  • Lec19-20:云数据库GuassDB、数据湖

集群

  • Lec21:集群部署-Nginx
  • Lec22:容器化-Docker
  • Lec23:批数据并行处理-Hadoop
  • Lec24:批数据并行处理-Spark
  • Lec24:流数据并行处理-Storm
  • Lec25:分布式文件系统-HDFS
  • Lec26:分布式数据库-HBase
  • Lec27:分布式数据仓库-Hive
  • Lec27:分布式并行处理框架-Flink
  • oneAPI

人工智能

  • Lec21:神经网络
  • Lec22:卷积神经网络CNN
  • Lec23:自然语言处理
  • Lec24:循环神经网络RNN
  • Lec25:ChatGPT
  • Lec27:Transformer

后端开发

实例管理

有状态服务和无状态服务

有状态服务:需要区分用户的服务,甚至单个用户的每次服务都不一样。状态需要保存,过多会爆内存,或者换页导致大量IO,内存大小通过实例池大小控制
无状态服务:无需对用户进行区分的服务,可以启动无数个以应对请求;无状态的极致是函数式编程

实例作用域

Scope作用域解释
singleton单例整个 Spring 容器中只创建一个实例,全局共享
prototype原型每次调用时都会创建一个新实例,比如controller调用service会创建新的。
request每次HTTP请求都一个新实例,适合与请求相关的数据处理,比如请求参数校验等。
session每个 HTTP 会话周期内都会有独立的 Bean 实例,适合用户登录状态,在一个session内多个http只有一个实例。
applicationBean 的生命周期与整个 Web 应用的生命周期一致,适合存储全局配置、共享资源等。和singleton区别是用在web。
websocket每个 WebSocket 会话创建一个 Bean 实例,适合实时通信场景下与单个用户连接相关的数据或状态。

分别使用两个浏览器,每个发两次请求,controller和service采用不同的scope,会得到不同的实例数量

连接池大小:大核*2+小核+有效硬盘数(真硬盘,分区不算)

与用户数无关,唯一准则是减少线程切换及IO等待

例:

  • CPU:12th Gen Intel(R) Core(TM) i7-12700H 内核:14 逻辑处理器: 20
  • 磁盘 0 (C:)SKHynix_HFS512
  • 磁盘 1 (D: E:)WD_BLACK SN770 1TB

连接池大小:20+2=22

异步通信JMS、Kafka、WebSocket

同步通信:请求→执行→返回,负载大时速度慢,甚至会崩溃。缺点:紧耦合;不保证成功服务;前后端契约;没有请求缓冲;过于强调返回值;请求不可重做
异步通信:请求→存储→返回一个响应→执行→返回结果。缺点:各种消息组装编程难度大

消息中间件-Kafka的原理:包含不同topic,controller将请求存储在对应的topic,service监听topic

结果如何返回给客户端?
前端Ajax轮询、WebSocket、发送message到某个topic等待前端查看

松散耦合:订阅topic的service可以使用任意方法处理订单,而非严格调用。

什么情况下消息中间件可以扔掉消息(定时删除),什么时候不能扔(持久化):股票实时推送(不收到也没事);订单(不能缺失)

JMS API

是API,不是实现

特点

  • 异步(receiver无需在消息送达时就接收消息,可以在很久后才接收)
  • 可靠性:保证At-least-once以及At-most-once

两种模式

  • 点对点:消息放在一个queue内,只有一个人可以拿取消息
  • 广播-订阅:消息放到一个topic里,所有订阅者都可以拿取消息

Kafka

采用广播-订阅模式,订阅者在这里叫做消费者,系统间各个部件解耦

kafka采用流式数据存储,使用只追加的日志文件,每个消费者有一个自己的指针,用于标记读到了哪个消息

kafka也可以做集群加备份,消息通过哈希做分区

WebSocket

全双工通信协议:支持双向通信-服务器也可以主动发送消息

通信过程

  • 握手:使用ws开头的url,发送一个http消息,Web服务器解释其为HTTP连接升级请求
  • 数据传输

endPoint分为客户端和服务器端,服务器端等待与客户端连接;使用一个异步队列维护session;可以借助模板类编写自己class的编码器、解码器,可以自动转换成java对象。

前后端通信方式

  • HTTP:全同步,前端等待时无法进行操作
  • Ajax:前端异步,得到后端消息后执行回调,无需等待
  • 后端异步:后端在处理大量请求时,先将消息存储,然后排队处理
  • WebSocket:后端将结果主动发送给前端

四种方式选择:login显然不能做后端异步

事务管理

不使用事务的后果:两个人同时买书:卖重了?转账只扣钱没加钱?

使用事务管理

AICD属性

  • atomic原子性:All-or-Nothing
  • consistency一致性:事务在开始和结束时,数据库都必须符合所有预设的规则、约束和数据完整性条件。事务不能破坏数据库的规则或完整性约束。(例:假设有一个数据库规则,规定银行账户的余额不能为负数。一旦事务执行完成,数据库必须确保这个规则依旧成立,如果某个操作试图违反规则,事务将被回滚。)
  • isolation隔离性:不会出现数据冲突(两个人同时改动又同时写入)
  • durability持久性:事务除非被回滚,否则不会丢失

6种事务传播属性:

一个事务内的操作能够做到All-or-Nothing,出错就会被重置到之前的状态

  • required:需要在事务中执行,如果没有会创建,如果有会加入
  • requiresNew:无论之前有没有事务,都新建一个事务;如果之前有事务,则把之前的事务挂起,执行完恢复。新建事务和旧事务无关
  • NotSupported:如果执行时有事务,则会把之前的事务挂起,然后执行
  • Supports:可以不在事务中,有事务则加入
  • Mandatory:必须已有事务
  • Never:必须没有事务

脏读Dirty Read:读到了别的事务执行的中间状态
不可重复读:首先事务A读取,执行时事务B写入,然后事务A再次读取,数据不匹配(读取后加锁)
幻读:事务A在全表中做范围查找,做处理时B添加了数据,A再次读取时数据增多(读取后全表加锁)

4种事务隔离级别:

通过设置数据隔离级别,告诉数据库使用什么隔离级别。

如果涉及到多个数据库,比如从中行向光大转账,两个数据库不知道彼此存在,就需要单独的事务管理器(使用2PC等),但是无法避免网络错误等问题,需要人工处理。参照CSE:Multi-site transaction & Multi-site atomicity

我们也可以自己加锁,可以分为乐观锁(OCC/MVCC)和悲观锁,取决于程序员认为冲突发生的可能性高低,通过数据库版本号进行控制。

粗粒度锁:目标是同时锁住多个表,需要对整个表进行控制。两个表绑定同一个版本号即可

注意普通的java函数添加Transactional标签是没有意义的,标签时给数据库看的,对内存里的变量没有任何影响

实现事务管理

可串行化调度:参照CSE:实现Before-or-after(Serializability)

日志:

四种方法数据库恢复算法的及其优劣:内存占用、IO操作、数据恢复时间

undo日志在写数据库之前,记录数据库修改前的值(防止事务回滚时无法恢复)
redo日志在写数据库之后,记录数据库修改后的值(防止提交后未刷盘导致的数据丢失)

日志写入是顺序写,顺序读,不修改;相较数据库的随机写速度很快。

逻辑日志:记录高层的抽象操作,例如用户输入的SQL语句
物理日志:记录数据库具体的物理变化,例如第10个page,offset为81-89的值修改为1
物理逻辑日志:在第10个page,执行xxx语句

物理日志与逻辑日志对比:

日志类型解析速度日志量可重做性幂等性可逆性应用场景
物理日志否(offset可能因为其他修改而变化)redo日志
逻辑日志否(插入到一半崩溃,再插入索引已被占用)undo日志
物理逻辑日志较快undo日志

Java多线程

带synchronized关键字的函数,会对这个类上锁,这个类的其他函数不能被调用,不会出现竞争的关系。(方法同步)
也可以在函数内的某一个部分使用synchronized关键字,只会对这一部分加锁。(语句同步)
带volatile关键字的变量,可保证操作是原子性的。

可重入锁:一个线程需要重复获取一个锁多次,需要使用可重入锁。

Java中除了long和double,其他基础类型的读写都是原子性的

锁的问题:死锁(deadlock)、活锁(livelock)、饥饿(starvation)
Starvation:是指如果线程T1占用了资源R,线程T2又请求封锁R,于是T2等待。T3也请求资源R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后,系统又批准了T4的请求……,T2可能永远等待。

数据库中会使用自旋锁,线程切换的切换比等待的时间还长的时候使用

不可变对象(Immutable Object)
解决了同一个线程两条语句(哪怕两个语句都是synchronized)之间,数据被其他线程修改的问题。在变量前标记final privite,无法执行写操作

高级特性

  • Lock对象:比一般的锁功能更多,提供了对锁的精细控制,特别适用于需要复杂同步控制的场景
  • Executors线程池:提供了线程池管理的实现,可以创建固定大小的线程池、缓存线程池或单线程执行器。
  • 并发数据结构:如ConcurrentHashMap通过分段锁技术,实现高效的并发操作,减少了锁竞争。
  • Fork/Join框架:将问题划分成小的子问题,然后多线程执行
  • 原子变量:如 AtomicInteger,通过硬件支持的原子操作(例如 CAS 操作)来保证数据的安全修改。
  • 虚拟多线程:与平台线程相对应。一般的java线程数被cpu核数限制住,适合涉及大量IO操作的服务,用于在等待时间执行更多操作,而且不占用系统进程栈

分布式缓存Redis

Spring框架和数据库都有自己缓存,为什么我们还需要建立一块缓存?
——这两个缓存不受自己控制,可能不满足我们的需求;数据库可能不在同一个机器上;可以使用空间更大的分布式缓存

什么数据放缓存里?
——不经常改的,比如书的标题、简介;经常改的库存数据不要放缓存

如果所有修改都放在缓存中,隔一段时间刷盘可不可以?
——可以,但是要防止有其他应用绕过缓存读取数据库,必须全部被缓存拦截

缓存换页和数据库直接读取哪个快?
——缓存的是join后的数据,即使从磁盘读也可能更快;MySQL读取SQL语句的编译过程有消耗;一般缓存在主机上,或靠主机比较近

Memcached

数据以键值对形式存储,按照数据大小和名字进行分类便于快速找到数据;数据-节点的分配方式使用一致性hash(具体算法参照SE3331-Network-P2P Network-DHT分布式哈希表),方便增删节点。

Redis

Redis同样是一个内存中的键值对存储;与一般键值存储不同,redis除了字符串以外还支持更复杂的数据结构(string、list、set、hash等);且支持主从备份

Redis也可以用来实现类似于kafka的消息中间件

问题:

  • 雪崩:启动时Redis读入大量数据,但过期时间是统一的,一旦全部过期会对数据库瞬间产生大量请求。设定随机过期时间即可
  • 击穿:大量请求同时对一个不在Redis内的数据访问,全部访问数据库。读取上锁,在进入Redis前拦截
  • 穿透:大量请求同时访问一个数据库中不存在的值,全部访问数据库。在Redis中缓存Bloom Filter,治标不治本。

全文搜索

为什么我们需要反向索引
——假设我们想要搜索书评中的关键字,使用一般的方式或浪费时间(全表扫描)或浪费空间(记录每条书评是否包含某个关键词),所以需要使用关键词作为索引,指向在文本中出现的次数及位置(用内容定位行)

Lucene

支持的功能:

  • 关键词搜索
  • 相似度排序(向量)
  • 自定义索引(标题、正文等是否参与索引)
  • 增量式(可以增删文章及关键词)

覆盖(召回)率和准确率需要权衡

Solr

基于Lucene,接口封装更简单;支持分布式、主从备份、负载均衡

Elasticsearch

将几个表合并成一个宽表,通过反向索引搜索一行内的多个字段是否含有关键词,然后回到原表中按索引拿取数据。

Web服务

如何通过网络请求与其他服务交互

方法调用

SOAP简单对象访问协议——传递方法的调用,包括url、方法名及变量等,并支持不同网络协议。
暴露在外面的是一个.WSDL文件,记录了如何通过各类协议及语言进行调用,并可以自动封装及解析。
消息传递使用XML格式的信封体,可以使用java插件自动解析。
还可以做一个注册表,防止不知道服务在哪里

优点:无需改动代码,可以生成多种语言的接口
缺点:方法签名绑定,需要一起变动;需要在格式上保持一致,解析成本高;需要额外的WDSL文件;需要需要根据WDSL生成一个代理

数据驱动

假设我们要设计一个RESTful风格的Web服务来管理“书籍”资源,资源路径:/books

请求方法URI操作
GET/books获取所有书籍
GET/books/{id}获取某一本书籍(通过ID)
POST/books添加一本新书
PUT/books/{id}更新某一本书籍的信息
DELETE/books/{id}删除某一本书籍

实际上REST请求与实现完全无关,也就导致全用POST(没有做到纯数据驱动,方法仍然和业务逻辑绑定

Web服务的优缺点:

优点:跨平台;自定义(WSDL/REST);模块化;可以跨过防火墙(对特定端口控制通过性)

缺点:格式转换性能消耗(二进制转ASCII码);开发生产率低;安全性依赖于协议,且效率低

微服务Eureka

将后端系统拆分成多个互不相干的小组件,互相之间通过网络请求调用,虽然降低了性能(可以用Kafka的方式,先返回一个响应,然后再执行),但是提高了容灾能力,降低开发难度,方便维护,负载均衡,安全(可以只暴露Eureka,其他用内部IP)。

需要注册中心(Eureka)及gateway

具体的不写了(

无服务(函数式编程)(事件驱动),适用于一些简单的功能,还可以对多个函数进行组合。可以在调用时才启动,调用后回收。也可以相对智能地调度。

数据库

MySQL优化

索引优化

一般索引B树/B+树(一个节点的大小和page大小相近)。空间索引R树。哈希索引(内存表),反向索引(FULLTEXT)等

当表格scan开销比建立索引并维护还要快,就没有必要建索引。比如34个省
聚簇索引和数据在磁盘上存储的顺序一致,所以会读一次就是一整页,一个表只能有一个,默认主键上就是聚簇索引

如果建了多列索引,会浪费空间,不如设定一列自增索引

如果允许索引为空:浪费1bit;运行时一直做检查;树退化成链表

外键:数据中每列的访问频率有差异,比如价格和详情,然后就可以分表,通过外键关联;同样可以减少冗余数据

数据太长可以使用前N个字符建索引,并可以使用前缀压缩

MySQL利用索引搜索必须按照索引建立的顺序,比如先在第二列建索引,再在第一列建索引,那么如果搜索时限定条件只有第一列或先限定第一列再限定第二列,就无法使用索引

B树索引对Between和Like适配度高,但是Like不能以通配符开始

哈希索引对单值搜索性能好,不支持范围查找

优化数据大小及类型

数据段尽量设置成不为空,有额外的bit ;前缀压缩;索引尽量小,尽量高效;UUID在一些情况下性能比自增主键好(因为自增需要管理,自增也可以设置成不严格递增,就可以给多个管理者分配不同的数据段)

范式化:减少数据冗余(一般选3NF/BCNF)

  • 第一范式:数据库总每一列都是不可再分的,每个表的字段都是最基本,不可再分的数据单位
  • 第二范式:满足第一范式基础上,表中的非主属性的字段必须都完全依赖主键,不能部分依赖
  • 第三范式:在满足第二范式的基础上,表中非主属性的字段不存在传递依赖关系
  • BC范式:所有属性的字段不存在传递依赖关系

选择正确的数据类型(学号用int比string空间小)

8KB(一个page的一半)以下,使用varchar而不使用BLOB;以上使用BLOB

MySQL限制

数据库上限:无上限,只受限于操作系统目录限制
表的数量:无上限,只受限于操作系统目录限制
文件大小:无上限,只受限于操作系统目录限制

表的大小:1TB
列数:4096

列的大小(对于varchar,blob和text):65535Byte(64KB)(Blob和Text类型占用9和12byte,因为之存了指针;Varchar比实际指定的空间大一点;可以为空也需要额外的空间)
以InnoDB做引擎的表的每列最大大小是比page大小的一半略小(4KB、8KB等)
CHAR不可压缩,建表时超过page的一半就报错;VARCHAR可压缩,所以VARCHAR还会出现建表不报错但插入报错的情况,我们可以通过VARCHAR建一个大小接近64KB的表,但是插入数据到达page大小的一半时就会报错

表优化

当表体积变大,达到一个较大的大小时,可以重新构建(比如存储到一个page上减少空间浪费,减少磁盘碎片,重新构建更平衡的索引),很浪费时间,但是优化后效果更好

不用很长的唯一键,使用自增键

尽量用VARCHAR(可压缩,对于变长及可为空数据更好),不用CHAR(学号这种可以用char,是对齐的,而且可以用前缀压缩)

默认每条语句都在一个事务中执行,所以可以设置是否立刻提交;也可以设置多条语句使用一个事务,但是要避免大事务(回滚耗时长);缓存刷盘设置、选择性刷盘、大事务拆分;事务隔离级别设置;只读操作不上锁

在插入大量数据时,可以关闭唯一键检查(但是要人为保证),也可以设置唯一键不严格按顺序递增,这样mysql会为每个线程分配一块自增id;所以就不能通过最后一个id判断数据库的大小

索引创建的顺序,将最常用的放在第一个;根据查询需求建复合索引,注意索引顺序

磁盘IO

提高磁盘IO速度:

  • 增加缓存(刷盘策略+Redis)
  • 落盘策略:文件属性可以后落盘
  • 设定刷盘间隔
  • 不用机械硬盘,用SSD
  • 设置存储介质(表放SSD,日志放机械;一个擅长随机读写,一个删除顺序读写)
  • 换硬盘

主键类型和值不要修改

缓存设置

设置Buffer size时,默认Chunksize是128。指令需要指定Buffer size大小和实例数量,如果设8G,16个实例,实际buffer是8G;但如果改成9G,实际会分配10G,因为9G/16不是128M的整数倍。,实例最多是64个,Buffer size最少是1G

用内存表存储一些一般不会被修改的数据,默认是哈希表

预读:读取少的时候多读一些放在内存里

落盘线程数(小于实例数量),落盘阈值

暖启动:即使关机,也把部分缓存落盘,然后恢复缓存

MySQL备份

为什么要备份:容灾及升级迁移

物理备份:存储数据,可能遇到版本兼容性问题;适合大数据库
逻辑备份:存储SQL语句,可以在不同数据库中迁移;但速度慢

冷备份:停机备份;适合小数据库
暖备份:上写锁,可以读
热备份:会遇到数据一致性问题

全量备份:上面两种
增量备份:增量备份由MySQL自己实现,存储上一次全量备份至今的SQL语句,存储在多个bin-log文件里
快照备份:快照备份需要第三方工具,原理是存储上次全量备份后被修改的块

备份前必须刷盘

主从备份,恢复可以直接切换主从

四类问题及处理

  • 操作系统崩溃:undo/redo恢复
  • 断电:同上
  • 文件系统崩溃:只能从备份恢复
  • 磁盘坏了:同上。或者用显微镜手抄0/1(bushi

注意别把备份和数据库放一块盘上

多线程备份、文件压缩

恢复后如何比对
——哈希校验/默克尔树(原理也是哈希)

为什么bin-log文件不能并行读取
——因为按时间顺序,可能会出错

MySQL分区

将一张表分布在不同的服务器上,可以根据索引锁定到一个或几个服务器,分担服务器压力;可以在SQL语句里直接指定分区;还可以在分区内指定子分区

不支持垂直分区,只能水平分区

分区方法支持范围、离散值、指定列哈希、默认哈希等

使用范围分区时只能通过VALUES LESS THAN描述,注意最右表示成VALUES LESS THAN MAXVALUE,否则会有数据找不到分区(能保证数据有最大值也可以不建这个区);支持多列同时索引,但是最好要递增递减;范围只能建在各种整数类型及日期类型上,浮点数因为精度丢失不能作比较,TEXT\BLOB也不行

范围分区如果想要添加一个分区,只能在最后添加一个更大的值,不能尝试将前面的分区拆开。拆开分区有其他的操作。

对于范围分区:值为空的记录会放在第一个分区中而不是当作0
对于离散值分区:必须指定NULL的分区
对于哈希分区:NULL会当作0

为什么用一致性哈希/线性哈希:为了增删分区更方便

一致性哈希算法

  • 假设一共有9个分区
  • 先找出比9大的一个2的n次幂,值是16
  • 将value与16-1按位与
  • 如果得出的值大于9,则除2再分区;如果不大于9,直接落

Nosql-文本数据库 MongoDB

Nosql

Nosql——Not Only SQL

为什么需要Nosql?
——数据量过于大。使用Mysql会导致B树索引过大;随机IO性能降低;Mysql不擅长非结构化数据

Nosql数据库的特点:
PB级数据、一般只写一次、动态结构(可以增删数据段)、可扩展

MongoDB

MongoDB——humongous DB

特点:

  • 文档型数据库:存储JSON格式
  • 支持复杂索引:id作为唯一标识,可以在多个列上建立不同类型的索引(如地理空间索引,映射到地球经纬度)
  • Auto-sharding自动分块
  • 就地修改速度快

MongoDB有三个默认表

  • admin用于做用户校验
  • local没有副本,存储每台服务器的本地数据
  • config用于记录分区等信息

对与关系型数据,性能及复杂语句支持不如MySQL

Sharding:将一个数据库分成多个shard,一个shard包含多个chunk。
如何确定数据存在哪里:使用router确定数据存储的位置,使用B树作为索引。
如何保证shard间数据量基本一致:默认按key的范围划分chunk,当某些chunk大小过大,则分裂成两个chunk,然后将chunk迁移,均匀分布在shard上

什么时候需要自动迁移
——数据访问量一致时,如果是新闻这种时效性强的数据,自动迁移可能导致sharding间负载不均衡

Nosql-图数据库 Neo4j

为什么需要图数据库
书店:查找用户买过什么书:user-order-orderitem-book,多次join,Mysql/MongoDB性能差,反向查询更复杂/无法实现
而图数据库无需扫描整个表,沿着边走即可

Neo4J 内部存储机制

  1. 存储文件:数据分块存储到不同文件中(节点、关系、标签、属性分别存储)。
  2. 关系存储的双向链表:节点与关系间使用双向链表连接,快速遍历关系网络。
  3. 支持分区,分成多个子图,并包含一定的冗余数据
  • 节点的存储占用15byte:第一个位是一个标志位,是否有关联的边是否正在使用,然后(偏移量 1)是存储下一个边的ID关系,然后存储下一个属性的id,然后存储labels,最后存储其他的内容
  • 边的存储占用34byte,第一个位是一个标志位,存储是否正在使用;之后存储这个关系的起始节点 是谁,终止节点是谁、关系的类型;然后存储第一个节点(边的起始节点)的前一个关系是谁,后 一个关系是谁,第二个节点的前一个关系是谁,后一个关系是谁,后面就是保留字段
  • 是通过链表连起来的

Nosql-日志结构数据库

LSM-Tree,LevelDB,RocksDB

为什么需要日志结构数据库:
——新订单比老订单访问频率高,没有很好的方法支撑热点数据

只有追加写入,不存在删除和修改,通过分层,将热点数据放在内存及较高的树中。牺牲部分读取性能,适合于写多读少的场景。

优化:对于“abc”“abd”等前缀一样的index,可以进行合并

问题:

  • 写放大:写入时compaction导致一次写操作时间过多
  • 读放大:读取时数据不存在/数据处于最底层

防止读放大写放大:层数过大的数据移动到其他数据库存储

HTAP混合事务分析处理

左边用“行存”,右边用“列存”。RocksDB两边都使用,但是造成了一定的空间浪费

能不能一边行存一边列存?
——首先会造成空间浪费,其次一致性维护难度大,再次性能受影响

Nosql-向量数据库

向量化:向量(embedding)由 AI 模型(如大型语言模型,LLM)生成,包含大量维度信息。向量的维度表示数据的特征,可用于捕获模式和关系。

用途:管理和存储高维特征数据,支持基于相似性的查询。

向量数据库的工作原理

核心:基于 近似最近邻(Approximate Nearest Neighbor, ANN) 搜索算法找到与查询向量最相似的向量。

相似度:余弦相似度、欧拉距离、曼哈顿距离等

主要算法随机投影(Random Projection):将高维数据投影到低维空间,减少计算复杂度。

权衡准确性 vs. 速度:向量数据库通常提供近似结果,需在性能和准确率之间做出取舍。

向量数据库的应用

典型工具:Pinecone 等。

场景:AI 推荐系统、图像/文本检索等。

Nosql-时序数据库 InfluxDB

时序数据库的特点:

  • 只存在一段时间,过期数据可删去
  • 格式简单
  • 支持增量存储:不一定存储原始数据,可以存储差值(占用磁盘空间少,但是需要运算)
  • 所有数据带有时间戳,前n位可能相同,可以使用前缀存储,也可以使用增量存储

InfluxDB的存储格式

  • 时间戳
  • tag(比如ServerA,ServerB)(参与索引)
  • field key(比如CPU、GPU)(不参与索引)
  • field value

tag和field根据实际需求确定,可以互相转换

InfluxDB使用类似于LSM-Tree的时间结构合并树TSM-Tree

批量写入批量读取,时间早的数据压缩存储

也可以shard分区,但是只能对未来时间分区

云数据库 GuassDB

分布式优化(部分)

  • 多服务器,多机器,多线程并行执行
  • 预编译执行
  • 启发式规则查询优化
  • 两地三中心:两个同城生产集群热备份(主从定期切换),一个异地容灾
  • 分布式事务
  • 节点内部计算等等

多租户

如何为多租户提供有细微差别的数据库

实现方式:

  • 两个数据库×
  • 两个互不相干的表×
  • 共同数据放一个大表,然后为每个租户创建一个小表√
  • 共同数据放一个大表,多出的字段转行存,记录“原表id-字段名-字段值”√

云原生

  • 高精度授时
  • 节点间数据传输优化
  • 计算存储内存分离
  • 节点级别日志回放(方便迁移及容灾)
  • 数据预分区

数据湖 Hudi

数据仓库:从多种数据库上获取信息,做解析,使用时然后分析
数据湖:存储数据的原始格式,使用时按用户描述进行格式转换,然后进行分析

数据仓库和数据湖的区别是存储的数据是否结构化,与使用无关。数据仓库的数据是结构化的;数据湖是不管不顾结构化非结构化半结构化都能放进去

特点数据仓库数据湖
数据格式使用ETL处理过的数据原式数据
数据类型尚未决定如何使用直接使用
面向用户数据科学商业
可用性高可用性,更新快修改耗时高

支持流数据、批数据处理

集群

为什么需要集群:
——可靠性、可用性、可维护性(热更新)、可扩展性

集群部署-Nginx

代理和反向代理:
代理——几个用户用同一个ip访问后端
反向代理——几个后端用一个ip暴露给用户

为什么需要反向代理
——集群中状态需要一致,数据不一致;需要负载均衡;带session会话必须在用一台机器上执行

负载均衡的三种策略:

  • Round-robin(weight):按比例分配,如3:1,则连续3个请求给server 1,1个请求给server 2
  • IP hash:按用户的ip分配,一个ip访问的server一样(会话粘性)
  • Least connected:给用户最少的server分配

故障转移:

  1. 请求级故障转移: 某节点无法服务时,将请求重定向到另一节点。
  2. 会话级故障转移: 如果会话状态在客户端和服务器间共享,需在新节点上重建状态。

都要求请求在不同程度上是幂等的

服务器响应还经过nginx吗?
——经过

Mysql InnoDB 集群

客户端与Mysql Router连接,Router与主数据库连接,主数据库与其他数据库做主从备份。写操作全部落到主服务器上,读请求在所有数据库上做负载均衡

云计算、边缘计算

云计算——计算资源像电一样即插即用

MapReduce
——将工作拆成多个互不影响的部分(map),全部结束后再合并到一起(reduce)
combine是本地结果合并,reduce是多个节点数据合并;有没有combine看业务需求,甚至可以没有reduce

GFS
——增加了可用性,但降低了一致性。读取性能好,写入性能差。

大文件分chunk存储在多个机器,有副本

使用大表(Big Table)将表与表间的关联去掉而合并成一张允许非结构化数据的表,然后分区存储

Hadoop
——提供公用接口、高可用性的分布式文件系统、MapReduce

边缘计算——计算无需在大型服务器中进行,在靠近客户端的边缘设备上计算。
计算任务需要在边缘设备和云服务器间调度

GraphQL

REST只能返回一整个对象,能否按需请求?
——GraphQL:一种新的查询语言

容器化部署-Docker

VM还要装系统,可是我连系统都不想装

不想要每次打包——挂载
不想每次启动很多容器——compose up
容器间通信——设置network

批数据并行处理-Hadoop

——分布式文件系统HDFS;任务调度MapReduce;分布式内存管理;分布式数据库HBase

HDFS建立在本机文件系统之上,将大文件切分后存储在本机文件系统中

使用MapReduce接口进行任务调度

框架:

JobTracker是性能瓶颈

Shuffle+Sort的作用是在reduce前将数据排好序,让reducer能够快速定位,减少IO

Yarn进行了改进,在每个节点上增加了一个管理者,减少了崩溃导致的重做成本

MapReduce的问题:大量使用IO操作,能否全部放在内存里?
——Spark

批数据并行处理-Spark

RDD: 弹性分布式数据集,放在内存里,不能被更改,所以可以随意复用

懒调度:每执行到一个stage的末尾时,才开始真正执行这个stage的代码

使用cache()函数可以使内容保存在内存中,Spark会尽量保证其不会被换出到磁盘

块与块间存在宽依赖和窄依赖

窄依赖:父分区只被子分区中的一个分区使用

需要根据算法进行设计,比如使用哈希等策略,尽量使用窄依赖,有些宽依赖无法避免。宽依赖被当作懒执行时两个stage的分界。

懒执行的好处:如果Stage过大,会占用过大的内存,使用懒执行可以在执行时才占用内存。

spark还支持SQL语句

Spark流式处理的逻辑是划分小时间片,然后采用批式处理(微批处理)

流数据并行处理-Storm

类似于Spark,是真流式数据处理

可以将执行流转换成DAG计算图,然后并行

流式数据不会终止,进程一直运行,逻辑上和Spark类似

zookeeper是啥:监控每个server的状态,并进行管理

分布式文件系统-HDFS

适用于:极大的文件(小文件namenode占用内存,占不满datanode),一次写入多次读,尽量不要并行写(副本会出问题)

分成内存中的namenode和分布式存储上的datanode,datanode至少3个;namenode动态管理datanode中的副本,保证基本平衡

使用“机架感知”,确定副本放置的位置,不会将所有数据放在一个机架上。写入代价与容灾冲突。

同样负载均衡,读操作分散,写操作集中。

启动后一段时间不能操作,需要确定副本数量,运行时可以动态增加服务器

容错:

  • datanode出错/网络问题:创建新的副本即可
  • 数量不平衡:自动重新分配
  • 数据传输错误:哈希校验码
  • 镜像、日志文件出错:也有副本

分布式数据库-HBase

分布式,多版本(可以记录多个时间点数据的变化),面向列存;可以将几个列一起存储成“列族”(最好不要超过3个列)

列和行的交叉叫Cell

为什么是稀疏表:对于每一行,存储多个版本,时间戳递增,只需要保存和上一个时间点不同的值,如果一样则为空。

分布式数据仓库-Hive

Hive基于HDFS,存储原始文件(是结构化的),借助一定的中间格式(分为行存和列存-用于数据分析),使用SQL语句进行查询。加入数据时先不转成中间格式,只有在使用时才进行转换并检查格式(Schema on Read读入时结构化)

为什么要使用数据仓库而不用数据库:
Hive对文件格式不限,都可以存储,然后通过中间格式访问,甚至支持MySQL的表

Schema on Read/Write:

  • Schema on Read可实现非常快速的初始加载,因为不必以数据库的内部格式将数据读取、解析和序列化到磁盘。
  • Schema on Write架构可以提高查询时间性能,因为数据库可以对列编制索引并对数据执行压缩。

分布式并行处理框架-Flink

真流式处理,与storm的区别是实现方式不同,各有所长

支持有状态数据的流式处理框架。有状态:处理当前数据需要用到先前数据的处理结果。状态数据存储在自己的内存和或硬盘中,周期性在主机上做增量式的快照

因为数据可能是乱序来的,所以需要先落盘,留出一段冗余,然后再处理

提供三个层,可以在不同层级控制:高层使用SQL语句管理数据,中层管理流数据,底层管理单独的事件

“水位线”:数据可能是乱序来的,每个处理流程记录一个值,表示比时间这个值小的数据都已经被处理,目的是防止多个流数据混乱

人工智能

神经网络

Keras是一个前端,支持三个后端,包括tensorflow和pytorch

神经元:神经元接受多个输入,经过加权计算,产生一个输出
神经网络:多个神经元组成一层,多层产生神经网络。分为输入层,隐藏层,输出层

如果是分类问题,理想状态下我们希望输出层只有一个为1,其余为0

我们需要根据问题复杂度设计隐藏层的大小,不匹配会导致过拟合或欠拟合

Dense:全连接层,两层间任意两个神经元间有连接

设置批量batch的原因:并行计算一个和多个速度一样;原理是执行完每个batch进行一些修正,执行完多个再修正效果更好

放缩到0-1的原因:防止越界导致过大的影响,放缩后即使越界也不会overflow

激活函数为什么必须是非线性的:因为多个线性函数的组合仍是线性函数

Dropout可以设置忽略一定量的数据,防止神经网络只关注局部特征

卷积神经网络CNN

目标:通过卷积提取局部的特征

例:通过卷积提取边缘

假设图片过于大,我们可以使用“池化”,即设置卷积的步幅,使卷积后的图片变小

还可以增加“深度”,使用多个卷积核,提取不同的特征。

模型的大小应该和问题的复杂度相匹配

自然语言处理

如何将一句话转成数值
——一个词语对应一个多维向量/BOW:只记录一句话中词语出现的次数,不记录顺序

假设我们有一个语料库,我们首先需要用字典进行词语切分,再产生一个新字典(包含词语及其id),再进行过滤,去除极常用和极不常用的词,再添加短语,得到最后字典。然后通过一维卷积,提取多个单词间的关系,得到分类结果

TextCNN和SimpleCNN的区别:SimpleCNN的卷积长度是固定的,而TextCNN将长短卷积核得到的结果进行拼接,可以提取不同长度上的特征

循环神经网络RNN

SimpleRNN:基本想法:使用前面得到的结果,产生后面的输出。坏处是不能并行。

LSTM:但是有时我们不需要把很前面的结果全部传递到后面,所以可以随机忽略前面的结果,也就得到了LSTM长短时记忆模型

ChatGPT

“Just adding one word at a time”

假设我们通过训练,得到了下一个词的不同可能性,以及对应的概率,我们如何保证每人得到的值不同?
——设置一个温度,温度越高,越有可能不选择概率最高的词语。

概率是如何得到的?
假设我们得到了关于“cat”的语料库,首先我们统计每个字母出现的次数,然后让a模型产生一些“单词”,显然这个单词并不存在于字典里,所以我们还需要关注字母与字母间的关系,比如q后面经常出现u,再进行训练,就可以产生一些正确的单词。
产生单词后,我们在词和词之间做同样的训练,但是词语的个数远大于字符数,所以需要进行降维处理。

Transformer

transformer的产生:
在CNN,中我们用小卷积核提取小范围特征,用多层卷积提取长范围特征,问题是不能看出词的顺序,而且很难反应前面词对后面词的影响;于是产生RNN及LSTM,通过将上一次输入变成下一次的输出,这样可以保持词与词之间的关系,缺点是不能并行执行。于是产生Transformer

Transformer架构:

左侧为编码器(输入一句话,输出一串特征),右侧为解码器(用特征产生结果),另外需要多个编码器嵌套

为什么需要多头记忆力:自然语言有歧义。注意力需要通过查询矩阵Q、键矩阵K、值矩阵V计算,得到一个注意力矩阵

为了反应词语顺序对句子的影响,可以使用位置编码

相较于LSTM的优点:编码器可以并行计算

write by zhuanzhuan
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇