Apache Ignite简介

Apache Ignite是一个开源分布式的数据库、缓存和计算平台。它的核心是一个内存数据网格,它可以将内存作为分布式的持久化存储,以提供高性能和可扩展性。它还提供了一个分布式的键值存储、SQL数据库、流式数据处理和复杂的事件处理等功能。

Ignite的核心竞争力包括:

  • 兼容Mysql、Oracle语法
  • 性能强大,可以水平扩展
  • 缓存与数据库同源,可通过KV、SQL、JDBC、ODBC等方式访问

同时,为了便于开发,除了jdbc、odbc、restful方式外,Ignite还官方提供了Java、C++、.Net、Python、Node.js、PHP等语言的客户端,可以方便的与Ignite进行交互。

ignite-storage-access

Apache Ignite的问题

频繁创建删除表,导致IGNITE_DISCOVERY_HISTORY_SIZE超过限制

根据Ignite2的拓扑模型,集群的拓扑版本会在创建表/删除表的时候发生变化,该变化版本号递增,且仅会保留最近$IgniteDiscoveryHistorySize条记录,程序某处会写死读取版本为0的数据,读取不到时,ignite集群会重启。默认值为500。
社区issue: https://github.com/apache/ignite/issues/10894
笔者暂时没有时间来修复这个issue,可以通过将IGNITE_DISCOVERY_HISTORY_SIZE设置地比较大,来规避这个问题。

Ignite2客户端易用性问题

Ignite2客户端超时默认值不合理

Ignite2客户端的连接超时、执行sql超时默认都是0,没有精心研究过配置的用户在异常场景下,应用程序可能会hang住。从易用性的角度来说,网络通信的任何操作,默认都应该有超时时间。

Ignite2客户端不支持永远的重试

Ignite通过预先计算出所有需要重连的时间点来实现重连,如果想配置成永远的重连,会因为时间点的计算导致内存溢出。从易用性的角度来说,应该支持永远的重连。

Ignite2客户端在某些异常下无法自愈

当client执行sql的时候,碰到如下异常的时候,无法自愈。可以通过执行SQL对client进行定期检查并重建。

1
Caused by: org.apache.ignite.internal.client.thin.ClientServerError: Ignite failed to process request [47]: 50000: Can not perform the operation because the cluster is inactive. Note, that the cluster is considered inactive by default if Ignite Persistent Store is used to let all the nodes join the cluster. To activate the cluster call Ignite.cluster.state(ClusterState.ACTIVE)

Ignite2 SocketChannel泄露问题

Ignite客户端在连接时,如果对应的Server端没有启动,会导致SocketChannel泄露,已由笔者提交代码修复:https://github.com/apache/ignite/pull/11016/files

通用 GitHub Actions

commit lint

1
2
3
4
5
6
7
8
9
10
11
12
name: commit lint
on:
pull_request:
branches:
- main

jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: wagoid/commitlint-github-action@v5

line lint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: line lint
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
name: line lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: linelint
uses: fernandrone/linelint@master

Go

golangci-lint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
name: go ci Lint
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
args: --timeout 3m0s

go mod check

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
name: go mod check

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
go_mod_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Go Mod Check Action
uses: hezhangjian/go-mod-check-action@main
with:
prohibitIndirectDepUpdate: 'true'

go unit tests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
name: go unit test

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
go_unit_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: setup OpenGemini
uses: hezhangjian/setup-opengemini-action@main
- name: Run coverage
run: go test ./... -coverpkg=./padmin/... -race -coverprofile=coverage.out -covermode=atomic

Java GitHub Actions

maven checkstyle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
name: java checkstyle
on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
java_checkstyle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Maven Central Repository
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: checkstyle
run: mvn -B clean checkstyle:check

maven spotbugs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
name: java spotbugs
on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
java_spotbugs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Maven Central Repository
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: spotbugs
run: mvn -B -DskipTests clean verify spotbugs:spotbugs

maven unit tests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
name: java unit tests
on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
java_unit_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Maven Central Repository
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: unit tests
run: mvn -B clean test

TypeScript GitHub Actions

npm build test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
name: npm build test
on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
npm_buid_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: latest
- run: npm install
- run: npm run build
- name: setup pulsar
uses: hezhangjian/setup-pulsar-action@main
- run: npm run test

prettier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
name: prettier
on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: latest
- run: npm install --save-dev prettier
- run: npm install --global prettier
- run: prettier --check '**/*.ts'

概述

我们以xyz文件格式为例,来说明文件编解码的代码设计。xyz文件格式内容如下:

  • header部分:文件头,包含文件版本号、文件类型、文件大小等信息
  • body部分:文件主体

通用设计大概如下

classDiagram
    class XyzHeader {
        + byte[] content
    }
    class XyzBody {
        + byte[] content
    }
    class Xyz{
        + XyzHeader header
        + XyzBody body
    }
    class XyzReader {
        + Xyz read(fileName: string)
        + void process(String fileName, XyzProcessor processor)
        - XyzHeader readHeader()
        - XyzBody readBody()
    }
    class XyzProcessor {
        <>
        + void processHeader(XyzHeader header)
        + void processBody(XyzBody body)
    }
    class XyzReadCollectProcessor {
        Xyz getXyz()
    }
    Xyz --> XyzHeader: contains
    Xyz --> XyzBody: contains
    XyzReader --> Xyz: reads
    XyzReader --> XyzProcessor: processes
    XyzReadCollectProcessor --|> XyzProcessor: implements

Java

使用java.io.RandomAccessFilejava.nio.channels.FileChannel来实现文件读取,使用io.netty.buffer.ByteBuf来读写文件。

核心代码举例:

XyzReader:

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
public class XyzHeader {
private byte[] content;
}
public class XyzBody {
private byte[] content;
}
public class Xyz {
private XyzHeader header;
private XyzBody body;
}
public interface XyzProcessor {
void processHeader(XyzHeader header);
void processBody(XyzBody body);
}
public class XyzReadCollectProcessor implements XyzProcessor {
private final Xyz xyz = new Xyz();
public Xyz getXyz() {
return xyz;
}
}
public class XyzReader {
public Xyz read(String fileName) throws Exception {
}

private XyzHeader readHeader(FileChannel fileChannel) throws Exception {
}

private XyzBody readBody(FileChannel fileChannel) throws Exception {
}
}

Go

Go标准库

timeout

1
2
3
client := http.Client{
Timeout: timeout,
}

connection timeout

1
2
3
4
5
6
7
client := http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: timeout,
}).Dial,
},
}

Java

标准库(jdk17+)

timeout

1
2
3
4
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://example.com"))
.timeout(Duration.ofSeconds(10))
.build();

connectionTimeout

1
2
3
HttpClient.Builder builder = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.version(HttpClient.Version.HTTP_1_1);

Reactor Netty

timeout

1
HttpClient client = HttpClient.create().responseTimeout(Duration.ofSeconds(10));

connectionTimeout

1
HttpClient client = HttpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);

多语言SDK设计的常见问题

日志打印的设计策略

在SDK的关键节点,比如初始化完成、连接建立或者连接断开,都可以打印日志。如果是PerRequest的日志,一般默认不会打印INFO级别的日志。

SDK应该避免仅仅打印错误日志然后忽略异常;相反,它应该提供机制让调用者能够捕获并处理异常信息。这种做法有助于保持错误处理的透明性,并允许调用者根据需要采取适当的响应措施。正如David J. Wheeler所说”Put the control in the hands of those who know how to handle the information, not those who know how to manage the computers, because encapsulated details will eventually leak out.”把控制权放到那些知道如何处理信息的人手中,而不是放在那些知道如何管理计算机的人手中,因为封装的细节最终都会暴露。

是否需要使用显式的start/connect方法?

像go这样的语言,一般来说不太在意特定的时间内,某个协程是否处于阻塞等待连接的状态。而在java这样的语言,特别是在采用响应式编程模型的场景下,通常需要通过异步操作来管理连接的建立。这可以通过显式的start/connect方法来或者是异步的工厂方法来实现。

背景

TLS(Transport Layer Security)是一种安全协议,用于在两个通信应用程序之间提供保密性和数据完整性。TLS是SSL(Secure Sockets Layer)的继任者。

不同的编程语言处理TLS配置的方式各有千秋, 本文针对TLS配置参数的设计进行探讨。

代码配置中,建议使用反映状态的参数名。

通用参数

  • tlsEnable: 是否启用TLS

Go

推荐使用方式一

方式一:

  • tlsConfig *tls.Config: Go标准库的内置TLS结构体

方式二:

由于Go不支持加密的私钥文件,推荐使用文件内容,而不是文件路径,避免敏感信息泄露。

  • tlsCertContent []byte: 证书文件内容
  • tlsPrivateKeyContent []byte: 私钥文件内容
  • tlsMinVersion uint16: TLS最低版本
  • tlsMaxVersion uint16: TLS最高版本
  • tlsCipherSuites []uint16: TLS加密套件列表

Java

Java的TLS参数基本上都是基于keystore和truststore来配置的。一般常见设计如下参数:

  • keyStorePath: keystore文件路径
  • keyStorePassword: keystore密码
  • trustStorePath: truststore文件路径
  • trustStorePassword: truststore密码
  • tlsVerificationDisabled: 是否禁用TLS校验
  • tlsHostnameVerificationDisabled: 是否禁用TLS主机名校验,仅部分框架支持。
  • tlsVersions: TLS版本列表
  • tlsCipherSuites: TLS加密套件列表

JavaScript

JavaScript可以使用标准库里的tls.SecureContextOptions

Kotlin

kotlin的Tls与Java相同:

  • keyStorePath: keystore文件路径
  • keyStorePassword: keystore密码
  • trustStorePath: truststore文件路径
  • trustStorePassword: truststore密码
  • tlsVerificationDisabled: 是否禁用TLS校验
  • tlsHostnameVerificationDisabled: 是否禁用TLS主机名校验,仅部分框架支持。
  • tlsVersions: TLS版本列表
  • tlsCipherSuites: TLS加密套件列表

Python

推荐使用方式一

方式一

  • ssl.SSLContext: Python标准库的内置TLS结构体

方式二

Python可以使用文件路径以及加密的私钥文件。

  • tlsCertPath: 证书文件路径
  • tlsPrivateKeyPath: 私钥文件路径
  • tlsPrivateKeyPassword: 私钥密码
  • tlsMinVersion: TLS最低版本
  • tlsMaxVersion: TLS最高版本
  • tlsCipherSuites: TLS加密套件列表

Rust

由于常见的Rust TLS实现不支持加密的私钥文件,推荐使用文件内容,而不是文件路径,避免敏感信息泄露。 一般常见如下设计参数:

  • tls_cert_content Vec: 证书内容
  • tsl_private_key_content Vec: 私钥内容
  • tls_versions: TLS版本列表
  • tls_cipher_suites: TLS加密套件列表
  • tls_verification_disabled: 是否禁用TLS校验

根据Python项目的需求和特性,可以为Python的Http SDK项目选择以下命名方式:

  • xxx-client-python:如果这个项目只有Http SDK,没有其他协议的SDK,推荐使用这个命名方式。
  • xxx-http-client-python:当存在其他协议的SDK时,可以使用这个命名方式,以区分不同协议的SDK。
  • xxx-admin-python:当项目使用其他协议作为数据通道,使用HTTP协议作为管理通道时,可以使用这个命名方式。

由于Python的调用方式通常是模块名.类名.方法名

TypeScript的调用方式通常是

1
2
import { ClassName } from 'moduleName';
const object = new ClassName();

根据TypeScript项目的需求和特性,可以为TypeScript的Http SDK项目选择以下命名方式:

  • xxx-client-ts:如果这个项目只有Http SDK,没有其他协议的SDK,推荐使用这个命名方式。在npm可以注册为”xxx”。
  • xxx-http-client-ts:当存在其他协议的SDK时,可以使用这个命名方式,以区分不同协议的SDK。
  • xxx-admin-ts:当项目使用其他协议作为数据通道,使用HTTP协议作为管理通道时,可以使用这个命名方式。

根据Go项目的需求和特性,可以为Go的Http SDK项目选择以下命名方式:

  • xxx-client-go:如果这个项目只有Http SDK,没有其他协议的SDK,推荐使用这个命名方式。
  • xxx-http-client-go:当存在其他协议的SDK时,可以使用这个命名方式,以区分不同协议的SDK。
  • xxx-admin-go:当项目使用其他协议作为数据通道,使用HTTP协议作为管理通道时,可以使用这个命名方式。

由于Go语言的调用方式是包名.结构体名.方法名,所以在设计SDK时,需要考虑包名、结构体名、方法名的设计。

以xxx业务为例,假设业务名为xxx,推荐包名也为xxx,结构体名为Client

目录布局可以是这样子的:

1
2
3
xxx-client-go/
|-- xxx/
| |-- client.go

Java Http SDK设计

根据Java项目的需求和特性,可以为Java的Http SDK项目选择以下命名方式:

  • xxx-client-java:如果这个项目只有Http SDK,没有其他协议的SDK,推荐使用这个命名方式。
  • xxx-http-client-java:当存在其他协议的SDK时,可以使用这个命名方式,以区分不同协议的SDK。
  • xxx-admin-java:当项目使用其他协议作为数据通道,使用HTTP协议作为管理通道时,可以使用这个命名方式。

maven模块设计

maven module命名可以叫xxx-client或者xxx-http-client,这通常取决于你的项目是否有其他协议的client,如果没有,那么推荐直接使用xxx-client。

假设包名前缀为com.xxx,module视图如下:

1
2
3
4
5
6
xxx-client-java(maven artifactId: xxx-client-parent)/
|-- xxx-client-api(接口定义,包名com.xxx.client.api,jdk8+)
|-- xxx-client-common/core(核心实现,包名com.xxx.client.common,jdk8+)
|-- xxx-client-jdk(基于jdk http client的实现,包名com.xxx.client.jdk,jdk17+)
|-- xxx-client-okhttp(基于okhttp的实现,包名com.xxx.client.okhttp,jdk8+)
|-- xxx-client-reactor(基于reactor-netty的实现,包名com.xxx.client.reactor,jdk8+)

依赖关系图:

graph TD
api[xxx-client-api]
common[xxx-client-common]
jdk[xxx-client-jdk]
okhttp[xxx-client-okhttp]
reactor[xxx-client-reactor]

common --> api

jdk --> common
okhttp --> common
reactor --> common
0%