一次简单的redis记录聊天群7天消息实现
最近做了个小功能,群聊天,包含发,收,改消息等基本功能
(预估下成熟的群组聊天系统, 群组量大,群组内成员多,消息发送多,消息发送完后通知每个成员量特别大.成员读取消息,同一个消息被多次读取,读取量也会很大.我的想法,热消息也是内存存储方式会比较好,历史消息归档)
没有考虑消息量比较大的场景,这个后续如果有机会设计,再补,这里就是记录下这种非常简单的群消息的实现方式
方式: 简单地从两个维度,群组 和时间 (一天N片), 一个分片用redis的list来存储,效率很高,查询从最新的片开始查,逐步往后翻,时间,群组id固定可以计算出固定的redis key.
图:
代码:
/**
* 使用redis的list结构按照时间每天4个key来保存数据
* @param groupOrderIMDtoList
* @return
*/
@Override
public Response<String> createGroupOrderIM(List<GroupOrderIMDto> groupOrderIMDtoList) {
for (GroupOrderIMDto groupOrderIMDto: groupOrderIMDtoList){
try {
Calendar cal = Calendar.getInstance();
String imKey = this.generateRedisKey(getPreviousDateHourOfMonth(0,cal),groupOrderIMDto.getGroupUUid());
groupOrderIMDto.setCreateAt(System.currentTimeMillis());
groupOrderIMDto.setCreateOrderType(groupOrderIMDto.getCreateOrderUserType());
factory.getExpireListService().setExpireList(imKey, Lists.newArrayList(groupOrderIMDto), redisKeyExpireTime);
boolean needPush = factory.getLockService().setEx(generateRedisKeyTodayLockSymbol(cal,groupOrderIMDto.getGroupUUid()),"pushed",redisKeyExpireTime);
if (needPush){
threadPoolExecutor.submit(new PushOper(groupOrderIMDto.getGroupUUid(),"createGroupOrderIM"));
}
}catch (Exception e){
log.error("createGroupOrderIM exception: ",e.getStackTrace());
}
}
return Response.createSuccess("success");
}
/**
* 更新消息状态,下个迭代实现
* @param groupOrderIMDto
* @return
*/
@Override
public Response<String> updateGroupOrderIM(GroupOrderIMDto groupOrderIMDto) {
try{
//todo
}catch (Exception e){
log.error(e.getMessage(),e.getStackTrace());
}
return Response.createSuccess("success");
}
/**
* 分页查询群消息,如果有给出上一条消息,查出上一条消息往前的pageSize条。
* @param groupOrderListDto
* @return
*/
@Override
public Response<PageResult<GroupOrderIMDto>> listGroupOrderIM(GroupOrderListDto groupOrderListDto) {
log.info("in param is : " + JSONObject.toJSONString(groupOrderListDto));
PageResult<GroupOrderIMDto> pageResult = new PageResult();
List<GroupOrderIMDto> ret = Lists.newArrayList();
try{
//标识是否已经知道入参里的消息,如果入参你消息orderUUid为空,则为true
boolean isSame = true;
if (groupOrderListDto.getOrderUUid() != null && groupOrderListDto.getOrderUUid().length()>0){
isSame = false;
}
Calendar cal = Calendar.getInstance();
int hour = 0;
int jumpNum = (groupOrderListDto.getCurrPage()-1) * groupOrderListDto.getPageSize();//根据分页判断可以在list跳过的消息条数
while(true){
String imKey = this.generateRedisKey(getPreviousDateHourOfMonth(hour,cal), groupOrderListDto.getGroupUUid());
Long size = factory.getExpireListService().getListSize(imKey);
hour = hour-spliteHour;
if (size == null || size <= 0) {
if (hour < (redisKeyExpireDay*(-1)* 24-spliteHour)){
//获取不到数据,且已经遍历了前7天的key,则推出循环
break;
}
//获取不到数据,还未遍历了前7天的key,则继续下一次循环
continue;
}
if (jumpNum >= size){
jumpNum -= size;
continue;
}
int loopSize = 10;
/*
* 下面的逻辑是从reids获取数据,如果有不是第一页,会跳过 jumpNum条数来取数据,一直取到pageSize 条数据,或者取到最后,返回
*/
for (long i = size-jumpNum-1;i>=0; i=i-loopSize ){
List<GroupOrderIMDto> list =
factory.getExpireListService().getListValue(
imKey,(i-loopSize+1)>0?(i-loopSize+1):0,!isSame?i+1:i,GroupOrderIMDto.class);
log.info("get from redis is :{} , key is {}, isSame value is {} ", JSONObject.toJSONString(list),imKey,isSame);
if (list != null && list.size()>0){
for (int j=list.size()-1;j>=0;j--){
//GroupOrderIMDto groupOrderIMDto1 = list.get(j); 这里特别奇怪
if (!isSame){
if (list.get(j).getOrderUUid().equals(groupOrderListDto.getOrderUUid())){
isSame = true;
}
}else{
ret.add(list.get(j));
}
if (ret.size()>=groupOrderListDto.getPageSize()){
pageResult.setHasMore(true);
List<GroupOrderIMDto> ret2 = Lists.newArrayList();
for (int k = ret.size()-1;k>=0;k--){
ret2.add(ret.get(k));
}
pageResult.setList(ret2);
return Response.createSuccess(pageResult);
}
}
}
}
jumpNum = 0;
}
List<GroupOrderIMDto> ret2 = Lists.newArrayList();
for (int k = ret.size()-1;k>=0;k--){
ret2.add(ret.get(k));
}
pageResult.setList(ret2);
pageResult.setHasMore(true);
return Response.createSuccess(pageResult);
}catch (Exception e){
log.error("listGroupOrderIM exception: ",e.getMessage(),e.getStackTrace());
}
return Response.createError("get error");
}
//private static final String PREFIX = "API:PICKRIDECOMM:";
//private static final String dayRedisKey = RedisGroupKeyEnum.GROUP_ROUTE_IM_LIST.getKey() ;
private static final Integer redisKeyExpireDay = 7;
private static final Integer redisKeyExpireTime = redisKeyExpireDay*24*60*60 ;//second
private static final int spliteHour = 6;
/**
* 根据入参拼接rediskey
* @param i
* @param groupUuid
* @return
*/
private String generateRedisKey(String i,String groupUuid) {
return String.format(RedisGroupKeyEnum.GROUP_ROUTE_IM_LIST.getKey(),i,groupUuid);
}
/**
* 计算返回当前在一月中的天数 和 小时/6 拼接的字符串
* @param i
* @param cal
* @return
*/
private String getPreviousDateHourOfMonth(int i,Calendar cal) {
//Calendar cal = Calendar.getInstance();
if (i < 0) {
cal.add(Calendar.HOUR, i);
}
return cal.get(Calendar.DAY_OF_MONTH)+":"+cal.get(Calendar.HOUR_OF_DAY)/spliteHour;
}
private String generateRedisKeyTodayLockSymbol(Calendar cal,String groupUuid) {
return String.format(RedisGroupKeyEnum.GROUP_ROUTE_IM_LIST.getKey(),cal.get(Calendar.DAY_OF_MONTH),groupUuid)+":PUSHSYMBOL";
}