redis高级篇三(分片集群)

一)进行测试Sentinel池:

集群的定义:所谓的集群,就是通过增加服务器的数量,提供相同的服务,从而让服务器达到一个稳定、高效的状态

之前的哨兵模式是存在着一些问题的,因为如果主节点挂了,那么sentinel集群会选举新的sentinel兵王来去进行故障修复操作,一旦此时受到了客户端的写的请求,就有可能会造成数据丢失的情况

​
@Controller
public class RestController {
    @RequestMapping("/Java100")
    @ResponseBody
    public String start(){
        //1.配置信息
        HashSet<String> set=new HashSet<>();
        // 连接信息 ip:port
        // set.add("127.0.0.1:27001");
        set.add("124.71.136.248:27001");
        //2.创建Sentinel连接池
        JedisSentinelPool pool = new JedisSentinelPool("mymaster", set, "12503487");
        //3.获取到Jedis客户端
        Jedis jedis=pool.getResource();
        //4.设置元素
        jedis.set("name","zhangsan");
        String value=jedis.get("name");
        return value;
    }
}
 public static void main(String[] args) {
        RedisURI redisURI = RedisURI.builder()
                .withHost("124.71.136.248")
                .withPort(6379)
                .withPassword("12503487")
                .build();
        RedisClient redisClient = RedisClient.create(redisURI);
//        // 创建 RedisClient
//        RedisClient redisClient = RedisClient.create("redis://124.71.136.248:7001");
        
        // 获取与 Redis 单节点的连接
        StatefulRedisConnection<String, String> connection = redisClient.connect();

        // 获取同步命令
        RedisCommands<String, String> syncCommands = connection.sync();
       
        // 执行 Redis 命令
        syncCommands.set("key", "value");
        String value = syncCommands.get("key");
        System.out.println("Value: " + value);

        // 关闭连接
        connection.close();
        redisClient.shutdown();
    }

二)Redis分片集群

主从集群可以应对高并发读的问题,我们的单节点的内存设置不要太高,如果内存占用的过高,那么在做RDB的持久化或者全量同步的时候就会导致大量的IO,性能下降

1)如果写的并发也很多怎么办呢?

2)如果需要进行存储的数据仍然是很多怎么办呢?

主从模式和哨兵模式可以解决高并发读,高可用的问题,但是仍然存在着海量数据存储的问题和高并发写的问题

3)分片集群的特征:

3.1)集群中有多个master,每一个master保存着不同的数据,这样进行存储的数据总量取决于master结点的数量;

3.2)每一个master本身都有多个slave节点

3.3)master相互之间可以做ping命令来进行检测检测彼此的健康状态

1)Redis集群支持多个Master,每一个Master又可以挂载个Slave,这样可以实现读写分离

支持数据的高可用,支持海量数据的读写存储操作;

2)由于Cluster自带的Sentinel的故障转移机制,内置了高可用的支持,这是无需再次使用哨兵模式;

3)客户端和Redis的节点相连,不再需要连接集群中的所有节点,只需要进行任意连接集群中的一个可用节点即可

4)槽位slot负责分配到各个物理服务节点,有对应的集群来负责维护节点,插槽和数据之间的关系

Redis会把每一个master节点映射到0-16383一共是16384个插槽上面,查看集群信息的时候就可以看到:

Redis中的key不是和master节点进行绑定的,而是和插槽进行绑定的,redis会根据key的有效部分来进行计算插槽的值,主要是分成两种情况:

1)key中包含{},况且大括号中至少包含一个字符,那么大括号中的部分就是有效部分

2)key中不包含大括号,整个key都是有效部分

假设如果key是num,那么key的有效部分就是num,但是假设key是{kkk}num,那么有效部分就是kkk,计算方法就是利用CRC16算法得到一个哈希值,针对这个16384取余,最后得到的结果一定是在0-16373之间,然后得到一个solt值;

3)因为redis的主节点是有可能出现宕机的情况的,或者是集群扩容增加了节点,或者是集群伸缩,删除了节点,如果一个节点宕机了,那么这个节点上面的数据也就丢失了,而数据如果是和插槽绑定的,那么当节点宕机的时候,我们可以把这个节点对应的插槽转移到正常的节点,集群扩容的时候,也可以将插槽进行转移,这样数据跟着插槽走,就永远可以找到数据的位置;

 一)Redis是如何进行判断Key在哪一个实例上面?

1)将16384个插槽分配到不同的实例节点上面

2)当存储一个key或者取出一个key的时候会,根据key的有效部分来计算哈希值,对16384进行取余操作;

3)将余数作为插槽,寻找到插槽所在的实例节点即可

二)如何将同一类数据固定的保存到同一个Redis实例中呢

假设我现在有不同的商品,空调洗衣机手机,各种类型不同的产品,就是将相同的商品放到同一个节点上面,因为将来用户搜索手机的时候,我就可以去同一个节点查询,避免出现请求重定向,因为重定向还要重新建立连接,性能上会有一定的损耗,

所以说这一类数据使用相同的有效部分,例如说key都是以{typeID}为前缀

三)redis集群的分片

3.1)分片的定义:在使用redis集群的时候,我们会将存储的数据分散到多台Redis机器上面,这就称之为是分片,简而言之集群中的每一个Redis实例都被认为是整个数据的一个分片

3.2)如何找到给定的key的分片:为了找到给定的key的分片,我们对key进行CRC16(key)算法处理并通过对总分片数量进行取模,然后使用确定性哈希函数,这就意味着给定的key将多次始终映射到一个分片,我们可以进行推断并且读到key的位置

四)slot槽位映射:

1)不一致哈希取余分区:2亿条记录就是2亿个K,V,我们单机是存储不下这些数据的,必须要使用分布式机器,假设有三台机器构成一个集群,用户每一次的读写操作都是根据公式:

hash(key)%机器数,来进行计算出哈希值,用来决定数据映射到哪一个机器上面

1.1)优点:简单粗暴,直接有效,只需要预估好数据,规划好节点,使用哈希算法让固定的一部分请求落在同一台服务器上面,这样每一台服务器来固定处理一部分请求,并且维护这些请求的信息,起到负载均衡和分而治之的作用;

1.2)缺点:原来规划好的节点,进行扩容和缩招就比较麻烦,因为映射关系要重新进行计算除非服务器永远不会变化,在服务器固定个数不变的时候没有问题,但是如果需要进行弹性扩容或者故障停机的情况下,原来的取模公式就会发生变化,Hash(Key)/3就会变成Hash(Key)/2,甚至于说如果某一台Redis直接宕机了,由于台数发生变化,就会导致哈希取余全部数据重新洗牌;

二)一致性哈希算法分区:设计目标是为了解决分布式缓存数据变动和映射问题,如果某一台机器宕机了,分母数量改变了,自然取余数就不OK了,就是为了当服务器的个数发生变动的时候,要尽量减少客户端到服务器的映射关系

2.1)哈希取余分区:一致性哈希是将整个哈希值空间组织成一个虚拟的圆环,比如说假设某一个哈希值的值空间是0-2^32-1,那么他的整个哈希环空间如下,也就是说所有的输入值都被映射到0-2^32之间,组成了一个圆环,前面介绍的算法是针对服务器的数量进行取余,而一致性哈希算法是针对2^32进行取余;

2.2)下一步将各个服务器使用Hash函数进行一个哈希, 具体可以选择服务器的ip或主机名或者其他业务属性作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置,这里假设将上文中四台服务器使用ip地址哈希后在环空间的位置如下:

2.3)将数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并且将该键值对存储到该节点上面

3)哈希槽算法

1)因为哈希槽本身就是一个数组,数组[0,2^14-1]会形成hashslot空间

2)为了解决均匀分配的问题,在数据和节点之间新加了一层,把这一层称之为是哈希槽,用于管理数据和节点之间的关系,现在就相当于是节点上存放的是槽,槽里面存放的是数据

 3)一个集群只能有16384个槽,编号为0-16343,这些槽会分配给集群中的所有主节点,分配策略是没有要求的在集群中会进行记录节点和槽的对应关系,解决了节点和槽的映射关系之后,余数是几,key就落到对应的槽里面

为什么槽位是16384个槽位? 

1)两个节点在握手成功后,两个节点之间会定期发送ping/pong消息,交换数据信息

2)交换的数据信息,由消息体和消息头组成,IP等等,端口号,发送时间

3) type表示消息类型,另外,消息头里面有个myslots的char数组,长度为16383/8,这其实是一个bitmap,每一个位代表一个槽,如果该位为1,表示这个槽是属于这个节点的

4)Redis集群并不是保证强一致性的,这意味着在特定的条件下,Redis集群可能会丢失掉一些被系统收到的一些写入请求命令,如果某一个节点挂了,此时刚好有一个key槽位被这个挂掉的主机器管控,集群会进行重新上位,把从节点升级为主节点;

五)搭建分片集群 

1)在/myredis目录下面创建9001 9002 9003 9004 9005 9006 9007目录,并分别写入redis.conf文件

2)在redis.conf文件中配置下面的信息

port 9001
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,只需要程序员指定位置即可这个文件将来由redis自己维护
cluster-config-file /myredis/9001/nodes.conf
# 节点心跳失败的超时时间,如果集群之间相互做心跳的时候超过5s没有心跳,就认为是疑似宕机了
cluster-node-timeout 5000
# 持久化文件存放目录
dir /myredis/9001
# 绑定地址,任何地址都可以访问
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 127.0.0.1
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /myredis/9001.log

3)启动各个目录下面的redis,虽然此时6个redis实例,全部启动起来了,但是它们彼此之间并没有相互联系起来,因为并没有进行指定谁和谁之间是有联系的

创建集群:

1)redis-cli --cluster create 127.0.0.1:9001 127.0.0.1:9002 127.0.0.1:9003 127.0.0.1:9004 127.0.0.1:9005 127.0.0.1:9006
2)redis-cli -p 9001 cluster nodes

1.1)这里面的redis-cli --cluster代表的是集群操作命令

1.2)create代表的是创建集群

1.3)--cluster-replicas 1,表示给每一个master节点分配一个从节点,代表指定集群中每一个master得副本是1,此时节点总数/(replicas+1)得到的就是master得数量,因此上面的节点列表的前n个就是master,其他节点的都是slave节点,被随机分配到不同的master节点

3)​​​​在我们进行连接集群中的某一个节点的时候,直接输入命令

redis -c -p 具体的端口号

4)查看某一个key属于哪一个对应的槽位值,使用命令cluster keysolt key的名称

port 9001
# 开启集群功能
cluster-enabled yes
cluster-node-timeout 5000
cluster-announce-ip 127.0.0.1
cluster-announce-port 9001
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /myredis/9001/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /myredis/9001
# 绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 127.0.0.1
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /myredis/9001.log 
~                                 

所以当你进行操作任意一个key的时候,会先进行计算插槽值,再进行判断你在哪一个节点,在完成一个请求的路由,所以你进行访问任意一个数据,都可以重定向到具体的节点;

集群伸缩

redis-cli --cluster提供了很多可以进行操作集群的命令,可以通过以下方式来进行查看:

1)如果想要进行新增节点,需要指明新的IP地址和新的端口号,还要进行指明集群中已经存在的IP和端口号(为了新增节点的时候通知每一个集群中每一个角色,从而联系上这个集群)

2)下面的--cluster-slave和--cluster--master-ip默认是没有进行添加的,如果没有添加这两个节点,那么这个节点默认就是一个主节点,如果你添加了这两个信息,那么默认就成为了指定节点的从节点了

需求:向集群中添加一个新的master节点,并向其中存储num=10

1)启动一个新的redis实例,端口号是1000

2)添加1000到之前的集群,并作为一个master节点

3)给7004节点分配插槽,使得num这个key可以存储到7004这个主节点上面

1.1)添加新节点到集群中

redis-cli --cluster add-node 127.0.0.1:1000 127.0.0.1:9001

1.2)通过命令查看集群状态

redis-cli -p 1000 cluster nodes

1.3)查看集群状态可知,1000的插槽数量是0,因此没有任何节点可以成功存储到1000这个主节点

1.4)先进行查看num的插槽值是多少,可以看到num的插槽值是2765,也就是说只要2765这个插槽能够成功分配到1000,那么1000这个主节点就可以成功的存储到num值了

1.5)先查看以下分配插槽的命令:

1.6)建立连接: redis-cli --cluster reshard 127.0.0.1:9001

1.7)当你输入命令成功之后,redis会想你进行询问你想要那一部分的插槽,我们需要转移3000个插槽就足够了

1.8)接下来redis有会向你进行询问,你需要将插槽转移到哪里去?所以我们需要直接输入插槽对应的ID即可

1.9)然后redis还是会向你进行询问,你这些插槽,从哪一个主节点开始进行拷贝,请输入主节点的ID,当然是从9001开始呀,于是输入9001对应的ID即可,然后输入done即可

 2.0)再次进行查看redis-cli -p 1000 cluster nodes节点信息

集群缩容

1)先删除主节点上面所对应的从节点:

redis-cli --cluster del-node 从机IP:从机端口 从机的ID

redis-cli --cluster del-node 127.0.0.1:9005 523b1cb07bec09c6c98a2218cbe247fa0b56c757

2)将9001对应的槽清空,将对应的槽位全部分配给1000

3)删除9001节点

故障转移:

1)先实现将节点断开连接redis -p 对应节点的端口号 shutdown

2)将这个节点重启,发现这个节点已经变成了从节点

当一个集群中有一个master宕机会发生什么呢?

1)首先是该实例会和其他实例断开连接

2)然后是疑似宕机

3)最后是确认下线,自动提升一个该节点的slave节点为新的master节点

从上面的日志信息我们可以看出,当我们把9001这个进程给干掉之后,9005就成为了主节点,如果我们再次启动9001,那么它会变成分片集群中的从节点 

手动故障转移:

1)利用cluster master命令可以让集群中的某一个master宕机,切换到cluster master命令的这个slave节点,实现无感知的数据迁移

2)具体的替换流程如下:现在我想让这个slave节点替换master节点

2.1)当我们去执行命令的这一刻,slave节点回向master节点发送一个消息,和master说我要替换你了,这个时候主节点为了避免消息的丢失,这个master节点就会拒绝客户端的一切请求,所有进来的命令都会进行阻塞;

2.2)master会返回一个offset给slave

2.3)slave也会等待当前的数据和master一致,如果不一致赶紧做主从同步

2.4)开始做故障迁移

2.5)让slave充当master,master来充当slave

2.6)这个新的master结点开始做一个广播,通知自己是新的节点

3)这种Failover支持三种不同版本的参数

3.1)不加任何参数,按照默认的流程来进行节点替换

3.2)fource,省略了对offset的一致性校验

3.3)takeover:直接执行2.6,忽略数据一致性,忽略master状态和其他master的意见

 需求:将9001这个slave节点手动进行故障转移,重新夺回master地位

1)需要使用redis-cli连接这个7002节点

2)执行cluster failover命令

StringRedisTemplate访问分片集群: 

1)引入redis的依赖

2)配置分片集群的地址

3)配置读写分离

1)不在同一个槽位下面的键值无法使用mget,mset等多键操作

2)我们可以使用{}来定义同一个组的概念,使得key内相同的内容的键值放到同一个slot槽位上面去,自然槽是一样的

3)cluster-require-full-coverage:这个信息表示是否集群完整才对外服务

4)cluster countkeysinslot+数字槽位编号,0表示该槽位没有占用,1表示该槽位已经被占用 

5)cluster keyslot 键的名称表示该键应该存在于哪一个槽位上面

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/22357.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

斯坦福、Nautilus Chain等联合主办的 Hackathon 活动,现已接受报名

由 Stanford Blockchain Accelerator、Zebec Protocol、 Nautilus Chain、Rootz Lab 共同主办的黑客松活动&#xff0c;现已接受优秀项目提交参赛申请。 在加密行业发展早期&#xff0c;密码极客们就始终在对区块链世界基础设施&#xff0c;在发展方向的无限可能性进行探索。而…

如何用Python进行屏幕录制?

文章目录 引言gpt3.5给出的代码更换截图函数——ImageGrab.grab禁用imshow解决递归现象摄像头录制代码后期需求 引言 关于屏幕录制这个功能需求&#xff0c;之前用过基于ffmpeg的Capture录屏软件&#xff0c;但是fps拉高以后会变得很卡&#xff0c;声音也同样出现卡顿。也自己…

nodej+vues汽车销售4s店服务平台商城系统购物车积分兑换7z9d2

在经济快速发展的带动下&#xff0c;汽车服务平台的发展也是越来越快速。用户对汽车服务信息的获取需求很大。在互联网飞速发展的今天&#xff0c;制作一个汽车服务平台系统是非常必要的。本系统是借鉴其他人的开发基础上&#xff0c;用MySQL数据库和nodejs定制了汽车服务平台系…

【MySQL】事务

事务是一组操作的集合,我们将一组操作视为一个整体,所以事务里面的操作的时候要么同时成功,要么同时失败,之所以会有事务也是因为我们在实际生活中会用到 最典型的例子就是转账操作:A向B进行转账,A这边扣款成功的同时B那边一定是收款成功的,如果没有事务的话就会出现A扣款成功但…

LMS,RGB,XYZ色彩空间转换

前言 首先需要指明本文中描述的R,G,B并非通常的sRGB中的三个分量R,G,B&#xff0c;而是波长分别为700nm&#xff0c;546.1nm&#xff0c;435.8nm的单色红光&#xff0c;单色绿光&#xff0c;单色蓝光。sRGB中的RGB中的红色、绿色、蓝色已经不是单色光了。虽然习惯上大家都叫RGB…

网络安全里的主要岗位有哪些?小白如何快速入门?

入门Web安全、安卓安全、二进制安全、工控安全还是智能硬件安全等等&#xff0c;每个不同的领域要掌握的技能也不同。 当然入门Web安全相对难度较低&#xff0c;也是很多人的首选。主要还是看自己的兴趣方向吧。 本文就以下几个问题来说明网络安全大致学习过程&#x1f447; 网…

SpringCloud微服务调用方式(RestTemplate)

服务调用方式 RPC和HTTP 无论是微服务还是SOA&#xff0c;都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢&#xff1f; 常见的远程调用方式有以下2种&#xff1a; RPC&#xff1a;Remote Produce Call远程过程调用&#xff0c;类似的还有 。自定义数据格式&am…

learn C++ NO.4 ——类和对象(2)

1.类的6个默认成员函数 1.1.默认成员函数的概念 在 C 中&#xff0c;如果没有显式定义类的构造函数、析构函数、拷贝构造函数和赋值运算符重载函数&#xff0c;编译器会自动生成这些函数&#xff0c;这些函数被称为默认成员函数。 class Date { };初步了解了默认成员函数&am…

STL-常用算法(二.拷贝 替换 算术 集合)

开篇先附上STL-常用算法(一)的链接 STL-常用算法&#xff08;一.遍历 查找 排序&#xff09;_小梁今天敲代码了吗的博客-CSDN博客 目录 常用拷贝和替换算法&#xff1a; copy函数示例&#xff1a;&#xff08;将v1容器中的元素复制给v2&#xff09; replace函数示例&#…

Java 9 - 18 各个版本新特性总结

【 Java 9 - 18 各个版本新特性总结&#xff0c;B站视频介绍】https://www.bilibili.com/video/BV1PT411P7Wn?vd_source5a3a58ca0e99223ffb58cddf2f3a7282 一、模块化引入 模块是 Java 9 中新增的一个组件&#xff0c;可以简单理解为是package的上级容器&#xff0c;是多个pa…

gitlab建立新分支提交,cherry-pick部分更新

gitlab介绍 GitLab是一个基于Git的在线代码托管和协作平台&#xff0c;提供源代码管理、单元测试、CI/CD构建、代码审查等功能。它是一个开放源代码的Git仓库管理系统&#xff0c;使用 Ruby on Rails 构建GitLab 不仅具有自己的 Git 仓库管理系统&#xff0c;还具有很多其他的…

网络协议与攻击模拟-11-DHCP协议原理

DHCP 协议 1、掌握 DHCP 的工作原理 2、会在 Windows server 上去部署 DHCP 服务 3、抓流量 &#xff0e;正常 收到攻击后 一、 DHCP 1、 DHCP 基本概念 dhcp &#xff08;动态主机配置协议&#xff09;&#xff1a;主要就是给客户机提供 TCP / IP 参数&#xff08; IP 地…

App外包开发上线Google Play流程

完成App开发后需要在各大应用市场上线&#xff0c;国内的应用市场比较多&#xff0c;各自的规则也不相同&#xff0c;上线审核也比较复杂&#xff1b;国外上线主要是Google Play市场&#xff0c;它更重视隐私的保护&#xff0c;必须严格按照规范来保护个人隐私&#xff0c;因此…

【C++】模板的一点简单介绍

模板 前言泛型编程函数模板概念格式函数模板的原理函数模板的实例化 类模板类模板的定义格式类模板的实例化 前言 这篇博客讲的是模板的一些基本知识&#xff0c;并没有那么深入&#xff0c;但是如果你是为了过期末考试而搜的这篇博客&#xff0c;我觉得下面讲的是够了的。 之…

阿里云、腾讯云、移动云飙“价”:智能普惠成新风向?

经过过去一年的“低迷”境况之后&#xff0c;2023年云服务商因为AI大模型的爆发&#xff0c;重新燃起了斗志。站在当下的时间节点&#xff0c;云服务商们也在重新思考如何在新形势下&#xff0c;让自己占据更大的优势&#xff0c;于是一场围绕“技术竞争与市场争夺”的新战争打…

【总结】Numpy2

Numpy 1. 数组和数的运算 array1 np.arange(1,10) array1 # array([1, 2, 3, 4, 5, 6, 7, 8, 9]) array1 10 # array([11, 12, 13, 14, 15, 16, 17, 18, 19]) array1 - 10 # array([-9, -8, -7, -6, -5, -4, -3, -2, -1]) array1 * 10 # array([10, 20, 30, 40, 50, 60, 70…

3-《安卓基础》

3-《安卓基础》 一.Android系统架构二.四大组件1. Activity1.1 生命周期1.2. Activity四种启动模式1.3.Activity任务栈的概念1.4 面试题面试题1&#xff1a;onSaveInstanceState(Bundle outState)&#xff0c;onRestoreInstanceState(Bundle savedInstanceState) 的调用时机&am…

小黑子—Java从入门到入土过程:第十一章 - 网络编程、反射及动态代理

Java零基础入门11.0 网络编程1. 初识网络编程2. 网络编程三要素3.IP三要素3.1 IPV4的细节3.1.1特殊的IP地址3.1.2 常用的CMD命令 3.2 InetAddress 的使用3.3 端口号3.4 协议3.4.1 UDP协议3.4.1 - I UDP 发送数据3.4.1 - II UDP 接收数据3.4.1 - III UDP 练习&#xff08;聊天室…

前端列表页+element-puls实现列表数据弹窗功能

效果图&#xff1a; 这是一个修改的弹窗&#xff0c;我们要实现的功能是&#xff0c;在列表&#xff0c;点击修改按钮时&#xff0c;将数据带入到弹窗里面&#xff0c;点击保存时关闭弹窗。 1&#xff0c;点击修改展开弹窗 使用 eldialog组件&#xff0c;v-model绑定的值为tru…

Godot引擎 4.0 文档 - 入门介绍 - 学习新功能

本文为Google Translate英译中结果&#xff0c;DrGraph在此基础上加了一些校正。英文原版页面&#xff1a; Learning new features — Godot Engine (stable) documentation in English 学习新功能 Godot 是一个功能丰富的游戏引擎。有很多关于它的知识。本页介绍了如何使用…
最新文章