MySQL 记录一些祖传SQL脚本

记录一些祖传SQL脚本

查找数组对象中

1
2
3
4
5
6
7
8
9
10
11
12
数据库中数据格式

items => [{"field": "AAA", "from": "A123", "to": "431"}, {"field": "BBBB", "from": "B123", "to": "B0"}]


当我要获取 field 的值为 AAA 的form字段

SELECT JSON_UNQUOTE(JSON_EXTRACT(items,replace (JSON_UNQUOTE(JSON_SEARCH(items, 'one','AAA')),'field','from') ))
FROM test
WHERE items->>'$[*].field' = 'AAA' ;

return A123
分段说明
说明 返回
JSON_SEARCH(items, ‘one’,’AAA’) 获取items 中 第一个值为’AAA’的路径 “$[0].field”
JSON_UNQUOTE(JSON_SEARCH(items, ‘one’,’AAA’)) 去除双引号 $[0].field
replace (JSON_UNQUOTE(JSON_SEARCH(items, ‘one’,’AAA’)),’field’,’from’) 字段字段为 from $[0].from
JSON_UNQUOTE(JSON_EXTRACT(items,replace (JSON_UNQUOTE(JSON_SEARCH(items, ‘one’,’AdminOrg’)),’field’,’from’) )) 根据 $[0].from 查找数据 A123

按类别sum

1
2
3
4
5
SELECT 
sum(if( type=1,money,0)) as "收入",
sum(if( type=0,money,0)) as "支出"

from test

根据经纬度计算距离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SET @lat = 123.123;
SET @lng = 456.123;

SELECT id,`name`,lat,lng,
ROUND(
6378.138*2*ASIN(
SQRT(
POW(
SIN(
(@lat*PI()/180-lat*PI()/180)/2
),2
)+COS(@lat*PI()/180)*COS(lat*PI()/180)*POW(
SIN(
(@lng*PI()/180-lng*PI()/180)/2
),2
)
)
)*1000
) AS lang
FROM test where 1 HAVING lang > 1

多类型组object-json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

case
WHEN test.type = 1 || test.type = 3
then
JSON_OBJECT(
"id",testB.id,
"test_id",testB.test_id,
"AAAA",testB.AAAA,
)
)
WHEN test.type = 2
then
JSON_OBJECT(
"id",testB.id,
"test_id",testB.test_id,
"BBBB",testB.BBB,
"CCCC",testB.CCCC
)

else JSON_OBJECT("id",null)
END as jdoc

Nginx 基础

Nginx 基础

引自 heibaiying/Full-Stack-Notes/Nginx_基础

一、Nginx 简介

1.1 简介

Nginx(engine x)是一个免费的,开源的,高性能的 HTTP 服务器, IMAP/POP3 代理服务器 和 TCP/UDP 代理服务器,通常可以用于进行反向代理和实现基于软件的负载均衡,除此之外,它还具备以下特性:

  • Nginx 在设计时遵循模块化的设计方案,可以通过组合模块来扩展实现不同的功能,具备很高的扩展性。
  • Nginx 遵循 matser \ worker 架构,主进程负责管理一个或者多个 worker 进程,每个 worker 进程负责处理实际的连接,当某个 worker 进程出错时,主进程会迅速创建一个新的 worker 来继续处理连接请求,从而保证高可用。
  • Nginx 在高连接数的情况下仍然可以保持极低的内存占用,从而可以支持高并发量地访问。
  • Nginx 支持热部署和配置热加载,并支持在不停机的情况下进行版本升级。
  • 有免费的开源版本和商业版本 ( Nginx Plus ),可以按需选择或者进行二次开发。
  • 在高并发环境下,Nginx 比其他 WEB 服务器有更快的响应速度。

1.2 正向代理和反向代理

Nginx 能够同时支持正向代理和反向代理,这两种代理模式的区别如下:

  • 正向代理发生在客户端,是客户端主动发起的代理。如我们不能直接访问某个服务器,但可以间接通过中间的代理服务器去进行访问,然后将访问结果再返回给我们。
  • 反向代理发生在服务端,客户端并不知道发生了代理,示例如下。用户只知道将请求发送给 Nginx,但是并不知道请求被转发了,也不知道被转发给了哪一台应用服务器。实际上对于用户来说,他也没必要知道,因为请求结果都是相同的。

二、基本命令

Nginx Shell 的基本使用格式如下:

1
nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
  • -?,-h :显示帮助信息;
  • -v:查看版本号;
  • -V:查看版本号及配置信息;
  • -t:检测配置文件是否有语法错误;
  • -q:静默模式,在检测配置期间除了错误提示外,不输出其他消息;
  • -s signal:向 Master 进程发送信号,支持以下信号类型:stop ( 立即停止 ),quit ( 优雅停止 ),reload ( 重新加载配置文件 ),reopen (打开一个新的日志文件来继续记录日志) ;
  • -p prefix :指定路径的前缀,默认为安装目录,如 /usr/app/nginx-1.16.1/
  • -c filename :指定配置文件的位置, 默认值为 conf/nginx.conf,其实际指向的路径为 prefix + filename
  • -g directives:从指定的配置文件中设置全局指令。

三、配置格式

3.1 基本配置格式

Nginx 的配置由全局配置和局部配置(指令块)共同组成,所有的指令块都遵循相同的配置格式:

1
2
3
<section>{
<directive> <parameters>;
}

指令块使用大括号进行划分,大括号内是独立的配置上下文,包含指令和具体的参数,每行指令以分号作为结尾。除此之外,Nginx 的配置中还支持以下操作:

  • 支持使用 include 语法来引入外部配置文件,从而可以将每个独立的配置拆分到单独的文件中;
  • 支持使用 # 符号来添加注释;
  • 支持使用 $ 符号来引用变量;
  • 部分指令的参数支持使用正则表达式。

3.2 时间和空间单位

Nginx 的配置文件支持以下空间和时间单位:

  • 空间单位:不加任何单位默认就是 bytes,除此之外还支持 k/K,m/M,g/G 等常用单位。

  • 空间单位:支持 ms (毫秒) ,s (秒) ,m (分钟) ,h (小时) ,d (天),w (星期),M (月,30天),y (年,365天) 等常用单位,并支持组合使用,如 1h 30m (1小时 30分),1y 6M(1年零6个月)。

3.3 官方配置模板

在安装 Nginx 后,在安装目录的 conf 目录下会有一个官方的配置样例 nginx.conf ,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# 使用这个参数来配置worker进程的用户和组,如果没有指定组,则默认为指定用户所处的组,默认值为nobody
user nobody;
# 指定用于处理客户端连接的worker进程的数量,通常设置为CPU的核心数。
# 如果是为IO密集型操作进行负载,可以设置为核心数的1.5 ~ 2倍
worker_processes 1;

# 指定日志的位置和日志级别,日志级别按照由低到高的顺序如下:[debug|info|notice|warn|error|crit]
error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

# 指定记录主进程的ID的文件
pid logs/nginx.pid;


events {
# 指定每个工程线程的最大连接数,总的连接数 max_clients = worker_processes * worker_connections
worker_connections 1024;
}


http {
# 使用include来引用外部文件
include mime.types;
# 指定默认MIME类型
default_type application/octet-stream;

# 定义日志的输出格式,使用$来进行变量引用
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

# 定义访问日志的存放位置
access_log logs/access.log main;


# 是否开启系统调用方法sendfile(),开启后可以直接在内核空间完成文件的发送,即零拷贝
sendfile on;
# 是否开启Socket选项,它只有在sendfile启用后才会生效
tcp_nopush on;

# 连接超时时间
keepalive_timeout 65;

# 开启文件压缩
gzip on;

# 配置nginx服务器(虚拟主机)
server {
# 监听端口
listen 80;
server_name localhost;

# 默认字符集
charset koi8-r;
# 配置当前虚拟主机的访问日志的存放位置
access_log logs/host.access.log main;

# 虚拟主机对应的映射目录
location / {
root html;
index index.html index.htm;
}

# 错误重定向页面
# error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

# 禁止访问根目录下以ht结尾的文件
location ~ /\.ht {
deny all;
}
}


# 支持配置多台虚拟主机
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;

# location / {
# root html;
# index index.html index.htm;
# }
#}


# 配置Https服务
server {
listen 443 ssl;
server_name localhost;

# 指定数字证书
ssl_certificate cert.pem;
# 指定密匙
ssl_certificate_key cert.key;

# 设置存储session的缓存类型和大小
ssl_session_cache shared:SSL:1m;
# session缓存时间
ssl_session_timeout 5m;

# 返回客户端支持的密码列表
ssl_ciphers HIGH:!aNULL:!MD5;
# 指定在使用SSLv3和TLS协议时,服务器密码应优先于客户端密码
ssl_prefer_server_ciphers on;

location / {
root html;
index index.html index.htm;
}
}
}

四、部署静态网站

Nginx 通常用作 HTTP 服务器来部署静态资源,其具体的操作步骤如下:

4.1 增加配置

修改 nginx.conf ,并在 http 指令块中增加如下配置:

1
2
3
4
5
6
7
8
9
10
server {
# 监听端口号
listen 9010;
# 如果有域名的话,可以在这里进行配置
server_name _;
# 存放静态页面的目录
root /usr/web;
# 主页面
index index.html;
}

/usr/web 目录下新建一个测试页面 index.html,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0,
maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Nginx静态资源网站</title>
</head>
<body>
<h1 style="text-align: center">Nginx静态资源网站</h1>
</body>
</html>

4.2 检查配置

使用 -t 参数来检查配置,出现 successful 则代表配置正确:

1
2
3
[root@node1 web]# nginx -t -c conf/nginx.conf
nginx: the configuration file /usr/app/nginx-1.16.1/conf/nginx.conf syntax is ok
nginx: configuration file /usr/app/nginx-1.16.1/conf/nginx.conf test is successful

4.3 重载配置

启动 Nginx ,如果 Nginx 已经启动,可以使用如下命令重载配置:

1
nginx -s reload

访问 http://hostname:9010/index.html ,即可查看到静态网站首页。

五、实现负载均衡

5.1 部署后台服务

这里我使用 Docker 来部署两个 Tomcat,之后将测试项目 WAR 包分别拷贝到 /usr/webapps001/usr/webapps002 两个挂载的容器卷下,程序会自动解压并运行,两个项目的端口号分别为 8080 和 8090:

1
2
run -d  -it --privileged=true -v /usr/webapps01:/usr/local/tomcat/webapps \
-p 8080:8080 --name tomcat8080 96c4e536d0eb
1
2
run -d  -it  --privileged=true -v /usr/webapps02:/usr/local/tomcat/webapps \
-p 8090:8080 --name tomcat8090 96c4e536d0eb

5.2 负载均衡配置

修改 nginx.conf ,并在 http 指令块中增加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
# 这里指令块的名称可以随意指定,只要和下面的proxy_pass的值相同即可,通常配置为项目名
upstream springboot {
server 192.168.0.226:8080;
server 192.168.0.226:8090;
}

server {
listen 9020;
location / {
proxy_pass http://springboot;
}
}

重载配置后,打开浏览器,通过 9020 端口访问项目,此时 Nginx 会以轮询的方式将请求分发到 8080 和 8090 端口上。在测试负载均衡策略的时候,最好将浏览器的缓存功能关闭,避免造成影响。

5.3 负载均衡策略

在上面的配置中,我们没有配置任何负载均衡策略,默认采用的是轮询策略,除此之外,Nginx 还支持以下负载均衡策略:

1. 权重策略 ( Weighted load balancing )

通过为不同的服务分配不同的权重来进行转发,配置示例如下:

1
2
3
4
5
upstream myapp1 {
server srv1.example.com weight=3;
server srv2.example.com weight=2;
server srv3.example.com;
}

2. 最少连接策略 ( Least connected load balancing )

将请求转发给连接数最少的服务,配置实例如下:

1
2
3
4
5
6
upstream myapp1 {
least_conn;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}

3. IP 策略 ( IP Hash load balancing )

通过对请求的 IP 地址进行哈希运算并取模来决定转发对象,配置示例如下:

1
2
3
4
5
6
upstream myapp1 {
ip_hash;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}

以上是 Nginx 内置的基础的负载均衡策略,如果想要实现其他更加复杂的负载均衡策略,可以通过第三方模块来实现。

4.4 声明备用服务

在上面任何负载均衡策略当中,都可以通过 backup 参数来添加备用服务,示例如下:

1
server backup1.example.com:8080   backup;

处于备用状态下的服务并不会参与负载均衡,除非所有主服务都已宕机,此时 Nginx 才会将请求转发到备用服务上。

六、实现动静分离

6.1 动静分离配置

Nginx 能够支持高并发的访问,并具有静态资源缓存等特性,因此相比于 Tomcat 等动态资源应用服务器,其更加适合于部署静态资源。想要实现动静分离,只需要在 server 指令块中通过正则表达式来划分静态资源,并指定其存放位置,示例如下:

1
2
3
4
5
6
7
8
9
10
11
server {
listen 9020;
location / {
proxy_pass http://springboot;
}
# 通过正则来控制所需要分离的静态资源
location ~ .*\.(html|htm|gif|jpg|jpeg|bmp|png|ico|txt|js|css)$ {
# 静态资源存放目录
root /usr/resources/;
}
}

6.2 常见配置异常

1. No such file or directory

第一个常见的问题是找不到静态资源,此时可以查看 logs 目录下的 error.log 日志,通常输出如下:

1
2
3
4
2019/09/01 17:12:43 [error] 12402#0: *163 open() "/usr/resources/spring-boot-tomcat/css/show.css"
failed (2: No such file or directory), client: 192.168.0.106, server: ,
request: "GET /spring-boot-tomcat/css/show.css HTTP/1.1", host: "192.168.0.226:9020",
referrer: "http://192.168.0.226:9020/spring-boot-tomcat/index"

出现这个问题,是因为 Nginx 要求静态资源的请求路径必须和原有请求路径完全相同,这里我的项目在 Tomcat 中解压后的项目名为 pring-boot-tomcat,以 show.css 文件为例,其正确的存储路径应该为:

1
/usr/resources/spring-boot-tomcat/css/show.css

即: 静态资源根目录 + 项目名 + 原有路径,通常我们在创建目录时会忽略掉项目名这一层级,从而导致异常。

2. Permission denied

路径正确后,另外一个常见的问题是权限不足,错误日志如下。此时需要保证配置文件中的 user 用户必须具有静态资源所处目录的访问权限,或者在创建静态资源目录时,直接使用 user 配置的用户来创建:

1
2
3
4
2019/09/01 17:15:14 [error] 12402#0: *170 open() "/usr/resources/spring-boot-tomcat/css/show.css" 
failed (13: Permission denied), client: 192.168.0.106, server: ,
request: "GET /spring-boot-tomcat/css/show.css HTTP/1.1", host: "192.168.0.226:9020",
referrer: "http://192.168.0.226:9020/spring-boot-tomcat/index"

参考资料

官方文档:nginx documentationUsing nginx as HTTP load balancer

docker 笔记

Docker 笔记

引自 heibaiying/Full-Stack-Notes/Docker_基础

Docker 常用命令

基础命令

  • docker version:用于查看 docker 的版本信息
  • docker info:用于查看 docker 的配置信息
  • docker help:用于查看帮助信息

镜像相关命令

1. docker search 镜像名

从官方镜像仓库 Docker Hub 查找指定名称的镜像。常用参数为 --no-trunc,代表显示完整的镜像信息。

2. docker images

列出所有顶层镜像的相关信息。常用参数如下:

  • -a :显示所有镜像,包括中间隐藏的镜像
  • -q :只显示镜像 ID
  • –digests :显示摘要信息
  • –no-trunc :显示完整镜像信息

3. docker pull 镜像名 [:TAG]

从官方仓库下载镜像,:TAG 为镜像版本,不加则默认下载最新版本。

4. docker rmi 镜像名或ID [:TAG]

删除指定版本的镜像,不加 :TAG 则默认删除镜像的最新版本。如果有基于该镜像的容器存在,则该镜像无法直接删除,此时可以使用参数 -f,代表强制删除。rmi 命令支持批量删除,多个镜像名之间使用空格分隔。如果想要删除所有镜像,则可以使用命令 docker rmi -f $(docker images -qa)

容器相关命令

1. docker run [OPTIONS] IMAGE [COMMAND] [ARG…]

run 是 docker 中最为核心的一个命令,用于新建并启动容器,其拥有众多可用参数,可以使用 docker run --help 查看所有可用参数。常用参数如下:

  • -i :表示使用交互模式,始终保持输入流开放;
  • -t :表示分配一个伪终端,通常和 -i 结合使用,表示使用伪终端与容器进行交互;
  • -d :以后台方式运行容器;
  • –name :指定启动容器的名字,如果不指定,则由 docker 随机分配;
  • -c :用于给运行在容器中的所有进程分配 CPU 的 shares 值,这是一个相对权重,实际的处理速度与宿主机的 CPU 相关;
  • -m :用于限制为容器中所有进程分配的内存总量,以 B、K、M、G 为单位;
  • -v :挂载数据卷 volume,可以用多个 -v 参数同时挂载多个 volume。volume 的格式为:[host-dir]:[container-dir]:[rw:ro][rw:ro] 用于指定数据卷的模式,rw 代表读写模式,ro 代表只读模式。
  • -p :用于将容器的端口暴露给宿主机的端口,格式为:hostPort:containerPort ,通过端口的暴露,可以让外部主机能够访问容器内的应用。

2. docker ps [OPTIONS]

列出当前所有正在运行的容器。常用参数如下:

  • -a :列出所有容器,包括运行的和已经停止的所有容器
  • -n :显示最近创建的 n 个容器
  • -q :只显示容器编号
  • –no-trunc :不要截断输出信息

3. 启动\重启\停止\强制停止容器

与容器启动、停止相关的命令为:docker start|restart|stop|kill 容器名或ID ,start 命令用于启动已有的容器,restart 用于重启正在运行的容器,stop 用于停止正在运行的容器,kill 用于强制停止容器。

4. 进入正在运行的容器

想要进入正在运行的容器,与容器主进程交互,有以下两种常用方法:

  • docker attach 容器名或ID
  • docker exec -it 容器名或ID /bin/bash

5. 退出容器

想要退出正在运行的容器,有以下两种常用方法:

  • exit :退出并停止容器;
  • ctrl+P+Q :退出。

6. docker rm 容器名或ID

删除已停止的容器,常用参数为-f,表示强制删除容器,即便容器还在运行。想要删除所有容器,可以使用 docker rm -f $(docker ps -aq) 命令。

7. 查看容器信息

可以使用 docker inspect [OPTIONS] NAME|ID [NAME|ID...] 查看容器或者镜像的详细信息,想要查看指定的信息,可以使用 -- format 参数来指定输出的模板格式,示例如下:

1
docker inspect --format='{{.NetworkSettings}}'  32cb3ace3279

8. 查看容器运行日志

可以使用 docker logs [OPTIONS] CONTAINER 查看容器中进程的运行日志,常用参数如下:

  • –details :显示日志详情
  • -f :跟随日志输出显示
  • –tail :从末尾开始显示指定行的数据
  • -t :显示时间戳
  • –since :开始时间
  • –until : 结束时间

DockerFile

dockerfile 是 Docker 用来构建镜像的文本文件,包含自定义的指令和格式,可以通过 build 命令从 dockerfile 中构建镜像,命令格式为:docker build [OPTIONS] PATH | URL | -

dockerfile 描述了组装镜像的步骤,其中每条指令都是单独执行的。除了 FROM 指令,其他的每一条指令都会在上一条指令所生成镜像的基础上执行,执行完后会生成一个新的镜像层,新镜像层覆盖在原来的镜像之上从而形成新的镜像。为了提高镜像构建的速度, Docker Daemon 会缓存构建过程中的中间镜像。在构建镜像时,它会将 dockerfile 中下一条指令和基础镜像的所有子镜像做比较,如果有一个子镜像是由相同的指令生成的,则命中缓存,直接使用该镜像,而不用再生成一个新的镜像。常用指令如下:

1. FROM

FROM 指令用于指定基础镜像,因此所有的 dockerfile 都必须使用 FROM 指令开头。FROM 指令可以出现多次,这样会构建多个镜像,每个镜像创建完成后,Docker 命令行界面会输出该镜像的 ID。常用指令格式为:FROM <image>[:<tag>] [AS <name>]

2. MAINTAINER

MAINTAINER 指令可以用来设置作者名称和邮箱,目前 MAINTAINER 指令被标识为废弃,官方推荐使用 LABEL 代替。

3. LABEL

LABEL 指令可以用于指定镜像相关的元数据信息。格式为:LABEL <key>=<value> <key>=<value> <key>=<value> ...

4. ENV

ENV 指令用于声明环境变量,声明好的环境变量可以在后面的指令中引用,引用格式为 $variable_name${variable_name} 。常用格式有以下两种:

  • ENV <key> <value> :用于设置单个环境变量;
  • ENV <key>=<value> ... :用于一次设置多个环境变量。

5. EXPOSE

EXPOSE 用于指明容器对外暴露的端口号,格式为:EXPOSE <port> [<port>/<protocol>...] ,您可以指定端口是侦听 TCP 还是 UDP,如果未指定协议,则默认为 TCP。

6. WORKDIR

WORKDIR 用于指明工作目录,它可以多次使用。如果指明的是相对路径,则它将相对于上一个WORKDIR指令的路径。示例如下:

1
2
3
4
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd # 此时pwd为:/a/b/c

7. COPY

COPY 指令的常用格式为:COPY <src>... <dest>,用于将指定路径中的文件添加到新的镜像中,拷贝的目标路径可以不存在,程序会自动创建。

8. ADD

ADD 指令的常用格式为:COPY <src>... <dest>,作用与 COPY 指令类似,但功能更为强大,例如 Src 支持文件的网络地址,且如果 Src 指向的是压缩文件,ADD 在复制完成后还会自动进行解压。

9. RUN

RUN 指令会在前一条命令创建出的镜像基础上再创建一个容器,并在容器中运行命令,在命令结束后提交该容器为新的镜像。它支持以下两种格式:

  • RUN <command>shell 格式)
  • RUN ["executable", "param1", "param2"] (exec 格式)

使用 shell 格式时候,命令通过 /bin/sh -c 运行,而当使用 exec 格式时,命令是直接运行的,容器不调用 shell 程序,这意味着不会发生正常的 shell 处理。例如,RUN ["echo","$HOME"] 不会对 $HOME 执行变量替换,此时正确的格式应为:RUN ["sh","-c","echo $HOME"]。下面的 CMD 指令也存在同样的问题。

10. CMD

  • CMD ["executable","param1","param2"] (exec 格式, 首选)
  • CMD ["param1","param2"] (作为 ENTRYPOINT 的默认参数)
  • CMD command param1 param2 (shell 格式)

CMD 指令提供容器运行时的默认值,这些默认值可以是一条指令,也可以是一些参数。一个 dockerfile 中可以有多条 CMD 指令,但只有最后一条 CMD 指令有效。CMD 指令与 RUN 指令的命令格式相同,但作用不同:RUN 指令是在镜像的构建阶段用于产生新的镜像;而 CMD 指令则是在容器的启动阶段默认将 CMD 指令作为第一条执行的命令,如果用户在 docker run 时指定了新的命令参数,则会覆盖 CMD 指令中的命令。

11. ENTRYPOINT

ENTRYPOINT 指令 支持以下两种格式:

  • ENTRYPOINT ["executable", "param1", "param2"] (exec 格式,首先)
  • ENTRYPOINT command param1 param2 (shell 格式)

ENTRYPOINT 指令 和 CMD 指令类似,都可以让容器在每次启动时执行相同的命令。但不同的是 CMD 后面可以是参数也可以是命令,而 ENTRYPOINT 只能是命令;另外 docker run 命令提供的运行参数可以覆盖 CMD,但不能覆盖 ENTRYPOINT ,这意味着 ENTRYPOINT 指令上的命令一定会被执行。如下 dockerfile 片段:

1
2
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]

当执行 docker run -it image 后,此时输出为 hello world,而当你执行 docker run -it image spring ,此时 CMD 中的参数会被覆盖,此时输出为hello spring

案例

部署GO项目

这里举例将GO源码,打入docker 在docker内进行编译的过程,如果在之前就已经编译,则可以选择更小的镜像,直接将编译好的文件复制到镜像即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 以官方仓库的golang镜像为基础开始创建
FROM golang
# 作者信息
MAINTAINER felix

#工作目录
WORKDIR $GOPATH/src/godocker

#将文件复制到镜像中
ADD . $GOPATH/src/godocker

# 执行操作(就跟在终端执行语句一样)
RUN go build main.go

EXPOSE 8080

ENTRYPOINT ["./main"]

执行下面镜像构建命令:

1
2
3
docker build -t go-docker:latest .

docker images 查看创建的镜像

镜像构建完成后,可以使用以下命令进行启动:

1
docker run -p 8080:8080 -d go-docker

使用es 来存放 jaeger

OpenTracing 基础知识准备

The OpenTracing 数据模型

首先需要阐明的是 Span 和 trace 的概念。 用图论的观点来看的话,traces 可以被认为是 spans 的 DAG。也就是说,对N个 spans 形成的 DAG 是一个 Traces。

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
单个 Trace 中 Span 之间的因果关系


[Span A] ←←←(the root span)
|
+------+------+
| |
[Span B] [Span C] ←←←(Span C is a `ChildOf` Span A)
| |
[Span D] +---+-------+
| |
[Span E] [Span F] >>> [Span G] >>> [Span H]



(Span G `FollowsFrom` Span F)

---------------------------------------------------------------------------

单个 Trace 中 Spans 之间的时间关系

––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time

[Span A···················································]
[Span B··············································]
[Span D··········································]
[Span C········································]
[Span E·······] [Span F··] [Span G··] [Span H··]
每个 Span 包含一些状态:
  • Operation 的 名字(An operation name)

  • 开始 ts (A start timestamp)

  • 结束 ts (A finish timestamp)

  • 0个或多个以 keys:values 为形式组成的 Span Tags。 key 必须是 string, values 则可以是 strings, bool,numeric types

  • 0个或多个 Span logs

一个 SpanContext
  • 通过 SpanContext 可以指向 0个 或者多个 因果相关的 Span。

  • 每个 SpanContext 包含以下状态:

  • 任何 OpenTraceing 实现相关的状态(比如 trace 和 span id)都需要被一个跨进程的 Span 所联系。

​- Baggage Items: 跨进程的 key value 对。

DAG

RDD的依赖关系分为两种:窄依赖(Narrow Dependencies)与宽依赖(Wide Dependencies,源码中称为Shuffle Dependencies)

- 窄依赖

    每个父RDD的一个Partition最多被子RDD的一个Partition所使用(1:1 或 n:1)。例如map、filter、union等操作都会产生窄依赖

    子RDD分区通常对应常数个父RDD分区(O(1),与数据规模无关)。

- 宽依赖

    一个父RDD的Partition会被多个子RDD的Partition所使用,例如groupByKey、reduceByKey、sortByKey等操作都会产生宽依赖;(1:m 或 n:m)

    子RDD分区通常对应所有的父RDD分区(O(n),与数据规模有关)

在Spark里每一个操作生成一个RDD,RDD之间连一条边,最后这些RDD和他们之间的边组成一个有向无环图,这个就是DAG。

Spark会根据宽依赖窄依赖来划分具体的Stage,而依赖有2个作用:

- 用来解决数据容错的高效性

- 其二用来划分stage。

OpenTracing API

在 OpenTracing 有着三个关键的并且相互关联的类型: Tracer, Span, SpanContext。下面,我们来介绍下每种类型的基本行为。 简单地说,每种行为都会在具体的语言中变为一个“方法”,

Tracer

Tracer接口用来创建Span,以及处理如何处理Inject(serialize) 和 Extract (deserialize),用于跨进程边界传递。

- 创建一个新的span

- 将SpanContext上下文Inject(注入)到carrier

- 将SpanContext上下文从carrier中Extract(提取)

    注意,对于Inject(注入)和Extract(提取),format是必须的。

    Inject(注入)和Extract(提取)依赖于可扩展的format参数。format参数规定了另一个参数"carrier"的类型,同时约束了"carrier"中SpanContext是如何编码的。

Span

#####设置(set) baggage item

Baggage item是对应于某个 Span 及其 SpanContext ,以及所有直接或间接引用自本地(local) Span 的 Span 的键值对。也就是说,baggage items是与其trace一起传播的。

Baggage item为全栈式的OpenTracing集成提供了强大的功能(比如在移动App上使用时,它可以一路追踪数据直至储存系统的深度),不过使用这些功能时要当心。

每个键值都会被拷贝到每一个本地(local)及远程的子Span,这可能导致巨大的网络和CPU开销。

SpanContext

相对于OpenTracing中其他的功能,SpanContext更多的是一个“概念”。也就是说,OpenTracing实现中,需要重点考虑,并提供一套自己的API。

OpenTracing的使用者仅仅需要,在创建span、向传输协议Inject(注入)和从传输协议中Extract(提取)时,使用SpanContext和references,

OpenTracing要求,SpanContext是不可变的,目的是防止由于Span的结束和相互关系,造成的复杂生命周期问题。

#不使用Cassandra来存放 jaeger 主要是因为我们已经部署了ELK 所以尝试使用elasticsearch来存放

#####Jaeger 组件

Agent

Agent是一个网络守护进程,监听通过UDP发送过来的Span,它会将其批量发送给collector。按照设计,Agent要被部署到所有主机上,作为基础设施。Agent将collector和客户端之间的路由与发现机制抽象了出来。

Collector

Collector从Jaeger Agent接收Trace,并通过一个处理管道对其进行处理。目前的管道会校验Trace、建立索引、执行转换并最终进行存储。存储是一个可插入的组件,现在支持Cassandra和elasticsearch。

Query

Query服务会从存储中检索Trace并通过UI界面进行展现,该UI界面通过React技术实现,其页面UI如下图所示,展现了一条Trace的详细信息。

存储

jaeger采集到的数据必须存储到某个存储引擎,目前支持Cassandra和elasticsearch

#####部署步骤

会通过docker-compose 统一部署 所以先列出完整yaml文件 再一一说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
version: '2.1'
services:

elasticsearch:
image: elasticsearch:5.6.4
environment:
- "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"

collector:
image: jaegertracing/jaeger-collector
environment:
- SPAN_STORAGE_TYPE=elasticsearch
- ES_SERVER_URLS=http://elasticsearch:9200
- ES_USERNAME=elastic
- LOG_LEVEL=debug
depends_on:
- elasticsearch

agent:
image: jaegertracing/jaeger-agent
environment:
- COLLECTOR_HOST_PORT=collector:14267
- LOG_LEVEL=debug
ports:
- "5775:5775/udp"
- "5778:5778"
- "6831:6831/udp"
- "6832:6832/udp"
depends_on:
- collector
query:
image: jaegertracing/jaeger-query
environment:
- SPAN_STORAGE_TYPE=elasticsearch
- ES_SERVER_URLS=http://elasticsearch:9200
- ES_USERNAME=elastic
- LOG_LEVEL=debug
ports:
- 16686:16686
depends_on:
- elasticsearch

hotrod:
image: jaegertracing/example-hotrod:1.6
command: all --jaeger-agent.host-port=agent:6831
ports:
- 8080:8080
depends_on:
- agent

Docker 安装ES
Docker 安装collector
如ES部署再同一个机器上 可以使用--link关联

多服务器则可以用过 ES_SERVER_URLS = http://你的es ip:9200 来关联
Docker 安装query
如同conllector

部署完成query之后,根据你暴露的端口号(-p 16686:16686/tcp),浏览器输入以下地址(将localhost换成你部署query的地址):

注意,ES_USERNAME、ES_PASSWORD这两个环境变量,当你的elasticsearch未设置账号密码时,你可以不填,也可以填上默认值,elasticsearch的默认ES_USERNAME=elastic,ES_PASSWORD=changeme
Docker 安装agent
根据uber jaeger官网的架构,agent一般是和jaeger-client部署在一起,agent作为一个基础架构,每一台应用(接入jaeger-client的应用)所在的机器都需要部署一个agent;

根据数据采集原理,jaeger-client采集到数据之后,是通过UDP端口发送到agent的,jaeger-client和agent部署在一起的好处是UDP传输数据都在应用所在的机器,可避免UDP的跨网络传输,多一层安全保障。

当然,架构可能是多变的,你的agent可能不和jaeger-client所在的应用在一台机器,这个时候,jaeger-client就必须显示的指定其连接的agent的IP及port,具体做法后文jaeger-client对应模块会讲到。

前文提到,jaeger-client采集到数据之后,是通过UDP端口发送到agent的,agent接收到数据之后,使用Uber的Tchannel协议,将数据发送到collector,所以,agent是必须和collector相连的;

--collector.host-port=collector ip1:14267,collector ip2:14267,collector ip3:14267,用逗号分开,连接三个collector,这样的话,这三个collector只要一个存活,agent就可以吧数据传输完成,以避免单点故障
hotrod
用以测试

Git fork、pr的开发模式

整理了 “GitHub 从单机到联机:玩转 Pull Request” 方便自己看

第一步:Fork原仓库

第二步:将 Fork 来的仓库 clone 到本地

1
git clone https://github.com/AA/BBA.git

第三步:创建新分支

可以但不推荐直接在master上修改。

1
git checkout -b impl-rational-numbers

第四步:正常提交代码

1
2
3
4
5
6
git status
git add .
git commit -am "balabalbabla"

提交代码到自己的远程仓库
git push -u origin impl-rational-numbers

第五步:Pull request

打开 自己项目 的页面会出现一个提示提示。

avatar

然后点击按钮 Compare & pull request,开始编辑我们的 PR:

GitHub 关键词:当使用 fix(es),close(s) 或 resolve(s) 时,如果这个 Pull Request 被合并,会自动关闭对应的 Issue。

这里我标出了 Closes #1300,那么当我们贡献的代码被接受时,就会关闭 rational-numbers: implement exercise 这个 Issue。

正确地使用 GitHub 关键词能够极大地方便仓库维护者,他们就不需要去查找对应的是哪个 Issue 并去手动关闭它了。

avatar

点击 Create pull request,进行提交!

第六步:等待作者反馈

作者提出修改建议 -> 修改重新提交 -> 作者合并分支

作者合并分支的方式为:将作者自己的master 合并到 我们所提交的分支

因为当我们在这个分支上工作时,AA/BB 的 master 分支上可能提交了新的修改,导致我们的分支并不是最新的。

通过将 master 分支并入我们的分支,我们的分支就能进入即将部署(Ready to Deploy)状态了。

然后等待CI检测、如果CI检测不通过,则需要根据提示修改代码 重新提交

善后

由于 impl-rational-numbers 已经合并,可以安全删除,所以我们点击 Delete branch 按钮,删除我们远程仓库 AA/BBA 中的分支。
然后在本地输入下面的命令,删除本地分支:

1
2
$ git checkout master
$ git branch -D impl-rational-numbers

分支操作

首先,我们创建dev分支,然后切换到dev分支:

1
2
$ git checkout -b dev
Switched to a new branch 'dev'

git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:

1
2
3
$ git branch dev
$ git checkout dev
Switched to branch 'dev'

然后,用git branch命令查看当前分支:

1
2
3
$ git branch
* dev
master

git branch命令会列出所有分支,当前分支前面会标一个*号。

然后,我们就可以在dev分支上正常提交,比如对readme.txt做个修改,加上一行,然后提交:

现在,dev分支的工作完成,我们就可以切换回master分支:

1
2
$ git checkout master
Switched to branch 'master'

现在,我们把dev分支的工作成果合并到master分支上:

1
2
3
4
5
$ git merge dev
Updating d46f35e..b17d20e
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)

git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。

注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。

当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。

合并完成后,就可以放心地删除dev分支了:

1
2
$ git branch -d dev
Deleted branch dev (was b17d20e).

查看分支:git branch

创建分支:git branch <name>

切换分支:git checkout <name>

创建+切换分支:git checkout -b <name>

合并某分支到当前分支:git merge <name>

删除分支:git branch -d <name>

###其他技巧

git remote add upstream git@code.izhaohu.com:dolphin/proto.git

upstream 已经在 remote 列表中!然后我们就可以轻松地进行同步了。
先确保当前处在 master 分支上,然后获取 upstream 的修改,再并入我们本地的 master 分支。

1
2
3
$ git checkout master
$ git fetch upstream
$ git merge upstream/master

再把本地的更新 push 到 origin,也就是我们的 GitHub 仓库:

1
$ git push

GO json的操作

##encoding/json标准库

Marshal 和 Unmarshal

编码:
func Marshal(v interface{}) ([]byte, error)
func NewEncoder(w io.Writer) *Encoder
[func (enc *Encoder) Encode(v interface{}) error
解码:
func Unmarshal(data []byte, v interface{}) error
func NewDecoder(r io.Reader) *Decoder
func (dec *Decoder) Decode(v interface{}) error

json类型仅支持string作为关键字,因而转义map时,map[int]T类型会报错(T为任意类型)
Channel, complex, and function types不能被转义
不支持循环类型的数据,因为这会导致Marshal死循环
指针会被转义为其所指向的值

MarshalIndent 函数(功能和 Marshal一致,只是格式化 json)

Encoder 和 Decoder

Request 之类的输入流中直接读取 json 进行解析或将编码的 json 直接输出,为了方便,标准库为我们提供了 Decoder 和 Encoder 类型。
它们分别通过一个 io.Reader 和 io.Writer 实例化,并从中读取数据或写数据。

1
2
3
输出JSON
jsonStr := []byte(`{"Name":"felix","Age":33}
json.NewEncoder(w).Encode(jsonStr);

坑爹的端口集

昨天被6000端口折腾了一天

查了端口占用情况没问题

通过后端程序调用没问题

但是用grpc-web调用端口就一直报错

一直以为是Enovy 和 docker配置有问题

尝试改成restful API还是不行,但是发现浏览器报的错是安全问题

原来6000 被视为非安全端口直接被拦截了。端口随手改成12000,通了

记录一下引以为戒

Firfox / Chrome 默认非安全端口列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
1,    // tcpmux
7, // echo
9, // discard
11, // systat
13, // daytime
15, // netstat
17, // qotd
19, // chargen
20, // ftp data
21, // ftp access
22, // ssh
23, // telnet
25, // smtp
37, // time
42, // name
43, // nicname
53, // domain
77, // priv-rjs
79, // finger
87, // ttylink
95, // supdup
101, // hostriame
102, // iso-tsap
103, // gppitnp
104, // acr-nema
109, // pop2
110, // pop3
111, // sunrpc
113, // auth
115, // sftp
117, // uucp-path
119, // nntp
123, // NTP
135, // loc-srv /epmap
139, // netbios
143, // imap2
179, // BGP
389, // ldap
465, // smtp+ssl
512, // print / exec
513, // login
514, // shell
515, // printer
526, // tempo
530, // courier
531, // chat
532, // netnews
540, // uucp
556, // remotefs
563, // nntp+ssl
587, // stmp?
601, // ??
636, // ldap+ssl
993, // ldap+ssl
995, // pop3+ssl
2049, // nfs
3659, // apple-sasl / PasswordServer
4045, // lockd
6000, // X11
6665, // Alternate IRC [Apple addition]
6666, // Alternate IRC [Apple addition]
6667, // Standard IRC [Apple addition]
6668, // Alternate IRC [Apple addition]
6669, // Alternate IRC [Apple addition]

NSQ(有赞版)

NSQ参数说明 请参阅 NSQ参数

####核心概念

  • Topic ——一个topic就是程序发布消息的一个逻辑键,当程序第一次发布消息时就会创建topic。

  • Channels ——channel组与消费者相关,是消费者之间的负载均衡,channel在某种意义上来说是一个“队列”。每当一个发布者发送一条消息到一个topic,消息会被复制到所有消费者连接的channel上,消费者通过这个特殊的channel读取消息,实际上,在消费者第一次订阅时就会创建channel。

  • Channel会将消息进行排列,如果没有消费者读取消息,消息首先会在内存中排队,当量太大时就会被保存到磁盘中。

  • Message s——消息构成了我们数据流的中坚力量,消费者可以选择结束消息,表明它们正在被正常处理,或者重新将他们排队待到后面再进行处理。每个消息包含传递尝试的次数,当消息传递超过一定的阀值次数时,我们应该放弃这些消息,或者作为额外消息进行处理。

  • NSQ在操作期间同样运行着两个程序:

    • Nsqd ——nsqd守护进程是NSQ的核心部分,它是一个单独的监听某个端口进来的消息的二进制程序。每个nsqd节点都独立运行,不共享任何状态。当一个节点启动时,它向一组nsqlookupd节点进行注册操作,并将保存在此节点上的topic和channel进行广播。

      客户端可以发布消息到nsqd守护进程上,或者从nsqd守护进程上读取消息。通常,消息发布者会向一个单一的local nsqd发布消息,消费者从连接了的一组nsqd节点的topic上远程读取消息。如果你不关心动态添加节点功能,你可以直接运行standalone模式。

    • Nsqlookupd ——nsqlookupd服务器像consul或etcd那样工作,只是它被设计得没有协调和强一致性能力。每个nsqlookupd都作为nsqd节点注册信息的短暂数据存储区。消费者连接这些节点去检测需要从哪个nsqd节点上读取消息。

    • NSQ 重要名词
      1、In-Flight
      客户端读到消息,正在处理的部分(可以理解成没有 NSQ 的 ACK)

      2、Finish
      正常流程下,处理完的逻辑,需要手动标记已经处理完了 msg.Finish()
      不标记的消息,过了一段时间后,会有重排机制,这种重排会导致整个消息的消费变慢。

      3、Requeue
      人工标记重排,该类重排属于正常业务逻辑范畴,不会拖慢消费速度。

####NSQ 组件

  • nsqd 是一个守护进程,负责接收,排队,投递消息给客户端。

  • nsqlookupd 是守护进程负责管理拓扑信息。

    客户端通过查询 nsqlookupd 来发现指定话题(topic)的生产者,并且 nsqd 节点广播话题(topic)和通道(channel)信息。

    有两个接口:TCP 接口,nsqd 用它来广播。HTTP 接口,客户端用它来发现和管理。

  • nsqadmin 是一套 WEB UI,用来汇集集群的实时统计,并执行不同的管理任务。

Linux 下安装

  • 安装使用 nsq环境
    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
    1、获取docker 镜像
    docker pull nsqio/nsq

    2、运行 nsqlookupd
    docker run -d --name nsqlookupd -p 4160:4160 -p 4161:4161 nsqio/nsq /nsqlookupd

    - -http-address="0.0.0.0:4161": <addr>:<port> 监听 HTTP 客户端

    - -tcp-address="0.0.0.0:4160": TCP 客户端监听的 <addr>:<port>

    3 docker 运行 nsqd
    docker run --name nsqd -p 4150:4150 -p 4151:4151 nsqio/nsq /nsqd --broadcast-address=127.0.0.1 --lookupd-tcp-address=127.0.0.1:4160

    - -broadcast-address="": 通过 lookupd 注册的地址(默认名是 OS)

    - -lookupd-tcp-address=: 解析 TCP 地址名字 (可能会给多次)

    4 docker 运行 nsqadmin
    docker run --name nsqadmin -p 4171:4171 nsqio/nsq /nsqadmin --lookupd-http-address=127.0.0.1:4161

    - -graphite-url="": URL to graphite HTTP 地址

    - -http-address="0.0.0.0:4171": <addr>:<port> HTTP clients 监听的地址和端口

    - -lookupd-http-address=[]: lookupd HTTP 地址 (可能会提供多次)

    - -notification-http-endpoint="": HTTP 端点 (完全限定) ,管理动作将会发送到

    - -nsqd-http-address=[]: nsqd HTTP 地址 (可能会提供多次)

一些认识

  • 发布者:消息发布,只能面向具体的nsqd服务进行

    • 一个topic的发布者只对应一个具体的NSQD,但可以多个发布者同时向一个NSQD发送消息,他们是N:1的关系。
      NSQD与topic是1:N的关系。
  • 消费者:

    • consumer要接收消息,是要连接到具体的nsqd服务的。通常我们能通过封装好的方法,基于lookupd服务来获取所有的nsqd服务地址并连接。

    • 一个消费者订阅的topic分布在哪些nsqd服务中,则会直接连接。nsqd之间是绝对不会互传topic的具体数据的。下图描绘了consumer与nsqd的关系:

    • 当多个nsqd服务都有相同的topic的时候,consumer要修改默认设置config.MaxInFlight才能连接。

    • consumer与topic没有直接联系,而是通过具体的channel接受数据。如果consumer退出,channel不会自动删除。 如果不再需要,需要通过http端口删除channel,否则很可能会导致磁盘空间不足。

TEST

1
2

curl -d 'Hello Linux localhost Msg' 'http://127.0.0.1:4151/pub?topic=test'

Envoy - 初探

##基础知识

网络代理/网络服务器

Envoy基本概念:集群,监听器,路由和过滤器

首先,有一个监听器。这告诉Envoy绑定到一个端口,在本例中为10000:

接下来,监听器有一个过滤器。过滤器告诉监听器如何处理它收到的请求,并为Envoy提供一系列过滤器。

如果您需要做一些复杂的事情,那么要对每个请求使用几个过滤器。

envoy.http_connection_manager 过滤器,它用于代理HTTP请求。

HTTP连接管理器还有一个适用的HTTP过滤器列表(请参阅HTTP过滤器列表)。

其中最重要的是将envoy.router 的请求路由到后端的过滤器。

TCP过滤器列表: https://www.envoyproxy.io/docs/envoy/v1.8.0/api-v2/api/v2/listener/listener.proto#listener-filter

HTTP过滤器列表: https://www.envoyproxy.io/docs/envoy/v1.8.0/api-v2/config/filter/network/http_connection_manager/v2/http_connection_manager.proto#envoy-api-msg-config-filter-network-http-connection-manager-v2-httpfilter


static_resources:
listeners:

  • name: listener_0
    address:
    socket_address: { address: 0.0.0.0, port_value: 10000 }
    filter_chains:
    • filters:
      • name: envoy.http_connection_manager
        config:
        access_log:
        • name: envoy.file_access_log
          config:
          path: “/var/log/envoy.log”
          codec_type: auto
          stat_prefix: ingress_http
          route_config:
          name: local_route
          virtual_hosts:
          • name: local_service
            domains: [“*”]
            routes:
            • match: { prefix: “/datacenter.AdverseEvent” }
              route:
              cluster: dc_ae
              max_grpc_timeout: 0s
            • match: { prefix: “/izhaohu-adverse/api/adverse” }
              route:
              cluster: dc_ae
              max_grpc_timeout: 0s
              cors:
              allow_origin:
              • “*”
                allow_methods: GET, PUT, DELETE, POST, OPTIONS
                allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
                max_age: “1728000”
                expose_headers: custom-header-1,grpc-status,grpc-message
                enabled: true
                http_filters:
                //一个翻译器 他帮助grpc 与 json互通。
        • name: envoy.grpc_json_transcoder
          config:
          //翻译器的字典,需要通过proto生成
          proto_descriptor: “/data/descriptor.pb”
          services: [“datacenter.AdverseEvent”]
          print_options:
          add_whitespace: false
          always_print_primitive_fields: true
          always_print_enums_as_ints: false
          preserve_proto_field_names: true
          
        • name: envoy.grpc_web
        • name: envoy.cors
        • name: envoy.router
          //服务模块
          clusters:
  • name: dc_ae
    connect_timeout: 1.25s
    type: logical_dns
    http2_protocol_options: {}
    lb_policy: round_robin
    hosts: [{ socket_address: { address: dc-ae, port_value: 60001 }}]

1
2
3
4
5
6
7
8
9
生成descriptor.pb
protoc -I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
-I$GOPATH/src/protobuf/src/ \
-I$GOPATH/src/datacenter/proto \
--include_imports \
--include_source_info \
--descriptor_set_out=../build/envoy/descriptor.pb \
*.proto

请参阅 案例分享

Elasticsearch (一)

概述

Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。

但是Lucene使用部署都非常复杂,而ES则可以通过 RESTful API 来使用 Lucene。

主要认识

  • 分布式的实时文件存储,每个字段都被索引并可被搜索

  • 分布式的实时分析搜索引擎

  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据

  • 面向文档

    存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在Elasticsearch中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。这种理解数据的方式与以往完全不同,这也是Elasticsearch能够执行复杂的全文搜索的原因之一。

  • 使用JSON 对文档进行序列化

索引

在Elasticsearch中存储数据的行为就叫做索引(indexing)

在Elasticsearch中,文档归属于一种类型(type),而这些类型存在于索引(index)中。

对比图来类比传统关系型数据库:

1
2
3
Relational DB -> Databases -> Tables -> Rows -> Columns

Elasticsearch -> Indices -> Types -> Documents -> Fields

Elasticsearch集群可以包含多个索引(indices)(数据库),每一个索引可以包含多个类型(types)(表),每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。

索引含义的区分

  • 索引(名词)一个索引(index)就像是传统关系数据库中的数据库,它是相关文档存储的地方,index的复数是indices 或indexes。

  • 索引(动词)「索引一个文档」表示把一个文档存储到索引(名词)里,以便它可以被检索或者查询。这很像SQL中的INSERT关键字,差别是,如果文档已经存在,新的文档将覆盖旧的文档。

  • 倒排索引 传统数据库为特定列增加一个索引,例如B-Tree索引来加速检索。Elasticsearch和Lucene使用一种叫做倒排索引(inverted index)的数据结构来达到相同目的。

建立索引(例子)

  • template
    默认的 order 值就是 0。order 值越大,在 merge 规则的时候优先级越高。
    模板 es 模板有个坑,如果若干个匹配模板的order一致,如order都等于0,则这几个模板的优先级顺序是随机的。

  • mappings 映射的定制
    ES 可以随时根据数据中的新字段来创建新的映射关系。我们也可以在还没有正式数据写入之前,先创建一个基础的映射。等后续数据有其他字段时,ES 也一样会自动处理。

特殊字符
ES有一些默认的特殊字段,这些字段统一以_下划线开头。如_index,_type,_id。默认不开启的还有 _ttl,_timestamp,_size,_parent 等;
  • _all
    _all 里存储了各字段的数据内容。其作用是,在检索的时候,如果无法或者未指明具体搜索哪个字段的数据,那么 ES 默认就会是从 _all 里去查找。

    对于日志场景,如果你的日志划分出来的字段比较少且数目固定。那么,完全可以关闭掉 _all 功能,节省这部分 IO 和 CPU。

    1
    2
    3
    "_all" : {
    "enabled" : false
    }
  • _source

    _source 里存储了该条记录的 JSON 源数据内容。这部分内容只是按照 ES 接收到的内容原样存储下来,并不经过索引过程。对于 ES 的请求过程来说,它不参与 Query 阶段,而只用于 Fetch 阶段。我们在 GET 或者 /_search 时看到的数据内容,都是从 _source 里获取到的。

    所以,虽然 _source 也重复了一遍索引中的数据,一般我们并不建议关闭这个功能。因为一旦关闭,你搜索的结果除了一个 _id,啥都看不到。对于日志场景,意义不是很大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{
"order": 0, //
"index_patterns": [
"es_test_index_2"
],
"settings": {
"index": {
"max_result_window": "30000",
"refresh_interval": "60s",
"analysis": {
"analyzer": {
"custom_standard": {
"type": "custom",
"tokenizer": "standard",
"char_filter": [
"my_char_filter"
],
"filter": "lowercase"
}
},
"char_filter": {
"my_char_filter": {
"type": "mapping",
"mappings": [
"· => xxDOT1xx",
"+ => xxPLUSxx",
"- => xxMINUSxx",
"\" => xxQUOTATIONxx",
"( => xxLEFTBRACKET1xx",
") => xxRIGHTBRACKET1xx",
"& => xxANDxx",
"| => xxVERTICALxx",
"—=> xxUNDERLINExx",
"/=> xxSLASHxx",
"!=> xxEXCLAxx",
"•=> xxDOT2xx",
"【=>xxLEFTBRACKET2xx",
"】 => xxRIGHTBRACKET2xx",
"`=>xxapostrophexx",
".=>xxDOT3xx",
"#=>xxhashtagxx",
",=>xxcommaxx"
]
}
}
},
"number_of_shards": "3",
"number_of_replicas": "1"
}
},
//索引映射
"mappings": {
"logs": {
"_all": {
"enabled": false
},
"properties": {
"search_text": {
"analyzer": "custom_standard",
"type": "text"
},
"search_word": {
"type": "keyword"
}
}
}
}
}