在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
一,什么是幂等性?1,幂等: 幂等操作:不管执行多少次,所产生的影响都和一次执行的影响相同。 幂等函数或幂等方法:可以使用相同的参数重复执行,并能获得相同的结果的函数/方法。 这些函数/方法不用担心重复执行会对系统造成改变。 2,幂等操作的一些例子: 前端重复提交相同的数据,后台只产生对应这个数据的一个相同的反应结果 发送验证码短信:应该只发一次,相同的验证短信不能多次发送。 生成订单:一个业务请求只能创建一个订单,不能重复创建相同的订单 用户付款:只能扣用户一次钱,不能重复扣费 3,实现幂等操作的一些方法: unique索引 悲观锁 乐观锁 token机制 ...
说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest 对应的源码可以访问这里获取: https://github.com/liuhongdi/ 说明:作者:刘宏缔 邮箱: [email protected]
二,关于演示代码的说明:1,项目的原理: 我们这里的演示的是表单提交时要避免重复提交相同的内容, 前端在用户点击提交按钮后,需要在后端返回结果之前,禁止用户再次点击提交按钮, 假如有请求绕过了前端的控制,直接向后端发送重复的相同请求, 后端如何避免? 在用户打开表单时,后端会生成一个token字符串,保存在redis后,传递给表单, 当表单提交时,这个字符串会再次提交到后端接口, 后端接口需要判断这个字符串是否在redis中存在? 如果不存在不允许提交,如果存在删除时能成功删除,允许提交, 删除时报错:表示已被其他进程删除,也不能允许提交.
2,项目在github的地址: https://github.com/liuhongdi/idempotent
3,代码结构截图:
三,lua代码的说明:checkidem.lua local current = redis.call('GET', KEYS[1]) if current == false then --redis.log(redis.LOG_NOTICE,KEYS[1]..' is nil ') return '-1' end local isdel = redis.call('DEL', KEYS[1]) if isdel == 1 then --redis.log(redis.LOG_NOTICE,' del '..KEYS[1]..' success') return '1'; else --redis.log(redis.LOG_NOTICE,'del '..KEYS[1]..' failed') return '0'; end 如果当前token在redis中不存在,返回 -1 如果token存在,删除成功,返回1 删除失败,返回0 说明:为什么使用lua脚本? redis上的lua脚本的执行是原子性的,不存在多个线程的并发问题, 使用lua脚本能保证不会出现重复的提交
四,java代码的说明:1,RedisLuaUtil @Service public class RedisLuaUtil { @Resource private StringRedisTemplate stringRedisTemplate; /* run a lua script luaFileName: lua file name,no path keyList: list for redis key return 0: delete fail -1: no this key 1: delete success */ public String runLuaScript(String luaFileName,List<String> keyList) { //System.out.println("redis script begin"); DefaultRedisScript<String> redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/"+luaFileName))); redisScript.setResultType(String.class); String argsone = "none"; //System.out.println("execute begin"); String result = stringRedisTemplate.execute(redisScript, keyList,argsone); System.out.println("lua result:"+result); return result; } } 说明: DefaultRedisScript:负责封装lua脚本 luaFileName: lua文件名 keyList: redis中的key列表,我们只需要传递token即可 stringRedisTemplate:负责执行脚本 argsone:值参数,我们传一个空字串即可
2,TokenServiceImpl.java中对redisLuaUtil类的调用 @Override public void checkToken(HttpServletRequest request) { String token = request.getHeader(TOKEN_NAME); if (StringUtils.isBlank(token)) {// header中不存在token token = request.getParameter(TOKEN_NAME); if (StringUtils.isBlank(token)) {// parameter中也不存在token //System.out.println("-----no token"); throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getMsg()); } } //System.out.println("runlua begin"); List<String> keyList = new ArrayList(); keyList.add(token); String res = redisLuaUtil.runLuaScript("checkidem.lua",keyList); if (res.equals("1")) { ServerResponseUtil.success("success"); } else { throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getMsg()); } }
五,测试幂等性的检测是否生效?提交表单前,先得到token 访问: http://127.0.0.1:8080/order/gettoken 返回: {"status":0,"msg":"8IgWPMtotKyzO13pnxCS9pc4","data":null}
用ab测试表单: #-c:指定请求的并发数量 #-n:指定请求的总数量 [root@localhost etc]# ab -c 10 -n 10 http://127.0.0.1:8080/order/addorder?form_token=8IgWPMtotKyzO13pnxCS9pc4 查看代码中system.out.println的打印输出: lua result:1 lua result:-1 lua result:-1 lua result:-1 lua result:-1 lua result:-1 lua result:-1 lua result:-1 lua result:-1 lua result:-1 可以看到只有一个是提交成功,其他的请求均给出了报错
六,查看spring boot的版本:. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.2.0.RELEASE)
|
请发表评论