科普文:软件架构数据库系列之【MySQL三高架构设计:高并发、高性能、高可用】
MySQL数据库三高高并发:同时处理的事务数高高性能:事务/SQL的执行速度高高可用:系统可用的时间高MySQL数据库如何实现三高高并发:通过复制和扩展,将数据分散至多个节点高性能:复制提升速度,扩展提升容量高可用:节点间身份切换保证随时可用要实现数据的三高,其实是和整个软件架构的三高设计是密不可分的。这里只是拆出来单独梳理一下。MySQL数据库实现三高的手段
概叙
高可用:数据库可用性强,具有高可替代性,故障发生后,系统能马上恢复。
高性能:数据库性能强,系统运算能力强,响应速度快。
高并发:数据库并发能力强,具有同时处理多种事务的能力。
备注:在这里我们主要从数据库角度去阐明三高。服务器、存储、网络、多中心暂不作说明。
MySQL数据库三高
高并发:同时处理的事务数高
高性能:事务/SQL的执行速度高
高可用:系统可用的时间高
MySQL数据库如何实现三高
高并发:通过复制和扩展,将数据分散至多个节点
高性能:复制提升速度,扩展提升容量
高可用:节点间身份切换保证随时可用
要实现数据的三高,其实是和整个软件架构的三高设计是密不可分的。这里只是拆出来单独梳理一下。
MySQL数据库实现三高的手段
- 复制
- 目的:数据冗余
- 手段:binlog传送
- 收货:并发量提升、可用性提高
- 问题:占用更多硬件资源
- 扩展
- 目的:扩展数据库容量
- 手段:数据分片分库、分表
- 收货:性能、并发量提升
- 问题:可能降低可用性
- 切换
- 目的:提高可用性
- 手段:主从身份切换
- 收货:并发量提升
- 问题:丢失切换时
MySQL数据库高可用HA(High Availability)
高可用HA(High Availability)是为了解决单点故障。
在现代企业中,数据库作为核心基础设施,其可用性、并发处理能力和性能显得尤为重要。MySQL作为一种流行的关系型数据库管理系统,具有较好的性能和可扩展性。高可用性指的是系统在发生故障时依然能保持服务,MySQL的高可用性主要通过主从复制、负载均衡和故障转移实现。
世界上没有任何服务或者说机器是绝对安全且可靠的,但是我们可以通过一定的方案来实现系统的高可用,从一定程度上降低单一系统宕机对整体系统带来毁灭的这种无法接受的风险。这便是高可用所要解决的痛点,分布式系统也是为了解决这个问题。
当然更高级别的高可用应该是需要考虑到并发访问,所以也应该是需要做到负载均衡的。
数据库也不例外,当然需要做到高可用。
我们假设系统一直能够提供服务,那么我们就说系统的可用性是100%。如果系统每运行100个时间单位,会有1个时间单位无法提供服务,我们说系统的可用性是99%。很多公司的高可用目标是4个9,也就是99.99%,这就意味着,系统的年停机时间为8.76个小时。
MySQL可用性和可靠性的选择
MySQL数据库的高可用的架构时,主要考虑如下几方面:
- 如果数据库发生了宕机或者意外中断等故障,能尽快恢复数据库的可用性,尽可能的减少停机时间,保证业务不会因为数据库的故障而中断。
- 用作备份、只读副本等功能的非主节点的数据应该和主节点的数据实时或者最终保持一致。
- 当业务发生数据库切换时,切换前后的数据库内容应当一致,不会因为数据缺失或者数据不一致而影响业务。
数据库高可用和主从身份切换的关键:保业务(可用性优先),还是保数据(可靠性优先)。
具体需要结合业务做出选择,一般我们都是保数据(可靠性优先),损失一定的可用性,确保业务数据无误,毕竟如果业务数据都错了,可用性就更没有保障(提供可用的服务,但是不保证业务数据可靠性,对用户来说也是不负责的表现)。
1.如何进行身份切换
- 停止备库同步
- 配置主库复制从库
2.可靠性优先策略
- 检查B库的seconds_behind_master,不能过大
- A库只读readonly = true
- 检查B库的seconds_behind_master = 0
- B库关只读,readonly = false
- B库停止复制A库,A库开始复制B库
3.可靠性优先策略
- 数据无丢失
- 有几秒的时间两个数据库均不可写
- 若一开始未检查seconds_behind_master, 不可用时间无法控制
4.可用性优先策略
- 取消等待数据一致的过程
- A库只读,B库关只读
- B库停止复制A库,A库开始复制B库
5.可用性优先策略
- 系统没有不可写的时间
- 若切换时还有未重放的relay log
- 可能造成数据不一致错误
6.总结:
- 普通业务执行时,尽量用可靠性优先策略
- 日志、流水等不太需要数据可靠性的,用可用性优先策略
MySQL高可用的实现
MySQL的高可用性主要通过主从复制、负载均衡和故障转移实现。
主从复制
MySQL高可用性架构主要是为了提高数据库的可用性,通过不同的技术和方法来保障数据库服务的连续性。
以下是一些常见的MySQL高可用性架构以及它们的优缺点:
1.主从复制(Replication)
-
优点:简单易用,可实现数据的热备份;
-
缺点:主服务器宕机时服务中断,且数据备份只能进行读操作。
2.半同步复制(Semisynchronous Replication)
-
优点:比标准的异步复制提供了更好的数据一致性;
-
缺点:在主服务器和备服务器之间的网络延迟情况下,可能会导致性能下降。
3.并行复制(Parallel Replication)
-
优点:通过并行复制提高复制效率;
-
缺点:需要额外配置,且可能会引起数据冲突。
4.组复制(Group Replication)
-
优点:提供了高可用性和数据一致性的解决方案;
-
缺点:需要MySQL版本支持,配置较复杂。
5.故障转移复制(Fault Tolerance Replication)
-
优点:自动管理备服务器,提供高可用性;
-
缺点:需要第三方软件支持,如Orchestrator。
5.多主复制(Multi-Master Replication)
-
优点:每个服务器都可以有自己的数据,可以同时进行写操作;
-
缺点:数据同步复杂,可能会导致数据冲突。
6.云数据库服务(如RDS)
-
优点:提供管理控制台,自动管理数据库实例;
-
缺点:依赖于第三方服务,可能会有额外的成本和性能损耗。
负载均衡和故障转移实现
负载均衡及故障转移
利用负载均衡器能够将请求均匀分配到多台数据库服务器,以提高系统的并发处理能力。在MySQL中可以使用ProxySQL、HAProxy、DBLE(爱可生开发的基于 MySQL 的高可扩展性的分布式中间件)等工具来进行负载均衡。
Cobar:属于阿里b2b事业群,始于08年,在阿里服役多年。接管3000+mysql数据库的schema集群日志处理,在线sql请求50亿以上,由于Cobar发起人的离职,停止维护。
Mycat:开源社区在阿里Cobar基础上进行的二次开发。并且加入新功能。
oneproxy:给予mysql官方的proxy思想,性能很好但是收费的。
kingshared:基于go语言,需要继续发展,开源的。
当然除此之外,还有很多中间件,例如vitess,atlas,maxscale等等。
可以直观地看到mycat应该是目前最合适的中间件,社区活跃度高切开源。
mycat的功能包括:读写分离、负载均衡、分库分表(数据分片)、多数据源整合。
Mycat原理:Mycat的原理中最重要的一个概念就是“拦截”,它拦截了用户发过来的sql语句,首先对sql语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等等,然后将此sql发送到后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。
DBLE是基于开源项目MyCat的,其授权协议为GPL,采用java语言进行开发。DBLE是一个开源的分布式数据库系统,实现了MySQL协议的服务器,前端用户可以把它看作是一个数据库代理,用MySQL客户端工具和命令行访问,而其后端可以用MySQL原生协议与多个MySQL服务器通信。DBLE实现了MySQL从单机到集群的扩展,能够满足有更大存储需求的业务,同时又使用户业务不会受到单节点故障的影响。
故障转移
使用MySQL的自动故障转移工具,如MHA(MySQL高可用性)或Orchestrator,可以自动监控主节点的状态,并在故障发生时自动切换到从节点。
MHA
MHA(Master High Availability Manager)是一款开源的高可用管理工具,用于监控MySQL复制、主从故障切换。
项目开发中需要使用Mycat作为中间件,主库A故障后,Mycat会自动把从B提升为写库。而C、D从库,则可以通过MHA等工具,自动修改其主库为B。进而实现自动切换的目地。
MHA Manager可以单独部署在一台独立的机器上管理多个master-slave集群,也可以部署在一台slave节点上。MHA Node运行在每台MySQL服务器上,MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将最新数据的slave提升为新的master,然后将所有其他的slave重新指向新的master。整个故障转移过程对应用程序完全透明。
- MHA优点:
- 可以进行故障的自动检测和转移;
- 可扩展性较好,可以根据需要扩展MySQL的节点数量和结构;
- 相比于双节点的MySQL复制,三节点/多节点的MySQL发生不可用的概率更低
- MHA缺点:
- 至少需要三节点,相对于双节点需要更多的资源;
- 逻辑较为复杂,发生故障后排查问题,定位问题更加困难;
- 数据一致性仍然靠原生半同步复制保证,仍然存在数据不一致的风险;
- 可能因为网络分区发生脑裂现象;
MariaDB Galera Cluster
Galera是一个MySQL(也支持MariaDB,Percona)的同步多主集群软件。
从用户视角看,一组Galera集群可以看作一个具有多入口的MySQL库,用户可以同时从多个IP读写这个库。
目前Galera已经得到广泛应用,例如Openstack中,在集群规模不大的情况下,稳定性已经得到了实践考验。
优点:
-
1)真正的多主模式(True Multi-master) 意味着你可以在任意节点读写,适度的规模可以提高集群整体的性能。不会出现主从模式的故障转移(Master-Slave Failover)操作,也不需要 VIP。
-
2)同步复制(Synchronous Replication)
意味着没有 slave lag,没有节点 crash 的时候出现的丢数据现象(Hot Standby)。
并且是 Multi-threaded Slave,性能也不错。
紧耦合(Tightly Coupled)所有节点数据和状态一致。
-
3)自动节点管理(Automatic Node Provisioning) 不需要人工去备份数据库恢复到新节点。
缺点:
-
1)只支持 InnoDB
对 MyISAM,只支持 DDL 语句,就是说,创建了表可以在其他节点看到,但 插入的数据各是各的。
为什么是 InnoDB? 多主进行更新的时候,每个节点执行的是乐观策略(假定没有冲突),然后开 始更新数据,等到要 commit 的时候,再问大家是不是有冲突,这样一来, 如果有冲突,执行的语句是不是得回滚,而你知道的 MyISAM 它不支持事务, 所以需要支持事务的引擎,而当前 MySQL 就 InnoDB 支持事务。
-
2)表里面需要有 PK
如果没有 Primary Key,那不同节点 DELETE 后,可能顺序不一致,表现在 select limit 语句在不同节点可能返回不一致。
-
3)不 支 持 lock/unlock tables, lock functions (GET_LOCK(), RELEASE_LOCK()... )
当两个 transaction 从不同节点更新同一行数据的时候,只有一个 transaction 会成功,另外一个会返回 ER_LOCK_DEADLOCK。这其实也没啥大问题,因为逻辑设计上也有问题,不应该将事同一时刻的 一 个写重定向到两个节点的情况,最好的解决方法是,只有一个节点写。
当然,这是一件极掉逼格的一件事,人家的多主模式一下子被干成了单主,说出去不好听。
-
4)查询日志不能存到表里,只能存文件。log_output=FILE,这个一般也不用,特别是对我来说没啥影响。
-
5)最大transactionsize受 wsrep_max_ws_rows,wsrep_max_ws_size 两变量控制。
超出会被 reject。准确的说,不是一个缺点。
MHA+DBLE
搭建多个DBLE,每个DBLE都做相同的配置,配置它连接MYSQL A和MYSQL B,然后每个DBLE都可以独立的访问,这样其实不可以!
因为分库分表了,虚拟表和虚拟数据库的信息是存在DBLE上的,进一步说每个表按照什么列分配的,比如按时间,三年前的放在A库,三年后的放在B库,这个信息怎么分,元数据是放在DBLE上,现在DBLE一个变成多个,它们之间的元数据如何同步?
很难同步!比如业务要新建一个表,新的表的数据是存在DBLE上的,比如有什么字段,怎么分表,都是存在DBLE上,比如客户端连接的是第一个DBLE,第一个DBLE记录了创建新表,但另外两个不知道,下次别的客户端连接另外两个DBLE,另外两个DBLE都不知道有新表创建,所以说多个DBLE之间的数据是需要同步的,比如让一个DBLE当主DBLE,其中的当备DBLE,可不是不可以,但DBLE可以借助zookeeper,zookeeper是一个经典的分布式协调服务,这个服务可以保存很多数据和元数据,而且在保存数据量不大的时候可以做到高可用,而且不需要DBLE从主复制到备的问题,任何的元数据都存到zookeeper上,遇到任何元数据的问题都从zookeeper拉回来,这样就用zookeeper存储表信息、分片等信息,当客户端在其中一个DBLE上创建新表插入了新数据或者修改了表的元数据的时候,DBLE会把数据存储到zookeeper集群里,然后另外的DBLE在需要元数据的时候,从zookeeper集群获取,这样就完美解决了多个DBLE节点数据同步问题。
那为什么MYSQL 不能用zookeeper做数据同步?
因为用zookeeper做数据同步代价非常大、性能非常差,因为MYSQL数据大,几个G的。
但现在看还有一些问题,比如如何实现负载均衡,比如有多个业务应用,分摊开DBLE上,人为分散开。业务是可以分散开,但是有个问题,MHA manager怎么分散开,MHA manager需要知道DBLE的IP,给MHA一个DBLE IP可以,但MHA manager修改MYSQL的主从IP信息的时候,它调一个DBLE,DBLE会把信息存储在zookeeper,别的DBLE也会知道,但配置在MHA manager上IP 所属的DBLE挂了呢,MHA manager就找不到DBLE了,如果主备切换了的话,MHA manager就不知道告诉谁了,因为MHA manger只能配置一个DBLE IP,但是脚本里只能配一个,也可以配置多个,但是很麻烦,有点多余!
鉴于这些情况,我们国内的数据库厂商推出了自己的DRDS(Distributed Relational Database Service)分布式关系型数据库服务。
DRDS(具体产品不做推荐)
国内的DRDS(Distributed Relational Database Service)产品很多,具体产品不做推荐,这里以腾讯云的TDSL和阿里云的PolarDB 为例。
腾讯云的相关产品是TencentDB for DRDS,更多信息请查看腾讯云官方文档:https://cloud.tencent.com/document/product/631
是腾讯云提供的一种分布式关系型数据库服务,它兼容MySQL。DRDS将多个MySQL实例组织成逻辑集群,提供统一的命名空间和SQL接口,用户可以通过DRDS访问和管理这些MySQL实例。
DRDS数据库具有以下特点和优势:
- 高可靠性:DRDS使用多副本同步复制和数据分片技术,保证数据的持久性和高可靠性。
- 高可扩展性:DRDS支持动态扩容,可以根据业务需求灵活地增加或减少节点数,以满足数据规模的快速增长。
- 分布式事务支持:DRDS提供分布式事务支持,保证在分布式环境下的数据一致性。
- 读写分离和负载均衡:DRDS可以配置读写分离,将读请求分发到不同的MySQL实例,以提高系统的吞吐量和响应性能。
- 多租户隔离:DRDS可以为不同的租户提供独立的数据库实例,实现数据的隔离和安全性。
- 弹性计算和弹性存储:DRDS可以与云服务器实例和云硬盘等腾讯云服务进行无缝集成,实现弹性的计算和存储能力。
DRDS数据库适用于以下场景:
- 高并发读写的在线业务:DRDS通过数据分片和读写分离,可以处理大量的并发读写请求,满足在线业务的高并发需求。
- 数据规模快速增长的应用:DRDS支持动态扩容,可以根据数据规模的快速增长进行水平扩展,保证系统的性能和可扩展性。
- 复杂的业务场景:DRDS提供强大的分布式事务支持,可以满足复杂业务场景下的数据一致性和可靠性要求。
- 多租户应用:DRDS可以为不同的租户提供独立的数据库实例,实现数据的隔离和安全性。
云原生数据库 PolarDB 分布式版
PolarDB 分布式版 (PolarDB for Xscale,简称“PolarDB-X”) 是阿里云自主设计研发的高性能云原生分布式数据库产品,为用户提供高吞吐、大存储、低延时、易扩展和超高可用的云时代数据库服务。
MySQL数据库高并发HC(High Concurrency)
高并发处理是指系统能够同时处理大量请求,这对数据库的设计和优化提出了挑战。以下是一些常见的方法来提升MySQL的并发能力。
使用索引
一个有效的索引策略能够显著提高查询性能。例如,对于一张用户表,应该根据经常查询的字段建立索引:
连接池
使用数据库连接池技术可以避免频繁开关数据库连接,从而提升系统的并发性。可以使用一些开源的连接池,如HikariCP。
实战:搞懂数据库连接池和Druid连接池_druid数据库连接池-CSDN博客
科普文: hikari druid c3p0 dbcp等主流数据库连接池性能比较_druid和hikari 比较-CSDN博客
MySQL线程池
如何避免 在连接数暴增时,因资源竞争而导致系统吞吐下降的问题呢?
MariaDB & Percona 中给出了简洁的答案: 线程池。
线程池的原理在percona blog 中有生动的介绍,其大致可类比为早高峰期间大量汽车想通过一座大桥,如果采用one-thread-per-connection
的方式则放任汽车自由行驶,由于桥面宽度有限,最终将导致所有汽车寸步难行。线程池的解决方案是限制同时行驶的汽车数,让桥面时刻保持最大吞吐,尽快让所有汽车抵达对岸。
数据库内核月报文章 《MySQL · 最佳实践 · MySQL 多队列线程池优化》中举了一个高铁买票的例子,也很形象,由于售票员(类比为 CPU 的核数)有限,当有 1000 个用户(类比为数据库连接)都想买票时,如果采用 one-thread-per-connection
的方式,则每个人都有一个专用窗口,需要售票员跑来跑去(CPU 上下文切换,售票窗口越多,跑起来越费力)来为你服务,可以看到这是不够合理的,特别是售票员比较少而购票者很多的场景。如果采用线程池的思想,则不再是每个人都有一个专用的售票窗口(每个客户端对应一个后端线程),而是通过限定售票窗口数,让购票者排队,来减少售票员跑来跑去的成本。
回归到数据库本身,MySQL 默认的线程使用模式是会话独占模式(one-thread-per-connection
),每个会话都会创建一个独占的线程。当有大量的会话存在时,会导致大量的资源竞争,同时,大量的系统线程调度和缓存失效也会导致性能急剧下降。
线程池线程池功能旨在解决以上问题,在存在大量连接的场景下,通过线程池实现线程复用:
- 当连接多、并发低时,通过连接复用,避免创建大量空闲线程,减少系统资源开销。
- 当连接多、并发高时,通过限制同时运行的线程数,将其控制在合理的范围内,可避免线程调度工作过多和大量缓存失效,减少线程池间上下文切换和热锁争用,从而对 OLTP 场景产生积极影响。
当连接数上升时,在线程池的帮助下,将数据库整体吞吐维持在一个较高水准,如图所示。
MySQL线程池是商业版功能。 MySQL线程池是MySQL Enterprise Edition的一个核心功能,主要用于提高资源利用效率和处理高并发请求。
MySQL线程池的功能和优势主要体现在以下几个方面:
- 提高资源利用效率:通过预先创建一定数量的线程,当有请求到达时,线程池分配一个线程提供服务,请求结束后,该线程又去服务其他请求,避免了线程和内存对象的频繁创建和释放。
- 减少上下文切换和资源竞争:减少了资源的创建和释放过程,降低了上下文切换和资源的竞争,提高了服务质量和效率。
- 适用于高并发环境:在Web应用或DB服务中,高并发请求是一个常见的问题。线程池技术能够有效处理大量并发请求,提高系统的整体性能和稳定性。
应用的分片
对于大数据量的应用,可以考虑将数据进行分片,分散到多个数据库节点上。在MySQL中,分片可以通过应用层进行实现。
在数据库领域,分片(Sharding)是一种用于水平扩展数据存储能力的技术,它将数据库中的数据分散存储在多个节点或服务器上,每个节点存储数据的一个子集(分片),从而提高数据库的整体性能和容量。
应用举例:
1. 分布式应用程序的数据分片
- 电子商务平台:例如,一个电子商务网站可能会根据地理位置或产品类别对订单和商品数据进行分片。每个分片可能包含特定地区或特定类别的订单和商品信息。这样可以提高查询效率和处理吞吐量,同时保持数据的逻辑整体性。
- 社交网络平台:社交网络平台可能根据用户ID范围进行数据分片。例如,用户数据根据用户ID的哈希值或范围分布到不同的分片中,这样可以实现用户信息的快速访问和管理,同时支持大规模用户增长。
2. 大数据分析和日志处理
- 日志处理系统:大型日志处理系统可能会将日志数据根据时间戳或其他标识进行分片存储。每个分片负责存储特定时间段的日志数据,这样可以提高日志数据的检索速度和分析效率。
- 数据仓库和分析平台:在大数据分析场景中,数据仓库可能会根据业务维度(如产品、客户等)进行数据分片。每个分片可能包含特定维度的数据,使得数据分析和查询能够并行执行,提高整体的数据处理能力。
实现原理:
1. 分片策略
- 哈希分片:根据数据的特定字段(如主键或特定的哈希函数)计算出哈希值,并根据哈希值的范围将数据分配到不同的分片中。这种方法确保了数据在分布式系统中均匀分布,但可能导致某些热点数据访问问题。
- 范围分片:根据数据的范围(如日期、地理位置、产品类别等)将数据分配到不同的分片中。这种方法更适合有序数据的分布,可以根据业务需求灵活调整分片策略。
- 复合分片:结合多种分片策略,根据业务特点和数据访问模式进行数据分片。例如,先根据哈希值分片,然后在每个分片内根据范围进行进一步的数据划分。
2. 数据一致性与管理
- 分片键管理:确保分片键的唯一性和稳定性,避免因为分片键变更或重复导致数据不一致或分片重组的复杂性。
- 分片元数据管理:维护和管理分片的元数据信息,包括每个分片的位置、容量、负载情况等,确保整个分片集群的平衡和健康。
3. 查询路由与事务处理
- 查询路由:根据分片键或查询条件将查询路由到适当的分片节点,确保查询可以并行执行,并将结果合并返回给客户端。
- 分布式事务处理:在跨多个分片节点的事务操作中,确保事务的原子性、一致性、隔离性和持久性,通常需要额外的分布式事务管理机制来实现。
分片技术通过将数据分散存储到多个节点中,实现了数据库系统的水平扩展和性能增强,特别适用于需要处理大量数据和高并发访问的应用场景。
MySQL数据库高性能HP(High Performance)
高性能是指程序处理速度非常快,所占内存少,cpu占用率低。高性能的指标经常和高并发的指标紧密相关,想要提高性能,那么就要提高系统发并发能力,两者互相捆绑在一起。应用性能优化的时候,对于计算密集型和IO密集型还是有很大差别,需要分开来考虑。还有可以增加服务器的数量,内存,IO等参数提升系统的并发能力和性能,但不要浪费资源,要考虑硬件的使用率最高才能发挥到极致。
怎么样提高性能呢?
- 避免因为IO阻塞让CPU闲置,导致CPU的浪费
- 避免多线程间增加锁来保证同步,导致并行系统串行化
- 避免创建、销毁、维护太多进程、线程,导致操作系统浪费资源在调度上
要实现高性能数据库,常用的方法包括查询优化、缓存机制及数据表设计等。
查询优化
对SQL查询进行优化可以大幅度提升数据库的响应速度。使用 EXPLAIN 帮助分析查询性能。
实战:搞懂SQL执行流程、SQL执行计划解读和SQL优化_sql的执行流程-CSDN博客
科普文:解读MySQL 执行计划explain_mysql执行计划-CSDN博客
科普文:软件架构数据库系列之【MySQL执行计划Extra梳理】_mysql extra-CSDN博客
数据表设计
合理的数据表设计能够减少冗余数据和提高查询效率。例如,在设计用户表时,可以使用适当的数据类型、规范化设计等确保结构清晰。
使用缓存
对于频繁访问的数据,可以使用Redis等内存数据库进行缓存,提高数据的访问速度。
科普文:Redis系列之【Redis 实践及思考】-CSDN博客
科普文:Redis系列之【Redis 性能问题及优化方案】_redis性能吞吐量瓶颈-CSDN博客
Java web应用性能分析之【高并发之缓存-多级缓存】_java多级缓存-CSDN博客
更多推荐
所有评论(0)