2016 年 2 月

第 31 卷,第 2 期

本文章是由機器翻譯。

Microsoft Azure - Azure Service Fabric、Q-Learning 和 Tic-Tac-Toe

Jesus Aguilar

雲端運算針對分散式運算,可減少障礙項目和機器學習服務需要特製化且昂貴的基礎結構,以提供任何軟體開發人員或解決方案架構設計人員商品的專門技術的應用程式中的創新功能。在本文中,我將說明學習技術,可利用 Azure 平台做為服務供應項目的下一個反覆項目,Azure 服務網狀架構的分散式運算和儲存功能增強式的實作。為了示範這種可能性,我將示範如何使用 Service fabric 可和其可靠執行者程式設計模型來建立智慧型的後端,可以預測下一個移動圈圈叉叉遊戲中。戰的遊戲,任何人嗎?

輸入 Q 學習

今天我們請參閱資料驅動的創新解決方案,例如建議、 面臨到處辨識與詐騙偵測。軟體工程小組會使用經過指導和未經指導的學習技術來實作這些解決方案。一種方式豐富的功能,儘管有一些不難套用的情況。

增強式學習是處理的案例,您可以表示成一連串的狀態和轉換的方法。相較於其他機器學習方法,增強式學習不嘗試培訓模型從加上標籤的資訊 (監督式學習) 或未標記的資料 (非監督式學習),以一般化模式。相反地,將重點放在您可以模型化為狀態和轉換的一連串的問題。

假設您有的案例,您可以表示為一連串通往 (又稱為 absorbing 狀態) 的最終狀態的狀態。將機器人,若要避免的障礙或打對手設計遊戲中的人工智慧 (AI) 的決策。在許多情況下,哪一種狀況會導致狀態的順序會決定最佳的下一個步驟,代理程式/機器人/AI 的字元。

問: 學習是增強式學習技術,可透過反覆獎勵機制來尋找最佳的過渡期路徑在狀態機器模型;也適用於非常有限的狀態和其轉換數目時。在本文中我將提供如何我使用 Service Fabric 來建置端對端 Q 學習方案,並示範如何建立智慧型的後端,「 得知 」 圈圈叉叉遊戲玩法。(請注意,狀態機器的案例也稱為 Markov 決策程序 [MDPs])。

問: 學習關於第一個、 一些基本理論。請考慮的狀態和轉換屬 [圖 1。假設您想要尋找,在任何狀態轉換到下一步] 來到達金級狀態的代理程式必須處理的狀態 — 降至最低的轉換數目。為了解決這個問題的方法之一是將獎勵值指派至每個狀態。獎勵建議向您的目標狀態轉換的值: 取得黃金。

一連串的狀態,黃金狀態
導致黃金狀態的狀態的 [圖 1] 順序

很簡單,對吧? 所面臨的挑戰會變成如何識別每個狀態的報酬。問: 學習演算法識別報酬以遞迴方式逐一查看,並指派獎賞導致 absorbing (金色) 狀態的狀態。演算法計算狀態獎勵若除去獎勵值從後續狀態。如果狀態有兩個獎賞 — 即如果狀態存在於一個以上的路徑可能會出現 — 遇到的最高。折扣有重要影響的系統。若除去獎勵,由演算法可減少長途跋涉黃金的狀態回報的值,並將較多的權重指派給黃金最接近的狀態。

演算法的報酬的計算方式的範例,以查看中的狀態圖表 [圖 1。如您所見,有三個路徑為金級:

1-> [5]-> [4]-> [G

1-> [5]-> [3]-> [4]-> [G

1-> [5]-> [3]-> [2]-> [4]-> [G

執行使用暴力的演算法之後強制執行轉換 (逐一查看所有可能的路徑,在圖形中),此演算法計算,並指派,不是有效的路徑。使用折扣的因素 0.9 計算獎賞。

1(R=72)]-> [5(R=81) 4(R=90)]-> [-> G (R = 100)

1(R=64)]-> [5(R=72)]-> [3(R=81)]-> [4(R=90)]-> [G(R=100)

1(R=58)]-> [5(R=64)]-> [3(R=72)]-> [2(R=81)]-> [4(R=90) G(R=100)]-> [

由於某些州具有一個以上的獎金,則會風行的最大值。[圖 2 說明最終的報酬指派。

最終獎賞
[圖 2 最終獎賞

利用此資訊,代理程式可以識別任何狀態中瀰的最佳路徑轉換至具有最高的獎勵的狀態。比方說,如果代理程式狀態 5 中,它可以選擇轉換為狀態 3 或 4,而 4 會變成選擇,因為較高的報酬。

Azure 服務網狀架構

Service Fabric 中,Azure 平台做為服務供應項目的下一個反覆項目可讓開發人員建立分散式應用程式使用兩個不同的最上層程式設計模型: 可靠動作項目和可靠的服務。這些程式設計模型可讓您充分利用分散式平台的基礎結構資源。平台會處理與維護,以及執行分散式應用程式相關聯的最困難工作 — 從失敗復原、 發佈的服務,以確保有效率的資源使用率,輪流更新與並存至版本控制,更別提少數。

服務網狀架構為您提供叢集,讓您較高層級的抽象概念,來使用,而不是不必擔心基礎結構。Service Fabric 叢集中的節點上執行的程式碼,而您可以裝載為開發用途在單一電腦上,或用於生產工作負載的多部伺服器 (虛擬或實體機器) 上的多節點叢集。平台會管理您的動作項目和服務和基礎結構失敗中的復原的生命週期。

Service Fabric 引入可靠動作項目和服務,可設定狀態的語意。這項功能會轉譯在架構中的完全整合的開發人員的經驗,您可以在其中開發分散式且因此高度可用的方式保存資料,而不需要包含外部儲存體的圖層 (例如,外接式儲存裝置上進行相依性或快取層) 的應用程式。

藉由實作 Q 學習演算法,以在 Service Fabric 服務,您可以受益於擁有分散式運算和低延遲的狀態儲存功能,讓您執行演算法、 保存結果,並將全面公開為可靠的結束點,讓用戶端存取。所有這些功能組合在一起的單一解決方案與統一的程式設計和管理堆疊。沒有需要新增額外元件至您的架構,例如外部儲存體、 快取或郵件系統。簡單地說,您必須將計算、 資料和服務相同的整合式平台內存放的解決方案。這是我的著作中更高招的解決方案!

問: 學習和可靠動作項目

動作項目模型簡化了大量的並行處理應用程式的設計。在動作項目模型中,動作項目是基本的運算單位。動作項目代表某界限內的功能和狀態。您可以想像的動作項目物件實體住在分散式系統。Service Fabric 會管理生命週期的動作項目。如果發生故障,Service Fabric 重新具現化的狀況良好的節點中的動作項目自動。比方說,如果因某種原因節點 (想像 VM) 執行失敗,便會當機可設定狀態的動作項目,是所有其狀態 (資料) 不變的另一部電腦上自動重新建立動作項目。

Service Fabric 也會管理的動作項目執行個體的存取方式。平台可確保在任何時間點,執行特定動作項目上的只有一個方法一次。如果有兩個並行的呼叫,以相同的動作項目,Service Fabric 將其中一個佇列,並讓其他繼續執行。其含意為動作項目內有您的程式碼不擔心競爭情況、 鎖定或同步處理。

如先前所述,Q 學習演算法會逐一尋找茶的狀態和狀態與報酬的目標狀態。一旦此演算法會識別獎勵 absorbing 的狀態,它會計算對導致 absorbing 狀態的所有狀態。

我可以使用動作項目模型,模型這項功能表示 Q 學習演算法 (想像階段整體圖形中) 的內容中的動作項目。在我的實作,表示這些狀態的動作項目型別是 QState。轉換至包含獎勵 QState 動作項目之後,QState 動作項目將 QState 動作項目路徑中的每個建立不同類型 (QTrainedState) 的另一個動作項目執行個體。QTrainedState 動作項目維持的最大的報酬值和產生獎勵後續狀態的清單。此清單包含狀態權杖 (可唯一識別在圖形中的狀態) 的後續狀態。

[圖 3, ,我說明的邏輯非常簡單的案例,其中狀態與狀態語彙基元 3 absorbing 的狀態、 包含報酬 100,而且已有兩個先前的狀態 (狀態權杖 1 和 2) 只能有一個路徑使用動作項目,此演算法。每個圓圈代表動作項目,以藍色 QStates 的執行個體和 QTrainedStates 以橘色表示。一旦轉換程序達到與狀態語彙基元 3 QState,QState 動作項目將會建立兩個 QTrainedStates,一個用於每個先前 QStates。QTrainedState 動作項目,表示狀態權杖 2,建議 (適用於 90 的報酬) 轉換為狀態語彙基元 3,而 QTrainedState 動作項目,表示狀態權杖 1,建議的轉換 (如 81 報酬) 是狀態 2 語彙基元。

判斷和保存獎賞
[圖 3 判斷和保存獎賞

很可能在多個狀態將會產生相同的報酬,因此 QTrainedState 動作項目保存狀態語彙基元的集合,做為子系狀態。

下列程式碼顯示 QState 和 QTrainedState 動作項目,稱為 IQState 和 IQTrainedState 介面實作。QStates 有兩種行為: 轉換為其他 QStates 及啟動轉換處理序沒有先前的轉換存在時:

public interface IQState : IActor
{
  Task StartTrainingAsync(int initialTransitionValue);
  Task TransitionAsync(int? previousStateToken, int transitionValue);
}
public interface IQTrainedState:IActor
{
 .Task AddChildQTrainedStateAsync(int stateToken, double reward);
 .Task<List<int>> GetChildrenQTrainedStatesAsync();
}

請注意 IQTrainedState 實作介面方法 GetChildrenQTrainedStatesAsync。這個方法是如何 QTrainedState 動作項目會公開已培訓的資料包含系統中的任何狀態的報酬值最高的狀態。(請注意在 Service Fabric 中的所有動作項目必須實作衍生自 IActor 介面)。

QState 動作項目

定義介面,我可以移至動作項目實作。我會先處理 QState 動作項目和 TransitionAsync 方法,也就是演算法的基石和大部分的工作所在的位置。TransitionAsync 會轉換到另一個狀態建立 QState 動作項目的新執行個體,並再次呼叫相同的方法。

您可能會懷疑是否藉由呼叫方法以遞迴方式將您想避免叫用的方法,透過另一個動作項目執行個體的額外負荷。遞迴方法呼叫是在單一節點計算密集作業。相較之下,以具現化另一個動作項目,就採用 advantange 服務網狀架構的功能,可讓平台上水平的運算資源分散處理。

若要管理的報酬指派,我要註冊提醒。提醒是新的建構導入的動作項目程式設計模型可讓您排定非同步工作而不會封鎖執行方法。

提醒是僅適用於可設定狀態動作項目。無狀態與可設定狀態動作項目,此平台提供啟用類似模式的計時器。一個重要的考量是,使用動作項目時,記憶體回收過程就會延遲。不過,在平台不會視為計時器回撥使用量。如果記憶體回收行程會介入,將會停止計時器。動作項目將不會執行方法時所收集到的記憶體回收。若要保證重複執行,使用提醒。詳細資訊,請參閱 bit.ly/1RmzKfr

目標,與演算法,是執行獎勵指派,而不會封鎖轉換程序。一般而言,排程與回呼執行緒集區中的工作項目就夠了。不過,在動作項目程式設計模型,這個方法不是個好主意,您將會遺失在平台的並行優點。

平台可確保只有一個方法在任何時候執行。這項功能可讓您撰寫程式碼,而不考慮並行存取。也就是說,而不需要擔心執行緒安全。如您所預期,因此需要有所取捨: 您必須避免建立工作或執行緒可以換行內的動作項目方法的操作。提醒可讓您實作並行保證的平台,以背景處理的案例中所示 [圖 4

[圖 4 TransitionAsync QState 類別中

public abstract class QState : StatefulActor, IQState, IRemindable
{
  // ...
  public Task TransitionAsync(int? previousStateToken, int transitionValue)
  {
    var rwd = GetReward(previousStateToken, transitionValue);
    var stateToken = transitionValue;
    if (previousStateToken != null)
        stateToken = int.Parse(previousStateToken.Value + stateToken.ToString());
    var ts = new List<Task>();
    if (rwd == null || !rwd.IsAbsorbent)
      ts.AddRange(GetTransitions(stateToken).Select(p =>
        ActorProxy.Create<IQState>(ActorId.NewId(),
        "fabric:/QLearningServiceFab").TransitionAsync(stateToken, p)));
    if (rwd != null)
      ts.Add(RegisterReminderAsync("SetReward",
        Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rwd))
        , TimeSpan.FromMilliseconds(0)
        , TimeSpan.FromMilliseconds(-1), ActorReminderAttributes.Readonly));
      return Task.WhenAll(ts);
  }
  // ...
}

(請注意,dueTime 設 TimeSpan.FromMilliseconds(0)) 表示立即執行)。

若要完成 IQState 的實作,下列程式碼會實作 StartTransitionAsync 方法,我使用提醒以避免封鎖長時間執行的呼叫:

public Task StartTrainingAsync(int initialTransitionValue)
  {
    return RegisterReminderAsync("StartTransition",
      Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new { TransitionValue =
      initialTransitionValue })), TimeSpan.FromMilliseconds(0),
      TimeSpan.FromMilliseconds(-1),
      ActorReminderAttributes.Readonly);
  }

若要完成 QState 類別的實作,我將說明 SetRewardAsync 和 ReceiveReminderAsync 中的方法,實作 [圖 5。SetReward 方法建立或更新的可設定狀態的動作項目 (IQTrainedState 實作)。若要尋找動作項目中的後續呼叫,我使用做為動作項目識別碼狀態語彙基元,動作項目來定址。

[圖 5 SetRewardAsync 和 ReceiveReminderAsync 方法

public Task SetRewardAsync(int stateToken, double stateReward, double discount)
  {
    var t = new List<Task>();
    var reward = stateReward;
    foreach (var pastState in GetRewardingQStates(stateToken))
    {
      t.Add(ActorProxy
        .Create<IQTrainedState>(new ActorId(pastState.StateToken),
          "fabric:/QLearningServiceFab")
        .AddChildQTrainedStateAsync(pastState.NextStateToken, reward));
      reward = reward * discount;
    }
    return Task.WhenAll(t);
  }
public async Task ReceiveReminderAsync(string reminderName,
  byte[] context, TimeSpan dueTime, TimeSpan period)
  {
    await UnregisterReminderAsync(GetReminder(reminderName));
    var state = JsonConvert.DeserializeObject<JObject>(
      Encoding.UTF8.GetString(context));
    if (reminderName == "SetReward")
    {
      await SetRewardAsync(state["StateToken"].ToObject<int>(),
        state["Value"].ToObject<double>(),
        state["Discount"].ToObject<double>());
    }
    if (reminderName == "StartTransition")
    {
      await TransitionAsync(null, state["TransitionValue"].ToObject<int>());
    }
  }

QTrainedState 動作項目

在方案中的第二個動作項目是 QTrainedState。QTrainedState 動作項目中的資料必須是永久性,因此我實作可設定狀態的動作項目為此動作項目。

在 Service Fabric 中,您從 StatefulActor 或 StatefulActor < T > 基底類別衍生您的類別來實作可設定狀態的動作項目並實作介面衍生自 IActor。T 是狀態執行個體,必須是可序列化的類型和參考型別。當您呼叫的類別,衍生自 StatefulActor < T > 方法時,在平台會從狀態提供者載入狀態,並完成呼叫時,此平台儲存自動。在 QTrainedState,我建立模型的狀態 (永久性資料) 使用下列類別:

[DataContract]
public class QTrainedStateState
{
  [DataMember]
  public double MaximumReward { get; set; }
  [DataMember]
  public HashSet<int> ChildrenQTrainedStates { get; set; }
}

[圖 6 顯示 QTrainedState 類別,它會實作 IQTrainedState 介面的兩個方法的完整實作。

圖 6 QTrainedState 類別

public class QTrainedState : StatefulActor<QTrainedStateState>, IQTrainedState
{
  protected async override Task OnActivateAsync()
  {
    this.State =
      await ActorService.StateProvider.LoadStateAsync<QTrainedStateState>(
      Id, "qts") ??
      new QTrainedStateState() { ChildrenQTrainedStates = new HashSet<int>() };
    await base.OnActivateAsync();
  }
  protected async override Task OnDeactivateAsync()
  {
    await ActorService.StateProvider.SaveStateAsync(Id, "qts", State);
    await base.OnDeactivateAsync();
  }
  [Readonly]
  public  Task AddChildQTrainedStateAsync(int stateToken, double reward)
  {
    if (reward < State.MaximumReward)
    {
      return Task.FromResult(true);
    }
    if (Math.Abs(reward - State.MaximumReward) < 0.10)
    {
      State.ChildrenQTrainedStates.Add(stateToken);
      return Task.FromResult(true);
    }
      State.MaximumReward = reward;
      State.ChildrenQTrainedStates.Clear();
      State.ChildrenQTrainedStates.Add(stateToken);
      return Task.FromResult(true);
  }
  [Readonly]
  public Task<List<int>> GetChildrenQTrainedStatesAsync()
  {
    return Task.FromResult(State.ChildrenQTrainedStates.ToList());
  }
}

呈現動作項目

此時,解決方案必須啟動定型程序和保存資料所需的所有項目。但我還沒討論用戶端如何與這些動作項目互動。概括而言,這種互動是由啟動定型程序和查詢保存的資料所組成。這些互動的每個相互關聯得很好的 API 作業,並符合 rest 限制實作能與用戶端的整合。

除了擁有兩種程式設計模型,Service Fabric 是完整的協調流程和程序管理平台。動作項目和服務存在失敗復原和資源管理也會提供給其他處理序的。比方說,您可以執行 Node.js 或 ASP.NET 5 處理序,由 Service Fabric 中,以及這些功能,而不需要進一步介入。所以我可以只使用標準的 ASP.NET 5 Web API 應用程式建立公開相關動作項目功能 API 控制器中所示 [圖 7

圖 7 API 控制器

[Route("api/[controller]")]
public class QTrainerController : Controller
{
  [HttpGet()]
  [Route("[action]/{startTrans:int}")]
  public  async Task<IActionResult>  Start(int startTrans)
  {
    var actor = ActorProxy.Create<IQState>(ActorId.NewId(),
      "fabric:/QLearningServiceFab/");
    await actor.StartTrainingAsync(startTrans);
    return Ok(startTrans); 
  }
  [HttpGet()]
  [Route("[action]/{stateToken}")]
  public async Task<int> NextValue(int stateToken)
  {
    var actor = ActorProxy.Create<IQTrainedState>(new ActorId(stateToken),
      "fabric:/QLearningServiceFab");
    var qs = await actor.GetChildrenQTrainedStatesAsync();
    return qs.Count == 0 ? 0 : qs[new Random().Next(0, qs.Count)];
  }
}

和圈圈叉叉嗎?

現在剩下是對使用具體案例與方案。為此,我將使用一個簡單的遊戲: 圈圈叉叉。

目標是要定型一組您可以查詢來預測圈圈叉叉遊戲中的下一波的 QTrainedStates。想想這個問題的方法之一是會做為兩名玩家的機器,並從結果中學習。

回到實作,請注意,QState 抽象類別。其概念是封裝之演算法的基本概念,以及特定案例的邏輯放在衍生類別中。案例定義演算法的三個部分: 狀態之間轉換發生 (原則) 的方式。何種狀態是 absorbing,而且有初始的報酬; 嗎然後狀態演算法指派折扣的報酬。針對每個部分,QState 類別有一個方法您可以在此實作這些語意,以解決特定的案例。這些方法是 GetTransitions、 GetReward 和 GetRewardingQStates。

所以問題就變成: 您要如何為一連串的狀態和轉換模型圈圈叉叉遊戲?

考慮屬遊戲 [圖 8, ,而且每個資料格都有指派的數字。您可以將從某個狀態轉換到另一個轉換的值是播放程式進行播放的儲存格為每個回合。每個狀態語彙基元,即先前開啟 (儲存格) 和轉換值的組合。例如在 [圖 8, ,轉換從 1 到 14,然後 142,依此類推,模型的播放程式播放第一個位置開啟 wins 遊戲的步驟。並在此情況下,會導致 14273 (獲獎並茶狀態) 的所有狀態都必須都指派報酬: 1,142。

圈圈叉叉案例
[圖 8 圈圈叉叉案例

回到 Q 學習,我必須提供是所有的最後一個 (absorbing) 狀態,各有初始的報酬。圈圈叉叉,針對三種類型的狀態,則會產生報酬: win、 繫結或區塊 (請參閱點時您的對手贏得勝利,如此您就必須使用輪封鎖他)。Win 和繫結是 absorbing,這表示在遊戲結束。區塊,不過,並不會繼續遊戲。[圖 9 顯示圈圈叉叉遊戲的 GetReward 方法的實作。

圖 9 GetReward 方法

internal override IReward GetReward(int? previousStateToken, int transitionValue)
{
  var game = new TicTacToeGame(previousStateToken,transitionValue);
  IReward rwd = null;
  if (game.IsBlock)
  {
    rwd = new TicTacToeReward() { Discount = .5, Value = 95, IsAbsorbent = false,
      StateToken = game.StateToken};
  }
  if (game.IsWin)
  {
    rwd = new TicTacToeReward() { Discount = .9, Value = 100, IsAbsorbent = true,
      StateToken = game.StateToken };
  }
  if (game.IsTie)
  {
    rwd = new TicTacToeReward() { Discount = .9, Value = 50, IsAbsorbent = true,
      StateToken = game.StateToken };
  }
  return rwd;
}

接下來,一旦報酬以識別狀態,我需要為演算法提供的狀態,因此可以指派折扣的獎勵會導致將狀態與初始的報酬。Win 或區塊的情況下,這些狀態分別是所有之前的狀態 (播放) 的成功或封鎖的播放程式。繫結,所有狀態 (播放) 的兩位玩家必須都指派報酬:

internal override IEnumerable<IPastState> GetRewardingQStates(int stateToken)
{
  var game = new TicTacToeGame(stateToken);
  if (game.IsTie)           
    return game.GetAllStateSequence();           
  return game.GetLastPlayersStateSequence();
}

最後,我需要實作轉換原則,以判斷如何演算法會逐一查看狀態。遊戲中,我將實作會在瀏覽所有可能組合的轉換原則:

internal override IEnumerable<int> GetTransitions(int stateToken)
{
  var game = new TicTacToeGame(stateToken);
  return game.GetPossiblePlays();
}

對電腦播放

此時,我可以發行方案,並開始呼叫 REST api 的訓練,並提供初始轉換: 1 到 9。

一次訓練完成時,您可以使用 API 建立應用程式可以只將狀態語彙基元傳遞和接收的建議的值。本文的原始程式碼包含使用這個後端的通用 Windows 平台應用程式。圖 10 顯示遊戲。

圈圈叉叉的遊戲
圈圈叉叉] 圖 10 A 遊戲

總結

問: 學習和服務網狀架構使用,很快就能建立端對端架構運用計算並保存資料的分散式平台。為了展示這種方法,我要建立可學習如何進行遊戲時,和做法在可接受的層級是唯一的指出 win、 繫結或區塊發生,並讓機器學習遊戲玩法的後端使用圈圈叉叉遊戲。


Jesus Aguilar是資深的雲端架構設計人員在 Microsoft 技術推廣和開發小組他在雲端、 出生的實在太酷了公司的合作夥伴,協助他們大規模提供吸引人的體驗。他是熱衷於軟體工程設計和解決方案設計,您將會吸引他利用 「 預測性分析,」 「 延展性 」,「 並行 」 之類的詞彙 」 設計模式 」 和 「 < 選擇的任何字母 > aaS。 」 您也可以關注他的 Twitter: @giventocode 和簽出他的部落格 giventocode.com

感謝以下的微軟技術專家對本文的審閱: Rob Bagby、 Mike Lanzetta 和 Matthew Snider
Rob 是資深Microsoft 雲端架構設計人員。在此角色中,Rob 適用於具影響力的 Isv,協助他們在 Azure 中實作軟體。之前這個角色,Rob 曾為顧問,建置鬆散結合,可管理、 可擴充的系統。

Mike Lanzetta 是 Microsoft 合作夥伴 Catalyst 小組專注於 Machine Learning、 巨量資料和系統程式設計的開發人員。他有 MS CSE 從華盛頓大學中並已在產業中 20 年來在公司從初學者到 Amazon 和 Microsoft。

Matt Snider 加入 Microsoft 在 2008 中,使用.NET 的一小部分。.NET 4 隨附之後,他加入 Service Fabric 小組為第一個技術下午、 使用不同功能區域,且已在小組從那時起。近來他主要適用於叢集資源管理員 (Orchestrator)、 可靠的集合,以及容錯移轉/複寫部分的堆疊。當不使用分散式系統,他喜歡啤酒、 健行和音樂。