gen_server + ets的几个小技巧
2016-04-14 12:25
696 查看
最近团队中的一个小伙伴在实现一个缓存服务,使用ets作为缓存的存储,使用gen_server实现管理。更新缓存和读取缓存的逻辑都是通过handle_call实现的。在性能测试过程中发现读取的效率不理想,并且gen_server有排队现象。
后来做代码评审的时候发现了上面的问题。由于handle_call处理请求是需要在gen_server的消息队列里排队的,因此如果在handle_call中实现读取逻辑是一定会影响读取效率的,同一时间只能处理一个读取请求。另一方面,读取请求排队也会影响更新请求的处理。实际上读取逻辑是没必要在handle_call中实现的,可以直接读取ets。修改之后性能有了明显的提升。
说到这里大家都以为解决问题了,实际上优化后还会引入另一个问题。因为更新的逻辑是要判断缓存中是否存在对应记录,不存在时再更新缓存。在并发场景下,同一时间可能存在很多对同一条记录的读取操作。如果这条记录不存在,就有可能产生很多对相同记录的更新请求,导致重复获取记录内容并写入ets。如果获取记录内容的操作很慢,就会导致gen_server消息队列不断挤压请求,很可能导致OOM。
我个人在解决这种问题的时候一般是两种思路:
在handle_call中先读取一次ets,如果存在就不用执行获取记录内容的操作。
在handle_call中做请求清理操作,将相同请求都receive出来并统一返回。
第2条中的清理操作是参考了《Programming Erlang》书中的一个例子,使用receive after 0实现:
原理就是利用了after 0会遍历一次消息队列,查找能够匹配receive中Pattern的消息,如果找到则执行对应分支操作。否则执行after 0的操作。
另外需要注意的是在handle_call中receive的被清理的消息Pattern是{‘$gen_call’, From, Msg},这个模式可以从gen_server的源码中可以找到。根据Msg找到相同的请求,再使用gen_server:reply(From, 返回值)方法返回响应。
gen_server确实给并发编程带来了很多的遍历,但会给初学者带来“已经掌握”的错觉。其实里面还是有坑的,一不小心就踩进去了。哪些操作可以防止client端,哪些请求需要做重复判断,子进程如何管理等问题还是需要在设计时结合业务场景好好考虑的。
后来做代码评审的时候发现了上面的问题。由于handle_call处理请求是需要在gen_server的消息队列里排队的,因此如果在handle_call中实现读取逻辑是一定会影响读取效率的,同一时间只能处理一个读取请求。另一方面,读取请求排队也会影响更新请求的处理。实际上读取逻辑是没必要在handle_call中实现的,可以直接读取ets。修改之后性能有了明显的提升。
说到这里大家都以为解决问题了,实际上优化后还会引入另一个问题。因为更新的逻辑是要判断缓存中是否存在对应记录,不存在时再更新缓存。在并发场景下,同一时间可能存在很多对同一条记录的读取操作。如果这条记录不存在,就有可能产生很多对相同记录的更新请求,导致重复获取记录内容并写入ets。如果获取记录内容的操作很慢,就会导致gen_server消息队列不断挤压请求,很可能导致OOM。
我个人在解决这种问题的时候一般是两种思路:
在handle_call中先读取一次ets,如果存在就不用执行获取记录内容的操作。
在handle_call中做请求清理操作,将相同请求都receive出来并统一返回。
第2条中的清理操作是参考了《Programming Erlang》书中的一个例子,使用receive after 0实现:
clear() -> receive 被清理的消息Pattern -> 清理操作, clear() after 0 -> 清理完成后的操作 end.
原理就是利用了after 0会遍历一次消息队列,查找能够匹配receive中Pattern的消息,如果找到则执行对应分支操作。否则执行after 0的操作。
另外需要注意的是在handle_call中receive的被清理的消息Pattern是{‘$gen_call’, From, Msg},这个模式可以从gen_server的源码中可以找到。根据Msg找到相同的请求,再使用gen_server:reply(From, 返回值)方法返回响应。
gen_server确实给并发编程带来了很多的遍历,但会给初学者带来“已经掌握”的错觉。其实里面还是有坑的,一不小心就踩进去了。哪些操作可以防止client端,哪些请求需要做重复判断,子进程如何管理等问题还是需要在设计时结合业务场景好好考虑的。
相关文章推荐
- Erlang项目内存泄漏分析方法
- Erlang实现的一个Web服务器代码实例
- Erlang并发编程介绍
- Erlang的一些编程技巧分享
- Erlang程序设计(第2版)读书笔记:Erlang安装和基础语法
- Erlang中的模块与模式匹配介绍
- Erlang中的函数与流程控制介绍
- Erlang语法学习笔记:变量、原子、元组、列表、字符串
- Erlang中的Record详解
- Erlang初学:Erlang的一些特点和个人理解总结
- Erlang中的OTP简介
- Erlang中遍历取出某个位置的最大值代码
- Erlang中3种生成随机数的方法
- Erlang中的并发程序简介
- Erlang分布式节点中的注册进程使用实例
- Erlang中的注册进程使用实例
- Erlang中的映射组Map详细介绍
- CentOS 6.5源码安装Erlang教程
- Erlang的运算符(比较运算符,数值运算符,移位运算符,逻辑运算符)
- Erlang实现的百度云推送Android服务端实例