ソースを参照

这是之前的零散笔记

YWJL 3 年 前
コミット
f508a75fb8

+ 192 - 0
ES6/es6.md

@@ -0,0 +1,192 @@
+# ECMAScript 6 入门
+
+# let 和 const 命令
+
+## 1. let 命令
+
+### 变量提升
+
+1. 变量提升是指,ES5版本,var`命令会发生“变量提升”现象,即变量可以在声明之前使用,值为`undefined
+
+2. 为了纠正这种奇怪的现象,`let`命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
+
+3. ```javascript
+   // var 的情况
+   console.log(foo); // 输出undefined
+   var foo = 2;
+   
+   // let 的情况
+   console.log(bar); // 报错ReferenceError
+   let bar = 2;
+   ```
+
+   上面代码中,变量`foo`用`var`命令声明,会发生变量提升,即脚本开始运行时,变量`foo`已经存在了,但是没有值,所以会输出`undefined`。变量`bar`用`let`命令声明,不会发生变量提升。这表示在声明它之前,变量`bar`是不存在的,这时如果用到它,就会抛出一个错误。
+
+### 暂时性死区
+
+1. 首先要了解一个概念,块级作用域(比如说函数的定义,if啥的,括号内的)
+
+2. 要了解一个概念,全局变量不能
+
+3. 什么是暂时性死区呢。比如说我申请了个全局变量tmp,然后我在某个函数中,又申请了个局部变量tmp,那么这个时候,在这个函数中,就以局部变量tmp为准,可以认为全局变量tmp不存在
+
+   ```js
+   var tmp = 123;
+   if (true) { //这里没申请同名局部变量,不存在死区
+       tmp = 'abc'; 
+       console.log(tmp)
+   }
+   if (true) {//这里申请了同名局部变量,存在死区
+       // TDZ开始
+       tmp = 'abc'; // ReferenceError
+       console.log(tmp); // ReferenceError
+       let tmp; // TDZ结束
+       console.log(tmp); // undefined
+       tmp = 123;
+       console.log(tmp); // 123
+   }
+   ```
+
+### 不允许重复声明
+
+1. let 不允许同一作用域内,重复声明一个变量
+
+
+
+## 2. 块级作用域
+
+1. 块级作用域可以任意嵌套,每一层都是一个单独的的作用域。第四层作用域无法读取第五层作用域的内部变量。
+
+2. 内层作用域可以定义外层作用域的同名变量。
+```js
+   {{{{
+     let insane = 'Hello World';
+     {let insane = 'Hello World'}
+   }}}};
+```
+3. 块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。
+ ```js
+   // IIFE 写法
+   (function () {
+     var tmp = ...;
+     ...
+   }());
+   
+   // 块级作用域写法
+   {
+     let tmp = ...;
+     ...
+   }
+ ```
+
+4. ES6 规定,允许在块级作用域之中申明函数。函数声明语句的行为类似于`let`,在块级作用域之外不可引用。
+
+   ```javascript
+   // 块级作用域内部的函数声明语句,建议不要使用
+   {
+     let a = 'secret';
+     function f() {
+       return a;
+     }
+   }
+   
+   // 块级作用域内部,优先使用函数表达式
+   {
+     let a = 'secret';
+     let f = function () {
+       return a;
+     };
+   }
+   ```
+
+## 3. const 命令
+
+### 基本用法
+
+1. `const`声明一个只读的常量。一旦声明,常量的值就不能改变。
+2. `const`声明的变量不得改变值,这意味着,`const`一旦声明变量,就必须立即初始化,不能留到以后赋值。
+3. `const`的作用域与`let`命令相同:只在声明所在的块级作用域内有效。
+4. `const`命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
+5. `const`声明的常量,也与`let`一样不可重复声明。
+
+### 本质
+
+1. `const` 实际上保证的,并不是变量的值不动,而是变量所指向的那个内存地址所保存的数据不动。
+
+2. 对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量
+
+3. 但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,`const`只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了
+
+   ```js
+   //ex1
+   const foo = {};
+   
+   // 为 foo 添加一个属性,可以成功
+   foo.prop = 123;
+   foo.prop // 123
+   
+   // 将 foo 指向另一个对象,就会报错
+   foo = {}; // TypeError: "foo" is read-only
+   
+   //ex2
+   const a = [];
+   a.push('Hello'); // 可执行
+   a.length = 0;    // 可执行
+   a = ['Dave'];    // 报错
+   
+   ```
+
+4. 如果想将`const`的对象冻结,应该用Object.freeze方法
+
+   ```js
+   const foo = Object.freeze({});
+   
+   // 常规模式时,下面一行不起作用;
+   // 严格模式时,该行会报错
+   foo.prop = 123;
+   ```
+
+5. 除了将对象本身冻结,应该要将对象的属性也得冻结
+
+   ```js
+   var constantize = (obj) => {
+     Object.freeze(obj);
+     Object.keys(obj).forEach( (key, i) => {
+       if ( typeof obj[key] === 'object' ) {
+         constantize( obj[key] );
+       }
+     });
+   };
+   ```
+
+# 变量的解构赋值
+
+1. 模式匹配,不匹配的返回`undefined`
+
+2. 实现方式,可能是对等号右边的值遍历。**Iterator 接口**
+
+   ```javascript
+   // 报错
+   let [foo] = 1;
+   let [foo] = false;
+   let [foo] = NaN;
+   let [foo] = undefined;
+   let [foo] = null;
+   let [foo] = {};
+   ```
+
+3. 允许制定匹配失败的默认值,但只有某个数组成员严格等于`undefined`时,默认值才会生效(null不生效)
+
+   ```javascript
+   let [foo = true] = [];
+   foo // true
+   let [x, y = 'b'] = ['a']; // x='a', y='b'
+   let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
+   
+   
+   let [x = 1] = [undefined];
+   x // 1
+   let [x = 1] = [null];
+   x // null
+   ```
+

BIN
README.md


+ 0 - 0
k8s/ks8了解学习.md


+ 140 - 0
lua/lua功能性函数.md

@@ -0,0 +1,140 @@
+# ENTNGX所有文件codeview
+
+## 背景与目标
+
+1. 要求对所有功能性函数熟记于心,至少下次用的时候能想到
+
+## ENTNGX下的配置文件
+
+1.  **.gitignore** 文件,用于忽略文件的add . 
+
+2.  .gitlab-ci.yml 文件,用于管理项目的runner任务
+   首先,before和afterscript都是空,这里不管
+   **variables**中定义了不少常量?变量。这里是ip变量的作用是,每个tag打上去后,会执行script,然后会curl 到每个ip服务上,启动唤醒,然后nginx -reload啥的(opent,opens)
+   **stages**:定义阶段,后续每个job中就可以使用这些阶段
+   不同job中的相同stages是并行的,但各个阶段是顺序的(比如说这个tag,触发了多个job,这些job都会同时执行build_dev 都执行通过后,会同时执行下一阶段)
+
+   **script** 这里Runner执行的命令或脚本,这里还没完全看懂
+
+   **only** 、**except** 这两个联系在一起,用于打tags行为与目标服务器的绑定。
+   only是,只有符合下面名称的tag名称,才会执行创建job(即执行这个job)
+
+3.  **.luacheckrc** 文件 
+   暂时不清楚这个
+   作用可能是对lua 全局变量的控制,比如说忽略
+
+4.  **elk_log_handler.lua** 文件
+   用于将日志信息交接到Kibana,[!网址连接](http://log-kibana.office.qixin007.com/app/kibana#/discover/96a82b50-9dbf-11eb-954e-41b83028731f?_g=h@44136fa&_a=h@d74cfcc),(志东的账号),数据结构是契合的。
+   用户请求lua接口,均会进入log_by_lua_file 阶段,执行elk_log_handler.lua文件
+   ![image-20211019170336162](C:\Users\fengchun_yuan\AppData\Roaming\Typora\typora-user-images\image-20211019170336162.png)
+   然后再elk_log_handler.lua中把日志发到Kibana
+
+   用户请求有时候会返回406,而 elk 在遇见 非200 返回时会无法打印到 Kibana 上面去,用log_by_lua_file这个阶段,不会受到非200返回的影响,所以能将信息打印到Kibana 去
+   **ngx.is_subrequest** 这个字段是如何为true的?这个还不清楚
+
+5.  **elk_socket_obj.lua** 
+   [!rawget函数的使用](https://www.jianshu.com/p/78f0e050ddad)
+   大概作用是,区分table与元表中字段的值
+
+6. **fastcgi_params** nginx自带的配置文件
+
+7.  **fastcgi.conf** nginx自带的配置文件
+
+8.  **init-by-lua.lua** lua执行阶段,初始化阶段,在此加载各种方法
+
+9.  **koi-utf,koi-win,mime.types,win-utf** nginx自带的
+
+10.  **netutil.lua** 这个方法没用过,大概是格式转换,暂时不深究
+
+11.  **nlog.lua**  包含所有nlog方法
+
+12.  **sit.conf  ------ uat.conf ---------- prod.conf ** nginx的代理配置文件,不同环境的中间件配置server不同(xxx_middleware_server)
+
+## backen_by_env文件夹
+
+1. sit_middleware_backend.conf
+2. uat_middleware_backend.conf
+3. prod_middleware_backend.conf
+
+**负载均衡**
+
+## route文件夹
+
+各个接口模块的路由
+
+**route_by_env文件夹**
+
+主要是上海接口的中间件路由,区分sit,uat,prod环境
+
+## util、constant文件夹
+
+包含各种功能性函数方法
+
+## config
+
+中间件、ots、es的upstream
+
+**反向代理与正向代理的区别**:**正向代理**代理的对象是客户端,**反向代理**代理的对象是服务端
+VPN,正向代理,用户通过代理上谷歌,谷歌不知道真实的用户是谁,隐藏的是客户端
+lua,反向代理,用户请求dev服务器,经过upsteam,由多个服务提供服务器,用户不知道最终是哪个ip的服务器提供的服务,隐藏的是服务端
+
+## constants目录下的文件
+
+1. area_const.lua 省市区的code—map,也可以反转,通过省市区来map-code(但是逻辑并没有完全完善)
+2. area_ip_const.lua    是1的加强版,比较通用的区域码map方法area_ip_const.areaCode_map
+3. bidding_const.lua  招投标
+   find_notice_type_func方法,用户输入code,返回notice_types列表
+   剩下的大多是一些字段map
+4. ent_tags_const.lua 
+   企业标签的tag_map,比如A股上市,国家技术创新中心,新三板啥的
+   包含desc字段(description),对map值的描述
+
+## util目录下的文件
+
+1. **bidding_util.lua** 招投标的轮子,但并不通用
+   比较通用的方法是add_etype 和add_info,配合common_util.get_etype_by_eid_list使用
+
+2.  **common_util.lua**  通用方法
+
+   get_requests_args、get_request_id
+   table的切片方法,merge方法,unique(去重),contains(table中是否有某个元素),table.sort(tb,function(a,b)) 
+   stiring_split方法
+   replace_string (替换,一一对应)
+
+   common_util.deep_copy 深拷贝
+   获取字符串长度:string.get_string_length
+   common_util.format_percent 有关百分比及保留小数(可以优化的)
+   common_util.get_etype_by_eid_list  获取etype,返回已经聚合的结果
+   common_util.person_job_sort_map 人员职位排序map
+
+   common_util.get_month_date 获取最近连续m个月的月份日期,还有每个月的第一天,最后一天(上下文)
+
+   随机数方法(随机取值),不知道作用
+   还有好多没用到的方法(有待优化),这里不作记录
+
+3.  **enterprise_panorama_util.lua** 
+   deal_start_year_func 处理成立年限,应该算是通用方法
+   deal_reg_capi_func 注册资本筛选,通用方法
+   deal_area_func 区域筛选
+   deal_domain_func 行业筛选
+   deal_distance_func 距离筛选
+   check_point 企业标签筛选  (创新情报的筛选搜索列表项有个类似的轮子)
+   deal_status_func 经营状态筛选
+   format_finance_amount 融资金额格式化
+
+4.  **env_variate_util** 暂时不看
+
+5.  **es_util.lua**
+
+   get_format_body_data 该方法对响应结果的状态码没有进行判断
+
+6.  **middleware_util.lua**
+   get_format_body_data 该方法对响应结果有进行判断,status==1
+   get_format_body_data2 与上一方法的区别在于,不包含对status的判断,常用于上海接口
+   get_enterprise_info_by_condition  参考wiki中的使用说明
+   get_ent_other_count 参考wiki
+   capture_multi  三个参数,params_arr, key_list, format_func
+   params_arr  请求队列,table
+   key_list 如果key_list不传,返回结果就是list,如果传key_list,那么 结果还是table,第一个元素 的key是 key_list[1],value是第一个请求的响应
+   format_func 是对结果的判断方法,默认判断方法是middleware_util.get_format_body_data ,即默认会对status判断,所有上海接口,不能用这个并发
+

BIN
mysql/mysql实战45讲.assets/image-20211101161431695.png


+ 197 - 0
mysql/mysql实战45讲.md

@@ -0,0 +1,197 @@
+# Mysql 实战45讲
+
+## 一条SQL查询语句执行过程
+
+### MYSQL 基本架构
+
+![image-20211101161431695](E:\YWJL\任务总览\之后要看的东西\mysql\mysql实战45讲.assets\image-20211101161431695.png)
+
+存储引擎的选择,默认,innodb
+
+连接器,长连接,短连接,爆内存,权限认证(拍照机制)
+
+查询缓存,key-value映射。key是语句,value是查询结果
+
+分析器,提取字段,比如把select后面跟着的东西提取出来。参数提取完成后,执行语法分析,看看sql语句是否满足。
+
+优化器,选择最后的查询语句。比如在表里面有多个索引的时候,决定使用哪个索引
+
+执行器,先判断权限是否满足,然后执行语句
+
+## 日志系统,sql更新语句执行过程
+
+**查询语句的那一套流程,更新语句也是同样会走一遍。**
+
+**更新语句会涉及两个重要的日志模块:redo log 和binlog**
+
+**redo log 和 binlog 区别**
+
+**更新语句执行流程**
+
+**两阶段提交机制**
+
+### redo log
+
+引擎层,是InnoDB引擎特有的日志。
+
+参考酒店老板,账本,粉笔板,赊欠事件。
+
+当赊欠的人少(更新操作较少),酒店老板就对账本完成赊欠的记录操作(即数据更新),而当赊欠的人很多,老板忙不过来的时候,会先把操作记录在粉笔板上,等闲下来再针对粉笔板上的记录,进行账本的更新。
+这里把赊账记录在粉笔板上,就相当于服务器将更新操作,记录在redo log 中。redo log 是物理日志,记录的是“在某个数据页上做了什么修改”。
+而当老板持续一段时间都特别忙,粉笔板上写满了赊欠的人,写不下后,老板会停止“把赊欠记录写在粉笔板”这一行为,会把粉笔板上的记录与账本中核对,进行更新操作。然后后粉笔版清除,这样就能继续记了。
+所以,当一段时间mysql更新语句的操作量都非常大的话,后面会有一段时间,mysql的吞吐量急剧减小(执行redo log 的日志更新),这就涉及到一个问题:更新不实时,会有影响的吧!
+
+### binlog
+
+Server层的日志,binlog(归档日志)
+
+binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”
+
+因为最开始 MySQL 里并没有 InnoDB 引擎。MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系统——也就是 redo log 来实现 crash-safe 能力。
+
+### redo log 与 binlog的区别
+
+这两种日志有以下三点不同。
+
+1. redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
+
+2. redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。
+
+3. redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
+
+###  *redo log 两阶段提交机制*
+
+## 事务隔离
+
+**事务一致性**
+
+**ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)**
+
+## 索引
+
+### mysql B+ 树
+
+MySQL的存储结构
+
+表存储结构
+
+单位:表>段>区>页>行
+
+在数据库中, 不论读一行,还是读多行,都是将这些行所在的页进行加载。也就是说存储空间的基本单位是页。
+
+一个页就是一棵树B+树的节点,数据库I/O操作的最小单位是页,与数据库相关的内容都会存储在页的结构里。
+
+B+树索引结构
+
+在一棵B+树中,每个节点为都是一个页,每次新建节点的时候,就会申请一个页空间
+
+同一层的节点为之间,通过页的结构构成了一个双向链表
+
+非叶子节点为,包括了多个索引行,每个索引行里存储索引键和指向下一层页面的指针
+
+叶子节点为,存储了关键字和行记录,在节点内部(也就是页结构的内部)记录之间是一个单向的表
+
+B+树页节点结构
+
+有以下几个特点
+
+将所有的记录分成几个组, 每组会存储多条记录,
+
+页目录存储的是槽(slot),槽相当于分组记录的索引,每个槽指针指向了不同组的最后一个记录
+
+我们通过槽定位到组,再查看组中的记录
+
+页的主要作用是存储记录,在页中记录以单链表的形式进行存储。
+
+单链表优点是插入、删除方便,缺点是检索效率不高,最坏的情况要遍历链表所有的节点。因此页目录中提供了二分查找的方式,来提高记录的检索效率。
+
+B+树的检索过程
+
+我们再来看下B+树的检索过程
+
+从B+树的根开始,逐层找到叶子节点。
+
+找到叶子节点为对应的数据页,将数据叶加载到内存中,通过页目录的槽采用二分查找的方式先找到一个粗略的记录分组。
+
+在分组中通过链表遍历的方式进行记录的查找。
+
+为什么要用B+树索引
+
+数据库访问数据要通过页,一个页就是一个B+树节点,访问一个节点相当于一次I/O操作,所以越快能找到节点,查找性能越好。
+
+B+树的特点就是够矮够胖,能有效地减少访问节点次数从而提高性能。
+
+下面,我们来对比一个二叉树、多叉树、B树和B+树。
+
+二叉树
+
+二叉树是一种二分查找树,有很好的查找性能,相当于二分查找。
+
+但是当N比较大的时候,树的深度比较高。数据查询的时间主要依赖于磁盘IO的次数,二叉树深度越大,查找的次数越多,性能越差。
+
+最坏的情况是退化成了链表,如下图
+
+为了让二叉树不至于退化成链表,人们发明了AVL树(平衡二叉搜索树):任何结点的左子树和右子树高度最多相差1
+
+多叉树
+
+多叉树就是节点可以是M个,能有效地减少高度,高度变小后,节点变少I/O自然少,性能比二叉树好了
+
+B树
+
+B树简单地说就是多叉树,每个叶子会存储数据,和指向下一个节点的指针。
+
+例如要查找9,步骤如下
+
+我们与根节点的关键字 (17,35)进行比较,9 小于 17 那么得到指针 P1;
+
+按照指针 P1 找到磁盘块 2,关键字为(8,12),因为 9 在 8 和 12 之间,所以我们得到指针 P2;
+
+按照指针 P2 找到磁盘块 6,关键字为(9,10),然后我们找到了关键字 9。
+
+B+树
+
+B+树是B树的改进,简单地说是:只有叶子节点才存数据,非叶子节点是存储的指针;所有叶子节点构成一个有序链表
+
+例如要查找关键字16,步骤如下
+
+与根节点的关键字 (1,18,35) 进行比较,16 在 1 和 18 之间,得到指针 P1(指向磁盘块 2)
+
+找到磁盘块 2,关键字为(1,8,14),因为 16 大于 14,所以得到指针 P3(指向磁盘块 7)
+
+找到磁盘块 7,关键字为(14,16,17),然后我们找到了关键字 16,所以可以找到关键字 16 所对应的数据。
+
+B+树与B树的不同:
+
+B+树非叶子节点不存在数据只存索引,B树非叶子节点存储数据
+
+B+树使用双向链表串连所有叶子节点,区间查询效率更高,因为所有数据都在B+树的叶子节点,但是B树则需要通过中序遍历才能完成查询范围的查找。
+
+B+树每次都必须查询到叶子节点才能找到数据,而B树查询的数据可能不在叶子节点,也可能在,这样就会造成查询的效率的不稳定
+
+B+树查询效率更高,因为B+树矮更胖,高度小,查询产生的I/O最少。
+
+这就是MySQL使用B+树的原因,就是这么简单!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

BIN
redis.zip


BIN
redis/redis_string了解学习.assets/image-20210917160229829.png


BIN
redis/redis_string了解学习.assets/image-20210917160325607.png


BIN
redis/redis_string了解学习.assets/image-20211018171806273.png


+ 43 - 0
redis/redis_string了解学习.md

@@ -0,0 +1,43 @@
+# redis
+
+## 启动
+
+```
+C:\Program Files\Redis>redisredis-server.exe redis.windows.conf
+PS C:\Users\fengchun_yuan> redis-cli.exe
+
+```
+
+![image-20210917160229829]( ./redis_string了解学习.assets/image-20210917160229829.png)
+
+### string
+
+![image-20210917160325607]( .\redis_string了解学习.assets\image-20210917160325607.png)
+
+这是最简单的string类型。
+这种类型,Redis就像一个可以持久化的memcached服务器(注:memcache的数据仅保存在内存中,服务器重启后,数据将丢失)。
+
+## Redis 命令
+
+下文中key,一般情况下均关联value
+
+1. DEL  key 删除key,key存在,就返回1,key不存在,返回0
+2. DUMP 序列化,将value转成字节
+   ![image-20211018171806273]( .\redis_string了解学习.assets\image-20211018171806273.png)
+3. EXISTS 检查key是否存在,存在返回1,不存在返回0
+4. EXPIRE 对key设置过期时间,以秒为单位
+5. EXPIREAT 以UNIX时间戳(unix timestamp)格式设置key的过期时间
+6. EXPIREAT 对key设置过期时间,以毫秒为单位
+7. PATTERN 查找所有符合模式pattern的key
+8. MOVE 将key移动指定数据库    redis默认使用数据库0,即 redis  > ...          redis:数据库名称(默认为0不显示)
+9. PERSIST 移除key的过期时间,使其永不过期   移出成功,返回1;若eky不存在或者没设置过期时间,返回0
+10. PTTL 以毫秒为单位返回key的剩余过期时间。 key不存在,返回-2;key存在但没设置剩余生存时间,返回-1
+11. TTL 以秒为单位……
+12. RANDOMKEY 从当前数据库中随机返回一个key
+13. RENAME 修改key的名称。改名成功时候返回ok,失败时返回error。当old_key_name与new_key_name相同,或者old_key_name不存在时,返回error。当new_key_name已经存在时,进行重命名,new_key_name原先的value会被旧的覆盖
+14. RENAMENX 修改key的名(当且仅当new_key_name不存在)。修改成功,返回1。若new_key_name已经存在,返回0;若old_key_name或new_key_name都不存在,返回0
+
+### String部分
+
+1. GETRANGE 获取存在指定key中的子字符串,截取范围由start和end两个偏移量决定(包括start和end)
+2. GETSET

+ 260 - 0
zookeeper/关于分布式锁.md

@@ -0,0 +1,260 @@
+# 分布式锁之Zookeeper
+
+## 参考文档
+
+https://mp.weixin.qq.com/s?__biz=MzAwNDA2OTM1Ng==&mid=2453141835&idx=1&sn=ff0867c9f5ecec9ea8187a21ef7edb2c&chksm=8cf2dbc8bb8552def9bb27fc6302e735eccdd68be5344d6e1d51d244b8e0753d56317cfe8bf2&token=1478279203&lang=zh_CN#rd
+
+## 源代码
+
+https://github1s.com/AobingJava/Thanos/blob/master/laogong/src/main/java/zookeeper/Zk.java
+
+```java
+package zookeeper;
+
+import org.I0Itec.zkclient.IZkDataListener;
+import org.I0Itec.zkclient.ZkClient;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+
+/**
+ * @Description: zkTest
+ * @Author: 敖丙
+ * @date: 2020-04-06
+ **/
+public class Zk implements Lock {
+    private static CountDownLatch cdl = new CountDownLatch(1);
+
+    private static final String IP_PORT = "127.0.0.1:2181";
+    private static final String Z_NODE = "/LOCK";
+
+    private volatile String beforePath;
+    private volatile String path;
+
+    private ZkClient zkClient = new ZkClient(IP_PORT);
+
+    public Zk() {
+        if (!zkClient.exists(Z_NODE)) {
+            zkClient.createPersistent(Z_NODE);
+        }
+    }
+
+    public void lock() {
+        if (tryLock()) {
+            System.out.println("获得锁");
+        } else {
+            // 尝试加锁
+            // 进入等待 监听
+            waitForLock();
+            // 再次尝试
+            lock();
+        }
+
+    }
+
+    public synchronized boolean tryLock() {
+        // 第一次就进来创建自己的临时节点
+        if (StringUtils.isBlank(path)) {
+            path = zkClient.createEphemeralSequential(Z_NODE + "/", "lock");
+        }
+
+        // 对节点排序
+        List<String> children = zkClient.getChildren(Z_NODE);
+        Collections.sort(children);
+
+        // 当前的是最小节点就返回加锁成功
+        if (path.equals(Z_NODE + "/" + children.get(0))) {
+            System.out.println(" i am true");
+            return true;
+        } else {
+            // 不是最小节点 就找到自己的前一个 依次类推 释放也是一样
+            int i = Collections.binarySearch(children, path.substring(Z_NODE.length() + 1));
+            beforePath = Z_NODE + "/" + children.get(i - 1);
+        }
+        return false;
+    }
+
+    public void unlock() {
+        zkClient.delete(path);
+    }
+
+    public void waitForLock() {
+
+        IZkDataListener listener = new IZkDataListener() {
+            public void handleDataChange(String s, Object o) throws Exception {
+            }
+
+            public void handleDataDeleted(String s) throws Exception {
+                System.out.println(Thread.currentThread().getName() + ":监听到节点删除事件!---------------------------");
+                cdl.countDown();
+            }
+        };
+        // 监听
+        this.zkClient.subscribeDataChanges(beforePath, listener);
+        if (zkClient.exists(beforePath)) {
+            try {
+                System.out.println("加锁失败 等待");
+                cdl.await();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        // 释放监听
+        zkClient.unsubscribeDataChanges(beforePath, listener);
+    }
+
+    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
+        return false;
+    }
+
+    public void lockInterruptibly() throws InterruptedException {
+
+    }
+
+    public Condition newCondition() {
+        return null;
+    }
+}
+
+```
+
+## 优化后的代码
+
+```java
+package zookeeper;
+
+import org.I0Itec.zkclient.IZkDataListener;
+import org.I0Itec.zkclient.ZkClient;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+
+/**
+ * @Description: zkTest
+ * @Author: 敖丙
+ * @date: 2020-04-06
+ **/
+public class Zk implements Lock {
+    private static CountDownLatch cdl = new CountDownLatch(1);
+
+    private static final String IP_PORT = "127.0.0.1:2181";
+    private static final String Z_NODE = "/LOCK";
+
+    private volatile String beforePath;
+    private volatile String path;
+
+    private ZkClient zkClient = new ZkClient(IP_PORT);
+
+    public Zk() {
+        if (!zkClient.exists(Z_NODE)) {
+            zkClient.createPersistent(Z_NODE);
+        }
+    }
+
+    public void lock() {
+        if (tryLock()) {
+            System.out.println("获得锁");
+            unlock();
+        } else {
+            // 尝试加锁
+            // 进入等待 监听
+            waitForLock();
+            // 再次尝试
+            lock();
+        }
+
+    }
+
+    public synchronized boolean tryLock() {
+        // 第一次就进来创建自己的临时节点
+        if (StringUtils.isBlank(path)) {
+            path = zkClient.createEphemeralSequential(Z_NODE + "/", "lock");
+        }
+
+        // 对节点排序
+        List<String> children = zkClient.getChildren(Z_NODE);
+        Collections.sort(children);
+
+        // 当前的是最小节点就返回加锁成功
+        if (path.equals(Z_NODE + "/" + children.get(0))) {
+            System.out.println(" i am true");
+            return true;
+        } else {
+            // 不是最小节点 就找到自己的前一个 依次类推 释放也是一样
+            int i = Collections.binarySearch(children, path.substring(Z_NODE.length() + 1));
+            beforePath = Z_NODE + "/" + children.get(i - 1);
+        }
+        return false;
+    }
+
+    public void unlock() {
+        zkClient.delete(path);
+    }
+
+    public void waitForLock() {
+
+        IZkDataListener listener = new IZkDataListener() {
+            public void handleDataChange(String s, Object o) throws Exception {
+            }
+
+            public void handleDataDeleted(String s) throws Exception {
+                System.out.println(Thread.currentThread().getName() + ":监听到节点删除事件!---------------------------");
+                cdl.countDown();
+            }
+        };
+        // 监听
+        this.zkClient.subscribeDataChanges(beforePath, listener);
+        if (zkClient.exists(beforePath)) {
+            try {
+                System.out.println("加锁失败 等待");
+                cdl.await();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        // 释放监听
+        zkClient.unsubscribeDataChanges(beforePath, listener);
+    }
+
+    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
+        return false;
+    }
+
+    public void lockInterruptibly() throws InterruptedException {
+
+    }
+
+    public Condition newCondition() {
+        return null;
+    }
+}
+
+```
+
+## 分析
+
+一些要注意的点
+
+1. 比如说10个进程,同时执行tryLock(),那么,第一个进来的进程,会创建第一个临时节点,后面9个进程会按次序创建节点
+2. 每个进程进来,都会将所有的节点sort一下,当且仅当这个临时节点,是最小的一个节点的时候,就加锁,返回true;其余节点不匹配,就返回false
+3. 返回true,执行锁的内容,然后unlock,删除path
+   ~~这里应该有个节点的重定向,要重新选中最小的一个节点,进行加锁~~
+4. 返回false,进入waitForLock,~~先监听path是否删除,删除后,执行cdl~~
+   如果存在beforpath,就await,然后释放节点,即断开链接
+5. A进程正在操控锁,这个时候A进行挂起了,就会死锁
+   但由于是临时节点,A进程挂起后,A会自动删除,这个时候就不会死锁了
+6. 整个逻辑就是,不停地创建临时节点,然后消逝,总有一个最小的节点存在。
+   当最小节点存在时,就加锁
+   当最小节点存在时,其余节点自动创建——>删除
+   当最小节点挂起时,最后节点自动删除,会新创建节点
+

+ 1 - 0
消息队列/消息队列.md

@@ -0,0 +1 @@
+https://www.cnblogs.com/rsapaper/p/10382423.html