即时消息技术剖析与实战
袁武林
微博研发中心技术专家
立即订阅
6503 人已学习
课程目录
已完结 24 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 搞懂“实时交互”的IM技术,将会有什么新机遇?
免费
基础篇 (8讲)
01 | 架构与特性:一个完整的IM系统是怎样的?
02 | 消息收发架构:为你的App,加上实时通信功能
03 | 轮询与长连接:如何解决消息的实时到达问题?
04 | ACK机制:如何保证消息的可靠投递?
05 | 消息序号生成器:如何保证你的消息不会乱序?
06 | HttpDNS和TLS:你的消息聊天真的安全吗?
07 | 分布式锁和原子性:你看到的未读消息提醒是真的吗?
08 | 智能心跳机制:解决网络的不确定性
场景篇 (4讲)
09 | 分布式一致性:让你的消息支持多终端漫游
10 | 自动智能扩缩容:直播互动场景中峰值流量的应对
11 | 期中实战:动手写一个简易版的IM系统
12 | 服务高可用:保证核心链路稳定性的流控和熔断机制
进阶篇 (10讲)
13 | HTTP Tunnel:复杂网络下消息通道高可用设计的思考
14 | 分片上传:如何让你的图片、音视频消息发送得更快?
15 | CDN加速:如何让你的图片、视频、语音消息浏览播放不卡?
16 | APNs:聊一聊第三方系统级消息通道的事
17 | Cache:多级缓存架构在消息系统中的应用
18 | Docker容器化:说一说IM系统中模块水平扩展的实现
19 | 端到端Trace:消息收发链路的监控体系搭建
20 | 存储和并发:万人群聊系统设计中的几个难点
21 | 期末实战:为你的简约版IM系统,加上功能
22 | 答疑解惑:不同即时消息场景下架构实现上的异同
结束语 (1讲)
结束语 | 真正的高贵,不是优于别人,而是优于过去的自己
即时消息技术剖析与实战
登录|注册

11 | 期中实战:动手写一个简易版的IM系统

袁武林 2019-09-20
你好,我是袁武林。
到上一讲为止,IM 的相关课程已经进行过半,在前面的课程中,我们讨论的大部分内容都比较偏理论,你理解起来可能会比较抽象。为了让你对前面讲到的知识有更深入的理解,今天我们就来回顾、梳理近期学习的内容,一起尝试搭建一个简单的 IM 聊天系统。
在开始之前呢,我先来说明一下 IM 课程的期中、期末实战的课程计划和设计思路。
期中和期末实战是希望你以自己动手实现为主,提供的 Demo 主要作为参考,在设计层面上,并不能让你直接用于线上使用。
本次期中实战 Demo 的主要关注点是:消息的存储、未读数的设计,并以“短轮询”的方式来实现消息的实时触达。希望你能从用户的使用场景出发,来理解消息存储设计的思路,以及未读数独立两套存储的必要性。
另外,在期末实战中,我会从“短轮询”方式调整为 WebSocket 长连接的方式,并且加上 ACK 机制、应用层心跳等特性。希望你能在两次实战中,通过对比,真正理解长连接方式相比“短轮询”方式的优势,并且通过 ACK 机制和应用层心跳,真正理解为什么它们能够解决“数据丢失”和“连接可靠性”的问题。
OK,下面我们说回本次实战。
这个聊天系统要求并不复杂,只需要构建简单的 Web 界面(没有界面通过命令行能实现也行)。我在这里写了一个简易版的 Demo,供你参考。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《即时消息技术剖析与实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(16)

  • 杨成龙
    只是基于redis吗?没用到netty吗?

    作者回复: 期中实战只是个简易版demo哈,期末的实战会基于目前轮询的方式进行长连接改造,再加上ack和心跳等功能,这样也让大家能对“消息推送实时性”的实现方案有一个比对。
    另外,实战的目的是让大家自己尝试结合课程前面的内容来练手加深对知识的理解,所以提供的demo并不具备线上可用性,只是一个简单的参考。

    2019-09-20
    2
  • 王蒙
    Embedding方式是什么意思

    作者回复: 就是嵌入到jvm里的一些中间件和db,web容器这些,随着jvm启动也能跟着一起run起来,用起来比较方便。

    2019-10-17
    1
  • Geek_71a2ee
    消息索引表:

    CREATE TABLE IM_MSG_RELATION (
    owner_uid INT NOT NULL,
    other_uid INT NOT NULL,
    mid INT NOT NULL,
    type INT NOT NULL,
    create_time TIMESTAMP NOT NULL,
    PRIMARY KEY (`owner_uid`,`mid`)
    );
    CREATE INDEX `idx_owneruid_otheruid_msgid` ON IM_MSG_RELATION(`owner_uid`,`other_uid`,`mid`);
    ownerid otherid是什么意思?张三给李四发一条消息,在这个表里存几条数据呢

    作者回复: 会存两条,咱们课程2里面有讲过的哦

    2019-09-21
    1
  • Geek_908e99
    老师我看更新未读数的逻辑并没有用到redis事务,我看到的就是下面两行,这个实现不能保证原子性吧:

    /**更未读更新 */
    redisTemplate.opsForValue().increment(recipientUid + "_T", 1); //加总未读
    redisTemplate.opsForHash().increment(recipientUid + "_C", senderUid, 1); //加会话未读

    作者回复: 嗯,这个demo没有用lua来保障原子性,所以是会存在并发更新的一致性问题,有兴趣的话可以尝试来优化改造试试哈,核心lua代码在文章中有。

    2019-11-14
  • Ricky Fung
    建议 期中 和 期末考试作业 代码可以分两个分支,这样大家看起来更直观。
    2019-10-24
  • 五点半先生
    搬运,https://github.com/coldwalker/Sample
    2019-10-20
  • Geek_defa2f
    能不能期中期末实战的代码分开部署在github上,现在才学到,发现期中的代码被期末的代码覆盖了。。。

    作者回复: 期末的代码基本没有覆盖期中的哈,都是独立的功能和实现,页面也都是独立的。

    2019-10-18
  • yic
    消息索引表:

    CREATE TABLE IM_MSG_RELATION (
    owner_uid INT NOT NULL,
    other_uid INT NOT NULL,
    mid INT NOT NULL,
    type INT NOT NULL,
    create_time TIMESTAMP NOT NULL,
    PRIMARY KEY (`owner_uid`,`mid`)
    );
    CREATE INDEX `idx_owneruid_otheruid_msgid` ON IM_MSG_RELATION(`owner_uid`,`other_uid`,`mid`);
    老师,消息索引表这么创建,请教一下群发(500人群)消息,是不是要插入500条记录? 如果是插库的话,性能能保证吗?

    作者回复: 群发消息qps很高的话就不要用mysql这种关系型数据库啦,另外也不需要发件人维度的消息存储,可以考虑采用hbase这类nosql数据库来存储索引。

    2019-10-05
    1
  • 王棕生
    感谢老师的总结和源码分享!

    作者回复: 😺 谢谢

    2019-09-25
  • YidWang
    消息 没有重复 设计
    2019-09-24
  • Geek_发现
    老师你好,我启动项目报错org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'embededRedis': Invocation of init method failed; nested exception is java.lang.RuntimeException: Can't start redis server. Check logs for details.
    我redis是启动了的,端口号也是6379,怎么回事呢?

    作者回复: 还有其他报错吗?比如“This redis server instance is already running...”或者“Failed to start Redis instance”。可以断点跟进到start方法里去看看。

    2019-09-24
    4
  • 给我点阳光就灿烂
    即使通讯的消息可不可以不存在数据库中而已消息队列的形式代替呢

    作者回复: 存消息队列的问题在于你需不需要按会话维度啥的来进行查询,分页等等。如果不需要,可以只根据uid维度来暂存消息和信令。

    2019-09-23
  • leslie
    期中考试 、、、还是等期末考试的时候一起做吧,看的懂写不来,出去的都是伪代码:忙起来就发现写这个东西自己的Coding太差了,被Coding能力拖后腿了:谁让这是DBA和OPS的通病呢、、、

    作者回复: 建议还是动手写一写,一码胜千言~

    2019-09-23
  • 飞翔
    老师 有一个问题
    对于redis 事务
            redisTemplate.multi();
            redisTemplate.opsForValue().increment(recipientUid + "_T", 1); //加总未读
            redisTemplate.opsForHash().increment(recipientUid + "_C", senderUid, 1); //加会话未读
            redisTemplate.exec();

    假设第一个加总未读失败, 事务并不会停止,而是继续进行,第二个加会话未读, 这样不也是数据就不一致了嘛, redis 事务完全和没有一样呀?

    作者回复: 是的,redis的事务只保证这些命令原子执行,执行过程中就算有命令失败,队列中的其他命令也会被执行,所以调用方需要根据返回结果来进行二次处理。

    2019-09-23
  • 飞翔

    CREATE INDEX `idx_owneruid_otheruid_msgid` ON IM_MSG_RELATION(`owner_uid`,`other_uid`,`mid`);
    老师 消息索引表中 为什么要创作(`owner_uid`,`other_uid`,`mid`); 这三个的联合索引 呀

    作者回复: 分页的时候会用到。比如:
    select msgid from IM_MSG_RELATION where owner_uid=? and other_uid=? and mid <= ? order by mid desc limit ?,?

    2019-09-22
  • 飞翔
    消息内容表:

    CREATE TABLE IM_MSG_CONTENT (
    mid INT AUTO_INCREMENT PRIMARY KEY,
    content VARCHAR(1000) NOT NULL,
    sender_id INT NOT NULL,
    recipient_id INT NOT NULL,
    msg_type INT NOT NULL,
    create_time TIMESTAMP NOT NUll
    );


    老师 消息内容表中的sender_id INT NOT NULL,
    recipient_id INT NOT NULL, 这两个字段是不是有些多余?

    作者回复: 嗯,是冗余了一下,主要是方便离线统计和后台使用,业务上可以不需要。

    2019-09-22
收起评论
16
返回
顶部