关于erlang:erlang-cowboy-在-nginx-499-时的-handler-process-shutdown

简述erlang 的 cowboy 是一个 web server 框架。它在客户端提前断开(nginx http code 499)时,会间接杀掉handler过程。这很容易造成bug。 示例代码参考 https://ninenines.eu/docs/en/...有handler代码如下: -module(hello_handler).-behavior(cowboy_handler).-export([init/2]).init(Req, State) -> erlang:display("before_sleep"), timer:sleep(3000), erlang:display("after_sleep"), Req = cowboy_req:reply( 200, #{<<"content-type">> => <<"text/plain">>}, <<"Hello Erlang!">>, Req ), {ok, Req, State}.在 curl http://localhost:8080时,有输入: ([email protected])1> "before_sleep""after_sleep"如果 curl http://localhost:8080 --max-time 0.001curl: (28) Resolving timed out after 4 milliseconds有输入: ([email protected])1> "before_sleep"这个阐明handler过程的执行被抢行掐断了。如果代码中有对过程内部资源的拜访,比方加锁,显然会造成锁开释问题。 问题起因见 cowboy_http.erl:loop loop(State=#state{parent=Parent, socket=Socket, transport=Transport, opts=Opts, buffer=Buffer, timer=TimerRef, children=Children, in_streamid=InStreamID, last_streamid=LastStreamID}) -> Messages = Transport:messages(), InactivityTimeout = maps:get(inactivity_timeout, Opts, 300000), receive %% Discard data coming in after the last request %% we want to process was received fully. {OK, Socket, _} when OK =:= element(1, Messages), InStreamID > LastStreamID -> loop(State); %% Socket messages. {OK, Socket, Data} when OK =:= element(1, Messages) -> parse(<< Buffer/binary, Data/binary >>, State); {Closed, Socket} when Closed =:= element(2, Messages) -> terminate(State, {socket_error, closed, 'The socket has been closed.'}); {Error, Socket, Reason} when Error =:= element(3, Messages) -> terminate(State, {socket_error, Reason, 'An error has occurred on the socket.'}); {Passive, Socket} when Passive =:= element(4, Messages); %% Hardcoded for compatibility with Ranch 1.x. Passive =:= tcp_passive; Passive =:= ssl_passive -> setopts_active(State), loop(State); %% Timeouts.最终会通过发送exit音讯形式,杀掉children过程。 ...

October 23, 2022 · 2 min · jiezi

关于erlang:谈谈erlang的nif

简述在某个需要的驱动下,心愿实现线程间共享的路由树。鉴于如下的外围需要: 读多写少,写入操作重,读取操作轻。性能足够快,不心愿有单点。鉴于有数据共享需要,没有单点的状况下,erlang中简直只有ets了。因为ets仅能实现k,v查问,不能实现更简单的读写需要,比方寻找某个子网(key="192.168.x.x")下的任意IP(values = ["192.168.1.1", "192.168.2.3"]),于是决定用NIF来实现。这是第一次尝试在理论业务中应用NIF。这里做一下总结。实现时的考量数据转换 erlang_term to c and c to erlang_term大部分语言都提供c拓展,第一件事就是,如何把语言的内置类型转为c中的类型,以及如何将c中的数据结构转为语言的内置类型。这里能够 间接应用enif_get/enif_make 系列接口。参考 https://github.com/goertzenat...,利用c++ template,在编译期主动依据类型开展雷同的 enif_get/enif_make 系列代码。资源管理参考 https://www.erlang.org/doc/ma...次要有两种: 本人治理,比方将资源存储在动态变量中,提供 destroy_nif 之类,由用户销毁。erlang治理,nif不治理资源,erlang_term 复制进来后,由erlang的援用计数治理。动静类型能够用enif_term_to_binary/enif_binary_to_term系列接口,在同样的数据结构中存储动静的erlang构造。 线程平安一般来说,nif是在erlang的scheduler线程执行的。所以,须要用enif_mutex/enif_rwlock 系列接口爱护资源,防止未定义的行为。也能够间接应用c++的mutex,操作系统api等等。如:https://en.cppreference.com/w... 调度器和上下文切换erlang调用nif是没有上下文切换的。那么就会有一个问题,如果nif始终占用cpu怎么办?如何偏心调度?https://www.erlang.org/doc/ma...nif函数应该尽快(1ms内)返回。否则,会导致一些异样行为,我遇到的状况是日志刷不进去(调度异样)。为防止这种状况,咱们须要更简单的策略如下。 协程化:用 enif_consume_timeslice 看本人是否还有工夫片。用 enif_schedule_nif yield to scheduler。 异步化:另起thread计算,在用 enif_send 将后果发回去,这样不影响调度线程。 Dirty Nif:其实就是官网的worker thread,按cpu/io bound分类,防止影响一般调度器。 调试占位。原理上,和一般c程序调试动静库没有太多区别。工作流倒是能够记录一下。 总结没有银弹,nif有其应用的场景:小音讯,大计算。参考https://www.erlang.org/doc/ma...https://www.erlang.org/doc/tu...https://github.com/goertzenat...

October 23, 2022 · 1 min · jiezi

关于erlang:Erlang中的if赞美与非议

本文是考古Erlang语言中对于if语句的探讨。 语法首先,咱们该如何对待Erlang中的“if”语义呢?在Armstrong编写的《Erlang程序设计》一书中,很明确地将"case和if表达式"一起放在了同一节,并且先介绍了case,后介绍了if。在介绍if时,给出的语法例子如下: if Guard1 -> Expr_seq1; Guard2 -> Expr_seq2; ...end能够从这个句式很显著地看出,整个if语句更像是一个case语句,通过一条条关卡(Guard)宰割执行不同的子句。if语句会执行每一条关卡,如果后果为true则执行子表达式并完结,如果为false则顺次向下匹配直到有一个关卡为true。 这里须要留神的一点是,整个语句必须保障至多有一个关卡为true,否则整段语句就会抛出异样。这样的语句在特定条件下会是一个异样谬误: if A > 0 -> do_this()end除非你无意想让谬误产生。当然,一位相熟编码的工程师晓得,任何暗藏的用意与可能的失误混同在一起时,整段代码都会变得难以浏览和了解,即应用正文明确标注,在之后的演进中也会难以迭代。举荐的做法以及各类我的项目中的实际是,在最初一个关卡中应用原子true,保障匹配最初一条子表达式。这就像Java中case最初的default一样。 if Guard1 -> Expr_seq1; Guard2 -> Expr_seq2; ... true -> Expr_defaultend决策表?查看Erlang各类阐明文档,stackoverflow中的例子,博客,都会优先举荐case,而非if。这里有一封归档邮件能够很清晰地阐明if在开发者眼中的位置:http://erlang.org/pipermail/e... (也是Erlang编码标准中征引的阐明用例)。 简略形容背景:Richard A. O'Keefe(间接搜寻名字就能够找到这位发表了不少计算机语言学钻研论文的Otago大学研究员)反对“聪慧”地应用if语句。他给出的邮件题目就是“赞美Erlang中的if”。阐明中列举了一篇论文,表明“结构化流程图优于伪代码”的观点,传统的伪代码相似: IF GREEN THEN IF CRISPY THEN STEAM ELSE CHOP ENDIF ELSE FRY IF LEAFY THEN IF HARD THEN GRILL ELSE BOIL ENDIF ELSE BAKE ENDIFENDIF常见的嵌套if引发的逻辑凌乱。在Erlang中,通过奇妙地利用Erlang语法,能够把这一段逻辑变动为以下模式: if Green, Crispy -> steam() ; Green, not Crispy -> chop() ; not Green, Leafy, Hard -> fry(), grill() ; not Green, Leafy, not Hard -> fry(), boil() ; not Green, not Leafy -> fry(), bake()end实质上是利用Erlang中的分号,逗号,空白符,发明出一张视觉上的“决策表”,可能清晰地表明每个分支对应的条件。 ...

April 20, 2022 · 2 min · jiezi

关于erlang:erlang的vm层profiling工具

简介profiling工具是语言生态的重要组成部分。这篇blog对erlang的vm层profiling做一个总结。 profiling tools overview可参考https://www.erlang.org/doc/ef... 名字性能残余性能)cprof统计模块的调用次数,可找出调用次数热点0.67eprof统计模块的调用次数和工夫,可找出调用工夫、次数热点0.17fprof统计模块调用次数和关系,可生成call-graph0.008eflame通过erlang trace采集调用栈,过程状态0.003looking_glass也是基于erlang trace0.004具体数据压测代码如下: defp do_sth(:cpu_intensive) do start_time = :erlang.system_time(:microsecond) end_time = start_time + 10_000_000 do_until_time(0, start_time, end_time) end def do_until_time(count, start_time, end_time) do now = :erlang.system_time(:microsecond) if now >= end_time do Logger.warn( "\ntotal_time: #{div(end_time - start_time, 1000_000)} second\ncount: #{count}, avg_s: #{div(end_time - start_time, count)}" ) else for v <- 1..100 do a = %{v => Base.encode64(:crypto.strong_rand_bytes(20))} b = Jason.encode!(a) Jason.decode!(b) end do_until_time(count + 1, start_time, end_time) end end无压测性能基准total_time: 10 secondcount: 24287, avg_s: 411cpu: %100cprof示例代码 :cprof.start() do_sth(:cpu_intensive) :cprof.pause() data = :cprof.analyse() Logger.warn("cprof_result:\n#{inspect(data, pretty: true)}")输入后果仅能取得模块的调用次数。 ...

February 26, 2022 · 3 min · jiezi

关于erlang:otp24-erlang-Process-aliases-引发的一个bug

前言最近降级了otp24,一个提供地位无关call entity的组件在调用的entity过程退出时。会呈现timeout。在追究后发现和erlang otp24的一个改良相干。https://www.erlang.org/blog/m... 起因实现简述在call一个entity时,不是间接call一个pid,而是通过节点的router转发: sequenceDiagram client->>+router: Request router->>+entity: Request entity-->>-router: Reply router-->>-client: Reply做过一个改良,由被调用的entity,间接返回。 sequenceDiagram client->>+router: Request router->>+entity: Request entity-->>-client: Reply在被调用的entity退出中时,须要由常驻的router返回client,entity在退出中。否则client会timeout。 sequenceDiagram client->>+router: Request entity->>+entity: terminate start Note over entity: 音讯没有机会被解决 router->>+entity: Request entity->>+entity: terminate end client->>+client: timeout 所以,router会monitor entity。在entity异样退出时,由router 返回client。问题也正呈现在这里。 问题根因首先看erlang otp23的call代码:gen.erl:202 do_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.在otp23中,咱们的代码运行如下。 ...

February 12, 2022 · 2 min · jiezi

关于erlang:elixir-0081-编译后的-beam-code-重建为-erlang-代码

elixir 或 erlang 或其它运行在 beam vm 上的语言,都会被编译成 .beam 文件。那么是否通过这些文件重建 erlang 代码呢?答案是能够的。 [file] = System.argv()beam = File.read!(file){:ok, {_, [{:abstract_code, {_, ac}}]}} = :beam_lib.chunks(beam, [:abstract_code])out = file <> ".erl"File.touch!(out)File.write!(out, :erl_prettypr.format(:erl_syntax.form_list(ac)))将下面的代码保留为 beam2erl.exs 文件。 而后咱们轻易找一个 elixir 文件, 比方: defmodule Demo do defdelegate puts(str), to: IOend将其编译,而后把对应的 beam 文件复制到 beam2erl.exs 的目录下,再执行: $ elixir beam2erl.exs Elixir.Demo.beam 就能看到生成了一个 .erl 文件,内容是: -file("lib/demo.ex", 1).-module('Elixir.Demo').-compile([no_auto_import]).-export(['__info__'/1, puts/1]).-spec '__info__'(attributes | compile | functions | macros | md5 | exports_md5 | module | deprecated) -> any().'__info__'(module) -> 'Elixir.Demo';'__info__'(functions) -> [{puts, 1}];'__info__'(macros) -> [];'__info__'(exports_md5) -> <<"\n\025Y�a#�x�\201W��a#�">>;'__info__'(Key = attributes) -> erlang:get_module_info('Elixir.Demo', Key);'__info__'(Key = compile) -> erlang:get_module_info('Elixir.Demo', Key);'__info__'(Key = md5) -> erlang:get_module_info('Elixir.Demo', Key);'__info__'(deprecated) -> [].puts(_str@1) -> 'Elixir.IO':puts(_str@1).下面的一大串是模块中内置的函数,最初一行是咱们代码的内容。 ...

November 24, 2021 · 1 min · jiezi

关于erlang:elixir-0079-erlang-版本升级-22-24

前几天降级了 elixir 版本, 明天想着罗唆把 erlang 的版本也降级一下好了。据说 OTP24的性能有很大晋升。降级之后一编译,果然又报了好些正告&谬误,咱们来一一解决。 1** (UndefinedFunctionError) function :crypto.block_encrypt/3 is undefined or private, use crypto:crypto_one_time/4 or crypto:crypto_init/3 + crypto:crypto_update/2 + crypto:crypto_final/1 instead首先是 :crypto 模块的api变了,变更的起因据说是OpenSSL 的 api变更。http://erlang.org/doc/apps/cr... 还好在网上搜寻了一番,找到了解决的办法。 应用 :crypto.crypto_one_time 函数代替即可,最初一个参数是一个布尔值,true 代表加密, false 代表解密。具体用法还是看文档吧。 2另外 :crypto.hmac 函数也没有了。对立改用 :crypto.mac(:hmac, ...)。具体参考函数文档。手动替换一下就能够了。 3因为下面的这些起因,一些依赖库也用不了了。好在 elixir 社区罕用的库保护都很频繁,到 hex.pm 上找到最近版本,更新一下就好了。 4这样,一次欢快的 erlang 大版本升级就实现了。

October 20, 2021 · 1 min · jiezi

关于erlang:erlang-nodename-phash-冲突坑

概述在线上遇到了因节点名哈希值抵触导致的局部机器无负载问题。10台机器中,抵触的机器达到了4台之多。假如哈希的概率是均匀的。10台机器中,不存在抵触的概率靠近 >>> 1 - (1.0 / (2 ** 32)) * 100.9999999976716936实际上,10台中哈希值抵触了6台。于是看源码找答案。 过程先从phash2 api动手erlang 的 api调用形式和 linux有相似之处。通过函数指针数组。erl_bif_list.h 中看到,咱们调用的phash2对应phash2_2。 BIF_LIST(am_erlang,am_phash,2,phash_2,phash_2,37)BIF_LIST(am_erlang,am_phash2,1,phash2_1,phash2_1,38)BIF_LIST(am_erlang,am_phash2,2,phash2_2,phash2_2,39)bif.c:4905 BIF_RETTYPE phash2_2(BIF_ALIST_2){ Uint32 hash; Uint32 final_hash; Uint32 range; Eterm trap_state = THE_NON_VALUE; /* Check for special case 2^32 */ if (term_equals_2pow32(BIF_ARG_2)) { range = 0; } else { Uint u; if (!term_to_Uint(BIF_ARG_2, &u) || ((u >> 16) >> 16) != 0 || !u) { BIF_ERROR(BIF_P, BADARG); } range = (Uint32) u; } hash = trapping_make_hash2(BIF_ARG_1, &trap_state, BIF_P); if (trap_state != THE_NON_VALUE) { BIF_TRAP2(&bif_trap_export[BIF_phash2_2], BIF_P, trap_state, BIF_ARG_2); } if (range) { final_hash = hash % range; /* [0..range-1] */ } else { final_hash = hash; } /* * Return either a small or a big. Use the heap for bigs if there is room. */#if defined(ARCH_64) BIF_RET(make_small(final_hash));#else if (IS_USMALL(0, final_hash)) { BIF_RET(make_small(final_hash)); } else { Eterm* hp = HAlloc(BIF_P, BIG_UINT_HEAP_SIZE); BIF_RET(uint_to_big(final_hash, hp)); }#endif}通过调试能够确认 ...

August 28, 2021 · 3 min · jiezi

关于erlang:Erlang-游戏开发经验总结

早早就想写这篇文章,但这段时间忙于工作的事件,就不盲目地给了本人各种懈怠的理由。当初回头看下这个问题,总结下erlang 游戏开发教训。就当是,为我过来一段时间的erlang开发经验,画上一个小句号。 1. 架构设计很多人都说Erlang天生分布式,归纳起因是erlang分布式这块实现比较完善。 节点间的通信是通明的,无论是节点内的过程,还是不同节点的过程,都能够应用 erlang:send/2 发送音讯,而且数据不须要做任何转换。所以,很多我的项目会选用多节点架构,能够通过横向拓展(减少机器数量)来撑持更多负载,把性能压力大的子系统调配到不同的节点,这样,就能够 反对更多的在线玩家。这样做的出发点是好的,但会引起一系列的问题:1、业务逻辑的问题: 跨节点逻辑变得复杂,玩家数据同步、一致性问题,节点断开及重连的解决等2、语言局限性问题: 节点间音讯通信要序列化和反序列化,原子 传输要转成字符串,二进制复制 等 那架构抉择单节点,还是多节点?没有相对,这要看游戏而定。如果你的游戏像页游那样,分服而且各个服之间交互较少,单节点会比拟适宜。但如果像lol那种对战玩法的游戏,多节点会比拟适合,登录和玩法在不同的节点,玩家对战时抉择集群内压力较小的节点做战斗计算。 单节点,益处是玩家数据容易保障统一,运维不便。以常见的MMORPG游戏,单节点在16G内存,8外围CPU,能够撑持2k人稳固在线,思考非沉闷玩家,达到5000也是可能的。当然,游戏的框架要正当设计,做一些斗争,比方玩家寻路不能在服务端做,地图 九宫格的设计,排行榜不实时刷新,管制同屏玩家数量等 等。当初,单节点做游戏服,还有一个重要起因是,当初的机器性能相较以前高很多了,加上erlang无锁的Actor设计在并发场合下解放了cpu,从某种程度上讲进步了cpu的运算能力。 单节点的设计: 多节点的设计: ! erlang节点留神一个问题,默认erlang节点是全联通的,也就是当一个节点退出集群时,集群其余所有节点会和新退出的节点建立联系。全联通带来的问题,集群节点间两两连贯,随着节点减少,连贯数量呈N*(N-1)/2增长,越发恐怖,连贯自身占用了端口资源。更坏的是,为了检测节点的存活,erlang会定期发心跳包查看,即便一分钟一个tick,节点多的话也会造成大量的网络风暴。解决办法就是在集群中暗藏节点,就能够防止全联通,只有erlang启动加个参数即可 -hidden 数据库数据库io向来都是游戏的次要性能瓶颈,解决不好容易导致 游戏卡顿,甚至解体。 而通用的长久化策略就是,内存读写+定时长久化。玩家上线时,加载玩家频繁用到的数据到内存,玩家大多时候就是在读写这些数据。而后定时把这些数据从内存刷到磁盘,在玩家下线时也做一次长久化。说下erlang自带数据库 - mnesia实际上,erlang已实现了这样一套长久化机制,也就是 mnesia数据库。mnesia数据存储是基于ets和dets实现,对于ram_copies表应用ets;disc_copies表同时应用ets和dets,数据读写应用ets,dets做长久化;而disc_only_copies表应用的是dets这里先探讨 disc_copies的状况, mnesia启动时,会将 disc_copies表所有数据加载到ets表中,每次读数据都是读ets,写数据则会先写到ets,而后再写一份到日志文件中,期待定时(或定量)长久化刷到dets。通常disc_copies表能够满足咱们的业务需要,但应用mnesia要留神一个问题。后面也提到了,mnesia启动会将表中的数据加载到ets,如果你的表过大,就会导致内存被急剧消耗掉。(特色就是,ets所占的内存比率过大)所以,应用mnesia表时,常常都是要disc_copies表和disc_only_copies表配合应用。那问题来了, 什么时候应用 disc_copies表,什么时候disc_only_copies表。 最简略的就是,对玩家数据动刀。玩家数据默认用disc_copies表,如果长时间没登录后将这个玩家的数据移到disc_only_copies表,等到他下次登录时再将数据移到disc_copies表。之所以能够这么做,理由有两个: 游戏中玩家数据所占的比例较大,调整玩家数据能够取得显著收益。玩家散失后回到游戏的可能性很小,就算有的话比例也不大。这么做的弊病就是玩家散失后从新登录的工夫较长,但通过这种形式缩小的内存很可观。 mnesia的应用,还要留神3个问题: mnesia单个表2G文件大小限度,所以要本人分表,或者应用表分片mnesia集群性能,过多的人说有坑,但我没有这方面的教训,就不做探讨mnesia事务并发性太差,尽可能不必mnesia事务,多脏写;事务可利用过程实现,保障数据安全过程每个玩家一个过程的设计曾经成为了erlang游戏开发的潜规则了。这个没什么好讲,玩家过程批改本人的数据,过程音讯同步解决机制保证数据一致性。可能有些游戏还会将玩家过程和scoket过程独立开,负责连贯的建设和保护,协定封包解包,甚至做攻打的防备等。但如果玩家过程和socket过程同在一个节点内,显然整合在一个过程较好,erlang音讯基于复制,两头多了一个过程,一次前后端交互要多了2次内存复制。那么,除玩家外,其余过程怎么确定? 地图过程每个玩家都是独立的过程,玩家pk要替换两个过程的公有数据,就要发消息给另一个过程解决。 如果是强pk的游戏,同时有N个玩家一起打斗,音讯就会繁多。因为数据一致性问题,过程间的并发机制就会弱化成同步机制,减少了战斗时延。所以,这里会引入地图过程,通常以一个地图一个过程。玩家进入地图时,会同步战斗相干数据到地图过程,玩家来到地图时,再将战斗数据同步回玩家过程。而在玩家进入地图到来到前的这段时间,所有的战斗计算都由地图过程实现。或者有人会有纳闷,就算有了地图过程,还是有同步问题,地图过程还是要同步解决pk申请,无奈并发解决,玩家过程还是要期待地图过程操作实现。其实,对于玩家的pk申请,解决至多有两个过程,第一个过程是验证攻打的合法性,如是否有这个技能,技能cd,等等。第二个过程才是战斗计算,玩家过程查看合法性,再由地图过程做外围的战斗计算。另外一个,玩家 过程除了战斗申请外,还有其余业务逻辑上的音讯,容易呈现过程挂起的状况,这时候,玩家过程不可能解决到战斗计算,就会导致战斗卡顿。2.公共过程公共过程指的是那些提供公共服务的过程,比方: 社交类,有好友、帮派、组队等,这些服务治理着少数玩家的数据,都须要一个过程来治理。计算类,这类有肯定的计算量,比如说排行榜,要有一个过程来承当计算。播送类,有聊天室、世界聊天、帮派聊天、地图播送等开关类,有 流动零碎,较量零碎等等,管制游戏流动的开启和敞开erlang过程尽管便宜,然而不要太过随便创立过程,比方创立一个长期过程异步传输数据等等。尽管这在某种程度上进步了并发性,但过程的创立和销毁须要肯定的零碎耗费,而且会导致我的项目中过程数量不可控,可能零碎莫名其妙多了很多过程,这些保护起来也麻烦。再说,erlang 同时存在的过程有最大 数量限度 过程字典与ets过程字典是erlang游戏开发中最为罕用的数据记录形式,理由很简略,因为它够快,差不多比ets快了一个数量级。然而,过程字典的数据为所在过程公有,无奈跨过程间接get到过程字典的数据,而且,在过程被销毁时,过程字典的数据也会被回收。再说下ets,比照过程字典,ets的实用场景是跨过程读写数据。遇到一个数据频繁被多个过程读到,就要思考应用ets了。另外,ets有归属过程,但归属过程销毁时,ets的数据就会被零碎回收。比照过程字典和ets的实现,区别如下: 锁方面,过程字典为无锁操作,ets是读写锁数据方面,过程字典数据在过程中,查问没有多余的复制操作;而ets,因为数据不在过程中,查问时会复制一份到过程。所以,过程字典通常是最优先的抉择。如果玩家的数据是存储在mnesia,特地是玩家的外围数据,就有必要从mnesia读到放在过程字典中。后面讲到了mnesia是利用ets和dets实现的,玩家上线时将外围数据读到过程字典,这样,读写都在过程字典,就能够利用过程字典带来的性能晋升。这个晋升是很可观,毕竟快了一个数量级。 后面提到了ets归属过程销毁时,ets数据也会被回收?那么如果预防数据失落的问题,ets也提供了办法,通过设置继承者过程就能够了。ets:new(person, [named_table, {heir, HeirPid, HeirData}])当归属过程解体时,继承者过程就会收到信息 {'ETS-TRANSFER', Tid, FromPid, HeirData},并且,ets的归属权就会转交到继承者过程。 音讯播送音讯播送是游戏中的性能耗费大头,次要包含地图的行走、战斗播送,世界聊天播送。世界聊天播送能够通过管制玩家发送工夫距离来限度, 地图中的播送包,比方行走和战斗包的播送实时性高,只需发给视线内的玩家就能够,不必全地图播送。所以常见的解决形式是,玩家的视线通过九宫格划分,玩家的地图播送只发送给九宫格内的玩家。如何在服务器压力大时,进一步优化播送音讯:针对时效性高的数据包,不重要的播送包,能够选择性抛弃:1、挂机玩家不发送战斗包,特地是战斗buff。2、每N个包抛弃一个挂机玩家的断定,前端以玩家长时间没动作来断定,或者将游戏切到后盾运行时断定。等玩家在游戏内点击鼠标,或键盘时解除挂机状态。 这里再谈下,音讯播送还可能进一步优化。通常,后端发数据给前端都会对协定数据进行序列化(封包),而后再发给前端反序列化(解包)。而播送数据,对于每个玩家可能都是一样的,没必要每个玩家发给前端时都各自序列化一次,就只有序列化一次,而后每个玩家拿到数据后间接发给前端。 erlang音讯播送要留神什么问题?1、reduction计数通常会启动一个音讯治理过程,这个过程就负责把播送音讯转发给对应的所有玩家过程。启用治理过程的一个益处是,过程发消息会扣除reduction,而且这个reduction扣除大小还受到接收者过程影响。如果间接在地图过程做音讯播送,就会导致地图过程受到的调度极度缩小,影响战斗计算。2、音讯复制erlang音讯发送基于复制,但对于比拟大的二进制数据,则会优化成二进制援用,缩小二进制复制带来的开销。所以,当一个音讯要发给多个过程,特地是协定数据(发给前端也要先转成二进制),能够先转成二进制再发送。 结束语文章到这里就完结了,这是我做 Erlang 游戏开发经验总结的一些教训,后续,我还会找工夫总结更多的开发教训。 图片起源:http://www.coubai.com/ 页游

April 20, 2021 · 1 min · jiezi

关于erlang:排查rtmp协议推流时握手bug

转推流程序的过程:从一个观看地址拉流,而后推流到另一个推流地址。次要用于cdn之间转推,目前市面上大多数cdn厂商都违心不反对动静转推,因而只能通过转推流程序进行转推。 bug景象:应用obs studio推流到微赞能够胜利,然而应用Erlang版本的转推流程序推流到微赞却失败。 日志如下: 14:12:35.926 [debug] payload [{amf,["onBWDone",0,null]}], msgtype[command_msg_4_amf0] 14:12:35.949 [debug] play succ ======> url ["/live-sz/w1520993434573948"] 14:12:35.949 [debug] {rtmp_msg,4,0,data_msg_4_amf0,1,{amf,["|RtmpSampleAccess",true,true]}}14:12:35.949 [debug] {rtmp_msg,4,0,data_msg_4_amf0,1,{amf,["onStatus",{object,[{"code","NetStream.Data.Start"}]}]}}14:12:36.038 [error] gen_server <0.122.0> terminated with reason: no match of right hand value <<0,0,0,0,0,0,0,0,113,142,194,240,185,25,41,180,242,33,5,112,128,97,178,8,79,179,28,53,152,242,82,43,234,104,113,246,170,189,182,146,122,36,155,3,152,180,226,122,36,97,52,67,53,158,107,170,178,119,209,132,40,233,102,182,142,233,218,71,55,8,121,67,117,58,130,91,107,224,202,5,1,132,37,245,143,231,20,198,121,204,57,80,102,165,104,245,79,71,254,169,15,3,166,12,148,45,24,62,253,66,93,139,84,139,54,236,47,5,98,95,51,231,222,144,8,153,232,166,227,151,57,98,214,63,238,167,212,49,51,160,83,248,246,199,...>> in rtmp_handshake:create_c2/2 line 61<!--more--> 很显然是rtmp_handshake:create_c2/2函数呈现匹配谬误,对应代码如下: -spec create_c2(C0C1, S0S1S2) -> Result when C0C1 :: iodata(), S0S1S2 :: binary(), Result :: {ok, C2}, C2 :: iolist().create_c2(C0C1, S0S1S2) when is_list(C0C1) -> create_c2(iolist_to_binary(C0C1), S0S1S2);create_c2(<<_C0:1/binary, C1:16#600/binary>>, <<S0:1/binary, S1:16#600/binary, S2:16#600/binary>>) -> <<3>> = S0, case C1 of <<_:32, 0:32, _/binary>> -> S2 = C1, {ok, S1}; _ -> {ok, S1DigestData} = verify_s1(S1), DigestKey = crypto:hmac(sha256, ?C2_PUBLIC_KEY, S1DigestData), C2Len = 16#600, C2DigestDataLen = 32, RandomBin = random_binary(C2Len - 8 - C2DigestDataLen), {T1, T2, _} = now(), Epoch = <<(1000000 * T1 + T2):32/little>>, Data = [Epoch, binary:part(S1, 0, 4), RandomBin], S2DigestData = crypto:hmac(sha256, DigestKey, Data), {ok, [Data, S2DigestData]} end.rtmp握手过程中C1数据包匹配<<_:32, 0:32, _/binary>>格局后和S2数据包匹配不胜利,程序间接crash dump。因而须要弄清楚rtmp握手过程中是否有对S2和C1进行匹配验证。 ...

March 12, 2021 · 3 min · jiezi

关于erlang:小心使用erlang的monitor

概述要从一次线上的内存透露说起. 最终定位到在异步send办法中, monitor了被调用过程, 而又没有开释, 导致透露. 违反直觉的是, 这个透露是双向的, A monitor B后, A会记录 monitors B, B会记录 monitored_by A. 过程A退出后, 过程B不会开释 monitored_by A. 所以, 在应用monitor时, 留神在任何状况下, 都要demonitor. 如何定位 monitor 的透露monitor 占用的内存被统计为 system/process, 属于std_alloc分配器.能够用process内存排序, 找出透露的过程. :recon.proc_count(:memory, 10)接着, 能够用process_info, 查看可疑的过程. monitors是monitors的过程, monitored_by是被哪些过程monitor. iex(xxxx@xxxx.)51> {_, b} = :erlang.process_info(pid(0,3182,0), :monitors) {:monitors, [#PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>,此外, 还能够用allocated_types, :erlang.memory, 看到system, std_alloc有大量透露. iex(xxxx@xxxx.)35> :recon_alloc.memory(:allocated_types) [ binary_alloc: 7634944, driver_alloc: 6324224, eheap_alloc: 58241024, ets_alloc: 10780672, fix_alloc: 4751360, ll_alloc: 96993280, sl_alloc: 557056, std_alloc: 830242816, temp_alloc: 2228224 ]iex(xxxx@xxxx.)4> :erlang.memory [ total: 28235126584, processes: 441032432, processes_used: 440339304, system: 27794094152, atom: 1982841, atom_used: 1971208, binary: 15334152, code: 46042580, ets: 28443288 ]如果狐疑monitor有透露, 能够间接依据monitors/monitored_by列表长度排序: ...

December 12, 2020 · 1 min · jiezi

关于erlang:最简洁的Erlang基础

0x00 说在后面Erlang读音/rlæŋ/。第一次见到的时候总感觉怎么读都读不对,起初在维基上看到Erlang标注了音标,能力精确的读出来,而且也没那么怪异。因为工作才有机会接触这门语言,也因而只有三天的工夫能够看《Erlang程序设计》这本书。学习这门语言的时候带着一个工作指标:把一个Erlang日志收集剖析统计的代码转换成Python的。而Erlang的格调是尽量不写正文,尽量在写函数名和变量名的时候表白分明代码的含意。这样一来学习Erlang就成了必要的,很庆幸,领导给了三天工夫学习,三天工夫根本也足够了。除了这一片根底语法的入门篇之外,后续还有一篇或者两篇并发编程和分布式编程的,毕竟这个才是Erlang善于的畛域。话不多说,show me your article <!--more--> 0x01 配置开发环境依赖工具: Erlang版本:18.3IDE:IDEA下载链接: Erlang:https://www.erlang.org/downloads 抉择otp18.3即可。IDEA:https://www.jetbrains.com/ide... 抉择社区版即可。IDEA配置Erlang插件: IDEA官网文档-应用IDEA开发Erlang0x02 基础知识正文% 百分比符号表明正文的开始。%% 两个符号通常用于正文函数。%%% 三个符号通常用于正文模块。变量所有的变量都必须以大写字母结尾,变量只可一次赋值,赋值之后不可在变。 f()函数开释shell绑定变量。 浮点数浮点数必须含有小数点且小数点后必须有一位10进制数用/来除两个整数时相除后果会主动转换成浮点数div取整,rem取余三种标点符号整个函数的定义完结时用一个句号“.”函数参数,数据构建,程序语句之间,用逗号“,”分隔函数定义、case、if、try..catch、receive表达式中的模式匹配时,用分号“;”分界恒等恒等测试符号 =:=以及不等测试符号 =/= 块表达式当程序中某处的语法要求只能应用单个表达式然而逻辑上又须要在此应用多个表达式时,就能够应用begin...end快表达式 begin Expr1, ... ExprNend0x03 内置数据结构元组及模式匹配(解构)_ 代表抛弃的变量,和python雷同匹配时模式匹配符=左右两边的元组的构造必须雷同。1> Point = {point, 20, 43}.{point,20,43}2> {point, x, y} = Point.** exception error: no match of right hand side value {point,20,43}3> {point, X, Y} = Point.{point,20,43}4> X.205> Y.436> Person = {person, {name, {first, joe}, {last, armstrong}}, {footsize, 42}}.{person,{name,{first,joe},{last,armstrong}},{footsize,42}}7> {_, {_, {_, Who}, {_, _}}, {_, Size}} = Person.{person,{name,{first,joe},{last,armstrong}},{footsize,42}}8> Who.joe9> Size.42列表列表元素能够是不同的类型。列表头:列表的第一个元素列表尾:列表除第一个元素剩下的局部竖线符号| ...

November 18, 2020 · 3 min · jiezi

关于erlang:从erlang-otp21-升级-otp23-遇到的坑

概述最近将erlang从otp21.3.8.17降级至最新的otp23.1. 遇到了两个编译不告警, 但版本前后语义不统一, 且在changelist中未提及的问题. 在此做个记录. 问题httpc ssl options verify: 0 导致failed_connect景象Erlang/OTP 23 [erts-11.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]Interactive Elixir (1.10.4) - press Ctrl+C to exit (type h() ENTER for help)iex(1)> :httpc.request(:get, {'https://www.baidu.com/', []}, [ssl: [verify: 0]], []){:error, {:failed_connect, [ {:to_address, {'www.baidu.com', 443}}, {:inet, [:inet], {:options, {:verify, 0}}} ]}}定位发现错误是ssl抛出的. Erlang/OTP 23 [erts-11.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]Interactive Elixir (1.10.4) - press Ctrl+C to exit (type h() ENTER for help)iex(1)> :ssl.handle_options([verify: 0], :client) ** (throw) {:error, {:options, {:verify, 0}}} (ssl 10.1) ssl.erl:2738: :ssl.handle_verify_option/2 (ssl 10.1) ssl.erl:1644: :ssl.process_options/3 (ssl 10.1) ssl.erl:1598: :ssl.handle_options/3ssl-9.2.3.5 无问题. ...

October 24, 2020 · 2 min · jiezi

关于erlang:使用fprof-profile-erlang

概述不谈profile tools的重要性. erlang profile肯定要看看上面这篇.http://erlang.org/doc/efficie...这篇blog记录一些实操. 步骤fprof采集数据在程序中, 或者间接remote_console触发采集. :fprof.trace(:start)# 一段时间后, 留神不要在线上采集, fprof时对性能影响最大的profile形式, 也有最全的信息:fprof.trace(:stop)不传递参数状况下, 默认会将fprof信息写入fprof.trace文件. 应用kcachegrind 查看fprof的后果应用erlgrind转化为callgrind格局~/install » wget https://raw.githubusercontent.com/isacssouza/erlgrind/master/src/erlgrind~/install » chmod a+x erlgrind ~/install » sudo mv erlgrind /usr/local/bin~/platform/xxxxx(xxxx*) » erlgrind fprof.traceReading trace data......................................................................................................,...................................................................................................,...................................................................................................,................................................................................End of trace!Processing data...Creating output...Done!装置kcachegrind能够间接用包管理器装置 sudo apt-get install -y kcachegrind~/platform/xxxxx(xxxxx*) » kcachegrind xxx.cgrind成果如图: flame graphtodo 参考http://erlang.org/doc/efficie...http://blog.equanimity.nl/blo...

July 31, 2020 · 1 min · jiezi

使用mnesia在节点间共享内存

概述有很多场景需要在一系列节点间共享内存数据. 如, 有一系列水平对等的网关, 可以在任意网关节点上拿到所有网关的特定内存信息. 一般的做法是使用zookeeper, etcd等提供了分布式一致性保证的服务. 使用zookeeper, etcd做节点间的数据同步当然没有问题. 但是: erlang内置数据类型需要额外的序列化/反序列化处理. 如pid.不想引入一个复杂系统.我最终使用了 gossip protocol 共享数据. 因为它非常简单可控, 能解决上面的痛点. 也可以实现节点间的最终一致性. erlang原生的mnesia看起来也很适合上述场景. 在最初做选型的时候, 对mnesia的实现没有透彻了解, 这里探讨一下使用mnesia的可行性, 以及mnesia是如何实现的: 分布式事务是如何实现的?有新节点加入时, 数据是如何同步的?有没有主节点概念? 网络分区后如何恢复?提供什么级别的一致性保证?使用mnesia在节点间共享数据~/platform/launcher(master*) » iex --sname t1Erlang/OTP 21 [erts-10.3.5.6] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)iex(t1@ubuntu)1> alias :mnesia, as: Mnesia:mnesiaiex(t1@ubuntu)2> Mnesia.start():okiex(t1@ubuntu)3> Mnesia.create_table(Person, [attributes: [:id, :name, :job]]) {:atomic, :ok}iex(t1@ubuntu)4> Mnesia.dirty_write({Person, 1, "Seymour Skinner", "Principal"}):okiex(t1@ubuntu)5> Mnesia.dirty_read({Person, 1})[{Person, 1, "Seymour Skinner", "Principal"}]iex(t1@ubuntu)6> Mnesia.table_info(Person, :all)[ access_mode: :read_write, active_replicas: [:t1@ubuntu], all_nodes: [:t1@ubuntu], arity: 4, attributes: [:id, :name, :job], checkpoints: [], commit_work: [], cookie: {{1593853684922256987, -576460752303423391, 1}, :t1@ubuntu}, cstruct: {:cstruct, Person, :set, [:t1@ubuntu], [], [], [], 0, :read_write, false, [], [], false, Person, [:id, :name, :job], [], [], [], {{1593853684922256987, -576460752303423391, 1}, :t1@ubuntu}, {{2, 0}, []}}, disc_copies: [], disc_only_copies: [], external_copies: [], frag_properties: [], index: [], index_info: {:index, :set, []}, load_by_force: false, load_node: :t1@ubuntu, load_order: 0, load_reason: {:dumper, :create_table}, local_content: false, majority: false, master_nodes: [], memory: 321, ram_copies: [:t1@ubuntu], record_name: Person, record_validation: {Person, 4, :set}, size: 1, snmp: [], storage_properties: [], storage_type: :ram_copies, subscribers: [], type: :set, user_properties: [], version: {{2, 0}, []}, where_to_commit: [t1@ubuntu: :ram_copies], where_to_read: :t1@ubuntu, where_to_wlock: {[:t1@ubuntu], false}, where_to_write: [:t1@ubuntu], wild_pattern: {Person, :_, :_, :_}]启动t2 ...

July 4, 2020 · 3 min · jiezi

谈谈erlang的timeout

gen_server call timeouterlang的gen_server call默认有超时, 若在指定的超时内没有收到返回. 则会exit(timeout). gen_server callgen.erl:160 do_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.什么时候应该捕获timeout?显然, 如果等待一段时间后, 没有收到消息, 有两种可能. ...

October 1, 2019 · 1 min · jiezi

Erlang-源码阅读-scheduler

移步 https://ruby-china.org/topics...

August 8, 2019 · 1 min · jiezi

Erlang-入坑笔记-Erlang-Process

移步 https://ruby-china.org/topics...

August 8, 2019 · 1 min · jiezi

Erlang-解决汉诺塔问题

最后一次更新于 2019/07/09 基本规则对于只有三个塔的汉诺塔问题我们有个基本规则: 将所有圆盘从塔1转移到塔3。小圆盘只能放在大圆盘上面。如果想移动某个特定的圆盘,必须先把其上的所有圆盘移走。基本算法根据以上规则,汉诺塔的算法可以写成以下几个步骤: 第一步: 将 N-1 个圆盘从初始塔移动到中间塔。 第二步: 再将该圆盘从初始塔移动到目的塔。 第三步: 再将剩下的 N-1 个圆盘从中间塔移动到目的塔。(对每个圆盘都进行上述操作那么这个思路可以看作是递归) 汉诺塔的 Erlang 源码在 Erlang 中,我们可以将不同塔中的圆盘表达成 [{tower1, [5,4,3,2,1]},{tower2,[]},{tower3,[]}] 的形式。 用户唯一需要做的事情就是传递初始的圆盘总数。这个数字将被列表转换成升序的数字形式。 此函数如下所示: produce(1) -> [1]; % 如果当前数字为1,就直接返回1,递归结束。produce(N) -> produce(N-1) ++ [N]. % 将为递归的数值放于当前数值之前。举个例子,如果总数为5,经过 produce() 函数运行后的结果为[1,2,3,4,5]。但这不是我们想要的结果。有人可能会提议把 produce(N-1) ++ [N] 语句掉换成 [N] ++ produce(N-1) 不就行了吗。是的,如果按当前的要求看效果已经达到了。然而实际上,我们每次都会移动塔顶最上方的圆盘,它的值一定是列表中最小的。如果在这里使用降序排列的话我们获取到的数值就会是5而不是1。因此,reverse() 函数应该作为一个额外调用的方法来写。 此函数如下所示: reverse_tower([H|T],M)-> reverse_tower(T,[H|M]); % 将新头圆盘添加到列表中,列表将自动反转。reverse_tower([],M)-> M. % 如果列表为空说明所有圆盘已排好序。reverse_tower(T)-> reverse_tower(T,[]). % 初始列表。在列表转换之后,我们可以开始通过上述算法解决问题。 该算法通过以下函数实现: %% ==================================================================================================================%% 此函数用于返回不同塔的状态和需要移动的圆盘。%% ==================================================================================================================check_status(T, Start, End)-> % 使用 _ 变量在模式匹配中作为通配符。 [{_,Tower1List},{_,Tower2List},{_,Tower3List}] = T, Status = if Start == tower1, End == tower2 -> % 添加新的圆盘到塔2并丢掉塔1最上方的圆盘。 Number = hd(Tower1List), [{tower1,tl(Tower1List)},{tower2,[Number|Tower2List]},{tower3,Tower3List}]; Start == tower1, End == tower3 -> % 添加新的圆盘到塔3并丢掉塔1最上方的圆盘。 Number = hd(Tower1List), [{tower1,tl(Tower1List)},{tower2,Tower2List},{tower3,[Number|Tower3List]}]; Start == tower2, End == tower1 -> % 添加新的圆盘到塔1并丢掉塔2最上方的圆盘。 Number = hd(Tower2List), [{tower1,[Number|Tower1List]},{tower2,tl(Tower2List)},{tower3,Tower3List}]; Start == tower2, End == tower3 -> % 添加新的圆盘到塔3并丢掉塔2最上方的圆盘。 Number = hd(Tower2List), [{tower1,Tower1List},{tower2,tl(Tower2List)},{tower3,[Number|Tower3List]}]; Start == tower3, End == tower1 -> % 添加新的圆盘到塔1并丢掉塔3最上方的圆盘。 Number = hd(Tower3List), [{tower1,[Number|Tower1List]},{tower2, Tower2List},{tower3,tl(Tower3List)}]; % 添加新的圆盘到塔2并丢掉塔3最上方的圆盘。 true -> Number = hd(Tower3List), [{tower1,Tower1List},{tower2, [Number|Tower2List]},{tower3,tl(Tower3List)}] end, {Number, Status}. % 返回一个包含当前移动圆盘的值和新的状态标识的元组。%% ==================================================================================================================%% 该函数用于将圆盘从起始塔转移到目的塔。%% 完成移动后,更新状态的表示。%% ==================================================================================================================move(T, Start, End)-> % 获得被移动圆盘的值和新的状态表示。 {Number, NewStatus} = tool:check_status(T, Start, End), io:format("-------------------------------------------~nMove No.~p disk: ~p -------> ~p ~n", [Number, Start, End]), % 打印出新的状态表示。 display_towers(NewStatus), % 返回新的状态。 NewStatus.%% ==================================================================================================================%% 该函数使用尾递归解决圆盘转移问题。%% ==================================================================================================================solve(1, Init, Aux, Dest, T)-> move(T, Init, Dest); % 最上方的圆盘可以直接移动。solve(N, Init, Aux, Dest, T) when N > 1-> % 将 N-1 个圆盘从初始塔移动到中间塔。 ResetStatus = solve(N-1, Init, Dest, Aux, T), % 再将该圆盘从初始塔移动到目的塔。 ResetStatusAgain = move(ResetStatus, Init, Dest), % 再将剩下的 N-1 个圆盘从中间塔移动到目的塔 solve(N-1, Aux, Init, Dest, ResetStatusAgain).

July 9, 2019 · 2 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

DOClever安装以及使用介绍

DOClever被赞为目前最好用的接口管理平台,强大之处在哪?试他一试。一、什么是DOClever?官网地址:http://doclever.cn/controller...DOClever与目前postman、swagger不同之处在于,不仅仅能满足接口文档开发、测试、数据mock等,还更轻量级,也对postman、swagger、RAP支持导入。注意:以下来自官网拷贝!(^▽^) DOClever是一个可视化免费开源的接口管理工具 ,可以分析接口结构,校验接口正确性, 围绕接口定义文档,通过一系列自动化工具提升我们的协作效率。DOClever前后端全部采用了javascript来作为我们的开发语言,前端用的是vue+element UI,后端是express+mongodb,这样的框架集成了高并发,迭代快的特点,保证系统的稳定可靠。主要特性:• 可以对接口信息进行编辑管理,支持 get,post,put,delete,patch 五种方法,支持 https 和 https 协议,并且支持 query,body,json,raw,rest,formdata 的参数可视化编辑。同时对 json 可以进行无限层次可视化编辑。并且,状态码,代码注入,markdown 文档等附加功能应有尽有。• 接口调试运行,可以对参数进行加密,从 md5 到 aes 一应俱全,返回参数与模型实时分析对比,给出不一致的地方,找出接口可能出现的问题。如果你不想手写文档,那么试试接口的数据生成功能,可以对接口运行的数据一键生成文档信息。• mock 的无缝整合,DOClever 自己就是一个 mock 服务器,当你把接口的开发状态设置成已完成,本地 mock 便会自动请求真实接口数据,否则返回事先定义好的 mock 数据。• 支持 postman,rap,swagger 的导入,方便你做无缝迁移,同时也支持 html 文件的导出,方便你离线浏览!• 项目版本和接口快照功能并行,你可以为一个项目定义 1.0,1.1,1.2 版本,并且可以自由的在不同版本间切换回滚,再也不怕接口信息的遗失,同时接口也有快照功能,当你接口开发到一半或者接口需求变更的时候,可以随时查看之前编辑的接口信息。• 自动化测试功能,目前市面上类似平台的接口自动化测试大部分都是伪自动化,对于一个复杂的场景,比如获取验证码,登陆,获取订单列表,获取某个特定订单详情这样一个上下文关联的一系列操作无能为力。而 DOClever 独创的自动化测试功能,只需要你编写极少量的 javascript 代码便可以在网页里完成这样一系列操作,同时,DOClever 还提供了后台定时批量执行测试用例并把结果发送到团队成员邮箱的功能,你可以及时获取接口的运行状态。• 团队协作功能,很多类似的平台这样的功能是收费的,但是 DOClever 觉得好东西需要共享出来,你可以新建一个团队,并且把团队内的成员都拉进来,给他们分组,给他们分配相关的项目以及权限,发布团队公告等等。二、DOClever环境依赖以及使用DOClever的使用,依赖nodejs和MongoDB,注意,这里的安装都是在windows系统上!(^▽^)1、安装nodejs去官网下载nodejs:https://nodejs.org/en/download/ 选择windows版本64位下载,下载完成后双击msi文件安装 至此,安装完成!win+r 输入cmd 表示安装成功!!(^▽^)PS:如果想配置环境变量等,可以参考此文:https://www.cnblogs.com/liuqi... 2、安装MongoDB去官网下载MongoDB:https://www.mongodb.com/downl... 选择windows版本64位下载,下载完成后双击msi文件安装 选择自定义路径 ...

May 12, 2019 · 1 min · jiezi

RabbitMQ的应用场景以及基本原理介绍

RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queuing Protocol)的开源实现。 AMQP :高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。 AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 一、应用场景异步处理应用解耦流量削峰二、RabbitMQ 特性RabbitMQ 最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。具体特点包括: - 可靠性(Reliability)RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。 - 灵活的路由(Flexible Routing)在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。 - 消息集群(Clustering)多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。 - 高可用(Highly Available Queues)队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。 - 多种协议(Multi-protocol)RabbitMQ 支持多种消息队列协议,比如 STOMP、MQTT 等等。 - 多语言客户端(Many Clients)RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby 等等。 - 管理界面(Management UI)RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker 的许多方面。 - 跟踪机制(Tracing)如果消息异常,RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。 - 插件机制(Plugin System)RabbitMQ 提供了许多插件,来从多方面进行扩展,也可以编写自己的插件。三、RabbitMQ 基本概念 - Message消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。 - Publisher消息的生产者,也是一个向交换器发布消息的客户端应用程序。 - Exchange交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。 - Routing Key路由关键字,exchange根据这个关键字进行消息投递。 - Binding绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。 - Queue消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。 - Connection网络连接,比如一个TCP连接。 - Channel信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。 - Consumer消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。 - Virtual Host虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。 - Broker表示消息队列服务器实体。它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输, 四、Exchange 类型Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由键,此外 headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型: ...

May 9, 2019 · 2 min · jiezi

erlang Cannot get connection id for node bug

bug 描述ping 同样sname, 使用ip作为hostname, 会使vm崩溃.~/ejoy/battlenet/gangplank/apps/gate(master*) » iex –name aaa@bbb enjolras@ubuntuErlang/OTP 21 [erts-10.0] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)iex(aaa@bbb)1> Node.ping(:aaa@ccc):pangiex(aaa@bbb)2> Node.ping(:aaa@cccddd):pangiex(aaa@bbb)3> Node.ping(:bbb@127.0.0.1)** (SyntaxError) iex:3: syntax error before: “iex(aaa@bbb)3> Node.ping(:“bbb@127.0.0.1”):pangiex(aaa@bbb)4> Node.ping(:“aaa@127.0.0.1”)11:40:38.491 [error] ** Cannot get connection id for node :aaa@bbb 11:40:38.495 [error] GenServer :net_kernel terminating** (stop) bad return value: {#PID<0.59.0>, {:accept_pending, :nok_pending}}Last message: {#PID<0.119.0>, {:accept_pending, :aaa@bbb, :aaa@bbb, {:net_address, {{127, 0, 0, 1}, 41657}, ‘bbb’, :tcp, :inet}, :normal}}State: {:state, :aaa@bbb, :aaa@bbb, :longnames, {:tick, #PID<0.61.0>, 15000}, 7000, :sys_dist, [{#PID<0.117.0>, :“aaa@127.0.0.1”}], [], [{:listen, #Port<0.3>, #PID<0.60.0>, {:net_address, {{0, 0, 0, 0}, 46035}, ‘ubuntu’, :tcp, :inet}, :inet_tcp_dist}], [], 0, :all}:pangiex(5)> Logger - error: {removed_failing_handler,‘Elixir.Logger’}{“Kernel pid terminated”,application_controller,{badarg,[{io_lib,format,["~0p”,[{application_terminated,kernel,shutdown}]],[{file,“io_lib.erl”},{line,183}]},{application_controller,to_string,1,[{file,“application_controller.erl”},{line,2028}]},{application_controller,handle_info,2,[{file,“application_controller.erl”},{line,1173}]},{gen_server,try_dispatch,4,[{file,“gen_server.erl”},{line,637}]},{gen_server,handle_msg,6,[{file,“gen_server.erl”},{line,711}]}]}}Kernel pid terminated (application_controller) ({badarg,[{io_lib,format,[[],[]],[{},{}]},{application_controller,to_string,1,[{},{}]},{application_controller,handle_info,2,[{},{}]},{gen_serverCrash dump is being written to: erl_crash.dump…done ...

April 11, 2019 · 1 min · jiezi

CAD图纸格式的版本高了的话,怎么进行手机快速转换图纸版本?

CAD图纸格式的版本高了的话,怎么进行手机快速转换图纸版本?那大家在平时得设计绘图中应该也都会遇到过相关的小问题,我们在将图纸打开需要进行查看编辑的时候,CAD图纸要不就是打不开,或者显示不全,再就是文件破损,那这是什么原因?其实,随着编辑器的版本的不同,绘制完保存的图纸也是会产出不同的版本的,随意,不同的版本的CAD图纸要是在不适应的编辑器上进行查看编辑,就会产生格式不兼容的问题,那么怎么解决?下面就最简单的手机移动端给大家解决一下方法。步骤一:手机端搜索“迅捷CAD转换器”下载安装至手机桌面(目前该软件在“百度”“腾讯应用宝”“360”“搜狗”“阿里应用”“联想”“VIVO”“小米”“华为”等多平台已上线)用户可根据需求自行选择,然后下载安装到手机桌面上即可。步骤二:在手机桌面找到迅捷CAD转换器APP,轻触点击启动进入软件缓冲可操作界面。如图所示软件主要含有CAD版本转换、CAD转PDF、PDF转CAD、CAD转图片四大功能,这里我们选择“CAD版本转换”功能即可。步骤三:点击,快速进入“数据扫描中”状态。此时软件会自动为你检索出所有适合转换的CAD文件。我们可以点击选择要进行转换的图纸文件即可,或者点击全部文件,在指定的位置打开图纸即可。步骤四:手动选择需要进行转换的CAD图纸文件,点击右上角设置小图标按钮可进行文件参数设置。主要有“输出类型”“输出版本”板块设置。这里我们选择并点击“输出版本”按钮。在底部选择输出版本(DXF)及类型!然后点击保存直接跳转至转换界面。步骤五:然后点击“开始转换”按钮进入文件转换状态。完成后,即可对文件进行转换前后查看啦!那么以上就是简单的转换方法了,希望可以帮助的到大家哦!

March 28, 2019 · 1 min · jiezi

erlang学习记录(二)进程

erlang进程erlang进程与操作系统进程不同,erlang进程的切换、生成和消息传递是由erlang虚拟机管理的。是erlang 并发单元的一个代称。 每个进程代表一个持续的活动,执行某一段代码。在执行完毕后自动退出。创建、链接进程erlang进程的创建由spawn()函数完成,spawn()常用的有以下两种形式。spawn(Fun()).spawn(Module,Function,ListOfArgs).spawn_link(Fun()).spawn_link(Module,Funtion,ListOfArgs).第二种方法要求给定的函数必须事先从模块中导出,并且初始数据只能由参数列表传入。同时,第二种方法总是采用模块的最新版本。这些函数均返回新进程的Pid。spawn_link(…)将创建的进程与原进程链接起来,确保这两个操作为原子操作。如果两个进程互相连接,则一个进程终止时会像另一个进程发错误信号{‘EXIT’,Pid,Reason}。 创建链接的函数为link(Pid),它将当前进程与Pid链接起来。监视进程链接的替代品。一种单向链接。由一下几种函数完成spawn_monitor(Fun()).spwan_monitor(Module,Function,ListOfArgs).被监视的进程退出会发消息给监视进程,监视进程退出则对被监视进程无影响。退出进程1、抛出异常终结进程。exit(Reason)除非被进程捕获,否则该调用将令进程停止,并将Reason作为退出信号的一部分发送给所有与该进程相链接的进程。2、直接向进程发送退出信号exit(Pid,Reason) 该信号终止的是接收方。发送该信号时,收发双方无需链接。 如果Reason是原子kill,则接受方无法捕捉该信号,会被强制终止。3、设置trap_exit标志process_flag(trap_exit,true) 默认情况下,进程收到链接的其他进程的退出信号就会退出。设置trap_exit标志可以避免这种情况。设置该标志后,除了无法捕捉的信号外,其他的外来退出信息都会被转换成无害的消息。进程间消息的传递receive Pattern1 when Guard1 -> %% do some thing Pattern2 when Guard2 -> %% do some thing after Time -> %% do some thing end上面代码中的关卡是为了提取部分信息,如果省略则会接受所有消息。after段也是可选的,如果省略,receive永不超时。否则 Time必须是表示毫秒数的整数或原子infinity。如果Time为0。则receive永不阻塞,如果为infinity,则receive永不超时。注册进程register(name,Pid)用于给进程绑定一个名字。方便对进程的操作。消息投递与信号进程间信息传递除了用消息投递符 ! 以外还有进程发出的退出信号和尝试链接两个进程时的链接请求。传递消息时,以下基本传递保障时对所有信号成立的如果进程A向进程B先后发送了S1和S2。无论信号间隔由多久,这两个信号都将按照发送顺序到达。尽力投递所有信号。进程字典进程自身状态的一部分,每个进程都有一个私有的进程字典。这是一个可以用任何值作为键的简单哈希表,用于存储Erlang项。通过内置函数put(key,value)和get(key,value)可以从中存取项。尽量使用Ets表。不要使用进程字典 。erlang fun函数erlang将函数视为数据。将函数封装成数据的对象称为fun函数(C++中的lamada表达式或者闭包)函数有命名函数和匿名函数命名函数可以直接通过函数名调用,也可以用来给其他函数当参数。匿名函数的形式为fun()-> end.匿名函数必须与变量绑定,或者作为参数传递给其他函数,或者作为函数返回值。

March 27, 2019 · 1 min · jiezi

erlang学习记录

记录与映射组记录(record)记录其实是元组的另一种形式。通过使用记录,可以给元组里的各个元素关联一个名称。记录使用方法通过记录命名元组里的元素:-record(Name,{ Key1 = Default1, Key2 = Default2, … key3,})创建记录#Name{Key1=Val1,Key2=Val2…}. % 所有的键都是原子提起记录字段用模式匹配操作符映射组:映射组是键值对的关联性集合。键可以是任意的Erlang数据类型。(c++/java里的map,哈希表)。#{ Key1 Op Val1,Key2 Op Val2,…}Op可以是=>或者:==>可以更新现有键的值,如果现有键不存在则创建新的键。 :=只能用来更新现有键的值,如果键不存在则报错。record或者有几个参数写几个,我们习惯这样map不是不行,只不过1)受到版本限制;2)需要用注解的形式告诉使用者可以填哪些参数;3)万一定义和使用时写的不同名字,不能通过编译器来发现问题因为是非静态类型语言,本来编译期内发现问题的机会已经减少很多了,还到处是map,就更难通过编译器发现问题了map有个好处,record没有的,是如果用map代替record,那么就不存在record改了线上不能热更的问题:Dmap我认为应该用在不确定参数的场合;而如果是参数确定的,但是可填可不填,其实用map, record, 参数列表,没什么区别,都是要去判断~顺序程序的错误处理异常错误是系统内部发生错误,或者通过在代码里显式调用exit(Exception)、throw(Exception)或error(Exception)时发出的信息。 典型内部错误有模式匹配错误用错误类型的参数调用函数用带有错误类型的参数调用内置函数抛异常的三种函数exit(Why). 一般用于终止当前进程error(Why). 指示“崩溃性错误”,也就是调用者没有准备好处理的非常严重的问题,与内部错误差不多。throw(Why). 抛出一个调用者有可能想要捕捉的异常错误。捕捉异常的方式1、把抛出异常的调用函数封装在try…catch表达式里try FunOrExperssionSeq of Pattern1[when guard1] -> Experssions1; Pattern2[when guard2] -> Experssions2; …catch ExceptionType1:Expattern1 [when ExGuard1] -> ExExpressions1; ExceptionType2:Expattern2 [when ExGuard2] ->ExExpressions2; …after AfterExperssionsend2、把调用封装在catch表达式里catch FunOrExperssionSeq 二进制与位语法类型spec :类型规范type :类型声明

March 26, 2019 · 1 min · jiezi

poolboy max_overflow 引发的血案

问题这是个线上问题. 某个服务节点在较低的qps(每秒2000次数据库访问)下, 在worker进程数100, max_overflow进程数100的情况下. 突然性能下降, 每秒只能处理1500次数据库访问. 导致请求处理延时从几MS上升至几百MS, 之后又逐渐恢复.原因逐渐把范围缩小至 mongodb poolboy 进程池的 checkout:check outhandle_call({checkout, CRef, Block}, {FromPid, _} = From, State) -> #state{supervisor = Sup, workers = Workers, monitors = Monitors, overflow = Overflow, max_overflow = MaxOverflow} = State, case Workers of [Pid | Left] -> MRef = erlang:monitor(process, FromPid), true = ets:insert(Monitors, {Pid, CRef, MRef}), {reply, Pid, State#state{workers = Left}}; [] when MaxOverflow > 0, Overflow < MaxOverflow -> {Pid, MRef} = new_worker(Sup, FromPid), true = ets:insert(Monitors, {Pid, CRef, MRef}), {reply, Pid, State#state{overflow = Overflow + 1}}; [] when Block =:= false -> {reply, full, State}; [] -> MRef = erlang:monitor(process, FromPid), Waiting = queue:in({From, CRef, MRef}, State#state.waiting), {noreply, State#state{waiting = Waiting}} end;可以看到, 当max_overflow不为0时, 瞬间过载会创建新的worker, 而这些worker, 都会去链接mongodb, 耗时1-2MS. 创建的消耗会阻塞master process.check in而归还时, 又会将worker销毁, 导致链接一直创建/销毁, 而且都卡在master process, 这导致所有的请求, 都会因master process的链接创建和销毁而阻塞, 导致qps雪崩下降.handle_checkin(Pid, State) -> #state{supervisor = Sup, waiting = Waiting, monitors = Monitors, overflow = Overflow, strategy = Strategy} = State, case queue:out(Waiting) of {{value, {From, CRef, MRef}}, Left} -> true = ets:insert(Monitors, {Pid, CRef, MRef}), gen_server:reply(From, Pid), State#state{waiting = Left}; {empty, Empty} when Overflow > 0 -> ok = dismiss_worker(Sup, Pid), State#state{waiting = Empty, overflow = Overflow - 1}; {empty, Empty} -> Workers = case Strategy of lifo -> [Pid | State#state.workers]; fifo -> State#state.workers ++ [Pid] end, State#state{workers = Workers, waiting = Empty, overflow = 0} end.结论不要使用 poolboy 的 max_overflow, 若创建/销毁 children process时有一定消耗, 很容易阻塞 poolboy master进程, 频繁创建/销毁 worker 导致雪崩.每次查BUG, 回头看来都是理所当然. 追查时却要费一番心思, 监控数据不便在个人blog给出. 不免省掉很多推断过程, 希望这个结论对大家有帮助. ...

March 6, 2019 · 2 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

EMQ源码之--EMQ的启动

又回到erlang了,使用了一段时间的golang再回到erlang有点那么的亲切感。在项目中也准备用mqtt来做消息上报,顺道就想看下他的代码。erlang中application都是通过supervisor来管理的,在emq中emqttd_sup是一个最大的supervisor,他下面面又连接了很多的supervisor或者worker。ekka:start()emqttd_sup|——–>emqttd_ctl 负责从emqttd_ctl命令过来的rpc handler|——–>emqttd_hooks(hook 函数的处理)|——–>emqttd_router(各node之间的消息路由)|——–>emqttd_pubsub_sup(管理pubsub相关的supervisor)|——–>emqttd_pool_sup(emqttd_pubsub的supervisor)gproc_pool |—–>emqttd_pubsub_1(worker) |—–>emqttd_pubsub_2(worker)|——–>emqttd_pool_sup(emqttd_server的supervisor)gproc_pool |—–>emqttd_server_1(worker) |—–>emqttd_server_2(worker)|———>emqttd_stats(stats topic相关的统计)|———>emqttd_stats(metrics topic相关的统计)|———>emqttd_pool_sup(pooler没看到哪里用到了这快)gproc_pool |——->pooler_1(worker) |——->pooler_2(worker)|———>emqttd_sm_sup( session management supervisor)gproc_pool |——->emqttd_sm_1(worker) |——->emqttd_sm_2(worker)|———>emqttd_ws_client_sup(websocket client supervisor)gproc_pool |——->emqttd_ws_client_1(worker) |——->emqttd_ws_client_2(worker)|———>emqttd_broker(broker统计相关handler)|———>emqttd_alarm(系统alerm相关的handler)|———>emqttd_mod_sup(管理外部mod的supervisor)|———>emqttd_bridge_sup_sup(bridge supervisor)|———>emqttd_access_control(auth/acl相关管理模块)|———>emqttd_sysmon_sup(system monitor supervisor) |——–>emqttd_sysmon(vm system monitor)register_acl_mod()start_listener()之后就开始socket监听了,等待新的连接到来。erlang的优势在于他又一套完善的process 监控系统。具体可以参考这里, 子进程退出后supervisor会给你自动重启。

February 21, 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

ERLANG 网工修炼笔记 ---- UDP

UDP是一个比较常用的传输层协议,erlang标准库中提供了 gen_dup 模块,要掌握UDP协议,我们首先要熟悉这个模块。函数首先看这个模块的几个公共函数:open <> closesend <> recvconnectcontrolling_processfdopenopen 和 close 比较好理解,就是在某个端口上打开一个socket,以及关闭某个socket。send 就是通过某个socket,往某个地址的某个端口发送packet。recv 就是接受某个socket中一定长度的数据。connect 就有点奇怪了,我们知道UDP协议是无连接的。事实上这个函数并没有文档,不是暴露给普通用户使用的。controlling_process 是为某个socket赋予一个新的归属进程。在erlang里,有很多的进程,他们之间相互传递消息来通信,socket从外界接收到的消息,自然也要先传递到某个进程里,才可以进入erlang的世界。归属进程的作用就是接收socket的消息。fdopen 这个函数也没有文档,作用是从某个文件描述符来打开一个socket。选项open ,也就是打开一个socket的时候需要传入Option设置,有下列选项:list | binary | {mode, list | binary} 以列表还是字符串的形式接收Packet。{ip, Address} 当host有多个网络接口的时候,选择其中一个。{ifaddr, Address} 和 {ip, Address} 一样的。{fd, integer() >= 0} 如果有socket不是使用 gen_udp 来打开的,那么就可能需要设置一个文件描述符。inet6 | inet | local 设置socket的类型。{udp_module, module()} 覆盖默认的udp模块。{multicast_if, Address} 为多播socket设置本地设备。{multicast_loop, true | false} 为真时,多播的packets会循环地返回到本地socket。{multicast_ttl, Integer} 多播的TTL,默认是1.{add_membership, {MultiAddress, InterfaceAddress}} 加入多播群。{drop_membership, {MultiAddress, InterfaceAddress}} 离开多播群。{active, true | false | once | N} 如果为真,socket收到的所有消息会发送到归属进程。如果为假,就需要显式调用recv;once 是收到消息后,就会发送到进程,但会变为false。数值是指接收多少条数据。{buffer, Size} 用户层级的缓冲区大小。{delay_send, Boolean} 通常erlang会立刻发出给socket的消息,开启这个选项后,会等待一会儿然后集合发出。{deliver, port | term} 发送给归属进程的消息的格式。{dontroute, Boolean} 对于发出的消息是否采用路由。{exit_on_close, Bloolean} socket 关闭时退出归属进程。{header, Size} 只有当binary起作用时才有用,单位是byte。选项太多了,下列的在之后有用到的话再来解释:{high_msgq_watermark, Size}{ipv6_v6only, Boolean}{linger, {true | false, Seconds}}{low_msgq_watermark, Size}{netns, Namespace :: file:filename_all()}{bind_to_device, Ifname :: binary()}{raw, Protocol, OptionNum, ValueBin}{read_packets, Integer}{recbuf, Size}{recvtclass, Boolean}{recvtos, Boolean}{recvttl, Boolean}{reuseaddr, Boolean}{send_timeout, Integer}{send_timeout_close, Boolean}{sndbuf, Size}{priority, Integer}{tos, Integer}{tclass, Integer} ...

December 29, 2018 · 1 min · jiezi

实现Golang和Erlang的连接(Port)

title: 实现Golang和Erlang的连接(Port)categories: Golang在Erlang中,有很多种方式去实现与其他语言的交互,常见的几种方式有使用TCP协议交互使用Port使用Erl_Interface方式去实现CNodeNIF后面几种难度都是有的,也使用了比较复杂的C/C++,而且比较容易出现问题。TCP的方式是通过网络协议,个人也不是很喜欢,那就剩下Port方式去连接Erlang服务器。Erlang的官方文档中对Port的介绍在Erlang中使用Port非常简单,实际上就是通过标准输入输出流与外部程序就行交互。现在我还是沿用官方文档中代码,仅做部分修改,实现Erlang与Golang的交互。%% complex1.erl-module(complex1).-export([start/1, stop/0, init/1]).-export([foo/1, bar/1]).start(ExtPrg) -> spawn(?MODULE, init, [ExtPrg]).stop() -> complex ! stop.foo(X) -> call_port({foo, X}).bar(Y) -> call_port({bar, Y}).call_port(Msg) -> complex ! {call, self(), Msg}, receive {complex, Result} -> Result end.init(ExtPrg) -> register(complex, self()), process_flag(trap_exit, true), %% 注意{packet, 2}代表的是用2个字节表示传输的 %% Port = open_port({spawn, ExtPrg}, [{packet, 2}]), %% 在这里不去处理这个数据头,不需要这个参数 Port = open_port({spawn, ExtPrg}, []), loop(Port).loop(Port) -> receive {call, Caller, Msg} -> Port ! {self(), {command, encode(Msg)}}, receive {Port, {data, Data}} -> Caller ! {complex, decode(Data)} end, loop(Port); stop -> Port ! {self(), close}, receive {Port, closed} -> exit(normal) end; {‘EXIT’, Port, Reason} -> exit(port_terminated) end.%% 进行编码,将数据转成2进制encode({foo, X}) -> <<1:8, X:8>>;encode({bar, Y}) -> <<2:8, Y:8>>].%% 这里有点特别,decode(Data) -> erlang:list_to_binary(Data).对于Golang,需要读取标准输入流的数据,这里我用bufio处理下(其实也没有太多必要,哈哈哈)。因为这里没有使用数据流的头部来记录数据的长度,所以默认将数据长度设置为2的byte数组。// port.gopackage mainimport ( “bufio” “os”)func main() { reader := bufio.NewReader(os.Stdin) writer := bufio.NewWriter(os.Stdout) for { buff := make([]byte, 2) _, err := reader.Read(buff) if err != nil { panic(err.Error()) } switch int(buff[0]) { case 1: buff[1] = byte(foo(int(buff[1]))) case 2: buff[1] = byte(bar(int(buff[1]))) } writer.Write(buff[1:2]) // 这里需要注意,要用Flush进行处理,否则erlang端收不到信息 writer.Flush() }}func foo(num int) int { return num + 1}func bar(num int) int { return 2 * num}使用步骤1 编译golanggo build port.go步骤2 运行Erlang虚拟机unix> erlErlang (BEAM) emulator version 4.9.1.2Eshell V4.9.1.2 (abort with ^G)1> c(complex1).{ok,complex1}步骤3 运行2> complex1:start(“extprg”).<0.34.0>3> complex1:foo(3).44> complex1:bar(5).105> complex1:stop().stop ...

December 24, 2018 · 1 min · jiezi