gitlab之docker内存优化
由于新版本的gitlab默认使用puma代替unicorn。如果你的配置文件里面以前启动了uncorn的设置,那么就会出现puma和unicorn冲突的问题。解决方法就是把gitlab.rb中的unicorn的配置改为puma相关配置即可。
旧版:
1 | unicorn['enable'] = true |
新版
1 | puma['enable'] = true |
由于新版本的gitlab默认使用puma代替unicorn。如果你的配置文件里面以前启动了uncorn的设置,那么就会出现puma和unicorn冲突的问题。解决方法就是把gitlab.rb中的unicorn的配置改为puma相关配置即可。
旧版:
1 | unicorn['enable'] = true |
新版
1 | puma['enable'] = true |
JIRA 是一个缺陷跟踪管理系统,为针对缺陷管理、任务追踪和项目管理的商业性应用软件,开发者是澳大利亚的Atlassian。JIRA这个名字并不是一个缩写,而是截取自“Gojira”,日文的哥斯拉发音。 官网
1 | FROM cptactionhank/atlassian-jira-software:8.1.0 |
1 | - JIRA |
1 | docker build -t dakewe/jira:v8.1.0 . |
1 | java -jar atlassian-agent.jar -d -m 82607314@qq.com -n dakewe -p jira -o http://localhost:8090 -s B5WY-IHN3-GITJ-FAA7 |
Atlassian Confluence(简称Confluence)是一个专业的wiki程序。它是一个知识管理的工具,通过它可以实现团队成员之间的协作和知识共享。官网
1 | FROM cptactionhank/atlassian-confluence:7.9.3 |
1 | - Confluence |
1 | docker build -f Dockerfile -t dakewe/confluence:7.9.3 . |
1 | java -jar atlassian-agent.jar -d -m 82607314@qq.com -n DAKEWE -p conf -o http://103.39.231.195 -s BJRF-N4SL-YE98-QPJA |
1 | --创建jira数据库及用户 |
上一次我们使用filebeat进行数据采集 filebeat与logstash实践,传输到logstash,并使用的logstash进行数据的过滤处理,本着能减少一个环节就是一个环节,这次我们就省去logstash这个环节,使用filebeats的pipeline的功能来做一次数据处理,并直接入库到es.
本次,我们依然使用的上一次的示例日志数据,整个过程如下
1 | filebeat.inputs: |
先测试一下
1 | // 模拟测试pipeline |
1 | // 增加pipeline |
1 | //模拟测试添加的pipeline |
开启filebeat,日志文件将传输到es并通过pipeline进行处理,其中pipeline中使用了processors的grok,与logstash相似。
ElasticStack-安装篇
ElasticStack-elasticsearch篇
ElasticStack-logstash篇
elasticSearch-mapping相关
elasticSearch-分词器介绍
elasticSearch-分词器实践笔记
elasticSearch-同义词分词器自定义实践
docker-elk集群实践
filebeat与logstash实践
filebeat之pipeline实践
Elasticsearch 7.x 白金级 破解实践
elk的告警调研与实践
开发时,碰到互斥问题,需要保证数据的一致性,避免重复性操作,我们就需要用锁来解决,简单的讲锁其实就是为了解决并发所带来的问题。
常见问题可查看从线上事故看mongodb事务ACID强弱
举个例子:库存
,并发情况下,我们通过ab模拟,一次10个请求,假设我们下单去核销库存数量,先查询订单数量,后扣除库存,那么在10个请求并发下,我们就可能获取到了错误的库存数量。这在事务中我们认为是脏读或幻读。
数据库的并发控制机制不外乎就两种情况:
悲观锁假定其他用户企图访问或者改变你正在访问、更改的对象的概率是很高的,因此在悲观锁的环境中,在你开始改变此对象之前就将该对象锁住,并且直到你提交了所作的更改之后才释放锁。悲观的缺陷是不论是页锁还是行锁,加锁的时间可能会很长,这样可能会长时间的限制其他用户的访问,也就是说悲观锁的并发访问性不好。
乐观锁则认为其他用户企图改变你正在更改的对象的概率是很小的,因此乐观锁直到你准备提交所作的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短,乐观锁可以用较大的锁粒度获得较好的并发访问性能。但是如果第二个用户恰好在第一个用户提交更改之前读取了该对象,那么当他完成了自己的更改进行提交时,数据库就会发现该对象已经变化了,这样,第二个用户不得不重新读取该对象并作出更改。这说明在乐观锁环境中,会增加并发用户读取对象的次数。
我们经常用的lock就是一种悲观锁。
todo
关于redis的分布式锁,redis官方引出了一个算法,命名为redlock。
同时,提供了各类的实现可供使用,例如Redlock-rb for Ruby、Redlock-py for Python、Redisson for Java等。
因此,深入了解Redis分布锁的运用同时分析下node-redlock。
我们在之前有用过ELK,并详细使用过logstash,作为数据从mysql到es的cdc的传输工具。可查看 ElasticStack-logstash篇
这一次让我们来通过filebeat采集,logstash过滤处理一下日志文件,通过采集日志文件进行数据提取,入库到mongodb
仅采集含有 A large volume of broadcast packets has been detected
内容的数据,并将所需要的数据提取出来入库
示例数据:
1 | 2021-12-01 00:00:07.115 [HUB "hub_dkwbj"] Session "SID-BRIDGE-5": A large volume of broadcast packets has been detected. There are cases where packets are discarded based on the policy. The source MAC address is 50-9A-4C-27-F9-D3, the source IP address is fe80::e8d3:8281:e69e:afda, the destination IP address is ff02::1:3. The number of broadcast packets is equal to or larger than 32 items per 1 second (note this information is the result of mechanical analysis of part of the packets and could be incorrect). |
1 | docker network create --driver bridge leiqin |
创建安装了logstash-output-mongodb的镜像包dockerfile文件logstash.dockerfile
文件
1 | FROM docker.elastic.co/logstash/logstash:7.13.0 |
打包自己的logstash镜像
1 | docker build -f logstash.dockerfile -t dakewe/logstash:1.0 . |
1 | version: '3.0' |
tip
: 如果是安装的官方的镜像包,安装后,请进入容器内安装logstash-output-mongodb
不要安装3.1.6新版本,请指定3.1.5版本。具体的坑详见:Github作者回复
1 | bin/logstash-plugin install --version=3.1.5 logstash-output-mongodb |
1 |
|
我们先让filebeat的文件到logstash直接输出处理
1 | input { |
在logstash过滤,入库到mongodb
1 | input { |
较为简单,如果配合elk,效果更佳。
ElasticStack-安装篇
ElasticStack-elasticsearch篇
ElasticStack-logstash篇
elasticSearch-mapping相关
elasticSearch-分词器介绍
elasticSearch-分词器实践笔记
elasticSearch-同义词分词器自定义实践
docker-elk集群实践
filebeat与logstash实践
filebeat之pipeline实践
Elasticsearch 7.x 白金级 破解实践
elk的告警调研与实践
最近因公司的CRM项目用的mongodb,浏览了所有旧代码,看到特别多多表原子操作的问题。借此机会就来看看mongodb4.0后出来的副本集事务的能力。
多表操作场景
涉及异常回滚的原子性问题,
1 | // 创建流水号(流水号表自增) |
问题:当异常中断时候,实际流水号表已经成功自增1,但是创建订单失败。当下一次进行操作的时候,实际流水号缺失了1位的订单流水号。
这里其实还隐藏着一个问题,高并发未加锁,会导致流水号异常。
这就是事务的原子性,实际应该当异常中断,启动事务回滚,回滚流水号的自增创建。
redis版本异常
1 |
|
问题:当异常redis中断导致发送消息失败,应该启动事务回滚
正确操作:启动事务,进行异常回滚,如下
app/extend/context.js
1 | module.exports = { |
1 |
|
错误或不建议的操作:人工删除
1 | try { |
1 | #!/bin/bash |
出现如下错误:
1 | configsvr01 | {"t":{"$date":"2021-05-29T17:38:02.750+00:00"},"s":"I", "c":"ACCESS", "id":20254, "ctx":"main","msg":"Read security file failed","attr":{"error":{"code":30,"codeName":"InvalidPath","errmsg":"error opening file: /data/mongo.key: bad file"}}} |
解决办法: 变更mongoReplSet-keyfile 所属用户chown 999 mongoReplSet-keyfile
docker-compose -f docker-compose.yml up -d
docker-compose
1 | version: '3.1' |
容器名 | ip | 备注 |
---|---|---|
mongo1 | 10.8.99.44:27011 | Primary(主, 读写) |
mongo2 | 10.8.99.44:27012 | Secondary1(从,读) |
mongo3 | 10.8.99.44:27013 | Secondary2(从, 读) |
1 | docker exec -it <container> mongo |
重置
1 | rs.reconfig( |
1 | rs.config() |
强制修改副本集host
1 | rs.reconfig( |
修改优先级
必须在primary节点上执行此操作,副本集中通过设置priority的值来决定优先权的大小。这个值的范围是0–100,值越大,优先权越高. 如果值是0,那么不能成为primay。适用于做冷备。
1 | PRIMARY> config=rs.conf() |
1 | 查看副本集状态 |
1 | config.mongoose = { |
默认情况下,读写都指定到副本集中的 Primary 节点。对于读多写少的情况我们可以使用读写分离来减轻 DB 的压力。MongoDB 驱动程序支持五种读取首选项(Read Preference) 模式。
Read Preference | 描述 |
---|---|
primary | 默认模式。 所有操作都从当前副本集 primary 读取。 |
primaryPreferred | 在大多数情况下,从 primary 读取,但如果不可用,则从 secondary 读取。 |
secondary | 所有操作都从 secondary 中读取。 |
secondaryPreferred | 在大多数情况下,从 secondary 读取,但如果没有 secondary 可用,则从 primary 读取。 |
nearest | 无论成员的类型如何,操作都从具有最小网络延迟的副本集成员读取 |
app/extend/context.js
1 | module.exports = { |
app/service/test.js
1 | async transaction() { |
可以看看res1 是不是回滚创建,在数据库中找不到了
https://www.jianshu.com/p/8d7dea5c067b
https://www.zhangshengrong.com/p/Ap1ZeQ2PX0/
众所周知,mongoose的日期格式是ISODate,也就是使用的utc时间,举个栗子:2020-12-11T16:00:00.000Z
,T
表示分隔符,Z
表示的是UTC。
UTC:世界标准时间,在标准时间上加上8小时,即东八区时间,也就是北京时间。
咱举个例子:
北京时间:2020-12-12 00:00:00
对应的国际标准时间格式为:2020-12-11T16:00:00.000Z
。
当我们的前端页面通过接口拿到我的utc时间后,一般通过new Date(时间),就能快速的转换成当地的时间。
这些周知的我就不再多举例了。
做这个笔记前,我遇到了时间进入到数据库没有准确的转换为utc,于是,好奇心驱使,我们开启mongoose的debug模式,来看看是什么实际mongoose到原生层的实际过程。
1 | // 开启mongoose调试 |
举例,我有一个schema,里面有个updateDate,我们先来看看不同的日期插入数据库时候,实际的表现。
1 | module.exports = (app) => { |
1 | const deliver = await ctx.model.Delivery.File.create({ |
通过debug日志:
1 | Mongoose: sys_files.insertOne({ isDel: false, _id: ObjectId("61b5599cba1fcfeeb79c57cd"), fileName: '文件名', uploadDate: new Date("Sat, 11 Dec 2021 16:00:00 GMT"), creatorName: '张三', __v: 0}, { session: null }) |
结论:`
我们发现mongoose的ORM层的
create
实际调用了insertOne
,插入的本地时间new Date("2021-12-12 00:00:00")
(2021-12-11T16:00:00.000Z
)到达原生层变成了Sat, 11 Dec 2021 16:00:00 GMT
并在进行了一次new Date(),
所以整个orm层到mongodb原生层的过程是这样的:
日期传入
->(ORM)转换为GMT零时区
->(ORM)new Date()转为ISODate
->入库
new Date("2021-12-12 00:00:00")
传入 -> 2021-12-11T16:00:00.000Z
Sat, 11 Dec 2021 16:00:00 GMT
new Date("Sat, 11 Dec 2021 16:00:00 GMT")
-> 2020-12-11T16:00:00.000Z
我们在拿一个字符串时间和moment时间对象来校验是不是这个过程:日期传入
-> (ORM)转换为GMT零时区
-> (ORM)new Date()转为ISODate
-> 入库
1 |
|
结论:
->2021-12-12 00:00:00
传入
->(ORM)转换为GMT零时区 Sat, 11 Dec 2021 16:00:00 GMT
->(ORM)new Date()转为ISODatenew Date("Sat, 11 Dec 2021 16:00:00 GMT")
-> 2020-12-11T16:00:00.000Z
-> 入库
方式三:使用moment 插入
1 |
|
结论:
moment("2021-12-12 00:00:00")
传入 -> Moment<2021-12-12T00:00:00+08:00>
Sat, 11 Dec 2021 16:00:00 GMT
new Date("Sat, 11 Dec 2021 16:00:00 GMT")
-> 2020-12-11T16:00:00.000Z
我们发现当我们的时间只要是精确到时分秒,进入到mongodb数据库后,都能正确的转换成UTC时间。
那我们来试试 年月日的情况
1 | const file = await ctx.model.Delivery.File.create({ |
结论:`
new Date('2021-12-12')
传入 -> 2021-12-12T00:00:00.000Z
(注意此处年月日时间转换为UTC时间与上面带时分秒的差异)Sat, 11 Dec 2021 00:00:00 GMT
new Date("Sat, 11 Dec 2021 00:00:00 GMT")
-> 2020-12-12T00:00:00.000Z
这也就解释了为什么本人在项目中传入2021-12-12
的日期最终却变成了utc 2020-12-12T00:00:00.000Z
,也就是为什么new Date()本地时间会多出来8个小时的原因了。
mongoose这个ORM实际做了一步强制new Date()转换为utc时间。所以无论传入什么本地时间,都会强制转换mongodb所需要的ISODate时期格式。
所以无论是moment、dayjs等时间库的时间,最后都会被momgoose强制转换为new Date 的UTC时间。与用什么时间库或时间格式并无直接关系。
我们来验证下查询的时候,传入的时间是不是也会通过mongoose自动强制new Date
验证发现流程:
Sat, 11 Dec 2021 00:00:00 GMT
new Date("Sat, 11 Dec 2021 00:00:00 GMT")
-> 2020-12-12T00:00:00.000Z
我们在 mongoose关联查询技巧笔记 的关联技巧一:关联引用子查父 中我们发现可以通过lookup进行子查父,那么我们是否有什么快捷方式能在子当中定义一个字段,查询子的时候就能带出父的信息呢?
schema 设置 toJSON 和toObject可获取
1 | toJSON: { |
1 | personSchema.virtual('fullName').get(function () { |
1 | BookSchema.virtual('author', { |
数据库设计中数据之间的关联关系是极其常见的:一对一、一对多、多对多,作为 NoSQL 领头羊的 MongoDB 中常用做法无非「内嵌」和「引用」两种,因为 Document 有 16MB 的大小限制且「内嵌」不适合复杂的多对多关系,「引用」是用得更广泛的关联方式,所以 MongoDB 官方称其为“Normalized Data Models”——标准化数据模型。
引用式的关联其实很简单,指文档与文档之间通过_id字段的引用来进行关联。Mongoose 4.5.0版本以后提供了与 aggregate 功能写法都非常类似的virtual()方法,这里先不做对比,可查看另一篇笔记mongoose4.5之virtual虚拟值填充,本文要阐述的重点就在于如何去查这两个表通过aggregate 与 populate,并介绍4.5的aggregate的关联引用反向查询。
先说说populate吧,首先,Mongoose 的一切始于 Schema,使用 populate 的重点也在于 Schema 中的设置:
1 | const mongoose = require('mongoose'); |
使用
1 | let result = await Author.find({ |
返回如下数据
1 | [ |
使用 aggregate 实现聚合查询作者 Zander 的基本信息及其所有著作信息:
1 | let result = await Author.aggregate([{ // 操作的Model为Author |
返回数据:
1 | [ |
1. 灵活性
现在可以观察到的就是 aggregate 灵活的点在于可以更改关联查询后返回数据的 key(返回数据中的bookList),而 populate 返回数据的 key 只能是原来的字段名(返回数据中的books)。值得一提的是 aggregate 更擅长在聚合管道中对数据进行二次处理,比如$unwind
拆分、$group
分组等等。
2. 功能性
此外,还有一种情况:依旧是上面的数据,如果要根据著作 name 找到著作信息和作者信息,使用 aggregate 的$lookup只需要这样就做到了😏:
1 | $lookup: { |
然而 populate:“我太难了!” 是的,它做不到这种使用_id实现的反向关联查询.
3. 性能方面
看完了外表再说说内在——查询性能,populate 实际是DBRef
[^4]的引用方式,相当于多构造了一层查询。比如有10条数据,在find()
查询到了主集合内的10条数据后会再进行populate()
引用的额外10条数据的查询,性能也相对的大打折扣了。这里有位大佬对aggregate()
和find()
进行了性能上的对比,结论也显而易见——比 find 查询速度都快的 aggregate 比关联查询的 find + populate 定是有过之而无不及了。
aggregation | populate | |
---|---|---|
灵活性 | ⭐️⭐️⭐️⭐️⭐ | ⭐️ |
反向关联 | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️ |
功能性 | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️ |
代码简洁度 | ⭐️ | ⭐️⭐️⭐️⭐️⭐️ |
查询性能 | ⭐️⭐️⭐️⭐️ | ⭐️⭐️ |
综合来看,aggregate 在多集合关联查询和对查询数据的二次处理方面更优,而 populate 更适合简单的正向关联关系且其形成的代码样式较优雅,可读性高而易于维护,性能方面的考究对日常开发中的普通应用来说则大可忽略不计。
1 | $lookup: { |
也可通过 mongoose4.5之virtual虚拟值填充 快速获取父内容
1 | const lookup = { |
1 | const project = { |
JSON 类型 | Elasticsearch 类型 |
---|---|
字符串 | 1 匹配日期格式设置成 Date |
2 设置数字设置为 float 或者 long,该选项默认关闭 | |
3 设置为 Text, 并增加 keyword 子字段 | |
布尔值 | boolean |
浮点数 | float |
整数 | long |
对象 | Object |
数组 | 由第一个非空数值的类型所决定 |
空值 | 忽略 |
1 | //写入文档 |
1 | //dynamic mapping 推断字符的类型 |
1 | PUT users |
1 | DELETE users |
1 | DELETE users |
ElasticStack-安装篇
ElasticStack-elasticsearch篇
ElasticStack-logstash篇
elasticSearch-mapping相关
elasticSearch-分词器介绍
elasticSearch-分词器实践笔记
elasticSearch-同义词分词器自定义实践
docker-elk集群实践
filebeat与logstash实践
filebeat之pipeline实践
Elasticsearch 7.x 白金级 破解实践
elk的告警调研与实践