【数据化】运维数据集中阶段性小结

2020-03-06 10:42:53 浏览数 (1)

注:

- “运维数据集中”属于知识体系的“工具-数据化-数据集中

- 本篇是结合行业内的一些技术方案分享、调研,以及对运维数据集中的理解,部份内容还要在实践中完善,属于阶段性学习小结

一、概述:

运维数据集中是运维数据化的前提,为了区别于业务大数据,这里的运维数据主要指日志类数据、指标类数据(监控事件、监控性能指标、其它指标数据),主要包括运维数据从收集、处理、归档、消费,整个过程需遵循运维场景驱动,系统可扩展性,分阶段实施,操作易用性,准入及落地数据规范化,为运维数据化提供高质量的数据支撑。主要从以下几点梳理小结:

- 技术方案,需包括技术架构,技术选型范围,技术规范目标是保证技术架构的高扩展性,支持持续分阶段实施的需求;

- 数据采集与处理,需包括数据源,数据类型,数据接入规范,数据传输,数据处理,目标是保证数据标准化,提高数据质量,为后续数据化做准备;

- 数据集中归档,需包括数据分类,数据存储方式,需设计好数据归档周期,目标是在数据保存期限与成本上面做到平衡,提高数据完整性,尽可能避免数据丢失情况;

- 数据消费,需包括数据检索可视化,数据分析可视化,目标是做好风险控制与可视化模板标准化,降低使用门槛,提高易用性;

另外,也将学习过程中遇到的一些问题,以及收集的资料作一些整理。

二、技术方案:


就目前行业内的技术方案来看,主要是以ELK为基础,并在采集上加上beat、syslog,并通过MQ或内存数据库做解耦,在最上面的可视化则加以grafana进行丰富。整体技术方案如下:

1)数据采集:在数据采集节点上通过安装beat代理,syslog,logstash三种方式(是否新增其它方式,待评估后确定),其中日志文件主要采用beat,系统日志或设备日志采用syslog,关系型数据库的数据指标数据采用logstash的JDBC插件(数据库文件采用beat)。 对于因系统性能要求不安装代理的操作系统,可以考虑采用两种方式:

- 非实时数据,自行生成文件到可安装beat代理的服务器上采集;

- 实时数据,提供数据传送接口,自行按接口要求发送数据到kafka消息队列;

当然,对于工具建设本身,则建议统一采用代理方式,便于管理,扩展性好,通常情况下稳定性更好。

2)数据传输:采集数据后,先传输到Kafka消息队列集群。Kafka topic按照集群类型来划分,如实时性要求高日志单独划分一个topic,系统中间件数据库划各划分一个topic,指标数据(主要是监控)对应放入同一个topic,其它指标放入同一个topic。将不同类型的数据划分不同的topic是为了让不同关注点的同事发挥专长处理不同的数据,比如DBA或中间件关注数据库错误日志与中间件日志的解码,业务运维的同事关注交易日志的解码。 3)数据处理:不同集群的logstash根据划分规则到kafka消息队列中拉取指定topic的消息数据,并推送到ES大集群。ES大集群统一规划不同的索引,不同的索引主要用于不同类型数据的划分,索引的拆分见下面规则。 logstash采用多套集群是由于数据解码是后继一项更为专业化工作,会涉及的修改数据解码配置、重启服务等工作,所以采用将不同类的数据分开,建议将运维数据划分不同的专业团队专项负责处理数据选取、解码,比如DBA负责数据库日志的解码、分析,系统管理员负责系统日志的解码、分析,同理应用管理员、中间件等专业角度人员。

ES主要是存储数据层,理论上可以不关注数据存储在哪个集群,且数据在一个大集群中便于后续数据集中处理。 4)可视化:可视化分为多个集群,一个处理重要业务数据或可视化,一个其它数据访问。由于开源的可视化工具在用户安全管控上支持不够,存在关键数据查阅,重要可视化报表被修改的情况,同理可视化可以根据需要增加集群。

三、技术选型范围


技术方案的选型主要目标是保证架构的扩展性,并支持分阶段建设,为了完成数据集中、初级的可视化(即数据检索,数据分析展示),在数据集中过程中需把控好数据标准化,为后续数据化,自动化,智能化做准备。可分解为:

  1. 核心技术:采用ELK
  2. 数据传输过程中加入kafka消息队列,集群由zookeeper管理;
  3. 负载均衡采用nginx;
  4. 可视化,仪表盘采用grafana,索检由kibana管理,个性化强的数据消费则建议另外调研可视化工具;
  5. 整个架构需作为一个业务系统等级纳入监控;

注:上述的技术造型范围比较成熟,网上可以找到很多案例借鉴,这里不做具体描述。

四、适用于哪些运维数据


1、日志类数据

-应用日志 -数据库日志 -中间件日志 -操作系统日志 -其它日志

2、指标类数据: -监控工具线性指标数据 -监控事件数据 -手工分析(如脚本生成)容量、性能数据

注:具体的数据采集设计,网上可以找到很多方案借鉴,主要是在数据采集节点上通过安装beat代理,syslog,logstash,API四种方式,这里不做具体描述。

五、运维数据消费场景


数据消费目标主要是易用性,引导具体专业人员发挥主观能动性,从消费方向看主要是自动化、分析、检索三方面。

1、自动化

运维自动化从某个角度看,可以分为"监、管、控"三部份,其中"监"指监控,"管"在我们公司主要是管理流程的工具化,比如ITIL,"控"是操作自动化,这一块最大,比如资源交付自动化,系统软件安装自动化,补丁安装自动化,批量脚本执行自动化,系统数据收集自动化,变更部署自动化,服务启停自动化等。

1)监控

列几点监控的"报警"提出数据消费场景:

-错误关键字报警

针对业务日志、系统日志、数据库日志等高危异常信息字段进行监控,当出现此类异常信息推动报警系统接口。异常字段的选择,可以考虑针对出现次数据的阀值进行匹配判断。

-性能报警

针对交易量、交易成功率、交易耗时的突降或突增进行监控,其中类似nginx等WEB日志,业务流水表(采用SQL),比较适合此类监控。

-异常操作报警

针对审计日志的异常操作情况,比如异常的时间,场合,通过异常的方式,实施了异常的操作行为进行监控报警。

-对监控指标数据进行二次分析报警

针对监控工具提供的指标类数据进行二次分析,发分析报警。

2)全局整体运行状况展示

数据归集后适合得到一个整体的运行状况的展示,故站在运行的整体感知方面,以下是以"全局、重要的指标"出发提出数据消费场景:

-核心指标度量

系统运行指标比较多,通过业务角度归纳出公司核心的业务指标,技术角度归纳出技术运行指标集中可视化。

-全局性运行状态可视化

以大屏为例,比较难将所有的运行状态均可视化,所以重点可以先以全局性运行状况为优先,先对这类信息做可视化。这个可视化可以从专业角度和管理角度出发,比如专业角度可以从基础设施、网络、系统的合局运行状态,由于业务系统越来越多,建议将最重要的精力关注在重要应用的运行状态的可视化上;管理角度可以从监控报警处理情况,重要系统故障处理情况,这块可视化可以考虑以KPI导向。

2、分析

运维分析在初级阶段,可以往技术角度投入,重点在性能,容量,审计三方面。通过对技术指标进行分析,其中性能分析主要目标是通过发现运行风险,推动运维优化,反推开发优化程序;容量分析主要目标是辅助容量管理;审计则是为了提高运维风险管控。

1)性能

-应用角度

应用性能角度,可以从交易量、成功率、单笔耗时三个维度进行分析,其中比较有效的方法是针对交易类系统的分析,数据来源可以从WEB日志,数据注交易流水(至少需有交易时间、交易成功状态)的数据。 在分析过程中,可以考虑抓典型的思路进行分析,比如采用TOP几的方式抓到性能瓶颈的应用,OS,开发项目组,业务功能、请求等方面。

-数据库角度

数据库DBA专业化往往比其它领域的管理员更俱有专注力和专业,且DBA面对的对象范围比较少,如能将DBA的专家经验转化为标准化的DBA日志分析,投入产出比可能比其它领域更高。 同理,系统管理、存储管理方面,也会比较应用运维更标准化,更易自动化。

2)容量

容量分析主要针对资源,比如CPU、内存、磁盘空间、带宽,对容量的分析首先需要有一个标准知道容量是否足够,其次是可供容量分析的指标数据,从存量数据来源看,监控数据满足此条件。

-基本的容量分析

这里基本的容量分析是指当前运行是否资源足够,由于监控事件是高于阀值的报警数据,对报警信息进行整合分析,可以比较快速发现己存在容量瓶颈的指标点,比如CPU,空间不足,再通过CMDB可以关联到具体的OS或业务系统。

-运行基线分析

大部份监控阀值是一个固定值,所以只以监控的报警数据会存在误报情况,如能针对系统指标运行情况计算一条运行基线,把运行指标偏离运行基线过多的数据进行容量判断可以更加准确。同样,运行基线也可以作为性能的分析基准。

3)审计

-可疑登录用户行为 做实时统计,统计任意时间范围内,10分钟内连续出现2个登录ip的用户。对于历史数据统计出最近一次登录用户,时间,上一次登录时间,时间相差3个月的列表,对于实时数据,进行实时告警,发现第一次登录或者3个月以前登录的用户进行告警。 -频繁登录的用户行为 登陆情况统计(新增) 筛选出从一年内登录次数10次/登陆频繁的用户(TOP 10)/登录次数超过N次 ,统计出用户登录次数top10。

-高危命令监测

监测开市期间执行命令"update、insert、delete、drop、alter、rm、chown、remove、stop、move、 mv 、kill、load、import、shutdown、configure terminal、system-view",并统计分析出top 10。

3、检索

-自定义数据检索

自定义检索是指针对ES中的运维数据进行实时自定义检索,这一块的可视化可以通过kibana实现数据检索。

-提前定制查询报表

针对重性比较高的运维数据检索,可以考虑有几个思方向: 1)提前制定好规则,定时对数据进行批量处理,生成目标数据,比如数据库关系数据库数据,EXCEL文本等; 2)在ES之上建立一个可视化数据定制工具(工具待找找)。

六、遇到的一些问题


1、乱码

背景:使用filebeat采集数据,并发送到logstash进行数据处理,会出现乱码问题

解决思路:

1)在logstash配置的输出中加入标准控制台输出:

output { elasticsearch { hosts => "logs.gf.com.cn:5045" manage_template => false index => es_hj document_type => "%{[@metadata][type]}" } stdout { codec => rubydebug } }

注:确定SCRT客户端的编码正确,但仍为乱码,说明非KIBANA或ES的问题,需要往前查,可能有两个问题:

  • logstash
  • filebeat

2)logstash编码问题:

在input和OUTPUT中加入:

codec => plain{

charset => "GB2312"

}

注:具体什么编码可以多试几个,比如GBK,UTF-8;比如以LINUX,可以通过file命令测试文件编码,测试后仍未解决,初步怀疑filebeat的编码问题

3)filebeat编码问题

  • 先尝试将filebeat的file插件打开,发现本地的文件内的编码也是乱码,初步定位为filebeat己为乱码;
  • 在filebeat.yml上发现有一个编码方式被注释掉,将该注释打开,并指定为GBK(也可能是其它)后,发现乱码问题解决。

2、grok指定语法:

建议是把所有的 grok 表达式统一写入到一个地方。然后用 filter/grok 的 patterns_dir 选项来指明。 如果你把 "message" 里所有的信息都 grok 到不同的字段了,数据实质上就相当于是重复存储了两份。所以你可以用 remove_field 参数来删除掉 message 字段,或者用 overwrite 参数来重写默认的 message 字段,只保留最重要的部 分。

filter { grok { patterns_dir => ["/path/to/your/own/patterns"] match => { "message" => "%{SYSLOGBASE} %{DATA:message}" } overwrite => ["message"] } }

例子:

http://www.run-debug.com/?p=633

https://www.elastic.co/blog/do-you-grok-grok

七、学习资料汇总


1、中文指南:

https://kibana.logstash.es/content/logstash/index.html

2、官网:

https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started.html

3、Filebeat入门

https://www.elastic.co/guide/en/beats/filebeat/5.5/filebeat-getting-started.html

注:Beats是建立轻量级的开源数据发货人的平台,可以为您在Logstash中丰富的各种操作数据,在Elasticsearch中进行搜索和分析,并在Kibana中进行可视化。无论您是对日志文件,基础设施指标,网络数据包或任何其他类型的数据感兴趣,Beats将作为您的数据保持打击的基础

目前有官方支持的三个子产品:packetbeat,topbeat,filebeat

  • packetbeat侧重于收集网络包
  • topbeat侧重于收集基础架构信息,如CPU,内存,进度相关信息
  • filebeat侧重于收集日志型信息

4、ELK整体介绍

Using elasticsearch, logstash & kibana to create realtime dashboards https://speakerdeck.com/elasticsearch/using-elasticsearch-logstash-and-kibana-tocreate- realtime-dashboards

5、可以⽤来学习和测试Grok匹配的⼀个网站 http://grokdebug.herokuapp.com/

6、官网插件介绍:

https://www.elastic.co/guide/en/logstash/current/working-with-plugins.html

7、grok预先定义类型

类型

说明

正则

字符串

由数字、大小写及特殊字符(._-)组成的字符串

USERNAME [a-zA-Z0-9._-]

由数字、大小写及特殊字符(._-)组成的字符串

USER %{USERNAME}

数字

整数,包括0和正负整数

INT (?:[ -]?(?:[0-9] ))

数字

十进制数字,包括整数和小数

BASE10NUM (?<![0-9. -])(?>[ -]?(?:(?:[0-9] (?:.[0-9] )?)|(?:.[0-9] )))

数字

十进制数字,包括整数和小数

NUMBER (?:%{BASE10NUM})

数字

十六进制数字

BASE16NUM (?<![0-9A-Fa-f])(?:[ -]?(?:0x)?(?:[0-9A-Fa-f] ))

数字

十六进制数字,小数

BASE16FLOAT b(?<![0-9A-Fa-f.])(?:[ -]?(?:0x)?(?:(?:[0-9A-Fa-f] (?:.[0-9A-Fa-f]*)?)|(?:.[0-9A-Fa-f] )))b

数字

POSINT b(?:[1-9][0-9]*)b

数字

NONNEGINT b(?:[0-9] )b

字符串

字符串,包括数字和大小写字母

WORD bw b

字符串

不带任何空格的字符串

NOTSPACE S

字符串

空格字符串

SPACE s*

字符串

任意字符串

DATA .*?

字符串

任意字符串

GREEDYDATA .*

字符串

带引号的字符串

QUOTEDSTRING (?>(?<!\)(?>"(?>\.|[^\"] ) "|""|(?>'(?>\.|[^\'] ) ')|''|(?>`(?>\.|[^\`] ) `)|``))

网络

UUID

UUID [A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}

网络

MAC

MAC (?:%{CISCOMAC}|%{WINDOWSMAC}|%{COMMONMAC})

网络

MAC

CISCOMAC (?:(?:[A-Fa-f0-9]{4}.){2}[A-Fa-f0-9]{4})

网络

MAC

WINDOWSMAC (?:(?:[A-Fa-f0-9]{2}-){5}[A-Fa-f0-9]{2})

网络

MAC

COMMONMAC (?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2})

网络

IPv6

IPV6 ((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%. )?

网络

ipv4

IPV4 (?<![0-9])(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))(?![0-9])

网络

ipv4或IPV6

IP (?:%{IPV6}|%{IPV4})

网络

HOSTNAME

HOSTNAME b(?:[0-9A-Za-z][0-9A-Za-z-]{0,62})(?:.(?:[0-9A-Za-z][0-9A-Za-z-]{0,62}))*(.?|b)

网络

HOSTNAME

HOST %{HOSTNAME}

网络

IP加主机名

IPORHOST (?:%{HOSTNAME}|%{IP})

网络

IP加主机名

HOSTPORT (?:%{IPORHOST=~/./}:%{POSINT})

操作系统

路径,Unix系统或者Windows系统里的路径格式

PATH (?:%{UNIXPATH}|%{WINPATH})

操作系统

UNXI PATh

UNIXPATH (?>/(?>[w_%!$@:.,-] |\.)*)

操作系统

TTY (?:/dev/(pts|tty([pq])?)(w )?/?(?:[0-9] ))

操作系统

WINDOWS PATh

WINPATH (?>[A-Za-z] :|\)(?:\[^\?*]*)

操作系统

URI协议比如:http、ftp等

URIPROTO [A-Za-z] ( [A-Za-z ] )?

操作系统

URI主机比如:www.stozen.net、10.0.0.1:22等

URIHOST %{IPORHOST}(?::%{POSINT:port})?

操作系统

URI路径比如://www.stozen.Net/abc/、/api.PHP等

URIPATH (?:/[A-Za-z0-9$. !*'(){},~:;=@#%_-]*)

操作系统

URI里的GET参数比如:?a=1&b=2&c=3

URIPARAM ?[A-Za-z0-9$. !*'|(){},~@#%&/=:;_?-[]]*

操作系统

URI路径 GET参数比如://www.stozen.net/abc/api.php?a=1&b=2&c=3

URIPATHPARAM %{URIPATH}(?:%{URIPARAM})?

操作系统

完整的URI比如:http://www.stozen.net/abc/api.php?a=1&b=2&c=3

URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?

时间

英文月份

MONTH b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)b

时间

数字月份

MONTHNUM (?:0?[1-9]|1[0-2])

时间

日期数字

MONTHDAY (?:(?:0[1-9])|(?:[12][0-9])|(?:3[01])|[1-9])

时间

星期几名称

DAY (?:Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)

时间

年份数字

YEAR (?>dd){1,2}

时间

小时数字

HOUR (?:2[0123]|[01]?[0-9])

时间

分钟数字

MINUTE (?:[0-5][0-9])

时间

秒数字

SECOND (?:(?:[0-5][0-9]|60)(?:[:.,][0-9] )?)

时间

比如:00:01:23

TIME (?!<[0-9])%{HOUR}:%{MINUTE}(?::%{SECOND})(?![0-9])

时间

美国日期格式,比如:10-15-1982、10/15/1982等

DATE_US %{MONTHNUM}[/-]%{MONTHDAY}[/-]%{YEAR}

时间

欧洲日期格式,比如:15-10-1982、15/10/1982、15.10.1982等

DATE_EU %{MONTHDAY}[./-]%{MONTHNUM}[./-]%{YEAR}

时间

ISO8601时间格式,比如: 10:23、-1023等

ISO8601_TIMEZONE (?:Z|[ -]%{HOUR}(?::?%{MINUTE}))

时间

ISO8601秒格式

ISO8601_SECOND (?:%{SECOND}|60)

时间

ISO8601时间戳格式,2016-07-03T00:34:06 08:00

TIMESTAMP_ISO8601 %{YEAR}-%{MONTHNUM}-%{MONTHDAY}[T ]%{HOUR}:?%{MINUTE}(?::?%{SECOND})?%{ISO8601_TIMEZONE}?

时间

日期,美国日期%{DATE_US}或者欧洲日期%{DATE_EU}

DATE %{DATE_US}|%{DATE_EU}

时间

完整日期 时间,比如:07-03-2016 00:34:06

DATESTAMP %{DATE}[- ]%{TIME}

时间

TZ (?:[PMCE][SD]T|UTC)

时间

DATESTAMP_RFC822 %{DAY} %{MONTH} %{MONTHDAY} %{YEAR} %{TIME} %{TZ}

时间

DATESTAMP_OTHER %{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{TZ} %{YEAR}

时间

SYSLOGTIMESTAMP %{MONTH} %{MONTHDAY} %{TIME}

PROG (?:[w._/%-] )

SYSLOGPROG %{PROG:program}(?:[%{POSINT:pid}])?

SYSLOGHOST %{IPORHOST}

SYSLOGFACILITY <%{NONNEGINT:facility}.%{NONNEGINT:priority}>

时间

03/Jul/2016:00:36:53 0800

HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT}

字符串

带引号的字符串

QS %{QUOTEDSTRING}

SYSLOGBASE %{SYSLOGTIMESTAMP:timestamp} (?:%{SYSLOGFACILITY} )?%{SYSLOGHOST:logsource} %{SYSLOGPROG}:

COMMONAPACHELOG %{IPORHOST:clientip} %{USER:ident} %{USER:auth} [%{HTTPDATE:timestamp}] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-)

COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent}

日志

比如:Alert、alert、ALERT、Error等

LOGLEVEL ([A-a]lert|ALERT|[T|t]race|TRACE|[D|d]ebug|DEBUG|[N|n]otice|NOTICE|[I|i]nfo|INFO|[W|w]arn?(?:ing)?|WARN?(?:ING)?|[E|e]rr?(?:or)?|ERR?(?:OR)?|[C|c]rit?(?:ical)?|CRIT?(?:ICAL)?|[F|f]atal|FATAL|[S|s]evere|SEVERE|EMERG(?:ENCY)?|[Ee]merg(?:ency)?)

0 人点赞