场景介绍

权限筛查需要汇总用户计算,比如领导角色权限能看到该部门下的所有员工单据。

不要考虑有没有更好的权限功能架构设计方案,因为只得基于现有架构上优化性能。

优化接口概述

接口定义:请忽略这个返回值,反面教材

/// <summary>
/// 获取当前登录人、当前页面得权限
/// 将项目和部门人员、个人的权限数据组合起来
/// </summary>
/// <param name="currentRouterUrl">当前路由</param>
/// <returns>权限(item1:全部 item2:所属部门或者公司 item3:项目 item4:个人 item5:选择得项目,item6:当前登录人所属部门下的所有人员或者是选择的公司下的所有人员)) item7:所属部门下的所有部门</returns>
public Tuple<bool, bool, bool, bool, List<string>, List<string>> GetCurrentUserDataRight(string currentRouterUrl = "")

接口调用方

var right = _businessRoleService.GetCurrentUserDataRight(); // 权限
return _dbContext.Queryable<表名称>()
.WhereIF(right.Item2 || right.Item3 || right.Item4, t => SqlFunc.ContainsArray(right.Item5, t.ProjectCode) || SqlFunc.ContainsArray(right.Item5, t.CreateUserCode));

数据环境

有这样一个用户表,数据量为8848条记录,且只查询特定需求字段,不含大varchar和text类属性值字段

使用redis进行缓存,以减轻数据库访问压力,注意不是数据库单次查询压力

单次查询数据库8848条记录,妥妥的无压力毫秒级

但是由于这个数据是为了做底层服务用的(权限筛查需要汇总用户计算,比如领导角色权限能看到该部门下的所有员工单据,需要抓到符合条件的员工工号),所以并发访问特别高,所以数据库的请求次数压力会不小,必须放入缓存中

之前人设计的是string缓存,8k+的数据反序列化到一个string缓存上, 容量大概7~8M空间。典型的大value型缓存事故,首先每次获取就很慢,会导致Redis服务线程阻塞不说, 这样的数据正反序列化开销也不小

补充点

权限筛查需要汇总用户计算,比如领导角色权限能看到该部门下的所有员工单据

这个呢 就不要考虑为啥不能按照部门id等其他维度来代替做权限功能,系统历史遗留问题,想办法再现有架构上优化性能。

改进方案

考虑改用hash

但是这样的结构对于单个用户查缓存so easy。给底层服务(权限筛查汇总计算用)

上HashScan代码

public static List<T> HashScan<T>(string key, long count = 50)
        {
            return Catch<List<T>>(key, () =>
            {
                List<T> result = new List<T>();
                var rediscli = GetClient();
                long cursor = 0;
                TimeConsumingLogHelper log = new TimeConsumingLogHelper().Start().AddLog("1");
                do
                {
                    var scanResp = rediscli.HScan<T>(key, cursor, count: count);
                    log.AddLog("xxx1");
                    cursor = scanResp.Cursor;
                    if (scanResp.Items.Length > 0)
                    {
                        result.AddRange(scanResp.Items.ToList().Select(x => x.value).ToList());
                    }
                    log.AddLog("xxx2");
                }
                while (cursor > 0);
                var sw = log.CollectLog();
                return result;
            });
        }

传入count为500时

当前毫秒数:30=>1
当前毫秒数:338=>xxx1
当前毫秒数:338=>xxx2
当前毫秒数:395=>xxx1
当前毫秒数:395=>xxx2
当前毫秒数:459=>xxx1
当前毫秒数:459=>xxx2
当前毫秒数:518=>xxx1
当前毫秒数:518=>xxx2
当前毫秒数:578=>xxx1
当前毫秒数:578=>xxx2
当前毫秒数:649=>xxx1
当前毫秒数:649=>xxx2
当前毫秒数:704=>xxx1
当前毫秒数:704=>xxx2
当前毫秒数:774=>xxx1
当前毫秒数:774=>xxx2
当前毫秒数:831=>xxx1
当前毫秒数:831=>xxx2
当前毫秒数:885=>xxx1
当前毫秒数:885=>xxx2
当前毫秒数:937=>xxx1
当前毫秒数:937=>xxx2
当前毫秒数:984=>xxx1
当前毫秒数:984=>xxx2
当前毫秒数:1030=>xxx1
当前毫秒数:1030=>xxx2
当前毫秒数:1080=>xxx1
当前毫秒数:1080=>xxx2
当前毫秒数:1135=>xxx1
当前毫秒数:1135=>xxx2
当前毫秒数:1184=>xxx1
当前毫秒数:1184=>xxx2
当前毫秒数:1219=>xxx1
当前毫秒数:1219=>xxx2
总耗时:1219毫秒

HMset复杂度

1.5s左右, 有时候会3s甚至6s的,不稳定

比for循环hset 性能好,减少网络和连接损耗等干扰

TimeConsumingLogHelper log = new TimeConsumingLogHelper().Start().AddLog("1");
            CSRedisHelper.HMSet(UserHashKey, mods);
            var sw = log.AddLog("xxx2").CollectLog()
当前毫秒数:0=>1
当前毫秒数:3503=>xxx2
总耗时:3503毫秒

当前毫秒数:0=>1
当前毫秒数:2196=>xxx2
总耗时:2196毫秒

当前毫秒数:0=>1
当前毫秒数:1303=>2
当前毫秒数:1972=>2.5
当前毫秒数:1973=>2.8
当前毫秒数:1975=>3
当前毫秒数:2879=>4
总耗时:2879毫秒

当前毫秒数:0=>1
当前毫秒数:1335=>2
当前毫秒数:3337=>2.5
当前毫秒数:3337=>2.8
当前毫秒数:3348=>3
当前毫秒数:6296=>4
总耗时:6296毫秒

Hset

public List<RedisUser> UpdateOrAddUser()
        {
            // var redisclient = RedisClient.GetDataBase();
            TimeConsumingLogHelper log = new TimeConsumingLogHelper().Start().AddLog("1");
            var usrslist = _dbContext.Boost.Queryable<USRUser>().Where(t => t.Status == 1)
               .ToList();
            log.AddLog("2");
            // string value = JsonConvert.SerializeObject(usrslist);
            // _redis.Client.StringSet("users", value);
            // CSRedisHelper.SetStr("users", value, -1);
            var mods = new Dictionary<string, RedisUser>();

            usrslist?.ForEach(u =>
            {
                mods.Add(u.UserID, u);
                //_redis.Client.HashSetAsync("users-hash", u.UserID, JsonConvert.SerializeObject(u));
                CSRedisHelper.SetHash(UserHashKey, u.UserID, u);
            });
            // 给单个用户查缓存用
            log.AddLog("3");
            CSRedisHelper.HMSet(UserHashKey, mods);
            var sw = log.AddLog("4").CollectLog();
            // HashSet(UserHashKey, mods);
            return usrslist;
        }
当前毫秒数:0=>1
当前毫秒数:1676=>2
当前毫秒数:187895=>3
当前毫秒数:188473=>4
总耗时:188473毫秒

stringset耗时

log.AddLog("2");
            string value = JsonConvert.SerializeObject(usrslist);
            // _redis.Client.StringSet("users", value);
             CSRedisHelper.SetStr("users", value, -1);
            log.AddLog("2.5");
            var mods = new Dictionary<string, RedisUser>();

            usrslist?.ForEach(u =>
            {
                mods.Add(u.UserID, u);
                //_redis.Client.HashSetAsync("users-hash", u.UserID, JsonConvert.SerializeObject(u));
                // CSRedisHelper.SetHash(UserHashKey, u.UserID, u); // 180多秒 不要用,网络连接损耗等干扰
            });
            // 给单个用户查缓存用
            log.AddLog("3");
            CSRedisHelper.HMSet(UserHashKey, mods);
            var sw = log.AddLog("4").CollectLog();
当前毫秒数:1275=>2
当前毫秒数:4310=>2.5


当前毫秒数:1270=>2
当前毫秒数:3301=>2.5

当前毫秒数:0=>1
当前毫秒数:1358=>2
当前毫秒数:2187=>2.5
当前毫秒数:2189=>3
当前毫秒数:2959=>4

stringget耗时

两个redis组件耗时差不多,都是700ms。

并且反映出==getstring获取7~8M左右的大value耗时还可以接受==,欣慰,只是set进去耗时长点2s多点

List<RedisUser> result = new List<RedisUser>();
            TimeConsumingLogHelper log = new TimeConsumingLogHelper().Start().AddLog("1");
            result = ServiceHelperProvider.Instance.RedisHelper.Get<List<RedisUser>>("users");
            log.AddLog("2");
            var result2 = CSRedisHelper.GetStr<List<RedisUser>>("users");
            var sw = log.AddLog("3").CollectLog();
当前毫秒数:0=>1
当前毫秒数:722=>2
当前毫秒数:1432=>3
总耗时:1432毫秒

==最终我的方案是用内存缓存~==

写入8k多条的集合只要10ms不到,读取也是2ms甚至不耗时,贼快

这玩意本身就是要到内存中进行后续计算的,直接放内存不是更好!注意内存的坑即可

使用本地缓存极有可能导致严重的线程安全问题,并发考虑严重

根据MSDN的描述:MemoryCache是线程安全的。那么说明,在操作MemoryCache中的缓存项时,MemoryCache保证程序的行为都是原子性的,而不会出现多个线程共同操作导致的数据污染等问题。

附录内存缓存测试打样

public List<RedisUser> UpdateOrAddUser()
        {
            // var redisclient = RedisClient.GetDataBase();
            TimeConsumingLogHelper log = new TimeConsumingLogHelper().Start().AddLog("1");
            var usrslist = _dbContext.Boost.Queryable<USRUser>().Where(t => t.Status == 1)
                .ToList();
            log.AddLog("2");
            string value = JsonConvert.SerializeObject(usrslist);
            // _redis.Client.StringSet(UserFullLoadKey, value);
            CSRedisHelper.SetStr(UserFullLoadKey, value, -1);
            log.AddLog("2.5");
            MemoryCacheCoreHelper.SetCache<List<RedisUser>>(UserFullLoadKey, usrslist, 24 * 60 * 60 - 500);
            log.AddLog("2.8");
            var mods = new Dictionary<string, RedisUser>();

            usrslist?.ForEach(u =>
            {
                mods.Add(u.UserID, u);
                //_redis.Client.HashSetAsync("users-hash", u.UserID, JsonConvert.SerializeObject(u));
                // CSRedisHelper.SetHash(UserHashKey, u.UserID, u); // 180多秒 不要用,网络连接损耗等干扰
            });
            // 给单个用户查缓存用
            log.AddLog("3");
            CSRedisHelper.HMSet(UserHashKey, mods);
            var sw = log.AddLog("4").CollectLog();
            // HashSet(UserHashKey, mods);
            return usrslist;
        }
当前毫秒数:0=>1
当前毫秒数:1656=>2
当前毫秒数:3877=>2.5
当前毫秒数:3883=>2.8
当前毫秒数:3886=>3
当前毫秒数:8681=>4
总耗时:8681毫秒