盘它 超实用的 Java Web知识总结汇总
Redis持久化机制
资料持久化方式Redis支援两种资料持久化方式:RDB方式和AOF方式。前者会根据配置的规则定时将内存中的资料持久化到硬盘上,后者则是在每次执行写命令之后将命令记录下来。两种持久化方式可以单独使用,但是通常会将两者结合使用。
RDB方式
RDB方式的持久化是通过快照的方式完成的。当符合某种规则时,会将内存中的资料全量生成一份副本储存到硬盘上,这个过程称作”快照”,Redis会在以下几种情况下对资料进行快照:
根据配置规则进行自动快照;
使用者执行SAVE, BGSAVE命令;执行FLUSHALL命令;执行复制(replication)时。快照生成原理:
快照执行的过程如下:
Redis使用fork函式复制一份当前程序(父程序)的副本(子程序);
父程序继续处理来自客户端的请求,子程序开始将内存中的资料写入硬盘中的临时档案;
当子程序写完所有的资料后,用该临时档案替换旧的RDB档案,至此,一次快照操作完成。
需要注意的是:
在执行fork的时候操作系统(类Unix操作系统)会使用写时复制(copy-on-write)策略,即fork函式发生的一刻,父程序和子程序共享同一块内存资料,当父程序需要修改其中的某片资料(如执行写命令)时,操作系统会将该片资料复制一份以保证子程序不受影响,所以RDB档案储存的是执行fork操作那一刻的内存资料。所以RDB方式理论上是会存在丢资料的情况的(fork之后修改的的那些没有写进RDB档案)。
AOF方式
在使用Redis储存非临时资料时,一般都需要开启AOF持久化来降低程序终止导致的资料丢失,AOF可以将Redis执行的每一条写命令追加到硬盘档案中,这一过程显然会降低Redis的效能,但是大部分情况下这个影响是可以接受的,另外,使用较快的硬盘能提高AOF的效能。
开启AOF
预设情况下,Redis没有开启AOF(append only file)持久化功能,可以通过在配置档案中作如下配置启用:
appendonly yes
开启之后,Redis每执行一条写命令就会将该命令写入硬盘中的AOF档案。AOF档案储存路径和RDB档案路径是一致的,都是通过dir引数配置,预设档名是:appendonly.aof,可以通过配置appendonlyfilename引数修改,例如:
appendfilename “appendonly.aof”
AOF持久化的实现
AOF以纯文字的形式记录了Redis执行的写命令。
AOF档案重写(删除AOF中无用的命令)
AOF档案是可识别的纯文字,它的内容就是一个个的Redis标准命令,
AOF日志也不是完全按客户端的请求来生成日志的,比如命令 INCRBYFLOAT 在记AOF日志时就被记成一条SET记录,因为浮点数操作可能在不同的系统上会不同,所以为了避免同一份日志在不同的系统上生成不同的资料集,所以这里只将操作后的结果通过SET来记录。
每一条写命令都生成一条日志,AOF档案会很大。
AOF重写是重新生成一份AOF档案,新的AOF档案中一条记录的操作只会有一次,而不像一份老档案那样,可能记录了对同一个值的多次操作。其生成过程和RDB类似,也是fork一个程序,直接遍历资料,写入新的AOF临时档案。在写入新档案的过程中,所有的写操作日志还是会写到原来老的AOF档案中,同时还会记录在内存缓冲区中。当重完操作完成后,会将所有缓冲区中的日志一次性写入到临时档案中。然后呼叫原子性的rename命令用新的 AOF档案取代老的AOF档案。
命令:BGREWRITEAOF
同步硬盘资料
虽然每次执行更改数据库的内容时,AOF都会记录执行的命令,但是由于操作系统本身的硬盘快取的缘故,AOF档案的内容并没有真正地写入硬盘,在预设情况下,操作系统会每隔30s将硬盘快取中的资料同步到硬盘,但是为了防止系统异常退出而导致丢资料的情况发生,我们还可以在Redis的配置档案中配置这个同步的频率:
# appendfsync always
appendfsync everysec
# appendfsync no
appendfsync always 表示每次AOF写入一个命令都会执行同步操作,这是最安全也是最慢的方式;
appendfsync everysec 表示每秒钟进行一次同步操作,一般来说使用这种方式已经足够;
appendfsync no 表示不主动进行同步操作,这是最不安全的方式。
选项:
1、appendfsync no
当设定appendfsync为no的时候,Redis不会主动呼叫fsync去将AOF日志内容同步到磁盘,所以这一切就完全依赖于操作系统的除错了。对大多数Linux操作系统,是每30秒进行一次fsync,将缓冲区中的资料写到磁盘上。
2、appendfsync everysec
当设定appendfsync为everysec的时候,Redis会预设每隔一秒进行一次fsync呼叫,将缓冲区中的资料写到磁盘。但是当这一次的fsync呼叫时长超过1秒时。Redis会采取延迟fsync的策略,再等一秒钟。也就是在两秒后再进行fsync,这一次的fsync就不管会执行多长时间都会进行。这时候由于在fsync时档案描述符会被阻塞,所以当前的写操作就会阻塞。所以,结论就是:在绝大多数情况下,Redis会每隔一秒进行一次fsync。在最坏的情况下,两秒钟会进行一次fsync操作。这一操作在大多数数据库系统中被称为group commit,就是组合多次写操作的资料,一次性将日志写到磁盘。
3、appednfsync always
当设定appendfsync为always时,每一次写操作都会呼叫一次fsync,这时资料是最安全的,当然,由于每次都会执行fsync,所以其效能也会受到影响。
建议采用 appendfsync everysec(预设方式)
快照模式可以和AOF模式同时开启,互补影响。
RDB与AOF的区别
RDBRDB持久化是指在指定的时间间隔内将内存中的资料集快照写入磁盘,实际操作过程是fork一个子程序,先将资料集写入临时档案,写入成功后,再替换之前的档案,用二进位制压缩储存。

AOF
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文字的方式记录,可以开启档案看到详细的操作记录。

RDB与AOF对比
RDB存在哪些优势呢?1). 一旦采用该方式,那么你的整个Redis数据库将只包含一个档案,这对于档案备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的资料,同时还要每天归档一次最近30天的资料。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
2). 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的档案压缩后再转移到其它储存介质上。
3). 效能最大化。对于Redis的服务程序而言,在开始持久化时,它唯一需要做的只是fork出子程序,之后再由子程序完成这些持久化的工作,这样就可以极大的避免服务程序执行IO操作了。
4). 相比于AOF机制,如果资料集很大,RDB的启动效率会更高。
RDB又存在哪些劣势呢?
1). 如果你想保证资料的高可用性,即最大限度的避免资料丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的资料都将丢失。
2). 由于RDB是通过fork子程序来协助完成资料持久化工作的,因此,如果当资料集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
AOF的优势有哪些呢?
1). 该机制可以带来更高的资料安全性,即资料永续性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的资料将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的资料变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。
2). 由于该机制对日志档案的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志档案中已经存在的内容。然而如果我们本次操作只是写入了一半资料就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决资料一致性的问题。
3). 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改资料写入到老的磁盘档案中,同时Redis还会建立一个新的档案用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证资料安全性。
4). AOF包含一个格式清晰、易于理解的日志档案用于记录所有的修改操作。事实上,我们也可以通过该档案完成资料的重建。
AOF的劣势有哪些呢?
1). 对于相同数量的资料集而言,AOF档案通常要大于RDB档案。RDB 在恢复大资料集时的速度比 AOF 的恢复速度要快。
2). 根据同步策略的不同,AOF在执行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。
二者选择的标准,就是看系统是愿意牺牲一些效能,换取更高的快取一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的效能,待手动执行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。
手写一个LRU
继承linkedHashMap实现,构建指定最先访问的元素放到列表最后,覆写相关方法
class LRUCache extends LinkedHashMap {
private final int CACHE_SIZE;
/**
* 传递进来最多能快取多少资料
*
* @param cacheSize 快取大小
*/
public LRUCache(int cacheSize) {
// true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
// 当 map中的资料量大于指定的快取个数的时候,就自动删除最老的资料。
return size() > CACHE_SIZE;
}
}
Redis快取数据库一致性
建议策略读请求:先读快取->无则读数据库->更新快取
写请求:先删除快取在更新数据库,还是先更新数据库,后删除快取。为什么不是写完数据库后,更新快取,请看下面的讨论。
两种方式各有优劣。但是都要解决两类问题:
写请求和读请求并发,导致的快取不一致问题(对于这个问题,如果并发量不是很大,可以使用分散式锁保证有序,但是这种方案可能因为操作都是序列,效能下降)
写请求中,第一步成功,但是第二部失败了,导致的快取不一致问题
先更新数据库,后删除快取
先更新数据库,后删除快取(写操作比读取慢,快取不一致出现概率较低),解决这一问题,可以给快取设定失效时间,或者延迟删除快取,保证读请求发生之后再删除快取,或者搞一个伫列,把对同一条记录操作的读写请求放到同一个伫列,当请求处理完毕后再删除快取。
关于先更新数据库,后删除快取,存在的另一大问题就是更新数据库成功,删除快取失败了,导致快取数据库不一致,可以
把失败的讯息传送到统一的讯息处理系统,不断重试,直到成功。也可以使用单独的MQ模组订阅数据库如MySQL的binlog,根据binlog去清理对应的快取。下面的讨论,倾向于先删除快取,后更新数据库,因为这种方案,可以很方便的解决删除快取成功,但是更新数据库失败的问题,因为这样不会导致快取不一致,快取只是被删除了,会在读请求时重新被更新。
写完数据库后是否需要马上更新快取还是直接删除快取
(1)、如果写数据库的值与更新到快取值是一样的,不需要经过任何的计算,可以马上更新快取,但是如果对于那种写资料频繁而读资料少的场景并不合适这种解决方案,因为也许还没有查询就被删除或修改了,这样会浪费时间和资源
(2)、如果写数据库的值与更新快取的值不一致,写入快取中的资料需要经过几个表的关联计算后得到的结果插入快取中,那就没有必要马上更新快取,只有删除快取即可,等到查询的时候在去把计算后得到的结果插入到快取中即可。
(3)、分散式锁保证有序
所以一般的策略是当更新资料时,先删除快取资料,然后更新数据库,而不是更新快取,等要查询的时候才把最新的资料更新到快取
数据库与快取双写情况下导致资料不一致问题
场景一
当更新资料时,如更新某商品的库存,当前商品的库存是100,现在要更新为99,先更新数据库更改成99,然后删除快取,发现删除快取失败了,这意味着数据库存的是99,而快取是100,这导致数据库和快取不一致。
场景一解决方案
这种情况应该是先删除快取,然后在更新数据库,如果删除快取失败,那就不要更新数据库,如果说删除快取成功,而更新数据库失败,那查询的时候只是从数据库里查了旧的资料而已,这样就能保持数据库与快取的一致性。
场景二
在高并发的情况下,如果当删除完快取的时候,这时去更新数据库,但还没有更新完,另外一个请求来查询资料,发现快取里没有,就去数据库里查,还是以上面商品库存为例,如果数据库中产品的库存是100,那么查询到的库存是100,然后插入快取,插入完快取后,原来那个更新数据库的执行绪把数据库更新为了99,导致数据库与快取不一致的情况
场景二解决方案
遇到这种情况,可以用伫列的去解决这个问题,建立几个伫列,如20个,根据商品的ID去做hash值,然后对伫列个数取摸,当有资料更新请求时,先把它丢到伫列里去,当更新完后在从伫列里去除,如果在更新的过程中,遇到以上场景,先去快取里看下有没有资料,如果没有,可以先去伫列里看是否有相同商品ID在做更新,如果有也把查询的请求传送到伫列里去,然后同步等待快取更新完成。
这里有一个优化点,如果发现伫列里有一个查询请求了,那么就不要放新的查询操作进去了,用一个while(true)循环去查询快取,循环个200MS左右,如果快取里还没有则直接取数据库的旧资料,一般情况下是可以取到的。
在高并发下解决场景二要注意的问题
(1)读请求时长阻塞
由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时间内返回,该解决方案最大的风险在于可能资料更新很频繁,导致伫列中挤压了大量的更新操作在里面,然后读请求会发生大量的超时,最后导致大量的请求直接走数据库,像遇到这种情况,一般要做好足够的压力测试,如果压力过大,需要根据实际情况新增机器。
(2)请求并发量过高
这里还是要做好压力测试,多模拟真实场景,并发量在最高的时候QPS多少,扛不住就要多加机器,还有就是做好读写比例是多少
(3)多服务例项部署的请求路由
可能这个服务部署了多个例项,那么必须保证说,执行资料更新操作,以及执行快取更新操作的请求,都通过nginx服务器路由到相同的服务例项上
(4)热点商品的路由问题,导致请求的倾斜
某些商品的读请求特别高,全部打到了相同的机器的相同丢列里了,可能造成某台服务器压力过大,因为只有在商品资料更新的时候才会清空快取,然后才会导致读写并发,所以更新频率不是太高的话,这个问题的影响并不是很大,但是确实有可能某些服务器的负载会高一些。
数据库与快取资料一致性解决方案流程图


综述
1、不一致产生的原因?
我们在是使用redis过程中,通常会这样做,先读取快取,如果快取不存在,则读取数据库。
不管是先写库,再删除快取;还是先删除快取,再写库,都有可能出现数据不一致的情况。
因为写和读是并发的,没法保证顺序,如果删除了快取,还没有来得及写库,另一个执行绪就来读取,发现快取为空,则去数据库中读取资料写入快取,此时快取中为脏资料。如果先写了库,在删除快取前,写库的执行绪宕机了,没有删除掉快取,则也会出现资料不一致情况。
如果是redis丛集,或者主从模式,写主读从,由于redis复制存在一定的时间延迟,也有可能导致资料不一致。
2、优化思路
(1)读操作优先读取redis,不存在的话就去访问MySql,并把读到的资料写回Redis中;
(2)写操作的话,直接写MySql,成功后再写入Redis,替换掉原来的旧资料(可以在MySql端定义CRUD触发器,在触发CRUD操作后写资料到Redis,也可以在Redis端解析binlog,再做相应的操作)
(3)设定合理的超时时间,即经过超时时间,自动将redis中相应的资料删除。这样最差的情况是在超时时间内,内存存在不一致。当然这种策略要考虑redis和数据库主从同步的耗时,所以在第二次删除前最好休眠一定的时间,比如500毫秒,这样无疑又增加了写请求的耗时。
快取雪崩、快取穿透、快取预热、快取更新、快取降级等问题
快取穿透快取穿透是指查询一个一定不存在的资料,由于快取是不命中时被动写的,并且出于容错考虑,如果从储存层查不到资料则不写入快取,这将导致这个不存在的资料每次请求都要到储存层去查询,失去了快取的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
解决方案
有很多种方法可以有效地解决快取穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的资料杂凑到一个足够大的bitmap中,一个一定不存在的资料会被 这个bitmap拦截掉,从而避免了对底层储存系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的资料为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行快取,但它的过期时间会很短,最长不超过五分钟。
快取雪崩
快取雪崩是指在我们设定快取时采用了相同的过期时间,导致快取在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决方案
快取失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者伫列的方式保证快取的单线 程(程序)写,从而避免失效时大量的并发请求落到底层储存系统上。这里分享一个简单方案就时讲快取失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个快取的过期时间的重复率就会降低,就很难引发集体失效的事件。
如果是黑客攻击,导致快取雪崩,可采用策略如下:
事前 保证redis高可用 可恢复,使用本地快取ehcache,限流及降级元件Hystrix
事后 进行恢复
快取击穿
对于一些设定了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的资料。这个时候,需要考虑一个问题:快取被“击穿”的问题,这个和快取雪崩的区别在于这里针对某一key快取,前者则是很多key。
快取在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现快取过期一般都会从后端DB载入资料并回设到快取,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案
使用互斥锁(mutex key)检查更新分级快取
Redis主从复制
概述基本思想:读写分离 - 提高Redis的QPS,主从复制 - 保证高可用

原理:
1、slave node传送PSYNC命令给master node
2、如果slave node是第一次连线master node,会触发一次full resynchronization,master会启动一个后台执行绪,开始生成一份RDB快照档案,同时会将从客户端收到的命令快取到内存中,RDB档案生成完毕后,master会将这个RDB传送给slave,slave先写入本地磁盘,然后从磁盘载入到内存中,然后master会将内存中快取的写命令传送给slave,slave也会同步这些资料;
如果不是第一次连线,master node仅赋值给slave缺少的资料即可。
3、slave node如果因网络故障和master断开,会自动重连,master如果发现有多个slave都来重新连线,仅会启动一个rdb save操作,用一份资料服务所有slave
详细
Redis主从复制可以根据是否是全量分为全量同步和增量同步。
全量同步
Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有资料都复制一份。具体步骤如下:
1)从服务器连线主服务器,传送SYNC命令;
2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB档案并使用缓冲区记录此后执行的所有写命令;
3)主服务器BGSAVE执行完后,向所有从服务器传送快照档案,并在传送期间继续记录被执行的写命令;
4)从服务器收到快照档案后丢弃所有旧资料,载入收到的快照;
5)主服务器快照发送完毕后开始向从服务器传送缓冲区中的写命令;
6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
图示:

增量同步
Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器传送相同的写命令,从服务器接收并执行收到的写命令。
整体同步策略
Redis的同步策略是:主从刚刚连线的时候,进行全量同步;全量同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
Redis哨兵机制
Redis哨兵简介Redis-Sentinel也就是哨兵机制,是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立执行的程序,它能监控多个master-slave丛集,发现master宕机后能进行自动切换。
它的主要功能有以下几点:
不时地监控redis是否按照预期良好地执行;
如果发现某个redis节点执行出现状况,能够通知另外一个程序(例如它的客户端);
能够进行自动切换(进行主备切换)。当一个master节点不可用时,能够选举出master的多个slave(如果有超过一个slave的话)中的一个来作为新的master,其它的slave节点会将它所追随的master的地址改为被提升为master的slave的新地址。
Sentinel支援丛集
很显然,只使用单个sentinel程序来监控redis丛集是不可靠的,当sentinel程序宕掉后(sentinel本身也有单点问题,single-point-of-failure)整个集群系统将无法按照预期的方式执行。所以有必要将sentinel丛集,这样有几个好处:
即使有一些sentinel程序宕掉了,依然可以进行redis丛集的主备切换;
如果只有一个sentinel程序,如果这个程序执行出错,或者是网络堵塞,那么将无法实现redis丛集的主备切换(单点问题);
如果有多个sentinel,redis的客户端可以随意地连线任意一个sentinel来获得关于redis丛集中的资讯。
由于过半策略,一般部署3个哨兵,保证高可用
另一个相关的概念:脑裂问题
指的是一个系统中由于网络分割槽等原因,出现了两个master,需要关注此问题
哨兵机制的执行过程

如上图所示,Redis为了保证高可用性,采用Master-slave形式部署,采用AOF或RDB进行持久化,采用丛集culster机制来分散式储存。多个哨兵,不仅同时监控主从数据库,而且哨兵之间互为监控,并且哨兵不会存在单点问题。哨兵会监控主从Redis服务器是否正常,当发现有问题时,会进行通知,并进行故障转移。当监控到master节点宕机后,会进行maser选举出新的master节点,并进行主从切换,并将其他节点作为新选举出的master节点的slave。哨兵Sentinel用到的分散式一致性算法是Raft分散式算法。
redis利用比较易于实现的raft协议实现了节点宕机的自动化处理,保障了丛集的高可用性。Raft协议与paxos算法类似,但比paxos算法好理解,我们都知道,paxos算法是经典的分散式一致性算法,zookeeper的ZAB协议也是在其基础上设计的。ZAB,paxos,Raft都是采用过半策略。关于相关分散式算法,我会在后边的系列,Zookeeper系列进行介绍。对于Redis的监控过程,Leader选举,主备切换,资料同步等过程,跟Zookeeper类似,我们可以把zookeeper的实现方式与其进行类比
Redis丛集模式
一致性Hash简单来说,一个物理节点与多个虚拟节点对映,在hash的时候,使用虚拟节点数目而不是物理节点数目。当物理节点变化的时候,虚拟节点的数目无需变化,只涉及到虚拟节点的重新分配。而且,调整每个物理节点对应的虚拟节点数目,也就相当于每个物理节点有不同的权重。
无虚拟节点:

加入虚拟节点,防止雪崩:

Redis丛集
redis丛集原理(杂凑槽、主从复制等技术)
设定redis服务器先经过CRC16杂凑到一个指定的Node上范围是0-16384 (平均分配,不能重复也不能缺失,否则会导致物件重复储存或无法储存,比如:三台啊服务器:节点1分配0-5600,节点二分配应该书5601-12000,节点3,12001-16384).
当资料要储存到redis时,通过CRC16杂凑到一个指定RC16杂凑值,储存在对应的节点上。
获取,当要获取一个数据时,先通过key获取到RC16杂凑值,再通过RC16杂凑值找到对应的节点,然后就能在对应的节点马上找到kye的值了。
Redis应用场景
快取分散式讯息伫列分散式锁位操作(去重,统计线上人数等)排行榜共同关注后面会分享更多devops和运维方面的内容,感兴趣的朋友可以关注一下~
