首页 文章 精选 留言 我的

精选列表

搜索[玩转 Agent Bucket],共10002篇文章
优秀的个人博客,低调大师

玩转Kubernetes TCP Ingress

问题 在Kuberetes应用中,一般都是通过Ingress来暴露HTTP/HTTPS的服务。但是在实际应用中,还是有不少应用是TCP长连接的,这个是否也是可以通过Ingress来暴露呢?大家知道Kubernetes社区默认带了一个Nginx的Ingress的,而它本身又是支持TCP做反向代理的。所以也就能支持TCP方式的Ingress的。 具体可以参考:https://kubernetes.github.io/ingress-nginx/user-guide/exposing-tcp-udp-services/ 原理 Ingress Controller在启动时会去watch两个configmap(一个tcp,一个 udp),里面记录了后面需要反向代理的TCP的服务以及暴露的端口。如果里面的key-value发生变换,Ingress c

优秀的个人博客,低调大师

玩转Android嵌套滚动

在Android UI开发过程中,经常会遇到嵌套滚动的需求,所谓嵌套滚动,就是父view可以滚动的情况下子view也可以滚动,例如下拉刷新(PullToRefresh)。在微信读书之前的版本中,书籍讨论圈有一个比较复杂的嵌套滚动的例子,我把它抽取出来作为今天讲解的例子: 这个例子的嵌套比较复杂,上方的header为书籍封面,下方是一个ViewPager+TabLayout组成的容器(下文简称VT容器),ViewPager中的三个item为三个列表,也是可以滚动的。业务需求是: VT容器可以滚动; 书籍封面可以滚动,并且有视差; 当VT容器滚动到顶部时,滚动列表,并且滚动可以衔接。 当列表滚动到顶部时,可以滚动书籍封面以及VT容器,并且滚动可以衔接 逻辑清楚了,接下来就看如何实现了。在android5以前,对于这种滚动,我们只能选择自己去拦截事件并处理,但在后面的某个版本,android推出了NestingScroll机制,开发者的日子就好过多了,并且android提供了一个非常好的容器类:CoordinatorLayout,极大的简化了开发者的工作。当然我们也需要投入精力去学习并运用这些新的Api了。 当然,我们也要知道如果没有这些API,我们应当如何去实现这些效果。因此本文会用三种方式去实现这个效果: 纯事件拦截与派发方案 基于NestingScroll机制的实现方案 基于CoordinatorLayout与Behavior方案的实现 示例代码放在Github上,可以clone下来结合文章观看 纯事件拦截与派发方案 这是最为原始的方案,当然也灵活性最高的了。其它的方案原理上都是系统基于它提供的封装。使用这种方案时,我们需要解决以下几个问题: view的滚动(Scroller); view的速度追踪(VelocityTracker); 当VT容器滚动到顶部时,我们如何将事件传递给ListView? 当ListView滚动到顶部时,VT容器如何拦截到事件? 1、2两点属于滚动的基础知识,这里不会做细致的讲解。而第3点为何会出现呢?因为android系统在事件派发时,如果事件被拦截,那么之后的事件都将不会传递给子view了。其解决方案也很简单:在滚动到顶部时主动派发一次Down事件: if(mTargetCurrentOffset+dy<=mTargetEndOffset){ moveTargetView(dy); //重新dispatch一次down事件,使得列表可以继续滚动 intoldAction=ev.getAction(); ev.setAction(MotionEvent.ACTION_DOWN); dispatchTouchEvent(ev); ev.setAction(oldAction); }else{ moveTargetView(dy); } 那么第4点是什么问题呢?这里就需要清楚一个坑点了:不是所用的事件都会走入onInterceptTouchEvent。有一种情况是子View主动调用parent.requestDisallowInterceptTouchEvent(true)来告诉系统说:这个事件我要了,父View不要拦截了。这就是所谓的内部拦截法。在ListView的某些时刻它会去调用这个方法。因此一旦事件传递给了ListView,外部容器就拿不到这个事件了。因此我们要打破它的内部拦截: @Override publicvoidrequestDisallowInterceptTouchEvent(booleanb){ //去掉默认行为,使得每个事件都会经过这个Layout } 方法如上,把requestDisallowInterceptTouchEvent的实现干掉就可以了。 主要的技术点已近提出来了。那么下面就看具体实现,首先看使用xml: <org.cgspine.nestscroll.one.EventDispatchPlanLayout android:id="@+id/scrollLayout" android:layout_marginTop="?attr/actionBarSize" android:layout_width="match_parent" android:layout_height="match_parent" app:header_view="@+id/book_header" app:target_view="@+id/scroll_view" app:header_init_offset="30dp" app:target_init_offset="70dp"> <View android:id="@id/book_header" android:layout_width="120dp" android:layout_height="150dp" android:background="@color/gray"/> <org.cgspine.nestscroll.one.EventDispatchTargetLayout android:id="@id/scroll_view" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="@color/white"> <android.support.design.widget.TabLayout android:id="@+id/tab_layout" android:background="@drawable/list_item_bg_with_border_top_bottom" android:layout_width="match_parent" android:layout_height="@dimen/tab_layout_height" android:fillViewport="true"/> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> </org.cgspine.nestscroll.one.EventDispatchTargetLayout> </org.cgspine.nestscroll.one.EventDispatchPlanLayout> EventDispatchTargetLayout实现了自定义接口ITargetView: publicinterfaceITargetView{ booleancanChildScrollUp(); voidfling(floatvy); } 这是因为与具体业务抽离,我并不清楚内层盒子是怎样的(有可能就是ListView了,也有可能是ViewPager包裹ListView) 主要的实现在EventDispatchPlanLayout,使用时在xml中指定header_init_offset、target_init_offset等变量就可以了,基本上与业务逻辑独立。 其重点实现逻辑在onInterceptTouchEvent与onTouchEvent中了。个人不是很建议去动dispatchTouchEvent,虽然所有事件都会经过这里,但是这也明显会增加代码处理复杂度: publicbooleanonInterceptTouchEvent(MotionEventev){ ensureHeaderViewAndScrollView(); finalintaction=MotionEventCompat.getActionMasked(ev); intpointerIndex; //不阻断事件的快路径:如果目标view可以往上滚动或者`EventDispatchPlanLayout`不是enabled if(!isEnabled()||mTarget.canChildScrollUp()){ Log.d(TAG,"fastendonIntercept:isEnabled="+isEnabled()+";canChildScrollUp=" +mTarget.canChildScrollUp()); returnfalse; } switch(action){ caseMotionEvent.ACTION_DOWN: mActivePointerId=ev.getPointerId(0); mIsDragging=false; pointerIndex=ev.findPointerIndex(mActivePointerId); if(pointerIndex<0){ returnfalse; } //在down的时候记录初始的y值 mInitialDownY=ev.getY(pointerIndex); break; caseMotionEvent.ACTION_MOVE: pointerIndex=ev.findPointerIndex(mActivePointerId); if(pointerIndex<0){ Log.e(TAG,"GotACTION_MOVEeventbuthaveaninvalidactivepointerid."); returnfalse; } finalfloaty=ev.getY(pointerIndex); //判断是否dragging startDragging(y); break; caseMotionEventCompat.ACTION_POINTER_UP: //双指逻辑处理 onSecondaryPointerUp(ev); break; caseMotionEvent.ACTION_UP: caseMotionEvent.ACTION_CANCEL: mIsDragging=false; mActivePointerId=INVALID_POINTER; break; } returnmIsDragging; } 代码逻辑很清晰,应该不用多说。接下来看onTouchEvent的处理逻辑。 publicbooleanonTouchEvent(MotionEventev){ finalintaction=MotionEventCompat.getActionMasked(ev); intpointerIndex; if(!isEnabled()||mTarget.canChildScrollUp()){ Log.d(TAG,"fastendonTouchEvent:isEnabled="+isEnabled()+";canChildScrollUp=" +mTarget.canChildScrollUp()); returnfalse; } //速度追踪 acquireVelocityTracker(ev); switch(action){ caseMotionEvent.ACTION_DOWN: mActivePointerId=ev.getPointerId(0); mIsDragging=false; break; caseMotionEvent.ACTION_MOVE:{ pointerIndex=ev.findPointerIndex(mActivePointerId); if(pointerIndex<0){ Log.e(TAG,"GotACTION_MOVEeventbuthaveaninvalidactivepointerid."); returnfalse; } finalfloaty=ev.getY(pointerIndex); startDragging(y); if(mIsDragging){ floatdy=y-mLastMotionY; if(dy>=0){ moveTargetView(dy); }else{ if(mTargetCurrentOffset+dy<=mTargetEndOffset){ moveTargetView(dy); //重新dispatch一次down事件,使得列表可以继续滚动 intoldAction=ev.getAction(); ev.setAction(MotionEvent.ACTION_DOWN); dispatchTouchEvent(ev); ev.setAction(oldAction); }else{ moveTargetView(dy); } } mLastMotionY=y; } break; } caseMotionEventCompat.ACTION_POINTER_DOWN:{ pointerIndex=MotionEventCompat.getActionIndex(ev); if(pointerIndex<0){ Log.e(TAG,"GotACTION_POINTER_DOWNeventbuthaveaninvalidactionindex."); returnfalse; } mActivePointerId=ev.getPointerId(pointerIndex); break; } caseMotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; caseMotionEvent.ACTION_UP:{ pointerIndex=ev.findPointerIndex(mActivePointerId); if(pointerIndex<0){ Log.e(TAG,"GotACTION_UPeventbutdon'thaveanactivepointerid."); returnfalse; } if(mIsDragging){ mIsDragging=false; //获取瞬时速度 mVelocityTracker.computeCurrentVelocity(1000,mMaxVelocity); finalfloatvy=mVelocityTracker.getYVelocity(mActivePointerId); finishDrag((int)vy); } mActivePointerId=INVALID_POINTER; //释放速度追踪 releaseVelocityTracker(); returnfalse; } caseMotionEvent.ACTION_CANCEL: releaseVelocityTracker(); returnfalse; } returnmIsDragging; } 或许有人会说:为何与onInterceptTouchEvent与有很多重复代码?这是因为如果事件不打断,并且子类不处理,就会走进onTouchEvent逻辑,所以这些重复处理是有意义的(其实是抄SwipeRefreshLayout的)。里面主要的逻辑就是两个: 滚动容器 TouchUp时滚动到特定位置以及fling传递 滚动容器的逻辑: privatevoidmoveTargetViewTo(inttarget){ target=Math.max(target,mTargetEndOffset); //用offsetTopAndBottom来偏移view ViewCompat.offsetTopAndBottom(mTargetView,target-mTargetCurrentOffset); mTargetCurrentOffset=target; //滚动书籍封面view,根据TargetView进行定位 intheaderTarget; if(mTargetCurrentOffset>=mTargetInitOffset){ headerTarget=mHeaderInitOffset; }elseif(mTargetCurrentOffset<=mTargetEndOffset){ headerTarget=mHeaderEndOffset; }else{ floatpercent=(mTargetCurrentOffset-mTargetEndOffset)*1.0f/mTargetInitOffset-mTargetEndOffset; headerTarget=(int)(mHeaderEndOffset+percent*(mHeaderInitOffset-mHeaderEndOffset)); } ViewCompat.offsetTopAndBottom(mHeaderView,headerTarget-mHeaderCurrentOffset); mHeaderCurrentOffset=headerTarget; } TouchUp的滚动逻辑: privatevoidfinishDrag(intvy){ Log.i(TAG,"TouchUp:vy="+vy); if(vy>0){ //向下触发fling,需要滚动到Init位置 mNeedScrollToInitPos=true; mScroller.fling(0,mTargetCurrentOffset,0,vy, 0,0,mTargetEndOffset,Integer.MAX_VALUE); invalidate(); }elseif(vy<0){ //向上触发fling,需要滚动到End位置 mNeedScrollToEndPos=true; mScroller.fling(0,mTargetCurrentOffset,0,vy, 0,0,mTargetEndOffset,Integer.MAX_VALUE); invalidate(); }else{ //没有触发fling,就近原则 if(mTargetCurrentOffset<=(mTargetEndOffset+mTargetInitOffset)/2){ mNeedScrollToEndPos=true; }else{ mNeedScrollToInitPos=true; } invalidate(); } } 当然这里会打上一些标志位,具体实现是在computeScroll中,这属于Scroller的功能,这里就不展开了。 这样大体逻辑就讲述清楚了,其它细节就请看官直接看源码了。 基于NestingScroll机制的实现方案 NestingScroll机制是在某个版本support包加入的,不过外界极少有文章介绍,所以应该大多数人并不知道这个机制。NestingScroll主要有两个接口: NestedScrollingParent NestedScrollingChild 当我们需要使用NestingScroll特性时,我们去实现这两个接口就好了。NestingScroll本质是内部拦截发然后将相应的接口开给外界。因此实现NestedScrollingChild接口是有难度的,不过像RecyclerView这些控件,官方已经帮我们实现好了NestedScrollingChild,要完成我们的需求,我们直接拿来用就好了(ListView就没办法使用了,当然你也可以去实现NestedScrollingChild接口)。并且NestedScrollingChild与NestedScrollingParent只要有嵌套关系就行了,并不一定NestedScrollingChild是直接的子View。 我们来来看看NestedScrollingParent的定义: publicinterfaceNestedScrollingParent{ //是否接受NestingScroll publicbooleanonStartNestedScroll(Viewchild,Viewtarget,intnestedScrollAxes); //接受NestingScroll的Hook钩子 publicvoidonNestedScrollAccepted(Viewchild,Viewtarget,intnestedScrollAxes); //NestingScroll结束 publicvoidonStopNestedScroll(Viewtarget); //NestingScroll进行中。重要参数dxUnconsumed,dyUnconsumed:用于表示没有被消耗的滚动量,一般是列表滚动到头了,就会产生未消耗量 publicvoidonNestedScroll(Viewtarget,intdxConsumed,intdyConsumed,intdxUnconsumed,intdyUnconsumed); //NestingScroll滚动之前。重要参数consumed:是用于告诉子View我消耗了多少。如果位全部消耗dy,那么子view就可以消耗了。 publicvoidonNestedPreScroll(Viewtarget,intdx,intdy,int[]consumed); //fling时 publicbooleanonNestedFling(Viewtarget,floatvelocityX,floatvelocityY,booleanconsumed); //fling之前:可以由父元素消耗这次fling事件 publicbooleanonNestedPreFling(Viewtarget,floatvelocityX,floatvelocityY); //获取滚动轴:x轴或y轴 publicintgetNestedScrollAxes(); } 接口是非常丰富的。有一个很重要的概念:消耗量。 比如我滑动了10dp,那么父元素先看看可以消耗多少(例如4dp),然后会把未消耗量传递给子View(6dp)。这就把嵌套滚动的问题转换为资源分配的问题了。非常机智。除此以外,官方提供了NestedScrollingParentHelper类帮我实现了一些公共方法并做好了低版本兼容,我们应当拿来用。 写在最后 虽然google提供了很多新颖好玩的接口。但这需要花费部分精力去实践这些新技术。这是非常有意义的投入。多看、多写,才能帮助我们用更少的时间写更好的代码。 本文作者:佚名 来源:51CTO

优秀的个人博客,低调大师

玩转SSH端口转发

SSH有三种端口转发模式,本地端口转发(Local Port Forwarding),远程端口转发(Local Port Forwarding)以及动态端口转发(Dynamic Port Forwarding)。对于本地/远程端口转发,两者的方向恰好相反。动态端口转发则可以用于科学上网。 SSH端口转发也被称作SSH隧道(SSH Tunnel),因为它们都是通过SSH登陆之后,在SSH客户端与SSH服务端之间建立了一个隧道,从而进行通信。SSH隧道是非常安全的,因为SSH是通过加密传输数据的(SSH全称为Secure Shell)。 在本文所有示例中,本地主机A1为SSH客户端,远程云主机B1为SSH服务端。从A1主机通过SSH登陆B1主机,指定不同的端口转发选项(-L、-R和-D),即可在A1与B1之间建立SSH隧道,从而进行不同的端口转发。 本地端口转发 应用场景: 远程云主机B1运行了一个服务,端口为3000,本地主机A1需要访问这个服务。 示例为一个简单的Node.js服务: var http = require('http'); var server = http.createServer(function(request, response) { response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Hello Fundebug\n"); }); server.listen(3000); 假设云主机B1的IP为103.59.22.17,则该服务的访问地址为:http://103.59.22.17:3000 为啥需要本地端口转发呢? 一般来讲,云主机的防火墙默认只打开了22端口,如果需要访问3000端口的话,需要修改防火墙。为了保证安全,防火墙需要配置允许访问的IP地址。但是,本地公忘IP通常是网络提供商动态分配的,是不断变化的。这样的话,防火墙配置需要经常修改,就会很麻烦。 什么是本地端口转发? 所谓本地端口转发,就是将发送到本地端口的请求,转发到目标端口。这样,就可以通过访问本地端口,来访问目标端口的服务。使用-L属性,就可以指定需要转发的端口,语法是这样的: -L 本地网卡地址:本地端口:目标地址:目标端口 通过本地端口转发,可以将发送到本地主机A1端口2000的请求,转发到远程云主机B1的3000端口。 # 在本地主机A1登陆远程云主机B1,并进行本地端口转发 ssh -L localhost:2000:localhost:3000 root@103.59.22.17 这样,在本地主机A1上可以通过访问http://localhost:2000来访问远程云主机B1上的Node.js服务。 # 在本地主机A1访问远程云主机B1上的Node.js服务 curl http://localhost:2000 Hello Fundebug 实际上,-L选项中的本地网卡地址是可以省略的,这时表示2000端口绑定了本地主机A1的所有网卡: # 在本地主机A1登陆远程云主机B1,并进行本地端口转发。2000端口绑定本地所有网卡 ssh -L 2000:localhost:3000 root@103.59.22.17 若本地主机A2能够访问A1,则A2也可以通过A1访问远程远程云主机B1上的Node.js服务。 另外,-L选项中的目标地址也可以是其他主机的地址。假设远程云主机B2的局域网IP地址为192.168.59.100,则可以这样进行端口转发: # 在本地主机A1登陆远程云主机B1,并进行本地端口转发。请求被转发到远程云主机B2上 ssh -L 2000:192.168.59.100:3000 root@103.59.22.17 若将Node.js服务运行在远程云主机B2上,则发送到A1主机2000端口的请求,都会被转发到B2主机上。 远程端口转发 应用场景:s 本地主机A1运行了一个服务,端口为3000,远程云主机B1需要访问这个服务。 将前文的Node.js服务运行在本地,在本地就可以通过http://localhost:3000访问该服务。 为啥需要远程端口转发呢? 通常,本地主机是没有独立的公网IP的,它与同一网络中的主机共享一个IP。没有公网IP,云主机是无法访问本地主机上的服务的。 什么是远程端口转发? 所谓远程端口转发,就是将发送到远程端口的请求,转发到目标端口。这样,就可以通过访问远程端口,来访问目标端口的服务。使用-R属性,就可以指定需要转发的端口,语法是这样的: -R 远程网卡地址:远程端口:目标地址:目标端口 这时,通过远程端口转发,可以将发送到远程云主机B1端口2000的请求,转发到本地主机A1端口3000。 # 在本地主机A1登陆远程云主机B1,并进行远程端口转发 ssh -R localhost:2000:localhost:3000 root@103.59.22.17 这样,在远程云主机A1可以通过访问http://localhost:2000来访问本地主机的服务。 # 在远程云主机B1访问本地主机A1上的Node.js服务 curl http://localhost:2000 Hello Fundebug 同理,远程网卡地址可以省略,目标地址也可以是其他主机地址。假设本地主机A2的局域网IP地址为192.168.0.100。 # 在本地主机A1登陆远程云主机B1,并进行远程端口转发 ssh -R 2000:192.168.0.100:3000 root@103.59.22.17 若将Node.js服务运行在本地主机A2上,则发送到远程云主机A1端口2000的请求,都会被转发到A2主机上。 动态端口转发 应用场景: 远程云主机B1运行了多个服务,分别使用了不同端口,本地主机A1需要访问这些服务。 为啥需要动态端口转发呢? 一方面,由于防火墙限制,本地主机A1并不能直接访问远程云主机B1上的服务,因此需要进行端口转发;另一方面,为每个端口分别创建本地端口转发非常麻烦。 什么是动态端口转发? 对于本地端口转发和远程端口转发,都存在两个一一对应的端口,分别位于SSH的客户端和服务端,而动态端口转发则只是绑定了一个本地端口,而目标地址:目标端口则是不固定的。目标地址:目标端口是由发起的请求决定的,比如,请求地址为192.168.1.100:3000,则通过SSH转发的请求地址也是192.168.1.100:3000。 -D 本地网卡地址:本地端口 这时,通过动态端口转发,可以将在本地主机A1发起的请求,转发到远程主机B1,而由B1去真正地发起请求。 # 在本地主机A1登陆远程云主机B1,并进行动态端口转发 ssh -D localhost:2000 root@103.59.22.17 而在本地发起的请求,需要由Socket代理(Socket Proxy)转发到SSH绑定的2000端口。以Firefox浏览器为例,配置Socket代理需要找到首选项>高级>网络>连接->设置: 这样的话,Firefox浏览器发起的请求都会转发到2000端口,然后通过SSH转发到真正地请求地址。若Node.js服务运行在远程云主机B1上,则在Firefox中访问localhost:3000即可以访问。如果主机B1能够访问外网的话,则可以科学上网…... 链式端口转发 本地端口转发与远程端口转发结合起来使用,可以进行链式转发。假设A主机在公司,B主机在家,C主机为远程云主机。A主机上运行了前文的Node.js服务,需要在B主机上访问该服务。由于A和B不在同一个网络,且A主机没有独立公共IP地址,所以无法直接访问服务。 通过本地端口转发,将发送到B主机3000端口的请求,转发到远程云主机C的2000端口。 # 在B主机登陆远程云主机C,并进行本地端口转发 ssh -L localhost:3000:localhost:2000 root@103.59.22.17 通过远程端口转发,将发送到远程云主机C端口2000的请求,转发到A主机的3000端口。 # 在A主机登陆远程云主机C,并进行远程端口转发 ssh -R localhost:2000:localhost:3000 root@103.59.22.17 这样,在主机B可以通过访问http://localhost:3000来访问主机A上的服务。 # 在主机B访问主机A上的服务 curl http://localhost:3000 Hello Fundebug 参考链接 SSH PortForwarding SSH隧道的原理和实现 关于Fundebug Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了6亿+错误事件,得到了Google、360、金山软件等众多知名用户的认可。欢迎免费试用! 版权声明: 转载时请注明作者Fundebug以及本文地址:https://blog.fundebug.com/2017/04/24/ssh-port-forwarding/

优秀的个人博客,低调大师

玩转Arduino之开篇

之前大学时候有玩过最基本的51单片机,还记得用C语言初学者的水平,颤抖着看着键盘敲打着 "# include<stdio.h>",可能有时候还无法拼写正确include这个单词,仅仅靠着编译器的高亮才找到正确的写法。记得开始学着用Keil IDE开发套件编写一些main()函数,纯粹的面向过程的拿来主义思想,大学的生活总是这样惬意,就像单片机代码一样单纯,都没有使用复杂的指针和递归算法,总是顺利的从头开始执行到尾,最多加上个while(1)循环,连最后的return都依赖编译器给我完成了。到了大学后期,感觉对着双列直插式的芯片,写些无聊的processing。。。感觉生活需要些速度和激情,于是将51单片机架上智能小车的外壳,似乎让我有了兴趣,于是一发不可收!从简单的循迹小车,到开始运用烧复杂传感器的避障智能车,再到了研究生时期做的视频和无线互联智能小车,以后后来做的车车互联通讯的图像识别智能车。小车的造价和运用的处理单元也越来越豪华,从8位8K内存的51单片机到可以运行操作系统的ARM11处理器,从简单的手工焊接的51单片机最小系统,到Friendly ARM6410开发板,一起越来越牛逼的传感器。小车一直伴随着我离开校园步入职场,所有的实习经历和目前的工作,感觉都没有离开嵌入式开发这个主线。每次我都提醒自己,不要被“嵌入式”这个流行词给欺骗了,因为我只是个寄存器程序员,即对着各种SPEC,然后板着指头省内存空间的码农,还不是高级互联网码农。互联网码农们早已经开始用面向对象的思维看待世间万物,而我们还是刀耕火种的时代,所有的努力只是让神秘的上层不要吐槽低层的“慢”!这几天,我在无意间,看到了Arduino这玩意,其实和51单片机差不多,只是浪漫的意大利人给这个Atmega328P-PU单片机开发板取了个貌似浪漫的名字。Arduino可以看做是一套很成熟的开发套件,包括开发板在内的其他外围设备,而且最吸引人的是它有一套带UI的开发软件IDE,这个软件将枯燥的C语言进行了封装,并且隐藏了复杂的编译过程,让开发自己的程序变得乐趣无穷!这也是我在苦逼的工作之余自娱自乐的不二选择。目前,Arduino的开发环境已经搭建完毕,开发套件花费了158¥备齐,好了,废话不说了,后续开始玩起来吧~!

优秀的个人博客,低调大师

玩转 Go 链路追踪

前言 链路追踪是每个微服务架构下必备的利器,go-zero 当然早已经为我们考虑好了,只需要在配置中添加配置即可使用。 关于 go-zero 如何追踪的原理追溯,之前已经有同学分享,这里我就不再多说,如果有想了解的同学去 https://mp.weixin.qq.com/s/hJEWcWc3PnGfWfbPCHfM9g 这个链接看就好了。默认会在 api 的中间件与 rpc 的 interceptor 添加追踪,如果有不了解 go-zero 默认如何使用默认的链路追踪的,请移步我的开源项目 go-zero-looklook 文档 https://github.com/Mikaelemmmm/go-zero-looklook/blob/main/doc/chinese/12-%E9%93%BE%E8%B7%AF%E8%BF%BD%E8%B8%AA.md。 今天我想讲的是,除了 go-zero 默认在 api 的 middleware 与 rpc 的 interceptor 中帮我们集成好的链路追踪,我们想自己在某些本地方法添加链路追踪代码或者我们想在 api 发送一个消息给 mq 服务时候想把整个链路包含 mq 的 producer、consumer 穿起来,在 go-zero 中该如何做。 场景 我们先简单讲一下我们的小 demo 的场景,一个请求进来调用 api 的 Login 方法,在 Login 方法中先调用 rpc 的 GetUserByMobile 方法,之后在调用 api 本地的 local 方法,紧接着调用 rabbitmq 传递消息到 mq 服务。 go-zero 默认集成了 jaeger、zinpink,这里我们就以 jaeger 为例 我们希望看到的链路是 rpc.GetUserByMobile" data-original="https://img-blog.csdnimg.cn/910d8fbfd04b4c6196b3e38479c1679b.png"> 也就是 api 衍生出来三条子链路,api.producerMq 有一条调用 mq.Consumer 的子链路。 我们想要将一个方法添加到链路中需要两个因素,一个 traceId,一个span,当我们在同一个 traceId 下开启 span 把相关的 span 都串联起来,如果想形成父子关系,就要把 span 之间相互串联起来,因为「微服务实践」公众号中讲解原理太多,我这里就简单提一下不涉及过多,如果不是特别熟悉原理可以看文章开头推荐的文章,这里我们只需要知道 traceId 与 spanId 关系就好。 核心业务代码 1、首先 API 中 LoginLogic 代码 type LoginLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic { return &amp;LoginLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } type MsgBody struct { Carrier *propagation.HeaderCarrier Msg string } func (l *LoginLogic) Login(req *types.RegisterReq) (*types.AccessTokenResp, error) { resp, err := l.svcCtx.UserRpc.GetUserByMobile(l.ctx, &amp;usercenter.GetUserByMobileReq{ Mobile: req.Mobile, }) if err != nil { return &amp;types.AccessTokenResp{}, nil } l.local() tracer := otel.GetTracerProvider().Tracer(trace.TraceName) spanCtx, span := tracer.Start(l.ctx, "send_msg_mq", oteltrace.WithSpanKind(oteltrace.SpanKindProducer)) carrier := &amp;propagation.HeaderCarrier{} otel.GetTextMapPropagator().Inject(spanCtx, carrier) producer := rabbit.NewRabbitmqPublisher(RabbitmqDNS) msg := &amp;MsgBody{ Carrier: carrier, Msg: req.Mobile, } b, err := json.Marshal(msg) if err != nil{ panic(err) } if err := producer.Publish(spanCtx, ExchangeName, RoutineKeys, b); err != nil { logx.Errorf("Publish Fail , msg :%s , err:%v", msg, err) } span.End() return &amp;types.AccessTokenResp{ AccessExpire: resp.User.Id, }, err } func (l *LoginLogic) local() { tracer := otel.GetTracerProvider().Tracer(trace.TraceName) _ , span := tracer.Start(l.ctx, "local", oteltrace.WithSpanKind(oteltrace.SpanKindInternal)) defer span.End() // 执行你的代码 ..... } 2、rpc 中 GetUserByMobile 的代码 func (s *Logic) GetUserByMobile(context.Context, *usercenterPb.GetUserByMobileReq) (*usercenterPb.GetUserByMobileResp, error) { vo := &amp;usercenterPb.UserVo{ Id: 1, } return &amp;usercenterPb.GetUserByMobileResp{ User: vo, }, nil } 3、mq 中 Consumer 的代码 type MsgBody struct { Carrier *propagation.HeaderCarrier Msg string } func (c *consumer) Consumer(ctx context.Context, data []byte) error { var msg MsgBody if err := json.Unmarshal(data, &amp;msg); err != nil { logx.Errorf(" consumer err : %v", err) } else { logx.Infof("consumerOne Consumer , msg:%+v", msg) wireContext := otel.GetTextMapPropagator().Extract(ctx, msg.Carrier) tracer := otel.GetTracerProvider().Tracer(trace.TraceName) _, span := tracer.Start(wireContext, "mq_consumer_msg", oteltrace.WithSpanKind(oteltrace.SpanKindConsumer)) defer span.End() } return nil } 代码详解 1、go-zero 默认集成 当一个请求进入 api 后,我们可以在 go-zero 源码中查看到 https://github.com/zeromicro/go-zero/blob/master/rest/engine.go#L92。go-zero 已经在 api 的 middleware 中帮我们添加了第一层 trace,当进入 Login 方法内,我们调用了 rpc 的 GetUserByMobile 方法,通过 go-zero 的源码 https://github.com/zeromicro/go-zero/blob/master/zrpc/internal/rpcserver.go#L55 可以看到在 rpc 的 interceptor 也默认帮我们添加好了,这两层都是 go-zero 默认帮我们做好的。 2、本地方法 当调用完 rpc 的 GetUserByMobile 之后,api 调用了本地的 local,如果我们想在整个链路上体现出来调用了本地 local 方法,那默认的 go-zero 是没有帮我们做的,需要我们手动来添加。 tracer := otel.GetTracerProvider().Tracer(trace.TraceName) _ , span := tracer.Start(l.ctx, "local", oteltrace.WithSpanKind(oteltrace.SpanKindInternal)) defer span.End() // 执行你的代码 ..... 我们通过上面代码拿到 tracer,ctx 之后开启一个 local 的 span,因为 start 时候会从 ctx 获取父 span 所以会将 local 方法与 Login 串联起父子调用关系,这样就将本次操作加入了这个链路 3、mq 的 producer 到 mq 的 consumer 我们在mq传递中如何串联起来这个链路呢?也就是形成 api.Login-&gt;api.producer-&gt;mq.Consumer。 想一下原理,虽然跨越了网络,api 可以通过 header 传递,rpc 可以通过 metadata 传递,那么 mq 是不是也可以通过 header、body 传递就可以了,按照这个想法来看下我门的代码。 tracer := otel.GetTracerProvider().Tracer(trace.TraceName) spanCtx , span := tracer.Start(l.ctx, "send_msg_mq", oteltrace.WithSpanKind(oteltrace.SpanKindProducer)) carrier := &amp;propagation.HeaderCarrier{} otel.GetTextMapPropagator().Inject(spanCtx,carrier) producer := rabbit.NewRabbitmqPublisher(RabbitmqDNS) msg := &amp;MsgBody{ Carrier: carrier, Msg: req.Mobile, } b , err := json.Marshal(msg) if err != nil{ panic(err) } if err := producer.Publish(spanCtx, ExchangeName, RoutineKeys, b); err != nil { logx.Errorf("Publish Fail, msg :%s, err:%v", msg, err) } span.End() 首先获取到了这个全局的 tracer,然后开启一个 producer 的 span,跟 local 方法一样,我们开启 producer 的 span 时候也是通过 ctx 获取到上一级父级 span,这样就可以将 producer 的 span 与 Login 形成父子 span 调用关系,那我们想将 producer 的 span 与 mq 的 consumer 中的 span 形成调用父子关系怎么做?我们将 api.producer 的 spanCtx 注入到 carrier 中,这里我们通过 mq 的 body 将 carrier 发送给 consumer,发送完成我们 stop 我们的 producer,那么 producer 的这层链路完成了。 随后我们来看 mq-consumer 在接收到 body 消息之后怎么做的。 type MsgBody struct { Carrier *propagation.HeaderCarrier Msg string } func (c *consumer) Consumer(ctx context.Context, data []byte) error { var msg MsgBody if err := json.Unmarshal(data, &amp;msg); err != nil { logx.Errorf(" consumer err : %v", err) } else { logx.Infof("consumerOne Consumer , msg:%+v", msg) wireContext := otel.GetTextMapPropagator().Extract(ctx, msg.Carrier) tracer := otel.GetTracerProvider().Tracer(trace.TraceName) _, span := tracer.Start(wireContext, "mq_consumer_msg", oteltrace.WithSpanKind(oteltrace.SpanKindConsumer)) defer span.End() } return nil } consumer 接收到消息后反序列化出来 Carrier *propagation.HeaderCarrier,然后通过 otel.GetTextMapPropagator().Extract 取出来 api.producer 注入的 wireContext,在通过 tracer.Start、wireContext 创建 consumer 的 span,这样 consumer 就是 api.producer 的子 span,就形成了调用链路关系,最终我们得到的关系就是 rpc.GetUserByMobile" data-original="https://img-blog.csdnimg.cn/910d8fbfd04b4c6196b3e38479c1679b.png"> 让我们来调用一下 Logic 方法,看下 jaeger 中的链路如果与我们预想的链路一致,so happy~ 项目地址 go-zero 微服务框架:https://github.com/zeromicro/go-zero https://gitee.com/kevwan/go-zero go-zero 微服务最佳实践项目:https://github.com/Mikaelemmmm/go-zero-looklook 欢迎使用 go-zero 并 star 支持我们! 微信交流群 关注『微服务实践』公众号并点击 交流群 获取社区群二维码。

优秀的个人博客,低调大师

管家助力,轻松玩转MaxCompute

精彩视频回顾请点击:MaxCompute管家详解以下是直播内容精华整理,主要包括以下四个方面:1.背景速览;2.功能介绍;3.案例讲解;4.新功能预告。 一、背景速览 MaxCompute(原ODPS)是一项大数据计算服务,它能提供快速、完全托管的PB级数据仓库解决方案,使用户可以经济并高效的分析处理海量数据。在购买了MaxCompute之后会有相当多而繁琐的管理和维护工作,比如如何对项目进行更精细化的管理、如何将项目与配额进行关联等等,而MaxCompute管家可以帮助用户更好地完成这些工作,它是一个为用户提供作业信息查看、资源消耗查看(涵盖CU资源和存储资源)、项目查看及调整、配额组增删改查等涉及日常MaxCompute运维能力的管理平台。目前,全球包括美国、英国、德国、印度、日本、新加坡在内的18个国家或地区(详情见官网)购买

优秀的个人博客,低调大师

怎么玩转Java线程池?

一:简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的。在jdk1.5之后这一情况有了很大的改观。Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用。为我们在开发中处理线程的问题提供了非常大的帮助。 二:线程池 线程池的作用: 线程池作用就是限制系统中执行线程的数量。 根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。 为什么要用线程池: 1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。 2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。 Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。 比较重要的几个类: ExecutorService 真正的线程池接口。 ScheduledExecutorService 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。 ThreadPoolExecutor ExecutorService的默认实现。 ScheduledThreadPoolExecutor 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。 要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。 newSingleThreadExecutor 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 2.newFixedThreadPool 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 newCachedThreadPool 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程, 那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 4.newScheduledThreadPool 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。 实例 1:newSingleThreadExecutor MyThread.java publicclassMyThread extends Thread { @Override publicvoid run() { System.out.println(Thread.currentThread().getName() + "正在执行。。。"); } } TestSingleThreadExecutor.java publicclassTestSingleThreadExecutor { publicstaticvoid main(String[] args) { //创建一个可重用固定线程数的线程池 ExecutorService pool = Executors. newSingleThreadExecutor(); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口 Thread t1 = new MyThread(); Thread t2 = new MyThread(); Thread t3 = new MyThread(); Thread t4 = new MyThread(); Thread t5 = new MyThread(); //将线程放入池中进行执行 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); //关闭线程池 pool.shutdown(); } } 输出结果 pool-1-thread-1正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-1正在执行。。。 2newFixedThreadPool TestFixedThreadPool.Java publicclass TestFixedThreadPool { publicstaticvoid main(String[] args) { //创建一个可重用固定线程数的线程池 ExecutorService pool = Executors.newFixedThreadPool(2); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口 Thread t1 = new MyThread(); Thread t2 = new MyThread(); Thread t3 = new MyThread(); Thread t4 = new MyThread(); Thread t5 = new MyThread(); //将线程放入池中进行执行 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); //关闭线程池 pool.shutdown(); } } 输出结果 pool-1-thread-1正在执行。。。 pool-1-thread-2正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-2正在执行。。。 pool-1-thread-1正在执行。。。 3 newCachedThreadPool TestCachedThreadPool.java publicclass TestCachedThreadPool { publicstaticvoid main(String[] args) { //创建一个可重用固定线程数的线程池 ExecutorService pool = Executors.newCachedThreadPool(); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口 Thread t1 = new MyThread(); Thread t2 = new MyThread(); Thread t3 = new MyThread(); Thread t4 = new MyThread(); Thread t5 = new MyThread(); //将线程放入池中进行执行 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); //关闭线程池 pool.shutdown(); } } 输出结果: pool-1-thread-2正在执行。。。 pool-1-thread-4正在执行。。。 pool-1-thread-3正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-5正在执行。。。 4newScheduledThreadPool TestScheduledThreadPoolExecutor.java publicclass TestScheduledThreadPoolExecutor { publicstaticvoid main(String[] args) { ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1); exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间就触发异常 @Override publicvoid run() { //throw new RuntimeException(); System.out.println("================"); } }, 1000, 5000, TimeUnit.MILLISECONDS); exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间打印系统时间,证明两者是互不影响的 @Override publicvoid run() { System.out.println(System.nanoTime()); } }, 1000, 2000, TimeUnit.MILLISECONDS); } } 输出结果 8384644549516 8386643829034 8388643830710 8390643851383 8392643879319 8400643939383 三:ThreadPoolExecutor详解 ThreadPoolExecutor的完整构造方法的签名是:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) . corePoolSize - 池中所保存的线程数,包括空闲线程。 maximumPoolSize-池中允许的最大线程数。 keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。 unit - keepAliveTime 参数的时间单位。 workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。 threadFactory - 执行程序创建新线程时使用的工厂。 handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。 ThreadPoolExecutor是Executors类的底层实现。 在JDK帮助文档中,有如此一段话: “强烈建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)Executors.newSingleThreadExecutor()(单个后台线程) 它们均为大多数使用场景预定义了设置。” 下面介绍一下几个类的源码: ExecutorService newFixedThreadPool (int nThreads):固定大小线程池。 可以看到,corePoolSize和maximumPoolSize的大小是一样的(实际上,后面会介绍,如果使用无界queue的话maximumPoolSize参数是没有意义的),keepAliveTime和unit的设值表名什么?-就是该实现不想keep alive!最后的BlockingQueue选择了LinkedBlockingQueue,该queue有一个特点,他是无界的。 public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } ExecutorService newSingleThreadExecutor():单线程 public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } ExecutorService newCachedThreadPool():无界线程池,可以进行自动线程回收 这个实现就有意思了。首先是无界的线程池,所以我们可以发现maximumPoolSize为big big。其次BlockingQueue的选择上使用SynchronousQueue。可能对于该BlockingQueue有些陌生,简单说:该QUEUE中,每个插入操作必须等待另一个线程的对应移除操作。 public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } 先从BlockingQueue workQueue这个入参开始说起。在JDK中,其实已经说得很清楚了,一共有三种类型的queue。 所有BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互: 如果运行的线程少于 corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行) 如果运行的线程等于或多于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。 queue上的三种类型。 排队有三种通用策略: 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 有界队列。当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。 BlockingQueue的选择。 例子一:使用直接提交策略,也即SynchronousQueue。 首先SynchronousQueue是无界的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。在这里不是核心线程便是新创建的线程,但是我们试想一样下,下面的场景。 我们使用一下参数构造ThreadPoolExecutor: new ThreadPoolExecutor( 2, 3, 30, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new RecorderThreadFactory("CookieRecorderPool"), new ThreadPoolExecutor.CallerRunsPolicy()); new ThreadPoolExecutor( 2, 3, 30, TimeUnit.SECONDS, new SynchronousQueue(), new RecorderThreadFactory("CookieRecorderPool"), new ThreadPoolExecutor.CallerRunsPolicy()); 当核心线程已经有2个正在运行. 此时继续来了一个任务(A),根据前面介绍的“如果运行的线程等于或多于 corePoolSize,则Executor始终首选将请求加入队列,而不添加新的线程。”,所以A被添加到queue中。 又来了一个任务(B),且核心2个线程还没有忙完,OK,接下来首先尝试1中描述,但是由于使用的SynchronousQueue,所以一定无法加入进去。 此时便满足了上面提到的“如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。”,所以必然会新建一个线程来运行这个任务。 暂时还可以,但是如果这三个任务都还没完成,连续来了两个任务,第一个添加入queue中,后一个呢?queue中无法插入,而线程数达到了maximumPoolSize,所以只好执行异常策略了。 所以在使用SynchronousQueue通常要求maximumPoolSize是无界的,这样就可以避免上述情况发生(如果希望限制就直接使用有界队列)。对于使用SynchronousQueue的作用jdk中写的很清楚:此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。 什么意思?如果你的任务A1,A2有内部关联,A1需要先运行,那么先提交A1,再提交A2,当使用SynchronousQueue我们可以保证,A1必定先被执行,在A1么有被执行前,A2不可能添加入queue中。 例子二:使用无界队列策略,即LinkedBlockingQueue 这个就拿newFixedThreadPool来说,根据前文提到的规则: 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。那么当任务继续增加,会发生什么呢? 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。OK,此时任务变加入队列之中了,那什么时候才会添加新线程呢? 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。这里就很有意思了,可能会出现无法加入队列吗?不像SynchronousQueue那样有其自身的特点,对于无界队列来说,总是可以加入的(资源耗尽,当然另当别论)。换句说,永远也不会触发产生新的线程!corePoolSize大小的线程数会一直运行,忙完当前的,就从队列中拿任务开始运行。所以要防止任务疯长,比如任务运行的实行比较长,而添加任务的速度远远超过处理任务的时间,而且还不断增加,不一会儿就爆了。 例子三:有界队列,使用ArrayBlockingQueue。 这个是最为复杂的使用,所以JDK不推荐使用也有些道理。与上面的相比,最大的特点便是可以防止资源耗尽的情况发生。 举例来说,请看如下构造方法: new ThreadPoolExecutor( 2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2), new RecorderThreadFactory("CookieRecorderPool"), new ThreadPoolExecutor.CallerRunsPolicy()); new ThreadPoolExecutor( 2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2), new RecorderThreadFactory("CookieRecorderPool"), new ThreadPoolExecutor.CallerRunsPolicy()); 假设,所有的任务都永远无法执行完。 对于首先来的A,B来说直接运行,接下来,如果来了C,D,他们会被放到queue中,如果接下来再来E,F,则增加线程运行E,F。但是如果再来任务,队列无法再接受了,线程数也到达最大的限制了,所以就会使用拒绝策略来处理。 keepAliveTime jdk中的解释是:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。 有点拗口,其实这个不难理解,在使用了“池”的应用中,大多都有类似的参数需要配置。比如数据库连接池,DBCP中的maxIdle,minIdle参数。 什么意思?接着上面的解释,后来向老板派来的工人始终是“借来的”,俗话说“有借就有还”,但这里的问题就是什么时候还了,如果借来的工人刚完成一个任务就还回去,后来发现任务还有,那岂不是又要去借?这一来一往,老板肯定头也大死了。 合理的策略:既然借了,那就多借一会儿。直到“某一段”时间后,发现再也用不到这些工人时,便可以还回去了。这里的某一段时间便是keepAliveTime的含义,TimeUnit为keepAliveTime值的度量。 RejectedExecutionHandler 另一种情况便是,即使向老板借了工人,但是任务还是继续过来,还是忙不过来,这时整个队伍只好拒绝接受了。 RejectedExecutionHandler接口提供了对于拒绝任务的处理的自定方法的机会。在ThreadPoolExecutor中已经默认包含了4中策略,因为源码非常简单,这里直接贴出来。 CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } 这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。 AbortPolicy:处理程序遭到拒绝将抛出运行时RejectedExecutionException public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException(); } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException(); } 这种策略直接抛出异常,丢弃任务。 DiscardPolicy:不能执行的任务将被删除 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } 这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。 DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程) public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } 该策略就稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。这个策略需要适当小心。 设想:如果其他线程都还在运行,那么新来任务踢掉旧任务,缓存在queue中,再来一个任务又会踢掉queue中最老任务。 总结: keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。 反之,如果核心数较小,有界BlockingQueue数值又较小,同时keepAliveTime又设的很小,如果任务频繁,那么系统就会频繁的申请回收线程。 public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } 文章来源 https://yq.aliyun.com/articles/94989?utm_campaign=wenzhang&utm_medium=article&utm_source=QQ-qun&2017622&utm_content=m_23936

优秀的个人博客,低调大师

玩转树莓派——安装系统

纠结了很久,终于在今年生日的时候买了个树莓派 3。拿到以后少不了各种折腾,慢慢的把折腾过程写下来吧。 有关RaspBerry Pi 3,技术参数以及对应提升,就没必要在这里赘述了,官网介绍应有尽有:https://www.raspberrypi.org/ 万能的淘宝买回一个电路板~没有选择所谓的套餐,那背离了我折腾的初衷。当然,考虑到折腾的程度,散热片和漂亮的框还是要搞一个的,电源可以用iPad的,全部到手也不到300元。 通电之前的第一件事是准备系统。有两种不同的方式来“安装”系统:直接刷tf卡,或者使用NOOBS来下载安装。 直接刷tf卡没啥可说的,Linux/MAC类用dd命令,Windows可以用一个镜像写入程序。可参考:https://www.raspberrypi.org/documentation/installation/installing-images/README.md 需要说明的是,这个镜像工具不光能写,也能将你做好的tf卡读成一个镜像文件。不过,不像ghost那样会跳过空扇区,所以会变得巨大…… 上述方式适合各种树莓派以及第三方的镜像,比如游戏机之类的,以后玩了再写。 如果不想刷卡,或者网速很快,可以选择NOOBS方式来下载安装系统。 NOOBS是树莓派的引导安装系统,可以让树莓派引导到Recovery环境,然后选择安装/下载安装需要的操作系统。 默认的版本会包含最近的树莓派操作系统——RaspBian,所以稍大。这个版本的镜像在安装的时候就直接从本地安装RaspBian了。如果你只打算安装别的系统,或者选择在线下载安装RaspBian,可以直接下载NOOBS Lite版本。 下载完成后,格式化tf卡,做成启动分区,然后将压缩包解压到tf卡即可。 将tf卡插入树莓派,上电启动,即可开始安装操作系统。 作为一个Microsoft MVP,自然对微软的系统十分有兴趣,包括运行于树莓派的Windows 10 IoT Core。树莓派系统可以直接在NOOBS支持下,下载安装Windows 10 IoT。 本文转自HaoHu 51CTO博客,原文链接:http://blog.51cto.com/haohu/1853397,如需转载请自行联系原作者

优秀的个人博客,低调大师

玩转大数据的建议

大数据成为今年李克强总理政府工作报告中提升为“互联网+”行动计划的一部分,并由此推动移动互联网、云计算、物联网等与现代制造业结合,促进电子商务、工业互联网和互联网金融健康发展,引导互联网企业拓展国际市场。可以说,大数据的存储、挖掘、分析能力已经彻底改变了传统互联网的局面。 通过大量的数据应用,我们完全可以总结出当今大数据时代如下一些数据特征: 一是数据的原始性,社会需要的是原始数据;二是数据的完整性,只要是非保密的,政府应该把掌握的数据提供给社会;三是数据的标准性,需要一个国家级标准,每个部门提供的数据,公众可以同一个标准使用;四是数据的及时性,数据拿到以后第一时间提供给社会,让创新更加及时;五是数据的可获取性,部门不能在数据的提供方面设置障碍,甚至收费赚钱。 因此,对大数据要有一个国家层面的认识,特别是应发挥政府在大数据时代的作用。既然这样,就迫切需要建立大数据资源的共建共享机制。如何做到?首先应结合目前国家的简政放权可喜形势,依托政务信息资源共享平台,把行政部门的审批职能和政务服务集中起来,提供一站式网上办理,形成横向到各委办局,纵向到各省市县区的网上办事系统。目前,国、省、市、县四级平台可实现互联互通,审批和服务事项基本实现应尽必尽。全国各省网上办事大厅系统均实现零的突破,今后逐步实现各项办事目录的数据同步和办事过程数据的交换对接,形成了网上办事全流程数据的共建共享机制。 这种机制的共建共享,首先要以需求为导向,部门的自身需求才是共建共享的真正动力;其次要加强流程再造,实行网上办事;还要开展全程督查,在推进部门信息资源共建共享过程中建立监督检查机制。 最后,对我国建立数据资源部门共建共享机制提出如下几点建议:一是尽快成立国家大数据管理局那样的机构;二是对建立大数据资源部门共建共享机制尽快立法,同时尽快出台国家层面的移动互联网信息安全法律,还要依法对数据进行挖掘和分析,同时完善安全体系;三是出台相关数据开放标准规范,指导各省开展数据资源开放工作。 本文作者:佚名 来源:51CTO

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册