2015年5月

今年4月份,Google drive的接口更新了,导致grive不可用。由于grive的作者已经N久没更新了,于是VPS的日常备份要做修改了。

一开始的方案是换个客户端。找来找去,就只有这个drive项目了:
https://github.com/odeke-em/drive
由于是用go写的,需要装go相关包。感觉很麻烦,于是放弃安装。

最后改为用Dropbox来做同步。优点是可选客户端比Google drive多,且安装方便。缺点就是免费空间小,默认只有2GB,目前还算够用。

Dropbox的Shell脚本客户端,只需要dropbox_uploader.sh文件就够了:
https://github.com/andreafabrizi/Dropbox-Uploader

参考了以下两个教程:
1)自动备份网站并同步到 Dropbox
http://www.lovelucy.info/backup-website-and-sync-to-dropbox.html
2)Linux VPS备份网站数据到Dropbox
https://maoxian.de/2013/01/linux-vps%E5%A4%87%E4%BB%BD%E7%BD%91%E7%AB%99%E6%95%B0%E6%8D%AE%E5%88%B0dropbox/755.html

简单总结一下步骤:
1)下载 dropbox_uploader.sh
2)登录 dropbox.com 并创建一个应用
3)初始化客户端,运行以下命令,并按提示输入App key和App secret,以及相关设置:

./dropbox_uploader.sh info

4)编写备份脚本。其实跟备份到Google drive的脚本差不多。
5)设置自动运行备份脚本,命令是:

crontab -e

备份脚本参考:

#!/bin/bash

# begin settings
# MySQL settings
MYSQL_USER=user #MySQL用于备份数据的用户
MYSQL_PASS=password #用户密码
MYSQL_BIN=/usr/bin
# backup directory and files
CUR_DIR=/opt/backup
TMP_DIR=$CUR_DIR/tmp
BAK_DIR=/opt/dropbox_bak/$(date +%Y%m%d)
BAK_DB_NAME=bak_$(date +"%Y%m%d")_mysql.tar.gz
BAK_FILE_NAME=bak_$(date +%Y%m%d)_file.tar.gz
# delete local backup files 20 days ago
BAK_EXPIRED=/opt/dropbox_bak/$(date -d -20day +"%Y%m%d")
# dropbox client
DROPBOX=$CUR_DIR/dropbox_uploader.sh
DROPBOX_DEL=/$(date -d -30day +%Y%m%d)
# website directory
WEB_DATA="/opt/www/"
# end settings

# Create the directory when not existed
if [ ! -d $BAK_DIR ]; then
    mkdir -p $BAK_DIR
fi
if [ ! -d $TMP_DIR ]; then
    mkdir -p $TMP_DIR
else
    rm -Rf $TMP_DIR/*
fi
cd $TMP_DIR

# begin backup database
DATABASES=`$MYSQL_BIN/mysql -h "127.0.0.1" -u$MYSQL_USER -p$MYSQL_PASS -Bse 'show databases'`

for DB in $DATABASES; do
    # ignore system tables
    if [ "$DB" == "information_schema" -o "$DB" == "performance_schema" -o "$DB" == "mysql" ]; then
        continue
    fi

    # export data to backup file
    $MYSQL_BIN/mysqldump -h "127.0.0.1" -u$MYSQL_USER -p$MYSQL_PASS $DB > $DB.sql
done

# compress and merge all the database backup files
tar zcf $BAK_DB_NAME *.sql
mv -f $BAK_DB_NAME $BAK_DIR/$BAK_DB_NAME
# end backup database

# backup website files
tar zcpf $BAK_FILE_NAME $WEB_DATA
mv -f $BAK_FILE_NAME $BAK_DIR/$BAK_FILE_NAME

# sync files
$DROPBOX upload -s $BAK_DIR /
$DROPBOX delete $DROPBOX_DEL

# delete local old files
rm -Rf $TMP_DIR/*
if [ -d $BAK_EXPIRED ]; then
    rm -Rf $BAK_EXPIRED
fi

dropbox_uploader.sh的操作跟一般的文件操作类似,可以上传指定的文件或文件夹。

好了,剩下的工作就是想办法免费扩容。

使用init脚本,无非就是实现软件服务的开机启动。但是用Debian或Ubuntu时,都是用apt-get来安装软件,而且服务类的软件都自带init脚本,对init脚本变得生疏了。工作中在CentOS上配置了两个开机启动脚本,并发现一个问题。

开机守护进程启动Tomcat6
用了Tomcat这么久,以前居然一直没做个这个配置。Tomcat官方网站推荐用jsvc守护进程来启动Tomcat。

关于jsvc,Apache官网上说得非常清楚了:
http://commons.apache.org/proper/commons-daemon/jsvc.html

关于Unix(包括Linux)系统上Tomcat6守护进程配置:
https://tomcat.apache.org/tomcat-6.0-doc/setup.html#Unix_daemon

这里的步骤简单为:
1)找到jsvc的源码包:tomcat6/bin/commons-daemon-native.tar.gz
2)解压、配置并编译

cd $CATALINA_HOME/bin
tar xvfz commons-daemon-native.tar.gz
cd commons-daemon-1.0.x-native-src/unix
./configure
make
cp jsvc $CATALINA_HOME/bin/

3)配置init脚本。jsvc源码包里有Tomcat5的init脚本,复制过来,按需要进行修改,并放到 /etc/init.d/ 目录下。例如文件名为 tomcat 。jsvc启动Tomcat后,可能根据Tomcat的启动情况来设置返回值(即通过$?获取的值),导致Tomcat正常启动后,jsvc仍会返回1,而不是0。因此,这里改为Tomcat启动后,通过检测相关端口来判断Tomcat是否启动成功并显示启动后的提示信息。脚本示例如下:

#!/bin/sh 
# 
# Startup Script for Tomcat6 
# 
# chkconfig: 345 98 02 
# description: Tomcat Daemon 
# processname: tomcat 
# pidfile: /var/run/tomcat.pid 
# config: 
# 
# Source function library.
. /etc/rc.d/init.d/functions 
# 
prog=tomcat

JAVA_HOME=/usr/local/jdk1.6.0_45
CATALINA_HOME=/usr/local/apache-tomcat-6.0.35
DAEMON_HOME=$CATALINA_HOME/bin
TOMCAT_USER=tomcat
HTTP_PORT=8080

# for multi instances adapt those lines.
TMP_DIR=$CATALINA_HOME/temp
PID_FILE=/var/run/tomcat.pid
CATALINA_BASE=$CATALINA_HOME

CATALINA_OPTS="-server -Xss512k -Xms64M -Xmx512M"
CLASSPATH=\
$JAVA_HOME/lib/tools.jar:\
$CATALINA_HOME/bin/commons-daemon.jar:\
$CATALINA_HOME/bin/bootstrap.jar

start() {
  echo -n $"Starting $prog: "
  
  #
  # Start Tomcat
  #
  $DAEMON_HOME/jsvc $JSVC_OPTS \
  -user $TOMCAT_USER \
  -home $JAVA_HOME \
  -Dcatalina.home=$CATALINA_HOME \
  -Dcatalina.base=$CATALINA_BASE \
  -Djava.io.tmpdir=$TMP_DIR \
  -wait 10 \
  -pidfile $PID_FILE \
  -outfile $CATALINA_HOME/logs/catalina.out \
  -errfile '&1' \
  $CATALINA_OPTS \
  -cp $CLASSPATH \
  org.apache.catalina.startup.Bootstrap
  #
  # To get a verbose JVM
  #-verbose \
  # To get a debug of jsvc.
  #-debug \

  # Check if Tomcat is running on HTTP_PORT
  lsof -i:$HTTP_PORT > /dev/null
  ret=$?
  if [ $ret -eq 0 ]; then
    echo_success
  else
    echo_failure
  fi
  echo
  return $ret
}

stop() {
  echo -n $"Stopping $prog: "
  
  #
  # Stop Tomcat
  #
  $DAEMON_HOME/jsvc $JSVC_OPTS \
  -stop \
  -pidfile $PID_FILE \
  org.apache.catalina.startup.Bootstrap
  
  ret=$?
  if [ $ret -eq 0 ]; then
    echo_success
  else
    echo_failure
  fi
  echo
  return $ret
}

restart() {
  stop
  start
}

case "$1" in
  start)
    start
    ;;

  stop)
    stop
    ;;

  restart)
    restart
    ;;

  *)
    echo "Usage $prog start/stop/restart"
    exit 1;;
esac

4)加入开机启动。利用chkconfig命令:

chkconfig --add tomcat
chkconfig tomcat on

所有Java程序都可以用jsvc来启动,但是,启动的类需要实现init(String[] args)、start()、stop()、destroy()这4个方法。服务在启动时会先调用init(String[] args)方法,然后调用start()方法,在服务停止是会首先调用stop()方法,然后调用destroy()方法。

直接启动Tomcat6
其实init脚本只是个含有特定格式(例如要求实现带start和stop参数的功能)的shell脚本。就是说,可以直接执行Tomcat6自带的启动和停止脚本($CATALINA_HOME/bin目录下的startup.sh和shutdown.sh)。

具体配置参考这个:
http://www.cnblogs.com/lianche/p/3154096.html

init脚本的模板如下:(以memcache为例)

#!/bin/sh
#
# Startup Script for memcached
#
# chkconfig: 345 65 35
# description: memcached
# processname: memcached
# pidfile: /var/run/memcached.pid
# config:
#
# Source function library.
. /etc/rc.d/init.d/functions
#
prog=memcached

FILE_HOME=/usr/local/memcache
PID_FILE=/var/run/memcached.pid

start() {
  echo -n $"Starting $prog: "
  
  $FILE_HOME/bin/memcached -d -m 128 -u root -l 0.0.0.0 -p 11211 -c 512 -P $PID_FILE

  retval=$?
  if [ $retval -eq 0 ]; then
    echo_success
  else
    echo_failure
  fi
  echo
  return $retval
}

stop() {
  echo -n $"Stopping $prog: "

  cat $PID_FILE | xargs kill -9

  ret=$?
  if [ $ret -eq 0 ]; then
    echo_success
  else
    echo_failure
  fi
  echo
  return $ret
}

restart() {
  stop
  start
}

case "$1" in
  start)
    start
    ;;

  stop)
    stop
    ;;

  *)
    echo "Usage $prog start/stop/restart"
    exit 1;;
esac

关于没有生成PID文件的程序
有些程序不像memcached那样把进程id记录到PID_FILE,于是就不能使用kill命令来关闭该进程了。虽然直接调用startup.sh和shutdown.sh来启动和关闭Tomcat也是没有PID_FILE,但是Tomcat会使用端口来关闭自己进程。遇到既没有PID_FILE,也没有像Tomcat那样有特殊的关闭方法的话,就自能自己用ps命令找到对应的进程id,然后kill掉。

以下以“花生壳(phddns)”为例。其中get_pid函数是获取进程号的。本来ps -C phddns -o pid=就可以获取到进程id了。但是由于启动脚本的文件名也是phddns,ps -C phddns会找到脚本所在的进程id,所以要再加上grep来找到/usr/bin/phddns命令的进程。

#!/bin/sh
#
# Startup Script for phddns
#
# chkconfig: 345 56 44
# description: phddns Daemon
# processname: phddns
# pidfile:
# config:
#
# Source function library.
. /etc/rc.d/init.d/functions
#
prog=phddns

get_pid() {
  #get the process id using ps
  echo $(ps -C phddns -o pid,command | grep '/usr/bin/phddns' | awk '{print $1}')
}

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

  PID_NUM=$(get_pid)

  if [ -n "$PID_NUM" ]; then
    echo_failure
    echo
    echo $prog is already running.
  else
    /usr/bin/phddns -c /etc/phlinux.conf -d > /dev/null

    ret=$?
    if [ $ret -eq 0 ]; then
      echo_success
    else
      echo_failure
    fi
    echo
  fi
  return $ret
}

stop() {
  echo -n $"Stopping $prog: "

  PID_NUM=$(get_pid)

  if [ -n "$PID_NUM" ]; then
    kill -9 $PID_NUM

    ret=$?
    if [ $ret -eq 0 ]; then
      echo_success
    else
      echo_failure
    fi
    echo
  else
    echo_failure
    echo
    echo $prog is not running.
  fi
  return $ret
}

restart() {
  stop
  start
}

case "$1" in
  start)
    start
    ;;

  stop)
    stop
    ;;

  restart)
    restart
    ;;

  *)
    echo "Usage $prog start/stop/restart"
    exit 1;;
esac

关于启动顺序
开机启动时,如果服务A依赖服务B来运行时,必须要先启动服务B,再启动服务A。通常在init脚本头(注释)中,用 chkconfig 及其后面3个参数来标记。例如:
chkconfig 345 65 35
345表示在init状态为3、4、5时启动,65表示启动顺序(只支持两位数字,数字越小,启动越早),35表示中止顺序(也只支持两位数字,数字越小,中止越早)。

那么,服务A的chkconfig第2个参数要大于服务B的。

生成的启动顺序,可以参照 /etc/rc.d 文件夹(号代表0到6)内的文件名称。其中K开头的表示中止,S开头的表示启动。

PS. 由于工作上用到了CentOS,相关的配置也用得越来越多。这是N年前想不到的事情。

网上看到关于Shadowsocks的优化,觉得有必要设置下自用的VPS。
shadowsocks 公共代理的必要设置
https://gist.github.com/fqrouter/95c037f9a3ba196fd4dd

但是VPS上的Shadowsocks一般只有自己在用,而且服务器限制了最大访问文件数。那么就剩下iptables值得设置了。

关于Debian的iptables,发现跟RedHat(或者CentOS)的不同。最后还是参照了官方Wiki进行了配置。
https://wiki.debian.org/iptables

PS. 本来大学时就学过iptables了,但是现在用来,发现基本忘干净了。

终于在5月来临前结束了上一份工作。回望过去,刚好满一年。

一年前,眼前一切都安好了,想再次投入到软件公司去做开发。面试多间以后,最后选择了本地数一数二的,而且声称做Java的公司。现在想来,进去也太过于轻松了。

首先是HR招人会有奖金,其次是公司的经营方式--可以理解为人力外包。就是找一些会写代码的程序员,拉到客户那边帮客户做开发。公司赚取客户报价与员工工资之间的差价。这种开发模式,除非客户那边有比较强的技术,并且只需要增加临时的开发人员来完成项目,否则对客户是非常不利的。最简单一点是项目开发过后,就找不到人去维护。实际项目中,也有出现某些代码想修正或者升级,问谁,谁都不知道。

基于这种经营方式,就会导致人员流动性很大。当然,公司只要从客户那边拿到钱,就不用管人员的问题了。更厉害的是,开发工具采用了图形化开发。就是像配置工作流一样去配置业务逻辑。简单直观,开发快,而且不公开技术实现。新人立马就能上手,就算离职也不会学到真正的技术。

这样来看,一个声称是做技术的公司,其实就没有什么核心技术了。进去工作的程序员,也得不到太多的提升。再加上利用晚上不算工资、没有补贴的加班来压缩成本,人就会越做越累。正如某领导所说的,能留下来的都是精英。我的理解,他们都是心态非常好,抗压能力超强的精英。

当然,老板和领导们都应该看到这些问题了(或者看到其它方面),于是踏上了产品研发的道路。我一进去就是投入到新产品的研发,一做就是大半年。然后我们拿着这个半成品,就去给客户做项目开发了。

现在看来,该产品也一样空洞。主要的原因还是缺乏一个技术总监去主导、把控和验收。最后随着产品开发的结束,开发人员的离去(调去其它项目或者离职),产品未能达到预期效果。最TMD的是,有一些东拼西凑的模块,隐含重大问题。例如在Windows上部署Weblogic来运行,核心Jar包会报包名与类名冲突的问题。这样连部署到客户的服务器都成为难题。

啰嗦了一堆问题后,还是总结一下收获吧。

重新投入到软件开发的环境中,感觉还是很好的。一个开发团队里,有问题可以大家一起讨论解决。下班后还能天马行空地讨论各种各样的问题。

不得不说的是,技术老大传授了一些不错的经验。

对于技术的选择,要用自己熟悉的,而不是选择那些吹嘘有多强的新方案。自己熟悉的,遇到问题都好解决。而且成熟稳定的方案会少很多问题,就算有问题,解决方法也比新方案容易找到。

要通过增强代码可复用性来提高开发效率,而不是复制修改代码。其实这个程序员都明白的,但是在开发过程中总是没有做到。

针对问题去解决问题。就是根据问题的提示去找原因,然后解决。

面试的时候,要把话题带到自己熟悉的领域,说出面试官不懂的、不敢反驳的话题。

展望未来,将有更多需要努力和学习的地方。Come on, go ahead!

新的工作需要用到MySQL。Leader让我复制一个数据库用来测试。以前用SQL Server都是直接图形化操作,从A数据库导出到B数据库就可以。Oracle就用expdp和impdp命令进行导出导入。MySQL的话,都是导出SQL脚本,再新建个数据库来执行。

网上找个这个教程:MySQL快速复制数据库的方法 http://www.tudaxia.com/archives/357

教程中只需两部:
1)新建数据库(shell命令)

# mysql -uroot -ppassword
mysql> CREATE DATABASE `newdb` DEFAULT CHARACTER SET UTF8 COLLATE UTF8_GENERAL_CI;

2)复制数据。通过管道的方式把两条命令合并为一条。(shell命令)

# mysqldump db1 -uroot -ppassword --add-drop-table | mysql newdb -uroot -ppassword

当数据很大的时候,建议还是老老实实地先把源数据库的数据备份成文件,再导入新的数据。(shell命令)

# mysqldump db1 -uroot -ppassword --add-drop-table --default-character-set=utf8 > /home/db_export/db1.sql
# mysql newdb -uroot -ppassword < /home/db_export/db1.sql