1:为什么说IM系统发送一条消息并不是简单的
“ 本文正在参加「金石计划」 ”我在网上看到很多的文章使用Netty来编写聊天室,或者是聊天相关的,但是大都是是在demo阶段,并不能使用在生产环境上使用,比如用户A给用户B发送一条消息,大都是让你使用
protected void channelRead0(ChannelHandlerContext ctx, Message msg) throws Exception {
ctx.writeAndFlush("你要发送的消息");
}
但是发送一条消息真的有这么简单吗? 在我们使用微信聊天的时候,首先会校验我们是否是好友,对方是否把我们拉黑,如果是群聊的时候,还要判断自己是否被禁言,或者群是否被禁言,这些前置的校验都是必不可少的
当我们网络不好的时候,发送一条消息会一直转圈圈,直到过了一会这个圈圈才会消失,这个就是消息确认接收的ack机制了,当你的IM系统收到这条消息,并成功保存到数据库中了,这时候IM系统会返回一个ack的确认机制给你,然后前端收到这个请求之后就把这个圈圈去掉,就代表我们这条消息已经发送成功了
还有虽然我们发送消息是使用TCP的,TCP本身是安全的,但是它只保证了传输层的安全,在应用层是不保证数据安全的,也就是有可能会导致数据的丢失,所以我们又应该如何保证聊天的消息不丢失呢
当然IM系统还有一个重要的地方,就是我们如何保证消息的顺序性,如果你发送的消息是乱序的,那就有可能造成误会了
.........
等等一系列的问题都需要我们去解决,所以别小看通讯系统简单的发送一条消息,其实需要解决的问题有很多
2:一条消息发送的大致流程
3:如何保证我们的消息已经成功发送到IM服务器了
当我们发送一条消息给对方之后,如果没发送出去就会有一个这样的圈圈,发送成功了就不会有。
消息会转圈圈就代表着对方还没有接收到这条消息,或者说这条消息还没有持久化到数据库中,这时候当服务端已经将这条消息持久化到数据库了,这时候就可以发送一个ack给客户端,告诉客户端这条消息已经发送成功了
4:我们消息发送到IM服务器失败了怎么办
网络是不稳定的,有时候我们发送一条消息给对方,但是这条消息根本没发送到IM服务器,或者说我们消息已经成功发送到IM服务器了,但是返回ack的时候,客户端没有接收到这条消息的ack,所以呢,对于发送这条消息的用户来说,这条消息就是没有发送成功的。所以这时候就会重发。但是也不能无限制的重发,要有限制次数,超过次数就要提示客户端这条消息发送失败了
5:重复发送消息如何保证幂等性
当我们发送一条消息给IM服务器。发送成功之后IM服务器会返回一个ack消息给客户端,如果这时候客户端没有接收到这个ack,那么就会重发。这时候IM服务器就会有二条一摸一样的消息了,但是消息接收方是只能接收一条消息的,所以这时候就要保证消息的幂等性了
6:如何保证消息的有序性
无论是发送消息还是接收到的消息,都应该是有顺序的,不能乱序的,不然很可能意思全变了,我们发送消息是 A,B,C,那么接收消息的一方也要是A-B-C这样的顺序。我们可以给消息设置一个序列号,这个序列号是自动递增不会重复的,比如时间戳,或者借用redis等一些工具
7:消息该如何存储,读扩散还是写扩散
读扩散: 一条消息在数据库中只存一条记录,比如用户A给用户B发送一条消息,那么这时候数据库就存一条记录即可,如下
key fromId toId body
读扩散:A发送一条消息给B 数据库就存一条记录就行 messageKey-1 A B messageBody
B发送一条消息给A 数据库就存一条记录就行 messageKey-2 B A messageBody
但是此时有个需求,就是查询用户A和用户B的聊天记录,那么SQL就要这样写: (select * from table where fromId = A and toId = B) or (select * from table where fromId = B and toId = A),也就是查询的时候变复杂了。
写扩散:
* key fromId toId ownerId body
* 写扩散:A发送一条消息给B 数据库就要存二条记录就行 messageKey-1 A B A messageBody
* messageKey-1 A B B messageBody
* messageKey-2 B A B messageBody
* messageKey-2 B A A messageBody
相对于读扩散,写扩散增加了写的压力,一条消息会在数据库存二份,甚至多份,
所以一般来说对于单聊和群聊我们的消息存储是不一样的,单聊我们完全可以使用写扩散,因为一条消息存储二份对于数据库而言压力不大,这样对于查询而言就简单了
但是对于群聊而言,如果一个群有1万个用户,那么就要存2万条记录,那么对于数据库而言写压力就大了,所以对于群聊一般而言,我们一般使用的是读扩散,查询的时候麻烦点,减少写的压力
但是写扩散真的不适合群聊嘛?也不绝对,对于钉钉那些要显示某些人已读或者未读的。那么就要用读扩散了
所以呀,一个简单的发送消息并不是简单的就是发送一条消息而已,要考虑的问题有很多