Sentinel限流算法:滑动时间窗算法、漏桶算法、令牌桶算法。拦截器定义资源实现原理

news2024/9/21 23:25:31

文章目录

    • 滑动时间窗算法
      • 基本知识
      • 源码算法分析
    • 漏桶算法
    • 令牌桶算法
    • 拦截器处理web请求



滑动时间窗算法

基本知识

限流算法最简单的实现就是使用一个计数器法。比如对于A接口来说,我要求一分钟之内访问量不能超过100,那么我们就可以这样来实现:

  • 最开始的时候就设置一个count值,每当一个请求过来我就count++
  • 如果count的值大于了100,并且与第一个请求的时间间隔小于1分钟,那么就表示请求数过多
  • 如果该请求与第一个请求的间隔时间大于1分钟,且count的值还在限流范围内,那么就重置count。

在这里插入图片描述

/**
 * @Description: 限流算法之计数器法,伪代码的实现
 * @Author 胡尚
 * @Date: 2024/7/12 8:50
 */
public class Count {
    // 当前时间
    private Long timeStamp = System.currentTimeMillis();
    // 请求数量
    private int count = 0;
    // 时间窗口的最大请求数
    private final int limit = 100;
    // 时间窗口ms
    private final long interval = 1000 * 60;

    /**
     * 限流校验,校验是否成功
     * @return true表示允许请求,false表示校验未通过
     */
    public boolean check(){
        long now = System.currentTimeMillis();
        // 还在一分钟 时间窗口之内
        if (now <= timeStamp + interval){
            count++;
            return count <= limit;
        } else {
            timeStamp = System.currentTimeMillis();
            // 超时后重置请求数
            count = 1;
        }

        return true;
    }
}



计数法的实现有一个缺点,那就是限流的时间精度不准确。比如第一个时间窗口的前半分钟只有十个请求,后半分钟有90个请求;第二个时间窗口的前半分钟有90个请求,后半分钟只有十个请求。这种情况下,限流器是不会出现限流的,但是我在在一个时间段中的请求却有180个了。

在这里插入图片描述



为了解决计算器法统计时间精度不够的问题,进而引入了滑动时间窗

滑动时间窗算法的基本原理是维护一个固定大小的时间窗口,‌窗口内的数据被认为是当前分析的有效数据。‌随着时间的推移,‌新的数据点进入窗口,‌而旧的数据点则被移出窗口,‌从而形成了一个滑动的时间窗口。‌

在这里插入图片描述


在上图中,一个红色窗格就是一个时间窗口,我们把一个时间窗口分为了6个小格子。就拿时间窗口为一分钟举例,上面每一个小格子就是10秒;每过10秒,我们的时间窗口就会往右移动一格。每一个格子都有自己的计数器count,比如当一个请求 在0:35秒的时候到达,那么0:30~0:39对应的counter就会加1。



在具体实现上,‌滑动时间窗算法可以通过多种数据结构来实现,‌例如使用环形数组、‌哈希表等。‌例如,‌可以使用一个环形数组来存储时间窗口内的数据点,‌数组的大小等于时间窗口的大小(‌以时间单位为单位)‌。‌每当有新的数据点进入时,‌旧的对应时间点的数据将被覆盖,‌从而实现滑动时间窗的效果。‌此外,‌还可以使用哈希表来记录每个时间点上的数据变化情况,‌其中键为时间点,‌值为该时间点的数据值或变化量。‌



/**
 * @Description: 限流算法之滑动时间窗算法的伪代码实现
 * 假设每秒的请求不能超过100,我们设置一个1s的时间窗口,时间窗口中共有10个小格子,
 * 每个格子记录100ms的请求数,每100毫秒移动一次,每次移动都需要记录当前服务请求数
 * 我这里就简单实现,只有一个计数器,最新的小窗口永远存储最新访问请求总数。当然也可以每个小窗口都有自己的计数器。
 * @Author 胡尚
 * @Date: 2024/7/12 9:23
 */
public class SlidingTimeWindow {

    /**
     * 服务器访问次数,可以放在redis中实现分布式系统访问
     */
    private Long count = 0L;
    /**
     * 滑动时间窗,使用linkedList来记录滑动窗口的10个格子
     */
    private LinkedList<Long> slots = new LinkedList<>();

    public static void main(String[] args) throws InterruptedException {
        SlidingTimeWindow timeWindow = new SlidingTimeWindow();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    timeWindow.onCheck();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        // 模拟一直都有请求数,随机休眠几毫秒
        while(true){
            // TODO 限流校验
            timeWindow.count++;
            Thread.sleep(new Random().nextInt(15));
        }

    }

    private void onCheck() throws InterruptedException {
        while (true){
            // 把当前访问总数存入时间小窗口中
            slots.add(count);

            // 时间窗口的剔除操作
            if (slots.size() > 10){
                slots.removeFirst();
            }

            // 最新的时间小窗口的数和最老的时间小窗口数进行比较,是否要限流
            if (slots.peekLast() - slots.peekFirst() > 100 ){
                // TODO 限流标识
            } else{
                // TODO 解除限流标识
            }

            Thread.sleep(100);
        }
    }


}



源码算法分析

我们接下来看看Sentinel它的滑动时间窗是怎么实现的。我们从StatisticSlot类的entry()方法开始看,因为它每次都会记录请求通过/请求拒绝相关的计数

public void entry (...) throws Throwable {
    ...
        // 请求通过,我们详细看看这个方法的处理逻辑
        node.addPassRequest(count);
}

// 调用到StatisticNode类的addPassRequest()方法中
public void addPassRequest(int count) {
    // 一个记录秒级
    // 它的创建语句 :Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,IntervalProperty.INTERVAL);
    // 小窗口总数SampleCountProperty.SAMPLE_COUNT = 2   滑动时间窗总时间数IntervalProperty.INTERVAL=1000 这两个数在下面的方法中会用到
    rollingCounterInSecond.addPass(count);
    // 一个记录分钟级
    rollingCounterInMinute.addPass(count);
}

// 进入到addPass()方法中
public void addPass(int count) {
    // 获取到当前时间小窗口
    WindowWrap<MetricBucket> wrap = data.currentWindow();
    // 往时间小窗口中进行相加操作
    // wrep.value()获取的是MetricBucket对象,它存储了一个LongAdder[] counters数组
    // 所以这里的添加操作应该是往LongAdder[]数组中对应的某一个小标进行相加操作
    wrap.value().addPass(count);
}



我们首先详细分析data.currentWindow();这里是如何根据当前时间获取到对应的时间小窗口的

  • 计算出当前请求时间对应的小窗口下标idx
  • 计算出当前请求的小窗口起始时间
  • 取出idx对应的老的小窗口对象
    • 如果老的小窗口对象为null,那么就直接创建一个小窗口对象,并存入滑动窗口对应的小窗口位置
    • 如果老的小窗口对象所代表的小窗口起始时间 = 当前请求的小窗口起始时间 ,那么就返回老的小窗口对象
    • 如果老的小窗口对象所代表的小窗口起始时间 < 当前请求的小窗口起始时间,那么就会替换覆盖掉老的小窗口对象
    • 时钟回拨的处理,创建一个新的无关紧要的小窗口对象返回
public WindowWrap<T> currentWindow(long timeMillis) {
    // 当前时间戳基本上不会小于0
    if (timeMillis < 0) {
        return null;
    }

    // 获取当前时间对应的小窗口的下标数: timeMillis / 小窗口总时间数 % 小窗口数
    // 比如当前限流时间是1秒,移动窗口的小窗口数是2,那么每个小窗口总时间数就是500ms。
    // 假如当前时间为700ms。那么就是 700 / 500 = 1 ,然后1%2 = 1  最终得到我当时间所在的小窗口是下标为1的小窗口
    int idx = calculateTimeIdx(timeMillis);
    
    // Calculate current bucket start time.
    // 计算当前小窗口的启动时间:timeMillis - timeMillis % 小窗口总时间数
    // 还是上面的案例: 700 - 700%500 = 500。 那么700ms这个时间对应的小窗口启动时间为500
    // 假如当前时间为1600ms:1600 - 1600%500 = 1500。  那么1600md对应小窗口的启动时间是1500
    long windowStart = calculateWindowStart(timeMillis);

    while (true) {
        // 取出老小窗口对象WindowWrap,根据上面得到的当前时间对应的小窗口的下标数
        WindowWrap<T> old = array.get(idx);
        
        // 如果滑动时间窗口中,该小窗口为null
        if (old == null) {
            // 那么就创建一个小窗口对象WindowWrap,参数有小窗口的总时长、小窗口的起始时间、当前请求时间戳
            WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
            // 再用CAS算法存入时间窗口中
            if (array.compareAndSet(idx, null, window)) {
                return window;
            } else {
                Thread.yield();
            }


            // 如果当前时间对应的小窗口起始时间 = 老小窗口的起始时间。那么就直接返回老的小窗口对象
            // 可能有一个700ms的请求先过来,创建了一个WindowWrap小窗口对象。然后来了一个750ms的请求,这时它们的小窗口的起始时间就是一样的
        } else if (windowStart == old.windowStart()) {
            return old;


            // 如果当前时间对应的小窗口起始时间 > 老的小窗口的起始时间。那么就把老的小窗口对象给覆盖掉
            // 可能有一个700ms的请求先过来,创建了一个WindowWrap小窗口对象。
            // 然后来了一个1600ms的请求,这时它们的小窗口的起始时间就是500 和 1500
            // 这里就会调用resetWindowTo(old, windowStart);方法进行滑动时间窗,剔除掉老的小窗口
        } else if (windowStart > old.windowStart()) {

            if (updateLock.tryLock()) {
                try {
                    // 重置小窗口
                    return resetWindowTo(old, windowStart);
                } finally {
                    updateLock.unlock();
                }
            } else {
                Thread.yield();
            }

            
            // 时钟回拨的处理逻辑,直接返回一个新的小窗口WindowWrap对象,它和滑动窗口都没有关系
        } else if (windowStart < old.windowStart()) {
            // Should not go through here, as the provided time is already behind.
            return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
        }
    }
}



我们再看看往时间小窗口中进行相加操作的具体实现wrap.value().addPass(count);

// 往时间小窗口中进行相加操作
// wrep.value()获取的是MetricBucket对象,它存储了一个LongAdder[] counters数组
// 所以这里的添加操作应该是往LongAdder[]数组中对应的某一个小标进行相加操作
wrap.value().addPass(count);

// 方法调用
public void addPass(int n) {
    // 这里就传了一个代码pass通过的枚举对象
    add(MetricEvent.PASS, n);
}

// 往代码Pass通过的数组下标对应位置进行相加操作
public MetricBucket add(MetricEvent event, long n) {
    // event.ordinal()获取的其实就是当前对象在枚举中的下标数
    // 官方解释:返回此枚举常量的序号(其在枚举声明中的位置,其中初始常量被赋值为0的序数)
    counters[event.ordinal()].add(n);
    return this;
}



通过上方的代码我们就已经知道了Sentinel在滑动窗口上的具体实现。我们现在再看看限流FlowSlot流程中,从时间窗口中取值的流程。

快速失败方式 --> DefaultController.canPass()

public boolean canPass(Node node, int acquireCount, boolean prioritized) {
    // 当前时间窗口中取统计指标数据
    int curCount = avgUsedTokens(node);
    // 当前qps > count阈值,那么就要返回false
    if (curCount + acquireCount > count) {
        // 因为prioritized默认情况下都是false,所以下面if不需要花太多精力去看
        if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {
            ...
        }
        return false;
    }
    return true;
}


// 我们看看avgUsedTokens(node)方法是怎么从时间窗口中取值的。最终会调用至	ArrayMetric.pass()方法中
public long pass() {
    data.currentWindow();
    long pass = 0;
    // data.values()其实就是从滑动时间窗口中取所有的小窗口对象中存的MetricBucket对象
    List<MetricBucket> list = data.values();

    for (MetricBucket window : list) {
        // 求总和
        pass += window.pass();
    }
    return pass;
}



漏桶算法

漏桶算法,又称leaky bucket。

在这里插入图片描述


从图中我们可以看到,整个算法其实十分简单。首先,我们有一个固定容量的桶,有水流进来,也有水流出去。对于流进来的水来说,我们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对于流出去的水来说,这个桶可以固定水流出的速率。而且,当桶满了之后,多余的水将会溢出。


我们将算法中的水换成实际应用中的请求,我们可以看到漏桶算法天生就限制了请求的速度。当使用了漏桶算法,我们可以保证接口会以一个常速速率来处理请求。所以漏桶算法天生不会出现临界问题。



/**
 * @Description: 限流算法之漏桶算法  伪代码实现
 * @Author 胡尚
 * @Date: 2024/7/12 11:57
 */
public class LeakyBucket {
    /**
     * 当前时间
     */
    private long timeStamp = System.currentTimeMillis();
    /**
     * 桶的容量
     */
    private long capacity;
    /**
     * 水漏出的速度(每秒系统能处理的请求数)
     */
    private long rate;
    /**
     * 当前水量(当前累积请求数)
     */
    private long water;

    public boolean check() {
        long now = System.currentTimeMillis();
        // 当前水量
        water = Math.max(0, capacity - ((now - timeStamp) / 1000) * rate); 
        // 更新timeStamp
        timeStamp = now;
        if (water + 1 > capacity){
            // 水满了,直接拒绝
            return false;
        }else{
            // 还能装水
            water++;
            return true;
        }
    }
}



我们接下来看看sentinel,它在流控规则中的排队等待是怎么实现漏桶算法的。

  • 计算出两次请求应该的间隔时间costTime
  • 计算出下一次请求期望的时间点expectedTime
  • 如果currentTime > currentTime 就返回true,放行
    • 否则计算当前请求需要等待时间waitTime
    • 判断waitTime > 最大超时时间 就返回false
    • 否则再又重新计算一次等待时间,重新判断一次是否超过最大等待时间,再去进入等待sleep()
// 排队等待 --> RateLimiterController.canPass() ---> 漏桶算法
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
    if (acquireCount <= 0) {
        return true;
    }

    if (count <= 0) {
        return false;
    }

    // 获取当前请求的当前时间
    long currentTime = TimeUtil.currentTimeMillis();

    // acquireCount一般就是一次请求,也就是1   count就是我们在控制台界面中设置的阈值
    // costTime就是算出两次请求之间应该的间隔时间,比如我1s中只能允许10个请求。那么这里就是 1.0 / 10 * 1000 = 100ms。
    // 也就是说我两次请求的间隔时间应该是100毫秒
    long costTime = Math.round(1.0 * (acquireCount) / count * 1000);
    
    // expectedTime就是一个期望时间。 期望时间 = 两次请求间隔时间 + 上一次请求时间
    // 比如我上一个请求的时间为300ms,那么我期望下一个请求应该是400ms
    long expectedTime = costTime + latestPassedTime.get();

    // 假如我当前的请求时间 比期望时间还要大,那么就直接更新最后一次请求时间,并返回true放行当前请求
    // 比如我期望下一个请求的时间是400ms。但我现在请求的时间已经是450ms了,我比你期望的时间还要长,那你是不是肯定要给我放行了
    if (expectedTime <= currentTime) {
        latestPassedTime.set(currentTime);
        return true;
    } else {
        // 接下来就是我当前的请求时间,比期望的时间要小的处理逻辑

        // 重新算出来的期望时间 - 当前请求的时间 = 我当前这个请求需要等待的时间
        long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();
        
        // 如果我这个请求要等待的时间,超过了控制台界面设置的最大超时时间那么就直接返回false
        if (waitTime > maxQueueingTimeMs) {
            return false;
        } else {
            // 接下来是我当前请求需要进行等待的处理逻辑

            // 修改上一次请求时间,上一次请求时间 + 间隔时间 = oldTime(其实就是重新算出来的期望时间)
            long oldTime = latestPassedTime.addAndGet(costTime);
            try {
                // 重新计算一次,期望时间 - 当前请求的时间 = 当前请求要等待的时间
                waitTime = oldTime - TimeUtil.currentTimeMillis();
                // 当前请求要等待的时间 是否大于了最大超时时间,如果大于了就把上一次请求时间改回来,在直接返回false
                if (waitTime > maxQueueingTimeMs) {
                    latestPassedTime.addAndGet(-costTime);
                    return false;
                }
                // 当前线程进入等待
                if (waitTime > 0) {
                    Thread.sleep(waitTime);
                }
                return true;
            } catch (InterruptedException e) {
            }
        }
    }
    return false;
}



令牌桶算法

令牌桶算法,又称token bucket。

在这里插入图片描述

有一个线程,会根据一定增长的预热速率往桶中放令牌,如果桶中的令牌满了那么就就直接将令牌丢弃掉。

当有请求过来时就会从桶中获取令牌,如果获取到了就放行,如果没有获取到就拒绝。



/**
 * 令牌桶限流算法  伪代码
 * 这里就没有预热、增长的速率往桶中放入令牌
 */
public class TokenBucket {
    public long timeStamp = System.currentTimeMillis();  // 当前时间
    public long capacity; // 桶的容量
    public long rate; // 令牌放入速度
    public long tokens; // 当前令牌数量
 
    public boolean grant() {
        long now = System.currentTimeMillis();
        // 先添加令牌
        tokens = Math.min(capacity, tokens + (now - timeStamp) * rate);
        timeStamp = now;
        if (tokens < 1) {
            // 若不到1个令牌,则拒绝
            return false;
        } else {
            // 还有令牌,领取令牌
            tokens--;
            return true;
        }
    }
}

对应的sentinel中,Warm Up的实现就是基于令牌桶实现的,Warm Up --> WarmUpController.canPass() --> 令牌桶算法

这里就不展开介绍sentinel具体的实现算法了

public boolean canPass(Node node, int acquireCount, boolean prioritized) {
    long passQps = (long) node.passQps();

    long previousQps = (long) node.previousPassQps();
    syncToken(previousQps);

    // 开始计算它的斜率
    // 如果进入了警戒线,开始调整他的qps
    long restToken = storedTokens.get();
    if (restToken >= warningToken) {
        long aboveToken = restToken - warningToken;
        // 消耗的速度要比warning快,但是要比慢
        // current interval = restToken*slope+1/count
        double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
        if (passQps + acquireCount <= warningQps) {
            return true;
        }
    } else {
        if (passQps + acquireCount <= count) {
            return true;
        }
    }

    return false;
}



限流算法小结

计数器 VS 滑动窗口:

计数器算法是最简单的算法,可以看成是滑动窗口的低精度实现。
滑动窗口由于需要存储多份的计数器(每一个格子存一份),所以滑动窗口在实现上需要更多的存储空间。
也就是说,如果滑动窗口的精度越高,需要的存储空间就越大。

漏桶算法 VS 令牌桶算法:

漏桶算法和令牌桶算法最明显的区别是令牌桶算法允许流量一定程度的突发。
因为默认的令牌桶算法,取走token是不需要耗费时间的,也就是说,假设桶内有100个token时,那么可以瞬间允许100个请求通过。
当然我们需要具体情况具体分析,只有最合适的算法,没有最优的算法。



拦截器处理web请求

我们引入了下面的依赖,也就是我们实际使用sentinel的场景下,我们会发现我们controller层编写的http请求接口都不需要我们自己额外的定义资源开启保护。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

会自动的把我们的http请求接口定义成一个一个的资源。具体的实现如下:

入口是SentinelWebAutoConfiguration自动配置类

在这里插入图片描述



SentinelWebAutoConfiguration自动配置类它实现了WebMvcConfigurer接口,所以在SpringBoot启动过程中就会调用这个类重写的addInterceptors方法

public class SentinelWebAutoConfiguration implements WebMvcConfigurer {

	......

    // 此数组存的就是最下面创建的拦截器
	@Autowired
	private Optional<SentinelWebInterceptor> sentinelWebInterceptorOptional;

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		if (!sentinelWebInterceptorOptional.isPresent()) {
			return;
		}
		SentinelProperties.Filter filterConfig = properties.getFilter();
        // 最终下面创建的拦截器会被添加在这里来
		registry.addInterceptor(sentinelWebInterceptorOptional.get())
				.order(filterConfig.getOrder())
				.addPathPatterns(filterConfig.getUrlPatterns()); // 这里其实就是拦截所有的请求  /**
		log.info(...);
	}

    // 往Spring容器中存一个SentinelWebInterceptor拦截器
	@Bean
	@ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled",matchIfMissing = true)
	public SentinelWebInterceptor sentinelWebInterceptor(SentinelWebMvcConfig sentinelWebMvcConfig) {
		return new SentinelWebInterceptor(sentinelWebMvcConfig);
	}
    
    ...
}

SentinelWebInterceptor extends AbstractSentinelInterceptor 这里是该类的继承关系,我们直接去看它父类中的preHandle()方法

// 前置拦截器方法
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    try {
        String resourceName = this.getResourceName(request);
        if (StringUtil.isEmpty(resourceName)) {
            return true;
        } else if (this.increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {
            return true;
        } else {
            String origin = this.parseOrigin(request);
            String contextName = this.getContextName(request);
            ContextUtil.enter(contextName, origin);
            
            // 这里定义了资源,开启了保护
            Entry entry = SphU.entry(resourceName, 1, EntryType.IN);
            request.setAttribute(this.baseWebMvcConfig.getRequestAttributeName(), entry);
            return true;
        }
    } catch (BlockException var12) {
        // 如果出现了BlockException,则去相应的处理逻辑
        BlockException e = var12;

        try {
            this.handleBlockException(request, response, e);
        } finally {
            ContextUtil.exit();
        }

        return false;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1923090.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习C++,应该循序渐进的看哪些书?

学习C是一个循序渐进的过程&#xff0c;需要根据自己的基础和目标来选择合适的书籍。以下是一个推荐的学习路径&#xff0c;包含了从入门到进阶的书籍&#xff1a; 1. 入门阶段 《C Primer Plus 第6版 中文版》 推荐理由&#xff1a;这本书同样适合C零基础的学习者&#xff0…

几何建模-Parasolid中GO功能使用

1.背景介绍 1.1 Parasolid和它的接口间关系 1.2 什么是GO GO全称是Graphical Output.你的程序需要在屏幕或者打印设备上显示模型数据时。在需要使用PK中的某个渲染函数时创建图形显示数据时&#xff0c;Parasolid会调用GO相关的函数。GO函数会输出绘图指令给你的应用程序提供…

映美精黑白相机IFrameQueueBuffer转halcon的HObject

映美精黑白相机&#xff0c;用wpfhalcon开发取图 1.到官网下载&#xff0c;开发包 1sdk 2c开发例子 3c#开发例子 引入TIS.Imaging.ICImagingControl35.dll 3.ICImagingControl使用这个类控制相机 /// <summary> /// 相机控制 /// </summary> public ICImagingC…

《昇思25天学习打卡营第16天|基于MindNLP+MusicGen生成自己的个性化音乐》

MindNLP 原理 MindNLP 是一个自然语言处理&#xff08;NLP&#xff09;框架&#xff0c;用于处理和分析文本数据。 文本预处理&#xff1a;包括去除噪声、分词、词性标注、命名实体识别等步骤&#xff0c;使文本数据格式化并准备好进行进一步分析。 特征提取&#xff1a;将文…

LightRAG:高效构建和优化大型语言模型应用的 PyTorch 框架

一、前言 随着大语言模型 (LLM) 的蓬勃发展&#xff0c;检索增强生成 (RAG) 技术作为一种将 LLM 与外部知识库结合的有效途径&#xff0c;受到了越来越多的关注。 然而&#xff0c;构建 LLM 应用的真正挑战在于开发者需要根据具体需求进行高度定制化&#xff0c;而现有的 RAG …

《向量数据库指南》2024年中国向量数据库名录大全

2024年中国向量数据库名录大全 序号 百科ID 数据库名称 slogan 厂商 来源 开源 类型 1 1228 TensorDB 爱可生向量数据库 上海爱可生信息技术股份有限公司 自研 商业 向量 2 972 Milvus 开源向量数据库 上海赜睿信息科技…

centos安装minio文件系统服务器(踩坑版)

centos安装minio文件系统服务器&#xff08;踩坑版&#xff09; 引安装1. 下载2. 启动3. 创建access keys4. 创建buckets 坑 引 本来安装挺简单的&#xff0c;网上的教程一大堆&#xff0c;有些写的也挺详细的。不过自己还是踩到坑了&#xff0c;耽误了个把小时&#xff0c;特…

【源码+文档+调试讲解】全国消费水平展示平台

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于全国消费水平展示平台当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了全国消费水平展示平台&#xff0c;它彻底…

如何找回误删的文件?4个常用文件恢复方法!

对于许多用户来说&#xff0c;误删文件是一种常见而令人懊恼的情况。恢复误删文件的重要性在于&#xff0c;它可以帮助用户找回宝贵的数据&#xff0c;避免因数据丢失带来的各种不便和损失。 如何找回不小心删除的文件&#xff1f; 误删数据不知道怎么恢复&#xff0c;会给我…

对进阶指针的追忆

目录 思维导图 指针前言 一&#xff1a;字符指针 二&#xff1a;指针数组 三&#xff1a;数组指针 四&#xff1a;数组参数 && 指针参数 五&#xff1a;函数指针 六&#xff1a;函数指针数组 七&#xff1a;函数指针数组的指针 八&#xff1a;回调函数 思维导…

MySql 数据库 (基础) - 下载安装

MySQL数据库 简单介绍 数据库 数据存储的仓库数据库管理系统 操作和管理数据库的大型软件SQL 操作关系型数据库的变成语言&#xff0c;是一套标准 版本 MySQL官方提供了两种不同的版本&#xff1a; 社区版 免费&#xff0c;MySQL不提供任何的技术支持商业版 收费&#xff0c…

暑期备考美国数学竞赛AMC8和AMC10:吃透1850道真题和知识点

距离接下来的AMC8、AMC10美国数学竞赛还有几个月的时间&#xff0c;实践证明&#xff0c;做真题&#xff0c;吃透真题和背后的知识点是备考AMC8、AMC10有效的方法之一。 通过做真题&#xff0c;可以帮助孩子找到真实竞赛的感觉&#xff0c;而且更加贴近比赛的内容&#xff0c;…

谷粒商城实战笔记-29~34-前端基础 - ES6

文章目录 零&#xff0c;安装Live Server插件一&#xff0c;创建前端工程1&#xff0c;创建工程2&#xff0c;在工程ES6中创建一个html文件 二&#xff0c;ES6 简介1&#xff0c;ES6 的历史 三&#xff0c;前端基础ES61&#xff0c;let 和 const1.1&#xff0c;let1.1.1 严格的…

JavaScript青少年简明教程:开发工具与运行环境

JavaScript青少年简明教程&#xff1a;开发工具与运行环境 JavaScript是一种基于对象和事件驱动且具有安全性能的脚本语言。使用它和HTML结合可以开发出交互式的Web页面。 脚本语言是为了缩短传统的编写-编译-链接-运行过程而创建的计算机编程语言。脚本通常是解释执行而非编…

text prompt如何超过77个词

【深度学习】sdwebui的token_counter,update_token_counter,如何超出77个token的限制?对提示词加权的底层实现_prompt中token权重-CSDN博客文章浏览阅读1.6k次,点赞26次,收藏36次。文章探讨了如何在StableDiffusionProcessing中处理超过77个token的提示,涉及token_counte…

Is Temperature the Creativity Parameter of Large Language Models?阅读笔记

最近有小伙伴来问LLM的参数该如何设计&#xff0c;废话不多说来看看paper吧。首先&#xff0c;常见的可以进行调参的几个值有temperature&#xff0c;top-p和top-k。今天这篇文章是关于temperature的。 原文链接&#xff1a;https://arxiv.org/abs/2405.00492 temperature如果…

基于双向长短期记忆 BiLSTM 实现股票单变量时间序列预测(PyTorch版)

前言 系列专栏:【深度学习&#xff1a;算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域&#xff0c;讨论了各种复杂的深度神经网络思想&#xff0c;如卷积神经网络、循环神经网络、生成对…

SpringBoot新手快速入门系列教程十一:基于Docker Compose部署一个最简单分布式服务项目

我的教程都是亲自测试可行才发布的&#xff0c;如果有任何问题欢迎留言或者来群里我每天都会解答。 如果您还对于Docker或者Docker Compose不甚了解&#xff0c;可以劳烦移步到我之前的教程&#xff1a; SpringBoot新手快速入门系列教程九&#xff1a;基于docker容器&#xff…

python | setup.py里有什么?

setup.py里有什么&#xff1f; 文章目录 setup.py里有什么&#xff1f;C/C扩展总结gcc/g的编译参数&#xff1a;Windows Visual StudioCmakesetup.py C/C扩展模块 为什么需要分发打包&#xff1f;Distutils一个简单的例子通用的 Python 术语 使用 Setuptools 构建和分发软件包源…

高铁站客运枢纽IPTV电视系统-盐城高铁站西广场IP电视系统应用浅析

高铁站客运枢纽IPTV电视系统-盐城高铁站西广场IP电视系统应用浅析 由北京海特伟业科技有限公司任洪卓于2024年7月9日发布 随着科技的飞速发展&#xff0c;特别是“互联网”战略的深入推进&#xff0c;高铁站客运枢纽的信息化建设成为提升服务质量、增强乘客体验的重要手段。盐…