[转][笔记]分布式文件系统 MofileFS

参考:
http://www.cnblogs.com/xiaocen/p/3721390.html
http://blog.csdn.net/21aspnet/article/details/6620723

注,本文有许多直接用了其中的图片,文字,但有很多更新修改 ,结合课程笔记,以及自己的实验做出此文

MogileFS 项目官方 wiki:

https://code.google.com/p/mogilefs/wiki

用作反向代理的 nginx 模块:

http://www.grid.net.ru/nginx/mogilefs.en.html

大纲

一、分布式文件系统
二、MogileFS 的实现
三、安装配置 mogilefsd和mogstored
四、安装及初始化 mysql
五、配置存储及上传文件测试
六、使用 nginx 反向代理 MogileFS
附录: nginx mogilefs 模块详解(翻译)

一、分布式文件系统


分布式文件系统(Distributed File System)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连,也就是集群文件系统,可以支持大数量的节点以及PB级的数量存储。相对地,在一个分享的磁盘文件系统中,所有节点对数据存储区块都有相同的访问权,在这样的系统中,访问权限就必须由客户端程序来控制。

分布式文件系统可能包含的功能有:透通的数据复制与容错。也就是说,即使系统中有一小部份的节点脱机,整体来说系统仍然可以持续运作而不会有数据损失

1.1分布式文件系统的类别:


MogileFS 适用于处理海量小文件,比如网站图片,使用 MySQL 存储元数据
FastDFS 适用于处理海量小文件,类似 MogileFS,但不使用 MySQL 存储元数据,在内存中存储。
Ceph 是一个 Linux PB 级别的分布式文件系统
MooseFS 通用简便,适用于研发能力不强的公司
Taobao Filesystem 适用于处理海量小文件
GlusterFS 适用于处理少量单个大文件,比如视频文件,比如云环境的虚拟机映像文件(用友科技的案例)
Google Filesystem GFS+MapReduce 擅长处理单个大文件
HDFS HDFS(GFS的山寨版)+MapReduce

GFS 构建的基础理论:

  • MapReduce
  • Bigtable: GFS 只支持文件增加和删除,不支持编辑,为解决这个问题,出现了 Bigtable

HDFS,即 Hadoop Distributed Filesystem,是GFS的山寨版,HDFS+MapReduce = Hadoop(平台,需要java编程调用其接口,运行于 Hadoop 平台之上),擅长处理单个大文件。

HBase 是基于 Bigtable 理论实现的列式 NoSQL 数据库。

分布式文件系统,一般日访问量PV千万级别时候使用。

一个分布式文件系统,是否适合处理海量文件,主要看 NameNode(存放文件元数据的节点)。MogileFS 不是直接在内存存放元数据,而是通过 MySQL 存储元数据,所以支持海量文件,但这对性能有影响。

二、MogileFS 的实现


MogileFS 是一个开源的分布式文件系统,用于组建分布式文件集群,由 LiveJournal 旗下 Danga Interactive 公司开发,Danga 团队开发了包括 Memcached、MogileFS、Perlbal 等不错的开源项目:(注:Perlbal 是一个强大的 Perl 写的反向代理服务器)。目前国内使用 MogileFS 的有图片托管网站 yupoo云 ,大众点评,安居客,豆瓣。

MogileFS 的优点:MogileFS是一个开源的分布式文件系统。主要特性包括:应用层的组件、无单点故障、自动文件复制、具有比RAID更好的可靠性、无需RAID支持等……核心角色如下:

  • tracker节点:借助数据库保存各节点文件的元数据信息,保存每个域中所有键的存储位置分布,方便检索定位数据位置,并且可监控各节点,告诉客户端存储区位置并指挥storage节点复制数据副本,进程名为mogilefsd(7001)。

  • mysql节点:为tracker节点提供数据存取服务。用于为tracker存储元数据信息;mogilefs的名称空间及文件名;

  • storage节点:将指定域中的键转换为其特有的文件名存储在指定的设备文件中,转换后的文件名为值,storage节点自动维护键值的对应关系,storage节点由于使用http进行数据传输,因此依赖于perlbal。storage节点前端可以使用nginx进行反向代理,但需要安装nginx-mogilefs-module-master模块进行名称转换,进程名mogstored(7501),perbal(7500)。能完成文件的创建、删除、重命名。
  • Domain:一个域中的键值是惟一的,一个MogileFS可以有多个域,域是用来存储不同应用类型的数据的容器。

  • Host:每一个存储节点称为一个主机,一个主机上可以有多个存储设备(单独的硬盘),每个设备都有ID号,Domain+Fid用来定位文件。

  • Class:复制最小单位,文件属性管理,定义文件存储在不同设备上份数。

为什么 MogileFS 号称无单点?以为每个 tracker节点可以有多个,storage节点也可以有多个,mysql 我们可以自行建立高可用。每个服务器可以承担多个角色。

Snip20160815_116.png

当 tracker 节点有多个的时候,我们需要对其做负载均衡,这里使用 nginx 做负载均衡。

MogileFS的特性:

  • 1、用户空间文件系统(FUSE):无须特殊的核心组件;
  • 2、无单点失败:
  • 3、自动文件复制
  • 4、比“RAID”好多了
  • 5、传输中立,无特殊协议(HTTP,NFS)
  • 6、命名空间较简单:每个文件对应于一个key;用于domain定义名称空间;
  • 7、不依赖于任何共享存储设备;

MogileFS由3个部分组成:

  • 第1个部分: 是server端,包括mogilefsd和mogstored两个程序。前者即是mogilefsd的tracker,它将一些全局信息保存在数据库里,例如站点domain,class,host等。后者即是存储节点(store node),它其实是个HTTP Daemon,默认侦听在7500端口,接受客户端的文件备份请求。在安装完后,要运行mogadm工具将所有的store node注册到mogilefsd的数据库里,mogilefsd会对这些节点进行管理和监控。

  • 第2个部分:是utils(工具集),主要是MogileFS的一些管理工具,例如mogadm等。

  • 第3个部分:是客户端API,目前只有Perl API(MogileFS.pm)、PHP,用这个模块可以编写客户端程序,实现文件的备份管理功能,提供MogileFS.pm。

实现MogileFS的分布式过程,按照下图构建MogileFS分布式文件系统的结构,这里限于机器的原因,一些服务都整合在一起;

实验拓扑

逻辑结构如上:两个 mogilefsd 节点 + 两个 mogstored 节点,一个 mysql 节点,一个 nginx 节点

实验环境:

一共三台服务器,将 mysql 和 nginx 安装到同一台服务器上(192.168.0.171);另两台服务器都分别安装了 mogilefsd和mogstored两个程序。

操作系统:CentOS 6.8

nginx: 192.168.0.171
node1(Trackers+Stored): 192.168.0.181
node2(Trackers+Stored): 192.168.0.182
mysql: 192.168.0.171

三、安装配置 mogilefsd和mogstored


node1

在 node1上,使用打包好的 rpm 包安装:

[root@tomcat1 packages]# ls
MogileFS-Server-2.46-2.el6.noarch.rpm
MogileFS-Server-mogilefsd-2.46-2.el6.noarch.rpm
MogileFS-Server-mogstored-2.46-2.el6.noarch.rpm
MogileFS-Utils-2.19-1.el6.noarch.rpm
Perlbal-1.78-1.el6.noarch.rpm
Perlbal-doc-1.78-1.el6.noarch.rpm
perl-MogileFS-Client-1.14-1.el6.noarch.rpm
perl-Net-Netmask-1.9015-8.el6.noarch.rpm
perl-Perlbal-1.78-1.el6.noarch.rpm

注,这些包不是官方提供的,官方的安装方法是使用 cpan,本文顶部给出的官方链接,其中有文档,我试过安装,安装过程较耗时间。

安装 MogileFS,这里直接 yum 安装所有 rpm 包:

# cd packages
# yum install *

mogstored 的运行需要 perl-IO-AIO

yum install perl-IO-AIO

编辑 mogilefsd和mogstored 的配置文件

配置 mogilefsd:

安装好之后修改配置文件,只需要修改以下几项就可以了
mysql指使用的数据库是什么,MariaDB也是写mysql;mogdb指数据库名;host=192.168.0.171指定安装数据的地址

[root@tomcat1 ~]# vi /etc/mogilefs/mogilefsd.conf

# Database connection information
db_dsn = DBI:mysql:mogdb:host=192.168.0.171
db_user = moguser # 指登录mogdb数据所使用的用户名
db_pass = guli123 # 登录mogdb数据所使用密码
listen = 0.0.0.0:7001 # 0.0.0.0表示监听所有地址

配置 mogilestored:

准备存储设备挂载至 /dfs/mogdata 目录下,确认此路径下存在目录devN,N 为 device id。

此路径的属主和属组为mogilefs.mogilefs

再编辑 mogstored.conf 这个配置文件

[root@tomcat1 ~]# vi /etc/mogilefs/mogstored.conf
maxconns = 10000
httplisten = 0.0.0.0:7500
mgmtlisten = 0.0.0.0:7501
docroot = /dfs/mogdata   # 把这项改一个特定的目录,以便存储数据,最好是一个独立分区

创建数据目录:

[root@node1 ~]# mkdir -pv /dfs/mogdata/dev1  # 创建修改的docroot目录
[root@node1 ~]# chown -R mogilefs.mogilefs /dfs/mogdata/dev1 # 修改属主属组
[root@node1 ~]# chown -R mogilefs.mogilefs /var/run/mogilefsd/
[root@node1 ~]# cd /etc/mogilefs
[root@node1 ~]# scp mogilefsd.conf mogstored.conf root@192.168.0.181:/etc/mogilefs/  

node2

node2 也使用 yum install * 安装。

# cd /root/packages
# ls
MogileFS-Server-2.46-2.el6.noarch.rpm
MogileFS-Server-mogilefsd-2.46-2.el6.noarch.rpm
MogileFS-Server-mogstored-2.46-2.el6.noarch.rpm
MogileFS-Utils-2.19-1.el6.noarch.rpm
Perlbal-1.78-1.el6.noarch.rpm
Perlbal-doc-1.78-1.el6.noarch.rpm
perl-MogileFS-Client-1.14-1.el6.noarch.rpm
perl-Net-Netmask-1.9015-8.el6.noarch.rpm
perl-Perlbal-1.78-1.el6.noarch.rpm

# yum install *

mogstored 的运行需要 perl-IO-AIO

yum install perl-IO-AIO

创建 docroot 目录及权限配置:

[root@node2 ~]# mkdir -pv /dfs/mogdata/dev2  # 创建修改的docroot目录
[root@node1 ~]# chown -R mogilefs.mogilefs /var/run/mogilefsd/
[root@node2 ~]# chown -R mogilefs.mogilefs /dfs/mogdata/dev3 # 修改属主属组   

复制修改好的两个配置文件到node2的mogilefs目录下,覆盖即可,两个节点都安装配置好之后先不要启动服务,数据库还没有安装。

注,node1 上的数据目录是 /dfs/mogdata/dev1;node2 上的目录是 /dfs/mogdata/dev3;这里的 1 和 3 是设备id,一会添加设备时会使用,设备id对于这里的一个 dev# 目录,# 是设备id号。

四、安装及初始化 mysql


在 192.168.0.171 节点上安装 mysql,我们这里直接 yum 安装

yum install mysql-server mysql

启动 mysql:

service mysqld start

修改 root 密码为 guli123

[root@vm1 src]# mysqladmin -u root password 'guli123'

登陆并创建用户:

# mysql -uroot -p

mysql> GRANT ALL ON *.* TO 'root'@'192.168.%.%' IDENTIFIED BY 'guli123';

mysql> GRANT ALL ON mogdb.* TO 'moguser'@'192.168.%.%' IDENTIFIED BY 'guli123';

mysql> FLUSH PRIVILEGES;

2、在两个节点上,在其中一个上设置即可,授权数据库用户,并初始化 mysql 数据库(会自动创建 mogdb 库)

# mogdbsetup --dbhost=192.168.0.171 \
--dbport=3306 \
--dbname=mogdb \
--dbrootuser=root \
--dbrootpass=guli123 \
--dbuser=moguser \
--dbpass=guli123 \
--yes

去数据库查看:

mysql> use mogdb
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+----------------------+
| Tables_in_mogdb      |
+----------------------+
| checksum             |
| class                |
| device               |
| domain               |
| file                 |
| file_on              |
| file_on_corrupt      |
| file_to_delete       |
| file_to_delete2      |
| file_to_delete_later |
| file_to_queue        |
| file_to_replicate    |
| fsck_log             |
| host                 |
| server_settings      |
| tempfile             |
| unreachable_fids     |
+----------------------+
17 rows in set (0.00 sec)

五、配置存储

使用 modadm 工具设置存储,为此需要先运行 mogilefsd。

启动 mogilefsd


启动 mogstored:

以 root 执行命令:

# mogstored --daemon

启动 mogilefsd

# su mogile
$ mogilefsd -c /etc/mogilefs/mogilefsd.conf --daemon
$ exit

或者。。

rpm 包里面自带了脚本,可以直接使用:

service mogilefsd start

脚本为:

[root@vm2 packages]# cat /etc/init.d/mogilefsd
#!/bin/bash
# Author: MageEdu <linuxedu@foxmail.com>
# mogilefsd - Startup script for the MogileFS tracker
#
# chkconfig: - 85 15
# description: MogileFS tracker
# processname: mogilefsd
# config: /etc/mogilefs/mogilefsd.conf
# pidfile: /var/run/mogilefsd/mogilefsd.pid

# Source function library.
. /etc/rc.d/init.d/functions

# Path to the apachectl script, server binary, and short-form for messages.
lockfile=${LOCKFILE-/var/lock/subsys/mogilefsd}
RETVAL=0

prog=$(which mogilefsd)

start() {
    ulimit -n 65535
    echo -n $"Starting mogilefsd"
    su - mogilefs -c "$prog -c /etc/mogilefs/mogilefsd.conf --daemon" &> /dev/null
    RETVAL=$?
    [ $RETVAL = 0 ] && success && touch ${lockfile} || failure
    echo
    return $RETVAL
}

stop() {
    echo -n $"Stopping mogilefsd"
    netstat -nlp|grep "mogilefsd"|grep -v grep|awk '{print $7}'|awk -F"/" '{print $1}'|xargs kill -9
    RETVAL=$?
    [ $RETVAL = 0 ] && success && rm -f ${lockfile} || failure
    echo
}

reload() {
    echo -n $"Reloading mogilefsd: "
    killall mogilefsd -HUP
    RETVAL=$?
    [ $RETVAL = 0 ] && success || failure
    echo
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status mogilefsd
        RETVAL=$?
        ;;
    restart)
        stop
        sleep 1
        start
        ;;
    reload)
        reload
        ;;
    *)
        echo $"Usage: mogilefsd {start|stop|restart|reload|status}"
        exit 1
esac
exit $RETVAL

查看启动的情况:

[root@vm2 packages]# ps -ef | grep mogilefs
mogilefs  1297     1  0 16:00 ?        00:00:00 mogilefsd
mogilefs  1299  1297  0 16:00 ?        00:00:00 mogilefsd [monitor]
mogilefs  1300  1297  0 16:00 ?        00:00:00 mogilefsd [replicate]
mogilefs  1301  1297  0 16:00 ?        00:00:00 mogilefsd [replicate]
mogilefs  1302  1297  0 16:00 ?        00:00:00 mogilefsd [replicate]
mogilefs  1303  1297  0 16:00 ?        00:00:00 mogilefsd [replicate]
mogilefs  1304  1297  0 16:00 ?        00:00:00 mogilefsd [replicate]
mogilefs  1305  1297  0 16:00 ?        00:00:00 mogilefsd [delete]
mogilefs  1306  1297  0 16:00 ?        00:00:00 mogilefsd [queryworker]
mogilefs  1307  1297  0 16:00 ?        00:00:00 mogilefsd [queryworker]
mogilefs  1308  1297  0 16:00 ?        00:00:00 mogilefsd [queryworker]
mogilefs  1309  1297  0 16:00 ?        00:00:00 mogilefsd [queryworker]
mogilefs  1310  1297  0 16:00 ?        00:00:00 mogilefsd [queryworker]
mogilefs  1311  1297  0 16:00 ?        00:00:00 mogilefsd [queryworker]
mogilefs  1312  1297  0 16:00 ?        00:00:00 mogilefsd [queryworker]
mogilefs  1313  1297  0 16:00 ?        00:00:00 mogilefsd [queryworker]
mogilefs  1314  1297  0 16:00 ?        00:00:00 mogilefsd [queryworker]
mogilefs  1315  1297  0 16:00 ?        00:00:00 mogilefsd [queryworker]
mogilefs  1316  1297  0 16:00 ?        00:00:00 mogilefsd [reaper]
mogilefs  1317  1297  0 16:00 ?        00:00:00 mogilefsd [fsck]
mogilefs  1318  1297  0 16:00 ?        00:00:00 mogilefsd [job_master]
root      1342  1209  0 16:02 pts/0    00:00:00 grep mogilefs

启动 mogstored


可以直接启动:

[root@vm2 mogilefs]# mogstored --daemon

也可以使用脚本启动,脚本为:

[root@vm2 mogilefs]# cat /etc/init.d/mogstored
#!/bin/bash
# Author: MageEdu <linuxedu@foxmail.com>
# mogstored - Startup script for the MogileFS storage
#
# chkconfig: - 86 14
# description: MogileFS storage
# processname: mogstored
# config: /etc/mogilefs/mogstored.conf
# pidfile: /var/run/mogilefsd/mogstored.pid

# Source function library.
. /etc/rc.d/init.d/functions

# Path to the apachectl script, server binary, and short-form for messages.
lockfile=${LOCKFILE-/var/lock/subsys/mogstored}
RETVAL=0

configfile='/etc/mogilefs/mogstored.conf'

prog=$(which mogstored)

start() {
    ulimit -n 65535
    echo -n $"Starting mogstored"
    su - mogilefs -c "$prog -c $configfile --daemon"  &> /dev/null
    RETVAL=$?
    [ $RETVAL = 0 ] && success && touch ${lockfile} || failure
    echo
    return $RETVAL
}

stop() {
    echo -n $"Stopping mogstored"
    netstat -nlp|grep "mogstored"|grep -v grep|awk '{print $7}'|awk -F"/" '{print $1}'|xargs kill -9
    RETVAL=$?
    [ $RETVAL = 0 ] && success && rm -f ${lockfile} || failure
    echo
}

reload() {
    echo -n $"Reloading mogstored: "
    killall mogstored -HUP
    RETVAL=$?
    [ $RETVAL = 0 ] && success || failure
    echo
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status mogstored
        RETVAL=$?
        ;;
    restart)
        stop
        sleep 1
        start
        ;;
    reload)
        reload
        ;;
    *)
        echo $"Usage: mogstored {start|stop|restart|reload|status}"
        exit 1
esac
exit $RETVAL

启动后查看:

[root@vm2 mogilefs]# ss -nlt
State      Recv-Q Send-Q           Local Address:Port             Peer Address:Port
LISTEN     0      128                          *:7500                        *:*
LISTEN     0      128                          *:7501                        *:*

设置 host


查看 host 相关命令:

[root@vm2 ~]# mogadm host
Help for 'host' command:
 (enter any command prefix, leaving off options, for further help)

  mogadm host add <hostname> [opts]                  Add a host to MogileFS.
  mogadm host delete <hostname>                      Delete a host.
  mogadm host list                                   List all hosts.
  mogadm host mark <hostname> <status>               Change the status of a host.  (equivalent to 'modify --status')
  mogadm host modify <hostname> [opts]               Modify a host's properties.

添加两个存储主机:mogstorage1,mogstorage1

mogadm --trackers=192.168.0.181:7001 host add mogstorage1 --ip=192.168.0.181 --status=alive
mogadm --trackers=192.168.0.181:7001 host add mogstorage2 --ip=192.168.0.182 --status=alive

另外一些操作:

mogadm --trackers=192.168.0.181:7001 host del mogstorage1 # 有设备时不能删除
mogadm --trackers=192.168.0.181:7001 host modify <hostname> --status={alive|down} # 修改 host 状态

检查是否添加上了存储主机:

# mogadm --trackers=192.168.0.181:7001 host list
mogstorage1 [1]: alive
  IP:       192.168.0.181:7500

mogstorage2 [2]: alive
  IP:       192.168.0.182:7500

给存储主机添加设备


设备相关命令:

[root@vm2 ~]# mogadm device
Help for 'device' command:
 (enter any command prefix, leaving off options, for further help)

  mogadm device add <hostname> <devid> [opts]        Add a device to a host.
  mogadm device list [opts]                          List all devices, for each host.
  mogadm device mark <hostname> <devid> <status>     Mark a device as {alive,dead,down,drain,readonly}
  mogadm device modify <hostname> <devid> [opts]     Modify a device's properties.
  mogadm device summary [opts]                       List the summary of devices, for each host.

注,这里很奇怪,没有 device delete 命令,在 man mogamd 手册中是有的。后来实验发现确实不能删除设备。

我们需要给每个存储主机添加设备,这里需要自己指定一个设备id,我们简单设置为 1,3:

mogadm --trackers=192.168.0.181:7001 device add mogstorage1 1
mogadm --trackers=192.168.0.181:7001 device add mogstorage2 3

查看 device 列表:mogadm --trackers=192.168.0.181:7001 device list

[root@vm2 ~]# mogadm --trackers=192.168.0.181:7001 device list
mogstorage1 [1]: alive
                    used(G)    free(G)   total(G)  weight(%)
   dev1:   alive      2.554      3.591      6.145        100

mogstorage2 [2]: alive
                    used(G)    free(G)   total(G)  weight(%)
   dev3:   alive      1.296      4.849      6.145        100

另外一些操作:

 mogadm device modify <hostname> <devid> --status={alive,dead,down,drain,readonly} # 设定设备的状态,为 dead 时,会从设备列表中删除
 mogadm device modify <hostname> <devid> --weight=i # 设定设备的权重   

设置 domain


domain 相关命令:

[root@vm2 ~]# mogadm domain
Help for 'domain' command:
 (enter any command prefix, leaving off options, for further help)

  mogadm domain add <domain>                         Add a domain (namespace)
  mogadm domain delete <domain>                      Delete a domain.
  mogadm domain list                                 List all hosts.

我们添加三个 domain,分别为 images, files, html:

mogadm --trackers=192.168.0.181:7001 domain add images
mogadm --trackers=192.168.0.181:7001 domain add files
mogadm --trackers=192.168.0.181:7001 domain add html

查看设置情况:

[root@vm2 ~]# mogadm domain list
 domain               class                mindevcount   replpolicy   hashtype
-------------------- -------------------- ------------- ------------ -------
 files                default                   2        MultipleHosts() NONE

 html                 default                   2        MultipleHosts() NONE

 images               default                   2        MultipleHosts() NONE

设置 class


class 相关命令:

[root@vm2 ~]# mogadm class
Help for 'class' command:
 (enter any command prefix, leaving off options, for further help)

  mogadm class add <domain> <class> [opts]           Add a file class to a domain.
  mogadm class delete <domain> <class>               Delete a file class from a domain.
  mogadm class list                                  List all classes, for each domain.
  mogadm class modify <domain> <class> [opts]        Modify properties of a file class.

看看 mogadm class add 的设置命令

  [root@vm2 ~]# mogadm class add

  ERROR: Missing argument 'domain'

  Help for 'class-add' command:

    mogadm class add <domain> <class> [opts]           Add a file class to a domain.

        <class>              Name of class to add.
        <domain>             Domain to add class to.
        --hashtype=s         Hash algorithm string ('MD5', 'NONE').
        --mindevcount=i      Minimum number of replicas.
        --replpolicy=s       Replication policy string.

这里看到可设置的选项,其中 --mindevcoun 是一份存储要复制的份数(>=1),一般设置为 3,这里因为我们只有两个存储节点,所以设置为 2。

在 images 域中新建一个 class:

mogadm --trackers=192.168.0.181:7001 class add images class0 --mindevcoun=2

查看设置情况:

[root@vm2 ~]# mogadm class list
 domain               class                mindevcount   replpolicy   hashtype
-------------------- -------------------- ------------- ------------ -------
 files                default                   2        MultipleHosts() NONE

 html                 default                   2        MultipleHosts() NONE

 images               class0                    2        MultipleHosts() NONE
 images               default                   2        MultipleHosts() NONE

这里除了我们设置的 class0,每个域中都有一个叫做 default 的 class,各自的 mindevcount 都为 2。

上传文件:


使用 mogupload 上传文件,相关命令使用方法是:

[root@vm2 ~]# mogupload
Usage: /usr/bin/mogupload --trackers=host --domain=foo --key='/hello.jpg' --file='./hello.jpg'

这里指定了 domain,存储的键,以及要存储的文件的路径,但没指定 --class,这就会使用默认的 default class。

我们上传几张图片和html文件上去:

/usr/share/nginx/html/50x.html
/usr/share/nginx/html/index.html

mogupload --trackers=192.168.0.181:7001 --domain=images --key="1.jpg" --file=/root/p936066463.jpg
mogupload --trackers=192.168.0.181:7001 --domain=images --key="2.jpg" --file=/root/p936066738.jpg

mogupload --trackers=192.168.0.181:7001 --domain=html --key="1.html" --file=/usr/share/nginx/html/index.html
mogupload --trackers=192.168.0.181:7001 --domain=html --key="2.html" --file=/usr/share/nginx/html/50x.html

mogupload --trackers=192.168.0.181:7001 --domain=files --key="1.file" --file=/root/epel-release-6-8.noarch.rpm

查看上传情况:

[root@tomcat1 ~]# moglistkeys --trackers=192.168.0.181:7001 --domain=images
1.jpg
2.jpg
[root@tomcat1 ~]# moglistkeys --trackers=192.168.0.181:7001 --domain=files
1.file
[root@tomcat1 ~]# moglistkeys --trackers=192.168.0.181:7001 --domain=html
1.html
2.html

查看一个 key=1.jpg 的详细信息:

# mogfileinfo --trackers=192.168.0.181:7001 --domain=images --key='1.jpg'
- file: 1.jpg
     class:              default
  devcount:                    2
    domain:               images
       fid:                    9
       key:                1.jpg
    length:                68326
 - http://192.168.0.181:7500/dev1/0/000/000/0000000009.fid
 - http://192.168.0.182:7500/dev3/0/000/000/0000000009.fid

可在浏览器访问一下 http://192.168.0.181:7500/dev1/0/000/000/0000000009.fid

查看一个 key=1.html 的详细信息:

[root@tomcat1 ~]# mogfileinfo --trackers=192.168.0.181:7001 --domain=html --key='1.html'
- file: 1.html
     class:              default
  devcount:                    2
    domain:                 html
       fid:                    6
       key:               1.html
    length:                  612
 - http://192.168.0.182:7500/dev3/0/000/000/0000000006.fid

html 类的文件通过浏览器访问,会返回文件的源代码:

<!DOCTYPE html>
<html>
<head>
<title>Error</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>An error occurred.</h1>
<p>Sorry, the page you are looking for is currently unavailable.<br/>
Please try again later.</p>
<p>If you are the system administrator of this resource then you should check
the <a href="http://nginx.org/r/error_log">error log</a> for details.</p>
<p><em>Faithfully yours, nginx.</em></p>
</body>
</html>

查看 rpm 包:

[root@tomcat1 ~]# mogfileinfo --trackers=192.168.0.181:7001 --domain=files --key='1.file'
- file: 1.file
     class:              default
  devcount:                    2
    domain:                files
       fid:                    8
       key:               1.file
    length:                14540
 - http://192.168.0.181:7500/dev1/0/000/000/0000000008.fid
 - http://192.168.0.182:7500/dev3/0/000/000/0000000008.fid

我们上传了一个 rpm 包,使用浏览器访问会直接下载文件。

检查总体状态:

[root@tomcat1 ~]# mogadm --trackers=192.168.0.181:7001,192.168.0.182:7001 check
Checking trackers...
  192.168.0.181:7001 ... OK
  192.168.0.182:7001 ... OK

Checking hosts...
  [ 1] mogstorage1 ... OK
  [ 2] mogstorage2 ... OK

Checking devices...
  host device         size(G)    used(G)    free(G)   use%   ob state   I/O%
  ---- ------------ ---------- ---------- ---------- ------ ---------- -----
  [ 1] dev1             6.145      2.555      3.590  41.57%  writeable   N/A
  [ 2] dev3             6.145      1.297      4.848  21.10%  writeable   N/A
  ---- ------------ ---------- ---------- ---------- ------
             total:    12.290      3.851      8.438  31.34%

六、使用 nginx 反向代理 MogileFS


环境准备:

yum -y groupinstall "Development Tools" "Server Platform Development"
yum -y install openssl-devel pcre-devel
groupadd -r nginx
useradd -r -g nginx nginx

nginx 下载:

wget http://nginx.org/download/nginx-1.10.1.tar.gz

模块下载:

wget http://www.grid.net.ru/nginx/download/nginx_mogilefs_module-1.0.4.tar.gz  

解压安装:

[root@vm1 src]# tar -xf nginx_mogilefs_module-1.0.4.tar.gz
[root@vm1 src]# tar -xf nginx-1.10.1.tar.gz
    

[root@vm1 src]# cd nginx-1.10.1
[root@vm1 nginx-1.10.1]# ./configure --prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--user=nginx \
--group=nginx \
--with-http_ssl_module \
--with-http_realip_module \
--with-http_addition_module \
--with-http_sub_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_mp4_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_random_index_module \
--with-http_secure_link_module \
--with-http_stub_status_module \
--with-http_slice_module \
--with-file-aio \
--with-http_v2_module \
--with-ipv6 \
--with-stream \
--with-stream_ssl_module \
--with-http_auth_request_module \
--add-module=/src/nginx_mogilefs_module-1.0.4 #模块的父目录


[root@vm1 nginx-1.10.1]# make && make install

注,测试发现一旦携带 --with-threads 参数,在 nginx_mogilefs_module 这里就出现编译错误

注,对于如下编译选项,make install 不会为其自动创建目录,需要我们自行创建,如果不创建启动 nginx 会报错:

--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \


[root@vm1 nginx-1.10.1]# nginx
Starting nginx: nginx: [emerg] mkdir() "/var/cache/nginx/client_temp" failed (2: No such file or directory)

方法一:

mkdir -p /var/cache/nginx/{client_temp,proxy_temp,fastcgi_temp,uwsgi_temp,scgi_temp}
chown -R nginx:nginx /var/cache/nginx/

方法二:在启动脚本中加入如下函数,它会自动检测 nginx 的编译选项,提取含有 -temp-path 的选项,创建对应的目录,并且修改权限。可让 start() 函数调用 make_dirs() 函数,让其在每次启动 nginx 之前检测是否有对应的目录。

make_dirs() {
  # make required directories
  user=`nginx -V 2>&1 | grep "configure arguments:" | sed 's/[^*]*--user=\([^ ]*\).*/\1/g' -`
  options=`$nginx -V 2>&1 | grep 'configure arguments:'`
  for opt in $options; do
      if [ `echo $opt | grep '.*-temp-path'` ]; then
          value=`echo $opt | cut -d "=" -f 2`
          if [ ! -d "$value" ]; then
              # echo "creating" $value
              mkdir -p $value && chown -R $user $value
          fi
      fi
  done
}

这里为简单起见,选择方法一,进行手动创建,然后测试启动一下:

[root@vm1 nginx-1.10.1]# nginx
[root@vm1 nginx-1.10.1]# ss  -nlt
State      Recv-Q Send-Q           Local Address:Port             Peer Address:Port
LISTEN     0      128                          *:80                          *:*
LISTEN     0      128                         :::22                         :::*
LISTEN     0      128                          *:22                          *:*
LISTEN     0      100                        ::1:25                         :::*
LISTEN     0      100                  127.0.0.1:25                          *:*

测试访问:

[root@vm1 nginx-1.10.1]# curl -I 127.0.0.1
HTTP/1.1 200 OK
Server: nginx/1.10.1
Date: Wed, 17 Aug 2016 03:23:16 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Wed, 17 Aug 2016 02:53:16 GMT
Connection: keep-alive
ETag: "57b3d19c-264"
Accept-Ranges: bytes

然后我们准备一个 /etc/init.d/nginx 脚本,方便调用:

[root@vm1 nginx]# cat /etc/init.d/nginx
#!/bin/sh
#
# nginx        Startup script for nginx
#
# chkconfig: - 85 15
# processname: nginx
# config: /etc/nginx/nginx.conf
# config: /etc/sysconfig/nginx
# pidfile: /var/run/nginx.pid
# description: nginx is an HTTP and reverse proxy server
#
### BEGIN INIT INFO
# Provides: nginx
# Required-Start: $local_fs $remote_fs $network
# Required-Stop: $local_fs $remote_fs $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: start and stop nginx
### END INIT INFO

# Source function library.
. /etc/rc.d/init.d/functions

if [ -L $0 ]; then
    initscript=`/bin/readlink -f $0`
else
    initscript=$0
fi

sysconfig=`/bin/basename $initscript`

if [ -f /etc/sysconfig/$sysconfig ]; then
    . /etc/sysconfig/$sysconfig
fi

nginx=${NGINX-/usr/sbin/nginx}
prog=`/bin/basename $nginx`
conffile=${CONFFILE-/etc/nginx/nginx.conf}
lockfile=${LOCKFILE-/var/lock/subsys/nginx}
pidfile=${PIDFILE-/var/run/nginx.pid}
SLEEPMSEC=${SLEEPMSEC-200000}
UPGRADEWAITLOOPS=${UPGRADEWAITLOOPS-5}
RETVAL=0

start() {
    echo -n $"Starting $prog: "

    daemon --pidfile=${pidfile} ${nginx} -c ${conffile}
    RETVAL=$?
    echo
    [ $RETVAL = 0 ] && touch ${lockfile}
    return $RETVAL
}

stop() {
    echo -n $"Stopping $prog: "
    killproc -p ${pidfile} ${prog}
    RETVAL=$?
    echo
    [ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}
}

reload() {
    echo -n $"Reloading $prog: "
    killproc -p ${pidfile} ${prog} -HUP
    RETVAL=$?
    echo
}

upgrade() {
    oldbinpidfile=${pidfile}.oldbin

    configtest -q || return
    echo -n $"Starting new master $prog: "
    killproc -p ${pidfile} ${prog} -USR2
    echo

    for i in `/usr/bin/seq $UPGRADEWAITLOOPS`; do
        /bin/usleep $SLEEPMSEC
        if [ -f ${oldbinpidfile} -a -f ${pidfile} ]; then
            echo -n $"Graceful shutdown of old $prog: "
            killproc -p ${oldbinpidfile} ${prog} -QUIT
            RETVAL=$?
            echo
            return
        fi
    done

    echo $"Upgrade failed!"
    RETVAL=1
}

configtest() {
    if [ "$#" -ne 0 ] ; then
        case "$1" in
            -q)
                FLAG=$1
                ;;
            *)
                ;;
        esac
        shift
    fi
    ${nginx} -t -c ${conffile} $FLAG
    RETVAL=$?
    return $RETVAL
}

rh_status() {
    status -p ${pidfile} -b ${nginx} ${nginx}
}

# See how we were called.
case "$1" in
    start)
        rh_status >/dev/null 2>&1 && exit 0
        start
        ;;
    stop)
        stop
        ;;
    status)
        rh_status
        RETVAL=$?
        ;;
    restart)
        configtest -q || exit $RETVAL
        stop
        start
        ;;
    upgrade)
        rh_status >/dev/null 2>&1 || exit 0
        upgrade
        ;;
    condrestart|try-restart)
        if rh_status >/dev/null 2>&1; then
            stop
            start
        fi
        ;;
    force-reload|reload)
        reload
        ;;
    configtest)
        configtest
        ;;
    *)
        echo $"Usage: $prog {start|stop|restart|condrestart|try-restart|force-reload|upgrade|reload|status|help|configtest}"
        RETVAL=2
esac

exit $RETVAL

而后为此脚本赋予执行权限:

# chmod +x /etc/rc.d/init.d/nginx

配置 nginx.conf


下面我们要编辑 nginx.conf 文件,关于 mogilefs 模块的使用方法,参考最后关于 nginx_mogilefs 模块的小节。

这里给出一份简单的 nginx 配置:

[root@vm1 nginx]# cat /etc/nginx/nginx.conf
worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    upstream mogcluster {
        server 192.168.0.182:7001;
        server 192.168.0.181:7001;
    }

    server {
        listen       80;
        server_name  localhost;

        location /images/ {
            mogilefs_tracker mogcluster;
            mogilefs_domain images; # 指定 domain

            mogilefs_pass {
                proxy_pass $mogilefs_path;
                proxy_hide_header Content-Type;
                proxy_buffering off;
            }
        }
    }
}

根据这个配置,如果访问:http://192.168.0.171/images/2.jpg,mogilefs 模块会用 2.jpg 为 key 向 MogileFS 请求文件。这个 key 的得出是用 URI - /images/ 得到的,也就是用请求 URI 减去与 location 名称匹配的部分得到的。

这是 mogilefs_pass 没有明确指定 key 时,默认的一种计算得出 key 的规则。实际上 mogilefs_pass 也可以明确指定 key,像下面这样:

location ~* ^/images/(.*)$ {
    mogilefs_tracker mogcluster;
    mogilefs_domain images;

    mogilefs_pass $1 {
        proxy_pass $mogilefs_path;
        proxy_hide_header Content-Type;
        proxy_buffering off;
    }
}

这里使用了正则表达式匹配,匹配的结果保存在 $1 变量中,用这一部分作为 key 向 MogileFS 发起请求。

我们之前上传了一些文件,我们查看一下上传情况:

[root@tomcat1 ~]# moglistkeys --trackers=192.168.0.181:7001 --domain=images
1.jpg
2.jpg
[root@tomcat1 ~]# moglistkeys --trackers=192.168.0.181:7001 --domain=files
1.file
[root@tomcat1 ~]# moglistkeys --trackers=192.168.0.181:7001 --domain=html
1.html
2.html

对 MogileFS 的请求是要看不同的 domain 的,在 domain 里,key 是唯一的。在上面的配置中我们用 mogilefs_domain 指令指定了 images domain。

用浏览器访问测试一下:http://192.168.0.171/images/2.jpg

Snip20160817_118.png

最后附上nginx mogilefs 模块的详解。

附录: nginx mogilefs 模块详解(翻译)


http://www.grid.net.ru/nginx/mogilefs.en.html

nginx mogilefs 模块是专为 nginx web server 打造的 MogileFS 客户端。

nginx 使用这个模块,可对 MogileFS 的 trackers 发起查询,trackers 返回文件的路径,nginx 使用第一个路径去获取文件。

mogilefs_tracker 指定 MogileFS 的 trackers

mogilefs_pass 指定文件的 key

如果 trackers 没有找到文件,nginx 返回 "404 Not found"

如果 trackers 找到了文件,但是 trackers 没有返回文件访问路径,nginx 返回 "503 Service unavailable"

如果 trackers 返回成功响应,nginx 使用这个模块尝试根据优先级最高的文件路径获取文件。

配置指令


syntax: mogilefs_pass [<key>] {<fetch block>}
default: none
severity: mandatory
context: server, location, limit_except

mogilefs_pass 指定请求文件的 key。key 可以包含任意变量。如果没有给出 key,默认使用 URI 一部分作为 key。URI 减去 与 location 匹配的部分,剩下的部分为 key。

location /download/ {
            mogilefs_pass {
                [...]
            }
        }

对于这个例子,如果请求 URI 为 /download/example.jpg,key 为 example.jpg。

mogilefs_pass 区块被称为 fetch 区块,在其中包含关于一些配置,当 nginx 从 MogileFS 的存储节点获取文件时,会使用这些配置。

$mogilefs_path 变量包含了文件的绝对 URL 路径。

fetch 区块会创建一个隐藏的内部 location,location 名为 /mogilefs_fetch_XXXXXXXX,当 trackers 返回成功响应时,会进行重定向。

$mogilefs_path1 ~ $mogilefs_path9 包含备份文件的 URL。


syntax: mogilefs_methods <[[method 1] method 2 ... ]>
default: GET
severity: optional
context: main, server, location

mogilefs_methods 指定允许的访问 MogileFS 的方法,可以指定多个。

GET 是默认方法,表示从 MogileFS 获取资源
PUT 表示创建或替换
DELETE 表示删除


syntax: mogilefs_domain <domain>
default: default
severity: optional
context: main, server, location

mogilefs_domain 指定查询哪个 MogileFS 域。可以包含变量。

默认查询 default 域。


syntax: mogilefs_class <class0> [ <class1> [ ... ] ]
default: N/A
severity: optional
context: main, server, location

This directive specifies what to use as "class" parameter when making a request to tracker. The arguments of this directives will be evaluated and first non-empty value will be used as class. Arguments can contain variables.

mogilefs_class 指定查询时的 class 参数。


syntax: mogilefs_tracker <IP|IP:port|upstream>
default: none
severity: mandatory
context: main, server, location

Specifies address of MogileFS tracker to query.

mogilefs_tracker 指定 MogileFS tracker 的访问地址。


syntax: mogilefs_noverify <on/off>
default: off
severity: optional
context: main, server, location

Enables sending of noverify argument to MogileFS.


syntax: mogilefs_connect_timeout <time>
default: 60s
severity: optional
context: main, server, location

Specifies a timeout to be used to connect to mogilefs tracker. Could not be longer than 75 seconds.

指定连接 tracker 的超时时间,最大不超过 75s。


syntax: mogilefs_send_timeout <time>
default: 60s
severity: optional
context: main, server, location

Specifies a timeout to be used to send data to mogilefs tracker. If no data will be received by mogilefs tracker during this time interval, nginx will close the connection.

发送数据到 tracker 的超时时间。


syntax: mogilefs_read_timeout <time>
default: 60s
severity: optional
context: main, server, location

Specifies a timeout to be used to receive data from mogilefs tracker. If no data will be sent by mogilefs tracker during this time interval, nginx will close the connection.

从 tracker 读取数据的超时时间。


nginx 配置示例

error_log  logs/error.log notice;

working_directory /usr/local/nginx;

http {
    include       mime.types;
    default_type  application/octet-stream;

    server {
        listen       80;

        #
        # This location could be used to retrieve files from MogileFS.
        # It is publicly available.
        #
        location /download/ { # 获取文件
            #
            # Query tracker at 192.168.2.2 for a file with the key
            # equal to remaining part of request URI
            #
            mogilefs_tracker 192.168.2.2;
            mogilefs_domain example_domain;

            mogilefs_pass {
                proxy_pass $mogilefs_path;
                proxy_hide_header Content-Type;
                proxy_buffering off;
            }
        }

        #
        # This location could be used to store or delete files in MogileFS.
        # It may be configured to be accessable only from local network.
        #
        location /upload/ { # 上传或删除文件
            allow 192.168.2.0/24;
            deny all;

            mogilefs_tracker 192.168.2.2;
            mogilefs_domain example_domain;
            mogilefs_methods PUT DELETE;

            mogilefs_pass {
                proxy_pass $mogilefs_path;
                proxy_hide_header Content-Type;
                proxy_buffering off;
            }
        }
    }
}

使用要求

nginx 0.7.1 或以上版本

下载地址:

Latest version 1.0.4:

http://www.grid.net.ru/nginx/download/nginx_mogilefs_module-1.0.4.tar.gz
MD5:2466aa02b225ad2aa1af22e6e50a9d2f
SHA1:4f6b774096a77aa8c550d8fd6a3f5d39a661d8ed

http://www.grid.net.ru/nginx/download/nginx_mogilefs_module-1.0.4.zip
MD5:4667d8b805aa4ecc94c7353d79a1020a
SHA1:8d86fa4f0fcb60cdd73195f77fbadf45ac51a875

或者这里:http://www.grid.net.ru/nginx/download

使用方法

下载文件之后,解压:

tar xvzf nginx_mogilefs_module-1.0.4.tar.gz

nginx 需要在编译安装时加入这个模块:

cd <path to nginx sources>
./configure --add-module=<path to mogilefs module sources> # 模块文件的父目录
make
make install

Licence

The above-described module is an addition to nginx web-server, nevertheless they are independent products. The licence of above-described module is BSD You should have received a copy of license along with the source code. By using the materials from this site you automatically agree to the terms and conditions of this license. If you don't agree to the terms and conditions of this license, you must immediately remove from your computer all materials downloaded from this site.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,573评论 18 139
  • 第一章 Nginx简介 Nginx是什么 没有听过Nginx?那么一定听过它的“同行”Apache吧!Ngi...
    JokerW阅读 32,633评论 24 1,002
  • 当数据量增大到超出了单个物理计算机存储容量时,有必要把它分开存储在多个不同的计算机中。那些管理存储在多个网络互连的...
    单行线的旋律阅读 1,903评论 0 7
  • 今天不知道写什么了。因为今天似乎没有输入和思考。瞎写吧。 1 如果生活中发生的意外在自己身上,我现在习惯性地把责任...
    笨蛋徒弟阅读 183评论 0 0
  • 风之壹把刀阅读 166评论 5 5