建议结合下一篇一起看

下一篇

数据结构+基础设施

数据结构

这里通过spring-data-jpa+mysql实现DB部分的处理,其中有lombok的参与

@MappedSuperclass
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BaseEntity {//公共基础实体字段
@Id //标识主键 公用主键
@GeneratedValue //递增序列
private Long id;
@Column(updatable = false) //不允许修改
@CreationTimestamp //创建时自动赋值
private Date createTime;
@UpdateTimestamp //修改时自动修改
private Date updateTime;
}
@Entity //标识这是个jpa数据库实体类
@Table
@Data //lombok getter setter tostring
@ToString(callSuper = true) //覆盖tostring 包含父类的字段
@Slf4j //SLF4J log
@Builder //biulder模式
@NoArgsConstructor //无参构造函数
@AllArgsConstructor //全参构造函数
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class RedPacketInfo extends BaseEntity implements Serializable {//红包信息表
private String red_packet_id;//红包ID
private int total_amount;//总金额
private int total_packet;//总红包数
private int remaining_amount;//剩余金额
private int remaining_packet;//剩余红包数
private String user_id;//发红包用户ID
}
@Entity //标识这是个jpa数据库实体类
@Table
@Data //lombok getter setter tostring
@ToString(callSuper = true) //覆盖tostring 包含父类的字段
@Slf4j //SLF4J log
@Builder //biulder模式
@NoArgsConstructor //无参构造函数
@AllArgsConstructor //全参构造函数
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class RedPacketRecord extends BaseEntity implements Serializable {//抢红包记录表
private int amount;
private String red_packet_id;
private String user_id;
}

REDIS数据结构

REDIS对于一个红包存储3部分信息:

1、KEY:红包ID+_TAL_PACKET VALUE:红包剩余数量

2、KEY:红包ID+_TOTAL_AMOUNT VALUE:红包剩余金额

3、KEY:红包ID+_lock VALUE:红包分布式锁

操作REDIS基础方法

  private static final TimeUnit SECONDS = TimeUnit.SECONDS;
private static final long DEFAULT_TOMEOUT = 5;
private static final int SLEEPTIME = 50; /**
* 获取分布式锁 2019
* @param lockKey
* @param timeout
* @param unit
*/
public boolean getLock(String lockKey, String value, long timeout, TimeUnit unit){
boolean lock = false;
while (!lock) {
//设置key自己的超时时间
lock = redisTemplate.opsForValue().setIfAbsent(lockKey, value,timeout,unit);
if (lock) { // 已经获取了这个锁 直接返回已经获得锁的标识
return lock;
}
try {
//暂停50ms,重新循环
Thread.sleep(SLEEPTIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return lock;
} /**
* 按照默认方式获得分布式锁 2019
* @param lockKey
* @return
*/
public boolean getLock(String lockKey){
return getLock(lockKey,String.valueOf(new Date().getTime()),DEFAULT_TOMEOUT,SECONDS);
}
/**
* 获取指定 key 的值
*
* @param key
* @return
*/
public String get(String key) {
return redisTemplate.opsForValue().get(key);
} /**
* 设置指定 key 的值
*
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}

DAO

public interface RedPacketInfoRepository extends JpaRepository<RedPacketInfo, Long> {
@Query("select o from RedPacketInfo o where o.red_packet_id=:redPacketId")
public RedPacketInfo findByRedPacketId(@Param("redPacketId") String redPacketId);
}
public interface RedPacketRecordRepository extends JpaRepository<RedPacketRecord,Long> {
}

配置

@Component
@EnableAsync//开启异步注解,回写处
public class RedPacketConfig implements ApplicationRunner {
  //启动自动发一个红包
@Autowired
RedPacketService redPacketService;
@Override
public void run(ApplicationArguments args) throws Exception {
String userId = "001";
redPacketService.handOut(userId,10000,20);
} /**
* 引入随机数组件
* @return
*/
@Bean
public RandomValuePropertySource randomValuePropertySource(){
return new RandomValuePropertySource("RedPackeRandom");
}
}

发红包

发红包通常没有特别需要处理高并发的点

 /**
* 发红包
* @param userId
* @param total_amount 单位为分,不允许有小数点
* @param tal_packet
* @return
*/
public RedPacketInfo handOut(String userId,int total_amount,int tal_packet){
RedPacketInfo redPacketInfo = new RedPacketInfo();
redPacketInfo.setRed_packet_id(genRedPacketId(userId));
redPacketInfo.setTotal_amount(total_amount);
redPacketInfo.setTotal_packet(tal_packet);
redPacketInfo.setRemaining_amount(total_amount);
redPacketInfo.setRemaining_packet(tal_packet);
redPacketInfo.setUser_id(userId);
redPacketInfoRepository.save(redPacketInfo); redisUtil.set(redPacketInfo.getRed_packet_id()+TAL_PACKET, tal_packet+"");
redisUtil.set(redPacketInfo.getRed_packet_id()+TOTAL_AMOUNT, total_amount+""); return redPacketInfo;
}
/**
* 组织红包ID
* @return
*/
private String genRedPacketId(String userId){
String redpacketId = userId+"_"+new Date().getTime()+"_"+redisUtil.incrBy("redpacketid",1);
return redpacketId;
}

抢红包

详见代码注释

/**
* 抢红包
* @param userId
* @param redPacketId
* @return
*/
public GrabResult grab(String userId, String redPacketId){
Date begin = new Date();
String msg = "红包已经被抢完!";
boolean resultFlag = false;
double amountdb = 0.00; try{
//抢红包的过程必须保证原子性,此处加分布式锁
if(redisUtil.getLock(redPacketId+"_lock")) {
RedPacketRecord redPacketRecord = new RedPacketRecord().builder().red_packet_id(redPacketId)
.user_id(userId).build();
//如果没有红包了,则返回
if (Integer.parseInt(redisUtil.get(redPacketId + TAL_PACKET)) <= 0) {
}else {
//抢红包过程
//获取剩余金额 单位分
int remaining_amount = Integer.parseInt(redisUtil.get(redPacketId + TOTAL_AMOUNT));
//获取剩余红包数
int remaining_packet = Integer.parseInt(redisUtil.get(redPacketId + TAL_PACKET));
//计算本次抢红包金额
//计算公式:remaining_amount/remaining_packet*2
//如果只剩下一个红包,则余额全由这次的人获得
int amount = remaining_amount;
if (remaining_packet != 1) {
int maxAmount = remaining_amount / remaining_packet * 2;
amount = Integer.parseInt(randomValuePropertySource.getProperty("random.int[0," + maxAmount + "]").toString());
}
//与redis进行incrBy应该原子,并且2次与redis交互还有一定性能消耗,通过lua脚本实现更为妥当
redisUtil.incrBy(redPacketId + TAL_PACKET, -1);
redisUtil.incrByFloat(redPacketId + TOTAL_AMOUNT, -amount);
//准备返回结果
redPacketRecord.setAmount(amount);
amountdb = amount / 100.00;
msg = "恭喜你抢到红包,红包金额" + amountdb + "元!";
resultFlag = true;
//异步记账
try {
redPacketCallBackService.callback(userId, redPacketId,
Integer.parseInt(redisUtil.get(redPacketId + TAL_PACKET)),
Integer.parseInt(redisUtil.get(redPacketId + TOTAL_AMOUNT)),
amount);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
}finally {
//解锁redis分布式锁
redisUtil.unLock(redPacketId+"_lock");
}
Date end = new Date();
System.out.println(msg+",剩余红包:"+redisUtil.get(redPacketId + TAL_PACKET)+"个,本次抢红包消耗:"+(end.getTime()-begin.getTime())+"毫秒");
return new GrabResult().builder().msg(msg).resultFlag(resultFlag).amount(amountdb).red_packet_id(redPacketId).user_id(userId).build(); }

异步入账

/**
* @program: redis
* @description: 回写信息
* @author: X-Pacific zhang
* @create: 2019-04-30 11:36
**/
@Service
public class RedPacketCallBackService {
@Autowired
private RedPacketInfoRepository redPacketInfoRepository; @Autowired
private RedPacketRecordRepository redPacketRecordRepository;
/**
* 回写红包信息表、抢红包表
*/
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void callback(String userId,String redPacketId,int remaining_packet,int remaining_amount,int amount) throws Exception {
//校验
RedPacketInfo redPacketInfo = redPacketInfoRepository.findByRedPacketId(redPacketId);
if(redPacketInfo.getRemaining_packet() <= 0 || redPacketInfo.getRemaining_amount() < amount){
throw new Exception("红包余额错误,本次抢红包失败!");
}
//先更新红包信息表
redPacketInfo.setRemaining_packet(remaining_packet);
redPacketInfo.setRemaining_amount(remaining_amount);
redPacketInfoRepository.save(redPacketInfo);
//新增抢红包信息
RedPacketRecord redPacketRecord = new RedPacketRecord().builder()
.user_id(userId).red_packet_id(redPacketId).amount(amount).build();
redPacketRecordRepository.save(redPacketRecord);
}
}

测试抢红包

  @Test
public void testConcurrent(){
String redPacketId = "001_1556677154968_19";
// System.out.println(redPacketInfoRepository.findByRedPacketId("001_1556619425512_5"));
Date begin = new Date();
for(int i = 0;i < 200;i++) {
Thread thread = new Thread(() -> {
String userId = "user_" + randomValuePropertySource.getProperty("random.int(10000)").toString();
redPacketService.grab(userId, redPacketId);
});
thread.start();
}
Date end = new Date();
System.out.println("合计消耗:"+(end.getTime() - begin.getTime()));
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

通过redis实现的一个抢红包流程,仅做模拟【上】的更多相关文章

  1. 优化通过redis实现的一个抢红包流程【下】

    上一篇文章通过redis实现的抢红包通过测试发现有严重的阻塞的问题,抢到红包的用户很快就能得到反馈,不能抢到红包的用户很久(10秒以上)都无法获得抢红包结果,起主要原因是: 1.用了分布式锁,导致所有 ...

  2. Activity 学习(二) 搭建第一个Activity流程框架

    本次示例使用的IDER测试完成 测试背景 : xx饿了去饭店吃饭  需要先和服务员点餐  点完餐后服务员将菜品传递给厨师制作  制作完成后吃饱 一 :创建流程图 创建上一篇测试成功出现的BpmnFil ...

  3. 曹工说Redis源码(6)-- redis server 主循环大体流程解析

    文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...

  4. ***Redis hash是一个string类型的field和value的映射表.它的添加、删除操作都是O(1)(平均)。hash特别适合用于存储对象

    http://redis.readthedocs.org/en/latest/hash/hset.html HSET HSET key field value   (存一个对象的时候key存) 将哈希 ...

  5. 给定一个字符串,仅由a,b,c 3种小写字母组成。

    package com.boco.study; /** * 题目详情 给定一个字符串,仅由a,b,c 3种小写字母组成. 当出现连续两个不同的字母时,你可以用另外一个字母替换它,如 有ab或ba连续出 ...

  6. Redis深入学习笔记(一)Redis启动数据加载流程

    这两年使用Redis从单节点到主备,从主备到一主多从,再到现在使用集群,碰到很多坑,所以决定深入学习下Redis工作原理并予以记录. 本系列主要记录了Redis工作原理的一些要点,当然配置搭建和使用这 ...

  7. 使用Redis List简单实现抢红包

    在这里不讨论抢红包的算法,只用redis简单尝试解决抢红包.借助redis单线程和List的POP方法. static void Main(string[] args) { IRedisHelper ...

  8. redis学习笔记——命令执行流程

    基础知识部分 如果需要掌握Redis的整个命令的执行过程,那么必须掌握一些基本的概念!否则根本看不懂,下面我就一些在我看来必备的基础知识进行总结,希望能为后面命令的整个执行过程做铺垫. 事件 Redi ...

  9. Redis hash 是一个 string 类型的 field 和 value 的映射表.它的添加、删除操作都是 O(1)(平均)。

    2.3 hashes 类型及操作 Redis hash 是一个 string 类型的 field 和 value 的映射表.它的添加.删除操作都是 O(1)(平均).hash 特别适合用于存储对象.相 ...

随机推荐

  1. Win10通过SSH与树莓派Raspbain系统互传文件

    1.在Linux系统上安装ssh-server(由于Raspbain系统自带ssh-server,这个步骤可以省略) 查看ssh是否运行的命令: ps -ef | grep ssh 如果没有安装,则安 ...

  2. git使用笔记-提高篇

    一.分支.合并 1.合并一个特定提交 a specific commit git cherry-pick commit-id 把commit-id代表的本次提交合并到当前分支,如果有冲突需要解决后,提 ...

  3. axios处理http请求

    axios中文文档 https://github.com/mzabriskie/axios#using-applicationx-www-form-urlencoded-format  axios文档 ...

  4. LeetCode 441.排列硬币(C++)

    你总共有 n 枚硬币,你需要将它们摆成一个阶梯形状,第 k 行就必须正好有 k 枚硬币. 给定一个数字 n,找出可形成完整阶梯行的总行数. n 是一个非负整数,并且在32位有符号整型的范围内. 示例 ...

  5. .NET标准化题目

    1. 下面对FxCop的描述中,错误的是:(D) A. FxCop是一个静态代码分析工具. B. 可以定制自己的规则加入FxCop引擎. C. FxCop主要是对.NET中托管代码的assembly进 ...

  6. Mac 安装GTK

    Mac上配置GTK环境 安装command line工具, 如果安装了Xcode, 就直接跳过该步骤 安装Homebrew 使用brew install pkg-config 使用brew insta ...

  7. maven课程 项目管理利器-maven 1-2maven介绍和环境搭建

    maven简介: Maven是基于项目对象模型(POM),通过一小段描述信息来管理项目的构建.报告和文档的软件项目管理工具. 1.1 Maven安装文件夹的结构 bin 目录放置包含mvn的运行脚本 ...

  8. 5.jQuery&Ajax

    1.jQuery 什么是 jQuery ? jQuery是一个JavaScript函数库.jQuery是一个轻量级的"写的少,做的多"的JavaScript库.包含以下功能: HT ...

  9. webpack-dev-server.js 服务器配置说明

    connect-history-api-fallback 使用: var app = express() var histroy = require('connect-history-api-fall ...

  10. python 修改xml文件

    在百度知道里面有人问了这么一个问题: 有一个xml文件:<root>text <a/> <a/> ...这里省略n个<a/> <root>想 ...