Memcached는 동적 시스템에서 데이터베이스 부하를 줄이고 성능을 향상시키기 위해 danga.com(LiveJournal을 운영하는 기술팀)에서 개발한 분산 메모리 개체 캐싱 시스템입니다. 이에 관해서는 많은 분들이 사용해 보신 것으로 생각됩니다. 이 글의 목적은 memcached의 구현과 코드 분석을 통해 이 뛰어난 오픈 소스 소프트웨어에 대해 더 깊이 이해하고 필요에 따라 더욱 최적화하는 것입니다. 마지막으로 BSM_Memcache 확장 분석을 통해 memcached 사용에 대한 이해를 심화시키겠습니다.
이 기사의 일부 내용에는 더 나은 수학적 기초가 필요할 수 있습니다.
◎Memcached란 무엇입니까?
이 문제에 대해 자세히 설명하기 전에 먼저 Memcached가 "아님"이 무엇인지 이해해야 합니다. 많은 사람들이 이를 SharedMemory와 같은 저장 매체로 사용합니다. memcached는 동일한 "Key=>Value" 방법을 사용하여 데이터를 구성하지만 공유 메모리 및 APC와 같은 로컬 캐시와는 매우 다릅니다. Memcached는 분산되어 있으므로 로컬이 아닙니다. 네트워크 연결을 기반으로 서비스를 완료합니다(물론 localhost를 사용할 수도 있음). 애플리케이션 독립적인 프로그램 또는 데몬 프로세스(데몬 모드)입니다.
Memcached는 libevent 라이브러리를 사용하여 네트워크 연결 서비스를 구현하고 이론적으로 무제한의 연결을 처리할 수 있습니다. 그러나 Apache와 달리 안정적인 연속 연결을 지향하는 경우가 많기 때문에 실제 동시성 기능이 제한됩니다. 보수적인 상황에서 memcached의 최대 동시 연결 수는 200이며 이는 Linux 스레드 기능과 관련이 있습니다. 이 값은 조정될 수 있습니다. libevent에 대한 내용은 관련 문서를 참고하시기 바랍니다. Memcached 메모리 사용량도 APC와 다릅니다. APC는 공유 메모리와 MMAP을 기반으로 하며, Memcachd는 자체 메모리 할당 알고리즘과 관리 방법을 가지고 있으며 공유 메모리와는 아무런 관련이 없으며 일반적으로 각 memcached 프로세스는 2GB의 메모리 공간을 관리할 수 있습니다. 더 많은 공간이 필요하면 프로세스 수가 늘어날 수 있습니다.
◎Memcached는 어떤 경우에 적합합니까?
Memcached가 남용되는 경우가 많으며 이로 인해 필연적으로 이에 대한 불만이 제기됩니다. 포럼에 "효율성을 높이는 방법"과 유사한 글을 올리는 사람들을 자주 보았는데, "memcached를 사용하라"는 답변이 있는데, 어떻게 사용하는지, 어디에 사용하는지, 어떤 용도로 사용하는지에 대해서는 문장이 없습니다. Memcached는 만병통치약이 아니며 모든 상황에 적합하지도 않습니다.
Memcached는 "분산" 메모리 객체 캐싱 시스템입니다. 즉, "분산"할 필요가 없거나, 공유할 필요가 없거나, 서버가 하나만 있을 정도로 작은 애플리케이션의 경우 memcached는 그렇지 않습니다. 오히려 네트워크 연결에도 리소스가 필요하므로 시스템 효율성도 저하됩니다. 심지어 UNIX 로컬 연결에도 마찬가지입니다. 이전 테스트 데이터에 따르면 memcached 로컬 읽기 및 쓰기 속도는 직접 PHP 메모리 배열보다 수십 배 느린 반면 APC 및 공유 메모리 방법은 직접 배열과 유사합니다. 로컬 수준의 캐시일 경우 memcached를 사용하는 것은 매우 비경제적이라는 것을 알 수 있습니다.
Memcached는 데이터베이스 프런트엔드 캐시로 사용되는 경우가 많습니다. 데이터베이스에 비해 SQL 구문 분석, 디스크 작업 및 기타 오버헤드가 훨씬 적고 메모리를 사용하여 데이터를 관리하기 때문에 데이터베이스를 직접 읽는 것보다 더 나은 성능을 제공할 수 있으므로 대규모 시스템에서는 동일한 데이터에 액세스하기가 매우 어렵습니다. . memcached는 데이터베이스 부담을 크게 줄이고 시스템 실행 효율성을 향상시키는 경우가 많습니다. 또한 memcached는 서버 간 데이터 공유를 위한 저장 매체로 자주 사용됩니다. 예를 들어 SSO 시스템에서 시스템의 Single Sign-On 상태를 저장하는 데이터는 memcached에 저장되어 여러 애플리케이션에서 공유될 수 있습니다.
memcached는 메모리를 사용하여 데이터를 관리하므로 휘발성입니다. 서버가 다시 시작되거나 memcached 프로세스가 종료되면 데이터가 손실되므로 memcached를 사용하여 데이터를 유지할 수 없습니다. 많은 사람들은 memcached의 성능이 메모리와 하드 디스크의 비교만큼 좋다고 오해합니다. 실제로 memcached는 메모리를 사용한다고 해서 수백, 수천 번의 읽기 및 쓰기 속도 향상을 얻을 수는 없습니다. 실제 병목 현상은 네트워크에 있습니다. 메모리 사용과 관련된 연결은 디스크 데이터베이스 시스템에 비해 매우 "가벼운" 장점이며, 과도한 오버헤드가 없고 직접 읽고 쓰기 방식이므로 매우 많은 양을 쉽게 처리할 수 있습니다. 데이터 교환의 경우 2기가비트 네트워크 대역폭이 있는 경우가 많습니다. 모두 완전히 로드되고 memcached 프로세스 자체는 CPU 리소스를 많이 차지하지 않습니다.
◎Memcached 작동 방식
다음 섹션에서는 독자가 Memcached 소스 코드 사본을 준비하는 것이 가장 좋습니다.
Memcached는 전통적인 네트워크 서비스 프로그램으로, 시작할 때 -d 매개변수를 사용하면 데몬 프로세스로 실행됩니다. 데몬 프로세스 생성은 daemon.c에 의해 완료됩니다. 이 프로그램에는 매우 간단한 데몬 기능이 하나만 있습니다(특별한 지침이 제공되지 않으면 코드는 1.2.1을 따릅니다).
코드:[클립보드에 복사]#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int
데몬(nochdir, noclose)
int nochdir, noclose;
{
int fd;
스위치(포크()) {
사례-1:
반환(-1);
사례 0:
부서지다;
기본:
_exit(0);
}
if (setsid() == -1)
반환 (-1);
if (!nochdir)
(void)chdir("/");
if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
(void)dup2(fd, STDIN_FILENO);
(void)dup2(fd, STDOUT_FILENO);
(void)dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO)
(무효)닫기(fd);
}
반환 (0);
}
이 함수가 전체 프로세스를 분기한 후 상위 프로세스가 종료되고 STDIN, STDOUT 및 STDERR을 빈 장치에 재배치하고 데몬이 성공적으로 설정됩니다.
Memcached 자체의 시작 프로세스는 memcached.c의 주요 함수에서 다음과 같습니다.
1. 초기화 매개변수를 설정하기 위해 settings_init()를 호출합니다.
2. 시작 명령에서 매개변수를 읽어 설정 값을 설정합니다.
3. LIMIT 매개변수 설정
4. 네트워크 소켓 모니터링 시작(소켓 경로가 아닌 경우) (UDP 모드는 1.2 이후 지원)
5. 사용자 ID 확인(Memcached에서는 루트 ID 시작을 허용하지 않음)
6. 소켓 경로가 존재하는 경우 UNIX 로컬 연결(Sock 파이프)을 엽니다.
7. -d 모드로 시작한 경우 데몬 프로세스를 생성합니다(위와 같이 데몬 함수 호출).
8. 아이템, 이벤트, 상태정보, 해시, 커넥션, 슬래브 초기화
9. 설정에서 관리가 적용되면 버킷 배열을 생성합니다.
10. 메모리 페이지를 잠가야 하는지 확인하세요.
11. 신호 초기화, 연결, 대기열 삭제
12. 데몬 모드인 경우 프로세스 프로세스 ID
13. 이벤트가 시작되고 시작 프로세스가 종료되며 주 함수가 루프에 들어갑니다.
데몬 모드에서는 stderr이 블랙홀로 전달되었기 때문에 실행 중에 눈에 띄는 오류 메시지가 피드백되지 않습니다.
memcached.c의 주요 루프 함수는 Drive_machine입니다. 들어오는 매개변수는 현재 연결을 가리키는 구조체 포인터이며, 작업은 상태 멤버의 상태에 따라 결정됩니다.
Memcached는 일련의 사용자 정의 프로토콜을 사용하여 데이터 교환을 완료합니다. 해당 프로토콜 문서는 http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt 를 참조할 수 있습니다.
API에서 개행 기호는 로 통일됩니다
.◎Memcached의 메모리 관리 방식
Memcached는 효율성을 높이기 위해 데이터를 쓸 때마다 malloc을 사용하는 것이 아니라 사전 적용(pre-application) 및 그룹화(grouping) 방식을 사용하여 메모리 공간을 관리하는 매우 독특한 메모리 관리 방식을 가지고 있습니다. . , 데이터를 삭제할 때 포인터를 해제합니다. Memcached는 슬랩->청크 구성 방법을 사용하여 메모리를 관리합니다.
1.1과 1.2의 slabs.c에는 슬래브 공간 분할 알고리즘에 약간의 차이점이 있는데, 이에 대해서는 나중에 별도로 소개하겠습니다.
슬랩은 메모리 블록으로 이해될 수 있습니다. 슬랩은 memcached가 한 번에 메모리를 적용하는 가장 작은 단위입니다. memcached에서 슬랩의 기본 크기는 1048576바이트(1MB)이므로 memcached는 전체 MB 메모리를 사용합니다. 각 슬래브는 여러 개의 청크로 나누어지고, 각 청크는 항목을 저장합니다. 각 항목에는 항목 구조, 키 및 값도 포함됩니다(memcached의 값은 문자열일 뿐입니다). 슬랩은 자신의 ID에 따라 연결된 목록을 형성하며, 이 연결된 목록은 ID에 따라 슬랩 클래스 배열에 매달려 있습니다. 전체 구조는 2차원 배열처럼 보입니다. slabclass의 길이는 1.1에서는 21이고 1.2에서는 200입니다.
slab의 초기 청크 크기는 1.1에서는 1바이트, 1.2에서는 80바이트입니다. 1.2에는 인수 값이 있으며 기본값은 1.25
입니다. 1.1에서는 청크 크기가 초기 크기 * 2^n으로 표시됩니다. classid, 즉: ID가 0인 슬래브의 청크 크기는 1바이트이고, ID가 1인 슬래브의 청크 크기는 2바이트이며, ID가 2인 슬래브의 청크 크기는 4바이트입니다... ID가 있는 슬래브는 20의 청크 크기는 4바이트입니다. 이는 ID가 20인 슬래브에 청크가 하나만 있음을 의미합니다.
CODE:[클립보드에 복사]void slabs_init(size_t 제한) {
나는 int;
int 크기=1;
mem_limit = 제한;
for(i=0; i<=POWER_LARGEST; i++, 크기*=2) {
slabclass[i].size = 크기;
slabclass[i].perslab = POWER_BLOCK / 크기;
slabclass[i].slots = 0;
slabclass[i].sl_curr = slabclass[i].sl_total = slabclass[i].slabs = 0;
slabclass[i].end_page_ptr = 0;
slabclass[i].end_page_free = 0;
slabclass[i].slab_list = 0;
slabclass[i].list_size = 0;
slabclass[i].killing = 0;
}
/* 테스트 스위트의 경우: 우리가 이미 malloc한 양을 속입니다 */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
if (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
/* 환경 변수가 없으면 기본적으로 슬랩을 미리 할당합니다.
테스트를 위해 0이 아닌 값으로 설정됨 */
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
if (!pre_alloc || atoi(pre_alloc)) {
slabs_preallocate(제한 / POWER_BLOCK);
}
}
}
1.2에서는 청크 크기가 초기 크기 * f^n으로 표현되며, f는 memcached.c에 정의된 인수이고, n은 classid입니다. 동시에 인수이므로 201개의 헤드를 모두 초기화할 필요는 없습니다. 변수이고 초기화는 다음으로만 반복됩니다. 계산된 크기는 슬래브 크기의 절반에 도달하고 id1에서 시작합니다. 즉, ID가 1인 슬래브, 각 청크 크기는 80바이트, ID가 2인 슬래브, 각 청크 크기는 80*입니다. f, id는 3개의 슬래브이고 각 청크 크기는 80*f^2이며 초기화 크기에는 n바이트 정렬을 보장하기 위한 수정 값 CHUNK_ALIGN_BYTES가 있습니다(결과가 CHUNK_ALIGN_BYTES의 정수배임을 보장). 이러한 방식으로 표준 환경에서는 memcached1.2가 id40으로 초기화됩니다. 이 슬래브의 각 청크 크기는 504692이며 각 슬래브에는 두 개의 청크가 있습니다. 마지막으로 slab_init 함수는 전체 블록인 id41을 끝에 추가합니다. 즉, 이 슬랩에는 1MB 청크가 하나만 있습니다.
CODE:[클립보드에 복사]void slabs_init(size_t 제한, 이중 요소) {
int i = POWER_SMALLEST - 1;
unsigned int size = sizeof(item) + settings.chunk_size;
/* 인수 2.0은 기본 memcached 동작을 사용함을 의미합니다. */
if (인수 == 2.0 && 크기 < 128)
크기 = 128;
mem_limit = 제한;
memset(slabclass, 0, sizeof(slabclass));
while (++i < POWER_LARGEST && size <= POWER_BLOCK / 2)
/* 항목이 항상 n바이트로 정렬되어 있는지 확인하세요 */
if (크기 % CHUNK_ALIGN_BYTES)
크기 += CHUNK_ALIGN_BYTES - (크기 % CHUNK_ALIGN_BYTES)
slabclass[i].size = 크기;
slabclass[i].perslab = POWER_BLOCK / slabclass[i].size;
크기 *= 인자;
if (settings.verbose > 1) {
fprintf(stderr, "슬랩 클래스 %3d: 청크 크기 %6d perslab %5dn",
i, slabclass[i].size, slabclass[i].perslab);
}
}
power_largest = i;
slabclass[power_largest].size = POWER_BLOCK;
slabclass[power_largest].perslab = 1;
/* 테스트 스위트의 경우: 우리가 이미 malloc한 양을 속입니다 */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
if (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
#ifndef DONT_PREALLOC_SLABS
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
if (!pre_alloc || atoi(pre_alloc)) {
slabs_preallocate(제한 / POWER_BLOCK);
}
}
#endif
}
위에서 볼 수 있듯이 memcached의 메모리 할당은 중복됩니다. 슬랩이 소유한 청크 크기로 나누어지지 않으면 슬랩 끝에 남은 공간이 삭제됩니다. 예를 들어 id40에서는 두 개의 청크를 차지합니다. 1009384바이트, 이 슬랩은 총 1MB이므로 39192바이트가 낭비됩니다.
Memcached는 항목 길이를 통해 슬래브의 클래스 ID를 빠르게 찾기 위해 이 방법을 사용합니다. 예를 들어 항목 길이가 1.2에서는 300바이트라는 점에서 해시와 비슷합니다. 위의 계산 방법에 따르면 id6의 청크 크기는 252바이트, id7의 청크 크기는 316바이트, id8의 청크 크기는 396바이트이기 때문에 id7의 슬래브에 저장되어야 함을 알 수 있습니다. 이는 252바이트부터 316바이트까지의 모든 항목이 id7에 저장되어야 함을 의미합니다. 마찬가지로 1.1에서는 256에서 512 사이로 계산할 수 있으며, Chunk_size가 512(32비트 시스템)인 id9에 배치되어야 합니다.
Memcached가 초기화되면 slab이 초기화됩니다(앞에서 볼 수 있듯이 slabs_init()는 기본 함수에서 호출됩니다). slabs_init()에서 DONT_PREALLOC_SLABS 상수를 확인합니다. 이것이 정의되어 있지 않으면 미리 할당된 메모리를 사용하여 slab을 초기화하므로 정의된 모든 slabclass 중 각 ID에 대해 slab이 생성됩니다. 이는 1.2가 기본 환경에서 프로세스를 시작한 후 41MB의 슬래브 공간을 할당한다는 것을 의미합니다. 이 과정에서 memcached의 두 번째 메모리 중복이 발생합니다. id가 전혀 사용되지 않았을 수도 있지만 A이기도 하기 때문입니다. slab은 기본적으로 적용되며, 각 slab은 1MB의 메모리를 사용하며
, 이 ID로 새 항목을 삽입해야 할 경우 새 slab을 신청할 때 다시 적용됩니다. slab, 해당 ID slab 연결 목록은 기하급수적으로 증가합니다.row_slab_list 함수에서 이 체인의 길이는 1에서 2로, 4에서 8로 변경됩니다.
CODE:[클립보드에 복사]static int Growth_slab_list (unsigned int id) {
slabclass_t *p = &slabclass[id];
if (p->slabs == p->list_size) {
size_t new_size = p->list_size ? p->list_size * 2 : 16;
void *new_list = realloc(p->slab_list, new_size*sizeof(void*));
if (new_list == 0) 0을 반환합니다.
p->list_size = new_size;
p->slab_list = new_list;
}
1을 반환합니다.
}
항목을 찾을 때 slabs_clsid 함수를 사용하는데, 들어오는 매개 변수는 항목 크기이고 반환 값은 classid입니다. 이 과정에서 항목을 저장하는 과정에서 memcached의 세 번째 메모리 중복이 발생하는 것을 알 수 있습니다. 항목은 항상 청크 크기보다 작거나 같습니다. 항목이 청크 크기보다 작으면 공간이 다시 낭비됩니다.
◎Memcached의 NewHash 알고리즘
Memcached의 항목 저장은 대규모 해시 테이블을 기반으로 합니다. 실제 주소는 슬래브의 청크 오프셋이지만 위치는 Primary_hashtable에 있는 키 해싱 결과에 따라 달라집니다. 모든 해시 및 항목 작업은 assoc.c 및 items.c에 정의됩니다.
Memcached는 매우 효과적이고 효율적인 NewHash라는 알고리즘을 사용합니다. 1.1과 1.2의 NewHash에는 몇 가지 차이점이 있지만 주요 구현 방법은 여전히 동일합니다. 1.2의 해시 함수는 구성 및 최적화되었으며 적응성이 더 좋습니다.
NewHash 프로토타입 참조: http://burtleburtle.net/bob/hash/evahash.html . 수학자들은 항상 좀 이상해요 ㅎㅎ~
변환을 용이하게 하기 위해 u4와 u1이라는 두 가지 데이터 유형이 정의됩니다. u4는 unsigned long 정수이고 u1은 unsigned char(0-255)입니다.
특정 코드에 대해서는 1.1 및 1.2 소스 코드 패키지를 참조하세요.
여기서도 해시테이블 길이에 주의하세요. 1.1과 1.2 사이에는 HASHPOWER 상수가 20으로 정의되어 있으며, 해시테이블 테이블 길이는 해시사이즈(HASHPOWER)로 4MB입니다. 1은 n 비트만큼 오른쪽으로 이동합니다. 1.2에서는 변수 16입니다. 즉, 해시 테이블 테이블 길이는 65536입니다.
CODE:[클립보드에 복사]typedef unsigned long int ub4 /* 부호 없는 4바이트 수량 */
typedef unsigned char ub1; /* 부호 없는 1바이트 수량 */
#define hashsize(n) ((ub4)1<<(n))
#define hashmask(n) (해시크기(n)-1)
assoc_init()에서 해당 해시 작업에는 항목의 읽기 및 쓰기 작업에 해당하는 assoc_find(), assoc_expand(), assoc_move_next_bucket(), assoc_insert(), assoc_delete()가 포함됩니다. 그 중 assoc_find() 는 키와 키 길이를 기준으로 해당 항목 주소를 찾아주는 함수입니다. (참고로 C에서는 함수 내부에서 strlen을 하는 대신 문자열과 문자열 길이를 동시에 직접 전달하는 경우가 많습니다.) ), 반환되는 것은 항목 구조 포인터이며, 해당 데이터 주소는 슬래브의 청크에 있습니다.
과
같이 정의된 여러 부분이 포함됩니다.
nkey: 키 길이
flags: 사용자 정의 플래그(실제로 이 플래그는 memcached에서 활성화되지 않습니다)
nbytes: 값 길이(개행 기호 rn 포함)
접미사: 접미사 버퍼
nsuffix: 전체 항목의 접미사
길이는 키 길이 + 값 길이 + 접미사 길이 + 항목 구조 크기(32바이트)입니다. 항목 작업은 이 길이를 기반으로 슬랩의 클래스 ID를 계산합니다.
해시 테이블의 각 버킷은 이중 연결 목록과 함께 중단됩니다. item_init() 중에 머리, 꼬리 및 크기의 세 배열이 0으로 초기화되었습니다. 이 세 배열의 크기는 상수 LARGEST_ID입니다(기본값은 255, 이 값은 요소로 수정해야 함) item_assoc()이 호출될 때마다 먼저 슬랩에서 사용 가능한 청크를 얻으려고 시도합니다. 사용 가능한 청크가 없으면 연결된 목록을 50번 스캔하여 해당 청크를 가져옵니다. LRU 항목에 의해 시작되고, 연결을 해제한 후 연결 목록에 삽입할 항목을 삽입합니다.
아이템의 refcount 멤버에 주목하세요. 항목이 연결 해제된 후에는 연결 목록에서만 제거되며 즉시 해제되지는 않습니다. 삭제 대기열(item_unlink_q() 함수)에 배치됩니다.
항목은 제거, 업데이트 및 교체를 포함한 일부 읽기 및 쓰기 작업에 해당합니다. 물론 가장 중요한 작업은 할당 작업입니다.
항목의 또 다른 특징은 만료 시간이 있다는 것인데, 이는 memcached의 매우 유용한 기능입니다. 많은 애플리케이션이 세션 저장, 작업 잠금 등과 같은 memcached의 항목 만료에 의존합니다. item_flush_expired() 함수는 테이블의 항목을 검색하고 만료된 항목에 대해 연결 해제 작업을 수행합니다. 물론 이는 재활용 작업일 뿐이며 다음을 가져올 때도 시간 판단이 필요합니다.
CODE:[클립보드에 복사]/* 가장 오래된_live 설정보다 최신 항목이 만료됩니다. */
무효 item_flush_expired() {
나는 int;
항목 *iter, *다음;
if (!settings.oldest_live)
반품;
for (i = 0; i < LARGEST_ID; i++) {
/* LRU는 시간 내림차순으로 정렬되며 항목의 타임스탬프는
*는 마지막 액세스 시간보다 최신이 아니므로 걷기만 하면 됩니다.
* 가장 오래된_실시간보다 오래된 항목에 도달할 때까지 돌아갑니다.
*old_live를 확인하면 나머지 항목이 자동으로 만료됩니다.
*/
for (iter =heads[i]; iter != NULL; iter = next) {
if (iter->time >= settings.oldest_live) {
다음 = 반복->다음;
if ((iter->it_flags & ITEM_SLABBED) == 0) {
item_unlink(iter);
}
} 또 다른 {
/* 첫 번째 이전 항목에 도달했습니다. 다음 대기열로 계속 진행합니다. */
부서지다;
}
}
}
}
CODE:[클립보드에 복사]/* 지연 만료/삭제 논리를 수행하는 assoc_find 주위의 래퍼 */
item *get_item_notedeleted(char *key, size_t nkey, int *delete_locked) {
item *it = assoc_find(key, nkey);
if (delete_locked) *delete_locked = 0;
if (it && (it->it_flags & ITEM_DELETED)) {
/* 삭제 잠금 상태로 플래그가 지정되어 있는지 살펴보겠습니다.
기한이 지났고 5초 delete_timer가 아직 완료되지 않았습니다.
아직 이해하지 못했어요... */
if (!item_delete_lock_over(it)) {
if (delete_locked) *delete_locked = 1;
그것은 = 0;
}
}
if (it && settings.oldest_live && settings.oldest_live <= 현재_시간 &&
그것->시간 <= settings.oldest_live) {
item_unlink(it);
그것은 = 0;
}
if (it && it->exptime && it->exptime <= current_time) {
item_unlink(it);
그것은 = 0;
}
돌려보내라;
}
Memcached의 메모리 관리 방법은 매우 정교하고 효율적이며 시스템 메모리의 직접 할당 수를 크게 줄이고 기능 오버헤드와 메모리 조각화 가능성을 줄입니다. 이 방법은 일부 중복 낭비를 유발하지만 대규모 시스템에서는 이러한 낭비가 사소한 일입니다. 응용 프로그램.
◎Memcached의 이론적 매개변수 계산 방법에는
memcached 작업에 영향을 미치는 여러 매개변수가 있습니다.
상수 REALTIME_MAXDELTA 60*60*24*30
conn_init()의
최대 만료 시간은 30일
무료 합계(=200)입니다.
최대 동시 연결 수
상수 KEY_MAX_LENGTH 250
최대 키 길이
설정.요소(=1.25)
settings.maxconns(=1024)
의 단계 크기에 영향을 미칩니다.
최대 소프트 연결
설정.chunk_size (=48)
id1에서 청크 길이(1.2)를 생성하는 데 사용되는 보수적으로 추정된 키+값 길이입니다. id1의 청크 길이는 이 값에 항목 구조의 길이(32)를 더한 값(기본값 80바이트)과 같습니다.
상수 POWER_SMALLEST 1
최소 분류 ID(1.2)
상수 POWER_LARGEST 200
최대 분류 ID(1.2)
상수 POWER_BLOCK 1048576
기본 슬래브 크기
상수 CHUNK_ALIGN_BYTES (sizeof(void *))
범위를 벗어나는 것을 방지하려면 청크 크기가 이 값의 정수 배수인지 확인하세요(void *의 길이는 시스템마다 다르며 표준 32비트 시스템에서는 4입니다).
상수 ITEM_UPDATE_INTERVAL 60
큐 새로 고침 간격
상수 LARGEST_ID 255
연결된 목록의 최대 항목 수(이 값은 가장 큰 분류 ID보다 작을 수 없음)
가변 해시파워(1.1의 상수 HASHPOWER)
해시테이블의 크기 결정
위에 소개된 내용과 매개변수 설정을 기반으로 다음과 같은 결과를 계산할 수 있습니다.
1. memcached에 저장할 수 있는 항목 수에는 소프트웨어 상한선이 없습니다. 잘못된.
2. NewHash 알고리즘에 균일한 충돌이 있다고 가정하면 항목을 찾는 주기 수는 총 항목 수를 해시 테이블 크기(해시력으로 결정됨)로 나눈 값으로 선형입니다.
3. Memcached는 최대 허용 항목을 1MB로 제한하며, 1MB보다 큰 데이터는 무시됩니다.
4. Memcached의 공간 활용도는 데이터 특성과 밀접한 관계가 있으며, DONT_PREALLOC_SLABS 상수와도 관련이 있습니다. 최악의 경우 198개의 슬래브가 낭비됩니다(모든 항목이 하나의 슬래브에 집중되어 있으며 199개의 ID가 모두 할당됩니다).
◎Memcached의 고정 길이 최적화
위의 설명을 바탕으로 Memcached에 대해 좀 더 깊이 이해하게 되었습니다. 심층적인 이해를 바탕으로만 최적화할 수 있습니다.
Memcached 자체는 데이터 특성에 따라 "공용 지향" 설계라고 할 수 있습니다. 그러나 일반적으로 우리의 데이터는 그렇게 "보편적"이지 않습니다. 즉, 데이터 길이가 여러 영역에 집중되어 있습니다(예: 사용자 세션 저장). 또 다른 더 극단적인 상태는 동일한 길이의 데이터(예: 고정 길이 키 값, 대부분 고정 길이 데이터)입니다. 액세스, 온라인 통계 또는 실행 잠금에서 볼 수 있음)
여기서는 고정 길이 데이터에 대한 최적화 솔루션(1.2)을 주로 연구합니다. 중앙 분산 가변 길이 데이터는 참고용이며 구현이 쉽습니다.
고정 길이 데이터를 해결하기 위해 가장 먼저 해결해야 할 것은 슬래브 할당 문제입니다. 먼저 확인해야 할 것은 활용도를 극대화하기 위해 서로 다른 청크 길이를 가진 슬래브가 그렇게 많이 필요하지 않다는 것입니다. 자원의 경우 청크와 항목의 길이가 동일한 것이 가장 좋으므로 먼저 항목 길이를 계산해야 합니다.
이전에도 항목 길이를 계산하는 알고리즘이 있었습니다. 문자열 길이 외에 항목 구조의 길이인 32바이트를 추가해야 한다는 점에 유의해야 합니다.
동일한 길이의 데이터 200바이트를 저장해야 한다고 계산했다고 가정해 보겠습니다.
다음 단계는 슬래브의 classid와 청크 길이 간의 관계를 수정하는 것입니다. 원본 버전에는 청크 길이와 classid 사이에 상응하는 관계가 있습니다. 이제 모든 청크가 200바이트로 설정되면 이 관계는 존재하지 않습니다. 한 가지 방법은 전체 저장소 구조에 대해 고정 ID만 사용하는 것입니다. 즉, 199개 슬롯 중 1개만 사용하는 것입니다. 이 조건에서는 추가적인 사전 할당 낭비를 방지하기 위해 DONT_PREALLOC_SLABS를 정의해야 합니다. 또 다른 방법은 해시 관계를 설정하여 항목의 클래스 ID를 결정하는 것입니다. 키의 NewHash 결과와 같은 변수 데이터를 사용하거나 키를 기반으로 직접 해시를 수행할 수 있습니다. 고정 길이 데이터의 키도 길이가 동일해야 합니다). 여기서는 단순화를 위해 첫 번째 방법을 선택합니다. 이 방법의 단점은 하나의 ID만 사용한다는 것입니다. 데이터 양이 매우 많으면 슬래브 체인이 매우 길어집니다. 하나의 체인) 통과 비용이 상대적으로 높습니다.
앞서 세 가지 유형의 공간 중복성을 도입했는데, 청크 길이를 항목 길이와 동일하게 설정하면 첫 번째 공간 낭비 문제가 해결되고, 사전에 공간을 신청하지 않으면 두 번째 공간 낭비 문제가 해결됩니다. )? 이 문제를 해결하려면 각 슬래브의 크기가 청크 길이의 정수배와 정확히 동일하도록 POWER_BLOCK 상수를 수정하여 슬래브를 n개의 청크로 나눌 수 있도록 해야 합니다. 이 값은 1MB에 가까워야 합니다. 너무 크면 중복이 발생합니다. 너무 작으면 할당이 너무 많아집니다. 200의 청크 길이에 따라 POWER_BLOCK 값을 1000000으로 선택합니다. 이렇게 하면 슬래브는 1048576이 아니라 100만 바이트가 됩니다. 세 가지 중복성 문제가 모두 해결되어 공간 활용도가 크게 향상됩니다.
고정된 값(예: 1)을 직접 반환하도록 slabs_clsid 함수를 수정합니다.
CODE:[클립보드에 복사]unsigned int slabs_clsid(size_t size) {
1을 반환합니다.
}
slabs_init 함수를 수정하고 모든 classid 속성을 생성하기 위해 반복되는 부분을 제거한 다음 slabclass[1]을 직접 추가합니다.
CODE:[클립보드에 복사]slabclass[1].size = 200; //청크당 200바이트
slabclass[1].perslab = 5000; //1000000/200
◎Memcached 클라이언트
Memcached는 서비스 프로그램으로 Memcached 서버에 해당 프로토콜에 따라 접속하여 서비스 프로세스에 명령을 보낸 후 위의 데이터를 연산할 수 있습니다. 사용 편의성을 위해 memcached에는 다양한 언어에 해당하는 많은 클라이언트 프로그램이 있으며 다양한 언어의 클라이언트가 있습니다. C 언어 기반에는 libmemcache 및 APR_Memcache가 포함되며 Perl 기반에는 Cache::Memcached가 포함됩니다. Python, Ruby, Java, C# 및 기타 언어도 지원됩니다. PHP에는 가장 많은 클라이언트가 있습니다. 두 가지 확장인 mcache와 PECL memcache뿐만 아니라 PHP에서 작성된 수많은 캡슐화 클래스도 있습니다. 다음은 PHP에서 memcached를 사용하는 방법에 대한 소개입니다.
mcache 확장은 libmemcache를 기반으로 다시 캡슐화됩니다. . libmemcache는 안정적인 버전을 출시하지 않았습니다. 현재 버전은 1.4.0-rc2이며 여기에서 찾을 수 있습니다. libmemcache의 아주 나쁜 특징은 stderr에 많은 오류 메시지를 쓴다는 것입니다. 일반적으로 lib로 사용하면 stderr는 대개 Apache의 오류 로그와 같은 다른 위치로 이동하며 libmemcache는 자살을 하게 되어 이상 현상이 발생할 수 있습니다. , 하지만 성능은 여전히 매우 좋습니다.
mcache 확장 프로그램이 1.2.0-beta10으로 마지막으로 업데이트되었습니다. 아마도 작성자는 업데이트를 중단했을 뿐만 아니라 웹사이트도 열 수 없었습니다(~_~). . 압축을 푼 후 설치 방법은 평소와 같습니다: phpize & 구성 & make & make install libmemcache를 먼저 설치하십시오. 이 확장 기능을 사용하는 방법은 간단합니다.
코드:[클립보드에 복사]<?php
$mc = memcache(); // memcache 연결 객체를 생성합니다. 여기서는 new가 사용되지 않습니다.
$mc->add_server('localhost', 11211); // 서비스 프로세스 추가
$mc->add_server('localhost', 11212); // 두 번째 서비스 프로세스 추가
$mc->set('key1', 'Hello') // key1 => Hello 작성
$mc->set('key2', 'World', 10); // key2 => World를 작성하고 10초 후에 만료됩니다.
$mc->set('arr1', array('Hello', 'World')) // 배열 작성
$key1 = $mc->get('key1'); // 'key1'의 값을 가져와서 $key1에 할당합니다.
$key2 = $mc->get('key2'); // 'key2' 값을 가져와 $key2에 할당하면 사용할 수 없습니다.
$arr1 = $mc->get('arr1'); // 'arr1' 배열을 가져옵니다.
$mc->delete('arr1') // 'arr1' 삭제
$mc->flush_all(); // 모든 데이터 삭제
$stats = $mc->stats(); // 서버 정보 가져오기
var_dump($stats); // 서버 정보는 배열입니다.
?>
이 확장의 장점은 여러 서비스 주소를 추가할 수 있기 때문에 쉽게 분산 스토리지와 로드 밸런싱을 구현할 수 있다는 점입니다. 데이터 저장 시 해시 결과를 기반으로 특정 서버에 위치하게 되는 것도 libmemcache의 기능입니다. . libmemcache는 CRC32, ELF 및 Perl 해시를 포함한 중앙 집중식 해싱 방법을 지원합니다.
PECL memcache는 PECL에서 출시한 확장 기능으로, 최신 버전은 2.1.0이며 pecl 웹사이트에서 다운로드할 수 있습니다. memcache 확장의 사용법은 일부 최신 PHP 매뉴얼에서 찾을 수 있습니다. 이는 mcache와 매우 유사합니다.
코드:[클립보드에 복사]<?php
$memcache = new Memcache;
$memcache->connect('localhost', 11211) 또는 die("연결할 수 없습니다.")
$version = $memcache->getVersion();
echo "서버 버전: ".$version."n"
$tmp_object = new stdClass;
$tmp_object->str_attr = '테스트';
$tmp_object->int_attr = 123;
$memcache->set('key', $tmp_object, false, 10) 또는 die("서버에 데이터를 저장하지 못했습니다.");
echo "캐시에 데이터 저장(데이터는 10초 후에 만료됨)n"
$get_result = $memcache->get('key');
echo "캐시 데이터:n"
)
;
이 확장은 PHP의 스트림을 사용하여 memcached 서버에 직접 연결하고 소켓을 통해 명령을 보냅니다. libmemcache만큼 완전하지도 않고 add_server와 같은 분산 작업도 지원하지 않지만, 다른 외부 프로그램에 의존하지 않기 때문에 호환성이 더 좋고 비교적 안정적입니다. 효율성 측면에서는 차이가 크지 않습니다.
또한 MemcacheClient.inc.php와 같은 많은 PHP 클래스가 있으며 phpclasses.org에서 찾을 수 있습니다. 이 클래스는 일반적으로 Perl 클라이언트 API를 다시 캡슐화한 것이며 비슷한 방식으로 사용됩니다.
◎BSM_Memcache
C 클라이언트 관점에서 APR_Memcache는 스레드 잠금 및 원자 수준 작업을 지원하여 운영 안정성을 보장하는 매우 성숙하고 안정적인 클라이언트 프로그램입니다. 그러나 APR을 기반으로합니다 (APR은 마지막 섹션에서 도입 될 것입니다 APR 환경 밖에서 달릴 수 없기 때문입니다. 그러나 APR은 APR에서 별도로 설치할 수 있으며 APR은 APR 웹 사이트에서 직접 설치할 수 있으며 교차 플랫폼입니다.
BSM_MEMCACHE는 BS.MAGIC 프로젝트에서 개발 한 APR_MEMCACHE를 기반으로 한 PHP 확장자입니다. 이 프로그램은 매우 간단하며 너무 많은 기능을 수행하지 않습니다.
다중 서버 분산 스토리지를 지원하는 MCACHEPLENSION과 다르면 BSM_MEMCACHE는 각 그룹의 서버가 여전히 해시 방법에 따라 데이터를 배포하고 저장합니다. , 핫 백업이 구현되어 모든 서버 그룹이 손상되지 않으면 (예 : 컴퓨터 룸의 전력 아웃). 물론,이 기능을 구현하는 비용은 데이터를 추가하거나 삭제할 때마다 데이터를 얻을 때마다 데이터가 발견 될 때까지 무작위로 선택해야합니다. 일반적으로 다음에 얻을 수 있습니다.
BSM_MEMCACHE는 이러한 기능 만 지원합니다.
코드 : [클립 보드에 복사] zend_function_entry bsm_memcache_functions [] =
{
php_fe (mc_get, null)
php_fe (mc_set, null)
php_fe (mc_del, null)
php_fe (mc_add_group, null)
php_fe (mc_add_server, null)
php_fe (mc_shutdown, null)
{null, null, null}
};
MC_ADD_GROUP 함수는 정수를 반환합니다 (실제로는 객체가되어야합니다 ~ _ ~) MC_ADD_SERVER가 사용되면 두 매개 변수가 제공되어야합니다. 하나는 그룹 ID이고 다른 하나는 서버 주소입니다. addrort).
코드 : [클립 보드에 복사]/**
*서버 그룹을 추가하십시오
*/
php_function (mc_add_group)
{
apr_int32_t group_id;
APR_STATUS_T RV
(0! = Zend_num_args ())
{
WRONG_PARAM_COUNT;
return_null ();
}
group_id = free_group_id ();
if (-1 == group_id)
{
return_false;
}
apr_memcache_t *mc;
rv = apr_memcache_create (p, max_g_server, 0, mc
)
;
}
코드 : [클립 보드에 복사]/**
* 서버를 그룹에 추가하십시오
*/
php_function (mc_add_server)
{
APR_STATUS_T RV;
apr_int32_t group_id;
이중 G;
char *srv_str;
int srv_str_l
(2! = zend_num_args ())
{
WRONG_PARAM_COUNT;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "ds", & g, & srv_str, & srv_str_l) == 실패)
{
return_false;
}
group_id = (apr_int32_t) g
;
{
return_false;
}
char *호스트, *범위;
apr_port_t port;
rv = apr_parse_addr_port (& scope, & port, srv_str, p);
if (apr_success == rv)
{
//이 서버 객체를 만듭니다
apr_memcache_server_t *st;
rv = apr_memcache_server_create (p, 호스트, 포트, 0, 64, 1024, 600, & st);
if (apr_success == rv)
{
if (null == mc_groups [group_id])
{
return_false;
}
// 서버 추가
=
apr_memcache_add_server (mc_groups [group_id], st);
{
return_true;
}
}
}
return_false;
}
데이터를 설정하고 대리 할 때 모든 그룹을 통한 루프 :
코드 : [클립 보드에 복사]/**
* 모든 그룹에 항목을 저장하십시오
*/
php_function (mc_set)
{
char *key, *value;
int key_l, value_l;
이중 TTL = 0;
Double set_ct = 0;
if (2! = zend_num_args ()).
{
WRONG_PARAM_COUNT;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "ss | d", & key, & key_l, & value, & value_l, ttl) == 실패)
{
return_false;
}
// 모든 객체에 데이터를 작성합니다
apr_int32_t i = 0;
if (ttl <0)
{
ttl = 0;
}
rv
;
{
if (0 == is_validate_group (i))
{
// 쓰기!
rv = apr_memcache_add (mc_groups [i], 키, 값, value_l, (apr_uint32_t) ttl, 0);
if (apr_success == rv)
{
set_ct ++;
}
}
}
return_double (set_ct);
}
MC_GET에서는 먼저 그룹을 무작위로 선택한 다음이 그룹의 폴링을 시작합니다.
코드 : [클립 보드에 복사]/**
* 임의의 그룹에서 항목을 가져 오십시오
*/
php_function (mc_get)
{
char *key, *value = null;
int key_l;
apr_size_t value_l
(1! = zend_num_args ())
{
WRONG_PARAM_COUNT;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "s", & key, & key_l) == 실패)
{
return_mull ();
}
// 시도해 볼게요 ...
// 무작위 읽기
apr_int32_t curr_group_id = random_group ();
apr_int32_t i = 0;
apr_int32_t try = 0;
apr_uint32_t 플래그;
APR_MEMCACHE_T *OPER;
APR_STATUS_T RV
;
{
try = i + curr_group_id;
try = try % max_group;
if (0 == is_validate_group (try))
{
// 값을 얻습니다
OPER = MC_GROUPS [try];
rv = apr_memcache_getp (mc_groups [try], p, (const char *) 키, & value, & value_l, 0);
if (apr_success == rv)
{
return_string (값, 1);
}
}
}
return_false;
}
코드 : [클립 보드에 복사]/**
* 랜덤 그룹 ID
* mc_get ()의 경우
*/
apr_int32_t random_group ()
{
Struct TimeVal TV;
Struct Timezone TZ;
int
usec
;
}
BSM_MEMCACHE의 사용은 다른 클라이언트와 유사합니다.
코드 : [클립 보드에 복사] <? php
$ g1 = mc_add_group (); // 첫 번째 그룹 추가
$ g2 = mc_add_group ();
MC_ADD_SERVER ($ G1, 'LocalHost : 11211');
MC_ADD_SERVER ($ G1, 'LocalHost : 11212');
MC_ADD_SERVER ($ G2, '10 .0.0.16 : 11211 ');
MC_ADD_SERVER ($ G2, '10 .0.0.17 : 11211 ')
;
$ key = mc_get ( 'key');
MC_DEL ( '키'); // 데이터 삭제
MC_SHUTDOWN (); // 모든 그룹을 종료합니다
?>
APR_MEMCACHE에 대한 관련 정보는 여기에서 확인할 수 있으며 BSM_MEMCACHE는이 사이트에서 다운로드 할 수 있습니다.
APR 환경 소개
APR : Apache Portable Runtime의 전체 이름. Apache Software Foundation에서 작성하고 유지 관리하는 크로스 플랫폼 C 언어 라이브러리 세트입니다. Apache Httpd1.x에서 추출되며 httpd와 독립적입니다. APR은 메모리 풀, 문자열 작업, 네트워크, 어레이, 해시 테이블 등과 같은 실제 기능을 포함하여 사용할 수있는 많은 편리한 API 인터페이스를 제공합니다. APACHE2 모듈을 개발하려면 많은 APR 기능에 대한 노출이 필요합니다.
postScript
이것은 음력의 빙구 연도의 마지막 기사입니다. 연구 기회와 부서의 동료들에게 도움을 주신 Sina.com에게 감사드립니다.
NP02-13-2007 박사