前言

在后端编程时,对需要立即返回的数据我们应当立刻返回,而对于可以慢慢处理而业务复杂的我们可以选择延迟返回。这个实现使用到了异步消息队列。

异步消息队列

主要用于实现生产者-消费者模式。也就是说,这个队列应当是可以阻塞的,否者会带来大量的性能浪费。

生产者-消费者模式

实现
1.定义事件类型 – 定义Enum类 – EnumType
用于表示该事件的类型

public enum EventType {//这里列举了四种类型LIKE(0),COMMENT(1),LOGIN(2),MAIL(3);private int value;EventType(int value) {this.value = value;}public int getValue() {return value;}
}

2.定义事件的实体 – EventModel
这里说明一下entityOwnerId的必要性。举个例子,当我们给一个人点赞时,系统要给那个人(也就是entityOwnerId)发送一个站内信,通知那个人他被点赞了。当然,我们也可以把entityOwnerId包装在exts里,但因为几乎每一个事件都需要这个字段,所以这里我们开一个字段给他。

public class EventModel {//之前定义的事件类型private EventType type;//触发者的idprivate int actorId;//entityId和entityType共同组成了所触发的事件private int entityId;private int entityType;//该事件的拥有者private int entityOwnerId;//需要传输的额外信息private Map<String, String> exts = new HashMap<>();public Map<String, String> getExts() {return exts;}public EventModel() {}public EventModel(EventType type) {this.type = type;}public String getExt(String name) {return exts.get(name);}public EventModel setExt(String name, String value) {exts.put(name, value);return this;}public EventType getType() {return type;}public EventModel setType(EventType type) {this.type = type;return this;}public int getActorId() {return actorId;}public EventModel setActorId(int actorId) {this.actorId = actorId;return this;}public int getEntityId() {return entityId;}public EventModel setEntityId(int entityId) {this.entityId = entityId;return this;}public int getEntityType() {return entityType;}public EventModel setEntityType(int entityType) {this.entityType = entityType;return this;}public int getEntityOwnerId() {return entityOwnerId;}public EventModel setEntityOwnerId(int entityOwnerId) {this.entityOwnerId = entityOwnerId;return this;}
}

3.生产者的实现 – EventProducer
这里的队列我们使用Redis的阻塞双向队列list来实现。
a) 我们先用JSON把事件序列化
b) 再通过lpush把事件推进队列里
EventPr oducer

@Service
public class EventProducer {@AutowiredJedisAdapter jedisAdapter;public boolean fireEvent(EventModel eventModel) {try {String json = JSONObject.toJSONString(eventModel);String key = RedisKeyUtil.getEventQueueKey();jedisAdapter.lpush(key, json);return true;} catch (Exception e) {return false;}}
}

RedisKeyUtil – 用于统一的管理Redis的Key

public class RedisKeyUtil {private static String BIZ_EVENT = "EVENT";public static String getEventQueueKey() {return BIZ_EVENT;}
}

JedisAdapter – 对Jedis的函数进行一层封装

@Service
public class JedisAdapter implements InitializingBean {private static final Logger logger = LoggerFactory.getLogger(JedisAdapter.class);private Jedis jedis = null;private JedisPool pool = null;@Overridepublic void afterPropertiesSet() throws Exception {pool = new JedisPool("localhost", 6379);}private Jedis getJedis() {return pool.getResource();}public long lpush(String key, String value) {Jedis jedis = null;try {jedis = getJedis();return jedis.lpush(key, value);} catch (Exception e) {logger.error("发生异常" + e.getMessage());return 0;} finally {if (jedis != null) {jedis.close();}}}public List<String> brpop(int timeout, String key) {Jedis jedis = null;try {jedis = pool.getResource();return jedis.brpop(timeout, key);} catch (Exception e) {logger.error("发生异常" + e.getMessage());return null;} finally {if (jedis != null) {jedis.close();}}}
}

4.定义一个事件处理器的接口 – EventHandler

public interface EventHandler {//事件处理函数void doHandle(EventModel model);//获取该事件处理器所支持的事件类型List<EventType> getSupportEventTypes();
}

5.消费者的实现 – EventConsumer
a)创建一个类型为Map<EventType, List>的map,用于存放所有的Handler。
b)在afterPropertiesSet函数中(这个函数在sping在初始化完该Bean后会执行),我们通过applicationContext获取实现了EventHandler接口的全部Handler。
b.1)通过for循环,分门别类的把各个Handler放到map中。
b.2)启动线程去消化事件
b.2.1)该线程使用死循环让其不间断的运行。
b.2.2)用brpop把事件拉出来
b.2.3)过滤掉key之后,剩下value,把value用JSON的api转化为EventModel
b.2.4)在map中寻找是否有能处理EventModel的Handler,判断方法是看EventType是否支持。
b.2.5)过滤掉不支持的EventType之后,调用每一个支持该EventType的doHandle方法。
具体代码实现

@Service
public class EventConsumer implements InitializingBean, ApplicationContextAware {private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class);private Map<EventType, List<EventHandler>> config = new HashMap<>();private ApplicationContext applicationContext;@Autowiredprivate JedisAdapter jedisAdapter;@Overridepublic void afterPropertiesSet() throws Exception {Map<String, EventHandler> beans = applicationContext.getBeansOfType(EventHandler.class);if (beans != null) {for (Map.Entry<String, EventHandler> entry : beans.entrySet()) {List<EventType> eventTypes = entry.getValue().getSupportEventTypes();for (EventType type : eventTypes) {if (!config.containsKey(type)) {config.put(type, new ArrayList<EventHandler>());}// 注册每个事件的处理函数config.get(type).add(entry.getValue());}}}// 启动线程去消费事件Thread thread = new Thread(new Runnable() {@Overridepublic void run() {// 从队列一直消费while (true) {String key = RedisKeyUtil.getEventQueueKey();List<String> messages = jedisAdapter.brpop(0, key);// 第一个元素是队列名字for (String message : messages) {if (message.equals(key)) {continue;}EventModel eventModel = JSON.parseObject(message, EventModel.class);// 找到这个事件的处理handler列表if (!config.containsKey(eventModel.getType())) {logger.error("不能识别的事件");continue;}for (EventHandler handler : config.get(eventModel.getType())) {handler.doHandle(eventModel);}}}}});thread.start();}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}

6.写一个实现了EventHandler接口的实现

@Component
public class LikeHandler implements EventHandler {@AutowiredMessageService messageService;@AutowiredUserService userService;@Overridepublic void doHandle(EventModel model) {Message message = new Message();User user = userService.getUser(model.getActorId());message.setToId(model.getEntityOwnerId());message.setContent("用户" + user.getName() +" 赞了你的资讯,http://127.0.0.1:8080/news/"+ String.valueOf(model.getEntityId()));// SYSTEM ACCOUNTmessage.setFromId(3);message.setCreatedDate(new Date());messageService.addMessage(message);}@Overridepublic List<EventType> getSupportEventTypes() {return Arrays.asList(EventType.LIKE);}
}

7.在Controller中调用Producer的fireEvent – 用于产生一个事件

@Controller
public class LikeController {@AutowiredLikeService likeService;@AutowiredHostHolder hostHolder;@AutowiredNewsService newsService;@AutowiredEventProducer eventProducer;@RequestMapping(path = {"/like"}, method = {RequestMethod.GET, RequestMethod.POST})@ResponseBodypublic String like(@Param("newId") int newsId) {long likeCount = likeService.like(hostHolder.getUser().getId(), EntityType.ENTITY_NEWS, newsId);// 更新喜欢数News news = newsService.getById(newsId);newsService.updateLikeCount(newsId, (int) likeCount);eventProducer.fireEvent(new EventModel(EventType.LIKE).setEntityOwnerId(news.getUserId()).setActorId(hostHolder.getUser().getId()).setEntityId(newsId));return ToutiaoUtil.getJSONString(0, String.valueOf(likeCount));}
}

(精华)2020年10月7日 高并发高可用 Redis实现异步架构相关推荐

  1. (精华)2020年10月5日 高并发高可用 分层架构(微服务技术中台)

    首先先上张技术中台架构图 概念 中台概念出现之前,在信息化模式上,前端为支撑业务的应用端,后端为各个应用系统,为前端用户,如:客户.供应商.伙伴.社会,提供服务,但随着市场.用户需求.业务的多变性,底 ...

  2. 【财经期刊FM-Radio|2020年10月27日】

    [财经期刊FM-Radio|2020年10月27日] 微信公众号: 张良信息咨询服务工作室 [今日热点新闻一览↓↓] 美股创两个月最大跌幅,欧股一个月新低,中概电商股优于大盘,美债两周最大涨幅. 美国 ...

  3. 程序猿学习笔记~2020年10月26日(数据类型与条件语句)

    Java学习日记~2020年10月26日 基本数据类型 整数类型 整数类型有byte.short.int.long.在不同位数的操作系统占用不同的字节.在64位操作系统中,它们分别占1.2.4.8个字 ...

  4. 炉石传说服务器维护时间2020,炉石传说2020年10月23日秋季发布会开始时间

    炉石传说2020年10月23日秋季发布会开始时间是什么时候.今年的秋季发布会将会迎来炉石的重大消息.之前一直有有消息的炉石新模式也即将在秋季发布会公布并且还有炉石下个拓展包的消息.那么一起看看秋季发布 ...

  5. #1024#番外篇科普为什么1024是程序员日?2020年10月24日,程序员为啥都不放假?

    1.1024为啥是程序员日? 因为1GB=1024MB,1MB=1024KB 2.2020年10月24日,程序员为啥都不放假? 因为2020-1024=996 ("996"指的是早 ...

  6. python 判断该地址 文件创建时间2020年10月14日14时25分32秒 文件最后一次访问时间 文件最后一次修改时间

    #1.判断该地址 #1.文件名 #2.文件路径 #3.文件扩展名 #4.文件创建时间2020年10月14日14时25分32秒 #5.文件最后一次访问时间 #6.文件最后一次修改时间 #7.文件的大小( ...

  7. 重装战姬服务器维护,重装战姬2020年10月1日更新维护公告_重装战姬2020年10月1日更新了什么_玩游戏网...

    在重装战姬手游中2020年10月1日更新了什么呢?本次更新的的情况内容又是什么呢?不清楚的小伙伴们,接下来就让我们一起来看一下吧! 亲爱的各位团长: 我们将于以下时间,对全平台服务器实施维护,进行游戏 ...

  8. 2020年10月31日

    2020年10月31日学习记录 leetcode刷题 第380题常数时间插入.删除和获取随即元素 解题思路:哈希拉链法,建立结点存储值.链表的长度以及下一个节点指针,建立一个结构体存储头指针,利用哈希 ...

  9. 2020年10月24日雅思考试(A类)

    本人打算备战雅思,转载我同学博客(巨佬一枚),顺便试试转载功能. 原文: https://blog.csdn.net/weixin_44077955/article/details/109920630 ...

最新文章

  1. js最小化浏览器_Handtrack.js 开源:3行JS代码搞定手部动作跟踪
  2. UNICODE转多字节
  3. 判断滚动条是否到某个位置,还有滑动的方向,以此来判断什么时候阻止滚动条滚动...
  4. 使用View的getWidth(),getHeight()方法返回0的问题
  5. 洛谷P2426 删数
  6. Ubuntu设置中修改密码,提示长度太短或太简单【终极解决办法】
  7. vs2010 编译linux,VS2010 Boost编译安装
  8. django-分页自带的分页-自定义分页
  9. [BZOJ3566][SHOI2014]概率充电器
  10. Leecode刷题热题HOT100(6)——Z 字形变换
  11. Linux+c语言结构体对齐,C语言中结构体struct的对齐问题解析
  12. Python 中文文本分词(包含标点的移除)
  13. PAT之图:遍历、最短路径dijkstra
  14. c# gerber文件读取_Gerber文件查看器
  15. 8086汇编语言:8086CPU寄存器的相关介绍
  16. Vray材质——金属材质
  17. [zz]u盘做系统启动盘后容量变小的解决方法 8GU盘变成2G 或 xG变成2G
  18. gromacs ngmx_GROMACS使用教程
  19. IEMS_8_图片识别_2
  20. bugku--PHP代码审计-sha()函数比较绕过

热门文章

  1. Codeforces 1144 D
  2. 写一个登录界面连接数据库,判断用户名和密码
  3. [注]微信公众号的运营推广总结方案(持续更新)
  4. 【控制篇 / 应用】(6.0) ❀ 02. 只允许使用 QQ 和微信 (下) ❀ FortiGate 防火墙
  5. c语言编程自幂数,【C语言基础】-自幂数优化-这个算法快得像一道闪电
  6. 广东惠州港口吞吐量稳中有升
  7. AE如何制作星云粒子特效
  8. docker镜像指定安装源_详解如何修改docker pull镜像源
  9. C# 编写的 64位操作系统 - MOOS
  10. 利用python画分形图_「分形」python简单的分形图片 - seo实验室