使用表格存储开发用户弹幕功能
目标
- 使用表格存储(TableStore,原称OTS)实现视频直播的弹幕功能,通过TableStore存储弹幕,并在TableStore中检索最新弹幕实时显示到直播页面中。
准备工作
- TableStore主页和控制台等
- 注意事项:预计耗费的费用
TableStore是一个按量计费的服务,根据存储量、读写吞吐量、外网流量等付费。具体的价格通过TableStore价格总览这个页面来获取。
TableStore目前每月赠送免费额度,包含1000万按量读、1000万按量写以及10GB存储。
考虑到TableStore的免费额度,在搭建视频网站过程中,除外网下行流量的费用外,将不会产生其他相关费用,因此课程中产生的总费用极低。
步骤
1. 创建TableStore实例。
打开TableStore控制台。点击右上角创建实例。
创建完成后可以看到我们刚刚创建的实例。
点击实例,可以进入实例详情页。
在实例详情页,我们可以查看该实例的访问地址,公网访问地址用于从公网访问,会收取外网下行流量费用,私网访问地址用于在阿里云的ECS*问,通过阿里云内网线路,不收取流量费用。
TableStore提供RESTful API接口,用户通过TableStore提供的各语言SDK进行数据操作以及表操作,在使用SDK时需要指定实例访问地址、AccessKeyId、AccessKeySecret、实例名。
2. 创建数据表。
在实例详情页,我们可以创建数据表(通过SDK也可以建表),如下图所示。
我们创建了名为danmaku的一张表,用于存储弹幕。预留的读写吞吐量全部设置为0,即不预留,完全采用按量计费的模式(预留会按照预留吞吐量计费)。
创建数据表时,设置表的主键非常重要。TableStore中的一行由主键列和属性列构成,主键列最多可以有4列,他们按照顺序构成一个主键,主键唯一的决定一行,不会有重复,TableStore会按照主键的大小对行进行排序。属性列目前最多可以有1024列,每行的属性列列数和列名都可以不同,而每行的主键列的列数和列名都是相同的。因此TableStore是半结构化类型的数据库。
这里我们设置三列主键分别为VideoID,Timestamp,UserID,分别代表视频的ID,弹幕发送时的服务端时间,弹幕发送者的ID。由于TableStore会按照主键排序,因此同一个VideoID下,Timestamp是有序的,可以使用TableStore的范围读取功能从某个Timestamp开始获取更新的弹幕。
加入UserID作为第三列主键,可以保证主键的唯一性。我们使用毫秒单位的Timestamp,假如同一视频在同一毫秒有两条弹幕写入,这两条弹幕的主键会重复,其中一条会覆盖另一条。有很多方法可以保证主键的唯一性,我们采用的是加入UserID作为第三列主键的方式。
弹幕的内容我们保存在属性列中,属性列还可以保存很多其他信息。TableStore是半结构化类型的数据库,因此可以动态的增加属性列,非常灵活。
3. 使用TableStore PHP SDK进行数据读写。
- 整个过程如下图所示,网页端在发送或获取弹幕时先访问php服务端,由php服务端调用TableStore的php sdk进行数据读写。
我们使用了两个接口,PutRow和GetRange,结合我们建表时设计的主键,数据读写的示意图如下:
写入一条弹幕:VideoID、UserID、Text由网页端提供,Timestamp使用服务端当前时间,调用PutRow接口写入。
获取最新弹幕:网页端提供VideoID,并提供一个startTimestamp,表示从该Timestamp开始读取,调用GetRange接口,设置起始主键为(VideoID, startTimestamp, ""),结束主键为(VideoID,服务端当前时间,"")。
好,下面进入具体的代码部分,我们在阿里云Code上查看代码,我们需要打开“demo/DanmakuDataModel.class.php”这个文件。
- 准备配置,包含endpoint(实例访问地址)、instanceName(实例名)、AccessKeyId和AccessKeySecret。
在上图中的位置设置相关配置。
- 根据配置构造otsClient,通过otsClient调用OTS的API。
代码如下:
public static function init() {
self::$otsClient = new OTSClient(array(
'EndPoint' => OTS_END_POINT,
'AccessKeyID' => OTS_ACCESS_KEY_ID,
'AccessKeySecret' => OTS_ACCESS_KEY_SECRET,
'InstanceName' => OTS_INSTANCE_NAME
));
}
- 将弹幕写入TableStore
我们通过TableStore的PutRow接口,将一条弹幕写入TableStore的数据表中。
在使用PutRow接口前,强烈建议用户阅读我们的API参考手册。
上面我们建表时设置的主键列分别为VideoID、Timestamp、UserID,我们设置对应的主键值构造出"primary_key", 然后将其他的一些信息存在属性列中,即构造attribute_columns。
代码如下所示:
public static function put($danmaku) {
$request = array(
'table_name' => self::$tableName,
'condition' => 'IGNORE',
'primary_key' => array(
self::$pk1 => $danmaku["VideoID"],
self::$pk2 => $danmaku["Timestamp"],
self::$pk3 => $danmaku["UserID"]
),
'attribute_columns' => array(
'text' => $danmaku["text"]
)
);
$response = self::$otsClient->putRow($request);
return true;
}
注意到$request中还有一个参数为condition,这个是设置行的存在性条件,我们在此设置为IGNORE,即不关心该行在写入前是否存在。
- 通过TableStore读取弹幕
读取弹幕时我们使用TableStore的GetRange接口,该接口设置一个起始主键和一个结束主键,读取该范围内的行。
同样,我们首先阅读API参考手册,对GetRange接口有一个了解。
前端的视频页面保存最后一条弹幕的Timestamp,获取弹幕时将该时间戳传给PHP端,希望获取该时间戳后的最新弹幕。PHP端根据该时间戳构造起始主键,根据当前时间构造结束主键,然后调用GetRange接口进行查找。
由于起始主键和结束主键中的数据可能非常多,一次GetRange调用可能无法取得全部数据,当遇到这种情况,GetRange会返回next_start_primary_key,表示要扫描的下一个主键的值,可以将这个next_start_primary_key设置为起始主键进行下一次查询。
代码如下:
public static function get($conditions) {
$startPk = array(
self::$pk1 => $conditions["VideoID"],
self::$pk2 => $conditions["StartTime"],
self::$pk3 => ""
);
$endPk = array(
self::$pk1 => $conditions["VideoID"],
self::$pk2 => (int)(microtime(true) * 1000),
self::$pk3 => ""
);
$request = array(
'table_name' => self::$tableName,
'direction' => 'FORWARD',
'inclusive_start_primary_key' => $startPk,
'exclusive_end_primary_key' => $endPk,
'limit' => self::$getRangeLimit
);
$list = array();
while (true) {
$response = self::$otsClient->getRange($request);
foreach ($response["rows"] as $item) {
$list[] = array(
'VideoID' => $item["primary_key_columns"]["VideoID"],
'Timestamp' => $item["primary_key_columns"]["Timestamp"],
'UserID' => $item["primary_key_columns"]["UserID"],
'text' => $item["attribute_columns"]["text"]
);
}
if (count($list) >= self::$getDanmakuLimit) {
break;
}
if ($response["next_start_primary_key"] == NULL) {
break;
} else {
$requset["inclusive_start_primary_key"] = $response["next_start_primary_key"];
}
}
return $list;
}
4.与前端视频页面进行交互。
- 弹幕效果: