Redis的使用

发布于 2021-11-10  664 次阅读


基于内存保存消息

最近开发基于Bio的Socket项目的时候,想把简单的聊天室往消息队列的方向靠拢,因此在考虑怎么记录每个客户端所发送的消息(即实现聊天记录的保存功能)

客户端发送消息--- 服务器端接收 转发 存储----目标客户端

服务器接收转发我使用的是Read线程转发即可,那么存储呢?一开始的想法是,要不存储在内存中,即创建HashMap<senderName,msgList>来保存

具体如下:

<橙汁,List<"消息一",“消息二”...>>

<小阮,List<"消息一","消息二"...>>

代码如下:

这样的实现好处在于:简单,明了。客户端将消息发送给服务器,服务器做转发,同时存入msgMemoryMap(全局变量);由于我的网络通信模型是使用的Bio,服务器循环监听,每来一个客户端就创建一个对应的读线程,而这些步骤也是在读线程里面完成的

也就是说 客户端A的消息存入 和客户端B的消息存入不是同一个线程实现的(当然已经使用了线程池进行优化,此处是有隐患的,如果客户端太多那么服务器就要创建很多线程),因为效率上不用太过担心

更高效的序列化

在存入msgMemoryMap之前,我将原来的Serialize序列化方式改为了Protostuff序列化,因为存入消息实在太占用内存了...

如果使用Serialize序列化,平均每条消息要占用300个字节(300B)这还得期望聊天用户发送的消息都是短消息,如果长消息那更恐怖,我计算了一下,如果同时有1000个用户在聊天,每人发送1条,也就是1*1000*300 = 300Kb!这个内存占用实在是太可怕了,因此我不得不考虑更高效的序列化方式,即Protostuff,关于Protostuff的文章会在之后写出,目前只需要知道它的序列化更高效且生成的byte数组大小更小,差不多是Serialize生成的十分之一,那么接下来直接看测试代码:

 

这样仿佛解决了写入的消息占用内存过大的问题?其实还可以进一步优化,比如再优化MessageRedis类的字段,或者先压缩再存入msgMemoryMap,等需要拿出来使用的时候再解压,也就是“时间换空间”的想法,听上去好像使用msgMemoryMap只要解决了内存占用问题就好了,其实并不然,因为内存具有掉电即失的特性。

任何实际开发的项目都不可能将数据简单的写在内存,必须要进行持久化,不然你的用户使用你的聊天室向其他人发送重要的资料和文件,等到后面他需要取查看这些消息的时候,却发现居然全丢了。持久化,欸,会做啊,我写入到数据库去不就不会丢失了嘛。

写入数据库有两种策略:

1.每来一条消息,服务器做转发后,将其写入到数据库;

2.每来一条消息,服务器将其写入到数据库后,再做转发;

我们来分析一下这两种策略,假设是 用户 橙汁 发送给 小阮的一条消息 ;

          策略1 :

           服务器先转发,那挺好,小阮可以立刻收到橙汁所发送的消息,然后橙汁的这条消息写入数据库,完美保存;橙汁第二条消息来的时候重复这个逻辑,因为橙汁发送消息中间是有间隔的,也就是不可能一直不停的发(假设消息有意义),那么这个间隔时间足够橙汁将第一条消息写入数据库了,到此,消息既转发了又保存了,服务器完成一次操作的时间为(T转发 + T存入)。

           似乎万事大吉?其实不然,现在都追求高可用,高并发,高可靠,我们的系统也不能落下。很显然目前的策略没有满足高并发和高可用,因为如果在服务器收到消息并转发后,断电了怎么办?消息并没有被写入数据库,如果小阮收到了消息,比如是 “明天一起去吃饭吧” 然后橙汁把这事忘了,第二天小阮来算账,说 “你昨天说的今天一起吃饭啊”;橙汁说:“噢 是吗? 我看看聊天记录”,结果消息记录居然真的没有!橙汁确实说了这句话,也就是说我们的系统会出现很多很多比这更复杂的问题,那么怎么解决呢?

                 策略2:

                  服务器先写入,再转发,如果写入失败则重试(或者其他策略),直到成功后再转发;这样的话,如果小阮收到了这条消息,一定和数据库中 的是一样的;同样的,如果写入数据库之后断电了,消息没被转发怎么办?只需要标记消息是否转发成功,如果没有的话就重新转发,可以从数据库去获取,不怕数据丢了,当然这里设计还可以详细展开。

分析了两种策略,如果是更追求消息一致性的话,优先选择策略2,当然策略2只是一个简版(悄咪咪的告诉你,这两个策略借鉴了Redis和Mysql的设计思路)

在上述策略的基础上继续思考,写入数据库是可以实现,但是不可忽略数据库的压力

数据库OS:对啊,你想的倒是好,每个线程都写数据库,如果采用策略2还需要从我身上拉取信息,线程压力小了,我数据库的压力大了喂,快找点人替我分担

 

是的,为了缓解数据库的压力,同时又可以完成我们的持久化功能,同时操作起来还快

缓存中间件:你他妈直接报我身份证得了

 

因此,我们可以采用Redis 来实现我们的需求---在转发消息的同时持久化保存消息

一样的:

1.每来一条消息,服务器做转发后,将其写入到Redis;

2.每来一条消息,服务器将其写入到Redis后,再做转发;

            策略1:服务器转发后,写入到Redis,Redis操作起来更快,对其上述写入数据库的策略1来说,就是T保存(保存需要的时间)更短了。如果转发完成后,断电了,没有写入到Redis,这里也会有问题,先按下不表

            策略2:先写入Redis,再转发,因为T保存的时间更短,写入Redis后转发失败的可能性就更小了,同样的,可以在接收消息的客户端设置一个接收反馈,接收到了反馈,没接收到的话这样服务器也知道,可以重发;

Redis的持久化功能保证了即使Redis所在的服务器机器掉电了,也不会丢失太多数据,丢失的数量取决了我们所采用的Redis持久化策略,比如是使用AOF还是使用RDB,其参数设置,以及是否做了Redis集群等等。

好啦,先介绍到这里啦~


她喜欢所以就做咯