об авторе
Чжан Янджун, старший DBA Центра технической поддержки Ctrip, участвовал в эксплуатации и обслуживании Ctrip Mysql и Redis. Он обладает сильным интересом к базе данных HA, автоматической эксплуатации и технического обслуживания, архитектуру базы данных и сложному анализу проблем и устранение неполадок.
Шоу Сянчен, старший администратор базы данных Центра технической поддержки Ctrip, участвует в эксплуатации и обслуживании Ctrip Redis и БД. У него больше практического опыта в автоматической эксплуатации и обслуживании, обработке и мониторинге и устранении неполадок, ему нравится глубоко анализировать проблемы и повышать эффективность командной работы и обслуживания.
Redis — очень широко используемая база данных кеша с открытым исходным кодом, которая используется почти во всех направлениях бизнеса Ctrip. Эта статья взята из реального онлайн-кейса, описывает процесс устранения случайной ошибки доступа к Redis и анализирует причины и последствия этой ошибки в сети и ядре, Надеюсь, она будет вам полезна.
1. Описание проблемы
В производственной среде есть Redis, который иногда сообщает об ошибке сбоя подключения. На момент сообщения об ошибке IP-адрес клиента не имеет особенно очевидного паттерна. Ниже приведено сообщение об ошибке, о котором сообщает клиент. Через некоторое время ошибка автоматически восстановится.
CRedis.Client.RExceptions.ExcuteCommandException:Unable to Connect redis server: ---> CRedis.Third.Redis.RedisException: Unable to Connect redis server:
в CRedis.Third.Redis.RedisNativeClient.CreateConnectionError()
в CRedis.Third.Redis.RedisNativeClient.SendExpectData(Byte[][]cmdWithBinaryArgs)
В CRedis.Client.Entities.RedisServer.C__DisplayClassd`1.
at CRedis.Client.Logic.ClientPool.ExecuteAction[T] (действие Func`2, String methodName)
at CRedis.Client.Logic.ClientPool.Execute[T] (действие Func`2, StringmethodName)
Судя по сообщению об ошибке, это должно быть вызвано невозможностью подключения к Redis. Версия Redis — 2.8.19. Хотя версия немного устарела, в основном она стабильна.
Только этот кластер имеет случайные ошибки в онлайн-среде. Очевидной особенностью этого кластера является наличие сотен клиентских серверов.
2. Анализ проблемы
Судя по сообщению об ошибке, клиент не может подключиться к серверу.
Общая причина состоит в том, что порт исчерпан, поэтому мы проверяем сетевое соединение. В точке задачи количество подключений TCP далеко от сценария, где порт исчерпан. Так что это не корневая причина Redis, не подключающихся.
Еще одна общая сцена состоит в том, что на сервере есть медленный запрос, вызывая блокировку службы Redis. На сервере Redis мы создали оператор, который проходит более 10 миллисекунд, и не захватывает оператор медленного запуска.
Из мониторинга развертывания сервера в момент возникновения проблемы количество подключений резко возросло с 3500 до 4100 ссылок. Как показано ниже.
В то же время на стороне сервера видно, что на стороне сервера Redis происходит потеря пакетов. 345539 – 344683 = 856 пакетов.
Sat Apr 710:41:40 CST 2018
1699 outgoing packets dropped
92 dropped because of missing route
344683 SYNs to LISTEN sockets dropped
344683 times the listen queue of a socket overflowed
Sat Apr 710:41:41 CST 2018
1699 outgoing packets dropped
92 dropped because of missing route
345539 SYNs to LISTEN sockets dropped
345539 times the listenqueue of a socket overflowed
Причина ошибки, о которой сообщает клиент, в основном определяется, потому что скорость установления соединения слишком высока, что приводит к переполнению очереди невыполненных работ на сервере, и соединение сбрасывается сервером.
три,О невыполненной работе
Это очень распространенный тип ошибки TCP в службе коротких соединений с большим количеством одновременных соединений. Обычный процесс построения TCP выглядит следующим образом:
1, клиент передает (SYN) на сервер;
2. Сервер возвращает (SYN, ACK) клиенту;
3. Клиент возвращает (ACK), и трехэтапное рукопожатие завершается.
Для клиента соединение успешно установлено, и клиент может продолжать отправлять пакеты данных на сервер, но сервер в это время может быть не готов, если его нарисовать в виде картинки, то это будет выглядеть так:
В протоколе tcp, реализованном BSD-версией ядра, для процесса установления соединения на стороне сервера требуются две очереди: одна — очередь SYN, а другая — очередь принятия.
Первая называется очередью полуоткрытого соединения (или полусоединения), которая добавляется в очередь при получении SYN, отправленного клиентом. (Распространенный метод сетевой атаки заключается в непрерывной отправке SYN, но не отправке ACK, что приводит к разрыву полуоткрытой очереди на стороне сервера, и сервер отказывается обслуживать).
Последний называется полной очередью подключения, сервер возвращается (SYN, ACK), после получения ACK, отправленного клиентом (клиент подумает, что создание подключения было завершено и начнет отправлять очередь PSH), если принять очередь Не заполнен, затем сервер начнется с SYN, очередь перемещает информацию о соединении к очереди приема; если переполнение очереди принятия в это время поведение сервера зависит от конфигурации.
Если tcp_abort_on_overflow равно 0 (по умолчанию), то пакет PSH, отправленный клиентом напрямую, будет удален, и клиент войдет в процесс повторной передачи.Если это 1, то сервер отправляет сброс сразу после обнаружения того, что очередь приема заполнена.
С помощью поиска wireshark обнаружено, что за одну секунду к серверу Redis поступает более 2000 запросов на установление соединения. Мы попытались изменить размер отставания tcp с 511 до 2048, но проблема не решилась. Поэтому такая тонкая настройка не может полностью решить проблему.
В-четвертых, анализ сетевых пакетов
Мы использовали wireshark для определения точного времени и причины перегрузки сети. Точное время сообщения об ошибке уже есть.Во-первых, используйте editcap, чтобы сократить слишком большой пакет tcp до 30-секундного интервала, и проанализируйте точную точку времени перегрузки сети через интервал ввода-вывода Wireshark 100 мс.
По значку видно, что в tcp пакетах есть блокировка.
Сетевые пакеты до и после блока подробно анализируются, и сетевые пакеты выглядят следующим образом:
Time |
Source |
Dest |
Description |
12:01:54.6536050 |
Redis-Server |
Clients |
TCP: Флаги = ... AP ... |
12:01:54.6538580 |
Redis-Server |
Clients |
TCP:Флаги=…AP… |
12:01:54.6539770 |
Redis-Server |
Clients |
TCP:Флаги=…AP… |
12:01:54.6720580 |
Redis-Server |
Clients |
TCP:Flags=…A..S.. |
12:01:54.6727200 |
Redis-Server |
Clients |
TCP:Флаги=…A… |
12:01:54.6808480 |
Redis-Server |
Clients |
TCP:Flags=…AP….. |
12:01:54.6910840 |
Redis-Server |
Clients |
TCP:Флаги=…A…S., |
12:01:54.6911950 |
Redis-Server |
Clients |
TCP:Флаги=…A… |
… |
… |
… |
… |
12:01:56.1181350 |
Redis-Server |
Clients |
TCP:Флаги=…AP…. |
В 12:01:54.6808480 сервер Redis отправил клиенту Push-пакет, то есть результат, возвращенный для запроса запроса. Следующие пакеты выполняют обработку соединения, включая пакеты Ack, пакеты подтверждения Ack и пакеты сброса RST.Следующий пакет Push отправляется в 12:01:56.1181350. Интервал между ними составляет 1,4372870 секунд.
То есть в течение этих 1.4372870 секунд на стороне сервера Redis, кроме запроса, другие операции выполняют установление соединения или отказ от соединения.
Логика до и после ошибки, о которой сообщил клиент, была ясна. Redis-сервер завис на 1,43 секунды. Пул соединений клиента был заполнен, и новые соединения создавались безумно. Очередь приема сервера была заполнена, и в услуге было отказано напрямую, и клиент сообщил об ошибке.
Я начал подозревать, что клиент отправил специальную команду, в это время нам нужно подтвердить, что это за последние несколько команд клиента, и найти первый пакет до того, как redis-сервер застрянет. Установите подключаемый модуль wireshark redis и убедитесь, что последние несколько команд — это просто get, а ключ-значение очень маленькое, поэтому выполнение не занимает 1,43 секунды. На стороне сервера нет медленного журнала, и в настоящее время устранение неполадок снова заблокировано.
V. Дальнейший анализ
Чтобы понять, что Redis Server делает в эти 1,43 секунды, мы используем pstack для получения информации. Pstack по сути является подключением gdb, и высокочастотное сканирование повлияет на пропускную способность redis. Бесконечный цикл 0,5 секунды для простого захвата.Когда redis-сервер зависает, стек захватывается следующим образом (бесполезная информация о стеке фильтруется):
Thu May 31 11:29:18 CST 2018
Thread 1 (Thread 0x7ff2db6de720 (LWP 8378)):
#0 0x000000000048cec4 in ?? ()
#1 0x00000000004914a4 in je_arena_ralloc ()
#2 0x00000000004836a1 in je_realloc ()
#3 0x0000000000422cc5 in zrealloc ()
#4 0x00000000004213d7 in sdsRemoveFreeSpace ()
#5 0x000000000041ef3c inclientsCronResizeQueryBuffer ()
#6 0x00000000004205de in clientsCron ()
#7 0x0000000000420784 in serverCron ()
#8 0x0000000000418542 in aeProcessEvents ()
#9 0x000000000041873b in aeMain ()
#100x0000000000420fce in main ()
Thu May 31 11:29:19 CST 2018
Thread 1 (Thread 0x7ff2db6de720 (LWP8378)):
#0 0x0000003729ee5407 in madvise () from/lib64/libc.so.6
#1 0x0000000000493a4e in je_pages_purge ()
#2 0x000000000048cf70 in ?? ()
#3 0x00000000004914a4 in je_arena_ralloc ()
#4 0x00000000004836a1 in je_realloc ()
#5 0x0000000000422cc5 in zrealloc ()
#6 0x00000000004213d7 in sdsRemoveFreeSpace ()
#7 0x000000000041ef3c inclientsCronResizeQueryBuffer ()
#8 0x00000000004205de in clientsCron ()
#9 0x0000000000420784 in serverCron ()
#100x0000000000418542 in aeProcessEvents ()
#110x000000000041873b in aeMain ()
#12 0x0000000000420fcein main ()
Thu May 31 11:29:19 CST 2018
Thread 1 (Thread 0x7ff2db6de720 (LWP8378)):
#0 0x000000000048108c in je_malloc_usable_size()
#1 0x0000000000422be6 in zmalloc ()
#2 0x00000000004220bc in sdsnewlen ()
#3 0x000000000042c409 in createStringObject ()
#4 0x000000000042918e in processMultibulkBuffer()
#5 0x0000000000429662 in processInputBuffer ()
#6 0x0000000000429762 in readQueryFromClient ()
#7 0x000000000041847c in aeProcessEvents ()
#8 0x000000000041873b in aeMain ()
#9 0x0000000000420fce in main ()
Thu May 31 11:29:20 CST 2018
Thread 1 (Thread 0x7ff2db6de720 (LWP8378)):
#0 0x000000372a60e7cd in write () from/lib64/libpthread.so.0
#1 0x0000000000428833 in sendReplyToClient ()
#2 0x0000000000418435 in aeProcessEvents ()
#3 0x000000000041873b in aeMain ()
#4 0x0000000000420fce in main ()
После многократного захвата в стеке обнаруживается подозрительное местоположение стекаclientsCronResizeQueryBuffer, которое принадлежит функции serverCron().Планирование времени внутри redis-сервера не находится под пользовательским потоком, что объясняет, почему нет медленного запроса, когда он застрял. .
Проверьте исходный код redis, чтобы убедиться, что делает redis-сервер:
clientsCron(server.h):
#define CLIENTS_CRON_MIN_ITERATIONS 5
voidclientsCron (void){
/*Makesuretoprocessatleastnumclients/server.hzofclients
*percall.Sincethisfunctioniscalledserver.hztimespersecond
*wearesurethatintheworstcaseweprocessalltheclientsin1
*second.*/
int numclients=listLength(server.clients);
int iterations=numclients/server.hz;
mstime_t now=mstime();
/*Processatleastafewclientswhileweareatit,evenifweneed
*toprocesslessthanCLIENTS_CRON_MIN_ITERATIONStomeetourcontract
*ofprocessingeachclientoncepersecond.*/
if (iterations<CLIENTS_CRON_MIN_ITERATIONS)
iterations =(numclients<CLIENTS_CRON_MIN_ITERATIONS)?
numclients:CLIENTS_CRON_MIN_ITERATIONS;
while (listLength(server.clients)&&iterations--){
client *c;
listNode *head;
/*Rotatethelist,takethecurrenthead,process.
*Thiswayiftheclientmustberemovedfromthelistit'sthe
*firstelementandwedon'tincurintoO(N)computation.*/
listRotate(server.clients);
head =listFirst(server.clients);
c =listNodeValue(head);
/*Thefollowingfunctionsdodifferentservicechecksontheclient.
*Theprotocolisthattheyreturnnon-zeroiftheclientwas
*terminated.*/
if (clientsCronHandleTimeout(c,now)) continue;
if (clientsCronResizeQueryBuffer(c))continue;
}
}
clientCron сначала определяет количество текущих клиентов, которое используется для контроля количества подключений, подлежащих очистке за один раз.Количество подключений для одного экземпляра рабочего сервера меньше 5000, то есть количество подключений быть убранным за один раз - 50.
clientsCronResizeQueryBuffer(server.h):
/*Theclientquerybufferisansds.cstringthatcanendwithalotof
*freespacenotused,thisfunctionreclaimsspaceifneeded.
*
*Thefunctionalwaysreturns0asitneverterminatestheclient.*/
intclientsCronResizeQueryBuffer (client*c){
size_t querybuf_size=sdsAllocSize(c-> querybuf);
time_t idletime=server.unixtime-c-> lastinteraction;
/*Изменять размер буфера запроса только в следующих двух случаях:
*1) Querybuffer>BIG_ARG (#definePROTO_MBULK_BIG_ARG определено в server.h (1024*32)) и этот буфер меньше пикового использования клиента за определенный период времени.
*2) Клиент бездействует более 2 с, а размер буфера превышает 1 КБ.*/
if (((querybuf_size>PROTO_MBULK_BIG_ARG)&&
(querybuf_size/( c->querybuf_peak+1))>2)||
(querybuf_size> 1024&&idletime>2))
{
/*Onlyresizethequerybufferifitisactuallywastingspace.*/
if (sdsavail(c->querybuf)> 1024){
c ->querybuf=sdsRemoveFreeSpace(c-> querybuf);
}
}
/*Resetthepeakagaintocapturethepeakmemoryusageinthenext
*cycle.*/
c ->querybuf_peak=0;
return 0;
}
Если буфер запроса объекта redisClient соответствует условиям, его размер будет изменен напрямую. Соединения, удовлетворяющие условиям, делятся на два типа, одно действительно большое, которое превышает пиковое значение, используемое клиентом за определенный период времени; другое — очень простое (idle>2), оба из которых должны удовлетворять одному Условием является то, что часть свободного буфера превышает 1k.
Тогда причина, по которой redis-сервер зависает, заключается в том, что когда имеется всего 50 больших или простаивающих подключений, а свободный размер превышает 1 КБ, изменение размера выполняется циклически. Поскольку Redis — однопоточная программа, клиент заблокирован.
Тогда решение этой проблемы очень ясно, так что частота изменения размера становится меньше или скорость выполнения изменения размера становится быстрее.
Поскольку проблема связана с буфером запросов, давайте посмотрим, где это модифицируется:
readQueryFromClient(сеть.с):
redisClient*createClient( intfd){
redisClient *c=zmalloc(sizeof( redisClient));
/*passing-1asfditispossibletocreateanonconnectedclient.
*ThisisusefulsincealltheRediscommandsneedstobeexecuted
*inthecontextofaclient.Whencommandsareexecutedinother
*contexts(forinstanceaLuascript)weneedanonconnectedclient.*/
if (fd!=-1){
anetNonBlock (NULL,fd);
anetEnableTcpNoDelay (NULL,fd);
if (server.tcpkeepalive)
anetKeepAlive (NULL,fd,server.tcpkeepalive);
if (aeCreateFileEvent(server.el,fd, AE_READABLE,
readQueryFromClient,c)== AE_ERR)
{
close (fd);
zfree (c);
return NULL;
}
}
selectDb (c,0);
c ->id=server.next_client_id++;
c ->fd=fd;
c ->name=NULL;
c ->bufpos=0;
c->querybuf= sdsempty(); инициализация равна 0
readQueryFromClient(networking.c):
voidreadQueryFromClient (aeEventLoop*el,intfd, void*privdata,intmask){
redisClient *c=(redisClient*)privdata;
int nread,readlen;
size_t qblen;
REDIS_NOTUSED (el);
REDIS_NOTUSED (mask);
server.current_client= c;
readlen =REDIS_IOBUF_LEN;
/*Ifthisisamultibulkrequest,andweareprocessingabulkreply
*thatislargeenough,trytomaximizetheprobabilitythatthequery
*buffercontainsexactlytheSDSstringrepresentingtheobject,even
*attheriskofrequiringmoreread(2)calls.Thiswaythefunction
*processMultiBulkBuffer()canavoidcopyingbufferstocreatethe
*RedisObjectrepresentingtheargument.*/
if (c->reqtype==REDIS_REQ_MULTIBULK&& c->multibulklen&&c->bulklen!=- 1
&&c-> bulklen>=REDIS_MBULK_BIG_ARG)
{
int remaining=(unsigned)(c-> bulklen+2)-sdslen(c-> querybuf);
if (remaining<readlen)readlen= remaining;
}
qblen =sdslen(c->querybuf);
if (c->querybuf_peak<qblen) c->querybuf_peak=qblen;
c ->querybuf=sdsMakeRoomFor(c->querybuf,readlen); здесь будет развернуто
Видно, что размер c->querybuf будет выделен как минимум 1024*32 после подключения первой команды чтения, так что явно есть проблема с логикой очистки ресайза при оглядывании назад.
Размер каждого используемого буфера запроса составляет не менее 1024 * 32, но при очистке условие оценки > 1024, то есть все используемые соединения с простоем > 2 будут изменены и получены в следующий раз. При запросе будет перераспределил на 1024*32.
На самом деле в этом нет необходимости, в кластерах с частым доступом память будет высвобождаться и перераспределяться часто, поэтому мы пытаемся преобразовать условия оценки очистки следующим образом, что позволяет избежать большинства ненужных операций изменения размера.
if (((querybuf_size >REDIS_MBULK_BIG_ARG) &&
(querybuf_size/(c->querybuf_peak+1)) > 2) ||
(querybuf_size >1024*32 && idletime > 2))
{
/* Only resize the query buffer if it is actually wasting space. */
if (sdsavail(c->querybuf) > 1024*32) {
c->querybuf = sdsRemoveFreeSpace(c->querybuf);
}
}
Побочным эффектом этого преобразования являются накладные расходы памяти, рассчитанные в соответствии с экземпляром 5 000 подключений. 5000*1024*32=160M, такое потребление памяти вполне приемлемо для серверов с сотнями гигабайт памяти.
6. Повторение проблемы
После использования Redisserver с измененным исходным кодом проблема по-прежнему появляется снова, клиент по-прежнему будет сообщать об ошибке того же типа, а при сообщении об ошибке память сервера все еще будет дрожать. Получите информацию о стеке памяти следующим образом
Thu Jun 14 21:56:54 CST 2018
#3 0x0000003729ee893d in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x7f2dc108d720 (LWP27851)):
#0 0x0000003729ee5400 in madvise () from /lib64/libc.so.6
#1 0x0000000000493a1e in je_pages_purge ()
#2 0x000000000048cf40 in arena_purge ()
#3 0x00000000004a7dad in je_tcache_bin_flush_large ()
#4 0x00000000004a85e9 in je_tcache_event_hard ()
#5 0x000000000042c0b5 in decrRefCount ()
#6 0x000000000042744d in resetClient ()
#7 0x000000000042963bin processInputBuffer ()
#8 0x0000000000429762 in readQueryFromClient ()
#9 0x000000000041847c in aeProcessEvents ()
#10 0x000000000041873b in aeMain ()
#11 0x0000000000420fce in main ()
Thu Jun 14 21:56:54 CST 2018
Thread 1 (Thread 0x7f2dc108d720 (LWP27851)):
#0 0x0000003729ee5400 in madvise () from /lib64/libc.so.6
#1 0x0000000000493a1e in je_pages_purge ()
#2 0x000000000048cf40 in arena_purge ()
#3 0x00000000004a7dad in je_tcache_bin_flush_large ()
#4 0x00000000004a85e9 in je_tcache_event_hard ()
#5 0x000000000042c0b5 in decrRefCount ()
#6 0x000000000042744d in resetClient ()
#7 0x000000000042963b in processInputBuffer ()
#8 0x0000000000429762 in readQueryFromClient ()
#9 0x000000000041847c in aeProcessEvents ()
#10 0x000000000041873b in aeMain ()
#11 0x0000000000420fce in main ()
Очевидно, что проблема частого изменения размеров Querybuffer решена, но ошибки клиента все равно будут. Это опять тупик. Могут ли быть другие факторы, вызывающие медленное изменение размера буфера запросов?
Снова хватаем pstack. Но на этот раз наше внимание привлек jemalloc.
На данный момент, вспоминая механизм распределения памяти Redis, Redis по умолчанию использует jemalloc для управления выделением памяти, чтобы избежать проблемы большого количества фрагментов памяти, вызванных невысвобожденной памятью libc. В информации о стеке, представленной на этот раз, je_pages_purge( ) Redis вызывает jemalloc для восстановления грязных страниц.
Большинство наших онлайн-версий Redis имеют версию 2.8.19, а в нативном коде используется jemalloc 3.6. Давайте посмотрим, что делает jemalloc.
arena_purge(arena.c)
staticvoid
arena_purge(arena_t*arena,bool all)
{
arena_chunk_t*chunk;
size_tnpurgatory;
if(config_debug){
size_tndirty=0;
arena_chunk_dirty_iter(&arena->chunks_dirty,NULL,
chunks_dirty_iter_cb,(void*)&ndirty);
assert(ndirty==arena->ndirty);
}
assert(arena->ndirty>arena->npurgatory||all);
assert((arena->nactive>>opt_lg_dirty_mult)<(arena->ndirty-
arena->npurgatory)||all);
if(config_stats)
arena->stats.npurge++;
npurgatory=arena_compute_npurgatory(arena, all);
arena->npurgatory+=npurgatory;
while(npurgatory>0){
size_tnpurgeable,npurged,nunpurged;
/*Getnextchunkwithdirtypages.*/
chunk=arena_chunk_dirty_first(&arena->chunks_dirty);
if(chunk==NULL){
arena->npurgatory-=npurgatory;
return;
}
npurgeable=chunk->ndirty;
assert(npurgeable!=0);
if(npurgeable>npurgatory&& chunk->nruns_adjac==0){
arena->npurgatory+=npurgeable-npurgatory;
npurgatory=npurgeable;
}
arena->npurgatory-=npurgeable;
npurgatory-=npurgeable;
npurged=arena_chunk_purge(arena, chunk,all);
nunpurged=npurgeable-npurged;
arena->npurgatory+=nunpurged;
npurgatory+=nunpurged;
}
}
Каждый раз, когда Jemalloc перерабатывает, он оценивает все фрагменты, которые действительно должны быть очищены, и подсчитывает очистку.Эта операция очень экстравагантна для систем с высокими требованиями к отклику, поэтому мы рассматриваем возможность оптимизации производительности очистки путем обновления версии jemalloc.
После выпуска Redis 4.0 производительность значительно улучшилась, а память можно восстановить с помощью команд.Мы также готовимся к онлайн-обновлению.Версия jemalloc, выпущенная с 4.0, — это 4.1.
Версия jemalloc использовала arena_purge() после того, как jemalloc 4.0 провел большую оптимизацию, удалил вызов счетчика, упростил большую часть логики оценки, добавил метод arena_stash_dirty(), объединил предыдущую логику расчета и оценки и добавил purge_runs_sentinel. , заменяя предыдущий метод хранения грязных блоков в фрагменте дерева арены, содержащем грязный запуск, на сохранение грязных блоков в каждом LRU арены, что значительно уменьшает размер очистки грязных блоков и не препятствует процессу высвобождения памяти. , Затем переместите блок памяти.
код показывает, как показано ниже:
arena_purge(arena.c)
staticvoid
arena_purge(arena_t*arena,bool all)
{
chunk_hooks_tchunk_hooks=chunk_hooks_get(arena);
size_tnpurge,npurgeable,npurged;
arena_runs_dirty_link_tpurge_runs_sentinel;
extent_node_tpurge_chunks_sentinel;
arena->purging=true;
/*
*Callstoarena_dirty_count()aredisabledevenfordebugbuilds
*becauseoverheadgrowsnonlinearlyasmemoryusageincreases.
*/
if(false&&config_debug){
size_tndirty=arena_dirty_count(arena);
assert(ndirty==arena->ndirty);
}
assert((arena->nactive>>arena->lg_dirty_mult)< arena->ndirty||all);
if(config_stats)
arena->stats.npurge++;
npurge=arena_compute_npurge(arena, all);
qr_new(&purge_runs_sentinel,rd_link);
extent_node_dirty_linkage_init(&purge_chunks_sentinel);
npurgeable=arena_stash_dirty(arena,& chunk_hooks,all,npurge,
&purge_runs_sentinel,&purge_chunks_sentinel);
assert(npurgeable>=npurge);
npurged=arena_purge_stashed(arena,& chunk_hooks,&purge_runs_sentinel,
&purge_chunks_sentinel);
assert(npurged==npurgeable);
arena_unstash_purged(arena,&chunk_hooks,& purge_runs_sentinel,
&purge_chunks_sentinel);
arena->purging=false;
}
7. Решение проблем
На самом деле у нас есть несколько вариантов. Вместо jemalloc можно использовать tcmalloc от Google, версию jemalloc можно обновить и так далее.
Согласно приведенному выше анализу, мы пытаемся решить проблему путем обновления версии jemalloc, а фактическая операция заключается в обновлении версии Redis. После обновления версии Redis до 4.0.9 была решена острая проблема тайм-аута подключения онлайн-клиента.
8. Краткое изложение проблемы
Redis широко используется в производственной среде, поскольку он поддерживает высокий уровень параллелизма, быстрое реагирование и простоту эксплуатации.Для эксплуатационного и обслуживающего персонала его требования ко времени отклика доставили различные проблемы.
Проблема тайм-аута соединения Redis является одной из наиболее типичных.От обнаружения проблемы, тайм-аута клиентского соединения, до перехвата сетевых пакетов между клиентом и сервером, проблемы с расположением стека памяти, мы также были сбиты с толку. некоторые из этих иллюзий и, наконец, прошли обновление версии jemalloc (Redis), чтобы решить проблему.
Я надеюсь, что эта статья может вдохновить друзей, столкнувшихся с той же проблемой.
【Рекомендуется к прочтению】