高并发场景:redis+lua防重校验
大家平时在做有并发量下单的项目时,代码层面基本上就分为这么几个步骤:参数校验--->防重校验--->库存校验扣减--->下单成功--->支付。
最近公司有个商城项目说要30分钟达到1亿的并发量。当时听到突然猛了一下。真是牛逼克拉斯呀。
不过该说不说还是得开搞,所谓的并发无非就是要想办法减少io的操作,尽量少查表,其余配置方面的问题就看公司舍不舍得花钱了。
其中防重校验这一部分选用了redis集合lua来做,来防止多次提交。
注意:真实下单操作是分为了订单前置操作 (前置校验和生成防重令牌)和提交订单两个接口来写的,这里为了测试简单合并了
@RestController @RequestMapping("/lua") public class TestLuaController { @Autowired private StringRedisTemplate redisTemplate; private static final String ORDER_CHECK_TOKEN = "order_check_token"; private static final String ORDER_TOKEN_DEFAULT_VALUE = "value"; /** * 场景:最近公司有高并发场景,涉及到用令牌防重(防止有人多次提交订单) * 可以使用redis执行lua脚本 比较key值令牌是否存在,如果存在即删除 */ @RequestMapping("/checkTempToken") public Long luaExecute(String checkToken){ //模拟提交订单生成的防重令牌 redisTemplate.opsForValue().set(ORDER_CHECK_TOKEN + checkToken,ORDER_TOKEN_DEFAULT_VALUE,60, TimeUnit.SECONDS); String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" + "then\n" + " return redis.call(\"del\",KEYS[1])\n" + "else\n" + " return 0\n" + "end"; //执行redis脚本操作的函数 Long execute = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(ORDER_CHECK_TOKEN + checkToken), ORDER_TOKEN_DEFAULT_VALUE); System.out.println(execute); return execute; } }
结果:
redis+lua语言去重
1)redis
redis是一种键值对的单线程架构模型,所以它是线程安全的,也是分布式缓存常用的解决方案。
2)lua
lua是基于c语言的一种脚本语言,它可以很轻便地被使用在嵌入式方面。我们不会去重写redis,但是我们可以去使用lua来扩展redis的功能。而redis也内置了对lua支持的模块。
3)redis+lua是我司基于分布式告警去重的一种解决方案,要达到的目的:堵住10是s内的重复告警,主要应用的场景是同一个告警,在不通的客户端同时推送了一条告警数据,此时要堵住
一条数据。
直接上代码吧
@Component public class RedisLua { @Autowired private RedisTemplate<String,Object> redisTemplate; private DefaultRedisScript<Boolean> script; private static final String KEY_PREX = "alertInfo_"; @PostConstruct private void init(){ script = new DefaultRedisScript<Boolean>(); script.setResultType(Boolean.class); script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/alert.lua")));//lua脚本文件 }public Boolean blockRepetition(String ruleId,String status,String ip,Object...args){ List<String> keys = new ArrayList<>(); keys.add(KEY_PREX + ip+ruleId+status);//key包括前缀+客户端ip+告警规则id+状态 return redisTemplate.execute(script, keys, args); } }
下面是lua脚本
--[[ ARGV1表示redis生效时间,key表示存储redis的key值]] local alert = redis.call('exists',KEYS[1]); if alert then return false else redis.call('set',KEYS[1],ngx.time()) redis.call('expire',KEYS[1],ARGV[1]) return true end
该方案主要用于微服务集群中,采用分布式锁防和redis集群确保数据唯一。
使用脚本的好处如下:
1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。
2.原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。
3.复用:客户端发送的脚本会永久存储在Redis中,意味着其他客户端可以复用这一脚本而不需要使用代码完成同样的逻辑。