禅修冥想

压力情绪与睡眠 我们的工作生活会存在各种各样的压力源:经济、各类人际关系、孩子教育、职业发展目标、娱乐方式、生活习惯,这些压力源会潜移默化影响自己的精神层面的东西。记得小时候听过类似观点“人类大脑最多开发了10%,利用好10%就能成为天才”,“大脑越用越灵,不用则废”,带着这样的印象生活在当下充满丰富资源的环境里,生活、工作、娱乐无不用尽大脑的能量,竭尽全力开发另外90%: 照顾家人、朋友、同事的情绪,运转大脑思索如何针对不同的人进行有效沟通 思考总结优化工作方法和内容,获得更好的结果 高效利用时间,在多个任务中频繁上下文切换,并行完成更多的任务 信手拈来的视频、游戏、新闻、社交媒体,带来多巴胺、认同感、最新信息的安定感 源源不断的碳水化合物的食物,让大脑拥有更多能量 长期如此这样的脑力压榨,感觉越来越颓废。仔细反思一下,这样的“高效”形成了一个恶性循环: 用脑过度产生疲惫; 长期疲惫产生大量负面情绪; 情绪失控则伤害人际关系,情绪压抑则在大脑内不断反刍,两种情况都会产生不快乐; 寻找快乐,游戏、社交网络、视频,刺激产生快乐,大脑疲惫而兴奋; 吃更多的碳水给大脑能量; 兴奋、情绪反刍,失眠,睡眠中脑脊液清洗程度越来越低,大脑无法完成修复; 第二天起床,昏昏沉沉,重复1-6,并且在大脑中刻下了大量负面回路; 有人说,要自律,该睡觉时睡觉,控制饮食。但是往往是“该睡觉时大脑渴望得到快乐”,“该控制饮食时大脑想要更多能量”。自律不是万能解药,而且自律也会消耗意志力,达不到自律目标时,会带来沮丧和自我否定,加重负面情绪。既然不是快乐的多巴胺,那么我们应该从本质解决问题——大脑需要什么?我的结论是: 需要两样东西:1、高效的代谢,把大脑清洗干净;2、放松、安定、喜乐的情绪,使得大脑中的负面回路消失。 满足需要 适量有氧运动,提供脑部代谢和氧气量; 有效的睡眠,脑脊液清洗; 正面安定的情绪,减少大脑走入自动的负面回路。 可行性 规律执行有氧运动计划,低成本,可行; 良好的睡眠习惯依赖于稳定的情绪; 稳定的情绪,减少大脑中的负面回路; 最后待解决的问题归结到:如果负面情绪不能丢到人际关系里产生伤害,那怎么让它消失?如果负面脑回路循环往复,如何打破循环? 接触禅修冥想 最早知道禅修冥想是各大健身瑜伽机构,没有考虑是因为要消耗时间去学,各大机构的理论五花八门,存在一些疑虑。以前基于OpenResty开发时,读了IT业界大神章亦春的很多文档,字里行间感觉他是个很靠谱的天才。偶然的机会,发现他就是禅修者,于是试着去了解他的方法。他提供了两条路径:1)A.K. Warder的《印度佛教史》,书中讲述了最早佛陀教的禅修理论和方法;2)美国维马拉西尊者提供的Tranquil Wisdom Insight Meditation (T.W.I.M.)内观禅修的方法,基于“梵天精舍修法”。1)辗转买到了《印度佛教史》,但没读完,其中禅修方法论部分比较复杂,还没头绪;2)是精炼的内观禅修法,可以引导初学者入门。因此2)是当下开始实践最好的选择。 方法 禅修中,成功的冥想需要产生正念的技能,需要能够理解思绪产生和转化的过程。TWIM给初学者提供了一套方法,叫6R’s,也就是6个词:Recognize、Release、Relax、Re-smile、Return、Repeat。 具体实践:先找个相对安静舒适的环境,静坐着、躺着都可以,要求是身体能放松舒适,不一定模仿盘腿坐。然后平定心情,先把注意力引导到自己的呼吸,开始: Recognize觉知:注意自己有什么思绪,任何思绪想法产生,不要对抗,去觉察它们的存在;就像海平面里跳出的鲸鱼海豚一样,看着它们; Release释放:思绪产生后,不要试图忘记,让它自行离开;就像鲸鱼海豚回到海里,消失;不驱赶,不留恋; Relax放松:思绪自行离开后,放松大脑;个人感觉头部里面的大脑由紧张发胀,到松弛;可以像呼吸一样,不断松弛; Re-smile:在意念中微笑(不是面部微笑,面部放松即可)。意念微笑的意思是,在意念中注入正念(爱、怜悯、善良、温和、美好),过程中去觉察自己的意念变化。这步比较重要,6R’s提供了一个具象的方式叫“心灵挚友”,去想一个真实存在的“心灵上的挚友”(TA),可以是朋友、老师、发小等等的同性的朋友,需要是在自己生活中真实存在的,身边的,并对TA个人有一定了解的人。意念中加入正念:想着希望TA的好,想着自己喜欢TA的感受,想着自己真诚希望TA好,想着与TA在一起的感觉,需要是固定的一位,不要跳跃切换。其他的具象正念也可以是厨房里自己一条喜爱的开心的狗,微笑的小孩等等。 Return: 过程中发生变化,让自己回归到呼吸、放松和正念中; Repeat:一遍一遍重复上述,用新的意念习惯取代旧的痛苦意念的习惯。从中可以内观到:痛苦是什么,觉察到造成紧张和紧绷的原因,学习如何释放和放松,探索如何停止自己造成的痛苦。 注意: 不要把步骤变成“纸上、脑海里的词语”,而是进行意念变化的实践; 冥想是为了了解意念转移的过程,不是为了对抗意念;对抗和强力改变会带来诸多痛苦; 怎么应对当前的情绪决定者未来会发生什么。 个人体验:30分钟弹指一挥间就过去,大脑得到了放松,混沌状态消失了,变得洞明和敏捷。 参考资源 Youtube the-6rs

JAVA幻象引用与NIO的直接内存释放

JAVA幻象引用 最近详细看了JAVA四种引用模式的机制,其中有个幻象引用,它无法获取到具体的对象值,那问题来了,这玩意到底有什么用呢? 幻象引用:没有强、软、弱引用可达,并且已经finalize过了,只有幻象引用只想这个对象。 引用队列:当GC释放相应当关联引用队列的引用所指向的对象时,会将引用push进队列,作为一种通知,方便后续操作。 NIO的DirectByteBuffer如何释放堆外内存 众所周知,在netty中我们尽可能使用PooledUnsafeDirectByteBuf去复用堆外的直接内存,原因就是避免创建和销毁的开销,统一管理。 那么既然堆外内存不在Heap内管理,它如何释放呢?下面摘自Stackoverflow (链接1): When you are calling the JNI: NewDirectByteBuffer(void* address, jlong capacity) a DirectByteBuffer object will be created, using the following constructor: private DirectByteBuffer(long addr, int cap) { super(-1, 0, cap, cap); address = addr; cleaner = null; att = null; }; Notice that the cleaner property is null. If you create a DirectByteBuffer via ByteBuffer.allocateDirect() the cleaner property is set (see source code of DirectByteBuffer).

用OpenResty构建网关(3)—— 数据访问

场景与挑战 作为API网关,我们有很多数据访问的需求,这些需求内包含两类数据: 网关的配置数据:数据更新不频繁,但是要求准实时生效,主要包含各种网关规则、API信息。 网关动态数据:数据写入频繁,吞吐量大,主要是限流的实时数据、API的接口调用时长收集的数据。 数据源与缓存思路 既然网关对数据读取效率上有着严格的要求,我们不能选择传统的关系数据库来作为运行时数据源。那么,在原始的界面配置读写的数据库与网关运行态之间: 用redis(对于短小的数据,吞吐能力强)来做运行时数据源,并且在网关与redis之间做一个缓存。 此思路带来两个问题: redis数据与数据库数据间存在同步的问题,缓存与redis之间存在同步问题。 redis写入动态实时的限流、API收集类数据时,需要考虑分摊压力,减少写入时延。 那么我来各个击破,做一个权衡(Tradeoff)。 方案细节 1.配置数据同步 网关的配置数据不是业务数据,没有事务问题,对实时性有一定的容忍力。但是,网关需要在一定的时间内获取到最新的配置,这个时间最坏做到在5秒以内。这样,数据更新的解决办法是:以通知形式告知所有网关节点进行缓存失效,然后去重新加载; 如何通知?有两种选择:1、实现gossip协议,让收到数据 老规矩,先上图: 2.redis cluster 数据分片控制 初始阶段(Initialization Phase):全局启动时初始化init_by_lua,nginx worker启动时的初始化init_worker_by_lua; 重写/接入阶段(Rewrite/Access Phase):set_by_lua流程分支处理判断变量初始化,rewrite_by_lua转发、重定向、缓存等功能(例如特定请求代理到外网),access_by_lua*IP 准入、接口权限等情况集中处理(例如配合 iptable 完成简单防火墙); 内容阶段(Content Phase):content_by_lua内容生成,balancer_by_lua代理内容,header_filter_by_lua响应头部过滤处理(例如添加头部信息),body_filter_by_lua响应体过滤处理(按chunk获取到内容,无法获取整体,例如完成应答内容统一成大写); 日志阶段(Log Phase):log_by_lua*会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器)。 lua语言本身、绑定变量不赘述,openresty官网有详细说明,我讲一下在网关开发过程中一些 trick and trap: 使用lua_shared_dict 这个LRU缓存配合Redis的pub/sub机制,做配置数据加载/读取/更新; 需要做集群,开发gossip协议太复杂,节点先为自己生成UUID,再用Lua脚本向Redis竞争成为Master,先到先得,定时续命; API的质量数据收集、动态限流均采用init阶段的定时器向Redis执行Lua脚本来做,发现超阈值立即发通知,进行限流规则的变更; 尽可能使用local变量、函数,尽可能使用lua Module,用require来组织模块链接; Redis Cluster分片需要考虑业务数据分片,这里采用了按时间片分片。执行redis lua时,需要对redis的lua客户端改造,支持lua脚本load到所有cluster节点,支持按key的hash tag的slot结果寻找对应的cluster node; 采用蓄水池算法进行采样,防止内存不够用。 网关架构 整个API网关主要为了做如下几件事情: 限流:根据规则对特定的请求进行限流; 分流:根据规则对特定的请求进行分流; 数据收集:对所有请求数据进行按API标识和时间片收集采样; 降级(动态限流) 网关的整体架构如下图: 后续 在这个专题的后几篇,讲把网关实现的细节关键点一一讲述。

用OpenResty构建网关(2)—— 命令链实现

设计模式 网关是对HTTP流量的检查、分发,需要支持易扩展、可分发的特性。为了支持这个特性,我们很自然的想到设计模式中的职责链模式。 职责链定义(来自wikipedia): 责任链模式在面向对象程式设计里是一种软件设计模式,它包含了一些命令对象和一系列的处理对象。每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。 有了职责链,请求进来以后,经过所有的处理命令,才会到达下游的后端服务。如果我们有新的需求,可以用职责链直接扩展,修改配置直接集成新的处理命令模块。 Lua面向对象 那么问题来了,职责链在Lua这个语言怎么实现呢? 上文我们其实已经提到了,Lua可以用 MetaTable的__index操作符以及语法糖":"(a:b(param1)代表 a.b(self, param1))来支持面向对象的特性。当然,class模版定义,我们尽量使用Lua的module来做,代码看起来更加没有违和感。 Lua命令链实现 **command实现:**实现命令链的命令,我们需要定义一个Command接口,Command接口中有execute方法,Lua里没有接口的概念,我们就直接定义个Prototype作为一个抽象类的作用: --[[ base command of chain,to be extend ]]-- local _M = { _VERSION = '0.01' } local mt = { __index = _M } --Constructor function _M:new() local o = {type = 'command'} return setmetatable(o, mt) end function _M:execute(context) ngx.log(ngx.ERR, "Not Implement!") --true:stop and return; false: continue to the next chain return false end return _M 可以看到,我们通过定义一个模块_M,然后定义原型mt重载 __index操作符,使得mt具有 _M的行为,在_M的new函数中我们可以获得一个对象。

用OpenResty构建网关(1)—— 基础框架与网关架构

Lua语言 Lua语言是一个轻量级的脚本,号称“性能最高的脚本语言“,已经有很多地方使用到它包括nginx、wireshark、wrk、魔兽世界等游戏插件等等。Lua脚本的解释器是C语言实现的,已经有人研究过解释器,才200K左右,它轻巧紧凑,且能与C语言交互,是一种很好的胶水语言。 Lua语言的特性有如下: 类C,大小写敏感,分号可选; 变量类型可推导,作用域默认是全局的,用_G[‘varName’]可访问全局变量,可以指定为local的; nil和false是false,其他一切都是true; 函数闭包,函数可作为变量使用,函数可以有多返回值; Table为推导的,根据值,可以是数组,可以是Map; MetaTable,用来重载各种操作符。其中重载 __index 操作并结合操作符:这个语法糖,可以扩展出面向对象的范式; 利用require、dofile函数可以动态载入lua文件; OpenResty框架 OpenResty是业界大牛章同学的作品,查过一些资料,这位大神低调自由,喜欢讲技术细节,专注写代码写文档,你说这样的程序员能在中国的典型的互联网混么,当然不可能,因为我们的牛人大多谈大架构、大体系、宏观,所以这个人被Cloudflare自由散漫的养着。居说他离开阿里的时候写的代码生成器,后来无人敢改。 废话少说,我们继续OpenResty,它是基于Nginx这个网络服务器,用lua语言扩展开的框架,大致就是在Nginx处理请求的不同阶段,嵌入执行Lua解释器。Lua脚本成了Nginx的接口,这一套东西被包装成了OpenResty这个框架,logo是一只蜂鸟,非常切意。 OpenResty在Nginx的的不同处理阶段嵌入了解释器,不同处理阶段如下图: *图片摘自https://github.com/openresty/lua-nginx-module 初始阶段(Initialization Phase):全局启动时初始化init_by_lua,nginx worker启动时的初始化init_worker_by_lua; 重写/接入阶段(Rewrite/Access Phase):set_by_lua流程分支处理判断变量初始化,rewrite_by_lua转发、重定向、缓存等功能(例如特定请求代理到外网),access_by_lua*IP 准入、接口权限等情况集中处理(例如配合 iptable 完成简单防火墙); 内容阶段(Content Phase):content_by_lua内容生成,balancer_by_lua代理内容,header_filter_by_lua响应头部过滤处理(例如添加头部信息),body_filter_by_lua响应体过滤处理(按chunk获取到内容,无法获取整体,例如完成应答内容统一成大写); 日志阶段(Log Phase):log_by_lua*会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器)。 lua语言本身、绑定变量不赘述,openresty官网有详细说明,我讲一下在网关开发过程中一些 trick and trap: 使用lua_shared_dict 这个LRU缓存配合Redis的pub/sub机制,做配置数据加载/读取/更新; 需要做集群,开发gossip协议太复杂,节点先为自己生成UUID,再用Lua脚本向Redis竞争成为Master,先到先得,定时续命; API的质量数据收集、动态限流均采用init阶段的定时器向Redis执行Lua脚本来做,发现超阈值立即发通知,进行限流规则的变更; 尽可能使用local变量、函数,尽可能使用lua Module,用require来组织模块链接; Redis Cluster分片需要考虑业务数据分片,这里采用了按时间片分片。执行redis lua时,需要对redis的lua客户端改造,支持lua脚本load到所有cluster节点,支持按key的hash tag的slot结果寻找对应的cluster node; 采用蓄水池算法进行采样,防止内存不够用。 网关架构 整个API网关主要为了做如下几件事情: 限流:根据规则对特定的请求进行限流; 分流:根据规则对特定的请求进行分流; 数据收集:对所有请求数据进行按API标识和时间片收集采样; 降级(动态限流) 网关的整体架构如下图: 后续 在这个专题的后几篇,讲把网关实现的细节关键点一一讲述。

NIO非阻塞模式的HTTP Client

背景 这段时间一直在开发一个API网关程序,因为旧的API网关出了一个大问题。 API网关顾名思义,上游的请求进来,下游的请求出去: 1)旧的API网关模式中,上游进来的请求采用NIO的模式处理请求接入,只需要少量IO线程就可以处理上游网络连接,这样上游就可以达到10k的连接数; 2)旧的API网关中,上游的线程模型采用的是Reactor模式,也就是 [CPU数量的IO线程Boss]+[128个请求处理线程worker],中间通过一个ArrayQueue来缓冲请求实体; 3)向下游发送请求时,是在worker线程中基于连接池创建一个HTTP Client 3.x的客户端,选择地址、组织数据,利用客户端对象发送数据,等待返回; 相信一眼就可以看到问题了,正如上文粗体的“等待”提示所述: 当上游有10k的请求连接进来的时候,网关中只能最多同时有128个worker线程等待下游的IO 返回。 这硬生生的给后端来了个“并发限制”,但是当下游某些特征的请求返回比较慢的时候,会造成线程全部类型请求的阻塞,这还能起到“网关”这个冠名的作用么?! 分析与实践 先上图,无图无真相: BIO模式 NIO模式 图片摘自https://github.com/Netflix/zuul/wiki 从BIO的图中看出,正式我们之前的网关模式,后端没有返回网络数据的时候,线程会一直阻塞等待网络返回;但是下一张图给我们提供了一个好的思路,就是: 线程不可以阻塞在网络事件上,而且让网络事件来出发线程的下一步操作。这样的话我们再也不用 担心“多少个线程形成多少个并发”这样无谓的等待。 这也正是NIO、事件通知甚至linux的多路复用epoll的核心思路。 二话不说,准备上代码,上代码前有个整体的代码框架环境的预设: 我们的客户端是处在基于netty这样的优秀的NIO网络通信框架中的,其中上游已经建立一个 worker线程组NioEventLoopGroup来处理网络事件。 整个改造思路是: 下游客户端的IO最好也复用这个事件循环线程组,这样就如上图NIO一样,这个线程组都是由事件 驱动的。 上游的服务端IO已经处理好,如果数据都准备好了,我们准备进入下游客户端,上代码: public class PerHostHttpClientPool implements IHttpClientPool{ private static final Logger logger = LoggerFactory.getLogger(PerHostHttpClientPool.class); private static final int DEFAULT_SO_TIMEOUT_MILLS = 5000; private final String clientPoolName; private final ConnectionProvider pooledConnectionProvider; private final PooledHttpClientConfiguration configuration; public boolean ownResource = false; private final EventLoopGroup eventLoopGroup; private final AtomicInteger total; private final AtomicInteger inUse; public PerHostHttpClientPool(String clientPoolName, PooledHttpClientConfiguration pooledHttpClientConfiguration, EventLoopGroup eventLoopGroup) { this.