javascript
微服务Spring Boot 整合 Redis 实现好友关注 – Feed流实现推送到粉丝收件箱
文章目錄
- ?引言
- 一、Redis 實(shí)現(xiàn)好友關(guān)注 -- Feed流實(shí)現(xiàn)推送到粉絲收件箱
- ?Feed 流實(shí)現(xiàn)方案
- ?推送到粉絲收件箱
- 三、Redis 實(shí)現(xiàn)好友關(guān)注 -- 實(shí)現(xiàn)分頁滾動(dòng)查詢 實(shí)時(shí)獲取信息
- ?小結(jié)
?引言
本博文參考 黑馬 程序員B站 Redis課程系列
在點(diǎn)評(píng)項(xiàng)目中,有這樣的需求,如何 Redis 實(shí)現(xiàn)好友關(guān)注 – Feed流實(shí)現(xiàn)推送到粉絲收件箱 功能?
采用 Feed流實(shí)現(xiàn)推送到粉絲收件箱
Redis 如此強(qiáng)大!
一、Redis 實(shí)現(xiàn)好友關(guān)注 – Feed流實(shí)現(xiàn)推送到粉絲收件箱
?Feed 流實(shí)現(xiàn)方案
假設(shè)我們關(guān)注了用戶,這個(gè)用戶發(fā)布了動(dòng)態(tài),那么就應(yīng)該把當(dāng)前用戶發(fā)布的動(dòng)態(tài)推送給他的粉絲,如何實(shí)現(xiàn)呢,這個(gè)我們把它叫做Feed流,關(guān)注推送也叫做 Feed流 ,直譯為投喂,為用戶持續(xù)的提供 ”沉浸式“ 的體驗(yàn),通過五險(xiǎn)下拉刷新獲取新的信息。
傳統(tǒng)模式是我們需要用戶去通過搜索引擎或者是其它的方式去解鎖想要看的內(nèi)容
新型的Feed流: 不需要用戶再去推送消息,而是系統(tǒng)分析用戶到底想要什么,然后直接把內(nèi)容推送給用戶,從而使用戶能夠更加的節(jié)約時(shí)間,不用主動(dòng)尋找。
Feed流主要分為兩種實(shí)現(xiàn)方式:
TimeLine: 不做內(nèi)容篩選,簡(jiǎn)單的按照內(nèi)容發(fā)布時(shí)間排序,常用于好友或關(guān)注。例如朋友圈
- 優(yōu)點(diǎn):信息全面,不會(huì)有缺失。并且實(shí)現(xiàn)也相對(duì)簡(jiǎn)單
- 缺點(diǎn):信息噪音較多,用戶不一定感興趣,內(nèi)容獲取效率低
智能排序:利用智能算法屏蔽掉違規(guī)的、用戶不感興趣的內(nèi)容。推送用戶感興趣信息來吸引用戶
- 優(yōu)點(diǎn):投喂用戶感興趣信息,用戶粘度很高,容易沉迷
- 缺點(diǎn):如果算法不精準(zhǔn),可能起到反作用
本次實(shí)現(xiàn)的功能中,是基于好友 來做Feed流,因此采用 TimeLine 模式。 該模式的實(shí)現(xiàn)方案分為三種
- 拉模式
- 推模式
- 推拉結(jié)合
拉模式:也叫做讀擴(kuò)散
該模式的核心含義就是:當(dāng)張三和李四和王五發(fā)了消息后,都會(huì)保存在自己的郵箱中,假設(shè)趙六要讀取信息,那么他會(huì)從讀取他自己的收件箱,此時(shí)系統(tǒng)會(huì)從他關(guān)注的人群中,把他關(guān)注人的信息全部都進(jìn)行拉取,然后在進(jìn)行排序
優(yōu)點(diǎn):比較節(jié)省空間,因?yàn)橛脩粼谧x取信息時(shí),并沒有重復(fù)讀取,而是讀取完后可以把他的收件箱進(jìn)行清除
缺點(diǎn):比較延遲,當(dāng)用戶讀取數(shù)據(jù)時(shí)才去關(guān)注的用戶的人里面讀取數(shù)據(jù),假設(shè)用戶關(guān)注了大量的用戶,那么此時(shí)就會(huì)拉取海量的數(shù)據(jù)內(nèi)容,對(duì)服務(wù)器壓力比較大。
推模式:也叫做寫擴(kuò)散
推模式是沒有寫郵箱的,當(dāng)張三寫了一個(gè)內(nèi)容,此時(shí)會(huì)主動(dòng)的把張三寫的內(nèi)容發(fā)送到他的粉絲收件箱中去,假設(shè)此時(shí)李四再來讀取,就不用再去臨時(shí)拉取了
優(yōu)點(diǎn):時(shí)效快,不用臨時(shí)拉取
缺點(diǎn):內(nèi)存壓力大,假設(shè)一個(gè)大V寫信息,很多人關(guān)注他,就會(huì)寫很多的數(shù)據(jù)到粉絲收件箱,加大服務(wù)器壓力。
推拉結(jié)合模式:也叫做讀寫混合,兼具推和拉兩種模式的優(yōu)點(diǎn)
推拉模式是一個(gè)折中的方案,站在發(fā)件人這一段,如果是個(gè)普通的人,那么我們采用寫擴(kuò)散的方式,直接把數(shù)據(jù)寫入到他的粉絲中去,因?yàn)槠胀ǖ娜怂姆劢z關(guān)注量比較小,所以這樣做沒有壓力,如果是大V,那么他是直接將數(shù)據(jù)先寫入到一份到發(fā)件箱里邊去,然后再直接寫一份到活躍粉絲收件箱里邊去,現(xiàn)在站在收件人這端來看,如果是活躍粉絲,那么大V和普通的人發(fā)的都會(huì)直接寫入到自己收件箱里邊來,而如果是普通的粉絲,由于他們上線不是很頻繁,所以等他們上線時(shí),再從發(fā)件箱里邊去拉信息。
?推送到粉絲收件箱
需求:
- 修改新增探店筆記的業(yè)務(wù),在保存blog到數(shù)據(jù)庫的同時(shí),推送到粉絲的收件箱
- 收件箱滿足可以根據(jù)時(shí)間戳排序,必須用Redis的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)
核心:當(dāng)我們保存完筆記后,獲取當(dāng)前用戶的粉絲,然后把數(shù)據(jù)推送到粉絲的Redis中。
@Override public Result saveBlog(Blog blog) {// 1.獲取登錄用戶UserDTO user = UserHolder.getUser();blog.setUserId(user.getId());// 2.保存探店筆記boolean isSuccess = save(blog);if(!isSuccess){return Result.fail("新增筆記失敗!");}// 3.查詢筆記作者的所有粉絲 select * from tb_follow where follow_user_id = ?List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();// 4.推送筆記id給所有粉絲for (Follow follow : follows) {// 4.1.獲取粉絲idLong userId = follow.getUserId();// 4.2.推送String key = FEED_KEY + userId;stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());}// 5.返回idreturn Result.ok(blog.getId()); }進(jìn)行測(cè)試
發(fā)布筆記
查看粉絲收件箱
三、Redis 實(shí)現(xiàn)好友關(guān)注 – 實(shí)現(xiàn)分頁滾動(dòng)查詢 實(shí)時(shí)獲取信息
滾動(dòng)分頁查詢思路分析
Feed流中的數(shù)據(jù)會(huì)不斷更新,所以數(shù)據(jù)的角標(biāo)也在變化,因此不能采用傳統(tǒng)的分頁模式。
傳統(tǒng)了分頁在feed流是不適用的,因?yàn)槲覀兊臄?shù)據(jù)會(huì)隨時(shí)發(fā)生變化
假設(shè)在t1 時(shí)刻,我們?nèi)プx取第一頁,此時(shí)page = 1 ,size = 5 ,那么我們拿到的就是10~6 這幾條記錄,假設(shè)現(xiàn)在t2時(shí)候又發(fā)布了一條記錄,此時(shí)t3 時(shí)刻,我們來讀取第二頁,讀取第二頁傳入的參數(shù)是page=2 ,size=5 ,那么此時(shí)讀取到的第二頁實(shí)際上是從6 開始,然后是6~2 ,那么我們就讀取到了重復(fù)的數(shù)據(jù),所以feed流的分頁,不能采用原始方案來做。
Feed流的滾動(dòng)分頁
我們需要記錄每次操作的最后一條,然后從這個(gè)位置開始去讀取數(shù)據(jù)
舉個(gè)例子:我們從t1時(shí)刻開始,拿第一頁數(shù)據(jù),拿到了10~6,然后記錄下當(dāng)前最后一次拿取的記錄,就是6,t2時(shí)刻發(fā)布了新的記錄,此時(shí)這個(gè)11放到最頂上,但是不會(huì)影響我們之前記錄的6,此時(shí)t3時(shí)刻來拿第二頁,第二頁這個(gè)時(shí)候拿數(shù)據(jù),還是從6后一點(diǎn)的5去拿,就拿到了5-1的記錄。我們這個(gè)地方可以采用sortedSet來做,可以進(jìn)行范圍查詢,并且還可以記錄當(dāng)前獲取數(shù)據(jù)時(shí)間戳最小值,就可以實(shí)現(xiàn)滾動(dòng)分頁了
實(shí)現(xiàn)滾動(dòng)分頁查詢
需求:
- 查詢收件箱數(shù)據(jù)時(shí),可以實(shí)現(xiàn)滾動(dòng)分頁查詢
- 在個(gè)人主頁的 ”關(guān)注“ 卡片中,查詢并展示推送的Blog信息
具體操作
1、每次查詢完成后,我們要分析出查詢數(shù)據(jù)的最小時(shí)間戳,這個(gè)值會(huì)作為下一次的查詢條件
2、 我們需要找到與上一次查詢相同的查詢個(gè)數(shù)作為偏移量,下次查詢后,跳過這些查詢過的數(shù)據(jù),拿到需要的數(shù)據(jù)
核心: 我們的請(qǐng)求參數(shù)中 就需要攜帶 lastId,上一次查詢的 最小時(shí)間戳 和 偏移量 這兩個(gè)參數(shù)
這兩個(gè)參數(shù)第一次由前端來指定,以后的查詢就根據(jù)后臺(tái)結(jié)果作為條件,再次傳遞給后臺(tái)。
核心源碼
一、定義出 具體的 返回實(shí)體類
@Data public class ScrollResult {private List<?> list;private Long minTime;private Integer offset; }BlogController
RequestParam 表示接受url地址欄傳參的注解,當(dāng)方法上參數(shù)的名稱和url地址欄不相同時(shí),可以通過RequestParam 來進(jìn)行指定
@GetMapping("/of/follow") public Result queryBlogOfFollow(@RequestParam("lastId") Long max, @RequestParam(value = "offset", defaultValue = "0")Integer offset) {return blogService.queryBlogOfFollow(max, offset); }BlogServiceImpl
@Override public Result queryBlogOfFollow(Long max, Integer offset) {// 1.獲取當(dāng)前用戶Long userId = UserHolder.getUser().getId();// 2.查詢收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset countString key = RedisConstants.FEED_KEY + userId;Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, max, offset, 2);// 3.非空判斷if (typedTuples == null || typedTuples.isEmpty()) {return Result.ok();}// 4.解析數(shù)據(jù):blogId、minTime(時(shí)間戳)、offsetList<Long> ids = new ArrayList<>(typedTuples.size());long minTime = 0; // 2int os = 1; // 2for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2// 4.1.獲取idids.add(Long.valueOf(tuple.getValue()));// 4.2.獲取分?jǐn)?shù)(時(shí)間戳)long time = tuple.getScore().longValue();if(time == minTime){os++;}else{minTime = time;os = 1;}}os = minTime == max ? os : os + offset;// 5.根據(jù)id查詢blogString idStr = StrUtil.join(",", ids);List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();for (Blog blog : blogs) {// 5.1.查詢blog有關(guān)的用戶queryBlogUser(blog);// 5.2.查詢blog是否被點(diǎn)贊isBlogLiked(blog);}// 6.封裝并返回ScrollResult r = new ScrollResult();r.setList(blogs);r.setOffset(os);r.setMinTime(minTime);return Result.ok(r); }結(jié)果測(cè)試
?小結(jié)
以上就是【Bug 終結(jié)者】對(duì) 微服務(wù)Spring Boot 整合 Redis 實(shí)現(xiàn)好友關(guān)注 – Feed流實(shí)現(xiàn)推送到粉絲收件箱 的簡(jiǎn)單介紹,微服務(wù)Spring Boot 整合 Redis 實(shí)現(xiàn)好友關(guān)注 – Feed流實(shí)現(xiàn)推送到粉絲收件箱功能也是 利用Set集合、ZSet集合實(shí)現(xiàn)這樣一個(gè)需求,同時(shí),采用Redis來實(shí)現(xiàn)更加的快速,減少系統(tǒng)的消耗,更加快速的實(shí)現(xiàn)數(shù)據(jù)展示!
如果這篇【文章】有幫助到你,希望可以給【Bug 終結(jié)者】點(diǎn)個(gè)贊👍,創(chuàng)作不易,如果有對(duì)【后端技術(shù)】、【前端領(lǐng)域】感興趣的小可愛,也歡迎關(guān)注?????? 【Bug 終結(jié)者】??????,我將會(huì)給你帶來巨大的【收獲與驚喜】💝💝💝!
總結(jié)
以上是生活随笔為你收集整理的微服务Spring Boot 整合 Redis 实现好友关注 – Feed流实现推送到粉丝收件箱的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos7升级内核重启问题:you
- 下一篇: BOOST的JSON解析库Boost.J