Java:构建简单的速率限制器

速率限制

现实世界中的用户是残暴的,并且没耐心,充满着各种不确定性。在高并发系统中,可能会出现服务器被虚假请求轰炸的情况,因此您可能希望控制这种情况。

一些实际使用情形可能如下所示:

  1. API配额管理-作为提供者,您可能希望根据用户的付款情况限制向服务器发出API请求的速率。这可以在客户端或服务端实现。
  2. 安全性-防止DDOS攻击。
  3. 成本控制--这对服务方甚至客户方来说都不是必需的。如果某个组件以非常高的速率发出一个事件,它可能有助于控制它,它可能有助于控制从客户端发送的遥测。

限速处理时的选项

根据我们处理的请求/事件类型,可能会发生以下情况:

  1. 我们可以放弃额外的请求
  2. 我们可以选择让请求等待,直到系统将它们降低到预定义的速率。

常用限速算法

  1. 令牌桶算法
  2. 漏桶算法

我们将不深入讨论这些算法的内部细节,因为这超出了本文的范围。

我们将以令牌桶算法为中心。其要求如下。

令牌桶算法基于以固定速率添加令牌的固定容量桶的类比。在允许API继续之前,将检查桶,以查看它当时是否包含至少一个令牌。如果令牌存在,则进行API调用。如果不是,则丢弃该消息/或使其等待。

需求

  1. 应该能够接受每秒所需的(TPS)事务或速率。
  2. 如果超过我们定义的比率,则应放弃交易。
  3. 应该在同时发生的情况下起作用。

高级功能(在后续文章中实现)

  1. 应该能够平滑突发的请求。例如,如果我们将TPS定义为5,并且所有五个请求都在同一时刻到达,那么它应该能够以固定的时间间隔将它们排成一行,即以200ms的时间间隔执行每个请求。它需要一个内部定时电路。
  2. 如果我们的TPS为5,并且在其中一个1秒的时段中,我们在下一秒只使用3个代币,那么我们应该能够提供5+2 = 7个代币作为奖励。但速率为每个令牌1/7(142.28ms)。奖金不应结转到下一个插槽。

让我们首先定义我们的 速率限制器

/**
 * Rate limiter helps in limiting the rate of execution of a piece of code. The rate is defined in terms of
 * TPS(Transactions per second). Rate of 5 would suggest, 5 transactions/second. Transaction could be a DB call, API call,
 * or a simple function call.
 * <p>
 * Every {@link RateLimiter} implementation should implement either {@link RateLimiter#throttle(Code)} or, {@link RateLimiter#enter()}.
 * They can also choose to implement all.
 * <p>
 * {@link Code} represents a piece of code that needs to be rate limited. It could be a function call, if the code to be rate limited
 * spreads across multiple functions, we need to use entry() and exit() contract.
 */
public interface RateLimiter {

/**
     * Rate limits the code passed inside as an argument.
     *
     * @param code representation of the piece of code that needs to be rate limited.
     * @return true if executed, false otherwise.
     */
    boolean throttle(Code code);
    /**
     * When the piece of code that needs to be rate limited cannot be represented as a contiguous
     * code, then entry() should be used before we start executing the code. This brings the code inside the rate
     * limiting boundaries.
     *
     * @return true if the code will execute and false if rate limited.
     * <p
     */
    boolean enter();
    /**
     * Interface to represent a contiguous piece of code that needs to be rate limited.
     */
    interface Code {
        /**
         * Calling this function should execute the code that is delegated to this interface.
         */
        void invoke();
    }
}
复制代码

我们的 RateLimit有两组API:一个是throttle(code),另一个是enter()。这两种方法都满足相同的功能,但采用以下两种方式:

  1. boolean throttle(代码)-如果我们有连续的代码,可以用来传递一个代码块。
  2. 布尔输入() - 通常可以在API、DB或任何我们想要节流的调用之前使用。如果执行此代码后面的代码,则将返回 ,以及 假的如果它是速率受限的话。您可以将这些请求排队或拒绝。

在生产环境中您永远不会看到节流(代码)实现,因为它不是最佳的。请在评论中告诉我原因。大多数速率限制器使用类似于enter()的API

核心功能

为了构建速率限制器的核心,我们需要确保在任意两秒之间不允许超过N个事务。我们将如何做到这一点?

考虑我们进行第一笔交易的时刻t0。 t0 .所以, 直到(t0 + 1)s,我们只允许进行N次交易。 (t0 + 1)s , we are allowed to make only N transactions.如何确保这一点?在下次交易时,我们将检查 当前时间≤(t0 + 1)。.如果没有,那么这意味着我们进入了不同的秒,并且我们被允许进行N次交易。 N transactions.让我们看一小段代码,它演示了:

long now = System.nanoTime();
if (now <= mNextSecondBoundary) { // If we are within the time limit of current second
    if (mCounter < N) { // If token available
        mLastExecutionNanos = now;
        mCounter++; // Allocate token
        invoke(code); // Invoke the code passed the throttle method.
    }
}
复制代码

那么,我们如何定义mNextSecondBoundary呢?这将在我们进行第一个事务时完成,如前所述,我们将在完成第一个事务的时刻增加一秒。

if (mLastExecutionNanos == 0L) {
    mCounter++; // Allocate the very first token here.
    mLastExecutionNanos = System.nanoTime();
    mNextSecondBoundary = mLastExecutionNanos + NANO_PER_SEC;  // (10^9)
}
复制代码

现在,如果我们执行代码并看到我们进入了不同的秒,我们应该怎么做?我们将通过重置上次执行时间、可用令牌数来增强前面的代码,并通过调用 节流阀()再一次。我们的方法已经知道如何处理新的秒。

@Override
public boolean throttle(Code code) {
    if (mTPS <= 0) {
        // We do not want anything to pass.
        return false;
    }

synchronized (mLock) {
        if (mLastExecutionNanos == 0L) {
            mCounter++;
            mLastExecutionNanos = System.nanoTime();
            mNextSecondBoundary = mLastExecutionNanos + NANO_PER_SEC;
            invoke(code);
            return true;
        } else {
            long now = System.nanoTime();
            if (now <= mNextSecondBoundary) {
                if (mCounter < mTPS) {
                    mLastExecutionNanos = now;
                    mCounter++;
                    invoke(code);
                    return true;
                } else {
                    return false;
                }
            } else {
                // Reset the counter as we in a different second now.
                mCounter = 0;
                mLastExecutionNanos = 0L;
                mNextSecondBoundary = 0L;
                return throttle(code);
            }
        }
    }
}
复制代码

在这个实现中,我们可以传递需要节流的代码块,但是这个代码有一个问题。这将工作,但它会表现不佳。不推荐,但为什么呢?请在评论中告诉我。

现在,可以使用相同的构建块和enter()构建第二个API。我们将使用相同的逻辑,但我们不会执行方法内部的代码块。相反,它将在调用enter()之后执行,就像我们执行状态管理一样。该方法的实现如下:

@Override
public boolean enter() {
    if (mTPS == 0L) {
        return false;
    }

synchronized (mBoundaryLock) {
        if (mLastExecutionNanos == 0L) {
            mLastExecutionNanos = System.nanoTime();
            mCounter++;
            mNextSecondBoundary = mLastExecutionNanos + NANO_PER_SEC;
            return true;
        } else {
            long now = System.nanoTime();
            if (now <= mNextSecondBoundary) {
                if (mCounter < mTPS) {
                    mLastExecutionNanos = now;
                    mCounter++;
                    return true;
                } else return false;
            } else {
                // Reset the counter as we in a different second now.
                mCounter = 0;
                mLastExecutionNanos = 0L;
                mNextSecondBoundary = 0L;
                return enter();
            }
        }
    }
}
复制代码

现在,我们简单的速率限制器已经可以使用了。您可以查看完整的代码 这里。

结果

我们将尝试创建一个可创建六个线程的驱动程序代码。每个线程尝试从0到100计数,延迟为50ms(可以设置为任何数字)。我们将按如下方式启动我们的限速器:

public static void main(String[] args) {
    RateLimiter limiter = new SimpleTokenBucketRateLimiter(1);
    Thread[] group = new Thread[6];
    Runnable r = () -> {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (limiter.enter()) {
                System.out.println("Values:- " + Thread.currentThread().getName() + ": " + i);
            }
        }
    };


for (int i = 0; i < 6; i++) {
        group[i] = new Thread(r);
        group[i].start();
    }
}
复制代码

我们的API不支持平滑事务,而是让事务等待下一个令牌被分配,而不是丢弃请求。在拒绝它们之后,它返回false,所以如果我们真的想的话,我们可以把它们排队。

if (limiter.enter()) {
                System.out.println("Values:- " + Thread.currentThread().getName() + ": " + i);
} else { // queue the work again }
复制代码

这是TPS设置为1时的输出。

当我们尝试将TPS设置为 2我们将看到以下输出:

真管用!

从Android的角度看

  1. 考虑这样一种情况:您正在编写代码以捕获用户签名。当他们拖动指针时,您会捕获数千个点。平滑签名可能不需要所有这些参数,因此您使用速率限制进行采样。
  2. 一些事件调用频率很高。你能控制的。
  3. 我们有MessageQueue的空闲侦听器。当我们在主线程中侦听它时,它被随意调用。有时候,它在一秒钟内被调用好几次。如果我们想构建一个心跳系统来告诉我们主线程何时空闲,我们可以使用它来接收每秒的事件。如果我们一秒钟内没有收到事件,我们可以假定主线程处于忙碌状态。
  4. 对于您的框架/库的API配额管理,您可以根据用户选择的付款计划情况API调用。

今天先到这里吧。 我们将在后续文章中构建一个更复杂的速率限制器。

本文转载于网络 如有侵权请联系删除

相关文章

  • 解读 MySQL Client/Server Protocol: Connection & Replication

    解读MySQLClient/ServerProtocol:Connection&ReplicationMySQL客户端与服务器之间的通信基于特定的TCP协议,本文将会详解其中的Connection和Replication部分,这两个部分分别对应的是客户端与服务器建立连接、完成认证鉴权,以及客户端注册成为一个slave并获取master的binlog日志。ConnetcionPhaseMySQL客户端想要与服务器进行通信,第一步就是需要成功建立连接,整个过程如下图所示:connection-phase1.client发起一个TCP连接。2.server响应一个InitialHandshakePacket(初始化握手包),内容会包含一个默认的认证方式。3.这一步是可选的,双方建立SSL加密连接。4.client回应HandshakeResponsePacket,内容需要包括用户名和按照指定方式进行加密后的密码数据。5.server响应OK_Packet确认认证成功,或者ERR_Packet表示认证失败并关闭连接。Packet一个Packet其实就是一个TCP包,所有包都有一个最基本

  • 持久内存指令(PMDK)简介

    持久内存指令(PMDK)简介PMDK函数libpmem库主要特性是提供一种将脏数据刷写到持久内存的方法。常用的函数主要包括pmem_flush、pmem_drain、pmem_memcpy_nodrain。由于CPUCACHE内容向PM刷写的时机和顺序不受用户控制,所以需要特定指令进行强制刷写。pmem_flush的功能为调用CLWB、CLFLUSHOPT或CLFLUSH指令强制将CPUCACHE中内容(以cacheline为单位)刷写到PM;指令发起后,由于CPU是多核,cache中内容到PM的顺序也不一样,所以还需要pmem_drain即调用SFENCE指令,确保CLWBs全部执行完成。如果pmem_flush调用的指令是CLFLUSH,则该指令中包含sfence,所以理论上不再需要调用pmem_drain,实际上如果是这个指令的话,pmem_drain什么也不做。上述讲述了CPUcache内容向PM刷写的函数。下面讲述内存拷贝,即从内存向PM拷贝数据。该功能由pmem_memcpy_nodrain完成,调用MOVNT指令(MOV或MOVNTDQ),该指令拷贝不经过CPUCACHE

  • PHP的mysqli_ssl_set()函数讲解

    PHPmysqli_ssl_set()函数实例创建SSL连接:<?php $con=mysqli_init(); if(!$con) { die("mysqli_initfailed"); } mysqli_ssl_set($con,"key.pem","cert.pem","cacert.pem",NULL,NULL); if(!mysqli_real_connect($con,"localhost","my_user","my_password","my_db")) { die("ConnectError:".mysqli_connect_error()); } //一些查询... mysqli_close($con); ?复制定义和用法mysqli_ssl_set()函数用于创建SSL安全连接。然而,该函数只有在启用OpenSSL支持时才有效。注释:该函数必须在mysqli_real_connec

  • Linphone即时信息加密

    本文是来自FOSDEM2020RealTime的演讲,演讲者是JohanPascal。演讲主题是Linphone即时信息加密。演讲分为四个部分,第一个部分讲述安全要求,第二个部分讲述协议概述,第三个部分介绍多设备环境下Linphone群组的集成,第四部分是中间人攻击检测。Johan在简单介绍了Linphone的基本信息之后,首先讲解了对于安全即时通信所需要的主要安全要求,包括保护内容——端到端加密、确认发件人和收件人的身份——认证方式、在密钥被泄露的情况下,过去的对话是安全的——过去保密、恢复秘钥——未来保密、用户能够尽量轻松地使用。Johan说明了其是建立在强大的协议上的。在信号协议的基础上,有许多扩展支持,包括每个账号的多设备支持、保证未来保密的群组聊天和使用的相互认证方法。也介绍了简化的数据流。其次,Johan给出了协议的概述。用了两个简单的例子介绍了异步密钥协商协议:X3DH,用图表介绍了DoubleRatchetprotocol,和实现。接着Johan介绍了Linphone上的集成。分为设备识别,flexisipsip代理,会议服务器,安全的设备/服务器连接。并用示意图介绍了

  • 动态规划:数塔问题

    动态规划问题我训练过一些题目,但是感觉自己掌握的还不是特别好! 下面以一道经典的动态规划题目说明动态规划算法的思想,文末会官方的给出对动态规划的文字叙述。先看题目:如下图(图片来自百度图片)是一个数塔,从顶部出发在每一个节点可以选择向左或者向右走,一直走到底层,要求找出一条路径,使得路径上的数字之和最大.思路分析: 这道题目如果使用贪婪算法不能保证找到真正的最大和。 在用动态规划考虑数塔问题时可以自顶向下的分析,自底向上的计算。 从顶点出发时到底向左走还是向右走应取决于是从左走能取到最大值还是从右走能取到最大值,只要左右两道路径上的最大值求出来了才能作出决策。同样的道理下一层的走向又要取决于再下一层上的最大值是否已经求出才能决策。这样一层一层推下去,直到倒数第二层时就非常明了。 所以第一步对第五层的8个数据,做如下四次决策: 如果经过第四层2,则在第五层的19和7中肯定是19; 如果经过第四层18,则在第五层的7和10中肯定是10; 如果经过第四层9,则在第五层的10和4中肯定是10; 如果经过第四层5,则在第五层的4和16中肯定是16; 经过一次决策,问题降了一阶。5层数塔问题转换成

  • Head First Android Testing 2

    深入浅出Android测试教程(2)###第二部分InstrumentationTestsInstrumentationTests又叫DeviceorEmulatorTests,即运行在设备或者模拟器上的测试。使用AndroidJunitRunner来运行,测试代码存放在androidTest目录下。①RunonDeviceorEmulator②RunWithAndroidJUnitRunner③LocatedandroidTest/sourceset使用它需要依赖AndroidSupportRepository,所以需要通过SDKManager下载最新版本的SupportRepository。参考网址TestingSupportLibrary提到,以前用来做测试的InstrumentationTestRunner类只支持Junit3,而新的AndroidJunitRunner类支持Junit4。测试步骤:(1)配置build.gradle其中testInstrumentationRunner"android.support.test.runner.AndroidJUnitR

  • 创建属于自己的第一个Composer/Packagist包

    Composer是PHP的一个依赖管理工具,Composer不是一个包管理器,它涉及“packages”和“libraries”,但它在每个项目的基础上进行管理,在你项目的某个目录中(例如vendor)进行安装。默认情况下它不会在全局安装任何东西,因此,仅仅是一个依赖管理然后今天博主要介绍一下如何通过Composer和Packagist向PHP社区贡献代码包首先,如果你是一个PHP开发者但是还不知道什么是Composer,请先参考了一下这篇文章Composer–PHP的春天依赖管理的新时代,或者这一篇简介使用ComposerComposer是PHP的一个包依赖管理工具,你可以使用第三方库也可以自行开发,现在我要告诉你如何创建一个Composer包并且发送到Packagist(其他开发者可以通过它在他们项目中使用这些发布到Packagist上的包)创建包我们可以创建一个新项目来使用Composer。我建一个格式化输出数组的类,目前大多数框架都内置了快速打印的方法,这边仅仅为了演示如何创建包文件结构先创建功能文件if(!function_exists('p')){   

  • Java并发编程(四)Java内存模型

    相关文章 Java并发编程(一)线程定义、状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域前言此前我们讲到了线程、同步以及volatile关键字,对于Java的并发编程我们有必要了解下Java的内存模型,因为Java线程之间的通信对于工程师来言是完全透明的,内存可见性问题很容易使工程师们觉得困惑,这篇文章我们来主要的讲下Java内存模型的相关概念。1.共享内存和消息传递线程之间的通信机制有两种:共享内存和消息传递;在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信。在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信。 同步是指程序用于控制不同线程之间操作发生相对顺序的机制。在共享内存并发模型里,同步是显式进行的。工程师必须显式指定某个方法或某段代码需要在线程之间互斥执行。在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。 Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对工程师完全透明。2.Java

  • 关于null的操作

    空值    空值一般用NULL表示    一般表示未知的、不确定的值,也不是空格    一般运算符与其进行运算时,都会为空    空不与任何值相等    表示某个列为空用:ISNULL  不能使用COMM=NULL这种形式    某个列不为空:ISNOTNULL 不能使用COMM!=NULL 这种形式    空值在作升序排列时,空值会放到最后。    相反作降序排列时,空值会放在最前。 空值作逻辑运算时:    AND运算:    FANDF =F       FANDT =F       FANDNULL =F     TANDF =F       TANDT =T       TANDNULLISNULL     NULLANDF =F    NULLANDTISNULL   NULLANDNULLISNULL复制    就是说AND的优先级是:F ->NULL ->T    OR运算:    TORT =T     TORF =T     TORNULL =T     FORT =T     FORF =F     FORNULLISNULL     NULLORT =T  NU

  • 进程调度与进程切换_模式切换和进程切换有什么区别

    ?写在前面 ?知识点7:进程的状态与切换?7.1进程的状态 ?知识点8:进程控制?8.1进程控制的宏观解读 ?1.什么是进程控制? 进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。简而言之,进程控制就是为了实现进程状态转换。 ?2.如何实现进程控制? 我们使用“原语”来实现进程控制,我们一般把进程控制使用的程序段称为原语,原语的特点是执行期间不允许中断,是一气呵成的,它是一个不可分割的基本单位。 ?3.为什么进程控制过程要一气呵成? 如果进程控制过程中不能“一气呵成”,就有可能导致操作系统中的某些关键数据结构信息不统一,这会影响操作系统进行其他管理工作,比如下面?这个例子: ?4.原语是如何做到“一气呵成的”? 原语的执行具有原子性,即执行过程只能一气呵成,期间不允许被中断;它使用“关中断指令“和”开中断指令“这两个特权指令实现原子性。 ✨✨✨我是分割线✨✨✨?8.2进程控制之进程创建 ?1.父进程与子进程 允许一个进程创建另一个进程,此时创建者称为父进程,被创建的进程称为子进程。子进程可以继承父进程所拥有的资源;当子进程

  • 腾讯云容器安全服务查询集群网络策略列表api接口

    1.接口描述接口请求域名:tcss.tencentcloudapi.com。 查询集群网络策略列表 默认接口请求频率限制:20次/秒。 APIExplorer提供了在线调用、签名验证、SDK代码生成和快速检索接口等能力。您可查看每次调用的请求内容和返回结果以及自动生成SDK调用示例。 2.输入参数以下请求参数列表仅列出了接口请求参数和部分公共参数,完整公共参数列表见公共请求参数。 参数名称 必选 类型 描述 Action 是 String 公共参数,本接口取值:DescribeNetworkFirewallPolicyList。 Version 是 String 公共参数,本接口取值:2020-11-01。 Region 否 String 公共参数,本接口不需要传递此参数。 ClusterId 是 String 集群id Offset 否 Integer 偏移量 Limit 否 Integer 每次查询的最大记录数量 Filters.N 否 ArrayofComplianceFilters Name-StringName可取值:C

  • 开发vue心得

    所有的vuejs组件都是被扩展的vue实例; varMyComponent=Vue.extend({ //扩展选项对象 }) varmyComponentInstance=newMyComponent();复制 每个Vue实例都会代理这个实例的data属性对象里的所有属性: vardata={a:1} varvm=newVue({ data:data }) vm.a===data.a//->true //设置属性也会影响到原始数据 vm.a=2 data.a//->2 //...反之亦然 data.a=3 vm.a//->3复制 所有Vue实例本身暴露的属性和方法,都以$为头来区别,对应Vue.setglobalAPI 例如:vm.$data,vm.$elvm.$watch,这个有利于和data属性对象的数据来区分; 所有directive都以v-xxx形式存在: <pv-if="greeting">Hello!</p>//根据greeting表达式的值真假来插入或者删除p元素; <av-bind:href="url"&g

  • TypeScript——枚举类型

    enum类型是对JavaScript标准数据类型的一个补充。   在运行环境下编译成对象,可用属性名索引,也可用属性值索引。而其实现原理为:反向映射(如下例)   数字枚举 enumRole{ Reporter,//Reporter=1,默认情况下,从0开始为元素编号。也可赋值,后续值递增 Developer, Maintainer, Owner, Guest }复制 编译准成为如下: varRole; (function(Role){ Role[Role["Reporter"]=0]="Reporter"; Role[Role["Developer"]=1]="Developer"; Role[Role["Maintainer"]=2]="Maintainer"; Role[Role["Owner"]=3]="Owner"; Role[Role["Guest"]=4]="Guest"; })(Role||(Role={}));复制 字符串枚举:  只有成员的名称被当作的key,未作反向映射 enumMessage{ Succ

  • selenium中页面截图和元素截图的方法

    一、页面截图   selenium中页面截图的方法比较简单,可以直接使用selenium自带的截图方式save_screenshot(‘filename’)。 fromseleniumimportwebdriver driver=webdriver.Chrome() driver.get("http://xlrz.chdi.com.cn/wssq/")driver.save_screenshot("login.png")复制   注意:save_screenshot的文件后缀名只能是png。   get_screenshot_as_flie("文件路径"),与save_screenshot(‘filename’)功能相似。不过get_screenshot_as_flie("文件路径")可以指定文件路径,而save_screenshot(‘filename’)是默认在项目目录下生成图片。 二、元素截图   元素截图需要先安装第三方pillow库,安装命令是“pipinstallpillow”.代码中需要先导入Image模组。   例如获取学历认证登录页面,【登录】按钮具体代码如下:

  • arthas与jvm-sandbox

    arthas与jvm-sandbox是阿里开源的JVM工具,都能够attach到一个正在运行的Java进程中,可以查看运行时的一些信息,并且可以通过对字节码的修改,来实现一些高级功能。  arthas和jvm-sandbox之所以能实现这些功能,主要依赖的有Javaagent和ASM字节码修改。 javaagent是Jdk1.5之后引入的技术,可以随Java进程一起启动,或者attach到一个已经运行的Java进程。 在attach到进程后,Java本身提供了一些类(Instrumentation), 可以获取到一些运行信息,并且也提供了一些方法,可以对字节码进行干预。 ASM可以生成或者修改字节码, 配合Instrumentation的addTransformer()和retransformClasses()方法,可以完成对字节码的修改。    为了便于对着两个技术的理解,我这里简化了一下,实现了一个demo。  业务类: packagecom.demo; publicclassService{ p

  • 博客作业——在一周之内,快速看完整部教材,列出你不懂的5-10个问题,发布在你的个人博客上。

     (1)书中提到的NABCD模型中的N,如何发掘市场不明确的潜在用户需求?  (2)PM是否负责团队职责的分配以及工程模块的设计等工作?如果是,在设计模块上有什么方法?  (3)如果用户的需求过于苛刻,有必要通过降低软件的运行效率来满足它们吗?  (4)测试员的工作和软件质量保障工作间有什么联系和区别?  (5)对繁杂的用户需求,如何取舍才能保障整体利益的最大化?  (6)如何做到用尽量短的时间作出尽量全面的调查,既满足领导的要求,有符合用户的需求,并且适合程序员的开发习惯?  (7)是否允许在工程进行途中修改某部分的设计?还是要求在工程开始之前就把所有的设计固定,不可更改?  (8)需求分析需要走入市场吗?如何获得市场上最新的需求信息?

  • [solution] JZOJ 5459. 密室

    [solution]JZOJ5459.密室 Description 小X正困在一个密室里,他希望尽快逃出密室。 密室中有$N$个房间,初始时,小X在1号房间,而出口在N号房间。 密室的每一个房间中可能有着一些钥匙和一些传送门,一个传送门会单向地创造一条从房间$X$到房间$Y$的通道。另外,想要通过某个传送门,就必须具备一些种类的钥匙(每种钥匙都要有才能通过)。幸运的是,钥匙在打开传送门的封印后,并不会消失。 然而,通过密室的传送门需要耗费大量的时间,因此,小X希望通过尽可能少的传送门到达出口,你能告诉小X这个数值吗? 另外,小X有可能不能逃出这个密室,如果是这样,请输出"NoSolution"。 Input 第一行三个整数$N$,$M$,$K$,分别表示房间的数量、传送门的数量以及钥匙的种类数。 接下来$N$行,每行$K$个$0$或$1$,若第$i$个数为$1$,则表示该房间内有第$i$种钥匙,若第$i$个数为$0$,则表示该房间内没有第$i$种钥匙。 接下来$M$行,每行先读入两个整数$X$,$Y$,表示该传送门是建立在$X$号房间,通向$Y$号房间的,再读入$K$个$0$或$1$,

  • React-router v4教程

    在这个教程里,我们会从一个例子React应用开始学习react-router-dom。其中你会学习如何使用Link、NavLink等来实现跳转,Switch和exact实现排他路由和浏览器路径历史。 也许学习react-router最好的办法就是用react-router-domv4来写一个多页的react应用。这个react应用会包含登录、注册、首页、联系人等页面。但是,首先让我们来看一下reactrouterv4的概念,以及它与v3有什么不同的地方。 Reactrouterv4vsv3 v4是reactrouter的一次重写,所以和v3有很多不同的地方。主要有: 在reactrouterv4里,路由不再是集中在一起的。它成了应用布局、UI的一部分。 浏览器用的router在react-router-dom里。所以,浏览器里使用的时候只需要importreact-router-dom就可以。 新的概念BrowerRouter和HashRouter。他们各自服务于不同的情景下。详见下文。 不在使用{props.children}来处理嵌套的路由。 v4的路由默认不再排他,会有多个匹配

  • 软件工程第一周作业

    软件工程第一周作业 刘阳 我是刘阳;我的爱好是读计算机类的书,看番;推荐海大食堂中最喜欢的一道菜:二餐二的黄焖鸡;我们用代码编织起整个世界。 (1)回想一下你初入大学时对计算机专业的畅想 当初你是如何做出选择计算机专业的决定的? 其实在很久很久之前,大概是初中的时候就十分喜欢计算机,当时自学过一段时间的C++,但是因为各种各样的事(当时用电脑就是玩玩游戏,基本没有用来学习,还有那时候很多东西都是英文的)没有坚持下来,最基本的东西HelloWorld和一些简单的数学计算还是学会了,从那时开始直到现在都感觉用代码实现各种各样的功能十分的神奇和wonderful。高考完报的志愿基本每个学校第一个选的专业都是计算机,不是计算机就是自动化,最重要的一方面就是自己的兴趣,只要关于电脑的一切我都挺感兴趣的,另一方面就是计算机行业火爆,无论是就业还是收入都十分可观,所以计算机专业是我的不二之选。 将来你会选择从事计算机相关的工作吗? 当然会的,毕竟计算机能给我带来乐趣,写写代码,做做项目还是蛮有意思的。现在我还没有确定以后要做什么方向,是做开发还是做研究,我感觉各有利弊吧,虽然现在最火的

  • yii2 使用 datetimepicker 插件报错【TypeError: icon is undefined】

    调用方式如下 usekartik\datetime\DateTimePicker; <?phpecho$form->field($model,'BEGIN_DATE')->widget(DateTImePicker::className(),[])?> 复制 控件按钮已经出来了,但是JS报错

  • WinHex 可发现一些图片或文件的隐含信息

    文章内容简介:用WinHex发现图片的隐含信息 一、取一张特殊的图片     我们正常打开,发现没有什么特殊的信息   二、用winhex打开    打开后,我们发现存在信息:tomisthekiller...   总结:     有些图片看似可以正常,但是可能含有某些重要信息,我们用特殊的工具打开,可以发现这些隐藏信息。  

相关推荐

推荐阅读