SpringBoot内嵌Tomcat优化

背景

  • springboot内嵌tomcat学习

系统参数

应用服务优化项

  1. springboot tomcat配置数字,当链接超出后依然会阻塞,不会立即返回链接失败
  2. tomcat配置线程不是轮询分配,相对轮询分配。
  3. dubbo链接超出配置线程数后会立即返回失败

linux限制目录大小

概念解读

  1. linux没有磁盘分区,新建镜像处理

方法如下

  1. dd if=/dev/zero of=/root/disk1.img bs=1M count=10 // 1M*10=10M zero 是dev下的文件,创建镜像
    1
    这里进行创建镜像,需要大小根据自己的需求制定,bs是区块,一个区块大小.count是10个块.
  2. losetup /dev/loop10 /root/disk1.img // 挂载硬盘为/dev/loop1
    1
    这里要自己查找一下可用的回环设备./dev/loop为linux的回环设备,losetup /dev/loop0~x.一个个试一下为空的就可以用.也可以直接fdisk -l查看哪个没有
  3. mkfs.ext3 /dev/loop1 // 格式化文件系统
    1
    格式化系统
  4. mkdir /test1 // 创建文件
  5. mount /dev/loop10 /data // 挂载硬盘,/test1目录的容量为20M
    1
    挂载设备

卸载方法如下

  1. umount test1
    1
    卸载设备
  2. losetup -d /dev/loop1
    1
    删除挂载镜像

rocketmq安装

官网地址

本文使用的是4.9.0

  • 官网
    1
    2
    大写的备注:第一次下载的是:rocketmq-all-4.9.0-source-release.zip,想自己编译,编译后运行broker一直抛出链接不上nameserver,一开始怀疑配置,后面重新下载低版本的通过.
    重新下载回该版本,下载了一个编译的好的包.一运行直接通过了.要么自行编译时候需要改参数(官网没看到).要么编译的代码就有问题.耗时4小时

启动rocketmq

  • 这里是单机测试,修改jvm使用的内存.默认的会占用4+8=12G内存.
  1. cd rocketmq-all-4.9.0-bin-release/
  2. vim bin/runserver.sh 和 vim bin/runbroker.sh
    1
    2
    3
    找到一行带有设置内存的,修改为下面的一句话
    runserver : JAVA_OPT="${JAVA_OPT} -server -Xms512m -Xmx512m -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
    runbroker : JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn512m"
  3. 开启 Name Server
    1
    2
    3
    > nohup sh bin/mqnamesrv &
    > tail -f ~/logs/rocketmqlogs/namesrv.log
    The Name Server boot success...
  4. 开启Broker
    1
    2
    3
    4
    5
    6
    7
    8
     备注:测试中我是在docker中,需要指定一下外网ip
    > vim conf/broker.conf (加入以下.根据个人需求来定)
    # brokerIP1和brokerIP2默认获取本地ip地址,在云服务器上会获取内网ip地址,因此必须显式设置
    brokerIP1=192.168.0.x
    brokerIP2=192.168.0.x
    > nohup sh bin/mqbroker -n 192.168.0.149:9876 -c conf/broker.conf &
    > tail -f ~/logs/rocketmqlogs/broker.log
    The broker[%s, 172.30.30.233:10911] boot success...

启动rocketmq console

  1. github:https://github.com/apache/rocketmq-externals
  2. 进入到文件夹:rocketmq-console
    1
    2
    3
    mvn clean package -Dmaven.test.skip=true
    nohup java -jar target/rocketmq-console-ng-2.0.0.jar > /dev/null 2>&1 &
    tail -100f ~/logs/consolelogs/rocketmq-console.log
  3. 启动后在管理台上有填入集群地址.进行查看

Runcher2.0安装

hello word开始

  • 使用的ubuntu 20.04.

安装1.6版本

  1. 镜像地址:https://hub.docker.com/r/rancher/server
  2. 启动docker:docker run -d –restart=unless-stopped -p 8082:8080 –name rancher1.6 rancher/server
  • 问题
    1. 添加主机的时候,如果是单机版本,住下docker-agent的dns错误.可参考下面: https://github.com/rancher/rancher/issues/1108
      1
      2
      3
      4
      5
      6
      7
      8
      我用的是:
      vim /etc/default/docker
      # replace
      # # DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4"
      # by
      DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4"
      # then restart Docker
      service docker restar

安装2.5版本

  1. 安装rancher(v2.5):直接通过docker镜像来运行我们的rancher,首先,先从镜像中心下载rancher镜像,如果是1.x系列的,镜像名为rancher/server,而2.x是rancher/rancher,我们使用2.x版本的,所以,执行如下命令即可:

    1
    docker pull rancher/rancher
  2. 执行如下命令,在宿主机创建两个挂载目录.(根据情况来使用,就只看看不用了);

    1
    2
    3
    mkdir -p /docker_volume/rancher_home/rancher
    mkdir -p /docker_volume/rancher_home/auditlog
    mkdir -p /home/rancher/ssl
  3. 生成签名证书:http://docs.rancher.cn/docs/rancher2/installation/resources/advanced/self-signed-ssl/_index

    1
    官方文档有很好的解决
  4. 接下来我们启动rancher容器:(端口根据自己需要来定义.我这里有其他端口被占用了).2.5版本中docker需要提权加:–privileged

    1
    2
    3
    4
    5
    6
    7
    8
     docker run -d --privileged --restart=unless-stopped  \
    -p 8081:80 -p 8443:443 \
    -e NO_PROXY="localhost,127.0.0.1,0.0.0.0,10.0.0.0/8,192.168.0.0/24,124.160.111.194" \
    -v /home/rancher/ssl:/container/certs \
    -e SSL_CERT_DIR="/container/certs" \
    -v /docker_volume/rancher_home/rancher:/var/lib/rancher \
    -v /docker_volume/rancher_home/auditlog:/var/log/auditlog \
    --name rancher rancher/rancher:v2.5-head
  5. 查看日志:

    1
    docker logs -f $ID

    单机(测试)使用

  6. 端口映射选择集群IP.

  7. 高级选项中:网络是用主机网络

  8. rancher如果是1.5版本,网络选择桥接,方便单机中使用.
    备注: 端口映射的是使用的iptables,非docker的端口映射.这里选择是为了方便,使用其他类型需要做网络转发.

rancher重新安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
docker stop $(docker ps -a -q)
docker system prune -f
docker volume rm $(docker volume ls -q)
docker rm -f $(docker ps -a -q)
rm -rf /etc/ceph \
/etc/cni \
/etc/kubernetes \
/opt/cni \
/opt/rke \
/run/secrets/kubernetes.io \
/run/calico \
/run/flannel \
/var/lib/calico \
/var/lib/etcd \
/var/lib/cni \
/var/lib/kubelet \
/var/lib/rancher/rke/log \
/var/log/containers \
/var/log/pods \

nginx配置HTTP1.x|2

nginx配置

nginx概念

  • nginx反向代理这里不在赘述了.
  1. nginx在配置中,默认是用http1.0进行代理的.
  2. http1.0在压测中也能发现问题.一旦QPS量大后,会出现很多的time_wait.连接不复用.吞吐量无法提升

nginx1.1的配置

  • 1.1的持久化连接,可以复用是提高吞吐量的秘诀
  • {PATH}:根据自己的电脑路径设置
  • {URLDOMIAN}:根据自己需要的域名设置.
    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
    upstream test-local {
    server 127.0.0.1:8443;
    # 配置连接数量,超时时间.线上根据实际情况配置
    keepalive 50;
    keepalive_requests 20;
    }
    http配置
    server {
    listen 80;
    server_name {URLDOMIAN}; # 没有可以忽略
    access_log {PATH}/local.log;
    location / {
    proxy_pass https://test-local;
    proxy_set_header Connection "keep-alive";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Reverse-Proxy nginx;
    }

    }
    # HTTPS server 需要安装https证书
    server {
    listen 443 ssl;
    server_name {URLDOMIAN};
    access_log {PATH}/local.log;
    ssl_certificate {PATH}/localhost_ca.cer;
    ssl_certificate_key {PATH}/localhost_ca.pvk;
    # 以下参数根据自己的情况以及服务的量来制定.在后面有部分说明字段的含义
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout 5m;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    # 必须声明HTTP的版本
    proxy_http_version 1.1;
    location / {
    proxy_pass https://test-local/;
    # 中间代理了一层,要告诉服务器,持久化链接
    proxy_set_header Connection "keep-alive";
    # 以下参数根据实际情况来配置
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Reverse-Proxy nginx;
    }
    }

http2的配置

  • 经过一个空接口压测,http2压测继续比http1.1的吞吐量上升.没有了http头堵塞.但tcp头堵塞还在
    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
      upstream test-local {
    server 127.0.0.1:8443;
    # 配置连接数量,超时时间.线上根据实际情况配置
    keepalive 50;
    keepalive_requests 20;
    }
    http配置
    server {
    listen 80;
    server_name {URLDOMIAN}; # 没有可以忽略
    access_log {PATH}/local.log;
    location / {
    proxy_pass https://test-local;
    proxy_set_header Connection "keep-alive";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Reverse-Proxy nginx;
    }

    }
    # HTTPS server 需要安装https证书
    server {
    # 配置一下这个就好了.学会了就很简单
    listen 443 ssl http2;
    server_name {URLDOMIAN};
    access_log {PATH}/local.log;
    ssl_certificate {PATH}/localhost_ca.cer;
    ssl_certificate_key {PATH}/localhost_ca.pvk;
    # 以下参数根据自己的情况以及服务的量来制定.在后面有部分说明字段的含义
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout 5m;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    location / {
    proxy_pass https://test-local/;
    # 中间代理了一层,要告诉服务器,持久化链接
    proxy_set_header Connection "keep-alive";
    # 以下参数根据实际情况来配置
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Reverse-Proxy nginx;
    }
    }

部分字段说明

  1. 禁止不安全的 SSL 协议,使用安全协议:ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  2. 禁止已经不安全的加密算法: ssl_ciphers HIGH:!aNULL:!MD5; (写法百度下)
  3. 缓存连接凭据:ssl_session_cache shared:SSL:20m; ssl_session_timeout 60m;
  4. 缓解 BEAST 攻击:ssl_prefer_server_ciphers on;

系统重构的构想

系统重构的构想

重构前的确认

  1. 为什么要重构?
    1
    当前哪里不满意,需要进行重构.重构的原因是什么?要花多久?如何进行,方案是什么?
  2. 重构带来影响是什么?
    1
    重构能解决什么问题,不重构能解决吗?重构后业务会有什么变化
  3. 重构后有什么好处
    1
    重构能带什么好处,有什么提升,对业务?对技术?
  • 问题的答案需要根据当前系统进行寻找,总体来说我认为有以下几点:
    • 开发的效率提升,能够引入更方便引入相关脚手架
    • 经过不断的迭代,项目中越来越臃肿,耦合度越来越深.当修改一项内容必然涉及很多功能.界限无法理清

重构进行中

  1. 重构之路
    • 技术改造
      • 升级当前的依赖和相关jar包
      • 改变当前的整体框架内容.对整体变革
    • 业务改造
      • 相关不同业务进行剥离
  2. 未完

第一期

  1. 如何记录?
    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
    // 63题
    func uniquePathsWithObstacles(obstacleGrid [][]int) int {
    var length = len(obstacleGrid)
    var leng = len(obstacleGrid[0])
    if length == 0 || leng == 0 {
    return 0
    }
    if obstacleGrid[0][0] == 1 {
    return 0
    }
    var tmp = make([][]int, length)
    for i := 0; i < length; i++ {
    for y := 0; y < leng; y++ {
    if i == 0 {
    if obstacleGrid[i][y] == 1 {
    tmp[i] = append(tmp[i], 0)
    continue
    }
    if y > 0 {
    if tmp[i][y-1] == 0 {
    tmp[i] = append(tmp[i], 0)
    continue
    }
    }
    tmp[i] = append(tmp[i], 1)
    continue
    }
    if y == 0 {
    if obstacleGrid[i][y] == 1 {
    tmp[i] = append(tmp[i], 0)
    continue
    }
    if i > 0 {
    if tmp[i-1][y] == 0 {
    tmp[i] = append(tmp[i], 0)
    continue
    }
    }
    tmp[i] = append(tmp[i], 1)
    continue
    }
    if obstacleGrid[i][y] == 1 {
    tmp[i] = append(tmp[i], 0)
    continue
    }
    tmp[i] = append(tmp[i], tmp[i][y-1]+tmp[i-1][y])
    }
    }
    return tmp[len(tmp)-1][len(tmp[0])-1]
    }

    //64题 最小路径
    func minPathSum(grid [][]int) int {
    current := grid[0][0]
    var length = len(grid)
    var leng = len(grid[0])
    var tmp = make([][]int, length)
    // 初始化
    tmp[0] = append(tmp[0], grid[0][0])
    for y := 1; y < leng; y++ {
    tmp[0] = append(tmp[0], tmp[0][y-1]+grid[0][y])
    current = tmp[0][y]
    }
    for i := 1; i < length; i++ {
    for y := 0; y < leng; y++ {
    if y == 0 {
    tmp[i] = append(tmp[i], tmp[i-1][y]+grid[i][y])
    current = tmp[i][y]
    continue
    }
    if tmp[i-1][y] > tmp[i][y-1] {
    tmp[i] = append(tmp[i], tmp[i][y-1]+grid[i][y])
    } else {
    tmp[i] = append(tmp[i], tmp[i-1][y]+grid[i][y])
    }
    current = tmp[i][y]
    }
    }
    return current
    }

web基础学习和动态编译

web框架加载

过程记录

  • 项目使用了gradle,为springmvc的结构.当前进行调试测试用例,

    1. gradle使用tomcat运行,类打包了到了build的目录,同时idea设置的自动编译目录为out目录.这里就出现一个问题,热部署时候类不在一起,不过发现类会在2个目录存在,估计为同时会产生编译
      1
      maven项目的为target目录.同时idea也会设置成这个.猜想maven自行设置的
    2. springmvc项目进行test用例时候运行,找不到WEB-INF下的资源.这就回到编译目录来看,运行的测试环境使用的是选择的是idea,不是gradle.所以WEB-INF不在范围内.
      1
      gradle中有单独参数设置webapp目录project.webAppDirName
    3. springboot的maven项目如何结合servlet.
      1
      2
      3
      1. Application中添加@ServletComponentScan
      2. 定义一个类贴上注解:@WebServlet(name = "MyServlet",urlPatterns = "/myServlet").这时候可以使用普通的servlet
      3. 普通servlet堆栈信息很简单,这时候如果要用filter需要使用原生的注解,可以百度,这里不在赘述
  • 动态编译

    1. idea有自带动态编译的功能,在tomcat或者springboot类的on aciont中选择:update class and resources
      1
      2
      1. 这里需要注意你的如果是gradle项目,注意你选择对你的项目编译采用的gradle还是idea.
      2. gradle默认的编译目录不一致,有可能会导致热部署失效,注意检查一下.
    2. 使用三方工具arthas.优秀的一款工具,具体使用可以参照说明文档.

springmvc和springboot以及servlet

  1. springmvc只是需要指定webapp路径
  2. springboot已经使用了servlet3.0+ 可以使用注解来进行,不要再配置web.xml
  3. 如果项目需要springboot但同时又需要web.xml的.更改springboot的注册方式(存疑)

SpringMvc启动初始化过程

springmvc

web.xml 配置解释

  1. 放在全局中,格式如下

    1
    2
    3
    4
    5
    <!--全局提前初始化的使用 -->
    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-mvc.xml</param-value>
    </context-param>
  2. 同spring的dispatchServlet放在一起

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!--configure the setting of springmvcDispatcherServlet and configure the mapping -->
    <!-- 如果把下面注释掉,会默认使用servlet-name的值拼上serlvet。找WEB-INF下的类。-->
    <servlet>
    <servlet-name>spring-mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    • spring-mvc下的servlet中init-param可以省略掉。如果省略掉会默认使用:servlet-name的value值拼上servlet.xml。如上文则是:(spring-mvc-servlet.xml).查找路径需在WEB-INF下。和web.xml一致
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      类名:XmlWebApplicationContext
      加载文件方法:
      protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
      String[] configLocations = getConfigLocations();
      if (configLocations != null) {
      for (String configLocation : configLocations) {
      reader.loadBeanDefinitions(configLocation);
      }
      }
      }

      springmvc的差别(谁加载了context-param的参数?)

  3. servelt的init-param的contextConfigLocation是为了加载DispatcherServlet的. 而context-param参数 的applicationContext.xm是为了加载web程序需要加载的数据库等等配置。

问题

  • 问题一:放context-param和init-param区别?
    1. Context-param(上下文参数),在这个元素中可以定义多个组成的键值对,但是要注意这里定义的键值对作用于是application,而且在有些应用中会提前定义自己的键值对,所以可以通过这种方式配置某些技术,同时这里也可以自定义一些参数,然后在业务逻辑中使用。获取键值对的方式如下
      1
      ServletContextEvent .getServletContext().getInitParameter("urlrewrite");
    2. 的作用范围则是当前对应的Servlet,只有对应的Servlet才能够调用到,有些提前定义的Servlet中也会判断是否有某些配置的键值对,如果有则根据配置的键值对处理逻辑,没有则根据默认的逻辑处理,同时也可以自定义键值对在后期自定义的Servlet当中使用。获取键值对的方式如下
      1
      this.getInitParameter("param1")
      备注: 注意以上两者获取键值对的方式的区别,第一个必须获取ServletContext之后才能够获取,因为第一个的键值对属于整个应用,而第二个则是通过this获取,因为这里获取的键值对仅仅属于当前的Servlet。

流程

  1. 启动一个WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml.读两个节点:
  2. 紧接着,容器创建一个ServletContext(上下文),这个WEB项目所有部分都将共享这个上下文.
  3. 容器将转化为键值对,并交给ServletContext.
  4. 容器创建中的类实例,即创建监听.
  5. 在监听中会有contextInitialized(ServletContextEvent args)初始化方法,在这个方法中获得ServletContext =ServletContextEvent.getServletContext();
    context-param的值 =ServletContext.getInitParameter(“context-param的键”);
  6. 得到这个context-param的值之后,你就可以做一些操作了.注意,这个时候你的WEB项目还没有完全启动完成.这个动作会比所有的Servlet都要早.
    换句话说,这个时候,你对中的键值做的操作,将在你的WEB项目完全启动之前被执行.
  7. 举例.你可能想在项目启动之前就打开数据库.那么这里就可以在中设置数据库的连接方式,在监听类中初始化数据库的连接.
  8. 这个监听是自己写的一个类,除了初始化方法,它还有销毁方法.用于关闭应用前释放资源.比如说数据库连接的关闭.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    如:
    <!-- 加载spring的配置文件 -->
    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml,/WEB-INF/action-servlet.xml,/WEB-

    INF/jason-servlet.xml</param-value>
    </context-param>
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

树型结构数据与应用

算法类

  1. Aho-Corasick算法
    1
    1. 进行全文匹配关键字,比如:敏感词匹配。需要预处理数据
  2. Boyer- Moore算法
    1
    1. 字符串查找。需要预处理数据
  3. KMP算法
    1
    1. 字符串查找。需要预处理数据
  4. Manacher算法
    1
    1. 中心扩展,计算最长回文子串。需要辅助空间

树(字符串类)

  1. 字典树(前缀树)

    1
    1. 利用字符串的公共前缀来减少无谓的字符串比较以达到提高查询效率的目的
  2. 后缀树

    1
    1. 快速解决很多关于字符串的问题;百度上说,具体应用待学习

编码类

  1. 霍夫曼编码(哈夫曼树)
    1
    1. 压缩数据存储,需要辅助空间。

树遍历构造

  1. 二叉树遍历:前序,中序,后序,Morris
  2. 树构造:状态机

知识界限

  • 个人理解,以下内容在于多多学习,学习其中思想理念,能够在使用中想到有什么解决方案。
    算法.树