秒杀系统 通用缓存Key
为什么要实现通用缓存Key?
先来看看不加前缀的情况,可以试想,项目必然不是同一人开发,那么key的命名必然可能出现相同的情况。
public <T> boolean set(String key,T value){
Jedis jedis=null;
//在JedisPool里面取得Jedis
try {
jedis=jedisPool.getResource();
//将T类型转换为String类型
String s=beanToString(value);
if(s==null) {
return false;
}
jedis.set(key, s);
return true;
}finally {
returnToPool(jedis);
}
}
当项目的规模业务不断拓展,那么需要存的缓存也在不断增加,比如本项目中的商品Id,订单Id,用户Id等,此时若是Id出现重复,将会出现数据丢失,数据覆盖等现象,严重影响数据可靠性。
思考如何实现?
在实现之前,像我一样的小白同学先要了解一下设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
整体思想:
- 那么我们可以给不同的key前缀,这里我们使用KeyPrefix来管理缓存中对应的key
实现策略:
- 将前缀+一个key一起作为一个redis里面真正的key,这样不同模块之间就不会重复。
好处:
- 不同功能或者不同模块的前缀不同,即使有同名key出现,那么前缀不同,并不会引起key冲突被其他功能覆盖的情况。
那么具体该如何实现,这里我们使用设计模式——模板模式
作为资深小白,之前只是简单的知道一些,没用过,这里复习一下
通俗解释:完成一件事情,有固定的数个步骤,但是每个步骤根据对象的不同,而实现细节不同;就可以在父类中定义一个完成该事情的总方法,按照完成事件需要的步骤去调用其每个步骤的实现方法。每个步骤的具体实现,由子类完成。
关键的两个类:
-
抽象父类(AbstractClass):实现了模板方法,定义了算法的骨架。
-
具体类(ConcreteClass):实现抽象类中的抽象方法,即不同的对象的具体实现细节。
下面是经典的结构图
再了解下它的优点就差不多可以知道为什么选模板模式了
-
具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构。(子类才是最终实现,具体实现)
-
实现代码复用。(key的实现步骤一致)
-
通过一个父类调用其子类的操作,通过子类对父类进行扩展增加新的行为。(步骤一致的前提下,根据不同模块修改部分代码)
具体实现思路和实现代码
接口
/**
*做缓存的前缀接口
*/
public interface KeyPrefix {
//有效期
public int expireSeconds();
//前缀
public String getPrefix();
}
BasePrefix 抽象类:
简单实现缓存key步骤,即简单实现KeyPrefix,因为步骤基本一致,我们定义为抽象类是因为不希望它被创建,具体差异我们可以再实现类中实现,这就实现了代码复用。
//定义成抽象类
public abstract class BasePrefix implements KeyPrefix{
private int expireSeconds;
private String prefix;
public BasePrefix() {
}
public BasePrefix(String prefix) {
//this(0, prefix);//默认使用0,不会过期
this.expireSeconds=0;
this.prefix=prefix;
}
public BasePrefix(int expireSeconds,String prefix) {//覆盖了默认的构造函数
this.expireSeconds=expireSeconds;
this.prefix=prefix;
}
//默认为0代表永不过期
public int expireSeconds() {
return expireSeconds;
}
//前缀为类名:+prefix
public String getPrefix() {
String className=getClass().getSimpleName();
return className+":"+prefix;
}
}
注意:该类2种不同构造方法:用于继承。一个只带前缀名,一个带前缀名和过期时间。当实现public BasePrefix(String prefix)的时候,我们将默认这个key不会失效,因为有一些场景,我们不希望key失效,但是有些场景我们需要设置key的合适的有效期。
具体实现类:
用户UserKey只去继承了super(prefix),即public BasePrefix(String prefix),那么代表user的key的过期时间为不会过期。
public class UserKey extends BasePrefix{
public UserKey(String prefix) {
super(prefix);
}
public static UserKey getById=new UserKey("id");
public static UserKey getByName=new UserKey("name");
}
秒杀用户的MiaoshaUserKey ,继承了super(expireSeconds,prefix),可以设置有效期时间为2天。
public class MiaoshaUserKey extends BasePrefix{
public static final int TOKEN_EXPIRE=3600*24*2;//3600S*24*2 =2天
public MiaoshaUserKey(int expireSeconds,String prefix) {
super(expireSeconds,prefix);
}
public static MiaoshaUserKey token=new MiaoshaUserKey(TOKEN_EXPIRE,"tk");
//对象缓存一般没有有效期,永久有效
public static MiaoshaUserKey getById=new MiaoshaUserKey(0,"id");
}
具体实现类的具体使用场景:
/**
*避免key被不同类的数据覆盖
*使用Prefix前缀-->不同类别的缓存,用户、部门、
*/
@RequestMapping("/redis/set")
@ResponseBody
public Result<Boolean> redisSet() {//0代表成功
User user=new User(1,"1111");
boolean f=redisService.set(UserKey.getById,""+1,user);
return Result.success(true);
}
@RequestMapping("/redis/getbyid")
@ResponseBody
public Result<User> redisGetById() {//0代表成功
User res=redisService.get(UserKey.getById,""+1,User.class);
//redisService.get("key1",String.class);
//System.out.println("res:"+userService.tx());
return Result.success(res);
}
完善后的get和set缓存的Key值的方法:
public <T> T get(KeyPrefix prefix,String key,Class<T> data){
Jedis jedis=null;
//在JedisPool里面取得Jedis
try {
jedis=jedisPool.getResource();
//生成真正的key className+":"+prefix; BasePrefix:id1
String realKey=prefix.getPrefix()+key;
//System.out.println("jedis:"+jedis);
String sval=jedis.get(realKey);
//将String转换为Bean入后传出
T t=stringToBean(sval,data);
return t;
}finally {
returnToPool(jedis);
}
}
/**
* 设置单个、多个对象
*/
//MiaoshaUserKey.token, token, user
public <T> boolean set(KeyPrefix prefix,String key,T value){
Jedis jedis=null;
try {//在JedisPool里面取得Jedis
jedis=jedisPool.getResource();
String realKey=prefix.getPrefix()+key;
String s=beanToString(value);
if(s==null||s.length()<=0) {
return false;
}
int seconds=prefix.expireSeconds();
if(seconds<=0) {//有效期:代表不过期,这样才去设置
jedis.set(realKey, s);
}else {//没有设置过期时间,即没有设置有效期,那么自己设置。
jedis.setex(realKey, seconds,s);
}
return true;
}finally {
returnToPool(jedis);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?