2015年4月

以库的形式调用YUI Compressor

import com.yahoo.platform.yui.compressor.CssCompressor;
import org.apache.commons.io.IOUtils;
import java.io.*;

public class YUIProcessor {
    public String compressCss(String code) {
        Reader in = new InputStreamReader(IOUtils.toInputStream(code));
        try (Writer out = new StringWriter()) {
            CssCompressor compressor = new CssCompressor(in);
            compressor.compress(out, -1);
            out.flush();
            return out.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }
}

追加:

yui compresser 很久没有更新,有很多的不足,已经废弃使用。

单锁的分拆降低冲突概率

      +-----------+
-->   | 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

线上数据无缝迁库

当 数据量 和 qps/tps 达到一定的时候,将某块业务的一系列的表迁移到另外一个库是必然需要的。

    +----------------------------------------------+
A   |                                              |
    +----------------------------------------------+

    ++
B   ||
    ++

双写

    +----------------------------------------------+------------+
A   |                                              |            |
    +----------------------------------------------+------------+

    ++                                             +------------+
B   ||                                             |            |
    ++                                             +------------+

数据 dump

可以一次dump 10条数据 (拍脑袋),对于更新比较频繁的表可以适当减少,这里会有少量的数据是不一致的 (表的并发更新之类,dump 不能保证原子性),不用担心,我们可以在后面校验数据的时候直接修复掉

对 id取模,多进程dump 加快速度

    +----------------------------------------------+------------+
A   |                                              |            |
    +----------------------------------------------+------------+

    +-------------+                                +------------+
B   |             |                                |            |
    +-------------+                                +------------+

数据校验

这块要一条一条数据的进行对比 (扫表)

切读

在业务代码里面,将 db 的连接改到新库 B 上

去写

到这一步,代码里面的双写就可以直接去掉了;完美迁库。(旧库中的表不急忙删除,你懂的)

私信的设计

  1. 发件人和消息的关系 (一对多)
  2. 消息和收件人的关系 (多对多)
pre_messages

+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(11) unsigned | NO   | PRI | NULL    | auto_increment |
| sender_id  | int(11) unsigned | NO   |     | 0       |                |
| message    | varchar(255)     | NO   |     |         |                |
| type       | varchar(50)      | NO   |     |         |                |
| expires_at | datetime         | YES  |     | NULL    |                |
| created_at | datetime         | NO   |     | NULL    |                |
| updated_at | datetime         | NO   |     | NULL    |                |
| deleted_at | datetime         | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+

pre_message_recipients

+--------------+---------------------+------+-----+---------+----------------+
| Field        | Type                | Null | Key | Default | Extra          |
+--------------+---------------------+------+-----+---------+----------------+
| id           | int(11) unsigned    | NO   | PRI | NULL    | auto_increment |
| recipient_id | int(11) unsigned    | NO   |     | 0       |                |
| message_id   | int(11) unsigned    | NO   |     | 0       |                |
| status       | tinyint(1) unsigned | NO   |     | 0       |                |
| created_at   | datetime            | NO   |     | NULL    |                |
| updated_at   | datetime            | NO   |     | NULL    |                |
| deleted_at   | datetime            | YES  |     | NULL    |                |
+--------------+---------------------+------+-----+---------+----------------+