秒杀系统 通用缓存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);			
	}
}
posted @   长勺  阅读(76)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示