下咽顿除烟火气,入齿便作冰雪声。 文天祥 西瓜吟
1.1 缓存目的 : 提升系统访问速度和增大系统能处理的容量。提升性能,缓解数据库压力
1.2 缓存的方式: redis , memcached
1.3 缓存带来的问题
【1】、缓存穿透:缓存穿透是说收到一个请求,但是该请求缓存中不存在,只能去数据库中查询,然后放进缓存。 【2】、缓存击穿:上面提到的某个数据没有,然后好多请求查询数据库,可以归为缓存击穿的范畴:对于热点数据,当缓存失效的一瞬间,所有的请求都被下放到数据库去请求更新缓存,数据库被压垮。 【3】、缓存雪崩:缓存雪崩是指当我们给所有的缓存设置了同样的过期时间,当某一时刻,整个缓存的数据全部过期了,然后瞬间所有的请求都被抛向了数据库,数据库就崩掉了。 【4】、缓存刷新:既清空缓存 ,一般在insert、update、delete操作后就需要刷新缓存,如果不执行就会出现脏数据。但当缓存请求的系统蹦掉后,返回给缓存的值为null。
修改配置中心的配置,代码里面根据相关配置开关去执行不同的代码逻辑块。
熔断策略
熔断本质上是一个过载保护机制。
举例:
微服务A -》微服务B -》微服务C,如果微服务A需要发布新版本,会对B,C产生影响。
房子的排污管道从上至下,如果你家楼下的管道堵住了,污水会灌到你们家。这时候你先避免自己
家收到影响,然后通知楼下去疏通。
3.1 api限流(黑白名单等皆可)
3.1.1 计数器(固定窗口) : 若干定长时间内,ip地址/用户/流量等访问次数.
String remoteAddr = request.getRemoteAddr();
Integer count = map.get(remoteAddr);
//统计ip访问次数,或者总共的访问次数
if(null == count){
map.put(remoteAddr,1);
}else{
map.put(remoteAddr,count+1);
}
//可以记录该ip的访问时间,限制其 1s 内只能访问一次等
long timeMillis = System.currentTimeMillis();
if(count > 5){
return ; //不继续处理请求
}
System.out.println(remoteAddr +" : "+ map.get(remoteAddr));
问题: 流量请求不是一个 **固定周期内的恒定值**,可能会所有请求在固定周期内最后一秒,或者第一秒涌入,这样固定周期内其他时间空闲了下来导致资源无法高效利用,或者固定周期内一些请求无法处理。滑动窗口可以解决这个问题。
滑动窗口
https://media.pearsoncmg.com/aw/ecs_kurose_compnetwork_7/cw/content/interactiveanimations/selective-repeat-protocol/index.html
3.1.2 漏桶算法
通过一个缓冲区将不平滑的流量“整形”成平滑的,也就是说将高于平均值的流量补充到低于平均值的流量,以此最大化计算处理资源的利用率。
应用场景: 如果你的系统需要调用第三方发短信,支付等接口。对方接口gps为2000,但是你的系统同一时间接收到5000个请求,这样可以设置漏桶,系统恒定输出2000,流量入口可以暂存。
3.1.3 令牌桶 。 不会误削峰
以恒定的速度 (Rate:令牌生成速率 / 每秒)往桶里面放入令牌,如果这时候有请求需要处理,则需要先从桶里面获取一个令牌,如果这时候桶里面的令牌被消耗完毕,则返回其他结果(停止服务等)。
应用场景:应对秒杀的场景,秒杀1000个商品,在1s之内,永远只接受10个请求去处理。
Semaphore : 限制现成个数(控制并发数量)
控制速率(处理突发流量): Burst 令牌桶大小 /
Ratelimiter实现令牌桶:
实现上并不是有个定时器在那里放令牌,然后消耗令牌,而是将空闲时间利用令牌的思想进行建模,实质上是计算并保留下次可执行请求的时间,并根据这个时间与请求到来的时间作比较,然后决定是否执行
配置tomcat中的自定义线程池,配置最大连接数,请求处理队列等参数来限流。
conf/server.xml文件中,在Connector之前配置一个Executor (线程池)
配置Connector
3.2.3 分布式限流(流量入口)
限制接口: 获取令牌的方式,采用redis。RedissonClient getRateLimiter()。
//结合redis,实现分布式的qpi接口限流
public static void test() throws InterruptedException, ExecutionException {
Config config = new Config();
// config.useClusterServers()
// .setScanInterval(2000)
// .addNodeAddress("127.0.0.1:7000", "127.0.0.1:7001")
// .addNodeAddress("127.0.0.1:7002");
config.useSingleServer().setAddress("redis://192.168.8.154:6379").setConnectionMinimumIdleSize(10);
RedissonClient redisson = Redisson.create(config);
/**
* 基于Redis的分布式限流器可以用来在分布式环境下现在请求方的调用频率。
* 既适用于不同Redisson实例下的多线程限流,也适用于相同Redisson实例下的多线程限流。
* 该算法不保证公平性。
*/
RRateLimiter myLimiter = redisson.getRateLimiter("my");
/**
* Total rate for all RateLimiter instances
* 作用在所有的RRateLimiter实例
* OVERALL
*
* Total rate for all RateLimiter instances working with the same Redisson instance
* 作用在同一个Redisson实例创建的 RRateLimiter上面。
* PER_CLIENT
*
* return : 设置是否成功。 对同一个redis服务端,只需要设置一次。如果redis重启需要重新设置
*/
boolean bl = myLimiter.trySetRate(RateType.OVERALL,5,5, RateIntervalUnit.SECONDS);
System.out.println(bl);
for(int i = 0 ; i < 200 ; i++){
System.out.println(myLimiter.tryAcquire());
Thread.sleep(100);
}
}
location /buy {
access_by_lua_block {
local limit_req = require "resty.limit.req"
-- 这里设置rate=2/s,漏桶桶容量设置为0,(也就是来多少水就留多少水)
-- 因为resty.limit.req代码中控制粒度为毫秒级别,所以可以做到毫秒级别的平滑处理
local lim, err = limit_req.new("my_limit_req_store", 2, 60)
if not lim then
ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
return ngx.exit(500)
end
local key = ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)
if not delay then
if err == "rejected" then
return ngx.exit(503)
end
ngx.log(ngx.ERR, "failed to limit req: ", err)
return ngx.exit(500)
end
-- 此方法返回,当前请求需要delay秒后才会被处理,和他前面对请求数
-- 所以此处对桶中请求进行延时处理,让其排队等待,就是应用了漏桶算法
-- 此处也是与令牌桶的主要区别既
if delay >= 0.001 then
ngx.sleep(delay)
end
}
proxy_pass http://192.168.10.111:6666;
proxy_set_header Host $host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 60;
proxy_read_timeout 600;
proxy_send_timeout 600;
}
分布式api限流实现代码: 链接: https://pan.baidu.com/s/1-1vY7KgIooQ8cOVS67UOWQ 提取码: wrqh