Categories
technology|技术 中文

以数据库为中心进行架构设计

互联网项目,除了数据库组建,还经常会用到Cache(如:redis or memcache),MQ(如:kafka or RocketMQ)。无论有多少个组件,一定要以数据库为中心,提供数据访问。所有的Cache数据,都是数据库内数据的拷贝。

核心就两条:

  • 1.以数据库的数据为准
  • 2.如果数据跟数据库不一样,参考第一条

切忌:有时候写数据库,有时候写Cache,有的以db为准,有的读Cache为准。后果是:会经常性出现数据不一致。

程序读取数据的规则:

  • 1.先从Cache读取,如果存在,直接返回;如果不存在,就从db中读取,并写入Cache。
  • 2.如果数据更新,通常是直接更新数据库,然后让缓存失效(也可以直接更新Cache)
  • 3.极少数业务需求(例如:秒杀活动)时间短,并发量极大,可以直接读写Cache,然后等活动结束再写回db。

内部项目的数据分享策略:通过从库+视图+权限控制,提供有限的数据访问权限。从库是独立于主业务之外的从库,只提供内部其它项目使用,所以不会影响主业务的性能;用视图可以控制哪些字段可以被访问,也方便对数据进行过滤和排序。

对外提供数据的策略:通过Rest API+权限控制提供数据访问。外部应用通过参数请求API获取需要的数据,加上白名单机制,以及鉴权规则来对访问进行记录和监控。

如果担心数据库的性能问题,请参考:

支撑10亿用户的技术方案(思路篇)

支撑10亿用户的技术方案(细节篇)

Categories
technology|技术 中文

程序员的进阶思维

这篇文章写给有1年以上工作经验的开发人员。

  • 检验每一个输入参数的合法性
  • 考虑逻辑判断的边界条件
  • 对代码进行合理的封装,提高代码的复用性
  • 站在用户的角度考虑问题

对于游戏产品中出现的兑换码,用户输入兑换码可以领取奖励。 为了方便用户的识别,兑换码中,我们需要去掉哪些容易混淆的字符,如:数字1和字母I, 数字9和字母g。

  • 对于脚本代码,重复执行也不会出现问题

在线上运行时间足够久,我们可能会遇到需要批量修复线上数据的需求。通常我们需要写脚本来进行处理,这个时候一定要考虑到这样的问题:脚本执行可能会中断,或者运维不消息多次执行这个脚本。

  • 学会异常情况:异常情况可考虑加入日志跟踪

对于上线运营的产品,对于异常情况进行汇总分析&重要异常的报警,可以让开发团队先于客服团队发现问题,避免问题上报到高层领导并由高层施压处理问题。高层频繁收到问题的汇报,是研发团队失信的开始。

  • 站在运维部署的角度思考问题

举个最简单的例子:数据库的配置需要放在运维控制的配置文件里,生产环境的数据库配置信息是不能放在代码的配置文件中。

要想成为一名优秀的后端开发工程师,必须是要熟悉运维的,从熟悉linux系统开始,学会用户权限、磁盘管理、网络配置、防火墙、端口等基础知识,然后在单台服务器上下载、编译、按照各种工具,搭建数据库、缓存、web server等基础设置,最后还需要懂得如何搭建集群、监控、备份、安全等。

  • 对新老版本的兼容性

例如:对于某个API,需要调整它的参数格式。这个时候通常是新加一个API,前端的老版本还是访问老的API,新版本访问新的API,等到确认所有的用户都更新到老版本,才可以把老的API下线。

  • 安全性

API中只返回必要的信息,非必要信息不必返回,更不能向他人暴露用户的隐私信息。

  • 对于代码执行结果的线上跟踪方案

笔者在实际工作中遇到了一个需求:原来的用户表的主键是username,其它用户相关的信息都是用username进行关联的。后来发现用户有修改username的需求,这样就需要加入数字uid,并对关联的表数据用id进行重新关联。

相关的执行步骤如下:

1.给每个老用户生成唯一的数字uid,创建新用户的时候自动生成uid(生产环境验证:所有的老用户都加入了uid,所有的新用户创建的时候都会加上新的uid,而且所有的uid都是唯一的)

2.在db的关联记录中都加入加入uid,cache里面用uid关联生成原来一模一样的数据,这样既可以用username,也可以用uid获取数据(生产环境验证:所有的新老信息都有uid关联)

3.代码中逐步切换成用uid获取数据,如果uid获取不到,就用username获取(生产环境加入日志,如果用uid无法获取到信息,就记录日志,直到稳定运行一段时间,无任何相关的日志产生)

4.修改外部依赖于username获取数据的系统

5.确认所有的代码都是用uid关联获取数据之后,方可失效username关联的数据。

Categories
management|管理 technology|技术 中文

代码的分支管理、CICD以及版本定义

分支用途详解

  1. dev 分支是开发用的
  2. qa分支是测试用的
  3. uat是的在发布之前给内部人员(运营人员,或者老板们)使用的
  4. staging是用于压测的环境,上线前的最后验证
  5. master是用于生产环境部署的,是稳定的版本;部署的时候可以打tag,如:V0.1
  6. 还有两个目录: feature, bugfix。 feature是用于新功能开发,bugfix是修复bug的分支。

开发的流程描述

  1. 从dev拉取代码分支进行开发,开发完以后要merge request到dev,这个时候,会进行代码的code view
  2. dev是开发的前后端联调用的,开发者自己测试没问题了之后,就可以由前后端的lead merge到qa,qa人员就可以基于qa分支测试(转测)
  3. qa测试如果发现问题了,提交bug单给开发人员,开发人员在自己的feature分支开发修改(或者是创建bugfix分支修改),最后还是要回到第1步的流程;如果qa测试没问题,就继续
  4. 把qa测试成功的代码merge到uat供内部体验(可以省略本步骤)。如uat测试有问题,则反馈到产品,产品完善需求再从头开始开发流程。
  5. 把uat验证过的代码merge到staging,可以进行压力测试,也可以作为production环境上线的预演。
  6. 如果staging环境预演成功,就可以把代码merge到master进行上线了。这时候可以基于master分支打tag,如:V0.1.2

CICD

CICD的意思是:持续集成,持续部署。 一般来最好做到自动部署:dev代码有更新,自动部署到dev环境;qa有更新,自动部署到qa;uat和stagingg都是如此;prodcution环境需要devops手动部署。

版本定义的规则

可以在开发之前加一下产品需求的定义和版本的规划,在打tag的时候,就是对应的版本号了

产品版本号的管理建议:

1.一个完整的版本定义为V1.0,前面阶段性的版本用V0.1, V0.2这样的;

2. 线上版本的bug修复版本定义为: V0..1, V0..2 这样的。

3.版本号跟jira上的版本定义一致,跟发布版本的tag一致

Categories
technology|技术 中文

支撑10亿用户的技术方案(实战篇)

项目简述

  • 总体需求:实现一个类Twitter的网站(仅实现网页版)
  • 功能列表:
  1. 帐号系统 (account)
  2. 个人资料页 (profile)
  3. 设置 (settings)
  4. 帮助 (help)
  5. 主页时间线 (home page, a.k.a., home timeline)
  6. 帖子 (post)
  7. 关注者,正在关注 (followers & following)
  8. 搜索 (search)
  9. 趋势 – 热门标签 (trending – popular hashtags)
  10. 热门用户 (popular users)
  11. 标签页 (搜索页的一种) – hashtags
  12. 通知 – notification

团队人员配置:

产品:1人,设计:1人,前端:1人,后端:2人

技术架构

参考文章: 支撑10亿用户的技术方案(思路篇)

Twitter架构图

开发实现

  • 开发节奏

1. 产品需求细化

对每个功能模块做具体的描述,画出产品的线框图

2. 前后端基础框架选择和制定标准的通讯接口

前后端都有成熟的框架可用,根据团队人员的经验进行选择即可。标准的通讯接口通常包括:1)正常代码和错误代码;2)返回的数据结构; 3)通讯安全策略

3. 数据库结构设计

根据需求文档定义数据库结构。在这个产品需求里,很容易整理出来,需要的数据表如下:user, post, reply, like, user_relationship。更详细的表结构定义在后面。

4. 数据接口(API)设计

有了线框图,就有了页面,根据页面元素,就可以整理出需要的数据,API就能定义出来了。

5. 前端页面实现(根据API,用模拟数据)

6. 后端API实现(常规实现,不包括高并发的方案)

7. 前后端联调

8. 后端支持分库分表,应对高并发

9. 部署方案

10. 功能测试 & 安全测试

11. 高并发测试

  • 时间评估

第一周:完成1和2

第二周:完成3和4

第三周~第五周:完成5、6、7

第六~第七周:完成8和9

第八~第十周:完成10和11,通过测试后就可以准备上线了

补充说明:时间的评估,要根据团队成员的经验和能力来,以上评估可以作为参考。由于产品有完全可参考的对象,无需原创;如果再机上团队成员能力很强,并且有丰富的高并发处理经验,以上评估是合理的。

数据库表结构设计

Mysql Database table structures


— Table structure for table tb_user

CREATE TABLE tb_user (
id bigint unsigned NOT NULL AUTO_INCREMENT,
username varchar(15) COLLATE utf8mb4_general_ci NOT NULL,
email varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
password varchar(64) COLLATE utf8mb4_general_ci NOT NULL,
display_name varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
banner_url varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
avatar_url varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
phone varchar(32) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
description text COLLATE utf8mb4_general_ci NOT NULL,
website varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
language varchar(8) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
time_zone varchar(8) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
location varchar(16) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
verified tinyint unsigned NOT NULL DEFAULT ‘0’,
suspended tinyint unsigned NOT NULL DEFAULT ‘0’,
dob_day tinyint unsigned NOT NULL DEFAULT ‘0’,
is_visible_dob_day tinyint unsigned NOT NULL DEFAULT ‘0’,
dob_month tinyint unsigned NOT NULL DEFAULT ‘0’,
is_visible_dob_month tinyint unsigned NOT NULL DEFAULT ‘0’,
dob_year smallint unsigned NOT NULL DEFAULT ‘0’,
is_visible_dob_year tinyint unsigned NOT NULL DEFAULT ‘0’,
activate_code varchar(6) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
is_activated tinyint unsigned NOT NULL DEFAULT ‘0’,
created_at bigint NOT NULL DEFAULT ‘0’,
updated_at bigint NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id),
UNIQUE KEY uiq_username (username),
UNIQUE KEY uiq_email (email)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_user_extended

CREATE TABLE tb_user_extended (
user_id bigint unsigned NOT NULL,
count_followers int unsigned NOT NULL DEFAULT ‘0’,
count_followings int unsigned NOT NULL DEFAULT ‘0’,
count_posts int unsigned NOT NULL DEFAULT ‘0’,
count_likes int unsigned NOT NULL DEFAULT ‘0’,
pinned_post_id bigint unsigned NOT NULL DEFAULT ‘0’,
last_login bigint NOT NULL DEFAULT ‘0’,
timeline_updated_at bigint NOT NULL DEFAULT ‘0’,
created_at bigint NOT NULL DEFAULT ‘0’,
updated_at bigint NOT NULL DEFAULT ‘0’,
PRIMARY KEY (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_user_reset

CREATE TABLE tb_user_reset (
id bigint unsigned NOT NULL AUTO_INCREMENT,
user_id bigint unsigned NOT NULL,
username varchar(16) COLLATE utf8mb4_general_ci NOT NULL,
email varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
reset_code varchar(64) COLLATE utf8mb4_general_ci NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id),
KEY idx_email (email),
KEY idx_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_reserved_username

CREATE TABLE tb_reserved_username (
id bigint unsigned NOT NULL AUTO_INCREMENT,
reserved_username varchar(16) COLLATE utf8mb4_general_ci NOT NULL,
notes text COLLATE utf8mb4_general_ci NOT NULL,
created_by int unsigned NOT NULL,
created_at bigint unsigned NOT NULL,
updated_at bigint unsigned NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY reserved_username (reserved_username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_post

CREATE TABLE tb_post (
id bigint unsigned NOT NULL AUTO_INCREMENT,
user_id bigint unsigned NOT NULL,
post_type tinyint unsigned NOT NULL COMMENT ‘0: original post; 1: repost; 2: quoted repost; 3: comment (or reply)’,
original_type tinyint DEFAULT ‘0’,
original_id bigint unsigned NOT NULL,
content_text varchar(300) COLLATE utf8mb4_general_ci NOT NULL COMMENT ‘Text content; Max length 280’,
image_url varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
video_url varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
hashtags text COLLATE utf8mb4_general_ci NOT NULL,
location varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
place varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
source tinyint unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘The source label of this post, e.g., iPhone, Android, Web, Default 0’,
reply_settings tinyint unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘Shows you who can reply to a given Tweet. Fields returned are “everyone”, “mentioned_users”, and “followers”. default 0 “reply_settings”: “everyone” ‘,
is_deleted tinyint unsigned NOT NULL DEFAULT ‘0’,
created_at bigint NOT NULL DEFAULT ‘0’,
updated_at bigint NOT NULL DEFAULT ‘0’,
deleted_at bigint NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_post_extended

CREATE TABLE tb_post_extended (
post_id bigint unsigned NOT NULL,
user_id bigint unsigned NOT NULL,
count_comments int unsigned NOT NULL DEFAULT ‘0’,
count_likes int unsigned NOT NULL DEFAULT ‘0’,
count_reposts bigint NOT NULL DEFAULT ‘0’,
count_quote int unsigned NOT NULL DEFAULT ‘0’,
count_shares int unsigned NOT NULL DEFAULT ‘0’,
count_reported bigint NOT NULL DEFAULT ‘0’,
is_deleted tinyint unsigned NOT NULL DEFAULT ‘0’,
created_at bigint NOT NULL DEFAULT ‘0’,
updated_at bigint NOT NULL DEFAULT ‘0’,
deleted_at bigint NOT NULL DEFAULT ‘0’ ,
PRIMARY KEY (post_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_post_like

CREATE TABLE tb_post_like (
id bigint unsigned NOT NULL AUTO_INCREMENT,
content_type tinyint DEFAULT ‘0’ COMMENT ‘0 post, 1 reply’,
record_id bigint unsigned NOT NULL,
liker_id bigint unsigned NOT NULL,
is_like tinyint unsigned NOT NULL DEFAULT ‘0’,
created_at bigint unsigned NOT NULL DEFAULT ‘0’,
updated_at bigint unsigned NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_post_reply

CREATE TABLE tb_post_reply (
id bigint unsigned NOT NULL AUTO_INCREMENT,
user_id bigint unsigned NOT NULL,
post_id bigint unsigned NOT NULL,
content_text varchar(300) COLLATE utf8mb4_general_ci NOT NULL COMMENT ‘Text content; Max length 280’,
image_url varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
video_url varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
hashtags text COLLATE utf8mb4_general_ci NOT NULL,
location varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
place varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ”,
source tinyint unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘The source label of this post, e.g., iPhone, Android, Web, Default 0’,
is_deleted tinyint unsigned NOT NULL DEFAULT ‘0’,
created_at bigint NOT NULL DEFAULT ‘0’,
deleted_at bigint NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_post_reply_extended

CREATE TABLE tb_post_reply_extended (
post_reply_id bigint unsigned NOT NULL,
user_id bigint unsigned NOT NULL,
post_id bigint unsigned NOT NULL,
count_comments int unsigned NOT NULL DEFAULT ‘0’,
count_likes int unsigned NOT NULL DEFAULT ‘0’,
count_reposts bigint NOT NULL DEFAULT ‘0’,
count_quote int unsigned NOT NULL DEFAULT ‘0’,
count_shares int unsigned NOT NULL DEFAULT ‘0’,
count_reported bigint NOT NULL DEFAULT ‘0’,
is_deleted tinyint unsigned NOT NULL DEFAULT ‘0’,
created_at bigint NOT NULL DEFAULT ‘0’,
updated_at bigint NOT NULL DEFAULT ‘0’,
deleted_at bigint NOT NULL DEFAULT ‘0’,
PRIMARY KEY (post_reply_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_post_reply_hashtag

CREATE TABLE tb_post_reply_hashtag (
id bigint unsigned NOT NULL AUTO_INCREMENT,
content_type tinyint DEFAULT NULL COMMENT ‘0 post, 1 reply’,
record_id bigint unsigned NOT NULL,
hashtag_id bigint unsigned NOT NULL,
created_at bigint NOT NULL DEFAULT ‘0’,
updated_at bigint NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_recommended_hashtag

CREATE TABLE tb_recommended_hashtag (
id int NOT NULL AUTO_INCREMENT,
hashtag_id int NOT NULL,
rank int NOT NULL DEFAULT ‘0’,
created_at bigint unsigned DEFAULT ‘0’,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;


— Table structure for table tb_recommended_user

CREATE TABLE tb_recommended_user (
id int NOT NULL AUTO_INCREMENT,
user_id bigint unsigned DEFAULT NULL,
rank int DEFAULT ‘0’,
created_at bigint unsigned DEFAULT ‘0’,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;


— Table structure for table tb_reported_content

CREATE TABLE tb_reported_content (
id bigint unsigned NOT NULL AUTO_INCREMENT,
content_type tinyint DEFAULT ‘0’ COMMENT ‘0 post, 1 reply’,
record_id bigint unsigned NOT NULL,
post_user_id bigint unsigned NOT NULL,
reporter_id bigint unsigned NOT NULL,
report_reason tinyint unsigned NOT NULL,
created_at bigint NOT NULL DEFAULT ‘0’,
updated_at bigint NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id),
KEY idx_post_id (record_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_reported_user

CREATE TABLE tb_reported_user (
id bigint unsigned NOT NULL AUTO_INCREMENT,
user_id bigint unsigned NOT NULL,
reporter_id bigint unsigned NOT NULL,
report_reason tinyint unsigned NOT NULL,
created_at bigint unsigned NOT NULL DEFAULT ‘0’,
updated_at bigint unsigned NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id),
KEY idx_user_id_reporter_id (user_id,reporter_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_user_relationship

CREATE TABLE tb_user_relationship (
id bigint unsigned NOT NULL AUTO_INCREMENT,
user_id bigint unsigned NOT NULL,
target_user bigint unsigned NOT NULL,
status_follow tinyint unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘0: unfollow; 1: follow; 2: block’,
status_mute tinyint unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘0: unmute; 1: mute’,
created_at bigint NOT NULL DEFAULT ‘0’,
updated_at bigint NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id),
UNIQUE KEY uiq_user_id_follwer_id (user_id,target_user),
KEY idx_follower_id (target_user)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_user_relationship_reversed

CREATE TABLE tb_user_relationship_reversed (
id bigint NOT NULL AUTO_INCREMENT,
user_id bigint DEFAULT NULL,
follower_id bigint DEFAULT NULL,
created_at bigint DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;


— Table structure for table tb_hashtag

CREATE TABLE tb_hashtag (
id bigint unsigned NOT NULL AUTO_INCREMENT,
tag_name varchar(128) COLLATE utf8mb4_general_ci NOT NULL,
user_id bigint unsigned NOT NULL,
count_mentions int unsigned NOT NULL DEFAULT ‘0’,
created_at bigint NOT NULL DEFAULT ‘0’,
updated_at bigint NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


— Table structure for table tb_parsed_url

CREATE TABLE tb_parsed_url (
id bigint NOT NULL AUTO_INCREMENT,
url varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
title varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
description varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
created_at bigint DEFAULT NULL,
updated_at bigint DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY id_UNIQUE (id),
UNIQUE KEY url_UNIQUE (url)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

API设计

基本约定:
1.获取数据用get,提交数据用post
2.返回的数据格式:{“code”: 0, “message”:”ok”, “data”:{} }, 成功的时候code为0,否则为非0

Account部分

1.创建账户 account/create
Method:POST
Params:
{“username”:”yourname”, “email”:”you@email.com”,”password”:”yourpwd”}
Response:
{
“code”: 0,
“data”: {
“activeCode”: “147849”,
“avatarUrl”: “”,
“bannerUrl”: “”,
“countFollowers”: “0”,
“countFollowings”: “0”,
“countLikes”: “0”,
“countPosts”: “0”,
“createdAt”: “1624266544”,
“description”: “”,
“displayName”: “”,
“dobDay”: “0”,
“dobMonth”: “0”,
“dobYear”: “0”,
“email”: “youremail@gmail.com”,
“iD”: “100000003”,
“isActived”: “0”,
“isVisibleDobDay”: “0”,
“isVisibleDobMonth”: “0”,
“isVisibleDobYear”: “0”,
“language”: “”,
“lastLogin”: “0”,
“location”: “”,
“password”: “123456”,
“phone”: “”,
“pinnedPostId”: “0”,
“suspended”: “0”,
“timeZone”: “”,
“updatedAt”: “1624266544”,
“username”: “myname”,
“verified”: “0”,
“website”: “”
},
“message”: “ok”
}

2.激活账户 account/activate (通过email里面的链接进行激活)
Method:POST
Params:
{“username”:”yourname”, “activate_code”:”toatciveyouraction”}
Response:
{
“code”: 0,
“data”: null,
“message”: “ok”
}

3.发送重置密码代码 account/send_reset_code
Method:POST
Params:
{“email”:”you@email.com”}
Response:
{
“code”: 0,
“data”: null,
“message”: “ok”
}
校验代码: account/verify_code
Method:POST
Params:
{“email”:”youremail@google.com”,”code”:”resetcode123″}
重置密码: account/reset_password
Method:POST
Params:
{“email”:”youremail@google.com”, “reset_code”:”restcode123″,”password”}
Response:
{
“code”: 0,
“data”: null,
“message”: “ok”
}

User 部分


1.用户登陆 user/login
Method:POST
Params:
{“name”:”yourname”,”password”:”yourpwd”}
Notes: name是username或者email
Response:
{
“code”: 0,
“data”: {
“token”: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjQ4NzA4MDAsImp0aSI6IjEwMDAwMDAwMyIsImlhdCI6MTYyNDI2NjAwMCwibmJmIjoxNjI0MjY2MDAwfQ.Hs2LTfG47Q1IDOhr9lY15usqyDPP1r7wKLk3l9nd4LQ”
},
“message”: “ok”
}

2.用户登出 user/logout(前端直接清理token)
Method:POST

3.修改语言 user/change_language
Method:POST
Params: {“language”:”en”}
Notes: 参数的值是options(目前只有en,zh),不能随意填写
Response:
{
“code”: 0,
“data”: {},
“message”: “ok”
}

4.修改密码 user/change_password
Method:POST
Params: {“password”:”newpasswd”,”old_password”:”oldpasswd”}
Notes: 密码有要求,最少8位,最少包含字母和数字
Response:
{
“code”: 0,
“data”: {},
“message”: “ok”
}
5.修改其它信息 user/save_info
Method:POST
Params:
{
“display_name”: “name1”,
“banner_url”: “”,
“avatar_url”: “”,
“location”: “bg”,
“website”: “http://www.mysite.com”,

“dob_year”: “2001”,
“dob_month”: “12”,
“dob_day”: “12”
}
Response:
{
“code”: 0,
“data”: {},
“message”: “ok”
}

6.查看用户页面(该页面有3个tab,所以一起有4个api)
6.1 user/profile
Method:GET
Params: {“username”:”yourname”}
Response:
{
“code”: 0,
“data”: {
“avatarUrl”: “”,
“bannelUrl”: “”,
“countFollowers”: “0”,
“countFollowings”: “0”,
“createdAt”: “1624266544”,
“description”: “”,
“displayName”: “name1”,
“dobDay”: “12”,
“dobMonth”: “12”,
“dobYear”: “2001”,
“email”: “youremail@gmail.com”,
“id”: “100000003”,
“isVisibleDobDay”: “0”,
“isVisibleDobMonth”: “0”,
“isVisibleDobYear”: “0”
},
“message”: “ok”
}

6.2 user/posts
Params: {“username”:”yourname”, “cursor”:101}
Notes: cursor初始不传,每次返回的结果中会带上新的cursor,下次请求的时候用这个值即可(后续所有的cursor都是一样的逻辑)
如果没有新的内容可加载,cursor会返回 -1

6.3 user/replies
Params: {“username”:”yourname”, “cursor”:101}

6.4 user/likes
Params: {“username”:”yourname”, “cursor”:101}

7.查看用户关注的人 user/following
Method:GET
Params: {“username”:”yourname”, “cursor”:101}
Response:
{
“code”: 0,
“data”: {
“cursor”: “0”,
“users”: [
{
“avatarUrl”: “”,
“description”: “”,
“displayName”: “”,
“id”: “100000001”
},
{
“avatarUrl”: “”,
“description”: “”,
“displayName”: “”,
“id”: “100000002”
},
{
“avatarUrl”: “”,
“description”: “”,
“displayName”: “name1”,
“id”: “100000003”
}
]
},
“message”: “ok”
}

8.查看关注该用户的人 user/followers
Method:GET
Params: {“username”:”yourname”,”cursor”:101}
用户部分2(在他人页面操作,登陆后可操作)
Response:
{
“code”: 0,
“data”: {
“cursor”: “0”,
“users”: [
{
“avatarUrl”: “”,
“description”: “”,
“displayName”: “”,
“id”: “100000001”
},
{
“avatarUrl”: “”,

“description”: “”,
“displayName”: “”,
“id”: “100000002”
},
{
“avatarUrl”: “”,
“description”: “”,
“displayName”: “name1”,
“id”: “100000003”
}
]
},
“message”: “ok”
}

——– 以下API需要用户登陆认证 ———

9.查看自己block的用户 user/blocked
Method:GET
Params: {“cursor”:101}

10.查看自己mute的用户 user/muted
Method:GET
Params: {“cursor”:101}

11.follow某个用户 user/follow
Method:POST
Params: {“target_username”:”tusername”, “flag”:1}
Notes: flag1表示follow, 0表示unfollow
database operation:在tb_user_relationship增加或者修改记录,具体参看表的定义
Response:
{
“code”: 0,
“data”: null,
“message”: “ok”
}

12.block某个用户 user/block
Method:POST
Params: {“target_username”:”tusername”, “flag”:1}
Notes: flag 1表示block, 0表示unblock
database operation:在tb_user_relationship增加或者修改记录,具体参看表的定义
Response:
{
“code”: 0,
“data”: null,
“message”: “ok”
}

13.mute某个用户 user/mute
Method:POST
Params: {“target_username”:”tusername”, “flag”:1}
Notes: flag1表示mute, 0表示unmute
database operation:在tb_user_relationship增加或者修改记录,具体参看表的定义
Response:
{
“code”: 0,
“data”: null,
“message”: “ok”
}

14.report某个用户 user/report
Method:POST
Params: {“target_username”:”tusername”, “report_reason”:1}
database operation: 在tb_reported_user表里增加记录
Response:
{
“code”: 0,
“data”: null,
“message”: “ok”
}


15.获取上传图片签名地址:upload/sign_image
Method: GET
Params: {“ext”:”jpg”} jpg|png|jpeg|gif
Response:
{
“code”: 0,
“data”: {
“url”: “https://twitter6-dev-bucket.s3.ap-northeast-1.amazonaws.com/images/20210622/cca1yk47jcsg92508.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIATWAJJDEPJQWRB524%2F20210622%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20210622T095116Z&X-Amz-Expires=1800&X-Amz-SignedHeaders=host&X-Amz-Signature=fb349171cd2d3cb5e49b711d9bf1950bba154081ad55534a3fcada33fcd0700a”
},
“message”: “ok”
}

16.获取上传视频签名地址:upload/sign_video
Method: GET
Params: {“ext”:”mp4″}
Response:
{
“code”: 0,
“data”: {
“url”: “https://twitter6-dev-bucket.s3.ap-northeast-1.amazonaws.com/videos/20210622/cca1yk47jcsg92508.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIATWAJJDEPJQWRB524%2F20210622%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20210622T095116Z&X-Amz-Expires=1800&X-Amz-SignedHeaders=host&X-Amz-Signature=fb349171cd2d3cb5e49b711d9bf1950bba154081ad55534a3fcada33fcd0700a”
},
“message”: “ok”
}


Post部分


1.发布推特 post/create
Method:POST
Params: {“content_text”:”text”, “media”: “image or video”}
Response:
{
“code”: 0,
“data”: {id: “a”},
“message”: “ok”
}

2.回复推特 post/reply
Method:POST
Params: {“content_text”:”text”, “media”: “image or video”,”original_post_id”:11122,”usename”:”namename”}
Response:
{
“code”: 0,
“data”: {id: “a”},
“message”: “ok”
}

3.转发推特
3.1 post/repost
Method:POST
Params: {“original_post_id”:11122, “usename”:”namename”,”content_text”:”repost content”}
Notes: 转推的时候也可以补充内容,原贴的repost数量要+1
Response:
{
“code”: 0,
“data”: {id: “a”},
“message”: “ok”
}
3.2 post/undo_repost 取消转推
Method:POST
Params: {“post_id”:11122}
Notes:这个记录可以直接删除了,另外原贴的repost数量要-1

4.点赞推特/取消点赞 post/like
Method:POST
Params: {“islike”:”1″,”post_id”:11122, “usename”:”namename”}
Notes: islike 1表示点赞,0表示取消点赞
Response:
{
“code”: 0,
“data”: null,
“message”: “ok”
}

5.举报推特 post/report
Method:POST
Params: {“post_id”:123123, “usename”:”namename”,”report_reason”:1}
database operation: 在tb_reported_post表里增加记录
Response:
{
“code”: 0,
“data”: null,
“message”: “ok”
}

6.推特详情
6.1 post/detail
Method: GET
Params: {“post_id”:123456, “usename”:”namename”}
Response:
{
“code”: 0,
“data”: {
“contentText”: “us is sb”,
“countComments”: “2”,
“countLikes”: “1”,
“countRepots”: “1”,
“id”: “b”,
“imageUrl”: “”,
“originalPostId”: “0”,
“userId”: “100000003”,
“videoUrl”: “”
},
“message”: “ok”
}
6.2 post/replies
Method: GET
Params: {“post_id”:123456, “usename”:”namename”,”cursor”:101}
Response:

7.删除推特 post/delete
Method: POST
Params: {“post_id”:123456}
database operation:把tb_post对应的记录is_deleted设置为1,并设置deleted_at
Response:
{
“code”: 0,
“data”: null,
“message”: “ok”
}

8.内容置顶 post/pin
Method: POST
Params: {“post_id”:123456, “ispin”:1}
Notes: ispin 1表示置顶,0表示取消置顶,默认是1
database operation:设置tb_user对应的记录pinned_post_id
Response:
{
“code”: 0,
“data”: {},
“message”: “ok”
}

数据表和API设计的细节

  • 分库分表策略:设计了tb_user_relationship和tb_user_relationship_reversed两个表,都按照user_id进行分库分表,这样不管是获取follower list还是following list,都是一次查询即可查到。
  • 数据归档策略:由于Twitter访问最多的是最新几天的post数据,可以对post按照一定的时间(如:一个月或者一周)进行归档,这样可以保证最新的post表数据不会过大。
  • 对于查询的优化:考虑数据的更新频率,把不会更新的数据放在一个表(如:tb_post),充分利用缓存;把经常更新的数据放在另外一个表(如:tb_post_extended),经常更新的字段是定长字段,mysql的查询性能也会非常高。
  • 体验上的优化:API设计的时候,把获取帖子内容和帖子统计数据(评论数、点赞数)也分开了。前端先获取帖子的内容,并立即显示给用户;然后再异步请求统计数据。这样还可以做到定时更新列表的统计数据,特别是大V可以不用刷新页面,也能看到点赞数和评论数在增加,获得良好的用户体验感。
  • Timeline处理细节:1.在cache中存放timeline的post id list,每次直接从cache中获取;2.用户如果是登陆后进入首页,登陆后异步生成timeline(生成时间小于3s),然后进入首页后再请求timeline,就可以直接从缓存获取到;3.用户如果已经是登陆状态,打开首页,前端从浏览器缓存中获取老的timeline数据先进行显示,同时异步请求timeline进行刷新。4.对于重要的用户,发了推之后,利用kafka的异步机制,主动刷新在线的关注者的timeline,以方便重要的post可以第一时间发出。
Categories
technology|技术 中文

支撑10亿用户的技术方案(细节篇)

  • 在思路篇中,我们谈到了解决海量用户的两个基本思路:1.化整为零,逐个击破;2.抽丝剥茧,直达核心。在这篇文章中,我们会对这两个策略中遇到的技术细节问题,进行详细的分析。

何为化整为零?其中有哪些需要注意的技术细节?

用技术的语言来解释化整为零,就是:分库分表。在思路篇中,我们谈到可以把10亿用户分成100份1000万的,那么分表就是解决1份1000万用户访问效率的问题,而分库就是对10亿用户进行的100份拆分。

分表是解决慢查询的一个非常实用的方案,分表一定要根据实际的使用场景来进行,最核心的原则就是:保证面向用户端的业务只需要查1~2次表(大部分1次,少量2次),就可以获取到所有的数据,而不需要查询所有相关的表做并集(内部的统计数据多查一些表,慢一些,是无所谓的,因为查出来后就可以存到新的数据表里)。

举例说明,抛砖引玉:Twitter这样的应用,1000万用户,平均每个用户follow 200个人,那么follow表的数据量将高达20亿条,那么从这样一个巨大的表中查询某个用户的数据,必然是很耗时的,这就是“慢查询”。

按照分表的思路,我们把这个follow关系表定义如下,以mysql数据库来举例:

CREATE TABLE tb_follow (
id bigint unsigned NOT NULL AUTO_INCREMENT,
user_id bigint unsigned NOT NULL,
target_user bigint unsigned NOT NULL,
created_at bigint NOT NULL DEFAULT '0',
updated_at bigint NOT NULL DEFAULT '0',
PRIMARY KEY (id),
UNIQUE KEY uiq_user_id_follwer_id (user_id,target_user),
KEY idx_follower_id (target_user)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CREATE TABLE tb_follower (
id bigint NOT NULL AUTO_INCREMENT,
user_id bigint NOT NULL DEFAULT ‘0’,
follower_id bigint NOT NULL DEFAULT ‘0’,
created_at bigint NOT NULL DEFAULT ‘0’,
updated_at bigint NOT NULL DEFAULT ‘0’,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

上面我定义了两个表,通过user_id可以从tb_follow查到follow list,通过user_id可以从tb_follower查到follower list,User A关注User B,将会在tb_follow中插入user_id=A_uid, target_user=B_uid,会在tb_follower中插入user_id=B_uid, follower_id=A_uid,即:1个follow关系,2条数据。

然后对tb_follow和tb_follower表都按照user_id拆分成10000个表,那么每个表的数据就变成了20万个,这样的数据量就很小了,查询起来就不会有慢查询了。

同理,在整个架构中的cache,kafka,也可以根据实际的需求进行拆分,避免单个实例的压力超出可承受的范围。

如何抽丝剥茧,找到性能瓶颈的核心所在?

在压测之后,我们通常会收到一份报告,类似下面这样的:

通过这份报告,可以看到正常请求几十毫秒的API,在压测的情况下,有的平均时间居然达到了100秒以上,是不是惊呆了?就问你慌不慌?

  • 先来对压测的请求做第一层分解:

压测机器发起请求,通过网络链路达到应用的机器的网卡(中间通常会有load balancer进行流量分发);网卡把命令发送给web service再转发给应用程序执行请求,并获取结果;结果通过网卡传输到网络返回到压测机器。

这里跟应用程序相关的:

一是网络,网络流量是否会占满带宽或者网卡?现在的服务器网卡基本上千兆级别,一般不会遇到网卡占满的情况,但是带宽被占满是有可能的,例如:压测的时候是通过外网访问的,而外网的带宽会有限制,而通过内网访问通常不会有这个问题

二是应用服务器,需要查看下应用服务器的cpu占用是否过高,内存是否占满,磁盘空间是否足够?另外,还有一个新手不了解的细节:文件描述符与并发tcp连接数的关系及限制。这个细节,可以通过google查相关的关键字,以及ulimit去做详细的了解,当你了解清楚之后,就会知道服务器上的相关参数是否限制了并发数。

  • 第二层分解,主要针对应用服务器内部执行的细节进行:

Web service把请求分发给应用程序进行执行,执行通常会有的操作:判断参数执行代码逻辑,读写cache,读写db,有的会包含写日志文件。

代码本身逻辑的问题需要具体问题具体分析,这里不做论述。关于Cache需要注意几点:1.命中率是否足够高(高访问量的情况下通常应该超过99%);2.单个实例是否被用满了(单实例250 million objects);3.实例的带宽、CPU等使用情况。

当然,最最重要的还是要分析db了。通常db出现性能问题,可以从错误日志中得到信息,例如:connect out of time。对于db的优化需要关注以下几点:

1.慢查询:可以通过优化查询、建立索引,来减少慢查询;当然,最最核心的还是通过分表来减少单表的数据量,以提升查询效率

2.实例的连接数:db instance都是有连接数限制的,通过调整相关参数,可以提高连接数,还可以通过建立连接池来提高连接的复用性;在参数都优化到极致之后,cpu和内存都耗尽,无法再提高上限,那么就需要用到分库的策略了,增加多个db instances来分担应用的连接。分库的规则可以参考分表的原则,基本上是一致的。

Categories
technology|技术 中文

支撑10亿用户的技术方案(思路篇)

大型互联网平台初期可以采用如图的架构来应对。

大型互联网平台有两个基本条件:

1.巨大的用户量(如:1亿以上)

2.并发访问量大(如:每天十亿级别的访问量)

巨大的用户量,以及用户的操作行为,会让数据库里的记录数量巨大,这会导致在做数据库查询的时候会比较慢;高并发访问,会导致单个数据库的连接数超出限制,导致数据库崩溃。

解决思路:

1.化整为零,逐个击破

把10亿用户拆分成100个1000万,先处理好1000万用户的问题,然后横向扩展100份就行了。这里只是举例,要根据实际应用的场景、数据量和使用的硬件等因素进行合理的拆分,而且实际情况中可能也不是平均来分。当然,这里还有很多技术细节需要考量,我们会在单独的博客里面进行详细的解读。

2.抽丝剥茧,直达核心

技术架构图中可以看到整个系统中的各个组件,其中数据库是最大的瓶颈,因此解决好数据库的访问压力问题,就可以很容易解决好其它组件面临的问题。