Адрес блога на Github:GitHub.com/message731/no…
Недавно команда проекта обновила и модифицировала старую версию Apple IOS. Глядя на документацию по интерфейсу Apple, я чувствую, что написать интерфейс, гарантирующий стабильность и эффективность, все еще немного сложно. часто используемые библиотеки, я выбрал Разработано и поддерживается командой Turopushy.
APN и Pushy
Отправка сообщений устройствами Apple зависит от службы Apple APNs (служба push-уведомлений Apple).APNsОфициальный профиль выглядит следующим образом:
Apple Push Notification service (APNs) is the centerpiece of the remote notifications feature. It is a robust, secure, and highly efficient service for app developers to propagate information to iOS (and, indirectly, watchOS), tvOS, and macOS devices.
Все сообщения, отправляемые на устройства IOS (tvOS, macOS), должны проходить через APN. Служба APN действительно очень мощная. Ей необходимо отправлять десятки миллиардов сообщений каждый день, что является надежным, безопасным и эффективным. Даже приложения для обмена мгновенными сообщениями на уровне пользователя, такие как WeChat и QQ, должны использовать APN, когда программа не запущена или когда программа работает в фоновом режиме (когда программа запускается, она использует длинное соединение, установленное самостоятельно), но Tencent оптимизировал вся ссылка от них.Линия от сервера к серверу Apple только,поэтому думаю пуш быстрее(см.Знай почти).
Старая служба Apple Push команды проекта использовала предыдущие APN Apple на основе двоичных сокетов, а также использовалаjavapnsБиблиотека с открытым исходным кодом, этот javapns, похоже, работает не очень хорошо, и некоторые люди обсуждали это в Интернете. javapns также прекратил поддержку DEPRECATED. Авторы рекомендуют перейти на библиотеку, основанную на новом сервисе APN от Apple.
Новые APN от Apple основаны на HTTP/2, который более эффективен благодаря мультиплексированию соединений.Конечно, есть и другие оптимизации и улучшения, о которых вы можете узнатьВведение в APN, объяснил более понятно.
Поговорим о том, что мы используемPushy, официальное введение выглядит следующим образом:
Pushy is a Java library for sending APNs (iOS, macOS, and Safari) push notifications. It is written and maintained by the engineers at Turo......We believe that Pushy is already the best tool for sending APNs push notifications from Java applications, and we hope you'll help us make it even better via bug reports and pull requests.
Документация и инструкции Pushy очень полны, и обсуждения очень активны.Автор в основном отвечает на все вопросы, и на большинство вопросов можно ответить, и им не сложно пользоваться.
Отправка сообщений APN с использованием Pushy
Сначала добавьте пакет
<dependency>
<groupId>com.turo</groupId>
<artifactId>pushy</artifactId>
<version>0.11.1</version>
</dependency>
Аутентификация
Apple APN предоставляет два метода аутентификации: аутентификация с помощью токена идентификационной информации на основе JWT и аутентификация на основе сертификата. Pushy также поддерживает эти два метода аутентификации.Здесь мы используем метод аутентификации сертификата.Для метода аутентификации токена вы можете проверить документацию Pushy.
Как получить сертификат аутентификации личности Apple APNs можно проверитьофициальная документация.
Напористое использование
ApnsClient apnsClient = new ApnsClientBuilder()
.setClientCredentials(new File("/path/to/certificate.p12"), "p12-file-password")
.build();
PS Функция setClientCredentials здесь также может поддерживать передачу InputStream и пароля сертификата.
В то же время вы также можете указать, является ли это средой разработки или рабочей средой через функцию setApnsServer:
ApnsClient apnsClient = new ApnsClientBuilder().setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
.setClientCredentials(new File("/path/to/certificate.p12"), "p12-file-password")
.build();
Pushy основан на Netty.Через ApnsClientBuilder мы можем изменить количество подключений ApnsClient и количество потоков EventLoopGroups по мере необходимости.
EventLoopGroup eventLoopGroup = new NioEventLoopGroup(4);
ApnsClient apnsClient = new ApnsClientBuilder()
.setClientCredentials(new File("/path/to/certificate.p12"), "p12-file-password")
.setConcurrentConnections(4).setEventLoopGroup(eventLoopGroup).build();
На официальном сайте есть следующие инструкции по количеству подключений и количеству потоков EventLoopGroup.Короче говоря, не настраивайте количество потоков EventLoopGroups так, чтобы оно превышало количество соединений APN.
Because connections are bound to a single event loop (which is bound to a single thread), it never makes sense to give an ApnsClient more threads in an event loop than concurrent connections. A client with an eight-thread EventLoopGroup that is configured to maintain only one connection will use one thread from the group, but the other seven will remain idle. Opening a large number of connections on a small number of threads will likely reduce overall efficiency by increasing competition for CPU time.
Что касается толчка сообщения, обратите внимание, чтобы обязательно использоватьАсинхронная работа, Pushy будет возвращать объект Netty Future при отправке сообщения, через который можно получить ситуацию отправки сообщения.
for (final ApnsPushNotification pushNotification : collectionOfPushNotifications) {
final Future sendNotificationFuture = apnsClient.sendNotification(pushNotification);
sendNotificationFuture.addListener(new GenericFutureListener<Future<PushNotificationResponse>>() {
@Override
public void operationComplete(final Future<PushNotificationResponse> future) throws Exception {
// This will get called when the sever has replied and returns immediately
final PushNotificationResponse response = future.getNow();
}
});
}
Сервер APNs может гарантировать одновременную отправку сообщений 1500. Когда этот предел превышен, Pushy будет кэшировать сообщения, поэтому нам не нужно беспокоиться об отправке слишком большого количества сообщений в асинхронных операциях (когда наши сообщения очень большие, достигнув сотен миллионов, мы также должны сделать некоторые элементы управления, избежать слишком большого кеша, нехватки памяти, Pushy предложил решение для использования семафора).
The APNs server allows for (at the time of this writing) 1,500 notifications in flight at any time. If we hit that limit, Pushy will buffer notifications automatically behind the scenes and send them to the server as in-flight notifications are resolved.
In short, asynchronous operation allows Pushy to make the most of local resources (especially CPU time) by sending notifications as quickly as possible.
Вышеупомянутое является лишь основным использованием Pushy, ситуация может быть более сложной в нашей производственной среде, нам может потребоваться знать, когда все нажатия завершены, и, возможно, потребуется подсчитать сообщения об успешном выполнении нажатия. , может быть необходимо предотвратить нехватку памяти, или может быть необходимо по-разному обрабатывать разные результаты отправки.... Нечего сказать, код выше.
Лучшие практики
Ссылка Pushy'sОфициальные рекомендации, мы добавили следующие операции:
- Управление потоком через семафор, чтобы предотвратить слишком большой кеш и нехватку памяти
- Используйте CountDownLatch, чтобы отметить, было ли сообщение отправлено.
- Используйте AtomicLong для завершения подсчета в анонимном методе OperationComplete внутреннего класса.
- Используйте объект Netty Future, чтобы судить о результате отправки сообщения
Для конкретного использования обратитесь к следующему коду:
public class IOSPush {
private static final Logger logger = LoggerFactory.getLogger(IOSPush.class);
private static final ApnsClient apnsClient = null;
private static final Semaphore semaphore = new Semaphore(10000);
public void push(final List<String> deviceTokens, String alertTitle, String alertBody) {
long startTime = System.currentTimeMillis();
if (apnsClient == null) {
try {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup(4);
apnsClient = new ApnsClientBuilder().setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
.setClientCredentials(new File("/path/to/certificate.p12"), "p12-file-password")
.setConcurrentConnections(4).setEventLoopGroup(eventLoopGroup).build();
} catch (Exception e) {
logger.error("ios get pushy apns client failed!");
e.printStackTrace();
}
}
long total = deviceTokens.size();
final CountDownLatch latch = new CountDownLatch(deviceTokens.size());
final AtomicLong successCnt = new AtomicLong(0);
long startPushTime = System.currentTimeMillis();
for (String deviceToken : deviceTokens) {
ApnsPayloadBuilder payloadBuilder = new ApnsPayloadBuilder();
payloadBuilder.setAlertBody(alertBody);
payloadBuilder.setAlertTitle(alertTitle);
String payload = payloadBuilder.buildWithDefaultMaximumLength();
final String token = TokenUtil.sanitizeTokenString(deviceToken);
SimpleApnsPushNotification pushNotification = new SimpleApnsPushNotification(token, "com.example.myApp", payload);
try {
semaphore.acquire();
} catch (InterruptedException e) {
logger.error("ios push get semaphore failed, deviceToken:{}", deviceToken);
e.printStackTrace();
}
final Future<PushNotificationResponse<SimpleApnsPushNotification>> future = apnsClient.sendNotification(pushNotification);
future.addListener(new GenericFutureListener<Future<PushNotificationResponse>>() {
@Override
public void operationComplete(Future<PushNotificationResponse> pushNotificationResponseFuture) throws Exception {
if (future.isSuccess()) {
final PushNotificationResponse<SimpleApnsPushNotification> response = future.getNow();
if (response.isAccepted()) {
successCnt.incrementAndGet();
} else {
Date invalidTime = response.getTokenInvalidationTimestamp();
logger.error("Notification rejected by the APNs gateway: " + response.getRejectionReason());
if (invalidTime != null) {
logger.error("\t…and the token is invalid as of " + response.getTokenInvalidationTimestamp());
}
}
} else {
logger.error("send notification device token={} is failed {} ", token, future.cause().getMessage());
}
latch.countDown();
semaphore.release();
}
});
}
try {
latch.await(20, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.error("ios push latch await failed!");
e.printStackTrace();
}
long endPushTime = System.currentTimeMillis();
logger.info("test pushMessage success. [共推送" + total + "个][成功" + (successCnt.get()) + "个],
totalcost= " + (endPushTime - startTime) + ", pushCost=" + (endPushTime - startPushTime));
}
}
- О вызове клиента из нескольких потоков
Pushy ApnsClient является потокобезопасным и может вызываться с использованием нескольких потоков.
- О создании нескольких клиентов
Создание нескольких клиентов может увеличить скорость отправки, но улучшение незначительное Автор рекомендует:
ApnsClient instances are designed to stick around for a long time. They're thread-safe and can be shared between many threads in a large application. We recommend creating a single client (per APNs certificate/key), then keeping that client around for the lifetime of your application.
- Информация об ответе APN (информация об ошибке)
Вы можете просмотреть форму кода ошибки на официальном сайте.Ссылка на сайт), поймите ошибку и вовремя скорректируйте ее.
Напористая производительность
Автор сказал в дискуссионной группе Google, что Pushy push может достигать 10-20 тыс./с с одним ядром и одним потоком, как показано на следующем рисунке:
Предложение автора по созданию нескольких клиентов и описание производительности PushyНо это может быть сеть или другие причины, результаты моих тестов не очень хорошие, результаты тестов выложены только для ознакомления (время мс):
ps. Поскольку это тест, не так много устройств, которые можно использовать для массового push-тестирования, поэтому в прошлом одно устройство вместо этого отправляло несколько push-уведомлений. Здесь на устройство отправляется большое количество push-уведомлений за короткий промежуток времени, APN сообщит об ошибке TooManyRequests, Слишком много запросов было сделано последовательно на один и тот же токен устройства. Таким образом, будет небольшое количество сообщений, которые невозможно отправить.
ps Время нажатия здесь не добавляет время инициализации клиента.
ps.Время отправки сообщения связано с размером отправленного сообщения.Здесь я не контролирую переменную сообщения во время теста (все заполнено мной, и все они очень короткие сообщения), поэтому данныетолько для справки.
- ConcurrentConnections: 1, EventLoopGroup Thread: 1
Отправьте 1 устройство | Отправьте 13 устройств | То же устройство нажимает 100 | То же устройство отправляет 1000 записей | |
---|---|---|---|---|
Средний успех отправки (шт.) | 1 | 13 | 100 | 998 |
Среднее время отправки (мс) | 222 | 500 | 654 | 3200 |
- ConcurrentConnections: 5, EventLoopGroup Thread: 1
Отправьте 1 устройство | Отправьте 13 устройств | То же устройство нажимает 100 | То же устройство отправляет 1000 записей | |
---|---|---|---|---|
Средний успех отправки (шт.) | 1 | 13 | 100 | 999 |
Среднее время отправки (мс) | 310 | 330 | 1600 | 1200 |
- ConcurrentConnections: 4, EventLoopGroup Thread: 4
Отправьте 1 устройство | Отправьте 13 устройств | То же устройство нажимает 100 | То же устройство отправляет 1000 записей | |
---|---|---|---|---|
Средний успех отправки (шт.) | 1 | 13 | 100 | 999 |
Среднее время отправки (мс) | 250 | 343 | 700 | 1700 |
По оптимизации производительности вы также можете ознакомиться с предложениями автора на официальном сайте:Threads, concurrent connections, and performance
Если у вас есть тестовые данные, вы также можете поделиться ими и обсудить их вместе.
Сегодня (12.11) я протестировал его еще раз и отправил на 3 устройства, каждое с 1000 повторных нажатий, всего 3000. Результаты следующие (время мс):
thread/connection | No.1 | No.2 | No.3 | No.4 | No.5 | No.6 | No.7 | No.8 | No.9 | No.10 | Avg |
---|---|---|---|---|---|---|---|---|---|---|---|
1/1 | 12903 | 12782 | 10181 | 10393 | 11292 | 13608 | - | - | - | - | 11859.8 |
4/4 | 2861 | 3289 | 6258 | 5488 | 6649 | 6113 | 7042 | 5393 | 4591 | 7269 | 5495.3 |
20/20 | 1575 | 1456 | 1640 | 2761 | 2321 | 2154 | 1796 | 1634 | 2440 | 2114 | 1989.1 |
40/40 | 1535 | 2134 | 3312 | 2311 | 1553 | 2088 | 1734 | 1834 | 1530 | 1724 | 1975.5 |
В то же время я протестировал его и повторно отправил 100 000 сообщений на эти 3 устройства, всего сообщений 300 000. Результаты следующие (время мс):
thread/connection | No.1 |
---|---|
20/20 | 43547 |
считать
Apple APN были обновлены и оптимизированы, постоянно охватывают новые технологии (HTTP/2, JWT и т. д.) и представляют собой удивительный сервис.
По-прежнему немного сложно вызвать службу APN напрямую, чтобы удовлетворить требования среды генерации. Туро дает нам хорошую библиотеку Java: Pushy. Pushy также имеет некоторые другие функции и способы использования (метрики, прокси, ведение журналов...), которые в целом очень хороши.
В то же время кажется, что мы также можем использовать Pushy для настройки...
2017/12/07 done
Эта статья также синхронизирована сЛичный блог на Github