предисловие
Только лысая голова может стать сильнее.
Текст был включен в мой репозиторий GitHub, добро пожаловать, звезда:GitHub.com/Zhongf UC очень…
Помните глупую операцию на работе, ключевое слово этой статьи: безопасность потоков
(Почему я пишу баги каждый день)
I. Предыстория
У меня здесь система, которая предоставляет интерфейс RPC для отправки различной информации (например, SMS, электронная почта, WECHAT) и другие каналы. Моя система архитектуры подобна этому:
Резюме: система обслуживания предоставляет интерфейс RPC.Другие вызывают интерфейс, предоставленный мной.Я оцениваю, сплайсинг и другую бизнес-логику сообщения в системе обслуживания, и, наконец, помещаю сообщение в очередь сообщений. Система-отправитель будет потреблять данные в очереди сообщений, а затем отправлять сообщение
Пример: Сяо Ван вызывает наш интерфейс RPC и хочет отправить электронное письмо. Я оцениваю и собираю параметры письма в задачу, которую я определил здесь, и бросаю эту задачу в очередь сообщений. Система-отправитель использует эту задачу и вызываетjava.mail
API завершает функцию отправки почты.
Xiao Wang вызывает наш RPC-интерфейс.Пока сервисная система бросает эту задачу в очередь сообщений, мы возвращаем ответ Xiao Wang.
- Пока задача находится в очереди сообщений, мы возвращаем успех. Поэтому иногда Сяо Ван спрашивал: «Мое возвращение прошло успешно, почему мое электронное письмо не было отправлено?» ------(асинхронный)
Каждый раз, когда отправляется электронное письмо, мы будем хранить информацию об этом электронном письме (хранится в MySQL), в MySQL мы можемЗнать, когда это письмо было отправлено, и его статуси Т. Д.
И электронные письма Сяо Вана очень обеспокоены тем, успешно ли они отправлены.Не удалось отправить, ему нужно отправить повторно. Поэтому он следит за бинлогом нашей БД, и судит о необходимости повторной передачи по информации в бинлоге.
По разным причинам Сяо Ван надеется, что при вызове нашего RPC-интерфейса он сможет получитьуникальный идентификаторТак хорошо судить, успешно это письмо или нет.
- Очевидно, email ID склада нельзя (т.к. он вызывает наш RPC интерфейс, мы ставим Task в очередь сообщений и возвращаем его. В это время система-отправитель его еще не израсходовала)
Поэтому мы планируем сгенерировать messageId в сервисной системе, затем вернуть его ему, и привязать этот messageId к Задаче, пока он не будет сохранен.
Во-вторых, крючок
После определения требований и идей, приведенных выше, я пойду посмотреть, как объект ответа возвращается Сяо Вану, и я обнаружу, чтоуже имеет msgIdполе вверх
public class SendResponse {
// 错误码
private int errCode;
// 错误信息
private String errInfo;
// messageId
private long msgId;
}
Я искал информацию об этом полеctrl + shift + f
, обнаружил, что msgId не использовался. Подумав об этом, это в самый раз, я пришел к этому. Я посмотрел на использование и обнаружил, что вместо прямого использования SendResponse класс перечисления обернут снаружи Код примерно выглядит следующим образом:
public enum Response {
SUCCESS(1, "success"),
PARAM_MISSING(2, "param is missing"),
INVALID_xxxx(3, "xxxx is invalid"),
INVALID_xxxx(4, "xxxx is invalid"),
private SendResponse sendResponse;
private Response(int errCode, String errInfo) {
sendResponse = new SendResponse();
sendResponse.setMsgId(0);
sendResponse.setErrCode(errCode);
sendResponse.setErrInfo(errInfo);
}
public SendResponse getSendResponse() {
return sendResponse;
}
}
С перечислением это очень просто использовать. Например, я обнаружил, что была проблема с определенным параметром, переданным Сяо Ваном. Мой удар слева:
Response.PARAM_ERROR
Система обслуживания в основном делает две вещи
- Судя по параметрам/типам, есть ли проблемы с различной бизнес-логикой, инкапсулировать параметры, принесенные Сяо Ваном, в объекты Task
- Поместите объект Task в очередь сообщений
Для ясности: объект sendResponse не будет возвращен до тех пор, пока не завершится вся цепочка вызовов (объект Task помещается в очередь сообщений). И поскольку может быть много мест, где можно судить, мы разработалиКарта для хранения данных, эта карта проходит через всю ссылку:
// 首先将sendResponse默认设置为success,也就是代码如下:
map.put("sendResponse",Response.SUCCESS);
// 如果中途某个地方可能有问题了,那我们将Map中sendResponse进行修改
map.put("sendResponse",Response.ERROR);
// 等整条链路完成,从Map拿出sendResponse返回
return map.get("sendResponse");
Итак, что я делаю:Прежде чем возвращать SendResponse, я создаю уникальный msgId и вставляю его в объект SendResponse..
Response.getSendResponse().setMsgid(uuid);
Это требование было завершено очень быстро, и не было ничего плохого в простом тесте, и он вышел в Интернете решительно. Сяо Ван не говорил, что произошла какие-либо проблемы после его использования, поэтому спрос был доставлен.
3. Проблемы
Вчера Сяо Ван сказал мне: "Мне не удалось отправить сюда электронное письмо. У меня есть msgId. Посмотрим, в чем причина".
Итак, я зашел в онлайн-журналы и обнаружил, что, согласно предоставленному им msgId, журналы, которые я набрал здесь, были не отправленными электронными письмами (а журналами других задач).Я в панике, что-то не так с нашей системой?
- Психологическая активность: msgId может однозначно идентифицировать эту задачу, но msgId, отправленный мне Сяо Ваном, является содержанием других задач. Есть ли большая проблема (неупорядоченное потребление? Данные все перепутались?), запаниковал
Затем он продолжил добавлять:
Позже он обнаружил, что электронное письмо было успешно отправлено, но он получилчастьmsgId предназначен для других задач, а не для электронных писем. Поэтому мы можем только сравнить оставшиеся электронные письма, чтобы увидеть, есть ли проблема, а затем посмотреть причину MsgId.
4. Найдите проблему
Существующие условия:
- Эта партия писем была успешно отправлена
- Сяо Ван получил msgId другой задачи
Поэтому проблемы в оценке системы нет, но есть проблема с msgId при параллельном процессе (получены msgId других задач)
Поэтому я пошел искать причину, когда проверил код, то обнаружил, что мой бывший коллега все же оставил запись о классе в системе Сервиса.@NotThreadSafe
. Я уверен, что не заметил, где в середине, ведущей к Ванге, есть msgId другой задачи.
Человеческую плоть отлаживал на обеденный перерыв и так и не нашел: у каждого потока свой уникальный объект операции, и ни одно из свойств объекта не экранировалось (все оперируются внутри метода), и он передается вместе со всей ссылкой до тех пор, пока конец ссылки..
Позже, когда я подумаю об этом, я должен просто посмотреть на место, где генерируется msgId. Я только что узнал, что проект используетперечислитьКакие!
// 首先将sendResponse默认设置为success,也就是代码如下:
map.put("sendResponse",Response.SUCCESS);
// 如果中途某个地方可能有问题了,那我们将Map中sendResponse进行修改
map.put("sendResponse",Response.ERROR);
// 把response的msgId的值设置为当前Task绑定的值
map.get("sendResponse").setMsgid(uuid);
// 等整条链路完成,从Map拿出sendResponse返回
return map.get("sendResponse");
Проснись:
- Теперь у меня есть 50 потоков, каждый поток будет иметь объект sendresponse по умолчанию при обработке данных, этот объект идентифицируется перечисление
Response.SUCCESS
.所以,这50个线程都общийс этим объектом sendResponse - 50 потоков совместно используют объект sendResponse, и каждый поток может изменять атрибут msgId в sendResponse, что, естественно, небезопасно для потоков.
- Таким образом, Xiao Wang может получить msgId других задач (поток Xiao Wang не вернулся после установки msgId, а поток Sanwei снова изменил msgId, в результате чего Xiao Wang получил msgId Sanwei)
Суммировать:
- Наконец-то я понял, почему мои предыдущие коллеги сохранили атрибут msgId в коде, но не использовали этот атрибут.
- При использовании перечисления не должно бытьсвойства с состоянием(модифицируемые, переменные свойства)
Наконец
рад вывестигалантерейные товарыОбщедоступный номер технологии Java:Java3y. В публичном аккаунтеБолее 200 оригинальных статейТехнические статьи, обширные видеоресурсы, красивые карты мозга,Следуйте, чтобы получить его!
Я думаю, что моя статья хорошо написана, нажмитеотличный!