一次性搞定分布式限流————手写分布式限流框架

2023-11-02


gitee: https://gitee.com/qiaodaimadewangcai/flood-myth

一、目标和需求分析

为了框架能满足当代互联网的基本需求,和使用的便利,优先实现以下几点需求。

  1. 支持分布式
  2. 支持SpringBoot-start
  3. 支持失败回调
  4. 单机模式下支持微秒级响应,分布式模式下支持毫秒级响应

其余还有写其他值得讨论实现的内容,优先级不是最高暂且先放着,我们以后有空再实现

  1. 支持高可靠性
  2. 支持监控
  3. 支持动态调节
  4. 支持持久化

二、初步设计

采用中心化的方式支持分布式,框架分成2部分“客户端”与”服务端”,为了方便还是将两部分写在一个工程中,客户端与服务端通过http进行通讯

三、客户端的实现

1)限流器参数定义

参数定义都是简单的pojo,不做过多说明,具体看代码

(1)限流规则定义

主要定义令牌桶的参数、限流器的运行模式和行为模式
https://gitee.com/qiaodaimadewangcai/flood-myth/blob/master/src/main/java/com/gyx/floodmyth/entity/LimiterRuleWrapper.java

(2)客户端配置定义

是一个单例模式,负责记录服务器的信息,包含一个线程池,用于向服务器发送心跳,并且拉取服务器上的信息
https://gitee.com/qiaodaimadewangcai/flood-myth/blob/master/src/main/java/com/gyx/floodmyth/entity/LimiterConfigWrapper.java

2)限流器实现

每个限流器都对应一个限流规则、一个客户端配置。

限流器中最重要的是实现一个限流算法,目前比较流行的几种限流算法——滑窗、漏桶、令牌桶。这里采用令牌桶限流。

令牌桶的实现主要包括2个部分

  • 填装令牌
  • 消耗令牌

整个限流器其实都是在令牌桶的实现上添加了一些功能

(1)接口定义

限流器主要考虑初始化方法、尝试访问限流的方法

public interface LimiterHandler {
    /**
     * 初始化
     * @param rule 限流规则的包装器
     */
    void init(LimiterRuleWrapper rule);
    /**
     * 尝试访问
     * @param tokenNum 消耗的令牌数量
     */
    boolean tryAccess(Integer tokenNum);
    /**
     * 获取限流规则标识
     */
    String getId();
    /**
     * 获取限流规则
     */
    LimiterRuleWrapper getRule();
}
(2)限流器的抽象实现

我们想看成员变量的部分,除了对限流规则、客户端配置的持有,包括一个令牌的计数器(bucket),一个令牌桶的填装器(scheduledFuture)。

  • 令牌计数器,会有多个线程频繁的读写,使用atomic包下的对象,保证线程安全

  • 令牌装填器是一个定时器,会按照配置定时增加令牌计数器,仅在单机的模式下会用到,分布式的时候令牌的填装的工作会移交给服务器

public abstract class AbstractLimiterHandler implements LimiterHandler {
    /**
     * 令牌桶
     * 初始容量为0
     */
    protected final AtomicLong bucket = new AtomicLong(0);
    /**
     * 限流规则
     */
    protected LimiterRuleWrapper rule;
    /**
     * 限流器集群配置
     */
    protected LimiterConfigWrapper config;
    /**
     * 令牌装填器
     *
     * 用于给令牌桶补充令牌
     */
    protected ScheduledFuture<?> scheduledFuture;
}

限流器初始化的时候,必须填入限流规则和客户端配置,并且会停止令牌桶的装填

在限流规则发生改变的时候,可以单独调用init方法,以便用新的规则替换旧的规则

public AbstractLimiterHandler(LimiterRuleWrapper rule, LimiterConfigWrapper config) {
    this.config = config;
    init(rule);
}

/**
 * 初始化
 * @param rule 限流规则的包装器
 */
@Override
public void init(LimiterRuleWrapper rule) {
    this.rule = rule;
    if (this.scheduledFuture != null) {
        this.scheduledFuture.cancel(true);
    }
}

尝试访问的方法会消耗令牌,当limit==0的时候,意味着不会填装令牌,所以直接返回false。

AccessStrategy是一个访问策略接口,这里使用了策略模式,提供2中访问策略

  • 快速失败策略
  • 阻塞策略
/**
 * 尝试访问
 *
 * @param tokenNum 消耗的令牌数量
 */
@Override
public boolean tryAccess(Integer tokenNum) {
    if (rule.isEnable()) {
        //限流功能已关闭
        return true;
    }
   if (rule.getLimit() == 0) {
        return false;
    }
    return AccessStrategy.strategy.get(rule.getAccessModel()).tryAccess(bucket, rule,tokenNum);
}

getId方法、getRule只是简单的get方法,前一个返回rule的id,后一个返回rule。

(2)访问策略实现

访问策略一共有2种,代码结构上通过策略模式进行解耦,以满足开闭原则。
接口中包含一个静态变量,和一个方法。静态变量实际上是一个简单工厂,用来初始化和访问不同的策略。

public interface AccessStrategy {
    /**
     * 用于访问策略
     */
    Map<AccessModel, AccessStrategy> strategy = new HashMap<AccessModel, AccessStrategy>(2) {{
        put(AccessModel.FAIL_FAST, new FailFastAccess());
        put(AccessModel.BLOCKING, new BlockingAccess());
    }};

    /**
     * 尝试访问
     *
     * @param bucket   令牌桶
     * @param rule     限流器规则
     * @param tokenNum 消耗的令牌数量
     */
    boolean tryAccess(AtomicLong bucket, LimiterRuleWrapper rule, Integer tokenNum);
}

快速失败访问策略,令牌不够立马失败,返回失败

public class FailFastAccess implements AccessStrategy {
    @Override
    public boolean tryAccess(AtomicLong bucket, LimiterRuleWrapper rule, Integer tokenNum) {
        //CAS获取令牌,没有令牌立即失败
        long l = bucket.longValue();
        while (l >= tokenNum) {
            if (bucket.compareAndSet(l, l - tokenNum)) {
                return true;
            }
            l = bucket.longValue();
        }
        return false;
    }
}

阻塞访问策略,令牌不够的时候,阻塞线程,直到令牌足够

public class BlockingAccess implements AccessStrategy {
    @Override
    public boolean tryAccess(AtomicLong bucket, LimiterRuleWrapper rule,Integer tokenNum) {
        //CAS获取令牌,阻塞直到成功
        long l = bucket.longValue();
        while (!(l >= tokenNum && bucket.compareAndSet(l, l - tokenNum))) {
            sleep(rule);
            l = bucket.longValue();
        }
        return true;
    }
    /**  sleep方法 **/
}
(3)单机限流器的实现

单机限流器继承抽象限流器,也是通过父类的构造器进行初始化,这里需要注意父类的构造器中会调用init初始化方法,但是实际执行的init并非父类中的init方法,而是子类重写的init方法。

public class LocalLimiterHandler extends AbstractLimiterHandler {

    public LocalLimiterHandler(LimiterRuleWrapper rule, LimiterConfigWrapper config) {
        super(rule, config);
    }
}

重写父类中的init方法,在父类的基础上额外初始化定时器,将限流规则中的参数,填入到线程池中就行了,定时器会按照指定的周期,定时的装填令牌。

/** * 初始化 * @param rule 限流规则的包装器 */@Overridepublic void init(LimiterRuleWrapper rule) {    super.init(rule);    if (rule.getLimit() == 0) {        return;    }    this.scheduledFuture = config.getScheduledThreadExecutor()            .scheduleAtFixedRate(() -> {                //当前的令牌数 + 每次填装的令牌数  < 最大令牌数                if (bucket.get() + rule.getLimit() < rule.getMaxLimit()) {                    bucket.set(rule.getLimit());                }                //首次延迟时间、周期单位时间、时间单位            }, rule.getInitialDelay(), rule.getPeriod(), rule.getUnit());}

其他方法都在抽象类中已经实现了不需要修改。

(4)分布式限流器的实现

分布式限流器一样继承抽象限流器,但是无需重写init方法,分布式限流器的令牌填装是通过与服务器连接完成的,所以不需要初始化定时器。

public class CloudLimiterHandler extends AbstractLimiterHandler {    public CloudLimiterHandler(LimiterRuleWrapper rule, LimiterConfigWrapper config) {        super(rule, config);    }}

重写尝试访问的方法,分布式消耗令牌的逻辑也是在客户端上实现的,和单机的逻辑没有区别,在尝试访问结束之后,会访问服务器获取令牌,填装令牌桶。

/** * 尝试访问 * * @param tokenNum 消耗的令牌数量 */@Overridepublic boolean tryAccess(Integer tokenNum) {    boolean accessFlag = super.tryAccess(tokenNum);    putCloudBucket();    return accessFlag;}

获取令牌的方法,看似繁琐,实际上只是用客户端配置中的定时器执行一个http请求,获取到令牌后填装到令牌桶中,其余的全是判断,中间用到一个经典的双重检查锁。

/** * 从集群令牌分发中心,获取令牌,填装到令牌桶中 */private void putCloudBucket() {    //校验令牌数量是否需要获取    if (bucket.get() * rule.getBatch() > rule.getRemaining()) {        return;    }    //获取定时器线程    config.getScheduledThreadExecutor().execute(() -> {        //双重检查锁  第一层        if (bucket.get() * rule.getBatch() <= rule.getRemaining()) {            //双重检查锁  上锁            synchronized (bucket) {                //双重检查锁 第二层                if (bucket.get() * rule.getBatch() <= rule.getRemaining()) {                    //发送http获取令牌,然后填装到令牌桶中                    String result = config.getAllotServer().connect(LimiterConfigWrapper.http_token, JSON.toJSONString(rule));                    if (result != null) {                        bucket.getAndAdd(Long.parseLong(result));                    }                }            }        }    });}

看完上面的代码肯定对AllotServer还不太清楚,AllotServer是对服务器资源访问和管理的类,接下来就一起看一下这个类。

3)分发服务器管理类实现

AllotServer用于记录服务器的地址,和提供相应的访问方法。一共4个成员变量

  • serverList——用于记录令牌分发服务器地址,读多写少
  • backupsList——地址的备份,当服务器访问失败的后,地址会被转移到这里
  • lock——读写锁,防止读取的地址的时候,地址发送变化
  • pos——用于轮询的计数器
public class AllotServer {    private List<String> serverList = new CopyOnWriteArrayList<>();    private List<String> backupsList = new CopyOnWriteArrayList<>();    private ReentrantLock lock = new ReentrantLock();    private int pos = 0;}

设置令牌分发服务器,map的key是服务器的ip地址,value是服务器的权重,先清空原本的列表,然后模仿CopyOnWriteArrayList,通过复制避免并发问题。
添加权重的方式,也采用比较偷懒的方式,像list中添加重复元素,权重越高的元素,重复的次数越多

public void setServer(Map<String, Integer> ip) {    // 清空List    serverList.clear();    // 重建一个Map,避免服务器的上下线导致的并发问题    Map<String, Integer> serverMap = new HashMap<>(ip);    // 取得Ip地址List    for (String server : serverMap.keySet()) {        int weight = serverMap.get(server);        //添加权重        for (int i = 0; i < weight; i++) {            serverList.add(server);        }    }}

获取服务器方法,获取服务器地址的时候需要上锁,防止冲突,当地址全部失效后,从之前失效的地址再次尝试连接,通过轮询的方式对服务器进行访问。

private String getServer() {    String server;    lock.lock();    try {        if (serverList.size()==0){            serverList.addAll(backupsList);            backupsList.clear();        }        if (pos >= serverList.size()) {            pos = 0;        }        server = serverList.get(pos);        pos++;    } finally {        lock.unlock();    }    return server;}

访问服务器的方法,非常简单向服务器发送一个http请求就行了

public String connect(String path, String data) {
    String server = getServer();
    try {
        return HttpUtil.connect("http://" + server + "/" + path)
                .setData("data", data)
                .setMethod("POST")
                .execute()
                .getBody();
    } catch (IOException e) {
        serverList.remove(server);
        backupsList.add(server);
    }
    return null;
}

4)限流器注册实现

限流器注册过程包括2部分,限流器的构造工厂和注册器。

(1)限流器构造工厂

限流器的构造工厂很简单,通过简单工厂进行创建,然后向注册器注册

public class LimiterFactory {

    public static LimiterHandler of(LimiterRuleWrapper rule) {
        return of(rule, LimiterConfigWrapper.getInstance());
    }

    public static LimiterHandler of(LimiterRuleWrapper rule, LimiterConfigWrapper config) {
        switch (rule.getLimiterModel()) {
            case LOCAL:
                //本地限流
                LimiterHandler limiter = new LocalLimiterHandler(rule, config);
                RegisterServer.registered(limiter);
                return limiter;
            case CLOUD:
                //集群限流
                limiter = new CloudLimiterHandler(rule, config);
                rule.setName(rule.getName() == null ? String.valueOf(limiter.hashCode()) : rule.getName());
                RegisterServer.registered(limiter, config);
                return limiter;
            default:
                throw new RuntimeException("无法识别限流处理器运行模式");
        }
    }
}
(2)限流注册器

注册器主要用于缓存所有的限流器,并且提供相应的访问方法

注册器通过一个map缓存所有的限流器,key为id,value为限流器实例。ConcurrentHashMap保证线程安全

public class RegisterServer {
    /**
     * 限流处理器的容器
     */
    private static Map<String, LimiterHandler> limiterContainer = new ConcurrentHashMap<>();
}

提供一个静态方法,可以很方便的访问所有的限流器

public static LimiterHandler get(String id) {
    LimiterHandler limiterHandler = limiterContainer.get(id);
    if (limiterHandler == null){
        throw new RuntimeException("无法查询到处理");
    }
    return limiterHandler;
}

本地限流器的注册非常简单,放入map就行了

public static void registered(LimiterHandler limiter) {
    if (limiterContainer.containsKey(limiter.getId())) {
        throw new RuntimeException("不可以重复注册限流处理器,限流器id:" + limiter.getId());
    }
    limiterContainer.put(limiter.getId(), limiter);
}

分布式限流器注册除了需要将对象存入map,还需要想服务器发出请求,同步服务器上的限流规则,如果连接失败,会转成本地服务运行。

/**
 * 分布式注册
 *
 * @param limiter 限流处理器
 * @param config  限流器配置包装类
 */
public static void registered(LimiterHandler limiter, LimiterConfigWrapper config) {
    //注册在本地
    registered(limiter);
    //从令牌中心拉取规则,更新本地限流规则
    rulePull(limiter, config);
}

/**
 * 从令牌中心拉取规则,更新本地限流规则
 *
 * @param limiter 限流处理器
 * @param config  限流器配置包装类
 */
private static void rulePull(LimiterHandler limiter, LimiterConfigWrapper config) {
    config.getScheduledThreadExecutor().scheduleWithFixedDelay(() -> {
        //连接远程获取配置
        String rules = config.getAllotServer().connect(LimiterConfigWrapper.http_heart, JSON.toJSONString(limiter.getRule()));
        if (rules == null) {
            //连接失败,转成本地模式运行
            LimiterRuleWrapper rule = limiter.getRule();
            rule.setLimiterModel(LimiterModel.LOCAL);
            limiter.init(rule);
            return;
        }
        LimiterRuleWrapper newestRule = JSON.parseObject(rules, LimiterRuleWrapper.class);
        if (newestRule.getVersion() > limiter.getRule().getVersion()) {
            //版本升级
            if (newestRule.getLimiterModel().equals(LimiterModel.LOCAL)) {
                //禁止改成本地模式
                newestRule.setLimiterModel(LimiterModel.CLOUD);
            }
            //更新规则
            limiterContainer.get(limiter.getId()).init(newestRule);
        }
    }, 0, 1, TimeUnit.SECONDS);
}

5)注解实现

通过注解对接口进行限流,被注解的方法就会访问限流器进行限流,如果限流失败会调用指定的回调方法

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface Limiter {
    /**
     * Limiter id
     */
    String value() default "";
    /**
     * 令牌消耗数量
     */
    int num() default 1;
    /**
     * 回调方法
     */
    String fallback() default "";
}

注解的实现很简单,通过环绕切面,将限流器调用包裹在目标方法外,如果执行失败,就调用回调方法,这个的回调方法比较简单,所以回调方法必须和注解注释的方法在同一个类中,并且参数完全一致

@Aspect
public class LimiterAspect {

    @Pointcut("@annotation(com.gyx.floodmyth.aspect.Limiter)")
    public void pointcut() {
    }

    @Around("pointcut() && @annotation(limiter)")
    public Object around(ProceedingJoinPoint pjp, Limiter limiter) throws Throwable {
        LimiterHandler rateLimiter = RegisterServer.get(limiter.value());
        if (rateLimiter.tryAccess(limiter.num())) {
            return pjp.proceed();
        }
        //快速失败后的回调方法
        return fallback(pjp, limiter);
    }

    /**
     * 快速失败的回调方法
     * @param pjp     切入点
     * @param limiter 注解数据
     */
    private Object fallback(ProceedingJoinPoint pjp, Limiter limiter) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Signature sig = pjp.getSignature();
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("此注解只能使用在方法上");
        }
        //回调方法必须和注解注释的方法在同一个类中,并且参数完全一致
        MethodSignature msg = (MethodSignature) sig;
        Object target = pjp.getTarget();
        Method fallback = target.getClass().getMethod(limiter.fallback(), msg.getParameterTypes());
        return fallback.invoke(target, pjp.getArgs());
    }
}

四、服务器实现

未完

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一次性搞定分布式限流————手写分布式限流框架 的相关文章

  • JPA 实体中的方法是否允许抛出异常?

    我尝试创建的 Entity 有问题 当尝试使用 OpenJPA 实现在 Eclipse 中测试类时出现问题 我有not尝试过其他人 所以不确定它是否适用于他们 我的测试用例非常简单 因为它创建一个 EntityManagerFactory
  • 在 Java 中重置 Graphics2D 对象

    我正在用 Java 尝试 Graphics2D 但像往常一样 我被困住了 P 问题是 假设我有这个代码 Graphics2D g Graphics2D this getGraphics Inside a JFrame g rotate Ma
  • Windows 上的虚假唤醒。是否可以?

    我最近学习了 虚假唤醒 有人说这个问题只可能发生在某些类型的 Linux PC 上 我用的是窗户 我为虚假唤醒编写了测试 我得到的结果是这是可能的 但我想向您展示这个测试 也许我在某个地方犯了错误 我的初始变体 import java ut
  • 定制法国号码格式

    我尝试为美国国家 地区使用自定义数字格式 到目前为止效果很好 Not something I want NumberFormat numberFormat0 NumberFormat getNumberInstance Locale US
  • 修复 java 内存泄漏的学习网站

    学习修复 java 内存泄漏的最佳地点是什么 我一直试图在网络上找到好的资源 但令我失望的是 我发现正在讨论玩具示例 我还能够对小型玩具转储进行故障排除 但现实世界的应用程序转储更具挑战性 并且提供的线索很少 我尝试过 Jhat JMap
  • JSP重定向和传值

    我有一个 JSP 其中我重定向到另一个 jsp 例如 我在该jsp中没有任何其他数据 我想将值从该jsp index jsp 传递到重定向jsp login jsp 我将如何做到这一点 这里的 logonInput 是在struts con
  • 在 Jenkins 内运行构建时,我收到“java/lang/OutOfMemoryError”

    2020 02 25 10 11 24 986 0000 id 79 信息hudson model AsyncPeriodicWork lambda doRun 0 开始maven repo cleanup 2020 02 25 10 11
  • 仅使用 ServletContext 查找应用程序的 URL

    我正在使用 Spring MVC 编写一个 Java Web 应用程序 我有一个后台进程 它会遍历数据库并查找必须通过电子邮件发送给我的用户的通知 这些电子邮件需要包含应用程序的超链接 对于网络应用程序来说 这似乎是相当常见的模式 但我遇到
  • Android - Java - 发送 facebook 聊天消息的意图(facebook 禁用 xmpp)

    Facebook 已弃用 xmpp API 有没有办法打开意图 或将数据传递到fb 以在Android设备上发送聊天消息 设备上安装的 Facebook 和 Messenger 应用 谢谢 您需要将 uri 传递给意图 这里10000572
  • Java 创建 Thread 实例时会发生什么

    我有一个关于 Java 线程和操作系统线程的问题 我读了Java 线程与 Pthreads https stackoverflow com questions 5269535 java threads vs pthreads and Jav
  • WSDL 表示中的枚举类型

    WSDL 表示如下
  • 按位非运算符

    为什么要按位运算 0 打印 1 在二进制中 不是0应该是1 为什么 你实际上很接近 在二进制中 不是0应该是1 是的 当我们谈论一位时 这是绝对正确的 然而 一个int其值为0的实际上是32位全零 将所有 32 个 0 反转为 32 个 1
  • 从外部 clojar 导入/使用资源

    我想做的是将一个大文件 MIDI 声音字体 打包到一个独立的 Maven repo clojar 中 然后能够以编程方式将其拉下来并从单独的项目中使用它 事实证明 这个看似简单的任务比我想象的要复杂 理想的情况是 如果有一种方法可以直接访问
  • SOAP Web 服务中的用户身份验证

    我提出了一个关于JAX WS 身份验证和授权 如何 https stackoverflow com questions 5314782 jax ws authentication and authorization how to 讨论了安全
  • 如何在 Eclipse 中使用 Hibernate Tools 生成 DAO?

    我在用着 Eclipse Java EE IDE Web 开发人员 版本 Indigo 发布 使用 hibernate 工具 我对 Eclipse 中的 hibernate 很陌生 所以我学习如何配置 hibernate 并使用注释生成 P
  • 在Java中一个接一个地播放WAV文件

    我正在尝试玩几个WAV http en wikipedia org wiki WAV文件一个接一个 我尝试了这个方法 for String file audioFiles new AePlayWave file start 但这会同时播放它
  • GAE - Eclipse 中的开发服务器未更新?

    我在 Eclipse 上使用 Google AppEngine 开发服务器 我的本地网页似乎没有更新 直到我在开发服务器上进行了多次重新启动 使用 Eclipse 中的 运行 或 调试 按钮 我究竟做错了什么 基本流程是 更改 java 文
  • 当相应的 JTextfield 为空时,如何填充 JTable 中的所有项目

    我正在 Java 项目中设计一个高级搜索选项sqlite在 NetBeans 中 有5种不同JTextfields和 5 列 我想填充JTable具有相应的匹配标准 如果一个JTextfield为空 那么它应该选择该列的所有项目 我使用的查
  • Android:如何以编程方式仅圆化位图的顶角?

    我目前正在使用这段代码 Override public Bitmap transform Bitmap source Bitmap result Bitmap createBitmap source getWidth source getH
  • PostgreSQL 使用 JPA 和 Hibernate 抛出“列的类型为 jsonb,但表达式的类型为 bytea”

    这是我的实体类 映射到表中postgres 9 4 我正在尝试将元数据存储为jsonb在数据库中输入 Entity Table name room categories TypeDef name jsonb typeClass JsonBi

随机推荐

  • 判断字符串的两半是否相似

    给你一个偶数长度的字符串 s 将其拆分成长度相同的两半 前一半为 a 后一半为 b 两个字符串 相似 的前提是它们都含有相同数目的元音 a e i o u A E I O U 注意 s 可能同时含有大写和小写字母 如果 a 和 b 相似 返
  • 现代处理器性能分析及优化-序

    一直以来 工程师都有一个观念 性能为王 以前是 现在是 以后更是 根据 Data Never Sleeps 5 0 调查研究 世界上每天产生2 5万亿字节的数据 并且保持着每年25 的速度递增 在我们如今的社会生活中 信息产生的来源越来越多
  • pgbench和sysbench初次压测PG集群

    pgbench和sysbench初次压测PG集群 pgbench和sysbench是两个不同的压测工具 前者只适用于pg数据库 后者可以适用于mysql pg sql server等常见关系型数据库 若是关于tps的测试 建议采用sysbe
  • tomcat配置

    1 概念 参数名 描述 maxThreads 每一次HTTP请求到达Web服务 tomcat都会创建一个线程来处理该请求 那么最大线程数决定了Web服务容器可以同时处理多少个请求 maxThreads默认200 肯定建议增加 但是 增加线程
  • A Comprehensive Survey of Dataset Distillation

    本文是蒸馏学习综述系列的第三篇文章 A Comprehensive Survey of Dataset Distillation 的一个翻译 数据集蒸馏综述 摘要 1 引言 2 背景 2 1 形式化数据集蒸馏 3 元学习框架 3 1 通过时
  • 【自然语言处理】关系抽取 —— GDPNet 讲解

    GDPNet 论文信息 标题 GDPNet Refining Latent Multi View Graph for Relation Extraction 作者 Fuzhao Xue Aixin Sun Hao Zhang Eng Sio
  • Elasticsearch插件开发与调试

    背景 elasticsearch version 6 8 5 插件开发类型 elasticsearch 变更数据记录 插件开发 略 完善后开源 插件debug 远程debug 1 准备发行版的ES 下载 解压 2 将开发好的插件 zip包
  • Python基础知识(三):Python错误、警告、异常处理总结

    1 语法错误 Python 的语法错误或者称之为解析错 是初学者经常碰到的 如下实例 gt gt gt while True print Hello world File
  • STM32——理解中断与中断配置

    前言 本文将从 这是什么 为什么需要它 如何配置操作它 三个角度展开讨论分析 目录 中断简介 抢占优先级和子优先级 中断分组 配置要点 EXTI EXTI框图讲解 信号产生过程 编程要点 中断简介 中断 即机器运行过程中出现某些意外情况 需
  • XBOX怎么查保修期限

    网站 Microsoft 帐户 设备https account microsoft com devices fref home drawers devices manage devices refd account microsoft co
  • python实战爬取招聘网站职位数据

    大家都知道金三银四是每年的求职高峰期 是中国招聘市场中最热门的季节之一 这段时间内 许多公司会发布大量的招聘信息 吸引大批求职者前来应聘 同时 也有许多人选择这个时候跳槽 因为这个时候找到新工作的机会相对较大 python 疫情放开后感觉求
  • (websocket)协议中Ping Pong,Socket通讯ping pong(长连接),心跳包

    Socket读写数据 流Socket 数据包Socket 1 流套接字 SOCK STREAM 流套接字用于提供面向连接 可靠的数据传输服务 该服务将保证数据能够实现无差错 无重复发送 并按顺序接收 流套接字之所以能够实现可靠的数据服务 原
  • 华硕天选无法识别蓝牙

    系列文章目录 文章目录 系列文章目录 前言 一 解决 二 链接蓝牙 三 搜索蓝牙 前言 无法连接蓝牙 一 解决 电脑桌面右下角 点开 二 链接蓝牙 三 搜索蓝牙
  • sql中join与left-join图解区别

    t1表内容如下 t2表内容如下 下面来简述join和left join right join的区别 inner join select from t1 inner join t2 on t1 id t2 id 公共部分的数据才会被查询出来
  • matlab判断两个数据是否相等的相关问题

    在matlab中如何判断两个数据的值是否相等呢 我们都知道通常使用isequal 方法 调用方法如下 tf isequal A B 然而数据在什么情况下是相等的呢 我们看如下情况 x 1 38389652673674e 20 y 1 383
  • ubuntu 交叉编译qt 5.7 程序到 arm 开发板

    ubuntu 交叉编译qt 5 7 程序到 arm 开发板 平台 1 ubuntu 12 04 2 arm linux gcc 4 5 1 3 QT 5 7 4 开发板210 armcortex A8 一 概述 QT5的ARM移植及其中文显
  • 内存泄露、内存溢出以及解决方法

    本文转自 http blog csdn net cjf iceking article details 7552802 内存泄露是指程序在运行过程中动态申请的内存空间不再使用后没有及时释放 从而很可能导致应用程序内存无线增长 更广义的内存泄
  • 语义分割、实例分割和全景分割的区别

    之前看过一篇使用分割思想进行目标检测 所以这里补习下一些分割相关的基础知识 这里重点说下语义分割 实力分割和全景分割的区别 1 semantic segmentation 语义分割 通常意义上的目标分割指的就是语义分割 图像语义分割 简而言
  • 【计算机网络】TCP超时重传时间的选择

    由于TCP下层是互联网环境 发送的报文会经过一个高速率的局域网 也可能经过多个低速率的网络 并且每个IP报文所选择的路由器还可能不同 那么问题就来了 如果把超时重传的时间设置得太短 就会引起很多报文产生不必要的重传 但如果把超时重传的时间设
  • 一次性搞定分布式限流————手写分布式限流框架

    目录 一 目标和需求分析 二 初步设计 三 客户端的实现 1 限流器参数定义 1 限流规则定义 2 客户端配置定义 2 限流器实现 1 接口定义 2 限流器的抽象实现 2 访问策略实现 3 单机限流器的实现 4 分布式限流器的实现 3 分发