1

我正在使用 Azure Redis 和 StackExchange.Redis 来提供 Twitter 之类的新闻提要。这发生在我身上:

我将代码发布到我的云服务,一切正常,包含 60 个提要项的页面在 300 毫秒内加载,这对我来说太棒了。这可以工作一段时间,比如说几个小时,然后提要页面的加载速度会减慢到 8000 毫秒左右。

奇怪的是,当我在 localhost 上测试相同的代码时,它始终保持在 500 毫秒左右的速度。我连接到同一个 Redis 实例,它在美国,我在欧洲(云服务和 Redis 在同一个 Azure 区域 - 美国东部)。

通过 Azue 门户,我可以看到 Redis Server 负载始终低于 10%,CPU 使用率低于 40%。由于这个和 localhost 上的良好速度加载,我相信 Redis 没有任何问题,显然我在我的代码中做了一些愚蠢的事情,我看不到它,所以这是我的代码:

这是我如何初始化与 Redis 的连接:

  public static class AzureRedisFeedsConnectionMultiplexer
{
    private static readonly Lazy<ConnectionMultiplexer> LazyConnection =
       new Lazy<ConnectionMultiplexer>(
           () =>
           {
               var config = new ConfigurationOptions
               {
                   AbortOnConnectFail = false,
                   Password = password,
                   EndPoints = { "myEndpoint" },
                   ConnectRetry = 5,
                   ConnectTimeout = 2 * 60 * 1000,
                   Ssl = true
               };

               return ConnectionMultiplexer.Connect(config);
           });

    private static ConnectionMultiplexer Connection
    {
        get { return LazyConnection.Value; }
    }

    public static ConnectionMultiplexer GetRedisConnection()
    {
        return Connection;
    }

    public static IDatabase GetRedisDatabase()
    {
        return Connection.GetDatabase();
    }
}

这是 API 调用(调用我的 FeedService):

  public async Task<List<FeedMobileHelper>> GetFeedForAllUsers(int page, int pageSize, int skip, int take)
    {
        return
            await
                _feedService.GetFeedWorkoutsFromRedisAsync(AzureRedisFeedsConnectionMultiplexer.GetRedisDatabase(),
                    User.Identity.GetUserId(), (page - 1)*pageSize, pageSize - 1);
    }

这是 FeedService 中的这个方法:

public async Task<List<FeedMobileHelper>> GetFeedWorkoutsFromRedisAsync(IDatabase redis, string userId, int numOfItemsToSkip, int numOfItemsToTake)
    {

        var redisFeed = await redis.ListRangeAsync("FeedAllWorkouts", numOfItemsToSkip, numOfItemsToSkip + numOfItemsToTake);

        /* WE KNOW FOR SURE TAHT THERE IS 10.000  ELEMENTS IN FEED SOTRED IN REDIS*/
        //so if there is request for more (i.e. already gotten (numOfItemsToSkip) +  numOfItemsToTake > 10.000 go to SQL

        if (numOfItemsToSkip + numOfItemsToTake > 10000)
        {
            //some items arealready fetched from redis, so we have to skip those
            var numOfItemsToGet = numOfItemsToSkip + numOfItemsToTake - 10000;
            return await GetFeedWorkoutsMobileAsync(numOfItemsToSkip, numOfItemsToGet, userId);
        }

        // if length is zero go to SQL
        if (redisFeed.Length <= 0)
        {
            return await GetFeedWorkoutsMobileAsync(numOfItemsToSkip, numOfItemsToSkip, userId);
        }

        return await CreateRedisFeedViewModelAsync(redisFeed, redis, userId);

    }

我想添加到 SQL(即调用方法 GetFeedWorkoutsMobileAsync)永远不会发生,所以 FeedItems 总是从 Redis 获取。在 Redis 中,它们存储为序列化对象列表。

现在这里是 CreateRedisFeedViewModelAsync:

        private async Task<List<FeedMobileHelper>> CreateRedisFeedViewModelAsync(RedisValue[] redisFeed, IDatabase redis, string userId)
    {
        // data to return
        var listToReturn = new List<FeedMobileHelper>();
        // feed in redis is serialized, we have to deserialize it 
        var feedDeserialized = new RedisFeedItemWorkout[redisFeed.Length];
        // list of all workout ids that are needed for this feed page
        var workoutIds = new string[redisFeed.Length];
        //list of alll user ids needed for this feed page
        var userIds = new string[redisFeed.Length];
        var redisFeedService = new RedisFeedService();

        using (var databaseContext = new MadBarzDatabaseContext())
        {
            // go to data from redis, deserialize everything, and allso fetch necessary ids
            for (int i = 0; i < redisFeed.Length; i++)
            {
                var feedItem = JsonConvert.DeserializeObject<RedisFeedItemWorkout>(redisFeed[i]);
                feedDeserialized[i] = feedItem;
                workoutIds[i] = feedItem.WorkoutId;
                userIds[i] = feedItem.UserId;
            }

            //go to redis and fetch data about users and workouts, idea here is that we don't go to redis seperately for each user, but we fetch data for all users
            //and workouts needed to create this feed page 
            // long live redis pipeling  
            var redisFeedData = await redisFeedService.GetDataForFeed(redis, userId, workoutIds, userIds);


            var workoutsFromRedis = redisFeedData.Workouts;
            var usersFromRedis = redisFeedData.Users;
            var commentsFromRedis = redisFeedData.Comments;

            // in this for loop I just map and connect everything and if needed (i.e. if I don't get data from redis) I go and fetch data from SQL
            // that almost never happens 
            for (int i = 0; i < feedDeserialized.Length; i++)
            {
                RedisWorkoutItem workout;
                bool hasRespected;
                string username, fullName, profilePic;

                var userRedis = usersFromRedis[i];
                var stringWorkout = workoutsFromRedis[i];
                var workoutComment = commentsFromRedis[i].HasValue ? commentsFromRedis[i].ToString() : "";

                if (userRedis != null)
                {
                    profilePic = userRedis["ProfilePhotoUrl"].HasValue
                        ? userRedis["ProfilePhotoUrl"].ToString()
                        : "";
                    fullName = userRedis["FirstName"] + " " + userRedis["LastName"];
                    username = userRedis["UserName"].HasValue ? userRedis["UserName"].ToString() : "";

                }
                else
                {
                    var user = databaseContext.Users.Find(feedDeserialized[i].UserId);
                    profilePic = user.ProfilePhotoUrl;
                    username = user.UserName;
                    fullName = user.FirstName + " " + user.LastName;
                }

                if (stringWorkout.HasValue)
                {
                    workout = JsonConvert.DeserializeObject<RedisWorkoutItem>(stringWorkout);
                    hasRespected = workout.UsersWhoRespected.Contains(userId);
                }
                else
                {
                    var workoutGuid = Guid.Parse(feedDeserialized[i].WorkoutId);

                    var workoutFromDb = await databaseContext.Trenings.FindAsync(workoutGuid);
                    var routine = await databaseContext.AllRoutineses.FindAsync(workoutFromDb.AllRoutinesId);
                    workout = new RedisWorkoutItem
                    {
                        Name = routine.Name,
                        Id = workoutFromDb.TreningId.ToString(),
                        Comment = workoutFromDb.UsersCommentOnWorkout,
                        DateWhenFinished = workoutFromDb.DateTimeWhenTreningCreated,
                        NumberOfRespects = workoutFromDb.NumberOfLikes,
                        NumberOfComments = workoutFromDb.NumberOfComments,
                        UserId = workoutFromDb.UserId,
                        Length = workoutFromDb.LengthInSeconds,
                        Points = workoutFromDb.Score


                    };

                    workoutComment = workoutFromDb.UsersCommentOnWorkout;


                    hasRespected = databaseContext.TreningRespects
                        .FirstOrDefault(r => r.TreningId == workoutGuid && r.UserId == userId) != null;
                }

                string workoutLength;

                if (workout.Length >= 3600)
                {
                    var t = TimeSpan.FromSeconds(workout.Length);
                    workoutLength = $"{t.Hours:D2}:{t.Minutes:D2}:{t.Seconds:D2}";


                }
                else
                {
                    var t = TimeSpan.FromSeconds(workout.Length);
                    workoutLength = $"{t.Minutes:D2}:{t.Seconds:D2}";

                }


                listToReturn.Add(new FeedMobileHelper
                {
                    Id = feedDeserialized[i].Id.ToString(),
                    UserId = workout.UserId,
                    WorkoutId = feedDeserialized[i].WorkoutId,
                    Points = workout.Points.ToString("N0", new NumberFormatInfo
                    {
                        NumberGroupSizes = new[] { 3 },
                        NumberGroupSeparator = "."
                    }),
                    WorkoutName = workout.Name,
                    WorkoutLength = workoutLength,
                    NumberOfRespects = workout.NumberOfRespects,
                    NumberOfComments = workout.NumberOfComments,
                    WorkoutComment = workoutComment,
                    HasRespected = hasRespected,
                    UserImageUrl = profilePic,
                    UserName = username,
                    DisplayName = string.IsNullOrWhiteSpace(fullName) ? username : fullName,
                    TimeStamp = workout.DateWhenFinished,
                    DateFormatted = workout.DateWhenFinished.FormatDateToHrsDaysWeeksString()
                });
            }
        }

        return listToReturn;
    }

我相信这种方法存在问题,但我无法识别它:

public async Task<RedisFeedDataModel> GetDataForFeed(IDatabase redisDb, string userId, string[] workoutIds, string[] userIds)
    {
        var listOfUsers = new Task<HashEntry[]>[userIds.Length];
        var workoutsToReturn = new List<RedisValue>();
        var usersToReturn = new List<Dictionary<RedisValue, RedisValue>>();
        var commentsToReturn = new List<RedisValue>();
        var listOfTaks = new List<Task<RedisValue>>();


        for (var i = 0; i < userIds.Length; i++)
        {
            listOfTaks.Add(redisDb.StringGetAsync("workout:" + workoutIds[i]));
            listOfTaks.Add(redisDb.StringGetAsync("workoutComment:" + workoutIds[i]));
            listOfUsers[i] = redisDb.HashGetAllAsync("user:" + userIds[i]);
        }

        var resultForListOfTasks =  await Task.WhenAll(listOfTaks);
        var resultForListOfUsers = await Task.WhenAll(listOfUsers);

        for (var i = 0; i < resultForListOfTasks.Length; i += 2)
        {
            workoutsToReturn.Add(resultForListOfTasks[i]);
            commentsToReturn.Add( resultForListOfTasks[i + 1]);
        }

        for (int i = 0; i < resultForListOfUsers.Length; i++)
        {

            var redisUser = resultForListOfUsers[i];
            var userToReturn = redisUser.Length > 0 ? redisUser.ToDictionary(t => t.Name, t => t.Value) : null;
            usersToReturn.Add(userToReturn);
        }


        return new RedisFeedDataModel
        {
            Comments = commentsToReturn,
            Workouts = workoutsToReturn,
            Users = usersToReturn

        };

    }

有趣的是,当生产中的提要调用减慢时,如果我尝试使用相同的数据对本地主机上的同一个 RedisDB 进行相同的调用,它比生产上要快得多(例如在生产上持续 8000 毫秒,在本地主机上需要 500 毫秒)

4

0 回答 0