当今从纯网站技术上来说,因为开源模式的发展,现在建一个小网站已经很简单也很便宜,所以很多人都把创业方向定位在互联网应用。这些人里大多数不是很懂技术,或者不是那么精通,而网站开发维护方面的知识又很分散,学习成本太高,所以这篇文章将这些知识点结合起来,系统的来说,一个从日几千访问的小小网站,到日访问一两百万的小网站,中间可能会产生什么问题,以及怎么才能在一开始做足工作尽量避免这些问题。

对于不同的初期投资成本,技术路线的选择是不同的。这里假设网站刚刚只是一个构想,计划第一年服务器硬件带宽投入5万左右。对于这个资金额度,有很多种方案可选择,例如租用虚拟主机、租用单独服务器,或者流行的私有云,或者托管服务器。前两种选择,网站发展到一定规模时需迁移,那时再重做规划显然影响更大。服务器托管因为配置自主、能完全掌握控制权,所以有一定规模的网站基本都是这种模式。采用自己托管服务器的网站,一开始要注意以下几点。


一、开发语言


一般来说,技术人员(程序员)都是根据自己技术背景选择自己最熟悉的语言,不过不可能永远是一个人写程序,所以在语言的选择上还要是要费些心思。首先明确一点,无论用什么语言,最终代码质量是看管理,因此我们从前期开发成本分析。现在国内流行的适用于网站的语言,大概有java、php、.net、 python、ruby这五大阵营。python和ruby因为在国内流行的比较晚,现在人员还是相对难招一些。.net平台的人相对多,但是到后期需要解决性能问题时,对人员技能的要求比较高。剩余的java、php用人可以说是最多的。java和php无法从语言层面做比较,但对于初期,应用几乎都是靠前端支撑的网站来说,php入门简单、编写快速,优势相对大一点。至于后端例如行为分析、银行接口、异步消息处理等,等真正需要时,就要根据不同业务需求来选择不同语言了。

二、代码版本管理


稍微有点规模的网站就需要使用代码版本管理了。代码版本管理两点最大的好处,一是方便协同工作,二是有历史记录可查询比较。代码版本管理软件有很多,vss/cvs/svn/hg等,目前国内都比较流行,其中svn的普及度还是很高的。


向服务器部署代码,可以手工部署也可以自动部署。手工部署相对简单,一般可直接在服务器上svn update,或者找个新目录svn checkout,再把web root给ln -s过去。应用越复杂,部署越复杂,没有什么统一标准,只是别再用ftp上传那种形式,一是上传时文件引用不一致错误率增加,二是很容易出现开发人员的版本跟线上版本不一致,导致本来想改个错字结果变成回滚。如果有多台服务器还是建议自动部署,更换代码的机器从当前服务池中临时撤出,更新完毕后再重新加入。

三、服务器硬件


在各个机房里,靠一台服务器孤独支撑的网站数不清,但如果资金稍微充足,建议至少三台的标准配置,分别用作web处理、数据库、备份。web服务器至少要8G内存,双sata raid1,如果经济稍微宽松,或静态文件或图片多,则15k sas raid10。数据库至少16G内存,15k sas raid 10。备份服务器最好跟数据库服务器同等配置。硬件可以上整套品牌,也可以兼容机,也可以半品牌半组装,取决于经济能力。当然,这是典型的搭配,有些类型应用的性能瓶颈首先出现在web上,那种情况就要单独分析了。


web服务器可以既跑程序又当内存缓存,数据库服务器则只跑主数据库(假如是MySQL的话),备份服务器所承担就相对多一些,web配置、缓存配置、数据库配置都要跟前两台一致,这样WEB和数据库任意一台出问题,很容易就可以将备份服务器切换过去临时顶替,直到解决完问题。要注意,硬件是随时可能坏掉的,特别是硬盘,所以宁可WEB服务器跟数据库服务器放在一起,也一定不能省掉备份,备份一定要异机,并且有异步,电力故障、误操作都可能导致一台机器上的所有数据丢失。很多的开源备份方案可选择,最简单的就是rsync,写crontab里,定时同步。备份和切换,建议多做测试,选最安全最适合业务的,并且尽可能异地备份。

四、机房


三种机房尽量不要选:联通访问特别慢的电信机房、电信访问特别慢的联通机房、电信联通访问特别慢的移动或铁通机房。机房要尽可能多的实地参观,多测试,找个网络质量好,管理严格的机房。机房可以说是非常重要,直接关系到网站访问速度,网站访问速度直接关系到用户体验,访问速度很慢的网站,很难获得用户青睐。

五、架构


在大方向上,被熟知的架构是web负载均衡+数据库主从+缓存+分布式存储+队列。在一开始,按照可扩展的原则设计和编程就可以。只是要多考虑缓存失效时的雪崩效应、主从同步的数据一致性和时间差、队列的稳定性和失败后的重试策略、文件存储的效率和备份方式等等意外情况。缓存失效、数据库复制中断、队列写入错误、电源损坏,在实际运维中经常发生,如果不注意这些,出现问题时恢复期可能会超出预期很长时间。

六、服务器软件


操作系统Linux很流行。在没有专业运维人员的情况下,应倾向于择使用的人多、社区活跃、配置方便、升级方便的发行版,例如RH系列、 debian、ubuntu server等,硬件和操作系统要一起选择,看是否有适合的驱动,如果确定用某种商业软件或解决方案,也要提前知晓其对哪种操作系统支持最佳。web服务器方面,apache、nginx、lighttpd三大系列中,apache占有量还是最大,但是想把性能调教好还是需要很专业的,nginx和 lighttpd在不需要太多调整的情况下可以达到一个比较不错的性能。无论选择什么软件,除非改过这些软件或你的程序真的不兼容新版本,否则尽量版本越新越好,版本新,意味着新特性增多、BUG减少、性能增加。一个典型的php网站,基本上大多数人都没改过任何服务器软件源代码,绝大多数情况是能平稳的升级到新版本的。类似于jdk5到 jdk6,python2到python3这类变动比较大的升级还是比较少见的。看看ChangeLog,看看升级说明,结合自己情况评估测试一下,越早升级越好,升级的越晚,所花费的成本越高。对于软件包,尽量使用发行版内置的包管理工具,没有特殊要求时不建议自己编译,那样对将来运维不利。

七、数据库


几乎所有操作最后都要落到数据库身上,它又最难扩展(存储也挺难)。数据库常见的扩展方法有复制、分片,设计时要考虑到每种应用的数据如何复制、分片,当然这种考虑一般会推迟到技术设计时期。在初期进行数据库结构设计时,要根据不同的业务类型和增长量预期来考虑是否要分库、分区,并且尽量不要使用联合查询、不使用自增ID以方便分片。复制延时问题、主从数据库数据一致性问题,可以自己写或者用已有的运维工具进行检测。


用存储过程是比较难扩展的,这种情形多发生于传统C/S,特别是OA系统转换过来的开发人员。低成本网站不是一两台小型机跑一个数据库处理所有业务的模式,是机海作战。方便水平扩展比那点预分析时间和网络传输流量要重要的多的多。


另外,现在流行一种概念叫NoSQL,可以理解为非传统关系型数据库。实际应用中,网站有着越来越多的密集写操作、上亿的简单关系数据读取、热备等,这都不是传统关系数据库所擅长的,于是就产生了很多非关系型数据库,比如Redis/TC&TT/MongoDB/Memcachedb等,在测试中,这些几乎都达到了每秒至少一万次的写操作,内存型的甚至5万以上。在设计时,可根据业务特点和性能要求来选择是否使用这类数据库。例如 MongoDB,几句配置就可以组建一个复制+自动分片+failover的环境,文档化的存储也简化了传统设计库结构再开发的模式。但是当你决定采用一项技术时,一定要真正了解其优劣,例如可能你所选择的技术并不能支持你所需要的事务和数据一致性要求。

八、文件存储


存储的分布几乎跟数据库扩展一样困难,不过只有百万的PV的情况下,磁盘IO方面一般不会成大问题,一两台采用SATA做条带RAID的机器可以应付,反而是自己做异步备份比较复杂,因为小文件多。如果只有一台机器做存储,可以做简单的优化,例如放最小缩略图的分区和放中等缩略图的分区,根据平均大小调整一下块大小。存储要规划好目录结构,否则文件增多后维护起来复杂,也不利于扩展。同时还要考虑将来扩容,例如采用LVM,或者把文件根据不同规则散列到不同机器。磁盘IO繁重的情况下更容易出现故障,所以要做好备份,若发现有盘坏掉,要马上行动更换,很多人的硬盘都是坏了一块之后,接二连三的坏下去。


为了将来图片走cdn做准备,一开始最好就将图片的域名分开,且不用主域名。因为很多网站都将cookie设置到了.domain.ltd,如果图片也在这个域名下,很可能因为cookie而造成缓存失效,并且占多余流量,还可能因为浏览器并发线程限制造成访问缓慢。

九、程序


一定硬件条件下,应用能承载多少访问量,很大一部分也取决于程序如何写。程序写的不好,可能一万的访问都承载不了,写的好,可能一两台机器就能承担几百万PV。越是复杂、数据实时性要求越高的应用,优化起来越难,但对普通网站有一个统一的思路,就是尽量向前端优化、减少数据库操作、减少磁盘IO。向前端优化指的是,在不影响功能和体验的情况下,能在浏览器执行的不要在服务端执行,能在缓存服务器上直接返回的不要到应用服务器,程序能直接取得的结果不要到外部取得,本机内能取得的数据不要到远程取,内存能取到的不要到磁盘取,缓存中有的不要去数据库查询。减少数据库操作指减少更新次数、缓存结果减少查询次数、将数据库执行的操作尽可能的让你的程序完成(例如join查询),减少磁盘IO指尽量不使用文件系统作为缓存、减少读写文件次数等。程序优化永远要优化慢的部分,换语法是无法“优化”的。


然而编程时不应该把重点放在优化上,应该关注扩展性。当今的WEB应用,需求变化非常之快,适应多种需求的架构是不存在的,我们的扩展性就要把要点放在跟底层交互的架构上,例如持久化数据的存取规则、缓存的存取规则等,还有一些共用服务,例如用户信息等。先把不变的部分做完善,剩下的部分就很容易将精力放在业务逻辑上面了。


关于作者


刘志一,从1999年做个人网站开始一直专注于互联网,目前就职于一家垂直行业C2C网站,做产品和开发方面工作。

原文链接:http://www.infoq.com/cn/articles ... hnical-preparations

GO Contact Sync 用来同步 Outlook 与 Gmail 的联系人,让你的通讯录从此彻底生活在云端。

在 GO Contact Sync 出现之前,同步 Outlook 与 Gmail 联系人最好的办法是通过手机,先让手机与 Gmail 通过网络自动同步,再用手机连接 Outlook 同步。

几天前还有人在 Group 里询问 

GO Contact Sync 实现了 Outlook 与 Gmail 联系人的直接同步,让通讯录彻底云端。

同步很简单,填写你的 Gmail 用户名密码,随意填写 Sync Profile,如果你想同步多台电脑的Outlook,这个 Sync Profile 需要唯一。Sync Deletion 为是否同步删除联系人。

同步选项有以下几种:

Merge Prompt: 如果两边的联系人都更新了,弹出对话框让你选择覆盖哪一个。
Merge Outlook Wins: 如果两边的联系人都更新了, Google 联系人信息会被覆盖掉.
Merge Google Wins: 如果两边的联系人都更新了, Outlook 联系人信息会被覆盖掉.
Outlook To Google Only: 只把 Outlook 的联系人更新到 Google.
Google To Outlook Only: 只把 Google 的联系人更新到 Outlook. via

鱼漂注: 通过go contact,可以方便将WM与Android手机同步联系人.


What is Highcharts?

Highcharts is a charting library written in pure JavaScript, offering an easy way of adding interactive charts to your web site or web application. Highcharts currently supports line, spline, area, areaspline, column, bar, pie and scatter chart types.

Compatible

It works in all modern browsers including the iPhone/iPad and Internet Explorer from version 6. Standard browsers use SVG for the graphics rendering. In Internet Explorer graphics are drawn using VML.

Pure JavaScript

Highcharts is solely based on native browser technologies and doesn't require client side plugins like Flash or Java. Furthermore you don't need to install anything on your server. No PHP or ASP.NET. Highcharts needs only two JS files to run: The highcharts.js core and either the jQuery or the MooTools framework. One of these frameworks is most likely already in use in your web page.

第一部分
在Linux中可以将一部分内存mount为分区来使用,通常称之为RamDisk。
RamDisk有三种实现方式:
第一种就是传统意义上的,可以格式化,然后加载。
这在Linux内核2.0/2.2就已经支持,其不足之处是大小固定,之后不能改变。
为了能够使用Ramdisk,我们在编译内核时须将block device中的Ramdisk支持选上,它下面还有两个选项,一个是设定Ramdisk的大小,默认是4096k;另一个是initrd的支持。
如果对Ramdisk的支持已经编译进内核,我们就可以使用它了:
查看一下可用的RamDisk,使用ls /dev/ram*
首先创建一个目录,比如test,运行mkdir /mnt/test;
然后对/dev/ram0 创建文件系统,运行mke2fs /dev/ram0;
最后挂载 /dev/ram0,运行mount /dev/ram /mnt/test,就可以象对普通硬盘一样对它进行操作了。

更详细的内容可以参考: http://www.linuxfocus.org/ChineseGB/November1999/article124.html                                                 http://www.vanemery.com/Linux/Ramdisk/ramdisk.html

另两种则是内核2.4才支持的,通过Ramfs或者Tmpfs来实现:
它们不需经过格式化,用起来灵活,其大小随所需要的空间而增加或减少。

Ramfs顾名思义是内存文件系统,它它处于虚拟文件系统(VFS)层,而不像ramdisk那样基于虚拟在内存中的其他文件系统(ex2fs)。

因而,它无需格式化,可以创建多个,只要内存足够,在创建时可以指定其最大能使用的内存大小。
如果你的Linux已经将Ramfs编译进内核,你就可以很容易地使用Ramfs了。创建一个目录,加载Ramfs到该目录即可:
                  # mkdir  /testRam
                  # mount -t ramfs none /testRAM
缺省情况下,Ramfs被限制最多可使用内存大小的一半。可以通过maxsize(以kbyte为单位)选项来改变。
                  # mount -t ramfs none /testRAM -o maxsize=2000 (创建了一个限定最大使用内存为2M的ramdisk)


Tmpfs是一个虚拟内存文件系统,它不同于传统的用块设备形式来实现的Ramdisk,也不同于针对物理内存的Ramfs。
  Tmpfs可以使用物理内存,也可以使用交换分区。在Linux内核中,虚拟内存资源由物理内存(RAM)和交换分区组成,这些资源是由内核中的虚拟内存子系统来负责分配和管理。
  Tmpfs向虚拟内存子系统请求页来存储文件,它同Linux的其它请求页的部分一样,不知道分配给自己的页是在内存中还是在交换分区中。同Ramfs一样,其大小也不是固定的,而是随着所需要的空间而动态的增减。
   使用tmpfs,首先你编译内核时得选择"虚拟内存文件系统支持(Virtual memory filesystem support)" 。
  然后就可以加载tmpfs文件系统了:
            # mkdir -p /mnt/tmpfs
            # mount tmpfs /mnt/tmpfs -t tmpfs
  同样可以在加载时指定tmpfs文件系统大小的最大限制:
           # mount tmpfs /mnt/tmpfs -t tmpfs -o size=32m

使用df -aT命令可以看到有个/dev/shm目录,该目录的文件系统是tmpfs的,因此这个目录下的文件访问是非常快的,但是其大小可能不同机器都不一样,而且每次重启后文件也就丢失了。

第二部分
LINUX下我所接触过的内存文件系统有三个:

(1)ramdisk,使用前需要先创建文件系统,并且调整文件系统大小比较麻烦,需要修改内核引导参数并重新启动操作系统,在繁杂多变的应用与需要 7X24不间断运行的系统来说,并不是一个可以接受的选择.好处是自2.0版本起内核便支持(这也算好处?嗯,确实算,如果你手头真有这样的系统的话)

(2)ramfs,使用前不需要去创建文件系统了,直接通过mount的方式即可挂载上来用,需要的时候可以使用"mount -o remount,maxsize=..."这种方式来调整大小.

(3)tmpfs,同ramfs在表面上基本上一样啦,不同于ramfs针对"物理内存",tmpfs是在虚拟内存下分配空间的,也就是说tmpfs实例中存储的文件既可能存在于物理内存中,也可能存在于交换分区中,具体存在哪里,是由"虚拟内存子系统"来调度的.

纯性能角度讲,ramfs会在进程占用内存使用较多的情况下会优于tmpfs,在没有交换分区或进程占用内存较小而不发生swap行为的情况下,两者性能不会有差异(这个结论没有实测过,我"想当然"用猜的)

基本情况介绍完毕,下面介绍tmpfs的应用,(没办法,我手头机器内存不是那么宽裕...)

0,根据需要创建挂载目录,例:

mkdir -p $DIR_TMP;

1,挂载

mount my_tmpfs $DIR_TMP -t tmpfs -o size=512m

my_tmpfs
这个名字需要起,一个标识而已,会出现在df 的Filesytem一列,起个别致点的名字比较容易被自己写的其它监控脚本找到,如果非要起个none或tmpfs之类的名字的话...反正系统默认挂载的tmpfs都比较喜欢用这两个名字,好坏自己琢磨吧.

成功以后自己用df 看一下就知道了,写监控脚本时可以用"df -t tmpfs|grep ^my_tmpfs"来找到这一行.

2,调整

应用中如果感觉不合适,随时可以用mount命令调整

mount $DIR_TMP -o remount,size=1024m,nr_inodes=100k

nr_inodes为最大节点数,如果你的$DIR_TMP使用df命令查看明明有空间,却无法创建新文件(例如touch一个新文件),可能是文件节点用到上限了,可以用"df -i"命令来查看,如果是有空间但节点达到上限,就需要用nr_inodes来调整了.

其它可以调整的参数:
mode,uid,gid,
        uid和gid就不多说了,不知道的回家补基础,mode也不多说了,取值是3个八进制数字,表示许可权限,不知道这个的也回家...
其它参数,参见mount命令的man page之OPTIONS一节中"-o"参数的说明.

3,卸载

umount $DIR_TMP

4,其它

mount 命令的man page中对tmpfs提及不多,详细一些的文档,请参阅内核文档Documentation/filesystems/tmpfs.txt(内核源代码目录内)

Android开发环境搭建

[不指定 2010/12/19 23:15 | by ipaddr ]

Android这几年发展迅猛,系统越来越稳定,强大.同时,越来越多的手机都搭上这个免费开源的系统.
最近也在入手一台Android2.1手机, 所以也开始尝试一下Android的开发. 下面是一个简单的环境搭建过程.

Android的开发语言是Java(个人认为,主要是采用了Java的语法, 因为Google为Android编写了一个特别的虚拟机, 程序的框架和运行方式,也与传统Java不太一样). 开发环境主要是使用Eclipse+Android插件的形式。

由于需要安装Eclipse,所以PC端的Java环境是必不可少的,同时,为了编译和运行Android模拟器,Android SDK也需要安装。(来源:http://www.eit.name 转载请注明)

以下大体步骤:

1. 安装JDK
到Oracle的Java官方,直接下载Windows的安装包,运行安装程序,根据提示完成即可。
http://www.oracle.com/technetwork/java/javase/downloads/jdk6-jsp-136632.html

2. 安装Eclipse
到Eclipse的下载官方: http://www.eclipse.org/downloads/
下载“Eclipse Classic 3.6.1”,解压即可。双击eclipse文件夹中的eclipse.exe程序即可。

3. 安装Android SDK
到Android开发官方: http://developer.android.com/sdk 下载sdk installer,根据提示安装即可。
安装完后,运行SDK Manager下载和安装2.1, 2.2等sdk.
(可恶的是,这网站被墙了。)

4. 安装ADT (https://dl-ssl.google.com/android/eclipse/)
运行Eclipse,打开Help=>Install New Software...菜单, 添加一个新站点ADT,网址为:
https://dl-ssl.google.com/android/eclipse/
再从此站点下载和安装ADT。

5. Eclipse中配置sdk
在Eclipse中,打开Window=>Preferences,在左边找到Android,并配置好SDK的根目录。
打开Window=>Android SDK and AVD Manager,创建一个相应版本的AVD。

6. 开始第一个程序
在Eclipse,打开File=>New=>Project, 开始你的Android开发之旅吧。

C++和Java的语法对比手册

[不指定 2010/12/05 22:20 | by ipaddr ]

首先,两件大事-主函数和怎样编译,下面是它们的小差别:


主函数


C++


// 自由函数

int main( int argc, char* argv[])

{

    printf( "Hello, world" );

}

 

Java


// 每个函数必须是类成员;当java类运行时类中的主函数就会被调用

//(所以你可以为每个类写一个主函数--这样用于给类写单元测试时会很方便)

class HelloWorld

{

    public static void main(String args[])

    {

        System.out.println( "Hello, World" );

    }

}

 

编译


C++


    // 编译

    g++ foo.cc -o outfile

    // 运行

    ./outfile

    

Java


    // 编译在foo.java里的类成为<类名>.class

    javac foo.java 


    // 调用<类名>中的静态main函数

    java <classname>

    

注释


两种语言完全相同 (// 和 /* */ 都能工作)

类声明


大部分一样,除了Java不要求后面有个分号

C++


    class Bar {};

    

Java


    class Bar {}

    

方法声明


基本相同,除了Java中必须是类成员并且可能有public/private/protected前缀之外。

构造和析构


构造语法相同,Java没有析构的等价物。

静态成员函数和变量


方法声明方式相同,不过Java提供了static initialization blocks 来初始化静态变量(代替在源码文件中放定义):

class Foo 

{

    static private int x;

    // static initialization block

    { x = 5; }

}

对象声明


C++


    // 栈对象

    myClass x;


    // 堆对象

    myClass *x = new myClass;

    

Java


    // 总是分配在堆上(而且,构造总是要写括号)

    myClass x = new myClass();

    

引用vs.指针


C++


    // 引用是不可改的,使用指针能得到更大的弹性

    int bar = 7, qux = 6;

    int& foo = bar;

    

Java


    // 引用是可改的,它仅存放对象的地址。没有原生指针。

    myClass x;

    x.foo(); // 错误,x是空“指针”


    // 注意Java里总是使用 . 存取域

    

继承


C++


    class Foo : public Bar

    { ... };

    

Java


    class Foo extends Bar

    { ... }

    

保护级别


C++


    public:

        void foo();

        void bar();

    

Java


    public void foo();

    public void bar();

    

虚函数


C++


    virtual int foo(); 

    

Java


    // 函数默认就是虚函数;使用final防止被重载

    int foo();

    

抽象类


C++


    // 只要包含一个纯虚函数

    class Bar { public: virtual void foo() = 0; };

    

Java


    // 可以用语法直接定义

    abstract class Bar { public abstract void foo(); }


    // 或者指定为接口

    interface Bar { public void foo(); }


    // 然后,用一个类实现implement

它:

    class Chocolate implements Bar

    {

        public void foo() { /* do something */ }

    }

    

内存管理


大致相同--new 分配, 不过因为Java著名的垃圾回收机制所以没有delete 。

NULL vs. null


C++


    // 初始化指针为NULL

    int *x = NULL;

    

Java


    // 使用未初始化的引用会被计算机捕获,不过可以赋值为null指明引用为无效。

    myClass x = null;

    

布尔值


Java要长一点,你得写boolean来代替简短的bool。

C++


bool foo;

Java


boolean foo;

常量


C++


    const int x = 7;

    

Java


    final int x = 7;

    

Throw说明


首先, Java在编译时强制要求有throw说明-如果一个方法要抛出一个异常你必须先说明它

C++


int foo() throw (IOException)

Java


int foo() throws IOException

数组


C++


    int x[10];

    // 或者 

    int *x = new x[10];

    // 使用x然后收回内存

    delete[] x;

    

Java


    int[] x = new int[10];

    // 使用x,内存由垃圾回收机制回收

    

集合和迭代


C++


迭代器是类成员,一个范围起始于<container>.begin(), 终止于<container>.end(). 使用++操作前进,使用*存取。

    vector myVec;

    for ( vector<int>::iterator itr = myVec.begin();

          itr != myVec.end();

          ++itr )

    {

        cout << *itr;

    }

    

Java


迭代器仅仅是一个接口。一个范围起始于<collection>.iterator, 接着使用itr.hasNext()检查确认是否已到末尾。使用itr.next()取得下一个数据。

    ArrayList myArrayList = new ArrayList();

    Iterator itr = myArrayList.iterator();

    while ( itr.hasNext() )

    {

        System.out.println( itr.next() );

    }


    // 在Java 5中:

    ArrayList myArrayList = new ArrayList();

    for( Object o : myArrayList ) {

        System.out.println( o );

    }

分页: 1/1 第一页 1 最后页 [ 显示模式: 摘要 | 列表 ]