1、处理高并发
1.1高并发处理方案:
缓存
缓存的目的是提升系统访问速度和增大系统处理容量降级
降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开限流
限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理
1、2限流方式: mq、ratelimiter
2、ratelimiter是基于令牌桶算法来做的
guava的RateLimiter使用的是令牌桶算法,也就是以固定的频率向桶中放入令牌,例如一秒钟10枚令牌,实际业务在每次响应请求之前都从桶中获取令牌,只有取到令牌的请求才会被成功响应,获取的方式有两种:阻塞等待令牌或者取不到立即返回失败
另外简单介绍漏铜算法:
请求以一定速度进入漏桶中,如果请求速度>处理请求速度则溢出,漏桶算法能强行处理请求速率。但如果在处理高并发时,突然来大量请求这种方案不合适
令牌桶代码:create():每秒创建多少令牌
tryacquire():尝试获取令牌,tryacquire():尝试获取一个令牌,如果获取不到立即返回;tryacquire(int permits, long timeout, TimeUnit unit):尝试获取permits个令牌,如果获取不到等待timeout时间
2.1 令牌桶简单代码实现
Main:
package com.citi.ark.mls.ratelimiter; public class TestTokenLimiter { final static Object lock=new Object(); public static void main(String[] args) throws InterruptedException { int period=500; int limit=2; int amount=2; TokenLimiter tokenLimiter=new TokenLimiter(limit,period,amount); //先产生两个令牌 tokenLimiter.startToAddToken(lock); synchronized (lock){ lock.wait(); } //开始消费令牌 for(int i=0;i<4;i++){ //开启4个线程去获取令牌 new Thread(()->{ while(true){ String name=Thread.currentThread().getName(); if(tokenLimiter.getToken()){ System.out.println(name+":拿到令牌"); }else{ System.out.println(name+":没拿到令牌"); } try{ Thread.sleep(period); }catch(Exception e){ e.printStackTrace(); } } }).start(); } } }
TokenLimiter:
package com.citi.ark.mls.ratelimiter; import jdk.nashorn.internal.ir.Block; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; //https://blog.csdn.net/billgates_wanbin/article/details/123556273 // https://blog.csdn.net/weixin_43939924/article/details/124668935 /* blockQueue add: 队列添加数据,当队列已经满了时会抛出异常 blockQueue offer: 队列添加数据,当队列满了时不会抛出异常,只会返回boolean值来过诉你添加成功与否 blockQueue poll: 队列移除数据,当队列没有数据不会抛出异常,指挥返回boolean值来告诉你添加成功与否 blockQueue remove:队列移除数据,当队列没有数据会抛出异常 */ public class TokenLimiter { public static final String token="lp"; private ArrayBlockingQueue<String> blockingQueue; //令牌桶 private int limit; //令牌桶容量 private int period; //令牌生产间隔(ms) private int amount; //每次生产令牌的个数 public TokenLimiter(int limit,int period,int amount){ this.limit=limit; this.period=period; this.amount=amount; blockingQueue=new ArrayBlockingQueue<>(limit); init();//初始化令牌 } //初始化令牌 private void init(){ for(int i=0;i<limit;i++){ blockingQueue.add(token); } } //生产令牌 public void startToAddToken(Object lock){ Executors.newScheduledThreadPool(1).scheduleAtFixedRate(()->{ synchronized (lock){ //锁对象是为了保证线程安全 addToken(); lock.notifyAll(); } },500,this.period, TimeUnit.MILLISECONDS); //initialDelay:初始化延时;period:两次开始执行时间间隔;unit;计时单位 } public void addToken(){ for(int i=0;i<=amount;i++){ blockingQueue.offer(token); } } //获取令牌 public boolean getToken(){ return blockingQueue.poll()!=null; } }
2.2 guava的RateLimiter:
package com.leolztang.sb.aop.ratelimiter; import org.springframework.stereotype.Service; import com.google.common.util.concurrent.RateLimiter; @Service public class AccessLimitService { RateLimiter rateLimiter=RateLimiter.create(100.0); //尝试获取令牌 public boolean tryAcquire() { return rateLimiter.tryAcquire(); } }
测试类:
package com.leolztang.sb.aop.ratelimiter; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/limit") public class AccessLimiterController { private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(120); @Autowired private AccessLimitService accessLimitService; @RequestMapping(value="/ratelimit",method = RequestMethod.GET) @ResponseBody public void tryForVisit() { for(int i=0;i<200;i++) { fixedThreadPool.execute(new Runnable() { @Override public void run() { String str=visitAccess(); System.out.println("output:"+str); } }); } } public String visitAccess() { if(accessLimitService.tryAcquire()) { try { Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } return "aceess success [" + sdf.format(new Date()) + "]"; }else { return "aceess limit [" + sdf.format(new Date()) + "]"; } } }
pom文件引入guava依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>