• 【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>*.jspexclude>
            excludes>
        resource>
        <resource>
            <directory>src/main/resourcesdirectory>
        resource>
    resources>
    
    <profiles>
        <profile>
            <id>devid>
            <activation>
                <activeByDefault>trueactiveByDefault>
            activation>
            <properties>
                <deploy.type>devdeploy.type>
            properties>
        profile>
        <profile>
            <id>betaid>
            <properties>
            <deploy.type>betadeploy.type>
            properties>
        profile>
        <profile>
            <id>prodid>
            <properties>
            <deploy.type>proddeploy.type>
            properties>
        profile>
    profiles>
            
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    mvn clean package -Dmaven.test.skip=true -Pdev
    
    clean	清除class
    package	打包
    -Dmaven.test.skip=true	跳过单元测试
    -Pdev	指定环境
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    在这里插入图片描述

    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("没找到对应的枚举");
            }
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    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);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    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

  • 相关阅读:
    不要用第三方日志包了Microsoft.Extensions.Logging功能就很强大
    【Elasticsearch教程19】IK分词器 ik_max_word、ik_smart
    基于Python的ArcGIS流程化数据处理及应用开发
    当苹果铅笔不能工作时,不一定都是苹果铅笔的问题!苹果铅笔不工作时如何修复
    刷题笔记:二叉树的中序遍历(三种解法-递归,迭代,Morris)
    docker安装nextcloud+onlyoffice+https
    springboot整合redis
    Kubernetes多租户策略的好处和挑战
    量化交易:开发传统趋势策略之---双均线策略
    微信公众号如何通过迁移变更主体?
  • 原文地址:https://blog.csdn.net/baidu_26646129/article/details/126773211