文章目录
1 Maven环境隔离
不同环境配置存在差异,例如FTP服务器地址不一致、数据库配置不一致、公钥私钥及账号密码不一致。通过环境隔离,避免人工修改环境配置产生错误,还能实现快速地分环境编译打包部署。
Maven
环境隔离配置步骤:
- 在工程
main
目录下新建dev/beta/prod
几个环境地配置目录,并且分别建立配置文件,如数据源配置、日志配置等; - 在
pom.xml
设置环境引用,将directory
中的配置文件地址指定为变量; - 命令行运行
mvn
命令(需安装maven
),输入参数指定环境,实现清理打包。
<resources>
<resource>
<directory>src/main/resources.${deploy.type}</directory>
<excludes>
<exclude>*.jsp</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<deploy.type>dev</deploy.type>
</properties>
</profile>
<profile>
<id>beta</id>
<properties>
<deploy.type>beta</deploy.type>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<deploy.type>prod</deploy.type>
</properties>
</profile>
</profiles>
mvn clean package -Dmaven.test.skip=true -Pdev
clean 清除class
package 打包
-Dmaven.test.skip=true 跳过单元测试
-Pdev 指定环境
2 公共类
将项目中使用的常量、资源池、响应代码等公共资源定义在common
包下,便于统一配置管理及调用。
常量类使用静态属性、接口、枚举等方式声明,示例如下:
package com.doric.demo.common;
import com.google.common.collect.Sets;
import java.util.Set;
public class Const {
public static final String CURRENT_USER = "currentUser";
public static final String EMAIL = "email";
public static final String USERNAME = "username";
public interface ProductListOrderBy {
Set<String> PRICE_ASC_DESC = Sets.newHashSet("price_asc", "price_desc");
}
public interface Cart {
int CHECKED = 1;
int UN_CHECKED = 0;
String LIMIT_NUM_SUCCESS = "limit_num_success";
String LIMIT_NUM_FAIL = "limit_num_success";
}
//tips:利用接口对常量进行分类
public interface Role {
int ROLE_CUSTOMER = 0; //普通用户
int ROLE_ADMIN = 1; //管理员
}
public enum ProductStatusEnum {
ON_SALE("在线", 1);
private String value;
private int code;
ProductStatusEnum(String value, int code) {
this.value = value;
this.code = code;
}
public String getValue() {
return value;
}
public int getCode() {
return code;
}
}
public enum OrderStatusEnum {
CANCELED("已取消", 0),
NO_PAY("未支付", 10),
PAYED("已支付", 20),
SHIPPED("已发货", 40),
ORDER_SUCCESS("订单完成", 50),
ORDER_CLOSE("订单关闭", 60);
private String value;
private int code;
OrderStatusEnum(String value, int code) {
this.value = value;
this.code = code;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public static OrderStatusEnum codeOf(int code){
for (OrderStatusEnum orderStatusEnum:values()){
if(orderStatusEnum.getCode() ==code){
return orderStatusEnum;
}
}
throw new RuntimeException("没找到对应的枚举");
}
}
}
Redis
资源池示例如下:
package com.doric.demo.common;
import redis.clients.jedis.JedisPool;
public class RedisPool {
private static JedisPool pool; // jedis连接池
private static Integer maxTotal = Integer.parseInt(PropertiesUtil.getProperty("redis.max.total", "20"));// 最大连接数
private static Integer maxIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle", "20"));// 在jedispool中最大的idle状态实例个数
private static Integer minIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle", "20"));// 在jedispool中最小的idle状态实例个数
private static Boolean testOnBorrow = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow", "true"));// borrow一个jedis实例时,是否进行验证操作
private static Boolean testOnReturn = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return", "true"));// return一个jedis实例时,是否进行验证操作
private static String redisIp = PropertiesUtil.getProperty("redis.ip");
private static String redisPort = PropertiesUtil.getProperty("redis.port");
// 声明为private防止外部调用
private static void initPool(){
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setTestOnBorrow(testOnBorrow);
config.setTestOnReture(testOnReturn);
config.setBlockWhenExhausted(true); // 连接耗尽时,是否阻塞,false会抛出异常,true阻塞直到超时
pool = new JedisPool(config, redisIp, redisPort, 1000*2);
}
static{
initPool();
}
public static Jedis getJedis(){
return pool.getResource();
}
public static void returnResource(Jedis jedis){
if(jedis != null){
pool.returnResource(jedis);
}
}
public static void returnBrokenResource(Jedis jedis){
if(jedis != null){
pool.returnBrokenResource(jedis);
}
}
}
3 运行日志保存
在Logback
的配置文件指定输出日志级别、输出位置、滚动策略等信息,在代码中需要输出错误日志的位置调用log
方法即可。可以参考:
logback日志配置(控制台日志、输出日志、错误日志)
Logback:只输出Info和Error级别的日志,并输出到不同的文件
4 全局异常
为什么需要?项目异常若未包装直接返回,那项目的内部信息比如包名、SQL
语句、配置信息存在泄露风险。
在Spring
的DispatcherServlet
返回结果前,使用自定义的ExceptionResolver
类对异常进行包装,仅保留简略的错误信息,而详细信息以日志形式保存到服务器文件中。
package com.doric.demo.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
public class ExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
log.error("{} Exception", request.getRequestURI(), ex);
ModelAndView modelAndView = new ModelAndView(new MappingJackson2JsonView());
modelAndView.addObject("status", ResponseCode.ERROR.getCode());
modelAndView.addObject("msg", "接口异常,详情查看服务器");
modelAndView.addObject("data", ex.toString());
return modelAndView;
}
}
5 自动化部署脚本
用于项目代码更新后,快速打包上线,实现的主要功能包括:
- 重新拉取最新版本代码
- 项目代码打包
- 删除
Tomcat
下旧的war
包,并将新war
包复制到Tomcat
目录下 - 重启
Tomcat
echo "=======================进入git项目目录=========================="
cd /developer/git-repository/demo
echo "=======================git切换分支到"S1"=========================="
git checkout $1
echo "=======================git fetch=========================="
git fetch
echo "=======================git pull=========================="
git pull
echo "=======================编译并跳过单元测试=========================="
mvn clean package -Dmaven.test.skip=true -Pprod
echo "=======================删除旧的ROOT.war=========================="
rm /developer/$2/webapps/ROOT.war
echo "=======================拷贝编译出来的war包到tomcat下 ROOT.war=========================="
cp /developer/git-repository/demo/target/demo.war /developer/$2/webapps/ROOT.war
echo "=======================删除tomcat下旧的ROOT文件夹=========================="
rm -rf /developer/$2/webapps/ROOT
echo "=======================关闭tomcat=========================="
/developer/$2/bin/shutdown.sh
echo "=======================sleep 10s=========================="
for i in {1..10}
do
echo $i"s"
sleep 1s
done
echo "=======================关闭tomcat=========================="
/developer/$2/bin/startup.sh
6 分布式架构
6.1 Java项目架构演进
ALL IN ONE
访问量增大,cpu内存硬盘资源吃紧
文件服务器:配置好的cpu内存
数据库服务器:配置更大更快的硬盘
并发增高,降低接口访问时间,提高服务性能
其实8成访问在2成数据上
缓存:本地缓存+远程缓存(分布式缓存服务器集群)
不同缓存类型适用的业务场景?分布式缓存扩容遇到什么问题?分布式缓存算法?
网站访问QPS继续提高,应用服务器tomcat成为瓶颈
应用服务器集群,横向扩展服务器
负载均衡调度服务器(Nginx)
负载均衡调度策略?优缺点及适合的场景?z
Session服务器
用户量大,数据库读写瓶颈
读写分离,数据库主从服务器
CDN,提高不同地区服务速度
反向代理服务器:缓存用户资源
分布式文件服务器集群
专库专用,垂直拆分
水平拆分
搜索服务需求激增
拆分为单独的搜索服务
6.2 单点登录:Session共享
6.3 程序并发:锁
7 实用工具
API
测试整理工具:RestLet
Redis
客户端管理工具:Redis Desktop Manager