CentOS7上搭建FTP服务+VSFTPD配置全解析

FTP 服务

   FTP是早期的应用级协议之一,是一种基于C/S结构的双通道协议。

   可以作为FTP服务器的软件有很多,比如Wu-ftpd,Proftpd,Pureftpd,ServU,IIS,都可以。其中vsftpd是CentOS默认的FTP服务器。也是我们今天的主角,接下来将会进行重点介绍。   既然是一种C/S结构的应用,有服务端就会有客户端。在Linux平台上可以作为FTP客户端的软件也有很多,诸如ftp,lftp,lftpget,wget,curl等。在Windows上也有很多的图形界面客户端,例如 filezilla,CuteFtp,FlashFXP,LeapFtp 等。

FTP 用户认证

FTP 的用户一共有三种类型

  • 匿名用户:ftp,anonymous,对应Linux用户ftp
  • 系统用户:Linux用户,用户/etc/passwd,密码/etc/shadow
  • 虚拟用户:特定服务的专用用户,独立的用户名/密码文件

VSFTPD服务

  • vsftpd 服务,由vsftpd包提供,直接使用yum安装就可以。
  • 用户认证配置文件:/etc/pam.d/vsftpd
  • 服务脚本: /usr/lib/systemd/system/vsftpd.service,/etc/rc.d/init.d/vsftp
  • 配置文件:/etc/vsftpd/vsftpd.conf
  • 匿名用户(映射为系统用户ftp )共享文件位置:/var/ftp
  • 系统用户共享文件位置:用户家目录
  • 虚拟用户共享文件位置:为其映射的系统用户的家目录

VSFTPD服务配置

vsftpd 服务配置 大多数都在配置文件/etc/vsftpd/vsftpd.conf中,所以我们的大部分配置只要修改这个文件就可以。

查看帮助文档

可以使用man命令查看配置文件的帮助文档,查看有哪些选项是我们需要的,以及各种值分别代表什么含义。

man 5 vsftpd.conf

命令端口

ftp默认端口是21端口,如果配置文件中没有明确指定的话,就是21端口。当然也可以明确指定,或者指定为别的端口。在配置文件中添加下面这一行就可以。

listen_port=21

主动模式端口

ftp 的链接方式有两种,分别是主动模式和被动模式。这两种模式都是从服务器的角度来查看和定义的。

# 这这里的选项一直定义为YES就可以。
# 如果不写这一行,默认的也是YES
connect_from_port_20=YES 

# 如果要重新定义主动模式的端口为其他的值的话,那就再加上下面行,并指定端口值
ftp_data_port=2020 

被动模式端口范围

linux客户端默认使用被动模式,windows 客户端默认使用主动模式。在配置文件中加入下面两行,代表可以定义被动模式的端口范围。

pasv_min_port=6000 
pasv_max_port=6010

# 如果这个属性指定为0的话,那么就随机分配
pasv_min_port=0 

使用当地时间

设置是否使用当地时间。默认就是YES.

use_localtime=YES

匿名用户

是否支持匿名用户

# YES 支持匿名用户
anonymous_enable=YES

匿名用户略过口令检查,默认为no。如果为YES的时候,匿名用户登陆时,不会提示用户名和密码,直接登陆。如果为NO的话,匿名用户登陆时还是会提示输入密码,只不过,系统不会再去验证密码的正确性。系统默认的匿名账户有两个,分别是ftp和anonymous。

no_anon_password=YES

匿名上传。首先,文件系统上用户的家目录,要具备写权限。其次,ftp服务器要开启允许匿名上传的配置。也就是说,ftp用户的家家目录需要进行配置权限。

anon_upload_enable=YES 

匿名用户能够创建目录。这一点与匿名上传一样,不仅需要注意服务的配置,同时需要注意用户的家目录权限。

anon_mkdir_write_enable=YES

默认只能下载全部读的文件。这句话的意思是说,如果启动了这句话,则匿名用户只能下载所有用户都具备读权限的文件。如果有一个文件,有一个用户不剧本读权限,匿名用户就不能够下载。

anon_world_readable_only=YES

匿名用户能否删除和修改上传的文件。默认是no,也就是不可以。

anon_other_write_enable=YES

指定匿名上传文件的umask值。umask的值可以直接影响到上传文件的权限。

anon_umask=077 

指定上传文件的默认的所有者和权限。 完成了这些指定之后,匿名用户上传的元数据就是我们指定的那些内容。

chown_uploads=YES(默认NO)
chown_username=wang
chown_upload_mode=0644

系统用户

默认情况下,操作系统的账户是可以直接使用用户名和密码来登陆的。并且登陆成功之后,默认进入到了自己的家目录。
所有系统用户都映射成guest用户,但是映射的guest账户,需要人为地去指定下。同时这个guest账户与匿名账户类似,不允许家目录具有写权限,否则容易导致安全隐患。

guest_enable=YES

#配合上面选项才生效,指定guest用户
guest_username=ftp 

是否允许Linux用户登陆,默认是允许的,当然也可以禁止。

local_enable=YES 

是否允许Linux用户上传文件,如果用户默认允许登陆的话,linux用户登陆成功之后,默认位于自己的家目录,这时是允许上传文件的。 如果将下面的这句话,改成no,则服务拒绝linux用户上传文件,即便位于自己的家目录也不可以。

write_enable=YES

指定系统用户上传文件的默认权限

local_umask=022

非匿名用户登录所在目录,当使用Linux用户登陆成功之后,就不会默认在自己的家目录了。相反,会位于下面指定的目录里。

local_root=/ftproot

禁锢所有系统用户在家目录中

禁锢所有的用户在家目录中的意思是说,用户登陆成功之后,不能够随意切换目录,只能够在自己的家目录中进行操作。 服务默认是NO,也就是不禁锢,这样的话,有比较大的安全隐患。

chroot_local_user=YES

禁锢或者不禁锢特定的用户

可以针对某一些用户进行单独的设置,例如对某一些用户进行禁锢,而对另外一些用户进行禁锢。
在/etc/vsftpd/chroot_list文件中,指定需要做出特殊处理的用户。当chroot_list_enable选项为YES的时候,列表中的用户不被禁锢。而不在列表中的用户则被禁锢。反之,则相反。

chroot_list_enable=YES
chroot_list_file=/etc/vsftpd/chroot_list

wu-ftp日志:默认启用

wu-ftp日志,默认安装,默认启用。

xferlog_enable=YES (默认)启用记录上传下载日志
xferlog_std_format=YES (默认)使用wu-ftp日志格式
xferlog_file=/var/log/xferlog (默认)可自动生成

vsftpd日志:默认不启用

vsftpd日志,默认不启用,如果想要启用的话,应该手动进行配置修改。

#使用vsftpd日志格式,默认不启用
dual_log_enable=YES 
#(默认)可自动生成
vsftpd_log_file=/var/log/vsftpd.log

登录提示信息

登陆提示信息图形界面是看不到的,只适用于ftp作为客户端的时候。可以使用下面的方式来进行配置。但是优先级却不一样。

ftpd_banner=“welcome to mage ftp server"

# 这一句话优先生效
banner_file=/etc/vsftpd/ftpbanner.txt 

目录访问提示信息

当用户进入到某一个目录之后,可以给用户一个提示消息。用来提示这个目录的作用。在相应的目录下建立一个隐藏文件 .message,在该文件中进行信息提示描述。

dirmessage_enable=YES (默认)
message_file=.message(默认)

使用pam(Pluggable Authentication Modules)完成用户认证

pam模块是Linux中的一种安全验证机制。所以vsftpd也可以根据pam模块进行用户的认证。 pam的配置文件/etc/pam.d/vsftpd,在这个文件中指定了默认文件/etc/vsftpd/ftpusers 默认文件中用户拒绝登录. 而vaftpd配置文件中,定义了pam模块。

pam_service_name=vsftpd

是否启用控制用户登录的列表文件

在黑名单中的用户,连密码提示都没有。

# 默认有此设置
# YES的时候启用了userlist
userlist_enable=YES

# YES(默认值)黑名单,不提示口令,NO为白名单
userlist_deny=YES

userlist_file=/etc/vsftpd/users_list 此为默认值

连接限制

链接限制,可以指定链接用户的最大数量,也就是最大的并发链接数。

# 最大并发连接数
# 如果为0的话,默认不限制
max_clients=0 

# 每个IP同时发起的最大连接数
# 如果为0的话,则默认不限制数量
max_per_ip=0 

vsftpd服务指定用户身份运行

vsftpd服务启动时要开启相应的进程,那么相应的进程就有一定的用户身份。如果指定了,那么服务启动之后,就会以指定的用户来启动服务进程。

nopriv_user=nobody

限定传输速率:字节/秒
如果在上传和下载的过程中,没有对速率进行限制,很容易对服务器造成损坏。所以最好进行一下限定。注意单位是 字节

# 匿名用户的最大传输速率
# 如果为0的话,则默认不限制
anon_max_rate=0 

# 本地用户的最大传输速率
# 如果为0的话,则默认不限制
local_max_rate=0 

限定链接时间:秒

指定用户连接到ftp服务器上的时间。时间单位是秒,这样做的好处是,如果用户连接到服务器之后,却不做任何操作的话,很容易浪费资源。这样是一种浪费。

# 主动模式数据连接超时时长
connect_timeout=60
# 被动模式数据连接超时时长
accept_timeout=60 
# 数据连接无数据输超时时长
data_connection_timeout=300 
# 无命令操作超时时长
idle_session_timeout=60 

优先以文本方式传输

文件在传输的过程中,可以指定是以文本方式传输,还是以二进制的方式传输。默认是以二进制的形式进行传输。 这里有一点需要注意,如果指定了以文本方式传输的话,Linux平台和Windows平台对文本的处理方式是不一样的,打个比方说,Linux平台上回车符是一个字节,但是在Windows的平台上是两个字节,也就是CRLF,这样也就导致,如果在Windows平台上使用客户端从Linux上FTP服务器下载文件,Windows会自动对文本进行转化的,反之也是一样。

如果我们传输一个二进制文件的话,二进制文件应该就会被破坏掉。

ascii_upload_enable=YES
ascii_download_enable=YES

配置FTP服务以非独立服务方运行:listen=NO,默认为独立方式

FTP服务既可以作为独立服务运行,也可以作为非独立服务进行运行。所谓非独立服务,指的就是使用xinetd服务代替FTP服务来监听21端口,当有用户访问FTP服务的时候,xinetd服务就会自动启动FTP服务,并提供给用户访问。

listen=YES,表示以独立的服务运行,listen=no表示以非独立的服务进行运行。

cat /etc/xinetd.d/vsftpd
service ftp
{
flags = REUSE
socket_type = stream
wait = no
user = root
server = /usr/sbin/vsftpd
log_on_failure += USERID
disable = no
}

实现基于SSL的FTPS

FTP服务,默认用户登陆时的口令是明文的。这样很容易被获取到,那么vsftpd服务是否支持SSL来进行加密呢?

查看是否支持SSL

# 使用下面的命令能够查看到是否支持SSL
# 查看到libssl.so
ldd `which vsftpd` 

创建自签名证书

# 进入到证书路径
cd /etc/pki/tls/certs/
# 使用下面的命令生成证书私钥和证书
make vsftpd.pem

openssl x509 -in vsftpd.pem -noout –text

配置vsftpd服务支持SSL:/etc/vsftpd/vsftpd.conf

#  启用SSL
ssl_enable=YES 
#  匿名不支持SSL
allow_anon_ssl=NO 
#  本地用户登录加密
force_local_logins_ssl=YES 
#  本地用户数据传输加密
force_local_data_ssl=YES
#  指定证书路径
rsa_cert_file=/etc/pki/tls/certs/vsftpd.pem

vsftpd虚拟用户

虚拟用户:

所有虚拟用户会统一映射为一个指定的系统帐号:访问共享位置,即为此系统帐号的家目录。

各虚拟用户可被赋予不同的访问权限,通过匿名用户的权限控制参数进行指定。

虚拟用户帐号的存储方式:

文件:编辑文本文件,此文件需要被编码为hash格式

奇数行为用户名,偶数行为密码

db_load -T -t hash -f vusers.txt vusers.db

  
关系型数据库中的表中:

实时查询数据库完成用户认证

mysql库:pam要依赖于pam-mysql

/lib64/security/pam_mysql.so
/usr/share/doc/pam_mysql-0.7/README

实现基于文件验证的vsftpd虚拟用户

1、创建用户数据库文件

# 创建用户文件
vim /etc/vsftpd/vusers.txt

ftp1    用户名
ftp1pass  密码
tom     用户名
tompass 密码


# 进入到相应的目录下
cd /etc/vsftpd/

# 创建数据库文件
db_load -T -t hash -f vusers.txt vusers.db

# 修改数据库权限
chmod 600 vusers.db

2、创建系统用户和访问FTP目录

虚拟用户访问FTP服务器的时候,要进入到自己的家目录下,但是系统中没有虚拟账户所对应的账号家目录,所以我们要创建一个系统用户,与虚拟账户关联起来,这样,当虚拟用户登陆之后,就会进入到我们创建的系统用户的家目录,然后进行数据访问。

对于CentOS7,家目录有写权限,用户在登陆的时候会出错的。所以要做一些特殊处理

# 创建系统用户并指定家目录
useradd -d /var/ftproot -s /sbin/nologin vuser
# 修改家目录权限
chmod +rx /var/ftproot/  

#CentOS 7 中要做出一些特殊的配置 

# 去掉家目录写权限
chmod -w /var/ftproot/

mkdir /var/ftproot/upload
setfacl -m u:vuser:rwx /var/ftproot/upload

3、创建pam配置文件

修改vsftpd的pam模块的配置文件 /etc/pam.d/vsftpd.db ,让vsftpd支持pam模块进行身份验证。

auth required pam_userdb.so db=/etc/vsftpd/vusers
account required pam_userdb.so db=/etc/vsftpd/vusers

4、指定pam配置文件

修改vsftpd的配置文件 /etc/vsftpd/vsftpd.conf ,给vsftpd指定pam模块。

guest_enable=YES
guest_username=vuser
pam_service_name=vsftpd.db

5、SELinux设置

如果对SELinux有什么特殊要求的话,可以单独对SELinux进行设置,如果没有特殊要求的话,直接禁用SELinux就可以。

6、虚拟用户建立独立的配置文件

在vsftpd的配置文件中指定,用户配置文件的存储路径,然后在/etc/目录下,创建相应的目录,并且在目录中定义与用户名一致的配置文件并写入相应权限就可以了。

在vsftpd的配置文件 /etc/vsftpd/vsftpd.conf 中指定如下路径.

user_config_dir=/etc/vsftpd/vusers.d/

在etc目录下,创建如下的文件目录,这一个目录与前面在vsftpd配置文件中定义的一致。

mdkir /etc/vsftpd/vusers.d/

进入到/etc/vsftpd/vusers.d/ 目录下,创建与用户名同名的配置文件,例如用户为tom,那么就创建一个与tom一致的配置文件。 将下面的内容,写入配置文件,就可以进行权限控制。

# 虚拟用户上传权限
anon_upload_enable=YES
# 虚拟用户创建文件夹
anon_mkdir_write_enable=YES
# 虚拟的其他用户对指定用户目录的写权限
anon_other_write_enable=YES

或者还可以改变用户的默认登陆目录。也就是FTP用户登陆成功之后的默认路径。

#登录目录改变至指定的目录
local_root=/ftproot

Redis-慢查询分析

一、慢查询日志

慢查询日志帮助开发和运维人员定位系统存在的慢操作。慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阀值,就将这条命令的相关信息(慢查询ID,发生时间戳,耗时,命令的详细信息)记录下来。Redis客户端一条名利分为如下四部分执行:

未分类

需要注意的是,慢查询日志只是统计步骤3)执行命令的时间,所以慢查询并不代表客户端没有超时问题。

二、慢查询的配置参数

2.1 慢查询的预设阀值 slowlog-log-slower-than

slowlog-log-slower-than参数就是预设阀值,单位是微秒,默认值是1000,如果一条命令的执行时间超过10000微妙,那么它将被记录在慢查询日志中。

如果slowlog-log-slower-than的值是0,则会记录所有命令。

如果slowlog-log-slower-than的值小于0,则任何命令都不会记录日志。

2.2 慢查询日志的长度slowlog-max-len

slowlog-max-len只是说明了慢查询日志最多存储多少条。Redis使用一个列表来存储慢查询日志,showlog-max-len就是列表的最大长度。当慢查询日志已经到达列表的最大长度时,又有慢查询日志要进入列表,则最早插入列表的日志将会被移出列表,新日志被插入列表的末尾。

三、慢查询日志的组成

慢查询日志由以下四个属性组成:标识ID,发生时间戳,命令耗时,执行命令和参数

四、慢查询日志的访问和管理

4.1 获取慢查询日志slowlog get [n]

命令:slowlog get [N]

选型:N,可选,代表获取的日志条数

例如:showlog get 5

返回:

1) 1) (integer) 1
2) (integer) 1499338521
    3) (integer) 10101
    4) 1) "SETEX"
     2) "com.yonyou.iuap.portal.integration.ticket.entity.Ticket/AhZwvMNspZnYEy3Zs5BhdA"
     3) "3600"
     4)"{"id":"AhZwvMNspZnYEy3Zs5BhdA","usercode":"b540903b584144d38a365a1fc593ee68","expire":1499342121122,"extendAttributes":{}}"
2)1) (integer) 0
   2)(integer) 1499251333
   3)(integer) 42683
   4)1) "HGETALL"
     2) "IUAP_SESSION_USER:b540903b584144d38a365a1fc593ee68"

4.2 获取慢查询日志列表的当前长度slowlog len

命令:slowlog len

返回:慢日志列表的当前长度

例如:slowlog len

返回:2

4.3 慢查询日志重置slowlog reset

慢查询日志重置实际是对列表做清理操作。

命令:slowlog reset

例如:slowlog reset

slowlog len

返回: 0

五、慢查询日志最佳实践

(1)slowlog-max-len的设置建议
线上环境建议调大慢查询日志的列表,记录慢查询日志时Redis会对长命令做截断操作,并不会占用大量内存。增大慢查询列表可以减缓慢查询被剔除出列表的可能性。例如线上可以设置为1000以上。

(2)slowlog-log-lower-than的设置建议
需要根据redis的并发量调整该值。由于redis采用单线程响应名利,对于高流量的场景,如果执行命令的时间在1毫秒以上,那么redis最多可支撑OPS(每秒操作次数)不到1000,因此高OPS场景的REDIS建议设置为1毫秒。

(3)慢查询只记录命令执行时间,并不包括命令排队时间和网络传输时间。因此客户端
命令的执行时间要大于redis服务器实际执行命令的时间。因为命令执行排队极致,慢查询会导致命令级联阻塞,因此当客户端出现请求超时,需要检查该时间点是否有对应的慢查询,从而分析是否因为慢查询导致的命令级联阻塞

(4)慢查询日志是一个先进先出队列,慢查询较多的情况下,可能会丢失部分慢查询命令,可以定期执行slow get命令将慢查询日志持久化到其他存储中。然后制作可视化界面查询。

Redis Set 实现关注,粉丝功能

采用 Redis 的 Set 类型 ,这是一种 string 类型的无序集合,成员具有唯一性,哈希表实现,复杂度为 O(1) ,成员的最大数量是 $2^{32-1}$,大约是40亿。

关注Key:followUID

粉丝Key:fansStaffID

往 key = follow417 添加一个 Staff147 成员

127.0.0.1:6379> SADD follow417 Staff147
(error) WRONGTYPE Operation against a key holding the wrong kind of value

发现报错了,字面意思是传值不对,其实是 operation417 这个 key 我之前用过,设置为 SetBit 类型了。用 del key 删除,重新执行 sadd 命令即可。

127.0.0.1:6379> type follow417
none
127.0.0.1:6379> del follow417
(integer) 1
127.0.0.1:6379> SADD follow417  147
(integer) 1
127.0.0.1:6379> SADD follow417  148
(integer) 1
# 给 集合中添加两个元素 147 ,148

查看集合的成员数目

127.0.0.1:6379> scard follow417
(integer) 2

查看所有成员

127.0.0.1:6379> smembers follow417
1) "147"
2) "148"

删除 集合中的某个元素

127.0.0.1:6379> srem follow417 147
(integer) 1
127.0.0.1:6379> scard follow417
(integer) 1
127.0.0.1:6379> SMEMBERS follow417
1) “148”

设置粉丝集合

127.0.0.1:6379> SMEMBERS fans1477
1) "40"
127.0.0.1:6379>

查看集合的交集

127.0.0.1:6379> SINTER follow407 fans407
(empty list or set)
# 可以查看两个人的共同关注
127.0.0.1:6379> SINTER follow407 follow417
1) “148”

Redis 官网集群步骤

1. 创建目录

要让集群正常运作至少需要三个主节点, 不过在刚开始试用集群功能时, 强烈建议使用六个节点: 其中三个为主节点, 而其余三个则是各个主节点的从节点
创建一个新目录 redis,并在其中创建六个以端口号为名字的子目录,每个子目录都是一个 redis

mkdir redis
cd redis
mkdir 7000 7001 7002 7003 7004 7005

2. 修改配置文件

分别进入每个子目录,修改 redis.conf 中如下的配置项

port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

其中端口号 port 分别为 7000-7005

3. 运行实例

接着,从 https://github.com/antirez/redis 页面的 unstable 分支中下载最新代码,然后编译出可执行文件放在 redis 文件夹中。(其实从我们的任一子目录里面编译后在 src 文件夹里拿出来也可以)通过如下命令分别创建出 6 个实例

cd 7000
../redis-server ./redis.conf

4. 创建集群

可以使用 src 目录中的 redis-trib 程序来创建集群,首先需要安装 redis gem 才能运行 redis-trib

gem install redis

然后创建集群

./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

然后终端会列出集群的情况,并礼貌性的问你这样创建可以吗,当然是输入 yes

>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
127.0.0.1:7000
127.0.0.1:7001
127.0.0.1:7002
Adding replica 127.0.0.1:7003 to 127.0.0.1:7000
Adding replica 127.0.0.1:7004 to 127.0.0.1:7001
Adding replica 127.0.0.1:7005 to 127.0.0.1:7002
M: 968f13cefb4f895cec739794835fcd160bde22af 127.0.0.1:7000
   slots:0-5460 (5461 slots) master
M: 3c088766a36ed8ccd7bef5e6e2fbe75c48bc10c0 127.0.0.1:7001
   slots:5461-10922 (5462 slots) master
M: 433145fbee24fa60a3e7194b9b8056b02f375023 127.0.0.1:7002
   slots:10923-16383 (5461 slots) master
S: d29b5ce6e4ea9a33a9044d4211ba37aafa5198f8 127.0.0.1:7003
   replicates 968f13cefb4f895cec739794835fcd160bde22af
S: ba9052a112e11e757a563ce6ed89adde463c7ec5 127.0.0.1:7004
   replicates 3c088766a36ed8ccd7bef5e6e2fbe75c48bc10c0
S: 130bfe6c6e1b8d31ed1e030ae7c41902be268a1f 127.0.0.1:7005
   replicates 433145fbee24fa60a3e7194b9b8056b02f375023
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join...
>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: 968f13cefb4f895cec739794835fcd160bde22af 127.0.0.1:7000
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
M: 433145fbee24fa60a3e7194b9b8056b02f375023 127.0.0.1:7002
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
M: 3c088766a36ed8ccd7bef5e6e2fbe75c48bc10c0 127.0.0.1:7001
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
S: ba9052a112e11e757a563ce6ed89adde463c7ec5 127.0.0.1:7004
   slots: (0 slots) slave
   replicates 3c088766a36ed8ccd7bef5e6e2fbe75c48bc10c0
S: d29b5ce6e4ea9a33a9044d4211ba37aafa5198f8 127.0.0.1:7003
   slots: (0 slots) slave
   replicates 968f13cefb4f895cec739794835fcd160bde22af
S: 130bfe6c6e1b8d31ed1e030ae7c41902be268a1f 127.0.0.1:7005
   slots: (0 slots) slave
   replicates 433145fbee24fa60a3e7194b9b8056b02f375023
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

到此集群就创建成功了。

但很明显这是个伪集群,如果条件允许的话建议使用 6 台机器来创建集群,只需修改相应的地址和端口,并在防火墙开放该端口就行。

5. 简单测试

使用 redis-cli 进行简单的命令测试

$ redis-cli -c -p 7000
redis 127.0.0.1:7000> set foo bar
-> Redirected to slot [12182] located at 127.0.0.1:7002
OK
redis 127.0.0.1:7002> set hello world
-> Redirected to slot [866] located at 127.0.0.1:7000
OK
redis 127.0.0.1:7000> get foo
-> Redirected to slot [12182] located at 127.0.0.1:7002
"bar"
redis 127.0.0.1:7000> get hello
-> Redirected to slot [866] located at 127.0.0.1:7000
"world"

redis统计大key

redis的–bigkeys参数:对redis整个keyspace进行统计(数据量大时采样,调用scan命令),寻找每种数据类型较大的keys,给出数据统计

redis-cli –bigkeys -i 0.1 -h 127.0.0.1

golang基于redis lua封装的优先级去重队列

前言:

 前两天由于某几个厂商的api出问题,导致后台任务大量堆积,又因为我这边任务流系统会重试超时任务,所以导致队列中有大量的重复任务。这时候我们要临时解决两个事情,一件事情,让一些高质量的任务优先执行; 另一件事情, 要有去重。 rabbitmq不能很好的针对这类情况去重、分优先级。  

 这时候我又想到了我最爱的redis…   去重?  list + set 就可以解决, 优先级,zset + zrange + zrem 也可以解决…  但问题这几个命令非原子,那么怎么让他们原子?   写模块 or redis lua script .   首先在 redis 4.x 写了个简单的module,但写完了发现一件颇为重要的事情,我们线上的是3.2 ….  然后又花了点时间改成redis lua的版本。项目本身的功能实现很简单,复杂的是创意 !!! 

项目名: redis_unique_queue, 项目地址,https://github.com/rfyiamcool/redis_unique_queue

主要功能介绍:

使用redis lua script 封装的去重及优先级队列方法, 达到了组合命令的原子性和节省来往的io请求的目的.

去重队列:

不仅能保证FIFO, 而且去重.

优先级去重队列:

按照优先级获取任务, 并且去重.

使用方法:

# xiaorui.cc

PriorityQueue

NewPriorityQueue(priority int, unique bool, r *redis.Pool)

Push(q string, body string, pri int) (int, error)

Pop(q string) (resp string, err error)
UniqueQueue

NewUniqueQueue(r *redis.Pool) *UniqueQueue

UniquePush(q string, body string) (int, error)

UniquePop(q string) (resp string, err error)

more..

下面是优先级去重队列的例子:

package main

// xiaorui.cc

import (
    "fmt"

    "github.com/rfyiamcool/redis_unique_queue"
)

func main() {
    fmt.Println("start")
    redis_client_config := unique_queue.RedisConfType{
        RedisPw:          "",
        RedisHost:        "127.0.0.1:6379",
        RedisDb:          0,
        RedisMaxActive:   100,
        RedisMaxIdle:     100,
        RedisIdleTimeOut: 1000,
    }
    redis_client := unique_queue.NewRedisPool(redis_client_config)

    qname := "xiaorui.cc"
    body := "message from xiaorui.cc"

    u := unique_queue.NewPriorityQueue(3, true, redis_client)
    // 3: 3个优先级,从1-3级
    // true: 开启unique set

    u.Push(qname, body, 2)
    // 2, 优先级

    fmt.Println(u.Pop(qname))
}

单单使用 去重队列的例子:

package main

import (
    "fmt"

    "github.com/rfyiamcool/redis_unique_queue"
)

func main() {
    fmt.Println("start")
    redis_client_config := unique_queue.RedisConfType{
        RedisPw:          "",
        RedisHost:        "127.0.0.1:6379",
        RedisDb:          0,
        RedisMaxActive:   100,
        RedisMaxIdle:     100,
        RedisIdleTimeOut: 1000,
    }
    redis_client := unique_queue.NewRedisPool(redis_client_config)


    qname := "xiaorui.cc"
    u := unique_queue.NewUniqueQueue(redis_client)
    for i := 0; i < 100; i++ {
        u.UniquePush(qname, "body...")
    }

    fmt.Println(u.Length(qname))

    for i := 0; i < 100; i++ {
        u.UniquePop(qname)
    }

    fmt.Println(u.Length(qname))


    fmt.Println("end")
}

需要改进地址也是很多, 比如 加入批量操作, 对于redis连接池引入方法改进等.

END.

redis批量删除key

最近生产环境的redis服务器由于key过期不及时,现在发现时key的个数已经暴增到5000多万了。然后运维同学那边就报警了,最大内存12G,已经用了9G多了,正好下面快要双11了,让我们快些解决。

redis服务器里面堆积大量的队列状态相关的key,其实这些key可以设置有效期,或者任务完成以后删除或者过期,但是由于我们使用类库的问题,这些key既没有删除也没有过期,堆积到redis里面去了,现在我们要做的就是删除这些无用key。在删除这些keys的过程中,走了不少弯路,这里说一下我最终采用的方案。

redis的del函数可以删除单个key,也可以删除多个key,del函数官方文档可以看这里。在google之后看到目前网络上很多文章的思路是使用keys匹配返回要删除的key,然后调用del函数去删除。这种方案在数据量较小时无可厚非,但如果像我这样面临的处理的数据有5千W时,keys的阻塞问题可能会给线上生产环境带来致命的问题。所以我们需要对这种方案作出一些修改。

可喜的是自从2.8.0以后redis提供scan来遍历key,而且这个过程是非阻塞,不会影响线上生产环境。最终经过修改的方案是用scan遍历要删除的key,然后调用del删除。

下面是我用python写的用来删除key的脚本。

import sys,redis

r = redis.Redis(host="127.0.0.1", port=6379,db=0)

if  len(sys.argv) <= 1:
    print("必须指明匹配key字符串")
    exit(1)
pattern = sys.argv[1]

cursor = 0
num = 1
while 1 :
    resut = r.scan(cursor, pattern, 10000)
    del_keys = []
    for i in resut[1]:
        key = i.decode()
        del_keys.append(key)
    #print("del keys len :%d" % len(result))
    if len(del_keys) == 0:
        break
    r.delete(*del_keys)
    cursor = resut[0]
    print("delete keys num : %dw" % (num))
    num +=1

print("donen")

如何利用我这个脚本删除符合某个规则的key哪,如以king开头的key?

下面的命令即可完成上面的问题。

python3 main.py "king*"

期间我看到网上利用keys+del的lua脚本的方案,花了一段时间把scan+del改成lua脚本来删除。但是可惜的是目前redis并不支持这么做,由于scan返回的结果是不确定的,所以禁止在其后直接调用del操作。

使用docker的redis镜像

前言

docker的官方镜像提供了redis的镜像,为了方便自己随时随地需要的使用,就学习一下,顺便记录下来。

参考Docker官方Redis镜像: https://hub.docker.com/_/redis/

准备工作

  • 一台linux机器(我用windows10自带的hyper-v装了个虚拟机)
  • 安装docker

获取redis镜像

一行命令搞定:

$> docker pull redis

说明: docker的官方镜像都可以在Docker Hub上找到,这里会自动从Docker Hub 上找到对应的镜像并下载。

下载完成之后,可以使用如下命令查看镜像:

$> docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
redis               latest              1fb7b6c8c0d0        6 days ago          107MB (1)
oraclelinux         latest              6c33a25f4a29        2 weeks ago         229MB
elasticsearch       latest              d1ac13423d3c        5 weeks ago         580MB
hello-world         latest              1815c82652c0        4 months ago        1.84kB

这个就是我们刚刚获取的镜像。

运行redis镜像

$> docker run --name my-redis -d redis
7dabbc56e3514e9ba1705d6c6ec180e603ceff7e6011f7915ef3b0f1f5b05b35

启动后可以查看redis运行的信息:

$> docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
7dabbc56e351        redis               "docker-entrypoint..."   35 minutes ago      Up 35 minutes       6379/tcp            my-redis

连接redis

在虚拟机中,没有ui工具可以连接redis,要测试redis是否正常服务,需要安装redis的命令行客户端工具(redis-cli)。

安装redis-cli

$> wget http://download.redis.io/releases/redis-2.8.17.tar.gz
$> tar xzf redis-2.8.17.tar.gz
$> cd redis-2.8.17
$> make

注意: 这里是下载redis源码进行编译,编译后在src目录下就会有redis-cli工具。 源码编译过程需要使用gcc,如果没有安装的话请先用yum或者apt-get安装gcc。 如:yum install gcc或apt-get install gcc。

编译完成后:

$> cd src
$> ./redis-cli
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> 

说明已经编译完成,可以使用了。

由于没有参数的情况下,默认是连接127.0.0.1:6379,这里连接失败了,先退出cli:

not connected> exit

连接docker-redis

现在我们需要知道docker中的redis的IP地址:

$> docker inspect --format '' ${容器id}

容器id我们前面已经用docker ps看到了,拷贝过来替换掉${容器id}。

$> docker inspect --format '' 7dabbc56e351
172.17.0.2

连接:

$> ./redis-cli -h 172.17.0.2
172.17.0.2:6379> 

不需要指定端口,使用默认端口6379即可。

正常连接上了,现在测试一下:

172.17.0.2:6379> set first:key "hello redis"
OK
172.17.0.2:6379> get first:key
"hello redis"
172.17.0.2:6379> 

成功!

允许物理机连接

上面成功连接之后,说明我们已经可以正常使用redis了,但是实际上这不是我们想要的效果,因为只能在虚拟机内连接,我们希望可以在物理机上连接,这样才可以进行开发。

现在让我们先停掉虚拟机里的dcoker:

$> docker stop ${容器id}

现在重新启动docker的redis镜像,同时指定主机端口映射容器端口:

$> docker run -p 6379:6379 --name port-map-redis -d redis

现在使用redis-cli直接连接本地:

$> ./redis-cli
127.0.0.1:6379>

说明连接本地成功。

Linux下Redis服务器搭建

一、系统环境

  • 操作系统:CentOS 6.9

  • redis版本:redis-4.0.2

二、安装步骤

1. 安装预环境

运行以下命令安装预环境。

[root@redis02 redis-4.0.2]# yum -y install gcc make

2. 下载redis源代码文件并解压缩

下载完redis源代码后,运行以下命令进行解压缩。

[root@redis02 softwares]# tar -xzf redis-4.0.2.tar.gz

未分类

3. redis编译

运行make命令进行编译。

未分类

make命令执行完成编译后,会在src目录下生成6个可执行文件,分别是redis-server、redis-cli、redis-benchmark、redis-check-aof、redis-check-dump、redis-sentinel。

4. redis安装配置

运行make install命令。

未分类

命令执行后会将make编译生成的可执行文件拷贝到/usr/local/bin目录下,如下图。

未分类

然后,运行./utils/install_server.sh配置向导来配置redis,并且可以将redis服务加到开机自启动中。【重要】

未分类

5. redis服务查看,开启和关闭

此时redis服务已经启动了。可以通过以下命令来操作redis了。

查看redis的运行状态:

[root@redis02 redis-4.0.2]# service redis_6379 status

关闭redis服务:

[root@redis02 redis-4.0.2]# service redis_6379 stop

开启redis服务:

[root@redis02 redis-4.0.2]# service redis_6379 start

最后可以通过redis内置的客户端工具来测试下:

[root@redis02 ~]# redis-cli
127.0.0.1:6379> get name

(nil)

127.0.0.1:6379> set name mcgrady

OK

127.0.0.1:6379> get name

"mcgrady"

127.0.0.1:6379>

可以看到,redis服务已经成功配置好了!

三、注意事项

1. 运行make命令报错?

错误信息如下:

make[3]: gcc: Command not found

/bin/sh: cc: command not found

解决方案:

因为预环境没有安装,运行以下命令安装预环境。

[root@redis02 redis-4.0.2]# yum -y install gcc make

2. 安装完预环境后运行make命令报以下错误?

错误信息:

zmalloc.h:50:31: error: jemalloc/jemalloc.h: No such file or directory

zmalloc.h:55:2: error: #error "Newer version of jemalloc required"

解决方案:

运行以下命令。

make MALLOC=libc

3. 运行make test命令报以下错误?

错误信息:

You need tcl 8.5 or newer in order to run the Redis test

解决方案:

运行以下命令安装tcl。

[root@redis02 redis-4.0.2]# yum -y install tcl

4. 调用ConnectionMultiplexer.Connect创建连接的时候报错?

错误信息:

It was not possible to connect to the redis server(s); to create a disconnected multiplexer, disable AbortOnConnectFail. UnableToResolvePhysicalConnection on TIME

解决方案:

1)关闭保护模式,注意默认是打开的。

未分类

2)绑定IP,注意默认只绑定了127.0.0.1。

未分类

有用命令:

telnet 192.168.1.29 6379,可以直接测试客户端是否能连上服务器,如果通的话,基本上就没有什么问题。

ps -aux | grep redis ,查看redis的进程,看redis是否正常启动。

Git常用命令总结

Git是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目,可以有效、高速的处理从很小到非常大的项目版本管理。Git是Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

Git常用操作命令

1、远程仓库相关命令

检出仓库:$ git clone git://github.com/jquery/jquery.git
查看远程仓库:$ git remote -v
添加远程仓库:$ git remote add [name] [url]
删除远程仓库:$ git remote rm [name]
修改远程仓库:$ git remote set-url --push [name] [newUrl]
拉取远程仓库:$ git pull [remoteName] [localBranchName]
推送远程仓库:$ git push [remoteName] [localBranchName]

如果想把本地的某个分支test提交到远程仓库,并作为远程仓库的master分支,或者作为另外一个名叫test的分支,如下:

$git push origin test:master         // 提交本地test分支作为远程的master分支
$git push origin test:test              // 提交本地test分支作为远程的test分支

2、分支(branch)操作相关命令

查看本地分支:$ git branch
查看远程分支:$ git branch -r
创建本地分支:$ git branch [name] ----注意新分支创建后不会自动切换为当前分支
切换分支:$ git checkout [name]
创建新分支并立即切换到新分支:$ git checkout -b [name]
删除分支:$ git branch -d [name] ---- -d选项只能删除已经参与了合并的分支,对于未有合并的分支是无法删除的。如果想强制删除一个分支,可以使用-D选项
合并分支:$ git merge [name] ----将名称为[name]的分支与当前分支合并
创建远程分支(本地分支push到远程):$ git push origin [name]
删除远程分支:$ git push origin :heads/[name] 或 $ gitpush origin :[name]

创建空的分支:(执行命令之前记得先提交你当前分支的修改,否则会被强制删干净没得后悔)

$git symbolic-ref HEAD refs/heads/[name]
$rm .git/index
$git clean -fdx

3、版本(tag)操作相关命令

查看版本:$ git tag
创建版本:$ git tag [name]
删除版本:$ git tag -d [name]
查看远程版本:$ git tag -r
创建远程版本(本地版本push到远程):$ git push origin [name]
删除远程版本:$ git push origin :refs/tags/[name]
合并远程仓库的tag到本地:$ git pull origin --tags
上传本地tag到远程仓库:$ git push origin --tags
创建带注释的tag:$ git tag -a [name] -m 'yourMessage'

4、子模块(submodule)相关操作命令

添加子模块:$ git submodule add [url] [path] 如下:

$git submodule add git://github.com/soberh/ui-libs.git src/main/webapp/ui-libs
初始化子模块:$ git submodule init  ----只在首次检出仓库时运行一次就行
更新子模块:$ git submodule update ----每次更新或切换分支后都需要运行一下
删除子模块:(分4步走哦)

1) $ git rm --cached [path]
2) 编辑“.gitmodules”文件,将子模块的相关配置节点删除掉
3) 编辑“ .git/config”文件,将子模块的相关配置节点删除掉
4) 手动删除子模块残留的目录

5、忽略一些文件、文件夹不提交

在仓库根目录下创建名称为“.gitignore”的文件,写入不需要的文件夹名或文件,每个元素占一行即可,如下:

target
bin
*.db

Git 常用命令

git branch 查看本地所有分支
git status 查看当前状态
git commit 提交
git branch -a 查看所有的分支
git branch -r 查看本地所有分支
git commit -am "init" 提交并且加注释
git remote add origin [email protected]:ndshow
git push origin master 将文件给推到服务器上
git remote show origin 显示远程库origin里的资源
git push origin master:develop
git push origin master:hb-dev 将本地库与服务器上的库进行关联
git checkout --track origin/dev 切换到远程dev分支
git branch -D master develop 删除本地库develop
git checkout -b dev 建立一个新的本地分支dev
git merge origin/dev 将分支dev与当前分支进行合并
git checkout dev 切换到本地dev分支
git remote show 查看远程库
git add .
git rm 文件名(包括路径) 从git中删除指定文件
git clone git://github.com/schacon/grit.git 从服务器上将代码给拉下来
git config --list 看所有用户
git ls-files 看已经被提交的
git rm [file name] 删除一个文件
git commit -a 提交当前repos的所有的改变
git add [file name] 添加一个文件到git index
git commit -v 当你用-v参数的时候可以看commit的差异
git commit -m "This is the message describing the commit" 添加commit信息
git commit -a -a是代表add,把所有的change加到git index里然后再commit
git commit -a -v 一般提交命令
git log 看你commit的日志
git diff 查看尚未暂存的更新
git rm a.a 移除文件(从暂存区和工作区中删除)
git rm --cached a.a 移除文件(只从暂存区中删除)
git commit -m "remove" 移除文件(从Git中删除)
git rm -f a.a 强行移除修改后文件(从暂存区和工作区中删除)
git diff --cached 或 $ git diff --staged 查看尚未提交的更新
git stash push 将文件给push到一个临时空间中
git stash pop 将文件从临时空间pop下来
---------------------------------------------------------
git remote add origin [email protected]:username/Hello-World.git
git push origin master 将本地项目给提交到服务器中
-----------------------------------------------------------
git pull 本地与服务器端同步
-----------------------------------------------------------------
git push (远程仓库名) (分支名) 将本地分支推送到服务器上去。
git push origin serverfix:awesomebranch
------------------------------------------------------------------
git fetch 相当于是从远程获取最新版本到本地,不会自动merge
git commit -a -m "log_message" (-a是提交所有改动,-m是加入log信息) 本地修改同步至服务器端 :
git branch branch_0.1 master 从主分支master创建branch_0.1分支
git branch -m branch_0.1 branch_1.0 将branch_0.1重命名为branch_1.0
git checkout branch_1.0/master 切换到branch_1.0/master分支
du -hs
-----------------------------------------------------------
mkdir WebApp
cd WebApp
git init
touch README
git add README
git commit -m 'first commit'
git remote add origin [email protected]:daixu/WebApp.git
git push -u origin master

以上为Git中常用的命令总结。