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

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


  • 首页

  • 归档

  • 搜索

性能测试小工具 wrk 可以怎么用

发表于 2021-10-30

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金

工作中,项目设计之初或者是项目快要结束的时候,大佬就会问我们,这个服务性能测试的结果是什么,QPS 可以达到多少,RPS 又能达到多少?

你自己写的接口性能可以满足未来生产环境的实际情况吗?有没有自己测试过自己接口的吞吐量等等

作为设计开发人员,这些问题不仅仅是用来面试,还是实实在在的落地在实际工作中

很多项目上线初期用户量较小,表面上看是风平浪静,实则暗流涌动,慢慢的用户量上来之后,系统的瓶颈慢慢凸显

曾经挖的坑,最后还是要我们自己来填,若不能及时填上,可能整个产品就这么断送了

今天一起来看看 wrk 轻量级的 性能测试工具如何使用

性能测试相关名词

  • QPS每秒查询率 (Query Per Second)

每秒查询率 QPS 是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准

  • 并发用户数

指系统可以同时承载的正常使用系统功能的用户的数量

  • 吞吐量 (Throughput)

吞吐量是指系统在单位时间内处理请求的数量

  • 响应时间 (RT)

指系统对请求作出响应的时间

wrk 是什么

wrk 是 github 的一个项目

github.com/wg/wrk

根据官方的说明,wrk 是一个HTTP基准测试工具

当运行在单个多核CPU上时,它能够产生巨大的负载。它结合了多线程设计和可伸缩的事件通知系统,如 epoll 和 kqueue 等等

wrk 中的一个可选的 LuaJIT 脚本可以执行 HTTP 请求生成、响应处理和自定义报告

wrk 如何使用

那么 wrk 如何使用呢,我们就来实操一下看看效果 , 既然是 开源工具,下载安装编译的方式都很类似

1、下载 wrk 项目

1
shell复制代码git clone https://github.com/wg/wrk.git wrk

2、编译项目

1
2
shell复制代码cd wrk
make

3、将编译出来的 wrk 可执行程序放到用户自己的 bin 目录下

1
shell复制代码 cp wrk /usr/local/sbin/

4、这个时候,我们就可以开始使用 wrk 工具了

咱们直接执行 wrk 来看看效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shell复制代码# wrk
Usage: wrk <options> <url>
Options:
-c, --connections <N> Connections to keep open // 和服务器建立连接并保持的 TCP 连接数量
-d, --duration <T> Duration of test // 具体的压测时间
-t, --threads <N> Number of threads to use // 使用的线程数量

-s, --script <S> Load Lua script file // 加载 lua 文件
-H, --header <H> Add header to request // 添加请求头
--latency Print latency statistics // 打印延迟统计数据
--timeout <T> Socket/request timeout // 超时时间
-v, --version Print version details // 版本信息

Numeric arguments may include a SI unit (1k, 1M, 1G)
Time arguments may include a time unit (2s, 2m, 2h)

上面的 N 表示数字参数,可以是 1k, 1M, 1G

T 表示时间参数,可以是 2s, 2m, 2h

尝试使用 wrk 工具

我们使用 wrk 同样的参数来性能测试一下 掘金 和 百度的地址

  • 200个链接
  • 8个线程
  • 测试 40 s

先测试掘金的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shell复制代码# wrk -c200 -t8 -d40 --latency https://juejin.cn/
Running 40s test @ https://juejin.cn/
8 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.53s 481.41ms 2.00s 87.68%
Req/Sec 10.27 7.50 70.00 73.61%
Latency Distribution
50% 1.67s
75% 1.85s
90% 1.95s
99% 2.00s
2406 requests in 40.05s, 476.65MB read
Socket errors: connect 0, read 0, write 0, timeout 1797
Requests/sec: 60.08
Transfer/sec: 11.90MB

再测试百度的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shell复制代码# wrk -c200 -t8 -d40 --latency https://www.baidu.com
Running 40s test @ https://www.baidu.com
8 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 284.06ms 332.08ms 2.00s 86.02%
Req/Sec 96.41 36.69 538.00 79.63%
Latency Distribution
50% 120.17ms
75% 357.11ms
90% 730.79ms
99% 1.56s
31088 requests in 40.07s, 466.95MB read
Socket errors: connect 0, read 138, write 0, timeout 264
Requests/sec: 775.87
Transfer/sec: 11.65MB

解释上面测试报告相关字段的含义:

  • Latency 延迟
  • Avg 平均值
  • Stdev 标准差
  • +/- Stdev 标准差占比
  • Requests/sec 平均每秒处理的请求数,通常说的 qps 这里可以看出 掘金是 60.08 , 百度是 775.87 ,差别还是有的

实践完毕之后,我们来捋一捋 wrk 的优势和劣势

优势:

  • wrk 是轻量级性能测试工具,用起来非常方便,且安装也很简单,学习成本低
  • 根据官方介绍,我们知道 wrk 基于系统自带的高性能 I/O 机制,如 epoll, kqueue

这些机制是利用异步的事件驱动框架 多路 IO 复用来提高并发性能的

劣势:

  • 仅支持单机压测,如果需要测试多台机器,wrk 就不合适了

小知识,大挑战,工具要用起来才有用

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里

常见技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~

本文转载自: 掘金

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

Nginx+Redis 高性能缓存利器

发表于 2021-10-30

OpenResty

OpenResty是一个基于 Nginx与 Lua的高性能 Web平台,其内部集成了大量精良的 Lua库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态Web 应用、Web 服务和动态网关。

接入层缓存技术就是使用OpenResty的技术用Lua语言进行二次开发。

)

Nginx+redis

下图左边是常用的架构,http请求经过nginx负载均衡转发到tomcat,tomcat再从redis读取数据,整个链路过程是串行的,当tomcat挂掉或者tomcat线程数被消耗完,就无法正常返回数据。

使用OpenResty的lua-resty-redis模块使nginx具备直接访问redis的能力,不占用tomcat线程,Tomcat暂时挂掉仍可正常处理请求,减少响应时长,提高系统并发能力。

)

压缩减少带宽

数据大于1K,nginx压缩再保存到redis:

提高redis的读取速度

减少带宽的占用

压缩会消耗cpu时间,小于1K的数据不压缩tps更高。

OpenResty并没有提供redis连接池的实现,需要自己用lua实现redis的连接池;

在网上已有实现的例子,直接参照使用。

wiki.jikexueyuan.com/project/ope…

Redis的value值用json格式保存{length:xxx,content:yyy},content是压缩后的页面内容,length是content压缩前的大小,length字段是为了在读取redis时,根据length的大小来判断是否要解压缩content的数据。

使用lua-zlib库进行压缩。

)

定时更新

按下图第1和第2步定时执行,nginx lua定时器定时请求tomcat页面的url,返回的页面html保存在redis。

缓存有效期可设置长些,比如1个小时,可保证1个小时内tomcat挂掉,仍可使用缓存数据返回,缓存的定时更新时间可设置短些,比如1分钟,保证缓存快速更新

)

请求转发

浏览器打开页面:

nginx先从redis获取页面html

redis不存在数据时,从tomcat获取页面,同时更新redis

返回页面HTML给浏览器

)

单进程定时更新

Nginx的所有worker进程都可以处理前端请求转发到redis,只有nginx worker 0才运行定时任务定时更新redis,lua脚本中通过ngx.worker.id()获取worker进程编号。

)

可配置化

通过管理后台配置需要缓存的URL,可配置缓存URL、缓存有效期、定时更新时间,比如modify?url=index&&expire=3600000&&intervaltime=300000&sign=xxxx,sign的值是管理后台secretkey对modify?url=index&&expire=3600000&&intervaltime=300000签名运算得到的,nginx端用相同的secretkey对modify?url=index&&expire=3600000&&intervaltime=300000签名运算,得到的值与sign的值相同则鉴权通过,允许修改nginx的配置。

)

本文转载自: 掘金

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

单元测试工具类ReflectionTestUtils 前言

发表于 2021-10-30

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

在写单元测试时,是否经常遇到要测试的目标类中有很多私有注入的变量呢?然而我们经常并没有报漏此私有属性的getter and setter,因为这些私有属性的注入可能是通过配置文件,或者其他渠道,是在程序启动时根据环境不同或者其他因素来决定的,所以当我们测试的时候,就需要通过为该属性设置不同的值而来测试各种分支case,但是前面已经说了一般这种变量都是私有变量,如果说专门为了测试而将修饰符改为public/protected 那未免过于暴力….

于是乎,大家常见的做法可能就是写一堆反射去修改,如果只有一处地方还好,但是如果需要测试此种场景的过多,那是不是写都写烦了,所以ReflectionTestUtils解放你的双手。

用法

首先搞一个service来辅助:

1
2
3
4
5
6
7
java复制代码public class TestServiceImpl {
private String configId = "11111";

public String getConfigId() {
return configId;
}
}

新建一个测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码class TestServiceImplTest {
private TestServiceImpl testService = new TestServiceImpl();

@Test
void getConfigId() {
System.out.println("修改前:" + testService.getConfigId());
ReflectionTestUtils.setField(testService,"configId","2222");
System.out.println("修改后:" + testService.getConfigId());

}
}

输出:
修改前:11111
22:35:40.628 [main] DEBUG org.springframework.test.util.ReflectionTestUtils - Setting field 'configId' of type [null] on target object [com.cf.springboot.TestServiceImpl@45018215] or target class [class com.cf.springboot.TestServiceImpl] to value [2222]
修改后:2222

通过输出以及代码,可以看到非常的简单,一行代码即可完成私有属性的更改,是不是十分的方便!

原理

这里就简单贴一下代码,看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public static void setField(@Nullable Object targetObject, @Nullable Class<?> targetClass, @Nullable String name, @Nullable Object value, @Nullable Class<?> type) {
Assert.isTrue(targetObject != null || targetClass != null, "Either targetObject or targetClass for the field must be specified");
if (targetObject != null && springAopPresent) {
targetObject = AopTestUtils.getUltimateTargetObject(targetObject);
}

if (targetClass == null) {
targetClass = targetObject.getClass();
}

Field field = ReflectionUtils.findField(targetClass, name, type);
if (field == null) {
throw new IllegalArgumentException(String.format("Could not find field '%s' of type [%s] on %s or target class [%s]", name, type, safeToString(targetObject), targetClass));
} else {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Setting field '%s' of type [%s] on %s or target class [%s] to value [%s]", name, type, safeToString(targetObject), targetClass, value));
}

ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, targetObject, value);
}
}

本文转载自: 掘金

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

考研数据结构之并查集

发表于 2021-10-30

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

image.png
漏网之鱼:逻辑结构——“集合”

逻辑结构——数据元素之间的逻辑关系是什么?

集合的两个基本操作——“并”和“查”

Find ——“查”操作:确定一个指定元 素所属集合
Union ——“并”操作:将两个不想交 的集合合并为一个
注:并查集(Disjoint Set)是逻辑结 构——集合的一种具体实现,只进行 “并”和“查”两种基本操作

image.png

image.png

image.png

image.png
如何“查”到一个元素到底属于哪一个集合? —— 从指定元素出发,一路向北,找到根节点
如何判断两个元素是否属于同一个集合?
—— 分别查到两个元素的根,判断根节点是否相同即可

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
c++复制代码#include<iostream>
using namespace std;
#define SIZE 13

int UFSets[SIZE]; //集合元素组

//初始化并查集
void Initial(int S[]){
for(int i=0;i<SIZE;i++)
S[i]=-1;
}

//Find "查"操作,找X所属集合(返回X所属根结点)
int Find(int S[],int X){
while(S[X]>=0) //循环寻找X的根
X=S[X];
return X;
}


//Union "并"操作,讲两个集合合并为一个
void Union(int S[],int Root1,int Root2){
//要求Root1与Root2是不同集合

if(Root1==Root2) return;

//将根Root2连接到另一根Root1下面

S[Root2]=Root1;

}


//Union "并"操作,小树合并到大树
void Union1(int S[],int Root1,int Root2){
if(Root1==Root2) return;
if(S[Root2]>S[Root1]){
S[Root1]+=S[Root2];
S[Root2]=Root1; //小树合并到大树
}else{
S[Root2]+=S[Root1];
S[Root1]=Root2;

}
}

//用并查集判断一个图有几个连通分量(图用邻接矩阵表示)
int ComponentCount(int g[5][5]){
//g[5][5]是一个二维数组表示的邻接矩阵
int S[5];
for(int i=0;i<5;i++) S[i]=-1; //定义,初始化并查集

for(int i=0;i<5;i++)
for(int j=i+1;j<5;j++)
if(g[i][j]>0){

int iRoot=Find(S,i);
int jRoot=Find(S,j);
if(iRoot!=jRoot)
Union1(S,iRoot,jRoot);

}


int count=0;
for(int i=0;i<5;i++)
if(S[i]<0) count++;
return count;

}

//用并查集判断一个图是否有环(图用邻接矩阵表示)
int hasAcyclic(int g[5][5]){
//g[5][5]是一个二维数组表示的邻接矩阵
int S[5]; //初始化并查集
for(int i=0;i<5;i++) S[i]=-1;
for(int i=0;i<5;i++)
for(int j=i+1;j<5;j++)
if(g[i][j]>0){
int iRoot=Find(S,i);
int jRoot=Find(S,j);
if(iRoot!=jRoot)
Union(S,iRoot,jRoot);
else
return 1;//在一个连通子图中,但凡再多一条边,必有环
}
return 0;//图中没环
}

int main(){

return 0;
}

本文转载自: 掘金

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

SkyWalking配上告警更优秀

发表于 2021-10-30

前言

对于监控系统来说,不可能让人一直盯着监控看板,而更多的是以自动提醒的方式,比如邮件、短信或微信推送等,当达到或超出预设的告警指标时,就自动发送消息提醒,下面就来说说如何配置SkyWalking的告警。

正文

在说告警之前呢,给小伙伴先演示一下SkyWalking跟踪数据库操作链路及监控数据库指标,支持EF Core的形式操作数据库,可以显示对应的SQL语句和执行时间等信息。

1. 跟踪数据库请求

对于项目来说,直接或间接访问数据库是避免不了的;对于业务数据量比较大或高并发场景,很多时候会因为数据库操作过慢或不及时返回数据,导致整个系统体验极差,所以对系统操作数据库的跟踪和监控少不了,以下就来演示一下SkyWalking对数据库操作的跟踪和监控。

1.1 环境准备

这里的SkyWalking环境搭建就不重复操作了,可以参考上一篇(分布式/微服务必配APM系统,SkyWalking让你不迷路)。

1.2 项目集成EF Core

关于EF Core的使用,之前分享过一篇很详细的文章,可参考查阅(跟我一起学.NetCore之EF Core 实战入门,一看就会)。

集成EF Core之后,为方便演示看效果,得增加一个API进行访问,这个API就是简单的通过EF访问数据库,如下:

注:这里的项目需要集成SkyWalking,和上一篇一样,不需要做额外处理。

1.3 看效果

运行项目,访问上一步编写的GetUser接口,然后再看SkyWalking的记录情况,如下:

可以切换成列表的形式,看着相对更直观一点:

点击对应每层可显示对应的详细信息,如点击数据库操作相关层,可显示具体的SQL语句及其他信息,如下:

更多操作演示,就留给小伙伴自己操作吧。

2. 告警配置及使用

自动告警基本上是监控系统的标配,接下来看看在SkyWalking中是如何使用的。

2.1 告警规则配置

所谓告警规则其实就是配置的告警条件及检查周期,根据业务需要进行配置。

在SkyWalking中配置告警条件是在后台服务端进行的,即环境搭建中启动的容器skywalking-oap,见上篇文章;

由于演示是采用Docker的形式启动的容器,也没有进行数据卷挂载,所以我们需要进入对应的容器进行配置,如下:

  • 进入容器,并到对应的配置目录

执行如下命令进入到SkyWalking后台容器;如果不是以容器启动的,直接进到配置文件目录修改对应文件即可;

1
bash复制代码 docker exec -it skywalking-oap /bin/bash

  • 查阅配置规则文件及配置规则解读

通过cat alarm-settings.yml可以查阅文件内容,如下:

规则常用指标解读:

rule name: 规则名称,必须唯一,必须以 _rule结尾;

metrics name: oal(Observability Analysis Language)脚本中的度量名;名称在SkyWalking后端服务中已经定义,进入容器skywalking-oap之后,进入如下目录就可以找到。

如果想更多了解oal,参照文档:github.com/apache/skyw…

include names: 本规则告警生效的实体名称,如服务名,终端名;

exclude-names:将此规则作用于不匹配的实体名称上,如服务名,终端名;

threshold: 阈值,可以是一个数组,即可以配置多个值;

op: 操作符, 可以设定 >, <, =;

period: 多久检查一次当前的指标数据是否符合告警规则;以分钟为单位

count: 超过阈值条件,达到count次数,触发告警;

silence period:在同一个周期,指定的silence period时间内,忽略相同的告警消息;

更多告警规则详情,请参照这个地址:github.com/apache/skyw…

  • 配置规则文件简单修改

这里挑一个模板规则稍微改一下,用于后续演示,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
yaml复制代码  # 告警规则名称,必须唯一,以_rule结尾
  service_sla_rule:
     # 指定metrics-name
    metrics-name: service_sla
     # 小于
    op: "<"
     # 指定阈值
    threshold: 8000                                                                              
     # 10分钟检测一次告警规则                                                
    period: 10                                                                                  
     # 触发2次告警规则就告警                  
    count: 2                                                                                    
     # 设置的3分钟时间段有相同的告警,不重复告警.
    silence-period: 3
      # 配置告警消息
    message: Successful rate of service {name} is lower than 80% in 2 minutes of last 10 minutes

规则概要:服务成功率在过去2分钟内低于80%

2.2 告警API编写

有了规则之后,如何进行自动发送告警信息呢?

这个本质还是SkyWalking根据规则进行检查,如果符合规则条件,就通过WebHook、gRPCHook、WeChat Hook、Dingtalk Hook等方式进行消息通知;接收到告警数据信息之后,可以自行处理消息。这里为了方便,就采用WebHook的方式进行演示,即触发告警条件之后,SkyWalking会调用配置的WebHook 接口,并传递对应的告警信息;

  • 传递的告警信息

SkyWalking后端服务会以Post的方式调用WebHook的接口,并以Json的形式向接口传递告警信息,如下格式:

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
json复制代码 [
    {
  "scopeId": 1, // 范围ID
  "name": "serviceA", //实体名称
     // 实体ID
  "id0": "12",  
     // 用于标识实体关系中的目标实体ID,没有关系就为空
  "id1": "",  
     // 规则名称 alarm-settings.yml中配置的规则名称
     "ruleName": "service_resp_time_rule",
     // 触发告警时发送的消息
  "alarmMessage": "alarmMessage xxxx",
     // 告警的时间戳
  "startTime": 1560524171000
  },
    {
  "scopeId": 1,
  "name": "serviceB",
  "id0": "23",
  "id1": "",
     "ruleName": "service_resp_time_rule",
  "alarmMessage": "alarmMessage yyy",
  "startTime": 1560524171000
  }
 ]

知道传递告警的信息的格式后,写API的时候就得以此格式接收。

  • 编写告警时调用的API,如下:

这里只是一个常规的API,关于发邮件的配置,之前在一篇文章中分享的很详细(来,Consul 服务发现入个门(一看就会的那种))。

  • 配置WebHook地址

由于SkyWalking的环境搭建在了我的云服务器,本地电脑没有配置外网访问,所以只能将API发布到云服务器上,这样SkyWalking后端服务调用告警接口就可以了,所以这里就在规则配置文件的最下面配置WebHook调用的接口地址即可;步骤如下:

修改alarm-settings.yml的文件,在文件最后配置WebHook地址,可以配置多个,如下:

告警规则和WebHook地址配置完毕之后,重启一下容器,如下:

1
2
arduino复制代码 docker stop skywalking-oap
 docker start skywalking-oap
2.3 运行看效果

启动项目,然后访问之前写好的接口,接口中特意搞了个异常,所以每次都会报错,错误率肯定是低于设置的规则80%,稍等一会就会产生告警信息;

界面上也可以看到告警信息,如下:

因为触发告警时会调用我们编写的WebHook接口,我们针对告警信息发送了邮件,所以同时会收到对应的告警邮件

演示代码:gitee.com/CodeZoe/mic…

总结

好了,关于告警的配置和使用就简单说这么多吧,如果有其他配置需求,可以参照官网,使用方式大同小异;后续会记录一些使用经验,关注“Code综艺圈”,和我一起学习吧;

本文转载自: 掘金

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

数据库系列之数据处理 1插入一列固定值 2json序列解

发表于 2021-10-30

所用到的数据表的结构如下:

image-20210919204526800.png

数据表的内容如下:

image-20210919205800863.png

1.插入一列固定值

有时候,我们除了要获取原始表中现有的数据。还想再查询结果中根据选取的数据特征插入一列固定值。其实现方法如下:

1
2
vbnet复制代码SELECT id, `name`,age<20 as label
FROM chapter5 WHERE age<20

result:

image-20210919210112455.png

2.json序列解析

针对json格式存储的数据,可以按照如下办法进行解析。

2.1 获取json数据的value

1
2
javascript复制代码SELECT id, `name`,JSON_EXTRACT(score, "$.数学")  as "数学成绩"
FROM chapter5

result:

image-20210919210417436.png

2.2 获取json数据的key

1
2
javascript复制代码SELECT id, `name`,JSON_KEYS(score)  as "科目"
FROM chapter5

result:

image-20210919210607450.png

3.缺失值

从第3节开始所用到的数据表的结构如下:

image-20210919230134993.png

数据表的内容如下:

image-20210919230202125.png

3.1. 缺失值过滤

缺失值有空格、null和空值(用””表示空值)三种表现形式,前2种形式虽然也表示缺失值,但是在对应的单元格内是有值的,而后一种空值是没有值的,表示这个单元格什么都没有。

空值的过滤:

1
sql复制代码SELECT * FROM chapter6 WHERE profession !=""

result:

image-20210919230332690.png

空格的过滤:

1
sql复制代码SELECT * FROM chapter6 WHERE profession !=" "

null的过滤

1
sql复制代码SELECT * FROM chapter6 WHERE profession is not NULL

3.2 缺失值填充

1
sql复制代码SELECT *, COALESCE(profession,"其他") FROM chapter6

result:

image-20210919231153645.png

4.重复值处理

4.1 distinct

1
sql复制代码SELECT DISTINCT order_id, memberid FROM chapter6

result:

image-20210920100707699.png

4.2 group by

1
vbnet复制代码SELECT order_id, memberid FROM chapter6 GROUP BY order_id, memberid

result:

image-20210920100757687.png

5.数据类型转换

数据类型转换主要有以下两个方法:
cast(value as type)

convert(value, type)

1
sql复制代码SELECT age, CAST(age as DECIMAL) decimal_age, CONVERT(age, CHAR) char_age FROM chapter6

result:

image-20210920101136798.png

本文转载自: 掘金

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

python封装一个四六级单词软件 ⚡导读 演示 爬虫阶段

发表于 2021-10-30

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金

⚡导读

你还在为小小英语而发愁吗?,你还在为备考四六级没有动力而难过吗?那么今天教你写一个背单词软件你不会不想学吧。兄弟们新学期都开始了大家和我一起肥学一起吧!!

在这里插入图片描述

演示

演示视频地址

爬虫阶段

我在网上找了一个适合我们做软件的网站给大家贴出来链接:网站

在这里插入图片描述
我们找到我们要获取的值使用

1
2
python复制代码import requests
from bs4 import BeautifulSoup

两个模块来将我们得到的网页进行处理。最重要的是我们是一个每日单词系列所以我们爬取的时候就要找到他每天数据的一个规律,经过我的对比他们的链接只有后面的course数值会发生变化,所以我们得到如下的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python复制代码import requests
from bs4 import BeautifulSoup
import lxml


url=('http://word.iciba.com/?action=words&class=122&course=%s' %s)
head={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
'Referer': 'http://word.iciba.com/?action=courses&classid=122'
}

res=requests.get(url=url,headers=head).text.encode('utf-8')
soup=BeautifulSoup(res,'lxml',from_encoding='utf-8')

words=soup.find_all('span')
word=[str(w.get_text()) for w in words]

这下我们就能得到每天的数据了。哈哈哈哈

GUI阶段

当然我这里的GUI不是很美观还需要后期的处理,这次我们依然使用的是easygui这个模块我之前也用过再这篇文章:爱情保温软件大家有兴趣可以看看哦。

关于这里的每日单词我们先设置初始窗体
在这里插入图片描述

1
python复制代码s=a.enterbox(msg="您学习的天数",title='每日单词',strip=True,default='')

这个用来得到您学到了第几天了。

为了激励大家我给大家设定了几句激励

在这里插入图片描述

1
2
3
4
5
6
7
python复制代码title=['一定要加油下去哦!','水滴石穿!','冲冲冲!','胜利属你你自己!','肥学起来']
b=True
for i in range(9,len(word)-5,2):
if b:
b = a.ccbox(msg=word[i]+word[i+1], title=title[i//10])
else:
exit()

朗读单词阶段

这里依然使用的我们的老朋友

1
python复制代码import pyttsx3#朗读模块

用法很简单

1
python复制代码pyttsx3.speak(word[i])

直接就朗读了获得的单词

封装阶段

这个之前再文章里面说过了,大家可以参考这篇文章看看哦爱情保温软件大家可以用pyinstaller但是我发现这款虽然说是封装起来很简单但是会出现运行cmd黑框针对这个问题肥学给出解决的方法就是

第一种在后面加-w,至于为什么我上面给出的链接里面有参数介绍

1
2
python复制代码
pyinstaller -F XX.py -w

第二种在后面加上–noconsole

1
python复制代码pyinstaller.exe -F 路径\文件名.py空格路径\文件名.py空格--noconsole

另外还要强调一点的就是在打包的时候尽量不要出现中文,而且千万把pyinstaller安装到你要打包的文件目录下,或者将你要打包的文件移过去,如果存在说哪一个依赖包不存在或者找不到的情况可以直接重装这个包。

⚡肥学有话说

想要源码或者懒得去动手做的同学可以私信我,。新学习开始了大家在享受大学生活的同时一定不要荒废了学业啊。有什么问题可以私信问肥学,而且我这里也弄了一些不错的专栏大家可以看看一起肥学。

本文转载自: 掘金

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

gRPC 调整数据传输大小限制 介绍 安装 快速开始 调整

发表于 2021-10-30

小知识,大挑战!本文正在参与“ 程序员必备小知识 ”创作活动

本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金

介绍

本文介绍如何通过 rk-boot 调整 gRPC 数据传输大小限制。

grpc 的大小限制存在于接收方,也就是说发送多大的数据没有限制,接收默认大小是 4MB。
例子里使用的是 google.golang.org/grpc v1.38.0 版本。

什么是 gRPC 数据传输大小限制?

gRPC 服务端默认最大数据传输大小为 4MB,有些时候,我们需要传输更大的数据,比如大图片。

请访问如下地址获取完整教程:

  • rkdocs.netlify.app/cn

安装

1
2
go复制代码go get github.com/rookie-ninja/rk-boot
go get github.com/rookie-ninja/rk-grpc

快速开始

rk-boot 支持通过代码 & YAML 文件的方式调整大小限制。

为了完整演示,我们创建一个 greeter API。

1.创建 protobuf 相关文件

我们使用 buf 命令行来编译 protobuf,需要创建如下几个文件。

文件名 描述
api/v1/greeter.proto protobuf 文件
buf.yaml 告诉 buf 命令行在哪里寻找 protobuf 文件
buf.gen.yaml 告诉 buf 命令行如何编译 protobuf 文件
  • api/v1/greeter.proto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protobuf复制代码syntax = "proto3";

package api.v1;

option go_package = "api/v1/greeter";

service Greeter {
rpc Greeter (GreeterRequest) returns (GreeterResponse) {}
}

message GreeterRequest {
bytes msg = 1;
}

message GreeterResponse {}
  • buf.yaml
1
2
3
4
5
yaml复制代码version: v1beta1
name: github.com/rk-dev/rk-demo
build:
roots:
- api
  • buf.gen.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
yaml复制代码version: v1beta1
plugins:
# protoc-gen-go needs to be installed, generate go files based on proto files
- name: go
out: api/gen
opt:
- paths=source_relative
# protoc-gen-go-grpc needs to be installed, generate grpc go files based on proto files
- name: go-grpc
out: api/gen
opt:
- paths=source_relative
- require_unimplemented_servers=false
  • 编译 protobuf 文件
1
shell复制代码$ buf generate

2.创建 boot.yaml

我们通过 boot.yaml 方式来【取消】大小限制,通过 boot.yaml 我们可以取消限制,但是无法调整限制。

调整限制的话,可以通过代码调整,我们也会在下面介绍。

1
2
3
4
5
6
yaml复制代码---
grpc:
- name: greeter # Name of grpc entry
port: 8080 # Port of grpc entry
enabled: true # Enable grpc entry
noRecvMsgSizeLimit: true

3.创建 server.go

我们实现了 greeter 接口。

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
go复制代码// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main

import (
"context"
"github.com/rookie-ninja/rk-boot"
"github.com/rookie-ninja/rk-demo/api/gen/v1"
"github.com/rookie-ninja/rk-grpc/boot"
"google.golang.org/grpc"
)

// Application entrance.
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot()

// Get grpc entry with name
grpcEntry := boot.GetEntry("greeter").(*rkgrpc.GrpcEntry)
grpcEntry.AddRegFuncGrpc(registerGreeter)

// Bootstrap
boot.Bootstrap(context.Background())

// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}

func registerGreeter(server *grpc.Server) {
greeter.RegisterGreeterServer(server, &GreeterServer{})
}

type GreeterServer struct{}

func (server *GreeterServer) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) {
return &greeter.GreeterResponse{}, nil
}

4.创建 client.go

我们试着传输 10MB 的数据。

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
go复制代码// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main

import (
"context"
"github.com/rookie-ninja/rk-demo/api/gen/v1"
"google.golang.org/grpc"
"log"
)

func main() {
opts := []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithBlock(),
}

// 1: Create grpc client
conn, client := createGreeterClient(opts...)
defer conn.Close()

kb := 1024
mb := 1024*kb

// 2: Call server with 10mb size of data
if _, err := client.Greeter(context.Background(), &greeter.GreeterRequest{Msg: make([]byte, 10*mb, 10*mb)}); err != nil {
panic(err)
}
}

func createGreeterClient(opts ...grpc.DialOption) (*grpc.ClientConn, greeter.GreeterClient) {
// 1: Set up a connection to the server.
conn, err := grpc.DialContext(context.Background(), "localhost:8080", opts...)
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}

// 2: Create grpc client
client := greeter.NewGreeterClient(conn)

return conn, client
}

5.文件夹结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
go复制代码.
├── api
│ ├── gen
│ │ └── v1
│ │ ├── greeter.pb.go
│ │ └── greeter_grpc.pb.go
│ └── v1
│ └── greeter.proto
├── boot.yaml
├── buf.gen.yaml
├── buf.yaml
├── client.go
├── go.mod
├── go.sum
└── server.go

6.验证

不会出现任何错误。

1
2
go复制代码$ go run server.go
$ go run client.go

因为服务端默认只允许接收 4MB 大小的数据,如果我们在 boot.yaml 里把 noRecvMsgSizeLimit 设置成 false,会得到如下错误。

1
ini复制代码rpc error: code = ResourceExhausted desc = grpc: received message larger than max (10485765 vs. 4194304)

调整【服务端】传输数据大小

上次的例子中,我们使用 noRecvMsgSizeLimit 选项取消了 gRPC 服务端的大小限制,这次,我们试着调整大小。
还是使用上面的 protobuf 文件。

1.修改 boot.yaml

这次我们把 noRecvMsgSizeLimit 设置成 false。

1
2
3
4
5
6
yaml复制代码---
grpc:
- name: greeter # Name of grpc entry
port: 8080 # Port of grpc entry
enabled: true # Enable grpc entry
noRecvMsgSizeLimit: false

2.修改 server.go

我们通过 AddServerOptions() 函数设置服务端接收最大值。

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
go复制代码// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main

import (
"context"
"github.com/rookie-ninja/rk-boot"
"github.com/rookie-ninja/rk-demo/api/gen/v1"
"github.com/rookie-ninja/rk-grpc/boot"
"google.golang.org/grpc"
)

// Application entrance.
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot()

// Get grpc entry with name
grpcEntry := boot.GetEntry("greeter").(*rkgrpc.GrpcEntry)
grpcEntry.AddRegFuncGrpc(registerGreeter)

// *************************************** //
// *** Set server receive size to 20MB ***
// *************************************** //
kb := 1024
mb := 1024*kb
grpcEntry.AddServerOptions(grpc.MaxRecvMsgSize(20*mb))

// Bootstrap
boot.Bootstrap(context.Background())

// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}

func registerGreeter(server *grpc.Server) {
greeter.RegisterGreeterServer(server, &GreeterServer{})
}

type GreeterServer struct{}

func (server *GreeterServer) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) {
return &greeter.GreeterResponse{}, nil
}

3.验证

不会出现任何错误。

1
2
go复制代码$ go run server.go
$ go run client.go

如果我们发送的数据大于 20mb,会出现如下错误。

1
ini复制代码rpc error: code = ResourceExhausted desc = grpc: received message larger than max (31457285 vs. 20971520)

调整【客户端】传输数据大小

如果服务端返回的数据大于 4MB,我们需要在客户端调整大小。

1
2
3
4
5
6
7
8
go复制代码kb := 1024
mb := 1024*kb

opts := []grpc.DialOption{
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(20*mb)),
grpc.WithInsecure(),
grpc.WithBlock(),
}

本文转载自: 掘金

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

【转载】map 和 unordered_map 的差别和使用

发表于 2021-10-30

前言

OpenGL 的扩展函数绑定用到了大量的 unordered_map,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cpp复制代码using namespace gl;

namespace glbinding
{

const std::unordered_map<GLextension, Version> Meta_ReqVersionsByExtension
{
{ GLextension::GL_ARB_color_buffer_float, { 3, 0 } },
{ GLextension::GL_ARB_depth_buffer_float, { 3, 0 } },
{ GLextension::GL_ARB_framebuffer_object, { 3, 0 } },
{ GLextension::GL_ARB_framebuffer_sRGB, { 3, 0 } },
{ GLextension::GL_ARB_half_float_pixel, { 3, 0 } },
{ GLextension::GL_ARB_map_buffer_range, { 3, 0 } },
{ GLextension::GL_ARB_texture_compression_rgtc, { 3, 0 } },
{ GLextension::GL_ARB_texture_float, { 3, 0 } },
{ GLextension::GL_ARB_texture_rg, { 3, 0 } },
{ GLextension::GL_ARB_vertex_array_object, { 3, 0 } }
...
};
}

它为什么不适用 map,这么做的原因是什么?有什么好处呢?

让我们看看以下这篇文章 《map 和 unordered_map 的差别和使用》

原文地址:map 和 unordered_map 的差别和使用

以下是正文,我对排版和错别字进行了一点修改

map 和 unordered_map 的差别

还不知道或者搞不清unordered_map和map是什么的,请见:
blog.csdn.net/billcyj/art…

需要引入的头文件不同

1
2
cpp复制代码map: #include < map >
unordered_map: #include < unordered_map >

内部实现机理不同

map: map 内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而 AVL 是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此 map 内部的所有元素都是有序的,红黑树的每一个节点都代表着 map 的一个元素。因此,对于 map 进行的查找、删除、添加等一系列的操作都相当于是对红黑树进行的操作。map 中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来。

unordered_map: unordered_map 内部实现了一个哈希表(也叫散列表,通过把关键码值映射到 Hash 表中一个位置来访问记录,查找的时间复杂度可达到 O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。哈希表详细介绍

优缺点以及适用处

map

优点

  • 有序性,这是 map 结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作
  • 红黑树,内部实现一个红黑树使得 map 的很多操作在 log(n) 的时间复杂度下就可以实现,因此效率非常的高

缺点

空间占用率高,因为 map 内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间

适用处
对于那些有顺序要求的问题,用map会更高效一些

unordered_map

优点

因为内部实现了哈希表,因此其查找速度非常的快

缺点

哈希表的建立比较耗费时间

适用处

对于查找问题,unordered_map 会更加高效一些,因此遇到查找问题,常会考虑一下用 unordered_map

小结

内存占有率的问题就转化成 红黑树 vs. hash 表 , 还是 unorder_map 占用的内存要高。
但是 unordered_map 执行效率要比 map 高很多
对于 unordered_map 或 unordered_set 容器,其遍历顺序与创建该容器时输入的顺序不一定相同,因为遍历是按照哈希表从前往后依次遍历的

map 和 unordered_map 的使用

unordered_map 的用法和 map 是一样的,提供了 insert、size、count 等操作,并且里面的元素也是以 pair 类型来存贮的。其底层实现是完全不同的,上方已经解释了,但是就外部使用来说却是一致的。

C++ map 常见用法说明

常用操作汇总举例

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
cpp复制代码// map_unordered_map.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <unordered_map>
#include <map>
#include <string>

using namespace std;

int main() {
//注意:C++11才开始支持括号初始化
//unordered_map<int, string> myMap = { { 5, "张大" },{ 6, "李五" } };//使用{}赋值
map<int, string> myMap = { { 5, "张大" },{ 6, "李五" } };//使用{}赋值

myMap[2] = "李四"; //使用[ ]进行单个插入,若已存在键值2,则赋值修改,若无则插入。
myMap.insert(pair<int, string>(3, "陈二"));//使用insert和pair插入

//遍历输出+迭代器的使用
auto iter = myMap.begin();//auto自动识别为迭代器类型unordered_map<int,string>::iterator
while (iter != myMap.end()) {
cout << iter->first << "," << iter->second << endl;
++iter;
}

//查找元素并输出+迭代器的使用
auto iterator = myMap.find(2);//find()返回一个指向2的迭代器
if (iterator != myMap.end())
cout << endl << iterator->first << "," << iterator->second << endl;
system("pause");
return 0;
}

此时用的是 unordered_map ,输出的结果为:

image.png

若把 unordered_map 换成 map ,输出的结果为:

image.png

set 和 unordered_set 的使用方法类似于 map 和 unordered_map ,详情请见:

【总结】 unordered_map, unordered_set , map 和 set 的用法和区别

参考

【1】c++ 中 map 与 unordered_map 的区别

【2】C++Map常见用法说明

本文转载自: 掘金

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

【分布式事务系列】两阶段提交协议执行流程 两阶段提交协议执行

发表于 2021-10-30

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

两阶段提交协议执行流程

两阶段提交协议执行流程如下:

  • 准备阶段:事务管理器(TM)通知资源管理器(RM)准备分支事务,记录事务日志,通知事务管理器的准备结果
  • 提交回滚阶段:如果所有的资源管理器(RM)在准备阶段返回成功,则事务管理器向所有的资源管理器发起事务提交指令完成数据的修改。反之,如果在任何一个资源管理器返回失败,则事务管理器会向所有资源管理器发送事务回滚指令。完整执行流程图如下:

两阶段提交将一个事务的处理过程分为投票和执行两个阶段,它的优点在于充分考虑到分布式的不可靠因素,并采用两阶段提交就把由于系统的不可靠而导致事务提交失败概率降到最小。

有以下缺点:

  • 同步阻塞:所有资源管理器都是事务阻塞状态,对于每一次指令都必须要有明确的响应才能继续进行下一步,否则会处于阻塞状态,占用的资源一直被锁定。
  • 过于保守,任何一个节点失败都会导致数据回滚。
  • 事务协调者的单点故障:如果协调者在第二阶段出现了故障,那其他参与者对应的资源会一直处于锁定状态
  • “脑裂”导致数据不一致问题:在第二阶段中,事务协调者向所有参与者RM发送commit请求后,发生局部网络异常导致只有一部分参与者RM接收到了commit请,这部分参与者RM收到请求后会执行commit事务操作,但是未收到commit请求的节点由于事务无法提交,导致数据出现不一致问题。

本文转载自: 掘金

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

1…452453454…956

开发者博客

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