Notifications - Push, Pull, Stream Notification #3


 Exchange 2013을 기반으로 개발을 진행하면서 맞닥뜨리게 된 케이스에 대해서 공유 하고자 한다. 이전에 잠깐 EWS(Exchange Web Service)를 통해 간단한 기능을 사용해본 경험이 전부라서 하나의 기능을 구현하기 위해 여러가지 방안에 대해서 바위에 계란치기로 직접 부딪혀 볼 수 밖에 없었다. 이런 힘들고 시간 싸움을 줄이는데 도움을 드리는데 일조 하고자 이곳에 공유 하고자 합니다. 비록 덜 정제 되고 문서 미비할 수 있지만 참고 사항으로 알아 두셨으면 합니다. 



 이번 포스트에서는 지난 시간에 다루지 못했던 프로그래머 관점에서 살펴 보고자 한다. Pull, Push, Stream 방식은 가각의 장단점이 있으니 적절히 판단하여 실 환경에 적용해야 하겠습니다. 각 장단점은 이전 포스트의 첫번째에서 비교를 해 놓았습니다.


1. Stream notifications

#region Streaming notifications test ExchangeService serviceConnection = EWSHelper.GetService();   //EWS를 반환 한다. StreamingSubscriptionConnection streamConnection = new StreamingSubscriptionConnection(serviceConnection30);  //Timeout은 1 ~ 30분, 하나의 커넥션을 만든다. FolderId[] foldersToWatch = null; StreamingSubscription streamingsubscription = null; foreach (var member in Members.GetUsers()) {     //비동기 상에서 overwrap이 되지 않도록 처리     var m = member;     var task = System.Threading.Tasks.Task.Factory.StartNew(() =>     {         Log.WriteLine(m + " : Subscription 등록중");         #region Server와 Impersonate를 진행         ExchangeService service = EWSHelper.GetService();   //GetService(member);         //가장할 사용자 정보         service.ImpersonatedUserId = new ImpersonatedUserId() { Id = mIdType = ConnectingIdType.SmtpAddress }; //구독에 필요한 정보를 세팅         StreamingSubscription streamSubscription = service.SubscribeToStreamingNotifications(                     new FolderId[] { WellKnownFolderName.Inbox },                     EventType.NewMail,                     EventType.Modified             );         //connection에 subscription를 추가하여 이벤트를 받을 수 있도록 함. //하나의 커넥션의 여러개의 Subscription을 추가한다. 기본적으로 StreamConnection은 2000를 넘으면 서버에서 Busy Exception을 발생시킨다.         streamConnection.AddSubscription(streamSubscription);         Log.WriteLine(m + " : Subscription 등록 완료");     });     tasks.Add(task); } #endregion //모든 비동기 겍체가 완료가 될때까지 대기 함. System.Threading.Tasks.Task.WaitAll(tasks.ToArray()); //Connections.TryAdd("Connection1", streamConnection); // 이벤트 등록 streamConnection.OnNotificationEvent += streamConnection_OnNotificationEvent; streamConnection.OnSubscriptionError += streamConnection_OnSubscriptionError; streamConnection.OnDisconnect += streamConnection_OnDisconnect; streamConnection.Open();

[코드1] Stream notification을 등록 요청 및 이벤트 핸들러 등록



 아래 "코드2"에서는 위에서 등록한 이벤트 핸들러에 대한 코드다.

/// <summary> /// Subscription 설정에 따른 Notification 이벤트 발생 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private void streamConnection_OnNotificationEvent(object senderNotificationEventArgs args) {     var task = System.Threading.Tasks.Task.Factory.StartNew(() =>         {             var message = string.Empty;             var id = args.Subscription.Service.ImpersonatedUserId.Id;             message = id + ";";             #region Binding된 데이터를 가지고 테스트             IEnumerable<ItemId> itemEvents = from e in args.Events.OfType<ItemEvent>()                                                 select e.ItemId;             //Item.Bind(args.Subscription.Service, itemId)   //하나의 객체에 대해서만 바인딩             var response = args.Subscription.Service.BindToItems(itemEventsnew PropertySet(BasePropertySet.IdOnlyEmailMessageSchema.From));             var items = response.Select(itemResponse => itemResponse.Item);             foreach (var item in items)             {                 EmailMessage msg = item as EmailMessage;                 if (msg != null)                 {                     message += msg.From.Address + "," + item.Id + "," + DateTime.Now.ToString() + ",watermark=" + args.Subscription.Watermark;                 }             }             #endregion 

            Log.WriteLine(message);         }); } /// <summary> /// Subscription 관련 에러 이벤트 발생 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> void streamConnection_OnSubscriptionError(object senderSubscriptionErrorEventArgs args) {              } /// <summary> /// Subscription Connection 설정 시간(최대 30분)이 지나서 Disconnect 이벤트 발생하면,  Connection 재 연결 처리 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> void streamConnection_OnDisconnect(object senderSubscriptionErrorEventArgs args) {     StreamingSubscriptionConnection connection = (StreamingSubscriptionConnection)sender;                  if (connection != null)     {         if (!connection.IsOpen)         {             //Connection이 끊어지면 다시 오픈하여 이벤트를 받을 수 있도록 함.             connection.Open();         }     } }

[코드2] 이벤트 핸들러


 위 코드에서 streamConnection_OnDisconnection 이벤트는 StreamConnection이 Timeout인해(등록시 30분으로 세팅하여 연결하였고 30분 지나면 OnDisconnection 발생 후 종료 한다) 연결이 끊어진다. 그래서 이 이벤트가 발생하면 계속 유지가 되도록 코딩이 되어 있다. OnNotificationEvent는 일반적인 시나리오에서 발생하는 이벤트이며 이곳에서 아이템에 대한 상태를 판단할 수 있다. 그리고 더 많은 정보를 가져오기 위해서는 Bind를 통해서 한번 더 서버에 접근해야 추가 정보를 가져올 수 있다.












Exchange flow diagram V1.pptx


Notifications - Push, Pull, Stream Notification #2


 Exchange 2013을 기반으로 개발을 진행하면서 맞닥뜨리게 된 케이스에 대해서 공유 하고자 한다. 이전에 잠깐 EWS(Exchange Web Service)를 통해 간단한 기능을 사용해본 경험이 전부라서 하나의 기능을 구현하기 위해 여러가지 방안에 대해서 바위에 계란치기로 직접 부딪혀 볼 수 밖에 없었다. 이런 힘들고 시간 싸움을 줄이는데 도움을 드리는데 일조 하고자 이곳에 공유 하고자 합니다. 비록 덜 정제 되고 문서 미비할 수 있지만 참고 사항으로 알아 두셨으면 합니다. 



 이전 포스트에서 Push, pull, Stream 방식으로 notification을 구독하는 컨셉에 대해서 알아 보았다. 이번 포스트에서는 실제 서비스 환경에서 고 가용성과 확장성을 고려한 환경에서 어떤 방식으로 이벤트 구독을 하는 것이 효과적인지에 대해서 알아 보도록 하자.



[그림1] 실서버 환경(고 가용성, 확장성 구조)


"그림1"은 고 가용성과 확장성을 고려해서 서비스를 하기 위한 구조라고 가정하자. 대 규모 서비스에서는 위와 같은 구조로 서비스를 할 것이다.




[그림2] Listener 서버가 별도의 서버로 구축된 환경


이와 같은 환경에서 Push notification을 사용해 이벤트를 구독하는 방안을 제안한다. Push notification 방식은 Listener이라는 별도의 서비스가 있어야 하며 다른 방법(Push, Stream)과 달리 세션을 유지 해야 하는 제약 사항이 없다. 그리하여 MBX에서 이벤트를 Listener로 넘겨주면 XML을 이용해서 정보를 파싱하여 알아내야 한다.




[그림3] CAS에 Listener 롤도 추가된 환경


 "그림3"은 CAS 서버와 Notification 이벤트를 구독하는 Listener이 하나의 서버로도 구축 할 수 있다. 서버 자원에 따라 선택하면 될 것이다.



 Push 이외에 Pull과 Stream은 EWS 사용 방법과 비슷하게 사용할 수 있으나 세션 유지가 되어야 함으로 서버가 다운 되었을 때 처리해야 하는 방식이 훨씬 복잡해진다. ( Notification을 동적으로 구독 추가 로직, 각 서버마다 어떤 사용자를 구독하는지 모니터링 필요, ... )  그럼으로 위와 같은 규모와 구조에서는 Push 방식으로 접근 하는게 더 효과적이라 할 수 있다.


Exchange flow diagram V1.pptx



이제 Notifications에 대해서 전체적인 흐름과 환경에 대해 알아보았으니 다음 포스트에서 실 코딩에 대해서 알아 보도록 하자.







Notifications - Push, Pull, Stream Notification #1


 Exchange 2013을 기반으로 개발을 진행하면서 맞닥뜨리게 된 케이스에 대해서 공유 하고자 한다. 이전에 잠깐 EWS(Exchange Web Service)를 통해 간단한 기능을 사용해본 경험이 전부라서 하나의 기능을 구현하기 위해 여러가지 방안에 대해서 바위에 계란치기로 직접 부딪혀 볼 수 밖에 없었다. 이런 힘들고 시간 싸움을 줄이는데 도움을 드리는데 일조 하고자 이곳에 공유 하고자 합니다. 비록 덜 정제 되고 문서 미비할 수 있지만 참고 사항으로 알아 두셨으면 합니다. 



 Exchange에는 메일(Item, 아이템 - 메일, 캘린더, 일정, 명함, ... - 이곳에서는 메일에 대해서만 언급 하도록 하겠다.)의 상태( 아래 "리스트1" 참고 )에 대해서 알아 낼 수 있는 방법이 Exchange에서 제공하는 EWS를 통해서 Notification이벤트로 알아 낼 수 있다.

public enum EventType
{
    Status = 0,
    NewMail = 1,
    Deleted = 2,
    Modified = 3,
    Moved = 4,
    Copied = 5,
    Created = 6,
    FreeBusyChanged = 7,
}

[리스트1] Notification 이벤트 타입


 EWS를 통해서 Notification을 알아 내는 방법은 아래와 같다.

  • Pull Notification
  • Push Notification
  • Stream Notification ( 2010 SP1 ↑ 지원 )


각 사용법에 대한 장단점은 아래 "표1", "표2", "표3"을 참고 하기 바란다.




Push Notification vs Pull Notification vs Stream Notification

Push Notifications

Pros

Cons

Nearly instantaneous notifications
거의 실시간으로 Notification을 받을 수 있고

Have to write a listener
http 리스너를 할 수 있는 서버가 필요

No wasted traffic

적은 트래픽

Listener must be addressable by the Exchange server

리스너를 받기 위해서 IP기반 주소가 필요

Does not require CAS affinity

CAS를 이용하지 않는다

 

[표1] Push Notification

 

Pull Notifications

Pros

Cons

Same simple request/response protocol as all other EWS web methods

EWS 웹 메소드와 같이 간단하게 사용

Receive notifications as frequently as client polls

클라이언트 Polling을 통해 자주 요청해야 한다.

Client does not need to be addressable (can be behind a proxy or firewall)

IP기반 주소가 필요 없다.

Wasted traffic

Polling 때문에 낭비되는 트래픽이 발생

Authentication is handles in the same way as for all other EWS web methods

다른 EWS 메소드와 같은 방식으로 인증

Fine tuning required to get optimal polling interval

최선의 Polling 주기를 조율해야 한다.

[표2] Pull Notification

 

Stream Notifications
(아직 공식적인 문서는 없으며 Pull, Push를 기반으로 작성 되었다.)

Pros

Cons

Same simple request/response protocol as all other EWS web methods

EWS 웹 메소드와 같이 간단하게 사용

Watermask를 사용해서 지난 메세지를 받을 수 없다.

(SyncFolderItems 으로 가능한지 테스트 중)

Client does not need to be addressable (can be behind a proxy or firewall)

IP기반 주소가 필요 없다.

즉시적인 Notifications을 받을 수 없다.

그렇지만 연결된 상태에서는 특정 주기(기본 5)마다 이벤트를 구독할 수 있다. - 이벤트 방식으로 동작

Authentication is handles in the same way as for all other EWS web methods

다른 EWS 메소드와 같은 방식으로 인증

 

[표3] Stream Notification






 다음은 전체적이 흐름을 이해하기 쉽도록 간단하게 도식을 살펴 보도록 하자. 

[그림1] Push notification


Push notification은 Register에서 CAS에 Push사용자를 등록하고 MBX에서는 Listener에게 발생된 이벤트를 즉시적으로 넘겨 준다. 다른 notification 방식보다 복잡하며 MBX에서 접근 할 수 있는 Listener 서버가 필요 하다. 그렇지만 세가지 방식중에서 가장 실시간 이벤트 정보를 받아 볼 수 있으므로 실시간 연동이 필요한 부분에서는 고려해 볼 만하다. 그렇지만 MBX에서 데이터를 XML 형식으로 넘겨 주기 때문에 Listener에서 XML을 파싱하는 작업이 수반되어야 한다.



[그림2] Pull notification


Pull notification은 CAS에 각 사용자를 Pulling하고 받은 정보를 받아와 처리한다. 다음 이벤트를 받기 위해서는 다시 같은 프로세스를 다시 실행해야 한다. Pull은 Receiver에서 주도적으로 CAS에 질의를 해야 하며 필요 없는 트래픽이 많이 발생 할 수 있거나 Pulling 주기에 따라 시간 차이가 발생 할 수 있다. 실 시간적인 처리가 필요한 시스템 연동에서는 데이터 불일치가 발생할 수 있다.



[그림3] Stream notification


 Stream notification은 Pull과 Push의 장점을 합쳐서 만든 방식이다. 1번 처럼 한번 등록이 되면 DisConnection이 되기 전까지는 주기적(기본적으로는 5초 단위)으로 CAS에서 Receiver에게 이벤트를 보내 준다. 그리고 EWS와 같은 방식으로 메소드를 사용할 수 있으며 별도의 파싱 작업을 해야 할 필요는 없다. 그렇지만 현 시점에서는 Watermask를 통해 지난 이벤트를 가지고 올 수 있는 방안을 제공하지 않고 있다.SyncFolderItems 으로 지난 Notification을 가져올 수 있는지 테스트 중에 있다. 관련 자료 - Stream Notification )


 지금까지 Notification을 구독하는 컨셉에 대해서 알아 보았다면 다음 포스트에서는 실 서버 환경에서 구축하는 방안에 대해서 알아 보도록 하겠다.


Exchange flow diagram V1.pptx

위 다이어그램 관련 작업하며 추가하고 있는 자료입니다. 시간이 지나면서 점점더 추가 하도록 하겠습니다.

+ Recent posts