Помните глупую операцию - проблемы по безопасности потоков

Java Java EE

предисловие

Только лысая голова может стать сильнее.

Текст был включен в мой репозиторий GitHub, добро пожаловать, звезда:GitHub.com/Zhongf UC очень…

Помните глупую операцию на работе, ключевое слово этой статьи: безопасность потоков

(Почему я пишу баги каждый день)

I. Предыстория

У меня здесь система, которая предоставляет интерфейс RPC для отправки различной информации (например, SMS, электронная почта, WECHAT) и другие каналы. Моя система архитектуры подобна этому:

系统架构

Резюме: система обслуживания предоставляет интерфейс RPC.Другие вызывают интерфейс, предоставленный мной.Я оцениваю, сплайсинг и другую бизнес-логику сообщения в системе обслуживания, и, наконец, помещаю сообщение в очередь сообщений. Система-отправитель будет потреблять данные в очереди сообщений, а затем отправлять сообщение

Пример: Сяо Ван вызывает наш интерфейс RPC и хочет отправить электронное письмо. Я оцениваю и собираю параметры письма в задачу, которую я определил здесь, и бросаю эту задачу в очередь сообщений. Система-отправитель использует эту задачу и вызываетjava.mailAPI завершает функцию отправки почты.

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);

在返回sendResponse之前插入msgId就好了

Это требование было завершено очень быстро, и не было ничего плохого в простом тесте, и он вышел в Интернете решительно. Сяо Ван не говорил, что произошла какие-либо проблемы после его использования, поэтому спрос был доставлен.

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 оригинальных статейТехнические статьи, обширные видеоресурсы, красивые карты мозга,Следуйте, чтобы получить его!

转发到朋友圈是对我最大的支持!

Я думаю, что моя статья хорошо написана, нажмитеотличный!