使用FriendFeed来提升MySQL性能的方法_Mysql

 背景

我们使用MySQL存储了FriendFeed的所有数据。数据库随着用户基数的增长而增长了很多。现在已经存储了超过2.5亿条记录与一堆涵盖了从评论和“喜欢”到好友列表的其他数据。

随着数据的增长,我们也曾迭代地解决了随着如此迅猛的增长而带来的扩展性问题。我们的尝试很有代表性,例如使用只读mysql从节点和memcache来增加读取吞吐量,对数据库进行分片来提高写入吞吐量。然而,随着业务的增长,添加新功能比扩展既有功能以迎合更多的流量变得更加困难。

特别的,对 schema 做改动或为超过 1000-2000 万行记录的数据库添加索引会将数据库锁住几个小时。删除旧索引也要占用这么多时间,但不删除它们会影响性能;因为数据库要持续地在每个INSERT上读写这些没用的区块,并将重要的区块挤出了内存。为避免这些问题需要采取一些复杂的措施(例如在从节点上设置新的索引,然后将从节点与主节点对调),但这些措施会引发错误并且实施起来比较困难,它们阻碍了需要改动 schema/索引才能实现的新功能。由于数据库的严重分散,MySQL 的关系特性(如join)对我们没用,所以我们决定脱离 RDBMS。

虽然已有许多用于解决灵活 schema 数据存储和运行时构建索引的问题(例如 CouchDB)的项目。但在大站点中却没有足够广泛地用到来说服人们使用。在我们看到和运行的测试中,这些项目要么不稳定,要么缺乏足够的测试(参见这个有点过时的关于 CouchDB 的文章)。MySQL 不错,它不会损坏数据;复制也没问题,我们已经了解了它的局限。我们喜欢将 MySQL 用于存储,仅仅是非关系型的存储。

几经思量,我们决定在 MySQL 上采用一种无模式的存储系统,而不是使用一个完全没接触过的存储系统。本文试图描述这个系统的高级细节。我们很好奇其他大型网站是如何处理这些问题的,另外也希望我们完成的某些设计会对其他开发者有所帮助。

综述

我们在数据库中存储的是无模式的属性集(例如JSON对象或python字典)。存储的记录只需一个名为id的16字节的UUID属性。对数据库而言实体的其他部分是不可见的。我们可以简单地存入新属性来改变schema(可以简单理解为数据表中只有两个字段:id,data;其中data存储的是实体的属性集)。

我们通过保存在不同表中的索引来检索数据。如果想检索每个实体中的三个属性,我们就需要三个数据表-每个表用于检索某一特定属性。如果不想再用某一索引了,我们要在代码中停止该索引对应表的写操作,并可选地删除那个表。如果想添加个新索引,只需要为该索引新建个MySQL表,并启动一个进程异步地为该表添加索引数据(不影响运行中的服务)。

最终,虽然我们的数据表增多了,但添加和删除索引却变得简单了。我们大力改善了添加索引数据的进程(我们称之为“清洁工")使其在快速添加索引的同时不会影响站点。我们可以在一天内完成新属性的保存和索引,并且我们不需要对调主从MySQL数据库,也不需要任何其他可怕的操作。

细节

MySQL 使用表保存我们的实体,一个表就像这样 :
 

CREATE TABLE entities (
  added_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  id BINARY(16) NOT NULL,
  updated TIMESTAMP NOT NULL,
  body MEDIUMBLOB,
  UNIQUE KEY (id),
  KEY (updated)
) ENGINE=InnoDB;

之所以使用 added_id 个字段是因为 InnoDB 按物理主键顺序存储数据,自增长主键确保新实例在磁盘上按顺序写到老实体之后,这样有助于分区读写(相对老的实体,新实体往往读操作更频繁,因为 FriendFeed 的 pages 是按时间逆序排列)。实体本身经 python 字典序列化后使用 zlib 压缩存储。

索引单独存在一张表里,如果要创建索引,我们创建一张新表存储我们想要索引的数据分片的所有属性。例如,一个 FriendFeed 实体通过看上去是这样的:
 

{
  "id": "71f0c4d2291844cca2df6f486e96e37c",
  "user_id": "f48b0440ca0c4f66991c4d5f6a078eaf",
  "feed_id": "f48b0440ca0c4f66991c4d5f6a078eaf",
  "title": "We just launched a new backend system for FriendFeed!",
  "link": "http://friendfeed.com/e/71f0c4d2-2918-44cc-a2df-6f486e96e37c",
  "published": 1235697046,
  "updated": 1235697046,
}

我们索引实体的属性 user_id,这样我们可以渲染一个页面,包含一个已提交用户的所有属性。我们的索引表看起来是这样的:

CREATE TABLE index_user_id (
  user_id BINARY(16) NOT NULL,
  entity_id BINARY(16) NOT NULL UNIQUE,
  PRIMARY KEY (user_id, entity_id)
) ENGINE=InnoDB;

我们的数据存储会自动为你维护索引,所以如果你要在我们存储上述结构实体的数据存储里开启一个实例,你可以写一段代码(用 python):
 

user_id_index = friendfeed.datastore.Index(
  table="index_user_id", properties=["user_id"], shard_on="user_id")
datastore = friendfeed.datastore.DataStore(
  mysql_shards=["127.0.0.1:3306", "127.0.0.1:3307"],
  indexes=[user_id_index])

new_entity = {
  "id": binascii.a2b_hex("71f0c4d2291844cca2df6f486e96e37c"),
  "user_id": binascii.a2b_hex("f48b0440ca0c4f66991c4d5f6a078eaf"),
  "feed_id": binascii.a2b_hex("f48b0440ca0c4f66991c4d5f6a078eaf"),
  "title": u"We just launched a new backend system for FriendFeed!",
  "link": u"http://friendfeed.com/e/71f0c4d2-2918-44cc-a2df-6f486e96e37c",
  "published": 1235697046,
  "updated": 1235697046,
}
datastore.put(new_entity)
entity = datastore.get(binascii.a2b_hex("71f0c4d2291844cca2df6f486e96e37c"))
entity = user_id_index.get_all(datastore, user_id=binascii.a2b_hex("f48b0440ca0c4f66991c4d5f6a078eaf"))

上面的 Index 类在所有实体中查找 user_id,自动维护 index_user_id 表的索引。我们的数据库是切分的,参数 shard_on 是用来确定索引是存储在哪个分片上(这种情况下使用 entity["user_id"] % num_shards)。

你可以使用索引实例(见上面的 user_id_index.get_all)查询一个索引,使用 python 写的数据存储代码将表 index_user_id 和表 entities 合并。首先在所有数据库分片中查询表 index_user_id 获取实体 ID 列,然后在 entities 提出数据。

新建一个索引,比如,在属性 link 上,我们可以创建一个新表:
 

CREATE TABLE index_link (
  link VARCHAR(735) NOT NULL,
  entity_id BINARY(16) NOT NULL UNIQUE,
  PRIMARY KEY (link, entity_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

我们可以修改数据存储的初始化代码以包含我们的新索引:
 

user_id_index = friendfeed.datastore.Index(
  table="index_user_id", properties=["user_id"], shard_on="user_id")
link_index = friendfeed.datastore.Index(
  table="index_link", properties=["link"], shard_on="link")
datastore = friendfeed.datastore.DataStore(
  mysql_shards=["127.0.0.1:3306", "127.0.0.1:3307"],
  indexes=[user_id_index, link_index])

我可以异步构建索引(特别是实时传输服务):
 

./rundatastorecleaner.py --index=index_link

一致性与原子性

由于采用分区的数据库,实体的索引可能存储在与实体不同的分区中,这引起了一致性问题。如果进程在写入所有索引表前崩溃了会怎样?

许多有野心的 FriendFeed 工程师倾向于构建一个事务性协议,但我们希望尽可能地保持系统的简洁。我们决定放宽限制:

  •     保存在主实体表中的属性集是规范完整的
  •     索引不会对真实实体值产生影响

因此,往数据库中写入实体时我们采用如下步骤:

  •     使用 InnoDB 的 ACID 属性将实体写入 entities 表。
  •     将索引写入所有分区中的索引表。

我们要记住从索引表中取出的数据可能是不准确的(例如如果写操作没有完成步骤2可能会影响旧属性值)。为确保采用上面的限制能返回正确的实体,我们用索引表来决定要读取哪些实体,但不要相信索引的完整性,要使用查询条件对这些实体进行再过滤:

1.根据查询条件从索引表中取得 entity_id

2.根据 entity_id 从 entities 表中读取实体

3.根据实体的真实属性(用 Python)过滤掉不符合查询条件的实体

为保证索引的持久性和一致性,上文提到的“清洁工”进程要持续运行,写入丢失的索引,清理失效的旧索引。它优先清理最近更新的实体,所以实际上维护索引的一致性非常快(几秒钟).
 
性能

我们对新系统的主索引进行了优化,对结果也很满意。以下是上个月 FriendFeed 页面的加载延时统计图(我们在前几天启动了新的后端,你可以根据延时的显著回落找到那一天)。

特别地,系统的延时现在也很稳定(哪怕是在午高峰期间)。如下是过去24小时FriendFeed页面加载延时图。

与上周的某天相比较:

系统到目前为止使用起来很方便。我们在部署之后也改动了几次索引,并且我们也开始将这种模式应用于 MySQL 中那些较大的表,这样我们在以后可以轻松地改动它们的结构。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索mysql
FriendFeed
提升mysql性能、mysql5.7性能提升、friendfeed、高性能mysql、mysql 性能优化,以便于您获取更多的相关知识。

时间: 2024-11-01 19:49:39

使用FriendFeed来提升MySQL性能的方法_Mysql的相关文章

MySQL服务器默认安装之后调节性能的方法_Mysql

My favorite question during Interview for people to work as MySQL DBAs or be involved with MySQL Performance in some way is to ask them what should be tuned in MySQL Server straight after installation, assuming it was installed with default settings.

linux忘记mysql密码处理方法_Mysql

linux忘记mysql密码处理方法: # /etc/init.d/mysql stop # mysqld_safe --user=mysql --skip-grant-tables --skip-networking & # mysql -u root mysql mysql> update user set password=password('newpassword') where user='root'; mysql> flush privileges; mysql> q

Ubuntu与windows双系统下共用MySQL数据库的方法_Mysql

双系统配置及MySQL数据库存储情境:Windows XP下d:\mysql\data中存有MySQL数据库,Linux系统为Ubuntu Server 9.10. 双系统安装完毕,利用GURB启动到Ubuntu Server 9.10,以管理员身份登录. 1)并读写方式(RW)认方式挂载Windows中D分区至/media/wind. 2)更改/media/wind/mysql/data权限 sudo chow mysql:mysql /media/wind/mysql/data (其中两个m

使用dreamhost空间实现MYSQL数据库备份方法_Mysql

如何利用SSH(Shell)来备份和恢复MySQL数据库的方法 例如: 数据库参数为:: MySQL地址:mysql.dh.net MySQL名称:mysql_dbname MySQL用户:mysql_dbuser MySQL密码:mysql_dbpass 我要把数据库备份为bak.sql 步骤: 同样,使用windows系统自己带的telnet或者去下载一个putty来,登陆以后,一路cd到自己觉得合适的目录下(确认当前目录可写). 输入下面的命令: mysqldump -h mysql.dh

MySQL配置文件my.cnf中文详解附mysql性能优化方法分享_Mysql

下面先说我的服务器的硬件以及论坛情况,CPU: 2颗四核Intel Xeon 2.00GHz内存: 4GB DDR硬盘: SCSI 146GB论坛:在线会员 一般在 5000 人左右 – 最高记录是 13264.下面,我们根据以上硬件配置结合一份已经做过一次优化的my.cnf进行分析说明:有些参数可能还得根据论坛的变化情况以及程序员的程序进行再调整.[mysqld]port = 3306serverid = 1socket = /tmp/mysql.sockskip-locking # 避免My

通过分区(Partition)提升MySQL性能

今天这么激动又想写文章的原因是MySQL5.1的发布带来了设计超强动力数据库的强有力的武器,任何MySQL的DBA都应该尽快学习并使用它.我觉得如果­能很好滴使用这个5.1版带来的新特性,DBA可以使自己管理的VLDB(不知道什么是VLDB?告诉你,是好大好大的数据库的意思,Very Large DB)或数据仓库奇迹般的获得巨大的性能提升. 什么是数据库分区? 数据库分区是一种物理数据库设计技术,DBA和数据库建模人员对其相当熟悉.虽然分区技术可以实现很多效果,但其主要目的是为了在特定的SQL操

MySQL延迟关联性能优化方法_Mysql

[背景]   某业务数据库load 报警异常,cpu usr 达到30-40 ,居高不下.使用工具查看数据库正在执行的sql ,排在前面的大部分是: 复制代码 代码如下: SELECT id, cu_id, name, info, biz_type, gmt_create, gmt_modified,start_time, end_time, market_type, back_leaf_category,item_status,picuture_url FROM relation where

提升电脑性能的方法

1.什么是系统优化? 答:系统优化模块,将"清理垃圾"."清除痕迹"."清理插件"."开机加速"."系统加速"集合至系统优化模块,系统优化操作更方便,全速提升系统性能. 2.什么是系统优化首页?如何一键优化系统? 答:QQ电脑管家5.0推出系统首页,一次点击完成全部优化项,包括开机加速.系统加速.IE插件清理.系统垃圾和清除痕迹.简单操作,全面解决电脑问题! 第一步,打开系统优化,点击"开始扫描&

高手心得:提高MySQL性能的方法 (1)

一.问题的提出 在应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,复杂视图的的编写等体会不出SQL语句各种写法的性能优劣,但是如果将应用系统提交实际应用后,随着数据库中数据的增加,系统的响应速度就成为目前系统需要解决的最主要的问题之一.系统优化中一个很重要的方面就是SQL语句的优化.对于海量数据,劣质SQL语句和优质SQL语句之间的速度差别可以达到上百倍,可见对于一个系统不是简单地能实现其功能就可,而是要写出高质量的SQL语句,提高系统的可用性. 在多数情况下,Oracle使用索