Hadoop高可用集群

若HDFS集群中只配置了一個NameNode混埠,那么當(dāng)該NameNode所在的節(jié)點宕機,則整個HDFS就不能進行文件的上傳和下載赘风。

若YARN集群中只配置了一個ResourceManager时鸵,那么當(dāng)該ResourceManager所在的節(jié)點宕機,則整個YARN就不能進行任務(wù)的計算。

*Hadoop依賴Zookeeper進行各個模塊的HA配置红碑,其中狀態(tài)為Active的節(jié)點對外提供服務(wù)赶站,而狀態(tài)為StandBy的節(jié)點則只負(fù)責(zé)數(shù)據(jù)的同步,在必要時提供快速故障轉(zhuǎn)移木蹬。

2.HDFS HA集群

2.1 模型

當(dāng)有兩個NameNode時至耻,提供哪個NameNode地址給客戶端?

1.Hadoop提供了NameService進程镊叁,其是NameNode的代理尘颓,維護NameNode列表并存儲NameNode的狀態(tài),客戶端直接訪問的是NameService晦譬,NameService會將請求轉(zhuǎn)發(fā)給當(dāng)前狀態(tài)為Active的NameNode疤苹。

2.當(dāng)啟動HDFS時,DataNode將同時向兩個NameNode進行注冊敛腌。

怎樣發(fā)現(xiàn)NameNode無法提供服務(wù)以及如何進行NameNode間狀態(tài)的切換卧土?

1.Hadoop提供了FailoverControllerActive和FailoverControllerStandBy兩個進程用于NameNode的生命監(jiān)控惫皱。

2.FailoverControllerActive和FailoverControllerStandBy會分別監(jiān)控對應(yīng)狀態(tài)的NameNode,若NameNode無異常則定期向Zookeeper集群發(fā)送心跳尤莺,若在一定時間內(nèi)Zookeeper集群沒收到FailoverControllerActive發(fā)送的心跳旅敷,則認(rèn)為此時狀態(tài)為Active的NameNode已經(jīng)無法對外提供服務(wù),因此將狀態(tài)為StandBy的NameNode切換為Active狀態(tài)颤霎。

NameNode之間的數(shù)據(jù)如何進行同步和共享媳谁?

1.Hadoop提供了JournalNode用于存放NameNode中的編輯日志。

2.當(dāng)激活的NameNode執(zhí)行任何名稱空間上的修改時友酱,它將修改的記錄保存到JournalNode集群中晴音,備用的NameNode能夠?qū)崟r監(jiān)控JournalNode集群中日志的變化,當(dāng)監(jiān)控到日志發(fā)生改變時會將其同步到本地缔杉。

*當(dāng)狀態(tài)為Active的NameNode無法對外提供服務(wù)時锤躁,Zookeeper將會自動的將處于StandBy狀態(tài)的NameNode切換成Active。

2.2 HDFS HA高可用集群搭建

1.配置HDFS(hdfs-site.xml)

<configuration>

<!-- 指定NameService的名稱 -->

<property>

<name>dfs.nameservices</name>

<value>mycluster</value>

</property>

<!-- 指定NameService下兩個NameNode的名稱 -->

<property>

<name>dfs.ha.namenodes.mycluster</name>

<value>nn1,nn2</value>

</property>

<!-- 分別指定NameNode的RPC通訊地址 -->

<property>

<name>dfs.namenode.rpc-address.mycluster.nn1</name>

<value>192.168.1.80:8020</value>

</property>

<property>

<name>dfs.namenode.rpc-address.mycluster.nn2</name>

<value>192.168.1.81:8020</value>

</property>

<!-- 分別指定NameNode的Web監(jiān)控頁面地址 -->

<property>

<name>dfs.namenode.http-address.mycluster.nn1</name>

<value>192.168.1.80:50070</value>

</property>

<property>

<name>dfs.namenode.http-address.mycluster.nn2</name>

<value>192.168.1.81:50070</value>

</property>

<!-- 指定NameNode編輯日志存儲在JournalNode集群中的目錄-->

<property>

<name>dfs.namenode.shared.edits.dir</name>

<value>qjournal://192.168.1.80:8485;192.168.1.81:8485;192.168.1.82:8485/mycluster</value>

</property>

<!-- 指定JournalNode集群存放日志的目錄-->

<property>

<name>dfs.journalnode.edits.dir</name>

<value>/usr/hadoop/hadoop-2.9.0/journalnode</value>

</property>

<!-- 配置NameNode失敗自動切換的方式-->

<property>

<name>dfs.client.failover.proxy.provider.mycluster</name>

<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>

</property>

<!-- 配置隔離機制-->

<property>

<name>dfs.ha.fencing.methods</name>

<value>sshfence</value>

</property>

<!-- 由于使用SSH,那么需要指定密鑰的位置-->

<property>

<name>dfs.ha.fencing.ssh.private-key-files</name>

<value>/root/.ssh/id_rsa</value>

</property>

<!-- 開啟失敗故障自動轉(zhuǎn)移-->

<property>

<name>dfs.ha.automatic-failover.enabled</name>

<value>true</value>

</property>

<!-- 配置Zookeeper地址-->

<property>

<name>ha.zookeeper.quorum</name>

<value>192.168.1.80:2181,192.168.1.81:2181,192.168.1.82:2181</value>

</property>

<!-- 文件在HDFS中的備份數(shù)(小于等于NameNode) -->

<property>

<name>dfs.replication</name>

<value>3</value>

</property>

<!-- 關(guān)閉HDFS的訪問權(quán)限 -->

<property>

<name>dfs.permissions.enabled</name>

<value>false</value>

</property>

<!-- 指定一個配置文件,使NameNode過濾配置文件中指定的host -->

<property>

<name>dfs.hosts.exclude</name>

<value>/usr/hadoop/hadoop-2.9.0/etc/hadoop/hdfs.exclude</value>

</property>

</configuration>

*指定NameNode的RPC通訊地址是為了接收FailoverControllerActive和FailoverControllerStandBy以及DataNode發(fā)送的心跳或详。

2.配置Hadoop公共屬性(core-site.xml)

<configuration>

<!-- Hadoop工作目錄,用于存放Hadoop運行時NameNode系羞、DataNode產(chǎn)生的數(shù)據(jù) -->

<property>

<name>hadoop.tmp.dir</name>

<value>/usr/hadoop/hadoop-2.9.0/data</value>

</property>

<!-- 默認(rèn)NameNode,使用NameService的名稱 -->

<property>

<name>fs.defaultFS</name>

<value>hdfs://mycluster</value>

</property>

<!-- 開啟Hadoop的回收站機制,當(dāng)刪除HDFS中的文件時,文件將會被移動到回收站(/usr/<username>/.Trash),在指定的時間過后再對其進行刪除,此機制可以防止文件被誤刪除 -->

<property>

<name>fs.trash.interval</name>

<!-- 單位是分鐘 -->

<value>1440</value>

</property>

</configuration>

*在HDFS HA集群中,StandBy的NameNode會對namespace進行checkpoint操作霸琴,因此就不需要在HA集群中運行SecondaryNameNode觉啊、CheckpintNode、BackupNode沈贝。

2.啟動HDFS HA高可用集群

1.分別啟動JournalNode

2.格式化第一個NameNode并啟動

3.第二個NameNode同步第一個NameNode的信息

4.啟動第二個NameNode

5.啟動Zookeeper集群

6.格式化Zookeeper

*當(dāng)格式化ZK后杠人,ZK中將會多了hadoop-ha節(jié)點。

7.重啟HDFS集群

當(dāng)HDFS HA集群啟動完畢后宋下,可以分別訪問NameNode管理頁面查看當(dāng)前NameNode的狀態(tài)

*可以查看到主機名為hadoop1的NamNode其狀態(tài)為StandBy嗡善,而主機名為hadoop2的NameNode其狀態(tài)為Active。

8.模擬NameNode宕機学歧,手動殺死進程罩引。

此時訪問NameNode管理頁面,可見主機名為hadoop1的NameNode其狀態(tài)從原本的StandBy切換成Active枝笨。

2.3 JAVA操作HDFS HA集群

*由于在HDFS HA集群中存在兩個NameNode袁铐,且服務(wù)端暴露的是NameService,因此在通過JAVA連接HDFS HA集群時需要使用Configuration實例進行相關(guān)的配置横浑。

/**

* @Auther: ZHUANGHAOTANG

* @Date: 2018/11/6 11:49

* @Description:

*/

public class HDFSUtils {

/**

* HDFS NamenNode URL

*/

private static final String NAMENODE_URL = "hdfs://mycluster:8020";

/**

* 配置項

*/

private static Configuration conf = null;

static {

conf = new Configuration();

//指定默認(rèn)連接的NameNode,使用NameService的名稱

conf.set("fs.defaultFS", "hdfs://mycluster");

//指定NameService的名稱

conf.set("dfs.nameservices", "mycluster");

//指定NameService下的NameNode列表

conf.set("dfs.ha.namenodes.mycluster", "nn1,nn2");

//分別指定NameNode的RPC通訊地址

conf.set("dfs.namenode.rpc-address.mycluster.nn1", "hadoop1:8020");

conf.set("dfs.namenode.rpc-address.mycluster.nn2", "hadoop2:8020");

//配置NameNode失敗自動切換的方式

conf.set("dfs.client.failover.proxy.provider.mycluster", "org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider");

}

/**

* 創(chuàng)建目錄

*/

public static void mkdir(String dir) throws Exception {

if (StringUtils.isBlank(dir)) {

throw new Exception("Parameter Is NULL");

}

dir = NAMENODE_URL + dir;

FileSystem fs = FileSystem.get(URI.create(NAMENODE_URL), conf);

if (!fs.exists(new Path(dir))) {

fs.mkdirs(new Path(dir));

}

fs.close();

}

/**

* 刪除目錄或文件

*/

public static void delete(String dir) throws Exception {

if (StringUtils.isBlank(dir)) {

throw new Exception("Parameter Is NULL");

}

dir = NAMENODE_URL + dir;

FileSystem fs = FileSystem.get(URI.create(NAMENODE_URL), conf);

fs.delete(new Path(dir), true);

fs.close();

}

/**

* 遍歷指定路徑下的目錄和文件

*/

public static List<String> listAll(String dir) throws Exception {

List<String> names = new ArrayList<>();

if (StringUtils.isBlank(dir)) {

throw new Exception("Parameter Is NULL");

}

dir = NAMENODE_URL + dir;

FileSystem fs = FileSystem.get(URI.create(dir), conf);

FileStatus[] files = fs.listStatus(new Path(dir));

for (int i = 0, len = files.length; i < len; i++) {

if (files[i].isFile()) { //文件

names.add(files[i].getPath().toString());

} else if (files[i].isDirectory()) { //目錄

names.add(files[i].getPath().toString());

} else if (files[i].isSymlink()) { //軟或硬鏈接

names.add(files[i].getPath().toString());

}

}

fs.close();

return names;

}

/**

* 上傳當(dāng)前服務(wù)器的文件到HDFS中

*/

public static void uploadLocalFileToHDFS(String localFile, String hdfsFile) throws Exception {

if (StringUtils.isBlank(localFile) || StringUtils.isBlank(hdfsFile)) {

throw new Exception("Parameter Is NULL");

}

hdfsFile = NAMENODE_URL + hdfsFile;

FileSystem fs = FileSystem.get(URI.create(NAMENODE_URL), conf);

Path src = new Path(localFile);

Path dst = new Path(hdfsFile);

fs.copyFromLocalFile(src, dst);

fs.close();

}

/**

* 通過流上傳文件

*/

public static void uploadFile(String hdfsPath, InputStream inputStream) throws Exception {

if (StringUtils.isBlank(hdfsPath)) {

throw new Exception("Parameter Is NULL");

}

hdfsPath = NAMENODE_URL + hdfsPath;

FileSystem fs = FileSystem.get(URI.create(NAMENODE_URL), conf);

FSDataOutputStream os = fs.create(new Path(hdfsPath));

BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

byte[] data = new byte[1024];

while (bufferedInputStream.read(data) != -1) {

os.write(data);

}

os.close();

fs.close();

}

/**

* 從HDFS中下載文件

*/

public static byte[] readFile(String hdfsFile) throws Exception {

if (StringUtils.isBlank(hdfsFile)) {

throw new Exception("Parameter Is NULL");

}

hdfsFile = NAMENODE_URL + hdfsFile;

FileSystem fs = FileSystem.get(URI.create(NAMENODE_URL), conf);

Path path = new Path(hdfsFile);

if (fs.exists(path)) {

FSDataInputStream is = fs.open(path);

FileStatus stat = fs.getFileStatus(path);

byte[] data = new byte[(int) stat.getLen()];

is.readFully(0, data);

is.close();

fs.close();

return data;

} else {

throw new Exception("File Not Found In HDFS");

}

}

}

2.YARN HA集群

2.1 模型

*啟動兩個ResourceManager后分別向Zookeeper注冊剔桨,通過Zookeeper管理他們的狀態(tài),一旦狀態(tài)為Active的ResourceManager無法正常提供服務(wù)徙融,Zookeeper將會立即將狀態(tài)為StandBy的ResourceManager切換為Active洒缀。

2.2 YARN HA高可用集群搭建

1.配置YARN(yarn-site.xml)

<configuration>

<!-- 配置Reduce取數(shù)據(jù)的方式是shuffle(隨機) -->

<property>

<name>yarn.nodemanager.aux-services</name>

<value>mapreduce_shuffle</value>

</property>

<!-- 開啟日志 -->

<property>

<name>yarn.log-aggregation-enable</name>

<value>true</value>

</property>

<!-- 設(shè)置日志的刪除時間 -1:禁用,單位為秒 -->

<property>

<name>yarn.log-aggregation。retain-seconds</name>

<value>864000</value>

</property>

<!-- 設(shè)置yarn的內(nèi)存大小,單位是MB -->

<property>

<name>yarn.nodemanager.resource.memory-mb</name>

<value>8192</value>

</property>

<!-- 設(shè)置yarn的CPU核數(shù) -->

<property>

<name>yarn.nodemanager.resource.cpu-vcores</name>

<value>8</value>

</property>

<!-- YARN HA配置 -->

<!-- 開啟yarn ha -->

<property>

<name>yarn.resourcemanager.ha.enabled</name>

<value>true</value>

</property>

<!-- 指定yarn ha的名稱 -->

<property>

<name>yarn.resourcemanager.cluster-id</name>

<value>cluster1</value>

</property>

<!-- 分別指定兩個ResourceManager的名稱 -->

<property>

<name>yarn.resourcemanager.ha.rm-ids</name>

<value>rm1,rm2</value>

</property>

<!-- 分別指定兩個ResourceManager的地址 -->

<property>

<name>yarn.resourcemanager.hostname.rm1</name>

<value>192.168.1.80</value>

</property>

<property>

<name>yarn.resourcemanager.hostname.rm2</name>

<value>192.168.1.81</value>

</property>

<!-- 分別指定兩個ResourceManager的Web訪問地址 -->

<property>

<name>yarn.resourcemanager.webapp.address.rm1</name>

<value>192.168.1.80:8088</value>

</property>

<property>

<name>yarn.resourcemanager.webapp.address.rm2</name>

<value>192.168.1.81:8088</value>

</property>

<!-- 配置使用的Zookeeper集群 -->

<property>

<name>yarn.resourcemanager.zk-address</name>

<value>192.168.1.80:2181,192.168.1.81:2181,192.168.1.82:2181</value>

</property>

<!-- ResourceManager Restart配置 -->

<!-- 啟用ResourceManager的restart功能,當(dāng)ResourceManager重啟時將會保存運行時信息到指定的位置,重啟成功后再進行讀取 -->

<property>

<name>yarn.resourcemanager.recovery.enabled</name>

<value>true</value>

</property>

<!-- ResourceManager Restart使用的存儲方式(實現(xiàn)類) -->

<property>

<name>yarn.resourcemanager.store.class</name>

<value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value>

</property>

<!-- ResourceManager重啟時數(shù)據(jù)保存在Zookeeper中的目錄 -->

<property>

<name>yarn.resourcemanager.zk-state-store.parent-path</name>

<value>/rmstore</value>

</property>

<!-- NodeManager Restart配置 -->

<!-- 啟用NodeManager的restart功能,當(dāng)NodeManager重啟時將會保存運行時信息到指定的位置,重啟成功后再進行讀取 -->

<property>

<name>yarn.nodemanager.recovery.enabled</name>

<value>true</value>

</property>

<!-- NodeManager重啟時數(shù)據(jù)保存在本地的目錄 -->

<property>

<name>yarn.nodemanager.recovery.dir</name>

<value>/usr/hadoop/hadoop-2.9.0/data/rsnodemanager</value>

</property>

<!-- 配置NodeManager的RPC通訊端口 -->

<property>

<name>yarn.nodemanager.address</name>

<value>0.0.0.0:45454</value>

</property>

</configuration>

ResourceManager Restart使用的存儲方式(實現(xiàn)類)

1.ResourceManager運行時的數(shù)據(jù)保存在ZK中:org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore

2.ResourceManager運行時的數(shù)據(jù)保存在HDFS中:org.apache.hadoop.yarn.server.resourcemanager.recovery.FileSystemRMStateStore

3.ResourceManager運行時的數(shù)據(jù)保存在本地:org.apache.hadoop.yarn.server.resourcemanager.recovery.LeveldbRMStateStore

*使用不同的存儲方式將需要額外的配置項,可參考官網(wǎng)树绩,http://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/ResourceManagerRestart.html

2.啟動YARN HA高可用集群

1.在ResourceManager所在節(jié)點中啟動YARN集群

2.手動啟動另一個ResourceManager

*當(dāng)啟動YARN HA集群后萨脑,可以分別訪問ResourceManager管理頁面,http://192.168.1.80:8088饺饭、http://192.168.1.81:8088渤早。

訪問狀態(tài)為StandBy的ResourceManager時,會將請求重定向到狀態(tài)為Active的ResourceManager的管理頁面瘫俊。

3.模擬ResourceManager宕機蛛芥,手動殺死進程

*Zookeeper在一定時間內(nèi)無法接收到狀態(tài)為Active的ResourceManager發(fā)送的心跳時,將會立即將狀態(tài)為StandBy的ResourceManager切換為Active军援。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市称勋,隨后出現(xiàn)的幾起案子胸哥,更是在濱河造成了極大的恐慌,老刑警劉巖赡鲜,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件空厌,死亡現(xiàn)場離奇詭異,居然都是意外死亡银酬,警方通過查閱死者的電腦和手機嘲更,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來揩瞪,“玉大人赋朦,你說我怎么就攤上這事±钇疲” “怎么了宠哄?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嗤攻。 經(jīng)常有香客問我毛嫉,道長,這世上最難降的妖魔是什么妇菱? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任承粤,我火速辦了婚禮,結(jié)果婚禮上闯团,老公的妹妹穿的比我還像新娘辛臊。我一直安慰自己,他們只是感情好房交,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布浪讳。 她就那樣靜靜地躺著,像睡著了一般涌萤。 火紅的嫁衣襯著肌膚如雪淹遵。 梳的紋絲不亂的頭發(fā)上口猜,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音透揣,去河邊找鬼济炎。 笑死,一個胖子當(dāng)著我的面吹牛辐真,可吹牛的內(nèi)容都是我干的须尚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼侍咱,長吁一口氣:“原來是場噩夢啊……” “哼耐床!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起楔脯,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤撩轰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后昧廷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堪嫂,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年木柬,在試婚紗的時候發(fā)現(xiàn)自己被綠了皆串。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡眉枕,死狀恐怖恶复,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情速挑,我是刑警寧澤寂玲,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站梗摇,受9級特大地震影響拓哟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伶授,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一断序、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧糜烹,春花似錦违诗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春阵苇,著一層夾襖步出監(jiān)牢的瞬間壁公,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工绅项, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留紊册,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓快耿,卻偏偏與公主長得像囊陡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子掀亥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 一撞反、系統(tǒng)參數(shù)配置優(yōu)化 1、系統(tǒng)內(nèi)核參數(shù)優(yōu)化配置 修改文件/etc/sysctl.conf搪花,添加如下配置遏片,然后執(zhí)行s...
    張偉科閱讀 3,752評論 0 14
  • 軟件環(huán)境: 主機配置: 一共m1, m2, m3這五部機, 每部主機的用戶名都為centos 前期準(zhǔn)備 1.配置主...
    咸魚翻身記閱讀 1,097評論 0 5
  • 1、在hadoop用戶的家目錄下創(chuàng)建一個data文件 指定hadoop/etc/hadoop下配置文件core-s...
    夙夜M閱讀 372評論 0 1
  • 之前的有點忘記了,這里在云筆記拿出來再玩玩.看不懂的可以留言 大家可以嘗試下Ambari來配置Hadoop的相關(guān)環(huán)...
    HT_Jonson閱讀 2,960評論 0 50
  • 秋已睡鳍侣,夜初眠。聲戾吵香酣吼拥。 餓來悠走滿歸歡倚聚,奇癢惹心煩。 身憔悴凿可,心思睡惑折,恨爾不該多嘴。 叫囂如此好良辰枯跑,拖起困乏身惨驶。
    琴詩音閱讀 196評論 0 3