.Net Core Polly重试、超时、缓存策略如何精编成?
摘要:Polly这个东西比较早的时候有去了解,没有实际应用也快忘得差不多了。 近期有个任务是把项目中的Hangfire依赖移除。项目中使用Hangfire执行一个定期任务,周期是一秒。 原本项目已经用到了Hangfire,选用它做定期Job是“顺
Polly这个东西比较早的时候有去了解,没有实际应用也快忘得差不多了。
近期有个任务是把项目中的Hangfire依赖移除。项目中使用Hangfire执行一个定期任务,周期是一秒。
原本项目已经用到了Hangfire,选用它做定期Job是“顺其自然+想当然”的,但它因为在仪表盘产生大量的log,影响其他项目的Debug,所以领导决定弃用它。
替代方案考虑了Thread、Thread.Timer、Polly、BackgroundService、IHostService。
个人选择创建一个派生自BackgroundService的Monitor类,后由于领导对性能高要求,最后又改为了Timer实现。
在最终实现方案确定前,又想到了Polly,所以就再次温习了一下。
看网上的讲解零零散散的,Demo也多比较潦草(且多是复制转载),于是决定自己整理一套更容易让大家看懂的Demo。
温馨提示:
在深究某个Demo的具体代码前,建议先理解一下这个Demo模拟的具体场景。
之所以整理这些Demo是因为看了很多文章,都是单纯的在贴代码,有些人代码都看懂了,但是根本不知道怎么用。
所以我尽可能地用有限的代码和注释模拟一个较为接近真实的应用场景。这有助于更好地立即Polly的使用场景和功能细节。
DemoBase
因为创建了一个独立的完整项目,所以所有Demo均继承自DemoBase类,提供公用的一些方法。
如果希望运行这些Demo,注意添加此类并使用下面的Program类。
1 using System;
2
3 namespace Demo.Core
4 {
5 public abstract class DemoBase
6 {
7 public abstract string DemoName { get; }
8
9 public void PrintText(string message, ConsoleColor consoleColor)
10 {
11 lock (this)
12 {
13 var oriColor = Console.ForegroundColor;
14 Console.ForegroundColor = consoleColor;
15 Console.WriteLine(message);
16 Console.ForegroundColor = oriColor;
17 }
18 }
19
20 public void Init()
21 {
22 PrintText($"------------{DemoName}------------", ConsoleColor.Cyan);
23 }
24
25 public void PrintInfo(string message)
26 {
27 Console.WriteLine(message);
28 }
29
30 public void PrintError(string message)
31 {
32 PrintText(message, ConsoleColor.Red);
33 }
34
35 public void PrintSuccess(string message)
36 {
37 PrintText(message, ConsoleColor.Green);
38 }
39
40 public void PrintTimeNow()
41 {
42 Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
43 }
44
45 public void PrintLog(string message, ConsoleColor consoleColor = ConsoleColor.DarkGray)
46 {
47 PrintText($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}\t{message}", consoleColor);
48 }
49
50 public void PrintSuccessLog(string message)
51 {
52 PrintLog(message, ConsoleColor.DarkGreen);
53 }
54
55 public void PrintErrorLog(string message)
56 {
57 PrintLog(message, ConsoleColor.DarkRed);
58 }
59
60 public abstract void RunDemo();
61 }
62 }
DemoBase
1 using Demo.Core;
2 using System;
3 using System.Collections.Generic;
4 using System.Linq;
5
6 namespace PollyDemo
7 {
8 class Program
9 {
10 static void Main(string[] args)
11 {
12 var demos = new List<DemoBase>();
13 demos.Add(new Retry());
14 demos.Add(new CircuitBreaker());
15 demos.Add(new AdvancedCircuitBreaker());
16 demos.Add(new Timeout());
17 demos.Add(new Fallback());
18 demos.Add(new Wrap());
19 demos.Add(new Cache());
20 demos.Add(new BulkheadIsolation());
21
22 bool continueDemo = true;
23 do
24 {
25 Console.WriteLine("-------请选择执行Demo-------");
26 int index = 0;
27 var demoList = demos.Select(x => $"{++index}:{ x.GetType().Name}\n").ToList();
28 demoList.Add("c:清空历史\n");
29 demoList.Add("q:退出\n");
30
31 Console.WriteLine(string.Concat(demoList));
32 var input = Console.ReadLine();
33 if(input == "q")
34 {
35 break;
36 }
37 if(input == "c")
38 {
39 Console.Clear();
40 continue;
41 }
42 if (int.TryParse(input, out int value))
43 {
44 if (value <= 0)
45 {
46 Console.WriteLine("输入数字超出范围");
47 continue;
48 }
49 if (value > demos.Count)
50 {
51 Console.WriteLine("输入数字超出范围");
52 continue;
53 }
54 demos[value - 1].RunDemo();
55 Console.WriteLine("-------执行Demo完毕-------");
56 }
57 else
58 {
59 Console.WriteLine("请输入有效编号");
60 }
61 }
62 while (continueDemo);
63 }
64 }
65 }
Program
Retry
1 public class Retry: DemoBase
2 {
3 public override string DemoName => "Polly-Retry Demo";
4
5 /// <summary>
6 /// 老板开出工资
7 /// </summary>
8 int SalaryFromBoss = 10_000;
9
10 public override void RunDemo()
11 {
12 base.Init();
13 try
14 {
15 int retryTimes = 5;
16
17 var retryTwoTimesPolicy = Policy
18 .Handle<SalaryException>() //支持的第1种异常类型
19 .Or<TimeException>() //支持的第2种异常类型
20 .Or<Exception>(ex => ex.Message == "攻城狮有点犹豫") //注意这个限定 这个根据业务场景 有很大灵活运用空间
21 //.OrInner<Exception>() //不再赘述
22 //.RetryForever();<Exception>() //不再赘述
23 //.WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(4) }
24 .Retry(retryTimes, (ex, onRetry) =>
25 {
26 PrintInfo($"攻城狮说:{ex.Message}");
27 PrintInfo($"第{onRetry}次谈心(薪)失败 老板得重新谈\n");
28 老板调薪();
29 });
30 retryTwoTimesPolicy.Execute(() =>
31 {
32 与老板谈心();
33 PrintSuccess("谈心成功");
34 });
35 }
36 catch (Exception e)
37 {
38 PrintError($"超出老板的预料:{e.Message}");
39 }
40 }
41
42 private void 老板调薪()
43 {
44 SalaryFromBoss = SalaryFromBoss + 10_000;
45 PrintInfo($"与老板谈心 给出薪资{SalaryFromBoss}");
46 }
47
48 private void 与老板谈心()
49 {
50
51 if (SalaryFromBoss < 20_000)
52 {
53 throw new TimeException($"天天都在加班,世界那么大,我想去走走。");
54 }
55 if (SalaryFromBoss < 30_000)
56 {
57 throw new SalaryException(SalaryFromBoss, $"薪资太少,您没别的事,我回去写Bug了!");
58 }
59 if (SalaryFromBoss < 40_000)
60 {
61 if (DateTime.Now.Second % 2 == 0)
62 throw new Exception("攻城狮有点犹豫");
63 else
64 throw new Exception("攻城狮没接电话 这下没法谈了");//这个指定类型和场景之外的异常 不会出发重试机制
65 }
66 PrintInfo($"攻城狮说:我这就回去赶进度~");
67 }
68 }
69
70 class SalaryException : Exception
71 {
72 int Salary { get; set; }
73
74 public SalaryException(int salary, string message) : base(message)
75 {
76 Salary = salary;
77 }
78 }
79
80 class TimeException : Exception
81 {
82 public TimeException(string message) : base(message)
83 {
84
85 }
86 }
Timeout
1 public class Timeout : DemoBase
2 {
3 public override string DemoName => "Polly-Timeout Demo";
4
5 public override void RunDemo()
6 {
7 base.Init();
8
9 int runTimes = 10;
10 int timeout = 2;
11
12 var policy = Policy.Timeout(timeout, Polly.Timeout.TimeoutStrategy.Pessimistic);
13
14 for (int i = 1; i <= runTimes; i++)
15 {
16 try
17 {
18 PrintInfo($"第{i}次执行");
19 policy.Execute(RequestFromWeb);
20 }
21 catch (Exception e)
22 {
23 PrintError($"异常:{e.GetType().Name},{e.Message}");
24 }
25 }
26 }
27
28 private void RequestFromWeb()
29 {
30 if (new Random().Next(10) < 5)
31 {
32 Thread.Sleep(3_000);
33 }
34 else
35 {
36 PrintSuccessLog("网络请求响应完毕");
37 }
38 }
39 }
Wrap
1 public class Wrap : DemoBase
2 {
3 public override string DemoName => "Polly-Wrap Demo";
4
5 public override void RunDemo()
6 {
7 //https://github.com/beckjin/PollySamples/blob/master/App/Polly/Wrap.cs
8 base.Init();
9 int timeout = 2;
10 ISyncPolicy policyException = Policy.Handle<TimeoutRejectedException>()
11 .Fallback(() =>
12 {
13 PrintErrorLog("执行降级该执行的操作");
14 });
15 ISyncPolicy policyTimeout = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic);
16 //ISyncPolicy mainPolicy = Policy.Wrap(policyTimeout, policyException);
17 ISyncPolicy mainPolicy = policyTimeout.Wrap( policyException);
18 try
19 {
20 for (int i = 0; i < 5; i++)
21 {
22 mainPolicy.Execute(RequestFromWeb);
23 }
24 }
25 catch (Exception e)
26 {
27 PrintError($"异常:{e.GetType().Name},{e.Message}");
28 }
29 }
30
31 private void RequestFromWeb()
32 {
33 if (new Random().Next(10) < 5)
34 {
35 PrintLog("开始请求大数据包");
36 Thread.Sleep(3000);
37 PrintSuccessLog("请求大数据包成功");
38 }
39 else
40 {
41 PrintLog("开始请求小数据包");
42 Thread.Sleep(100);
43 PrintSuccessLog("请求小数据包成功");
44 }
45 }
46 }
1 public class Fallback : DemoBase
2 {
3 public override string DemoName => "Polly-Fallback Demo";
4
5 /// <summary>
6 /// 全局动态调整时长
7 /// </summary>
8 private int _timeout = 1;
9 public override void RunDemo()
10 {
11 base.Init();
12
13 var mainPolicy = Policy.Handle<Exception>()
14 .Fallback(() =>
15 {
16 _timeout++;
17 PrintErrorLog($"数据量太大 调整超时时间:{_timeout} 秒");
18 });
19 try
20 {
21 for (int i = 0; i < 10; i++)
22 {
23 mainPolicy.Execute(RequestFromWeb);
24 }
25 }
26 catch (Exception e)
27 {
28 PrintError($"异常:{e.GetType().Name},{e.Message}");
29 }
30 }
31
32 private void RequestFromWeb()
33 {
34 if (_timeout < 5)
35 {
36 PrintLog("开始请求数据包");
37 Thread.Sleep(_timeout * 1000);
38 throw new WebException("请求超时");
39 }
40 else
41 {
42 PrintLog("开始请求数据包");
43 Thread.Sleep(_timeout * 1000);
44 PrintSuccessLog("请求数据包成功");
45 _timeout = 3;//模拟网络状态变化
46 }
47 }
48 }
CircuitBreaker
1 public class CircuitBreaker : DemoBase
2 {
3 public override string DemoName => "Polly-Basic CircuitBreaker Demo";
4
5 public override void RunDemo()
6 {
7 base.Init();
8
9 int runTimes = 8;
10 int exceptionTimes = runTimes - 3;
11 int durationOfBreak = 5;
12
13 var policy = Policy
14 .Handle<Exception>()
15 .CircuitBreaker(exceptionTimes, TimeSpan.FromSeconds(durationOfBreak));
16
17 for (int i = 1; i <= runTimes; i++)
18 {
19 try
20 {
21 PrintInfo($"第{i}次执行");
22 policy.Execute(RequestFromWeb);
23 }
24 catch (BrokenCircuitException e)
25 {
26 PrintError($"熔断触发{nameof(BrokenCircuitException)}异常:{e.Message}");
27 }
28 catch (Exception e)
29 {
30 PrintError($"异常:{e.GetType().Name},{e.Message}");
31 }
32 }
33 }
34
35 private void RequestFromWeb()
36 {
37 throw new Exception("调用方法抛出异常");
38 }
39
40 }
Bulkhead Isolation
1 public class BulkheadIsolation : DemoBase
2 {
3 public override string DemoName => "Polly-Bulkhead Isolation Demo";
4 int resourceId = 0;
5
6 public override void RunDemo()
7 {
8 base.Init();
9 resourceId = 0;
10 //制定策略最大的并发数为5
11 Policy policyExecutes = Policy.Bulkhead(5);
12 for (int i = 0; i < 50; i++)
13 {
14 Task.Factory.StartNew(() =>
15 {
16 try
17 {
18 policyExecutes.Execute(() =>
19 {
20 RequestFromWeb();
21 });
22 }
23 catch (Exception ex)
24 {
25 PrintErrorLog(ex.Message);
26 throw;
27 }
28 });
29 }
30 }
31
32
33 private void RequestFromWeb()
34 {
35 resourceId++;//模拟异常场景
36 if (resourceId % 3 == 0)
37 {
38 throw new Exception($"请求数据包{resourceId}失败 请求过于频繁(报异常 但程序没崩溃哟)");
39 }
40 else
41 {
42 PrintSuccessLog($"请求数据包{resourceId}成功");
43 }
44 }
45 }
AdvancedCircuitBreaker
1 public class AdvancedCircuitBreaker : DemoBase
2 {
3 public override string DemoName => "Polly-Advanced CircuitBreaker Demo";
4
5 public override void RunDemo()
6 {
7 base.Init();
8
9 int runTimes = 50;
10 int minimumThroughput = 5;
11 int durationOfBreak = 5;
12 int samplingDuration = 5;
13
14
15 var policy = Policy
16 .Handle<Exception>()
17 .AdvancedCircuitBreaker(
18 failureThreshold: 0.5, //失败率达到50%才熔断
19 minimumThroughput: minimumThroughput, //最多调用多少次
20 samplingDuration: TimeSpan.FromSeconds(samplingDuration), //评估故障率的时间间隔的持续时间
21 durationOfBreak: TimeSpan.FromSeconds(durationOfBreak), //熔断时间
22 onBreak: (ex, breakDelay) => //熔断器打开时触发事件
23 {
24 PrintErrorLog($"进入熔断状态 熔断时长:{breakDelay} 错误信息:{ex.Message}");
25 },
26 onHalfOpen: () => //熔断器进入半打开状态触发事件
27 {
28 PrintSuccessLog($"进入HalfOpen状态");
29 },
30 onReset: () => //熔断器关闭状态触发事件
31 {
32 PrintSuccessLog("熔断器关闭");
33 }
34 );
35
36 for (int i = 1; i <= runTimes; i++)
37 {
38 try
39 {
40 policy.Execute(RequestFromWeb);
41 }
42 catch (BrokenCircuitException e)
43 {
44 PrintLog($"熔断触发{nameof(BrokenCircuitException)}异常");
45 }
46 catch (Exception e)
47 {
48 PrintLog($"异常:{e.GetType().Name},{e.Message}");
49 }
50 finally
51 {
52 Thread.Sleep(500);
53 }
54 }
55 }
56
57 private void RequestFromWeb()
58 {
59 if (new Random().Next(10) < 5)
60 {
61 throw new Exception("调用方法内部异常");
62 }
63 else
64 {
65 PrintSuccessLog("内部方法执行成功");
66 }
67 }
68
69 }
Cache
1 public class Cache : DemoBase
2 {
3 public override string DemoName => "Polly-Cache Demo";
4
5 public override void RunDemo()
6 {
7 base.Init();
8
9 var memoryCache = new MemoryCache(new MemoryCacheOptions());
10 ISyncCacheProvider memoryCacheProvider = new MemoryCacheProvider(memoryCache);
11
12 CachePolicy cache = Policy.Cache(memoryCacheProvider, ttl: TimeSpan.FromSeconds(3));//此处可以传递更多细节配置 当前仅设置了缓存超时时长
13
14 for (int i = 0; i < 5; i++)
15 {
16 string orderId = "12345678";
17 Context policyExecutionContext = new Context("GetOrder-By-Id-");
18 policyExecutionContext["orderId"] = orderId;
19 var result = cache.Execute(context => GetOrderInfoById(orderId), policyExecutionContext);
20 PrintInfo($"订单详情:{result}");
21 Thread.Sleep(i * 1000);
22 }
23 }
24
25 private string GetOrderInfoById(string orderId)
26 {
27 PrintInfo("从数据获取订单信息");
28 return $"订单号:{orderId} 订单金额:¥{124.5}";
29 }
30 }
努力工作 认真生活 持续学习 以勤补拙
