1. 程式人生 > 資料庫 >Redis實戰(4)-資料結構List實戰之佇列特性實現訊息多執行緒 廣播通知

Redis實戰(4)-資料結構List實戰之佇列特性實現訊息多執行緒 廣播通知

概述:本系列博文所涉及的相關內容來源於debug親自錄製的實戰課程:快取中介軟體Redis技術入門與應用場景實戰(SpringBoot2.x + 搶紅包系統設計與實戰),感興趣的小夥伴可以點選自行前往學習(畢竟以視訊的形式來掌握技術 會更快!)     原文連結:Redis實戰(4)-資料結構List實戰之佇列特性實現訊息多執行緒 廣播通知  ,文章所屬技術專欄:快取中介軟體Redis技術入門與實戰

摘要:電商平臺的管理後端一般有兩大角色的使用者可以使用,一個是系統管理員,一個是平臺的賣家/商家,對於商家而言,管理自個兒的商品是日常工作中再為普通不過的事情了,而對於系統管理員而言,有時候需要釋出一些活動公告通知商家進行報名參加,本文我們將基於List的佇列特性實現公告訊息的廣播通知功能!

內容:在上篇文章中我們介紹了Redis的資料結構~列表List,簡單介紹了其基本特性及其在電商應用後端管理平臺下如何實現“商家”新增商品時的有序儲存,以及如何以有序列表的形式進行展示!(文章連結:Redis實戰(3)-資料結構List實戰一之商品資訊的有序儲存

在其中,我們給大家展示了列表List在儲存和獲取資料時的流程圖,不曉得大夥兒還記不記得,如下圖所示:

從該圖中可以看出,當我們往Redis的列表List中新增資料時,資料的流動是具有“先進先出”的特性,即所謂的“FIFO”(有點佇列Queue的特性!)的,而且資料是緊湊、一個挨著一個儲存的!

即當我們在往快取Redis的列表List新增資料時,可以採用“LPush 即從左邊的方向新增”的方式往快取Redis的List中新增,然後再採用“LPop 即從左邊的方向彈出資料”或者“RPop 即從右邊的方向彈出資料”的方式獲取這一有序儲存的列表資料!

知道了列表List的資料儲存和讀取流程,其實我們也就幾乎知曉了在實際的專案實戰開發中的程式碼實現了。

下面我們以“電商應用~平臺管理員在平臺釋出活動公告資訊之後,除了將公告資訊塞入資料庫DB之外,同時以LPush的方式將其塞入快取Redis的列表List中,並在介面的另一端開啟定時檢測的方式,隨時檢測快取中指定的列表Redis是否有通告資訊過來,如果有,則採取RPop的方式彈出該公告資訊,並以郵件的形式傳送給商戶!”,如下圖所示:

下面,我們就進入程式碼實戰環節吧!

(1)首先,當然是需要來個“通告資訊表”啦,其完整的DDL(即資料定義語言)如下所示:

CREATE TABLE `notice` (
  `id` int(11) NOT NULL AUTO_INCREMENT,`title` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '通告標題',`content` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '內容',`is_active` tinyint(4) DEFAULT '1',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8 COMMENT='通告';

(2)然後,當然是需要開發一個Controller啦(上文我們已經開發過了)!在該Controller中我們需要開設一個請求方法,給平臺管理員新增“通告資訊”,該請求方法在接收到公告資訊之後需要將其塞入資料庫DB中,同時也需要往快取Redis的列表List中LPush一條公告資訊,準備被監聽檢測!其完整的原始碼如下所示:  

    //平臺傳送通知給到各位商戶
    @RequestMapping(value = "/notice/put",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public BaseResponse putNotice(@RequestBody @Validated Notice notice,BindingResult result){
        String checkRes=ValidatorUtil.checkResult(result);
        if (StrUtil.isNotBlank(checkRes)){
            return new BaseResponse(StatusCode.Fail.getCode(),checkRes);
        }
        BaseResponse response=new BaseResponse(StatusCode.Success);
        try {
            log.info("--平臺傳送通知給到各位商戶:{}",notice);

            listService.pushNotice(notice);
        }catch (Exception e){
            response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
        }
        return response;
    }

(3)緊接著,我們需要開發Controller對應的Service,其職責當然是用來處理真正的業務邏輯,即“新增完成通告資訊”時,它負責將該商品資訊新增進DB資料庫,並新增進快取Redis的列表List中,其完整的原始碼如下所示:  

    @Autowired
    private NoticeMapper noticeMapper;

    //建立通告
    @Transactional(rollbackFor = Exception.class)
    public void pushNotice(Notice notice) throws Exception{
        if (notice!=null){
            notice.setId(null);
            //TODO:將通告資訊塞入資料庫DB中
            noticeMapper.insertSelective(notice);
            final Integer id=notice.getId();

            if (id>0){
                //TODO:塞入List列表中(佇列),準備被拉取非同步通知至不同的商戶的郵箱 - applicationEvent&Listener;Rabbitmq;jms
                ListOperations<String,Notice> listOperations=redisTemplate.opsForList();
                listOperations.leftPush(Constant.RedisListNoticeKey,notice);
            }
        }
    }

(4)之後,我們需要建立一個“定時任務排程器”,用於“近實時”的檢測快取Redis中的列表List是否有通知公告資訊,如果有,則將其RPop取出來,然後採取多執行緒的形式將其傳送給“平臺的商家”,讓他們趕緊報名參加相關的活動!其完整的原始碼如下所示:  

/**
 * Redis列表-佇列的消費者監聽器
 * @Author:debug (SteadyJack)
 * @Link: weixin-> debug0868 qq-> 1948831260
 * @Date: 2019/10/30 14:51
 **/
@Component
@EnableScheduling
public class ListListenerScheduler {

    private static final Logger log= 
    LoggerFactory.getLogger(ListListenerScheduler.class);

    private static final String listenKey= Constant.RedisListNoticeKey;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private EmailService emailService;

    //TODO:近實時的定時任務檢測
    //@Scheduled(cron = "0/10 * * * * ?")
    @Scheduled(cron = "0/59 * * * * ?")
    public void schedulerListenNotice(){
        log.info("----定時任務排程佇列監聽、檢測通告訊息,監聽list中的資料");

        ListOperations<String,Notice> listOperations=redisTemplate.opsForList();
        Notice notice=listOperations.rightPop(listenKey);
        while (notice!=null){
            //TODO:傳送給到所有的商戶的郵箱
            this.noticeUser(notice);

            notice=listOperations.rightPop(listenKey);
        }
    }

    //TODO:傳送通知給到不同的商戶
    @Async("threadPoolTaskExecutor")
    private void noticeUser(Notice notice){
        if (notice!=null){
            //TODO:查詢獲取所有商戶資訊
            List<User> list=userMapper.selectList();

            //TODO:執行緒池/多執行緒觸發群發郵件
            try {
                if (list!=null && !list.isEmpty()){
                    ExecutorService executorService=Executors.newFixedThreadPool(4);
                    List<NoticeThread> threads= Lists.newLinkedList();

                    list.forEach(user -> {
                        threads.add(new NoticeThread(user,notice,emailService));
                    });

                    executorService.invokeAll(threads);
                }
            }catch (Exception e){
                log.error("近實時的定時任務檢測-傳送通知給到不同的商戶-法二-執行緒池/多執行緒觸發-發生異常:",e.fillInStackTrace());
            }
        }
    }
}

(5)至此,我們的程式碼實戰就完畢了,最後我們就基於Postman進入測試環節吧,幾張圖加以概括吧:

最後,上一下郵箱看看吧,可以發現確實收到了郵件:

好了,本篇文章我們就介紹到這裡了,建議各位小夥伴一定要照著文章提供的樣例程式碼擼一擼,只有擼過才能知道這玩意是咋用的,否則就成了“空談者”!

對Redis相關技術棧以及實際應用場景實戰感興趣的小夥伴可以前往Debug搭建的技術社群的課程中心進行學習觀看:http://www.fightjava.com/web/index/course/detail/12 !

其他相關的技術,感興趣的小夥伴可以關注底部debug的技術公眾號,或者加debug的微信(debug0868),拉你進“微信版”的真正技術交流群!一起學習、共同成長!

補充:

1、本文涉及到的相關的原始碼可以到此地址,check出來進行檢視學習:https://gitee.com/steadyjack/SpringBootRedis

2、目前debug已將本文所涉及的內容整理錄製成視訊教程,感興趣的小夥伴可以前往觀看學習:http://www.fightjava.com/web/index/course/detail/12

3、關注一下debug的技術微信公眾號,最新的技術文章、課程以及技術專欄將會第一時間在公眾號釋出哦!