Bootstrap

【Java】B站课程《基于分布式架构项目实战》学习总结

1 Maven环境隔离

不同环境配置存在差异,例如FTP服务器地址不一致、数据库配置不一致、公钥私钥及账号密码不一致。通过环境隔离,避免人工修改环境配置产生错误,还能实现快速地分环境编译打包部署。

Maven环境隔离配置步骤:

  1. 在工程main目录下新建dev/beta/prod几个环境地配置目录,并且分别建立配置文件,如数据源配置、日志配置等;
  2. pom.xml设置环境引用,将directory中的配置文件地址指定为变量;
  3. 命令行运行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语句、配置信息存在泄露风险。

SpringDispatcherServlet返回结果前,使用自定义的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

;