前言

看了一下在阿里云买的hk服务器每个月得34,算下来一年也不少钱了

而且内存也开始不是很够用了就打算把服务都迁移到树莓派上(主要是ttrss),然后就折腾了将近一个星期都差不多迁移完成了.

唯一的深坑就是某些docker镜像不支持arm架构没法继续使用了吧(比如Huginn).

安装Ubuntu Server

官方文档:How to install Ubuntu on your Raspberry Pi

镜像源

/etc/apt/sources.list

1
2
3
4
5
6
7
8
9
10
11
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ b#  清华
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-security main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-updates main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-proposed main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-security main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-proposed main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-backports main restricted universe multiverse

NPS

文档

安装直接下载二进制文件执行

服务端配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
## 因为服务器上配置了nginx所以取消监听80和443端口
#HTTP(S) proxy port, no startup if empty
http_proxy_ip=0.0.0.0
#http_proxy_port=80
#https_proxy_port=443
#https_just_proxy=true
#default https certificate setting
#https_default_cert_file=conf/server.pem
#https_default_key_file=conf/server.key

## 指定服务端连接时的端口
##bridge
bridge_type=tcp
bridge_port=444
bridge_ip=0.0.0.0

## 管理面板配置
#web
web_host= 1.1.1.1
web_username=admin
web_password=admin
web_port = 8080

客户端

1
2
3
./npc -server=1.1.1.1:444 -vkey=key
./npc install -server=1.1.1.1:444 -vkey=key
sudo npc start

v2ray安装

在没有代理的情况下哪怕配置了docker的国内镜像源,下载v2ray的镜像还是非常的折磨,所以选择直接从官网下载了二进制包直接使用.

启动脚本

偷了服务器上原来的service配置

/etc/systemd/system/v2ray.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Unit]
Description=V2Ray Service
After=network.target
Wants=network.target

[Service]
# This service runs as root. You may consider to run it as another user for security concerns.
# By uncommenting the following two lines, this service will run as user v2ray/v2ray.
# More discussion at https://github.com/v2ray/v2ray-core/issues/1011
# User=v2ray
# Group=v2ray
Type=simple
PIDFile=/run/v2ray.pid
ExecStart=/usr/bin/v2ray/v2ray -config /etc/v2ray/config.json
Restart=on-failure
# Don't restart in the case of configuration error
RestartPreventExitStatus=23

[Install]
WantedBy=multi-user.target

客户端配置

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
50
51
52
53
54
55
56
57
58
{
"log": {
"access": "/var/log/v2ray/access.log",
"error": "/var/log/v2ray/error.log",
"loglevel": "warning"
},
"inbounds": [
{
"port":"7892",
"protocol":"http",
"settings":{},
"tag":"in-1"
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "",
"port": 443,
"users": [
{
"id": "",
"alterId": 64,
"security": "auto"
}
]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": {
"allowInsecure": true,
"serverName": ""
},
"tcpSettings": null,
"kcpSettings": null,
"wsSettings": {
"connectionReuse": true,
"path": "",
"headers": {
"Host": ""
}
},
"httpSettings": null,
"quicSettings": null
},
"mux": {
"enabled": true
}
}
]
}

因为大部分的软件配置代理都无法通过socks,所以直接配置了http的代理使用

docker安装

一键脚本

1
2
curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh --mirror Aliyun

配置docker的代理(镜像源感觉并没有什么卵用, pull v2ray的时候还是跟爬一样)

1
2
3
4
5
6
7
8
9
10
11
12
sudo mkdir -p /etc/systemd/system/docker.service.d
## 文件写入下面配置
/etc/systemd/system/docker.service.d/http-proxy.conf
/etc/systemd/system/docker.service.d/https-proxy.conf
## 配置 v2ray配置的http代理
[Service]
Environment="HTTPS_PROXY=http://127.0.0.1:1080/"

sudo systemctl daemon-reload
sudo systemctl restart docker
## 查看配置
systemctl show --property=Environment docker

TTRSS

使用了Awesome TTRSS

通过 docker-compose 部署

主要增加了代理配置,直接配置了全局的http代理(毕竟有些订阅源)

1
2
environment:
- HTTP_PROXY=http://172.17.0.1:7892

这个ip是主机上的docker0的虚拟网卡地址:(总比那些动不动就叫人-h的好多了)

IPv4 address for docker0: 172.17.0.1

挂载硬盘

查看设备:

sudo lsblk -o UUID,NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL,MODEL

安装驱动:

sudo apt-get install ntfs-3g

手动mount:

sudo mount /dev/sdc2 /mnt/share

并没有配置开机自动挂载,因为连接硬盘启动一次失败之后就懒得折腾了,毕竟一直开着启动次数也不多,唯一的坑是如果把某些docker镜像设置了always启动而服务器启动之后硬盘没有正确挂载的话-v的文件夹内外会不一致,暂时的解决办法是把always启动关闭手动启动docker镜像在挂载硬盘之后

Aria2 Pro

1
2
3
4
5
6
7
8
9
10
11
12
13
docker run -d \
--name aria2-pro \
--restart unless-stopped \
--log-opt max-size=1m \
--network host \
-e PUID=$UID \
-e PGID=$GID \
-e RPC_SECRET=<TOKEN> \
-e RPC_PORT=6800 \
-e LISTEN_PORT=6888 \
-v ~/aria2-config:/config \
-v ~/aria2-downloads:/downloads \
p3terx/aria2-pro
1
2
3
4
5
6
docker run -d \
--name ariang \
--restart unless-stopped \
--log-opt max-size=1m \
-p 6880:6880 \
p3terx/ariang

因为下载文件配置在了挂载的机械盘上面,所以启动之后需要在面板的高级设置里把文件分配方法改为prealloc,否则下载失败

Git ssh代理配置

git clone时使用ssh方式clone需要在ssh中配置:

~/.ssh/config

1
2
3
4
5
6
7
8
# 必须是 github.com
Host github.com
HostName github.com
User git
# 走 HTTP 代理
ProxyCommand connect -H 127.0.0.1:7892 %h %p
# 走 socks5 代理(如 Shadowsocks)
# ProxyCommand nc -v -x 127.0.0.1:1080 %h %p

ajenti

1
sudo pip3 install --proxy http://127.0.0.1:7892 --cache-dir=/home/ubuntu/piptmp --build /home/ubuntu/piptmp ajenti-panel ajenti.plugin.ace ajenti.plugin.augeas ajenti.plugin.auth-users ajenti.plugin.core ajenti.plugin.dashboard ajenti.plugin.datetime ajenti.plugin.filemanager ajenti.plugin.filesystem ajenti.plugin.network ajenti.plugin.notepad ajenti.plugin.packages ajenti.plugin.passwd ajenti.plugin.plugins ajenti.plugin.power ajenti.plugin.services ajenti.plugin.settings ajenti.plugin.terminal

–cache-dir=/home/ubuntu/piptmp –build /home/ubuntu/piptmp

用来解决/var/tmp空间不足的问题,自行决定路径

磁盘空间管理

journalctl 命令自动维护文件大小

journalctl --vacuum-size=500M

docker磁盘分析

1
2
3
docker system df
## 清理
docker system prune

df du使用

1
2
df -h 
du -h -d 1 | grep G

ncdu

sudo apt install ncdu

深入浅出现代Web编程:https://fullstackopen.com/zh/

full_stack_open.xmind

前端

JavaScript

Variables

  • const
  • let
  • var (不建议使用)

Arrays

  • const t2 = t.concat(5)
  • const m1 = t.map(value => value * 2)
  • 解构赋值: const [first, second, ...rest] = t
  • 展开语法
    • […numbers, 4, 5]

Objects

1
2
3
4
5
6
7
8
const object3 = {
name: {
first: 'Dan',
last: 'Abramov',
},
grades: [2, 3, 5, 3],
department: 'Stanford University',
}
  • 对象展开
    • const changedNote = { ...note, important: !note.important }

Functions

  • 箭头函数
    • 完整过程
      1
      2
      3
      4
      5
      const sum = (p1, p2) => {
      console.log(p1)
      console.log(p2)
      return p1 + p2
      }
    • 只有一个参数
      1
      2
      3
      4
      const square = p => {
      console.log(p)
      return p * p
      }
    • 只包含一个表达式
      • const square = p => p * p
  • Object methods and “this”
    • 使用箭头函数可以解决与 this相关的一系列问题

Classes

1
2
3
4
5
6
7
8
9
10
11
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
greet() {
console.log('hello, my name is ' + this.name)
}
}
const adam = new Person('Adam Ondra', 35)
adam.greet()

Destructuring (解构)

1
2
3
4
5
6
7
8
9
10
const Hello = ({ name, age }) => {  const bornYear = () => new Date().getFullYear() - age
return (
<div>
<p>
Hello {name}, you are {age} years old
</p>
<p>So you were probably born in {bornYear()}</p>
</div>
)
}
  • 解构使变量的赋值变得更加容易,因为我们可以使用它来提取和收集对象属性的值,将其提取到单独的变量中
  • const { name, age } = props
  • const Hello = ({ name, age }) => {

对象的展开语法

1
2
3
4
5
6
7
const handleLeftClick = () => {
const newClicks = {
...clicks,
left: clicks.left + 1
}
setClicks(newClicks)
}
  • { ...clicks, right: 1 }
  • Math.max(...notes.map(n => n.id))

promises(axios)

  • 表示异步操作的对象
  • 注册一个事件处理访问操作的结果 ==> .then()
    1
    2
    3
    4
    5
    6
    axios
    .get('http://localhost:3001/notes')
    .then(response => {
    const notes = response.data
    console.log(notes)
    })
  • 错误处理
    1
    2
    3
    .catch(error => {
    console.log('fail')
    })

template string

  • console.log(importance of ${id} needs to be toggled)

Property definitions

  • export default { getAll, create, update }

ESLint

async/await

  • 错误处理
    • express-async-errors

local storage

  • window.localStorage

TypeScript

  • 主要语言特性
    • 类型注解
      • const birthdayGreeter = (name: string, age: number): string => {
    • 结构化类型
    • 类型推断
    • 类型擦除
  • ts-node-dev
  • 工具类型
    • Pick
    • Omit

React

Component 组件

  • 语法
    • 箭头函数 + 代码简写

      1
      2
      3
      4
      5
      const App = () => (
      <div>
      <p>Hello world</p>
      </div>
      )
    • 不简写版本

      1
      2
      3
      4
      5
      6
      7
      const App = () => {
      return (
      <div>
      <p>Hello world</p>
      </div>
      )
      }
    • 多组件

      • React 的核心理念,就是将许多定制化的、可重用的组件组合成应用
    • props

      • 向组件传递数据
        • const Hello = (props) =>
        • <Hello name="Maya" age={26 + 10} />
      • 将状态传递给子组件
        • 状态提升
          • 将共享状态提升到它们最接近的共同祖先
    • 组件辅助函数: 在函数中定义函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const Hello = (props) => {
      const bornYear = () => { const yearNow = new Date().getFullYear() return yearNow - props.age }
      return (
      <div>
      <p>
      Hello {props.name}, you are {props.age} years old
      </p>
      <p>So you were probably born in {bornYear()}</p> </div>
      )
      }
    • 将事件处理传递给子组件

      • handleClick = { () => func() }
    • 受控组件

      • 组件现在控制了元素的行为
      • 注册一个事件处理来同步对元素所做的更改和组件的状态
        1
        2
        3
        4
        5
        6
        7
        8
        <input
        value={newNote}
        onChange={handleNoteChange} />

        const handleNoteChange = (event) => {
        console.log(event.target.value)
        setNewNote(event.target.value)
        }
    • 表单

      1
      2
      3
      4
      5
      6
      7
      8
      <form onSubmit={addNote}>
      <input
      value={newNote}
      onChange={handleNoteChange} />
      <button type="submit">save</button>
      </form>

      const handleNoteChange = (event) => { console.log(event.target.value) setNewNote(event.target.value) }

JSX

Hook

  • Hook的规则

    • 不能从循环、条件表达式或任何不是定义组件的函数的地方调用 useState
  • state hook

    • 导入 useState 函数
      • import React, { useState } from 'react'
    • 定义组件的函数体
      • const [ counter, setCounter ] = useState(0)
    • 每次 setCounter 修改状态时,它都会导致组件重新渲染
  • effect hooks

    • 从服务器获取数据时使用的正确工具
      1
      2
      3
      4
      5
      6
      7
      useEffect(() => {
      axios
      .get('http://localhost:3001/notes')
      .then(response => {
      setNotes(response.data)
      })
      }, [])
    • 在函数组件中执行副作用
    • 数据获取、设置订阅和手动更改 React 组件中的 DOM

事件处理

  • 鼠标事件
    • <button onClick={() => console.log('clicked')}>

渲染

  • 页面重渲染

    • ReactDOM.render
  • 条件渲染

  • 渲染集合

    • notes.map(note => <li>{note.content}</li>)
    • Key-属性
      • React 使用数组中对象的key属性来确定组件在重新渲染时,如何更新组件生成的视图
      • <li key={note.id}>

调试

  • React developer tools扩展

React router

  • Parameterized route
  • useHistory

样式

production build (生产构建)

  • npm run build
    • 创建一个名为build 的目录
    • 代码的Minified版本将生成到static 目录
    • 所有的 JavaScript 都将被缩小到一个文件中

Flux-架构

  • redux
    • Action
    • reducer
      • Reducer 状态必须由不可变 immutable 对象组成
    • store
    • dispatch
    • subscribe
    • immutable
  • react-redux
    • Provider
  • deep-freeze
    • 确保 reducer 被正确定义为不可变函数
  • Pure functions
  • redux-thunk

Webpack

后端

Express

  • 路由参数

    • app.get('/api/notes/:id', ...)
      1
      2
      3
      app.get('/api/notes/:id', (request, response) => {
      const id = request.params.id
      }
  • 错误处理

    • response.status(404).end()
  • json-parser

    • app.use(express.json())
  • Promise chaining

REST

  • 统一接口 uniform interface

CORS

  • const cors = require('cors')

部署静态文件

  • app.use(express.static('build'))

MongoDB

错误处理

  • .catch(error => {
  • 移入中间件
  • next 函数
    • .catch(error => next(error))

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── index.js
├── app.js
├── build
│ └── ...
├── controllers
│ └── notes.js
├── models
│ └── note.js
├── package-lock.json
├── package.json
├── utils
│ ├── config.js
│ ├── logger.js
│ └── middleware.js

密钥认证

GraphQL

  • Apollo server

Testing

  • Jest
    • afterAll
    • beforeEach
  • mock
    • mongo-mock
  • supertest

其余

模块提取 (单一职责原则)

  • components
  • service

教材

Node.js

  • 基于谷歌的 chrome V8 引擎的 JavaScript 运行时环境

资源

工具

  • create-react-app
  • JSONView
  • npm
    • json-server
    • axios
    • express
    • nodemon (代码热更新)
      • npm install --save-dev nodemon
    • npm install cors --save
    • Mongoose
      • npm install mongoose --save
    • dotenv
      • npm install dotenv --save
    • npm install eslint --save-dev
    • npm install --save-dev jest
    • npm install --save-dev deep-freeze
    • npm install --save react-redux
    • npm install --save-dev redux-devtools-extension
    • npm install --save-dev ts-node-dev
  • Postman
  • Visual Studio Code REST client
  • Redux DevTools

第一部分: 什么是日志

什么是日志

image-20200428143046866

  • 新记录被添加到尾部
  • 读取顺序从左至右
  • 每个记录都会被赋予一个唯一的顺序的编号
    • 日志记录的顺序定义了一个“时间”的概念
    • 日志的编号可以理解为一个时间戳(相对时间)
  • 日志和文件或者(数据)表没有区别
  • 日志的目的是:用于记录发生了什么和什么时候发生的 ==> 分布式系统所关心的核心问题
  • 日志的类型:
    • 应用日志:非结构化的错误信息或者应用跟踪信息
    • 数据日志:为程序访问设计 这里讨论的类型

数据库中的日志

作为一种ACID模型的实现细节,日志的作用:

  • 保证数据结构的同步
  • 崩溃恢复时的数据索引
  • 使用日志记录将要被修改的记录的信息
  • 日志记录在数据上发生了什么

作为数据库之间的同步的方法 (MySQL主从同步通过日志实现)

分布式系统中的日志

分布式系统的设计核心问题: 如何根据事件发生的顺序在多台机器上更新对应的数据并保持同步

也就是: 使得多台机器都对接收的相同的分布式日志进行相同的操作

日志解决了两个问题:

  • 有序的变化 (事件顺序)
  • 分布式的数据

状态机复制原理

如果两个确定性的进程以相同的状态开始并且以相同的顺序获得相同的输入,则产生相同的输出并且结束在相同的状态(状态机的性质)

  • 确定性意味着处理过程过程与时间无关
  • 状态指进程留在机器上的任何数据
  • 确定性的处理过程就是确定性的(deterministic processing is deterministic)

日志的目的是排除了输入流中的全部的不确定性,确保处理相同数据的每个节点状态都保持同步

这种方法的好处在于索引日志的时间戳可以作为每个节点状态的唯一ID来表达每个节点的状态

分布式系统中两种复制和处理数据的方法:

  • 主-主模型:每个节点都到日志读取操作复制到本地进行操作和状态的保持,例如每个节点读取 ”+1“,”*2“操作到本地进行计算然后保存结果
  • 主-备模型:选举一个主节点进行数据的操作,把操作的结果记录到日志里供其他节点读取,例如主节点进行”+1“,”*2“的操作,把操作的结果”1“,”3“,”6“记录到日志给其他节点读取

主-主模型

主-备模型

这也揭示了为什么记录的顺序是数据一致性的关键:操作的顺序不同就会导致完全不一样的结果(运算顺序不同结果也会不同)

一些分布式一致性协议

  • Paxos
  • ZAB
  • RAFT
  • Viewstamped Replication

日志和表的二象性

把日志类比成银行账户的交易记录的话

  • 表: 当前账户的状态
  • 日志: 表的所有状态的备份

所以可以看到日志所能表达的信息量是比表多很多的,但是相对于的存储的成本也高很多

第二部分: 数据集成

数据集成指:一个组织的所有数据对这个组织所有的服务和系统可用

马斯洛的层次需求理论

  • 收集所有相关的数据
  • 放到适当的处理环境中
  • 建议可靠完整的数据流
  • 有了数据和处理过程:
    • 关注更好的数据建模
    • 更好的可视化,报表,处理算法,数据预测

两个难题

  • 事件数据流水线
    • 记录发生了什么而不是产生的结果
    • 数据量比传统数据量级大很多,处理更加困难
  • 专用数据系统的爆发: 集成更加困难

日志结构的数据流

  • 提取所有组织的数据并放入用于实时订阅的日志中心
  • 每个数据源都可以建模成日志
  • 日志的概念为每次更改提供了逻辑时钟
  • 日志可以充当缓冲区,是数据的产生和消耗可以异步进行
  • 目标系统只需要知道日志而不需要知道数据源系统的任何信息

image-20200428160306069

在LinkedIn

大量的数据专用系统:

  • Search
  • Social Graph
  • Voldemort (key-value store)
  • Espresso (document store)
  • OLAP query engine
  • …………

原先的数据管道架构:

image-20200428163507582

直接使用管道对接数据源的问题:

任何时候任何管道都可能出问题,在坏数据上进行操作只会产生更多坏数据

在实践中意识到的几件事:

  1. 虽然很混乱,但是建立的管道是有价值的
  2. 需要对数据管道更深入的支持才能对数据进行可靠的加载
  3. 数据覆盖率仍然很低

需要的架构: 一个数据源只连接一个单一的管道 ==> kafka

image-20200428163527737

ETL和数据仓库的关系

数据仓库

  • 一个干净,集成的数据仓库,其结构支持分析
  • 定期从数据源提取数据
  • 将其拼凑成可以理解的形式
  • 加载到一个中心数据仓库
  • 不能对数据进行实时处理

ETL

  • 提取和清理数据的过程
  • 将数据重组成用于查询的结构

数据仓库团队的经典问题

  • 通常不了解数据仓库中的数据如何使用
  • 创建难以提取或者很难获取的数据
  • 很难转换成可用的形式

一个更好的形式

image-20200428164628144

  • 一个中心管道
  • 定义良好的用于添加数据的API
  • 组织的可扩展性
    • 可以提供完整数据集的搜索功能
    • 亚秒级的数据流监控
  • 进行特定的清理或转换的位置
    • 由数据生产者完成后添加到中心日志(最好的方法, 可以保证中心日志系统数据的规范性)
    • 在日志中心进行实时转换(会产生转换日志)
    • 由加载数据的系统进行处理
  • 在添加数据时保留原有的数据格式,需要的特定操作在处理原始数据时进行
  • 只有针对目标系统的聚合操作才应该加到加载过程中

日志文件和事件

优势: 支持解耦的事件驱动的系统

构建可伸缩的日志

  • 日志分片
  • 通过批量读写优化吞吐量
  • 避免无用的数据拷贝

image-20200428165518804

  • 每个分片的日志是有序的,但是分片之间没有全局的次序
  • 由写入者决定消息发送到特定的日志分片
  • 每个分片通过可配置数字指定数据复制的复本个数,每个复本都有一个分片日志完全一致的一份拷贝
  • 对于顺序读写可以方便地优化

第三部分:日志与实时流处理

  • 日志是流处理的核心
  • 流处理是连续数据处理的基础架构
  • 计算模型是通用的
  • 具有低延迟产生结果的能力
  • 数据的收集方法是真正的驱动力:
    • 分批收集的数据自然进行分批处理
    • 连续收集数据时自然进行连续的处理
  • 连续处理可以平滑的处理资源并且降低延迟

流处理的另一个观点

  • 处理数据时引入了时间概念
  • 不需要数据的静态快照
  • 可以控制输出的频率
  • 不需要等待数据集收集结束再进行处理
  • 是批处理的一种泛化

其实只有很少的公司拥有实时数据流,因为缺少实时的数据收集

在金融领域实时数据流已经成为标准并且流处理已经成为了瓶颈

流处理覆盖了实时响应服务和离线批处理架构之间的差距

日志解决的最大问题是使得多订阅者可以获得实时的数据输入

数据流图

  • 扩展了早期对数据集成中数据订阅的想法
  • 根据订阅的数据进行计算得出新的订阅数据
  • 可以封装处理的复杂性
  • 可以把所有对组织中数据获取,转换和数据流处理操作视为一系列的日志以及对其写入过程进行处理的结合
  • 日志在其中的作用:
    • 使得每个数据集可以有多个订阅者并且保证有序
    • 提供了对数据处理的缓冲

image-20200428172047446

有状态的实时处理

需要在流处理的某个大小的时间窗口内进行更复杂的计数、聚合和关联操作

如何支持类似”表”的东西并将其与处理过程分开:

  • 把流转换为同一位置的表
  • 把状态保持在本地表或者索引中
  • 允许崩溃重启恢复
  • 处理过程的状态也保持为日志

日志合并

  • 两种数据清理的形式:

    • 事件数据,Kafka支持仅维护一个窗口的数据
    • 键值存储数据,使用压缩技术,通过重放技术来重建源系统的状态
  • 删除过时的记录但是保证日志包含了源系统的完整备份

  • 不再重现原系统曾经的所有状态,只保留最新的状态

image-20200428173223740

第四部分:系统构建

  • 分拆系统
    • 把整个组织系统和数据流视为单个分布式数据库
    • 面向系统的查询只是特定的索引
    • 流处理是一种完善的触发器和视图的实现机制
    • 将每个系统的许多小实例合并为几个大集群
  • 对于现有系统过多的复杂度,三个可能的进化方向

日志在分布式系统中的地位

日志能做什么:

  • 处理数据的一致性问题
  • 提供节点之间的数据复制
  • 提供”提交”语义(在保证写入不丢失时才确认写入)
  • 提供系统的外部数据订阅
  • 提供从失败副本恢复丢失数据或者引导新副本的能力
  • 处理节点之间的数据重平衡

这也是一个分布式系统所需要做的

如何工作

分为两个逻辑部分:

  • 日志: 按顺序捕获状态变化
  • 服务层
    • 服务节点存储查询所需索引
    • 服务节点订阅日志
    • 按照日志存储顺序尽快写入本地索引
    • 接收到请求时
      • 比较请求需要的时间戳和自己的索引位置
      • 延迟请求直到索引位置超过请求时间戳,保证数据正确性

image-20200428175431735

分布式系统中比较复杂的事

  • 处理故障节点
  • 在节点之间移动分区

处理方法:

  • 使日志保留一个数据窗口和分区数据存储的快照结合
  • 日志保留完整的数据副本并进行垃圾回收

image-20200428175515805

以日志为核心的分布式系统

  • 把查询相关的因素和系统的可用性与一致性解耦
  • 日志是一种特别有效的存储机制
  • 日志系统只执行线性的读取和写入
  • 系统的强度取决于日志的使用方式

资料

0%