Closed sczyh30 closed 1 year ago
关于 client 模型 的设计,需要关注以下几点: 1、shared 模型下,同一应用不同框架所引入的 OpenSergo SDK 对接的是否为同一个 OpenSergo Control Plane。 2、用户是否希望使用 share 模型
从这几点出发,share 模型 大致需要如下功能:
1、实例化 OpenSergoClient 实例的时候,需要辨别所要实例化的 OpenSergoClient 是否已存在(根据实例化参数判断),如果存在,则进行复用,不存在则进行实例化。
2、添加 client 模型
的可选配置项,供用户自行选择是否启用 share 模型。(资源开销角度我们建议 shared 模型,若用户有其他考量可不使用shared 模型)。
因此,得到初步设计如下: 1、多个 OpenSergoClient 实例之间需要互相隔离,需要管理好 OpenSergoClient 之间的资源问题。确定每个 OpenSergoClient 自己独有的属性对象有哪些:
OpenSergoClient
向OpenSergo Control Plane
订阅资源在本地的缓存OpenSergoClient
需要向OpenSergo Control Plane
订阅资源的Subscriber
的本地缓存OpenSergoClient
独自管理,需要社区进行讨论。)2、在 OpenSergo SDK 定义一个 OpenSergoOptions 的类, 这个类里包含整个SDK生命周期所需要的配置(例如 client 模型
,Logger 日志输出
,version 管理机制
等,)。OpenSergoOptions 可作为一个扩展点,后续如果有新的配置项,均可在其中定义
应用程序设置的全局 OpenSergoOptions
和OpenSergoClient实例化参数中的 OpenSergoOptions
进行合并。(如果未指定则使用默认值,如果应有程序
和框架
都指定了,优先级需要社区进行讨论后再做决定,推荐“就近原则”,以框架的在实例化参数中指定的为准)伪代码如下:
// OpenSergoOptions 类定义
public class OpenSergoOptions {
// shared/individual 可定义枚举类
private String clientMode;
...
}
// 默认全局配置
public static OpenSergoOptions globalOpenSergoOptions = new OpenSergoOptions("shared");
// 应用程序 APP 自定义全局配置
globalOpenSergoOptions.setClientMode("individual");
// sentinel 定义并初始化 OpenSergClient
OpenSergoOptions sentinelOpenSergoOptions = new OpenSergoOptions("shared");
OpenserClient sentinelOenserClient = new OpensergoClient(ip, port, sentinelOpenSergoOptions)
sentinelOenserClient.start()
// dubbo 定义并初始化 OpenSergClient
OpenSergoOptions dubboOpenSergoOptions = new OpenSergoOptions("shared");
OpenserClient dubboOpenserClient = new OpensergoClient(ip, port, dubboOpenSergoOptions)
dubboOpenserClient.start();
// Spring Cloud Alibaba 定义并初始化 OpenSergClient
OpenserClient dubboOpenserClient = new OpensergoClient(ip, port, null)
dubboOpenserClient.start();
// OpensergoClient 有参构造
public class OpensergoClient(String ip, int port, OpenSergoOptions openSergoOptions) {
// mergeOpenSergoOptions
// 需要注意 全局OpenSergoOptions 与参数OpenSergoOptions 冲突时的优先级设计
mergeOpenSergoOptions(openSergoOptions)
// 实例化 OpensergoClient
// 需要注意 OpensergoClient 的资源隔离,以及多 OpensergoClient 的管理
}
是否可以考虑:保持 OpenSergoClient 的接口不变,内部实现增加两种客户端:一个连接原生客户端 GrpcClient,一个是资源客户端 ResourceClient。连接客户端可以支持多个资源客户端,也可以只支持一个资源客户端。 创建时,不同的控制面,可以通过注册的方式来创建资源客户端,可以达到复用连接客户端的目的。
你说的资源客户端,我理解的是在 OpenSergoClient 内部维护一个类似于缓存的结构,将不同数据面的订阅信息( SubscribeTarget 与 ConfigSubscriber )按数据面分组保存,与控制面交互的时候,将订阅信息遍历出来,但还是沿用原先的订阅通道进行交互,是这样吗?
对差不多这个意思,这样可以保持 OpenSergoClient 本身的易用性,将复杂性隐藏到实现内部。 从内部结构上看,确实存在 资源端与连接端两种类型,资源端依赖连接端获取数据。 OpenSergoClient 负责将数据以统一的方式提供给使用方昂。
嗯。我们 的 SDK 目前在 OpenSergoClient 内部本身已经维护了一个 SubscribeTarget 与 ConfigSubscriber 的缓存,在与 控制面交互的时候,也确实是 将其逐个来订阅的。只是这个模式其实只要做个简单改进:把 OpenSergoClient 做成单例 即 shared模式
,就可以简单实现 单 OpenSergoClient 的复用了,但这样又会产生另一个问题,尽管shared模式
性能表现良好,但并不是所有接入方都希望使用shared模式
的 OpenSergoClient。
因此我们现在考虑的是:
OpenSergoClient 模式多样化
。如果同个应用中的不同接入方有自己的需求,用户不希望与某个接入方与其他接入方共用一个 OpenSergoClient 时,怎么去实现。因此我们后续的规划是,默认采用并且也推荐用户使用shared
模式,同时也提供individual
模式。OpenSergoClient 模式
引出的如何提高 OpenSergoClient 扩展性以及如何增强版本兼容
。客户端模式只是目前发现的其中的一个可配置项,后续OpenSergo继续推进,版本升级时会有更多功能点的可配置项。如何使 SDK 提供方与使用方都能够顺滑的升级,也是我们考虑的点。SDK 提供方,只需要增加 SDK 配置项以及相关逻辑代码,尽量少的修改 OpenSergoClient 暴露出去的接口或者方法。SDK 使用方 升级时只需要 添加相关配置即可。至于你提到的思路中,保留 OpenSergoClient 的接口不变,在 OpenSergoClient 中维护多个资源客户端,本质上也是将 OpenSergoClient 做成单例进行复用。而维护多个资源客户端应该是不必要的,因为对于 控制面来说,数据变动只会对 OpenSergoClient 发送一次数据,而数据面对于数据的处理是通过 ConfigSubscriber 来进行的(也就是说,不同资源客户端只需要 向 OpenSergoClient 添加不同的 ConfigSubscriber即可)。
同时你的方案已经是默认了 OpenSergoClient 是shared模式
,无法满足特殊情况下用户不希望采用单例 OpenSergoClient 与其他接入方共用的需求。
以上是我的理解 😃 ,如果我阐述的哪里不合适或者有其他更好的建议,欢迎详细展开,我们继续讨论哟 ~
OpenSergoClient 本身不做成单例,用户依然可以根据场景和需要创建多个 OpenSergoClient,满足特殊场景的用户需要。 OpenSergoClient 本身没有 shared 模式和 individual 模式,新的设计会按需自动共享连接,来处理不连接下的不同类型的资源更新,某种意义上这确实是 shared 模式。不过,这个 shared 是由用户的定义情况产生的,比如针对不同的资源,使用了相同的连接客户端,那么这就是 shared。 本质上,既然是相同的连接ip和 port,使用共享是合理的,极端情况下,确实不想共享,可以自行创建新的 OpenSergoClient 客户端。
Some of my ideas, just for reference:
OpenSergoClientManager
,用来自动创建和复用托管 client。如果用户不需要复用连接,则仍可以自主创建 OpenSergoClient,而不需要关注 manager。
getOrCreateClient(host, port)
在首次获取某个 endpoint 时创建并缓存对应 client,后续再取相同 endpoint 时直接返回对应的 client@sczyh30 我对上述几点,逐点进行考虑,得出如下:
- OpenSergoClient 作为基础的客户端,完全由使用者创建和管理,这个是没问题的;
- 我们现在需要的是 OpenSergo SDK 自身有一套管理机制,能够复用同个 endpoint 下的 client 连接。这里面是不是可以抽象出一种 OpenSergoClientManager,用来自动创建和复用托管 client。如果用户不需要复用连接,则仍可以自主创建 OpenSergoClient,而不需要关注 manager。
- getOrCreateClient(host, port) 在首次获取某个 endpoint 时创建并缓存对应 client,后续再取相同 endpoint 时直接返回对应的 client
// OpenSergoClientManager 管理复用的 Client public class OpenSergoClientManager { public OpenSergoClient getOrCreateClient(host, port) { return getOrCreateClient(host, port, defaultOpenSergoConfig); }
public OpenSergoClient getOrCreateClient(host, port, openSergoConfig) {
// TODO
if (首次) {
// 首次获取某个 endpoint 时创建并缓存对应 client,
return new OpenSergoClient(...)
} else {
// 后续再取相同 endpoint 时直接返回对应的 client
return getFromCache(endpoint);
}
}
}
// 独立的 Client 则通过 Client 的构造方法进行创建 public class OpenSergoClient { public OpenSergoClient(...) { ...... } }
> - 采用OpenSergoClientManager,需要考虑 client config 怎么提供;是否支持不同 endpoint 不同 config 的场景;
- 不同 endpoint 不同 config 的场景:实例化 OpenSergoClient 时,传入不同的 config 即可
- 相同 endpoint 不同 config 的场景:我们进行约定,以第一次实例化 OpenSergoClient 时传入的 config 为准
> - 异常场景考虑
暂未考虑
> - 是否需要做回收
首先,用户如果创建 Client 就代表着 Client 是有用的,如果 Client 的 keep-alive 机制能够确保断线重连,那么就没必要回收了。所以此处的回收机制要结合 Client 的 keep-avlie 断线重连机制 进行考虑
@jnan806 相同 endpoint 同个 config 的场景,用户在 OpenSergoClientManager 里面怎么提供?
@jnan806 相同 endpoint 同个 config 的场景,用户在 OpenSergoClientManager 里面怎么提供?
这种场景不正是我们希望的复用的最佳场景么:joy:,如果 endpoint 存在就缓存里取client,不存在就实例化
OpenSergoClientManager 主要解决的就是 不同 endpoint
场景的client 实例化,以及 相同 endpoint 相同 config
场景的 client 复用
@jnan806 相同 endpoint 同个 config 的场景,用户在 OpenSergoClientManager 里面怎么提供?
这种场景不正是我们希望的复用的最佳场景么😂,如果 endpoint 存在就缓存里取client,不存在就实例化
OpenSergoClientManager 主要解决的就是
不同 endpoint
场景的client 实例化,以及相同 endpoint 相同 config
场景的 client 复用
这里指的是 这个通用的 config 怎么提供?比如可以通过环境变量来配。
这个我们可以在 https://github.com/opensergo/opensergo-java-sdk/issues/22 里面进一步展开
这里指的是 这个通用的 config 怎么提供?比如可以通过环境变量来配。
配置的提供方式,我个人不建议以 OpenSergo 为主要的配置文件或者环境变量来提供。
我们仅仅是 SDK ,只需要提供接口,或者 构造方法,或是 Builder ,而配置文件或环境变量,最好是注入到框架中,由框架进行转换后调用 SDK 时提供 config。
// OpenSergoClientManager 获取 Client, config 暂时以参数形式传递,具体方式待定
public OpenSergoClient getOrCreateClient(host, port, openSergoConfig) {
// TODO 在首次获取某个 endpoint 时创建并缓存对应 client,后续再取相同 endpoint 时直接返回对应的 client
}
// Sentinel 中的 的配置
public class SentinelOpenSergoConfig {
public String host;
public int port;
......
}
// 将 SentinelOpenSergoConfig 转为 openSergoConfig后,实例化 OpenSergoClient
public OpenSergoConfig convertToOpenSergoConfig(SentinelOpenSergoConfig config) {
// TODO 转化逻辑
}
sentinel:
opensergo:
type: opensergo
host: opensergo.svc.endpoint
port: opensergo.svc.port
TLS: ...
此处配置的提供方式,应该是接入框架需要考虑的问题
至于 SDK 中 OpenSergoClientManager 如何接受 config 的配置,在 #22 进行讨论。
OpenSergoClientManager
需要保证单例,这样无论间接创建还是直接创建多份 OpenSergoClient
实例,都不会造成资源的浪费。
我们的微服务通常会使用好几种框架,比如 RPC 使用的 Spring Cloud Alibaba,流量治理使用的 Sentinel,数据库中间件使用的 ShardingSphere。在这些框架都对接了 OpenSergo 的情况下,在接入到 OpenSergo 控制面,对 OpenSergo client 的使用会有几种方式:
目前的实现未预留 shared 的设计,社区可以一起讨论下这几种情况的最佳实践,以及 shared client 的设计。