在.NET项目中利用PostSharp,使用CacheManager实现多种缓存框架的处理澳门美高梅手机网站

1、kafka有什么?

  • producer 消息的生成者,即发表音讯

  • consumer 音讯的消费者,即订阅音信

  • broker Kafka以集群的不二法门运行,可以由一个或五个服务组合,服务即broker

  • zookeeper 协调转发

1、CacheManager缓存框架的追思

有关这些缓存框架,我在小说《.NET缓存框架CacheManager在混合式开发框架中的应用(1)-CacheManager的介绍和利用》中举行了介绍,读者可以从中领会一下CacheManager缓存框架究竟是一个咋样的事物。

CacheManager是一个以C#语言开发的开源.Net缓存框架抽象层。它不是现实性的缓存实现,但它补助多种缓存提供者(如Redis、Memcached等)并提供成千上万高级特性。
CacheManager
重要的目标使开发者更易于处理各个繁复的缓存场景,使用CacheManager可以实现多层的缓存,让过程内缓存在分布式缓存从前,且仅需几行代码来处理。
CacheManager
不仅仅是一个接口去联合不同缓存提供者的编程模型,它使我们在一个系列里面改变缓存策略变得非常容易,同时也提供更多的性状:如缓存同步、并发更新、系列号、事件处理、性能总计等等,开发人士能够在需要的时候采用这个特点。

CacheManager缓存框架协理Winform和Web等使用开发,以及匡助多种风行的缓存实现,如MemoryCache、Redis、Memcached、Couchbase、System.Web.Caching等。

综观整个缓存框架,它的一定很显著,在扶助多种缓存实现外,本身重假使以内存缓存(进程内)为主,其他分布式缓存为辅的多层缓存架构情势,以达到飞速命中和处理的机制,它们中间有相关的音讯处理,使得即使是分布式缓存,也可以立时落实产出同步的缓存处理。

CacheManager缓存框架在布置方面,协助代码形式的配置、XML配置,以及JSON格式的配备处理,极度便利。

CacheManager缓存框架默认对缓存数据的系列化是拔取二进制形式,同时也帮忙多种自定义体系化的法门,如基于JOSN.NET的JSON序列化或者自定义体系化形式。

CacheManager缓存框架可以对缓存记录的加码、删除、更新等息息相关事件展开记录。

CacheManager缓存框架的缓存数据是强类型的,可以支撑各个正规项目标处理,如Int、String、List类型等各个基础项目,以及可序列号的各类对象及列表对象。

CacheManager缓存框架协助多层的缓存实现,内部可以的建制得以连忙、及时的同步好各层缓存的数目。

CacheManager缓存框架帮助对各样操作的日志记录。

CacheManager缓存框架在分布式缓存实现中补助对改进的锁定和事务处理,让缓存保持更好的协同处理,内部机制落实版本争论处理。

CacheManager缓存框架辅助两种缓存过期的拍卖,如相对时间的逾期处理,以及定位时段的晚点处理,是大家处理缓存过期更加有益。

….

过多风味基本上覆盖了缓存的正常化特性,而且提供的接口基本上也是我们所平常用的Add、Put、Update、Remove等接口,使用起来也相当有利于。

CacheManager的GitHub源码地址为:https://github.com/MichaCo/CacheManager,如若需要实际的Demo及表达,可以访问其官网:http://cachemanager.net/

 

相似的话,对于单机版本的应用场景,基本上是无需引入这种缓存框架的,因为客户端的并发量很少,而且数量请求也是只身可数的,性能方便不会有其他问题。

假定对于分布式的利用系统,如本人在广大小说中牵线到自家的《混合式开发框架》、《Web开发框架》,由于数量请求是并发量随着用户增长而提升的,特别对于有些互联网的行使类别,极端气象下某个时刻点一下也许就会达到了全部应用出现的峰值。那么那种分布式的系列架构,引入数据缓存来降低IO的并发数,把耗时伏乞改换为内存的神速请求,可以极大程度的狂跌系统宕机的风险。

我们以基于常规的Web API层来构建利用框架为例,整个数据缓存层,应该是在Web
API层之下、业务实现层以上的一个层,如下所示。

澳门美高梅手机网站 1

 

1、下载Kafka并解压

  • 下载:

curl -L -O http://mirrors.cnnic.cn/apache/kafka/0.9.0.0/kafka_2.10-0.9.0.0.tgz 
  • 解压:

tar zxvf kafka_2.10-0.9.0.0.tgz 

2、整合Post夏普(Sharp)和CacheManager实现多种缓存框架的拍卖

由于MemoryCache是在单个机器上进展缓存的处理,而且不可以进展连串号,电脑宕机后就会全部撇下缓存内容,由于那一个毛病,我们对《在.NET项目中行使Post夏普(Sharp),使用MemoryCache实现缓存的拍卖》基础上拓展更加的调动,整合CacheManager举行使,从而得以应用缓存弹性化处理以及可体系号的性状。

咱们在常规意况下,依旧需要选取Redis这一个强大的分布式缓存的,关于Redis的安装和选取,请参考我的随笔《基于C#的MongoDB数据库开发应用(4)–Redis的装置及采用》。

咱俩首先定义一个CacheAttribute的Aspect类,用来对缓存的断面处理。

    /// <summary>
    /// 方法实现缓存的标识
    /// </summary>
    [Serializable]
    public class CacheAttribute : MethodInterceptionAspect
    {
        /// <summary>
        /// 缓存的失效时间设置,默认采用30分钟
        /// </summary>
        public int ExpirationPeriod = 30;

        /// <summary>
        /// PostSharp的调用处理,实现数据的缓存处理
        /// </summary>
        public override void OnInvoke(MethodInterceptionArgs args)
        {
            //默认30分钟失效,如果设置过期时间,那么采用设置值
            TimeSpan timeSpan = new TimeSpan(0, 0, ExpirationPeriod, 0);

            var cache = MethodResultCache.GetCache(args.Method, timeSpan);
            var arguments = args.Arguments.ToList();//args.Arguments.Union(new[] {WindowsIdentity.GetCurrent().Name}).ToList();
            var result = cache.GetCachedResult(arguments);
            if (result != null)
            {
                args.ReturnValue = result;
                return;
            }
            else
            {
                base.OnInvoke(args);
                //调用后更新缓存
                cache.CacheCallResult(args.ReturnValue, arguments);
            }
        }
    }

下一场就是更进一步处理完善类
MethodResultCache来对缓存数据举办拍卖了。该类负责协会一个CacheManager管理类来对缓存举行处理,如下代码所示。

澳门美高梅手机网站 2

初阶化缓存管理器的代码如下所示,这里运用了MemoryCache作为急忙的内存缓存(主缓存),以及Redis作为连串化存储的缓存容器(从缓存),它们有内在机制举办共同处理。

        /// <summary>
        /// 初始化缓存管理器
        /// </summary>
        private void InitCacheManager()
        {
            _cache = CacheFactory.Build("getStartedCache", settings =>
            {
                settings
                .WithSystemRuntimeCacheHandle("handleName")
                .And
                .WithRedisConfiguration("redis", config =>
                {
                    config.WithAllowAdmin()
                        .WithDatabase(0)
                        .WithEndpoint("localhost", 6379);
                })
                .WithMaxRetries(100)
                .WithRetryTimeout(50)
                .WithRedisBackplane("redis")
                .WithRedisCacheHandle("redis", true)
                ;
            });
        }

对缓存结果举办拍卖的函数如下所示。

        /// <summary>
        /// 缓存结果内容
        /// </summary>
        /// <param name="result">待加入缓存的结果</param>
        /// <param name="arguments">方法的参数集合</param>
        public void CacheCallResult(object result, IEnumerable<object> arguments)
        {
            var key = GetCacheKey(arguments);
            _cache.Remove(key);

            var item = new CacheItem<object>(key, result, ExpirationMode.Sliding, _expirationPeriod);
            _cache.Add(item);
        }

率先就是收获情势参数的键,然后移除对应的缓存,插足新的缓存,并设定缓存的失灵时间段即可。

清空缓存的时候,直接调用管理类的Clear方法即可达成目的。

        /// <summary>
        /// 清空方法的缓存
        /// </summary>
        public void ClearCachedResults()
        {
            _cache.Clear();
        } 

那样,我们处理好后,在一个作业调用类里面举行设置缓存标志即可,如下代码所示。

        /// <summary>
        /// 获取用户全部简单对象信息,并放到缓存里面
        /// </summary>
        /// <returns></returns>
        [Cache(ExpirationPeriod = 1)]
        public static List<SimpleUserInfo> GetSimpleUsers(int userid)
        {
            Thread.Sleep(500);
            //return CallerFactory<IUserService>.Instance.GetSimpleUsers(); 

            //模拟从数据库获取数据
            List<SimpleUserInfo> list = new List<SimpleUserInfo>();
            for (int i = 0; i < 10; i++)
            {
                var info = new SimpleUserInfo();
                info.ID = i;
                info.Name = string.Concat("Name:", i);
                info.FullName = string.Concat("姓名:", i);
                list.Add(info);
            }
            return list;
        }

为了测试缓存的拍卖,以及对Redis的支撑情形,我编写了一个概括的案例,效率如下所示。

澳门美高梅手机网站 3

测试代码如下所示。

        //测试缓存
        private void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine(" 测试缓存: ");

            //测试反复调用获取数值的耗时
            DateTime start = DateTime.Now;
            var list = CacheService.GetSimpleUsers(1);
            int end = (int)DateTime.Now.Subtract(start).TotalMilliseconds;

            Console.WriteLine(" first: " + end);
            Console.WriteLine(" List: " + list.Count);

            //Second test
            //检查不同的方法参数,对缓存值的影响
            start = DateTime.Now;
            list = CacheService.GetSimpleUsers(2);
            end = (int)DateTime.Now.Subtract(start).TotalMilliseconds;
            Console.WriteLine(" Second: " + end);
            Console.WriteLine(" List2: " + list.Count);
        }

        //更新缓存
        private void button2_Click(object sender, EventArgs e)
        {
            Console.WriteLine(" 更新缓存: ");

            //首先获取对应键的缓存值
            //然后对缓存进行修改
            //最后重新加入缓存
            var key = "CacheManagerAndPostSharp.CacheService.GetSimpleUsers";
            var item = MethodResultCache.GetCache(key);
            var argument = new List<object>(){1};
            var result = item.GetCachedResult(argument);
            Console.WriteLine("OldResult:" + result.ToJson());

            List<SimpleUserInfo> newList = result as List<SimpleUserInfo>;
            if(newList != null)
            {
                newList.Add(new SimpleUserInfo() { ID = new Random().Next(), Name = RandomChinese.GetRandomChars(2) });
            }
            item.CacheCallResult(newList, argument);
        }

        //清空缓存
        private void button3_Click(object sender, EventArgs e)
        {
            Console.WriteLine(" 清空缓存: ");

            //首先获取对应键的缓存值
            var key = "CacheManagerAndPostSharp.CacheService.GetSimpleUsers";
            var item = MethodResultCache.GetCache(key);
            var argument = new List<object>(){1};

            //然后清空方法的所有缓存
            item.ClearCachedResults();

            //最后重新检验缓存值为空
            var result = item.GetCachedResult(argument);
            Console.WriteLine("Result:" + result !=null ? result.ToJson() : "null");
        }

测试运行结果如下所示。

 测试缓存: 
 first: 870
 List: 10
 Second: 502
 List2: 10

 更新缓存: 
OldResult:[
{"ID":0,"HandNo":null,"Name":"Name:0","Password":null,"FullName":"姓名:0","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":1,"HandNo":null,"Name":"Name:1","Password":null,"FullName":"姓名:1","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":2,"HandNo":null,"Name":"Name:2","Password":null,"FullName":"姓名:2","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":3,"HandNo":null,"Name":"Name:3","Password":null,"FullName":"姓名:3","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":4,"HandNo":null,"Name":"Name:4","Password":null,"FullName":"姓名:4","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":5,"HandNo":null,"Name":"Name:5","Password":null,"FullName":"姓名:5","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":6,"HandNo":null,"Name":"Name:6","Password":null,"FullName":"姓名:6","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":7,"HandNo":null,"Name":"Name:7","Password":null,"FullName":"姓名:7","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":8,"HandNo":null,"Name":"Name:8","Password":null,"FullName":"姓名:8","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":9,"HandNo":null,"Name":"Name:9","Password":null,"FullName":"姓名:9","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null}]

 测试缓存: 
 first: 0
 List: 11
 Second: 0
 List2: 10

 清空缓存: 
null

再者咱们看来在Redis里面,有连锁的笔录如下所示。

澳门美高梅手机网站 4

整合Post夏普和CacheManager,使得大家在拔取缓存方面更兼具弹性化,可以依据事态通过配备实现采取不同的缓存处理,可是在代码中动用缓存就是只需要注脚一下即可,十分方便简单了。

 

(5) kill some broker

kill broker(id=0)

率先,我们依据后面的配备,得到broker(id=0)应该在9092监听,这样就能确定它的PID了。

broker0没kill从前topic在kafka cluster中的情状

bin/kafka-topics.sh --describe --zookeeper localhost:2181
Topic:test  PartitionCount:1    ReplicationFactor:1 Configs:
    Topic: test Partition: 0    Leader: 0   Replicas: 0 Isr: 0
Topic:topic_1   PartitionCount:1    ReplicationFactor:3 Configs:
    Topic: topic_1  Partition: 0    Leader: 2   Replicas: 2,1,0 Isr: 2,1,0
Topic:topic_2   PartitionCount:1    ReplicationFactor:3 Configs:
    Topic: topic_2  Partition: 0    Leader: 1   Replicas: 1,2,0 Isr: 1,2,0
Topic:topic_3   PartitionCount:1    ReplicationFactor:3 Configs:
    Topic: topic_3  Partition: 0    Leader: 2   Replicas: 0,2,1 Isr: 2,1,0

kill之后,再观察,做下比较。很扎眼,首要变化在于Isr,未来再分析

bin/kafka-topics.sh --describe --zookeeper localhost:2181
Topic:test  PartitionCount:1    ReplicationFactor:1 Configs:
    Topic: test Partition: 0    Leader: -1  Replicas: 0 Isr: 
Topic:topic_1   PartitionCount:1    ReplicationFactor:3 Configs:

    Topic: topic_1  Partition: 0    Leader: 2   Replicas: 2,1,0 Isr: 2,1
Topic:topic_2   PartitionCount:1    ReplicationFactor:3 Configs:
    Topic: topic_2  Partition: 0    Leader: 1   Replicas: 1,2,0 Isr: 1,2
Topic:topic_3   PartitionCount:1    ReplicationFactor:3 Configs:
    Topic: topic_3  Partition: 0    Leader: 2   Replicas: 0,2,1 Isr: 2,1

测试下,发送消息,接受音信,是否接受影响。

  • 发送音讯

bin/kafka-console-producer.sh --topic topic_1 --broker-list 192.168.1.181:9092,192.168.1.181:9093,192.168.1.181:9094
  • 收取音讯

bin/kafka-console-consumer.sh --topic topic_1 --zookeeper 192.168.1.181:2181 --from-beginning

看得出,kafka的分布式机制,容错能力或者挺好的~

回来顶部

在前头几篇小说中,介绍了Post夏普的施用,以及构成MemoryCache,《在.NET项目中动用PostSharp,实现AOP面向切面编程处理》、《在.NET项目中使用Post夏普(Sharp),使用MemoryCache实现缓存的处理》参数了对Post夏普(Sharp)的接纳,并介绍了MemoryCache的缓存使用,不过缓存框架的社会风气中间,有很多早熟的缓存框架,如MemoryCache、Redis、Memcached、Couchbase、System.Web.Caching等,这时候我们只要有一个大内总管或者一个吸星大法的成绩,把它们融合起来,那么就着实是这一个周详的一件工作,这多少个就是大家CacheManager缓存框架了,这样的八面玲珑缓存框架并整合了Post夏普(Sharp)横切面对常规代码的简化效用,简直就是好鞍配好马、宝剑赠英雄,整合起来处理缓存真的是如虎添翼。

2、kafka的工作图

澳门美高梅手机网站 5

producers通过网络将音讯发送到Kafka集群,集群向顾客提供新闻

kafka对信息进行汇总,即topic,也就是说producer发布topic,consumer订阅topic

3、配置

  • 配置zookeeper

请参考zookeeper

  • 进去kafka安装工程根目录编撰config/server.properties

kafka最为重大六个布局依次为:broker.id、log.dir、zookeeper.connect,kafka
server端config/server.properties参数表明和释疑如下:

server.properties配置属性表明

5、单机连通性测试

启航2个XSHELL客户端,一个用来生产者发送信息,一个用以消费者接受音信。

  • 运行producer,随机敲入多少个字符,相当于把这些敲入的字符音讯发送给队列。

bin/kafka-console-producer.sh --broker-list 192.168.153.118:9092 --topic test

证实:早版本的Kafka,–broker-list 192.168.1.181:9092需改为–zookeeper
192.168.1.181:2181

  • 运行consumer,可以看出刚才发送的消息列表。

bin/kafka-console-consumer.sh --zookeeper 192.168.153.118:2181 --topic test --from-beginning  
  • 注意:

producer,指定的Socket(192.168.1.181+9092),表明劳动者的信息要发往kafka,也即是broker

consumer,
指定的Socket(192.168.1.181+2181),表明消费者的消息来自zookeeper(协调转发)

地方的只是一个单个的broker,下边我们来尝试一个多broker的集群。

2、Kafka目录介绍

  • /bin 操作kafka的可举办脚本,还蕴含windows下脚本

  • /config 配置文件所在目录

  • /libs 依赖库目录

  • /logs
    日志数据目录,目录kafka把server端日志分为5系列型,分为:server,request,state,log-cleaner,controller

6、搭建一个多少个broker的伪集群

刚刚只是启动了单个broker,现在开行有3个broker组成的集群,这么些broker节点也都是在本机上。

(1)为每一个broker提供配置文件

咱俩先看看config/server0.properties配置新闻:

澳门美高梅手机网站 6

broker.id=0
listeners=PLAINTEXT://:9092
port=9092
host.name=192.168.1.181
num.network.threads=4
num.io.threads=8
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
log.dirs=/tmp/kafka-logs
num.partitions=5
num.recovery.threads.per.data.dir=1
log.retention.hours=168
log.segment.bytes=1073741824
log.retention.check.interval.ms=300000
log.cleaner.enable=false
zookeeper.connect=192.168.1.181:2181
zookeeper.connection.timeout.ms=6000
queued.max.requests =500
log.cleanup.policy = delete

澳门美高梅手机网站 7

  • 说明:

broker.id为集群中绝无仅有的标号一个节点,因为在同一个机器上,所以必须指定不同的端口和日志文件,制止数据被掩盖。

在上头单个broker的试验中,为什么kafka的端口为9092,这里可以看得很通晓。

kafka cluster怎么同zookeeper交互的,配置音信中也有体现。

这就是说下边,我们仿照下面的布置文件,提供2个broker的配置文件:

  • server1.properties:

按 Ctrl+C 复制代码

按 Ctrl+C 复制代码

  • server2.properties:

澳门美高梅手机网站 8

broker.id=2
listeners=PLAINTEXT://:9094
port=9094
host.name=192.168.1.181
num.network.threads=4
num.io.threads=8
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
log.dirs=/tmp/kafka-logs2
num.partitions=5
num.recovery.threads.per.data.dir=1
log.retention.hours=168
log.segment.bytes=1073741824
log.retention.check.interval.ms=300000
log.cleaner.enable=false
zookeeper.connect=192.168.1.181:2181
zookeeper.connection.timeout.ms=6000
queued.max.requests =500
log.cleanup.policy = delete

澳门美高梅手机网站 9

(2)启动所有的broker

一声令下如下:

bin/kafka-server-start.sh config/server0.properties &   #启动broker0
bin/kafka-server-start.sh config/server1.properties & #启动broker1
bin/kafka-server-start.sh config/server2.properties & #启动broker2

查看2181、9092、9093、9094端口

netstat -tunlp|egrep "(2181|9092|9093|9094)"
tcp        0      0 :::9093                     :::*                        LISTEN      29725/java          
tcp        0      0 :::2181                     :::*                        LISTEN      19787/java          
tcp        0      0 :::9094                     :::*                        LISTEN      29800/java          
tcp        0      0 :::9092                     :::*                        LISTEN      29572/java  

一个zookeeper在2181端口上监听,3个kafka
cluster(broker)分别在端口9092,9093,9094监听。

参考资料

apache
kafka技术分享系列(目录索引)

Kafka深度分析,众人举荐,精粹好文!

(4)模拟客户端发送,接受音讯
  • 出殡音讯

bin/kafka-console-producer.sh --topic topic_1 --broker-list 192.168.1.181:9092,192.168.1.181:9093,192.168.1.181:9094
  • 收取消息

bin/kafka-console-consumer.sh --topic topic_1 --zookeeper 192.168.1.181:2181 --from-beginning

亟待小心,此时producer将topic揭橥到了3个broker中,现在就有点分布式的概念了。

二、操作过程

读书目录

一、环境布置

  • 操作系统:Cent OS 7

  • Kafka版本:0.9.0.0

  • Kafka官网下载:请点击

  • JDK版本:1.7.0_51

  • SSH Secure Shell版本:XShell 5

归来顶部

4、启动Kafka

  • 启动

进入kafka目录,敲入命令 bin/kafka-server-start.sh config/server.properties &
  • 检测2181与9092端口

netstat -tunlp|egrep "(2181|9092)"
tcp        0      0 :::2181                     :::*                        LISTEN      19787/java          
tcp        0      0 :::9092                     :::*                        LISTEN      28094/java 

说明:

Kafka的历程ID为28094,占用端口为9092

QuorumPeerMain为对应的zookeeper实例,进程ID为19787,在2181端口监听

(3)创建topic
bin/kafka-topics.sh --create --topic topic_1 --partitions 1 --replication-factor 3  \--zookeeper localhost:2181
bin/kafka-topics.sh --create --topic topic_2 --partitions 1 --replication-factor 3  \--zookeeper localhost:2181
bin/kafka-topics.sh --create --topic topic_3 --partitions 1 --replication-factor 3  \--zookeeper localhost:2181

翻看topic创立情形:

bin/kafka-topics.sh --list --zookeeper localhost:2181
test
topic_1
topic_2
topic_3
[root@atman081 kafka_2.10-0.9.0.0]# bin/kafka-topics.sh --describe --zookeeper localhost:2181
Topic:test  PartitionCount:1    ReplicationFactor:1 Configs:
    Topic: test Partition: 0    Leader: 0   Replicas: 0 Isr: 0
Topic:topic_1   PartitionCount:1    ReplicationFactor:3 Configs:
    Topic: topic_1  Partition: 0    Leader: 2   Replicas: 2,1,0 Isr: 2,1,0
Topic:topic_2   PartitionCount:1    ReplicationFactor:3 Configs:
    Topic: topic_2  Partition: 0    Leader: 1   Replicas: 1,2,0 Isr: 1,2,0
Topic:topic_3   PartitionCount:1    ReplicationFactor:3 Configs:
    Topic: topic_3  Partition: 0    Leader: 0   Replicas: 0,2,1 Isr: 0,2,1

地方的有点东西,也许还不太明了,暂放,继续试验。需要专注的是topic_1的Leader=2

Kafka介绍

澳门美高梅手机网站, 安装及布局

回去顶部

发表评论

电子邮件地址不会被公开。 必填项已用*标注