背景:为提升会员对当前等级的权益感知,需对用户仍未领取的权益进行弹框或消息位置推荐,会员需推荐权益有10+项,且项权益均需需校验当日推荐次数并做推送限制,推荐次数记入Redis缓存,会员数据庞大,将占用大量内存空间。
常规做法:key键 = 前缀(RECOMMONE) + 会员号(USERID) + 权益编码(REGISTER),每个会员每项权益维护一个推荐值,大量消耗内存空间;
压缩设计:key键 = 前缀(RECOMMONE) + 会员号(USERID) ,采用同一用户只记录一条权益推荐记录,提前确认权益推荐次数的最大值并分配占位位数,每项权益通过占位标识来获取。例如值为0101040201,前两位01标识生活权益,后两个01标识注册有礼。每两位代表一项权益记录位。如图示:
实现过程:
1)定义权益枚举
@AllArgsConstructor @Getter public enum RecommendRedisPlaceholderEnum { /** * 缓存键 */ private String redisKey; /** * 偏移量,及占用位置起始值 */ private int offset; /** * 占用长度 */ private int size; /** *乘率,位置计算时使用 */ private long rate; /** * 描述 */ private String desc; }
枚举设置:
说明如FIRST_ORDER("RECOMMEND_DAY_CHECK:", 5, 2, 10000,"生日奖励")
如果当前KEY值为0101040201, 偏移量5,表示第5位开始为生日有礼权益占位,占用Size为2位,计算时乘率为10000(设置生日有礼权益推荐值时,incryBy需要增加的值为次数*乘率)
REGISTER_GIFT("RECOMMEND_DAY_CHECK:", 1, 2,1, "注册有礼"),
UPGRADE_GIFT("RECOMMEND_DAY_CHECK:", 3, 2, 100,"升级有礼"),
FIRST_ORDER("RECOMMEND_DAY_CHECK:", 5, 2, 10000,"生日奖励"),
EXCLUSIVE("RECOMMEND_DAY_CHECK:", 7, 2,1000000L, "每月券包"),
BRAND_DISCOUNT("RECOMMEND_DAY_CHECK:", 9, 2,100000000L, "生活特权"),
2)设置缓存值
传入需增长值incNum及权益枚举类。通过incNum * reEnum.getRate() 计算用户当前权益值需增加的数值。调用incrByLong设置权益新值。
/**
* @param key redis的key
* @param incNum 需要自增的数值
* @param time redis的失效时间
*/
public long setRedisValue(String key, long incNum, long time ,RecommendRedisPlaceholderEnum reEnum){
long n = incNum * reEnum.getRate();
if(sfRedisUtil.setNxWithAtomicity(key, String.valueOf(n), time, TimeUnit.SECONDS)){
return incNum;
}
long result = sfRedisUtil.incrByLong(key, n);
if(result == 0){
return 0;
}
return handleValue(String.valueOf(result), reEnum.getOffset(), reEnum.getSize());
}
3)获取权益缓存值
传入权益值及枚举类,通过枚举中的offset及size确定当前权益所占权益值的位置,通过sbustring截取获得。
public int findDayRedisValue(String userId, RecommendRedisPlaceholderEnum reEnum, RecommendEquityFlowContext context){
if(StringUtils.isNotBlank(context.getRecommendDayRedis())){
return handleValue(context.getRecommendDayRedis(), reEnum.getOffset(), reEnum.getSize());
}
String value = sfRedisUtil.get(reEnum.getRedisKey()+userId);
context.setRecommendDayRedis(value);
return handleValue(value,reEnum.getOffset(),reEnum.getSize());
}
4)根据缓存值计算权益次数公共方法
/**
* 处理字符串返回对应数字
* @param value 缓存值
* @param offset 偏移量
* @param size 占用位数
* @return
*/
private int handleValue(String value, int offset, int size){
try {
if(!StringUtils.isNumeric(value) || value.length() < offset || offset < 1 || size < 1){
return 0;
}
int length = value.length();
size = Math.min(length, size);
// 计算权益开始结束位置
int endIndex = length - offset + 1;
int beginIndex = endIndex - size;
beginIndex = Math.max(beginIndex, 0);
String valueString = value.substring(beginIndex, endIndex);
return StringUtils.isNumeric(valueString) ? Integer.parseInt(valueString) : 0;
}catch (Exception e){
log.error("RedisPlaceholderUtils handleValue is error");
}
return 0;
}