華為云計(jì)算 云知識 使用分布式緩存DCS實(shí)現(xiàn)視頻直播彈幕和社交網(wǎng)站評論的功能
使用分布式緩存DCS實(shí)現(xiàn)視頻直播彈幕和社交網(wǎng)站評論的功能

方案概述

應(yīng)用場景

視頻、直播彈幕展示和社交網(wǎng)站評論回復(fù)等場景,要求時(shí)效性高,互動性強(qiáng),類似這樣的業(yè)務(wù)對平臺的系統(tǒng)時(shí)延有著非常高的要求。如果使用關(guān)系型 數(shù)據(jù)庫 ,會涉及到按評論時(shí)間逆排序,隨著評論越來越多,排序效率越來越低,且并發(fā)頻繁。

解決方案

使用分布式緩存服務(wù)(DCS)的Redis緩存,可以從不同的維度,對某個(gè)key-value的列表進(jìn)行降序顯示。例如,直播彈幕中的彈幕列表,可以采用zset有序集合結(jié)構(gòu),以時(shí)間戳為score權(quán)重參數(shù)進(jìn)行排序,value可以直接存儲彈幕內(nèi)容。社交網(wǎng)站評論回復(fù),同樣也可以采用zset結(jié)構(gòu),但是由于社交網(wǎng)站評論和回復(fù)的內(nèi)容很多,展示結(jié)構(gòu)有一定的層級,同時(shí)需要持久化到本地,可以用value存儲評論主鍵ID,評論內(nèi)容存放到數(shù)據(jù)庫,通過ID查詢評論內(nèi)容。

前提條件

  • 已創(chuàng)建DCS緩存實(shí)例,且狀態(tài)為“運(yùn)行中”。
  • 客戶端所在服務(wù)器與DCS緩存實(shí)例網(wǎng)絡(luò)互通:
    • 客戶端與Redis實(shí)例所在VPC為同一VPC

      同一VPC內(nèi)網(wǎng)絡(luò)默認(rèn)互通。

    • 客戶端與Redis實(shí)例所在VPC為相同region下的不同VPC

      如果客戶端與Redis實(shí)例不在相同VPC中,可以通過建立VPC對等連接方式連通網(wǎng)絡(luò),具體請參考:緩存實(shí)例是否支持跨VPC訪問?

    • 客戶端與Redis實(shí)例所在VPC不在相同region

      如果客戶端服務(wù)器和Redis實(shí)例不在同一region,僅支持通過 云專線 打通網(wǎng)絡(luò),請參考云專線。

    • 公網(wǎng)訪問

      客戶端公網(wǎng)訪問Redis 4.0/5.0/6.0實(shí)例時(shí),需要開啟實(shí)例公網(wǎng)訪問開關(guān),具體請參考開啟Redis 4.0/5.0/6.0公網(wǎng)訪問并獲取公網(wǎng)訪問地址

  • 客戶端所在的服務(wù)器已安裝JDK1.8以上版本和開發(fā)工具(本文檔以安裝Eclipse為例),下載jedis客戶端(單擊此處直接下載jar包)。

    本文檔下載的開發(fā)工具和客戶端僅為示例,您可以選擇其它類型的工具和客戶端。

實(shí)施步驟

  1. 在服務(wù)器上運(yùn)行Eclipse,單擊“File>New Project”創(chuàng)建一個(gè)java工程,并將jedis客戶端作為library引用到工程中。
  2. 單擊“New>Class”創(chuàng)建一個(gè)VideoBulletScreenDemo.java文件。
  3. 將以下示例代碼復(fù)制到VideoBulletScreenDemo.java文件中。

     

    • 視頻直播 彈幕代碼示例
      package org.example.task;
       
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Set;
      import java.util.UUID;
       
      import redis.clients.jedis.Jedis;
      import redis.clients.jedis.Tuple;
       
      public class VideoBulletScreenDemo {
       
          static final int MESSAGE_NUM = 30;
       
          public static void main(String[] args) {
       
              // Redis實(shí)例連接地址和端口,需替換為實(shí)際獲取的值
              String host = "127.0.0.1";
              int port = 6379;
       
              Jedis jedisClient = new Jedis(host,port);
       
              try {
                  // Redis實(shí)例連接密碼,需替換為實(shí)際獲取的值
                  String authMsg = jedisClient.auth("******");
       
                  if (!authMsg.equals("OK")){
                      System.out.println("AUTH FAILED: " + authMsg);
                  }
       
                  String key = "直播彈幕列表";
       
                  jedisClient.del(key);
       
                  // 隨機(jī)生成彈幕消息
                  List<String> messageList = new ArrayList<>();
                  for (int i = 0; i < MESSAGE_NUM; i++){
                      messageList.add("message-" + UUID.randomUUID().toString());
                  }
       
                  // 隨機(jī)生成消息的時(shí)間戳
                  for (int i = 0; i < messageList.size(); i++){
                      String message = messageList.get(i);
                      int sales = (int)(Math.random()*1000);
                      long time = System.currentTimeMillis() + sales;
                      // 插入redis的sortedSet中
                      jedisClient.zadd(key,time,message);
                  }
       
                  System.out.println("    " + key);
       
                  // 獲取所有列表并按時(shí)間先后順序輸出
                  Set<Tuple> sortedMessageList  = jedisClient.zrangeWithScores(key, 0, -1);
                  for (Tuple message : sortedMessageList){
                      System.out.println("彈幕內(nèi)容: " + message.getElement() + ", 發(fā)送時(shí)間: " + Double.valueOf(message.getScore()).longValue());
                  }
       
       
                  System.out.println();
                  System.out.println("    最新的5條彈幕信息");
       
                  Set<Tuple> sortedTopList = jedisClient.zrevrangeWithScores(key,0,4);
                  for (Tuple product : sortedTopList){
                      System.out.println("彈幕內(nèi)容: " + product.getElement() + ", 發(fā)送時(shí)間: " + Double.valueOf(product.getScore()).longValue());
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  jedisClient.quit();
                  jedisClient.close();
              }
       
          }
       
      }
       
    • 社交網(wǎng)站評論回復(fù)代碼示例
      package org.example.task;
      import java.util.ArrayList;
      import java.util.HashMap;
      import java.util.List;
      import java.util.Set;
      import java.util.UUID;
      import redis.clients.jedis.Jedis;
      import redis.clients.jedis.Tuple;
      public class SiteCommentsDemo {
          // 評論 + 回復(fù) 總數(shù)
          static final  int COMMENT_NUM = 20;
          public static void main(String[] args) {
              // Redis實(shí)例連接地址和端口,需替換為實(shí)際獲取的值
              String host = "127.0.0.1";
              int port = 6379;
      
              Jedis jedisClient = new Jedis(host,port);
              try {
                  // Redis實(shí)例連接密碼,需替換為實(shí)際獲取的值
                  String authMsg = jedisClient.auth("******");
                  if (!authMsg.equals("OK")){
                      System.out.println("AUTH FAILED: " + authMsg);
                  }
                  String key = "社交網(wǎng)站評論回復(fù)列表";
                  jedisClient.del(key);
                  HashMap<Integer, Comment> map = new HashMap<>();
                  // 隨機(jī)生成評論回復(fù)數(shù)據(jù)對象
                  List<Comment> commentList = new ArrayList<>();
                  for (int i = 0; i < COMMENT_NUM; i++){
                      Comment comment = new Comment();
                      comment.setId(i+1);
                      comment.setContent(UUID.randomUUID().toString().substring(0,8));
                      long time = System.currentTimeMillis();
                      Thread.sleep(50);
                      comment.setTime(time);
                      // 隨機(jī)生成回復(fù)
                      if (i > 0 && Math.random() < 0.5){
                          comment.setParentId((int)(Math.random()*i) + 1);
                      }
                      commentList.add(comment);
                      map.put(comment.getId(),comment);
                      // 插入redis的sortedSet中
                      jedisClient.zadd(key,time,String.valueOf(comment.getId()));
                  }
                  System.out.println("    " + key);
                  // 獲取所有列表并按時(shí)間先后順序輸出
                  Set<Tuple> sortedCommentList  = jedisClient.zrangeWithScores(key, 0, -1);
                  for (Tuple comment : sortedCommentList){
                      Integer commentId = Integer.valueOf(comment.getElement());
                      Comment tmpComment = map.get(commentId);
                      System.out.println("評論id: " + comment.getElement() + " 評論父id:" + tmpComment.getParentId() + ", 評論時(shí)間: " + Double.valueOf(comment.getScore()).longValue());
                  }
                  System.out.println();
                  System.out.println("    最新的5條評論回復(fù)信息");
                  Set<Tuple> sortedTopList = jedisClient.zrevrangeWithScores(key,0,4);
                  for (Tuple comment : sortedTopList){
                      Integer commentId = Integer.valueOf(comment.getElement());
                      Comment tmpComment = map.get(commentId);
                      if (tmpComment.getParentId() != null){
                          System.out.println("評論id: " + comment.getElement() + " 回復(fù):" + tmpComment.getParentId()  + " 評論內(nèi)容:" + tmpComment.getContent() + ", 評論時(shí)間: " + Double.valueOf(comment.getScore()).longValue());
                      }else {
                          System.out.println("評論id: " + comment.getElement() + ", 評論時(shí)間: " + Double.valueOf(comment.getScore()).longValue());
                      }
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  jedisClient.quit();
                  jedisClient.close();
              }
          }
          /**
           * 評論數(shù)據(jù)對象
           */
          static class Comment{
              // 評論id
              private Integer id;
              // 評論內(nèi)容
              private String content;
              // 評論時(shí)間
              private Long time;
              // 父評論id,針對回復(fù)評論
              private Integer parentId;
              public Integer getId() {
                  return id;
              }
              public void setId(Integer id) {
                  this.id = id;
              }
              public String getContent() {
                  return content;
              }
              public void setContent(String content) {
                  this.content = content;
              }
              public Long getTime() {
                  return time;
              }
              public void setTime(Long time) {
                  this.time = time;
              }
              public Integer getParentId() {
                  return parentId;
              }
              public void setParentId(Integer parentId) {
                  this.parentId = parentId;
              }
          }
      }
       

     

  4. 將DCS緩存實(shí)例的連接地址、端口以及連接密碼配置到代碼示例中。
  5. 編譯并運(yùn)行得到結(jié)果。

運(yùn)行結(jié)果

  • 視頻直播彈幕代碼示例運(yùn)行結(jié)果如下:
    直播彈幕列表
    彈幕內(nèi)容: message-07f1add5-2f85-4309-9f31-313c860b33dc, 發(fā)送時(shí)間: 1686902337377
    彈幕內(nèi)容: message-2062e817-3145-4d8b-af7f-46f334c8569c, 發(fā)送時(shí)間: 1686902337394
    彈幕內(nèi)容: message-ad36a0ca-e8bd-4883-a091-e12a25c00106, 發(fā)送時(shí)間: 1686902337396
    彈幕內(nèi)容: message-f02f9960-bb57-49ae-b7d8-6bd6d3ad3d14, 發(fā)送時(shí)間: 1686902337412
    彈幕內(nèi)容: message-5ca39948-866e-4e54-a469-f958cae843f6, 發(fā)送時(shí)間: 1686902337457
    彈幕內(nèi)容: message-5cc8b4ba-da61-4d01-9625-cf2e7337ef10, 發(fā)送時(shí)間: 1686902337489
    彈幕內(nèi)容: message-15378516-18ce-4da7-bd3c-35c57dd65602, 發(fā)送時(shí)間: 1686902337495
    彈幕內(nèi)容: message-1b280525-53e5-4fc6-a3e7-fb8e71eef85e, 發(fā)送時(shí)間: 1686902337540
    彈幕內(nèi)容: message-adf876d1-e747-414e-92a2-397fc329bd58, 發(fā)送時(shí)間: 1686902337541
    彈幕內(nèi)容: message-1d8d7901-164f-4dd4-abb4-6f2345164b0e, 發(fā)送時(shí)間: 1686902337582
    彈幕內(nèi)容: message-fb35b1b4-277a-48bf-b22b-80070aae8475, 發(fā)送時(shí)間: 1686902337667
    彈幕內(nèi)容: message-973b1b03-bf95-44d8-ab91-0c317b2d61b3, 發(fā)送時(shí)間: 1686902337755
    彈幕內(nèi)容: message-1481f883-757d-47f7-b8c0-df024d6e64a4, 發(fā)送時(shí)間: 1686902337770
    彈幕內(nèi)容: message-b79292ca-2409-43fb-aaf0-e33f3b9d9c8d, 發(fā)送時(shí)間: 1686902337820
    彈幕內(nèi)容: message-66b0e955-d509-4475-9ae5-12fb86cf9596, 發(fā)送時(shí)間: 1686902337844
    彈幕內(nèi)容: message-12b6d15a-037a-47ee-8294-8625d202c0a0, 發(fā)送時(shí)間: 1686902337907
    彈幕內(nèi)容: message-fbc06323-da2a-44b8-874b-d2cf1a737064, 發(fā)送時(shí)間: 1686902337927
    彈幕內(nèi)容: message-7a0f787c-aff1-422f-9e62-4beda0cd5914, 發(fā)送時(shí)間: 1686902337977
    彈幕內(nèi)容: message-8ba5e4e0-22af-4f80-90a6-35062967e0fd, 發(fā)送時(shí)間: 1686902337992
    彈幕內(nèi)容: message-fa9e1169-e918-4141-9805-87edcf84c379, 發(fā)送時(shí)間: 1686902338000
    彈幕內(nèi)容: message-5d17be15-ba2e-461f-aba5-65c20c21d313, 發(fā)送時(shí)間: 1686902338059
    彈幕內(nèi)容: message-dcedc840-1be7-496a-b781-5b79c2091fe5, 發(fā)送時(shí)間: 1686902338067
    彈幕內(nèi)容: message-9e39eb28-6629-4d4c-8970-2acdc0e81a5c, 發(fā)送時(shí)間: 1686902338102
    彈幕內(nèi)容: message-030b11fe-c258-4ca2-ac82-5e6ca1eb688f, 發(fā)送時(shí)間: 1686902338211
    彈幕內(nèi)容: message-93322018-a987-47ba-8093-3937dddda97d, 發(fā)送時(shí)間: 1686902338242
    彈幕內(nèi)容: message-bc04a9b0-ec83-4a24-83f6-0a4f25ee8896, 發(fā)送時(shí)間: 1686902338281
    彈幕內(nèi)容: message-c6dd96d0-c938-41e4-b5d8-6275fdf83050, 發(fā)送時(shí)間: 1686902338290
    彈幕內(nèi)容: message-12b70173-1b86-4370-a7ea-dc0ade135422, 發(fā)送時(shí)間: 1686902338312
    彈幕內(nèi)容: message-a39c2ef8-8167-4945-b60d-355db6c69005, 發(fā)送時(shí)間: 1686902338318
    彈幕內(nèi)容: message-2c3bf2fb-5298-472c-958c-c4b53d734e89, 發(fā)送時(shí)間: 1686902338326
     
    最新的5條彈幕信息
    彈幕內(nèi)容: message-2c3bf2fb-5298-472c-958c-c4b53d734e89, 發(fā)送時(shí)間: 1686902338326
    彈幕內(nèi)容: message-a39c2ef8-8167-4945-b60d-355db6c69005, 發(fā)送時(shí)間: 1686902338318
    彈幕內(nèi)容: message-12b70173-1b86-4370-a7ea-dc0ade135422, 發(fā)送時(shí)間: 1686902338312
    彈幕內(nèi)容: message-c6dd96d0-c938-41e4-b5d8-6275fdf83050, 發(fā)送時(shí)間: 1686902338290
    彈幕內(nèi)容: message-bc04a9b0-ec83-4a24-83f6-0a4f25ee8896, 發(fā)送時(shí)間: 1686902338281
     
    Process finished with exit code 0
     
  • 社交網(wǎng)站評論回復(fù)代碼示例運(yùn)行結(jié)果如下:
    社交網(wǎng)站評論回復(fù)列表
    評論id: 1 評論父id:null, 評論時(shí)間: 1684745729506
    評論id: 2 評論父id:1, 評論時(shí)間: 1684745729567
    評論id: 3 評論父id:null, 評論時(shí)間: 1684745729630
    評論id: 4 評論父id:3, 評論時(shí)間: 1684745729692
    評論id: 5 評論父id:3, 評論時(shí)間: 1684745729755
    評論id: 6 評論父id:4, 評論時(shí)間: 1684745729819
    評論id: 7 評論父id:null, 評論時(shí)間: 1684745729879
    評論id: 8 評論父id:6, 評論時(shí)間: 1684745729942
    評論id: 9 評論父id:null, 評論時(shí)間: 1684745730006
    評論id: 10 評論父id:7, 評論時(shí)間: 1684745730069
    評論id: 11 評論父id:null, 評論時(shí)間: 1684745730132
    評論id: 12 評論父id:9, 評論時(shí)間: 1684745730194
    評論id: 13 評論父id:null, 評論時(shí)間: 1684745730256
    評論id: 14 評論父id:9, 評論時(shí)間: 1684745730320
    評論id: 15 評論父id:null, 評論時(shí)間: 1684745730382
    評論id: 16 評論父id:1, 評論時(shí)間: 1684745730444
    評論id: 17 評論父id:null, 評論時(shí)間: 1684745730508
    評論id: 18 評論父id:12, 評論時(shí)間: 1684745730570
    評論id: 19 評論父id:null, 評論時(shí)間: 1684745730631
    評論id: 20 評論父id:12, 評論時(shí)間: 1684745730694
     
    最新的5條評論回復(fù)信息
    評論id: 20 回復(fù):12 評論內(nèi)容:877ba7f1, 評論時(shí)間: 1684745730694
    評論id: 19, 評論時(shí)間: 1684745730631
    評論id: 18 回復(fù):12 評論內(nèi)容:b29f2077, 評論時(shí)間: 1684745730570
    評論id: 17, 評論時(shí)間: 1684745730508
    評論id: 16 回復(fù):1 評論內(nèi)容:9f31200e, 評論時(shí)間: 1684745730444