关于elixir:使用-Phoenix-LiveView-构建-Instagram-4

应用PETAL(Phoenix、Elixir、TailwindCSS、AlpineJS、LiveView)技术栈构建一个简化版的Instagram Web应用程序<!--more--> 在第 3 局部中,咱们增加了个人资料页面以及关注和显示帐户的性能,在这部分中,咱们将解决用户的帖子。您能够赶上Instagram 克隆 GitHub Repo。 让咱们首先增加一个路由来显示用于增加帖子的表单,关上lib/instagram_clone_web/router.ex: scope "/", InstagramCloneWeb do pipe_through :browser live "/", PageLive, :index live "/:username", UserLive.Profile, :index live "/p/:id", PostLive.Show # <-- THIS LINE WAS ADDED end scope "/", InstagramCloneWeb do pipe_through [:browser, :require_authenticated_user] get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email live "/accounts/edit", UserLive.Settings live "/accounts/password/change", UserLive.PassSettings live "/:username/following", UserLive.Profile, :following live "/:username/followers", UserLive.Profile, :followers live "/p/new", PostLive.New # <-- THIS LINE WAS ADDED end在文件夹中创立咱们的实时视图文件lib/instagram_clone_web/live/post_live: ...

September 1, 2023 · 6 min · jiezi

关于elixir:使用-Phoenix-LiveView-构建-Instagram-2

应用PETAL(Phoenix、Elixir、TailwindCSS、AlpineJS、LiveView)技术栈构建一个简化版的Instagram Web应用程序<!--more--> 在第 1 局部中,咱们已实现所有设置并筹备好根本布局,让咱们开始解决用户设置。您能够赶上Instagram 克隆 GitHub Repo。 让咱们首先创立路由,关上lib/instagram_clone_web/router.ex并在范畴下增加以下 2 条路由:require_authenticated_user: scope "/", InstagramCloneWeb do pipe_through [:browser, :require_authenticated_user] get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email live "/accounts/edit", UserLive.Settings live "/accounts/password/change", UserLive.PassSettings end而后咱们须要创立这些 liveview 文件,在该文件夹内创立一个名为user_liveunder 的文件夹lib/instagram_clone_web/live,增加以下 4 个文件: lib/instagram_clone_web/live/user_live/settings.ex lib/instagram_clone_web/live/user_live/settings.html.leex lib/instagram_clone_web/live/user_live/pass_settings.ex lib/instagram_clone_web/live/user_live/pass_settings.html.leex 在咱们的导航题目中,咱们须要链接到该新路线,lib/instagram_clone_web/live/header_nav_component.html.leex在第 60 行关上,将以下内容增加到Settings live_patch to: <%= live_patch to: Routes.live_path(@socket, InstagramCloneWeb.UserLive.Settings) do %> <li class="py-2 px-4 hover:bg-gray-50">Settings</li><% end %>当初,当咱们拜访该链接时,咱们应该会呈现谬误,因为文件是空的,因而关上lib/instagram_clone_web/live/user_live/settings.ex并增加以下内容: defmodule InstagramCloneWeb.UserLive.Settings do use InstagramCloneWeb, :live_view @impl true def mount(_params, session, socket) do socket = assign_defaults(session, socket) {:ok, socket} endend当初咱们应该有一个空白页面,只有顶部导航栏,所以让咱们开始工作吧。 ...

September 1, 2023 · 11 min · jiezi

关于elixir:使用-Phoenix-LiveView-构建-Instagram-1

应用PETAL(Phoenix、Elixir、TailwindCSS、AlpineJS、LiveView)技术栈构建一个简化版的Instagram Web应用程序<!--more--> 更好的学习办法是亲自动手构建货色,让咱们应用很棒的 PETAL(Phoenix、Elixir、TailwindCSS、AlpineJS、LiveView)堆栈构建一个简化版的 Instagram Web 应用程序,并深刻理解函数式的黑暗世界编程和最热门的孩子在凤凰框架与LiveView。 我不认为本人是一名老师,也不是任何方面的专家,我只是一个像你一样的普通人。任何人都能够遵循,即便您可能会被整个堆栈吓倒,这是一种新技术,不是很风行,而且没有很多资源和资料。如果您是一位经验丰富的开发人员,那么您不会有任何问题,这并不意味着如果您是初学者,您就无奈跟上,我会尽力使其对初学者敌对,但我不会具体介绍堆栈的每个基础知识或网络开发,所以你曾经被正告了。 Elixir 是我有幸学习和尝试的最好的语言之一,我想与世界分享我的激情,我心愿其他人能感触到我对这门语言的感触。 免责申明: Elixir、函数式编程、Phoenix 框架,可能听起来、看起来很艰难和简单,但它基本不是,比其余任何货色都容易,它可能不适宜每个人,因为咱们的想法并不相同,但对于那些认为就像我尝试的感觉一样。TailwindCSS 可能有些回心转意,看起来不值得尝试,我晓得,因为我也是这么感觉的,但只有尝试一下,你用得越多,它就会变得越有意义,你就会越喜爱它,它让 CSS 变得不简单,让你不放弃前端开发,CSS依然会很苦楚,作为开发者,咱们没有急躁把UI弄好,但它是一股新鲜空气。 咱们不会在本文中实现整个我的项目,这将是一系列文章,因而这将是第 1 局部。我假如您有本人的开发环境,装置了 Elixir,我的开发环境是在带有 WSL 的 Windows 10 上。咱们将尽力尽可能具体,但放弃简略,这仅用于学习目标,因而它不会是准确的正本,也不会具备所有性能,咱们将尽可能靠近实在的货色,也咱们不会专一于使网站具备响应能力,咱们只会使其实用于大屏幕。 让咱们首先转到终端并应用 LiveView 创立一个新的 Phoenix 应用程序。 $ mix phx.new instagram_clone --live 装置并获取所有依赖项后。 $ cd instagram_clone && mix ecto.create 我创立了一个 GitHub 存储库,您能够在此处拜访Instagram 克隆 GitHub 存储库,您能够随便应用代码,欢送奉献。 让咱们运行服务器以确保一切正常。 $ iex -S mix phx.server 如果没有谬误,当您拜访 http://localhost:4000/ 时,您应该会看到默认的 Phoenix 框架主页 我应用 Visual Studio Code,因而我将应用以下命令关上我的项目文件夹。 $ code . 当初让咱们在 mix.exs 文件中增加混合依赖项。 ...

September 1, 2023 · 14 min · jiezi

关于elixir:译-Nx-入门-Sean-Moriarity

Nx 是一个 BEAM 上的,用于操作张量(tensor)和数值计算的新库。Nx 冀望为elixir、erlang以及其它 BEAM 语言关上一扇大门,通往一个簇新的畛域 -- 用户可能应用 JIT 和高度特殊化的 tensor 操作来减速他们的代码。本文中,你会学到根底的操作 Nx 的办法,以及如何将其用于机器学习利用中。 适应 TensorNx 的 Tensor 相似于 PyTorch 或 TensorFlow 的 tensor,NumPy 的多维数组。用过它们,那就好办。不过它与数学定义不完全一致。Nx 从 Python 生态里借鉴了许多,所以适应起来应该是很容易。Elixir 程序员能够把 tensor 设想为嵌套列表,附带了一些元数据。 iex> Nx.tensor([[1, 2, 3], [4, 5, 6]])#Nx.Tensor< s64[2][3] [ [1, 2, 3], [4, 5, 6] ]>Nx.tensor/2 是用来创立 tensor 的,它能够承受嵌套列表和标量: iex> Nx.tensor(1.0)#Nx.Tensor< f32 1.0>元数据在 tensor 被检视时能够看到,比方例子里的 s64[2][3] 和 f32。Tensor 有形态和类型。每个维度的长度所组成的元祖形成了形态。在下面的例子里第一个 tensor 的形态是 {2, 3},示意为 [2][3]: iex> Nx.shape(Nx.tensor([[1, 2, 3], [4, 5, 6]])){2, 3}把 tensor 设想为嵌套列表的话,就是两个列表,每个蕴含3个元素。嵌套更多: ...

February 18, 2023 · 5 min · jiezi

关于elixir:使用-Phoenix-LiveBook-做一个小实验-实时编码部署http服务

LiveBook 是 elixir 团队新推出的一款利用,能够应用它很不便地在浏览器中编写文章,并且在其中运行 elixir 代码。 我很好奇是否应用 LiveBook 间接更改以后服务器的路由配置,使得咱们能够实现实时部署服务。比方咱们在 LiveBook 里写好一个页面,而后间接配置到某个 url 门路上,他人就能够拜访到。这样感觉十分酷,省去了繁琐的配置打包和公布的流程,而且从实践上是齐全能够实现的。 说干就干,首先我在 fly.io 上部署了一个收费的 livebook 实例,你也能够在本地部署,很不便的。 启动之后在左侧的配置按钮里抉择 Runtime settings,选则 Embedded。即在 livebook 自身的 erlang node 里执行代码。livebook 为了保障安全性和隔离性,默认是会另启动一个 node 来执行代码的,也就是 Elixir standalone 选项,但这样咱们是无奈批改路由配置的。 批改好之后,我新建了一个文档,就能够开始写代码了。Livebook 应用的是 Phoenix 框架,其底层的 HTTP 服务器是 Cowboy,再底层是 ranch。所以咱们先通过 :ranch.info() 来获取以后服务器的一些信息。从返回值里咱们晓得了以后的 ranch server 的ref是 LivebookWeb.Endpoint.HTTP,所有的路由(或者叫散发 Dispatch) 配置都是在这外面保留。下一步咱们就能够对 dispatch 配置进行批改。 每个http申请都会被散发到不同的 handler(这个怎么翻译来着,抓手?),所以咱们首先须要写一个用于测试的 handler。 defmodule TestHandler do @behaviour :cowboy_handler def init(req, state) do req = :cowboy_req.reply(200, %{ "content-type" => "text/plain" }, "Hello World!", req) {:ok, req, state} endend它的性能非常简单,无论收到什么都返回你好世界。接下来将它配置到咱们的 dispatch 外面,留神要保留之前的 dispatch 内容,否则咱们的 livebook 就拜访不了了哈哈。 ...

June 13, 2022 · 1 min · jiezi

关于elixir:了解Flow-elixir的并行计算库

“咱们不短少计算机,短少的是聪慧地应用计算机的办法。”日常编程的时候,我有时候会不盲目的把计算机当成一个人,以对人谈话的形式来给计算机布置任务。然而,计算机和人类的一个次要区别就是,它会一字不差地执行程序,遇到非凡状况时不会做变通。 比方咱们想统计一个文件里的词频,最直观的形式就是: File.stream!("path/to/some/file")|> Enum.flat_map(&String.split(&1, " "))|> Enum.reduce(%{}, fn word, acc -> Map.update(acc, word, 1, & &1 + 1)end)|> Enum.to_list()第一行是应用 File.stream!/1 关上文件,它能够让咱们逐行读取文件,这一步不会把文件内容读取进去。第二行就不得了了,会把文件的全部内容都读取到内存中。在这里如果文件过大,有可能间接就撑爆内存了。 File.stream!("path/to/some/file")|> Stream.flat_map(&String.split(&1, " "))|> Enum.reduce(%{}, fn word, acc -> Map.update(acc, word, 1, & &1 + 1)end)|> Enum.to_list()既然 Enum.flat_map/2 太过暴力,咱们就用 Stream.flat_map/2 来代替它,这样,在第二行仍旧不会读取任何文件内容。到第三行的 Enum.reduce/3 这里会开始逐行读取文件内容并且应用一个 hash map 来统计词频。这样做根本不会呈现内存爆炸的状况了。当初的处理器根本都是多核的,咱们能不能把多核处理器利用起来呢? 不便起见,咱们用上面这个列表示意文件的每一行(只管这样就无奈体现出解决大文件的特点了,但咱们只有晓得程序不会一下子读取全部内容到内存就行了) data = [ "rose are red", "violets are blue"]第一步,和 Stream 相似,咱们生成一个 lazy 的 Flow 数据结构: opts = [stages: 2, max_demand: 1]flow = flow |> Flow.from_enumerable(opts)%Flow{ operations: [], options: [stages: 2, max_demand: 1], producers: {:enumerables, [["rose are red", "violets are blue"]]}, window: %Flow.Window.Global{periodically: [], trigger: nil}}stages 能够了解为并行的外围数量,实质上是参加并行处理的gen_stage 过程数量。这里咱们设置为2,与双核机器上的默认配置雷同。 ...

May 10, 2022 · 2 min · jiezi

关于elixir:elixir-0083-Streamtransform-的用法

在 elixir 里能够用 Stream 来示意有限长的序列,例如 0,1,2,3... 就能够示意为: iex> s = Stream.iterate(0, & &1 + 1)#Function<62.50989570/2 in Stream.unfold/2>如果咱们想要计算这个数列中每5个数的和,就能够应用 Stream.transform 函数: iex> s1 = Stream.transform(s, {0, 0}, fn x, {sum, count} ->...> if count == 5 do ...> {[sum], {x, 1}} ...> else...> {[], {sum + x, count + 1}} ...> end...> end)#Function<60.50989570/2 in Stream.transform/3>iex> Enum.take(s1, 10)[10, 35, 60, 85, 110, 135, 160, 185, 210, 235]它是 Enum.flat_map_reduce 的 Stream 版本。

December 26, 2021 · 1 min · jiezi

关于elixir:elixir-0082-application-controller-应用是如何被载入和启动的

相熟 erlang/elixir 的敌人们应该晓得 application 的概念,它是一种非凡的构造,用于启动和进行一个利用。每当咱们新建一个 erlang/elixir 我的项目,也同时新建了一个同名的 利用。在应用依赖库的时候,个别每个依赖库也是一个利用,会在咱们运行我的项目时被载入和启动。 那么,erlang是如何治理这些利用的呢?这就是 :application_controller 发挥作用的中央了。所有的利用的载入、启动等状态的保留和变更,都要通过这个过程。 # 列出以后的全副利用状态> :application_controller.info()虚利用不是所有的利用都会启动过程树,有些利用即便处于 :started 状态,也没有启动任何过程。权且称这种利用为虚利用吧。 跟踪利用我之前文章里提到的 bony_trance 库来跟踪一下 在load 和 start 一个利用时,:application_controller 过程都做了什么吧。 stop 利用iex(3)> :application_controller.stop_application :play#PID<0.44.0> RECEIVED +67.069019sMESSAGE: {:"$gen_call", {#PID<0.199.0>, #Reference<0.2783639343.3287547907.121340>}, {:stop_application, :play}}#PID<0.44.0> SENT TO: :code_server +0.000047sMESSAGE: {:code_call, #PID<0.44.0>, {:ensure_loaded, Logger.Translator}}#PID<0.44.0> RECEIVED +0.004582sMESSAGE: {:code_server, {:module, Logger.Translator}}#PID<0.44.0> SENT TO: :code_server +0.000033sMESSAGE: {:code_call, #PID<0.44.0>, {:ensure_loaded, Logger.Utils}}#PID<0.44.0> RECEIVED +0.002413sMESSAGE: {:code_server, {:module, Logger.Utils}}#PID<0.44.0> SENT TO: :code_server +0.000012sMESSAGE: {:code_call, #PID<0.44.0>, {:ensure_loaded, :calendar}}#PID<0.44.0> RECEIVED +0.012388sMESSAGE: {:code_server, {:module, :calendar}}12:38:54.714 [info] Application play exited: :stopped:ok#PID<0.44.0> SENT TO: Logger +0.000015sMESSAGE: {:notify, {:info, #PID<0.64.0>, {Logger, ["Application ", "play", " exited: " | ":stopped"], {{2021, 11, 28}, {12, 38, 54, 714}}, [erl_level: :notice, domain: [:otp], error_logger: %{report_cb: &:application_controller.format_log/1, tag: :info_report, type: :std_info}, file: "application_controller.erl", function: "info_exited/3", gl: #PID<0.64.0>, line: 2119, mfa: {:application_controller, :info_exited, 3}, module: :application_controller, pid: #PID<0.44.0>, report_cb: &:application_controller.format_log/2, time: 1638074334714339]}}}#PID<0.44.0> SENT TO: #PID<0.199.0> +0.000015sMESSAGE: {#Reference<0.2783639343.3287547907.121340>, :ok}首先向 code_server 确认一些必要的用于打印log的模块是已载入的, 这一步的后果会缓存,之后不用反复询问。如果该利用有 application 过程,则会向其发送 :stop 音讯。最初打印出利用已进行的log。 ...

November 28, 2021 · 2 min · jiezi

关于elixir:elixir-0080-读-erlang-开发团队博客-之-N-对-1-并行消息的性能优化

自从 erlang OTP 团队开设技术博客以来,很多高质量的文章让咱们有机会可能理解 erlang 外部的各种机制。 譬如最近的这篇 https://www.erlang.org/blog/p... ,就讲述了在 erlang 虚拟机中是如何对 “N对1” 的过程消息传递进行性能优化的。 本文只是站在笔者的角度对文章内容进行转述,如有了解谬误或者不到位的中央,敬请在评论中指出。 下面这张图很直观地体现了优化的成果,这是在多核机器上,很多过程同时向一个过程发送短消息的性能比照。其中横轴是过程数量,纵轴是每秒操作数。能够看到在优化后,曾经实现了程度扩大,即过程数量越多,每秒操作数越多。而在优化之前,过程数越多,性能越低。 在深刻理解这个优化是如何做到的之前,先来理解一下 erlang 虚拟机中的信号(signal)机制。 在 erlang 虚拟机中,实体(entity)代表所有并发执行的货色,包含过程、Port 等等。一般的过程音讯也是一种信号。信号的程序遵循以下规定: 如果实体 A 先发送信号 S1 给 B, 而后发送 S2 给 B。那么 S1 保障不会在 S2 之后达到。 艰深地讲,设想一条N个车道的公路,不容许超车,那么在同一条车道上,汽车的程序是肯定的;而不同车道之间,汽车的前后总是在变动。 下图是在优化之前,一个过程内简略构造。 过程发送音讯的步骤是这样的: 调配一个链表的节点,其中蕴含信号获取外信号队列(OuterSinalQueue)的锁将信号节点增加到外信号队列的前面开释锁。过程收取音讯的步骤是这样的: 获取外信号队列的锁将外信号队列的内容增加到内信号队列(InnerSinalQueue)前面开释锁。以上是选项 {message_queue_data, off_heap} 开启时的机制。而默认的选项是 {message_queue_data, on_heap}, 本次的这个优化其实只作用于 off_heap 的状况,也就是如果咱们没有对 message_queue_data 这个选项进行配置,那么这个优化就和咱们无关。那么默认状况下的消息传递步骤是什么呢?尽管和这个优化无关,但文章里还是具体介绍了一下: 发送音讯的步骤: 尝试用 try_lock 来获取主过程锁(MainProcessLock)。如果胜利:1.在过程的主堆(main heap)上为信号调配空间,并将信号复制到那里2.调配一个链表节点,蕴含指向那个信号的地位的指针3.获取外信号队列锁4.将信号节点增加到外信号队列的前面5.开释外信号队列锁6.开释主过程锁如果失败:1.调配一个链表的节点,其中蕴含信号2.获取外信号队列锁3.将信号节点增加到外信号队列的前面4.开释外信号队列锁。能够看出 on_heap 的益处就是在获取主过程锁胜利的状况下,信号数据被间接复制到了过程的主堆上。害处就是须要获取主过程锁,来避免在这个过程中产生垃圾回收。所以,在十分多的过程同时给一个过程发消息的时候,off_heap具备更好的扩展性,因为不须要去争抢接收者的主过程锁。 尽管如此,外信号队列锁仍旧是一个性能瓶颈。 上面咱们能够聊聊如何优化了。 回顾咱们之前提到的 erlang 虚拟机对于信号程序的要求,能看出咱们须要的是一条N车道的公路,当初却只有一个收费站(接收者的外信号队列锁),车全堵在这了。优化的计划显然也跃然纸上了,就是减少“收费站“的数量。通过简略地对发送者过程的pid做哈希,将信号分流到64个 slot 队列中。 只有在同时获取外信号队列的过程数量超过肯定阈值的时候,此优化才会被触发。 ...

November 12, 2021 · 1 min · jiezi

关于elixir:elixir-0078-elixir-版本升级的历程111-112

想平常一样关上 github,发现我最喜爱的编程语言 elixir 公布了新的版本。立马下载安装,没想到一运行公司的我的项目,爆了一堆谬误。 1首先看到的是: warning: ^^^ is deprecated. It is typically used as xor but it has the wrong precedence, use Bitwise.bxor/2 instead查了一下 Changelog,原来这个函数曾经被淘汰了,像提醒里说的那样改为 Bitwise.bxor/2 就能够了。 2而后 elixir 1.12 版本修改了一个对于 behaviour 的bug。之前如果某个 callback 的实现函数没有标注 @impl true 的话是不会有正告的。当初会报: warning: module attribute @impl was not set for function xxx/2 callback (specified in XXX). This either means you forgot to add the "@impl true" annotation before the definition or that you are accidentally overriding this callback咱们把 @impl true 在对应函数下面加上就能够了。 ...

October 13, 2021 · 1 min · jiezi

关于elixir:Elixir-bcryptelixir-在-windows-中的-Compile-错误

形容 开发 Elixir 的时候,根本都是在 MacOS 或者 Linux 下。对于 bcrypt_elixir 这个包 须要用到 nmake 去构建和编译。 装置必须工具 先去下载安装 Microsoft Visual Studio, 我这里装置的是2019的。为了必须的 nmake 须要装置 应用 C++ 的桌面开发. 装置实现后,先设置 nmake到环境变量中。 如果不晓得 nmake 在哪,能够关上资源管理器 去到 C:\Program Files (x86)\Microsoft Visual Studio\2019 而后通过搜寻栏搜素 nmake.exe. 这里找到了4个,有两个是 x86 文件夹下, 两个是 x64 文件夹下. 具体设置那个到环境变量中,应用对应零碎是多少位的那个。例如我这里是 x64 文件夹下的。 设置后,从新 应用 mix deps.get 命令,可能提醒你须要应用 cmd /k "C:\Program Files (x86)\....\vcvarsall.bat" amd64 如果呈现下面提醒,跟着它操作就行了。只是把两头的 "C:\Program Files (x86)....\vcvarsall.bat" 换成你零碎中 vcvarsall.bat 所在的目录。也是去下面那个 2019 中搜寻进去。 最初就是执行 mix deps.compile 从新编译之前的 bcrypt_elixir 包了。 我这里中途呈现了 modules base64url not found 的问题,引入了base64url 包都不行。然而执行下面后,从新执行 mix compile, 而后 mix deps.get 后又能够了,起因不明。 ...

May 20, 2021 · 1 min · jiezi

关于elixir:遍历语法树的两种方式-prewalk-和-postwalk

在编辑形象语法树(AST) 的时候, 咱们常常须要遍历整个构造, elixir 规范库中提供了两种遍历形式. 举个例子, 有这样一个 AST: quote do add(1, 2)endquote 后的数据结构是这样: {:add, [], [1, 2]}用图像示意的话, 大略就是这样, 有很显著的层级关系: Prewalkprewalk 就是以从外层到内层的程序, 进行遍历. 留神最初返回的元组里, 第二个参数才是咱们的 acc. iex(5)> Macro.prewalk(ast, [], fn ...(5)> x, acc ->...(5)> {x, acc ++ [x]}...(5)> end...(5)> ){{:add, [], [1, 2]}, [{:add, [], [1, 2]}, 1, 2]}postwalk 正相反, 是从外向外遍历. iex(6)> Macro.postwalk(ast, [], fn...(6)> x, acc -> ...(6)> {x, acc ++ [x]} ...(6)> end ...(6)> ){{:add, [], [1, 2]}, [1, 2, {:add, [], [1, 2]}]}

February 26, 2021 · 1 min · jiezi

关于elixir:追求速度的极限-在elixir里使用-atomics-模块操作-mutable-数据

在 elixir 中罕用的数据结构都是不可变(immutable)的,也就是每次批改实际上是在内存中新建一个数据。不可变数据的益处是能够防止副作用,不便测试,缩小bug。毛病也很显著,就是速度慢。 例如 advent of code 2020 的第23天Part2 这道题,通过应用可变的数据结构,能够大幅晋升速度。 题目粗心是:给定一个数列,结尾9个数是 “389125467”,前面是 10 到 1百万,最初一个数连贯到结尾造成环。以 3 为以后数,执行以下操作 100 百万次 —— “将以后数左边3个数拿起,而后在残余的数字里寻找一个指标数,它比以后数小,且值最靠近,如果没有,则返回最大的数。将拿起的3个数插入到指标数的左边。以以后数左边的数作为新的以后数。” 在 elixir 里是无奈间接操作链表指针的,但咱们能够用 map 来模仿一个单链表: defmodule MapList do def new do %{} end def put(s, a, next) do Map.put(s, a, next) end def find(s, a) do Map.get(s, a) end def exchange(s, a, x) do Map.get_and_update!(s, a, fn c -> {c, x} end) end end上面是理论的解题逻辑: ...

December 30, 2020 · 2 min · jiezi

关于elixir:使用-Elixir-推导-Y-组合子

如何递归调用匿名函数,这个问题困扰我很久了。直到我据说了 Y 组合子。 一般的递归函数是这样的: defmodule M do def foo(x) do case x do 0 -> 0 n -> foo(n-1) + n end endend而后我一步步把它革新成匿名函数,首先,函数体大抵不会变: foo = fn x -> case x do 0 -> 0 n -> foo.(n-1) + n endend这里第二个 foo 的中央应该是 foo 这个函数自身被递归调用,然而这个时候 foo 的定义还没有实现。没关系,遇到不晓得的货色,就把它作为参数吧。 所以咱们批改了函数的定义,让它首先从参数 g 承受它自身的定义,因为 g 就是 bar, 为了失去下面的本来的 foo , 须要将它自身作为参数传递给本人,用 g.(g) 来失去 foo。 这里有点绕,可能须要多看几遍。 bar = fn g -> # 上一步里的 foo 从这里开始 fn x -> case x do 0 -> 0 n -> g.(g).(n-1) + n end endendbaz = bar.(bar)接下来想方法先把 g.(g)这个货色替换掉,替换的准则是不扭转运行时的执行逻辑: ...

December 29, 2020 · 2 min · jiezi

关于elixir:Advent-of-code-2020-elixir-解法回顾-上

Advent of code 2020 elixir 解法回顾 (上) 网络上有很多乏味的编程题库,其中 Advent of code 近几年收到越来越多人的关注。起因是题目很乏味,联合圣诞节主题,在圣诞节前的25天每天一题。另外不限度编程语言,只须要输出正确答案即可。每做出一题还会失去一颗圣诞树上的小星星,有成就感。往年我应用 elixir 来解题,转眼间曾经做了过半的题目,于是写一篇文章来回顾一下。如果你也想尝试解题,倡议做完再看。 第一天第一局部是从一个由数字组成的列表中,找到两个数,它们的和等于2020,返回它们的积。 为了让查问快一些,我用了一个 MapSet (查问复杂度 O(1)) 来代替 List (查问复杂度O(n)) 存储这些数字,而后遍历 List,在 MapSet 中寻找可能和以后的数相加等于 2020 的数。 Enum.reduce_while(list, mapset, fn x, acc -> if MapSet.member?(acc, 2020 - x) do {:halt, x * (2020 - x)} else {:cont, MapSet.put(acc, x)} endend)第二局部是把两个数变成了三个数。能够三次遍历 List,而后应用 raise 语句来抛出异样从在找到满足条件的数时突破循环。因为题目规定不能够重复使用同一个地位的数,所以须要记录每个数的 index。 也能够应用三次嵌套的 reduce_while 来寻找答案,益处是不须要 raise 和 index 了,毛病是代码看起来很丑。 Enum.reduce_while(list, list, fn x, list -> case Enum.reduce_while(tl(list), tl(list), fn y, list -> case Enum.reduce_while(tl(list), tl(list), fn z, list -> if x + y + z == 2020 do {:halt, x * y * z} else {:cont, tl(list)} end end) do [] -> {:cont, tl(list)} a -> {:halt, a} end end) do [] -> {:cont, tl(list)} a -> {:halt, a} endend)第二天第一局部和第二局部都是依据既定的规定来统计非法的 “明码” 的数量,不同之处是应用的规定不同。第一局部的规定是字符串里某个字母呈现的次数,例如 1-2 z 示意字符串里只能由 1 到 2 个 z 。比拟麻烦的中央可能就是把规定解码进去,如果你相熟正则表达式会比拟快。而后依据规定查看每个 "password"。 ...

December 14, 2020 · 5 min · jiezi

关于elixir:Phoenix-集成-ejabberd

mix.exs 增加依赖{:ejabberd, "~> 20.4"}配置 ejabberd application config/config.exsconfig :ejabberd, file: "config/ejabberd.yml", log_path: 'logs/ejabberd.log'config :mnesia, dir: 'mnesiadb/'下载官网示例配置文件到 config/ejabberd.yml https://github.com/processone... 编译mix.deps get mix compileopenssl 问题===> /Users/lidashuang/Github/ejabberd/deps/fast_tls/c_src/fast_tls.c:21:10: fatal error: 'openssl/err.h' file not foundopenssl https://github.com/processone... 设置环境变量 export LDFLAGS="-L/usr/local/opt/openssl/lib"export CFLAGS="-I/usr/local/opt/openssl/include/"export CPPFLAGS="-I/usr/local/opt/openssl/include/"

November 23, 2020 · 1 min · jiezi

关于elixir:elixir-0067-保存-IEX-的历史记录

平时应用 iex 进行调试时常常会遇到须要从新关上 iex 的状况,这时候方才输出的历史内容就全副失落了。那么如何让 iex 保留历史记录呢? 只须要在你的 shell 的 profile 外面加上这一行(例如 bash 对应的 ~/.bash_profile 文件: export ERL_AFLAGS="-kernel shell_history enabled"source 之后,iex就会保留历史记录啦。默认的下限是 512kb,如果想要更多,把命令改成: export ERL_AFLAGS="-kernel shell_history enabled -kernel shell_history_file_bytes 1024000"就能够啦.

October 30, 2020 · 1 min · jiezi

关于elixir:elixir-0061-高负载高并发问题的万能钥匙-队列queue

高负载高并发问题,不仅仅呈现在面试中,在日常生活中也很常见,比方周末去冷落的商场吃饭,餐厅们口常常会须要排队取号。能够演绎为“需要”和“资源”的不匹配,多进去的“需要”的不到满足,就须要有适合的机制让这些”需要“进行 期待 或者 撤销。 让咱们用 elixir 模仿这样一个场景:一个服务器里一共有 3 个座位,每进来1个客人就要占用一个座位,座位占满之后服务器就无奈提供服务。 defmodule M5 do use GenServer @seats 3 @wait_timeout 1000 def start() do GenServer.start(__MODULE__, :ok) end def enter(pid) do GenServer.call(pid, :enter, @wait_timeout) end def leave(pid) do GenServer.cast(pid, :leave) end def init(_) do {:ok, @seats} end def handle_call(:enter, {_pid, _ref}, seats) do IO.puts("got enter request") if seats > 0 do {:reply, :ok, print(seats - 1)} else {:noreply, print(seats)} end end def handle_cast(:leave, seats) do IO.puts("free seats: #{seats}") {:noreply, print(seats + 1)} end defp print(seats) do IO.puts("free seats: #{seats}") seats endend再定义这样一个函数,模仿客人们同时要求进入服务器,如果得不到响应,就会 BOOM! ...

October 23, 2020 · 2 min · jiezi

关于elixir:elixir-0060-玩转外部资源-Port-入门

在 erlang 虚拟机中,port 是链接 process 的消息传递世界,与 erlang 虚拟机之外 linux 零碎世界的桥梁。在 process 看来,port是一种非凡的资源。 让咱们来看看在 elixir 中能够如何操作 port。 新建 portiex(5)> port = Port.open('computer', [])#Port<0.5>留神 port 的 name 因为历史起因须要应用 charlist. 新建胜利后会失去一个 port 的 id。 查看 port 信息iex(6)> Port.info port[ name: 'computer', links: [#PID<0.105.0>], id: 40, connected: #PID<0.105.0>, input: 0, output: 0, os_pid: :undefined]咱们看到一个 port 能够 link 到多个 porcess,这里的 link 机制和 process 之间的 link 机制是一样的,即 crash 会传导。 一个 port 同时只能 connect 到一个 process。connect 意味着 port 的所有音讯都会发送到这个 process. ...

October 23, 2020 · 1 min · jiezi

关于elixir:elixir-0059-Elixir-是如何获取到-doc-内容的

初学 elixir 时就被它不便的文档编写形式所吸引,咱们能够这样编写模块的文档和函数的文档: defmodule M4 do @moduledoc """ Module doc for M4. """ @doc "function doc for f" def f do endend能够在 repl 里间接查看,也能够生成网页版的文档。 iex(2)> h M4 M4 Module doc for M4.iex(3)> h M4.f def f() function doc for f那么 elixir 到底是如何从代码文件中获取到 doc 内容的呢。 Code.fetch_docs规范库的 Code 模块里自带了很多用于解决源文件的函数,其中 Code.fetch_docs 能够间接获取一个模块里全副的 doc 内容: iex(1)> Code.fetch_docs M4{:docs_v1, 2, :elixir, "text/markdown", %{"en" => "Module doc for M4.\n"}, %{}, [{{:function, :f, 0}, 6, ["f()"], %{"en" => "function doc for f"}, %{}}]}让咱们来看看它的外部实现: ...

October 21, 2020 · 2 min · jiezi

关于elixir:elixir-0058-使用macro为函数添加参数

有时咱们在批改程序的时候只是心愿给旧的函数增加一个参数,咱们能够应用macro来简化这一流程。 这里是一个简略的例子,咱们应用了一个名为 define 的宏,它的作用是增加一个名为 state 的参数到函数里。 defmodule M3 do use M2, head: :state define f(:a, a) define f(:b, b) define g(a, c)endiex(1)> h M3.f def f(state, atom, a) define 宏的实现是这样的: defmodule M2 do defmacro __using__(opts) do head = opts[:head] Module.put_attribute(__CALLER__.module, :__head__, head) quote do import M2 end end defmacro define(call) do case Macro.decompose_call(call) do {f, a} -> mod = __CALLER__.module var = Module.get_attribute(mod, :__head__) |> Macro.var(nil) args = [var | a] quote do def unquote(f)(unquote_splicing(args)) do IO.puts("args: #{inspect(unquote(args))}") end end _ -> raise "invalid args" end endend次要的几个点: ...

October 21, 2020 · 1 min · jiezi

谈谈GenServer的terminate回调

概述GenServer的terminate callback在进程退出时会被调用. 但若没有:erlang.process_flag(:trap_exit, true), 进程可能被悄无声息地kill掉, 而不走terminate回调. 示例App.Supervisor下有一个App.Worker, 代码如下. defmodule App do use Application require Logger def start(_type, _args) do children = [ %{id: App.Supervisor, start: {App.Supervisor, :start_link, []}, type: :supervisor} ] opts = [strategy: :one_for_one, name: __MODULE__] Supervisor.start_link(children, opts) endenddefmodule App.Supervisor do use Supervisor require Logger def start_link() do Logger.info("app.sup start") Supervisor.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do children = [ %{id: App.Worker, start: {App.Worker, :start_link, []}, type: :worker} ] opts = [strategy: :one_for_one, name: __MODULE__] Supervisor.init(children, opts) endenddefmodule App.Worker do use GenServer require Logger require Record Record.defrecordp(:state, []) def start_link() do Logger.info("app.worker start") GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init(_) do # :erlang.process_flag(:trap_exit, true) {:ok, nil} end def handle_call(:stop, _from, state) do {:stop, nil, state} end def handle_call(:raise, _from, state) do raise RuntimeError {:reply, nil, state} end def terminate(reason, state) do Logger.warn("app.worker terminate #{inspect(reason)} #{inspect(state)}") :ok endend没有trap_exit若没有:erlang.process_flag(:trap_exit, true). ...

June 25, 2019 · 1 min · jiezi

Discord-CTO-谈如何支撑500W并发用户的应用Programming-in-Elixir

从一开始,Discord就是Elixir的早期使用者。 Erlang VM是我们打算构建的高并发、实时系统的完美候选者。我们用Elixir开发了Discord的原型,这成为我们现在的基础设施的基础。 Elixir的承诺很简单:通过更加现代化和用户友好的语言和工具集,使用Erlang VM的强大功能。 两年多的发展,我们的系统有近500万并发用户和每秒数百万个事件。虽然我们对选择的基础设施没有任何遗憾,但我们需要做大量的研究和实验才能达到这种程序。 Elixir是一个全新的生态系统,Erlang的生态系统缺乏在生产环境中的使用信息(尽管erlang in anger非常棒)。我们为Discord工作的过程中吸取了一系列的经验教训和创造了一系列的libs。 消息发布虽然Discord功能丰富,但大多数功能都归结为发布/订阅。用户连接WebSocket并启动一个会话process(一个GenServer),然后会话process与包含公会process(内部称为“Discord Server”,也是一个GenServer)的远程Erlang节点进行通信。当公会中发布任何内容时,它会被展示到每个与其相关的会话中。 当用户上线时,他们会连接到公会,并且公会会向所有连接的会话发布状态。公会在幕后有很多其他逻辑,但这是一个简化的例子: def handle_call({:publish, message}, _from, %{sessions: sessions}=state) do Enum.each(sessions, &send(&1.pid, message)) {:reply, :ok, state}end我们找到一个好方法。我们最初将Discord构建成只能创建少于25成员的公会。当人们开始将Discord用于大型公会时,我们很幸运能够出现“问题”。最终,用户创建了许多像守望先锋这样的Discord服公会,最多可以有30,000个并发用户。在高峰时段,我们开始看到这些process无法跟上其消息队列。在某个时刻,我们必须手动干预并关闭生成消息的功能以应对高负载。在达到超负载之前,我们必须弄清楚问题所在。 我们首先在公会process中对热门路径进行基准测试,并迅速发现了一个明显的问题。在Erlang process之间发送消息并不像我们预期的那么高效,并且reduction(用于进程调度的Erlang工作单元)也非常高。我们发现单次 send/2 调用的运行时间可能在30s到70us之间。这意味着在高峰时段,从大型公会(3W人)发布活动可能需要900毫秒到2.1秒! Erlang process实际上是单线程的,并行工作的唯一方法是对它们进行分片。这本来是一项艰巨的任务。 我们必须以某种方式分发发布消息的工作。由于Erlang中创建process很廉价,我们的第一个猜测就是创建另一个process来处理每次发布。但是,每次发布的时间安排不同,Discord客户端依赖于事件的原子一致性(linearizability)。该解决方案也不能很好地扩展,因为公会服务本身的压力并没有减轻。 受一篇关于提高节点之间消息传递性能的博客文章的启发,Manifold诞生了。 Manifold将消息的发送工作分配给PID的远程节点(Erlang进程标识符),这保证了发送进程最多只调用send / 2等于所涉及的远程节点的数量。 Manifold通过首先按其远程节点对PID进行分组,然后在每个节点上发送给Manifold.Partitioner来实现此目的。 然后,分区程序使用:erlang.phash2 / 2一致地散列PID,按核心数分组,并将它们发送给子工作者。 最后,这些工作人员将消息发送到实际进程。 这可以确保分区器不会过载,并且仍然提供send / 2保证的线性化。 这个解决方案实际上是send / 2的替代品: 受到一篇《Boost message passing between Erlang nodes》博客文章的启发,Manifold诞生了。 Manifold将消息的发送工作分配给的远程分区节点(一系列PID),这保证了发送process调用send/2的次数最多等于远程分区节点的数量。 Manifold首先对会话process PID进行分组,然后发送给每个远程分区节点的Manifold.Partitioner。然后Partitioner使用 erlang.phash2/2 对会话process PID进行一致性哈希,分成N组,并将消息发送给子workers(process)。最后,这些子workers将消息发送到会话process。这可以确保Partitioner不会过载,并且通过 send/2 保证原子一致性。这个解决方案实际上是 send/2 的替代品: Manifold.send([self(), self()], :hello)Manifold的作用是不仅可以分散消息发布的CPU成本,还可以减少节点之间的网络流量: ...

June 13, 2019 · 1 min · jiezi

使用Rust和Elixir实现高效的下发好友列表

去年,Discord的后端基础设施团队努力提高核心实时通信基础设施的可扩展性和性能。 我们进行的一个大项目是改变我们更新会员列表的方式(屏幕右侧的那些漂亮的头像)。我们可以直接发送会员列表中可见部分的更新(分页),而不是为会员列表中的每个人发送更新。这样做的好处很明显,例如网络流量更少,CPU使用率更低,电池寿命更长等等。 然而,这在服务器端造成了一个大问题:我们需要一个能够容纳数十万个条目的数据结构,以一种可以处理大量更新的方式进行排序,并且可以上报会员的位置索引添加和删除。 Elixir是一种函数式语言,它的数据结构是不可变的。这对推理代码并支撑大量并发性都非常好。不可变数据结构的双刃剑。现有的数据结构的更新是通过创建全新数据结构来实现的,该全新数据结构是将该操作应用于现有的数据结构的结果。 这意味着当有人加入服务器(内部称为公会)并拥有100,000名成员的成员列表时,我们必须构建一个包含100,001名成员的新列表。 BEAM VM非常快速,并且每天都在变得更快。Elixir试图在可能的情况下利用persistent data structure。但是在我们的运营规模下,这样的更新效率是无法被接受的。 将Elixir推至极限两位工程师接受了制作纯Elixir数据结构的挑战,该数据结构可以容纳大型sorted sets并支持快速更新操作。这说起来容易做起来难。 Elixir附带一个名为MapSet的set实现。 MapSet是构建在Map数据结构之上的通用数据结构。它对许多Set操作很有用,但它不能保证有序,但这是成员列表的关键要求,排除MapSet。 接下来将是古老的List类型:对List做一层封装,强制保证唯一性并在插入新元素后对列表进行排序。这种方法的压测数据表明,对于小型列表(5,000个元素) ,插入时间在500s和3,000s之间。这太慢了,不可行。更糟糕的是,插入的性能与列表的大小和列表中的位置深度成正比。在250,000个元素的末尾添加一个新元素,大约170,000s:基本上是恒定的。 两次失败,但BEAM尚未退出竞争。 Erlang附带一个名为ordsets的模块。 Ordsets是有序sets,所以听起来我们找到了解决问题的方法:让我们压测一下以检查可行性。当列表很小时,性能看起来相当不错,测量范围在0.008s和288s之间。遗憾的是,当测试的大小增加到250,000时,最坏情况下的性能提高到27,000s,这比我们的自定义List实现速度提高了五倍,但仍然不够快。 尝试了语言附带的所有候选者,粗略地搜索了lib,看看其他人是否已经解决了这个问题并开源。看了一些lib,但它们都没有提供所需的属性和性能。值得庆幸的是,计算机科学领域一直在优化用于存储和分类数据的算法和数据结构,因此有很多关于如何进行的想法。 SkipListordset在小数据下表现非常出色。也许有一些方法可以将一堆非常小的ordsets链接在一起,并在访问特定位置时快速访问正确的ordset。这类似于一个skiplist。 这个新数据结构的第一个版本非常简单。 OrderedSet是一个Cell列表的封装,每个Cell内部都是一个小的ordset:ordset的第一项,ordset的最后一项,以及count。这允许OrderedSet快速遍历Cells列表以找到适当的Cell,然后执行非常快速的ordset操作。在250,000项目列表的末尾插入项目从27,000s降至5,000s,比原始ordsets快5倍,比原始List实现快34倍。 性能有所提升,但是在列表的头部Cell创建250,000个元素,单个插入时间仍为19,000s。 这是有道理的。当你在OrderedSet的前面插入一个项目时,它会在第一个Cell中结束,但是Cell已经满了,所以它将它的最后一个项目驱逐到下一个Cell,但是Cell已经满了,所以它将它的最后一个项目驱逐到下一个Cell,依此类推。 OrderedSet问题在于,当元素填满时,操作会从Cell级联到下一个Cell。如果我们允许Cell分裂,在列表中间动态插入新Cell呢?好处是:最坏的情况是Cell分裂,而不是级联。 优化后的情况:在小列表时,这个新的OrderedSet可以在列表中的任何点执行4s和34s之间的插入,不错。我们将尺寸调整到250,000。在列表的开头插入,第一个插入为4s,后面会逐惭变慢。最终在列表末尾插入一个项目需要640s,看起来还行。 必须更快!上面的解决方案适用于高达250,000名成员的公会,但我们想要更多!Discord一直在使用Rust来让事情变得更快,我们可以使用Rust来加快速度吗? Rust不是一种函数式语言,可以使用可变数据结构。它也没有运行时并提供“zero-cost abstractions”。如果我们用Rust,它可能会表现得更好。 我们的核心服务不是用Rust编写的,它们是基于Elixir的。 Elixir非常适合调用Rust,幸运的是,BEAM VM还有另一个漂亮的技巧。 BEAM VM有三种类型的函数: 用Erlang或Elixir编写的函数。这些是简单的用户空间函数。内置于语言中的函数,充当用户空间函数的构建块。这些被称为BIF或内置函数。NIF或native函数。这些是使用C或Rust构建并编译到BEAM VM中的函数。调用这些函数就像调用BIF一样,但是你可以控制它的功能。有一个名为Rustler的Elixir项目。它为Elixir和Rust提供了很好的支持,可以创建一个表现良好的安全的NIF,并保证使用Rust不会VM崩溃或内存泄漏。 我们预留了一个星期,看看这是否值得付出努力。到本周末,我们给出一个非常有限的概念验证。压测数据看上去很有希望,与OrderedSet的4s至640s相比,向SortedSet添加元素的最佳情况是0.4s,最差情况为2.85s。这只是使用integer的测试,但它足以证明优于Elixir的实现。 有了数据支撑,我们决定继续扩展程序支持更多的Elixir数据类型。最后我们的测试数据如下:我们将数量一直增加到1,000,000。最后打印出结果:SortedSet最佳情况为0.61s,最差情况为3.68s,其基于多种大小的sets,大小从5,000到1,000,000。 我们使最坏的情况与先前的迭代最佳情况一样好!Rust支持的NIF提供了巨大的性能优势,而无需牺牲易用性或内存。 喜讯今天,Rust版的SortedSet为每一个Discord公会提供支持:从计划到日本旅行的3人公会到享受最新、有趣的游戏的20万人公会。 自布署SortedSet以来,我们已经看到性能全面提升,不会对内存压力产生影响。我们了解到Rust和Elixir可以并肩工作。我们仍然可以将我们的核心实时通信逻辑保留在更高级别的Elixir中,它具有出色的保护和简单的并发实现,同时在需要时可以使用Rust。 如果你需要一个高效更新的SortedSet,我们已经开源了SortedSet。

June 1, 2019 · 1 min · jiezi

umbrella-project-to-deps

https://stackoverflow.com/que...

May 7, 2019 · 1 min · jiezi

docker swarm mode 下容器重启IP引发的 CLOSE_WAIT 问题

问题问题简述如下图. server docker restart后, client端写入的日志丢失, 并且无报错.因为不支持时序图, 把时序图代码嵌入在代码里.sequenceclient-&gt;server: log_dataclient-&gt;server: log_dataserver-&gt;server: docker restartserver-&gt;client: finclient-&gt;server: log_data loss without errortcp state diagram问题定位过程为什么卡在CLOSE_WAIT.看tcp状态转换图, 可以看到client收到了fin, 一直没有recv, 一直卡在CLOSE_WAIT. 和实际的代码是吻合的. 那么, 为什么在server docker restart 引发CLOSE_WAIT后, client发消息仍然不报错呢?因为:tcp协议允许client在收到fin后, 继续发送消息.server 在docker restart后 ip 改变, client还是往原来的ip发送消息, 没有主机通知client rst, 导致消息在系统buffer里积压.积压信息如下:root@9eeaefa7fe57:/# netstat -nap | grep 27017 | grep 10.0.0tcp 1 402 10.0.0.186:62281 10.0.0.16:27017 CLOSE_WAIT 4308/serverroot@9eeaefa7fe57:/# netstat -nap | grep 27017 | grep 10.0.0tcp 1 70125 10.0.0.186:62281 10.0.0.16:27017 CLOSE_WAIT 4308/server此时, 在elixir socket接口层面来看, 不管socket的状态, 还是发送, 都是ok的.iex(client@client.)25> socket |> :inet.port{:ok, 57395}iex(client@client.)26> socket |> :gen_tcp.send(“aaa”):ok如果主动close, 则会进入LAST_ACK状态iex(client@client.)27> socket |> :gen_tcp.close() :okroot@9eeaefa7fe57:/# netstat -nap | grep 27017 | grep 10.0.0tcp 1 70126 10.0.0.186:62281 10.0.0.16:27017 LAST_ACK - CLOSE_WAIT的恢复如果代码还是只发不收. 是检测不到CLOSE_WAIT的. 显然, 应用层心跳是一个解决方案. 那么, 不使用心跳, 只发不收的情况下, 什么时候才能检测到错误呢?send buffer 满todo 深究tcp keepalive, 不使用 keepalive情况下的 tcp 最大链接空闲时间. ...

February 22, 2019 · 1 min · jiezi

inside gen_server call

动机前段时间的两个工作.一个是entity集群库, 可以通过entity_id调用任意节点上的entity. 一个是名字服务, 可以为一系列pid注册名字, 并可以以这些名字调用对应的pid.都会遇到同一些问题: 当我们使用GenServer.call/2时, 发生了什么, 会有什么异常情况发生? 哪些异常应该捕获? 以什么样的方式处理这些异常/错误?当call的pid所在的node崩溃时, 会有什么异常? 在调用开始前/中崩溃, 有什么不同.当call的pid所在的node网络突然中断呢? 会有什么表现?当call的pid崩溃时呢?是否应该捕获timeout?这些问题在文档中并没有答案. 所以, 探索一下.深挖实现源码版本erlang: OTP-21.0.9inside gen_server.erl callgen_server.erl:203%% —————————————————————–%% Make a call to a generic server.%% If the server is located at another node, that node will%% be monitored.%% If the client is trapping exits and is linked server termination%% is handled here (? Shall we do that here (or rely on timeouts) ?).%% —————————————————————– call(Name, Request) -> case catch gen:call(Name, ‘$gen_call’, Request) of {ok,Res} -> Res; {‘EXIT’,Reason} -> exit({Reason, {?MODULE, call, [Name, Request]}}) end.call(Name, Request, Timeout) -> case catch gen:call(Name, ‘$gen_call’, Request, Timeout) of {ok,Res} -> Res; {‘EXIT’,Reason} -> exit({Reason, {?MODULE, call, [Name, Request, Timeout]}}) end.gen.erl:160do_call(Process, Label, Request, Timeout) when is_atom(Process) =:= false -> Mref = erlang:monitor(process, Process), %% OTP-21: %% Auto-connect is asynchronous. But we still use ’noconnect’ to make sure %% we send on the monitored connection, and not trigger a new auto-connect. %% erlang:send(Process, {Label, {self(), Mref}, Request}, [noconnect]), receive {Mref, Reply} -> erlang:demonitor(Mref, [flush]), {ok, Reply}; {‘DOWN’, Mref, _, _, noconnection} -> Node = get_node(Process), exit({nodedown, Node}); {‘DOWN’, Mref, _, _, Reason} -> exit(Reason) after Timeout -> erlang:demonitor(Mref, [flush]), exit(timeout) end.可以看到, call一个process的过程:monitor processsend_msg to process and receive for reply.可能的情况有正常返回, demonitornoconnectionpid down for any reasontimeout那么, 前面的各种异常, 会对应到哪些情况呢? 有没有意外?先看看monitor一个process时到底做了什么.inside monitorerlang.erl:1291-type registered_name() :: atom().-type registered_process_identifier() :: registered_name() | {registered_name(), node()}.-type monitor_process_identifier() :: pid() | registered_process_identifier().-type monitor_port_identifier() :: port() | registered_name().%% monitor/2-spec monitor (process, monitor_process_identifier()) -> MonitorRef when MonitorRef :: reference(); (port, monitor_port_identifier()) -> MonitorRef when MonitorRef :: reference(); (time_offset, clock_service) -> MonitorRef when MonitorRef :: reference().monitor(_Type, _Item) -> erlang:nif_error(undefined).在monitor process时, 可以是一个pid, name, name node tuple. 但这里没有具体实现, 找一下nif.inside receive ...

January 12, 2019 · 2 min · jiezi

gen_tcp参数总结

动机在用elixir 写 rpc server/client时, 需要对传入gen_tcp的参数做一些考量. 如, 部分参数应该允许用户修改, 比如sndbuf recbuf, 让用户根据使用场景调节, 部分参数应该屏蔽, 减少使用理解成本.故, 深挖了一下gen_tcp的option代码版本erlang: OTP-21.0.9optionsAvailable options for tcp:connect%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Available options for tcp:connect%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%connect_options() -> [tos, tclass, priority, reuseaddr, keepalive, linger, sndbuf, recbuf, nodelay, header, active, packet, packet_size, buffer, mode, deliver, line_delimiter, exit_on_close, high_watermark, low_watermark, high_msgq_watermark, low_msgq_watermark, send_timeout, send_timeout_close, delay_send, raw, show_econnreset, bind_to_device].tostype of service下图来自tcp ip详解 卷1tclassIPV6_TCLASS{tclass, Integer}Sets IPV6_TCLASS IP level options on platforms where this is implemented. The behavior and allowed range varies between different systems. The option is ignored on platforms where it is not implemented. Use with caution.不知道具体含义, 忽略prioritySO_PRIORITY SO_PRIORITY Set the protocol-defined priority for all packets to be sent on this socket. Linux uses this value to order the networking queues: packets with a higher priority may be processed first depending on the selected device queueing discipline. Setting a priority outside the range 0 to 6 requires the CAP_NET_ADMIN capability.reuseaddrSO_REUSEPORT SO_REUSEPORT (since Linux 3.9) Permits multiple AF_INET or AF_INET6 sockets to be bound to an identical socket address. This option must be set on each socket (including the first socket) prior to calling bind(2) on the socket. To prevent port hijacking, all of the pro‐ cesses binding to the same address must have the same effec‐ tive UID. This option can be employed with both TCP and UDP sockets. For TCP sockets, this option allows accept(2) load distribu‐ tion in a multi-threaded server to be improved by using a dis‐ tinct listener socket for each thread. This provides improved load distribution as compared to traditional techniques such using a single accept(2)ing thread that distributes connec‐ tions, or having multiple threads that compete to accept(2) from the same socket. For UDP sockets, the use of this option can provide better distribution of incoming datagrams to multiple processes (or threads) as compared to the traditional technique of having multiple processes compete to receive datagrams on the same socket.keepalive [SO_KEEPALIVE][6] Enable sending of keep-alive messages on connection-oriented sockets. Expects an integer boolean flag. ...

January 12, 2019 · 2 min · jiezi