HDFS 简介
HDFS (Hadoop Distributed File System),Hadoop分布式文件系统。是Apache Hadoop核心组件之一。
优势
- 处理超大文件
- 处理非结构化数据
- 流式数据访问模式 ( 一次写入,多次读取 )
- 运行于廉价的商用机器集群
- 发生故障时能继续运行且不被用户察觉
局限性
- 不适合处理低延迟数据访问
- 无法高效存储大量小文件
- 不支持多用户写入及任意修改同一个文件
特性
- 高容错,可扩展性及可配置性强
- 跨平台性
- shell命令行接口
- Web界面
实现目标
- 检测和快速恢复硬件故障
- 流式数据访问
- 大规模数据集
- 简化一致性模型
- 移动计算代价比移动数据代价低
- 异构软硬件平台间的可移植性
HDFS 相关概念
1. 命名空间 Namespace
HDFS支持传统的层次型文件组织结构。用户可以创建目录,然后将文件保存在这些目录里。文件系统名字空间的层次结构和大多数现有的文件系统类似:用户可以创建、删除、移动或重命名文件。
2. 主从架构
HDFS采用master/slave架构。一般一个HDFS集群是有一个NameNode和一定数目的DataNode组成。NameNode是HDFS主节点, DataNode是HDFS从节点,两种角色各司其职,共同协调完成分布式的文件存储服务。
HDFS 核心设计
1. 分块存储
HDFS中的文件在物理上是分块存储(block)的,块的大小可以通过配置参数来规定(参数位于hdfs-default.xml中: dfs. blocksize)。默认大小是128M (1.x是64MB,2.x和3.x是128MB)。
根据集群参数(网络传输速率、块查询时间)对HDFS块大小进行设置
- HDFS文件块大小设置主要取决于磁盘传输速率。目前通过Namenode对HDFS元数据进行寻址的时间约为 10ms,即查找到目标block的时间为 10ms。
- 寻址时间为传输时间的1%时,则为最佳状态。因此,传输时间为10ms/0.01 = 1000ms = 1s
- 目前磁盘的传输速率普遍为 100MB/s。
因此,block大小为1s * 100MB/s = 100MB。
又因为电脑底层数据采用二进制存储,所以目前的block块官方大小设置为128MB。
同理,磁盘的传输速率普遍为 200MB/s 时,block块大小设置为1s * 200MB/s = 200MB => 256MB。
设置集群Block的备份数
方法一:配置文件hdfs-site.xml
<property>
<name>dfs.replication</name>
<value>x</value>
<!-- x为修改的冗余因子 -->
</property>
方法二:通过命令修改备份数
bin/hadoop fs -setrep -R x /
(x为修改的冗余因子)
注意:方法二可以改变整个HDFS里面的备份数,不需要重启HDFS系统,而方法一需要重启HDFS系统才能生效。
1.如何分块
- 当我们遇到一个大的数据时,HDFS会将它拆分成许多个小的数据块(Block),以便分布式存储。
- 数据块容量太小不合适,寻址困难。
- 数据块容量太大也不合适,容易产生外部碎片。
2.如何备份
分好块后,为了保证容错性(软硬件出错导致的数据丢失之类),HDFS需要对数据块进行备份(冗余存储)。Hadoop默认一个数据块备份3个副本,分布策略如下:
- 第一个副本:随机挑选一台磁盘不太满,CPU不太忙的节点。
- 第二个副本:放置在于第一个副本不同的机架的节点上。
- 第三个副本:与第二个副本相同机架的节点。
- 更多副本:随机节点。
3.检测数据(块)损坏流程
- 数据节点会周期性向主节点汇报自己的块列表信息。
- 汇报信息前,数据节点会通过验证校验码去筛选是否存在数据块的数据损失,如果发现数据块数据有所损失,则不上报。
- 主节点通过对比自己的块列表和数据节点上报的信息,知道数据块的损坏情况,并更新自己的块表。
2. 安全模式(SafeMode)
安全模式是Hadoop集群的一种保护模式.
NameNode在启动时会自动进入安全模式,也可以手动进入。
当系统处于安全模式时,会检查数据块的完整性。
hadoop dfsadmin -safemode leave //强制NameNode退出安全模式hadoop dfsadmin -safemode enter //进入安全模式
hadoop dfsadmin -safemode get //查看安全模式状态
hadoop dfsadmin -safemode wait //等待,一直到安全模式结束
3.负载均衡
机器与机器之间磁盘利用率不平衡是HDFS集群非常容易出现的情况,尤其是在DataNode节点出现故障或在现有的集群上增添新的DataNode的时候。
分析数据块分布和重新均衡DataNode上的数据分布的工具
HADOOP_HOME/bin 目录下D的 start-balancer.sh 脚本就是该任务的启动脚本:
$HADOOP_HOME /bin/start-balancer.sh -threshold
参数:
- -threshold
默认值为10,取值范围 0 ~ 100
理论上,该参数越小,整个集群越均衡
DataNode 分组
HDFS 根据阈值设定情况,将当前DataNode节点分组。
- over组
DataNode使用空间比例 > 集群平均使用空间比例 + 设定的阈值空间比 - Above组
集群平均使用空间比例 + 设定的阈值空间比 > DataNode使用空间比例 > 集群平均使用空间比例 - Below组
集群平均使用空间比例 - 设定的阈值空间比 < DataNode使用空间比例 < 集群平均使用空间比例 - Under组
DataNode使用空间比例 < 集群平均使用空间比例 - 设定的阈值空间比
4. 心跳机制
- hdfs是master/slave结构,master包括NameNode和ResourceManager,slave包括DataNode和NodeManager
- master启动时会开启一个IPC服务,等待slave连接
- slave启动后,会主动连接IPC服务,并且每隔3秒链接一次,这个时间是可以调整的,设置heartbeat,这个每隔一段时间连接一次的机制,称为心跳机制。slave通过心跳给master汇报自己信息,master通过心跳下达命令。
- NameNode通过心跳得知DataNode状态。ResourceManager通过心跳得知NodeManager状态
- 当master长时间没有收到slave信息时,就认为slave挂掉了。
检测数据节点损坏流程
- 数据节点通过向主节点发送心跳保持与其联系(3秒一次)。
- 如果主节点10分钟没有收到数据节点的心跳,则认为其已经意外丢失,主节点会开始复制他在其他数据节点上的备份数据块,重新备份。
5. 机架感知
大型Hadoop集群是以机架的形式来组织的
同一个机架上不同节点间的网络状况比不同机架之间的更为理想
默认情况下,Hadoop的机架感知是没有被启用的
启用机架感知功能,在NameNode所在机器的core-site.xml中配置一个选项:
<property>
<name>topology.script.file.name</name>
<value>/path/to/script</value>
<!-- value的值是一个脚本 -->
</property>
核心组件
1. NameNode 名称节点
也称主节点,作为中心服务器,主要负责接受客户端的读写请求。在主节点的统一调度下进行数据块的创建、删除和复制等操作,主节点的元数据信息会在启动后加载到内存里,以便快速查询。
名称节点负责管理分布式文件系统的命名空间(Namespace),保存了两个核心数据结构:FsImage和EditLog。
FsImage用于维护文件系统树以及文件树中所有文件和文件夹的元数据(文件自身属性:文件名称、权限、修改时间、文件大小、冗余因子、数据块大小,文件块位置映射信息:记录文件块和DataNode间的映射信息,即哪个块位于哪个节点上)。
EditLog操作日志文件记录了所有针对文件的创建、删除、重命名等操作。
2. DataNode 数据节点
数据节点是分布式文件系统HDFS的工作节点,负责文件的存储和读取,会根据客户端或者名称节点的调度来进行数据的存储和检索,并且向名称节点定期发送自己所存储的块的列表(必须向主节点汇报心跳、块列表和其他确认信息ack)。
每个数据节点中的数据会被保存到各自节点的本地Linux文件系统中。
3. SecondNameNode 第二名称节点
并不是主节点的备份文件,但是是备用主节点。
它主要为了帮助主节点分担压力,类似主节点的“助手”。它的主要工作是在主节点忙时,帮助主节点合并Edits ,减少NameNode流程如下图。
- secondarynamenode通知namenode切换edits文件
- secondarynamenode从namenode获得fsimage和edits(通过http)
- secondarynamenode将fsimage载入内存,然后开始合并edits
- secondarynamenode将新的fsimage发回给namenode
- namenode用新的fsimage替换旧的fsimage
什么时候checkpiont?
- fs.checkpoint.period 指定两次checkpoint的最大时间间隔,默认3600秒。
- fs.checkpoint.size 规定edits文件的最大值,一旦超过这个值则强制checkpoint,不管是否到达最大时间间隔。
HDFS Shell
hadoop dfs 只能操作HDFS文件系统(包括与Local FS间的操作)。(已经弃用—)
hdfs dfs 只能操作HDFS文件系统相关(包括与Local FS间的操作)。(市面上常用)
hadoop fs 可操作任意文件系统,不仅仅是HDFS文件系统。(官方推荐)
1. 创建一个或多个指定目录(文件夹)
hadoop fs -mkdir [-p] [paths]
path
为代创建目录-p
递归创建
2. 显示指定文件或文件夹的详细信息
hadoop fs -ls [-h] [-R] [path]
-h
人性化显示文件/文件夹大小size(如:不加-h
时显示47340,加后显示46.2KB)-R
递归显示所有文件的详细信息
3. 上传文件[localsrc]到指定目录[dst]
hadoop fs -put [-f] [-p] [loaclsrc] [dst]
-f
覆盖目标文件(如果文件已经存在)-p
保留访问和修改时间、所有的权限dst
目标文件系统(HDFS)路径
hadoop fs -moveFromLocal [loaclsrc] [dst]
- 和-put功能相同,只不过上传结束后,源数据会被删除(文件的移动)
4. 查看 HDFS 文件内容
hadoop fs -cat [dst]
- 读取指定文件全部内容,显示在标准输出控制台上
- 注意:对于大文件内容的读取,慎重
hadoop fs -head [file]
- 查看文件前1KB的内容
hadoop fs -tail [-f] [file]
- 查看文件最后1KB的内容
-f
可以可以动态查看文件中的追加内容
5. 下载HDFS文件到本地
hadoop fs -get [-f] [-p] [src] [loacldst]
- 下载文件到本地文件系统指定目录,locakdst必须是目录
-f
覆盖目标文件(如果文件已存在)-p
保留访问和修改时间、所有权限
hadoop fs -getmerge [-nl] [-skip-empty-file] [src] [localdst]
- 对[src]中所有文件进行合并,并写入本地系统的指定文件中
-nl
在每个文件末尾添加一个换行符skip-empty-file
跳过空文件
6. 将文件从源路径[sec]复制到目标路径[dst]
hadoop fs -cp [-f] [src] [dst]
-f
覆盖目标文件(如果文件已经存在)
7. 追加数据到HDFS文件中
hadoop fs -appendToFile [loaclsec] [dst]
- 将给定本地文件的内容追加到dst文件
dst
如果不存在,将创建该文件- 如果
localsrc
为-
,则从标准输入中读取
8. 查看磁盘空间
hadoop fs -df [-h] [path]
- 显示文件系统的容量,可以空间和已用空间
9. 查看HDFS文件使用空间量du
hadoop fs -du [-s] [-h] [path]
- 由于HDFS冗余存储,文件大小不等于占用磁盘空间
-s
表示显示指定路径文件长度的汇总摘要,而不是单个文件的摘要
10. HDFS数据移动操作
hadoop fs -mv [src] [dst]
- 移动文件到指定文件夹下
- 可以用该命令移动数据,实现重命名文件
11. HDFS文件副本个数
hadoop fs -setrep [-R] [-w] [rep] [path]
- 修改副本数(冗余因子)
-R
递归改变目录下所有文件的副本系数-w
客户端是否等待副本修改完毕
HDFS Java客户端操作
客户端核心类
Configuration 配置对象类,用于加载或设置参数属性
FileSystem文件系统基类。针对不同文件系统有不同的具体实现。该类封装了文件系统的相关操作方法。
模板
public class myHDFS {
private FileSystem fs = null;
private Configuration conf = null;
//构造函数
@Before
public void init() throws URISyntaxException, IOException, InterruptedException {
//入口网址
URI uri=new URI("hdfs://localhost:9000");
//配置文件
conf = new Configuration();
//conf.set("dfs.replication","1");
//给用户名称修改成自己的用户名
String user="buyunaihe";
//文件系统对象
fs = FileSystem.get(uri,conf,user);
}
//操作
@Test
public void solve() throws IOException{
}
//析构函数
@After
public void close() throws IOException {
if(fs != null)
fs.close();
}
}
创建文件夹
@Test
public void solve() throws IOException{
if(fs.exists(new Path("/hdfs"))
fs.mkdirs(new Path("/hdfs"))
}
//fs.exists(Path) 判断文件夹是否存在
上传与下载文件
@Test
public void put() throws IOException{
Path src = new Path("/Users/hdfs.txt");
Path dst = new Path("/");
//src是本地路径,dst是目标路径
fs.copyFromLocalFile(src,dst);
//第一个参数表示是否删除源文件,第二个参数表示是否覆盖目标文件(若目标文件已存在)
fs.copyFromLocalFile(false,true,src,dst);
}
@Test
public void get() throws IOException{
Path src = new Path("/Users/hdfs.txt");
Path dst = new Path("/");
//src是hdfs源路径,dst是本地目标路径
fs.copyToLocalFile(src,dst);
//第一个参数表示是否删除源文件,第四个参数表示是否使用RawLocalFileSystem作为本地文件系统
fs.copyToLocalFile(false,src,dst,true);
}
HDFS 的运行机制
HDFS中数据流的读写
1. RPC实现流程
一个典型的RPC框架主要包括以下几个部分:
- 通信模块:两个相互协作的通信模块实现请求--应答协议。
- 代理程序:客户端和服务器端均包含代理程序。
- 调度程序:调度程序接受来自通信模块的请求消息,并根据其中的标志选择一个代理程序处理。
一个RPC请求从发送到获取处理结果,所经历的步骤如下:
- 客户程序以本地方式调用系统产生的Stub程序
- 该Stub程序将函数调用信息按照网络通信模块的要求封装成消息包,并交给通信模块发送到远程服务器端
- 远程服务器端接收到此消息后,将此消息发送给相应的Stub程序
- Stub程序拆封消息,形成被调过程要求的形式,并调用对应的函数
- 被调用函数按照所获参数执行,并将结果返回给Stub程序
- Stub将此结果封装成消息,通过网络通信模块逐级地传送给客户程序
2. RPC Server实现模型
Server:RPC Server实现了一种抽象的RPC服务,同时提供Call队列。
RPC Server结构
- Server.Listener:RPC Server的监听者,用来接收RPC Client的连接请求和数据,其中将数据封装成CALL后PUSH到CALL队列中。
- Server.Handler:RPC Server的CALL处理者,和Server.Listener通过CALL队列交互。
- Server.Responder:RPC Server响应者,Server.Handler按照异步非阻塞的方式向RPC Client发送响应,如果有未发送出去的数据,交由Server.Responder来处理完成。
- Server.Connection:RPC Server数据的接收者。提供接收数据,解析数据包的功能。
- Server.Call:持有客户端的Call信息。
RPC Server的主要流程
RPC Server作为服务的提供者主要有两部分组成:接收Call调用和处理Call调用。
接收Call调用负责接收来自RPC Client的调用请求,编码成Call对象放入到Call队列中,这一过程有Server.Listener完成。具体步骤如下:
- Listener线程监听RPC Client发过来的数据
- 当有数据可以接收时,调用Connection的readAndProcess方法
- Connection边接受数据边处理数据,当接到一个完整的Call包,则构建一个Call对象,PUSH到Call 队列中,有Handler来处理Call队列中的所有Call处理完的Call调用负责处理Call队列中的每一个调用请求,由Handler线程来完成。
- Handler线程监听Call队列,如果Call队列非空,按FIFO规则从Call队列中取出Call
- 将Call交给RPC.Server来处理
- 借助JDK提供的Method,完成对目标方法的调用,目标方法由具体的业务逻辑实现
- 返回响应。Server.Handler按照异步非阻塞的方式向RPC Client发送响应,如果有未发送出的数据,则交由Server.Responder来完成。
3. 文件的读取
详细过程:
- 使用HDFS提供的客户端Client,向远程的Namenode发起RPC请求
- Namenode会视情况返回文件的部分或者全部block列表,对于每个block,Namenode都会返回有该block拷贝的DataNode地址;
- 客户端Client会选取离客户端最近的DataNode来读取block;如果客户端本身就是DataNode,那么将从本地直接获取数据;
- 读取完当前block的数据后,关闭当前的DataNode链接,并为读取下一个block寻找最佳的DataNode;
- 当读完列表block后,且文件读取还没有结束,客户端会继续向Namenode获取下一批的block列表;
- 读取完一个block都会进行checksum验证,如果读取datanode时出现错误,客户端会通知Namenode,然后再从下一个拥有该block拷贝的datanode继续读。
简略描述:
- 客户端向NameNode发起请求,需要获取名字为1的数据块。
- NameNode中保存了该数据块存储的位置,将DataNode的信息返回给客户端。
- 客户端就近的方式去从DataNode获取数据。
- 如果某个DataNode无法访问。
- 从另一个DataNode中去获取数据。
4. 文件的写入
详细描述:
- 使用HDFS提供的客户端Client,向远程的Namenode发起RPC请求
- Namenode会检查要创建的文件是否已经存在,创建者是否有权限进行操作,成功则会为文件创建一个记录,否则会让客户端抛出异常;
- 当客户端开始写入文件的时候,客户端会将文件切分成多个packets,并在内部以数据队列“data queue(数据队列)”的形式管理这些packets,并向Namenode申请blocks,获取用来存储replicas的合适的datanode列表,列表的大小根据Namenode中replication的设定而定;
- 开始以pipeline(管道)的形式将packet写入所有的replicas中。开发库把packet以流的方式写入第一个datanode,该datanode把该packet存储之后,再将其传递给在此pipeline中的下一个datanode,直到最后一个datanode,这种写数据的方式呈流水线的形式。
- 最后一个datanode成功存储之后会返回一个ack packet(确认队列),在pipeline里传递至客户端,在客户端的开发库内部维护着"ack queue",成功收到datanode返回的ack packet后会从"ack queue"移除相应的packet。
- 如果传输过程中,有某个datanode出现了故障,那么当前的pipeline会被关闭,出现故障的datanode会从当前的pipeline中移除,剩余的block会继续剩下的datanode中继续以pipeline的形式传输,同时Namenode会分配一个新的datanode,保持replicas设定的数量。
- 客户端完成数据的写入后,会对数据流调用close()方法,关闭数据流。
- 只要写入了dfs.replication.min的复本数(默认为1),写操作就会成功,并且这个块可以在集群中异步复制,直到达到其目标复本数(dfs.replication的默认值为3),因为namenode已经知道文件由哪些块组成,所以它在返回成功前只需要等待数据块进行最小量的复制。
简略描述:
- 客户端向NameNode发起请求,需要存储数据Data。
- 因为NameNode中是记录了所有DataNode的相关信息的,而数据最终要保存的地方就是DataNode,所以NameNode会返回可用的DataNode的信息给客户端。
- 将Data分为1和2这两个数据块。
- 客户端会将数据块存储到NameNode返回给他的DataNode1中去。
- 因为数据块需要存储多份,所以DataNode之间会相互传输来进行存储。
- DataNode存储完数据后,会反馈给NameNode,NameNode会将对应的DataNode的相关信息进行更新。
HDFS的HA机制
在Hadoop 2.0之前,在HDFS 集群中 NameNode 存在单点故障 (SPOF)。对于只有一个 NameNode 的集群,如果 NameNode 机器出现故障(比如宕机或是软件、硬件升级),那么整个集群将无法使用,直到 NameNode 重新启动。
HDFS 的 HA 功能通过配置 Active/Standby 两个 NameNodes 实现在集群中对NameNode 的热备份来解决上述问题。如果出现故障,如机器崩溃或机器需要升级维护,这时可通过此种方式将>热备来解决上述问题。如果出现故障,如机器崩溃或机器需要升级维护,这时可通过此种方式将 NameNode 很快的切换到另外一台机器。
HA流程
- 在一个典型的HDFS(HA) 集群中,使用两台单独的机器配置为NameNodes 。在任何时间点,确保NameNodes 中只有一个处于Active 状态,其他的处在Standby 状态。其中ActiveNameNode 负责集群中的所有客户端操作,StandbyNameNode 仅仅充当备机,保证一旦ActiveNameNode 出现问题能够快速切换。
- 为了能够实时同步Active和Standby两个NameNode的元数据信息(实际上editlog),需提供一个共享存储系统,可以是NFS、QJM(Quorum Journal Manager)或者Bookeeper,Active Namenode将数据写入共享存储系统,而Standby监听该系统,一旦发现有新数据写入,则读取这些数据,并加载到自己内存中,以保证自己内存状态与Active NameNode保持基本一致,如此这般,在紧急情况下standby便可快速切为active namenode。
- 为了实现快速切换,Standby 节点获取集群的最新文件块信息也是很有必要的。为了实现这一目标,DataNode 需要配置NameNodes 的位置,并同时给他们发送文件块信息以及心跳检测。
注意:Secondary NameNode。它不是HA,它只是阶段性的合并edits和fsimage,以缩短集群启动的时间。当NameNode失效的时候,Secondary NN并无法立刻提供服务,Secondary NN甚至无法保证数据完整性:如果NN数据丢失的话,在上一次合并后的文件系统的改动会丢失。
HDFS的Federation机制
在Hadoop 2.0之前,HDFS的单NameNode设计带来诸多问题,包括单点故障、内存受限,制约集群扩展性和缺乏隔离机制(不同业务使用同一个NameNode导致业务相互影响)等,为了解决这些问题,除了用基于共享存储的HA解决方案我们还可以用HDFS的Federation机制来解决这个问题。
什么是Federation机制
HDFS Federation是指HDFS集群可同时存在多个NameNode,这些NameNode分别管理一部分数据,且共享所有DataNode的存储资源。
可解决单NameNode存在的问题
- HDFS集群扩展性。多个NameNode分管一部分目录,使得一个集群可以扩展到更多节点,不再像1.0中那样由于内存的限制制约文件存储数目。
- 性能更高效。多个NameNode管理不同的数据,且同时对外提供服务,将为用户提供更高的读写吞吐率。
- 良好的隔离性。用户可根据需要将不同业务数据交由不同NameNode管理,这样不同业务之间影响很小。
需要注意的,HDFS Federation并不能解决单点故障问题,也就是说,每个NameNode都存在在单点故障问题,你需要为每个namenode部署一个backup namenode以应对NameNode挂掉对业务产生的影响。
Federation架构
- 为了水平扩展namenode,federation使用了多个独立的namenode/namespace。这些namenode之间是联合的,也就是说,他们之间相互独立且不需要互相协调,各自分工,管理自己的区域。分布式的datanode被用作通用的数据块存储存储设备。每个datanode要向集群中所有的namenode注册,且周期性地向所有namenode发送心跳和块报告,并执行来自所有namenode的命令。
- 一个block pool由属于同一个namespace的数据块组成,每个datanode可能会存储集群中所有block pool的数据块。
- 每个block pool内部自治,也就是说各自管理各自的block,不会与其他block pool交流。一个namenode挂掉了,不会影响其他namenode。
- 某个namenode上的namespace和它对应的block pool一起被称为namespace volume。它是管理的基本单位。当一个namenode/nodespace被删除后,其所有datanode上对应的block pool也会被删除。当集群升级时,每个namespace volume作为一个基本单元进行升级。
多命名空间管理
Federation中存在多个命名空间,如何划分和管理这些命名空间非常关键。在Federation中并采用“文件名hash”的方法,因为该方法的locality非常差,比如:查看某个目录下面的文件,如果采用文件名hash的方法存放文件,则这些文件可能被放到不同namespace中,HDFS需要访问所有namespace,代价过大。为了方便管理多个命名空间,HDFS Federation采用了经典的Client Side Mount Table。
如图所示,下面四个深色三角形代表一个独立的命名空间,上方浅色的三角形代表从客户角度去访问的子命名空间。各个深色的命名空间Mount到浅色的表中,客户可以访问不同的挂载点来访问不同的命名空间,这就如同在Linux系统中访问不同挂载点一样。这就是HDFS Federation中命名空间管理的基本原理:将各个命名空间挂载到全局mount-table中,就可以做将数据到全局共享;同样的命名空间挂载到个人的mount-table中,这就成为应用程序可见的命名空间视图。
Federation的不足
HDFS Federation并没有完全解决单点故障问题。虽然namenode/namespace存在多个,但是从单个namenode/namespace看,仍然存在单点故障:如果某个namenode挂掉了,其管理的相应的文件便不可以访问。Federation中每个namenode仍然像之前HDFS上实现一样,配有一个secondary namenode,以便主namenode挂掉一下,用于还原元数据信息。
Comments | NOTHING