初
入职快一周,正在熟悉整个开发环境,想尽快的跟上前辈们。
部门是做游戏活动的,项目框架主要是spring,持久层框架没有用mybtis,用的是spring 的jdbc;
上周五的时候想要独自实现一个简单的手机验证码发送和CDK发放的活动(游戏名叫星际战甲),因为入职后就一直在看前辈们的代码,已经从头到尾看过了两个另外的活动。
总共三天做完,期间踩的坑主要是在数据库和开发工具。Oracle Idea。
今天周一,上午把活动跑起来了,几个异常处理也没问题。但是跟老大汇报完工作,发现疏漏了一个很严重的问题,并发情况下数据库的读写问题。
问题1.CDK发送问题(老大提醒的):高并发情况下,上一个请求已经将某个CDK获取,但是还没有修改成已发送状态,马上又被另一个请求线程获取到,结果就是两个账号获得了同一个CDK。
问题2.恶意请求攻击下,用户获得多个CDK:同一个用户发起了获取CDK的请求,请求线程来不及修改为此用户已获取CDK状态,又一个请求过来(还是这用户的),结果就是同一个账号获得了多个CDK.
问题3.同一用户发起大量的获取验证码请求:因为短信发送需要成功,所以这个api调用需要次数限制。
问题4.db各个表记录的状态'不一致':因为db操作是不能肯定是成功的,结果表现为:获取验证码请求执行期间,获取到了CDK,CDK也有效,也修改成已发送状态,但是想要修改用户为已获取CDK状态失败,那么这CDK就浪费掉了。
再
问题1 2 总的来说,db锁解决
问题3 可以用ip访问次数限制 或者账号发送验证码次数限制解决
问题4 把获取CDK, 修改用户、 CDK状态信息,集成到一个事务中进行,根据事务的特性(要么全部成功,要么都不成功回滚)解决掉
终
1 .db锁
数据库锁按粒度大小分为 表级锁 行级锁 (常见DBMS都支持) 还有页级锁(Mysql支持)
按照策略 乐观锁 悲观锁
从读写角度,分共享锁(S锁,Shared Lock)和排他锁(X锁,Exclusive Lock),也叫读锁(Read Lock)和写锁(Write Lock)。
理解 :当一个事务(DB Connection)对某一数据进行读取,加上S锁,接着另一个事务也想要读取这个数据,这是可以的。但是修改就不行。(大家都可以读,在有人读的时候是不可以修改的)
当一个事务对某一数据进行读/写操作加上X锁,接着另一个事务想要读 /写 都不可以 (一个数据(一个厕所)排队使,在对同一数据(数量不限)上实现事务序列化执行)
使用方法: for update (作用是用于对选择的行加排他锁的) ,with(rowlock,xlock,tablock,paglock)(mysql 可使用)
2.事务
public void getGiftRequestProcess(Map<String,String> result ,String phone,String identifyCode){
try{ liveRequireDao.getJdbcTemplate().getDataSource().getConnection().setAutoCommit(false);
//1.根据活动登记表是否存在该手机号
boolean isEixtLoginByPhone=isExitPhone(phone); //Rlock Xlock 对这个phone这条记录加行级排它锁 //防止同一个用户同时多次的获取礼包请求
if (isEixtLoginByPhone){
//2.判断是否已经获得过礼包
boolean isAuth=isAuth(phone);
if (!isAuth){
//3.匹配验证码
boolean isIdentifyCodeRight=isIdentifyCodeRight(phone,identifyCode);
if (isIdentifyCodeRight){
//4.从db中获取一个新的cdk
CdkBean cdkOfNew=getCdkForNew(); //Rlock Xlock对这个cdk记录加行级排它锁 防止被其他请求对应的线程给获取到
//5.判断是否获取到有效的Cdk
boolean isCdkLeagal=isCdkLegal(cdkOfNew);
if (isCdkLeagal){
//6.修改这个cdk的信息 从可获得 -> 已领取
int dbOpreOfUpdateCdk=updateCDKToSended(cdkOfNew.getId());
if (dbOpreOfUpdateCdk>0){
//7.修改用户登录信息置为已获取cdk
int dbOpreOfUpdateAuth=updateLoginToGetCdk(phone,cdkOfNew.getCdkValue());
if (dbOpreOfUpdateAuth>0){
//成功获取cdk 响应前端