BlueXIII's Blog

热爱技术,持续学习

0%

引言

当修改一个Linux系统参数或限制,比如文件打开数时,之前用到过的方式有ulimit、limits.conf、sysctl和/proc文件系统。
对这几个东西一直比较困惑,翻了几篇文档,简单写一下它们之间的区别。

ulimit

什么是ulimit

ulimit是linux shell的内键命令,它具有一套参数集,用于对 shell进程 及其 子进程 进行 资源限制
例如用户同时运行了两个shell终端进程,只在其中一个环境中执行了ulimit – s 100,则该shell进程里创建文件的大小会有相应的限制,而另一个shell终端包括其上运行的子程序都不会受其影响。

ulimit的设定值是 per-process 的,也就是说,每个进程有自己的limits值。
使用ulimit进行修改,是 立即生效 的。
ulimit只影响shell进程及其子进程,用户登出后失效。
可以在profile中加入ulimit的设置,变相的做到永久生效。

查看ulimit的设定值

使用ulimit -a可以查看所有的设定值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pi@raspberrypi:~ $ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7336
max locked memory (kbytes, -l) unlimited
max memory size (kbytes, -m) unlimited
open files (-n) 65536
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 95
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7336
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

ulimit的参数

  • -H 设置硬件资源限制.
  • -S 设置软件资源限制.
  • -a 显示当前所有的资源限制.
  • -c size:设置core文件的最大值.单位:blocks
  • -d size:设置数据段的最大值.单位:kbytes
  • -f size:设置创建文件的最大值.单位:blocks
  • -l size:设置在内存中锁定进程的最大值.单位:kbytes
  • -m size:设置可以使用的常驻内存的最大值.单位:kbytes
  • -n size:设置内核可以同时打开的文件描述符的最大值.单位:n
  • -p size:设置管道缓冲区的最大值.单位:kbytes
  • -s size:设置堆栈的最大值.单位:kbytes
  • -t size:设置CPU使用时间的最大上限.单位:seconds
  • -v size:设置虚拟内存的最大值.单位:kbytes
  • unlimited 是一个特殊值,用于表示不限制

注意:

  1. -n 限制文件描述符的最大值,在Linux下一切资源皆文件,所以该参数也限制了socket链接数
  2. 查询时,若不加H或S参数,默认显示的是软限制
  3. 修改时,若不加H或S参数,两个参数一起改变

设置ulimit的位置

可以在以下位置进行ulimit设置:

  • /etc/profile 所有用户有效,永久生效
  • ~/.bash_profile 当前用户有效,永久生效
  • 直接在控制台输入 当前用户有效,临时生效
  • 放在程序的启动脚本中

limit.conf

什么是limits.conf

limits.conf文件实际是Linux PAM中 pam_limits.so 的配置文件,pam_limits模块对 用户的会话 进行 资源限制

一个shell的初始limits就是由pam_limits设定的,用户登录后,pam_limits会给用户的shell设定在limits.conf定义的值。

pam_limits的设定值也是 per-process 的。
pam_limits的设置是 永久生效 的。

limits.conf的位置

1
/etc/security/limits.conf

配置limits.conf

示例:

1
2
3
4
5
6
*    soft    nofile    655360
* hard nofile 655360
* soft noproc 655360
* hard noproc 655355
* soft core 0
* hard core 0

4个字段的含义分别为: domain type item value

  • domain: username|@groupname
  • type: soft、hard、-
  • item:
    core - 限制内核文件的大小
    date - 最大数据大小
    fsize - 最大文件大小
    memlock - 最大锁定内存地址空间
    nofile - 打开文件的最大数目
    rss - 最大持久设置大小
    stack - 最大栈大小
    cpu - 以分钟为单位的最多 CPU 时间
    noproc - 进程的最大数目
    as - 地址空间限制
    maxlogins - 此用户允许登录的最大数目
  • value: 值的大小

sysctl

什么是sysctl

sysctl是一个允许改变正在运行中的Linux系统的接口,修改的是针对 整个系统内核参数
sysctl的修改是 立即临时 的(重启后失效)。
可以通过修改sysctl.conf配置文件,达到 永久 生效。

sysctl的选项

  • -n 打印值时不打印关键字
  • -e 忽略未知关键字错误
  • -N 仅打印名称
  • -w 当改变sysctl设置时使用此项
  • -p 从配置文件“/etc/sysctl.conf”加载内核参数设置
  • -a 打印当前所有可用的内核参数变量和值
  • -A 以表格方式打印当前所有可用的内核参数变量和值

查看某个内核参数

1
2
3
sysctl fs.file-max

fs.file-max = 93796

临时修改某个内核参数

1
sysctl -w fs.file-max=100000  # 设置文件打开数
1
sysctl -w net.ipv4.ip_forward=1  # 开启IP转发

sysctl.conf配置文件

1
2
/etc/sysctl.conf

修改sysctl.conf并使其生效

修改/etc/sysctl.conf可以做到永久生效:

1
2
3
vi /etc/sysctl.conf

fs.file-max = 100000

最后使用-p命令从配置文件中加载内核参数,使其立即生效:

1
sysctl -p

sysctl.conf配置示例

下面是一个摘抄的配置示例,出处:http://www.jianshu.com/p/9a8e383b5b49

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
# Kernel sysctl configuration file for Red Hat Linux
#
# For binary values, 0 is disabled, 1 is enabled. See sysctl(8) and
# sysctl.conf(5) for more details.

# Controls IP packet forwarding
net.ipv4.ip_forward = 0

# Controls source route verification
net.ipv4.conf.default.rp_filter = 1

# Do not accept source routing
net.ipv4.conf.default.accept_source_route = 0

# Controls the System Request debugging functionality of the kernel

# Controls whether core dumps will append the PID to the core filename.
# Useful for debugging multi-threaded applications.
kernel.core_uses_pid = 1

# Controls the use of TCP syncookies
net.ipv4.tcp_syncookies = 1

# Disable netfilter on bridges.
net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-arptables = 0

# Controls the default maxmimum size of a mesage queue
kernel.msgmnb = 65536

# Controls the maximum size of a message, in bytes
kernel.msgmax = 65536

# Controls the maximum shared segment size, in bytes
kernel.shmmax = 68719476736

# Controls the maximum number of shared memory segments, in pages
kernel.shmall = 4294967296
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.netfilter.nf_conntrack_max = 1000000
kernel.unknown_nmi_panic = 0
kernel.sysrq = 0
fs.file-max = 1000000
vm.swappiness = 10
fs.inotify.max_user_watches = 10000000
net.core.wmem_max = 327679
net.core.rmem_max = 327679
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0

/proc文件系统

什么是/proc文件系统

Linux内核提供了一种通过/proc文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。
proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
最初开发/proc文件系统是为了提供有关系统中进程的信息。但是由于这个文件系统非常有用,因此内核中的很多元素也开始使用它来报告信息,或启用动态运行时配置。

对/proc中内核文件的修改,针对的是 整个系统内核参数 ,修改后 立即生效 ,但修改是 临时 的(重启后失效)。

/proc文件系统与sysctl.conf的对应关系

/proc/sys下内核文件与配置文件sysctl.conf中变量的对应关系:

  1. 去掉前面部分/proc/sys
  2. 将文件名中的斜杠变为点

例如:

  • /proc/sys/net/ipv4/ip_forward -> net.ipv4.ip_forward
  • /proc/sys/kernel/hostname -> kernel.hostname

/proc文件系统中几个常用的内核文件

下面几个是经常会用到的文件:

  • /proc/meminfo 内存信息
  • /proc/cpuinfo CPU信息
  • /proc/sys/fs/file-max 文件打开数
  • /proc/sys/fs/file-nr 整个系统目前使用的文件句柄数量

/proc文件系统中文件的权限

proc 中的每个文件都有一组分配给它的非常特殊的文件许可权,并且每个文件属于特定的用户标识。

  • 只读:任何用户都不能更改该文件;它用于表示系统信息
  • root 写:如果 /proc 中的某个文件是可写的,则通常只能由 root 用户来写
  • root 读:有些文件对一般系统用户是不可见的,而只对 root 用户是可见的

对/proc进行读写

以开启IP转发为例:

1
2
3
4
5
cat /proc/sys/net/ipv4/ip_forward
0
echo "1" > /proc/sys/net/ipv4/ip_forward
cat /proc/sys/net/ipv4/ip_forward
1

当然,也可以使用sysctl来配置这些内核条目

内核文件详解

  • /proc/buddyinfo 每个内存区中的每个order有多少块可用,和内存碎片问题有关
  • /proc/cmdline 启动时传递给kernel的参数信息
  • /proc/cpuinfo cpu的信息
  • /proc/crypto 内核使用的所有已安装的加密密码及细节
  • /proc/devices 已经加载的设备并分类
  • /proc/dma 已注册使用的ISA DMA频道列表
  • /proc/execdomains Linux内核当前支持的execution domains
  • /proc/fb 帧缓冲设备列表,包括数量和控制它的驱动
  • /proc/filesystems 内核当前支持的文件系统类型
  • /proc/interrupts x86架构中的每个IRQ中断数
  • /proc/iomem 每个物理设备当前在系统内存中的映射
  • /proc/ioports 一个设备的输入输出所使用的注册端口范围
  • /proc/kcore 代表系统的物理内存,存储为核心文件格式,里边显示的是字节数,等于RAM大小加上4kb
  • /proc/kmsg 记录内核生成的信息,可以通过/sbin/klogd或/bin/dmesg来处理
  • /proc/loadavg 根据过去一段时间内CPU和IO的状态得出的负载状态,与uptime命令有关
  • /proc/locks 内核锁住的文件列表
  • /proc/mdstat 多硬盘,RAID配置信息(md=multiple disks)
  • /proc/meminfo RAM使用的相关信息
  • /proc/misc 其他的主要设备(设备号为10)上注册的驱动
  • /proc/modules 所有加载到内核的模块列表
  • /proc/mounts 系统中使用的所有挂载
  • /proc/mtrr 系统使用的Memory Type Range Registers (MTRRs)
  • /proc/partitions 分区中的块分配信息
  • /proc/pci 系统中的PCI设备列表
  • /proc/slabinfo 系统中所有活动的 slab 缓存信息
  • /proc/stat 所有的CPU活动信息
  • /proc/sysrq-trigger 使用echo命令来写这个文件的时候,远程root用户可以执行大多数的系统请求关键命令,就好- 像在本地终端执行一样。要写入这个文件,需要把/proc/sys/kernel/sysrq不能设置为0。这个文件对root也是不可- 读的
  • /proc/uptime 系统已经运行了多久
  • /proc/swaps 交换空间的使用情况
  • /proc/version Linux内核版本和gcc版本
  • /proc/bus 系统总线(Bus)信息,例如pci/usb等
  • /proc/driver 驱动信息
  • /proc/fs 文件系统信息
  • /proc/ide ide设备信息
  • /proc/irq 中断请求设备信息
  • /proc/net 网卡设备信息
  • /proc/scsi scsi设备信息
  • /proc/tty tty设备信息
  • /proc/net/dev 显示网络适配器及统计信息
  • /proc/vmstat 虚拟内存统计信息
  • /proc/vmcore 内核panic时的内存映像
  • /proc/diskstats 取得磁盘信息
  • /proc/schedstat kernel调度器的统计信息
  • /proc/zoneinfo 显示内存空间的统计信息,对分析虚拟内存行为很有用

以下是/proc目录中进程N的信息:

  • /proc/N pid为N的进程信息
  • /proc/N/cmdline 进程启动命令
  • /proc/N/cwd 链接到进程当前工作目录
  • /proc/N/environ 进程环境变量列表
  • /proc/N/exe 链接到进程的执行命令文件
  • /proc/N/fd 包含进程相关的所有的文件描述符
  • /proc/N/maps 与进程相关的内存映射信息
  • /proc/N/mem 指代进程持有的内存,不可读
  • /proc/N/root 链接到进程的根目录
  • /proc/N/stat 进程的状态
  • /proc/N/statm 进程使用的内存的状态
  • /proc/N/status 进程状态信息,比stat/statm更具可读性
  • /proc/self 链接到当前正在运行的进程

参考文档

crontab

crontab并不陌生,在*nix的操作系统之中,使用它设置周期性被执行的指令.每个用户可以拥有自己的crontab文件;
同时,操作系统保存一个针对整个系统的crontab文件,该文件通常存放于/etc或者/etc之下的子目录中。

1
2
3
4
5
6
7
pi@raspberrypi:/etc $ ls -al|grep cron
drwxr-xr-x 2 root root 4096 Jan 1 1970 cron.d
drwxr-xr-x 2 root root 4096 Jan 1 1970 cron.daily
drwxr-xr-x 2 root root 4096 Jan 1 1970 cron.hourly
drwxr-xr-x 2 root root 4096 Jan 1 1970 cron.monthly
drwxr-xr-x 2 root root 4096 Jan 1 1970 cron.weekly
-rw-r--r-- 1 root root 722 Sep 5 2015 crontab

基本用法

  • -u user:用来设定某个用户的crontab服务;
  • file:file是命令文件的名字,表示将file做为crontab的任务列表文件并载入crontab。如果在命令行中没有指定这个文件,crontab命令将接受标准输入(键盘)上键入的命令,并将它们载入crontab。
  • -e:编辑某个用户的crontab文件内容。如果不指定用户,则表示编辑当前用户的crontab文件。
  • -l:显示某个用户的crontab文件内容,如果不指定用户,则表示显示当前用户的crontab文件内容。
  • -r:从/var/spool/cron目录中删除某个用户的crontab文件,如果不指定用户,则默认删除当前用户的crontab文件。
  • -i:在删除用户的crontab文件时给确认提示。

最常用的参数有-l 查询-e 编辑

配置格式

  • 第1列分钟0~59
  • 第2列小时0~23(0表示子夜)
  • 第3列日1~31
  • 第4列月1~12
  • 第5列星期0~7(0和7表示星期天)
  • 第6列要运行的命令

配置示例

每分钟执行一次:

1
* * * * * /path/to/your/app.sh

每半分钟执行一次:

1
*/2 * * * * /path/to/your/app.sh

每小时的第3和第15分钟执行:

1
3,15 * * * * /path/to/your/app.sh

在上午8点到11点的第3和第15分钟执行:

1
3,15 8-11 * * * /path/to/your/app.sh

每周六23点执行:

1
0 23 * * 6 /etc/init.d/smb restart

简单应用

检测并自动重启服务

1
2
crontab -e
*/2 * * * * isfound=$(ps | grep "redsocks" | grep -v "grep"); if [ -z "$isfound" ]; then echo "$(date): restart redsocks...">>/tmp/log/redsocks-mon.log && /etc/init.d/redsocks restart; fi

检测并自动重启tomcat

1
*/2 * * * * sh /path/to/tomcat_monitor.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
vi tomcat_monitor.sh
#!/bin/sh
source /home/user/.bash_profile
export PATH=$PATH:.:/bin:/sbin:/usr/bin:
pid_count=`ps aux|grep java|grep your-tomcat|grep -v grep|wc -l`
if [ $pid_count -ne 1 ]; then
echo "stop download-tomcat, time: `date`"
if [ $pid_count -gt 0 ]; then
ps aux|grep java|grep your-tomcat|grep -v grep|awk '{print $2}'|xargs kill
fi
sh /path/to/tomcat/bin/startup.sh
echo "start download-tomcat finished, time: `date`"
fi

清理30天前的tomcat日志

1
2
crontab -e
0 23 * * * sh /path/to/clean_log.sh
1
2
3
4
vi clean_log.sh
DATE=$(date -d "30 days ago" +%Y-%m-%d)
echo $DATE
rm /path/to/tomcat/logs/*$DATE*

备份并清理catalina.out

1
2
crontab -e
00 22 * * * sh /path/to/clean_catalina_out.sh
1
2
3
4
5
6
7
vi clean_catalina_out.sh
y=`date "+%Y"`
m=`date "+%m"`
d=`date "+%d"`
cp /path/to/tomcat/logs/catalina.out /path/to/tomcat/logs/catalina.out.$y-$m-$d
echo > /path/to/tomcat/logs/catalina.out
exit

注意设置环境变量

由于crontab执行任务时不加载任何环境变量,可以在脚本中加入必要的环境变量,甚至ulimit设置,以保证程序可以正常执行。

1
2
3
4
5
6
7
vi start.sh

!/bin/sh
source /etc/profile
export YOUR_ENV=value
ulimit -n 10240
/path/to/your/app/run

参考文档

引言

最近在某个小型项目上进行了一些尝试,目标是用极快的速度构建一套简洁且优雅的小型WEB系统。当然这次尝试可以说是失败的,稍后会提到。

架构上主要是前后端分离,后端使用Python实现RESTFul API,前端直接套用AdminLTE模版。

得益于Python动态语言的特性,后端可以做到非常少量的代码实现增删改查等基本功能,大约是可以使用100多行代码写了25个REST服务吧,非常惊人。虽然比较简陋,但比起之前使用Spring Boot全家桶来开发REST,要快得太多。

但比起后端,前端的代码量,初版写出来之后,是后端的几十倍,使用gulp构建,include等插件后,做到了一定简化,接下来还要做SPA化,引入vue.js、webpack等等,最终会变成 为了目的不择手段,为了手段而忘记目的 。一个很重要的前提是,要做一个 小型WEB系统 ,整体成本要小。所以前后端分离虽然好处多多,但前端太重了,并不适合小型系统。

最终还是使用了Flask+Jinja,传统的重后端、轻前端的方式来实现,将总体代码量控制到了极少。
但这次的REST API部分,不失为一次有益的尝试,单独拿出来做成脚手架,提供给有需要的人吧。

源码

http://git.si-tech.com.cn/guolei/flask-rest-sample
https://github.com/xiiiblue/flask-rest-sample

简介

flask-rest-sample 是一个使用Python编写的REST API,可以对多个资源提供增删改查服务,用于快速构建REST应用。

  1. Web框架使用Flask
  2. ORM框架使用peewee
  3. 安全方面使用了flask-jwt插件进行JWT(Json Web Token)认证
  4. 由于flask-rest/flask-restplus侵略性较强,本次没有使用。
  5. 可以考虑自行使用flasgger添加SwaggerUI文档。

第三方依赖

  • peewee
  • pymysql
  • Flask
  • Flask-JWT
  • flask-script

主要代码示例

  1. 建模时尽量避免使用外键等约束条件,保证模型结构上一致,可以做到共用一个公共服务。

  2. 可使用@rest.route添加多个资源

    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
    # 基本API
    @rest.route('/api/reports', methods=['GET', 'POST'])
    @rest.route('/api/reports/<id>', methods=['GET', 'PUT', 'DELETE'])
    @rest.route('/api/res2', methods=['GET', 'POST'])
    @rest.route('/api/res2/<id>', methods=['GET', 'PUT', 'DELETE']) #只要模型结构一致,可以添加多个资源
    @jwt_required()
    def common_rest_api(id=None):
    model_name = request.path.split('/')[2]
    pee_wee_model = utils.get_model_by_name(model_name) #从URL中确定模型
    if not pee_wee_model:
    return utils.jsonresp(status=400, errinfo='model_name不正确')

    if id:
    # 查询
    if request.method == 'GET':
    try:
    model = pee_wee_model.get(pee_wee_model.id == id)
    except:
    return utils.jsonresp(status=404, errinfo='查询不到资料')
    return utils.jsonresp(jsonobj=utils.obj_to_dict(model))
    # 修改
    elif request.method == 'PUT':
    json_dict = request.get_json(silent=True, force=True)
    if not json_dict: return utils.jsonresp(status=400, errinfo='参数格式不正确')
    try:
    model = pee_wee_model.get(pee_wee_model.id == id)
    except:
    return utils.jsonresp(status=404, errinfo='查询不到资料')
    utils.dict_to_obj(dict=json_dict, obj=model, exclude=('id',)) # 去掉ID字段
    model.save()
    return utils.jsonresp(status=201)
    # 删除
    elif request.method == 'DELETE':
    try:
    pee_wee_model.get(pee_wee_model.id == id).delete_instance()
    except:
    return utils.jsonresp(status=404, errinfo='查询不到资料')
    return utils.jsonresp(status=204)
    else:
    return utils.jsonresp(status=405, errinfo='不支持的HTTP方法')
    else:
    # 全量查询(支持分页、排序、搜索)
    if request.method == 'GET':
    # 处理查询参数
    logger.debug(request.args)
    try:
    # 当前页码
    page = request.args.get('page', '')
    if page: page = int(page) + 1
    # 每页展示数量
    length = request.args.get('length', '')
    if length:
    length = int(length)
    else:
    length = cfg.ITEMS_PER_PAGE
    # 排序
    sort = request.args.get('sort', '')
    if sort:
    sort_column = sort.split(',')[0]
    sort_direction = sort.split(',')[1]
    except:
    return utils.jsonresp(status=400, errinfo='参数格式不正确')

    # 查询
    query = pee_wee_model.select()
    total_count = query.count()

    # 排序
    if sort:
    if sort_column in pee_wee_model._meta.fields:
    field = getattr(pee_wee_model, sort_column)
    if sort_direction != 'asc':
    field = field.desc()
    query = query.order_by(field)
    # 分页
    if page:
    query = query.paginate(page, length)

    dict = {'content': utils.query_to_list(query), 'totalElements': total_count}
    return utils.jsonresp(jsonobj=dict)
    # 新增
    elif request.method == 'POST':
    json_dict = request.get_json(silent=True, force=True)
    if not json_dict: return utils.jsonresp(status=400, errinfo='参数格式不正确')
    user = utils.dict_to_obj(dict=json_dict, obj=pee_wee_model(), exclude=['id']) # 去掉ID字段
    user.save()
    return utils.jsonresp(status=201)
    else:
    return utils.jsonresp(status=405, errinfo='不支持的HTTP方法')

    环境配置

    venv虚拟环境安装配置

    1
    2
    3
    sudo pip3 install virtualenv
    virtualenv venv
    . venv/bin/activate

    第三方依赖安装

    1
    2
    pip3 install -r requirements.txt

    系统参数配置

  3. 编辑config.py, 修改SECRET_KEY及MySQL数据库相关参数

    1
    2
    3
    4
    5
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'your-secret'
    DB_HOST = '127.0.0.1'
    DB_USER = 'foobar'
    DB_PASSWD = 'foobar'
    DB_DATABASE = 'foobar'
  4. 编辑log-app.conf,修改日志路径

    1
    args=('/path/to/log/flask-rest-sample.log','a','utf8')

    数据库初始化

  5. 自动建表
    直接运行python3 models.py

  6. 插入管理员用户(默认admin/admin)

    1
    2
    3
    INSERT INTO `user` (`id`, `username`, `password`, `fullname`, `email`, `phone`, `status`)
    VALUES
    (1, 'admin', 'pbkdf2:sha1:1000$Km1vdx3W$9aa07d3b79ab88aae53e45d26d0d4d4e097a6cd3', '管理员', 'admin@admin.com', '18612341234', 1);

    启动应用

    1
    2
    3
    nohup ./manage.py runserver 2>&1 &

    ./run_app_dev.py (仅限测试)

REST接口说明

以reports资源为例:

  • GET /api/reports/ 查询
    200 成功
  • PUT /api/reports/ 修改
    201 成功

  • DELETE /api/reports/ 删除
    204 成功

  • POST /api/reports 新增
    200 成功

  • GET /api/reports 全量查询
    200 成功

    支持分页(URL参数:page、length)及排序(URL参数:sort)
    参数示例:?page=1&length=5&sort=serial_number,desc

JWT认证简单说明

  1. 向/api/auth发起用户认证请求
    1
    2
    3
    4
    5
    6
    POST http://127.0.0.1:5000/api/auth
    Content-Type: application/json
    {
    "username": "admin",
    "password": "admin"
    }
  2. 获取响应,取得access_token
    1
    2
    3
    {
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0OTE2MzUyNTQsIm5iZiI6MTQ5MTYzNTI1NCwiaWRlbnRpdHkiOjEsImV4cCI6MTQ5MTYzNTU1NH0.wq-uer9LbRP5hEYGT4WfD5O4jf7k7du2Q1K6musKzvU"
    }
  3. 在接下来的HTTP请求头中,加入Authorization,值为JWT+空格+access_token
    以获取当前用户信息为例
    请求:
    1
    2
    GET http://127.0.0.1:5000/api/identity
    Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0OTE2MzUyNTQsIm5iZiI6MTQ5MTYzNTI1NCwiaWRlbnRpdHkiOjEsImV4cCI6MTQ5MTYzNTU1NH0.wq-uer9LbRP5hEYGT4WfD5O4jf7k7du2Q1K6musKzvU
    响应:
    1
    2
    3
    4
    5
    6
    {
    "email": "admin@admin.com",
    "fullname": "管理员",
    "username": "admin",
    "phone": "18612341234"
    }

现象

macOS版SoapUI启动后,窗口卡死,只能Cmd-Option-Esc强制退出。

系统版本: macOS Sierra 10.12.4
SoapUI版本: 5.3.0/5.2.1

解决方法

  1. 修改vmoptions.txt

    1
    vi /Applications/SoapUI-5.3.0.app/Contents/vmoptions.txt

    最后下新增一行-Dsoapui.browser.disabled=true

  2. 修改soapui.sh

    1
    vi /Applications/SoapUI-5.3.0.app/Contents/java/app/bin/soapui.sh

    JAVA_OPTS="$JAVA_OPTS -Dsoapui.browser.disabled=true"一行的注释去掉。

问题

最初安装Ubuntu时,/boot分区只给了200M空间(使用CentOS延续下来的习惯),结果每次生级内核时都会报错/boot空间不足。
下次全新安装Ubuntu,一定记得分出2G空间来给/boot。

临时解决方法

  1. uname -a 查看目前在用的内核版本
  2. dpkg --get-selections|grep linux-image 显示所有的内核版本
  3. sudo apt remove linux-image-XXXX-generic 卸载旧内核,注意只保留最新的 两个 版本
  4. sudo apt autoremove 清理无用的依赖

简介

Gradle是一种类似Maven的项目构建工具,它没有使用繁琐的XML,而是使用Groovy语言进行配置。
作为后起之秀,Gradle继承了Maven的一些思想,并且 配置简洁 ,有更强的 灵活性

Android Studio从一定程度上也加快了Gradle的流行,目前有非常多的开源项目已经迁移到了Gradle。
但现阶段Gradle还不能完全替代Maven,从目前GitHub上的趋势看来,二者可能要并存一段时间了。

从去年开始在一些中小型项目上尝试引入Gradle,仅做简单的项目构建,没有太过深入研究,整体使用下来的体验还是很愉快的。
本文主要是作一些科普,抛砖引玉,并贴出两个可以直接拿来使用的示例,帮助大家快速上手。

与Maven简单对比

  1. 简洁的配置。现在有很多人都对XML深恶痛绝,在Maven中,添加一个依赖需要编写以下5行配置:

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>2.5.0</version>
    </dependency>

    而在Gradle中,只需要一行:

    1
    compile 'com.zaxxer:HikariCP:2.5.0'

    所以Gradle配置文件的整体长度大约是Maven的1/4到1/5左右,并且更加易读。

  2. 灵活性。例如要执行一条shell命令,只需要3行。当然客观来说,灵活往往是复杂的同义词:

    1
    2
    3
    task dropDB(type: Exec) {
    commandLine ‘curl’,’-s’,’s’,’-x’,’DELETE’,"http://${db.server}:{db.port}/db_name"
    }
  3. 约定优于配置。Gradle的Java Plugin,定义了与Maven完全一致的项目布局:

    1
    2
    3
    4
    src/main/java
    src/main/resources
    src/test/java
    src/test/resources

    更多的比较,可以参考 https://gradle.org/maven-vs-gradle

安装

  • macOS下安装:
    1
    brew install gradle
  • Ubuntu下安装:
    1
    sudo apt install gradle
  • Windows下安装:
    1
    2
    3
    Step1: 在[https://gradle.org/releases](https://gradle.org/releases) 下载binary-only的zip包  
    Step2: 解压至某一目录,如C:/bin/gradle
    Step3: 在系统属性-高级-环境变量中,新增GRADLE_HOME环境变量来指向安装路径,并在PATH环境变量的最后追加上GRADLE_HOME/bin

    学习资源

    关于Gradle的学习,不再赘述,有大量的资源可供查阅
  • Gradle 官网
  • Gradle User Guide
  • Gradle User Guide 中文版

单模块项目构建示例

下面是一个单模块Spring Boot项目的示例,适合快速搭建小型项目

/build.gradle

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
buildscript {
repositories {
flatDir {
dirs 'libs'
}
mavenLocal()
maven { url "http://xxx.xxx.xxx.xxx:8081/nexus/content/groups/public" }
// // mavenCentral() //jcenter()
}

dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.0.RELEASE")
}
}

apply plugin: 'java'
apply plugin: "spring-boot"

jar {
baseName = 'project'
version = '1.0-SNAPSHOT'
}

sourceCompatibility = 1.7
targetCompatibility = 1.7

repositories {
mavenCentral()
}

dependencies {
compile 'org.springframework.boot:spring-boot-starter'
compile 'org.apache.commons:commons-email:1.4'
compile files('libs/jxl-2.6.12.jar')
runtime 'mysql:mysql-connector-java:5.1.36'
testCompile 'org.springframework.boot:spring-boot-starter-test'
}

test {
exclude 'com/foo/**'
}

task listJars(description: 'Display all compile jars.') << {
configurations.compile.each { File file -> println file.name }
}

多模块项目构建示例

下面是一个基于Spring Boot的多模块项目示例,可以裁剪后直接拿来做为脚手架使用,适合中型项目

/settings.gradle

1
2
3
include 'common'
include 'repository'
include 'restapi'

/build.gradle

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
buildscript {
repositories {
mavenLocal()
maven { url "http://xxx.xxx.xxx.xxx:8081/nexus/content/groups/public" }
//mavenCentral()
//jcenter()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.0.RELEASE")
}
}

allprojects {
apply plugin: 'idea'
apply plugin: 'eclipse'

group = 'com.foo.bar'
version = '1.0-SNAPSHOT'
//archivesBaseName = 'project'

repositories {
mavenLocal()
maven { url "http://xxx.xxx.xxx.xxx:8081/nexus/content/groups/public" }
//mavenCentral()
//jcenter()
}
}

subprojects {
apply plugin: 'java'
apply plugin: "spring-boot"

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
compile 'org.springframework.boot:spring-boot-starter'
compile 'org.springframework.boot:spring-boot-configuration-processor'
compile 'org.springframework.boot:spring-boot-devtools'
testCompile 'org.springframework.boot:spring-boot-starter-test'
}

test {
exclude 'com/foo/bar/**'
}

task listJars(description: 'Display all compile jars.') << {
configurations.compile.each { File file -> println file.name }
}
}

task wrapper(type: Wrapper) {
gradleVersion = '3.1'
}

/common/build.gradle

1
2
3
4
5
6
7
8
9
10
bootRepackage.enabled = false
jar.baseName 'project-common'

dependencies {
compile project(':repository')
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'commons-lang:commons-lang:2.6'
compile 'org.apache.httpcomponents:httpclient:4.5.2'
compile 'org.modelmapper:modelmapper:0.7.5'
}

/repository/build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bootRepackage.enabled = false
jar.baseName 'project-repository'

dependencies {
//spring boot
compile ('org.springframework.boot:spring-boot-starter-data-jpa')
compile 'org.springframework.boot:spring-boot-starter-jdbc'
//jackson
compile 'com.fasterxml.jackson.core:jackson-annotations:2.8.1'
//hibernate validator
compile 'javax.validation:validation-api:1.1.0.Final'
compile 'org.hibernate:hibernate-validator:5.2.4.Final'
compile 'org.hibernate:hibernate-validator-cdi:5.2.4.Final'
compile 'javax.el:javax.el-api:2.2.4'
compile 'org.glassfish.web:javax.el:2.2.4'
//swagger
compile 'io.springfox:springfox-swagger2:2.6.0'
compile 'io.springfox:springfox-swagger-ui:2.6.0'
//jdbc driver
runtime 'mysql:mysql-connector-java:5.1.36'
runtime 'com.h2database:h2:1.4.192'
//test
testCompile 'org.springframework.security:spring-security-core:4.1.1.RELEASE'
}

/restapi/build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
jar.baseName 'project-rest-api'

dependencies {
compile project(':repository')
compile project(':common')
//spring boot
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
compile 'org.springframework.boot:spring-boot-starter-cache'
compile 'org.springframework.boot:spring-boot-starter-security'
compile 'org.springframework.boot:spring-boot-starter-actuator'
//oauth2
compile 'org.springframework.security.oauth:spring-security-oauth2:2.0.11.RELEASE'
//cache
compile 'net.sf.ehcache:ehcache:2.10.2.2.21'
//pool
compile 'com.alibaba:druid:1.0.23'
compile 'com.zaxxer:HikariCP:2.5.0'
//swagger-staticdocs
testCompile 'io.springfox:springfox-staticdocs:2.6.0'
}

简介

某同事要用Socket连接外围系统,帮忙写了个简单的Demo。
包含一个客户端和一个服务端的,仅是示例,比较简陋,服务端也没有用多线程。
直接贴代码了。

服务端

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
import java.io.*;
import java.net.*;

/**
* Socket服务端示例
*/
public class SocketServer {
ServerSocket serverSocket;
Socket connection = null;
ObjectOutputStream out;
ObjectInputStream in;
String message;

void runServer() {
try {
// 创建服务端监听
serverSocket = new ServerSocket(31313, 10);

// 等待客户端连接
System.out.println("等待客户端连接...");
connection = serverSocket.accept();
System.out.println("收到客户端连接: " + connection.getInetAddress().getHostName());

// 获取输入输出流
out = new ObjectOutputStream(connection.getOutputStream());
out.flush();
in = new ObjectInputStream(connection.getInputStream());

// 连接成功后,首先向客户端发成功消息
sendMsg("连接成功");

// 发送接收消息
do {
try {
message = (String) in.readObject();
System.out.println("client>" + message);

// 发送退出消息
if (message.equals("bye")) {
sendMsg("bye");
}
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
System.err.println("数据格式无效");
}
} while (!message.equals("bye")); //当对方消息为bye时退出循环
} catch (IOException ex) {
ex.printStackTrace();
} finally {
// 关闭Socket连接
try {
in.close();
out.close();
serverSocket.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}

void sendMsg(String msg) {
try {
out.writeObject(msg);
out.flush();
System.out.println("server>" + msg);
} catch (IOException ex) {
ex.printStackTrace();
}
}

public static void main(String args[]) {
SocketServer server = new SocketServer();
while (true) {
server.runServer();
}
}
}

客户端

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
import java.io.*;
import java.net.*;

/**
* Socket客户端示例
*/
public class SocketClient {
Socket clientSocket;
ObjectOutputStream out;
ObjectInputStream in;
String message;

void runClient() {
try {
// 连接到服务端
clientSocket = new Socket("localhost", 31313);
System.out.println("已连接到服务端");

// 获取输入输出流
out = new ObjectOutputStream(clientSocket.getOutputStream());
out.flush();
in = new ObjectInputStream(clientSocket.getInputStream());

// 发送接收消息
do {
try {
// 接收消息
message = (String) in.readObject();
System.out.println("server>" + message);

// 发送消息
sendMsg("hello");
sendMsg("hello again");

// 发送退出消息
message = "bye";
sendMsg(message);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
System.err.println("数据格式无效");
}
} while (!message.equals("bye")); //当对方消息为bye时退出循环
} catch (UnknownHostException ex) {
ex.printStackTrace();
System.err.println("无法连接到服务端");
} catch (IOException ex) {
ex.printStackTrace();
} finally {
// 关闭Socket连接
try {
in.close();
out.close();
clientSocket.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

void sendMsg(String msg) {
try {
out.writeObject(msg);
out.flush();
System.out.println("client>" + msg);
} catch (IOException ex) {
ex.printStackTrace();
}
}

public static void main(String args[]) {
SocketClient client = new SocketClient();
client.runClient();
}
}

pom.xml配置示例

以下是一个比较精简的spring-boot项目的配置文件,包含了Swagger和Jacoco插件。

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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.foo</groupId>
<artifactId>bar</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>bar</name>
<description>blablabla</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.6.RELEASE</version>
<relativePath/>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.RELEASE</spring-cloud.version>
<java.version>1.8</java.version>

<!-- swagger配置 必需!-->
<host>x.x.x.x</host>
<basepath>/</basepath>
<mainTitle>bar</mainTitle>
<mainVersion>v1</mainVersion>
<location>com.foo.bar.web</location>
<swaggerDirectory>${basedir}/target</swaggerDirectory>
<swaggerFileName>swagger</swaggerFileName>
</properties>

<!-- 可根据需要增加自己的依赖 -->
<dependencies>
<!--springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>

<!-- swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.foo.bar.Application</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<!-- swagger插件配置 必需!-->
<plugin>
<groupId>com.github.kongchen</groupId>
<artifactId>swagger-maven-plugin</artifactId>
<version>3.1.4</version>
<configuration>
<apiSources>
<apiSource>
<springmvc>true</springmvc>
<locations>
<location>${location}</location>
</locations>
<schemes>
<scheme>http</scheme>
<scheme>https</scheme>
</schemes>
<host>${host}</host>
<basePath>${basepath}</basePath>
<info>
<title>${mainTitle}</title>
<version>${mainVersion}</version>
</info>
<swaggerDirectory>${swaggerDirectory}</swaggerDirectory>
<swaggerFileName>${swaggerFileName}</swaggerFileName>
</apiSource>
</apiSources>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- jacoco插件 必需!-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<skip>false</skip>
<argLine>${surefireArgLine}</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.7.201606060606</version>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<destFile>
${project.build.directory}/coverage-reports/jacoco.exec
</destFile>
<propertyName>surefireArgLine</propertyName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

settings.xml配置示例

以下是一个使用nexus时的settings.xml配置文件示例

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
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

<pluginGroups></pluginGroups>
<proxies></proxies>

<servers>
<server>
<id>nexus-releases</id>
<username>admin</username>
<password>foobar</password>
</server>
<server>
<id>nexus-snapshots</id>
<username>admin</username>
<password>foobar</password>
</server>
</servers>

<mirrors>
<mirror>
<id>nexus-releases</id>
<mirrorOf>*</mirrorOf>
<url>http://x.x.x.x:8081/nexus/content/groups/public</url>
</mirror>
<mirror>
<id>nexus-snapshots</id>
<mirrorOf>*</mirrorOf>
<url>http://x.x.x.x:8081/nexus/content/groups/public-snapshots</url>
</mirror>
</mirrors>

<profiles>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>nexus-releases</id>
<url>http://nexus-releases</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>nexus-snapshots</id>
<url>http://nexus-snapshots</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>nexus-releases</id>
<url>http://nexus-releases</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>nexus-snapshots</id>
<url>http://nexus-snapshots</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>

<activeProfiles>
<activeProfile>nexus</activeProfile>
</activeProfiles>

</settings>

清理脚本

当maven下载依赖时,如果网络中断,会产生一些.lastUpdated文件,导致下次无法正常下载依赖,以下是一个简单的清理脚本

1
2
cd ~/.m2
find . -name "*.lastUpdated" -exec rm -rf {} \;

简介

tmon - 一个简陋的应用健康度检测系统,基于Python3。
请注意,这是一个重复的轮子,源于某项目的紧急救火过程中,需要快速搭建一套监控系统实时监控几套后台服务的需求。
相较于使用现成的开源系统,花时间进行技术选型、部署、修改源码等操作,重写一套系统花费的时间更少,定制性更高。
本系统不再继续完善,仅供学习目的使用,类以的,有更多更为成熟的开源应用,请自行搜索比较。

功能特性

  • 参数可配置化,可同时配置检测多个后台服务
  • 多线程并行检测
  • 支持邮件、短信两种通知方式
  • 防止重复通知机制、每日频繁通知融熔断机制

检测方式及流程

可配置检测多个服务,针对某一个单独的服务,分别启两个线程,一个用于服务调用测试(收集样本),另一个用于健康度检测(裁决)

服务调用测试线程:

  1. 每隔一定时间(默认5秒)向特定的URL发起一次GET或POST请求。
  2. 如果请求的返回结果不是HTTP 2XX,或直接请求失败,则向一个FIFO的测试结果样本池(默认20个)中新增一条失败记录,反之增加一条成功记录。

健康度检测线程:

  1. 每隔一定时间(默认20秒),统计样本池中失败的次数,如果大于阈值(默认2个),则发起告警通知,并休眠一定时间。反之,认为系统运行正常,不做任何操作,仅休眠一定时间。
  2. 告警通知分为短信和邮件两类,参数配置实现。
  3. 首先判断今日的通知次数是否大于阈值(10次),如果是,则进行熔化断操作,不予发送。避免每天发送过量的SPAM通知。
  4. 然后判断re_notify_tag(每次检测时,如果成功,则re_notify_tag会置为True,反之为False),如果为真才会发送通知。re_notify_tag用于避免系统宕机后,重复发送通知。

安装及运行

venv虚拟环境安装配置

1
2
3
sudo pip3 install virtualenv
virtualenv venv
. venv/bin/activate

第三方依赖安装

1
2
pip3 install -r requirements.txt

守护进程启动

1
nohup ./tmon.py 2>&1 &

GitLab源码

更多源码请移步GitLab:
http://git.si-tech.com.cn/guolei/tmon

简介

Spring3.0以下的遗留工程,由于无法使用RestTemplate,所以调用REST接口时会比较繁琐。
通过使用第三方REST户端框架Resty,可以大幅简化调用过程。
Resty的优势是使用简单,不需要引入过多的依赖。但相比于resttemplate/jersey/resteasy等,Resty还是过于小众,而且已有2年时间没有维护,异常处理也不是很完善。所以只建议在老旧项目中使用。
本文演示了Resty各种用法,包括代理设置、Header设置等。

添加依赖

对于较老的项目,直接下载并添加resty-0.3.2.jar

1
wget http://repo1.maven.org/maven2/us/monoid/web/resty/0.3.2/resty-0.3.2.jar

如果项目使用了maven或gradle,直接添加依赖即可

1
2
3
4
5
<dependency>
<groupId>us.monoid.web</groupId>
<artifactId>resty</artifactId>
<version>0.3.2</version>
</dependency>
1
compile 'us.monoid.web:resty:0.3.2'

示例

初始化

1
2
String baseUrl = "http://127.0.0.1:8080/foobar";
Resty resty = new Resty();

设置代理

1
resty.setProxy("127.0.0.1", 8888);

设置token

1
2
3
4
5
6
7
8
resty.setOptions(new Resty.Option() {
@Override
public void apply(URLConnection aConnection) {
aConnection.setRequestProperty("Authorization", "Bearer foobarboobarfoobar");
super.apply(aConnection);
}
});

GET 查询

1
2
3
String restUrl = baseUrl + "/users/4038488549360733";
JSONObject jsonObject = resty.json(restUrl).object();
String serialNumber = jsonObject.getString("serialNumber");

PUT 修改

1
2
3
String restUrl = baseUrl + "/users/4038488549360733";
String jsonStr = "{\"brandCode\":\"3G02\"}";
resty.json(restUrl, Resty.put(new Content("application/json", jsonStr.getBytes())));

POST 新增

1
2
3
4
String restUrl = baseUrl + "/users";
String jsonStr = "{\"brandCode\":\"3G03\"}";
resty.json(restUrl, new Content("application/json", jsonStr.getBytes()));

DELETE 删除

1
2
String restUrl = baseUrl + "/users/4038488549360733";
resty.json(restUrl, Resty.delete());

综合示例

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
String serialNumber = "12345678";
String restUrl;
JSONObject jsonObject;
JSONArray jsonArray;
String itemId = "";

try {
// 查询用户信息,通过serialNumber取到userId
restUrl = baseUrl + "/users?serialNumber=" + serialNumber;
jsonObject = resty.json(restUrl).object();
System.out.println(jsonObject);
String userId = jsonObject.getString("userId");

// 查询资费信息,通过userId取到所有资费
restUrl = baseUrl + "/discnts?userId=" + userId;
jsonArray = resty.json(restUrl).array();
for (int i = 0; i < jsonArray.length(); i++) {
jsonObject = jsonArray.getJSONObject(i);
System.out.println(jsonObject);
// 随机取一个itemId
itemId = jsonObject.getString("itemId");
}

// 更改资费信息,通过itemId修改资费信息
restUrl = baseUrl + "/discnts/" + itemId;
String jsonStr = "{\"startDate\":\"2001-01-01 20:01:02\", \"endDate\":\"2018-05-08 20:01:02\"}";
resty.json(restUrl, Resty.put(new Content("application/json", jsonStr.getBytes())));
} catch (Exception e) {
e.printStackTrace();
}

GitHub

https://github.com/beders/Resty