首页
说说
博客APP
友情链接
时间轴
更多
精美壁纸
留言板
扶贫计划
投稿
推荐
网站优化
高考倒计时
烟花模拟器
Search
1
QQ打开链接跳转浏览器代码
4,812 阅读
2
中国疫情和全球疫情实时地图
3,935 阅读
3
QQ卡片实现红包强制进群代码
2,882 阅读
4
利用html写APP公告、更新的文章
2,467 阅读
5
机器人框架倒闭
2,194 阅读
新闻资讯
技术教程
闲言碎语
小曲配故事
话题探讨
源码
站外篇
杂乱分享
Search
标签搜索
Typecho
代码
源码
热歌推荐
话题
swap
api
web
机器人
引流
新冠实时疫情
一言
备份
红包卡片
代刷
宝塔
远程公告
APP
Python
GitHub
有钱终成眷属,没钱亲眼目睹
累计撰写
85
篇文章
累计收到
93
条评论
首页
栏目
新闻资讯
技术教程
闲言碎语
小曲配故事
话题探讨
源码
站外篇
杂乱分享
页面
说说
博客APP
友情链接
时间轴
精美壁纸
留言板
扶贫计划
投稿
推荐
网站优化
高考倒计时
烟花模拟器
搜索到
36
篇与
站外篇
的结果
2020-10-27
技术心得丨一种有效攻击BERT等模型的方法
Is BERT Really Robust? A Strong Baseline for Natural Language Attack on Text Classification and Entailment作者机构:MIT,香港大学,A* STAR论文发表:AAAI2020论文连接:http://aaai.org/Papers/AAAI/2020GB/AAAI-JinD.7014.pdf概要:机器学习模型对对抗样本敏感,在对抗样本上效果下降明显。本文提出了一个生成对抗样本的模型,TEXTFOOLER。通过替换样本中的重要词汇获得对抗样本,在对抗样本上模型的效果急剧下降。该方法可以用于数据增广,提升模型的鲁棒性和泛化能力。背景介绍对抗样本攻击是指通过某种方法生成一些样本,已经训练好的模型在这些生成的对抗样本上的效果急剧下降,模型非常脆弱。对抗样本攻击在计算机视觉领域研究的比较多,但是文本领域相对较少。本文提出了一种对抗样本生成模型,TEXTFOOLER,可以有效的生成对抗样本,并且生成的样本在语法、语义上都比较合理,其计算复杂度是线性的。方法TEXTFOOLER输入:是候选样本X、样本的标注标签Y、已经训练好的模型F、句子相似度计算模型Sim,句子相似度阈值、整个语料词典对应的词向量Emb输出:候选样本的对抗样本,即新生成的样本。主要分两步:第一步:词重要性排序,即获得基于重要性分数排序的词集合W。第二步:对抗样本生成,即基于集合W对原始样本进行词替换获得对抗样本。1. 词重要性排序目标是获得输入样本中每个词在模型预测过程中的重要性。Equation 1 词重要性分数词的重要性分数按上述公式计算,即如果对抗样本标签原始样本标签一样,则重要性分数等于模型预测值得差值,若标签不一样,则重要性分数为标签为原始标签的模型预测值差值和标签为预测标签的模型预测值差值之和。得到每个词的重要性分数后,基于NLTK和spaCy过滤掉停用词,获得最终的词重要性排序集合W。2. 对抗样本生成目标是找到最终的每个词的替换词并用替换词替换样本得到最终的对抗样本集合。1)同义词提取:对W中的每个词wj,根据词向量从词典中找到Top N的同义词,并通过词性过滤后得到候选替换词集合CANDIDATES。2)句子相似度检查:对CANDIDATES中每个词ck,用ck替换wj得到新的对抗样本 同时计算原始样本X和对抗样本之间的相似度 (通过Universal Sentence Encoder得到句子的向量表示,然后计算余弦距离作为相似度)。作为两个句子的语义相似度。相似度高于给定阈值的替换词放进最终的替换词候选集合FINCANDIDATES.3)对于FINCANDIDATES的每个词,如果有候选词改变了模型的预测的类别,那么选择句子相似度最大的词作为最终候选词。如果没有改变模型的预测类别,选择预测置信度最低的词作为最终的替换词。4)重复1)-3)的操作。图 1 生成的对抗样本的例子实验结果实验数据主要包含: 文本分类任务:预测文本的某个标签。 文本蕴含任务:预测文本对中两个句子的关系,即蕴含关系、矛盾关系或者中性。 图 2 在分类任务上的对抗结果图 3 在文本蕴含上的对抗结果结果:对测试集进行对抗样本替换后,准确率急剧下降,甚至到0.和其他对抗模型比较图 4 和其他对抗模型比较结论:从替换词比例和攻击成功率(模型预测错误的比例)两个维度都比基线模型好。人工评价人工评价对抗样本的语法、人工标签、是否保留了原始样本的语义这三个维度。结论:对抗样本语法合理,人工标签和原始样本标签在MR数据集上一致率达92%,句子语义相似度达0.91.控制变量实验通过控制变量的方法验证各个步骤对模型效果的影响。词重要性排序通过取消词重要性排序的步骤看该步骤对模型效果的影响。图 5 取消词重要性排序的结果(Random)结论:词重要性排序很重要。语义相似度约束通过取消候选替换词中的语义相似度约束看该步骤对模型效果的影响。图 6 语义相似度约束对比 “/”前后表示有和无语义相似度约束的结果对比结论:语义相似度约束对结果影响很大。可迁移性由一个模型生成的对抗样本是否可以使得其他模型出错。图 7 对抗样本的可迁移性。行i,列j表示模型i生成的对抗样本在模型j上的准确率结论:模型效果越好,基于该模型生成的对抗样本的可迁移性越高。对抗训练生成的对抗样本可以用于训练模型,增强模型的鲁棒性和泛化能力。图 8 基于对抗样本的对抗训练结果结论:对抗训练可显著提高模型效果。启发:1. 可以通过此方法生成对抗样本可以用于数据增广,加入到训练数据中来增强模型的鲁棒性和泛化能力。2. 可通过文字的重要性词汇排序方法筛选标签相关的主题词汇,如构建情感词典、主题词挖掘、关键词挖掘等。 点击关注,第一时间了解华为云新鲜技术~
2020年10月27日
429 阅读
0 评论
0 点赞
2020-10-27
(数据科学学习手札97)掌握pandas中的transform
本文示例文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes1 简介 开门见山,在pandas中,transform是一类非常实用的方法,通过它我们可以很方便地将某个或某些函数处理过程(非聚合)作用在传入数据的每一列上,从而返回与输入数据形状一致的运算结果。 本文就将带大家掌握pandas中关于transform的一些常用使用方式。 图12 pandas中的transform 在pandas中transform根据作用对象和场景的不同,主要可分为以下几种:2.1 transform作用于Series 当transform作用于单列Series时较为简单,以前段时间非常流行的企鹅数据集为例: 图2我们在读入数据后,对bill_length_mm列进行transform变换: 单个变换函数 我们可以传入任意的非聚合类函数,譬如对数化:# 对数化 penguins['bill_length_mm'].transform(np.log) 图3 或者传入lambda函数:# lambda函数 penguins['bill_length_mm'].transform(lambda s: s+1) 图4 多个变换函数 也可以传入包含多个变换函数的列表来一口气计算出多列结果:penguins['bill_length_mm'].transform([np.log, lambda s: s+1, np.sqrt]) 图5 而又因为transform传入的函数,在执行运算时接收的输入参数是对应的整列数据,所以我们可以利用这个特点实现诸如数据标准化、归一化等需要依赖样本整体统计特征的变换过程:# 利用transform进行数据标准化 penguins['bill_length_mm'].transform(lambda s: (s - s.mean()) / s.std()) 图62.2 transform作用于DataFrame 当transform作用于整个DataFrame时,实际上就是将传入的所有变换函数作用到每一列中:# 分别对每列进行标准化 ( penguins .loc[:, 'bill_length_mm': 'body_mass_g'] .transform(lambda s: (s - s.mean()) / s.std()) ) 图7 而当传入多个变换函数时,对应的返回结果格式类似agg中的机制,会生成MultiIndex格式的字段名:( penguins .loc[:, 'bill_length_mm': 'body_mass_g'] .transform([np.log, lambda s: s+1]) ) 图8 而且由于作用的是DataFrame,还可以利用字典以键值对的形式,一口气为每一列配置单个或多个变换函数:# 根据字典为不同的列配置不同的变换函数 ( penguins .loc[:, 'bill_length_mm': 'body_mass_g'] .transform({'bill_length_mm': np.log, 'bill_depth_mm': lambda s: (s - s.mean()) / s.std(), 'flipper_length_mm': np.log, 'body_mass_g': [np.log, np.sqrt]}) ) 图92.3 transform作用于DataFrame的分组过程 在对DataFrame进行分组操作时,配合transform可以完成很多有用的任务,譬如对缺失值进行填充时,根据分组内部的均值进行填充:# 分组进行缺失值均值填充 ( penguins .groupby('species')[['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g']] .transform(lambda s: s.fillna(s.mean().round(2))) ) 图10 并且在pandas1.1.0版本之后为transform引入了新特性,可以配合Cython或Numba来实现更高性能的数据变换操作,详细的可以阅读( https://github.com/pandas-dev/pandas/pull/32854 )了解更多。 除了以上介绍的内容外,transform还可以配合时间序列类的操作譬如resample等,功能都大差不差,感兴趣的朋友可以自行了解。 以上就是本文的全部内容,欢迎在评论区与我进行讨论
2020年10月27日
489 阅读
0 评论
0 点赞
2020-10-26
【译】自动发现 .NET 5 中代码的潜在错误
写代码是一件令人兴奋的事情,特别是对于 .NET 开发人员来说,平台越来越智能化了。我们现在默认在 .NET SDK 中包含丰富的诊断和代码建议。在您需要安装 NuGet 包或其他独立工具来进行更多的代码分析之前。现在,您将在新的 .NET 5 SDK 中自动获得这些内容。 过去,我们一直不愿意向 C# 添加新的警告。这是因为,对于将警告视为错误的用户来说,添加新的警告从技术上来说是一种对源代码的影响。然而,这些年来,在我们遇到的很多情况中,我们也确实想警告人们有些地方出了问题,从常见的编码错误到常见的 API 误用等等。 从 .NET 5 开始,我们在 C# 编译器中引入了 AnalysisLevel,以一种安全的方式引入新的警告。所有针对 .NET 5 的项目的 AnalysisLevel 默认将被设置为 5,这意味着将引入更多的警告(以及修复它们的建议)。 让我们讨论一下 AnalysisLevel 可能的值在您的项目中意味着什么。首先我们要注意的是:除非你覆盖默认值,否则 AnalysisLevel 是基于你的目标框架设置的: 目标框架 默认值 net5.0 5 netcoreapp3.1 or lower 4 netstandard2.1 or lower 4 .NET Framework 4.8 or lower 4 但是,0-3 呢?下面是对每个分析级别值含义的更详细的细分: AnalysisLevel 对C#编译器的影响 高级平台API分析 5 获得新的编译器语言分析(详细内容如下) Yes 4 与之前版本中向 C# 编译器传递 -warn:4 相同 No 3 与之前版本中向 C# 编译器传递 -warn:3 相同 No 2 与之前版本中向 C# 编译器传递 -warn:2 相同 No 1 与之前版本中向 C# 编译器传递 -warn:1 相同 No 0 与之前版本中向 C# 编译器传递 -warn:0 一样,关闭所有发出警告 No 由于 AnalysisLevel 与项目的目标框架绑定在一起,除非你改变了你的代码目标框架,否则你永远不会改变默认的分析级别。不过,你可以手动设置分析级别。例如,即使我们的目标是 .NET Core App 3.1 或 .NET Standard (因此 AnalysisLevel 默认为 4),你仍然可以选择更高的级别。这里有一个例子: Exe netcoreapp3.1 5 如果你想要最高的分析级别,你可以在你的项目文件中指定 latest: Exe netcoreapp3.1 latest 如果你很有冒险精神,并且希望尝试实验性的编译器和平台分析,那么可以指定 preview 来获得最新的、最前沿的代码诊断。 请注意,当您使用 latest 或 preview 时,分析结果可能会因机器而异,这取决于可用的 SDK 和它提供的最高分析级别。 Exe netcoreapp3.1 preview 最后,也可以设置为 none,这意味着“我不想看到任何新的警告”。在这种模式下,你不会得到任何高级 API 分析,也不会得到新的编译器警告。如果你需要更新框架,但还没有准备好接受新的警告,那么这将非常有用。 Exe net5 none 你还可以在 Visual Studio 中通过 Code Analysis 属性页配置项目的分析级别。只需从解决方案资源管理器导航到项目属性页。然后转到 Code Analysis 选项卡。 在未来,我们将为 .NET 的每个版本添加一个新的分析级别。目标是确保给定的分析级别总是表示相同的默认分析集(规则及其严重性)。如果我们想在默认情况下启用现有的规则,我们将在即将到来的分析级别中这样做,而不是更改现有的级别。这确保了已有的项目/源代码总是产生相同的警告,不管 SDK 有多新(当然,除了项目使用 preview 或 latest)。 由于所有的 .NET 5 项目都将进入分析级别 5,让我们来看看一些新的警告和建议。分析级别 5 中出现的所有新的警告和错误 粗体部分将在 .NET 5 发布的时候进入第 5 级。剩下的是 Visual Studio 2019 16.8 预览2 中的 .NET 5 预览8 中的新警告! 常见错误的警告 第一组新的警告旨在发现潜在的错误,通常是在较大的代码库中。现在不需要额外的编译器分析就可以很容易地发现它们。当表达式永真或永假时发出警告 这种新的警告非常普遍,考虑以下代码:public voidM(DateTime dateTime) {if (dateTime == null) //warning CS8073 {return; } } DateTime 是一个结构体,结构体不能为空。从 .NET 开始,我们将在 CS8073 中警告这种情况。警告信息是: Warning CS8073: The result of the expression is always ‘false’ since the value of type ‘DateTime’ is never equal to ‘null’ of type ‘DateTime?’ 很明显,这段代码所做的事情没有意义,但是考虑到这样的检查可能发生在有多个参数要验证的方法中。要解决这个问题,你可以删除代码(因为它总是假的,它没有做任何事情),或者改变它的类型为 DateTime? 如果参数的预期值为 null。public void M(DateTime? dateTime) //We accept a null DateTime {if (dateTime == null) //No Warnings {return; } }不允许在静态类型上用as、 is 下面是一个很好的小改进:static classFiz { }classP {bool M(objecto) {return o is Fiz; //CS7023 } } 因为 Fiz 是一个静态类,所以像 o 这样的实例对象永远不可能是这种类型的实例。我们会收到这样的警告: Warning CS7023 The second operand of an ‘is’ or ‘as’ operator may not be static type ‘Fiz’ 解决这个问题的方法是重构我们的代码(也许我们一开始就检查错类型了),或者让类 Fiz 是非静态的:classFiz { }classP {bool M(objecto) {return o is Fiz; //no error } }不允许锁定非引用类型 锁定非引用类型(比如 int)什么也做不了,因为它们是按值传递的,所以每个堆栈帧上都有不同版本的非引用类型。在过去,对于像 lock(5) 这样简单的情况,我们会警告你对非引用类型的锁定,但是直到最近,我们对泛型方法的也支持警告:public classP {public static void GetValue(TKey key) {lock (key) //CS0185 { } }static voidMain() { GetValue(1); } } 这是一个错误,因为传入 int(在这个不受约束的泛型中允许)实际上不会正确锁定。我们会看到这个错误: Error CS0185 ‘TKey’ is not a reference type as required by the lock statement 要解决这个问题,我们需要指出 GetValue 方法应该只提供引用类型。我们可以使用泛型类型约束来做到这一点,where TKey : classpublic classP {public static void GetValue(TKey key) where TKey : class{lock (key) //no error { } } }重新抛出以保留堆栈细节 我们都是“优秀的”开发人员,所以我们的代码不会抛出异常,对吗?好吧,即使是最好的开发人员也需要处理异常,而新程序员常陷入的一个陷阱是:try{throw newException(); }catch(Exception ex) {//probably logging some info here...//rethrow now that we are done throw ex; //CA2200 } 在学校里,我学到如果有人向我扔球,我接住它,我必须把球扔回去!像这样的比喻让很多人相信 throw ex 是重新抛出这个异常的正确方式。遗憾的是,这将改变原来异常中的堆栈。现在您将收到一个警告,说明正在发生这种情况。它是这样的: Warning CA2200 Re-throwing caught exception changes stack information 在几乎所有情况下,这里要做的正确事情是简单地使用 throw 关键字,而不提及我们捕获的异常的变量。try{throw newException(); }catch(Exception ex) {//probably logging some info here...//rethrow now that we are done throw; } 我们还提供了一个代码修复,可以轻松地在您的文档、项目或解决方案中一次性修复所有这些问题!不要在值类型中使用 ReferenceEquals Equality 在 .NET 中是一个棘手的话题。下一个警告试图使意外地通过引用比较一个 struct 。考虑以下代码:int int1 = 1;int int2 = 1; Console.WriteLine(object.ReferenceEquals(int1, int2)); //warning CA2013 这将装箱两个 int,而 ReferenceEquals 将总是返回 false 作为结果。我们将看到这个警告描述: Warning CA2013: Do not pass an argument with value type ‘int’ to ‘ReferenceEquals’. Due to value boxing, this call to ‘ReferenceEquals’ will always return ‘false’. 解决此错误的方法是使用相等运算符 == 或 object.Equals:int int1 = 1;int int2 = 1; Console.WriteLine(int1== int2); //using the equality operator is fine Console.WriteLine(object.Equals(int1, int2)); //so is object.Equals跟踪跨程序集中结构的明确赋值(definite assignment) 很多人可能会惊讶地发现,下一个警告其实并不算是警告:usingSystem.Collections.Immutable;classP {public void M(out ImmutableArray immutableArray) //CS0177 { } } 这条规则是关于明确赋值的,这是 C# 中一个有用的特性,可以确保你不会忘记给变量赋值。 Warning CS0177: The out parameter ‘immutableArray’ must be assigned to before control leaves the current method 目前已经针对几种不同的情况发布了 CS0177,但不是前面展示的情况。这里的历史是,这个 bug 可以追溯到 C# 编译器的原始实现。以前,C# 编译器在计算明确赋值时忽略从元数据导入的值类型中的引用类型的私有字段。这个非常特殊的错误意味着像 ImmutableArray 这样的类型能够逃脱明确赋值分析。 现在编译器将正确的显示错误,你可以修复它,只要确保它总是分配一个值,像这样:usingSystem.Collections.Immutable;classP {public bool M(out ImmutableArray immutableArray) //no warning { immutableArray= ImmutableArray.Empty; } }.NET API 使用错误的警告 下面示例是关于正确使用 .NET 库的。分析级别可以防止现有的 .NET API 的不当使用,但它也会影响 .NET 库的发展。如果设计了一个有用的 API,但它有可能被误用,那么还可以在新增 API 的同时添加一个检测误用的新警告。不要给从 MemoryManager 的派生类定义终结器 当你想实现自己的 Memory 类型时,MemoryManager 是一个有用的类。这不是你经常做的事情,但是当你需要它的时候,你真的需要它。这个新的警告会触发这样的情况:class DerivedClass : MemoryManager{public override bool Dispose(booldisposing) {if(disposing) { _handle.Dispose(); } }~DerivedClass() => Dispose(false); //warning CA2015 } 向这种类型添加终结器可能会在垃圾收集器中引入漏洞,这是我们都希望避免的! Warning CA2015 Adding a finalizer to a type derived from MemoryManager may permit memory to be freed while it is still in use by a Span. 解决方法是删除这个终结器,因为它会在你的程序中导致非常细微的 bug,很难找到和修复。class DerivedClass : MemoryManager{public override bool Dispose(booldisposing) {if(disposing) { _handle.Dispose(); } }//No warning, since there is no finalizer here }参数传递给 TaskCompletionSource,调用错误的构造函数 这个警告告诉我们,我们使用了错误的枚举。var tcs = new TaskCompletionSource(TaskContinuationOptions.RunContinuationsAsynchronously); //warning CA2247 除非你已经意识到这个问题,否则你可能会在盯着它看一会儿才能发现。问题是这样的,这个构造函数不接受 TaskContinuationOptions 枚举,它接受 TaskCreationOptions 枚举。发生的事情是,我们正在调用的 TaskCompletionSource 的构造函数接受 object 类型参数!考虑到它们的名称特别相似,并且它们的值也非常相似,所以这种错误容易发生。 Warning CA2247: Argument contains TaskContinuationsOptions enum instead of TaskCreationOptions enum. 修复它只需要传递正确的枚举类型:var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); //no warning当代码不能在所有平台上工作时发出警告 这个真是太棒了!我不会在这里详细讨论它的复杂之处(期待以后关于这个主题的博客文章)。但这里警告的目的是让您知道,您正在调用的 api 可能无法在您正在构建的所有目标上工作。 假设我有一个同时在 Linux 和 Windows 上运行的应用程序。我有一个方法,我使用它来获得路径来创建日志文件,根据运行环境,它有不同的行为。private static stringGetLoggingPath() {var appDataDirectory =Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);var loggingDirectory = Path.Combine(appDataDirectory, "Fabrikam", "AssetManagement", "Logging");//Create the directory and restrict access using Windows//Access Control Lists (ACLs). var rules = new DirectorySecurity(); //CA1416 rules.AddAccessRule(new FileSystemAccessRule(@"fabrikam\log-readers", FileSystemRights.Read, AccessControlType.Allow) ); rules.AddAccessRule(new FileSystemAccessRule(@"fabrikam\log-writers", FileSystemRights.FullControl, AccessControlType.Allow) );if (!OperatingSystem.IsWindows()) {//Just create the directory Directory.CreateDirectory(loggingDirectory); }else{ Directory.CreateDirectory(loggingDirectory, rules); }returnloggingDirectory; } 我正确地使用了 OperatingSystem.IsWindows() 来检查操作系统是否是 Windows 操作系,但是实际上 if 分支之前已经使用了平台特定的 API,将不能工作在 Linux! Warning CA1416: ‘DirectorySecurity’ is unsupported on ‘Linux’ 处理这个问题的正确方法是将所有特定于平台的代码移动到 else 语句中。private static stringGetLoggingPath() {var appDataDirectory =Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);var loggingDirectory = Path.Combine(appDataDirectory, "Fabrikam", "AssetManagement", "Logging");if (!OperatingSystem.IsWindows()) {//Just create the directory Directory.CreateDirectory(loggingDirectory); }else{//Create the directory and restrict access using Windows//Access Control Lists (ACLs). var rules = new DirectorySecurity(); //CA1416 rules.AddAccessRule(new FileSystemAccessRule(@"fabrikam\log-readers", FileSystemRights.Read, AccessControlType.Allow) ); rules.AddAccessRule(new FileSystemAccessRule(@"fabrikam\log-writers", FileSystemRights.FullControl, AccessControlType.Allow) ); Directory.CreateDirectory(loggingDirectory, rules); }returnloggingDirectory; }低级编码帮助 在编写高性能应用程序时,还有一些有用的警告。下面这些警告确保您不需要为这些情况牺牲安全性。P/Invoke 时不要在 string 参数上使用 OutAttribute 有时你需要与本地代码进行互操作。.NET 有使用平台调用服务的概念(P/ Invoke)来简化这个过程。但是,在 .NET 中,在向本地库发送数据和从本地库发送数据方面存在一些问题。考虑以下代码:[DllImport("MyLibrary")]private static extern void Goo([Out] string s); //warning CA1417 除非您非常熟悉 P/Invoke 的编写,否则这里的错误并不明显。通常将 OutAttribute 应用于运行时不知道的类型,以指示应该如何封送类型。OutAttribute 表示您正在按值传递数据。字符串按值传递没有意义,而且有可能导致运行时崩溃。 Warning CA1417 Do not use the ‘OutAttribute’ for string parameter ‘s’ which is passed by value. If marshalling of modified data back to the caller is required, use the ‘out’ keyword to pass the string by reference instead. 解决这个问题的方法是将其作为一个普通的 out 参数(通过引用传递)来处理。[DllImport("MyLibrary")]private static extern void Goo(out string s); //no warning 或者如果你不需要将字符串封送回调用者,你可以这样做:[DllImport("MyLibrary")]private static extern void Goo(string s); //no warning在适当情况下,string 使用 AsSpan 而不是基于范围的索引器 这都是为了确保您不会意外地分配字符串。classProgram {public void TestMethod(stringstr) { ReadOnlySpan slice = str[1..3]; //CA1831 } } 在上面的代码中,开发者的意图是使用 C# 中新的基于范围的索引特性来索引一个字符串。不幸的是,这实际上会分配一个字符串,除非您首先将该字符串转换为 span。 Warning CA1831 Use ‘AsSpan’ instead of the ‘System.Range’-based indexer on ‘string’ to avoid creating unnecessary data copies 解决方法是在这种情况下添加 AsSpan 调用:classProgram {public void TestMethod(stringstr) { ReadOnlySpan slice = str.AsSpan()[1..3]; //no warning } }不要在循环中使用 stackalloc stackalloc 关键字非常适合于确保正在进行的操作对垃圾收集器来说比较容易。在过去,stackalloc 关键字用于不安全的代码上下文中,以便在堆栈上分配内存块。但自从 C# 8 以来,它也被允许在 unsafe 的块之外,只要这个变量被分配给一个 Span 或一个 ReadOnlySpan。classC {public void TestMethod(stringstr) {int length = 3;for (int i = 0; i < length; i++) { Span numbers = stackalloc int[length]; //CA2014 numbers[i] =i; } } } 在堆栈上分配大量内存可能会导致著名的 StackOverflow 异常,即我们在堆栈上分配的内存超过了允许的范围。在循环中分配尤其危险。 Warning CA2014 Potential stack overflow. Move the stackalloc out of the loop. 解决方法是将 stackalloc 移出循环:classC {public void TestMethod(stringstr) {int length = 3; Span numbers = stackalloc int[length]; //no warning for (int i = 0; i < length; i++) { numbers[i]=i; } } }设置分析级别 现在您已经看到了这些警告的重要性,您可能永远不想回到一个没有它们的世界,对吗?我知道世界并不总是这样运转的。正如在这篇文章的开头提到的,这些都是打破源代码的改变,你应该在适合自己的时间表中完成它们。我们现在介绍这个的部分原因是为了得到两个方面的反馈: 我们提出的这一小部分警告是否太过破坏性 调整警告的机制是否足以满足您的需要 回到 .NET Core 3.1 的分析等级 如果你只想回到 .NET 5 之前的状态(即.NET Core 3.1 中的警告级别),你所需要做的就是在你的项目文件中将分析级别设置为4。下面是一个例子: Exe net5.0 4 只关闭一个规则 如果有一个你认为不适用于你的代码库的特定警告,你可以使用一个 editorconfig 文件来关闭它。你可以通过在错误列表中将警告的严重性设置为“none”来做到这一点。 或者从编辑器中出现警告的灯泡菜单中选择“None”关闭警告的单个实例 如果你大部分时间都想使用这个警告,但在少数情况下要关闭它,你可以使用灯泡菜单中的一个: 在源码中禁止 在单独的禁止文件中禁止它 在源码中禁用并标记一个特性总结 我们希望你对 .NET 5 代码分析的改进感到兴奋,请给我们一些反馈。原文链接 https://devblogs.microsoft.com/dotnet/automatically-find-latent-bugs-in-your-code-with-net-5/
2020年10月26日
406 阅读
0 评论
0 点赞
2020-10-26
Spring Boot 系列:最新版优雅停机详解
爱生活,爱编码,本文已收录架构技术专栏关注这个喜欢分享的地方。开源项目: 分布式监控(Gitee GVP最有价值开源项目 ):https://gitee.com/sanjiankethree/cubic 摄像头视频流采集:https://gitee.com/sanjiankethree/cubic-video 优雅停机目前Spring Boot已经发展到了2.3.4.RELEASE,伴随着2.3版本的到来,优雅停机机制也更加完善了。目前版本的Spring Boot 优雅停机支持Jetty, Reactor Netty, Tomcat和 Undertow 以及反应式和基于 Servlet 的 web 应用程序都支持优雅停机功能。优雅停机的目的:如果没有优雅停机,服务器此时直接直接关闭(kill -9),那么就会导致当前正在容器内运行的业务直接失败,在某些特殊的场景下产生脏数据。增加了优雅停机配置后:在服务器执行关闭(kill -2)时,会预留一点时间使容器内部业务线程执行完毕,此时容器也不允许新的请求进入。新请求的处理方式跟web服务器有关,Reactor Netty、 Tomcat将停止接入请求,Undertow的处理方式是返回503.新版配置YAML配置新版本配置非常简单,server.shutdown=graceful 就搞定了(注意,优雅停机配置需要配合Tomcat 9.0.33(含)以上版本)server: port: 6080 shutdown: graceful #开启优雅停机 spring: lifecycle: timeout-per-shutdown-phase: 20s #设置缓冲时间 默认30s 在设置了缓冲参数timeout-per-shutdown-phase 后,在规定时间内如果线程无法执行完毕则会被强制停机。下面我们来看下停机时,加了优雅停日志和不加的区别://未加优雅停机配置 Disconnected from the target VM, address: '127.0.0.1:49754', transport: 'socket' Process finished with exit code 130 (interrupted by signal 2: SIGINT) 加了优雅停机配置后,可明显发现的日志 Waiting for active requests to cpmplete,此时容器将在ShutdownHook执行完毕后停止。关闭方式1、 一定不要使用kill -9 操作,使用kill -2 来关闭容器。这样才会触发java内部ShutdownHook操作,kill -9不会触发ShutdownHook。2、可以使用端点监控 POST 请求 /actuator/shutdown 来执行优雅关机。添加ShutdownHook通过上面的日志我们发现Druid执行了自己的ShutdownHook,那么我们也来添加下ShutdownHook,有几种简单的方式:1、实现DisposableBean接口,实现destroy方法@Slf4j @Service public class DefaultDataStore implements DisposableBean { private final ExecutorService executorService = new ThreadPoolExecutor(OSUtil.getAvailableProcessors(), OSUtil.getAvailableProcessors() + 1, 1, TimeUnit.MINUTES, new ArrayBlockingQueue(200), new DefaultThreadFactory("UploadVideo")); @Override public void destroy() throws Exception { log.info("准备优雅停止应用使用 DisposableBean"); executorService.shutdown(); } } 2、使用@PreDestroy注解@Slf4j @Service public class DefaultDataStore { private final ExecutorService executorService = new ThreadPoolExecutor(OSUtil.getAvailableProcessors(), OSUtil.getAvailableProcessors() + 1, 1, TimeUnit.MINUTES, new ArrayBlockingQueue(200), new DefaultThreadFactory("UploadVideo")); @PreDestroy public void shutdown() { log.info("准备优雅停止应用 @PreDestroy"); executorService.shutdown(); } } 这里注意,@PreDestroy 比 DisposableBean 先执行关闭原理1、使用kill pid关闭,源码很简单,大家可以看下GracefulShutdown private void doShutdown(GracefulShutdownCallback callback) { List connectors = getConnectors(); connectors.forEach(this::close); try { for (Container host : this.tomcat.getEngine().findChildren()) { for (Container context : host.findChildren()) { while (isActive(context)) { if (this.aborted) { logger.info("Graceful shutdown aborted with one or more requests still active"); callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE); return; } Thread.sleep(50); } } } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } logger.info("Graceful shutdown complete"); callback.shutdownComplete(GracefulShutdownResult.IDLE); } 2、使用端点监控 POST 请求 /actuator/shutdown关闭因为actuator 都使用了SPI的扩展方式,所以我们看下AutoConfiguration,可以看到关键点就是ShutdownEndpoint@Configuration( proxyBeanMethods = false ) @ConditionalOnAvailableEndpoint( endpoint = ShutdownEndpoint.class ) public class ShutdownEndpointAutoConfiguration { public ShutdownEndpointAutoConfiguration() { } @Bean( destroyMethod = "" ) @ConditionalOnMissingBean public ShutdownEndpoint shutdownEndpoint() { return new ShutdownEndpoint(); } } ShutdownEndpoint,为了节省篇幅只留了一点重要的@Endpoint( id = "shutdown", enableByDefault = false ) public class ShutdownEndpoint implements ApplicationContextAware { @WriteOperation public Map shutdown() { if (this.context == null) { return NO_CONTEXT_MESSAGE; } else { boolean var6 = false; Map var1; try { var6 = true; var1 = SHUTDOWN_MESSAGE; var6 = false; } finally { if (var6) { Thread thread = new Thread(this::performShutdown); thread.setContextClassLoader(this.getClass().getClassLoader()); thread.start(); } } Thread thread = new Thread(this::performShutdown); thread.setContextClassLoader(this.getClass().getClassLoader()); thread.start(); return var1; } } private void performShutdown() { try { Thread.sleep(500L); } catch (InterruptedException var2) { Thread.currentThread().interrupt(); } this.context.close(); //这里才是核心 } } 在调用了 this.context.close() ,其实就是AbstractApplicationContext 的close() 方法 (重点是其中的doClose())/** * Close this application context, destroying all beans in its bean factory. * Delegates to {@code doClose()} for the actual closing procedure. * Also removes a JVM shutdown hook, if registered, as it's not needed anymore. * @see #doClose() * @see #registerShutdownHook() */ @Override public void close() { synchronized (this.startupShutdownMonitor) { doClose(); //重点:销毁bean 并执行jvm shutdown hook // If we registered a JVM shutdown hook, we don't need it anymore now: // We've already explicitly closed the context. if (this.shutdownHook != null) { try { Runtime.getRuntime().removeShutdownHook(this.shutdownHook); } catch (IllegalStateException ex) { // ignore - VM is already shutting down } } } } 后记到这里,关于单机版本的Spring Boot优雅停机就说完了。为什么说单机?因为大家也能发现,在关闭时,其实只是保证了服务端内部线程执行完毕,调用方的状态是没关注的。不论是Dubbo还是Cloud 的分布式服务框架,需要关注的是怎么能在服务停止前,先将提供者在注册中心进行反注册,然后在停止服务提供者,这样才能保证业务系统不会产生各种503、timeout等现象。好在当前Spring Boot 结合Kubernetes已经帮我们搞定了这一点,也就是Spring Boot 2.3版本新功能Liveness(存活状态) 和Readiness(就绪状态)简单的提下这两个状态: Liveness(存活状态):Liveness 状态来查看内部情况可以理解为health check,如果Liveness失败就就意味着应用处于故障状态并且目前无法恢复,这种情况就重启吧。此时Kubernetes如果存活探测失败将杀死Container。 Readiness(就绪状态):用来告诉应用是否已经准备好接受客户端请求,如果Readiness未就绪那么k8s就不能路由流量过来。
2020年10月26日
360 阅读
0 评论
0 点赞
2020-10-24
最简单入门深度学习
最简单入门深度学习该篇文档基于kaggle course,通过简单的理论介绍、程序代码、运行图以及动画等来帮助大家入门深度学习,既然是入门,所以没有太多模型推导以及高级技巧相关,都是深度学习中最基础的内容,希望大家看过之后可以自己动手基于Tensorflow或者Keras搭建一个处理回归或者分类问题的简单的神经网络模型,并通过dropout等手段优化模型结果;每部分都有对应的练习,练习都是很有针对性的,而且都很有趣,尤其是一些练习中都写好了动画的可视化展示,还是很有心的;目录: 概述 线性模型:单神经元 非线性模型:深度神经网络 模型训练:随机梯度下降 验证模型:过拟合和欠拟合 提升性能:Dropout和Batch Normalization 分类问题 概述经过本篇文章,你将搭建自己的深度神经网络,使用Keras和Tensorflow,创建全连接神经网络,在分类和回归问题上应用神经网络,通过随机梯度下降训练网络、通过dropout等技术提升模型性能;近些年在AI方面的主要发展都在深度学习,尤其是应用于自然语言处理、图像识别、游戏AI等领域,深度学习能得到更接近于人类的结果;深度学习是一种允许大量深度计算为特征的机器学习方法,深度计算使得深度学习模型可以理解真实世界数据中的复杂和高维的信息模式,比如这句话的含义是什么、这张图中的人在干嘛等等;通过这种优势和灵活性,神经网络成为深度学习的定义模型,神经网络由神经元组成,每个神经元单独看只是一个简单的计算单元,神经网络的能力来自于许多神经元之间的复杂的组合模式;单个神经元线性单元只有一个输入的线性单元对应公式如下:y = w*x+bx为输入,神经元连接的权重为w,w的更新就是神经网络学习的过程,b为偏差,它与输入没有关系,偏差允许神经元不依赖输入来修改输出,y是神经元的输出,即公式y=w*x+b的结果;线性单元作为模型的例子神经元通常作为神经网络的一部分,往往也会将一个单独的神经元模型作为基准模型,单神经元模型是线性模型;假设我们使用糖分作为输入训练模型,卡路里作为输出,假设偏差b为90,权重w为2.5,当糖分为5时,卡路里为2.5*5+90=102.5;多个输入当我们期望使用多个输入而不是一个时,其实就是将多个输入连接并神经元,计算每个连接权重,并全部加起来得到最终输出,如下:y = w_0x_0 + w_1x_1 + w_2*x_2 + b上述公式使用了三个输入,并分别对应各自的连接权重,从输入维度上看,单个输入拟合一条直线,两个输入你和一个平面,多个输入拟合的则是超平面;Keras中使用线性单元最简单的创建线性单元模型是通过keras.Sequential,可以通过dense层来创建上述提到的线性单元模型,对于一个有三个输入,一个输出的线性模型,Keras创建方式如下:from tensorflow import keras from tensorflow.keras import layers # Create a network with 1 linear unit model = keras.Sequential([ layers.Dense(units=1, input_shape=[3]) ]) 其中units为1表示该层只有一个输出,input_shape为[3]则表示有3个输入,之所以参数是个列表[],这是因为在图像领域可能需要三维输入,比如[高度,宽度,通道];线性单元练习可以通过这个notebook来进行这部分的练习,里面包含了如何通过keras搭建线性单元的神经元模型,并通过其weights属性来查看模型的连接权重和偏差,最后还有一个未训练的模型在预测中的表现,可以看到其随机权重在每次运行结果都不一样;深度神经网络层典型的神经网络通过层来组织他们的神经元,当我们把线性单元整理到一起时,我们就得到了一个dense层,神经网络通过叠加dense层来将输入以越来越复杂的方式进行转换,在一个训练好的神经网络模型,每一层都会将输入转换的更接近结果一点;激活函数激活函数作用于层的输出,最常用的是整流函数max(0,x),纠正函数将负部分处理为0,当我们将整流函数应用于一个线性单元时,也就得到了ReLU,而之前的线性公式:y=w*x+b也变成了:y = max(0, w*x+b)可以看到,函数也从线性转为了非线性,整流函数图像如下:堆叠dense层输出层之前通常有一些隐含层,一般我们不能直接看到他们的输出(因为他们的输出并不是最后输出,而是作为下一层的输入,因此无法直接看到),注意当处理回归问题时,最后一层也就是输出层是线性单元,也就是没有应用激活函数,当我们要处理分类或者其他问题时,仍然需要对应的激活函数;通过keras.Sequential创建多层神经网络方式很简单,只要从第一层到最后一层依次通过layer定义即可,第一层获取输入,最后一层产生输出,代码如下:from tensorflow.keras import layers model = keras.Sequential([ # the hidden ReLU layers layers.Dense(units=4, activation='relu', input_shape=[2]), layers.Dense(units=3, activation='relu'), # the linear output layer layers.Dense(units=1), ]) 其中各个layer表示各个堆叠的网络层,activation表示各个层的激活函数,可以看到最后一层是没有的,这是因为它处理的是回归问题,且最后一层输出只有一个,而其他层则不一定;深度神经网络练习你可以通过这个notebook来进行这部分练习,其中包含如何通过keras.Sequential搭建3个隐含层1个输出层的非线性神经网络模型,以及如何使用单独的激活层来代替activation参数,以及ReLU、eLU、SeLU、swish等各个激活函数的差异,实验证明ReLU适用于大多数场景,因此最适合作为初始激活函数选择,下面给出各个接获函数的图像:relu:elu:selu:swish:随机梯度下降在之前创建的神经网络模型中,网络中的权重都是随机指定的,此时的模型还没有学习到任何东西,这也是第一个练习中每次运行结果都不一样的原因;所谓训练一个神经网络,指的是通过某种方式不断更新网络中的权重,使得模型通过输入可以得到期望的输出,如果可以做到,那么也说明了这些权重在某种程度上表达了输入特征与输出之间的关系;训练模型需要两个必要元素: 损失函数:衡量模型预测结果好坏; 优化方法:指导模型如何去修改权重; 损失函数损失函数用于衡量模型的预测值与真实值之间的差异,不同的问题使用的损失函数一般也是不同的,例如对于回归问题,即我们要预测的是数值,一个常用的用于回归问题的损失函数为MAE,即平均绝对误差,对于每个预测值y_pred,MAE计算它与y_true的差值的绝对值,所有这些绝对值取平均就是MAE的结果,除了MAE,用于回归问题的还有很多损失函数,比如MSE、MASE、Huber loss等等,对于模型来说,在训练过程中,损失函数起到向导的作用,最小化损失函数就是模型要解决的问题,以此来指导网络中权重的更新方向;优化方法 - 随机梯度下降通过损失函数我们确定了模型要解决的问题,但是依然需要告知模型如何去解决这个问题,此时就需要一种优化方法,优化方法是一种最小化损失的算法;实际上所有应用于深度学习的优化算法都属于随机梯度下降族,它们都是迭代算法,一步一步的训练模型,每一步的训练过程如下: 抽样部分训练数据,通过模型运行得到预测结果y_pred; 测量这些y_pred与y_true之间的损失函数值; 通过损失更小的方向来修改权重; 上述过程一遍一遍的运行,直到损失为0或者损失无法再下降为止;迭代中从训练集中抽样的部分称之为minibatch,或者一般直接叫做batch,每一轮完整的训练称之为epoch,epoch的数量决定了模型使用各个数据点的次数;理想的训练过程中,权重不断更新,损失不断减少,预测值越来越接近于真实值;学习率和Batch Size学习率决定了模型在每一个batch上学习到的内容的大小,学习率越小意味着模型需要更多的batch来帮助其学习,学习率和batch size是两个训练过程中影响很大的参数,通常也是主要要调的超参数;可惜的是,对于很多情况下都没有必要通过非常耗时的超参数调整来获取最优的结果,Adam是一种不需要设置学习率的随机梯度下降算法,它不需要调试任何参数,或者说它是自调整的,因此它成为一种很好的通用优化方法;添加损失函数和优化方法在定义模型后,可以通过模型的compile方法添加损失函数和优化方法:model.compile( optimizer="adam", loss="mae", ) 例子 - 红酒品质数据格式如下,最后一列为预测目标列: fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality 10.8 0.470 0.43 2.10 0.171 27.0 66.0 0.99820 3.17 0.76 10.8 6 8.1 0.820 0.00 4.10 0.095 5.0 14.0 0.99854 3.36 0.53 9.6 5 9.1 0.290 0.33 2.05 0.063 13.0 27.0 0.99516 3.26 0.84 11.7 7 10.2 0.645 0.36 1.80 0.053 5.0 14.0 0.99820 3.17 0.42 10.0 6 可以看到,除了最后一列总有11列作为输入,神经网络搭建代码如下:from tensorflow import keras from tensorflow.keras import layers model = keras.Sequential([ layers.Dense(512, activation='relu', input_shape=[11]), layers.Dense(512, activation='relu'), layers.Dense(512, activation='relu'), layers.Dense(1), ]) 看到网络由3个隐含层和1个输出层组成,其中隐含层的units均为512,表示每个隐含层输出都有512个,第一层负责接受输入,最后一层输出结果;定义完了网络结构,下面需要设置训练需要使用的损失函数和优化方法:model.compile( optimizer='adam', loss='mae', ) 任务为回归预测,损失函数选择平均绝对误差,优化器使用adam;训练前的准备已经就绪,下面需要告诉模型训练使用的batch数量、迭代次数等信息:history = model.fit( X_train, y_train, validation_data=(X_valid, y_valid), batch_size=256, epochs=10, ) 对于训练过程中的loss进行可视化后可以更好的观察模型的整个迭代过程:import pandas as pd # convert the training history to a dataframe history_df = pd.DataFrame(history.history) # use Pandas native plot method history_df['loss'].plot(); 可以看到,在迭代次数达到6次时,后续的迭代中loss的下降不明显,甚至还有变大的情况出行,一般来说这说明迭代次数足够了;模型训练练习这部分练习可以通过这个notebook,其中包含了完整的神经网络模型,从定义到设置其损失和优化方法,再到最后的训练过程,并通过很有趣的动画方式展示了在不同的学习率、batch size、样本数量等情况下的模型迭代过程,对于理解各个参数的作用非常有帮助哦,这里展示其中一组参数下的训练过程:过拟合和欠拟合过拟合和欠拟合是机器学习中绕不开的两个问题,通常我们可以使用学习曲线来观察模型迭代表现并判断其当前属于过拟合还是欠拟合,通常来说过拟合指的是模型过于复杂,将数据中的噪声部分也拟合了,因此使得模型在真实数据上的表现明显差于在训练集的表现,而欠拟合则指的是模型在训练集上都没有达到足够好的效果,可能是因为模型太简单,也可能是因为数据量太大;容量容量指的是模型可以学习到的数据模式的复杂度大小,或者说容量越大的模型,越能深入的理解数据,对于神经网络来说,可以通过增加其宽度和高度来扩大其模型容量;所谓增大网络宽度指的是增加已有层中的神经元个数,而增大高度指的是增加新的层,一般来说使用同样的神经元个数,增加高度带来的容量增益要大于增加宽度,简单理解如下:假设当前网络有两层,每一层都有3个神经元,则其组合为3*3=9,此时我们要增加2个神经元: 如果是用于增加宽度,每层增加一个神经元变为4个,则有4*4=16; 如果是用于增加高度,增加一个单独的层,有2个神经元,则有3*3*2=18; 因此都是使用了两个神经元,从结果上看是高度的收益更大,当然这个只是一种直观理解,实际的解释要比这个复杂的多; 提前停止训练对于模型训练过程,尤其是基于真实数据的训练过程,很多时候是无法完全收敛的,而我们需要保证训练一定可以结束而不是无限运行下去的,因此可以通过Early Stopping来控制其迭代在满足某些条件下提前结束;增加Early Stoppingkeras通过callback的方式添加Early Stopping,所谓callback指的是在每次epoch后运行的内容,用于判断是否应该终止训练过程:from tensorflow.keras.callbacks import EarlyStopping early_stopping = EarlyStopping( min_delta=0.001, # minimium amount of change to count as an improvement patience=20, # how many epochs to wait before stopping restore_best_weights=True, ) 上述代码的含义是,如果连续20次迭代,每次的loss下降都不足0.001,那么训练终止,反正目前为止表现最好的权重数据;例子 - 使用Early Stopping训练模型还是之前的红酒例子,数据格式如下: fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality 10.8 0.470 0.43 2.10 0.171 27.0 66.0 0.99820 3.17 0.76 10.8 6 8.1 0.820 0.00 4.10 0.095 5.0 14.0 0.99854 3.36 0.53 9.6 5 9.1 0.290 0.33 2.05 0.063 13.0 27.0 0.99516 3.26 0.84 11.7 7 10.2 0.645 0.36 1.80 0.053 5.0 14.0 0.99820 3.17 0.42 10.0 6 模型定义、指定loss和优化器、指定Early Stopping代码如下:from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.callbacks import EarlyStopping early_stopping = EarlyStopping( min_delta=0.001, # minimium amount of change to count as an improvement patience=20, # how many epochs to wait before stopping restore_best_weights=True, ) model = keras.Sequential([ layers.Dense(512, activation='relu', input_shape=[11]), layers.Dense(512, activation='relu'), layers.Dense(512, activation='relu'), layers.Dense(1), ]) model.compile( optimizer='adam', loss='mae', ) history = model.fit( X_train, y_train, validation_data=(X_valid, y_valid), batch_size=256, epochs=500, callbacks=[early_stopping], verbose=0, # turn off training log ) history_df = pd.DataFrame(history.history) history_df.loc[:, ['loss', 'val_loss']].plot(); print("Minimum validation loss: {}".format(history_df['val_loss'].min())) 以上,通过fit方法的callbacks参数将Early Stopping作为一个callback添加到了迭代过程中,用于控制训练的提前结束,运行图如下:结合代码和上图可以看到,虽然我们设置了epoch为500,但是在迭代不到70次时就终止了,这就是Early Stopping在起作用,一定程度上可以避免不必要的训练过程,减少训练时间;过拟合和欠拟合的练习这部分练习可以通过这个notebook完成,这里有通过训练简单线性模型和复杂神经网络模型等,并通过学习曲线来观察模型的拟合情况,并通过添加Early Stopping来控制过拟合情况;Dropout和Batch Normalization实际的神经网络结构中往往包含更多的层,不仅仅是dense层,比如激活层、Dropout层等等,有些类似dense层,定义神经元的连接,而有些则是用于预处理和转换等;DropoutDropout层有助于纠正过拟合问题,在每次训练迭代中,随机的去掉网络层中的一部分输入单元,使得模型难以从训练数据学习到错误的模式,取而代之的是模型会搜索更普遍适用的模式,也就是具有更好的鲁棒性的模式,借此解决过拟合问题;可以把Dropout看作是一种集成方法,与随机森林类似,Dropout的随机抽取类似随机森林的行抽取和列抽取,二者的目的都是解决原始模型的过拟合问题,思路是一样的;增加Dropout在keras中,Drouput作为层使用,作用于其下的一层,通过参数rate指定随机取出的比例:keras.Sequential([ # ... layer.Dropout(rate=0.3), # apply 30% dropout to the next layer layer.Dense(16), # ... ]) Batch Normalization模型在迭代过程中,权重的更新主要由loss和optimater决定,假设我们的输入特征的量纲不一致,比如有的特征范围从0到1,有的特征是从-100到+100,那么在优化器计算过程中就会产生差异很大的结果,并使得训练过程很不稳定,体现就是学习曲线的波动严重;一个小栗子:比如我们要预测房价,目前有两个属性,一个是面积,范围是10到200,另一个是距离火车站距离,范围是100到100000,如果不进行量纲统一,可以遇见的是在计算过程中由于火车站距离值更大,因此会影响对结果的预测,或者说这个范围一定程度上参与了原来权重该起到的作用;Batch Normalization类似SKLearn里的StandardScaler和MinMaxScaler的作用,用于将输入特征的量纲统一,避免因为量纲不同导致对于预测结果影响的权重差异;增加Batch Normalization可以用在某一层之后:layers.Dense(16, activation='relu'), layers.BatchNormalization(), 也可以用在某一层和它的激活层之间:layers.Dense(16), layers.BatchNormalization(), layers.Activation('relu'), 例子 - 使用Dropout和Batch Normalization继续红酒例子,在每一个隐含层后都先加一个Dropout过滤一部分输入解决过拟合,再应用Batch Normalization优化不稳定情况:from tensorflow import keras from tensorflow.keras import layers model = keras.Sequential([ layers.Dense(1024, activation='relu', input_shape=[11]), layers.Dropout(0.3), layers.BatchNormalization(), layers.Dense(1024, activation='relu'), layers.Dropout(0.3), layers.BatchNormalization(), layers.Dense(1024, activation='relu'), layers.Dropout(0.3), layers.BatchNormalization(), layers.Dense(1), ]) 训练过程不使用Early Stopping:model.compile( optimizer='adam', loss='mae', ) history = model.fit( X_train, y_train, validation_data=(X_valid, y_valid), batch_size=256, epochs=100, verbose=0, ) # Show the learning curves history_df = pd.DataFrame(history.history) history_df.loc[:, ['loss', 'val_loss']].plot(); 学习曲线如下:可以看到,首先虽然没有Early Stopping,但是过拟合问题不明显,其次在迭代20次之后不稳定的情况基本消失了,说明Dropout和Batch Normalization都起到了各自的作用;Dropout和Batch Normalization练习这部分练习在这个notebook里,其中分别使用两个数据集,对比其上应用Dropout与不应用,应用Batch Normalization与不应用在学习曲线上的差异,可以很直观的看到二者起到的作用;下面是应用Batch Normalization后的学习曲线,要知道在不应用的情况下曲线都无法绘制出来:分类问题之前处理的都是回归问题,处理分类问题的区别只有以下两点: 损失函数:分类与回归在损失函数应用上不同,比如MAE和准确率; 输出层输出类型:也就是网络结构最后一层输出的内容,之前都是数值,如果是二分类问题,则应该是0/1; Sigmoid函数Sigmoid函数同样作为激活函数,它可以将实数输出映射到0到1之间,也就是通常的概率范围,而不管是准确率还是交叉熵等都可以利用概率来计算得到;Sigmoid函数图像如下,上一个使用它的地方是逻辑回归,同样是将线性回归的结果映射到0和1之间:例子 - 二分类数据格式如下: V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 ... V26 V27 V28 V29 V30 V31 V32 V33 V34 Class 1 0 0.99539 -0.05889 0.85243 0.02306 0.83398 -0.37708 1.00000 0.03760 ... -0.51171 0.41078 -0.46168 0.21266 -0.34090 0.42267 -0.54487 0.18641 -0.45300 good 1 0 1.00000 -0.18829 0.93035 -0.36156 -0.10868 -0.93597 1.00000 -0.04549 ... -0.26569 -0.20468 -0.18401 -0.19040 -0.11593 -0.16626 -0.06288 -0.13738 -0.02447 bad 1 0 1.00000 -0.03365 1.00000 0.00485 1.00000 -0.12062 0.88965 0.01198 ... -0.40220 0.58984 -0.22145 0.43100 -0.17365 0.60436 -0.24180 0.56045 -0.38238 good 1 0 1.00000 -0.45161 1.00000 1.00000 0.71216 -1.00000 0.00000 0.00000 ... 0.90695 0.51613 1.00000 1.00000 -0.20099 0.25682 1.00000 -0.32382 1.00000 bad 1 0 1.00000 -0.02401 0.94140 0.06531 0.92106 -0.23255 0.77152 -0.16399 ... -0.65158 0.13290 -0.53206 0.02431 -0.62197 -0.05707 -0.59573 -0.04608 -0.65697 good 像之前处理回归问题一样定义模型,区别在于最后一层的激活函数选择sigmoid用于输出概率:from tensorflow import keras from tensorflow.keras import layers model = keras.Sequential([ layers.Dense(4, activation='relu', input_shape=[33]), layers.Dense(4, activation='relu'), layers.Dense(1, activation='sigmoid'), ]) 添加交叉熵和准确率到模型中,继续使用adam,他在分类问题上表现依然很好:model.compile( optimizer='adam', loss='binary_crossentropy', metrics=['binary_accuracy'], ) 使用Early Stopping控制训练过程:early_stopping = keras.callbacks.EarlyStopping( patience=10, min_delta=0.001, restore_best_weights=True, ) history = model.fit( X_train, y_train, validation_data=(X_valid, y_valid), batch_size=512, epochs=1000, callbacks=[early_stopping], verbose=0, # hide the output because we have so many epochs ) 分别观察其交叉熵和准确率的变化情况:history_df = pd.DataFrame(history.history) # Start the plot at epoch 5 history_df.loc[5:, ['loss', 'val_loss']].plot() history_df.loc[5:, ['binary_accuracy', 'val_binary_accuracy']].plot() print(("Best Validation Loss: {:0.4f}" +\ "\nBest Validation Accuracy: {:0.4f}")\ .format(history_df['val_loss'].min(), history_df['val_binary_accuracy'].max())) 交叉熵:准确率:分类练习这部分练习在这个notebook,很完整的一个分类模型搭建过程,从基于结构图创建神经网络结构到添加loss和优化器,使用Early Stopping等都有,包括对于结果是否过拟合和欠拟合的讨论等,可以通过这个notebook再次练习下整个深度学习流程,麻雀虽小,五脏俱全;交叉熵:准确率:最后对于深度学习还有很多很多可以学习的内容,本篇文章以最简单的方式对其中各个基础模块进行介绍,并结合代码和运行结果图等进行说明,希望看完能够在脑海中形成对于深度学习的一个感性认识;最后的最后欢迎大佬们关注我的公众号:尼莫的AI小站,新开的公众号,后续会不定期更新有关机器学习、深度学习、数据处理分析、游戏的内容;
2020年10月24日
382 阅读
0 评论
0 点赞
2020-10-24
如何实现自己的任务调度系统?--开源软件诞生18
任务调度与ERP难舍难分--第18篇用日志记录“开源软件”的诞生【点亮星标】----祈盼着一个鼓励博主开源地址:码云:https://gitee.com/redragon/redragon-erpGitHub:https://github.com/redragon1985/redragon-erp 什么是任务调度?任务调度这个词貌似很高大上,其实不难理解。我们知道一个应用它执行任务的方式默认是实时且同步的,而所谓的任务调度就是让任务非实时的或异步的进行。换一个词也可以把它理解成计划任务或定时任务。它解决了什么问题要分析任务调度所解决的问题,先要看这个任务的特点以及需要如何执行。首先这类任务一般是系统根据一定的预设逻辑去代替人工完成的一件事。其次这类事无需实时完成或需要在某个特定时间完成,任务启动后只需在后台静默执行,执行完成后有条件的通知用户或用户自主查询执行结果即可。由此可见,以上需求是传统的应用无法做到的,所以就需要任务调度系统去实现它。为什么单独研发调度系统?从Java技术角度有很多定时任务的实现方式,比如Timer、ScheduledExecutor、Quartz,无论是哪种方式都可以简单的在一个项目中去集成定时任务的功能,而无需单独开发项目。那为什么我们在进行系统设计时,要提出任务调度系统这一思路呢?那就需要先来分析下信息化系统的特点,首先信息化系统从功能设计的角度是分散的,即一个项目或模块一般只完成一件事的情况下,我们从需要设计很多很多的应用才能满足用户需求。就拿ERP举例,ERP包含计划预算、订单管理、库存管理、财务管理等一系列的模块或系统,而这些系统都是与业务直接关联的,所以哪个系统也离不开计划任务的功能,如果按照上述方式,我们必不可少的要将每个项目中都整合一套完整的计划任务组件才能满足我们的需求。这种做法不仅开发时更加麻烦、同时也带来了维护或扩展复杂度的指数提升,这种设计的风险可想而知。所以独立的任务调度系统就应运而生。设计思路探讨那么我们如何设计这个系统呢?我们来重点关注一下几点。1、统一灵活的任务配置(1)任务实现可配置:任务的具体逻辑可以通过方法或存储过程去实现,然后通过配置方式根据不同参数去自定义不同的任务需求。(2)定时执行配置:定义好任务的逻辑后我们就需要配置执行逻辑,什么时间执行,是固定时间还是循环,何时开始何时结束,单次执行还是多次执行都需要详细的配置。(3)线程配置:任务需要同步执行还是异步执行,是守护线程还是非守护线程。异常处理机制等都需要分别配置好。(4)任务流程配置:有的时候我们不仅仅需要执行一个任务,可能需要执行一系列的任务,而任务的执行又是有先后之分的,这时候我们可以将一个一个配置好的任务串联成一个流程去执行。2、便捷的任务启动和反馈(1)启动任务:所有的任务配置好以后我们就需要启动任务,通过API的方式启动是最优的选择。(2)反馈机制:对于任务的执行效果要对用户有一个反馈机制,给用户通知、直接回调或反写都是可以选择的方式。3、监控任务执行情况和日志追踪(1)任务监控:任务的执行过程不是一帆风顺的,可能成功,也可能出现执行异常、意外终止、人工终止等多种情况,所以需要根据任务的执行情况显示执行状态,并根据需要,让用户可重启或继续任务。(2)日志追踪:如果出现重新执行仍然无法成功的情况,那么多数一定是出现了程序异常,所以日志的记录就必不可少,可让维护人员根据日志处理问题并解决问题。抛砖引玉的分布式当一个任务调度系统研发完成后,随着使用频率的增加,一定会遇到瓶颈,所以我们就会考虑到部署多个任务调度系统形成分布式,但部署多个系统,就会涉及到注册中心、负载、路由、数据同步等各种问题。本文只是在讨论如何设计任务调度系统的功能,并不涉及分布式调度的思路。在此不展开谈,只是抛砖引玉,有兴趣的朋友可以深入探索。后记如果您对我们正在做的开源软件感兴趣,欢迎各种形式的合作,作为贡献者或直接加入我们!让我们一起打造一套开源的信息化解决方案。 【码云】或【GitHub】搜索“赤龙ERP”点击星标,亦可加入我们! 让我们从小开始做点伟大的事!与开发者交流 kzca2000
2020年10月24日
311 阅读
0 评论
0 点赞
2020-10-23
从面试角度学完 Kafka
Kafka 是一个优秀的分布式消息中间件,许多系统中都会使用到 Kafka 来做消息通信。对分布式消息系统的了解和使用几乎成为一个后台开发人员必备的技能。今天码哥字节就从常见的 Kafka 面试题入手,和大家聊聊 Kafka 的那些事儿。讲一讲分布式消息中间件问题 什么是分布式消息中间件? 消息中间件的作用是什么? 消息中间件的使用场景是什么? 消息中间件选型? 分布式消息是一种通信机制,和 RPC、HTTP、RMI 等不一样,消息中间件采用分布式中间代理的方式进行通信。如图所示,采用了消息中间件之后,上游业务系统发送消息,先存储在消息中间件,然后由消息中间件将消息分发到对应的业务模块应用(分布式生产者 - 消费者模式)。这种异步的方式,减少了服务之间的耦合程度。定义消息中间件: 利用高效可靠的消息传递机制进行平台无关的数据交流 基于数据通信,来进行分布式系统的集成 通过提供消息传递和消息排队模型,可以在分布式环境下扩展进程间的通信 在系统架构中引用额外的组件,必然提高系统的架构复杂度和运维的难度,那么在系统中使用分布式消息中间件有什么优势呢?消息中间件在系统中起的作用又是什么呢? 解耦 冗余(存储) 扩展性 削峰 可恢复性 顺序保证 缓冲 异步通信 面试时,面试官经常会关心面试者对开源组件的选型能力,这既可以考验面试者知识的广度,也可以考验面试者对某类系统的知识的认识深度,而且也可以看出面试者对系统整体把握和系统架构设计的能力。开源分布式消息系统有很多,不同的消息系统的特性也不一样,选择怎样的消息系统,不仅需要对各消息系统有一定的了解,也需要对自身系统需求有清晰的认识。下面是常见的几种分布式消息系统的对比:答案关键字 什么是分布式消息中间件?通信,队列,分布式,生产消费者模式。 消息中间件的作用是什么? 解耦、峰值处理、异步通信、缓冲。 消息中间件的使用场景是什么? 异步通信,消息存储处理。 消息中间件选型?语言,协议、HA、数据可靠性、性能、事务、生态、简易、推拉模式。 Kafka 基本概念和架构问题 简单讲下 Kafka 的架构? Kafka 是推模式还是拉模式,推拉的区别是什么? Kafka 如何广播消息? Kafka 的消息是否是有序的? Kafka 是否支持读写分离? Kafka 如何保证数据高可用? Kafka 中 zookeeper 的作用? 是否支持事务? 分区数是否可以减少? Kafka 架构中的一般概念: Producer:生产者,也就是发送消息的一方。生产者负责创建消息,然后将其发送到 Kafka。 Consumer:消费者,也就是接受消息的一方。消费者连接到 Kafka 上并接收消息,进而进行相应的业务逻辑处理。 Consumer Group:一个消费者组可以包含一个或多个消费者。使用多分区 + 多消费者方式可以极大提高数据下游的处理速度,同一消费组中的消费者不会重复消费消息,同样的,不同消费组中的消费者消息消息时互不影响。Kafka 就是通过消费组的方式来实现消息 P2P 模式和广播模式。 Broker:服务代理节点。Broker 是 Kafka 的服务节点,即 Kafka 的服务器。 Topic:Kafka 中的消息以 Topic 为单位进行划分,生产者将消息发送到特定的 Topic,而消费者负责订阅 Topic 的消息并进行消费。 Partition:Topic 是一个逻辑的概念,它可以细分为多个分区,每个分区只属于单个主题。同一个主题下不同分区包含的消息是不同的,分区在存储层面可以看作一个可追加的日志(Log)文件,消息在被追加到分区日志文件的时候都会分配一个特定的偏移量(offset)。 Offset:offset 是消息在分区中的唯一标识,Kafka 通过它来保证消息在分区内的顺序性,不过 offset 并不跨越分区,也就是说,Kafka 保证的是分区有序性而不是主题有序性。 Replication:副本,是 Kafka 保证数据高可用的方式,Kafka 同一 Partition 的数据可以在多 Broker 上存在多个副本,通常只有主副本对外提供读写服务,当主副本所在 broker 崩溃或发生网络一场,Kafka 会在 Controller 的管理下会重新选择新的 Leader 副本对外提供读写服务。 Record: 实际写入 Kafka 中并可以被读取的消息记录。每个 record 包含了 key、value 和 timestamp。 Kafka Topic Partitions LayoutKafka 将 Topic 进行分区,分区可以并发读写。Kafka Consumer Offsetzookeeper Broker 注册:Broker 是分布式部署并且之间相互独立,Zookeeper 用来管理注册到集群的所有 Broker 节点。 Topic 注册: 在 Kafka 中,同一个 Topic 的消息会被分成多个分区并将其分布在多个 Broker 上,这些分区信息及与 Broker 的对应关系也都是由 Zookeeper 在维护 生产者负载均衡:由于同一个 Topic 消息会被分区并将其分布在多个 Broker 上,因此,生产者需要将消息合理地发送到这些分布式的 Broker 上。 消费者负载均衡:与生产者类似,Kafka 中的消费者同样需要进行负载均衡来实现多个消费者合理地从对应的 Broker 服务器上接收消息,每个消费者分组包含若干消费者,每条消息都只会发送给分组中的一个消费者,不同的消费者分组消费自己特定的 Topic 下面的消息,互不干扰。 答案关键字 简单讲下 Kafka 的架构? Producer、Consumer、Consumer Group、Topic、Partition Kafka 是推模式还是拉模式,推拉的区别是什么? Kafka Producer 向 Broker 发送消息使用 Push 模式,Consumer 消费采用的 Pull 模式。拉取模式,让 consumer 自己管理 offset,可以提供读取性能 Kafka 如何广播消息? Consumer group Kafka 的消息是否是有序的? Topic 级别无序,Partition 有序 Kafka 是否支持读写分离? 不支持,只有 Leader 对外提供读写服务 Kafka 如何保证数据高可用? 副本,ack,HW Kafka 中 zookeeper 的作用? 集群管理,元数据管理 是否支持事务? 0.11 后支持事务,可以实现”exactly once“ 分区数是否可以减少? 不可以,会丢失数据 Kafka 使用问题 Kafka 有哪些命令行工具?你用过哪些? Kafka Producer 的执行过程? Kafka Producer 有哪些常见配置? 如何让 Kafka 的消息有序? Producer 如何保证数据发送不丢失? 如何提升 Producer 的性能? 如果同一 group 下 consumer 的数量大于 part 的数量,kafka 如何处理? Kafka Consumer 是否是线程安全的? 讲一下你使用 Kafka Consumer 消费消息时的线程模型,为何如此设计? Kafka Consumer 的常见配置? Consumer 什么时候会被踢出集群? 当有 Consumer 加入或退出时,Kafka 会作何反应? 什么是 Rebalance,何时会发生 Rebalance? 命令行工具Kafka 的命令行工具在 Kafka 包的/bin目录下,主要包括服务和集群管理脚本,配置脚本,信息查看脚本,Topic 脚本,客户端脚本等。 kafka-configs.sh: 配置管理脚本 kafka-console-consumer.sh: kafka 消费者控制台 kafka-console-producer.sh: kafka 生产者控制台 kafka-consumer-groups.sh: kafka 消费者组相关信息 kafka-delete-records.sh: 删除低水位的日志文件 kafka-log-dirs.sh:kafka 消息日志目录信息 kafka-mirror-maker.sh: 不同数据中心 kafka 集群复制工具 kafka-preferred-replica-election.sh: 触发 preferred replica 选举 kafka-producer-perf-test.sh:kafka 生产者性能测试脚本 kafka-reassign-partitions.sh: 分区重分配脚本 kafka-replica-verification.sh: 复制进度验证脚本 kafka-server-start.sh: 启动 kafka 服务 kafka-server-stop.sh: 停止 kafka 服务 kafka-topics.sh:topic 管理脚本 kafka-verifiable-consumer.sh: 可检验的 kafka 消费者 kafka-verifiable-producer.sh: 可检验的 kafka 生产者 zookeeper-server-start.sh: 启动 zk 服务 zookeeper-server-stop.sh: 停止 zk 服务 zookeeper-shell.sh:zk 客户端 我们通常可以使用kafka-console-consumer.sh和kafka-console-producer.sh脚本来测试 Kafka 生产和消费,kafka-consumer-groups.sh可以查看和管理集群中的 Topic,kafka-topics.sh通常用于查看 Kafka 的消费组情况。Kafka ProducerKafka producer 的正常生产逻辑包含以下几个步骤: 配置生产者客户端参数常见生产者实例。 构建待发送的消息。 发送消息。 关闭生产者实例。 Producer 发送消息的过程如下图所示,需要经过拦截器,序列化器和分区器,最终由累加器批量发送至 Broker。Kafka Producer 需要以下必要参数: bootstrap.server: 指定 Kafka 的 Broker 的地址 key.serializer: key 序列化器 value.serializer: value 序列化器 常见参数: batch.num.messages 默认值:200,每次批量消息的数量,只对 asyc 起作用。 request.required.acks 默认值:0,0 表示 producer 毋须等待 leader 的确认,1 代表需要 leader 确认写入它的本地 log 并立即确认,-1 代表所有的备份都完成后确认。 只对 async 模式起作用,这个参数的调整是数据不丢失和发送效率的 tradeoff,如果对数据丢失不敏感而在乎效率的场景可以考虑设置为 0,这样可以大大提高 producer 发送数据的效率。 request.timeout.ms 默认值:10000,确认超时时间。 partitioner.class 默认值:kafka.producer.DefaultPartitioner,必须实现 kafka.producer.Partitioner,根据 Key 提供一个分区策略。有时候我们需要相同类型的消息必须顺序处理,这样我们就必须自定义分配策略,从而将相同类型的数据分配到同一个分区中。 producer.type 默认值:sync,指定消息发送是同步还是异步。异步 asyc 成批发送用 kafka.producer.AyncProducer, 同步 sync 用 kafka.producer.SyncProducer。同步和异步发送也会影响消息生产的效率。 compression.topic 默认值:none,消息压缩,默认不压缩。其余压缩方式还有,"gzip"、"snappy"和"lz4"。对消息的压缩可以极大地减少网络传输量、降低网络 IO,从而提高整体性能。 compressed.topics 默认值:null,在设置了压缩的情况下,可以指定特定的 topic 压缩,未指定则全部压缩。 message.send.max.retries 默认值:3,消息发送最大尝试次数。 retry.backoff.ms 默认值:300,每次尝试增加的额外的间隔时间。 topic.metadata.refresh.interval.ms 默认值:600000,定期的获取元数据的时间。当分区丢失,leader 不可用时 producer 也会主动获取元数据,如果为 0,则每次发送完消息就获取元数据,不推荐。如果为负值,则只有在失败的情况下获取元数据。 queue.buffering.max.ms 默认值:5000,在 producer queue 的缓存的数据最大时间,仅仅 for asyc。 queue.buffering.max.message 默认值:10000,producer 缓存的消息的最大数量,仅仅 for asyc。 queue.enqueue.timeout.ms 默认值:-1,0 当 queue 满时丢掉,负值是 queue 满时 block, 正值是 queue 满时 block 相应的时间,仅仅 for asyc。 Kafka ConsumerKafka 有消费组的概念,每个消费者只能消费所分配到的分区的消息,每一个分区只能被一个消费组中的一个消费者所消费,所以同一个消费组中消费者的数量如果超过了分区的数量,将会出现有些消费者分配不到消费的分区。消费组与消费者关系如下图所示:Kafka Consumer Client 消费消息通常包含以下步骤: 配置客户端,创建消费者 订阅主题 拉去消息并消费 提交消费位移 关闭消费者实例 因为 Kafka 的 Consumer 客户端是线程不安全的,为了保证线程安全,并提升消费性能,可以在 Consumer 端采用类似 Reactor 的线程模型来消费数据。Kafka consumer 参数 bootstrap.servers: 连接 broker 地址,host:port 格式。 group.id: 消费者隶属的消费组。 key.deserializer: 与生产者的key.serializer对应,key 的反序列化方式。 value.deserializer: 与生产者的value.serializer对应,value 的反序列化方式。 session.timeout.ms: coordinator 检测失败的时间。默认 10s 该参数是 Consumer Group 主动检测 (组内成员 comsummer) 崩溃的时间间隔,类似于心跳过期时间。 auto.offset.reset: 该属性指定了消费者在读取一个没有偏移量后者偏移量无效(消费者长时间失效当前的偏移量已经过时并且被删除了)的分区的情况下,应该作何处理,默认值是 latest,也就是从最新记录读取数据(消费者启动之后生成的记录),另一个值是 earliest,意思是在偏移量无效的情况下,消费者从起始位置开始读取数据。 enable.auto.commit: 否自动提交位移,如果为false,则需要在程序中手动提交位移。对于精确到一次的语义,最好手动提交位移 fetch.max.bytes: 单次拉取数据的最大字节数量 max.poll.records: 单次 poll 调用返回的最大消息数,如果处理逻辑很轻量,可以适当提高该值。 但是max.poll.records条数据需要在在 session.timeout.ms 这个时间内处理完 。默认值为 500 request.timeout.ms: 一次请求响应的最长等待时间。如果在超时时间内未得到响应,kafka 要么重发这条消息,要么超过重试次数的情况下直接置为失败。 Kafka Rebalancerebalance 本质上是一种协议,规定了一个 consumer group 下的所有 consumer 如何达成一致来分配订阅 topic 的每个分区。比如某个 group 下有 20 个 consumer,它订阅了一个具有 100 个分区的 topic。正常情况下,Kafka 平均会为每个 consumer 分配 5 个分区。这个分配的过程就叫 rebalance。什么时候 rebalance?这也是经常被提及的一个问题。rebalance 的触发条件有三种: 组成员发生变更(新 consumer 加入组、已有 consumer 主动离开组或已有 consumer 崩溃了——这两者的区别后面会谈到) 订阅主题数发生变更 订阅主题的分区数发生变更 如何进行组内分区分配?Kafka 默认提供了两种分配策略:Range 和 Round-Robin。当然 Kafka 采用了可插拔式的分配策略,你可以创建自己的分配器以实现不同的分配策略。答案关键字 Kafka 有哪些命令行工具?你用过哪些?/bin目录,管理 kafka 集群、管理 topic、生产和消费 kafka Kafka Producer 的执行过程?拦截器,序列化器,分区器和累加器 Kafka Producer 有哪些常见配置?broker 配置,ack 配置,网络和发送参数,压缩参数,ack 参数 如何让 Kafka 的消息有序?Kafka 在 Topic 级别本身是无序的,只有 partition 上才有序,所以为了保证处理顺序,可以自定义分区器,将需顺序处理的数据发送到同一个 partition Producer 如何保证数据发送不丢失?ack 机制,重试机制 如何提升 Producer 的性能?批量,异步,压缩 如果同一 group 下 consumer 的数量大于 part 的数量,kafka 如何处理?多余的 Part 将处于无用状态,不消费数据 Kafka Consumer 是否是线程安全的?不安全,单线程消费,多线程处理 讲一下你使用 Kafka Consumer 消费消息时的线程模型,为何如此设计?拉取和处理分离 Kafka Consumer 的常见配置?broker, 网络和拉取参数,心跳参数 Consumer 什么时候会被踢出集群?奔溃,网络异常,处理时间过长提交位移超时 当有 Consumer 加入或退出时,Kafka 会作何反应?进行 Rebalance 什么是 Rebalance,何时会发生 Rebalance?topic 变化,consumer 变化 高可用和性能问题 Kafka 如何保证高可用? Kafka 的交付语义? Replic 的作用? 什么事 AR,ISR? Leader 和 Flower 是什么? Kafka 中的 HW、LEO、LSO、LW 等分别代表什么? Kafka 为保证优越的性能做了哪些处理? 分区与副本在分布式数据系统中,通常使用分区来提高系统的处理能力,通过副本来保证数据的高可用性。多分区意味着并发处理的能力,这多个副本中,只有一个是 leader,而其他的都是 follower 副本。仅有 leader 副本可以对外提供服务。 多个 follower 副本通常存放在和 leader 副本不同的 broker 中。通过这样的机制实现了高可用,当某台机器挂掉后,其他 follower 副本也能迅速”转正“,开始对外提供服务。为什么 follower 副本不提供读服务?这个问题本质上是对性能和一致性的取舍。试想一下,如果 follower 副本也对外提供服务那会怎么样呢?首先,性能是肯定会有所提升的。但同时,会出现一系列问题。类似数据库事务中的幻读,脏读。 比如你现在写入一条数据到 kafka 主题 a,消费者 b 从主题 a 消费数据,却发现消费不到,因为消费者 b 去读取的那个分区副本中,最新消息还没写入。而这个时候,另一个消费者 c 却可以消费到最新那条数据,因为它消费了 leader 副本。Kafka 通过 WH 和 Offset 的管理来决定 Consumer 可以消费哪些数据,已经当前写入的数据。只有 Leader 可以对外提供读服务,那如何选举 Leaderkafka 会将与 leader 副本保持同步的副本放到 ISR 副本集合中。当然,leader 副本是一直存在于 ISR 副本集合中的,在某些特殊情况下,ISR 副本中甚至只有 leader 一个副本。 当 leader 挂掉时,kakfa 通过 zookeeper 感知到这一情况,在 ISR 副本中选取新的副本成为 leader,对外提供服务。 但这样还有一个问题,前面提到过,有可能 ISR 副本集合中,只有 leader,当 leader 副本挂掉后,ISR 集合就为空,这时候怎么办呢?这时候如果设置 unclean.leader.election.enable 参数为 true,那么 kafka 会在非同步,也就是不在 ISR 副本集合中的副本中,选取出副本成为 leader。副本的存在就会出现副本同步问题Kafka 在所有分配的副本 (AR) 中维护一个可用的副本列表 (ISR),Producer 向 Broker 发送消息时会根据ack配置来确定需要等待几个副本已经同步了消息才相应成功,Broker 内部会ReplicaManager服务来管理 flower 与 leader 之间的数据同步。性能优化 partition 并发 顺序读写磁盘 page cache:按页读写 预读:Kafka 会将将要消费的消息提前读入内存 高性能序列化(二进制) 内存映射 无锁 offset 管理:提高并发能力 Java NIO 模型 批量:批量读写 压缩:消息压缩,存储压缩,减小网络和 IO 开销 Partition 并发一方面,由于不同 Partition 可位于不同机器,因此可以充分利用集群优势,实现机器间的并行处理。另一方面,由于 Partition 在物理上对应一个文件夹,即使多个 Partition 位于同一个节点,也可通过配置让同一节点上的不同 Partition 置于不同的 disk drive 上,从而实现磁盘间的并行处理,充分发挥多磁盘的优势。顺序读写Kafka 每一个 partition 目录下的文件被平均切割成大小相等(默认一个文件是 500 兆,可以手动去设置)的数据文件,每一个数据文件都被称为一个段(segment file), 每个 segment 都采用 append 的方式追加数据。答案关键字 Kafka 如何保证高可用? 通过副本来保证数据的高可用,producer ack、重试、自动 Leader 选举,Consumer 自平衡 Kafka 的交付语义? 交付语义一般有at least once、at most once和exactly once。kafka 通过 ack 的配置来实现前两种。 Replic 的作用? 实现数据的高可用 什么是 AR,ISR? AR:Assigned Replicas。AR 是主题被创建后,分区创建时被分配的副本集合,副本个 数由副本因子决定。ISR:In-Sync Replicas。Kafka 中特别重要的概念,指代的是 AR 中那些与 Leader 保 持同步的副本集合。在 AR 中的副本可能不在 ISR 中,但 Leader 副本天然就包含在 ISR 中。关于 ISR,还有一个常见的面试题目是如何判断副本是否应该属于 ISR。目前的判断 依据是:Follower 副本的 LEO 落后 Leader LEO 的时间,是否超过了 Broker 端参数 replica.lag.time.max.ms 值。如果超过了,副本就会被从 ISR 中移除。 Leader 和 Flower 是什么? Kafka 中的 HW 代表什么? 高水位值 (High watermark)。这是控制消费者可读取消息范围的重要字段。一 个普通消费者只能“看到”Leader 副本上介于 Log Start Offset 和 HW(不含)之间的 所有消息。水位以上的消息是对消费者不可见的。 Kafka 为保证优越的性能做了哪些处理? partition 并发、顺序读写磁盘、page cache 压缩、高性能序列化(二进制)、内存映射 无锁 offset 管理、Java NIO 模型 本文并没有深入 Kafka 的实现细节和源码分析,但 Kafka 确实是一个 优秀的开源系统,很多优雅的架构设计和源码设计都值得我们学习,十分建议感兴趣的同学更加深入的去了解一下这个开源系统,对于自身架构设计能力,编码能力,性能优化都会有很大的帮助。推荐阅读以下几篇文章阅读量与读者反馈都很好,推荐大家阅读: 数据库系统设计概述 不可不知的软件架构模式 Tomcat 架构原理解析到架构设计借鉴 Tomcat 高并发之道原理拆解与性能调优
2020年10月23日
285 阅读
0 评论
0 点赞
2020-10-23
手写一个HTTP框架:两个类实现基本的IoC功能
jsoncat: 仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架国庆节的时候,我就已经把 jsoncat 的 IoC 功能给写了,具体可以看这篇文章《手写“SpringBoot”近况:IoC模块已经完成》 。今天这篇文章就来简单分享一下自己写 IoC 的思路与具体的代码实现。IoC (Inverse of Control:控制反转) 和 AOP(Aspect-Oriented Programming:面向切面编程) 可以说是 Spring 框架提供的最核心的两个功能。但凡是了解过 Spring 的小伙伴,那肯定对这个两个概念非常非常了解。不了解的小伙伴,可以查看《面试被问了几百遍的 IoC 和 AOP ,还在傻傻搞不清楚?》这篇通俗易懂的文章。考虑到这篇文章要手写 Spring 框架的 IoC 功能,所以,我这里还是简单介绍一下 IoC 。如果你不太清楚 IoC 这个概念,一定要搞懂之后再看后面具体的代码实现环节。IoC 介绍IoC(Inverse of Control:控制反转)是一种设计思想,也就是 将原本在程序中手动创建对象的控制权交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器IoC 容器是用来实现 IoC 的载体,被管理的对象就被存放在IoC容器中。IoC 容器在 Spring 中实际上就是个Map(key,value),Map 中存放了各种被管理的对象。IoC 解决了什么问题将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。IoC 和 DI 别再傻傻分不清楚IoC(Inverse of Control:控制反转)是一种设计思想 或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种被管理的对象。IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。并且,老马(Martin Fowler)在一篇文章中提到将 IoC 改名为 DI,原文如下,原文地址:https://martinfowler.com/articles/injection.html 。IoC实现思路注意 :以下思路未涉及解决循环依赖的问题!开始代码实现之前,我们先简单聊聊实现 IoC 的思路,搞清楚了思路之后,实现起来就非常简单了。 扫描指定包下的特定注解比如@Component标记的类,并将这些类保存起来。 遍历所有被特定注解比如@Component标记的类,然后将这些类通过反射实例化并通过一个 Map 保存起来,Map 的 key 为类名,value为类对象。 再一次遍历所有被特定注解比如@Component标记的类,并获取类中所有的字段,如果类被 @Autowired 注解标记的话,就进行第 4 步。 通过字段名 key,从bean容器中获取对应的对象 value。 判断获取到的对象是否为接口。如果是接口的话,需要获取接口对应的实现类,然后再将指定的实现类的实例化对象通过反射赋值给指定对象。如果不是接口的话,就直接将获取到的对象通过反射赋值给指定对象。 IoC 实现核心代码核心注解@Autowired :注解对象@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { } @Component :声明对象被IoC容器管理 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Component { String name() default ""; } @Qualifier: 指定注入的bean(当接口有多个实现类的时候需要使用)@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Qualifier { String value() default ""; } 工具类简单封装一个反射工具类。工具类包含3个后面会用到的方法: scanAnnotatedClass() :扫描指定包下的被指定注解标记的类(使用Reflections这个反射框架一行代码即可解决扫描获取指定注解的类)。 newInstance() : 传入 Class 即可返回 Class 对应的对象。 setField() :为对象的指定字段赋值。 @Slf4j public class ReflectionUtil { /** * scan the classes marked by the specified annotation in the specified package * * @param packageName specified package name * @param annotation specified annotation * @return the classes marked by the specified annotation in the specified package */ public static Set> annotatedClass = reflections.getTypesAnnotatedWith(annotation, true); log.info("The number of class Annotated with @RestController :[{}]", annotatedClass.size()); return annotatedClass; } /** * create object instance through class * * @param cls target class * @return object created by the target class */ public static Object newInstance(Class cls) { Object instance = null; try { instance = cls.newInstance(); } catch (InstantiationException | IllegalAccessException e) { log.error("new instance failed", e); } return instance; } /** * set the value of a field in the object * * @param obj target object * @param field target field * @param value the value assigned to the field */ public static void setField(Object obj, Field field, Object value) { field.setAccessible(true); try { field.set(obj, value); } catch (IllegalAccessException e) { log.error("set field failed", e); e.printStackTrace(); } } } 根据实现思路写代码注意 :以下代码未涉及解决循环依赖的问题!以下是 IoC 实现的核心代码,完整代码地址:https://github.com/Snailclimb/jsoncat 。1.扫描指定包下的特定注解比如@Component标记的类,并将这些类保存起来。扫描指定注解@RestController和@Component并保存起来:public class ClassFactory { public static final Map>> CLASSES = new ConcurrentHashMap(); //1.扫描指定包下的特定注解比如`@Component`标记的类,并将这些类保存起来 public static void loadClass(String packageName) { Set> componentSets = ReflectionUtil.scanAnnotatedClass(packageName, Component.class); CLASSES.put(RestController.class, restControllerSets); CLASSES.put(Component.class, componentSets); } } 2.遍历所有被特定注解比如@Component标记的类,然后将这些类通过反射实例化并通过一个 Map 保存起来,Map 的 key 为类名,value为类对象。public final class BeanFactory { public static final Map BEANS = new ConcurrentHashMap(128); public static void loadBeans() { // 2.遍历所有被特定注解比如 @Component 标记的类,然后将这些类通过反射实例化并通过一个 Map 保存起来,Map 的 key 为类名,value为类对象 ClassFactory.CLASSES.forEach((annotation, classes) -> { if (annotation == Component.class) { //将bean实例化, 并放入bean容器中 for (Class aClass : classes) { Component component = aClass.getAnnotation(Component.class); String beanName = "".equals(component.name()) ? aClass.getName() : component.name(); Object obj = ReflectionUtil.newInstance(aClass); BEANS.put(beanName, obj); } } if (annotation == RestController.class) { for (Class aClass : classes) { Object obj = ReflectionUtil.newInstance(aClass); BEANS.put(aClass.getName(), obj); } } }); } } 3.再一次遍历所有被特定注解比如@Component标记的类,并获取类中所有的字段,如果类被 @Autowired 注解标记的话,就进行第 4 步。public class DependencyInjection { public static void dependencyInjection(String packageName) { Map beans = BeanFactory.BEANS; if (beans.size() == 0) return; //3.再一次遍历所有被特定注解比如 @Component 标记的类,并获取类中所有的字段,如果类被 `@Autowired` 注解标记的话,就进行第 4 步。 // 3.1.遍历bean容器中的所有对象 beans.values().forEach(bean -> { // 3.2.获取对象所属的类声明的所有字段/属性 Field[] beanFields = bean.getClass().getDeclaredFields(); if (beanFields.length == 0) return; //3.3.遍历对象所属的类声明的所有字段/属性 for (Field beanField : beanFields) { //3.4.判断字段是否被 @Autowired 注解标记 if (beanField.isAnnotationPresent(Autowired.class)) { //4.通过字段名 key,从bean容器中获取对应的对象 value。 //4.1.字段对应的类型 Class beanFieldClass = beanField.getType(); //4.2.字段对应的类名 String beanName = beanFieldClass.getName(); if (beanFieldClass.isAnnotationPresent(Component.class)) { Component component = beanFieldClass.getAnnotation(Component.class); beanName = "".equals(component.name()) ? beanFieldClass.getName() : component.name(); } //4.3.从bean容器中获取对应的对象 Object beanFieldInstance = beans.get(beanName); //5.判断获取到的对象是否为接口。如果是接口的话,需要获取接口对应的实现类,然后再将指定的实现类的实例化对象通过反射赋值给指定对象。如果不是接口的话,就直接将获取到的对象通过反射赋值给指定对象。 if (beanFieldClass.isInterface()) { //如果是接口,获取接口对应的实现类 Set aClass = subClasses.iterator().next(); beanFieldInstance = ReflectionUtil.newInstance(aClass); } //实现类多与一个的话,根据 Qualifier 注解的值获取 if (subClasses.size() > 1) { Class aClass = subClasses.iterator().next(); Qualifier qualifier = beanField.getDeclaredAnnotation(Qualifier.class); beanName = qualifier == null ? aClass.getName() : qualifier.value(); beanFieldInstance = beans.get(beanName); } } // 如果最后获取到的字段对象为null,就抛出异常 if (beanFieldInstance == null) { throw new CanNotDetermineTargetBeanException("can not determine target bean"); } //通过反射设置指定对象中的指定字段的值 ReflectionUtil.setField(bean, beanField, beanFieldInstance); } } }); } /** * 获取接口对应的实现类 */ @SuppressWarnings("unchecked") public static Set
2020年10月23日
322 阅读
0 评论
0 点赞
2020-10-22
Kubernetes K8S之存储ConfigMap详解
K8S之存储ConfigMap概述与说明,并详解常用ConfigMap示例 主机配置规划 服务器名称(hostname) 系统版本 配置 内网IP 外网IP(模拟) k8s-master CentOS7.7 2C/4G/20G 172.16.1.110 10.0.0.110 k8s-node01 CentOS7.7 2C/4G/20G 172.16.1.111 10.0.0.111 k8s-node02 CentOS7.7 2C/4G/20G 172.16.1.112 10.0.0.112 ConfigMap概述ConfigMap 是一种 API 对象,用来将非机密性的数据保存到健值对中。使用时可以用作环境变量、命令行参数或者存储卷中的配置文件。ConfigMap 将环境配置信息和容器镜像解耦,便于应用配置的修改。当你需要储存机密信息时可以使用 Secret 对象。备注:ConfigMap 并不提供保密或者加密功能。如果你想存储的数据是机密的,请使用 Secret;或者使用其他第三方工具来保证数据的私密性,而不是用 ConfigMap。 ConfigMap创建方式通过目录创建配置文件目录1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# ll /root/k8s_practice/storage/configmap # 配置文件存在哪个目录下4 total 8 5 -rw-r--r-- 1 root root 159 Jun 7 14:52game.properties6 -rw-r--r-- 1 root root 83 Jun 7 14:53ui.properties7 [root@k8s-master storage]#8 [root@k8s-master storage]# cat configmap/game.properties # 涉及文件19 enemies=aliens10 lives=3 11 enemies.cheat=true 12 enemies.cheat.level=noGoodRotten13 secret.code.passphrase=UUDDLRLRBABAs14 secret.code.allowed=true 15 secret.code.lives=30 16 17 [root@k8s-master storage]#18 [root@k8s-master storage]# cat configmap/ui.properties # 涉及文件219 color.good=purple20 color.bad=yellow21 allow.textmode=true 22 how.nice.to.look=fairlyNice 创建ConfigMap并查看状态1 [root@k8s-master storage]# kubectl create configmap game-config --from-file=/root/k8s_practice/storage/configmap2 configmap/game-config created3 [root@k8s-master storage]#4 [root@k8s-master storage]# kubectl get configmap5 NAME DATA AGE6 game-config 2 14s 查看ConfigMap有哪些数据1 [root@k8s-master storage]# kubectl get configmap -o yaml ##### 查看方式12 apiVersion: v13 items:4 -apiVersion: v15 data:6 game.properties: |+ ##### 本段最后有一行空格,+表示保留字符串行末尾的换行7 enemies=aliens8 lives=3 9 enemies.cheat=true 10 enemies.cheat.level=noGoodRotten11 secret.code.passphrase=UUDDLRLRBABAs12 secret.code.allowed=true 13 secret.code.lives=30 14 15 ui.properties: | 16 color.good=purple17 color.bad=yellow18 allow.textmode=true 19 how.nice.to.look=fairlyNice20 kind: ConfigMap21 metadata:22 creationTimestamp: "2020-06-07T06:57:28Z" 23 name: game-config24 namespace: default25 resourceVersion: "889177" 26 selfLink: /api/v1/namespaces/default/configmaps/game-config27 uid: 6952ac85-ded0-4c5e-89fd-b0c6f0546ecf28 kind: List29 metadata:30 resourceVersion: "" 31 selfLink: "" 32 [root@k8s-master storage]#33 [root@k8s-master storage]# kubectl describe configmap game-config ##### 查看方式234 Name: game-config35 Namespace: default36 Labels: 37 Annotations: 38 39 Data40 ==== 41 game.properties:42 ---- 43 enemies=aliens44 lives=3 45 enemies.cheat=true 46 enemies.cheat.level=noGoodRotten47 secret.code.passphrase=UUDDLRLRBABAs48 secret.code.allowed=true 49 secret.code.lives=30 50 51 52 ui.properties:53 ---- 54 color.good=purple55 color.bad=yellow56 allow.textmode=true 57 how.nice.to.look=fairlyNice58 59 Events: 通过文件创建配置文件位置1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# cat /root/k8s_practice/storage/configmap/game.properties4 enemies=aliens5 lives=3 6 enemies.cheat=true 7 enemies.cheat.level=noGoodRotten8 secret.code.passphrase=UUDDLRLRBABAs9 secret.code.allowed=true 10 secret.code.lives=30 创建ConfigMap并查看状态1 [root@k8s-master storage]# kubectl create configmap game-config-2 --from-file=/root/k8s_practice/storage/configmap/game.properties2 configmap/game-config-2created3 [root@k8s-master storage]#4 [root@k8s-master storage]# kubectl get configmap game-config-2 5 NAME DATA AGE6 game-config-2 1 29s 查看ConfigMap有哪些数据1 [root@k8s-master storage]# kubectl get configmap game-config-2 -o yaml ##### 查看方式12 apiVersion: v13 data:4 game.properties: |+ ##### 本段最后有一行空格,+表示保留字符串行末尾的换行5 enemies=aliens6 lives=3 7 enemies.cheat=true 8 enemies.cheat.level=noGoodRotten9 secret.code.passphrase=UUDDLRLRBABAs10 secret.code.allowed=true 11 secret.code.lives=30 12 13 kind: ConfigMap14 metadata:15 creationTimestamp: "2020-06-07T07:05:47Z" 16 name: game-config-2 17 namespace: default18 resourceVersion: "890437" 19 selfLink: /api/v1/namespaces/default/configmaps/game-config-2 20 uid: 02d99802-c23f-45ad-b4e1-dea9bcb166d821 [root@k8s-master storage]#22 [root@k8s-master storage]# kubectl describe configmap game-config-2##### 查看方式223 Name: game-config-2 24 Namespace: default25 Labels: 26 Annotations: 27 28 Data29 ==== 30 game.properties:31 ---- 32 enemies=aliens33 lives=3 34 enemies.cheat=true 35 enemies.cheat.level=noGoodRotten36 secret.code.passphrase=UUDDLRLRBABAs37 secret.code.allowed=true 38 secret.code.lives=30 39 40 41 Events: 通过命令行创建创建ConfigMap并查看状态1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# kubectl create configmap special-config --from-literal=special.how=very --from-literal="special.type=charm" 4 configmap/special-config created5 [root@k8s-master storage]#6 [root@k8s-master storage]# kubectl get configmap special-config7 NAME DATA AGE8 special-config 2 23s 查看ConfigMap有哪些数据1 [root@k8s-master storage]# kubectl get configmap special-config -o yaml ##### 查看方式12 apiVersion: v13 data:4 special.how: very5 special.type: charm6 kind: ConfigMap7 metadata:8 creationTimestamp: "2020-06-07T09:32:04Z" 9 name: special-config10 namespace: default11 resourceVersion: "912702" 12 selfLink: /api/v1/namespaces/default/configmaps/special-config13 uid: 76698e78-1380-4826-b5ac-d9c81f746eac14 [root@k8s-master storage]#15 [root@k8s-master storage]# kubectl describe configmap special-config ##### 查看方式216 Name: special-config17 Namespace: default18 Labels: 19 Annotations: 20 21 Data22 ==== 23 special.how:24 ---- 25 very26 special.type:27 ---- 28 charm29 Events: 通过yaml文件创建yaml文件1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# catconfigmap.yaml4 apiVersion: v15 kind: ConfigMap6 metadata:7 name: configmap-demo8 data:9 # 类属性键;每一个键都映射到一个简单的值10 player_initial_lives: "3" 11 ui_properties_file_name: 'user-interface.properties' 12 #13 # 类文件键14 game.properties: | 15 enemy.types=aliens,monsters16 player.maximum-lives=5 17 user-interface.properties: | 18 color.good=purple19 color.bad=yellow20 allow.textmode=true 创建ConfigMap并查看状态1 [root@k8s-master storage]# kubectl apply -f configmap.yaml2 configmap/configmap-demo created3 [root@k8s-master storage]# kubectl get configmap configmap-demo4 NAME DATA AGE5 configmap-demo 4 2m59s 查看ConfigMap有哪些数据1 [root@k8s-master storage]# kubectl get configmap configmap-demo -o yaml ##### 查看方式12 apiVersion: v13 data:4 game.properties: | 5 enemy.types=aliens,monsters6 player.maximum-lives=5 7 player_initial_lives: "3" 8 ui_properties_file_name: user-interface.properties9 user-interface.properties: | 10 color.good=purple11 color.bad=yellow12 allow.textmode=true 13 kind: ConfigMap14 metadata:15 annotations:16 kubectl.kubernetes.io/last-applied-configuration: | 17 {"apiVersion":"v1","data":{"game.properties":"enemy.types=aliens,monsters\nplayer.maximum-lives=5\n","player_initial_lives":"3","ui_properties_file_name":"user-interface.properties","user-interface.properties":"color.good=purple\ncolor.bad=yellow\nallow.textmode=true\n"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"configmap-demo","namespace":"default"}}18 creationTimestamp: "2020-06-07T11:36:46Z" 19 name: configmap-demo20 namespace: default21 resourceVersion: "931685" 22 selfLink: /api/v1/namespaces/default/configmaps/configmap-demo23 uid: fdad7000-87bd-4b72-be98-40dd8fe6400a24 [root@k8s-master storage]#25 [root@k8s-master storage]#26 [root@k8s-master storage]# kubectl describe configmap configmap-demo ##### 查看方式227 Name: configmap-demo28 Namespace: default29 Labels: 30 Annotations: kubectl.kubernetes.io/last-applied-configuration:31 {"apiVersion":"v1","data":{"game.properties":"enemy.types=aliens,monsters\nplayer.maximum-lives=5\n","player_initial_lives":"3","ui_proper... 32 33 Data34 ==== 35 game.properties:36 ---- 37 enemy.types=aliens,monsters38 player.maximum-lives=5 39 40 player_initial_lives:41 ---- 42 3 43 ui_properties_file_name:44 ---- 45 user-interface.properties46 user-interface.properties:47 ---- 48 color.good=purple49 color.bad=yellow50 allow.textmode=true 51 52 Events: Pod中使用ConfigMap如何在Pod中使用上述的ConfigMap信息。当前存在的ConfigMap1 [root@k8s-master storage]# kubectl get configmap2 NAME DATA AGE3 configmap-demo 430m4 game-config 25h9m5 game-config-2 15h1m6 special-config 2 5m48s 使用ConfigMap来替代环境变量yaml文件1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# catpod_configmap_env.yaml4 apiVersion: v15 kind: Pod6 metadata:7 name: pod-configmap-env 8 spec:9 containers:10 -name: myapp11 image: registry.cn-beijing.aliyuncs.com/google_registry/myapp:v112 command: ["/bin/sh", "-c", "env"]13 ### 引用方式114 env:15 -name: SPECAIL_HOW_KEY16 valueFrom:17 configMapKeyRef:18 name: special-config ### 这个name的值来自 ConfigMap19 key: special.how ### 这个key的值为需要取值的键20 -name: SPECAIL_TPYE_KEY21 valueFrom:22 configMapKeyRef:23 name: special-config24 key: special.type25 ### 引用方式226 envFrom:27 -configMapRef:28 name: game-config-2### 这个name的值来自 ConfigMap29 restartPolicy: Never 启动pod并查看状态1 [root@k8s-master storage]# kubectl apply -f pod_configmap_env.yaml2 pod/pod-configmap-envcreated3 [root@k8s-master storage]#4 [root@k8s-master storage]# kubectl get pod -o wide5 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES6 pod-configmap-env 0/1 Completed 0 6s 10.244.2.147 k8s-node02 查看打印日志1 [root@k8s-master storage]# kubectl logs pod-configmap-env 2 MYAPP_SVC_PORT_80_TCP_ADDR=10.98.57.156 3 KUBERNETES_SERVICE_PORT=443 4 KUBERNETES_PORT=tcp://10.96.0.1:443 5 MYAPP_SVC_PORT_80_TCP_PORT=80 6 HOSTNAME=pod-configmap-env 7 SHLVL=1 8 MYAPP_SVC_PORT_80_TCP_PROTO=tcp9 HOME=/root10 SPECAIL_HOW_KEY=very ### 来自ConfigMap11 game.properties=enemies=aliens ### 来自ConfigMap12 lives=3### 来自ConfigMap13 enemies.cheat=true### 来自ConfigMap14 enemies.cheat.level=noGoodRotten ### 来自ConfigMap15 secret.code.passphrase=UUDDLRLRBABAs ### 来自ConfigMap16 secret.code.allowed=true### 来自ConfigMap17 secret.code.lives=30### 来自ConfigMap18 19 20 SPECAIL_TPYE_KEY=charm ### 来自ConfigMap21 MYAPP_SVC_PORT_80_TCP=tcp://10.98.57.156:80 22 NGINX_VERSION=1.12.2 23 KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1 24 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin25 KUBERNETES_PORT_443_TCP_PORT=443 26 KUBERNETES_PORT_443_TCP_PROTO=tcp27 MYAPP_SVC_SERVICE_HOST=10.98.57.156 28 KUBERNETES_SERVICE_PORT_HTTPS=443 29 KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443 30 PWD=/ 31 KUBERNETES_SERVICE_HOST=10.96.0.1 32 MYAPP_SVC_SERVICE_PORT=80 33 MYAPP_SVC_PORT=tcp://10.98.57.156:80 使用ConfigMap设置命令行参数yaml文件1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# catpod_configmap_cmd.yaml4 apiVersion: v15 kind: Pod6 metadata:7 name: pod-configmap-cmd8 spec:9 containers:10 -name: myapp11 image: registry.cn-beijing.aliyuncs.com/google_registry/myapp:v112 command: ["/bin/sh", "-c", "echo \"===$(SPECAIL_HOW_KEY)===$(SPECAIL_TPYE_KEY)===\""]13 env:14 -name: SPECAIL_HOW_KEY15 valueFrom:16 configMapKeyRef:17 name: special-config18 key: special.how19 -name: SPECAIL_TPYE_KEY20 valueFrom:21 configMapKeyRef:22 name: special-config23 key: special.type24 restartPolicy: Never 启动pod并查看状态1 [root@k8s-master storage]# kubectl apply -f pod_configmap_cmd.yaml2 pod/pod-configmap-cmd created3 [root@k8s-master storage]#4 [root@k8s-master storage]# kubectl get pod -o wide5 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES6 pod-configmap-cmd 0/1 Completed 0 5s 10.244.4.125 k8s-node01 查看打印日志1 [root@k8s-master storage]# kubectl logs pod-configmap-cmd2 ===very===charm=== 通过数据卷插件使用ConfigMap【推荐】在数据卷里面使用ConfigMap,最基本的就是将文件填入数据卷,在这个文件中,键就是文件名【第一层级的键】,键值就是文件内容。yaml文件1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# catpod_configmap_volume.yaml4 apiVersion: v15 kind: Pod6 metadata:7 name: pod-configmap-volume8 spec:9 containers:10 -name: myapp11 image: registry.cn-beijing.aliyuncs.com/google_registry/myapp:v112 #command: ["/bin/sh", "-c", "ls -l /etc/config/"]13 command: ["/bin/sh", "-c", "sleep 600"]14 volumeMounts:15 - name: config-volume16 mountPath: /etc/config17 volumes:18 - name: config-volume19 configMap:20 name: configmap-demo21 restartPolicy: Never 启动pod并查看状态1 [root@k8s-master storage]# kubectl apply -f pod_configmap_volume.yaml2 pod/pod-configmap-volume created3 [root@k8s-master storage]#4 [root@k8s-master storage]# kubectl get pod -o wide5 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES6 pod-configmap-volume 1/1 Running 0 5s 10.244.2.153 k8s-node02 进入pod并查看1 [root@k8s-master storage]# kubectl exec -it pod-configmap-volume sh 2 / # ls /etc/config3 game.properties player_initial_lives ui_properties_file_name user-interface.properties4 /#5 /#6 /#7 / # cat /etc/config/player_initial_lives8 3/#9 /#10 /#11 / # cat /etc/config/ui_properties_file_name12 user-interface.properties/#13 /#14 /#15 / # cat /etc/config/game.properties16 enemy.types=aliens,monsters17 player.maximum-lives=5 18 /#19 /#20 / # cat /etc/config/user-interface.properties21 color.good=purple22 color.bad=yellow23 allow.textmode=true ConfigMap热更新准备工作yaml文件1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# catpod_configmap_hot.yaml4 apiVersion: v15 kind: ConfigMap6 metadata:7 name: log-config8 namespace: default9 data:10 log_level: INFO11 --- 12 apiVersion: apps/v113 kind: Deployment14 metadata:15 name: myapp-deploy16 namespace: default17 spec:18 replicas: 2 19 selector:20 matchLabels:21 app: myapp22 release: v123 template:24 metadata:25 labels:26 app: myapp27 release: v128 env: test29 spec:30 containers:31 -name: myapp32 image: registry.cn-beijing.aliyuncs.com/google_registry/myapp:v133 imagePullPolicy: IfNotPresent34 ports:35 - containerPort: 80 36 volumeMounts:37 - name: config-volume38 mountPath: /etc/config39 volumes:40 - name: config-volume41 configMap:42 name: log-config 应用yaml文件并查看状态1 [root@k8s-master storage]# kubectl apply -f pod_configmap_hot.yaml2 configmap/log-config created3 deployment.apps/myapp-deploy created4 [root@k8s-master storage]#5 [root@k8s-master storage]# kubectl get configmap log-config6 NAME DATA AGE7 log-config 121s8 [root@k8s-master storage]#9 [root@k8s-master storage]# kubectl get pod -o wide10 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES11 myapp-deploy-58ff9c997-drhwk 1/1 Running 0 30s 10.244.2.154 k8s-node02 12 myapp-deploy-58ff9c997-n68j2 1/1 Running 0 30s 10.244.4.126 k8s-node01 查看ConfigMap信息1 [root@k8s-master storage]# kubectl get configmap log-config -o yaml2 apiVersion: v13 data:4 log_level: INFO5 kind: ConfigMap6 metadata:7 annotations:8 kubectl.kubernetes.io/last-applied-configuration: | 9 {"apiVersion":"v1","data":{"log_level":"INFO"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"log-config","namespace":"default"}}10 creationTimestamp: "2020-06-07T16:08:11Z" 11 name: log-config12 namespace: default13 resourceVersion: "971348" 14 selfLink: /api/v1/namespaces/default/configmaps/log-config15 uid: 7e78e1d7-12de-4601-9915-cefbc96ca305 查看pod中的ConfigMap信息1 [root@k8s-master storage]# kubectl exec -it myapp-deploy-58ff9c997-drhwk -- cat /etc/config/log_level2 INFO 热更新修改ConfigMap1 [root@k8s-master storage]# kubectl edit configmap log-config ### 将 INFO 改为了 DEBUG2 # Please edit the object below. Lines beginning with a '#'will be ignored,3 # and an empty file will abort the edit. If an error occurs while saving this filewill be4 # reopened with the relevant failures.5 #6 apiVersion: v17 data:8 log_level: DEBUG9 kind: ConfigMap10 metadata:11 annotations:12 kubectl.kubernetes.io/last-applied-configuration: | 13 {"apiVersion":"v1","data":{"log_level":"DEBUG"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"log-config","namespace":"default"}}14 creationTimestamp: "2020-06-07T16:08:11Z" 15 name: log-config16 namespace: default17 resourceVersion: "971348" 18 selfLink: /api/v1/namespaces/default/configmaps/log-config19 uid: 7e78e1d7-12de-4601-9915-cefbc96ca305 查看ConfigMap信息1 [root@k8s-master storage]# kubectl get configmap log-config -o yaml2 apiVersion: v13 data:4 log_level: DEBUG5 kind: ConfigMap6 metadata:7 annotations:8 kubectl.kubernetes.io/last-applied-configuration: | 9 {"apiVersion":"v1","data":{"log_level":"DEBUG"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"log-config","namespace":"default"}}10 creationTimestamp: "2020-06-07T16:08:11Z" 11 name: log-config12 namespace: default13 resourceVersion: "972893" 14 selfLink: /api/v1/namespaces/default/configmaps/log-config15 uid: 7e78e1d7-12de-4601-9915-cefbc96ca305 稍后10秒左右,再次查看pod中的ConfigMap信息1 [root@k8s-master storage]# kubectl exec -it myapp-deploy-58ff9c997-drhwk -- cat /etc/config/log_level2 DEBUG由此可见,完成了一次热更新 相关阅读1、YAML 语言教程与使用案例2、Kubernetes K8S之通过yaml创建pod与pod文件常用字段详解3、Kubernetes K8S之存储Secret详解 ———END———如果觉得不错就关注下呗 (-^O^-) !
2020年10月22日
316 阅读
0 评论
0 点赞
2020-10-22
C# 范型约束 new() 你必须要知道的事
C# 范型约束 new() 你必须要知道的事注意:本文不会讲范型如何使用,关于范型的概念和范型约束的使用请移步谷歌。本文要讲的是关于范型约束无参构造函数 new 的一些底层细节和注意事项。写这篇文章的原因也是因为看到 github 上,以及其他地方看到的代码都是那么写的,而我一查相关的资料,发现鲜有人提到这方面的细节,所以才有了此文。这里我先直接抛出一段代码,请大家看下这段代码有什么问题?或者说能说出什么问题?public static T CreateInstance() where T: new() => new T(); 先不要想这种写法的合理性(实际上很多人都会诸如此类的这么写,无非就是中间多了一些业务处理,最后还是会 return new T())。先想一下,然后在看下面的分析。假设这样的问题出现在面试上,其实能有很多要考的点。首先是范型约束的底层细节如果说我们不知道范型底下到底做了什么操作,我们也不用急,我们可以用 ILSpy 来看查看一下,代码片段如下:.method public hidebysig static !!T CreateInstance () cil managed { // Method begins at RVA 0x2053 // Code size 6 (0x6) .maxstack 8 IL_0000: call !!0 [System.Private.CoreLib]System.Activator::CreateInstance() IL_0005: ret } // end of method C::CreateInstance 没有 ILSpy 的同学可以移步这里在线查看在 IL_0000 就能明显看出范型约束 new() 的底层实现是通过反射来实现的。至于 System.Activator.CreateInstance 方法实现我在这里就不提了。只知道这里用的是它就足够了。不知道大家看到这里有没有觉得一丝惊讶,我当时是有被惊到的,因为我的第一想法就是觉得这么简单肯定是直接调用无参 .ctor,居然是用到的反射。毕竟编译器拥有在编译器就能识别具体的范型类了。现在可以马后炮的讲:正因为是编译器只有在编译期才确定具体范型类型,所以编译器无法事先知道要直接调用哪些无参构造函数类,所以才用到了反射。如果本文仅仅只是这样,那我肯定没有勇气写下这片文章的。因为其实已经有人早在 04 年园子里就提到了这一点。但是我查到的资料也就止步于此。试想一下 ,如果你的框架中有些方法用到了无参构造函数范型约束,并且处于调用的热路径上,其实这样性能是大打折扣的,因为反射 Activator.CreateInstance 性能肯定是远远不如直接调用无参构造函数的。那么有没有什么方法能够在使用范型约束这个特征的同时,又不会让编译器去用反射呢?答案肯定是有的,这点我想喜欢动手实验肯定早就知道了。其实我们可以用到委托来初始化类。范型约束 return new T() 的优化——委托如果大家对这点都知道的话,可以略过本节(在这里鼓励大家可以写出来造福大家呀,对于这点那些不知道的人(我)要花很长时间才弄清楚 -_-)。让我们把上面的例子改成如下方式:public static Func InstanceFactory => () => new Bar(); 对于委托的底层相信大家还是都知道的,底层是通过生成一个类 C,在这个类中直接实例化类 Bar。下面我只贴出关键的代码片段.method public hidebysig specialname static class [System.Private.CoreLib]System.Func`1 get_InstanceFactory () cil managed { // Method begins at RVA 0x205a // Code size 32 (0x20) .maxstack 8 IL_0000: ldsfld class [System.Private.CoreLib]System.Func`1 C/'c'::'9__3_0' IL_0005: dup IL_0006: brtrue.s IL_001f IL_0008: pop IL_0009: ldsfld class C/'c' C/'c'::'9' IL_000e: ldftn instance class Bar C/'c'::'b__3_0'() IL_0014: newobj instance void class [System.Private.CoreLib]System.Func`1::.ctor(object, native int) IL_0019: dup IL_001a: stsfld class [System.Private.CoreLib]System.Func`1 C/'c'::'9__3_0' IL_001f: ret } // end of method C::get_InstanceFactory .method assembly hidebysig instance class Bar 'b__3_0' () cil managed { // Method begins at RVA 0x2090 // Code size 6 (0x6) .maxstack 8 IL_0000: newobj instance void Bar::.ctor() IL_0005: ret } // end of method 'c'::'b__3_0' 同样我们可以通过 ILSpy 或者 在线查看示例 查看委托生成的代码。这里可以明显看出是不存在反射调用的,IL_000e 处直接调用编译器生成的类 C 的方法 b__3_0 ,在这个方法中就会直接调用类 Bar 的构造函数。所以性能上绝对要比上种写法要高得多。看到这里可能大家又有新问题了,众所周知,委托要在初始化时就要确定表达式。所以与此处的范型动态调用是冲突的。的确没错,委托必须要在初始化表达式时就要确定类型。但是我们现在已经知道了委托是能够避免让编译器不用反射的,剩下的只是解决动态表达式的问题,毫无疑问表达式树该登场了。范型约束 return new T() 的优化——表达式树对于这部分已经知道的同学可以跳过本节。把委托改造成表达式树那是非常简单的,我们可以不假思索的写出下面代码:private static readonly Expression ctorExpression = () => new T(); public static T CreateInstance() where T : new() { var func = ctorExpression.Compile(); return func(); } 到这里其实就有点”旧酒装新瓶“的意思了。不过有点要注意的是,如果单纯只是表达式树的优化,从执行效率上来看肯定是不如委托来的快,毕竟表达式树多了一层构造表达式然后编译成委托的过程。优化也是有的,再继续往下讲就有点“偏题”了。因为往后其实就是对委托,对表达式树的性能优化问题。跟范型约束倒没关系了总结其实如果面试真的有问到这个问题的话,其实考的就是对范型约束 new() 底层的一个熟悉程度,然后转而从反射的点来思考问题的优化方案。因为这可以散发出很多问题,比如性能优化,从直接返回 new T() 到委托,因为委托无法做到动态变化,所以想到了表达式树。那么我们继而也能举一反三的知道,如果要继续优化的话,在构造表达式树时,我们可以用缓存来节省每次调用方法的构造表达式树的时间(DI 的 CallSite 实现细节就是如此)。如果我们生思熟虑之后还要选择继续优化,那么我们还可以从表达式树转到动态生成代码这一领域,通过编写 IL 代码来生成表达式树,进而缓存下来达到近乎直接调用的性能。这也是为什么我花了很长时间弄清楚这个的原因。最后关于代码代码地址在:https://github.com/MarsonShine/Books/tree/master/WHPerformanceDotNet/src/GenericOptimization注意:我上传这一版是下方第一个文章给出的例子的整理之后的版本。文中有很多代码我都没贴出来,一是觉得意义不大,重要的是思考过程和实践过程,还占文章篇幅。二是还是想让不知道这些的同学能自己动手编码自己的版本,最后才看与那些大牛写的版本的差距在哪,这样才会更有收获。参考资料 https://devblogs.microsoft.com/premier-developer/dissecting-the-new-constraint-in-c-a-perfect-example-of-a-leaky-abstraction/ https://alexandrnikitin.github.io/blog/dotnet-generics-under-the-hood/ https://www.microsoft.com/en-us/research/wp-content/uploads/2001/01/designandimplementationofgenerics.pdf 《编写高性能.NET代码》
2020年10月22日
282 阅读
0 评论
0 点赞
2020-10-21
蒲公英 · JELLY技术周刊 Vol.25 · Webpack 5 正式发布,你学废了么
蒲公英 · JELLY技术周刊 Vol.25阔别两年,Webpack 5 正式发布了,不仅清理掉很多冗余的功能,同样也为我们带来了很多新鲜的能力,不论是默认开启的持久缓存,还是反病毒保护,亦或者被其作者之一称为 JS 架构变革者的Module Federation。虽然不知道你有没有心动呢,但现在却正是时候上车 Webpack 5 体验一把装备升级。登高远眺天高地迥,觉宇宙之无穷基础技术看完这篇文章,我奶奶都懂了https的原理文章由浅入深展示了对称加密、非对称加密、数字证书、CA机构、数字签名等在 https 协议过程中所扮演的角色,语言通俗易懂,虽然拉着奶奶一起看,奶奶表示看不懂,但还是值得推荐!工程化阔别两年,webpack 5 正式发布自从 2018 年 2 月,Webpack 4 发布以来,Webpack 就暂时没有更进一步的重大更新,为了保持 API 的一致性,旧的架构没有做太多改变,遗留了很多的包袱。阔别 2 年多后,2020 年 10 月 10 日,Webpack 5 正式发布,并带来了诸多重大的变更,将会使前端工程师的构建效率与质量大为提升。Webpack 5 的 Release Note 非常长,本文尝试摘出最简练的信息。图形编程AR 核心概念及技术解析这是谷歌推出的 ARCore 开发入门课程系列中的第二节课,介绍了 AR 的六大理念,以及 ARCore 实现这六大理念的思路与技术解决方案。文章开头提到的上节课《解密 AR 增强现实》的内容也同样值得一读。RTF: 用 React 来写 three.js如何优雅地使用 React 写 three.js,本篇文章提供了入门教程。文中罗列了选择 R3F(react-three-fiber)的理由:组件化场景的开发模式可读性极高且便于复用,内置了很多相当有用的辅助函数,暴露了足够多的 hooks 以便开发者灵活扩展,提前预处理画布尺寸,不限制 three.js 的使用版本,对二次渲染的算法做了优化。人工智能AI 聊天机器人——京东·数据科学实验室A Survey on Dialogue Systems: Recent Advances and New Frontiers.(智能对话系统调查:前沿与进展)虽然也是2018年的,全文引用了124篇论文,是一篇综合全面的介绍对话系统的文章,现在看来仍具有参考意义。论文解读:基于动态词表的对话生成研究虽然是18年的文章,但通过本文主要可以了解聊天机器人的分类以及实现思路,至于本文的核心内容,用动态词典替换静态词典的实现思路以及优化词预测损失和回复生成损失方法,则适合更深入研究的读者。工具推介历经十年岁月磨砺,这个网页视频播放器中的王牌终于开源了!ckplayer 是一款用于网页上播放视频的软件,它有着高度自定义化的特点;而且作为一个老牌的网页视频播放器,使用起来只需几个简单的文件即可插入视频。而在最近,该播放器作者在更新了 X2 版本后,又将 ckplayer 正式开源给各位开发者,本文将带大家简单介绍该播放器的特点以及使用和安装方式。沧海拾遗沧海拾遗,积跬步以至千里Nginx 配置 HTTPS 服务器了解了那些关于 HTTPS 的基础,在实际的项目中该如何应用呢?本文讲解了如何使用 Nginx 来配置 HTTPS 服务器,还不快用起来,给自己的小网站按上象征安全认证的「绿锁」。Webpack原理浅析Webpack 5 正式发布了,当我们抽丝剥茧的去学习其中的内容时,还是需要先对 Webpack 有更为深刻的了解,这篇 Webpack 原理浅析,就是很好的教程,文章以实现一个简单的打包工具为路线图,逐一解析了 Webpack 中的各项能力,如果将它放在收藏夹吃灰就太可惜了。「蒲公英」期刊,每周更新,我们专注于挖掘「基础技术、工程化、跨端框架技术、图形编程、服务端开发、桌面开发、人工智能、设计哲学、前端框架」等多个大方向的业界热点,并加以专业的解读;不仅如此,我们还会推介精选凹凸技术文章,向大家呈现团队内的研究技术方向。抬头仰望,蒲公英的种子会生根发芽,如夏花绚烂;格物致知,我们登高远眺、沧海拾遗,以求积硅步而至千里。蒲公英 · JELLY技术周刊贡献指南欢迎关注凹凸实验室博客:aotu.io或者关注凹凸实验室公众号(AOTULabs),不定时推送文章:
2020年10月21日
300 阅读
0 评论
0 点赞
2020-10-21
如何提升前端基建的效能价值?
写在前面上一篇如何衡量工具平台的效能价值?推导出了一种度量模型,通过具体的数据指标来衡量效能价值,让内部工具/平台的价值也能看得见、说得清那么,对于正在做或者将要做的工具平台,如何进一步提升其效能价值呢? 一.效能价值有哪些影响因素?首先,工具的关键目标是解决实际问题:工具总是为解决问题而生的选定目标问题之后,接着通过工具化、平台化等自动/半自动的手段来尝试解决,并通过效率和体验两方面的提升体现出解决方案的效能价值:效能价值 = 效率价值 * 体验因子 进一步细化:工具效率 = 问题规模 / 操作时间 工具效率 = (不用该工具解决所需的)时间成本 / (用该工具解决所需的)时间成本 工具体验 = 易用程度 * 稳定程度 因此,工具的效能价值取决于 4 个因素: 问题规模 操作时间 易用性 稳定性 提升工具效能就是想办法增大分子、减小分母,即提升问题规模、易用性、稳定性,降低操作时间 二.如何提升问题规模?对于选定的目标问题,其规模通常是固定的,所以关键在于如何选择目标价值最高的问题:问题的目标价值 = 目标用户量 * 需求频率 * 单次的价值 多数情况下,我们倾向于选择目标用户量更大的问题,因为解决一个普遍存在的问题要比解决只有小部分用户才会遇到的特殊问题更有意义然而,需求频率与单价对目标价值的影响却不那么显而易见:其中: 首选高频高价:非常难得的需求,如果有,优先满足 不做低频低价:此类需求不值得做 高频低价、低频高价并重:大多数需求都是这两类,选择也都集中在这里 在高频低价与低频高价之间,产品经理的一般策略是:高频抓用户,低频做利润也就是说,前期先通过满足高频低价的需求获得大量用户,中后期再将低频高价的需求考虑进来:先利用高频低价的需求抓用户,因为高频场景和用户互动的机会多,而低价的轻决策场景可以降低用户进入门槛,容易拉新、引流;再用低频高价的需求做利润,因为单价高了,可以切分的蛋糕才大。之所以采取这样的先后次序,是因为必须有海量用户做基础,低频需求的总量才足够大。 三.如何降低操作时间?当然,如果有明显的待优化项,应该尽快去做,先把工具自身的效率提升到相当高的水准,减少用户等待工具运转完成的时间但如果工具本身在耗时上已经没有太大的优化空间,此时就需要将目光从局部的工具中移出来,放眼全局考虑整体优化: 面向过程的视角:流程上,能否减少一些中间环节,简化工作流 面向对象的视角:模式上,能否减少参与其中的相关角色,减少人与工具、工具与工具、工具与人之间的交互,减少一些中间产物 流程上,甚至协作模式上的变革通常有机会颠覆先前解决问题的关键路径,绕过既有工具的效率瓶颈,从而大幅降低操作时间 四.如何提升易用性?工具型产品的第一要义是用户会用,让用户至少会用,才能体现产品的价值易用性要求产品功能尽可能地符合用户心智(至少要保证核心功能的易用性),简化交互,降低用户上手使用的学习成本:从用户心智向产品功能做映射,极致的易用是符合直觉,上手即用那么,首先要明确用户心智,做法非常简单:告诉用户,这个工具能给你解决什么具体问题。接着(在产品功能不那么符合直觉的阶段)先教会用户怎么用,功能引导、新手教程/视频、帮助文档等都是不错的方法,旨在提升易用性,让用户先用起来。同时根据用户真实反馈不断优化使用体验,缩小产品功能与用户心智之间的差距,使之最终符合直觉: 心智负担小(学习成本低) 交互友好 UI 美观 核心功能流程顺畅 除了让产品功能向用户心智靠拢外,还有一种非常规思路是培养用户心智(即改变用户直觉,使之符合产品功能),多出现在颠覆式创新的场景,必须改变用户根深蒂固的直觉才能真正提高效率 五.如何提升稳定性?从用户心智向产品性能做映射,极致的稳定是完全信任,从不怀疑工具会出问题与易用性相比,稳定性是客观而明确的,单从技术角度就能在很大程度上确保稳定性,例如: 降低 crash 率:持续关注 top 崩溃,及时修复影响范围较大的 减少 bug 数:持续观察 bug 增长趋势,快速迭代修复,收敛功能性问题 减少操作失败次数:记录失败操作,分析改善常见误操作,同时反向丰富功能 其中,值得注意是记录失败操作,以搜索功能为例,失败操作包括: 搜索服务出错 搜索无结果 搜索结果与预期不符(结果没有帮助) 从技术上看,后两类并不属于操作失败,但同样值得关注,因为无结果的搜索通常意味着语义化/模糊搜索功能不够完善,或者相关内容有缺失,这些信息对于丰富产品功能很有帮助。同理,不符合用户预期的搜索结果也是一种有价值的负反馈,有助于发现问题,改善用户使用体验 六.如何提升用户量?当工具的效率和体验都达标后,最关键的问题是如何提升用户量,放大工具的价值与其它产品相比,工具型产品的难点在于: 可替代性强 用户不知道(有工具可以用) 用户粘性差,容易流失 强的不可替代性是决定性因素,作为唯一选项自然不必考虑用户量的问题,例如小程序开发者工具如果不具备强的不可替代性,就要通过其它手段来增加用户的替换成本,常用的策略有场景化运营、社区运营、内容运营等 场景化运营 将工具与使用场景紧密关联起来,培养用户的使用习惯:做工具型产品一定要时刻追问用户在什么样的场景下会想到打开你的产品,这个具体场景就是一切运营的基础围绕一个核心场景,充分满足关键需求,成为该场景下的最优解决方案,从而解决用户不知道的问题另一方面,场景化的温馨提示有助于提升产品的温度,让用户感受到人性关怀,而不只是冷冰冰的工具 社区运营加强产品与用户,以及用户与用户的联系,建立社区是提高用户粘性的有效手段,例如: 运营一个群组:将冰冷的工具做成能够交流的“活人”,拉近产品与用户的距离 增加社交功能:用户订阅产品更新,用户之间关注、评论、点赞等,增加用户的参与感和归属感 通过群组将产品的变化告知用户,这种持续的频繁正向反馈能够激发用户反馈问题的积极性,增强产品与用户的联系社交化听起来与供内部使用的工具平台有些距离,实际上并不遥远。以前端工程为例,像公共组件/代码片段、Code Review、新手教程/API 文档等都可以有简单的社交功能(点赞、评论),看似细小,却有助于提升用户的参与度 内容运营与社区一样,内容也是一种场景延伸,将工具产出的内容也作为工具的一部分,例如: WPS 与稻壳儿模版 Git 与 Gist 工具引导用户输出附加价值,从而提升工具的整体价值(工具 + 共享内容)。另一方面,用户将产生的内容分享给其它用户,也有助于提升自身的影响力,互相促进 参考资料 再理解“高频打低频”,对也不对 工具类产品应该怎样运营? 工具类产品到底该怎么运营?附案例
2020年10月21日
279 阅读
0 评论
0 点赞
1
2
3