首页 文章 精选 留言 我的

精选列表

搜索[表单设计器],共10000篇文章
优秀的个人博客,低调大师

MASA Framework 缓存入门与设计

概念 什么是缓存,在项目中,为了提高数据的读取速度,我们会对不经常变更但访问频繁的数据做缓存处理,我们常用的缓存有: 本地缓存 内存缓存:IMemoryCache 分布式缓存 Redis: StackExchange.Redis 功能 目前,MasaFramework为我们提供了以下能力 IDistributedCacheClient: 分布式缓存 Masa.Contrib.Caching.Distributed.StackExchangeRedis: 基于StackExchange.Redis实现的分布式缓存 IMultilevelCacheClient: 多级缓存 Masa.Contrib.Caching.MultilevelCache: 基于内存缓存以及分布式缓存实现的多级缓存,支持监控缓存变更,分布式缓存更新后相应的内存缓存也会同步更新,避免命中过时的内存缓存导致获取错误的数据,同时也尽可能的将多个副本的内存缓存保持同步 入门 前提条件:安装.NET 6.0 分布式缓存 新建ASP.NET Core 空项目Assignment.DistributedCache,并安装Masa.Contrib.Caching.Distributed.StackExchangeRedis dotnet new web -o Assignment.DistributedCache cd Assignment.DistributedCache dotnet add package Masa.Contrib.Caching.Distributed.StackExchangeRedis --version 0.6.0-rc.5 配置Redis配置信息 { "RedisConfig":{ "Servers":[ { "Host":"localhost", "Port":6379 } ], "DefaultDatabase":3, "ConnectionPoolSize":10 } } 注册分布式缓存,并使用Redis缓存,修改Program.cs var builder = WebApplication.CreateBuilder(args); //注册分布式缓存 builder.Services.AddDistributedCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache();//使用分布式Redis缓存, 默认使用本地`RedisConfig`下的配置 }); 使用分布式缓存的数据来源默认为 IOptionsMonitor<RedisConfigurationOptions>,如果本地未正确在RedisConfig节点配置缓存信息,且项目中也没有通过其它方式配置使其支持选项模式,则默认使用的Redis配置为: 地址: localhost、端口:6379,密码:空,数据库:db0 新建User类,用于接收用户信息 public class User { public string Name { get; set; } public int Age { get; set; } } 如何使用IDistributedCacheClient,修改Program.cs // 设置缓存 app.MapPost("/set/{id}", async (IDistributedCacheClient distributedCacheClient, [FromRoute] string id, [FromBody] User user) => { await distributedCacheClient.SetAsync(id, user); return Results.Accepted(); }); // 获取缓存 app.MapGet("/get/{id}", async (IDistributedCacheClient distributedCacheClient, [FromRoute] string id) => { var value = await distributedCacheClient.GetAsync<User>(id); return Results.Ok(value); }); 多级缓存 新建ASP.NET Core 空项目Assignment.DistributedCache,并安装Masa.Contrib.Caching.MultilevelCache、Masa.Contrib.Caching.Distributed.StackExchangeRedis dotnet new web -o Assignment.MultilevelCache cd Assignment.MultilevelCache dotnet add package Masa.Contrib.Caching.MultilevelCache --version 0.6.0-rc.5 dotnet add package Masa.Contrib.Caching.Distributed.StackExchangeRedis --version 0.6.0-rc.5 注册多级缓存,并使用分布式Redis缓存,修改Program.cs var builder = WebApplication.CreateBuilder(args); //注册多级缓存 builder.Services.AddMultilevelCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache();//使用分布式Redis缓存 }); 新建User类,用于接收用户信息 public class User { public string Name { get; set; } public int Age { get; set; } } 如何使用IMultilevelCacheClient,修改Program.cs // 设置缓存 app.MapPost("/set/{id}", async (IMultilevelCacheClient multilevelCacheClient, [FromRoute] string id, [FromBody] User user) => { await multilevelCacheClient.SetAsync(id, user); return Results.Accepted(); }); // 获取缓存 app.MapGet("/get/{id}", async (IMultilevelCacheClient multilevelCacheClient, [FromRoute] string id) => { var value = await multilevelCacheClient.GetAsync<User>(id); return Results.Ok(value); }); 测试 借助Postman或者Swagger或者使用其它API测试工具,分别测试设置缓存与获取缓存,以验证分布式缓存以及多级缓存是可以正常使用的。 友情提示:检查Redis缓存,找到刚刚你配置的缓存,确定下它的存储结果是否与你想象的一致!! 规则 经过测试,我们的分布式缓存与多级缓存是可以正常使用的,但查看Redis的存储结果后,发现它们实际的存储与我们心目中的结果好像是有点出入,它们分别是: 缓存Key不同 (与我们设置的Key不完全一致) 结构不同 (实际存储的为Hash类型) 内容不同 (内容经过压缩) 缓存Key的生成规则 缓存Key支持三种规则: 枚举 值 描述 None 1 不做处理,传入的Key即为实际的缓存Key TypeName 2 实际的缓存Key = $"{GetTypeName(T)}.{传入缓存Key}" (默认) TypeAlias 3 根据TypeName得到对应的别名与Key的组合,Format: ${TypeAliasName}{:}{key} 详细规则可查看 存储结构与规则 Masa.Contrib.Caching.Distributed.StackExchangeRedis使用的是Hash存储,通过使用Hash存储,支持缓存的绝对过期以及相对过期,其中: 键 描述 详细 特殊 absexp 绝对过期时间的Ticks 自公历 0001-01-01 00:00:00:000 到绝对过期时间的计时周期数 (1周期 = 100ns 即 1/10000 ms) -1 为永不过期 sldexp 滑动过期时间的Ticks 自公历 0001-01-01 00:00:00:000 到滑动过期时间的计时周期数 (1周期 = 100ns 即 1/10000 ms,每次获取数据时会刷新滑动过期时间) -1 为永不过期 data 数据 存储用户设置的缓存数据 内容压缩规则 当存储值类型为以下类型时,不对数据进行压缩: Byte SByte UInt16 UInt32 UInt64 Int16 Int32 Int64 Double Single Decimal 当存储值类型为字符串时,对数据进行压缩 当存储值类型不满足以上条件时,对数据进行序列化并进行压缩 分布式Redis缓存示例 分布式缓存注册 方案一. 通过本地配置文件注册 修改appsettings.json文件 { "RedisConfig":{ "Servers":[ { "Host":"localhost", "Port":6379 } ], "DefaultDatabase":3, "ConnectionPoolSize":10 } } 注册分布式Redis缓存 builder.Services.AddDistributedCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache(); }); 方案二. 手动指定Redis配置注册 builder.Services.AddDistributedCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache(options => { options.Servers = new List<RedisServerOptions>() { new("localhost", 6379) }; options.DefaultDatabase = 3; options.ConnectionPoolSize = 10; options.GlobalCacheOptions = new CacheOptions() { CacheKeyType = CacheKeyType.None //全局禁用缓存Key格式化处理 }; }); }); 方案三. 通过选项模式注册 通过Configure方法使其支持选项模式 builder.Services.Configure<RedisConfigurationOptions>(redisConfigurationOptions => { redisConfigurationOptions.Servers = new List<RedisServerOptions>() { new("localhost", 6379) }; redisConfigurationOptions.DefaultDatabase = 3; redisConfigurationOptions.ConnectionPoolSize = 10; redisConfigurationOptions.GlobalCacheOptions = new CacheOptions() { CacheKeyType = CacheKeyType.None }; }); 注册分布式Redis缓存 builder.Services.AddDistributedCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache(); }); 方案四. 通过指定Configuration注册 在Redis缓存的配置存储到本地appsettings.json文件 { "RedisConfig":{ "Servers":[ { "Host": "localhost", "Port": 6379 } ], "DefaultDatabase": 3, "ConnectionPoolSize": 10 } } 指定Configuration注册分布式Redis缓存 var builder = WebApplication.CreateBuilder(args); //注册分布式缓存 builder.Services.AddDistributedCache(distributedCacheOptions => { // 使用存储Redis配置的Configuration distributedCacheOptions.UseStackExchangeRedisCache(builder.Configuration.GetSection("RedisConfig")); }); 方案五. 将配置存储到Dcc上,并通过Configuration提供的手动映射功能,实现选项模式 使用Dcc,并手动指定映射 builder.AddMasaConfiguration(configurationBuilder => { configurationBuilder.UseDcc();//使用Dcc 扩展Configuration能力,支持远程配置 configurationBuilder.UseMasaOptions(options => { //通过手动映射RedisConfigurationOptions的配置,实现选项模式 options.MappingConfigurationApi<RedisConfigurationOptions>("{替换为Dcc中配置所属的AppId}", "{替换为Redis配置的对象名称}"); }); }); 注册分布式Redis缓存 builder.Services.AddDistributedCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache(); }); 方案三、四、五的本质都是通过支持选项模式来注册分布式Redis缓存 修改缓存Key映射规则 修改缓存Key映射规则十分简单,我们在配置时更改CacheKeyType为对应的规则即可,但当 CacheKeyType = 3 需要注意,它需要额外提供类型名与别名的对应关系,完整例子如下: 修改appsettings.json, 将CacheKeyType的值改为 3 { "RedisConfig":{ "Servers":[ { "Host":"localhost", "Port":6379 } ], "DefaultDatabase":3, "ConnectionPoolSize":10, "GlobalCacheOptions": { "CacheKeyType": 3 //CacheKeyType为3时启用别名格式化缓存Key,可节省缓存Key的键长度 } } } 注册分布式缓存并配置类型名与别名的对应关系 builder.Services.AddDistributedCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache(); }, typeAliasOptions => { typeAliasOptions.GetAllTypeAliasFunc = () => new Dictionary<string, string>() { { "String", "s" }//当类型为String时,格式化后的Key为 s:key }; }); 通过指定类型与别名的对应关系,从而使得最终形成较短的缓存Key,以达到节省存储空间的目的,缓存Key生成规则可查看 多级缓存示例 多级缓存注册 方案一. 通过本地配置文件注册 修改appsettings.json文件,分别配置多级缓存配置以及Redis缓存配置 { // 多级缓存全局配置,非必填 "MultilevelCache": { "SubscribeKeyPrefix": "masa",//默认订阅方key前缀,用于拼接channel "SubscribeKeyType": 3, //默认订阅方key的类型,默认ValueTypeFullNameAndKey,用于拼接channel "CacheEntryOptions": { "AbsoluteExpirationRelativeToNow": "00:00:30",//绝对过期时长(距当前时间) "SlidingExpiration": "00:00:50"//滑动过期时长(距当前时间) } }, // Redis分布式缓存配置 "RedisConfig": { "Servers": [ { "Host": "localhost", "Port": 6379 } ], "DefaultDatabase": 3 } } 添加多级缓存并使用分布式Redis缓存 builder.Services.AddMultilevelCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache(); }); 方案二. 通过手动指定配置 builder.Services.AddMultilevelCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache(RedisConfigurationOptions); }); 未配置内存缓存时,默认内存缓存永久有效 除了上述两种方式以外,多级缓存的内存缓存配置也同样支持选项模式,我们可以通过Dcc或者利用 builder.Services.Configure<MultilevelCacheOptions>(builder.Configuration)来支持选项模式 修改缓存Key映射规则 源码解读 <a id = "IDistributedCacheClient">IDistributedCacheClient (分布式缓存客户端)</a> IDistributedCacheClient接口提供以下方法来处理分布式缓存 以下方法会根据全局缓存Key的规则配置以及传入缓存Key的规则配置,检测是否需要格式化缓存Key,对需要格式化Key的操作按照缓存Key格式化规则进行处理,详细查看: Get<T>、GetAsync<T>: 根据缓存Key返回类型为T的结果 (如果缓存不存在,则返回Null) GetList<T>、GetListAsync<T>: 根据缓存Key集合返回对应的缓存值的集合 (针对不存在的缓存key,其值返回Null) GetOrSet<T>、GetOrSetAsync<T>: 如果在缓存中找到,则返回类型为T的结果,如果缓存未找到,则执行Setter,并返回Setter的结果 Set<T>、SetAsync<T>: 将指定的缓存Key以及缓存值添加到缓存 SetList<T>、SetListAsync<T>: 将指定的缓存Key、Value集合添加缓存 Remove<T>、RemoveAsync<T>: 将指定的缓存Key (缓存Key集合) 从缓存中移除 Refresh<T>、RefreshAsync<T>: 刷新指定的缓存Key (缓存Key集合) 的生命周期 适用于未被删除、绝对过期时间没有到,但相对过期时间快到的缓存 (延长滑动过期时间) Exists<T>、ExistsAsync<T>: 如果在缓存中找到,则返回true,否则返回false GetKeys<T>、GetKeysAsync<T>: 根据key pattern 得到符合规则的所有缓存Key GetByKeyPattern<T>、GetByKeyPatternAsync<T>: 根据key pattern 得到符合规则的所有缓存Key、Value集合 HashIncrementAsync: 将指定的缓存Key的值增加Value,并返回增长后的结果 HashDecrementAsync: 将指定的缓存Key的值减少Value,并返回减少后的结果 支持设置最小的Value,避免减少后的值低于设置的最小值,执行失败则返回: -1 KeyExpire<T>、KeyExpireAsync<T>: 设置缓存Key的生命周期 以下方法不执行缓存Key格式化, 应传入缓存完整Key: Remove、RemoveAsync: 将指定的缓存Key (缓存Key集合) 从缓存中移除 Refresh、RefreshAsync: 刷新指定的缓存Key (缓存Key集合) 的生命周期 适用于未被删除、绝对过期时间没有到,但相对过期时间快到的缓存 Exists、ExistsAsync: 如果在缓存中找到,则返回true,否则返回false GetKeys、GetKeysAsync: 根据key pattern 得到符合规则的所有缓存Key 例: 传入User*,可得到缓存中以User开头的所有缓存Key KeyExpire、KeyExpireAsync: 设置缓存Key的生命周期 <a id = "IMultilevelCacheClient">IMultilevelCacheClient (多级缓存客户端)</a> Get<T>、GetAsync<T>: 根据缓存Key返回类型为T的结果 (如果缓存不存在,则返回Null) (支持监控缓存变更) GetList<T>、GetListAsync<T>: 根据缓存Key集合返回对应的缓存值的集合 (针对不存在的缓存key,其值返回Null) GetOrSet<T>、GetOrSetAsync<T>: 如果在缓存中找到,则返回类型为T的结果,如果缓存未找到,则执行Setter,并返回Setter的结果 Set<T>、SetAsync<T>: 将指定的缓存Key以及缓存值添加到缓存 SetList<T>、SetListAsync<T>: 将指定的缓存Key、Value集合添加缓存 Remove<T>、RemoveAsync<T>: 将指定的缓存Key (缓存Key集合) 从缓存中移除 Refresh<T>、RefreshAsync<T>: 刷新指定的缓存Key (缓存Key集合) 的生命周期 适用于未被删除、绝对过期时间没有到,但相对过期时间快到的缓存 (延长滑动过期时间) <a id = "IDistributedCacheClient">IDistributedCacheClientFactory (分布式缓存工厂)</a> Create: 返回指定Name的分布式缓存客户端 <a id = "IMultilevelCacheClientFactory">IMultilevelCacheClientFactory (多级缓存工厂)</a> Create: 返回指定Name的多级缓存客户端 如果Name为空字符串时,可直接使用IDistributedCacheClient或IMultilevelCacheClient, 默认注册不指定Name时,则其Name为空字符串,可不通过Factory创建 总结 Masa Framework提供了分布式缓存以及多级缓存的实现,其中有几个优秀的功能: 多级缓存提供了缓存更新后同步更新内存缓存功能 当我们的服务是多副本时,不必担心会缓存更新后其它副本由于内存缓存未过期,导致获取到过期的缓存数据,大大提升我们的用户体验 支持滑动过期以及绝对过期混合使用 避免无用的缓存长时间被持久化,但对于热点数据又可以避免打到Redis或者数据库 配置支持热更新,配置更新后同步生效,无需重启项目 缓存Key支持格式化,可根据当前缓存值类型与传入缓存Key结合形成新的缓存Key,提高了开发效率以及代码可读性 比如获取用户id为1的数据,可通过Client.Get<User>("1"),而无需:Client.Get<User>("User.1") 本章源码 Assignment16 https://github.com/zhenlei520/MasaFramework.Practice 开源地址 MASA.Framework:https://github.com/masastack/MASA.Framework 如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们 WeChat:MasaStackTechOps QQ:7424099

优秀的个人博客,低调大师

浅析扣减库存的方案设计

你好,我是悟空,今天来探讨下扣减库存的方案。 生活中,我们总是用各种电商app抢购商品,但是库存数是很少的,特别是秒杀场景,商品可能就一件,那如何保证不会出现超卖的情况呢? 一、扣减库存的三种方案 (1)下单减库存 用户下单时减库存 优点:实时减库存,避免付款时因库存不足减库存的问题 缺点:恶意买家大量下单,将库存用完,但是不付款,真正想买的人买不到 (2)付款减库存 下单页面显示最新的库存,下单时不会立即减库存,而是等到支付时才会减库存。 优点:防止恶意买家大量下单用光库存,避免下单减库存的缺点 缺点:下单页面显示的库存数可能不是最新的库存数,而库存数用完后,下单页面的库存数没有刷新,出现下单数超过库存数,若支付的订单数超过库存数,则会出现支付失败。 (3)预扣库存 下单页面显示最新的库存,下单后保留这个库存一段时间(比如10分钟),超过保留时间后,库存释放。若保留时间过后再支付,如果没有库存,则支付失败。 优点:结合下单减库存的优点,实时减库存,且缓解恶意买家大量下单的问题,保留时间内未支付,则释放库存。 缺点:保留时间内,恶意买家大量下单将库存用完。并发量很高的时候,依然会出现下单数超过库存数。 二、如何解决恶意买家下单的问题 这里的恶意买家指短时间内大量下单,将库存用完的买家。 (1)限制用户下单数量 优点:限制恶意买家下单 缺点:用户想要多买几件,被限制了,会降低销售量 (2)标识恶意买家 优点:卖家设定一个备用库存,当支付时,库存已用完,扣减备用库存数,这就是常见的补货场景 缺点:因高并发场景下,数据可能存在不一致性的问题 三、如何解决下单成功而支付失败(库存不足)的问题 (1)备用库存 商品库存用完后,如果还有用户支付,直接扣减备用库存。 优点:缓解部分用户支付失败的问题 缺点:备用库存只能缓解问题,不能从根本上解决问题。另外备用库存针对普通商品可以,针对特殊商品这种库存少的,备用库存量也不会很大,还是会出现大量用户下单成功却因库存不 足而支付失败的问题。 四、如何解决高并发下库存超卖的场景 库存超卖最简单的解释就是多成交了订单而发不了货。 场景: 用户A和B成功下单,在支付时扣减库存,当前库存数为10。因A和B查询库存时,都还有库存数,所以A和B都可以付款。 A和B同时支付,A和B支付完成后,可以看做两个请求回调后台系统扣减库存,有两个线程处理请求,两个线程查询出来的库存数 inventory=10, 然后A线程更新最终库存数 lastInventory=inventory - 1 = 9, B线程更新库存数 lastInventory=inventory - 1 = 9。 而实际最终的库存应是8才对,这样就出现库存超卖的情况,而发不出货。 那如何解决库存超卖的情况呢? 1.SQL语句直接更新库存,而不是先查询出来,然后赋值 UPDATE[库存表]SET库存数-1 2.SQL语句更新库存时,如果扣减库存后,库存数为负数,直接抛异常,利用事务的原子性进行自动回滚。 3.利用SQL语句更新库存,防止库存为负数 UPDATE[库存表]SET库存数-1WHERE库存数-1>0 如果影响条数大于1,则表示扣减库存成功,否则不更新库存,并退款。 五、秒杀场景下如何扣减库存 (1)下单减库存 因秒杀场景下,大部分用户都是想直接购买商品的,可以直接用下单减库存。 大量用户和恶意用户都是同时进行的,区别是正常用户会直接购买商品,恶意用户虽然在竞争抢购的名额,但是获取到的资格和普通用户一样,所以下单减库存在秒杀场景下,恶意用户下单并不能造成之前说的缺点。 而且下单直接扣减库存,这个方案更简单,在第一步就扣减库存了。 (2)将库存放到redis缓存中 查询缓存要比查询数据库快,所以将库存数放在缓存中,直接在缓存中扣减库存。 (3)使用量自增方式 可以先增加已使用量,然后与设定的库存进行比较,如果超出,则将使用量减回去。 另外其实真实的项目中,用到了更多的机制来保证能够正常扣减库存,本篇是抛砖引入,希望大家提出宝贵的建议和方案~。 本文同步分享在 博客“7年一线互联网经验,超爱图解底层原理,全栈一枚”(CNBlog)。 如有侵权,请联系 support@oschina.cn 删除。 本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册