美文网首页
HBase 基础原理

HBase 基础原理

作者: 十维_fun | 来源:发表于2022-05-17 14:49 被阅读0次

    概念:

    数据模型:表由行和列组成。表格的“单元格”(cell)由行和列的坐标交叉决定,是有版本的。默认情况下,版本号是自动分配的

    Hbase内部保留名为hbase:meta的特殊目录表(catalog table)。他们维护着当前集群上所有区域的列表、状态和位置。

    NoSQL的基础

    1、not only sql

    2、回顾:关系型数据库:Oracle、MySQL等 ——> 面向行

    适合insert,update,delete

    不适合查询

    3、常见的NoSQL数据库

    基于Key——Value模型:Redis(基于内存)

    前身:MEMCached(不足:不支持持久化)

    面向列的模型:Hbase、Cassandra

    适合select(查询)

    基于文档型:MongoDB

    文档:BSON文档(json的二进制)

    1.Hbase的表结构

    BigTable:大表

    思想:通过牺牲存储的空间,来换取性能

    把所有的数据存入一张表,完全违背了关系型数据库范式的要求

    HBase 基于HDFS之上的NoSQL数据库、列式数据库

    表 ——> 目录

    数据 ——> 文件

    2.Hbase 在ZK中保存数据

    配置信息、Hbase集群结构信息

    表的元信息

    实现Hbase的HA(high avaibility)高可用性

    3.Hbase配置信息

    4.操作Hbase

    4.1 Web Console网页:端口-16010

    4.2 命令行

    hbase shell

    1)创建表

    create 'students','info','grade'

    创建表'students'相当于创建一个目录

    2)查看表结构

    desc 'students'

    describe 'students'

    hbase:003:0> desc 'students'

    Table students is ENABLED                                          

    students                                                            

    COLUMN FAMILIES DESCRIPTION                                  

    {NAME => 'grade', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '1', KEEP_DELETED_CE

    SIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICATION_SCOPE => '0'}          

    {NAME => 'info', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '1', KEEP_DELETED_CEL

    IONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICATION_SCOPE => '0'}            

    2 row(s)

    Quota is disabled

    Took 0.0337 seconds

    3)插入数据

    put 'students','stu001','info:name','Tom'

    put 'students','stu001','info:age','24'

    put 'students','stu001','info:gender','Male'

    put 'students','stu001','grade:math','80'

    put 'students','stu002','info:name','Mike'

    4)查询数据

    scan 相当于:select * from students

    scan '表名'

    get  相当于 select * from students where rowkey=?

    get '表名','行健'

    get 'student','stu001'

    5)关闭和开启表

    disable '表名'

    enable '表名'

    6)删除表

    disable '表名'

    drop '表名'

    7)清空表中的数据

    truncate '表名'

    truncate本质:先删除表,再重建

    日志:

    Truncating 'students' table (it may take a while):

    - Disabling table...

    - Truncating table...

    0 row(s) in 4.0840 seconds

    5.数据保存的过程

    注意:region分裂

    HBase 架构

    ZooKeeper 作为分布式的协调。RegionServer也会把自己的信息写到ZooKeeper中。

    HDFS是Hbase运行的底层文件系统

    RegionServer,数据节点,存储数据

    Master,RegionServer要实时向Master报告信息。Master知道全局的RegionServer运行情况,可以控制RegionServer的故障转移和Region的切分。

    架构细化

    HMaster 是Master Server的实现,负责监控集群中的Region Server实例,同时是所有metadata改变的接口,在集群中,通常运行在NameNode上

    HMasterInterface暴露的接口,Table(createTable,modifyTable,removeTable,enable,disable),ColumnFamily(addColumn,modifyColumn,removeColumn),Region(move,assign,unassign)

    Master运行的后台线程:LoadBalancer线程,控制region来平衡集群的负载。CatalogJanitor线程,周期性的检查hbase:meta表。

    HRegionServer 是RegionServer的实现,服务和管理Regions,集群中RegionServer运行在DataNode

    HRegionInterface暴露接口:Data(get,put,delete,next,etc.),Region(splitRegion,compactRegion,etc.)

    RegionServer后台线程:CompactSplitThread,MajorCompactionChecker,MenStoreFlusher,LogRoller

    Regions,代表table,Region有多个Store(列簇),Store有一个Memstore和多个StoreFiles(HFiles),StoreFiles的底层是Block。

    Hbase 存储设计

    在Hbase中,表被分割成多个更小的块然后分散的存储在不同的服务器上,这些小块叫做Regions,存放Regions的地方叫做RegionServer。Master进程负责处理不同的RegionServer和Region。HRegionServer除了包含一些HRegions之外,还处理两种类型的文件用于数据存储

    HLog,预写日志文件,也叫做WAL(write-ahead log)

    HFile 真实的数据存储文件

    Hlog

    MasterProcWAL:HMaster记录管理操作,比如解决冲突的服务器,表创建和其他DDLs等操作到它的WAL文件中,这个WALs存储在MasterProcWALs目录下,它不像RegionServer的WALs,Hmaster的WAL也支持弹性操作,就是如果Master服务器挂了,其他的Master接管的时候继续操作这个文件。

    WAL记录所有的Hbase数据改变,如果一个RegionServer在MemStore进行Flush的时候挂掉了,WAL可以保证数据的改变被应用到。如果写WAL失败了,那么修改数据的完整操作就是失败的。

    通常情况,每个RegionServer只有一个WAL实例。在2.0之前,WAL的实现叫做HLog

    WAL位于/hbase/WALs/ 目录下

    MultiWAL:如果每个RegionServer只有一个WAL,由于HDFS必须是连续的,导致必须写WAL连续的,然后出现性能问题。MultiWAL可以让RegionServer同时写多个WAL并行的,通过HDFS底层的多管道,最终提升总的吞吐量,但是不会提升单个Region的吞吐量。

    WAL的配置

    // 启用multiwal

    <property>

      <name>hbase.wal.provider</name>

      <value>multiwal</value>

    </property>

    HFile

    HFile 是Hbase在HDFS中存储数据的格式,它包含多层的索引,在Hbase检索数据的时候就不用完全的加载整个文件。索引的大小(keys的大小,数据量的大小)影响block的大小,在大数据的情况下,block的大小设置为每个RegionServer 1GB也是常见的。

    HFile生成方式

    起初,HFile中并没有任何Block,数据还存在于MemStore中。

    Flush发生时,创建HFile Writer,第一个空的Data Block出现,初始化后的Data Block中为Header部分预留了空间,Header部分用来存放一个Data Block的元数据信息。而后,位于MemStore中的KeyValues被一个个append到位于内存中的第一个Data Block中:

    :如果配置了Data Block Encoding,则会在append KeyValue的时候进行同步编码,编码后的数据不再是单纯的KeyValue模式。Data Block Encoding是为了降低KeyValue结构性膨胀而提供的内部编码机制。

    读写简流程

    HBase Region管理

    HFile 合并

    每个RegionServer包含多个Region,而每个Region又对应多个Store,每一个Store对应表中一个列簇的存储,且每个Store由一个MemStore和多个StoreFile文件组成。

    StoreFile在底层文件系统中由HFile实现,也可以把Store看作由一个MemStore和多个HFile文件组成。MemStore充当内存写缓存,默认大小64MB,当MemStore超过阈值时,MemStore中的数据会刷新到一个新的HFile文件中来持久化存储。

    久而久之,每个Store中的HFile文件会越来越多,I/O操作的速度也随之变慢,读写也会延时,导致慢操作。因此,需要对HFile文件进行合并,让文件更紧凑,让系统更有效率。

    HFile的合并分为两种类型,分别是Minior合并和Major合并。这两种合并都发生在Store内部,不是Region的合并,如下图所示。

    Minor合并

    Minor合并是把多个小HFile合并生成一个大的HFile。

    执行合并时,Hbase读出已有的多个HFile的内容,把记录写入一个新文件中。然后把新文件设置为激活状态,并标记旧文件为删除。

    在Minor合并中,这些标记为删除的旧文件是没有被移除的,仍然会出现在HFile中,只有在进行Major合并时才会移除这些旧文件。对需要进行Minor合并的文件的选择是触发式的,当达到触发条件才会进行Minor合并,而触发条件有很多,例如,在将MemStore的数据刷新到HFile时会申请对Store下符合条件的HFile进行合并,或者定期对Store内的HFile进行合并。

    另外,对选择合并的HFile也是有条件的,如下表所示。

    参数名配置项默认值备注

    minFileToCompacthbase.hstore.compaction.min3至少需要三个满足条件的 HFile 才启动合并

    minFileToCompacthbase.hstore.compaction.max10一次合并最多选择 10 个

    maxCompactSizehbase.hstore.compaction.max.sizeLong.MAX_VALUEHFile 大于此值时被排除合并,避免对大文件的合并

    minCompactSizehbase.hstore.compaction.min.sizeMemStoreFlushSizeHFile 小于 MemStore 的默认值时被加入合并队列

    在执行Minor合并时,系统会根据上述配置参数选择合适的HFile进行合并。Minor合并对HBase的性能是有轻微影响的,因此,合并的HFile数量是有限的,默认最多为10个。

    Major合并

    Major合并针对的是给定Region的一个列簇的所有HFile,如图1所示。它将Store中的所有HFile合并成一个大文件,有时也会对整个表的同一列簇的HFile进行合并,这是一个耗时和耗费资源的操作,会影响集群性能。

    一般情况下都是做Minor合并,不少集群是禁止Major合并的,只有在集群负载较小时进行手动Major合并操作,或者配置Major合并周期,默认为7天。另外,Major合并时会清理Minor合并中被标记为删除的HFile。

    Region的相关知识

    Region在HBase中的角色

    Table                    (HBase表)

        Region              (Region)

            Store            (每个Region的每个列簇独立存储)

                MemStore    (MemStore每个Store有一个,用于在内存中保存数据)

                StoreFile    (StoreFile对应于Store,是具体存储在磁盘的文件)

                    Block    (Blocks是HDFS上的存储单元)

    Region的管理

    一般来说对于每个Region Server,官方推荐最好是控制Region的数量在20-200个、大小在5-20Gb左右。

    为什么要控制Region的数量呢?

    1、默认MemStore需要2MB的空间用来存储数据,如果一台机器上有1000个Region,每个有两个列簇,那就需要3.9GB的数据。

    2、如果同时以某个相同的频率更新所有的Region,当同时进行数据持久化的时候也会有问题

    3、Master对于维护大量的Region有很大的性能问题,因为在平衡Region的时候,在ZK中的操作都是同步的。

    4、Region Server需要维护Region的索引信息

    Region Server如何管理Region

    启动

    1、HMaster 创建 AssignmentManager

    2、AssignmentManager查看当前的Region分配信息

    3、满足条件后,通过LoadBalancerFactory创建LoadBalancer,1.0后的版本默认是StochasticLoadBalancer

    Region的状态机

    HBase中每个Region自己维护其在hbase:meta表中的信息。

    状态机中包含下面几种状态:

    offline:region离线没有开启

    opening:region正在被打开

    open:region正在打开,并且region server通知了master

    failed_open:region server打开失败

    closing:region正在被关闭

    closed:region server正在关闭,并且已经通知了master

    failed_close:region server关闭失败了

    splitting:region server通知master,region正在被切分

    split:region server通知master,region已经被切分完了

    spliting_new:region是切分过程中新建的文件

    merging:region server通知master,region正在合并

    merged:region server 通知master,region合并完了

    merging_new:region是合并新建出来的

    不同的颜色是不同含义:

    棕色:离线状态,属于一种短暂的瞬间状态(比如关闭后开启的中间状态)、停止状态或者初始化的时候的状态

    绿色:正常的状态,可以支持请求访问

    蓝色:短暂的状态

    红色:失败

    黄色:合并或者切分的状态

    灰色:刚开始的状态

    各个序号代表不同的操作场景:

    1、Master向Region server发起region从offline到openning的状态请求,region server如果没有收到,master会尝试重试几次。RegionServer接收到请求后,region状态变为opening

    2、如果Master发起的open请求超过次数,那么无论RegionServer是否已经打开region,master都会命令RegionServer关闭文件,状态变为closing

    3、当RegionServer打开region后,会尝试通知Master,让它把region状态修改为open状态

    4、如果RegionServer打开失败,会尝试通知Master,让它把region状态修改为closed,并且尝试去其他的RegionServer打开region

    5、如果Master尝试几次后,都没有打开region,就会把状态变更为failed_open

    6、master通知RegionServer关闭region,如果没有反应,会重试

    7、如果RegionServer没有在线,会抛出异常。然后region的状态会变成closing

    8、如果RegionServer在线,但是好几次都没响应,就会更新状态为failed_closed。并且把region分配给其他的server

    10、再分配之前,master会先把region从closed状态变为offline

    11、如果RegionServer正在切分region,会通知master。master把region状态由open变为splitting,并且新增两个region的信息,这两个region都是splitting_new状态

    12、如果region切分成功,当前的region状态从splitting变成split;新增的两个region状态从splitting_new变成open

    13、如果切分失败,状态从splitting回到open,两个region也从splitting_new变成offline

    14、如果RegionServer想要合并两个region,那么也会先通知master。master把两个region从open变为merging,然后增加一个新的region,状态为merging_new

    15、如果合并成功,旧的region状态从merging变为merged,新的region从merging_new变为open

    16、如果合并失败,region的状态从merging变回open,新建的一个region状态又变成offline

    17、如果管理员通过hbase shell操作分配region,master会尝试把失败的状态变成close

    Region的数据本地性

    数据本地性通过来自于hdfs client和hdfs block存储的节点差异性,针对数据备份来说,会按照下面的机制进行:

    1、第一个备份会优先选择本地node节点上

    2、第二个备份会随机选择一个不同的机架

    3、第三个备份会在第二个备份所在的机架上,在随机选择一个节点

    4、如果还有其他的备份节点,就在集群中随机选择了

    这样HBase在刷新或者压缩时,可以体现数据的本地性。如果一个RegionServer出现故障,那么就没有数据本地性可言了,因为它的备份都在其他的节点上。

    Region拆分

    Region拆分是HBase能够拥有良好扩展性的最重要因素。一旦Region的负载过大或者超过阈值时,它就会被分裂成两个新的Region,如图所示。

    这个过程是由RegionServer完成的,其拆分流程如下。

    1、将需要拆分的Region下线,阻止所有对该Region的客户端请求,Master会检测到Region的状态为SPLITTING。

    2、将一个Region拆分成两个子Region,先在父Region下建立两个引用条件,分别指向Region的首行和末行,这时两个引用文件并不会从父Region中复制数据。

    3、之后在HDFS上建立两个子Region的目录,分别复制上一步建立的引用文件,每个子Region分别占父Region的一半数据。复制登录完成后删除两个引用文件。

    4、完成子Region创建后,向Meta表发送新产生的Region的元数据信息。

    5、将Region的拆分信息更新到HMaster,并且每个Region进入可用状态。

    以上是Region的拆分过程,那么,Region在什么时候才会触发拆分呢?常用的拆分策略如下表所示

    策略原理描述

    ConstantSizeRegionSplitPolicyRegion 中最大 Store 的大小大于设置阈值(hbase.hregion.max.filesize)之后才会触发拆分。 拆分策略原理相同,只是阈值的设置不同拆分策略对于大表和小表没有明显的区分。阈值设置较大时小表可能不会触发分裂。如果阈值设置较小,大表就会在整个集群产生大量的 Region,影响整个集群的性能

    IncreasingToUpper BoundRegionSplitPolicy阈值在一定条件下不断调整,调整规则与 Region 所属表在当前 Region 服务器上的 Region 个数有关系很多小表会在大集群中产生大量小 Region,分散在整个集群中

    SteppingSplitPolicy阈值可变。如果 Region 个数等于 1,则拆分阈值为 flushsize × 2;否则为 MaxRegionFileSize小表不会再产生大量的小 Region,而是适可而止

    DisabledRegionSplitPolicy关闭策略,手动拆分可控制拆分时间,选择集群空闲时间

    Region 合并

    从Region的拆分过程中可以看到,随着表的增大,Region的数量也越来越大。如果有很多Region,它们中MemStore也过多,会频繁出现数据从内存被刷新到HFile的操作,从而会对用户请求产生较大的影响,可能阻塞该Region服务器上的更新操作。过多的Region会增加ZooKeeper的负担。

    因此,当Region服务器中的Region数量达到阈值时,Region服务器就会发起Region合并,其合并过程如下。

    1、客户端发起Region合并处理,并发送Region合并请求给Master。

    2、Master在Region服务器上把Region移到一起,并发起一个Region合并操作的请求。

    3、Region服务器将准备合并的Region下线,然后进行合并。

    4、从Meta表删除被合并的Region元数据,新的合并了的Region的元数据被更新写入Meta表中。

    5、合并的Region被设置为上线状态并接受访问,同时更新Region信息到Master。

    Region 负载均衡

    当Region分裂之后,Region服务器之间的Region数量差距变大时,Master便会执行负载均衡来调整部分Region的位置,使每个Region服务器的Region数量保持在合理范围之内,负载均衡会引起Region的重新定位,使涉及的Region不具备数据本地性。

    Region的负载均衡由Master来完成,Master有一个内置的负载均衡器,在默认情况下,均衡器每5分钟运行一次,用户可以配置。负载均衡操作分为两步进行:首先生成负载均衡计划表,然后按照计划表执行Region的分配。

    执行负载均衡前要明确,在以下几种情况时,Master是不会执行负载均衡的。

    均衡负载开关关闭

    Master没有初始化

    当前有Region处于拆分状态

    当前集群中有Region服务器出现故障

    Master内部使用一套集群负载评分的算法,来评估HBase某一个表的Region是否需要进行重新分配。这套算法分别从Region服务器中Region的数目、表的Region数、MemStore大小、StoreFile大小、数据本地性等几个维度来对集群进行评分,评分越低代表集群的负载越合理。

    确定需要负载均衡后,再根据不同策略选择Region进行分配,负载均衡策略有三种,如下表所示。

    策略原理

    RandomRegionPicker随机选出两个 Region 服务器下的 Region 进行交换

    LoadPicker获取 Region 数目最多或最少的两个 Region 服务器,使两个 Region 服务器最终的 Region 数目更加平均

    LocalityBasedPicker选择本地性最强的 Region

    根据上述策略选择分配Region后再继续对整个表的所有Region进行评分,如果依然未达到标准,循环执行上述操作直至整个集群达到负载均衡的状态。

    Region/Store/StoreFile/Hfile之间的关系

    1、Region

    table在行的方向上分隔为多个Region。Region是Hbase中分布式存储和负载均衡的最小单元,即不同的Region可以分别在不同的Region Server上,但同一个Region是不会拆分到多个Server上。

    Region按大小分隔,表中每一行只能属于一个region。随着数据不断插入表,region不断增大,当region的某个列簇达到一个阈值(默认值)时就会分成两个新的region。

    2、Store

    每一个region有一个或多个store组成,至少是一个store,hbase会把一起访问的数据放在一个store里面,即为每个ColumnFamily建一个store(即有几个ColumnFamily,也就有几个Store)。一个Store由一个memStore和0或多个StoreFile组成。

    :Hbase以store的大小来判断是否需要切分region

    3、MemStore

    memStore是放在内存里的。保存修改的数据即keyValues。当memStore的大小达到一个阈值(默认值)时,memStore会被flush到文件,即生成一个快照。目前hbase会有一个线程来负责memStore的flush操作。

    4、StoreFile

    memStore内存中的数据写到文件后就是StoreFile(即memStore的每次flush操作都会生成一个新的StoreFile),StoreFile底层是以HFile的格式保存。

    5、HFile

    HFile是Hbase中KeyValue数据的存储格式,是hadoop的二进制格式文件。一个StoreFile对应着一个HFile。而HFile是存储在HFDS之上的。HFile文件格式是基于Google Bigtable中的 SS Table,如下图所示:

    Data Block段:保存表中的数据,这部分可以被压缩。Data Block是 HBase I/O的基本单元,为了提高效率。HRegion Server中有基于LRU的Block Cache机制,每个Data Block大小可以在创建一个Table的时候通过参数指定,较大的Data Block有利于顺序scan,小的Data Block有利于随机查询

    Meta Block段:保存用户自定义的kv对,可以被压缩。

    File Info段:HFile的元信息,不被压缩,用户也可以在这一部分添加自己的元信息,记录了文件的META信息,例如:AVG_KEY_LEN,ACG_VALUE_LEN,LAST_KEY,COMPARATOR,MAX_SEQ_ID_KEY等

    Data Block Index段:Data Block的索引。每条索引的key是被索引的block的第一条记录的key

    Meta Block Index段:Meta Block的索引

    Trailer:这一段是定长的。保存了每一段的偏移量,读取一个HFile时,会首先读取Trailer,Trailer保存了每个段的起始位置(段的Magic Number用来做安全check),然后,DataBlock Index会被读取到内存中,这样,当检索某个key时,不需要扫描整个HFile,而只需要从内存中找到key所在的block,通过一次磁盘io将整个block读取到内存中,再找到需要的key。DataBlock Index采用LRU机制淘汰。

    HFile的Data Block,Meta Block通常采用压缩方式存储,压缩之后可以大大减少网络IO和磁盘IO,随之而来的开销当然是需要花费cpu进行压缩和解压缩。

    HFile里面的每个key-value对就是一个简单的byte数组,这个byte数组具有固定结构。

    (1)KeyLength和ValueLength:两个固定的长度,分别代表Key和Value的长度

    (2)Key部分:Row Length是固定长度的数值,表示RowKey的长度,Row 就是RowKey

    (3)Column Family Length是固定长度的数值,表示Family的长度

    (4)接着就是Column Family,再接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key Type(Put/Delete)

    (5)Value部分没有这么复杂的结构,就是纯粹的二进制数据

    首先HFile文件是不定长的,长度固定的只有其中的两块:Trailer和FileInfo。Trailer中指针又指向其它数据块的起始点,FileInfo记录了文件的一些meta信息。

    HBase 在HDFS上的存储结构

    Hbase表的HDFS目录结构为

    /hbase /data /<Namespace> (集群里的Namespaces) /<Table> (该集群的Tables) /<Region> (该table的Regions) /<ColumnFamily> (该Region的列族) /<StoreFile> (该列族的StoreFiles)

    HLog的HDFS目录结构

    /hbase /WALs /<RegionServer> (RegionServers) /<WAL> (WAL files for the RegionServer)

    6、Meta表

    有了 Region 标识符,就可以唯一标识每个 Region。为了定位每个 Region 所在的位置,可以构建一张映射表。

    映射表的每个条目包含两项内容,一项是 Region 标识符,另一项是 Region 服务器标识。这个条目就表示 Region 和 Region 服务器之间的对应关系,从而就可以使用户知道某个 Region 存储在哪个 Region 服务器中。这个映射表包含了关于 Region 的元数据,因此也被称为“元数据表”,又名“Meta表”。

    使用 scan 命令可查看 Meta 表的结构,如图所示。

    Meta 表中的每一行记录了一个 Region 的信息。RowKey 包含表名、起始行键和时间戳信息,中间用逗号隔开,第一个 Region 的起始行键为空。时间戳之后用.隔开的为分区名称的编码字符串,该信息是由前面的表名、起始行键和时间戳进行字符串编码后形成的。

    Meta 表里有一个列簇 info。info 包含了三个列,分别为 Regionlnfo、RegioninfoServer 和 Serverstartcode。Regionlnfo中记录了 Region 的详细信息,包括行键范围 StartKey 和 EndKey、列族列表和属性。

    Server 记录了管理该 Region 的 Region 服务器的地址,如 localhost:16201。

    Serverstartcode 记录了 Region 服务器开始托管该 Region 的时间。

    当用户表特别大时,用户表的 Region 也会非常多。Meta 表存储了这些 Region 信息,也变得非常大。Meta 表也需要划分成多个 Region,每个 Meta 分区记录一部分用户表和分区管理的情况。

    hbase:meta表的一个rowkey对应一个region,rowkey设计如下:

    表名,region的startRowkey,region创建时的时间戳.EcodedName

    例:test:vt_article,66192017090716590_4149388609714192,1542770105784.5c44f752e1012fba9eacad769185b9dd

    表名 : test:vt_article

    开始rowkey :66192017090716590_4149388609714192

    创建Region时间戳:1542770105784

    EncodedName :5c44f752e1012fba9eacad769185b9dd = MD5(test:vt_article,66192017090716590_4149388609714192,1542770105784)

    相关文章

      网友评论

          本文标题:HBase 基础原理

          本文链接:https://www.haomeiwen.com/subject/eqazmftx.html