一.未做消息队列
缺陷:用户秒杀,请求到了上游服务秒杀服务,然后上游服务调用下游服务下订单,减去库存,更新余额。
上游服务秒杀服务的并发量能力有10000,下游服务的并发量能力有1000,当真的客户端并发量是10000,上游服务秒杀服务能接收10000个请求,但是下游服务只能接收1000个请求,那么下游服务就宕机了。
二.配合消息队列
上游服务并发来了10000个请求,只把1000个请求写入消息队列。
三.封装Redis消息队列,优化流量请求
namespace MyRedisUnitty
{
/// <summary>
/// 封装Redis消息队列
/// </summary>
public class RedisMsgQueueHelper : IDisposable
{
/// <summary>
/// Redis客户端
/// </summary>
public RedisClient redisClient { get; }
public RedisMsgQueueHelper(string redisHost)
{
redisClient = new RedisClient(redisHost);
}
/// <summary>
/// 入队
/// </summary>
/// <param name="qKeys">入队key</param>
/// <param name="qMsg">入队消息</param>
/// <returns></returns>
public long EnQueue(string qKey, string qMsg)
{
//1.编码字符串
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(qMsg);
//2.Redis消息队列入队
long count = redisClient.LPush(qKey, bytes);
return count;
}
/// <summary>
/// 出队(非阻塞) === 拉
/// </summary>
/// <param name="qKey">出队key</param>
/// <returns></returns>
public string DeQueue(string qKey)
{
//1.redis消息出队
byte[] bytes = redisClient.RPop(qKey);
string qMsg = null;
//2.字节转string
if (bytes == null)
{
Console.WriteLine($"{qKey}队列中数据为空");
}
else
{
qMsg = System.Text.Encoding.UTF8.GetString(bytes);
}
return qMsg;
}
/// <summary>
/// 出队(阻塞) === 推
/// </summary>
/// <param name="qKey">出队key</param>
/// <param name="timespan">阻塞超时时间</param>
/// <returns></returns>
public string DeQueueBlock(string qKey, TimeSpan? timespan)
{
//1.Redis消息出队
string qMsg = redisClient.BlockingPopItemFromList(qKey, timespan);
return qMsg;
}
/// <summary>
/// 获取队列数量
/// </summary>
/// <param name="qKey">队列key</param>
/// <returns></returns>
public long GetQueueCount(string qKey)
{
return redisClient.GetListCount(qKey);
}
/// <summary>
/// 关闭Redis
/// </summary>
public void Dispose()
{
redisClient.Dispose();
}
}
}
四.上游服务-使用Redis消息队列优化流量请求
发送消息,模拟下游可以接受请求量100,队列中数量超出100则抛出异常,否则写入队列。
namespace MyRedisFlowPeak.FlowLimit
{
/// <summary>
/// 秒杀上游服务:客户接受请求量100
/// </summary>
class SecKillUpstream
{
/// <summary>
/// 处理的最大请求数
/// </summary>
private int HandlerRequestCounts = 100;
/// <summary>
/// 秒杀方法
/// </summary>
/// <param name="RequestCounts">请求数量</param>
public void CreateSkillOrder(int requestCounts)
{
//1.创建秒杀订单
Console.WriteLine($"秒杀请求数量:{requestCounts}");
//如何使用Redis消息队列优化流量请求?
//Redis优化
using (var msgQueue = new RedisMsgQueueHelper("localhost:6379"))
{
//1.循环写入队列
for (int i = 0; i < requestCounts; i++)
{
//1.1.获取消息队列数量
long count = msgQueue.GetQueueCount("My_Order");
//1.2.判断是否已满
if (count >= HandlerRequestCounts)
{
Console.WriteLine($"系统正常繁忙,请稍后...");
}
else
{
//1.3.写入队列订单编号
Console.WriteLine($"入队成功");
msgQueue.EnQueue("My_Order", "OrderNo:" + i + "");
}
}
}
}
}
五.下游服务
消费上游消息,做下游服务的业务逻辑。
namespace MyRedisSecKillDownStream
{
class Program
{
static void Main(string[] args)
{
//Redis优化
using (var msgQueue = new RedisMsgQueueHelper("localhost:6379"))
{
Console.WriteLine("上游消息......");
//1.获取上游消息
while (true)
{
string rm_sms = msgQueue.DeQueueBlock("My_Order", TimeSpan.FromSeconds(60));
Console.WriteLine($"*****************开始秒杀下游业务调用**********************");
//2.下游业务逻辑,秒杀业务处理
SecKillDownstream secKillDownstream = new SecKillDownstream();
secKillDownstream.HandlerRequest(rm_sms);
Console.WriteLine($"*****************秒杀下游业务调用完成**********************");
}
}
}
}
}
namespace MyRedisSecKillDownStream.FlowLimit
{
/// <summary>
/// 下游服务:最大请求处理量为100
/// </summary>
class SecKillDownstream
{
/// <summary>
/// 处理请求
/// </summary>
/// <param name="requestCount"></param>
public void HandlerRequest(string requestCount)
{
Thread.Sleep(10);
//1.创建订单
Console.WriteLine($"秒杀订单创建成功");
//2.扣减库存
Console.WriteLine($"秒杀订单库存扣减生成");
//3.扣减余额
Console.WriteLine($"用户余额扣减成功");
Console.WriteLine($"处理的请求数:{requestCount}");
}
}
}
六.调用客户端
namespace MyRedisFlowPeak
{
class Program
{
static void Main(string[] args)
{
SecKillUpstream secKillUpstream = new SecKillUpstream();
secKillUpstream.CreateSkillOrder(1000);
}
}
}
七.运行效果
八.项目截图
旧书不厌百回读,熟读深思子自知。