이것은 여러 MySQL 서버에서 데이터를 분할하는 방법에 대한 기술 연구입니다. 우리는 2012년 초에 이 샤딩 접근 방식을 완료했으며 오늘날에도 여전히 핵심 데이터를 저장하는 데 사용하는 시스템입니다.
데이터를 분할하는 방법을 논의하기 전에 먼저 데이터를 이해하겠습니다. 무드 조명, 초콜릿으로 덮인 딸기, Star Trek 인용문…
Pinteres는 관심 있는 모든 것을 검색하는 엔진입니다. 데이터 관점에서 볼 때, Pinterest는 전 세계에서 인간이 관심을 갖는 이미지를 가장 많이 모아 놓은 곳입니다. 10억 개의 보드에 걸쳐 피너가 저장한 핀이 500억 개가 넘습니다. 사용자는 다른 사람의 핀(대략적으로 말하면 얕은 복사본)과 마찬가지로 다시 핀을 고정하고 다른 피너, 보드 및 관심사를 팔로우한 다음 홈 페이지에서 구독한 피너의 게시물을 모두 볼 수 있습니다. 매우 좋은! 이제 규모를 키워보세요!
Growing Pains
2011년에 우리는 성공했습니다. 일부 평가 보고서에서는 다른 스타트업에 비해 훨씬 빠르게 성장하고 있다고 합니다. 2011년 9월, 우리의 기본 장비가 모두 초과되었습니다. 우리는 다양한 NoSQL 기술을 적용했지만 모두 비참한 결과를 가져왔습니다. 동시에 읽기에 사용되는 수많은 MySQL 슬레이브 서버로 인해 특히 캐싱과 같은 성가신 버그가 많이 발생했습니다. 우리는 전체 데이터 스토리지 모델을 재구성했습니다. 이를 효과적으로 수행하기 위해 우리는 요구 사항을 신중하게 공식화합니다.
비즈니스 요구 사항
전체 시스템은 매우 안정적이고 운영 및 확장이 쉬워야 합니다. 지원되는 데이터베이스가 작은 저장 용량으로 시작하여 비즈니스 성장에 따라 확장될 수 있기를 바랍니다.
핀으로 생성된 모든 콘텐츠는 사이트에서 쉽게 액세스할 수 있어야 합니다.
N개의 핀에 대한 액세스를 요청하여 특정 순서(예: 생성 시간 또는 사용자별 순서)로 아트보드에 표시되도록 지원합니다. 좋아요를 누른 핀 친구와 핀 친구의 핀 목록도 특정 순서로 표시될 수 있습니다.
간결함을 위해 업데이트는 일반적으로 최상의 결과를 보장해야 합니다. 최종 일관성을 얻으려면 분산 트랜잭션 로그와 같은 추가 항목이 필요합니다. 이것은 흥미롭고 (아님) 간단한 일입니다.
솔루션 아이디어 및 핵심 포인트
이 솔루션은 대용량 데이터 조각을 여러 데이터베이스 인스턴스에 분산해야 하며, 연결, 외래 키 또는 관계형 데이터베이스의 인덱스와 같은 방법으로는 전체 데이터를 통합할 수 없습니다. 생각해 보면 상관 하위 쿼리는 서로 다른 데이터베이스 인스턴스에 걸쳐 있을 수 없습니다.
저희 솔루션에는 로드 밸런싱 데이터 액세스가 필요합니다. 우리는 데이터 마이그레이션, 특히 오류가 발생하기 쉽고 관계의 복잡성으로 인해 시스템에 불필요한 복잡성을 추가하는 레코드별 마이그레이션을 싫어합니다. 데이터를 마이그레이션해야 하는 경우 전체 논리 노드 세트를 마이그레이션하는 것이 가장 좋습니다.
솔루션을 안정적이고 신속하게 구현하려면 분산 데이터 플랫폼에서 구현하기 가장 쉽고 강력한 기술 솔루션을 사용해야 합니다.
각 인스턴스의 모든 데이터는 데이터 백업으로 슬레이브 인스턴스에 완전히 복사됩니다. 우리는 가용성이 높은 MapReduce(분산 컴퓨팅 환경)인 S3를 사용하고 있습니다. 우리의 프런트 엔드 비즈니스 로직은 백그라운드 데이터에 액세스하고 데이터베이스의 기본 인스턴스에만 액세스합니다. 프런트엔드 비즈니스에 슬레이브 인스턴스에 대한 읽기 및 쓰기 액세스 권한을 부여하지 마십시오. 마스터 인스턴스와의 데이터 동기화가 지연되므로 설명할 수 없는 오류가 발생하게 되며, 일단 데이터가 슬라이싱되어 분산되면 프런트엔드 비즈니스가 슬레이브 인스턴스에서 데이터를 읽고 쓸 이유가 없습니다.
마지막으로 모든 데이터 객체의 UUID(Universally Unique Identifier)를 생성하고 구문 분석하기 위한 우수한 솔루션을 신중하게 설계해야 합니다.
우리의 슬라이싱 솔루션
무슨 일이 있어도 우리의 요구 사항을 충족하고 강력하며 성능이 좋고 유지 관리가 가능한 데이터 배포 솔루션을 설계해야 합니다. 즉, (광범위한 검증 없이) 순진할 수 없습니다. 따라서 우리의 기본 설계는 MySQL을 기반으로 구축되었습니다. 성숙한 기술을 선택했습니다. 설계 초기에 우리는 MongoDB, Cassandra 및 Membase와 같이 Auto Scaling의 새로운 기능이 있다고 주장하는 데이터베이스 제품을 자연스럽게 기피하게 됩니다. 설명할 수 없는 오류가 발생하여 충돌이 발생했습니다.)
나레이션: 최신 유행이나 새로운 것들은 피하고 기본부터 시작하는 것이 좋습니다. MySQL을 실제적인 방법으로 잘 배우고 사용하세요. 저를 믿으십시오. 모든 단어는 눈물로 가득 차 있습니다.
MySQL은 성숙하고 안정적이며 사용하기 쉬운 관계형 데이터베이스 제품입니다. 우리가 사용할 뿐만 아니라, 많은 유명 대기업에서도 대규모 데이터를 저장하기 위한 백엔드 데이터 지원으로 사용하고 있습니다. (주석: 몇 년 전쯤 MySQL은 SUN과 함께 Oracle에 인수되어 Oracle이라는 이름으로 바뀌게 되었습니다. Google, Facebook 등 많은 기업들이 MySQL의 오픈 소스 문제를 우려하여 다른 회사로 전환했습니다. MySQL의 원저작자가 개발한 것입니다. 오픈 소스 데이터베이스 MariaDB(아래) MySQL은 데이터베이스에 대한 순차적 데이터 요청, 지정된 범위의 데이터 쿼리 및 행(레코드) 수준의 트랜잭션 처리에 대한 기술 요구 사항을 지원합니다. MySQL에는 많은 기능이 있지만 우리에게는 그런 기능이 필요하지 않습니다. MySQL 자체는 모놀리식 솔루션이므로 데이터를 분할해야 합니다. (주석: 여기서의 의미는 단일 인스턴스가 막대한 양의 데이터를 관리하므로 필연적으로 성능 문제가 발생할 수 있다는 것입니다. 이제 방대한 전체 데이터를 개별 데이터 세트로 분할하려면 각 개별 데이터를 분리할 수 있는 강력한 기술 솔루션이 필요합니다. 전체적으로, 문제 없이 성능 향상) 다음은 우리의 설계 계획입니다.
우리는 8개의 EC2 서버로 시작하고 각 서버는 MySQL 인스턴스를 실행합니다.
각 MySQL 서버에는 자체 마스터-마스터가 1개로 복제됩니다. 재해 복구용 중복 호스트. 우리의 프런트 엔드 비즈니스는 기본 서비스 인스턴스에서만 데이터를 읽고 씁니다. 나는 당신도 똑같이 할 것을 권장합니다. 이렇게 하면 많은 일이 단순화되고 지연된 결함이 방지됩니다. (주석: 마스터-마스터 복제는 MySQL 데이터베이스 자체에서 제공하는 기능입니다. 두 머신이 서로 백업하는 모드를 말합니다. 마스터-슬레이브 백업 등 다른 모드에 비해 두 머신의 데이터는 완전히 일관된 백그라운드 동기화를 통해 각 머신은 자체 독립 IP를 가지며 동시에 액세스할 수 있습니다. 그러나 원본 기사의 작성자는 두 머신이 중복되어 기본-기본 백업을 사용하더라도 논리적으로 액세스할 수 있다고 반복해서 강조했습니다. 마스터와 슬레이브는 항상 둘 중 하나에서 읽기/쓰기가 가능합니다. 예를 들어 그림에 표시된 것처럼 MySQL001A와 MySQL001B 사이에는 마스터-마스터 백업이 있지만 MySQL001A에서만 읽기/쓰기가 가능합니다. 이들은 16개의 머신을 사용합니다. , 나머지 8개의 슬레이브 머신은 EC2가 아닐 수도 있습니다.)
각 MySQL 인스턴스는 여러 개의 데이터베이스를 가질 수 있습니다:
각 데이터베이스의 이름이 dbNNNN까지 고유하게 db00000, db00001로 지정되는 방식에 유의하세요. 각 데이터베이스는 우리 데이터베이스의 조각입니다. 우리는 데이터 조각이 샤드에 할당되면 해당 샤드 밖으로 이동되지 않도록 설계했습니다. 그러나 샤드를 다른 머신으로 이동하면 더 많은 용량을 얻을 수 있습니다(이에 대해서는 나중에 논의하겠습니다).
슬라이스 데이터베이스가 어느 컴퓨터에 있는지 기록하는 구성 데이터베이스 테이블을 유지 관리합니다.
[ {“range”: (0,511), “master”: “MySQL001A”, “slave”: “MySQL001B”}, {“range”: (512, 1023), “master”: “MySQL002A”, “slave”: “MySQL002B”}, ... {“range”: (3584, 4095), “master”: “MySQL008A”, “slave”: “MySQL008B”} ]
이 구성 테이블은 슬라이스 데이터베이스가 마이그레이션되거나 호스트가 교체될 때만 수정됩니다. 예를 들어 마스터 인스턴스 호스트가 다운되면 해당 슬레이브 인스턴스 호스트를 마스터 인스턴스로 승격한 후 가능한 한 빨리 슬레이브 인스턴스 호스트인 새 머신으로 교체합니다. 구성 스크립트는 ZooKeeper에 유지됩니다. 위의 수정 사항이 발생하면 구성 변경을 위해 슬라이싱 서비스를 유지 관리하는 시스템으로 스크립트가 전송됩니다. (주석: 원저자가 항상 프런트엔드 비즈니스는 논리적 마스터 인스턴스에서만 데이터를 읽고 쓴다는 점을 강조했다는 장점을 찾을 수 있습니다.)
각 슬라이스 데이터베이스는 핀, 보드, users_has_pins, users_likes_pins, pin_liked_by_user 및 기타 데이터베이스 테이블과 같은 동일한 데이터베이스 테이블 및 테이블 구조를 유지합니다. 배포 시 동기적으로 빌드합니다.
슬라이스 서버에 데이터를 배포하기 위한 설계 솔루션
슬라이스 ID(샤드 ID), 데이터 유형 식별자 및 로컬 ID(로컬 ID)를 결합하여 64비트 전역 고유 식별자(ID)를 형성합니다. 슬라이스 ID(shard ID)는 16비트(bit), 데이터 유형 식별자는 10비트(bit), 로컬 ID(local ID)는 36비트(bit)를 차지합니다. 안목이 있는 사람이라면 이것이 단지 62비트라는 것을 즉시 알아차릴 것입니다. 데이터 분산 및 집계에 대한 나의 과거 경험에 따르면 확장을 위해 몇 가지를 유지하는 것이 매우 귀중합니다. 따라서 2비트(0으로 설정)를 유지했습니다. (주석: 여기서 설명하겠습니다. 다음 작업 및 설명에 따르면 모든 객체의 고유 식별 ID는 64비트이고 가장 높은 2비트는 항상 0이며 그 다음은 36비트 로컬 식별, 그 다음은 10비트입니다. 유형 식별, 마지막으로 16비트 슬라이스 식별자는 2^36 최대 600억 개 이상의 ID를 나타낼 수 있으며 데이터 유형은 2^10 최대 1024개의 객체 유형을 나타낼 수 있으며 슬라이스 식별자는 2^16으로 세분화될 수 있습니다. 최대 65536개의 슬라이스 데이터베이스를 잘라냅니다. 4096개의 슬라이스 데이터베이스를 잘라냅니다.)
ID = (shard ID << 46) | (type ID << 36) | (local ID<<0) 以 Pin: https://www.pinterest.com/pin/241294492511... 为例,让我们解构这个 Pin 对象的 全局 ID 标识 241294492511762325 : Shard ID = (241294492511762325 >> 46) & 0xFFFF = 3429 Type ID = (241294492511762325 >> 36) & 0x3FF = 1 Local ID = (241294492511762325 >> 0) & 0xFFFFFFFFF = 7075733
이 Pin 개체가 3429 슬라이스 데이터베이스에 있음을 알 수 있습니다. Pin 개체 데이터 유형 식별자가 1이고 해당 레코드가 3429 슬라이스 데이터베이스의 핀 데이터 테이블에 있는 7075733 레코드 행에 있다고 가정합니다. 예를 들어, 슬라이스 3429 데이터베이스가 MySQL012A에 있다고 가정하면 다음 명령문을 사용하여 해당 데이터 레코드를 가져올 수 있습니다. (주석: 여기의 원 작성자는 일반적인 예를 제공합니다. 이전 예에 따르면 3429는 MySQL007A에 있어야 합니다.)
conn = MySQLdb.connect(host=”MySQL012A”) conn.execute(“SELECT data FROM db03429.pins where local_id=7075733”)
有两种类型的数据:对象或关系。对象包含对象本身细节。 如 Pin 。
存储对象的数据库表
对象库表中的每个记录,表示我们前端业务中的一个对象,诸如:Pins(钉便签), users(用户),boards(白板)和 comments(注释),每个这样的记录在数据库表中设计一个标识 ID 字段(这个字段在表中作为记录的 自增主键「auto-incrementing primary key」 ,也就是我们前面提到的 局部 ID「 local ID」 ),和一个 blob 数据字段 -- 使用 JSON 保存对象的具体数据 --。
CREATE TABLE pins ( local_id INT PRIMARY KEY AUTO_INCREMENT, data TEXT, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB;
举例,一个 Pin 对象形状如下:
{“details”: “New Star Wars character”, “link”: “http://webpage.com/asdf”, “user_id”: 241294629943640797, “board_id”: 241294561224164665, …}
创建一个 Pin 对象,收集所有的数据构成 JSON blob 数据。然后,确定它的 切片 ID「 shard ID」 (我们更乐意把 Pin 对象的切片数据放到跟其所在 白板「 board」 对象相同的切片数据库里,这不是强制设计规则)。Pin 对象的数据类型标识为 1。连接到 切片 ID 指示的切片数据库,插入(insert)Pin 对象的 JOSON 数据到 Pin 对象数据库表中,MySQL 操作成功后将会返回 自增主键「auto-incrementing primary key」 给你,这个作为此 Pin 对象的 局部 ID「 local ID」。现在,我们有了 shard 、类型值、local ID 这些必要信息,就可以构建出此 Pin 对象的 64 位 ID 。(译注:原作者提到的,他们的前端业务所用到的每种对象都保存在一个对象数据库表里,每个对象记录都通过一个全局唯一 ID 去找到它,但这个全局唯一 ID 并不是数据库表中的 局部 ID,由于切片的缘故。原作者一直在讲这个设计及其原理。这样设计的目的为了海量数据切片提高性能,还要易用,可维护,可扩展。后面,作者会依次讲解到)
编辑一个 Pin 对象,使用 MySQL 事务「transaction」 在 Pin 对象的数据记录上 读出 -- 修改 -- 写回「read-modify-write」 Pin 对象的 JOSON 数据字段:
> BEGIN > SELECT blob FROM db03429.pins WHERE local_id=7075733 FOR UPDATE [修改 json blob] > UPDATE db03429.pins SET blob=’<修改后的 blob>’ WHERE local_id=7075733 > COMMIT
编辑一个 Pin 对象,您当然可以直接删除这个对象在 MySQL 数据库表中的数据记录。但是,请仔细想一下,是否在对象的 JSON 数据上加个叫做「 active」的域,把剔除工作交由前端中间业务逻辑去处理或许会更好呢。
(译注:学过关系数据库的应知道,自增主键在记录表中是固实,在里面删除记录,会造成孔洞。当多了,势必造成数据库性能下降。数据库只负责保存数据和高性能地查询、读写数据,其数据间的关系完全靠设计精良的对象全局 ID 通过中间件逻辑去维护 这样的设计理念一直贯穿在作者的行文中。只有理解了这点您才能抓住这篇文章的核心)
关系映射数据库表
关系映射表表示的是前端业务对象间的关系。诸如:一个白板(board)上有哪些钉便签(Pin), 一个钉便签(Pin)在哪些白板(board)上等等。表示这种关系的 MySQL 数据库表包括 3 个字段:一个 64 位的「from」ID, 一个 64 位的「to」ID 和一个顺序号。每个字段上都做索引方便快速查询。其记录保存在根据「from」字段 ID 解构出来的切片 ID 指示出的切片数据库上。
CREATE TABLE board_has_pins ( board_id INT, pin_id INT, sequence INT, INDEX(board_id, pin_id, sequence) ) ENGINE=InnoDB;
(译注:这里的关系映射指前端业务对象间的关系用数据库表来运维,并不指我上节注释中说到的关系数据库的关系映射。作者开篇就讲到,由于切片,不能做关系数据库表间的关系映射的,如一对一,一对多,多对多等关系关联)
关系映射表是单向的,如 board_has_pins(板含便签)表方便根据 board (白板)ID 查询其上有多少 Pin(钉便签)。若您需要根据 Pin(钉便签)ID 查询其都在哪些 board(白板)上,您可另建个表 pin_owned_by_board(便签属于哪些白板)表,其中 sequence 字段表示 Pin 在 board 上的顺序号。(由于数据分布在切片数据库上,我们的 ID 本身无法表示其顺序)我们通常将一个新的 Pin 对象加到 board 上时,将其 sequence 设为当时的系统时间。sequence 可被设为任意整数,设为当时的系统时间,保证新建的对象的 sequence 总是大于旧对象的。这是个方便易行的方法。您可通过下面的语句从关系映射表中查询对象数据集:
SELECT pin_id FROM board_has_pins WHERE board_id=241294561224164665 ORDER BY sequence LIMIT 50 OFFSET 150
语句会查出 50 个 pin_ids(便签 ID ), 随后可用这些对象 ID 查询其具体信息。
board_id -> pin_ids -> 핀 개체(화이트보드 ID -> 노트 ID -> 노트 개체)와 같은 비즈니스 애플리케이션 계층에서만 이러한 관계를 매핑합니다. 이 디자인의 가장 큰 특징은 이러한 관계형 맵 쌍을 별도로 캐시할 수 있다는 것입니다. 예를 들어, memcache(메모리 캐시) 클러스터 서버에 pin_id -> pin 개체(note ID -> note 개체) 관계 매핑을 저장하고, board_id -> pin_ids(whiteboard ID -> note IDs) 관계를 캐시합니다. 클러스터 서버의 Redis에 캐시를 매핑합니다. 이런 방식으로 우리의 최적화 캐싱 기술 전략에 매우 적합할 수 있습니다.
서비스 역량 강화
저희 시스템에는 서비스 처리 역량을 향상시키는 방법이 크게 3가지 있습니다. 가장 쉬운 방법은 머신을 업그레이드하는 것입니다(더 큰 공간, 더 빠른 하드 디스크 속도, 더 많은 메모리, 시스템 병목 현상을 해결하는 업그레이드)
또 다른 방법은 슬라이싱 범위를 확장하는 것입니다. 처음에 우리는 4096개의 데이터베이스만을 슬라이스하도록 설계했습니다. 우리가 설계한 16비트 슬라이스 ID와 비교하면 16비트가 65536개의 숫자를 나타낼 수 있기 때문에 여전히 공간이 많습니다. 어느 시점에서 8개의 MySQL 데이터베이스 인스턴스를 실행하기 위해 8개의 머신을 추가로 제공하고 4096에서 8192까지의 슬라이싱 데이터베이스를 제공하면 새 데이터는 이 범위의 슬라이싱 데이터베이스에만 저장됩니다. 16개의 병렬 컴퓨팅 데이터베이스가 있으며 서비스 역량은 필연적으로 향상될 것입니다.
마지막 방법은 슬라이싱 데이터베이스 호스트를 새로운 슬라이싱 호스트로 마이그레이션(로컬 슬라이싱 확장)하여 기능을 향상시키는 것입니다. 예를 들어 이전 예제의 MySQL001A 슬라이싱 호스트(0부터 511까지의 슬라이싱 데이터베이스가 있음)를 2개의 슬라이싱 호스트로 확장하고 배포하려고 합니다. 우리가 설계한 대로 새로운 마스터-마스터 상호 백업 호스트 쌍을 새로운 슬라이스 호스트(MySQL009A 및 B로 명명)로 생성하고 MySQL001A에서 데이터를 완전히 복사합니다.
데이터 복사가 완료된 후 슬라이싱 구성을 수정합니다. MySQL001A는 0부터 255까지의 슬라이싱 데이터베이스만 담당하고, MySQL009A는 256부터 511까지의 슬라이싱 데이터베이스만 담당합니다. 이제 두 호스트는 각각 이전 호스트가 담당했던 작업의 절반만 담당하게 되었고, 서비스 능력도 향상되었습니다.
일부 기능 설명
기존 시스템에서 생성된 비즈니스 개체 데이터의 경우 설계에 따라 비즈니스 개체가 새 시스템에서 UUID를 생성해야 합니다. 어떤 슬라이스 데이터베이스)는 귀하에게 달려 있습니다. (주석: 슬라이스 데이터베이스에 오래된 데이터의 배포를 계획할 수 있습니다.) 그러나 이를 슬라이스 데이터베이스에 넣을 때 레코드를 삽입할 때만 데이터베이스는 삽입된 객체의 로컬 ID를 반환합니다. 객체를 구성할 수 있습니다.
(주석: 마이그레이션 시 UUID를 통해 비즈니스 객체 간의 관계 설정을 고려해야 합니다.)
이미 많은 양의 데이터가 있는 데이터베이스 테이블에 대해서는 테이블 구조 수정 명령(ALTER)을 사용했습니다. a 필드 같은 것들은 매우 길고 고통스러운 과정입니다. 우리의 설계는 MySQL에서 (데이터가 이미 사용 가능한 경우) ALTER 수준 명령을 사용하지 않는 것입니다. 우리 비즈니스 시스템인 Pinterest에서 마지막으로 사용한 ALTER 문은 약 3년 전이었습니다. 객체 테이블의 객체에 대해 객체 속성 필드를 추가해야 하는 경우 이를 객체 데이터의 JSON Blob 필드에 추가합니다. 새 개체의 속성에 대한 기본값을 설정할 수 있습니다. 이전 개체의 데이터에 액세스할 때 이전 개체에 새 속성이 없으면 새 속성 기본값을 해당 개체에 추가할 수 있습니다. 관계형 매핑 테이블의 경우 필요에 맞게 새 관계형 매핑 테이블을 생성하면 됩니다. 당신은 이것을 모두 알고 있습니다! 시스템을 시작해보세요!
모드 데이터베이스 슬라이싱
모드 샤드의 이름은 Mod Squad와 같지만 사실은 전혀 다릅니다.
일부 비즈니스 개체는 ID가 아닌 쿼리를 통해 액세스해야 합니다. (번역: 이 ID는 이전 디자인 설명의 64비트 UUID를 나타냅니다.) 예를 들어 피너가 Facebook에 등록된 계정으로 비즈니스 플랫폼에 등록하고 로그인하는 경우입니다. 피너의 ID와 Facebook ID를 매핑해야 합니다. 우리 시스템의 경우 페이스북 ID는 일련의 이진수입니다. (주석: 이는 우리 시스템 플랫폼의 디자인처럼 다른 플랫폼의 ID를 해체할 수 없으며 슬라이스를 디자인하는 방법에 대해 이야기할 수도 없다는 의미입니다. 단지 저장하고 우리 ID와 매핑되도록 디자인하면 됩니다.) 저장하려면 슬라이스 데이터베이스에도 각각 저장해야 합니다. 우리는 이것을 모드 샤드라고 부릅니다. 다른 예로는 IP 주소, 사용자 이름, 사용자 이메일이 있습니다.
模转数据切片(mod shard)类似前述我们业务系统的数据切片设计。但是,你需要按照其输入的原样进行查询。如何确定其切片位置,需要用到哈希和模数运算。哈希函数将任意字串转换成定长数值,而模数设为系统已有切片数据库的切片数量,取模后,其必然落在某个切片数据库上。结果是其数据将保存在已有切片数据库上。举例:
shard = md5(“1.2.3.4") % 4096
(译注:mod shard 这个词,我网上找遍了,试图找到一个较准确权威的中文翻译!无果,因为 mod 这个词有几种意思,最近的是 module 模块、模组,同时它也是模运算符(%)。我根据原文意思,翻译为 模转 。或可翻译为 模式,但个人感觉意思模糊。不当之处,请指正。另,原作者举的例子是以 IP 地址举例的,哈希使用的是 md5,相比其它,虽老但性能最好)
在这个例子中分片是 1524。 我们维护一个类似于 ID 分片的配置文件:
[{“range”: (0, 511), “master”: “msdb001a”, “slave”: “msdb001b”}, {“range”: (512, 1023), “master”: “msdb002a”, “slave”: “msdb002b”}, {“range”: (1024, 1535), “master”: “msdb003a”, “slave”: “msdb003b”}, …]
因此,为了找到 IP 为 1.2.3.4 的数据,我们将这样做:
conn = MySQLdb.connect(host=”msdb003a”) conn.execute(“SELECT data FROM msdb001a.ip_data WHERE ip='1.2.3.4'”)
你失去了一些分片好的属性,例如空间位置。你必须从一开始就设置分片的密钥(它不会为你制作密钥)。最好使用不变的 id 来表示系统中的对象。这样,当用户更改其用户名时,您就不必更新许多引用。
最后的提醒
这个系统作为 Pinterest 的数据支撑已良好运行了 3.5 年,现在看来还会继续运行下去。设计实现这样的系统是直观、容易的。但是让它运行起来,尤其迁移旧数据却太不易了。若您的业务平台面临着急速增长的痛苦且您想切片自己的数据库。建议您考虑建立一个后端集群服务器(优先建议 pyres)脚本化您迁移旧数据到切片数据库的逻辑,自动化处理。我保证无论您想得多周到,多努力,您一定会丢数据或丢失数据之间的关联。我真的恨死隐藏在复杂数据关系中的那些捣蛋鬼。因此,您需要不断地迁移,修正,再迁移... 你需要极大的耐心和努力。直到您确定您不再需要为了旧数据迁移而往您的切片数据库中再操作数据为止。
这个系统的设计为了数据的分布切片,已尽最大的努力做到最好。它本身不能提供给你数据库事务 ACID 四要素中的 Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)哇呕!听起来很坏呀,不用担心。您可能不能利用数据库本身提供的功能很好地保证这些。但是,我提醒您,一切尽在您的掌握中,您只是让它运行起来,满足您的需要就好。设计简单直接就是王道,(译注:也许需要您做许多底层工作,但一切都在您的控制之中)主要是它运行起来超快! 如果您担心 A(原子性)、I(隔离性)和 C(一致性),写信给我,我有成堆的经验让您克服这些问题。
还有最后的问题,如何灾难恢复,啊哈? 我们创建另外的服务去维护着切片数据库,我们保存切片配置在 ZooKeeper 上。当单点主服务器宕掉时,我们有脚本自动地提升主服务器对应的从服务器立即顶上。之后,以最快的速度运行新机器顶上从服务器的缺。直至今日,我们从未使用过类似自动灾难恢复的服务。
推荐教程:《MySQL教程》
위 내용은 MySQL이 샤딩을 사용하여 500억 개의 데이터 저장 문제를 해결하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!