0%

前言

这里发一个案例,获取 RestController 注解的所有bean

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
* 获取有特定注解的Bean<br>
* demo
*/
@Component
public class InitRestControllerBeanCache implements ApplicationListener<ContextRefreshedEvent> {

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 根容器为Spring容器
if (event.getApplicationContext().getParent() == null) {
Map<String, Object> beans = event.getApplicationContext().getBeansWithAnnotation(RestController.class);
for (Object bean : beans.values()) {
System.err.println(bean == null ? "null" : bean.getClass().getName());
}
}
}
}

本文地址: https://github.com/maxzhao-it/blog/post/380b5f6e/

RBAC是Role Based Access Control的缩写,是基于角色的访问控制。一般都是分为用户(user),
角色(role),权限(permission)三个实体,角色(role)和权限(permission)是多对多的
关系,用户(user)和角色(role)也是多对多的关系。用户(user)和权限(permission)
之间没有直接的关系,都是通过角色作为代理,才能获取到用户(user)拥有的权限。

本文地址: https://github.com/maxzhao-it/blog/post/18acddd2/

服务器

在终端(黑框框)输入如下命令:

1
bash <(curl -sL https://raw.githubusercontent.com/hijkpw/scripts/master/centos_install_v2ray.sh)

成功后输出:

1
2
3
4
5
6
7
8
9
10
 v2ray运行状态:正在运行
v2ray配置文件:/etc/v2ray/config.json

v2ray配置信息:
IP(address): 104.156.227.241
端口(port):8888
id(uuid):d71a4507-5d29-452e-81d0-235e89a1868b
额外id(alterid): 60
加密方式(security): auto
传输协议(network): tcp

防火墙

1
2
3
4
5
firewall-cmd --permanent --add-port=8888/tcp
firewall-cmd --reload
iptables -I INPUT -p tcp --dport 23581 -j ACCEPT
# 开机启动
systemctl enable v2ray

ss -ntlp | grep v2ray 命令可以查看v2ray是否正在运行。如果输出为空,大概率是被selinux限制了,解决办法如下:

  1. 禁用selinux:setenforce 0;
  2. 重启v2ray:systemctl restart v2ray

服务器的 /etc/v2ray/config.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
"log": {
"loglevel": "info",
"access": "/var/log/v2ray/access.log",
"error": "/var/log/v2ray/error.log"
},
"inbounds": [{
"port": 8888,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "d71a4507-5d29-452e-81d0-235e89a1868b",
"level": 1,
"alterId": 60
}
]
}
}],
"outbounds": [{
"protocol": "freedom",
"settings": {}
},{
"protocol": "blackhole",
"settings": {},
"tag": "blocked"
}],
"routing": {
"rules": [
{
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "blocked"
}
]
}
}

\1. 查看v2ray配置/运行状态:bash <(curl -sL https://raw.githubusercontent.com/hijkpw/scripts/master/centos_install_v2ray.sh) info;

\2. v2ray管理命令:启动:systemctl start v2ray,停止:systemctl stop v2ray,重启:systemctl restart v2ray;

\3. 更改端口、alterid最简单的办法:重新运行一键脚本;

\4. 更新v2ray到最新版:bash <(curl -L -s https://install.direct/go.sh)

\5. 卸载v2ray: bash <(curl -sL https://raw.githubusercontent.com/hijkpw/scripts/master/centos_install_v2ray.sh) uninstall

客户端

下载地址 https://github.com/v2ray/v2ray-core/tags

1
2
wget https://install.direct/go.sh

go.sh 需要配置地址 LOCAL为对应v2ray-linux64.zip

详细配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PROXY=''
HELP=''
FORCE=''
CHECK=''
REMOVE=''
VERSION=''
VSRC_ROOT='/tmp/v2ray'
EXTRACT_ONLY=''
LOCAL='v2ray-linux-64.zip'
LOCAL_INSTALL='1'
DIST_SRC='github'
ERROR_IF_UPTODATE=''

CUR_VER=""
NEW_VER=""
VDIS=''
ZIPFILE="/tmp/v2ray/v2ray.zip"
V2RAY_RUNNING=0

安装

1
2
3
sudo bash go.sh
or
sudo ./go.sh

其它令名

1
2
3
4
5
6
systemctl enable v2ray # 设置开机自启
systemctl start v2ray # 运行v2ray
systemctl stop v2ray # 停止v2ray
# ubuntu上述启动命令无效时:
service v2ray start # 运行v2ray
service v2ray stop # 停止v2ray

查看v2ray服务运行端口:

1
2
3
sudo netstat -tunlp | grep v2ray
# 或
sudo ss -tunlp | grep v2ray

客户端 /etc/v2ray/config.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
{
"inbounds": [
{
"port": 1080,
"listen": "127.0.0.1",
"protocol": "socks",
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls"
]
}
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "104.156.227.241",
"port": 8888,
"users": [
{
"alterId": 60,
"id": "d71a4507-5d29-452e-81d0-235e89a1868b",
"level": 0,
"security": "auto"
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": ""
}
},
{
"protocol": "freedom",
"settings": {}
},
{
"protocol": "blackhole",
"settings": {},
"tag": "blocked"
}
],
"routing": {
"rules": [
{
"type": "field",
"ip": [
"geoip:private"
],
"outboundTag": "blocked"
}
]
}
}

本文地址: https://github.com/maxzhao-it/blog/post/6635/

记录自己在Linux上打包APK的过程。

环境

  • OS: Arch Linux
  • Kernel: x86_64 Linux 5.6.13-arch1-1
  • SDK: 26.0.3
  • 签名v2: apksigner\zipalign (我这里的属于SDK29.0.3,可以签名)

安装SDK

ArchLinux 下直接可以安装 sudo pacman -S android-sdk

其它发行版百度可以解决;

安装需要的 版本

1
sdkmanager "build-tools;26.0.0"

把签名工具放入环境变量中

开始打包

1
2
3
cordova platform rm android
cordova platform add android@6.4.0
gulp build:android

gulp build:android是自定义脚本

主要执行三步操作:

  • ‘cordova-build:android’ 打包

  • ‘zipalign’ 签名

  • ‘apksigner:sign’ 签名

本文地址: https://github.com/maxzhao-it/blog/post/8778/

前言

已有 Angular 项目,使用 Cordova 打包成 APP

Angular version = 11.2.10
Cordova version = 10.0.0

创建 Cordova 项目

项目名称与 Angular 项目名称相同

下载 gradle-6.5-all.zip timtout

1
Downloading https://services.gradle.org/distributions/gradle-6.5-all.zip failed: timeout

直接浏览器下载,然后放入路径

1
/home/maxzhao/.gradle/wrapper/dists/gradle-6.5-all/2oz4ud9k3tuxjg84bbf55q0tn

GRADLE_USER_HOME可以修改面上的路径。

gradle 使用 阿里云镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
}

allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
}

备用

1
2
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' }

本文地址: https://github.com/maxzhao-it/blog/post/18836/

前言

当前主要记录问题的发现过程以及解决过程,其中包含关键性的代码(也包含错误代码)

主线任务外的分析过程省略,但会展示部分的分析结果。

问题的发现

1. 使用文件服务

有一个文件服务,这个文件服务可以对接多种存储,所以我们封装了上传、下载文件的方法。

2. 调用文件服务接口

上传、下载文件是没什么问题的,问题出现在了使用 标签上。

标签的 src 指向音频(视频)文件的下载地址。

备注:如果这里不是通过下载地址返回文件流,下面的叙述就可以不用看了。

下载代码展示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FileSystemController {
@GetMapping({"/{id}"})
public ResponseEntity<org.springframework.core.io.Resource> download(@PathVariable String id) throws IOException {
org.springframework.core.io.Resource resource = this.fileServerService.downloadAttach(id);
HttpHeaders headers = new HttpHeaders();
long contentLength = 0L;
try {
headers.add("Content-Disposition", "attachment;filename=" + resource.getFilename());
contentLength = resource.contentLength();
} catch (IOException var7) {
log.error(var7.getMessage());
}
return ((BodyBuilder) ResponseEntity.ok().headers(headers)).contentLength(contentLength).contentType(MediaType.parseMediaType("application/octet-stream")).body(resource);
}
}

此时会发现,所有的音频(视频)文件流 src都是播放不了的。

如果仔细探究当前代码,会发现当前会返回”多余的结果”,由此猜测:这些”多余的结果”导致无法直接播放音频(视频),图片也是一样的道理。

3. 单独开发音频(视频)文件预览接口

第一个版本比较简陋,这里不做分析。
代码展示:第二个版本

第二个版本对音频(视频)文件做了分片处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class FileSystemController {
@GetMapping({"/{id}"})
public void download(@PathVariable String id,
HttpServletRequest request,
HttpServletResponse response) {
org.springframework.core.io.Resource resource;
try {
resource = this.fileServerService.downloadAttach(id);
} catch (RuntimeException runtimeException) {
/*文件不存在*/
log.error("当前获取的文件不存在 {} ", id);
return;
}
try {
response.setHeader("Content-Disposition", "attachment;filename=" + resource.getFilename());
String fileName = id.toLowerCase();
/*获取扩展名*/
String extName = fileName.substring(fileName.lastIndexOf(".") + 1);
/*获取 range*/
String requestRange = request.getHeader("range");
/*实际长度*/
long contentLength = resource.contentLength();
if (StringUtils.hasText(requestRange)) {
/*获取需要的长度*/
String[] rangeLimit = requestRange.substring(6).split("-");
String rangeStart = rangeLimit[0];
if (!StringUtils.hasText(rangeStart)) {
rangeStart = "0";
}
String rangeEnd = rangeLimit.length == 2 ? rangeLimit[1] : "";
if (StringUtils.hasText(rangeEnd)) {
/*存在结束*/
response.setHeader("Content-Range", "bytes " + rangeStart + "-" + rangeEnd + "/" + resource.contentLength());
contentLength = Integer.parseInt(rangeEnd) - Integer.parseInt(rangeStart) + 1;
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Last-Modified", new Date().toString());
} else {
/*不存在分片*/
response.setHeader("Content-Range", "bytes " + rangeStart + "-" + resource.contentLength() + "/" + resource.contentLength());
}
} else {
response.setHeader("Content-Range", "bytes 0-" + resource.contentLength() + "/" + resource.contentLength());
}
response.setContentLengthLong(contentLength);
if (videoExtNames.contains(extName)) {
response.setHeader("Content-Type", "video/" + extName);
response.setContentType("video/" + extName);
} else if (audioExtNames.contains(extName)) {
response.setHeader("Content-Type", "audio/" + extName);
response.setContentType("audio/" + extName);
} else {
response.setContentType("application/octet-stream");
}
BufferedInputStream bufferedInputStream = new BufferedInputStream(resource.getInputStream());
byte[] b = new byte[bufferedInputStream.available()];
bufferedInputStream.read(b);
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(b);
outputStream.flush();
outputStream.close();
} catch (Exception e) {
log.error("获取文件报错:{}", e.getMessage());
}
}
}

4. 使用第二个版本的代码

  1. 当前代码在安卓chrome等苹果外的设备测试正常,图片、音频(视频)展示正常,播放正常;
  2. 当打包IOSsafari浏览器中使用时,会出现两种现象:
    1. 视频在网络信号差时会缺少片段;
    2. 音频文件大于 50KB时,会出现异常情况,比如:全是杂音、播放出错;

5. 对苹果设备中的现象不断测试

经过长时间的测试,发现了不少现象:

  1. 本地访问不会报错(app与文件服务在同一台设备上);
  2. 音频大于 50KB时,会出现杂音、播放出错、时长不够、重复播放片段;
  3. 视频播放会缺少部分片段、时长不够;

6. 根据出现的现象,对现象进行分析

这些问题的出现,主要在app与文件服务是否在统一服务器上;

  1. 测试情况1app与文件服务部署在同一台服务器上;
  2. 测试情况2app与文件服务部署在不同服务器上;

针对这个相同服务器与不同服务器的请求进行分析,得出几种结论:

  1. 苹果外的设备会一次获取整个音频(视频)文件;
    1. header中的rangebytes=0-
  2. 苹果设备在测试情况1中,会发送两次请求获取文件:
    1. range 0-1请求当前文件是否存在。
    2. range 0-3000 请求当前文件(3000是文件大小,也就是header中的ContentLength)。
    3. 结果:音频(视频)可以正常播放;
  3. 苹果设备在测试情况2中,会发送多次请求获取文件:
    1. range 0-1请求当前文件是否存在。——请求正常返回
    2. range 0-3000 请求当前文件。——请求错误
    3. range 588-2563 分片请求当前文件。——请求正常返回
    4. range 2563-3000 分片请求当前文件。——请求正常返回
    5. 结果:会出现杂音、播放出错、时长不够、重复播放片段等现象

7. 通过现象调整代码

经过一番风雨发现:

分片操作不对,在分片时,需要根据分片的大小,读取文件不同的片段。

改动后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
public class FileSystemController {
// ****
public void download(@PathVariable String id,
HttpServletRequest request,
HttpServletResponse response) {
if (StringUtils.hasText(requestRange)) {
outputStream.write(b, (int) rangeStart, (int) contentLength);
} else {
outputStream.write(b);
}
}
}

这里会把当前苹果设备请求的分片大小,写到输出流中。

8. 针对有问题的测试情况2进行测试

苹果设备在测试情况2中,会发送多次请求获取文件:

  1. range 0-1请求当前文件是否存在。——请求正常返回
  2. range 0-3000 请求当前文件。——请求错误
  3. range 588-2563 分片请求当前文件。——请求正常返回
  4. range 2563-3000 分片请求当前文件。——请求正常返回
  5. 结果:语音(视频)播放正常。

9. 针对正常结果进行分析

  1. 对于苹果设备的分片请求,没有正确的分片,会导致苹果设备获取到错误的片段,从而导致杂音。
  2. 苹果设备有一个分片是请求全部文件,但是报错了,不影响播放。

代码中的问题

这里的问题主要体现在文件分片的逻辑错误,在苹果设备外的其它设备上,是体现不出当前分片效果的。

本文地址: https://github.com/maxzhao-it/blog/post/40242/

前言

为什么要做一个IM?这个想法是从一个聊天项目开始,公司原来的聊天项目已破败不堪,继续优化不如重构。经过一番了解,决定构建一个即时通讯,并在此基础上扩展聊天功能。

目前网络上的IM案例数不胜数,参考网络中众多的案例并结合自身业务需要,来构建属于自己的IM系统。

场景

  1. 社交
  2. 在线客服
  3. 待办提醒、工作提醒等
  4. 抽奖
  5. 红包

举个例子:

我们年会有红包抽奖活动,有999红包10个 ,888红包20个,88红包50个,8红包1000个,我们需要实时看到每种红包剩余数量。

目标

整体目标

整体项目构建目标:

  1. 即时通讯系统(IM)。
  2. 聊天系统(IM-Chat
  3. 后端管理平台
  4. 应用管理平台

技术目标

  1. 小型化
  2. 单节点万人同时在线
  3. 可水平扩展
  4. 多通讯协议
  5. 第三方登录
  6. 服务监控

在移动端,长连接是未来趋势,并且比Http请求的数据量更小(像WebSocket,连接只需要一个 headers)。

阶段性目标

一阶段

  1. 小型化
  2. 单节点万人同时在线
  3. 可水平扩展
  4. 通讯协议:WS
  5. 接入端:HTML5

二阶段

  1. 第三方登录
  2. 服务监控

三阶段

  1. 通讯协议:TCP/HTTPS
  2. Android/IOS

四阶段

  1. 界面美化

难点目标

  1. Android/IOS离线推送
  2. 多节点时服务治理

关键字

后台管理系统:指的是IM全局管理系统,包括租户管理、租户的应用管理等。

应用管理系统:指的是IM租户管理当前租户应用的系统。

应用:指的是租户创建的IM应用,IM允许跨应用之间的通信,IM-Chat则不允许跨应用添加好友。

系统功能

参考

基于Websocket的IM即时通讯技术

软件体系结构分析方法

本文地址: https://github.com/maxzhao-it/blog/post/eb63d0bf/

前言

如今高度信息化的互联网时代,生活中Instant Messaging与我们息息相关,例如微信、钉钉等以 IM 系统为核心的产品。像一些游戏、社交软件都离不开 IM

IM 从早起的 QQ、飞信等发展到现在,软件架构也不断的在迭代,从早期的CS、P2P演变到现在,后台已经成为了一个复杂的分布式系统,涉及网络通讯、移动端、安全、存储、检索等技术。

IM系统中最核心的是消息系统,消息系统的核心功能有消息的同步、存储和检索;

下面就基于社区比较火的 Timeline模型来构建消息系统(不依赖 TableStore)。

传统架构VS现代架构

主要区别就是 现代架构下,消息是先存储后同步,消息不会丢失,多端同步、消息检索都是全量消息存储带来的好处。

现在架构下最大的挑战就是消息管理和索引。

基础模型

Timeline 模型

Timeline模型是针对消息数据场景所新创的一个数据模型,它的特色在于能够满足消息数据场景对消息保序、海量消息存储、实时同步的特殊需求。

Timeline的构成主要包括:

  • Timeline ID:唯一标识Timeline的ID。
  • Timeline MetaTimeline的元数据,元数据内可包含任意键值对属性。
  • Message Sequence:消息队列,承载该Timeline下的所有消息。消息在队列里有序保存,并且根据写入顺序分配自增的ID。一个消息队列可承载的消息个数无上限,在消息队列内部可根据消息ID随机定位某条消息,并提供正序或者反序扫描。
  • Message Entry:消息体,包含消息的具体内容,可以包含任意键值对。

消息同步可以基于 Timeline实现,搭配 ack 机制,无障碍拉取各端消息。

消息存储可以基于 Timeline实现,持久化所有数据。

消息检索一般基于消息内容和消息类型来灵活检索。

消息存储模型

消息存储要求每个会话都对应一个 Timeline,消息根据会话顺序排序,然后持久化存储。

消息同步模型

消息同步模型一般有读扩散(也叫拉模式)和写扩散(也叫推模式)两种不同的方式。

  • 读扩散:消息存储模型中,每个会话的 Timeline 中保存了这个会话的全量消息。读扩散的消息同步模式下,每个会话中产生的新的消息,只需要写一次到其用于存储的 Timeline 中,接收端从这个 Timeline 中拉取新的消息。优点是消息只需要写一次,相比写扩散的模式,能够大大降低消息写入次数,特别是在群消息这种场景下。但其缺点也比较明显,接收端去同步消息的逻辑会相对复杂和低效。接收端需要对每个会话都拉取一次才能获取全部消息,读被大大的放大,并且会产生很多无效的读,因为并不是每个会话都会有新消息产生。
  1. 写扩散:写扩散的消息同步模式,需要有一个额外的 Timeline 来专门用于消息同步,通常是每个接收端都会拥有一个独立的同步 Timeline(或者叫收件箱),用于存放需要向这个接收端同步的所有消息。每个会话中的消息,会产生多次写,除了写入用于消息存储的会话 Timeline,还需要写入需要同步到的接收端的同步 Timeline。在个人与个人的会话中,消息会被额外写两次,除了写入这个会话的存储 Timeline,还需要写入参与这个会话的两个接收者的同步 Timeline。而在群这个场景下,写入会被更加的放大,如果这个群拥有 N 个参与者,那每条消息都需要额外的写 N 次。写扩散同步模式的优点是,在接收端消息同步逻辑会非常简单,只需要从其同步 Timeline 中读取一次即可,大大降低了消息同步所需的读的压力。其缺点就是消息写入会被放大,特别是针对群这种场景。

Timeline 模型不会对选择读扩散还是写扩散做约束,而是能同时支持两种模式,因为本质上两种模式的逻辑数据模型并无差别,只是消息数据是用一个 Timeline 来支持多端读还是复制到多个 Timeline 来支持多端读的问题。

IM消息系统用,通常选择写扩散,消息一般写入一次,频繁读取,典型的读多写少的场景。大大增加了读的性能,用空间换时间。但是对于万人大群,读扩散又是一个好的选择。

架构设计

如图是一个典型的消息系统架构,架构中包含几个重要组件:

  • 接入端:作为消息的发送和接收端。
  • 消息服务:一组无状态的服务器,可水平扩展,处理消息的发送和接收请求,连接后端消息系统。
  • 消息队列:新写入消息的缓冲队列,消息系统的前置消息存储,用于削峰填谷以及异步消费。
  • 消息处理:一组无状态的消费处理服务器,用于异步消费消息队列中的消息数据,处理消息的持久化和写扩散同步。
  • 消息存储和索引库:持久化存储消息,每个会话对应一个 Timeline 进行消息存储,存储的消息建立索引来实现消息检索。
  • 消息同步库:写扩散形式同步消息,每个用户的收件箱对应一个 Timeline,同步库内消息不需要永久保存,通常对消息设定一个生命周期。
    新消息会由端发出,通常消息体中会携带消息 ID(用于去重)、逻辑时间戳(用于排序)、消息类型(控制消息、图片消息或者文本消息等)、消息体等内容。消息会先写入消息队列,作为底层存储的一个临时缓冲区。消息队列中的消息会由消息处理服务器消费,可以允许乱序消费。消息处理服务器对消息先存储后同步,先写入发件箱 Timeline(存储库),后写扩散至各个接收端的收件箱(同步库)。消息数据写入存储库后,会被近实时的构建索引,索引包括文本消息的全文索引以及多字段索引(发送方、消息类型等)。

对于在线的设备,可以由消息服务器主动推送至在线设备端。对于离线设备,登录后会主动向服务端同步消息。每个设备会在本地保留有最新一条消息的顺序 ID,向服务端同步该顺序 ID 后的所有消息。

数据库选型

基于 TableStore

消息系统最核心的两个库是消息同步库和消息存储库,两个库对数据库有不同的要求:

消息同步库 消息存储库
数据模型 Timeline模型 Timeline模型
写能力 高并发写,十万级TPS 高并发写,少量读,万级TPS
读能力 高并发范围读,十万级TPS 少量范围读,千级TPS
存储规模 保存一段时间内的同步消息,TB级。保留千万级的Timeline规模。 保存全量消息,百TB级。保留亿级的Timeline规模。

总结下来,对数据库的要求有如下几点:

  1. 表结构设计能够满足Timeline模型的功能要求:不要求关系模型,能够实现队列模型,并能够支持生成自增的SeqId
  2. 能够支持高并发写和范围读,规模在十万级TPS。
  3. 能够保存海量数据,百TB级。
  4. 能够为数据定义生命周期。

阿里云表格存储(TableStore)是基于LSM存储引擎的分布式NoSQL数据库,支持百万TPS高并发读写,PB级数据存储,数据支持TTL,能够很好的满足以上需求,并且支持自增列,能够非常完美的设计和实现Timeline的物理模型。

基于传统数据库

消息同步库 消息存储库
数据模型 MongoDB MySQL
写能力 高并发写,万级TPS 高并发写,少量读,千级TPS
读能力 高并发范围读,万级TPS 少量范围读,千级TPS
存储规模 保存一段时间内的同步消息,GB级。 保存全量消息,GB级。

在写能力与读能力上,基于目前所用服务器的性能标准,在目标上与TableStore存储有巨大差距。

参考

现代IM系统中消息推送和存储架构的实现

TableStore Timeline:轻松构建千万级IM和Feed流系统

MongoDB亿级数据量的性能测试

本文地址: https://github.com/maxzhao-it/blog/post/7552f68d/