开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Nebula Graph 源码解读系列 | Vol05 S

发表于 2021-11-19

本文首发于 Nebula Graph Community 公众号

Nebula Graph 源码解读系列 | Vol.05 Scheduler 和 Executor 两兄弟

上篇我们讲述了 Query Engine Optimizer 部分的内容,在本文我们讲解下 Query Engine 剩下的 Scheduler 和 Executor 部分。

概述

在执行阶段,执行引擎通过 Scheduler(调度器)将 Planner 生成的物理执行计划转换为一系列 Executor,驱动 Executor 的执行。
Executor,即执行器,物理执行计划中的每个 PlanNode 都会对应一个 Executor。

源码定位

调度器的源码在 src/scheduler 目录下:

1
2
3
4
5
6
arduino复制代码src/scheduler
├── AsyncMsgNotifyBasedScheduler.cpp
├── AsyncMsgNotifyBasedScheduler.h
├── CMakeLists.txt
├── Scheduler.cpp
└── Scheduler.h

Scheduler 抽象类定义了调度器的公共接口,可以继承该类实现多种调度器。
目前实现了 AsyncMsgNotifyBasedScheduler 调度器,它基于异步消息通信与广度优先搜索避免栈溢出。
执行器的源码在 src/executor 目录下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bash复制代码src/executor
├── admin
├── algo
├── CMakeLists.txt
├── ExecutionError.h
├── Executor.cpp
├── Executor.h
├── logic
├── maintain
├── mutate
├── query
├── StorageAccessExecutor.cpp
├── StorageAccessExecutor.h
└── test

执行过程

首先,调度器从执行计划的根节点开始通过使用广度优先搜索算法遍历整个执行计划并根据节点间的执行依赖关系,构建它们的消息通知机制。
执行时,每个节点收到它的所依赖的节点全部执行完毕的消息后,会被调度执行。一旦自身执行完成,又会发送消息给依赖自己的节点,直至整个计划执行完毕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
C++复制代码void AsyncMsgNotifyBasedScheduler::runExecutor(
std::vector<folly::Future<Status>>&& futures,
Executor* exe,
folly::Executor* runner,
std::vector<folly::Promise<Status>>&& promises) const {
folly::collect(futures).via(runner).thenTry(
[exe, pros = std::move(promises), this](auto&& t) mutable {
if (t.hasException()) {
return notifyError(pros, Status::Error(t.exception().what()));
}
auto status = std::move(t).value();
auto depStatus = checkStatus(std::move(status));
if (!depStatus.ok()) {
return notifyError(pros, depStatus);
}
// Execute in current thread.
std::move(execute(exe)).thenTry(
[pros = std::move(pros), this](auto&& exeTry) mutable {
if (exeTry.hasException()) {
return notifyError(pros, Status::Error(exeTry.exception().what()));
}
auto exeStatus = std::move(exeTry).value();
if (!exeStatus.ok()) {
return notifyError(pros, exeStatus);
}
return notifyOK(pros);
});
});
}

每个 Executor 会经历 create-open-execute-close 四个阶段:

create

根据节点类型生成对应的 Executor。

open

在 Executor 正式执行前做一些初始化操作,以及慢查询终止和内存水位的判断。
Nebula 支持手动 kill 掉某个查询语句的执行,因此每个 Executor 执行前需要检查下当前执行计划状态,若被标记为 killed,则终止执行。
每个 Query 类型的 Executor 执行前,还需要检查当前系统所占用内存是否达到内存水位。若达到内存水位,则终止执行,这能在一定程度上避免 OOM。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
C++复制代码Status Executor::open() {
if (qctx_->isKilled()) {
VLOG(1) << "Execution is being killed. session: " << qctx()->rctx()->session()->id()
<< "ep: " << qctx()->plan()->id()
<< "query: " << qctx()->rctx()->query();
return Status::Error("Execution had been killed");
}
auto status = MemInfo::make();
NG_RETURN_IF_ERROR(status);
auto mem = std::move(status).value();
if (node_->isQueryNode() && mem->hitsHighWatermark(FLAGS_system_memory_high_watermark_ratio)) {
return Status::Error(
"Used memory(%ldKB) hits the high watermark(%lf) of total system memory(%ldKB).",
mem->usedInKB(),
FLAGS_system_memory_high_watermark_ratio,
mem->totalInKB());
}
numRows_ = 0;
execTime_ = 0;
totalDuration_.reset();
return Status::OK();
}

execute

Query 类型的 Executor 的输入和输出都是一张表(DataSet)。
Executor 的执行基于迭代器模型:每次计算时,调用输入表的迭代器的 next() 方法,获取一行数据,进行计算,直至输入表被遍历完毕。
计算的结果构成一张新表,输出给后续的 Executor 作为输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
C++复制代码folly::Future<Status> ProjectExecutor::execute() {
SCOPED_TIMER(&execTime_);
auto* project = asNode<Project>(node());
auto columns = project->columns()->columns();
auto iter = ectx_->getResult(project->inputVar()).iter();
DCHECK(!!iter);
QueryExpressionContext ctx(ectx_);

VLOG(1) << "input: " << project->inputVar();
DataSet ds;
ds.colNames = project->colNames();
ds.rows.reserve(iter->size());
for (; iter->valid(); iter->next()) {
Row row;
for (auto& col : columns) {
Value val = col->expr()->eval(ctx(iter.get()));
row.values.emplace_back(std::move(val));
}
ds.rows.emplace_back(std::move(row));
}
VLOG(1) << node()->outputVar() << ":" << ds;
return finish(ResultBuilder().value(Value(std::move(ds))).finish());
}

如果当前 Executor 的输入表不会被其他 Executor 作为输入时,这些输入表所用的内存会在执行阶段被 drop 掉,减小内存占用。

1
2
3
4
5
6
7
8
9
10
11
12
13
C++复制代码void Executor::drop() {
for (const auto &inputVar : node()->inputVars()) {
if (inputVar != nullptr) {
// Make sure use the variable happened-before decrement count
if (inputVar->userCount.fetch_sub(1, std::memory_order_release) == 1) {
// Make sure drop happened-after count decrement
CHECK_EQ(inputVar->userCount.load(std::memory_order_acquire), 0);
ectx_->dropResult(inputVar->name);
VLOG(1) << "Drop variable " << node()->outputVar();
}
}
}
}

close

Executor 执行完毕后,将收集到的一些执行信息如执行时间,输出表的行数等添加到 profiling stats 中。
用户可以在 profile 一个语句后显示的执行计划中查看这些统计信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
yaml复制代码
Execution Plan (optimize time 141 us)

-----+------------------+--------------+-----------------------------------------------------+--------------------------------------
| id | name | dependencies | profiling data | operator info |
-----+------------------+--------------+-----------------------------------------------------+--------------------------------------
| 2 | Project | 3 | ver: 0, rows: 56, execTime: 147us, totalTime: 160us | outputVar: [ |
| | | | | { |
| | | | | "colNames": [ |
| | | | | "VertexID", |
| | | | | "player.age" |
| | | | | ], |
| | | | | "name": "__Project_2", |
| | | | | "type": "DATASET" |
| | | | | } |
| | | | | ] |
| | | | | inputVar: __TagIndexFullScan_1 |
| | | | | columns: [ |
| | | | | "$-.VertexID AS VertexID", |
| | | | | "player.age" |
| | | | | ] |
-----+------------------+--------------+-----------------------------------------------------+--------------------------------------
| 3 | TagIndexFullScan | 0 | ver: 0, rows: 56, execTime: 0us, totalTime: 6863us | outputVar: [ |
| | | | | { |
| | | | | "colNames": [ |
| | | | | "VertexID", |
| | | | | "player.age" |
| | | | | ], |
| | | | | "name": "__TagIndexFullScan_1", |
| | | | | "type": "DATASET" |
| | | | | } |
| | | | | ] |
| | | | | inputVar: |
| | | | | space: 318 |
| | | | | dedup: false |
| | | | | limit: 9223372036854775807 |
| | | | | filter: |
| | | | | orderBy: [] |
| | | | | schemaId: 319 |
| | | | | isEdge: false |
| | | | | returnCols: [ |
| | | | | "_vid", |
| | | | | "age" |
| | | | | ] |
| | | | | indexCtx: [ |
| | | | | { |
| | | | | "columnHints": [], |
| | | | | "index_id": 325, |
| | | | | "filter": "" |
| | | | | } |
| | | | | ] |
-----+------------------+--------------+-----------------------------------------------------+--------------------------------------
| 0 | Start | | ver: 0, rows: 0, execTime: 1us, totalTime: 19us | outputVar: [ |
| | | | | { |
| | | | | "colNames": [], |
| | | | | "type": "DATASET", |
| | | | | "name": "__Start_0" |
| | | | | } |
| | | | | ] |
-----+------------------+--------------+-----------------------------------------------------+--------------------------------------

以上,源码解析 Query Engine 相关的模块就讲解完毕了,后续将讲解部分特性内容。

交流图数据库技术?加入 Nebula 交流群请先填写下你的 Nebula 名片,Nebula 小助手会拉你进群~~

【活动】Nebula Hackathon 2021 进行中,一起来探索未知,领取 ¥ 150,000 奖金 →→ nebula-graph.com.cn/hackathon/

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring Boot接口版本管理

发表于 2021-11-19

gitee链接

Spring Boot版本:2.3.4.RELEASE

目的

项目迭代升级,接口要更新,并且要在接口路径不变的清空下兼容老接口,就可以做接口版本管理,像这样:

老接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
json复制代码{
'headers': {
'apiverison': 'v1',
'apiplatform': 'web',
...
},
'url': '/user/login',
'data': {
'username': 'cc',
'password': '123'
},
...
}

新接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
json复制代码{
'headers': {
'apiverison': 'v2',
'apiplatform': 'web',
...
},
'url': '/user/login',
'data': {
'username': 'cc',
'password': '123'
},
...
}

对于前端来说,可以在headers中指定接口的版本,不需要修改接口的路径。

除了指定接口版本,还能指定接口的支持平台,比如web端、移动端。

实现

需要四个实现类,和web mvc的配置类

目录是这样的:

1
2
3
4
5
6
7
8
markdown复制代码- com.cc
- config
- version
ApiHandlerMapping
ApiPlatform
ApiVersion
ApiVersionCondition
WebMvcConfig

@ApiVersion:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码package com.cc.config.version;

import org.springframework.lang.Nullable;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD}) // 注解修饰的对象范围,TYPE:类,METHOD:方法
@Retention(RetentionPolicy.RUNTIME) // 注解保存到class和jvm内
public @interface ApiVersion {

// 标识版本号
int value();

// 兼容平台
int platform() default 0;
}

ApiPlatform:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码package com.cc.config.version;

/**
* api接口调用平台声明
* @author cc
* @date 2021-11-19 11:23
*/
public interface ApiPlatform {
/**
* 默认
*/
public static int DEFAULT = 0;

/**
* web端
*/
public static int WEB = 1;

/**
* 移动端
*/
public static int MOBILE = 2;
}

ApiHandlerMapping:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
java复制代码package com.cc.config.version;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;

public class ApiHandlerMapping extends RequestMappingHandlerMapping {
// 对类修饰的注解
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
// 判断是否有@ApiVersion注解
ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
return createCondition(apiVersion);
}

// 对方法修饰的注解
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
return createCondition(apiVersion);
}

// 创建基于@APIVersion的RequestCondition
private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) {
return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
}
}

ApiVersionCondition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
java复制代码package com.cc.config.version;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;

import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
// 路径中版本的前缀,这里写正则:\d表示一位数字,\d+表示一位以上数字,如v1,v2
private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)");

// header中的key
private final static String HEADER_VERSION = "apiversion";
private final static String HEADER_PLATFORM = "apiplatform";

// api的版本
private int apiVersion;

// api的平台
private int apiplatform;

public ApiVersionCondition(int apiVersion, int apiplatform) {
this.apiVersion = apiVersion;
this.apiplatform = apiplatform;
}

// 将不同的筛选条件合并
@Override
public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {
// 采用最后定义优先原则,则方法上的定义覆盖类上面的定义
return new ApiVersionCondition(apiVersionCondition.getApiVersion(), apiVersionCondition.getApiplatform());
}

// 根据request查找匹配到的筛选条件
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
/**
* 正则匹配请求header参数中是否有版本号
* 版本号应该以v开头,如:v1、v2,这个可以通过修改正则自定义
*/
String apiversion = request.getHeader(HEADER_VERSION);
String platformStr = request.getHeader(HEADER_PLATFORM);
int apiplatform = 0;
if (!StringUtils.isEmpty(platformStr)) {
apiplatform = Integer.parseInt(platformStr);
}

if (!StringUtils.isEmpty(apiversion)) {
Matcher m = VERSION_PREFIX_PATTERN.matcher(apiversion);
if (m.find()) {
int version = Integer.parseInt(m.group(1));

// 版本匹配到了
if (version == this.apiVersion) {

// 如果有传入平台platform参数,那么就找指定了平台的接口,找不到该接口就不通过
if (apiplatform > 0) {
if (this.apiplatform == apiplatform) {
return this;
} else {
return null;
}
}

// 如果该接口指定了平台,但是没有传platform或者传错,那么也不允许通过
if (this.apiplatform > 0 && this.apiplatform != apiplatform) {
return null;
}

return this;
}

// 没有可以匹配的接口
return null;
}
}
throw new RuntimeException("请检查header中的版本参数");
}

// 不同筛选条件比较,用于排序
@Override
public int compareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) {
return apiVersionCondition.getApiVersion() - this.apiVersion;
}

public int getApiVersion() {
return apiVersion;
}

public void setApiVersion(int apiVersion) {
this.apiVersion = apiVersion;
}

public int getApiplatform() {
return apiplatform;
}

public void setApiplatform(int apiplatform) {
this.apiplatform = apiplatform;
}
}

WebMvcConfig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
java复制代码package com.cc.config;


import com.cc.config.version.ApiHandlerMapping;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.resource.ResourceUrlProvider;

/**
* web mvc的配置类
* @author cc
* @date 2021-07-12 10:29
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

// 版本管理相关
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping(ContentNegotiationManager contentNegotiationManager, FormattingConversionService conversionService, ResourceUrlProvider resourceUrlProvider) {
RequestMappingHandlerMapping handlerMapping = new ApiHandlerMapping();
handlerMapping.setOrder(0);

return handlerMapping;
}
}

使用

后端编写接口的时候只需要添加@ApiVersion注解即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
java复制代码package com.cc.controller;

import com.cc.config.version.ApiPlatform;
import com.cc.config.version.ApiVersion;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
@ApiVersion(1)
@GetMapping("/login")
public String login() {
return "this api version is v1";
}

@ApiVersion(2)
@GetMapping("/login")
public String login2() {
return "this api version is v2";
}

@ApiVersion(value = 2, platform = ApiPlatform.WEB)
@GetMapping("/login")
public String login21() {
return "this api version is v3";
}

// 要避免这种相同版本和平台的接口,这种请求的时候会报错,因为和上面的接口重复了,系统就不知道你要请求的是哪个
// @ApiVersion(value = 2, platform = ApiPlatform.WEB)
// @GetMapping("/login")
// public String login211() {
// return "this api version is v4";
// }
}

controller里一共有三个接口,分别的调用方式是:

  1. 接口版本为v1,那么header是这样的:
1
2
3
4
5
6
7
8
json复制代码{
'headers': {
'apiverison': 'v1',
...
},
'url': '/login',
...
}

请求结果为:

this api version is v1
2. 接口版本为v2,header是这样的:

1
2
3
4
5
6
7
8
json复制代码{
'headers': {
'apiverison': 'v2',
...
},
'url': '/login',
...
}

请求结果为:

this api version is v2
3. 接口版本为v2,并且仅支持web端平台,在ApiPlatform里可以知道web端的标识是1,所以header是这样:

1
2
3
4
5
6
7
8
9
json复制代码{
'headers': {
'apiverison': 'v2',
'apiplatform': '1',
...
},
'url': '/login',
...
}

请求结果为:

this api version is v3

另外,@ApiVersion注解还能作用于类上,可以很方便的给类里所有的接口指定版本,并且因为最后定义优先原则,作用于函数上的注解会覆盖类上面的,所以像下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码package com.cc.controller;

import com.cc.config.version.ApiVersion;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@ApiVersion(1)
@RestController
public class TestController {
@GetMapping("/t")
public String t1() {
return "t1";
}

@ApiVersion(2)
@GetMapping("/t")
public String t2() {
return "t2";
}
}

t1函数的接口版本为类声明的v1,t2函数的接口版本为覆盖后的v2。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring Cloud Alibaba 学习 -- 3、S

发表于 2021-11-19

这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战

学习视频(B站):www.bilibili.com/video/BV1Mt…

GitHub 源码地址:github.com/tyronczt/sp…

简介

Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性。

雪崩效应

如果在A的链路上某个或几个被调用的子服务不可用或延迟较高,则会导致调用A服务的请求被堵住。堵住的请求会消耗占用掉系统的线程、io等资源,当该类请求越来越多,占用的计算机资源越来越多的时候,会导致系统瓶颈出现,造成其他的请求同样不可用,最终导致业务系统崩溃,又称:雪崩效应。

解决方案

  1. 设置线程超时
  2. 设置限流
  3. 熔断器 Sentinel、Hystrix
  • 降级:在高并发情况下,防止用户一直等待,使用服务降级方式(直接返回一个友好的提示给客户端,调用fallBack方法)
  • 限流:服务限流就是对接口访问进行限制,常用服务限流算法令牌桶、漏桶,计数器也可以进行粗暴限流实现。
  • 熔断:熔断机制目的为了保护服务,在高并发的情况下,如果请求达到一定极限(可以自己设置阔值)如果流量超出了设置阈值,让后直接拒绝访问,保护当前服务。

集成Sentinel

  • provider 的 pom.xml 引入依赖
1
2
3
4
5
6
7
8
9
10
xml复制代码<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • application.yml 配置
1
2
3
4
5
6
7
8
9
10
11
12
yaml复制代码# 配置限流路径
management:
endpoints:
web:
exposure:
include: "*"
# sentinel的控制面板路径
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
  • 下载 Sentinel 控制台,解压,启动,下载地址:github.com/alibaba/Sen…

用户名/密码:sentinel/sentinel

实时监控

当访问provider项目中index接口时 [http://localhost:3333/index],sentinel会将流量实时记录,每十秒刷新:

在这里插入图片描述

簇点链路

在这里插入图片描述

流控规则

在这里插入图片描述

一般阈值类型选择QPS(Query Per Second每秒查询率)

直接限流

选择单机阈值为1时,即表示每秒查询超过1时,即被流量限制,默认提示:Blocked by Sentinel (flow limiting)

关联限流

流控模式为 **关联 **时,输入关联资源,如list,即效果为:当list超过阈值时,index限制访问

模拟访问list接口:

1
2
3
4
5
6
7
java复制代码public static void main(String[] args) throws InterruptedException {
RestTemplate restTemplate = new RestTemplate();
for (int i = 0; i < 1000; i++) {
restTemplate.getForEntity("http://localhost:3333/list",String.class);
Thread.sleep(200);
}
}

效果:
在这里插入图片描述

链路限流

主要对service或dao层的接口进行限流保护。

pom.xml 添加依赖

1
2
3
4
5
6
7
8
9
10
11
xml复制代码<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.1</version>
</dependency>

<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
<version>1.7.1</version>
</dependency>

修改配置文件application.yml

1
2
3
4
5
yaml复制代码spring:
cloud:
sentinel:
filter:
enabled: false

添加配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码@Configuration
public class FilterConfiguration {

@Bean
public FilterRegistrationBean registrationBean(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new CommonFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY,"false");
registrationBean.setName("sentinelFilter");
return registrationBean;
}
}

Service层

1
2
3
4
5
6
7
8
java复制代码@Service
public class ProviderService {

@SentinelResource("provider")
public String provider(){
return "ProviderService";
}
}

Controller层

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码@Autowired
private ProviderService providerService;

@GetMapping("/provider1")
public String provider1() {
return providerService.provider();
}

@GetMapping("/provider2")
public String provider2() {
return providerService.provider();
}

配置规则
在这里插入图片描述

访问:http://localhost:3333/provider2,当QPS超过1时,提示500

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

LeetCode 34 在排序数组中查找元素的第一个和最后

发表于 2021-11-19

「这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战」。

1、题目

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:

  • 你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例 1:

1
2
ini复制代码输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

1
2
ini复制代码输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

1
2
ini复制代码输入:nums = [], target = 0
输出:[-1,-1]

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • num 是一个非递减数组
  • -109 <= target <= 109

2、思路

(二分) O(logn)O(logn)O(logn)

在一个范围内,查找一个数字,要求找到这个元素的开始位置和结束位置,这个范围内的数字都是单调递增的,即具有单调性质,因此可以使用二分来做。


两次二分,第一次二分查找第一个>=target的位置,第二次二分查找最后一个<=target的位置。查找成功则返回两个位置下标,否则返回[-1,-1]。

实现细节:

  • 二分查找时,首先要确定我们要查找的边界值,保证每次二分缩小区间时,边界值始终包含在内。

第一次查找起始位置:

  • 1、二分的范围,l = 0, r = nums.size() - 1,我们去二分查找>=target的最左边界。
  • 2、当nums[mid] >= target时,往左半区域找,r = mid。

  • 3、当nums[mid] < target时, 往右半区域找,l = mid + 1。

  • 4、如果 nums[r] != target,说明数组中不存在目标值 target,返回 [-1, -1]。否则我们就找到了第一个>=target的位置L。

第二次查找结束位置:

  • 1、二分的范围,l = 0, r = nums.size() - 1,我们去二分查找<=target的最右边界。
  • 2、当nums[mid] <= target时,往右半区域找,l = mid。

  • 3、当nums[mid] > target时, 往左半区域找,r = mid - 1。

  • 4、找到了最后一个<=target的位置R,返回区间[L,R]即可。

时间复杂度分析: 两次二分查找的时间复杂度为 O(logn)O(logn)O(logn)。

3、c++代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
c复制代码class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.empty()) return {-1,-1};

int l = 0, r = nums.size() - 1; //二分范围
while( l < r) //查找元素的开始位置
{
int mid = (l + r )/2;
if(nums[mid] >= target) r = mid;
else l = mid + 1;
}
if( nums[r] != target) return {-1,-1};
int L = r;
l = 0, r = nums.size() - 1;
while( l < r) //查找元素的结束位置
{
int mid = (l + r + 1)/2;
if(nums[mid] <= target ) l = mid;
else r = mid - 1;
}
return {L,r};
}
};

4、java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码class Solution {
public int[] searchRange(int[] nums, int target) {
if(nums.length == 0) return new int[]{-1,-1};

int l = 0, r = nums.length - 1; //二分范围
while( l < r) //查找元素的开始位置
{
int mid = (l + r )/2;
if(nums[mid] >= target) r = mid;
else l = mid + 1;
}
if( nums[r] != target) return new int[]{-1,-1};
int L = r;
l = 0; r = nums.length - 1;
while( l < r) //查找元素的结束位置
{
int mid = (l + r + 1)/2;
if(nums[mid] <= target ) l = mid;
else r = mid - 1;
}
return new int[]{L,r};
}
}

原题链接: 34. 在排序数组中查找元素的第一个和最后一个位置
在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot定时任务功能 一、开启功能 二、指定执行

发表于 2021-11-19

项目开发经常需要异步处理一些业务,这时候我们经常会用到定时任务。Spring 提供了@Scheduled注解,开发者只需简单配置即可使用。

一、开启功能

启动类添加注解@EnableScheduling,表示开启定时任务
image.png

二、指定执行逻辑

在需要定时执行的方法上加上注解@Scheduled,方法所在的类需要加上@Component,如下所示
image.png

@Scheduled参数说明

参数 参数说明 示例
cron 任务执行的cron表达式 0/5 * * * * ?
zone cron表达时解析使用的时区,默认为服务器的本地时区,使用java.util.TimeZone#getTimeZone(String)方法解析 GMT-8:00
fixedDelay 上一次任务执行结束到下一次执行开始的间隔时间,单位为ms 3000
fixedDelayString 上一次任务执行结束到下一次执行开始的间隔时间,使用java.time.Duration#parse解析 PT15M
fixedRate 以固定间隔执行任务,即上一次任务执行开始到下一次执行开始的间隔时间,单位为ms,若在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后立即执行下一次任务 5000
fixedRateString 与fixedRate逻辑一致,只是使用java.time.Duration#parse解析 PT15M
initialDelay 首次任务执行的延迟时间 10000
initialDelayString 首次任务执行的延迟时间,使用java.time.Duration#parse解析 PT15M

三、启动

启动项目后,方法就能定时执行

四、cron表达式生成工具

Cron表达式工具:www.pppet.net/

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Dataphin核心功能(四) 安全——基于数据权限分类分级

发表于 2021-11-19

简介: 《数据安全法》的发布,对企业的数据安全使用和管理提出了更高的要求。Dataphin提供基于数据分级分类和数据脱敏的敏感数据识别和保护能力,助力企业建立合规的数据安全体系,保障企业数据安全。本篇,我们就来聊聊Dataphin的数据安全能力。

作者:龙裔

1、企业的数据安全挑战

“2021年6月10日,经第十三届全国人民代表大会常务委员会第二十九次会议审议,通过了《中华人民共和国数据安全法》(简称‘《数据安全法》’),该法将于2021年9月1日起施行。”

近几年,随着消费者个人意识的崛起和对隐私的重视,数据安全成为了一个越来越热门的话题,国家也陆续发布了一些相关规定,来规范数据的采集和使用。而《数据安全法》的正式发布,标志着数据的开发利用和数据的安全保障正式进入法律范畴,从而对企业的数据安全管控提出了更高的要求。

在企业的发展过程中,如果不重视敏感数据的保护,不重视数据安全体系的建设,那么一旦发生了敏感数据泄漏事件,轻则企业口碑受损,业务受影响;重则会直接触法律,受到主管部门的处罚和制裁。对企业来说,可以考虑以下措施,来构建合规的数据体系:

1、树立全面的数据合规理念。对数据安全相关的法律法规进行关注和及时响应,同时建立企业内部的安全合规团队,对企业数据安全的方方面面进行安全设计和审计。

2、事前:做好数据分类分级和敏感数据保护。基于法律法规、主管部门要求和自身行业和业务的需要,建立企业的数据分类分级制度,并对敏感数据制定相应的保护策略。

3、事中:做好风险审计和风险识别。成立专门的数据安全小组,定期开展数据安全的风险评估,及时发现风险,消灭风险。

4、事后:做好事故追溯和补救措施,加固安全措施。出现安全事故后,要立即追责,同时视影响的严重程度,上报主管部门,并即时进行止损,将影响降低到最小。

本文,我们重点关注一下数据资产安全能力,资产安全为Dataphin提供了数据生命周期中统一的敏感数据识别与保护能力。通过数据分类分级、敏感数据识别、敏感数据脱敏等措施,帮助客户建立完善的数据安全体系,确保数据使用的安全合规。

2、Dataphin的数据安全能力

数据安全如此重要,当然不能只靠人来治理,而需要有一套成熟的产品和流程来进行安全的管控。这时候,企业一般面临着自建系统和采购第三方安全系统的决策问题。

选择企业自己建设数据安全产品,存在较多的弊端:

1、消耗大量的研发资源,且后续需要不断投入资源进行维护升级

2、在产品的设计和实现上可能存在漏洞,导致安全隐患

3、存在生产和实际存在偏差,导致无法落地到实际生产中的情况。比如数据安全产品和数据生产平台结合不紧密;产品功能没有满足国家的数据安全标准等。

对比企业自建数据安全产品,使用Dataphin数据安全产品,存在以下优势:

1、Dataphin数据安全能力齐全,开箱即用,帮助您低成本的快速构建数据安全体系

2、Dataphin数据安全能力和数据开发过程紧密结合,确保数据开发全链路上的数据安全

3、Dataphin实时跟踪政策和行业动态,不断升级安全能力,保障您的企业一直享受先进技术的保护

4、Dataphin有完善的专家咨询和专业化服务,帮助您更好的在企业构建数据安全体系

Dataphin作为智能数据构建与管理平台,作为企业数字化转型中的核心引擎,对数据生产和管理过程中的数据安全,当然十分重视。Dataphin提供了完整的产品能力,来保障数据建设中的数据安全,并且整个安全体系和数据研发生产紧密结合,确保数据从进入Dataphin开始,到从Dataphin输出的全链路,都安全可控。

图1:Dataphin安全概览

Dataphin安全能力图解

在Dataphin的能力大图上,安全模块出现在资产管理模块,但在实际的安全体系中,从系统的底座安全、研发过程的数据处理安全、到数据资产的管理、数据消费的安全,都会涉及到并提供安全保障。

当前Dataphin提供了以下安全能力来确保客户的数据安全:

1、底座安全:保障底层的系统安全和网络安全,这一部分主要由云底座提供安全保障。在底座的安全措施之外,Dataphin提供了租户隔离、网络控制、敏感信息安全加密存储等安全功能,确保系统的底层安全。

2、平台安全(权限):在Dataphin内部,提供了完整的角色体系,以及权限申请与审批功能。让企业能够实现精细化授权管理,可以对用户执行最小粒度的授权和管控,防止权限漏洞。

3、数据安全:Dataphin提供了数据分类分级、敏感数据识别和脱敏功能,来保障数据流转过程中的数据安全。通过敏感数据保护功能,在不改变底层数据的情况下,保障日常流转中展示的数据都是加密脱敏后的数据,确保敏感数据不泄漏。

4、安全服务:为了更好的帮助客户建设数据安全体系,Dataphin还集成了众多生态产品、专家服务和文档服务,保障客户建立起完善的数据安全体系。

图2:Dataphin安全能力图解(简化版)

上图是Dataphin安全能力的整体图解,为了方便理解,简化了模块内部的详细功能。可以看到,Dataphin对整个数据的生产和管理体系,进行了全方位的数据安全保障。

3、Dataphin数据安全应用场景

首先,我们先看一下数据安全模块的应用场景,从而对数据安全的价值有一个更直观的认知。以下是通过Dataphin实现数据安全保护的一些典型的场景:

场景1:数据业务中的敏感数据保护

在日常的数据业务运转中,数仓工程师/数据研发、数据分析师/业务分析师,需要经常接触数据,包括对数据的查询、统计、修改等。在这个过程中,存在着大量的数据泄漏的风险,比如可以直接查询到用户的姓名、手机号等。虽然可以通过授权的方式,严格控制人员的数据权限,但是因为接触的是明文的敏感信息,所以仍然存在数据泄漏的风险。

而基于Dataphin的敏感数据识别和保护能力,可以让敏感数据在日常的流转和查询中,对外展示的始终是脱敏之后的数据,如姓名【张三】显示为【*三】,手机【18612345678】显示为【186****5678】,确保数据在流转过程中,不会异常泄漏。

场景2:脱敏白名单的灵活运用

前面两个场景简单介绍了在正常场景下对数据的保护,那在有些场景下,有需要看到最原始的数据,那么就需要用到脱敏白名单的功能,在特定的时间,对特定的用户或者角色开放原始数据。

场景1:对于企业中一些比较敏感的数据,比如上市公司的财务数据,特殊人员(如高层级的员工、公司宏观决策支持分析师)在一定时间段(比如公司财报发布前一个月)是可以看到明文,但是一般人员或这些人员其他时间不可以,就可以通过设置脱敏的白名单及有效时间来实现。

场景2:对于电商每天的销售额,正常情况下不能展示真实数字,一般都是脱敏展示为***元,但是在双十一等特殊场景,需要显示真实销售额用于宣传的情况下,可以开启为期一天的白名单,可以看到当天的销售额数据。

4、如何使用Dataphin实现敏感保护

那么如何利用Dataphin的安全能力,来保障企业的数据安全呢?

在Dataphin中,实现敏感数据保护,主要可以分为以下三个步骤:

1、识别敏感数据:即设定数据分类、数据分级、识别规则等内容

2、设置敏感数据保护方式:为识别的敏感数据选择合适的脱敏算法、设定脱敏规则

3、数据消费:在即席查询、开发数据写生产等场景进行数据消费时脱敏

通过Dataphin实现数据安全体系建设的详细过程可以参考:如何基于Dataphin实现敏感数据保护

图3:Dataphin数据安全核心操作流程图

5、未来展望

虽然当前Dataphin已经有了比较完善的数据安全体系,但是基于客户需求的多样性以及对政策的研究与响应,未来会陆续支持以下功能和优化,从而帮助客户更好的构建数据安全体系,实现业务发展的安全与合规。

1、数据安全审计:提供安全审计功能,详细的记录用户对敏感数据的每一次查询、下载操作,从而发现风险操作,进行事故追责和体系优化。

2、风险自动发现与告警:基于规则和算法,自动发现异常的用户操作,并进行告警提示,及时发现风险、阻断风险,将数据风险的影响最小化

3、更多业务场景中的安全脱敏:支持数据集成和数据服务过程中的数据脱敏,确保数据的每一次消费和使用都安全可控,将敏感数据泄漏的风险从源头消灭。

4、集成更多的生态产品和专家服务,帮助客户更好的建立起可持续、可运营、有效率、有效果的数据安全体系。

在新的法律环境和数据安全的挑战下,Dataphin也会不断想客户所想,以创造更大的客户价值为己任,持续增强数据安全能力,来帮助客户建立完善的数据安全体系,为客户的业务发展保驾护航。

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot之Aspect切面使用:一、reque

发表于 2021-11-19

参考文档:blog.csdn.net/zhibo_lv/ar…

为了防止博主大神帖子丢失,重新复写了一下

新建HttpHelper (用于读取Body)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
java复制代码package com.xxx.util.core.filter.request;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

public class HttpHelper {
public static String getBodyString(HttpServletRequest request) throws IOException {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}

新建RequestReaderHttpServletRequestWrapper(防止流丢失)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
java复制代码package com.xxx.util.core.filter.request;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class RequestReaderHttpServletRequestWrapper extends HttpServletRequestWrapper{

private final byte[] body;

public RequestReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
}

@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}

@Override
public ServletInputStream getInputStream() throws IOException {

final ByteArrayInputStream bais = new ByteArrayInputStream(body);

return new ServletInputStream() {

@Override
public int read() throws IOException {
return bais.read();
}

@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener readListener) {

}
};
}
}

新建HttpServletRequestReplacedFilter(过滤器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
java复制代码package com.xxx.util.core.filter.request;


import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;


public class HttpServletRequestReplacedFilter implements Filter {
@Override
public void destroy() {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(request instanceof HttpServletRequest) {
requestWrapper = new RequestReaderHttpServletRequestWrapper((HttpServletRequest) request);
}
//获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
// 在chain.doFiler方法中传递新的request对象
if(requestWrapper == null) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}

@Override
public void init(FilterConfig arg0) throws ServletException {

}
}

最后我们只需要在Application.java中加上如下代码注入过滤器即可(好多博文就是没有这关键的异步)

1
2
3
4
5
6
7
8
9
10
java复制代码@Bean
public FilterRegistrationBean httpServletRequestReplacedRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new HttpServletRequestReplacedFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter("paramName", "paramValue");
registration.setName("httpServletRequestReplacedFilter");
registration.setOrder(1);
return registration;
}

如下代码即可在拦截其中获取body且保证了controller中依旧可以再次获取

1
vbscript复制代码HttpHelper.getBodyString(request);

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python数据分析-文件读取 txt、Csv、Excel、

发表于 2021-11-19

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

txt、Csv、Excel、JSON、SQL文件读取(Python)

txt文件读写

创建一个txt文件

image-20211117191500810

1
2
3
4
python复制代码f=open(r'text.txt','r',encoding='utf-8')
s=f.read()
f.close()
print(s)

image-20211117192720306

open( )是打开文件的方法

‘text.txt’文件名 在同一个文件夹下所以可以省略路径

如果不在同一个文件夹下 ‘xxx/xxx/text.txt’ 文件名前加路径

encoding:设置字符编码

read( )是读取文件内容

close( )是关闭文件

with

open( )函数方法打开文件读取文件内容时,如果不关闭文件,将无法对该文件进行修改。当打开文件并写入文件内容后,不关闭文件会造成写入的内容不能保存。

在Python语言中,提供了with与open( )函数方法搭配使用

通过with与open( )函数搭配使用无须再去书写close( )函数方法

1
2
3
python复制代码with open(r'text.txt','r',encoding='utf-8') as f:
s=f.read()
print(s)

image-20211117193040278

写入

1
2
python复制代码with open(r'text.txt','w') as f:
f.write('qwertyuiop')

image-20211117194905626

写入多行

1
2
3
python复制代码with open(r'text.txt','w') as f:
text=['asdfghjk\n','xcvbnmrtyui\n','123456789\n']
f.writelines(text)

image-20211117195100045

open(r’text.txt’,’w’)函数中,’w’参数意为写入,会将文件原有的内容进行覆盖

文件打开模式

  • r 只读 只读默认模式
  • w 只写 在原文件写,覆盖原文件
  • a 只写 不覆盖原文件,末尾追加
  • wb 写入 以二进制形式写入,保存图片时使用
  • r+ 读写 不覆盖原文件,末尾追加
  • w+ 读写 在原文件写,覆盖原文件
  • a+ 读写 不覆盖原文件,末尾追加

CSV文件读写

read_csv( )

读取当前目录下的text.csv

1
2
3
python复制代码import pandas as pd
a=pd.read_csv(r'text.csv')
print(a)

image-20211118154707796

设置字段

1
2
3
python复制代码import pandas as pd
a=pd.read_csv(r'text.csv',names=['id','name'])
print(a)

image-20211118154925920

指定相应的索引列

1
2
3
python复制代码import pandas as pd
a=pd.read_csv(r'text.csv',names=['id','name'],index_col='id')
print(a)

image-20211118155101296

1
2
3
python复制代码import pandas as pd
a=pd.read_csv(r'text.csv',names=['id','name'],index_col=0)
print(a)

image-20211118155936730

获取指定列

1
2
3
4
5
python复制代码import pandas as pd
a=pd.read_csv(r'text.csv',names=['id','name'],usecols=[0])
print(a)
b=pd.read_csv(r'text.csv',names=['id','name'],usecols=['id'])
print(b)

image-20211118160111622

写入

to_csv( )

1
2
3
4
python复制代码import pandas as pd
data={'id':['1','2','3'],'name':['gh','jk','ty']}
a=pd.DataFrame(data)
a.to_csv(r'text.csv')

image-20211118160721939

设置写入列

1
2
3
4
python复制代码import pandas as pd
data={'id':['1','2','3'],'name':['gh','jk','ty']}
a=pd.DataFrame(data)
a.to_csv(r'text.csv',columns=['id'])

image-20211118160925017

设置写入模式

mode w为写(覆盖) a为追加

1
2
3
4
5
python复制代码import pandas as pd
data={'id':['1','2','3'],'name':['gh','jk','ty']}
a=pd.DataFrame(data)
a.to_csv(r'text.csv')
a.to_csv(r'text.csv',mode='a')

image-20211118161148822

是否写入列名字段

header

1
2
3
4
5
python复制代码import pandas as pd
data={'id':['1','2','3'],'name':['gh','jk','ty']}
a=pd.DataFrame(data)
a.to_csv(r'text.csv')
a.to_csv(r'text.csv',mode='a',header=False)

image-20211118161439353

第二次写入不写入列名

删除索引

index=None

1
2
3
4
5
python复制代码import pandas as pd
data={'id':['1','2','3'],'name':['gh','jk','ty']}
a=pd.DataFrame(data)
a.to_csv(r'text.csv',index=None)
a.to_csv(r'text.csv',mode='a',header=False,index=None)

image-20211118161615766

Excel文件读写

read_excel( )

参数:

sheet_name=’name’为读取的分表名,可以写表名、位置下标。

index_col为指定相应的索引列,为字段名或者字段列表下标。

usecols为获取指定列

names为设置列字段

header为用哪一行做字段名

nrows为指定获取的行数

skiprows为跳过特定行,skipfooter跳过末尾n行

1
2
3
python复制代码import pandas as pd
a=pd.read_excel(r'text.xlsx')
print(a)

image-20211118163152122

image-20211118163158707

选择表

sheet_name

image-20211118163248615

新建一个表

1
2
3
python复制代码import pandas as pd
a=pd.read_excel(r'text.xlsx',sheet_name=1)
print(a)

image-20211118163411089

设置索引列

index_col

1
2
3
python复制代码import pandas as pd
a=pd.read_excel(r'text.xlsx',sheet_name=0,index_col=[0])
print(a)

image-20211118163509449

获取指定列

usecols

1
2
3
python复制代码import pandas as pd
a=pd.read_excel(r'text.xlsx',sheet_name=0,usecols=[0])
print(a)

image-20211118163733752

设置列字段

names

1
2
3
python复制代码import pandas as pd
a=pd.read_excel(r'text.xlsx',sheet_name=0,names=['ID','NAME','CLASS'])
print(a)

image-20211118163832628

指定某行为字段名

header

1
2
3
python复制代码import pandas as pd
a=pd.read_excel(r'text.xlsx',sheet_name=0,header=1)
print(a)

image-20211118164019711

设置获取行数

nrows

1
2
3
python复制代码import pandas as pd
a=pd.read_excel(r'text.xlsx',sheet_name=0,nrows=2)
print(a)

image-20211118164126982

跳过n行

skiprows 跳过前n行

1
2
3
python复制代码import pandas as pd
a=pd.read_excel(r'text.xlsx',sheet_name=0,skiprows=1)
print(a)

image-20211118164512708

skipfooter跳过末尾n行

1
2
3
python复制代码import pandas as pd
a=pd.read_excel(r'text.xlsx',sheet_name=0,skipfooter=3)
print(a)

image-20211118164541263

写入

1
2
3
4
python复制代码import pandas as pd
data={'id':[1,2,3,4],'name':['A','B','C','D']}
a=pd.DataFrame(data)
a.to_excel(r'text.xlsx')

image-20211118170357301

写入多表

1
2
3
4
5
6
7
8
python复制代码import pandas as pd
data={'id':[1,2,3,4],'name':['A','B','C','D']}
a=pd.DataFrame(data)
writer = pd.ExcelWriter(r'text.xlsx')
a.to_excel(writer,sheet_name='1')
a.to_excel(writer,sheet_name='2')
writer.save()
writer.close()

image-20211118192201277

image-20211118192210232

写入新分表

1
2
3
4
5
6
7
8
9
10
11
python复制代码import pandas as pd
import openpyxl
book = openpyxl.load_workbook(r'text.xlsx')
writer=pd.ExcelWriter(r'text.xlsx')
writer.book=book
writer.sheets=dict((ws.title,ws) for ws in book.worksheets)
data={'id':[5,2,8,4],'name':['H','B','C','D']}
a=pd.DataFrame(data)
a.to_excel(writer,sheet_name="3")
writer.save()
writer.close()

image-20211118192654125

JSON文件读写

read_json()

1
2
3
python复制代码import pandas as pd
a=pd.read_json(r'text.json',encoding='utf8')
print(a)

image-20211118194544366

序列化

1
2
3
4
5
python复制代码import pandas as pd
a=pd.read_json(r'text.json',encoding='utf8')
b=pd.json_normalize(a.data)
print(a)
print(b)

image-20211118194921915

写入

to_json( )

force_ascii为数据编码格式,默认为True,中文以Unicode形式写入,如果为False,中文以ANSI形式写入。

1
2
3
4
python复制代码import pandas as pd
data={'id':[1,2,3],'name':['a','b','c']}
a=pd.DataFrame(data)
a.to_json('text.json',force_ascii=False)

image-20211118200027122

SQL文件读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
python复制代码import pymysql
con = pymysql.connect(
host="127.0.0.1",
port=3306,
user='root',
password='123456',
db='test03',
charset='utf8'
)
# 创建游标
cursor=con.cursor()
# 执行sql语句
cursor.execute("select * from test")
# 解释全部返回结果
res=cursor.fetchall()
print(res)
con.close()

image-20211119091834244

Pandas读取MySQL数据库内容

1
2
3
4
5
6
7
8
9
10
11
12
13
python复制代码import pymysql
import pandas as pd
con = pymysql.connect(
host="127.0.0.1",
port=3306,
user='root',
password='123456',
db='test03',
charset='utf8'
)
sql="select * from test"
pd=pd.read_sql_query(sql,con)
print(pd)

image-20211119092109695

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

操作符这块,你可得把握住(下)

发表于 2021-11-19

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战」


最近,想复习一下C语言,所以笔者将会在掘金每天更新一篇关于C语言的文章! 各位初学C语言的大一新生,以及想要复习C语言/C++知识的不要错过哦! 夯实基础,慢下来就是快!


~ 按位取反操作符

1
c复制代码对二进制序列进行取反

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
c复制代码int main()
{
int a = 1;
int b = 0;
b = ~a;
printf("%d\n",b); //-2
}
//a : 00000000 00000000 00000000 00000001
~a: 11111111 11111111 11111111 11111110 -补码
符号位为1,负数
要转化为原码,%d打印的是原码
对补码求补码就是原码
11111111 11111111 11111111 11111110 -补码

10000000 00000000 00000000 00000001 -反码符号位不变其他位按位取反
10000000 00000000 00000000 00000010 - 反码+1
->值为-2

6.程序题:如何把二进制序列某一位为0的翻转1

或运算:某一位或上0,对应的位不翻转。

某一位或上1,如果该位为1,不翻转。如果为0,翻转

方法:使某一位二进制数翻转为1,就或上其他为0,只有对应该位的二进制为1的数

image.png

1
2
3
4
5
6
7
8
9
10
c复制代码//使0的某一位翻转  
int main()
{
int a = 0;
int n = 0;
scanf("%d",&n); //第几位翻转
int b = a | (1 << (n-1));
printf("%d对应二进制序列第%d位翻转的结果为%d\n",a,n,b);
return 0;
}

想要第n个二进制位变成1 只需要 按位或(|)上 1<<n-1即可 ( 1左移n-1位的数)


7,程序题,如何把二进制序列某一位为1的翻转0

方法: 1左移n-1位的数按位取反 再与上该数即可

image.png

1
2
3
4
5
6
7
8
c复制代码int main()
{
int a = 15; //0000 1111
//使第三(第n)位翻转为0
a &= ~(1<<2);
printf("%d\n",a);//11 0000 1011
return 0;
}

以8bit为例子

1左移0位:0000 0001

1左移1位:0000 0010


1
2
3
4
5
6
7
8
9
10
11
c复制代码int main()
{
int a =13; //0000 1101
a |=(1<<1); //相当于 a = a|(1<<1) 即使a二进制序列第二位翻转为1
printf("%d\n",a);//0000 1111 -15

//恢复
a &=(~(1<<1)); //a = a&(~(1<<1)) 即使a的二进制序列第二位翻转为0
printf("%d\n",a);//0000 1101 -13
return 0;
}

&& || 逻辑与 逻辑或

C语言中0表示假,非0表示真


&&发生短路现象:

1
2
c复制代码   A&&B&&C   当A,B,C同时为真时,结果才为真  
A,B,C中有一个为假(0)了,就终止后序计算
1
2
3
4
5
6
7
8
9
10
c复制代码int main()
{
int a =0;
int b = 1;
int c = 1;
int d = a++ && b++ && c++;
printf("%d %d %d %d\n",a,b,c,d);//1 1 1 0
//原因:a++为后置++,先使用a的值,为0,直接终止运算,然后a的值变成1,后续的i表达式不执行
return 0;
}

||发生短路现象:

1
2
c复制代码A || B || C   A,B,C只要有一个为真,结果就为真
A,B,C中有一个为真(非0为真)了,就终止后序计算

1
2
3
4
5
6
7
8
9
10
c复制代码int main()
{
int a = 0;
int b = 1;
int c = 1;
int d = a++ || b++ || c++;
printf("%d %d %d %d\n", a, b, c, d);//1 2 1 1
///原因:a++为后置++,先使用a的值,为0,b++,后置++,先使用b的值,b为1,条件为真,终止后序运算, 然后a++变成1,b++变成2
return 0;
}

唯一的三目操作符 ? :

1
2
3
4
c复制代码P? A:B ;   
如果P为真,执行A,否则执行B

如:C= A > B? A:B; ->求两个数的最大值 如果A>B为真。C = A,否则C = B

逗号表达式

逗号表达式的结果为最后一个式子

1
2
3
4
5
6
7
8
9
10
11
12
13
c复制代码int main()
{
int a = 1;
int b = 3;
int c = 0;
// c =a++, b++, a + 1;
//printf("%d %d %d\n",a,b, c); // 2 4 1 并不是2 4 3
//原因:赋值表达式= 优先级比逗号表达式高,所以相当于c=a++,然后再执行后面的表达式
//正解
c = (a++, b++, a + 1);
printf("%d %d %d\n",a,b, c); //2 4 3
return 0;
}

1
2
3
4
5
6
7
8
c复制代码a = get_val();
count_vao(a);
while(a>0)
{
//处理
a = get_val;
count_val(a);
}

—>上面的表达式也可以用逗号表达式

1
2
3
4
c复制代码while(a = get_val(),count_val(a),a > 0)
{
//处理
}

[] 操作符

1
2
3
4
5
6
7
8
9
10
c复制代码int main()
{
int arr[] = {1,2,3,4,5};
int i = 0;
for(i = 0;i < 5;i ++)
{
printf("%p----%p\n",&arr[i],arr+i);
}
return 0;
}

image.png


数组名是首元素地址,所以 arr+i: 下标为i的元素的地址


1
2
3
4
5
C复制代码arr[4] :[]是操作符,arr和4是它的两个操作数

arr[4] == *(arr+4) 加法支持交换律 -> *(4+arr) ->4[arr]

arr[4] 和*(arr+4)对于 所以*(4+arr) -->对于4[arr]

image.png
再一次说明[]是操作符 arr和4是操作数而已


():函数调用操作符

如:

strlen() 函数:返回的是无符号整形


1
2
3
c复制代码// /0  不算进字符串长度
printf("%d\n",strlen("abc"));//3
printf("%u\n",strlen("abc"));//3

对于函数调用操作符 ()

接收一个或者多个操作数,第一个操作数是函数名,剩余的操作数就是传递给函数的参数


如: my_Add(a,b) ()有3个操作数为 my_Add (函数名) a , b


结构体成员访问操作符 -> .

创建结构体类型本身不占用内存空间,创建结构体变量时才开辟空间

1
2
3
4
5
6
7
8
9
10
c复制代码struct Stu
{
int age; //结构体成员
char name[20]; //结构体成员
}s1,s2;
struct Stu s3;

//struct Stu是结构体类型
// s1,s2为结构体全局变量
// s3为结构体局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
c复制代码struct Stu
{
int age;
char name[20];
};
int main()
{
struct Stu s[3] = { {20,"zhangsan"} ,{30,"lemon"} ,{10,"shuai"} };
int i = 0;

//方法1:结构体变量用.访问
for (i = 0; i < 3; i++)
{
printf("%d %s\n", s[i].age, s[i].name);
}

//方法2:结构体指针用->访问
struct Stu* p = &s[0];
printf("%d %s\n", p->age,p->name);

//方法3:解引用结构体访问
printf("%d %s\n", (*p).age, (*p).name);

return 0;
}

注意事项:

不可以直接把字符串放进结构体里面的数组中。要使用strcpy函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
c复制代码#include<string.h>
int main()
{
struct Stu
{
int age;
char name[20];
};
struct Stu s;
s.age = 10;
//s.name = "mango"; //err,name是数组名,是常量地址,应该把要改的字符串放到name指向的空间里

//正解 -字符串拷贝函数strcpy()
strcpy(s.name, "数据结构");

printf("%d %s\n", s.age, s.name);
}

1
2
3
4
5
6
c复制代码总结:
结构成员访问操作符

方法1: 结构体变量.成员名
方法2: 结构体指针->成员名
方法3: (*结构体指针).成员名

6.整形提升

当计算的两个操作数的类型为short或者char,会发生整形提升,提升为int


7.算术转化

大于4个字节的类型变量进行计算,发生的是算术变化,往高字节的类型转化,最后的结果类型为参加计算的变量类型中字节最大的。

image.png

例如:

1
2
3
4
5
6
7
8
9
10
c复制代码int a = 0;
float b = 0.0f; //C语言的小数默认是double类型,如果想为float类型,就在初始值后面加f
double c = 0.0;
d = a+b+c;
问:d是什么类型?
-》往高字节转化,最后结果为字节数最大的类型
字节:double 8byte
int 4 byte
float 4byte
所以d为double类型

今天就先到这吧~感谢你能看到这里!希望对你有所帮助!欢迎老铁们点个关注订阅这个专题! 同时欢迎大佬们批评指正!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

日志服务Dashboard加速 背景 方案 配置 总结

发表于 2021-11-19

简介: 阿里云日志服务致力于为用户提供统一的可观测性平台,同时支持日志、时序以及Trace数据的查询存储。用户可以基于收集到的各类数据构建统一的监控以及业务大盘,从而及时发现系统异常,感知业务趋势。但是随着收集到的数据量不断增长,特别是遇到业务峰值的时候,大盘报表展示会产生明显的延迟,无法及时查看重要数据。Scheduled SQL支持定时分析数据、存储聚合数据、投影与过滤数据,并将执行的分析结果存入用户指定的日志库或者时序库中,供用户后续分析使用。由于在聚合后数据量将大大小于之前,因而非常适合进行即时数据分析以及大盘展示。

背景

阿里云日志服务致力于为用户提供统一的可观测性平台,同时支持日志、时序以及Trace数据的查询存储。用户可以基于收集到的各类数据构建统一的监控以及业务大盘,从而及时发现系统异常,感知业务趋势。但是随着收集到的数据量不断增长,特别是遇到业务峰值的时候,大盘报表展示会产生明显的延迟,无法及时查看重要数据。Scheduled SQL支持定时分析数据、存储聚合数据、投影与过滤数据,并将执行的分析结果存入用户指定的日志库或者时序库中,供用户后续分析使用。由于在聚合后数据量将大大小于之前,因而非常适合进行即时数据分析以及大盘展示。下面我们以服务的请求成功率为例,介绍下如何基于Scheduled SQL加速大盘报表。

方案

假如我们需要查看以一分钟为粒度,一小时内的请求成功率。在构建报表的时候,需要基于当前不足一分钟的部分数据配置实时报表,而针对之前已满一分钟的历史数据配置历史报表。当然,如果用户觉得一分钟的数据延迟是可以接受的,就可以只配置历史报表,而不需要实时报表。假如当前时间为11:09:47,需要查看10:11:00一直到11:09:00的分钟级请求成功率,以及11:09:00到11:09:47的秒级成功请求率。

日志内容

历史报表

如图所示展示了分钟级的请求成功率,可以通过配置分钟级的ScheduledSQL任务,计算每分钟的成功率,并通过历史报表直接展示。因为只需要直接拉取聚合结果,不需要即时计算,所以展示速度大大提升。

实时报表

如图所示展示了秒级的请求成功率,因为只需要计算不到一分钟的数据,而不是1小时的数据,因而速度也得到的提升。

配置

下面仍然以请求成功率为例,向大家介绍下如何实现通过ScheduledSQL加速报表。

创建目标时序库

首先需要创建目标时序库存储ScheduledSQL的聚合数据。

创建Scheduled SQL任务

在存储服务请求的数据logstore查询界面,输入查询语句,点击查询/分析按钮,在成功执行查询分析之后,点击创建Scheduled SQL按钮。

1
sql复制代码*| select  (__time__ - __time__ % 60) as time , sum(IF(status = 200, 1, 0)) * 1.0 / count(*) AS success_ratio  from log group by time  order by time

计算配置

  1. 填入对应的作业名以及作业描述,写入模式选择日志库导入时序库;
  2. 指标列指选择结果中的一列作为时序结果,此处选择success_ratio;
  3. Labels指选择结果中的哪几列作为时序数据的标签,此处留空即可;
  4. 时间列指时序数据的时间,此处选择time;
  5. 目标库选择刚刚创建的目标时序库;

调度配置

因为我们需要查看分钟级别的服务请求成功率,所以调度间隔还有SQL时间窗口均需要以分钟为粒度。用户也可以根据自己的需求进行调整。

  1. 调度间隔选择1分钟;
  2. SQL时间窗口填入@m - 1m ~ @m;
  3. 点击确认创建任务

查看任务详情

在作业菜单中点击Scheduled SQL,即可查看Scheduled SQL任务列表。点击刚刚创建的任务名称即可查看任务执行详情。在任务执行成功之后,我们就可以创建历史报表了。

配置历史报表

在目标时序库查询界面,执行查询语句,点击添加到仪表盘,即可创建历史报表。

1
csharp复制代码* | select promql_query_range('success_ratio') from metrics limit 1000

配置实时报表

在存储服务请求的数据logstore查询界面,输入查询语句,并选择时间范围为1分钟,点击添加到仪表盘创建实时报表:

1
sql复制代码*| select  __time__ as time , sum(IF(status = 200, 1, 0)) * 1.0 / count(*) AS success_ratio  from log group by time  order by time

总结

Scheduled SQL为用户周期性的进行分析数据、存储聚合数据、投影与过滤数据提供了较大的便利。用户还可以使用Scheduled SQL定时执行聚合任务,减少即时查询所需要的数据量,从而加速大盘展示。

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…280281282…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%