2021年会成为元宇宙元年吗

什么是元宇宙?2021年会成为元宇宙元年吗?

2021年进入倒数第两个月,元宇宙大爆炸一般的出现在了科技圈和大众的视野里。腾讯、字节跳动等纷纷进入相关领域,国外脸书、微软、英伟达等科技公司也均已入局。随着Facebook正式改名为Meta(中文译为元宇宙)彻底地引燃了大众的兴趣。

Facebook表示这算是重大品牌重塑计划的一部分。该公司表示,它将更好地“涵盖”它所做的事情,因为它将其影响范围从社交媒体扩展到虚拟现实 (VR) 等领域。创始人马克扎克伯格10月29日表示,Facebook 将其公司名称更改为 Meta,有效地将 Facebook 的同名服务降级为该公司的子公司之一,与 Instagram 和 WhatsApp 并列,而不是总体品牌。

而这其中最引人注意的就是扎克伯格提到的Metaversa-元宇宙,科技巨头竞相入局“元宇宙”究竟有何种魅力?元宇宙到底是什么?而这个世界为什么需要元宇宙?0

“元宇宙”源起

“那是超元域(元宇宙)的百老汇,超元域的香榭丽舍大道。它是一条灯火辉煌的主干道,反射在阿弘的目镜中,能够被眼睛看到,能够被缩小,被倒转。它并不真正存在,但此时,那里正有数百万人在街上往来穿行。”这是第一次元宇宙的概念在元宇宙前传《雪崩》中被准确描写出来,这一年是1992年。

至此,多元宇宙的世界逐渐打开。那么为什么说2021年会是元宇宙的元年呢,首先来说在2021年前的铺垫条件。2020年全世界面临了诸多挑战,这也成为了人类社会虚拟化的临界点。

首先疫情加速社会虚拟化,新冠疫情隔离政策下,全社会上网时长大幅增长,“宅经济”快速发展。因此,线上生活由原先短时期的例外状态成为了常态,由现实世界的补充变成了与现实世界的平行世界。

并且,大家对于元宇宙的认知发生转变,虚拟的并不是虚假的,更不是无关紧要的。其次,生活发生迁移,线上与线下打通,人类的现实生活开始大规模向虚拟世界迁移,人类成为现实与数字的两栖物种。

这些变化都为元宇宙的爆发蓄力,2021之所以可以被称为元宇宙元年,是因为‘元宇宙“呈现超出想象的爆发力,其背后是元宇宙要素的“群聚效应”近似1995年互联网所经历的“群聚效应”。

什么是元宇宙?

在现下的认知来看,元宇宙可能看起来像是虚拟现实 (VR) 的增强版——但有些人认为元宇宙可能是互联网的未来。事实上,人们相信元宇宙对和VR 之间的差距可能就像现代智能手机和1980 年代第一批笨重的手机之间的差距一样。

元宇宙的英文是metaverse, meta(超越)+universe(宇宙),元宇宙可以说是一个平行于现实世界,又独立于现实世界的虚拟空间,是映射现实世界的在线虚拟世界,是越来越真实的数字虚拟世界。

在 metaverse 中,用户可以使用耳机进入连接各种数字环境的虚拟世界,而不是在计算机上。与目前主要用于游戏的 VR 不同,这个虚拟世界几乎可以用于任何事情——工作、娱乐、音乐、约会、电影旅行——或者只是闲逛。

元宇宙是整合多种新技术而产生的新型虚实相融的互联网应用和社会形态,它基于扩展现实技术提供沉浸式体验,基于数字孪生技术生成现实世界的镜像,基于区块链技术搭建经济体系、社交体系、身份系统上密切融合,并且允许每个用户进行内容生产和世界编辑。这是清华大学给出的暂时定义,因为元宇宙仍是一个不断发展,演变的概念,不同的参与者仍在以自己的方式不断丰富着它的含义。

通俗来讲,想象你住在《动物森友会》里的一个岛上,每天打工做任务,并且出售自己设计的家具和服装,用挣来的钱叫了一份外卖,还买了一个虚拟艺人演唱会的票。在演唱会你认识了几个朋友,并相约在线下见面。这是不是有一种多元宇宙融合的感觉。

但元宇宙不等于是电子游戏也不是虚拟世界,元宇宙的公式是元宇宙等于虚拟世界乘以现实世界。扎克伯格强调“元宇宙”是一个长期产品,现在暂时还不存在。“你可以把元宇宙看作是一个具身性的互联网。在这里你不再浏览内容——而是在内容中。”扎克伯格说。

为什么需要元宇宙?

当前互联网产业的主要瓶颈是内卷化的平台形态。在内容载体、传播方式、交互方式、参与感和互动性上长期缺乏突破,导致“没有发展的增长”。元宇宙是打破桎梏,走出内卷的一个契机。

技术渴望新产品的出现,例如AI、XR、数字孪生、5G、大数据等对多种新型技术的统摄性想象。资本也一直都在寻找新出口,现实叠加虚拟打开广阔商业潜能。用户也一直期待着刺激的新体验,他们能够感受全新的交互体验从而摆脱“拇指党”。例如,可编辑开放世界,体感世界,真沉浸式社交,创造性游玩。还记得斯皮尔伯格2018年的电影《头号玩家》?这部“老”电影,就为我们展示了元宇宙的部分基础玩法。

Facebook早在2021年6月5日收购Unit2 Games,拥有了类似Roblox+Fortnite的元宇宙游戏平台《Crayta》。除了Facebook全世界入股元宇宙的公司还有Roblox、Epic Games,微软,字节跳动和腾讯。

现阶段,中国在元宇宙方面属于闷声干大事的阶段,能够看到腾讯,字节跳动和网易都先后在该领域有所涉猎,尤其是财大气粗的腾讯已经有了一套完善的元宇宙资本布局。

以腾讯为例,其元宇宙布局宏大,可无线缝合我们社交、生活、消费的各种平台,跨越了AR、VR、音频、互联网和物理世界。更重要的是,腾讯在其中每个环节,都具备了相当的竞争力,算得上是国内竞逐元宇宙企业中的全能型选手。

当然,入场元宇宙的正确通道是投资而并非完全拥有,对于本身就具备去中心化特征的元宇宙来说,把筹码分散在平台和内容创造者之间,而不是试图建立一个中央平台,是更合理的选择。

国内巨头们对元宇宙如此热衷,是可以理解的。仅从搜索热度来看,国人对次的热情和关注即可见一斑:4月18日,元宇宙相关关键词的搜索热度达到峰值,这可能这和4月13日Epic Games获得10亿美元投资和4月20日游戏引擎研发商代码乾坤(号称:中国版Roblox)获字节跳动近1亿人民币的战略投资有关;在10月末,由于Facebook更名事件,相关关键词的搜索量又被重新炒热。从世界范围来看,国内搜索引擎对于“元宇宙”的搜索量最高,也侧面证明了其在中国市场的无限潜力。

而元宇宙的竞争才刚刚开始,元宇宙的发展必然是一个长期的过程,从长远来看,元宇宙必将开启科技的新浪潮,开启一个全真互联网新时代。

元宇宙是一种互联网吗?它会取代互联网吗?

马克扎克伯格将元宇宙描述为“一个实体互联网”,基本上是互联网的升级版本,人们可以在其中拥有“在 2D 应用程序或网页上不一定能获得的不同体验”。

我们可以将元宇宙想象成“一种在线游乐场,用户可以和朋友一起玩像 Epic 的‘堡垒之夜’这样的多人游戏,然后通过 Netflix 观看电影。” 如果我们正在想象一个可以观看视频、与朋友一起玩游戏和购买东西的地方,那么它最终会看起来很像互联网。

元宇宙的关键承诺是“存在”。元宇宙的主要好处之一应该是“临场感”——一种你在身体上与地方和人物接触的感觉,而不是通过窗户观察他们。

如果你想看看一套衣服在你身上的样子,虚拟更衣室是有意义的。不过,它不一定需要整个虚拟商店,只是一种在平面和空间体验之间切换的简单方法。一个很好的比较点可能是移动互联网,它看到了大量基于应用程序的服务补充甚至取代了传统网站——但也没有让基于桌面的选项过时。所以,同样,元宇宙会是一个新的流量入口,但是不会取代互联网。

Go语言Web编程有哪些优势?

1.Go语言之前编程的痛点

(1)为什么会设计Go语言?

我们先来了解一下Go的作者和主要核心开发者们:Robert Griesemer, Rob Pike 和 Ken Thompson。设计Go语言是为了解决当时Google开发遇到的以下这些问题:

  • 大量的C++代码,同时又引入了Java和Python;
  • 成千上万的工程师;
  • 数以万计行的代码;
  • 分布式的编译系统;
  • 数百万的服务器;

其主要有以下几个方面的痛点:

  • 编译慢;
  • 失控的依赖;
  • 每个工程师只是用了一个语言里面的一部分;
  • 程序难以维护(可读性差、文档不清晰等);
  • 更新的花费越来越长;
  • 交叉编译困难;

所以,他们当时设计Go的目标是为了消除各种缓慢和笨重、改进各种低效和扩展性。Go是由那些开发大型系统的人设计的,同时也是为了这些人服务的;它是为了解决工程上的问题,不是为了研究语言设计;它还是为了让我们的编程变得更舒适和方便。

但是结合Google当时内部的一些现实情况,如很多工程师都是C系的,所以新设计的语言一定要易学习,最好是C-like的语言;因为有太多的分布式系统、太多的开发者,所以新的语言一定要可以Scale,这个包括开发、工程师、代码、部署和依赖;20年没有出新的语言了,所以新设计的语言必须是现代化的(例如内置GC)等情况,他们觉得要实现这个目标就需要Go成为一个大家都认可的语言。

最后根据实战经验,他们向着目标设计了Go这个语言,其主要的特色有:

  • 没有继承的OO;
  • 强一致类型;
  • Interface但是不需要显示申明(Duck Type);
  • Function 和Method;
  • 没有异常处理(Error is value);
  • 基于首字母的可访问特性;
  • 不用的Import或者变量引起编译错误;
  • 完整而卓越的标准库包;

Go发布之后,很多公司特别是云计算公司开始用Go重构他们的基础架构,很多都是直接采用Go进行了开发。这几年火到爆的Docker、Kubernetes就是采用Go开发的。我们来看看目前为止采用Go的一些国内外公司,国外的如Google、Docker、Apple、Cloud Foundry、CloudFlare、Couchbase、CoreOS、Dropbox、MongoDB、AWS等公司,国内的如阿里云CDN、百度、小米、七牛云、PingCAP、华为、金山软件、猎豹移动、饿了么等公司。

(2)Go主要应用的系统。

上面那些基本上就是Go的历史背景和设计初衷,那么目前Go主要应用于哪些系统呢?

目前Go主要应用在下面这些系统:

①Web服务器编程,以前你如果使用C或者C++做的那些事情,用Go来做很合适,例如处理日志、数据打包、虚拟机处理、文件系统等。

②容器开发:Docker,使开发高效且可预测;Kubernetes,也称为K8s,是一个开源系统,用于自动化容器化应用程序的部署,扩展和管理。

③微服务、分布式系统、数据库代理器等,例如gRPC、Etcd等。

④Web网络编程,这一块目前应用最广,包括Web应用、API应用、下载应用,而且Go内置的net/http包足够强大,基本就涵盖了常用的Web应用方法了。

⑤数据库,前一段时间Google开发的Groupcache,Couchbase的部分组建,Tidb,Cockroachdb,Influxdb等。

⑥云平台,目前国外很多云平台在采用Go开发,CloudFoundy的部分组建,前VMare的技术总监自己出来搞的Apcera云平台。

(3)为什么选择使用Go语言?

国内很多云创业公司都会选择把Go作为首要语言,例如七牛云等。为什么会选择Go呢?与其他语言的应用相比,它有什么优点呢?

①简单,学习曲线平稳;

它包含了类C语法、GC内置和工程工具。这一点非常重要,因为Go语言容易学习,所以一个普通的大学生花一个星期就能写出来可以上手的、高性能的应用。在国内大家都追求快,这也是为什么国内Go流行的原因之一。

②极致效率;

Go拥有接近C的运行效率和接近PHP的开发效率,这就很有利的支撑了上面大家追求快速的需求。

③Google公司开发;

之所以说Go出身名门,是因为我们知道Go语言出自Google公司,这个公司在业界的知名度和实力自然不用多说。Google公司聚集了一批牛人,在各种编程语言称雄争霸的局面下推出新的编程语言,自然有它的战略考虑。而且从Go语言的发展态势来看,Google对它这个新的宠儿还是很看重的,Go自然有一个良好的发展前途。我们看看Go语言的主要创造者,血统纯正这点就可见端倪了。

④自由高效:组合的思想、无侵入式的接口;

Go语言可以说是开发效率和运行效率二者的完美融合,天生的并发编程支持。Go语言支持当前所有的编程范式,包括过程式编程、面向对象编程以及函数式编程。程序员们可以各取所需、自由组合、想怎么玩就怎么玩。

⑤强大的标准库;

这包括互联网应用、系统编程和网络编程。Go里面的标准库基本上已经是非常稳定了,特别是我这里提到的三个,网络层、系统层的库非常实用。

⑥部署方便:二进制文件、Copy部署;

这一点是很多人选择Go的最大理由,因为部署太方便了,所以现在也有很多人用Go开发运维程序。

⑦简单而强大的并发处理能力;

它包含了降低心智的并发和简易的数据同步,我觉得这是Go最大的特色。之所以写正确的并发、容错和可扩展的程序如此之难,是因为我们用了错误的工具和错误的抽象,Go可以说这一块做的相当简单。

⑧规范,不会写出垃圾代码;

Go拥有强大的编译检查、严格的编码规范和完整的软件生命周期工具,具有很强的稳定性,稳定压倒一切。那么为什么Go相比于其他程序会更稳定呢?这是因为Go提供了软件生命周期(开发、测试、部署、维护等等)的各个环节的工具,如go tool、gofmt、go test等。

2.Go语言Web编程的优势

通过上面的介绍我们知道,Go语言Web开发领域有得天独厚的优势!Go语言被称为云计算时代的C语言。我们知道,在Web开发的世界里,敏捷才是王道。谁能使用更少的费用和资源来更快地完成网站和网络应用,谁就获得更多的竞争优势。此外,对于编程者而言,不仅希望快速完成Web开发,对可用性和用户体验的要求也很高。

这需要开发更多的功能和高级编程语言来开发网站功能,例如Go语言。本文着重介绍了Go语言 Web开发的好处,并将Go语言 Web编程与其他高度流行的语言进行了比较,例如 Python。

让我们看一下Python的特性,以及为什么它能够成为Web开发领域的主流。

(1)Python vs Go语言:优点与缺点

Python自20世纪80年代就出现了,但直到近些年它才变得流行。事实上,Python受欢迎程度已经连续6年增长,现在是最常用的编程语言之一。在Web开发中,它主要用于后端编程,但它也用于前端任务。最终,Python在Web开发场景中占优势的主要原因是它帮助开发人员解决敏捷性的迫切需求。它易于学习,简单易用,有助于快速完成复杂的目标。Python是灵活的,语法也很简单。

Go语言现在被认为是Python、Java等传统语言的替代品,关于Go语言 Web开发与Python、Java的讨论不胜枚举。

虽然简单是Python的主要优点,但也有一些缺点。许多开发人员发现,虽然对于一些简单和基本的开发Python很实用,但如果要构建更复杂的系统和定制模块,使用Python可能会变得很复杂。此外,经常出现令人沮丧的编码错误。在HekReNo.com文章中“从Python转到Go语言的五个原因”Tigran Bayburstyan说:“Python是一个伟大而有趣的语言,但有时你会遇到异常,因为你试图使用一个变量作为一个整数,但事实证明它是一个字符串。”Go会让你在编译时消除这些问题。

总而言之,在许多用例中,Go语言 Web开发已被证明比使用Python更快地完成同一类任务。最终,Go语言是为那些想要完成任务的人快速而有效地完成任务,而不需要进入编程语言的微妙之处。

(2)Python vs Go语言:哪一个更好?

那么,Go语言是否会在不久的将来取代Python呢?开发者社区正在讨论两种编程语言的优缺点。如果您正在寻找用于Web编程、移动开发、微服务和ERP系统的强大工具,我们相信,您应该切换到Go语言的原因是:

①简单性;

如果Python是迄今为止最容易学习的编程语言,那么Go语言甚至更简单。学习Go语言要比学习Python快得多。一些开发人员声称Go语言几乎和JavaScript一样简单。

②高级编译能力;

Go语言是一个编译执行语言,它能够直接将代码编译成可执行的二级制机器码。Go语言静态地将所有依赖库和模块编译到到一个二进制文件中。你不再需要在服务器上安装任何依赖项——你所需要做的就是上传一个编译的文件,你的应用就可以简单的运行了。

③并发和性能;

Go语言并发模型能够确保更高的性能(性能甚至 2x、3x的提升)。大多数现代编程语言都支持并发,但是Go语言采用了更节省资源的方法。相对于Python,Go语言提供更高性能的goroutine模型来支持多线程,goroutine能够更加节省CPU和内存资源。所以Go语言更有助于降低成本和资源。

④框架和库的本地支持;

Go语言 sdk包提供了功能丰富的API,因此不用过多的依赖第三方库就能很好的使用Go语言。当然,如果你需要的话,你可以下载很多工具和框架(Go语言周围的社区已经很强大很完善了),但是常用的API都已经内置到Go语言核心库中了。这样可以加快整个Go语言 Web开发过程的速度,并且使您无需寻找任何第三方依赖。

⑤顶级IDE和调试;

Go语言创作者在创建具有先进调试工具和插件的最先进的集成开发环境方面做得很好。这可以说是编程中最关键的方面,因为IDE会严重阻碍或加速开发过程。今天,当敏捷性给软件公司带来竞争优势时,伟大的IDE和调试工具是一个非常重要的优势。

⑥清晰的语法;

另一个有助于Go语言 Web编程出色的简单性和易用性的是其清晰的语法,它包含零不必要的组件。Go是建立在实用的头脑中的:而不是必须深入研究语言结构,开发者现在可以自由地专注于开发本身。

总结,通过上面的对比我们发现,Go语言在各方面基本都已经超过Python,尽管Python社区仍然超过Gophers,Go倡导者的数量每天都在增加,相信Go语言接近并超过Python、Java是大势所趋。

有了充分的理由,Go语言证明了“先进”不再等同于复杂、缓慢和昂贵。我们可以在不牺牲质量和大量投资的情况下实现显著的开发速度。此外,如果你决定使用Go,那么已经有许多Go语言网络框架供你选择。

3.Go语言Web开发常用框架

Go语言被称为云计算时代的C语言,它以其独特的优势逐渐被越来越多的公司所关注和使用。为了充分利用Go语言的Web开发优势,有必要熟悉一下Go语言的Web框架。

(1)Beego (http://beego.me/)

Beego是一个完全的MVC框架,你可以使用你的Go语言专业技术构建你的web应用程序。Beego 是用Go 语言开发的高效的 HTTP 框架,可以用来快速开发 API、Web应用及后端服务等各种应用。Beego是一个 RESTful 的框架,主要设计灵感来源于 tornado、sinatra 和 flask 这三个框架。它还结合了 Go语言自身的一些特性(接口、结构体嵌入等)。

1)Beego 架构简介。

Beego 是基于多个独立模块构建的,是一个高度解耦的框架。最初在设计 Beego 时就考虑到了功能模块化,用户即使不适用 Beego 的HTTP逻辑,也可以独立使用这些模块(例如可以使用cache模块来处理的缓存逻辑,使用日志模块来记录操作信息,使用config模块来解析各种格式的文件)。

Beego各模块的功能,以及使用方法会在接下来逐一介绍。

2)Beego 执行逻辑。

既然Beego 是基于模块构建的,那么它的执行逻辑是怎么样的呢?Beego 是一个典型的MVC框架,其执行逻辑如下图所示。

图1

执行逻辑可以拆分以下几段:

①main文件监听启动端口接收请求。

②请求经过路由和参数过滤功能被转发给绑定URL的控制器处理。

③控制器(Controller)调用辅助工具包、Model、Session管理、日志处理、缓存处理模块进行相应的业务处理。其中,模型(Model)通过ORM直接操作数据库。

④业务处理完成,返回响应或视图(View)给请求方。

(2)Gin(https://gin-gonic.github.io/gin/)

Gin是一个基于 Go 语言编写的 Web 框架。Gin框架拥有很好的性能,其借助高性能的httprouter,运行速度得到了极大提升。目前的 Gin 框架是1.x版本。

①安装。

下载并安装Gin:

$ go get -u github.com/gin-gonic/gin

②第一个Gin示例。

安装完成后,让我们开启Gin之旅。

package main

import (

    “github.com/gin-gonic/gin”

)

func main() {

    // 创建一个默认的路由引擎

    r := gin.Default()

    // GET:请求方式;/hello:请求的路径

    // 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数

    r.GET(“/hello”, func(c *gin.Context) {

        // c.JSON:返回JSON格式的数据

        c.JSON(200, gin.H{

            “message”: “Hello world!”,

        })

    })

    // 启动HTTP服务,默认在0.0.0.0:8080启动服务

    r.Run()

}

运行以上代码,然后使用浏览器打开127.0.0.1:8080/hello即可看到一串JSON字符串。

(3)Revel(http://revel.github.io/)

Revel能让Go语言的web开发如虎添翼,大大提高你的开发效率。

(4)Martini (http://martini.codegangsta.io)

Martini是一个受到Sinatra (一个Ruby 框架)启发而开发的Go语言web框架。使用Martini,你可以快速写你的模块化应用或者服务。

(5)Traffic(https://github.com/pilu/traffic)

Traffic 同样也是受Sinatra的regexp/pattern 互斥思想启发下开发的web框架。它是一个小框架,并且是轻量型的。因此,它具有很好的灵活性,扩展性。

(6)Goji(https://goji.io/)

Goji是一个轻量型的web框架,具有简约性和灵活性的多路路由请求特性,正如它所标榜自己的那样。

4.Go语言Web编程书籍

(1)《Go Web编程实战派从入门到精通》廖显东 著 2021年4月出版

采用Go最新版本编写,拒绝纯理论,直接实战!实战!实战!重要的事说3遍!本书聚焦Go Web开发领域,对Go Web知识进行全面深入地讲解。货真价实高质量精品图书!本书有如下特色:

①一线技术,突出实战;

本书以实战为核心,贯穿整本书。所有代码采用Go最新版本编写。

②精雕细琢,阅读性强;

全书的语言经过多次打磨,力求精确。同时注重阅读体验,让没有任何基础的读者也可以很轻松的读懂书中的知识。

③零基础入门,循序渐进,实现快速从菜鸟向实战派高手迈进;

本书以Go入门级程序员为主要对象,初、中、高级程序员都可以从书中学到干货。先从Go的基础学起,然后学习Go核心的技术,再学Go的高级应用,然后再进行项目实战,最后介绍应用程序的Docker实战部署。全书从最基础的知识讲解一步一步到最核心的B2C电子商务系实战开发,真正帮助读者从基础入门向开发高手迈进。

④极客思维,极致效率;

本书以极客思维、深入Go语言底层进行探究,帮助读者了解背后原理。全书言简意赅,以帮助读者提升开发效率为导向,同时尽可能帮助读者缩短阅读本书的时间。

⑤由易到难,重难点标注并重点解析;

本书编排由易到难,内容基本覆盖Go Web的主流前沿技术。同时对重难点进行重点讲解,对易错点和注意点进行了提示说明,帮助读者克服学习过程中的困难。

⑥突出实战,快速突击;

本书的实例代码是绝大部分都是来自于最新的企业实战项目。对于购买本书的读者,所有的源代码均可以通过网上下载,直接下载即可运行,让读者通过实践来加深理解。

⑦实战方案,可直接二次开发进行实战部署;

本书全书以实战为主,所有的示例代码,拿来即可运行。特别是第9章,购买本书的读者可以直接获得B2C电子商务系统的全部源代码。可以直接作为电商项目进行二次开发,用于读者的项目。读者购买本书不仅可以学习本书的各种知识,也相当于购买一个最新版的Go语言电商系统解决方案及项目源码。

(2)《Go Web编程》【新加坡】郑兆雄 著 2017年12月出版

该书的优点:基础入门,出版较早。

该书的缺点:

①太过基础,并且没有循序渐进,需要一定Go语言基础。

②开发例子是国内已经过时的论坛系统,不符合当前市场需求。

③开发的部署是以Heroku、GoogleAppEngine、DigitalOcean等云平台上部署Go Web应用,不符合国内的具体情况。

④缺乏系统深入的知识体系。

⑤缺乏高级实战教程及国内大型架构经验的介绍,无法满足读者向高级迈进。

人工智能深度学习总结

人工智能实战主要由以下四部分组成:

  • 先从分工谈起
  • 深度学习基础概念
  • 深度学习进阶概念
  • 深度学习实战

先从分工谈起

机器学习工程师在公司中到底是一个怎样的存在?他的职责到底是什么?估计有不少开发者会有这样的疑惑。从机器学习的书中,从招工简章中,似乎都隐隐约约告诉大家:数学、算法……。然而,这个岗位中不是还有“工程师”三个字么?

image.png

先来看看机器学习工程师要构建的系统是什么样子:

image.png

诚如图中展示的,机器学习相关功能虽然是系统的核心,但它只是整个系统的一小部分,其他的功能(如安全权限、数据导入和处理、可视化等)并没有超出大多数普通开发者需要掌握的知识范畴。并且由上图,也很自然地导出了机器学习工程师需要掌握的知识技能:

从图中可以看出,“机器学习工程师”相当于“掌握了机器学习技能的工程师”:

  • 工程能力依旧是其首要掌握的技能,它需要利用这些技能在产品环境中实现模型:包括模型的训练和推理两个阶段。
  • 机器学习知识不可或缺。这一点毫无疑问,假如对机器学习一无所知,所谓的实现自然无从谈起。但就理论知识而言,并不要求他达到算法工程师或数据分析师那样的水准。两者的分工更多的是首先由后者在实验环境中把模型设计出来,然后再由机器学习工程师去实现即可。
  • 对于业务知识的理解同样也是必须。因为机器学习不是空中楼阁,它必须要解决实际问题。如果对问题域完全一抹黑,很难想象实际系统能工作得很好。这里很典型的一个例子就是对于输入数据的处理,要是缺乏业务问题的背景知识,可能连数据的含义都弄不明白,更不要说如何处理了。同样的,虽说机器学习工程师要求具备业务领域知识,但他不必成为领域专家,对于更深层次的问题,可以由后者提供专业帮助。

那么,作为普通工程师来讲,我们该如何开始去学习机器学习(或者说本文更关注的深度学习)呢?结合个人的学习过程,我建议将整个学习过程分为 3 个阶段。

阶段 1:找感觉

这一步是最难的。

假如你还没有被书中那些数学公式和推导过程吓到,仍然坚定地要入手专研机器学习,请给自己一个大大的 👍🏻!不过,在这个阶段,我建议不要把理论学习作为重点。相反地,我更建议大家先去找上一两个当前流行好用的机器学习框架,直接照着例子开始动手实验。这便是所谓的“找感觉”。

这个阶段的主要目的就是破除大多数开发者对于机器学习的神秘感和畏惧感,形成对机器学习的感性认识,进而找到自己的“感觉”。若是一开始就去专研机器学习理论,除非你天赋异禀,很容易陷入一大堆数学公式爬不出来,学了大半年却讲不清楚机器学习到底是什么,遇到实际问题也不知如何入手,其挫败感可想而知。

以上也是我自己的亲身体会。因此,为了避免上面的问题,我推荐大家一开始先不要去看理论背景很强的机器学习书籍,尤其是那些大学教材和算法类数据。而是先从一些实战类的书籍看起,以例子驱动的方式去学习。在形成对机器学习的感性认识之后,再去着手学习理论,以增强作为机器学习工程师的内涵。

同时,对于深度学习而言,为了更清楚地了解其背后的实现机理,不妨自己动手去实现一个简单地深度网络。

阶段 2:找队友

一个人学习总是孤单的,假如有同伴的话,情况就不一样了。不仅可以相互扶持和鼓励,也有助于形成一种竞争和合作的良好氛围。对于那些已经找到感觉的开发者,我建议可以去Kaggle找同好,顺便练练级,赚点外快(竞赛奖金)。作为国际知名的竞赛平台,Kaggle 的排名含金量非常高,并且还有什么比起在实战中提高能力更好的途径吗?

在练级的过程中,你很快就会发现自己的知识短板,此时,只需跟随你的感觉,缺啥补啥就好了。

一段时间之后,你已经具备了一定的理论知识和实战技巧。这时,不妨可以考虑将自己的所学和经验向社区(如自己的博客和 TFUG)输出,树立自己的个人品牌并检验自己的实际掌握程度。因为“你只能讲清楚你理解的东西”,假如你发现自己下笔困难或无法清晰地向他人讲清楚“机器学习是何物”,那么很可能是哪些地方还没有完全弄明白。

阶段 3:找方案

机器学习是门实践性很强的课程,不断有新的理论、方法和模型诞生。为了保持跟得上技术的发展,你还得去抽时间去搜集模型、论文和案例,好让这些能成为你未来武器库中的一部分。并且,作为开发者,为了更好地发挥你所用框架的潜力,对于框架的源码和机理需要做必要的了解,这样才有可能去进一步扩展和定制。

最后,在结束本节之前,我再来说说需要避免的学习误区:

  • 光看不练,这一点前面已经强调,不结合工具实战练习是无法掌握机器学习的。
  • 克服心理障碍,这一点非常重要。学习者千万不要被书中的数学公式吓到,随着技术的发展和时代的进步,一些平民化的工具已经出现了,典型如:Keras 和 TF。利用它们,实现一个深度网络并没有看上去的那么难。并且,参照上文所述,就机器学习工程师而言,数学的要求并不太高。
  • 神秘化 ML 类项目,这一类问题也同样需要避免。从前文可以看出,ML 项目就是用 ML 模型解决实际问题,与一般项目没有本质区别。
  • 模型自己写,这一点对于初学者来讲非常容易忽视。站在工程角度,预训练模型本质上就是类库,只要其解决的问题和我们当前正在解决的问题相当或相似,就可以考虑直接使用。
  • 不重视数据,这一点同样也是初学者容易忽视的问题。过于强调模型的重要性,这是初学者的通病,但倘若结合机器学习项目中的模型训练过程,就不难发现数据的重要性。假如把数据比做原材料,模型比做机器设备,很显然只有好数据才能得出好结果。

深度学习基础概念

在了解深度学习的基础概念之前,先来看看典型的机器学习分类:

  • 以解决问题的类型来分
    • 回归,预测连续值,如根据历史温度数据预测未来几天的温度值。
    • 分类,预测离散值,如 mnist 数据集的 0~9 的手写识别。
  • 以学习方式来分
    • 有监督学习,先通过标记好的数据集完成训练,然后使用训练好的模型完成预测。典型如上面手写识别和预测温度值的例子。
    • 无监督学习,训练数据没有标记,模型自行发现其中的关联。典型如聚类算法,输入一组数据,自动将数据完成分组。

讲深度学习,不能不提神经网络,而且既然有“深”自然就有“浅”,下图给出了两种网络的示意图,左边为“浅层”网络,右边为“深度”网络。

image.png

就大的结构而言,它们都由:输入层、隐藏层和输出层构成。所谓深浅之别,只不过在于:浅层网络的层数不会超过 3 层,反之则为深度网络。这个区分是不是有些意外地令人失望 😄?

根据神经网络理论,只需要输入输出两层全连接网络就可以“创造出世界”(只要不断加入神经元就好了,即胖网络),但为何又要引入“隐藏层”,并且后来又不断加深“隐藏层”的深度呢?原因就在于:

  • 计算的可行性。理论上虽然可行,但由于硬件的原因,两层神经网络不可能无限加宽,否则将导致整个计算无法进行。
  • 关联的灵活性。全连接可以视为暴力穷举的做法,而隐藏层则相当于对数据有一个二次加工的过程,利用不同的隐藏层结构和激活函数可以灵活地加工出所需的中间数据集,有助于改善数据之间的关联性。而层层递增的隐藏层其实就相当于数据不断精加工的过程,关联性一步步得到增强。

同时,“深”网络相比起“胖”网络,在计算上可以用现在的硬件得以实现。

了解完深度网络的概念,让我们再来看看深度网络的使用。整个使用过程分为两个阶段:

  • 训练阶段,这个阶段的目标就是得到一定网络模型下的权重矩阵和偏置矩阵。不妨将建模类比为“列方程组”,训练类比于“解方程组”的过程。
    1. 构建网络
    2. 训练数据数值化处理,即深度网络只能处理数值类型数据,对于文本、图像、音频等数据,需要借助一定的手段,将其转化成能被网络接受的数值表示。
    3. 前向传播计算偏差
    4. 后向传播调整权重和偏置
    5. 重复 3/4 直到偏差不再缩小
    6. 保存模型
  • 推理阶段
    1. 加载模型(网络 + 权重/偏置)
    2. 转化输入符合训练时的输入格式
    3. 调用模型预测结果
    4. 解释结果,深度网络预测的结果也是数值类型,对于这个类型的含义,应该由使用者来进行解释。

请注意,推理阶段的模型来源可以有两种:

  • 方式 1:自己造轮子。即自己建模型,自己训练,自己使用。
  • 方式 2:使用现成的预训练模型。还记得前面说的吗,模型本质上与类库没有什么区别,只要预训练模型要解决的问题和手头待解决问题属于一类问题,就可以考虑直接使用现成模型。

其中:

  • x 和 y 分别表示输入和输出。
  • W 和 b 分别表示网络的权重和偏置,也就是所谓的“知识”。
  • f 代表激活函数,它决定了神经元的输出,这也是为何它被称为“激活函数”的原因。
    • 如图所示,输出 Y = f((sum(x*w) + b),

然而,单个神经元并不能翻起多大风浪,必须将神经元按层组织,形成网络之后才有实际意义。对于网络中的层而言:

  • 层 = 一组神经元,可视为单个神经元的矩阵化
    • 输入(1 x m)、输出(1 x n)、W(m x n)、偏置(1 x n)
    • 输出 = 激活函数(输入 * 权重) + 偏置
  • 每个层可被视为数据的加工处理函数,其输出为中间数据集,并作为下一层的输入。
    • 层的组合形成了数据处理流水线,即网络结构。
  • 对于层的激活函数
    • 隐藏层常用:sigmod、relu、tanh 等
    • 输出层根据问题类型决定:
      • 回归问题(任意值),无
      • 回归问题(0~1),sigmod
      • 分类问题(n 选 1),softmax
      • 分类问题(2 选 1),sigmod

组建好了网络,自然需要对它进行训练。就训练过程而言,整个流程如下:

其中:

  • 损失函数,评判预测和实际的差距,不同类型的问题使用不同类型的损失函数,典型的有:
    • 回归问题:MSE
    • 分类问题:Cross Entropy
  • 优化器,调整权重和偏置,常见 SGD。

前面也说过,深度网络对于输入数据的要求是数值性,然而现实世界中的数据则不止于此,因此就必然涉及到对于输入数据预处理的问题。常见的预处理有:

  • 标准化和归一化
    • 避免量纲影响。比如有的输入量是 10000,有的又是 0.1,这样的数据直接进入网络训练往往效果不如人意。通过一定手段,将输入量统一到一个量纲之后,既简化了计算,同时也避免了量纲问题带来的训练效果不佳。
    • 简化计算
  • 缺失值
    • 众数(数据集中最常见的值)或均值
  • 字符标签
    • one-hot 编码,即有多少相异标签就引入多少个特征,处理时将相应特征对应的列标记为 1,如下图。可能有朋友会问,为什么不简单地用数字(如 1、2、3)来指代不同的分类呢?原因很简单:这样会带来隐含的关联关系。假设 red = 1,yellow = 2,这就隐含着 yellow > red,这显然是荒谬的。而采用 one-hot 编码则能有效避免这种问题。
  • 文本数据:先 token 化,再转换成数值向量。
  • 图像数据
    • 预处理,如灰度化
    • 动态生成图片,扩充数据集,如扭曲、颠倒等
  • 正则化,防止过拟合,关于过拟合的问题,参见下文。

此外,关于训练,你还需要了解下面的常见术语,有时候你会在一些书籍和代码中看到它们:

  • 1 pass,一条数据处理完毕
  • 1 iteration,一批样本数据中所有数据处理完毕
  • 1 epoch,所有批次所有样本数据处理完毕

同时,在训练模型时,你需要考虑:

  • batch 大小,它决定了权重更新时机,越小,则更新越频繁
  • epoch 大小,它决定了训练次数,随着一轮一轮的训练,你会发现预测的差异在不断变小。最后,每一 epoch 之间差异改善并不大,这时就可以考虑停止训练了。

在实际过程中,常常将训练数据集划分为 3 部分:

  • 训练集,训练数据
  • 验证集,训练过程中验证训练效果没有下降
  • 测试集,训练完毕后检验训练效果

验证集存在的价值在于提早发现问题,终止无用的训练。因为训练本身是一个耗时耗力的过程,若训练了半天发现效果很差,那么还不如提早结束。利用验证集,可以很早就发现过拟合现象。

这三类数据集的比例通常如下(训练集、验证集、测试集):

  • 60:20:20
  • 70:10:20
  • 超大数据集:95:2:3

为了最大化利用这些来之不易的数据的价值,在实际部署之前,往往会将整个训练数据集合为一体,输入模型,完成对模型上线前的最终训练。

在本节最后,看看深度模型的部署方式。还记得吗,在本文中一直强调“模型即类库”。那么在部署方式上,其实也非常类似:

  • 方式 1:API。如:Rest 框架 + 模型
  • 方式 2:嵌入应用。如:MVC 框架 + 模型

在实际过程中需要注意:

  • 输入数据需符合模型预期
  • 模型输出需应用自己解释。这一点很显然,因为模型的输出是一个数值类型,对于它的具体含义解释必然是跟应用相关的。
  • 训练和使用可以是不同框架,如在 DL4J 环境中使用 TF 训练的模型

深度学习进阶概念

上一节中提到,将层视为不同的数据处理器,不同类型的层完成不同类型的工作。Keras 框架中典型的层有:

  • Convolution,抽取局部特征
  • Pooling,跟 Convolution 配合使用,典型 MaxPooling、AvgPooling 等
  • DropOut,随机去掉一定比例神经元
  • Dense,全连接
  • Embedding,词向量转换
  • Recurrent,处理序列数据,如文本和时序
  • Merge,合并多个输入层数据

因此,构建网络的过程就是使用不同层进行组合的过程,本质上等同于使用编程语句构建程序。同时需要注意,一般情况下,输出层都为 Dense。这里可以理解为,通过隐藏层不断加强数据的关联性和缩小范围之后,最终通过暴力穷举解决问题。

前文已经出现了“过拟合”一词,现在我们来讨论一下关于拟合的两个概念:欠拟合和过拟合。

  • 欠拟合,可视为网络学习能力低下,无法从训练数据学到任何东西。导致它出现的典型原因主要是网络结构存在缺陷,比如“小网络,大数据”。修复它的办法也很直接,改进网络,比如:
    • 增加每层神经元
    • 增加网络层级
  • 过拟合,可类比于“死记硬背”,对训练数据几乎能达到 100%,对测试数据,则表现不如人意。典型原因“大网络、小数据”。主要的改进对策:
    • 数据正则化:L1 和 L2,本质上是通过对成本函数引入惩罚机制来限制网络的自由度。
    • DropOut,在训练过程中随机丢弃层中一定比例的神经元,不让它们参与计算。

如果将网络类比为“内存”就很容易理解上面的两个概念。前一种相当于内存不足以容纳全部数据,从而无法学习;后者则相当于内存远超数据量,导致对现有数据通过死记硬背的方式就能蒙混过关。一般在实际工作中,欠拟合很容易发现,因此这里也不做过多强调。过拟合,则是从业者经常与之斗争的“顽疾”。这也是为何要引入“验证集”,提前发现过拟合现象,及时终止训练,避免浪费时间。

另一个需要了解的进阶概念就是“鞍点”,有一定高数背景的读者应该对于通过“求导求极值”的做法并不陌生,深度学习中的优化方法本质上仍是如此。由上图可见,在鞍点附近,导数(斜率)几乎为 0。这样造成的负面后果就是:

  • 学习性能低下,迟迟无法收敛
  • 无法获得理想结果,这里只是得到了局部的优化值,因此鞍点本身又有“局部极值”的叫法。

为了应对这种情况,一般采用:

  • 使用有动量的优化算法,如 Adagrad、RMSProp。套用一般深度学习书中将优化过程比喻成“小球”沿梯度方向滚动的说法,所谓动量可视为让小球具有一定的加速度,快速通过鞍点的过程。
  • 自适应学习率,因为学习率决定了权重调整的大小

最后一个需要掌握的进阶概念就是“超参数”,它本质上相当于模型的“元数据”,无法通过训练过程得到。典型的超参数有:

  • 神经元个数
  • 网络层数
  • 激活函数
  • 优化器
  • 学习率
  • Epoch 数
  • Batch 大小

并且超参数的调优过程是一个不断实验的体力活,一般的做法有:人工、Grid Search、随机优化、贝叶斯优化。随着技术的发展,目前也有类似 AutoML 的的端到端解决方案出现,相信未来会更美好。

深度学习实战

关于深度学习用到的框架和工具,以及实验环境的搭建,如下代码大家可以对于使用 Keras 编写深度网络有一个感性认识:

from keras import layers
from keras import models

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

总结

讲到这里,我希望这次分享能够消除大家对于深度学习的神秘感和恐慌,真正动手去做做练习,在实践中成长。最后总结一下:

  • 深度学习入门并没有想象中那么困难
  • 借助 Keras 等工具可以快速实现网络模型
  • 可以看出,深度网络的调参是个脏活累活。但 AutoML 和 AutoKeras 这类工具的出现,意味着自动调参时代即将到来。
  • 建议学习方法:Make Your Hands Dirty

参考

Go语言 同步sync包简介

Sync包简介

1. 什么是Sync包?

Package sync provides basic synchronization primitives such as mutual exclusion locks. Other than the Once and WaitGroup types, most are intended for use by low-level library routines. Higher-level synchronization is better done via channels and communication.

Values containing the types defined in this package should not be copied.

这句话大意是说:
Sync包同步提供基本的同步原语,如互斥锁。 除了Once和WaitGroup类型之外,大多数类型都是供低级库例程使用的。 通过Channel和沟通可以更好地完成更高级别的同步。并且此包中的值在使用过后不要拷贝。

从描述中可以看到的是,Go语言并不推荐这个包中的大多数并发控制方法,但还是提供了相关方法,主要原因是Go语言中提倡以共享内存的方式来通信:

不要以共享内存的方式来通信,作为替代,我们应该以通信的手段来共享内存

共享内存的方式使得多线程中的通信变得简单,但是在并发的安全性控制上将变得异常繁琐。
正确性不是我们唯一想要的,我们想要的还有系统的可伸缩性,以及可理解性,我觉得这点非常重要,比如现在广泛使用的Raft算法。

2. 包中的Type

包中主要有: Locker, Cond, Map, Mutex, Once, Pool,
RWMutex, WaitGroup

type Locker interface {
        Lock()
        Unlock()
}
type Cond struct {
        // L is held while observing or changing the condition
        L Locker
}

3. 什么是锁,为什么需要锁?

锁是sync包中的核心,他主要有两个方法,加锁和解锁。
在单线程运行的时候程序是顺序执行的,程序对数据的访问也是:
读取 => 一顿操作(加减乘除之类的) => 写回原地址
但是一旦程序中进行了并发编程,也就是说,某一个函数可能同时被不同的线程执行的时候,以时间为维度会发生以下情况:

可以看到的是,A地址的数字被执行了两次自增,若A=5,我们在执行完成后预期的A值是7,但是在这种情况下我们得到的A却是6,bug了~
还有很多类似的并发错误,所以才有锁的引入。若是我们在线程2读取A的值的时候对A进行加锁,让线程2等待,线程1执行完成之后在执行线程2,这样就能够保证数据的正确性。但是正确性不是我们唯一想要的。

4.写更优雅的代码

在很多语言中我们经常为了保证数据安全正确,会在并发的时候对数据加锁

Lock()
doSomething()
Unlock()

Go语言在此包中也提供了相关的锁,但是标明了”most are intended for use by low-level library routines” 所以我这里只对 Once and WaitGroup types做简述。

5.Once 对象

Once 是一个可以被多次调用但是只执行一次,若每次调用Do时传入参数f不同,但是只有第一个才会被执行。

func (o *Once) Do(f func())


    var once sync.Once
    onceBody := func() {
        fmt.Println("Only once")
    }
    done := make(chan bool)
    for i := 0; i < 10; i++ {
        go func() {
            once.Do(onceBody)
            done <- true
        }()
    }
    for i := 0; i < 10; i++ {
        <-done
    }

如果你执行这段代码会发现,虽然调用了10次,但是只执行了1次。BTW:这个东西可以用来写单例。

6. WaitGroup

func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()

wait group 用来等待一组goroutines的结束,在主Goroutine里声明,并且设置要等待的goroutine的个数,每个goroutine执行完成之后调用 Done,最后在主Goroutines 里Wait即可。下面是个官方的例子:

var wg sync.WaitGroup
var urls = []string{
        "http://www.golang.org/",
        "http://www.google.com/",
        "http://www.somestupidname.com/",
}
for _, url := range urls {
        // Increment the WaitGroup counter.
        wg.Add(1)
        // Launch a goroutine to fetch the URL.
        go func(url string) {
                // Decrement the counter when the goroutine completes.
                defer wg.Done()
                // Fetch the URL.
                http.Get(url)
        }(url)
}
// Wait for all HTTP fetches to complete.
wg.Wait()

7. 总结

Go语言中高级的并发可以通过channel来实现,这是Go语言所倡导的,但是Go语言也提供了锁等先关操作。

恭喜老詹,35岁的詹姆斯,依旧狂奔在追逐乔丹的路上!


在去年7月,这支湖人刚刚组建完成时,有几个人愿意相信他们能够成功地走到最后呢?在那些质疑者的眼里,这是一支危机四伏,随时都有可能被推翻重来的球队。
他们认为湖人的管理团队太过混乱,缺少成为一支伟大球队所需要的稳定性。
他们虽然认可球队的教练组豪华万分,但却认为由三名前任NBA主帅搭建起来的核心构架,更有可能发生的是暗流涌动,各自心怀鬼胎。
他们认为湖人的阵容缺乏深度,虽有詹眉打底,但却没有稳定可靠的第三点,远不及其他的竞争对手。
他们认为詹姆斯老了,即将年满35周岁的他,很有可能再次被伤病给盯上,一个无法全力输出的詹姆斯,必定无法统帅一支成功的球队。
但,他们错了。
在走完了这段长达一年多的漫长旅途后,洛杉矶湖人,时隔十年再度踏上了世界之巅。至少在这个夜晚,那些质疑者们可以闭嘴了。
湖人这一次的成功,属于队里的每一个人。

他们低估了湖人的教练组。
事后来看,这是一个出色的团队,初期的试探,赛间的调整,甚至有魄力在赛中对阵容的搭配进行临时的修整。他们的准备之充分,应对手法之深厚,显然超过了外界最初的预期。
他们低估了湖人的角色球员与年轻人,能在一年的时间里所收获的成长。
这些失意的,不被人看好的,无人问津的球员,在洛杉矶重新收获了信任,他们打出了强硬的防守表现,留下了无数闪耀的瞬间。

他们低估了安东尼-戴维斯的价值。
在来到这之前,他是众人嘴中调侃的“美国之子”,现役最著名的“荣誉摇摆人”,名大于实的代表人物。
但在来到这之后,他用行动证明了自己的价值,证明了为什么他是这个时代最好最全能的内线球员。他不仅构建起了球队的防守体系,更是他们在进攻端最锋利的刺刃,因为浓眉的存在,让洛杉矶人拥有了更多破解防守与应对进攻的办法,他是球队能够登顶的重要功臣与关键之一。

当然,被他们所低估的,还有勒布朗-詹姆斯的领导力与决心。
的确,他35岁了,跑的没有以前那么快了,跳的没有年轻时那么高了,身体也会受到一些伤病的困扰了,可他追逐伟大的心,依旧炽热。
“我努力奋斗的所有动力,都是为了追逐那个曾经在芝加哥打球的幽灵。”
4年前,刚刚率领着克利夫兰骑士打破52年魔咒,逆天登顶的勒布朗,在接受时任《体育画报》记者的詹金斯采访时,说出了这句话。而在时隔4年之后今天,他依旧在用行动践行着自己曾经许下的诺言。
每当詹姆斯赢得一次总冠军,关于他能否超越乔丹这个议题,就会重新开始被人热议。对此,我也听过不少的“狠话”,总结着来讲,大概意思就是:“从从前,到现在,乃至将来,‘乔丹是至高无上的篮球之神’这件事,永远都不会发生改变。”
我当然也跟大多数人一样,非常认同乔丹在篮球世界里如象征图腾一般的地位。他所取得的荣誉是无上的,他所做到过的那些事情是极富传奇色彩的,他配得上任何人为他送上的任何赞美,哪怕距离他完全退役已过去近20年之久,迈克尔-乔丹这个名字也依旧还在影响着这项运动,正如我们每个人在最初遇见篮球时所知道的第一个名字,永远属于他一样。
但是,仅就我个人而言,我并不相信所谓“真理的永恒”。
乔丹之所以被称为篮球之神,绝非靠的一手虚无的赞美,而是因为那一连串的数字实在太过耀眼——10届得分王,3届抢断王,1个DPOY,5个MVP,6次夺得总冠军,6次拿下FMVP,10个年度一阵,9个年度一防,外加一票多到数不清,哪哪都有他的数据纪录——他的伟大,无需吹捧,也经得起任何比较。
但这并不意味着,当这些数字开始受到别人挑战时,他依旧能靠着所谓“神性”,永远高枕无忧地坐在那个位子上。
虽然詹姆斯目前还不足以威胁到乔丹,但显然,他已经拉近了自己与少年偶像的距离。只是偏见与固有看法的存在,让人或多或少地低估了眼前人所创造的那些传奇成就。

比如有的人会用总决赛胜率来贬低詹姆斯。
的确,40%的总决赛胜率在“乔丹不打第七场”和“乔丹从未输过总决赛”面前,显得有些脆弱不堪。
但又有多少人忽略了,那可是长达10年的总决赛征战啊,更何况我们在评判一件事物的好与坏时,真的该用如此一刀切的方式么?
2007年,四年级的新星勒布朗-詹姆斯独自率领着克利夫兰骑士,史无前例地杀进了总决赛,然后败给了无论阵容,或是核心都要更加强大且老辣的圣安东尼奥马刺,这样的失败,难道不该被加上引号么;
2015年,乐福与欧文相继倒下,詹姆斯带着82万年薪的先发控卫,总价不过2120万美元的核心6人组,与勇士厮杀了6场最终抱憾收场的失败,也应该受人唾弃么;
2018年,欧文出走,球队中期地震式动荡,在经历了两轮大清洗之后,不仅丢掉了明星实力,也彻底失去了自己的阵容深度。整个季后赛,除詹姆斯能每场稳定贡献34分之外,仅乐福一人场均得分上双(14.9分),且命中率只有惨淡的39.2%。但就是在这种极端的情况下,他依旧扛着骑士踏上了总决赛的舞台。的确,在强悍的勇士面前,他一场都没赢,可单场51分拼到加时的神勇,你真的会觉得这是一种耻辱么?
我永远不会这么认为,我只会觉得这是詹姆斯将一支本不属于总决赛的球队,带到了那个位置上,我更愿意将其视作是个人的一次巨大成功。

再比如,有人认为詹姆斯的数据积累之所以如此骇人,纯粹只是因为他打的比较多而已。
但近在眼前的例子——金州勇士核心成员的遭遇——告诉我们:长时间高强度的征战,所能带来的东西绝非只有数据与荣誉,更有身体上的超负荷运转。
17年的职业生涯,有超过一半的时间,詹姆斯都打进了总决赛,他在季后赛的出场时间超过了1万分钟,排名NBA历史第一,几乎是现役排行老二的凯文-杜兰特的一倍之多。
他能够扛住如此长时间的高强度对抗,除了得益于他惊人的身体天赋之外,更重要的,是数年如一日的努力,是对饮食与生活的严苛管理,以及对自身身体的精心照料。
我们早已习惯了他展现在世人面前的优秀,却总是会忽略掉掩藏在这一切背后的血与泪。
打的比赛多,是努力付出之后的回报,更是能力的体现。这本该受到褒奖,但在詹姆斯身上,却无奈地成了另一种被人看低的方式。
这一切,真的就本该如此么?

我不想说詹姆斯未来必定还能取得怎样的荣誉,或是数据积累。毕竟谁都无法确定没有发生过的事。但在今天,在詹姆斯拿下自己职业生涯第4个总冠军和第4座总决赛MVP的奖杯后,他已经成为了NBA历史上仅次于迈克尔-乔丹的球员——在这之前,除了乔丹,没人拿到过超过3个FMVP——他理应获得足够的尊重。
就像他在捧起奖杯时说的那番话一样:“我想得到的,是尊重。”
在他出现在这之前,我们甚至没有见过一个哪怕能够接近乔丹的人,而在这之后,我们也无从确认,下一个拥有这等能力的天才,会在何年何月再次降临。
第17个赛季落幕,年近36岁的詹姆斯,依旧狂奔在这条追赶伟大幽灵的路上。他或许可以成功,又或许不能。但我想,当你有机会去见证这样一段跨越历史的较量时,期待与祝福,终归是要强过诋毁与谩骂的。
在这样一个时刻,在所有人在高呼着“湖人总冠军”的时候,我更想把我的心里话,送给这个时代最伟大的篮球运动员:
“恭喜你,勒布朗-詹姆斯,在经历了17年的漫长旅途之后,你终于获得了挑战神的资格。这个画面,是自你踏入联盟的第一天起,就被世人所寄予的期待。就现在,抬起头看看,上帝的神像就矗立在那座山峰之上,你能看见他,他不远了,去挑战他吧!”

kubectl 技巧总结

Kubectl 是 Kubernetes 最重要的命令行工具。在 Flant,我们会在 Wiki 和 Slack 上相互分享 Kubectl 的妙用(其实我们还有个搜索引擎,不过那就是另外一回事了)。多年以来,我们在 kubectl 方面积累了很多技巧,现在想要将其中的部分分享给社区。

我相信很多读者对这些命令都非常熟悉;然而我还是希望读者能够从本文中有所获益,进而提高生产力。

下列内容有的是来自我们的工程师,还有的是来自互联网。我们对后者也进行了测试,并且确认其有效性。

现在开始吧。

获取 Pod 和节点

  1. 我猜你知道如何获取 Kubernetes 集群中所有 Namespace 的 Pod——使用 --all-namepsaces 就可以。然而不少朋友还不知道,现在这一开关还有了 -A 的缩写。
  2. 如何查找非 running 状态的 Pod 呢? kubectl get pods -A --field-selector=status.phase!=Running | grep -v Complete顺便一说,--field-selector 是个值得深入一点的参数。
  3. 如何获取节点列表及其内存容量: kubectl get no -o json | \
    jq -r '.items | sort_by(.status.capacity.memory)[]|[.metadata.name,.status.capacity.memory]| @tsv'
  4. 获取节点列表,其中包含运行在每个节点上的 Pod 数量: kubectl get po -o json --all-namespaces | \
    jq '.items | group_by(.spec.nodeName) | map({"nodeName": .[0].spec.nodeName, "count": length}) | sort_by(.count)'
  5. 有时候 DaemonSet 因为某种原因没能在某个节点上启动。手动搜索会有点麻烦: $ ns=my-namespace
    $ pod_template=my-pod
    $ kubectl get node | grep -v \"$(kubectl -n ${ns} get pod --all-namespaces -o wide | fgrep ${pod_template} | awk '{print $8}' | xargs -n 1 echo -n "\|" | sed 's/[[:space:]]*//g')\"
  6. 使用 kubectl top 获取 Pod 列表并根据其消耗的 CPU 或 内存进行排序: # cpu
    $ kubectl top pods -A | sort --reverse --key 3 --numeric
    # memory
    $ kubectl top pods -A | sort --reverse --key 4 --numeric
  7. 获取 Pod 列表,并根据重启次数进行排序:kubectl get pods —sort-by=.status.containerStatuses[0].restartCount当然也可以使用 PodStatus 以及 ContainerStatus 的其它字段进行排序。

获取其它数据

  1. 运行 Ingress 时,经常要获取 Service 对象的 selector 字段,用来查找 Pod。过去要打开 Service 的清单才能完成这个任务,现在使用 -o wide 参数也可以: $ kubectl -n jaeger get svc -o wide
    NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
    jaeger-cassandra ClusterIP None <none> 9042/TCP 77d app=cassandracluster,cassandracluster=jaeger-cassandra,cluster=jaeger-cassandra
  2. 如何输出 Pod 的 requests 和 limits $ kubectl get pods -A -o=custom-columns='NAME:spec.containers[*].name,MEMREQ:spec.containers[*].resources.requests.memory,MEMLIM:spec.containers[*].resources.limits.memory,CPUREQ:spec.containers[*].resources.requests.cpu,CPULIM:spec.containers[*].resources.limits.cpu'
    NAME MEMREQ MEMLIM CPUREQ CPULIM
    coredns 70Mi 170Mi 100m <none>
    coredns 70Mi 170Mi 100m <none>
    ...
  3. kubectl run(以及 createapplypatch)命令有个厉害的参数 --dry-run,该参数让用户无需真正操作集群就能观察集群的行为,如果配合 -o yaml,就能输出命令对应的 YAML: $ kubectl run test --image=grafana/grafana --dry-run -o yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    creationTimestamp: null
    labels:
    run: test
    name: test
    spec:
    replicas: 1
    selector:
    matchLabels:
    run: test
    简单的把输出内容保存到文件,删除无用字段就可以使用了。1.18 开始 kubectl run 生成的是 Pod 而非 Deployment。
  4. 获取指定资源的描述清单: kubectl explain hpa
    KIND: HorizontalPodAutoscaler
    VERSION: autoscaling/v1
    DESCRIPTION:
    configuration of a horizontal pod autoscaler.
    FIELDS:
    apiVersion <string>
    ...

网络

  1. 获取集群节点的内部 IP: $ kubectl get nodes -o json | jq -r '.items[].status.addresses[]? | select (.type == "InternalIP") | .address' | \
    paste -sd "\n" -
    9.134.14.252
  2. 获取所有的 Service 对象以及其 nodePort $ kubectl get -A svc -o json | jq -r '.items[] | [.metadata.name,([.spec.ports[].nodePort | tostring ] | join("|"))]| @tsv'

    kubernetes null
    ...
  3. 在排除 CNI(例如 Flannel)故障的时候,经常会需要检查路由来识别故障 Pod。Pod 子网在这里非常有用: $ kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' | tr " " "\n" fix-doc-azure-container-registry-config ✭
    10.120.0.0/24
    10.120.1.0/24
    10.120.2.0/24

日志

  1. 使用可读的时间格式输出日志: $ kubectl logs -f fluentbit-gke-qq9w9 -c fluentbit --timestamps
    2020-09-10T13:10:49.822321364Z Fluent Bit v1.3.11
    2020-09-10T13:10:49.822373900Z Copyright (C) Treasure Data
    2020-09-10T13:10:49.822379743Z
    2020-09-10T13:10:49.822383264Z [2020/09/10 13:10:49] [ info] Configuration:
  2. 只输出尾部日志: kubectl logs -f fluentbit-gke-qq9w9 -c fluentbit --tail=10
    [2020/09/10 13:10:49] [ info] ___________
    [2020/09/10 13:10:49] [ info] filters:
    [2020/09/10 13:10:49] [ info] parser.0
    ...
  3. 输出一个 Pod 中所有容器的日志:kubectl -n my-namespace logs -f my-pod —all-containers
  4. 使用标签选择器输出多个 Pod 的日志:kubectl -n my-namespace logs -f -l app=nginx
  5. 获取“前一个”容器的日志(例如崩溃的情况):kubectl -n my-namespace logs my-pod —previous

其它

  1. 把 Secret 复制到其它命名空间: kubectl get secrets -o json --namespace namespace-old | \
    jq '.items[].metadata.namespace = "namespace-new"' | \
    kubectl create-f -
  2. 下面两个命令可以生成一个用于测试的自签发证书: openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=grafana.mysite.ru/O=MyOrganization"
    kubectl -n myapp create secret tls selfsecret --key tls.key --cert tls.crt

相关链接

本文没什么结论,但是可以提供一个小列表,其中包含本文相关的有用链接。

  1. Kubernetes 官方文档:https://kubernetes.io/docs/reference/kubectl/cheatsheet/
  2. Linux Academy 的入门参考:https://linuxacademy.com/blog/containers/kubernetes-cheat-sheet/
  3. Blue Matador 分类整理的命令列表:https://www.bluematador.com/learn/kubectl-cheatsheet
  4. 另一个命令指南,部分内容和本文重复:https://gist.github.com/pydevops/0efd399befd960b5eb18d40adb68ef83
  5. kubectl 别名搜集:https://github.com/ahmetb/kubectl-aliases

人工智能发展简史

1.人工智能的诞生(20世纪40~50年代)

①1950年:图灵测试诞生。

1950年,著名的图灵测试诞生,按照“人工智能之父”艾伦·图灵的定义:如果一台机器能够与人类展开对话(通过电传设备)而不能被辨别出其机器身份,那么称这台机器具有智能。同一年,图灵还预言会创造出具有真正智能的机器的可能性。

②1954年:第一台可编程机器人诞生。

1954年美国人乔治·戴沃尔设计了世界上第一台可编程机器人。

③1956年:人工智能诞生。

1956年夏天,美国达特茅斯学院举行了历史上第一次人工智能研讨会,被认为是人工智能诞生的标志。会上,麦卡锡首次提出了“人工智能”这个概念,纽厄尔和西蒙则展示了编写的逻辑理论机器。

2.人工智能的黄金时代(20世纪50~70年代)

①1966年~1972年:首台人工智能机器人Shakey诞生。

1966年~1972年期间,美国斯坦福国际研究所研制出机器人Shakey,这是首台采用人工智能的移动机器人。

②1966年:世界上第一个聊天机器人ELIZA发布。

美国麻省理工学院(MIT)的魏泽鲍姆发布了世界上第一个聊天机器人ELIZA。ELIZA的智能之处在于她能通过脚本理解简单的自然语言,并能产生类似人类的互动。

③1968年:计算机鼠标发明。

1968年12月9日,美国加州斯坦福研究所的道格·恩格勒巴特发明计算机鼠标,构想出了超文本链接概念,它在几十年后成了现代互联网的根基。

3.人工智能的低谷(20世纪70~80年代)

20世纪70年代初,人工智能遭遇了瓶颈。当时的计算机有限的内存和处理速度不足以解决任何实际的人工智能问题。要求程序对这个世界具有儿童水平的认识,研究者们很快发现这个要求太高了:1970年没人能够做出如此巨大的数据库,也没人知道一个程序怎样才能学到如此丰富的信息。由于缺乏进展,对人工智能提供资助的机构(如英国政府、美国国防部高级研究计划局和美国国家科学委员会)对无方向的人工智能研究逐渐停止了资助。美国国家科学委员会(NRC)在拨款二千万美元后停止资助。

4.人工智能的繁荣期(1980年~1987年)

①1981年:日本研发人工智能计算机。

1981年,日本经济产业省拨款8.5亿美元用以研发第五代计算机项目,在当时被叫做人工智能计算机。随后,英国、美国纷纷响应,开始向信息技术领域的研究提供大量资金。

②1984年:启动Cyc(大百科全书)项目。

在美国人道格拉斯·莱纳特的带领下,启动了Cyc项目,其目标是使人工智能的应用能够以类似人类推理的方式工作。

③1986年:3D打印机问世。

美国发明家查尔斯·赫尔制造出人类历史上首个3D打印机。

5.人工智能的冬天(1987年~1993年)

“AI(人工智能)之冬”一词由经历过1974年经费削减的研究者们创造出来。他们注意到了对专家系统的狂热追捧,预计不久后人们将转向失望。事实被他们不幸言中,专家系统的实用性仅仅局限于某些特定情景。到了上世纪80年代晚期,美国国防部高级研究计划局(DARPA)的新任领导认为人工智能并非“下一个浪潮”,拨款将倾向于那些看起来更容易出成果的项目。

6.人工智能真正的春天(1993年至今)

①1997年:电脑深蓝战胜国际象棋世界冠军。

1997年5月11日,IBM公司的电脑“深蓝”战胜国际象棋世界冠军卡斯帕罗夫,成为首个在标准比赛时限内击败国际象棋世界冠军的电脑系统。

②2011年:开发出使用自然语言回答问题的人工智能程序。

2011年,Watson(沃森)作为IBM公司开发的使用自然语言回答问题的人工智能程序参加美国智力问答节目,打败两位人类冠军,赢得了100万美元的奖金。

③2012年:Spaun诞生。

加拿大神经学家团队创造了一个具备简单认知能力、有250万个模拟“神经元”的虚拟大脑,命名为“Spaun”,并通过了最基本的智商测试。

④2013年:深度学习算法被广泛运用在产品开发中。

Facebook人工智能实验室成立,探索深度学习领域,借此为Facebook用户提供更智能化的产品体验;Google收购了语音和图像识别公司DNNResearch,推广深度学习平台;百度创立了深度学习研究院等。

⑤2015年:人工智能突破之年。

Google开源了利用大量数据直接就能训练计算机来完成任务的第二代机器学习平台Tensor Flow;剑桥大学建立人工智能研究所等。

⑥2016年:AlphaGo战胜围棋世界冠军李世石。

2016年3月15日,Google人工智能AlphaGo与围棋世界冠军李世石的人机大战最后一场落下了帷幕。人机大战第五场经过长达5个小时的搏杀,最终李世石与AlphaGo总比分定格在1比4,以李世石认输结束。这一次的人机对弈让人工智能正式被世人所熟知,整个人工智能市场也像是被引燃了导火线,开始了新一轮爆发。

高并发架构实践

高并发经常会发生在有大活跃用户量,用户高聚集的业务场景中,如:秒杀活动,定时领取红包等。
为了让业务可以流畅的运行并且给用户一个好的交互体验,我们需要根据业务场景预估达到的并发量等因素,来设计适合自己业务场景的高并发处理方案。

在电商相关产品开发的这些年,我有幸的遇到了并发下的各种坑,这一路摸爬滚打过来有着不少的血泪史,这里进行的总结,作为自己的归档记录,同时分享给大家。

服务器架构

业务从发展的初期到逐渐成熟,服务器架构也是从相对单一到集群,再到分布式服务。 
一个可以支持高并发的服务少不了好的服务器架构,需要有均衡负载,数据库需要主从集群,nosql缓存需要主从集群,静态文件需要上传cdn,这些都是能让业务程序流畅运行的强大后盾。

服务器这块多是需要运维人员来配合搭建,具体我就不多说了,点到为止。
大致需要用到的服务器架构如下:

  • 服务器
    • 均衡负载(如:nginx,阿里云SLB)
    • 资源监控
    • 分布式
  • 数据库
    • 主从分离,集群
    • DBA 表优化,索引优化,等
    • 分布式
  • nosql
    • redis
      • 主从分离,集群
    • mongodb
      • 主从分离,集群
    • memcache
      • 主从分离,集群
  • cdn
    • html
    • css
    • js
    • image

并发测试

高并发相关的业务,需要进行并发的测试,通过大量的数据分析评估出整个架构可以支撑的并发量。

测试高并发可以使用第三方服务器或者自己测试服务器,利用测试工具进行并发请求测试,分析测试数据得到可以支撑并发数量的评估,这个可以作为一个预警参考,俗话说知己自彼百战不殆。

第三方服务:

  • 阿里云性能测试

并发测试工具:

  • Apache JMeter
  • Visual Studio性能负载测试
  • Microsoft Web Application Stress Tool

实战方案

通用方案

日用户流量大,但是比较分散,偶尔会有用户高聚的情况;

通用

场景: 用户签到,用户中心,用户订单,等
服务器架构图: 

说明:

场景中的这些业务基本是用户进入APP后会操作到的,除了活动日(618,双11,等),这些业务的用户量都不会高聚集,同时这些业务相关的表都是大数据表,业务多是查询操作,所以我们需要减少用户直接命中DB的查询;优先查询缓存,如果缓存不存在,再进行DB查询,将查询结果缓存起来。

更新用户相关缓存需要分布式存储,比如使用用户ID进行hash分组,把用户分布到不同的缓存中,这样一个缓存集合的总量不会很大,不会影响查询效率。

方案如:

  • 用户签到获取积分
    • 计算出用户分布的key,redis hash中查找用户今日签到信息
    • 如果查询到签到信息,返回签到信息
    • 如果没有查询到,DB查询今日是否签到过,如果有签到过,就把签到信息同步redis缓存。
    • 如果DB中也没有查询到今日的签到记录,就进行签到逻辑,操作DB添加今日签到记录,添加签到积分(这整个DB操作是一个事务)
    • 缓存签到信息到redis,返回签到信息
    • 注意这里会有并发情况下的逻辑问题,如:一天签到多次,发放多次积分给用户。
  • 用户订单
    • 这里我们只缓存用户第一页的订单信息,一页40条数据,用户一般也只会看第一页的订单数据
    • 用户访问订单列表,如果是第一页读缓存,如果不是读DB
    • 计算出用户分布的key,redis hash中查找用户订单信息
    • 如果查询到用户订单信息,返回订单信息
    • 如果不存在就进行DB查询第一页的订单数据,然后缓存redis,返回订单信息
  • 用户中心
    • 计算出用户分布的key,redis hash中查找用户订单信息
    • 如果查询到用户信息,返回用户信息
    • 如果不存在进行用户DB查询,然后缓存redis,返回用户信息
  • 其他业务
    • 上面例子多是针对用户存储缓存,如果是公用的缓存数据需要注意一些问题,如下
    • 注意公用的缓存数据需要考虑并发下的可能会导致大量命中DB查询,可以使用管理后台更新缓存,或者DB查询的锁住操作。
    • 我的博文[大话Redis进阶]对更新缓存问题和推荐方案的分享。

以上例子是一个相对简单的高并发架构,并发量不是很高的情况可以很好的支撑,但是随着业务的壮大,用户并发量增加,我们的架构也会进行不断的优化和演变,比如对业务进行服务化,每个服务有自己的并发架构,自己的均衡服务器,分布式数据库,nosql主从集群,如:用户服务、订单服务;

消息队列

秒杀、秒抢等活动业务,用户在瞬间涌入产生高并发请求

消息队列

场景:定时领取红包,等
服务器架构图:

说明:

场景中的定时领取是一个高并发的业务,像秒杀活动用户会在到点的时间涌入,DB瞬间就接受到一记暴击,hold不住就会宕机,然后影响整个业务;

像这种不是只有查询的操作并且会有高并发的插入或者更新数据的业务,前面提到的通用方案就无法支撑,并发的时候都是直接命中DB;

设计这块业务的时候就会使用消息队列的,可以将参与用户的信息添加到消息队列中,然后再写个多线程程序去消耗队列,给队列中的用户发放红包;

方案如:

  • 定时领取红包
    • 一般习惯使用 redis的 list
    • 当用户参与活动,将用户参与信息push到队列中
    • 然后写个多线程程序去pop数据,进行发放红包的业务
    • 这样可以支持高并发下的用户可以正常的参与活动,并且避免数据库服务器宕机的危险

附加: 
通过消息队列可以做很多的服务。 
如:定时短信发送服务,使用sset(sorted set),发送时间戳作为排序依据,短信数据队列根据时间升序,然后写个程序定时循环去读取sset队列中的第一条,当前时间是否超过发送时间,如果超过就进行短信发送。

一级缓存

高并发请求连接缓存服务器超出服务器能够接收的请求连接量,部分用户出现建立连接超时无法读取到数据的问题;

因此需要有个方案当高并发时候时候可以减少命中缓存服务器;

这时候就出现了一级缓存的方案,一级缓存就是使用站点服务器缓存去存储数据,注意只存储部分请求量大的数据,并且缓存的数据量要控制,不能过分的使用站点服务器的内存而影响了站点应用程序的正常运行,一级缓存需要设置秒单位的过期时间,具体时间根据业务场景设定,目的是当有高并发请求的时候可以让数据的获取命中到一级缓存,而不用连接缓存nosql数据服务器,减少nosql数据服务器的压力

比如APP首屏商品数据接口,这些数据是公共的不会针对用户自定义,而且这些数据不会频繁的更新,像这种接口的请求量比较大就可以加入一级缓存;

通用

服务器架构图:

合理的规范和使用nosql缓存数据库,根据业务拆分缓存数据库的集群,这样基本可以很好支持业务,一级缓存毕竟是使用站点服务器缓存所以还是要善用。

静态化数据

高并发请求数据不变化的情况下如果可以不请求自己的服务器获取数据那就可以减少服务器的资源压力。

对于更新频繁度不高,并且数据允许短时间内的延迟,可以通过数据静态化成JSON,XML,HTML等数据文件上传CDN,在拉取数据的时候优先到CDN拉取,如果没有获取到数据再从缓存,数据库中获取,当管理人员操作后台编辑数据再重新生成静态文件上传同步到CDN,这样在高并发的时候可以使数据的获取命中在CDN服务器上。

CDN节点同步有一定的延迟性,所以找一个靠谱的CDN服务器商也很重要

其他方案

  • 对于更新频繁度不高的数据,APP,PC浏览器,可以缓存数据到本地,然后每次请求接口的时候上传当前缓存数据的版本号,服务端接收到版本号判断版本号与最新数据版本号是否一致,如果不一样就进行最新数据的查询并返回最新数据和最新版本号,如果一样就返回状态码告知数据已经是最新。减少服务器压力:资源、带宽

分层,分割,分布式

大型网站要很好支撑高并发,这是需要长期的规划设计 
在初期就需要把系统进行分层,在发展过程中把核心业务进行拆分成模块单元,根据需求进行分布式部署,可以进行独立团队维护开发。

  • 分层
    • 将系统在横向维度上切分成几个部分,每个部门负责一部分相对简单并比较单一的职责,然后通过上层对下层的依赖和调度组成一个完整的系统
    • 比如把电商系统分成:应用层,服务层,数据层。(具体分多少个层次根据自己的业务场景)
    • 应用层:网站首页,用户中心,商品中心,购物车,红包业务,活动中心等,负责具体业务和视图展示
    • 服务层:订单服务,用户管理服务,红包服务,商品服务等,为应用层提供服务支持
    • 数据层:关系数据库,nosql数据库 等,提供数据存储查询服务
    • 分层架构是逻辑上的,在物理部署上可以部署在同一台物理机器上,但是随着网站业务的发展,必然需要对已经分层的模块分离部署,分别部署在不同的服务器上,使网站可以支撑更多用户访问
  • 分割
    • 在纵向方面对业务进行切分,将一块相对复杂的业务分割成不同的模块单元
    • 包装成高内聚低耦合的模块不仅有助于软件的开发维护,也便于不同模块的分布式部署,提高网站的并发处理能力和功能扩展
    • 比如用户中心可以分割成:账户信息模块,订单模块,充值模块,提现模块,优惠券模块等
  • 分布式
    • 分布式应用和服务,将分层或者分割后的业务分布式部署,独立的应用服务器,数据库,缓存服务器
    • 当业务达到一定用户量的时候,再进行服务器均衡负载,数据库,缓存主从集群
    • 分布式静态资源,比如:静态资源上传cdn
    • 分布式计算,比如:使用hadoop进行大数据的分布式计算
    • 分布式数据和存储,比如:各分布节点根据哈希算法或其他算法分散存储数据
image

网站分层-图1来自网络

集群

对于用户访问集中的业务独立部署服务器,应用服务器,数据库,nosql数据库。 核心业务基本上需要搭建集群,即多台服务器部署相同的应用构成一个集群,通过负载均衡设备共同对外提供服务, 服务器集群能够为相同的服务提供更多的并发支持,因此当有更多的用户访问时,只需要向集群中加入新的机器即可, 另外可以实现当其中的某台服务器发生故障时,可以通过负载均衡的失效转移机制将请求转移至集群中其他的服务器上,因此可以提高系统的可用性

  • 应用服务器集群
    • nginx 反向代理
    • slb
    • … …
  • (关系/nosql)数据库集群
    • 主从分离,从库集群
image

通过反向代理均衡负载-图2来自网络

异步

在高并发业务中如果涉及到数据库操作,主要压力都是在数据库服务器上面,虽然使用主从分离,但是数据库操作都是在主库上操作,单台数据库服务器连接池允许的最大连接数量是有限的 
当连接数量达到最大值的时候,其他需要连接数据操作的请求就需要等待有空闲的连接,这样高并发的时候很多请求就会出现connection time out 的情况 
那么像这种高并发业务我们要如何设计开发方案可以降低数据库服务器的压力呢?

  • 如:
    • 自动弹窗签到,双11跨0点的时候并发请求签到接口
    • 双11抢红包活动
    • 双11订单入库
  • 设计考虑:
    • 逆向思维,压力在数据库,那业务接口就不进行数据库操作不就没压力了
    • 数据持久化是否允许延迟?
    • 如何让业务接口不直接操作DB,又可以让数据持久化?
  • 方案设计:
    • 像这种涉及数据库操作的高并发的业务,就要考虑使用异步了
    • 客户端发起接口请求,服务端快速响应,客户端展示结果给用户,数据库操作通过异步同步
    • 如何实现异步同步?
    • 使用消息队列,将入库的内容enqueue到消息队列中,业务接口快速响应给用户结果(可以温馨提示高峰期延迟到账)
    • 然后再写个独立程序从消息队列dequeue数据出来进行入库操作,入库成功后刷新用户相关缓存,如果入库失败记录日志,方便反馈查询和重新持久化
    • 这样一来数据库操作就只有一个程序(多线程)来完成,不会给数据带来压力
  • 补充:
    • 消息队列除了可以用在高并发业务,其他只要有相同需求的业务也是可以使用,如:短信发送中间件等
    • 高并发下异步持久化数据可能会影响用户的体验,可以通过可配置的方式,或者自动化监控资源消耗来切换时时或者使用异步,这样在正常流量的情况下可以使用时时操作数据库来提高用户体验
    • 异步同时也可以指编程上的异步函数,异步线程,在有的时候可以使用异步操作,把不需要等待结果的操作放到异步中,然后继续后面的操作,节省了等待的这部分操作的时间

缓存

高并发业务接口多数都是进行业务数据的查询,如:商品列表,商品信息,用户信息,红包信息等,这些数据都是不会经常变化,并且持久化在数据库中
高并发的情况下直接连接从库做查询操作,多台从库服务器也抗不住这么大量的连接请求数(前面说过,单台数据库服务器允许的最大连接数量是有限的)
那么我们在这种高并发的业务接口要如何设计呢?

  • 设计考虑:
    • 还是逆向思维,压力在数据库,那么我们就不进行数据库查询
    • 数据不经常变化,我们为啥要一直查询DB?
    • 数据不变化客户端为啥要向服务器请求返回一样的数据?
  • 方案设计:
    • 数据不经常变化,我们可以把数据进行缓存,缓存的方式有很多种,一般的:应用服务器直接Cache内存,主流的:存储在memcache、redis内存数据库
    • Cache是直接存储在应用服务器中,读取速度快,内存数据库服务器允许连接数可以支撑到很大,而且数据存储在内存,读取速度快,再加上主从集群,可以支撑很大的并发查询
    • 根据业务情景,使用配合客户端本地存,如果我们数据内容不经常变化,为啥要一直请求服务器获取相同数据,可以通过匹配数据版本号,如果版本号不一样接口重新查询缓存返回数据和版本号,如果一样则不查询数据直接响应
    • 这样不仅可以提高接口响应速度,也可以节约服务器带宽,虽然有些服务器带宽是按流量计费,但是也不是绝对无限的,在高并发的时候服务器带宽也可能导致请求响应慢的问题
  • 补充:
    • 缓存同时也指静态资源客户端缓存
    • cdn缓存,静态资源通过上传cdn,cdn节点缓存我们的静态资源,减少服务器压力
image

面向服务

  • SOA面向服务架构设计
  • 微服务更细粒度服务化,一系列的独立的服务共同组成系统

使用服务化思维,将核心业务或者通用的业务功能抽离成服务独立部署,对外提供接口的方式提供功能。
最理想化的设计是可以把一个复杂的系统抽离成多个服务,共同组成系统的业务,优点:松耦合,高可用性,高伸缩性,易维护。
通过面向服务化设计,独立服务器部署,均衡负载,数据库集群,可以让服务支撑更高的并发

  • 服务例子:
    • 用户行为跟踪记录统计
  • 说明:
    • 通过上报应用模块,操作事件,事件对象,等数据,记录用户的操作行为
    • 比如:记录用户在某个商品模块,点击了某一件商品,或者浏览了某一件商品
  • 背景:
    • 由于服务需要记录用户的各种操作行为,并且可以重复上报,准备接入服务的业务又是核心业务的用户行为跟踪,所以请求量很大,高峰期会产生大量并发请求。
  • 架构:
    • nodejs WEB应用服务器均衡负载
    • redis主从集群
    • mysql主
    • nodejs+express+ejs+redis+mysql
    • 服务端采用nodejs,nodejs是单进程(PM2根据cpu核数开启多个工作进程),采用事件驱动机制,适合I/O密集型业务,处理高并发能力强
  • 业务设计:
    • 并发量大,所以不能直接入库,采用:异步同步数据,消息队列
    • 请求接口上报数据,接口将上报数据push到redis的list队列中
    • nodejs写入库脚本,循环pop redis list数据,将数据存储入库,并进行相关统计Update,无数据时sleep几秒
    • 因为数据量会比较大,上报的数据表按天命名存储
  • 接口:
    • 上报数据接口
    • 统计查询接口
  • 上线跟进:
    • 服务业务基本正常
    • 每天的上报表有上千万的数据

冗余,自动化

当高并发业务所在的服务器出现宕机的时候,需要有备用服务器进行快速的替代,在应用服务器压力大的时候可以快速添加机器到集群中,所以我们就需要有备用机器可以随时待命。 最理想的方式是可以通过自动化监控服务器资源消耗来进行报警,自动切换降级方案,自动的进行服务器替换和添加操作等,通过自动化可以减少人工的操作的成本,而且可以快速操作,避免人为操作上面的失误。

  • 冗余
    • 数据库备份
    • 备用服务器
  • 自动化
    • 自动化监控
    • 自动化报警
    • 自动化降级

通过GitLab事件,我们应该反思,做了备份数据并不代表就万无一失了,我们需要保证高可用性,首先备份是否正常进行,备份数据是否可用,需要我们进行定期的检查,或者自动化监控, 还有包括如何避免人为上的操作失误问题。(不过事件中gitlab的开放性姿态,积极的处理方式还是值得学习的)

总结

高并发架构是一个不断衍变的过程,冰洞三尺非一日之寒,长城筑成非一日之功 
打好基础架构方便以后的拓展,这点很重要

image

这里重新整理了下高并发下的架构思路,举例了几个实践的例子,如果对表述内容有啥意见或者建议欢迎留言。

Mac下安装Rust

Mac 上有两种安装方式,一种是官方的命令行运行:

 $ curl https://sh.rustup.rs -sSf | sh

可以参考: Wiki:Rust 语言环境安装:Linux 开发环境(Ubuntu 18)

另一种是使用 Homebrew,这里我们使用此方法来安装,还没有安装 Homebrew 的用户需先自行安装。

以下操作使用 macOS Mojave (10.14.2) 系统,其他比较新的 Mac 系统应该也类似。

# 编辑文件
$ vim ~/.bashrc
# 在文件中加入以下两句
export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static
export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup
# 接下来运行命令使文件生效
$ source ~/.bashrc
# 然后就可以进行安装
curl -sSf https://mirrors.ustc.edu.cn/rust-static/rustup.sh | sh -s
# 进行最后的配置,config可能不存在,创建就完了
$ cd /root/
$ mkdir .cargo
$ cd .cargo 
$ vim config
# 在文件中填入以下内容
[registry]
index = "https://mirrors.ustc.edu.cn/crates.io-index/"
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'
[source.ustc]
registry = "https://mirrors.ustc.edu.cn/crates.io-index/"

brew install rust

命令行:

$ brew install rust

会安装下载已经编译好的二进制文件,总共大几百兆,安装成功后测试下:

$ rustc --version

$ cargo --version
Mac 开发环境

Hello World

创建 hello.rs 文件,内容如下:

hello.rs

// This is a comment
// hello.rs

// main function
fn main() {

    // Print text to the console
    println!("Hello World!");
}

命令行 rustchello.rs 编译为可自行文件:

$ rustc hello.rs

接下来运行可执行文件:

$ ./hello
Hello World!
Mac 开发环境