开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

jenkins部署及问题解决

发表于 2021-11-28

我遇到的问题

问题1

linux主机上kubectl 访问腾讯云集群没有问题, linux主机上安装的jenkins启动的一个docker容器上用kubectl访问就报连接超时

ping只能ping通IP,在docker容器中可以带IP去ping,是因为容器帮忙直接把端口去掉了。

在linux主机上ping 域名(不要带https:),可以看到经过dns解析的内网IP地址 10.132.18.116
ping cls-igajfhjz.ccs.tencent-cloud.com PING cls-igajfhjz.ccs.tencent-cloud.com (10.132.18.116) 56(84) bytes of data.
64 bytes from cls-igajfhjz.ccs.tencent-cloud.com (10.132.18.116): icmp_seq=1 ttl=54 time=7.03 ms

docker容器中通过域名解析到的大网地址,在linux主机上也是访问不通的
nc -vz 81.69.157.133 443, ip可以ping通

直接在容器中配置k8s集群的内网地址 https://10.132.18.116 后问题解决

K8S集群的默认端口是6443,但是腾讯云的不是啊,太坑了。

问题2

用dockerhub里的jenkins:lts镜像安装jenkins到k8s集群,登录后任何请求都报错”jenkins 403 No valid crumb was included in the request”

参考:www.codenong.com/cs109265932…

启动参数中添加
Dhudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION=true

1
2
3
ini复制代码        env:
- name: JAVA_OPTS
value: -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85 -Dhudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION=true -Duser.timezone=Asia/Shanghai

因为域名无法解析,无法访问git仓库

报错:

1
yaml复制代码 Could not resolve host: git.XXXXXX.com

集群dnsPolicy配置和场景说明

找到如下资料:
help.aliyun.com/document_de…
ACK支持通过dnsPolicy字段为每个Pod配置不同的DNS策略。目前ACK集群支持四种策略:

  • ClusterFirst:通过CoreDNS来做域名解析,Pod内/etc/resolv.conf配置的DNS服务地址是集群DNS服务的kube-dns地址。该策略是集群工作负载的默认策略。
  • None:忽略集群DNS策略,需要您提供dnsConfig字段来指定DNS配置信息。
  • Default:Pod直接继承集群节点的域名解析配置。即在ACK集群直接使用ECS的/etc/resolv.conf文件(文件内配置的是阿里云DNS服务)。
  • ClusterFirstWithHostNet:强制在hostNetWork网络模式下使用ClusterFirst策略(默认使用Default策略)。

jenkins属于第三种情况,yaml直接配置dnsPolicy: Default就OK了

Jenkins配置访问K8S集群的两种方式

插件方式

Jenkins下载插件 Kubernete CLI,然后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
less复制代码{
agent any
parameters {
string(name: 'k8sCredentialsId', defaultValue: 'XXXXXX', description: 'k8s集群配置id')
string(name: 'k8sServerUrl', defaultValue: 'XXXXXX', description: 'k8s集群服务地址')
}
stages {
stage('检出代码') {
steps{
withKubeConfig([credentialsId: "${params.k8sCredentialsId}",serverUrl: "${params.k8sServerUrl}"]) {

}
}
}
}
}

非插件方式

在/root/.kubectl中配置config文件方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typescript复制代码{
agent any
environment {
K8S_CONFIG = credentials('XXXXXX')
}
stages {
stage('检出代码') {
steps{
sh "mkdir -p /root/.kube"
sh "echo ${K8S_CONFIG} | base64 -d > /root/.kube/config"
sh "kubectl config --kubeconfig=/root/.kube/config get-contexts"
sh "kubectl config --kubeconfig=/root/.kube/config use-context cls-igajfhjz-100021810018-context-default"
}
}
}
}
}

Jenkins部署推荐

Jenkins部署上手,推荐mldong的系列博客,相当赞!

手把手带你玩转k8s-jenkins安装与流水线

手把手带你玩转k8s-jenkins流水线语法

手把手带你玩转k8s-jenkins流水线发布springboot项目

手把手带你玩转k8s-jenkins流水线发布vue项目

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

计算机网络思维导图学习总结(一) 计算机网络 后语

发表于 2021-11-28

计算机网络

本文正在参与 “网络协议必知必会”征文活动

关于作者

  • 作者介绍

🍓 博客主页:作者主页

🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆

🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻


计算机网络概述

本章主将的计算机的一些概要,先介绍计算机网络在信息时代的作用。接着对互联网进行了概述,包括互联网基础结构发展的三个阶段,以及今后的发展趋势。然后,讨论了互联网组成的边缘部分和核心部分。简单介绍了计算机网络在我国的发展以及计算机网络的类别后,讨论了计算机网络的性能指标。最后,论述了整个课程都要用到的重要概念计算机网络的体系结构。

本章最重要的内容是:

  • 互联网边缘部分和核心部分的作用,其中包含分组交换的概念。
  • 计算机网络的性能指标。
  • 计算机网络分层次的体系结构,包含协议和服务的概念。这部分内容比较抽象。在

jw1

本章重要概念:

  • 计算机网络(可简称为网络)把许多计算机连接在一起,而互连网则把许多网络连接在一起,是网络的网络。
  • 以小写字母 i 开始的internet ( 互连网)是通用名词,它泛指由多个计算机网络互连而成的网络。在这些网络之间的通信协议( 即通信规则)可以是任意的。
  • 以大写字母 I 开始的Internet ( 互联网)是专用名词,它指当前全球最大的、开放的、由众多网络相互连接而成的特定互连网,并采用TCP/IP协议族作为通信规则,且其前身是美国的ARPANET. Internet 的推荐译名是“因特网”,但很少被使用。
  • 互联网现在采用存储转发的分组交换技术,以及三层ISP结构。
  • 互联网按工作方式可划分为边缘部分与核心部分。主机在网络的边缘部分,其作用是进行信息处理。路由器在网络的核心部分,其作用是按存储转发方式进行分组交换。
  • 计算机通信是计算机中的进程(即运行着的程序)之间的通信。计算机网络采用的通信方式是客户服务器方式和对等连接方式(P2P 方式)。
  • 客户和服务器都是指通信中所涉及的应用进程。客户是服务请求方,服务器是服务提供方。
  • 按作用范围的不同,计算机网络分为广域网WAN、城域网MAN、局域网LAN和个人区域网PAN。
  • 计算机网络最常用的性能指标是:速率、带宽、 吞吐量、时延(发送时延、传播时延、处理时延、排队时延)、时延带宽积、往返时间和信道(或网络)利用率。
  • 网络协议即协议,是为进行网络中的数据交换而建立的规则。计算机网络的各层及其协议的集合,称为网络的体系结构。
  • 五层协议的体系结构由应用层、运输层、网络层(或网际层)、数据链路层和物层组成。运输层最重要的协议是TCP和UDP协议,而网络层最重要的协议是IP协议。

后语

厂长写博客目的初衷很简单,希望大家在学习的过程中少走弯路,多学一些东西,对自己有帮助的留下你的赞赞👍或者关注➕都是对我最大的支持,你的关注和点赞给厂长每天更文的动力。

对文章其中一部分不理解,都可以评论区回复我,我们来一起讨论,共同学习,一起进步!

微信(z613500)或者 qq(1016942589) 详细交流。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Ubuntu1604/Hadoop313安装教程_单机

发表于 2021-11-28

Hadoop3.1.3安装教程

「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战」。

关于作者

  • 作者介绍

🍓 博客主页:作者主页

🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆

🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻


🧂安装 Hadoop3.1.3

文件资源已经再文章开头给了链接。😁我就知道会有小伙伴会忘记所以在这里再一次放出来,小伙伴不用再翻一次前文了,点击这里从百度云盘下载软件(提取码:5few)

我们选择将 Hadoop 安装至 /usr/local/src 中:

1
2
3
4
5
shell复制代码cd /usr/local/src/hadoop
tar -zxvf hadoop-3.1.3.tar.gz # 解压到/usr/local/src中
cd /usr/local/src/hadoop
sudo mv ./hadoop-3.1.3/ ./hadoop # 将文件夹名改为hadoop
sudo chown -R hadoop ./hadoop # 修改文件权限

image-20210917211429415

Hadoop 解压后即可使用。输入如下命令来检查 Hadoop 是否可用,成功则会显示 Hadoop 版本信息:

1
2
shell复制代码cd /usr/local/src/hadoop/hadoop
./bin/hadoop version

image-20210917222209506

相对路径与绝对路径

请务必注意命令中的相对路径与绝对路径,本文后续出现的 ./bin/...,./etc/... 等包含 ./ 的路径,均为相对路径,以 /usr/local/hadoop 为当前目录。例如在 /usr/local/hadoop 目录中执行 ./bin/hadoop version 等同于执行 /usr/local/hadoop/bin/hadoop version。可以将相对路径改成绝对路径来执行,但如果你是在主文件夹 ~ 中执行 ./bin/hadoop version,执行的会是 /home/hadoop/bin/hadoop version,就不是我们所想要的了。

🥓Hadoop单机配置(非分布式)

Hadoop 默认模式为非分布式模式(本地模式),无需进行其他配置即可运行。非分布式即单 Java 进程,方便进行调试。

现在我们可以执行例子来感受下 Hadoop 的运行。Hadoop 附带了丰富的例子(运行 ./bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar 可以看到所有例子),包括 wordcount、terasort、join、grep 等。

image-20210917222534742

在此我们选择运行 grep 例子,我们将 input 文件夹中的所有文件作为输入,筛选当中符合正则表达式 dfs[a-z.]+ 的单词并统计出现的次数,最后输出结果到 output 文件夹中。

1
2
3
4
5
shell复制代码cd /usr/local/src/hadoop/hadoop
mkdir ./input
cp ./etc/hadoop/*.xml ./input # 将配置文件作为输入文件
./bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar grep ./input ./output 'dfs[a-z.]+'
cat ./output/* # 查看运行结果

image-20210917223235568

注意,Hadoop 默认不会覆盖结果文件,因此再次运行上面实例会提示出错,需要先将 ./output 删除。

1
shell复制代码rm -r ./output

🍖Hadoop伪分布式配置

Hadoop 可以在单节点上以伪分布式的方式运行,Hadoop 进程以分离的 Java 进程来运行,节点既作为 NameNode 也作为 DataNode,同时,读取的是 HDFS 中的文件。

Hadoop 的配置文件位于 /usr/local/hadoop/etc/hadoop/ 中,伪分布式需要修改2个配置文件 core-site.xml 和 hdfs-site.xml 。Hadoop的配置文件是 xml 格式,每个配置以声明 property 的 name 和 value 的方式来实现。

修改配置文件 core-site.xml (通过 gedit 编辑会比较方便: gedit ./etc/hadoop/core-site.xml),将当中的

1
2
xml复制代码<configuration>
</configuration>

修改为下面配置:

1
2
3
4
5
6
7
8
9
10
11
xml复制代码<configuration>
<property>
<name>hadoop.tmp.dir</name>
<value>file:/usr/local/src/hadoop/hadoop/tmp</value>
<description>Abase for other temporary directories.</description>
</property>
<property>
<name>fs.defaultFS</name>
<value>hdfs://localhost:9000</value>
</property>
</configuration>

同样的修改配置文件hdfs-site.xml

1
shell复制代码gedit ./etc/hadoop/hdfs-site.xml

修改为下面配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
xml复制代码<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>file:/usr/local/src/hadoop/hadoop/tmp/dfs/name</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>file:/usr/local/src/hadoop/hadoop/tmp/dfs/data</value>
</property>
</configuration>

Hadoop配置文件说明

Hadoop 的运行方式是由配置文件决定的(运行 Hadoop 时会读取配置文件),因此如果需要从伪分布式模式切换回非分布式模式,需要删除 core-site.xml 中的配置项。

此外,伪分布式虽然只需要配置 fs.defaultFS 和 dfs.replication 就可以运行(官方教程如此),不过若没有配置 hadoop.tmp.dir 参数,则默认使用的临时目录为 /tmp/hadoo-hadoop,而这个目录在重启时有可能被系统清理掉,导致必须重新执行 format 才行。所以我们进行了设置,同时也指定 dfs.namenode.name.dir 和 dfs.datanode.data.dir,否则在接下来的步骤中可能会出错。

配置完成后,执行 NameNode 的格式化:

1
2
shell复制代码cd /usr/local/src/hadoop/hadoop
./bin/hdfs namenode -format

没有出现ERROR就说明成功

如果在这一步时提示 Error: JAVA_HOME is not set and could not be found. 的错误,则说明之前设置 JAVA_HOME 环境变量那边就没设置好,请按教程先设置好 JAVA_HOME 变量,否则后面的过程都是进行不下去的。如果已经按照前面教程在.bashrc文件中设置了JAVA_HOME,还是出现 Error: JAVA_HOME is not set and could not be found. 的错误,那么,请到hadoop的安装目录修改配置文件“/usr/local/src/hadoop/etc/hadoop/hadoop-env.sh”,在里面找到“export JAVA_HOME=${JAVA_HOME}”这行,然后,把它修改成JAVA安装路径的具体地址,比如,“export JAVA_HOME=/usr/lib/jvm/default-java”,然后,再次启动Hadoop。

接着开启 NameNode 和 DataNode 守护进程。

1
2
shell复制代码cd /usr/local/src/hadoop/hadoop
./sbin/start-dfs.sh #start-dfs.sh是个完整的可执行文件,中间没有空格
😺hdfs namenode问题解决

出现这个问题的小伙伴不要慌,老哥又来帮你们解决问题了😁😁😁

image-20210918104810959

1
2
3
4
5
6
7
8
9
10
Shell复制代码[root@master ~]# start-dfs.sh
Starting namenodes on [master]
ERROR: Attempting to operate on hdfs namenode as root
ERROR: but there is no HDFS_NAMENODE_USER defined. Aborting operation.
Starting datanodes
ERROR: Attempting to operate on hdfs datanode as root
ERROR: but there is no HDFS_DATANODE_USER defined. Aborting operation.
Starting secondary namenodes [master]
ERROR: Attempting to operate on hdfs secondarynamenode as root
ERROR: but there is no HDFS_SECONDARYNAMENODE_USER defined. Aborting operation.

解决方法:

在Hadoop安装目录下找到sbin文件夹

在里面修改四个文件

对start-dfs.sh和stop-dfs.sh文件,添加下列参数(😋这里我用的shift+g定位到末尾,按下a键或者e键进行编辑,添加在末尾,添加完成后按Esc输入:wq进行保存):

1
2
3
4
5
sh复制代码#!/usr/bin/env bash
HDFS_DATANODE_USER=root
HADOOP_SECURE_DN_USER=hdfs
HDFS_NAMENODE_USER=root
HDFS_SECONDARYNAMENODE_USER=root

对start-yarn.sh和stop-yarn.sh文件,添加下列参数(😋这里我用的shift+g定位到末尾,按下a键或者e键进行编辑,添加在末尾,添加完成后按Esc输入:wq进行保存):

1
2
3
4
sh复制代码#!/usr/bin/env bash
YARN_RESOURCEMANAGER_USER=root
HADOOP_SECURE_DN_USER=yarn
YARN_NODEMANAGER_USER=root

修改后重启 ./start-dfs.sh,成功!

😺JAVA_HOME问题解决

如果修改后又出现一下问题:

image-20210918105248931

1
2
3
4
5
6
shell复制代码Starting namenodes on [localhost]
localhost: ERROR: JAVA_HOME is not set and could not be found.
Starting datanodes
localhost: ERROR: JAVA_HOME is not set and could not be found.
Starting secondary namenodes [utu-VirtualBox]
utu-VirtualBox: ERROR: JAVA_HOME is not set and could not be found.

JAVA环境变量已经配好了,终端输入java -version也会显示版本信息。那这是什么问题呢?

😺 Could not resolve hostname问题

👼老哥帮你解决问题:

修改hadoop-env.sh (我的hadoop安装在/usr/local/src/hadoop/hadoop 目录下)

1
java复制代码sudo gedit /usr/local/src/hadoop/hadoop/etc/hadoop/hadoop-env.sh

将原本的JAVA_HOME 替换为绝对路径就可以了

1
2
3
sh复制代码#export JAVA_HOME=${JAVA_HOME}

export JAVA_HOME=/usr/local/src/jdk/jdk1.8.0_162

出现这个问题的小伙伴不要慌,老哥又来帮你们解决问题了😁😁😁

启动 Hadoop 时提示 Could not resolve hostname

如果启动 Hadoop 时遇到输出非常多“ssh: Could not resolve hostname xxx”的异常情况,如下图所示:

image-20210918110016189

这个并不是 ssh 的问题,可通过设置 Hadoop 环境变量来解决。首先按键盘的 ctrl + c 中断启动,然后在 ~/.bashrc 中,增加如下两行内容(设置过程与 JAVA_HOME 变量一样,其中 HADOOP_HOME 为 Hadoop 的安装目录):

1
2
3
4
> shell复制代码export HADOOP_HOME=/usr/local/src/hadoop/hadoop
> export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native
>
>

保存后,务必执行 source ~/.bashrc 使变量设置生效,然后再次执行 ./sbin/start-dfs.sh 启动 Hadoop。

启动完成后,可以通过命令 jps 来判断是否成功启动,若成功启动则会列出如下进程: “NameNode”、”DataNode” 和 “SecondaryNameNode”(如果 SecondaryNameNode 没有启动,请运行 sbin/stop-dfs.sh 关闭进程,然后再次尝试启动尝试)。如果没有 NameNode(建议从Hadoop伪分布式配置进行配置)或 DataNode ,那就是配置不成功,请仔细检查之前步骤,或通过查看启动日志排查原因。

image-20210918114323771

Hadoop无法正常启动的解决方法

一般可以查看启动日志来排查原因,注意几点:

  • 启动时会提示形如 “DBLab-XMU: starting namenode, logging to /usr/local/hadoop/logs/hadoop-hadoop-namenode-DBLab-XMU.out”,其中 DBLab-XMU 对应你的机器名,但其实启动日志信息是记录在 /usr/local/hadoop/logs/hadoop-hadoop-namenode-DBLab-XMU.log 中,所以应该查看这个后缀为 .log 的文件;
  • 每一次的启动日志都是追加在日志文件之后,所以得拉到最后面看,对比下记录的时间就知道了。
  • 一般出错的提示在最后面,通常是写着 Fatal、Error、Warning 或者 Java Exception 的地方。
  • 可以在网上搜索一下出错信息,看能否找到一些相关的解决方法。

此外,若是 DataNode 没有启动,可尝试如下的方法(注意这会删除 HDFS 中原有的所有数据,如果原有的数据很重要请不要这样做):

1
2
3
4
5
6
7
8
> shell复制代码# 针对 DataNode 没法启动的解决方法
> cd /usr/local/src/hadoop/hadoop
> ./sbin/stop-dfs.sh # 关闭
> rm -r ./tmp # 删除 tmp 文件,注意这会删除 HDFS 中原有的所有数据
> ./bin/hdfs namenode -format # 重新格式化 NameNode
> ./sbin/start-dfs.sh # 重启
>
>

成功启动后,可以访问 Web 界面 http://localhost:9870 查看 NameNode 和 Datanode 信息,还可以在线查看 HDFS 中的文件。

image-20210918114426003

🍳运行Hadoop伪分布式实例

上面的单机模式,grep 例子读取的是本地数据,伪分布式读取的则是 HDFS 上的数据。要使用 HDFS,首先需要在 HDFS 中创建用户目录:

1
shell复制代码./bin/hdfs dfs -mkdir -p /user/hadoop

image-20210918114629340

注意

教材《大数据技术原理与应用》的命令是以”./bin/hadoop dfs”开头的Shell命令方式,实际上有三种shell命令方式。

  1. hadoop fs
  2. hadoop dfs
  3. hdfs dfs

hadoop fs适用于任何不同的文件系统,比如本地文件系统和HDFS文件系统
hadoop dfs只能适用于HDFS文件系统
hdfs dfs跟hadoop dfs的命令作用一样,也只能适用于HDFS文件系统

接着将 ./etc/hadoop 中的 xml 文件作为输入文件复制到分布式文件系统中,即将 /usr/local/hadoop/etc/hadoop 复制到分布式文件系统中的 /user/hadoop/input 中。我们使用的是 hadoop 用户,并且已创建相应的用户目录 /user/hadoop ,因此在命令中就可以使用相对路径如 input,其对应的绝对路径就是 /user/hadoop/input:

1
2
shell复制代码./bin/hdfs dfs -mkdir input
./bin/hdfs dfs -put ./etc/hadoop/*.xml input

image-20210918114725681

复制完成后,可以通过如下命令查看文件列表:

1
shell复制代码./bin/hdfs dfs -ls input

image-20210918114801197

伪分布式运行 MapReduce 作业的方式跟单机模式相同,区别在于伪分布式读取的是HDFS中的文件(可以将单机步骤中创建的本地 input 文件夹,输出结果 output 文件夹都删掉来验证这一点)。

1
shell复制代码./bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar grep input output 'dfs[a-z.]+'

image-20210918114937054

查看运行结果的命令(查看的是位于 HDFS 中的输出结果):

1
shell复制代码./bin/hdfs dfs -cat output/*

结果如下,注意到刚才我们已经更改了配置文件,所以运行结果不同。

image-20210918115017808

我们也可以将运行结果取回到本地:

1
2
3
shell复制代码rm -r ./output    # 先删除本地的 output 文件夹(如果存在)
./bin/hdfs dfs -get output ./output # 将 HDFS 上的 output 文件夹拷贝到本机
cat ./output/*

image-20210918115141758

Hadoop 运行程序时,输出目录不能存在,否则会提示错误 “org.apache.hadoop.mapred.FileAlreadyExistsException: Output directory hdfs://localhost:9000/user/hadoop/output already exists” ,因此若要再次执行,需要执行如下命令删除 output 文件夹:

1
shell复制代码./bin/hdfs dfs -rm -r output    # 删除 output 文件夹

运行程序时,输出目录不能存在

运行 Hadoop 程序时,为了防止覆盖结果,程序指定的输出目录(如 output)不能存在,否则会提示错误,因此运行前需要先删除输出目录。在实际开发应用程序时,可考虑在程序中加上如下代码,能在每次运行时自动删除输出目录,避免繁琐的命令行操作:

1
2
3
4
5
6
7
8
> java复制代码Configuration conf = new Configuration();
> Job job = new Job(conf);
>
> /* 删除输出目录 */
> Path outputPath = new Path(args[1]);
> outputPath.getFileSystem(conf).delete(outputPath, true);
>
>

若要关闭 Hadoop,则运行

1
shell复制代码./sbin/stop-dfs.sh

注意

下次启动 hadoop 时,无需进行 NameNode 的初始化,只需要运行 ./sbin/start-dfs.sh 就可以!

后语

厂长写博客目的初衷很简单,希望大家在学习的过程中少走弯路,多学一些东西,对自己有帮助的留下你的赞赞👍或者关注➕都是对我最大的支持,你的关注和点赞给厂长每天更文的动力。

对文章其中一部分不理解,都可以评论区回复我,我们来一起讨论,共同学习,一起进步!

微信(z613500)或者 qq(1016942589) 详细交流。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Docker部署MySQL服务 安装部署环境 下载镜像 简单

发表于 2021-11-28

安装部署环境

  • Ubuntu 20.04.1 LTS
  • Docker 20.10.11
  • MySQL latest(8.0.27)

下载镜像

从仓库中拉取mysql镜像,如果没加标签的话,默认获取最新版本(latest)

1
bash复制代码$ docker pull mysql:tag

简单启动 MySQL服务器

启动 MySQL 实例很简单:

1
2
3
bash复制代码$ docker run --name some-mysql \
-e MYSQL_ROOT_PASSWORD='my-secret-pw' \
-d mysql:tag
  • some-mysql:分配给容器的名称
  • root:为root用户设置的密码
  • tag:mysql镜像标签

挂载数据卷以及配置文件启动

查找配置文件位置

不同版本部署的 MySQL 内,可能文件路径不一致,如 /etc/mysql/my.cnf、 /etc/mysql/conf.d 或者 /etc/mysql/mysql.conf.d等。运行以下命令查找mysql配置文件my.cnf的位置:

1
bash复制代码$ docker run --rm mysql mysql --help | grep my.cnf

得到结果:

1
2
bash复制代码                      order of preference, my.cnf, $MYSQL_TCP_PORT,
/etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf

意思是按照/etc/my.cnf、/etc/mysql/my.cnf、~/.my.cnf路径按优先排序。

依次访问上述路径:

1
2
3
4
5
6
7
8
bash复制代码$ docker run --rm mysql cat /etc/my.cnf
cat: /etc/my.cnf: No such file or directory

$ docker run --rm mysql cat /etc/mysql/my.cnf
# Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
...

可以得到镜像中mysql配置文件路径为/etc/mysql/my.cnf

查找数据文件位置

运行以下命令查看mysql镜像信息:

1
bash复制代码$ docker inspect mysql

得到结果:

1
2
3
4
5
bash复制代码...
"Volumes": {
"/var/lib/mysql": {}
},
...

可以得到mysql挂载的数据文件路径为/var/lib/mysql

创建本地路径

在物理机上,创建指定数据和配置文件的挂载路径(须在docker进程有写权限的目录下,如/home/docker)

1
bash复制代码$ mkdir -p /home/docker/mysql/conf && mkdir -p /home/docker/mysql/datadir

将容器的mysql默认配置文件/etc/mysql/my.cnf拷贝出来

1
2
3
4
bash复制代码$ CID=`docker run -d mysql` \
&& docker cp $CID:/etc/mysql/my.cnf /home/docker/mysql/conf \
&& docker stop $CID \
&& docker rm $CID

挂载启动MySQL

知道了mysql配置文件和数据文件位置,就可以挂载启动mysql服务了

1
2
3
4
5
6
7
bash复制代码$ docker run --name some-mysql \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=my-secret-pw \
-v /home/docker/mysql/conf/my.cnf:/etc/my.cnf \
-v /home/docker/mysql/datadir:/var/lib/mysql \
--restart=on-failure:3 \
-d mysql
  • –name:为容器指定一个名字
  • -p:指定端口映射,格式为:主机(宿主)端口:容器端口
  • -e:设置环境变量
  • –restart=on-failure:3:是指容器在未来出现异常退出(退出码非0)的情况下循环重启3次
  • –mount:目录挂载
  • -d:后台运行容器,并返回容器 id

MySQL 命令行客户端

命令启动另一个mysql容器实例进入命令行客户端:

1
2
bash复制代码$ docker run -it --rm mysql mysql \
-hsome.mysql.host -usome-mysql-user -p

查看MySQL日志

1
bash复制代码$ docker logs some-mysql

环境变量说明

在启动mysql镜像时,可以通过在docker run命令行中传递一个或多个环境变量来调整 MySQL 实例的配置。请注意,如果您使用已经包含数据库的数据目录启动容器,则以下任何变量都不会产生任何影响:在容器启动时,任何预先存在的数据库将始终保持不变。

MYSQL_ROOT_PASSWORD

此变量是必需的,它指定将为 MySQLroot超级用户帐户设置的密码。在上面的例子中,它被设置为root。

MYSQL_DATABASE

此变量是可选的,允许您指定要在映像启动时创建的数据库的名称。如果提供了用户/密码(见下文),则该用户将被授予对该数据库的超级用户访问权限(对应于GRANT ALL)。

MYSQL_USER, MYSQL_PASSWORD

这些变量是可选的,结合使用来创建新用户和设置该用户的密码。该用户将被授予对MYSQL_DATABASE变量指定的数据库的超级用户权限(见上文)。

MYSQL_ALLOW_EMPTY_PASSWORD

这是一个可选变量。设置为非空值,例如yes,以允许使用 root 用户的空白密码启动容器。

MYSQL_RANDOM_ROOT_PASSWORD

这是一个可选变量。设置为非空值,如yes,为 root 用户生成随机初始密码(使用pwgen)。生成的 root 密码将打印到控制台 ( GENERATED ROOT PASSWORD: .....)。

MYSQL_ONETIME_PASSWORD

初始化完成后,将root(不是在MYSQL_USER! 中指定的用户)用户设置为过期,强制在首次登录时更改密码。任何非空值都将激活此设置。注意:此功能仅在 MySQL 5.6+ 上受支持。在 MySQL 5.5 上使用此选项将在初始化期间引发适当的错误。

MYSQL_INITDB_SKIP_TZINFO

默认情况下,入口点脚本会自动加载CONVERT_TZ()函数所需的时区数据。如果不需要,任何非空值都会禁用时区加载。

备份和恢复

创建数据库转储

启动mysql镜像使用mysqldump命令备份:

1
bash复制代码$ docker exec some-mysql sh -c 'exec mysqldump --all-databases -uroot -p"$MYSQL_ROOT_PASSWORD"' > /some/path/on/your/host/all-databases.sql

从转储文件中恢复数据

启动mysql镜像执行.sql文件

1
bash复制代码$ docker exec -i some-mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD"' < /some/path/on/your/host/all-databases.sql

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

AEJoy —— 开发第一个 After Effects 插

发表于 2021-11-28

「这是我参与11月更文挑战的第 26 天,活动详情查看:2021最后一次更文挑战」。

参加该活动的第 43 篇文章

目标

开发我的第一个 After Effects 插件 —— 变色效果。

Plugin.gif

环境

Adobe CC2019 v16.0.0

Windows10 with environment for C++ compile

Microsoft Visual C++ 2017 ver15.8.4

具体步骤

环境搭建

下载 After Effects SDK

访问 Adobe 网站,单击 “获取 SDKs” 按钮获取 After Effects SDK。选择 AE 版本的 After Effects Plug-in SDK。

将创建路径设置为环境变量

添加环境变量 “AE_PLUGIN_BUILD_DIR” 并设置输出目录。如果它不存在,则会报错误 “fatal error LNK1104: cannot open file ***.aex”。

创建样例插件,测试环境配置成功与否

解压 “After+Effects+CC+16.0+Win+SDK.zip”,并将解压后的目录“AfterEffectsSDK”放到合适的位置。

打开 AfterEffectsSDK\Examples\BuildAll 例子。sln 将启动 Visual Studio 。

右键单击 “SDK Noise” 并构建它。

image.png

如果出现错误 “C2220: warning treated as error - no 'object' file generated”,修改项目配置——不将警告视为错误。

image.png

放置插件到 AE 目录

把 “SDK_Noise.aex“ 文件放到 “C:\Program Files\Adobe\Adobe After Effects CC 2019\Support Files“ 目录。

添加插件效果

打开 AE 项目,点击 Effect>Sample Plug-ins>SDK_Noise

image.png

一个噪声效果被添加到视频中。

image.png

拷贝模板

从 “AfterEffectsSDK\Examples\template\Skeleton” 复制骨架模板,保留“Skeleton”、“Headers”、“Resources” 和 “Util” 的目录层次结构。

重命名文件名,将 “Skeleton” 替换为 “Color Change” 。不要重命名 “SkeletonPiPL.rc”。因为二进制的 .rc 文件是自动从 .r 文件生成的(AE插件 SDK 指南 “PiPL资源” )。

EffectMain 函数

在 SkeletonPiPL.r 中定义了 Main 函数,如下。

1
2
3
4
5
6
7
8
9
cpp复制代码#ifdef AE_OS_WIN
#ifdef AE_PROC_INTELx64
CodeWin64X86 {"EffectMain"},
#endif
#else
#ifdef AE_OS_MAC
CodeMacIntel64 {"EffectMain"},
#endif
#endif

EffectMain 是接受 PF_Cmd cmd 作为一个参数的函数,并使用(如PF_Cmd_ABOUT,PF_Cmd_PARAMS_SETUP 和 PF_Cmd_RENDER 的) cmd 作为一个选择器来调用该函数。参考 “AE 插件 SDK 指南命令选择器”。

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
cpp复制代码PF_Err EffectMain(
PF_Cmd cmd,
PF_InData *in_data,
PF_OutData *out_data,
PF_ParamDef *params[],
PF_LayerDef *output,
void *extra)
{
PF_Err err = PF_Err_NONE;
try {
switch (cmd) {
case PF_Cmd_ABOUT:
err = About(in_data,
out_data,
params,
output);
break;
case PF_Cmd_GLOBAL_SETUP:
err = GlobalSetup(in_data,
out_data,
params,
output);
break;
case PF_Cmd_PARAMS_SETUP:
err = ParamsSetup(in_data,
out_data,
params,
output);
break;
case PF_Cmd_RENDER:
err = Render(in_data,
out_data,
params,
output);
break;
}
}
catch(PF_Err &thrown_err){
err = thrown_err;
}
return err;
}

About 函数

About 函数用于显示描述插件的对话框。在 ColorChange_Stgrings.cpp 中修改 TableString 如下。

1
2
3
4
5
6
7
cpp复制代码TableString g_strs[StrID_NUMTYPES] = {
StrID_NONE, "",
StrID_Name, "ColorChange",
StrID_Description, "Change comp color to specified color",
StrID_Gain_Param_Name, "Gain",
StrID_Color_Param_Name, "Color",
};

image.png

打开 About 对话框,就会显示如下信息

image.png

参数设置

ParamsSetup 是设置 UI、描述参数和注册它们的函数。

我将 SKELETON 重命名为 COLORCHANGE ,删除 GAIN 参数,添加 LEVEL 参数。然后在 ColorChange.h 中重置值如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cpp复制代码/* Parameter defaults */

#define COLORCHANGE_LEVEL_MIN 0
#define COLORCHANGE_LEVEL_MAX 100
#define COLORCHANGE_LEVEL_DFLT 50
enum {
COLORCHANGE_INPUT = 0,
COLOECHANGE_LEVEL,
COLORCHANGE_COLOR,
COLORCHANGE_NUM_PARAMS
};
enum {
LEVEL_DISK_ID = 1,
COLOR_DISK_ID,
};

对应地,在 ColorChange.cpp 中也重命名常量名称。

渲染

渲染函数是根据输入和参数将效果渲染到输出的函数。

1. GainInfo

GainInfo 是处理参数 GAIN 的结构体。在 ColorChange.h 中创建用于传递参数数据 level 和 color 的新结构体。

1
2
3
4
5
cpp复制代码typedef struct ParamInfo {
PF_FpLong level;
PF_Pixel color;
PF_Pixel16 color16;
} PramInfo, *PramInfoP, **PramInfoH;
1
2
3
4
5
6
7
8
9
cpp复制代码//GainInfo          giP;
//AEFX_CLR_STRUCT(giP);
ParamInfo paramDataP;
AEFX_CLR_STRUCT(paramDataP);
A_long linesL = 0;

linesL = output->extent_hint.bottom - output->extent_hint.top;
paramDataP.level = params[COLOECHANGE_LEVEL]->u.fs_d.value;
paramDataP.color = params[COLORCHANGE_COLOR]->u.cd.value;

2. iterate

迭代函数扫描输入帧并且计算输出帧作为像素对像素的操作,即像素函数。在本例中,像素函数是 “MySimpleGainFunc16” 或 “MySimpleGainFunc8” 。重命名并将它们改为 “MyColorChangeFunc16” 和 “MyColorChangeFunc8” 。

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
cpp复制代码if (PF_WORLD_IS_DEEP(output))
{
paramDataP.color16.red = CONVERT8TO16(paramDataP.color.red);
paramDataP.color16.green = CONVERT8TO16(paramDataP.color.green);
paramDataP.color16.blue = CONVERT8TO16(paramDataP.color.blue);
paramDataP.color16.alpha = CONVERT8TO16(paramDataP.color.alpha);
ERR(suites.Iterate16Suite1()->iterate(
in_data,
0, // progress base
linesL, // progress final
&params[COLORCHANGE_INPUT]->u.ld, // src
NULL, // area - null for all pixels
(void *)&paramDataP, // refcon - your custom data pointer
MyColorChangeFunc16, // pixel function pointer
output));
}
else
{
ERR(suites.Iterate8Suite1()->iterate(
in_data,
0, // progress base
linesL, // progress final
&params[COLORCHANGE_INPUT]->u.ld, // src
NULL, // area - null for all pixels
(void *)&paramDataP, // refcon - your custom data pointer
MyColorChangeFunc8, // pixel function pointer
output));
}

3. MyColorChangeFunc

我将像素函数更改如下。

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
cpp复制代码static PF_Err MyColorChangeFunc8(
void *refcon,
A_long xL,
A_long yL,
PF_Pixel8 *inP,
PF_Pixel8 *outP)
{
PF_Err err = PF_Err_NONE;
ParamInfo *paramDataP = reinterpret_cast<ParamInfo *>(refcon);
PF_FpLong levelF = 0;
float red_diff, green_diff, blue_diff;

if (paramDataP)
{
levelF = paramDataP->level / 100.0;
red_diff = (paramDataP->color.red - inP->red) * levelF;
green_diff = (paramDataP->color.green - inP->green) * levelF;
blue_diff = (paramDataP->color.blue - inP->blue) * levelF;

outP->alpha = inP->alpha;
outP->red = MIN(inP->red + red_diff, PF_MAX_CHAN8);
outP->green = MIN(inP->green + green_diff, PF_MAX_CHAN8);
outP->blue = MIN(inP->blue + blue_diff, PF_MAX_CHAN8);
}
return err;
}
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
cpp复制代码static PF_Err MyColorChangeFunc16(
void *refcon,
A_long xL,
A_long yL,
PF_Pixel16 *inP,
PF_Pixel16 *outP)
{
PF_Err err = PF_Err_NONE;
ParamInfo *paramDataP = reinterpret_cast<ParamInfo *>(refcon);
PF_FpLong levelF = 0;
float red_diff, green_diff, blue_diff;

if (paramDataP)
{
levelF = paramDataP->level / 100.0;
red_diff = (paramDataP->color16.red - inP->red) * levelF;
green_diff = (paramDataP->color16.green - inP->green) * levelF;
blue_diff = (paramDataP->color16.blue - inP->blue) * levelF;

outP->alpha = inP->alpha;
outP->red = MIN(inP->red + red_diff, PF_MAX_CHAN16);
outP->green = MIN(inP->green + green_diff, PF_MAX_CHAN16);
outP->blue = MIN(inP->blue + blue_diff, PF_MAX_CHAN16);
}
return err;
}

构建并安装

构建解决方案,并将生成的 .aex 文件放入 Adobe AE 目录,如 “C:\Program Files\Adobe\Adobe After Effects CC 2019\Support Files”。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

AEJoy —— AE 插件开发中的 PF_InData 结

发表于 2021-11-28

「这是我参与11月更文挑战的第 27 天,活动详情查看:2021最后一次更文挑战」。

参加该活动的第 44 篇文章

正文

PF_InData

After Effects 使用 PF_InData 与系统、项目、图层和音频信息进行通信。 在将每个命令选择器发送到插件之前,将更新此结构体。 注意,该字段们仅在特定 PF_Cmds 期间有效的字段。 另外,不用担心; 尽管 PF_InData 很大,但您不必记住每个成员的作用; 仅是有时会使用某些字段。

PF_InData 成员

字段名 描述
inter 用于用户交互,添加参数,检查用户是否已中断了效果,显示进度条以及在非当前渲染时间的其他时间获取源帧和参数值的回调。 此非常有用的功能套件在 “交互回调函数” 中进行了描述。
utils 图形和数学回调。 这个指针在任何时候都需要定义的。
effect_ref 必须传递给大多数各种回调例程的不透明数据。 After Effects 使用此来识别您的插件。
quality 当前质量设置,即 PF_Quality_HI 或 PF_Quality_LO。 效果在 LO 中的执行速度应更快,而在 HI 中的效果则应更准确。 图形工具回调在 LO 和 HI 质量之间的执行方式有所不同。 你的效果也应该如此! 在所有帧和序列选择器中都定义了该字段。
version 效果规范版本,指示在 PF_Cmd_GLOBAL_SETUP 期间成功运行所需的版本。
serial_num 调用应用程序的序列号。
appl_id 调用应用程序的标识符。 如果您的插件在 After Effects 中运行,则 appl_id 包含应用程序创建者代码 “FXTC”。 如果它在 Premiere Pro 中运行,它将是 “PrMr” 。 使用它来测试是否允许将您的插件与一种应用程序一起使用的同时,还与另一种应用程序一起使用。
num_params 输入参数计数。
what_cpu 在 macOS 下,它包含 CPU 类型的 Gestalt 值(请参阅 Macintosh 内部,第 6 卷)。 在 Windows 上未定义。
what_fpu 在 macOS 下,它包含 FPU 类型的 Gestalt 值。 在 Windows 上未定义。
current_time 当前帧的渲染时间,在 PF_Cmd_RENDER 期间有效。 这是图层中的当前时间,而不是任何合成中的。 如果图层在时间 0 以外的地方开始或经过时间拉伸,则图层时间和合成时间会不同。 当前帧号是 current_time 除以 time_step。 当前时间(以秒为单位)是 current_time 除以 time_scale。 为了处理时间拉伸,合成帧速率更改和时间重新映射,After Effects 可能会要求效果在非整数时间(两个帧之间)进行渲染。 为此做好准备; 不要假定仅会要求您提供帧边界上的帧。 注意:从 CS3(8.0) 开始,可能需要在时间为负值时调用。
time_step 正在渲染的当前源帧的持续时间。 在具有嵌套合成的几种情况下,此源帧持续时间可能不同于该层中帧之间的时间跨度(local_time_step)。 可以通过除以 time_scale 将该值转换为秒。 在计算其他源帧时间时(比如,对于 PF_CHECKOUT_PARAM ),请使用 tile_scale,而不要使用 local_time_step。 如果该层是时间反转的,则可以为负。 如果将时间重新映射应用于嵌套的合成的话,则可能在一帧与下一帧之间发生变化。 当源材质在嵌套合成中被拉伸或重新映射时,其 local_time_step 可能会不同。 例如,当内部合成嵌套在具有不同帧速率的外部合成内或对外部合成应用了时间重新映射时,可能会发生这种情况。 如果在所有帧中该值都不恒定,则在 PF_Cmd_SEQUENCE_SETUP 期间该值将置为 0 。 将在 PF_Cmd_FRAME_SETUP 和 PF_Cmd_FRAME_SETDOWN 选择器中正确设置。 警告:这可以是零,因此在除法之前请检查它。
total_time 图层的持续时间。 如果该层的时间拉伸长于 100%,则将相应地调整该值; 但是如果该层被时间拉伸得更短,则该值将不会受到影响。 如果启用了时间重新映射,则此值为合成的持续时间。 可以通过除以 time_scale 将该值转换为秒。
local_time_step 图层中帧之间的时间差。 会受到应用于图层的任何时间拉伸的影响。 如果该层是时间反转的,则可以为负。 与 time_step 不同,此值从一帧到下一帧是恒定的。 可以通过除以 time_scale 将该值转换为秒。 对于在图层的整个帧范围内恒定的步长值,请使用 local_time_step,它基于合成的帧率和图层拉伸。
time_scale current_time,time_step,local_time_step 和 total_time 的每秒单位。如果time_scale 为 30 ,则 current_time,time_step,local_time_step 和 total_time 的单位为 1/30 秒 。 然后如果 time_step 为 3 ,则表示该序列实际上以每秒 10 帧的速度渲染。 如果 total_time 是 105 ,表示该序列长 3.5 秒。
field 仅当在 PF_Cmd_GLOBAL_SETUP 期间设置了 PF_OutFlag_PIX_INDEPENDENT 时有效。 检查此字段以查看您是否只能处理上方或下方的字段。
shutter_angle 运动模糊快门角度。 值的范围是 0 到 1 ,代表 360 度。 除非启用运动模糊并检查目标层,否则它将为零。 shutter_angle == 180 表示 current_time 和 current_time + 1/2 time_step 之间的时间间隔。 仅在 PF_Cmd_GLOBAL_SETUP 期间设置了 PF_OutFlag_I_USE_SHUTTER_ANGLE 时有效。 有关如何在效果中实现运动模糊的详细信息,请参见“运动模糊”部分。
width height 源图层的尺寸,不一定与输入图像参数中的宽度和高度字段相同。 缓冲区大小调整效果可能导致这种差异。 不受下采样的影响。
extent_hint 输入和输出层的可见部分的交集; 包含转换为图层坐标的合成矩形。 仅在此矩形像素上进行迭代可以显着加快效果。 有关正确用法,请参见本章后面的注释。
output_origin_x output_origin_y 输入缓冲区中输出缓冲区的原点。 仅当效果更改原点时才为非零。
downsample_x downsample_y 点控制参数和图层参数尺寸会自动调整,以补偿用户告诉 After Effects 每隔 n 个像素渲染的情况。 效果需要下采样因子来解释代表图像中像素距离的标量参数(如滑块)。 例如,如果在每个方向上的下采样因子为 1/2(下采样因子表示为比率),则 4 像素的模糊应解释为 2 像素的模糊 PF_Cmd_SEQUENCE_SETUP, PF_Cmd_SEQUENCE_RESETUP, PF_Cmd_FRAME_SETUP, PF_Cmd_FRAME_RENDER。
pixel_aspect_ratio 像素长宽比(宽高比)。
in_flags 暂时没用
global_data sequence_data frame_data 插件在其他选择器期间存储的数据。 在调用插件前后,由 After Effects 锁定和解锁。
start_sampL 起始样本编号,相对于音频层的起始位置。
dur_sampL 音频的持续时间,表示为样本数。 特定于音频。
total_sampL 音频层中的样本; 等效于样本中表示的 total_time 。
src_snd PF_SoundWorld 描述输入声音。 特定于音频。
pica_basicP 指向 PICA Basic 套件的指针,用于获取其他套件。
pre_effect_source_origin_x pre_effect_source_origin_y 输入缓冲区中源图像的原点。 仅在与帧选择器一起发送时有效。 仅当在同一层上此效果之前的一个或多个效果调整了输出缓冲区的大小并移动了原点时,才为非零。 检查调整大小和新原点,以确定输出区域。 这对于具有隐式空间操作(点控件除外)的效果很有用,例如围绕图像中心翻转文件。 注意:检出点参数针对当前时间(而不是检出时间)的 pre-effect 原点进行调整。
shutter_phase 从帧时间到快门打开时间的偏移量(以帧持续时间的百分比表示)

extent_hint 的用法

提示矩形对于 SmartFX 更加有效…也更复杂…

使用 extent_hint 只处理那些需要输出的像素; 这是您可以进行的最简单的优化之一。 通过在PF_Cmd_GLOBAL_SETUP(和您的 PiPL)中在 PF_OutData 中设置 PF_OutFlag_USE_OUTPUT_EXTENT,来告诉 After Effects 您使用 in_data> extent_hint。

在测试 extent_hint 代码之前,请从首选项菜单中禁用缓存,因此,只要合成中的任何内容发生变化,After Effects 都将渲染您的效果。 否则,缓存机制会使您的插件的输出(可能不正确)模糊不清。

在合成中移动图层,以便对其进行裁剪。 output>extent_hint 是图层在合成中可见的部分。 在您的图层上添加一个遮罩并四处移动。 这将改变 extent_hint,它包含图像的所有非零 alpha 区域。

in_data -> extent_hint 是这两个矩形(合成和遮罩)的交集,并且每当它们发生变化就改变。

在调整大小和原点偏移之前,会在原始输入层的坐标空间中计算范围矩形(extent rectangles),以简化设置了 PF_OutFlag_PIX_INDEPENDENT 的效果的输入和输出范围之间的矩形相交。 要在输出缓冲区的坐标系中获取输出范围(output extent),请通过 PF_InData -> output_origin_x 和 y 字段将 extent_hint 偏移。

计算输出大小时考虑下采样; 用户必须能够以全分辨率进行渲染。 如果输出缓冲区超过 30,000 x 30,000,请将其限制为该大小,并考虑显示警告对话框。

代码正确运行后,启用缓存并查看效果需要多长时间重新渲染一次。 考虑一个阴影(drop shadow); 用户经常将静态阴影应用于静止图像。

output -> extent_hint 被忽略 z 的话,那么将更频繁地使用缓存。

要获得缓冲区扩展(buffer-expanding)效果,请将 output> extent_hint 与插件的变换范围(transformed bound)相交,并在 PF_Cmd_FRAME_SETUP 期间相应地设置大小。

现在多出 20% 的像素!

从 6.0 版开始,传递的 extent_hints 比图层本身大 20 %,以帮助我们进行预测性渲染决策。 许多效果仅通过触摸即可扩展缓冲区,After Effects 以后经常使用提示矩形。

点控制和缓冲区扩展

通过在 PF_Cmd_FRAME_SETUP 期间在 PF_InData 结构体中设置 set output_origin_x / y 来扩展输出缓冲区的效果,使其位于原始图层的左上角。 在 pre_effect_source_origin_x / y 中报告了此偏移对后续效果的影响。 点参数会针对该移位自动进行调整。

在效果生效之前,应用缓冲扩展器,例如高斯模糊或 Resizer SDK 示例,并使用较大的 resize 值。 如果您的效果未正确处理 pre_effect_source_origin_x / y 的话,则打开和关闭模糊将改变输出的位置。

所有点参数值(在任何时候)都具有 pre_effect_source_origin_x / y 描述的偏移值。 对于大多数效果而言,这是透明的。 但是,如果缓冲区扩展随时间变化(如动画模糊量一样),则原点偏移将移动非动画点。 设计在帧之间缓存点参数值的效果时,请考虑这一点。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java反射(三):反射与动态代理 反射与动态代理

发表于 2021-11-28

反射与动态代理

代理模式

代理模式定义:

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。

为什么使用代理模式:

使用代理模式创建代表对象,让代表对象控制真实对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。

简单来说,我们用代理对象来代替真实对象,这样就可以在不修改真实对象的前提下,提供额外的功能操作,拓展目标对象的功能。

以现实生活为例:被告人接受法官问话时,委托一个律师来代替他接受问话。这里的代表对象是律师,真实对象是被告人。律师依据他的专业知识会对法官的问题做出进一步的解释后,私底下来问被告人。嫌疑人私底下给律师说明情况后,律师依靠他的应答技巧再向法官说明解释。

代理模式.jpg

代理模式有静态代理和动态代理两种实现方式。

静态代理

静态代理在程序运行前就将接口、委托类、代理类准备完成(即存在相应的字节码文件)。

以打印”Hello, world!”为例,静态代理实现步骤为:

  1. 定义一个接口
1
2
3
java复制代码interface HelloWordService {
void printHelloWorld();
}
  1. 委托类实现此接口
1
2
3
4
5
6
java复制代码class HelloWorldServiceImpl implements HelloWordService {
@Override
public void printHelloWorld() {
System.out.println("Hello, world!");
}
}
  1. 代理类同样实现此接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码class HelloWorldProxy implements HelloWordService {
private final HelloWordService helloWorldService;

public HelloWorldProxy(HelloWordService helloWorldService) {
this.helloWorldService = helloWorldService;
}

@Override
public void printHelloWorld() {
System.out.println("作为代理我先来做些事");
helloWorldService.printHelloWorld();
System.out.println("作为代理最后再做些事");
}
}
  1. 将委托对象注入进代理对象,在代理对象的对应方法中调用委托对象的对应方法
1
2
3
4
5
6
7
java复制代码public class Main {
public static void main(String[] args) {
HelloWordService helloWordService = new HelloWorldServiceImpl();
HelloWordService helloWordProxy = new HelloWorldProxy(helloWordService);
helloWordProxy.printHelloWorld();
}
}

运行后,控制台打印输出

作为代理我先来做些事
Hello, world!
作为代理最后再做些事

静态代理中,需要对每一种委托类单独写一个代理类,且对委托对象的每个方法的增强都是手动完成的。一旦接口增加新的方法,委托类和代理类都要进行修改。因此我们需要更加灵活的动态代理。

JDK 动态代理

动态代理在程序运行时动态生成代理类字节码并加载进JVM中。

相比于静态代理,动态代理更为灵活。我们只需要定义接口和实现类,无需编写代理类。

JDK 为我们提供了InvocationHandler接口Proxy类去实现动态代理。

Proxy类

Proxy类中最常用的方法是:

1
2
3
java复制代码public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

这个静态方法的参数为:

  1. loader:类加载器,用于加载代理对象
  2. interfaces:委托类实现的一些接口,至少传入一个接口进去
  3. h:实现了InvocationHandler接口的实例来进行逻辑处理

InvocationHandler接口

由第3个参数知,我们必须要实现InvocationHandler接口。当我们调用动态代理对象一个方法时,JVM会调用InvocationHandler接口实现类的invoke方法来处理对应的逻辑。

1
2
3
4
java复制代码public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

该方法有的参数为:

  1. proxy:JVM动态生成的代理类实例
  2. method:与调用代理类实例的方法相对应的Method类实例
  3. args:调用代理类实例方法的参数

JDK 动态代理的使用

继续以打印”Hello, world!”为例,动态代理的实现步骤为:

  1. 定义接口
1
2
3
java复制代码interface HelloWordService {
void printHelloWorld();
}
  1. 委托类实现该接口
1
2
3
4
5
6
java复制代码class HelloWorldServiceImpl implements HelloWordService {
@Override
public void printHelloWorld() {
System.out.println("Hello, world!");
}
}
  1. 定义一个 JDK 动态代理类,在invoke方法中调用原生方法并自定义逻辑处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码class SimpleInvocationHandler implements InvocationHandler {
private final Object target;

public SimpleInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("作为代理我先来做些事");
Object result = method.invoke(target, args);
System.out.println("作为代理最后再做些事");
return result;
}
}
  1. 通过Proxy.newProxyInstance方法创建代理对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码// 定义一个生产代理对象的静态工厂类
class ProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T getProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new SimpleInvocationHandler(target)
);
}
}

public class Main {
public static void main(String[] args) {
// 通过代理工厂,生产代理类实例
HelloWordService serviceProxy =
ProxyFactory.getProxy(new HelloWorldServiceImpl());

serviceProxy.printHelloWorld();
}
}

运行后,控制台打印输出

作为代理我先来做些事
Hello, world!
作为代理最后再做些事

总结

  • 使用代理模式可以在不修改真实对象的前提下,提供额外的功能操作,拓展目标对象的功能
  • 静态代理在程序运行前就将接口、委托类、代理类准备完成(即存在相应的字节码文件)
  • 动态代理在程序运行时动态生成代理类字节码并加载进JVM中

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Arthas在Docker容器中的使用-环境搭建 前言 1

发表于 2021-11-28

前言

  • 实际开发中,项目通常运行在 docker 容器内,学习 Arthas 前,需要构建容器环境
  • 使用 docker pull tomcat 命令从 docker Hub拉取下来的镜像,使用的是 openJdk,其不含用 jps 命令,执行java -jar arthas.jar 启动 arthas 时,会报错,提示找不到 java process

1.构建 Tomcat+Jdk 环境前,下载 tomcat、jdk 安装包

下载 tomcat8.5image.png
下载 jdk1.8image.png

2.创建 Dockerfile 文件,生成镜像

  • 将下载好的 tomcat、jdk 上传至 Linux 中image.png
  • 创建 Dockerfile 文件,文件内容如下,其中 CMD ["/usr/local/apache-tomcat-8.5.31/bin/catalina.sh","run"] 可以使用 docker logs 打印日志。CMD /usr/local/apache-tomcat-8.5.31/bin/startup.sh && tail -F /usr/local/apache-tomcat-8.5.31/logs/catalina.out 会将日志放在 catalina.out 文件中,在docker run 时需配置上日志的挂载,方便查看日志,使用docker logs 无法查看到日志
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
bash复制代码FROM centos
# 设置镜像标签
LABEL commant=easysan
# 设置镜像作者
MAINTAINER easysan<easysan@136.com>
#把java与tomcat添加到容器中
ADD jdk-8u212-linux-x64.tar.gz /usr/local
ADD apache-tomcat-8.5.31.tar.gz /usr/local
#安装vim编辑器
RUN yum -y install vim
#设置工作访问时候的WORKDIR路径,登录落脚点
ENV MYPATH /usr/local
WORKDIR $MYPATH
#配置java与tomcat环境变量
ENV JAVA_HOME /usr/local/jdk1.8.0_212
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-8.5.31
ENV CATALINA_BASE /usr/local/apache-tomcat-8.5.31
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
#容器运行时监听的端口
EXPOSE 8080
#启动时运行tomcat
# ENTRYPOINT ["/usr/local/apache-tomcat-9.0.8/bin/startup.sh" ]
CMD ["/usr/local/apache-tomcat-8.5.31/bin/catalina.sh","run"]
# CMD /usr/local/apache-tomcat-8.5.31/bin/startup.sh && tail -F /usr/local/apache-tomcat-8.5.31/logs/catalina.out
  • 在目录 /mydata/tomcat下使用 docker build -t arthas-tomcat:v1.1.0 . 创建镜像image.png)image.png

3.启动容器

1
ruby复制代码docker run -p 9999:8080 --name arthas_tomcat -v /mydata/tomcat/webapps/:/usr/local/apache-tomcat-8.5.31/webapps -v /etc/localtime:/etc/localtime:ro -d arthas-tomcat:v1.1.0

4.创建一个简单的 spring boot 项目,打war包上传到 /mydata/tomcat/webapps/目录下,重启容器docker restart 9b87fdb14cb2

image.png

image.png

5.下载启动 Arthas,详细步骤可参考官网

  • 进入容器 docker exec -it 9b87fdb14cb2 /bin/bash,此时所在目录为/usr/local。新建目录mkdir arthas 进入cd arthas目录,此处用于存放下载下来的 arthas.jar
  • 下载curl -O https://arthas.aliyun.com/arthas-boot.jar,并启动java -jar arthas.jar
  • 根据检查出的 java 进程,选择对应的序号

image.png

6.Arthas-容器环境搭建完成

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go语言搬砖 dolphinscheduler任务处理

发表于 2021-11-28

「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战」。

简介

image.png
dolphinscheduler是一个可视化DAG工作流任务调度平台,在大数据领域做任务调用非常流行

提供了类似azkaban工作流调度,比azkaban更强的可视化DAG,支持大数据领域flink,spark,shell,python,java,scala,http等各种类型任务

官网传送门: dolphinscheduler.apache.org/zh-cn/

自动化

为什么需要自动化任务处理,当你的dolphinscheduler有几百上千个任务,管理是非常耗时的,如果每个任务都配置邮件告警,那一有问题整天都在救火

此时就需要任务结果监控和任务重跑来解决 失败任务和任务自动重跑,避免浪费过多时间在维护dolphinscheduler任务上

使用

在调用api之前需要为用户申请token,按图操作
image.png

dolphinscheduler提供类似swagge接口UI工具,访问doc地址访问
http://ip:12345/dolphinscheduler/doc.html?language=zh_CN&lang=cn
image.png

例子

该demo还是使用了http请求包(HttpRequest),json数据搜索包(go-jmespath)

任务结果检查

填坑说明

  1. 日期处理: 使用了%20转译空格,使用Sprintf方法拼接字符串
  2. 多种数据类型: 使用interface{}来支持int,string等多种数据类型
  3. 数据转换1: 将byte数据转成json格式,方便搜索
  4. 数据转换2: 将interface{}数据转成字符串切片,方便使用

该方法可以做成周期性任务运行,将失败的job查出来,后续是要告警通知,还是根据job名称查出对应id进行重跑任务

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
js复制代码package main

import (
"encoding/json"
"fmt"
"github.com/jmespath/go-jmespath"
"github.com/kirinlabs/HttpRequest"
"time"
)

var (
url = "http://ip:12345/dolphinscheduler"
token = "xxxxxxx"
req *HttpRequest.Request
)

func init() {
req = HttpRequest.NewRequest().Debug(true).SetTimeout(time.Second*5).
SetHeaders(map[string]string{
"token":token,
})
}
func main() {
//testConn()
jobCheck()
}

func jobCheck() {
//获取日期
today := time.Now().Format("2006-01-02")
tomorrow := time.Now().AddDate(0, 0, +1).Format("2006-01-02")
//拼接日期 %20是空格的转译
fmt.Println(fmt.Sprintf("%v%v",today,"%2000:00:00"))
fmt.Println(fmt.Sprintf("%v%v",tomorrow,"%2000:00:00"))

//需要检查的项目名称
projects := []string{"jdOrder","jdPlay"}

//需要检查的时间段 页码是int类型,日期是string类型
m := make(map[string]interface{})
m["pageNo"] = 1
m["pageSize"] = 22
m["stateType"] = "FAILURE"
m["startDate"] = fmt.Sprintf("%v%v",today,"%2000:00:00")
m["endDate"] = fmt.Sprintf("%v%v",tomorrow,"%2000:00:00")

for _, project := range projects {
resp, _ := req.Get(url+"/projects/"+project+"/task-instance/list-paging",m)
if resp.StatusCode() != 200 {
fmt.Println("job检查状态码不符期望: ",resp.StatusCode())
return
}
fmt.Println("resp",resp)
//将返回数据从byte转成json格式
body, _ := resp.Body()
var i interface{}
var s []string
_ = json.Unmarshal(body, &i)
//搜索出需要的字段对应数据
processInstanceNames, _ := jmespath.Search("data.totalList[*].processInstanceName", i)
//将interface转成[]string
for _,v := range processInstanceNames.([]interface{}) {
s = append(s,v.(string))
}
//打印出结果
for _,v := range s {
fmt.Println(v)
}

}
}

测试连接

如果上小节任务跑不成功,可以先运行该方法,测试连接正确性

1
2
3
4
5
6
7
8
js复制代码func testConn() {
resp, _ := req.Get(url + "/projects/query-project-list")
fmt.Println("resp",resp)
body, _ := resp.Body()
var i interface{}
_ = json.Unmarshal(body, &i)
fmt.Println("i",i)
}

重跑任务

重跑任务其实就是再次启动任务,直接调用start_job既可

项目名称和ID需要通过该接口获取,这个是固定的
http://ip:12345/dolphinscheduler/projects/monitor/process/list-paging

调用示例: startJob(“ads_jd_order”,678)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
js复制代码func startJob(projectName string,projectId int)  {

m := make(map[string]interface{})
m["failureStrategy"] = "CONTINUE"
m["warningGroupId"] = 0
m["warningType"] = "NONE"
m["runMode"] = "RUN_MODE_SERIAL"
m["processInstancePriority"] = "MEDIUM"
m["workerGroup"] = "default"
m["processDefinitionId"] = projectId
resp, _ := req.JSON().Post(url+"projects/" + projectName+"/executors/start-process-instance",m)
if resp.StatusCode() != 200 {
fmt.Println("job开始状态码不符期望: ",resp.StatusCode())
return
}
}

小结

dolphinscheduler api调用有文档,不太复杂,但网上资料较少,需要自行摸索,博主先在这里分享一波

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

JVM 垃圾回收器总结

发表于 2021-11-28

在说垃圾回收器之前先复习一下一些概念,以便能更形象的知道各个收集器的设计目标及特性。

一.先回顾一下分代收集理论(毕竟hotSpot以此为基础进行收集的)

当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”的理论去设计的。也就是将堆分为“新生代”与“老年代”。一个在新生代的对象经过一次次GC 不断的改变着自己对象头上的GC年龄 直至进入老年代,当然新生代进行收集的时候98%基本都会被清除。(简单带过 具体的自己去看去吧)

image.png

1.部分收集(Partial)

1.1 新生代收集(Minor GC / Young GC)

指目标只是新生代的垃圾收集

1.2 老年代收集(Major GC / Old GC)

只发生在老年代的GC,目前就只有CMS收集器会有单独收集老年代的行为

1.3 混合收集(Mixed GC)

指目标是收集整个新生代以及部分老年代的老年代的垃圾收集,目前只有G1收集器会有这种行为

2.整堆收集(Full GC)

收集整个Java堆和方法区的垃圾收集器

二.可达性分析的实际应用以及并发的可达性分析

2.1 一致性快照及垃圾收集器为之努力的其中一个目标

当前主流的垃圾收集器基本都是依靠可达性分析算法来判定对象是否存活的,可达性分析算法理论上要求全过程都基于一个能保障一致性的快照中才能进行分析,这就意味着要冻结用户线程的运行。由于GC Root的个数和整个堆中对象比起来还是占据极少数,并且在OopMap等优化的加持下,它带来的停顿时间已经是非常短暂且固定(不会随堆容量而增长),但是!!!从GC Root往下进行遍历对象图,这的时间就会和堆的容量成正比了,于是为了减少这部分的时间便成了各个虚拟机为之努力的目标(除了 PS收集器 它追寻的是吞吐量)

2.2 为什么一定要一致性的快照?—三色标记法

要想解决或降低用户线程的停顿时间,就必须要搞清楚为什么必须在一个能保障一致性的快照上才能进行对象图的遍历?
再遍历对象图的时候,我们可以把遇到的对象,按照“是否访问过”这个条件进行标记成“黑”,“灰”,“白”三种颜色

白色:表示对象尚未被垃圾收集器访问过。显然在刚开始分析的时候都是白色,如果分析完了,仍然是白色,即代表不可达,这些白色就可以被打扫掉了。
黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有饮用都已经扫描过。黑色的代表是安全存活的,如果有其它对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能不经过灰色直接指向白色对象
灰色:表示对象已经被垃圾收集器访问过,但这个对象至少还存在一个引用还没有被扫描过。

上图描述

2.2.1 初始状态,只有GC Root 是黑色的,对象只有被黑色对象引用才会存活,其它的都会消亡
image.png

2.2.2 扫描过程中,推进式扫描,以灰色为中间介质 由黑向白推进
image.png

2.2.3 扫描完成,此时黑色就是可以存活的,白色的就是可以被清除的
image.png

2.3 并发的可行性分析

了解了上面的几个概念,那我们改如何优化“冻结用户线程”这个问题呢? 我们是不是可以GC 线程进行工作的同时让用户线程也可以继续工作呢?在得到结论前 我们不妨来想一下如果并发进行会出现的问题。

问题一:把原本消亡的对象错误的标记为存活 如图在推进过程中正在(ing)扫描的灰色的对象的引用被黑色饮用
image.png

问题二:把该活的对象标记死了 如图切断后重新被黑色引用,由于黑色不会重新扫描,所以导致最后扫描完成这个新的引用的对象仍是白色 会被清除掉 这是不应该的

image.png

其中第一个问题是可以容忍的,但是第二个问题(误删) 我们该怎么解决呢?

1.增量更新
当黑色对象插入新的指向白色对象的引用关系时,会将这个新插入的引用记录下来,等并发扫描结束后,再将这些记录的引用关系中的黑色对象为根再重新扫描一次 (记录新插入黑色和白色的引用关系,之后以关系中的黑色为root再扫,黑退化为灰) (CMS)

2.原始快照
当灰色对象要删除指向白色对象的引用关系时,就把这个删除的记录下来,在并发扫描结束后,再以记录下来的关系中的灰色为Root重新扫描(记录灰色和白色的引用关系,之后再以灰色为Root扫,相当于记录下了那一时刻的快照) (G1,Shenandoah)

以上两种方式的引用关系记录都是通过“写屏障”来完成的

三.经典的垃圾收集器

找了一张对比图 感谢水印大佬

image.png

Serial

作用于年轻代 标记复制 虽然是单线程的 但是在单核处理器中或者处理器核心数较少的环境中,Serial由于没有线程交互的开销,倒是可以高效率的运行 可以和SerialOld 和CMS一起使用 低内存下可以考虑使用 但是现在很少使用了

image.png

ParNew

作用于年轻代 标记复制 多线程并行进行垃圾收集 除了线程其余基本上完全等同于Serial 可以与SerialOld 和CMS搭配使用 自JDK9开始官方不推荐使用ParNew + SerialOld以及 Serial + CMS了
ParNew在单线程里绝对不会比Serial表现的好因为会有多线程的开销 不过在多核环境下表现还ok

image.png

Parallel Scavenge(PS)

作用于年轻代 标记复制 多线程并行进行垃圾收集 但是他追求的不是用户线程停顿的时间,追求的是吞吐量,所以适合应用于纯后台计算的服务而不是和前台用户交互的服务中 可以于Parallel Old搭配使用
吞吐量=运行用户代码时间/运行用户代码时间+运行垃圾收集时间
可以通过-XX:GCTimeRatio(设置吞吐量大小) 大于0小于99 -XX:MaxGCPauseMillis(控制最大垃圾收集时间) 大于0 的毫秒数 控制吞吐量
也可通过-XX:+UserAdaptiveSizePolcy 让系统自己控制新生代Eden 与Survivor区的比例 以及晋升老年代对象的大小等参数 系统动态调节这些参数(自适应策略)来提升吞吐量

Parallel Old(PO)

作用于老年代 标记整理 多线程并发收集 自JDK 6时一直搭配着PS使用 保证吞吐量

image.png

CMS

作用于老年代 标记清除 多线程并发收集 追求的是最短回收停顿时间 适用于B/S服务 及时响应客户端请求
CMS整个收集过程分为4步

1.初始标记 (Stop The World)

2.并发标记

3.重新标记 (Stop The World)

4.并发清除

缺点:

  • 虽然并发阶段不会暂停用户线程,但是新开出的GC线程也是要占用资源的,可能会导致程序变慢,降低总吞吐量,CMS默认开启的回收线程数是(处理器核心数量+3)/4
  • 在并发标记和并发清理阶段,因为不STW,所以用户县城还是正常运行的,程序就自然伴随着新的垃圾对象产生,但是这一部分是在标记完成后出现的,所以当次无法回收,这一部分垃圾称为“浮动垃圾”,所以CMS要预留出一定的空间给新的对象,这个时候可能面临一个风险,就是来了一个大对象,预留空间因无法分配内存,就会出现一次“并发失败”,这个时候就不得不启动预案:Serial Old重新进行收集这个时候 时间就长了 所以我们应该避免这种状况
    可以适当的调小一点 -XX:CMSInitiatingOccupancyFraction (触发百分比)但是太小了也不行,太小了就总会触发GC

image.png

Garbage First(G1)

目标:停顿时间模型(支持指定一个长度为M毫秒的时间片段,消耗在垃圾收集上的时间大概率不会超过N这样的目标)

分为4步:

  • 初始标记(Stop The World)
  • 并发标记
  • 最终标记(Stop The World)
  • 筛选回收 (Stop The World)

分代:作用于年轻代+老年代,但是它的分代并没有严格的物理界限 标记整理+标记复制 多线程并发 追求同CMS 适用于面向服务端的应用 保证响应时间

G1不直接面向老年代或者年轻代进行收集,而是面向堆内任何部分组成回收集(CSet)进行回收,衡量标准不再是属于哪个代,而是哪块内存存放的垃圾最多,回收效益最大,这就是Mixed GC模式

G1把连续的Java堆划分为多个大小相等且独立的区(Region)每个Region都可以根据需要扮演Eden空间或者Suivivor空间,或者是老年代空间,之后收集器能根据扮演角色的不同去用不同的策略处理,可根据 -XX:G1HeapRegionSize设定(1M~32M)一般是2的N次幂
Region中还有一个比较特殊的Humongous区域专门用来存放大对象的(大小超过Region的一半),会被当作老年代看待

G1虽然仍保留了新生代老年代的概念,但是位置不在是固定的了,他们是一系列区域的集合(可以不连续)也就是说会把这些Region放到一个优先级列表之后根据列表进行垃圾回收

缺点:

  • G1也存在跨代问题,也是用卡表维护关系,但是它的卡表不是数组,本质上是一个Hash表,key是别的Region的起始地址,value是一个集合,存着卡表的索引号(也就是说hash里不仅存着我指向谁,还要存着谁指向我)为什么这么存呢:预防追溯不到
  • 并发时问题2的解决G1是通过原始快照解决的,也就是存在着新对象内存分配问题,如果内存回收速度赶不上内存分配速度G1也要被迫冻结用户线程执行从而导致Full GC而产生长时间STW。

ZGC
在jdk11中,即将迎来ZGC(The ZGarbage Collector),这是一个处于实验阶段的,可扩展的低延迟垃圾回收器。

  • 每次GC STW的时间不超过10ms
  • 能够处理从几百M到几T的JAVA堆
  • 与G1相比,吞吐量下降不超过15%

image.png

四.如何选用合适的垃圾收集器呢

三选二原则

1.吞吐量 2.延迟 3.内存占用 任选两个去选择对应的

2.根据自己业务去实际进行压测

引用:深入理解JVM虚拟机 周志明

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…146147148…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%