关于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