拒绝白嫖,开源项目作者删库跑路,数千个应用程序无限输出乱码
「我删我自己的开源项目代码,需要经过别人允许吗?」
几天前,开源库「faker.js」和「colors.js」的用户打开电脑,发现自己的应用程序正在输出乱码数据,那一刻,他们惊呆了。
更令人震惊的是,开发者们发现,造成这一混乱局面的就是「faker.js」和「colors.js」的作者 Marak Squires 本人。
一夜之间,Marak Squires 主动删除了「faker.js」和「colors.js」项目仓库的所有代码,让正在使用这两个开源项目的数千位开发者直接崩溃。
「faker.js」和「colors.js」
faker.js 在 npm 上的每周下载量接近 250 万,color.js 每周的下载量约为 2240 万,本次删库的影响是极其严重的,使用这两个项目开发的工具包括 AWS CDK 等。
如果在构建和测试应用时,真实的数据量远远不够,那么 Faker 类工具将帮助开发者生成伪数据。faker.js 就是可为多个领域生成伪数据的 Node.js 库,包括地址、商业、公司、日期、财务、图像、随机数、名称等。
faker.js 支持生成英文、中文等多语种信息,包含丰富的 API,此前版本通常一个月迭代更新一次。faker.js 不仅可以使用在服务器端的 JavaScript,还可以应用在浏览器端的 JavaScript。
现在,faker.js 项目的所有 commit 信息都被改为「endgame」,在 README 中,作者写下这样一句话:「What really happened with Aaron Swartz?」
Swartz 是一位杰出的开发人员,帮助建立了 Creative Commons、RSS 和 Reddit。2011 年,Swartz 被指控从学术数据库 JSTOR 中窃取文件,目的是免费访问这些文件。Swartz 在 2013 年自杀,Squires 提到 Swartz 可能意指围绕这一死亡疑云。
Marak Squires 向 colors.js 提交了恶意代码,添加了一个「a new American flag module」,然后将其发布到了 GitHub 和 npm。
随后他在 GitHub 和 npm 发布了 faker.js 6.6.6,这两个动作引发了同样的破坏性事件。破坏后的版本导致应用程序无限输出奇怪的字母和符号,从三行写着「LIBERTY LIBERTY LIBERTY」的文本开始,后面跟着一系列非 ASCII 字符:
目前,color.js 已经更新了一个可以使用的版本。faker.js 项目尚未恢复,开发者只能通过降级到此前的 5.5.3 版本来解决问题。
为了解决问题,Squires 在 GitHub 上还发布了更新以解决「zalgo 问题」,该问题是指损坏文件产生的故障文本。
「我们注意到在 v1.4.44-liberty-2 版本的 colors 中有一个 zalgo 错误,」Squires 以一种讽刺的语气写道。「我们现在正在努力解决这个问题,很快就会有解决方案。」
在将更新推送到 faker.js 两天后,Squires 发了一条推文,表示自己存储了数百个项目的 GitHub 账户已经被封。Squires 在 1 月 4 日发布了 faker.js 的最新 commit,在 1 月 6 日被封,直到 1 月 7 日推送了 colors.js 的「liberty」版本。然而,从 faker.js 和 colors.js 的更新日志来看,他的账户似乎被解封过。目前尚不清楚 Squires 的帐户是否再次被封。
至此,故事并没有就此结束。Squires 2020 年 11 月发在 GitHub 上的一篇帖子被挖出来,在帖子中他写道自己不再想做免费的工作了。「恕我直言,我不想再用我的免费工作来支持财富 500 强(和其他小型公司),以此为契机,向我发送一份六位数的年度合同,或者 fork 项目并让其他人参与其中。」
Squires 的大胆举动引起了人们对开源开发者的道德和财务困境的关注,这可能是 Marak Squires 行动的目标。大量网站、软件和应用程序依赖开源开发人员来创建基本工具和组件,而所有这些都是免费的,无偿开发人员经常不知疲倦地工作,努力修复其开源软件中的安全问题。
开发者们怎么看
软件工程师 Sergio Gómez 表示:「从 GitHub 删除自己的代码违反了他们的服务条款?WTF?这是绑架。我们需要开始分散托管免费软件源代码。」
「不知道发生了什么,但我将我所有的项目都托管在 GitLab 私有 instance 上,永远不要相信任何互联网服务提供商。」
有网友认为 faker.js 团队的反应有些夸张了,并说道:「没有人会用一个只生成一些虚假数据的包赚大钱。faker.js 的确为开发者生成伪数据节省了一些时间,但我们也可以让实习生编写类似程序来生成数据。这对企业来说并没有那么重要。」
甚至有人认为 Marak 这么做是一种冲动行为,不够理性,并和他之前「卖掉房子购买 NFT」的传闻联系起来,认为 Marak 需要学会控制自己的情绪:
这种说法很快带偏部分网友的看法,有人原本同情开源项目被「白嫖」,但现在已转向认为 Marak 是恶意删库,并指出:「停止维护他的项目或完全删除都是他的权利,但故意提交有害代码是不对的。」
当然,也有人为开源软件(FOSS)开发者的待遇鸣不平:「希望有相关的基金会位 FOSS 开发人员提供资金支持」,而软件的可靠性和稳定性也是至关重要的
有人表示:一些大公司确实不尊重开源项目的版权,滥用开源项目对于 FOSS 开发者来说是绝对不公平的。但 Marak 对 faker.js 的做法并不可取,不是正面例子,存在 Marak 的个人负面原因。
对此,你有什么看法?
作者:机器之心Pro
来源:https://www.163.com/dy/article/GTC8PE5M0511AQHO.html
收起阅读 »崩溃的一天,西安一码通崩溃背后的技术问题
1.崩溃的一天
12月20号,算得上西安崩溃的一天。
12月19号新增病例21个,20号新增病例42个,并且有部分病例已经在社区内传播.
西安防疫压力巨大,各单位公司要求,需48小时核酸检测报告上班。
在这样严峻的情况下,作为防控最核心的系统:西安一码通竟然崩溃了,并且崩溃得是那么的彻底。
足足瘫痪超过 15 个多小时!
整整一天的时间呀,多少上班族被堵在地铁口,多少旅客被冻在半路上,进退不能...
到了下午,新闻甚至提示:
为了减轻系统压力,建议广大市民非必要不展码、亮码,在出现系统卡顿时,请耐心等待,尽量避免反复刷新,也感谢广大市民朋友们的理解配合。
这是解决问题的方法吗?
如果真的需要限流来防止系统崩溃,用技术手段来限流是不是会更简单一些,甚至前面加一个 nginx 就能解决的问题。
今天,我们就试着分析一下这个业务、以及对应的技术问题。
2.产品分析
西安一码通其它业务我们暂且不分析,那并不是重点,并且当天也没有完全崩溃,崩溃的仅有扫码功能。
其实这是一个非常典型的大量查询、少数更新的业务,闭着眼睛分析一下,可以说, 90% 以上的流量都是查询。
我们先来看看第一版的产品形态,扫码之后展示个人部分姓名和身份政信息,同时下面展示绿、黄、红码。
这是西安一码通最开始的样子,业务流程仅仅只需要一个请求,甚至一个查询的 SQL 就可以搞定。
到了后来,这个界面做了2次比较大的改版。
第一次改版新增了疫苗接种信息,加了一个边框;第二次改版新增了核酸检测信息,在最下方展示核酸检测时间、结果。
整个页面增加了2个查询业务,如果系统背后使用的是关系数据库,可能会多增加至少2个查询SQL。
基本上就是这样的一个需求,据统计西安有1300万人口,按照最大10%的市民同时扫码(我怀疑不会有这么多),也就是百万的并发量。
这样一个并发量的业务,在互联网公司很常见,甚至比这个复杂的场景也多了去了。
那怎么就崩了呢?
3.技术分析
在当天晚上的官方回复中,我们看到有这样一句话:
12月20日早7:40分左右,西安“一码通”用户访问量激增,每秒访问量达到以往峰值的10倍以上,造成网络拥塞,致使包括“一码通”在内的部分应用系统无法正常使用。“
一码通”后台监控第一时间报警,各24小时驻场通信、网络、政务云、安全和运维团队立即开展排查,平台应用系统和数据库运行正常,判断问题出现在网络接口侧。
根据上面的信息,数据库和平台系统都正常,是网络出现了问题。
我之前在文章《一次dns缓存引发的惨案》画过一张访问示意图,用这个图来和大家分析一下,网络出现问题的情况。
一般用户的请求,会先从域名开始,经过DNS服务器解析后拿到外网IP地址,经过外网IP访问防火墙和负载之后打到服务器,最后服务器响应后将结果返回到浏览器。
如果真的是网络出现问题,一般最常见的问题就是 DNS 解析错误,或者外网的宽带被打满了。
DNS解析错误一定不是本次的问题,不然可能不只是这一个功能出错了;外网的宽带被打满,直接增加带宽就行,不至于一天都没搞定。
如果真的是网络侧出现问题,一般也不需要改动业务,但实际上系统恢复的时候,大家都发现界面回到文章开头提到了第一个版本了。
也就是说系统“回滚”了。
界面少了接种信息和核酸检测信息的内容,并且在一码通的首页位置,新增加了一个核酸查询的页面。
所以,仅仅是网络接口侧出现问题吗?我这里有一点点的疑问。
4.个人分析
根据我以往的经验,这是一个很典型的系统过载现象,也就是说短期内请求量超过服务器响应。
说人话就是,外部请求量超过了系统的最大处理能力。
当然了,系统最大处理能力和系统架构息息相关,同样的服务器不同的架构,系统负载量差异极大。
应对这样的问题,解决起来无非有两个方案,一个是限流,另外一个就是扩容了。
限流就是把用户挡在外面,先处理能处理的请求;扩容就是加服务器、增加数据库承载能力。
上面提到官方让大家没事别刷一码通,也算是人工限流的一种方式;不过在技术体系上基本上不会这样做。
技术上的限流方案有很多,但最简单的就是前面挂一个 Nginx 配置一下就能用;复杂一点就是接入层自己写算法。
当然了限流不能真正的解决问题,只是负责把一部分请求挡在外面;真正解决问题还是需要扩容,满足所有用户。
但实际上,根据解决问题的处理和产品回滚的情况来看,一码通并没有第一时间做扩容,而是选择了回滚。
这说明,在系统架构设计上,没有充分考虑扩容的情况,所以并不能支持第一时间选择这个方案。
5.理想的方案?
上面说那么多也仅仅是个人推测,实际上可能他们会面临更多现实问题,比如工期紧张、老板控制预算等等...
话说回来,如果你是负责一码通公司的架构师,你会怎么设计整个技术方案呢?欢迎大家留言,这里说说我的想法。
第一步,读写分离、缓存。
至少把系统分为2大块,满足日常使用的读业务单独抽取出来,用于承接外部的最大流量。
单独抽出一个子系统负责业务的更新,比如接种信息的更新、核酸信息的变化、或者根据业务定时变更码的颜色。
同时针对用户大量的单查询,上缓存系统,优先读取缓存系统的信息,防止压垮后面的数据库。
第二步,分库分表、服务拆分。
其实用户和用户之间的单个查询是没有关系的,完全可以根据用户的属性做分库分表。
比如就用用户ID取模分64个表,甚至可以分成64个子系统来查询,在接口最前端将流量分发掉,减轻单个表或者服务压力。
上面分析没有及时扩容,可能就是没有做服务拆分,如果都是单个的业务子服务的话,遇到过载的问题很容易做扩容。
当然,如果条件合适的话,上微服务架构就更好了,有一套解决方案来处理类似的问题。
第三步,大数据系统、容灾。
如果在一个页面中展示很多信息,还有一个技术方案,就是通过异步的数据清洗,整合到 nosql 的一张大表中。
用户扫描查询等相关业务,直接走 nosql 数据库即可。
这样处理的好处是,哪怕更新业务完全挂了,也不会影响用户扫码查询,因为两套系统、数据库都是完全分开的。
使用异地双机房等形式部署服务,同时做好整体的容灾、备灾方案,避免出现极端情况,比如机房光缆挖断等。
还有很多细节上的优化,这里就不一一说明了,这里也只是我的一些想法,欢迎大家留言补充。
6.最后
不管怎么分析,这肯定是人祸而不是天灾。
系统在没有经过严格测试之下,就直接投入到生产,在强度稍微大一点的环境中就崩溃了。
比西安大的城市很多,比西安现在疫情还要严重的情况,其它城市也遇到过,怎么没有出现类似的问题?
西安做为一个科技大省,出现这样的问题真的不应该,特别是我看了这个小程序背后使用的域名地址之后。
有一种无力吐槽的感觉,虽然说这和程序使用没有关系,但是从细节真的可以看出一个技术团队的实力。
希望这次能够吸取教训,避免再次出现类似的问题!
作者:纯洁的微笑
出处:https://www.cnblogs.com/ityouknow/p/15719395.html
利用好 git bisect 这把利器,帮助你快速定位疑难 bug
使用git bisect二分法定位问题的基本步骤:
git bisect start [最近的出错的commitid] [较远的正确的commitid]
测试相应的功能
git bisect good 标记正确
直到出现问题则 标记错误 git bisect bad
提示的commitid就是导致问题的那次提交
问题描述
我们以Vue DevUI组件库的一个bug举例子🌰
5d14c34b
这一次commit,执行yarn build
报错,报错信息如下:
✓ building client + server bundles...
✖ rendering pages...
build error:
ReferenceError: document is not defined
我可以确定的是上一次发版本(d577ce4)是可以build成功的。
git bisect 简介
git bisect
命令使用二分搜索算法来查找提交历史中的哪一次提交引入了错误。它几乎能让你闭着眼睛快速定位任何源码导致的问题,非常实用。
你只需要告诉这个命令一个包含该bug的坏commit ID
和一个引入该bug之前的好commit ID
,这个命令会用二分法在这两个提交之间选择一个中间的commit ID
,切换到那个commit ID
的代码,然后询问你这是好的commit ID
还是坏的commit ID
,你告诉它是好还是坏,然后它会不断缩小范围,直到找到那次引入bug的凶手commit ID
。
这样我们就只需要分析那一次提交的代码,就能快速定位和解决这个bug(具体定位的时间取决于该次提交的代码量和你的经验),所以我们提交代码时一定要养成小批量提交的习惯,每次只提交一个小的独立功能,这样出问题了,定位起来会非常快。
接下来我就以Vue DevUI之前出现过的一个bug为例,详细介绍下如何使用git bisect
这把利器。
定位过程
git bisect start 5d14c34b d577ce4
or
git bisect start HEAD d577ce4
其中5d14c34b
这次是最近出现的有bug的提交,d577ce4
这个是上一次发版本没问题的提交。
执行完启动bisect
之后,马上就切到中间的一次提交啦,以下是打印结果:
kagol:vue-devui kagol$ git bisect start 5d14c34b d577ce4
Bisecting: 11 revisions left to test after this (roughly 4 steps)
[1cfafaaa58e03850e0c9ddc4246ae40d18b03d71] fix: read-tip icon样式泄露 (#54)
可以看到已经切到以下提交:
[1cfafaaa] fix: read-tip icon样式泄露 (#54)
执行命令:
yarn build
构建成功,所以标记下good
:
git bisect good
kagol:vue-devui kagol$ git bisect good
Bisecting: 5 revisions left to test after this (roughly 3 steps)
[c0c4cc1a25c5c6967b85100ee8ac636d90eff4b0] feat(drawer): add service model (#27)
标记万good
,马上又通过二分法,切到了一次新的提交:
[c0c4cc1a] feat(drawer): add service model (#27)
再次执行build
命令:
yarn build
build失败了,出现了我们最早遇到的报错:
✓ building client + server bundles...
✖ rendering pages...
build error:
ReferenceError: document is not defined
标记下bad
,再一次切到中间的提交:
kagol:vue-devui kagol$ git bisect bad
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[86634fd8efd2b808811835e7cb7ca80bc2904795] feat: add scss preprocessor in docs && fix:(Toast) single lifeMode bug in Toast
以此类推,不断地验证、标记、验证、标记...最终会提示我们那一次提交导致了这次的bug,提交者、提交时间、提交message等信息。
kagol:vue-devui kagol$ git bisect good
c0c4cc1a25c5c6967b85100ee8ac636d90eff4b0 is the first bad commit
commit c0c4cc1a25c5c6967b85100ee8ac636d90eff4b0
Author: nif <lnzhangsong@163.com>
Date: Sun Dec 26 21:37:05 2021 +0800
feat(drawer): add service model (#27)
* feat(drawer): add service model
* docs(drawer): add service model demo
* fix(drawer): remove 'console.log()'
packages/devui-vue/devui/drawer/index.ts | 7 +++--
.../devui-vue/devui/drawer/src/drawer-service.ts | 33 ++++++++++++++++++++++
packages/devui-vue/devui/drawer/src/drawer.tsx | 3 ++
packages/devui-vue/docs/components/drawer/index.md | 29 +++++++++++++++++++
4 files changed, 69 insertions(+), 3 deletions(-)
create mode 100644 packages/devui-vue/devui/drawer/src/drawer-service.ts
最终定位到出问题的commit:
c0c4cc1a is the first bad commit
整个定位过程几乎是机械的操作,不需要了解项目源码,不需要了解最近谁提交了什么内容,只需要无脑地:验证、标记、验证、标记,最后git会告诉我们那一次提交出错。
这么香的工具,赶紧来试试吧!
问题分析
直到哪个commit出问题了,定位起来范围就小了很多。
如果平时提交代码又能很好地遵循小颗粒提交的话,bug呼之欲出。
这里必须表扬下我们DevUI的田主(Contributor)们,他们都养成了小颗粒提交的习惯,这次导致bug的提交c0c4cc1a
,只提交了4个文件,涉及70多行代码。
我们在其中搜索下document
关键字,发现了两处,都在drawer-service.ts
整个文件中:
一处是12行的:
static $body: HTMLElement | null = document.body
另一处是17行的:
this.$div = document.createElement('div')
最终发现罪魁祸首就是12行的代码!
破案!
作者:DevUI团队
来源:https://juejin.cn/post/7046409685561245733
手把手带你配置MySQL主备环境
为了保障生产数据的安全性,我们往往需要对数据库做备份,而通过MySQL主备配置,则是一种MySQL数据库备份的好的实现方案。本文将一步步带你搭建MySQL主备环境。
MySQL主备搭建
现有两台虚拟机,192.168.56.11(主)和192.168.56.12(备)
1 MySQL安装
离线安装解压版MySQL
1 上传压缩包
以root用户上传mysql-5.7.29-linux-glibc2.12-x86_64.tar.gz到/root/Downloads/下
2 解压
cd /root/Downloads
tar -zxvf mysql-5.7.29-linux-glibc2.12-x86_64.tar.gz
mv mysql-5.7.29-linux-glibc2.12-x86_64 mysql5.7
mkdir /usr/database
mv mysql5.7 /usr/database
3 创建系统用户组和用户
groupadd mysql
useradd -r -g mysql mysql
id mysql
4 创建mysql data目录
cd /usr/database/mysql5.7
mkdir data
4 设置data目录权限
chown -R mysql:mysql /usr/database/mysql5.7/
ll /usr/database
5 修改my.cnf文件
删除并重新创建/etc/my.cnf:
rm -rf /etc/my.cnf
vim /etc/my.cnf
[client]
port = 3306
socket = /tmp/mysql.sock
[mysqld]
init-connect='SET NAMES utf8'
basedir=/usr/database/mysql5.7 #根据自己的安装目录填写
datadir=/usr/database/mysql5.7/data #根据自己的mysql数据目录填写
socket=/tmp/mysql.sock
max_connections=200 # 允许最大连接数
character-set-server=utf8 # 服务端使用的字符集默认为8比特编码的latin1字符集
default-storage-engine=INNODB # 创建新表时将使用的默认存储引擎
最大连接数
max_connections<=16384
管理员(SUPER)登录的连接,不计其中
mysql会为每个连接提供连接缓冲区,连接越多内存开销越大
查询:
show variables like '%_connections';
,show status like '%_connections';
max_used_connections / max_connections * 100% (理想值≈ 85%)
数值过小会经常出现ERROR 1040: Too many connections错误
修改方法:
永久:在配置文件my.cnf中设置max_connections的值
临时:以root登录mysql:
set GLOBAL max_connections=xxx;
->flush privileges;
mysql存储引擎
InnoDB:5.5及之后版本的默认引擎
支持事务
聚集索引,文件存放在主键索引的叶子节点上,必须要有主键。通过主键索引效率很高,但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。
不支持全文类型索引
支持外键
不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。
最小锁粒度是行锁
适用场景:支持事物、较多写操作、系统崩溃后相对易恢复
MyISAM:5.5版本之前的默认引擎
不支持事务
非聚集索引,数据文件是分离的,索引保存的是数据文件的指针,主键索引和辅助索引是独立的。
保存了整个表的行数,执行select count(*) 时只需要读出该变量即可,速度很快;
支持全文类型索引
不支持外键
最小锁粒度是表锁,一个更新语句会锁住整张表,导致其他查询和更新都被阻塞,因此并发访问受限
表以文件形式保存,跨平台使用较方便
适用场景:非事物型、读操作、小型应用
6 mysql初始化
/usr/database/mysql5.7/bin/mysqld --initialize-insecure --user=mysql --basedir=/usr/database/mysql5.7 --datadir=/usr/database/mysql5.7/data
#注意:mysqld --initialize-insecure初始化后的mysql是没有密码的
chown -R root:root /usr/database/mysql5.7/
#把安装目录的目录的权限所有者改为rootchown -R mysql:mysql /usr/database/mysql5.7/data/
#把data目录的权限所有者改为mysql
7 启动mysql
/usr/database/mysql5.7/bin/mysqld_safe --user=mysql &
8 修改root密码
cd /usr/database/mysql5.7/bin
./mysql -u root -p # 默认没有密码,直接回车就行
use mysql;
update user set authentication_string=password('``rootpasswd``') where user='root';
flush privileges;
exit;
9 登录测试
/usr/database/mysql5.7/bin/mysql mysql -u root -p
(输入密码)
show databases;
exit;
10 启动设置
cp /usr/database/mysql5.7/support-files/mysql.server /etc/init.d/mysql
chkconfig --add mysql # 添加服务
chkconfig --list # 查看服务列表
chkconfig --level 345 mysql on # 设置开机启动
11 测试服务命令是否可用
systemctl status mysql
systemctl start mysql
systemctl stop mysql
12 设置远程访问
登录数据库:
mysql -uroot -p[password]
use mysql;
select host,user from user;
update user set host='%' where user='root';
flush privileges;
如果还是无法访问,检查防火墙
13 创建新用户
创建用户app:
create user 'app'@'%' identified by 'password';
用户赋权(具有数据库appdb的所有权限,并可远程不限ip访问):
grant all on appdb.* to 'app'@'%';
flush privilegesl;
14 其他问题
-bash: mysql: command not found
临时方案,重新登录后失效:
alias mysql=/usr/database/mysql5.7/bin/mysql
永久方案,将命令路径添加到PATH中:
vim /etc/profile
PATH="$PATH:/usr/database/mysql5.7/bin"
source /etc/profile
2 主备配置
1 生产环境为什么需要MySQL集群
高可用性,在主节点失效时自动切换,不需要技术人员紧急处理
高吞吐,可以多个节点同时提供读取数据服务,降低主节点负载,实现高吞吐
可扩展性强,支持在线扩容
无影响备份,在备节点进行备份操作不会对业务产生影响
MySQL集群的缺点:
架构复杂,在部署、管理方面对技术人员要求高
备节点拉取主节点日志时会对主节点服务器性能有一定影响
如果配置了半同步复制,会对事物提交有一定影响
2 修改my.cnf
主备服务器均创建日志文件路径:
mkdir /data/mysql_log
修改master下的/etc/my.cnf:
[client]
port = 3306
default-character-set=utf8mb4
socket = /usr/database/mysql5.7/mysql.sock
[mysqld]
basedir = /usr/database/mysql5.7
datadir = /usr/database/mysql5.7/data
tmpdir = /tmp
socket = /tmp/mysql.sock
# pid-file = /data/mysql_db/mysql_seg_3306/mysql.pid
skip-external-locking = 1
skip-name-resolve = 1
port = 3306
server-id = 113306
default-storage-engine = InnoDB
character-set-server = utf8mb4
default_password_lifetime=0
#### log ####
log_timestamps=system
log_bin = /data/mysql_log/mysql-bin
log_bin_index = /data/mysql_log/mysql-bin.index
binlog_format = row
relay_log_recovery=ON
relay_log=/data/mysql_log/mysql-relay-bin
relay_log_index=/data/mysql_log/mysql-relay-bin.index
log_error = /data/mysql_log/mysql-error.log
#### replication ####
replicate_wild_ignore_table = information_schema.%,performance_schema.%,sys.%
#### semi sync replication settings #####
plugin_dir=/usr/database/mysql5.7/lib/plugin
plugin_load = "rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so"
loose_rpl_semi_sync_master_enabled = 1
loose_rpl_semi_sync_slave_enabled = 1
loose_rpl_semi_sync_master_timeout = 5000
修改slave下的/etc/my.cnf:
[client]
port = 3306
default-character-set=utf8mb4
socket = /usr/database/mysql5.7/mysql.sock
[mysqld]
basedir = /usr/database/mysql5.7
datadir = /usr/database/mysql5.7/data
tmpdir = /tmp
socket = /tmp/mysql.sock
# pid-file = /data/mysql_db/mysql_seg_3306/mysql.pid
skip-external-locking = 1
skip-name-resolve = 1
port = 3306
server-id = 123306
read-only=1
default-storage-engine = InnoDB
character-set-server = utf8mb4
default_password_lifetime=0
#### log ####
log_timestamps=system
log_bin = /data/mysql_log/mysql-bin
log_bin_index = /data/mysql_log/mysql-bin.index
binlog_format = row
relay_log_recovery=ON
relay_log=/data/mysql_log/mysql-relay-bin
relay_log_index=/data/mysql_log/mysql-relay-bin.index
log_error = /data/mysql_log/mysql-error.log
#### replication ####
replicate_wild_ignore_table = information_schema.%,performance_schema.%,sys.%
#### semi sync replication settings #####
plugin_dir=/usr/database/mysql5.7/lib/plugin
plugin_load = "rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so"
loose_rpl_semi_sync_master_enabled = 1
loose_rpl_semi_sync_slave_enabled = 1
loose_rpl_semi_sync_master_timeout = 5000
utf8:最大unicode字符是0xffff,仅支持Unicode 中的基本多文种平面(BMP),任何不在基本多文本平面的 Unicode字符,都无法使用 Mysql 的 utf8 字符集存储。
utf8mb4(most bytes 4)专门用来兼容四字节的unicode,是utf8的超集,包含很多不常用的汉字、Emoji表情,以及任何新增的 Unicode 字符等等
3 master上创建同步用户
mysql -uroot -proot
use mysql;
create user 'repl'@'%' identified by 'repl';
grant replication slave on . to 'repl'@'%';
flush privileges;
4 备份master数据库
/usr/database/mysql5.7/bin/mysqldump -S /tmp/mysql.sock -F --opt -R --single-transaction --master-data=2 --default-character-set=utf8 -A > mysql_backup_full.sql
上述命令报错,1045,增加登录信息:
/usr/database/mysql5.7/bin/mysqldump -uroot -proot -S /tmp/mysql.sock -F --opt -R --single-transaction --master-data=2 --default-character-set=utf8 -A > mysql_backup_full.sql
导出指定数据库(仅表结构):
/usr/database/mysql5.7/bin/mysqldump -uroot -proot -S /tmp/mysql.sock -F --opt -R --single-transaction --master-data=2 --default-character-set=utf8 -d testdb1 testdb2 > mysql_backup_test.sql
导出指定数据库(表结构+数据):
**/usr/database/mysql5.7/bin/mysqldump -uroot -proot -S /tmp/mysql.sock -F --opt -R --single-transaction --master-data=2 --default-character-set=utf8 testdb1 testdb2 > mysql_backup_testdatas.sql**
5 slave上恢复master数据库
master推送sql:rsync -avzP mysql_backup_test.sql 192.168.56.12:/root/Downloads/
slave导入sql:/usr/database/mysql5.7/bin/mysqldump -S /tmp/mysql.sock < mysql_backup_test.sql
上述命令不成功,需先创建数据库,改为下述操作:
**mysql -uroot -proot**
**use test;**
**source /root/Downloads/**``**mysql_backup_test.sql**
6 开启同步
mysql命令行中查看master status中的File和Position参数:
show master status;
查看进程:
show processlist\G
slave的mysql命令行执行:
mysql> CHANGE MASTER TO
-> MASTER_HOST='192.168.41.83',
-> MASTER_PORT=3306,
-> MASTER_USER='repl',
-> MASTER_PASSWORD='repl',
-> MASTER_LOG_FILE='mysql-bin.000004',
-> MASTER_LOG_POS=154;
mysql> start slave;
mysql> show slave status\G
状态中注意这几项:
Slave_IO_Running
:取 Master 日志的线程, Yes 为正在运行
Slave_SQL_Running
:从日志恢复数据的线程, Yes 为正在运行
Seconds_Behind_Master
:当前数据库相对于主库的数据延迟, 这个值是根据二进制日志的时间戳计算得到的(秒)
7 同步测试
master数据库插入一条数据,可用看到slave同步更新了
收起阅读 »主备有了,要是能够故障自动切换就完美了,这正是下一篇内容。
引用请注明出处!
动态代理是如何实现的?JDK Proxy 和 CGLib 有什么区别?
90% 的程序员直接或者间接的使用过动态代理,无论是日志框架或 Spring 框架,它们都包含了动态代理的实现代码。动态代理是程序在运行期间动态构建代理对象和动态调用代理方法的一种机制。
今天的面试题是:如何实现动态代理?JDK Proxy 和 CGLib 有什么区别?
典型回答
动态代理的常用实现方式是反射。反射机制 是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力,使用反射我们可以调用任意一个类对象,以及类对象中包含的属性及方法。
但动态代理不止有反射一种实现方式,例如,动态代理可以通过 CGLib 来实现,而 CGLib 是基于 ASM(一个 Java 字节码操作框架)而非反射实现的。简单来说,动态代理是一种行为方式,而反射或 ASM 只是它的一种实现手段而已。
JDK Proxy 和 CGLib 的区别主要体现在以下几个方面:
JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy,例如 Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
JDK Proxy 是通过拦截器加反射的方式实现的;
JDK Proxy 只能代理继承接口的类;
JDK Proxy 实现和调用起来比较简单;
CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。
考点分析
此面试题考察的是你对反射、动态代理及 CGLib 的了解,很多人经常会把反射和动态代理划为等号,但从严格意义上来说,这种想法是不正确的,真正能搞懂它们之间的关系,也体现了你扎实 Java 的基本功。和这个问题相关的知识点,还有以下几个:
你对 JDK Proxy 和 CGLib 的掌握程度。
Lombok 是通过反射实现的吗?
动态代理和静态代理有什么区别?
动态代理的使用场景有哪些?
Spring 中的动态代理是通过什么方式实现的?
知识扩展
1.JDK Proxy 和 CGLib 的使用及代码分析
JDK Proxy 动态代理实现
JDK Proxy 动态代理的实现无需引用第三方类,只需要实现 InvocationHandler 接口,重写 invoke() 方法即可,整个实现代码如下所示:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK Proxy 相关示例
*/
public class ProxyExample {
static interface Car {
void running();
}
static class Bus implements Car {
@Override
public void running() {
System.out.println("The bus is running.");
}
}
static class Taxi implements Car {
@Override
public void running() {
System.out.println("The taxi is running.");
}
}
/**
* JDK Proxy
*/
static class JDKProxy implements InvocationHandler {
private Object target; // 代理对象
// 获取到代理对象
public Object getInstance(Object target) {
this.target = target;
// 取得代理对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
/**
* 执行代理方法
* @param proxy 代理对象
* @param method 代理方法
* @param args 方法的参数
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws InvocationTargetException, IllegalAccessException {
System.out.println("动态代理之前的业务处理.");
Object result = method.invoke(target, args); // 执行调用方法(此方法执行前后,可以进行相关业务处理)
return result;
}
}
public static void main(String[] args) {
// 执行 JDK Proxy
JDKProxy jdkProxy = new JDKProxy();
Car carInstance = (Car) jdkProxy.getInstance(new Taxi());
carInstance.running();
}
以上程序的执行结果是:
动态代理之前的业务处理.
The taxi is running.
可以看出 JDK Proxy 实现动态代理的核心是实现 Invocation 接口,我们查看 Invocation 的源码,会发现里面其实只有一个 invoke() 方法,源码如下:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
这是因为在动态代理中有一个重要的角色也就是代理器,它用于统一管理被代理的对象,显然 InvocationHandler 就是这个代理器,而 invoke() 方法则是触发代理的执行方法,我们通过实现 Invocation 接口来拥有动态代理的能力。
CGLib 的实现
在使用 CGLib 之前,我们要先在项目中引入 CGLib 框架,在 pom.xml 中添加如下配置:
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
CGLib 实现代码如下:
package com.lagou.interview;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLibExample {
static class Car {
public void running() {
System.out.println("The car is running.");
}
}
/**
* CGLib 代理类
*/
static class CGLibProxy implements MethodInterceptor {
private Object target; // 代理对象
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
// 设置父类为实例类
enhancer.setSuperclass(this.target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method,
Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("方法调用前业务处理.");
Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用
return result;
}
}
// 执行 CGLib 的方法调用
public static void main(String[] args) {
// 创建 CGLib 代理类
CGLibProxy proxy = new CGLibProxy();
// 初始化代理对象
Car car = (Car) proxy.getInstance(new Car());
// 执行方法
car.running();
}
以上程序的执行结果是:
方法调用前业务处理.
The car is running.
可以看出 CGLib 和 JDK Proxy 的实现代码比较类似,都是通过实现代理器的接口,再调用某一个方法完成动态代理的,唯一不同的是,CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。因此被代理类不能被关键字 final 修饰,如果被 final 修饰,再使用 Enhancer 设置父类时会报错,动态代理的构建会失败。
2.Lombok 原理分析
在开始讲 Lombok 的原理之前,我们先来简单地介绍一下 Lombok,它属于 Java 的一个热门工具类,使用它可以有效的解决代码工程中那些繁琐又重复的代码,如 Setter、Getter、toString、equals 和 hashCode 等等,向这种方法都可以使用 Lombok 注解来完成。
例如,我们使用比较多的 Setter 和 Getter 方法,在没有使用 Lombok 之前,代码是这样的:
public class Person {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
在使用 Lombok 之后,代码是这样的:
@Data
public class Person {
private Integer id;
private String name;
}
可以看出 Lombok 让代码简单和优雅了很多。
小贴士:如果在项目中使用了 Lombok 的 Getter 和 Setter 注解,那么想要在编码阶段成功调用对象的 set 或 get 方法,我们需要在 IDE 中安装 Lombok 插件才行,比如 Idea 的插件如下图所示:
接下来讲讲 Lombok 的原理。
Lombok 的实现和反射没有任何关系,前面我们说了反射是程序在运行期的一种自省(introspect)能力,而 Lombok 的实现是在编译期就完成了,为什么这么说呢?
回到我们刚才 Setter/Getter 的方法,当我们打开 Person 的编译类就会发现,使用了 Lombok 的 @Data 注解后的源码竟然是这样的:
可以看出 Lombok 是在编译期就为我们生成了对应的字节码。
其实 Lombok 是基于 Java 1.6 实现的 JSR 269: Pluggable Annotation Processing API 来实现的,也就是通过编译期自定义注解处理器来实现的,它的执行步骤如下:
从流程图中可以看出,在编译期阶段,当 Java 源码被抽象成语法树(AST)之后,Lombok 会根据自己的注解处理器动态修改 AST,增加新的代码(节点),在这一切执行之后就生成了最终的字节码(.class)文件,这就是 Lombok 的执行原理。
3.动态代理知识点扩充
当面试官问动态代理的时候,经常会问到它和静态代理的区别?静态代理其实就是事先写好代理类,可以手工编写也可以使用工具生成,但它的缺点是每个业务类都要对应一个代理类,特别不灵活也不方便,于是就有了动态代理。
动态代理的常见使用场景有 RPC 框架的封装、AOP(面向切面编程)的实现、JDBC 的连接等。
Spring 框架中同时使用了两种动态代理 JDK Proxy 和 CGLib,当 Bean 实现了接口时,Spring 就会使用 JDK Proxy,在没有实现接口时就会使用 CGLib,我们也可以在配置中指定强制使用 CGLib,只需要在 Spring 配置中添加 <aop:aspectj-autoproxy proxy-target-/> 即可。
小结
今天我们介绍了 JDK Proxy 和 CGLib 的区别,JDK Proxy 是 Java 语言内置的动态代理,必须要通过实现接口的方式来代理相关的类,而 CGLib 是第三方提供的基于 ASM 的高效动态代理类,它通过实现被代理类的子类来实现动态代理的功能,因此被代理的类不能使用 final 修饰。
除了 JDK Proxy 和 CGLib 之外,我们还讲了 Java 中常用的工具类 Lombok 的实现原理,它其实和反射是没有任何关系的;最后讲了动态代理的使用场景以及 Spring 中动态代理的实现方式,希望本文可以帮助到你。
作者:Java面试真题解析
来源:https://mp.weixin.qq.com/s/UIMeWRerV9FhtEotV8CF9w
Java内存区域异常
一、内存区域划分
Java程序执行时在逻辑上按照功能划分为不同的区域,其中包括方法区、堆区、Java虚拟机栈、本地方法栈、程序计数器、执行引擎、本地库接口、本地方法库几个部分,各个区域宏观结构如下,各部分详细功能及作用暂不展开论述。
JVM内存自动管理是Java语言的一大优良特性,也是Java语言在软件市场长青的一大优势,有了JVM的自动内存管理,程序员不再为令人抓狂的内存泄漏担忧,但深入理解Java虚拟机内存区域依然至关重要,JVM内存自动管理并非永远万无一失,不当的使用也会造成OOM等内存异常,本文尝试列举Java内存溢出相关的常见异常并提供使用建议。
二、栈溢出
方法调用深度过大
我们知道Java虚拟机栈及本地方法栈中存放的是方法调用所需的栈帧,栈帧是一种包括局部变量表、操作数栈、动态链接和方法返回地址的数据结构,每一次方法的调用和返回对应着压栈及出栈操作。Java虚拟机栈及本地方法栈由每个Java执行线程独享,当方法嵌套过深直至超过栈的最大空间时,当再次执行压栈操作,将抛出StackOverflowError异常。为演示栈溢出,假设存在如下代码:
/**
* 最大栈深度
*/
public class MaxInvokeDeep {
private static int count = 0;
/**
* 无终止条件的递归调用,每次调用将进行压栈操作,超过栈最大空间后,将抛出stackOverFlow异常
*/
public static void increment() {
count++;
increment();
}
public static void main(String[] args) {
try {
increment();
} catch (Throwable e) {
System.out.println("invokeDeep:" + count);
e.printStackTrace();
}
}
}
对于示例中的increment()方法,每次方法调用将深度+1,通过不断的栈帧压栈操作,当栈中空间无法再进行扩展时,程序将抛出StackOverflowError异常。Java虚拟机栈的空间大小可以通过JVM参数调整,我们先设置栈空间大小为512K(-Xss512k),程序执行结果如下:(方法调用5355次后触发栈溢出) 我们再将栈空间缩小至256k(-Xss256k),程序执行结果如下:(方法调用2079次时将触发栈溢出):
由此可见,Java虚拟机栈的空间是有限的,当我们进行程序设计时,应尽量避免使用递归调用。生产环境抛出StackOverflowError异常时,可以排查系统中是否存在调用深度过大的方法。方法的不断嵌套调用,不但会占用更多的内存空间,也会影响程序的执行效率。当我们进行程序设计时需要充分考虑并设置合理的栈空间大小,一般情况下虚拟机默认配置即满足大部分应用场景。
局部变量表过大
局部变量表用于存放Java方法中的局部变量,我们需要合理的设置局部变量,避免过多的冗余变量产生,否则可能会导致栈溢出,沿用刚刚的实例代码,我们定义一个创建大量局部变量的重载方法,则在栈空间不变的情况下(-Xss512k),创建大量局部变量的方法将降低栈调用深度,更容易触发StackOverflowError异常,通过分别执行increment及其重载方法,其中代码及执行结果如下:
package org.learn.jvm.basic;
/**
* 最大栈深度
*/
public class MaxInvokeDeep {
private static int count = 0;
/**
* 无终止条件的递归调用,每次调用将进行压栈操作,超过栈最大空间后,将抛出stackOverFlow异常
*/
public static void increment() {
count++;
increment();
}
/**
* 创建大量局部变量,导致局部变量表过大,影响栈调用深度
*
* @param a
* @param b
* @param c
*/
public static void increment(long a, long b, long c) {
long d = 1, e = 2, f = 3, g = 4, h = 5, i = 6,
j = 7, k = 8, l = 9, m = 10;
count++;
increment(a, b, c);
}
public static void main(String[] args) {
try {
//increment();
increment(1, 2, 3);
} catch (Throwable e) {
System.out.println("invokeDeep:" + count);
e.printStackTrace();
}
}
}
执行increment()时,最大栈深度为5355(栈空间大小-Xss512k)
执行increment(1,2,3)时,最大栈深度为1487(栈空间大小-Xss512k)
通过对比我们知道,创建大量的局部变量将使得局部变量表膨胀从而引发StackOverflowError异常,程序设计时应当尽量避免同一个方法中包含大量局部变量,实在无法避免可以考虑方法的重构及拆分。
三、OOM
对于Java程序员而言,OOM算是比较常见的生产环境异常,OOM往往容易引发线上事故,因此有必要梳理常见可能导致OOM的场景,并尽量规避保障服务的稳定性。
创建大量的类
方法区主要的职责是用于存放类型相关信息,如类名、接口名、父类、访问修饰符、字段描述、方法描述等,Java8以后方法区从永久区移到了Metaspace,若系统中创建过多的类时,可能会引发OOM。Java开发中经常会利用字节码增强技术,通过创建代理类从而实现系统功能,以我们熟知的Spring框架为例,经常通过Cglib运行时创建代理类实现类的动态性,通过设置虚拟机参数,-XX:MaxMetaspaceSize=10M,则示例程序运行一段时间时间后,将触发Full GC并最终抛出OOM异常,因此日常开发过程中要特别留意,系统中创建的类的数量是否合理,以免产生OOM。实例代码如下:
/**
* 大量创建类导致方法区OOM
*/
public class JavaMethodAreaOOM {
/**
* VM Args: -XX:MaxMetaspaceSize=10M(设置Metaspace空间为10MB)
*
* @param args
*/
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
Object object = enhancer.create();
System.out.println("hashcode:" + object.hashCode());
}
}
/**
* 对象
*/
static class OOMObject {
}
}
设置虚拟机参数:-Xms1M -Xmx1M -verbose:gc -XX:+PrintGCDetails 限定元数据区为10M,则虚拟机Full GC之后,将引发OOM异常,程序执行结果如下:
创建大对象
Java堆内存空间非常宝贵,若系统中存在数组、复杂嵌套对象等大对象,将会引发FullGc并最终引发OOM异常,因此程序设计时需要合理设置类结构,避免产生过大的对象,示例代码如下:
/**
* 堆内存溢出
*/
public class HeapOOM {
/**
*
*/
static class InnerClass {
private int value;
private byte[] bytes = new byte[1024];
}
/**
* VM Args: -Xms1M -Xmx1M -verbose:gc -XX:+PrintGCDetails
*
* @param args
*/
public static void main(String[] args) {
List<InnerClass> innerClassList = new ArrayList<>();
while (true) {
innerClassList.add(new InnerClass());
}
}
}
设置虚拟机参数:-Xms1M -Xmx1M -verbose:gc -XX:+PrintGCDetails 限定最大堆内存空间为10M,则虚拟机Full GC之后,将引发OOM异常,程序执行结果如下。
常量池溢出
Java8开始,常量池从永久区移至堆区,当系统中存在大量常量并超过常量池最大容量时,将引发OOM异常。String类的intern()方法内部逻辑是:若常量池中存在字符串常量,则直接返回字符串引用,否则创建常量将其加入常量池中并返回常量池引用。通过每次创建不同的字符串,常量池将因为无法容纳新创建的字符串常量而最终引发OOM,示例代码如下:
/**
* VM Args: -Xms1M -Xmx1M -verbose:gc -XX:+PrintGCDetails
*
* @param args
*/
public static void main(String[] args) {
Set<String> set = new HashSet<>();
int i = 0;
while (true) {
set.add(String.valueOf(i++).intern());
}
}
设置虚拟机参数:-Xms1M -Xmx1M -verbose:gc -XX:+PrintGCDetails 限定最大堆内存空间为10M,则虚拟机Full GC之后,将引发OOM异常,程序执行结果如下。
直接内存溢出
直接内存是因NIO技术而引入的独立于堆区的一块内存空间,对于读写频繁的场景,通过直接操作直接内存可以获得更高的执行效率,但其内存空间受制于操作系统本地内存大小,超过最大限制后,也将抛出OOM 异常,以下代码通过UnSafe类,直接操作内存,程序执行一段时间后,将引发OOM异常。
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
/**
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
* @param args
* @throws IllegalAccessException
*/
public static void main(String[] args) throws IllegalAccessException {
Field field = Unsafe.class.getDeclaredFields()[0];
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
while (true) {
unsafe.allocateMemory(_1MB*1024);
}
}
}
设置虚拟机参数:-Xmx20M -XX:MaxDirectMemorySize=10M 限定最大直接内存空间为10M,程序最终执行结果如下:
四、总结
本文通过实际的案例列举了常见的OOM异常,由此我们可以得知,程序设计过程中应当合理的设置栈区、堆区、直接内存的大小,从实际情况出发,合理设计数据结构,从而避免引发OOM故障,此外通过分析引发OOM的原因也有利于我们针对深入理解JVM并对现有系统进行系统调优。
作者:洞幺幺洞
来源:https://juejin.cn/post/7043442191619850277
消息队列的使用场景是什么样的?
本文从异步、解耦、削峰填谷等核心应用场景,以及消息中间件常用协议、推拉模式对比来解答此问题。
什么是消息中间件
作为一种典型的消息代理组件(Message Broker),是企业级应用系统中常用的消息中间件,主要应用于分布式系统或组件之间的消息通讯,提供具有可靠、异步和事务等特性的消息通信服务。应用消息代理组件可以降低系统间耦合度,提高系统的吞吐量、可扩展性和高可用性。
分布式消息服务主要涉及五个核心角色,消息发布者(Publisher)、可靠消息组件(MsgBroker)、消息订阅者(Subscriber)、消息类型(Message Type)和订阅关系(Binding),具体描述如下:
消息发布者,指发送消息的应用系统,一个应用系统可以发送一种或者多种消息类型,发布者发送消息到可靠消息组件 (MsgBroker)。
可靠消息组件,即 MsgBroker,负责接收发布者发送的消息,根据消息类型和订阅关系将消息分发投递到一个或多个消息订阅者。整个过程涉及消息类型校验、消息持久化存储、订阅关系匹配、消息投递和消息恢复等核心功能。
消息订阅者,指订阅消息的应用系统,一个应用系统可以订阅一种或者多种消息类型,消息订阅者收到的消息来自可靠消息组件 (MsgBroker)。
消息类型:一种消息类型由 TOPIC 和 EVENTCODE 唯一标识。
订阅关系,用来描述一种消息类型被订阅者订阅,订阅关系也被称为 Binding。
核心功能特色
可为不同应用系统间提供可靠的消息通信,降低系统间耦合度并提高整体架构的可扩展性和可用性。
可为不同应用系统间提供异步消息通信,提高系统吞吐量和性能。
发布者系统、消息代理组件以及订阅者系统均支持集群水平扩展,可依据业务消息量动态部署计算节点。
支持事务型消息,保证消息与本地数据库事务的一致性。
远程调用RPC和消息MQ区别
谈到消息队列,有必要看下RPC和MQ的本质区别,从两者的定义和定位来看,RPC(Remote Procedure Call)远程过程调用,主要解决远程通信间的问题,不需要了解底层网络的通信机制;消息队列(MQ)是一种能实现生产者到消费者单向通信的通信模型。核心区别在于RPC是双向直接网络通讯,MQ是单向引入中间载体的网络通讯。单纯去看队列,队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。在队列前面增加限定词“消息”,意味着通过消息驱动来进行整体的架构实现。RPC和MQ本质上是网络通讯的两种不同的实现机制,RPC同步等待结果对比于MQ在异步、解耦、削峰填谷等上的特征显著差异主要有以下几点差异:
在架构上,RPC和MQ的差异点是,Message有一个中间结点Message Queue,可以把消息存储起来。
同步调用:对于要立即等待返回处理结果的场景,RPC是首选。
MQ的使用,一方面是基于性能的考虑,比如服务端不能快速的响应客户端(或客户端也不要求实时响应),需要在队列里缓存;另外一方面,它更侧重数据的传输,因此方式更加多样化,除了点对点外,还有订阅发布等功能。
随着业务增长,有的处理端调用下游服务太多或者处理量会成为瓶颈,会进行同步调用改造为异步调用,这个时候可以考虑使用MQ。
核心应用场景
针对MQ的核心场景,我们从异步、解耦、削峰填谷等特性进行分析,区别于传统的RPC调用。尤其在引入中间节点的情况下,通过空间(拥有存储能力)换时间(RPC同步等待响应)的思想,增加更多的可能性和能力。
异步通信
针对不需要立即处理消息,尤其那种非常耗时的操作,通过消息队列提供了异步处理机制,通过额外的消费线程接管这部分进行异步操作处理。
解耦
在应用和应用之间,提供了异构系统之间的消息通讯的机制,通过消息中间件解决多个系统或异构系统之间除了RPC之外另一种单向通讯的机制。
扩展性
因为消息队列解耦了主流程的处理过程,只要另外增加处理过程即可,不需要改变代码、不需要调整参数,便于分布式扩容。
分布式事务一致性
在2个应用系统之间的数据状态同步,需要考虑数据状态的最终一致性的场景下,利用消息队列所提供的事务消息来实现系统间的数据状态一致性。
削峰填谷
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量无法提前预知;如果为了能处理这类瞬间峰值访问提前准备应用资源无疑是比较大的浪费。使用消息队列在突发事件下的防脉冲能力提供了一种保障,能够接管前台的大脉冲请求,然后异步慢速消费。
可恢复性
系统的一部分组件失效时,不会影响到整个系统。消息队列降低了应用间的耦合度,所以即使一个处理消息的应用挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
顺序保证
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来进行处理。
大量堆积
通过消息堆积能力处理数据迁移场景,针对旧数据进行全量迁移的同时开启增量消息堆积,待全量迁移完毕,再开启增量,保证数据最终一致性且不丢失。
数据流处理
分布式系统产生的海量数据流,如:业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后导入到大数据实时计算引擎,通过消息队列解决异构系统的数据对接能力。
业界消息中间件对比
详细的对比可以参考:blog.csdn.net/wangzhipeng…
消息中间件常用协议
AMQP协议
AMQP即Advanced Message Queuing Protocol,提供统一消息服务的高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。
优点:可靠、通用
MQTT协议
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和致动器(比如通过Twitter让房屋联网)的通信协议。
优点:格式简洁、占用带宽小、移动端通信、PUSH、嵌入式系统
STOMP协议
STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息的中间件)设计的简单文本协议。STOMP提供一个可互操作的连接格式,允许客户端与任意STOMP消息代理(Broker)进行交互。
优点:命令模式(非topic/queue模式)
XMPP协议
XMPP(可扩展消息处理现场协议,Extensible Messaging and Presence Protocol)是基于可扩展标记语言(XML)的协议,多用于即时消息(IM)以及在线现场探测。适用于服务器之间的准即时操作。核心是基于XML流传输,这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同。
优点:通用公开、兼容性强、可扩展、安全性高,但XML编码格式占用带宽大
基于TCP/IP自定义的协议
有些特殊框架(如:redis、kafka、rocketMQ等)根据自身需要未严格遵循MQ规范,而是基于TCP/IP自行封装了一套二进制编解码协议,通过网络socket接口进行传输,实现了MQ的标准规范相关功能。
消息中间件推和拉模式对比
Push推模式:服务端除了负责消息存储、处理请求,还需要保存推送状态、保存订阅关系、消费者负载均衡;推模式的实时性更好;如果push能力大于消费能力,可能导致消费者崩溃或大量消息丢失
Push模式的主要优点是:
对用户要求低,方便用户获取需要的信息
及时性好,服务器端即时地向客户端推送更行的动态信息
Push模式的主要缺点是:
推送的信息可能并不能满足客户端的个性化需求
Push消息大于消费者消费速率额,需要有协调QoS机制做到消费端反馈
Pull拉模式:客户端除了消费消息,还要保存消息偏移量offset,以及异常情况下的消息暂存和recover;不能及时获取消息,数据量大时容易引起broker消息堆积。
Pull拉模式的主要优点是:
针对性强,能满足客户端的个性化需求
客户端按需获取,服务器端只是被动接收查询,对客户端的查询请求做出响应
Pull拉模式主要的缺点是:
实时较差,针对于服务器端实时更新的信息,客户端难以获取实时信息
对于客户端用户的要求较高,需要维护位点
相关资料
建议学习以下的技术文档,了解更多详细的技术细节和实现原理,加深对消息中间件的理解和应用,同时可以下载开源的源代码,本地调试相应的代码,加深对技术原理的理解和概念的掌握,以及在实际生产中更多的掌握不同的消息队列应用的场景下,高效和正确地使用消息中间件。
RocketMQ资料: github.com/apache/rock…
Kafka资料: kafka.apache.org/documentati…
阿里云RocketMQ文档: help.aliyun.com/document_de…
作者:阿里巴巴淘系技术
来源:https://juejin.cn/post/7025955365812437028
业务实战中经典算法的应用
有网友提问:各种机器学习算法的应用场景分别是什么(比如朴素贝叶斯、决策树、K 近邻、SVM、逻辑回归最大熵模型)?
这些在一般工作中分别用到的频率多大?一般用途是什么?需要注意什么?
根据问题,核心关键词是基础算法和应用场景,比较担忧的点是这些基础算法能否学有所用?毕竟,上述算法是大家一上来就接触的,书架上可能还放着几本充满情怀的《数据挖掘导论》《模式分类》等经典书籍,但又对在深度学习时代基础算法是否有立足之地抱有担忧。
网上已经有很多内容解答了经典算法的基本思路和理论上的应用场景,这些场景更像是模型的适用范围,这与工业界算法实际落地中的场景其实有很大区别。
从工业界的角度看,业务价值才是衡量算法优劣的金钥匙,而业务场景往往包含业务目标、约束条件和实现成本。如果我们只看目标,那么前沿的算法往往占据主导,可如果我们需兼顾算法运行复杂度、快速迭代试错、各种强加的业务限制等,经典算法往往更好用,因而就占据了一席之地。
针对这个问题,淘系技术算法工程师感知同学写出本文详细解答。
在实际业务价值中,算法模型的影响面大致是10%
在工业界,算法从想法到落地,你不是一个人在战斗。我以推荐算法为例,假设我们现在接到的任务是支持某频道页feeds流的推荐。我们首先应该意识到对业务来说,模型的影响面大致是10%,其他几个重要影响因子是产品设计(40%)、数据(30%)、领域知识的表示和建模(20%)。
这意味着,你把普通的LR模型升级成深度模型,即使提升20%,可能对业务的贡献大致只有2%。当然,2%也不少,只是这个折扣打的让人脑壳疼。
当然,上面的比例划分并不是一成不变的,在阿里推荐算法元年也就是2015年起,个性化推荐往往对标运营规则,你要是不提升个20%都不好意思跟人打招呼。那么一个算法工程师的日常除了接需求,就是做优化:业务给我输入日志、特征和优化目标,剩下的事情就交给我吭哧吭哧。
可随着一年年的水涨船高,大家所用模型自然也越来越复杂,从LR->FTRL->WDL->DeepFM->MMOE,大家也都沿着前辈们躺过的路径一步步走下去。这时候你要是问谁还在用LR或是普通的决策树,那确实会得到一个尴尬的笑容。
但渐渐的,大家也意识到了,模型优化终究是一个边际收益递减的事情。当我们把业务方屏蔽在外面而只在一个密闭空间中优化,天花板就已经注定。于是渐渐的,淘系的推荐慢慢进入第二阶段,算法和业务共建阶段。业务需求和算法优化虽然还是分开走,但已经开始有融合的地方。
集团对算法工程师的要求也在改变:一个高大上的深度模型,如果不能说清楚业务价值,或带来特别明显提升,那么只能认为是自嗨式的闭门造车。这时,一个优秀的算法工程师,需要熟悉业务,通过和业务反复交流中,能够弄清楚业务痛点。
注意,业务方甚至可能当局者迷,会提出既要又要还要的需求给你,而你需要真正聚焦到那个最值得做的问题上。然后,才是对问题的算法描述。做到这一步你会发现,并不是你来定哪个模型牛逼,而是跟着问题走来选择模型。这个模型的第一版极大可能是一个经典算法,因为,你要尽快跑通链路,快速验证你的这个idea是有效的。后面的模型迭代提升,只是时间问题。
经典算法在淘系的应用场景示例:TF-IDF、K近邻、朴素贝叶斯、逻辑回归等
因而现阶段,在淘系的大多数场景中,并不是算法来驱动业务,而是配合业务一起来完成增长。一个只懂技术的算法工程师,最多只能拿到那10%的满分。为了让大家有体感,这里再举几个小例子:
比如业务问题是对用户进行人群打标,人群包括钓鱼控、豆蔻少女、耳机发烧友、男神style等。在实操中我们不仅需考虑用户年龄、性别、购买力等属性,还需考虑用户在淘系的长期行为,从而得到一个多分类任务。如果模型所用的的特征是按月访问频次,那么豆蔻少女很可能网罗非常多的用户,因为女装是淘系行为频次最多的类目。
比如,某用户对耳机发烧友和豆蔻少女一个月内都有4次访问,假设耳机发烧友人均访问次数是3.2次,而豆蔻少女是4.8次,那么可知该用户对耳机发烧友的偏好分应更高。因此,模型特征不仅应该使用用户对该人群的绝对行为频次,还需参照大盘的水位给出相对行为频次。
这时,入选吴军老师《数学之美》的TF-IDF算法就派上用场了。通过引入TF-IDF构建特征,可以显著提高人群标签的模型效果,而TF-IDF则是非常基础的文本分类算法。
在淘系推荐场景,提升feeds流的点击率或转化率往往是一个常见场景。可业务总会给你惊喜:比如商品的库存只有一件(阿里拍卖),比如推荐的商品大多是新品(天猫新品),比如通过小样来吸引用户复购正品,这些用户大多是第一次来(天猫U先),或者是在提升效率的同时还需兼顾类目丰富度(很多场景)。
在上述不同业务约束背景,才是我们真实面对的应用场景。面对这种情况首先是定方向,比如在阿里拍卖中的问题可描述为“如何在浅库存约束下进行个性化推荐”。假如你判断这是一个流量调控问题,就需要列出优化目标和约束条件,并调研如何用拉格朗日乘子法求解。重要的是,最终的结果还需要和个性化推荐系统结合。详见:阿里拍卖全链路导购策略首次揭秘。
面对上述应用场景,你需明白你的战略目标是证明浅库存约束下的推荐是一个流量调控问题,并可以快速验证拿到效果。采用一个成熟经典的方法先快速落地实验,后续再逐步迭代是明智的选择。
又比如,K近邻算法似乎能简单到能用一张图或一句话来描述。可它在解决正负样本不均衡问题中就能派上用场。上采样是说通过将少数类(往往是正样本)的数据复制多份,但上采样后存在重复数据集可能会导致过拟合。过拟合的一种原因是在局部范围正负样本比例存在差异,如下图所示:
我们只需对C类局部样本进行上采样,这其中就运用到了K近邻算法。本例中的经典算法虽然只是链路的一部分,甚至只是配角儿,但离了它不行。
朴素贝叶斯虽然简单了点,但贝叶斯理论往后的发展,包括贝叶斯网络、因果图就不那么simple了。比如不论是金融业务中的LR评分卡模型,或是推荐算法精排中的深度模型,交叉特征往往由人工经验配置,这甚至是算法最不自动的一个环节。
使用贝叶斯网络中的structure learning,并结合业务输入的行业知识,构建出贝叶斯概率图,并从中发现相关特征做交叉,会比人工配置带来一定提升,也具有更好的可解释性。这就是把业务领域知识和算法模型结合的一个很好的例子。可如果贝叶斯理论不扎实,很难走到这一步。
不论深度学习怎么火,LR都是大多场景的一个backup。比如在双十一大促投放场景中,如果在0:00~0:30这样的峰值期,所有场景都走深度模型,机器资源肯定不足,这时候就需要做好一个使用LR或FTRL的降级预案。
如何成为业界优秀的算法工程师?
在淘系,算法并不是孤立的存在,算法工程师也不只是一个闭关的剑客。怎么切中业务痛点,快速验证你的idea,怎么进行合适的算法选型,这要求你有很好的算法基本功,以及较广的算法视野。一个模型无论搞的多复杂最终的回答都是业务价值,一个有良好基本功、具备快速学习能力,且善于发掘业务价值的算法工程师,将有很大成长空间。
好吧,假设上面就是我们的职业目标,可怎么实现呢。元(ye)芳(jie),你怎么看?其实只有一个句话,理论和实践相结合。
理论
对算法基本思路的了解,查查知乎你可能只需要20分钟,可你忘记它可能也只需要2周。这里的理论是你从内而外对这个算法的感悟,比如说到决策树,你脑海里好像就能模拟出它的信息增益计算、特征选择、节点分裂的过程,知道它的优和劣。最终,都是为了在实践中能快速识别出它就是最适合解决当前问题的模型。
基本功弄扎实是一个慢活儿,方法上可以是看经典原著、学习视频分享或是用高级语言实现。重要的是心态,能平心静气,不设预期,保持热情;另外,如果你有个可以分享的兴趣小组,那么恭喜你。因为学习书籍或看paper的过程其实挺枯燥的,但如果有分享的动力你可以走的更远。
实践
都知道实践出真知,可实践往往也很残酷。因为需求和约束多如牛毛,问题需要你来发现,留给你的时间窗口又很短暂。也就是,算法是一个偏确定性、有适用边界、标准化的事情;而业务则是发散的、多目标的、经验驱动的事情。
你首先是需要有双发现的眼睛,找到那个最值得发力的点,这个需要数据分析配合业务经验,剥丝抽茧留下最主要的优化目标、约束条件,尽量的简化问题;其次是有一张能说会道的嘴,要不然业务怎么愿意给你这个时间窗口让你尝试呢;最后是,在赌上你的人设之后,需要尽快在这个窗口期做出效果,在这个压力之下,考验你算法基本功的时候就到了。
结语
最后,让大家猜个谜语:它是一个贪心的家伙,计算复杂度不大,可以动态的做特征选择,不论特征是离散还是连续;它还可以先剪枝或后剪枝避免过拟合;它融合了信息熵的计算,也可以不讲道理的引入随机因子;它可以孤立的存在,也完全可以做集成学习,还可以配合LR一起来解决特征组合的问题;对小样本、正负样本不均、在线数据流的学习也可以支持;所得到则是有很强可解释性的规则。它,就是决策树。
每一个基础算法,都好比是颗带有智慧的种子,这不是普通的锦上添花,而是石破天惊的原创思维。有时不妨傻傻的回到过去的年代,伴着大师原创的步伐走一走,这种原创的智慧对未来长期的算法之路,大有裨益。神经网络从种下到开花,将近50年;贝叶斯也正在开花结果的路上。下一个,将会是谁呢?
作者:阿里巴巴淘系技术
来源:https://juejin.cn/post/7025964095530598436
背包问题_概述(动态规划)
写在前
问题描述
有N件物品和一个最多能被重量为W 的背包。一个物品只有两个属性:重量和价值。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
注意:0-1 背包问题无法使用贪心算法来求解,也就是说不能按照先添加性价比最高的物品来达到最优,这是因为这种方式可能造成背包空间的浪费,从而无法达到最优。
基本思路
这里有两个可变量体积和价值,我们定义dp[i][j]表示前i件物品体积不超过j能达到的最大价值
,设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论:
第 i 件物品没添加到背包,最大价值:
dp[i][j] = dp[i - 1][j]
。第 i 件物品添加到背包中:
dp[i][j] = dp[i - 1][j - w] + v
。
第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。因此,0-1 背包的状态转移方程为: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w] + v)
代码实现
// W 为背包总重量
// N 为物品数量
// weights 数组存储 N 个物品的重量
// values 数组存储 N 个物品的价值
public int knapsack(int W, int N, int[] weights, int[] values) {
// dp[i][0]和dp[0][j]没有价值已经初始化0
int[][] dp = new int[N + 1][W + 1];
// 从dp[1][1]开始遍历填表
for (int i = 1; i <= N; ++i) {
// 第i件物品的重量和价值
int w = weights[i - 1], v = values[i - 1];
for (int j = 1; j <= W; ++j) {
if (j < w) {
// 超过当前状态能装下的重量j
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i]);
}
}
}
return dp[N][W];
}
dp[i][j]
的值只与dp[i-1][0,...,j-1]
有关,所以我们可以采用动态规划常用的方法(滚动数组)对空间进行优化(即去掉dp的第一维)。因此,0-1 背包的状态转移方程为: dp[j] = max(dp[j], dp[j - w] + v)
特别注意:为了防止上一层循环的dp[0,...,j-1]
被覆盖,循环的时候 j 只能逆向遍历。优化空间复杂度:
ps:滚动数组:即让数组滚动起来,每次都使用固定的几个存储空间,来达到压缩,节省存储空间的作用。
public int knapsack(int W, int N, int[] weights, int[] values) {
int[] dp = new int[W + 1];
for (int i = 1; i <= N; i++) {
int w = weights[i - 1], v = values[i - 1];
for (int j = W; j >= 1; --j) {
if (j >= w) {
dp[j] = Math.max(dp[j], dp[j - w] + v);
}
}
}
return dp[W];
}
ps:01背包内循环理解:还原成二维的dp就很好理解,一维的dp是二维dp在空间上进行复用的结果。dp[i]=f(dp[i-num])
,等式的右边其实是二维dp上一行的数据,应该是只读的,在被读取前不应该被修改。如果正序的话,靠后的元素在读取前右边的dp有可能被修改了,倒序可以避免读取前被修改的问题。
作者:_code_x
来源:https://www.jianshu.com/p/b789ec845641
驳“低代码开发取代程序员”论 为什么专业开发者也需要低代码?
低代码又火了。
近几年,腾讯、阿里、百度等互联网大厂纷纷入局,国内外低代码平台融资动辄数千万甚至数亿,以及伴随着热度而来的巨大争议……无不说明“低代码”的火爆。
事实上,低代码并非新概念,它可以追溯到上世纪80年代的“第四代编程语言”。2014年,Forrester正式提出低代码的概念。低代码是一种软件开发技术,衍生于软件开发的高级语言,让使用者通过可视化的方式,以更少的编码,更快速地构建和交付应用软件,全方位降低软件的开发成本。与传统软件开发方式相比,低代码开发平台整合了软件开发和部署所需的 IDE(集成开发环境)、服务器和数据库管理工具,覆盖软件开发的全生命周期,我们可以将其理解为 Visual Studio + IIS + SQL Management Studio(.NET 技 术)或 Eclipse + Tomcat + MySQL Workbench(Java 技术)的组合。
编码更少、交付更快、成本更低,还覆盖软件开发全生命周期,怎么看低代码都可以说是不错的软件开发工具。那么,它又为什么引发争议,甚至被其主要用户群体之一——程序员所诟病呢?“低代码开发会取代程序员” 这一观点大行其是,它说得对吗?
为什么低代码引起专业开发者的反感?
技术浪潮引发巨大变革,也带来了无数“取代论”,比如机器翻译是否取代人类翻译、机器人记者是否取代人类记者,以及低代码开发是否取代程序员。
低代码虽然火爆,但程序员对此抱有不同的心态:
轻视:低代码技术的诸多优势只是炒作,该技术更适合初学者,解决不了复杂的技术问题;
恐惧:担心被低代码取代;
抵触:低代码开发平台能够覆盖所有需求吗;大量封装组件使得低代码开发平台更像一个黑盒子,可能导致难以debug、难以修改和迭代升级等技术问题;低代码开发平台配置有大量组件,简单的拖拉拽动作即可完成大量开发工作,程序员不再需要厉害的技术能力。
那么,上述理由真的站得住脚吗?我们一一来看。
低代码的门槛真的低吗?
低代码开发过程常被比作拼积木:像拼搭积木一样,以可视化的方式,通过拖拉拽组件快速开发出数据填报、流程审批等应用程序,满足企业里比较简单的办公需求。
但这并不意味着低代码开发平台只能做到这些。
Gartner在2020年9月发布的《企业级低代码开发平台的关键能力报告》(Critical Capabilities for Enterprise Low-Code Application Platforms)中,列举了低代码的11项关键能力。
图源:http://www.gartner.com/en/document…
这里我们着重来看其中三项关键能力。
数据建模和管理:该指标就是通常所讲的 “模型驱动” 。相比于表单驱动,模型驱动能够提供满足数据库设计范式的数据模型设计和管理能力。开发的应用复杂度越高,系统集成的要求越高,这个能力就越关键。
流程和业务逻辑:流程应用与业务逻辑开发能力和效率。这个能力有两层,第一层是指使用该低代码开发平台能否开发出复杂的工作流和业务处理逻辑;第二层是开发这些功能时的便利性和易用性程度有多高。
接口和集成:编程接口与系统集成能力。为了避免“数据孤岛”现象,企业级应用通常需要与其他系统进行集成,协同增效。此时,内置的集成能力和编程接口就变得至关重要。除非确认可预期的未来中,项目不涉及系统集成和扩展开发,开发者都应该关注这个能力。
这些关键能力表明低代码平台在建模与逻辑方面具备较强的能力,而接口和集成能力可使专业开发人员完成低代码无法实现的部分,通过低代码与专业代码开发的协作实现复杂应用的开发。 在涉及高价值或复杂的核心业务时,专业开发人员需要理解业务需求,厘清业务逻辑。从这个层面上看,低代码开发的门槛并不低。事实也是如此:海比研究在《2021 年中国低代码/无代码市场研究报告》中提到,截至 2020 年底,技术人员在低代码使用者中的比例超 75%,占主体地位。
低代码什么都能做吗?
程序员的工作围绕开发需求展开。在选择开发工具时,程序员通常考虑的首要问题是:这款工具能否覆盖所有需求?如果需求增加或变更,该工具是否支持相关操作?这些问题同样适用于低代码平台的选型。
在实际项目交付过程中,如果我们仅可以满足99%的需求,另外1%的需求满足不了,那么真实用户大概率是不会买单的。因此,在评估低代码产品的时候,我们一定要保证该平台可以支撑所有系统模块类型的开发,同时也要具备足够的扩展性,确保使用纯代码开发出的模块能够与低代码模块进行无缝集成,而这离不开编程接口。
以国内主流低代码开发平台活字格为例。该平台提供开箱即用的开发组件,同时为系统的各个分层均提供编程扩展能力,以满足企业级应用开发对扩展性的高要求。借助分层编程接口,开发者可以用纯代码的方式实现新增功能,无需受限于低代码开发平台的版本和现有功能。
活字格的编程扩展能力
当然,就具体应用领域而言,低代码开发平台也有其擅长和不擅长的地方。目前,低代码开发更多地被应用于2B企业应用开发,而对于用户量特大的头部互联网应用、对算法和复杂数据结构要求较高的应用,低代码平台则不太适合。
低代码开发不可控?
“低代码开发平台是个黑盒子,内部出问题无法排查和解决。开发过程中发现有问题怎么办?迭代升级难以实现怎么办?”很多程序员会有这种疑惑。
但我们需要注意的是,低代码开发平台本质上仍是软件开发工具,用户模型与软件开发周期支持是其关键能力之一。也就是说,成熟的低代码开发平台具备软件开发全生命周期所需的各项功能,从而大大简化开发者的技术栈,进一步提高开发效率。
具体而言,在面对频繁的需求变更、棘手的问题排查时,低代码开发平台引入了版本管理机制,从而更高效地进行代码审查、版本管理与协调,以及软件的迭代升级。至于debug,日志分析无疑是个好办法。例如,活字格把执行过程及细节以日志方式输出,方便程序员高效debug。
对程序员而言,低代码平台是限制还是助力?
“低代码”意味着更少的代码。代码都不怎么写了,程序员又该怎么成长,怎么获得职业成就感呢?
其实不然。
首先,开发 ≠ 写代码。低代码平台可以减少大量重复工作,提升开发效率,把专业开发人员从简单、重复的开发需求中解放出来,把精力投入到更有价值的事情上,比如精进技术、理清业务逻辑。
其次,低代码平台的组件化和拖拽式配置降低了开发门槛,新手程序员能够借助此类平台快速入门,加速升级打怪;有经验的程序员也有机会参与更多项目,甚至带团队,积累更多经验值,实现快速成长。
宁波聚轩就是一个例子。这家公司自2009年起就专注于智能制造、工业4.0、系统方案集成等领域的探索研究。在接触了低代码之后,项目负责人发现开发效率得到极大提升,采用传统方式需要一个月开发量的项目,现在需要半个月甚至更短的时间就可以完成。此外,其实践经验表明,低代码开发的学习成本较低,毕业新生经过一周学习,两周就可做项目,一个月就能熟练开发。
该公司在2021企业级低代码应用大赛中获得了应用创新奖,获奖作品是一套轴承行业数字化智造系统。这套系统主要集成了ERP、MES、WMS和设备机联网系统,覆盖了销售、采购、仓库、计划、生产、财务等全流程功能,且已经在生产现场投入使用。在开发过程中,宁波聚轩的开发团队利用低代码平台成功解决了定制化要求高、多终端需求等难题,及时完成项目交付。
结语
当迷雾散尽,低代码开发平台重新露出高效率开发工具的本色时,你会选择它吗?
作者:SegmentFault思否
来源:https://juejin.cn/post/7023579572096466974
JVM整体结构
Java虚拟机主要负责自动内存管理、类加载与执行、主要包括执行引擎、垃圾回收器、PC寄存器、方法区、堆区、直接内存、Java虚拟机栈、本地方法栈、及类加载子系统几个部分,其中方法区与Java堆区由所有线程共享、Java虚拟机栈、本地方法栈、PC寄存器线程私有,宏观的结构如下图所示:
JVM结构图
类加载子系统
从文件或网络中加载Class信息,类信息存放于方法区,类的加载包括加载->验证->准备->解析->初始化->使用->卸载几个阶段,详细流程后续文章会介绍。
加载
从文件或网络中读取类的二进制数据、将字节流表示的静态存储结构转换为方法区运行时数据结构、并于堆中生成Java对象实例,类加载器既可以使用系统提供的加载器(默认),也可以自定义类加载器。
连接
连接分为验证、准备、解析3个阶段,验证阶段确保类加载的正确性、准备阶段为类的静态变量分配内存,并将其初始化为默认值、解析阶段将类中的符号引用转换为直接引用
初始化
初始化阶段负责类的初始化,Java中类变量初始化的方式有2种,声明类变量时指定初始值、静态代码块指定初始化,只有类被主动使用时才会触发类的初始化,类的初始化会先初始化父类,然后再初始化子类。
使用
类访问方法区内的数据结构的接口,对象是堆区的数据
卸载
程序执行了System.exit()、程序正常执行结束、JVM进程异常终止等
运行时数据区域
程序从静态的源代码到编译成为JVM执行引擎可执行的字节码,会经历类的加载过程,并于内存空间开辟逻辑上的运行时数据区域,便于JVM管理,其中各数据区域如下,其中垃圾回收JVM会自动自行管理
栈帧数据结构
Java中方法的执行在虚拟机栈中执行,为每个线程所私有,每次方法的调用和返回对应栈帧的压栈和出栈,其中栈帧中保存着局部变量表、方法返回地址、操作数栈及动态链接信息。
动态链接
Java中方法执行过程中,栈帧保存方法的符号引用,通过动态链接,将解析为符号引用。
内存管理
内存划分(逻辑上)
Java内存区域从逻辑上分为堆区和非堆区,Java8以前,非堆区又称为永久区,Java8以后统一为原数据区,堆区按照分代模型分为新生代和老年代,其中新生代分为Eden、so、s1,so和s1是大小相同的2块区域,生产环境可以根据具体的场景调整虚拟机内存分配比例参数,达到性能调优的效果。
堆区是JVM管理的最大内存区域,由所有线程共享,采用分代模型,堆区主要用于存放对象实例,堆可以是物理上不连续的空间,逻辑上连续即可,其中堆内存大小可以通过虚拟机参数-Xmx、-Xms指定,当堆无法继续扩展时,将抛出OOM异常。
运行时实例
假设存在如下的SimpleHeap测试类,则SimpleHeap在内存中的堆区、Java栈、方法区对应的映射关系如下图所示:
public class SimpleHeap {
/**
* 实例变量
*/
private int id;
/**
* 构造器
*
* @param id
*/
public SimpleHeap(int id) {
this.id = id;
}
/**
* 实例方法
*/
private void displayId() {
System.out.println("id:" + id);
}
public static void main(String[]args){
SimpleHeap heap1=new SimpleHeap(1);
SimpleHeap heap2=new SimpleHeap(2);
heap1.displayId();
heap2.displayId();
}
}
复制代码
同理,建设存在Person类,则创建对象实例后,内存中堆区、Java方法栈、方法区三者关系如下图:
直接内存
Java NIO库允许Java程序使用直接内存,直接内存是独立于Java堆区的一块内存区域,访问内存的速度优于堆区,出于性能考虑,针对读写频繁的场景,可以直接操作直接内存,它的大小不受Xmx参数的限制,但堆区内存和直接内存总和必须小于系统内存。
PC寄存器
线程私有空间,又称之为程序计数器,任意时刻,Java线程总在执行一个方法,正在执行的方法,我们称之为:"当前方法”,如果当前方法不是本地方法,则PC寄存器指向当前正在被执行的指令;若当前方法是本地方法,则PC寄存器的值是undefined。
垃圾回收系统
垃圾回收系统是Java虚拟机中的重要组成部分,其主要对方法区、堆区、直接内存空间进行回收,与C/C++不同,Java中所有对象的空间回收是隐式进行的,其中垃圾回收会根据GC算法自动完成内存管理。
作者:洞幺幺洞
来源:https://juejin.cn/post/7040742081236566029
Apache Log4j 漏洞(JNDI注入 CVE-2021-44228)
本周最热的事件莫过于 Log4j 漏洞,攻击者仅需向目标输入一段代码,不需要用户执行任何多余操作即可触发该漏洞,使攻击者可以远程控制用户受害者服务器,90% 以上基于 java 开发的应用平台都会受到影响。通过本文特推项目 2 你也能近距离感受这个漏洞的“魅力”,而特推 1 则是一个漏洞检测工具,能预防类似漏洞的发生。
除了安全相关的 2 个特推项目之外,本周 GitHub 热门项目还有高性能的 Rust 运行时项目,在你不知道用何词时给你参考词的反向词典 WantWords,还有可玩性贼高的终端模拟器 Tabby。
以下内容摘录自微博@HelloGitHub 的 GitHub Trending 及 Hacker News 热帖(简称 HN 热帖),选项标准:新发布
| 实用
| 有趣
,根据项目 release 时间分类,发布时间不超过 14 day 的项目会标注 New
,无该标志则说明项目 release 超过半月。由于本文篇幅有限,还有部分项目未能在本文展示,望周知 🌝
本文目录
本周特推
1.1 JNDI 注入测试工具:JNDI-Injection-Exploit
1.2 Apache Log4j 远程代码执行:CVE-2021-44228-Apache-Log4j-Rce
GitHub Trending 周榜
2.1 塞尔达传说·时之笛反编译:oot
2.2 终端模拟器:Tabby
2.3 反向词典:WantWords
2.4 CPU 性能分析和调优:perf-book
2.5 高性能 Rust Runtime:Monoio
往期回顾
1. 本周特推
1.1 JNDI 注入测试工具:JNDI-Injection-Exploit
本周 star 增长数: 200+
JNDI-Injection-Exploit 并非是一个新项目,它是一个可用于 Fastjson、Jackson 等相关漏洞的验证的工具,作为 JNDI 注入利用工具,它能生成 JNDI 链接并启动后端相关服务进而检测系统。
GitHub 地址→github.com/welk1n/JNDI…
1.2 Apache Log4j 远程代码执行:CVE-2021-44228-Apache-Log4j-Rce
本周 star 增长数: 1,500+
New
CVE-2021-44228-Apache-Log4j-Rce 是 Apache Log4j 远程代码执行,受影响的版本 < 2.15.0。项目开源 1 天便标星 1.5k+ 可见本次 Log4j 漏洞受关注程度。
GitHub 地址→github.com/tangxiaofen…
2. GitHub Trending 周榜
2.1 塞尔达传说·时之笛反编译:oot
本周 star 增长数:900+
oot 是一个反编译游戏塞尔达传说·时之笛的项目,目前项目处于半成品状态,会有较大的代码变更,项目从 scratch 中重新构建代码,并用游戏中发现的信息以及静态、动态分析。如果你想通过这个项目了解反编译知识,建议先保存好个人的塞尔代资产。
GitHub 地址→github.com/zeldaret/oo…
2.2 终端模拟器:Tabby
本周 star 增长数:1,800+
Tabby(曾叫 Terminus)是一个可配置、自定义程度高的终端模拟器、SSH 和串行客户端,适用于 Windows、macOS 和 Linux。它是一种替代 Windows 标准终端 conhost、PowerShell ISE、PuTTY、macOS terminal 的存在,但它不是新的 shell 也不是 MinGW 和 Cygwin 的替代品。此外,它并非一个轻量级工具,如果你注重内存,可以考虑 Conemu 或 Alacritty。
GitHub 地址→github.com/Eugeny/tabb…
2.3 反向词典:WantWords
本周 star 增长数:1,000+
WantWords 是清华大学计算机系自然语言处理实验室(THUNLP)开源的反向字词查询工具,反向词典并非是查询反义词的词典,而是基于目前网络词官广泛使用导致部分场景下,我们表达某个意思未能找到精准的用词,所以它可以让你通过想要表达的意思来找寻符合语境的词汇。你可以在线体验反向词典:wantwords.thunlp.org/ 。下图分别为项目 workflow 以及查询结果。
GitHub 地址→github.com/thunlp/Want…
2.4 CPU 性能分析和调优:perf-book
本周 star 增长数:1,300+
perf-book 是书籍《现代 CPU 的性能分析和调优》开源版本,你可以通过 python.exe export_book.py && pdflatex book.tex && bibtex book && pdflatex book.tex && pdflatex book.tex
命令导出 pdf。
GitHub 地址→github.com/dendibakh/p…
2.5 高性能 Rust Runtime:Monoio
本周 star 增长数:1,250+
New
Monoio 是字节开源基于 io-uring 的 thread-per-core 模型高性能 Rust Runtime,旨在为高性能网络中间件等场景提供必要的运行时。详细项目背景可以阅读团队的文章Monoio:基于 io-uring 的高性能 Rust Runtime。
GitHub 地址→github.com/bytedance/m…
3. 往期回顾
以上为 2021 年第 50 个工作周的 GitHub Trending 🎉如果你 Pick 其他好玩、实用的 GitHub 项目,记得来 HelloGitHub issue 区和我们分享下哟 🌝
最后,记得你在本文留言区留下你想看的主题 Repo(限公众号),例如:AI 换头。👀 和之前的送书活动类似,留言点赞 Top5 的小伙伴(棒),小鱼干会努力去找 Repo 的^^
作者:HelloGitHub
来源:https://juejin.cn/post/7040980646423953439
Redis分布式锁
需求
分布式应⽤进⾏逻辑处理时经常会遇到并发问题。
互斥访问某个网络上的资源,需要有一个存在于网络上的锁服务器,负责锁的申请与回收。Redis 可以充当锁服务器的角色。首先,Redis 是单进程单线程的工作模式,所有前来申请锁资源的请求都被排队处理,能保证锁资源的同步访问。
适用原因:
Redis 可以被多个客户端共享访问,是·共享存储系统,可以用来保存分布 式锁
Redis 的读写性能高,可以应对高并发的锁操作场景。
实现
在分布式场景下,锁变量需要由一个共享存储系统来维护,这样,多个客户端可以通过访问共享存储系统来访问锁变量。
简单实现
模仿单机上的锁,使用锁变量即可在Redis上实现分布式锁。
我们可以在 Redis 服务器设置一个键值对,用以表示一把互斥锁,当申请锁的时候,要求申请方设置(SET)这个键值对,当释放锁的时候,要求释放方删除(DEL)这个键值对。
但最基本需要保证加锁解锁操作的原子性。同时为了保证锁在异常情况下能被释放,必须设置超时时间。
Redis 2.8 版本中作者加⼊了 set 指令的扩展参数,使得 setnx 和 expire 指令可以⼀起执⾏
加锁原子操作
加锁包含了三个操作(读取锁变量、判断锁变量值以及把锁变量值设置为 1),而这 三个操作在执行时需要保证原子性。
首先是 SETNX 命令,它用于设置键值对的值。具体来说,就是这个命令在执行时会判断键 值对是否存在,如果不存在,就设置键值对的值,如果存在,就不做任何设置。
超时问题
Redis 的分布式锁不能解决超时问题,如果在加锁和释放锁之间的逻 辑执⾏的太长,超出了锁的超时限制,就无法保证互斥。
解决方案
最简单的就是避免Redis 分布式锁⽤于较⻓时间的任务。如果真的偶尔出现了,数据出现的⼩波错乱可能需要⼈⼯介⼊解决。
判断拥有者
为了防止锁变量被拥有者之外的客户端进程删除,需要能区分来自不同客户端的锁操作
set 指令的 value 参数设置为⼀个 随机数,释放锁时先匹配随机数是否⼀致,然后再删除 key,这是为 了确保当前线程占有的锁不会被其它线程释放,除⾮这个锁是过期了被服务器⾃动释放的。
Redis 给 SET 命令提供 了类似的选项 NX,用来实现“不存在即设置”。如果使用了 NX 选项,SET 命令只有在键 值对不存在时,才会进行设置,否则不做赋值操作。此外,SET 命令在执行时还可以带上 EX 或 PX 选项,用来设置键值对的过期时间。
可重入问题
可重⼊性是指线程在持有锁的情况下再次请求加锁,如果⼀个锁⽀持 同⼀个线程的多次加锁,那么这个锁就是可重⼊的。Redis 分布式锁如果要⽀持 可重⼊,需要对客户端的 set ⽅法进⾏包装,使⽤线程的 Threadlocal 变量存储当前持有锁的计数。
分布式拓展
单Redis实例并不能满足我们的高可用要求,一旦实例崩溃,就无法对外分布式锁服务。
但在集群环境下,这种只对主Redis实例使用上述方案是有缺陷 的,它不是绝对安全的。
一旦主节点挂掉,但锁变量没有及时同步,就会导致互斥被破坏。
Redlock
为了解决这个问题,Antirez 发明了 Redlock 算法
Redlock 算法的基本思路,是让客户端和多个独立的 Redis 实例依次请求加锁,如果客户 端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布 式锁了,否则加锁失败。
执行步骤:
获取当前时间
客户端按顺序依次向 N 个 Redis 实例执行加锁操作
一旦客户端完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过 程的总耗时。客户端只有在满足下面的这两个条件时,才能认为是加锁成功
客户端从超过半数(大于等于 N/2+1)的 Redis 实例上成功获取到了锁;
客户端获取锁的总耗时没有超过锁的有效时间。
作者:不是二向箔
来源:https://juejin.cn/post/7038155529566289957
短信跳小程序
方案:使用微信提供的url link方法 生成短链接 发送给用户 用户点击短链接会跳转到微信提供默认的默认页面 进而打开小程序
场景假设:经理人发布一条运输任务 司机收到短信点击打开小程序接单
实现:
经理人发布时点击发布按钮 h5调用服务端接口 传参服务端要跳转的小程序页面 所需要参数
服务端拿access_token和前端传的参数 加链接失效时间 调用微信api
api.weixin.qq.com/wxa/generat… 得到链接 如wxaurl.cn/ow7ctZP4n8v 将此链接发送短信给司机
司机点击此链接 效果如下图所示:打开小程序 h5写逻辑跳转指定页面
自己调postman调微信api post方式 接口: api.weixin.qq.com/wxa/generat…
传参
{
"path": "pages/index/index",\
"query": "?fromType=4&transportBulkLineId=111&isLinkUrlCome=1&SCANFROMTYPE=143&lineAssignRelId=111",\
"env_version": "trial",\
"is_expire": true,\
"expire_time": "1638855772"\
}
返参
{
"errcode": 0,
"errmsg": "ok",
"url_link": "https://wxaurl.cn/GAxGcil2Bbp"
}
url link说明文档: developers.weixin.qq.com/miniprogram…
url link方法需要服务端调用 调用接口方式参考: developers.weixin.qq.com/miniprogram…
作者:懿小诺
来源:https://juejin.cn/post/7037356611031007239
前端自动化部署:借助Gitlab CI/CD实现
🛫 前端自动化部署:借助Gitlab CI/CD实现
🌏 概论
传统的前端部署往往都要经历:本地代码更新
=> 本地打包项目
=> 清空服务器相应目录
=> 上传项目包至相应目录
几个阶段,这些都是机械重复的步骤。对于这一过程我们往往可以通过CI/CD
方法进行优化。所谓CI/CD
,即持续集成/持续部署,以上我们所说的步骤便可以看作是持续部署的一种形态,其更详细的解释大家可以自行了解。
Jenkins
、Travis CI
这些都是可以完成持续部署的工具。除此之外,Gitlab CI/CD
也能很好的完成这一需求。下面就来详细介绍下。
🌏 核心工具
GitLab Runner
GitLab Runner
是配合GitLab CI/CD
完成工作的核心程序,出于性能考虑,GitLab Runner
应该与Gitlab
部署在不同的服务器上(Gitlab
在单独的仓库服务器上,GitLab Runner
在部署web应用的服务器上)。GitLab Runner
在与GitLab
关联后,可以在服务器上完成诸如项目拉取、文件打包、资源复制等各种命令操作。
Git
web服务器上需要安装Git
来进行远程仓库的获取工作。
Node
用于在web服务器上完成打包工作。
NPM or Yarn or pnpm
用于在web服务器上完成依赖下载等工作(用yarn
,pnpm
亦可)。
🌏 流程
这里我自己用的是centOS环境:
1. 在web服务器上安装所需工具
(1)安装Node
# 下载node包
wget https://nodejs.org/dist/v16.13.0/node-v16.13.0-linux-x64.tar.xz
# 解压Node包
tar -xf node-v16.13.0-linux-x64.tar.xz
# 在配置文件(位置多在/etc/profile)末尾添加:
export PATH=$PATH:/root/node-v16.13.0-linux-x64/bin
# 刷新shell环境:
source /etc/profile
# 查看版本(输出版本号则安装成功):
node -v
#后续安装操作,都可通过-v或者--version来查看是否成功
npm
已内置在node
中,如要使用yarn
或,则可通过npm
进行全局安装,命令与我们本地环境下的使用命令是一样的:
npm i yarn -g
#or
npm i pnpm -g
(2)安装Git
# 利用yum安装git
yum -y install git
# 查看git版本
git --version
(3)安装Gitlab Runner
# 安装程序
wget -O /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
# 等待下载完成后分配权限
chmod +x /usr/local/bin/gitlab-runner
# 创建runner用户
useradd --comment 'test' --create-home gitlab-runner --shell /bin/bash
# 安装程序
gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
# 启动程序
gitlab-runner start
# 安装完成后可使用gitlab-runner --version查看是否成功
2. 配置Runner及CI/CD
基本的安装操作完成后,就是最核心的阶段:Runner与CI/CD的配置。
(1)配置Gitlab Runner
首先打开待添加自动部署功能的gitlab仓库,在其中设置 > CI/CD > Runner
中找到runner配置信息备用:
在web服务器中配置runner:
gitlab-runner register
>> Enter the GitLab instance URL (for example, https://gitlab.com/):
# 输入刚才获取到的gitlab仓库地址
>> Enter the registration token:
# 输入刚才获取到的token
>> Enter a description for the runner:
# 自定runner描述
>> Enter tags for the runner (comma-separated):
# 自定runner标签
>> Enter an executor: docker-ssh, docker+machine, docker-ssh+machine, docker, parallels, shell, ssh, virtualbox, kubernetes, custom:
# 选择执行器,此处我们输入shell
完整示例:
(2)配置.gitlab-ci.yml
.gitlab-ci.yml
文件是流水线执行的流程文件,Runner会据此完成规定的一系列流程。
我们在项目根目录中创建.gitlab-ci.yml
文件,然后在其中编写内容:
# 阶段
stages:
- install
- build
- deploy
cache:
paths:
- node_modules/
# 安装依赖
install:
stage: install
# 此处的tags必须填入之前注册时自定的tag
tags:
- deploy
# 规定仅在package.json提交时才触发此阶段
only:
changes:
- package.json
# 执行脚本
script:
yarn
# 打包项目
build:
stage: build
tags:
- deploy
script:
- yarn build
# 将此阶段产物传递至下一阶段
artifacts:
paths:
- dist/
# 部署项目
deploy:
stage: deploy
tags:
- deploy
script:
# 清空网站根目录,目录请根据服务器实际情况填写
- rm -rf /www/wwwroot/stjerne/salary/*
# 复制打包后的文件至网站根目录,目录请根据服务器实际情况填写
- cp -rf ${CI_PROJECT_DIR}/dist/* /www/wwwroot/stjerne/salary/
保存并推送至gitlab后即可自动开始构建部署。
构建中可在gitlab CI/CD面板查看构建进程:
待流水线JOB完成后可前往页面查看🛫🛫🛫🛫🛫
作者:星始流年
来源:https://juejin.cn/post/7037022688493338661
如何实现跨设备的双向连接? Labo涂鸦鸿蒙亲子版分布式开发技术分享
近期,首届HarmonyOS开发者创新大赛正式落下帷幕。大赛共历时5个月,超过3000支队伍的10000多名选手参赛,25000多位开发者参与了大赛学习,最终23支参赛队伍斩获奖项,产出了多款有创新、有创意、有价值的优秀作品。其中由“Labo Lado儿童艺术创想”团队打造的《Labo涂鸦鸿蒙亲子版》就是其中之一,其创造性地通过HarmonyOS分布式技术,实现了多设备下的亲子互动涂鸦功能,最终摘得大赛一等奖。
在很早以前,“Labo Lado儿童艺术创想”团队就做过一款涂鸦游戏的应用,该应用可以让孩子和父母在一个平板或者手机上进行绘画比赛,比赛的方式就是屏幕一分为二,两人各在设备的一边进行涂鸦。这种方式虽然有趣,但是对于绘画而言,屏幕尺寸限制了用户的发挥和操作。因此团队希望这类玩法能通过多个设备完成,于是他们研究了ZeroConf、iOS的Multipeer Connectivity、Google Nearby等近距离互联的技术, 结果发现这些技术在设备发现和应用拉起方面实现的都不理想,尤其是当目标用户是儿童的情况下,操作起来不够简便也不易上手。
HarmonyOS的出现给团队带来了希望。他们发现HarmonyOS的分布式技术有着很大的应用潜力,这项技术让设备的发现和应用拉起变的非常的简单自然,互联的过程也很流畅,很好地解决了单机操作的限制,让跨设备联机功能能够非常容易地实现。此外,HarmonyOS的开发也给团队留下了很深刻的印象,以往繁琐的开发步骤,在 HarmonyOS 中仅需几个配置、几行代码即可完成,无需花费太多精力。在《Labo涂鸦鸿蒙亲子版》里面的5个分布式玩法的开发只用了团队一名开发者不到两个月的时间,其中还包括了学习上手、解决文档不全和各种疑难问题的过程。
以下是“Labo Lado儿童艺术创想”团队基于HarmonyOS的分布式开发关键技术的简单分享:
一、分布式技术实践
HarmonyOS的分布式能力是在系统层面实现的,在设备双方同属一个局域网的情况下,设备都可以快速的发现和进行流畅的通讯。下面将从HarmonyOS设备的发现、应用的拉起、应用通讯和双向通讯几个部分来进行分享。
1、设备的发现
假设设备A想要邀请另外一个设备B加入,AB任何一方都无需启动特别的广播服务,只要发起方设备A在应用内调用设备发现代码,就可以列出附近符合条件可用的的设备。
以下是获取设备列表的示例代码:
public static List<DeviceInfo> getRemoteDevice() {
List<DeviceInfo> deviceInfoList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
return deviceInfoList;
}
列出设备之后,用户就可以通过设备名选择想要邀请的设备了。
(左侧设备A发现右侧名为“ye”的设备B的界面展示)
2、应用的拉起
设备A邀请了设备B之后,如果设备B上应用没启动,设备A可直接通过调用startAbility方法来拉起设备B上的应用。双方应用都启动了之后,就可以进行RPC通讯了。如果需要事先检查设备B上的应用是否已经启动或者是否在后台,可通过在应用中增加一个PA来实现。在拉起之前,设备A先连接设备B的应用中的PA可以实现更复杂精准的远程应用启动控制。
3、应用通讯
在应用中启动一个PA,专门用作通讯的服务器端。当设备B的应用被拉起之后,设备A就会通过connectAbility与设备B的PA进行连接,通讯采用RPC方式实现,并使用IDL定义通讯接口。
4、双向通讯
RPC的通讯方式使用简单,但是只能支持单向通讯。为了实现双向通讯,可在设备A与设备B发起建立连接成功之后,再让设备B与设备A发起建立一个连接,用两个连接实现了双向通讯。下面是这两个连接建立过程的示意时序图:
在设备A与设备B建立连接的时候,设备A必须将自己的DeviceId发送给设备B,然后设备B才可以主动发起一个与设备A的连接,获取当前设备的DeviceId方法如下:
KvManagerFactory.getInstance().createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getId()
应用中,FA主要实现了界面层逻辑,PA部分用做数据通讯的服务端。为了防止拉起应用导致用户当前面的操作被中断,可通过PA来查询当前FA的状态,如果FA已经启动了,就跳过拉起,直接进行下一步操作即可。
二、数据接口与数据结构定义
使用了IDL定义了两个通用的接口,分别用来进行异步和同步调用:
int sendSyncCommand([in] int command, [in] String params);
void sendAsyncCommand([in] int command, [in] String params, [in] byte[] content);
大部分情况下,远程调用大部分都通过同步的方式进行,用户之间的绘画数据通过异步接口传输,数据在用户绘制的时候采集,每50ms左右发送一次,这个频率可以大概保证用户视觉上没有卡顿,而又不至于因为接口过度调用导致卡顿或者耗电量过大。采集的绘画数据的数据结构大致如下:
enum action //动作,表示落笔、移动、提笔等动作
int tagId //多点触摸识别标记
int x //x坐标
int y //y坐标
enum brushType //笔刷类型
int brushSize //笔刷大小
enum brushColor //笔刷颜色
int layer //图层
这款应用是支持多点触摸的,所以每个触摸点在落笔的的时候,都使用了tagId进行标记。这些数据除了通讯外,还会完整地保存在文件中,这样用户就可以通过应用内的播放功能播放该数据,回看绘画的整个过程。
三、教程录制与曲线平滑
1、教程制作
这款产品的特色之一是教程是动态的,用户可以自己拼装或者通过游戏生成教程角色。目前应用内置六种教程。这些教程预先由设计师在photoshop中画好并标记各个部位,然后再通过专门的photoshop脚本导出到教程录制应用中,再由设计师按部位逐个进行临摹绘制,绘制完成,应用会将设计师的绘制过程数据保存为json文件,通过将这些json的文件里的部位互换,我们就实现了用户自己拼装教程的功能了。
2、曲线平滑
绘制过程,为了让用户绘制的曲线更加平滑,采用二次贝塞尔曲线算法进行差值(Quadratic Bezier Curve),该算法简单效率也非常不错:
public Point quadraticBezier(Point p0, Point p1, Point p2, float t) {
Point pFinal = new Point();
pFinal.x = (float) (Math.pow(1 - t, 2) * p0.x + (1 - t) * 2 * t * p1.x + t * t * p2.x);
pFinal.y = (float) (Math.pow(1 - t, 2) * p0.y + (1 - t) * 2 * t * p1.y + t * t * p2.y);
return pFinal;
}
基于HarmonyOS的分布式特性,《Labo涂鸦鸿蒙亲子版》完成了一次已有应用的自我尝试和突破,大大的增加了用户在使用过程中的乐趣,为用户带来了全新的跨设备亲子交互体验,“Labo Lado儿童艺术创想”团队在未来将与更多的HarmonyOS开发者一起,为用户创作出更多更有趣的儿童创造类应用。
近一段时间以来,HarmonyOS 2的发布吸引了广大开发者的关注。作为一款面向万物互联时代的智能终端操作系统,HarmonyOS 2带来了诸多新特性、新功能和新玩法,等待开发者去探索、去学习、去实践。也欢迎广大开发者继续发挥创造力和想象力,基于HarmonyOS开发出更多有创新、有创意、有价值的作品,打造出专属于万物互联时代的创新产品。
收起阅读 »Uni-app:(不通用)HbuilderX启动别人uni-app项目运行到小程序,提示打开了小程序开发者工具但是不进页面
不是什么大问题,但是也很耽误时间,Hello world,连Hello都Hello不出来,最后研究了半天才解决了这个问题下面是解决方法:
除了查看对应版本和开启端口外还需注意,因为微信开发者工具是和对应的微信账号绑定的,所以需要设置对应账号的AppID才行,如果使用的是别人的需要用自己的AppID,但是启动别人的项目只用在,mainifest.json下的微信小程序配置,找到微信小程序APPID 删除掉然后再重新启动运行就可以了。
由于大小限制上传的图片是个压缩包,大家不清楚位置的可以下载下来瞜一眼。
收起阅读 »