[Firebase] FCM에 대해서 알아보자. 🔔
Note : 이 글은 지극히 주관적인 생각을 토대로 작성된 글입니다. 혹시나 잘못된 부분이 있다면 메일 또는 코멘트를 통해 알려주시면 감사하겠습니다. 😄 제 메일은 About 탭에서 확인하실 수 있습니다. 📧
P.S : 이 페이지는 웹에 최적화 된 페이지입니다. 가급적 모바일이 아닌 웹에서 보시는 것을 추천드립니다.
Intro
지난번 회고록 이후에 오랜만에 포스팅을 작성하게 되는것 같은데, 작은 핑계를 대보자면 최근에 회사 일 뿐만 아니라 다른 일들도 겹치다 보니까, 포스팅을 작성할 여유가 생기지 않았던 것 같다. 당분간은 계속 바쁘지 않을까 조심스럽게 예측하고 있다.
그러던 와중에 연휴 기간에 시간이 살짝 생겨서 오랜만에 포스팅을 작성해보고자 한다. 최근에 업무를 진행함에 있어서 FCM
을 사용할 일이 생겨서 FCM
에 대해서 알아보는 시간을 가졌었는데, 그 내용을 포스팅으로 남기고자 한다.
FCM이란 무엇인가?
-
FCM은 Firebase Cloud Messaging의 약자로, 무료로 메시지를 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔루션이다.
-
모든 사용자에게 알림 메세지를 전송할 수도 있고, 그룹을 지어 메시지를 전송할 수도 있다.
-
Friebase의 서비스는 요금 정책에 따라, 이용할 수 있는 범위가 다르지만 FCM은 요금 정책에 구분 없이 무료로 사용하는 것이 가능하다.
왜 FCM을 사용해서 Push 메시지를 보낼까?
-
FCM이 어떤 것인지는 알겠으나, 왜 FCM을 사용해서 메세지를 전송하는지에 대해서 궁금증을 가지게 되어 찾아보았다.
-
기존에는 iOS, Android, Web 등의 플랫폼에서 Push 메시지를 보내기 위해서는 각 플랫폼 환경(APNS, GCM)별로 개발해야 하는 불편함이 있었다.
-
하지만 FCM은 교차 플랫폼 메시지 솔루션이기 때문에 FCM을 이용해서 개발을 진행하면, 플랫폼에 종속되지 않고 Push 메시지를 전송할 수 있다.
-
즉, 위에서 얘기한 문제를 해결할 수 있는 좋은 대안이 된다.
-
-
FCM을 이용했을 때 얻을 수 있는 이점은 위에서 확인할 수 있다. 하지만 근본적으로 클라우드 메시징 서비스를 이용해서 Push 메시지를 보내는 이유는 무엇일까? 서버 단에서 직접 로직을 처리해도 상관 없지 않을까? 라는 생각이 들어서 이와 관련된 내용을 찾아보았다.
-
관련된 내용을 찾던 와중에 좋은 예시를 찾게 되어서 그 내용을 인용하고자 한다.
-
B라는 사람에게 어떠한 메시지를 전달하는 데 있어서, 아래와 같은 두 가지 상황을 생각해보자.
-
첫번째 상황은 A가 B에게 메시지를 전달하기 위해, 어플리케이션을 열어서 메시지를 보내면, A의 메시지는 해당 어플리케이션의 서버를 거쳐서 B에게 도달하게 되는 상황이다.
- A -> 어플리케이션 서버 -> B
-
두번째 상황은 어플리케이션을 통해서 서비스를 제공하는 회사에서 B에게 마케팅을 위한 메시지를 보내기 위해서 어플리케이션 서버를 통해서 B에게 메시지를 보내는 상황이다.
- 어플리케이션 서버 -> B
-
위 두 가지 상황에 대해서 B가 실시간으로 메시지를 받기 위해서 B는 서버에 계속 접속해 있어야 한다. 이것을 실제로 구현한다면, 많은 배터리와 네트워크 사용으로 인해 문제가 생길 수 있다.
-
클라우드 메시징 서비스를 이용하면, 위 문제를 어느정도 해결할 수 있다. 클라우드 메시징 서버를 중간에 둠으로써, 사용자는 낮은 배터리와 네트워크의 사용만으로도 메세지를 실시간으로 송수신 처리를 할 수 있기 때문이다.
-
A -> 어플리케이션 서버 -> 클라우드 메시징 서버 -> B
-
대략 위와 같은 형태로 작업을 처리하게 된다.
-
-
위와 같은 이유로 대부분의 어플리케이션 서비스들은 클라우드 메시징 서버를 경유해서, 실시간으로 유저들에게 메시지를 전송해주고 있다.
-
FCM의 주요 특징
-
메세지 타입
-
FCM의 메세지 타입은 알림 메시지와 데이터 메시지로 구분할 수 있다.
-
보통 알림 메세지와 데이터 메시지를 같이 혼용해서 사용한다.
- 예를 들어, 휴대폰 푸시 알림 메세지는 알림 메세지를 이용하고, 알림 메세지를 클릭 하였을 때 앱 내 특정 페이지로 이동이나, 어떠한 액션은 데이터 메세지를 통해서 이뤄진다.
-
메세지 종류 | 알림 가능 여부 | 알림 저장 개수 | 알림 처리 방법 |
---|---|---|---|
알림 메시지 | 가능 | 여러 알림을 저장하나, OS 환경마다 다르다. | 앱이 백그라운드 일 때 |
데이터 메시지 | 가능 | 1개의 알림만 저장 | 앱이 포그라운드 일 때 |
-
타켓팅
- 단일 기기, 기기 그룹, 주제를 구독한 기기 3가지 방식으로 클라이언트 앱에 메시지를 배포할 수 있다.
종류 | 대상수 | 설명 |
---|---|---|
단일 기기 | 1개 | 하나의 기기(앱 기준) |
기기 그룹 | 20개 | 알림 키에 허용되는 그룹 |
주제 구독 | 1000개 | 등록 토큰에 구독된 기기 |
-
클라이언트 앱에서 메시지 전송
-
FCM을 이용하면 앱 서버에서 클라이언트 앱으로 다운 스트림 메세지를 보낼 수 있을 뿐만 아니라, 클라이언트 앱에서 앱 서버로도 업 스트림 메세지를 보낼 수 있다.
-
하지만 클라이언트 앱에서 앱 서버로 업 스트림 메세지를 보내기 위해서는 선행 조건이 필요하다. 그 부분은 밑에서 설명한다.
-
FCM의 동작 원리
-
크게 송신자, FCM Backend Server, 수신자로 구분한다.
FCM 공식 문서 사진 참조. -
송신자는 주로 앱 서버, HTTP 프로토콜을 사용하는 서버, Firebase Console GUI 등이 될 수 있고, 수신자는 우리가 흔히 사용하는 iOS 또는 Android 운영체제를 사용하는 모바일 기가 될 수 있다.
-
FCM Backend 서버는 실질적으로 앱 서버에서 요청을 받아서 메세지를 처리하는 서버에 해당된다.
-
FCM 클라우드 메시지의 흐름
- HTTP 프로토콜을 사용할 경우, FCM 클라우드 메시지가 처리되는 과정을 그림으로 나타내보면 다음과 같다.
FCM 구조 관련 블로그 사진 참조. -
앱 서버에서 FCM Backend 서버에 클라이언트 앱에 보내고자 하는 메세지를 담은 정보와, 서버의 인증 정보 클라이언트의 토큰을 담아서 HTTP POST를 요청을 보낸다.
-
요청을 받은 FCM Backend 서버는 요청을 통해 받은 메시지의 이상 유무에 따라 앱 서버에 적절한 응답을 보낸다.
-
이후 FCM Backend 서버에서 여러가지(우선 순위, 클라이언트 앱과의 통신 가능 여부 등)을 고려하여 매세지를 클라이언트 앱에 보낸다.
-
클라이언트 앱에서는 받은 메세지를 적절하게 처리하고 응답 메세지를 FCM Backend 서버로 보내게 된다.
-
요약하면, 앱 서버에서 FCM Backend 서버에 메시지 요청을 보내고, FCM Backend 서버는 사용자 기기에서 실행되는 클라이언트 앱에 메시지를 보내게 된다.
-
한 마디로 제대로 된 메시지만 만들어 주면 그것을 실제 기기로 전달하는 것은 FCM Backend에서 처리해줄 것이란 얘기다.
-
단, 위의 플로우 대로 메시지를 전송하려면 반드시 선행되어야 하는 작업이 있는데, 당연하겠지만 메시지를 수신할 클라이언트는 자신의 정보를 FCM Backend 서버에 등록해야 한다는 점이다.
-
클라이언트는 자신의 정보(토픽, 디바이스 정보)를 FCM Backend 서버에 등록해야 한다.
-
메시지를 전송할 주체(앱 서버)는 등록된 정보를 획득해야 하며, 해당 정보로 다운 스트림 메시지를 전송한다.
-
FCM을 사용하기 위한 서버 환경 구성.
-
FCM을 사용하기 위해 FCM Backend 서버와 통신을 위한 어플리케이션 서버를 구축하기 위해선 다음 3가지 규칙을 지켜야 한다.
-
FCM Backend 서버에 FCM에서 지정한 형식의 메시지 요청을 보낼 수 있어야 한다.
-
지수 백오프를 사용하여 요청을 처리하고 다시 보낼 수 있어야 한다.
-
*지수 백오프 : 요청이 실패할 때마다 다음 요청까지의 유휴시간 간격을 n배씩 늘리면서 재요청을 지연시키는 알고리즘이다.
- 임의 지연을 사용하여 연쇄 충돌을 방지하기 위해서 사용한다.
-
-
서버 승인 사용자의 인증 정보와 클라이언트의 등록 토큰을 안전하게 저장 할 수 있어야 한다.
-
서버 승인 사용자의 인증 정보 : 메시지를 보낼 앱 서버가 인증된 서버라는 것을 증명하는 정보.
-
클라이언트의 등록 토큰 : 메시지를 보내고자 하는 디바이스의 정보.
-
FCM Backend 서버와 통신을 위한 앱 서버의 옵션 선택사항.
-
FCM Backend 서버와 상호 작용할 방법을 결정해야 한다.
-
Firebase Admin SDK
-
Node.js, Java, Python, C#, Go 등의 프로그래밍 언어 지원.
-
기기에서 주제 구독 및 구독 취소가 가능하고, 다양한 타켓 플랫폼에 맞는 메세지 페이로드 구성.
-
나머지 옵션과는 다르게, 초기화 작업만 잘 진행하면 인증 처리를 자동으로 수행한다.
-
FCM에서 가장 권장하는 옵션이다.
-
-
FCM HTTP v1 API
- 가장 최신 프로토콜로서 보다 안전한 승인과 유연한 교차 플랫폼 메시징 기능 제공.
-
기존의 HTTP 프로토콜
-
XMPP 서버 프로토콜
-
클라이언트 애플리케이션에서 업스트림 메시징을 사용하려면 이 옵션을 선택해야 한다.
-
위에서 얘기했던 FCM의 마지막 특징인 업스트림 메시징을 사용하려면, 다른 선택지 없이 이 옵션을 선택해야 한다.
-
Sample Code
-
내가 작성한 샘플 코드의 경우, FCM을 서버단에서 사용할 경우의 샘플 코드이고, 언어 및 프레임워크는 Java와 Spring이다.
-
초기 Dependency 설정. (Firebase Admin SDK 추가)
- Firebase 의존성 추가.
<dependency>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>6.8.1</version>
</dependency>
-
SDK 초기화.
-
SDK 초기화를 진행할 때 Firebase 인증 처리를 해줘야 하는데, 먼저 Firebase 프로젝트 생성 후, 거기 설정에서 키를 발급받아야 한다.
-
본인의 경우 GCP의 서비스 계정 그룹 안에 Firebase가 이미 등록되어 있어서, GCP Credential로 인증 처리를 해주었다.
-
private FirebaseApp createFirebaseApp() {
FirebaseOptions options = null;
ClassPathResource resource =
new ClassPathResource(gcpCredentialFile);
try (InputStream is = resource.getInputStream()) {
options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(is))
.setDatabaseUrl("https://[My-Database-URL]
.firebaseio.com")
.build();
} catch (IOException e) {
log.error(e.getMessage());
}
return FirebaseApp.initializeApp(options);
}
-
특정 기기에 대한 메세지 전송
- 다음과 같은 Sample Code를 통해서 특정 사용자에게 메시지를 Push 할 수 있다.
private void sendToToken(Push push) throws FirebaseMessagingException {
Message message = Message.builder()
.setNotification(Notification.builder()
.setTitle("[광고] 테스트 광고")
.setBody("테스트 내용")
.build())
// Device를 특정할 수 있는 토큰.
.setToken(push.getRegistrationToken())
.build();
String response = FirebaseMessaging.getInstance().send(message);
log.debug("Successfully sent message: " + response);
}
-
여러 기기에 대한 메세지 전송
-
다음과 같은 Sample Code를 통해서 여러 기기에 메시지를 보낼 수 있다.
-
현재는 내 디바이스의 토큰 밖에 없어서 다음과 같이 작성하였지만, List에 토큰 ID만 잘 담아서 MulticastMessage에 설정만 해주면 문제 없이 동작한다.
-
private void multipleSendToToken(Push push)
throws FirebaseMessagingException {
List<String> tokenList = IntStream.rangeClosed(1, 30).mapToObj(index
-> push.getRegistrationToken()).collect(Collectors.toList());
MulticastMessage message = MulticastMessage.builder()
.setNotification(Notification.builder()
.setTitle("[많은 양의 메세지] 알람입니다.")
.setBody("많은 알람에 당황하지 마세요.")
.build())
.addAllTokens(tokenList)
.build();
BatchResponse response = FirebaseMessaging.getInstance()
.sendMulticast(message);
if (response.getFailureCount() > 0) {
List<SendResponse> responses = response.getResponses();
List<String> failedTokens = new ArrayList<>();
for (int i = 0; i < responses.size(); i++) {
if (!responses.get(i).isSuccessful()) {
failedTokens.add(tokenList.get(i));
}
}
System.out.println("List of tokens that caused failures: "
+ failedTokens);
}
}
-
특정 주제 구독 및 주제 구독자에게 메세지 전송
-
다음 Sample Code를 통해서 특정 기기를 특정 주제에 구독시킬 수 있다.
-
구독하고자 하는 주제가 현재 존재하지 않는다면 FCM Backend에서 새롭게 생성해주고, 존재한다면 해당 주제에 구독처리가 된다.
-
물론 주제 구독이 가능하기 때문에, 구독 취소 또한 가능하다.
- 밑에서 사용하는 subscribeToTopic 메소드를 unsubscribeFromTopic으로 변경하면 구독 취소도 FCM Backend에 요청할 수 있다.
-
여기서 주의해야 할 점은 원인을 정확하게 파악하진 못했지만, 주제의 이름은 한글일 경우 Invalid topic name이라는 에러가 발생하게 된다.
-
그 이후에 해당 주제에 구독된 클라이언트 기기에 메세지를 전송할 수 있다.
-
그리고 한 기기는 여러 주제에 구독할 수 있고, 여러 주제에 구독했다면 구독한 주제별로 보내는 메세지를 모두 받게 된다.
-
예를 들어, 구글과 애플이란 주제 두 개를 구독했다면, 두 주제에 해당하는 메세지를 모두 받게 된다.
-
private void sendSubscribeTopic(Push push)
throws FirebaseMessagingException {
List<String> registrationTokens =
Collections.singletonList(push.getRegistrationToken());
TopicManagementResponse response = FirebaseMessaging.getInstance()
.subscribeToTopic(Collections.singletonList
(push.getRegistrationToken()), "Apple News");
System.out.println(response.getSuccessCount() +
" tokens were subscribed successfully");
Message message = Message.builder()
.setNotification(Notification.builder()
.setTitle("[Apple News] IPhone SE 출시 임박!!!")
.setBody("빠르게 사전 예약 하세요. 주문이 폭주하고 있습니다.")
.build())
.setTopic("Apple News")
.build();
String response2 = FirebaseMessaging.getInstance().send(message);
log.debug("Successfully sent message: " + response2);
}
-
일괄 메시지 전송
-
하나의 메세지를 많은 사람들에게 보낼 수 있을 뿐만 아니라, 다양한 메세지를 다양한 사람들에게 일괄 전송하는 것도 가능하다.
-
밑의 Sample Code는 특정 기기의 클라이언트와, 주제를 구독한 기기에게 성격이 다른 메세지를 일괄 전송하고 있다.
-
private void multipleEachOtherSend(Push push)
throws FirebaseMessagingException {
List<Message> messages = Arrays.asList(
Message.builder()
.setNotification(Notification.builder()
.setTitle("[한국전기공사] 공과금 수납 일자 오늘까지..")
.setBody("오늘까지 수납이 되지 않을 경우,
전기가 끊길 수 있습니다.")
.build())
.setToken(push.getRegistrationToken())
.build(),
Message.builder()
.setNotification(Notification.builder()
.setTitle("[배달의 민족] 모두에게 드리는 특별한 쿠폰")
.setBody("음식 종류에 상관없이 모두 3000원 할인")
.build())
.setTopic("배달의 민족")
.build()
);
BatchResponse response = FirebaseMessaging.getInstance()
.sendAll(messages);
System.out.println(response.getSuccessCount() +
" messages were sent successfully");
}
마치며
이전에 FCM을 사용해본 적이 없었기에 이번에 FCM에 대해 알아보게 되면서, 처음 알게 된 내용이 많았다. 알게된 내용을 포스팅으로 남겨서 내용을 공유하고 싶어서 이번 포스팅을 작성하게 되었다. 내용이 엄청 고도화된 내용은 아니기에 다음에 시간이 더 생기면 더 고도화된 내용을 가지고 포스팅을 작성해보고자 한다. 이번 포스팅은 여기서 마무리 짓고 다음에 더 새로운 포스팅을 작성해보도록 하겠다.