请选择 进入手机版 | 继续访问电脑版

默认
发表评论 7
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
关于微信的IM聊天消息序列号生成实践的讨论
前一阵子阅读了本站的这篇文章

IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)

当下觉得蛮有趣的,最近就用Java自己实现看看

完成的结果如下


@Service
@Scope(value="singleton")
public class SequenceGenerator {
    private static final Logger LOGGER = LoggerFactory.getLogger(SequenceGenerator.class);

    private final float REDUNDANCY = 0.85F;

    @Autowired
    private JdbcTemplate jdbcTemplate;
    private int machineID;
    private long seq = 0;
    private long maxSeq = 100000;
    private int stepSeq = 100000;
    private long deathLine = (long)(maxSeq * REDUNDANCY);
    private boolean initFlag = false;
    private boolean updateFlag = false;
    private Map<String, Long> container = new HashMap();

    public void init(int machineID) throws DataAccessException {
        this.machineID = machineID;

        List<Map<String, Object>> rows = jdbcTemplate.queryForList("SELECT * FROM chat WHERE machine_id = ?", new Object[] { machineID });

        if (rows.size() == 0) {
            initFlag = true;
            String dateString = DateUtil.getNowString();

            jdbcTemplate.update(
                    "INSERT INTO chat (machine_id, max_value, step_value, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
                    new Object[] {machineID, maxSeq, stepSeq, dateString, dateString}
            );
            LOGGER.info(toString());

            return;
        }

        seq = (long) rows.get(0).get("max_value");
        maxSeq = (long) rows.get(0).get("max_value");
        stepSeq = (int) rows.get(0).get("step_value");

        updateSeq();

        initFlag = true;
    }

    private void updateSeq() throws DataAccessException {
        LOGGER.info("Update sequence starting...");

        String dateString = DateUtil.getNowString();

        long _maxSeq = maxSeq + stepSeq;
        long _deathLine = (long)(_maxSeq * REDUNDANCY);

        jdbcTemplate.update(
                "UPDATE chat SET max_value = ?, updated_at = ? WHERE machine_id = ?",
                new Object[] {_maxSeq, dateString, machineID}
        );

        maxSeq = _maxSeq;
        deathLine = _deathLine;

        LOGGER.info(toString());
        LOGGER.info("Update sequence finish");
    }

    public synchronized long getSeq(String groupID) {
        if (!initFlag) {
            throw  new RuntimeException("The generator not initialized yet");
        }

        long _seq = container.getOrDefault(groupID, seq);

        _seq++;

        if (_seq >= deathLine) {
            startUpdateSeq();
        }

        if (_seq >= maxSeq) {
            throw  new RuntimeException("The sequence has reached its limit");
        }

        container.put(groupID, _seq);

        return _seq;
    }

    private synchronized void startUpdateSeq() {
        if (updateFlag) {
            return;
        }

        updateFlag = true;

        new Thread(() -> {
            try {
                updateSeq();
            } catch (DataAccessException dae) {
                LOGGER.error(dae.getMessage());
            } finally {
                updateFlag = false;
            }
        }).start();
    }

    @Override
    public String toString() {
        return "SequenceGenerator{" +
                "machineID=" + machineID +
                ", seq=" + seq +
                ", maxSeq=" + maxSeq +
                ", stepSeq=" + stepSeq +
                ", deathLine=" + deathLine +
                '}';
    }
}


我不确定我实现的跟那篇文章的想法有没有对上

不过我不是以 uid 当区分,我用的是 gid(两人私聊也可以当作是一种群聊)

但我对以下这部分有点存疑


public synchronized long getSeq(String groupID) {
        if (!initFlag) {
            throw  new RuntimeException("The generator not initialized yet");
        }

        long _seq = container.getOrDefault(groupID, seq);

        _seq++;

        if (_seq >= deathLine) {
            startUpdateSeq();
        }

        if (_seq >= maxSeq) {
            throw  new RuntimeException("The sequence has reached its limit");
        }

        container.put(groupID, _seq);

        return _seq;
    }


为了确保不会有两个人拿到一样的 seq ,所以加上了 synchronized

但是这种方式对吞吐量来说好像反而是种拖累,不管外层开放给用户的方式是走 HTTP 还是 TCP

到了 getSeq 还是得要排队,不知道大神们有没有什么建议可以改善这部分的

又或是说我做错了?

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

上一篇:有没有对微信开源的即时通信底层框架Mars有深入的了解的下一篇:Im客户端聊天记录存储本地和存储服务器那个中方案最佳,为什么?
推荐方案
评论 7
微信的这个id,是按会话来的,也就是每对聊天的人保存一个序列号的基准值,不是全局一个。
引用:JackJiang 发表于 2021-04-11 17:15
微信的这个id,是按会话来的,也就是每对聊天的人保存一个序列号的基准值,不是全局一个。

我也是依照会话(group_id)来取 Seq

但如果不加入synchronized,两个相同的 group_id 会有取到相同Seq的风险
引用:Mike12138 发表于 2021-04-11 18:23
我也是依照会话(group_id)来取 Seq

但如果不加入synchronized,两个相同的 group_id 会有取到相同S ...

微信的这个seq只是从服务端拉取一个基准值,然后在客户端去自增,不是每个seq用时从服务端拉,所以资源争用的情况,没你想的这样
引用:JackJiang 发表于 2021-04-11 20:50
微信的这个seq只是从服务端拉取一个基准值,然后在客户端去自增,不是每个seq用时从服务端拉,所以资源争 ...

如果文章内真的是客户端取一次后自增,那微信支持电脑、手机双登入,不就一样有可能会变成 seq 冲突..

想不透阿~想不透
引用:Mike12138 发表于 2021-04-11 21:40
如果文章内真的是客户端取一次后自增,那微信支持电脑、手机双登入,不就一样有可能会变成 seq 冲突..

...

对于微信来说,这个seq基准值的拉取,应该也是需要多端同步的。
引用:JackJiang 发表于 2021-04-11 20:50
微信的这个seq只是从服务端拉取一个基准值,然后在客户端去自增,不是每个seq用时从服务端拉,所以资源争 ...

站主你好,我有一个疑问,如果是客户端去拉去自增的话,假设两个用户同时上线时都获取到了200的max_seq,然后两个人互相对话,由于是每次+1的自增,后面说话的人的cur_seq反而会比前面说话的人的cur_seq小,这样顺序不就乱套了吗?
引用:461611894 发表于 2021-04-13 15:08
站主你好,我有一个疑问,如果是客户端去拉去自增的话,假设两个用户同时上线时都获取到了200的max_seq, ...

微信的消息顺序,应该不全是由这个seq的顺序来保证,以目前的资料来说,确实存在你说的这种可能性。
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部