MemCachedClient优化记
最近跟个朋友的聊天,谈到了Java的Memcached客户端的问题。他提到Java的Memcached客户端的性能并不乐观,而这激起了我的兴趣。
于是下载了一个MemCachedClient,开始研究。既然说它慢,但是光看源代码来进行“大脑编译运行”太累,而且还不容易找到病根,所以借助了一下JProfiler。在eclipse下写了个简单的UnitTest,不过是循环地向memcached添加和读取数据罢了。不运行不知道,一运行吓一跳。1000次循环写入居然用了3秒多,而且memcached服务器还在本机,没有网络延迟。(不过机器并不快)
索性挂着JProfiler跑了一遍,发现一个非常恶心的热点——Log4J的日志操作!反正源代码是要改的,所以干脆把所有日志的调用都注释掉了。再跑一遍测试,恩,这次快了不少。有人要说了,日志并不会浪费太多的时间啊。没错,有时候我们使用日志来反馈程序运行的状态是个好习惯,但当你看到MemCachedClient代码中所用到的日志,想必你也会大吃一惊,这里有太多的日志了!而且有一部分日志所调用的Socket.toString方法更是耗费CPU时间。虽然这种优化有一棍子打死之嫌疑,不过真正要在系统跑起来,你不会太在乎一个缓存系统的日志,何况这玩意的日志信息多的要命!
好了,日志的问题解决了,但在此用JProfiler分析之后,发现了3个比较重要的热点,1个是I/O流的读取,一个是String.format,一个是String.split。
对于I/O流的读取,优化的方法是套一层BufferedInputStream,这一点很容易想到(为啥MemCachedClient的作者没有用BufferedInputStream套一下呢?)。至于String.format,看看源码发现直接用字符串相加,让编译器编译成StringBuffer.append比较合适。优化后跑了一遍测试发现这样效果确实会好一点。
对于String.split,可以看看JDK的源码,其实是调用了正则表达式的库,所以这个split会比较慢。不过从MemCachedClient的源码和memcached的协议来看,可以手工写一个split方法来替代这个比较慢的split方法:

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

在另外一个地方需要提到的是在做JProfiler时,发现ByteArrayOutputStream.toString也是比较耗时的方法。在此我有个不太有把握的优化:
把ByteArrayOutputStream替换成CharArrayWriter。虽然在向ByteArrayOutputStream中写入数据时是写入byte,到了CharArrayWriter时是char,但是可以用0x00FF & b的方法来把byte转换成char。也就是前面加一个字节的0。不过这可能会遇到一个问题——当我们存入memcached的key是非英文字母时,这个优化就得不到正确的结果。不过另外一想,用中文当key的情况会有很多吗?当然如果真的会出现这种情况,替换成原来的方案也不是很方便的。
为了正确性,这个优化可以暂时放弃。不过在某种极限的情况下,似乎还是可行的。
通过这次经历,发现了几点对性能考量比较多的时候应该注意的地方:
1 日志太多会影响性能
2 format方法虽然好用,但性能不如StringBuilder
3 split方法有时候自己写会比库中的要快一点
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端