道汐

道汐 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

道汐 发布了文章 · 2020-04-19

浅谈Retry设计模式及在前端的应用与实现

引子

当一个网页刷不出来的时候,我们常常会本能得刷新一下。这就是一个最简单的 retry (重试)。有时重试一次就加载成功了,有时需要几次,有时需要隔半个小时再来尝试,有时再怎么尝试也没有用。在软件工程的世界里,发生这样失败与重试的场景不仅仅是加载网页,它还可以是向数据库读取数据,向某个微服务请求资源,或是向云端的某些服务发送计算请求等。尤其在今天这个纷纷向云上迁移,走向万物互联的时代,这样的场景正在变得越来越多。一个聪明完善的Retry机制可以使系统变得更有韧性,并减少冗余的报错。在客户端,它还可以提高用户体验。这篇文章会以前端为应用背景介绍一些retry相关的知识和个人经验,它们将涉及:

  • 什么情况下要 retry:认识并鉴别Transient fault(短暂故障)
  • 如何Retry: 一些 retry 设计模式
  • Retry 设计模式在客户端的应用与实现
  • 监控:更好地在运行时了解你的系统

如果你感兴趣,就请继续往下看吧~

什么情况下要 retry: 认识并鉴别Transient fault(短暂故障)

什么是Transient fault(短暂故障)

Transient fault(短暂故障),顾名思义,它是一个短暂存在,并且在一段时间后会被修复的故障。这个时间可以短到几毫秒也可以长达几小时。如果我们的请求是因为一个这样的故障而失败的,那我们在适当的时候重试就可以了。在前端应用中,短暂故障往往发生在你向服务端请求资源时。比如你向一个API发送一个AJAX请求,对面返回一个 “5XX” 的响应。

如何鉴别短暂故障

造成请求失败的原因有很多。它可以是因为服务端内部逻辑的错误,可以是因为客户端发送了一个糟糕的请求,也可以是由Infrastructure造成的一些短暂故障(比如暂时负载量过大,某个服务器的宕机,或网络问题等)。而只有在短暂故障的情况下进行retry才有意义。那么客户端如何鉴别短暂故障呢?最简单的方法是运用 HTTP 请求的响应码。根据规范, 400-499 之间是客户端造成的问题,500-599之间是服务端的故障。(完整响应码列表请参照 MDN文档)。显然,如果返回的错误码是4xx ,我们就没有必要重试了。那问题就缩小到了如何在5xx 的故障中鉴别出短暂故障。如果服务端对错误响应码有标准的定义,我们就可以通过不同的号码得知错误的原因,从而决定是进行retry还是做别的处理。这同时也说明服务端开发中标准并清晰的定义错误码和给与错误信息的重要性。

如何retry: 一些 retry 设计模式

认识了Transient fault,当请求失败时我们可以有一个基本的处理步骤:

  1. 鉴别是不是 transient fault
  2. 如果是,启动retry机制,进行一定次数的retry
  3. 当retry达到最大次数还没有成功,报错并说明原因:服务端暂时无法响应。

那么如何retry ?retry机制可以各种各样,我们应该根据需求的和失败原因的不同来选择不同的retry机制。下面让我们来看一些一些基本的retry设计模式。

简单的立即retry

当请求失败,立即retry,是我们会想到的最直接的方法。它可以被用于一些不常见的失败原因,因为原因罕见,立刻retry也许就修复了。但当碰到一些常见的失败原因如服务端负载过高,不断的立即retry只会让服务端更加不堪重负。试想如果有多个客户端instance在同时发送请求,那越是retry情况就越糟糕。

有延迟的retry

在遇到上面的失败原因,与其立即retry, 倒不如等待一会,也许那时服务端的负载就降下来了。这个 delay(延迟)的时间可以是一个常量,也可以是根据一定数学公式变化的变量。比如我们可以使用一些逐次增加delay算法。这样的算法就好像我们不断地去服务端敲敲门,每次没人开门,我们就意识到自己太急了,那下次来之前就再多等一会好了,因为再一次的失败也是在说明情况还没有得到缓解。Exponential Backoff (指数后退算法)就是一个这个类型的算法,它以指数的方式来增加delay。打个比方就是:第一次失败等待1秒,第二次再失败等待2秒,接下去4秒,8秒...。

上面的例子被广泛使用,但也不一定适应每一个场景。我们可以根据自己系统的特性和业务的需求,设计更适合更优化的算法。

Circuit Breaker(断路器)

那如果Transient fault 修复的时间特别长怎么办?比如长时间的网络问题,那就算我们有再好的retry机制,也免不了是徒劳。我们只会一次又一次地retry, 失败,再retry, 直到达到上限。这一来浪费资源,二来或许又会干扰服务端的自我修复。别担心,Circuit Breaker (断路器)的设计模式也许能拯救我们。它的原意其实就是电路中的开关,大家在中学物理中应该都有接触过。在电路里一旦开关断开,电流就别想通过了。那么在计算机系统中,我们就可以想象一旦开关断开,我们就不会再发送任何请求了。

Circuit Breaker在retry机制中的应用是一个状态机,有三种状态:OPEN,HALF-OPEN, CLOSE。我们设定一个threshold(阈值)和一个timeout,当retry的次数超过这个threshold时,我们认为服务端进入一个较长时间的Trasient fault。那么我们就开关断开,进入OPEN 状态。这时我们将不再向服务端发送任何请求,就像开关断开了,电流(请求)怎么也不会从客户端流向服务端。当过了一段时间到了timeout,我们就将状态设为HALF-OPEN( 这时电路中不具备的),这时我们会尝试把客户端的请求发往服务端去试探它是否已经恢复。如果是就进入CLOSE状态,回到我们正常的机制中,如果不是,就再次进入OPEN 状态。

这个机制既节约了资源,防止了无休止的无用的尝试,又保证了在修复后,客户端能知晓,并恢复的正常的运行。

以上是一些经典的设计模式,它为我们设计retry机制提供了范本和思路。在不同的应用场景下我们可以根据需求灵活地变换。也可以对上面的机制加以修改,设计出更适合自己的版本。

Retry 设计模式在客户端的应用与实现

在服务端,Retry 的机制被大量运用,尤其是在云端微服务的架构上。很多云平台本身就提供了主体(比如服务,节点等)之间的retry机制从而提高整个系统的稳定性。而客户端,作为一个独立于服务端系统之外,运行在用户手机或电脑上的一个App, 并没有办法享受到平台的这个功能。这时,就需要我们自己去为App加入retry机制, 从而使整个产品更加强壮。

npm 有一个 retry 的包可以帮助我们快速加入retry机制,具体如何使用可以参照 https://www.npmjs.com/package...

其实retry的实现并不复杂,我们完全可以自己写一个这样的工具供一个或多个产品使用。这可以让我们更容易更改其中的算法来适应产品的需求。

下面是我写的一个简单的retry小工具,由于我们向服务端做请求的函数常常是返回promise的,比如 fetch 函数 。这个工具可以为任何一个返回promise的函数注入retry机制。

// 这个函数会为你的 promiseFunction (一个返回promise的函数) 注入retry的机制。
// 比如 retryPromiseFunctionGenerator(myPromiseFunction, 4, 1000, true, 4000)
// 会返回一个函数,它的用法和功能与 myPromiseFunction 一样。但如果 Promise reject 了,
// 它就会进行retry, 最多retry 4 次,每次时间间隔指数增加,最初是1秒,随后2秒,4秒,由于
// 我们设定最大delay是4秒,那么之后就会持续delay4秒,直到达到最大retry次数 4 次。而如果
// enableExponentialBackoff 设为 false, delay就会是一个常量1秒。
const retryPromiseFunctionGenerator = (
  promiseFunction, // 需要被retry的function
  numRetries = defaultNumRetries, // 最多retry几次
  retryDelayMs = defaultRetryDelayMs, // 两次retry间的delay
  enableExponentialBackoff = false, // 是否启动指数增加delay
  maxRetryDelayMs // 最大delay时间
) => async (...args) => {
  for (
    let numRetriesLeft = numRetries;
    numRetriesLeft >= 0;
    numRetriesLeft -= 1
  ) {
    try {
      return await promiseFunction(...args);
    } catch (error) {
      if (numRetriesLeft === 0 || !isTransientFault(error)) {
        throw error;
      }

      const delay = enableExponentialBackoff
        ? Math.min(
            retryDelayMs * 2 ** (numRetries - numRetriesLeft),
            maxRetryDelayMs || Number.MAX_SAFE_INTEGER
          )
        : retryDelayMs;

      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
};

这个小工具可以实现上面提到的设计模式中的第二种:有延迟的retry。你可以方便地调节retry的参数以及是否要应用Exponential Backoff 的算法。你也可以稍作修改让计算delay的function本身也作为一个参数传进来,从而让这个工具变得更灵活。

监控:更好地在运行时了解你的系统

现在我们的App拥有了retry机制,在客户端运行时,它变得更强壮了,一些失败的服务端请求并不能打到它。但作为开发者的你一定想知道它在用户手上retry了几次,什么时候retry的,最终失败了没有。这些信息不仅让我更好的了解用户的实际体验,它们也可以作为服务端性能的指标之一。实时对这些信息进行监控甚至可以让我们尽早的发现服务端的故障以减少损失。

客户端的监控是一个很大的话题,Retry信息的收集只是其中一个应用场景。那如何实现呢?很简单。我们可以在每一次执行retry时发送一条log(日志)其中包含你想了解的信息。然后运用第三方或公司内部的日志浏览工具去分析这些日志,从而获得许多有意思的指标。

举个例子,我们可以简单地监控retry log 的数量,如果突然激增,那就说明服务端也许出现了一些故障,这时候开发团队可以在第一时间做出反应修复故障,以免对大面积的客户造成影响。当然这不仅仅可以通过监控retry实现,我们也可以监控服务端的http请求失败的数量,这是题外话了^^

结束语

这次的话题好像不那么前端,但我觉得它很有意思。同时我也在慢慢探索写作的方向。如果你有任何前端或监控相关感兴趣的话题,或对我的建议,欢迎留言告诉我。最后,感谢你的阅读。

查看原文

赞 3 收藏 0 评论 1

道汐 发布了文章 · 2020-04-08

单线程的JavaScript如何通过事件循环实现异步

前言

当谈及Javascript时, 我们常常听到 单线程、异步非阻塞、事件循环这样的关键词,然而它们是什么? 为什么单线程还可以实现异步?怎么实现?相信这些问题都曾今或正在困扰着许多前端爱好者。通过这篇文章我们将对它们一一梳理。文章将讲解:

  • 什么是同步和异步
  • 单线程的JS如何通过事件循环实现异步
  • 这些机制是如何影响 web 应用的性能的
  • setTimeout 与 setInverval 为什么不准时

如果你对它们感兴趣,就请继续往下读吧。

什么是同步和异步

笼统抽象地说:

  • 同步 (Synchronous):同一时间只做一件事,做完一件事才开始下一件。
  • 异步 (Asynchronous):同时可以做N件事,不一定要做完一件才能开始下一件。

回到编程的世界里具体地说:

  • 同步:代码是一行一行执行的,一行完成了才会跳到下一行。
  • 异步:某一行代码还没完成,我们可以让他执行着先,但我们可以开始下一行。也就实现了我们所谓的并行。

用图表达:

由于步骤2 的执行时间较长,在同步执行过程中他会阻塞步骤3的执行一段时间,反之在异步的机制中,如果我们标明了步骤2是异步的, 那么在完成步骤1之后我们只会开始执行步骤二并让他在另一个世界里执行,然后立刻开始执行步骤3.
image

用JavaScript来模拟上面的过程:

const step1 = () => console.log(1);
const step3 = () => console.log(3);
const step4 = () => console.log(4);

// 用 Promise 简单模拟一个执行时间为1秒,并在结束时候打印2的函数
// 如果你不了解Promise,没关系,盖住函数的内容,只要记得它执行时间很长,会在结束时打印2,并会告诉你”我执行完了“
const step2 = () =>
  new Promise((resolve, _) => {
    setTimeout(() => {
      console.log(2);
      resolve();
    }, 1000);
  });

// 异步机制执行
// JS引擎支持返回Promise的函数异步执行,所以我们不需要做额外的包装,直接执行step2,它便是异步的)
const asyncExecute = () => {
  step1();
  step2();
  step3();
  step4();
};

// 同步机制执行
// 如果你不了解async、await不要紧,只要知道 await 就是要等到它后面的语句执行完了才会进行下一步
const syncExecute = async () => {
  step1();
  await step2();
  step3();
  step4();
};

asyncExecute(); // 打印 1 3 4 2
syncExecute(); // 打印 1 2 3 4

同步与异步各有优点,同步可以保证执行的顺序,异步可以保证程序的非阻塞。 在一个web应用中,如果我们把向服务端请求一个资源看成一个时间很长的步骤,那么处理这个请求返回信息就需要我们去同步执行。然而在如果我们在请求资源的同时还想让用户可以继续使用我们的应用,那这就需要异步地去实现。 Javascript 和他的引擎给我们提供丰富的资源去实现这两种机制。

讲到这里,也许还有些抽象,但相信在下面的章节里,这一切会变得越来越清晰。

单线程的JS如何通过事件循环实现异步

如何理解 "JavaScript 是单线程的"

首先我们来理解几个概念:

  • JS运行时环境 (JS runtime environment): 一个JS代码运行的环境。浏览器和Node里都有。
  • JS引擎 (JS engine):一个可以编译并执行JS的程序,执行中的它是运行时环境的一部分。
  • 线程 (Thread):操作系统执行和调度的最小单元,它也是一个程序进行自我分割然后并行执行的最小单元。

在一个JS运行时环境中,JS 代码只在一个线程中执行, 所以我们说 JS 是单线程的。然而运行时环境本身(比如在浏览器中)并不是单线程的,他包含了JS引擎的运行、一系列的web API 调用、以及我们后面要讲到的事件循环机制的运行等。

如果说线程是程序自我分割、并行执行的最小单元,那在单个线程里执行的JS代码又怎么可能实现并行,也就是异步呢?

事件循环实现异步

假设我们在Chrome浏览器中,事件循环的机制可以用这样一张图来解释。
image

在这里我们需要记住五个模块:

  • 堆 (Heap / Memory Heap)
  • 栈 (Stack / Call stack)
  • Web API
  • 回调函数队列(Callback queue / Message queue)
  • 事件循环(Event loop)

接下来让我们一一解释

堆 (Heap / Memory Heap)

当 JS 引擎解析JS代码的过程中遇到一些变量或者函数申明的时候,它会将它们存储到里面。

栈 (Stack / Call stack)

  • 我们也叫它 Call stack,是一个LIFO-last in first out (后进先出) 的数据结构。
  • 每当 JS 引擎要调用一个函数是就会把这个调用放到 stack 的顶端,然后开始解析执行函数里面的内容。当函数返回一个值了或者所有内容都执行完了(如果没有返回值就会默认返回 undefined ),引擎就会把这个调用从stack顶端删除, 然后继续执行它下面的函数。

举个例子:

const func2 = ()=> {
  console.log("我是 func2 ")
}
const func1 = () => {
  console.log("func1 开始了")
  func2();
  console.log("func1 结束了")
}
func1();
// 打印:
// func1 开始了
// 我是 func2 
// func1 结束了

执行这段代码时,引擎就会先调用 func1, 将它的调用放到stack里,然后执行func1 中第一行打印。然后执行第二行 调用 func2。这时引擎会把 func2调用放到stack的顶端(如大图中所示),然后执行 func2 的内容也就是打印。结束之后,因为func2 中没有更多的内容,引擎会删除stack顶端的func2 的调用,然后继续执行func1 第三行,当第三行结束完毕,引擎删除stack中func1 的调用。最后我们会看到这段程序的打印如代码最后的注释中所示。

Web API

  • Web API 在这里是有浏览器提供的一些API (如果你在Node 环境中,那就会是由Node提供的一些API),它们常常是异步调用的。
  • 它们会接受一个参数叫“回调函数”(Callback), 这个函数会在API触发某个事件时执行。
  • 当Stack顶端的函数调用API时,API 的内容就会在另外的线程中执行(原来所谓的并行还是用到了多个线程)。API在监听到某些事件时会把对应的回调函数放到一个回调函数队列里。这个事件可以是一个http请求的结束,一个 Timer倒计时结束,或者一个鼠标点击事件。

回调函数队列 (Callback queue)

  • 回调函数队列是一个 FIFO-first in first out(先进先出)的数据结构。它按放入顺序存储了需要执行的回调函数。
  • 当 Stack 空了的时候,队列中第一个回调函数就会被放到 Stack 里去调用。

事件循环(Event loop)

  • 事件循环是一种机制,它被运行在在 JS 运行时环境中
  • 它不断地去检查 Stack 和 回调函数队列, 当 Stack 空了,它就会立刻通知回调函数队列把第一个函数发送过去。
  • 有时候Stack和回调函数队列可能都会空着一段时间,但这个检查是不会因此而停下来的。

用代码举例

callback1 = () => console.log("我是 callback1");
callback2 = () => console.log("我是 callback2");

const func2 = () => {
  console.log("func2 开始");
  // setTimeout 就是一个异步调用的 Timer API, 他会让 Timer 计时一定的时间,比如这里是1秒,然后触发计时结束,随后callback将会被放入 callback queue
  setTimeout(callback2, 1000);
  console.log("func2 结束");
};

const func1 = () => {
  console.log("func1 开始");
  setTimeout(callback1, 0);
  func2();
  console.log("func1 结束");
};

func1();

// 打印
// func1 开始
// func2 开始
// func2 结束
// func1 结束
// 我是 callback1
// 我是 callback2

我们来说说这段代码是怎么在在刚才解释的机制下执行的(超长!如果你已经理解了可以跳过这段 ^^):

  1. func1被放入stack,并开始执行里面的内容
  2. 执行funct1 第一行:打印 "func1 开始"
  3. 执行func1 第二行:setTimeout的调用被放入stack
  4. 在别的线程里, 一个计时API 被调用 (在0秒后将callback1放入回调函数队列) 所以callback1立刻会被放入回调函数队列
  5. 将setTimeout从stack中删除。

    由于4和5是在两个线程里执行的,所以我们可以把它们看成几乎是同时执行的。

  6. 执行func1 第三行:func2被放入stack
  7. 执行func2 第一行:打印 "func2 开始"
  8. 执行func2 第二行:setTimeout的调用被放入stack
  9. 在别的线程里, 一个计时API 被调用 (在1秒后将callback2放入回调函数队列)
  10. 将setTimeout从stack中删除。

    由于9和10是在两个线程里执行的,所以我们可以把它们看成几乎是同时执行的

  11. 执行func2 第三行:打印"func2 结束"
  12. 将func2 的调用从stack中删除
  13. 执行func1 第三行:打印"func1 结束"
  14. 将func1 的调用从stack中删除
  15. 事件循环机制发现stack空了,发消息给回调函数队列
  16. callback1被取出并放入stack执行
  17. 执行callback1第一行:打印 "我是 callback1"
  18. 将callback1的调用从stack中删除
  19. 事件循环机制发现stack空了,发消息给回调函数队列,但队列中什么也没有,所以什么也不做
  20. 第9步中的计时1秒时间到,callback2被放入回调函数队列
  21. 事件循环机制发现stack空了,发消息给回调函数队列
  22. Callback2被取出并放入stack执行
  23. 执行callback2第一行:打印 "我是 callback2"
  24. 将callback1的调用从stack中删除

文字表现比较局限,我们可以按步骤动手画一画,就非常清晰了。

总结

  • 所有 JS 代码都运行在一个线程里,它们在一个stack里同步(synchronous)执行。
  • 浏览器JS 运行时环境中包含 JS引擎(内有heap 、stack)、WebAPI、回调函数队列、事件循环机制,它们一同协作实现了JS 的异步。
  • 一些我们认为时间长或需要并行执行的内容,其实是由运行时环境提供的WebAPI来完成的,它们是在其他的线程里完成的。
  • 回调函数和异步调用WebAPI的机制让 JS 可以不阻塞地运行下去,到一定的时候再执行回调函数。

这些机制是如何影响 web 应用的性能的

Web应用的性能是个很大的话题,在这里我们只讨论性能中与 JS 的单线程和异步相关的部分。

首先提几个概念作为准备:

  • 大部分情况下浏览器是16.66ms一帧, 也就是每16.66ms刷新(或者说render)一次你屏幕上的内容。
  • render (其中包括一些计算工作) 也是在 JS 的线程里进行的。
  • render 类似一个我们上一节提到的callback, 在 Stack 没有被清空的情况下是没法被放入执行的。

那如果Stack中有一个function执行时间超过16.66ms 会怎么样?答案是它会导致下一个render的推迟执行。在这个function结束前,页面是停在一个静止的状态的,用户在页面上点击也不会有什么反应。这就是我们有时会感受到的 “页面有点卡”。所以为了防止这种性能差的表现,我们不建议将耗时的function放到 JS 的主线程里执行

其实由于render本身的执行也需要消耗时间,所以我们还要给它留出空间。根据谷歌的官方文档,我们最好是将自己的逻辑保证在10ms以下,甚至是3-4ms。

然而由于业务的需要,在开发中一些耗时的逻辑是无法避免的,例如排序、搜索等。在这样的情况下我们可以将逻辑分成小块,然后使用requestAnimationFrame,或者将耗时的逻辑放到service worker中进行。 具体如何使用在这里不做细说,我们可以参照谷歌的这篇文档 Optimize JavaScript Execution,上面有详细的解说。

setTimeout 与 setInterval 为什么不准时

长话短说:

setTimeout 只是在给定的时间之后将它的 callback function 放入 callback queue 但并不能保证function 的准时执行。setInterval 也是,只是每隔固定的时间放入一次callback function. 所以它们是否能准时执行都取决于当时stack 和 callback queue 的状态。但我们还是可以粗略地认为它们是准时的,因为大部分情况下这些不准时只是毫秒级的,但也需要理解它们其中的原理来处理和解释那些小部分的情况。

详细解释:

看了 MDNw3schoolssetTimeout 的解释,我们容易简单地认为它的作用是在一定的时间后执行一个callback function。然而这并不完全正确。根据我们在第二节中解释的 stack 和 callback queue 的概念, setTimeout 只能保证将它的callback function在一定时间之后放入callback queue 而不是执行。 如果此时callback queue 中只有这个function 且stack是空的,当然它就会被准时执行。但如果此时stack中还有尚未执行完的内容,或者在callback queue 中还有好几个callback在排队,显然我们的function会被推后执行,这个推后的时间取决与stack中的内容 和 callback queue 中排在前面的 callback 要执行多久。

但我们还是可以粗略地认为它是准时的。 只要我们不在JS的线程里放入一个十分耗时的function, 或者在callback queue里瞬间塞入一大堆callback, 那么stack是时常会被空出来执行我们 setTimeout 扥 callback 的。在这样的情况下不准时的偏差也就只是毫秒级的。

细心的你也许发现了在第二节的例子中我们使用了 setTimeout(callback,0), 也就是在 0 毫秒后将callback 放入 callback queue。 由于queue 中的 callback 会在stack 空了之后在执行,那么这个用法其实可以作为一种控制执行顺序的工具。 让我们来看一个简单地例子:

setTimeout(() => console.log("我想后执行"), 0);
console.log("我想先执行");
// 打印
// 我想先执行
// 我想后执行

setIntervalsetTimeout 的实现原理是相似的。我们粗略地理解它为每隔一定的时间执行一次callback。其实是每隔一定的时间在 callback queue 中加入一次 callback, 所以它前后两次执行callback 的间隔时间也是不能保证的, 它们可长可短, 取决于stack和callback queue 的状态。

结束语

谢谢你一直读到现在。这是我的第一篇博文,它记录了我对前端知识学习和思考的过程。希望你在阅读过程中有所收获。我会继续坚持下去分享我在学习工作中的心得和体会。

最后,感谢这些帮助我学习文章相关内容的资料:

Asynchronous JavaScript: Promises, Callbacks, Async Await

The Javascript Runtime Environment

How JavaScript Timers Work

What the heck is the event loop anyway? | Philip Roberts | JSConf EU

Optimize JavaScript Execution

查看原文

赞 6 收藏 3 评论 1

道汐 关注了专栏 · 2020-04-08

SegmentFault 行业快讯

第一时间为开发者提供行业相关的实时热点资讯

关注 63311

道汐 关注了专栏 · 2020-04-08

民工哥技术之路

公众号:民工哥技术之路、《Linux系统运维指南 从入门到企业实战》作者。专注系统架构、高可用、高性能、高并发,数据库、大数据、数据分析、Python技术、集群中间件、后端等开源技术分享。

关注 31980

道汐 关注了专栏 · 2020-04-08

kubernetes solutions

专注k8s,serverless,service mesh,devops

关注 2239

道汐 关注了专栏 · 2020-04-08

LeanCloud 官方专栏

LeanCloud(原 AVOS Cloud) 是针对移动应用的一站式云端服务,专注于为应用开发者提供工具和平台。提供包括LeanStorage 数据存储、LeanMessage 通信服务、LeanAnalytics 统计分析、LeanModules 拓展模块等四大类型的后端云服务,加速应用开发。

关注 5011

道汐 关注了专栏 · 2020-04-08

Python数据科学

微信公众号:Python数据科学

关注 7698

道汐 关注了标签 · 2020-04-08

java

Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台(即 JavaSE, JavaEE, JavaME)的总称。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

Java编程语言的风格十分接近 C++ 语言。继承了 C++ 语言面向对象技术的核心,Java舍弃了 C++ 语言中容易引起错误的指針,改以引用取代,同时卸载原 C++ 与原来运算符重载,也卸载多重继承特性,改用接口取代,增加垃圾回收器功能。在 Java SE 1.5 版本中引入了泛型编程、类型安全的枚举、不定长参数和自动装/拆箱特性。太阳微系统对 Java 语言的解释是:“Java编程语言是个简单、面向对象、分布式、解释性、健壮、安全与系统无关、可移植、高性能、多线程和动态的语言”。

版本历史

重要版本号版本代号发布日期
JDK 1.01996 年 1 月 23 日
JDK 1.11997 年 2 月 19 日
J2SE 1.2Playground1998 年 12 月 8 日
J2SE 1.3Kestrel2000 年 5 月 8 日
J2SE 1.4Merlin2002 年 2 月 6 日
J2SE 5.0 (1.5.0)Tiger2004 年 9 月 30 日
Java SE 6Mustang2006 年 11 月 11 日
Java SE 7Dolphin2011 年 7 月 28 日
Java SE 8JSR 3372014 年 3 月 18 日
最新发布的稳定版本:
Java Standard Edition 8 Update 11 (1.8.0_11) - (July 15, 2014)
Java Standard Edition 7 Update 65 (1.7.0_65) - (July 15, 2014)

更详细的版本更新查看 J2SE Code NamesJava version history 维基页面

新手帮助

不知道如何开始写你的第一个 Java 程序?查看 Oracle 的 Java 上手文档

在你遇到问题提问之前,可以先在站内搜索一下关键词,看是否已经存在你想提问的内容。

命名规范

Java 程序应遵循以下的 命名规则,以增加可读性,同时降低偶然误差的概率。遵循这些命名规范,可以让别人更容易理解你的代码。

  • 类型名(类,接口,枚举等)应以大写字母开始,同时大写化后续每个单词的首字母。例如:StringThreadLocaland NullPointerException。这就是著名的帕斯卡命名法。
  • 方法名 应该是驼峰式,即以小写字母开头,同时大写化后续每个单词的首字母。例如:indexOfprintStackTraceinterrupt
  • 字段名 同样是驼峰式,和方法名一样。
  • 常量表达式的名称static final 不可变对象)应该全大写,同时用下划线分隔每个单词。例如:YELLOWDO_NOTHING_ON_CLOSE。这个规范也适用于一个枚举类的值。然而,static final 引用的非不可变对象应该是驼峰式。

Hello World

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

编译并调用:

javac -d . HelloWorld.java
java -cp . HelloWorld

Java 的源代码会被编译成可被 Java 命令执行的中间形式(用于 Java 虚拟机的字节代码指令)。

可用的 IDE

学习资源

常见的问题

下面是一些 SegmentFault 上在 Java 方面经常被人问到的问题:

(待补充)

关注 142248

道汐 关注了标签 · 2020-04-08

javascript

JavaScript 是一门弱类型的动态脚本语言,支持多种编程范式,包括面向对象和函数式编程,被广泛用于 Web 开发。

一般来说,完整的JavaScript包括以下几个部分:

  • ECMAScript,描述了该语言的语法和基本对象
  • 文档对象模型(DOM),描述处理网页内容的方法和接口
  • 浏览器对象模型(BOM),描述与浏览器进行交互的方法和接口

它的基本特点如下:

  • 是一种解释性脚本语言(代码不进行预编译)。
  • 主要用来向HTML页面添加交互行为。
  • 可以直接嵌入HTML页面,但写成单独的js文件有利于结构和行为的分离。

JavaScript常用来完成以下任务:

  • 嵌入动态文本于HTML页面
  • 对浏览器事件作出响应
  • 读写HTML元素
  • 在数据被提交到服务器之前验证数据
  • 检测访客的浏览器信息

《 Javascript 优点在整个语言中占多大比例?

关注 176482

道汐 关注了标签 · 2020-04-08

vue.js

Reactive Components for Modern Web Interfaces.

Vue.js 是一个用于创建 web 交互界面的。其特点是

  • 简洁 HTML 模板 + JSON 数据,再创建一个 Vue 实例,就这么简单。
  • 数据驱动 自动追踪依赖的模板表达式和计算属性。
  • 组件化 用解耦、可复用的组件来构造界面。
  • 轻量 ~24kb min+gzip,无依赖。
  • 快速 精确有效的异步批量 DOM 更新。
  • 模块友好 通过 NPM 或 Bower 安装,无缝融入你的工作流。

官网:https://vuejs.org
GitHub:https://github.com/vuejs/vue

关注 137587

认证与成就

  • 获得 9 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-04-08
个人主页被 536 人浏览