Redis是一个使用内存进行存储的高性能的NoSQL数据库. 它看上去很快, 用起来也确实很快. 但是, 它不是万灵药, 仍然存在掉速的问题. 此外, Redis为了实现极高的性能, 在一些地方做出了妥协, 比如牺牲了SQL那样的事务能力. 本文将讲述个人使用Redis中遇到的问题及解决方案.
- 尽管个人正在做的项目比较玩具, 但这并不意味着可以少了一颗针对高性能优化的心. 在使用Redis做存储的时候, 将所有东西放在一个key下很方便. 尤其是在使用hash等数据类型的时候, 常常会只顾往里存key, 而不管hash的总value已经大到什么程度. 甚至在使用bitmap存储点赞信息的时候, value也会超过建议值: 10KB内存可以存放大概8万人/8万视频, 看起来在企业环境中很容易就会超出. 大value/bigkey会导致内存创建和释放时间长的问题, 更重要的是, 网络延迟是不可忽略的降低性能的因素. 如果实在无法避免, 或许lazy-free机制可以缓解删除bigkey时的阻塞问题.
- Redis看上去性能很高, 但是它是单线程的! 所以, 复杂度过高的指令会阻碍后边指令的执行. 不要看着各种count甚至是sort等操作眼馋. 这里有一个经常被忽视的坑: 读写list的时候, linsert, lrange等命令是o(n)的! 对list还是只顾开头结尾, 当个队列比较好, 插入等要慎重.
- Redis有一个特性不得不提. 那就是事务问题. Redis本身是支持事务的 , 但是这个事务非各种SQL数据库的那种事务, 它只能保证事务本身执行的原子性, 一点点事务整体回滚的支持还是靠watch实现的. 至于是我内某条命令失败, Redis是不会回滚单条命令或者终止执行的! 使用要小心, 不要有那些SQL数据库的思维惯性. Redis有一个很容易被误解的功能, 那就是pipline. 它并不是事务! pipeline并不会被Redis服务器特殊处理, 它只不过是用于将多条命令整合在一起减少传输延迟的, 它甚至不能保证pipeline各条命令的连续执行! 需要这种多条命令整体表现出原子性, 比如读取完直接删除以防止把读后加入的数据误删, 需要multi和exec实现事务. Redis的事务支持看上去很弱, 但事实上还是很实用的, 至少保证里命令的连续执行. 事务内虽然无法单条回滚, 但是出错仅有可能是在语法错误或类型错误的时候, 这些在编写程序时很容易避免, 也就没有了事务内回滚这种削弱性能的东西存在的必要.
- 最后, Redis的单条命令是原子性的! 这就体现出incr等命令的价值. 能一条命令解决, 不要迷信事务.