【分布式消息服务】——Kafka——SpringBoot操作

Kafka——API——SpringBoot环境搭建

maven核心引入

     <!-- kafka应用-->
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>

其他maven引入(方便测试)

    <!--Spring Boot 测试组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.0.9.RELEASE</version>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>com.vaadin.external.google</groupId>
                    <artifactId>android-json</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
      <!-- 测试应用-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- swagger应用-->
       <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>
        <!-- mvc应用-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- lombok应用-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- 测试应用-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

配置swaggerUI

注意RequestHandlerSelectors.basePackage(“com.xdoc.template.module”),将这个包名改成自己的

@Configuration
@EnableSwagger2
public class SwaggerConfig extends WebMvcConfigurerAdapter {
    private boolean enableSwagger=true;

    @Autowired
    private ApplicationContext applicationContext;


    @PostConstruct
    public void setObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        objectMapper.registerModule(module);

        JacksonAnnotationIntrospector jacksonAnnotationIntrospector=  new JacksonAnnotationIntrospector();

        objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {

            @Override
            public boolean isAnnotationBundle(Annotation ann) {
                if (ann.annotationType() == JSONField.class) {
                    return true;
                }
                return super.isAnnotationBundle(ann);
            }

            @Override
            public PropertyName findNameForSerialization(Annotated a) {
                PropertyName nameForSerialization = super.findNameForSerialization(a);
                if (nameForSerialization == null || nameForSerialization == PropertyName.USE_DEFAULT) {
                    JSONField jsonField = _findAnnotation(a, JSONField.class);
                    if (jsonField != null) {
                        return PropertyName.construct(jsonField.name());
                    }
                }
                return nameForSerialization;
            }

            @Override
            public PropertyName findNameForDeserialization(Annotated a) {
                PropertyName nameForDeserialization = super.findNameForDeserialization(a);
                if (nameForDeserialization == null || nameForDeserialization == PropertyName.USE_DEFAULT) {
                    JSONField jsonField = _findAnnotation(a, JSONField.class);
                    if (jsonField != null) {
                        return PropertyName.construct(jsonField.name());
                    }
                }
                return nameForDeserialization;
            }
        });

        ObjectMapperConfigured objectMapperConfigured = new ObjectMapperConfigured(applicationContext, objectMapper);
        applicationContext.publishEvent(objectMapperConfigured);
    }


    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2).groupName("framework").enable(enableSwagger)
                .genericModelSubstitutes(DeferredResult.class).useDefaultResponseMessages(false)
                .forCodeGeneration(false).pathMapping("/").apiInfo(apiInfo()).select()
                .apis(RequestHandlerSelectors.basePackage("com.xdoc.template.module")).build()
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder().title("kafka测试").description("测试文档").termsOfServiceUrl("")
                .contact("dragon").version("1.0").build();
    }


}

application.yml

spring:
  kafka:
    bootstrap-servers: 112.126.57.37:9092,112.126.57.37:9093
    producer:
      # 生产者————序列化机制配置
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-deserializer: org.apache.kafka.common.serialization.StringSerializer
      # 生产者————消息缓存配置
      batch-size: 65536
      buffer-memory: 524288
    listener:
      concurrency: 1
      type: batch
    consumer:
      # 消费者————标识/事务配置
      group-id: tencent-trip
      max-poll-records: 20
      auto-commit-interval: 100
      enable-auto-commit: true
      # 消费者————序列化器配置
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer

Kafka——API——简单DEMO

我们首先演示一个简单的发布订阅模式,就是消费者利用监听模式获取消费信息;
消费者配置类

@Component
@Slf4j
public class KafkaConsumerConfig {
    @Value("${spring.kafka.bootstrap-servers}")
    private String bootstrapServers;
    @Value("${spring.kafka.consumer.group-id}")
    private String groupId;
    @Value("${spring.kafka.consumer.key-deserializer}")
    private String consumerKeyDeserializer;
    @Value("${spring.kafka.consumer.value-deserializer}")
    private String consumerValueDeserializer;
    @Value("${spring.kafka.consumer.max-poll-records}")
    private String maxPollRecords;

    @Value("${spring.kafka.consumer.auto-commit-interval}")
    private String autoCommitIntervalMs;
    @Value("${spring.kafka.consumer.enable-auto-commit}")
    private String enableAutoCommit;
    @Value("${spring.kafka.listener.concurrency}")
    private Integer concurrency;

    /**
     * 消费者—poll配置的时候会用到
     */
    @Bean
    public KafkaConsumer  kafkaConsumer(){
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(consumerConfigs());
        kafkaConsumer.subscribe(Collections.singletonList("test1"));
        return kafkaConsumer;
    }
    /**
     * 消费者——监听器整体配置
     * @return
     */
    @Bean
    public ConcurrentKafkaListenerContainerFactory ContainerFactory() {
        ConcurrentKafkaListenerContainerFactory container = new ConcurrentKafkaListenerContainerFactory();
        container.setConsumerFactory(new DefaultKafkaConsumerFactory<>(consumerConfigs()));
        //禁止自动启动
        container.setAutoStartup(false);
        container.setBatchListener(true);
        container.setConcurrency(concurrency);
        return container;
    }
    /**
     * 消费者配置生成
     * @return
     */
    private Map<String, Object> consumerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);//连接地址
        props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);//组标识
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, consumerKeyDeserializer);//序列化配置
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, consumerValueDeserializer);//序列化配置
        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords);//每一批拉取的数量,监听时
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, enableAutoCommit);//是否自动提交消费位移
        props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitIntervalMs);//消费位移自动提交时间间隔
        return props;
    }
}

生产者配置类

@Component
@Slf4j
public class KafkaProducerConfig {
    @Value("${spring.kafka.bootstrap-servers}")
    private String bootstrapServers;
    @Value("${spring.kafka.producer.key-serializer}")
    private String producerKeyDeserializer;
    @Value("${spring.kafka.producer.value-deserializer}")
    private String producerValueDeserializer;


    /**
     * 生产者——kafkaTemplate配置
     * @return
     */
    @DependsOn("producerFactory")
    @Bean
    public KafkaTemplate<Integer, String> kafkaTemplate(KafkaGlobalHandler kafkaGlobalHandler,ProducerFactory producerFactory) {
        KafkaTemplate template = new KafkaTemplate<String, String>(producerFactory);
        template.setDefaultTopic("defalutTopic");
        template.setProducerListener(kafkaGlobalHandler);
        return template;
    }
    /**
     * 生产者配置工厂
     * @return
     */
    @Bean
    public ProducerFactory<Integer, String> producerFactory() {
        DefaultKafkaProducerFactory factory = new DefaultKafkaProducerFactory<>(producerConfigs());
        return factory;
    }
    /**
     * 生产者者配置生成
     * @return
     */
    private Map<String, Object> producerConfigs() {
        Map<String, Object> producerConfigs = new HashMap<>();
        producerConfigs.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        producerConfigs.put(ProducerConfig.RETRIES_CONFIG, 1);// 重试,0为不启用重试机制
        producerConfigs.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); // 批量发送,延迟为1毫秒,启用该功能能有效减少生产者发送消息次数,从而提高并发量
        producerConfigs.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 1024000); // 生产者可以使用的总内存字节来缓冲等待发送到服务器的记录
        producerConfigs.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, producerKeyDeserializer);
        producerConfigs.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,producerValueDeserializer);
        return producerConfigs;
    }
}

创建生产者的api接口

@Api(tags = "kafka——Api测试")
@RestController
public class KafkaProducerController {
    @Autowired
    KafkaTemplate kafkaTemplate;
    @PostMapping("/kafkaTest/sendMessage")
    public ResponseVO sendMessage(String message){
        kafkaTemplate.send("test1", message);
        return ApiResult.success("成功");
    }
}

创建监听者的处理接口

@Component
public class KafkaComuserListener {
    @KafkaListener(topics = "testTopic")
    public void receiveMessage(String message){
        System.out.println("testTopic:"+message);
    }
}

测试
输入swaggerUI地址:http://localhost:30002/swagger-ui.html#
发送消息:
在这里插入图片描述
观察控制台:

Kafka——API——KafkaTemplate演示

/**
 * @send方法api汇总
 * topic:这里填写的是Topic的名字
 * partition:这里填写的是分区的id,其实也是就第几个分区,id从0开始。表示指定发送到该分区中
 * timestamp:时间戳,一般默认当前时间戳
 * key:消息的键
 * data:消息的数据
 * ProducerRecord:消息对应的封装类,包含上述字段
 * Message<?>:Spring自带的Message封装类,包含消息及消息头
 */
@Api(tags = "kafka——Api测试")
@RestController
public class KafkaProducerController {
    @Autowired
    KafkaTemplate kafkaTemplate;
    /**
     * @需指定topic发送
     * ListenableFuture<SendResult<K, V>> send(String topic, V data);
     * ListenableFuture<SendResult<K, V>> send(String topic, K key, V data);
     * ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, K key, V data);
     * ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, Long timestamp, K key, V data);
     */
    @PostMapping("/kafkaTest/sendMessage")
    public ResponseVO sendMessage(String message){
        kafkaTemplate.send("test1", message);
        return ApiResult.success("成功");
    }
    @PostMapping("/kafkaTest/sendProducerRecord")
    public ResponseVO sendProducerRecord(String message){
        kafkaTemplate.send(new ProducerRecord("testTopic",message));
        return ApiResult.success("成功");
    }
    /**
     * @template配置的默认topic
     * ListenableFuture<SendResult<K, V>> sendDefault(V data);
     * ListenableFuture<SendResult<K, V>> sendDefault(V data);
     * ListenableFuture<SendResult<K, V>> sendDefault(K key, V data);
     * ListenableFuture<SendResult<K, V>> sendDefault(Integer partition, K key, V data);
     * ListenableFuture<SendResult<K, V>> sendDefault(Integer partition, Long timestamp, K key, V data);
     */
    @PostMapping("/kafkaTest/sendDefault")
    public ResponseVO sendDefault(String message){
        kafkaTemplate.sendDefault("defalutTopic", message);
        return ApiResult.success("成功");
    }
    /**
     * @同步回调
     * 回调是复杂编程中,根据需要去设计,一般就是可靠性的验证
     */
    @PostMapping("/kafkaTest/sendCallBack1")
    public ResponseVO sendCallBack1(String message) throws Exception{
        ListenableFuture future = kafkaTemplate.send("testTopic", message);
        /**
         * @获取future状态
         */
        future.isCancelled();
        future.isDone();
        future.get();//会阻塞
        future.get(1000, TimeUnit.SECONDS);//超时自动抛异常
        return ApiResult.success("成功");
    }
    /**
     * @异步回调
     * 回调是复杂编程中,根据需要去设计,一般就是可靠性的验证
     */
    @PostMapping("/kafkaTest/sendCallBack2")
    public ResponseVO sendCallBack2(String message) throws Exception{
        ListenableFuture future = kafkaTemplate.send("testTopic", message);
        future.addCallback(new ListenableFutureCallback<SendResult>() {
            @Override
            public void onFailure(Throwable throwable) {
            }
            @Override
            public void onSuccess(SendResult result) {
            }
        });
        return ApiResult.success("成功");
    }
}

默认主题可以添加这个测试,但注意默认消费者要在template中配置好topic

  @KafkaListener(topics = "defalutTopic")
    public void receiveDefalutTopicMessage(String message){
        System.out.println("defalutTopic: " + message);
        System.out.println("defalutTopic: " + message);
        System.out.println("defalutTopic: " + message);
        System.out.println("defalutTopic: " + message);
        System.out.println("defalutTopic: " + message);
        System.out.println("defalutTopic: " + message);
        System.out.println("defalutTopic: " + message);
        System.out.println("defalutTopic: " + message);
        System.out.println("defalutTopic: " + message);
        System.out.println("defalutTopic: " + message);
        System.out.println("defalutTopic: " + message);
        System.out.println("defalutTopic: " + message);
        System.out.println("defalutTopic: " + message);
    }

Kafka——API——KafkaConsumer演示(消费位移管理)

手动提交

kafka将消费位移交给消费者自己去管理那么,如何设置手动提交呢?

首先关闭消费者的自动提交功能

spring:
  kafka:
    ……
    consumer:
      ……
      auto-commit-interval: 100
      enable-auto-commit: false
      ……

我们利用junit测试类去测试
在这里插入图片描述

@RunWith(SpringRunner.class)
@SpringBootTest
class DemoApplicationTests {
    @Autowired
    KafkaTemplate KafkaConsumer;
    @Test
    void contextLoads() {
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(100);
            for (ConsumerRecord<String, String> record : records)
                System.out.printf("offset = %d, key = %s, value = %s\n", record.offset(), record.key(), record.value());
            if(records.count()!=0){
                consumer.commitSync();
            }
        }
    }
}

发送消息
在这里插入图片描述
所有api

consumer.commitSync();//同步提交位移
consumer.commitAsync();//异步提交位移
consumer.wakeup();//其他线程调用一次wakeup可以中断while Ture循环
consumer.close();//被中断的线程需要调用关闭按钮让出连接权,让另外一个consumer建立连接
consumer.seek(new TopicPartition("test1",2),2);//更改consumer的拉取位置,seek()只是指定了poll()拉取的开始位移,这并不影响在Kafka中保存的提交位移
 /**
* 获取某个主题的分区信息
 */
List<PartitionInfo> partitionInfoList = consumer.partitionsFor("topic1");
/**
 * 让消费者固定分区和指定消费
 * 可以遍历分区信息区绑定
*/
partitionInfoList.forEach(partitionInfo->{
   consumer.assign(Collections.singletonList(new TopicPartition(partitionInfo.topic(),partitionInfo.partition())));
});
//重平衡监听
consumer.subscribe(Arrays.asList("test1"), new ConsumerRebalanceListener() {
   public void onPartitionsAssigned(Collection<TopicPartition> partitions) { //均衡之前
   }
   public void onPartitionsRevoked(Collection<TopicPartition> partitions) {//均衡之后
   }
});

重平衡监听

 /**
* @消费者重平衡监听器
*/
consumer.subscribe(Arrays.asList("test1"), new ConsumerRebalanceListener() {
            @Override
            public void onPartitionsAssigned(Collection<TopicPartition> partitions) { //均衡之前
                /**
                 * 业务还没处理完,先提交位移然后保存数据库
                 */
            }
            @Override
            public void onPartitionsRevoked(Collection<TopicPartition> partitions) {//均衡之后
                /**
                 * 从数据库拿出未处理的消息,接着处理
                 */
            }
        });

Kafka——API——事务管理

Kafka——网络IO原理

NetworkClient是SpringBoot中作为消费者向kafak服务器发送的网络关键类,利用这个类我们可以看到提交前的数据,重而排查一些问题;
然后Fetcher是作为拉取者最核心的部分,我们关注一下这个方法就行;

 public synchronized int sendFetches() {
        // Update metrics in case there was an assignment change
        sensors.maybeUpdateAssignment(subscriptions);

        Map<Node, FetchSessionHandler.FetchRequestData> fetchRequestMap = prepareFetchRequests();
        for (Map.Entry<Node, FetchSessionHandler.FetchRequestData> entry : fetchRequestMap.entrySet()) {
            final Node fetchTarget = entry.getKey();
            final FetchSessionHandler.FetchRequestData data = entry.getValue();
            final FetchRequest.Builder request = FetchRequest.Builder
                    .forConsumer(this.maxWaitMs, this.minBytes, data.toSend())
                    .isolationLevel(isolationLevel)
                    .setMaxBytes(this.maxBytes)
                    .metadata(data.metadata())
                    .toForget(data.toForget())
                    .rackId(clientRackId);

            if (log.isDebugEnabled()) {
                log.debug("Sending {} {} to broker {}", isolationLevel, data.toString(), fetchTarget);
            }
            RequestFuture<ClientResponse> future = client.send(fetchTarget, request);
            // We add the node to the set of nodes with pending fetch requests before adding the
            // listener because the future may have been fulfilled on another thread (e.g. during a
            // disconnection being handled by the heartbeat thread) which will mean the listener
            // will be invoked synchronously.
            this.nodesWithPendingFetchRequests.add(entry.getKey().id());
            future.addListener(new RequestFutureListener<ClientResponse>() {
                @Override
                public void onSuccess(ClientResponse resp) {
                    synchronized (Fetcher.this) {
                        try {
                            @SuppressWarnings("unchecked")
                            FetchResponse<Records> response = (FetchResponse<Records>) resp.responseBody();
                            FetchSessionHandler handler = sessionHandler(fetchTarget.id());
                            if (handler == null) {
                                log.error("Unable to find FetchSessionHandler for node {}. Ignoring fetch response.",
                                        fetchTarget.id());
                                return;
                            }
                            if (!handler.handleResponse(response)) {
                                return;
                            }

                            Set<TopicPartition> partitions = new HashSet<>(response.responseData().keySet());
                            FetchResponseMetricAggregator metricAggregator = new FetchResponseMetricAggregator(sensors, partitions);

                            for (Map.Entry<TopicPartition, FetchResponse.PartitionData<Records>> entry : response.responseData().entrySet()) {
                                TopicPartition partition = entry.getKey();
                                FetchRequest.PartitionData requestData = data.sessionPartitions().get(partition);
                                if (requestData == null) {
                                    String message;
                                    if (data.metadata().isFull()) {
                                        message = MessageFormatter.arrayFormat(
                                                "Response for missing full request partition: partition={}; metadata={}",
                                                new Object[]{partition, data.metadata()}).getMessage();
                                    } else {
                                        message = MessageFormatter.arrayFormat(
                                                "Response for missing session request partition: partition={}; metadata={}; toSend={}; toForget={}",
                                                new Object[]{partition, data.metadata(), data.toSend(), data.toForget()}).getMessage();
                                    }

                                    // Received fetch response for missing session partition
                                    throw new IllegalStateException(message);
                                } else {
                                    long fetchOffset = requestData.fetchOffset;
                                    FetchResponse.PartitionData<Records> partitionData = entry.getValue();

                                    log.debug("Fetch {} at offset {} for partition {} returned fetch data {}",
                                            isolationLevel, fetchOffset, partition, partitionData);

                                    Iterator<? extends RecordBatch> batches = partitionData.records.batches().iterator();
                                    short responseVersion = resp.requestHeader().apiVersion();

                                    completedFetches.add(new CompletedFetch(partition, partitionData,
                                            metricAggregator, batches, fetchOffset, responseVersion));
                                }
                            }

                            sensors.fetchLatency.record(resp.requestLatencyMs());
                        } finally {
                            nodesWithPendingFetchRequests.remove(fetchTarget.id());
                        }
                    }
                }

                @Override
                public void onFailure(RuntimeException e) {
                    synchronized (Fetcher.this) {
                        try {
                            FetchSessionHandler handler = sessionHandler(fetchTarget.id());
                            if (handler != null) {
                                handler.handleError(e);
                            }
                        } finally {
                            nodesWithPendingFetchRequests.remove(fetchTarget.id());
                        }
                    }
                }
            });

        }
        return fetchRequestMap.size();
    }

热门文章

暂无图片
编程学习 ·

C语言期末考试内容(2)选择填空答案整理(基础章节内容)

C语言期末考试内容(2)选择填空答案整理(基础章节内容)文章目录C语言期末考试内容(2)选择填空答案整理(基础章节内容)作业二:变量定义/读/写与数据的存储表示一、判断题:答案: F F F F解析:1-4:C语言中的结束符是以分号来结束的,一个分号就代表一条语句。二、单选…
暂无图片
编程学习 ·

javaScript之ES6

ES6新增的内容 新增的let和constlet num1 = 10console.log(num1) //10const num2 = 10console.log(num2) //10let const声明变量和 var声明变量的区别:用let 和const 声明的变量不会进行预解析,只能先声明后使用 用let 和const 不能重复声明同一个变量 用let 和const声明 变…
暂无图片
编程学习 ·

深度学习在美团推荐平台排序中的运用

美团作为国内最大的生活服务平台,业务种类涉及食、住、行、玩、乐等领域,致力于让大家吃得更好,活得更好,有数亿用户以及丰富的用户行为。随着业务的飞速发展,美团的用户和商户数在快速增长。在这样的背景下,通过对推荐算法的优化,可以更好的给用户提供感兴趣的内容,帮…
暂无图片
编程学习 ·

离线安装pyinstaller时,报错的解决过程

报错内容: Command ““c:\program files\python37\python.exe” “c:\program files\python37\lib\site-packages\pip” install --ignore-installed --no-user --prefix C:\Users\yf\AppData\Local\Temp\pip-build-env-l034cdvw\overlay --no-warn-script-location --no-bina…
暂无图片
编程学习 ·

DeFi隐患重重,DREP如何见招拆招?

在许多人眼中,DeFi将是区块链应用未来发展的一大热点:由去中心化的第三方平台提供一系列金融服务,尽管这些服务与传统银行业务尚且没有太大差别,例如借贷服务,货币兑换以及支付结算等。但是DeFi凭借宽松的KYC使用门槛和极大的交易自由吸引了大批用户。许多去中心化金融机构…
暂无图片
编程学习 ·

Linux 通过关键字查询文档内容

命令grep keyword test.log -C500 --colorgrep -C500 keyword test.log --color说明:第一个命令和第二个命令都可以keyword:是要查询的关键字,关键字可以不用引号引起来test.log:是文件名称,即要查询的文件-C500:显示的行数,显示500行,可以没有--color:颜色,给关键字…
暂无图片
编程学习 ·

软件测试的基本流程

软件测试的基本流程 1. 测试需求分析阶段阅读需求 理解需求 主要就是对业务的学习 分析需求点 参与需求评审会议2. 测试计划阶段主要任务就是编写测试计划 参考软件需求规格说明书 项目总体计划,内容包括测试范围(来自需求文档),进度安排,人力物力的分配,整体测试策略的制…
暂无图片
编程学习 ·

查找 -- 7.1 Sear for a Range -- 图解

/********************************************************************************************************** 描述 Given a sorted array of integers, find the starting and ending position of a given target value. Your algorithm’s runtime complexity must be i…
暂无图片
编程学习 ·

远程工作和数字鸿沟

云栖号资讯:【点击查看更多行业资讯】 在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!在全球持续蔓延的冠状病毒疫情的影响下,一场革命正在发生:弹性工作革命。很多企业开始意识到这样一个现实,即他们的员工可以远程工作。经过数月的在家工作之后,许多员…
暂无图片
编程学习 ·

简单动态字符串

SDS(simple synamic String)用作Redis默认字符串表示。C字符串只会作为字符串字面量用在一些无须对字符串进行修改的地方,例如打印日志等。 SDS定义 每个sds.h/sdshdr结构表示一个SDS值 struct sdshdr {//字符串的长度int len;// buf数组中未使用字节的数量int free;// 字节…
暂无图片
编程学习 ·

C++笔记 变量

primer c++笔记 变量 变量定义首先是类型说明符随后紧跟由一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。列表初始化 int units_sold = 0; int units_sold = {0}; int units_sold {0}; int units_sold (0);花括号来初始化变量,这种初始化的形式被称为列…
暂无图片
编程学习 ·

typescript学习笔记

typescript是微软开发的一个javascript的一个超集。支持es6规范。它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。es是客户端脚本的规范,es5,es6是这些规范的不同版本。JavaScript与typescript是两种客户端脚本语言,JavaScript实现了es5规范,t…
暂无图片
编程学习 ·

mysql服务无法启动,报服务正在启动或停止中,请稍后片刻再试一次

这个错误我尝试了网上好多得方法最后只能卸载重装是最简单得。 于是我后面就是卸载重装了。后面就不上图了。希望有朋友碰到这个问题能给我一个解决方法。 在这里特此说明,我写得所有博客都是小编自己实际操作得。碰到得问题记录和写下解决方法得。小编也验证了很多网上别人得…
暂无图片
编程学习 ·

C++ 11 之 移动语义 左值引用 完美转发

C++ 11 的 移动语义,左值引用 ,完美转发 三部分相互关联。阅读 两本数的相关章节即可完全掌握。首先是,强烈推荐IBM XL编译器开发团队推出的《深入理解C++11》3.3章节,内容讲解到为,鞭辟入里,自成系统,开发编译器的人果然对语言的理解很到位。其次是,《Effective Mocer…
暂无图片
编程学习 ·

JS Array

一、鉴别数组 typeof Array :Object (不可取) array instanof(Array) :true (可取) 二、转换方法 array.toString()返回字符串 array.valueOf() 返回数组本身 三、栈方法 pop()从尾部删除最后一个数据,并返回该值 push()在尾部加入新值,并返回加入后的…