在昨天文章的尾端部分新增了關於如何觸發HystrixCommand的RunAsync()跟RunFallbackAsync(),有興趣的朋友可以看一下
來看Fortune-Teller-UI專案下,HomeController怎麼調用HystrixCommand,首先在constructor注入FortuneServiceCommand,在controller對應的方法裡面調用FortuneServiceCommand的RandomFortune()方法,藉由RandomFortune()方法來觸發ExecuteAsync(),判斷要執行RunAsync()或是RunFallbackAsync()
[Route("/")]
public class HomeController : Controller
{
FortuneServiceCommand _fortuneServiceCommand;
IFakeService1 _service1;
public HomeController(FortuneServiceCommand fortuneServiceCommand, IFakeService1 service1)
{
_fortuneServiceCommand = fortuneServiceCommand;
_service1 = service1;
}
[HttpGet]
public IActionResult Index()
{
return View();
}
[HttpGet("random")]
public async Task<Fortune> Random()
{
return await _fortuneServiceCommand.RandomFortune();
}
}
這個東西還蠻有趣的,千言萬語不如一圖!
(https://raw.githubusercontent.com/wiki/Netflix/Hystrix/images/collapser-1280.png)
由圖可以可以清楚看到Hystrix Collapser可以將來自前端的多個類似的請求合併成一個(在一定的時間間隔之內),有效減輕後端服務的負載(較少的執行緒與連線)
先看一下Hystrix Collapser定義的介面
public abstract class HystrixCollapser<BatchReturnType, RequestResponseType, RequestArgumentType> : IHystrixExecutable<RequestResponseType>, IHystrixObservable<RequestResponseType>, IHystrixInvokable
{
protected internal CancellationToken token;
protected HystrixCollapser();
protected HystrixCollapser(IHystrixCollapserKey collapserKey);
protected HystrixCollapser(IHystrixCollapserOptions options);
protected HystrixCollapser(IHystrixCollapserKey collapserKey, RequestCollapserScope scope);
protected HystrixCollapser(IHystrixCollapserKey collapserKey, RequestCollapserScope scope, ICollapserTimer timer, IHystrixCollapserOptions options);
protected HystrixCollapser(IHystrixCollapserKey collapserKey, RequestCollapserScope scope, ICollapserTimer timer, IHystrixCollapserOptions optionsDefault, HystrixCollapserMetrics metrics);
public virtual HystrixCollapserMetrics Metrics { get; }
public virtual RequestCollapserScope Scope { get; }
public virtual IHystrixCollapserKey CollapserKey { get; }
public abstract RequestArgumentType RequestArgument { get; }
protected virtual string CacheKey { get; }
public RequestResponseType Execute();
public Task<RequestResponseType> ExecuteAsync(CancellationToken token);
public Task<RequestResponseType> ExecuteAsync();
public IObservable<RequestResponseType> Observe();
public IObservable<RequestResponseType> Observe(CancellationToken token);
public IObservable<RequestResponseType> ToObservable();
public Task<RequestResponseType> ToTask();
protected bool AddCacheEntryIfAbsent(string cacheKey, out HystrixCachedTask<RequestResponseType> entry);
protected abstract HystrixCommand<BatchReturnType> CreateCommand(ICollection<ICollapsedRequest<RequestResponseType, RequestArgumentType>> requests);
protected virtual Exception DecomposeException(Exception e);
protected abstract void MapResponseToRequests(BatchReturnType batchResponse, ICollection<ICollapsedRequest<RequestResponseType, RequestArgumentType>> requests);
protected virtual ICollection<ICollection<ICollapsedRequest<RequestResponseType, RequestArgumentType>>> ShardRequests(ICollection<ICollapsedRequest<RequestResponseType, RequestArgumentType>> requests);
}
以Sample來看,需要注意的參數跟方法有
BatchReturnType: 整批回傳的型別,在Sample裡就是List<Fortune>
RequestResponseType: 單個request的回傳型別Fortune ps: 命名ResponseType好像比較好
RequestArgumentType: request的參數 例如userId productID之類的 這邊用FortuneId
CreateCommand(...): 創建合併後的HystrixCommand
MapResponseToRequests(...): 將回傳的結果對應到請求
Execute()/ExecuteAsync(): 單一請求之下由HystrixCommand負責觸發後續的RunAsync(),在合併請求下由HystrixCollapser負責
實作HystrixCollapser的部分請看FortuneServiceCollapser.cs
當然還是得定義一個合併後用的HystrixCommand, 下面是用於合併請求的MultiFortuneServiceCommand.cs
public class MultiFortuneServiceCommand : HystrixCommand<List<Fortune>>
{
ILogger<MultiFortuneServiceCommand> _logger;
ICollection<ICollapsedRequest<Fortune, int>> _requests;
IFortuneService _fortuneService;
public MultiFortuneServiceCommand(IHystrixCommandGroupKey groupKey,
ICollection<ICollapsedRequest<Fortune, int>> requests,
IFortuneService fortuneService,
ILogger<MultiFortuneServiceCommand> logger) : base(groupKey)
{
_fortuneService = fortuneService;
_logger = logger;
_requests = requests;
}
protected override async Task<List<Fortune>> RunAsync()
{
List<int> ids = new List<int>();
foreach(var req in _requests)
{
ids.Add(req.Argument);
}
return await _fortuneService.GetFortunesAsync(ids);
}
protected override async Task<List<Fortune>> RunFallbackAsync()
{
List<Fortune> results = new List<Fortune>() { new Fortune() { Id = 9999, Text = "You will have a happy day!" } };
return await Task.FromResult(results);
}
}
比較跟單一請求的HystrixCommand不同點是constructor傳入的參數不同,像是IHystrixCommandGroupKey跟
ICollection,此外由於在多重請求之下的ExecuteAsync()是由HystrixCollapser負責,所以HystrixCommand不會 撰寫給外部調用的方法
public FortuneServiceCommand(IHystrixCommandOptions options, IFortuneService fortuneService,
ILogger<FortuneServiceCommand> logger) : base(options)
public MultiFortuneServiceCommand(IHystrixCommandGroupKey groupKey,
ICollection<ICollapsedRequest<Fortune, int>> requests,
IFortuneService fortuneService,
ILogger<MultiFortuneServiceCommand> logger) : base(groupKey)
{
來看一下官方文件裡面的範例HomeController ps: Sample code裡面沒有
合併請求之下使用的是HystrixCollapser而非直接用HystrixCommand,所以在constructor注入FortuneServiceCollapser,然後在GetFortuneById(int id)方法裡,直接調用HystrixCollapser的ExecuteAsync()觸發MultiFortuneServiceCommand
public class HomeController : Controller
{
FortuneServiceCollapser _fortuneService;
public HomeController(FortuneServiceCollapser fortuneService)
{
_fortuneService = fortuneService;
}
[HttpGet("random")]
public async Task<Fortune> Random()
{
// Fortune IDs are 1000-1049
int id = random.Next(50) + 1000;
return GetFortuneById(id);
}
[HttpGet]
public IActionResult Index()
{
return View();
}
protected Task<Fortune> GetFortuneById(int id)
{
// Use the FortuneServiceCollapser to obtain a Fortune by its Fortune Id
_fortuneService.FortuneId = id;
return _fortuneService.ExecuteAsync();
}
}