前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[Java项目Gradle依赖包整合]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
转载文章
...了,我在看之前所做的项目时,在我的收藏夹中看到了以前收藏的有关爬虫的文章,点开后又重新学习了一下。 下面是这两篇文章的链接 java实现网络爬虫:https://www.cnblogs.com/1996swg/p/7355577.html Jsoup教程:https://www.jianshu.com/p/fd5caaaa950d 接下来,我通过Jsoup来实现爬取彼岸桌面里面的图片进行爬虫学习!!! 我用的开发工具是IDEA,jdk是1.7版本,项目结构大致如下所示: 一、页面分析 首先来分析一下彼岸桌面的网页的结构: 我们第一个看到的是网站的域名为http://www.netbian.com/,它有如上所示的分类,我们尝试着点开一些分类去看一下他的链接。 通过点击每个分类,发现不同的分类下,地址栏显示为域名后面拼接这对应分类的拼音,但在分类为王者荣耀之后的拼接的确是“s/分类拼音”。这样我们可以创建一个枚举类,将所有分类集中管理。在common包下创建一个Kind枚举类: package com.asahi.common;/ 分类的枚举/public enum Kind {RILI("rili"), DONGMAN("dongman"), FENGJING("fengjing"), MEINV("meinv"), YOUXI("youxi"), YINGSHI("yingshi"),DONGTAI("dongtai"), WEIMEI("weimei"), SHEJI("sheji"), KEAI("keai"), QICHE("qiche"), HUAHUI("huahui"),DONGWU("dongwu"), JIERI("jieri"), RENWU("renwu"), MEISHI("meishi"), SHUIGUO("shuiguo"), JIANZHU("jianzhu"),TIYU("tiyu"), JUNSHI("junshi"), FEIZHULIU("feizhuliu"), QITA("qita"), WANGZHERONGYAO("s/wangzherongyao"), HUYAN("s/huyan"), LOL("s/lol");String kind;Kind(String kind) {this.kind = kind;}public static boolean contains(String test) {for (Kind c : Kind.values()) {if (c.kind.equals(test)) {return true;} }return false;} } 这里我添加了一个比较的方法供之后判断输入的分类名是否包含在这些分类里面。 接下来我们在分析分类面的展示情况,以美女分类页面为例(●´∀`●),最下边有分页,如果只获取这个页面的图片并不能获取所有美女图,我们还需要点击每一个分页,从分页中获取所有的图片。通过分析发现,第一页的链接是在原有链接基础上拼接“/index.htm”,从第二页之后拼接的是“/index_页号.htm”。 这样我们只需要获取总页数在依次遍历拼接就可以了,现在的问题是如何获取总页数,我一开始的想法是获取分页中“共167页”这个标签后再只保留数字就可以个,但发现运行后获取不到该元素节点,经过排查了解到这个标签是通过js生成的,于是我转换了思路,通过获取最后一个页号来得到一共分了多少页 Document root_doc = Jsoup.connect("http://www.netbian.com/" + kind + "/").get();Elements els = root_doc.select("main .page a");//这里els.eq(els.size() - 2的原因是后边确定按钮用的是a标签要去掉,再去掉一个“下一页”标签Integer page = Integer.parseInt(els.eq(els.size() - 2).text()); 分类页中图片所在的标签结构为: 分类页面下的图片不是我们想要的,我们想要的是点击进去详细页的高清大图,所以需要获取a标签的链接,再从这个链接中获取真正想要的图片。 详细页中图片所在的标签结构为: 二、代码实现 到这里分类页分析的差不多了,我们通过代码来进行获取图片。首先导入Jsoup的jar包:jsoup-1.12.1.jar,如果采用Maven请导入下边的依赖。 <dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.12.1</version></dependency> 在utils创建JsoupPic类,并添加getPic方法,代码如下: public static void getPic(String kind) throws Exception {//get请求方式进行请求Document root_doc = Jsoup.connect("http://www.netbian.com/" + kind + "/").get();//获取分页标签,用于获取总页数Elements els = root_doc.select("main .page a");Integer page = Integer.parseInt(els.eq(els.size() - 2).text());for (int i = 1; i < page; i++) {Document document = null;//这里判断的是当前页号是否为1,如果为1就不拼页号,否则拼上对应的页号if (i == 1) {document = Jsoup.connect("http://www.netbian.com/" + kind + "/index.htm").get();} else {document = Jsoup.connect("http://www.netbian.com/" + kind + "/index_" + i + ".htm").get();}//获取每个分页链接里面a标签的链接,进入链接页面获取当前图拼的大尺寸图片Elements elements = document.select("main .list li a");for (Element element : elements) {String href = element.attr("href");String picUrl = "http://www.netbian.com" + href;Document document1 = Jsoup.connect(picUrl).get();Elements elements1 = document1.select(".endpage .pic p a img");//获取所有图片的链接System.out.println(elements1);} }} 在分类页中有一个隐藏的问题图片: 正常的图片链接都是以“/”开头,以“.htm”结尾,而每个分类下的第三张图片的链接都是“http://pic.netbian.com/”,如果不过滤的话会报如下错误: 所以这里必须要判断一下: Elements elements = document.select("main .list li a");for (Element element : elements) {String href = element.attr("href");//判断是否是以“/”开头if (href.startsWith("/")) {String picUrl = "http://www.netbian.com" + href;Document document1 = Jsoup.connect(picUrl).get();Elements elements1 = document1.select(".endpage .pic p a img");System.out.println(elements1);} } 到这里,页面就已经分析好了,问题基本上已经解决了,接下来我们需要将图片存到我们的系统里,这里我将图片保存到我的电脑桌面上,并按照分类来存储图片。 首先是要获取桌面路径,在utils包下创建Download类,添加getDesktop方法,代码如下: public static File getDesktop(){FileSystemView fsv = FileSystemView.getFileSystemView();File path=fsv.getHomeDirectory(); return path;} 接着我们再该类中添加下载图片的方法: //urlPath为网络图片的路径,savePath为要保存的本地路径(这里指定为桌面下的images文件夹)public static void download(String urlPath,String savePath) throws Exception {// 构造URLURL url = new URL(urlPath);// 打开连接URLConnection con = url.openConnection();//设置请求超时为5scon.setConnectTimeout(51000);// 输入流InputStream is = con.getInputStream();// 1K的数据缓冲byte[] bs = new byte[1024];// 读取到的数据长度int len;// 输出的文件流File sf=new File(savePath);int randomNo=(int)(Math.random()1000000);String filename=urlPath.substring(urlPath.lastIndexOf("/")+1,urlPath.length());//获取服务器上图片的名称filename=new java.text.SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())+randomNo+filename;//时间+随机数防止重复OutputStream os = new FileOutputStream(sf.getPath()+"\\"+filename);// 开始读取while ((len = is.read(bs)) != -1) {os.write(bs, 0, len);}// 完毕,关闭所有链接os.close();is.close();} 写好后,我们再完善一下JsouPic中的getPic方法。 public static void getPic(String kind) throws Exception {//get请求方式进行请求Document root_doc = Jsoup.connect("http://www.netbian.com/" + kind + "/").get();//获取分页标签,用于获取总页数Elements els = root_doc.select("main .page a");Integer page = Integer.parseInt(els.eq(els.size() - 2).text());for (int i = 1; i < page; i++) {Document document = null;//这里判断的是当前页号是否为1,如果为1就不拼页号,否则拼上对应的页号if (i == 1) {document = Jsoup.connect("http://www.netbian.com/" + kind + "/index.htm").get();} else {document = Jsoup.connect("http://www.netbian.com/" + kind + "/index_" + i + ".htm").get();}File desktop = Download.getDesktop();Download.checkPath(desktop.getPath() + "\\images\\" + kind);//获取每个分页链接里面a标签的链接,进入链接页面获取当前图拼的大尺寸图片Elements elements = document.select("main .list li a");for (Element element : elements) {String href = element.attr("href");if (href.startsWith("/")) {String picUrl = "http://www.netbian.com" + href;Document document1 = Jsoup.connect(picUrl).get();Elements elements1 = document1.select(".endpage .pic p a img");Download.download(elements1.attr("src"), desktop.getPath() + "\\images\\" + kind);} }} } 在Download类中,我添加了checkPath方法,用于判断目录是否存在,不存在就创建一个。 public static void checkPath(String savePath) throws Exception {File file = new File(savePath);if (!file.exists()){file.mkdirs();} } 最后在mainapp包内创建PullPic类,并添加主方法。 package com.asahi.mainapp;import com.asahi.common.Kind;import com.asahi.common.PrintLog;import com.asahi.utils.JsoupPic;import java.util.Scanner;public class PullPic {public static void main(String[] args) throws Exception {new PullPic().downloadPic();}public void downloadPic() throws Exception {System.out.println("启动程序>>\n请输入所爬取的分类:");Scanner scanner = new Scanner(System.in);String kind = scanner.next();while(!Kind.contains(kind)){System.out.println("分类不存在,请重新输入:");kind = scanner.next();}System.out.println("分类输入正确!");System.out.println("开始下载>>");JsoupPic.getPic(kind);} } 三、成果展示 最终的运行结果如下: 最终的代码已上传到我的github中,点击“我的github”进行查看。 在学习Java爬虫的过程中,我收获了很多,一开始做的时候确实遇到了很多困难,这次写的获取图片也是最基础的,还可以继续深入。本来我想写一个通过多线程来获取图片来着,也尝试着去写了一下,越写越跑偏,暂时先放着不处理吧,等以后有时间再来弄,我想问题应该不大,只是考虑的东西有很多。希望大家多多指点不足,有哪些需要改进的地方,我也好多学习学习๑乛◡乛๑。 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_39693281/article/details/108463868。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-06-12 10:26:04
130
转载
Apache Atlas
...问题的出现 在我们的项目中,我们使用了Apache Atlas来进行数据管理。然而,当我们在进行系统升级时,发现数据迁移失败了。具体来说,当我们尝试将旧版本的数据迁移到新版本时,出现了错误。 二、分析原因 那么,为什么会出现这种问题呢?我们需要对这个问题进行深入的分析。首先,我们需要查看错误信息,看看是否有明确的错误提示。通常情况下,错误信息会提供一些线索,帮助我们找到问题的原因。 例如,假设错误信息如下: bash java.lang.RuntimeException: Failed to migrate data from old version to new version 从这个错误信息可以看出,问题可能出在数据迁移的过程中。那么,我们应该如何进一步查找原因呢? 三、解决问题 为了解决这个问题,我们可以采取以下几种方法: 1. 检查数据结构 首先,我们需要检查数据结构是否正确。要是我们对数据模型做了改动,比如加了几个新的字段啥的,那么在搬运数据的过程中,就可能会遇到点小状况。 例如,假设我们在旧版本中有一个用户表,而在新版本中,我们添加了一个新的字段"email"。那么,在进行数据迁移时,我们就需要确保所有的用户都有一个有效的电子邮件地址。 sql UPDATE user SET email = NULL WHERE email IS NOT NULL; 2. 检查映射规则 其次,我们需要检查映射规则是否正确。如果我们改变了映射关系,那么在进行数据迁移时也可能会出现问题。 例如,假设我们在旧版本中有一个用户表和一个订单表,它们之间的映射关系是通过用户的ID来建立的。而在新版本中,我们改变成了通过用户的邮箱地址来建立映射关系。那么,在进行数据迁移时,我们就需要重新建立映射关系。 sql ALTER TABLE order ADD CONSTRAINT fk_user_email FOREIGN KEY (email) REFERENCES user(email); 3. 检查权限设置 最后,我们需要检查权限设置是否正确。如果我们改变了权限设置,那么在进行数据迁移时也可能会出现问题。 例如,假设我们在旧版本中允许所有用户都可以查看订单。而在新版本中,我们只允许管理员可以查看订单。那么,在进行数据迁移时,我们就需要修改权限设置。 sql GRANT SELECT ON order TO admin; 四、总结 总的来说,解决Apache Atlas数据迁移失败的问题需要我们进行深入的分析,并采取相应的措施。只有这样,我们才能保证数据迁移的成功。 在这个过程中,我们需要不断学习和提高,以应对各种挑战。因为说到底,只有当我们真正掌握了那些关键的技能和知识,才能手到擒来地解决各种问题,让我们的项目顺风顺水地向前推进。所以,让我们一起努力吧!
2023-11-27 10:58:16
271
人生如戏-t
Struts2
... Struts2中“Java.lang.NullPointerException in Action class while executing method 'execute'”问题解析与解决方案 在我们深入使用Struts2框架进行Java Web开发时,偶尔会遇到一种常见的运行时异常——Java.lang.NullPointerException,尤其在Action类执行execute方法时。这篇东西,咱们就来点儿接地气的,从实际动手干的视角,一边瞅着代码实例,一边掰扯这个问题是怎么冒出来的、怎么把它摆平的,还有怎样提前给它上个“紧箍咒”,预防它再出来闹腾。 1. 异常现象分析 首先,让我们通过一个示例来直观感受一下这个问题。假设我们有一个简单的Struts2 Action类: java public class UserAction extends ActionSupport { private UserService userService; // 这是一个依赖注入的对象 public String execute() { User user = userService.getUserById(1); // 假设这里调用服务层获取用户信息 // ... 其他业务逻辑 return SUCCESS; } // getter 和 setter 方法省略... } 当执行上述execute方法时,如果出现NullPointerException,则意味着在执行userService.getUserById(1)这行代码时,userService对象未被正确初始化,其值为null。 2. 问题根源探究 原因一:依赖注入失败 在Struts2中,我们通常利用框架的依赖注入功能来实现Action和Service之间的解耦。就像刚才举的例子那样,如果咱们没有给userService这个家伙喂饱饭(也就是没有正确注入它),或者在喂饭的过程中出了岔子,那么到执行execute方法的时候,userService就会变成一个空肚子(null),这样一来,就难免会闹肚子(引发异常)了。 原因二:实例化时机不当 另一种可能的情况是,尽管在配置文件中设置了依赖注入,但可能由于某些原因(例如配置错误或加载顺序问题),导致注入的服务对象尚未初始化完成,此时访问也会抛出空指针异常。 3. 解决方案及示例 解决方案一:确保依赖注入生效 在Struts2的配置文件中(通常是struts.xml),我们需要明确指定Action类中需要注入的属性和服务对象的关系: xml /success.jsp userServiceBean 解决方案二:检查并修正实例化顺序 如果确认了依赖注入配置无误,但仍出现空指针异常,则应检查应用启动过程中相关Bean的加载顺序,确保在Action类执行execute方法之前,所有依赖的对象已经成功初始化。 解决方案三:防御性编程 无论何种情况,我们在编码时都应当遵循防御性编程原则,对可能为null的对象进行判空处理: java public class UserAction extends ActionSupport { private UserService userService; public String execute() { if (userService != null) { // 防御性判空 User user = userService.getUserById(1); // ... 其他业务逻辑 } else { System.out.println("userService is not initialized correctly!"); // 打印日志或采取其他容错处理 } return SUCCESS; } // getter 和 setter 方法省略... } 4. 总结与思考 面对“Java.lang.NullPointerException in Action class while executing method 'execute'”这样的问题,我们需要从多方面进行排查和解决。不仅仅是对Struts2框架的依赖注入机制了如指掌,更要像侦探一样时刻保持警惕,做好咱们的防御性编程工作。为啥呢?这就像是给程序穿上防弹衣,能有效防止那些突如其来的运行时异常搞崩我们的程序,让程序稳稳当当地跑起来,不尥蹶子。在实际做项目的时候,把这些技巧学懂了、用溜了,那咱们的开发速度和代码质量绝对会嗖嗖往上涨,没跑儿!
2023-06-26 11:07:11
69
青春印记
转载文章
...除相应内容。 资料 Java Swing 介绍 JavaFX快速入门 Java Swing 图形界面开发简介 GUI Designer Basics scrcpy ScrcpyController Services 创建一个Gui Form文件(类+form文件) 创建一个Gui Form 此时生成两个类, 是自动关联的 鼠标可以拖入控件,每拖入一个组件,在TestForm.java类中会自动生成对应的属性名. 界面显示 显示在ToolWindow的右侧 我们拿ScrcpyController举例,显示如下的界面 需要用到如下三个文件 其中ScrcpyController显示的就是界面, ScrcpyToolWindowFactory是把界面放入的的工厂类. 声明这个工厂类 <extensions defaultExtensionNs="com.intellij"><toolWindowanchor="right"canCloseContents="false"factoryClass="com.codertainment.scrcpy.controller.ui.ScrcpyToolWindowFactory" icon="/toolWindowIcon.svg"id="scrcpy"/>...</extensions> 设置界面 需要如下三个类 ScrcpyControllerSettingsComponent是界面, ScrcpyControllerconfigurable是绑定的工厂类. 在plugin.xml中声明 <extensions defaultExtensionNs="com.intellij"><applicationConfigurabledisplayName="Scrcpy Controller" id="com.codertainment.scrcpy.controller.ui.ScrcpyControllerConfigurable"instance="com.codertainment.scrcpy.controller.ui.ScrcpyControllerConfigurable"parentId="tools"/></extensions> 存储服务 本篇文章为转载内容。原文链接:https://blog.csdn.net/AdrianAndroid/article/details/127860988。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-05-01 10:38:51
437
转载
转载文章
在了解了如何通过Java的JACOB工具调用中控考勤机SDK进行设备连接后,我们可以进一步关注企业级考勤系统与现代技术融合的最新趋势。近日,多家知名人力资源管理软件提供商推出了基于云服务和人工智能技术的智能考勤解决方案,不仅能够实现传统考勤功能,如记录员工上下班时间、异常考勤提醒等,还能够结合大数据分析提供出勤统计报表、劳动力效能分析等增值服务。 例如,阿里云的人力资源管理系统就集成了先进的面部识别技术,将考勤机与云端数据同步,实现了无接触式的高效打卡体验,并且支持远程办公场景下的虚拟签到。此外,该系统还能与其他业务模块深度集成,为企业决策者提供全面的人力资源视图,助力优化企业运营策略。 深入探讨考勤系统的安全性问题也不容忽视,随着数据隐私保护法规日益严格,如何确保考勤数据的安全存储与传输成为业界焦点。一些厂商开始采用区块链技术,确保考勤信息不可篡改,保障员工隐私权益。 总的来说,随着信息技术的日新月异,考勤系统的开发与应用正不断突破边界,从单一的硬件接入转变为云服务+AI赋能的整体解决方案,为企业提供了更强大、安全且便捷的考勤管理方式。在实际项目开发过程中,理解并掌握类似JACOB这样的中间件工具,对于整合不同平台资源,实现多元化的企业级应用具有重要意义。
2023-03-31 22:17:40
215
转载
Groovy
...vy注解处理器是基于Java的JSR-269标准实现的,可以在编译时扫描并处理源代码中的注解,从而生成新的类、方法或其他程序元素。这就像一个神奇的“预处理器”,在我们的代码真正执行前就对其进行加工和优化。 groovy @MyCustomAnnotation class MyClass { // ... } 在上面的例子中,@MyCustomAnnotation就是一个自定义注解,如果我们有一个对应的注解处理器,那么在编译阶段,它就能检测到这个注解,并根据注解的含义进行相应的处理。 3. 创建Groovy注解处理器 (1)定义注解 首先,我们需要定义一个注解,例如: groovy import java.lang.annotation. @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @interface MyCustomAnnotation { String value() default "default_value" } 这里的MyCustomAnnotation是一个简单的注解,它可以被应用于类型上,并且具有一个可选的属性value。 (2)实现注解处理器 接下来,我们创建一个实现了org.codehaus.groovy.transform.ASTTransformation接口的类,作为我们的注解处理器: groovy import org.codehaus.groovy.ast.; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.transform.GroovyASTTransformation; @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) public class MyCustomAnnotationProcessor implements ASTTransformation { @Override void visit(ASTNode[] nodes, SourceUnit source) { ClassNode annotatedClass = (ClassNode) nodes[1]; AnnotationNode annotationNode = (AnnotationNode) nodes[0]; // 获取注解的值 String annotationValue = annotationNode.getMember("value").toString(); // 这里进行具体的处理逻辑,如修改类定义等 // ... } } 在这个处理器中,visit方法会在编译期间被调用,我们可以在这里读取注解的信息并对类结构进行修改。 4. 注解处理器的应用及思考 想象一下,当我们为MyCustomAnnotation编写了一个实际的处理器后,就可以对标记了该注解的类进行各种有趣的操作,比如生成日志代码、实现AOP切面编程、动态生成数据库访问层等等。这种能力让Groovy如虎添翼,灵活性和实用性蹭蹭上涨,开发者们能够更“接地气”地深入到编译的各个环节,亲手打造更高层次的抽象和自动化功能,简直爽翻天! 当然,在享受这种强大功能的同时,我们也需要谨慎地权衡。过多的编译时处理可能会增加项目的复杂度,使得代码变得难以理解和维护。所以在实际编程干活儿的时候,咱们得瞅准具体的需求,聪明地、恰到好处地用上Groovy注解处理器这个小功能,别浪费也别滥用。 结语 总的来说,Groovy的注解处理器为我们提供了一种深度介入编译过程的方式,使我们有机会创造出更为高效、精简的代码结构。让我们怀揣着对编程艺术的满腔热爱,就像拥有了Groovy注解处理器这个强大的秘密武器,一起勇往直前去探索、去创新,一块儿携手并肩,让软件工程的世界不断向前奔跑,蓬勃发展!下次你要是碰到个编程难题,纠结得头发都快薅光了,试试看用Groovy注解处理器来对付它,没准儿能给你整出个意料之外、惊喜连连的解决方案!
2024-03-18 11:15:36
490
飞鸟与鱼
Gradle
Gradle插件 , Gradle插件是扩展Gradle构建系统的组件,允许开发者为Gradle添加新的功能和自定义行为。这些插件通常使用Groovy或Kotlin编写,并通过实现org.gradle.api.Plugin接口来定义其逻辑。在本文中,Gradle插件被用于定制错误处理流程,以应对构建过程中的各种异常情况。 GradleException , GradleException是Gradle系统内建的一种异常类型,当在Gradle构建过程中遇到无法继续执行的错误时抛出。在自定义错误处理逻辑中,如果决定由于特定异常导致构建应停止执行,可以抛出GradleException并附带相应的错误消息,以便向用户清晰展示问题原因及上下文信息。 TaskExecutionGraph , 在Gradle中,TaskExecutionGraph是一个数据结构,它代表了项目中所有任务及其相互依赖关系的整体视图。这个图形结构使得Gradle能够确定任务执行的顺序,并支持全局监听任务执行状态(包括异常)。虽然文章没有直接提到TaskExecutionGraph,但在实际开发Gradle插件时,它可以作为强大的工具用于更复杂的错误处理场景,比如根据任务执行的状态和依赖关系动态调整错误处理策略。
2023-05-21 19:08:26
427
半夏微凉
Kylin
...数据来毫不含糊。这个项目最早是eBay的大佬们搞出来的,后来他们把它交给了Apache基金会,让它成为大家共同的宝贝。在用Kylin的时候,我真是遇到了一堆麻烦事儿,从设置到安装,再到调整性能,每一步都像是在闯关。嘿,今天我打算分享点实用的东西。基于我个人的经验,咱们来聊聊在配置和部署Kylin时会遇到的一些常见坑,还有我是怎么解决这些麻烦的。准备好了吗?让我们一起避开这些小陷阱吧! 2. Kylin环境搭建 首先,我们来谈谈环境搭建。搭建Kylin环境需要一些基本的软件支持,如Java、Hadoop、HBase等。我刚开始的时候就因为没有正确安装这些软件而走了不少弯路。比如我以前试过用Java 8跑Kylin,结果发现好多功能都用不了。后来才知道是因为Java版本太低了,怪自己当初没注意。所以在启动之前,记得检查一下你的电脑上是不是已经装了Java 11或者更新的版本,最好是长期支持版(LTS),这样Kylin才能乖乖地跑起来。 java 检查Java版本 java -version 接下来是Hadoop和HBase的安装。如果你用的是Cloudera CDH或者Hortonworks HDP,那安装起来就会轻松不少。但如果你是从源码编译安装,那么可能会遇到更多问题。比如说,我之前碰到过Hadoop配置文件里的一些参数不匹配,结果Kylin就启动不了。要搞定这个问题,关键就是得仔仔细细地检查一下配置文件,确保所有的参数都跟官方文档上说的一模一样。 xml 在hadoop-env.sh中设置JAVA_HOME export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 3. Kylin配置详解 在完成环境搭建后,我们需要对Kylin进行配置。Kylin的配置主要集中在kylin.properties文件中。这个文件包含了Kylin运行所需的几乎所有参数。我头一回设置的时候,因为对那些参数不太熟悉,结果Kylin愣是没启动起来。后来经过多次尝试和查阅官方文档,我才找到了正确的配置方法。 一个常见的问题是,如何设置Kylin的存储位置。默认情况下,Kylin会将元数据存储在HBase中。不过,如果你想把元数据存在本地的文件系统里,只需要调整一下kylin.metadata.storage这个参数就行啦。这可以显著提高开发阶段的效率,但在生产环境中并不推荐这样做。 properties 设置Kylin元数据存储为本地文件系统 kylin.metadata.storage=fs:/path/to/local/directory 另一个重要的配置是Kylin的Cube构建策略。Cube是Kylin的核心概念之一,它用于加速查询响应时间。不同的Cube构建策略会影响查询性能和存储空间的占用。我曾经因为选择了错误的构建策略而导致Cube构建速度极慢。后来,通过调整kylin.cube.algorithm参数,我成功地优化了Cube构建过程。 properties 设置Cube构建策略为INMEM kylin.cube.algorithm=INMEM 4. Kylin部署与监控 最后,我们来谈谈Kylin的部署与监控。Kylin提供了多种部署方式,包括单节点部署、集群部署等。对于初学者来说,单节点部署可能更易于理解和操作。但是,随着数据量的增长,单节点部署很快就会达到瓶颈。这时,就需要考虑集群部署方案。 在部署过程中,我遇到的一个主要问题是服务之间的依赖关系。Kylin依赖于Hadoop和HBase,如果这些服务没有正确配置,Kylin将无法启动。要搞定这个问题,就得细细排查每个服务的状况,确保它们都乖乖地在运转着。 bash 检查Hadoop服务状态 sudo systemctl status hadoop-hdfs-namenode 部署完成后,监控Kylin的运行状态变得非常重要。Kylin提供了Web界面和日志文件两种方式来进行监控。你可以直接在网页上看到Kylin的各种数据指标,就像看仪表盘一样。至于Kylin的操作记录嘛,就都记在日志文件里头了。我经常使用日志文件来排查问题,因为它能提供更多的上下文信息。 bash 查看Kylin日志文件 tail -f /opt/kylin/logs/kylin.log 结语 通过这次分享,我希望能让大家对Kylin的配置与部署有一个更全面的理解。尽管在过程中会碰到各种难题,但只要咱们保持耐心,不断学习和探索,肯定能找到解决的办法。Kylin 的厉害之处就在于它超级灵活,还能随意扩展,这正是我们在大数据分析里头求之不得的呢。希望你们在使用Kylin的过程中也能感受到这份乐趣! --- 希望这篇技术文章对你有所帮助!如果你有任何疑问或需要进一步的帮助,请随时联系我。
2024-12-31 16:02:29
28
诗和远方
Dubbo
...巴巴开源的一个高性能Java RPC框架,一直备受青睐。不过嘛,在实际用起来的时候,服务一多啊,咱们就难免要跟分布式追踪系统打交道,各种问题接踵而至。这篇文章主要是想聊聊Dubbo怎么和Zipkin、Jaeger这些分布式追踪系统打交道,以及怎么优化它们的合作。我们会用一些真实的例子来说明,怎样才能更好地应对分布式追踪中遇到的各种问题。 1. 分布式追踪系统的重要性 首先,让我们来谈谈为什么需要分布式追踪系统。想想看,当你得照顾一大堆微服务组成的复杂系统时,每个请求都像是个大冒险,得穿梭在好几个服务之间打交道。在这种情况下,要准确地定位问题所在变得极其困难。而分布式追踪系统就像一双眼睛,能够帮助我们清晰地看到每一次请求的完整路径,包括它经过了哪些服务、耗时多少、是否有错误发生等关键信息。这对于提升系统性能、快速定位故障以及优化用户体验都至关重要。 2. Dubbo集成分布式追踪系统的初步探索 Dubbo本身并不直接支持分布式追踪功能,但可以通过集成第三方工具来实现这一目标。比如说Zipkin吧,这是Twitter推出的一个开源工具,专门用来追踪应用程序在分布式环境中的各种请求路径和数据流动情况。用它就像是给你的系统搭建了一个超级详细的导航地图,让你能一眼看清楚每个请求走过了哪些地方。接下来,我们将通过几个步骤来演示如何在Dubbo项目中集成Zipkin。 2.1 添加依赖 首先,我们需要向项目的pom.xml文件中添加Zipkin客户端的依赖。这步超级重要,因为得靠它让我们的Dubbo服务乖乖地把追踪信息发给Zipkin服务器,不然出了问题我们可找不到北啊。 xml io.zipkin.java zipkin-reporter-brave 2.7.5 2.2 配置Dubbo服务端 然后,在Dubbo服务端配置文件(如application.properties)中加入必要的配置项,让其知道如何连接到Zipkin服务器。 properties dubbo.application.qos-enable=false dubbo.registry.address=multicast://224.5.6.7:1234 指定Zipkin服务器地址 spring.zipkin.base-url=http://localhost:9411/ 使用Brave作为追踪库 brave.sampler.probability=1.0 这里,spring.zipkin.base-url指定了Zipkin服务器的URL,而brave.sampler.probability=1.0则表示所有请求都会被追踪。 2.3 编写服务接口与实现 假设我们有一个简单的服务接口,用于处理用户订单: java public interface OrderService { String placeOrder(String userId); } 服务实现类如下: java @Service("orderService") public class OrderServiceImpl implements OrderService { @Override public String placeOrder(String userId) { // 模拟业务逻辑 System.out.println("Order placed for user: " + userId); return "Your order has been successfully placed!"; } } 2.4 启动服务并测试 完成上述配置后,启动Dubbo服务端。你可以试试调用placeOrder这个方法,然后看看在Zipkin的界面上有没有出现相应的追踪记录。 3. 深入探讨 从Dubbo到Jaeger的转变 虽然Zipkin是一个优秀的解决方案,但在某些场景下,你可能会发现它无法满足你的需求。例如,如果你需要更高级别的数据采样策略或是对追踪数据有更高的控制权。这时,Jaeger就成为一个不错的选择。Jaeger是Uber开源的分布式追踪系统,它提供了更多的定制选项和更好的性能表现。 将Dubbo与Jaeger集成的过程与Zipkin类似,主要区别在于依赖库的选择和一些配置细节。这里就不详细展开,但你可以按照类似的思路去尝试。 4. 结语 持续优化与未来展望 集成分布式追踪系统无疑为我们的Dubbo服务增添了一双“慧眼”,使我们能够在复杂多变的分布式环境中更加从容不迫。然而,这只是一个开始。随着技术日新月异,咱们得不停地充电,学些新工具新技能,才能跟上这变化的脚步嘛。别忘了时不时地检查和调整你的追踪方法,确保它们跟得上你生意的发展步伐。 希望这篇文章能为你提供一些有价值的启示,让你在Dubbo与分布式追踪系统的世界里游刃有余。记住,每一次挑战都是成长的机会,勇敢地迎接它们吧!
2024-11-16 16:11:57
54
山涧溪流
转载文章
...ionimport java.util.Propertiesobject DataHiveToMySQL {def main(args: Array[String]): Unit = {val sparkConf = new SparkConf().setMaster("local[]").setAppName("sparkSQL")val spark = SparkSession.builder().enableHiveSupport().config(sparkConf).getOrCreate()val result=spark.sql("select from ods.nationeverymonths")val props=new Properties()props.setProperty("user","root")props.setProperty("password","123456")props.setProperty("driver","com.mysql.jdbc.Driver")result.write.mode("overwrite").jdbc("jdbc:mysql://192.168.230.132:3306/user?serverTimezone=UTC&characterEncoding=UTF-8&useSSL=false", "nationeverymonth", props)println("导入成功")spark.stop()} } 运行可见导入成功 进入MySQL中查看结果 可见数据成功导入 接下来按照要求查询: 2.请根据dwd层表计算出某年每个国家的平均消费额和所有国家平均消费额相比较结果(“高/低/相同”),存入MySQL数据库shtd_store的nationavgcmp表(表结构如下)中,然后在Linux的MySQL命令行中根据订单总数、消费总额、国家表主键三列均逆序排序的方式,查询出前5条,将SQL语句与执行结果截图粘贴至对应报告中; 在解这道题的时候遇见一个问题,在求所有国家平均消费额的时候一直报错,由于没有数据这道题的题意还是有点没看明白,于是我就用了最简单的办法先新增一列,再单独将所有国家平均消费额求出来然后再插入,如果各位大佬有解决这个问题的办法希望能指导一下 先将每个国家的平均消费额求出来 spark.sql("select nationkey,nationname,avg(totalconsumption) as nationavgconsumption from nationeverymonths group by nationkey,nationname") 再新增一列所有国家平均消费额 spark.sql("alter table nationeverymonths add columns(avg_allstring)") 再将查询到的所有国家平均消费额导入进去 spark.sql("insert overwrite table nationeverymonths1 select nationkey,nationname,avg_totalconsumpt,1500 from nationeverymonths1") 再次查表 按照题意添加比较结果字段 spark.sql("select ,case when avg_totalconsumpt>avg_all then '高' when avg_totalconsumpt<avg_all then '低' when avg_totalconsumpt=avg_all then '相同' else 'null' end as comparison from nationeverymonths1").show 最后的排序语句和题一一样 本篇文章为转载内容。原文链接:https://blog.csdn.net/guo_0423/article/details/126352162。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-01 10:55:33
319
转载
Spark
...尤其是大规模数据处理项目中,如使用Apache Spark构建的分布式计算框架,日志记录成为了不可或缺的一部分。哎呀,这些家伙可真是帮了大忙了!它们就像是你编程时的私人侦探,随时盯着你的代码,一有风吹草动就给你报信。特别是当你遇上疑难杂症,它们能迅速揪出问题所在,就像医生找病因一样专业。有了它们,找bug、修bug的过程变得快捷又高效,简直就像开了挂一样爽快!哎呀,咱们这篇文章啊,就是要好好聊聊在Spark这个超级棒的大数据处理工具里,咱们可能会遇到的各种小麻烦,还有呢,怎么用那些日志记录来帮咱们找到问题的根儿。你想象一下,就像你在厨房里做饭,突然发现菜炒糊了,这时候你就会看看锅底,找找是火开太大了还是调料放多了,对吧?这文章呢,就是想教你用同样的方法,在大数据的世界里,通过查看日志,找出你的Spark程序哪里出了问题,然后迅速解决它,让一切恢复正常。是不是听起来既实用又有趣?咱们这就开始吧! 二、Spark错误类型概述 Spark应用程序可能遭遇多种错误类型,从内存溢出、任务失败到网络通信异常等。这些错误通常由日志系统捕获并记录下来,为后续分析提供依据。下面,我们将通过几个具体的错误示例来了解如何阅读和解析Spark日志文件。 三、实例代码 简单的Spark Word Count应用 首先,让我们构建一个简单的Spark Word Count应用作为起点。这个应用旨在统计文本文件中单词的频率。 scala import org.apache.spark.SparkConf import org.apache.spark.SparkContext object WordCount { def main(args: Array[String]) { val conf = new SparkConf().setAppName("Word Count").setMaster("local") val sc = new SparkContext(conf) val textFile = sc.textFile("file:///path/to/your/textfile.txt") val counts = textFile.flatMap(line => line.split(" ")) .map(word => (word, 1)) .reduceByKey(_ + _) counts.saveAsTextFile("output") sc.stop() } } 四、错误日志分析 内存溢出问题 在实际运行上述应用时,如果输入文本文件过大,可能会导致内存溢出错误。日志文件中可能会出现类似以下的信息: org.apache.spark.SparkException: Job aborted due to stage failure: Task 0 in stage 37.0 failed 1 times, most recent failure: Lost task 0.3 in stage 37.0 (TID 208, localhost): java.lang.OutOfMemoryError: Java heap space 这段日志信息清晰地指出错误原因(OutOfMemoryError: Java heap space),并提供了关键细节,包括任务编号、所在节点以及错误类型。针对这一问题,可以通过增加Spark集群的内存资源或者优化数据处理逻辑来解决。 五、调试策略与最佳实践 1. 使用日志级别 调整日志级别(如INFO、DEBUG)可以帮助开发者在日志中获取更多详细信息。 2. 定期检查日志 通过自动化工具定期检查日志文件,可以及时发现潜在问题。 3. 利用Spark UI Spark自带的Web UI提供了详细的作业监控界面,直观显示任务状态和性能指标。 4. 错误重试机制 合理配置Spark任务的重试策略,避免因一次失败而影响整体进程。 5. 性能监控工具 集成性能监控工具(如Prometheus、Grafana)有助于实时监控系统性能,预防内存泄漏等严重问题。 六、总结与展望 日志记录是Spark应用程序开发和维护过程中的关键环节。哎呀,你知道吗?程序员们在遇到bug(小错误)的时候,那可是得使出浑身解数了!他们可不是对着电脑屏幕发呆,而是会仔细地分析问题,就像侦探破案一样。找到问题的源头后,他们就开始了他们的“调试大作战”,就像是医生给病人开药一样精准。通过这些努力,他们能优化代码,让程序跑得更顺畅,就像给汽车加了润滑剂,不仅跑得快,还稳当当的。这样,我们的应用就能更加可靠,用户用起来也更舒心啦!哎呀,你懂的,随着咱们每天产生的数据就像自来水一样哗哗流,那处理这些数据的大数据工具就得越来越厉害才行。特别是那些记录我们操作痕迹的日志管理系统,不仅要快得跟闪电一样,操作起来还得像玩手机游戏一样简单,最好还能自己动脑筋分析出点啥有价值的信息来。这样,未来日志记录这事儿就不仅仅是记录,还能帮我们找到问题、优化流程,简直就是一大神器嘛!所以,你看,这发展方向就是越来越智能、好用、高效,让科技真正服务于人,而不是让人被科技牵着鼻子走。 --- 通过本文的探讨,我们不仅学习了如何理解和利用Spark的日志信息来诊断问题,还了解了一些实用的调试技巧和最佳实践。希望这些内容能帮助你更有效地管理你的Spark应用程序,确保其在复杂的数据处理场景下稳定运行。
2024-09-07 16:03:18
141
秋水共长天一色
Kafka
...方便又清晰! java // 创建一个Kafka主题 kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic user_events 这里的关键点在于,主题的名字要尽量简单明了,避免使用特殊字符或者空格。哎呀,这就好比你给文件夹起个特别绕口的名字,结果自己都记不住路径了,Kafka也是一样!它会根据主题的名字创建对应的文件夹结构,但要是主题名太复杂,搞不好就会在找东西的时候迷路,路径解析起来就容易出岔子啦。而且啊,主题的名字最好起得通俗易懂一点,让大伙儿一眼扫过去就明白这是干啥用的。 2.2 分区(Partition):主题的分身术 接着说分区(Partition)。每个主题都可以被划分为多个分区,每个分区就是一个日志文件。分区的作用是什么呢?它可以提高并发性和扩展性。比如说,你有个主题叫orders(订单),你可以把它分成5个区(分区)。这样一来,不同的小伙伴就能一起开工,各自处理这些区里的数据啦! java // 查看主题的分区信息 kafka-topics.sh --describe --zookeeper localhost:2181 --topic orders 分区的数量决定了并发的上限。所以,在设计主题时,你需要仔细权衡分区数量。太多的话,管理起来麻烦;太少的话,可能无法充分利用资源。我一般会根据预计的消息量来决定分区的数量。比如说,如果一秒能收到几千条消息,那分区设成10到20个就挺合适的。毕竟分区太多太少了都不好,得根据实际情况来调,不然可能会卡壳或者资源浪费啊! 2.3 消费者组(Consumer Group):团队协作的秘密武器 最后,我们来说消费者组(Consumer Group)。消费者组是一组消费者的集合,它们共同消费同一个主题的消息。每个消费者组都有一个唯一的名称,这个名字同样非常重要。 java // 创建一个消费者组 kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic user_events --group my_consumer_group 消费者组的设计理念是为了实现负载均衡和故障恢复。比如说,如果有两个小伙伴在一个小组里,系统就会帮他们自动分配任务(也就是主题的分区),这样大家就不会抢来抢去,重复干同样的活儿啦!而且呢,要是有个消费者挂掉了或者出问题了,其他的消费者就会顶上来,接手它负责的那些分区,接着干活儿,完全不受影响。 --- 3. 组织结构 Kafka的大脑与四肢 3.1 集群(Cluster):Kafka的心脏 Kafka集群是由多个Broker组成的,Broker是Kafka的核心组件,负责存储和转发消息。一个Broker就是一个节点,多个Broker协同工作,形成一个分布式的系统。 java // 启动Kafka Broker nohup kafka-server-start.sh config/server.properties & Broker的数量决定了系统的容错能力和性能。其实啊,通常咱们都会建议弄三个Broker,为啥呢?就怕万一有个家伙“罢工”了,比如突然挂掉或者出问题,别的还能顶上,整个系统就不耽误干活啦!不过,Broker的数量也不能太多,否则会增加管理和维护的成本。 3.2 Zookeeper:Kafka的大脑 Zookeeper是Kafka的协调器,它负责管理集群的状态和配置。没有Zookeeper,Kafka就无法正常运作。比如说啊,新添了个Broker(也就是那个消息中转站),Zookeeper就会赶紧告诉其他Broker:“嘿,快看看这位新伙伴,更新一下你们的状态吧!”还有呢,要是某个分区的老大换了(Leader切换了),Zookeeper也会在一旁默默记好这笔账,生怕漏掉啥重要信息似的。 java // 启动Zookeeper nohup zookeeper-server-start.sh config/zookeeper.properties & 虽然Zookeeper很重要,但它也有一定的局限性。比如,它可能会成为单点故障,影响整个系统的稳定性。因此,近年来Kafka也在尝试去掉对Zookeeper的依赖,开发了自己的内部协调机制。 3.3 日志(Log):Kafka的四肢 日志是Kafka存储消息的地方,每个分区对应一个日志文件。嘿,这个日志设计可太聪明了!它用的是顺序写入的方法,就像一条直线往前跑,根本不用左顾右盼,写起来那叫一个快,效率直接拉满! java // 查看日志路径 cat config/server.properties | grep log.dirs 日志的大小可以通过参数log.segment.bytes来控制。默认值是1GB,你可以根据实际情况调整。要是日志文件太大了,查个东西就像在大海捞针一样慢吞吞的;但要是弄得太小吧,又老得换新的日志文件,麻烦得很,还费劲。 --- 4. 实战演练 从零搭建一个Kafka环境 说了这么多理论,咱们来实际操作一下吧!假设我们要搭建一个简单的Kafka环境,用来收集用户的登录日志。 4.1 安装Kafka和Zookeeper 首先,我们需要安装Kafka和Zookeeper。可以从官网下载最新的二进制包,解压后按照文档配置即可。 bash 下载Kafka wget https://downloads.apache.org/kafka/3.4.0/kafka_2.13-3.4.0.tgz 解压 tar -xzf kafka_2.13-3.4.0.tgz 4.2 创建主题和消费者 接下来,我们创建一个名为login_logs的主题,并启动一个消费者来监听消息。 bash 创建主题 bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic login_logs 启动消费者 bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic login_logs --from-beginning 4.3 生产消息 最后,我们可以编写一个简单的Java程序来生产消息。 java import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import java.util.Properties; public class KafkaProducerExample { public static void main(String[] args) { Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer<>(props); for (int i = 0; i < 10; i++) { producer.send(new ProducerRecord<>("login_logs", "key" + i, "value" + i)); } producer.close(); } } 这段代码会向login_logs主题发送10条消息,每条消息都有一个唯一的键和值。 --- 5. 总结 Kafka的魅力在于细节 好了,到这里咱们的Kafka之旅就告一段落了。通过这篇文章,我希望大家能更好地理解Kafka的命名规范和组织结构。Kafka为啥这么牛?因为它在设计的时候真是把每个小细节都琢磨得特别透。就像给主题起名字吧,分个区啦,还有消费者组怎么配合干活儿,这些地方都能看出人家确实是下了一番功夫的,真不是随便凑合出来的! 当然,Kafka的学习之路还有很多内容需要探索,比如监控、调优、安全等等。其实我觉得啊,只要你把命名的规矩弄明白了,东西该怎么放也心里有数了,那你就算是走上正轨啦,成功嘛,它就已经在向你招手啦!加油吧,朋友们! --- 希望这篇文章对你有所帮助,如果有任何疑问,欢迎随时交流哦!
2025-04-05 15:38:52
95
彩虹之上
转载文章
...个客户问我,为啥你们项目都搞了好几年了,为啥线上还会经常反馈卡顿,呃呃呃。。 于是根据自己的理解以及网上大佬们的思路总结了一篇关于卡顿优化这块的文章。 卡顿问题是一个老生常谈的话题了,一个App的好坏,卡顿也许会占一半,它直接决定了用户的留存问题,各大app排行版上,那些知名度较高,但是排行较低的,可能就要思考思考是不是和你app本身有关系了。 卡顿一直是性能优化中相对重要的一个点,因为其涉及了UI绘制、垃圾回收(GC)、线程调度以及Binder,CPU,GPU方面等JVM以及FrameWork相关知识 如果能做好卡顿优化,那么也就间接证明你对Android FrameWork的理解之深。 接下来我们就来讲解下卡顿方面的知识。 什么是卡顿: 对用户来讲就是界面不流畅,滞顿。 场景如下: 1.视频加载慢,画面卡顿,卡死,黑屏 2.声音卡顿,音画不同步。 3.动画帧卡顿,交互响应慢 4.滑动不跟手,列表自动更新,滚动不流畅 5.网络响应慢,数据和画面展示慢、 6.过渡动画生硬。 7.界面不可交互,卡死,等等现象。 卡顿是如何发生的 卡顿产生的原因一般都比较复杂,如CPU内存大小,IO操作,锁操作,低效的算法等都会引起卡顿。 站在开发的角度看: 通常我们讲,屏幕刷新率是60fps,需要在16ms内完成所有的工作才不会造成卡顿。 为什么是16ms,不是17,18呢? 下面我们先来理清在UI绘制中的几个概念: SurfaceFlinger: SurfaceFlinger作用是接受多个来源的图形显示数据Surface,合成后发送到显示设备,比如我们的主界面中:可能会有statusBar,侧滑菜单,主界面,这些View都是独立Surface渲染和更新,最后提交给SF后,SF根据Zorder,透明度,大小,位置等参数,合成为一个数据buffer,传递HWComposer或者OpenGL处理,最终给显示器。 在显示过程中使用到了bufferqueue,surfaceflinger作为consumer方,比如windowmanager管理的surface作为生产方产生页面,交由surfaceflinger进行合成。 VSYNC Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,VSYNC是一种在PC上很早就有应用,可以理解为一种定时中断技术。 tearing 问题: 早期的 Android 是没有 vsync 机制的,CPU 和 GPU 的配合也比较混乱,这也造成著名的 tearing 问题,即 CPU/GPU 直接更新正在显示的屏幕 buffer 造成画面撕裂。 后续 Android 引入了双缓冲机制,但是 buffer 的切换也需要一个比较合适的时机,也就是屏幕扫描完上一帧后的时机,这也就是引入 vsync 的原因。 早先一般的屏幕刷新率是 60fps,所以每个 vsync 信号的间隔也是 16ms,不过随着技术的更迭以及厂商对于流畅性的追求,越来越多 90fps 和 120fps 的手机面世,相对应的间隔也就变成了 11ms 和 8ms。 VSYNC信号种类: 1.屏幕产生的硬件VSYNC:硬件VSYNC是一种脉冲信号,起到开关和触发某种操作的作用。 2.由SurfaceFlinger将其转成的软件VSYNC信号,经由Binder传递给Choreographer Choreographer: 编舞者,用于注册VSYNC信号并接收VSYNC信号回调,当内部接收到这个信号时最终会调用到doFrame进行帧的绘制操作。 Choreographer在系统中流程: 如何通过Choreographer计算掉帧情况:原理就是: 通过给Choreographer设置FrameCallback,在每次绘制前后看时间差是16.6ms的多少倍,即为前后掉帧率。 使用方式如下: //Application.javapublic void onCreate() {super.onCreate();//在Application中使用postFrameCallbackChoreographer.getInstance().postFrameCallback(new FPSFrameCallback(System.nanoTime()));}public class FPSFrameCallback implements Choreographer.FrameCallback {private static final String TAG = "FPS_TEST";private long mLastFrameTimeNanos = 0;private long mFrameIntervalNanos;public FPSFrameCallback(long lastFrameTimeNanos) {mLastFrameTimeNanos = lastFrameTimeNanos;mFrameIntervalNanos = (long)(1000000000 / 60.0);}@Overridepublic void doFrame(long frameTimeNanos) {//初始化时间if (mLastFrameTimeNanos == 0) {mLastFrameTimeNanos = frameTimeNanos;}final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;if (jitterNanos >= mFrameIntervalNanos) {final long skippedFrames = jitterNanos / mFrameIntervalNanos;if(skippedFrames>30){//丢帧30以上打印日志Log.i(TAG, "Skipped " + skippedFrames + " frames! "+ "The application may be doing too much work on its main thread.");} }mLastFrameTimeNanos=frameTimeNanos;//注册下一帧回调Choreographer.getInstance().postFrameCallback(this);} } UI绘制全路径分析: 有了前面几个概念,这里我们让SurfaceFlinger结合View的绘制流程用一张图来表达整个绘制流程: 生产者:APP方构建Surface的过程。 消费者:SurfaceFlinger UI绘制全路径分析卡顿原因: 接下来,我们逐个分析,看看都会有哪些原因可能造成卡顿: 1.渲染流程 1.Vsync 调度:这个是起始点,但是调度的过程会经过线程切换以及一些委派的逻辑,有可能造成卡顿,但是一般可能性比较小,我们也基本无法介入; 2.消息调度:主要是 doframe Message 的调度,这就是一个普通的 Handler 调度,如果这个调度被其他的 Message 阻塞产生了时延,会直接导致后续的所有流程不会被触发 3.input 处理:input 是一次 Vsync 调度最先执行的逻辑,主要处理 input 事件。如果有大量的事件堆积或者在事件分发逻辑中加入大量耗时业务逻辑,会造成当前帧的时长被拉大,造成卡顿,可以尝试通过事件采样的方案,减少 event 的处理 4.动画处理:主要是 animator 动画的更新,同理,动画数量过多,或者动画的更新中有比较耗时的逻辑,也会造成当前帧的渲染卡顿。对动画的降帧和降复杂度其实解决的就是这个问题; 5.view 处理:主要是接下来的三大流程,过度绘制、频繁刷新、复杂的视图效果都是此处造成卡顿的主要原因。比如我们平时所说的降低页面层级,主要解决的就是这个问题; 6.measure/layout/draw:view 渲染的三大流程,因为涉及到遍历和高频执行,所以这里涉及到的耗时问题均会被放大,比如我们会降不能在 draw 里面调用耗时函数,不能 new 对象等等; 7.DisplayList 的更新:这里主要是 canvas 和 displaylist 的映射,一般不会存在卡顿问题,反而可能存在映射失败导致的显示问题; 8.OpenGL 指令转换:这里主要是将 canvas 的命令转换为 OpenGL 的指令,一般不存在问题 9.buffer 交换:这里主要指 OpenGL 指令集交换给 GPU,这个一般和指令的复杂度有关 10.GPU 处理:顾名思义,这里是 GPU 对数据的处理,耗时主要和任务量和纹理复杂度有关。这也就是我们降低 GPU 负载有助于降低卡顿的原因; 11.layer 合成:Android P 修改了 Layer 的计算方法 , 把这部分放到了 SurfaceFlinger 主线程去执行, 如果后台 Layer 过多, 就会导致 SurfaceFlinger 在执行 rebuildLayerStacks 的时候耗时 , 导致 SurfaceFlinger 主线程执行时间过长。 可以选择降低Surface层级来优化卡顿。 12.光栅化/Display:这里暂时忽略,底层系统行为; Buffer 切换:主要是屏幕的显示,这里 buffer 的数量也会影响帧的整体延迟,不过是系统行为,不能干预。 2.系统负载 内存:内存的吃紧会直接导致 GC 的增加甚至 ANR,是造成卡顿的一个不可忽视的因素; CPU:CPU 对卡顿的影响主要在于线程调度慢、任务执行的慢和资源竞争,比如 1.降频会直接导致应用卡顿; 2.后台活动进程太多导致系统繁忙,cpu \ io \ memory 等资源都会被占用, 这时候很容易出现卡顿问题 ,这种情况比较常见,可以使用dumpsys cpuinfo查看当前设备的cpu使用情况: 3.主线程调度不到 , 处于 Runnable 状态,这种情况比较少见 4.System 锁:system_server 的 AMS 锁和 WMS 锁 , 在系统异常的情况下 , 会变得非常严重 , 如下图所示 , 许多系统的关键任务都被阻塞 , 等待锁的释放 , 这时候如果有 App 发来的 Binder 请求带锁 , 那么也会进入等待状态 , 这时候 App 就会产生性能问题 ; 如果此时做 Window 动画 , 那么 system_server 的这些锁也会导致窗口动画卡顿 GPU:GPU 的影响见渲染流程,但是其实还会间接影响到功耗和发热; 功耗/发热:功耗和发热一般是不分家的,高功耗会引起高发热,进而会引起系统保护,比如降频、热缓解等,间接的导致卡顿。 如何监控卡顿 线下监控: 我们知道卡顿问题的原因错综复杂,但最终都可以反馈到CPU使用率上来 1.使用dumpsys cpuinfo命令 这个命令可以获取当时设备cpu使用情况,我们可以在线下通过重度使用应用来检测可能存在的卡顿点 A8S:/ $ dumpsys cpuinfoLoad: 1.12 / 1.12 / 1.09CPU usage from 484321ms to 184247ms ago (2022-11-02 14:48:30.793 to 2022-11-02 14:53:30.866):2% 1053/scanserver: 0.2% user + 1.7% kernel0.6% 934/system_server: 0.4% user + 0.1% kernel / faults: 563 minor0.4% 564/signserver: 0% user + 0.4% kernel0.2% 256/ueventd: 0.1% user + 0% kernel / faults: 320 minor0.2% 474/surfaceflinger: 0.1% user + 0.1% kernel0.1% 576/vendor.sprd.hardware.gnss@2.0-service: 0.1% user + 0% kernel / faults: 54 minor0.1% 286/logd: 0% user + 0% kernel / faults: 10 minor0.1% 2821/com.allinpay.appstore: 0.1% user + 0% kernel / faults: 1312 minor0.1% 447/android.hardware.health@2.0-service: 0% user + 0% kernel / faults: 1175 minor0% 1855/com.smartpos.dataacqservice: 0% user + 0% kernel / faults: 755 minor0% 2875/com.allinpay.appstore:pushcore: 0% user + 0% kernel / faults: 744 minor0% 1191/com.android.systemui: 0% user + 0% kernel / faults: 70 minor0% 1774/com.android.nfc: 0% user + 0% kernel0% 172/kworker/1:2: 0% user + 0% kernel0% 145/irq/24-70900000: 0% user + 0% kernel0% 575/thermald: 0% user + 0% kernel / faults: 300 minor... 2.CPU Profiler 这个工具是AS自带的CPU性能检测工具,可以在PC上实时查看我们CPU使用情况。 AS提供了四种Profiling Model配置: 1.Sample Java Methods:在应用程序基于Java的代码执行过程中,频繁捕获应用程序的调用堆栈 获取有关应用程序基于Java的代码执行的时间和资源使用情况信息。 2.Trace java methods:在运行时对应用程序进行检测,以在每个方法调用的开始和结束时记录时间戳。收集时间戳并进行比较以生成方法跟踪数据,包括时序信息和CPU使用率。 请注意与检测每种方法相关的开销会影响运行时性能,并可能影响性能分析数据。对于生命周期相对较短的方法,这一点甚至更为明显。此外,如果您的应用在短时间内执行大量方法,则探查器可能会很快超过其文件大小限制,并且可能无法记录任何进一步的跟踪数据。 3.Sample C/C++ Functions:捕获应用程序本机线程的示例跟踪。要使用此配置,您必须将应用程序部署到运行Android 8.0(API级别26)或更高版本的设备。 4.Trace System Calls:捕获细粒度的详细信息,使您可以检查应用程序与系统资源的交互方式 您可以检查线程状态的确切时间和持续时间,可视化CPU瓶颈在所有内核中的位置,并添加自定义跟踪事件进行分析。在对性能问题进行故障排除时,此类信息可能至关重要。要使用此配置,您必须将应用程序部署到运行Android 7.0(API级别24)或更高版本的设备。 使用方式: Debug.startMethodTracing("");// 需要检测的代码片段...Debug.stopMethodTracing(); 优点:有比较全面的调用栈以及图像化方法时间显示,包含所有线程的情况 缺点:本身也会带来一点的性能开销,可能会带偏优化方向 火焰图:可以显示当前应用的方法堆栈: 3.Systrace Systrace在前面一篇分析启动优化的文章讲解过 这里我们简单来复习下: Systrace用来记录当前应用的系统以及应用(使用Trace类打点)的各阶段耗时信息包括绘制信息以及CPU信息等。 使用方式: Trace.beginSection("MyApp.onCreate_1");alt(200);Trace.endSection(); 在命令行中: python systrace.py -t 5 sched gfx view wm am app webview -a "com.chinaebipay.thirdcall" -o D:\trac1.html 记录的方法以及CPU中的耗时情况: 优点: 1.轻量级,开销小,CPU使用率可以直观反映 2.右侧的Alerts能够根据我们应用的问题给出具体的建议,比如说,它会告诉我们App界面的绘制比较慢或者GC比较频繁。 4.StrictModel StrictModel是Android提供的一种运行时检测机制,用来帮助开发者自动检测代码中不规范的地方。 主要和两部分相关: 1.线程相关 2.虚拟机相关 基础代码: private void initStrictMode() {// 1、设置Debug标志位,仅仅在线下环境才使用StrictModeif (DEV_MODE) {// 2、设置线程策略StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectCustomSlowCalls() //API等级11,使用StrictMode.noteSlowCode.detectDiskReads().detectDiskWrites().detectNetwork() // or .detectAll() for all detectable problems.penaltyLog() //在Logcat 中打印违规异常信息// .penaltyDialog() //也可以直接跳出警报dialog// .penaltyDeath() //或者直接崩溃.build());// 3、设置虚拟机策略StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects()// 给NewsItem对象的实例数量限制为1.setClassInstanceLimit(NewsItem.class, 1).detectLeakedClosableObjects() //API等级11.penaltyLog().build());} } 线上监控: 线上需要自动化的卡顿检测方案来定位卡顿,它能记录卡顿发生时的场景。 自动化监控原理: 采用拦截消息调度流程,在消息执行前埋点计时,当耗时超过阈值时,则认为是一次卡顿,会进行堆栈抓取和上报工作 首先,我们看下Looper用于执行消息循环的loop()方法,关键代码如下所示: / Run the message queue in this thread. Be sure to call {@link quit()} to end the loop./public static void loop() {...for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {// 1logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}...try {// 2 msg.target.dispatchMessage(msg);dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);} }...if (logging != null) {// 3logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);} 在Looper的loop()方法中,在其执行每一个消息(注释2处)的前后都由logging进行了一次打印输出。可以看到,在执行消息前是输出的">>>>> Dispatching to “,在执行消息后是输出的”<<<<< Finished to ",它们打印的日志是不一样的,我们就可以由此来判断消息执行的前后时间点。 具体的实现可以归纳为如下步骤: 1、首先,我们需要使用Looper.getMainLooper().setMessageLogging()去设置我们自己的Printer实现类去打印输出logging。这样,在每个message执行的之前和之后都会调用我们设置的这个Printer实现类。 2、如果我们匹配到">>>>> Dispatching to "之后,我们就可以执行一行代码:也就是在指定的时间阈值之后,我们在子线程去执行一个任务,这个任务就是去获取当前主线程的堆栈信息以及当前的一些场景信息,比如:内存大小、电脑、网络状态等。 3、如果在指定的阈值之内匹配到了"<<<<< Finished to ",那么说明message就被执行完成了,则表明此时没有产生我们认为的卡顿效果,那我们就可以将这个子线程任务取消掉。 这里我们使用blockcanary来做测试: BlockCanary APM是一个非侵入式的性能监控组件,可以通过通知的形式弹出卡顿信息。它的原理就是我们刚刚讲述到的卡顿监控的实现原理。 使用方式: 1.导入依赖 implementation 'com.github.markzhai:blockcanary-android:1.5.0' Application的onCreate方法中开启卡顿监控 // 注意在主进程初始化调用BlockCanary.install(this, new AppBlockCanaryContext()).start(); 3.继承BlockCanaryContext类去实现自己的监控配置上下文类 public class AppBlockCanaryContext extends BlockCanaryContext {....../ 指定判定为卡顿的阈值threshold (in millis), 你可以根据不同设备的性能去指定不同的阈值 @return threshold in mills/public int provideBlockThreshold() {return 1000;}....} 4.在Activity的onCreate方法中执行一个耗时操作 try {Thread.sleep(4000);} catch (InterruptedException e) {e.printStackTrace();} 5.结果: 可以看到一个和LeakCanary一样效果的阻塞可视化堆栈图 那有了BlockCanary的方法耗时监控方式是不是就可以解百愁了呢,呵呵。有那么容易就好了 根据原理:我们拿到的是msg执行前后的时间和堆栈信息,如果msg中有几百上千个方法,就无法确认到底是哪个方法导致的耗时,也有可能是多个方法堆积导致。 这就导致我们无法准确定位哪个方法是最耗时的。如图中:堆栈信息是T2的,而发生耗时的方法可能是T1到T2中任何一个方法甚至是堆积导致。 那如何优化这块? 这里我们采用字节跳动给我们提供的一个方案:基于 Sliver trace 的卡顿监控体系 Sliver trace 整体流程图: 主要包含两个方面: 检测方案: 在监控卡顿时,首先需要打开 Sliver 的 trace 记录能力,Sliver 采样记录 trace 执行信息,对抓取到的堆栈进行 diff 聚合和缓存。 同时基于我们的需要设置相应的卡顿阈值,以 Message 的执行耗时为衡量。对主线程消息调度流程进行拦截,在消息开始分发执行时埋点,在消息执行结束时计算消息执行耗时,当消息执行耗时超过阈值,则认为产生了一次卡顿。 堆栈聚合策略: 当卡顿发生时,我们需要为此次卡顿准备数据,这部分工作是在端上子线程中完成的,主要是 dump trace 到文件以及过滤聚合要上报的堆栈。分为以下几步: 1.拿到缓存的主线程 trace 信息并 dump 到文件中。 2.然后从文件中读取 trace 信息,按照数据格式,从最近的方法栈向上追溯,找到当前 Message 包含的全部 trace 信息,并将当前 Message 的完整 trace 写入到待上传的 trace 文件中,删除其余 trace 信息。 3.遍历当前 Message trace,按照(Method 执行耗时 > Method 耗时阈值 & Method 耗时为该层堆栈中最耗时)为条件过滤出每一层函数调用堆栈的最长耗时函数,构成最后要上报的堆栈链路,这样特征堆栈中的每一步都是最耗时的,且最底层 Method 为最后的耗时大于阈值的 Method。 之后,将 trace 文件和堆栈一同上报,这样的特征堆栈提取策略保证了堆栈聚合的可靠性和准确性,保证了上报到平台后堆栈的正确合理聚合,同时提供了进一步分析问题的 trace 文件。 可以看到字节给的是一整套监控方案,和前面BlockCanary不同之处就在于,其是定时存储堆栈,缓存,然后使用diff去重的方式,并上传到服务器,可以最大限度的监控到可能发生比较耗时的方法。 开发中哪些习惯会影响卡顿的发生 1.布局太乱,层级太深。 1.1:通过减少冗余或者嵌套布局来降低视图层次结构。比如使用约束布局代替线性布局和相对布局。 1.2:用 ViewStub 替代在启动过程中不需要显示的 UI 控件。 1.3:使用自定义 View 替代复杂的 View 叠加。 2.主线程耗时操作 2.1:主线程中不要直接操作数据库,数据库的操作应该放在数据库线程中完成。 2.2:sharepreference尽量使用apply,少使用commit,可以使用MMKV框架来代替sharepreference。 2.3:网络请求回来的数据解析尽量放在子线程中,不要在主线程中进行复制的数据解析操作。 2.4:不要在activity的onResume和onCreate中进行耗时操作,比如大量的计算等。 2.5:不要在 draw 里面调用耗时函数,不能 new 对象 3.过度绘制 过度绘制是同一个像素点上被多次绘制,减少过度绘制一般减少布局背景叠加等方式,如下图所示右边是过度绘制的图片。 4.列表 RecyclerView使用优化,使用DiffUtil和notifyItemDataSetChanged进行局部更新等。 5.对象分配和回收优化 自从Android引入 ART 并且在Android 5.0上成为默认的运行时之后,对象分配和垃圾回收(GC)造成的卡顿已经显著降低了,但是由于对象分配和GC有额外的开销,它依然又可能使线程负载过重。 在一个调用不频繁的地方(比如按钮点击)分配对象是没有问题的,但如果在在一个被频繁调用的紧密的循环里,就需要避免对象分配来降低GC的压力。 减少小对象的频繁分配和回收操作。 好了,关于卡顿优化的问题就讲到这里,下篇文章会对卡顿中的ANR情况的处理,这里做个铺垫。 如果喜欢我的文章,欢迎关注我的公众号。 点击这看原文链接: 参考 Android卡顿检测及优化 一文读懂直播卡顿优化那些事儿 “终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解! 深入探索Android卡顿优化(上) 西瓜卡顿 & ANR 优化治理及监控体系建设 5376)] 参考 Android卡顿检测及优化 一文读懂直播卡顿优化那些事儿 “终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解! 深入探索Android卡顿优化(上) 西瓜卡顿 & ANR 优化治理及监控体系建设 本篇文章为转载内容。原文链接:https://blog.csdn.net/yuhaibing111/article/details/127682399。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-26 08:05:57
214
转载
转载文章
...tvirt-sfc JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk CLASSPATH=.:$JAVA_HOME/lib/tools.jar PATH=$JAVA_HOME/bin:$PATH JVM_OPTS="-Xms256m -XX:PermSize=256m -XX:MaxPermSize=512m" MAVEN_OPTS="$MAVEN_OPTS -Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m" export MAVEN_OPTS JAVA_HOME CLASSPATH JVM_OPTS PATH [root@localhost ~] netstat -ntpl Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0: LISTEN 3327/sshd tcp 0 0 127.0.0.1:25 0.0.0.0: LISTEN 3620/master tcp6 0 0 :::6633 ::: LISTEN 868/java tcp6 0 0 127.0.0.1:1099 ::: LISTEN 868/java tcp6 0 0 :::6640 ::: LISTEN 868/java tcp6 0 0 127.0.0.1:6644 ::: LISTEN 868/java tcp6 0 0 :::8181 ::: LISTEN 868/java tcp6 0 0 127.0.0.1:2550 ::: LISTEN 868/java tcp6 0 0 :::22 ::: LISTEN 3327/sshd tcp6 0 0 :::8185 ::: LISTEN 868/java tcp6 0 0 127.0.0.1:44601 ::: LISTEN 868/java tcp6 0 0 :::33273 ::: LISTEN 868/java tcp6 0 0 ::1:25 ::: LISTEN 3620/master tcp6 0 0 :::44444 ::: LISTEN 868/java tcp6 0 0 :::6653 ::: LISTEN 868/java tcp6 0 0 :::39169 ::: LISTEN 868/java tcp6 0 0 :::8101 ::: LISTEN 868/java tcp6 0 0 :::6886 ::: LISTEN 868/java openstack配置 openstack的networking-odl插件安装方式 https://docs.openstack.org/networking-odl/latest/install/installation.htmlodl-installation yum install python-networking-odl.noarch -y https://docs.openstack.org/networking-odl/latest/install/installation.htmlnetworking-odl-configuration systemctl restart neutron-server /etc/neutron/plugins/ml2 测试端口可连接性 curl -u admin:admin http://10.13.80.34:8181/controller/nb/v2/neutron/networks odl配置文件修改 etc/custom.properties ovsdb.l3.fwd.enabled=yes ovsdb.l3gateway.mac=0a:00:27:00:00:0d telnet 10.13.80.34 8181 netstat -nlp | grep 8181 telnet 127.0.0.1 8181 telnet 10.13.80.34 8181 systemctl status firewall iptables iptables -nvL iptables -F 清空iptables openstack server create --flavor tiny --image cirros --nic net-id=24449ee2-b84e-493f-8d76-139ac3e4f3cd --key-name mykey provider-instance nova service-list nova show ae5e26d1-c84d-40fa-bb27-f0b46d6a7061 查看虚机详情 ovs-vsctl set Open_vSwitch 89444614-3bf8-4d7a-b3a0-df5d20b48b7a other_config={'local_ip'='192.168.56.102'} ovs-vsctl set Open_vSwitch b084eccf-b92e-470c-8dff-8549e92c2104 other_config={'local_ip'='192.168.56.122'} ovs-vsctl list interface eth0 ovs-appctl fdb/show br-int [root@rcontroller01 ~] openstack security group rule list 2e19a748-9086-49f8-9498-01abc1a964fe 一个神奇的命令 +--------------------------------------+-------------+-----------+------------+--------------------------------------+ | ID | IP Protocol | IP Range | Port Range | Remote Security Group | +--------------------------------------+-------------+-----------+------------+--------------------------------------+ | 0184e6b3-4f7f-4fd5-8125-b80682e7ee48 | None | None | | 2e19a748-9086-49f8-9498-01abc1a964fe | | 1e0bfedc-8f25-408a-9328-708113bbbc52 | icmp | 0.0.0.0/0 | | None | | 39116d39-454b-4d82-867e-bbfd3ea63182 | None | None | | None | | 4032366f-3ac9-4862-85a7-c7411a8b7678 | None | None | | 2e19a748-9086-49f8-9498-01abc1a964fe | | dc7bc251-f0d0-456a-9102-c5b66646aa84 | tcp | 0.0.0.0/0 | 22:22 | None | | ddacf7ea-57ea-4c8a-8b68-093766284595 | None | None | | None | +--------------------------------------+-------------+-----------+------------+--------------------------------------+ dpif/dump-flows dp 想控制端打印dp中流表的所有条目。 这个命令主要来与debugOpen Vswitch.它所打印的流表不是openFlow的流条目。 它打印的是由dp模块维护的简单的流。 如果你想查看OpenFlow条目,请使用ovs-ofctl dump-flows。dpif/del-fow dp 删除指定dp上所有流表。同上所述,这些不是OpenFlow流表。 ovs-appctl dpif/dump-flows br-int 创建网络 openstack network create --share --external --provider-physical-network provider --provider-network-type flat provider $ openstack subnet create --network provider \ --allocation-pool start=192.168.56.100,end=192.168.56.200 \ --dns-nameserver 8.8.8.8 --gateway 192.168.56.1 \ --subnet-range 192.168.56.0/24 provider openstack network create selfservice $ openstack subnet create --network selfservice \ --dns-nameserver 8.8.8.8 --gateway 192.168.1.1 \ --subnet-range 192.168.1.0/24 selfservice openstack router create router openstack router add subnet router selfservice openstack router set router --external-gateway provider openstack port list --router router +--------------------------------------+------+-------------------+-------------------------------------------------------------------------------+--------+ | ID | Name | MAC Address | Fixed IP Addresses | Status | +--------------------------------------+------+-------------------+-------------------------------------------------------------------------------+--------+ | bff6605d-824c-41f9-b744-21d128fc86e1 | | fa:16:3e:2f:34:9b | ip_address='172.16.1.1', subnet_id='3482f524-8bff-4871-80d4-5774c2730728' | ACTIVE | | d6fe98db-ae01-42b0-a860-37b1661f5950 | | fa:16:3e:e8:c1:41 | ip_address='203.0.113.102', subnet_id='5cc70da8-4ee7-4565-be53-b9c011fca011' | ACTIVE | +--------------------------------------+------+-------------------+-------------------------------------------------------------------------------+--------+ $ ping -c 4 203.0.113.102 创建虚机 openstack keypair list $ ssh-keygen -q -N "" $ openstack keypair create --public-key ~/.ssh/id_rsa.pub mykey openstack flavor list openstack image list openstack network list openstack server create --flavor tiny --image cirros --nic net-id=27616098-0374-4ab4-95a8-b5bf4839dcf8 --key-name mykey provider-instance 网络配置 python /usr/lib/python2.7/site-packages/networking_odl/cmd/set_ovs_hostconfigs.py --ovs_hostconfigs='{ "ODL L2": { "allowed_network_types": [ "flat", "vlan", "vxlan" ], "bridge_mappings": { "provider": "br-int" }, "supported_vnic_types": [ { "vnic_type": "normal", "vif_type": "ovs", "vif_details": {} } ] }, "ODL L3": {} }' ovs-vsctl list open . [2019/1/16 19:09] 高正伟: ovs-vsctl set Open_vSwitch . other_config:local_ip=hostip ovs-vsctl set Open_vSwitch . other_config:local_ip=192.168.56.122 ovs-vsctl set Open_vSwitch . other_config:remote_ip=192.168.56.122 ovs-vsctl remove interface tunca7b782f232 options remote_ip ovs-vsctl set Open_vSwitch . other_config:provider_mappings=provider:br-ex ovs-vsctl set Open_vSwitch . external_ids:provider_mappings="{\"provider\": \"br-ex\"}" 清空 ovs-vsctl clear Open_vSwitch . external_ids ovs-vsctl set-manager tcp:10.13.80.34:6640 ovs-vsctl set-controller br-ex tcp:10.13.80.34:6640 ovs-vsctl del-controller br-ex sudo neutron-odl-ovs-hostconfig ovs-vsctl show ovs-vsctl add-port <bridge name> <port name> ovs-vsctl add-port br-ex enp0s10 ovs-vsctl del-port br-ex phy-br-ex ovs-vsctl del-port br-ex tun2ad7e9e91e4 重启odl后 systemctl restart openvswitch.service systemctl restart neutron-server.service systemctl stop neutron-server.service 创建虚机 openstack network create --share --external --provider-physical-network provider --provider-network-type flat provider openstack subnet create --network provider --allocation-pool start=192.168.56.2,end=192.168.56.100 --dns-nameserver 8.8.8.8 --gateway 192.168.56.1 --subnet-range 192.168.56.0/24 provider nova boot --image cirros --flavor tiny --nic net-id= --availability-zone nova:rcontroller01 vm-01 openstack server create --flavor tiny --image cirros --nic net-id= --key-name mykey test nova boot --image cirros --flavor tiny --nic net-id=0fe983c2-8178-403b-a00e-e8561580b210 --availability-zone nova:rcontroller01 vm-01 虚机可以学习到mac但是ping不通 抓包,先在虚机网卡上抓包, 然后在br-int上抓包 发现虚拟网卡上是发送了icmp请求报文的,但是br-int上没有 查看报文情况 [root@rcontroller01 ~] ovs-appctl dpif/dump-flows br-int recirc_id(0),tunnel(tun_id=0x0,src=192.168.56.102,dst=192.168.56.122,flags(-df-csum+key)),in_port(4),eth(),eth_type(0x0800),ipv4(proto=17,frag=no),udp(dst=3784), packets:266436, bytes:17584776, used:0.591s, actions:userspace(pid=4294962063,slow_path(bfd)) recirc_id(0xa0),in_port(5),ct_state(+new-est-rel-inv+trk),ct_mark(0/0x1),eth(),eth_type(0x0800),ipv4(frag=no), packets:148165, bytes:14520170, used:0.566s, actions:drop recirc_id(0),in_port(3),eth(),eth_type(0x0806), packets:1, bytes:60, used:5.228s, actions:drop recirc_id(0),tunnel(tun_id=0xb,src=192.168.56.102,dst=192.168.56.122,flags(-df-csum+key)),in_port(4),eth(dst=fa:16:3e:ab:ba:7e),eth_type(0x0806), packets:0, bytes:0, used:never, actions:5 recirc_id(0),in_port(5),eth(src=fa:16:3e:ab:ba:7e),eth_type(0x0800),ipv4(src=192.168.0.16,proto=1,frag=no), packets:148165, bytes:14520170, used:0.566s, actions:ct(zone=5004),recirc(0xa0) recirc_id(0),in_port(3),eth(),eth_type(0x0800),ipv4(frag=no), packets:886646, bytes:316947183, used:0.210s, flags:SFPR., actions:drop recirc_id(0),in_port(5),eth(src=fa:16:3e:ab:ba:7e,dst=fa:16:3e:7d:95:75),eth_type(0x0806),arp(sip=192.168.0.16,tip=192.168.0.5,op=1/0xff,sha=fa:16:3e:ab:ba:7e), packets:0, bytes:0, used:never, actions:userspace(pid=4294961925,controller(reason=4,dont_send=0,continuation=0,recirc_id=4618,rule_cookie=0x822002d,controller_id=0,max_len=65535)),set(tunnel(tun_id=0xb,src=192.168.56.122,dst=192.168.56.102,ttl=64,tp_dst=4789,flags(df|key))),4 安全组设置 openstack security group rule create --proto tcp 2e19a748-9086-49f8-9498-01abc1a964fe openstack security group rule create --proto tcp 6095293d-c2cd-433d-8a8f-e77ecb03609e openstack security group rule create --proto udp 2e19a748-9086-49f8-9498-01abc1a964fe openstack security group rule create --proto udp 6095293d-c2cd-433d-8a8f-e77ecb03609e ovs-vsctl add-port br-ex "ex-patch-int" ovs-vsctl set interface "ex-patch-int" type=patch ovs-vsctl set interface "ex-patch-int" options:peer=int-patch-ex ovs-vsctl add-port br-int "int-patch-ex" ovs-vsctl set interface "int-patch-ex" type=patch ovs-vsctl set interface "int-patch-ex" options:peer=ex-patch-int ovs-vsctl del-port br-ex "ex-patch-int" ovs-vsctl del-port br-int "int-patch-ex" ovs-vsctl del-port br-ex enp0s9 ovs-vsctl add-port br-int enp0s9 ovs-appctl ofproto/trace 重要命令 sudo ovs-ofctl -O OpenFlow13 show br-int sudo ovs-appctl ofproto/trace br-int "in_port=5,ip,nw_src=192.168.0.16,nw_dst=192.168.0.5" ovs-appctl dpctl/dump-conntrack 11.查看接口id等 ovs-appctl dpif/show 12.查看接口统计 ovs-ofctl dump-ports br-int 查看接口 sudo ovs-ofctl show br-int -O OpenFlow13 ovs常用命令 控制管理类 1.查看网桥和端口 ovs-vsctl show 1 2.创建一个网桥 ovs-vsctl add-br br0 ovs-vsctl set bridge br0 datapath_type=netdev 1 2 3.添加/删除一个端口 for system interfaces ovs-vsctl add-port br0 eth1 ovs-vsctl del-port br0 eth1 for DPDK ovs-vsctl add-port br0 dpdk1 -- set interface dpdk1 type=dpdk options:dpdk-devargs=0000:01:00.0 for DPDK bonds ovs-vsctl add-bond br0 dpdkbond0 dpdk1 dpdk2 \ -- set interface dpdk1 type=dpdk options:dpdk-devargs=0000:01:00.0 \ -- set interface dpdk2 type=dpdk options:dpdk-devargs=0000:02:00.0 1 2 3 4 5 6 7 8 9 4.设置/清除网桥的openflow协议版本 ovs-vsctl set bridge br0 protocols=OpenFlow13 ovs-vsctl clear bridge br0 protocols 1 2 5.查看某网桥当前流表 ovs-ofctl dump-flows br0 ovs-ofctl -O OpenFlow13 dump-flows br0 ovs-appctl bridge/dump-flows br0 1 2 3 6.设置/删除控制器 ovs-vsctl set-controller br0 tcp:1.2.3.4:6633 ovs-vsctl del-controller br0 1 2 7.查看控制器列表 ovs-vsctl list controller 1 8.设置/删除被动连接控制器 ovs-vsctl set-manager tcp:1.2.3.4:6640 ovs-vsctl get-manager ovs-vsctl del-manager 1 2 3 9.设置/移除可选选项 ovs-vsctl set Interface eth0 options:link_speed=1G ovs-vsctl remove Interface eth0 options link_speed 1 2 10.设置fail模式,支持standalone或者secure standalone(default):清除所有控制器下发的流表,ovs自己接管 secure:按照原来流表继续转发 ovs-vsctl del-fail-mode br0 ovs-vsctl set-fail-mode br0 secure ovs-vsctl get-fail-mode br0 1 2 3 11.查看接口id等 ovs-appctl dpif/show 1 12.查看接口统计 ovs-ofctl dump-ports br0 1 流表类 流表操作 1.添加普通流表 ovs-ofctl add-flow br0 in_port=1,actions=output:2 1 2.删除所有流表 ovs-ofctl del-flows br0 1 3.按匹配项来删除流表 ovs-ofctl del-flows br0 "in_port=1" 1 匹配项 1.匹配vlan tag,范围为0-4095 ovs-ofctl add-flow br0 priority=401,in_port=1,dl_vlan=777,actions=output:2 1 2.匹配vlan pcp,范围为0-7 ovs-ofctl add-flow br0 priority=401,in_port=1,dl_vlan_pcp=7,actions=output:2 1 3.匹配源/目的MAC ovs-ofctl add-flow br0 in_port=1,dl_src=00:00:00:00:00:01/00:00:00:00:00:01,actions=output:2 ovs-ofctl add-flow br0 in_port=1,dl_dst=00:00:00:00:00:01/00:00:00:00:00:01,actions=output:2 1 2 4.匹配以太网类型,范围为0-65535 ovs-ofctl add-flow br0 in_port=1,dl_type=0x0806,actions=output:2 1 5.匹配源/目的IP 条件:指定dl_type=0x0800,或者ip/tcp ovs-ofctl add-flow br0 ip,in_port=1,nw_src=10.10.0.0/16,actions=output:2 ovs-ofctl add-flow br0 ip,in_port=1,nw_dst=10.20.0.0/16,actions=output:2 1 2 6.匹配协议号,范围为0-255 条件:指定dl_type=0x0800或者ip ICMP ovs-ofctl add-flow br0 ip,in_port=1,nw_proto=1,actions=output:2 7.匹配IP ToS/DSCP,tos范围为0-255,DSCP范围为0-63 条件:指定dl_type=0x0800/0x86dd,并且ToS低2位会被忽略(DSCP值为ToS的高6位,并且低2位为预留位) ovs-ofctl add-flow br0 ip,in_port=1,nw_tos=68,actions=output:2 ovs-ofctl add-flow br0 ip,in_port=1,ip_dscp=62,actions=output:2 8.匹配IP ecn位,范围为0-3 条件:指定dl_type=0x0800/0x86dd ovs-ofctl add-flow br0 ip,in_port=1,ip_ecn=2,actions=output:2 9.匹配IP TTL,范围为0-255 ovs-ofctl add-flow br0 ip,in_port=1,nw_ttl=128,actions=output:2 10.匹配tcp/udp,源/目的端口,范围为0-65535 匹配源tcp端口179 ovs-ofctl add-flow br0 tcp,tcp_src=179/0xfff0,actions=output:2 匹配目的tcp端口179 ovs-ofctl add-flow br0 tcp,tcp_dst=179/0xfff0,actions=output:2 匹配源udp端口1234 ovs-ofctl add-flow br0 udp,udp_src=1234/0xfff0,actions=output:2 匹配目的udp端口1234 ovs-ofctl add-flow br0 udp,udp_dst=1234/0xfff0,actions=output:2 11.匹配tcp flags tcp flags=fin,syn,rst,psh,ack,urg,ece,cwr,ns ovs-ofctl add-flow br0 tcp,tcp_flags=ack,actions=output:2 12.匹配icmp code,范围为0-255 条件:指定icmp ovs-ofctl add-flow br0 icmp,icmp_code=2,actions=output:2 13.匹配vlan TCI TCI低12位为vlan id,高3位为priority,例如tci=0xf123则vlan_id为0x123和vlan_pcp=7 ovs-ofctl add-flow br0 in_port=1,vlan_tci=0xf123,actions=output:2 14.匹配mpls label 条件:指定dl_type=0x8847/0x8848 ovs-ofctl add-flow br0 mpls,in_port=1,mpls_label=7,actions=output:2 15.匹配mpls tc,范围为0-7 条件:指定dl_type=0x8847/0x8848 ovs-ofctl add-flow br0 mpls,in_port=1,mpls_tc=7,actions=output:2 1 16.匹配tunnel id,源/目的IP 匹配tunnel id ovs-ofctl add-flow br0 in_port=1,tun_id=0x7/0xf,actions=output:2 匹配tunnel源IP ovs-ofctl add-flow br0 in_port=1,tun_src=192.168.1.0/255.255.255.0,actions=output:2 匹配tunnel目的IP ovs-ofctl add-flow br0 in_port=1,tun_dst=192.168.1.0/255.255.255.0,actions=output:2 一些匹配项的速记符 速记符 匹配项 ip dl_type=0x800 ipv6 dl_type=0x86dd icmp dl_type=0x0800,nw_proto=1 icmp6 dl_type=0x86dd,nw_proto=58 tcp dl_type=0x0800,nw_proto=6 tcp6 dl_type=0x86dd,nw_proto=6 udp dl_type=0x0800,nw_proto=17 udp6 dl_type=0x86dd,nw_proto=17 sctp dl_type=0x0800,nw_proto=132 sctp6 dl_type=0x86dd,nw_proto=132 arp dl_type=0x0806 rarp dl_type=0x8035 mpls dl_type=0x8847 mplsm dl_type=0x8848 指令动作 1.动作为出接口 从指定接口转发出去 ovs-ofctl add-flow br0 in_port=1,actions=output:2 1 2.动作为指定group group id为已创建的group table ovs-ofctl add-flow br0 in_port=1,actions=group:666 1 3.动作为normal 转为L2/L3处理流程 ovs-ofctl add-flow br0 in_port=1,actions=normal 1 4.动作为flood 从所有物理接口转发出去,除了入接口和已关闭flooding的接口 ovs-ofctl add-flow br0 in_port=1,actions=flood 1 5.动作为all 从所有物理接口转发出去,除了入接口 ovs-ofctl add-flow br0 in_port=1,actions=all 1 6.动作为local 一般是转发给本地网桥 ovs-ofctl add-flow br0 in_port=1,actions=local 1 7.动作为in_port 从入接口转发回去 ovs-ofctl add-flow br0 in_port=1,actions=in_port 1 8.动作为controller 以packet-in消息上送给控制器 ovs-ofctl add-flow br0 in_port=1,actions=controller 1 9.动作为drop 丢弃数据包操作 ovs-ofctl add-flow br0 in_port=1,actions=drop 1 10.动作为mod_vlan_vid 修改报文的vlan id,该选项会使vlan_pcp置为0 ovs-ofctl add-flow br0 in_port=1,actions=mod_vlan_vid:8,output:2 1 11.动作为mod_vlan_pcp 修改报文的vlan优先级,该选项会使vlan_id置为0 ovs-ofctl add-flow br0 in_port=1,actions=mod_vlan_pcp:7,output:2 1 12.动作为strip_vlan 剥掉报文内外层vlan tag ovs-ofctl add-flow br0 in_port=1,actions=strip_vlan,output:2 1 13.动作为push_vlan 在报文外层压入一层vlan tag,需要使用openflow1.1以上版本兼容 ovs-ofctl add-flow -O OpenFlow13 br0 in_port=1,actions=push_vlan:0x8100,set_field:4097-\>vlan_vid,output:2 1 ps: set field值为4096+vlan_id,并且vlan优先级为0,即4096-8191,对应的vlan_id为0-4095 14.动作为push_mpls 修改报文的ethertype,并且压入一个MPLS LSE ovs-ofctl add-flow br0 in_port=1,actions=push_mpls:0x8847,set_field:10-\>mpls_label,output:2 1 15.动作为pop_mpls 剥掉最外层mpls标签,并且修改ethertype为非mpls类型 ovs-ofctl add-flow br0 mpls,in_port=1,mpls_label=20,actions=pop_mpls:0x0800,output:2 1 16.动作为修改源/目的MAC,修改源/目的IP 修改源MAC ovs-ofctl add-flow br0 in_port=1,actions=mod_dl_src:00:00:00:00:00:01,output:2 修改目的MAC ovs-ofctl add-flow br0 in_port=1,actions=mod_dl_dst:00:00:00:00:00:01,output:2 修改源IP ovs-ofctl add-flow br0 in_port=1,actions=mod_nw_src:192.168.1.1,output:2 修改目的IP ovs-ofctl add-flow br0 in_port=1,actions=mod_nw_dst:192.168.1.1,output:2 17.动作为修改TCP/UDP/SCTP源目的端口 修改TCP源端口 ovs-ofctl add-flow br0 tcp,in_port=1,actions=mod_tp_src:67,output:2 修改TCP目的端口 ovs-ofctl add-flow br0 tcp,in_port=1,actions=mod_tp_dst:68,output:2 修改UDP源端口 ovs-ofctl add-flow br0 udp,in_port=1,actions=mod_tp_src:67,output:2 修改UDP目的端口 ovs-ofctl add-flow br0 udp,in_port=1,actions=mod_tp_dst:68,output:2 18.动作为mod_nw_tos 条件:指定dl_type=0x0800 修改ToS字段的高6位,范围为0-255,值必须为4的倍数,并且不会去修改ToS低2位ecn值 ovs-ofctl add-flow br0 ip,in_port=1,actions=mod_nw_tos:68,output:2 1 19.动作为mod_nw_ecn 条件:指定dl_type=0x0800,需要使用openflow1.1以上版本兼容 修改ToS字段的低2位,范围为0-3,并且不会去修改ToS高6位的DSCP值 ovs-ofctl add-flow br0 ip,in_port=1,actions=mod_nw_ecn:2,output:2 1 20.动作为mod_nw_ttl 修改IP报文ttl值,需要使用openflow1.1以上版本兼容 ovs-ofctl add-flow -O OpenFlow13 br0 in_port=1,actions=mod_nw_ttl:6,output:2 1 21.动作为dec_ttl 对IP报文进行ttl自减操作 ovs-ofctl add-flow br0 in_port=1,actions=dec_ttl,output:2 1 22.动作为set_mpls_label 对报文最外层mpls标签进行修改,范围为20bit值 ovs-ofctl add-flow br0 in_port=1,actions=set_mpls_label:666,output:2 1 23.动作为set_mpls_tc 对报文最外层mpls tc进行修改,范围为0-7 ovs-ofctl add-flow br0 in_port=1,actions=set_mpls_tc:7,output:2 1 24.动作为set_mpls_ttl 对报文最外层mpls ttl进行修改,范围为0-255 ovs-ofctl add-flow br0 in_port=1,actions=set_mpls_ttl:255,output:2 1 25.动作为dec_mpls_ttl 对报文最外层mpls ttl进行自减操作 ovs-ofctl add-flow br0 in_port=1,actions=dec_mpls_ttl,output:2 1 26.动作为move NXM字段 使用move参数对NXM字段进行操作 将报文源MAC复制到目的MAC字段,并且将源MAC改为00:00:00:00:00:01 ovs-ofctl add-flow br0 in_port=1,actions=move:NXM_OF_ETH_SRC[]-\>NXM_OF_ETH_DST[],mod_dl_src:00:00:00:00:00:01,output:2 1 2 ps: 常用NXM字段参照表 NXM字段 报文字段 NXM_OF_ETH_SRC 源MAC NXM_OF_ETH_DST 目的MAC NXM_OF_ETH_TYPE 以太网类型 NXM_OF_VLAN_TCI vid NXM_OF_IP_PROTO IP协议号 NXM_OF_IP_TOS IP ToS值 NXM_NX_IP_ECN IP ToS ECN NXM_OF_IP_SRC 源IP NXM_OF_IP_DST 目的IP NXM_OF_TCP_SRC TCP源端口 NXM_OF_TCP_DST TCP目的端口 NXM_OF_UDP_SRC UDP源端口 NXM_OF_UDP_DST UDP目的端口 NXM_OF_SCTP_SRC SCTP源端口 NXM_OF_SCTP_DST SCTP目的端口 27.动作为load NXM字段 使用load参数对NXM字段进行赋值操作 push mpls label,并且把10(0xa)赋值给mpls label ovs-ofctl add-flow br0 in_port=1,actions=push_mpls:0x8847,load:0xa-\>OXM_OF_MPLS_LABEL[],output:2 对目的MAC进行赋值 ovs-ofctl add-flow br0 in_port=1,actions=load:0x001122334455-\>OXM_OF_ETH_DST[],output:2 1 2 3 4 28.动作为pop_vlan 弹出报文最外层vlan tag ovs-ofctl add-flow br0 in_port=1,dl_type=0x8100,dl_vlan=777,actions=pop_vlan,output:2 1 meter表 常用操作 由于meter表是openflow1.3版本以后才支持,所以所有命令需要指定OpenFlow1.3版本以上 ps: 在openvswitch-v2.8之前的版本中,还不支持meter 在v2.8版本之后已经实现,要正常使用的话,需要注意的是datapath类型要指定为netdev,band type暂时只支持drop,还不支持DSCP REMARK 1.查看当前设备对meter的支持 ovs-ofctl -O OpenFlow13 meter-features br0 2.查看meter表 ovs-ofctl -O OpenFlow13 dump-meters br0 3.查看meter统计 ovs-ofctl -O OpenFlow13 meter-stats br0 4.创建meter表 限速类型以kbps(kilobits per second)计算,超过20kb/s则丢弃 ovs-ofctl -O OpenFlow13 add-meter br0 meter=1,kbps,band=type=drop,rate=20 同上,增加burst size参数 ovs-ofctl -O OpenFlow13 add-meter br0 meter=2,kbps,band=type=drop,rate=20,burst_size=256 同上,增加stats参数,对meter进行计数统计 ovs-ofctl -O OpenFlow13 add-meter br0 meter=3,kbps,stats,band=type=drop,rate=20,burst_size=256 限速类型以pktps(packets per second)计算,超过1000pkt/s则丢弃 ovs-ofctl -O OpenFlow13 add-meter br0 meter=4,pktps,band=type=drop,rate=1000 5.删除meter表 删除全部meter表 ovs-ofctl -O OpenFlow13 del-meters br0 删除meter id=1 ovs-ofctl -O OpenFlow13 del-meter br0 meter=1 6.创建流表 ovs-ofctl -O OpenFlow13 add-flow br0 in_port=1,actions=meter:1,output:2 group表 由于group表是openflow1.1版本以后才支持,所以所有命令需要指定OpenFlow1.1版本以上 常用操作 group table支持4种类型 all:所有buckets都执行一遍 select: 每次选择其中一个bucket执行,常用于负载均衡应用 ff(FAST FAILOVER):快速故障修复,用于检测解决接口等故障 indirect:间接执行,类似于一个函数方法,被另一个group来调用 1.查看当前设备对group的支持 ovs-ofctl -O OpenFlow13 dump-group-features br0 2.查看group表 ovs-ofctl -O OpenFlow13 dump-groups br0 3.创建group表 类型为all ovs-ofctl -O OpenFlow13 add-group br0 group_id=1,type=all,bucket=output:1,bucket=output:2,bucket=output:3 类型为select ovs-ofctl -O OpenFlow13 add-group br0 group_id=2,type=select,bucket=output:1,bucket=output:2,bucket=output:3 类型为select,指定hash方法(5元组,OpenFlow1.5+) ovs-ofctl -O OpenFlow15 add-group br0 group_id=3,type=select,selection_method=hash,fields=ip_src,bucket=output:2,bucket=output:3 4.删除group表 ovs-ofctl -O OpenFlow13 del-groups br0 group_id=2 5.创建流表 ovs-ofctl -O OpenFlow13 add-flow br0 in_port=1,actions=group:2 goto table配置 数据流先从table0开始匹配,如actions有goto_table,再进行后续table的匹配,实现多级流水线,如需使用goto table,则创建流表时,指定table id,范围为0-255,不指定则默认为table0 1.在table0中添加一条流表条目 ovs-ofctl add-flow br0 table=0,in_port=1,actions=goto_table=1 2.在table1中添加一条流表条目 ovs-ofctl add-flow br0 table=1,ip,nw_dst=10.10.0.0/16,actions=output:2 tunnel配置 如需配置tunnel,必需确保当前系统对各tunnel的remote ip网络可达 gre 1.创建一个gre接口,并且指定端口id=1001 ovs-vsctl add-port br0 gre1 -- set Interface gre1 type=gre options:remote_ip=1.1.1.1 ofport_request=1001 2.可选选项 将tos或者ttl在隧道上继承,并将tunnel id设置成123 ovs-vsctl set Interface gre1 options:tos=inherit options:ttl=inherit options:key=123 3.创建关于gre流表 封装gre转发 ovs-ofctl add-flow br0 ip,in_port=1,nw_dst=10.10.0.0/16,actions=output:1001 解封gre转发 ovs-ofctl add-flow br0 in_port=1001,actions=output:1 vxlan 1.创建一个vxlan接口,并且指定端口id=2001 ovs-vsctl add-port br0 vxlan1 -- set Interface vxlan1 type=vxlan options:remote_ip=1.1.1.1 ofport_request=2001 2.可选选项 将tos或者ttl在隧道上继承,将vni设置成123,UDP目的端为设置成8472(默认为4789) ovs-vsctl set Interface vxlan1 options:tos=inherit options:ttl=inherit options:key=123 options:dst_port=8472 3.创建关于vxlan流表 封装vxlan转发 ovs-ofctl add-flow br0 ip,in_port=1,nw_dst=10.10.0.0/16,actions=output:2001 解封vxlan转发 ovs-ofctl add-flow br0 in_port=2001,actions=output:1 sflow配置 1.对网桥br0进行sflow监控 agent: 与collector通信所在的网口名,通常为管理口 target: collector监听的IP地址和端口,端口默认为6343 header: sFlow在采样时截取报文头的长度 polling: 采样时间间隔,单位为秒 ovs-vsctl -- --id=@sflow create sflow agent=eth0 target=\"10.0.0.1:6343\" header=128 sampling=64 polling=10 -- set bridge br0 sflow=@sflow 2.查看创建的sflow ovs-vsctl list sflow 3.删除对应的网桥sflow配置,参数为sFlow UUID ovs-vsctl remove bridge br0 sflow 7b9b962e-fe09-407c-b224-5d37d9c1f2b3 4.删除网桥下所有sflow配置 ovs-vsctl -- clear bridge br0 sflow 1 QoS配置 ingress policing 1.配置ingress policing,对接口eth0入流限速10Mbps ovs-vsctl set interface eth0 ingress_policing_rate=10000 ovs-vsctl set interface eth0 ingress_policing_burst=8000 2.清除相应接口的ingress policer配置 ovs-vsctl set interface eth0 ingress_policing_rate=0 ovs-vsctl set interface eth0 ingress_policing_burst=0 3.查看接口ingress policer配置 ovs-vsctl list interface eth0 4.查看网桥支持的Qos类型 ovs-appctl qos/show-types br0 端口镜像配置 1.配置eth0收到/发送的数据包镜像到eth1 ovs-vsctl -- set bridge br0 mirrors=@m \ -- --id=@eth0 get port eth0 \ -- --id=@eth1 get port eth1 \ -- --id=@m create mirror name=mymirror select-dst-port=@eth0 select-src-port=@eth0 output-port=@eth1 2.删除端口镜像配置 ovs-vsctl -- --id=@m get mirror mymirror -- remove bridge br0 mirrors @m 3.清除网桥下所有端口镜像配置 ovs-vsctl clear bridge br0 mirrors 4.查看端口镜像配置 ovs-vsctl get bridge br0 mirrors Open vSwitch中有多个命令,分别有不同的作用,大致如下: ovs-vsctl用于控制ovs db ovs-ofctl用于管理OpenFlow switch 的 flow ovs-dpctl用于管理ovs的datapath ovs-appctl用于查询和管理ovs daemon 转载于:https://www.cnblogs.com/liuhongru/p/10336849.html 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_30876945/article/details/99916308。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-06-08 17:13:19
294
转载
转载文章
...开源面临的困境:过度依赖少数项目维护志愿者。 (开源项目由志愿者自发来维护,)这本来会是一件很有趣的事情,只是去年十二月在Log4j中发现的安全漏洞也确实存在着上述情况。 然而这个基于Java的日志记录工具已经在企业记录中无处不在。例如根据软件公司Sonatype的一份报告显示,在过去的三个月里,Log4j的下载量就已经超过3000万次。 Log4j是Sonatype公司旗下的Black Duck Open Hub所研发的研究工具。Log4j有着440,000行代码,由近200名开发人员贡献了将近24,000行代码。其实与其他开源项目相比,这是一个庞大的开发团队。但是如果关注数据的话,就会发现超过70%的工作是仅仅靠五个人来完成的。 Log4j的主页上展示了十几位项目团队的成员。而大多项目的开发人员要比其原本需要的少得多----这是高度依赖开发人员团队所呈现出来的问题。 “如今几乎没有人愿意为现有的开源项目作出贡献”,来自DNS网络公司NS1的杰出工程师Jeremy Strech说,“因为通常来说,这没有直接的物质回报,也很少提供荣誉----大多数用户甚至不知道他们所用的软件是谁维护的。” 他说,开源贡献者们最常见的动机就是添加他们自己想要的功能。“一旦实现了这一点,他们几乎都不会留下来。” 与此同时,随着项目的逐渐火爆,对于维护方面的核心团队来说,他们的负担也在不断增加。 “更多的用户意味有着更多的功能需求和错误报告----但不是更多的维护人员”,Stretch说。“曾经令人愉快的爱好很快就会变成一项乏味的项目,所以很多维护人员选择干脆完全放弃他们的项目,这也是可以理解的。” Part1公地悲剧 开源软件的生态系统,就是“公地悲剧”的一个完美例子。 这个悲剧就是---当一种资源,无论是一个超限的公园还是一个开源项目,所有人都在使用而没有人贡献之时,最终都会因为过度使用和投入不足而崩溃坍塌。 这种方式可以在短期内为你节省资金,但随着时间的推移,它可能会变成项目里致命的缺陷。 拿Linux来说,这个开源操作系统在全球前100万台服务器中运行率在96%以上,且这些服务器90%的云基础设施也都在Linux上。更不用说世界上85%的智能手机都运行着Linux,即Android操作系统。 这些常见开源项目的列表还在逐渐增加着。 所以没有开源,今天的大部分技术基础设施的建设也将会戛然而止。 “这是一个很现实的问题”,Data.org的执行董事Danil Mikhailov说,该组织是由万事达包容性发展中心和洛克菲勒基金会支持,旨在促进使用数据科学来应对当今社会所面临的巨大挑战的非营利性组织。 虽然几乎所有组织都在使用着开源软件,但只有少数组织为这些项目作出了贡献。The New Stack、Linux Foundation Research 和 TODO Group 在 9 月发布的一项调查中,42% 的参与者表示,他们至少有时会为开源项目做出贡献。 而同一项研究表明,只有36%的组织会培训他们的工程师为开源作出贡献。 个体公司应该支持贡献这些他们使用最多且对他们成功至关重要的项目,Mikhailov认为:“如果你使用开源,你就应该为他做出属于你自己的贡献。” Part2OSPO的好处:更少的技术负债,更好的招聘效果 参与开源社区----特别是在内部开源计划办公室(OSPO)的指导下----不仅可以保证对组织成功至关重要项目的健康发展,还可以提高项目安全性,同时可以允许工程师在项目发展规划中起到更大的作用。 例如,如果一家公司使用了开源工具,并对其进行了一些调整使其变得更好。但如果这项改进没有反馈到开源社区,那么开源项目的正式版本就会一开始与该公司所使用的版本有所不同。 “当原始数据来源发生变化且你所使用的是不同的版本时,你的技术负债将越来越多。而这些差异是以天为单位迅速增长的。”VMware 开源营销和战略总监 Suzanne Ambiel 表示,“所以你很快就会变成一个开源项目里独一无二变体的‘自豪’用户和维护人员。” “如果技术负债越来越多,那么公司的管理成本则会非常昂贵”。 实际上对于开源活动的支持也变成了一种招聘途径。“这真是一块吸引人才的磁铁,”Ambiel说,“这也是新员工所寻求的“。 她还提到,一些工程经理可能会对贡献开源而减损核心产品的开发的精力而感到担忧。她补充到,他们的理由有可能是这样的:“我只有有限的才华与时间,且我需要这些只做我认为可以处理且看到投资回报的事情。” 但她说,这是一种鼠目寸光的态度。支持开源社区并且作出贡献的员工,可以从中培养技能与增长才干。 云安全供应商 Sysdig 的首席技术官兼创始人 Loris Degionni 也赞同这一观点:“找到为开源做出贡献的员工无疑就找到一座金矿,”他说。 他认为,这些参与开源的员工更具备公司想拥有的竞争力并将一些功能融入至社区所支持的标准中。且在人才争夺战中,拥抱开源的公司也更受到开发人员的青睐。 “最后,开源项目是由你可能无法聘请的技术专家社区推动的”,他说,“当员工积极参与并于这些专家合作时,他们将能更好地深入这些顶级的实践,并将这些收获带回到你的组织之中。” “当原始数据来源发生变化且你所使用的是不同的版本时,你的技术负债将越来越多...所以你很快就会变成一个开源项目里独一无二变体的”自豪“用户和维护人员。”— Suzanne Ambiel,VMware 开源营销和战略总监 “但是这一切终究不会白费--开发人员不应该把空闲时间用在磨练他们的技能上,因为你的公司很快就会在他们的努力中看到好处。” Degionni认为,OSPO(开源计划办公室)可以帮助公司实现这些目标,以及帮助确定贡献的优先级并确保合作的进行。除此之外,他们也可以对公司内部开发应用程序方面的治理提供相关帮助。 “开源团队的成员也可以成为开源技术的伟大内部传播者,并充当组织与更广泛社区之间的桥梁。”他补充道。 在 The New Stack、Linux Foundation Research 和 TODO Group 的 9 月调查中,近 53% 的拥有 OSPO的组织表示,由于拥有了OSPO,他们看到了更多创新,而近 43% 的组织表示,他们在外部开源项目的参与度上有所增加。 Part3更多OSPO的好处:商业优势 网络安全公司 ThreatX 的首席创新官 Tom Hickman 表示,为开源社区做出贡献,不仅有助于社区,还有助于为社区做出贡献的公司。 “围绕一个项目而发展的开发人员社区,有助于代码库的形成,并吸引更多的开发人员参与”,他说,“这可以变成一个良性循环。” 此外,根据哈佛商学院的研究,为开源项目作出贡献的公司从使用开源的项目中获得的生产价值,是不参与开源项目公司的两倍。 Cloud Native Computing Foundation 的首席技术官 Chris Aniszczyk 说,世界上许多巨头公司都为开源作出了贡献。他还提到,开源贡献者的指数是作为公司是否有所作为的参考。 科技巨头占据了这份榜单的主导地位:谷歌、微软、红帽、英特尔、IBM、亚马逊、Facebook、VMware、GitHub 和 SAP 依次是排名前 10 的贡献者。但Aniszczyk 表示,但也有很多终端用户公司进入前 100 名,包括 Uber、BBC、Orange、Netflix 和 Square。 “我们一直知道,在上游项目中工作不仅仅是关正确与否----它是开源软件开发的最佳方法,也是向客户提供开源福利的最佳方式”他说,“很高兴看到IT领导者们也认识到了这一点。” 为了和这些公司一起作出贡献,公司也需要有自己的开源策略,而拥有一个开源计划办公室则可以为其提供帮助。 “在使用开源软件方面,OPSO为公司提供了一个至关重要的能力中心”他说。 这与公司拥有安全运营中心的方式类似,他说。 “围绕一个项目而发展的开发人员社区,有助于代码库的形成,并吸引更多的开发人员参与,这可以变成一个良性循环。” ——Tom Hickman,ThreatX 首席创新官 “如果你对安全团队进行相应投资,你通常是不会期望你的软件是安全的,也无法及时应对安全事件。”他说。 “同样的逻辑也适用于 OSPO,这就是为什么你会看到许多领先的公司,例如Apple、Meta、Twitter、Goldman Sachs、Bloomberg 和 Google 都拥有 OSPO。他们走在了趋势的前面。” 而对组织内的开源活动的支持态度亦可成为软件供应商们的差异化原因与营销的机会。 根据Red Hat 2月分发布的一项调查,82%的IT领导者更倾向于选择为开源社区作出贡献的软件供应商。 受访者表示,当供应商支持开源社区时,就表示着他们更熟悉开源的流程并且在客户遇到技术难题时会更加有效。 但收益的不仅仅是软件供应商们。 根据 The New Stack、Linux Foundation Research 和 TODO Group 9 月份的调查,57% 拥有 OSPO 的组织将使用它们来进一步发展战略关系和建立合作伙伴关系。 十年前,Mark Hinkle 在 Citrix 工作时创办了一个开源计划办公室。他指出了在内部拥有一个 OSPO将如何使公司受益。 “对于我们来说,最大的工作是让不熟悉开源的员工学会并参与其中,成为优秀的社区成员”,他说,“我们还就如何确保我们的IP不会在没有正确理解的情况下进入项目的情况提供了指导,并确保我们没有与我们企业软件许可相冲突的开源项目合作。” 他说,OSPO还帮助Citrix确定了公司参与开源项目和Linux基金会等贸易组织的战略机会。 如今,他是云原生开源集成平台 TriggerMesh 的首席执行官兼联合创始人。 他说,参与开源系统对公司来说有着重大的经济效益。 “我们参与Knative是为了分享我们基础底层平台的开发,但作为业务的一部分,我们也拥有相关的增值服务。”他说,“通过共享该平台的研发,这为我们提供了更多的资源来改进我们自己的差异化技术。” Part4如何入门开源 在 The New Stack、Linux Foundation Research 和 TODO Group 的 9 月份调查中,有 63% 的公司表示,拥有OSPO 对其工程或产品团队的成功至关重要,高于上一年度该项研究数据的 54%。 其中77% 的人表示他们的开源程序对他们的软件实践产生了积极影响,例如提高了代码质量。 但公司也不可能总是为他们使用的每一个开源项目而花费精力。 “首先,节流一下”,VMware 的 Ambiel 建议道。 公司应该关注投入使用中最有意义的项目。而这也是OSPO可以帮助确定优先事项并确保技术与战略一致性的领域。 之后,开发人员应该自己去了解一下。项目通常提供相关在线文档,一般包含贡献着指南、治理文档和未解决问题列表。 “对于那些你较感兴趣的项目中,你可以介绍一下自己----打个招呼”,她说。“然后转到Slack频道或者分发列表,询问他们需要帮助的地方。也许他们不需要帮助,一切完好;又或者他们也有可能使用新人来审查核验代码。” Ambiel 说,开源计划办公室不仅可以帮助制定为开源社区做出贡献的商业案例,还可以帮助公司以安全、可靠和健全的方式来做这件事。 “如果我为一家公司工作,并想为开源做出贡献,我不想意外披露、泄露或破坏任何专利,”她说。“而OSPO可以帮助您做出明智的选择。” 她说,OSPO还可以在开源方面提供领导力和指导理念的支持。“它可以提供引领、指导、辅导和最佳实践的作用。” Aqua Security的开发人员倡导者Anaïs Urlichs则认为,支持开源的承诺必须从高层开始。 她说,“公司在多数时候往往不重视对开源的投资,所以员工自然而然不被鼓励对此作出贡献。” 在这些情况下,员工对于开源的热情也会在空闲时间里对开源的建设而消散殆尽,这对于开源的发展来说是不可持续的。 “如果公司对开源项目依赖度高,那么将开源贡献纳入工程师的日程安排是很重要的,”她说。“一些公司定义了员工可以为开源建设的时间百分比,将其作为他们正常工作日的一部分。” The New Stack 是 Insight Partners 的全资子公司,Insight Partners 是本文提到的以下公司的投资者:Sysdig、Aqua Security。 中英对照版 How an OSPO Can Help Your Engineers Give Back to Open Source OSPO (开源项目办公室)是如何使工程师回馈开源的 When it comes to open source software, there’s a big and growing problem: most organizations are takers, not givers. 谈到开源软件,有一个较大且日益严重的问题:大多数组织都是索取者,而不是给予者。 There’s a classic XKCD comic that shows a giant structure representing modern digital infrastructure, dependent on a tiny component created by “some random person in Nebraska” who has been “thanklessly maintaining since 2003.” 经典漫画XKCD展示了一个代表现代数字基础设施的巨大结构,它依赖于“内布拉斯加州的某位人士”创建的微小组件,该组件“自2003年来一直都处于吃力不讨好的状态”。 Randall Monroe’s XKCD comic illustrates the open source dilemma: overreliance on a small number of volunteer project maintainers. Randall Monroe 的XKCD漫画展示了目前开源面临的窘境:过度依赖少数项目维护志愿者的志愿服务。 This would have been funny, except that this is exactly what happened when security vulnerabilities were discovered in Log4j last December. (开源项目由志愿者自发来维护,)这听起来像是一件很滑稽的事情,但事实上去年十二月在Log4j中发现的安全漏洞也确实存在着上述情况。 The Java-based logging tool is ubiquitous in enterprise publications. In the last three months, for example, Log4j has been downloaded more than 30 million times, according to a report by the enterprise software company Sonatype. 然而这个基于Java的日志记录工具已经在企业内部刊物中无处不在。例如根据软件公司Sonatype的一份报告显示,在过去的三个月里,Log4j的下载量就已经超过3000万次。 The tool has 440,000 lines of code, according to Synopsys‘ Black Duck Open Hub research tool, with nearly 24,000 contributions by nearly 200 developers. That’s a large dev team compared to other open source projects. But looking closer at the numbers, more than 70% of commits were by just five people. 根据Synopsys(新思)公司旗下的Black Duck Open Hub 研究工具显示。Log4j有着440,000行代码,由近200名开发人员贡献了将近24,000行代码。其实与其他开源项目相比,这是一个庞大的开发团队。但是如果关注数据的话,就会发现超过70%的提交是仅仅靠五个人来完成的。 Log4j’s home page lists about a dozen members on its project team. Most projects have far fewer developers working on them — and that presents a problem for the organizations that depend on them. Log4j的主页上展示了十几位项目团队的成员。而大多项目的开发人员要比其原本需要的少得多----这是高度依赖开发人员团队所呈现出来的问题。 “There is little incentive for anyone today to contribute to an existing open source project,” said Jeremy Stretch, distinguished engineer at NS1, a DNS network company. “There’s usually no direct compensation, and few accolades are offered — most users don’t even know who maintains the software that they use.” “如今的人没有什么动力去为现有的开源项目做贡献”,来自DNS网络公司NS1的杰出工程师Jeremy Strech说,“因为通常来说,这没有直接的物质回报,也很少提供荣誉----大多数用户甚至不知道他们所用的软件是谁维护的。” The most common motivation among open source contributors is to add a feature that they themselves want to see, he said. “Once this has been achieved, the contributor rarely sticks around.” 他说,开源贡献者们最常见的动机就是添加他们自己想要的功能。“一旦实现了这一点,他们几乎都不会留下来。” Meanwhile, as a project becomes more popular, the burden on the core team of maintainers keeps increasing. 与此同时,随着项目的逐渐流行,对于维护方面的核心团队来说,他们的负担也在不断增加。 “More users means more feature requests and more bug reports — but not more maintainers,” Stretch said. “What was once an enjoyable hobby can quickly become a tedious chore, and many maintainers understandably opt to simply abandon their projects altogether.” “更多的用户意味有着更多的功能需求和错误报告----但不是更多的维护人员”,Stretch说。“曾经令人愉快的爱好很快就会变成一项乏味的项目,所以很多维护人员选择干脆完全放弃他们的项目,这也是可以理解的。” Part1The Tragedy of the Commons The open source software ecosystem is a perfect example of the “tragedy of the commons.” 开源软件的生态系统,就是“公地悲剧”的一个完美例子。 And the tragedy is — when everyone uses, but no one contributes, that resource — whether it’s an overrun park or an open source project — eventually collapses from overuse and underinvestment. Everyone loves using free stuff, but everyone expects someone else to take care of it. 这个悲剧就是---当一种资源,无论是一个超限的公园还是一个开源项目,所有人都在使用而没有人贡献之时,最终都会因为过度使用和投入不足而崩溃坍塌。 This approach can save you money in the short term, but it can become a fatal flaw over time. Especially since open source software is everywhere, running everything. 这种方式可以在短期内为你节省资金,但随着时间的推移,它可能会变成项目里致命的缺陷。 Linux, for example, the open source operating system, runs on 96% of the world’s top 1 million servers, and 90% of all cloud infrastructure is on Linux. Not to mention that 85% of all smartphones in the world run Linux, in the form of the Android OS. 拿Linux来说,这个开源操作系统在全球前100万台服务器中运行率在96%以上,且这些服务器90%的云基础设施也都在Linux上。更不用说世界上85%的智能手机都运行着Linux,即Android操作系统。 Then there’s Java, Apache, WordPress, Cassandra, Hadoop, MySQL, PHP, ElasticSearch, Kubernetes — the list of ubiquitous open source projects goes on and on. 还有Java, Apache, WordPress, Cassandra, Hadoop, MySQL, PHP, ElasticSearch, Kubernetes--这些常见开源项目的列表还在逐渐增加着。 Without open source, much of today’s technical infrastructure would immediately grind to a halt. 如果没有开源,今天的大部分技术基础设施的建设也将会戛然而止。 “It is a real problem,” said Danil Mikhailov, executive director at Data.org, a nonprofit backed by the Mastercard Center for Inclusive Growth and The Rockefeller Foundation that promotes the use of data science to tackle society’s greatest challenges. “这是一个很现实的问题”,Data.org的执行董事Danil Mikhailov说,该组织是由万事达包容性发展中心和洛克菲勒基金会支持,旨在促进使用数据科学来应对当今社会所面临的巨大挑战的非营利性组织。 While nearly all organizations use open source software, only a minority contribute to those projects. Forty-two percent of participants in a survey released in September by The New Stack, Linux Foundation Research, and the TODO Group said tthey contribute at least sometimes to open source projects. 虽然几乎所有组织都在使用着开源软件,但只有少数组织为这些项目作出了贡献。The New Stack、Linux Foundation Research 和 TODO Group 在 9 月发布的一项调查中,42% 的参与者表示,他们至少有时会为开源项目做出贡献。 The same study showed that only 36% of organizations train their engineers to contribute to open source. 而同一项研究表明,只有36%的组织会培训他们的工程师为开源作出贡献。 Individual companies should support projects that they use the most and are critical to their success, Mikhailov said: “If you use, you contribute.” 个体公司应该支持贡献这些他们使用最多且对他们成功至关重要的项目,Mikhailov认为:“如果你使用开源,你就应该为他做出属于你自己的贡献。” Part2OSPO Benefits:Less Tech Debt,Better Recruiting Participating in open source communities — especially when guided by an in-house open source program office (OSPO) — can help ensure the health of projects critical to your organization’s success, improve those projects’ security, and allow your engineers to have more impact in the projects’ development road map. 参与开源社区——特别是在内部开源项目办公室(OSPO)的指导下——不仅可以保证对组织成功至关重要项目的健康发展,还可以提高项目安全性,同时可以允许工程师在项目发展规划中起到更大的影响。 Say, for example, a company uses an open source tool and modifies it a little to make it better. If that improvement isn’t contributed back to the community, then the official version of the open source project will start to diverge from what the company is using 例如,如果一家公司使用了开源工具,并对其进行了一些调整使其变得更好。但如果这项改进没有反馈到开源社区,那么开源项目的正式版本就会一开始与该公司所使用的版本有所不同。 “You start to grow technical debt because when the original source changes and you’ve got a different version. Those differences grow rapidly, compounding daily. It doesn’t take long for you to be the proud user and maintainer of a one-of-a-kind open source project variant,” said Suzanne Ambiel, director, open source marketing and strategy at VMware. “当原始代码来源发生变化且你所使用的是不同的版本时,你的技术负债将越来越多。而这些差异是以天为单位迅速增长的。”VMware 开源营销和战略总监 Suzanne Ambiel 表示,“所以你很快就会变成一个开源项目里独一无二变体的‘自豪’用户和维护人员。” “The technical debt gets bigger and bigger and it gets very expensive for a company to manage.” “如果技术负债越来越多,那么公司的管理成本则会非常昂贵”。 Support for open source activity can also be a recruiting tool. “It’s really a talent magnet,” said Ambiel. “It’s one of the things that new hires look for.” 实际上对于开源活动的支持也变成了一种招聘途径。“这真是一块吸引人才的磁铁,”Ambiel说,“这也是新员工所寻求的“。 Some engineering managers might worry that open source contributions will detract from core product development, she said. Their rationale, she added, might run along the lines of, “I only have so much talent, and so many hours, and I need them to only work on things where I can measure and see the return on investment.” 她还提到,一些工程经理可能会对贡献开源而减损核心产品的开发的精力而感到担忧。她补充到,他们的理由有可能是这样的:“我只有有限的才华与时间,且我需要这些只做我认为可以度量且看到投资回报的事情。” But that attitude, she said, is shortsighted. Supporting employees who contribute to open source communities can build skills and develop talent, she said. 但她说,这是一种鼠目寸光的态度。支持开源社区并且作出贡献的员工,可以从中培养技能与增长才华。 Loris Degionni, chief technology officer and founder at Sysdig, a cloud security vendor, echoed this notion: “Finding employees who contribute to open source is a gold mine,” said. 云安全供应商 Sysdig 的首席技术官兼创始人 Loris Degionni 也赞同这一观点:“找出为开源做出贡献的员工无疑就找到一座金矿,”他说。 These employees are more capable of delivering features a company wants to use and merge them into community-supported standards, he said. And in a war for talent, companies that embrace open source are more attractive to developers. 他认为,这些参与开源的员工更具备公司想拥有的竞争力并将一些功能融入至社区所支持的标准中。且在人才争夺战中,拥抱开源的公司也更受到开发人员的青睐。 “Lastly, open source is driven by a community of technical experts you may not be able to hire,” he said. “When employees actively contribute and collaborate with these experts, they’ll be better informed of best practices and bring them back to your organization. “最后,开源项目是由你可能无法聘请的技术专家社区推动的”,他说,“当员工积极参与并于这些专家合作时,他们将能更好地深入这些最佳实践,并将这些收获带回到你的组织之中。” “You start to grow technical debt because when the original source changes and you’ve got a different version … It doesn’t take long for you to be the proud user and maintainer of a one-of-a-kind open source project variant.” —Suzanne Ambiel, director, open source marketing and strategy, VMware “当原始数据来源发生变化且你所使用的是不同的版本时,你的技术负债将越来越多...所以你很快就会变成一个开源项目里独一无二变体的”自豪“用户和维护人员。” — Suzanne Ambiel,VMware 开源营销和战略总监 “All of this should be rewarded — developers shouldn’t have to spend their free time honing their skills, as your company will quickly see benefits from their efforts.” “但是这一切终究不会白费--开发人员不应该把业余时间用在磨练他们的技能上,因为你的公司很快就会在他们的努力中看到好处。” An OSPO, Degionni suggested, can help achieve these goals, as well as help prioritize contributions and ensure collaboration. In addition, they can help provide governance that mirrors what companies would have for internally developed applications. Degionni认为,OSPO(开源计划办公室)可以帮助公司实现这些目标,以及帮助确定贡献的优先级并确保合作的进行。除此之外,他们也可以对公司内部开发应用程序方面的治理提供相关帮助。 “Members of the open source team are also in a position to be great internal evangelists for open source technologies, and act as bridges between the organization and the broader community,” he added. “开源团队的成员也可以成为开源技术的伟大内部布道师,并充当组织与更广泛社区之间的桥梁。”他补充道。 In the September survey from The New Stack, Linux Foundation Research and the TODO Group, nearly 53% of organizations with OSPOs said they saw more innovation as a result of having an OSPO, while almost 43% said they saw increased participation in external open source projects. 在 The New Stack、Linux Foundation Research 和 TODO Group 的 9 月调查中,近 53% 的拥有 OSPO的组织表示,由于拥有了OSPO,他们看到了更多创新,而近 43% 的组织表示,他们在外部开源项目的参与度上有所增加。 Part3More OSPO Benefits:A Business Edge Contributing to open source communities doesn’t just help the communities, but the companies that contribute to them, said Tom Hickman, chief innovation officer at ThreatX, a cybersecurity firm. 网络安全公司 ThreatX 的首席创新官 Tom Hickman 表示,为开源社区做出贡献,不仅有助于社区,还有助于为社区做出贡献的公司。 “Growing the community of developers around a project helps the code base, and attracts more developers,” he said. “It can become a virtuous circle.” “围绕一个项目而发展的开发人员社区,有助于代码库的形成,并吸引更多的开发人员参与”,他说,“这可以变成一个良性循环。” Also, companies that contribute to open source projects get twice the productive value from their use of open source than companies that don’t, according to research by Harvard Business School. 此外,根据哈佛商学院的研究,为开源项目作出贡献的公司从使用开源的项目中获得的生产价值,是不参与开源项目公司的两倍。 Many of the biggest companies in the world are contributing to open source, said Chris Aniszczyk, chief technology officer at Cloud Native Computing Foundation. He pointed to the Open Source Contributor Index as a reference for exactly just how much companies are doing. Cloud Native Computing Foundation 的首席技术官 Chris Aniszczyk 说,世界上许多巨头公司都为开源作出了贡献。他还提到,开源贡献者的指数是作为公司是否有所作为的参考。 The tech giants dominate the list: Google, Microsoft, Red Hat, Intel, IBM, Amazon, Facebook, VMware, GitHub and SAP are the top 10 contributors, in that order. But there are also a lot of end users on the top 100 list, said Aniszczyk, including Uber, the BBC, Orange, Netflix, and Square. 科技巨头占据了这份榜单的主导地位:谷歌、微软、红帽、英特尔、IBM、亚马逊、Facebook、VMware、GitHub 和 SAP 依次是排名前 10 的贡献者。但Aniszczyk 表示,但也有很多终端用户公司进入前 100 名,包括 Uber、BBC、Orange、Netflix 和 Square。 “We’ve always known working in upstream projects is not just the right thing to do —it’s the best approach to open source software development and the best way to deliver open source benefits to our customers,” he said. “It’s great to see that IT leaders recognize this as well.” “我们一直知道,在上游项目中工作不仅仅是关正确与否----它是开源软件开发的最佳方法,也是向客户提供开源福利的最佳方式“他说,“很高兴看到IT领导者们也认识到了这一点。” To contribute alongside these giants, companies need to have their own open source strategies, and having an open source program office can help. 为了和这些公司一起作出贡献,公司也需要有自己的开源策略,而拥有一个开源项目办公室则可以为其提供帮助。 “OSPOs provide a critical center of competency in a company when it comes to utilizing open source software,” he said. “在使用开源软件方面,OPSO为公司提供了一个至关重要的能力中心”他说。 It’s similar to the way that companies have security operations centers, he said. 这与公司拥有安全运营中心的方式类似,他说。 “Growing the community of developers around a project helps the code base, and attracts more developers. It can become a virtuous circle.” —Tom Hickman, chief innovation officer, ThreatX “围绕一个项目而发展的开发人员社区,有助于代码库的形成,并吸引更多的开发人员参与,这可以变成一个良性循环。” ——Tom Hickman,ThreatX 首席创新官 “If you don’t make the investment in a security team, you generally don’t expect your software to be secure or be able to respond to security incidents in a timely fashion,” he said. “如果你没有对安全团队进行相应投资,你通常是不会期望你的软件是安全的,也无法及时响应安全事件。”他说。 “The same logic applies to OSPOs and is why you see many leading companies out there such as Apple, Meta, Twitter, Goldman Sachs, Bloomberg, and Google all have OSPOs. They are ahead of the curve.” “同样的逻辑也适用于 OSPO,这就是为什么你会看到许多领先的公司,例如 Apple、Meta、Twitter、Goldman Sachs、Bloomberg 和 Google 都拥有 OSPO。他们走在了趋势的前面。” Support for open source activity within your organization can become a differentiator and marketing opportunity for software vendors. 而对组织内的开源活动的支持态度亦可成为软件供应商们的差异化原因与营销的机会。 According to a Red Hat survey released in February, 82% of IT leaders are more likely to select a vendor who contributes to the open source community. 根据Red Hat2月分发布的一项调查,82%的IT领导者更倾向于选择为开源社区作出贡献的软件供应商。 Respondents said that when vendors support open source communities they are more familiar with open source processes and are more effective if customers have technical challenges. 受访者表示,当供应商支持开源社区时,就表示着他们更熟悉开源的流程并且在客户遇到技术难题时会更加有效。 But it’s not just software vendors who benefit. 但收益的不仅仅是软件供应商们。 According to September’s survey by The New Stack, Linux Foundation Research, and the TODO Group, 57% of organizations with OSPOs use them to further strategic relationships and build partnerships. 根据 The New Stack、Linux Foundation Research 和 TODO Group 9 月份的调查,57% 拥有 OSPO 的组织将使用它们来进一步发展战略关系和建立合作伙伴关系。 Mark Hinkle started an open source program office back when he worked at Citrix a decade ago. He pointed out how having an OSPO in-house benefited the company. 十年前,Mark Hinkle 在 Citrix 工作时创办了一个开源计划办公室。他指出了在内部拥有一个 OSPO将如何使公司受益。 “For us the biggest job was to educate our employees who weren’t familiar with open source to get involved and be good community members,” he said. “We also provided guidance on how to make sure our IP didn’t enter projects without proper understanding and we made sure we didn’t incorporate open source that conflicted with our enterprise software licensing.” “对于我们来说,最大的工作是让不熟悉开源的员工学会并参与其中,成为优秀的社区成员”,他说,“我们还就如何确保我们的IP不会在没有正确理解的情况下进入项目的情况提供了指导,并确保我们没有与我们企业软件许可相冲突的开源项目合作。” The OSPO also helped Citrix identify strategic opportunities for the company to participate in open source projects and trade organizations like The Linux Foundation, he said. 他说,OSPO还帮助Citrix确定了公司参与开源项目和Linux基金会等贸易组织的战略机会。 Today, he’s the CEO and co-founder of TriggerMesh, a cloud native, open source integration platform. 如今,他是云原生开源集成平台 TriggerMesh 的首席执行官兼联合创始人。 There are some significant economic benefits to participating in the open source ecosystem, he said. 他说,参与开源系统对公司来说有着重大的经济效益。 “We participate in Knative to share the development of our underlying platform but we develop value-added services as part of our business,” he said. “By sharing the R and D for the platform, it gives us more resources to develop our own differentiated technology.” “我们参与Knative是为了分享我们基础底层平台的开发,但作为业务的一部分,我们也拥有相关的增值服务。”他说,“通过共享该平台的研发,这为我们提供了更多的资源来改进我们自己的差异化技术。” Part4How to Get Started in Open Source Sixty-three percent of companies in the September survey from The New Stack, Linux Foundation Research and the TODO Group said that having an OSPO was very or extremely critical to the success of their engineering or product teams, up from 54% in the previous annual study. 在 The New Stack、Linux Foundation Research 和 TODO Group 的 9 月份调查中,有 63% 的公司表示,拥有OSPO 对其工程或产品团队的成功至关重要,高于上一年度该项研究数据的 54%。 In particular, 77% said that their open source program had a positive impact on their software practices, such as improved code quality. 其中77% 的人表示他们的开源程序对他们的软件实践产生了积极影响,例如提高了代码质量。 But companies can’t always contribute to every single open source project that they use. 但公司也不可能总是为他们使用的每一个开源项目而花费精力。 “First, thin the herd a little bit,” advised VMware’s Ambiel. “首先,节流一下”,VMware 的 Ambiel 建议道。 Companies should look at the projects that make the most sense for their use cases. This is an area where an OSPO can help set priorities and ensure technical and strategic alignment. 公司应该关注投入使用中最有意义的项目。而这也是OSPO可以帮助确定优先事项并确保技术与战略一致性的领域。 Then, developers should go and check out the projects themselves. Projects typically offer online documentation, often with contributor guides, governance documents, and lists of open issues. 之后,开发人员应该自己去了解一下。项目通常提供相关在线文档,一般包含贡献着指南、治理文档和未解决问题列表。 “For the projects that rise to the top of your strategic list, introduce yourself — say hello,” she said. “Go to the Slack channel or the distribution list and ask where they need help. Maybe they don’t need help and everything is good. Or maybe they can use a new person to review code.” “对于那些上升到你的战略清单顶端的项目,你可以介绍一下自己----打个招呼”,她说。“然后转到Slack频道或者分发列表,询问他们需要帮助的地方。也许他们不需要帮助,一切完好;又或者他们也有可能使用新人来审查核验代码。” An open source program office can not only help make a business case for contributing to the open source community, Ambiel said, but can help companies do it in a way that’s safe, secure and sound. Ambiel 说,开源项目办公室不仅可以帮助制定为开源社区做出贡献的商业案例,还可以帮助公司以安全、可靠和健全的方式来做这件事。 “If I work for a company and want to contribute to open source, I don’t want to accidentally disclose, divulge or undermine any patents,” she said. “An OSPO helps you make smart choices.” “如果我为一家公司工作,并想为开源做出贡献,我不想意外披露、泄露或破坏任何专利,”她说。“而OSPO可以帮助您做出明智的选择。” An OSPO can also help provide leadership and the guiding philosophy about supporting open source, she said. “It can provide guidance, mentorship, coaching and best practices.” 她说,OSPO还可以在开源方面提供领导力和指导理念的支持。“它可以提供引领、指导、辅导和最佳实践的作用。” Commitment to support open source has to start at the top, said Anaïs Urlichs, developer advocate at Aqua Security. Aqua Security的开发人员倡导者Anaïs Urlichs则认为,支持开源的承诺必须从高层开始。 “Too often,” she said, “companies do not value investment into open source, so employees are not encouraged to contribute to it.” 她说,“公司在多数时候往往不重视对开源的投资,所以员工自然而然不被鼓励对此作出贡献。” In those cases, employees with a passion for open source end up contributing during their free time, which is not sustainable. 在这些情况下,员工对于开源的热情也会在空闲时间里对开源的建设而消散殆尽,这对于开源的发展来说是不可持续的。 “If companies rely on open source projects, it is important to make open source contributions part of an engineer’s work schedule,” she said. “Some companies define a time percentage that employees can contribute to open source as part of their normal workday.” “如果公司对开源项目依赖度高,那么将开源贡献纳入工程师的日程安排是很重要的,”她说。“一些公司定义了员工可以为开源建设的时间百分比,将其作为他们正常工作日的一部分。” The New Stack is a wholly owned subsidiary of Insight Partners, an investor in the following companies mentioned in this article: Sysdig, Aqua Security. The New Stack 是 Insight Partners 的全资子公司,Insight Partners 是本文提到的以下公司的投资者:Sysdig、Aqua Security。 相关阅读 | Related Reading 《开源合规指南(企业篇)》正式发布,为推动我国开源合规建设提供参考 “目标->用户->指标”——企业开源运营之道|瞰道@谭中意 开源之夏邀请函——仅限高校学子开启 开源社简介 开源社成立于 2014 年,是由志愿贡献于开源事业的个人成员,依 “贡献、共识、共治” 原则所组成,始终维持厂商中立、公益、非营利的特点,是最早以 “开源治理、国际接轨、社区发展、开源项目” 为使命的开源社区联合体。开源社积极与支持开源的社区、企业以及政府相关单位紧密合作,以 “立足中国、贡献全球” 为愿景,旨在共创健康可持续发展的开源生态,推动中国开源社区成为全球开源体系的积极参与及贡献者。 2017 年,开源社转型为完全由个人成员组成,参照 ASF 等国际顶级开源基金会的治理模式运作。近八年来,链接了数万名开源人,集聚了上千名社区成员及志愿者、海内外数百位讲师,合作了近百家赞助、媒体、社区伙伴。 本篇文章为转载内容。原文链接:https://blog.csdn.net/kaiyuanshe/article/details/124976824。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-05-03 09:19:23
273
转载
Dubbo
...。比如说,如果你没把JAVA_HOME环境变量设置对,Dubbo就找不到Java的藏身之处(也就是安装路径),这样一来,它就没法正常启动运行啦。 解决这个问题的方法非常简单,只需要在系统环境变量中添加JAVA_HOME即可。例如,在Windows系统中,可以在"我的电脑" -> "属性" -> "高级系统设置" -> "环境变量"中添加。 三、日志配置错误 日志配置错误也是导致Dubbo无法正常运行的一个重要原因。要是你日志的配置文件,比如说logback.xml,搞错了设定,那就等于给日志输出挖了个坑。这样一来,日志就无法顺畅地“说话”了,我们也就没法通过这些日志来摸清系统的运行状况,了解它到底是怎么干活儿的了。 解决这个问题的方法也很简单,只需要检查日志配置文件中的配置是否正确即可。比如,我们可以瞅瞅日志输出的目的地是不是设定对了,还有日志的详细程度级别是否也调得恰到好处,这些小细节都值得我们关注检查一下。 四、代码示例 为了更直观地理解环境配置问题和日志配置错误,下面给出一些代码示例。 首先,来看一下不正确的环境变量设置。假设我们在没有设置JAVA_HOME的情况下尝试启动Dubbo,那么就会出现以下错误: Exception in thread "main" java.lang.UnsatisfiedLinkError: no javassist in java.library.path at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867) at java.lang.Runtime.loadLibrary0(Runtime.java:870) at java.lang.System.loadLibrary(System.java:1122) at com.alibaba.dubbo.common.logger.LoggerFactory.getLogger(LoggerFactory.java:39) at com.alibaba.dubbo.common.logger.LoggerFactory.getLogger(LoggerFactory.java:51) at com.alibaba.dubbo.config.ApplicationConfig.(ApplicationConfig.java:114) at com.example.demo.DemoApplication.main(DemoApplication.java:12) Caused by: java.lang.ClassNotFoundException: javassist at java.net.URLClassLoader.findClass(URLClassLoader.java:382) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 6 more 可以看出,由于JAVA_HOME环境变量未设置,所以无法找到Java的安装路径,从而导致了这个错误。 接下来,来看一下不正确的日志配置。假设我们在日志配置文件中错误地指定了日志输出的目标位置,那么就会出现以下错误: 2022-03-08 15:29:54,742 ERROR [main] org.apache.log4j.ConsoleAppender - Error initializing ConsoleAppender appenders named [STDOUT] org.apache.log4j.AppenderSkeleton$InvalidAppenderException: No such appender 'STDOUT' in category [com.example.demo]. at org.apache.log4j.Category.forcedLog(Category.java:393) at org.apache.log4j.Category.access$100(Category.java:67) at org.apache.log4j.Category$AppenderAttachedObject.append(Category.java:839) at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:248) at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51) at org.apache.log4j.Category.callAppenders(Category.java:206) at org.apache.log4j.Category.debug(Category.java:267) at org.apache.log4j.Category.info(Category.java:294) at org.apache.log4j.Logger.info(Logger.java:465) at com.example.demo.DemoApplication.main(DemoApplication.java:16) 可以看出,由于日志配置文件中的配置错误,所以无法将日志输出到指定的位置,从而导致了这个错误。 五、总结 通过以上分析,我们可以看出,环境配置问题和日志配置错误都是非常严重的问题,如果不及时处理,就会导致Dubbo无法正常运行,从而影响我们的工作。所以呢,咱们得好好学习、掌握这些知识点,这样一来,在实际工作中碰到问题时,就能更有效率地避开陷阱,解决麻烦了。同时,我们也应该养成良好的编程习惯,比如定期检查环境变量和日志配置文件,确保它们的正确性。
2023-06-21 10:00:14
435
春暖花开-t
转载文章
...n.net/grd_java/article/details/113116244。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 网页右边,向下滑有目录索引,可以根据标题跳转到你想看的内容 如果右边没有就找找左边 上一节:JUC锁,一些面试题和源码讲解 1、引用 java引用共4种,强软弱虚 强引用:我们普通的new一个对象,就是强引用,只有它指向为空了,或者已经没用了,才会被回收 软引用:JVM内存不够了,就回收软引用 弱引用:只要碰见垃圾回收器(System.gc()),就被回收 虚引用:对象当被回收时,会将其放在队列中 1、软引用 / 软引用 软引用是用来描述一些还有用但并非必须的对象。 对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。 如果这次回收还没有足够的内存,才会抛出内存溢出异常。 -Xmx20M/import java.lang.ref.SoftReference;public class T02_SoftReference {public static void main(String[] args) {SoftReference<byte[]> m = new SoftReference<>(new byte[1024102410]);//创建软引用,分配10M//m = null;System.out.println(m.get());//获取System.gc();//垃圾回收try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(m.get());//再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉byte[] b = new byte[1024102415];System.out.println(m.get());} }//软引用非常适合缓存使用 2、弱引用 public class M {@Overrideprotected void finalize() throws Throwable {System.out.println("finalize");} } 上图中,tl对象强引用指向ThreadLocal,map中key弱引用指向ThreadLocal,当tl=null时,强引用消失,此时弱引用也将自动被回收,但是此时key=null,value指向10M这个就永远访问不到,既内存泄露 下图中,18行到20行为解决内存泄露问题的,那就是通过remove()将它消除了 / 弱引用遭到gc就会回收/import java.lang.ref.WeakReference;public class T03_WeakReference {public static void main(String[] args) {WeakReference<M> m = new WeakReference<>(new M());System.out.println(m.get());System.gc();System.out.println(m.get());ThreadLocal<M> tl = new ThreadLocal<>();tl.set(new M());tl.remove();} } 3、虚引用 虚引用 虚引用不是给开发人员用的,一般是给写JVM(java虚拟机,没有它java程序运行不了),Netty等技术大牛用的 虚引用,对象当被回收时,会将其放在队列中,此时我们监听到队列中有新值了,就知道有虚引用被回收了 此时我们要做相应的处理,虚引用指向的值,是无法直接get()获取的 虚引用使用场景 一般情况(其它情况暂时没什么用),虚引用指向堆外内存(直接被操作系统管理的内存),JVM无法对其回收 当虚引用对象被回收时,JVM的垃圾回收无法自动回收堆外内存, 但是此时,虚引用对象被回收,会将其放在队列中 操作人员,看到队列中有对象被回收,就进行相应操作,回收堆内存 如何回收堆外内存 C和C++有函数可以用 java现在也提供了Unsafe类可以操作堆外内存,具体请参考上一篇博客,总之,JDK1.8只能通过反射来用,JDK1.9以上可以通过new Unsafe对象来用 Unsafe类的方法有: copyMemory():直接访问内存 allocateMemory():直接分配内存,这就必须手动回收内存了 freeMemory():回收内存 下面是一个虚引用例子,自己看吧,懂得自然懂,现在看不懂的,先收藏或者保存上,以后回来看 / 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响, 也无法通过虚引用来获取一个对象的实例。 为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。 虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用活着弱引用关联着对象, 那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null, 弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。 jdk中直接内存的回收就用到虚引用,由于jvm自动内存管理的范围是堆内存, 而直接内存是在堆内存之外(其实是内存映射文件,自行去理解虚拟内存空间的相关概念), 所以直接内存的分配和回收都是有Unsafe类去操作,java在申请一块直接内存之后, 会在堆内存分配一个对象保存这个堆外内存的引用, 这个对象被垃圾收集器管理,一旦这个对象被回收, 相应的用户线程会收到通知并对直接内存进行清理工作。 事实上,虚引用有一个很重要的用途就是用来做堆外内存的释放, DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。/import java.lang.ref.PhantomReference;import java.lang.ref.Reference;import java.lang.ref.ReferenceQueue;import java.util.LinkedList;import java.util.List;public class T04_PhantomReference {private static final List<Object> LIST = new LinkedList<>();private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();public static void main(String[] args) {PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);new Thread(() -> {while (true) {LIST.add(new byte[1024 1024]);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();Thread.currentThread().interrupt();}System.out.println(phantomReference.get());} }).start();new Thread(() -> {while (true) {Reference<? extends M> poll = QUEUE.poll();if (poll != null) {System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);} }}).start();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();} }} 2、容器 1、发展历史(一定要了解) map容器你需要了解的历史 JDK早期,java提供了Vector和Hashtable两个容器,这两个容器,很多操作都加了锁Synchronized,对于某些不需要用锁的情况下,就显得十分影响性能,所以现在基本没人用这两个容器,但是面试经常问这两个容器里面的数据结构等内容 后来,出现了HashMap,此容器完全不加锁,是用的最多的容器 但是完全不加锁未免不完善,所以java提供了如下方式,将HashMap变为加锁的 //通过Collections.synchronizedMap(HashMap)方法,将其变为加锁Map集合,其中泛型随意,UUID只是举例。static Map<UUID, UUID> m = Collections.synchronizedMap(new HashMap<UUID, UUID>()); 通过阅读源码发现,上面方法将HashMap变为加锁,也是使用Synchronized,只是锁的内容更细,但并不比HashTable效率高多少 所以衍生除了新的容器ConcurrentHashMap ConcurrentHashMap 此容器,插入效率不如上面的,因为它做了各种判断和CAS,但是差距不是特别大 读取效率很高,100个线程同时访问,每个线程读取一百万次实测 Hashtable 39s ,SynchronizedHashMap 38s ,ConcurrentHashMap 1.7s 前两个将近40秒,ConcurrentHashMap只需要不到2s,由此可见此容器读取效率极高 2、为什么推荐使用Queue来做高并发 为什么推荐Queue(队列) Queue接口提供了很多针对多线程非常友好的API(offer ,peek和poll,其中BlockingQueue还添加了put和take可以阻塞),可以说专门为多线程高并发而创造的接口,所以一般我们使用Queue而不用List 以下代码分别使用链表LinkList和ConcurrentQueue,对比一下速度 LinkList用了5s多,ConcurrentQueue几乎瞬间完成 Concurrent接口就是专为多线程设计,多线程设计要多考虑Queue(高并发用)的使用,少使用List / 有N张火车票,每张票都有一个编号 同时有10个窗口对外售票 请写一个模拟程序 分析下面的程序可能会产生哪些问题? 重复销售?超量销售? 使用Vector或者Collections.synchronizedXXX 分析一下,这样能解决问题吗? 就算操作A和B都是同步的,但A和B组成的复合操作也未必是同步的,仍然需要自己进行同步 就像这个程序,判断size和进行remove必须是一整个的原子操作 @author 马士兵/import java.util.LinkedList;import java.util.List;import java.util.concurrent.TimeUnit;public class TicketSeller3 {static List<String> tickets = new LinkedList<>();static {for(int i=0; i<1000; i++) tickets.add("票 编号:" + i);}public static void main(String[] args) {for(int i=0; i<10; i++) {new Thread(()->{while(true) {synchronized(tickets) {if(tickets.size() <= 0) break;try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("销售了--" + tickets.remove(0));} }}).start();} }} 队列 import java.util.Queue;import java.util.concurrent.ConcurrentLinkedQueue;public class TicketSeller4 {static Queue<String> tickets = new ConcurrentLinkedQueue<>();static {for(int i=0; i<1000; i++) tickets.add("票 编号:" + i);}public static void main(String[] args) {for(int i=0; i<10; i++) {new Thread(()->{while(true) {String s = tickets.poll();if(s == null) break;else System.out.println("销售了--" + s);} }).start();} }} 3、多线程常用容器 1、ConcurrentHashMap(无序)和ConcurrentSkipListMap(有序,链表,使用跳表数据结构,让查询更快) 跳表:http://blog.csdn.net/sunxianghuang/article/details/52221913 import java.util.;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentSkipListMap;import java.util.concurrent.CountDownLatch;public class T01_ConcurrentMap {public static void main(String[] args) {Map<String, String> map = new ConcurrentHashMap<>();//Map<String, String> map = new ConcurrentSkipListMap<>(); //高并发并且排序//Map<String, String> map = new Hashtable<>();//Map<String, String> map = new HashMap<>(); //Collections.synchronizedXXX//TreeMapRandom r = new Random();Thread[] ths = new Thread[100];CountDownLatch latch = new CountDownLatch(ths.length);long start = System.currentTimeMillis();for(int i=0; i<ths.length; i++) {ths[i] = new Thread(()->{for(int j=0; j<10000; j++) map.put("a" + r.nextInt(100000), "a" + r.nextInt(100000));latch.countDown();});}Arrays.asList(ths).forEach(t->t.start());try {latch.await();} catch (InterruptedException e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println(end - start);System.out.println(map.size());} } 2、CopyOnWriteList(写时复制)和CopyOnWriteSet 适用于,高并发是,读的多,写的少的情况 当我们写的时候,将容器复制,让写线程去复制的线程写(写的时候加锁) 而读线程依旧去读旧的(读的时候不加锁) 当写完,将对象指向复制后的已经写完的容器,原来容器销毁 大大提高读的效率 / 写时复制容器 copy on write 多线程环境下,写时效率低,读时效率高 适合写少读多的环境 @author 马士兵/import java.util.ArrayList;import java.util.Arrays;import java.util.List;import java.util.Random;import java.util.Vector;import java.util.concurrent.CopyOnWriteArrayList;public class T02_CopyOnWriteList {public static void main(String[] args) {List<String> lists = //new ArrayList<>(); //这个会出并发问题!//new Vector();new CopyOnWriteArrayList<>();Random r = new Random();Thread[] ths = new Thread[100];for(int i=0; i<ths.length; i++) {Runnable task = new Runnable() {@Overridepublic void run() {for(int i=0; i<1000; i++) lists.add("a" + r.nextInt(10000));} };ths[i] = new Thread(task);}runAndComputeTime(ths);System.out.println(lists.size());}static void runAndComputeTime(Thread[] ths) {long s1 = System.currentTimeMillis();Arrays.asList(ths).forEach(t->t.start());Arrays.asList(ths).forEach(t->{try {t.join();} catch (InterruptedException e) {e.printStackTrace();} });long s2 = System.currentTimeMillis();System.out.println(s2 - s1);} } 3、synchronizedList和ConcurrentLinkedQueue package com.mashibing.juc.c_025;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.Queue;import java.util.concurrent.ConcurrentLinkedQueue;public class T04_ConcurrentQueue {public static void main(String[] args) {List<String> strsList = new ArrayList<>();List<String> strsSync = Collections.synchronizedList(strsList);//加锁ListQueue<String> strs = new ConcurrentLinkedQueue<>();//Concurrent链表队列,就是读快for(int i=0; i<10; i++) {strs.offer("a" + i); //add添加,但是不同点是,此方法会返回一个布尔值}System.out.println(strs);System.out.println(strs.size());System.out.println(strs.poll());//取出,取完后将元素去除System.out.println(strs.size());System.out.println(strs.peek());//取出,但是不会将元素从队列删除System.out.println(strs.size());//双端队列Deque} } 4、LinkedBlockingQueue 链表阻塞队列(无界链表,可以一直装东西,直到内存满(其实,也不是无限,其长度Integer.MaxValue就是上限,毕竟最大就这么大)) 主要体现在put和take方法,put添加的时候,如果队列满了,就阻塞当前线程,直到队列有空位,继续插入。take方法取的时候,如果没有值,就阻塞,等有值了,立马去取 import java.util.Random;import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.TimeUnit;public class T05_LinkedBlockingQueue {static BlockingQueue<String> strs = new LinkedBlockingQueue<>();static Random r = new Random();public static void main(String[] args) {new Thread(() -> {for (int i = 0; i < 100; i++) {try {strs.put("a" + i); //如果满了,当前线程就会等待(实现阻塞),等多会有空位,将值插入TimeUnit.MILLISECONDS.sleep(r.nextInt(1000));} catch (InterruptedException e) {e.printStackTrace();} }}, "p1").start();for (int i = 0; i < 5; i++) {new Thread(() -> {for (;;) {try {System.out.println(Thread.currentThread().getName() + " take -" + strs.take()); //取内容,如果空了,当前线程就会等待(实现阻塞)} catch (InterruptedException e) {e.printStackTrace();} }}, "c" + i).start();} }} 5、ArrayBlockingQueue 有界阻塞队列(因为Array需要指定长度) import java.util.Random;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;import java.util.concurrent.TimeUnit;public class T06_ArrayBlockingQueue {static BlockingQueue<String> strs = new ArrayBlockingQueue<>(10);static Random r = new Random();public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {strs.put("a" + i);}//strs.put("aaa"); //满了就会等待,程序阻塞//strs.add("aaa");//strs.offer("aaa");strs.offer("aaa", 1, TimeUnit.SECONDS);System.out.println(strs);} } 6、特殊的阻塞队列1:DelayQueue 延时队列(按时间进行调度,就是隔多长时间运行,谁隔的少,谁先) 以下例子中,我们添加线程到队列顺序为t12345,正常情况下,会按照顺序运行,但是这里有了延时时间,也就是时间越短,越先执行 步骤很简单,拿到延时队列 指定构造方法 继承 implements Delayed 重写 compareTo和getDelay import java.util.Calendar;import java.util.Random;import java.util.concurrent.BlockingQueue;import java.util.concurrent.DelayQueue;import java.util.concurrent.Delayed;import java.util.concurrent.TimeUnit;public class T07_DelayQueue {static BlockingQueue<MyTask> tasks = new DelayQueue<>();static Random r = new Random();static class MyTask implements Delayed {String name;long runningTime;MyTask(String name, long rt) {this.name = name;this.runningTime = rt;}@Overridepublic int compareTo(Delayed o) {if(this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS))return -1;else if(this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) return 1;else return 0;}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}@Overridepublic String toString() {return name + " " + runningTime;} }public static void main(String[] args) throws InterruptedException {long now = System.currentTimeMillis();MyTask t1 = new MyTask("t1", now + 1000);MyTask t2 = new MyTask("t2", now + 2000);MyTask t3 = new MyTask("t3", now + 1500);MyTask t4 = new MyTask("t4", now + 2500);MyTask t5 = new MyTask("t5", now + 500);tasks.put(t1);tasks.put(t2);tasks.put(t3);tasks.put(t4);tasks.put(t5);System.out.println(tasks);for(int i=0; i<5; i++) {System.out.println(tasks.take());//获取的是toString方法返回值} }} 7、特殊的阻塞队列2:PriorityQueque 优先队列(二叉树算法,就是排序) import java.util.PriorityQueue;public class T07_01_PriorityQueque {public static void main(String[] args) {PriorityQueue<String> q = new PriorityQueue<>();q.add("c");q.add("e");q.add("a");q.add("d");q.add("z");for (int i = 0; i < 5; i++) {System.out.println(q.poll());} }} 8、特殊的阻塞队列3:SynchronusQueue 同步队列(线程池用处非常大) 此队列容量为0,当插入元素时,必须同时有个线程往外取 就是说,当你往这个队列里面插入一个元素,它就拿着这个元素站着(阻塞),直到有个取元素的线程来,它就把元素交给它 就是用来同步数据的,也就是线程间交互数据用的一个特殊队列 package com.mashibing.juc.c_025;import java.util.concurrent.BlockingQueue;import java.util.concurrent.SynchronousQueue;public class T08_SynchronusQueue { //容量为0public static void main(String[] args) throws InterruptedException {BlockingQueue<String> strs = new SynchronousQueue<>();new Thread(()->{//这个线程就是消费者,来取值try {System.out.println(strs.take());//和同步队列要值} catch (InterruptedException e) {e.printStackTrace();} }).start();strs.put("aaa"); //阻塞等待消费者消费,就拿着aaa站着,等线程来取//strs.put("bbb");//strs.add("aaa");System.out.println(strs.size());} } 9、特殊的阻塞队列4:TransferQueue 传递队列 此队列加入了一个方法transfer()用来向队列添加元素 但是和put()方法不同的是,put添加完元素就走了 而这个方法,添加完自己就阻塞了,直到有人将这个元素取走,它才继续工作(省去我们手动阻塞) import java.util.concurrent.LinkedTransferQueue;public class T09_TransferQueue {public static void main(String[] args) throws InterruptedException {LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();new Thread(() -> {try {System.out.println(strs.take());} catch (InterruptedException e) {e.printStackTrace();} }).start();strs.transfer("aaa");//放东西到队列,同时阻塞等待消费者线程,取走元素//strs.put("aaa");//如果用put就和普通队列一样,放完东西就走了/new Thread(() -> {try {System.out.println(strs.take());} catch (InterruptedException e) {e.printStackTrace();} }).start();/} } 3、线程池 线程池 由于单独创建线程,十分影响效率,而且无法对线程集中管理,一旦疏落,可能线程无限执行,浪费资源 线程池就是一个存储线程的游泳池,而每个线程就是池子里面的赛道 池子里的线程不执行任何任务,只是提供一个资源 而谁提交了任务,比如我想来游泳,那么池子就给你一个赛道,让你游泳 比如它想练憋气,那么给它一个赛道练憋气 当他们用完,走了,那么后面其它人再过来继续用 这就是线程池,始终只有这几个线程,不做实现,而是借用这几个线程的用户,自己掌控用这些线程资源做什么(提交任务给线程,线程空闲就帮他们完成任务) 线程池的两种类型(两类,不是两个) ThreadPoolExecutor(简称TPE) ForkJoinPool(分解汇总任务(将任务细化,最后汇总结果),少量线程执行多个任务(子任务,TPE做不到先执行子任务),CPU密集型) Executors(注意这后面有s) 它可以说是线程池工厂类,我们一般通过它创建线程池,并且它为我们封装了线程 1、常用类 Executor ExecutorService 扩展了execute方法,具有一个返回值 规定了异步执行机制,提供了一些执行器方法,比如shutdown()关闭等 但是它不知道执行器中的线程何时执行完 Callable 对Runnable进行了扩展,实现Callable的调用,可以有返回值,表示线程的状态 但是无法返回线程执行结果 Future 获得未来线程执行结果 由此,我们可以得知线程池基本的一个使用步骤 其中service.submit():为异步提交,也就是说,主线程该干嘛干嘛,我是异步执行的,和同步不一样(当前线程执行完,主线程才能继续执行,叫同步) futuer.get():获取结果集结果,此时因为异步,主线程执行到这里,结果集可能还没封装好,所以此时如果没有值,就阻塞,直到结果集出来 public static void main(String[] args) throws ExecutionException, InterruptedException {Callable<String> c = new Callable() {@Overridepublic String call() throws Exception {return "Hello Callable";} };ExecutorService service = Executors.newCachedThreadPool();Future<String> future = service.submit(c); //异步System.out.println(future.get());//阻塞service.shutdown();} 2、FutureTask 可充当任务的结果集 上面我们介绍Future是用来得到任务的执行结果的 而FutureTask,可以当做一个任务用,并且返回任务的结果,也就是可以跑线程,然后还可以得到线程结果 public static void main(String[] args) throws InterruptedException, ExecutionException {FutureTask<Integer> task = new FutureTask<>(()->{TimeUnit.MILLISECONDS.sleep(500);return 1000;}); //new Callable () { Integer call();}new Thread(task).start();System.out.println(task.get()); //阻塞} 3、CompletableFuture 非常灵活的任务结果集 一个非常灵活的结果集 他可以将很多执行不同任务的线程的结果进行汇总 比如一个网站,它可以启动多个线程去各大电商网站,比如淘宝,京东,收集某些或某一个商品的价格 最后,将获取的数据进行整合封装 最终,客户就可以通过此网站,获取某类商品在各网站的价格信息 / 假设你能够提供一个服务 这个服务查询各大电商网站同一类产品的价格并汇总展示 @author 马士兵 http://mashibing.com/import java.io.IOException;import java.util.Random;import java.util.concurrent.CompletableFuture;import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit;public class T06_01_CompletableFuture {public static void main(String[] args) throws ExecutionException, InterruptedException {long start, end;/start = System.currentTimeMillis();priceOfTM();priceOfTB();priceOfJD();end = System.currentTimeMillis();System.out.println("use serial method call! " + (end - start));/start = System.currentTimeMillis();CompletableFuture<Double> futureTM = CompletableFuture.supplyAsync(()->priceOfTM());CompletableFuture<Double> futureTB = CompletableFuture.supplyAsync(()->priceOfTB());CompletableFuture<Double> futureJD = CompletableFuture.supplyAsync(()->priceOfJD());CompletableFuture.allOf(futureTM, futureTB, futureJD).join();//当所有结果集都获取到,才汇总阻塞CompletableFuture.supplyAsync(()->priceOfTM()).thenApply(String::valueOf).thenApply(str-> "price " + str).thenAccept(System.out::println);end = System.currentTimeMillis();System.out.println("use completable future! " + (end - start));try {System.in.read();} catch (IOException e) {e.printStackTrace();} }private static double priceOfTM() {delay();return 1.00;}private static double priceOfTB() {delay();return 2.00;}private static double priceOfJD() {delay();return 3.00;}/private static double priceOfAmazon() {delay();throw new RuntimeException("product not exist!");}/private static void delay() {int time = new Random().nextInt(500);try {TimeUnit.MILLISECONDS.sleep(time);} catch (InterruptedException e) {e.printStackTrace();}System.out.printf("After %s sleep!\n", time);} } 4、TPE型线程池1:ThreadPoolExecutor 原理及其参数 线程池由两个集合组成,一个集合存储线程,一个集合存储任务 存储线程:可以规定大小,最多可以有多少个,以及指定核心线程数量(不会被回收) 任务队列:存储任务 细节:初始线程池没有线程,当有一个任务来,线程池起一个线程,又有一个任务来,再起一个线程,直到达到核心线程数量 核心线程数量达到时,新来的任务将存储到任务队列中等待核心线程处理完成,直到任务队列也满了 当任务队列满了,此时再次启动一个线程(非核心线程,一旦空闲,达到指定时间将会消失),直到达到线程最大数量 当线程容器和任务容器都满了,又来了线程,将会执行拒绝策略 上面的细节涉及的所有步骤内容,均由创建线程池的参数执行 下面是ThreadPoolExecutor构造方法参数的源码注释 / 用给定的初始值,创建一个新的线程池 @param corePoolSize 核心线程数量 @param maximumPoolSize 最大线程数量 @param keepAliveTime 当线程数大于核心线程数量时,空闲的线程可生存的时间 @param unit 时间单位 @param workQueue 任务队列,只能包含由execute提交的Runnable任务 @param threadFactory 工厂,用于创建线程给线程池调度的工厂,可以自定义 @param handler 拒绝策略(可以自定义,JDK默认提供4种),当线程边界和队列容量已经满了,新来线程被阻塞时使用的处理程序/public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) JDK提供的4种拒绝策略,不常用,一般都是自己定义拒绝策略 Abort:抛异常 Discard:扔掉,不抛异常 DiscardOldest:扔掉排队时间最久的(将队列中排队时间最久的扔掉,然后让新来的进来) CallerRuns:调用者处理任务(谁通过execute方法提交任务,谁处理) ThreadPoolExecutor继承关系 继承关系:ThreadPoolExecutor->AbstractExectorService类->ExectorService接口->Exector接口 Executors(注意这后面有s) 它可以说是线程池工厂类,我们一般通过它创建线程池,并且它为我们封装了线程 看看下面创建线程池,哪里用到了它 使用实例 import java.io.IOException;import java.util.concurrent.;public class T05_00_HelloThreadPool {static class Task implements Runnable {private int i;public Task(int i) {this.i = i;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " Task " + i);try {System.in.read();} catch (IOException e) {e.printStackTrace();} }@Overridepublic String toString() {return "Task{" +"i=" + i +'}';} }public static void main(String[] args) {ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4,60, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());//创建线程池,核心2个,最大4个,空闲线程存活时间60s,任务队列容量4,使用默认线程工程,创建线程。拒绝策略是JDK提供的for (int i = 0; i < 8; i++) {tpe.execute(new Task(i));//供提交8次任务}System.out.println(tpe.getQueue());//查看任务队列tpe.execute(new Task(100));//提交新的任务System.out.println(tpe.getQueue());tpe.shutdown();//关闭线程池} } 5、TPE型线程池2:SingleThreadPool 单例线程池(只有一个线程) 为什么有单例线程池 有任务队列,有线程池管理机制 Executors(注意这后面有s) 它可以说是线程池工厂类,我们一般通过它创建线程池,并且它为我们封装了线程 看看下面哪里用到了它 /创建单例线程池,扔5个任务进去,查看输出结果,看看有几个线程执行任务/import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class T07_SingleThreadPool {public static void main(String[] args) {ExecutorService service = Executors.newSingleThreadExecutor();for(int i=0; i<5; i++) {final int j = i;service.execute(()->{System.out.println(j + " " + Thread.currentThread().getName());});} }} 6、TPE型线程池3:CachedPool 缓存,存储线程池 此线程池没有核心线程,来一个任务启动一个线程(最多Integer.MaxValue,不会放在任务队列,因为任务队列容量为0),每个线程空闲后,只能活60s 实例 import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class T07_SingleThreadPool {public static void main(String[] args) {ExecutorService service = Executors.newSingleThreadExecutor();//通过Executors获取池子for(int i=0; i<5; i++) {final int j = i;service.execute(()->{//提交任务System.out.println(j + " " + Thread.currentThread().getName());});}service.shutdown();} } 7、TPE型线程池4:FixedThreadPool 固定线程池 此线次池,用于创建一个固定线程数量的线程池,不会回收 实例 import java.util.ArrayList;import java.util.List;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class T09_FixedThreadPool {public static void main(String[] args) throws InterruptedException, ExecutionException {//并发执行long start = System.currentTimeMillis();getPrime(1, 200000); long end = System.currentTimeMillis();System.out.println(end - start);//输出并发执行耗费时间final int cpuCoreNum = 4;//并行执行ExecutorService service = Executors.newFixedThreadPool(cpuCoreNum);MyTask t1 = new MyTask(1, 80000); //1-5 5-10 10-15 15-20MyTask t2 = new MyTask(80001, 130000);MyTask t3 = new MyTask(130001, 170000);MyTask t4 = new MyTask(170001, 200000);Future<List<Integer>> f1 = service.submit(t1);Future<List<Integer>> f2 = service.submit(t2);Future<List<Integer>> f3 = service.submit(t3);Future<List<Integer>> f4 = service.submit(t4);start = System.currentTimeMillis();f1.get();f2.get();f3.get();f4.get();end = System.currentTimeMillis();System.out.println(end - start);//输出并行耗费时间}static class MyTask implements Callable<List<Integer>> {int startPos, endPos;MyTask(int s, int e) {this.startPos = s;this.endPos = e;}@Overridepublic List<Integer> call() throws Exception {List<Integer> r = getPrime(startPos, endPos);return r;} }static boolean isPrime(int num) {for(int i=2; i<=num/2; i++) {if(num % i == 0) return false;}return true;}static List<Integer> getPrime(int start, int end) {List<Integer> results = new ArrayList<>();for(int i=start; i<=end; i++) {if(isPrime(i)) results.add(i);}return results;} } 8、TPE型线程池5:ScheduledPool 预定,延时线程池 根据延时时间(隔多长时间后运行),排序,哪个线程先执行,用户只需要指定核心线程数量 此线程池返回的池对象,和提交任务方法都不一样,比较涉及到时间 import java.util.Random;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;public class T10_ScheduledPool {public static void main(String[] args) {ScheduledExecutorService service = Executors.newScheduledThreadPool(4);service.scheduleAtFixedRate(()->{//提交延时任务try {TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName());}, 0, 500, TimeUnit.MILLISECONDS);//指定延时时间和单位,第一个任务延时0毫秒,之后的任务,延时500毫秒} } 9、手写拒绝策略小例子 import java.util.concurrent.;public class T14_MyRejectedHandler {public static void main(String[] args) {ExecutorService service = new ThreadPoolExecutor(4, 4,0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(6),Executors.defaultThreadFactory(),new MyHandler());//将手写拒绝策略传入}static class MyHandler implements RejectedExecutionHandler {//1、继承RejectedExecutionHandler@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {//2、重写方法//log("r rejected")//伪代码,表示通过log4j.log()报一下日志,拒绝的时间,线程名//save r kafka mysql redis//可以尝试保存队列//try 3 times //可以尝试几次,比如3次,重新去抢队列,3次还不行就丢弃if(executor.getQueue().size() < 10000) {//尝试条件,如果size>10000了,就执行拒绝策略//try put again();//如果小于10000,尝试将其放到队列中} }} } 10、ForkJoinPool线程池1:ForkJoinPool 前面我们讲过线程分为两大类,TPE和FJP ForkJoinPool(分解汇总任务(将任务细化,最后汇总结果),少量线程执行多个任务(子任务,TPE做不到先执行子任务),CPU密集型) 适合将大任务切分成多个小任务运行 两个方法,fork():分子任务,将子任务分配到线程池中 join():当前任务的计算结果,如果有子任务,等子任务结果返回后再汇总 下面实例实现,一百万个随机数求和,由两种方法实现,一种ForkJoinPool分任务并行,一种使用单线程做 import java.io.IOException;import java.util.Arrays;import java.util.Random;import java.util.concurrent.ForkJoinPool;import java.util.concurrent.RecursiveAction;import java.util.concurrent.RecursiveTask;public class T12_ForkJoinPool {//1000000个随机数求和static int[] nums = new int[1000000];//一堆数static final int MAX_NUM = 50000;//分任务时,每个任务的操作量不能多于50000个,否则就继续细分static Random r = new Random();//使用随机数将数组初始化static {for(int i=0; i<nums.length; i++) {nums[i] = r.nextInt(100);}System.out.println("---" + Arrays.stream(nums).sum()); //stream api 单线程就这么做,一个一个加}//分任务,需要继承,可以继承RecursiveAction(不需要返回值,一般用在不需要返回值的场景)或//RecursiveTask(需要返回值,我们用这个,因为我们需要最后获取求和结果)两个更好实现的类,//他俩继承与ForkJoinTaskstatic class AddTaskRet extends RecursiveTask<Long> {private static final long serialVersionUID = 1L;int start, end;AddTaskRet(int s, int e) {start = s;end = e;}@Overrideprotected Long compute() {if(end-start <= MAX_NUM) {//如果任务操作数小于规定的最大操作数,就进行运算,long sum = 0L;for(int i=start; i<end; i++) sum += nums[i];return sum;//返回结果} //如果分配的操作数大于规定,就继续细分(简单的重中点分,两半)int middle = start + (end-start)/2;//获取中间值AddTaskRet subTask1 = new AddTaskRet(start, middle);//传入起始值和中间值,表示一个子任务AddTaskRet subTask2 = new AddTaskRet(middle, end);//中间值和结尾值,表示一个子任务subTask1.fork();//分任务subTask2.fork();//分任务return subTask1.join() + subTask2.join();//最后返回结果汇总} }public static void main(String[] args) throws IOException {/ForkJoinPool fjp = new ForkJoinPool();AddTask task = new AddTask(0, nums.length);fjp.execute(task);/ForkJoinPool fjp = new ForkJoinPool();//创建线程池AddTaskRet task = new AddTaskRet(0, nums.length);//创建任务fjp.execute(task);//传入任务long result = task.join();//返回汇总结果System.out.println(result);//System.in.read();} } 11、ForkJoinPool线程池2:WorkStealingPool 任务偷取线程池 原来的线程池,都是有一个任务队列,而这个不同,它给每个线程都分配了一个任务队列 当某一个线程的任务队列没有任务,并且自己空闲,它就去其它线程的任务队列中偷任务,所以叫任务偷取线程池 细节:当线程自己从自己的任务队列拿任务时,不需要加锁,但是偷任务时,因为有两个线程,可能发生同步问题,需要加锁 此线程继承FJP 实例 import java.io.IOException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;public class T11_WorkStealingPool {public static void main(String[] args) throws IOException {ExecutorService service = Executors.newWorkStealingPool();System.out.println(Runtime.getRuntime().availableProcessors());service.execute(new R(1000));service.execute(new R(2000));service.execute(new R(2000));service.execute(new R(2000)); //daemonservice.execute(new R(2000));//由于产生的是精灵线程(守护线程、后台线程),主线程不阻塞的话,看不到输出System.in.read(); }static class R implements Runnable {int time;R(int t) {this.time = t;}@Overridepublic void run() {try {TimeUnit.MILLISECONDS.sleep(time);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(time + " " + Thread.currentThread().getName());} }} 12、流式API:ParallelStreamAPI 不懂的请参考:https://blog.csdn.net/grd_java/article/details/110265219 实例 import java.util.ArrayList;import java.util.List;import java.util.Random;public class T13_ParallelStreamAPI {public static void main(String[] args) {List<Integer> nums = new ArrayList<>();Random r = new Random();for(int i=0; i<10000; i++) nums.add(1000000 + r.nextInt(1000000));//System.out.println(nums);long start = System.currentTimeMillis();nums.forEach(v->isPrime(v));long end = System.currentTimeMillis();System.out.println(end - start);//使用parallel stream apistart = System.currentTimeMillis();nums.parallelStream().forEach(T13_ParallelStreamAPI::isPrime);//并行流,将任务切分成子任务执行end = System.currentTimeMillis();System.out.println(end - start);}static boolean isPrime(int num) {for(int i=2; i<=num/2; i++) {if(num % i == 0) return false;}return true;} } 13、总结 总结 Callable相当于一Runnable但是它有返回值 Future:存储执行完产生的结果 FutureTask 相当于Future+Runnable,既可以执行任务,又能获取任务执行的Future结果 CompletableFuture 可以多任务异步,并对多任务控制,整合任务结果,细化完美,比如可以一个任务完成就可以整合结果,也可以所有任务完成才整合结果 4、ThreadPoolExecutor源码解析 依然只讲重点,实际还需要大家按照上篇博客中看源码的方式来看 1、常用变量的解释 // 1. ctl,可以看做一个int类型的数字,高3位表示线程池状态,低29位表示worker数量private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));// 2. COUNT_BITS,Integer.SIZE为32,所以COUNT_BITS为29private static final int COUNT_BITS = Integer.SIZE - 3;// 3. CAPACITY,线程池允许的最大线程数。1左移29位,然后减1,即为 2^29 - 1private static final int CAPACITY = (1 << COUNT_BITS) - 1;// runState is stored in the high-order bits// 4. 线程池有5种状态,按大小排序如下:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATEDprivate static final int RUNNING = -1 << COUNT_BITS;private static final int SHUTDOWN = 0 << COUNT_BITS;private static final int STOP = 1 << COUNT_BITS;private static final int TIDYING = 2 << COUNT_BITS;private static final int TERMINATED = 3 << COUNT_BITS;// Packing and unpacking ctl// 5. runStateOf(),获取线程池状态,通过按位与操作,低29位将全部变成0private static int runStateOf(int c) { return c & ~CAPACITY; }// 6. workerCountOf(),获取线程池worker数量,通过按位与操作,高3位将全部变成0private static int workerCountOf(int c) { return c & CAPACITY; }// 7. ctlOf(),根据线程池状态和线程池worker数量,生成ctl值private static int ctlOf(int rs, int wc) { return rs | wc; }/ Bit field accessors that don't require unpacking ctl. These depend on the bit layout and on workerCount being never negative./// 8. runStateLessThan(),线程池状态小于xxprivate static boolean runStateLessThan(int c, int s) {return c < s;}// 9. runStateAtLeast(),线程池状态大于等于xxprivate static boolean runStateAtLeast(int c, int s) {return c >= s;} 2、构造方法 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {// 基本类型参数校验if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();// 空指针校验if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;// 根据传入参数unit和keepAliveTime,将存活时间转换为纳秒存到变量keepAliveTime 中this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;} 3、提交执行task的过程 public void execute(Runnable command) {if (command == null)throw new NullPointerException();/ Proceed in 3 steps: 1. If fewer than corePoolSize threads are running, try to start a new thread with the given command as its first task. The call to addWorker atomically checks runState and workerCount, and so prevents false alarms that would add threads when it shouldn't, by returning false. 2. If a task can be successfully queued, then we still need to double-check whether we should have added a thread (because existing ones died since last checking) or that the pool shut down since entry into this method. So we recheck state and if necessary roll back the enqueuing if stopped, or start a new thread if there are none. 3. If we cannot queue task, then we try to add a new thread. If it fails, we know we are shut down or saturated and so reject the task./int c = ctl.get();// worker数量比核心线程数小,直接创建worker执行任务if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}// worker数量超过核心线程数,任务直接进入队列if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();// 线程池状态不是RUNNING状态,说明执行过shutdown命令,需要对新加入的任务执行reject()操作。// 这儿为什么需要recheck,是因为任务入队列前后,线程池的状态可能会发生变化。if (! isRunning(recheck) && remove(command))reject(command);// 这儿为什么需要判断0值,主要是在线程池构造方法中,核心线程数允许为0else if (workerCountOf(recheck) == 0)addWorker(null, false);}// 如果线程池不是运行状态,或者任务进入队列失败,则尝试创建worker执行任务。// 这儿有3点需要注意:// 1. 线程池不是运行状态时,addWorker内部会判断线程池状态// 2. addWorker第2个参数表示是否创建核心线程// 3. addWorker返回false,则说明任务执行失败,需要执行reject操作else if (!addWorker(command, false))reject(command);} 4、addworker源码解析 private boolean addWorker(Runnable firstTask, boolean core) {retry:// 外层自旋for (;;) {int c = ctl.get();int rs = runStateOf(c);// 这个条件写得比较难懂,我对其进行了调整,和下面的条件等价// (rs > SHUTDOWN) || // (rs == SHUTDOWN && firstTask != null) || // (rs == SHUTDOWN && workQueue.isEmpty())// 1. 线程池状态大于SHUTDOWN时,直接返回false// 2. 线程池状态等于SHUTDOWN,且firstTask不为null,直接返回false// 3. 线程池状态等于SHUTDOWN,且队列为空,直接返回false// Check if queue empty only if necessary.if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;// 内层自旋for (;;) {int wc = workerCountOf(c);// worker数量超过容量,直接返回falseif (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;// 使用CAS的方式增加worker数量。// 若增加成功,则直接跳出外层循环进入到第二部分if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get(); // Re-read ctl// 线程池状态发生变化,对外层循环进行自旋if (runStateOf(c) != rs)continue retry;// 其他情况,直接内层循环进行自旋即可// else CAS failed due to workerCount change; retry inner loop} }boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {w = new Worker(firstTask);final Thread t = w.thread;if (t != null) {final ReentrantLock mainLock = this.mainLock;// worker的添加必须是串行的,因此需要加锁mainLock.lock();try {// Recheck while holding lock.// Back out on ThreadFactory failure or if// shut down before lock acquired.// 这儿需要重新检查线程池状态int rs = runStateOf(ctl.get());if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {// worker已经调用过了start()方法,则不再创建workerif (t.isAlive()) // precheck that t is startablethrow new IllegalThreadStateException();// worker创建并添加到workers成功workers.add(w);// 更新largestPoolSize变量int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;} } finally {mainLock.unlock();}// 启动worker线程if (workerAdded) {t.start();workerStarted = true;} }} finally {// worker线程启动失败,说明线程池状态发生了变化(关闭操作被执行),需要进行shutdown相关操作if (! workerStarted)addWorkerFailed(w);}return workerStarted;} 5、线程池worker任务单元 private final class Workerextends AbstractQueuedSynchronizerimplements Runnable{/ This class will never be serialized, but we provide a serialVersionUID to suppress a javac warning./private static final long serialVersionUID = 6138294804551838833L;/ Thread this worker is running in. Null if factory fails. /final Thread thread;/ Initial task to run. Possibly null. /Runnable firstTask;/ Per-thread task counter /volatile long completedTasks;/ Creates with given first task and thread from ThreadFactory. @param firstTask the first task (null if none)/Worker(Runnable firstTask) {setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;// 这儿是Worker的关键所在,使用了线程工厂创建了一个线程。传入的参数为当前workerthis.thread = getThreadFactory().newThread(this);}/ Delegates main run loop to outer runWorker /public void run() {runWorker(this);}// 省略代码...} 6、核心线程执行逻辑-runworker final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;// 调用unlock()是为了让外部可以中断w.unlock(); // allow interrupts// 这个变量用于判断是否进入过自旋(while循环)boolean completedAbruptly = true;try {// 这儿是自旋// 1. 如果firstTask不为null,则执行firstTask;// 2. 如果firstTask为null,则调用getTask()从队列获取任务。// 3. 阻塞队列的特性就是:当队列为空时,当前线程会被阻塞等待while (task != null || (task = getTask()) != null) {// 这儿对worker进行加锁,是为了达到下面的目的// 1. 降低锁范围,提升性能// 2. 保证每个worker执行的任务是串行的w.lock();// If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted. This// requires a recheck in second case to deal with// shutdownNow race while clearing interrupt// 如果线程池正在停止,则对当前线程进行中断操作if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();// 执行任务,且在执行前后通过beforeExecute()和afterExecute()来扩展其功能。// 这两个方法在当前类里面为空实现。try {beforeExecute(wt, task);Throwable thrown = null;try {task.run();} catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {afterExecute(task, thrown);} } finally {// 帮助gctask = null;// 已完成任务数加一 w.completedTasks++;w.unlock();} }completedAbruptly = false;} finally {// 自旋操作被退出,说明线程池正在结束processWorkerExit(w, completedAbruptly);} } 本篇文章为转载内容。原文链接:https://blog.csdn.net/grd_java/article/details/113116244。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-21 16:19:45
327
转载
转载文章
...g Boot作为主流Java Web开发框架,在其最新的2.5版本中增强了对文件上传的支持,不仅简化了配置流程,还优化了大文件分块上传与断点续传等功能。例如,开发者可以利用MultipartFile接口轻松处理多部分表单提交的文件,并结合云存储服务(如阿里云OSS或AWS S3)进行分布式文件存储与管理,极大地提高了系统的稳定性和可扩展性。 同时,针对安全性问题,Spring Security框架提供了更严格的CSRF保护和JWT token验证等机制,确保用户在执行敏感操作(如文件上传与下载)时的身份合法性。此外,OAuth 2.0授权协议在企业级应用中的普及,使得跨系统、跨平台的用户身份验证与授权更为便捷且安全。 另外,随着前端技术的发展,诸如React、Vue.js等现代前端框架也实现了对文件上传组件的高度封装,配合后端API能够提供无缝的用户体验。例如,通过axios库在前端发起multipart/form-data类型的POST请求,配合后端的RESTful API完成文件上传过程,而后再通过响应式编程实现文件上传状态的实时反馈。 综上所述,随着技术的演进,无论是后端框架还是前端技术,都在不断提升文件上传下载功能的安全性、易用性和性能表现。在实际项目开发中,除了掌握基础的文件处理方法外,还需关注行业前沿趋势,灵活运用新技术手段以满足不断变化的业务需求。
2023-11-12 20:53:42
140
转载
转载文章
在深入学习Java Swing库进行图形用户界面开发之后,我们了解到swingJpanelQieHuan类在实现动态面板切换和响应事件方面扮演了重要角色。为了进一步提升GUI设计的专业性和效率,开发者可以关注以下最新的技术和实践: 1. JavaFX替代Swing的探讨:随着Oracle将重心转向JavaFX,许多开发者开始考虑从Swing迁移到JavaFX以获得更现代化、高性能且易于维护的UI体验。JavaFX提供了丰富的图形效果、CSS样式支持以及与Web技术更好的集成能力。 2. 现代Java GUI框架介绍:除了官方推荐的JavaFX外,还有诸如TornadoFX(基于JavaFX的声明式MVVM框架)、 Griffon(Groovy为基础的桌面应用框架)等新兴框架,它们为开发者提供了更简洁直观的API和强大的功能集。 3. 跨平台UI工具包Qt Jambi:尽管Java Swing是纯Java环境下的GUI解决方案,但也有其他跨平台工具包值得关注,例如Qt Jambi,它允许开发者使用Java编写原生速度和外观的应用程序,并兼容多种操作系统。 4. 无障碍性设计原则在Swing中的应用:针对日益增长的无障碍需求,开发者应了解并遵循WCAG标准,在Swing应用程序中实施无障碍设计,如提供键盘导航支持、可调整的文字大小及高对比度模式等。 5. Swing组件最佳实践分享:查阅最新的开发者博客和论坛讨论,可以发现众多关于如何优化Swing组件性能、处理并发问题以及改善用户体验的实际案例和建议,这些都能帮助你更好地运用Swing进行复杂GUI的设计与实现。 综上所述,不断跟进最新的GUI开发趋势和技术发展,结合实际项目需求,灵活运用和扩展Swing或其他相关框架,将有助于打造更为出色和易用的桌面应用程序。
2023-01-18 08:36:23
525
转载
JQuery插件下载
...的前端插件。它采用纯JavaScript编写,并结合了CSS3技术,以实现丰富的动画特效和灵活的位置定位功能。该插件能够在用户鼠标悬停或交互时动态显示自定义的内容提示,这些提示框会根据触发元素在页面上的实际位置自动调整自身的显示位置,确保最佳的视觉效果和用户体验。通过整合CSS3的动画特性,插件赋予了工具提示平滑且吸引人的过渡效果,增强了网页的互动性和美观度。开发者无需深入理解复杂的定位计算与动画编排,只需简单配置即可轻松集成到项目中,让任何HTML元素具备优雅且响应迅速的工具提示功能。此外,由于不依赖于其他大型框架如jQuery,这款插件拥有轻量级、易于维护的特点,非常适合注重性能和简洁性的现代Web应用开发场景。 点我下载 文件大小:263.26 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2023-03-18 19:38:12
90
本站
JQuery插件下载
...注于提升用户体验的纯JavaScript智能缩放图片预览插件。它专为网页开发者设计,无需引入额外的库或框架依赖,仅需JavaScript即可实现高度定制化的图片预览功能。这款插件旨在简化网页中图片的展示方式,通过点击缩略图的方式,动态加载并显示高清大图,从而提升用户在浏览图片时的视觉体验。zooming.js的核心优势在于其简洁性与灵活性。它不仅提供直观且响应迅速的图片缩放效果,还能适应各种设备与屏幕尺寸,确保在不同环境下都能提供一致的高质量图像显示。此外,插件支持无缝过渡动画,使得用户在切换图片时获得流畅而自然的视觉体验。对于网页开发者而言,整合zooming.js非常便捷。只需在HTML中引用插件文件,并在需要预览的图片标签上添加特定属性,即可激活预览功能。开发者还可以根据项目需求调整插件的行为,例如设置预览图的尺寸、缩放比例以及动画速度等参数,以优化整体设计风格和用户体验。总之,zooming.js作为一款基于纯JavaScript的图片预览解决方案,以其轻量级、高效性和高度可定制性,成为提升网页中图片展示效果的理想选择。无论是用于电子商务网站、博客、相册展示或是其他需要丰富图片交互功能的场景,zooming.js都能提供出色的表现,增强用户与内容的互动体验。 点我下载 文件大小:341.59 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2024-08-17 11:05:23
54
本站
Java
在深入理解Java中对象的比较方式后,我们可以进一步探索更广阔的应用场景和实践策略。近期,随着JDK 17的发布,对内存管理和字符串处理的优化更加凸显了正确使用“==”和equals()方法的重要性。例如,在JDK 17引入的String类的内部优化中,对于相同的字符串字面量,其“==”比较的结果在更多情况下将表现为true,这是由于对字符串常量池进行了更为高效的管理。 此外,对于自定义类的对象比较,不仅需要重写equals()方法以实现内容比较,还应遵循约定,同时重写hashCode()方法以保持equals()和hashCode()的一致性原则。这在诸如HashMap、HashSet等集合类的使用场景中至关重要,因为这些类会依赖hashCode()来快速定位元素,而equals()则用于最终确定元素是否相等。 实践中,对于复杂的对象结构,如多层嵌套的对象属性,可以采用Apache Commons Lang库中的EqualsBuilder和HashCodeBuilder工具类进行深度内容比较。这些工具提供了链式调用的方式,能确保即使对象结构复杂也能准确地判断内容是否相等,从而避免因属性遗漏而导致的比较错误。 总之,理解并有效运用Java中的对象比较方式是编程过程中的基石之一,它不仅关乎程序逻辑的准确性,也在很大程度上影响着应用程序的性能与健壮性。紧跟技术发展趋势,结合实际项目需求,灵活选择和定制合适的比较策略,是每个Java开发者不断提升技能的重要环节。
2023-06-27 10:13:01
314
键盘勇士
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
cut -d ',' -f 1,3 file.csv
- 根据逗号分隔符提取csv文件中第1列和第3列的内容。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"