想到之前面试遇到的一个关于Redis解决方案的问题,趁今天有时间来进行一番实践,问题是这样的:
Redis的五种数据结构中,使用什么样的数据结构来实现发红包、抢红包以及红包记录?
首先回顾一下redis的五种数据结构以及底层实现原理:
- String:最基本的数据类型,底层主要是int和SDS实现
- Hash:String元素组成的字典,底层使用ziplist和hashtable,适合用于存储对象
- List:列表,底层使用链表(大数据量)/压缩列表(小数据量)实现,按照String元素插入顺序排序
- Set:String元素组成的无序集合,底层使用intset和hashtable实现,不允许重复,支持 多个集合取交集/并集/差集
- ZSet:通过分数值(score)来为集合中的成员进行从小到大的排序,底层使用ziplist或skiplist实现
面试时回答的是使用Hash结构来实现红包功能,既可以存储每个红包的值也可以存储每个用户抢到红包的多少。
可能也是由于面试时时间紧张,思考问题没有那么全面,下来一细想这样也会存在一些弊端,比如红包数值明显会多存储一份在Redis,增加Redis内存负担。现在我的思路是使用List结构存储红包金额,然后每个用户抢红包的时候从List中取一个红包,并把红包记录存入Hash结构中,红包抢完后再次从List中获取数据时会返回null表示红包已经抢完,List过期时也方便退回余额。
代码如下:
1、根据红包大小和个数定义红包金额
public static double[] defineRedPacket(int total, double amount) {
Random random = new Random();
DecimalFormat df = new DecimalFormat(".##");
double middle = Double.parseDouble(df.format(amount / total));
double[] result = new double[total];
double redMoney;
double nextMoney = amount;
int index = 0;
for (int i = total; i > 0; i--) {
if (i == 1) {
result[index] = nextMoney;
} else {
while (true) {
String str = df.format(random.nextDouble() * nextMoney);
redMoney = Double.parseDouble(str);
if (redMoney > 0 && redMoney < middle) {
break;
}
}
nextMoney = Double.parseDouble(df.format(nextMoney - redMoney));
result[index] = redMoney;
middle = Double.parseDouble(df.format(nextMoney / (i - 1)));
index++;
}
}
return result;
}
2、保存红包 使用redis的list数据类型
public static void saveRedPacket(String packetId, double[] redPacketArr) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
for (double amount : redPacketArr) {
jedis.lpush(packetId, String.valueOf(amount));
}
jedis.close();
}
3、用户抢红包 使用redis的hash保存红包记录(可持久化到数据库)
public static String getRedPacket(String packetId, String userId) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
String amount = jedis.lpop(packetId);
//保存红包记录
jedis.hset("user_" + packetId, userId, amount);
jedis.close();
return amount;
}
4、测试类
public static void main(String[] args) {
String redPacketId = UUID.randomUUID().toString();
double[] packets = RedPacketRedis.defineRedPacket(100, 10000d);
RedPacketRedis.saveRedPacket(redPacketId, packets);
CountDownLatch latch = new CountDownLatch(100);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
latch.countDown();
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
String userId = Thread.currentThread().getName();
String amount = RedPacketRedis.getRedPacket(redPacketId, userId);
System.out.println("userId:" + userId + ",金额:" + amount);
}
};
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(runnable);
thread.start();
}
}