php页面不能被解析成网页，而是直接显示了页面代码
Overview问题描述解决方案参考文献 Overview 之前安装过很多次的phpMyAdmin，但最近一次安装时，出现了一个之前从来没遇到的问题：访问phpMyAdmin时，主页的php页面会被不能被解析成网页，而是直接显示了页面代码。 问题描述 文字描述已经比较清楚了，就不截图了。如果想看截图，参考 phpmyadmin showing php script [duplicate]。 对比之前的安装流程，发现这次安装Apache2时，php还没装（这个一般装Ubuntu系统时都会自动装上，这次居然没装，所以才会碰到这么少见的错误），所以php是在Apache2后面安装的。这样的问题在于，当Apache2安装时，没有检测到系统有php，所以就以为用户安装Apache2不是为了运行php网站（尽管Apache2最常见的应用就是做php网站容器，但实际上可以用作多种语言网站的容器，比如perl的CGI程序，但需要手动配置添加专门的模块，使Apache2支持perl的CGI程序，不然你通过Apache2访问你的perl页面，也会被直接显示为代码，而不是页面，参见配置Apache2服务器以CGI方式运行Perl程序），也就没有安装配置支持php的模块。 解决方案 先安装php，再安装Apache2，这样Apache2安装时，会检测到php，然后自动安装配置好支持php网站的模块。 如果不幸顺序反了（后面又发现，好像跟顺序无关，还是有可能报这个错误 =。=），那使用下面的命令手动给Apache2安装支持php的模块 先用下面的命令看看你装的php版本： php --version 出现下面的信息： PHP 7.0.32-0ubuntu0.16.04.1 (cli) ( NTS ) Copyright (c) 1997-2017 The PHP Group Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies with Zend OPcache v7.0.32-0ubuntu0.16.04.1, Copyright (c) 1999-2017, by Zend Technologies 如果你装的是php 7.0，那通过下面的命令 sudo apt-get install libapache2-mod-php7.0 然后重启Apache2 sudo service apache2 restart 就可以了。 参考文献 phpmyadmin showing php script [duplicate] 配置Apache2服务器以CGI方式运行Perl程序
FastDFS分布式文件系统在服务器集群上的安装部署
Overview1.必备软件2.libfastcommon的安装3.FastDFS的安装4. 配置FastDFS4.1 创建跟踪器和存储节点的配置文件4.2 修改tracker配置文件4.3 修改storage配置文件4.4 查看所有存储节点信息5. 测试上传文件5.1 修改tracker服务器上的client配置文件6. 测试下载文件 Overview 到目前为止，我们手里已经有了10台服务器了。之前一直说要把这些服务器搭建一个分布式文件系统，现在条件终于成熟了。这些服务器预装的系统大多数是Ubuntu16.04LTS发行版的Linux，少部分是Ubuntu14.04LTS。这次我们选择5台服务器，1台feagen(118.138.241.39)服务器作为tracker，4台服务器(Bastion3(118.138.240.146)，Bastion4(118.138.233.74)，Bastion6(118.138.233.26)，POSSUM(118.138.233.27))作为storage。这里会记录下FastDFS分布式文件系统在Ubuntu16.04上的安装部署过程的细节。 5台服务器都要安装，只是配置文件稍有不同。 1.必备软件 FastDFS 这个去GitHub上面下载源码：FastDFS libfastcommon 这个仍然要去GitHub上面下载源码：libfastcommon 这两个Zip包用wget或者在本地下载上传至服务器均可。 2.libfastcommon的安装 首先解压libfastcommon-master.zip包： unzip libfastcommon-master.zip 进入这个文件夹： cd libfastcommon-master/ 依次输入如下命令： sudo ./make.sh sudo ./make.sh install 输入第二条命令之后，会有如下显示： mkdir -p /usr/lib64 mkdir -p /usr/lib install -m 755 libfastcommon.so /usr/lib64 install -m 755 libfastcommon.so /usr/lib mkdir -p /usr/include/fastcommon install -m 644 common_define.h hash.h chain.h logger.h base64.h shared_func.h pthread_func.h ini_file_reader.h _os_define.h sockopt.h sched_thread.h http_func.h md5.h local_ip_func.h avl_tree.h ioevent.h ioevent_loop.h fast_task_queue.h fast_timer.h process_ctrl.h fast_mblock.h connection_pool.h fast_mpool.h fast_allocator.h fast_buffer.h skiplist.h multi_skiplist.h flat_skiplist.h skiplist_common.h system_info.h fast_blocked_queue.h php7_ext_wrapper.h id_generator.h char_converter.h char_convert_loader.h /usr/include/fastcommon 可以看到libfastcommon.so安装到了/usr/lib64. 下面，我们需要为两个文件创建软链接，指向FastDFS主程序的lib目录： sudo ln -s /usr/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so sudo ln -s /usr/lib64/libfastcommon.so /usr/lib/libfastcommon.so sudo ln -s /usr/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so sudo ln -s /usr/lib64/libfdfsclient.so /usr/lib/libfdfsclient.so 3.FastDFS的安装 解压fastdfs-master.zip文件: unzip fastdfs-master.zip 进入FastDFS源码根目录: cd fastdfs-master 依次输入下面两条命令： sudo ./make.sh sudo ./make.sh install 安装完成之后，所有的可执行文件都被放在了/usr/bin/目录下面，可以用下面的命令查看： ls /usr/bin/fdfs* 显示共有14个文件： /usr/bin/fdfs_appender_test /usr/bin/fdfs_download_file /usr/bin/fdfs_test1 /usr/bin/fdfs_appender_test1 /usr/bin/fdfs_file_info /usr/bin/fdfs_trackerd /usr/bin/fdfs_append_file /usr/bin/fdfs_monitor /usr/bin/fdfs_upload_appender /usr/bin/fdfs_crc32 /usr/bin/fdfs_storaged /usr/bin/fdfs_upload_file /usr/bin/fdfs_delete_file /usr/bin/fdfs_test 4. 配置FastDFS 配置文件在目录/etc/fdfs下，查看一下： ls /etc/fdfs 显示如下： client.conf.sample storage.conf.sample storage_ids.conf.sample tracker.conf.sample 4.1 创建跟踪器和存储节点的配置文件 cd /etc/fdfs sudo cp tracker.conf.sample tracker.conf sudo cp storage.conf.sample storage.conf 4.2 修改tracker配置文件 在feagen(118.138.241.39)服务器中修改tracker配置文件如下： sudo vim /etc/fdfs/tracker.conf 可以看到如下显示： # the base path to store data and log files base_path=/home/yuqing/fastdfs 这行改成如下内容： base_path=/feagen/fastdfs/tracker 这种配置文件，注意等号两边不能有空格。且上面的目录必须是真实存在的。 启动tracker： sudo fdfs_trackerd /etc/fdfs/tracker.conf start 查看监听端口： sudo netstat -unltp|grep fdfs 如果显示如下，则证明tracker启动成功： tcp 0 0 0.0.0.0:22122 0.0.0.0:* LISTEN 6951/fdfs_trackerd 4.3 修改storage配置文件 在Bastion3(118.138.240.146)服务器中，修改storage配置文件如下： sudo vim /etc/fdfs/storage.conf 可以看到如下显示： group_name=group1 base_path=/home/yuqing/fastdfs store_path0=/home/yuqing/fastdfs tracker_server=192.168.209.121:22122 修改为： group_name=group1 base_path=/bastion3_cache/fastdfs/storage store_path0=/bastion3_cache/fastdfs/storage tracker_server=118.138.241.39:22122 在Bastion4(118.138.233.74)服务器中，修改storage配置文件如下： sudo vim /etc/fdfs/storage.conf 可以看到如下显示： group_name=group1 base_path=/home/yuqing/fastdfs store_path0=/home/yuqing/fastdfs tracker_server=192.168.209.121:22122 修改为： group_name=group1 base_path=/bastion4_cache/fastdfs/storage store_path0=/bastion4_cache/fastdfs/storage tracker_server=118.138.241.39:22122 在Bastion6(118.138.233.26)服务器中，修改storage配置文件如下： sudo vim /etc/fdfs/storage.conf 可以看到如下显示： group_name=group1 base_path=/home/yuqing/fastdfs store_path0=/home/yuqing/fastdfs tracker_server=192.168.209.121:22122 修改为： group_name=group1 base_path=/bastion6_cache/fastdfs/storage store_path0=/bastion6_cache/fastdfs/storage tracker_server=118.138.241.39:22122 在POSSUM(118.138.233.27)服务器中，修改storage配置文件如下： sudo vim /etc/fdfs/storage.conf 可以看到如下显示： group_name=group1 base_path=/home/yuqing/fastdfs store_path0=/home/yuqing/fastdfs tracker_server=192.168.209.121:22122 修改为： group_name=group1 base_path=/possum/fastdfs/storage store_path0=/possum/fastdfs/storage tracker_server=118.138.241.39:22122 启动storage服务： sudo fdfs_storaged /etc/fdfs/storage.conf start 显示如下，即代表成功启动： process fdfs_storaged already running, pid: 28250 查看监听端口： sudo netstat -unltp|grep fdfs 如果显示如下，则证明storage启动成功： tcp 0 0 0.0.0.0:23000 0.0.0.0:* LISTEN 28250/fdfs_storaged 如果没有显示，则有可能是storage启动失败。 那么我们以Bastion6服务器为例，查看启动日志： tail /bastion6_cache/fastdfs/storage/logs/storaged.log 如果显示如下，代表启动失败： [2018-05-13 19:56:31] ERROR - file: storage_ip_changed_dealer.c, line: 186, connect to tracker server 118.138.2 40.146:22122 fail, errno: 110, error info: Connection timed out 连接超时； 如果显示如下，代表启动成功： [2018-05-14 12:59:40] INFO - file: tracker_client_thread.c, line: 310, successfully connect to tracker server 118.138.240.146:22122, as a tracker client, my ip is 118.138.233.26 [2018-05-14 13:00:10] INFO - file: tracker_client_thread.c, line: 1263, tracker server 118.138.240.146:22122, set tracker leader: 118.138.240.146:22122 [2018-05-14 13:03:06] INFO - file: storage_sync.c, line: 2733, successfully connect to storage server 118.138.233.74:23000, continuous fail count: 16 [2018-05-14 13:03:41] INFO - file: storage_sync.c, line: 2733, successfully connect to storage server 118.138.233.74:23000 连接成功。 超时解读： 之前跟Chris讨论的时候，刚开始以为这几天服务器都是Monash云服务器中心的，属于内网，所以相互之间的端口是不用开的，实验之后才知道不开端口是不行的。后来联系Jerico开开3个端口之后，才连接成功。这3个端口分别是22122、8888和23000： 22122:代表tracker服务端口； 8888:代表HTTP协议端口，网页上传下载文件需要这个端口； 23000:代表storage服务端口。 至此，storage存储节点安装成功。 4.4 查看所有存储节点信息 所有存储节点都启动之后，在任意一台storage上面用下面命令，查看集群状态信息： sudo /usr/bin/fdfs_monitor /etc/fdfs/storage.conf 显示如下： [2018-05-24 01:27:18] DEBUG - base_path=/bastion6_cache/fastdfs/storage, connect_timeout=10, network_timeout=60, tracker_server_count=1, anti_steal_token=0, anti_steal_secret_key length=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s, use_storage_id=0, storage server id count: 0 server_count=1, server_index=0 tracker server is 118.138.241.39:22122 group count: 1 Group 1: group name = group1 disk total space = 100665 MB disk free space = 72339 MB trunk free space = 0 MB storage server count = 4 active server count = 4 storage server port = 23000 storage HTTP port = 8888 store path count = 1 subdir count per path = 256 current write server index = 0 current trunk file id = 0 Storage 1: id = 118.138.233.26 ip_addr = 118.138.233.26 (vm-118-138-233-26.erc.monash.edu.au) ACTIVE http domain = version = 5.12 join time = 2018-05-13 19:55:46 up time = 2018-05-20 00:51:17 total storage = 483679 MB free storage = 452554 MB upload priority = 10 store_path_count = 1 subdir_count_per_path = 256 storage_port = 23000 storage_http_port = 8888 current_write_path = 0 source storage id = if_trunk_server = 0 connection.alloc_count = 256 connection.current_count = 3 connection.max_count = 3 total_upload_count = 1 success_upload_count = 1 total_append_count = 0 success_append_count = 0 total_modify_count = 0 success_modify_count = 0 total_truncate_count = 0 success_truncate_count = 0 total_set_meta_count = 0 success_set_meta_count = 0 total_delete_count = 0 success_delete_count = 0 total_download_count = 0 success_download_count = 0 total_get_meta_count = 0 success_get_meta_count = 0 total_create_link_count = 0 success_create_link_count = 0 total_delete_link_count = 0 success_delete_link_count = 0 total_upload_bytes = 87026 success_upload_bytes = 87026 total_append_bytes = 0 success_append_bytes = 0 total_modify_bytes = 0 success_modify_bytes = 0 stotal_download_bytes = 0 success_download_bytes = 0 total_sync_in_bytes = 11288 success_sync_in_bytes = 11288 total_sync_out_bytes = 0 success_sync_out_bytes = 0 total_file_open_count = 5 success_file_open_count = 5 total_file_read_count = 0 success_file_read_count = 0 total_file_write_count = 5 success_file_write_count = 5 last_heart_beat_time = 2018-05-24 01:26:54 last_source_update = 2018-05-17 01:27:29 last_sync_update = 2018-05-17 01:06:46 last_synced_timestamp = 1970-01-01 10:00:00 (never synced) Storage 2: id = 118.138.233.27 ip_addr = 118.138.233.27 (vm-118-138-233-27.erc.monash.edu.au) ACTIVE http domain = version = 5.12 join time = 2018-05-17 00:39:17 up time = 2018-05-20 00:52:30 total storage = 2015737 MB free storage = 1689979 MB upload priority = 10 store_path_count = 1 subdir_count_per_path = 256 storage_port = 23000 storage_http_port = 8888 current_write_path = 0 source storage id = if_trunk_server = 0 connection.alloc_count = 256 connection.current_count = 3 connection.max_count = 3 total_upload_count = 0 success_upload_count = 0 total_append_count = 0 success_append_count = 0 total_modify_count = 0 success_modify_count = 0 total_truncate_count = 0 success_truncate_count = 0 total_set_meta_count = 0 success_set_meta_count = 0 total_delete_count = 0 success_delete_count = 0 total_download_count = 0 success_download_count = 0 total_get_meta_count = 0 success_get_meta_count = 0 total_create_link_count = 0 success_create_link_count = 0 total_delete_link_count = 0 success_delete_link_count = 0 total_upload_bytes = 0 success_upload_bytes = 0 total_append_bytes = 0 success_append_bytes = 0 total_modify_bytes = 0 success_modify_bytes = 0 stotal_download_bytes = 0 success_download_bytes = 0 total_sync_in_bytes = 98314 success_sync_in_bytes = 98314 total_sync_out_bytes = 0 success_sync_out_bytes = 0 total_file_open_count = 5 success_file_open_count = 5 total_file_read_count = 0 success_file_read_count = 0 total_file_write_count = 5 success_file_write_count = 5 last_heart_beat_time = 2018-05-24 01:27:08 last_source_update = 1970-01-01 10:00:00 last_sync_update = 2018-05-21 16:32:36 last_synced_timestamp = 2018-05-17 01:06:38 (20m:51s delay) Storage 3: id = 118.138.233.74 ip_addr = 118.138.233.74 (vm-118-138-233-74.erc.monash.edu.au) ACTIVE http domain = version = 5.12 join time = 2018-05-13 20:03:06 up time = 2018-05-20 00:53:40 total storage = 100665 MB free storage = 72339 MB upload priority = 10 store_path_count = 1 subdir_count_per_path = 256 storage_port = 23000 storage_http_port = 8888 current_write_path = 0 source storage id = if_trunk_server = 0 connection.alloc_count = 256 connection.current_count = 3 connection.max_count = 3 total_upload_count = 2 success_upload_count = 2 total_append_count = 0 success_append_count = 0 total_modify_count = 0 success_modify_count = 0 total_truncate_count = 0 success_truncate_count = 0 total_set_meta_count = 2 success_set_meta_count = 2 total_delete_count = 0 success_delete_count = 0 total_download_count = 0 success_download_count = 0 total_get_meta_count = 0 success_get_meta_count = 0 total_create_link_count = 0 success_create_link_count = 0 total_delete_link_count = 0 success_delete_link_count = 0 total_upload_bytes = 11190 success_upload_bytes = 11190 total_append_bytes = 0 success_append_bytes = 0 total_modify_bytes = 0 success_modify_bytes = 0 stotal_download_bytes = 0 success_download_bytes = 0 total_sync_in_bytes = 174142 success_sync_in_bytes = 87026 total_sync_out_bytes = 0 success_sync_out_bytes = 0 total_file_open_count = 3 success_file_open_count = 3 total_file_read_count = 0 success_file_read_count = 0 total_file_write_count = 3 success_file_write_count = 3 last_heart_beat_time = 2018-05-24 01:27:13 last_source_update = 2018-05-17 01:06:38 last_sync_update = 2018-05-21 16:32:54 last_synced_timestamp = 2018-05-17 01:27:30 (-1s delay) Storage 4: id = 118.138.240.146 ip_addr = 118.138.240.146 (vm-118-138-240-146.erc.monash.edu.au) ACTIVE http domain = version = 5.12 join time = 2018-05-20 00:53:13 up time = 2018-05-20 00:53:13 total storage = 483679 MB free storage = 449872 MB upload priority = 10 store_path_count = 1 subdir_count_per_path = 256 storage_port = 23000 storage_http_port = 8888 current_write_path = 0 source storage id = 118.138.233.26 if_trunk_server = 0 connection.alloc_count = 256 connection.current_count = 3 connection.max_count = 3 total_upload_count = 0 success_upload_count = 0 total_append_count = 0 success_append_count = 0 total_modify_count = 0 success_modify_count = 0 total_truncate_count = 0 success_truncate_count = 0 total_set_meta_count = 0 success_set_meta_count = 0 total_delete_count = 0 success_delete_count = 0 total_download_count = 0 success_download_count = 0 total_get_meta_count = 0 success_get_meta_count = 0 total_create_link_count = 0 success_create_link_count = 0 total_delete_link_count = 0 success_delete_link_count = 0 total_upload_bytes = 0 success_upload_bytes = 0 total_append_bytes = 0 success_append_bytes = 0 total_modify_bytes = 0 success_modify_bytes = 0 stotal_download_bytes = 0 success_download_bytes = 0 total_sync_in_bytes = 98314 success_sync_in_bytes = 98314 total_sync_out_bytes = 0 success_sync_out_bytes = 0 total_file_open_count = 5 success_file_open_count = 5 total_file_read_count = 0 success_file_read_count = 0 total_file_write_count = 5 success_file_write_count = 5 last_heart_beat_time = 2018-05-24 01:27:03 last_source_update = 1970-01-01 10:00:00 last_sync_update = 2018-05-21 16:32:54 last_synced_timestamp = 2018-05-17 01:27:30 (-1s delay) 可以看到，4台storage状态都是ACTIVE，表示都启动成功了。 5. 测试上传文件 需要注意一点，我们之后的特征提取程序会在feagen服务器上，所以，我们暂时将client安装在这台服务器上。测试一下在本台服务器上上传文件，能否在所有storage服务器上面查看得到此文件的备份。 5.1 修改tracker服务器上的client配置文件 先后输入以下命令: cd /etc/fdfs sudo cp client.conf.sample client.conf sudo vim client.conf 将base_path和tracker_server两处配置修改为: base_path=/feagen/fastdfs/client tracker_server=118.138.241.39:22122 在shell当中输入下面的命令: sudo /usr/bin/fdfs_upload_file /etc/fdfs/client.conf fastdfs-master.zip 返回如下，即可表明上传成功： group1/M00/00/00/dorpG1td37yAFSYPAAZ-Sk23ivY904.zip 这个返回值，在perl当中，以如下方式获取： my $file_name = qx(sudo /usr/bin/fdfs_upload_file /etc/fdfs/client.conf fastdfs-master.zip); 到任意一个storage服务器当中查看，以Bastion3为例，至此处： /bastion3_cache/fastdfs/storage/data/00/00 发现存在如下文件: dorpG1td37yAFSYPAAZ-Sk23ivY904.zip 即表明上传成功。 6. 测试下载文件 当前目录下，运行下面的程序: sudo /usr/bin/fdfs_download_file /etc/fdfs/client.conf group1/M00/00/00/dorpG1td37yAFSYPAAZ-Sk23ivY904.zip 即可在当前目录下面找到dorpG1td37yAFSYPAAZ-Sk23ivY904.zip，表明下载成功。 至此我们的分布式文件服务器算是搭建完成了。 我们参考了这些文章，表示感谢！ FastDFS分布式文件系统集群安装与配置 FastDFS--原理篇 分布式文件系统FastDFS原理介绍 Ubuntu下安装并配置FastDFS
机器学习项目服务器上线部署全流程记录
Overview1.Java端1.1 安装JDK1.81.2 安装Tomcat71.2.1 配置Tomcat7启动内存1.3 检查安装php和Apache21.4 设置Apache2反向代理1.5 安装MySQL5.71.6 安装配置phpmyadmin2.Perl后台2.1 设置Apache2 CGI2.2 Perl相关模块安装2.3 gearman消息队列安装3. Python相关安装4. R语言及相关包安装 Overview 我们团队完整的机器学习项目已经做了两个了，分别是Bastion4和Bastion6。之前两个项目上线部署的记录过于片段化，针对现在马上要做完的Bastion3项目，我们在此完整记录搭建服务器各种环境的全部流程，以备不时之需。 完整的项目基本分为三大部分：Java处理业务逻辑；Perl后台消息队列；R机器学习模型预测。每个大部分还有很多细节，我们会在每部分都详细记录。 新申请的云服务器预装的仍然是Ubuntu16.04LTS发行版的Linux系统。 1.Java端 Java端负责将用户提交的任务信息提交至后台Perl服务器并记录在MySQL数据库中。因此这部分会安装JDK1.8，Tomcat7和MySQL5.7。 1.1 安装JDK1.8 安装JDK1.8，用apt命令很方便，不用单独下载解压。命令如下： sudo apt-get install software-properties-common -y sudo add-apt-repository ppa:webupd8team/java -y sudo apt-get update sudo apt-get install oracle-java8-installer oracle-java8-set-default -y 安装完成之后，JDK1.8会被安装在/usr/lib/jvm/java-8-oracle中，在/etc/profile或者~/.bashrc中配置JAVA_HOME变量 (我用的是~/.bashrc文件)： export JAVA_HOME=/usr/lib/jvm/java-8-oracle 安装好之后，需要以下命令使配置生效： source ~/.bashrc 1.2 安装Tomcat7 使用以下的命令安装Tomcat7: sudo apt-get install tomcat7 安装过程中，可能会报如下的错误(即使你已经配置好了JAVA_HOME): Jul 09 16:52:16 bastiondb systemd[1]: Starting LSB: Start Tomcat.... Jul 09 16:52:16 bastiondb tomcat7[6413]: * no JDK or JRE found - please...E Jul 09 16:52:16 bastiondb systemd[1]: tomcat7.service: Control process e...1 Jul 09 16:52:16 bastiondb systemd[1]: Failed to start LSB: Start Tomcat.. Jul 09 16:52:16 bastiondb systemd[1]: tomcat7.service: Unit entered fail.... Jul 09 16:52:16 bastiondb systemd[1]: tomcat7.service: Failed with resul.... Hint: Some lines were ellipsized, use -l to show in full. dpkg: error processing package tomcat7 (--configure): subprocess installed post-installation script returned error exit status 1 Errors were encountered while processing: tomcat7 E: Sub-process /usr/bin/dpkg returned an error code (1) 出现这个错误，说明tomcat7已经装好了，但是由于tomcat在启动时找不到JAVA_HOME的路径，所以报错。查看/etc/default/tomcat7，会发现下面这行： #JAVA_HOME=/usr/lib/jvm/openjdk-6-jdk 去掉注释，并改为我们自己的JAVA_HOME路径： JAVA_HOME=/usr/lib/jvm/java-8-oracle 重新运行安装命令： sudo apt-get install tomcat7 安装成功... 安装好之后，tomcat7已经在运行了，使用下面命令查看： ps aux | grep tomcat 用下面的命令启动/停止/重启Tomcat7了: sudo service tomcat7 start/stop/restart 但是现在还有一个问题，Tomcat7的端口是8080，访问的时候必须在地址后面加上:8080，这样看起来不太和谐。由于Apache已经占用了80端口，所以我们采用Apache反向代理转发Tomcat请求的方式来解决这个问题。 1.2.1 配置Tomcat7启动内存 Tomcat启动内存不大，在网站数据量增多的情况下，就会在网页访问时报错，很难根据错误信息推测到这里。我们提前增大Tomcat的启动内存。 修改/usr/share/tomcat7/bin/catalina.sh文件 sudo vim /usr/share/tomcat7/bin/catalina.sh 可以看到： # OS specific support. $var _must_ be set to either true or false. cygwin=false darwin=false os400=false case "`uname`" in CYGWIN*) cygwin=true;; Darwin*) darwin=true;; OS400*) os400=true;; 在这段前面添加： JAVA_OPTS="-Xmx4096m" 就可以将tomcat启动内存增加为4G，保存并重启tomcat，就可以了。 下面的内容选择性安装，如果没有特殊需求，最好不要修改 默认的tomcat工作主目录为/var/lib/tomcat6/，网站目录为/var/lib/tomcat6/webapps，如果你的文章会产生大量文件，并且需要存放在网站的目录下，那这个目录就不太合适，因为这个目录通常占用的是系统盘空间。 假设你有一个新的扩展盘，目录为/my_extended_disk，在上面创建一个目录为tomcatWebapps，并想把这个目录，当做你的tomcat网站目录，修改/etc/tomcat7/server.xml sudo vim /etc/tomcat7/server.xml 把 <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> 修改为 <Host name="localhost" appBase="/my_extended_disk/tomcatWebapps" unpackWARs="true" autoDeploy="true"> 然后重启服务器，就可以了。 1.3 检查安装php和Apache2 一般来说，装好的Ubuntu 16.04系统都会默认装好php和Apache2，但配置使用之前最好还是检查一下。 使用下面命令检查php： php -v 如果已经安装，大概会显示下面的内容： PHP 7.0.32-0ubuntu0.16.04.1 (cli) ( NTS ) Copyright (c) 1997-2017 The PHP Group Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies with Zend OPcache v7.0.32-0ubuntu0.16.04.1, Copyright (c) 1999-2017, by Zend Technologies 说明安装了php 7.0。如果没装的话，用下面的命令安装： sudo apt-get install php7.0 在Ubuntu 16.04中，直接用 sudo apt-get install php 默认安装的也是php7.0版本。 使用下面命令检查Apache2： apache2 -v 如果已经安装，大概会显示下面的内容： Server version: Apache/2.4.18 (Ubuntu) Server built: 2018-04-18T14:53:04 说明没装的话，用下面的命令安装： sudo apt-get install apache2 注意：如果php和Apache2都没有安装的情况下，安装顺序一定要先装php，在装Apache2，如果顺序反了，可能会导致Apache2服务器不能识别php页面，在你访问这些页面的时候，直接显示页面的代码，而不是页面本身。 参见 php页面不能被解析成网页，而是直接显示了页面代码。 1.4 设置Apache2反向代理 Apache2默认是随系统安装的，但有时候为了快速安装，去掉了也有可能。由于Apache2是随系统自动启动的，如果Apache2已经安装了并且没有人主动关闭的话，使用： ps aux | grep apache2 就应该看到进程信息，如果没看到，用下面的命令安装： sudo apt-get install apache2 安装成功之后，使用服务器的地址(比如bastion3.erc.monash.edu)访问，就应该看到Apache2的欢迎界面。 如果安装成功，但是网址却无法访问，说明80端口没开，联系管理员，打开这个端口。 修改Apache2配置文件： sudo vim /etc/apache2/apache2.conf 在文件末尾添加以下内容： ProxyPass / http://bastion3.erc.monash.edu:8080/ ProxyPassReverse / http://bastion3.erc.monash.edu:8080/ 可以让Apache2把http://bastion3.erc.monash.edu请求转发到http://bastion3.erc.monash.edu:8080/由tomcat7处理。如果你在Apache2中有一个网站cgi-bin，你访问http://bastion3.erc.monash.edu/cgi-bin时，就想访问Apache2下的cgi-bin，而不是转成http://bastion3.erc.monash.edu:8080/cgi-bin由tomcat7处理，那么改成这样就可以了： ProxyPass /phpmyadmin ! ProxyPass /cgi-bin ! ProxyPass / http://bastion3.erc.monash.edu:8080/ ProxyPassReverse / http://bastion3.erc.monash.edu:8080/ 注意：这里同时写了一个phpmyadmin的配置，是因为我们会使用phpmyadmin管理和访问数据库。参考1.6小节的内容。 重启Apache2服务： sudo service apache2 restart 如果报错： Job for apache2.service failed because the control process exited with error code. See "systemctl status apache2.service" and "journalctl -xe" for details. 根据提示，使用 systemctl status apache2.service 查看错误信息： Jul 09 17:15:56 bastiondb apache2[7914]: * The apache2 configtest failed. Jul 09 17:15:56 bastiondb apache2[7914]: Output of config test was: Jul 09 17:15:56 bastiondb apache2[7914]: AH00526: Syntax error on line 223 o Jul 09 17:15:56 bastiondb apache2[7914]: Invalid command 'ProxyPass', perhap Jul 09 17:15:56 bastiondb apache2[7914]: Action 'configtest' failed. Jul 09 17:15:56 bastiondb apache2[7914]: The Apache error log may have more Jul 09 17:15:56 bastiondb systemd[1]: apache2.service: Control process exite Jul 09 17:15:56 bastiondb systemd[1]: Failed to start LSB: Apache2 web serve Jul 09 17:15:56 bastiondb systemd[1]: apache2.service: Unit entered failed s Jul 09 17:15:56 bastiondb systemd[1]: apache2.service: Failed with result 'e 发现Apache2不能识别新添加的ProxyPass，导致重启失败。这是因为Apache2的代理模块proxy_http未启用，使用下面命令： sudo a2enmod proxy_http 重新启动Apache2： sudo service apache2 restart 成功...这时候再输入服务器的地址，即可访问到tomcat的默认项目了，后面不用加:8080。 1.5 安装MySQL5.7 先更新一下系统，再用apt-get命令直接安装： sudo apt-get update sudo apt-get install mysql-server 安装过程会让选择yes/no,选yes；还有就是设置root用户密码。 安装完成可以查看是不是安装好了,即查看MySQL的状态： sudo service mysql status 显示如下即可： ?.mysql.service - MySQL Community Server Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled) Active: active (running) since Mon 2018-03-19 14:35:03 UTC; 27s ago Main PID: 17432 (mysqld) CGroup: /system.slice/mysql.service ?..17432 /usr/sbin/mysqld Mar 19 14:35:02 bastion3 systemd[1]: Starting MySQL Community Server... Mar 19 14:35:03 bastion3 systemd[1]: Started MySQL Community Server. 安装好之后，可以用以下命令启动/停止/重启： sudo service mysql stop/stop/restart 本项目的数据的形式和之前项目的很相似，所以可以将之前的Bastion6项目数据库文件导出，然后再导入本项目。 导出命令： mysqldump -uroot -p bastion6 > bastion6.sql 回车之后输入数据库密码即可。 将bastion6.sql内容修改为自己需要的： CREATE DATABASE IF NOT EXISTS bastion3; USE bastion3; DROP TABLE IF EXISTS `job`; CREATE TABLE `job` ( `jobID` int(100) NOT NULL AUTO_INCREMENT, `jobName` varchar(256) NOT NULL, `orgnization` varchar(1000) , `email` varchar(100) NOT NULL, `status`int(1) NOT NULL, `createTime` datetime NOT NULL, `startTime` datetime , `endTime` datetime , `sequence` longtext NOT NULL, `sequenceNumber` int(10) NOT NULL, PRIMARY KEY (`jobID`) ) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `predictresult`; CREATE TABLE `predictresult` ( `jobID` int(100) NOT NULL AUTO_INCREMENT, `jobName` varchar(256) NOT NULL, `predictionResult` longtext, PRIMARY KEY (`jobID`) ) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `pssm`; CREATE TABLE `pssm` ( `id` int(100) NOT NULL AUTO_INCREMENT, `seq` varchar(10000), `output` varchar(500) , PRIMARY KEY (`id`) ) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `downloadInfo`; CREATE TABLE `downloadInfo` ( `id` int(100) NOT NULL AUTO_INCREMENT, `type` varchar(10000), `count` varchar(500) , PRIMARY KEY (`id`) ) DEFAULT CHARSET=utf8; INSERT INTO `downloadInfo` VALUES (1,'originalData','0'),(2,'independentData','0'),(3,'predictData','0'); 然后登录数据库: mysql -u root -p 回车之后输入密码。 将上面的SQL命令输入，回车即可创建完整的数据库表。 至此，Java部分安装配置就算完成了。 1.6 安装配置phpmyadmin 命令行查看和管理MySQL还是有点不方便，所以我们这里安装一个phpmyadmin，就可以使用web界面查看和操作数据库。 这时候直接使用网址+phpmyadmin，浏览器会显示找不到phpmyadmin。那是因为apache2默认的网站路径是/var/www/html，而不是/var/www，编辑/etc/apache2/sites-enabled/000-default.conf： sudo vim /etc/apache2/sites-enabled/000-default.conf 把DocumentRoot /var/www/html修改为DocumentRoot /var/www 重启apache2，重新访问，就可以了。 安装phpmyadmin sudo apt-get install phpmyadmin 安装过程中，会弹出一个Configuring phpmyadmin框，让你选择要运行phpmyadmin的服务器，默认是apache2，还可以选择lighttpd，因为我们之前安装的web服务器是apache2，所以直接敲回车就行了。 然后安装一阵子继续弹出一个Configuring phpmyadmin框，提示你是否需要配置数据库，毕竟phpmyadmin本身就是管理数据库的，不配置好数据库，也就没有安装的意义了，默认选择的是Yes，回车，会转到新的界面让你输入密码，也就是刚才安装MySQL时设置的密码，输入之后敲回车确认，再重复确认密码，完成安装。 安装完成之后，发现在apache2的网站目录/var/www下，并没有多出来一个phpmyadmin，这是因为Ubuntu系统在安装phpmyadmin时，默认安装在了/usr/share/下: ls /usr/share/ 就可以看到里面有phpmyadmin。 我们直接创建一个软连接： sudo ln -s /usr/share/phpmyadmin /var/www/phpmyadmin 这样就可以在/var/www下访问到phpmyadmin。 如果发现虽然能访问phpmyadmin了，但是浏览器直接显示了代码，而不是网页，说明apache2没有自动安装解析php的包。 先用下面的命令看看你装的php版本： php --version 如果是php7.0，那么安装下面的包： sudo apt-get install libapache2-mod-php7.0 然后重启apache2，现在应该可以访问到正常的网页了。具体原因，参见php页面不能被解析成网页，而是直接显示了页面代码。 用户名是root，密码是MySQL设置的密码。 注意： 由于phpmyadmin网站暴露在公网之后，可能会招到攻击，比如使用脚本破解密码等，因此我们需要配置一下白名单，中只允许指定IP访问。修改/etc/apache2/apache2.conf，在里面添加: <Directory /var/www/phpmyadmin> Options Indexes FollowSymLinks Includes ExecCGI AllowOverride All # Key configuration for white list Order Deny,Allow Allow from xx.xx.xx.xx Allow from xx.xx.xx.xy Deny from All </Directory> 其中，/var/www/phpmyadmin为phpmyadmin网站路径，xx.xx.xx.xx和xx.xx.xx.xy为允许访问的IP地址。 2.Perl后台 Perl的主要作用是它的消息队列以及和Java通讯的CGI模块。因此需要配置Apache2 CGI以及安装相应的Perl模块。 2.1 设置Apache2 CGI 在安装CGI模块之前，最好先将make安装好： sudo apt-get install make 然后安装Perl的CGI模块，推荐使用cpan命令安装 (一定要用sudo权限打开)： 先打开cpan： sudo cpan 然后输入下面的命令: install CGI 下面修改CGI程序的根目录： sudo vim /etc/apache2/apache2.conf 将配置文件中对应的地方改为如下： <Directory /var/www/> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory> 在/var/www/cgi-bin目录中新建一个cgi_test.pl文件，以创建一个Perl CGI网站，文件内容为： #!/usr/bin/perl -w use warnings; use CGI qw(:standard); #! must use 'my' to define a variable print header; my $now_string = localtime(); print "Hello, CGI using Perl! It's $now_string NOW! "; 为此文件添加执行权限： sudo chmod +x /var/www/cgi-bin/cgi_test.pl 在命令行中执行此文件： sudo perl /var/www/cgi-bin/cgi_test.pl 如果之前的设置正确，则会显示以下内容： Content-Type: text/html; charset=ISO-8859-1 Hello, CGI using Perl! It's Tue Mar 20 14:17:15 2018 NOW! 至此，虽然可以在命令行中运行此程序，但是还不能作为服务器远程访问。我们需要做以下配置，让Apache2对Perl支持： sudo vim /etc/apache2/sites-enabled/000-default.conf 将下面的内容： <VirtualHost *:80> ... ServerAdmin webmaster@localhost DocumentRoot /var/www ... ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> 修改为： <VirtualHost *:80> ... ServerAdmin webmaster@localhost DocumentRoot /var/www ScriptAlias /cgi-bin/ /var/www/cgi-bin/ <Directory "/var/www/cgi-bin"> AllowOverride all Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all AddHandler cgi-script .cgi .pl </Directory> ... ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> 在Apache中启用CGI模块。注意 VirtualHost *:80这里的端口号尽量不要改动，因为使用网址或者ip地址访问，不加端口号的话，默认就是访问80端口号，如果改了端口号，就得显示加上。 默认情况下，这个模块是没有开启的，这点可以用以下命令确认： ls -l /etc/apache2/mods-enabled/ | grep cgi 显示为空表示当前启用的与CGI相关的模块为空。用下面的命令查看有哪些模块可以被启用： ls -l /etc/apache2/mods-available/ | grep cgi 显示如下： -rw-r--r-- 1 root root 74 Mar 19 2016 authnz_fcgi.load -rw-r--r-- 1 root root 115 Mar 19 2016 cgid.conf -rw-r--r-- 1 root root 60 Mar 19 2016 cgid.load -rw-r--r-- 1 root root 58 Mar 19 2016 cgi.load -rw-r--r-- 1 root root 89 Mar 19 2016 proxy_fcgi.load -rw-r--r-- 1 root root 89 Mar 19 2016 proxy_scgi.load 这样，我们在mods-enabled中创建软连接，指向mods-available中的cgid.*文件： sudo ln -s /etc/apache2/mods-available/cgid.load /etc/apache2/mods-enabled/ sudo ln -s /etc/apache2/mods-available/cgid.conf /etc/apache2/mods-enabled/ 重启Apache服务器： sudo service apache2 restart 此时，如果在浏览器中访问：http://bastion3.erc.monash.edu/cgi-bin/cgi_test.pl应该会显示： Hello, CGI using Perl! It's Tue Mar 20 14:17:15 2018 NOW! 这样就算成功了。 2.2 Perl相关模块安装 在安装这部分模块之前，最好保证gcc和g++两个程序已经安装好了，如果没有安装好，那么按照以下命令安装： sudo apt-get install gcc sudo apt-get install g++ Perl模块仍然要使用sudo cpan命令安装，然后依次输入以下命令： install Storable install DBI install IO::All install Bio::SeqIO install Bio::Seq install Capture::Tiny install SOAP::Transport::HTTP install MIME::Lite install Mail::Sendmail 2.3 gearman消息队列安装 当前最新版的gearman是gearmand-1.1.12。我们执行下面几步，先将其下载到本地主文件夹，并解压缩。 cd ~ sudo apt-get update wget https://launchpad.net/gearmand/1.2/1.1.12/+download/gearmand-1.1.12.tar.gz tar zxvf gearmand-1.1.12.tar.gz cd gearmand-1.1.12/ 进入gearmand-1.1.12文件夹后，如果直接运行 ./configure 就会报缺少如下几个依赖包错误： configure: error: could not find boost configure: error: Could not find a version of the library configure: error: could not find gperf configure: error: Unable to find libevent configure: error: Unable to find libuuid 所以，我们先将这些依赖都安装好： sudo apt-get install libboost-dev sudo apt-get install libboost-all-dev sudo apt-get install gperf sudo apt-get install libevent-dev sudo apt-get install uuid-dev 安装好之后，如果没有错误，则运行： ./configure 然后仍在gearmand-1.1.12文件夹下运行下面两条命令，编译时间比较长： sudo make sudo make install 这个过程中如果出现了错误，就运行下面的命令清除一下之前编译产生的可执行文件以及object文件(即扩展名为o的文件)： sudo make clean 继续重新安装编译： ./configure sudo make sudo make install 没有错误的话，就安装gearman的job server： sudo apt-get install gearman-job-server 安装好以后，运行一下gearman: gearman -d 会提示错误： gearman: error while loading shared libraries: libgearman.so.8: cannot open shared object file: No such file or directory 这表示找不到libgearman.so.8所在的目录。这时我们打开/etc/ld.so.conf文件： sudo vim /etc/ld.so.conf 添加一句话: include /usr/local/lib 保存退出，并执行下面这句： sudo /sbin/ldconfig 这样就不会出错了。 启动一下job server： gearmand -d 报错如下： gearmand: Could not open log file "/usr/local/var/log/gearmand.log", from "/home/ubuntu/gearmand-1.1.12", switching to stderr. (No such file or directory) 我们这样解决：在/usr/local/下面新建var子目录，进去，新建log子目录，再进去，新建文件gearmand.log。这样就没有问题了。 用sudo权限运行下面的命令： sudo gearmand -d -L 127.0.0.1 -p 4730 -d表示daemon，在后台运行； -L表示监听的ip，默认是localhost； -p表示监听的端口号port，默认是4730。 此时，使用下面的命令，即可查看gearman当前状态： sudo lsof -i:4730 如果显示如下，就表示成功安装： COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gearmand 27831 gearman 10u IPv4 320614 0t0 TCP localhost:4730 (LISTEN) 这样gearman在ubuntu16.04上面就安装成功了。 至此，我们还需要安装三个Perl模块： Gearman::Server Gearman::Client Gearman::Worker 在安装Gearman::Server的时候，需要依赖Net::SSLeay,IO::Socket::SSL和Gearman::Util,而安装Net::SSLeay的时候，如果没有libssl-dev这个系统包，是安装不上Net::SSLeay的。所以我们必须要先安装好libssl-dev。 sudo apt-get install libssl-dev 然后必须按顺序依次安装Net::SSLeay，顺序不能乱: 继续打开cpan： sudo cpan 然后 install Net::SSLeay install IO::Socket::SSL install Gearman::Util 依次安装： install Gearman::Server install Gearman::Client install Gearman::Worker 都安装后之后，可以重启一下机器。 启动之后，用如下命令观察4730端口，查看gearman的job server是否已经启动(gearmand -d这条命令是否生效)： sudo lsof -i:4730 显示如下： COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gearmand 907 gearman 10u IPv4 21414 0t0 TCP localhost:4730 (LISTEN) 可以知道，这个server是开机自启动的。 至此，gearman消息队列的安装就算完成了。 每次重启服务器，都需要重启worker脚本。worker和client脚本都会放在/var/www/cgi-bin/目录下面，因此启动worker和命令如下(比如我们要开16个worker)： sudo nohup perl /var/www/cgi-bin/worker_bastion3.pl >~/worker_bastion3_log_1.txt & sudo nohup perl /var/www/cgi-bin/worker_bastion3.pl >~/worker_bastion3_log_2.txt & sudo nohup perl /var/www/cgi-bin/worker_bastion3.pl >~/worker_bastion3_log_3.txt & sudo nohup perl /var/www/cgi-bin/worker_bastion3.pl >~/worker_bastion3_log_4.txt & ... sudo nohup perl /var/www/cgi-bin/worker_bastion3.pl >~/worker_bastion3_log_16.txt & 而client会在提交任务的时候，由Java调用，因此不需要我们手动启动。 现在不再需要手动重启了。 运行sudo crontab -e（e代表edit），可以编辑创建一个cron任务，可能需要先选择编辑器，熟悉哪个选哪个就行了，我选的3. vim： @reboot cd /var/www/cgi-bin && sudo perl /var/www/cgi-bin/worker_Bastion3.pl > /var/log/worker_Bastion3_log_1.txt 2>&1 @reboot cd /var/www/cgi-bin && sudo perl /var/www/cgi-bin/worker_Bastion3.pl > /var/log/worker_Bastion3_log_2.txt 2>&1 @reboot cd /var/www/cgi-bin && sudo perl /var/www/cgi-bin/worker_Bastion3.pl > /var/log/worker_Bastion3_log_3.txt 2>&1 @reboot cd /var/www/cgi-bin && sudo perl /var/www/cgi-bin/worker_Bastion3.pl > /var/log/worker_Bastion3_log_4.txt 2>&1 ... @reboot cd /var/www/cgi-bin && sudo perl /var/www/cgi-bin/worker_Bastion3.pl > /var/log/worker_Bastion3_log_16.txt 2>&1 这样之后服务器重启，就不必再手动开这些进程了。 使用sudo crontab -l(l代表list)可以查看所有你创建的cron任务。注意：用sudo创建的任务用sudo查看，才能看得到，同样sudo crontab -l查看不到crontab -e 创建的任务。 相对于手动执行脚本，这里的配置有三个变化： 把log日志重定向到了/var/log/目录下，可以直接在这里查看错误信息。 不再需要用nohup命令了，nohup是因为我们用ssh登录，需要在ssh退出后脚本依然运行才这么做的，现在重启时系统系统这个脚本，这个脚本会一直留在系统中，不必再用nohup。 由cron启动脚本时，当前工作目录可能跟想象中不一样，我们执行sudo perl /var/www/cgi-bin/worker_bastion3.pl（不管用不用nohup）时，脚本的工作目录在/var/www/cgi-bin，而由cron执行时，当前工作目录却是sudo perl /var/www/cgi-bin/worker_bastion3.pl，导致脚本中使用相对目录的地方会报错，比如这里： $seqs > io("sequence/$jobName.fasta"); … 当然，好的习惯是尽量不要用相对路径，因为你不知道这个脚本会在哪里或者哪种环境下执行，执行时的当前工作目录也可能无法预测。 3. Python相关安装 Python在此系列项目当中，主要是用来提取特征，因此需要依次安装以下几个工具包： sudo apt-get install python-scipy sudo apt-get install python-dev sudo apt-get install python-numpy sudo apt-get install python-matplotlib sudo apt-get install python-pandas sudo apt-get install python-sklearn 4. R语言及相关包安装 R语言在此系列项目当中，既要提取部分特征，又要负责训练模型，还要使用ggplot2画图，所以有很多相关的包是要安装的。 首先安装R语言： sudo apt-get install r-base 然后安装以下几个包 (其实这个服务器在机器学习部分主要作用就是预测，因为训练部分的工作都会在线下进行，不过仍然将所有机器学习部分必须的包都记录在此，以备查阅)。 sudo权限启动R语言： sudo R 这里必须用sudo启动R安装，这样R包一般会安装在/usr/local/lib/R/site-library中，不用sudo权限，会把R包装在当前用户的个人空间，比如~/R/x86_64-pc-linux-gnu-library/3.2。在普通状态下这两个位置并无不同，但是如果当前用户进入root模式(比如用sudo -i命令)，再使用R就不能正确找到R包了。很不幸，因为我们用了crontab设置了worker开机自启动，导致在sudo模式调用了worker的程序，并由worker进一步调用了R程序，就没能找到普通权限下安装的R包，导致报错了。 然后在启动的R语言对话框中输入以下命令： install.packages("protr") install.packages("caret") install.packages("e1071") install.packages("plyr") install.packages("RSNNS") install.packages("ROCR") install.packages("ggplot2") install.packages("randomForest") 至此一个完整的机器学习项目的服务器就已经算是搭建好了。 需要格外注意的是，因为我们的缓存文件都在外接硬盘上，所以每次都重启服务器之后都需要手动加载此硬盘： sudo mount /dev/vdb /bastion3_cache/ 现在不用这么做了，通过配置/etc/fstab可以直接再重启服务器时自动挂载，在/etc/fstab文件中添加下面的行（需要用sudo编辑）： /dev/vdb /bastion3_cache auto defaults,nofail,x-systemd.requires=cloud-init.service,comment=cloudconfig 0 2 其中，/dev/vdb是要挂在的物理硬盘盘符，/bastion3_cache是挂载盘目录。 如果你不清楚哪个盘需要挂载，用df -h查看： ubuntu@bastion3:~$ df -h Filesystem Size Used Avail Use% Mounted on udev 32G 0 32G 0% /dev tmpfs 6.3G 180M 6.2G 3% /run /dev/vda1 9.9G 4.2G 5.2G 45% / tmpfs 32G 0 32G 0% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock tmpfs 32G 0 32G 0% /sys/fs/cgroup /dev/vdb 473G 9.0G 440G 3% /bastion3_cache tmpfs 6.3G 0 6.3G 0% /run/user/1000 如果有硬盘为未挂载，那Mounted on这列应该是空的，或者是/mnt（默认挂载在/mnt目录）。挂载前需要自己创建一个目录，比如sudo mkdir /bastion3_cache。
Overview1.下载安装gearman2.安装perl扩展包3.运行测试perl脚本 Overview Bastion4这个项目经过我们实验验证和安全考虑，决定舍弃kafka而转用gearman这个消息队列框架，具体分析将在后续文章中给出，这里只记录gearman相关的安装。 1.下载安装gearman 最新版的gearman是gearmand-1.1.12。我们执行下面几步，先将其下载到本地主文件夹，并解压缩。 sudo apt-get update wget https://launchpad.net/gearmand/1.2/1.1.12/+download/gearmand-1.1.12.tar.gz tar zxvf gearmand-1.1.12.tar.gz cd gearmand-1.1.12/ 进入gearmand-1.1.12文件夹后，如果直接运行 ./configure 就会报缺少如下几个依赖包错误： configure: error: could not find boost configure: error: Could not find a version of the library configure: error: could not find gperf configure: error: Unable to find libevent configure: error: Unable to find libuuid 所以，我们先将这些依赖都安装好： sudo apt-get install libboost-dev sudo apt-get install libboost-all-dev sudo apt-get install gperf sudo apt-get install libevent-dev sudo apt-get install uuid-dev 安装好之后，如果没有错误，仍在gearmand-1.1.12文件夹下运行下面两条命令，编译时间比较长： sudo make sudo make install 这个过程中如果出现了错误，就运行下面的命令清除一下之前编译产生的可执行文件以及object文件(即扩展名为o的文件)： sudo make clean 继续重新安装编译： ./configure sudo make sudo make install 没有错误的话，就安装gearman的job server： sudo apt-get install gearman-job-server 安装好以后，运行一下gearman: gearman 会提示错误： gearman: error while loading shared libraries: libgearman.so.8: cannot open shared object file: No such file or directory 这表示找不到libgearman.so.8所在的目录。这时我们打开/etc/ld.so.conf文件： sudo vim /etc/ld.so.conf 添加一句话: include /usr/local/lib 保存退出，并执行下面这句： sudo /sbin/ldconfig 这样就不会出错了。 启动一下job server： gearmand -d 报错如下： gearmand: Could not open log file "/usr/local/var/log/gearmand.log", from "/home/young/gearmand-1.1.12", switching to stderr. (No such file or directory) 我们这样解决：在/usr/local/下面新建var子目录，进去，新建log子目录，再进去，新建文件gearmand.log。这样就没有问题了。 用sudo权限运行下面的命令： sudo gearmand -d -L 127.0.0.1 -p 4730 -d表示daemon，在后台运行； -L表示监听的ip，默认是localhost； -p表示监听的端口号port，默认是4730。 这样gearman在ubuntu14.04上面就安装成功了。 2.安装perl扩展包 perl端需要3个扩展包： Gearman::Server Gearman::Client Gearman::Worker 可以用之前Chris的博客BioPerl（一）：安装BioPerl中介绍的方法，先安装好CPAN。 之后，用sudo权限打开CPAN： sudo cpan 然后依次安装3个扩展包： install Gearman::Server install Gearman::Client install Gearman::Worker 都安装后之后，可以重启一下机器。 启动之后，用如下命令观察4730端口，查看gearman的job server是否已经启动(gearmand -d这条命令是否生效)： sudo lsof -i:4730 如果不加sudo是看不到的，所以linux命令建议都要加上sudo。 可以看到： COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gearmand 576 gearman 9u IPv4 12861 0t0 TCP *:4730 (LISTEN) gearmand 576 gearman 10u IPv6 12862 0t0 TCP *:4730 (LISTEN) 服务器端可能会只显示 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gearmand 576 gearman 9u IPv4 12861 0t0 TCP *:4730 (LISTEN) 可见这个server是开机启动的。 3.运行测试perl脚本 client.pl将消息发送到server，相当于生产者； worker.pl来处理这些消息，并把结果返回给client.pl， 相当于消费者。我们观察输出即可。 client.pl代码如下： #!/usr/bin/perl use strict; use warnings; use Gearman::Client; use Storable; use Storable qw(freeze); use Storable qw(thaw); use IO::All; # fork this process my $pid = fork(); if ($pid == 0) { # do this in the child print "start new client \n"; my $client = Gearman::Client->new; print "finish new client \n"; print "start job_servers \n"; $client->job_servers('127.0.0.1',4730); print "finish job_servers \n"; # 设置异步任务 print "start new_task_set \n"; my $tasks = $client->new_task_set; print "finish new_task_set \n"; print "start add_task \n"; #handle database my @rows=('hello','byebye'); $tasks->add_task( # 开始任务,多个参数 showMessage => freeze(\@rows), # 注册回调函数 { on_complete => \&complete }, ); print "finish add_task \n"; print "start wait \n"; # 等待任务结束 $tasks->wait; print "finish wait \n"; exit; } print "The background task will be finished shortly.\n"; sub complete{ my $ret = ${ $_[0] }; #io("complete.txt")->print($ret); print $ret, "\n"; } worker.pl代码如下： #!/usr/bin/perl use strict; use warnings; use Gearman::Worker; use Storable qw(thaw); use Storable qw(freeze); print "start new worker \n"; my $worker = Gearman::Worker->new; print "finish new worker \n"; print "start job_servers \n"; $worker->job_servers('127.0.0.1',4730); print "finish job_servers \n"; # Worker 注册可以使用的功能 print "start register_function \n"; $worker->register_function( showMessage => \&showMessage ); print "finish register_function \n"; # 等待连接的任务 print "start work \n"; $worker->work while 1; print "finish work \n"; sub showMessage{ my @row=@{ thaw($_[0]->arg) }; my $job = \@row; print "\n"; print "$row[0] \n"; print "$row[1] \n"; print "start sleep \n"; my $date = &getTime(); print $date->{date}," ",$date->{hour},":",$date->{minute},":",$date->{second}; print "\n"; sleep(10); print "finish sleep \n"; $date = &getTime(); print $date->{date}," ",$date->{hour},":",$date->{minute},":",$date->{second}; print "\n"; my $ret = "hello world"; return $ret; } sub getTime { my $time = shift || time(); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time); $year += 1900; $mon ++; $min = '0'.$min if length($min) < 2; $sec = '0'.$sec if length($sec) < 2; $mon = '0'.$mon if length($mon) < 2; $mday = '0'.$mday if length($mday) < 2; $hour = '0'.$hour if length($hour) < 2; my $weekday = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat')[$wday]; return { 'second' => $sec, 'minute' => $min, 'hour' => $hour, 'day' => $mday, 'month' => $mon, 'year' => $year, 'weekNo' => $wday, 'wday' => $weekday, 'yday' => $yday, 'date' => "$year-$mon-$mday" }; } 我们先运行worker.pl sudo perl worker.pl 再查看4730端口： sudo lsof -i:4730 发现多了两个占用： COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gearmand 576 gearman 9u IPv4 12861 0t0 TCP *:4730 (LISTEN) gearmand 576 gearman 10u IPv6 12862 0t0 TCP *:4730 (LISTEN) gearmand 576 gearman 33u IPv4 191760 0t0 TCP localhost:4730->localhost:37151 (ESTABLISHED) perl 4508 root 3u IPv4 192605 0t0 TCP localhost:37151->localhost:4730 (ESTABLISHED) 再运行多个client.pl sudo perl client.pl 每启动一个client.pl就会多两个端口占用。 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gearmand 576 gearman 9u IPv4 12861 0t0 TCP *:4730 (LISTEN) gearmand 576 gearman 10u IPv6 12862 0t0 TCP *:4730 (LISTEN) gearmand 576 gearman 33u IPv4 191760 0t0 TCP localhost:4730->localhost:37151 (ESTABLISHED) gearmand 576 gearman 34u IPv4 215493 0t0 TCP localhost:4730->localhost:37189 (ESTABLISHED) perl 4508 root 3u IPv4 192605 0t0 TCP localhost:37151->localhost:4730 (ESTABLISHED) perl 4709 root 3u IPv4 214688 0t0 TCP localhost:37189->localhost:4730 (ESTABLISHED) 每运行一次worker.pl表示启动一个新的消费者。如果只启动一个消费者，而启动多个生产者，就可以很好地观察到“排队”效果了：一定是一个任务执行完成之后才会开始处理另一个任务。 这样gearman和perl扩展包的安装使用就结束了。 这篇文章主要参考了如下几篇文章： Gearman Job Server Gearman 使用 Gearman 实现分布式处理 Gearman 安装使用 以及 问题处理 Ubuntu下Gearman安装搭建
Overview1. Apache2基本配置2. 配置Apache2支持Perl CGI程序2.1 创建一个Perl网站2.2 配置Apache服务器3. 可能遇到的一些错误3.1 500 Internal Server Error3.2 503 Service Unavailable3.3 404 Not Found3.4 403 Forbidden4. 小结参考文章 Overview 这次我们开发Bastion4服务器使用了JAVA+Perl的架构，后端用Perl做服务器提供Webservice，用JAVA框架Struts接收处理用户请求，再跟Perl服务器交互。 我们使用Apache2作为Perl服务器，由于Apache2默认并不支持Perl，因此需要简单配置一下，使得Apache2以CGI的方式支持Perl运行。在配置的过程中，参考了一些网页，但由于各个网页都没有完整地描述配置过程（至少在我们的服务器上是这样），所以这里记录一下我们配置的全过程。 1. Apache2基本配置 我们为Bastion4申请了一个新的云服务器，版本为NeCTAR Ubuntu 16.04 LTS x86_64，自带了Apache2服务器，下面简单列出了Apache2的一些基本信息： /etc/apache2/ Apache2的配置文件目录 /var/www/ Apache2的网站存放目录 /var/log/apache2/ Apache2的日志存放目录 /etc/apache2/apache2.conf Apache2的配置主文件，以前的Apache版本中，主配置文件名字叫httpd.conf，所以很多网页中还用到这个名字，不知道的话会造成很多混淆。尽管apache2.conf是主配置文件，但是多数具体的配置信息并不是写在这个文件中的，/etc/apache2目录中有还有几个文件夹分别记录了几个模块的配置信息，apache2.conf负责把他们导入整合在一起。 /etc/apache2/sites-enabled/ 看名字就可以明白，Apache2的网站配置文件一般都是放在这个目录中的，这个目录下的000-default.conf是我们经常要修改的配置文件。 我们假定Apache存放网站的根目录是/var/www/，如果不确定(因为默认好像是/var/www/html这个目录)，打开/etc/apache2/apache2.conf目录，查看下面的配置： <Directory /var/www/> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory> 如果在你的配置里是 <Directory /var/www/html/> 修改成 <Directory /var/www/> 这样，当你把一个名叫Mywebsite的网站(通常是一个文件夹)，扔到/var/www中时，你就可以以http://localhost/Mywebsite/访问到这个网站了。 如果你的配置是<Directory /var/www/html/>，那你需要把你的网站扔到/var/www/html中，才能使用http://localhost/Mywebsite/访问，这是因为<Directory>设置了Apache的根目录，当你使用http://****/Mywebsite访问时，Apache会去当前的根目录/Mywebsite找这个网站。 由于Apache的端口号默认是80，而不加端口号的url访问都会被服务器默认定向到80端口，因此这里不需要添加端口号，如果你手动将Apache端口号修改成了别的端口号（比如8888），那你需要使用http://localhost:8888/Mywebsite/访问这个网站。 2. 配置Apache2支持Perl CGI程序 2.1 创建一个Perl网站 在/var/www下创建网站的目录，我们创建一个名字叫cgi-bin的网站作为例子： mkdir /var/www/cgi-bin 需要使用管理员权限的命令，请自己加上sudo。 在/var/www/cgi-bin中创建一个cgi_test.pl脚本，输入下面的内容： #!/usr/bin/perl -w use warnings; use CGI qw(:standard); #! must use 'my' to define a variable print header; my $now_string = localtime(); print "Hello, CGI using Perl! It's $now_string NOW! "; 为cgi_test.pl添加可执行权限： chmod +x /var/www/cgi-bin/cgi_test.pl 在命令行运行cgi_test.pl： /var/cgi-bin/www/cgi_test.pl 会得到下面的结果： Content-Type: text/html; charset=ISO-8859-1 Hello, CGI using Perl! It's Mon Aug 1 03:35:42 2016 NOW! 成功运行这个perl脚本需要先安装perl的CGI库 尽管现在我们可以在本地以命令行的形式运行这个perl脚本，但还是不能让这个脚本在服务器中运行，访问http://localhost:8888/cgi-bin/cgi_test.pl，浏览器会直接显示整个脚本的内容： #!/usr/bin/perl -w use warnings; use CGI qw(:standard); #! must use 'my' to define a variable print header; my $now_string = localtime(); print "Hello, CGI using Perl! It's $now_string NOW! "; 而不是只把脚本打印的内容显示出来，所以，接下来我们继续配置Apache服务器。 2.2 配置Apache服务器 打开/etc/apache2/sites-enabled/000-default.conf配置文件，找到下面的配置信息： <VirtualHost *:8080> ... ServerAdmin webmaster@localhost DocumentRoot /var/www ... ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> 我们需要添加一些配置信息，添加之后，000-default.conf的内容如下： <VirtualHost *:8080> ... ServerAdmin webmaster@localhost DocumentRoot /var/www ScriptAlias /cgi-bin/ /var/www/cgi-bin/ <Directory "/var/www/cgi-bin"> AllowOverride all Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all AddHandler cgi-script .cgi .pl </Directory> ... ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> 很容易看得出来，这部分配置主要是用来添加Apache对perl的支持。 默认情况下，Apache并没有启用CGI模块，我们可以从/etc/apache2/mods-enabled/中确认这点。 ls -l /etc/apache2/mods-enabled/ | grep cgi 显示结果为空。 ls -l /etc/apache2/mods-available/ | grep cgi 显示下面的结果： -rw-r--r-- 1 root root 74 Mar 19 09:48 authnz_fcgi.load -rw-r--r-- 1 root root 58 Mar 19 09:48 cgi.load -rw-r--r-- 1 root root 115 Mar 19 09:48 cgid.conf -rw-r--r-- 1 root root 60 Mar 19 09:48 cgid.load -rw-r--r-- 1 root root 89 Mar 19 09:48 proxy_fcgi.load -rw-r--r-- 1 root root 89 Mar 19 09:48 proxy_scgi.load 从名字上就可以看出来，mods-enabled中存放的是已经启用的模块，mods-available中存放的是所有可用的模块，所以启用CGI模块变得很简单，把mods-available中的相关文件在mods-enabled中创建软连接就可以了。 在mods-enabled中创建软连接，指向mods-available中的cgid.*文件： ln -s /etc/apache2/mods-available/cgid.load /etc/apache2/mods-enabled/ ln -s /etc/apache2/mods-available/cgid.conf /etc/apache2/mods-enabled/ 再次查看mods-enabled： ls -l /etc/apache2/mods-enabled/ | grep cgi 显示结果为： lrwxrwxrwx 1 root root 37 Jul 24 11:55 cgid.conf -> /etc/apache2/mods-available/cgid.conf lrwxrwxrwx 1 root root 37 Jul 24 11:55 cgid.load -> /etc/apache2/mods-available/cgid.load 重启Apache服务器： service apache2 restart 或者重新载入配置文件: service apache2 reload 稳妥的方式是restart Apache服务器，而不只是reload服务器，参见3.2。 在浏览器中访问：http://localhost:8888/cgi-bin/cgi_test.pl就会显示： Hello, CGI using Perl! It's Mon Aug 1 03:48:37 2016 NOW! 可以看到perl脚本在Apache服务器中以CGI脚本的形式运行了。 3. 可能遇到的一些错误 实际上，如果你严格按照上面的步骤，基本上不会出现错误，但是在这里，还是列出一些常见的错误，以便真的遇到这些问题时可以快速查看。 本节主要取自Perl/CGI script with Apache2，所以里面的脚本名字（这里是echo.pl，脚本的路径（这里是/var/cgi-bin而不是/var/www/cgi-bin）和错误信息都沿用了这篇文章的内容，与本文上面使用的脚本名字，脚本路径和脚本内容内容稍有不同，但也都大同小异。 3.1 500 Internal Server Error 如果你使用浏览器访问perl脚本时，看到500 Internal Server Error错误，打开Apache的错误日志/var/log/apache2/error.log。 如果显示下面的错误信息： [Wed Mar 19 15:19:15.740781 2014] [cgid:error] [pid 3493:tid 139896478103424] (8)Exec format error: AH01241: exec of '/var/cgi-bin/echo.pl' failed [Wed Mar 19 15:19:15.741057 2014] [cgid:error] [pid 3413:tid 139896186423040] [client 192.120.120.120:62309] End of script output before headers: echo.pl 说明脚本的第一行sh-bang line没有正确指向'perl'的安装路径，检查你的perl脚本，确认第一行是下面的样子： #!/usr/bin/perl 其实这样的错误非常罕见，除非你是新手，而且又完全自己手写了一段perl脚本。 如果显示下面的错误信息： No such file or directory: AH01241: exec of '/var/cgi-bin/echo.pl' failed [Wed Mar 19 15:24:33.505429 2014] [cgid:error] [pid 3412:tid 139896261957376] [client 192.120.120.120:58087] End of script output before headers: echo.pl 说明echo.pl是DOS格式，而非Unix格式，如果你经常的是Windows，或者喜欢在Windows下写好脚本再上传到Unix/Linux服务器中，那么这会是一个常见而且通用的错误。使用dos2unix命令转换脚本的格式： dos2unix /var/cgi-bin/echo.pl 系统默认可能并没有装dos2unix命令，需要自己安装一下。如果你用的是Ubuntu，用sudo apt install dos2unix安装。 如果显示下面的错误信息： [Wed Mar 19 15:40:31.179155 2014] [cgid:error] [pid 4796:tid 140208841959296] (13)Permission denied: AH01241: exec of '/var/cgi-bin/echo.pl' failed [Wed Mar 19 15:40:31.179515 2014] [cgid:error] [pid 4702:tid 140208670504704] [client 192.120.120.120:60337] End of script output before headers: echo.pl 说明你没有为perl脚本添加可执行权限，使用chmod命令很容易更正这个错误： chmod +x /var/cgi-bin/echo.pl 如果显示下面的错误信息： Wed Mar 19 16:02:20.239624 2014] [cgid:error] [pid 4703:tid 140208594970368] [client 192.120.120.120:62841] malformed header from script 'echo.pl': Bad header: hi 上面错误对应的脚本： #!/usr/bin/perl use strict; use warnings; print "hi\n"; print qq(Content-type: text/plain\n\n); 在perl脚本输出Content-type之前输出了别的字符，而浏览器解析的时候把那些字符当做了Content-type，所以报了Bad header错误。所以，不要在print qq(Content-type: text/plain\n\n);之前，输出别的字符。 如果显示下面的错误信息： [Wed Mar 19 16:08:00.342999 2014] [cgid:error] [pid 4703:tid 140208536221440] [client 192.120.120.120:59319] End of script output before headers: echo.pl 还是一个跟header相关的问题，说明你的脚本在打印Content-type之前就出了错误或者异常，error.log中也可能在上面的错误信息之前提供其他更具体的相关信息。所以，请仔细检查打印Content-type之前的perl代码。 关于End of script output before headers错误，原文作者认为可能与Premature end of script headers是同样的原因。 3.2 503 Service Unavailable 如果在本文2.2节为mods-enabled创建了软连接之后，并及时重载了apache，会报下面的错误： [Wed Mar 19 15:30:22.515457 2014] [cgid:error] [pid 3927:tid 140206699169536] (22)Invalid argument: [client 192.120.120.120:58349] AH01257: unable to connect to cgi daemon after multiple tries: /var/cgi-bin/echo.pl 我猜reload只是重新载入了服务器配置信息，并未将CGI进程启动，所以尽管已经配置了，但是由于CGI模块并未启动，所以自然也就没法连接CGI的守护进程。 所以，如果你只是修改了配置文件，reload就可以了，如果你启用或禁用了某个模块，需要restart，因为所有启用的模块，是在服务器启动过程中启动的。 稳妥的方式是restart Apache服务器，而不只是reload服务器。 3.3 404 Not Found 如果显示下面的错误信息： [Wed Mar 19 15:35:13.487333 2014] [cgid:error] [pid 4194:tid 139911599433472] [client 192.120.120.120:58339] AH01264: script not found or unable to stat: /usr/lib/cgi-bin/echo.pl 而echo.pl确实已经存在了，那么检查/etc/apache2/sites-enabled/000-default.conf中的DocumentRoot和ScriptAlias是否配置正确。 3.4 403 Forbidden 如果你遇到403 Forbidden错误，问题一般也出现在/etc/apache2/sites-enabled/000-default.conf这里。检查<Directory>，确保里面涉及到权限的语句正确配置了。 4. 小结 以CGI的方式运行perl是最直接最原始的方式，如果你已经成功地以CGI形式运行了perl脚本，建议你尝试下面的方式： PSGI： 更灵活也更高效的方式。 Perl Dancer或者Mojolicious：成熟的perl服务器框架，非常易用。 参考文章 Background start Linux, install Apache2 在apache下以cgi方式运行perl
Overview1.修改hosts文件2.设置eclipse Overview 前面讲到，消息队列需要用到客户端的ip地址和时间戳，获取客户端的ip地址就需要下面几行简单的代码。 import javax.servlet.http.HttpServletRequest; HttpServletRequest request = ServletActionContext.getRequest(); String ipAddress = request.getHeader("X-FORWARDED-FOR"); if (ipAddress == null) { ipAddress = request.getRemoteAddr(); } System.out.println(ipAddress); 我将上面几行代码，放入之前的PhosphoPredict项目的action中做实验，结果显示如下： 0:0:0:0:0:0:0:1 这和我们平时的认知不同，下面就是解决办法。 1.修改hosts文件 我在ubuntu14.04系统下，打开/etc/hosts文件，看到如下信息： # Localhost 127.0.0.1 localhost 127.0.0.1 young 255.255.255.255 broadcasthost ::1 localhost fe80::1%lo0 localhost 我们将 ::1 localhost 这句注释掉，再提交， 127.0.0.1 正确答案就出现了。 其他系统，请自行查找文件目录并修改。 2.设置eclipse 我们在eclipse运行本程序的时候，可以配置一下运行参数： 依次点击Run→Run Configurations…→Apache Tomcat→Tomcat v7.0 Server at localhost,这时候点击右边的Arguments，在下面的VM arguments框中加上一句： -Djava.net.preferIPv4Stack=true 最后点击Apply。然后就可以了。 总结：这是因为，只有在本机提交的时候，才会出现，客户端提交就会显示真实ip地址了。而系统会优先显示ipv6格式的本机ip。我们两种方法，第一种是屏蔽掉ipv6的表示方法，第二种则是指定优先ipv4表示。 两种方法更推荐第二种。 这篇文章主要参考了stackoverflow.
Overview1.新建项目配置环境2.生产者消费者程序2.1 生产者2.2 消费者3. 测试 Overview 之前的这篇博客ubuntu14.04单机安装配置zookeeper和kafka，介绍了zookeeper和kafka的安装配置，并在命令行下验证了生产者消费者可以跑通。但是实际项目中，需要和java交互，不可能接触到命令行和后台的。本文旨在记录一下java和kafka的简单交互，web中道理相同，只不过程序入口换成了action。 1.新建项目配置环境 打开eclipse，依次点击Window→Preferences→Java→Build Path→User Libraries，然后在右边选择New ，添加一个自己常用的Library，我命名为kafka。选中kafka，右边选择Add External JARS，然后到之前安装好的kafka的目录，找到libs这个文件夹，如果按照上次配置好的情况，这里应该是15个jar文件，见下图： 全部选中，点击确定。这样，我们以后就可以复用了。 然后我们在eclipse中构建一个普通的java项目testKafka。右击项目，依次点击Build Path→Add Libraries→User Library，选择kafka这个library，点击Finish。这样环境就搭建好了。 2.生产者消费者程序 下面编码测试程序，即消息生产者和消息消费者。 2.1 生产者 package testKafka; import java.util.Properties; import kafka.javaapi.producer.Producer; import kafka.producer.KeyedMessage; import kafka.producer.ProducerConfig; public class MsgProducer { private static Producer<String,String> producer; private final Properties props=new Properties(); public MsgProducer(){ //定义连接的broker list props.put("metadata.broker.list", "127.0.0.1:9092"); //定义序列化类,Java中对象传输之前要序列化 props.put("serializer.class", "kafka.serializer.StringEncoder"); producer = new Producer<String, String>(new ProducerConfig(props)); } public static void main(String[] args) { MsgProducer mProducer=new MsgProducer(); //定义topic String topic="testkafka"; //定义要发送给topic的消息 String mString = "Hello kafka!"; //构建消息对象 KeyedMessage<String, String> data = new KeyedMessage<String, String>(topic, mString); //推送消息到broker producer.send(data); producer.close(); } } 这里需要注意，生产者这里，最少需要两个配置项：metadata.broker.list为127.0.0.1:9092和serializer.class设置为kafka.serializer.StringEncoder。打开上次配置的producer.properties文件，看到这两项配置分别为metadata.broker.list=localhost:9092，serializer.class=kafka.serializer.DefaultEncoder。broker list要一致，否则会报错。 这些项，最好写在配置文件里，方便以后添加服务器时候更改。 2.2 消费者 package testKafka; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import kafka.consumer.Consumer; import kafka.consumer.ConsumerConfig; import kafka.consumer.ConsumerIterator; import kafka.consumer.KafkaStream; import kafka.javaapi.consumer.ConsumerConnector; public class MsgConsumer { private final ConsumerConnector consumer; private final String topic; public MsgConsumer(String zookeeper, String groupId, String topic) { Properties props = new Properties(); //定义连接zookeeper信息 props.put("zookeeper.connect", zookeeper); //定义Consumer所有的groupID props.put("group.id", groupId); props.put("zookeeper.session.timeout.ms", "500"); props.put("zookeeper.sync.time.ms", "250"); props.put("auto.commit.interval.ms", "1000"); consumer = Consumer.createJavaConsumerConnector(new ConsumerConfig(props)); this.topic = topic; } public void testConsumer() { Map<String, Integer> topicCount = new HashMap<String, Integer>(); //定义订阅topic数量 topicCount.put(topic, new Integer(1)); //返回的是所有topic的Map Map<String, List<KafkaStream<byte[], byte[]>>> consumerStreams = consumer.createMessageStreams(topicCount); //取出我们要需要的topic中的消息流 List<KafkaStream<byte[], byte[]>> streams = consumerStreams.get(topic); for (final KafkaStream stream : streams) { ConsumerIterator<byte[], byte[]> consumerIte = stream.iterator(); while (consumerIte.hasNext()) { System.out.println(new String(consumerIte.next().message())); } } if (consumer != null) { consumer.shutdown(); } } public static void main(String[] args) { String topic = "testkafka"; MsgConsumer mConsumer = new MsgConsumer("127.0.0.1:2181", "test-consumer-group", topic); mConsumer.testConsumer(); } } 这里需要注意，消费者的配置信息，应该和生产者对应。最关键的配置是两项：zookeeper.connect和group.id。这两项打开consumer.properties就可以看到。 3. 测试 首先，要在命令行中启动zookeeper和kafka。 在消费者程序里面，运行一下，Console框显示如下： log4j:WARN No appenders could be found for logger (kafka.utils.VerifiableProperties). log4j:WARN Please initialize the log4j system properly. 这个不是错误信息，不用理睬。 接着在生产者那里，运行一下，Console框显示如下： log4j:WARN No appenders could be found for logger (kafka.utils.VerifiableProperties). log4j:WARN Please initialize the log4j system properly. Hello kafka! 这样，我们的程序就跑通了。 这里主要参考了kafka官方例子：生产者，消费者。
Overview1.安装SQLite2.SQLite与java交互2.1 创建并连接数据库2.2 建表2.3 增加(insert)2.4 查找(select)2.5 更改(update)2.6 删除(delete) Overview 这次的服务器，我们准备使用消息队列和SQLite这两个新东西，SQLite主要负责将用户提交的序列信息存储，并在结果页面查询显示。存储的信息包括用户的ip+timeStamp，序列内容，序列的处理状态。 1.安装SQLite SQLite的安装特别简单。 ubuntu14.04这个版本自带SQLite，在命令行输入sqlite3，显示如下： ～sqlite3 SQLite version 3.8.2 2013-12-06 14:53:30 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> 如果没有出现上述信息，可以使用如下命令进行安装： sudo apt-get install sqlite3 每个版本的ubuntu的软件源略有新旧不同，但不影响使用。 2.SQLite与java交互 SQLite在java中的基本使用方法和其他诸如mysql等数据库大同小异，下面我们从创建并连接数据库，建表，增，删，改，查这6个方面简单介绍下。 2.1 创建并连接数据库 package testSQLite; import java.sql.Connection; import java.sql.DriverManager; public class SQLiteJDBC { public static void main( String args[] ) { Connection c; try { Class.forName("org.sqlite.JDBC"); c = DriverManager.getConnection("jdbc:sqlite:testSQLite.db"); } catch ( Exception e ) { e.printStackTrace(); } System.out.println("Connected database successfully"); } } 2.2 建表 package testSQLite; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; public class createTable { public static void main( String args[] ) { Connection c; Statement stmt; try { Class.forName("org.sqlite.JDBC"); c = DriverManager.getConnection("jdbc:sqlite:testSQLite.db"); System.out.println("Connected database successfully"); stmt = c.createStatement(); String sql = "CREATE TABLE TEAM " + "(NUMBER INT PRIMARY KEY NOT NULL, " + " NAME TEXT NOT NULL, " + " AGE INT NOT NULL, " + " COUNTRY CHAR(50), " + " POSITION CHAR(50))"; stmt.executeUpdate(sql); stmt.close(); c.close(); } catch ( Exception e ) { e.printStackTrace(); } System.out.println("Table created successfully"); } } 2.3 增加(insert) package testSQLite; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; public class insert { public static void main(String args[]) { Connection c; Statement stmt; try { Class.forName("org.sqlite.JDBC"); c = DriverManager.getConnection("jdbc:sqlite:testSQLite.db"); c.setAutoCommit(false); System.out.println("Connected database successfully"); stmt = c.createStatement(); String sql = "INSERT INTO TEAM (NUMBER,NAME,AGE,COUNTRY,POSITION) " + "VALUES (1, 'Navas', 29, 'Costa Rica', 'goalkeeper' );"; stmt.executeUpdate(sql); sql = "INSERT INTO TEAM (NUMBER,NAME,AGE,COUNTRY,POSITION) " + "VALUES (2, 'Varane', 23, 'France', 'center back' );"; stmt.executeUpdate(sql); sql = "INSERT INTO TEAM (NUMBER,NAME,AGE,COUNTRY,POSITION) " + "VALUES (3, 'Pepe', 33, 'Portugal', 'center back' );"; stmt.executeUpdate(sql); sql = "INSERT INTO TEAM (NUMBER,NAME,AGE,COUNTRY,POSITION) " + "VALUES (4, 'Ramos', 30, 'Spain', 'center back' );"; stmt.executeUpdate(sql); sql = "INSERT INTO TEAM (NUMBER,NAME,AGE,COUNTRY,POSITION) " + "VALUES (7, 'Ronaldo', 31, 'Portugal', 'stricker' );"; stmt.executeUpdate(sql); stmt.close(); c.commit(); c.close(); } catch ( Exception e ) { e.printStackTrace(); } System.out.println("Insert successfully"); } } 2.4 查找(select) package testSQLite; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class select { public static void main(String[] args) { Connection c; Statement stmt; try { Class.forName("org.sqlite.JDBC"); c = DriverManager.getConnection("jdbc:sqlite:testSQLite.db"); c.setAutoCommit(false); System.out.println("Connected database successfully"); stmt = c.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT * FROM TEAM;" ); while ( rs.next() ) { int number = rs.getInt("number"); String name = rs.getString("name"); int age = rs.getInt("age"); String country = rs.getString("country"); String position = rs.getString("position"); System.out.println( "NUMBER = " + number ); System.out.println( "NAME = " + name ); System.out.println( "AGE = " + age ); System.out.println( "COUNTRY = " + country ); System.out.println( "POSITION = " + position ); System.out.println(); } rs.close(); stmt.close(); c.close(); } catch ( Exception e ) { e.printStackTrace(); } System.out.println("Select successfully"); } } 2.5 更改(update) package testSQLite; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class update { public static void main(String[] args) { // TODO Auto-generated method stub Connection c = null; Statement stmt = null; try { Class.forName("org.sqlite.JDBC"); c = DriverManager.getConnection("jdbc:sqlite:testSQLite.db"); c.setAutoCommit(false); System.out.println("Connected database successfully"); stmt = c.createStatement(); String sql = "UPDATE TEAM set POSITION = 'fullback' where NUMBER=4;"; stmt.executeUpdate(sql); c.commit(); ResultSet rs = stmt.executeQuery( "SELECT * FROM TEAM;" ); while ( rs.next() ) { int number = rs.getInt("number"); String name = rs.getString("name"); int age = rs.getInt("age"); String country = rs.getString("country"); String position = rs.getString("position"); System.out.println( "NUMBER = " + number ); System.out.println( "NAME = " + name ); System.out.println( "AGE = " + age ); System.out.println( "COUNTRY = " + country ); System.out.println( "POSTION = " + position ); System.out.println(); } rs.close(); stmt.close(); c.close(); } catch ( Exception e ) { e.printStackTrace(); } System.out.println("Update successfully"); } } 2.6 删除(delete) package testSQLite; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class delete { public static void main(String[] args) { Connection c = null; Statement stmt = null; try { Class.forName("org.sqlite.JDBC"); c = DriverManager.getConnection("jdbc:sqlite:testSQLite.db"); c.setAutoCommit(false); System.out.println("Connected database successfully"); stmt = c.createStatement(); String sql = "DELETE from TEAM where NUMBER=3;"; stmt.executeUpdate(sql); c.commit(); ResultSet rs = stmt.executeQuery( "SELECT * FROM TEAM;" ); while ( rs.next() ) { int number = rs.getInt("number"); String name = rs.getString("name"); int age = rs.getInt("age"); String country = rs.getString("country"); String position = rs.getString("position"); System.out.println( "NUMBER = " + number ); System.out.println( "NAME = " + name ); System.out.println( "AGE = " + age ); System.out.println( "COUNTRY = " + country ); System.out.println( "POSTION = " + position ); System.out.println(); } rs.close(); stmt.close(); c.close(); } catch ( Exception e ) { e.printStackTrace(); } System.out.println("Delete successfully"); } } 这篇文章主要参考了这里：SQLite - Java。
Overview1. 安装jdk,zookeeper,kafka2. 安装配置KafkaOffsetMonitor3. 启动KafkaOffsetMonitor4.运行KafkaOffsetMonitor Overview 这次做服务器，计划加入消息队列，并在web页面显示当前提交的序列处理状态和已处理序列的信息。我们知道，在后台命令行中可以看到kafka的消息者处理消息的状态，但是，对于访问者来说，查看命令行是不现实的，于是我们便采用了KafkaOffsetMonitor这一开源软件。Github的下载地址如下:Kafka Offset Monitor。 1. 安装jdk,zookeeper,kafka 这部分可以参考上一篇文章：ubuntu14.04单机安装配置zookeeper和kafka。 2. 安装配置KafkaOffsetMonitor 新建一个文件夹，比如我在kafka文件夹下建立子文件夹kafkaMonitor下载好以后，把这个KafkaOffsetMonitor-assembly-0.2.0.jar文件放入kafkaMonitor文件夹。在当前位置新建一个kafkaMonitor.sh文件，文件内容如下： #! /bin/bash java -cp KafkaOffsetMonitor-assembly-0.2.0.jar \ com.quantifind.kafka.offsetapp.OffsetGetterWeb \ --zk localhost:2181 \ --port 8089 \ --refresh 10.seconds \ --retain 7.days 上面是最关键的几条配置，剩余配置不写也可以运行。逐一分析上面每一项配置： --zk 这里写的地址和端口，是zookeeper集群的各个地址和端口。应和kafka/bin文件夹中的zookeeper.properties中的host.name和clientPort一致。本机是host.name=localhost，clientPort=2181。 --port 这个是本软件KafkaOffsetMonitor的端口。注意不要使用那些著名的端口号，例如80,8080等。我采用了8089. --refresh 这个是软件刷新间隔时间，不要太短也不要太长。 --retain 这个是数据在数据库中保存的时间。 3. 启动KafkaOffsetMonitor 首先，启动zookeeper 切换到/home/young/zookeeper/bin目录下，运行下面命令： ./zkServer.sh start 然后，启动kafka： 切换到/home/young/kafka目录下，执行下面命令： bin/kafka-server-start.sh config/server.properties 最后，启动KafkaOffsetMonitor： 切换到/home/young/kafka/kafkaMonitor目录下面，执行下面命令： ./kafkaMonitor.sh 如果显示如下，就证明成功了： serving resources from: jar:file:/home/young/kafka/kafkaMonitor/KafkaOffsetMonitor-assembly-0.2.0.jar!/offsetapp SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. 2016-06-10 12:05:13.724:INFO:oejs.Server:jetty-7.x.y-SNAPSHOT log4j:WARN No appenders could be found for logger (org.I0Itec.zkclient.ZkConnection). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. 2016-06-10 12:05:13.781:INFO:oejsh.ContextHandler:started o.e.j.s.ServletContextHandler{/,jar:file:/home/young/kafka/kafkaMonitor/KafkaOffsetMonitor-assembly-0.2.0.jar!/offsetapp} 2016-06-10 12:05:13.802:INFO:oejs.AbstractConnector:Started SocketConnector@0.0.0.0:8089 4.运行KafkaOffsetMonitor 这里就不新建话题topic了，沿用上一篇文章中的testkafka。 切换到/home/young/kafka，打开两个命令行终端，分别打开生产者和消费者： bin/kafka-console-producer.sh --broker-list localhost:9092 --topic testkafka bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic testkafka --from-beginning 在生产者那里输入一条内容，如果在消费者那里能够收到，就证明成功了： Hello kafkaOffsetMonitor！ 这时候，我们打开浏览器，输入如下地址(对应你前面设置的地址和端口)： http://127.0.0.1:8089/ 显示如下： 我们选择点击下面那个(因为上面那个已经死掉了): 其中，logSize,Offset,Lag分别代表总消息数,已处理消息数,未处理消息数。 我们点击最上面的Topic List,就会显示所有Topic： 我们点击最下面那个Topic，testkafka，一路点下去，显示的是处理进度的过程图: 这里消息不多，如果多的话，将会非常壮观。 我们点击标题栏最后一项Visualization，就可以查看其他消费者或者整个集群的情况了(我们这里只有一个消费者，一个服务器)： 这样，我们的安装就结束了，实际工程中使用，根据需要更改配置。 这篇文章主要参考了官方文档，和以下文章： Kafka实战－KafkaOffsetMonitor Apache Kafka监控之KafkaOffsetMonitor apache kafka监控系列-KafkaOffsetMonitor
Overview1.安装配置zookeeper单机模式2.安装配置kafka单机模式2.1 配置server.properties2.2 配置zookeeper.properties2.3 配置producer.properties2.4 配置consumer.properties2.5 补充说明3.kafka的使用3.1 启动zookeeper服务3.2 启动kafka服务3.3 创建话题topic3.4 启动生产者producer3.5 启动消费者consumer Overview T4这个项目实验部分已经结束，下面需要将服务器做出来。为了方便以后扩展分布式的需要，这次将会运用Apache Kafka这个分布式消息发布订阅系统，以满足多人同时提交计算T4序列，解决并发排队问题。Apache kafka的详细介绍详见官网：Apache kafka。 运行Apache Kafka，需要先安装好jdk和zookeeper。jdk安装过程就不赘述了。 1.安装配置zookeeper单机模式 这里，我们选择的是zookeeper-3.4.5这个版本，官网下载链接zookeeper-3.4.5。下载之后将zookeeper-3.4.5.tar.gz移动到主文件夹并重命名： mv Downloads/zookeeper-3.4.5.tar.gz zookeeper.tar.gz 然后解压缩为zookeeper文件夹： tar -zxvf zookeeper.tar.gz 切换到zookeeper/conf目录： cd /home/young/zookeeper/conf 复制zoo_simple.cfg为zoo.cfg: cp zoo_simple.cfg zoo.cfg 打开zoo.cfg并修改内容如下： initLimit=10 syncLimit=5 dataDir=/home/young/zookeeper/data clientPort=2181 配置好后，手动创建dataDir目录： mkdir /home/young/zookeeper/data 为zookeeper配置环境变量，打开/etc/profile，在结尾添加如下两句，并保存： export ZOOKEEPER_HOME=/home/young/zookeeper export PATH=.:$ZOOKEEPER_HOME/bin:$JAVA_HOME/bin:$PATH 切换到zookeeper/bin目录： cd /home/young/zookeeper/bin 启动zookeeper的server： ./zkServer.sh start 显示效果如下，则证明zookeeper配置成功： JMX enabled by default Using config: /home/young/zookeeper/bin/zookeeper/conf/zoo.cfg Starting zookeeper ... STARTED 需要结束服务时，仍在本目录下，执行如下命令即可： ./zkServer.sh stop 2.安装配置kafka单机模式 我们选择的是kafka_2.10-0.8.1.1.tgz，下载链接在这里：Apache kafka。下载之后放到主文件夹，并改名为kafka.tgz，然后解压缩到当前文件夹： tar -zxvf kafka.tgz 切换到kafka/config目录： cd /home/young/kafka/config 这里，我们需要修改4个配置文件：server.properties，zookeeper.properties，producer.properties，consumer.properties。 2.1 配置server.properties 下面几项是必须修改的，其他项目为默认配置： #broker.id需改成正整数，单机为1就好 broker.id=1 #指定端口号 port=9092 #localhost这一项还有其他要修改，详细见下面说明 host.name=localhost #指定kafka的日志目录 log.dirs=/home/young/kafka/kafka-logs #连接zookeeper配置项，这里指定的是单机，所以只需要配置localhost，若是实际生产环境，需要在这里添加其他ip地址和端口号 zookeeper.connect=localhost:2181 然后手动创建log.dirs空目录： mkdir /home/young/kafka/kafka-logs 2.2 配置zookeeper.properties 很简单，这个文件暂时只需要修改3项： #数据目录 dataDir=/home/young/kafka/zookeeper/data #客户端端口 clientPort=2181 host.name=localhost 然后手动创建dataDir空目录： mkdir /home/young/kafka/zookeeper/data 2.3 配置producer.properties 只需要指定一项： metadata.broker.list=localhost:9092 2.4 配置consumer.properties 只需要指定一项： zookeeper.connect=localhost:2181 2.5 补充说明 对于这个版本的kafka，在配置好以上4项之后，仍然需要做2件事： 配置localhost，修改/etc/hosts文件 我的hosts文件里面，localhost部分是如下配置： 127.0.0.1 localhost #下面这句，你的计算机名是什么就填什么，我的是young 127.0.0.1 young 255.255.255.255 broadcasthost ::1 localhost fe80::1%lo0 localhost 这里如果不修改不添加，就会产生异常java.net.UnknownHostException。 添加slf4j-simple-1.7.2.jar 这里是个bug，/home/young/kafka/libs这个目录缺少slf4j-simple-1.7.2.jar这个文件，只有slf4j-api-1.7.2.jar这个文件是不够的，必须两个都有。或者可以下载其他版本的两个slf4j文件，放入本目录。slf4j-simple-1.7.2.jar可以去官网下载slf4j-1.7.2.tar.gz，下载后解压缩，就可以看到这两个文件了。 如果这两个文件不全，就会有错误： SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. 环境安装配置部分到此结束，下面介绍简单使用。 3.kafka的使用 3.1 启动zookeeper服务 切换至/home/young/kafka目录，执行以下命令，以启动zookeeper服务： bin/zookeeper-server-start.sh config/zookeeper.properties 3.2 启动kafka服务 仍在/home/young/kafka目录下，执行以下命令，以启动kafka服务： bin/kafka-server-start.sh config/server.properties 对于这种启动之后就可以忽略的服务，可以在最前面加上nohup，让其在后台自己运行。 3.3 创建话题topic 新开一个命令行窗口，创建一个叫做testkafka的topic： bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic testkafka 然后可以根据地址和端口号将话题topic 展示出来： bin/kafka-topics.sh --list --zookeeper localhost:2181 显示如下: testkafka 3.4 启动生产者producer 再开一个producer命令行窗口，执行以下命令： bin/kafka-console-producer.sh --broker-list localhost:9092 --topic testkafka 然后可以之间在本窗口输入消息，每遇到换行符就认为是一条消息输入完成。 Hello kafka ! 3.5 启动消费者consumer 再新开一个consumer命令行窗口，执行以下命令： bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic testkafka --from-beginning 显示如下： Hello kafka ! 这时候，启动kafka服务的命令行就会有显示： [2016-06-07 14:19:12,683] INFO Closing socket connection to /127.0.0.1. (kafka.network.Processor) 然后，每在producer那里输入一条，consumer这里就会显示一条，然后kafka服务那里也会产生日志记录。 在以后启动时，只需要依次启动kafka server，producer，consumer就可以了。 查阅了很多资料，但是都不是很详细，但是仍要感谢这些参考文章： Kafka快速入门 Zookeeper & Kafka Install ZooKeeper安装配置 Kafka单机环境部署 SLF4J warning or error messages and their meanings stackoverflow stackoverflow