Sentinel源码—5.FlowSlot借鉴Guava的限流算法一
大纲
1.Guava提供的RateLimiter限流使用示例
2.Guava提供的RateLimiter简介与设计
3.继承RateLimiter的SmoothBursty源码
4.继承RateLimiter的SmoothWarmingUp源码
1.Guava提供的RateLimiter限流使用示例
(1)拦截器示例
(2)AOP切面示例
(1)拦截器示例
一.pom文件中引入Guava的依赖包
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>27.0.1-jre</version>
</dependency>
二.自定义拦截器并在拦截器中实现限流
首先定义一个拦截器抽象类,用于多个拦截器复用。主要是继承HandlerInterceptorAdapter,重写preHandle()方法,并且提供preFilter()抽象方法供子类实现。
public abstract class AbstractInterceptor extends HandlerInterceptorAdapter {private Logger logger = LoggerFactory.getLogger(AbstractInterceptor.class);@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {ResponseEnum result;try {result = preFilter(request);} catch (Exception e) {logger.error("preHandle catch a exception:" + e.getMessage());result = ResponseEnum.FAIL;}if (ResponseEnum.SUCCESS.code.equals(result.code)) {return true;}handlerResponse(result, response);return false;}//自定义pre处理protected abstract ResponseEnum preFilter(HttpServletRequest request);//错误处理事件private void handlerResponse(ResponseEnum result, HttpServletResponse response) {ResponseDto responseDto = new ResponseDto();responseDto.setCode(result.code);responseDto.setStatus(result.status);responseDto.setMessage(result.message);response.setStatus(HttpServletResponse.SC_OK);response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);PrintWriter printWriter = null;try {printWriter = response.getWriter();printWriter.write(JsonUtils.toJson(responseDto));} catch (Exception e) {logger.error("handlerResponse catch a exception:" + e.getMessage());} finally {if (printWriter != null) {printWriter.close();}}}
}
然后定义流量控制拦截器,流量控制拦截器继承自上面的拦截器抽象类,并在preFilter()方法中进行流量控制。
使用Guava提供的RateLimiter类来实现流量控制,过程很简单:定义一个QPS为1的全局限流器,使用tryAcquire()方法来尝试获取令牌。如果成功则返回允许通过,否则返回限流提示。
@Component("rateLimitInterceptor")
public class RateLimitInterceptor extends AbstractInterceptor {private Logger logger = LoggerFactory.getLogger(RateLimitInterceptor.class);//定义一个QPS为1的全局限流器private static final RateLimiter rateLimiter = RateLimiter.create(1);public static void setRate(double limiterQPS){rateLimiter.setRate(limiterQPS);}@Overrideprotected ResponseEnum preFilter(HttpServletRequest request) {if (!rateLimiter.tryAcquire()) {logger.warn("限流中......");return ResponseEnum.RATE_LIMIT;}return ResponseEnum.SUCCESS;}
}
三.继承WebMvcConfigurerAdapter来添加自定义拦截器
@Configuration
public class MyWebAppConfigurer extends WebMvcConfigurationSupport {@Overridepublic void addInterceptors(InterceptorRegistry registry) {//多个拦截器组成一个拦截器链//addPathPatterns()方法用于添加拦截规则//excludePathPatterns()方法用户排除拦截registry.addInterceptor(new RateLimitInterceptor()).addPathPatterns("/**");super.addInterceptors(registry);}
}
四.写一个Controller来提供一个简单的访问接口
@RestController
public class GuavaController {@RequestMapping(value = "getUserList", method = RequestMethod.GET)public String getUserList() {String result = null;try {result = "请求成功";} catch (Exception e) {logger.error("请求失败", e);return JsonUtils.toJson(ResponseUtils.failInServer(result));}return JsonUtils.toJson(ResponseUtils.success(result));}
}
(2)AOP切面示例
一.编写自定义注解Limiter
当需要对接口限流时,可直接使用@Limiter注解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Limiter {//默认每秒放入桶中的tokendouble limitNum()default 5;Stringname()default "";
}
二.编写切面处理逻辑
@Aspect
@Component
public class RateLimitAspect {private ConcurrentHashMap RATE_LIMITER = new ConcurrentHashMap<>();private RateLimiter rateLimiter;@Pointcut("@annotation(添加Limiter注解所在类路径)")public void serviceLimit() {}@Around("serviceLimit()")public Objectaround(ProceedingJoinPoint point)throws Throwable {//获取拦截的方法名Signature sig = point.getSignature();//获取拦截的方法名MethodSignature msig = (MethodSignature) sig;//返回被织入增加处理目标对象Object target = point.getTarget();//为了获取注解信息Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());//获取注解信息Limiter annotation = currentMethod.getAnnotation(Limiter.class);//获取注解每秒加入桶中的tokendouble limitNum = annotation.limitNum();//注解所在方法名区分不同的限流策略String methodName = msig.getName();if (RATE_LIMITER.containsKey(methodName)) {rateLimiter = RATE_LIMITER.get(methodName);} else {RATE_LIMITER.put(methodName, RateLimiter.create(limitNum));rateLimiter = RATE_LIMITER.get(methodName);}if (rateLimiter.tryAcquire()) {log.info("流量正常范围内");return point.proceed();} else {log.info("您被限流了");}}
}
三.业务代码中添加限流控制
注意:@Limiter注解中的limitNum参数表示每秒接口最大调用次数,而name表示限流名称,整个工程中需要保证全局唯一。
@RequestMapping("/test/path")
@RestController
public class LimiterController {@PostMapping("/do")@Limiter(limitNum = 30, name = "test_name")public Result vote(@RequestBody @Validated TestRequest request) {//编写业务逻辑代码return nulll;}
}
2.Guava提供的RateLimiter简介与设计
(1)RateLimiter的简介
(2)RateLimiter通过延迟计算的方式实现限流
(3)SmoothRateLimiter的注释说明
(4)SmoothWarmingUp的注释说明
(1)RateLimiter的介绍
RateLimiter是一个抽象类,从它的注释可知:
一.限流器会以固定配置的速率来分配令牌。每次调用acquire()方法,如果令牌不够则会阻塞,直到有足够的令牌。
二.RateLimiter是线程安全的。它会限制所有线程的总速率,但是它并不能保证公平。
三.acquire(1)和acquire(100)所产生的影响是一样的。它们都不会影响第一次调用这个方法的请求,影响的是后面的请求。
RateLimiter类本身是一个抽象类,子类SmoothRateLimiter又做了一层抽象。SmoothRateLimiter有两个子类SmoothBursty和SmoothWarmingUp,可以说SmoothWarmingUp是SmoothBursty的升级版,SmoothWarmingUp是为了弥补SmoothBursty的不足而实现的。
所以RateLimiter有两个具体的继承类:SmoothWarmingUp和SmoothBursty。SmoothWarmingUp和SmoothBursty都是SmoothRateLimiter的内部类。分别对应两种限流方式:一是有预热时间,一是没有预热时间。
//Conceptually:在概念上;distributes:分发、分配;permits:许可、令牌;configurable:可配置的
//restrict:限制;in contrast to:与...相反、相比之下
//Absent additional configuration:缺少额外配置;individual:单独的;maintained:维持的
//steadily:稳定地;smoothly:平稳地、平滑地;accomplished:完成;specifying:明确规定
//throttling、throttle:阻碍、抑制、使节流;
//i.e.:也就是//Conceptually, a rate limiter distributes permits at a configurable rate.
//Each acquire() blocks if necessary until a permit is available, and then takes it.
//Once acquired, permits need not be released.//从概念上讲,速率限制器RateLimiter会以可配置的速率分配令牌;
//如果需要,每个acquired()方法都会阻塞,直到一个令牌可用,然后再获取到一个令牌;
//一旦获得令牌,就不需要发放了;//RateLimiter is safe for concurrent use:
//It will restrict the total rate of calls from all threads.
//Note, however, that it does not guarantee fairness.//并发使用RateLimiter时是安全的;
//它将限制来自所有线程的调用的总速率;
//然而,它并不能保证公平;//Rate limiters are often used to restrict the rate at which some physical or logical resource is accessed.
//This is in contrast to JDK's Semaphore which restricts the number of concurrent accesses instead of the rate.//速率限制器RateLimiter通常用于限制访问某些物理或逻辑资源的速率;
//这与JDK的Semaphore形成对比,后者限制并发访问的数量而不是速率;//A RateLimiter is defined primarily by the rate at which permits are issued.
//Absent additional configuration, permits will be distributed at a fixed rate, defined in terms of permits per second.
//Permits will be distributed smoothly,
//with the delay between individual permits being adjusted to ensure that the configured rate is maintained.//一个RateLimiter主要由发放令牌的速率来定义;
//如果没有额外的配置,令牌将以固定的速率分发,以每秒多少个令牌的形式定义;
//通过调整各个令牌之间的延迟来确保维持所配置的速率,来实现令牌被平滑地发放;//It is possible to configure a RateLimiter to have a warmup period during which time
//the permits issued each second steadily increases until it hits the stable rate.
//可以将RateLimiter配置为具有预热期,在此期间每秒发放的令牌会稳步增加,直到达到稳定的速率;//As an example, imagine that we have a list of tasks to execute,
//but we don't want to submit more than 2 per second:
//举个例子,假设我们有一个要执行的任务列表,但我们不希望每秒超过2个提交:
// final RateLimiter rateLimiter = RateLimiter.create(2.0); // rate is "2 permits per second"
// void submitTasks(List<Runnable> tasks, Executor executor) {
// for (Runnable task : tasks) {
// rateLimiter.acquire(); // may wait
// executor.execute(task);
// }
// }//As another example, imagine that we produce a stream of data, and we want to cap it at 5kb per second.
//This could be accomplished by requiring a permit per byte, and specifying a rate of 5000 permits per second:
//作为另一个例子,假设我们产生一个数据流,我们希望将其限制在每秒5kb;
//这可以通过要求每个字节有一个令牌,并指定每秒5000个令牌的速率来实现:
// final RateLimiter rateLimiter = RateLimiter.create(5000.0); // rate = 5000 permits per second
// void submitPacket(byte[] packet) {
// rateLimiter.acquire(packet.length);
// networkService.send(packet);
// }//It is important to note that the number of permits requested never affects the throttling of the request itself
//(an invocation to acquire(1) and an invocation to acquire(1000) will result in exactly the same throttling, if any),
//but it affects the throttling of the next request.
//I.e., if an expensive task arrives at an idle RateLimiter, it will be granted immediately,
//but it is the next request that will experience extra throttling,
//thus paying for the cost of the expensive task.//需要注意的是,请求的令牌数量永远不会影响请求本身的限流;
//调用acquire(1)和调用acquire(1000)将导致完全相同的限流(如果有的话);
//但是它会影响下一个请求的限流;
//也就是说,如果一个昂贵的任务到达空闲的RateLimiter,它将会被立即允许;
//但是下一个请求将经历额外的限流,从而支付了昂贵的限流成本;@Beta
@GwtIncompatible
@SuppressWarnings("GoodTime") // lots of violations - also how should we model a rate?
public abstract class RateLimiter {......
}
(2)RateLimiter通过延迟计算的方式实现限流
令牌桶算法就是以固定速率生成令牌放入桶中。每个请求都需要从桶中获取令牌,没有获取到令牌的请求会被阻塞限流。当令牌消耗速度小于生成速度时,令牌桶内就会预存这些未消耗的令牌。当有突发流量进来时,可以直接从桶中取出令牌,而不会被限流。
漏桶算法就是将请求放入桶中,然后以固定的速率从桶中取出请求来处理。当桶中等待的请求数超过桶的容量后,后续的请求就不再加入桶中。
漏桶算法适用于需要以固定速率处理请求的场景。在多数业务场景中,其实并不需要按照严格的速率进行请求处理。而且多数业务场景都需要应对突发流量的能力,所以会使用令牌桶算法。
但不管是令牌桶算法还是漏桶算法,都可以通过延迟计算的方式来实现。延迟计算指的是不需要单独的线程来定时生成令牌或者从漏桶中定时获取请求,而是由调用限流器的线程自己计算是否有足够的令牌以及需要sleep的时间。延迟计算的方式可以节省一个线程资源。
Guava提供的RateLimiter就是通过延迟计算的方式来实现限流效果的。
(3)SmoothRateLimiter的注释说明
//How is the RateLimiter designed, and why?//The primary feature of a RateLimiter is its "stable rate",
//the maximum rate that is should allow at normal conditions.
//This is enforced by "throttling" incoming requests as needed,
//i.e. compute, for an incoming request, the appropriate throttle time,
//and make the calling thread wait as much.//RateLimiter的主要特点是其"稳定的速率"——即在正常条件下被允许的最大速率;
//这是通过根据需要,强制限制"到达的请求"来实现的;
//也就是说,对于一个到达的请求,计算一个合适的限流时间,然后让调用线程等待同样多的时间;//The simplest way to maintain a rate of QPS is to keep the timestamp of the last granted request,
//and ensure that (1/QPS) seconds have elapsed since then.
//For example, for a rate of QPS=5 (5 tokens per second),
//if we ensure that a request isn't granted earlier than 200ms after the last one, then we achieve the intended rate.
//If a request comes and the last request was granted only 100ms ago, then we wait for another 100ms.
//At this rate, serving 15 fresh permits (i.e. for an acquire(15) request) naturally takes 3 seconds.//保持QPS速率的最简单方法是保留最后一个允许请求的时间戳,并确保从那时起过了(1/QPS)秒之后再放行另外一个请求;
//例如:对于QPS=5的速率(每秒五个令牌,200ms一个)
//如果我们能保证一个请求被允许通过的时间点比上次放行的请求的时间点之差不小于200ms,那么我们就算保证了这个QPS=5的速率;
//如果一个请求到达时,上次放行的请求才过了100ms,那么当前这个请求就得再等待100ms;
//按照这个速率,如果调用acquire(15),就是想得到15个新鲜的令牌,那么就需要花费3秒的时间;//It is important to realize that such a RateLimiter has a very superficial memory of the past: it only remembers the last request.
//What if the RateLimiter was unused for a long period of time, then a request arrived and was immediately granted?
//This RateLimiter would immediately forget about that past underutilization.
//This may result in either underutilization or overflow,
//depending on the real world consequences of not using the expected rate.//重要的是需要认识到,这样一个RateLimiter对于过去的请求是不怎么记忆的,唯一记忆的是上次的请求;
//如果RateLimiter在很长一段时间内未使用,那么一个请求到达并立即被批准,该怎么办?
//RateLimiter会立刻忘记它之前是处于一个未被充分利用的情况("过去未充分利用");
//这可能导致利用不足或溢出,具体取决于未使用预期速率的实际后果;//Past underutilization could mean that excess resources are available.
//Then, the RateLimiter should speed up for a while, to take advantage of these resources.
//This is important when the rate is applied to networking (limiting bandwidth),
//where past underutilization typically translates to "almost empty buffers", which can be filled immediately.//"过去未充分利用"可能意味着资源过剩;RateLimiter应该要加快速度来充分利用资源;
//当这个速率代表着宽带限制的时候,"过去未充分利用"这种状况通常意味着"几乎是空的缓存区",它可以被瞬间填充满;//On the other hand, past underutilization could mean that
//"the server responsible for handling the request has become less ready for future requests",
//i.e. its caches become stale, and requests become more likely to trigger expensive operations
//(a more extreme case of this example is when a server has just booted,
//and it is mostly busy with getting itself up to speed).//另一方面,"过去未充分利用"也可能意味着:负责处理请求的服务器对未来的请求准备不足;
//例如:它的缓存失效了,服务端需要花费更多的时间来处理请求;
//一个更极端的情况就是是服务端刚刚启动,它忙于让自己跟上速度;//To deal with such scenarios, we add an extra dimension,
//that of "past underutilization", modeled by "storedPermits" variable.
//This variable is zero when there is no underutilization,
//and it can grow up to maxStoredPermits, for sufficiently large underutilization.
//So, the requested permits, by an invocation acquire(permits), are served from:
// - stored permits (if available)
// - fresh permits (for any remaining permits)//为了处理这种情况,我们增加了一个额外的维度:"过去未充分利用"使用storedPermits变量来表示;
//当不存在未充分利用时,该变量为零;并且它可以增长到maxStoredPermits,以获得足够大的未充分利用;
//因此acquire(permits)得到的令牌由两部分组成:存储的令牌、新鲜的令牌//How this works is best explained with an example:
//For a RateLimiter that produces 1 token per second,
//every second that goes by with the RateLimiter being unused, we increase storedPermits by 1.
//Say we leave the RateLimiter unused for 10 seconds
//(i.e., we expected a request at time X, but we are at time X + 10 seconds before a request actually arrives;
//this is also related to the point made in the last paragraph),
//thus storedPermits becomes 10.0 (assuming maxStoredPermits >= 10.0).
//At that point, a request of acquire(3) arrives.
//We serve this request out of storedPermits,
//and reduce that to 7.0 (how this is translated to throttling time is discussed later).
//Immediately after, assume that an acquire(10) request arriving.
//We serve the request partly from storedPermits, using all the remaining 7.0 permits,
//and the remaining 3.0, we serve them by fresh permits produced by the rate limiter.//这是如何工作的呢,最好用一个例子来解释:
//对于每秒生成1个令牌的RateLimiter,
//在RateLimiter未使用的情况下,每过一秒,我们就会将storedPermits增加1;
//假设我们让RateLimiter闲置10秒钟(即,我们期望在时间点X收到请求,但是在时间点X+10秒时才收到请求;这也与上一段中提出的观点有关)
//那么storedPermits就会变成10.0(假设maxStoredPermits >= 10.0)
//在这种情况下,有一个acquire(3)的请求到达;
//我们会从storedPermits中取出令牌来服务这个请求,并将storedPermits减少到7.0;
//刚处理完这个请求,马上有个acquire(10)的请求到来,
//我们会继续从storedPermits中取出剩下的7个,其它3个从freshPermits中取出;//We already know how much time it takes to serve 3 fresh permits:
//if the rate is "1 token per second", then this will take 3 seconds.
//But what does it mean to serve 7 stored permits?
//As explained above, there is no unique answer.
//If we are primarily interested to deal with underutilization,
//then we want stored permits to be given out faster than fresh ones,
//because underutilization = free resources for the taking.
//If we are primarily interested to deal with overflow,
//then stored permits could be given out slower than fresh ones.
//Thus, we require a (different in each case) function that translates storedPermits to throttling time.//我们已经知道提供3个新鲜的令牌需要多长时间:如果速率是"每秒1个令牌",那么这将需要3秒;
//但是,提供7个存储的令牌意味着什么?如上所述,这没有唯一的答案;
//如果我们主要感兴趣的是处理未充分利用的问题,那么可以让存储的令牌比新鲜的令牌发放得更快,因为未充分利用 = 存在可供占用的空闲资源;
//如果我们主要感兴趣的是处理溢出的问题,那么可以让存储的令牌比新鲜的令牌发放得更慢;
//因此,我们需要一个(在每种情况下都不同)函数将storedPermits转换为限流时间;//This role is played by storedPermitsToWaitTime(double storedPermits, double permitsToTake).
//The underlying model is a continuous function mapping storedPermits (from 0.0 to maxStoredPermits)
//onto the 1/rate (i.e. intervals) that is effective at the given storedPermits.
//"storedPermits" essentially measure unused time;
//we spend unused time buying/storing permits.
//Rate is "permits / time", thus "1 / rate = time / permits".
//Thus, "1/rate" (time / permits) times "permits" gives time,
//i.e., integrals on this function (which is what storedPermitsToWaitTime() computes)
//correspond to minimum intervals between subsequent requests,
//for the specified number of requested permits.//这个角色由函数storedPermitsToWaitTime(double storedPermits,double permitsToTake)来扮演;
//该函数的底层模型是一个映射"storedPermits"到"1/rate"的连续函数;
//其中"storedPermits"主要衡量RateLimiter未被使用的时间,我们会在这段未使用的时间内存储令牌;
//而"rate"(速率)就是"申请的令牌/时间",即"rate" = "permits/time";
//因此,"1/rate" = "time/permits",表示每个令牌需要的时间;
//因此,"1/rate"(time/permits)乘以"permits"等于给定的时间time;
//也就是说,此连续函数上的积分(就是storedPermitsToWaitTime()计算的值),
//对应于随后的(申请指定令牌permits的)请求之间的最小时间间隔;//Here is an example of storedPermitsToWaitTime:
//If storedPermits == 10.0, and we want 3 permits,
//we take them from storedPermits, reducing them to 7.0,
//and compute the throttling for these as a call to storedPermitsToWaitTime(storedPermits = 10.0, permitsToTake = 3.0),
//which will evaluate the integral of the function from 7.0 to 10.0.//下面是一个关于storedPermitsToWaitTime()函数的例子:
//如果storedPermits==10.0,并且我们想要3个令牌;
//那么我们会从存储的令牌中获取,将其降低到7.0;
//而且会调用函数storedPermitsToWaitTime(storedPermits = 10.0, permitsToTake = 3.0)计算出限流信息,
//这可以评估出连续函数从7.0到10.0的积分;
//注意:积分的结果是请求间的最小间隔;//Using integrals guarantees that the effect of a single acquire(3) is equivalent to
//{ acquire(1); acquire(1); acquire(1); }, or { acquire(2); acquire(1); }, etc,
//since the integral of the function in [7.0, 10.0] is equivalent to
//the sum of the integrals of [7.0, 8.0], [8.0, 9.0], [9.0, 10.0] (and so on),
//no matter what the function is.
//This guarantees that we handle correctly requests of varying weight (permits),
//no matter what the actual function is - so we can tweak the latter freely.
//(The only requirement, obviously, is that we can compute its integrals).//使用积分可以保证acquire(3)得到的结果和{ acquire(1); acquire(1); acquire(1); }或者{ acquire(2); acquire(1); }的结果是一样的,
//由于不管连续函数是什么,[7.0,10.0]中的积分等于[7.0,8.0],[8.0,9.0],[9.0,10.0]的积分之和(依此类推);
//这保证了我们能正确处理不同令牌申请的请求;
//显然,我们唯一需要做的就是计算该连续函数的积分;//Note well that if, for this function, we chose a horizontal line, at height of exactly (1/QPS),
//then the effect of the function is non-existent:
//we serve storedPermits at exactly the same cost as fresh ones (1/QPS is the cost for each).
//We use this trick later.//注意,如果对于这个函数,我们选择了一条水平线,高度恰好为(1/QPS),则函数的效果不存在:
//我们以与新鲜的令牌完全相同的成本提供储存的令牌(1/QPS是每个令牌的成本);//If we pick a function that goes below that horizontal line,
//it means that we reduce the area of the function, thus time.
//Thus, the RateLimiter becomes faster after a period of underutilization.
//If, on the other hand, we pick a function that goes above that horizontal line,
//then it means that the area (time) is increased, thus storedPermits are more costly than fresh permits,
//thus the RateLimiter becomes slower after a period of underutilization.//如果我们选择一个位于该水平线下方的函数,
//这意味着我们减少了函数的面积,从而减少了时间;
//因此,经过一段时间的未充分利用后,RateLimiter会变得更快;
//另一方面,如果我们选择一个位于水平线上的函数,
//则意味着面积(时间)增加,因此存储的令牌比新鲜的令牌更具成本,
//因此RateLimiter在一段时间的未充分利用之后变得更慢;//Last, but not least: consider a RateLimiter with rate of 1 permit per second,
//currently completely unused, and an expensive acquire(100) request comes.
//It would be nonsensical to just wait for 100 seconds, and then start the actual task.
//Why wait without doing anything?
//A much better approach is to allow the request right away (as if it was an acquire(1) request instead),
//and postpone subsequent requests as needed.
//In this version, we allow starting the task immediately, and postpone by 100 seconds future requests,
//thus we allow for work to get done in the meantime instead of waiting idly.//最后,考虑速率为每秒1个令牌的RateLimiter,当前完全未使用,并且来了一个acquire(100)请求;
//如果要等待100秒才开始返回100个令牌,那是没有意义的;为什么在等待的时候什么都不做呢?
//一个更好的方法是立即允许该acquire(100)请求,就好像它是一个acquire(1)请求一样,并根据需要推迟后续请求;
//在这个版本中,我们允许立即启动任务,并将未来的请求推迟100秒;
//因此,我们允许工作在此期间完成,而不是无所事事地等待;//This has important consequences:
//it means that the RateLimiter doesn't remember the time of the last request,
//but it remembers the (expected) time of the next request.
//This also enables us to tell immediately (see tryAcquire(timeout)) whether a particular timeout is enough to
//get us to the point of the next scheduling time, since we always maintain that. //And what we mean by "an unused RateLimiter" is also defined by that notion:
//when we observe that the "expected arrival time of the next request" is actually in the past,
//then the difference (now - past) is the amount of time that the RateLimiter was formally unused,
//and it is that amount of time which we translate to storedPermits.
//(We increase storedPermits with the amount of permits that would have been produced in that idle time).
//So, if rate == 1 permit per second, and arrivals come exactly one second after the previous,
//then storedPermits is never increased -- we would only increase it for arrivals later than the expected one second.//这有一个很重要的结论:
//这意味着RateLimiter不保存上一次请求的时间,但是它保存下一次请求期望到达的时间;
//这也使我们能够立即判断下次被调度的时间是否是超时;//我们所说的"一个未使用的RateLimiter"也是由这个概念定义的:
//当我们观察到"下一个请求的预期到达时间"实际上已经过去时,并假设下次请求期望到达的时间点是past, 现在的时间点是now,
//那么now - past这段时间表示着RateLimiter没有被使用,所以在这段空闲时间内我们会增加storedPermits的数量;
//(我们只会在这种空闲的时间内增加储存的令牌)
//注意:假设速率rate = 1,即每秒一个permit,并且请求刚好在上一次请求之后的1s到达,
//那么storedPermits是不会增加的,只有超过1s才会增加;
假设RateLimiter对于过去的请求不怎么记忆,只记忆上一次的请求。那么当RateLimiter在很长一段时间内未被使用且一个请求到达时,该如何处理?
由于RateLimiter之前是处于一个长时间未被使用的状态("过去未充分利用"),所以可能是由如下两种情况导致的:利用不足或溢出。
如果是利用不足导致的,即资源过剩,那么RateLimiter应该马上批准新到来的请求。如果是溢出导致的,即服务器因缓存失效等原因花更多时间处理请求,那么RateLimiter应该阻塞新到来的请求。
所以,为了处理RateLimiter在过去未充分利用时面对的情况,我们增加一个storedPermits变量来表示"过去未充分利用"。当不存在未充分利用时,storedPermits变量为零,并且可以增长到maxStoredPermits,以获得足够大的未充分利用。因此acquire(permits)得到的令牌由两部分组成:存储的令牌、新鲜的令牌。
在RateLimiter处于长时间未被使用的状态下:如果是利用不足导致的,那么应该让存储的令牌比新鲜的令牌发放得更快。如果是溢出导致的,那么应该让存储的令牌比新鲜的令牌发放得更慢。因此,我们需要一个函数将storedPermits转换为限流时间。
这个函数是一个将storedPermits映射到1/rate的连续函数。其中storedPermits主要衡量RateLimiter未被使用的时间,我们会在这段未被使用的时间内存储令牌。而rate(速率)就是申请的令牌 / 时间,即rate = permits / time。因此1 / rate = time / permits,表示每个令牌需要的时间。也就是说,这个连续函数上的积分,对应于随后的请求之间的最小时间间隔。
接下来假设这个函数是一条水平线:如果其高度恰好为1/QPS,则函数的效果不存在,因为此时表示以与新鲜的令牌完全相同的成本提供存储的令牌。其中,1/QPS是每个新鲜令牌的成本。
如果该水平线的高度位于1/QPS下方,则意味着减少了函数的面积,从而减少了时间。因此RateLimiter经过一段时间的未充分利用后,会变得更快。
如果该水平线的高度位于1/QPS上方,则意味着增加了函数的面积,从而增加了时间。因此存储的令牌比新鲜的令牌更具成本,RateLimiter经过一段时间的未充分利用后,会变得更慢。
注意:RateLimiter不保存上一次请求的时间,但是它保存下一次请求期望到达的时间。如果下一个请求的预期到达时间实际上已经过去了,并且假设下次请求期望到达的时间点是past,现在的时间点是now。那么now - past的这段时间表示RateLimiter没有被使用,所以在这段空闲时间内我们会增加storedPermits的数量。
(4)SmoothWarmingUp的注释说明
//This implements a "bursty" RateLimiter, where storedPermits are translated to zero throttling.
//The maximum number of permits that can be saved (when the RateLimiter is unused) is defined in terms of time,
//in this sense: if a RateLimiter is 2qps, and this time is specified as 10 seconds, we can save up to 2 * 10 = 20 permits.
static final class SmoothBursty extends SmoothRateLimiter {......
}//This implements the following function where coldInterval = coldFactor * stableInterval.
// ^ throttling
// |
// cold + /
// interval | /.
// | / .
// | / . ← "warmup period" is the area of the trapezoid between thresholdPermits and maxPermits
// | / . "预热期"是指thresholdPermits和maxPermits之间的梯形区域
// | / .
// | / .
// | / .
// stable +----------/ WARM .
// interval | . UP .
// | . PERIOD.
// | . .
// 0 +----------+-------+--------------→ storedPermits
// 0 thresholdPermits maxPermits//Before going into the details of this particular function, let's keep in mind the basics:
//The state of the RateLimiter (storedPermits) is a vertical line in this figure.
//When the RateLimiter is not used, this goes right (up to maxPermits).
//When the RateLimiter is used, this goes left (down to zero), since if we have storedPermits, we serve from those first.
//When unused, we go right at a constant rate! The rate at which we move to the right is chosen as maxPermits / warmupPeriod.
//This ensures that the time it takes to go from 0 to maxPermits is equal to warmupPeriod.
//When used, the time it takes, as explained in the introductory class note,
//is equal to the integral of our function, between X permits and X-K permits,
//assuming we want to spend K saved permits.//在深入了解这个特定函数的细节之前,让我们记住以下基本内容:
//横坐标是RateLimiter的状态,表示storedPermits的值;
//当RateLimiter没有被使用时,横坐标向右移动直到maxPermits;
//当RateLimiter被使用时,横坐标开始向左移动直到0,storedPermits有值,会优先使用它;
//当未被使用时,坐标以恒定的速率向右移动,这个速率被选为:maxPermits / warmupPeriod;
//这样可以保证横坐标从0到maxPermits花费的时间等于warmupPeriod;
//当被使用时,花费的时间就是花费K个permits宽度之间的积分;
//(纵坐标就是1 / rate = time / permits,每个令牌的时间)//In summary, the time it takes to move to the left (spend K permits),
//is equal to the area of the function of width == K.//Assuming we have saturated demand, the time to go from maxPermits to thresholdPermits is equal to warmupPeriod.
//And the time to go from thresholdPermits to 0 is warmupPeriod/2.
//(The reason that this is warmupPeriod/2 is to maintain the behavior of the original implementation where coldFactor was hard coded as 3.)//假设我们的令牌桶满了,maxPermits到thresholdPermits花费的时间等于warmupPeriod;
//从thresholdPermits到0花费的时间是warmupPeriod / 2;//It remains to calculate thresholdsPermits and maxPermits.
//The time to go from thresholdPermits to 0 is equal to the integral of the function between 0 and thresholdPermits.
//This is thresholdPermits * stableIntervals. By (5) it is also equal to warmupPeriod/2.
//Therefore thresholdPermits = 0.5 * warmupPeriod / stableInterval
//The time to go from maxPermits to thresholdPermits is equal to the integral of the function between thresholdPermits and maxPermits.
//This is the area of the pictured trapezoid, and it is equal to 0.5 * (stableInterval + coldInterval) * (maxPermits - thresholdPermits).
//It is also equal to warmupPeriod, so maxPermits = thresholdPermits + 2 * warmupPeriod / (stableInterval + coldInterval)//从thresholdPermits到0花费的时间,是从0到thresholdPermits之间的函数积分;
//等于thresholdPermits * stableIntervals,按照上面讲的它也等于warmupPeriod / 2;
//所以thresholdPermits = 0.5 * warmupPeriod / stableInterval;
//从maxPermits到thresholdPermits花费的时间,等于上图梯形的面积:
//0.5 * (stableInterval + coldInterval) * (maxPermits - thresholdPermits) = warmupPeriod
//所以maxPermits = thresholdPermits + 2 * warmupPeriod / (stableInterval + coldInterval)static final class SmoothWarmingUp extends SmoothRateLimiter {......
}
说明一:RateLimiter是令牌桶算法的具体实现
所以其获取令牌和放入令牌的方法可配合漏桶算法的流程去理解。
说明二:预热模式图中涉及的变量
其中横轴表示令牌桶中的令牌数量,纵轴表示生成令牌的时间间隔。令牌的消费是从右往左进行的。当限流器RateLimiter未被使用时,即空闲时,会生成令牌放入桶中。
SmoothBursty生成令牌的速率是固定的:
stableInterval = 1 / permitsPerSecond;
SmoothWarmingUp生成令牌的速率则会随storedPermits变化而变化:
当storedPermits < thresholdPermits时,速率为stableInterval;
当storedPermits > thresholdPermits时,速率为变化的coldInterval;
变量一:stableIntervalMicros
表示系统预热完成后,生成令牌的时间间隔。若QPS限制为100,则说明每10ms生成一个令牌。
stableIntervalMicros = 1 / permitsPerSecond
变量二:coldIntervalMicros
表示系统水位最低时,生成令牌的时间间隔,与coldFactor有关。
变量三:coldFactor
冷却因子,表示倍数,即coldInterval是stableInterval的多少倍。
变量四:thresholdPermits
表示进入预热阶段的临界值。当令牌桶中的令牌数量减少到临界值时,系统预热结束。当令牌桶中的令牌数量大于临界值时,系统进入冷启动模式。
变量五:maxPermits
表示令牌桶的容量。当令牌桶中的令牌数达到最大容量时,生成的令牌将被抛弃。
变量六:slope
表示斜率。用于计算当前令牌生成时的时间间隔,从而计算当前每秒能生成多少令牌。
变量七:warmupPeriod
表示系统预热时间,即梯形的面积。在预热模型图中,梯形的面积 = (coldFactor - 1) * 长方形面积。
根据梯形面积的计算公式:知道warmupPeriodMicros和permitsPerSecond,就可以计算maxPermits和thresholdPermits。也就是thresholdPermits = 0.5 * warmupPeriodMicros / stableInterval,所以maxPermits = warmupPeriodMicros / stableInterval。
stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros;
maxPermits = thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros);
相关文章:
Sentinel源码—5.FlowSlot借鉴Guava的限流算法一
大纲 1.Guava提供的RateLimiter限流使用示例 2.Guava提供的RateLimiter简介与设计 3.继承RateLimiter的SmoothBursty源码 4.继承RateLimiter的SmoothWarmingUp源码 1.Guava提供的RateLimiter限流使用示例 (1)拦截器示例 (2)AOP切面示例 (1)拦截器示例 一.pom文件中引入G…...
dev_set_drvdata、dev_get_drvdata使用详解
在Linux内核驱动开发中,dev_set_drvdata() 及相关函数用于管理设备驱动的私有数据,是模块化设计和数据隔离的核心工具。以下从函数定义、使用场景、示例及注意事项等方面进行详细解析: 一、函数定义与作用 核心函数 dev_set_drvdata() 和 dev…...
OCR技术与视觉模型技术的区别、应用及展望
在计算机视觉技术飞速发展的当下,OCR技术与视觉模型技术成为推动各行业智能化变革的重要力量。它们在原理、应用等方面存在诸多差异,在自动化测试领域也展现出不同的表现与潜力,下面将为你详细剖析。 一、技术区别 (一ÿ…...
B端管理系统:企业运营的智慧大脑,精准指挥
B端管理系统的定义与核心功能 B端管理系统(Business Management System)是专门设计用于支持企业内部运作和外部业务交互的一套软件工具。它集成了多种功能模块,包括但不限于客户关系管理(CRM)、供应链管理(SCM)、人力资源管理(HRM)以及财务管…...
实现Azure Synapse Analytics安全地请求企业内部API返回数据
需要编写一个Synapse Analytics在Azure云上运行,它需要访问企业内部的API获取JSON格式的数据,企业有网关和防火墙,API有公司的okta身份认证,通过公司的域账号来授权访问,现在需要创建一个专用的域账号,让Sy…...
让机器学习更透明:使用 Python 开发可解释性模型工具包
友友们好! 我是Echo_Wish,我的的新专栏《Python进阶》以及《Python!实战!》正式启动啦!这是专为那些渴望提升Python技能的朋友们量身打造的专栏,无论你是已经有一定基础的开发者,还是希望深入挖掘Python潜力的爱好者,这里都将是你不可错过的宝藏。 在这个专栏中,你将会…...
PHP实现简单的爬虫功能
<?php// 目标URL $url https://example.com;// 初始化cURL $ch curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_USERAGENT, Mozilla/5…...
手撕 简易HashMap
put()、get()、remove() 方法 计算存储数组位置和k-vNode节点 public int indexOf(K key){return key.hashCode() & (table.length - 1);}static class Node<K, V>{K key;V value;Node<K, V> next;public Node(K key, V value){this.key key;this.value val…...
go-map+sync.map的底层原理
map 哈希冲突解决方式 1.拉链法 2.开放地址法 底层结构 Go 的 map 在源码中由 runtime.hmap 结构体表示,buckets-指向桶数组的指针(常规桶),oldbuckets-扩容时指向旧桶数组的指针。 type hmap struct {count int // 当前元素个数(len…...
日语学习-日语知识点小记-进阶-JLPT-N2阶段(6): - (1)ても てでも特别强调(2)~もしないで = 聞かないで:根本不做某动作”
日语学习-日语知识点小记-进阶-JLPT-N2阶段(6): - (1)ても てでも特别强调(2)~もしないで 聞かないで:根本不做某动作”。 1、前言(1)情况说…...
读文献方法
虽然读了很多文献,但是并不代表我把读文献这件事真的做到了极致。因此不断优化迭代,反思自己哪里做的不好,非常重要。 阅读学术论文时,采用结构化、分阶段的策略可以显著提高效率。以下是针对这篇流体力学论文的高效阅读方法&…...
对象存储概述
对象存储概述 1. 定义与基本概念 对象存储(Object-based Storage)是一种新型网络存储架构,其核心是将数据作为对象(Object)进行管理,每个对象包含数据本身、元数据(Metadata)和唯一…...
Grallvm技术介绍
GrallVM介绍 GraalVM核心特性 GraalVM 是由 Oracle 开发的高性能运行时环境,基于 HotSpot JVM 构建,支持多语言互操作和 Ahead-of-Time (AOT) 编译。以下是其核心特性: 核心技术 Graal JIT 编译器:替代传统 JVM 的 C2 编译器&am…...
matlab 环形单层柱状图
matlab 环形单层柱状图 matlab 环形单层柱状图 matlab 环形单层柱状图 图片 图片 【图片来源粉丝】 我给他的思路是:直接使用风玫瑰图可以画出。 rose_bar 本次我的更新和这个有些不同!是环形柱状图,可调节细节多; 只需要函数…...
Java调用LLM大模型 - 基于 Spring AI 实现
Spring AI Alibaba实战:Java集成通义千问构建流式对话应用 一、Spring AI核心架构解析 1.1 框架定位与优势对比 graph TDA[Spring AI] --> B[统一API接口]A --> C[多模型支持]A --> D[企业级特性]B --> E(OpenAI/Azure/阿里云)C --> F(LLaMA/Qwen…...
RK | rk3568开发与学习
RK | rk3568开发与学习 时间:2025年4月19日17:20:28 文章目录 RK | rk3568开发与学习1.参考2.资料3.初次使用连接串口网络连接有线连接SSH登录 1.参考 Rockchip: Rockchip-瑞芯微电子股份有限公司 正点原子: 1.正点原子RK3568开发板瑞芯微Linux嵌入式ARM…...
使用Service发布前后端应用程序
使用Service发布前后端应用程序 文章目录 使用Service发布前后端应用程序[toc]一、创建并发布后端应用程序二、创建并发布前端应用程序三、通过前端发送流量进行测试 部署前端(Frontend)微服务和后端(Backend)微服务是比较常见的应…...
测试第四课---------性能测试
作者前言 🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂 🎂 作者介绍: 🎂🎂 🎂 🎉🎉🎉…...
数据结构(6)——队列
目录 前言 一、队列的概念及其结构 二、实现 2.1结构体定义 2.2初始化 2.3销毁链表 2.4尾插入(入队) 2.5头删(出队) 2.6个数 2.7检验是否为空 2.8取队头数据 2.9取队尾数据 三、检验 总结 前言 本文介绍队列&#x…...
在RK3588上使用ZLMediaKit
在RK3588上使用ZLMediaKit ZLMediaKit是一个高性能的流媒体服务器框架,可以在RK3588平台上运行。以下是在RK3588上使用ZLMediaKit的指南: 1. 环境准备 首先确保你的RK3588开发板已安装好Linux系统(如Debian或Ubuntu)。 安装依…...
分布式系统核心原理
CAP定理与权衡实践 CAP定理 一致性(Consistency) 强一致性:所有读写操作均基于最新数据(如银行转账)。 最终一致性:数据副本经过一段时间后达到一致(如社交媒体的点赞数)。 技术实现…...
【进程信号】五、信号集操作接口详解
文章目录 Ⅰ. 操作sigset_t变量接口Ⅱ. sigprocmask(阻塞信号集)Ⅲ. sigpending(未决信号集)Ⅳ. 接口使用代码⚜️sigaction(捕捉信号)Ⅴ. 测试sigaction的一些场景Ⅰ. 操作sigset_t变量接口 还记得我们上面讲过的 sigset_t 类型吗,sigset_t 类型对于每种信号用一个…...
Doris 本地部署集群重启后报错
报错描述 Docker 版本: apache/doris:fe-2.1.9 apache/doris:be-2.1.9 连接 MySQL 报错: ERROR 2003 (HY000): Cant connect to MySQL server on 127.0.0.1:9030 (111)FE 日志: INFO (UNKNOWN fe_e7cff187_69d4_42ee_90be_147e87310549(-1…...
bat脚本转换为EXE应用程序文件
很多时候,我们使用电脑时会编辑bat脚本文件 很多时候,我们制作的玩笑,病毒也会使用这个格式. 但这个格式也有很多缺点 1,如果是需要管理员运行的程序,需要费劲的自己使用管理员身份运行 2,文件并不为大家所熟知,认同度不高 3,可以非常轻松的看到原代…...
爬虫入门与requests库的使用——python爬虫
文章目录 浏览器抓包浏览器抓包介绍浏览器抓包页面介绍 python 爬虫爬虫是什么web网页渲染的方式http 协议http协议对资源的操作requests 库requests 是什么requests 的安装requests库的基础使用requests中不同的请求方式GET传递参数POST传递参数响应内容定制请求头Cookie获取服…...
[Java EE] Spring 配置 和 日志
目录 1. 配置文件 1.1 作用 1.2 Spring Boot 配置文件 1.3 读取配置文件 1.3.1 配置对象 1.3.2 配置集合 1.3.3 配置Map 1.4 yml 优缺点 2. 日志 2.1 日志的作用 2.2 日志的使用 2.3 日志框架 2.3.1 门面模式(外观模式) 2.4 SLF4J 框架介绍 2.5 日志格式的说明 …...
如何0基础学stm32?
如何0基础学stm32? 作为一个混迹嵌入式领域十余年的老兵,每次看到"0基础学STM32"这样的提问,我都忍不住想笑,又有些无奈。这就像问"如何0基础学开飞机"一样—虽然理论上可行,但过程恐怕没那么愉快…...
XCZU27DR‑2FFVE1156I Xilinx Zynq UltraScale+ RFSoC
一、概述 XCZU27DR‑2FFVE1156I 属于 Zynq UltraScale™ RFSoC Gen 2 系列,采用 TSMC 16 nm FinFET 工艺,Speed Grade ‑2,集成了 ARM 处理系统、可编程逻辑与高性能射频数据转换单元,为软件定义无线电、5G 前端、测试测量等场景…...
取值运算符*和地址运算符
在指针的学习中,必不可少的两个操作符:*和&。 在定义一个指针的时候,比如 short *p; 表示一个指向short数据类型的指针,具体表达的意思就是这个指针P指向的一个数据类型是short类型,也就是说操作的这…...
LNA设计
设计目的 为后级提供足够的增益以克服后级电路噪声 尽可能小的噪声和信号失真 确保输入和输出端的阻抗匹配 确保信号线性度 评价标准 噪声系数 功率增益 工作频率和带宽 输入信号功率动态范围 端口电压驻波比 稳定性 基于SP模型的LNA设计 直流分析 S参数分析 设计指标 …...
FPGA——DDS信号发生器设计
文章目录 任务要求一、DDS简介二、设计过程1、相位累加器的设计2、波形存储器设计3、锁相环倍频电路设计4、顶层电路设计 三、设计实现四、运行结果总结参考资料 任务要求 1)利用DDS技术合成正弦波和方波; 2)输出信号的频率范围为10Hz~5MHz,…...
【网络编程】TCP数据流套接字编程
目录 一. TCP API 二. TCP回显服务器-客户端 1. 服务器 2. 客户端 3. 服务端-客户端工作流程 4. 服务器优化 TCP数据流套接字编程是一种基于有连接协议的网络通信方式 一. TCP API 在TCP编程中,主要使用两个核心类ServerSocket 和 Socket ServerSocket Ser…...
数据可视化(Matplotlib和pyecharts)
一 常见图形概念及使用 图表类型适用场景核心特点柱状图(bar)比较不同类别数据(如各地区销售额对比)、时间序列分析(离散时间)高度反映数值大小,支持横向/纵向展示,可叠加分组折线图(plot)连续数据趋势比较(适合展示随时间的变化,如股票价格走势、用户增长趋势)、多变…...
如何系统地入门学习stm32?
如何系统地入门学习stm32? 作为一个在嵌入式领域摸爬滚打十余年的工程师,看到这个问题,我不禁想起自己当年啃着厚重的数据手册,对着一块蓝色的PCB板冥思苦想的日子。STM32的学习之路,说难不算特别难,说简单…...
matlab读取CMEMS海洋温度数据并调整图片的比例
matlab读取CMEMS海洋温度数据并调整图片的比例 matlab读取CMEMS海洋温度数据并调整图片的比例 matlab读取CMEMS海洋温度数据并调整图片的比例 数据的下载见上期: 链接到CMEMS数据下载{python} 本文还会给出另一个关键技巧: 通常设置图片比列直接可以通过…...
ReSearch:基于强化学习的大语言模型推理搜索框架
ReSearch是一种创新性框架,通过强化学习技术训练大语言模型执行"推理搜索",无需依赖推理步骤的监督数据。该方法将搜索操作视为推理链的有机组成部分,其中搜索的时机与方式由基于文本的推理过程决定,而搜索结果进一步引…...
【记录】服务器安装ffmpeg
前言 因为项目中需要用到 ffmpeg 进行图像的一些操作,本文记录下在服务器安装 ffmpeg 的全过程,还是具有一定挑战性的。 系统详情 本文使用的操作系统详情如下 通过 命令 cat /etc/os-release 获取 虽然操作系统为 Rocky Linux,但安装过程是通用的,因为本文记录的是从源代码…...
部署rocketmq集群
容器化部署RocketMQ5.3.1集群 背景: 生产环境单机的MQ不具有高可用,所以我们应该部署成集群模式,这里给大家部署一个双主双从异步复制的Broker集群 一、安装docker yum install -y docker systemctl enable docker --now # 单机部署参考: https://www.cnblogs.com/hsyw/p/1…...
中国AIOps行业分析
基本术语 AIOps是"Artificial Intelligence for IT Operations"(IT运维人工智能)的缩写,它指的是将人工智能技术应用于IT运维领域,基于已有的运维数据(如日志、监控信息、应用信息等),通过机器学习的方式解决自动化运维无法解决的问题6。AIOps将机器学习(ML)…...
C++入门[超详细]
#include <iostream c的标准输入输出流 C的域 using namespace std; namespace本质是一个域 只有域里面的定义代码才能使用 std包含了c输入输出的标准库 缺省 只能从左到右缺省,不能中间空格 void f1(int a10,int b20,int c0) { } f1(); f1(1); f1(1,2); f1(1,2,3); f1(…...
字符串系列一>二进制求和
目录 题目:解析:代码: 题目: 链接: link 解析: 代码: class Solution {public String addBinary(String a, String b) {StringBuffer ret new StringBuffer();int t 0;char[] aa a.toCharArray();char[…...
序列化和反序列化
概念 创建出来的这些对象都存在于JVM中的堆(heap)内存中,只有JVM处于运行状态的时候,这些对象才可能存在。当JVM停止,这些对象也就随之消失。 java序列化可以帮我们实现:将这些对象持久化,并且…...
rebase和merge的区别
目录 1. 合并机制与提交历史 2. 冲突处理方式 3. 历史追溯与团队协作 4. 推荐实践 5. 撤销难度 git rebase和git merge是Git中两种不同的分支合并策略,核心区别在于提交历史的处理方式:merge保留原始分支结构并生成合并提交&am…...
linux查看目录相关命令
查看目录命令 学习目标 能够使用Linux命令查看目录信息 1. 查看目录命令的使用 命令说明ls查看当前目录信息tree以树状方式显示目录信息 ls命令效果图: tree命令效果图: 2. 查看当前目录路径 命令说明pwd查看当前目录路径 pwd命令效果图: 3. 清除终端内容 命令说明clear…...
203. 移除链表元素
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 示例 1: 输入:head [1,2,6,3,4,5,6], val 6 输出:[1,2,3,4,5]示例 2: 输入:…...
Cursor新版0.49.x发布
小子看到 Cursor 0.49.x 版本正式发布,截止今天已经有两个小patch版本!本次更新聚焦于 自动化Rules生成、改进的 Agent Terminal 以及 MCP 图像支持,并带来了一系列旨在提升编码效率和协作能力的改进与修复。 以下是本次更新的详细内容&…...
music21:伍佰 泪桥 MIDI 音乐分析
以下是使用 music21 对伍佰《泪桥》MIDI 音乐进行分析的一些可能方面: 基本信息3 曲长:全曲长 2 分 31 秒。音符数量:共 273 个音符。音轨信息:共 2 个音轨,其中 1 个音轨有音符,可视为单轨 MIDI 文件&am…...
Mybatis源码01-SpringBoot启动时mybatis加载过程
使用了mybatis这么久还没有具体探究了SpringBoot启动时候对于mybatis是怎么加载的。 1、首先项目构建时我们会引入相关的依赖: <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</arti…...
springCloud/Alibaba常用中间件全集(上)
文章目录 SpringCloud:一、Consul:服务注册与发现1、下载Consul2、运行Consul3、服务注册①. 导入依赖②. 配置yml③. 启动类添加Consul的启动服务发现注解④. 解决 **硬编码** 问题⑤. 此时便可以将IP地址改为服务名 4、服务配置与刷新①. 引入Consul-Config依赖②. 修改boots…...
嵌入式单片机通过ESP8266连接物联网实验
第一:通过手机APP远程监控和控制 ESP8266驱动RST低电平触发复位,平时需要跟EN一样分别接10k拉高到3.3V 如果是12E/F的话管脚比较多,GPIO15也要接个1K到地 烧录时GPIO要接地,正常工作时将其拉高或者悬空 主要使用串口通信,烧录固件也是通过串口,烧录时,启动烧录程序后…...