Les principaux avantages de Redis prenant en charge les scripts LUA
L'intégration des scripts LUA créera davantage de scénarios d'utilisation pour la base de données Redis et fera ressortir davantage de nouveaux avantages :
local key = KEYS[1] --限流KEY local limitCount = tonumber(ARGV[1]) --限流大小 local limitTime = tonumber(ARGV[2]) --限流时间 local current = redis.call('get', key); if current then if current + 1 > limitCount then --如果超出限流大小 return 0 else redis.call("INCRBY", key,"1") return current + 1 end else redis.call("set", key,"1") redis.call("expire", key,limitTime) return 1 end
package com.shinedata.ann; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimiter { /** * 限流唯一标识 * @return */ String key() default "rate.limit:"; /** * 限流时间 * @return */ int time() default 1; /** * 限流次数 * @return */ int count() default 100; /** *是否限制IP,默认 否 * @return */ boolean restrictionsIp() default false; }
package com.shinedata.aop; import com.shinedata.ann.RateLimiter; import com.shinedata.config.redis.RedisUtils; import com.shinedata.exception.RateLimiterException; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; /** * @ClassName RateLimiterAspect * @Author yupanpan * @Date 2020/5/6 13:46 */ @Aspect @Component public class RateLimiterAspect { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private static ThreadLocal<String> ipThreadLocal=new ThreadLocal(); private DefaultRedisScript<Number> redisScript; @PostConstruct public void init(){ redisScript = new DefaultRedisScript<Number>(); redisScript.setResultType(Number.class); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/req_ratelimit.lua"))); } @Around("@annotation(com.shinedata.ann.RateLimiter)") public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable { try { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); Class<?> targetClass = method.getDeclaringClass(); RateLimiter rateLimit = method.getAnnotation(RateLimiter.class); if (rateLimit != null) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); boolean restrictionsIp = rateLimit.restrictionsIp(); if(restrictionsIp){ ipThreadLocal.set(getIpAddr(request)); } StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append(rateLimit.key()); if(StringUtils.isNotBlank(ipThreadLocal.get())){ stringBuffer.append(ipThreadLocal.get()).append("-"); } stringBuffer.append("-").append(targetClass.getName()).append("- ").append(method.getName()); List<String> keys = Collections.singletonList(stringBuffer.toString()); Number number = RedisUtils.execute(redisScript, keys, rateLimit.count(), rateLimit.time()); if (number != null && number.intValue() != 0 && number.intValue() <= rateLimit.count()) { logger.info("限流时间段内访问第:{} 次", number.toString()); return joinPoint.proceed(); }else { logger.error("已经到设置限流次数,当前次数:{}",number.toString()); throw new RateLimiterException("服务器繁忙,请稍后再试"); } } else { return joinPoint.proceed(); } }finally { ipThreadLocal.remove(); } } public static String getIpAddr(HttpServletRequest request) { String ipAddress = null; try { ipAddress = request.getHeader("x-forwarded-for"); if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); } // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()= 15 if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } } catch (Exception e) { ipAddress = ""; } return ipAddress; } }
Spring data redis fournit DefaultRedisScript pour interagir avec Lua et Redis. Il existe de nombreux articles sur Internet pour des détails spécifiques. . ThreadLocal est utilisé ici car l'IP est variable, garantissant que l'IP de son propre thread ne sera pas modifiée par d'autres threads, n'oubliez pas de nettoyer ThreadLocal en dernier pour éviter les fuites de mémoire
Classe d'outil RedisUtils ( trop de méthodes, seule la méthode d'exécution est affichée) , juste une annotation
Supplément : Lua optimisé pour
package com.shinedata.config.redis; import org.checkerframework.checker.units.qual.K; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import javax.annotation.PostConstruct; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * @ClassName RedisUtils * @Author yupanpan * @Date 2019/11/20 13:38 */ @Component public class RedisUtils { @Autowired @Qualifier("redisTemplate") private RedisTemplate<String, Object> redisTemplate; private static RedisUtils redisUtils; @PostConstruct public void init() { redisUtils = this; redisUtils.redisTemplate = this.redisTemplate; } public static Number execute(DefaultRedisScript<Number> script, List keys, Object... args) { return redisUtils.redisTemplate.execute(script, keys,args); } }
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!