Nine MVP's Blog

01/04/2014

มาแล้ว ASP.NET Identity 2.0 Released!

Filed under: Uncategorized — Tags: , , — Nine MVP @ 8:16 am

มาแล้ว ASP.NET Identity 2.0 Released! ‪#‎เพิ่งใช้v1ทำระบบไป‬ =”=

Announcing RTM of ASP.NET Identity 2.0.0 New Feature เยอะมากกกก

  • Two-Factor Authentication
  • Account Lockout
  • Account Confirmation
  • Password Reset
  • Security Stamp (Sign out everywhere)
  • Make the type of Primary Key be extensible for Users and Roles
  • Support IQueryable on Users and Roles
  • Delete User account
  • IdentityFactory Middleware/ CreatePerOwinContext – UserManager,DbContext
  • Indexing on Username
  • Enhanced Password Validator

V2 รุ่นนี้ทำงานกับ EF 6.1.0

มีตัวอย่างบน nuget ให้ใช้งานโดยติดตั้งบน Empty web project *ตัวอย่างเป็นMVC

PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre

 

ref: http://blogs.msdn.com/b/webdev/archive/2014/03/20/test-announcing-rtm-of-asp-net-identity-2-0-0.aspx

Advertisements

24/10/2012

ASP.NET MVC Series : Web Push Technology II.2

Filed under: ASP.NET, ASP.NET MVC, C#, html5, javascript, knockout, WEB, websockets — Tags: , , , , , , , — Nine MVP @ 4:10 pm

จาก ตอนที่ I ผมได้แนะนำการทำงานพื้นฐานของคำสั่งทั้ง javascript และ protocol กันไปแล้ว และเมื่อที่ผ่านมา ตอนที่ II.1 ได้พูดถึงการทำ Push Mail Service ด้วย SSE

สำหรับตอนนี้เราจะมาทดสอบตัว websockets โดยใช้โจทย์เดิมกัน  ซึ่งสำหรับตอนนี้จะเป็นการเปลี่ยนการทำงานจากการใช้ SSE มาเป็น WebSockets Engine ในการสื่อสารกับ Push Mail Service    ซึ่งจะมีการปรับเปลี่ยนทั้ง client javascript  และ Web API Service มาดูกัน

การทำงานและ output จะเหมือนกับโปรเจ็ค SSE ก่อนนี้ทุกอย่าง ดังนั้นผมจะอธิบายเพียงส่วนที่แตกต่างกันครับ

 

แนะนำ MvcPushWS Project

ส่วนทีต้องติดตั้งเพิ่มเติมสำหรับ MvcPushWS Project ก็คือ

Microsoft.WebSockets โดยหาได้จาก nuget windows ตามภาพด้านล่าง

image

 

สำหรับ Flow โปรแกรมมีจำนวน step มี 9 จุดที่น่าสนใจ

ลำดับที่ 0.-3. ผมจะข้ามไปโดยหากสนใจจะอ่านให้กลับไปดู ตอนที่ II.1 ครับ

image

 

4. เมื่อ user มี email รีเทิร์นค่ากลับมาก็ให้ทำการ connect to Push Service

ใน javascript function InitialWS(); จะเป็นการสร้างและกำหนดค่าการทำงานของ WebSockets  ให้ชี้ไปยัง EmailServiceController (WebAPI) ซึ่งเป็น push service ที่เราสร้างขึ้นมาสำหรับส่ง email ใหม่ที่ส่งเข้ามาจากผู้ใช้อื่นๆ โดยจะมีการ add event ไว้ 1 คือ onmessage

   1: //WS Setup

   2:     function InitialWS() {

   3:         if ("WebSocket" in window) {

   4:             var uri = 'ws://' + window.location.host + '/api/EmailService/?email=' + viewModel.email();

   5:             socket = new WebSocket(uri);

   6:             socket.onmessage = function(e) {

   7:                 console.log(e.data); // log 

   8:                 var mails = JSON.parse(e.data);

   9:                 //alert from email

  10:                 showNewMessageAlert(mails[0].From);

  11:                 //binding array object to viewmodel(template binding)

  12:                 viewModel.messages.valueWillMutate();

  13:                 ko.utils.arrayPushAll(viewModel.messages(), mails);

  14:                 viewModel.messages.valueHasMutated();

  15:                 //sort inbox item

  16:                 SortMessageDesc();

  17:             };

  18:         } else {

  19:             // the browser doesn't support WebSockets

  20:             alert("WebSockets NOT supported here!\r\n\r\nBrowser: " + navigator.userAgent + "\r\n\r\n");

  21:         }

  22:     }

  1. line3: ทำการตรวจสอบว่า user browser รองรับการทำงานของ websockets หรือไม่
  2. line4: สร้าง url ปลายทางไปยัง push mail service และส่ง email pameter ไปด้วย
  3. line5: สร้างตัวแปร WebSocket และส่ง url ให้เพื่อทำการติดต่อกับ service
  4. line6-8: ทำการกำหนดการทำงานให้กับ onmessage event โดยให้เอาข้อมูลที่ส่งมาจากทางฝั่ง service ทำการ log และแปลงค่าเป็น json object
  5. line10-16: ทำการแสดงผล popup, โยนเข้า knockout model แล้วจัดลำดับ email ใน inbox ให้ DESC ReceivedDate

เมื่อมีการทำการ new WebSocket(url) ตรงนี้จะทำงานทันที โดยติดต่อไปยัง EmailServiceController.Get() Action โดยมี request header หน้าตาแบบนี้

           WebSockets Request Header

image

มีพารามิเตอร์ที่เกี่ยวข้องกับ websocket ดังนี้

  • Upgrade: websocket
  • Connection: xxx, Upgrade

เป็นค่าเพื่อบอก push email service ว่านี่เป็นการติดต่อมาจาก websocket  ให้ทำการสลับการทำงานจาก HTTP Protocol ไปเป็น WebSocket Protocol

  • Sec-WebSocket-Key: kmDqIkNBFU0iWMWb5fNiPQ==

จะอยู่ในรูปของ base64 encode  ทาง push email service จะเอาค่านี้ไปใช้งานเพื่อสร้าง handshake สำหรับคุยกันระหว่าง websocket client/server

ส่วนของ origin url จะถูกสร้างโดย browser

  • Sec-WebSocket-Version: 13

เป็นการบอก websocket client ว่าทางฝั่ง Client Browser นั้นใช้ protocol version ไหน ซึ่ง version 13 นั้นเป็น final spec ที่ทาง IEFT กำหนดให้เป็น standard

 

5. Create PushEmailHandler for new subscriber (EmailServiceController)

เมื่อ websocket มีการทำ connect มาที่ EmailService มีการสั่งยิงมาด้วย GET (WebSocket ทำงานโดยใช้ GET) มาเข้า HttpGet Action ซึ่งมีอยู่ตัวเดียวใน EmailServiceController ก็คือ

Get(HttpRequestMessage request)  ซึ่งจะมีการสร้าง WebSocketHandler class (มีใน Microsoft.Websockets จาก Nuget) ขี้นใหม่สำหรับ PushMailWSHandler เอาไว้ช่วยจัดการกับ ws request

   1: public class EmailServiceController : ApiController

   2: {

   3:     //Get

   4:     public HttpResponseMessage Get(HttpRequestMessage request)

   5:     {

   6:         //accept only websockets request

   7:         if (!HttpContext.Current.IsWebSocketRequest)

   8:             return request.CreateErrorResponse(HttpStatusCode.Forbidden, "the service support only websocket client");

   9:  

  10:         //get user's email

  11:         var email = request.QueryString("email");

  12:  

  13:         //user's email is required parameter

  14:         if (string.IsNullOrEmpty(email.Trim()))

  15:             return request.CreateErrorResponse(HttpStatusCode.BadRequest, "the service required users email parameter.");

  16:  

  17:         //register websockets handler to context

  18:         HttpContext.Current.AcceptWebSocketRequest(new PushMailWSHandler(email));

  19:         return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);

  20:     }

  21: }

  1. line7-8: ตรวจสอบว่าเป็นการส่งค่าสั่งมาจาก websocket หรือไม่ถ้าไม่ก็ส่ง error response แบบ forbidden พร้อม error message กลับไป
  2. line11: อ่าน email parameter มาจาก QueryString
  3. line14-15: หากไม่พบ email parameter ก็ส่ง error response แบบ badrequest พร้อม error message กลับไป
  4. line18-19: กำหนดให้ HttpContext ทำการรับ websocket handler ตัวใหม่สำหรับ pushmail ที่สร้างไว้ (เล่าตอนหลัง) และสร้าง response ตาม handler ที่เพิ่งกำหนดเข้าไปใหม่ ก็คือ switch to websocket protocol

  อธิบายตัว PushMailHandler class

ได้ทำการสืบทอดจาก WebSocketHandler Class ที่มีมากับ Microsoft.WebSockets โดยเมื่อมีการเรียก constructor ให้ทำงานโดย remove old pushmail handler ตัวเก่าออกไปก่อน แล้วค่อย add ตัวใหม่ที่เพิ่งสร้างเข้าไป (ทำตามแนวคิด *มาทีหลังดังกว่า)

   1: using MvcPushWS.Models;

   2: using Newtonsoft.Json;

   3: using Microsoft.Web.WebSockets;

   4:  

   5: namespace MvcPushWS.Controllers

   6: {

   7:     public class PushMailWSHandler : WebSocketHandler

   8:     {

   9:         //EFcontext

  10:         private MailBoxDBContext mbctx = new MailBoxDBContext();

  11:         //message pool

  12:         private static List<MailMessageView> _newMails = new List<MailMessageView>();

  13:         //subscriber pools

  14:         private static ConcurrentDictionary<string, SubscriberPushMail> _subscribers = 

  15:             new ConcurrentDictionary<string, SubscriberPushMail>();

  16:  

  17:         //constructor

  18:         public PushMailWSHandler(string email)

  19:         {

  20:             // add subscriber to pool

  21:             if (!string.IsNullOrEmpty(email.Trim()))

  22:             {

  23:                 if (_subscribers.ContainsKey(email))

  24:                 {

  25:                     SubscriberPushMail dead;

  26:                     if (_subscribers.TryRemove(email, out dead))

  27:                     {

  28:                         dead.client.Close();

  29:                         dead = null;

  30:                     }

  31:                 }

  32:                 _subscribers.TryAdd(email, new SubscriberPushMail

  33:                 {

  34:                     Email = email,

  35:                     client = this

  36:                 });

  37:  

  38:             }

  39:         }

  40:  

  41: ...............

*ไม่ขออธิบาย code เพราะการทำงานจะคล้ายกับตอนที่แล้ว

 

6. เมื่อตอบกลับการทำงานไปยัง WebSocket Client

ตัว WebSocketHandler จะเพิ่มตัวแปะลงใน Response Header เพื่อตอบกลับการทำงานไปยัง client

          WebSocket Response Header

image

  • Upgrade: Websocket
  • Connection: Upgrade
  • Sec-WebSocket-Accept: teTd3/I5X/xGOf2RHcLCvf49Jb4=

เมื่อมี response ตอบกลับมาตัว HTTP Code เราจะมองเห็นเป็น 101 Switching Protocols โดยการทำงานต่อจากนี้เราจะไม่สามารถดูได้จาก browser dev tool อีกเนื่องจากทำงานจะมีการส่งข้อมูลกันเป็น frame ไปมาตามรูปด้านล่างนี้

image

image

 

 

7. เมื่อ User2 ทำการส่งอีเมลไปหา User1

ต่อไปเมื่อมี user2 เปิด browser เข้ามา load mailbox ของตัวเอง และทำการส่งอีเมลถึง user1 หลังจากกดปุ่ม Send จะเข้ามาทำงานใน click ของ jquery ที่เราได้ hook event เอาไว้

โดยใช้ตัว websocket object โดยให้ส่ง viewModel.newEmail ไปด้วยในรูปแบบของ json string และให้ทำการล้างค่าของ newEmail หลังจากทำงานเสร็จแล้ว

   1: $(document).ready(function () {

   2:     

   3:     //binding mapping to viewModel

   4:     ko.applyBindings(viewModel);

   5:  

   6:     $("#sendMail").click(function () {

   7:         

   8:         if (socket.readyState != WebSocket.OPEN)

   9:             return;

  10:  

  11:         var data = JSON.stringify(ko.mapping.toJS(viewModel.newEmail));

  12:  

  13:         //use websocket send json string to pushmail service

  14:         socket.send(data);

  15:        

  16:         //clear value

  17:         viewModel.newEmail.To('');

  18:         viewModel.newEmail.Title('');

  19:         viewModel.newEmail.Message('');

  20:     });

  21:  

  22: });

  1. line14: เป็นการใช้ websocket ส่งข้อมูลกลับไปที่ push mail service ของเรา

หลังจากที่ websocket client ได้ส่ง json string เข้ามาจะไปทำงานที่ PushMailHandler ตัวที่เราได้เก็บเอาไว้ก่อนหน้านี้ โดยจะเกิด Event OnMessage(string data) ขึ้น

   1: public class PushMailWSHandler : WebSocketHandler

   2: {

   3:     .........................

   4:     .....................

   5:  

   6:     //User send a email message to service

   7:     public override void OnMessage(string message)

   8:     {

   9:         var dtomail = JsonConvert.DeserializeObject<MailMessageView>(message);

  10:         try

  11:         {

  12:             var user = mbctx.Users.SingleOrDefault(o => o.UserName == dtomail.Username);

  13:             if (user == null)

  14:                 this.Send(JsonConvert.SerializeObject(new {success = false, msg = "not found user"}));

  15:  

  16:             var dt = DateTime.Now;

  17:  

  18:             dtomail.ReceivedDate = dt.ToString("dd/MM/yyyy HH:mm:ss");

  19:             var mail = new MailMessage

  20:             {

  21:                 Id = dtomail.Id = Guid.NewGuid(),

  22:                 From = dtomail.From,

  23:                 To = dtomail.To,

  24:                 Title = dtomail.Title,

  25:                 Message = dtomail.Message,

  26:                 IsRead = false,

  27:                 ReceivedDate = dt,

  28:                 User_Id = user.Id

  29:             };

  30:             mbctx.MailMessages.Add(mail);

  31:             mbctx.SaveChanges();

  32:             //add alert email if the target subscriber is online

  33:             if (_subscribers.ContainsKey(mail.To))

  34:             {

  35:                 _newMails.Add(dtomail);

  36:                 //push email to subscriber

  37:                 PushMessageToClient(mail.To);

  38:             }

  39:         }

  40:         catch (Exception ex)

  41:         {

  42:             //this.Send(JsonConvert.SerializeObject(new { success = false, msg = ex.Message }));

  43:         }

  44:  

  45:     }

  46:  

  47:     //push email to target subscriber

  48:     private void PushMessageToClient(string toEmail)

  49:     {

  50:         var subscribers = _subscribers.Where(o => o.Key.ToLower() == toEmail.ToLower().Trim());

  51:         foreach (var subscriber in subscribers)

  52:         {

  53:             var mails = _newMails.Where(o => o.To.ToLower().Trim() == toEmail.Trim().ToLower()).ToList();

  54:             if (mails.Count > 0)

  55:             {

  56:                 subscriber.Value.client.Send(JsonConvert.SerializeObject(mails));

  57:                 _newMails.RemoveAll(o => o.To == toEmail);

  58:             }

  59:         }

  60:  

  61:  

  62:     }

  63: }

  1. line7: คือ method ที่เราได้ override ไว้เมื่อมี client ได้ส่งข้อมูลกลับมาที่ service
  2. line9-31: ทำการ convert json string กลับมาเป็น object เพื่อใช้บันทึกลง database
  3. line37: เรียก pushmessagetoClient ไปยัง client ตามอีเมลที่ส่งเข้ามา
  4. line56: เมื่อหา client ปลายทางเจอแล้วก็เรียกใช้ PushMailWSHandler ที่เราเก็บไว้ตอนแรกใน flow ที่ 5.  ทำการเรียก Send(json string); ส่งไป

 

ถ้าอยากเขียน WebSocket Server เองไม่ต้องการใช้ Microsoft.WebSocket ?

ก็เขียนแบบ TCP Server ปกติได้เลยครับ เพียงแต่ตอน response กลับก็เขียน header กลับไปให้ครบตามนี้

   1: var listener = new TcpListener(IPAddress.Loopback, 8181);

   2: listener.Start();

   3: using (var client = listener.AcceptTcpClient())

   4: using (var stream = client.GetStream())

   5: using (var reader = new StreamReader(stream))

   6: using (var writer = new StreamWriter(stream))

   7: {

   8:     writer.WriteLine("HTTP/1.1 101 Web Socket Protocol Handshake");

   9:     writer.WriteLine("Upgrade: WebSocket");

  10:     writer.WriteLine("Connection: Upgrade");

  11:     writer.WriteLine("WebSocket-Origin: http://localhost:8080");

  12:     writer.WriteLine("WebSocket-Location: ws://localhost:8181/websession");

  13:     writer.WriteLine("");

  14: }

  15: listener.Stop();

 

ทิ้งท้ายก่อนจบ

สำหรับตอนนี้เป็นการทดสอบการใช้งานตัว WebSocket และ WebSocket Server ขึ้นใช้งานเอง เพื่อสร้างความเข้าใจในการทำงาน  แต่ตัวอย่างข้างต้นที่ได้หยิบยกมายังไม่เหมาะจะนำไปใช้งานจริง ดังนั้นอาจจะต้องแก้ไขและปรับแต่งหรืออาจจะไปมองหา WebSocket Server ที่มีอยู่มากมายตอนนี้ ซึ่งน่าสนใจอยู่หลายตัวครับ SuperWebSocket.Net, XSockets.NET หรืออีกมากมายที่ http://en.wikipedia.org/wiki/Comparison_of_WebSocket_implementations 

ปล. ก่อนจะเอาโปรเจ็ค demo ไปรันทดสอบรบกวนตรวจสอบ browser ของคูณกันก่อนนะครับ Smile  http://websocketstest.com/

image

 

Download Demo Project

*มี database script อยู่ใน project folder เอาไปสร้าง database และอย่าลืมเปลี่ยน connectionstring ใหม่


SONY DSC

About Me:

Chalermpon Areepong : Nine (นาย)

Microsoft MVP Thailand ASP.NET

ASP.NET & MVC Developers Thailand Group :http://www.facebook.com/groups/MVCTHAIDEV/

Greatfriends.biz Community Leader : http://greatfriends.biz

Email : nine_biz-talk.net at hotmail dot com

Blog : https://nine69.wordpress.com

16/05/2011

ASP.NET 4.0 : High Scalable ASP.NET Session and Cache with Windows Server AppFabric Caching vol.2

Filed under: ASP.NET, Core System — Tags: , — Nine MVP @ 1:58 am
ตอนที่ 1 (Part 1)
ตอนที่ 2 (Part 2)

 

Programming Level:

  • Intermediate

Computer Skills:

  1. ASP.NET 4.0
  2. C#

Development Tool and Library

  1. OS: Windows 7 or Windows Server 2008 or later
  2. Visual Studio 2010 or later
  3. Windows Server AppFabric Caching (download for x86, for x64)
  4. ASP.NET 4 Providers for Windows Server AppFabric Caching (download)

Agenda

  • Introduce
  • Basic of ASP.NET Session and Cache
  • Sample ASP.NET Web Application with InProc Mode
  • Case Study: Problem of ASP.NET Web Application with InProc Mode
  • Solution: Implement Cache Server
  • Create and Setting AppFabric Cache for ASP.NET Web Application
  • Using AppFabric Cache within ASP.NET Web Application
  • Test Run ASP.NET Web Application and Monitoring Cache
  • Conclusion

Introduce

จากตอนที่แล้วผมได้กล่าวถึง Session State ใน Mode ต่าง ๆ  โดยเราจะมาทำความเข้าใจเกี่ยวกับหน่วยความจำภายในของ ASP.NET กัน และดูตัวอย่างเว็บที่ใช้งาน Session และ Cache ของ ASP.NET ด้วยค่าเริ่มต้นของ web.config

ต่อด้วยทำความรู้จักกับปัญหาของ ASP.NET Web Application ที่ออกแบบไว้โดยสถาปัตยกรรมแบบง่ายๆ ที่มีข้อจำกัดในหลาย ๆ ด้าน รวมไปถึงผลกระทบอื่น ๆ ซึ่งเราจะมาแก้ปัญหาเหล่านี้ด้วยการใช้ AppFabric Caching มาช่วยในการแก้ปัญหา

 

ASP.NET Session and Cache

เรามาทำความรู้จักกับหน่วยความจำที่ของ ASP.NET คร่าว ๆ กันก่อนครับ

Session

System.Web.HttpContext.Current.Session หรือ Session เป็นหน่วยความจำที่สามารถโปรแกรมเมอร์สามารถเก็บข้อมูลของ user แต่ละคนโดยแยกจากกัน ไม่สามารถใช้ร่วมกันได้ ซึ่งเซสชั่นจะยังมีอายุคงอยู่จนกว่าจะ timeout หรือสั่งทำลายโดยโปรแกรม

Session[“SessionName”] = null;

Session.Remove(“session name”);

Session.Abandon();

Cache มี 3 ชนิดครับ

Data Caching เป็น Programmatically สามารถเรียกใช้โดยอ้างถึง HttpContext.Current.Cache หรือ Cache ซึ่งเป็นหน่วยความจำที่เก็บข้อมูลคล้ายกับเซสชั่น แต่แคชสามารถแชร์กันใช้งานกันภายใน Web Application โดยทุกเซสชั่นสามารถเข้าถึงข้อมูลของแคชได้ สามารถจัดเก็บได้โดยโปรแกรมเมอร์เอง และการสร้างแคชจะต้องกำหนดอายุุของแคชเสมอ

var expiredDT = DateTime.Now.AddMinutes(10);
Cache.Insert("AllCustomers",custs, null, expiredDT, TimeSpan.Zero, CacheItemPriority.Default, null);

Output Caching เป็น Automatic Cache ของ Page โปรแกรมเมอร์ไม่สามารถสั่งเก็บค่าหรือเรียกมาใช้งานได้ การเก็บแคชจะกระทำโดย ASP.NET โดยเก็บข้อมูลตามค่า Vary Option ที่ตั้งไว้ และจะทำลายแคชตามอายุที่กำหนดไว้ โดยสามารถกำหนด directive tag ใน aspx page ไว้ประมาณนี้

<%@ OutputCache Duration="30" Location="Any" VaryByParam="none" %>

Fragment Caching เป็น Automatic Cache ของ user control มีลักษณะเหมือนกับ Output Caching แต่จะจัดเก็บแคชของบางส่วนใน Page เช่น user control เป็นต้น การใช้งานก็สามารถเพิ่ม directive tag ในหน้า ascx ดังนี้

<%@ OutputCache Duration="30" Location="Any" VaryByParam="none" %>

ข้อมูลที่จะสามารถเก็บไว้ใน Session และ Cache ได้คือข้อมูลที่สามารถ Serializable ได้เท่านั้น เช่น Primitive data type, POCO, XML เป็นต้น

เราก็ได้รู้จักกับ Session, Cache กันไปแล้ว ต่อไปเราจะไปดูการเรียกใช้งานในเว็บโปรแกรมกันครับ

 

ตัวอย่าง: ASP.NET Web Application with InProc Mode (Default Mode)

Download Code Sample Here

InProc Mode จะเป็นการเก็บข้อมูลเซสชั่นและแคชทั้งหมดเอาที่หน่วยความจำของเครื่อง Web Server โดยตัวอย่างเว็บนี้จะมีหน้า default.aspx เป็นส่วนที่ใช้ทำการทดสอบครับ ซึ่งจะมีการเรียกใช้งานหน่วยความจำของ ASP.NET ทั้ง Session, Cache Data, OutputCache ตามปกติดังนี้

image

image

ในส่วนที่ 1 ไว้สำหรับทดสอบ OutputCache ของ Page ซึ่งผมได้วาง @ OutputCahce เอาไว้ในหน้า default.aspx

<%@ OutputCache Duration="1" Location="Any" VaryByParam="none" %>

ปล. เอา tag ออกเพื่อทดสอบกรณีที่ไม่ใช้ OutputCache หรือกำหนดค่า 1 จะใกล้เคียงกับไม่เก็บแคช

GetDate Button
  1. protected void btnGetDate_Click(object sender, EventArgs e)
  2. {
  3.     lblShowDate.Text =  DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss");
  4. }

ทดสอบ 1 Duration=1 เมื่อกด Get Date Time button เพื่อดึงเวลาจะเห็นว่าเวลาจะเปลี่ยนแปลงตลอดทุกครั้งที่กดปุ่มเนื่องจาก กำหนดค่าไว้ต่ำสุดที่ 1 วินาที (แต่ถ้าไวพอ 1วิ กด2คลิ๊ก ก็ไม่เปลี่ยนจ้า)

ทดสอบ 2 Duration=15 ASP.NET จะเก็บแคชของหน้านั้นเอาไว้ 15 วินาทีโดยไม่ต้องกลับไปทำงานใน CodeBehind เพื่อโหลดข้อมูลกลับมาใหม่ สังเกตุได้ว่าเวลาจะไม่เปลี่ยนไปจนกว่าจะครบ 15 วินาที

ส่วนที่ 2 จะ load data มาจาก database และนำไปเก็บ ไว้ใน Session และ Cache

Load Data Button
  1. protected void btnLoadDataToSession_Click(object sender, EventArgs e)
  2. {
  3.     using( var db = new NorthwindEntities())
  4.     {
  5.         db.ContextOptions.LazyLoadingEnabled = false;
  6.         var custs = db.Customers.ToList();
  7.         var orders = db.Orders.ToList();
  8.        
  9.         // Stored to Session
  10.         // System.Web.HttpContext.Current.Session
  11.         Session["AllCustomers"] = custs;
  12.         Session["AllOrders"] = orders;
  13.  
  14.         // Stored to Cache
  15.         // System.Web.HttpContext.Current.Cache
  16.         var expiredDT = DateTime.Now.AddMinutes(10);
  17.         Cache.Insert("AllCustomers",custs, null,
  18.             expiredDT, TimeSpan.Zero, CacheItemPriority.Default, null);
  19.         Cache.Insert("AllOrders", orders, null,
  20.             expiredDT, TimeSpan.Zero, CacheItemPriority.Default, null);
  21.  
  22.         DropDownList1.Items.Clear();
  23.         DropDownList1.DataSource = Session.Keys;
  24.         DropDownList1.DataBind();
  25.         DropDownList1.Items.Insert(0, "");
  26.         DropDownList1.SelectedIndex = 0;
  27.  
  28.         DropDownList2.Items.Clear();
  29.         DropDownList2.DataSource = Cache.GetEnumerator().ForLinq().Select(pair => pair.Key);
  30.         DropDownList2.DataBind();
  31.         DropDownList2.Items.Insert(0, "");
  32.         DropDownList2.SelectedIndex = 0;
  33.  
  34.  
  35.     }
  36. }

line 3-7 ทำการดึงข้อมูลของ Customer, Order ขึ้นมาทั้งหมด

line 11-12 เก็บข้อมูล Customer, Order ลงใน Session ทั้งสองตัว

line 16-20 เก็บข้อมูล Customer, Order ลงใน Cache และกำหนดอายุของแคชไว้ที่ 10 นาที

line 22-32 ทำการผูกคีย์ของ Session และ Cache กับ Dropdownlist ทั้ง 2 เพื่อเอาไว้ใช้ในการดึงข้อมูล

และเราจะทำการดักจับไอเท็มของ Session และ Cache ไว้เพื่อทดสอบ scope การใช้งาน ว่าแตกต่างกันอย่างไร

Page Load Event
  1. protected void Page_Load(object sender, EventArgs e)
  2. {
  3.     if (!IsPostBack)
  4.     {
  5.         DropDownList1.Items.Clear();
  6.         DropDownList1.DataSource = Session.Keys;
  7.         DropDownList1.DataBind();
  8.         DropDownList1.Items.Insert(0, "");
  9.         DropDownList1.SelectedIndex = 0;
  10.  
  11.         DropDownList2.Items.Clear();
  12.         DropDownList2.DataSource = Cache.GetEnumerator().ForLinq().Select(pair => pair.Key);
  13.         DropDownList2.DataBind();
  14.         DropDownList2.Items.Insert(0, "");
  15.         DropDownList2.SelectedIndex = 0;
  16.     }
  17. }

ส่วนที่ 3 ใน dropdownlist ทั้งสองจะมี Key ที่ใช้ในการดึงข้อมูลออกมาจาก Session และ Cache เพื่อที่จะเลือกและผูกข้อมูลเข้ากับ DataGrid เพื่อแสดงผล เมื่อเลือก item ใน dropdownlist จะ postback กลับไปเพื่อดึงข้อมูลจาก session, cache ที่ต้องการ

Dropdown IndexChanged Code
  1. // Get Data From Session
  2. protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
  3. {
  4.     List<Order> orders = null;
  5.     List<Customer> custs = null;
  6.     if (!string.IsNullOrEmpty(DropDownList1.SelectedValue))
  7.     {
  8.         if (DropDownList1.SelectedValue == "AllCustomers")
  9.         {
  10.             custs = Session[DropDownList1.SelectedValue] as List<Customer>;
  11.             GridView1.DataSource = custs;
  12.             GridView1.DataBind();
  13.         }
  14.         else if (DropDownList1.SelectedValue == "AllOrders")
  15.         {
  16.             orders = Session[DropDownList1.SelectedValue] as List<Order>;
  17.             GridView1.DataSource = orders;
  18.             GridView1.DataBind();
  19.         }
  20.     }
  21. }
  22.  
  23. //Get Data From Cache
  24. protected void DropDownList2_SelectedIndexChanged(object sender, EventArgs e)
  25. {
  26.     List<Order> orders = null;
  27.     List<Customer> custs = null;
  28.     if (!string.IsNullOrEmpty(DropDownList2.SelectedValue))
  29.     {
  30.         if (DropDownList2.SelectedValue == "AllCustomers")
  31.         {
  32.             custs = Cache.Get(DropDownList2.SelectedValue) as List<Customer>;
  33.             GridView1.DataSource = custs;
  34.             GridView1.DataBind();
  35.         }
  36.         else if (DropDownList1.SelectedValue == "AllOrders")
  37.         {
  38.             orders = Cache.Get(DropDownList2.SelectedValue) as List<Order>;
  39.             GridView1.DataSource = orders;
  40.             GridView1.DataBind();
  41.         }
  42.     }
  43. }

line 8-18 เป็นการดึงค่าโดยใช้ key AllCustomers, AllOrders ไปดึงค่าที่เก็บอยู่ใน Session และผูกข้อมูลเข้ากับ Gridview เพื่อแสดงค่า

line 30-40 เป็นการดึงค่าโดยใช้ key AllCustomers, AllOrders ไปดึงค่าที่เก็บอยู่ใน Cache และผูกข้อมูลเข้ากับ Gridview เพื่อแสดงค่า

และสุุดท้ายก็จะเป็น web.config ที่เป็น ASP.NET4 ของเว็บนี้ครับ (โล่งๆ ไม่มีอะไรเลย)

web.config
  1. <?xml version="1.0"?>
  2. <configuration>
  3.   <connectionStrings>
  4.     <add name="NorthwindEntities" connectionString="metadata=res://*/NorthwindDB.csdl|res://*/NorthwindDB.ssdl|
  5.          res://*/NorthwindDB.msl;provider=System.Data.SqlClient;provider connection string=&quot;
  6.          data source=.;initial catalog=Northwind;integrated security=True;multipleactiveresultsets=True;App=EntityFramework&quot;"
  7.          providerName="System.Data.EntityClient" />
  8.   </connectionStrings>
  9.  
  10.   <system.web>
  11.     <compilation debug="true" targetFramework="4.0" />
  12.   </system.web>
  13.  
  14.   <system.webServer>
  15.      <modules runAllManagedModulesForAllRequests="true"/>
  16.   </system.webServer>
  17. </configuration>

 

Case Study : ตัวอย่างปัญหาจากตัวอย่างเว็บด้านบน (Problem of ASP.NET Session and Cache with InProc Mode)

ในกรณีตัวอย่างนี้ ตัว web application วิเคราะห์และทดสอบการจำเป็นในการใช้ Session, Cache, OutputPage  ในส่วนที่เหมาะสมของเว็บแล้ว แต่ว่ายังใช้หน่วยความจำของ Web Server ในการเก็บข้อมูลเหล่านั้นตรง ๆ ก็คือ Session = InProc Mode และ default Cache Provider ที่มีมาให้นั่นเอง

เมื่อเราพัฒนาเว็บแอ๊พพลิเคชั่นผ่านการทดสอบในส่วนของบิสซิเนสเรียบร้อยแล้ว ก็จะทำเทสในเรื่อง Load Test เพื่อหาค่าความสามารถในการรองรับ concurrent user ที่จะเข้ามาใช้งานระบบ ซึ่งในตัวอย่างได้สมมติเอาว่าผ่านการทำ load test มาแล้ว ซึ่งทั้งระบบ Web Server และ Database นั้น รองรับจำนวน user อยู่ที่ 200 concurrent โดยใช้งานปกติจะมีจำนวน concurrent ประมาณ 80-150 คน

image

เมื่อถึงเวลาหนึ่งทางกิจการได้เติบโตขึ้น ทำให้จำนวนผู้เข้าใช้งานเว็บมีจำนวนมากขึ้นเป็นลำดับ ซึ่งอาจจะเป็นช่วงเวลาเช่นช่วงเที่ยง เย็น และวันหยุด พบว่าหลายครั้งที่ Web Server Down เพราะต้องบริการข้อมูลยูสเซอร์ที่เข้าใช้งานพร้อมกันจำนวน 300 user concurrents ซึ่งเกินจากที่ระบบจะรองรับได้ ดังภาพด้านล่าง

image

ผลคือระบบล่ม ทุกอย่างจะเริ่มต้นได้ใหม่ก็จนกว่าจะ restart Web Site/Database กันใหม่ แบบนี้ทำให้ธุรกิจเสียหายแน่ ๆ ครับ

ปัญหาและสาเหตุมีดังนี้

Client : เนื่อง user แต่ละคนจะต้องสร้าง session ขึ้นเป็นของตัวเอง และการกดเพื่อสร้าง request กลับไปที่ web server ทุกครั้ง และจำเป็นจะต้องเข้าเว็บเซอร์เวอร์เดิมทุกรีเควส หากเว็บเซอร์เวอร์ตัวนั้นล่มไป เซสชั่นทุกอย่างก็จะหายไปหมดเช่นกัน

Web Server : มีการเก็บ Session, Cache แยกจากกันในแต่ละเว็บเซอร์เวอร์ ซึ่งไม่สามารถที่จะแชร์ข้ามเครื่องกันได้ ทำให้ข้อมูลบางตัวที่ควรจะแชร์กันได้ต้องถูกสร้างขึ้นใหม่ทุกครั้งที่มี user เข้ามาใช้งาน

Database : มีการใช้งาน Disk อย่างหนัก เพราะทุก request ที่ส่งเข้ามาเกือบทั้งหมดจะต้องเข้าถึงข้อมูลตลอดเวลา

ก่อนที่เราจะเลือกทางแก้ปัญหาโดยการลงทุนเพิ่ม Web Server, Upgrade Database Server เหล่านั้น หากพิจารณากันให้ดี  ปัญหาที่สำคัญก็คือ

1. เกิดข้อจำกัดของ Web Server ในส่วนของ Memory สำหรับเก็บข้อมูลพวก Session, Cache โดยแยกไปตามขนาดของ RAM ที่ติดตั้งในแต่ไว้ใน Server

2. เกิดข้อจำกัดของ User Process ในการทำงาน โดยจะทำงานได้เพียงบน Web Server ตัวเดียวเท่านั้น

3. มีการเข้าถึง Database ตลอดเวลาเพื่อดึงข้อมูลที่ซ้ำๆกันทุกครั้ง ที่ผู้ใช้ร้องขอบริการเดิม ๆ

4. Web Server ใน Farm ทุกตัวควรจะเห็น Session ของ User คนเดิม เมื่อเกิดปัญหากรณี Web Server ตัวใด ๆ ที่ User ใช้งานอยู่เกิดล่ม ก็สามารถที่จะยังทำงานต่อด้วย web server ตัวอื่นได้ทัทีโดยข้อมูลปัจจุบันยังคงอยู่

ซึ่งปัญหาเหล่านี้แก้ได้ครับ

 

Solution : Implement Cache Server (AppFabric Caching)

จากตอนที่ 1 นั้นผมได้แนะนำตัว AppFabric Cache Service ไปแล้ว ซึ่งเราจะนำเข้ามาใช้แก้ไขปัญหาที่เกิดขึ้นข้างต้น โดยก่อนที่จะเข้าถึง Database เราก็วาง Cache Server เอาไว้ให้เป็นตัวบริการเก็บข้อมูลทั้ง Session, Cache ตามรูปดังนี้

image

จากโมเดลด้านบนนี้ เราใช้ Cache Server มาวางโดยเอาไว้แก้ไขปัญหาของ Web Server ดังนี้

1. เพิ่มขนาดของ Cache บน Web Server  โดยขนาดจะขึ้นอยู่กับหน่วยความจำของ Cache Server

2. เอาไว้เก็บ User Session แทนการเก็บบน Web Server ป้องกันปัญหากรณี web server ที่ user ใช้งานอยู่ขณะนั้นเกิดปัญหา ทำให้ Web Server อีกตัวสามารถทำงานแทน web server ตัวที่ล่มไปได้ทันที

3. เป็นข้อมูลที่ถูกเรีกยใช้บ่อย และข้อมูลนั้น อาจจะเป็น Static Data หรือมีการเปลี่ยนแปลงของข้อมูลน้อย  ลดภาระของ Database ในการดึงข้อมูลส่งให้ Web Server บ่อย ๆ

ต่อไปเข้าสู่ขั้นตอนการสร้างและนำ AppFabric Cache ไปใช้ใน ASP.NET กันครับ

 

Create and Setting AppFabric Cache for ASP.NET Web Application

ต่อไปเราจะทำการสร้างแคชดาต้าเบส และตั้งค่าเอาไว้ใช้งานกับ asp.net web application ของเราครับ

โดยจะสร้างแคชเอาไว้ 2 ตัวโดยให้ชือ่ว่า ASPNETSessionCache เอาไว้เก็บข้อมูลที่เป็นเซสชั่นและ ASPNETOutputCache เอาไว้เก็บข้อมูลแคช ทำตามขึ้นตอนดังนี้

1. เรียกหน้าต่าง Caching Administration Windows Powershell ออกมาใช้งาน

2. พิมพ์

Use-CacheCluster

3. สั่งสตาร์ท service ด้วยคำสั่ง

Start-CacheCluster

4. เพิ่ม user สำหรับเข้าใช้งานแคช ให้ Add Current user สำหรับ Debug

เลือก 1 ชุดคำสั่งตาม windows ที่ใช้ครับ

– กรณีใช้ service account  (all windows edition)

Grant-CacheAllowedClientAccount –Account “ComputerName\UserName”

– กรณีใช้ Network Server Account (win03, win03r2, win08, vista)

grant-cacheallowedclientaccount -Account "NETWORK SERVICE"

– ใช้ ASPNET User account ใน Windows 7/WS08R2:

grant-cacheallowedclientaccount -Account "IIS AppPool\ASP.NET v4.0"

5. สร้างแคชสำหรับ asp.net ไว้เก็บเซสชั่นชื่อ ASPNETSessionCache และ ASPNETOutputCache

New-Cache –CacheName ASPNETSessionCache –Eviction None –TimeToLive 60

New-Cache –CacheName ASPNETOutputCache –Eviction None –TimeToLive 60

6. เรียกดู Cache DB ที่สร้างไว้ทั้งสองก้อนว่าสำเร็จหรือไม่ด้วยคำสั่ง

Get-Cache

image

7. ตรวจสอบดูว่าแคชทั้ง 2 ว่าตั้งค่าไว้อย่างไรบ้าง

Get-CacheConfig ASPNETSessionCache

Get-CacheConfig ASPNETOutputCache

image

7. ตรวจสอบค่า Staticstics ว่ามีสถานะปัจจุบันอย่างไรบ้าง

Get-CacheStatistics ASPNETSessionCache

Get-CacheStatistics ASPNETOutputCache

image

แค่นี้ AppFabric Cache ก็พร้อมใช้งานแล้วครับ ต่อไปเราจะไปดูการตั้งค่าและเรียกใช้งานภายใน asp.net กัน

 

Using AppFabric Cache within ASP.NET Web Application

Download Code Sample Here

ในขั้นตอนนี้เราจะย้าย Session และ Cache จากหน่วยความจำภายในเครื่องไปเก็บไว้ใน AppFabric Cache

การเรียกใช้ AppFabric Cache ภายใน Web Application นั้น ในตอนนี้ผมขอเลือกใช้ Library ตัวนึงซึ่งมีความสามารถเยอะกว่า Cache Client ที่ไมโครซอฟต์มีมาให้ตั้งแต่ติดตั้ง AppFabric

การนำไปใช้งานนั้นมี 2 วิธีคือ ทำผ่าน web.config และ เขียนโค้ด

ในขั้นตอนแรก  ดาวน์โหลด ASP.NET 4 Providers for AppFabric Caching จาก CodePlex มาใช้งาน ให้ unzip ออกและมองหา “Microsoft.Web.DistributedCache.dll” โดยตัวนี้จะเป็น Custom Provider ที่เรียกใช้ Microsoft.ApplicationServer.Caching.Client.dll ของ AppFabric อีกทีนึง

Setting Method 1: Edit web.config

เปิด Web Application ขึ้นมาแก้ไข web.config ดังนี้

– เพิ่มค่า configSection สำหรับ Cache Client ของ Microsoft เพื่อให้รู้จัก dataCachcClient Section

web.config: configSections
  1. <section name="dataCacheClient"
  2.     type="Microsoft.ApplicationServer.Caching.DataCacheClientSection,
  3.         Microsoft.ApplicationServer.Caching.Core, Version=1.0.0.0,
  4.         Culture=neutral, PublicKeyToken=31bf3856ad364e35"
  5.     allowLocation="true"
  6.     allowDefinition="Everywhere"/>

– เพิ่ม dataCacheClient Section เพื่อกำหนดที่อยู่ของ AppFabric Host และ Port

web.config: dataCacheClient
  1. <dataCacheClient>
  2.   <hosts>
  3.     <host name="Nine-NB" cachePort="22233" />
  4.   </hosts>
  5. </dataCacheClient>

– ตั้งค่าการใช้ Cache ของ ASP.NET ไปที่ AppFabric โดยกำหนดเพิ่มเติมใน <system.web>

web.config: <sessionState>
  1. <sessionState mode="Custom" customProvider="DistributedSessionAspSessionProvider">
  2.   <providers>
  3.     <!– specify the named cache for session data –>
  4.     <!–<add name="AppFabricCacheSessionStoreProvider"
  5.       type="Microsoft.ApplicationServer.Caching.DataCacheSessionStoreProvider"
  6.       cacheName="ASPNETSession"
  7.       sharedId="ASPNETSession"/>–>
  8.     <add name="DistributedSessionAspSessionProvider"
  9.          type="Microsoft.Web.DistributedCache.DistributedCacheSessionStateStoreProvider,
  10.          Microsoft.Web.DistributedCache"
  11.         cacheName="ASPNETSessionCache"
  12.           applicationName="ASPNETWeb"
  13.           useBlobMode="false" />
  14.   </providers>
  15. </sessionState>

line 8  ตั้งค่าชื่อของ provider ที่เราต้องการจะเพิ่มเติม

line 9 กำหนด type ของ dll ที่เรานำมาใช้

line11-13  เป็นการะบุ Cache โดยอ้างถึง ASPNETSession ที่สร้างไว้ใน AppFabric Cache, กำหนด ApplicationName ที่จะเข้าใช้แคชก้อนดังกล่าว และระบุไม่ใช่ Blob Mode

line – 1 กำหนด mode=”Custom” และเลือกใช้ customProivder=”DistributedSessionAspSessionProvider” ตามที่ได้ตั้งค่าไว้

– ตั้งค่าการใช้ Page Output Cache ของ ASP.NET ไปที่ AppFabric โดยกำหนดเพิ่มเติมใน <system.web>

web.config: <caching>
  1. <caching>
  2.   <outputCache defaultProvider="DistributedCache">
  3.     <providers>
  4.       <add name="DistributedCache"
  5.            type="Microsoft.Web.DistributedCache.DistributedCacheOutputCacheProvider,
  6.            Microsoft.Web.DistributedCache" cacheName="ASPNETOutputCache" applicationName ="ASPNETWeb" />
  7.     </providers>
  8.   </outputCache>
  9. </caching>

line 4 กำหนดชื่อของ Cache ที่เราใช้อ้างอิง

line 5 ใส่ type ของ dll ที่จะใช้ในการทำ page output cache

line 6 cacheName กำหนดให้ใช้แคชที่ชื่อ ASPNEToutputCache และกำหนดชื่อ applicationName เป็น ASPNETWeb

line 1 ที่ defaultProvider กำหนดให้ใช้ DistributedCache name ที่เราได้สร้างไว้

Setting Method 2: Cache with Programmatically

เพิ่มโค้ดใน Global.asax.cs ดังนี้

Code Snippet
  1. void Application_Start(object sender, EventArgs e)
  2. {
  3.     DistributedCacheSessionStateStoreProvider.CacheConnecting += DistributedCacheSessionStateStoreProvider_CacheConnecting;
  4.     DistributedCacheOutputCacheProvider.CacheConnecting += DistributedCacheOutputCacheProvider_CacheConnecting;
  5. }
  6.  
  7. void DistributedCacheOutputCacheProvider_CacheConnecting(object sender, CacheConnectingEventArgs e)
  8. {
  9.     e.CacheFactoryConfiguration = new DataCacheFactoryConfiguration()
  10.             {
  11.                 Servers = new DataCacheServerEndpoint[] {new DataCacheServerEndpoint("Nine-NB", 22233)}
  12.             };
  13.     e.CacheName = "ASPNETOutputCache";
  14. }
  15.  
  16. void DistributedCacheSessionStateStoreProvider_CacheConnecting(object sender, CacheConnectingEventArgs e)
  17. {
  18.     e.CacheFactoryConfiguration = new DataCacheFactoryConfiguration()
  19.         {
  20.             Servers = new DataCacheServerEndpoint[] { new DataCacheServerEndpoint("Nine-NB", 22233) }
  21.         };
  22.     e.CacheName = "ASPNETSessionCache";
  23. }

line 3-4 ทำการผูก event ให้ CacheSession และ OutputCache Provider เมื่อมีการร้องขอให้ทำการ connect ไปยัง Cache Server ก็ให้ไปทำ event Connecting ทั้งสอง

line 7-14 เป็น event ของ Output Cache โดยทำการ add host, port number และกำหนดชื่อของ Cache Name ที่จะใช้

line 16-23 เป็น event ของ Cache Session โดยทำการ add host, port number และกำหนดชื่อของ Cache Name ที่จะใช้

 

Test Run ASP.NET Web Application and Monitoring Cache

เริ่มแรกให้ทำการตรวจสอบว่า Cache ของ ASPNETSessionCache และ ASPNETOutputCache นั้นยังมีสถานะก่อนใช้งานเป็นดังนี้

image

ยังไม่มีการเก็บข้อมูลใด ๆ ลงใน Cache ทั้งสองก้อน    จากนั้นเราจะทำการ Run Web Site ด้วย Visual Studio 2010 ด้วยการกด F5 ได้ผลขึ้นดังนี้

image

เนื่องจากมี page output cache เกิดขึ้น ดงันั้นเราจะไปตรวจสอบที่ ASPNETOutputCache จะพบได้ว่ามีการเก็บ object ลงไปใน Cache เพิ่มดังนี้

image

ที่เห็นนี้จะเป็น Automatic Cache และ Session สำหรับ System ในการทำงานของ ASP.NET Web Application ซึ่งจะเกิดขึ้นทุกครั้งที่ User เข้ามาทำงานในหน้า Default.aspx

ต่อไปเราจะทำการ เก็บค่าลง Session และ Cache ด้วยการกด button ในหัวข้อที่ 2 Load Data

เมื่อ response กลับมา ลองไปคลิ๊กที่ downdown ทั้งสองจะพบว่ามี key ของทั้ง Session และ Cache ที่เราเก็บค่าไว้ใน AppFabric Caching นั่นเอง

ทีนี้เรากลับไปดูความเปลี่ยนแปลงที่เกิดขึ้นใน Cache ทั้งสองตัวกันครับ

image

ก็จะพบว่ามีจำนวนของ Item ที่เก็บเพิ่มขึ้นอันเนื่องมาจากการเก็บข้อมูลลงไปนั่นเอง

 

Conclusion

หวังว่าพอจะมองเห็นประโยชน์จากดีไซน์นี้ หากต้องการที่จะ scale ระบบ web server ให้ทำงานได้สมบูรณ์และมีประสิทธิภาพสูงสุดนั้น บทความนี้ก็เป็นอีกหนึ่งแนวทางในการนำไปสู่ผลสำเร็จนั้นครับ

ในตอนต่อไป ผมจะพูดถึงการนำ AppFabric Caching ไปใช้ใน MVC, EF, NHibernate เท่าที่จะพอนึกออกครับ และหากมีโอกาสผมจะทำการทดสอบในส่วนของการทำ Cache Cluster, Fault Torrelance Testing, Performance Tesing ต่อไปด้วยครับ


Download Code Sample Here

Chalermpon Areepong Nine (นาย)

Microsoft MVP Thailand ASP.NET

email : nine_biz-talk.net at hotmail dot com

17/04/2011

ASP.NET 4.0 : High Scalable ASP.NET Session and Cache with Windows Server AppFabric Caching vol.1

Filed under: ASP.NET, Core System — Tags: , — Nine MVP @ 1:10 am
Part 1
Part 2

Programming Level:

  • Intermediate

Computer Skills:

  1. ASP.NET 4.0

Development Tool and Library

  1. OS: Windows 7 or Windows Server 2008 R2
  2. Windows Server AppFabric Caching (download for x86, for x64)

Agenda

  • Introduce
  • ASP.NET Session Problem
  • ASP.NET Session Mode
  • Solution for Large Session State Problem
  • Advantages of AppFabric Caching
  • AppFabric Caching Architecture
  • Install And Config Windows Server AppFabric Caching (Non-Cluster)
  • Using Caching Administration Windows PowerShell
  • Summary

Introduce

หลายๆครั้งที่มีการถามถึงประสิทธิภาพการทำงานของ ASP.NET ในเรื่องการความเร็วตั้งแต่กด Request เข้าไปยัง Server จนกระทั้ง Response ข้อมูลออกมายัง Browser ของผู้ใช้งาน     ซึ่งมีหลายปัจจัยทีเดียวที่จะต้องออกแบบและปรัปปรุงขั้นตอนการทำงานเพื่อให้ตอบสนองผู้ใช้ได้อย่างรวดเร็วไม่ว่าจะเป็นในส่วนของการ Tuning Database เพิ่มความเร็วในการ Query Data, การใช้ paging query, เขียน code logic ให้มีประสิทธิภาพ, ออกแบบหน้า html, image ให้มีสัดส่วนที่เหมาะสม ขนาดเล็ก, การบีบอัดข้อมูลจาก Web Server ไปยัง browser ของผู้ใช้  รวมไปถึงการทำ Network Load Balance(NLB) เป็นต้น

ซึ่งวิธีการเหล่านี้ก็เป็นส่วนสำคัญในการสร้างระบบที่มีการทำงานที่รวดเร็วโดยทั้งหมด  แต่ปัจจัยที่สำคัญมากอีกตัวก็คือหน่วยความจำสำหรับระบบนั้นเอง โดยส่วนใหญ่มักจะมีปัญหาในการใช้งานเมื่อจำนวนยูสเซอร์มีจำนวนมากขึ้น โดยระบบหน่วยความจำที่สำคัญนั้นคือการใช้ Session, Internal Cache แม้จะมีหลายท่านพยายามหนีการใช้เซสชั่นแต่มั่นใจได้ครับ ไม่ทุกระบบที่จะหลีกเลี่ยงได้ หากเจอข้อจำกัดในการ load data และเงื่อนไขที่รัดตัว

ASP.NET Session Problem

สำหรับงานเว็บของ ASP.NET นั้น หลายๆระบบต่างไม่ได้ให้ความสำคัญในการกำหนด physical memory สำหรับใช้เก็บเซสชั่น โดยส่วนใหญ่จะใช้ค่า default ที่ Visual Studio สร้าง web.config มาให้ หากคุณใช้เครื่อง Web Server ที่มีเสป็คสูงก็อาจจะไม่ต้องกังวล เช่น มี RAM 8-16 GB เป็นต้น  แต่หาก web server ต้องติดตั้ง web app มากกว่า 1 ระบบแล้วนั้น มีโอกาสที่จะทำให้เกิดปัญหาเรื่องหน่วยความจำไม่พอ ซึ่งเอกชนส่วนใหญ่จะใช้การติดตั้งลักษณะนี้เนื่องจากประหยัดค่าใช้จ่าย

ในการทำงานบ่อยครั้งก็มีความจำเป็นในการโหลดข้อมูลจาก Database เข้ามาเก็บในเซสชั่น หลายๆครั้งนั้นมีความจำเป็นที่เลี่ยงไม่ได้ที่จะต้องโหลดข้อมูลจำนวนมาก ๆ เข้ามาเก็บไว้ เช่น Global Data สำหรับแชร์ให้ทุกเซสชั่น, ข้อมูลเพื่อรองรับการทำงานของยูสเซอร์กำลังใช้งานเว็บ เมื่อจำนวนยูสเซอร์มีการเข้าใช้งานในประมาณที่มากขึ้นจึงทำให้ IIS process มีขนาดใหญ่บางท่านอาจจะเจอว่ากิน ram ไปขนาด 4 GB. ทำให้ต้องมีการ upgrade web server อาจจะต้องเพิ่ม RAM หรือเพิ่ม server เป็นต้น

ASP.NET Session Model

เพื่อความเข้าใจในการแก้ปัญหาและพัฒนา ผมขออธิบายเกี่ยวกับ Session Mode ของ ASP.NET ที่มีอยู่ 5 Mode กันก่อนครับ

1. InProc mode เป็นโหมดที่ทำงานโดยเก็บข้อมูลไว้ใน RAM ของ Web Server ซึ่งเป็นโหมดพื้นฐานที่ตั้งค่ามาให้ใช้งาน

ข้อดี ง่ายและเป็นโหมดที่ทำงานได้เร็วทีสุด

ข้อเสีย แต่มีข้อจำกัดในการรองรับจำนวนข้อมูลและการแชร์ session ใน Farm และเมื่อ web server restart ทุกอย่างที่เก็บไว้ก็จะหายไปทันที

2. StateServer (OutProc) mode เป็นโหมดที่ทำงานโดยแยก Service ที่เป็นส่วนเสริมของ ASP.NET เองออกไปจาก IIS process ซึ่งมีชื่อว่า ASP.NET state service โดยจะมีติดตั้งให้ทันทีที่ลง .NET Framework ซึ่งจะอยู่ที่ “systemroot\Microsoft.NET\Framework\versionNumber\aspnet_state.exe”

ข้อดี ด้วยตัว state server นี้แม้หากว่า web server เกิด restart ตัวเองขึ้นมาคุณก็แน่ใจได้ว่าเซสชั่นนั้นยังคงถูกเก็บไว้อยู่แน่นอน ซึ่งเหมาะที่จะใช้สำหรับ Web Server ใน Farm และมีความเร็วในการทำงานเป็นลำดับรองจาก InProc Mode อยู่ 1 เท่าตัว

ข้อเสีย คือกิน RAM เหมือน InProc เพราะเก็บไว้ใน RAM เหมือนกับ InProc mode เพียงแต่แยก service ออกมาต่างหาก แปลว่าคุณควรจะมีเครื่องแยกออกมาและมีช่องเสียบ RAM เยอะๆ และเสียบ RAM ไว้เพื่อรองรับงานดังกล่าว แต่เมื่อ StateServer restart ทุกเซสชั่นก็จะหายไปหมดเช่นกัน

ตัวอย่าง web.config

<configuration>
  <system.web>
    <sessionStatemode=StateServerstateConnectionString=tcpip=SampleStateServer:42424
                  cookieless=falsetimeout=20/>
  </system.web>
</configuration>

3. SQLServer mode เป็นโหมดที่เก็บเซสชั่นไว้ใน Database

ข้อดี คุณสมบัติเหมือนกับ StateServer mode ทุกอย่าง และถึงแม้ว่า SqlServer จะ restart ข้อมูลเซสชั่นก็ยังคงถูกเก็บเอาไว้ใน Database ไม่หายไป และดีกว่าที่ว่าสามารถขยายความจุของเซสชั่นได้เท่ากับพื้นที่บน HDD ที่มีอยู่ เหมาะที่ใช้กับ Web Server ใน Farm

ข้อเสียที่ว่า ช้ากว่า InProc mode ประมาณ 3 เท่าตัว

ตัวอย่าง web.config

<configuration>
  <system.web>
    <sessionStatemode=SQLServersqlConnectionString=
                  Integrated Security=SSPI;data source=SampleSqlServer; />
  </system.web>
</configuration>

*** หากต้องการจะใช้งาน SqlServer Mode คุณต้องทำการ register database ด้วย aspnet_regsql.exe ตัวนี้ก่อน

4. Custom mode หาตัวอื่นมาใช้ทดแทนทั้ง 3 mode ที่กล่าวไปข้างต้น โดยอาจจะเก็บไว้ใน Disk, NoSql เป็นต้น ซึ่งเราจะมาพูดคุยและทดสอบใช้งานกันในตอนนี้

5. Off mode ไม่ใช้ Session (ฟังดูจะโหดร้ายไปหน่อย)

Solution for Large Session State Problem

แนวทางการการแก้ไขปัญหาเรื่อง RAM บน Web Server ไม่พอเพียงนั้น ก็มีแนวทางที่เป็น best practice ให้เห็นกันอยู่แล้วนะครับก็คือ

1. เลือกใช้ SQLServer Mode  โดยตัวอย่างก็มีเช่น SharePoint, MS CRM  ก็ถูกพัฒนา base on ASP.NET เช่นกัน ซึ่งได้เลือกที่จะแก้ปัญหานี้ด้วยการใช้ SQLServer Mode เป็นตัวเก็บ Session ซึ่งก็จะหมดปัญหาในเรื่อง web app ที่ต้องใช้หน่วยความจำขนาดใหญ่อีกต่อไป อาจจะทำงานช้าลงบ้างแต่ก็คุ้ม

2. เลือกใช้ Custom Mode อาจจะมองหา Distributed Cached System ที่ดีๆมาใช้งานเช่น Windows Server AppFabric Caching (velocity), MemCached, NCache เป็นต้น

ในตอนนี้ขอเลือก Custom Mode มาใช้แก้ปัญหาของ Server Memory โดยใช้ AppFabric Caching

Advantages of AppFabric Caching

1. ทำงานเร็วกว่า SqlServer mode 4 เท่า

2. ฟรีสำหรับ windows server 2008, windows 7

3. มี Client library สำหรับ .NET ให้ใช้งาน

4. สั่งงานได้จาก powershell และมี management tool ที่ใช้งานง่าย

5. High Availability ทำงานแบบ cluster node ได้ (for windows server 2008 enterprise edition only)

AppFabic Caching Architecture

image

Physical Architecture Diagram

กรณีที่ต้องทำ Web Server Farm การทำ Cluster node นั้นเป็นสิ่งจำเป็น จากรูปด้านบนนี้ จะเห็นว่า Cache Server นั้นมีจำนวน 3 Nodes โดยใช้ Cluster configuration Strorage location ที่เดียวกัน (ต้องอยู่ใน AD เท่านั้นถึงจะทำ cluster ได้) และก็สามารถเพิ่ม node เข้าไปได้อีก เพื่อรองรับการร้องขอเก็บบันทึกและอ่านข้อมูล Session/Cache ตามที่ต้องการ  โดยสามารถจัดการทั้ง cluster ได้โดยผ่าน PowerShell

image

Logical Architecture Diagram

จาก logical diagram ด้านบนนี้จะเห็นได้ว่าภายใน Cache Cluster นั้นมี Cache Host อยู่ 3 ตัว   และแถบเหลือง ๆ ก็คือ Cache มีไว้ใช้เก็บข้อมูลโดย Default Cache จะมีมาให้เสมอหลังติดตั้ง ส่วน Inventory และ Catalog นั้นเป็นส่วนที่สร้างเพิ่มเติม

สังเกตุว่า cache สามารถ Replicate ไปยัง Cache Host ทั้ง 3 ตัวได้  แต่การกำหนด Data Region นั้นจะไม่สามารถ replicate ข้อมูลได้

Install And Config Windows Server AppFabric Caching (Non-Cluster)

ก่อนอื่นตรวจาสอบว่าในเครื่องได้ติดตั้ง IIS 7 Manager for Remote Administration แล้วหรือไม่ ถ้ายังไม่ติดตั้ง ก็ดาวโหลดมาติดตั้งเสียก่อน

จากนั้นค่อยไป download AppFabric (for x86, for x64)

1. เมื่อรัน *.msi ติดตั้งขั้นตอนแรก

image

2.  ให้กดเลือก checkbox ทั้งสอง จากนั้น monitoring provider เลือก System.Data.SqlClient และ Persistance provider ให้เลือก sqlStoreProvider และกดปุ่ม configure.. โดยทำซ้ำตามขั้นตอนถัดไปทั้ง 2 ปุ่ม

image

3.  จากนั้นไปสร้าง database สำหรับเก็บ Cache ของเรา ผมได้สร้าง AppFabricMonitoringDB และ AppFabricPersistanceDB ไว้ใน SQL Server ก่อนนี้แล้ว

จากนั้นกด checkbox ทั้งสองตามรูปด้านล่างและเลือก Database ที่ได้สร้างไว้

image

4.  กดปุ่ม install

a4

5. เมื่อ install เสร็จก็จได้ภาพนี้ สังเกตุจะมี checkbox ถามว่าให้รัน configuration tool เลยหรือไม่

จากนั้นกด Finish

a5

6. ก็จะได้หน้าจอของตัว Configuration Wizard มา

a6

7. เช็คเลือก Set Caching Service config  เปลี่ยน Service Account เป็นชื่อ user ที่ต้องการ

ผมเลือก caching service configuration provider เป็น XML เนื่องจากติดตั้งบน Windows7 จึงไม่สามารถทำ Cluster ได้

และกำหนด ShareFolder ชื่อว่า \\Nine-NB\AppFabricCacheXml เอาไว้เก็บ xml config file

image

8.  กำหนด port สำหรับ Service ต่าง ๆ จะแก้ไขตามต้องการก็ได้ (ห้ามใช้ port ซ้ำกัน)

ตรวจเช็คว่าเครื่องตัวเองไม่ได้ติดตั้ง Firewall เพราะจะทำให้ติดต่อกับ service ไม่ได้

a8

9.  จบขั้นตอนการติดตั้ง

image

10. ก็จะ start IIS7 ขึ้นมาตามรูปและมี AppFabric Section เพิ่มเข้ามาตามภาพ

image

11. หลังจากติดตั้งเสร็จแล้วก็ลองมาตรวจสอบที่ Start –> Program –> Windows Server AppFabric  จะพบรายการดังนี้

a9

Using Caching Administration Windows PowerShell

ไปที่ Start –> All Programs –> Windows Server AppFabric

เลือก Caching Administration Windows PowerShell จะได้หน้าต่างตามนี้

image

1. Start-CacheCluster พิมพ์คำสั่งเพื่อสั่งให้ cache service เริ่มทำงาน จะได้เห็นว่า service กำลัง start

image

และจะพบว่า Service จะมี Status เป็น UP

image

2. Stop-CacheCluster  เป็นคำสั่งเพื่อ shutdown service

3. Restart-CacheCluster เป็นคำสั่งให้ Restart service

4. Get-CacheClusterHealth เป็นคำสั่งเพื่อตรวจดูสถานะของ service ว่าปัจจุบันเป็นอย่างไร

image

5. Grant-CacheAllowedClientAccount เป็นคำสั่ง Add Client Account เพื่อเข้าใช้งานระบบ cache

image

6. Get-Cache จะลิสรายการ cache ออกมาแสดงทั้ง cluster

image

7. New-Cache เป็นคำสั่งสร้าง cache

image

8. Get-CacheConfig  เป็นคำสั่งในการเรียกดูค่าคอนฟิกของ cache แต่ละตัว (ระวัง cache name เป็น case sensitive)

image

9. Set-CacheConfig เป็นคำสั่งในการตั้งค่าคอนฟิกให้ cache

10 Get-CacheStatistics เป็นคำสั่งในการดูค่าการทำงานที่ผ่านมาของ cache ตัวนั้น ๆ

image

11. Remove-Cache  เป็นคำสั่งในการลบ cache ตัวที่ต้องการ

image

*** get more information : Cache Administration with Windows PowerShell (Windows Server AppFabric Caching)

Summary

ในตอนนี้เราได้ทำความรู้จักกับปัญหาของเซสชั่นและทำความรู้จักกับ AppFabric Caching รวมไปถึงการติดตั้งและการใช้คำสั่งในการทำงานกับตัว Cache Cluster ในตอนหน้าเราจะมาย้าย ASP.NET Session ไปเก็บลงใน AppFabric Caching กันครับ


Chalermpon Areepong Nine (นาย)

Microsoft MVP Thailand ASP.NET

email : nine_biz-talk.net at hotmail dot com

Blog at WordPress.com.