• [技术干货] MySQL+NodejS+ES的全文搜索实战
    一、Elasticsearch单独使用1、Elasticsearch安装(建议Linux系统):步骤一:安装较新版本的Java,确保环境变量配置正确,JDK版本不能低于1.7_55。步骤二:安装Elasticsearch:https://www.elastic.co/cn/downloads/elasticsearch    Linux版本$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.10.0-linux-x86_64.tar.gz$ tar -xzf elasticsearch-7.10.0-linux-x86_64.tar.gz    windows版本:官网下载windows版本安装包,解压。2、Elasticsearch启动$ cd elasticsearch-7.10.0/$ ./bin/elasticsearch (Linux版本)$ .\bin\elasticsearch.bat (windows版本)运行成功后,浏览器访问http://localhost:9200/?pretty页面出现如下信息意味着启动成功了!!!或者打开另一个终端 执行:curl 'http://localhost:9200/?pretty' ,与上一种方式启动成功信息显示一致。(windows可以安装cURL)。可以搭配图形用户界面一起使用,安装kibana(https://www.elastic.co/guide/en/kibana/4.6/index.html), 与 elasticsearch 版本对应即可。二、Node连接MySQL1、安装ES模块$ npm install elasticsearch --save2、安装MySQL驱动$ npm install mysql --save3、这里的框架使用的是koa,先写配置文件,代码如下:4、插入数据,测试数据使用 [Faker-zh-cn.js](https://github.com/layerssss/Faker-zh-cn.js) 生成。5、使用ES全文高亮搜索,代码如下:
  • [技术干货] Angular 单元测试
    隔离程序的每个部件,在隔离环境中运行测试用例。karma.conf.js :文件是为了告知 Karma 需要启用哪些插件、加载哪些测试脚本、需要哪些测试浏览器环境、测试报告通知方式、日志等等。具体配置内容参见 karma.conf.js。https://karma-runner.github.io/5.0/config/configuration-file.html>测试文件的扩展名必须是 .spec.ts,这样工具才能识别出它是一个测试文件,也叫规约(spec)文件"test": {    "options": {        "codeCoverage": true    }}Jasmine Angular 单元测试是使用 Jasmine 框架来编写的。基础知识describe(string, function):是 Jasmine 的全局函数,可以理解为一个测试集(Test Suite),主要功能是用来划分单元测试的。describe 可以嵌套使用。it(string, function):可以理解为测试用例。Specs 通过调用 it 的全局函数来定义。每个 Spec 包含一个或多个 expectations 期望值来测试需要测试代码。Jasmine 中的每个 expectation 是一个断言,可以是 true 或者 false。当每个 Spec 中的所有 expectations 都是 true,则通过测试。有任何一个 expectation 是 false,则未通过测试。而方法的内容就是测试主体。每个 Matchers 实现一个布尔值,在实际值和期望值之间比较。它负责通知 Jasmine,此 expectation 是真或者假。然后 Jasmine 会认为相应的 spec 是通过还是失败。所有的 expect 都可以使用 not 表示否定的断言。JavaScript 的作用域的规则适用,所以在 describe 定义的变量对 Suite 中的任何 it 代码块都是可见的。beforeAll:每个 suite(即 describe)中所有 spec(即 it)运行之前运行,整个suite里只运行一次afterAll:每个 suite(即 describe)中所有 spec(即 it)运行之后运行xdescribe:该 describe下的所有 it 将被忽略,jasmine 将直接忽略这些it,因此不会被运行// 引入相关模块import { async, ComponentFixture, TestBed } from '@angular/core/testing';import { RouterTestingModule } from '@angular/router/testing';import { HttpClientTestingModule } from '@angular/common/http/testing';import { BackupListComponent } from './backup-list.component';import { DebugElement } from '@angular/core';describe('HorizontalGridComponent test', () => {   let component: HorizontalGridComponent; let fixture: ComponentFixture<HorizontalGridComponent>;    // 配置 TestBed 环境   beforeEach(async(() => {      TestBed.configureTestingModule({         imports: [           HttpClientTestingModule         ],         declarations: [HorizontalGridComponent],  }).compileComponents();   }));  beforeEach(() => {        // 创建一个HorizontalGridComponent 的实例  fixture = TestBed.createComponent(HorizontalGridComponent);        // 使用 fixture.componentInstance 来访问组件实例  component = fixture.componentInstance;        // TestBed.createComponent 不能触发变更检测,使用 detectChanges() 触发检查。  fixture.detectChanges(); });   it('should create', () => {  expect(fixture).toBeDefined();       expect(component).toBeDefined(); }); it('should have <h3> with "Hello"', () => {        // const El: HTMLElement = fixture.nativeElement;        const De: DebugElement = fixture.debugElement;        const El: HTMLElement = De.nativeElement; // nativeElement相当于debugElement的一个属性        // js原生的querySelector api去获取h3标签        const p = El.querySelector('h3');        // 判断h3标签的内容        expect(p.textContent).toEqual('Hello');    });    it('should find the <h3> with fixture.debugElement.query(By.css)', () => {        const De: DebugElement = fixture.debugElement;        const El = De.query(By.css('h3')); // nativeElement相当于debugElement的一个属性        // js原生的querySelector api去获取h3标签        const p: HTMLElement = El.nativeElement;        // 判断h3标签的内容        expect(p.textContent).toEqual('Hello');    });   afterEach(() => {      TestBed.resetTestingModule();   });});置好 TestBed 之后,调用它的 createComponent() 方法,它会创建一个 HorizontalGridComponent 的实例,把相应的元素添加到测试运行器的 DOM 中,然后返回一个 ComponentFixture 对象。ComponentFixture 用来与所创建的组件及其 DOM 元素进行交互。获取组件元素 nativeElement 和 DebugElement。 前者原生 DOM 元素,属性取决于运行环境,如果是浏览器,就提供浏览器的一些api,后者是由 Angular 进行包装可以安全的横跨其支持的所有平台运行并提供诸如 query 或者 triggerEventHandler 事件dom操作等方法。TestBed.createComponent 不能触发变更检测,使用 detectChanges() 触发检查。运行完成后,控制台输出:chrome浏览器输出:
  • [博文鉴赏] 华为云数仓GaussDB(DWS)之地理数据库: PostGIS介绍(一)
       PostGIS为PostgreSQL提供了空间数据库分析能力,是目前业界主流的地理数据库之一,提供如下空间信息服务功能:空间对象、空间索引、空间操作函数和空间操作符等。在GaussDB 中,目前已支持PostGIS地理数据库扩展,并已广泛应用于国内外安防、农业、安平等政企客户。https://bbs.huaweicloud.com/blogs/190410
  • [博文鉴赏] GaussDB Roach逻辑备份恢复
    目录:                  GaussDB Roach逻辑备份恢复原理gs_dump逻辑备份恢复工具roach逻辑备份恢复GaussDB Roach逻辑备份恢复实践                逻辑备份恢复支持、规格及约束         续备份、续恢复         逻辑备份删除         存储介质         容错支持         特殊表名         并行处理         最佳实践FAQ GaussDB Roach逻辑备份恢复原理:            1.  gs_dump逻辑备份恢复: 可以设置事务隔离级别。 gs_dump一致性备份:在单个database内保持一致,不同database之间无法保持一致,因为不同database之间需要切换connection,不能在不同database之间共享snapshot,因此只能在同一个database内保持一致性。 如果不保持一致。一个database数据量非常大,gs_dump备份时间会比较长,将会持有锁比较长的时间,对于一些需要ddl操作的database实例来说无法忍受。 解决方案:细粒度备份,而不要备份整个database,一次只备份一个table,调用gs_dump多次。如果多个表之间有关联,将这些表放入一个gs_dump操作中,用-t tablename来操作。但是这个-t其实是解决了依赖关系,本质上还是按照依赖顺序串行导出。一个单表数据量非常大,考虑分区表,也就是将数据进行了分片。否则这些表的ddl将会增加gs_dump之间的冲突。对于数据量非常大的databse实例,推荐使用PITR物理增量备份。 gs_dump并行备份: gs_dump版本>=9.3,server版本>=9.2,需要支持pg_export snapshot                                     n+1个连接,-j n是并行度,1是主节点的连接                                     对于版本<9.2,需要并行度和数据库一致性,在备份的时候不能做dml操作。             2.  roach逻辑备份恢复: GaussDB是一个分布式数据库,数据自动分片存储。GaussDB roach采用了基于单表粒度的备份恢复来实现逻辑备份,即一次只备份一个table,每次通过gs_dump导出table的ddl语句,并通过单独的外表方法(roach外表方法)来导出数据(不同表之间不保证数据一致性)。  GaussDB Roach逻辑备份恢复实践: 1. 逻辑备份恢复: 支持单表、多表(可以是不同schema的表)、schema、database级别逻辑备份。schema、database都默认转为多表逻辑执行(基于单表的串行实现),备份出schema、database内包含的所有表数据。可以从多表、schema、或者database逻辑备份中恢复出任意单张表或者多张表。 规格约束:不支持private table,nodegroup table, replication table,table has trigger/sequence表的备份恢复。                      对于表之间依赖关系,trigger,sequence,index等没有进行导出,只备份恢复数据。 单表: --tablename table  其中tablename可以是schema.table; schema默认为public,database默认为postgres单表备份:python $GPHOME/script/GaussRoach.py --master-port 9500 --agent-port 9600 --media-destination media_path  --metadata-destination metadata_path  --media-type DISK  --logging --logging-level INFO -t backup --tablename table [--schemaname schema] [--dbname database]              单表恢复:python $GPHOME/script/GaussRoach.py --master-port 9500 --agent-port 9600 --media-destination media_path  --metadata-destination metadata_path  --media-type DISK  --logging --logging-level INFO -t restore --tablename table [--schemaname schema] [--dbname database]  --backup-key bkpkey [--clean]/[--create] 多表: --table-list       --table-list选项与--tablename以及--schemaname不兼容;可以是不同schema中的表  tablelist文件内容示例:public.t1gauss.testT2Public."Temp"汉字表名cc.1a 多表备份:python $GPHOME/script/GaussRoach.py --master-port port_no --agent-port port_no --media-destination media_path  --metadata-destination metadata_path  --media-type DISK  --logging --logging-level INFO -t backup --table-list tablelist [--schemaname schema] [--dbname database]              多表恢复:python $GPHOME/script/GaussRoach.py --master-port 9500 --agent-port 9600 --media-destination media_path  --metadata-destination metadata_path  --media-type DISK  --logging --logging-level INFO -t restore --tablelist tablelist  [--dbname database]  --backup-key bkpkey [--clean]/[--create]  Schema: --schemaname  Schema规格约束: 其中系统级schema "dbms_job", "dbms_lob", "dbms_om", "dbms_output", "dbms_random", "dbms_redact", "dbms_sql", "sys", "utl_file", "utl_raw", "cstore"不支持备份恢复,都会报错退出。 Schema备份:python $GPHOME/script/GaussRoach.py --master-port port_no  --agent-port port_no --media-destination media_path  --metadata-destination metadata_path  --media-type DISK  --logging --logging-level INFO -t backup --schemaname schema [--dbname database]              Schema恢复:python $GPHOME/script/GaussRoach.py --master-port port_no --agent-port port_no --media-destination media_path  --metadata-destination metadata_path  --media-type DISK  --logging --logging-level INFO -t restore --schemaname schema  [--dbname database]  --backup-key bkpkey [--clean]/[--create] 其中-t restore --schemaname schema  --dbname database --backup-key bkpkey 可以从database级别逻辑备份中恢复出指定的schema Database:   --dbname        Database规格约束:其中系统级schema "dbms_job", "dbms_lob", "dbms_om", "dbms_output", "dbms_random", "dbms_redact", "dbms_sql", "sys", "utl_file", "utl_raw", "cstore"不支持备份恢复,都会被默认过滤掉。 Database备份:python $GPHOME/script/GaussRoach.py --master-port port_no --agent-port port_no --media-destination media_path  --metadata-destination metadata_path  --media-type DISK  --logging --logging-level INFO -t backup --dbname database             Database恢复:python $GPHOME/script/GaussRoach.py --master-port port_no --agent-port port_no --media-destination media_path  --metadata-destination metadata_path  --media-type DISK  --logging --logging-level INFO -t restore --dbname database  --backup-key bkpkey [--clean]/[--create]  2. 逻辑续备份、续恢复: 逻辑备份续备份、续恢复。其中续备份对于已经进行过校验的表名不会继续校验,以提升效率。如果在续备份时某些表已经被过滤掉,没有实际备份,而用户又对表对象属性进行了修改,希望能够得到备份,则考虑重新进行单表、多表重新备份,或者等待下次备份即可。 多表、schema、database等均支持续备份、续恢复,仅在备份、恢复失败之后才需要做。 续备份: --resume-backup --backup-key bkpkeypython $GPHOME/script/GaussRoach.py --master-port port_no --agent-port port_no --media-destination media_path  --metadata-destination metadata_path  --media-type DISK  --logging --logging-level INFO -t backup --schemaname schema [--dbname database] --resume-backup  --backup-key bkpkey 续恢复: --resume-restore--backup-key bkpkeypython $GPHOME/script/GaussRoach.py --master-port port_no --agent-port port_no --media-destination media_path  --metadata-destination metadata_path  --media-type DISK  --logging --logging-level INFO -t restore --schemaname schema  [--dbname database]  --backup-key bkpkey [--clean]/[--create] --resume-restore  3. 逻辑备份删除: 通过指定backupkey来删除,cascade是将所有backupkey全部删除。 python $GPHOME/script/GaussRoach.py --master-port port_no --media-destination /home/dulong/backup/media  --metadata-destination /home/dulong/backup/metadata  --media-type DISK  --logging --logging-level INFO -t delete --cascade  --backup-key bkpkey  4. 存储介质:一般指定DISK,兼容NBU、OBS等多种存储方式  5. 容错支持:对于不支持的表会进行过滤,不会影响其他表的正常备份恢复。 恢复到新集群:默认备份恢复database是postgres,可以指定其他database。恢复到新集群需要加--create;而恢复到老集群则最好加--clean,会将原有数据清理删除再恢复已防止数据冗余;不加则直接恢复数据,可能会导致冲突或者数据冗余。 同样的,如果一个数据库中某些表已经被drop掉,恢复时这些表需要--create,而其他表需要--clean。用--clean时恢复时另一部分需要--create的表会被过滤掉,反之亦然。  6. 特殊表名: 汉字表名不需要加双引号 大写表名,大小写表名,首字母为数字,含有$, . 等特殊字符在对象中等都为特殊对象名。其中除了含大写字符的不能自动识别外,其余均可自动识别并加上双引号。表名含有. 时,默认. 前为schema名称,如果该.就是表名的一部分,则需要加两侧加双引号,以保证可以识别正确。 特殊对象名称需要加双引号,例如schema名为 a.b,表名为 'public.',传入必须加双引号,如‘“a.b”’,‘“public.”’,双引号外侧再加单引号是为了保证正确传入了对象名,如果传入“public.”,内部传入其实为'public.';如果对象名内有大写字符,也需要加双引号,否则内核会默认转为小写。 PS: 尽量不要数据库对象内加各种特殊符号。  7. 并行处理: Gauss数据库为分布式数据库,默认进行了分片处理。每个数据库节点上有一个或多个datanode,一般一个datanode对应一个磁盘,而我们每个节点推荐>=2个datanode保证高可用等。数据落盘为每个datanode同时向磁盘写数据,不同datanode并行的向磁盘写入数据。 每个datanode内为串行的处理表数据,一个表数据处理完之后再处理下一个表(8.1)。在8.1及8.2后续版本中,将实现单个datanode的多个表并行的处理数据(自定义并发度)。  8. 最佳实践: 设置常规作业,利用多表逻辑备份业务中比较重要的表,schema及database备份往往将所有表备份,重要程度不高的表备份恢复仍然有较高开销。对常规作业设置优先级,按优先级进行不同时间间隔的备份与恢复。Database级别逻辑备份将备份整库数据,如果database实例内表数量大且规模大,性能会比较差(单个datanode内串行备份恢复);推荐使用PITR物理增量备份来替代Database逻辑备份。如果逻辑备份恢复中途失败,尽可能使用续备份、续恢复以完成作业。续备份只备份未备份成功的表,且对已经校验过的表将不会进行二次校验,而现行的校验为串行校验,表数量比较大的时候开销仍然较大;续恢复将恢复未恢复成功的表。在8.1以及8.2后续版本中,将实现更为细粒度的续备份、续恢复,备份效率将更为提高。合理设置压缩算法和压缩率  FAQ:     1. Why not gs_dumpall?The  gs_dumpall program exports all databases, one after another, into a single script file, which prevents you from performing the parallel restore. If you back up all      databases this way, the restore process will take more time.The processing of dumping all databases takes longer than each individual one so you do not know which dump of each database relates to a specific point in time.              If you have a good reason to use the gs_dump all to backup all databases, the following is the command:          gs_dumpall -U postgres > c:\gsbackup\all.sql         2. Why not gs_dump?        gs_dump虽然灵活,但是数据导出按照sql或者二进制,仅支持在单节点导出。数据量较大的情况下性能往往较差,且不易扩展。      3.一致性?   数据的一致性要求在备份恢复过程中要求并不一定很高,而对于性能,扩展性,灵活性等均有一定要求。因此牺牲一定一致性来换取更好的性能,易用性等,对于实际场景往往可以接受。    
  • [技术干货] 前端树结构优化
    ## 一、 场景和问题树结构,多级嵌套(不确定几级),使用递归方式实现,数据量上万情况下,页面加载很慢。数据结构:十个父级节点,每个父节点有10000个子节点,父子是嵌套关系#### 代码:!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/21/1015289541bgbfss4v8yqi.png)#### 页面加载:!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/21/101559ltvf2s6kmuwa5w36.png)此时页面渲染需要13.5s## 二、问题解决#### 2.1 树数据结构扁平化!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/21/101735nlhao6wsmnqgwpin.png)从performance工具定位问题:调用栈createChildren被疯狂调用,在vue源码中,createChildren做的是创建Vue实例这件事。创建Vue实例包括依赖收集、响应式监听、数据事件绑定、编译模板等等。如果能将数据结构扁平化,就只会有一个tree组件,createChildren只执行进行一次。扁平化数据还有个好处就是: + 减少栈的读取,递归的本质是栈的读取,栈的读取是由解析器做的,扁平化可以减少V8引擎的开销。 + 扁平化数据可以减少dom 的数量。!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/21/101744sxwbkqtpgc9ccmou.png)扁平化后优化到6.5s,我们觉得6.5s还是远远不够#### 2.2 虚拟长列表我们可以用懒加载解决海量数据一次性加载的问题,但是最终dom数量还是很大,容易造成页面卡顿。所以我们使用虚拟长列表解决这个问题:!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/21/101800h4an6sdtrumx9beu.png)通过虚拟长列表,我们就控制住了DOM数量。#### 2.3 其他优化思考滚动事件加防抖,但要保证滚动的平滑度,使我们需要去权衡的。样式直接在数据中输出,不通过计算:树结构缩进padding距离不通过层级计算,而是在数据中直接给出样式,避免每个节点都计算一次样式的性能问题。
  • [技术干货] Raft原理及应用实践
    1、引言在华为分布式数据库的工程实践过程中,我们实现了一个计算存储分离、 底层存储基于Raft协议进行复制的分布式数据库系统原型。下面是它的架构图。计算节点生成日志经过封装后通过网络下发到存储节点,在Raft层达成一致后日志被应用到状态机wal Engine,完成日志的回放和数据的存储管理。下面简单介绍一下Raft的原理、以及存储节点(Pinetree)如何应用 Raft实现复制的一些工程实践经验。2、Raft的原理2.1 Raft的基本原理      Raft 算法一切以领导者为准,实现一系列值的共识和各节点日志的一致。下面重点介绍一下Raft协议的Leader选举、log复制 和 成员变更。Raft的选举机制:协议为每个节点定义了三个状态:Leader、Candidate、Follower,将时间定义为Term,每个Term有一个ID。Term类似逻辑时钟,在每个Term都会有Leader被选举出来。Leader负责处理所有的写请求、发起日志复制、定时心跳,每个Term期间最多只能有一个Leader,可能会存在选举失败的场景,那么这个Term内是没有Leader。Follower 处于被动状态,负责处理Leader发过来的RPC请求,并且做出回应。Candidate 是用来选举一个新的Leader,当Follower超时,就会进入Candidate状态。 初始状态,所有的节点都处于Follower状态,节点超时后,递增current Term进入Candidate,该节点发送广播消息RequestVote RPC给其他Follower请求投票。当收到多数节点的投票后,该节点从Candidate进入Leader。Follower在收到投票请求后,会首先比较Term,然后再比较日志index,如果都满足则更新本地Current Term然后回应RequestVote  RPC为其投票。每个Term期间,follower只能投一次票。Raft的日志同步机制:当Leader被选举出来后,就可以接受写请求。每个写请求即代表了用户需要复制的指令或Command。Raft协议会给写请求包装上Term和Index,由此组成了Raft的Log entry. Leader把Log entry append到日志中,然后给其它的节点发AppendEntries RPC请求。当Leader确定一个Log entry被大多数节点已经写入日志当中,就apply这条Log entry到状态机中然后返回结果给客户端。Raft成员变更机制: 成员变更就意味着集群节点数的增加或减少以及替换。Raft协议定义时考虑了成员变更的场景,从而避免由于集群变化引起的系统不可用。Raft是利用上面的Log Entry和一致性协议来实现该功能。成员的变更也是由Leader发起的,Leader会在本地生成一个新的Log entry,同时将Log entry推送到其他的Follower节点。Follower节点收到Log entry后更新本地日志,并且应用该log中的配置关系。多数节点应用后,Leader就会提交这条变更log entry。还要考虑新就配置的更替所带来的问题。更详细的不再赘述。2.2 Raft的开源实现Raft的实现有coreos的etcd/raft、kudu、consul、logcabin、cockroach等。Etcd 、LogCabin 、Consul 实现的是单个Raft环,无法做到弹性伸缩。而kudu和cockroach实现了多个raft环。kudu的consensus 模块实现了副本的数据复制一致性,kudu将数据分片称之为Tablet, 是kudu table的水平分表,TabletPeer就是在Raft环里面的一个节点. 一个Tablet相当于一个Raft环,一个Tablet对应一个Raft Consensus,这些对应Raft里面的一个环,Consensus Round相当于同步的消息,一个环之间会有多个Consensus Round做同步。而cockroach则是基于etcd/raft实现的多Raft环,它维护了多个Raft实例,被称之为multiraft。 因为Etcd的Raft是目前功能较全的Raft实现之一,最早应用于生产环境,并且做到了很好的模块化。其中Raft内核部分实现了Raft大部分协议,而对外则提供了storage和transport所用的interface,对于使用者可以单独实现灵活性较高,用户还可以自主实现 snapshot、wal ,Raft非常便于移植和应用,因此存储节点Pinetree采用了开源的Etcd中的Raft实现来构建我们的原型系统,也便于后期向Multiraft演进。。3、工程实践3.1  实现Raft的存储接口和网络传输Raft存储部分指的是raft- log的存储,是对日志条目进行持久化的存储,通过benchmark测试发现,raft-log引擎性能是影响整体ops的主要瓶颈,为了更灵活的支持底层存储引擎的快速替换,增加可插拔的存储引擎框架,我们对底层存储引擎进行解耦。Pinetree封装了第三方独立存储接口来适配etcd raft的log存储接口;通讯部分即Raft Transport、snapShot传输等,采用GRPC+Protobuf来实现,心跳、日志传输AppendEntries RPC、选举RequestVote RPC等应用场景将GRPC设置为简单式,snapShot设置为流式的形式。3.2  选举问题Raft可以实现自我选举。但是在实践中发现缺点也很明显,Raft自主选主可能存在如下的问题:1、不可控:可能随意选择一个满足Raft条件的节点2、网络闪断导致Leader变动3、节点忙导致的Leader变动4、破坏性的节点为了防止存储节点Leader在不同的AZ或者节点间进行切换,Pinetree采用的方案是由集群管理模块来指定 Leader。Pinetree中将electionTimeout设置为无穷大,关闭Follower可能触发的自动选举过程,一切选举过程由集群管理的建议选主模块来控制。3.3  读一致性模型在 Raft 集群中,一般会有 default、consistent、stale 三种一致性模型,如何实现读操作关乎一致性的实现。一般的做法是将一致性的选择权交给用户,让用户根据实际业务特点,按需选择,灵活使用。Consistent具有最高的读一致性,但是实现上要求所有的读请求都要走一遍Raft 内核并且将会与写操作串行,会给集群造成一定的压力。stale具有很好的性能优势,但是读操作可能会落到数据有延迟的节点上。在Pinetree的设计中,集群管理负责维护存储节点的信息,管理所有节点的Raft主副本的状态,一方面可以对读请求进行负载均衡,另一方面可以根据AZ亲和性、副本上的数据是否有最新的log 来路由读请求。这样在性能和一致性之间进行了最大的tradeoff。3.4  日志问题Raft以Leader为中心进行复制需要考虑几个问题:1、性能问题,如果leader为慢节点会导致长尾2、日志的同步必须是有序提交3、切换leader时有一段时间的不可用问题3我们通过集群管理来最大程度的防止Leader的切换。对于问题2,因为Pinetree的日志类似innodb的redo log ,采用LSN来编号的,所以应用到Pinetree存储层的的日志必须要保序,不能出现跳过日志段或日志空洞的情况。这就要求发给Raft的日志要做保序处理。计算层产生的wal log都对应一个LSN,LSN代表的是日志在文件中的偏移量,具有单调递增且不连续的特点。因此要求Wal log产生的顺序和apply到pinetree storage的顺序要保证一致。为了满足这一需求,我们在计算层和Raft层中间增加一个适配层,维护一个队列负责进行排序,同时为了应对计算层主备的切换,对消息增加Term以保证日志不会乱序。Raft指令还可能会被重复提交和执行,所以存储层要考虑幂等性的问题。因为Pinetree storage的日志用LSN进行编号,所以可以进行重复apply。3.5  如何解决假主问题计算节点需要获取某些元数据信息,每次都必须从Leader中读取数据防止出现备机延迟。在网络隔离的情况下,老的leader不会主动退出,会出现双主的情况,这个假主可能永远不知道自己其实已经不是真正的Raft主节点,导致真Leader和假Leader同时存在并提供读服务,这在无延迟系统是不允许的。如果每次读请求都走一遍Raft协议可以识别出假主,但是将会严重的影响系统的性能。Pinetree是通过租约(lease)的方式,让一个Pinetree主节点在提供服务之前,保守地检查自身在这一时刻是否拥有lease,再决定自身能不能提供读服务。因此,就算访问了一个Pinetree假主,假主也因为没有lease而不能提供服务。3.6  性能问题   涉及到性能Pinetree考虑和优化的地方:1  如果使用 Raft 算法 保证强一致性,那么读写操作都应该在领导者节点上进行。这样的话,读的性能相当于单机,不是很理想, 优化实现了基于leader+lease的方式来提供读服务即能保证一致性又不影响性能。2 优化raft参数 :in-flight的数目;transport queue的数量3  最大限度的异步化,例如:指令在raft达成一致完成持久化后传递给状态机存入消息队列立即返回,后续对消息进行异步并行解析。4  最大限度的进行Batch和Cache。例如:把一个事务内的写操作缓存到客户端,在事务提交时,再把所有的写打包成一个batch与事务commit请求一起发送给服务端
  • [技术干货] 浅谈云数据库的优势
    没经历过自建机房和自建数据库的维护,就不会对云数据库在使用上的便捷有强烈的体会。自建数据库和云数据库的比较,就好像是自己挑水喝和自来水供水的区别,使用自建数据库,就像是自己挑水喝。云数据库有即开即用、稳定可靠、便捷管理、弹性伸缩等优势,具体表现在哪里呢?相比自建数据库,云数据库在机房风火水电网络等基础设施方面、服务器部署及维护、数据库软件安装、数据库软件版本升级和补丁等云数据库相比自建数据库在机房、电力、空调、网络等基础设施方面节省了很多的人力、硬件、维护服务成本,尤其对于中小型企业,没有足够的财力和技术基础建设专业的机房网络等环境,则很容易导致各种各样让人抓狂的问题,比如机房所在的大楼动辄断电停电、空调停摆、UPS故障,总是让人心里慌慌,睡觉不踏实,更有时候会出现老鼠窜进去咬断网线之类的让人匪夷所思的事情,导致整个IT环境的不稳定。往往在这些不太起眼细节上的一个疏忽,会消耗掉管理维护人员的很多时间精力,甚至导致业务中断等故障。做过系统管理的人都知道,每当系统需要上线或者需要迁移时,就需要开始做规划,之后是网络环境准备、服务器上架、操作系统安装、数据库安装等一系列的事情,从规划到上线往往需要很长的时间,如涉及到设备采购的话,往往需要一两个月的时间等待。在这种情况下,云数据库的优势就能发挥出来,它即开即用的特性,只需要几个步骤与,就可以完成底层基础一揽子的事情。在当前信息化的社会,软件应用更新越来越快,同时对安全的要求越来高的情况下,必须要经常保持数据补丁的更新,才能减少各种各样的漏洞风险和安全威胁,对于比较大规模的企业IT系统来说,则意味着会消耗更多的人力来维护数据库等软件的版本,而对于专业能力比较弱的小规模IT信息系统来说,可能会更窘迫。从另一个角度来讲,数据库管理员DBA每天都奔波于软件升级、系统升级等事情,则无法聚焦于架构设计和调优等DBA核心价值所在的地方,于企业单位、于DBA个人成长都不是最优。此外,华为云上也提供了很多数据库相关的工具,比如华为云的DAS(Data Admin Service)工具与云数据库就相当于PL/SQL Developer与Oracle。DAS在云上免费使用且保证安全。而对于PL/SQL Developer则需要购买License,网上虽然有时候可以下载到,但由于来源不明,在使用时要保持谨慎。早在几年前出现的PL/SQL Developer比特币勒索病毒让多家企业单位的数据库中招。而在云上,则会有专业的团队来维护和管理,不会出现这种病毒侵扰。      遥想当年,公司找来了一个不懂技术的顾问,经常在对系统安装上线等方案评审时提的建议是“做一个按钮”,之后被大家嘲笑。如今想来,也许在当时他的想法有些不切实际,却看到了未来趋势。计算能力作为一种资源,未来也会像水电煤气一样,只需要简单的几个操作,就能使用到上面的各种服务,包括数据库。
  • [技术干货] MongoDB经典故障系列四|调整oplog大小,引起从库宕机怎么办?
  • [技术干货] 编码规范轻体验
    前言编码规范指的是针对特定编程语言约定的一系列规则,通常包括文件内容组织、缩进、注释、声明、语句、空格、命名约定、编程实践、编程原则和最佳实践等。 比如各类方法的命名约定:查询的方法以get或query做前缀;插入的方法以add或insert做前缀;删除的方法以delete或remove做前缀;……如果你对编码规范已经有了一定了解,那么我们继续往下。规范制定编码规范制定一般会参考业界公认的标准,在通用的编码规范基础上,再结合公司、项目的要求而形成的。编码规范制定的目标是为了实现团队成员代码一致性和最佳实践,通过代码风格的一致性,降低维护代码的成本以及改善多人协作的效率。同时遵守最佳实践,确保页面性能得到最佳优化和高效的代码。校验工具目前前端领域主要的代码静态检查工具有JSLint 、JSHint、 JSCS和ESLint等。它们各有的优缺点,本文不再详述,如有兴趣可自行了解。如何选择校验工具依据项目实际情况而定,本人比较推荐ESLint,因为它对ES6支持的最广泛,下面内容将会以ESLint如何在项目开发中实际应用展开。ESLint简介ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误。 安装npm install eslint --save-devESLint规则可以通过以下两种主要的方式来配置:Configuration Comments - 使用 JavaScript 注释把配置信息直接嵌入到一个代码源文件中。Configuration Files - 使用 JavaScript、JSON 或者 YAML 文件为整个目录(处理你的主目录)和它的子目录指定配置信息。可以配置一个独立的 .eslintrc.* 文件。配置说明ESLint 附带有大量的规则。你可以使用注释或配置文件修改你项目中要使用的规则。要改变一个规则设置,你必须将规则 ID 设置为下列值之一:"off" 或 0 - 关闭规则"warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)"error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)比如通过设置.eslintrc.js强制使用一致风格的反勾号、双引号或单引号。{    "rules": {        "quotes": ["error", "double"] // 要求尽可能使用双引号    }}编码规范应用实践Visual Studio Code+ESLint环境准备:Node.js、git、Visual Studio Code在VSCode扩展页搜索ESLint,下载并安装完成后应用到工作区,就可以实现IDE的智能提示。Git Hooks+ESLint Git hooks能够在发生某特定行为的时机,触发执行自定义的脚本。 我们可以通过pre-commit来触发执行git commit命名时的一些自定义脚本,比如代码静态检查。依赖huskyInclinthuskyhusky是用node实现的一个快速安装git hooks的工具。安装npm install husky --save-devpackage.json配置"husky": {    "hooks": {      "pre-commit": "npm run inclint"    } },Inclintinclint 是用来对每次改动的文件执行 eslint,提高 eslint 执行的速度。安装npm install inclint --save-dev命令及参数inclint -c path/to/eslintrc --targetDir dir[,dir2,...,dirn]options:   -c path 必须,指定 eslint 配置文件的路径--targetDir 必须,提供一个或多个需要检查的目录,注意多个目录用,分割package.json配置{  "lint": "inclint -c ./.eslintrc --targetDir ./src",}husky+Inclint一起使用时的配置如下:{  "dependencies": {    "eslint-config-angular": "^0.5.0",    "eslint-config-xo-space": "^0.21.0",    "eslint-plugin-angular": "^4.0.0",  },  "devDependencies": {    "eslint": "^5.7.0",    "eslint-config-prettier": "^3.1.0",    "eslint-loader": "^2.1.1",    "husky": "^3.0.1",    "inclint": "^1.0.6",  },  "scripts": {    "lint": "inclint -c ./.eslintrc --targetDir ./src",    "fix": "eslint --fix src",  },  "husky": {    "hooks": {      "pre-commit": "npm run lint"    }  }}提交代码git add * // 将修改的文件提交到暂存区git commit -m 'xxx' // 将暂存区里的改动给提交到本地的版本库,触发执行pre-commit中的自定义脚本npm run fix // 通过eslint自带命令修复代码门禁代码门禁指在某些特定的场景下(比如开发者提交MR到主干库、出包构建等)触发的一系列测试,使得主干代码能维持在某一个程度的质量标准,例如:编译必须通过,代码重复率、圈复杂度不能高于某个水平、静态代码扫描必须通过、开源无风险、安全检查通过等等,一般使用Jenkins完成环境搭建和门禁配置。因每个项目的要求不一,此处不再详细的讲述。如有需求可以使用云服务提供商的代码构建服务,比如华为云的代码检查 CodeCheck :https://support.huaweicloud.com/productdesc-codecheck/devcloud_pdtd_30001.html
  • [技术干货] 初识Webpack
    1. 摘要Webpack是一种前端资源构建工具,一个静态模块打包器。在Webpack看来,前端的所有资源文件(js/json/css/img/less/…)都会作为模块处理,当Webpack处理应用程序时,它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源。Webpack打包流程图如图1-1所示。图1-1 Webpack打包流程图2. Webpack五个核心概念2.1 Entry入口(Entry)指示Webpack以哪个文件作为入口起点分析构建内部依赖图并进行打包。2.2 Output输出(Output)指示Webpack打包后的资源bundles输出到哪里去,以及如何命名。2.3 LoaderLoader让Webpack能够去处理那些非JavaScript语言的文件,Webpack本身只能理解JavaScript。2.4 Plugins插件(Plugins)可以用于执行范围更广的任务,插件的范围包括从打包和压缩,一直到重新定义环境中的变量等。2.5 Mode模式(Mode)指示Webpack使用相应模式的配置。分为development和production两种模式,下面分别进行简述。development: 开发模式,能让代码本地运行的环境,会将process.env.NODE_ENV的值设为development,同时启用NamedChunksPlugin和NamedModulesPlugin插件;production: 生产模式,能让代码优化运行的环境,会将process.env.NODE_ENV的值设为production,同时启用FlagDependencyUsagePlugin、FlagIncludedChunksPlugin、ModuleConcatenationPlugin、NoEmitOnErrorsPlugin、OccurrenceOrderPlugin、SideEffectsFlagPlugin和UglifyJsPlugin插件。3. Wbepack配置3.1 webpack.config.js文件webpack.config.js是webpack的配置文件,用来指示webpack工作,运行webpack指令时,会加载里面的配置,所有构建工具都是基于nodejs平台运行的,默认采用commonjs模块化。webpack.config.js基础配置如图3-1所示。图3-1 webpack.config.js基础配置3.2 devServer配置开发服务器(devServer)用来实现自动化(自动编译、自动打开浏览器、自动刷新浏览器),只会在内存中编译打包,不会有任何文件输出,本地安装webpack-dev-server后,通过npx webpack-dev-server命令启动devServer,核心代码如图3-2所示。图3-2 devServer配置核心代码3.3 打包html/样式/图片/其它资源打包不同的资源会使用不同的loader和插件,打包html/样式/图片/其它资源的流程如下所述。3.3.1 打包html资源1.下载html-webpack-plugin插件;2.引入html-webpack-plugin插件;3.使用html-webpack-plugin插件,并进行相应配置。3.3.2 打包样式资源不同的样式文件需要配置不同的loader1.下载loader;2.配置loader,css样式文件使用css-loader和style-loader,less文件使用less-loader、css-loader和style-loader。其中css-loader的作用是将css文件变成commonjs模块加载到js文件中,style-loader的作用是创建style标签,将js中的样式资源插入进去,添加到head中生效。3.3.3 打包图片资源1.下载url-loader,file-loader2.配置loader3.3.4 打包其它资源1.下载file-loader2. 配置loader,配置该loader作用于不为html/css/less/js的其他文件3.4 提取css成单独文件/css兼容性处理/压缩css3.4.1  提取css成单独文件样式文件打包后会默认和js文件一起输出,可以通过插件将打包后的css文件单独输出,流程如下所述。1.下载mini-css-extract-plugin插件2.引用该插件3.配置3.4.2 css兼容性处理1.下载postcss-loader和postcss-preset-env2.在package.json中browsetslist属性中分别对开发环境和生产环境进行兼容性配置,设置支持样式的浏览器版本3.通过postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容性样式。3.4.3 压缩css1.下载optimize-css-assets-webpack-plugin插件2.引用该插件3.使用该插件3.5 js语法检查eslint/js兼容性处理/js压缩3.5.1 js语法检查eslint1.下载eslint-loader和eslint2.在package.json中的eslintConfig中进行配置3.配置eslint-loader,其中只需检测js文件并要排除第三方库,只检测自己写的源代码,同时可在options配置中设置fix:true,自动修复eslint的错误。3.5.2 js兼容性处理    1.下载babel-loader、@babel/core、@babel/preset-env,通过@babel/preset-env做基本的js兼容性处理,然后通过corejs做前面无法实现的兼容性处理,并实现按需加载    2. 配置loaderjs兼容性处理核心代码如图3-3所示图3-3 js兼容性处理核心代码3.5.3 js压缩mode设置为production生产环境时会自动压缩js代码。4. webpack性能优化可以从开发环境和生产环境分别对webpack进行性能优化。其中开发环境主要考虑从打包构建速度和代码调试两个方面进行优化,生产环境主要考虑从打包构建速度和代码运行性能这两个方面进行优化。下面简单介绍下开发环境上通过HMR提升构建速度。4.1 HMR    HMR(热模块替换),作用是一个模块发生变化后,只会更新打包这一个模块而不是所有模块,通过在devServer中设置hot:true属性启动HMR功能。其中对于样式文件,可以使用HMR功能,因为style-loader内部实现了;对于js文件,默认不能使用HMR功能,解决方法:修改入口文件js代码,添加支持HMR功能的代码,另外HMR只能处理非入口js文件的其他文件,对入口文件并不能生效,因为一旦入口文件更新,入口文件引入的其他文件一定会被重新加载;对于html文件,默认不能使用HMR功能,同时会导致html文件不能热更新,解决方法:修改entry入口文件,将html文件引入,只能解决html文件不能热更新的问题。js文件支持HMR功能的核心代码如图4-1所示。图4-1 js文件支持HMR功能核心代码4.2  HMR效果在入口index.js文件中引入print.js文件,运行npx webpack-devserver后,页面如图4-2所示。4-2 初始页面修改print.js文件后,只会重新加载print.js文件,而不会重新加载index.js文件,HMR效果如图4-3所示。4-3 HMR效果图
  • [技术干货] 什么是数据库“存算分离”架构?
        今天的话题要从一个朋友的咨询开始    所以准备写一篇短文谈谈我对“存算分离”架构的理解,不一定全面,欢迎在评论区探讨。    其实这个朋友是误解了“存算分离”这个概念。他认为普通MySQL云数据库用evs做存储,计算资源和存储资源是分开的,比如可以单独扩容计算资源或单独扩容存储资源,所以就是存算分离的架构,其实这么理解是片面的。要理解“存算分离”架构,还得追根溯源,从传统MySQL主备架构说起。    这张图熟悉MySQL的人应该都见过,我们知道,MySQL的master端有数据变更时,备机是通过读取和回放binlog,涉及到三个线程,一个运行在主节点(log dump thread),其余两个(I/O thread, SQL thread)运行在备节点,三个线程配合完成数据复制的工作。但是,不难发现,这个架构在某些场景会有明显的缺陷:主库写入压力大时。当主库的写入压力比较大的时候,主备复制的时延会变大,因为需要回放完所有binlog的事务才会完全达到数据同步。增加只读节点时。增加备机/只读节点的速度很慢,因为我们需要将数据全量的复制到从节点,如果主节点此时存量的数据已经很多,那么扩展一个备机节点速度就会很慢高。使用多个只读节点时。存储的成本线性增长,如果数据库磁盘空间比较大,那么相应的所有只读节点挂载的磁盘空间都需要和主节点一样大,成本将会随着只读库数量增加进行线性增加。    这些问题通过存算分离架构就能得到很好的解决,以华为云GaussDB(for MySQL)为例,作为华为自研的最新一代高性能企业级分布式数据库,基于华为最新一代DFV分布式存储,采用计算存储分离架构,最高支持128TB的海量存储,可实现超百万级QPS吞吐。    首先,GaussDB(for MySQL)采用计算与存储解耦的技术架构,让所有的节点都共享一个存储,也就是说,增加计算节点时,无需调整存储资源,真正做到计算与存储分离,并且可支持 15 个只读节点的扩展,主节点和只读节点之间是 Active-Active 的 Failover 方式,计算节点资源得到充分利用,由于使用共享存储,降低了用户使用成本。完美契合了企业级数据库系统对高可用性、性能和扩展性、云服务托管的需求。GaussDB(for MySQL)将MySQL存储层变为独立的存储节点,在GaussDB(for MySQL)中认为日志即数据,将日志彻底从MySQL计算节点中抽离出来,都由存储节点进行保存,与传统 RDS for MySQL 相比,不再需要刷 page,所有更新操作都记录日志,不再需要 double write,从而大大减少了网络通信。    小结一下,以“存算分离”架构来答复一下上面的3个问题:    1. 当主库的写入压力比较大的时候,由于不再有double write入,主节点和只读节点之间的复制时延基本得以消除。    2. 增加只读节点的速度非常快,因为不再需要将数据全量的复制到只读节点,无论多大数据量,只需 5 分钟左右即可完成增加只读节点。    3. 使用多个只读节点时,因为只有一份存储,所以存储的成本不会有变化,存储空间越大,只读节点越多,节省成本越明显。
  • [技术干货] Nest.js初探:这很Angular
    一、前言Nest是构建高效,可扩展的 Node.js Web 应用程序的框架。 它使用现代的 JavaScript 或 TypeScript(保留与纯 JavaScript 的兼容性),并结合 OOP(面向对象编程),FP(函数式编程)和FRP(函数响应式编程)的元素。 在底层,Nest 使用了 Express,但也提供了与其他各种库的兼容,例如Fastify,可以方便地使用各种可用的第三方插件。以上是Nest.js的官方介绍,其实我们只需知道,它是一款Node.js的后端框架,并且官方提到了这么一段话:Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, loosely coupled and easily maintainable applications. The architecture is heavily inspired by Angular.翻译过来就是:Nest 旨在提供一个开箱即用的应用程序体系结构,允许轻松创建高度可测试,可扩展,松散耦合且易于维护的应用程序。并且设计灵感来自于Angular。所以这应和了这篇文章的标题,而Angular很多模式又来自于Spring框架,比如依赖注入等等,所以可以认为:Nest.js是Node.js版的Spring框架。Nest.js的社区非常活跃,Github上已经有32.9k Star。不过我们国内也有类似的Node.js框架,比如阿里出品的Midway,两者设计思路也非常类似。二、创建新应用通过Nest CLI脚手架生成一个新应用:初始化完成后,执行npm run start即可启动项目。在项目的src目录下,我们可以看到以下几个文件:main.ts是入口文件,可以看到总共就8行代码:简单来说就是使用NestFactory这个工厂函数生成一个实例,然后监听3000端口即可。这时候打开浏览器访问localhost:3000,可以看到打印出了“Hello World!”三、实现接口接下来我们来实现一个查询用户的接口,来熟悉一下基本的开发流程。第一步,创建一个module:通过脚手架命令,可以自动帮我们生成一个module文件:这时候会发现,AppModule中自动引入了该module:第二步,创建Controller:Controller就类似前端的路由,负责处理服务器响应和客户端请求。举个例子,我们要创建一个接口获取所有的用户信息,接口路径为http://localhost:3000/user/users,那么我们可以在UserController中创建一个GET方法,路径为users:当我们访问上面的接口时,就会返回“all users”。再写一个获取单个用户信息的接口: 当然,这只是Controller能力的冰山一角,官方还提供了更多的装饰器:除此之外,Nest.js里还有很多概念都与Angular一一对应,如Provider、Pipe、Guard、Interceptors等等,对于熟悉Angular的开发者来说,Nest.js可谓是量身打造的后端框架。
  • [技术干货] 前端模块规范简介
    模块化的好处:避免命名冲突(减少命名空间污染)更好的分离, 按需加载更高复用性高可维护性1.CommonJSNode.js 应用由模块组成,采用 CommonJS 模块规范。 1.1 语法风格//Math.jsmodule.exports = {    'add': function(a, b) { return a + b;    }}//main.jsconst Math = require('./Math');console.log(Math.add(2, 3));console.log('done');/**输出:-->node main5done**/1.2 同步加载由上面例子可以看出,CommonJS中模块是同步加载的1.3 动态加载//main.jsconst Math = require('./Ma' + 'th');//动态拼接CommonJS支持动态加载模块。因为conmonJs是同步的,执行到这一行时,才会加载模块。1.4 浏览器不支持CommonJS规范。浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量。moduleexportsrequireglobal可以使用工具进行转换,例如:Browserify1.5 其他特点所有代码都运行在模块作用域,不会污染全局作用域。模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。模块加载的顺序,按照其在代码中出现的顺序。CommonJS模块的加载机制中,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。适合服务器端小测验//Math.jslet counter = 1;let addCounter = function () {    counter++;};module.exports = {    counter,    addCounter};console.log('Math init');//main.jsconst Math = require('./Math');Math.addCounter();console.log(Math.counter); // position AMath.counter = 5;const Math2 = require('./Math');console.log(Math2.counter); // position B//位置A 和 位置B的值分别是什么?2.AMDCommonJS是主要为了JS在后端的表现制定的,他是不适合前端的。AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。RequireJS实现了AMD规范。下面以RequireJS为例,了解一下AMD规范。2.1 语法风格//Math.jsdefine([], function(){    return {        'add': function(a, b) { return a + b; }    }})//main.jsrequire.config({    paths : {        "math" : "Math"    }});require(['math'], function (math) { console.log(math.add(2, 3));});console.log('done');//done//52.2 异步加载由上面的例子可以看出,require命令是异步执行的2.3 动态加载2.4 依赖前置,提前执行3.CMD(Common Module Definition)CMD是SeaJS 在推广过程中对模块定义的规范化产出。CMD规范和 AMD 很相似,尽量保持简单,并与 CommonJS 规范保持了很大的兼容性。 3.1 语法风格// CMDdefine(function(require, exports, module) {    var a = require('./a');    a.doSomething();    //...    var b = require('./b');   // 依赖可以就近书写    b.doSomething();    // ...     require.async('./c',function(c){ //支持异步加载        c.doSomething();    });})// AMD 默认推荐的是define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好    a.doSomething();    //...    b.doSomething();    //...})3.2 AMD和CMD的区别1) 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible(尽可能的懒加载,也称为延迟加载,即在需要的时候才加载)。2) CMD 推崇依赖就近,AMD 推崇依赖前置。虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢依赖前置的写法,也是官方文档里默认的模块定义写法。3.3 推荐链接与 RequireJS 的异同二、ModuleES中Module的特点浏览器,服务器通用静态加载1. 基本语法1.1 export一个模块就是一个独立的文件。export关键字用来输出模块内部的某个变量 。可以输出变量,函数或类。// test.jsexport var name = 'zhangsan';export var age = 18;// test.jsvar name = 'zhangsan';var age = 18;export { name, age };可以使用as为输出变量重命名。var name = 'zhangsan';var age = 18;export { name as personName,     age};需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。// 报错export 1;// 报错var m = 1;export m;export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。//m.jsexport var foo = 'bar';setTimeout(() => foo = 'baz', 1000);//main.jsimport { foo } from './m1.js';console.log(1, foo);setTimeout(function () {    console.log(2, foo);}, 1000);<!DOCTYPE html><html>    <script type="module" src='./main.js'></script>    <body></body></html>//console1 "bar"2 "baz"export命令可以出现在模块的任何位置,只要处于模块顶层就可以。1.2 import其他 JS 文件通过import命令加载模块。大括号里面的变量名,必须与被导入模块(test.js)对外接口的名称相同。// main.jsimport { name, age } from './test.js';function showName() {  console.log(name);}//zhangsanimport命令输入的变量都是只读的,因为它的本质是输入接口。import {a} from './xxx.js'a = {}; // Assignment to constant variable.如果a是一个对象,改写a的属性是允许的。和const一样。import {a} from './xxx.js'a.foo = 'hello'; // 合法操作可以用星号(*)指定一个对象,进行整体加载。// main.jsimport * as test from './test.js';function showName() {  console.log(test.name);}//zhangsantest.name = 'yun';//Cannot assign to read only property 'name' of object '[object Module]'//但如果是对象,可以修改对象的属性。import命令具有提升效果,会提升到整个模块的头部,首先执行。foo();import { foo } from 'my_module';由于import是静态执行,所以不能使用表达式和变量。// 报错import { 'f' + 'oo' } from 'my_module';import语句会执行所加载的模块,因此可以有下面的写法。仅仅执行lodash模块,但是不输入任何值。import 'lodash';即使加载多次,也只会执行一次。也就是说,import语句是 Singleton 模式。1.3 单例模式解读//counter.jsexport let counter = 1;export function addCounter(){    counter++;}console.log('counter init');//main.jsimport {counter, addCounter} from './counter';console.log('main:' + counter);addCounter();console.log('main:' + counter);//counter init//main:1//main:2//从这例子中可以看出和CommonJS的区别另一个例子//main2.jsimport {counter, addCounter} from './counter';console.log('main2:' + counter);addCounter();console.log('main2:' + counter);<!DOCTYPE html><html> <head> <title>module test</title> <script type="module" src='main.js'></script> <script type="module" src='main2.js'></script> </head></html>//consolecounter initmain:1main:2main2:2main2:3从上面这个例子中可以看出,即使加载多次,也只会执行一次。并且是 Singleton 模式。1.4 export default使用export default可以不用关注输出模块中的变量名。// export-default.jsexport default function () {  console.log('foo');}// import-default.jsimport customName from './export-default';customName(); // 'foo'import命令可以为该匿名函数指定任意名字。import命令后面,不使用大括号。// export-default.jsfunction foo() {  console.log('foo');}export default foo;export default的本质,就是输出一个叫做default的变量或方法。imort something from ...的本质,就是import {default as something} from ...// 正确export var a = 1;// 正确var a = 1;export default a;// 错误export default var a = 1;// 正确export default 42;// 报错export 42;所以export default是比较常用的方法:// MyClass.jsexport default class { ... }// main.jsimport MyClass from 'MyClass';let o = new MyClass();1.5 import&export混合使用export { foo, bar } from 'my_module';// 可以简单理解为import { foo, bar } from 'my_module';export { foo, bar };上面代码中,export和import语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar。// 接口改名export { fooName as newName } from 'my_module';//具名接口改为默认接口export { foo as default } from './someModule';//接口也可以改名为具名接口export { default as ES } from './someModule';1.6 模块的继承//calculator.jsexport function add(a, b) {  return a + b;}//calculatorPlus.jsexport * from './calculator.js';export function multiply(a, b) {  return a * b;}//main.jsimport * as cal from './calculatorPlus.js';cal.add(2, 3);//5cal.multiply(2, 3);//61.7 import()ES2020 (ES11)新增了 动态import特性,解决了import动态加载,和不能写在代码块中的问题。import()返回一个 Promise 对象 。import(a + '.js').then(...);      import(f()).then(...);if (condition) {  import('moduleA').then(...);} else {  import('moduleB').then(...);}2. Module补充2.1 ES模块和CommonJS模块的差异import和export是关键字,require不是。CommonJS 模块输出的是一个值的拷贝,ES 模块输出的是值的引用。(详情见上方【单例模式解读】)CommonJS 模块是运行时加载,ES 模块是编译时输出接口。    因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。this指向不同。ES 模块之中,顶层的this指向undefined;CommonJS 模块的顶层this指向当前模块,这是两者的一个重大差异。2.2 循环加载CommonJS中的循环加载// a.jslet bar = require('./b').bar;console.log('bar:', bar);exports.ada = 'ada';// b.jslet ada = require('./a').ada;exports.bar = 'bar';setTimeout(function () {    console.log('ada:', ada);}, 0);执行结果:-->node abar: barada: undefinedES Module中的循环加载// a.jsimport {bar} from './b.js';console.log('a.js', bar);export let ada = 'ada';// b.jsimport {ada} from './a.js';//console.log(ada); //此处访问ada会报错setTimeout(function () {    console.log('b.js', ada);}, 0);export let bar = 'bar';<!DOCTYPE html><html><head>    <title>module test</title>    <script type="module" src='a.js'></script></head></html>执行结果://consolea.js barb.js ada解读:因为CommonJS 模块输出的是一个值的拷贝,当拷贝的动作发生时,ada值为undefined,之后模块a中对ada进行赋值已经不会影响到模块b中保存的ada。ES 模块输出的是值的引用。所以模块a对ada进行赋值,会影响b中打印的结果。
  • [技术干货] 前端调试机器
    ## Chrome调试工具!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/1946416mxtydgfysrjdlpr.png)#### 箭头 & Elements##### **箭头:** 用于在页面选择一个元素来审查和查看它的相关信息。##### Elements 内容: + 样式!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/194757x6mhhgmwz9gbreas.png)+ 计算属性!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/194818ubcs8xiddehihlld.png)+ 事件监听> 将Ancestors和Framework Listeners两个checkbox取消选中可以看到 选择dom绑定的事件。 !(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/19485498syfy4vrdtswdyu.png)+ DOM breakpoints + 在Chrome开发者工具里,选中想要监控的DOM元素,点击右键,选择Break on->Attributes modifications: !(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/194930vyblmcvnviuv0ibv.png) + 之后在DOM Breakpoints的tab里能看到对应的断点 + 然后回到Chrome里鼠标悬停百度一下,Chrome开发者工具的调试器就会自动在DOM的属性发生变化的地方停下来: !(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/195014m4pmhursabbfcgvl.png) + 从调试器的调用上下文能了解到是上图第118行的className 改变了导致DOM断点的触发。+ Properties> 改选项卡里面可以看到 选择dom对象,及类的继承链。!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/195235nmhgfm2ud8xqqlaa.png)#### 设备模拟器> 模拟移动屏幕,或者一些其它的像素比例的屏幕。!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/195301vmhsnbu9oek0zngc.png)#### Console控制台> 控制台打印, 执行脚本。!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/195332g2eidbbtv36dtdyn.png)#### Sources!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/195348qeupdyr3bfddtzvq.png)#### NetWork!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/195402wvgl1f8vwx0fcpou.png)#### Performance!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/195415ap0bhda0beswf2qf.png)#### Memory: 内存快照!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/1954370qptqahc0icxkxly.png)## Vscode内代码调试#### 调试环境配置> 在.vscode / launch.json 文件夹为配置文件。(可以快捷键ctrl + p 然后输入 debug 进行快速配置)!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/195510wrw7ka6vzelhpqth.png)**参数说明:**```json"name": "Attach to Chrome", // 调试环境名称"type": "pwa-chrome", //调试环境运行环境, 取值: node pwa-chrome 或其它自行安装的程序命令"request": "attach" | "launch", // attach模式: 监听一个启动进程 launch模式: 由vscode 来启动一个独立的debug进程 "url": "http://localhost:8080", // 服务地址 launch模式才会有"port": 9222, // 端口 attach才会有"webRoot": "${workspaceFolder}" // 静态资源目录"cwd": "${workspaceFolder}/dist", // 指定程序启动调试的目录 ,当vscode启动目录不是项目根目录,并且调试npm script时非常有用"args": ["--no-install"],"outFiles": ["${workspaceFolder}/lib/**/*.js"], //指定 sourceMaps的位置"skipFiles": [ "<node_internals>/**/*.js", "${workspaceFolder}/node_modules/**/*.js" ], //指定跳过单步调试的代码"preLaunchTask": "npm: build", // launch之前做的事情"stopOnEntry": true, //自动断点到第一行代码处"smartStep": true, //自动跳过未映射到源代码的代码```#### 单个JS调试> 添加如下配置到launch.json 选择test.js F5 debug模式运行```json{ "type": "node", "request": "launch", "name": "debug test", "runtimeExecutable": "node", "program": "${workspaceFolder}/code/debugger/test.js", "restart": true, "console": "integratedTerminal", "internalConsoleOptions": "neverOpen" }```#### 项目调试+ 安装插件:`Debugger for Chrome`!(https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/195956i5k2fllzldgodgyh.png)+ 配置launch.json```json{ "type": "chrome", "request": "launch", "name": "launch project dbug", "url": "http://localhost:8993/drs", "webRoot": "${workspaceFolder}", "preLaunchTask": "debug", "sourceMapPathOverrides": { "webpack://[name]/./*": "${webRoot}/*", "webpack:///src/*": "${webRoot}/*", "webpack:///*": "*", "webpack:///./~/*": "${webRoot}/node_modules/*", }, },```+ 配置debug命令```json{ "version": "2.0.0", "command": "npm", // 运行命令的程序 "tasks": [ { "label": "debug", // Task 名称 "isBackground": true, "type": "npm", "script": "start", // npm 要执行的 script 名称,对应 package.json 中的定义 "detail": "编译至开发环境", // Task 的描述,在命令面板中显示 "group": "test", "problemMatcher": { "fileLocation": "relative", "pattern": { "regexp": "^([^\\s].*)\\((\\d+|\\,\\d+|\\d+,\\d+,\\d+,\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$", "file": 1, "location": 2, "severity": 3, "code": 4, "message": 5 }, "background": { "activeOnStart": true, "beginsPattern": ".", "endsPattern": "Version: webpack.+" } } } ]}```+ 运行`launch project dbug` 命令
  • [技术干货] 华为云PB级数据库GaussDB(for Redis)揭秘第二期:Redis消息队列Stream的应用探讨
    引言:Redis Stream是Redis 5.0引入的一种新的数据类型,其本质是一个消息队列,类似于 kafka等消息中间件。它提供了消息的落地存储功能,并实现了类似kafka消费组和消费者的功能。与kafka相比,Redis Stream同样拥有强大的功能,但因原生Redis无法有效支持大规模数据存储,成本昂贵,并存在数据丢失/不一致风险等原因,导致其未能流行起来。本文将对Stream的常用命令和应用场景进行介绍,并探讨原生Redis Stream消息队列的缺陷以及GaussDB(for Redis)提供的解决方案,供大家学习和选用。一、Redis Stream简介与Pub/Sub相比,Redis Stream 具有消息的落地存储功能,每一个客户端能访问任意时刻的消息,并且能记录每一个客户端的访问位置,还能保证消息不会丢失。Redis Stream 的结构如下所示,它有一个消息链表,将所有加入的消息都链接起来,每个消息都有唯一的 ID 和对应的内容。如图所示,每一个Stream队列包含多条消息,每条消息由唯一的ID进行标识,由时间戳和序列号组成,例如1627849609889-0。每条消息以追加的方式添加到Stream队列中。同一个Stream队列可以包含多个消费组(Consumer Group),每个消费组的状态都是独立的,同一个Stream队列的消息可以被多个消费组重复消费。同一个消费组又包含多个消费者(Consumer),这些消费者之间是竞争关系,不同消费者不会重复消费同一条消息,任意一个消费者读取了队列中的一条消息都会使消费组中的游标last_delivered_id往前移动。该方式提高了并发效率,例如,多个进程并发处理Stream队列中的消息。每个消费者中维持一个状态变量pending_ids,简称为PEL(Pending Entries List),记录了当前已经被客户端读取的但尚未被ACK的消息,确保消息被客户端成功消费。Redis Stream命令可以分为消息队列命令和消费者命令两类,如下所示:以即时通讯中的聊天室场景为例,使用Redis Stream作为中间件,实现聊天室的发言以及信息查看。使用XADD命令进行发言使用XLEN命令获取聊天室发言的数量使用XRANGE获取消息队列的消息使用XREAD命令读取消息。可以在不设置消费组和消费者的情况下,使用XREAD的命令进行消息读取,此时Stream队列类似于一个普通的列表(list)。更多的Redis Stream命令使用请参考官方文档(https://redis.io/commands/xread)。二、应用场景由于Redis Stream天然有序,特别适合存储时序数据,应用场景包括即时通讯、智慧医疗、流量削峰、智慧城市等领域。(1)即时通讯:微信、QQ等是我们日常生活中常用的通讯软件,常用的聊天方式包含点对点通讯和群聊两种方式。下图是一个群聊的模型图,当采用Redis Stream作为通讯的中间件,创建一个群聊时,在Redis中对应地为该群聊创建一个Stream队列。在发送消息时,将每个用户的消息按照时间顺序添加到Stream队列中,保证了消息的有序性。由于Stream是一个持久化的队列,无论是在线还是离线状态,每个用户可以多次查看历史消息,保证了通讯的完整性。(2)智慧医疗:医疗行业的信息化,可以更好地为服务于每一个人。为每一个人从出生起建立一份健康档案,记录相应的健康信息,如体检报告、诊断报告、用药信息、以及智能终端实时上传的健康指标。这些信息都是一些时序数据,同样可以采用Redis Stream来实现智慧医疗系统。建立起智慧医疗系统后,使用终端可以查看所有的医疗信息,并会提示患者按时吃药,在终端上传身体指标异常时,会自动报警并预约挂号。现阶段每个医院都有自己的信息系统,不同的医院很难查到同一个患者的医疗信息,在未来,医疗上云将有利于解决医疗信息孤岛,更好的帮助每一位患者。(3)流量削峰:在常见的秒杀活动或团购中,如春运抢票、商城促销等,通常短时间内有大量的流量,导致系统崩溃。由于每一个用户在请求时对应唯一的时间戳,所有的请求都有一个先后顺序,同样可以采用Redis Stream作为中间件,将请求加入到Redis Stream消息队列。将消息转存到消息队列间接提供给应用,而非直接发送给应用,可以防止大流量冲击导致的系统崩溃。当消息队列中的请求数量达到规定的最大值时,直接回复客户端抢购失败。三、原生Redis是否真的适用于以上场景?如上应用场景具有数据规模大、数据持续增长的特点,虽然原生Redis有良好的设计初衷,但是并不能解决实际问题。具体体现在:无法有效应对大规模数据:原生Redis是一个基于内存的数据库,单个节点存储容量有限,当扩展至TB级别的集群,将会出现管理困难,运维成本高等问题。集群扩容影响业务性能:原生Redis在进行集群扩容时,需要重新划分hash槽并进行数据迁移,必定会影响业务性能。数据可能会丢失:原生Redis虽然可以采用RDB和AOF的方式对数据进行持久化,但是并不会实时地将每一条命令写入到硬盘中,当出现掉电或集群崩溃的情况,必定会丢失一部分数据,对于类似智慧医疗场景,是难以忍受的。除此以外,必须考虑数据库系统的可用性、数据一致性、成本和备份恢复能力等情况:可用性: 原生Redis若采用一主一备的集群模式,当一对主备节点下线,集群部分数据将不可用。数据一致性:当主节点宕机,主备节点切换,数据存在没有完全同步的情况。成本:原生Redis是一种内存型数据库,当内存容量扩展至TB级别,成本将非常昂贵。备份恢复:需要人工连接数据库执行 SAVE或BGSAVE命令,不能支持定期自动备份,在恢复到新实例时需要手动拷贝备份数据。四、是否有更好的解决方案?在以上场景中,亟需一种能够存储和处理大规模Stream数据、鲁棒性强、且成本低廉的数据库系统。而GaussDB(for Redis)(下文简称高斯Redis)正是以上场景中一种很好的应用解决方案。高斯Redis是华为云数据库团队自主研发的兼容Redis协议的云原生数据库,该数据库突破原生Redis的内存限制,可轻松扩展至PB级存储,具有秒扩容、超可用、强一致和低成本等特点。五、总结Redis Stream可以广泛应用在即时通讯、智慧医疗、流量削峰等领域。在面对大规模的Stream数据时,原生Redis存在成本过高、容量太小、可用性差、数据不一致等问题,无法适用于海量消息队列的场景。与原生Redis相比,高斯Redis具有海量存储,低成本,可持久化等优点,可做为比原生Redis更理想的Stream队列承载方案。
总条数:20 到第
上滑加载中