Erlang频率分配计费服务实现:耗时计算与代码修正
Erlang 频率管理计费服务实现修正
原代码存在的核心问题
- 计费逻辑缺失:注释掉了费率计算部分,无法生成正确的费用账单
- 时间粒度过粗:使用秒级时间戳统计时长,短于1秒的使用场景会出现计费误差
- 容错能力不足:DETS表未配置自动修复,服务异常崩溃后可能出现表损坏无法加载的问题
- 配置硬编码:合法频率范围、费率、表名等参数散落在逻辑代码中,后续调整维护成本高
- 错误提示模糊:原有错误码语义不明确,客户端无法区分是自身重复申请还是频率被其他用户占用
- 状态定义无效:使用占位符
other_data_you_need作为服务状态,未实际承载运行时配置
修正后完整实现代码
-module(b). -compile([export_all, nowarn_export_all]). % 宏定义配置项 -define(VALID_FREQS, lists:seq(1,10)). -define(RATE_PER_SEC, 5). -define(DETS_FILE, "example.dets"). -define(DETS_TABLE, example). run() -> file:delete(?DETS_FILE), % 打开DETS表并开启自动修复 case dets:info(?DETS_TABLE, owner) of undefined -> {ok, _} = dets:open_file(?DETS_TABLE, [{file, ?DETS_FILE}, {repair, true}]); _ -> skip end, spawn(fun() -> init() end), timer:sleep(100), Client = self(), % 第一轮基础功能测试 ?DETS_TABLE ! {allocate, Client, aaa, 5}, ?DETS_TABLE ! {allocate, Client, bbb, 5}, ?DETS_TABLE ! {allocate, Client, aaa, 5}, timer:sleep(1000), ?DETS_TABLE ! {release, Client, aaa, 5}, ?DETS_TABLE ! {release, Client, aaa, 5}, timer:sleep(100), io:format("第一轮测试返回结果:~p~n", [receive_server_msg([])]), % 第二轮服务重启持久化测试 ?DETS_TABLE ! {allocate, Client, aaa, 5}, timer:sleep(100), exit(whereis(?DETS_TABLE), kill), timer:sleep(50), spawn(fun() -> init() end), timer:sleep(100), ?DETS_TABLE ! {allocate, Client, bbb, 5}, ?DETS_TABLE ! {allocate, Client, aaa, 5}, timer:sleep(1000), ?DETS_TABLE ! {release, Client, aaa, 5}, ?DETS_TABLE ! {release, Client, aaa, 5}, timer:sleep(100), io:format("第二轮测试(服务重启后)返回结果:~p~n", [receive_server_msg([])]), ?DETS_TABLE ! {stop}, dets:close(?DETS_TABLE). receive_server_msg(List) -> receive Reply -> receive_server_msg([Reply|List]) after 0 -> lists:reverse(List) end. init() -> erlang:register(?DETS_TABLE, self()), % 初始化服务状态,存储运行时配置 State = #{rate => ?RATE_PER_SEC, valid_freqs => ?VALID_FREQS}, manager(State). manager(State) -> receive {stop} -> ok; {allocate, Requestor, Name, Frequency} -> case check_allocate_request_and_modify_state(Name, Frequency, State) of {ok, State1} -> Requestor ! {grant, Frequency}, manager(State1); {error, Reason} -> Requestor ! {error, Reason}, manager(State) end; {release, Requestor, Name, Frequency} -> case check_release_request_and_modify_state(Name, Frequency, State) of {ok, Cost, State1} -> Requestor ! {bill, Cost}, manager(State1); {error, Reason} -> Requestor ! {error, Reason}, manager(State) end; UnknownMsg -> logger:error("收到未知消息: ~p", [UnknownMsg]), manager(State) end. check_allocate_request_and_modify_state(Name, Frequency, State = #{valid_freqs := ValidFreqs}) -> case lists:member(Frequency, ValidFreqs) of true -> case dets:lookup(?DETS_TABLE, Frequency) of [] -> % 记录毫秒级起始时间,提升计费精度 dets:insert(?DETS_TABLE, {Frequency, Name, erlang:system_time(millisecond)}), {ok, State}; [{_, Name, _}] -> {error, already_using_by_self}; _ -> {error, occupied_by_other} end; false -> {error, invalid_frequency} end. check_release_request_and_modify_state(Name, Frequency, State = #{rate := Rate}) -> case dets:lookup(?DETS_TABLE, Frequency) of [{_, Name, StartTimeMs}] -> dets:delete(?DETS_TABLE, Frequency), % 时长转秒后乘以费率,保留2位小数 DurationSec = (erlang:system_time(millisecond) - StartTimeMs) / 1000, Cost = round(DurationSec * Rate * 100) / 100, {ok, Cost, State}; [] -> {error, frequency_not_allocated}; [{_, _OtherName, _}] -> {error, occupied_by_other} end.
耗时计算实现逻辑
频率使用时长的统计逻辑不需要额外复杂组件,按时间差计算即可:
- 客户端申请频率通过校验后,立刻获取当前服务器毫秒级时间戳,和频率ID、客户端标识一起写入DETS持久化存储
- 客户端发起释放请求时,再次获取当前服务器时间,减去存储的起始时间戳得到实际使用时长(毫秒),转换为秒单位后乘以5Rs/Sec的费率得到总费用
- 因为占用记录做了持久化,即使服务进程中途崩溃重启,起始时间数据不会丢失,重启后释放频率依然可以统计完整的使用周期时长,不会出现计费断层
内容的提问来源于stack exchange,提问作者Shree priya
相关产品推荐
相关产品推荐

