Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
AWS + Windows(C#)で構築する.NET最先端技術によるハイパフォーマンスウェブアプリケーション開発実践
@仕事
http://grani.jp/
C#
@個人活動
http://neue.cc/
@neuecc
https://www.facebook.com/neuecc
神獄のヴァルハラゲート
モンスタハンターロアオブカード
AWS+C#によるウェブソーシャルゲーム
汎用的
C#
50%
AWS
20%
その他
30%
using
何故C#?
リリース後わずか半年でC#に全面移行
Windows
WinForms, WPF
Mac
Xamarin.Mac
Windows Tablet
Windows Store Application
Web Application
ASP.NET MVC/WebAPI, OWIN
Cloud
Windows Azure, AWS
C# Everywhere
Game
Unity, PlayStation Mobile SDK
Mobile
Xamarin.iOS
Xamarin.Android
Windows Phone 8 SDK
Embedded
Windows Embedded
.NET Micro Framework
NUI
Kinect, LeapMotion
Windows
WinForms, WPF
Mac
Xamarin.Mac
Windows Tablet
Windows Store Application
Web Application
ASP.NET MVC/WebAPI, OWIN
Cloud
Windows Azure, AWS
C# Everywhere - Current
Game
Unity, PlayStation Mobile SDK
Mobile
Xamarin.iOS
Xamarin.Android
Windows Phone 8 SDK
Embedded
Windows Embedded
.NET Micro Framework
NUI
Kinect, LeapMotion
Windows
WinForms, WPF
Mac
Xamarin.Mac
Windows Tablet
Windows Store Application
Web Application
ASP.NET MVC/WebAPI, OWIN
Cloud
Windows Azure, AWS
C# Everywhere - Future
Game
Unity, PlayStation Mobile SDK
Mobile
Xamarin.iOS
Xamarin.Android
Windows Phone 8 SDK
Embedded
Windows Embedded
.NET Micro Framework
NUI
Kinect, LeapMotion
on AWS
問題ない。
C#によるウェブソーシャルゲーム開発
現在の規模感
100 アプリケーションサーバー
10,000 リクエスト/秒
100,000,000 ページビュー/日
AWS + Windows(C#)で構築する.NET最先端技術によるハイパフォーマンスウェブアプリケーション開発実践
IIS8 (EC2 Windows Server 2012)
MySQL 5.6(RDS)
Redis(EC2 Amazon Linux)
Database
NoSQLでいい?
RDBMSの利点
RDSこそAWSを使う最大の理由!
SQL Server vs MySQL
AWS + C#の企業としては、
RDSとしての良さのほうを選ぶ
r3.8xlargeったら最強ね!
機能単位の垂直分割を採用
水平分割は避ける
機能単位の垂直分割を採用
水平分割は避ける
ヴァルハラゲートレベルの負荷でも、垂直分割で
耐えられているので(r3.8xlargeは素晴らしい)、ほ
とんどのアプリケーションは、大きなデメリット
を抱える水平分割する必要性はないのでは?
DB管理はGUIツールを使いたい
水平分割はツールと相性最悪
アプリケーションからはMasterのみ参照
同一AvailabilityZoneへ配置する
public interface ITypedConnection : IDisposable
{
DbConnection Slave { get; }
DbConnection Master { get; }
}
public BattleEntity SelectById(BattleConnection battle, int id)
{
return battle.Master.Query<BattleEntity>("select * from battle where id = @id", new { id });
}
public UserEntity SelectById(UserInfoConnection user, int id)
{
return user.Master.Query<UserEntity>("select * from user where id = @id", new { id });
}
コーディング時のミス防止(間違った接続の利用はコンパイル時に弾かれる)
テーブルの別DBへの分割時にも完全にコンパイルチェックが効くので安全に行える
C#(というか型付き言語)を使う利点
クエリは生SQLを手書き
Dapper
https://code.google.com/p/dapper-dot-net/
http://neue.cc/2013/08/06_423.html
connection.Query<Dog>("select * from dogs where id = @id", new { id = 100 })
テーブルと1:1で関連づいたクラス
T4テンプレート + ADO.NET GetSchema
Int → Enumへの変換などは生成後、手で修
正している。半自動生成、初回の雛形作成、
というぐらいの位置づけ
[Serializable]
[DataContract]
public class GuideTemplateMaster
{
[DataMember(Order = 1)]
public GuideCode GuideId { get; private set; }
[DataMember(Order = 2)]
public Int32 No { get; private set; }
[DataMember(Order = 3)]
public String Title { get; private set; }
[DataMember(Order = 4)]
public String Body { get; private set; }
[DataMember(Order = 5)]
public String LinkController { get; private set; }
[DataMember(Order = 6)]
public String LinkAction { get; private set; }
[DataMember(Order = 7)]
public Int32 Priority { get; private set; }
[DataMember(Order = 8)]
public NavicoCharacter NaviId { get; private set; }
[DataMember(Order = 9)]
public NavicoFaceType NaviPattern { get; private set; }
public override string ToString()
{
return ""
+ "GuideId : " + GuideId + "|"
+ "No : " + No + "|"
+ "Title : " + Title + "|"
+ "Body : " + Body + "|"
+ "LinkController : " + LinkController + "|"
+ "LinkAction : " + LinkAction + "|"
+ "Priority : " + Priority + "|"
+ "NaviId : " + NaviId + "|"
永久に保存する領域 – データベースなど
期間保存 – Memcached/RedisなどExpire付き
リクエスト単位 - HttpContext.Items
アプリケーション単位 – Static変数
永久に保存する領域 – データベースなど
期間保存 – Memcached/RedisなどExpire付き
リクエスト単位 - HttpContext.Items
アプリケーション単位 – Static変数
アイテム名など不変の情報はキャッシュ
public static class GuideTemplateMasterCache
{
public static readonly ReadOnlyCollection<GuideTemplateMaster> All;
public static readonly ReadOnlyDictionary<Tuple<GuideCode, Int32>, GuideTemplateMas
public static readonly ILookup<GuideCode, GuideTemplateMaster> ByGuideCode;
static GuideTemplateMasterCache()
{
using (var connection = new GeneralConnection())
{
All = connection.Master.QueryEnumerable<GuideTemplateMaster>(
"select * from GuideTemplateMaster").ToList().AsReadOnly();
}
ByGuideIdAndNo = All.ToDictionary(x => Tuple.Create(x.GuideId, x.No)).AsReadOnl
ByGuideCode = All.ToLookup(x => x.GuideId);
}
}
キャッシュ用コードもインデックス見
て使い方が判別できるものも、テーブ
ル定義と一緒に自動生成してしまう
(マスタじゃない普通のテーブルに関
しても、インデックスを見てデータ
ベースアクセサの雛形は自動生成して
ます)
結合はアプリケーション側で
LINQ to Objectsで簡単
Redis
インメモリKey-Valueストア
RDSの不得意な部分を補える
用途毎のグループ分けと単純分散
定時(21:00~21:30, 22:00~22:30など)開催のバト
ルの時だけ忙しいがそれ以外はほぼ0という極
端なグラフになるBattleグループ、など
Slave
vs ElastiCache Redis
Protocol Buffers
Speed?
Performance + Async
Time To First Byte
言語構文レベルでサポートされる非同期
var names = Members.Select(x => new
{
Name = x.GetName()
})
.ToArray();
var names = await Members.Select(async x => new
{
Name = await x.GetNameAsync()
})
.WhenAll();
Membersが10人だとして、GetNameが
2msかかると、同期だと10 * 2 = 20ms
非同期で一気に同時に取得すれば
2ms で済む
// 例えばmemcachedの場合
var memcached = new MemcachedClient();
// 3回アクセスがあって辛ぽよ
var a = memcached.Get("hoge"); // +10ms = 10ms
var b = memcached.Get("hage"); // +10ms = 20ms
var c = memcached.Get("huga"); // +10ms = 30ms
// 1度に問い合わせて、分配
var all = memcached.Get(new[] { "hoge", "hage", "huga" }); // +10ms
var a2 = all["hoge"];
var b2 = all["hage"];
var c2 = all["huga"];
別に非同期構文とかなくてもできるじゃん!?
// 例えばmemcachedの場合
var memcached = new MemcachedClient();
// 3回アクセスがあって辛ぽよ
var a = memcached.Get("hoge"); // +10ms = 10ms
var b = memcached.Get("hage"); // +10ms = 20ms
var c = memcached.Get("huga"); // +10ms = 30ms
// 1度に問い合わせて、分配
var all = memcached.Get(new[] { "hoge", "hage", "huga" }); // +10ms
var a2 = all["hoge"];
var b2 = all["hage"];
var c2 = all["huga"]; でもIncrとか、Get以外のコマンドは?
それに、こうしたコードってオブジェク
トモデルでまとめにくい!
性能優先 vs 設計優先の対立になるの?
全コマンドがパイプライン化可能
Client-Server間で4回の応答待ちが発生
パイプラインで呼ぶと、全部まとまってコマンド飛ば
せるので往復遅延時間が削減
全てが非同期で自動でパイプライン化される
var a = redis.TryGet("hoge"); // Taskなのでひどぅーき
var b = redis.TryGet("huga");
var c = redis.TryGet("hage");
await Task.WhenAll(a, b, c); // 10ms
var frontHPs = await field.OwnGuild.Members
.Where(x => x.Position == Position.Front)
.Select(async x => new
{
Name = await x.Name,
CurrentHP = (await x.UserStatus).CurrentHP
})
.WhenAll();
x.Nameやx.UserStatusはRedisへの
通信、こうして書いたコードは、自
動的にパイプライン化されて非同期
実行されている
// 自分の実行可能(TP不足じゃないとか)なアビリティをActionTypeでグループ分け
var abilities = (await field.OwnStatus.GetCommandAbilities())
.Where(x => x.CanExecute == CanExecuteReason.CanExecute)
.GroupBy(x => x.ActionType);
LINQと相性良い、
IntelliSensable超大事
そうしたLINQableのための
設計と性能が両立できる
Log for Performance
外部通信(HTTP, SQL, Redis)を全て記録する
アプリ側から行う利点
http://neue.cc/2013/07/30_420.html
public class HttpProfilingHandler : DelegatingHandler
{
static readonly Logger httpLogger = NLog.LogManager.GetLogger("Http");
public HttpProfilingHandler() : base(new HttpClientHandler()) { }
public HttpProfilingHandler(HttpMessageHandler innerHandler) : base(innerHandler){ }
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
// 通信の前後をStopwatchで測る
var sw = Stopwatch.StartNew();
var result = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
sw.Stop();
// 以下に好きなようにログ仕込む、例えばJSON化
httpLogger.Trace(ApplicationPerformanceLog.ToJson(
DateTime.Now, request.Method.ToString(),
request.RequestUri.ToString(),
sw.ElapsedMilliseconds));
return result;
}
HttpClientに対してDelegatingHandlerを挟むこ
とで処理の前後を簡単にフックできる
new HttpClient(new HttpProfilingHandler());
public class LoggingDbProfiler : IDbProfiler
{
// 中略
// コマンドが完了された時に呼ばれる
public void ExecuteFinish(System.Data.IDbCommand pro
ExecuteType executeType, S
{
commandText = profiledDbCommand.CommandText;
if (executeType != ExecuteType.Reader)
{
stopwatch.Stop();
sqlLogger.Trace(Newtonsoft.Json.JsonConvert.
{
date = DateTime.Now,
command = executeType,
key = commandText,
ms = stopwatch.ElapsedMilliseconds
}, Newtonsoft.Json.Formatting.None));
}
}
}
MiniProfilerに用意されている
IDbProfilerをカスタムし
て,ADO.NETのコネクションとして
使うことで自由に仕込める
var conn = new
ProfiledDbConnection(new
SqlConnection(), new
LoggingDbProfiler());
CloudStructures(自社製のRedisラ
イブラリ)に用意されてるプロファ
イラの口に通すことで、送った/受
け取ったオブジェクトなどがモニ
タできる
public class RedisProfiler : ICommandTracer
{
static readonly Logger redisLogger = NLog.LogMan
Stopwatch stopwatch;
RedisSettings usedSettings;
public void CommandStart(RedisSettings usedSetti
{
this.usedSettings = usedSettings;
stopwatch = Stopwatch.StartNew();
}
public void CommandFinish(object sentObject, obj
{
stopwatch.Stop();
var ms = (long)System.Math.Round(stopwatch.E
redisLogger.Trace(ApplicationPerformanceLog
.ToJsonWithHost(DateTime.Now,
usedSettings.Host, command,
key, ms));
}
}
記録したら簡単に見れなければならない
Glimpse
http://getglimpse.com/
目に見えてRedis並列実行
全体の実行時間のほか、
あらゆるメトリクスを
常時可視化
開発環境上では、常に全SQL発行に
対して、explain結果も出すように
している(手動でやるようだと絶
対にやらないので、機械的に自動
でやって、常に見えるところに置
かなければならない)
明らかにヤヴァそうなもの(Using
filesortとか)は警告する。これによ
り、開発者が「開発中」に、自分
で気づけるように誘導
・同一キーの重複時警告
・送信、受信オブジェクトのダンプ
・オブジェクトサイズ
・Expire残り時間
・通信時間
などの表示
Workflow
Git + GitHub(Business)
Jenkins
Deploy
https://github.com/guitarrapc/valentia
Next Generation Log Management & Analytics
ログをクエリ
最高のモニタリングSaaS
自社製プラグインで
Performance
Counterなどの情報
も集積・表示
SDKを叩いてアプリ
側からRedisの利用
具合を可視化
Analytics
アプリケーション分析のためのロギング
Semantic Logging Application Block
https://slab.codeplex.com/
Tableau
アプリケーション分析のためのロギング
Semantic Logging Application Block
https://slab.codeplex.com/
Tableau
とかやってたら、東京リージョンに
来たKinesisがきになるぅぅぅ!
Conclusion
C# + AWSは現実解
構成は堅く、シンプルに
環境は常に最新に
AWS + Windows(C#)で構築する.NET最先端技術によるハイパフォーマンスウェブアプリケーション開発実践

More Related Content

AWS + Windows(C#)で構築する.NET最先端技術によるハイパフォーマンスウェブアプリケーション開発実践