如果是我startup JavaWeb项目开发,我会这样子做
Contents
Maven 模块化开发
按功能模块? 如果按功能分模块的话,比较复杂,难免会有代码循环依赖的问题。 按分层模块? 这个相对比较简单点。
然后还要区分不同的环境下的包(web与非web环境,为以后的task等非web代码部署时,不需要再依赖tomcat等容器)
日志的统一
slf4j,不过不能配置将标准输出,重定向到Tomcat的catalina.out
里。
即不能这样子配置:
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%C{50}.%M %L [%date{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] : %m%ex%n</Pattern>
</layout>
</appender>
而应该将应用的所有输出,重定向到一个文件(然后根据该文件增长的快慢,来设置按天,小时,或分或切割文件),例如按天:
<appender name="STDOUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/console/console.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover. Make sure the path matches the one in the file element or else
the rollover logs are placed in the working directory. -->
<fileNamePattern>${LOG_HOME}/console/console_%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%-10(%C{40}.%M %L) [%date{yyyy-MM-dd HH:mm:ss}] [%level]: %m%ex%n</Pattern>
</layout>
</appender>
然后每种业务,添加一个统一的logger,再分别存在到不同的日志文件里,方便排查问题.
框架的选择
SpringBoot + Redis + RabbitMQ + MySQL
然后部署时,配置多种profile
,并且要简化xml
冗余的问题。(通过import来避免重复的配置文件问题)
注意事项:
redis:尽可能用pipeline,以及选择最优的数据类型来存取数据.不能使用keys命令,可参考Github rabbitmq:添加用户和vhost来隔离(利用好交换机的类型,处理相应的业务,比如扇形交换机,可以模似做发布-订阅功能等,listener要不要ACK以提升性能等,Executor的配置计算好) MySQL:使用最新稳定版,做好备份和复制。(not null default, 每张表都要有create_time, update_time这些与业务无关的,但又很重要的信息, id auto_increment非业务主键) nginx:负载均衡(其他的负载均衡有 haproxy)
代码注意
将一些模块,写成服务。比如发邮件,写个统一的Service,由其他系统来调用(强调的是自治,以及自运行,这时可以有一个独立的redis+mq来为它服务)。
不同系统间的调用(比如请求多个HTTP,善于利用异步Future的方式来调用)
同一个方法里,避免多次查询DB,HTPP,Redis等调用(利用本地缓存来暂存)
千万不写如下类似代码!!!(即删除时,尽可能使用 Iterator来做)
List xxxx for(int i=0 ;i<10; i++){ xxxx list.remove(i); }
HTTP请求时的超时设置(一般的类库,默认是无限)
因为spring里的对象,几乎都是单例的。所以,在操作时要特别注意,不要随便set这些单例的状态(如rabbitTemplate.setxxx,或者类似这种代码!!!!!)
统一
checkstyle
: 这里以Google的 Java Style. Github
关于自动化部署
脚本rsync + nginx做负载均衡(部署时自动切换下)
开发流程
关于代码质量监控
Sonar + Findbugs 结合,每周一次自我检测代码质量.不断完善.
关于Tomcat
如果要进行远程调试,完成后,千万不要关闭远程调试再重启Tomcat!! 不然,JVM极有可能会因为这个原因,而莫名停掉!
Tomcat启动前添加参数
tomcat目录下添加bin/setenv.sh
,内容如下:
#!/bin/sh
JAVA_HOME=/home/you/jdk/jdk1.7.0_79
JRE_HOME=/home/you/jdk/jdk1.7.0_79/jre
#CATALINA_OPTS="-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -XX:+PrintGCDetails -Xloggc:weibosdk-gc.log -XX:+DisableExplicitGC -Xdebug -Xrunjdwp:transport=dt_socket,address=9999,server=y,suspend=n"
NOW="$(date +'%Y_%m_%d_%H_%M_%S')"
JVM_TYPE=" -server"
JVM_GC=" -XX:+PrintGCDateStamps -verbose:gc -XX:+PrintGCDetails -Xloggc:weibosdk-gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=10M"
JVM_DUMP=" -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/you/tomcat/product_wbsdk_jvm-dump.hprof"
#JVM_GC_TYPE=" -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=90 -XX:+ScavengeBeforeFullGC -XX:+CMSScavengeBeforeRemark"
JVM_GC_TYPE=" "
# 单位:秒
JVM_DNS_CACHE=" -Dsun.net.inetaddr.ttl=60"
# hostname如果为远程,则是外网的IP
JVM_RMI=" "
# survivorratio = eden / from 的比值
#JVM_MEM=" -Xms2048m -Xmx2048m -XX:PermSize=512m -XX:MaxPermSize=512m -XX:SurvivorRatio=8"
JVM_MEM=" "
#JVM_DEBUG=" -Xdebug -Xrunjdwp:transport=dt_socket,address=9999,server=y,suspend=n"
JVM_DEBUG=" "
JMX="-XX:+UnlockCommercialFeatures -XX:+FlightRecorder -Dcom.sun.management.jmxremote.port=7002 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=IP"
CATALINA_OPTS=" -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager ${JVM_TYPE} ${JVM_GC_TYPE} ${JVM_GC} ${JVM_DUMP} ${JVM_DNS_CACHE} ${JVM_RMI} ${JVM_MEM} ${JVM_DEBUG} ${JMX}"
echo "java opts = " ${CATALINA_OPTS}
ulimit -c unlimited
tomcat_dir/bin/startup.sh
Tomcat控制脚本
#!/bin/bash
###############################################################
#
#author: Zhiyong Yang
#date: 2015-12-4
#
###############################################################
#set -x
#作用 启动,停止,重启某tomcat,注意 ,tomcat使用绝对路径,并以/结束.
#./tomcat_ct.sh /path/to/tomcat/ start|stop|restart
TOMCAT_HOME_DIR=""
if [ ! -z $1 ]; then
TOMCAT_HOME_DIR=$1
fi
if [ -z ${TOMCAT_HOME_DIR} ]; then
echo "no tomcat special..., so exit" && exit 1
fi
TOMCAT_HOME_DIR_GREP="${TOMCAT_HOME_DIR/apache/[a]pache}"
echo "-->$TOMCAT_HOME_DIR_GREP"
function stop(){
if checkIsExist ; then
"${TOMCAT_HOME_DIR}"bin/shutdown.sh
sleep 1
while checkIsExist ; do
sleep 2
kill $(getPID)
done
fi
}
function getPID(){
# replace the grep content with your tomcat path, start with []
#$PID=`ps aux | grep ${TOMCAT_HOME_DIR_GREP}`
local PID=`ps aux | grep ${TOMCAT_HOME_DIR_GREP} | grep "[o]rg.apache.catalina.startup.Bootstrap" | awk '{print $2}'`
echo $PID
}
function start(){
if ! checkIsExist ; then
"${TOMCAT_HOME_DIR}"bin/startup.sh
tail -f -n 100 "${TOMCAT_HOME_DIR}"logs/catalina.out
fi
}
function checkIsExist(){
local P_ID=$(getPID)
if [ -z ${P_ID} ]; then
echo "not found running tomcat : ${TOMCAT_HOME_DIR}"
return 1
else
echo "found running tomcat pid = ${P_ID}"
return 0
fi
}
function usage(){
echo "Usage:"
echo "$0 $1 start : for start tomcat, no repeat"
echo "$0 $1 stop : for stop tomcat"
echo "$0 $1 restart : for restart"
}
if [ -z $2 ];then
usage $1
exit 0
fi
if [ $2 = "stop" ]; then
stop
elif [ $2 = "start" ]; then
start
elif [ $2 = "restart" ]; then
stop
start
else
usage $1
fi
错误时发送警报(这些方面要尽可能完善, 及时发现问题, 然后修复问题.)
在所有出现异常的地方, 都添加上通知 RabbitMQ + Email
的方式来发送警报.
SpringMVC加上如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class ControllerExceptionAdvice {
@Autowired
private RabbitMQService rabbitMQService;
@ExceptionHandler(value = {Exception.class, RuntimeException.class})
@ResponseBody
public Map<String, Object> defaultErrorHandler(HttpServletRequest request, Exception e) {
String requestUrl = request.getRequestURI();
Map<String, String[]> params = request.getParameterMap();
String stackMsg = getStack(e);
rabbitMQService.sendEmail("yourmail", "subject", getFormatText(requestUrl, params, stackMsg));
Map<String, Object> json = new HashMap<>();
json.put(Constants.ERROR_CODE, 500);
json.put(Constants.ERROR_MSG, "server interval error.");
return json;
}
private String getFormatText(String requestUrl, Map<String, String[]> params, String stackMsg) {
return String.format("请求的URL为 %s\n请求的参数为%s\n异常信息为:%s\n", requestUrl, params, stackMsg);
}
private String getStack(Exception e) {
StackTraceElement[] stackTraceElements = e.getStackTrace();
StringBuilder sb = new StringBuilder(e.getMessage());
sb.append("\n");
for (StackTraceElement stackTraceElement : stackTraceElements) {
String className = stackTraceElement.getClassName();
String methodName = stackTraceElement.getMethodName();
int lineNum = stackTraceElement.getLineNumber();
String fileName = stackTraceElement.getFileName();
sb.append(className).append(".").append(methodName).append(" --> ").append(fileName).append(":").append(lineNum);
sb.append("\n");
}
return sb.toString();
}
}