wenmo8 发布的文章

这是小卷对分布式系统架构学习的第6篇文章,关于链路追踪,之前写过traceId的相关内容:
https://juejin.cn/post/7135611432808218661,不过之前写的太浅了,且不成系统,只是简单的理解,今天来捋一下链路追踪的理论

1.为什么需要链路追踪

在复杂的分布式系统中,系统通常由多个独立的服务组成,几乎每一个前端请求都会形成一个复杂的分布式服务调用链路。

这种复杂的系统会带来一系列问题:

1.如何快速定位问题,判断故障影响范围?

2.如何梳理服务间的依赖关系?

链路追踪的用途就是为了知道请求在系统中的流转路径,定位性能瓶颈,诊断故障等。

2.追踪与跨度

要理解链路追踪的原理,先理解
Trace
追踪 和
Span
跨度两个概念。

  • Trace(追踪)
    :一个完整的用户请求流程,从用户发起请求开始,到请求结束。一个追踪包含多个 Span。
  • Span(跨度)
    :一种表示工作单元的结构,通常对应着请求经过的某个服务或者操作,每个Span包含以下信息:
    • Span ID:唯一标识当前Span
    • Trace ID:标识属于同一个Trace的所有Span
    • 父Span ID:如果当前Span由另一个Span引发,则会记录父Span ID
    • 时间戳、标签和日志

每一次Trace是由若干个有顺序、有层级关系的Span组成的一棵追踪树结构,图片来源
Dapper论文

3.链路追踪的概念


广义
上,分布式链路追踪系统可以分为三个部分:
数据收集、数据存储、数据展示


狭义
上,指链路追踪的
数据收集
部分

比如:Spring Cloud Sleuth就属于狭义的追踪系统,通常会搭配 Zipkin 作为数据展示,搭配 Elasticsearch 作为数据存储来组合使用。

这里从Dapper论文的内容总结下链路追踪的设计目标如下:

  • 低开销
    :追踪系统对正在运行的服务应该具备很小的性能影响
  • 应用层透明性
    :开发人员无需关注追踪系统,作为业务组件,尽可能减少对业务系统的代码侵入性。使用时透明,减少开发负担。如果需要依赖开发者配合才能使追踪系统生效,这样是无法满足追踪系统“无所不在的部署”这个需求
  • 可扩展性
    :支持分布式部署,具备良好的扩展性,能支持的组件越多越好,至少在接下来几年内能处理服务和集群的规模
  • 数据的快速分析
    :追踪数据生成后的数据分析要快,分析维度尽可能多,理想情况下是一分钟内,数据的新鲜度能快速对生产异常做出反应。

4.功能模块

生产环境的链路追踪系统,主要分为4个大模块:

4.1 埋点与生成日志

分客户端埋点、服务端埋点、以及客户端和服务端双向埋点,埋点日志通常包含了traceId、spanId、调用的开始时间,协议类型、调用方ip和端口,请求的服务名、调用耗时,调用结果,异常信息等

需要写log,高并发服务中,性能影响越重,通常使用采样+异步log的方式解决

4.2 收集和存储日志

特点是需支持分布式日志采集方案,一般还会用MQ作为缓冲

每个机器上有个daemon,这里的daemon指的后台服务进程,专门用于日志收集和Trace转发;

多级collector,类似pub/sub架构,可以负载均衡;

聚合数据进行实时分析和离线存储;

离线分析 需将同一条调用链的日志汇总在一起;

4.3 分析和统计调用链数据

调用链跟踪分析
:把同一TraceID的Span收集起来,按时间排序就是timeline。
把ParentID串起来就是调用栈

4.4数据展现以及决策支持

5.数据收集的三种实现方式

不论是狭义还是广义的链路追踪系统,都必须包含数据收集的工作,介绍三种主流的数据收集方式:

5.1基于日志的追踪Log-based Tracing

思路是将 Trace、Span 等信息直接输出到应用日志中,然后将日志归集过程汇聚到一起,再从全局日志信息中反推出完整的调用链拓扑关系;

日志追踪对网络消息完全没有侵入性,对应用程序只有很少量的侵入性,对性能的影响也非常低

缺点:

  • 依赖日志归集过程,日志不求决对的一致和连续,精准性较低。
  • 业务服务的调度和日志归集不是由同一个进程同时完成的,存在日志延迟或丢失的问题,从而产生追踪失真的情况

5.2基于服务的追踪

目前最常见的追踪实现方式,如Zipkin、SkyWalking、Pinpoint 等主流追踪系统都采用这种方式,其实现思路是:
通过某些手段给目标应用注入追踪探针(Probe)
,比如针对 Java 应用,一般就是通过 Java Agent 注入的。

探针可以看作是目标服务身上的小型微服务系统,有服务注册、心跳检测等功能,有专门的数据收集协议,可以把从目标系统收集的服务调用信息,通过HTTP 或者 RPC 请求,发送给追踪系统

该方式具备追踪的精确性和稳定性,缺点是消耗的资源更多,具备更强的侵入性

下图是pinpoint的调用栈示例:

Pinpoint 本身就是比较重负载的系统(运行它必须先维护一套 HBase),服务追踪这方面国产开源的Skywalking更加轻量化

5.3基于边车代理的追踪

·基于边车代理的追踪是服务网格的专属方案,也是最理想的分布式追踪模型,对应用完全透明,无论是日志还是服务本身,都不会有任何变化;

边车代理本身对应用透明的工作原理,决定了它只能实现服务调用层面的追踪,像前面 Pinpoint 截图那样的本地方法调用级别的追踪诊断,边车代理是做不到的。

6.链路追踪协议

链路追踪协议的发展历史,2016 年 11 月,CNCF 技术委员会接受了 OpenTracing 作为基金会的第三个项目。OpenTracing 是一套与平台无关、与厂商无关、与语言无关的追踪协议规范。

但是,Google 却在这个时候出来并提出了与 OpenTracing 目标类似的 OpenCensus 规范,且得到了巨头 Microsoft 的支持,这样就形成了OpenTracing 和 OpenCensus两大可观测性的阵营。

2019 年,OpenTracing 和 OpenCensus 宣布握手言和,共同发布了可观测性的终极解决方案OpenTelemetry,并宣布会各自冻结 OpenTracing 和 OpenCensus 的发展。

6.1 OpenTracing

概述
:OpenTracing是一个开放的API规范,旨在通过提供一套统一的接口,帮助开发人员能够在其应用中实现分布式追踪

和一般的规范标准不同,Opentracing 不是传输协议,消息格式层面上的规范标准,而是一种语言层面上的
API 标准
。以 Go 语言为例,只要某链路追踪系统实现了 Opentracing 规定的接口(interface),符合Opentracing 定义的表现行为,那么就可以说该应用符合 Opentracing 标准。

官网:
https://opentracing.io/

6.2 OpenCensus

OpenCensus为微服务和单体应用提供可观测性,通过追踪请求在服务之间传播并捕获关键的时间序列指标。其核心功能是从应用程序中收集追踪和指标,能够在本地显示并将其发送到任何分析工具(也称为“后端”)

官网:
https://opencensus.io/

6.3 OpenTelemetry

官网:
https://opentelemetry.io/

OpenTelemetry 可以用于从应用程序收集数据。它是一组工具、API 和 SDK 集合,我们可以使用它们来检测、生成、收集和导出遥测数据(指标、日志和追踪),以帮助分析应用的性能和行为。具体的解释为:

  • 一个可观测性框架和工具包,旨在创建和管理遥测数据,如追踪、指标和日志。
  • 与供应商和工具无关,这意味着它可以与各种可观测性后端一起使用,包括开源工具如Jaeger和Prometheus,以及商业产品。
  • 不是像Jaeger、Prometheus或其他商业供应商那样的可观测性后端。
  • 专注于遥测的生成、收集、管理和导出。OpenTelemetry的一个主要目标是能够轻松地在应用程序或系统中插桩,无论它们使用何种语言、基础设施或运行时环境。遥测的数据存储和可视化故意留给其他工具。

篇幅问题就不继续详细介绍这三个协议了,感兴趣的小伙伴们可以自行去官方了解。

总结:今天讲了链路追踪的理论知识,包括:追踪与跨度的概念,一个追踪系统的模块划分,数据收集的3种方式,以及链路追踪协议的发展。了解这些概念后再更容易去理解开源的链路追踪框架。

文件包含

文件包含漏洞(File Inclusion Vulnerability)是一种常见的网络安全漏洞,主要出现在应用程序中不安全地处理文件路径时。攻击者可以利用此漏洞执行恶意文件,或者访问不该被访问的文件

1. low

有3个页面

随便点击一个,可以在url处发现传参点

访问:
http://127.0.0.1/DVWA/vulnerabilities/fi/?page=file4.php
,可以看到下面的提示

远程文件包含

本地文件包含

源码审计

<?php

// The page we wish to display
$file = $_GET[ 'page' ];

?>

可以看到没有做任何过滤

2. medium

访问
file4.php,
依然可以访问

访问
?page=../../phpinfo.php
发现没有回显,可以确定是被过滤了

尝试双写绕过,成功绕过

..././ <=> ../

源码审计

<? php
// 显示的页面
$file = $_GET['page'];

// 输入验证
$file = str_replace(array("http://", "https://"), "", $file);
$file = str_replace(array("../", "..\\"), "", $file);
?>


http://; https://; ../ ; ..\
替换为了
' '
,可以利用双写绕过过滤

3. high

访问
1.php
看看报错信息

直接文件读取

源码审计

<?php
// 显示的页面
$file = $_GET['page'];

// 输入验证
if (!fnmatch("file*", $file) && $file != "include.php") {
    // 这不是我们想要的页面!
    echo "错误:文件未找到!";
    exit;
}  
?>

要求了参数必须以file开头,即
?page=file..........
,否则就会报错

4. impossible

源码审计

<?php
  // 我们希望显示的页面
$file = $_GET['page'];

// 只允许 include.php 或 file{1..3}.php
if ($file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php") {
    // 这不是我们想要的页面!
    echo "错误:文件未找到!";
    exit;
}
?>

只允许访问
include.php 或 file{1..3}.php
页面,其他页面全部过滤

本文介绍在
高德开放平台
中,申请、获取地图
API

Key
的方法;同时通过简单的
Python
代码,调取
API
信息,对所得
Key
的可用性加以验证。

首先,我们进入
高德开放平台

官方网站
。如果大家是第一次使用高德地图开放平台,那么需要点击右上角注册一个开发者账号。

注册完毕后,登录这一账号,并点击如下图紫色框内所示的“
控制台
”。

image

随后,点击左侧“
应用管理
”中的“
我的应用
”,并到右上角点击“
创建新应用
”;如下图所示。

随后,在弹出的窗口中,依据我们的需求,输入应用的名称及其类型;如下图所示。其中,
名称
就按照我们实际需求来命名,
类型
则按照我们的应用场景来选择即可。

接下来,在界面右上角,点击“
添加Key
”;如下图所示。

随后,为这个
Key
命名,并选择所需的服务平台;如下图所示。我这里后续打算基于
Python
语言的
requests
库来使用
API
,所以这里就选择了“
Web服务
”。

随后,即可生成
Key
,如下图中紫色框内所示。

获取
Key
之后,我们可以验证一下这个
Key
是否可以使用。在这里,给出一个基于
Python
语言,使用高德开放平台提供的“
天气查询
”功能,对
Key
加以验证的代码;代码如下。

# -*- coding: utf-8 -*-
"""
Created on Wed May 29 20:36:11 2024

@author: fkxxgis
"""

import json
import requests

key = "在这里输入您的key"
city_code = "410300"

response = requests.get("https://restapi.amap.com/v3/weather/weatherInfo?key=" + key + "&city=" + city_code)
text = response.text
result = json.loads(text)
need = result["lives"][0]

print("您查询的" + need["province"] + need["city"] + "在" + need["reporttime"] + "的天气为" + need["weather"] + ",温度为" + need["temperature_float"] + "," + need["winddirection"] + "风" + need["windpower"] +"级。")

其中,大家在使用上述验证代码时,需要将
key = "在这里输入您的key"
这句代码引号中的内容,修改为通过前述操作所获取的
Key
;同时,还可以修改
city_code
,从而对不同城市、区域的天气加以查询。这里关于城市的代码,大家可以参考这个
网站

随后,即可执行上述代码;执行代码后,将打印出指定区域在查询时刻的实时天气。

可以看到,已经可以获取到对应地区的实时天气了,说明前述获取
Key
的流程已经成功。

此外,关于
API
使用的方法,我们后续会用更多博客来详细介绍,所以上述代码我们就暂时不再逐句解释了,在这里仅仅用来验证我们所获取的
Key
的可用性。

此外,对于每一位高德开放平台的开发者,我们所能调取不同功能
API
的次数也是有每日限制的,具体配额我们可以在“
我的应用
”中,通过“
查看配额
”选项来查阅;如下图所示。

至此,大功告成。

随着多核的发展,
Python
中并发编程也变得越来越广泛且发展很快。

一方面,
Python
提供了多种并发编程工具。

比如,传统的多线程,通过
threading
模块方便地创建和管理线程,可用于I/O密集型任务;

多进程,利用
multiprocessing
模块充分利用多核CPU优势,适合CPU密集型任务。

另一方面,随着异步编程的兴起。
asyncio
库也让开发者能够编写高效的异步代码,提升程序性能,尤其在处理大量并发I/O操作场景表现出色。

不过,
Python
中全局解释器锁(
GIL
)为并发编程带来了不小的挑战,目前社区正在积极探索绕过
GIL
的方法和优化策略,推动
Python
并发编程持续进步。

本篇打算一一介绍如何
Python
中使用多线程、多进程或异步的方式来编写程序。

1. 多线程

Python
中多线程的模块是
threading
,早在
Python 1.5
版本时就加入到标准库中了。

threading
一直在发展,特别是进入
Python3.x
之后,


Python3.3~Python3.13
,几乎每次
Python
的升级都伴随着
threading
的变化。

所以,使用时务必根据自己
Python
版本来正确使用
threading
的接口。

1.1. 使用场景和局限

Python
的多线程广泛用于
I/O 密集型
的任务场景中,如网络请求、文件读写等,让程序在等待
I/O
操作时切换执行其他线程,从而提升整体效率。

随着应用场景拓展,多线程局限性也逐渐凸显。

最主要的是
全局解释器锁

GIL
),这是
Python
解释器的一个特性,同一时刻只有一个线程能执行
Python
字节码。

这导致在
CPU
密集型任务中,多线程无法充分利用多核
CPU
优势,性能提升不明显甚至可能降低。

不过,尽管存在局限,多线程在
Python
生态中仍有重要地位。

开发者不断探索优化方法,如使用
threading
结合
multiprocessing
等其他并发模块,扬长避短。同时,新的
Python
版本也在尝试改进
GIL
机制,为多线程发展提供更多可能 。

1.2. 使用方式

在实际开发中,使用多线程主要有3种方式:

第一种方式是直接使用
threading.Thread
类创建线程,

这是最基本的方式,直接实例化threading.Thread类并传入目标函数及参数。

import threading


def worker():
    print('线程正在执行')


# 创建线程
t = threading.Thread(target=worker)
# 启动线程
t.start()
# 等待线程执行完毕
t.join()

第二种方式通过继承
threading.Thread
类创建线程类,并重写
run
方法来定义线程执行的任务。

import threading


class MyThread(threading.Thread):
    def run(self):
        print(f'{self.name} 线程正在执行')


# 创建线程实例
my_thread = MyThread()
# 启动线程
my_thread.start()
# 等待线程执行完毕
my_thread.join()

最后一种方式是使用
threading.ThreadPool
实现线程池,在
Python 3
中,建议使用
concurrent.futures
模块中的
ThreadPoolExecutor
来实现线程池功能。

threading.ThreadPool
已经标记过时,不建议在新的项目中再使用。

线程池的好处是可以管理一组线程,重用线程资源,减少线程创建和销毁的开销。

import concurrent.futures


def task(num):
    print(f"执行任务 {num}")
    return num * 2


# 创建线程池,最大线程数为3
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
    # 提交任务
    future_to_num = {executor.submit(task, num): num for num in range(5)}
    for future in concurrent.futures.as_completed(future_to_num):
        num = future_to_num[future]
        try:
            result = future.result()
        except Exception as e:
            print(f"任务 {num} 执行失败: {e}")
        else:
            print(f"任务 {num} 结果: {result}")

执行结果:

$  python.exe .\thread.py
执行任务 0
执行任务 1
执行任务 2
执行任务 3
任务 1 结果: 2
执行任务 4
任务 2 结果: 4
任务 0 结果: 0
任务 3 结果: 6
任务 4 结果: 8

2. 多进程

多线程模块
multiprocessing

Python 2.6
版本引入,随后在
Python 3.x
中持续发展。

在发展过程中,
multiprocessing
不断完善。它提供了简洁且强大的接口,让开发者能轻松创建和管理多个进程,充分利用多核
CPU
的优势,大幅提升
CPU
密集型任务的处理效率。

它支持多种进程间通信方式,如队列、管道等,方便进程间的数据共享与同步。

2.1. 使用场景和局限

multiprocessing
适用于
CPU
密集型计算,如科学计算、数据分析、图像处理等需要大量计算资源的任务。

当有多个独立任务需要同时执行时,也可以使用
multiprocessing
,例如批量文件处理、任务队列处理等。可以为每个任务分配一个进程,提高任务执行效率。

此外,在一些服务器应用中,也可以使用多进程让主进程处理请求的同时,其他进程负责后台任务,如数据缓存更新、日志记录等,从而避免阻塞主线程,提升应用的响应速度 。

不过,
multiprocessing
也存在一些局限性。

由于每个进程都有独立的内存空间,进程间数据共享和通信相对复杂,需要额外的机制和同步操作,可能带来性能损耗。

并且,创建和销毁进程的开销较大,频繁地创建和销毁进程会影响程序的整体性能。

此外,它的使用场景相对受限,不适用于简单的并发任务,相比多线程,在 I/O 密集型任务中优势不明显,因为多线程在 I/O 等待时能切换执行其他任务,多进程则会耗费更多资源。

2.2. 使用方式

这里也介绍使用
multiprocessing
的3种常用的方式:

第一种是直接使用
Process
类,通过实例化
multiprocessing.Process
类并传入目标函数及参数来创建进程。

import multiprocessing


def worker():
    print('进程正在执行')


if __name__ == '__main__':
    # 创建进程
    p = multiprocessing.Process(target=worker)
    # 启动进程
    p.start()
    # 等待进程执行完毕
    p.join()

第二种方式是通过继承
multiprocessing.Process
类,并重写
run
方法来定义进程执行的任务。

import multiprocessing


class MyProcess(multiprocessing.Process):
    def run(self):
        print(f'{self.name} 进程正在执行')


if __name__ == '__main__':
    # 创建进程实例
    my_process = MyProcess()
    # 启动进程
    my_process.start()
    # 等待进程执行完毕
    my_process.join()

最后一种方式是通过
multiprocessing.Pool
类创建一个进程池,自动分配任务给进程,提高资源利用率。

import multiprocessing


def task(num):
    return num * 2


if __name__ == '__main__':
    # 创建进程池,最大进程数为3
    with multiprocessing.Pool(processes=3) as pool:
        # 使用map方法并行执行任务
        results = pool.map(task, range(5))
        print(results)

这三种使用方式看起来和上一节中的
threading
都差不多,不过,它们底层的处理是完全不一样的,

multiprocessing
会为每个任务单独创建一个进程去执行;而
threading
中的所有任务都是在同一个进程中执行的。

3. 异步

异步模块
asyncio
的历史比上面的两个模块要迟很多,它在
Python 3.4
版本中被首次引入。


Python 3.5
时, 引入了
async

await
关键字,让异步代码的编写更加简洁、易读,大大提升了异步编程的体验,推动了asyncio的广泛应用。

3.1. 使用场景和局限

asyncio
适用于下面几种对并发处理要求高的场景:

  • 网络爬虫
    :在爬取多个网页时,
    asyncio
    能在等待响应的同时,继续发送其他请求,大大提高爬取效率,缩短获取大量数据的时间。

  • 网络服务端开发
    :处理高并发的客户端连接,如构建聊天服务器、实时数据推送服务等。它能异步处理每个客户端请求,避免阻塞,确保服务器高效运行。

  • I/O密集型任务
    :如文件读写、数据库操作等。
    asyncio
    可在等待
    I/O
    操作完成时执行其他任务,减少整体等待时间,提升程序性能。

当然,
asyncio
的优势明显,但也存在一些局限性。

一方面,由于它基于单线程,在处理
CPU
密集型任务时性能欠佳,无法充分利用多核
CPU
的优势。

另一方面,异步编程模型相对复杂,代码调试和维护难度较高,需要开发者对异步概念有深入理解,否则容易出现逻辑错误。

此外,
asyncio
与一些传统的同步库可能存在兼容性问题,在集成现有代码时可能会遇到困难。

3.2. 使用方式

asyncio
是比较新的模块,它的使用方式主要有:

  1. 定义一个协程函数,使用
    async def
    关键字声明,在函数内部使用
    await
    关键字暂停协程执行,等待其他异步操作完成。
import asyncio


async def coroutine():
    print('开始执行协程函数')
    await asyncio.sleep(1)
    print('协程函数执行结束')


if __name__ == '__main__':
    asyncio.run(coroutine())

asyncio.run()
用于运行最高层级的协程。

  1. 使用
    asyncio.gather()
    函数可以同时运行多个协程。
import asyncio


async def coroutine1():
    await asyncio.sleep(1)
    print('协程1执行完毕')


async def coroutine2():
    await asyncio.sleep(2)
    print('协程2执行完毕')


if __name__ == "__main__":
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
    try:
        loop.run_until_complete(asyncio.gather(coroutine1(), coroutine2()))
    finally:
        loop.close()
  1. 使用
    async for
    对异步可迭代对象进行迭代。
import asyncio


async def async_generator():
    for i in range(3):
        await asyncio.sleep(1)
        yield i


async def main():
    async for num in async_generator():
        print(num)


if __name__ == "__main__":
    asyncio.run(main())

这种方式适用于处理异步产生的数据序列。

4. 总结

总的来看,

多线程
是在一个进程里创建多个线程,共享资源,线程切换开销小,适合
I/O
密集型任务,像网络请求、文件读写。

它编程简单,能提高程序响应性,但因全局解释器锁,在
CPU
密集型任务中无法发挥多核优势,还存在线程安全问题。

多进程
中每个进程有独立内存和资源,适合
CPU
密集型任务,能充分利用多核
CPU
,稳定性高。

不过,进程创建和销毁开销大,进程间通信和数据共享复杂。

异步编程
基于事件循环和协程,在单线程内实现异步。

它并发性能高,代码简洁,适合大量 I/O 密集型任务。但不适合 CPU 密集型任务,编程模型复杂,调试维护难。

简单来说,在开发时,I/O 密集型任务少用
多线程
,任务多用
异步
;CPU 密集型任务就选
多进程
;混合任务则按需组合。

策略模式的应用,我们以一个配置字典来说一下这个问题;首先这个字典用来管理若干个配置,每个配置项都有key和value,key是字符串,value是某种类型;我们通过一个
ConfigServiceStrategy
接口来规定配置的操作行为,通过
ConfigServiceContext
来表示一个配置上下文,通过这个对象可以写配置,读配置等;通过接口隔离原则,像上下文里传递的参数是一个抽象的接口
ConfigServiceStrategy
,而具体的实现就是配置持久化的方式,如内存hash表,redis的hash存储等。

配置文件的策略接口

/**
 * 配置服务策略.
 *
 * @author lind
 * @date 2024/12/23 22:00
 * @since 1.0.0
 */
public interface ConfigServiceStrategy {

	/**
	 * 存储配置.
	 */
	<T> void put(Class<T> type, String key, T value);

	/**
	 * 获取配置.
	 * @param type
	 * @param key
	 * @return
	 * @param <T>
	 */
	<T> T get(Class<T> type, String key);

}

内存hash表实现策略

/**
 * 基于类型和key的字典存储.
 *
 * @author lind
 * @date 2024/12/23 14:22
 * @since 1.0.0
 */
public class DictionaryConfigService implements ConfigServiceStrategy {

	private Map<ConfigKey<?>, Object> configKeyObjectMap = new HashMap<>();

	@Override
	public <T> void put(Class<T> type, String key, T value) {
		configKeyObjectMap.put(ConfigKey.of(type, key), value);
	}

	@Override
	public <T> T get(Class<T> type, String key) {
		ConfigKey configKey = ConfigKey.of(type, key);

		return type.cast(configKeyObjectMap.get(configKey));
	}

}

配置文件的上下文

/**
 * 配置服务上下文
 *
 * @author lind
 * @date 2024/12/23 22:57
 * @since 1.0.0
 */
public class ConfigServiceContext implements ConfigServiceStrategy {

	private ConfigServiceStrategy configServiceStrategy;

	public ConfigServiceContext(ConfigServiceStrategy configServiceStrategy) {
		this.configServiceStrategy = configServiceStrategy;
	}

	/**
	 * 存储配置.
	 * @param type
	 * @param key
	 * @param value
	 */
	@Override
	public <T> void put(Class<T> type, String key, T value) {
		if (this.configServiceStrategy == null) {
			throw new IllegalStateException("未设置配置服务");
		}
		this.configServiceStrategy.put(type, key, value);
	}

	/**
	 * 获取配置.
	 * @param type
	 * @param key
	 * @return
	 */
	@Override
	public <T> T get(Class<T> type, String key) {
		if (this.configServiceStrategy == null) {
			throw new IllegalStateException("未设置配置服务");
		}
		return this.configServiceStrategy.get(type, key);
	}

}

测试用例

可以通过bean的方式进行注入,这里只是测试

public static void main(String[] args) {
    ConfigServiceContext configServiceContext = new ConfigServiceContext(new DictionaryConfigService());
    configServiceContext.put(String.class, "test", "test");
    System.out.println(configServiceContext.get(String.class, "test"));
}