C++和Rust_RustCon Asia 实录 | Rust 在国内某视频网站的应用

news/2024/5/11 19:12:40/文章来源:https://blog.csdn.net/weixin_39606361/article/details/111050054

作者介绍

hawkingrei(王维真),中间件高级开发工程师,开源爱好者,TiDB & TiKV Contributor。

WaySLOG(雪松),Rust 铁粉一枚,专注中间件,bug creator。

b8af6989b8eb5f49aabd86e5971ae75a.png

本文根据 hawkingrei & WaySLOG 在 首届 RustCon Asia 大会 上的演讲整理。

今天我们会和大家聊聊 Rust 在我们公司的二三事,包括在公司产品里面用的两个工具,以及雪松(WaySLOG)做的 Cache Proxy —— Aster 的一些经验。

be5037d18f9f22a0b06c478e61036336.png

图 1

十年前,我司刚刚成立,那时候其实很多人都喜欢用 PHP 等一些动态语言来支持自己的早期业务。用动态语言好处在于开发简单,速度快。但是动态语言对代码质量、开发的水平的要求不是很高。所以我来到公司以后的第一个任务就是把我们的 PHP 改写成 Golang 业务。在我看了当时 PHP 的代码以后的感受是:动态语言一时爽,代码重构火葬场。因为早期我司还是个人网站,PHP 代码质量比较差,代码比较随意,整套系统做在了一个单体的软件里,我们称这个软件是一个全家桶,所有的业务都堆在里面,比较恶心。所以导致早期我司的服务质量也是非常差,观众给我们公司一个绰号叫「小破站」。

但是随着规模越来越大,还上市了,如果还停留在「小破站」就十分不妥,因此我们开始用 Golang 对服务进行一些改进,包括开发一些微服务来稳定我们的业务。通过这些改造也获得了很好的一个效果,因为 Golang 本身非常简洁,是一个带 GC 的语言,同时还提供了 goroutine 和 channel 一些功能,可以很方便的实现异步操作。但随着业务规模变大,也出现了一些 Golang 无法支持的一些情况。于是,我们将目光转向了 Rust。

1. Remote Cache

Remote Cache 是我们第一个 Rust 服务。该服务是我们公司内部的一套 Cache 服务。

在具体介绍这个服务之前,先介绍一下背景。首先在我们内部,我们的代码库并不像普通的一些公司一个项目一个库,我们是大仓库,按语言分类,把所有相同语言的一个业务代码放到一个仓库里,同时在里面还会封装一些同一种语言会用到的基础库,第三方的依赖放在一个库里面。这样所有的业务都放在一个仓库,导致整个仓库的体积非常巨大,编译也会花很多的时间,急需优化。

e1e06fd592ebf9a7b3c4a61050da56d6.png

图 2

此时,我们用到了两个工具—— Bazel 和 Gradle,这两个编译工具自带了 Remote Cache 功能。比如你在一台机器上编译以后,然后换了台机器,你还可以重新利用到上次编译的一个中间结果继续编译,加快编译的速度。

还有一个是叫 Prow 的分布式 CI/CD 系统,它是构建在 K8s 上运行的一套系统,来进行我们的一个分布式编译的功能,通过上面三个工具就可以来加速我们大仓库的一个编译的效率。但是,大家也看到了,首先中间一个工具,Bazel 跟 Gradle 他需要上传我的一个中间产物。这样就需要远端有一个服务,可以兜住上传结果,当有编译任务时,会把任务分布在一个 K8s 集群里面,就会同时有大量的请求,这样我们就需要有个 Remote Cache 的服务,来保证所有任务的 cache 请求。同时,因为我们使用了 Bazel 跟 Gradle,所以在办公网里面,很多开发也需要去访问我们的 Remote Cache 服务,来进行编译加速。

4aef37c06ab515890e611a19f9c0670b.png

图 3

所以对我们 Remote Cache 服务的负担其实是很重的。在我们早期的时候,因为一些历史原因,我们当时只有一台服务器,同时还要承担平均每天 5000-6000 QPS 的请求,每天的量大概是 3TB 左右,并且仓库单次编译的大小还会不断的增加,所以对 Remote Cache 服务造成很大压力。 

1.1 Kubernetes Greenhouse

我们当时在想如何快速解决这个问题,最开始我们的解决方法是用 K8s 的 Greenhouse 开源服务(https://github.com/kubernetes/test-infra/tree/master/greenhouse)。

刚开始用的时候还挺好的,但是后来发现,他已经不太能满足我们的需求,一方面是我们每天上传的 Cache 量比较大,同时也没有进行一些压缩,它的磁盘的 GC 又比较简单,它的 GC 就是设置一个阈值,比如说我的磁盘用到了 95%,我需要清理到 80% 停止,但是实际我们的 Cache 比较多。而且我们编译的产物会存在一种情况,对我们来说并不是比较老的 Cache 就没用,新的 Cache 就比较有用,因为之前提交的 Cache 在之后也可能会有所使用,所以我们需要一个更加强大的一个 GC 的功能,而不是通过时间排序,删除老的 Cache,来进行 GC 的处理。

1.2 BGgreenhouse

于是我们对它进行了改造,开发出了 BGreenhouse,在 BGreenhouse 的改造里面,我们增加了一个压缩的功能,算法是用的 zstd,这是 Facebook 的一个流式压缩算法,它的速度会比较快,并且我们还增加了一个基于 bloomfilter 过滤器的磁盘 GC。在 K8s 的 Greenhouse 里面,它只支持 Bazel。在 BGreenhouse 中,我们实现了不仅让它支持 Bazel,同时也可以支持 Gradle。

f3f9dea489b3b5267a512f0e5f6782a5.png

图 4

最初上线的时候效果非常不错,但是后来还是出现了一点问题(如图 5 和图 6)。大家从图中可以看到 CPU 的负载是很高的,在这种高负载下内存就会泄露,所以它就「炸」了……

24a813857c0fca6d3286e89865e2b7a2.png

图 5

d37115d6ae43e327df209657d0b04360.png

图 6

1.3 Cgo is not Go

我们分析了问题的原因,其实就是我们当时用的压缩算法,在 Golang 里面,用的是 Cgo 的一个版本,Cgo 虽然是带了一个 go,但他并不是 Go。在 Golang 里面,Cgo 和 Go 其实是两个部分,在实际应用的时候,需要把 C 的部分,通过一次转化,转换到 Golang 里,但 Golang 本身也不太理解 C 的部分,它不知道如何去清理,只是简单的调用一下,所以这里面会存在一些很不安全的因素。同时,Golang 里面 debug 的工具,因为没法看到 C 里面的一些内容,所以就很难去做 debug 的工作,而且因为 C 跟 Golang 之间需要转换,这个过程里面也有开销,导致性能也并不是很好。所以很多的时候,Golang 工程师对 Cgo 其实是避之不及的。

1.4 Greenhouse-rs

在这个情况下,当时我就考虑用 Rust 来把这个服务重新写一遍,于是就有了 Greenhouse-rs。Greenhouse-rs 是用 Rocket 来写的,当中还用了 zstd 的库和 PingCAP 编写的 rust-prometheus,使用以后效果非常明显。在工作日的时间段,CPU 和内存消耗比之前明显低很多,可谓是一战成名(如图 7 和图 8 所示)。

674605267384d71613cd563f7cebed3d.png

图 7

ef4790f3938c5e4de74cdc4f2128657a.png

图 8

1.5 Golang vs Rust

然后我们对比来了一下 Golang 和 Rust。虽然这两门语言完全不一样,一个是带 GC 的语言,一个是静态语言。Golang 语言比较简洁,没有泛型,没有枚举,也没有宏。其实关于性能也没什么可比性,一个带 GC 的语言的怎么能跟一个静态语言做对比呢?Rust 性能特别好。

另外,在 Golang 里面做一些 SIMD 的一些优化,会比较恶心(如图 9)。因为你必须要在 Golang 里先写一段汇编,然后再去调用这段汇编,汇编本身就比较恶心, Golang 的汇编更加恶心,因为必须要用 plan9 的一个特别的格式去写,让人彻底没有写的兴趣了。

9ee06c3bf3bf21f0bc4d69f8186a1607.png

图 9

但在 Rust 里面,你可以用 Rust 里核心库来进行 SIMD 的一些操作,在 Rust 里面有很多关于 SIMD 优化过的库,它的速度就会非常快(如图 10)。经过这一系列对比,我司的同学们都比较认可 Rust 这门语言,特别是在性能上。

c11b90c6c1aa71163446dfa68c0ef845.png

图 10

2. Thumbnail service

之后,我们又遇到了一个服务,就是我们的缩略图谱,也是用 Rust 来做图片处理。缩略图谱服务的主要任务是把用户上传的一些图片,包括 PNG,JPEG,以及 WEBP 格式的图,经过一些处理(比如伸缩/裁剪),转换成 WEBP 的图来给用户做最后的展示。

679afbf291f00f4089d588c312e0b751.png

图 11

但是在图片处理上我们用了 Cgo,把一些用到的基础库进行拼装。当然一提到 Cgo 就一种不祥的预感,线上情况跟之前例子类似,负载很高,而在高负载的情况下就会发生内存泄露的情况。

2.1 Thumbnail-rs

于是我们当时的想法就是把 Golang 的 Cgo 全部换成 Rust 的 FFI,同时把这个业务重新写了一遍。我们完成的第一个工作就是写了一个缩略图的库,当时也看了很多 Rust 的库,比如说 image-rs,但是这个里面并没有提供 SIMD 的优化,虽然这个库能用也非常好用,但是在性能方面我们不太认可。

2.2 Bindgen

所以我们就需要把现在市面上用的比较专业的处理 WEBP,将它的基础库进行一些包装。一般来说,大家最开始都是用 libwebp 做一个工作库,简单的写一下,就可以自动的把一个 C++ 的库进行封装,在封装的基础上进行一些自己逻辑上的包装,这样很容易把这个任务完成。但是这里面其实是存在一些问题的,比如说 PNG,JPEG,WEBP 格式,在包装好以后,需要把这几个库 unsafe 的接口再组装起来,形成自己的逻辑,但是这些 unsafe 的东西在 Rust 里面是需要花一些精力去做处理的, Rust 本身并不能保证他的安全性,所以这里面就需要花很多的脑力把这里东西整合好,并探索更加简单的方法。

我们当时想到了一个偷懒的办法,就是在 libwebp 里边,除了库代码以外会提供一些 Example,里面有一个叫 cwebp 的一个命令行工具,他可以把 PNG,JPEG 等格式的图片转成 WEBP,同时进行一些缩略剪裁的工作。它里面存在一些相关的 C 代码,我们就想能不能把这些 C 的代码 Copy 到项目里,同时再做一些 Rust 的包装?答案是可以的。所以我们就把这些 C 的代码,放到了我们的项目里面,用 Bindgen 工具再对封装好的部分做一些代码生成的工作。这样就基本写完我们的一个库了,过程非常简单。

2.3 Cmake && Bindgen

但是还有一个问题,我们在其中用了很多 libpng、libwebp 的一些库,但是并没有对这些库进行一些版本的限制,所以在正式发布的时候,运维同事可能不知道这个库是什么版本,需要依赖与 CI/CD 环境里面的一些库的安装,所以我们就想能不能把这些 lib 库的版本也托管起来,答案也是可以的。

d4af65588ef479431e8818da4e3658ad.png

图 12

图 12 中有一个例子,就是 WEBP 的库是可以用 Cmake 来进行编译的,所以在我的 build.rc 里面用了一个 Cmake 的库来指导 Rust 进行 WEBP 库的编译,然后把编译的产物再去交给 Bindgen 工具进行自动化的 Rust 代码生成。这样,我们最简单的缩略图库很快的就弄完了,性能也非常好,大概是 Golang 三倍。我们当时测了 Rust 版本请求的一个平均的耗时,是 Golang 版本的三倍(如图 13)。

adf95cb325b60adf44072fbd258b2ce5.png

图 13

2.4 Actix_Web VS Rocket

在写缩略图服务的时候,我们是用的 Actix_Web 这个库,Greenhouse 是用了 Rocket 库,因为同时连续两个项目都使用了不同的库,也有一种试水的意思,所以在两次试水以后我感觉还是有必要跟大家分享一下我的感受。这两个库其实都挺好的,但是我觉得 Rocket 比较简单,同时还带一些宏路由,你可以在 http handle 上用一个宏来添加你的路由,在 Actix 里面就不可以。 Actix 支持 Future,性能就会非常好,但是会让使用变得比较困难。Rocket 不支持 Future,但基本上就是一个类似同步模型的框架,使用起来更简单,性能上很一般。我们后续计划把 Greenhouse 用 Actix_web 框架再重新写一遍,对比如下图所示。

5fd145d6efe7501269cf9c7515ba5cb5.png

图 14

以上就是我司两个服务的小故事和一些小经验。

3. Rust 编译过慢

前面分享了很多 Rust 的优点,例如性能非常好,但是 Rust 也有一个很困扰我们的地方,就是他编译速度和 Golang 比起来太慢了, 在我基本上把 Rust 编译命令敲下以后,出去先转上一圈,回来的时候还不一定能够编译完成,所以我们就想办法让 Rust 的编译速度再快一点。

3.1 Prow

首先是我们公司的 Prow,它其实也不是我司原创,是从 K8s 社区搬过来的。Prow 的主要功能是把一个大仓库里面的编译任务通过配置给拆分出来。这项功能比较适合于大仓库,因为大的仓库里面包含了基础库和业务代码,修改基础库以后可能需要把基础库和业务代码全部再进行编译,但是如果只改了业务代码,就只需要对业务代码进行编译。另外同基础库改动以后,时还需要按业务划分的颗粒度,分散到不同的机器上对这个分支进行编译。

在这种需求下就需要用到 Prow 分布式编译的功能,虽然叫分布式编译,但其实是个伪分布式编译,需要提前配置好,我们现在是在大仓库里面通过一个工具自动配置的,通过这个工具可以把一个很大规模存量的编译拆成一个个的小的编译。但是有时候我们并一定个大仓库,可能里面只是一个很简单的业务。所以 Prow 对我们来说其实并不太合适。

3.2 Bazel

另外介绍一个工具 Bazel,这是谷歌内部类似于 Cargo 的一个编译工具,支持地球上几乎所有的语言,内部本质是一个脚本工具,内置了一套脚本插件系统,只要写一个相应的 Rules 就可以支持各种语言,同时 Bazel 的官方又提供了 Rust 的编译脚本,谷歌官方也提供了一些相应的自动化配置生成的工具,所以 Golang 在使用的时候,优势也很明显,支持 Remote Cashe。同时 Bazel 也支持分布式的编译,可以去用 Bazel 去做 Rust 的分布式编译,并且是跨语言的,但这个功能可能是实验性质的。也就是说 Rust 可能跟 Golang 做 Cgo,通过 Golang Cgo 去调 Rust。所以我们通过 Bazel 去进行编译的工作。但缺点也很明显,需要得从零开始学 Rust 编译,必须要绕过 Cargo 来进行编译的配置,并且每个目录层级下面的原代码文件都要写一个 Bazel 的配置文件来描述你的编译过程。

为了提升性能,就把我们原来使用 Rust 的最大优势——Cargo 这么方便的功能直接给抹杀掉了,而且工作量也很大。所以 Bazel 也是针对大仓库使用的一个工具,我们最后认为自己暂时用不上 Bazel 这么高级的工具。

3.3 Sccahe

于是我们找了一个更加简单的工具,就是 Firefox 官方开发的 Sccahe。它在远端的存储上面支持本地的缓存,Redis,Memcache,S3,同时使用起来也非常简单,只要在 Cargo 里面安装配置一下就可以直接使用。这个工具缺点也很明显,简单的解释一下, Sccahe 不支持 ffi 里涉及到 C 的部分,因为 C 代码的 Cache 会存在一些问题,编译里开的一些 Flag 有可能也会不支持(如下图所示)。

437f206edb1c74a789283aec5bd3b41f.png

图 15

所以最后的结论就是,如果你的代码仓库真的很大,比 TiKV 还大,可能还是用 Bazel 更好,虽然有学习的曲线很陡,但可以带来非常好的收益和效果,如果代码量比较小,那么推荐使用 Sccahe,但是如果你很不幸,代码里有部分和 C 绑定的话,那还是买一台更好的电脑吧。

4. Cache Proxy

这一部分分享的主题是「技术的深度决定技术的广度」,出处已经不可考了,但算是给大家一个启迪吧。

f0976039bb0157735a59982db06e2648.png

图 16

下面来介绍 Aster。Aster 是一个简单的缓存代理,基本上把 Corvus(原先由饿了么的团队维护)和 twemproxy 的功能集成到了一起,同时支持 standalone 和 redis cluster 模式。当然我们也和 Go 版本的代理做了对比。相比之下,QPS 和 Latency 指标更好。因为我刚加入我司时是被要求写了一个 Go 版本的代理,但是 QPS 和 Latency 的性能不是很好,运维又不给我们批机器,无奈只能是自己想办法优化,所以在业余的时间写了一个 Aster 这个项目。但是成功上线了。

243cec57aaff7ff4e1e24a33385bdba0.png

图 17

图 18 是我自己写的缓存代理的进化史,Corvus 的话,本身他只支持 Redis Cluster,不支持 memcache 和是 Redis Standalone 的功能。现在 Overlord 和 Aster 都在紧张刺激的开发中,当然我们现在基本上也开发的差不多了,功能基本上完备。

d686018bcd73d0dc21edfc8bf3645fce.png

图 18

因为说到 QPS 比较高,我们就做了一个对比,在图 19 中可以看到 QPS 维度的对比大概是 140 万比 80 万左右,在 Latency 维度上 Aster 相较于 Overlord 会更稳定,因为 Aster 没有 GC。

210a12971372b9350a4fa9009ecc1ac8.png

图 19

4.1 无处安放的类型转换

给大家介绍一下我在写 Aster 的时候遇到了一些问题,是某天有人给我发了图 20,是他在写 futures 的时候,遇到了一个类型不匹配的错误,然后编译报出了这么长的错误。

3b0779f62b65e888fdde74ccffa4fb95.png

图 20

可能大家在写 Future 的时候都会遇到这样的问题,其实也没有特别完善的解决办案,但可以在写 Future 和 Stream 的时候尽量统一 Item 和 Error 类型,当然我们现在还有  failure::Error 来帮大家统一。

这里还重点提一下 SendError。SendError 在很多 Rust 的 Channal 里面都会实现。在我们把对象 Push 进这个队列的时候,如果没有足够的空间,并且 ownership 已经移进去了,那么就只能把这个对象再通过 Error 的形式返回出来。在这种情况下,如果你不处理这个 SendError,不把里面的对象接着拿下来,就有可能造成这个对象无法得到最后的销毁处理。我在写 Aster 的时候就遇到这样的情况。

4.2 drop 函数与唤醒

下面再分享一下我认为 Rust 相比 Golang 、 C 及其他语言更好的一个地方,就是 Drop 函数。每一个 Future 最终都会关联到一个前端的一个 FD 上面,关联上去之后,我们需要在这个 Future 最后销毁的时候,来唤醒对应的 FD ,如果中间出现了任何问题,比如 SendError 忘了处理,那么这个 Future 就会一直被销毁,FD 永远不会被唤醒,这个对于前端来说就是个大黑盒。

034c57d6f73d020fb9ed9b866dbbe3b9.png

图 21

于是我们就想到用 Drop 函数维持一个命令的 Future 的引用计数,引用计数到了归零的时候,实际上就相当于这个 Future 已经完全结束了,我们就可以通过归零的时候来对它进行唤醒。但是一个命令可能包含很多子命令,每一个子命令完成之后都要进行一次唤醒,这样代价太高,所以我们又加入了一个计数,只有这个计数归零的时候才去唤醒一次。这样的话,效率会很高。

a13fbca937c61fe2ab366253ef9438fe.png

图 22

4.3 让人头秃的 profile

Aster 最初的版本性能已经很高了,接着我们对它进行了两版优化,然而越优化性能越低,我们感到很无奈,然后去对它做了一个 Profile,当然,现在一般我采用的手段都是 perf 或者火焰图,我在对 Rust 程序做火焰图的时候,顺手跑了个命令,perf 命令,用火焰图工具把他处理一下,最后生成出来的结果不是很理想,有很多 unknown  的函数,还有函数名及线程名显示不全的情况(如图 23)。

b71aef278f82c6534ff849653e818452.png

图 23

然后我们开始尝试加各种各样的参数,包括 force-frame-pointers 还有 call-graph 但是最后的效果也不是很理想。直到有一天,我发现了一个叫 Cargo Flame Graph 的库,尝试跑了一下,很不幸失败了,它并没有办法直接生成我们这种代理程序的火焰图,但是在把它 CTRL-C 掉了之后,我们发现了 stacks 文件。如果大家熟悉火焰图生成的话,对 stacks 肯定是很熟悉的。然后我们就直接用火焰图生成工具,把它再重新展开。这次效果非常好,基本上就把所有的函数都打全了(如图 24)。

ae2e28c1a3a82cd81a1adef186ac4616.png

图 24

4.4 paser 回溯

这个时候我们就可以针对这个火焰图去找一下我们系统的瓶颈,在我们测 benchmark 的时候,发现当处理有几万个子命令的超长命令的时候,Parser 因为缓存区读不完,会来回重试解析,这样非常消耗 CPU 。于是我们请教了 DC 老师,让 DC 老师去帮我们写一个不带回溯的、带着状态机的 Parser。

f50855ef5aedb4680f682e9ade164dc0.png

图 25

这种解法对于超长命令的优化情况非常明显,基本上就是最优了,但是因为存了状态,所以它对正常小命令优化的耗时反而增加了。于是我们就面临一个取舍,要不要为了 1% 的超长命令做这个优化,而导致 99% 的命令处理都变慢。我们觉得没必要,最后我们就也舍去了这种解法,DC 老师的这个 Commit 最终也没有合进我的库,当然也很可惜。

4.5 我最亲爱的 syscall 别闹了

我们做 Profile 的时候发现系统的主要瓶颈是在于syscall,也就是 readfrom 和 sendto 这两个 syscall 里面。

这里插入一个知识点,就是所谓的零拷贝技术。

b1dd3ac913c496a951e103f8236306ce.png

图 26

在进行 syscall 的时候,读写过程中实际上经历了四次拷贝,首先从网卡 buffer 拷到内核缓存区,再从内核缓存区拷到用户缓存区,如果用户不拷贝的话,就去做一些处理然后再从用户缓冲区拷到内核缓存区,再从内核缓存区再把他写到网卡 buffer 里面,最后再发送出去,总共是四次拷贝。有人提出了一个零拷贝技术,可以直接用 sendfile() 函数通过 DMA 直接把内核态的内存拷贝过去。

还有一种说法是,如果网卡支持 SCATTER-GATHER 特性,实际上只需要两次拷贝(如下图右半部分)。

3dbeb5ed537d21155f115c92f17090e3.png

图 27

但是这种技术对我们来说其实没有什么用,因为我们还是要把数据拷到用户态缓冲区来去做一些处理的,不可能不处理就直接往后发,这个是交换机干的事,不是我们服务干的事。

4.6 DPDK + 用户态协议栈

那么有没有一种技术既能把数据拷到用户态又能快速的处理?有的,就是 DPDK。

接下来我为大家简单的介绍一下 DPDK,因为在 Aster 里面没有用到。DPDK 有两种使用方式,第一种是通过 UIO,直接劫持网卡的中断,再把数据拷到用户态,然后再做一些处理(如图 28)。这样的话,实际上就 bypass 了 syscall。

8fe1908a45a57f7e5dbea99d812416a0.png

图 28

第二个方式是用 Poll Model Driver(如图 29)。这样就有一颗 CPU 一直轮循这个网卡,让一颗 CPU 占用率一直是百分之百,但是整体效率会很高,省去了中断这些事情,因为系统中断还是有瓶颈的。

766cdbd3df8e596e298a5f6b9b786639.png

图 29

这就是我们今天的分享内容,谢谢大家。

? 延展阅读:

Distributed Actor System in Rust

? 后台回复【RustCon Asia】获取本届 RustCon Asia 部分 PPT 链接

  首届 RustCon Asia   

2019 年 4 月 23 日,由秘猿科技和 PingCAP 主办的首届 RustCon Asia 在北京圆满落幕,300 余位来自中国、美国、加拿大、德国、俄罗斯、印度、澳大利亚等国家和地区的 Rust 爱好者参加了本次大会。作为 Rust 亚洲社区首次「大型网友面基 Party」,本届大会召集了 20 余位海内外顶尖 Rust 开发者讲师,为大家带来一天半节奏紧凑的分享和两天 Workshop 实操辅导,内容包括 Rust 在分布式数据存储、安全领域、搜索引擎、嵌入式 IoT、图像处理等等跨行业、跨领域的应用实践。

59c6d60e2848c220f24c5fec7411bd1c.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_757343.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue 写门户网站_vue 美团项目初始化 - 一文g

在使用vue create xxx 创建项目后要在文件中加入/* jshint esversion: 6 */vue-clivue-cli是和vue进行深度组合的工具,可以快速帮我们创建vue项目,并且把一些脚手架相关的代码给我们创建好。真正使用vue开发项目,都是用vue-cli来创建项目的。…

浏览器打开出现证书错误_证书错误IE所有HTTPS网站都打不开[解决]

笔记本电脑修过之后装上硬盘,发现打开outlook提示证书过期错误,使用IE打开http网站都正常,但https网站全部提示证书失效错误.解决过程1/删除证书重新安装-无效2/重置IE浏览器-无效3/重新安装IE组件-无效4/mmc-控制台根节点-添加删除管理单元-证书-删除所有无用的证书,依然不行最…

drupal建站案例_Drupal8 快速建站教程

由于种种原因,项目需要使用 Drupal8 来进行开发,Drupal8 的中文资料又非常少,于是将这段时间的学习收获分享出来,若有错误欢迎指正。本教程针对初次接触 Drupal8 的开发者,并不涉及二次开发(因为我也不大会 )##Drupal …

php网站缩略图,如何使用PHP对网站进行缩略图截图?[关闭]

有很多开源项目可以生成网站截图和缩略图,可以从PHP调用。例如phantomjs、webkit2ping等这些项目的最大问题是,它们基于较旧的浏览器技术,并且在呈现许多站点时存在问题,尤其是在过去几个月/年中使用WebFonts、FlexBox、SVG和各种其他添加到HTML5和CSS规范中的站点。我尝试过一…

什么是最好的网站学习如何使用Linux系统?

其实不是很难使用Linux,由于有关系统的大量技术信息加上现在网上的信息鱼龙混杂,各种信息让我们分不清是真是假,您可能会被愚弄,但毕竟真的不是那么困难。为了给你一个开始,我会说一些你可能知道的一些事情&#xff0c…

Linux四条指令创建Hpptd网站服务

Linux快速创建Hpptd网站前言一、Hpptd网站服务是什么?二、操作步骤1.下载Hpptd2.开启Hpptd服务3.设置开机自启4.关闭防火墙5.创建一个网站主页6.查看主页ip,并本地浏览器访问7.修改主页内容插入图片前言 Linux模拟创建一个网站,通过本实例初…

华软html5本地存储作业,课程作业2-大一纯网页课程作业——垃圾分类网站设计...

“垃圾分类网”项目文档源码地址: https://download.csdn.net/download/weixin_41446786/12358553一、 项目概述1、 设计思路本次网页设计主题是以宣传垃圾分类为主题,因此秉着平台需要突出宣传垃圾分类、提高垃圾分类意识的想法,网页共分6个…

最新的seo需要学html,css还是php,有没有做seo的同学进来看下_html/css_WEB-ITnose

个人求租如果不是是哪些原因呢网站是 成都信息网,除了首页,其余发布信息的每页都提示静态页面上使用动态参数,会造成spider多次和重复抓取 .请高手帮忙解决下,谢谢回复讨论(解决方案)在HTML页面里有很多{$mymps_global.SiteUrl}这种全局变量的…

怎么向后台提交对象_网站文章不被收录该怎么解决

什么情况?网站更新的文章都不被收录了?自己原创的文章不收录了怎么办?网站收录是优化基础,连文章都不搜录,怎么优化关键词,怎么提升网站权重,如果一两篇没有收录,这个是正常的,如果更新的文章一直都不收…

动态网站的技术路线_旅游胜地不改变也是坐以待毙 旅游行业网站建设解决方案...

互联网的突飞猛进,改变了人们的生活习惯及消费方式,旅游行业的发展竞争已经从以前的线下转移到了线上,品牌宣传从人口相传发展到铺天盖地的互联网广告,即便是旅游胜地,如果不做出改变同样会遭受时代的抛弃,…

shell 分割文本_SEO优化实战小技巧:利用Shell语句一行命令快速分割文本

SEO优化实战小技巧系列开更咯!shell语句命令:split -2000 2jzs.txt 2jzs-2000 //分割条件行数2jzs.txt //文件名称2jzs //分割后前缀应用背景:适合文本快速分割!不知道大家是否有使用过类似百度主动推送工具适合用来大量推送url给…

seo从入门到精通_「SEO如何学习」SEO优化这个技术到底怎么学,多久才能入门?...

【SEO如何学习】SEO优化这个技术到底怎么学,多久才能入门?SEO如何学习,建议如下:1、理论知识学习主要是对这一行中某些术语的理解。例如,刚才提到了搜索引擎优化这个词。理论研究用词太多,如优化、外部链、网站排名、流…

php字体鼠标移上触发,html中在鼠标指针移动到元素上时触发的事件属性onmouseover...

实例当鼠标指针移动到图像上时执行一段 JavaScript:浏览器支持IEFirefoxChromeSafariOpera所有主流浏览器都支持 onmouseover 属性。定义和用法onmouseover 属性在鼠标指针移动到元素上时触发。注释:onmouseover 属性不适用以下元素:、、、、…

spring mvc 实现网站登录与非登录的控制

在我们的网站平台上,主要有两类页面,一类是非登录也能查看的页面,另一类是登录后才能查看的页面 通过使用 spring拦截器来实现,当用户没有登录时访问需要登录的页面时自动实现跳转至登录页 1、添加接口用于拦截器与控制器交互数据…

jsp为什么错误页面不跳转_为什么网站页面不收录,如何提高收录率?

当百度蜘蛛去爬行互联网上的每一个URL的时候,它是有一套完整的过滤机制,言外之意,并非是每一个URL都是编入百度的索引库。 它会根据目标网站的信任度,以及内容质量,外部链接结构等诸多因素去衡量。 那么,为…

敏捷开发流程的8个步骤_8个步骤快速构建WooCommerce B2C商城网站

建立一个完全自主可控的外贸B2C商城网站,有多种技术方案路径,其中wordpress woocommerce插件是目前最成熟,也是最易入手的方案,全球有超过20%的网上商城采用以上技术方案来构建。如果我们已经熟练使用wordpress了,那么…

模板建站和开发网站区别_定制营销型网站建设与模板建站相比的优势特点讲解...

网络时代,独立网站的开发与选择模板实现建站的企业数量不相上下,根据实际需求,选择建站的方式各不相同,但是这两者之间有何区别呢?随着互联网之风盛行,越来越多的中小企业建立了企业网站,做起了…

miniui页面移动的时候透明_移动端快速建站,你应该关注的5个指标

在2019年,移动端建站是一个重要的侧重点,这是不争的事实,随着PC端流量向移动端大量转移的过程中,它变得格外的重要。 因此,在做移动端快速建站的时候,我们除了要保持站点的搜索引擎友好,还需要重…

python爬虫实验(静态网页内容爬取):爬取中国工程院网站相关信息

实验内容:爬取中国工程院网页上的院士信息,把每位院士的简介保存为本地文本文件,把每位院士的照片保存为本地图片,文本文件和图片文件都以院士的姓名为主文件名。 #-*- codeing utf-8 -*- #Time :2021/5/20 8:54 #Author :Onion #File :Exp…

Vaadin在SEO中的冒险

TL; DR :Vaadin过去几乎对SEO不友好。 不再有新的伏尔加河图书馆。 为页面添加书签 书签与www本身一样古老。 能够保存URL是网站ADN的一部分。 关于网络应用程序,这有所不同。 例如,在电子商务Web应用程序中,为特定产品添加书签确…