分布式接口幂等性、分布式限流总结整理

1、Update操作的幂等性
1)根据唯一业务号去更新数据
update set version = version +1 ,xxx=${xxx} where id =xxx and version = ${version};
2、使用Token机制,保证update、insert操作的幂等性
1)没有唯一业务号的update与insert操作
1、分布式限流的几种维度
时间 限流基于某段时间范围或者某个时间点,也就是我们常说的“时间窗口”,比如对每分钟、每秒钟的时间窗口做限定 资源 基于可用资源的限制,比如设定最大访问次数,或最高可用连接数
1)QPS和连接数控制
2)传输速率
3)黑白名单
4)分布式环境
网关层限流
中间件限流
2、限流方案常用算法讲解
1)令牌桶算法
令牌 获取到令牌的Request才会被处理,其他Requests要么排队要么被直接丢弃 桶用来装令牌的地方,所有Request都从这个桶里面获取令牌
2)漏桶算法
3、分布式限流的主流方案
1)Guava RateLimiter客户端限流
com.google.guava guava 18.0
@RestController@Slf4jpublic class Controller{//每秒钟可以创建两个令牌RateLimiter limiter = RateLimiter.create(2.0);//非阻塞限流@GetMapping("/tryAcquire")public String tryAcquire(Integer count){//count 每次消耗的令牌if(limiter.tryAcquire(count)){log.info("成功,允许通过,速率为{}",limiter.getRate());return "success";}else{log.info("错误,不允许通过,速率为{}",limiter.getRate());return "fail";}}//限定时间的非阻塞限流@GetMapping("/tryAcquireWithTimeout")public String tryAcquireWithTimeout(Integer count, Integer timeout){//count 每次消耗的令牌 timeout 超时等待的时间if(limiter.tryAcquire(count,timeout,TimeUnit.SECONDS)){log.info("成功,允许通过,速率为{}",limiter.getRate());return "success";}else{log.info("错误,不允许通过,速率为{}",limiter.getRate());return "fail";}}//同步阻塞限流@GetMapping("/acquire")public String acquire(Integer count){limiter.acquire(count);log.info("成功,允许通过,速率为{}",limiter.getRate());return "success";}}
2)基于Nginx的限流
1.iP限流
@RestController@Slf4jpublic class Controller{//nginx测试使用@GetMapping("/nginx")public String nginx(){log.info("Nginx success");}}
127.0.0.1 www.test.com
vim /usr/local/nginx/conf/nginx.conf
#根据IP地址限制速度#1)$binary_remote_addr binary_目的是缩写内存占用,remote_addr表示通过IP地址来限流#2)zone=iplimit:20m iplimit是一块内存区域(记录访问频率信息),20m是指这块内存区域的大小#3)rate=1r/s 每秒放行1个请求limit_req_zone $binary_remote_addr zone=iplimit:20m rate=1r/s;server{server_name www.test.com;location /access-limit/ {proxy_pass http://127.0.0.1:8080/;#基于ip地址的限制#1)zone=iplimit 引用limit_rep_zone中的zone变量#2)burst=2 设置一个大小为2的缓冲区域,当大量请求到来,请求数量超过限流频率时,将其放入缓冲区域#3)nodelay 缓冲区满了以后,直接返回503异常limit_req zone=iplimit burst=2 nodelay;}}
www.test.com/access-limit/nginx
2.多维度限流
#根据IP地址限制速度limit_req_zone $binary_remote_addr zone=iplimit:20m rate=10r/s;#根据服务器级别做限流limit_req_zone $server_name zone=serverlimit:10m rate=1r/s;#根据ip地址的链接数量做限流limit_conn_zone $binary_remote_addr zone=perip:20m;#根据服务器的连接数做限流limit_conn_zone $server_name zone=perserver:20m;server{server_name www.test.com;location /access-limit/ {proxy_pass http://127.0.0.1:8080/;#基于ip地址的限制limit_req zone=iplimit burst=2 nodelay;#基于服务器级别做限流limit_req zone=serverlimit burst=2 nodelay;#基于ip地址的链接数量做限流 最多保持100个链接limit_conn zone=perip 100;#基于服务器的连接数做限流 最多保持100个链接limit_conn zone=perserver 1;#配置request的异常返回504(默认为503)limit_req_status 504;limit_conn_status 504;}location /download/ {#前100m不限制速度limit_rate_affer 100m;#限制速度为256klimit_rate 256k;}}
3)基于Redis+Lua的分布式限流
1.Lua脚本
2.Lua安装
参考http://www.lua.org/ftp/教程,下载5.3.5_1版本,本地安装 如果你使用的是Mac,那建议用brew工具直接执行brew install lua就可以顺利安装, 有关brew工具的安装可以参考https://brew.sh/网站,建议翻墙否则会很慢。 使用brew安装后的目录在/usr/local/Cellar/lua/5.3.5_1 安装IDEA插件,在IDEA->Preferences面板,Plugins, 里面Browse repositories,在里面搜索lua,然后就选择同名插件lua。安装好后重启IDEA 配置Lua SDK的位置:IDEA->File->Project Structure, 选择添加Lua,路径指向Lua SDK的bin文件 都配置好之后,在项目中右键创建Module,左侧栏选择lua,点下一步,选择lua的sdk,下一步,输入lua项目名,完成
3.编写hello lua
print 'Hello Lua'
4.编写模拟限流
-- 模拟限流-- 用作限流的keylocal key = 'my key'-- 限流的最大阈值local limit = 2-- 当前限流大小local currentLimit = 2-- 是否超过限流标准if currentLimit + 1 > limit thenprint 'reject'return falseelseprint 'accept'return trueend
5.限流组件封装
org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-starter-aop com.google.guava guava 18.0
server.port=8080spring.redis.database=0spring.redis.host=localhostspring.redis.port=6376
-- 获取方法签名特征local methodKey = KEYS[1]redis.log(redis.LOG_DEBUG,'key is',methodKey)-- 调用脚本传入的限流大小local limit = tonumber(ARGV[1])-- 获取当前流量大小local count = tonumber(redis.call('get',methodKey) or "0")--是否超出限流值if count + 1 >limit then-- 拒绝访问return falseelse-- 没有超过阈值-- 设置当前访问数量+1redis.call('INCRBY',methodKey,1)-- 设置过期时间redis.call('EXPIRE',methodKey,1)-- 放行return trueend
@Service@Slf4jpublic class AccessLimiter{@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate RedisScriptrateLimitLua; public void limitAccess(String key,Integer limit){boolean acquired = stringRedisTemplate.execute(rateLimitLua,//lua脚本的真身Lists.newArrayList(key),//lua脚本中的key列表limit.toString()//lua脚本的value列表);if(!acquired){log.error("Your access is blocked,key={}",key);throw new RuntimeException("Your access is blocked");}}}
@Configurationpublic class RedisConfiguration{public RedisTemplateredisTemplate(RedisConnectionFactory factory){ return new StringRedisTemplate(factory);}public DefaultRedisScript loadRedisScript(){DefaultRedisScript redisScript = new DefaultRedisScript();redisScript.setLocation(new ClassPathResource("rateLimiter.lua"));redisScript.setResultType(java.lang.Boolean.class);return redisScript;}}
@RestController@Slf4jpublic class Controller{@Autowiredprivate AccessLimiter accessLimiter;@GetMapping("test")public String test(){accessLimiter.limitAccess("ratelimiter-test",1);return "success";}}
6.编写限流注解
@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface AccessLimiterAop{int limit();String methodKey() default "";}
@Slf4j@Aspect@Componentpublic class AccessLimiterAspect{@Autowiredprivate AccessLimiter accessLimiter;//根据注解的位置,自己修改@Pointcut("@annotation(com.gyx.demo.annotation.AccessLimiter)")public void cut(){log.info("cut");}@Before("cut()")public void before(JoinPoint joinPoint){//获取方法签名,作为methodkeyMethodSignature signature =(MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();AccessLimiterAop annotation = method.getAnnotation(AccessLimiterAop.class);if(annotation == null){return;}String key = annotation.methodKey();Integer limit = annotation.limit();//如果没有设置methodKey,就自动添加一个if(StringUtils.isEmpty(key)){Class[] type = method.getParameterType();key = method.getName();if (type != null){String paramTypes=Arrays.stream(type).map(Class::getName).collect(Collectors.joining(","));key += "#"+paramTypes;}}//调用redisreturn accessLimiter.limitAccess(key,limit);}}
@RestController@Slf4jpublic class Controller{@Autowiredprivate AccessLimiter accessLimiter;@GetMapping("test")@AccessLImiterAop(limit =1)public String test(){return "success";}}
作者:敲代码的旺财
来源:blog.csdn.net/qq_34886352/article/details/104694550

评论
