李磊的笔记本

纸上得来终觉浅,绝知此事要躬行。

0%

简介

原文介绍:

1
2
3
4
An unbounded blocking queue of Delayed elements, in which an element can only be taken when its delay has expired. The head of the queue is that Delayed element whose delay expired furthest in the past. If no delay has expired there is no head and poll will return null. Expiration occurs when an element's getDelay(TimeUnit.NANOSECONDS) method returns a value less than or equal to zero. Even though unexpired elements cannot be removed using take or poll, they are otherwise treated as normal elements. For example, the size method returns the count of both expired and unexpired elements. This queue does not permit null elements.
This class and its iterator implement all of the optional methods of the Collection and Iterator interfaces. The Iterator provided in method iterator() is not guaranteed to traverse the elements of the DelayQueue in any particular order.
This class is a member of the Java Collections Framework.

简单翻译

DelayQueue是一个无界阻塞队列,只有在延迟期满时,才能从中提取元素。
队列的头部,是延迟期满后保存时间最长的delay元素。

阅读全文 »

起因

最近重构单据中心的数据层,以满足公司业务发展需求。目前用的是MySQL5.4的版本,
公司的单据中心设计方案为:大JSON存储单据内容,再辅助各个子单据小表,达到搜索目的,
但随着单据类型的增多和字段增加,以及业务方越来愈多,这种方案不能很好的满住需求。
单据中心需要能检索json的能力,在技术选项上有ES,MonggoDB,PostgreSQL,MySQL8

为什么选择MySQL8而不是其他MySQL版本?

MySQL对json的支持是在5.7版本开始支持的。而需要使用的JSON_TABLE函数是在8.0版本才支持。

JSON函数

官方文档

函数名 描述
JSON_VALID() JSON值是否是有效的
JSON_UNQUOTE() JSON值而言
JSON_TYPE() JSON值类型
JSON_STORAGE_SIZE() 用于一个JSON文件的二进制表示形式存储空间;一个JSON柱,空间时使用的文档插入到任何部分更新之前,
JSON_SET() 将数据插入到JSON文档
JSON_SEARCH() 在JSON文件价值路径
JSON_REPLACE() 在JSON文件的值替换
JSON_REMOVE() 从JSON文件中删除数据
JSON_QUOTE() 引用JSON文档
JSON_PRETTY() 版画在人类可读的格式JSON文档,每个数组元素或对象成员打印在新的行中,缩进两个空格就其母。
JSON_OBJECT() 创建JSON对象
JSON_MERGE_PRESERVE() 合并的JSON文件,保存重复键
JSON_MERGE_PATCH() 合并的JSON文件,免去重复键的值
JSON_MERGE()(废弃的5.7.22) 合并的JSON文件,保存重复键。不json_merge_preserve()的同义词
JSON_LENGTH() 在JSON文档中的元素数
JSON_KEYS() 从JSON文件密钥数组
JSON_INSERT() 将数据插入到JSON文档
JSON_EXTRACT() 从JSON文档返回数据
JSON_DEPTH() JSON文档的最大深度
JSON_CONTAINS_PATH() 无论是JSON文件包含任何数据路径
JSON_CONTAINS() 是否包含特定对象的JSON文档路径
JSON_ARRAY_INSERT() 插入JSON数组
JSON_ARRAY_APPEND() JSON文件追加数据
JSON_ARRAY() 创建JSON数组
JSON_APPEND()(废弃的5.7.9) JSON文件追加数据
->> 在评估路径和结束引语结果返回JSON列值;相当于json_unquote(json_extract())。
-> 在评估路径返回JSON列值;相当于json_extract()。

JSON_TABLE函数的应用

官方说明

读取单据行数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

SELECT
jt.*
FROM
document_content dc,
JSON_TABLE (
`content`,
'$.Lines[*]' COLUMNS (
ID VARCHAR (100) PATH "$.ID",
Price VARCHAR (100) PATH "$.Price",
TaxClassificationCode VARCHAR (100) PATH "$.TaxClassificationCode"
)
) AS jt
WHERE
dc.id=168718

提高json检索速度

很遗憾mysql原生并不支持json列中的属性索引,
但是我们可以通过mysql的虚拟列(5.7版本支持)间接的为json中的某些属性创建索引
,原理就是为json中的属性创建虚拟列,然后通过给虚拟列建立索引,
从而间接的给属性创建了索引。

建立虚拟列

在MySQL 5.7中,支持两种

  • Generated Column,即Virtual Generated Column
  • Stored Generated Column

前者只将Generated Column保存在数据字典中(表的元数据),并不会将这一列数据持久化到磁盘上;
后者会将Generated Column持久化到磁盘上,而不是每次读取的时候计算所得。
很明显,后者存放了可以通过已有数据计算而得的数据,需要更多的磁盘空间,
与Virtual Column相比并没有优势
,因此,MySQL 5.7中,不指定Generated Column的类型,默认是Virtual Column。

1
2
3

fieldname <type> [ GENERATED ALWAYS ] AS ( <expression> ) [ VIRTUAL|STORED ] [ UNIQUE [KEY] ] [ [PRIMARY] KEY ] [ NOT NULL ] [ COMMENT <text> ]

创建表

1
2
3
4
5
6
7
8

CREATE TABLE 表 (
id INT UNSIGNED NOT NULL primary key auto_increment,
json字段名 JSON NOT NULL,
虚拟列字段名 数据类型 GENERATED ALWAYS AS (`json字段名` ->> '$.字段') NOT NULL -- 虚拟列
);


添加索引

跟添加一般索引一样

1
2
3

CREATE INDEX `索引名称` ON `表`(`虚拟列名`);

缺点

这种方法只能只能对json的某个单值属性或者给数组中的某一个特定位置上的元素有效,
如果你想给对象数组中的所有元素的某个属性使用索引,
那么最后你只能匹配数组中的所有对象的该属性或者使用like,
但是使用like的话就不能使用索引,也就是说对于这种情况是不可用的。

五指棋界面

利用html5的canvas组件画出棋盘,棋盘为15*15格

SSL证书生成

生产环境是向证书供应商进行购买,在使用工具转换
我们着使用java自带的工具进行生成,可以使用keytool –help查看具体用法

jks证书(java专用)

1
2
keytool -genkeypair -alias tomcat -keyalg RSA -keysize 2048 -keystore keystore.jks -validity 3650  #生成证书
keytool -list -v -keystore keystore.jks # 查看生成的信息

PKCS12

1
2
keytool -genkeypair -alias tomcat -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650 #生成证书
keytool -list -v -storetype pkcs12 -keystore keystore.p12 #查看生成的信息

生成证书都会要求你输入密码,注意,请记住这个密码,后面springboot配置要用到。

JKS迁移成PKCS12证书

1
keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.p12 -deststoretype pkcs12

证书供应商提供的证书

1
keytool -import -alias tomcat -file myCertificate.crt -keystore keystore.p12 -storepass password

Spring Boot 启用https

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#访问的端口
server.port=8443
#自定义配置用于开启http端口访问
http.port=8080

#默认就是true 不用配置
server.ssl.enabled=true
#这用到生成证书时的密码
server.ssl.key-password=111111
#证书地址 如果是在jar里面 使用classpath:keystore.jks
server.ssl.key-store=./keystore.jks
#证书alias 别名
server.ssl.key-alias=tomcat
#证书格式 还有 PKCS12
server.ssl.key-store-type=jks

启动服务器 访问https 的 8488端口 应该就能访问了

http重定向到https

spring boot 2.x 版本代码

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
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
/*配置http自动转为https*/
public class ConnectorConfig {

@Bean
public ServletWebServerFactory servletWebServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");//机密的
SecurityCollection securityCollection = new SecurityCollection();
securityCollection.addPattern("/*");
securityConstraint.addCollection(securityCollection);
context.addConstraint(securityConstraint);
}
};
factory.addAdditionalTomcatConnectors(httpConnector());
return factory;
}

//https端口
@Value("${server.port}")
private int httpsPort;

//http端口
@Value("${http.port}")
private int httpPort = 8080;

@Bean
public Connector httpConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(httpPort);
connector.setSecure(false);
connector.setRedirectPort(httpsPort);
return connector;
}
}

访问8080端口 会自动跳转到 https 的端口

其他(非必要操作)

获取分发给客户端的证书

1
keytool -export -keystore keystore.jks -alias tomcat -file myCertificate.crt

jdk导入证书 方便java程序访问https访问时进行证书的验证

1
2
keytool -importcert -file myCertificate.crt -alias tomcat -keystore $JDK_HOME/jre/lib/security/cacerts

思考❓

浏览器怎么和证书进行关联?通过别名么? 别名设置成域名?

安装supervisor

官方安装教程

1
pip install supervisor

如果环境没有 pip工具

1
2
3
4
5
wget supervisor下载地址
tar -zxvf supervisor-x.x.x.tar.gz #解压
cd supervisor-x.x.x
python setup.py install #本地python版本为python2.7
# python2.7 setup.py install #本地python版本为python3以上

使用

生成配置文件

1
echo_supervisord_conf > /etc/supervisord.conf

启动supervisor

1
supervisord -c /etc/supervisord.conf

检查是否启动成功

可以通过查看进程列表方式进行判断

1
ps aux | grep supervisord

配置

/etc/supervisord.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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
; Sample supervisor config file.
;
; For more information on the config file, please see:
; http://supervisord.org/configuration.html
;
; Notes:
; - Shell expansion ("~" or "$HOME") is not supported. Environment
; variables can be expanded using this syntax: "%(ENV_HOME)s".
; - Quotes around values are not supported, except in the case of
; the environment= options as shown below.
; - Comments must have a leading space: "a=b ;comment" not "a=b;comment".
; - Command will be truncated if it looks like a config file comment, e.g.
; "command=bash -c 'foo ; bar'" will truncate to "command=bash -c 'foo ".

[unix_http_server]
file=/tmp/supervisor.sock ; the path to the socket file
;chmod=0700 ; socket file mode (default 0700)
;chown=nobody:nogroup ; socket file uid:gid owner
;username=user ; default is no username (open server)
;password=123 ; default is no password (open server)


;配置内部HTTP-SERVER,注意0.0.0.0 是开放给任何IP访问的,
[inet_http_server] ; inet (TCP) server disabled by default
port=0.0.0.0:9001 ; ip_address:port specifier, *:port for all iface
;登录管理界面需要的的用户名以及密码
username=user ; default is no username (open server)
password=123 ; default is no password (open server)

[supervisord]
logfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log
logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB
logfile_backups=10 ; # of main logfile backups; 0 means none, default 10
loglevel=info ; log level; default info; others: debug,warn,trace
pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid
nodaemon=false ; start in foreground if true; default false
minfds=1024 ; min. avail startup file descriptors; default 1024
minprocs=200 ; min. avail process descriptors;default 200
;umask=022 ; process file creation umask; default 022
;user=supervisord ; setuid to this UNIX account at startup; recommended if root
;identifier=supervisor ; supervisord identifier, default is 'supervisor'
;directory=/tmp ; default is not to cd during start
;nocleanup=true ; don't clean up tempfiles at start; default false
;childlogdir=/tmp ; 'AUTO' child log dir, default $TEMP
;environment=KEY="value" ; key value pairs to add to environment
;strip_ansi=false ; strip ansi escape codes in logs; def. false

; The rpcinterface:supervisor section must remain in the config file for
; RPC (supervisorctl/web interface) to work. Additional interfaces may be
; added by defining them in separate [rpcinterface:x] sections.

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

; The supervisorctl section configures how supervisorctl will connect to
; supervisord. configure it match the settings in either the unix_http_server
; or inet_http_server section.

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
;username=chris ; should be same as in [*_http_server] if set
;password=123 ; should be same as in [*_http_server] if set
;prompt=mysupervisor ; cmd line prompt (default "supervisor")
;history_file=~/.sc_history ; use readline history if available

; The sample program section below shows all possible program subsection values.
; Create one or more 'real' program: sections to be able to control them under
; supervisor.

;[program:theprogramname]
;command=/bin/cat ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1 ; number of processes copies to start (def 1)
;directory=/tmp ; directory to cwd to before exec (def no cwd)
;umask=022 ; umask for process (default None)
;priority=999 ; the relative start priority (default 999)
;autostart=true ; start at supervisord start (default: true)
;startsecs=1 ; # of secs prog must stay up to be running (def. 1)
;startretries=3 ; max # of serial start failures when starting (default 3)
;autorestart=unexpected ; when to restart if exited after running (def: unexpected)
;exitcodes=0 ; 'expected' exit codes used with autorestart (default 0)
;stopsignal=QUIT ; signal used to kill process (default TERM)
;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false ; send stop signal to the UNIX process group (default false)
;killasgroup=false ; SIGKILL the UNIX process group (def false)
;user=chrism ; setuid to this UNIX account to run the program
;redirect_stderr=true ; redirect proc stderr to stdout (default false)
;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10)
;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
;stdout_events_enabled=false ; emit events on stdout writes (default false)
;stdout_syslog=false ; send stdout to syslog with process name (default false)
;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10)
;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
;stderr_events_enabled=false ; emit events on stderr writes (default false)
;stderr_syslog=false ; send stderr to syslog with process name (default false)
;environment=A="1",B="2" ; process environment additions (def no adds)
;serverurl=AUTO ; override serverurl computation (childutils)

; The sample eventlistener section below shows all possible eventlistener
; subsection values. Create one or more 'real' eventlistener: sections to be
; able to handle event notifications sent by supervisord.

;[eventlistener:theeventlistenername]
;command=/bin/eventlistener ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1 ; number of processes copies to start (def 1)
;events=EVENT ; event notif. types to subscribe to (req'd)
;buffer_size=10 ; event buffer queue size (default 10)
;directory=/tmp ; directory to cwd to before exec (def no cwd)
;umask=022 ; umask for process (default None)
;priority=-1 ; the relative start priority (default -1)
;autostart=true ; start at supervisord start (default: true)
;startsecs=1 ; # of secs prog must stay up to be running (def. 1)
;startretries=3 ; max # of serial start failures when starting (default 3)
;autorestart=unexpected ; autorestart if exited after running (def: unexpected)
;exitcodes=0 ; 'expected' exit codes used with autorestart (default 0)
;stopsignal=QUIT ; signal used to kill process (default TERM)
;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false ; send stop signal to the UNIX process group (default false)
;killasgroup=false ; SIGKILL the UNIX process group (def false)
;user=chrism ; setuid to this UNIX account to run the program
;redirect_stderr=false ; redirect_stderr=true is not allowed for eventlisteners
;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10)
;stdout_events_enabled=false ; emit events on stdout writes (default false)
;stdout_syslog=false ; send stdout to syslog with process name (default false)
;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10)
;stderr_events_enabled=false ; emit events on stderr writes (default false)
;stderr_syslog=false ; send stderr to syslog with process name (default false)
;environment=A="1",B="2" ; process environment additions
;serverurl=AUTO ; override serverurl computation (childutils)

; The sample group section below shows all possible group values. Create one
; or more 'real' group: sections to create "heterogeneous" process groups.

;[group:thegroupname]
;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions
;priority=999 ; the relative start priority (default 999)

; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.

;[include]
;files = relative/directory/*.ini

;引入其他配置文件
[include]
files=/etc/supervisor/*.conf

###自定义配置文件

/etc/supervisor/spring-boot-demo.conf 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
; 设置进程的名称,使用 supervisorctl 来管理进程时需要使用该进程名
[program:spring-boot-demo]
;运行的命令
command=java -jar demo-0.0.1-SNAPSHOT.jar
;numprocs=1 ; 默认为1 进程数量
;process_name=%(program_name)s ; 默认为 %(program_name)s,即 [program:x] 中的 x
directory=/root/demo/target ; 执行 command 之前,先切换到工作目录
;user=spring-boot ; 使用 哪个 用户来启动该进程 linux要有该用户
; 程序崩溃时自动重启,重启次数是有限制的,默认为3次
autorestart=true
redirect_stderr=true ; 重定向输出的日志
stdout_logfile = /var/log/supervisord/spring-boot-demo.log ;日志文件位置
loglevel=info

以下是我在网上找的全部的配置说明

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
;[program:theprogramname]      ;这个就是咱们要管理的子进程了,":"后面的是名字,最好别乱写和实际进程
有点关联最好。这样的program我们可以设置一个或多个,一个program就是
要被管理的一个进程
;command=/bin/cat ; 这个就是我们的要启动进程的命令路径了,可以带参数
例子:/home/test.py -a 'hehe'
有一点需要注意的是,我们的command只能是那种在终端运行的进程,不能是
守护进程。这个想想也知道了,比如说command=service httpd start。
httpd这个进程被linux的service管理了,我们的supervisor再去启动这个命令
这已经不是严格意义的子进程了。
这个是个必须设置的项
;process_name=%(program_name)s ; 这个是进程名,如果我们下面的numprocs参数为1的话,就不用管这个参数
了,它默认值%(program_name)s也就是上面的那个program冒号后面的名字,
但是如果numprocs为多个的话,那就不能这么干了。想想也知道,不可能每个
进程都用同一个进程名吧。


;numprocs=1 ; 启动进程的数目。当不为1时,就是进程池的概念,注意process_name的设置
默认为1 。。非必须设置
;directory=/tmp ; 进程运行前,会前切换到这个目录
默认不设置。。。非必须设置
;umask=022 ; 进程掩码,默认none,非必须
;priority=999 ; 子进程启动关闭优先级,优先级低的,最先启动,关闭的时候最后关闭
默认值为999 。。非必须设置
;autostart=true ; 如果是true的话,子进程将在supervisord启动后被自动启动
默认就是true 。。非必须设置
;autorestart=unexpected ; 这个是设置子进程挂掉后自动重启的情况,有三个选项,false,unexpected
和true。如果为false的时候,无论什么情况下,都不会被重新启动,
如果为unexpected,只有当进程的退出码不在下面的exitcodes里面定义的退
出码的时候,才会被自动重启。当为true的时候,只要子进程挂掉,将会被无
条件的重启
;startsecs=1 ; 这个选项是子进程启动多少秒之后,此时状态如果是running,则我们认为启
动成功了
默认值为1 。。非必须设置
;startretries=3 ; 当进程启动失败后,最大尝试启动的次数。。当超过3次后,supervisor将把
此进程的状态置为FAIL
默认值为3 。。非必须设置
;exitcodes=0,2 ; 注意和上面的的autorestart=unexpected对应。。exitcodes里面的定义的
退出码是expected的。
;stopsignal=QUIT ; 进程停止信号,可以为TERM, HUP, INT, QUIT, KILL, USR1, or USR2等信号
默认为TERM 。。当用设定的信号去干掉进程,退出码会被认为是expected
非必须设置
;stopwaitsecs=10 ; 这个是当我们向子进程发送stopsignal信号后,到系统返回信息
给supervisord,所等待的最大时间。 超过这个时间,supervisord会向该
子进程发送一个强制kill的信号。
默认为10秒。。非必须设置
;stopasgroup=false ; 这个东西主要用于,supervisord管理的子进程,这个子进程本身还有
子进程。那么我们如果仅仅干掉supervisord的子进程的话,子进程的子进程
有可能会变成孤儿进程。所以咱们可以设置可个选项,把整个该子进程的
整个进程组都干掉。 设置为true的话,一般killasgroup也会被设置为true。
需要注意的是,该选项发送的是stop信号
默认为false。。非必须设置。。
;killasgroup=false ; 这个和上面的stopasgroup类似,不过发送的是kill信号
;user=chrism ; 如果supervisord是root启动,我们在这里设置这个非root用户,可以用来
管理该program
默认不设置。。。非必须设置项
;redirect_stderr=true ; 如果为true,则stderr的日志会被写入stdout日志文件中
默认为false,非必须设置
;stdout_logfile=/a/path ; 子进程的stdout的日志路径,可以指定路径,AUTO,none等三个选项。
设置为none的话,将没有日志产生。设置为AUTO的话,将随机找一个地方
生成日志文件,而且当supervisord重新启动的时候,以前的日志文件会被
清空。当 redirect_stderr=true的时候,sterr也会写进这个日志文件
;stdout_logfile_maxbytes=1MB ; 日志文件最大大小,和[supervisord]中定义的一样。默认为50
;stdout_logfile_backups=10 ; 和[supervisord]定义的一样。默认10
;stdout_capture_maxbytes=1MB ; 这个东西是设定capture管道的大小,当值不为0的时候,子进程可以从stdout
发送信息,而supervisor可以根据信息,发送相应的event。
默认为0,为0的时候表达关闭管道。。。非必须项
;stdout_events_enabled=false ; 当设置为ture的时候,当子进程由stdout向文件描述符中写日志的时候,将
触发supervisord发送PROCESS_LOG_STDOUT类型的event
默认为false。。。非必须设置
;stderr_logfile=/a/path ; 这个东西是设置stderr写的日志路径,当redirect_stderr=true。这个就不用
设置了,设置了也是白搭。因为它会被写入stdout_logfile的同一个文件中
默认为AUTO,也就是随便找个地存,supervisord重启被清空。。非必须设置
;stderr_logfile_maxbytes=1MB ; 这个出现好几次了,就不重复了
;stderr_logfile_backups=10 ; 这个也是
;stderr_capture_maxbytes=1MB ; 这个一样,和stdout_capture一样。 默认为0,关闭状态
;stderr_events_enabled=false ; 这个也是一样,默认为false
;environment=A="1",B="2" ; 这个是该子进程的环境变量,和别的子进程是不共享的
;serverurl=AUTO ;

更改了配置文件需要重新加载配置文件,supervisor会重启各个引用

1
supervisorctl reload

supervisorctl 使用

1
2
3
4
5
6
7
8
supervisord #启动supervisor
supervisorctl reload #修改完配置文件后重新启动supervisor
supervisorctl status #查看supervisor监管的进程状态
supervisorctl start 进程名 #启动XXX进程
supervisorctl stop 进程名 #停止XXX进程
supervisorctl stop all #停止全部进程,注:start、restart、stop都不会载入最新的配置文件。
supervisorctl update #根据最新的配置文件,启动新配置或有改动的进程,配置没有改动的进程不会受影响而重启

使用浏览器来管理进程

编辑/etc/supervisord.conf 文件

1
2
3
4
5
6
;配置内部HTTP-SERVER,注意0.0.0.0 是开放给任何IP访问的,
[inet_http_server] ; inet (TCP) server disabled by default
port=0.0.0.0:9001 ; ip_address:port specifier, *:port for all iface
;登录管理界面需要的的用户名以及密码
username=user ; default is no username (open server)
password=123 ; default is no password (open server)

补充

命令运行不了

有可能是运行命令的权限不够 需要 sudo 命令

浏览器访问不了管理界面

有可能是Linux防火墙不允许访问9001端口
以centos7为例:

1
2
firewall-cmd --permanent --add-port=9001/tcp #防火墙加入9001端口
firewall-cmd --reload #重启防火墙 使端口生效

起因

最近向海关上报支付单时,加密过程只能用海关提供的websocket服务进行加签。
在使用过程中,由于海关的服务不够稳定 socket经常中断,所以需要对websocket 进行断线重连

解决方案

不停的检查websocket的连接状态,对于已经关闭连接的websocket进行重新初始化(因为断掉的websocket不能进行重新连接)

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

@Slf4j
public class SignDataWebSocketClient extends WebSocketClient {

Producer producer;

/**
* 初始化
*
* @param serverUri
*/
public SignDataWebSocketClient(URI serverUri, Producer producer) {
super(serverUri);
this.producer = producer;
}


public void onOpen(ServerHandshake arg0) {
log.debug("onOpen打开链接,当前状态{}", getReadyState());
}

public void onMessage(String arg0) {
log.debug("onMessage,当前状态{}", getReadyState());
log.debug("收到消息:" + arg0);
try {
JSONObject jsonObject = JSON.parseObject(arg0);
String _status = jsonObject.getString("_status");
String _method = jsonObject.getString("_method");
Integer _id = jsonObject.getInteger("_id");

if ("cus-sec_SpcSignDataAsPEM".equals(_method) && "00".equals(_status)) {
String _args = jsonObject.getString("_args");

JSONObject args = JSON.parseObject(_args);
boolean result = args.getBoolean("Result");

JSONObject resp = new JSONObject();
resp.put("orderSubNo", map.remove(_id));
resp.put("success", result);
if (result) {
//签名成功
JSONArray data = args.getJSONArray("Data");
String signValueStr = data.getString(0);
log.debug("签名成功:{}", signValueStr);
resp.put("signValue", signValueStr);
resp.put("certNo", data.getString(1));

} else {
log.error("签名失败:{}", args.getString("Error"));
resp.put("error", args.getString("Error"));
}
producer.produce(resp.toJSONString().getBytes());
}
} catch (Exception e) {
e.printStackTrace();
}

}

public void onError(Exception arg0) {
log.debug("onError当前状态{}", getReadyState());
arg0.printStackTrace();
log.error("发生错误已关闭");
}

public void onClose(int arg0, String arg1, boolean arg2) {
log.debug("onClose链接关闭,当前状态{}", getReadyState());
}


private static Map<Integer, String> map = new ConcurrentHashMap<>();
private static AtomicInteger count = new AtomicInteger(1);


void cusSecSpcSignDataAsPEM(Payload payload) throws UnsupportedEncodingException {
Integer id = count.incrementAndGet();
map.put(id, payload.getOrderSubNo());
JSONObject jsonObject = new JSONObject();
jsonObject.put("_method", "cus-sec_SpcSignDataAsPEM");
jsonObject.put("_id", id);
JSONObject pargs = new JSONObject();
pargs.put("inData", payload.getInData());
String passwd = PropertiesUtil.getProperty("passwd");
log.debug("passwd={}", passwd);
pargs.put("passwd", passwd);
jsonObject.put("args", pargs);
log.debug("{}:发送消息:{}", payload.getOrderSubNo(), jsonObject.toJSONString());
byte[] str = jsonObject.toJSONString().getBytes("UTF-8");
FramedataImpl1 framedataImpl1 = FramedataImpl1.get(Framedata.Opcode.TEXT);
ByteBuffer byteBuffer = ByteBuffer.wrap(str);
framedataImpl1.setPayload(byteBuffer);
this.sendFrame(framedataImpl1);
}

}



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

@Slf4j
public class PayloadSender extends Thread {

Producer producer;
URI serverUri;

/**
* 初始化
*
* @param serverUri
*/
public PayloadSender(URI serverUri, Producer producer) {
this.serverUri = serverUri;
this.producer = producer;
}

private Queue<Payload> payloads = new ConcurrentLinkedQueue<>();


private SignDataWebSocketClient client;

/**
* 消费消息
*/
private void producePayload() {
while (!payloads.isEmpty()) {
try {
//调用海关的加签方法
client.cusSecSpcSignDataAsPEM(payloads.poll());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}

/**
* 提交消息
*
* @param payload 消息
*/
public void submitPayload(Payload payload) {
payloads.add(payload);
}

@Override
public void run() {
while (true) {
try {
if (client == null
||
WebSocket.READYSTATE.CLOSING.equals(client.getReadyState())
||
WebSocket.READYSTATE.CLOSED.equals(client.getReadyState())
) {
log.debug("客户端未初始化或者客户端连接关闭");
//client 未初始化 或者关闭
client = new SignDataWebSocketClient(serverUri, producer);
} else {
if (NOT_YET_CONNECTED.equals(client.getReadyState())) {
// client 未连接
boolean isConnect = client.connectBlocking();
if (isConnect) {
producePayload();
} else {
//连接失败
log.error("连接失败");
throw new RuntimeException("连接失败");
}
} else if (WebSocket.READYSTATE.OPEN.equals(client.getReadyState())) {
producePayload();
}
}
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
client = null;
}
}
}
}

WebSocketClient只能连接一次的原因


    /**
     * Initiates the websocket connection. This method does not block.
     */
    public void connect() {
        if( writeThread != null )
            throw new IllegalStateException( "WebSocketClient objects are not reuseable" );
        writeThread = new Thread( this );
        writeThread.start();
    }

···


 

#shell入门

原文

Shell是什么

Shell是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务, Shell脚本(shell script),是一种为Shell编写的脚本程序。我们经常说的shell通常都是指shell脚本。

环境和工具

Shell跟java、php、Python编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。

Mac OS,Linux 自带了shell解释器,Windows比较麻烦,没有内置shell解释器,可以自行安装
一个cygwin

PHP、Python 也可以作为Shell编程

PHP、Python是属于高级编程语言,但是也可以做Shell编程,因为只要有解释器,也可以用作脚本编程

如下是一个Python Shell Script示例(假设文件名叫op_python_base.py):

1
2
3
4
5
#!/usr/bin/env python3 //告诉Python从系统环境中找python
# -*- coding: utf-8 -*- //设置为UTF-8编码

for index in range(10):
print(index);

如下是一个PHP Shell Script示例(假设文件名叫op_php_base.php):

1
2
3
4
5
6
7
8
9
#!/usr/bin/php
<?php

for($i=0 ;$i<10; $i++){
echo $i;
}

?>

为什么要学习Shell

既然PHP、Python都可以用来写脚本编程,那为什么还要学习陌生、晦涩难懂的Shell,主要有一下几个原因

  • 环境兼容性,因为除了windows,基本其他主流的操作系统都预制了Shell解释器,所以使用sh、bash编写,提供给其他人使用是非常方便的,但是PHP、Python 等等需要安装相应的环境

  • 如果你想做一些定时任务比如说检测进程是否存在,自动备份,或者说自动部署环境、服务器之间的数据同步等等sh、bash会是你最好的选择

sh与bash

sh: Bourne shell,POSIX(Portable Operating System Interface)标准的shell解释器,它的二进制文件路径通常是/bin/sh

bash: Bash是Bourne shell的替代品,属GNU Project,二进制文件路径通常是/bin/bash

第一个shell脚本

我们先来看一个例子

我相信写过代码的童鞋,应该对下面的代码很熟悉并不陌生,(假设文件名叫op_base.sh):

1
2
3
4
5
6
7
#!/usr/bin/env bash
mkdir code
cd code
for ((i=0; i<3; i++)); do
touch test_${i}.txt
echo "shell很简单" >> test_${i}.txt
done

第一行:从系统path中寻找指定脚本的解释程序
第二行:创建 名叫code文件夹
第三行:进入创建的文件夹
第四行:for循环3次
第四行:创建文件
第五行:往创建的文件中写入信息
第六行:结束循环

mkdir, touch,cd,touch,echo都是系统命令,在命令行下可以直接执行
for, do, done 是shell脚本语言 for循环的语法

编写Shell

新建一个文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好,如果你用php,扩展名为php,如果你用Python,扩展名为python

第一行一般是这样:

1
2
3
#!/usr/bin/php
#!/usr/bin/env python3
#!/usr/bin/env bash

#!”是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行
/env 是系统的PATH目录中查找

运行 Shell 脚本有两种方法:

作为可执行程序

1
2
chmod +x op_base.sh
./op_base.sh

第一行设置 op_base.sh可执行权限
第二行执行op_base.sh

作为参数

1
/bin/sh op_base.sh

变量

定义变量时,变量名前不需要加符号和Python一样但是在PHP语言中变量需要加$,如:

1
2
my_name="jack"
my_name='jack';

ps: 变量名和等号之间不能有空格,变量后面不能有;

Shell中的引号和PHP类似,字符串可以用单引号,也可以用双引号

单引号字符串的限制:

  • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的
  • 单引号字串中不能出现单引号(对单引号使用转义符后也不行

双引号:

  • 双引号里可以有变量
  • 双引号里可以出现转义字符

但是在Python中单引号和双引号是没有区别,但是Python 还有三个引号,在三个引号内字符都不会被转义

使用变量

对于已经定义过的变量,使用的适合在前面添加$

1
2
echo $my_name
echo ${my_name}

变量名外面的花括号是可选的,加不加都行,建议使用第二种形式

注释

以“#”开头的行就是注释,会被解释器忽略。

多行注释

sh里没有多行注释,只能每一行加一个#号。就像这样:

1
2
3
4
#--------------------------------------------
#这里是注释
# 还是注释
#--------------------------------------------

字符串

字符串可以用单引号,也可以用双引号,也可以不用引号。单双引号的区别跟PHP类似

Shell不像其他语言有php、python 有很多数据类型,在Shell中常用的数据类型字符串数字和字符串(ps: 除了数字和字符串,也没啥其它类型好用了,哈哈)

单引号字符串的限制:

  • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的
  • 单引号字串中不能出现单引号(对单引号使用转义符后也不行

双引号:

  • 双引号里可以有变量
  • 双引号里可以出现转义字符

字符串操作

拼接字符串

1
2
3
4
my_name="jack";
my_age="20岁"
echo $my_name $my_age
echo $my_name$my_age

获取字符串长度

1
echo ${#my_name}

截取字符串

1
echo ${my_name:0:2}

Shell 数组

定义数组

在Shell中,用括号来表示数组,数组元素用”空格”符号分割开。定义数组的一般形式为:

1
name=(name1 name2 name3)

还可以单独定义数组的各个分量:

1
2
3
ary[0]=name1
ary[1]=name2
ary[3]=name3

ps: 可以不使用连续的下标,而且下标的范围没有限制

读取数组

读取数组元素值的一般格式是:

1
${数组名[下标]}

例如:

1
echo ${name[0]}

使用@符号可以获取数组中的所有元素,例如:

1
echo ${name[@]}

获取数组的长度

获取数组长度的方法与获取字符串长度的方法相同,例如:

1
2
3
4
5
6
7
8
9
10
11
# 取得数组元素的个数
length=${#name[@]}
echo $length

# 或者
length=${#name[*]}
echo $length

# 取得数组单个元素的长度
lengthn=${#name[n]}
echo $length

Shell 流程控制

和Java、PHP、Python等语言不一样,sh的流程控制不可为空,如(以下为PHP流程控制写法):

1
2
3
4
5
6
7
<?php
if (isset($_GET["q"])) {
search(q);
}
else {
// 不做任何事情
}

在sh/bash里可不能这么写,如果else分支没有语句执行,就不要写这个else

if

1
2
3
4
5
6
7
8
9
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env bash

a=1
b=2
if [ $a == $b ]
then
echo "a 等于 b"
elif [ $a -gt $b ]
then
echo "a 大于 b"
elif [ $a -lt $b ]
then
echo "a 小于 b"
else
echo "没有符合的条件"

fi

for 循环

Shell的for循环和Python 有点类似

Python的for循环
1
2
for index in 1,2,3,4,5:
print(index);
Shell的for循环,第一种写法
1
2
3
for index in 1 2 3 4 5; do
echo "index="$index
done
Shell的for循环,第二种写法
1
2
3
for ((i=0; i<5; i++)); do
echo "i="$i
done

while 语句

while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件。

1
2
3
4
5
6
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done

Shell结合系统命令

sh脚本结合系统命令便有了强大的威力,在字符处理领域,有grep、awk、sed三剑客,grep负责找出特定的行,awk能将行拆分成多个字段,sed则可以实现更新插入删除等写操作。

例如定时检测nginx、mysql是否被关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
path=/var/log
log=${path}/httpd-mysql.log

name=(apache mysql)

exs_init[0]="service httpd start"
exs_init[1]="/etc/init.d/mysqld restart"

for ((i=0; i<2; i++)); do
echo "检查${name[i]}进程是否存在"
ps -ef|grep ${name[i]} |grep -v grep
if [ $? -eq 0 ]; then
pid=$(pgrep -f ${name[i]})
echo "`date +"%Y-%m-%d %H:%M:%S"` ${name[$i]} is running with pid $pid" >> ${log}
else
$(${exs_init[i]})
echo "`date +"%Y-%m-%d %H:%M:%S"` ${name[$i]} start success" >> ${log}
fi
done

解释:检测 nginx、mysql进程是否存在,如果不存在了会自动重新启动。
脚本每次运行会写日志的,没事可以去看看该日志文件,如果进程是不是真的经常性不存在,恐怕就要排查一下深层原因了。

编辑 /etc/crontab 文件

1
crontab -e

在文件最后添加一行:

1
*/5 * * * * /xxx/check_nginx.sh > /dev/null 2>&1

上表示每 5 分钟,执行一下脚本 /xxx/check_nginx.sh,其中xxx代表路径

/dev/null 2>&1 的意思是该条shell命令将不会输出任何信息到控制台,也不会有任何信息输出到文件中。

1
2
3
4
5
6
7
8
9
10
11
#cron表达式每个位置的含义
# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * command to be executed

添加完配置,重启才能生效

1
service crond restart