Redis緩存相關(guān)問題總結(jié)

使用緩存是系統(tǒng)性能優(yōu)化的第一黃金法則。

緩存的設(shè)計和使用對一個系統(tǒng)的性能至關(guān)重要,平時接觸到項目無論多少也都會在某些層面用到緩存,比如用HashMap實現(xiàn),Ehcache,memcached、redis等。Redis算是目前最火的方案之一,今天看了它相關(guān)的一些問題,總結(jié)匯總一下。

一、Redis的優(yōu)缺點及適用場景

Redis 是一個基于內(nèi)存的高性能key-value數(shù)據(jù)庫。很像memcached,整個數(shù)據(jù)庫統(tǒng)統(tǒng)加載在內(nèi)存當中進行操作,定期通過異步操作把數(shù)據(jù)庫數(shù)據(jù)flush到硬盤上進行保存。它的優(yōu)點如下:

(1) 速度快,因為數(shù)據(jù)存在內(nèi)存中,類似于HashMap,HashMap的優(yōu)勢就是查找和操作的時間復雜度都是O(1)

(2) 支持豐富數(shù)據(jù)類型,支持string,list,set,sorted set,hash

(3) 支持事務,操作都是原子性,所謂的原子性就是對數(shù)據(jù)的更改要么全部執(zhí)行,要么全部不執(zhí)行

(4) 豐富的特性:可用于緩存,消息,按key設(shè)置過期時間,過期后將會自動刪除

Redis的主要缺點是數(shù)據(jù)庫容量受到物理內(nèi)存的限制,不能用作海量數(shù)據(jù)的高性能讀寫,因此Redis適合的場景主要局限在較小數(shù)據(jù)量的高性能操作和運算上。

Redis最適合所有數(shù)據(jù)in-momory的場景,如:

(1)、會話緩存(Session Cache)

最常用的一種使用Redis的情景是會話緩存(session cache)。用Redis緩存會話比其他存儲(如Memcached)的優(yōu)勢在于:Redis提供持久化。

(2)、全頁緩存(FPC)

除基本的會話token之外,Redis還提供很簡便的FPC平臺?;氐揭恢滦詥栴},即使重啟了Redis實例,因為有磁盤的持久化,用戶也不會看到頁面加載速度的下降,這是一個極大改進,類似PHP本地FPC。

(3)、隊列

Reids在內(nèi)存存儲引擎領(lǐng)域的一大優(yōu)點是提供 list 和 set 操作,這使得Redis能作為一個很好的消息隊列平臺來使用。Redis作為隊列使用的操作,就類似于本地程序語言(如Python)對 list 的 push/pop 操作。

如果你快速的在Google中搜索“Redis queues”,你馬上就能找到大量的開源項目,這些項目的目的就是利用Redis創(chuàng)建非常好的后端工具,以滿足各種隊列需求。例如,Celery有一個后臺就是使用Redis作為broker,你可以從這里去查看。

(4),排行榜/計數(shù)器

Redis在內(nèi)存中對數(shù)字進行遞增或遞減的操作實現(xiàn)的非常好。集合(Set)和有序集合(Sorted

Set)也使得我們在執(zhí)行這些操作的時候變的非常簡單,Redis只是正好提供了這兩種數(shù)據(jù)結(jié)構(gòu)。所以,我們要從排序集合中獲取到排名最靠前的10個用戶–我們稱之為“user_scores”,我們只需要像下面一樣執(zhí)行即可:

當然,這是假定你是根據(jù)你用戶的分數(shù)做遞增的排序。如果你想返回用戶及用戶的分數(shù),你需要這樣執(zhí)行:

ZRANGE user_scores 0 10 WITHSCORES

Agora Games就是一個很好的例子,用Ruby實現(xiàn)的,它的排行榜就是使用Redis來存儲數(shù)據(jù)的,你可以在這里看到。

(5)、發(fā)布/訂閱

最后(但肯定不是最不重要的)是Redis的發(fā)布/訂閱功能。發(fā)布/訂閱的使用場景確實非常多。



二、redis的緩存失效策略和主鍵失效機制

作為緩存系統(tǒng)都要定期清理無效數(shù)據(jù),就需要一個主鍵失效和淘汰策略.

在Redis當中,有生存期的key被稱為volatile。在創(chuàng)建緩存時,要為給定的key設(shè)置生存期,當key過期的時候(生存期為0),它可能會被刪除。

1、影響生存時間的一些操作

生存時間可以通過使用 DEL 命令來刪除整個 key 來移除,或者被 SET 和 GETSET 命令覆蓋原來的數(shù)據(jù),也就是說,修改key對應的value和使用另外相同的key和value來覆蓋以后,當前數(shù)據(jù)的生存時間不同。

比如說,對一個 key 執(zhí)行INCR命令,對一個列表進行LPUSH命令,或者對一個哈希表執(zhí)行HSET命令,這類操作都不會修改 key 本身的生存時間。另一方面,如果使用RENAME對一個 key 進行改名,那么改名后的 key的生存時間和改名前一樣。

RENAME命令的另一種可能是,嘗試將一個帶生存時間的 key 改名成另一個帶生存時間的 another_key ,這時舊的 another_key (以及它的生存時間)會被刪除,然后舊的 key 會改名為 another_key ,因此,新的 another_key 的生存時間也和原本的 key 一樣。使用PERSIST命令可以在不刪除 key 的情況下,移除 key 的生存時間,讓 key 重新成為一個persistent key 。

2、如何更新生存時間

可以對一個已經(jīng)帶有生存時間的 key 執(zhí)行EXPIRE命令,新指定的生存時間會取代舊的生存時間。過期時間的精度已經(jīng)被控制在1ms之內(nèi),主鍵失效的時間復雜度是O(1),

EXPIRE和TTL命令搭配使用,TTL可以查看key的當前生存時間。設(shè)置成功返回 1;當 key 不存在或者不能為 key 設(shè)置生存時間時,返回 0 。

最大緩存配置

在 redis 中,允許用戶設(shè)置最大使用內(nèi)存大小

server.maxmemory

默認為0,沒有指定最大緩存,如果有新的數(shù)據(jù)添加,超過最大內(nèi)存,則會使redis崩潰,所以一定要設(shè)置。redis 內(nèi)存數(shù)據(jù)集大小上升到一定大小的時候,就會實行數(shù)據(jù)淘汰策略。

redis 提供 6種數(shù)據(jù)淘汰策略:

. volatile-lru:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰

. volatile-ttl:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù)據(jù)淘汰

. volatile-random:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰

. allkeys-lru:從數(shù)據(jù)集(server.db[i].dict)中挑選最近最少使用的數(shù)據(jù)淘汰

. allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰

. no-enviction(驅(qū)逐):禁止驅(qū)逐數(shù)據(jù)

注意這里的6種機制,volatile和allkeys規(guī)定了是對已設(shè)置過期時間的數(shù)據(jù)集淘汰數(shù)據(jù)還是從全部數(shù)據(jù)集淘汰數(shù)據(jù),后面的lru、ttl以及random是三種不同的淘汰策略,再加上一種no-enviction永不回收的策略。

使用策略規(guī)則:

1、如果數(shù)據(jù)呈現(xiàn)冪律分布,也就是一部分數(shù)據(jù)訪問頻率高,一部分數(shù)據(jù)訪問頻率低,則使用allkeys-lru

2、如果數(shù)據(jù)呈現(xiàn)平等分布,也就是所有的數(shù)據(jù)訪問頻率都相同,則使用allkeys-random

三種數(shù)據(jù)淘汰策略:

ttl和random比較容易理解,實現(xiàn)也會比較簡單。主要是Lru最近最少使用淘汰策略,設(shè)計上會對key 按失效時間排序,然后取最先失效的key進行淘汰


三、Redis是單進程單線程的,并發(fā)問題如何解決

Redis為單進程單線程模式,采用隊列模式將并發(fā)訪問變?yōu)榇性L問。Redis本身沒有鎖的概念,Redis對于多個客戶端連接并不存在競爭,但是在Jedis客戶端對Redis進行并發(fā)訪問時會發(fā)生連接超時、數(shù)據(jù)轉(zhuǎn)換錯誤、阻塞、客戶端關(guān)閉連接等問題,這些問題均是由于客戶端連接混亂造成。對此有2種解決方法:

   1.客戶端角度,為保證每個客戶端間正常有序與Redis進行通信,對連接進行池化,同時對客戶端讀寫Redis操作采用內(nèi)部鎖synchronized。

2.服務器角度,利用setnx實現(xiàn)鎖。

注:對于第一種,需要應用程序自己處理資源的同步,可以使用的方法比較通俗,可以使用synchronized也可以使用lock;第二種需要用到Redis的setnx命令,但是需要注意一些問題。


四、redis常見性能問題和解決方案:?  

   1).Master寫內(nèi)存快照,save命令調(diào)度rdbSave函數(shù),會阻塞主線程的工作,當快照比較大時對性能影響是非常大的,會間斷性暫停服務,所以Master最好不要寫內(nèi)存快照。

   2).Master

AOF持久化,如果不重寫AOF文件,這個持久化方式對性能的影響是最小的,但是AOF文件會不斷增大,AOF文件過大會影響Master重啟的恢復速度。Master最好不要做任何持久化工作,包括內(nèi)存快照和AOF日志文件,特別是不要啟用內(nèi)存快照做持久

    化,如果數(shù)據(jù)比較關(guān)鍵,某個Slave開啟AOF備份數(shù)據(jù),策略為每秒同步一次。

   3).Master調(diào)用BGREWRITEAOF重寫AOF文件,AOF在重寫的時候會占大量的CPU和內(nèi)存資源,導致服務load過高,出現(xiàn)短暫服務暫?,F(xiàn)象。

   4). Redis主從復制的性能問題,為了主從復制的速度和連接的穩(wěn)定性,Slave和Master最好在同一個局域網(wǎng)內(nèi)。


五、redis持久化的幾種方式

1、快照(snapshots)

缺省情況情況下,Redis把數(shù)據(jù)快照存放在磁盤上的二進制文件中,文件名為dump.rdb。你可以配置Redis的持久化策略,例如數(shù)據(jù)集中每N秒鐘有超過M次更新,就將數(shù)據(jù)寫入磁盤;或者你可以手工調(diào)用命令SAVE或BGSAVE。

工作原理

. Redis forks.

. 子進程開始將數(shù)據(jù)寫到臨時RDB文件中。

. 當子進程完成寫RDB文件,用新文件替換老文件。

. 這種方式可以使Redis使用copy-on-write技術(shù)。

2、AOF

快照模式并不十分健壯,當系統(tǒng)停止,或者無意中Redis被kill掉,最后寫入Redis的數(shù)據(jù)就會丟失。這對某些應用也許不是大問題,但對于要求高可靠性的應用來說,

Redis就不是一個合適的選擇。

Append-only文件模式是另一種選擇。

你可以在配置文件中打開AOF模式

3、虛擬內(nèi)存方式

當你的key很小而value很大時,使用VM的效果會比較好.因為這樣節(jié)約的內(nèi)存比較大.

當你的key不小時,可以考慮使用一些非常方法將很大的key變成很大的value,比如你可以考慮將key,value組合成一個新的value.

vm-max-threads這個參數(shù),可以設(shè)置訪問swap文件的線程數(shù),設(shè)置最好不要超過機器的核數(shù),如果設(shè)置為0,那么所有對swap文件的操作都是串行的.可能會造成比較長時間的延遲,但是對數(shù)據(jù)完整性有很好的保證.

  自己測試的時候發(fā)現(xiàn)用虛擬內(nèi)存性能也不錯。如果數(shù)據(jù)量很大,可以考慮分布式或者其他數(shù)據(jù)庫。


六,其它

Redis和Memcached的區(qū)別

1、數(shù)據(jù)類型支持不同

與Memcached僅支持簡單的key-value結(jié)構(gòu)的數(shù)據(jù)記錄不同,Redis支持的數(shù)據(jù)類型要豐富得多。最為常用的數(shù)據(jù)類型主要由五種:String、Hash、List、Set和Sorted

Set。Redis內(nèi)部使用一個redisObject對象來表示所有的key和value。redisObject最主要的信息如圖所示:



type代表一個value對象具體是何種數(shù)據(jù)類型,encoding是不同數(shù)據(jù)類型在redis內(nèi)部的存儲方式,比如:type=string代表value存儲的是一個普通字符串,那么對應的encoding可以是raw或者是int,如果是int則代表實際redis內(nèi)部是按數(shù)值型類存儲和表示這個字符串的,當然前提是這個字符串本身可以用數(shù)值表示,比如:”123″

“456”這樣的字符串。只有打開了Redis的虛擬內(nèi)存功能,vm字段字段才會真正的分配內(nèi)存,該功能默認是關(guān)閉狀態(tài)的。

1)String

常用命令:set/get/decr/incr/mget等;

應用場景:String是最常用的一種數(shù)據(jù)類型,普通的key/value存儲都可以歸為此類;

實現(xiàn)方式:String在redis內(nèi)部存儲默認就是一個字符串,被redisObject所引用,當遇到incr、decr等操作時會轉(zhuǎn)成數(shù)值型進行計算,此時redisObject的encoding字段為int。

2)Hash

常用命令:hget/hset/hgetall等

應用場景:我們要存儲一個用戶信息對象數(shù)據(jù),其中包括用戶ID、用戶姓名、年齡和生日,通過用戶ID我們希望獲取該用戶的姓名或者年齡或者生日;

實現(xiàn)方式:Redis的Hash實際是內(nèi)部存儲的Value為一個HashMap,并提供了直接存取這個Map成員的接口。如圖所示,Key是用戶ID,

value是一個Map。這個Map的key是成員的屬性名,value是屬性值。這樣對數(shù)據(jù)的修改和存取都可以直接通過其內(nèi)部Map的Key(Redis里稱內(nèi)部Map的key為field),

也就是通過 key(用戶ID) + field(屬性標簽)

就可以操作對應屬性數(shù)據(jù)。當前HashMap的實現(xiàn)有兩種方式:當HashMap的成員比較少時Redis為了節(jié)省內(nèi)存會采用類似一維數(shù)組的方式來緊湊存儲,而不會采用真正的HashMap結(jié)構(gòu),這時對應的value的redisObject的encoding為zipmap,當成員數(shù)量增大時會自動轉(zhuǎn)成真正的HashMap,此時encoding為ht。


3)List

常用命令:lpush/rpush/lpop/rpop/lrange等;

應用場景:Redis list的應用場景非常多,也是Redis最重要的數(shù)據(jù)結(jié)構(gòu)之一,比如twitter的關(guān)注列表,粉絲列表等都可以用Redis的list結(jié)構(gòu)來實現(xiàn);

實現(xiàn)方式:Redis list的實現(xiàn)為一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內(nèi)存開銷,Redis內(nèi)部的很多實現(xiàn),包括發(fā)送緩沖隊列等也都是用的這個數(shù)據(jù)結(jié)構(gòu)。

4)Set

常用命令:sadd/spop/smembers/sunion等;

應用場景:Redis

set對外提供的功能與list類似是一個列表的功能,特殊之處在于set是可以自動排重的,當你需要存儲一個列表數(shù)據(jù),又不希望出現(xiàn)重復數(shù)據(jù)時,set是一個很好的選擇,并且set提供了判斷某個成員是否在一個set集合內(nèi)的重要接口,這個也是list所不能提供的;

實現(xiàn)方式:set 的內(nèi)部實現(xiàn)是一個 value永遠為null的HashMap,實際就是通過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內(nèi)的原因。

5)Sorted Set

常用命令:zadd/zrange/zrem/zcard等;

應用場景:Redis sorted set的使用場景與set類似,區(qū)別是set不是自動有序的,而sorted

set可以通過用戶額外提供一個優(yōu)先級(score)的參數(shù)來為成員排序,并且是插入有序的,即自動排序。當你需要一個有序的并且不重復的集合列表,那么可以選擇sorted

set數(shù)據(jù)結(jié)構(gòu),比如twitter 的public timeline可以以發(fā)表時間作為score來存儲,這樣獲取時就是自動按時間排好序的。

實現(xiàn)方式:Redis sorted

set的內(nèi)部使用HashMap和跳躍表(SkipList)來保證數(shù)據(jù)的存儲和有序,HashMap里放的是成員到score的映射,而跳躍表里存放的是所有的成員,排序依據(jù)是HashMap里存的score,使用跳躍表的結(jié)構(gòu)可以獲得比較高的查找效率,并且在實現(xiàn)上比較簡單。

2、內(nèi)存管理機制不同

在Redis中,并不是所有的數(shù)據(jù)都一直存儲在內(nèi)存中的。這是和Memcached相比一個最大的區(qū)別。當物理內(nèi)存用完時,Redis可以將一些很久沒用到的value交換到磁盤。Redis只會緩存所有的key的信息,如果Redis發(fā)現(xiàn)內(nèi)存的使用量超過了某一個閥值,將觸發(fā)swap的操作,Redis根據(jù)“swappability

=

age*log(size_in_memory)”計算出哪些key對應的value需要swap到磁盤。然后再將這些key對應的value持久化到磁盤中,同時在內(nèi)存中清除。這種特性使得Redis可以保持超過其機器本身內(nèi)存大小的數(shù)據(jù)。當然,機器本身的內(nèi)存必須要能夠保持所有的key,畢竟這些數(shù)據(jù)是不會進行swap操作的。同時由于Redis將內(nèi)存中的數(shù)據(jù)swap到磁盤中的時候,提供服務的主線程和進行swap操作的子線程會共享這部分內(nèi)存,所以如果更新需要swap的數(shù)據(jù),Redis將阻塞這個操作,直到子線程完成swap操作后才可以進行修改。當從Redis中讀取數(shù)據(jù)的時候,如果讀取的key對應的value不在內(nèi)存中,那么Redis就需要從swap文件中加載相應數(shù)據(jù),然后再返回給請求方。

這里就存在一個I/O線程池的問題。在默認的情況下,Redis會出現(xiàn)阻塞,即完成所有的swap文件加載后才會相應。這種策略在客戶端的數(shù)量較小,進行批量操作的時候比較合適。但是如果將Redis應用在一個大型的網(wǎng)站應用程序中,這顯然是無法滿足大并發(fā)的情況的。所以Redis運行我們設(shè)置I/O線程池的大小,對需要從swap文件中加載相應數(shù)據(jù)的讀取請求進行并發(fā)操作,減少阻塞的時間。

3、持久化方案

4、集群管理的不同

Redis有哪些數(shù)據(jù)結(jié)構(gòu)?

字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。

如果你是Redis中高級用戶,還需要加上下面幾種數(shù)據(jù)結(jié)構(gòu)HyperLogLog、Geo、Pub/Sub。

如果你說還玩過Redis Module,像BloomFilter,RedisSearch,Redis-ML,面試官得眼睛就開始發(fā)亮了。

使用過Redis分布式鎖么,它是什么回事?

先拿setnx來爭搶鎖,搶到之后,再用expire給鎖加一個過期時間防止鎖忘記了釋放。

這時候?qū)Ψ綍嬖V你說你回答得不錯,然后接著問如果在setnx之后執(zhí)行expire之前進程意外crash或者要重啟維護了,那會怎么樣?

這時候你要給予驚訝的反饋:唉,是喔,這個鎖就永遠得不到釋放了。緊接著你需要抓一抓自己得腦袋,故作思考片刻,好像接下來的結(jié)果是你主動思考出來的,然后回答:我記得set指令有非常復雜的參數(shù),這個應該是可以同時把setnx和expire合成一條指令來用的!對方這時會顯露笑容,心里開始默念:摁,這小子還不錯。

假如Redis里面有1億個key,其中有10w個key是以某個固定的已知的前綴開頭的,如果將它們?nèi)空页鰜恚?/p>

使用keys指令可以掃出指定模式的key列表。

對方接著追問:如果這個redis正在給線上的業(yè)務提供服務,那使用keys指令會有什么問題?

這個時候你要回答redis關(guān)鍵的一個特性:redis的單線程的。keys指令會導致線程阻塞一段時間,線上服務會停頓,直到指令執(zhí)行完畢,服務才能恢復。這個時候可以使用scan指令,scan指令可以無阻塞的提取出指定模式的key列表,但是會有一定的重復概率,在客戶端做一次去重就可以了,但是整體所花費的時間會比直接用keys指令長。

使用過Redis做異步隊列么,你是怎么用的?

一般使用list結(jié)構(gòu)作為隊列,rpush生產(chǎn)消息,lpop消費消息。當lpop沒有消息的時候,要適當sleep一會再重試。

如果對方追問可不可以不用sleep呢?list還有個指令叫blpop,在沒有消息的時候,它會阻塞住直到消息到來。

如果對方追問能不能生產(chǎn)一次消費多次呢?使用pub/sub主題訂閱者模式,可以實現(xiàn)1:N的消息隊列。

如果對方追問pub/sub有什么缺點?在消費者下線的情況下,生產(chǎn)的消息會丟失,得使用專業(yè)的消息隊列如rabbitmq等。

如果對方追問redis如何實現(xiàn)延時隊列?我估計現(xiàn)在你很想把面試官一棒打死如果你手上有一根棒球棍的話,怎么問的這么詳細。但是你很克制,然后神態(tài)自若的回答道:使用sortedset,拿時間戳作為score,消息內(nèi)容作為key調(diào)用zadd來生產(chǎn)消息,消費者用zrangebyscore指令獲取N秒之前的數(shù)據(jù)輪詢進行處理。

到這里,面試官暗地里已經(jīng)對你豎起了大拇指。但是他不知道的是此刻你卻豎起了中指,在椅子背后。

如果有大量的key需要設(shè)置同一時間過期,一般需要注意什么?

如果大量的key過期時間設(shè)置的過于集中,到過期的那個時間點,redis可能會出現(xiàn)短暫的卡頓現(xiàn)象。一般需要在時間上加一個隨機值,使得過期時間分散一些。

Redis如何做持久化的?

bgsave做鏡像全量持久化,aof做增量持久化。因為bgsave會耗費較長時間,不夠?qū)崟r,在停機的時候會導致大量丟失數(shù)據(jù),所以需要aof來配合使用。在redis實例重啟時,會使用bgsave持久化文件重新構(gòu)建內(nèi)存,再使用aof重放近期的操作指令來實現(xiàn)完整恢復重啟之前的狀態(tài)。

對方追問那如果突然機器掉電會怎樣?取決于aof日志sync屬性的配置,如果不要求性能,在每條寫指令時都sync一下磁盤,就不會丟失數(shù)據(jù)。但是在高性能的要求下每次都sync是不現(xiàn)實的,一般都使用定時sync,比如1s1次,這個時候最多就會丟失1s的數(shù)據(jù)。

對方追問bgsave的原理是什么?你給出兩個詞匯就可以了,fork和cow。fork是指redis通過創(chuàng)建子進程來進行bgsave操作,cow指的是copy

on write,子進程創(chuàng)建后,父子進程共享數(shù)據(jù)段,父進程繼續(xù)提供讀寫服務,寫臟的頁面數(shù)據(jù)會逐漸和子進程分離開來。

Pipeline有什么好處,為什么要用pipeline?

可以將多次IO往返的時間縮減為一次,前提是pipeline執(zhí)行的指令之間沒有因果相關(guān)性。使用redis-benchmark進行壓測的時候可以發(fā)現(xiàn)影響redis的QPS峰值的一個重要因素是pipeline批次指令的數(shù)目。

Redis的同步機制了解么?

Redis可以使用主從同步,從從同步。第一次同步時,主節(jié)點做一次bgsave,并同時將后續(xù)修改操作記錄到內(nèi)存buffer,待完成后將rdb文件全量同步到復制節(jié)點,復制節(jié)點接受完成后將rdb鏡像加載到內(nèi)存。加載完成后,再通知主節(jié)點將期間修改的操作記錄同步到復制節(jié)點進行重放就完成了同步過程。

是否使用過Redis集群,集群的原理是什么?

Redis Sentinal著眼于高可用,在master宕機時會自動將slave提升為master,繼續(xù)提供服務。


Redis Cluster著眼于擴展性,在單個redis內(nèi)存不足時,使用Cluster進行分片存儲。

https://mp.weixin.qq.com/s/507jyNbL4xCkxyW6Xk15Xg

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容