Redian新闻
>
skywalking 沉了一年的线程池插件 bug 被我解决了

skywalking 沉了一年的线程池插件 bug 被我解决了

公众号新闻

点击上方“芋道源码”,选择“设为星标

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 
来源:捉虫大师


修复了Skywalking某些场景下线程池插件增强失败 的问题,心急的人可以直接看问题及代码。

以下是我提交的 issue 和 PR(已经被 merged):

  • issue: https://github.com/apache/skywalking/issues/10925
  • pull request: https://github.com/apache/skywalking-java/pull/556

一、背景

最近公司在做全链路追踪,在 agent 这块使用 skywalking 作为核心实现。同步链路 trace 的覆盖基本上都有 plugin 实现了,对于异步链路 trace 这块开源社区也已经有了线程池的增强 plugin(bootstrap-plugins\apm-jdk-threadpool-plugin.jar).

首先, 需要把bootstrap-plugins\apm-jdk-threadpool-plugin.jar移动到plugins目录下.

但是在应用启动之后的使用过程中,发现线程池增强失败了,表现出来的是在线程池中提交的任务打印日志的traceId是空的.

主要环境说明:

  1. JDK 1.8
  2. skywalking agent 8.14.0
  3. spring boot 2.2.6.RELEASE

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

二、排查过程

2.1 关于 AgentClassLoader 的一个发现

这个发现纯粹是一个巧合, 我发现在控制台和 skywalking-api.log 中都有同样的一份扫描 plugins 目录中 jar 包的相关日志

我觉得扫描 plugins 目录中 jar 包这个事情应该只做一次就行了, 基于这个日志去搜索了一下源码, 发现这个动作是在 skywalking 的自定义类加载器AgentClassLoader中的findClass方法中触发的, 但是奇怪的是我看到扫描 jar 包这个事情是有缓存的, 理论上只会做一次才对, 为什么触发两次?

在扫描 jar 包的入口代码处打了断点调试, 发现确实进来了两次, 但是从 debug 的视图看到, 两次进来的AgentClassLoader是 2 个不同的实例。

AgentClassLoader被实例化了 2 次?!

因为本身类的字节码增强就是在类加载的阶段去做的, 而增强字节码主要是靠 plugins 中的 Interceptor, 然后 plugins 中的 class 都是由AgentClassLoader进行加载的, 所以我这里本能的怀疑跟AgentClassLoader被实例化了 2 次有关系.

带着这样的怀疑, 我排查了AgentClassLoader被实例化的地方. 在AgentClassLoader的构造器中打了断点, 发现第一次是在 premain 方法 agent 刚启动的时候去实例化了, 第二次是 premain 方法已经执行完毕, 在 main 方法启动时第一次加载 plugins 中的 Interceptor 类时去实例化了.

第二次实例化的代码在InterceptorInstanceLoader类中的load方法

public class InterceptorInstanceLoader {

    public static <T> load(String className,
        ClassLoader targetClassLoader)
 throws IllegalAccessException, InstantiationException, ClassNotFoundException, AgentPackageNotFoundException 
{
        if (targetClassLoader == null) {
            targetClassLoader = InterceptorInstanceLoader.class.getClassLoader();
        }
        String instanceKey = className + "_OF_" + targetClassLoader.getClass()
                                                                   .getName() + "@" + Integer.toHexString(targetClassLoader
            .hashCode());
        Object inst = INSTANCE_CACHE.get(instanceKey);
        if (inst == null) {
            INSTANCE_LOAD_LOCK.lock();
            ClassLoader pluginLoader;
            try {
                pluginLoader = EXTEND_PLUGIN_CLASSLOADERS.get(targetClassLoader);
                // ------------------- 关键代码在这里 -------------------
                if (pluginLoader == null) {
                    pluginLoader = new AgentClassLoader(targetClassLoader);
                    EXTEND_PLUGIN_CLASSLOADERS.put(targetClassLoader, pluginLoader);
                }
            } finally {
                INSTANCE_LOAD_LOCK.unlock();
            }
            inst = Class.forName(className, true, pluginLoader).newInstance();
            if (inst != null) {
                INSTANCE_CACHE.put(instanceKey, inst);
            }
        }

        return (T) inst;
    }
}

因为已经认定了AgentClassLoader就应该是单例的, 这里是重复实例化了, 所以我把这里的源码改成了复用第一次实例化的AgentClassLoader对象.

重新打包 agent -> 启动应用 -> 观察

首先, 扫描 jar 包的日志只剩一份了(在控制台), 接下来测试线程池的调用, 发现这个时候线程池的增强起作用了, 表现为日志中能看到 traceId 的值, 且和主线程中日志的 traceId 一致.

测试代码:

    @GetMapping(value = "testHello")
    public String testHello(@RequestParam("msg") String msg){
        log.info("testHello:{}, traceId:{}", msg, org.apache.skywalking.apm.toolkit.trace.TraceContext.traceId());
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10101000, TimeUnit.HOURS, new ArrayBlockingQueue<>(10));
        executor.submit(() -> {
            log.info("executor.execute:{}, traceId:{}", msg, org.apache.skywalking.apm.toolkit.trace.TraceContext.traceId());
        });
       return "ok";
    }

日志输出:

2023-06-21 20:38:55.359  INFO 20808 - [nio-8081-exec-1] c.ScController   : testHello:111, traceId:753241bce942437a9cfd0daea7e21578.65.16873511352010001
2023-06-21 20:38:55.368  INFO 20808 - [pool-2-thread-2] c.ScController   : executor.execute:111, traceId:753241bce942437a9cfd0daea7e21578.65.16873511352010001

在对AgentClassLoader使用单例后问题解决.

但是问题就此结束了吗? 看了下 git commit 历史记录, AgentClassLoader的实例化代码一直都没有变过, 如果是因为AgentClassLoader的重复实例化问题导致的, 那么有 2 个问题:

  1. 线程池插件在 merge 到主干时一定是经过测试, 且有测试用例提交的, 为什么官方的测试用例可以通过?
  2. 如果是因为AgentClassLoader的重复实例化问题导致的, 那么理论上其他类的增强也会出问题, 但是其他类的增强为什么没问题呢?

基于上面两个问题, 我觉得"因为AgentClassLoader的重复实例化"这个原因似乎并不是根因。

也就是说线程池插件一定能生效, but how or when?

2.2 ThreadPoolExecutor 的类加载时机

尝试用最简 plugins 配置(因为官方的 test case 都是单个插件单独测试的, 怀疑是不是插件之间的有一些冲突或者不兼容的问题), 只保留了 tomcat-7.x-8.x-plugin-8.14.0.jarapm-jdk-threadpool-plugin-8.14.0.jar

果然,启动应用之后再调用线程池的方法,发现 traceId 就正常生效了,也就是插件增强成功。

我想到的是, 字节码增强是在类加载的时候触发了 transform 操作(注: 类加载完之后也能 retransform, 但是这个不在本文的讨论范围内), 会不会是在 skywalking 注册 transformer 之前ThreadPoolExecutor已经被加载到 JVM 中了呢?

带着这个疑问, 我在应用启动时增加了-XX:+TraceClassLoading参数, 打印了类加载的具体情况, 如下:

[Opened C:\Users\xiaqi\.jdks\corretto-1.8.0_372\jre\lib\rt.jar]
[Loaded java.lang.Object from C:\Users\xiaqi\.jdks\corretto-1.8.0_372\jre\lib\rt.jar]
...
[Loaded java.lang.Runnable from C:\Users\xiaqi\.jdks\corretto-1.8.0_372\jre\lib\rt.jar]
[Loaded java.lang.Thread from C:\Users\xiaqi\.jdks\corretto-1.8.0_372\jre\lib\rt.jar]
...
---------------------------------------------------agent jar包加载---------------------------------------------------
[Loaded org.apache.skywalking.apm.agent.SkyWalkingAgent from file:/D:/IdeaProjects/skywalking-agent/skywalking-agent/skywalking-agent.jar]
...
---------------------------------------------------准备注册transformer---------------------------------------------------------
[Loaded org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder.AgentBuilder$Transformer from file:/C:/Users/xiaqi/IdeaProjects/skywalking-agent/skywalking-agent/skywalking-agent.jar]
[Loaded org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder.AgentBuilder$RedefinitionStrategy$Listener from file:/C:/Users/xiaqi/IdeaProjects/skywalking-agent/skywalking-agent/skywalking-agent.jar]
[Loaded org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder.AgentBuilder$Listener from file:/C:/Users/xiaqi/IdeaProjects/skywalking-agent/skywalking-agent/skywalking-agent.jar]
...
[Loaded org.apache.skywalking.apm.dependencies.net.bytebuddy.implementation.bind.MethodDelegationBinder$1 from file:/D:/IdeaProjects/skywalking-agent/skywalking-agent/skywalking-agent.jar]
[Loaded org.apache.skywalking.apm.dependencies.net.bytebuddy.implementation.bind.MethodDelegationBinder$AmbiguityResolver$Resolution from file:/D:/IdeaProjects/skywalking-agent/skywalking-agent/skywalking-agent.jar]
---------------------------------------------------注册transformer结束---------------------------------------------------------
...
[Loaded java.util.concurrent.ThreadPoolExecutor from C:\Users\xiaqi\.jdks\corretto-1.8.0_372\jre\lib\rt.jar]
...

从上面的日志中可以看出来, ThreadPoolExecutor是在注册了 transformer 之后, 因此不符合我上面的猜测. 这里顺便说一下, 从上面的日志中看得出来, RunnableThread这 2 个类都是在 JVM 刚启动时且 agent 尚未加载时就已经被加载到 JVM 中了, 因此这 2 个类想要做字节码增强是非常困难的.

既然从类加载的顺序上看不出来问题, 我决定看一下第一次使用ThreadPoolExecutor是在具体干什么.

于是我在ThreadPoolExecutor的构造器上打了断点, 正常启动应用后进入了断点, 调用栈如下:

<init>:1310, ThreadPoolExecutor (java.util.concurrent)
<init>:1237, ThreadPoolExecutor (java.util.concurrent)
<init>:447, ScheduledThreadPoolExecutor (java.util.concurrent)
newSingleThreadScheduledExecutor:272, Executors (java.util.concurrent)
<init>:66, FileWriter (org.apache.skywalking.apm.agent.core.logging.core)
get:56, FileWriter (org.apache.skywalking.apm.agent.core.logging.core)
getLogWriter:49, WriterFactory (org.apache.skywalking.apm.agent.core.logging.core)
logger:207, AbstractLogger (org.apache.skywalking.apm.agent.core.logging.core)
info:72, AbstractLogger (org.apache.skywalking.apm.agent.core.logging.core)
doGetJars:197, AgentClassLoader (org.apache.skywalking.apm.agent.core.plugin.loader)
getAllJars:177, AgentClassLoader (org.apache.skywalking.apm.agent.core.plugin.loader)
findClass:94, AgentClassLoader (org.apache.skywalking.apm.agent.core.plugin.loader)
loadClass:418, ClassLoader (java.lang)
loadClass:351, ClassLoader (java.lang)
forName0:-1, Class (java.lang)
forName:348, Class (java.lang)
load:71, InterceptorInstanceLoader (org.apache.skywalking.apm.agent.core.plugin.loader)
<init>:48, ConstructorInter (org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance)
enhanceInstance:124, ClassEnhancePluginDefine (org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance)
enhance:116, AbstractClassEnhancePluginDefine (org.apache.skywalking.apm.agent.core.plugin)
define:96, AbstractClassEnhancePluginDefine (org.apache.skywalking.apm.agent.core.plugin)
transform:167, SkyWalkingAgent$Transformer (org.apache.skywalking.apm.agent)
doTransform:12104, AgentBuilder$Default$ExecutingTransformer (org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder)
transform:12041, AgentBuilder$Default$ExecutingTransformer (org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder)
access$1800:11758, AgentBuilder$Default$ExecutingTransformer (org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder)
run:12441, AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher (org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder)
run:12381, AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher (org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder)
doPrivileged:-1, AccessController (java.security)
doPrivileged:11936, AgentBuilder$Default$ExecutingTransformer (org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder)
transform:11950, AgentBuilder$Default$ExecutingTransformer (org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder)
transform:188, TransformerManager (sun.instrument)
transform:428, InstrumentationImpl (sun.instrument)
defineClass1:-1, ClassLoader (java.lang)
defineClass:756, ClassLoader (java.lang)
defineClass:142, SecureClassLoader (java.security)
defineClass:473, URLClassLoader (java.net)
access$100:74, URLClassLoader (java.net)
run:369, URLClassLoader$1 (java.net)
run:363, URLClassLoader$1 (java.net)
doPrivileged:-1, AccessController (java.security)
findClass:362, URLClassLoader (java.net)
loadClass:418, ClassLoader (java.lang)
loadClass:352, Launcher$AppClassLoader (sun.misc)
loadClass:351, ClassLoader (java.lang)
registerAnnotationConfigProcessors:170, AnnotationConfigUtils (org.springframework.context.annotation)
registerAnnotationConfigProcessors:137, AnnotationConfigUtils (org.springframework.context.annotation)
<init>:88, AnnotatedBeanDefinitionReader (org.springframework.context.annotation)
<init>:71, AnnotatedBeanDefinitionReader (org.springframework.context.annotation)
<init>:73, AnnotationConfigServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
instantiateClass:204, BeanUtils (org.springframework.beans)
instantiateClass:139, BeanUtils (org.springframework.beans)
createApplicationContext:588, SpringApplication (org.springframework.boot)
run:311, SpringApplication (org.springframework.boot)
run:1226, SpringApplication (org.springframework.boot)
run:1215, SpringApplication (org.springframework.boot)
main:18, DemoClientApplication (com.demo.client)

通过调用栈, 我发现ThreadPoolExecutor的第一次实例化是在 skywalking 的日志组件FileWriter的实例化中触发的.

但是这个调用栈比较诡异的点就在于触发这个的上层调用是因为引入了 spring 的 plugin 而导致加载 spring 中的AutowiredAnnotationBeanPostProcessor类的时候触发了 transform(字节码增强), 并且在 transform 阶段依赖了ThreadPoolExecutor, 而在对AutowiredAnnotationBeanPostProcessor类的 transform 还没有结束时, 似乎在这个时候被依赖的ThreadPoolExecutor类并不会再次触发 transform(即字节码增强).

调用序列图:

虽然ThreadPoolExecutor类在这里没有触发 transform, 但是类已经确确实实被加载到 JVM 了, 而类只会被加载一次, 也就是说后面不会再进行ThreadPoolExecutor的类加载, 也就不会进行字节码增强了.

2.3 关于 JVM 对于 transform 逻辑的猜想

JVM 对于已经在类 A 的 transform 阶段依赖的类 B 的类加载行为不会再触发类 B 的 transform.

猜测这个逻辑是 JVM 为了防止循环调用 transform 而导致栈溢出的问题.

假如类 B 也会触发 transform, 那么可能会出问题的循环逻辑如下:

类 A 加载 -> 类 A transform

-> transformer 中的类 B 加载 -> 类 B transform(这次类 B 还未完成类加载过程)

-> transformer 中的类 B 加载 -> 类 B transform(这次类 B 还未完成类加载过程)

...

为了进一步验证这个猜想, 我把 skywalking 中的FileWriter中创建线程池的代码注释掉了, 然后再次启动应用, 发现线程池插件增强成功, 这个结果和我的猜想是相符的.

但是这里又有一个疑问:

如果是因为 transform 的逻辑问题, 那么只要存在需要被增强的类, 则一定会有这个问题, 但是为什么上面我验证了只有 tomcat 插件和线程池插件的时候, 线程池插件是可以正常生效的呢?

后来, 我发现对于 JDK 中的类的增强和三方类库中类的增强的逻辑是不一样的. JDK 中的类的增强在 transform 阶段是不会去调用AgentClassLoader去加载 Interceptor 类, 而是在刚开始的时候就用模板动态定义好了 Interceptor 类, 并由AppClassLoader完成了加载. 而AppClassLoader是不依赖ThreadPoolExecutor的.

也就是说, 在ThreadPoolExecutor增强之前如果有三方类库的类先被增强了, 最后ThreadPoolExecutor就会增强失败, 否则就不会有问题了.(划重点)

关于 JDK 中的类的增强的逻辑可以具体看BootstrapInstrumentBoost这个类的实现逻辑.

注: 由于没有去看 JVM 的源码, 这个地方的猜想是不严谨的. 后面有时间我会再去研究一下 JVM 在这块的源码. 如果有经验的同学也可以在后面留言给我说明实际源码的逻辑是怎么样的, 不胜感激.

2.4 问题总结

至此, 所有的疑问都解开了.

上面在 3.1 章节的最后提出的 2 个问题, 就可以回答了.

Q: 线程池插件在 merge 到主干时一定是经过测试, 且有测试用例提交的, 为什么官方的测试用例可以通过?

A: 官方的测试用例是单个 plugin 去测试的, 此时只有ThreadPoolExecutor被增强, 不存在三方类库增强的问题, 因此可以正常生效.

Q: 如果是因为AgentClassLoader的重复实例化问题导致的, 那么理论上其他类的增强也会出问题, 但是其他类的增强为什么没问题呢?

A: ThreadPoolExecutor增强失败的问题主要是因为对三方类库增强的 transform 阶段依赖了ThreadPoolExecutor, 而其他类的增强没有这样的情况. 至于把AgentClassLoader改成单例之后为什么就没问题了, 是因为这样就不会重复扫描 plugins 目录中 jar 包, 也就不会打日志, 也就是 transform 阶段就不依赖ThreadPoolExecutor了, 因此不会有问题.

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

三、解决方案

排查原因很曲折, 解决方案很简单, 我能想到的有 2 种.

  1. AgentClassLoader变成单例, 避免重复扫描 jar 包导致的日志输出, 从而避免日志组件对ThreadPoolExecutor的依赖.
  2. 去除日志组件FileWriter对于ThreadPoolExecutor的依赖, 改为其他的实现.

第一种方案, 在和 @吴晟 大佬讨论之后被否决了, 原因是大佬说AgentClassLoader的多例是一种简单的代理模式, 在还没有深入理解这里的设计思想的前提下, 还是选择了尊重权威.

最后用了第二种方案作为实现思路. FileWriterThreadPoolExecutor是为了定时把日志刷到文件中. 首先, 我想到的是用TimerTimerTask来做替代方案, 但是实际上谁也不知道后面会不会有增强TimerTimerTask的插件出现, 因此最后采用了简单的 Thread+循环的方式做了替代方案. 具体的实现代码可以看我最开始贴的 PR 链接.

四、相关的 issues 和 discussions

从源码提交记录来看, 线程池的 plugin 是从 2022 年 4 月第一次提交的, 这一年我也看到有人陆续反馈了线程池增强失败的问题, 但是一直没有得到解决。现在已经被我解决啦,在未来的 skywalking agent 8.17.0 版本就能用上了。

以下是相关的 issues 和 discussions:

  • https://github.com/apache/skywalking/issues/9425
  • https://github.com/apache/skywalking/issues/9850
  • https://github.com/apache/skywalking/issues/10374
  • https://github.com/apache/skywalking/issues/10685
  • https://github.com/apache/skywalking/discussions/10207
  • https://github.com/apache/skywalking/discussions/9888


欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
纽约要沉了?曼哈顿将被洪水淹,地质局发布警告纽约要沉了?华人区将被洪水淹! 地质局发布警告Kmart推出的$5新品数千人点赞!开车族狂喜:解决了大bug花费上百元,顶着大太阳逛马路?飞盘之后,年轻人扎堆Citywalk!组织者:这不是遛弯儿8 个线程池最佳实践和坑!使用不当直接生产事故!!老版孙猴子扮相的一个小 Bug扬州,最适合citywalk的城市Java远程热部署插件 - 吊打市面插件小说 电话的故事完了!无证客泛滥 亚当斯:“我解决不了 纽约市要被毁掉了。”特种兵旅行后,年轻人爱上Citywalk8 大分类、80+ ChatGPT 插件一网打尽,最全 ChatGPT 插件清单来了突发,偷渡英国小船沉了,多人死亡!英国经济超预期,英镑稳住上涨势头...“爱国侨领”被判无期徒刑天,刚才在自己身上发现了 bed bug不擅长发疯的中国人,都在偷偷citywalk人类进步,不过是靠解决了一个小问题认识bug,理解bug,爱上debug!大二科研小白,发论文啦!【报名】海狸学院2024荷华写作营(IYWS)辅导项目!(附IYWS往届学生本科录取榜单)酒店Bug价:凯悦难得出Bug,10欧元入住全新收购的酒店品牌【𝐂𝐚𝐥𝐧𝐢𝐊𝐞𝐚𝐧双皮奶无痕内裤】49元三条!巨巨巨好穿 !!简直就是辣妹顶配,食品级冰箱收纳盒【一日团】大帝(1)——成吉斯汗: Genghis Khan矛盾玩完中产最爱的“Citywalk”,我只想骂WTF细数线程池的10个坑,面试线程不怕不怕啦纽约要沉了?曼哈顿华人区将被洪水淹! 地质局发布警告美国纽约要沉了?华人区将被洪水淹! 地质局发布警告查理·芒格35年股东会语录:把问题彻底想明白,问题就解决了一半赴了一趟家宴,我解锁了高品质生活的密码万豪Bug级别住二送一大礼包的免房券已入账,但是没想到又出了新的Bugbug怎样算修完?浅谈团队分布式bug管理 —— git-poison简单易用的bug管理工具【第3讲】Problem: 我解决的是「真」问题吗?𝐂𝐚𝐥𝐧𝐢𝐊𝐞𝐚𝐧双皮奶内衣裤,软弹有度,上身0束缚~Citywalk第一城!烟火气爆表,好吃到离谱,哇嘎里贡,这可能是中国最神仙的城市!8大分类、80+ChatGPT插件一网打尽,最全ChatGPT插件清单来了40张图,带你搞懂SkyWalking
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。