AskMe项目 异步队列

异步队列简单介绍

队列实现异步可以用单向队列,任务放到队列中,先进先出,或者使用优先队列,按照优先级来选择谁先执行,来防止某一个用户执行大量的请求,如一个用户发送了100个请求,如果用单向队列,其它用户必须要等这个用户的100个请求结束后才能执行,这就不合理,所以可以给第2个请求设置比较低的优先级,这样其他用户的请求也可以被执行

使用异步队列可以让主线程继续运行,减少请求响应时间和解耦,主要的使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。

主要编写两个类

  1. EventProducer把事件推到Redis队列中去
  2. EventConsumer把事件从队列中取出来,找到关联的handler,一件一件地去处理

其他用到了一些辅助类

  1. 枚举型的EventType,用来表示不同的Event类型
  2. EventModel来存放Event中的相关信息
  3. 接口类EventHandler,其中有两个函数,一个是执行Handler,另一个是返回需要使用这个Handler的EventType列表

具体实现

1. 枚举型EventType

public enum EventType {
    LIKE(0),
    COMMENT(1),
    LOGIN(2),
    ASK(3),
    Mail(4);

    private int value;
    EventType(int value)
    {
        this.value=value;
    }
    public int getValue()
    {
        return this.value;
    }

}

2.EventModel

public class EventModel {
    private int userid;//事件执行者,如点赞的人
    private EventType eventype;//事件类型,如点赞
    private  int entitytype;//操作对象类型,如给评论点赞,则为评论类型
    private int entityid;//操作对象ID,如评论的ID
    private int entityownerid;//对象的拥有者,如发表该评论的用户

    public int getUserid() {
        return userid;
    }

    public EventModel setUserid(int userid) {
        this.userid = userid;
        return this;
    }

    public EventType getEventype() {
        return eventype;
    }

    public EventModel setEventype(EventType eventype) {
        this.eventype = eventype;
        return this;
    }

    public int getEntitytype() {
        return entitytype;
    }

    public EventModel setEntitytype(int entitytype) {
        this.entitytype = entitytype;
        return this;
    }

    public int getEntityid() {
        return entityid;
    }

    public EventModel setEntityid(int entityid) {
        this.entityid = entityid;
        return this;
    }

    public int getEntityownerid() {
        return entityownerid;
    }

    public EventModel setEntityownerid(int entityownerid) {
        this.entityownerid = entityownerid;
        return this;
    }

    public Map<String, String> getMap() {
        return map;
    }

    public EventModel setMap(Map<String, String> map) {
        this.map = map;
        return this;
    }

    private Map<String,String> map=new HashMap<String, String>();

    public EventModel setkeyvalue(String key,String value)
    {
        map.put(key,value);
        return this;
    }
    public String getkeyvalue(String key)
    {
        return map.get(key);
    }


}

3. EventHandler

    public interface EventHandler {
    void doHandle(EventModel event);//执行Event的具体操作函数
    List<EventType> getSupportEventType();//返回该handler对那些类型的Event是关心的,即这些EventType进入队列需要运行时,需要调用该Handler
}

4. EventProducer

@Service
public class EventProducer {
    @Autowired
    JedisService jedis;
    //将EVENT加入到redis队列中
    public boolean fireEvent(EventModel event)
    {
        try
        {
            String eventstring= JSONObject.toJSONString(event);
            //将event转换为JSON字符串,取出时Parse回到EventModel类型
            String key= RedisKeyUtil.eventKey;
            jedis.lpush(key,eventstring);
            //将该event加入list中
            return true;
        }
        catch(Exception e)
        {
            return false;
        }
    }
}

5.EventConsumer

@Service
public class EventConsumer implements InitializingBean,ApplicationContextAware{
    @Autowired
    JedisService jedis;
    
    private static final Logger logger= LoggerFactory.getLogger(EventConsumer.class) ;
    
    private Map<EventType,List<EventHandler>> eventConsumerMap=new HashMap<EventType,List<EventHandler>>();
    //一种类型的EventType进来,就寻找这件Event所需要的Handler的列表
    
    private ApplicationContext applicationContext;
    //运行的上下文
    
    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String,EventHandler> beans=applicationContext.getBeansOfType(EventHandler.class);
        //找到所有的EventHandler,注意在具体实现handler的时候一定要加上@Component,否则找不到这个handler
        if(beans!=null)
        {
            for(Map.Entry<String,EventHandler> entry:beans.entrySet())
            {
                List<EventType> tmp=entry.getValue().getSupportEventType();
                for(EventType a:tmp)
                {
                    if(eventConsumerMap.containsKey(a)==false)
                    {
                        eventConsumerMap.put(a,new ArrayList<EventHandler>());
                    }
                        eventConsumerMap.get(a).add(entry.getValue());
                }
            }
        }
        //初始化Map<EventType,List<EventHandler>>将Event类型和需要用到的EventHandler关联起来

        //新建线程,该线程不断地循环,从redis的list中找event,找到了就将Json字符串parse成event,然后执行它所需要的handler
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                while(true)
                {
                    String key= RedisKeyUtil.eventKey;
                    List<String> eventlist=jedis.brpop(0,key);
                    for(String tmp:eventlist)
                    {
                        if(tmp.equals(key))
                            continue;
                        EventModel model= JSON.parseObject(tmp,EventModel.class);
                        if(!eventConsumerMap.containsKey(model.getEventype()))
                        {
                            logger.error("不能识别的事件");
                            continue;
                        }
                            for(EventHandler handler:eventConsumerMap.get(model.getEventype()))
                        {
                            handler.doHandle(model);
                        }
                    }
                }
            }
        });
        thread.start();

    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }
}

解释一下其中的brpop指令:
假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长(即第一个参数,表示等待time时间后返回nil)。 反之,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key (所以下面遍历的时候要把Key过滤掉),第二个元素是被弹出元素的值。

6.具体的Handler实现 LikeHandler

@Component
public class LikeHandler implements EventHandler {
    @Autowired
    UserService userService;
    @Autowired
    MessageService messageService;
    //给被点赞用户发送站内信
    @Override
    public void doHandle(EventModel event) {
        Message msg=new Message();
        msg.setToid(event.getEntityownerid());
        msg.setCreateddate(new Date());
        msg.setFromid(888);
        User user=userService.getuserbyid(event.getUserid());
        msg.setContent("用户"+user.getName()+"赞了您的评论 "+event.getkeyvalue("questionid"));
        msg.setHasread(0);
        msg.setConversationid(event.getEntityownerid(),888);
        messageService.insertMessage(msg);
    }

    @Override
    public List<EventType> getSupportEventType() {
        return Arrays.asList(EventType.LIKE);
    }
}

7. Controller中将任务放入异步队列中

eventProducer.fireEvent(new EventModel().setEntityid(entity_id).setEntitytype(EntityType.ENTITY_COMMENT).setUserid(user.getId()).setEventype(EventType.LIKE).setkeyvalue("questionid","http://127.0.0.1:8080/question/"+String.valueOf(comment.getEntityid())).setEntityownerid(comment.getUserid()));

总结:

通过异步队列,如果用户有一个耗时且不需要同步响应的事件时,可以将事件通过事件生产者,将事件推入异步队列中,消费者线程不断从异步队列中取出事件,来执行,适用于Message Service等应用场景

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,525评论 25 707
  • 前端开发面试题 <a name='preface'>前言</a> 只看问题点这里 看全部问题和答案点这里 本文由我...
    自you是敏感词阅读 754评论 0 3
  • 数据结构与算法 栈和队列的区别 网络基础 HTTP 无状态怎么理解 可以从REST的角度来理解这个问题。我们知道R...
    笑极阅读 661评论 1 5
  • [玫瑰]20170315营养早餐 ——林青贤经典幸福语录 《不要试图培养一个完美的孩子》: 从不完美中看到完美反而...
    觉之灯阅读 216评论 0 0
  • 这件事情在脑子里盘算了好几次,要么是被冲绳酒店丰盛的自助早餐、要么是被新加坡火树银花的夜晚,搅得不得不放弃。 但这...
    鱼骨练习本阅读 354评论 0 1