开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

一文带你了解NB-IoT标准演进与产业发展

发表于 2021-08-19

摘要:本文将带大家详细了解 NB-IoT 标准演进与产业发展。

本文分享自华为云社区《一文带你了解NB-IoT标准演进与产业发展》,作者:万万万。

我们都知道,物联网的场景和手机、电脑在使用的传统互联网是不太一样的。那么,就无线通信场景而言,物联网有什么样的特点呢?首先,感知层的物联网设备在进行数据收发的时候,那些数据包是比较小的,并且收发的频率也是比较低的,有的时候每天只需要发送不到十个数据。其次,为了提高物联网设备的使用寿命,这些设备对能源的消耗是比较小的,所以这也要求设备在通信的时候功耗也是要比较低的。

总结起来,就是无源、小包、偶发的通信需求。基于这样的场景需求,就要求通信网络必须要是功耗低,覆盖广的,也就是 LowPowerWideArea 的场景。

在 LPWA 场景当中,当下最热门的一项技术莫过于 NB-IoT 通信技术。它被广泛使用于现如今的公共事业、城市管理当中,所以了解 NB-IoT 的技术细节以及解决方案对学习物联网就显得很重要了。

本文将带大家详细了解 NB-IoT 标准演进与产业发展。

NB-IoT 标准演进

NB-IoT 技术标准最早是由华为和沃达丰主导提出来的,之后又吸引了高通和爱立信等一些厂家。从一开始的 NB-M2M 经过不断的演进和研究,在 2015 年的时候演进为 NB-IoT,在 2016 的时候,NB-IoT 的标准就正式被冻结了。当然,NB-IoT 的标准依然在持续的演进当中,在 17 年的 R14 当中就新增了许多特性,到了 R14 版本,NB-IoT 具有了更高的速率,同时也支持站点定位和多播业务了。在 2020 年 7 月 9 日最新召开的会议上,NB-IoT 这项技术已经被正式接纳为 5G 的一部分了。

这一事件对于 NB-IoT 来说有一个什么样的好处呢?当 NB-IoT 这项技术被归为 5G 的标准之后,也就是说,即使是通过 NB-IoT 接入网络的物联网设备,最终也可以连接 5G 核心网,享受 5G 的边缘计算、网络切片等一些服务。所以,这一事件对于 NB-IoT 来说是非常非常重要的。但是由于现阶段的 NB-IoT 并不支持接入 5G 网络,所以该技术在后续仍需要经过不断的演化和技术的演进才能进入 5G 网络当中。

运营商 LPWA 技术选择

图 1 全球运营商 LPWA 技术选择分布

从上图可知,全球大多数的运营商在进行 LPWA 技术选择的时候都是先选择去部署一张 NB-IoT 的网络,之后再去部署一张 eMTC 的网络。其原因在于运营商都是倾向于先去部署一张他们本来没有的网络,因为之前没有像 NB-IoT 这样的网络去支持低功耗广域网的场景,并且也从来没有专门为了设备去设计一张网络供物联网终端设备来使用。

之前所使用的运营商网络其实都是给人来使用的,为了方便人们的通信,所拥有的语音通信以及越来越高的传输速率等等。但是 NB-IoT 不一样,这张网络速率是非常慢的,人类去使用的话体验肯定是非常差的,但是这张网络对于底层的设备来说是非常合适的。原因之一是因为覆盖范围非常广,另一个原因是能耗低,速率低等。至于 eMTC 这张网络,它的速率相对于 NB-IoT 是要高的,并且还支持语音通信,所以它与用户现在正在使用的 2G 网络是比较相近的。所以在 2G 网络退网之后,运营商就可以选择使用 eMTC 去代替 2G 网络来进行使用,这就是大部分运营商选择先部署 NB-IoT 网络再部署 eMTC 网络的原因。

运营商 NB-IoT 技术频谱选择

对于运营商来说,除了有选择技术的问题之外,另一个就是频谱选择的问题,因为这是一个避不开的问题。如果要满足低功率广域网的场景的话,网络的频段要够低,因为它既要满足广覆盖,还要满足网络的穿透性。大部分感知层的物联网设备,像气表、水表等,它们是被放在厨房的柜子里的,相当于是被层层遮蔽的,如果网络穿透力不够的话是没有办法跟设备进行连接的。

图 2 全球运营商 NB-IoT 频谱选择

同时,频段越低穿透性越强,频段越高穿透性越弱。所以由图 2 可以看到,对于运营商来讲,他们相当于把最合适的一部分频段都拿出来了。所以大部分的运营商都是在 700 到 900M 这一部分也就是 SubG 频段来进行部署。当然,也有少数的部分像中国联通他有一部分是放在 1800M。所以在上文中提到的,NB-IoT 网络主要是部署在 SubG 频段的,而不是说全部都是在 SubG 频段原因就在于此。

另外,由于 NB-IoT 的网络是基于 4GLTE 的网络的。所以运营商会在 4G 的基站中选择一部分基站去做软件升级来作为 NB-IoT 的基站。但是中国联通不一样,因为中国联通的 4G 基站就是基于 3G 基站升级得到的。所以就相当于它可以直接使用 3G1800MHz 的基站升级得到 NB-IoT 的基站,所以联通经过基站平滑升级之后,就直接在 1800M 使用 NB-IoT 网络,节省了很大的成本。这也就是为什么中国联通可以在 1800MHz 部署 NB-IoT 网络。

NB-IoT 产业发展

除了网络技术,基站和频段之外,如果想要使用这个网络也得有支持设备与基站连接的芯片。所以华为早在 R13 就推出了 Boudica120 芯片,由于它推出的比较早,所以芯片的功能并不是特别强,只支持 SubG 频段,并且也不支持移动性这些在 R14 才演进的特性。所以基于 R14 的一些新特性,华为又推出了 Boudica150 芯片来满足新特性的使用。

图 3NB-IoT 产业生态

图 3 为 NB-IoT 技术的应用情况,其实 NB-IoT 所涉及的领域是比较多的。像水表、气表、路灯、智能停车等等应用当中都有涉及。

点击关注,第一时间了解华为云新鲜技术~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Vue 32 发布了,那尤雨溪是怎么发布 Vuejs 的

发表于 2021-08-19
  1. 前言

大家好,我是若川。为了能帮助到更多对源码感兴趣、想学会看源码、提升自己前端技术能力的同学。我倾力组织了每周大家一起学习200行左右的源码共读活动,感兴趣的可以点此扫码加我微信 ruochuan02 参与。

之前写的《学习源码整体架构系列》 包含jQuery、underscore、lodash、vuex、sentry、axios、redux、koa、vue-devtools、vuex4十篇源码文章。

写相对很难的源码,耗费了自己的时间和精力,也没收获多少阅读点赞,其实是一件挺受打击的事情。从阅读量和读者受益方面来看,不能促进作者持续输出文章。

所以转变思路,写一些相对通俗易懂的文章。其实源码也不是想象的那么难,至少有很多看得懂。

最近尤雨溪发布了3.2版本。小版本已经是3.2.4了。本文来学习下尤大是怎么发布vuejs的,学习源码为自己所用。

本文涉及到的 vue-next/scripts/release.js文件,整个文件代码行数虽然只有 200 余行,但非常值得我们学习。

歌德曾说:读一本好书,就是在和高尚的人谈话。 同理可得:读源码,也算是和作者的一种学习交流的方式。

阅读本文,你将学到:

1
2
3
bash复制代码1. 熟悉 vuejs 发布流程
2. 学会调试 nodejs 代码
3. 动手优化公司项目发布流程

环境准备之前,我们先预览下vuejs的发布流程。

vue 发布流程

  1. 环境准备

打开 vue-next,
开源项目一般都能在 README.md 或者 .github/contributing.md 找到贡献指南。

而贡献指南写了很多关于参与项目开发的信息。比如怎么跑起来,项目目录结构是怎样的。怎么投入开发,需要哪些知识储备等。

你需要确保 Node.js 版本是 10+, 而且 yarn 的版本是 1.x Yarn 1.x。

你安装的 Node.js 版本很可能是低于 10。最简单的办法就是去官网重新安装。也可以使用 nvm等管理Node.js版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bash复制代码node -v
# v14.16.0
# 全局安装 yarn

# 建议克隆我的项目
git clone https://github.com/lxchuan12/vue-next-analysis.git
cd vue-next-analysis/vue-next

# 或者克隆官方项目
git clone https://github.com/vuejs/vue-next.git
cd vue-next

# 安装 yarn
npm install --global yarn
# 安装依赖
yarn # install the dependencies of the project
# yarn release

2.1 严格校验使用 yarn 安装依赖

接着我们来看下 vue-next/package.json 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
json复制代码// vue-next/package.json
{
"private": true,
"version": "3.2.4",
"workspaces": [
"packages/*"
],
"scripts": {
// --dry 参数是我加的,如果你是调试 代码也建议加
// 不执行测试和编译 、不执行 推送git等操作
// 也就是说空跑,只是打印,后文再详细讲述
"release": "node scripts/release.js --dry",
"preinstall": "node ./scripts/checkYarn.js",
}
}

如果你尝试使用 npm 安装依赖,应该是会报错的。为啥会报错呢。
因为 package.json 有个前置 preinstall node ./scripts/checkYarn.js 判断强制要求是使用yarn安装。

scripts/checkYarn.js文件如下,也就是在process.env环境变量中找执行路径npm_execpath,如果不是yarn就输出警告,且进程结束。

1
2
3
4
5
6
7
js复制代码// scripts/checkYarn.js
if (!/yarn\.js$/.test(process.env.npm_execpath || '')) {
console.warn(
'\u001b[33mThis repository requires Yarn 1.x for scripts to work properly.\u001b[39m\n'
)
process.exit(1)
}

如果你想忽略这个前置的钩子判断,可以使用yarn --ignore-scripts 命令。也有后置的钩子post。更多详细的可以查看 npm 文档

2.2 调试 vue-next/scripts/release.js 文件

接着我们来学习如何调试 vue-next/scripts/release.js文件。

这里声明下我的 VSCode 版本 是 1.59.0 应该 1.50.0 起就可以按以下步骤调试了。

1
2
bash复制代码code -v
# 1.59.0

找到 vue-next/package.json 文件打开,然后在 scripts 上方,会有debug(调试)按钮,点击后,选择 release。即可进入调试模式。

debugger

这时终端会如下图所示,有 Debugger attached. 输出。这时放张图。

terminal

更多 nodejs 调试相关 可以查看官方文档

学会调试后,先大致走一遍流程,在关键地方多打上几个断点多走几遍,就能猜测到源码意图了。

3 文件开头的一些依赖引入和函数声明

我们可以跟着断点来,先看文件开头的一些依赖引入和函数声明

3.1 第一部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
js复制代码// vue-next/scripts/release.js
const args = require('minimist')(process.argv.slice(2))
// 文件模块
const fs = require('fs')
// 路径
const path = require('path')
// 控制台
const chalk = require('chalk')
const semver = require('semver')
const currentVersion = require('../package.json').version
const { prompt } = require('enquirer')

// 执行子进程命令 简单说 就是在终端命令行执行 命令
const execa = require('execa')

通过依赖,我们可以在 node_modules 找到对应安装的依赖。也可以找到其README和github仓库。

3.1.1 minimist 命令行参数解析

minimist

简单说,这个库,就是解析命令行参数的。看例子,我们比较容易看懂传参和解析结果。

1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码$ node example/parse.js -a beep -b boop
{ _: [], a: 'beep', b: 'boop' }

$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
{ _: [ 'foo', 'bar', 'baz' ],
x: 3,
y: 4,
n: 5,
a: true,
b: true,
c: true,
beep: 'boop' }
1
js复制代码const args = require('minimist')(process.argv.slice(2))

其中process.argv的第一和第二个元素是Node可执行文件和被执行JavaScript文件的完全限定的文件系统路径,无论你是否这样输入他们。

3.1.2 chalk 终端多色彩输出

chalk

简单说,这个是用于终端显示多色彩输出。

3.1.3 semver 语义化版本

semver

语义化版本的nodejs实现,用于版本校验比较等。关于语义化版本可以看这个语义化版本 2.0.0 文档

版本格式:主版本号.次版本号.修订号,版本号递增规则如下:

主版本号:当你做了不兼容的 API 修改,

次版本号:当你做了向下兼容的功能性新增,

修订号:当你做了向下兼容的问题修正。

先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

3.1.4 enquirer 交互式询问 CLI

简单说就是交互式询问用户输入。

enquirer

3.1.5 execa 执行命令

简单说就是执行命令的,类似我们自己在终端输入命令,比如 echo 若川。

execa

1
2
3
4
5
6
7
8
js复制代码// 例子
const execa = require('execa');

(async () => {
const {stdout} = await execa('echo', ['unicorns']);
console.log(stdout);
//=> 'unicorns'
})();

看完了第一部分,接着我们来看第二部分。

3.2 第二部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
js复制代码// vue-next/scripts/release.js

// 对应 yarn run release --preid=beta
// beta
const preId =
args.preid ||
(semver.prerelease(currentVersion) && semver.prerelease(currentVersion)[0])
// 对应 yarn run release --dry
// true
const isDryRun = args.dry
// 对应 yarn run release --skipTests
// true 跳过测试
const skipTests = args.skipTests
// 对应 yarn run release --skipBuild
// true
const skipBuild = args.skipBuild

// 读取 packages 文件夹,过滤掉 不是 .ts文件 结尾 并且不是 . 开头的文件夹
const packages = fs
.readdirSync(path.resolve(__dirname, '../packages'))
.filter(p => !p.endsWith('.ts') && !p.startsWith('.'))

第二部分相对简单,继续看第三部分。

3.3 第三部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
js复制代码// vue-next/scripts/release.js

// 跳过的包
const skippedPackages = []

// 版本递增
const versionIncrements = [
'patch',
'minor',
'major',
...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : [])
]

const inc = i => semver.inc(currentVersion, i, preId)

这一块可能不是很好理解。inc是生成一个版本。更多可以查看semver文档

1
2
js复制代码semver.inc('3.2.4', 'prerelease', 'beta')
// 3.2.5-beta.0

3.4 第四部分

第四部分声明了一些执行脚本函数等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
js复制代码// vue-next/scripts/release.js

// 获取 bin 命令
const bin = name => path.resolve(__dirname, '../node_modules/.bin/' + name)
const run = (bin, args, opts = {}) =>
execa(bin, args, { stdio: 'inherit', ...opts })
const dryRun = (bin, args, opts = {}) =>
console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)
const runIfNotDry = isDryRun ? dryRun : run

// 获取包的路径
const getPkgRoot = pkg => path.resolve(__dirname, '../packages/' + pkg)

// 控制台输出
const step = msg => console.log(chalk.cyan(msg))

3.4.1 bin 函数

获取 node_modules/.bin/ 目录下的命令,整个文件就用了一次。

1
js复制代码bin('jest')

相当于在命令终端,项目根目录 运行 ./node_modules/.bin/jest 命令。

3.4.2 run、dryRun、runIfNotDry

1
2
3
4
5
js复制代码const run = (bin, args, opts = {}) =>
execa(bin, args, { stdio: 'inherit', ...opts })
const dryRun = (bin, args, opts = {}) =>
console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)
const runIfNotDry = isDryRun ? dryRun : run

run 真实在终端跑命令,比如 yarn build --release

dryRun 则是不跑,只是 console.log(); 打印 ‘yarn build –release’

runIfNotDry 如果不是空跑就执行命令。isDryRun 参数是通过控制台输入的。yarn run release --dry这样就是true。runIfNotDry就是只是打印,不执行命令。这样设计的好处在于,可以有时不想直接提交,要先看看执行命令的结果。不得不说,尤大就是会玩。

在 main 函数末尾,也可以看到类似的提示。可以用git diff先看看文件修改。

1
2
3
js复制代码if (isDryRun) {
console.log(`\nDry run finished - run git diff to see package changes.`)
}

看完了文件开头的一些依赖引入和函数声明等,我们接着来看main主入口函数。

4 main 主流程

第4节,主要都是main 函数拆解分析。

4.1 流程梳理 main 函数

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
js复制代码const chalk = require('chalk')
const step = msg => console.log(chalk.cyan(msg))
// 前面一堆依赖引入和函数定义等
async function main(){
// 版本校验

// run tests before release
step('\nRunning tests...')
// update all package versions and inter-dependencies
step('\nUpdating cross dependencies...')
// build all packages with types
step('\nBuilding all packages...')

// generate changelog
step('\nCommitting changes...')

// publish packages
step('\nPublishing packages...')

// push to GitHub
step('\nPushing to GitHub...')
}

main().catch(err => {
console.error(err)
})

上面的main函数省略了很多具体函数实现。接下来我们拆解 main 函数。

4.2 确认要发布的版本

第一段代码虽然比较长,但是还好理解。
主要就是确认要发布的版本。

调试时,我们看下这段的两张截图,就好理解啦。

终端输出选择版本号

终端输入确认版本号

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
js复制代码// 根据上文 mini 这句代码意思是 yarn run release 3.2.4 
// 取到参数 3.2.4
let targetVersion = args._[0]

if (!targetVersion) {
// no explicit version, offer suggestions
const { release } = await prompt({
type: 'select',
name: 'release',
message: 'Select release type',
choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom'])
})

// 选自定义
if (release === 'custom') {
targetVersion = (
await prompt({
type: 'input',
name: 'version',
message: 'Input custom version',
initial: currentVersion
})
).version
} else {
// 取到括号里的版本号
targetVersion = release.match(/\((.*)\)/)[1]
}
}

// 校验 版本是否符合 规范
if (!semver.valid(targetVersion)) {
throw new Error(`invalid target version: ${targetVersion}`)
}

// 确认要 release
const { yes } = await prompt({
type: 'confirm',
name: 'yes',
message: `Releasing v${targetVersion}. Confirm?`
})

// false 直接返回
if (!yes) {
return
}

4.3 执行测试用例

1
2
3
4
5
6
7
8
js复制代码// run tests before release
step('\nRunning tests...')
if (!skipTests && !isDryRun) {
await run(bin('jest'), ['--clearCache'])
await run('yarn', ['test', '--bail'])
} else {
console.log(`(skipped)`)
}

4.4 更新所有包的版本号和内部 vue 相关依赖版本号

这一部分,就是更新根目录下package.json 的版本号和所有 packages 的版本号。

1
2
3
js复制代码// update all package versions and inter-dependencies
step('\nUpdating cross dependencies...')
updateVersions(targetVersion)
1
2
3
4
5
6
js复制代码function updateVersions(version) {
// 1. update root package.json
updatePackage(path.resolve(__dirname, '..'), version)
// 2. update all packages
packages.forEach(p => updatePackage(getPkgRoot(p), version))
}

4.4.1 updatePackage 更新包的版本号

1
2
3
4
5
6
7
8
js复制代码function updatePackage(pkgRoot, version) {
const pkgPath = path.resolve(pkgRoot, 'package.json')
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
pkg.version = version
updateDeps(pkg, 'dependencies', version)
updateDeps(pkg, 'peerDependencies', version)
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
}

主要就是三种修改。

1
2
3
bash复制代码1. 自己本身 package.json 的版本号
2. packages.json 中 dependencies 中 vue 相关的依赖修改
3. packages.json 中 peerDependencies 中 vue 相关的依赖修改

一图胜千言。我们执行yarn release --dry 后 git diff 查看的 git 修改,部分截图如下。

更新的版本号举例

4.4.2 updateDeps 更新内部 vue 相关依赖的版本号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
js复制代码function updateDeps(pkg, depType, version) {
const deps = pkg[depType]
if (!deps) return
Object.keys(deps).forEach(dep => {
if (
dep === 'vue' ||
(dep.startsWith('@vue') && packages.includes(dep.replace(/^@vue\//, '')))
) {
console.log(
chalk.yellow(`${pkg.name} -> ${depType} -> ${dep}@${version}`)
)
deps[dep] = version
}
})
}

一图胜千言。我们在终端执行yarn release --dry。会看到这样是输出。

更新 Vue 相关依赖的终端输出

也就是这句代码输出的。

1
2
3
js复制代码console.log(
chalk.yellow(`${pkg.name} -> ${depType} -> ${dep}@${version}`)
)

4.5 打包编译所有包

1
2
3
4
5
6
7
8
9
10
js复制代码// build all packages with types
step('\nBuilding all packages...')
if (!skipBuild && !isDryRun) {
await run('yarn', ['build', '--release'])
// test generated dts files
step('\nVerifying type declarations...')
await run('yarn', ['test-dts-only'])
} else {
console.log(`(skipped)`)
}

4.6 生成 changelog

1
2
js复制代码// generate changelog
await run(`yarn`, ['changelog'])

yarn changelog 对应的脚本是conventional-changelog -p angular -i CHANGELOG.md -s。

4.7 提交代码

经过更新版本号后,有文件改动,于是git diff。
是否有文件改动,如果有提交。

git add -A
git commit -m 'release: v${targetVersion}'

1
2
3
4
5
6
7
8
js复制代码const { stdout } = await run('git', ['diff'], { stdio: 'pipe' })
if (stdout) {
step('\nCommitting changes...')
await runIfNotDry('git', ['add', '-A'])
await runIfNotDry('git', ['commit', '-m', `release: v${targetVersion}`])
} else {
console.log('No changes to commit.')
}

4.8 发布包

1
2
3
4
5
js复制代码// publish packages
step('\nPublishing packages...')
for (const pkg of packages) {
await publishPackage(pkg, targetVersion, runIfNotDry)
}

这段函数比较长,可以不用细看,简单说就是 yarn publish 发布包。
我们 yarn release --dry后,这块函数在终端输出的如下:

发布终端输出命令

值得一提的是,如果是 vue 默认有个 tag 为 next。当 Vue 3.x 是默认时删除。

1
2
3
4
js复制代码} else if (pkgName === 'vue') {
// TODO remove when 3.x becomes default
releaseTag = 'next'
}

也就是为什么我们现在安装 vue3 还是 npm i vue@next命令。

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
js复制代码async function publishPackage(pkgName, version, runIfNotDry) {
// 如果在 跳过包里 则跳过
if (skippedPackages.includes(pkgName)) {
return
}
const pkgRoot = getPkgRoot(pkgName)
const pkgPath = path.resolve(pkgRoot, 'package.json')
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
if (pkg.private) {
return
}

// For now, all 3.x packages except "vue" can be published as
// `latest`, whereas "vue" will be published under the "next" tag.
let releaseTag = null
if (args.tag) {
releaseTag = args.tag
} else if (version.includes('alpha')) {
releaseTag = 'alpha'
} else if (version.includes('beta')) {
releaseTag = 'beta'
} else if (version.includes('rc')) {
releaseTag = 'rc'
} else if (pkgName === 'vue') {
// TODO remove when 3.x becomes default
releaseTag = 'next'
}

// TODO use inferred release channel after official 3.0 release
// const releaseTag = semver.prerelease(version)[0] || null

step(`Publishing ${pkgName}...`)
try {
await runIfNotDry(
'yarn',
[
'publish',
'--new-version',
version,
...(releaseTag ? ['--tag', releaseTag] : []),
'--access',
'public'
],
{
cwd: pkgRoot,
stdio: 'pipe'
}
)
console.log(chalk.green(`Successfully published ${pkgName}@${version}`))
} catch (e) {
if (e.stderr.match(/previously published/)) {
console.log(chalk.red(`Skipping already published: ${pkgName}`))
} else {
throw e
}
}
}

4.9 推送到 github

1
2
3
4
5
6
7
8
js复制代码// push to GitHub
step('\nPushing to GitHub...')
// 打 tag
await runIfNotDry('git', ['tag', `v${targetVersion}`])
// 推送 tag
await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
// git push 所有改动到 远程 - github
await runIfNotDry('git', ['push'])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
js复制代码// yarn run release --dry

// 如果传了这个参数则输出 可以用 git diff 看看更改

// const isDryRun = args.dry
if (isDryRun) {
console.log(`\nDry run finished - run git diff to see package changes.`)
}

// 如果 跳过的包,则输出以下这些包没有发布。不过代码 `skippedPackages` 里是没有包。
// 所以这段代码也不会执行。
// 我们习惯写 arr.length !== 0 其实 0 就是 false 。可以不写。
if (skippedPackages.length) {
console.log(
chalk.yellow(
`The following packages are skipped and NOT published:\n- ${skippedPackages.join(
'\n- '
)}`
)
)
}
console.log()

我们 yarn release --dry后,这块函数在终端输出的如下:

发布到github

到这里我们就拆解分析完 main 函数了。

整个流程很清晰。

1
2
3
4
5
6
7
8
9
10
bash复制代码1. 确认要发布的版本
2. 执行测试用例
3. 更新所有包的版本号和内部 vue 相关依赖版本号
3.1 updatePackage 更新包的版本号
3.2 updateDeps 更新内部 vue 相关依赖的版本号
4. 打包编译所有包
5. 生成 changelog
6. 提交代码
7. 发布包
8. 推送到 github

用一张图总结则是:

vue 发布流程

看完vue-next/scripts/release.js,感兴趣还可以看vue-next/scripts文件夹下其他代码,相对行数不多,但收益较大。

  1. 总结

通过本文学习,我们学会了这些。

1
2
3
bash复制代码1. 熟悉 vuejs 发布流程
2. 学会调试 nodejs 代码
3. 动手优化公司项目发布流程

同时建议自己动手用 VSCode 多调试,在终端多执行几次,多理解消化。

vuejs发布的文件很多代码我们可以直接复制粘贴修改,优化我们自己发布的流程。比如写小程序,相对可能发布频繁,完全可以使用这套代码,配合miniprogram-ci,再加上一些自定义,加以优化。

关于小程序 ci 上传,再分享两篇文章。

基于 CI 实现微信小程序的持续构建

小打卡小程序自动化构建及发布的工程化实践 虽然文章里不是最新的 miniprogram-ci,但这篇场景写得比较全面。

当然版本发布也可以用开源的 release-it。

同时,我们可以:

引入 git flow,管理git分支。估计很多人不知道windows git bash已经默认支持 git flow命令。

引入 husky 和 lint-staged 提交commit时用ESLint等校验代码提交是否能够通过检测。

引入 单元测试 jest,测试关键的工具函数等。

引入 conventional-changelog

引入 git-cz 交互式git commit。

等等规范自己项目的流程。如果一个候选人,通过看vuejs发布的源码,积极主动优化自己项目。我觉得面试官会认为这个候选人比较加分。

看开源项目源码的好处在于:一方面可以拓展视野,另外一方面可以为自己所用,收益相对较高。

关于

最后可以持续关注我@若川。我倾力持续组织了一年每周大家一起学习200行左右的源码共读活动,感兴趣的可以点此扫码加我微信 ruochuan02 参与。

另外,想学源码,极力推荐关注我写的专栏《学习源码整体架构系列》,目前是掘金关注人数(4.8k+人)第一的专栏,写有20余篇源码文章。包含jQuery、underscore、lodash、vuex、sentry、axios、redux、koa、vue-devtools、vuex4、koa-compose、vue 3.2 发布、vue-this、create-vue、玩具vite、create-vite 等20余篇源码文章。


作者:常以若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。

公众号若川视野,致力于帮助5年内前端成长

若川的博客

segmentfault若川视野专栏,开通了若川视野专栏,欢迎关注~

掘金专栏,欢迎关注~

知乎若川视野专栏,开通了若川视野专栏,欢迎关注~

github blog,求个star^_^~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

impala、hive、hbase对比(主要是impala

发表于 2021-08-19

这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战
​

1. 什么是实时分析(在线查询)系统?

大数据领域里面,实时分析(在线查询)系统是最常见的一种场景,通常用于客户投诉处理,实时数据分析,在线查询等等过。因为是查询应用,通常有以下特点:

a. 时延低(秒级别)。

b. 查询条件复杂(多个维度,维度不固定),有简单(带有ID)。

c. 查询范围大(通常查询表记录在几十亿级别)。

d. 返回结果数小(几十条甚至几千条)。

e. 并发数要求高(几百上千同时并发)。

f. 支持SQL(这个业界基本上达成共识了,原因是很难找到一个又会数据分析,还能写JAVA代码的分析工程师)。

传统上,常常使用数据仓库来承担这一任务,数据仓库通过创建索引来应对多维度复杂查询。传统数据仓库也存在很明显的缺点,扩展性不强,索引创建成本高,索引易失效等等。 当查询条件复杂时,传统领域和hadoop目前都没有一个特别好的解决方案。维度如果不固定,就无法创建索引或者索引代价太高,通常只能通过全盘暴力SCAN的方法来解决。

目前来完美解决实时分析的系统还在探索中,下面来讲讲hadoop领域几种常见的解决方案

2. Hive

)​

一句话描述Hive: hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供完整的sql查询功能,可以将sql语句转换为MapReduce任务进行运行。Hive支持HSQL,是一种类SQL。

也真是由于这种机制导致Hive最大的缺点是慢。Map/reduce调度本身只适合批量,长周期任务,类似查询这种要求短平快的业务,代价太高。

Map/reduce为什么只适合批量任务,这里不解释,建议大家看下相关原理,业界对这快的分析比较多,由此也诞生了spark等一系列解决方案。

3. Hbase

HBase是一个分布式的、面向列的开源数据库,该技术来源于Chang et al所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统(File System)所提供的分布式数据存储一样,HBase在Hadoop之上提供了类似于Bigtable的能力。HBase是Apache的Hadoop项目的子项目。HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。另一个不同的是HBase基于列的而不是基于行的模式。

)​

Hbase核心是将数据抽象成表,表中只有rowkey和column family。Rowkey是记录的主键,通过key /value很容易找到。Colum family中存储实际的数据。仅能通过主键(row key)和主键的range来检索数据,仅支持单行事务(可通过hive支持来实现多表join等复杂操作)。主要用来存储非结构化和半结构化的松散数据。

正是由于Hbase这种结构,应对查询中带了主键(use id)的应用非常有效果,查询结果返回速度非常快。对没有带主键,通过多个维度来查询时,就非常困难。业界为了解决这个问题,在上面实现了一些技术方案,效果也基本差强人意:

a. 华为的二级索引,核心思路仿照数据库建索引方式对需要查询的列建索引,带来的问题时影响加载速度,数据膨胀率大,二级索引不能建太多,最多1~2个。

b. Hbase自身的协处理器,碰到不带rowkey的查询,由协处理器,通过线程并行扫描。

c. Hbase上的Phoniex,Phoniex 可以让开发者在HBase数据集上使用SQL查询。Phoenix查询引擎会将SQL查询转换为一个或多个HBase scan,并编排执行以生成标准的JDBC结果集,对于简单查询来说,性能甚至胜过Hive。

4. Impala

)​

Impala是Cloudera在受到Google的Dremel启发下开发的实时交互SQL大数据查询工具,Impala没有再使用缓慢的Hive+MapReduce批处理,而是通过使用与商用并行关系数据库中类似的分布式查询引擎(由Query Planner、Query Coordinator和Query Exec Engine三部分组成),可以直接从HDFS或HBase中用SELECT、JOIN和统计函数查询数据,从而大大降低了延迟。其架构如图 1所示,Impala主要由Impalad, State Store和CLI组成。

Impalad: 与DataNode运行在同一节点上,由Impalad进程表示,它接收客户端的查询请求(接收查询请求的Impalad为Coordinator,Coordinator通过JNI调用java前端解释SQL查询语句,生成查询计划树,再通过调度器把执行计划分发给具有相应数据的其它Impalad进行执行),读写数据,并行执行查询,并把结果通过网络流式的传送回给Coordinator,由Coordinator返回给客户端。同时Impalad也与State Store保持连接,用于确定哪个Impalad是健康和可以接受新的工作。在Impalad中启动三个ThriftServer: beeswax_server(连接客户端),hs2_server(借用Hive元数据), be_server(Impalad内部使用)和一个ImpalaServer服务。

Impala State Store: 跟踪集群中的Impalad的健康状态及位置信息,由statestored进程表示,它通过创建多个线程来处理Impalad的注册订阅和与各Impalad保持心跳连接,各Impalad都会缓存一份State Store中的信息,当State Store离线后(Impalad发现State Store处于离线时,会进入recovery模式,反复注册,当State Store重新加入集群后,自动恢复正常,更新缓存数据)因为Impalad有State Store的缓存仍然可以工作,但会因为有些Impalad失效了,而已缓存数据无法更新,导致把执行计划分配给了失效的Impalad,导致查询失败。

CLI: 提供给用户查询使用的命令行工具(Impala Shell使用python实现),同时Impala还提供了Hue,JDBC, ODBC使用接口。

Impala架构类似分布式数据库Greenplum数据库,一个大的查询通过分析为一一个子查询,分布到底层的执行,最后再合并结果,说白了就是通过多线程并发来暴力SCAN来实现高速。

架构是完美的,现实是骨感的,实际使用过程中,Impala性能和稳定性还差得远。尤其是Impala虽然号称支持HDFS和HBASE,但实际使用中发现,运行在HDFS上,性能还差强人意,运行在HBASE上性能很差,另外还经常有内存溢出之类的问题尚待解决。

5. 结语

目前来看,业界还没有一个完美的解决方案,通常的思路有:

a. 提前根据查询结果来组织数据。每种业务都是不同的,要想查询得快,就要提前分析场景,在数据入库时,就提前根据查询结果来组织数据。这也是微博等应用的做法,根据显示结果提前存储数据。

b. 对不固定维度的,多维度查询,目前来看hadoop和传统的并行数据库架构上会有一个融合的过程,相信最后会殊途同归,Impala还是有前途的。

c. 多查询引擎的融合,通常我们希望一份数据,可以承担多种应用,既可以承担直接带用户id的快速查询,也系统可以搞定多维度的复杂分析,所以要支持多种应用,多查询引擎的特点融合不可以避免。希望后面impala可以解决在habase上性能不高的问题。

d. 用高速硬件加速,flash卡目前越来越便宜,将需要高速查询的数据换成到flash等高速硬件上。


impala vs hive

1.Impala为什么比hive速度快

Impala自称数据查询效率比hive快几倍甚至数十倍,它之所以这么快的原因大致有以下几点:

  • 真正的MPP查询引擎
  • 使用C++开发而不是Java,降低运行负荷
  • 运行时生成代码(LLVM IR),提高效率。

)​

  • 全新的执行引擎(不是mapreduce)。
  • 在执行sql语句的时候,impala不会把中间数据写入到磁盘,而是在内存中完成所有的处理。
  • 使用impala的时候,查询任务会马上执行而不是生成mapreduce任务,这会节约大量的初始化时间。
  • Impala查询计划解析器使用更智能的算法在多节点上分布式执行各个查询步骤,同时避免了sorting和shuffle这两个非常耗时的阶段,这两个阶段是不需要的。
  • Impala拥有HDFS上面各个data block的信息,当它处理查询的时候能够在各个datanode上面更均衡的分发查询。
  • 另外一个关键原因,impala为每个查询产生汇编级的代码,当impala在本地内存中运行的时候,这些汇编代码执行效率比其他任何代码框架更快,因为代码框架会增加额外的延迟。

2.与Hive的关系

Impala与Hive都是构建在Hadoop之上的数据查询工具各有不同的侧重适应面,但从客户端使用来看Impala与Hive有很多的共同之处,如数据表元数据、ODBC/JDBC驱动、SQL语法、灵活的文件格式、存储资源池等。Impala与Hive在Hadoop中的关系如图 2所示。Hive适合于长时间的批处理查询分析,而Impala适合于实时交互式SQL查询,Impala给数据分析人员提供了快速实验、验证想法的大数据分析工具。可以先使用hive进行数据转换处理,之后使用Impala在Hive处理后的结果数据集上进行快速的数据分析。

)​

3. Impala的查询处理过程

  Impalad分为Java前端与C++处理后端,接受客户端连接的Impalad即作为这次查询的Coordinator,Coordinator通过JNI调用Java前端对用户的查询SQL进行分析生成执行计划树,不同的操作对应不用的PlanNode, 如:SelectNode, ScanNode, SortNode, AggregationNode, HashJoinNode等等。

  执行计划树的每个原子操作由一个PlanFragment表示,通常一条查询语句由多个Plan Fragment组成, Plan Fragment 0表示执行树的根,汇聚结果返回给用户,执行树的叶子结点一般是Scan操作,分布式并行执行。

Java前端产生的执行计划树以Thrift数据格式返回给Impala C++后端(Coordinator)(执行计划分为多个阶段,每一个阶段叫做一个PlanFragment,每一个PlanFragment在执行时可以由多个Impalad实例并行执行(有些PlanFragment只能由一个Impalad实例执行,如聚合操作),整个执行计划为一执行计划树),由Coordinator根据执行计划,数据存储信息(Impala通过libhdfs与HDFS进行交互。通过hdfsGetHosts方法获得文件数据块所在节点的位置信息),通过调度器(现在只有simple-scheduler, 使用round-robin算法)Coordinator::Exec对生成的执行计划树分配给相应的后端执行器Impalad执行(查询会使用LLVM进行代码生成,编译,执行。对于使用LLVM如何提高性能这里有说明),通过调用GetNext()方法获取计算结果,如果是insert语句,则将计算结果通过libhdfs写回HDFS当所有输入数据被消耗光,执行结束,之后注销此次查询服务。  

Impala的查询处理流程大概如图所示:

)​

下面以一个SQL查询语句为例分析Impala的查询处理流程。如select sum(id), count(id), avg(id) from customer_small group by id; 以此语句生成的计划为:

PLAN FRAGMENT 0 PARTITION: UNPARTITIONED 4:EXCHANGE tuple ids: 1PLAN FRAGMENT 1 PARTITION: HASH_PARTITIONED: <slot 1> STREAM DATA SINK EXCHANGE ID: 4 UNPARTITIONED 3:AGGREGATE

执行行计划树如图所示, 绿色的部分为可以分布式并行执行:

)​

4. Impala相对于Hive所使用的优化技术

1)、没有使用MapReduce进行并行计算,虽然MapReduce是非常好的并行计算框架,但它更多的面向批处理模式,而不是面向交互式的SQL执行。与MapReduce相比:Impala把整个查询分成一执行计划树,而不是一连串的MapReduce任务,在分发执行计划后,Impala使用拉式获取数据的方式获取结果,把结果数据组成按执行树流式传递汇集,减少的了把中间结果写入磁盘的步骤,再从磁盘读取数据的开销。Impala使用服务的方式避免每次执行查询都需要启动的开销,即相比Hive没了MapReduce启动时间。

2)、使用LLVM产生运行代码,针对特定查询生成特定代码,同时使用Inline的方式减少函数调用的开销,加快执行效率。

3)、充分利用可用的硬件指令(SSE4.2)。

4)、更好的IO调度,Impala知道数据块所在的磁盘位置能够更好的利用多磁盘的优势,同时Impala支持直接数据块读取和本地代码计算checksum。

5)、通过选择合适的数据存储格式可以得到最好的性能(Impala支持多种存储格式)。

6)、最大使用内存,中间结果不写磁盘,及时通过网络以stream的方式传递。

5. Impala与Hive的异同

相同点:

数据存储:使用相同的存储数据池都支持把数据存储于HDFS, HBase。

元数据:两者使用相同的元数据。

SQL解释处理:比较相似都是通过词法分析生成执行计划。

不同点:

执行计划:

Hive: 依赖于MapReduce执行框架,执行计划分成map->shuffle->reduce->map->shuffle->reduce…的模型。如果一个Query会被编译成多轮MapReduce,则会有更多的写中间结果。由于MapReduce执行框架本身的特点,过多的中间过程会增加整个Query的执行时间。

Impala: 把执行计划表现为一棵完整的执行计划树,可以更自然地分发执行计划到各个Impalad执行查询,而不用像Hive那样把它组合成管道型的map->reduce模式,以此保证Impala有更好的并发性和避免不必要的中间sort与shuffle。

数据流:

Hive: 采用推的方式,每一个计算节点计算完成后将数据主动推给后续节点。

Impala: 采用拉的方式,后续节点通过getNext主动向前面节点要数据,以此方式数据可以流式的返回给客户端,且只要有1条数据被处理完,就可以立即展现出来,而不用等到全部处理完成,更符合SQL交互式查询使用。

内存使用:

Hive: 在执行过程中如果内存放不下所有数据,则会使用外存,以保证Query能顺序执行完。每一轮MapReduce结束,中间结果也会写入HDFS中,同样由于MapReduce执行架构的特性,shuffle过程也会有写本地磁盘的操作。

Impala: 在遇到内存放不下数据时,当前版本1.0.1是直接返回错误,而不会利用外存,以后版本应该会进行改进。这使用得Impala目前处理Query会受到一定的限制,最好还是与Hive配合使用。Impala在多个阶段之间利用网络传输数据,在执行过程不会有写磁盘的操作(insert除外)。

调度:

Hive: 任务调度依赖于Hadoop的调度策略。

Impala: 调度由自己完成,目前只有一种调度器simple-schedule,它会尽量满足数据的局部性,扫描数据的进程尽量靠近数据本身所在的物理机器。调度器目前还比较简单,在SimpleScheduler::GetBackend中可以看到,现在还没有考虑负载,网络IO状况等因素进行调度。但目前Impala已经有对执行过程的性能统计分析,应该以后版本会利用这些统计信息进行调度吧。

容错:

Hive: 依赖于Hadoop的容错能力。

Impala: 在查询过程中,没有容错逻辑,如果在执行过程中发生故障,则直接返回错误(这与Impala的设计有关,因为Impala定位于实时查询,一次查询失败,再查一次就好了,再查一次的成本很低)。但从整体来看,Impala是能很好的容错,所有的Impalad是对等的结构,用户可以向任何一个Impalad提交查询,如果一个Impalad失效,其上正在运行的所有Query都将失败,但用户可以重新提交查询由其它Impalad代替执行,不会影响服务。对于State Store目前只有一个,但当State Store失效,也不会影响服务,每个Impalad都缓存了State Store的信息,只是不能再更新集群状态,有可能会把执行任务分配给已经失效的Impalad执行,导致本次Query失败。

适用面(应用场景):

Hive: 复杂的批处理查询任务,数据转换任务。

Impala:实时数据分析,因为不支持UDF,能处理的问题域有一定的限制,与Hive配合使用,对Hive的结果数据集进行实时分析。

6. Impala的优缺点

优点:

支持SQL查询,快速查询大数据。

可以对已有数据进行查询,减少数据的加载,转换。

多种存储格式可以选择(Parquet, Text, Avro, RCFile, SequeenceFile)。

可以与Hive配合使用。

缺点:

不支持用户定义函数UDF。

不支持text域的全文搜索。

不支持Transforms。

不支持查询期的容错。

对内存要求高。

​

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

TCP 粘包/拆包的原因及解决方法? 粘包/拆包的原因 粘包

发表于 2021-08-18

本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力。

TCP粘包、拆包属于网络底层问题,在数据链路层、网络层、传输层都有可能出现。日常的网络应用开发大多数在传输层出现,而UDP是由消息保护边界的,不会发生粘包、拆包问题,只发生在TCP协议中。假设客户端向服务端发送了两个连续的数据包Packet1、Packet2;

在这个过程中可能会出现3种情况:

  • 正常:两个数据包逐一分开发送
  • 粘包:两个包一同发送,
  • 拆包:Server接收到不完整的或多出一部分的数据包

如下图所示:

粘包/拆包的原因

发生TCP粘包或拆包有很多原因,现列出常见的几点:

  1. 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
  2. 待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
  3. 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
  4. 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

粘包、拆包解决办法

TCP本身是面向流的,作为网络服务器,如何从这源源不断涌来的数据流中拆分出或者合并出有意义的信息呢?通常会有以下一些常用的方法:

  1. 发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
  2. 发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
  3. 可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

举个例子

下面是我之前一个项目的数据包头仅供参考:

消息头定义

序号 字段 长度(字节) 说明
1 headFlag 4 0-3 标志位:固定四个字符‘AABB’ 掘金社区:固定 XTXT
2 version 2 4-5 主版本.次版本,各一个字节 掘金社区:主版本.次版本00 01
3 packetNo 4 6-9 无符号整数,1-int.Max之间循环,应答必须和请求包号相同
4 length 4 10-13 数据区data长度
5 direction 1 14 0:请求,1:应答
6 command 2 15-16 采用接口编号:1-66536
7 data N 17-length+16 length 指定长度,JSON格式,编码采用UTF-8
8 crc16 4 length+17 - 18 从 headFlag到data(包括)所有数据crc16(ccitt-xmodem)校验

其实这个设计就是用到了解决方法中的方法 1 的方案:

首先我将设置一个消息头的标志为固定的 4 个字节长度,如 “XTXT” 表示掘金社区,然后固定前面 17 位按照顺序分别有:标识符、版本号、流水号、数据长度、传输方向、数据包类型。最后为数据区段和最后一位 CRC16 校验码。

下面是拆包的逻辑,代码比较简陋:

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
ini复制代码try {
// TODO 待优化
byte[] head = new byte[17];
in.readBytes(head);

byte[] h = new byte[4];
System.arraycopy(head, 0, h, 0, 4);

// flag 4
String f = new String(h, "UTF-8");
// version 2
short v = ByteUtil.byteArrayToShort(new byte[]{head[4], head[5]});
// serial number 4
int no = ByteUtil.byteArrayToInt(new byte[]{head[6], head[7], head[8], head[9]});
// length 4
int len = ByteUtil.byteArrayToInt(new byte[]{head[10], head[11], head[12], head[13]});
// direction
byte d = head[14];
// command
short com = ByteUtil.byteArrayToShort(new byte[]{head[15], head[16]});

byte[] content = new byte[len];
in.readBytes(content);

int cc = in.readInt();
byte[] packet = new byte[17 + len];
System.arraycopy(head, 0, packet, 0, head.length);
System.arraycopy(content, 0, packet, 17, len);
int i = CRC16.crc16CcittXmodem(packet);
if (cc != i) {
logger.error("decode crc16 fail {}", cc);
}

// 校验包
MessageProtocol protocol = new MessageProtocol();
protocol.setHeadFlag(f);
protocol.setVersion(v);
protocol.setPacketNo(no);
protocol.setLength(len);
protocol.setDirection(d);
protocol.setCommand(com);
protocol.setData(new String(content, "UTF-8"));
protocol.setCrc16(cc);

logger.info("encode MessageProtocol:{}", protocol);

out.add(protocol);
} catch (Throwable t) {
logger.error("decode fail", t);
throw t;
}

参考资料

  • blog.csdn.net/weixin_4499…
  • github.com/zhengsh/ssm…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MySQL面试:left join我要怎优化? MRR

发表于 2021-08-18

在实际开发中,相信大多数人都会用到join进行连表查询,但是有些人发现,用join好像效率很低,而且驱动表不同,执行时间也不同。那么join到底是如何执行的呢?

这里有两个表t1,t2

1
mysql复制代码explain select * from t1 left join t2 on t1.a=t2.a;

上面语句使用left join,说明t1是驱动表(left join谁在左谁是驱动表),t2是被驱动表,执行一下

image.png

可以看到,驱动表是的type是ALL,所以是全表扫描,被驱动表有a索引,left join的时候,用到了a这个索引,因此这个语句执行流程是:

  1. 从表t1中读入一行数据
  2. 从数据行中,取出a字段到表t2里去查找
  3. 取出表t2中满足条件的行,跟t1的数据组成一行,作为结果集的一部分
  4. 重复上面操作,直到t1的数据取完为止

这个过程是先遍历表t1,然后根据表t1中取出的每行数据中的a值,去表t2中查询满足条件的记录。这里我们成为”Index Nested-Loop Join”,简称NLJ。

image.png

通过上面所述,如果我们选择驱动表的话,就要选择小表来做驱动表。否则大表做驱动表是要查询所有的,效率会低很多。当然,前提是”可以使用被驱动表的索引”

这里我们把sql语句改一下:

1
mysql复制代码explain select * from t1 left join t2 on t1.a=t2.b;

t2表的b字段是无索引的

image.png
结果就是两个表都要全表扫描,这里我们看到,Extra显示的是(Using where; Using join buffer (Block Nested Loop))

这个其实是MySQL对join不走索引全表扫描做了一个优化,简称BNL。

BNL流程:

  1. 把表t1的数据读入线程内存join_buffer中,这里我们是把整个表t1放入内存中。
  2. 扫描表t2,把表t2中的每一行取出来,跟join_buffer中的数据做对比,满足join条件的,作为结果集的一部分返回。

image.png

这里,我们两个表都是做的全表扫描,所以不管是哪个表做驱动表都是执行消耗都是一样的。

如果一个表的数据太大了,根本装不下所有数据的话,就采用分段放。也可以修改join_buffer_size。

针对于有索引的被驱动表,MySQL5.6版本开始增加了Batched Key Access(BKA)的新特性

对于多表join语句,当MySQL使用索引访问第一个join表的时候,使用join_buffer来收集第一个操作对象生成的相关列的值。BKA构建好key之后,通过MRR接口提交给引擎做查询。

BKA步骤:

  1. 将驱动表相关的列放入join_buffer中。
  2. 批量的将Key(索引键值)发送到MRR接口。
  3. MRR通过收到的key,根据其对应的ROWID进行排序,然后再进行数据的读取操作。

这里来看,BKA和BNL其实是差不多的,主要区别就是BKA是针对被驱动表是走索引的情况下,索引是非主键索引的时候,按照索引字段进行排序,因此减少了随机IO,提高性能。

MRR

MySQL5.6版本开始支持的Multi-Range Read(MRR)优化。MRR目的是为了减少磁盘的随机访问,并且将随机访问转换为较为顺序的数据访问,MRR可适用于range,ref,eq_ref类型的查询

MRR优化有以下几个好处:

  1. MRR使数据访问变的较为顺序。在查询辅助索引时,首先根据得到的查询结果,按照主键进行排序,并按照主键排序的顺序进行书签查找
  2. 减少缓冲池中页被替换的次数
  3. 批量处理对键值的查询操作

MRR的设计思路是因为大多数的数据都是按照主键递增顺序插入得到的,所以我们可以认为,如果按照主键的递增顺序查询的话,对磁盘的读比较接近顺序读,能够提升读性能。

MRR 能够提升性能的核心在于,这条查询语句在索引 a 上做的是一个范围查询(也就是说,这是一个多值查询),可以得到足够多的主键 id。这样通过排序以后,再去主键索引查数据,才能体现出“顺序性”的优势。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

用了redis一定可以保证原子性吗 概念 库存问题 解决方式

发表于 2021-08-18

概念

什么原子性?程序在执行过程中,要么全部都执行,要么全部都不执行,不可能执行了一半,滞留了一半。我们知道redis是IO多路复用模型,即一个线程来处理多个TCP连接,这样的好处就是,即使客户端并发请求,也得排队处理,一定程度上解决了多线程模型带的并发问题,所以redis是并发安全的?从redis本身的架构模式来说,并发是安全的,不存在同时执行两个客户端的命令。但是如果因为某些业务场景用的有问题,那么即使是单线程的redis也无能为力。

库存问题

假设现在redis有个叫stock的key,用于存放商品的库存数据,只要进货了库存就加一,出货了库存就减一。于是这时候机智的程序员小王这样设计了一下,每次通过get获取stock的值,然后再set stock=stock+1。这时候有两个业务员A、B分别进货了,由于时间线的问题,程序大概是这样跑的:

image.png

  1. A先获取value=100
  2. B先获取value=100
  3. A设置value=101
  4. B设置value=101
    整个流程下来,发现库存丢了一个,这正是因为整个操作不是原子性的,导致A影响了B,或者B影响了A。

解决方式

原生的原子命令

incr是原子操作的,对于这种场景,可以不用获取原来的值,直接用redis的incr实现read和write的打包原子操作,就不会出现读了一半,然后被别人篡改了。真实场景可能不仅仅是这种库存问题,那么像批量设置多个值的场景可以用mset,批量获取多个值的mget,与incr相对应的decr,这些都是原子的。

单机锁or分布式锁

加锁可以解决,先抢占了锁的用户,可以去操作,没抢到锁的,自旋等待下次去获取锁,然后操作。如果不是分布式系统,可以用类似golang sync.mutex锁,如果是分布式系统得用分布式锁了,可以用类似zookeeper或者redis本身的setnx。

watch + 事务

结合watch和事务也可以解决,每个客户端可以通过watch来监控自己即将要更新的key,这样在事务更新的时候,如果发现自己监控的key被修改了,那么拒绝执行,事务执行失败,这样就不会存在更新覆盖的问题。watch的本质还是乐观锁,当客户端执行watch的时候,实际上是watch的key会维护一个客户端的队列,这样就知道这个key被哪些客户端监视了。当其中的一个客户端执行完毕之后,那么它会从这个队列中移除,并且会把这个列表中剩余的所有客户端的CLIENT_DIRTY_CAS标识打开,这样剩余的客户端在执行exec的时候,发现自己的CLIENT_DIRTY_CAS已经被打开,那么就会拒绝执行。

image.png

客户端1:

1
2
3
4
5
6
7
8
9
10
ruby复制代码127.0.0.1:6379> watch store
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET store 99
QUEUED
127.0.0.1:6379> EXEC
1) OK
127.0.0.1:6379> GET store
"99"

客户端2:

1
2
3
4
5
6
7
8
9
10
ruby复制代码127.0.0.1:6379> watch store
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET store 100
QUEUED
127.0.0.1:6379> EXEC
(nil)
127.0.0.1:6379> GET store
"99"

客户端1和客户端2都尝试来修改store的值,然而客户端2修改失败了。

lua脚本

即使redis支持很多原子命令,但是还是无法满足所有场景,于是redis在2.6之后开始支持开发者编写lua脚本传到redis中,使用lua脚本的好处就是:

  1. 减少网络开销,通过lua脚本可以一次性的将多个请求合并成一个请求。
  2. 原子操作,redis将lua脚本作为一个整体,执行过程中,不会被其他命令打断,不会出现竞态问题。
  3. 复用,客户端发送的lua脚本会永远存在redis服务中。
1
2
3
4
5
6
ruby复制代码127.0.0.1:6379> EVAL "redis.call('SET',KEYS[1],ARGV[1]);redis.call('EXPIRE',KEYS[1],ARGV[2]);return 1;" 1 name sun 60
(integer) 1
127.0.0.1:6379> get name
"sun"
127.0.0.1:6379> ttl name
(integer) 56

通过eval关键字指明导入lua,在redis收到lua指令时,会把整个lua指令作为一个整体,比如上面的set和expire之间不会有其他客户端请求的指令,一定是set和expire一起做完之后,其他的命令才能执行。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring Boot 集成 ElasticSearch,实

发表于 2021-08-18

这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战

作者:Tom哥

微信公众号:微观技术

1、ElasticSearch介绍

Elasticsearch 是java开发的,基于 Lucene 的搜索引擎。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful Web接口。Elasticsearch 可以快速有效地存储,搜索和分析大量数据,而且在处理半结构化数据(即自然语言)时特别有用。

应用集成Elasticsearch有4种方式:

  • REST Client
  • Jest
  • Spring Data
  • Spring Data Elasticsearch Repositories

本文主要介绍一下用Spring Data Elasticsearch Repositories 是如何使用的。该方式与spring boot高度集成,日常开发时较方便,只需要简单的配置即可开箱使用。

2、运行 Elasticsearch

为了便于测试,我们使用 Docker 镜像方式快速部署一个单节点的 Elasticsearch实例,容器启动时并绑定宿主机的9200和9300端口

拉取镜像:

1
复制代码docker pull elasticsearch:7.4.2

查看镜像:

1
复制代码docker images

创建宿主机挂载目录:

1
2
3
bash复制代码mkdir -p /mydata/elasticsearch/config/
mkdir -p /mydata/elasticsearch/data/
echo "http.host: 0.0.0.0">>/mydata/elasticsearch/config/elasticsearch.yml

运行容器:

1
2
3
4
5
6
bash复制代码docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e ES_JAVA_OPS="-Xms256m -Xmx256m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2

参数说明:

-p 9200:9200 将容器的9200端口映射到主机的9200端口;

–name elasticsearch 给当前启动的容器取名叫 elasticsearch

-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data 将数据文件夹挂载到主机; -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml 将配置文件挂载到主机;

-d 以后台方式运行(daemon)

-e ES_JAVA_OPS=”-Xms256m -Xmx256m” 测试时限定内存小一点

查看容器进程:

1
css复制代码docker ps -a

3、项目集成

按照Spring Boot的惯例,我们不必在上下文中提供任何bean来启用对Elasticsearch的支持。我们只需要在pom.xml中添加以下依赖项:

1
2
3
4
5
6
7
8
9
xml复制代码<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

由于spring-boot-starter-parent指定的版本号是2.2.1.RELEASE,所以上面引入的两个starter组件会被强制一样的版本号,便于统一化管理。而底层引入的 spring-data-elasticsearch 是3.2.1.RELEASE

在配置文件 application.yml 中配置 ES 的相关参数,应用程序尝试在localhost上与Elasticsearch连接,具体内容如下:

1
2
3
4
5
6
7
yaml复制代码spring:
  application:
    name: spring-boot-bulking-elasticsearch
  elasticsearch:
    rest:
      uris: 127.0.0.1:9200
      read-timeout: 5s

Spring Boot 操作 ES 数据有三种方式:

  • 实现 ElasticsearchRepository 接口
  • 引入 ElasticsearchRestTemplate
  • 引入 ElasticsearchOperations

使用Spring Data Elasticsearch Repositories操作 Elasticsearch,定义实体类,并设置对应的索引名

1
2
3
4
5
6
7
8
9
kotlin复制代码@Document(indexName = "order", type = "biz1", shards = 2)
public class OrderModel {

    @Id
    private Long orderId;
    private Double amount;
    private Long buyerUid;
    private String shippingAddress;
}

常用注解说明:

@Document:表示映射到Elasticsearch文档上的领域对象

@Id:表示是文档的id,文档可以认为是mysql中表行的概念

@Filed:文档中字段的类型、是否建立倒排索引、是否进行存储

OrderModel表示订单的索引模型,一个OrderModel对象表示一条ES索引记录。如果用关系数据库做参照,Index相当于表,Document相当于记录

然后,需要自己定义一个业务接口 OrderRepository,并继承扩展接口 ElasticsearchRepository

1
2
csharp复制代码public interface OrderRepository extends ElasticsearchRepository<OrderModel, Long> {
}

ElasticsearchRepository 是 Spring boot Elasticsearch 框架预留的扩展接口,内部的类依赖关系如下图所示:

图片

ElasticsearchRepository 接口内提供常用的操作ES的方法,如:新增、修改、删除、各种维度条件查询及分页等,详细方法内容如下:

图片

save() 方法是创建索引,如果索引document已经存在,后面的 save 方法则是对之前的数据覆盖。也就是说新增和修改都可以通过 save 方法 实现。

最后,通过编写单元测试类来验证方法功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
scss复制代码@Test
public void test1() {

    OrderModel orderModel = OrderModel.builder()
            .orderId(1L)
            .amount(25.5)
            .buyerUid(13201L)
            .shippingAddress("上海")
            .build();

    orderModel = orderRepository.save(orderModel);
    System.out.println(orderModel);
}

@Test
public void test2() {
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder()
            .withQuery(boolQueryBuilder);
    List<OrderModel> orderDocumentList = orderRepository.search(searchQueryBuilder.build()).getContent();
    System.out.println(JSON.toJSONString(orderDocumentList));
}

使用这个OrderRepository 来操作 ES 中的 OrderModel 数据。我们这里并没有手动创建OrderModel 对应的索引,由 elasticsearch 默认生成。

4、kibana 可视化控制台

安装 kibana,比较简单,这里就不在累述了。先下载kibana安装包,再解压

1
2
3
bash复制代码# bin目录下,执行启动脚本

./kibana

浏览器访问:http://localhost:5601/app/kibana#/dev\_tools/console?\_g=()

可以看到刚才执行单元测试,创建的索引记录。

图片

5、项目源码地址

github.com/aalansehaiy…

模块:spring-boot-bulking-elasticsearch

作者介绍:

Tom哥,计算机研究生,校招进阿里,P7技术专家,出过专利,CSDN博客专家。负责过电商交易、社区生鲜、流量营销、互联网金融等业务,多年一线团队管理经验

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Redis也能做搜索引擎?

发表于 2021-08-18

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

RediSearch 是一个高性能的全文搜索引擎,它可以作为一个 Redis Module(扩展模块)运行在 Redis 服务器上。

特性

RediSearch 主要特性如下:

  • 基于文档的多个字段全文索引
  • 高性能增量索引
  • 文档排序(由用户在索引时手动提供)
  • 自动完成建议(带有模糊前缀建议)
  • 精确的短语搜索
  • 在许多语言中基于词干分析的查询扩展
  • 支持用于查询扩展和评分的自定义函数
  • 将搜索限制到特定的文档字段
  • 数字过滤器和范围
  • 在子查询之间使用 AND 或 NOT 操作符的复杂布尔查询
  • 可选的查询子句
  • 基于前缀的搜索
  • 支持字段权重设置
  • 使用 Redis 自己的地理命令进行地理过滤
  • Unicode 支持(需要 UTF-8 字符集)
  • 检索完整的文档内容或只是 ID 的检索
  • 支持部分更新和条件文档更新
  • 支持文档删除和更新与索引垃圾收集

性能

与ElasticSearch在同等硬件环境下进行性能评测对比,对维基百科560万个文档(约 5.3GB)进行索引,并对索引的数据集做双单词搜索。对比图如下:

生成索引耗时:

1673984e6558842f.png

搜索性能对比:

1673986259461614.png

安装

通过docker快速部署

1
bash复制代码 docker run -p 6379:6379 redislabs/redisearch:latest

使用

使用 redis-cli 来对 RediSearch 进行相关的操作。

创建索引和字段

1
2
arduino复制代码127.0.0.1:6379> ft.create myidx schema title text weight 5.0 desc text
OK

其中“myidx”为索引的名称,此索引包含了两个字段“title”和“desc”,“weight”为权重,默认值为 1.0。

将内容添加到索引

1
2
ruby复制代码127.0.0.1:6379> ft.add myidx doc1 1.0 fields title "He urged her to study English" desc "good idea"
OK

其中“doc1”为文档 ID(docid),“1.0”为评分(score)。

根据关键查询

1
2
3
4
5
6
7
bash复制代码127.0.0.1:6379> ft.search myidx "english" limit 0 10
1) (integer) 1
2) "doc1"
3) 1) "title"
2) "He urged her to study English"
3) "desc"
4) "good idea"

可以看出我们使用 title 字段中的关键字“english”查询出了一条满足查询条件的数据。

中文搜索

首先我们需要先给索引中,添加一条中文数据,执行命令如下:

1
2
ruby复制代码127.0.0.1:6379> ft.add myidx doc2 1.0 language "chinese" fields title "Java 14 发布了!新功能速览" desc "Java 14 在 2020.3.17 日发布正式版了,但现在很多公司还在使用 Java 7 或 Java 8"
OK

注意:这里必须要设置语言编码为中文,也就是“language “chinese”” ,默认是英文编码,如果不设置则无法支持中文查询(无法查出结果)。

删除索引的数据

1
2
ruby复制代码127.0.0.1:6379> ft.del myidx doc1
(integer) 1

我们使用索引加文档 ID 就可以实现删除数据的功能。

删除索引

我们可以使用“ft.drop”关键字删除整个索引,执行命令如下:

1
2
ruby复制代码127.0.0.1:6379> ft.drop myidx
OK

客户端库支持

支持大部分语言,详见clients library

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Flink 从0-1实现 电商实时数仓 - 业务数据采集

发表于 2021-08-18

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

业务数据采集

流程

image.png

  1. 用户产生业务数据
  2. 后端服务存入Mysql
  3. 开启Mysql binlog
  4. 使用 Maxwell 将业务数据发送至 Kafka

Maxwell 介绍

Maxwell 是由美国 Zendesk 开源,用Java编写的MySQL实时抓取软件。实时读取MySQL二进制日志Binlog,并生成 JSON 格式的消息,作为生产者发送给 Kafka,Kinesis、RabbitMQ、Redis、Google Cloud Pub/Sub、文件或其它平台的应用程序。

官网地址:maxwells-daemon.io/

与 Maxwell 相同功能的框架还有:Debezium、canal

选择 Maxwell 的原因的,一行数据会生成一个json,结构简单,而且没有表字段类型什么的,比较清晰,很符合业务需要。

Maxwell 原理

很简单,就是把自己伪装成 slave , 假装从 master 复制数据

1. MySQL主从复制过程
  • Master主库将改变记录,写到二进制日志(binary log)中
  • Slave从库向mysql master发送dump协议,将master主库的binary log events拷贝到它的中继日志(relay log);
  • Slave从库读取并重做中继日志中的事件,将改变的数据同步到自己的数据库。
2. MySQL的binlog
  1. 什么是binlog

 MySQL的二进制日志可以说MySQL最重要的日志了,它记录了所有的DDL和DML(除了数据查询语句)语句,以事件形式记录,还包含语句所执行的消耗的时间,MySQL的二进制日志是事务安全型的。

 一般来说开启二进制日志大概会有1%的性能损耗。二进制有两个最重要的使用场景:

   Ø 其一:MySQL Replication在Master端开启binlog,Master把它的二进制日志传递给slaves来达到master-slave数据一致的目的。

   Ø 其二:自然就是数据恢复了,通过使用mysqlbinlog工具来使恢复数据。

 二进制日志包括两类文件:二进制日志索引文件(文件名后缀为.index)用于记录所有的二进制文件,二进制日志文件(文件名后缀为.00000*)记录数据库所有的DDL和DML(除了数据查询语句)语句事件。
2. binlog 的开启

1. 找到MySQL配置文件的位置
2. Linux: /etc/my.cnf


如果/etc目录下没有,可以通过locate my.cnf查找位置
3. 在mysql的配置文件下,修改配置


在[mysqld] 区块,设置/添加  log-bin=mysql-bin这个表示binlog日志的前缀是mysql-bin,以后生成的日志文件就是 mysql-bin.123456 的文件后面的数字按顺序生成,每次mysql重启或者到达单个文件大小的阈值时,新生一个文件,按顺序编号。
  1. binlog 的分类设置

mysql binlog的格式有三种,分别是STATEMENT,MIXED,ROW。

在配置文件中可以选择配置 binlog_format= statement|mixed|row

三种格式的区别:

1. statement


语句级,binlog会记录每次一执行写操作的语句。


相对row模式节省空间,但是可能产生不一致性,比如


update  tt set create\_date=now()


如果用binlog日志进行恢复,由于执行时间不同可能产生的数据就不同。


优点: 节省空间


缺点: 有可能造成数据不一致。
2. row


行级, binlog会记录每次操作后每行记录的变化。


优点:保持数据的绝对一致性。因为不管sql是什么,引用了什么函数,他只记录执行后的效果。


缺点:占用较大空间。
3. mixed


statement的升级版,一定程度上解决了,因为一些情况而造成的statement模式不一致问题


默认还是statement,在某些情况下譬如:



1
2
3
4
scss复制代码 当函数中包含 UUID() 时;
包含 AUTO_INCREMENT 字段的表被更新时;
执行 INSERT DELAYED 语句时;
用 UDF 时;
会按照 ROW的方式进行处理 优点:节省空间,同时兼顾了一定的一致性。 缺点:还有些极个别情况依旧会造成不一致,另外statement和mixed对于需要对binlog的监控的情况都不方便。**综合上面对比,Maxwell想做监控分析,选择row格式比较合适**

实现

1. 配置 MySQL
  1. 配置binlog
1
bash复制代码sudo vim /etc/my.cnf
1
2
3
4
ini复制代码server-id=1
log-bin=mysql-bin
binlog_format=row
binlog-do-db=tmall

注意:binlog-do-db根据自己的情况进行修改,指定具体要同步的数据库
2. 重启MySQL使配置生效

1
复制代码sudo systemctl restart mysqld
2. 配置 Maxwell
  1. 下载 Maxwell

官网地址:maxwells-daemon.io/

github:github.com/zendesk/max…

我使用的是 1.25.0 版本
2. 解压 Maxwell

1
bash复制代码tar -zxvf /opt/software/maxwell-1.25.0.tar.gz -C /opt/module/
  1. 初始化 Maxwell 元数据库
1. 创建 `maxwell` 数据库

1
ini复制代码CREATE DATABASE maxwell ;
2. 分配一个账号可以操作该数据库
1
sql复制代码GRANT ALL ON maxwell.* TO 'maxwell'@'%' IDENTIFIED BY '666666';
3. 分配这个账号可以监控其他数据库的权限
1
vbnet复制代码GRANT SELECT ,REPLICATION SLAVE , REPLICATION CLIENT  ON *.* TO maxwell@'%';
  1. 配置 Maxwell
1. 拷贝配置文件

1
2
arduino复制代码cd /opt/module/maxwell-1.25.0/
cp config.properties.example config.properties
2. 修改配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	ini复制代码# 配置kafka
producer=kafka
kafka.bootstrap.servers=hd1:9092,hd2:9092,hd3:9092

# 需要添加topic
kafka_topic=ods_base_db_m

# mysql
host=hd3
user=maxwell
password=666666

# 处理历史数据需要用到
client_id=maxwell_1

```**`注意`**:默认还是输出到指定Kafka主题的`一个`kafka分区,因为多个分区并行可能会打乱binlog的顺序


如果要提高并行度,首先设置kafka的分区数 > 1,然后设置`producer_partition_by`属性


可选值 producer\_partition\_by=database|table|primary\_key|random|column
5. 启动 Maxwell

bash复制代码/opt/module/maxwell-1.25.0/bin/maxwell –config /opt/module/maxwell-1.25.0/config.properties >/dev/null 2>&1 &

1
6. 测试 Maxwell

bash复制代码cd /opt/module/kafka_2.11-2.4.1/
bin/kafka-console-consumer.sh –bootstrap-server hd1:9092 –topic ods_base_db_m




> 关注专栏持续更新 👇🏻👇🏻👇🏻👇🏻👇🏻👇🏻👇🏻👇🏻



**本文转载自:** [掘金](https://juejin.cn/post/6997667773472047118)

*[开发者博客 – 和开发相关的 这里全都有](https://dev.newban.cn/)*

如何搭建个人博客站点

发表于 2021-08-18

原文地址:www.hz-bin.cn/BuildBlog

1、如何使用 github page 部署个人博客

  • 登录 github,点击右上角新建仓库,仓库名填 username.github.io,其中 username 填自己的 github 名称,例如博主的是 hz-bin,则仓库名为 hz-bin.github.io。

  • 进入项目的 Settings->Pages 页面,Source 选择 master,然后点击 Save,则完成部署,即可通过 hz-bin.github.io 进行访问了。


2、如何使用 jekyll 和 TeXt 主题编辑博客内容

  • 本地部署 jekyll 环境参考 jekyll 中文文档
  • 本地克隆 TeXt 主题,然后进入到 jekyll-TeXt-theme 目录,如果有 Gemfile.lock 文件,先删除该文件。
  • 然后在命令行中先执行 gem install bundler jekyll 命令,再执行 bundle exec jekyll serve,则本地博客搭建完成
  • 浏览器中输入 http://localhost:4000/ 即可访问博客内容。
  • 如何新增一篇博客:在 _posts 目录中新建 markdown 格式文件,文件名格式为 yyyy-mm-dd-blogname.md。jekyll 会自动进行构建。

3、如何配置 gitalk 作为博客的评论系统

  • 首先需要新建一个 OAuth application(右上角头像 -> Settings -> Developer settings -> OAuth Apps -> New OAuth App),如下图所示:

  • 点击 Generate a new client secret:


  • 将 Client ID 和 刚刚产生的 Client secrets 拷贝到 _config.yml 文件中,其他参数如图所示:

配置完成之后,登录 gitalk 时可能会出现403错误,参考 Gitalk 评论登录 403 问题解决 和 在cloudflare上创建一个免费的在线代理来解决gitalk授权登录报403问题

4、如何配置 leancloud 记录文章访问次数

  • 首先进入 LeanCloud,新建一个应用,应用名称自己随便填。然后进入到设置界面,如下所示,_config.yml 配置中,pageview 的 provider 填 leancloud,leancloud 的 app_id 和 app_key 分别填应用凭证界面的 AppID 和 AppKey。


  • app_class 填 Blog 或者自己定义一个名字,LeanCloud 会用该名称在 数据存储 -> 结构化数据 中创建Class,相当于存储访问次数的表。

  • 修改 _includes/pageview-providers/leancloud/leancloud.js,如下所示,serverURLs 改为 serverURL,值改为上面应用凭证中的 REST API 服务器地址。

可参考官方文档:

初始化:leancloud.cn/docs/sdk_se…

数据存储入门教程 · JavaScript:leancloud.cn/docs/leanst…

5、如何给自己的博客设置自定义域名

  • 首先需要申请域名,本博客域名从腾讯云申请。
  • 域名申请完成之后,进入域名管理界面,点击解析。

  • 添加如下2条记录,其中 ip 地址是通过 ping hz-bin.github.io 得到的:


  • 进入到 github 项目的 Settings -> Pages 配置界面,Custom domain 填刚刚注册的域名,同时勾选上 Enforce HTTPS,然后保存。等待10分钟左右,就可以通过 https://www.hz-bin.cn 访问了。

gitalk 和 cloudflare 修改

  • 配置完域名之后,会发现 gitalk 登录不上了,且文章计数也获取不到了,需要对相关配置进行修改。
  • 登录 github,右上角头像 -> Settings -> Developer settings -> OAuth Apps,看到如下界面,点击第3节新建的 OAuth application,将 Homepage URL 和 Authorization callback URL 改成新的博客地址 https://www.hz-bin.cn,然后保存。

  • 登录 cloudflare,按如图所示操作:





本文由博客一文多发平台 OpenWrite 发布!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…558559560…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%