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

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


  • 首页

  • 归档

  • 搜索

Spring配置数据源 1 数据源(连接池)的作用 2

发表于 2021-11-06
  • 这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战
  1. 数据源(连接池)的作用

  • 数据源(连接池)是提高程序性能如出现的
  • 事先实例化数据源,初始化部分连接资源
  • 使用连接资源时从数据源中获取
  • 使用完毕后将连接资源归还给数据源

常见的数据源(连接池):DBCP、C3P0、BoneCP、Druid等

  1. 数据源的开发步骤

  1. 导入数据源的坐标和数据库驱动坐标
  2. 创建数据源对象
  3. 设置数据源的基本连接数据
  4. 使用数据源获取连接资源和归还连接资源
  1. 数据源的手动创建

  1. 导入c3p0和druid的坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
xml复制代码<!-- C3P0连接池 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>

<!-- Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
  1. 导入mysql数据库驱动坐标
1
2
3
4
5
xml复制代码<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
  1. 创建C3P0连接池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@Test
//测试手动创建 c3p0 连接池
public void test1() throws Exception {
//创建数据源
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//设置数据库连接参数
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test1");
dataSource.setUser("root");
dataSource.setPassword("123456");
//获得连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
  • 成功返回连接地址
    在这里插入图片描述
  1. 创建Druid连接池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@Test
//测试手动创建 druid 连接池
public void test2() throws Exception {
//创建数据源
DruidDataSource dataSource = new DruidDataSource();
//设置数据库连接参数
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test1");
dataSource.setUsername("root");
dataSource.setPassword("123456");
//获得连接对象
DruidPooledConnection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
  • 成功返回连接地址
    在这里插入图片描述
  1. 提取 jdbc.properties 配置文件
    在这里插入图片描述
1
2
3
4
xml复制代码jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test1
jdbc.username=root
jdbc.password=123456
  1. 读取jdbc.properties配置文件创建连接池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码@Test
//测试手动创建 c3p0 数据源(加载 properties 配置文件)
public void test3() throws Exception {
//读取配置文件
ResourceBundle rb = ResourceBundle.getBundle("jdbc");
String driver = rb.getString("jdbc.driver");
String url = rb.getString("jdbc.url");
String username = rb.getString("jdbc.username");
String password = rb.getString("jdbc.password");
//创建数据源对象 设置连接参数
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);

Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}

在这里插入图片描述

  1. Spring配置数据源

可以将DataSource的创建权交由Spring容器去完成

  • DataSource有无参构造方法,而Spring默认就是通过无参构造方法实例化对象的
  • DataSource要想使用需要通过set方法设置数据库连接信息,而Spring可以通过set方法进行字符串注入
1
2
3
4
5
6
xml复制代码<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test1"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
  • 测试从容器当中获取数据源
1
2
3
4
5
6
7
8
9
java复制代码@Test
//测试Spring容器产生数据源对象
public void test4() throws Exception {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = app.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}

在这里插入图片描述

  1. 抽取jdbc配置文件

applicationContext.xml加载jdbc.properties配置文件获得连接信息

首先,需要引入context 命名空间和约束路径:

  • 命名空间:
1
xml复制代码xmlns:context="http://www.springframework.org/schema/context"
  • 约束路径:
1
2
xml复制代码http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
1
2
3
4
5
6
7
8
9
xml复制代码<!--加载外部的properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  1. 知识要点

  • Spring容器加载properties文件
1
2
xml复制代码<context:property-placeholder location="xx.properties"/>
<property name="" value="${key}"/>

本文转载自: 掘金

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

goalng 如何获取 ldap 服务器的数据?

发表于 2021-11-06

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

我们工作在和其他组织配合时,我们可能不是作为服务器搭建的一方,而是属于客户端的一方,需要去获取服务器的组织结构,按照某些条件去获取服务器的数据,也可以是同步组织结构

虽然说 golang 的数据结构没有 c++ 那么丰富,不过对于这个 ldap 还是有相应的库来进行处理的

官方文档地址:pkg.go.dev/gopkg.in/ld…

我们也可以下载 github 上面的这个库

1
go复制代码go get github.com/go-ldap/ldap/v3

golang 对于 ldap 库最新的版本是 Version: v3.1.0

开始编码

我们来写一个 demo ,获取我们上次搭建的 ldap 服务器上的组织结构

这是我们简单搭建的 ldap 服务器,可以使用 LDAP Admin 可视化管理工具来查看具体的页面效果

我们把这个库下载下来后,我们的编码思路如下:

  • 填写 ldap 服务器地址以及填写相应的管理员信息,与 ldap 服务器建立连接
  • 编写查询请求,并开始向 ldap 服务器进行查询
  • 将查询结构,按照 ldap v3 库提供的方式 打印出效果来

连接 服务器

我们可以使用 func DialURL(addr string, opts ...DialOpt) (*Conn, error) 函数来与 ldap 服务器建立连接

1
2
3
4
5
go复制代码ml, err := ldap.DialURL("ldap://xxxx")
if err != nil {
log.Fatal(err)
}
defer ml.Close()

我们填入的地址中,可以不用输入端口号,库函数已经有给我们做好处理,我们可以来看看源码

DialURL 函数用于连接 ldap 服务器,连接成功会给我们返回一个新的连接

我们可以继续看一下这个函数调用 c, err := dc.dial(u)

golang 的库会根据我们填写的地址是 ldap 还是 ldaps 来判断是做加密传输还是不加密传输,与之对应的就是访问不加密的用 389 端口,加密的就使用 636 端口

添加管理员绑定信息

我们添加的 ldap 域信息为:dc=xiaomotong,dc=com

我的管理员是:cn=admin,dc=xiaomotong,dc=com

1
2
3
4
5
6
7
8
9
go复制代码_, err = ml.SimpleBind(&ldap.SimpleBindRequest{
Username: "cn=admin,dc=xiaomotong,dc=com",
Password: "123123",
})
if err != nil {
log.Fatalf("Failed to bind: %s\n", err)
}

fmt.Println("connect successfully !!")

来看看实际的 SimpleBindRequest 数据结构

1
2
3
4
5
6
7
8
9
10
11
12
go复制代码// SimpleBindRequest represents a username/password bind operation
type SimpleBindRequest struct {
// Username is the name of the Directory object that the client wishes to bind as
Username string
// Password is the credentials to bind with
Password string
// Controls are optional controls to send with the bind request
Controls []Control
// AllowEmptyPassword sets whether the client allows binding with an empty password
// (normally used for unauthenticated bind).
AllowEmptyPassword bool
}
  • Username
  • Password

客户端需要绑定的域用户和密码

  • Controls

需要绑定请求的控件

  • AllowEmptyPassword

是否允许空密码,若是空密码,一般是绑定一个未授权的用户

编写查询请求,并开始查询 ldap 服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
go复制代码searchRequest := ldap.NewSearchRequest(
"dc=xiaomotong,dc=com",
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
"(ou=People)",
[]string{},
nil,
)
searchResult, err := ml.Search(searchRequest)
if err != nil {
log.Println("can't search ", err.Error())
}
log.Printf("%d", len(searchResult.Entries))

编写查询请求,也就是简单的给我们的结构体进行一个负值操作,填写好对相应的参数,即可开始查询,一起来看看这个结构体NewSearchRequest

基本上就是填写相应的域信息

  • BaseDN , 一个域唯一的标识
  • scope 范围的选择,我们默认选择ScopeWholeSubtree ,查询所有的子树
  • DerefAliases , SizeLimit,TimeLimit,TypesOnly 填写默认值即可
  • Filter , 查询需要的过滤条件,可以按照我们的实际情况写条件,就像写查询数据库的条件一样,这里不能为空,否则会程序崩溃
1
2
3
4
5
6
7
8
9
10
shell复制代码F:\codegitee\golang_study\later_learning\ldap_test>go run main.go
connect successfully !!
2021/11/06 21:07:59 can't search LDAP Result Code 201 "Filter Compile Error": ldap: error parsing filter
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x4 pc=0xa33cef]

goroutine 1 [running]:
main.main()
F:/codegitee/golang_study/later_learning/ldap_test/main.go:40 +0x24f
exit status 2
  • Attributes , 需要返回的属性有哪些,是一个切片,如果我们默认填空,则会返回所有属性

查看对应的 Search 函数源码

代码的大致逻辑是,doRequest 将数据组包向 ldap 服务器发送请求,请求成功之后,将响应的数据按照 tag 不同的内容进行解析

最终返回一个 *SearchResult 查询结果的指针

输入查询信息

1
2
3
4
go复制代码for _, item := range searchResult.Entries {
item.Print()
fmt.Printf("\n\n")
}

上图源码我们可以看到输出查询信息就是遍历一下 searchResult.Entries ,我们可以来看看对应的数据结构

我们可以看到结果里面,有一个 Entries []*Entry 是一个切片,里面放了多个 *Entry , 在 ldap 服务器中, 1 个 Entry 就代表一条唯一的记录

Entry 结构体就是对应的 DN,一条记录唯一的辨别名 , 和他涉及的属性

EntryAttribute 属性结构体中,我们可以看到 有 Name ,有 Values ,这里就是对应我们之前说到的 RDN,也就是一个键值对,多个键值对组成一个 DN

最终我们来查看一下效果

1
2
3
4
5
6
shell复制代码>go run main.go
connect successfully !!
2021/11/06 21:20:58 1
DN: ou=People,dc=xiaomotong,dc=com
objectClass: [organizationalUnit]
ou: [People]

结果是输出了 1 条信息,没错,因为我们的 ou=people 只有 1条记录,如果我们需要查询整个 ldap服务器的所有数据,则我们可以将上述代码的 Filter 位置,修改成 objectClass=*

解释上述结果:

  • DN 表示唯一的记录,是辨别名的意思
  • objectClass 是一个类,这里对应的是 organizationalUnit ,表示组织单元OU,可以理解为 组
  • ou: [People] 指的是这个 ou 对应的名字是 People

欢迎点赞,关注,收藏

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

好了,本次就到这里

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

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

本文转载自: 掘金

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

Net Core通用数据库操作类

发表于 2021-11-06

背景

  正在开发一个平台项目,既然是平台自然是要足够通用,从数据库的角度说就是要兼容足够多的主流数据库。但是平台又不想使用现有的ORM库,一是希望ORM功能在平台里足够贴合平台功能,二是希望ORM足够轻量级,因此决定自行开发一套ORM库,首先ORM需要一套最终执行数据库命令落地的操作类,并直接兼容多数据库。

目的

  自己动手开发既满足了平台对于数据库操作的刚需,同时熟悉.net core的技术。

  另一方面,该库可被独立使用,无需ORM的包装;使用该库可以将数据库操作和具体数据库类型隔离,对于未来进行数据库类型切换来说几乎是可以说零工作量。

思路

  实现一套数据库操作类,简单来说就是数据库的增、删、改、查,以及事务操作等基本功能,基于轻量级的要求,平台不需要或小概率需要的数据库操作将不被支持。

  基于以上想法,决定采用DbProvider机制实现,做最简单的封装,具体逻辑如下:

  1. 提供RegisterDbProvider方法增加新的数据库类型支持

  2. 通过AddDataSource方法增加数据库连接

  3. 提供ExecuteNonQuery、ExecuteScalar、ExecuteDataReader、GetTransaction、CommitTransaction、RollbackTransaction等方法执行数据库相关操作和事务

实现

  代码实现比较简单,这里不再贴出来了,有需要的同学直接移步github,[netcoreDbHelper源码],记得star啊 :)。

本文转载自: 掘金

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

Redis常见的问题:缓存雪崩、缓存击穿、缓存穿透难题 (六

发表于 2021-11-06

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

Redis 缓存异常常见的问题有缓存雪崩、缓存击穿、缓存穿透。这三个问题一旦发生,请求量就会堆积到数据库层。如果请求的并发量大,那么就会导致数据库宕机或是故障。

缓存雪崩

缓存雪崩指的是,大量的应用请求无法在Redis缓存中处理,大量请求会发送到数据库层,导致数据库层的压力激增。

引发雪崩的原因有两个。

一是因为,缓存中有大量的缓存同时过期失效,导致大量请求无法得到处理;

还有是因为,Redis缓存实例发生故障宕机了,无法处理请求,大量请求就会发送到数据库层。

针对缓存同时失效的问题,有这样的解决方案。

一是尽量避免给大量的数据设置相同的过期时间,如果业务层上有要求某些数据同时失效,可以在设置过期时间时,给这个过期时间加上个较小的随机数,例如随机个1-3分钟。这样就不会有大量的数据同时过期失效,同时也保证了这些数据在相近的时间失效,仍然满足业务需求。

二是可以通过服务降级去应对缓存雪崩。针对不同的数据有不同的访问方式。

  • 针对非核心数据,暂停从缓存中获取这些数据,而是直接返回预定义、空值或是错误信息;
  • 针对核心数据,仍然走缓存,缓存找不到,继续通过数据库读取。

如果是因为实例发生宕机了,就需要依靠Redis缓存高可用集群了,通过发现节点宕机,将该节点切换为其它节点。

缓存击穿

缓存击穿指的是,针对某个访问非常频繁的热点数据的请求,无法在缓存中进行处理,访问该数据的请求就会发生给数据库层,导致数据库压力激增,会影响到数据库处理其他请求。

缓存击穿的情况,经常发生在热点数据过期失效时。

解决方案是,针对热点数据不设置过期时间,这样一来,热点数据就可以在缓存中处理,而不需要去数据库中获取了。

缓存穿透

缓存穿透指的是,访问的某个数据既不在缓存中也不在数据库中。这样的请求就会一直访问到数据库层。如果有大量的该数据请求进来,缓存就成了摆设,请求直接到了数据库,会影响到其它数据的访问。

发生缓存穿透的场景是,

  • 恶意攻击,专门访问数据库中没有的数据;
  • 业务请求的误操作,缓存和数据库中的数据都被误删除了,导致都没有数据;
  • 新业务上线时,缓存和数据库都没有用户的业务数据;

针对缓存穿透的问题,可以通过以下的方式进行处理:

  • 缓存空值或缺省值。当查询的数据在缓存和数据库都没有数据时,可以在缓存中缓存一个空值或缺省值,防止不存在数据访问到了数据库层。当后续新增了该数据时,注意需要将该空值缓存给移除掉。
  • 使用布隆过滤器。利用布隆过滤器的特点可以校验数据是否存在,每新增一个数据时,在布隆过滤器做个标记。这样当缓存缺失时,就可以先通过布隆过滤器检测到该数据不存在,就不用再去数据库中访问了。布隆过滤器可以使用Redis实现。
  • 前端拦截恶意请求。当有恶意请求访问不存在的数据时,在前端针对请求的参数进行合法性检测,过滤请求参数不合理、参数非法值、字段不存在的恶意请求。不让它们访问数据库,这样就不会产生缓存穿透的问题了。

本文转载自: 掘金

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

SpringBoot 多次读取requestBody

发表于 2021-11-06

背景:项目中需要对请求体做校验,因为HttpServletRequest不能多次读取inputStream,因此需要对它进行包装,Spring提供请求包装器HttpServletRequesWrapper可以处理这类问题。

  • 自定义请求包装器

image.png

使用成员变量存储请求体的内容

然后覆写getInputStream()方法,每次从成员变量中重新构造新的输入流

  • 自定义过滤器,替换请求

image.png

替换HttpServletRequest类型的请求,使用包装器包装

  • 在拦截器中读取请求体

image.png

image.png


本文转载自: 掘金

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

Python CSV文件操作 复习回顾 1 CSV 模块概

发表于 2021-11-06

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

复习回顾

我们日常工作中总是离不开对各类文件操作,强大的Python也提供我们对不同文件的模块方法。

  • Python内置提供open()、write()等基本的读取操作
  • 对文本进行删除、移动、复制等高级的操作的shutil模块
  • 对文件路径提供的相关操作,os.path、pathlib模块
  • 创建临时目录、文件等操作的template模块
  • 还有对文件进行专门的压缩、解压的zipfile、tarfile、gzip等模块

image.png

本期,我们将学习通过Python CSV库对Excel表格进行相关操作学习,let’s go~

  1. CSV 模块概述

csv 模块是python内置库,专门提供对电子表格.csv结尾的文件操作方法。

  • 什么是 csv 文件

+ csv 是使用逗号分隔值文件格式,可以使用记事本或者Excel打开
+ csv 文件以纯文本形式存在,通常用作数据存储使用
+ csv 文件每条记录都有同样的字段序列
  • csv 模块特点

+ csv 模块实现对csv文件表格数据的读写
+ csv 兼容Excel输出的数据文件
+ csv 可以定义其他程序CSV格式
  • csv 模块使用

csv 模块是Python内置的,因此只需在代码中使用import来进行导入即可

1
python复制代码import csv
  1. CSV 相关方法

  • csv 模块提供相关方法

方法 作用
csv.reader(csvfile) 对csv文件进行逐行变量
csv.writer(csvfile) 将用户数据写入到指定的文件对象中并转换成分隔符的字符串
csv.register_dialect(name,[,dialect]) 将dialect与name关联起来
csv.unregister(name) 从变种注册表中删除name对应变种
csv.get_dialect(name) 返回name对应的变种
csv.list_dialects() 返回所有已注册变种的名称
csv.field_size_limit() 返回解析器当前运行最大自短板大小

  • csv 模块提供相关属性

属性 作用
csv.QUOTE_ALL 指示writer对象给所有字段加上引号
csv.QUOTE_MINIMAL 指示writer对象仅包含特殊字符的字段加上引号
csv.QUOTE_NONNUMERTC 指示writer对象为所有非数字字段转换为float类型
csv.QUOTE_NONE 指示writer对象不使用引号引出字段

  • csv 模块提供相关类方法

方法 作用
class csv.DictReader(f,filedname,restkey,restval,dialect=’excel’,*args,**kwds) 创建一个可以映射到一个dict的对象
class csv.DictWriter(f,filedname,restkey,restval,dialect=’excel’,*args,**kwds) 创建一个可以以字典形式写入到输出行
class csv.dialect 是一个容器类,属性包含如何处理双引号、空白符、分隔符
class csv.excel 定义Excel生成csv文件的常规属性
class csv.excel_tab 定义Excel生成制表符分隔的CSV文件常规属性
class csv.unix_dialect 定义在UNIX系统上生成CSV文件常规属性
class csv.Sniffer 推断CSV文件格式

  • csv.DictReader 实例对象属性

方法 作用
csvreader.next() 等同于next(reader)返回迭代对象下一行
csvreader.dialect 变化描述,只读
csvreader.line_num 源迭代器一级读了的行数
csvreader.filednames 字段名称

  • csv.DictWriter 实例对象属性

方法 作用
csvwriter.writerow(row) 将row形参写入到writer文件对象
csvreader.writerows(rows) 将row* 中所有元素写入到writer文件对象

  • csv.Dialect 实例对象属性

方法 作用
Dialect.delimiter 一个用于分隔字段的单字符,默认为“,”
Dialect.doublequote 控制出现在字段中引号字符本身如何被引出
Dialect.escapechar 用于writer的单字符
Dialect.lineterminator 放在writer产生行的结尾,默认”\r\n”
Dialect.quotechar 单字符,用于包住有特殊字符的字段
Dialect.quoting 控制writer何时生成引号
Dialect.skipinitialspace 如果为True,则忽略,默认为False
Dialect.strict 如果为True则输入错误的CSV时抛出error异常,默认为False
  1. 小试牛刀

我们根据上面已经学习到的cvs模块的方法来实操一下吧

  • 读取指定csv文件中数据,并逐行打印出来

+ 我们要使用with上下文管理器进行打开csv文件
+ 设置dialect属性 delimiter 字符串有效界定符为“,”
+ 设置quotechar属性,quotechar 包含特殊字符的字段
1
2
3
4
5
6
7
8
python复制代码 import csv


with open(r"C:\Users\user\Desktop\devices.csv",newline="") as content:
content = csv.reader(content,delimiter=",",quotechar="|")
for row in content:

print("-".join(row))

总结

本期,我们对python内置模块csv相关方法进行学习,了解csv文件格式通常用于数据存储的。

我们在工作中,遇到需要存储数据的,也可以使用csv文件来存储。

以上是本期内容,欢迎大佬们点赞评论,下期见~

本文转载自: 掘金

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

从http协议到一次网络请求 前言 七层/四层网络模型 ht

发表于 2021-11-06

前言

你好奇之前学习的http协议、七层/四层网络模型和业务代码中的一次网络请求过程是怎么联系起来的吗? 你好奇框架封装的http请求和原生请求的区别在哪里吗?感兴趣的话,一起来看一下吧。
注:因微信平台封禁外部链接,所有参考文献链接均在文末。

七层/四层网络模型

七层/四层模型到底有什么用?其实,没啥用。哦,不,是对一般互联网产品开发人员来说没啥用。它就是在解释一个网络请求的的数据传输链路,代码开发中,没有特殊需求,是不用操作这些的。完整2020版网络协议图可参见:网络通讯协议-2020.pdf

image.png

以四层模型为例,上面第4层(应用层)就是日常研发人员写的接口操作的那一层。当这个网络请求的接口函数被执行之后,会根据下面的1,2,3层协议进行传输协议确认、路由分发、寻址之后到达下面的链路层,然后链路层实现数据的传输。

http协议&tcp协议

好多人把http协议和tcp协议混淆。其实,http协议是一种网络消息规范。就好比是两个人要开始交流,大家先约定好说中文还是英语。如果一个人只懂中文,一个人只懂英语,那怎么能交流的上。两台计算机之间消息传输也是一样的,约定好用http协议,就都用这种协议,这样一台计算机发送出去的消息,另一台计算机才能解析出来。至于为什么用http协议,而不是其他协议,当然是一开始互联网诞生的时候,有人提出了http协议,大家就懒得改其他的协议了,既然大家都用这种,那我也用这种吧。不过,近年来因为企业架构体系的演进,相继出现了rpc协议,dubbo协议等。

而tcp是一种消息传输协议。它是约定了这条按照http协议封装的网络消息是如何传输的。类似于2个人约定好用中文沟通之后,选择什么途径交流,是面对面还是打电话或者写信。tcp 就是负责这个传输过程的,所以tcp协议属于传输层。

因此,http协议和tcp协议没啥竞争关系啦,一个属于应用层、一个属于传输层。它们经常一起被提到,是因为都是最常用的协议,最佳合作伙伴。就像刘国梁和张继科一样,但是属于不同赛道,哈哈哈。

Linux 网络请求

由于C/C++本身没有http通讯工具,因此一般会安装libcurl组件库,使得linux系统具有网络请求功能。

curl 安装过程:

1、使用linux 的系统安装命令

1
复制代码  yum install curl

2、编译源码包进行安装

1
2
3
4
5
6
7
复制代码  (1)下载源代码

(2)编译源代码

(3)使用指定配置并安装

参考博客:curl码源编译安装

那么,curl 命令是如何发起这次的网络请求的?

我们可以查阅一下curl的源代码,确认具体的实现过程分析(具体还没看,后续补上)可以看这里:libcurl源代码分析。

如果不想看那么复杂的代码,可以看这个简版的一个博客:C语言实现http请求。从博客中的代码段可以看出,实现一次http请求,主要使用的c函数,除了strcat(字符串拼接)之外,还有几个比较关键的函数:inet_pton(ip地址转换函数)、socket 和 connect。

其中strcat 的作用就是封装http协议格式的消息,包括请求头、域名、端口、路由、发送消息内容、消息长度等。具体的请求报文和响应报文,可以看这里:http/https请求报文和响应报文。

根据socket函数详细解释soket函数,可知,其中一个参数用来标识是使用tcp还是udp协议传输,也就是我们上面提到的传输层协议的设置。当完成tcp协议的设置之后,调用connect函数会触发计算机与远端服务器进行连接,这个连接过程会经历我们常见的三次握手(TCP三次握手详解)。

至此,我们就介绍完了http协议和tcp协议具体在一次网络请求中是如何实现的。

网络数据传输过程

当消息按照http协议格式封装之后,并选择了tcp协议作为传输协议,计算机会调用网络层设备(路由器),进行路由的寻址,然后经过数据链路层发送数据。

这个过程从代码运行角度来分析,整体过程是高级语言(业务代码逻辑,也就是上面提到的消息处理逻辑)经过编译之后形成汇编语言、汇编语言执行CPU指令,CPU指令对计算机进行控制。然后计算机的数字信号(字节码)经过晶体管转换为模拟信号(电流信息),通过传输线路进行传输。到达对方计算机之后逆向执行该过程,得到请求数据。

CPU执行指令集,以及数字信号和模拟信号的转换解释,可以看这里:CPU指令集是如何执行的。

Java原生网络请求

JDK的java.net包中提供了访问http协议的基本功能类HttpURLConnection。具体的网络请求示例代码可以看这篇博客Java发起Http请求,从主要函数逻辑可以看出,Java发起网络请求同样也是经过了网络请求参数的设置:包括Host等信息,以及消息体的封装(所以,http和tcp协议才会被称为计算机组成的基本原理,而不同的语言栈编写业务代码,只不过是对原理的不同类型的实现。就像是买了怡宝和农夫山泉、他们都是水,只是品牌不同而已。因此才会有“语言只是实现工具”这种说法)。具体的消息发送底层原理也是通过socket进行发送的,相关源码分析可以看这里:HttpURLConnection源码分析。

由此可以看出,无论什么语言发起http网络请求,都需要遵循http协议规范的消息封装格式,选择tcp还是udp等传输协议,并调用socket进行消息的发送。而socket函数能够实现通信是因为计算机把高级编程语言最终编译为汇编语言,汇编语言执行了机器命令(CPU指令集等),控制内存单元、逻辑处理单元、以及外围电路进行工作。而外围电路中晶体管会将机器指令的数字信号转换为模拟信号,形成传输电流在电路中传输,当模拟信号传输到另一个消息接收设备的时候,再逆向执行这个过程,从而目标计算机可以获得当前计算机发送出去的消息,实现一次网络通信。

Spring 框架网络请求

框架是为了提升开发效率而实现的对底层函数的封装。Java的原生类库封装了socket函数实现了网络请求。但是使用起来不是特别优雅,而且代码比较冗余。

为了方便开发人员使用,同时降低代码的冗余率,Spring框架中的客户端网络请求函数同样是封装了Java的基础函数。Spring boot 常见的发起网络请求的方式除了上面的httpUrlConnection,还有httpClient、springBoot-restTemplate、closeableHttpClient 以及feign、openFeign。具体实现代码可以参考博客:Java中发起Http请求的常见方式。

π酱留言:本篇为初稿,后续不定时补充和完善细节

参考文献:

1、网络通讯协议-2020.pdf :www.colasoft.com.cn/download/ne…
2、curl码源编译安装:blog.csdn.net/peng3148995…
3、libcurl源代码分析:www.cnblogs.com/lidabo/p/45…
4、C语言实现http请求:www.jianshu.com/p/867632980…
5、http/https请求报文和响应报文:www.jianshu.com/p/64a64a049…
6、soket函数:baike.baidu.com 搜索socket函数
7、TCP三次握手详解:zhuanlan.zhihu.com/p/40013850
8、CPU指令集是如何执行的:zhidao.baidu.com/question/23…
9、Java发起Http请求:blog.csdn.net/qq_40036754…
10、HttpURLConnection源码分析:blog.csdn.net/Charon_Chui…
11、Java中发起Http请求的常见方式:blog.csdn.net/riemann_/ar…

转自微信公众号【程序媛的被窝】:mp.weixin.qq.com/s?__biz=Mzk…

本文转载自: 掘金

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

Mysql 温故知新系列「表记录」「批量更新」

发表于 2021-11-06

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

表记录管理

测试表

1
2
3
4
5
sql复制代码create table test(
id int PRIMARY key auto_increment,
uname VARCHAR(20),
sex TINYINT default 1
)

数据更新

常规操作

根据 id 为条件,精准匹配需要修改单条记录,使用 set column_name = xx 的方式,对指定的字段进行赋值操作,未指定的字段,将会维持旧的值

1
2
3
4
5
sql复制代码UPDATE test
SET uname = 'newe',
sex = 0
WHERE
id = 5;

image.png

扩展

我们也可以修改条件,用其他条件去匹配 1 条或多条符合要求的记录,统一按照 set 语句块中的设定对值进行覆盖操作

批量修改

这里的批量修改,与上面的 扩展 操作类型,一次更新多条记录。但有一个非常大的区别: 同样是 1 条 sql,但每条记录需要维护的字段值,却不一定相同!! 举个例子,我需要将 id=1 的记录,set name='a', id=2 的记录, set name='b'

最常见的操作,将需要维护的数据,拆为单个 update 语句进行更新。数量级较小时,可以容忍 for 循环对单个记录进行维护,但还是非常不建议这么做

推荐有 3 种批量更新的方案:

① 使用 replace into

对原纪录删除再批量插入,这样会存在一个致命的问题: 如果我们在使用这种方案更新时,遗漏了一些有字段的数据,则这些数据会丢失!!可以理解为,在执行第二步的插入操作时,这些数据是不在 sql 上!!

1
2
3
4
5
6
7
sql复制代码REPLACE INTO test_tbl (id, dr)
VALUES
(1, '2'),
(2, '3'),
...
(x, 'y'
);

② 使用 duplicate key

duplicate key 可以在原地对原纪录进行更新,相比于 REPLACE INTO,他的缺点时性能略差,优点是:我们可以按需更新部分字段

1
2
3
4
5
6
7
8
sql复制代码INSERT INTO test_tbl (id, dr)
VALUES
(1, '2'),
(2, '3'),
...
(x, 'y')
) ON DUPLICATE KEY
UPDATE dr = VALUES (dr);

③ 使用 update categories

具体写法可见代码

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码update categories 
set orderid = case id
when 1 then 3
when 2 then 4
when 3 then 5
end,
title = case id
when 1 then 'new title 1'
when 2 then 'new title 2'
when 3 then 'new title 3'
end
where id in (1,2,3)

方法,可行,但实践体验不如上两种,语句显得啰嗦,原理是使用了类似 switch...case 对条件匹配的记录进行操。

了解有这么一种批量更新的方法足够了

本文转载自: 掘金

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

JVM之老年代垃圾收集器

发表于 2021-11-06

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

引言:前文介绍了部分Java虚拟机新生代的垃圾收集器,于是今儿就介绍下部分老年代垃圾收集器。

老年代垃圾收集器

image.png

如上图,接下来我们要介绍的老年代垃圾收集器大致为CMS、Serial Old、Parallel Old收集器。

Serial Old垃圾收集器

Serial Old收集器是单线程收集器,并使用标记-整理算法,主要是提供给客户端下的HotSpot虚拟机使用。服务端模式下的话在JDK5之前是配合Parallel Scavenge收集器使用以及作为CMS收集器失败时的后备预案。

image.png

Parallel Old收集器

该收集器是Parallel Scavenge收集器的老年代版本,该收集器支持多线程并基于标记-整理算法实现。在注重吞吐量或者处理器资源比较稀缺的场合可以优先考虑Parallel Scavenge收集器加Parallel Old收集器的组合。其工作流程如下图所示:

image.png

CMS收集器

CMS收集器是一款以获取最短回收暂停时间为目标的收集器,很大一部分应用于互联网网站或者基于浏览器的B/S系统的服务端上,因为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户较好的交互体验。

CMS收集器是基于标记-清除实现的,其过程分为四个阶段:

  • 初始标记:标记GC Root能直接关联到的对象,速度很快,但还是需要暂停用户线程。
  • 并发标记:从GC Root的直接关联对象开始遍历整个对象图,这个过程很长不过不用暂停用户线程。
  • 重新标记:该阶段是为了修正并发标记阶段变动的标记。该阶段需要暂停用户线程。
  • 并发清除:清理删除标记阶段的已经死亡的对象。

CMS收集器是一个并发低停顿收集器,不过其还存在以下三个缺点:

1.在并发阶段虽然不会暂停线程,但是其因为占用了一部分线程会导致应用程序变慢,降低总吞吐量。虽然提供了增量式并发收集器的CMS收集器变种,也就是在并发标记和清理的时候让线程和用户线程交替运行,尽量减少垃圾收集线程独占资源的时间。

2.CMS收集器无法处理浮动垃圾(在CMS并发标记和并发清除阶段,用户进程运行时候产生的垃圾),还有就是垃圾收集阶段用户线程还要运行,所以需要预留一部分空间给用户线程使用。

3.因为CMS收集器是基于标记-清除算法的,这意味着收集结束会产生许多空间碎片,空间碎片过多会影响大对象的分配。

本文转载自: 掘金

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

Service Mesh 框架对比

发表于 2021-11-06

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

当前,业界主要有以下主要几种 Service Mesh 框架,下面进行详细的说明及对比。

1、Linkerd

Linkerd 是 Buoyant 公司 2016 年率先开源的高性能网络代理,是业界的第一款 Service Mesh 框架。其主要用于解决分布式环境中服务之间通信面临的一些问题,如网络不可靠、不安全、延迟丢包等问题。

Linkerd 使用Scala 语言编写,运行于 JVM,底层基于 Twitter 的 Finagle 库,并对其做了相应的扩展。最主要的是 Linkerd 具有快速、轻量级、高性能等特点,每秒以最小的延迟及负载处理万级请求,易于水平扩展。除此之外,还有以下功能:

  • 支持多平台:可运行于多种平台,比如 Kubernetes、DC/OS、Docker,甚至虚拟机或物理机。
  • 无缝集成多种服务发现工具。
  • 支持多协议,如 gRPC、HTTP/1.x、HTTP/2,甚至可通过 linkerd-tcp 支持 TCP 协议。
  • 支持与第三方分布式追踪系统 Zipkin 集成。
  • 灵活性、扩展性高,可通过其提供的接口开发自定义插件。

目前,Linkerd和Linkerd2并行开发,其情况如下:

  • Linkerd:Linkerd使用**Scala语言编写**,运行于JVM,底层基于 Twitter 的Finagle库,并对其做了相应的扩展。
  • Linkerd2:使用Go语言和Rust语言完全重写了Linkerd,专门用于Kubernetes。

Linkerd本身是数据平面,负责将数据路由到目标服务,同时保证数据在分布式环境中传输是安全、可靠、快速的。另外,Linkerd还包括控制平面组件Namerd,通过控制平面Namerd实现中心化管理和存储路由规则、中心化管理服务发现配置、支持运行时动态路由以及暴露Namerd API管理接口。

Linkerd架构图

图 3.2.1:Linkerd架构图

  • 控制平面

是在Kubernetes特定命名空间中运行的一组服务。这些服务可以完成各种事情:聚集遥测数据,提供面向用户的 API,向数据平面代理提供控制数据等。

由以下部分组成:

+ `Controller`:由`public-api`容器组成,该容器为`CLI`和`dashboard`提供接口 API。
+ `Destination`:数据平面中的每个代理都使用此组件来查找将请求发送到哪里。还用于获取服务配置信息,如:路由指标,重试和超时等。
+ `Identity`:该组件提供了证书的颁发,接受来自代理的[CSRs](https://en.wikipedia.org/wiki/Certificate_signing_request)并返回正确身份签名的证书。这些证书由代理在启动时获取,并且必须在代理准备就绪之前发出。随后,它们可用于`Linkerd`代理之间的任何连接以实现`mTLS`。
+ `Proxy Injector`:是一个注入程序,每次创建一个`pod`时,它都会接收一个`webhook`请求。该注入程序检查资源以查找特定于`Linkerd`的注释(`linkerd.io/inject: enabled`)。当存在该注释时,注入器将更改容器的规范,并添加 `initContainer`包含代理本身的以及附属工具。
+ `Service Profile Validator`:用于在保存新服务配置文件之前先对其进行验证。
+ `Tap`:从`CLI`和`dashboard`接收请求,以实时监视请求和响应。
  • 数据平面

由轻量级代理组成,这些代理作为sidecar容器与服务代码的每个实例一起部署。为了将服务“添加”到Linkerd服务网格,必须重新部署该服务的Pod,以在每个 Pod 中包含数据平面代理。

2、Envoy

同Linkerd一样,Envoy也是一款高性能的网络代理,于 2016 年 10 月份有 Lyft 公司开源,为云原生应用而设计,可作为边界入口,处理外部流量,此外,也作为内部服务间通信代理,实现服务间可靠通信。Envoy的实现借鉴现有生产级代理及负载均衡器,如Nginx、HAProxy、硬件负载均衡器及云负载均衡器的实践经验,同时基于C++编写及 Lyft 公司生产实践证明,Envoy性能非常优秀、稳定。

Envoy既可用作独立代理层运行,也可作为Service Mesh架构中数据平面层,因此通常Envoy跟服务运行在一起,将应用的网络功能抽象化,Envoy提供通用网络功能,实现平台及语言无法性。除此之外,还有以下功能:

  • 优先支持HTTP/2和gRPC,同时支持Websocket和 TCP 代理。
  • API 驱动的配置管理方式,支持动态管理、更新配置以及无连接和请求丢失的热重启功能。
  • L3/L4层过滤器形成Envoy核心的连接管理功能。
  • 通过与多种指标收集工具及分布式追踪系统集成,实现运行时指标收集、分布式追踪,提供整个系统及服务的运行时可见性。
  • 内存资源使用率低,Sidecar是Envoy最常用的部署模式。

3、Istio

Istio是由Google、IBM和Lyft发起的开源的Service Mesh框架。该项目在 2017 年推出,并在 2018 年 7 月发布了 1.0 版本。

Istio是Service Mesh目前的实现的典型代表,如果Sidecar是整个Service Mesh的数据面,那么Istio主要在控制面上做了更多的改进,Istio使用Envoy作为Sidecar,控制面相关全部使用Golang编写,性能上有了很大的提升。

Istio 首先是一个服务网格,但是Istio又不仅仅是服务网格:在 Linkerd,Envoy 这样的典型服务网格之上,Istio提供了一个完整的解决方案,为整个服务网格提供行为洞察和操作控制,以满足微服务应用程序的多样化需求。

Istio在服务网络中统一提供了许多关键功能:

  • 流量管理:控制服务之间的流量和 API 调用的流向,使得调用更可靠,并使网络在恶劣情况下更加健壮。
  • 可观察性:了解服务之间的依赖关系,以及它们之间流量的本质和流向,从而提供快速识别问题的能力。
  • 策略执行:将组织策略应用于服务之间的互动,确保访问策略得以执行,资源在消费者之间良好分配。策略的更改是通过配置网格而不是修改应用程序代码。
  • 服务身份和安全:为网格中的服务提供可验证身份,并提供保护服务流量的能力,使其可以在不同可信度的网络上流转。
  • 除此之外,Istio针对可扩展性进行了设计,以满足不同的部署需要。
  • 平台支持:Istio旨在在各种环境中运行,包括跨云, 预置,Kubernetes,Mesos等。最初专注于Kubernetes,但很快将支持其他环境。
  • 集成和定制:策略执行组件可以扩展和定制,以便与现有的ACL,日志,监控,配额,审核等解决方案集成。

这些功能极大的减少了应用程序代码,底层平台和策略之间的耦合,使微服务更容易实现。

Istio架构图

图 3.2.2:Istio架构图

Istio架构图中各个子模块功能如下:

  • Envoy:负责各个应用服务之间通信。
  • Pilot:管理和配置Envoy,提供服务发现、负载均衡和智能路由,保证弹性服务(服务超时次数、重试、熔断策略)。
  • Mixer:信息监控检查。
  • Istio-Auth:提供服务和服务、用户和服务之间的认证服务,实现访问控制,解决是谁访问的是哪个 API 的问题。

其中,图中的通信代理组件为Envoy,这是Istio原生引入的,但Linkerd也能够集成对接Istio。

4、Conduit

Conduit于 2017 年 12 月发布,作为由 Buoyant 继Linkerd后赞助的另外一个开源项目,作为Linkerd面向Kubernetes的独立版本。Conduit旨在彻底简化用户在Kubernetes使用服务网格的复杂度,提高用户体验,而不是像Linkerd一样针对各种平台进行优化。

Conduit的主要目标是轻量级、高性能、安全并且非常容易理解和使用。同Linkerd和Istio,Conduit也包含数据平面和控制平面,其中数据平面由Rust语言开发,使得Conduit使用极少的内存资源,而控制平面由Go语言开发。Conduit依然支持Service Mesh要求的功能,而且还包括以下功能:

  • 超级轻量级和极快的性能。
  • 专注于支持Kubernetes平台,提高运行在Kubernetes平台上服务的可靠性、可见性及安全性。
  • 支持gRPC、HTTP/2和HTTP/1.x请求及所有 TCP 流量。

Conduit以极简主义架构,以零配置理念为中心,旨在减少用户与Conduit的交互,实现开箱即用。

5、对比总结

下面对上述各种 Service Mesh 框架进行简单的比较汇总,见下表所示:

功能

Linkerd

Envoy

Istio

Conduit

代理

Finagle + Jetty

Envoy

Envoy

Conduit

熔断

支持。基于连接的熔断器Fast Fail和基于请求的熔断器Failure Accrual。

支持。通过特定准则,如最大连接数、 最大请求数、最大挂起请求数或者最大重试数的设定。

支持。通过特定准则,如最大连接数和最大请求数等的设定。

暂不支持。

动态路由

支持。通过设置Linkerd的dtab规则实现不同版本服务请求的动态路由。

支持。通过服务的版本或环境信息实现。

支持。通过服务的版本或环境信息实现。

暂不支持。

流量分流

支持。以增量和受控的方式实现分流。

支持。以增量和受控的方式实现分流。

支持。以增量和受控的方式实现分流。

暂不支持。

服务发现

支持。支持多种服务发现机制,如基于文件的服务发现、Consul、Zookeeper、Kubernetes等。

支持。通过提供平台无关的服务发现接口实现与不同服务发现工具集成。

支持。通过提供平台无关的服务发现接口实现与不同服务发现工具集成。

只支持Kubernetes。

负载均衡

支持。提供多种负载均衡算法。

支持。提供多种负载均衡算法,如Round Robin、加权最小请求、哈希环、Maglev等。

支持。提供多种负载均衡算法,如Round Robin、加权最小请求、哈希环、Maglev等。

支持。当前只有 HTTP 请求支持基于P2C + least-loaded的负载均衡算法。

安全通信

支持 TLS。

支持 TLS。

支持 TLS。

支持TLS。

访问控制

不支持。

不支持。

支持。基于RBAC的访问控制。

暂不支持。

可见性

分布式追踪(Zipkin)、运行时指标(InfluxDB、Prometheus、statsd)

分布式追踪(Zipkin)、运行时指标(statsd)

分布式追踪(Zipkin)、运行时指标(Prometheus、statsd)、监控(NewRepic、Stackdriver)

运行时指标(Prometheus)

部署模式

Sidecar 或者 per-host 模式

Sidecar 模式

Sidecar 模式

Sidecar 模式

控制平面

Namerd

没有,但可通过 API 实现。

Pilot、Mixer、Citadel

Conduit

协议支持

HTTP/1.x、HTTP/2、gRPC

HTTP/1.x、HTTP/2、gRPC、TCP

HTTP/1.x、HTTP/2、gRPC、TCP

HTTP/1.x、HTTP/2、gRPC、TCP

运行平台

平台无关

平台无关

目前支持Kubernetes,平台无关是最终实现目标。

只支持Kubernetes。

上述任何一个 Service Mesh 框架都能够满足您的基本需求。到⽬前为⽌,Istio 具有这几个服务⽹格框架中最多的功能和灵活性,灵活性意味着复杂性,因此需要团队更为充⾜的准备。如果只想使⽤基本的 Service Mesh 治理功能,Linkerd 可能是最佳选择。如果您想⽀持同时包含 Kubernetes 和 VM 的异构环境,并且不需要 Istio 的复杂性,那么 Conduit 可能是您的最佳选择,⽬前 Istio 也提供了同时包含 Kubernetes 和 VM 的异构环境的⽀持。

本文转载自: 掘金

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

1…407408409…956

开发者博客

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