0%

视频直播解决方案

方案一ffmpeg+nginx(rtmp/hls)

rtmp解决方案大众,但是依赖adobe flash player

hls延时高

方案二ffmpeg+webSocket

原理ffmpeg解码转流(图片),webSocket接收,然后前端画布按帧绘制

1
2
3
4
5
6
7
8
#拉去代码https://github.com/phoboslab/jsmpeg
git clone git@github.com:phoboslab/jsmpeg.git
#进入项目目录执行
npm install ws
#运行JSMpeg,8081为ffmpeg推流端口,8082为websocket端口
node websocket-relay.js supersecret 8081 8082
#运行转码推流
ffmpeg -i rtsp://admin:admin@10.30.11.119:554/h264/ch1/main/av_stream -q 0 -f mpegts -codec:v mpeg1video -s 352x240 http://10.30.11.40:8081/supersecret

修改view-stream.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<title>JSMpeg Stream Client</title>
<style type="text/css">
html, body {
background-color: #111;
text-align: center;
}
</style>
</head>
<body>
<canvas id="video-canvas"></canvas>
<script type="text/javascript" src="jsmpeg.min.js"></script>
<script type="text/javascript">
var canvas = document.getElementById('video-canvas');
var url = 'ws://10.30.11.150:8082/';
var player = new JSMpeg.Player(url, {canvas: canvas});
</script>
</body>
</html>

测试

访问静态网页

file:///Users/xuanleung/Downloads/jsmpeg-master/view-stream.html

参考

html5播放rtsp方案

phoboslab/jsmpeg

语法

1. SUM(<需要求和的字段,字段必须是数字类型>)count(<需要统计条数的字段>)

sum注意区分count,一个是根据字段里面的值求和,一个是根据条数求数据总条数

1
2
-- 对所有用户的年龄进行累加求和
select SUM(u.AGE) from t_user u ;

2. CASE WHEN <条件> THEN <满足条件的结果> ELSE <不满足条件的结果> END

CASE <条件字段> WHEN <值1> THEN <满足条件=值1的结果> WHEN <值2> THEN <满足条件=值2的结果> ... ELSE <不满足所有条件的结果> END

1
2
3
4
5
6
7
8
9
10
11
12
--eg:查询年龄大于18的flag输出成年人,否则未成年人
select CASE WHEN u.age>18 THEN '成年人' ELSE '未成年人' END as flag from t_user u;
--eg:多条件组合查询年龄大于18且是男的的flag输出男成年人,否则未成年人
select CASE WHEN u.age>18 and u.sex='男' THEN '男成年人' ELSE '未成年人' END as flag from t_user u;
--按条件统计总数,sum是求和,输出只能是1 ELSE 0,因为要进行累加
--统计大于18的总人数
select sum(CASE WHEN u.age>18 THEN 1 ELSE 0 END) as total from t_user u;
--多条件switch实现,将boy替换成男,girl替换成女,其他输出人妖
select CASE u.sex
WHEN 'boy' THEN '男'
WHEN 'girl' THEN '女'
ELSE '人妖' END from t_user u;

3. DECODE(<条件字段>,<值1>,<满足条件=值1的结果>,<值2>,<满足条件=值2的结果>,....,<都不满足>)

1
2
--将boy替换成男,girl替换成女,其他输出人妖,等效于case when
select DECODE(u.SEX,'boy','男','girl','女','人妖') from t_user u;

4. NVL(<需要判断的字段>,<如果判断的字段为null输出的结果>)

1
2
3
4
--数据为null的会替换成人妖
select nvl(u.SEX,'人妖') from t_user u;
--没有年龄的设置为0,方便统计空数据
select sum(nvl(u.age,0)) from t_user u;

5. group by <分组的字段1,字段2...>分组统计

select后面的字段=分组的字段+统计求和等字段,原理分组过后,查询不能查一个组有多个不同结果的字段,如果是相同的结果加入group by 字段1,字段2

1
2
3
4
--按年龄分组统计各个年龄的总数
select u.AGE,count(u.SEX) from T_USER u group by u.AGE;
--按年龄性别进行分组统计,统计年龄相同且性别相同的个数
select u.AGE,u.SEX,count(*) from T_USER u group by u.AGE,u.SEX

5. order by <排序字段> <desc/asc> asc升序,desc降序

1
2
--按时间升序
select * from T_USER order by "creat_time" asc

6. to_char(sysdate, 'yyyy-MM-dd')格式化日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--sysdate获取当前日期,to_char格式化为天
select to_char(sysdate, 'yyyy-MM-dd') from DUAL;
--按天分组统计
select sum(1),to_char(u."creat_time",'YYYY-MM-DD') as day from t_user u group by to_char(u."creat_time",'YYYY-MM-DD');
--按月分组统计,按年等其他日期类似
select sum(1),to_char(u."creat_time",'YYYY-MM') as day from t_user u group by to_char(u."creat_time",'YYYY-MM');
--查询7天前的数据,其他天类似
select * from T_USER u where to_char(u."creat_time",'yyyy-MM-dd')>to_char(sysdate-7, 'yyyy-MM-dd')
--统计查询前7天的数据,当天没有统计为0,按时间降序
select t_date.day, NVl(t_data.total, 0)
from (select TO_CHAR(trunc(sysdate + 1 - ROWNUM), 'yyyy-MM-dd') day from DUAL connect by ROWNUM <= 7) t_date
left join (select to_char(u."creat_time", 'yyyy-MM-dd') as day, count(1) as total
from T_USER u
group by to_char(u."creat_time", 'yyyy-MM-dd')) t_data
on t_data.day = t_date.day
order by t_date.day asc;

7. round(<小数>,<保留小数点后位数>)

1
2
--保留小数点后2位,输出33.33
select round( 1/3*100 ,2) from dual;

8. left join 左连接

以左边为主,右边有就连接,没有就null

1
select * from T_USER l left join T_USER r on l.AGE=r.FLAG;

9. substr(<需要裁剪的字符串>,<开始位置>, <结束位置>)

1
2
-- 输出2019
select substr('2019-01-02',1, 4) from DUAL;

10. connect by

其他用法,获取树形数据(也就是父子关系)见google

rownum数据库关键字,行数

1
2
3
4
--生成1-10的序列
select rownum from dual connect by rownum<=10;
--生成7天的日期
select TO_CHAR(trunc(sysdate+1-ROWNUM),'yyyy-MM-dd') dd from DUAL connect by ROWNUM <= 7

11. union <all> 两个结果集合并

有all 全连接,不去重,没有all 去重

1
2
3
4
5
6
7
8
-- 输出1-4-1-4
select rownum from dual connect by rownum<=4
union all
select rownum from dual connect by rownum<=4;
-- 输出1-4
select rownum from dual connect by rownum<=4
union
select rownum from dual connect by rownum<=4;

12. ROLLUP 分组汇总

ROLLUP汇总分组排列在最后一条数据,但是数据头为null,可以通过null判断取别名为总数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT nvl(CASE
WHEN sex = 'boy' THEN '男'
WHEN sex = 'girl' THEN '女'
ELSE '人妖'
END, '总数') AS type,
count(1) as num
from t_user
GROUP BY
ROLLUP
( CASE
WHEN sex = 'boy' THEN '男'
WHEN sex = 'girl' THEN '女'
ELSE '人妖'
END);

13. ||字符连接符

用于单位,用于多条数据拼接

1
2
3
4
select 'sex是'||u.SEX||',年龄是'||u.AGE as detail from T_USER u;
--------输出结果------
--sex是boy,年龄是1
--sex是girl,年龄是2

示例数据

1
2
3
4
5
6
7
8
9
10
11
12
create table T_USER
(
AGE NUMBER,
FLAG VARCHAR2(10),
SEX VARCHAR2(4),
"creat_time" DATE
)
INSERT INTO SYSTEM.T_USER (AGE, FLAG, SEX, "creat_time") VALUES (1, '2', 'boy', TO_DATE('2019-10-23 03:14:11', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO SYSTEM.T_USER (AGE, FLAG, SEX, "creat_time") VALUES (2, '4', 'girl', TO_DATE('2019-10-24 03:14:14', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO SYSTEM.T_USER (AGE, FLAG, SEX, "creat_time") VALUES (3, '6', 'ff', TO_DATE('2019-10-26 03:14:19', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO SYSTEM.T_USER (AGE, FLAG, SEX, "creat_time") VALUES (3, '2', null, TO_DATE('2019-10-23 03:14:23', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO SYSTEM.T_USER (AGE, FLAG, SEX, "creat_time") VALUES (null, '2', null, TO_DATE('2019-10-23 03:14:25', 'YYYY-MM-DD HH24:MI:SS'));

gitlab-ci构建docker镜像的三种方式

官方教程

shell模式(dood),自定义runner镜像

见: Docker-Gitlab-Runner

优点:

  1. 自定义镜像,集成自己需要的工具

缺点:

  1. 采用宿主机docker进行编译

docker模式(dind),采用docker内部docker

Docker in Docker 19.03 service fails

优点:

  1. 独立(不影响宿主机),可以多线程构建

缺点:

  1. 需要vi /etc/gitlab-runner/config.toml设置[runners.docker]->privileged = true特权模式
  2. 编译慢每次要启动docker服务

版本19以后tls需要挂载或者禁用

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
#[三种方式使用docker构建](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html)
#如果stages没有设置镜像,就使用该镜像
image: docker:19.03.1

# docker in docker =docker dind 容器内部启动docker
# docker out docker = dood 使用宿主机的docker,需要挂在/var/run/docker.sock
services:
- name: docker:19.03.1-dind

#docker in docker 版本19之后要禁用tls,后者配置证书
variables:
DOCKER_TLS_CERTDIR: ""


#每一个stages都会git clone项目
stages:
- package
- build
- deploy

#每一个stages前都会执行这下面的脚本
before_script:
- pwd
- ls

gradle_package:
image: java:8
stage: package
only:
- deploy-dev
script:
- ./gradlew bootJar
artifacts:
paths:
- build/libs/
docker_build:
stage: build
only:
- deploy-dev
script:
- docker build -t test:latest .

docker模式(dood)

错误1:

1
2
3
4
Exception in thread "main" java.lang.UnsatisfiedLinkError: Unable to load library 'NVSSDK':
libNVSSDK.so: 无法打开共享对象文件: 没有那个文件或目录
libossdk.so: 无法打开共享对象文件: 没有那个文件或目录
Native library (linux-x86-64/libNVSSDK.so) not found in resource path ([file:/opt/bpf/package/term_model/NetCameraCapture/NetCameraCapture-0.0.1-SNAPSHOT.jar])

解决

1
2
3
4
5
vi /etc/ld.so.conf
-------------------
/usr/lib #so包路径
-------------------
ldconfig

supervisord文件添加

1
environment=PATH=/home/face/jdk1.8/bin:/opt/bpf/package/term_model/NetCameraCapture,LD_LIBRARY_PATH=/usr/lib

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 密码登录验证
mongo 10.30.80.194:27017 -u admin --authenticationDatabase=admin -p mima
#查看数据库列表
>show dbs
#切换数据库
>use admin
#查看当前数据库
>db
#查看所有表
>show tables
#验证密码
>db.auth("admin", "adminPass")
#查询表中所有数据
>db.表名.find()
#查看集群状态
>rs.status()

常用场景

1. 批量更新updateMany

更新对象A中包含list<对象B中设备id为a1>,且对象B中的状态为0的所有数据,把状态更新为1

数据结构
1
2
3
4
5
6
7
8
9
10
11
{ 
"_id" : ObjectId("5d566377e831932f076cecfe"),
"name" : "222",
"devices_statuses" : [
{
"device_id" : "a1",
"status" : "0"
}
],
"_class" : "com.xxx.bean.CustomerInfoDevice"
}

####updateMany更新多条

findAndModify区别 findAndModify更新单条,sort排序的首条

mongo对应的js查询脚步

1
db.b_customer_info_device.updateMany({"devices_statuses.status":"0","devices_statuses.device_id":"a1"},{$pull:{"devices_statuses":{term_id:"D00010"}}});

springboot对应写法

1
2
3
4
5
6
7
Query query = new Query(Criteria.where("devices_statuses.device_id").is(termId).and("devices_statuses.status").in("0");
Update update = new Update();
//更新删除,删除devices_statuses数组对象中termId=?的,pull为从数组移出
//update.pull("devices_statuses", Query.query(Criteria.where("term_id").is(termId)));
//更新状态为1
update.set("devices_statuses.$.sync_status", "1");
mongoTemplate.updateMulti(query, update, CustomerInfoDevice.class);

2. 正则匹配$regex

1
2
3
4
5
6
---data
{"notice_key" : "[E00000003, 测试1]"}
---shell 且查询
db.getCollection("m_mq_log_record").find({"notice_key":{ $regex: /(?=.*测试1)(?=.*E00000003)/ }})
---java 且查询,`|`为或查询
query.addCriteria(Criteria.where("notice_key").regex("(?=.*测试1)(?=.*E00000003|.*E00000004|)"));

3. 聚合查询aggregate

数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
{
_id: ObjectId(7df78ad8902c)
name: '张三',
user: "san"
code: '0'
},
{
_id: ObjectId(7df78ad8902c)
name: '李四',
user: "si"
code: '1'
},
{
_id: ObjectId(7df78ad8902c)
name: '张三',
user: "san"
code: '1'
}
}
group

_id里面的字段进行分组统计,这里按code字段进行分组

_id:null 统计所有

_id:"$code"按code字段进行统计

1
2
3
4
5
6
7
8
9
db.getCollection("m_user").aggregate([
{
"$group":{
_id:"$code"
,recordNum:{'$sum': 1}
}
}
]);

执行结果

1
2
3
4
5
6
7
8
9
{ 
"_id" : "0",
"recordNum" : 1.0
}
// ----------------------------------------------
{
"_id" : "1",
"recordNum" : 2.0
}

group双层嵌套($push)-Pivot Data

先group统计最内层,然后把group聚合的数组对象放到子对象那(利用 subName: { $push: "$$ROOT" }

$push: "$$ROOT"是把聚合的对象放到一个字段subName里面

然后对统计好的再进行统计,如果要统计子对象数组里面的某个字段的数量,用{ $sum: "$$ROOT.total" }

1
2
3
4
-- 先统计event_id
{ "$group": { "_id": { "event_id": "$event_id", "event_sub_type": "$event_sub_type" }, "total": { "$sum": "$num" }, sub: { $push: "$$ROOT" } } }
-- 在分组统计event_sub_type
{ "$group": { "_id": "$_id.event_sub_type", sub: { $push: "$$ROOT" },total: { $sum: "$$ROOT.total" } } }
project控制输出显示的结果

1为显示该字段

1
2
3
4
"$project": {
"_id": 1,
"customer_id": 1
}
cond类似case when

cond里面的if只支持一个条件,但是cond可以嵌套

Java: ConditionalOperators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--如果$err_status=5 输出 1否则输出 0
"$cond": { "if": { "$eq": ["$err_status", "5"] }, "then": 1, "else": 0 }
-- 结合project可以统计错误信息等于5的数据条数
"$project": {
customer_id": 1
, "fail_tatus": { "$cond": { "if": { "$eq": ["$err_msg", "5"] }, "then": 1, "else": 0 } }
}
-- cond嵌套使用,"$gt": ["$record", 0] 可以用于判断对象是否为null,不为null继续cond,然后继续添加条件可以判断数组对象内的某个条件,达到1对多,子对象数组统计,判读子对象满足某个条件就设置为1,达到统计效果
{
"$cond": {
"if": { "$gt": ["$record", 0] }, "then":
{
"$cond": {
"if": { "$eq": ["$record.is_opposite", false] }, "then":
1, "else": 0
}
}, "else": 0
}
}
match条件过滤
1

unwind

嵌入实体平铺,1个对象里面包含数组,平铺成一个对象对一个数组的内容,最终等于数组的条数

1
2
3
"$unwind": "$term_info"
-- preserveNullAndEmptyArrays 为true时允许对象为null,不然平铺时如果对象为null时为null的这条数据就会消失
{ "$unwind": { "path": "$record", "preserveNullAndEmptyArrays": true } }
lookup

Java: LookupOperation

表关联左连接

1
2
3
4
-- 表2为主集合
-- from1, localField 表1字段, foreignField 表2字段, as 新表表面
"$lookup": { "from": "b_terminfo", "localField": "devices_statuses.term_id", "foreignField": "term_id", "as": "term_info" }
-- 查询出的结果,表2-[表1数组]
elemMatch 内嵌数组,查询,其中数组里面的一个对象完全满足才会查出来
1
2
3
4
5
"$elemMatch": {
"term_id": "M59903"
, "sync_status": "progressFail"
, "err_msg": { "$ne": "5" }
}}

对应java

1
2
3
4
5
Query query = new Query(Criteria.where("devices_statuses").elemMatch(
Criteria.where("term_id").is(termId)
.and("sync_status").is(Constants.OFFLINE_SYNC_DEVICE_PROGRESSFAIL)
.and("err_msg").ne(Constants.OFFLINE_SYNC_ERRORREASON_FAIL_FEATURE)
));
facet

多条语句组合一个结果,a,b,c各为独立的查询语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
db.getCollection("b_company").aggregate([
{
"$facet": {
"a": [
{ "$project": { Id: 1, "day": { "$substr": ["$time", 0, 10] } } }
, { "$match": { day: "2020-07-09" } }
, { "$group": { "_id": "$day", sum: { "$sum": 1 } } }
],
"b": [
{ "$group": { "_id": null, total2: { "$sum": 1 } } }
],
"c":[
{"$lookup":{from:"b_user",localField:"Id",foreignField:"company_id",as:"user"}}
,{"$unwind":"$user"}
,{ "$project": { Id: 1, "day": { "$substr": ["$user.user_login_time", 0, 10] } } }
,{ "$match": { day: "2021-02-05" } }
,{"$group":{"_id":"$Id",sum:{"$sum":1}}}
,{"$count":"total"}
]
}
}
])

对应java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.facet()
.and(
Aggregation.project("_id").and("time").substring(0, 10).as("day")
, Aggregation.match(Criteria.where("day").is(nowDate))
, Aggregation.count().as("count")
).as("day")
.and(
Aggregation.count().as("total")
).as("total")
.and(
Aggregation.lookup("b_user", "Id", "company_id", "user")
, Aggregation.unwind("$user")
, Aggregation.project("_id").and("user.user_login_time").substring(0, 10).as("day")
, Aggregation.match(Criteria.where("day").is(nowDate))
, Aggregation.group("_id")
, Aggregation.count().as("total")
).as("login")
);
$substr

日期转换为天

1
2
3
4
-- yyyy-mm-dd HH:mm:ss 转化成 yyyy-mm-dd
{ "$project": { Id: 1, "day": { "$substr": ["$time", 0, 10] } } }
--java
Aggregation.project("_id").and("time").substring(0, 10).as("day")

最终示例

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
db.getCollection("b_customer_info_device").aggregate([
{ "$unwind": "$devices_statuses" }
, { "$lookup": { "from": "b_terminfo", "localField": "devices_statuses.term_id", "foreignField": "term_id", "as": "term_info" } }
, { "$unwind": "$term_info" }
, { "$match": {} }
, {
"$project": {
"_id": 1, "customer_id": 1, "class_name": 1
, "feature_fail": { "$cond": { "if": { "$eq": ["$devices_statuses.err_msg", "5"] }, "then": 1, "else": 0 } }
, "total_fail": { "$cond": { "if": { "$eq": ["$devices_statuses.sync_status", "progressFail"] }, "then": 1, "else": 0 } }
}
}
, {
"$group": {
"_id": { "_id": "$_id", "customer_id": "$customer_id", "class_name": "$class_name" }
, "total_fail": { "$sum": "$total_fail" }
, "feature_fail": { "$sum": "$feature_fail" }
}
}
, {
"$project": {
"_id": 1, "customer_id": 1, "img_store_data": 1, "customer_name": 1, "class_name": 1
, "feature_fail": { "$cond": { "if": { "$gt": ["$feature_fail", 0] }, "then": 1, "else": 0 } }
, "total_fail": { "$cond": { "if": { "$gt": ["$total_fail", 0] }, "then": 1, "else": 0 } }
}
}

, {
"$group": {
"_id": null
, "total_customer": { "$sum": 1 }
, "total_fail": { "$sum": "$total_fail" }
, "feature_fail": { "$sum": "$feature_fail" }
}
}
]);

对应mongotemplate

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
ConditionalOperators.Cond condOperatorsFeature=ConditionalOperators.when(
criteria.where("devices_statuses.err_msg").is(Constants.OFFLINE_SYNC_ERRORREASON_FAIL_FEATURE))
.then(1)
.otherwise(0);
ConditionalOperators.Cond condOperatorsFail=ConditionalOperators.when(
criteria.where("devices_statuses.sync_status").is(Constants.OFFLINE_SYNC_DEVICE_PROGRESSFAIL))
.then(1)
.otherwise(0);


ConditionalOperators.Cond condOperatorsFeatureTotal=ConditionalOperators.when(
criteria.where("feature_fail").gt(0))
.then(1)
.otherwise(0);
ConditionalOperators.Cond condOperatorsFailTotal=ConditionalOperators.when(
criteria.where("total_fail").gt(0))
.then(1)
.otherwise(0);

Aggregation aggregation = Aggregation.newAggregation(
Aggregation.unwind("$devices_statuses")
,LookupOperation.newLookup().from("b_terminfo")
.localField("devices_statuses.term_id")
.foreignField("term_id").as("term_info")
,Aggregation.unwind("$term_info")
,Aggregation.match(criteria)
,Aggregation.project("customer_id","class_name")
.and(condOperatorsFeature).as("feature_fail")
.and(condOperatorsFail).as("total_fail")
,Aggregation.group("customer_id","class_name")
.sum("total_fail").as("total_fail")
.sum("feature_fail").as("feature_fail")
,Aggregation.project()
.and(condOperatorsFeatureTotal).as("feature_fail")
.and(condOperatorsFailTotal).as("total_fail")
,Aggregation.group()
.count().as("total_customer")
.sum("total_fail").as("total_fail")
.sum("feature_fail").as("feature_fail")
);
AggregationResults<BasicDBObject> dBObjects=mongoTemplate.aggregate(aggregation,"b_customer_info_device",BasicDBObject.class);

JSONArray countResult = JSON.parseObject(dBObjects.getRawResults().toJson()).getJSONArray("result");
if(countResult!=null&&countResult.size()>0){
JSONObject jobj = (JSONObject)countResult.get(0);
return jobj;
}

表关联子类统计

按类别统计每个事件的数量,输出结果如下

1
2
3
4
5
6
7
8
9
|-类型1
|--事件1
|----事件1记录1
|----事件1记录2
|----事件1记录n条
|--事件2
|-类型2
|--事件3
|----事件3记录1
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
String startTime = startDay + " 00:00:00";

ConditionalOperators.Cond condCompany= addCompanyIdCriteriaInCond("record.company_id");

//判断是否是当天
ConditionalOperators.Cond condDay = ConditionalOperators.when(Criteria.where("record.trans_time").gte(startTime)
).then(condCompany!=null?condCompany:1).otherwise(0);

//添加字表的判断是否是应答事件
ConditionalOperators.Cond condOpposite = ConditionalOperators.when(Criteria.where("record.is_opposite").is(false)
).then(condDay).otherwise(0);

//gt(0)是为了判断对象(是否有记录)是否为null,如果有对象就会大于0
ConditionalOperators.Cond condOperators = ConditionalOperators.when(Criteria.where("record").gt(0)
).then(condOpposite).otherwise(0);

Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("event_type").is("device"))
, Aggregation.lookup("r_event_record", "event_id", "event_id", "record")
, Aggregation.unwind("$record", true)
, Aggregation.project("event_id", "event_name","event_sub_type").and(condOperators).as("num")
, Aggregation.group("event_id", "event_name","event_sub_type").sum("num").as("total")
, Aggregation.group("_id.event_sub_type").push("$$ROOT").as("sub").sum("$$ROOT.total").as("total")
);

AggregationResults<BasicDBObject> dBObjects = mongoTemplate.aggregate(aggregation, "b_event_info", BasicDBObject.class);
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
db.getCollection("b_event_info").aggregate([
{ "$match": { "event_type": "device" } }
, { "$lookup": { "from": "r_event_record", "localField": "event_id", "foreignField": "event_id", "as": "record" } }
, { "$unwind": { "path": "$record", "preserveNullAndEmptyArrays": true } }

, {
"$project": {
"event_id": 1, "event_name": 1, "num":
{
"$cond": {
"if": { "$gt": ["$record", 0] }, "then":
{
"$cond": {
"if": { "$eq": ["$record.is_opposite", false] }, "then":
{ "$cond": { "if": { "$gte": ["$record.trans_time", "2021-03-16 00:00:00"] }, "then": 1, "else": 0 } }, "else": 0
}
}, "else": 0
}
}
, "event_sub_type": 1
}
}
, { "$group": { "_id": { "event_id": "$event_id", "event_sub_type": "$event_sub_type" }, "total": { "$sum": "$num" }, sub: { $push: "$$ROOT" } } }
, { "$group": { "_id": "$_id.event_sub_type", sub: { $push: "$$ROOT" },total: { $sum: "$$ROOT.total" } } }
--因为$addFields在mongotemplate找不到对应的语句,所以用上面的$group替代
// ,{"$project": {"_id":1,"sub":1, total: { $sum: "$sub.total" }}}
// , {
// $addFields:
// {
// total: { $sum: "$books.total" }
// }
// }
])

常用命令

1
2
3
4
5
6
7
8
9
10
#查看mongo内存使用命令
mongostat
---------------------------------------------------------------------------------------
insert query update delete getmore command dirty used flushes vsize res qrw arw net_in net_out conn time
2 10 4 *0 0 3|0 0.1% 80.0% 0 8.98G 7.85G 0|0 5|0 10.8k 68.6k 30 Aug 20 11:51:55.367
*0 *0 *0 *0 0 2|0 0.1% 80.0% 0 8.98G 7.85G 0|0 5|0 212b 62.0k 29 Aug 20 11:51:56.368
2 10 4 *0 0 2|0 0.1% 80.0% 0 8.98G 7.86G 0|0 5|0 10.8k 68.2k 29 Aug 20 11:51:57.367
*0 3 1 *0 0 2|0 0.1% 80.0% 0 8.98G 7.86G 0|0 5|0 5.38k 65.1k 29 Aug 20 11:51:58.367
*0 *0 *0 *0 0 1|0 0.1% 80.0% 0 8.98G 7.86G 0|0 5|0 157b 61.8k 29 Aug 20 11:51:59.368
--------------------------------------------------------------

WiredTiger存储引擎

wiredTiger对内存使用会分为两大部分,一部分是内部内存,另外一部分是文件系统的缓存。内部内存默认值有一个计算公式{ 50% of(RAM-1GB) ,or256MB },索引和集合的内存都被加载到内部内存,索引是被压缩的放在内部内存,集合则没有压缩。wiredTiger会通过文件系统缓存,自动使用其他所有的空闲内存,放在文件系统缓存里面的数据,与磁盘上的数据格式一致,可以有效减少磁盘I/O。

mongodb不干涉内存管理,将内存管理工作交给操作系统去处理。在使用时必须随时监测内存使用情况,因为mongodb会把所有能用的内存都用完。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
For example, on a system with a total of 4GB of RAM the WiredTiger cache will use 1.5GB of RAM (0.5 * (4 GB - 1 GB) = 1.5 GB). Conversely, a system with a total of 1.25 GB of RAM will allocate 256 MB to the WiredTiger cache because that is more than half of the total RAM minus one gigabyte (0.5 * (1.25 GB - 1 GB) = 128 MB < 256 MB).

NOTE

In some instances, such as when running in a container, the database can have memory constraints that are lower than the total system memory. In such instances, this memory limit, rather than the total system memory, is used as the maximum RAM available.

To see the memory limit, see hostInfo.system.memLimitMB.

By default, WiredTiger uses Snappy block compression for all collections and prefix compression for all indexes. Compression defaults are configurable at a global level and can also be set on a per-collection and per-index basis during collection and index creation.

Different representations are used for data in the WiredTiger internal cache versus the on-disk format:

Data in the filesystem cache is the same as the on-disk format, including benefits of any compression for data files. The filesystem cache is used by the operating system to reduce disk I/O.
Indexes loaded in the WiredTiger internal cache have a different data representation to the on-disk format, but can still take advantage of index prefix compression to reduce RAM usage. Index prefix compression deduplicates common prefixes from indexed fields.
Collection data in the WiredTiger internal cache is uncompressed and uses a different representation from the on-disk format. Block compression can provide significant on-disk storage savings, but data must be uncompressed to be manipulated by the server.
Via the filesystem cache, MongoDB automatically uses all free memory that is not used by the WiredTiger cache or by other processes.

参考

mongodb——内存

什么是容器

容器本质上是一种进程隔离的技术。容器为进程提供了一个隔离的环境,容器内的进程无法访问容器外的进程。

为什么说容器本质上是进程,通过执行查看进程树命令(pstree -pa)就可以看出来:

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
[root@centos7 ~]# pstree -pa
systemd,1 --switched-root --system --deserialize 22
├─NetworkManager,1177 --no-daemon
│ ├─dhclient,1222 -d -q -sf /usr/libexec/nm-dhcp-helper -pf /var/run/dhclient-eth0.pid ...
│ ├─{NetworkManager},1200
│ └─{NetworkManager},1202
├─acpid,1212
├─agetty,1198 --noclear tty1 linux
├─agetty,1199 --keep-baud 115200,38400,9600 ttyS0 vt220
├─auditd,1150
│ └─{auditd},1151
├─avahi-daemon,1175
│ └─avahi-daemon,1184
├─containerd,13634
│ ├─{containerd},13635
......
├─containerd-shim,31245 -namespace moby -id03ee100f264b24f13474f82459512670a15815312027
│ ├─sh,31270 -c java ${JAVA_OPTS} -jar /app.jar
│ │ └─java,31294 -jar /app.jar
│ │ ├─{java},31305
│ │ ├─{java},31310
│ ├─{containerd-shim},31248
......
#---------小实验---------------:
#进入一个容器执行ping
[root@centos7 ~]# docker exec -it 03ee sh
/ # ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.264 ms
#在启动一个终端查看该容器的进程树
[root@centos7 ~]# pstree -pa 31245
containerd-shim,31245 -namespace moby -id03ee100f264b24f13474f82459512670a15815312027
├─sh,31270 -c java ${JAVA_OPTS} -jar /app.jar
│ └─java,31294 -jar /app.jar
│ ├─{java},31305
......
│ └─{java},31699
├─sh,43316
│ └─ping,43602 127.0.0.1
├─{containerd-shim},31248
......
└─{containerd-shim},34818
#可以看到该容器进程下多了一个刚刚在容器里面执行的ping命令进程

对于容器技术而言,它实现资源层面上的限制和隔离,依赖于 Linux 内核所提供的 cgroup 和 namespace 技术。

  • cgroup 的主要作用:管理资源的分配、限制;
  • namespace 的主要作用:封装抽象,限制,隔离,使命名空间内的进程看起来拥有他们自己的全局资源;

容器和虚拟机区别

容器和虚拟机之间的主要区别在于,虚拟机将整个计算机虚拟化到硬件层,而容器只虚拟化操作系统级别以上的软件层。

cgroup

简介

cgroups 的全称是control groups,cgroup 主要限制的资源cpu、内存、网络、磁盘I/O,cgroups为每种可以控制的资源定义了一个子系统,可以通过执行cat /proc/cgroups或者ls /sys/fs/cgroup/查看所有支持的子系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@centos7 ~] cat /proc/cgroups #查看所有支持的subsystem
#subsys_name hierarchy num_cgroups enabled
cpuset 9 89 1 #可以为cgroups中的进程分配单独的cpu节点或者内存节点
cpu 6 294 1 #主要限制进程的cpu使用率
cpuacct 6 294 1 #可以统计cgroups中的进程的cpu使用报告
memory 2 294 1 #可以限制进程的memory使用量
devices 5 294 1 #可以控制进程能够访问某些设备
freezer 7 89 1 #可以挂起或者恢复cgroups中的进程
net_cls 10 89 1 #可以标记cgroups中进程的网络数据包,然后可以使用tc模块对数据包进行控制
blkio 4 294 1 #可以限制进程的块设备io
perf_event 8 89 1 #对cgroup进行性能监控
hugetlb 3 89 1 #限制cgroup的huge pages(大内存页)使用量
pids 11 294 1 #限制进程数量
net_prio 10 89 1 #可以为进程设置优先级

实战

cpu-手动限制进程的cpu使用率

这里以cpu子系统来限制进程的cpu使用率进行实战认识,其他子系统也是差不多的原理。

  1. 进入到/sys/fs/cgroup/cpu目录,创建一个container_test目录,会在该目录下自动产生一些文件,这些文件代表cpu资源的各种控制指标。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@centos7 ~]# cd /sys/fs/cgroup/cpu
    [root@centos7 cpu]# mkdir container_test
    [root@centos7 cpu]# ls
    cgroup.clone_children container_test cpu.cfs_period_us cpu.shares release_agent
    cgroup.event_control cpuacct.stat cpu.cfs_quota_us cpu.stat system.slice
    cgroup.procs cpuacct.usage cpu.rt_period_us kubepods.slice tasks
    cgroup.sane_behavior cpuacct.usage_percpu cpu.rt_runtime_us notify_on_release user.slice
    [root@centos7 cpu]# cd container_test/
    [root@centos7 container_test]# ls
    cgroup.clone_children cpuacct.stat cpu.cfs_period_us cpu.rt_runtime_us notify_on_release
    cgroup.event_control cpuacct.usage cpu.cfs_quota_us cpu.shares tasks
    cgroup.procs cpuacct.usage_percpu cpu.rt_period_us cpu.stat
  2. 创建一个测试脚本,来模拟进程吃掉所有资源,通过top命令,可以看到sh命令进程cpu使用率已经100%了。

    1
    2
    3
    4
    5
    [root@centos7 container_test]# cat ~/while.sh
    #!/bin/bash
    while : ; do : ; done &
    [root@centos7 container_test]# sh ~/while.sh
    [root@centos7 container_test]# top

    pSzRynI.png

  3. 查看cpu.cfs_quota_us 值,默认为-1,代表未限制,cfs_period_us默认为100000us=100ms=0.1s(秒),接下来我们向cpu.cfs_quota_us 输入20ms=20000us,cfs_period_us值维持不变还是为100ms,cfs_quota_us表示的是cfs_period_us的周期内,分配20/100的时间,即20%,接下来验证下

    1
    2
    3
    4
    5
    6
    7
    [root@centos7 container_test]# cat cpu.cfs_quota_us
    -1
    [root@centos7 container_test]# cat cpu.cfs_period_us
    100000
    [root@centos7 container_test]# echo 20000 > cpu.cfs_quota_us //只能用echo进行写入,用vim会提示错误
    [root@centos7 container_test]# cat cpu.cfs_quota_us
    20000
  4. 要让cpu限制生效,还需要进行进程id绑定到限制资源里面。

    1
    2
    3
    [root@centos7 container_test]# echo 39442 > tasks
    [root@centos7 container_test]# cat tasks
    39442
  5. 最后执行top查看cpu使用率,基本在20以下,最后记得结束掉测试程序,删除目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [root@centos7 container_test]# top

    top - 17:29:29 up 318 days, 4:33, 1 user, load average: 1.60, 1.13, 0.97
    Tasks: 685 total, 2 running, 683 sleeping, 0 stopped, 0 zombie
    %Cpu(s): 0.9 us, 0.2 sy, 0.0 ni, 98.7 id, 0.1 wa, 0.0 hi, 0.1 si, 0.0 st
    KiB Mem : 74055424 total, 12164068 free, 25939268 used, 35952088 buff/cache
    KiB Swap: 0 total, 0 free, 0 used. 47543512 avail Mem

    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
    39442 root 20 0 113284 384 192 R 19.8 0.0 0:58.94 sh

    [root@centos7 container_test]# kill 39442
    [root@centos7 cpu]# rmdir container_test

通过容器ID查看对应的cgroup

知道了cgroup的基本原理,现在就可以来验证k8s的pod是不是通过cgroup来控制的了。

  1. 查询容器id

    1
    2
    [root@centos7 ~]# docker ps | grep exxk-api
    03ee100f264b harbor…/exxk-api "sh -c 'java ${JAVA_…" 4 days ago Up 4 days k8s_springboot-app_exxk-api…}'"
  2. 根据容器id查询进程PID

    1
    2
    3
    4
    [root@centos7 ~]# docker top 03ee100f264b
    UID PID PPID C STIME TTY TIME CMD
    root 31270 31245 0 Feb22 ? 00:00:00 sh -c java ${JAVA_OPTS} -jar /app.jar
    root 31294 31270 0 Feb22 ? 00:13:44 java -jar /app.jar
  3. 根据进程PID查询cgroup,可以看到name、memory、hugetlb、blkio…各个subsystem的配置目录,其实都是指向的同一个目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@centos7 ~]# cat /proc/31270/cgroup
    11:pids:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8ddb18c0_ed52_4ac3_877c_4c53bac38e1c.slice/docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope
    10:net_prio,net_cls:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8ddb18c0_ed52_4ac3_877c_4c53bac38e1c.slice/docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope
    9:cpuset:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8ddb18c0_ed52_4ac3_877c_4c53bac38e1c.slice/docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope
    8:perf_event:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8ddb18c0_ed52_4ac3_877c_4c53bac38e1c.slice/docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope
    7:freezer:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8ddb18c0_ed52_4ac3_877c_4c53bac38e1c.slice/docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope
    6:cpuacct,cpu:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8ddb18c0_ed52_4ac3_877c_4c53bac38e1c.slice/docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope
    5:devices:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8ddb18c0_ed52_4ac3_877c_4c53bac38e1c.slice/docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope
    4:blkio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8ddb18c0_ed52_4ac3_877c_4c53bac38e1c.slice/docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope
    3:hugetlb:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8ddb18c0_ed52_4ac3_877c_4c53bac38e1c.slice/docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope
    2:memory:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8ddb18c0_ed52_4ac3_877c_4c53bac38e1c.slice/docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope
    1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8ddb18c0_ed52_4ac3_877c_4c53bac38e1c.slice/docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope
  4. 进入到其中一个目录,就能看到该容器的cgroup相关资源配置

    1
    2
    3
    [root@centos7 ~]# cd /sys/fs/cgroup/cpu//kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8ddb18c0_ed52_4ac3_877c_4c53bac38e1c.slice/docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope
    [root@centos7 docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope]# ls
    cgroup.clone_children cgroup.event_control cgroup.procs cpuacct.stat cpuacct.usage cpuacct.usage_percpu cpu.cfs_period_us cpu.cfs_quota_us cpu.rt_period_us cpu.rt_runtime_us cpu.shares cpu.stat notify_on_release tasks
  5. 在关联进程的配置里面可以进一步确定关联了该容器的pid

    1
    2
    3
    4
    5
    [root@centos7 docker-03ee100f264b24f13474f82459512670a15815312027345bbecd82fe66ab709f.scope]# cat tasks
    31270
    31294
    31305
    ...

namespace

简介

容器和虚拟机技术一样,从操作系统级上实现了资源的隔离,它本质上是宿主机上的进程(容器进程),所以资源隔离主要就是指进程资源的隔离。实现资源隔离的核心技术就是 Linux namespace。

隔离意味着可以抽象出多个轻量级的内核(容器进程),这些进程可以充分利用宿主机的资源,宿主机有的资源容器进程都可以享有,但彼此之间是隔离的,同样,不同容器进程之间使用资源也是隔离的,这样,彼此之间进行相同的操作,都不会互相干扰,安全性得到保障。

为了支持这些特性,Linux namespace 实现了 6 项资源隔离,基本上涵盖了一个小型操作系统的运行要素,包括主机名、用户权限、文件系统、网络、进程号、进程间通信。

Namespace 隔离内容
ipc 信号量、消息队列、共享内存
mnt 挂载点(文件系统)
net 网络设备、网络栈、端口等
pid 进程编号
user 用户和用户组
uts 主机名或域名

可以通过执行ls -al /proc/<PID>/ns查看进程的隔离资源

1
2
3
4
5
6
7
8
9
10
[root@centos7 ~]# ls -al /proc/31245/ns
总用量 0
dr-x--x--x 2 root root 0 2月 28 14:59 .
dr-xr-xr-x 9 root root 0 2月 22 10:40 ..
lrwxrwxrwx 1 root root 0 2月 28 14:59 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 2月 28 14:59 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 2月 28 14:59 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 2月 28 14:59 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 2月 28 14:59 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 2月 28 14:59 uts -> uts:[4026531838]

Namespace的API由三个系统调用(clone、unshare、setns)和一系列 /proc 文件组成。

  • clone() : 实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述系统调用参数达到隔离的目的。
  • unshare() : 使某进程脱离某个 namespace。
  • setns() : 把某进程加入到某个 namespace。

unshare 同名的命令行工具(它实际上是调用了系统调用 unshare)

实战

pid-手动隔离进程的pid

要只能看到自己的pid,让进程看起来像在一个新的系统,这就是pid隔离。

  1. 创建pid namespace

  2. 在namespace中挂载proc文件系统

    说明:如果只是创建pid namespace,不能保证只看到namespace中的进程。因为类似ps这类系统工具读取的是proc文件系统。proc文件系统没有切换的话,虽然有了pid namespace,但是不能达到我们在这个namespace中只看到属于自己namespace进程的目的。在创建pid namespace的同时,使用--mount-proc选项,会创建新的mount namespace,并自动mount新的proc文件系统。这样,ps就可以看到当前pid namespace里面所有的进程了。因为是新的pid namespace,进程的PID也是从1开始编号。对于pid namespace里面的进程来说,就好像只有自己这些个进程在使用操作系统。

    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
    #前两个步骤可以一个命令完成
    [root@centos7 ~]# unshare --pid --mount-proc --fork bash
    [root@centos7 ~]# ps -ef
    UID PID PPID C STIME TTY TIME CMD
    root 1 0 0 10:37 pts/0 00:00:00 bash
    root 12 1 0 10:37 pts/0 00:00:00 ps -ef

    #启动一个新的bash窗口
    [root@mh-k8s-worker-247-16 ~]# pstree -pa
    systemd,1 --switched-root --system --deserialize 22
    ├─NetworkManager,1177 --no-daemon
    ├─sshd,1427 -D
    │ ├─sshd,48070
    │ │ └─bash,48082
    │ │ └─unshare,63281 --pid --mount-proc --fork bash
    │ │ └─bash,63282
    │ │ └─ping,29347 127.0.0.1
    ......

    [root@centos7 ~]# ps -ef | grep bash
    root 63281 48082 0 10:37 pts/0 00:00:00 unshare --pid --mount-proc --fork bash
    root 63282 63281 0 10:37 pts/0 00:00:00 bash

    #查看新建的namespace,会发现和之前执行这个命令的结果不同,而且只有mnt和pid不同。
    [root@centos7 ~]# ls -al /proc/self/ns
    总用量 0
    dr-x--x--x 2 root root 0 3月 1 14:52 .
    dr-xr-xr-x 9 root root 0 3月 1 14:52 ..
    lrwxrwxrwx 1 root root 0 3月 1 14:52 ipc -> ipc:[4026531839] #ipc:[4026531839]
    lrwxrwxrwx 1 root root 0 3月 1 14:52 mnt -> mnt:[4026534037] #mnt:[4026531840]
    lrwxrwxrwx 1 root root 0 3月 1 14:52 net -> net:[4026531956] #net:[4026531956]
    lrwxrwxrwx 1 root root 0 3月 1 14:52 pid -> pid:[4026534038] #pid:[4026531836]
    lrwxrwxrwx 1 root root 0 3月 1 14:52 user -> user:[4026531837] #user:[4026531837]
    lrwxrwxrwx 1 root root 0 3月 1 14:52 uts -> uts:[4026531838] #uts:[4026531838]

    #查看挂载信息
    [root@centos7 ~]#cat /proc/self/mountinfo | sed 's/ - .*//'

    从上面的结果可以知道PID= 63281其实和PID=1是同一个进程。

mount-手动隔离进程的文件系统

  1. 创建一个根文件系统,利用docker的alpine系统创建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [root@centos7 ~]# docker run -it  alpine sh
    #另开一个bash窗口
    [root@centos7 ~]# docker ps | grep alpine
    b8824ba694ab alpine "sh" About a minute ago Up About a minute great_mcnulty
    [root@centos7 ~]# docker export b8824ba694ab --output=alpine.tar
    [root@centos7 ~]# mkdir alpine
    [root@centos7 ~]# tar -xf alpine.tar -C alpine
    [root@centos7 ~]# ls alpine
    bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
  2. 创建一个容器,--propagation private 是为了容器内部的挂载点都是私有,private 表示既不继承主挂载点中挂载和卸载操作,自身的挂载和卸载操作也不会反向传播到主挂载点中。

    1
    [root@centos7 ~]# unshare --pid --mount --fork --propagation private bash
  3. 使用pivot_root命令,在容器里面更换root目录

    1
    2
    3
    4
    5
    6
    7
    8
    [root@centos7 ~]# mkdir -p alpine/old_root
    [root@centos7 ~]# mount --bind ./alpine/ ./alpine/
    [root@centos76 ~]# cd alpine
    [root@centos7 alpine]# pivot_root ./ ./old_root/
    [root@centos7 alpine]# exec /bin/sh
    / # /bin/ls
    bin etc lib mnt opt root sbin sys usr
    dev home media old_root proc run srv tmp var
  4. 额外,可以使用cat /proc/self/mountinfo | sed 's/ - .*//'命令分析挂载信息。

使用shell命令创建一个简单容器

下面所有建容器的命令,就相当于docker run -it alpine sh命令了

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
#执行报错,检查max_user_namespaces的值,见常见问题1
[root@centos7 ~]# unshare --user --mount --ipc --pid --net --uts -r --fork --propagation private bash
#会发现所有的值,和之前都不同了,说明这就新建了一个全新的容器了
[root@centos7 ~]# ls -al /proc/self/ns
总用量 0
dr-x--x--x 2 root root 0 3月 1 16:31 .
dr-xr-xr-x 9 root root 0 3月 1 16:31 ..
lrwxrwxrwx 1 root root 0 3月 1 16:31 ipc -> ipc:[4026533088]
lrwxrwxrwx 1 root root 0 3月 1 16:31 mnt -> mnt:[4026533086]
lrwxrwxrwx 1 root root 0 3月 1 16:31 net -> net:[4026533091]
lrwxrwxrwx 1 root root 0 3月 1 16:31 pid -> pid:[4026533089]
lrwxrwxrwx 1 root root 0 3月 1 16:31 user -> user:[4026533085]
lrwxrwxrwx 1 root root 0 3月 1 16:31 uts -> uts:[4026533087]
[root@centos7 ~]# mkdir -p alpine/old_root
[root@centos7 ~]# mount --bind ./alpine/ ./alpine/
[root@centos76 ~]# cd alpine
[root@centos7 alpine]# pivot_root ./ ./old_root/
[root@centos7 alpine]# exec /bin/sh
#另一个终端,进入容器
[root@mh-k8s-worker-247-16 ~]# ps -ef | grep bash
root 14050 63703 0 16:30 pts/0 00:00:00 unshare --user --mount --ipc --pid --net --uts -r --fork --propagation private bash
#也可以用该命令进入指定容器
[root@mh-k8s-worker-247-16 ~]# nsenter -n -m -i -u -U -t 14050 /bin/sh
/ # /bin/ls
bin etc lib mnt opt root sbin sys usr
dev home media old_root proc run srv tmp var
#通过该命令进入docker容器
[root@mh-k8s-worker-247-16 ~]# nsenter -n -m -i -u -t 31270 /bin/sh
/ # /bin/ls
app.jar home proc sys
application.properties lib root tmp
bin linuxrc run usr
dev media sbin var
etc mnt srv

UnionFs

简介

Docker中还有另一个非常重要的问题需要解决 - 也就是镜像,我们知道镜像是有很多层的,相同的层可以共用,通过拉取(pull)镜像,就能清晰看到。那到底是如何实现的呢?

Union File System,简称UnionFS,UnionFS 其实是一种为 Linux 操作系统设计的用于把多个文件系统『联合』到同一个挂载点的文件系统服务。

UnionFS 有很多种,Docker 目前支持的联合文件系统包括 OverlayFS, AUFS, Btrfs, VFS, ZFSDevice Mapper,不同版本的Linux使用的UnionFS不同,可以通过执行docker info来查看,例如:

  • centos, docker18.03.1-ce: Storage Driver: overlay2
  • debain, docker17.03.2-ce: Storage Driver: aufs

执行docker inspect <容器id>查看容器的overlay2,可以看到

  • LowerDir:目录里面的内容是不会被修改的,目录权限是只读的(ro)。
  • MergedDir:挂载目录,合并后的目录,也就是用户看到的目录,用户的实际文件操作在这里进行。
  • UpperDir:目录里的创建,修改,删除操作,都会在这一层反映出来,权限是可读写(rw)。
  • WorkDir:它只是一个存放临时文件的目录,OverlayFS 中如果有文件修改,就会在中间过程中临时存放文件到这里。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@centos7 ~]# docker inspect 03ee100f264b
[
{
.....
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/68610da418c2e4881980fad99a2018d309a075771baaa04c7743a09035c597d4-init/diff:/var/lib/docker/overlay2/56d7c3c8a9d1c53f70cebcc9466c9eec50fab2ef61adcf5b0a5a1c971063009c/diff:/var/lib/docker/overlay2/cf9f8fb18f51fe9d1e61d36202a74ec38017f3f666e4376a5b2cdfbf48ab7e1a/diff:/var/lib/docker/overlay2/e9b2548eeefb0f65a95ee21834f9ca60fb4c34bdf3e54c559753975308d4b816/diff:/var/lib/docker/overlay2/4f5c9ca36cfb299dd4fca2f43a61b3ac62af489dd34e55d976adc6e74ea46762/diff:/var/lib/docker/overlay2/c5e61cd405a027d203aa444dfc57eb365603cf857f3a14fdc49a609166c6123f/diff:/var/lib/docker/overlay2/3dbc972492fac5b02282c8da7c04d0c49c574231cd9d11fee5a8325afe283513/diff",
"MergedDir": "/var/lib/docker/overlay2/68610da418c2e4881980fad99a2018d309a075771baaa04c7743a09035c597d4/merged",
"UpperDir": "/var/lib/docker/overlay2/68610da418c2e4881980fad99a2018d309a075771baaa04c7743a09035c597d4/diff",
"WorkDir": "/var/lib/docker/overlay2/68610da418c2e4881980fad99a2018d309a075771baaa04c7743a09035c597d4/work"
},
"Name": "overlay2"
},
......
}
]

实战

手动创建overlay

  1. 先创建四个目录,写入值,进行区分

    1
    2
    3
    4
    5
    [root@centos7 unionfs]# mkdir upper lower merged work
    [root@centos7 unionfs]# ls
    lower merged upper work
    [root@centos7 unionfs]# echo "hello lower" >lower/in_lower.txt
    [root@centos7 unionfs]# echo "hello upper" >lower/in_upper.txt
  2. 挂载一个overlay类型的目录,挂载成功,发现merged已经融合了lower文件的内容。

    1
    2
    3
    [root@centos7 unionfs]# mount -t overlay overlay -o lowerdir=./lower,upperdir=./upper,workdir=./work/ ./merged
    [root@centos7 unionfs]# ls merged/
    in_lower.txt in_upper.txt
  3. 现在修改merged下面的文件,发现对应的upper会跟着修改,但是lower并不会修改。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [root@centos7 unionfs]# cd merged/
    [root@centos7 merged]# echo "change ">> in_lower.txt
    [root@centos7 merged]# cat in_lower.txt
    hello lower
    change
    [root@centos7 merged]# cat ../upper/in_lower.txt
    hello lower
    change
    [root@centos7 merged]# cat ../lower/in_lower.txt
    hello lower
  4. 删除in_lower文件,merged目录下换删除,但是upper目录下还是存在该文件,只是文件文件类型变成了c

    1
    2
    3
    4
    5
    [root@centos7 merged]# rm in_lower.txt
    rm:是否删除普通文件 "in_lower.txt"?y
    [root@centos7 merged]# ll ../upper/
    总用量 0
    c--------- 1 root root 0, 0 3月 2 14:24 in_lower.txt

常见问题

  1. 执行unshare --user返回unshare: unshare 失败: 无效的参数

    解决:max_user_namespaces文件记录了允许创建的user namespace数量,有的系统默认是0,执行echo 2147483647 > /proc/sys/user/max_user_namespaces

参考

Linux资源管理之cgroups简介

Cgroup的原理和实践-cpu使用率控制

命名空间介绍之八:挂载命名空间和共享子树

使用Linux Namespace把进程一步一步隔离起来

容器技术原理(五):文件系统的隔离和共享

shell命令创建一个简单的容器

搞懂容器技术的基石: namespace (上)

搞懂容器技术的基石: namespace (下)

深入学习docker – 联合文件系 OverlayFS

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class TestSchedule {
@ScheduleAnnotaion(serviceId = "DataSync", scheduleDesc = "数据同步", checkIntervalTime = 30, isSignalExecuteFlag = true, addHisTaskTableFlag = false)
@Scheduled(cron = "0 * * * * ?") // 每1分钟执行一次

public void executeSchedule() throws ResponseException {
executeScheduleNow();
}

public void executeScheduleNow() {
//todo 需要添加的操作
}
}

基础

工具

服务器

下载http://websocketd.com/

添加脚本count.sh然后添加权限chmod +x count.sh

1
2
3
4
5
#!/bin/bash
for ((COUNT = 1; COUNT <= 10; COUNT++)); do
echo $COUNT
sleep 1
done

启动 ./websocketd --port=8080 ./count.sh服务端

客户端

https://jsbin.com/zemigup/edit?js,console在该网页运行下面的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var ws = new WebSocket("ws://127.0.0.1:8080");

ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};

ws.onclose = function(evt) {
console.log("Connection closed.");
};

上面不支持非127.0.0.1的,测试ws://10.30.6.10:8080需要在http://www.blue-zero.com/WebSocket/测试

参考

官网

WebSocket 教程-阮一峰

单行语法
1
2
3
cmd1 && cmd2 #cmd1正确执行cmd2,相反不执行
cmd1 || cmd2 #cmd1错误执行cmd2,相反不执行
docker ps | grep snx-vpn2 && echo 1 || echo 2 #docker ps | grep snx-vpn2存在执行1,不存在执行2
错误重定向不输出
1
cmd 1>/dev/null 2>&1 #错误不会打印

实战

1
2
3
4
5
6
#用户bpf不存在则创建用户,且不输出错误日志,但异常还是存在所以可能会导致整个脚本终止
id bpf 1>/dev/null 2>&1 || useradd bpf -d /opt/bpf/
#方式2
if id bpf1 >/dev/null 2>&1; then echo 11; fi
#方式3(推荐),不会被推出,因为没有异常,经测试还是被退出
cat /etc/passwd | cut -f1 -d':' | grep -w "bpf" -c || useradd bpf -d /opt/bpf/

优化

1
2
<! maven pom.xml & 符号报错,解决用<![CDATA[ cmd ]]> -->
<script><![CDATA[id bpf 1>/dev/null 2>&1 || useradd bpf -d /opt/bpf/]]></script>