
# 使用DCS实现视频直播弹幕和社交网站评论的功能
#### 方案概述
**应用场景**
视频、直播弹幕展示和社交网站评论回复等场景，要求时效性高，互动性强，类似这样的业务对平台的系统时延有着非常高的要求。如果使用关系型数据库，会涉及到按评论时间逆排序，随着评论越来越多，排序效率越来越低，且并发频繁。
**解决方案**
使用分布式缓存服务（DCS）的Redis缓存，可以从不同的维度，对某个key-value的列表进行降序显示。例如，直播弹幕中的弹幕列表，可以采用zset有序集合结构，以时间戳为score权重参数进行排序，value可以直接存储弹幕内容。社交网站评论回复，同样也可以采用zset结构，但是由于社交网站评论和回复的内容很多，展示结构有一定的层级，同时需要持久化到本地，可以用value存储评论主键ID，评论内容存放到数据库，通过ID查询评论内容。
#### 前提条件
- 已创建DCS缓存实例，且状态为"运行中"。
- 客户端所在服务器与DCS缓存实例网络互通：
  - 客户端与Redis实例所在VPC为同一VPC 同一VPC内网络默认互通。
    
  
  - 客户端与Redis实例所在VPC为相同region下的不同VPC 如果客户端与Redis实例不在相同VPC中，可以通过建立VPC对等连接方式连通网络，具体请参考：[缓存实例是否支持跨VPC访问？](https://support.huaweicloud.com/dcs_faq/dcs-faq-0427002.html)。
    
  
  - 客户端与Redis实例所在VPC不在相同region 如果客户端服务器和Redis实例不在同一region，仅支持通过云专线打通网络，请参考[云专线](https://support.huaweicloud.com/productdesc-dc/zh-cn_topic_0032053183.html)。
    
  
  - 公网访问 客户端公网访问Redis 4.0/5.0/6.0实例时，需要开启实例公网访问开关，具体请参考[开启Redis 4.0/5.0/6.0公网访问并获取公网访问地址](https://support.huaweicloud.com/usermanual-dcs/dcs_03_0011.html)。
    
   
- 客户端所在的服务器已安装[JDK1.8](https://www.oracle.com/java/technologies/downloads/)以上版本和开发工具（本文档以安装[Eclipse](https://www.eclipse.org/downloads/)为例），下载jedis客户端（[单击此处直接下载jar包](https://jar-download.com/artifacts/redis.clients/jedis/3.5.1/source-code)）。
  本文档下载的开发工具和客户端仅为示例，您可以选择其它类型的工具和客户端。
  
 
#### 实施步骤
1. 在服务器上运行Eclipse，单击"File\>New Project"创建一个java工程，并将jedis客户端作为library引用到工程中。
2. 单击"New\>Class"创建一个VideoBulletScreenDemo.java文件。
3. 将以下示例代码复制到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实例连接地址和端口，需替换为实际获取的值
             String host = "127.0.0.1";
             int port = 6379;
      
             Jedis jedisClient = new Jedis(host,port);
      
             try {
                 // Redis实例连接密码，需替换为实际获取的值
                 String authMsg = jedisClient.auth("******");
      
                 if (!authMsg.equals("OK")){
                     System.out.println("AUTH FAILED: " + authMsg);
                 }
      
                 String key = "直播弹幕列表";
      
                 jedisClient.del(key);
      
                 // 随机生成弹幕消息
                 List<String> messageList = new ArrayList<>();
                 for (int i = 0; i < MESSAGE_NUM; i++){
                     messageList.add("message-" + UUID.randomUUID().toString());
                 }
      
                 // 随机生成消息的时间戳
                 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);
      
                 // 获取所有列表并按时间先后顺序输出
                 Set<Tuple> sortedMessageList  = jedisClient.zrangeWithScores(key, 0, -1);
                 for (Tuple message : sortedMessageList){
                     System.out.println("弹幕内容: " + message.getElement() + ", 发送时间: " + 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("弹幕内容: " + product.getElement() + ", 发送时间: " + Double.valueOf(product.getScore()).longValue());
                 }
             } catch (Exception e) {
                 e.printStackTrace();
             } finally {
                 jedisClient.quit();
                 jedisClient.close();
             }
      
         }
      
     }
     ```
     
   
   - 社交网站评论回复代码示例
     ```
     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 {
         // 评论 + 回复 总数
         static final  int COMMENT_NUM = 20;
         public static void main(String[] args) {
             // Redis实例连接地址和端口，需替换为实际获取的值
             String host = "127.0.0.1";
             int port = 6379;
             Jedis jedisClient = new Jedis(host,port);
             try {
                 // Redis实例连接密码，需替换为实际获取的值
                 String authMsg = jedisClient.auth("******");
                 if (!authMsg.equals("OK")){
                     System.out.println("AUTH FAILED: " + authMsg);
                 }
                 String key = "社交网站评论回复列表";
                 jedisClient.del(key);
                 HashMap<Integer, Comment> map = new HashMap<>();
                 // 随机生成评论回复数据对象
                 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);
                     // 随机生成回复
                     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);
                 // 获取所有列表并按时间先后顺序输出
                 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() + ", 评论时间: " + Double.valueOf(comment.getScore()).longValue());
                 }
                 System.out.println();
                 System.out.println("    最新的5条评论回复信息");
                 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() + " 回复:" + tmpComment.getParentId()  + " 评论内容:" + tmpComment.getContent() + ", 评论时间: " + Double.valueOf(comment.getScore()).longValue());
                     }else {
                         System.out.println("评论id: " + comment.getElement() + ", 评论时间: " + Double.valueOf(comment.getScore()).longValue());
                     }
                 }
             } catch (Exception e) {
                 e.printStackTrace();
             } finally {
                 jedisClient.quit();
                 jedisClient.close();
             }
         }
         /**
          * 评论数据对象
          */
         static class Comment{
             // 评论id
             private Integer id;
             // 评论内容
             private String content;
             // 评论时间
             private Long time;
             // 父评论id,针对回复评论
             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缓存实例的连接地址、端口以及连接密码配置到代码示例中。
5. 编译并运行得到结果。
 
#### 运行结果
- 视频直播弹幕代码示例运行结果如下：
  ```
  直播弹幕列表
  弹幕内容: message-07f1add5-2f85-4309-9f31-313c860b33dc, 发送时间: 1686902337377
  弹幕内容: message-2062e817-3145-4d8b-af7f-46f334c8569c, 发送时间: 1686902337394
  弹幕内容: message-ad36a0ca-e8bd-4883-a091-e12a25c00106, 发送时间: 1686902337396
  弹幕内容: message-f02f9960-bb57-49ae-b7d8-6bd6d3ad3d14, 发送时间: 1686902337412
  弹幕内容: message-5ca39948-866e-4e54-a469-f958cae843f6, 发送时间: 1686902337457
  弹幕内容: message-5cc8b4ba-da61-4d01-9625-cf2e7337ef10, 发送时间: 1686902337489
  弹幕内容: message-15378516-18ce-4da7-bd3c-35c57dd65602, 发送时间: 1686902337495
  弹幕内容: message-1b280525-53e5-4fc6-a3e7-fb8e71eef85e, 发送时间: 1686902337540
  弹幕内容: message-adf876d1-e747-414e-92a2-397fc329bd58, 发送时间: 1686902337541
  弹幕内容: message-1d8d7901-164f-4dd4-abb4-6f2345164b0e, 发送时间: 1686902337582
  弹幕内容: message-fb35b1b4-277a-48bf-b22b-80070aae8475, 发送时间: 1686902337667
  弹幕内容: message-973b1b03-bf95-44d8-ab91-0c317b2d61b3, 发送时间: 1686902337755
  弹幕内容: message-1481f883-757d-47f7-b8c0-df024d6e64a4, 发送时间: 1686902337770
  弹幕内容: message-b79292ca-2409-43fb-aaf0-e33f3b9d9c8d, 发送时间: 1686902337820
  弹幕内容: message-66b0e955-d509-4475-9ae5-12fb86cf9596, 发送时间: 1686902337844
  弹幕内容: message-12b6d15a-037a-47ee-8294-8625d202c0a0, 发送时间: 1686902337907
  弹幕内容: message-fbc06323-da2a-44b8-874b-d2cf1a737064, 发送时间: 1686902337927
  弹幕内容: message-7a0f787c-aff1-422f-9e62-4beda0cd5914, 发送时间: 1686902337977
  弹幕内容: message-8ba5e4e0-22af-4f80-90a6-35062967e0fd, 发送时间: 1686902337992
  弹幕内容: message-fa9e1169-e918-4141-9805-87edcf84c379, 发送时间: 1686902338000
  弹幕内容: message-5d17be15-ba2e-461f-aba5-65c20c21d313, 发送时间: 1686902338059
  弹幕内容: message-dcedc840-1be7-496a-b781-5b79c2091fe5, 发送时间: 1686902338067
  弹幕内容: message-9e39eb28-6629-4d4c-8970-2acdc0e81a5c, 发送时间: 1686902338102
  弹幕内容: message-030b11fe-c258-4ca2-ac82-5e6ca1eb688f, 发送时间: 1686902338211
  弹幕内容: message-93322018-a987-47ba-8093-3937dddda97d, 发送时间: 1686902338242
  弹幕内容: message-bc04a9b0-ec83-4a24-83f6-0a4f25ee8896, 发送时间: 1686902338281
  弹幕内容: message-c6dd96d0-c938-41e4-b5d8-6275fdf83050, 发送时间: 1686902338290
  弹幕内容: message-12b70173-1b86-4370-a7ea-dc0ade135422, 发送时间: 1686902338312
  弹幕内容: message-a39c2ef8-8167-4945-b60d-355db6c69005, 发送时间: 1686902338318
  弹幕内容: message-2c3bf2fb-5298-472c-958c-c4b53d734e89, 发送时间: 1686902338326
   
  最新的5条弹幕信息
  弹幕内容: message-2c3bf2fb-5298-472c-958c-c4b53d734e89, 发送时间: 1686902338326
  弹幕内容: message-a39c2ef8-8167-4945-b60d-355db6c69005, 发送时间: 1686902338318
  弹幕内容: message-12b70173-1b86-4370-a7ea-dc0ade135422, 发送时间: 1686902338312
  弹幕内容: message-c6dd96d0-c938-41e4-b5d8-6275fdf83050, 发送时间: 1686902338290
  弹幕内容: message-bc04a9b0-ec83-4a24-83f6-0a4f25ee8896, 发送时间: 1686902338281
   
  Process finished with exit code 0
  ```
  
 
- 社交网站评论回复代码示例运行结果如下：
  ```
  社交网站评论回复列表
  评论id: 1 评论父id：null, 评论时间: 1684745729506
  评论id: 2 评论父id：1, 评论时间: 1684745729567
  评论id: 3 评论父id：null, 评论时间: 1684745729630
  评论id: 4 评论父id：3, 评论时间: 1684745729692
  评论id: 5 评论父id：3, 评论时间: 1684745729755
  评论id: 6 评论父id：4, 评论时间: 1684745729819
  评论id: 7 评论父id：null, 评论时间: 1684745729879
  评论id: 8 评论父id：6, 评论时间: 1684745729942
  评论id: 9 评论父id：null, 评论时间: 1684745730006
  评论id: 10 评论父id：7, 评论时间: 1684745730069
  评论id: 11 评论父id：null, 评论时间: 1684745730132
  评论id: 12 评论父id：9, 评论时间: 1684745730194
  评论id: 13 评论父id：null, 评论时间: 1684745730256
  评论id: 14 评论父id：9, 评论时间: 1684745730320
  评论id: 15 评论父id：null, 评论时间: 1684745730382
  评论id: 16 评论父id：1, 评论时间: 1684745730444
  评论id: 17 评论父id：null, 评论时间: 1684745730508
  评论id: 18 评论父id：12, 评论时间: 1684745730570
  评论id: 19 评论父id：null, 评论时间: 1684745730631
  评论id: 20 评论父id：12, 评论时间: 1684745730694
   
  最新的5条评论回复信息
  评论id: 20 回复:12 评论内容:877ba7f1, 评论时间: 1684745730694
  评论id: 19, 评论时间: 1684745730631
  评论id: 18 回复:12 评论内容:b29f2077, 评论时间: 1684745730570
  评论id: 17, 评论时间: 1684745730508
  评论id: 16 回复:1 评论内容:9f31200e, 评论时间: 1684745730444
  ```
  
 
