单锁的分拆降低冲突概率

      +-----------+
-->   | counter 1 |
      +-----------+
-->   | counter 2 |
      +-----------+   -> sum(subCounter)
-->   | counter 3 |
      +-----------+
-->   | counter 4 |
      +-----------+

JAVA 中的 ConcurrentHashMap 和 LongAdder 的应用

在内部都有多个计数器,这样在并发更新对象的时候,随机选择一个,就可以将锁冲突的概率降低到 1/n

mysql 计数器的应用

拿文章的喜欢计数来做例子,当有n个人同时点击喜欢的时候,INNODB会锁住这条记录,这篇文章的喜欢计数器将是串行增长的。

pre_posts

+-------+-----+----------+
|  id   | ... |   favs   |
+-------+-----+----------+
| 10000 | ... |   8001   |
+-------+-----+----------+

再看下面的,可以将喜欢数从 posts 表拆分出来,一个文章 id,对应多个子计数器,在更新计数器的时候,可以随机选择一条记录增加,在取文章喜欢数的时候,可以sum一下累加起来;这样就降低了并发时候的冲突。

pre_post_favs

+-----+----------+----------+
| id  | post_id  |   favs   |
+-----+----------+----------+
|  1  |  10000   |   2001   |
|  2  |  10000   |   1994   |
|  3  |  10000   |   2010   |
|  4  |  10000   |   1898   |
+-----+----------+----------+

对于计数器的缓存

显然上面的数据解决了数据并发写入的问题,但是查询的代价却是提高了,想到的办法就是对计数器进行缓存。

读取的时候被动缓存的话,就会产生计数器的延迟;所以这里可以采用 canal 监听 mysql binlog 主动触发 cache 的更新。

那么问题又来了,既然是存到 redis 中,该用什么类型的;如果使用大量的 string 的话,key 就会占用大量的内存,可以部分改用 hash,但是 redis 的设定中,只有 数量 < hash-max-ziplist-entries 的时候才会采用优化的存储方式;根据多位大牛的研究 1000 是比较合适的。

favs:10
    000 -> 8001
    001 -> 0
    ...
    999 -> 0

标签: none

添加新评论