基于net core2.2的redis秒杀+数据持久化+数据恢复系列(2)

第一篇我们总结了秒杀的整个流程,本篇我们详细介绍下redis的秒杀实现,基于.net core2.2开发。

首先,需要安装redis,因为我在本地测试的,所以安装的windows版本的redis。redis分为服务端和客户端,这个redis怎么安装,本篇不详细说明,如果有安装问题和无法下载redis的windows版本的话可以私聊我。

第二步,就是编码部分,新建一个web api接口服务,使用redis的lua脚本做库存扣减,属于原子操作。大家就用下面的代码,可以支持1000个先线程的并发,在大的并发我没有测试,可能需要换架构。核心编码如下:

public string doKill(string threadId)
        {
            string tId = threadId;
            string uid = getCheckNumber(); //生成一个随机的用户id
            string prodid = "1";//商品固定001
            try
            {
                ConnectionMultiplexer redis = radisTemplate.getConn();
                IDatabase db = redis.GetDatabase();

                //定义Lua脚本
                string secKillScript = "local userid=KEYS[1];\r\n" +
                       "local prodid=KEYS[2];\r\n" +
                       "local qtkey='sk:'..prodid..\":qt\";\r\n" +
                       "local usersKey='sk:'..prodid..\":user\";\r\n" +
                       "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
                       "if tonumber(userExists)==1 then \r\n" +
                       "   return 2;\r\n" +
                       "end\r\n" +
                       "local num= redis.call(\"get\" ,qtkey);\r\n" +
                       "if tonumber(num)<=0 then \r\n" +
                       "   return 0;\r\n" +
                       "else \r\n" +
                       "   redis.call(\"decr\",qtkey);\r\n" +
                       "   redis.call(\"sadd\",usersKey,userid);\r\n" +
                       "end\r\n" +
                       "return 1";

                RedisKey[] s = new RedisKey[2];
                s[0] = uid;
                s[1] = prodid;

                //使用ScriptEvaluate执行脚本
                Object result = db.ScriptEvaluate(secKillScript, s);
                string result1 = result.ToString();
                if ("0".Equals(result1))
                {
                    Log.Info($"线程:{tId}:已抢空");
                    return "已抢空!!";
                }
                else if ("1".Equals(result1))
                {
                    RequestDto requestDto = new RequestDto
                    {
                        Id = tId,
                        SuccessDate = DateTime.Now,
                        Message = $"请求成功:{tId}"
                    };
                    string str = JsonConvert.SerializeObject(requestDto);
                    db.ListLeftPushAsync("sk2", str);
                    Log.Info($"线程:{tId}:抢到了");
                    return "抢购成功!!!!";
                }
                else if ("2".Equals(result1))
                {
                    Log.Info($"线程:{tId}:该用户已抢过");
                    return "该用户已抢过!!";
                }
                else
                {
                    Log.Info($"线程:{tId}:抢购异常");
                    return "抢购异常!!";
                }
            }
            catch (Exception ex)
            {
                Log.Info($"线程:{tId}:发生错误");
                Console.WriteLine(ex);
                Log.Error($"发生错误:{ex}");
                return "系统异常!!";
            }
        }
public static String getCheckNumber()
        {
            Random rd = new Random();
            int num = rd.Next(100000, 1000000);
            return num.ToString();
        }
 ///这上面是接口代码


///这边封装的一个redis帮助类


public class RadisConn
    {
        //定义连接器
        static ConnectionMultiplexer redis = null;
        //获取连接数据库
        public ConnectionMultiplexer getConn()
        {
            try
            {
                if (redis != null && redis.IsConnected)
                {
                    //Log.Info($"redis实例有效,直接返回!");
                    return redis;
                }
                //Log.Info($"redis实例无效,开始连接服务!");
                redis = ConnectionMultiplexer.Connect("127.0.0.1:6379,password=123456,abortConnect=false");
                //if (redis.IsConnected)
                //{
                //    Log.Info($"redis 连接服务成功!");
                //}
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            return redis;
        }

    }

    public class RequestDto
    {
        public string Id { get; set; }
        public DateTime SuccessDate { get; set; }
        public string Message { get; set; }
    }

第三步,切换到redis客户端,然后设置库存 set sk:1:qt 50

第四部,用压力测试工具或者自己写一个200个任务的请求这个接口,看看库存扣减是否正常。提供一个多线程实例,代码如下:

using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace HttpClient请求
{
    internal class Program
    {
        static void Main(string[] args)
        {
            try
            {
                for (int i = 0; i <40; i++)
                {
                    Task task = Task.Run(() =>
                    {
                        Console.WriteLine($"创建线程成功,当前线程号:{Task.CurrentId}");
                        SendRequest(Task.CurrentId.ToString());
                    });
                }
                Console.ReadKey();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }

        static void SendRequest(string threadId)
        {
            try
            {
                var url = $"https://localhost:5001/api/values/doKill?threadId="+ threadId;
                using (var client = new HttpClient())
                {
                    var content = client.GetStringAsync(url).Result;
                    //var data = JsonConvert.DeserializeObject(content);
                    Console.WriteLine($"线程号:{threadId}:{content}");
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }
}

 

你可能感兴趣的:(redis,数据库,缓存)