未来会怎样构建 Web 应用程序?

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
简介:   在未来,我们会怎样构建 Web 应用程序呢?如果行业正常发展下去的话,那么今天我们认为很难、做起来很有价值的事情在明天都会变得很轻松普遍。我想我们会发现很多新的抽象,让Google Docs写起来也能像今天的普通 Web 应用一样简单。  这就引出来一个问题——这些抽象会是什么样子?我们今天能发现它们吗?想要找出答案,一种方法是审视我们在构建 Web 应用程序时必须经历的所有问题,然后看看我们能做些什么。  亲爱的读者,这篇文章就是我对上述方法的一次实践尝试。我们会走过一段旅程,看看今天我们是如何构建 Web 应用程序的:我们将回顾行业面临的各种问题,评估Firebase、Supa

  在未来,我们会怎样构建 Web 应用程序呢?如果行业正常发展下去的话,那么今天我们认为很难、做起来很有价值的事情在明天都会变得很轻松普遍。我想我们会发现很多新的抽象,让Google Docs写起来也能像今天的普通 Web 应用一样简单。

  这就引出来一个问题——这些抽象会是什么样子?我们今天能发现它们吗?想要找出答案,一种方法是审视我们在构建 Web 应用程序时必须经历的所有问题,然后看看我们能做些什么。

  亲爱的读者,这篇文章就是我对上述方法的一次实践尝试。我们会走过一段旅程,看看今天我们是如何构建 Web 应用程序的:我们将回顾行业面临的各种问题,评估Firebase、Supabase、Hasura 等解决方案,看看还有什么需要做的事情。我想到了旅途的最后,你一定会同意我的观点,那就是浏览器中的数据库看起来应该是最有用的抽象之一。不过,这里说的有点太远了,我们先从头开始。

  这段旅程始于浏览器中的Javascript。

  我们的第一步工作是获取信息并将其显示在各个位置。例如,我们可能会显示一个好友列表、好友数量、特定好友组的一个模态等。

  我们面临的问题是,所有组件看到的信息都需要是一致的。如果一个组件看到的好友数据和别的不一样,你就可能显示出错误的“计数”,或者一个视图与另一个视图中的昵称不一样。

  为解决这个问题,我们需要有一个核心的事实来源。于是每当我们获取什么东西时,我们都会对其标准化并把它放在一个地方(通常是一个存储)。然后,每个组件(使用一个选择器)读取并转换所需的数据。下面这样的代码是很常见的:

  // normalise [posts] -> {[id]: post}

  fetchRelevantPostsFor(user).then(posts=> {

  posts.forEach(post=> {

  store.addPost(post);

  })

  })

  // see all posts by author:

  store.posts.values().reduce((res, post)=> {

  res[post.authorId]=res[post.authorId] || [];

  res[post.authorId].push(post);

  return res;

  }, {})

  复制代码

  这里的问题是,为什么我们需要做这些工作呢?我们得编写自制代码来处理这些数据,可是数据库早就解决这个问题了。我们应该能够“查询”数据才是,比如说:

  SELECT posts WHERE post.author_id=?;

  复制代码

  这样查询我们浏览器内部的信息不是很方便吗?

  下一个问题是让数据保持最新状态。假设我们删除了一个好友,会发生什么呢?

  我们发送一个 API 请求,等待它完成,然后编写一些逻辑来“删除”关于这个好友的所有信息。比如这样的代码:

  deleteFriend(user, friend.id).then(res=> {

  userStore.remove(friend.id);

  postStore.removeUserPosts(friend.id);

  })

  复制代码

  但这种机制很快就会变得很麻烦:我们必须记住存储中可能受这一更改影响的所有位置才行,就好像我们要在大脑里搞一个垃圾收集器,可我们的大脑不擅长这种活儿。为了避开它,人们想出的一种办法是跳过问题并重新获取整个世界:

  deleteFriend(user, id).then(res=> {

  fetchFriends(user);

  fetchPostsRelevantToTheUser(post);

  })

  复制代码

  这两种解决方案都不是很好。在这两种情况下都存在我们需要留意的隐式不变量(基于这一更改,我们还需要注意其他哪些更改?),并且我们在应用程序中引入了延迟。

  问题是,当我们对数据库做任何更改时,它用不着我们这么小心就可以完成工作。为什么浏览器不能自动搞定这种事情呢?

  DELETE FROM friendships WHERE friend_one_id=? AND friend_two_id=?

  -- Browser magically updates with all the friend and post information removed

  复制代码

  你可能已经注意到 B.的问题是,我们必须等待好友被移除才能更新浏览器状态。

  在大多数情况下,我们可以通过一个乐观更新来加快速度——毕竟,我们知道调用很可能会成功。为此,我们执行以下操作:

  friendPosts=userStore.getFriendPosts(friend);

  userStore.remove(friend.id);

  postStore.removeUserPosts(friend.id);

  deleteFriend(user, id).catch(e=> {

  // undo

  userStore.addFriend(friend);

  postStore.addPosts(friendPosts);

  })

  复制代码

  这更烦人了。现在我们需要手动更新成功操作和失败操作才行。

  这是为什么?在后端,数据库本来就能做乐观更新啊——为什么我们不能在浏览器中这样做?

  DELETE friendship WHERE friend_one_id=? AND friend_two_id=?

  -- local store optimistically updated, if operation fails we undo

  复制代码

  数据不仅会因我们自己的行为而改变。有时我们需要连接到其他用户所做的更改。例如,有人可以取消我们的好友关系,或者有人可以向我们发送消息。

  为了完成这项工作,我们需要做的事情与在 API 端点中所做的是一样的,但这次是在我们的 websocket 连接上:

  ws.listen(${user.id}/friends-removed, friend=> {

  userStore.remove(friend.id);

  postStore.removeUserPosts(friend.id);

  }

  复制代码

  但这又引入两个问题。首先,我们又得玩垃圾收集器那套了,需要记住可能受事件影响的每一个位置。

  其次,如果我们要做乐观更新,我们就会遇到争用情况。想象一下,你运行一个乐观更新,将一个形状的颜色设置为blue,同时一个陈旧(stale)更新跑来了,说它是red。

  Optimistic Update: Blue

  Stale reactive update: Red

  Successful Update, comes in through socket: Blue

  复制代码

  现在你会看到闪烁的图像。乐观更新把形状改成蓝色,响应更新又会把它改成红色,但是一旦乐观更新成功,新的响应更新又会把它变回蓝色。

  解决这样的问题涉及一致性的主题,于是你会去搜索关于……数据库的资料。

  其实,用不着这么麻烦。如果每个查询都是响应式的呢?

  SELECT friends FROM users JOIN friendships on friendship.user_one_id=?

  复制代码

  现在,好友关系的任何变化都会自动更新订阅这个查询的视图。你不必操心哪些内容出现了更改,并且你的本地数据库可以找出“最新更新”的内容,于是消除了大部分复杂性。

  在服务器上,问题只会更复杂。

  许多后端开发工作到头来成为了数据库和前端之间的一种粘合剂。

  // db.js

  function getRelevantPostsFor(userId) {

  db.exec("SELECT * FROM users WHERE ...")

  }

  // api.js

  app.get("relevantPosts", (req, res)=> {

  res.status(200).send(getRelevantPosts(req.userId));

  })

  复制代码

  这里面也太多重复了,以至于我们最后要创建脚本来生成这些文件。但是为什么我们需要这样做呢?不管怎样,它们通常是与客户端非常紧密地耦合的。为什么我们不能直接将数据库暴露给客户端呢?

  好吧,我们不这样做的原因是我们需要确保权限正确设置。例如,你应该只能看到你好友的帖子。为此,我们向 API 端点添加中间件:

  app.put("user", auth, (req, res)=> {

  ...

  }

  复制代码

  但这会变得越来越混乱。Websocket 呢?新的代码更改有时会引入一些你意想不到的方法来更新数据库对象。突然之间,你就遇到了麻烦。

  这里要问的问题是,为什么要在 API 级别进行身份验证?理想情况下,我们应该有一些非常接近数据库的东西,确保任何数据访问都通过权限检查。像 Postgres 这样的数据库有行级安全性,但这很快就会变得很麻烦。但如果你能“描述”数据库附近的实体呢?

  User {

  view: [

  IAllowIfAdmin(),

  IAllowIfFriend(),

  IAllowIfSameUser(),

  ]

  write: [

  IAllowIfAdmin(),

  IAllowIfSameUser(),

  ]

  }

  复制代码

  在这里,我们编写一些身份验证规则,并确保不管你尝试用哪种方式来编写和更新用户实体,你都可以被许可。于是乎,现在只有少数代码更改(而不是大多数更改)会影响权限了。

  并且在某些时候,我们要完成的需求会增加复杂性。

  例如,假设我们需要支持“撤消/重做”,用于好友操作。一个用户删除了一个好友,然后他们按下了“撤消”——我们怎么来支持这一过程呢?

  我们不能直接删除好友关系,因为如果我这样做的话,就没法不知道这个人原本“已经是好友”,还是现在刚请求成为好友。在后一种情况下,我们可能需要发送好友请求才行。

  为了解决这个问题,我们改进了数据模型。我们将用“好友事实”来代替单一的好友关系。

  [

  {status: "friends", friend_one_id: 1, friend_two_id: 2, at: 1000},

  {status: "disconnected", friend_one_id: 1, friend_two_id: 2, at: 10001},

  ]

  复制代码

  那么“最新事实”会代表俩人之间是否存在好友关系。

  这种办法是可行的,但大多数数据库并不是为它设计的:查询不像我们预期的那样工作,优化起来也比我们预期的更难。我们最后不得不非常小心地处理更新机制,以免意外删除记录。

  突然之间,我们变成了“某种数据库工程师”,跑去大量查阅有关查询优化的资料。

  这种要求看似独特,但在实践中越来越常见。如果你处理的是金融交易,你需?要这样的机制来做审计。撤消/重做是许多应用中的必需品。

  也许突然发生了一个错误,于是我们不小心删除了数据。在事实统治的世界中不会有这样的事情——反正你可以撤销删除操作。但这并不是我们大多数人生活的世界。

  有一些模式将事实视为一等公民(Datomic,后文具体讨论),但现在它们还是很罕见的,很少有工程师能做到。如果这种模式没那么罕见呢?

  令人头疼的例子还有很多。比如说离线模式——许多应用程序都是长期运行的,可以在没有互联网连接的情况下继续运行一段时间。我们如何支持这一特性呢?

  我们只能再次进化我们的数据模型,但这一次真正将所有内容都作为“事实”,并准备一个客户端数据库,该数据库基于这些事实来演进自己的内部状态。恢复连接后,我们应该能够协调更改。

  这很难做到。从本质上讲,能做到这一步的程序员都变成了数据库工程师。但是,如果我们在浏览器中有一个数据库,让它扮演分布式数据库中的一个“节点”,上面的任务不就可以自动完成了吗?

  事实证明,基于事实的系统实际上更容易做到这一点。许多人认为我们需要求助于操作转换来做这样的事情,但正如 figma 展示的那样,只要我们允许单一的领导者,并且可以接受最后写入者获胜这样的语义,我们就可以彻底简化这个机制,只要事实就足够了。当你需要更严肃的解决方案时,你可以打开 OT 兔子洞。

  想象一下......立即启用离线模式。这样一来,大多数应用程序会变成什么样?

  前面,我们讨论了来自客户端的响应性。在服务器上的响应性也是个问题。我们必须确保在数据更改时更新所有相关客户端。例如,如果添加了一个“帖子”,我们需要通知与这个帖子相关的所有可能订阅。

  function addPost(post) {

  db.addPost(post);

  getAllFriends(post).forEach(notifyNewPost);

  }

  复制代码

  这会变得相当混乱。我们很难知晓所有可能相关的主题。错过一些主题也是很容易的:如果使用addPost之外的查询更新数据库,我们永远不会知道是不是有主题被错过了。这项工作需要开发人员来完成。它开始做起来很容易,但会变得越来越复杂。

  然而,数据库也可以知晓所有这些订阅,并且可以只处理更新相关的查询。RethinkDB 是在这方面做得很好的一个例子。如果你选择的查询语言可以做到这一点,是不是会很方便?

  最终,我们需要将数据放在多个位置:缓存(Redis)、搜索索引(ElasticSearch)或分析引擎(Hive)。这个步骤会变得非常麻烦。你可能需要引入某种队列(Kafka),确保所有这些衍生源都保持最新状态。这里面的工作涉及配置机器、引入服务发现和整个 shebang 等操作。

  可为什么要这么复杂呢?在一个常规数据库中,你可以执行以下操作:

  CREATE INDEX ...

  复制代码

  对于其他服务,我们为什么不能这样做?Martin Kleppman 在他的《数据密集型应用程序》中提出了这样一种语言:

  db |> ElasticSearch

  db |> Analytics

  db.user |> Redis

  // Bam, we've connected elastic search, analytics, and redis to our db

  复制代码

  我们都列举到了 J。但这些只是你开始构建应用程序后才开始面临的问题。那么在开始构建之前呢?

  也许今天对开发人员来说最难办的问题是上手。如果你想存储用户信息并显示一个页面,你会怎么做?

  以前,你只需要一个index.html和 FTP 就行了。现在,你需要 webpack、typescript、大量的构建过程,经常还需要多个服务。活动的部件太多了,迈出第一步都绝非易事。

  这似乎是一个菜鸟才需要面对的问题,似乎有经验的程序员上手起来会快很多。我认为情况更复杂一些。大多数项目都处于边缘场景——它们不是你日常应对的那种类型。这意味着原型制作阶段哪怕只多了几分钟,也可能会让我们淘汰很多项目。

  简化这一步骤将大大增加我们可以使用的应用程序数量。如果这一阶段能比index.html和 FTP 更容易完成呢?

  这问题可是真够多的。情况看起来很糟糕,但如果你回过头看看区区几年前的样子,就会发现我们已经有了这么大的进步。不管怎样,我们不再需要自己应付那些机架了。如同文艺复兴时代一样,很多杰出的人才正在努力开发这些问题的解决方案。这些方案有哪些代表呢?

  我认为 Firebase 在推动 Web 应用程序开发方面做了一些最具创新性的工作。他们做的最重要的一件事情就是浏览器上的数据库。

  有了 firebase,你可以像在服务器上一样查询数据。通过这种抽象,他们解决了上面列出的 A-E 问题。Firebase 可以处理乐观更新,默认就是响应式的。它提供了对权限的支持,从而消除了对端点的需求。

  K 问题也可以从中大大获益:我认为它的原型制作速度表现还是市面上最出色的。你只需从index.html开始就行了!

  但它也有两个问题:

  第一,查询能力。Firebase 选择的文档模型简化了抽象管理,但会破坏你的查询能力。很多时候,你必须对数据做反正则化,或者查询变得很难处理。例如,要记录像好友这样的多对多关系,你需要执行以下操作:

  userA:

  friends:

  userBId: true

  userB:

  friends:

  userAId: true

  复制代码

  你通过两个不同的路径(userA/friends/userBId)和(userB/friends/userAId)对好友关系进行反正则化。要获取完整数据,你需要手动复制一个联接(join):

  1. get userA/friends

  2. for each id, get /${id}

  复制代码

  这种关系在你的应用程序中很快就会出现。如果能有解决方案帮助你处理它就太好了。

  第二,权限。Firebase 要求你使用一种受限的语言来编写权限。在实践中,这些规则很快就会变得非常混乱——于是人们开始自己编写一些高级语言并编译成 Firebase 规则。

  我们在 Facebook 对此进行了大量实验,得出的结论是,你需要一种真正的语言来表达权限。如果 Firebase 有这样的语言就会更加强大。

  至于剩下的项目(审计、撤消/重做、写入的离线模式、衍生数据)——Firebase 还没有解决它们。

  Supabase 正在尝试做 Firebase 为 Mongo 所做的事情,但 Supabase 是为 Postgres 做的。如果他们成功了,这将是一个非常有吸引力的选择,因为它将解决 Firebase 面临的最大问题:查询能力。

  到目前为止,Supabase 取得了一些重大进展。他们的身份验证抽象非常棒,这让它成为少数几个像 firebase 一样容易上手的平台之一。

  他们的实时选项允许你订阅行级更新。例如,如果我们想知道一个好友是何时被创建、更新或更改的,我们可以这样写:

  const friendsChange=supabase

  .from('friendships:friend_one_id=eq.200')

  .on('*', handleFriendshipChange)

  .subscribe()

  复制代码

  在实践中这可以让你走得更远。不过它可能会变得很麻烦。例如,如果我们创建了一个好友,我们可能没有用户信息,所以必须获取它。

  function handleFriendshipChange(friendship) {

  if (!userStore.get(friendship.friend_two_id)) {

  fetchUser(...)

  }

  }

  复制代码

  这里指出了 Supabase 的主要弱点:它还没有“浏览器上的数据库”这种抽象。虽然你可以做查询,但你要自己负责正则化并处理数据。这意味着它不能自动进行乐观更新,不能做响应式查询等。他们的权限模型也很像 Firebase,因为它遵循了 Postgres 的行级安全性。一开始这是很好用的,但就像 Firebase,它很快就会变得很麻烦。这些规则往往会拖慢查询优化器的速度,并且 SQL 本身会变得越来越难推理。

  GraphQL 是一种很好的方法来声明性地定义你想要从客户端获取的数据。像 Hasura 这样的服务可以使用像 Postgres 这样的数据库,并做一些聪明的事情,比如给你一个 GraphQL API。

  Hasura 很适合读取数据。他们在处理联接方面做得很聪明,并且可以给你一个很好的数据视图。你可以用一个 flip 将任何查询转换为订阅。当我第一次尝试将查询转换为订阅时,确实感觉这很神奇。

  今天 GraphQL 工具的一大问题是它们的二手原型制作速度。你往往需要多个不同的库和构建步骤。他们在数据写入方面做得也没那么好。乐观更新不会自动发生——你必须自己处理它。

  我们已经研究了三个最有前途的解决方案。现在,Firebase 可以立刻解决大多数问题。Supabase 以牺牲更多客户端支持为代价为你提供了更好的查询能力。Hasura 以牺牲原型制作速度为代价,为你提供了更强大的订阅和更强大的本地状态。据我所知,还没有方案能在客户端解决冲突,提供撤消/重做和强大的响应式查询。

  现在的问题是:这些工具会演变成什么样子?

  在某些层面,未来已经到来了。例如,我认为 Figma 就是一款来自未来的应用:它可以出色地处理离线模式、撤消/重做和多人关系。如果我们想制作这样的应用,理想的数据抽象应该是什么样的?

  从浏览器来看,这种抽象必须像 firebase 一样,但要有强大的查询语言。

  你应该能够查询本地数据,并且它应该与 SQL 一样强大。你的查询应该是响应式的,如果有更改会自动更新。它也应该为你处理乐观更新。

  user=useQuery("SELECT * FROM users WHERE id=?", 10);

  复制代码

  接下来,我们需要一种可组合的权限语言。FB 的 EntFramework 也是我经常使用的例子,因为它非常强大。我们应该能够定义实体的规则,并且应该保证我们不会意外看到不允许我们看到的东西。

  User {

  view: [

  IAllowIfAdmin(),

  IAllowIfFriend(),

  IAllowIfSameUser(),

  ]

  write: [

  IAllowIfAdmin(),

  IAllowIfFriend(),

  ]

  }

  复制代码

  最后,这个抽象应该让我们更容易实现离线模式,或者撤消重做。如果发生本地写入,并且服务器上存在写入冲突,则应该有一个协调器在大多数情况下做出正确的决定。如果有问题,我们应该能够朝着正确的方向推动它前进。

  无论我们选择什么抽象,它都应该让我们能够在离线时运行写入操作。

  最后,我们应该能够表达数据依赖关系,而无需启动任何东西。一个简单的命令:

  db.user |> Redis

  复制代码

  对用户的所有查询都应该神奇地被 Redis 缓存。

  好吧,这些需求听起来很神奇。那么今天满足它们的实现会是什么样子?

  在 Clojure 世界中,人们长期以来一直是 Datomic 的粉丝。Datomic 是一个基于事实的数据库,可以让你“看到时间线上的每一个更改”。Nikita Tonsky 还实现了 datascript,这是一个与 Datomic 语义相同的客户端数据库和查询引擎!

  它们已被用于构建支持离线的应用程序(如 Roam)或协作应用程序(如 Precursor)。如果我们在后端打包一个类似 Datomic 的数据库,在前端打包一个类似 datascript 的数据库,它就可以成为“具有强大查询语言的客户端数据库”!

  Datomic 让你可以轻松地将新提交的事实订阅到数据库。如果我们在顶层创建一个服务,让它保留查询并听取这些事实,是不是会很棒?出现一个更改后,我们将更新相关查询。突然之间,我们的数据库变成实时的了!

  我们的服务器可以接受一些代码片段,并在获取数据时运行它们。这些片段将负责处理权限,为我们提供强大的权限语言!

  最后,我们可以编写一些 DSL,让你可以根据用户的喜好将数据通过管道传输到 Elastic Search、Redis 等。

  有了它,我们就有了一个优秀的方案。

  那么,为什么这种方案还不存在呢?那是因为……

  如果我们使用 Datomic 这样的数据库,我们就不会再使用 SQL。Datomic 使用一种基于逻辑的查询语言,称为 Datalog。现在它与 SQL 一样强大,甚至更为强大。唯一的问题是,对于外行来说,它看起来非常难上手的样子:

  [:find [(pull ?c [:conversation/user :conversation/message]) ...]

  :where [?e :session/thread ?thread-id]

  [?c :conversation/thread ?thread-id]]

  复制代码

  这个查询将查找当前“会话”中活动线程的所有消息以及用户信息。不错!一旦你学会了它,就会意识到它是一种优雅而出色的语言。但我认为这还不够。原型制作速度需要非常快才行,我们可能没时间去学这种语言了。

  有一些有趣的实验可以简化这一过程。例如,Dennis Heihoff尝试使用自然语言。这给我们启发了一种有趣的解决方案:我们能否编写一种稍微冗长但更加自然的查询语言,把它编译为 Datalog?我认同这种想法。

  另一个问题是数据建模也与人们习惯的做法不一样。Firebase 是黄金标准,你可以在不指定任何 schema 的情况下编写你的第一个更改。

  虽然做起来很难,但我认为我们的目标应该是尽可能接近“简单易用”。Datascript 只要求你指明引用和多值属性。Datomic 需要一个 schema,但也许如果我们使用开源的、基于 datalog 的数据库,我们可以增强它来做类似的事情。要么尽可能少用 schema,要么是“神奇的可检测 schema”。

  SQL 和 Datalog 都存在的一个大问题是,它们很难基于一些新的更改来确定哪些查询需要更新。

  我不认为这是不可能解决的障碍。Hasura 可以做轮询,而且可扩展。我们也可以尝试使用特定的订阅语言,类似于 Supabase。如果我们可以证明某些查询只能通过事实的某些子集来更改,我们可以将它们从轮询中移出。

  这是一个棘手的问题,但我认为它还是可以解决的。

  让权限检查成为一种成熟的语言的话,一个问题是我们容易过度获取数据。

  我认为这个问题是值得考虑的,但如果使用像 Datomic 这样的数据库,我们就可以解决它。数据读取很容易扩展和缓存。因为一切都是事实,我们可以创建一个界面来引导人们只获取他们需要的值。

  Facebook 就做到了这一点。这可能会很难,但终究是可行的。

  框架通常无法通用化。例如,如果我们想共享鼠标位置怎么办?这是短暂的状态,不适合数据库,但我们确实需要让它实时化——我们应该把它保存在哪里?如果你构建这样的抽象,将会出现很多这样的事情,并且你很可能会搞错。

  我认为这确实是一个问题。如果有人要解决这个问题,最好的办法是采用 Rails 方法:使用它构建一个生产应用,并将内部组件提取为产品。我认为他们很有可能找到正确的抽象。

  这类产品的共同问题是,人们只会将它们用于业余爱好项目,而且里面不会有很多商机。我认为 Heroku 和 Firebase 在这里指明了正确的出路。

  大企业都是从业余项目开始起家的。老一辈工程师可能将 Firebase 视为玩具,但现在许多成功的初创公司都在使用 Firebase。它不仅仅是一个数据库,也许它还会成为一个全新的平台——甚至是 AWS 的继任者。

  市场竞争非常激烈,用户变化无常。Slava 的《为什么RethinkDB会失败》描绘了在开发工具市场中获胜的难度有多大。我不认为他是错的。这样做需要对如何构建护城河并扩展成下一个 AWS 给出令人信服的回答。

  好吧,我们涵盖了痛点,讨论了竞争对手,介绍了理想的解决方案,并考虑了诸多问题。谢谢你陪我走过这段旅程!

相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
目录
相关文章
|
12天前
|
缓存 监控 前端开发
构建高性能Web应用的最佳实践
【8月更文挑战第8天】构建高性能的Web应用需要综合考虑多个方面,包括选择合适的技术栈、优化后端和前端性能、以及进行性能测试和监控。通过遵循这些最佳实践,开发者可以构建出高效、可扩展且用户友好的Web应用,提升用户体验和业务价值。
|
18天前
|
缓存 安全 网络协议
Web应用程序的DDoS攻击防护技术详解
【8月更文挑战第2天】DDoS攻击对Web应用程序的稳定性和可用性构成严重威胁。然而,通过综合运用上述防护技术,可以构建一个多层次、立体化的DDoS防御体系,有效应对各类攻击,最大限度地保障服务的连续性和可用性。网站程序开发人员和安全人员应密切关注最新的安全技术和趋势,不断优化和调整防护措施,确保Web应用程序的安全稳定运行。
|
19天前
|
移动开发 开发框架 小程序
开发H5程序或者小程序的时候,后端Web API项目在IISExpress调试中使用IP地址,便于开发调试
开发H5程序或者小程序的时候,后端Web API项目在IISExpress调试中使用IP地址,便于开发调试
|
19天前
|
SQL 关系型数据库 PHP
深入PHP:构建高效Web应用的实用指南
【8月更文挑战第1天】本文将引导您深入了解PHP,并探讨如何利用这一流行的服务器端脚本语言来构建高效的Web应用程序。我们将通过实际代码示例来展示PHP的强大功能和灵活性,包括数据处理、数据库交互以及安全性考虑。准备好一起探索PHP的世界,并学习如何优化您的开发流程吧!
14 2
|
1天前
|
前端开发 测试技术 定位技术
Web Accessibility基础:构建无障碍的前端应用
网络无障碍确保所有人平等访问网站与应用,覆盖视觉、听觉等多种障碍。关键技术包括:文本替代(alt属性)、ARIA角色、清晰表单标签、键盘导航、颜色对比、视觉隐藏、ARIA live区域、触控优化、语义化编码、视觉反馈、语音支持、易读文本、状态指示、色盲友好、屏幕阅读器兼容、响应式设计、媒体内容字幕、定期测试、图像地图、语音合成、明确错误提示及焦点管理。
4 0
|
23天前
|
存储 监控 安全
如何构建安全的Web应用程序:全方位指南
【7月更文挑战第28天】构建安全的Web应用程序是一个持续的过程,需要贯穿于整个应用程序的生命周期中。通过规划阶段的安全设计、开发阶段的安全措施实施、测试阶段的漏洞发现与修复以及部署与运维阶段的持续监控与维护,可以显著提高Web应用程序的安全性。希望本文的全方位指南能够为您在构建安全的Web应用程序方面提供有益的参考。
|
8天前
|
Java Maven Windows
java -jar 启动 boot 程序 no main manifest attribute, in .\vipsoft-model-web-0.0.1-SNAPSHOT.jar
java -jar 启动 boot 程序 no main manifest attribute, in .\vipsoft-model-web-0.0.1-SNAPSHOT.jar
30 0
|
13天前
|
网络安全 数据安全/隐私保护 iOS开发
【Mac os】如何在服务器上启动Jupyter notebook并在本地浏览器Web端环境编辑程序
本文介绍了如何在服务器上启动Jupyter Notebook并通过SSH隧道在本地浏览器中访问和编辑程序的详细步骤,包括服务器端Jupyter的启动命令、本地终端的SSH隧道建立方法以及在浏览器中访问Jupyter Notebook的流程。
21 0
|
18天前
|
JavaScript 前端开发 中间件
打造卓越后端:构建高效API的最佳实践与实战代码示例——解锁高性能Web服务的秘密
【8月更文挑战第2天】构建高效后端API:最佳实践与代码示例
33 0
|
19天前
|
测试技术 Shell 数据库
Django视图测试:构建可靠Web应用的关键步骤
Django视图测试:构建可靠Web应用的关键步骤
15 0