前端技术
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
[UTC时间 Coordinated Un...]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
JSON
...类型,并且支持日期和时间的ISO-8601格式表示。 UTC时间(Coordinated Universal Time) , 协调世界时,是一种国际标准的时间尺度,基于原子钟精确测量得到,不随地球自转变化而调整。在JSON中通常以ISO-8601格式(如1995-01-02T15:04:05.999Z)表示时间戳,其中“Z”代表零时区,即UTC时间。 ISO-8601 , 是国际标准化组织制定的日期和时间表示法标准,它定义了一套统一的日期和时间表示格式。在JSON中,时间戳通常按照ISO-8601格式进行序列化,例如\ 1995-01-02T15:04:05.999Z\ ,这种格式包括年月日小时分钟秒以及可选的小数秒部分,并可以通过加减时区偏移量来转换为不同地区的本地时间。 JavaScript Date对象 , 在JavaScript编程语言中,Date对象用于处理日期和时间,提供了获取当前日期和时间、设置日期和时间、计算日期与时间之间的时间差等功能。在处理JSON中的时间信息时,可以将符合ISO-8601格式的字符串转化为Date对象,进而进行各种时间相关的计算,包括时区转换等操作。 时区偏移量 , 是指特定地点或地区相对于UTC时间的标准时间差,通常用小时和分钟表示,例如+08:00表示东八区(北京时间)。在处理多时区的JSON数据时,需要计算并应用这个偏移量来实现时间的正确转换和显示。
2023-08-18 10:38:11
520
算法侠
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 1、时间与日期 ECMAScript 提供了 Date 类型来处理时间和日期。Date 类型内置一系列获取和设置日期时间信息的方法。 Date 类型 ECMAScript 中的 Date 类型是在早期 Java 中 java.util.Date 类基础上构建的。为此,Date 类型使用 UTC(Coordinated Universal Time,国际协调时间[又称世界统一时间])1970 年 1 月 1 日午夜(零时)开始经过的毫秒来保存日期。在使用这种数据存储格式的条件下,Date 类型保存的日期能够精确到 1970 年 1 月 1 日之前或之后的 285616 年。 创建一个日期对象,使用 new 运算符和 Date 构造方法(构造函数)即可。 var box = new Date(); // 创建一个日期对象 在调用 Date 构造方法而不传递参数的情况下,新建的对象自动获取当前的时间和日期。 alert(box); // 不同浏览器显示不同 ECMAScript 提供了两个方法,Date.parse()和 Date.UTC()。Date.parse()方法接收一个表示日期的字符串参数,然后尝试根据这个字符串返回相应的毫秒数。ECMA-262 没有定义 Date.parse()应该支持哪种日期格式,因此方法的行为因实现而异,因地区而异。默认通常接收的日期格式如下: ‘月/日/年’,如 6/13/2011; ‘英文月名 日, 年’,如 May 25, 2004; ‘英文星期几 英文月名 日 年 时:分:秒 时区’,如 Tue May 25 2004 00:00:00 GMT-070 alert(Date.parse('6/13/2011')); // 1307894400000 如果 Date.parse()没有传入或者不是标准的日期格式,那么就会返回 NaN。 alert(Date.parse()); // NaN 如果想输出指定的日期,那么把 Date.parse()传入 Date 构造方法里。 var box = new Date(Date.parse('6/13/2011')); // Mon Jun 13 2011 00:00:00 GMT+0800var box = new Date('6/13/2011'); // 直接传入,Date.parse()后台被调用 Date 对象及其在不同浏览器中的实现有许多奇怪的行为。其中有一种倾向是将超出的范围的值替换成当前的值,以便生成输出。例如,在解析“January 32, 2007”时,有的浏览器会将其解释为“February 1, 2007”。而 Opera 则倾向与插入当前月份的当前日期。 Date.UTC()方法同样也返回表示日期的毫秒数,但它与 Date.parse()在构建值时使用不同的信息。(年份,基于 0 的月份[0 表示 1 月,1 表示 2 月],月中的哪一天[1-31],小时数[0-23] ,分钟,秒以及毫秒)。只有前两个参数是必须的。如果没有提供月数,则天数为 1;如果省略其他参数,则统统为 0。 alert(Date.UTC(2011,11)); // 1322697600000 如果 Date.UTC()参数传递错误,那么就会出现负值或者 NaN 等非法信息。 alert(Date.UTC()); // 负值或者 NaN 如果要输出指定日期,那么直接把 Date.UTC()传入 Date 构造方法里即可。 var box = new Date(Date.UTC(2011,11, 5, 15, 13, 16)); 通用的方法 与其他类型一样,Date 类型也重写了 toLocaleString()、toString()和 valueOf()方法;但这些方法返回值与其他类型中的方法不同。 var box = new Date(Date.UTC(2011,11, 5, 15, 13, 16));alert('toString:' + box.toString());alert('toLocaleString:' + box.toLocaleString()); // 按本地格式输出 这两个方法在不同浏览器显示的效果又不一样,但不用担心,这两个方法只是在调试比较有用,在显示时间和日期上,没什么价值。valueOf()方法显示毫秒数。 日期格式化方法 Date 类型还有一些专门用于将日期格式化为字符串的方法。 var box = new Date();alert(box.toDateString()); // 以特定的格式显示星期几、月、日和年alert(box.toTimeString()); // 以特定的格式显示时、分、秒和时区alert(box.toLocaleDateString()); // 以特定地区格式显示星期几、月、日和年alert(box.toLocaleTimeString()); // 以特定地区格式显示时、分、秒和时区alert(box.toUTCString()); // 以特定的格式显示完整的 UTC 日期 组件方法 组件方法,是为我们单独获取你想要的各种时间/日期而提供的方法。需要注意的时候 ,这些方法中,有带 UTC 的,有不带 UTC 的。UTC 日期指的是在没有时区偏差的情况下的日期值。 alert(box.getTime()); // 获取日期的毫秒数,和 valueOf()返回一致alert(box.setTime(100)); // 以毫秒数设置日期,会改变整个日期alert(box.getFullYear()); // 获取四位年份alert(box.setFullYear(2012)); // 设置四位年份,返回的是毫秒数alert(box.getMonth()); // 获取月份,没指定月份,从 0 开始算起alert(box.setMonth(11)); // 设置月份alert(box.getDate()); // 获取日期alert(box.setDate(8)); // 设置日期,返回毫秒数alert(box.getDay()); // 返回星期几,0 表示星期日,6 表示星期六alert(box.setDay(2)); // 设置星期几alert(box.getHours()); // 返回时alert(box.setHours(12)); // 设置时alert(box.getMinutes()); // 返回分钟alert(box.setMinutes(22)); // 设置分钟alert(box.getSeconds()); // 返回秒数alert(box.setSeconds(44)); // 设置秒数alert(box.getMilliseconds()); // 返回毫秒数alert(box.setMilliseconds()); // 设置毫秒数alert(box.getTimezoneOffset()); // 返回本地时间和 UTC 时间相差的分钟数 以上方法除了 getTimezoneOffset(),其他都具有 UTC 功能,例如 setDate()及 getDate()获取星期几,那么就会有 setUTCDate()及getUTCDate(),表示世界协调时间。 2、正则表达式 假设用户需要在 HTML 表单中填写姓名、地址、出生日期等。那么在将表单提交到服务器进一步处理前,JavaScript 程序会检查表单以确认用户确实输入了信息并且这些信息是符合要求的。 什么是正则表达式 正则表达式(regular expression)是一个描述字符模式的对象。ECMAScript 的 RegExp 类表示正则表达式,而 String 和 RegExp 都定义了使用正则表达式进行强大的模式匹配和文本检索与替换的函数。 正则表达式主要用来验证客户端的输入数据。用户填写完表单单击按钮之后,表单就会被发送到服务器,在服务器端通常会用 PHP、ASP.NET 等服务器脚本对其进行进一步处理 。因为客户端验证,可以节约大量的服务器端的系统资源,并且提供更好的用户体验。 创建正则表达式 创建正则表达式和创建字符串类似,创建正则表达式提供了两种方法,一种是采用 new 运算符,另一个是采用字面量方式。 两种创建方式 var box = new RegExp('box'); // 第一个参数字符串var box = new RegExp('box', 'ig'); // 第二个参数可选模式修饰符 模式修饰符的可选参数 参数 含义 i 忽略大小写 g 全局匹配 m 多行匹配 var box = /box/; // 直接用两个反斜杠var box = /box/ig; // 在第二个斜杠后面加上模式修饰符 测试正则表达式 RegExp 对象包含两个方法:test()和 exec(),功能基本相似,用于测试字符串匹配。test()方法在字符串中查找是否存在指定的正则表达式并返回布尔值,如果存在则返回 true,不存在则返回 false。exec()方法也用于在字符串中查找指定正则表达式,如果 exec()方法执行成功,则返回包含该查找字符串的相关信息数组。如果执行失败,则返回 null。 RegExp 对象的方法 方法 功能 test 在字符串中测试模式匹配,返回 true 或 false exec 在字符串中执行匹配搜索,返回结果数组 // 使用 new 运算符的 test 方法示例var pattern = new RegExp('box', 'i'); // 创建正则模式,不区分大小写var str = 'This is a Box!'; // 创建要比对的字符串alert(pattern.test(str)); // 通过 test()方法验证是否匹配// 使用字面量方式的 test 方法示例var pattern = /box/i; // 创建正则模式,不区分大小写var str = 'This is a Box!';alert(pattern.test(str));// 使用一条语句实现正则匹配alert(/box/i.test('This is a Box!')); // 模式和字符串替换掉了两个变量// 使用 exec 返回匹配数组var pattern = /box/i;var str = 'This is a Box!';alert(pattern.exec(str)); // 匹配了返回数组,否则返回 null 使用字符串的正则表达式方法 除了 test()和 exec()方法,String 对象也提供了 4 个使用正则表达式的方法。 String 对象中的正则表达式方法 方法 含义 match(pattern) 返回 pattern 中的子串或 null replace(pattern, replacement) 用 replacement 替换 pattern search(pattern) 返回字符串中 pattern 开始位置 split(pattern) 返回字符串按指定 pattern 拆分的数组 // 使用 match 方法获取获取匹配数组var pattern = /box/ig; // 全局搜索var str = 'This is a Box!,That is a Box too';alert(str.match(pattern)); // 匹配到两个 Box,Boxalert(str.match(pattern).length); // 获取数组的长度// 使用 search 来查找匹配数据var pattern = /box/ig;var str = 'This is a Box!,That is a Box too';alert(str.search(pattern)); // 查找到返回位置,否则返回-1 因为 search 方法查找到即返回,也就是说无需 g 全局。 // 使用 replace 替换匹配到的数据var pattern = /box/ig;var str = 'This is a Box!,That is a Box too';alert(str.replace(pattern, 'Tom')); // 将 Box 替换成了 Tom// 使用 split 拆分成字符串数组var pattern = / /ig;var str = 'This is a Box!,That is a Box too';alert(str.split(pattern)); // 将空格拆开分组成数组 RegExp 对象的静态属性 属性 短名 含义 input $_ 当前被匹配的字符串 lastMatch $& 最后一个匹配字符串 lastParen $+ 最后一对圆括号内的匹配子串 leftContext $ 最后一次匹配前的子串 multiline $ 用于指定是否所有的表达式都用于多行的布尔值 rightContext $’ 在上次匹配之后的子串 // 使用静态属性var pattern = /(g)oogle/;var str = 'This is google!';pattern.test(str); // 执行一下alert(RegExp.input); // This is google!alert(RegExp.leftContext); // This isalert(RegExp.rightContext); // !alert(RegExp.lastMatch); // googlealert(RegExp.lastParen); // galert(RegExp.multiline); // false Opera 不支持 input、lastMatch、lastParen 和 multiline 属性。IE 不支持 multiline 属性。所有的属性可以使用短名来操作。RegExp.input 可以改写成 RegExp['$_'],依次类推。但 RegExp.input 比较特殊,它还可以写成 RegExp.$_。 RegExp 对象的实例属性 属性 含义 global Boolean 值,表示 g 是否已设置 ignoreCase Boolean 值,表示 i 是否已设置 lastIndex 整数,代表下次匹配将从哪里字符位置开始 multiline Boolean 值,表示 m 是否已设置 Source 正则表达式的源字符串形式 // 使用实例属性var pattern = /google/ig;alert(pattern.global); // true,是否全局了alert(pattern.ignoreCase); // true,是否忽略大小写alert(pattern.multiline); // false,是否支持换行alert(pattern.lastIndex); // 0,下次的匹配位置alert(pattern.source); // google,正则表达式的源字符串var pattern = /google/g;var str = 'google google google';pattern.test(str); // google,匹配第一次alert(pattern.lastIndex); // 6,第二次匹配的位 以上基本没什么用。并且 lastIndex 在获取下次匹配位置上 IE 和其他浏览器有偏差 ,主要表现在非全局匹配上。lastIndex 还支持手动设置,直接赋值操作。 获取控制 正则表达式元字符是包含特殊含义的字符。它们有一些特殊功能,可以控制匹配模式的方式。反斜杠后的元字符将失去其特殊含义。 字符类:单个字符和数字 元字符/元符号 匹配情况 . 匹配除换行符外的任意字符 [a-z0-9] 匹配括号中的字符集中的任意字符 [^a-z0-9] 匹配任意不在括号中的字符集中的字符 \d 匹配数字 \D 匹配非数字,同[^0-9]相同 \w 匹配字母和数字及_ \W 匹配非字母和数字及_ 字符类:空白字符 元字符/元符号 匹配情况 \0 匹配 null 字符 \b 匹配空格字符 \f 匹配进纸字符 \n 匹配换行符 \r 匹配回车字符 \t 匹配制表符 \s 匹配空白字符、空格、制表符和换行符 \S 匹配非空白字符 字符类:锚字符 元字符/元符号 匹配情况 ^ 行首匹配 $ 行尾匹配 \A 只有匹配字符串开始处 \b 匹配单词边界,词在[]内时无效 \B 匹配非单词边界 \G 匹配当前搜索的开始位置 \Z 匹配字符串结束处或行尾 \z 只匹配字符串结束处 字符类:重复字符 元字符/元符号 匹配情况 x? 匹配 0 个或 1 个 x x 匹配 0 个或任意多个 x x+ 匹配至少一个 x (xyz)+ 匹配至少一个(xyz) x{m,n} 匹配最少 m 个、最多 n 个 x 字符类:替代字符 元字符/元符号 匹配情况 this where 字符类:记录字符 元字符/元符号 匹配情况 (string) 用于反向引用的分组 \1 或$1 匹配第一个分组中的内容 \2 或$2 匹配第二个分组中的内容 \3 或$3 匹配第三个分组中的内容 // 使用点元字符var pattern = /g..gle/; // .匹配一个任意字符var str = 'google';alert(pattern.test(str));// 重复匹配var pattern = /g.gle/; // .匹配 0 个一个或多个var str = 'google'; //,?,+,{n,m}alert(pattern.test(str));// 使用字符类匹配var pattern = /g[a-zA-Z_]gle/; // [a-z]表示任意个 a-z 中的字符var str = 'google';alert(pattern.test(str));var pattern = /g[^0-9]gle/; // [^0-9]表示任意个非 0-9 的字符var str = 'google';alert(pattern.test(str));var pattern = /[a-z][A-Z]+/; // [A-Z]+表示 A-Z 一次或多次var str = 'gOOGLE';alert(pattern.test(str));// 使用元符号匹配var pattern = /g\wgle/; // \w匹配任意多个所有字母数字_var str = 'google';alert(pattern.test(str));var pattern = /google\d/; // \d匹配任意多个数字var str = 'google444';alert(pattern.test(str));var pattern = /\D{7,}/; // \D{7,}匹配至少 7 个非数字var str = 'google8';alert(pattern.test(str));// 使用锚元字符匹配var pattern = /^google$/; // ^从开头匹配,$从结尾开始匹配var str = 'google';alert(pattern.test(str));var pattern = /goo\sgle/; // \s 可以匹配到空格var str = 'goo gle';alert(pattern.test(str));var pattern = /google\b/; // \b 可以匹配是否到了边界var str = 'google';alert(pattern.test(str));// 使用或模式匹配var pattern = /google|baidu|bing/; // 匹配三种其中一种字符串var str = 'google';alert(pattern.test(str));// 使用分组模式匹配var pattern = /(google){4,8}/; // 匹配分组里的字符串 4-8 次var str = 'googlegoogle';alert(pattern.test(str));var pattern = /8(.)8/; // 获取 8..8 之间的任意字符var str = 'This is 8google8';str.match(pattern);alert(RegExp.$1); // 得到第一个分组里的字符串内容var pattern = /8(.)8/;var str = 'This is 8google8';var result = str.replace(pattern,'<strong>$1</strong>'); // 得到替换的字符串输出document.write(result);var pattern = /(.)\s(.)/;var str = 'google baidu';var result = str.replace(pattern, '$2 $1'); // 将两个分组的值替换输出document.write(result); 贪婪 惰性 + +? ? ?? ? {n} {n}? {n,} {n,}? {n,m} {n,m}? // 关于贪婪和惰性var pattern = /[a-z]+?/; // ?号关闭了贪婪匹配,只替换了第一个var str = 'abcdefjhijklmnopqrstuvwxyz';var result = str.replace(pattern, 'xxx');alert(result);var pattern = /8(.+?)8/g; // 禁止了贪婪,开启的全局var str = 'This is 8google8, That is 8google8, There is 8google8';var result = str.replace(pattern,'<strong>$1</strong>');document.write(result);var pattern = /8([^8])8/g; // 另一种禁止贪婪var str = 'This is 8google8, That is 8google8, There is 8google8';var result = str.replace(pattern,'<strong>$1</strong>');document.write(result);// 使用 exec 返回数组var pattern = /^[a-z]+\s[0-9]{4}$/i;var str = 'google 2012';alert(pattern.exec(str)); // 返回整个字符串var pattern = /^[a-z]+/i; // 只匹配字母var str = 'google 2012';alert(pattern.exec(str)); // 返回 googlevar pattern = /^([a-z]+)\s([0-9]{4})$/i; // 使用分组var str = 'google 2012';alert(pattern.exec(str)[0]); // google 2012alert(pattern.exec(str)[1]); // googlealert(pattern.exec(str)[2]); // 2012// 捕获性分组和非捕获性分组var pattern = /(\d+)([a-z])/; // 捕获性分组var str = '123abc';alert(pattern.exec(str));var pattern = /(\d+)(?:[a-z])/; // 非捕获性分组var str = '123abc';alert(pattern.exec(str));// 使用分组嵌套var pattern = /(A?(B?(C?)))/; // 从外往内获取var str = 'ABC';alert(pattern.exec(str));// 使用前瞻捕获var pattern = /(goo(?=gle))/; // goo 后面必须跟着 gle 才能捕获var str = 'google';alert(pattern.exec(str));// 使用特殊字符匹配var pattern = /\.\[\/b\]/; // 特殊字符,用\符号转义即可var str = '.[/b]';alert(pattern.test(str));// 使用换行模式var pattern = /^\d+/mg; // 启用了换行模式var str = '1.baidu\n2.google\n3.bing';var result = str.replace(pattern, '');alert(result); 常用的正则 检查邮政编码 var pattern = /[1-9][0-9]{5}/; // 共 6 位数字,第一位不能为 0var str = '224000';alert(pattern.test(str)); 检查文件压缩包 var pattern = /[\w]+\.zip|rar|gz/; // \w 表示所有数字和字母加下划线var str = '123.zip'; // \.表示匹配.,后面是一个选择alert(pattern.test(str)); 删除多余空格 var pattern = /\s/g; // g 必须全局,才能全部匹配var str = '111 222 333';var result = str.replace(pattern,''); // 把空格匹配成无空格alert(result); 删除首尾空格 var pattern = /^\s+/; // 强制首var str = ' goo gle ';var result = str.replace(pattern, '');pattern = /\s+$/; // 强制尾result = result.replace(pattern, '');alert('|' + result + '|');var pattern = /^\s(.+?)\s$/; // 使用了非贪婪捕获var str = ' google ';alert('|' + pattern.exec(str)[1] + '|');var pattern = /^\s(.+?)\s$/;var str = ' google ';alert('|' + str.replace(pattern, '$1') + '|'); // 使用了分组获取 简单的电子邮件验证 var pattern = /^([a-zA-Z0-9_\.\-]+)@([a-zA-Z0-9_\.\-]+)\.([a-zA-Z]{2,4})$/;var str = 'yc60.com@gmail.com';alert(pattern.test(str));var pattern = /^([\w\.\-]+)@([\w\.\-]+)\.([\w]{2,4})$/;var str = 'yc60.com@gmail.com';alert(pattern.test(str)); 3、Function类型 在 ECMAScript 中,Function(函数)类型实际上是对象。每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针。 函数的声明方式 普通的函数声明 function box(num1, num2) {return num1+ num2;} 使用变量初始化函数 var box= function(num1, num2) {return num1 + num2;}; 使用 Function 构造函数 var box= new Function('num1', 'num2' ,'return num1 + num2'); 第三种方式我们不推荐,因为这种语法会导致解析两次代码(第一次解析常规 ECMAScript 代码,第二次是解析传入构造函数中的字符串),从而影响性能。但我们可以通过这种语法来理解"函数是对象,函数名是指针"的概念。 作为值的函数 ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。 function box(sumFunction, num) {return sumFunction(num); // someFunction}function sum(num) {return num + 10;}var result = box(sum, 10); // 传递函数到另一个函数里 函数内部属性 在函数内部,有两个特殊的对象:arguments 和 this。arguments 是一个类数组对象,包含着传入函数中的所有参数,主要用途是保存函数参数。但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。 function box(num) {if (num <= 1) {return 1;} else {return num box(num-1); // 一个简单的的递归} } 对于阶乘函数一般要用到递归算法,所以函数内部一定会调用自身;如果函数名不改变是没有问题的,但一旦改变函数名,内部的自身调用需要逐一修改。为了解决这个问题,我们可以使用 arguments.callee 来代替。 function box(num) {if (num <= 1) {return 1;} else {return num arguments.callee(num-1); // 使用 callee 来执行自身} } 函数内部另一个特殊对象是 this,其行为与 Java 和 C中的 this 大致相似。换句话说 ,this 引用的是函数据以执行操作的对象,或者说函数调用语句所处的那个作用域。当在全局作用域中调用函数时,this 对象引用的就是 window。 // 便于理解的改写例子window.color = '红色的'; // 全局的,或者 var color = '红色的';也行alert(this.color); // 打印全局的 colorvar box = {color : '蓝色的', // 局部的 colorsayColor : function () {alert(this.color); // 此时的 this 只能 box 里的 color} };box.sayColor(); // 打印局部的 coloralert(this.color); // 还是全局的// 引用教材的原版例子window.color = '红色的'; // 或者 var color = '红色的';也行var box = {color : '蓝色的'};function sayColor() {alert(this.color); // 这里第一次在外面,第二次在 box 里面}getColor();box.sayColor = sayColor; // 把函数复制到 box 对象里,成为了方法box.sayColor(); 函数属性和方法 ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性 :length 和 prototype。其中,length 属性表示函数希望接收的命名参数的个数。 function box(name, age) {alert(name + age);}alert(box.length); // 2 对于 prototype 属性,它是保存所有实例方法的真正所在,也就是原型。这个属性 ,我们将在面向对象一章详细介绍。而 prototype 下有两个方法:apply()和 call(),每个函数都包含这两个非继承而来的方法。这两个方法的用途都在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。 function box(num1, num2) {return num1 + num2; // 原函数}function sayBox(num1, num2) {return box.apply(this, [num1, num2]); // this 表示作用域,这里是 window} // []表示 box 所需要的参数function sayBox2(num1, num2) {return box.apply(this, arguments); // arguments 对象表示 box 所需要的参数}alert(sayBox(10,10)); // 20alert(sayBox2(10,10)); // 20 call()方法于 apply()方法相同,他们的区别仅仅在于接收参数的方式不同。对于 call()方法而言,第一个参数是作用域,没有变化,变化只是其余的参数都是直接传递给函数的。 function box(num1, num2) {return num1 + num2;}function callBox(num1, num2) {return box.call(this, num1, num2); // 和 apply 区别在于后面的传参}alert(callBox(10,10)); 事实上,传递参数并不是 apply()和 call()方法真正的用武之地;它们经常使用的地方是能够扩展函数赖以运行的作用域。 var color = '红色的'; // 或者 window.color = '红色的';也行var box = {color : '蓝色的'};function sayColor() {alert(this.color);}sayColor(); // 作用域在 windowsayColor.call(this); // 作用域在 windowsayColor.call(window); // 作用域在 windowsayColor.call(box); // 作用域在 box,对象冒充 这个例子是之前作用域理解的例子修改而成,我们可以发现当我们使用 call(box)方法的时候,sayColor()方法的运行环境已经变成了 box 对象里了。 使用 call()或者 apply()来扩充作用域的最大好处,就是对象不需要与方法发生任何耦合关系(耦合,就是互相关联的意思,扩展和维护会发生连锁反应)。也就是说,box 对象和 sayColor()方法之间不会有多余的关联操作,比如 box.sayColor = sayColor;。 本篇文章为转载内容。原文链接:https://blog.csdn.net/gongxifacai_believe/article/details/108286196。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-24 13:01:25
529
转载
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 All the charts, single package It’s easy with amCharts 5 – all chart types come in a single, easy to understand package! No need to figure out product line up – just get amCharts 5 for everything. And since it was designed to work with modern web dev toolkits like React, Angular, Vue it will just fall into place, right out of the box. X/Y Line & Smoothed line Area Column & Bar Scatter & Bubble Candlestick & OHLC Step (incl. w/ no-riser) Floating & Gantt Waterfall Error Stacked (regular or 100%) Heatmap … and any combination of the above Percent Pie & Donut Nested donut Funnel Pyramid Pictorial Geo maps Map chart Geo heat map Map combined with charts Maps is an add-on and requires separate license. Other Sankey diagram Chord, Chord directed, and Chord non-ribbon diagrams Pack Treemap Tree Sunburst Partition Force-directed tree Radar & Polar Word cloud Venn diagram Efficiency built-in Canvas rendering amCharts 5 uses browser’s Canvas API which in most cases is way faster than SVG. Less moving parts in the DOM tree, faster rendering. Layering Common element groups are isolated into separate independent canvases, so that heavily updated sections do not trigger expensive repaints in places that do not change. Fast data processing Data processing in amCharts 5 is designed to be as efficient as possible. Incremental updates, lack of repetitive aggregations, and lightweight data object use makes data processing fast and very memory-efficient. Faster dashboards amCharts 5 is capable of running scores of charts on the same page, without crippling the browser, due to its lightweight approach to data parsing and rendering. Tiny binaries We made amCharts 5 really small – the core functionality compiles to a file of only around 400KB. Each niche functionality is separated into files, so you load only what you really need. Users will surely appreciate faster load times. Better tree-shaking We also designed amCharts 5 to be extremely tree-shakable. If you are using Webpack or similar packager, only code that is really needed will be included into your final application. The most advanced chart package Classics with some new twists XY charts are now so powerful and flexible, you can plot any data on them. Number, date, duration, or category axes are supported, in all directions. Pie charts are now fully nestable, with support for custom start and end angles, to create half circles. New geo maps Our maps use GeoJSON format. Being open and widely accepted standard it opens up a lot of possibilities and sources for ready-made and custom maps. Furthermore, maps are now very flexible, with multi-series support, configurable down to the nut and bolt. (Maps is and add-on to amCharts 5: Charts which requires separate license) More about amCharts 5: Maps Pictorials Create multi-layer, multi-series pictorial charts. Any SVG path can be used as a shape for your chart. Sankey Diagrams Stunning flow diagrams, in horizontal and vertical. With draggable, fully configurable nodes. Enhanced radar charts With stacked column, bands, axes, and other dramatic enhancements, radar charts are now way more useful. Treemaps Completely zoomable, multi-level, highly configurable. Heatmaps Automatically build heat-maps, with custom axes, color ranges, and awesome new interactive Heat Legend. Create heatmaps using colors, or point size or both. Universal and flexible heat rules allow attaching any value in data to any property or properties on any element. Chord diagrams Visualize your 2-way relational data in a neat circular Chord diagrams. We do have different variations of the classic diagram: Chord, Chord directed, and Chord non-ribbon. True funnel charts amCharts 5 offers true Funnel charts the way they were meant to be. Slice’s area size represents the value, so each step’s influence on overall volume reduction is more prominent than with basic funnels. Trapezoid form can also be configured to further emphasize reduction. Complete it with other visual elements, like fully configurable slice lines, multiple series support, togglable legends, and many many more options. Customizable beauty built-in Powerful theme engine amCharts 5 comes with a bunch of beautiful themes as well as a super flexible theme engine, which you can use. We devised a CSS-like rule-based theme targeting system in themes. Using, creating, customizing themes or standalone rules has never been so easy. The new system allows applying defaults to elements based on their type, features, or position in a virtual element tree. Fresh new look Default looks designed to look fresh, like something out of tomorrow. Carefully selected color schemes and default settings were specifically chosen to make the charts stand out. Silk-smooth animations Every setting – colors, positions, sizes, opacity, and many more – is animatable to ensure smooth transitions. No choppy, stepped animations – everything is fluid, including zoom and toggling of series and other items. Flexibility Element templates Most elements are created using templates: a collection of default settings, events, and adapters. Changing template automatically propagates changes to actual elements, making it easy to do batch updates. Everything’s configurable Numerous configuration options allow inventive uses, bordering on new chart types. Angles, colors, positions, radii, a-n-y-t-h-i-n-g can be set to bend classic and new chart types exactly the way you need them. Element states Easily change how an element looks like under different circumstances, e.g. on some interaction, hover, click, or related to data (eg. column look if a value is down). The engine will automatically apply the required properties as needed, animating between old and new values smoothly. Create and apply custom states via the API. Multi-type multi-axis support Add any number of axes of any type. Create overlaid comparison of different time scales. Use any mix of dimensional values: numbers, dates, categories, or duration. Adapters “Adapters” functionality allows plugging in custom code to dynamically override just about any setting or data value. Text formatting All text labels – tooltips, axis labels, titles, etc. – now support rich text formatting options, like changing colors, font weight, or applying just about any styling option from the CSS arsenal. In addition to formatting support, labels can now contain in-line placeholders for real data, with the ability to apply custom formatting to values. Accessibility & Interactivity Accessibility Accessibility was riding shotgun when amCharts 5 was being developed. All interactive elements are TAB-selectable, with customizable roles, order, and screen-reader texts. Everything that can be moved by touch or mouse, can be moved by keyboard. Everything that can be clicked or toggled, can be interacted with with keyboard, too. Touch support Charts have been designed to work with touch devices out-of-the-box. They will work not only on phones or tablets, but also touch capable computers. Under the hood Built with TypeScript Supports strong type and error checking in TypeScript applications. Enjoy code completion, error checking and dynamic help popups in major IDEs. Full support for TypeScript and ES6 modules. 100% for JavaScript Can be fully used in any vanilla JavaScript application. amCharts 5 does not use or rely on globals, external frameworks or 3rd party libraries. Universal rendering engine Can be used to build dynamic, interactive Canvas-based interfaces and applications. Add various elements to the screen, make them interactive, with a few lines of code. Make them clickable, draggable, hoverable, using built-in interactivity functionality. Our universal layout engine will place, size and arrange elements according to set rules. 本篇文章为转载内容。原文链接:https://blog.csdn.net/john_dwh/article/details/127460821。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-17 18:18:34
351
转载
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 介绍Postgres-XL Postgres-XL 全称为 Postgres eXtensible Lattice,是TransLattice公司及其收购数据库技术公司–StormDB的产品。Postgres-XL是一个横向扩展的开源数据库集群,具有足够的灵活性来处理不同的数据库任务。 Postgres-XL功能特性 开放源代码:(源协议使用宽松的“Mozilla Public License”许可,允许将开源代码与闭源代码混在一起使用。) 完全的ACID支持 可横向扩展的关系型数据库(RDBMS) 支持OLAP应用,采用MPP(Massively Parallel Processing:大规模并行处理系统)架构模式 支持OLTP应用,读写性能可扩展 集群级别的ACID特性 多租户安全 也可被用作分布式Key-Value存储 事务处理与数据分析处理混合型数据库 支持丰富的SQL语句类型,比如:关联子查询 支持绝大部分PostgreSQL的SQL语句 分布式多版本并发控制(MVCC:Multi-version Concurrency Control) 支持JSON和XML格式 Postgres-XL缺少的功能 内建的高可用机制 使用外部机制实现高可能,如:Corosync/Pacemaker 有未来功能提升的空间 增加节点/重新分片数据(re-shard)的简便性 数据重分布(redistribution)期间会锁表 可采用预分片(pre-shard)方式解决,在同台物理服务器上建立多个数据节点,每个节点存储一个数据分片。数据重分布时,将一些数据节点迁出即可 某些外键、唯一性约束功能 Postgres-XL架构 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M9lFuEIP-1640133702200)(./assets/postgre-xl.jpg)] 基于开源项目Postgres-XC XL增加了MPP,允许数据节点间直接通讯,交换复杂跨节点关联查询相关数据信息,减少协调器负载。 多个协调器(Coordinator) 应用程序的数据库连入点 分析查询语句,生成执行计划 多个数据节点(DataNode) 实际的数据存储 数据自动打散分布到集群中各数据节点 本地执行查询 一个查询在所有相关节点上并行查询 全局事务管理器(GTM:Global Transaction Manager) 提供事务间一致性视图 部署GTM Proxy实例,以提高性能 Postgre-XL主要组件 GTM (Global Transaction Manager) - 全局事务管理器 GTM是Postgres-XL的一个关键组件,用于提供一致的事务管理和元组可见性控制。 GTM Standby GTM的备节点,在pgxc,pgxl中,GTM控制所有的全局事务分配,如果出现问题,就会导致整个集群不可用,为了增加可用性,增加该备用节点。当GTM出现问题时,GTM Standby可以升级为GTM,保证集群正常工作。 GTM-Proxy GTM需要与所有的Coordinators通信,为了降低压力,可以在每个Coordinator机器上部署一个GTM-Proxy。 Coordinator --协调器 协调器是应用程序到数据库的接口。它的作用类似于传统的PostgreSQL后台进程,但是协调器不存储任何实际数据。实际数据由数据节点存储。协调器接收SQL语句,根据需要获取全局事务Id和全局快照,确定涉及哪些数据节点,并要求它们执行(部分)语句。当向数据节点发出语句时,它与GXID和全局快照相关联,以便多版本并发控制(MVCC)属性扩展到集群范围。 Datanode --数据节点 用于实际存储数据。表可以分布在各个数据节点之间,也可以复制到所有数据节点。数据节点没有整个数据库的全局视图,它只负责本地存储的数据。接下来,协调器将检查传入语句,并制定子计划。然后,根据需要将这些数据连同GXID和全局快照一起传输到涉及的每个数据节点。数据节点可以在不同的会话中接收来自各个协调器的请求。但是,由于每个事务都是惟一标识的,并且与一致的(全局)快照相关联,所以每个数据节点都可以在其事务和快照上下文中正确执行。 Postgres-XL继承了PostgreSQL Postgres-XL是PostgreSQL的扩展并继承了其很多特性: 复杂查询 外键 触发器 视图 事务 MVCC(多版本控制) 此外,类似于PostgreSQL,用户可以通过多种方式扩展Postgres-XL,例如添加新的 数据类型 函数 操作 聚合函数 索引类型 过程语言 安装 环境说明 由于资源有限,gtm一台、另外两台身兼数职。 主机名 IP 角色 端口 nodename 数据目录 gtm 192.168.20.132 GTM 6666 gtm /nodes/gtm 协调器 5432 coord1 /nodes/coordinator xl1 192.168.20.133 数据节点 5433 node1 /nodes/pgdata gtm代理 6666 gtmpoxy01 /nodes/gtm_pxy1 协调器 5432 coord2 /nodes/coordinator xl2 192.168.20.134 数据节点 5433 node2 /nodes/pgdata gtm代理 6666 gtmpoxy02 /nodes/gtm_pxy2 要求 GNU make版本 3.8及以上版本 [root@pg ~] make --versionGNU Make 3.82Built for x86_64-redhat-linux-gnuCopyright (C) 2010 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law. 需安装GCC包 需安装tar包 用于解压缩文件 默认需要GNU Readline library 其作用是可以让psql命令行记住执行过的命令,并且可以通过键盘上下键切换命令。但是可以通过--without-readline禁用这个特性,或者可以指定--withlibedit-preferred选项来使用libedit 默认使用zlib压缩库 可通过--without-zlib选项来禁用 配置hosts 所有主机上都配置 [root@xl2 11] cat /etc/hosts127.0.0.1 localhost192.168.20.132 gtm192.168.20.133 xl1192.168.20.134 xl2 关闭防火墙、Selinux 所有主机都执行 关闭防火墙: [root@gtm ~] systemctl stop firewalld.service[root@gtm ~] systemctl disable firewalld.service selinux设置: [root@gtm ~]vim /etc/selinux/config 设置SELINUX=disabled,保存退出。 This file controls the state of SELinux on the system. SELINUX= can take one of these three values: enforcing - SELinux security policy is enforced. permissive - SELinux prints warnings instead of enforcing. disabled - No SELinux policy is loaded.SELINUX=disabled SELINUXTYPE= can take one of three two values: targeted - Targeted processes are protected, minimum - Modification of targeted policy. Only selected processes are protected. mls - Multi Level Security protection. 安装依赖包 所有主机上都执行 yum install -y flex bison readline-devel zlib-devel openjade docbook-style-dsssl gcc 创建用户 所有主机上都执行 [root@gtm ~] useradd postgres[root@gtm ~] passwd postgres[root@gtm ~] su - postgres[root@gtm ~] mkdir ~/.ssh[root@gtm ~] chmod 700 ~/.ssh 配置SSH免密登录 仅仅在gtm节点配置如下操作: [root@gtm ~] su - postgres[postgres@gtm ~] ssh-keygen -t rsa[postgres@gtm ~] cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys[postgres@gtm ~] chmod 600 ~/.ssh/authorized_keys 将刚生成的认证文件拷贝到xl1到xl2中,使得gtm节点可以免密码登录xl1~xl2的任意一个节点: [postgres@gtm ~] scp ~/.ssh/authorized_keys postgres@xl1:~/.ssh/[postgres@gtm ~] scp ~/.ssh/authorized_keys postgres@xl2:~/.ssh/ 对所有提示都不要输入,直接enter下一步。直到最后,因为第一次要求输入目标机器的用户密码,输入即可。 下载源码 下载地址:https://www.postgres-xl.org/download/ [root@slave ~] ll postgres-xl-10r1.1.tar.gz-rw-r--r-- 1 root root 28121666 May 30 05:21 postgres-xl-10r1.1.tar.gz 编译、安装Postgres-XL 所有节点都安装,编译需要一点时间,最好同时进行编译。 [root@slave ~] tar xvf postgres-xl-10r1.1.tar.gz[root@slave ~] ./configure --prefix=/home/postgres/pgxl/[root@slave ~] make[root@slave ~] make install[root@slave ~] cd contrib/ --安装必要的工具,在gtm节点上安装即可[root@slave ~] make[root@slave ~] make install 配置环境变量 所有节点都要配置 进入postgres用户,修改其环境变量,开始编辑 [root@gtm ~]su - postgres[postgres@gtm ~]vi .bashrc --不是.bash_profile 在打开的文件末尾,新增如下变量配置: export PGHOME=/home/postgres/pgxlexport LD_LIBRARY_PATH=$PGHOME/lib:$LD_LIBRARY_PATHexport PATH=$PGHOME/bin:$PATH 按住esc,然后输入:wq!保存退出。输入以下命令对更改重启生效。 [postgres@gtm ~] source .bashrc --不是.bash_profile 输入以下语句,如果输出变量结果,代表生效 [postgres@gtm ~] echo $PGHOME 应该输出/home/postgres/pgxl代表生效 配置集群 生成pgxc_ctl.conf配置文件 [postgres@gtm ~] pgxc_ctl prepare/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxl/pgxc_ctl/pgxc_ctl_bash.ERROR: File "/home/postgres/pgxl/pgxc_ctl/pgxc_ctl.conf" not found or not a regular file. No such file or directoryInstalling pgxc_ctl_bash script as /home/postgres/pgxl/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxl/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxl/pgxc_ctl --configuration /home/postgres/pgxl/pgxc_ctl/pgxc_ctl.confFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxl/pgxc_ctl 配置pgxc_ctl.conf 新建/home/postgres/pgxc_ctl/pgxc_ctl.conf文件,编辑如下: 对着模板文件一个一个修改,否则会造成初始化过程出现各种神奇问题。 pgxcInstallDir=$PGHOMEpgxlDATA=$PGHOME/data pgxcOwner=postgres---- GTM Master -----------------------------------------gtmName=gtmgtmMasterServer=gtmgtmMasterPort=6666gtmMasterDir=$pgxlDATA/nodes/gtmgtmSlave=y Specify y if you configure GTM Slave. Otherwise, GTM slave will not be configured and all the following variables will be reset.gtmSlaveName=gtmSlavegtmSlaveServer=gtm value none means GTM slave is not available. Give none if you don't configure GTM Slave.gtmSlavePort=20001 Not used if you don't configure GTM slave.gtmSlaveDir=$pgxlDATA/nodes/gtmSlave Not used if you don't configure GTM slave.---- GTM-Proxy Master -------gtmProxyDir=$pgxlDATA/nodes/gtm_proxygtmProxy=y gtmProxyNames=(gtm_pxy1 gtm_pxy2) gtmProxyServers=(xl1 xl2) gtmProxyPorts=(6666 6666) gtmProxyDirs=($gtmProxyDir $gtmProxyDir) ---- Coordinators ---------coordMasterDir=$pgxlDATA/nodes/coordcoordNames=(coord1 coord2) coordPorts=(5432 5432) poolerPorts=(6667 6667) coordPgHbaEntries=(0.0.0.0/0)coordMasterServers=(xl1 xl2) coordMasterDirs=($coordMasterDir $coordMasterDir)coordMaxWALsernder=0 没设置备份节点,设置为0coordMaxWALSenders=($coordMaxWALsernder $coordMaxWALsernder) 数量保持和coordMasterServers一致coordSlave=n---- Datanodes ----------datanodeMasterDir=$pgxlDATA/nodes/dn_masterprimaryDatanode=xl1 主数据节点datanodeNames=(node1 node2)datanodePorts=(5433 5433) datanodePoolerPorts=(6668 6668) datanodePgHbaEntries=(0.0.0.0/0)datanodeMasterServers=(xl1 xl2)datanodeMasterDirs=($datanodeMasterDir $datanodeMasterDir)datanodeMaxWalSender=4datanodeMaxWALSenders=($datanodeMaxWalSender $datanodeMaxWalSender) 集群初始化,启动,停止 初始化 pgxc_ctl -c /home/postgres/pgxc_ctl/pgxc_ctl.conf init all 输出结果: /bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.conf/home/postgres/pgxc_ctl/pgxc_ctl.conf: line 189: $coordExtraConfig: ambiguous redirectFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlStopping all the coordinator masters.Stopping coordinator master coord1.Stopping coordinator master coord2.pg_ctl: directory "/home/postgres/pgxc/nodes/coord/coord1" does not existpg_ctl: directory "/home/postgres/pgxc/nodes/coord/coord2" does not existDone.Stopping all the datanode masters.Stopping datanode master datanode1.Stopping datanode master datanode2.pg_ctl: PID file "/home/postgres/pgxc/nodes/datanode/datanode1/postmaster.pid" does not existIs server running?Done.Stop GTM masterwaiting for server to shut down.... doneserver stopped[postgres@gtm ~]$ echo $PGHOME/home/postgres/pgxl[postgres@gtm ~]$ ll /home/postgres/pgxl/pgxc/nodes/gtm/gtm.^C[postgres@gtm ~]$ pgxc_ctl -c /home/postgres/pgxc_ctl/pgxc_ctl.conf init all/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.conf/home/postgres/pgxc_ctl/pgxc_ctl.conf: line 189: $coordExtraConfig: ambiguous redirectFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlInitialize GTM masterERROR: target directory (/home/postgres/pgxc/nodes/gtm) exists and not empty. Skip GTM initilializationDone.Start GTM masterserver startingInitialize all the coordinator masters.Initialize coordinator master coord1.ERROR: target coordinator master coord1 is running now. Skip initilialization.Initialize coordinator master coord2.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/coord/coord2 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.Done.Starting coordinator master.Starting coordinator master coord1ERROR: target coordinator master coord1 is already running now. Skip initialization.Starting coordinator master coord22019-05-30 21:09:25.562 EDT [2148] LOG: listening on IPv4 address "0.0.0.0", port 54322019-05-30 21:09:25.562 EDT [2148] LOG: listening on IPv6 address "::", port 54322019-05-30 21:09:25.563 EDT [2148] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"2019-05-30 21:09:25.601 EDT [2149] LOG: database system was shut down at 2019-05-30 21:09:22 EDT2019-05-30 21:09:25.605 EDT [2148] LOG: database system is ready to accept connections2019-05-30 21:09:25.612 EDT [2156] LOG: cluster monitor startedDone.Initialize all the datanode masters.Initialize the datanode master datanode1.Initialize the datanode master datanode2.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/datanode/datanode1 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/datanode/datanode2 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.Done.Starting all the datanode masters.Starting datanode master datanode1.WARNING: datanode master datanode1 is running now. Skipping.Starting datanode master datanode2.2019-05-30 21:09:33.352 EDT [2404] LOG: listening on IPv4 address "0.0.0.0", port 154322019-05-30 21:09:33.352 EDT [2404] LOG: listening on IPv6 address "::", port 154322019-05-30 21:09:33.355 EDT [2404] LOG: listening on Unix socket "/tmp/.s.PGSQL.15432"2019-05-30 21:09:33.392 EDT [2404] LOG: redirecting log output to logging collector process2019-05-30 21:09:33.392 EDT [2404] HINT: Future log output will appear in directory "pg_log".Done.psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"Done.psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"Done.[postgres@gtm ~]$ pgxc_ctl -c /home/postgres/pgxc_ctl/pgxc_ctl.conf stop all/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.conf/home/postgres/pgxc_ctl/pgxc_ctl.conf: line 189: $coordExtraConfig: ambiguous redirectFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlStopping all the coordinator masters.Stopping coordinator master coord1.Stopping coordinator master coord2.pg_ctl: directory "/home/postgres/pgxc/nodes/coord/coord1" does not existDone.Stopping all the datanode masters.Stopping datanode master datanode1.Stopping datanode master datanode2.pg_ctl: PID file "/home/postgres/pgxc/nodes/datanode/datanode1/postmaster.pid" does not existIs server running?Done.Stop GTM masterwaiting for server to shut down.... doneserver stopped[postgres@gtm ~]$ pgxc_ctl/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.conf/home/postgres/pgxc_ctl/pgxc_ctl.conf: line 189: $coordExtraConfig: ambiguous redirectFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlPGXC monitor allNot running: gtm masterRunning: coordinator master coord1Not running: coordinator master coord2Running: datanode master datanode1Not running: datanode master datanode2PGXC stop coordinator master coord1Stopping coordinator master coord1.pg_ctl: directory "/home/postgres/pgxc/nodes/coord/coord1" does not existDone.PGXC stop datanode master datanode1Stopping datanode master datanode1.pg_ctl: PID file "/home/postgres/pgxc/nodes/datanode/datanode1/postmaster.pid" does not existIs server running?Done.PGXC monitor allNot running: gtm masterRunning: coordinator master coord1Not running: coordinator master coord2Running: datanode master datanode1Not running: datanode master datanode2PGXC monitor allNot running: gtm masterNot running: coordinator master coord1Not running: coordinator master coord2Not running: datanode master datanode1Not running: datanode master datanode2PGXC exit[postgres@gtm ~]$ pgxc_ctl -c /home/postgres/pgxc_ctl/pgxc_ctl.conf init all/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.conf/home/postgres/pgxc_ctl/pgxc_ctl.conf: line 189: $coordExtraConfig: ambiguous redirectFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlInitialize GTM masterERROR: target directory (/home/postgres/pgxc/nodes/gtm) exists and not empty. Skip GTM initilializationDone.Start GTM masterserver startingInitialize all the coordinator masters.Initialize coordinator master coord1.Initialize coordinator master coord2.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/coord/coord1 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/coord/coord2 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.Done.Starting coordinator master.Starting coordinator master coord1Starting coordinator master coord22019-05-30 21:13:03.998 EDT [25137] LOG: listening on IPv4 address "0.0.0.0", port 54322019-05-30 21:13:03.998 EDT [25137] LOG: listening on IPv6 address "::", port 54322019-05-30 21:13:04.000 EDT [25137] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"2019-05-30 21:13:04.038 EDT [25138] LOG: database system was shut down at 2019-05-30 21:13:00 EDT2019-05-30 21:13:04.042 EDT [25137] LOG: database system is ready to accept connections2019-05-30 21:13:04.049 EDT [25145] LOG: cluster monitor started2019-05-30 21:13:04.020 EDT [2730] LOG: listening on IPv4 address "0.0.0.0", port 54322019-05-30 21:13:04.020 EDT [2730] LOG: listening on IPv6 address "::", port 54322019-05-30 21:13:04.021 EDT [2730] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"2019-05-30 21:13:04.057 EDT [2731] LOG: database system was shut down at 2019-05-30 21:13:00 EDT2019-05-30 21:13:04.061 EDT [2730] LOG: database system is ready to accept connections2019-05-30 21:13:04.062 EDT [2738] LOG: cluster monitor startedDone.Initialize all the datanode masters.Initialize the datanode master datanode1.Initialize the datanode master datanode2.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/datanode/datanode1 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/datanode/datanode2 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.Done.Starting all the datanode masters.Starting datanode master datanode1.Starting datanode master datanode2.2019-05-30 21:13:12.077 EDT [25392] LOG: listening on IPv4 address "0.0.0.0", port 154322019-05-30 21:13:12.077 EDT [25392] LOG: listening on IPv6 address "::", port 154322019-05-30 21:13:12.079 EDT [25392] LOG: listening on Unix socket "/tmp/.s.PGSQL.15432"2019-05-30 21:13:12.114 EDT [25392] LOG: redirecting log output to logging collector process2019-05-30 21:13:12.114 EDT [25392] HINT: Future log output will appear in directory "pg_log".2019-05-30 21:13:12.079 EDT [2985] LOG: listening on IPv4 address "0.0.0.0", port 154322019-05-30 21:13:12.079 EDT [2985] LOG: listening on IPv6 address "::", port 154322019-05-30 21:13:12.081 EDT [2985] LOG: listening on Unix socket "/tmp/.s.PGSQL.15432"2019-05-30 21:13:12.117 EDT [2985] LOG: redirecting log output to logging collector process2019-05-30 21:13:12.117 EDT [2985] HINT: Future log output will appear in directory "pg_log".Done.psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"Done.psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"Done. 启动 pgxc_ctl -c /home/postgres/pgxc_ctl/pgxc_ctl.conf start all 关闭 pgxc_ctl -c /home/postgres/pgxc_ctl/pgxc_ctl.conf stop all 查看集群状态 [postgres@gtm ~]$ pgxc_ctl monitor all/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.conf/home/postgres/pgxc_ctl/pgxc_ctl.conf: line 189: $coordExtraConfig: ambiguous redirectFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlRunning: gtm masterRunning: coordinator master coord1Running: coordinator master coord2Running: datanode master datanode1Running: datanode master datanode2 配置集群信息 分别在数据节点、协调器节点上分别执行以下命令: 注:本节点只执行修改操作即可(alert node),其他节点执行创建命令(create node)。因为本节点已经包含本节点的信息。 create node coord1 with (type=coordinator,host=xl1, port=5432);create node coord2 with (type=coordinator,host=xl2, port=5432);alter node coord1 with (type=coordinator,host=xl1, port=5432);alter node coord2 with (type=coordinator,host=xl2, port=5432);create node datanode1 with (type=datanode, host=xl1,port=15432,primary=true,PREFERRED);create node datanode2 with (type=datanode, host=xl2,port=15432);alter node datanode1 with (type=datanode, host=xl1,port=15432,primary=true,PREFERRED);alter node datanode2 with (type=datanode, host=xl2,port=15432);select pgxc_pool_reload(); 分别登陆数据节点、协调器节点验证 postgres= select from pgxc_node;node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id-----------+-----------+-----------+-----------+----------------+------------------+-------------coord1 | C | 5432 | xl1 | f | f | 1885696643coord2 | C | 5432 | xl2 | f | f | -1197102633datanode2 | D | 15432 | xl2 | f | f | -905831925datanode1 | D | 15432 | xl1 | t | f | 888802358(4 rows) 测试 插入数据 在数据节点1,执行相关操作。 通过协调器端口登录PG [postgres@xl1 ~]$ psql -p 5432psql (PGXL 10r1.1, based on PG 10.6 (Postgres-XL 10r1.1))Type "help" for help.postgres= create database lei;CREATE DATABASEpostgres= \c lei;You are now connected to database "lei" as user "postgres".lei= create table test1(id int,name text);CREATE TABLElei= insert into test1(id,name) select generate_series(1,8),'测试';INSERT 0 8lei= select from test1;id | name----+------1 | 测试2 | 测试5 | 测试6 | 测试8 | 测试3 | 测试4 | 测试7 | 测试(8 rows) 注:默认创建的表为分布式表,也就是每个数据节点值存储表的部分数据。关于表类型具体说明,下面有说明。 通过15432端口登录数据节点,查看数据 有5条数据 [postgres@xl1 ~]$ psql -p 15432psql (PGXL 10r1.1, based on PG 10.6 (Postgres-XL 10r1.1))Type "help" for help.postgres= \c lei;You are now connected to database "lei" as user "postgres".lei= select from test1;id | name----+------1 | 测试2 | 测试5 | 测试6 | 测试8 | 测试(5 rows) 登录到节点2,查看数据 有3条数据 [postgres@xl2 ~]$ psql -p15432psql (PGXL 10r1.1, based on PG 10.6 (Postgres-XL 10r1.1))Type "help" for help.postgres= \c lei;You are now connected to database "lei" as user "postgres".lei= select from test1;id | name----+------3 | 测试4 | 测试7 | 测试(3 rows) 两个节点的数据加起来整个8条,没有问题。 至此Postgre-XL集群搭建完成。 创建数据库、表时可能会出现以下错误: ERROR: Failed to get pooled connections 是因为pg_hba.conf配置不对,所有节点加上host all all 192.168.20.0/0 trust并重启集群即可。 ERROR: No Datanode defined in cluster 首先确认是否创建了数据节点,也就是create node相关的命令。如果创建了则执行select pgxc_pool_reload();使其生效即可。 集群管理与应用 表类型说明 REPLICATION表:各个datanode节点中,表的数据完全相同,也就是说,插入数据时,会分别在每个datanode节点插入相同数据。读数据时,只需要读任意一个datanode节点上的数据。 建表语法: CREATE TABLE repltab (col1 int, col2 int) DISTRIBUTE BY REPLICATION; DISTRIBUTE :会将插入的数据,按照拆分规则,分配到不同的datanode节点中存储,也就是sharding技术。每个datanode节点只保存了部分数据,通过coordinate节点可以查询完整的数据视图。 CREATE TABLE disttab(col1 int, col2 int, col3 text) DISTRIBUTE BY HASH(col1); 模拟数据插入 任意登录一个coordinate节点进行建表操作 [postgres@gtm ~]$ psql -h xl1 -p 5432 -U postgrespostgres= INSERT INTO disttab SELECT generate_series(1,100), generate_series(101, 200), 'foo';INSERT 0 100postgres= INSERT INTO repltab SELECT generate_series(1,100), generate_series(101, 200);INSERT 0 100 查看数据分布结果: DISTRIBUTE表分布结果 postgres= SELECT xc_node_id, count() FROM disttab GROUP BY xc_node_id;xc_node_id | count ------------+-------1148549230 | 42-927910690 | 58(2 rows) REPLICATION表分布结果 postgres= SELECT count() FROM repltab;count -------100(1 row) 查看另一个datanode2中repltab表结果 [postgres@datanode2 pgxl9.5]$ psql -p 15432psql (PGXL 10r1.1, based on PG 10.6 (Postgres-XL 10r1.1))Type "help" for help.postgres= SELECT count() FROM repltab;count -------100(1 row) 结论:REPLICATION表中,datanode1,datanode2中表是全部数据,一模一样。而DISTRIBUTE表,数据散落近乎平均分配到了datanode1,datanode2节点中。 新增数据节点与数据重分布 在线新增节点、并重新分布数据。 新增datanode节点 在gtm集群管理节点上执行pgxc_ctl命令 [postgres@gtm ~]$ pgxc_ctl/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.confFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlPGXC 在服务器xl3上,新增一个master角色的datanode节点,名称是datanode3 端口号暂定5430,pool master暂定6669 ,指定好数据目录位置,从两个节点升级到3个节点,之后要写3个none none应该是datanodeSpecificExtraConfig或者datanodeSpecificExtraPgHba配置PGXC add datanode master datanode3 xl3 15432 6671 /home/postgres/pgxc/nodes/datanode/datanode3 none none none 等待新增完成后,查询集群节点状态: postgres= select from pgxc_node;node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id-----------+-----------+-----------+-----------+----------------+------------------+-------------datanode1 | D | 15432 | xl1 | t | f | 888802358datanode2 | D | 15432 | xl2 | f | f | -905831925datanode3 | D | 15432 | xl3 | f | f | -705831925coord1 | C | 5432 | xl1 | f | f | 1885696643coord2 | C | 5432 | xl2 | f | f | -1197102633(4 rows) 节点新增完毕 数据重新分布 由于新增节点后无法自动完成数据重新分布,需要手动操作。 DISTRIBUTE表分布在了node1,node2节点上,如下: postgres= SELECT xc_node_id, count() FROM disttab GROUP BY xc_node_id;xc_node_id | count ------------+-------1148549230 | 42-927910690 | 58(2 rows) 新增一个节点后,将sharding表数据重新分配到三个节点上,将repl表复制到新节点 重分布sharding表postgres= ALTER TABLE disttab ADD NODE (datanode3);ALTER TABLE 复制数据到新节点postgres= ALTER TABLE repltab ADD NODE (datanode3);ALTER TABLE 查看新的数据分布: postgres= SELECT xc_node_id, count() FROM disttab GROUP BY xc_node_id;xc_node_id | count ------------+--------700122826 | 36-927910690 | 321148549230 | 32(3 rows) 登录datanode3(新增的时候,放在了xl3服务器上,端口15432)节点查看数据: [postgres@gtm ~]$ psql -h xl3 -p 15432 -U postgrespsql (PGXL 10r1.1, based on PG 10.6 (Postgres-XL 10r1.1))Type "help" for help.postgres= select count() from repltab;count -------100(1 row) 很明显,通过 ALTER TABLE tt ADD NODE (dn)命令,可以将DISTRIBUTE表数据重新分布到新节点,重分布过程中会中断所有事务。可以将REPLICATION表数据复制到新节点。 从datanode节点中回收数据 postgres= ALTER TABLE disttab DELETE NODE (datanode3);ALTER TABLEpostgres= ALTER TABLE repltab DELETE NODE (datanode3);ALTER TABLE 删除数据节点 Postgresql-XL并没有检查将被删除的datanode节点是否有replicated/distributed表的数据,为了数据安全,在删除之前需要检查下被删除节点上的数据,有数据的话,要回收掉分配到其他节点,然后才能安全删除。删除数据节点分为四步骤: 1.查询要删除节点dn3的oid postgres= SELECT oid, FROM pgxc_node;oid | node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id -------+-----------+-----------+-----------+-----------+----------------+------------------+-------------11819 | coord1 | C | 5432 | datanode1 | f | f | 188569664316384 | coord2 | C | 5432 | datanode2 | f | f | -119710263316385 | node1 | D | 5433 | datanode1 | f | t | 114854923016386 | node2 | D | 5433 | datanode2 | f | f | -92791069016397 | dn3 | D | 5430 | datanode1 | f | f | -700122826(5 rows) 2.查询dn3对应的oid中是否有数据 testdb= SELECT FROM pgxc_class WHERE nodeoids::integer[] @> ARRAY[16397];pcrelid | pclocatortype | pcattnum | pchashalgorithm | pchashbuckets | nodeoids ---------+---------------+----------+-----------------+---------------+-------------------16388 | H | 1 | 1 | 4096 | 16397 16385 1638616394 | R | 0 | 0 | 0 | 16397 16385 16386(2 rows) 3.有数据的先回收数据 postgres= ALTER TABLE disttab DELETE NODE (dn3);ALTER TABLEpostgres= ALTER TABLE repltab DELETE NODE (dn3);ALTER TABLEpostgres= SELECT FROM pgxc_class WHERE nodeoids::integer[] @> ARRAY[16397];pcrelid | pclocatortype | pcattnum | pchashalgorithm | pchashbuckets | nodeoids ---------+---------------+----------+-----------------+---------------+----------(0 rows) 4.安全删除dn3 PGXC$ remove datanode master dn3 clean 故障节点FAILOVER 1.查看当前集群状态 [postgres@gtm ~]$ psql -h xl1 -p 5432psql (PGXL 10r1.1, based on PG 10.6 (Postgres-XL 10r1.1))Type "help" for help.postgres= SELECT oid, FROM pgxc_node;oid | node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id-------+-----------+-----------+-----------+-----------+----------------+------------------+-------------11739 | coord1 | C | 5432 | xl1 | f | f | 188569664316384 | coord2 | C | 5432 | xl2 | f | f | -119710263316387 | datanode2 | D | 15432 | xl2 | f | f | -90583192516388 | datanode1 | D | 15432 | xl1 | t | t | 888802358(4 rows) 2.模拟datanode1节点故障 直接关闭即可 PGXC stop -m immediate datanode master datanode1Stopping datanode master datanode1.Done. 3.测试查询 只要查询涉及到datanode1上的数据,那么该查询就会报错 postgres= SELECT xc_node_id, count() FROM disttab GROUP BY xc_node_id;WARNING: failed to receive file descriptors for connectionsERROR: Failed to get pooled connectionsHINT: This may happen because one or more nodes are currently unreachable, either because of node or network failure.Its also possible that the target node may have hit the connection limit or the pooler is configured with low connections.Please check if all nodes are running fine and also review max_connections and max_pool_size configuration parameterspostgres= SELECT xc_node_id, FROM disttab WHERE col1 = 3;xc_node_id | col1 | col2 | col3------------+------+------+-------905831925 | 3 | 103 | foo(1 row) 测试发现,查询范围如果涉及到故障的node1节点,会报错,而查询的数据范围不在node1上的话,仍然可以查询。 4.手动切换 要想切换,必须要提前配置slave节点。 PGXC$ failover datanode node1 切换完成后,查询集群 postgres= SELECT oid, FROM pgxc_node;oid | node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id -------+-----------+-----------+-----------+-----------+----------------+------------------+-------------11819 | coord1 | C | 5432 | datanode1 | f | f | 188569664316384 | coord2 | C | 5432 | datanode2 | f | f | -119710263316386 | node2 | D | 15432 | datanode2 | f | f | -92791069016385 | node1 | D | 15433 | datanode2 | f | t | 1148549230(4 rows) 发现datanode1节点的ip和端口都已经替换为配置的slave了。 本篇文章为转载内容。原文链接:https://blog.csdn.net/qianglei6077/article/details/94379331。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-30 11:09:03
94
转载
转载文章
...学领域,尤其是涉及到时间管理和数据分析时,这种转换机制尤为重要。 近期,随着大数据和实时流处理技术的发展,对时间精度的要求愈发严格。例如,在监控系统中,记录每项操作的耗时通常以毫秒为单位,而为了便于运维人员直观判断性能瓶颈,就需要将这些毫秒数转化为更易于理解的时间格式。此外,在游戏开发、金融交易、物联网设备数据同步等领域,精准的时间戳处理同样至关重要。 另外,Java 8及以上版本引入了全新的日期和时间API(java.time包),提供了更强大且灵活的方式来处理日期、时间和时区问题。LocalDateTime、Duration和Period等类可以高效准确地完成时间单位之间的转换,包括毫秒到小时、分钟、秒的转换,同时支持格式化输出。 不仅如此,对于大规模分布式系统,微服务架构下的各个组件间的时间同步也是基础能力之一,NTP(网络时间协议)等协议便承担着将UTC时间精确到毫秒级同步到全球各节点的任务。而在呈现给终端用户时,仍需经过类似上述"convertMillis"方法的处理,转化为人性化的“小时:分钟:秒”格式。 综上所述,无论是基础的编程实践还是高级的应用场景,将毫秒数转换为小时、分钟、秒不仅是一种基本技能,更是解决复杂时间管理问题的关键环节。与时俱进地掌握并运用相关技术和最佳实践,有助于提升系统的可靠性和用户体验。
2024-03-25 12:35:31
506
转载
JSON
...理JSON里的日期和时间其实挺让人挠头的,特别是当你还得在各种时区和日期格式之间换来换去的时候,那简直就是一场时区版的"找不同"游戏啊!来吧,伙计们,今天咱们要一起探索一个超实用的话题——如何轻松搞定JSON里的日期时间格式!就像煮咖啡一样,我们要一步步把那些看似复杂的日期数据结构梳理得井井有条,让你的操作行云流水,帅气非凡!跟着我,咱们边聊边实战,让这些数字瞬间变得亲切又好玩! 二、JSON日期时间格式的基本概念 1. JSON中的日期表示法 JSON本身并不直接支持日期时间类型,它通常将日期时间转换为字符串,使用ISO 8601标准格式:YYYY-MM-DDTHH:mm:ss.sssZ。例如: json { "createdAt": "2023-01-01T12:00:00.000Z" } 这里,Z表示的是协调世界时(UTC)。 三、日期时间格式的常见问题与解决方案 2. 处理本地时间和UTC时间 当你的应用需要处理用户所在地区的日期时间时,可能需要进行时区转换。JavaScript的Date对象可以方便地完成这个任务。例如,从UTC到本地时间: javascript const dateInUtc = new Date("2023-01-01T12:00:00.000Z"); const localDate = new Date(dateInUtc.getTime() + dateInUtc.getTimezoneOffset() 60 1000); console.log(localDate.toISOString()); // 输出本地时间的ISO格式 3. 自定义格式化 如果你想输出特定格式的日期时间,可以借助第三方库如moment.js或date-fns。例如,使用date-fns: javascript import { format } from 'date-fns'; const formattedDate = format(new Date(), 'yyyy-MM-dd HH:mm:ss'); console.log(formattedDate); // 输出自定义格式的日期字符串 四、跨平台兼容性和API设计 4. 跨平台兼容性 在处理跨平台的API接口时,确保日期时间格式的一致性至关重要。JSON.stringify()和JSON.parse()方法默认会按照ISO 8601格式进行序列化和反序列化。但如果你的后端和前端使用的时区不同,可能会引发混淆。这时,可以通过传递一个可选的时间zone参数来指定: javascript const date = new Date(); const jsonDate = JSON.stringify(date, null, 2, "America/New_York"); // 使用纽约时区 五、总结与展望 5. 总结 JSON日期时间格式化虽然看似简单,但在实际应用中可能会遇到各种挑战。懂规矩,还得配上好工具和诀窍,这样玩数据才能又快又溜!就像厨师炒菜,得知道怎么配料,用啥锅具,才能做出美味佳肴一样。嘿,你知道吗?JavaScript的世界就像个不停冒泡的派对,新潮的库和工具层出不穷,比如那个超酷的day.js和超级实用的js-time-ago,它们让日期时间这事儿变得轻松多了,简直就像魔法一样! 通过这次探索,我们不仅掌握了JSON日期时间的格式,还了解了如何优雅地解决跨平台和时区问题。记住,无论何时,面对复杂的数据格式,耐心和实践总是关键。希望这篇文章能帮你更好地驾驭JSON中的日期时间格式,提升你的开发效率。 --- 本文作者是一位热爱编程的开发者,对JSON和日期时间处理有着深厚的兴趣。在日常的码农生涯里,他深感不少小伙伴在这个领域摸不着头脑,于是他慷慨解囊,把自己摸爬滚打的经验和领悟一股脑儿分享出来,就想让大家能少踩点坑,少走点冤枉路。
2024-04-14 10:31:46
564
繁华落尽
Beego
..., 全局唯一标识符(Universally Unique Identifier),是一种由特定算法生成的长度为128位的数字,通常以32个十六进制数表示。在分布式系统中广泛应用,用于确保每个对象或实体拥有一个全球范围内都不会重复的唯一ID,可以有效避免数据冲突和混淆。 ORM(Object-Relational Mapping) , 对象关系映射,是一种程序技术,用于将数据库中的表结构与编程语言中的对象模型进行关联和转换。在Beego框架中,ORM通过简化数据库操作,使得开发者可以直接对数据库记录进行面向对象的操作,如定义模型、执行CRUD(增删改查)操作等。例如,在文章中提及的User模型,其ID uint orm:column(id);auto 表示在数据库中创建一个自动递增的主键字段。 分布式系统 , 一种由多台计算机通过网络通信协议协同工作,共同完成任务的系统架构。在这样的系统中,各个节点相对独立,各自处理部分任务,并通过网络实现信息交换和资源共享。由于分布式系统的特性,因此需要全局唯一的标识符(如UUID)来保证不同节点生成的数据不会产生标识冲突。 Snowflake算法 , Twitter开源的一种分布式ID生成算法,能够在分布式环境下生成全局唯一且趋势递增的ID。该算法结合了时间戳、数据中心ID、机器ID和序列号四部分信息,具有良好的性能、高可用性和可扩展性,适用于云原生环境下的大规模服务集群。在实际应用中,Snowflake算法生成的ID既满足了唯一性需求,又能够反映出ID生成的时间顺序及生成位置信息。
2023-11-17 22:27:26
589
翡翠梦境-t
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 java实现点赞(顶)功能 需求分析 分析:1.必须先登录,否则提示2.第一次点赞(顶),点赞操作,点赞数+1,提示顶成功2.第二次点赞(顶),没有操作,提示今天顶过了---------------------------------------------核心问题:1>怎么区分当前请求时顶成功操作(第一次顶)还是今天已经顶过(第二次顶)2>怎么考虑今天已顶过 ----------------------------------------------核心问题需要区分是第一次顶还是的二次顶,这种请求操作属于有状态请求操作,需要后端设计一个记号,这个记号注意需要设置时效性(今天最后一秒到当前时间间隔[单位是秒])//如何设计记号?------------------------------------------------方案1:可以参照之前攻略收藏记号操作方式,设计一个key,用户uid做区分(保证唯一),value值是攻略id集合,一顶将攻略uid添加集合中方案2:设计一个key,使用用户uid跟攻略sid进行区分,value值随意,需要设置有效性 实现步骤 1.创建一个点赞接口,传入当前点赞攻略sid,获取当前登录用户uid2.通过sid跟uid拼接记号的key3.判断key是否存在如果存在,说明今天已经点赞(顶)过,不做任何处理,页面提示如果不存在,说明具体没点赞(顶)过,获取vo对象,点赞数属性+1,将记号缓存到redis中,设置过期时间:今天最后一秒到当前时间间隔[单位是秒]4.更新vo对象 具体实现 //判断是否顶过@Overridepublic boolean strategyThumbup(String id, String sid) {String key = RedisKeys.USER_STRATEGY_THUMBUP.join(id, sid);//如果不包含,表示没有顶过,执行点赞,点赞数+1,并设置key有效时间if (!template.hasKey(key)) {StrategyStatisVO statisVO = this.getStrategyStatisVO(sid);statisVO.setThumbsupnum(statisVO.getThumbsupnum() + 1);this.setStrategyStatisVO(statisVO);//拿到最晚时间Date endDate = DateUtil.getEndDate(new Date());//计算时间间隔long time = DateUtil.getDateBetween(endDate, new Date());//设置有效时间template.opsForValue().set(key, "1", time, TimeUnit.SECONDS);return true;}return false;}-----------------------------------------------------------------------------------//时间工具类public class DateUtil {/ 获取两个时间的间隔(秒) /public static long getDateBetween(Date d1, Date d2){return Math.abs((d1.getTime()-d2.getTime())/1000);//取绝对值}public static Date getEndDate(Date date) {if (date == null) {return null;}Calendar c = Calendar.getInstance();c.setTime(date);c.set(Calendar.HOUR_OF_DAY,23);c.set(Calendar.MINUTE,59);c.set(Calendar.SECOND,59);return c.getTime();} } 小结 1.核心问题需要区分是第一次顶还是的二次顶,这种请求操作属于有状态请求操作2.有状态请求操作我们需要设置记号,问题的关键在于记号的设计3.这个记号,我们也可以使用与点赞/收藏功能类似的记号,就是以用户id为key,然后将顶的文章id放到集合中为value4.但是更推荐使用以用户id和攻略id拼接而成的为key,value随意取5.我们操作时只需要判断key是否存在,存在,我们什么操作也不用做,不存在,我们就将点赞(数)+1,然后设置key的时间即可6.最后更新vo对象7.难点在于时间的设置,看工具类,这个key键设置体现了key键的唯一性,灵活性和时效性 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_47555380/article/details/108081752。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-08-31 21:48:44
128
转载
转载文章
...)。Angular Universal项目允许开发者在服务器端预渲染应用,从而提升网页加载速度和搜索引擎可见性,这对于电商类网站的商品评价列表展示场景尤其重要。 总之,虽然文章关注的是AngularJS 1.7中的具体实践,但放眼当前的技术趋势,不断学习和掌握新版Angular框架及其生态系统中的最新工具和技术,将有助于开发者更好地应对复杂多变的前端需求,高效构建出实用高效的商品评价系统和其他丰富的Web应用程序。
2023-10-12 14:36:16
72
转载
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 😎 知识点概览 为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。 本章节为【学成在线】项目的 day15 的内容 根据 课程ID 搜索该课程已发布的课程信息,并返回该课程的所有课程计划信息。 将指定课程 发布时 所的课程计划的媒资信息保存到 teachplan_media_publish 表中, 根据 课程计划id 搜索该课程计划所对应的媒资信息,需要用到的是该课程计划对应的 m3u8 地址,用于在线播放视频,该接口在课程管理服务中开发,供学习服务进行远程调用。 在学习服务中远程调用 课程计划媒资信息查询接口,获取该课程计划的视频播放的 m3u8 url地址,并返回给前端,前端使用该 url 进行视频的在线播放。 在线学习完整的测试流程:媒资信息的上传、选择、发布到前端门户、搜索门户测试,在线学习的播放视频。 目录 内容会比较多,小伙伴门可以根据目录进行按需查阅。 文章目录 😎 知识点概览 目录 一、学习页面:查询课程计划 0x01 需求分析 0x02 Api接口 0x03 服务端开发 Controller Service 测试 0x04 前端开发 配置NGINX虚拟主机 前端 API 方法 前端 API 方法调用 测试 二、学习页面:获取视频播放地址 0x01 需求分析 0x02 课程发布:储存媒资信息 需求分析 数据模型 Dao Service 测试 0x03 Logstash:扫描课程计划媒资 创建索引 创建模板文件 配置 mysql.conf 启动 logstash.bat Logstash多实例运行 0x04 搜素服务:查询课程媒资接口 需求分析 Api接口定义 Service Controller 测试 三、在线学习:接口开发 0x01 需求分析 0x02 搭建开发环境 0x03 Api接口 0x04 服务端开发 需求分析 搜索服务注册Eureka 搜索服务客户端 自定义错误代码 Service Controller 测试 0x05 前端开发 需求分析 api方法 配置代理 视频播放页面 简单的测试 完整的测试 1、上传文件 一些问题 ~~方案1:删除本地分块文件重新尝试上传~~ 方案2:检查前端提交的MD5值是否正确 2、为课程计划选择媒资信息 3、前端门户测试 四、待完善的一些功能 😁 认识作者 一、学习页面:查询课程计划 0x01 需求分析 到目前为止,我们已可以编辑课程计划信息并上传课程视频,下一步我们要实现在线学习页面动态读取章节对应的视频并进行播放。在线学习页面所需要的信息有两类: 课程计划信息 课程学习信息(视频地址、学习进度等) 如下图: 在线学习集成媒资管理的需求如下: 1、在线学习页面显示课程计划 2、点击课程计划播放该课程计划对应的视频 本章节实现学习页面动态显示课程计划,进入不同课程的学习页面右侧动态显示当前课程的课程计划。 0x02 Api接口 课程计划信息从哪里获取? 在课程发布完成后会自动发布到一个 course_pub 的表中,logstash 会自动将课程发布后的信息自动采集到 ES 索引库中,这些信息也包含课程计划信息。 所以考虑性能要求,课程发布后对课程的查询统一从 ES 索引库中查询。 前端通过请求 搜索服务 获取课程信息,需要单独在 搜索服务 中定义课程信息查询接口。 本接口接收课程id,查询课程所有信息返回给前端。 我们在搜素服务 API 下添加以下方法 @ApiOperation("根据id搜索课程发布信息")public Map<String,CoursePub> getdetail(String id); 返回的课程信息为 json 结构:key 为课程id,value 为课程内容。 0x03 服务端开发 在搜索服务中开发查询课程信息接口。 Controller 在搜素服务下添加以下方法 / 根据id搜索课程发布信息 @param id 课程id @return JSON数据/@Override@GetMapping("/getdetail/{id}")public Map<String, CoursePub> getdetail(@PathVariable("id")String id) {return esCourseService.getdetail(id);} Service / 根据id搜索课程发布信息 @param id 课程id @return JSON数据/public Map<String, CoursePub> getdetail(String id) {//设置索引SearchRequest searchRequest = new SearchRequest(es_index);//设置类型searchRequest.types(es_type);//创建搜索源对象SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//设置查询条件,根据id进行查询searchSourceBuilder.query(QueryBuilders.termQuery("id",id));//这里不使用source的原字段过滤,查询所有字段// searchSourceBuilder.fetchSource(new String[]{"name", "grade", "charge","pic"}, newString[]{});//设置搜索源对象searchRequest.source(searchSourceBuilder);//执行搜索SearchResponse searchResponse = null;try {searchResponse = restHighLevelClient.search(searchRequest);} catch (IOException e) {e.printStackTrace();}//获取搜索结果SearchHits hits = searchResponse.getHits();SearchHit[] searchHits = hits.getHits(); //获取最优结果Map<String,CoursePub> map = new HashMap<>();for (SearchHit hit: searchHits) {//从搜索结果中取值并添加到coursePub对象Map<String, Object> sourceAsMap = hit.getSourceAsMap();String courseId = (String) sourceAsMap.get("id");String name = (String) sourceAsMap.get("name");String grade = (String) sourceAsMap.get("grade");String charge = (String) sourceAsMap.get("charge");String pic = (String) sourceAsMap.get("pic");String description = (String) sourceAsMap.get("description");String teachplan = (String) sourceAsMap.get("teachplan");CoursePub coursePub = new CoursePub();coursePub.setId(courseId);coursePub.setName(name);coursePub.setPic(pic);coursePub.setGrade(grade);coursePub.setTeachplan(teachplan);coursePub.setDescription(description);//设置map对象map.put(courseId,coursePub);}return map;} 测试 使用 swagger-ui 或 postman 测试查询课程信息接口。 0x04 前端开发 配置NGINX虚拟主机 学习中心的二级域名为 ucenter.xuecheng.com ,我们在 nginx 中配置 ucenter 虚拟主机。 学成网用户中心server {listen 80;server_name ucenter.xuecheng.com;个人中心location / {proxy_pass http://ucenter_server_pool;} } 前端ucenterupstream ucenter_server_pool{server 127.0.0.1:7081 weight=10;server 127.0.0.1:13000 weight=10;} 在学习中心要调用搜索的 API,使用 Nginx 解决代理,如下图: 在 ucenter 虚拟主机下配置搜索 Api 代理路径 后台搜索(公开api)upstream search_server_pool{server 127.0.0.1:40100 weight=10;} 学成网用户中心server {listen 80;server_name ucenter.xuecheng.com;个人中心location / {proxy_pass http://ucenter_server_pool;}后端搜索服务location /openapi/search/ {proxy_pass http://search_server_pool/search/;} } 前端 API 方法 在学习中心 xc-ui-pc-leanring 对课程信息的查询属于基础常用功能,所以我们将课程查询的 api 方法定义在base 模块下,如下图: 在system.js 中定义课程查询方法: import http from './public'export const course_view = id => {return http.requestGet('/openapi/search/course/getdetail/'+id);} 前端 API 方法调用 在 learning_video.vue 页面中调用课程信息查询接口得到课程计划,将课程计划json 串转成对象。 xc-ui-pc-leanring/src/module/course/page/learning_video.vue 1、定义视图 课程计划 <!--课程计划部分代码--><div class="navCont"><div class="course-weeklist"><div class="nav nav-stacked" v-for="(teachplan_first, index) in teachplanList"><div class="tit nav-justified text-center"><i class="pull-left glyphicon glyphicon-th-list"></i>{ {teachplan_first.pname} }<i class="pull-right"></i></div><li v-if="teachplan_first.children!=null" v-for="(teachplan_second, index) in teachplan_first.children"><i class="glyphicon glyphicon-check"></i><a :href="url" @click="study(teachplan_second.id)">{ {teachplan_second.pname} }</a></li><!-- <div class="tit nav-justified text-center"><i class="pull-left glyphicon glyphicon-th-list"></i>第一章<i class="pull-right"></i></div><li ><i class="glyphicon glyphicon-check"></i><a :href="url" >第一节</a></li>--><!--<li><i class="glyphicon glyphicon-unchecked"></i>为什么分为A、B、C部分</li>--></div></div></div> 课程名称 <div class="top text-center">{ {coursename} }</div> 定义数据对象 data() {return {url:'',//当前urlcourseId:'',//课程idchapter:'',//章节Idcoursename:'',//课程名称coursepic:'',//课程图片teachplanList:[],//课程计划playerOptions: {//播放参数autoplay: false,controls: true,sources: [{type: "application/x-mpegURL",src: ''}]},} } 在 created 钩子方法中获取课程信息 created(){//当前请求的urlthis.url = window.location//课程idthis.courseId = this.$route.params.courseId//章节idthis.chapter = this.$route.params.chapter//查询课程信息systemApi.course_view(this.courseId).then((view_course)=>{if(!view_course || !view_course[this.courseId]){this.$message.error("获取课程信息失败,请重新进入此页面!")return ;} let courseInfo = view_course[this.courseId]console.log(courseInfo)this.coursename = courseInfo.nameif(courseInfo.teachplan){let teachplan = JSON.parse(courseInfo.teachplan);this.teachplanList = teachplan.children;} })}, 测试 在浏览器请求:http://ucenter.xuecheng.com//learning/4028e581617f945f01617f9dabc40000/0 4028e581617f945f01617f9dabc40000:第一个参数为课程 id,测试时从 ES索引库找一个课程 id 0:第二个参数为课程计划 id,此参数用于点击课程计划播放视频。 如果出现跨域问题,但是确定已经配置了跨域,请尝试结束所以 nginx.exe 的进程 和 清空浏览器缓存。 如果还没有解决?重启电脑试试。 二、学习页面:获取视频播放地址 0x01 需求分析 用户进入在线学习页面,点击课程计划将播放该课程计划对应的教学视频。 业务流程如下: 业务流程说明: 1、用户进入在线学习页面,页面请求搜索服务获取课程信息(包括课程计划信息)并且在页面展示。 2、在线学习请求学习服务获取视频播放地址。 3、学习服务校验当前用户是否有权限学习,如果没有权限学习则提示用户。 4、学习服务校验通过,请求搜索服务获取课程媒资信息。 5、搜索服务请求ElasticSearch获取课程媒资信息。 为什么要请求 ElasticSearch 查询课程媒资信息? 出于性能的考虑,公开查询课程信息从搜索服务查询,分摊 mysql 数据库的访问压力。 什么时候将课程媒资信息存储到 ElasticSearch 中? 课程媒资信息是在课程发布的时候存入 ElasticSearch,因为课程发布后课程信息将基本不再修改。 0x02 课程发布:储存媒资信息 需求分析 课程媒资信息是在课程发布的时候存入 ElasticSearch 索引库,因为课程发布后课程信息将基本不再修改,具体的业务流程如下。 1、课程发布,向课程媒资信息表写入数据。 1)根据课程 id 删除 teachplanMediaPub 中的数据 2)根据课程 id 查询 teachplanMedia 数据 3)将查询到的 teachplanMedia 数据插入到 teachplanMediaPub 中 2、Logstash 定时扫描课程媒资信息表,并将课程媒资信息写入索引库。 数据模型 在 xc_course 数据库创建课程计划媒资发布表: CREATE TABLE teachplan_media_pub (teachplan_id varchar(32) NOT NULL COMMENT '课程计划id',media_id varchar(32) NOT NULL COMMENT '媒资文件id',media_fileoriginalname varchar(128) NOT NULL COMMENT '媒资文件的原始名称',media_url varchar(256) NOT NULL COMMENT '媒资文件访问地址',courseid varchar(32) NOT NULL COMMENT '课程Id',timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT'logstash使用',PRIMARY KEY (teachplan_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 数据模型类如下: package com.xuecheng.framework.domain.course;import lombok.Data;import lombok.ToString;import org.hibernate.annotations.GenericGenerator;import javax.persistence.;import java.io.Serializable;import java.util.Date;@Data@ToString@Entity@Table(name="teachplan_media_pub")@GenericGenerator(name = "jpa-assigned", strategy = "assigned")public class TeachplanMediaPub implements Serializable {private static final long serialVersionUID = -916357110051689485L;@Id@GeneratedValue(generator = "jpa-assigned")@Column(name="teachplan_id")private String teachplanId;@Column(name="media_id")private String mediaId;@Column(name="media_fileoriginalname")private String mediaFileOriginalName;@Column(name="media_url")private String mediaUrl;@Column(name="courseid")private String courseId;@Column(name="timestamp")private Date timestamp;//时间戳} Dao 创建 TeachplanMediaPub 表的 Dao,向 TeachplanMediaPub 存储信息采用先删除该课程的媒资信息,再添加该课程的媒资信息,所以这里定义根据课程 id 删除课程计划媒资方法: public interface TeachplanMediaPubRepository extends JpaRepository<TeachplanMediaPub, String> {//根据课程id删除课程计划媒资信息long deleteByCourseId(String courseId);} 从TeachplanMedia查询课程计划媒资信息 //从TeachplanMedia查询课程计划媒资信息public interface TeachplanMediaRepository extends JpaRepository<TeachplanMedia, String> {List<TeachplanMedia> findByCourseId(String courseId);} Service 编写保存课程计划媒资信息方法,并在课程发布时调用此方法。 1、保存课程计划媒资信息方法 本方法采用先删除该课程的媒资信息,再添加该课程的媒资信息,在 CourseService 下定义该方法 //保存课程计划媒资信息private void saveTeachplanMediaPub(String courseId){//查询课程媒资信息List<TeachplanMedia> byCourseId = teachplanMediaRepository.findByCourseId(courseId);if(byCourseId == null) return; //没有查询到媒资数据则直接结束该方法//将课程计划媒资信息储存到待索引表//删除原有的索引信息teachplanMediaPubRepository.deleteByCourseId(courseId);//一个课程可能会有多个媒资信息,遍历并使用list进行储存List<TeachplanMediaPub> teachplanMediaPubList = new ArrayList<>();for (TeachplanMedia teachplanMedia: byCourseId) {TeachplanMediaPub teachplanMediaPub = new TeachplanMediaPub();BeanUtils.copyProperties(teachplanMedia, teachplanMediaPub);teachplanMediaPubList.add(teachplanMediaPub);}//保存所有信息teachplanMediaPubRepository.saveAll(teachplanMediaPubList);} 2、课程发布时调用此方法 修改课程发布的 coursePublish 方法: ....//保存课程计划媒资信息到待索引表saveTeachplanMediaPub(courseId);//页面urlString pageUrl = cmsPostPageResult.getPageUrl();return new CoursePublishResult(CommonCode.SUCCESS,pageUrl);..... 测试 测试课程发布后是否成功将课程媒资信息存储到 teachplan_media_pub 中,测试流程如下: 1、指定一个课程 2、为课程计划添加课程媒资 3、执行课程发布 4、观察课程计划媒资信息是否存储至 teachplan_media_pub 中 注意:由于此测试仅用于测试发布课程计划媒资信息的功能,可暂时将 cms页面发布的功能暂时屏蔽,提高测试效率。 测试结果如下 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vrzs5589-1595567273126)(https://qnoss.codeyee.com/20200704_15/image7)] 0x03 Logstash:扫描课程计划媒资 Logstash 定时扫描课程媒资信息表,并将课程媒资信息写入索引库。 创建索引 1、创建 xc_course_media 索引 2、并向此索引创建如下映射 POST: http://localhost:9200/xc_course_media/doc/_mapping {"properties" : {"courseid" : {"type" : "keyword"},"teachplan_id" : {"type" : "keyword"},"media_id" : {"type" : "keyword"},"media_url" : {"index" : false,"type" : "text"},"media_fileoriginalname" : {"index" : false,"type" : "text"} }} 索引创建成功 创建模板文件 在 logstach 的 config 目录文件 xc_course_media_template.json 文件路径为 %ES_ROOT_DIR%/logstash6.8.8/config/xc_course_media_template.json %ES_ROOT_DIR% 为 ElasticSearch 和 logstash 的安装目录 内容如下: {"mappings" : {"doc" : {"properties" : {"courseid" : {"type" : "keyword"},"teachplan_id" : {"type" : "keyword"},"media_id" : {"type" : "keyword"},"media_url" : {"index" : false,"type" : "text"},"media_fileoriginalname" : {"index" : false,"type" : "text"} }},"template" : "xc_course_media"} } 配置 mysql.conf 在logstash的 config 目录下配置 mysql_course_media.conf 文件供 logstash 使用,logstash 会根据 mysql_course_media.conf 文件的配置的地址从 MySQL 中读取数据向 ES 中写入索引。 参考https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html 配置输入数据源和输出数据源。 input {stdin {} jdbc {jdbc_connection_string => "jdbc:mysql://localhost:3306/xc_course?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC" 数据库信息jdbc_user => "root"jdbc_password => "123123" MYSQL 驱动地址,修改为maven仓库对应的位置jdbc_driver_library => "D:/soft/apache-maven-3.5.4/repository/mysql/mysql-connector-java/5.1.40/mysql-connector-java-5.1.40.jar" the name of the driver class for mysqljdbc_driver_class => "com.mysql.jdbc.Driver"jdbc_paging_enabled => "true"jdbc_page_size => "50000"要执行的sql文件statement_filepath => "/conf/course.sql"statement => "select from teachplan_media_pub where timestamp > date_add(:sql_last_value,INTERVAL 8 HOUR)"定时配置schedule => " "record_last_run => truelast_run_metadata_path => "D:/soft/elasticsearch/logstash-6.8.8/config/xc_course_media_metadata"} } output {elasticsearch {ES的ip地址和端口hosts => "localhost:9200"hosts => ["localhost:9200","localhost:9202","localhost:9203"]ES索引库名称index => "xc_course_media"document_id => "%{teachplan_id}"document_type => "doc"template => "D:/soft/elasticsearch/logstash-6.8.8/config/xc_course_media_template.json"template_name =>"xc_course_media"template_overwrite =>"true"} stdout {日志输出codec => json_lines} } 启动 logstash.bat 启动 logstash.bat 采集 teachplan_media_pub 中的数据,向 ES 写入索引。 logstash.bat -f ../config/mysql_course_media.conf 课程发布成功后,Logstash 会自动参加 teachplan_media_pub 表中新增的数据,效果如下 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ILPBxfXi-1595567273134)(https://qnoss.codeyee.com/20200704_15/image10)] Logstash多实例运行 由于之前我们还启动了一个 Logstash 对课程的发布信息进行采集,所以如果想两个 logstash 实例同时运行,因为每个实例都有一个.lock文件,所以不能使用同一个目录来存放数据,所以我们需要使用 --path.data= 为每个实例指定单独的数据目录,具体的代码如下: 该配置是在windows下进行的 课程发布实例 logstash_start_course_pub.bat @title logstash in course_publogstash.bat -f ..\config\mysql.conf --path.data=../data/course_pub 课程计划媒体发布实例 logstash_start_teachplan_media.bat @title logstash i n teachplan_media_publogstash.bat -f ../config/mysql_course_media.conf --path.data=../data/teachplan_media/ 同时运行效果如下 0x04 搜素服务:查询课程媒资接口 需求分析 搜索服务 提供查询课程媒资接口,此接口供学习服务调用。 Api接口定义 @ApiOperation("根据课程计划查询媒资信息")public TeachplanMediaPub getmedia(String teachplanId); Service 1、配置课程计划媒资索引库等信息 在 application.yml 中配置 xuecheng:elasticsearch:hostlist: ${eshostlist:127.0.0.1:9200} 多个结点中间用逗号分隔course:index: xc_coursetype: docsource_field: id,name,grade,mt,st,charge,valid,pic,qq,price,price_old,status,studymodel,teachmode,expires,pub_time,start_time,end_timemedia:index: xc_course_mediatype: docsource_field: courseid,media_id,media_url,teachplan_id,media_fileoriginalname 2、service 方法开发 在 课程搜索服务 中定义课程媒资查询接口,为了适应后续需求,service 参数定义为数组,可一次查询多个课程计划的媒资信息。 / 根据一个或者多个课程计划id查询媒资信息 @param teachplanIds 课程id @return QueryResponseResult/public QueryResponseResult<TeachplanMediaPub> getmedia(String [] teachplanIds){//设置索引SearchRequest searchRequest = new SearchRequest(media_index);//设置类型searchRequest.types(media_type);//创建搜索源对象SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//源字段过滤String[] media_index_arr = media_field.split(",");searchSourceBuilder.fetchSource(media_index_arr, new String[]{});//查询条件,根据课程计划id查询(可以传入多个课程计划id)searchSourceBuilder.query(QueryBuilders.termsQuery("teachplan_id", teachplanIds));searchRequest.source(searchSourceBuilder);SearchResponse searchResponse = null;try {searchResponse = restHighLevelClient.search(searchRequest);} catch (IOException e) {e.printStackTrace();}//获取结果SearchHits hits = searchResponse.getHits();long totalHits = hits.getTotalHits();SearchHit[] searchHits = hits.getHits();//数据列表List<TeachplanMediaPub> teachplanMediaPubList = new ArrayList<>();for(SearchHit hit:searchHits){TeachplanMediaPub teachplanMediaPub =new TeachplanMediaPub();Map<String, Object> sourceAsMap = hit.getSourceAsMap();//取出课程计划媒资信息String courseid = (String) sourceAsMap.get("courseid");String media_id = (String) sourceAsMap.get("media_id");String media_url = (String) sourceAsMap.get("media_url");String teachplan_id = (String) sourceAsMap.get("teachplan_id");String media_fileoriginalname = (String) sourceAsMap.get("media_fileoriginalname");teachplanMediaPub.setCourseId(courseid);teachplanMediaPub.setMediaUrl(media_url);teachplanMediaPub.setMediaFileOriginalName(media_fileoriginalname);teachplanMediaPub.setMediaId(media_id);teachplanMediaPub.setTeachplanId(teachplan_id);//将对象加入到列表中teachplanMediaPubList.add(teachplanMediaPub);}//构建返回课程媒资信息对象QueryResult<TeachplanMediaPub> queryResult = new QueryResult<>();queryResult.setList(teachplanMediaPubList);queryResult.setTotal(totalHits);return new QueryResponseResult<TeachplanMediaPub>(CommonCode.SUCCESS,queryResult);} Controller / 根据课程计划id搜索发布后的媒资信息 @param teachplanId @return/@GetMapping(value="/getmedia/{teachplanId}")@Overridepublic TeachplanMediaPub getmedia(@PathVariable("teachplanId") String teachplanId) {//为了service的拓展性,所以我们service接收的是数组作为参数,以便后续开发查询多个ID的接口String[] teachplanIds = new String[]{teachplanId};//通过service查询ES获取课程媒资信息QueryResponseResult<TeachplanMediaPub> mediaPubQueryResponseResult = esCourseService.getmedia(teachplanIds);QueryResult<TeachplanMediaPub> queryResult = mediaPubQueryResponseResult.getQueryResult();if(queryResult!=null&& queryResult.getList()!=null&& queryResult.getList().size()>0){//返回课程计划对应课程媒资return queryResult.getList().get(0);} return new TeachplanMediaPub();} 测试 使用 swagger-ui 和 postman 测试课程媒资查询接口。 三、在线学习:接口开发 0x01 需求分析 根据下边的业务流程,本章节完成前端学习页面请求学习服务获取课程视频地址,并自动播放视频。 0x02 搭建开发环境 1、创建数据库 创建 xc_learning 数据库,学习数据库将记录学生的选课信息、学习信息。 导入:资料/xc_learning.sql 2、创建学习服务工程 参考课程管理服务工程结构,创建学习服务工程: 导入:资料/xc-service-learning.zip 项目工程结构如下 0x03 Api接口 此 api 接口是课程学习页面请求学习服务获取课程学习地址。 定义返回值类型: package com.xuecheng.framework.domain.learning.response;import com.xuecheng.framework.model.response.ResponseResult;import com.xuecheng.framework.model.response.ResultCode;import lombok.Data;import lombok.NoArgsConstructor;import lombok.ToString;@Data@ToString@NoArgsConstructorpublic class GetMediaResult extends ResponseResult {public GetMediaResult(ResultCode resultCode, String fileUrl) {super(resultCode);this.fileUrl = fileUrl;}//媒资文件播放地址private String fileUrl;} 定义接口,学习服务根据传入课程 ID、章节 Id(课程计划 ID)来取学习地址。 @Api(value = "录播课程学习管理",description = "录播课程学习管理")public interface CourseLearningControllerApi {@ApiOperation("获取课程学习地址")public GetMediaResult getMediaPlayUrl(String courseId,String teachplanId);} 0x04 服务端开发 需求分析 学习服务根据传入课程ID、章节Id(课程计划ID)请求搜索服务获取学习地址。 搜索服务注册Eureka 学习服务要调用搜索服务查询课程媒资信息,所以需要将搜索服务注册到 eureka 中。 1、查看服务名称是否为 xc-service-search 注意修改application.xml中的服务名称:spring:application:name: xc‐service‐search 2、配置搜索服务的配置文件 application.yml,加入 Eureka 配置 如下: eureka:client:registerWithEureka: true 服务注册开关fetchRegistry: true 服务发现开关serviceUrl: Eureka客户端与Eureka服务端进行交互的地址,多个中间用逗号分隔defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/,http://localhost:50102/eureka/}instance:prefer-ip-address: true 将自己的ip地址注册到Eureka服务中ip-address: ${IP_ADDRESS:127.0.0.1}instance-id: ${spring.application.name}:${server.port} 指定实例idribbon:MaxAutoRetries: 2 最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试,如果eureka中找不到服务则直接走断路器MaxAutoRetriesNextServer: 3 切换实例的重试次数OkToRetryOnAllOperations: false 对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为falseConnectTimeout: 5000 请求连接的超时时间ReadTimeout: 6000 请求处理的超时时间 3、添加 eureka 依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring‐cloud‐starter‐netflix‐eureka‐client</artifactId></dependency> 4、修改启动类,在class上添加如下注解: @EnableDiscoveryClient 搜索服务客户端 在 学习服务 创建搜索服务的客户端接口,此接口会生成代理对象,调用搜索服务: package com.xuecheng.learning.client;import com.xuecheng.framework.domain.course.TeachplanMediaPub;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;@FeignClient(value = "xc‐service‐search")public interface CourseSearchClient {@GetMapping(value="/getmedia/{teachplanId}")public TeachplanMediaPub getmedia(@PathVariable("teachplanId") String teachplanId);} 自定义错误代码 我们在 com.xuecheng.framework.domain.learning.response 包下自定义一个错误消息模型 package com.xuecheng.framework.domain.learning.response;import com.xuecheng.framework.model.response.ResultCode;import lombok.ToString;@ToStringpublic enum LearningCode implements ResultCode {LEARNING_GET_MEDIA_ERROR(false,23001,"学习中心获取媒资信息错误!");//操作代码boolean success;//操作代码int code;//提示信息String message;private LearningCode(boolean success, int code, String message){this.success = success;this.code = code;this.message = message;}@Overridepublic boolean success() {return success;}@Overridepublic int code() {return code;}@Overridepublic String message() {return message;} } 该消息模型基于 ResultCode 来实现,代码如下 package com.xuecheng.framework.model.response;/ Created by mrt on 2018/3/5. 10000-- 通用错误代码 22000-- 媒资错误代码 23000-- 用户中心错误代码 24000-- cms错误代码 25000-- 文件系统/public interface ResultCode {//操作是否成功,true为成功,false操作失败boolean success();//操作代码int code();//提示信息String message(); 从 ResultCode 中我们可以看出,我们约定了用户中心的错误代码使用 23000,所以我们定义的一些错误信息的代码就从 23000 开始计数。 Service 在学习服务中定义 service 方法,此方法远程请求课程管理服务、媒资管理服务获取课程学习地址。 package com.xuecheng.learning.service.impl;import com.netflix.discovery.converters.Auto;import com.xuecheng.framework.domain.course.TeachplanMediaPub;import com.xuecheng.framework.domain.learning.response.GetMediaResult;import com.xuecheng.framework.exception.ExceptionCast;import com.xuecheng.framework.model.response.CommonCode;import com.xuecheng.learning.client.CourseSearchClient;import com.xuecheng.learning.service.LearningService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class LearningServiceImpl implements LearningService {@AutowiredCourseSearchClient courseSearchClient;/ 远程调用搜索服务获取已发布媒体信息中的url @param courseId 课程id @param teachplanId 媒体信息id @return/@Overridepublic GetMediaResult getMediaPlayUrl(String courseId, String teachplanId) {//校验学生权限,是否已付费等//远程调用搜索服务进行查询媒体信息TeachplanMediaPub mediaPub = courseSearchClient.getmedia(teachplanId);if(mediaPub == null) ExceptionCast.cast(CommonCode.FAIL);return new GetMediaResult(CommonCode.SUCCESS, mediaPub.getMediaUrl());} } Controller 调用 service 根据课程计划 id 查询视频播放地址: @RestController@RequestMapping("/learning/course")public class CourseLearningController implements CourseLearningControllerApi {@AutowiredLearningService learningService;@Override@GetMapping("/getmedia/{courseId}/{teachplanId}")public GetMediaResult getMediaPlayUrl(@PathVariable String courseId, @PathVariable String teachplanId) {//获取课程学习地址return learningService.getMedia(courseId, teachplanId);} } 测试 使用 swagger-ui 或postman 测试学习服务查询课程视频地址接口。 0x05 前端开发 需求分析 需要在学习中心前端页面需要完成如下功能: 1、进入课程学习页面需要带上 课程 Id参数及课程计划Id的参数,其中 课程 Id 参数必带,课程计划 Id 可以为空。 2、进入页面根据 课程 Id 取出该课程的课程计划显示在右侧。 3、进入页面后判断如果请求参数中有课程计划 Id 则播放该章节的视频。 4、进入页面后判断如果 课程计划id 为0则需要取出本课程第一个 课程计划的Id,并播放第一个课程计划的视频。 进入到模块 xc-ui-pc-leanring/src/module/course api方法 let sysConfig = require('@/../config/sysConfig')let apiUrl = sysConfig.xcApiUrlPre;/获取播放地址/export const get_media = (courseId,chapter) => {return http.requestGet(apiUrl+'/api/learning/course/getmedia/'+courseId+'/'+chapter);} 配置代理 在 Nginx 中的 ucenter.xuecheng.com 虚拟主机中配置 /api/learning/ 的路径转发,此url 请转发到学习服务。 学习服务upstream learning_server_pool{server 127.0.0.1:40600 weight=10;}学成网用户中心server {listen 80;server_name ucenter.xuecheng.com;个人中心location / {proxy_pass http://ucenter_server_pool;}后端搜索服务location /openapi/search/ {proxy_pass http://search_server_pool/search/; }学习服务location ^~ /api/learning/ {proxy_pass http://learning_server_pool/learning/;} } 视频播放页面 1、如果传入的课程计划id为0则取出第一个课程计划id 在 created 钩子方法中完成 created(){//当前请求的urlthis.url = window.location//课程idthis.courseId = this.$route.params.courseId//章节idthis.chapter = this.$route.params.chapter//查询课程信息systemApi.course_view(this.courseId).then((view_course)=>{if(!view_course || !view_course[this.courseId]){this.$message.error("获取课程信息失败,请重新进入此页面!")return ;}let courseInfo = view_course[this.courseId]console.log(courseInfo)this.coursename = courseInfo.nameif(courseInfo.teachplan){console.log("准备开始播放视频")let teachplan = JSON.parse(courseInfo.teachplan);this.teachplanList = teachplan.children;//开始学习if(this.chapter == "0" || !this.chapter){//取出第一个教学计划this.chapter = this.getFirstTeachplan();console.log("第一个教学计划id为 ",this.chapter);this.study(this.chapter);}else{this.study(this.chapter);} }})}, 取出第一个章节 id,用户未输入课程计划 id 或者输入为 0 时,播放第一个。 //取出第一个章节getFirstTeachplan(){for(var i=0;i<this.teachplanList.length;i++){let firstTeachplan = this.teachplanList[i];//如果当前children存在,则取出第一个返回if(firstTeachplan.children && firstTeachplan.children.length>0){let secondTeachplan = firstTeachplan.children[0];return secondTeachplan.id;} }return ;}, 开始学习: //开始学习study(chapter){// 获取播放地址courseApi.get_media(this.courseId,chapter).then((res)=>{if(res.success){let fileUrl = sysConfig.videoUrl + res.fileUrl//播放视频this.playvideo(fileUrl)}else if(res.message){this.$message.error(res.message)}else{this.$message.error("播放视频失败,请刷新页面重试")} }).catch(res=>{this.$message.error("播放视频失败,请刷新页面重试")});}, 2、点击右侧课程章节切换播放 在原有代码基础上添加 click 事件,点击调用开始学习方法(study)。 <li v‐if="teachplan_first.children!=null" v‐for="(teachplan_second, index) inteachplan_first.children"><i class="glyphicon glyphicon‐check"></i><a :href="url" @click="study(teachplan_second.id)">{ {teachplan_second.pname} }</a></li> 3、地址栏路由url变更 这里需要注意一个问题,在用户点击课程章节切换播放时,地址栏的 url 也应该同步改变为当前所选择的课程计划 id 4、在线学习按钮 将 learnstatus 默认更改为 1,这样就能显示出马上学习的按钮,方便我们后续的集成测试。 文件路径为 xc-ui-pc-static-portal/include/course_detail_dynamic.html 部分代码块如下 <script>var body= new Vue({ //创建一个Vue的实例el: "body", //挂载点是id="app"的地方data: {editLoading: false,title:'测试',courseId:'',charge:'',//203001免费,203002收费learnstatus: 1 ,//课程状态,1:马上学习,2:立即报名、3:立即购买course:{},companyId:'template',company_stat:[],course_stat:{"s601001":"","s601002":"","s601003":""} }, 简单的测试 访问在线学习页面:http://ucenter.xuecheng.com//learning/课程id/课程计划id 通过 url 传入两个参数:课程id 和 课程计划id 如果没有课程计划则传入0 测试项目如下: 1、传入正确的课程id、课程计划id,自动播放本章节的视频 2、传入正确的课程id、课程计划id传入0,自动播放第一个视频 3、传入错误的课程id 或 课程计划id,提示错误信息。 4、通过右侧章节目录切换章节及播放视频。 访问: http://ucenter.xuecheng.com//learning/4028e58161bcf7f40161bcf8b77c0000/4028e58161bd18ea0161bd1f73190008 传入正确的课程id、课程计划id,自动播放本章节的视频 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ef0xxym7-1595567273153)(https://qnoss.codeyee.com/20200704_15/image17)] 传入正确的课程id、课程计划id传入0,自动播放第一个视频 访问 http://ucenter.xuecheng.com//learning/4028e58161bcf7f40161bcf8b77c0000/0 识别出第一个课程计划的 id 需要注意的是这里的 chapter 参数是我自己在 study 函数里加上去的,可以忽略。 传入错误的课程id或课程计划id,提示错误信息。 通过右侧章节目录切换章节及播放视频。 点击章节即可播放,但是点击制定章节后 url 没有发生改变,这个问题暂时还没有解决,关注笔记后面的内容。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TOGdxwb4-1595567273158)(https://qnoss.codeyee.com/20200704_15/image20)] 完整的测试 准备工作 启动 RabbitMQ,启动 Logstash、ElasticSearch 建议把所有后端服务都开起来 启动 前端静态门户、启动 nginx 、启动课程管理前端 我们整理一下测试的流程 上传两个媒资视频文件,用于测试 进入到课程管理,为课程计划选择媒资信息 发布课程,等待 logstash 将数据采集到 ElasticSearch 的索引库中 进入学成网主页,点击课程,进入到搜索门户页面 搜索课程,进入到课程详情页面 点击开始学习,进入到课程学习页面,选择课程计划中的一个章节进行学习。 1、上传文件 首先我们使用之前开发的媒资管理模块,上传两个视频文件用于测试。 第一个文件上传成功 一些问题 在上传第二个文件时,发生了错误,我们来检查一下问题出在了哪里 在媒体服务的控制台中可以看到,在 mergeChunks 方法在校验文件 md5 时候抛出了异常 我们在 MD5 校验这里打个断点,重新上传文件,分析一下问题所在。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OpEMZGI8-1595567273166)(https://qnoss.codeyee.com/20200704_15/image23)] 单步调试后发现,合并文件后的MD5值与用户上传的源文件值不相等 方案1:删除本地分块文件重新尝试上传 考虑到可能是在用户上传完 视频的分块文件时发生了一些问题,导致合并文件后与源文件的大小不等,导致MD5也不相同,这里我们把这个视频上传到本地的文件全部删除,在媒资上传页面重新上传文件。 对比所有分块文件的字节大小和本地源文件的大小,完全是相等的 删除所有文件后重新上传,md5值还是不等,考虑从调试一下文件合并的代码。 方案2:检查前端提交的MD5值是否正确 在查阅是否有其他的MD5值获取方案时,发现了一个使用 windows 本地命令获取文件MD5值的方法 certutil -hashfile .\19-在线学习接口-集成测试.avi md5 惊奇的发现,TM的原来是前端那边转换的MD5值不正确,后端这边是没有问题的。 从前面的图可以看出,本地和后端转换的都是以一个 f6f0 开头的MD5值 那么问题就出现在前端了,还需要花一些时间去分析一下,这里暂时就先告一段落,因为上传了几个文件测试中只有这一个文件出现了问题。 2、为课程计划选择媒资信息 进入到一个课程的管理页面 http://localhost:12000//course/manage/baseinfo/4028e58161bcf7f40161bcf8b77c0000 将刚才我们上传的媒资文件的信息和课程计划绑定 选择效果如下 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-epKaqzCD-1595567273178)(https://qnoss.codeyee.com/20200704_15/image29)] 2、发布课程,等待 logstash 从 course_pub 以及 teachplan_media_pub 表中采集数据到 ElasticSearch 当中 发布成功后,我们可以从 teachplan_media_pub 表中看到刚才我们发布的媒资信息 再观察 Logstash 的控制台,发现两个 Logstash 的实例都对更新的课程发布信息进行了采集 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hTUve2ik-1595567273183)(https://qnoss.codeyee.com/20200704_15/image32)] 3、前端门户测试 打开我们的门户主站 http://www.xuecheng.com/ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4wZe9R84-1595567273185)(https://qnoss.codeyee.com/20200704_15/image33)] 点击导航栏的课程,进入到我们的搜索门户页面 如果无法进入到搜索门户,请检查你的 xc-ui-pc-portal 前端工程是否已经启动 进入到搜索门户后,可以看到一些初始化时搜索的课程数据,默认是搜索第一页的数据,每页2个课程。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BJ1AKoJb-1595567273187)(https://qnoss.codeyee.com/20200704_15/image34)] 我们可以测试搜索一下前面我们选择媒资信息时所用的课程 点击课程,进入到课程详情页面,然后再点击开始学习。 点击马上学习后,会进入到该课程的在线学习页面,默认自动播放我们第一个课程计划中的视频。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tcuLWnf2-1595567273193)(https://qnoss.codeyee.com/20200704_15/image37)] 我们可以在右侧的目录中选择第二个课程计划,会自动播放所选的课程计划所对应的媒资视频播放地址,该 播放地址正是我们刚才通过 Logstash 自动采集到 ElasticSearch 的索引信息,效果图如下 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cvi9Dr0Y-1595567273195)(https://qnoss.codeyee.com/20200704_15/image38)] 四、待完善的一些功能 课程发布前,校验课程计划里面是否包含二级课程计划 课程发布前,校验课程计划信息里面是否全部包含媒资信息 删除媒资信息,并且同步删除ES中的索引 在获取该课程的播放地址时校验用户的合法、 在线学习页面,点击右侧目录中的课程计划同时改变url中的课程计划地址 视频文件 19-在线学习接口-集成测试.avi 前端上传时提交的MD5值不正确 😁 认识作者 作者:👦 LCyee ,全干型代码🐕 自建博客:https://www.codeyee.com 记录学习以及项目开发过程中的笔记与心得,记录认知迭代的过程,分享想法与观点。 CSDN 博客:https://blog.csdn.net/codeyee 记录和分享一些开发过程中遇到的问题以及解决的思路。 欢迎加入微服务练习生的队伍,一起交流项目学习过程中的一些问题、分享学习心得等,不定期组织一起刷题、刷项目,共同见证成长。 本篇文章为转载内容。原文链接:https://blog.csdn.net/codeyee/article/details/107558901。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-12-16 12:41:01
73
转载
Go-Spring
...th/rand" "time" "go.uber.org/zap" ) func main() { rand.Seed(time.Now().UnixNano()) ctx, cancel := context.WithTimeout(context.Background(), 5time.Second) defer cancel() for i := 0; i < 10; i++ { err := makeNetworkCall(ctx) if err != nil { zap.Sugar().Errorf("Network call %d failed: %s", i, err) } else { zap.Sugar().Infof("Network call %d succeeded", i) } time.Sleep(1 time.Second) } } func makeNetworkCall(ctx context.Context) error { time.Sleep(time.Duration(rand.Intn(10)) time.Millisecond) return fmt.Errorf("network call failed after %d ms", rand.Intn(10)) } 在这个例子中,我们展示了如何在一个循环中处理网络调用,同时利用context来控制调用的超时时间。在每次调用失败时,我们记录详细的错误信息和调用次数。这种做法有助于在出现问题时快速响应和诊断。 结论 通过上述实践,我们可以看到GoSpring如何通过结构化错误处理和日志记录来提升应用的健壮性和维护性。哎呀,兄弟!如果咱们能好好执行这些招数,那可真是大有裨益啊!不仅能大大缩短遇到问题时,咱们得花多少时间去修复,还能省下一大笔银子呢!更棒的是,还能让咱们团队里的小伙伴们,心往一处想,劲往一处使,互相理解,配合得天衣无缝。这感觉,就像是大家在一块儿打游戏,每个人都有自己的角色,但又都为了一个共同的目标而努力,多带劲啊!哎呀,你知道吗?当咱们的应用越做越大,用GoSpring的那些工具和好方法,简直就是如虎添翼啊!这样咱就能打造出一个既稳如泰山又快如闪电,还特别容易打理的系统。想象一下,就像给你的小花园施肥浇水,让每一朵花都长得茁壮又美丽,是不是感觉棒极了?所以啊,别小看了这些工具和最佳实践,它们可是你建大事业的得力助手!
2024-07-31 16:06:44
277
月下独酌
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 首先感谢以下大佬提供帮助 斗鱼视频下载-> https://www.jianshu.com/p/feccccb097be 批量合并处理B站视频->https://www.wandouip.com/t5i227224/ You-Get->https://you-get.org/ 思路 首先打开了PC端斗鱼视频一个链接:https://v.douyu.com/show/0Q8mMY0xXDL749Ad 发现一个参数在链接中 0Q8mMY0xXDL749Ad 通过抓包发现了一个很重要的文件:playlist.m3u8 里面包含了数个 .ts 的网络地址; .ts 文件是可以播放的视频片段; 发现 可以通过合并 .ts 片段可以得到完整视频; 出现一个问题:playlist.m3u8 怎么获取? 发现:https://v.douyu.com/api/stream/getStreamUrl 可以获取 playlist.m3u8 文件地址; 需要POST传入一些参数才行,发现: sign 参数是一种签名,一般通过JS生成,找了半天没有方法生成 sign 参数; 通过查阅大佬文献发现:手机端的斗鱼视频有接口可以直接获取 playlist.m3u8 文件地址,成功越过 sign 签名防线; 手机端斗鱼视频链接:https://vmobile.douyu.com/show/0Q8mMY0xXDL749Ad 通过抓包发现:https://vmobile.douyu.com/video/getInfo?vid=0Q8mMY0xXDL749Ad; 这就解决了playlist.m3u8 文件获取问题:json[‘data’][‘video_url’] 第一个难题解决!!; 综上所述,整理一下具体采集流程: 获取vid = 0Q8mMY0xXDL749Ad (就是链接中的参数); 通过 https://vmobile.douyu.com/video/getInfo?vid=0Q8mMY0xXDL749Ad 获取 playlist.m3u8 文件地址; 解析 playlist.m3u8 文件提取所有 .ts文件; 下载所有 .ts 文件; 合并 .ts 成视频文件输出; Python实现 不要开启线程池,因为会有一些问题 app.py config 中可以配置 import requestsimport reimport jsonimport timeimport pymongoimport psutilfrom hashlib import md5from moviepy.editor import from multiprocessing import Pool基本配置config = {'UID':'gKpdxKRWXwaW',用户ID'CID':104,栏目ID'TYPE':1, 1=>按用户id采集列表,2=>按栏目ID采集列表'TIME_START':1,起始时间'TIME_ENT':500,结束时间'PAGE_START':1,起始页'PAGE_END':10,结束页'TIME_GE':0,每个下载间隔时间'POOL':False,是否开启线程池'CHECKID':True, True 过滤已经下载过的视频 False 不过滤'FILE_PATH':'F:/ceshi/',下载目录,【会自动创建文件夹】'TS_PATH':'F:/ceshi/download/',缓存文件目录,【会自动创建文件夹】'DB_URL':'localhost',数据库地址'DB_NAME':'douyu',数据库名称''DB_TABLE':'douyu'数据库表}MongoDB初始化client = pymongo.MongoClient(config['DB_URL'])mango_db = client[config['DB_NAME']]MongoDB存储def save_to_mango(result):if mango_db[config['DB_TABLE']].insert_one({'vid':result}):print('成功存储到MangoDB')return Truereturn FalseMongoDB验证重复def check_to_mongo(vid):count = mango_db[config['DB_TABLE']].find({'vid':vid}).count()if count==0:return Falsereturn True删除文件def del_file(page):if os.path.exists(page): 删除文件,可使用以下两种方法。os.remove(page) os.unlink(my_file)else:print('no such file:%s' % page)循环列表删除文件def loop_del_file(arr):for item in arr:del_file(item)请求器def get_content_requests(url):headers = {}headers['user-agent']='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'headers['cookie'] = 'dy_did=07f83a57d1d2e22942e0883200001501; acf_did=07f83a57d1d2e22942e0883200001501; Hm_lvt_e99aee90ec1b2106afe7ec3b199020a7=1556514266,1557050422,1557208315; acf_auth=; acf_auth_wl=; acf_uid=; acf_nickname=; acf_username=; acf_own_room=; acf_groupid=; acf_notification=; acf_phonestatus=; _dys_lastPageCode=page_video,page_video; Hm_lpvt_e99aee90ec1b2106afe7ec3b199020a7=1557209469; _dys_refer_action_code=click_author_video_cate2'try:req_content = requests.get(url,headers = headers)if req_content.status_code == 200:return req_contentprint('请求失败:',url)return Noneexcept:print('请求失败:', url)return None把时间换算成秒def str_to_int(time):try:time_array = time.split(':')time_int = (int(time_array[0])60)+int(time_array[1])return time_intexcept:print('~~~~~计算视频时间失败~~~~~')return None提取需要采集的数据def get_list(html,type = 1):data = []try:list_json = json.loads(str(html))for om in list_json['data']['list']:gtime = str_to_int(om['video_str_duration'])if gtime > config['TIME_START'] and gtime < config['TIME_ENT']:if type == 2:data.append({'title': om['title'], 'vid': om['url'].split('show/')[1]})else:data.append({'title': om['title'], 'vid': om['hash_id']})return dataexcept:print('~~~~~数据提取失败~~~~~')return None解析playlist.m3u8def get_ts_list(m3u8):data = []try:html_m3u8_json = json.loads(m3u8)m3u8_text = get_content_requests(html_m3u8_json['data']['video_url'])m3u8_vurl =html_m3u8_json['data']['video_url'].split('playlist.m3u8?')[0]if m3u8_text:get_text = re.findall(',\n(.?).ts(.?)\n',m3u8_text.text,re.S)for item in get_text:data.append(m3u8_vurl+item[0]+'.ts'+item[1])return datareturn Noneexcept:print('~~~~~解析playlist.m3u8失败~~~~~')return None 杀死moviepy产生的特定进程def killProcess(): 处理python程序在运行中出现的异常和错误try: pids方法查看系统全部进程pids = psutil.pids()for pid in pids: Process方法查看单个进程p = psutil.Process(pid) print('pid-%s,pname-%s' % (pid, p.name())) 进程名if p.name() == 'ffmpeg-win64-v4.1.exe': 关闭任务 /f是强制执行,/im对应程序名cmd = 'taskkill /f /im ffmpeg-win64-v4.1.exe 2>nul 1>null' python调用Shell脚本执行cmd命令os.system(cmd)except:pass下载.ts文件def download_ts(m3u8_list,name):try:if not os.path.exists(config['FILE_PATH']):os.makedirs(config['FILE_PATH'])if not os.path.exists(config['TS_PATH']):os.makedirs(config['TS_PATH'])if os.path.exists(config['FILE_PATH']+name+'.mp4'):name = name+'_'+str(int(time.time()))print('开始下载:',name)L = []R = []for p in m3u8_list:ts_find = get_content_requests(p)file_ts = '{0}{1}.ts'.format(config['TS_PATH'],md5(ts_find.content).hexdigest())with open(file_ts,'wb') as f:f.write(ts_find.content)R.append(file_ts)hebing = VideoFileClip(file_ts)L.append(hebing)killProcess()print('下载完成:',file_ts)mp4file = '{0}{1}.mp4'.format(config['FILE_PATH'],name)final_clip = concatenate_videoclips(L)final_clip.to_videofile(mp4file, fps=24, remove_temp=True)killProcess()loop_del_file(R)print('\n下载完成:',name)print('')return Trueexcept:print('~~~~~合成.ts文件失败~~~~~')return None下载视频列表def list_get_kong(list_json):for item in list_json:y = Trueif config['CHECKID']:if check_to_mongo(item['vid']):print('~~~~~检测到重复项~~~~~')y = Falseif y:get_show_html = get_content_requests('https://vmobile.douyu.com/video/getInfo?vid=' + item['vid'])if get_show_html:m3u8_list = get_ts_list(get_show_html.text)if m3u8_list:download = download_ts(m3u8_list, item['title'])if download: save_to_mango(item['vid'])time.sleep(config['TIME_GE'])控制器def main(page):if config['TYPE']==1:print('~~~~~按用户ID采集~~~~~')listurl = 'https://v.douyu.com/video/author/getAuthorVideoListByNew?up_id={0}&cate2_id=0&limit=30&page={1}'.format(config['UID'],page)get_list_html = get_content_requests(listurl)if get_list_html:list_json = get_list(get_list_html.text,1)if list_json:list_get_kong(list_json)else:print('~~~~~按列表ID采集~~~~~')listurl = 'https://v.douyu.com/video/video/listData?page={1}&cate2Id={0}&action=new'.format(config['CID'],page)get_list_html = get_content_requests(listurl)if get_list_html:list_json = get_list(get_list_html.text,2)if list_json:list_get_kong(list_json)初始化if __name__=='__main__':if config['POOL']:groups = [x for x in range(config['PAGE_START'],config['PAGE_END']+1)]pool = Pool()pool.map(main, groups)else:for item in range(config['PAGE_START'],config['PAGE_END']+1):main(item)print('~~~~~已经完成【所有操作】~~~~~') 总结:众所周知,BiliBili是一个学习的网站! 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_35875470/article/details/89857445。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-12-18 11:34:00
119
转载
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 python curl.py !/usr/bin/python -- coding: utf-8 -- import httplib 连接服务器 conn=httplib.HTTPConnection('www.dnspod.cn') 发送HTTP请求 conn.request('GET','url') 得到结果 result=conn.getresponse() 获取HTTP请求结果值。200为成功 resultresultStatus=result.status print resultStatus 获取请求的页面内容 content=result.read() 关闭连接 conn.close() 如果要模拟客户端进行请求,可以发送HTTP请求头 headers={"Content-Type":"text/html;charset=gb2312"} conn.requeset('POST','url',headersheaders=headers) 带参数传送 params=urllib.urlencode({'key':'value'}); conn.request('POST','url',body=params) 还有一个 模拟 浏览器的方式~ !/usr/bin/python -- coding: utf-8 -- import httplib conn = httplib.HTTPConnection('www.hao123.com') conn.request('GET', '/', headers = { "User-Agent" : "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1) Gecko/20090624 Firefox/3.5", "Accept" : "/", "Accept-Encoding" : "gzip,deflate", }) res = conn.getresponse() print conn.getresponse().status print res.status print res.msg print res.read() conn.close() 下面是 并发的测试~ 类似 ab 和 webbench~~~~ -- coding: utf8 -- import threading, time, httplib HOST = "www.baidu.com"; 主机地址 例如192.168.1.101 PORT = 80 端口 URI = "/?123" 相对地址,加参数防止缓存,否则可能会返回304 TOTAL = 0 总数 SUCC = 0 响应成功数 FAIL = 0 响应失败数 EXCEPT = 0 响应异常数 MAXTIME=0 最大响应时间 MINTIME=100 最小响应时间,初始值为100秒 GT3=0 统计3秒内响应的 LT3=0 统计大于3秒响应的 创建一个 threading.Thread 的派生类 class RequestThread(threading.Thread): 构造函数 def __init__(self, thread_name): threading.Thread.__init__(self) self.test_count = 0 线程运行的入口函数 def run(self): self.test_performace() def test_performace(self): global TOTAL global SUCC global FAIL global EXCEPT global GT3 global LT3 try: st = time.time() conn = httplib.HTTPConnection(HOST, PORT, False) conn.request('GET', URI) res = conn.getresponse() print 'version:', res.version print 'reason:', res.reason print 'status:', res.status print 'msg:', res.msg print 'headers:', res.getheaders() start_time if res.status == 200: TOTAL+=1 SUCC+=1 else: TOTAL+=1 FAIL+=1 timetime_span = time.time()-st print '%s:%f\n'%(self.name,time_span) self.maxtime(time_span) self.mintime(time_span) if time_span>3: GT3+=1 else: LT3+=1 except Exception,e: print e TOTAL+=1 EXCEPT+=1 conn.close() def maxtime(self,ts): global MAXTIME print ts if ts>MAXTIME: MAXTIME=ts def mintime(self,ts): global MINTIME if ts<MINTIME: MINTIME=ts main 代码开始 print '===========task start===========' 开始的时间 start_time = time.time() 并发的线程数 thread_count = 300 i = 0 while i <= thread_count: t = RequestThread("thread" + str(i)) t.start() i += 1 t=0 并发数所有都完成或大于50秒就结束 while TOTAL<thread_count|t>50: print "total:%d,succ:%d,fail:%d,except:%d\n"%(TOTAL,SUCC,FAIL,EXCEPT) print HOST,URI t+=1 time.sleep(1) print '===========task end===========' print "total:%d,succ:%d,fail:%d,except:%d"%(TOTAL,SUCC,FAIL,EXCEPT) print 'response maxtime:',MAXTIME print 'response mintime',MINTIME print 'great than 3 seconds:%d,percent:%0.2f'%(GT3,float(GT3)/TOTAL) print 'less than 3 seconds:%d,percent:%0.2f'%(LT3,float(LT3)/TOTAL) 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_33835103/article/details/85213806。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-10-19 20:57:06
74
转载
Apache Solr
...,可以在本地缓存一段时间。当外部服务不可用时,可以回退使用缓存数据,直到服务恢复。 python class ExternalMetadataCache: def __init__(self, ttl=600): self.cache = {} self.ttl = ttl def get(self, doc_id): if doc_id not in self.cache or (self.cache[doc_id]['timestamp'] + self.ttl) < time.time(): self.cache[doc_id] = {'data': fetch_external_metadata(doc_id), 'timestamp': time.time()} return self.cache[doc_id]['data'] metadata_cache = ExternalMetadataCache() def fetch_external_metadata_safe(doc_id): return metadata_cache.get(doc_id) 2. 重试机制 在请求外部服务时添加重试逻辑,当第一次请求失败后,可以设置一定的时间间隔后再次尝试,直到成功或达到最大重试次数。 python def fetch_external_metadata_retriable(doc_id, max_retries=3, retry_delay=5): for i in range(max_retries): try: return fetch_external_metadata(doc_id) except Exception as e: print(f"Attempt {i+1} failed with error: {e}. Retrying in {retry_delay} seconds...") time.sleep(retry_delay) raise Exception("Max retries reached.") 四、结论与展望 通过上述策略,我们可以在一定程度上减轻外部服务依赖对Solr性能的影响。然而,重要的是要持续监控系统的运行状况,并根据实际情况调整优化措施。嘿,你听说了吗?科技这玩意儿啊,那可是越来越牛了!你看,现在就有人在琢磨怎么对付那些让人上瘾的东西。将来啊,说不定能搞出个既高效又结实的办法,帮咱们摆脱这个烦恼。想想都挺激动的,对吧?哎呀,兄弟!构建一个稳定又跑得快的搜索系统,那可得好好琢磨琢磨外部服务这事儿。你知道的,这些服务就像是你家里的电器,得选对了,用好了,整个家才能舒舒服服的。所以啊,咱们得先搞清楚这些服务都是干啥的,它们之间怎么配合,还有万一出了点小状况,咱们能不能快速应对。这样,咱们的搜索系统才能稳如泰山,嗖嗖地飞快,用户一搜就满意,那才叫真本事呢! --- 请注意,以上代码示例是基于Python和相关库编写的,实际应用时需要根据具体环境和技术栈进行相应的调整。
2024-09-21 16:30:17
39
风轻云淡
Ruby
...编程就是让程序在同一时间执行多个任务。在Ruby中,我们可以用线程(Thread)来实现这一点。比如说啊,你正在倒腾一堆数据的时候,完全可以把它切成一小块一小块的,然后让每个线程去负责一块,这样一来,效率直接拉满,干活儿的速度蹭蹭往上涨! 但是,问题来了:并发编程虽然强大,但它并不是万能药。哎呀,经常会有这样的情况呢——自个儿辛辛苦苦改代码,还以为是在让程序变得更好,结果一不小心,又给它整出了新麻烦,真是“好心办坏事”的典型啊!接下来,我们来看几个具体的例子。 --- 3. 示例一 共享状态的混乱 场景描述: 假设你正在开发一个电商网站,需要统计用户的购买记录。你琢磨着干脆让多线程上阵,给这个任务提速,于是打算让每个线程各管一拨用户的活儿,分头行动效率肯定更高!看起来很合理对不对? 问题出现: 问题是,当你让多个线程共享同一个变量(比如一个全局计数器),事情就开始变得不可控了。Ruby 的线程可不是完全分开的,这就有点像几个人共用一个记事本,大家都能随便写东西上去。结果就是,这本子可能一会儿被这个写点,一会儿被那个划掉,最后你都不知道上面到底写了啥,数据就乱套了。 代码示例: ruby 错误的代码 counter = 0 threads = [] 5.times do |i| threads << Thread.new do 100_000.times { counter += 1 } end end threads.each(&:join) puts "Counter: {counter}" 分析: 这段代码看起来没什么问题,每个线程都只是简单地增加计数器。但实际情况却是,输出的结果经常不是期望的500_000,而是各种奇怪的数字。这就好比说,counter += 1 其实不是一步到位的简单操作,它得先“读一下当前的值”,再“给这个值加1”,最后再“把新的值存回去”。问题是,在这中间的每一个小动作,都可能被别的线程突然插队过来捣乱! 解决方案: 为了避免这种混乱,我们需要使用线程安全的操作,比如Mutex(互斥锁)。Mutex可以确保每次只有一个线程能够修改某个变量。 修正后的代码: ruby 正确的代码 require 'thread' counter = 0 mutex = Mutex.new threads = [] 5.times do |i| threads << Thread.new do 100_000.times do mutex.synchronize { counter += 1 } end end end threads.each(&:join) puts "Counter: {counter}" 总结: 这一段代码告诉我们,共享状态是一个雷区。如果你非要用共享变量,记得给它加上锁,不然后果不堪设想。 --- 4. 示例二 死锁的诅咒 场景描述: 有时候,我们会遇到更复杂的情况,比如两个线程互相等待对方释放资源。哎呀,这种情况就叫“死锁”,简直就像两只小猫抢一个玩具,谁都不肯让步,结果大家都卡在那里动弹不得,程序也就这样傻乎乎地停在原地,啥也干不了啦! 问题出现: 想象一下,你有两个线程,A线程需要获取锁X,B线程需要获取锁Y。想象一下,A和B两个人都想打开两把锁——A拿到了锁X,B拿到了锁Y。然后呢,A心想:“我得等B先把他的锁Y打开,我才能继续。”而B也在想:“等A先把她的锁X打开,我才能接着弄。”结果俩人就这么干等着,谁也不肯先放手,最后就成了“死锁”——就像两个人在拔河,谁都不松手,僵在那里啥也干不成。 代码示例: ruby 死锁的代码 lock_a = Mutex.new lock_b = Mutex.new thread_a = Thread.new do lock_a.synchronize do puts "Thread A acquired lock A" sleep(1) lock_b.synchronize do puts "Thread A acquired lock B" end end end thread_b = Thread.new do lock_b.synchronize do puts "Thread B acquired lock B" sleep(1) lock_a.synchronize do puts "Thread B acquired lock A" end end end thread_a.join thread_b.join 分析: 在这段代码中,两个线程都在尝试获取两个不同的锁,但由于它们的顺序不同,最终导致了死锁。运行这段代码时,你会发现程序卡住了,没有任何输出。 解决方案: 为了避免死锁,我们需要遵循“总是按照相同的顺序获取锁”的原则。比如,在上面的例子中,我们可以强制让所有线程都先获取锁A,再获取锁B。 修正后的代码: ruby 避免死锁的代码 lock_a = Mutex.new lock_b = Mutex.new thread_a = Thread.new do [lock_a, lock_b].each do |lock| lock.synchronize do puts "Thread A acquired lock {lock.object_id}" end end end thread_b = Thread.new do [lock_a, lock_b].each do |lock| lock.synchronize do puts "Thread B acquired lock {lock.object_id}" end end end thread_a.join thread_b.join 总结: 死锁就像一只隐形的手,随时可能掐住你的喉咙。记住,保持一致的锁顺序是关键! --- 5. 示例三 不恰当的线程池 场景描述: 线程池是一种管理线程的方式,它可以复用线程,减少频繁创建和销毁线程的开销。但在实际使用中,很多人会因为配置不当而导致性能下降甚至崩溃。 问题出现: 假设你创建了一个线程池,但线程池的大小设置得不合理。哎呀,这就好比做饭时锅不够大,菜都堆在那儿煮不熟,菜要是放太多呢,锅又会冒烟、潽得到处都是,最后饭也没做好。线程池也一样,太小了任务堆成山,程序半天没反应;太大了吧,电脑资源直接被榨干,啥事也干不成,还得收拾烂摊子! 代码示例: ruby 线程池的错误用法 require 'thread' pool = Concurrent::FixedThreadPool.new(2) 20.times do |i| pool.post do sleep(1) puts "Task {i} completed" end end pool.shutdown pool.wait_for_termination 分析: 在这个例子中,线程池的大小被设置为2,但有20个任务需要执行。哎呀,这就好比你请了个帮手,但他一次只能干两件事,其他事儿就得排队等着,得等前面那两件事儿干完了,才能轮到下一件呢!这种情况下,整个程序的执行时间会显著延长。 解决方案: 为了优化线程池的性能,我们需要根据系统的负载情况动态调整线程池的大小。可以使用Concurrent::CachedThreadPool,它会根据当前的任务数量自动调整线程的数量。 修正后的代码: ruby 使用缓存线程池 require 'concurrent' pool = Concurrent::CachedThreadPool.new 20.times do |i| pool.post do sleep(1) puts "Task {i} completed" end end sleep(10) 给线程池足够的时间完成任务 pool.shutdown pool.wait_for_termination 总结: 线程池就像一把双刃剑,用得好可以提升效率,用不好则会成为负担。记住,线程池的大小要根据实际情况灵活调整。 --- 6. 示例四 忽略异常的代价 场景描述: 并发编程的一个常见问题是,线程中的异常不容易被察觉。如果你没有妥善处理这些异常,程序可能会因为一个小错误而崩溃。 问题出现: 假设你有一个线程在执行某个操作时抛出了异常,但你没有捕获它,那么整个线程池可能会因此停止工作。 代码示例: ruby 忽略异常的代码 threads = [] 5.times do |i| threads << Thread.new do raise "Error in thread {i}" if i == 2 puts "Thread {i} completed" end end threads.each(&:join) 分析: 在这个例子中,当i == 2时,线程会抛出一个异常。哎呀糟糕!因为我们没抓住这个异常,程序直接就挂掉了,别的线程啥的也别想再跑了。 解决方案: 为了防止这种情况发生,我们应该在每个线程中添加异常捕获机制。比如,可以用begin-rescue-end结构来捕获异常并进行处理。 修正后的代码: ruby 捕获异常的代码 threads = [] 5.times do |i| threads << Thread.new do begin raise "Error in thread {i}" if i == 2 puts "Thread {i} completed" rescue => e puts "Thread {i} encountered an error: {e.message}" end end end threads.each(&:join) 总结: 异常就像隐藏在暗处的敌人,稍不注意就会让你措手不及。学会捕获和处理异常,是成为一个优秀的并发编程者的关键。 --- 7. 结语 好了,今天的分享就到这里啦!并发编程确实是一项强大的技能,但也需要谨慎对待。大家看看今天这个例子,是不是觉得有点隐患啊?希望能引起大家的注意,也学着怎么避开这些坑,别踩雷了! 最后,我想说的是,编程是一门艺术,也是一场冒险。每次遇到新挑战,我都觉得像打开一个神秘的盲盒,既兴奋又紧张。不过呢,光有好奇心还不够,还得有点儿耐心,就像种花一样,得一点点浇水施肥,不能急着看结果。相信只要我们不断学习、不断反思,就一定能写出更加优雅、高效的代码! 祝大家编码愉快!
2025-04-25 16:14:17
32
凌波微步
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 | 翻译:邓永嘉 | 校对:王永雷、陈久宁 | 编辑:金心悦 | 设计:张千禧 中文版 谈到开源软件,有一个较大且日益严重的问题:大多数组织都是索取者,而不是给予者。 漫画XKCD展示了一个较为经典的代表现代数字基础设施的巨大结构,它是由“内布拉斯加州的某位人士”创建的微小组件,该组件“自2003年来一直都处于吃力不讨好的状态”。 Randall Monroe 的XKCD漫画展示了目前开源面临的困境:过度依赖少数项目维护志愿者。 (开源项目由志愿者自发来维护,)这本来会是一件很有趣的事情,只是去年十二月在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
转载
Go Gin
...于记录每次请求的处理时间: go router.Use(func(c gin.Context) { start := time.Now() c.Next() // 传递控制权给下一个中间件或处理函数 duration := time.Since(start) log.Printf("%s took %s", c.Request.Method, duration) }) 四、创建Go Gin应用 接下来,我们将创建一个简单的Go Gin应用程序。 首先,我们需要导入所需的包: go import ( "fmt" "log" "github.com/gin-gonic/gin" ) 然后,我们可以创建一个函数,用于初始化我们的应用: go func main() { router := gin.Default() // 在这里添加你的路由和中间件... router.Run(":8080") } 在这个函数中,我们创建了一个新的路由器实例,并调用了其Run方法来启动我们的应用程序。 五、第一个Hello World示例 现在,让我们来看一个简单的例子,它将输出"Hello, Gin!"。 go router := gin.Default() router.GET("/", func(c gin.Context) { c.String(200, "Hello, Gin!") }) 当你运行这个程序并访问"http://localhost:8080/"时,你应该可以看到"Hello, Gin!"。 六、总结 Go Gin是一个强大而易于使用的Web开发框架。经过这篇教程的学习,你现在对如何亲手安装Go Gin这套工具已经门儿清了,而且还掌握了创建并跑起一个基础的Go Gin应用程序的独门秘籍。接下来,你可以试着解锁更多Go Gin的玩法,比如捣鼓捣鼓错误处理、尝试尝试模板渲染这些功能,这样一来,你的编程技能肯定能噌噌噌地往上涨!最后,祝愿你在学习Go Gin的过程中愉快!
2024-01-04 17:07:23
527
林中小径-t
Go-Spring
...cache.New(time.Duration(5time.Minute), time.Minute) 然后,我们在某个业务逻辑中,使用这个缓存实例来获取数据: go val, ok := cache.Get("key") if !ok { val = doSomeExpensiveWork() cache.Set("key", val, 5time.Minute) } 在这个案例中,如果我们的缓存服务出现了异常,那么就会导致缓存无法正确工作,从而影响到整个系统的运行。 五、解决缓存服务异常的方法 针对上述案例中的缓存服务异常问题,我们可以采取以下几种方法进行解决: 1. 监控缓存服务状态 我们可以通过日志或者告警工具,对缓存服务的状态进行实时监控,一旦发现异常,就可以立即进行处理。 2. 分析异常原因 对于出现的异常,我们需要对其进行详细的分析,找出问题的根源。可能的原因包括缓存数据过期、缓存污染等。 3. 修复异常 根据异常的原因,我们可以采取相应的措施进行修复。比如说,如果是因为缓存数据过期引发的问题,我们在给缓存设定有效期的时候,可以适当把它延长一下,就像把牛奶的保质期往后推几天,保证它不会那么快变质一样。 六、结论 总的来说,缓存服务异常是我们在使用Go-Spring时经常会遇到的问题。对于这个问题,咱们得瞪大眼睛瞧清楚,心里有个数,这样才能在第一时间察觉到任何不对劲的地方,迅速把它摆平。同时呢,咱们也得不断给自己充电、提升技能,好让自己能更游刃有余地应对那些越来越复杂的开发难题。 七、结尾 希望通过这篇文章,大家能够对缓存服务异常有一个更深入的理解,并学会如何去解决这类问题。如果你有任何其他的问题或者建议,欢迎留言讨论。让我们一起进步,共同成长!
2023-11-23 18:26:05
511
心灵驿站-t
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 手机都是有震动的效果的,前天飞刀从手机里拆了一个振动器,然后让我下周把这个调一下,昨天来公司小试了一把,就搞定了。下面把过程讲一下吧。 其实android中已经做好了底层的驱动,那便是timed_gpio,就是把定时功能和gpio的功能结合在一起了,振动就是一个小直流电机了,当gpio口是高电平的时候,电机就转动了,当gpio口为低电平的时候,电机就不转了,而time是控制转的时间,也就是gpio口处于高电平的时间。 具体的代码就在/drivers/staging/android/timed_gpio.c 在相关平台的platform.c中加入platform device就可以了。 static struct timed_gpio vibrator = {.name = “vibrator”,.gpio = 61, //对应自己平台的gpio号.max_timeout = 100000,.active_low = 0;};static struct timed_gpio_platform_data timed_gpio_data = {.num_gpios = 1,.gpios = &vibrator,};static struct platform_device my_timed_gpio = {.name = “timed-gpio”,.id = -1,.dev = {.platform_data = &timed_gpio_data,},}; 然后在make menuconfig中选上device下的staging下的android中的相关选项 然后就可以跑一下内核来了,当内核跑起来后,就可以测试了。 因为timed gpio驱动程序为每个设备在/sys/class/timed_output/目录下建立一个子 录,设备子目录的enable文件就是控制设备的时间的。因为在platform中名称为vibrator, 所以,用以下命令可以测试: echo 10000 > /sys/class/timed_output/vibrator/enable 然后可以看下振动器在转了,也可以用示波器或者万用表来验证 接着可以 cat /sys/class/timed_output/vibrator/enable 发现enable的值一直在变小,直到为0的时候停止了转动了。 OK,底层驱动好了,那么android上层就好办多了,因为android上层几乎和平台关系不大,要改的东西很少很少。 至于android硬件抽象层,在hardware/libhardware_legacy/include/hardware_legacy/ vibrator目录下。 include <hardware_legacy/vibrator.h>include "qemu.h"include <stdio.h>include <unistd.h>include <fcntl.h>include <errno.h>define THE_DEVICE "/sys/class/timed_output/vibrator/enable"int vibrator_exists(){int fd;ifdef QEMU_HARDWAREif (qemu_check()) {return 1;}endiffd = open(THE_DEVICE, O_RDWR);if(fd < 0)return 0;close(fd);return 1;}static int sendit(int timeout_ms){int nwr, ret, fd;char value[20];ifdef QEMU_HARDWAREif (qemu_check()) {return qemu_control_command( "vibrator:%d", timeout_ms );}endiffd = open(THE_DEVICE, O_RDWR);if(fd < 0)return errno;nwr = sprintf(value, "%d\n", timeout_ms);ret = write(fd, value, nwr);close(fd);return (ret == nwr) ? 0 : -1;}int vibrator_on(int timeout_ms){/ constant on, up to maximum allowed time /return sendit(timeout_ms);}int vibrator_off(){return sendit(0);} 看到了吧 define THE_DEVICE "/sys/class/timed_output/vibrator/enable" 就是我们要操作的底层驱动的地方,只要这个和驱动配上,那么剩下的事情就木有了,直接搞定了。 其实她也是往这里写数据,android的java层就不关心她了。好了,然后可以在android启动后设置一个闹钟来测试下了,发现可以,至此android的vibrator移植成功。 突然发现了,其实以前觉得很难得东西,很不好理解的东西,在过一段时间后再回过头去看的时候才会恍然大悟。学习是个漫长的过程,是一个知识慢慢积累的过程,一口气是吃不成胖子的。 本篇文章为转载内容。原文链接:https://blog.csdn.net/eastmoon502136/article/details/7909688。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-01-17 14:30:45
82
转载
Beego
... ( "fmt" "time" ) func main() { for i := 1; i <= 5; i++ { go func(i int) { time.Sleep(time.Second) fmt.Println("Task", i, "completed") }(i) } } 在这个示例中,我们创建了5个goroutine,每个goroutine都会打印出一条消息,然后暂停1秒钟再继续执行下一个任务。 四、将队列系统集成到Beego中 有了goroutine,我们就可以开始考虑如何将队列系统集成进来了。在这里,我们选择RabbitMQ作为我们的队列系统。RabbitMQ,这可是个超级实用的开源消息“快递员”,它能和各种各样的通信协议打成一片,而且这家伙的可靠性贼高,性能也是杠杠的,就像个不知疲倦的消息传输小超人一样。 在Beego中,我们可以使用beego-queue这个库来与RabbitMQ进行交互。首先,我们需要安装这个库: bash go get github.com/jroimartin/beego-queue 然后,我们可以创建一个生产者,用于向队列中添加任务: go package main import ( "github.com/jroimartin/beego-queue" ) func main() { queue := beego.NewQueue(8, "amqp://guest:guest@localhost:5672/") defer queue.Close() for i := 1; i <= 5; i++ { task := fmt.Sprintf("Task %d", i) if err := queue.Put(task); err != nil { panic(err) } } } 在这个示例中,我们创建了一个新的队列,并向其中添加了5个任务。每个任务都是一条字符串。 接下来,我们可以创建一个消费者,用于从队列中获取并处理任务: go package main import ( "github.com/jroimartin/beego-queue" ) func handleTask(task string) { fmt.Println("Received task:", task) } func main() { queue := beego.NewQueue(8, "amqp://guest:guest@localhost:5672/") defer queue.Close() go queue.Consume(handleTask) for i := 1; i <= 5; i++ { task := fmt.Sprintf("Task %d", i) if err := queue.Put(task); err != nil { panic(err) } } } 在这个示例中,我们创建了一个消费者函数handleTask,它会接收到从队列中取出的任务,并打印出来。然后,我们启动了一个goroutine来监听队列的变化,并在队列中有新任务时调用handleTask。 五、结论 通过以上步骤,我们已经在Beego中成功地实现了异步任务处理和队列系统的集成。这不仅可以提高我们的程序性能,还可以使我们的代码更易于维护和扩展。当然啦,这只是处理异步任务的一种入门级做法,实际上,咱们完全可以按照自身需求,解锁更多玩法。比如,我们可以用Channel来搭建一个沟通桥梁,或者尝试不同类型的队列系统,这些都能够让任务处理变得更灵活、更高效。希望这篇文章能对你有所帮助!
2023-04-09 17:38:09
487
昨夜星辰昨夜风-t
Golang
...系统时,可能会涉及长时间运行的操作。Go的context包能帮助我们优雅地取消长时间运行的任务。例如,在读取大文件时,我们可以适时地中止IO操作。 go import ( "context" "io/ioutil" "time" ) ctx, cancel := context.WithTimeout(context.Background(), 5time.Second) defer cancel() data, err := ioutil.ReadAll(ctx, openFile("largefile.bin")) if err != nil { select { case <-ctx.Done(): fmt.Println("Read operation timed out.") default: panic(err) } } 4. 并发操作 同步与互斥 Go的并发特性使得同时对多个文件进行操作变得轻而易举,但同时也需要注意同步问题。在日常使用中,比如大家伙都在同一个文件夹里操作文件的时候,咱们得聪明点,巧妙运用像sync.Mutex这样的同步工具,来避免出现资源争夺的情况哈。就像是大家一起玩一个游戏,要轮流来,不能抢,这样才能保证每个人的操作都能顺利完成,不乱套。 go import ( "os" "sync" ) var mutex = &sync.Mutex{} func writeFile(filename string, content string) { mutex.Lock() defer mutex.Unlock() file, err := os.Create(filename) if err != nil { panic(err) } defer file.Close() _, err = file.WriteString(content) if err != nil { panic(err) } } // 在多个goroutine中调用writeFile函数,此时它们会按照顺序依次执行 总之,熟练掌握Go语言进行文件系统操作的关键在于理解并正确应用相关API,严谨对待错误处理,充分利用Go的并发特性并妥善解决由此带来的同步问题。希望以上的探讨和实例代码能实实在在帮到你,让你更溜地掌握Go语言在操作文件系统方面的绝活儿,这样一来,你的程序设计不仅效率更高,还更稳更靠谱!
2024-02-24 11:43:21
428
雪落无痕
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 4、第四部分 (1) oname = ${patsubst %.c,%.o,${patsubst %.S,%.o,$(1)} } 自定义函数,$(1)表示调用oname这个函数的第一个参数,patsubst是make内置函数,即模式字符串替换函数。 oname函数实现的功能是: 将第一个参数中符合%.s模式的替换成%.o 再继续将上述结果中符合%.c模式的替换成%.o 也就是把所有 .s 和 .c文件名替换成 .o文件名。 这个函数的功能就是计算源文件名(c源文件,汇编源文件)所相对应的目标文件名(经过编译汇编后的文件)。 CONTIKI_OBJECTFILES = ${addprefix $(OBJECTDIR)/,${call oname, $(CONTIKI_SOURCEFILES)} }PROJECT_OBJECTFILES = ${addprefix $(OBJECTDIR)/,${call oname, $(PROJECT_SOURCEFILES)} } 定义CONTIKI_OBJECTFILES变量 首先用oname函数,将CONTIKI_SOURCEFILES所对应的源文件名,改为目标文件名,如process.c将会变为process.o 再在文件名前边加上前缀$(OBJECTDIR)/,前边我们知道这个变量为obj_native,故process.c会变为obj_native/process.o 这个变量应该是代表即将生成的Contiki操作系统的目标文件名 定义PROJECT_OBJECTFILES变量 功能同上 这个变量应该是代表即将生成的项目中的目标文件名 PROJECT_SOURCEFILES这个变量为空,所以PROJECT_OBJECTFILES也为空。 Provide way to create $(OBJECTDIR) if it has been removed by make clean$(OBJECTDIR):mkdir $@ $@是自动化变量,表示规则中的目标文件集。我们知道OBJECTDIR为obj_native,所以$@为obj_native。 mkdir $@生成obj_native目录。 但是这个依赖关系链,怎么会涉及到obj_native的? 调试了一下: 在生成CONTIKI_OBJECTFILES所代表的文件时,目录不存在,会先找依赖关系生成目录,再生成具体文件。 所以mkdir obj_native会被执行。 (2) ifdef APPSAPPDS = ${wildcard ${foreach DIR, $(APPDIRS), ${addprefix $(DIR)/, $(APPS)} }} \${wildcard ${addprefix $(CONTIKI)/apps/, $(APPS)} \${addprefix $(CONTIKI)/platform/$(TARGET)/apps/, $(APPS)} \$(APPS)}APPINCLUDES = ${foreach APP, $(APPS), ${wildcard ${foreach DIR, $(APPDS), $(DIR)/Makefile.$(APP)} }}-include $(APPINCLUDES)APP_SOURCES = ${foreach APP, $(APPS), $($(APP)_src)}DSC_SOURCES = ${foreach APP, $(APPS), $($(APP)_dsc)}CONTIKI_SOURCEFILES += $(APP_SOURCES) $(DSC_SOURCES)endif The project's makefile can also define in the APPS variable a list of applications from the apps/ directory that should be included in the Contiki system. hello-world这个例子没有定义APPS变量,故这段不会执行。 我们假设定义了APPS变量,其值为APPS += antelope unit-test。 相关知识点: wildcard函数: 返回所有符合pattern的文件名,以空格隔开。 $(wildcard pattern) The argument pattern is a file name pattern, typically containing wildcard characters (as in shell file name patterns). The result of wildcard is a space-separated list of the names of existing files that match the pattern. foreach函数: The syntax of the foreach function is: $(foreach var,list,text) The first two arguments, var and list, are expanded before anything else is done; note that the last argument, text, is not expanded at the same time. Then for each word of the expanded value of list, the variable named by the expanded value of var is set to that word, and text is expanded. Presumably text contains references to that variable, so its expansion will be different each time. The result is that text is expanded as many times as there are whitespace-separated words in list. The multiple expansions of text are concatenated, with spaces between them, to make the result of foreach. 每次从list中取出一个词(空格分隔),赋给var变量,然后text(一般有var变量)被拓展开来。 只要list中还有空格分隔符就会一直循环下去,每一次text返回的结果都会以空格分隔开。 ${wildcard ${foreach DIR, $(APPDIRS), ${addprefix $(DIR)/, $(APPS)} }} 先分析${foreach DIR, $(APPDIRS), ${addprefix $(DIR)/, $(APPS)} } 其中DIR是变量(var),$(APPDIRS)是列表(list),这个例子中没有定义APPDIRS这个变量,估计是用于定义除了$CONTIKI/apps/之外的apps目录。 ${addprefix $(DIR)/, $(APPS)}是text。我们假设定义了APPDIRS为a b。 那么第一次:DIR 会被赋值为a,${addprefix $(DIR)/, $(APPS)},又我们假定APPS为antelope unit-test,所以最终会被拓展为a/antelope a/unit-test。 DIR 会被赋值为b,${addprefix $(DIR)/, $(APPS)},又我们假定APPS为antelope unit-test,所以最终会被拓展为b/antelope b/unit-test。 最终这两次结果会以空格分隔开,即a/antelope a/unit-test b/antelope b/unit-test ${wildcard a/antelope a/unit-test b/antelope b/unit-test} 返回空,因为找不到符合这样的目录。 所以最终这句语句,实现的功能是,返回$APPDIRS目录中,所有符合$APPS的目录。 ${wildcard ${addprefix $(CONTIKI)/apps/, $(APPS)} 这句语句返回$(CONTIKI)/apps/目录下所有符合$APPS的目录,即contiki-release-2-7/apps/antelope contiki-release-2-7/apps/unit-test ${addprefix $(CONTIKI)/platform/$(TARGET)/apps/, $(APPS)} 这句语句返回$(CONTIKI)/platform/$(TARGET)/apps/目录下所有$APPS的目录,即contiki-release-2-7/platform/native/apps/antelope contiki-release-2-7/platform/native/apps/unit-test。 在contiki-release-2-7/platform/native目录下,并没有apps目录,后边有差错处理机制。 $(APPS) 在当前目录下的所有$APPS目录,即antelope unit-test。 在hello-world例子中,并没有这些目录。 所以APPDS变量是包含所有与$APPS有关的目录。 APPINCLUDES变量是所有需要导入的APP Makefile文件。 在所有APPDS目录下,所有Makefile.$(APPS)文件。 在我们的假设条件APPS = antelope unit-test, APPDIRS = 只会导入contiki-release-2-7/apps/antelope/Makefile.antelope contiki-release-2-7/apps/unit-test/Makefile.unit-test 其余的均不存在,所以在include指令前要有符号-,即出错继续执行后续指令。 contiki-release-2-7/apps/antelope/Makefile.antelope: 分别定义了两个变量,antelope_src用于保存antelope这个app的src文件,antelope_dsc用于保存antelope这个app的dsc文件。 contiki-release-2-7/apps/unit-test/Makefile.unit-test: 分别定义了两个变量,unit-test_src用于保存unit-test这个app的src文件,unit-tes_dsc用于保存unit-test这个app的dsc文件。 变量APP_SOURCES APP_SOURCES = ${foreach APP, $(APPS), $($(APP)_src)} 取出所有APPS中的src文件变量,这个例子是$(antelope_src) 和$(unit-test_src) 变量APP_SOURCES DSC_SOURCES = ${foreach APP, $(APPS), $($(APP)_dsc)} 取出所有APPS中的dsc文件变量,这个例子是$(antelope_dsc) 和$(unit-test_dsc) CONTIKI_SOURCEFILES += $(APP_SOURCES) $(DSC_SOURCES) 这段话的最终目的: 将$APPS相关的所有源文件添加进CONTIKI_SOURCEFILES变量中。 (3) target_makefile := $(wildcard $(CONTIKI)/platform/$(TARGET)/Makefile.$(TARGET) ${foreach TDIR, $(TARGETDIRS), $(TDIR)/$(TARGET)/Makefile.$(TARGET)}) Check if the target makefile exists, and create the object directory if necessary.ifeq ($(strip $(target_makefile)),)${error The target platform "$(TARGET)" does not exist (maybe it was misspelled?)}elseifneq (1, ${words $(target_makefile)})${error More than one TARGET Makefile found: $(target_makefile)}endifinclude $(target_makefile)endif 这断代码主要做的就是,找到在所有TAGET目录下找到符合的Makefile.$(TARGET)文件,放到target_makefile变量中。 再检查是否存在或者重复。并做相应的错误提示信息。 ${error The target platform "$(TARGET)" does not exist (maybe it was misspelled?)} ${error More than one TARGET Makefile found: $(target_makefile)} 我们这个例子中 TARGET = native 并且 TARGETDIRS为空 所以最后会导入$(CONTIKI)/platform/native/Makefile.native 接下去要开始分析target和cpu的makefile文件了。 转载于:https://www.cnblogs.com/songdechiu/p/6012718.html 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_34399060/article/details/94095820。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-28 09:49:23
282
转载
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
ps aux | grep keyword
- 查找包含关键词的进程。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
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
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"