从本地到云端:豆瓣统一的数据存储实践

豆瓣成立于 2005 年,是中国最早的社交网站之一。在 2009 到 2019 的十年间,豆瓣数据平台经历了几轮变迁,形成了 DPark + Mesos + MooseFS 的架构。

由机房全面上云的过程中,原有这套架构并不能很好的利用云的特性,豆瓣需要做一次全面的重新选型,既要考虑未来十年的发展趋势,也需要找到与现有组件兼容且平滑过渡的解决方案。一番改造后, 豆瓣数据平台目前形成了 Spark + Kubernetes + JuiceFS 的云上数据湖架构,本文将分享此次选型升级的整体历程。

01 豆瓣早期数据平台

在 2019 年,豆瓣所使用的数据平台主要由以下组件构成:

Gentoo Linux,内部使用的 Linux 发行版;MooseFS ,分布式文件系统;Apache Mesos 负责整个集群的资源管理,以及 Dpark 作为分布式计算框架提供给开发者使用。

豆瓣早期数据平台架构

从上图可以看到在这个数据平台中,计算和存储是一体的,每个计算任务是由 Mesos 进行调度的。计算任务的 I/O 操作都是通过 MooseFS 的 Master 获取元数据,并在本地获取需要计算的数据。此外,GPU 计算集群也是通过 Mesos 进行管理,不同的是, GPU 会基于显存进行共享。

平台组件介绍

Gentoo Linux

Gentoo Linux 是一个较为小众的 Linux 发行版,具有几乎无限制的适应性特性,是一个原发行版。Gentoo Linux 采用滚动更新的方式,所有软件包都直接从社区中获取二进制包,我们则通过源代码构建我们所需的软件包。Gentoo Linux 有一个强大的包管理器,使用它也会带来很多便利,也同时存在一些问题。比如,滚动更新的速度非常快,但对于服务器来说,可能存在一定的不稳定性。

使用源代码构建软件包的好处是当社区没有预编译好我们所需的软件包时,我们可以非常简单地构建出自己所需的软件包,并且当已有的软件包无法满足我们的需求时,也可以很容易地进行定制调整。但这也会带来较高的维护成本。

另外,如果所有软件包都能按照规范进行编写的话,依赖冲突问题几乎是不存在的,因为在打包过程中就已经可以发现。但实际情况是并不是所有软件包都能遵守一个好的依赖描述的约定,因此依赖冲突问题可能仍然存在。

Gentoo Linux 是较为小众的选择,尽管社区质量很高,但是用户也比较少,一些新项目可能没有用户进行足够的测试,我们在实际使用过程中会遇到各种各样的问题。这些问题大部分需要我们自己解决,如果等待其他人回复的话,响应会比较慢。

MooseFS

MooseFS 是一个开源的、符合 POSIX 标准的分布式文件系统,它只使用 FUSE 作为 I/O 接口,并拥有分布式文件系统的标准特性,如容错、高可用、高性能和可扩展性。

对于几乎所有需要使用标准文件系统的场景,我们都使用 MooseFS 作为替代品,并在其基础上开发了一些自己的小工具。例如,我们可以直接使用分布式文件系统来处理 CDN 的回源。在早期版本中,MooseFS 没有主节点的备份功能,因此我们开发了一个 ShadowMaster 作为元数据的热备节点,并编写了一些分析 MooseFS 元数据的工具,以解决一些运维问题。作为一个存储设施,MooseFS 整体比较稳定,并且没有出现重大的问题。

Apache Mesos

Mesos 是一个开源的集群管理器,与YARN 有所不同,它提供公平分配资源的框架,并支持资源隔离,例如 CPU 或内存。Mesos 早在 2010 年就被 Twitter 采用, IBM 在 2013 年开始使用。

Dpark

由于公司全员使用 Python,因此使用了 Python 版的 Spark,即 Dpark,它扩展了RDD API,并提供了 DStream。

公司内部还开发了一些小工具,例如 drun 和 mrun,可以通过 Dpark 将任意 Bash 脚本或数据任务提交到 Mesos 集群,并支持 MPI 相关的任务提交。Dgrep 是用于快速查询日志的小工具,JuiceFS 也提供了类似的工具。虽然 Dpark 本身可以容器化,但公司主要的数据任务是在物理服务器上运行的。支持容器化可以让场内任务更好地利用线上业务的模型代码。

02 平台演进的思考

在 2019 年,公司决定将基础设施转移到云端并实现计算和存储分离,以提高平台的灵活性。由于以前的计算任务在物理机上运行,随着时间的推移,出现了越来越多的依赖冲突问题,维护难度不断增加。

同时,公司希望内部平台能够与当前的大数据生态系统进行交互,而不仅仅是处理文本日志或无结构化、半结构化的数据。此外,公司还希望提高数据查询效率,现有平台上存储的数据都是行存储,查询效率很低。最终,公司决定重新设计一个平台来解决这些问题。

平台演进时,我们没有非常强的兼容性需求。只要成本收益合理,我们就可以考虑将整个平台替换掉。这就像是环法自行车比赛中,如果车有问题就会考虑换车,而不是只换轮子。在更换平台时,我们如果发现现有平台的任务无法直接替换,可以先保留它们。在切换过程中,我们有以下主要需求:

  • Python 是最优先考虑的开发语言。
  • 必须保留 FUSE 接口,不能直接切换到 HDFS 或者 S3。
  • 尽可能统一基础设施,已经选用了部分 Kubernetes,就放弃了 Mesos 或其他备选项。
  • 新平台的学习成本应尽可能低,让数据组和算法组的同事能够以最低的成本切换到新的计算平台上。

03 云上构建数据平台

目前的云上数据平台几乎是全部替换了,Gentoo Linux 的开发环境变变成了 Debian based container 的环境, MooseFS 是换用了现在的 JuiceFS,资源管理使用了 Kubernetrs,计算任务的开发框架使用了 Spark,整体进行了彻底替换的,其他的设施是在逐渐缩容的过程,还会共存一段时间。

豆瓣数据平台架构

JuiceFS 作为统一存储数据平台

为了更好地满足不同的 I/O 需求和安全性考虑,我们会为不同的使用场景创建不同的 JuiceFS 卷,并进行不同的配置。JuiceFS 相对于之前的 MooseFS,创建文件系统更加简单,实现了按需创建。除了 SQL 数据平台外,我们的使用场景基本上都是由 JuiceFS 提供的服务。

在 JuiceFS 中,数据有几种类型:在线读写、在线读取离线写入、在线写入离线读取、离线读写。

所有的读写类型都在 JuiceFS 上进行,比如日志汇聚到卷中,Spark 可能会读取并进行 ETL,然后将数据写入数据湖。此外,从 Kafka 数据源读取的数据也会通过 Spark 进行处理并写入数据湖。

Spark 的 Check Point 直接存储在另一个 JuiceFS 卷中,而数据湖的数据则直接提供给算法组的同学进行模型训练,并将训练结果通过 JuiceFS 写回。我们的运维团队则通过各种脚本或工具来管理 JuiceFS 上的文件生命周期,包括是否对其进行归档处理等。因此,整个数据在 JuiceFS 中的流转过程大致如上图所示。

新数据平台组件介绍

Debian based container

首先,运维团队选择了 Debian based container 作为基础镜像,我们就直接使用了。我们的计算平台的镜像很大,为了解决任务启动速度的问题,团队在每个节点上预拉取了镜像。

JuiceFS

切换到 JuiceFS 存储系统时,用户感受不到变化,JuiceFS 非常稳定。JuiceFS 比 MooseFS 更好的一点是,它拥有 HDFS 的 SDK,方便了团队将来切换到 Spark 等工具。团队在 Kubernetes 上使用了 JuiceFS CSI,直接实现了 KV 存储的情况,按需创建 volume 也很方便。JuiceFS 团队沟通高效,解决问题迅速。例如,当 stream 的 checkpoint 频率太高时,JuiceFS 团队早早通知并迅速解决。

Kubernentes

我们早在 1.10 版本的时候就开始试用 Kubernetes。后来豆瓣对外的服务集群在 1.12 版本开始逐步迁移到 Kubernetes,基本上是在现有机器上完成了原地的替换。计算集群则是在上云后开始搭建的,基于1.14 版本。我们在版本升级方面可能比其他公司更为激进,目前我们的 Kubernetes 版本已经升级到了1.26 版。

我们选择 Kubernetes 作为计算平台的原因之一是它有比较统一的组件。此外,通过 scheduling framework 或者 Volcano,我们可以影响它的调度,这是我们比较希望拥有的一个特性。

我们还可以利用社区的 Helm 非常快速地部署一些需要的东西,比如 Airflow、Datahub 和 Milvus 等服务,这些服务都是通过 Helm 部署到我们的离线 Kubernetes 集群中提供的。

Spark

在最开始测试 Spark 时,我们像使用 Dpark 一样将任务运行在 Mesos 集群上。之后我们选定了 Kubernetes,使用 Google Cloud Platform 上的 spark-on-k8s-operator 将 Spark 任务部署到 Kubernetes 集群中,并部署了两个 Streaming 任务,但并未进行大规模的部署。

随后,我们确定了使用 Kubernetes 和 Airflow,计划自己实现一个 Airflow Operator,在 Kubernetes 中直接提交 Spark 任务,并使用 Spark 的 Cluster Mode 将任务提交到 Kubernetes 集群中。

对于开发环境,我们使用 JupyterLab 进行开发。厂内有一个 Python 库对 Spark Session 进行了一些小的预定义配置,以确保 Spark 任务能够直接提交到 Kubernetes 集群上。

目前,我们使用 Kubernetes Deployment 直接部署 Streaming 任务,这是一个很简单的状态,未来可能会有一些改进的地方。另外,我们正在准备试用 Kyuubi & Spark Connect 项目,希望能够为线上任务提供更好的读写离线数据的体验。

我们的版本升级非常激进,但确实从社区中获益匪浅。我们解决了日常计算任务中许多常见的优化场景。我们激进升级的原因是希望能够尽可能多地利用社区的资源,提供新特性给开发者。但我们也遇到了问题,例如 Spark 3.2 的 parquet zstd 压缩存在内存泄漏。为了规避这个问题,我们提前引入了未发布的补丁。

现在,我们使用两种方式来读写 JuiceFS 数据:FUSE 和 HDFS。FUSE 主要用于 ETL 任务,例如读写日志和 CSV 文件。我们也会将 Hive 表转存为 CSV 文件下载供未切换到 Spark 的任务进行计算。其他的数据,则直接通过预先配置好的 HDFS(如 Hive Table 和 Iceberg Table)进行读写,这大大简化了我们的工作。

在数据湖的选择上,我们一开始考虑了 Delta Lake,但由于它不支持 Merge on Read,在目前的使用场景存在写放大,我们放弃了它。取而代之,我们选择了 Iceberg,并将其用于 MySQL CDC 处理。我们将数据直接存储在 JuiceFS 上进行读写,并且目前没有遇到任何性能上的问题。未来,如果我们需要扩大规模使用,可能需要与 JuiceFS 的团队沟通一下,看看有哪些优化措施。

04 收获与展望

我们切换到新的计算平台之后,获得了很多原来没有的功能。例如,我们现在可以使用基于 SQL 的大量任务,这些任务的性能比以前好得多,各种报表的实时性也更好了。

与 Mesos 的情况不同,Spark 声明了多少资源就使用多少资源,这与以前的 Dpark 相比有很大的差异,因为以前大家都是公平分享,相互之间会有影响。现在,每个任务的执行时间都比较可预测,任务评估也比较容易预测,整个新平台对于业务数据的读取也有更好的时效性。

以前的历史包袱是相当沉重的,现在我们已经赶上了社区的步伐。去年年末的各种统计和排名都已经迁移到了新的计算平台上,并且运行非常稳定。

我们正在优先考虑采取一些成本下降措施,以实现整个计算集群的动态扩缩容。我们正积极努力实现此目标,并希望提供更加稳定的 SQL 接口。为此,我们计划采用支持 Multi-tenant 的 SQL 服务器,并尝试引入 Spark 3.4 的最新特性。

长远来看,我们希望通过 Spark Remote Shuffle Service 进一步实现存算分离,以便更有效地利用资源。也许未来我们会开发一个“Spark as a Service”,提供给开发者使用。总之,我们正在追赶社区的步伐,并不断努力提升我们的技术水平。

如有帮助的话欢迎关注我们项目 Juicedata/JuiceFS 哟! (0ᴗ0✿)

本文转载于网络 如有侵权请联系删除

相关文章

  • Flash Scope

    项目中遇到了一个潜在的问题,大致就是说,在一个流程的两个或某几个环节中,需要短暂地存储一部分对象(如果不存储,就需要在这几个环节中多次调用同一个外部接口,这被认为是不够合理的实现)。而这部分对象的存储:(1)如果用request,太小,毕竟一次提交以后就丢失了,如果需要往后传递,可能需要借助一些页面参数传值等丑陋或是不易控制的方法;(2)如果用session,太大,我不需要在整个用户会话生命周期内使用,而且如果同个用户并行地操作两个流程,期间会互相影响到。其实在Rails/Grails里面就已经包含了一个机制,它将对象短暂地放置在session中,request-response连续的两个环节后再清除掉这个短暂保存的对象,就叫是FlashScope。它的原理很简单,内部定义了两个Map,将保存在里面的数据,在两个Map里面来回转移,这样就保证在下一次请求到来时,只需要将其中一个Map的数据转移到另外一个Map中,而清除掉一个Map。这样就保证一次在重定向时,该作用域下依然保存数据。而在第二次的请求时,如果没有新的数据加进来,原来的数据将会被清空。这里有一个简单的例子:http://yo

  • 在 C# 中调用 MySQL 存储过程

    这段代码演示在C#程序中调用MySQL的存储过程,没有返回值,没有参数传递。MySqlConnectionmyConnection; myConnection=newMySqlConnection(); myConnection.ConnectionString="database="+database+";server="+ server+";userid="+user+";Password="+password; try{ myConnection.Open(); } catch(MySqlExceptionMyException){ Console.WriteLine("Connectionerror:MySQLcode:"+ MyException.Number+""+ MyException.Message); } try{ MySqlCommandmyCommand=newMySqlCommand( "callerror_test_proc(

  • laravel5.0在linux下解决.htaccess无效和去除index.php的问题

    刚入手的laravel,记录下此次遇到的坑,文章写的不多,但程序员写博客文章是个好习惯,这个我坚持并努力的。确保以下配置项已正确配置:LoadModulerewrite_modulemodules/mod_rewrite.so(去掉前面的#注释)AllowOverrideAll(根目录的配置下,确保设置成All)框架安装不用多说,大家自行解决咯。其它版本不知道,博主是5.0的版本,所以直接找到根目录下的public目录,下面有个.htaccess文件,但是这个框架,在windows下安装,上面两项打开的话,是没有任何问题的,可以不带index.php。然而Linux需要加上一句话(”RewriteBase/“):<IfModulemod_rewrite.c <IfModulemod_negotiation.c Options-MultiViews </IfModule RewriteEngineOn ################################ RewriteBase/ ################################ #Red

  • sync_hooks、CLS 与 Node 中异步资源生命周期监听

    ❝在Node中为什么需要监听异步资源? ❞在一个Node应用中,异步资源监听使用场景最多的地方在于:异常捕捉时需要提供用户信息,在每次客户端请求中保持一致的用户信息全链路式日志追踪,设计每次请求的第三方服务、数据库、Redis携带一致的traceId下图为zipkin根据traceId定位的全链路追踪: zipkin全链路追踪我们来看一个在异常处理中配置用户信息的「错误示例」:constsession=newMap() app.use((ctx,next)=>{ //设置用户信息 constuser=getUserById() session.set('user',user) awaitnext() }) app.use((ctx,next)=>{ try{ awaitnext() }catch(e){ constuser=session.get('user') //把user上报给异常监控系统 } }) 复制当在后端服务全局配置用户信息,以便异常及日志追踪。「由于此时采用的session是异步的,用户信息极其容易被随后而来的请

  • jQuery

    jQuery特性隐式迭代链式编程,在于一个方法返回的是一个jQuery对象,既然是jQuery对象就可以点出jQuery的方法来window.onload资源加载完成时调用$(function(){})页面dom树加载完完成时调用dom对象转换jQuery对象dom对象转换成jQuery对象vardiv1=document.getElementById("one")var$div1=$(div1)console.log($div1)jQuery对象转换成dom对象使用下标取出来var$divs=$('div')vardiv1=$divs[0]console.log(div1)使用jQuery的方法vardiv2=$divs.get(0)console.log(div2)小案例———-开光灯方法text()获取和设置文本内容 text()方法不写参数获取文本text()方法写参数设置文本如果设置的文本中包含标签,是不会把这个标签给解析出来的$('#div1').text('我是新设置的文本<a>我是链接&l

  • JfreeChart 乱码问题处理

    在前面之间加上下面这段代码即可。//创建主题样式 StandardChartThemestandardChartTheme=newStandardChartTheme("CN"); //设置标题字体 standardChartTheme.setExtraLargeFont(newFont("隶书",Font.BOLD,20)); //设置图例的字体 standardChartTheme.setRegularFont(newFont("宋书",Font.PLAIN,15)); //设置轴向的字体 standardChartTheme.setLargeFont(newFont("宋书",Font.PLAIN,15)); //应用主题样式 ChartFactory.setChartTheme(standardChartTheme);复制为了验证,先给出没有上面代码的一串代码:importjava.awt.Font; importjavax.swing.JPanel; importorg.jfree.chart

  • TensorFlow从1到2(六)结构化数据预处理和心脏病预测

    结构化数据的预处理前面所展示的一些示例已经很让人兴奋。但从总体看,数据类型还是比较单一的,比如图片,比如文本。 这个单一并非指数据的类型单一,而是指数据组成的每一部分,在模型中对于结果预测的影响基本是一致的。 更通俗一点说,比如在手写数字识别的案例中,图片坐标(10,10)的点、(14,14)的点、(20,20)的点,对于最终的识别结果的影响,基本是同一个维度。 再比如在影评中,第10个单词、第20个单词、第30个单词,对于最终结果的影响,也在同一个维度。 是的,这里指的是数据在维度上的不同。在某些问题中,数据集中的不同数据,对于结果的影响维度完全不同。这是数据所代表的属性意义不同所决定的。这种情况在《从锅炉工到AI专家(2)》一文中我们做了简单描述,并讲述了使用规范化数据的方式在保持数据内涵的同时降低数据取值范围差异对于最终结果的负面影响。 随着机器学习应用范围的拓展,不同行业的不同问题,让此类情况出现的越加频繁。特别是在与大数据相连接的商业智能范畴,数据的来源、类型、维度,区别都很大。 在此我们使用心脏病预测的案例,对结构化数据的预处理做一个分享。心脏病预测我们能从TensorFl

  • java之学习Arrays类的方法的应用

    结果示意图:A:Arrays类概述A:Arrays类概述*针对数组进行操作的工具类。*提供了排序,查找等功能。*B:成员方法*publicstaticStringtoString(int[]a)*publicstaticvoidsort(int[]a)*publicstaticintbinarySearch(int[]a,intkey)packagecom.ifenx8.study.array;importjava.util.Arrays;publicclassDemo_Arrays{/**A:Arrays类概述*针对数组进行操作的工具类。*提供了排序,查找等功能。*B:成员方法*publicstaticStringtoString(int[]a)*publicstaticvoidsort(int[]a)*publicstaticintbinarySearch(int[]a,intkey)*/publicstaticvoidmain(String[]args){int[]arr={22,55,11,66,44,33};System.out.println(Arrays.toString

  • JavaScript二维码生成——qrcode.js

    在开发中,有时候,我们需要根据不同的内容来动态生成二维码,则可以使用qrcode.js这个小插件来实现。1.qrcode.js文件内容:(1)未压缩(qrcode.js):/** *@fileoverview *-Usingthe'QRCodeforJavascriptlibrary' *-Fixeddatasetof'QRCodeforJavascriptlibrary'forsupportfull-spec. *-thislibraryhasnodependencies. * *@authordavidshimjs *@see<ahref="http://www.d-project.com/"target="_blank">http://www.d-project.com/</a> *@see<ahref="http://jeromeetienne.github.com/jquery-qrcode/"target="_blank"&g

  • PLC编程基础[通俗易懂]

    大家好,又见面了,我是你们的朋友全栈君。 1.开始一个新的工程按照以下步骤来建立一个新的工程:(1)选择工具栏中的新建按钮。(2)定义工程的设备条目。(3)保存工程当一个新的PLC被添加到工程中的时候,将创建以下空表:1)空的本地符号表;2)全局符号表;3)IO表;4)PLC内存数据;5)PLC设置数据。2.编写一个梯形图程序下面以一个交通灯次序控制为例说明,该交通灯次序是一个标准的英国交通灯次序,顺序如下:只有红灯→红灯和和黄灯同时→只有绿灯→只有黄灯。编写一个梯形图程序,包括:生成符号和地址、创建一个梯形图程序、编译程序。(1)按照以下步骤来生成符号1)单击图表窗口,在工具栏中选择查看本地符号按钮。2)从工具栏选择新建符号按钮,符号插入对话框将被显示。3)在名称栏中键入‘AmberLight’.4)在地址栏中键入’10.01’5)将数据类型栏设置为‘BOOL’,表示一个位(二进制)值6)在注释栏中输入‘准备通行/停止’7)选择确定按钮以继续进行对下表的每一项重复以上操作名称地址数据类型注释RedLight10.00BOOL停止GreenLight10.02BOOL通行RedLigh

  • vps服务器性能一键测试脚本

    说明1、显示当前测试的各种系统信息; 2、取自世界多处的知名数据中心的测试点,下载测试比较全面; 3、支持IPv6下载测速; 4、IO测试三次,并显示平均值。命令1(聚合版本)wget www.xinua.cn/it && bash it复制命令2wget -qO- bench.sh | bash复制命令3curl -Lso- bench.sh | bash复制命令4wget -qO- 86.re/bench.sh | bash复制命令5curl -so- 86.re/bench.sh | bash复制测试结果图

  • Scrapy创建工程

    创建工程:scrapystartprojectnews163Program 创建爬虫模板:scrapygenspidermovie  ent.163.com/movie 就会在spiders目录下生成movie文件: 启动运行:在工程根目录执行 scrapycrawlmovie 或者写成代码执行: fromscrapyimportcmdline importos #cmd的多个只运行第一个 cmdline.execute('scrapycrawlmovie--nolog'.split()) cmdline.execute('scrapycrawlmovie'.split()) #os的可以后台运行 os.system("scrapycrawlmovie--nolog")复制      

  • 【std::regex】C++文件路径正则表达式

    今天代码中遇到使用正则表达式匹配一个文件路径的问题,需要验证传入的文件路径是否合法,学习了简单的正则表达式的写法后,简单写了如下代码,在网上找了一些在线测试正则表达式的网站,测试可以通过。 std::regexmatch("^[a-zA-Z]:(\\[a-zA-Z0-9]+)+$"); std::stringpath("D:\\test"); if(!std::regex_match(path,match)){ cout<<"!!!"<<endl; } 复制 但是整到C++里面后,结果报错了,报错原因是regex_errorcaught:regex_error(error_brack):Theexpressioncontainedmismatched[and]. 我开始怀疑是不是正则表达式在\\[部分把\\[组合在了一起,我的本意是匹配一个\字符结果却变成了匹配一个[字符,导致方括号表达式只剩下右半边所以报错。 本身C++用\来当做转义符,则\\表示一个正常的\,如果放在正则表达式中的话,\\表示一个\字符,那么在C++中可能\\\\才是匹配\的正确做法 有点

  • 【软件工程】 结对作业

    项目 内容 这个作业属于哪个课程 软件工程罗杰 这个作业的要求在哪里 结对项目最长单词链 我在这个课程的目标是 熟悉软件开发整体流程,提升自身能力 这个作业在哪个具体方面帮助我实现目标 实践教材中内容,体会“结对编程”模式 本项目的Github链接为:https://github.com/Diralpo/LongestWordChain 开发前的PSP表格 PSP2.1 PersonalSoftwareProcessStages 预估耗时(分钟) 实际耗时(分钟) Planning 计划 30 ·Estimate ·估计这个任务需要多少时间 30 Development 开发 720 ·Analysis ·需求分析(包括学习新技术) 90 ·DesignSpec ·生成设计文档 60 ·DesignReview ·设计复审(和同事审核设计文档) 30 ·CodingStandard ·代码规范(为目前的开发制定合适的规范) 30 ·Design ·具体设计 120 ·Cod

  • Hitachi Programming Contest 2020 E Odd Sum Rectangle

    当自己改题的时候发现场上猜的一个结论二维差分一下就是正解的时候非常崩溃TAT 先把原来的矩阵\(a_{i,j}\)拓展成\(2^N\)行\(2^M\)列,其中第\(0\)行和第\(0\)列的数全部都是\(0\),然后对其二维前缀和得到矩阵\(b_{i,j}\)。 先考虑答案上界,枚举\(0\leqi<j<2^N\),设\(c_k=b_{i,k}\\mathrm{xor}\b_{j,k}\),那么所有上边界在\(i+1\)行、下边界在\(j\)行的奇矩阵数量是\((\sum\limits_{p=0}^{2^M-1}[c_p=0])\times(\sum\limits_{p=0}^{2^M-1}[c_p=1])\leq\frac{2^{2M}}{4}\),故一个答案上界是\(\frac{2^N(2^N-1)}{2}\times\frac{2^{2M}}{4}\)。 看到矩阵大小是二的整数幂果断地往位运算上考虑,接下来给出一个构造:\(b_{i,j}=\mathrm{popcount}(i\\mathrm{and}\j)\bmod2\)并证明其能达到答案上界。 先考虑\(N=

  • vuessr

    更好的SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。Google和Bing可以很好的对同步JavaScript应用程序进行索引。如果你的应用程序初始展示loading图,然后通过ajax获取内容,抓取工具并不会等待异步完成后在进行抓取页面的内容。也就是说,如果SEO对你的站点至关重要,而你的页面又是异步获取内容,则你可能需要服务器端渲染解决此问题。 更快的内容到达时间(time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备。无需等待所有的JavaScript都完成下载并执行,才显示服务器渲染的标记,对于那些内容到达时间(time-to-content)与转化率直接相关的应用程序,服务器端渲染至关重要。  Head管理 asyncfunctiongetTitle(vm){ consttitle=vm.$options if(title){ returntypeoftitle==='function'?awaittitle.call(vm):title } } constserverTitleMixin={ created(){ cons

  • centos 守护 node 进程

    废话不多说:  vi/usr/lib/systemd/system/media-auth.service   [Unit] Description=media-authserverdaemon Documentation=no After=no Wants=no [Service] EnvironmentFile=no ExecStart=/usr/bin/node/root/MediaAuth/index.js ExecReload=/bin/kill-HUP$MAINPID ExecStop=/bin/kill-9$MAINPID KillMode=process Restart=on-failure RestartSec=1s [Install] WantedBy=multi-user.target复制   启动: //开机启动 systemctlenablemedia-auth.service //启动 systemctlstartmedia-auth.service复制   测试一下,正常,启动停止!!! kill-9&n

  • 二维数组作为参数传递

    l 二维数组题的思维收获 这个算法跟数据结构没关系,主要是逻辑思维看图思考利用的是数据之间的关系;多多举例具体化尝试,把复杂的问题具体化,分析那种情况,时间复杂度低。先有思路,再写代码。代码贴近自己的思维。 l 二维数组处理的代码收获 在c++中将二维数组当作参数的时候,必须指明所有维数大小或者省略第一维。但是我们在写程序的时候经常会遇到各个维数都不固定的情况,我们完全可以不把它当作一个二维数组,而是把它当作一个普通的指针,在另外加上两个参数指明各个维数,然后我们为二维数组手工寻址,这样就达到了将二维数组作为参数传递的目的 #include<iostream> usingnamespacestd; //int*matrix与intmatrix[][]在传递参数时候的区别; //有时候递归反而把简单问题复杂了; //有时候我们写代码定义变量的时候要贴近思维,也要注意代码速度; intFind(int*matrix,introws,intcolumns,intnumber) { introw=0,column=columns-1; while(colu

  • Ubuntu--Ubuntu18.04设置服务器代理,安装常用软件

    1. 设置镜像服务器   由于Ubuntu中的软件都是从Ubuntu服务器中中下载安装的,Ubuntu的服务器在美国,我们使用下载较慢,但是有很多国内镜像可以使用,这样可以大大提高软件安装和更新的速度   找到左下角的框中图标,点击后,在出现的界面中找到软件与更新图标   选择其他站点      点击关闭按钮,出现下面的界面,选择重新载入    2.软件更新   sudoaptupdate,只会显示可以更新的软件列表       sudoaptupgrade,更新可以更新的软件列表    3.   

  • 所谓jQuery.append()、jQuery.html()存在的XSS漏洞

    使用jQuery.append()、jQuery.html()方法时,如果其中内容包含<script>脚本而没有经过任何处理的话,会执行它。 简单的示例代码如下: varxssStr='<script>console.log(1)</script>'; $('#test').html(xssStr);复制 控制台会打印出“1”。 同样的情况也存在于jQuery.append(),因为jQuery.html()内部也是调用jQuery.append()。 既然会存在执行<script>脚本的情况,那么就会有xss风险。 解决办法也很简单,将需要作为参数的字符串进行转义: varxssEscapeStr=xssStr.replace(/</g,'&lt;').replace(/>/g,'&gt;');复制 这样输出在页面上的只是单纯的一段<script>字符串,并未执行。 但这并不是jQuery的一个bug,查看jQuery源码,jQuery.append()对于<script>的

  • @RequestBody、@RequestParam和@PathVariable的区别

    来自:https://blog.csdn.net/qq_32683235/article/details/113878052

相关推荐

推荐阅读