redis分布式锁实践

分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的

我们先来看一个无锁的情况下会导致什么问题:

这是一个普通的更新用户年龄的功能,各层代码如下,访问controller层,一个更新,一个查询

未分类

这是service层,我们使用contdownlatch发令枪来模拟线程同时并发的情况,发令枪设为32,即32个线程同时去请求修改年龄,

未分类

这里使用线程池来提交多线程任务,看代码知道,这里我们已经有了判断年龄的操作,当查询用户查询大于0时,才去调更新用户年龄-1的方法,等下看看有没有用

未分类

这里是sql,可以看到两个sql,一个查询用户年龄,一个会执行用户年龄每次减1 ,

未分类

这里是用户数据,我们可以看到,用户UID为UR12324的用户,他的年龄是30,接着我们来调32个线程来操作减他年龄

未分类

我们请求下这个方法

未分类

然后看看结果:

未分类

未分类

可以看到库中年龄已被减为-2,在未加锁的情况下,查询较验并没有什么作用,此时如果加个synchronized或lock锁肯定能避免这种情况,但我们本文讨论的是多实例或分布式环境中,此加锁方式仍然会产生问题,感兴趣的可以试下是不是

下面我们开始实现一个redis分布式锁,来避免这种情况发生,先说说实现思路:

1、线程请求访问前先调用加锁的方法,加锁就去里生成一个随机数同时保存在线程本地变量和redis的某key中,此key设有效期为200ms,具体值根据业务执行时间自行调整,加锁成功;

2、其它线程试着访问拿出它本地变量与redis中某key进行比较,如果不一致,则说明有锁,此线程休眠一段时间,再试着加锁;

3、加锁成功的线程在操作结束后删掉它持有锁(用lua实现,保证原子性,在它比对和删除锁的过程中,其它线程不会加锁成功),让其它线程再次加锁以执行任务;

说明:锁的时间为200ms可预防线程挂掉之后死锁,200ms后会自动释放

下面看看我们写的锁代码:

片段1:使用redislock 实现lock来复写它的方法

未分类

片段2:试着加锁的方法

未分类

片段3:解锁方法,此处首先从线程本地变量获取它的随机数,然后调用lua脚本,与redis中key相比较,如果相同则删除,否则返回0;

未分类

此为lua脚本方法,用此方法可以保证判断和删除的原子性,在此过程中没有线程可以操作此key

未分类

到此为止,我们锁基本写完,来测试下有没有用:

未分类

我们在此方法前后分别加入加锁和解锁方法,使用方式和lock锁一样, 我们重新把年龄恢复到30后来测试一下吧

先看看日志

未分类

这里可以看到各个线程争夺锁的情况,再看看执行结果

未分类

未分类

这里我们可以看到虽然是32个线程并发执行,但此值并不会变为负数,加锁成功.

我们可以看到最后2个线程并没有执行方法

未分类

未分类

在具体生产环境中,比如典型的用户余额扣减,我们可以用用户UID作KEY,这样就不会造成100个用户,500个线程争夺一个锁的情况发生,100个用户会有100个锁,此时假如每个用户5个请求,一个锁只处理5个线程

大大提高锁的效率.

此时说明加锁成功,大家可以在分布式环境中测试更明显,有关极端情况下解锁失败后应该做什么也可以由我们自己决定,比redission要灵活,带锁的redis最好是单实例,在集群中可能会出问题,有机会我们再用zk实现下.

redis数据迁移方案

一、主从复制

二、数据文件拷贝

1、copy dump.rdb 到目标主机dump.rdb存储目录下后启动redis服务

$ scp /var/lib/redis/dump.rdb user@ip:/var/lib/redis/dump.rdb
# 登录目标主机后重启redis服务
$ docker restart redis

2、启动aof模式,copy appendonly.aof 到目标主机 redis 数据存储目录下后

$ redis-cli config set appendonly yes
$ scp /var/lib/redis/appendonly.aof user@ip:/var/lib/redis/appendonly.aof
$ redis-cli config set appendonly no
# 登录目标主机后加载数据
$ docker restart redis

设置Redis最大占用内存

设置Redis最大占用内存

Redis需要设置最大占用内存吗?如果Redis内存使用超出了设置的最大值会怎样?

设置Redis最大占用内存

Redis设置最大占用内存,打开redis配置文件,找到如下段落,设置maxmemory参数,maxmemory是bytes字节类型,注意转换。修改如下所示:

# In short... if you have slaves attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for slave
# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes>
maxmemory 268435456

本机服务器redis配置文件路径:/etc/redis/6379.conf,由于本机自带内存只有1G,一般推荐Redis设置内存为最大物理内存的四分之三,所以设置0.75G,换成byte是751619276.

可以在CentOS下输入命令:find / -name redis查找redis目录:

[root@iZ94r80gdghZ ~]# find / -name redis
/usr/share/nginx/html/blog.tanteng.me/wp-content/cache/supercache/blog.tanteng.me/tag/redis
/etc/redis
/var/redis

Redis配置文件一般在etc下的redis安装目录下。

Redis使用超过设置的最大值

如果Redis的使用超过了设置的最大值会怎样?我们来改一改上面的配置,故意把最大值设为1个byte试试。

# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes>
maxmemory 1

打开debug模式下的页面,提示错误:OOM command not allowed when used memory > ‘maxmemory’.

设置了maxmemory的选项,redis内存使用达到上限。可以通过设置LRU算法来删除部分key,释放空间。默认是按照过期时间的,如果set时候没有加上过期时间就会导致数据写满maxmemory。

如果不设置maxmemory或者设置为0,64位系统不限制内存,32位系统最多使用3GB内存。

LRU是Least Recently Used 近期最少使用算法。

  • volatile-lru -> 根据LRU算法生成的过期时间来删除。
  • allkeys-lru -> 根据LRU算法删除任何key。
  • volatile-random -> 根据过期设置来随机删除key。
  • allkeys->random -> 无差别随机删。
  • volatile-ttl -> 根据最近过期时间来删除(辅以TTL)
  • noeviction -> 谁也不删,直接在写操作时返回错误。

如果设置了maxmemory,一般都要设置过期策略。打开Redis的配置文件有如下描述,Redis有六种过期策略:

# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key accordingly to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys-random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# noeviction -> don't expire at all, just return an error on write operations

那么打开配置文件,添加如下一行,使用volatile-lru的过期策略:

maxmemory-policy volatile-lru

保存文件退出,重启redis服务。

info命令查看Redis内存使用情况

如服务器Redis所在目录:/usr/local/redis-3.0.7/src

在终端输入./redis-cli,打开Redis客户端,输入info命令。

出来如下信息:

[root@iZ94r80gdghZ src]# ./redis-cli
127.0.0.1:6379> info
# Server
redis_version:3.0.7
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:f07a42660a61a05e
redis_mode:standalone
os:Linux 3.10.0-327.10.1.el7.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
gcc_version:4.8.5
process_id:2165
run_id:8ec8a8dc969d6e2f2867d9188ccb90850bfc9acb
tcp_port:6379
uptime_in_seconds:668
uptime_in_days:0
hz:10
lru_clock:15882419
config_file:/etc/redis/6379.conf

# Clients
connected_clients:1
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0

# Memory
used_memory:816232
used_memory_human:797.10K
used_memory_rss:7655424
used_memory_peak:816232
used_memory_peak_human:797.10K
used_memory_lua:36864
mem_fragmentation_ratio:9.38
mem_allocator:jemalloc-3.6.0

# Persistence
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
rdb_last_save_time:1458722327
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:-1
rdb_current_bgsave_time_sec:-1
aof_enabled:0
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok

# Stats
total_connections_received:1
total_commands_processed:0
instantaneous_ops_per_sec:0
total_net_input_bytes:14
total_net_output_bytes:0
instantaneous_input_kbps:0.00
instantaneous_output_kbps:0.00
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:0
migrate_cached_sockets:0

# Replication
role:master
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

# CPU
used_cpu_sys:0.30
used_cpu_user:0.29
used_cpu_sys_children:0.00
used_cpu_user_children:0.00

# Cluster
cluster_enabled:0

# Keyspace
db0:keys=1,expires=1,avg_ttl=425280

其中used_memory:816232,仅用了0.7M左右。

本文在网上查阅资料,在阿里云CentOS主机实践。

自动布署服务器环境,并利用 GIt 实现本地代码自动同步到服务器!

这里说的自动布署是两方面的,第一部分是脚本自动布署服务器环境,第二部份是自动布署代码,完成这两部分,在我理解,就算是完成自动部署了。

我们要做的,就是本地写完代码提交 git 后,使用git push 自动将代码推送到测试或生产环境的站点目录。 好的,开工吧!

LNMP线上环境自动布署脚本

使用 此脚本 可在一台全新的 Ubuntu 14.04 LTS 或者 Ubuntu 16 上自动部署适合 Laravel 使用的 LNMP 生产环境。 按照此 文档 安装即可。

但是此方法在使用中会有一些小问题

1、网易镜像加速后会出现一些安装错误 可能是网易镜像没有更新完全。 需要将网易镜像地址更新为其它地址,可以参考 Ubuntu 官方模版 来更新镜像。 我使用的是阿里云的镜像替换了网易的镜像。

vi /etc/apt/sources.list

更新为

deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
##測試版源
deb http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse
# 源碼
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
##測試版源
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse
# Canonical 合作夥伴和附加
deb http://archive.canonical.com/ubuntu/ xenial partner
deb http://extras.ubuntu.com/ubuntu/ xenial main

2、正常安装后Nginx 启动前需要把 apache 卸载掉,然后再启动 Nginx .

apt-get purge apache2
service nginx restart

3、redis 默认没有启动

service redis-server start  // 启动 redis
service redis-server status  // 查看 redis
service redis-server  stop  // 停止 redis

配置Git自动部署

创建Git远程仓库

我们用一个独立的路径来做远程仓库。 然后在仓库的路径下,创建一个git裸仓库:

cd /home/ubuntu/repo/
git init --bare blog.git

git 默认是禁止 push 的,所有要设置允许 push:

vi config

修改或添加如下内容:

[receive]
        denyCurrentBranch = ignore

编辑自动部署脚本

自动部署用到 git hooks ,在 git 路径下有个 hooks 文件夹,里面有一些示例。我们把 post-update.sample 重命名为 post-update ,并进行编辑:

mv post-update.sample post-update
vi post-update
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".

#exec git update-server-info

unset GIT_DIR

NowPath=`pwd`

DeployPath="/home/ubuntu/www/blog"

cd $DeployPath
git pull origin master
composer install

cd $NowPath

echo 'deploy success'
exit 0

这样每当 push master 分支到服务器时,都会自动切换到 DeployPath ,也就是 Nginx root 路径,执行 git pull origin master 从仓库拉去最新 master 分支,并执行 composer install ,如果项目 composer 有变更则安装,没有变更则不会安装。

部署路径初始化

先把空的仓库克隆到 home/ubuntu/www/ 路径下 :

git clone  /home/ubuntu/repo/blog.git

服务器的配置就级别完成了,此 blog 即为你的站点目录

推送git仓库

进入本地共享文件夹,执行

git clone root@server_ip:/home/ubuntu/repo/blog.git blog_back

将远程的空仓库克隆下来。名称为 blog_back,防止与本地 blog 目录冲突。 ok,现在我们来创建一个项目

本地创建项目并提交 Git

composer create-project --prefer-dist laravel/laravel blog
mv blog_back/.git  blog/.git  //将 .git 目录拷贝过来即可在此目录操作 git
cd  blog
git add -A
git commit -m 'init'
git push origin master //此时输入服务器密码即可将本地项目推送到服务器上

这里我使用的是默认的 root 用户,我们可以单独设置一个 git 用户,也可以使用公钥的形式,类似于 github 的方式。

安装 Composer

打开命令行并依次执行下列命令安装最新版本的 Composer:

php -r "copy('https://install.phpcomposer.com/installer', 'composer-setup.php');"
//下载安装脚本 - composer-setup.php - 到当前目录。
php composer-setup.php //执行安装过程。
php -r "unlink('composer-setup.php');"  //删除安装脚本

执行第一条命令下载下来的 composer-setup.php 脚本将简单地检测 php.ini 中的参数设置,如果某些参数未正确设置则会给出警告;然后下载最新版本的 composer.phar 文件到当前目录。 打开命令行窗口并执行如下命令将前面下载的 composer.phar 文件移动到 /usr/local/bin/ 目录下面:

sudo mv composer.phar /usr/local/bin/composer

进程监控器 Supervisor 配置

项目中有些脚本需要在后台运行,比如队列、Horizon ,Supervisor可以监控后台脚本的运行,再产生异常或是停止后自动重启,保证了脚本不会被异常中断。定时任备也可以直接用 Supervisor 来执行,Supervisor 还可以写入执行成功或失败的日志,方便查看。 此脚本默认安装了 supervisor ,我们只需设置好启动即可。

配置

在 /etc/supervisor/conf.d/ 下新建一个配置文件 horizon.conf,写入以下内容

[program:horizon]
process_name=%(program_name)s_%(process_num)02d
command=php /home/ubuntu/www/site/artisan horizon  //启动脚本命令。
autostart=true  //随着supervisord的启动而启动
autorestart=true //自动重启
user=ubuntu  // 用户组
numprocs=1 // 启动进程,根据脚本决定
redirect_stderr=true  //重定向stderr到stdout
stdout_logfile=/var/log/supervisor/horizon.log  //日志 注意日志目录的权限

接下来就可以启动 supervisord 了。

supervisord -c /etc/supervisord.conf  //启动
supervisorctl shutdown //关闭
supervisorctl reload  //重新载入配置

好了。到这里自动布署就完成了,接下来就是尽情的敲代码吧!

【Git学习笔记】用git pull取回远程仓库某个分支的更新,再与本地的指定分支自动merge

git pull的作用是,从远程库中获取某个分支的更新,再与本地指定的分支进行自动merge。完整格式是:

$ git pull <远程库名> <远程分支名>:<本地分支名>  

比如,取回远程库中的develop分支,与本地的develop分支进行merge,要写成:

git pull origin develop:develop  

如果是要与本地当前分支merge,则冒号后面的<本地分支名>可以不写。

git pull origin develop  

通常,git会将本地库分支与远程分支之间建立一种追踪关系。比如,在git clone的时候,所有本地分支默认与远程库的同名分支建立追踪关系。也就是说,本地的master分支自动追踪origin/master分支。因此,如果当前处于本地develop分支上,并且本地develop分支与远程的develop分支有追踪关系,那么远程的分支名可以省略:

git pull origin  

其实,git pull 命令等同于先做了git fetch ,再做了git merge。即:

git fetch origin develop  
git checkout develop  
git merge origin/develop  

好多人不建议使用git pull,喜欢自己merge,以便万一自动merge出错的时候可以解决冲突。

Git 更安全的强制推送,–force-with-lease

由于 git rebase 命令的存在,强制将提交推送到远端仓库似乎也有些必要。不过都知道 git push –force 是不安全的,这让 git rebase 命令显得有些鸡肋。

本文将推荐 –force-with-lease 参数,让我们可以更安全地进行强制推送。

–force-with-lease 参数自 Git 的 1.8.5 版本开始提供,只在解决 git push –force 命令造成的安全问题。

那么 git push –force 命令有什么安全问题?

–force 会使用本地分支的提交覆盖远端推送分支的提交。也就是说,如果其他人在相同的分支推送了新的提交,你的这一举动将“删除”他的那些提交!就算在强制推送之前先 fetch 并且 merge 或 rebase 了也是不安全的,因为这些操作到推送之间依然存在时间差,别人的提交可能发生在这个时间差之内。

–force-with-lease 将解决这种安全问题

使用了 –force-with-lease 参数之后,上面那种安全问题就没有那么危险了。

使用此参数推送,如果远端有其他人推送了新的提交,那么推送将被拒绝,这种拒绝和没有加 –force 参数时的拒绝是一样的。

walterlv$ git push --force-with-lease
To https://github.com/walterlv/walterlv.github.io.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'https://github.com/walterlv/walterlv.github.io.git'

请特别注意——如果你 fetch 之后在本地的 origin 相关分支上已经看到了别人的提交,依然进行强制推送,你还是会覆盖别人的提交。也就是说,–force-with-lease 解决的是本地仓库不够新时,依然覆盖了远端新仓库的问题,如果你执意想要覆盖远端提交,只需要先 fetch 再推送,它也不会拒绝的。

在使用 git push –force-with-lease 命令被拒绝时,你需要 fetch 仓库,然后确认其他人是否对此分支有新的修改,如果没有,你才可以继续强制推送。

walterlv$ git fetch
remote: Counting objects: 46, done.
remote: Compressing objects: 100% (29/29), done.
remote: Total 46 (delta 21), reused 40 (delta 15), pack-reused 0
Unpacking objects: 100% (46/46), done.
From https://github.com/walterlv/walterlv.github.io
   e75edf0..217a49d  master     -> origin/master

在 fetch 完毕之后,请一定检查此分支是否已经被其他人修改,如果有新的提交,你应该进行一次 merge 或者 rebase。

walterlv$ git rebase
First, rewinding head to replay your work on top of it...
Applying: Add post "safe push using force with lease".

此后,再次进行推送或强制推送即可。

walterlv$ git push --force-with-lease
Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 363 bytes | 363.00 KiB/s, done.
Total 4 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To https://github.com/walterlv/walterlv.github.io.git
   219a6d5..dff94a5  master -> master

额外的问题:为什么推送到远端的提交还依然要用 rebase?

Git 官方文档对 rebase 有如下描述:

未分类

▲ 如果你想吐槽那段中文翻译,我只想说——那是 Git 的官方中文文档

既然已经推送的提交不应该再进行 rebase,那本不应该会遇到本文提到的问题。但是——GitHub 的工作流或者 GitLab 的工作流中,都有一种行为是 rebase 自己的分支到 origin/master 上,以保证 master 分支上的提交是纯粹的干净的。也就是说,本意是禁止对合并到 master 或 develop 分支上的提交进行 rebase;但对于自己的 temp 分支或者 feature 分支,因为提交还没有合并到主干中,随时删除掉或者将历史进行美化也不会造成太大的问题。

未分类

▲ 这是 GitLab 上的设置,可以要求提交者必须进行 rebase 才允许合并。

Git 在不同的项目使用不同的author

安装好 Git 的时候,每个人都会设置全局的账户:

git config --global user.name "laixintao"
git config --global user.email "[email protected]"

公司的 Git 账户和个人的 Git 账户不一样,可以在项目中设置此项目的 Author :

git config user.name "laixintao"
git config user.email "[email protected]"

这样私人账户每次都要设置,可以设置一条 Git 的 alias ,这样每次都用这一条命令就可以了。

git config --global alias.private 'config user.email "[email protected]"'

其实 git config –global 命令是修改的一个文件,$HOME/.gitconfig 例如我的文件如下:

[core]
        editer = "/usr/local/bin/vim"
        editor = vim
        excludesfile = ~/.gitignore_global
[user]
        name = laixintao
        email = [email protected]
        username = laixintao
[alias]
        pr = pullrequest
        ck = checkout
        br = branch
        lg = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
[merge]
        tool = vimdiff
        conflictstyle = diff3
[pull]
    rebase = true
[mergetool]
        prompt = false
[github]
        user = [email protected]
[filter "lfs"]
        process = git-lfs filter-process
        required = true
        clean = git-lfs clean -- %f
        smudge = git-lfs smudge -- %f

同之前提到的 myrc 项目一样,gitconfig 这个文件我也是用 git 来追踪的,去一个新环境只要安装好这个项目就可以回到自己熟悉的 git 了。

zabbix 监控 Resin

一、环境介绍

服务器信息:
zabbix-server:172.16.4.6
zabbix-java-gateway:172.16.0.7
Resin:172.16.0.6

软件环境:
 zabbix版本:Zabbix server v2.4.6
 zabbix-java-gateway:zabbix-java-gateway-2.4.6-1.el6.x86_64.rpm
 测试jar:cmdline-jmxclient-0.10.3.jar

二、安装zabbix

zabbix安装可以参考zabbix官网介绍,这里就不在详细说明了,参考如下网址:

https://www.zabbix.com/documentation/2.4/manual/installation/install_from_packages

zabbix-java-gateway下载地址:

http://www.zabbix.com/download.php

zabbix-java-gateway官网安装介绍:

https://www.zabbix.com/documentation/2.4/ru/manual/concepts/java?s[]=java

测试jar下载地址:(需要翻。。。)

http://crawler.archive.org/cmdline-jmxclient/cmdline-jmxclient-0.10.3.jar

三、安装zabbix-java-gateway网关

安装

# rpm -ihv zabbix-java-gateway-2.4.6-1.el6.x86_64.rpm 
Preparing...                ########################################### [100%]
   1:zabbix-java-gateway    ########################################### [100%]

修改配置文件:

# grep  -Ev "(^#|^$)" /etc/zabbix/zabbix_java_gateway.conf 
LISTEN_IP="172.16.0.7"   \zabbix-java-gateway 网关监听地址
LISTEN_PORT=10052        \zabbix-java-gateway  网关监听端口
PID_FILE="/var/run/zabbix/zabbix_java.pid" \zabbix-java-gateway 的pid文件路径
START_POLLERS=5    \启动5个线程
TIMEOUT=10        \超时时长

启动zabbix-java-gateway网关

# /etc/init.d/zabbix-java-gateway start

检查zabbix-java-gateway网关是否启动成功

# netstat -tpln | grep 10052
tcp        0      0 172.16.0.7:10052            0.0.0.0:*                   LISTEN      6976/java

四、配置Resin,启用JMX

在Resin家目录下的conf/目录中,修改resin.conf配置文件,添加一下信息:

可以参考官网介绍:http://caucho.com/resin-4.0/admin/advanced-jmx.xtp

      <jvm-arg>-Dcom.sun.management.jmxremote</jvm-arg>
      <jvm-arg>-Dcom.sun.management.jmxremote.port=12345</jvm-arg>
      <jvm-arg>-Dcom.sun.management.jmxremote.ssl=false</jvm-arg>
      <jvm-arg>-Dcom.sun.management.jmxremote.authenticate=false</jvm-arg>
      <jvm-arg>-Djava.rmi.server.hostname=172.16.0.6</jvm-arg> \此项可选

在resin中添加完Resin后,重启启动resin

# /etc/init.d/resin stop
# /etc/init.d/resin start

在zabbix-server端启用zabbix-java-gateway选项:

# grep -Ev "(^#|^$)" /etc/zabbix/zabbix_server.conf | grep -i java
JavaGateway=172.16.0.7 \zabbix-java-gateway的地址
JavaGatewayPort=10052   \zabbix-java-gateway的端口号
StartJavaPollers=5       \启动5个线程

五、在zabbix-java-gateway服务器上,利用cmdline-jmxclient-0.10.3.jar获取resin服务器的key

获取key

# java -jar cmdline-jmxclient-0.10.3.jar  - 172.16.0.6:12345 | sort
com.alibaba.druid:id=1924372698,type=DruidDataSource
com.alibaba.druid:type=DruidDataSourceStat
com.alibaba.druid:type=DruidDriver
com.alibaba.druid:type=DruidStatService
com.alibaba.druid:type=MockDriver
com.sun.management:type=HotSpotDiagnostic
java.lang:name=CMS Old Gen,type=MemoryPool
java.lang:name=CMS Perm Gen,type=MemoryPool
java.lang:name=CodeCacheManager,type=MemoryManager
java.lang:name=Code Cache,type=MemoryPool
java.lang:name=ConcurrentMarkSweep,type=GarbageCollector
java.lang:name=Par Eden Space,type=MemoryPool
java.lang:name=ParNew,type=GarbageCollector
java.lang:name=Par Survivor Space,type=MemoryPool
java.lang:type=ClassLoading
java.lang:type=Compilation
java.lang:type=Memory
java.lang:type=OperatingSystem
java.lang:type=Runtime
java.lang:type=Threading
java.util.logging:type=Logging
JMImplementation:type=MBeanServerDelegate
resin:Host=www.magedu.com,name=deploy,type=EarDeploy
resin:Host=www.magedu.com,name=deploy,type=ResourceDeploy
resin:Host=www.magedu.com,name=/resin-doc,type=WebApp
resin:Host=www.magedu.com,name=/solr,type=WebApp
resin:Host=www.magedu.com,name=/,type=WebApp
resin:Host=www.magedu.com,name=webapps,type=WebAppDeploy
resin:Host=www.magedu.com,WebApp=/,name=/data/web/www.magedu.com/WEB-INF/rewrite.xml,type=RewriteImport
resin:Host=www.magedu.com,WebApp=/resin-doc,name=jdbc/resin,type=ConnectionPool
resin:Host=www.magedu.com,WebApp=/resin-doc,name=jdbc/resin,type=JdbcDriver
resin:Host=www.magedu.com,WebApp=/resin-doc,name=/resin-doc/examples/amber-basic-field,type=WebApp
resin:Host=www.magedu.com,WebApp=/resin-doc,name=/resin-doc/examples/amber-basic,type=WebApp
resin:Host=www.magedu.com,WebApp=/resin-doc,name=/resin-doc/examples/amber-create,type=WebApp
resin:Host=www.magedu.com,WebApp=/resin-doc,name=/resin-doc/examples/amber-inherit,type=WebApp
resin:Host=www.magedu.com,WebApp=/resin-doc,name=/resin-doc/examples/amber-many2many,type=WebApp
resin:Host=www.magedu.com,WebApp=/resin-doc,name=/resin-doc/examples/amber-many2one,type=WebApp
resin:Host=www.magedu.com,WebApp=/resin-doc,name=/resin-doc/examples/amber-one2many,type=WebApp
resin:Host=www.magedu.com,WebApp=/resin-doc,name=/resin-doc/examples/amber-query,type=WebApp
resin:Host=www.magedu.com,WebApp=/resin-doc,name=/resin-doc/examples/amber-session,type=WebApp
。。。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。。\此处省略一些信息
resin:name=127.0.0.1-6800,type=Port
resin:name=app-tier,type=Cluster
resin:name=hosts,type=HostDeploy
resin:name=INADDR_ANY-8002,type=Port
resin:name=www.magedu.com,type=Host
resin:type=BlockManager
resin:type=PersistentStore
resin:type=ProxyCache
resin:type=Resin
resin:type=Server
resin:type=ThreadPool

获取value

# java -jar cmdline-jmxclient-0.10.3.jar  - 172.16.0.6:12345  "resin:type=ThreadPool" \利用此key可以获取此key中的多个属性,然后利用属性获取值
Attributes:
 Name: The name property of the JMX ObjectName (type=java.lang.String)
 ObjectName: The JMX ObjectName for the MBean (type=javax.management.ObjectName)
 ThreadActiveCount: The current number of active threads (type=int)
 ThreadCount: The current number of managed threads (type=int)
 ThreadIdleCount: The current number of idle threads (type=int)
 ThreadIdleMax: The configured maximum number of idle threads (type=int)
 ThreadIdleMin: The configured minimum number of idle threads (type=int)
 ThreadMax: The configured maximum number of threads (type=int)
 Type: The type property of the JMX ObjectName (type=java.lang.String)
# java -jar cmdline-jmxclient-0.10.3.jar  - 172.16.0.6:12345  "resin:type=ThreadPool"  ThreadMax  \利用"resin:type=ThreadPool" 获取ThreadMax的值
04/01/2016 18:29:58 +0800 org.archive.jmx.Client ThreadMax: 15000

六、作为zabbix使用的key、value都获取到以后,我们接下来制作zabbix模板

创建一个名为Resin的模板

未分类

添加Applications;以下是我自己添加的;大家可以根据自己的需求添加:

未分类

添加完Applications以后,在添加item,在这里以MemoryPool_Eden为例,添加item

未分类

添加完item以后,在添加图形;以下是我自己添加的绘图信息:

未分类

到此模板制作完成,现在开始在主机中关联模板:

未分类

添加完后,可以看到此主机的JMX变为绿色:

未分类

到此我们可以静心心来,查看绘制的图形信息了

未分类

到此,监控已经完成,如有做的不好的地方,请大家帮忙指正,O(∩_∩)O谢谢

使用vsftpd搭建显式SSL/TLS加密的FTP服务器(FTPS)

最近在Hostens购买了立陶宛的大硬盘小鸡,用于存放备份数据,因此需要在服务器上搭建FTP,而通过普通FTP协议在公网传输数据具有一定的安全隐患,因此需要为FTP服务器配置SSL来对传输的数据进行加密。

服务端软件:vsftpd

以下是/etc/vsftpd/vsftpd.conf文件的SSL配置部分

vsftpd的基础及虚拟用户配置:

https://lonelyboy.org/post/34.html

使用FTP over SSL必须拥有SSL证书,证书获取方法可以使用openssl自签署或者通过letsencrypt的certbot获取证书。

#SSL配置部分

ssl_enable=YES

allow_anon_ssl=NO

force_local_data_ssl=YES

force_local_logins_ssl=YES

force_anon_logins_ssl=YES

force_anon_data_ssl=YES

ssl_tlsv1=YES

ssl_sslv2=NO

ssl_sslv3=NO

require_ssl_reuse=NO

ssl_ciphers=HIGH

rsa_cert_file=服务器证书存放目录

rsa_private_key_file=服务器证书对应的私钥存放目录

使用Docker Compose部署python应用

这篇文章我们通过Docker Compose来运行部署一个简单的Python web应用,我们将使用Flask框架和Redis。

预备的东西

  • 安装好Docker
  • 安装好Docker Compose

定义应用的依赖

1、创建项目目录

mkdir composetest
cd composetest

2、创建应用文件app.py

在项目根目录composetest下创建并打开app.py文件,编写如下代码:

import time

import redis
from flask import Flask


app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)


def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)


@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.n'.format(count)

if __name__ == "__main__":
    app.run(host="0.0.0.0", debug=True)

这里,redis是应用网络上edis容器的主机名,我们使用Redis的默认端口:6379。

3、创建应用requirements.txt文件

在项目根目录composetest下创建requirements.txt文件,编辑文件:

flask
redis

创建Dockerfile文件

我们写个Dockerfile文件来构建Docker镜像,该镜像会包含该Python应用的所有依赖,包括Python本身。

在项目根目录composetest下创建Dockerfile文件并编辑文件:

FROM python:3.4-alpine
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

该文件告诉Docker完成以下工作:

  • 构建一个以Python 3.4镜像为基础镜像的镜像.
  • 把当前目录 . 复制到镜像的 /code 路径下.
  • 设置工作目录为 /code .
  • 安装Python依赖.
  • 设置容器的默认命令.

在Compose文件定义服务

在项目根目录composetest下创建docker-compose.yml文件并编辑:

version: '3'
services:
  web:
    build: .
    ports:
     - "5000:5000"
  redis:
    image: "redis:alpine"

该Compose文件定义两个服务:web和redis。该web服务将

  • 使用从Dockerfile文件构建的镜像.
  • 暴露容器的5000端口映射到宿主机的5000端口。我们使用Flask的web服务器默认端口 5000.

redis服务使用从Docker Hub registry仓库拉取的公有Redis镜像。

使用Compose构建和运行app

在项目根目录composetest下启动运行应用:

$ docker-compose up
Starting composetest_web_1   ... done
Starting composetest_redis_1 ... done
Attaching to composetest_redis_1, composetest_web_1
redis_1  | 1:C 05 May 02:38:07.354 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis_1  | 1:C 05 May 02:38:07.354 # Redis version=4.0.9, bits=64, commit=00000000, modified=0, pid=1, just started
redis_1  | 1:C 05 May 02:38:07.354 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
redis_1  | 1:M 05 May 02:38:07.355 * Running mode=standalone, port=6379.
redis_1  | 1:M 05 May 02:38:07.355 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
redis_1  | 1:M 05 May 02:38:07.355 # Server initialized
redis_1  | 1:M 05 May 02:38:07.355 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
redis_1  | 1:M 05 May 02:38:07.355 * DB loaded from disk: 0.000 seconds
redis_1  | 1:M 05 May 02:38:07.355 * Ready to accept connections
web_1    |  * Serving Flask app "app" (lazy loading)
web_1    |  * Environment: production
web_1    |    WARNING: Do not use the development server in a production environment.
web_1    |    Use a production WSGI server instead.
web_1    |  * Debug mode: on
web_1    |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
web_1    |  * Restarting with stat
web_1    |  * Debugger is active!
web_1    |  * Debugger PIN: 320-444-115
web_1    | 172.18.0.1 - - [05/May/2018 02:38:11] "GET / HTTP/1.1" 200 -

Compose拉取Redis镜像,为我们的应用构建一个镜像,并开始我们定义的服务。这种情况,代码在构建时是被静态地复制到镜像里。

打开浏览器,输入http://0.0.0.0:5000/, 会看到页面输出:

Hello World! I have been seen 1 times.

刷新页面,这里的数字会顺序递增

Hello World! I have been seen 2 times.

打开另一个terminal窗口,输入docker image ls可以看到本地的镜像列表,列表中会看到redis和web:

$ docker image ls
REPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
composetest_web                  latest              88cd35029579        12 hours ago        94.9MB
springio/gs-spring-boot-docker   latest              6c173fe17a59        26 hours ago        118MB
python                           3.4-alpine          42756b6e26a0        2 weeks ago         83.6MB
hello-world                      latest              e38bc07ac18e        3 weeks ago         1.85kB
redis                            alpine              98bd7cfc43b8        5 weeks ago         27.8MB
openjdk                          8-jdk-alpine        224765a6bdbe        3 months ago        102MB

要停止应用,可以输入docker-compose down命令或者按CTRL + C。

修改Compose文件

编辑docker-compose.yml,为web服务添加一个bind mount:

version: '3'
services:
  web:
    build: .
    ports:
     - "5000:5000"
    volumes:
     - .:/code
  redis:
    image: "redis:alpine"

新添加的 volume 键将宿主机的项目目录挂载到容器里的 /code 目录,这样我们可以即时地修改代码,而不用重新构建镜像。

重新构建和运行应用

项目根目录下输入 docker-compose up 重新构建并运行修改Compose文件后的应用

$ docker-compose up
Creating network "composetest_default" with the default driver
Creating composetest_web_1 ...
Creating composetest_redis_1 ...
Creating composetest_web_1
Creating composetest_redis_1 ... done
Attaching to composetest_web_1, composetest_redis_1
web_1    |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
...

刷新浏览器,会看到计数依然会递增。

更新应用

现在项目代码已经通过 volume 被挂载到容器,我们可以修改代码并即时看到变化,而不用重新构建镜像。我们编辑修改下 app.py文件,比如将 Hello World! 修改为 Hello from Docker!:

return 'Hello from Docker! I have been seen {} times.n'.format(count)

刷新浏览器,页面会相应地更新

未分类

docke-compose其他命令

在后台运行服务:

$ docker-compose up -d

查看当前运行服务:

$ docker-compose ps

查看web服务在使用的环境变量:

$ docker-compose run web env

停止服务:

$ docker-compose stop

停止并删除容器、网络、镜像和volumes卷

$ docker-compose down --volumes